diff --git a/Documentation/admin-guide/media/bcm2835-isp.rst b/Documentation/admin-guide/media/bcm2835-isp.rst
new file mode 100644
index 000000000000..e1c19f78435e
--- /dev/null
+++ b/Documentation/admin-guide/media/bcm2835-isp.rst
@@ -0,0 +1,127 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+BCM2835 ISP Driver
+==================
+
+Introduction
+------------
+
+The BCM2835 Image Sensor Pipeline (ISP) is a fixed function hardware pipeline
+for performing image processing operations.  Images are fed to the input
+of the ISP through memory frame buffers.  These images may be in various YUV,
+RGB, or Bayer formats.  A typical use case would have Bayer images obtained from
+an image sensor by the BCM2835 Unicam peripheral, written to a memory
+frame buffer, and finally fed into the input of the ISP.  Two concurrent output
+images may be generated in YUV or RGB format at different resolutions.
+Statistics output is also generated for Bayer input images.
+
+The bcm2835-isp driver exposes the following media pads as V4L2 device nodes:
+
+.. tabularcolumns:: |l|l|l|l|
+
+.. cssclass: longtable
+
+.. flat-table::
+
+    * - *Pad*
+      - *Direction*
+      - *Purpose*
+      - *Formats*
+
+    * - "bcm2835-isp0-output0"
+      - sink
+      - Accepts Bayer, RGB or YUV format frame buffers as input to the ISP HW
+        pipeline.
+      - :ref:`RAW8 <V4L2-PIX-FMT-SRGGB8>`,
+        :ref:`RAW10P <V4L2-PIX-FMT-SRGGB10P>`,
+        :ref:`RAW12P <V4L2-PIX-FMT-SRGGB12P>`,
+        :ref:`RAW14P <V4L2-PIX-FMT-SRGGB14P>`,
+        :ref:`RAW16 <V4L2-PIX-FMT-SRGGB16>`,
+        :ref:`RGB24/BGR24 <V4L2-PIX-FMT-RGB24>`,
+        :ref:`YUYV <V4L2-PIX-FMT-YUYV>`,
+        :ref:`YVYU <V4L2-PIX-FMT-YVYU>`,
+        :ref:`UYVY <V4L2-PIX-FMT-UYVY>`,
+        :ref:`VYUY <V4L2-PIX-FMT-VYUY>`,
+        :ref:`YUV420/YVU420 <V4L2-PIX-FMT-YUV420>`
+
+    * - "bcm2835-isp0-capture1"
+      - source
+      - High resolution YUV or RGB processed output from the ISP.
+      - :ref:`RGB565 <V4L2-PIX-FMT-RGB565>`,
+        :ref:`RGB24/BGR24 <V4L2-PIX-FMT-RGB24>`,
+        :ref:`ABGR32 <V4L2-PIX-FMT-ABGR32>`,
+        :ref:`YUYV <V4L2-PIX-FMT-YUYV>`,
+        :ref:`YVYU <V4L2-PIX-FMT-YVYU>`,
+        :ref:`UYVY <V4L2-PIX-FMT-UYVY>`,
+        :ref:`VYUY <V4L2-PIX-FMT-VYUY>`.
+        :ref:`YUV420/YVU420 <V4L2-PIX-FMT-YUV420>`,
+        :ref:`NV12/NV21 <V4L2-PIX-FMT-NV12>`,
+
+    * - "bcm2835-isp0-capture2"
+      - source
+      - Low resolution YUV processed output from the ISP. The output of
+        this pad cannot have a resolution larger than the "bcm2835-isp0-capture1" pad in any dimension.
+      - :ref:`YUYV <V4L2-PIX-FMT-YUYV>`,
+        :ref:`YVYU <V4L2-PIX-FMT-YVYU>`,
+        :ref:`UYVY <V4L2-PIX-FMT-UYVY>`,
+        :ref:`VYUY <V4L2-PIX-FMT-VYUY>`.
+        :ref:`YUV420/YVU420 <V4L2-PIX-FMT-YUV420>`,
+        :ref:`NV12/NV21 <V4L2-PIX-FMT-NV12>`,
+
+    * - "bcm2835-isp0-capture1"
+      - source
+      - Image statistics calculated from the input image provided on the
+        "bcm2835-isp0-output0" pad.  Statistics are only available for Bayer
+        format input images.
+      - :ref:`v4l2-meta-fmt-bcm2835-isp-stats`.
+
+Pipeline Configuration
+----------------------
+
+The ISP pipeline can be configure through user-space by calling
+:ref:`VIDIOC_S_EXT_CTRLS <VIDIOC_G_EXT_CTRLS>` on the “bcm2835-isp0-output0”
+node with the appropriate parameters as shown in the table below.
+
+.. tabularcolumns:: |p{2cm}|p{5.0cm}|
+
+.. cssclass: longtable
+
+.. flat-table::
+
+    * - *id*
+      - *Parameter*
+
+    * - ``V4L2_CID_USER_BCM2835_ISP_CC_MATRIX``
+      - struct :c:type:`bcm2835_isp_custom_ccm`
+
+    * - ``V4L2_CID_USER_BCM2835_ISP_LENS_SHADING``
+      - struct :c:type:`bcm2835_isp_lens_shading`
+
+    * - ``V4L2_CID_USER_BCM2835_ISP_BLACK_LEVEL``
+      - struct :c:type:`bcm2835_isp_black_level`
+
+    * - ``V4L2_CID_USER_BCM2835_ISP_GEQ``
+      - struct :c:type:`bcm2835_isp_geq`
+
+    * - ``V4L2_CID_USER_BCM2835_ISP_GAMMA``
+      - struct :c:type:`bcm2835_isp_gamma`
+
+    * - ``V4L2_CID_USER_BCM2835_ISP_DENOISE``
+      - struct :c:type:`bcm2835_isp_denoise`
+
+    * - ``V4L2_CID_USER_BCM2835_ISP_SHARPEN``
+      - struct :c:type:`bcm2835_isp_sharpen`
+
+    * - ``V4L2_CID_USER_BCM2835_ISP_DPC``
+      - struct :c:type:`bcm2835_isp_dpc`
+
+++++++++++++++++++++++++
+Configuration Parameters
+++++++++++++++++++++++++
+
+.. kernel-doc:: include/uapi/linux/bcm2835-isp.h
+   :functions: bcm2835_isp_rational bcm2835_isp_ccm bcm2835_isp_custom_ccm
+                bcm2835_isp_gain_format bcm2835_isp_lens_shading
+                bcm2835_isp_black_level bcm2835_isp_geq bcm2835_isp_gamma
+                bcm2835_isp_denoise bcm2835_isp_sharpen
+                bcm2835_isp_dpc_mode bcm2835_isp_dpc
diff --git a/Documentation/devicetree/bindings/clock/gpio-gate-clock.yaml b/Documentation/devicetree/bindings/clock/gpio-gate-clock.yaml
index d09d0e3f0c6e..a5fa40048bed 100644
--- a/Documentation/devicetree/bindings/clock/gpio-gate-clock.yaml
+++ b/Documentation/devicetree/bindings/clock/gpio-gate-clock.yaml
@@ -11,7 +11,15 @@ maintainers:
 
 properties:
   compatible:
-    const: gpio-gate-clock
+    enum:
+      - gpio-gate-clock
+      - gpio-gate-clock-releasing
+    description: |
+      Use "gpio-gate-clock" for normal operation where the GPIO is held for the
+      lifetime of the clock. Use "gpio-gate-clock-releasing" for power-sensitive
+      applications where the GPIO should be acquired only when the clock is
+      enabled and released when disabled, allowing shared regulators to be
+      powered down.
 
   clocks:
     maxItems: 1
@@ -40,3 +48,14 @@ examples:
         #clock-cells = <0>;
         enable-gpios = <&gpio 1 GPIO_ACTIVE_HIGH>;
     };
+
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    /* Power-sensitive clock that releases GPIO when disabled */
+    clock {
+        compatible = "gpio-gate-clock-releasing";
+        clocks = <&parentclk>;
+        #clock-cells = <0>;
+        enable-gpios = <&gpio 1 GPIO_ACTIVE_HIGH>;
+    };
diff --git a/Documentation/devicetree/bindings/display/brcm,bcm2711-hdmi.yaml b/Documentation/devicetree/bindings/display/brcm,bcm2711-hdmi.yaml
index 5b35adf34c7b..6d11f5955b51 100644
--- a/Documentation/devicetree/bindings/display/brcm,bcm2711-hdmi.yaml
+++ b/Documentation/devicetree/bindings/display/brcm,bcm2711-hdmi.yaml
@@ -14,6 +14,8 @@ properties:
     enum:
       - brcm,bcm2711-hdmi0
       - brcm,bcm2711-hdmi1
+      - brcm,bcm2712-hdmi0
+      - brcm,bcm2712-hdmi1
 
   reg:
     items:
diff --git a/Documentation/devicetree/bindings/display/brcm,bcm2835-dsi0.yaml b/Documentation/devicetree/bindings/display/brcm,bcm2835-dsi0.yaml
index c8b2459d64f6..af638b224619 100644
--- a/Documentation/devicetree/bindings/display/brcm,bcm2835-dsi0.yaml
+++ b/Documentation/devicetree/bindings/display/brcm,bcm2835-dsi0.yaml
@@ -21,6 +21,7 @@ properties:
       - brcm,bcm2711-dsi1
       - brcm,bcm2835-dsi0
       - brcm,bcm2835-dsi1
+      - brcm,bcm2711-dsi1
 
   reg:
     maxItems: 1
diff --git a/Documentation/devicetree/bindings/display/brcm,bcm2835-hvs.yaml b/Documentation/devicetree/bindings/display/brcm,bcm2835-hvs.yaml
index 2e8566f47e63..f91c9dce2a44 100644
--- a/Documentation/devicetree/bindings/display/brcm,bcm2835-hvs.yaml
+++ b/Documentation/devicetree/bindings/display/brcm,bcm2835-hvs.yaml
@@ -13,6 +13,7 @@ properties:
   compatible:
     enum:
       - brcm,bcm2711-hvs
+      - brcm,bcm2712-hvs
       - brcm,bcm2835-hvs
 
   reg:
@@ -36,7 +37,9 @@ if:
   properties:
     compatible:
       contains:
-        const: brcm,bcm2711-hvs
+        enum:
+          - brcm,bcm2711-hvs
+          - brcm,bcm2712-hvs
 
 then:
   required:
diff --git a/Documentation/devicetree/bindings/display/brcm,bcm2835-pixelvalve0.yaml b/Documentation/devicetree/bindings/display/brcm,bcm2835-pixelvalve0.yaml
index 4e1ba03f6477..6b5b1d3fbc0b 100644
--- a/Documentation/devicetree/bindings/display/brcm,bcm2835-pixelvalve0.yaml
+++ b/Documentation/devicetree/bindings/display/brcm,bcm2835-pixelvalve0.yaml
@@ -20,6 +20,9 @@ properties:
       - brcm,bcm2711-pixelvalve2
       - brcm,bcm2711-pixelvalve3
       - brcm,bcm2711-pixelvalve4
+      - brcm,bcm2712-pixelvalve0
+      - brcm,bcm2712-pixelvalve1
+      - brcm,bcm2712-pixelvalve2
 
   reg:
     maxItems: 1
diff --git a/Documentation/devicetree/bindings/display/brcm,bcm2835-txp.yaml b/Documentation/devicetree/bindings/display/brcm,bcm2835-txp.yaml
index bb186197e471..16f45afd2bad 100644
--- a/Documentation/devicetree/bindings/display/brcm,bcm2835-txp.yaml
+++ b/Documentation/devicetree/bindings/display/brcm,bcm2835-txp.yaml
@@ -11,7 +11,10 @@ maintainers:
 
 properties:
   compatible:
-    const: brcm,bcm2835-txp
+    enum:
+      - brcm,bcm2712-mop
+      - brcm,bcm2712-moplet
+      - brcm,bcm2835-txp
 
   reg:
     maxItems: 1
diff --git a/Documentation/devicetree/bindings/display/brcm,bcm2835-vc4.yaml b/Documentation/devicetree/bindings/display/brcm,bcm2835-vc4.yaml
index 49a5e041aa49..2aa9d5d2afff 100644
--- a/Documentation/devicetree/bindings/display/brcm,bcm2835-vc4.yaml
+++ b/Documentation/devicetree/bindings/display/brcm,bcm2835-vc4.yaml
@@ -18,6 +18,7 @@ properties:
   compatible:
     enum:
       - brcm,bcm2711-vc5
+      - brcm,bcm2712-vc6
       - brcm,bcm2835-vc4
       - brcm,cygnus-vc4
 
diff --git a/Documentation/devicetree/bindings/display/panel/ilitek,ili9881c.yaml b/Documentation/devicetree/bindings/display/panel/ilitek,ili9881c.yaml
index baf5dfe5f5eb..9edb85103c86 100644
--- a/Documentation/devicetree/bindings/display/panel/ilitek,ili9881c.yaml
+++ b/Documentation/devicetree/bindings/display/panel/ilitek,ili9881c.yaml
@@ -22,6 +22,8 @@ properties:
           - startek,kd050hdfia020
           - tdo,tl050hdv35
           - wanchanglong,w552946aba
+          - raspberrypi,dsi-5inch
+          - raspberrypi,dsi-7inch
       - const: ilitek,ili9881c
 
   reg:
diff --git a/Documentation/devicetree/bindings/display/panel/panel-dsi.yaml b/Documentation/devicetree/bindings/display/panel/panel-dsi.yaml
new file mode 100644
index 000000000000..0576541d9567
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/panel/panel-dsi.yaml
@@ -0,0 +1,118 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/panel/panel-dsi.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Generic MIPI DSI Panel
+
+maintainers:
+  - Timon Skerutsch <kernel@diodes-delight.com>
+
+allOf:
+  - $ref: panel-common.yaml#
+
+properties:
+  compatible:
+    description:
+      Shall contain a panel specific compatible and "panel-dsi"
+      in that order.
+    items:
+      - {}
+      - const: panel-dsi
+
+  dsi-color-format:
+    description: |
+      The color format used by the panel. Only DSI supported formats are allowed.
+    enum:
+      - RGB888
+      - RGB666
+      - RGB666_PACKED
+      - RGB565
+
+  port:
+    $ref: /schemas/graph.yaml#/$defs/port-base
+    unevaluatedProperties: false
+    description:
+      Panel MIPI DSI input
+
+    properties:
+      endpoint:
+        $ref: /schemas/media/video-interfaces.yaml#
+        unevaluatedProperties: false
+
+        properties:
+          data-lanes: true
+
+        required:
+          - data-lanes
+
+  mode:
+    description: |
+      DSI mode flags. See DSI Specs for details.
+      These are driver independent features of the DSI bus.
+    items:
+      - const: MODE_VIDEO
+      - const: MODE_VIDEO_BURST
+      - const: MODE_VIDEO_SYNC_PULSE
+      - const: MODE_VIDEO_AUTO_VERT
+      - const: MODE_VIDEO_HSE
+      - const: MODE_VIDEO_NO_HFP
+      - const: MODE_VIDEO_NO_HBP
+      - const: MODE_VIDEO_NO_HSA
+      - const: MODE_VSYNC_FLUSH
+      - const: MODE_NO_EOT_PACKET
+      - const: CLOCK_NON_CONTINUOUS
+      - const: MODE_LPM
+      - const: HS_PKT_END_ALIGNED
+
+  reg: true
+  backlight: true
+  enable-gpios: true
+  width-mm: true
+  height-mm: true
+  panel-timing: true
+  power-supply: true
+  reset-gpios: true
+  ddc-i2c-bus: true
+
+required:
+  - panel-timing
+  - reg
+  - power-supply
+  - dsi-color-format
+  - port
+
+additionalProperties: false
+
+examples:
+  - |
+    panel {
+        compatible = "panel-mfgr,generic-dsi-panel","panel-dsi";
+        power-supply = <&vcc_supply>;
+        backlight = <&backlight>;
+        dsi-color-format = "RGB888";
+        reg = <0>;
+        mode = "MODE_VIDEO", "MODE_VIDEO_BURST", "MODE_NO_EOT_PACKET";
+
+        port {
+            panel_dsi_port: endpoint {
+                data-lanes = <1 2>;
+                remote-endpoint = <&dsi_out>;
+            };
+        };
+
+        panel-timing {
+            clock-frequency = <9200000>;
+            hactive = <800>;
+            vactive = <480>;
+            hfront-porch = <8>;
+            hback-porch = <4>;
+            hsync-len = <41>;
+            vback-porch = <2>;
+            vfront-porch = <4>;
+            vsync-len = <10>;
+        };
+    };
+
+...
diff --git a/Documentation/devicetree/bindings/display/panel/panel-simple.yaml b/Documentation/devicetree/bindings/display/panel/panel-simple.yaml
index b89e39790579..2f1c39e3afdd 100644
--- a/Documentation/devicetree/bindings/display/panel/panel-simple.yaml
+++ b/Documentation/devicetree/bindings/display/panel/panel-simple.yaml
@@ -142,6 +142,8 @@ properties:
       - frida,frd350h54004
         # FriendlyELEC HD702E 800x1280 LCD panel
       - friendlyarm,hd702e
+        # Geekworm MZP280 2.8" 480x640 LCD panel with capacitive touch
+      - geekworm,mzp280
         # GiantPlus GPG48273QS5 4.3" (480x272) WQVGA TFT LCD panel
       - giantplus,gpg48273qs5
         # GiantPlus GPM940B0 3.0" QVGA TFT LCD panel
@@ -154,6 +156,8 @@ properties:
       - hit,tx23d38vm0caa
         # Innolux AT043TN24 4.3" WQVGA TFT LCD panel
       - innolux,at043tn24
+        # Innolux AT056tN53V1 5.6" VGA (640x480) TFT LCD panel
+      - innolux,at056tn53v1
         # Innolux AT070TN92 7.0" WQVGA TFT LCD panel
       - innolux,at070tn92
         # Innolux G070ACE-L01 7" WVGA (800x480) TFT LCD panel
diff --git a/Documentation/devicetree/bindings/gpu/brcm,bcm-v3d.yaml b/Documentation/devicetree/bindings/gpu/brcm,bcm-v3d.yaml
index dc078ceeca9a..edac0ee17722 100644
--- a/Documentation/devicetree/bindings/gpu/brcm,bcm-v3d.yaml
+++ b/Documentation/devicetree/bindings/gpu/brcm,bcm-v3d.yaml
@@ -7,7 +7,7 @@ $schema: http://devicetree.org/meta-schemas/core.yaml#
 title: Broadcom V3D GPU
 
 maintainers:
-  - Eric Anholt <eric@anholt.net>
+  - Maíra Canal <mcanal@igalia.com>
   - Nicolas Saenz Julienne <nsaenzjulienne@suse.de>
 
 properties:
@@ -16,26 +16,19 @@ properties:
 
   compatible:
     enum:
+      - brcm,2712-v3d
       - brcm,2711-v3d
       - brcm,2712-v3d
       - brcm,7268-v3d
       - brcm,7278-v3d
 
   reg:
-    items:
-      - description: hub register (required)
-      - description: core0 register (required)
-      - description: GCA cache controller register (if GCA controller present)
-      - description: bridge register (if no external reset controller)
     minItems: 2
+    maxItems: 4
 
   reg-names:
-    items:
-      - const: hub
-      - const: core0
-      - enum: [ bridge, gca ]
-      - enum: [ bridge, gca ]
     minItems: 2
+    maxItems: 4
 
   interrupts:
     items:
@@ -58,6 +51,69 @@ required:
   - reg-names
   - interrupts
 
+allOf:
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - brcm,2711-v3d
+              - brcm,7278-v3d
+    then:
+      properties:
+        reg:
+          items:
+            - description: hub register (required)
+            - description: core0 register (required)
+            - description: bridge register (if no external reset controller)
+        reg-names:
+          items:
+            - const: hub
+            - const: core0
+            - const: bridge
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: brcm,2712-v3d
+    then:
+      properties:
+        reg:
+          items:
+            - description: hub register (required)
+            - description: core0 register (required)
+            - description: SMS register (required)
+            - description: bridge register (if no external reset controller)
+          minItems: 3
+        reg-names:
+          items:
+            - const: hub
+            - const: core0
+            - const: sms
+            - const: bridge
+          minItems: 3
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: brcm,7268-v3d
+    then:
+      properties:
+        reg:
+          items:
+            - description: hub register (required)
+            - description: core0 register (required)
+            - description: GCA cache controller register (required)
+            - description: bridge register (if no external reset controller)
+          minItems: 3
+        reg-names:
+          items:
+            - const: hub
+            - const: core0
+            - enum: [ bridge, gca ]
+            - enum: [ bridge, gca ]
+          minItems: 3
+
 additionalProperties: false
 
 examples:
diff --git a/Documentation/devicetree/bindings/hwmon/microchip,emc2305.yaml b/Documentation/devicetree/bindings/hwmon/microchip,emc2305.yaml
new file mode 100644
index 000000000000..efdc3cecb03d
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/microchip,emc2305.yaml
@@ -0,0 +1,54 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+
+$id: http://devicetree.org/schemas/hwmon/emc2305.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Microchip EMC230[1|2|3|5] RPM-based PWM Fan Speed Controller
+
+properties:
+  compatible:
+    enum:
+      - microchip,emc2305
+      - microchip,emc2301
+  emc2305,pwm-min:
+    description:
+      Min pwm of emc2305
+    maxItems: 1
+  emc2305,pwm-max:
+    description:
+      Max pwm of emc2305
+    maxItems: 1
+  emc2305,pwm-channel:
+    description:
+      Max number of pwm channels
+    maxItems: 1
+  emcs205,max-state:
+    description:
+    maxItems: 1
+  emc2305,cooling-levels:
+    description:
+      Quantity of cooling level state.
+    maxItems: 1
+
+required:
+  - compatible
+
+optional:
+  - emc2305,min-pwm
+  - emc2305,max-pwm
+  - emc2305,pwm-channels
+  - emc2305,cooling-levels
+
+additionalProperties: false
+
+examples:
+  - |
+    fan {
+        compatible = "microchip,emc2305";
+        emc2305,pwm-min = <0>;
+        emc2305,pwm-max = <255>;
+        emc2305,pwm-channel = <5>
+        emc2305,cooling-levels = <10>;
+    };
diff --git a/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm2712-msix.yaml b/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm2712-msix.yaml
new file mode 100644
index 000000000000..c84614663b5d
--- /dev/null
+++ b/Documentation/devicetree/bindings/interrupt-controller/brcm,bcm2712-msix.yaml
@@ -0,0 +1,60 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/interrupt-controller/brcm,bcm2712-msix.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Broadcom bcm2712 MSI-X Interrupt Peripheral support
+
+maintainers:
+  - Stanimir Varbanov <svarbanov@suse.de>
+
+description:
+  This interrupt controller is used to provide interrupt vectors to the
+  generic interrupt controller (GIC) on bcm2712. It will be used as
+  external MSI-X controller for PCIe root complex.
+
+allOf:
+  - $ref: /schemas/interrupt-controller/msi-controller.yaml#
+
+properties:
+  compatible:
+    const: brcm,bcm2712-mip
+
+  reg:
+    items:
+      - description: Base register address
+      - description: PCIe message address
+
+  "#msi-cells":
+    const: 0
+
+  brcm,msi-offset:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: Shift the allocated MSI's.
+
+unevaluatedProperties: false
+
+required:
+  - compatible
+  - reg
+  - msi-controller
+  - msi-ranges
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+    axi {
+        #address-cells = <2>;
+        #size-cells = <2>;
+
+        msi-controller@1000130000 {
+            compatible = "brcm,bcm2712-mip";
+            reg = <0x10 0x00130000 0x00 0xc0>,
+                  <0xff 0xfffff000 0x00 0x1000>;
+            msi-controller;
+            #msi-cells = <0>;
+            msi-ranges = <&gicv2 GIC_SPI 128 IRQ_TYPE_EDGE_RISING 64>;
+        };
+    };
diff --git a/Documentation/devicetree/bindings/media/bcm2835-unicam.txt b/Documentation/devicetree/bindings/media/bcm2835-unicam.txt
new file mode 100644
index 000000000000..164d0377dcd2
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/bcm2835-unicam.txt
@@ -0,0 +1,85 @@
+Broadcom BCM283x Camera Interface (Unicam)
+------------------------------------------
+
+The Unicam block on BCM283x SoCs is the receiver for either
+CSI-2 or CCP2 data from image sensors or similar devices.
+
+The main platform using this SoC is the Raspberry Pi family of boards.
+On the Pi the VideoCore firmware can also control this hardware block,
+and driving it from two different processors will cause issues.
+To avoid this, the firmware checks the device tree configuration
+during boot. If it finds device tree nodes called csi0 or csi1 then
+it will stop the firmware accessing the block, and it can then
+safely be used via the device tree binding.
+
+Required properties:
+===================
+- compatible	: must be "brcm,bcm2835-unicam".
+- reg		: physical base address and length of the register sets for the
+		  device.
+- interrupts	: should contain the IRQ line for this Unicam instance.
+- clocks	: list of clock specifiers, corresponding to entries in
+		  clock-names property.
+- clock-names	: must contain "lp" and "vpu" entries, matching entries in the
+		  clocks property.
+
+Unicam supports a single port node. It should contain one 'port' child node
+with child 'endpoint' node. Please refer to the bindings defined in
+Documentation/devicetree/bindings/media/video-interfaces.txt.
+
+Within the endpoint node the "remote-endpoint" and "data-lanes" properties
+are mandatory.
+Data lane reordering is not supported so the data lanes must be in order,
+starting at 1. The number of data lanes should represent the number of
+usable lanes for the hardware block. That may be limited by either the SoC or
+how the platform presents the interface, and the lower value must be used.
+
+Lane reordering is not supported on the clock lane either, so the optional
+property "clock-lane" will implicitly be <0>.
+Similarly lane inversion is not supported, therefore "lane-polarities" will
+implicitly be <0 0 0 0 0>.
+Neither of these values will be checked.
+
+Example:
+	csi1: csi1@7e801000 {
+		compatible = "brcm,bcm2835-unicam";
+		reg = <0x7e801000 0x800>,
+		      <0x7e802004 0x4>;
+		interrupts = <2 7>;
+		clocks = <&clocks BCM2835_CLOCK_CAM1>,
+			 <&firmware_clocks 4>;
+		clock-names = "lp", "vpu";
+		port {
+			csi1_ep: endpoint {
+				remote-endpoint = <&tc358743_0>;
+				data-lanes = <1 2>;
+			};
+		};
+	};
+
+	i2c0: i2c@7e205000 {
+		tc358743: csi-hdmi-bridge@0f {
+			compatible = "toshiba,tc358743";
+			reg = <0x0f>;
+
+			clocks = <&tc358743_clk>;
+			clock-names = "refclk";
+
+			tc358743_clk: bridge-clk {
+				compatible = "fixed-clock";
+				#clock-cells = <0>;
+				clock-frequency = <27000000>;
+			};
+
+			port {
+				tc358743_0: endpoint {
+					remote-endpoint = <&csi1_ep>;
+					clock-lanes = <0>;
+					data-lanes = <1 2>;
+					clock-noncontinuous;
+					link-frequencies =
+						/bits/ 64 <297000000>;
+				};
+			};
+		};
+	};
diff --git a/Documentation/devicetree/bindings/media/i2c/ad5398.txt b/Documentation/devicetree/bindings/media/i2c/ad5398.txt
new file mode 100644
index 000000000000..446ac9717598
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/ad5398.txt
@@ -0,0 +1,20 @@
+* Analog Devices AD5398 autofocus coil
+
+Required Properties:
+
+  - compatible: Must contain one of:
+		- "adi,ad5398"
+
+  - reg: I2C slave address
+
+  - VANA-supply: supply of voltage for VANA pin
+
+Example:
+
+       ad5398: coil@c {
+               compatible = "adi,ad5398";
+               reg = <0x0c>;
+
+               VANA-supply = <&vaux4>;
+       };
+
diff --git a/Documentation/devicetree/bindings/media/i2c/arducam,64mp.yaml b/Documentation/devicetree/bindings/media/i2c/arducam,64mp.yaml
new file mode 100644
index 000000000000..b71a19782f97
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/arducam,64mp.yaml
@@ -0,0 +1,115 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/arducam,64mp.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Arducam 1/1.7-Inch 64Mpixel CMOS Digital Image Sensor
+
+maintainers:
+  - Lee Jackson <info@arducam.com>
+
+description: |-
+  The Arducam 1/1.7-Inch 64Mpixel CMOS active pixel digital image sensor
+  with an active array size of 9248 x 6944. It is programmable through
+  I2C interface. The I2C address is fixed to 0x1A as per sensor data sheet.
+  Image data is sent through MIPI CSI-2, which can be configured for operation
+  with either 2 or 4 data lanes.
+
+properties:
+  compatible:
+    const: arducam,64mp
+
+  reg:
+    description: I2C device address
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  VDIG-supply:
+    description:
+      Digital I/O voltage supply, 1.05 volts
+
+  VANA-supply:
+    description:
+      Analog voltage supply, 2.8 volts
+
+  VDDL-supply:
+    description:
+      Digital core voltage supply, 1.8 volts
+
+  reset-gpios:
+    description: |-
+      Reference to the GPIO connected to the xclr pin, if any.
+      Must be released (set high) after all supplies and INCK are applied.
+
+  # See ../video-interfaces.txt for more details
+  port:
+    type: object
+    properties:
+      endpoint:
+        type: object
+        properties:
+          data-lanes:
+            description: |-
+              The sensor supports either two-lane, or four-lane operation.
+              For two-lane operation the property must be set to <1 2>.
+            anyOf:
+              - items:
+                  - const: 1
+                  - const: 2
+              - items:
+                  - const: 1
+                  - const: 2
+                  - const: 3
+                  - const: 4
+
+          clock-noncontinuous: true
+
+          link-frequencies:
+            allOf:
+              - $ref: /schemas/types.yaml#/definitions/uint64-array
+            description:
+              Allowed data bus frequencies.
+
+        required:
+          - link-frequencies
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - VANA-supply
+  - VDIG-supply
+  - VDDL-supply
+  - port
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c0 {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        arducam_64mp: sensor@1a {
+            compatible = "arducam,64mp";
+            reg = <0x1a>;
+            clocks = <&arducam_64mp_clk>;
+            VANA-supply = <&arducam_64mp_vana>;   /* 2.8v */
+            VDIG-supply = <&arducam_64mp_vdig>;   /* 1.05v */
+            VDDL-supply = <&arducam_64mp_vddl>;   /* 1.8v */
+
+            port {
+                arducam_64mp_0: endpoint {
+                    remote-endpoint = <&csi1_ep>;
+                    data-lanes = <1 2>;
+                    clock-noncontinuous;
+                    link-frequencies = /bits/ 64 <456000000>;
+                };
+            };
+        };
+    };
+
+...
diff --git a/Documentation/devicetree/bindings/media/i2c/arducam-pivariety.yaml b/Documentation/devicetree/bindings/media/i2c/arducam-pivariety.yaml
new file mode 100644
index 000000000000..92bf4ff32eb4
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/arducam-pivariety.yaml
@@ -0,0 +1,112 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/arducam-pivariety.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Arducam Pivariety Series CMOS Digital Image Sensor
+
+maintainers:
+  - Lee Jackson <info@arducam.com>
+
+description: |-
+  Arducam Pivariety series cameras make compatibility layers for various CMOS
+  sensors and provide a unified command interface. It is programmable through
+  I2C interface. The I2C address is fixed to 0x0C. Image data is sent through
+  MIPI CSI-2, which is configured as either 1, 2 or 4 data lanes.
+
+properties:
+  compatible:
+    const: arducam,arducam-pivariety
+
+  reg:
+    description: I2C device address
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  VDIG-supply:
+    description:
+      Digital I/O voltage supply, 1.05 volts
+
+  VANA-supply:
+    description:
+      Analog voltage supply, 2.8 volts
+
+  VDDL-supply:
+    description:
+      Digital core voltage supply, 1.8 volts
+
+  reset-gpios:
+    description: |-
+      Reference to the GPIO connected to the xclr pin, if any.
+      Must be released (set high) after all supplies and INCK are applied.
+
+  # See ../video-interfaces.txt for more details
+  port:
+    type: object
+    properties:
+      endpoint:
+        type: object
+        properties:
+          data-lanes:
+            description: |-
+              The sensor supports either two-lane, or four-lane operation.
+              For two-lane operation the property must be set to <1 2>.
+            items:
+              - const: 1
+              - const: 2
+
+          clock-noncontinuous:
+            type: boolean
+            description: |-
+              MIPI CSI-2 clock is non-continuous if this property is present,
+              otherwise it's continuous.
+
+          link-frequencies:
+            allOf:
+              - $ref: /schemas/types.yaml#/definitions/uint64-array
+            description:
+              Allowed data bus frequencies.
+
+        required:
+          - link-frequencies
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - VANA-supply
+  - VDIG-supply
+  - VDDL-supply
+  - port
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c0 {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        arducam_pivariety: sensor@0c {
+            compatible = "arducam,arducam-pivariety";
+            reg = <0x0c>;
+            clocks = <&arducam_pivariety_clk>;
+            VANA-supply = <&arducam_pivariety_vana>;   /* 2.8v */
+            VDIG-supply = <&arducam_pivariety_vdig>;   /* 1.05v */
+            VDDL-supply = <&arducam_pivariety_vddl>;   /* 1.8v */
+
+            port {
+                arducam_pivariety_0: endpoint {
+                    remote-endpoint = <&csi1_ep>;
+                    data-lanes = <1 2>;
+                    clock-noncontinuous;
+                    link-frequencies = /bits/ 64 <493500000>;
+                };
+            };
+        };
+    };
+
+...
diff --git a/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml b/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml
index aae246ca3fcf..6de07543e973 100644
--- a/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml
+++ b/Documentation/devicetree/bindings/media/i2c/dongwoon,dw9807-vcm.yaml
@@ -5,22 +5,32 @@
 $id: http://devicetree.org/schemas/media/i2c/dongwoon,dw9807-vcm.yaml#
 $schema: http://devicetree.org/meta-schemas/core.yaml#
 
-title: Dongwoon Anatech DW9807 voice coil lens driver
+title: Dongwoon Anatech DW9807 and DW9817 voice coil lens driver
 
 maintainers:
   - Sakari Ailus <sakari.ailus@linux.intel.com>
 
 description: |
   DW9807 is a 10-bit DAC with current sink capability. It is intended for
-  controlling voice coil lenses.
+  controlling voice coil lenses. The output drive is 0-100mA.
+  DW9817 is very similar as a 10-bit DAC with current sink capability,
+  however the output drive is a bidirection -100 to +100mA.
+
 
 properties:
   compatible:
-    const: dongwoon,dw9807-vcm
+    items:
+      - enum:
+          - dongwoon,dw9807-vcm
+          - dongwoon,dw9817-vcm
 
   reg:
     maxItems: 1
 
+  VDD-supply:
+    description:
+      Definition of the regulator used as VDD power supply to the driver.
+
 required:
   - compatible
   - reg
diff --git a/Documentation/devicetree/bindings/media/i2c/imx378.yaml b/Documentation/devicetree/bindings/media/i2c/imx378.yaml
new file mode 100644
index 000000000000..f832b4bfab93
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/imx378.yaml
@@ -0,0 +1,113 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/imx378.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sony 1/2.3-Inch 12Mpixel CMOS Digital Image Sensor
+
+maintainers:
+  - Naushir Patuck <naush@raspberypi.com>
+
+description: |-
+  The Sony IMX378 is a 1/2.3-inch CMOS active pixel digital image sensor
+  with an active array size of 4056H x 3040V. It is programmable through
+  I2C interface. The I2C address is fixed to 0x1A as per sensor data sheet.
+  Image data is sent through MIPI CSI-2, which is configured as either 2 or
+  4 data lanes.
+
+properties:
+  compatible:
+    const: sony,imx378
+
+  reg:
+    description: I2C device address
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  VDIG-supply:
+    description:
+      Digital I/O voltage supply, 1.05 volts
+
+  VANA-supply:
+    description:
+      Analog voltage supply, 2.8 volts
+
+  VDDL-supply:
+    description:
+      Digital core voltage supply, 1.8 volts
+
+  reset-gpios:
+    description: |-
+      Reference to the GPIO connected to the xclr pin, if any.
+      Must be released (set high) after all supplies and INCK are applied.
+
+  # See ../video-interfaces.txt for more details
+  port:
+    type: object
+    properties:
+      endpoint:
+        type: object
+        properties:
+          data-lanes:
+            description: |-
+              The sensor supports either two-lane, or four-lane operation.
+              For two-lane operation the property must be set to <1 2>.
+            items:
+              - const: 1
+              - const: 2
+
+          clock-noncontinuous:
+            type: boolean
+            description: |-
+              MIPI CSI-2 clock is non-continuous if this property is present,
+              otherwise it's continuous.
+
+          link-frequencies:
+            allOf:
+              - $ref: /schemas/types.yaml#/definitions/uint64-array
+            description:
+              Allowed data bus frequencies.
+
+        required:
+          - link-frequencies
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - VANA-supply
+  - VDIG-supply
+  - VDDL-supply
+  - port
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c0 {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        imx378: sensor@10 {
+            compatible = "sony,imx378";
+            reg = <0x1a>;
+            clocks = <&imx378_clk>;
+            VANA-supply = <&imx378_vana>;   /* 2.8v */
+            VDIG-supply = <&imx378_vdig>;   /* 1.05v */
+            VDDL-supply = <&imx378_vddl>;   /* 1.8v */
+
+            port {
+                imx378_0: endpoint {
+                    remote-endpoint = <&csi1_ep>;
+                    data-lanes = <1 2>;
+                    clock-noncontinuous;
+                    link-frequencies = /bits/ 64 <450000000>;
+                };
+            };
+        };
+    };
+
+...
diff --git a/Documentation/devicetree/bindings/media/i2c/imx477.yaml b/Documentation/devicetree/bindings/media/i2c/imx477.yaml
new file mode 100644
index 000000000000..0994e13e67f6
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/imx477.yaml
@@ -0,0 +1,113 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/imx477.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sony 1/2.3-Inch 12Mpixel CMOS Digital Image Sensor
+
+maintainers:
+  - Naushir Patuck <naush@raspberypi.com>
+
+description: |-
+  The Sony IMX477 is a 1/2.3-inch CMOS active pixel digital image sensor
+  with an active array size of 4056H x 3040V. It is programmable through
+  I2C interface. The I2C address is fixed to 0x1A as per sensor data sheet.
+  Image data is sent through MIPI CSI-2, which is configured as either 2 or
+  4 data lanes.
+
+properties:
+  compatible:
+    const: sony,imx477
+
+  reg:
+    description: I2C device address
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  VDIG-supply:
+    description:
+      Digital I/O voltage supply, 1.05 volts
+
+  VANA-supply:
+    description:
+      Analog voltage supply, 2.8 volts
+
+  VDDL-supply:
+    description:
+      Digital core voltage supply, 1.8 volts
+
+  reset-gpios:
+    description: |-
+      Reference to the GPIO connected to the xclr pin, if any.
+      Must be released (set high) after all all supplies and INCK are applied.
+
+  # See ../video-interfaces.txt for more details
+  port:
+    type: object
+    properties:
+      endpoint:
+        type: object
+        properties:
+          data-lanes:
+            description: |-
+              The sensor supports either two-lane, or four-lane operation.
+              For two-lane operation the property must be set to <1 2>.
+            items:
+              - const: 1
+              - const: 2
+
+          clock-noncontinuous:
+            type: boolean
+            description: |-
+              MIPI CSI-2 clock is non-continuous if this property is present,
+              otherwise it's continuous.
+
+          link-frequencies:
+            allOf:
+              - $ref: /schemas/types.yaml#/definitions/uint64-array
+            description:
+              Allowed data bus frequencies.
+
+        required:
+          - link-frequencies
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - VANA-supply
+  - VDIG-supply
+  - VDDL-supply
+  - port
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c0 {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        imx477: sensor@10 {
+            compatible = "sony,imx477";
+            reg = <0x1a>;
+            clocks = <&imx477_clk>;
+            VANA-supply = <&imx477_vana>;   /* 2.8v */
+            VDIG-supply = <&imx477_vdig>;   /* 1.05v */
+            VDDL-supply = <&imx477_vddl>;   /* 1.8v */
+
+            port {
+                imx477_0: endpoint {
+                    remote-endpoint = <&csi1_ep>;
+                    data-lanes = <1 2>;
+                    clock-noncontinuous;
+                    link-frequencies = /bits/ 64 <450000000>;
+                };
+            };
+        };
+    };
+
+...
diff --git a/Documentation/devicetree/bindings/media/i2c/imx519.yaml b/Documentation/devicetree/bindings/media/i2c/imx519.yaml
new file mode 100644
index 000000000000..717230a21764
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/imx519.yaml
@@ -0,0 +1,113 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/imx519.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sony 1/2.5-Inch 16Mpixel CMOS Digital Image Sensor
+
+maintainers:
+  - Lee Jackson <info@arducam.com>
+
+description: |-
+  The Sony IMX519 is a 1/2.5-inch CMOS active pixel digital image sensor
+  with an active array size of 4656H x 3496V. It is programmable through
+  I2C interface. The I2C address is fixed to 0x1A as per sensor data sheet.
+  Image data is sent through MIPI CSI-2, which is configured as either 2 or
+  4 data lanes.
+
+properties:
+  compatible:
+    const: sony,imx519
+
+  reg:
+    description: I2C device address
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  VDIG-supply:
+    description:
+      Digital I/O voltage supply, 1.05 volts
+
+  VANA-supply:
+    description:
+      Analog voltage supply, 2.8 volts
+
+  VDDL-supply:
+    description:
+      Digital core voltage supply, 1.8 volts
+
+  reset-gpios:
+    description: |-
+      Reference to the GPIO connected to the xclr pin, if any.
+      Must be released (set high) after all supplies and INCK are applied.
+
+  # See ../video-interfaces.txt for more details
+  port:
+    type: object
+    properties:
+      endpoint:
+        type: object
+        properties:
+          data-lanes:
+            description: |-
+              The sensor supports either two-lane, or four-lane operation.
+              For two-lane operation the property must be set to <1 2>.
+            items:
+              - const: 1
+              - const: 2
+
+          clock-noncontinuous:
+            type: boolean
+            description: |-
+              MIPI CSI-2 clock is non-continuous if this property is present,
+              otherwise it's continuous.
+
+          link-frequencies:
+            allOf:
+              - $ref: /schemas/types.yaml#/definitions/uint64-array
+            description:
+              Allowed data bus frequencies.
+
+        required:
+          - link-frequencies
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - VANA-supply
+  - VDIG-supply
+  - VDDL-supply
+  - port
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c0 {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        imx519: sensor@1a {
+            compatible = "sony,imx519";
+            reg = <0x1a>;
+            clocks = <&imx519_clk>;
+            VANA-supply = <&imx519_vana>;   /* 2.8v */
+            VDIG-supply = <&imx519_vdig>;   /* 1.05v */
+            VDDL-supply = <&imx519_vddl>;   /* 1.8v */
+
+            port {
+                imx519_0: endpoint {
+                    remote-endpoint = <&csi1_ep>;
+                    data-lanes = <1 2>;
+                    clock-noncontinuous;
+                    link-frequencies = /bits/ 64 <493500000>;
+                };
+            };
+        };
+    };
+
+...
diff --git a/Documentation/devicetree/bindings/media/i2c/irs1125.txt b/Documentation/devicetree/bindings/media/i2c/irs1125.txt
new file mode 100644
index 000000000000..25a48028c957
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/irs1125.txt
@@ -0,0 +1,48 @@
+* Infineon irs1125 time of flight sensor
+
+The Infineon irs1125 is a time of flight digital image sensor with
+an active array size of 352H x 286V. It is programmable through I2C
+interface. The I2C address defaults to 0x3D, but can be reconfigured
+to address 0x3C or 0x41 via I2C commands. Image data is sent through
+MIPI CSI-2, which is configured as either 1 or 2 data lanes.
+
+Required Properties:
+- compatible: value should be "infineon,irs1125" for irs1125 sensor
+- reg: I2C bus address of the device
+- clocks: reference to the xclk input clock.
+- pwdn-gpios: reference to the GPIO connected to the reset pin.
+	      This is an active low signal to the iirs1125.
+
+The irs1125 device node should contain one 'port' child node with
+an 'endpoint' subnode. For further reading on port node refer to
+Documentation/devicetree/bindings/media/video-interfaces.txt.
+
+Endpoint node required properties for CSI-2 connection are:
+- remote-endpoint: a phandle to the bus receiver's endpoint node.
+- clock-lanes: should be set to <0> (clock lane on hardware lane 0)
+- data-lanes: should be set to <1> or <1 2> (one or two lane CSI-2
+  supported)
+
+Example:
+	sensor@10 {
+		compatible = "infineon,irs1125";
+		reg = <0x3D>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		clocks = <&irs1125_clk>;
+		pwdn-gpios = <&gpio 5 0>;
+
+		irs1125_clk: camera-clk {
+			compatible = "fixed-clock";
+			#clock-cells = <0>;
+			clock-frequency = <26000000>;
+		};
+
+		port {
+			sensor_out: endpoint {
+				remote-endpoint = <&csiss_in>;
+				clock-lanes = <0>;
+				data-lanes = <1 2>;
+			};
+		};
+	};
diff --git a/Documentation/devicetree/bindings/media/i2c/rohm,bu64754.yaml b/Documentation/devicetree/bindings/media/i2c/rohm,bu64754.yaml
new file mode 100644
index 000000000000..22da4a46bb0c
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/rohm,bu64754.yaml
@@ -0,0 +1,48 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright (C) 2023 Ideas on Board Oy.
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/rohm,bu64754.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ROHM BU64754 Actuator Driver for Camera Autofocus
+
+maintainers:
+  - Kieran Bingham <kieran.bingham@ideasonboard.com>
+
+description: |
+  The BU64754GWZ is an actuator driver IC which can control the actuator
+  position precisely using an internal Hall Sensor.
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - rohm,bu64754
+
+  reg:
+    maxItems: 1
+
+  vdd-supply:
+    description:
+      Definition of the regulator used as VDD power supply to the driver.
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        lens@76 {
+            compatible = "rohm,bu64754";
+            reg = <0x76>;
+            vdd-supply = <&cam1_reg>;
+        };
+    };
+...
diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml
index bf05ca48601a..fa69bd21c8da 100644
--- a/Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml
+++ b/Documentation/devicetree/bindings/media/i2c/sony,imx290.yaml
@@ -33,6 +33,8 @@ properties:
           - sony,imx290lqr # Colour
           - sony,imx290llr # Monochrome
           - sony,imx327lqr # Colour
+          - sony,imx462lqr # Colour
+          - sony,imx462llr # Monochrome
       - const: sony,imx290
         deprecated: true
 
diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx500.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx500.yaml
new file mode 100644
index 000000000000..b8538ae6c80f
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/sony,imx500.yaml
@@ -0,0 +1,132 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/sony,imx500.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sony CMOS Digital Image Sensor and CNN
+
+maintainers:
+  - Raspberry Pi <kernel-list@raspberrypi.com>
+
+description: |-
+  The Sony IMX500 is a stacked 1/2.3-inch CMOS digital image sensor and inbuilt
+  AI processor with an active array CNN (Convolutional Neural Network) inference
+  engine.  The native sensor size is 4056H x 3040V, and the module also contains
+  an in-built ISP for the CNN. The module is programmable through an I2C
+  interface with firmware and neural network uploads being made over SPI. The
+  default I2C address is 0x1A, with an address of 0x10 being selectable via
+  SLASEL. The module also has a second I2C interface available with a fixed
+  address of 0x36.  Image data is sent through MIPI CSI-2, which is configured
+  as either 2 or 4 data lanes.
+
+properties:
+  compatible:
+    const: sony,imx500
+
+  reg:
+    description: I2C device address
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  clock-names:
+    description: |-
+      Input clock (12 to 27 MHz)
+    items:
+      - const: inck
+
+  interrupts:
+    maxItems: 1
+
+  vana-supply:
+    description: Supply voltage (analog) - 2.7 V
+
+  vdig-supply:
+    description: Supply voltage (digital) - 0.84 V
+
+  vif-supply:
+    description: Supply voltage (interface) - 1.8 V
+
+  reset-gpios:
+    description: |-
+      Sensor reset (XCLR) GPIO
+
+      Chip clear in lieu of built-in power on reset. To be set 'High' after
+      power supplies are brought up and INCK supplied.
+
+  port:
+    $ref: /schemas/graph.yaml#/$defs/port-base
+    additionalProperties: false
+    description: |
+      Video output port
+
+    properties:
+      endpoint:
+        $ref: /schemas/media/video-interfaces.yaml#
+        type: object
+        unevaluatedProperties: false
+        properties:
+          data-lanes:
+            items:
+              - const: 2
+              - const: 4
+          clock-noncontinuous: true
+          link-frequencies: true
+        required:
+          - link-frequencies
+          - data-lanes
+
+  spi:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description: |-
+      SPI peripheral
+
+      Optional SPI peripheral for uploading firmware and network weights to AI
+      processor.
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-names
+  - vana-supply
+  - vdig-supply
+  - vif-supply
+  - port
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    i2c {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      imx500: sensor@1a {
+        compatible = "sony,imx500";
+        reg = <0x1a>;
+
+        clocks = <&imx500_clk>;
+        clock-names = "inck";
+
+        vana-supply = <&imx500_vana>; /* 2.7  +/- 0.1  V */
+        vdig-supply = <&imx500_vdig>; /* 0.84 +/- 0.04 V */
+        vif-supply  = <&imx500_vif>;  /* 1.8  +/- 0.1  V */
+
+        reset-gpios = <&gpio_sensor 0 GPIO_ACTIVE_LOW>;
+
+        port {
+          imx500_0: endpoint {
+            remote-endpoint = <&csi1_ep>;
+            data-lanes = <1 2>;
+            clock-noncontinuous;
+            link-frequencies = /bits/ 64 <499500000>;
+          };
+        };
+      };
+    };
+
+...
+
diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml
new file mode 100644
index 000000000000..286aad2e8c69
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml
@@ -0,0 +1,128 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/sony,imx708.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Sony 1/2.3-Inch 12Mpixel CMOS Digital Image Sensor
+
+maintainers:
+  - Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
+
+description: |-
+  The Sony IMX708 is a 1/2.3-inch CMOS active pixel digital image sensor
+  with an active array size of 4608H x 2592V. It is programmable through
+  I2C interface. The I2C address is fixed to 0x1A as per sensor data sheet.
+  Image data is sent through MIPI CSI-2, which is configured as either 2 or
+  4 data lanes.
+
+properties:
+  compatible:
+    const: sony,imx708
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  clock-names:
+    description: Input clock (6 to 27 MHz)
+    items:
+      - const: inck
+
+  vdig-supply:
+    description:
+      Digital I/O voltage supply, 1.1 volts
+
+  vana1-supply:
+    description:
+      Analog1 voltage supply, 2.8 volts
+
+  vana2-supply:
+    description:
+      Analog2 voltage supply, 1.8 volts
+
+  vddl-supply:
+    description:
+      Digital core voltage supply, 1.8 volts
+
+  reset-gpios:
+    description: Sensor reset (XCLR) GPIO
+    maxItems: 1
+
+  port:
+    $ref: /schemas/graph.yaml#/$defs/port-base
+    description: |
+      Video output port
+
+    properties:
+      endpoint:
+        $ref: /schemas/media/video-interfaces.yaml#
+        unevaluatedProperties: false
+
+        properties:
+          data-lanes:
+            anyOf:
+              - items:
+                  - const: 1
+                  - const: 2
+              - items:
+                  - const: 1
+                  - const: 2
+                  - const: 3
+                  - const: 4
+
+          link-frequencies: true
+
+        required:
+          - data-lanes
+          - link-frequencies
+
+    additionalProperties: false
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-names
+  - vdig-supply
+  - vana1-supply
+  - vana2-supply
+  - vddl-supply
+  - port
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        imx708: camera-sensor@1a {
+            compatible = "sony,imx708";
+            reg = <0x1a>;
+
+            clocks = <&clk 90>;
+            clock-names = "inck";
+
+            vdig-supply = <&camera_vdig>;
+            vana1-supply = <&camera_vana1>;
+            vana2-supply = <&camera_vana2>;
+            vddl-supply = <&camera_vddl>;
+
+            reset-gpios = <&gpio 35 GPIO_ACTIVE_LOW>;
+
+            port {
+                imx708_ep: endpoint {
+                    data-lanes = <1 2>;
+                    link-frequencies = /bits/ 64 <450000000>;
+                    remote-endpoint = <&csi_ep>;
+                };
+            };
+        };
+    };
+...
diff --git a/Documentation/devicetree/bindings/media/raspberrypi,hevc-dec.yaml b/Documentation/devicetree/bindings/media/raspberrypi,hevc-dec.yaml
new file mode 100644
index 000000000000..b86534f2689f
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/raspberrypi,hevc-dec.yaml
@@ -0,0 +1,70 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/raspberrypi,hevc-dec.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Raspberry Pi HEVC Decoder
+
+maintainers:
+  - John Cox <john.cox@raspberrypi.com>
+  - Dom Cobley <dom@raspberrypi.com>
+  - Dave Stevenson <dave.stevenson@raspberrypi.com>
+  - Raspberry Pi internal review list <kernel-list@raspberrypi.com>
+
+description:
+  The Raspberry Pi HEVC decoder is a hardware video decode accelerator block
+  found in the BCM2711 and BCM2712 processors used on Raspberry Pi 4 and 5
+  boards respectively.
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - brcm,bcm2711-hevc-dec
+          - brcm,bcm2712-hevc-dec
+      - const: raspberrypi,hevc-dec
+
+  reg:
+    items:
+      - description: The HEVC main register region
+      - description: The Interrupt control register region
+
+  reg-names:
+    items:
+      - const: hevc
+      - const: intc
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    items:
+      - description: The HEVC block clock
+
+required:
+  - compatible
+  - reg
+  - reg-names
+  - interrupts
+  - clocks
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+    video-codec@7eb10000 {
+        compatible = "brcm,bcm2711-hevc-dec", "raspberrypi,hevc-dec";
+        reg = <0x7eb00000 0x10000>, /* HEVC */
+              <0x7eb10000 0x1000>;  /* INTC */
+        reg-names = "hevc",
+                    "intc";
+
+        interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;
+
+        clocks = <&clk 0>;
+    };
+
+...
diff --git a/Documentation/devicetree/bindings/media/rpivid_hevc.yaml b/Documentation/devicetree/bindings/media/rpivid_hevc.yaml
new file mode 100644
index 000000000000..ce6b81a10303
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/rpivid_hevc.yaml
@@ -0,0 +1,72 @@
+# SPDX-License-Identifier: GPL-2.0-only
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/rpivid_hevc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Raspberry Pi HEVC Decoder
+
+maintainers:
+  - Raspberry Pi <kernel-list@raspberrypi.com>
+
+description: |-
+  The Camera Adaptation Layer (CAL) is a key component for image capture
+  applications. The capture module provides the system interface and the
+  processing capability to connect CSI2 image-sensor modules to the
+  DRA72x device.
+
+properties:
+  compatible:
+    enum:
+      - raspberrypi,rpivid-vid-decoder
+
+  reg:
+    minItems: 2
+    items:
+      - description: The HEVC main register region
+      - description: The Interrupt controller register region
+
+  reg-names:
+    minItems: 2
+    items:
+      - const: hevc
+      - const: intc
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    items:
+      - description: The HEVC block clock
+
+  clock-names:
+    items:
+      - const: hevc
+
+required:
+  - compatible
+  - reg
+  - reg-names
+  - interrupts
+  - clocks
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+    video-codec@7eb10000 {
+        compatible = "raspberrypi,rpivid-vid-decoder";
+        reg = <0x0 0x7eb10000 0x1000>,	/* INTC */
+              <0x0 0x7eb00000 0x10000>; /* HEVC */
+        reg-names = "intc",
+                    "hevc";
+
+        interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;
+
+        clocks = <&clk 0>;
+        clock-names = "hevc";
+    };
+
+...
diff --git a/Documentation/devicetree/bindings/misc/brcm,bcm2835-smi-dev.txt b/Documentation/devicetree/bindings/misc/brcm,bcm2835-smi-dev.txt
new file mode 100644
index 000000000000..68cc8ebc3392
--- /dev/null
+++ b/Documentation/devicetree/bindings/misc/brcm,bcm2835-smi-dev.txt
@@ -0,0 +1,17 @@
+* Broadcom BCM2835 SMI character device driver.
+
+SMI or secondary memory interface is a peripheral specific to certain Broadcom
+SOCs, and is helpful for talking to things like parallel-interface displays
+and NAND flashes (in fact, most things with a parallel register interface).
+
+This driver adds a character device which provides a user-space interface to
+an instance of the SMI driver.
+
+Required properties:
+- compatible: "brcm,bcm2835-smi-dev"
+- smi_handle: a phandle to the smi node.
+
+Optional properties:
+- None.
+
+
diff --git a/Documentation/devicetree/bindings/misc/brcm,bcm2835-smi.txt b/Documentation/devicetree/bindings/misc/brcm,bcm2835-smi.txt
new file mode 100644
index 000000000000..b76dc694f1ac
--- /dev/null
+++ b/Documentation/devicetree/bindings/misc/brcm,bcm2835-smi.txt
@@ -0,0 +1,48 @@
+* Broadcom BCM2835 SMI driver.
+
+SMI or secondary memory interface is a peripheral specific to certain Broadcom
+SOCs, and is helpful for talking to things like parallel-interface displays
+and NAND flashes (in fact, most things with a parallel register interface).
+
+Required properties:
+- compatible: "brcm,bcm2835-smi"
+- reg: Should contain location and length of SMI registers and SMI clkman regs
+- interrupts: *the* SMI interrupt.
+- pinctrl-names: should be "default".
+- pinctrl-0: the phandle of the gpio pin node.
+- brcm,smi-clock-source: the clock source for clkman
+- brcm,smi-clock-divisor: the integer clock divisor for clkman
+- dmas: the dma controller phandle and the DREQ number (4 on a 2835)
+- dma-names: the name used by the driver to request its channel.
+  Should be "rx-tx".
+
+Optional properties:
+- None.
+
+Examples:
+
+8 data pin configuration:
+
+smi: smi@7e600000 {
+	compatible = "brcm,bcm2835-smi";
+	reg = <0x7e600000 0x44>, <0x7e1010b0 0x8>;
+	interrupts = <2 16>;
+	pinctrl-names = "default";
+	pinctrl-0 = <&smi_pins>;
+	brcm,smi-clock-source = <6>;
+	brcm,smi-clock-divisor = <4>;
+	dmas = <&dma 4>;
+	dma-names = "rx-tx";
+
+	status = "okay";
+};
+
+smi_pins: smi_pins {
+	brcm,pins = <2 3 4 5 6 7 8 9 10 11 12 13 14 15>;
+	/* Alt 1: SMI */
+	brcm,function = <5 5 5 5 5 5 5 5 5 5 5 5 5 5>;
+	/* /CS, /WE and /OE are pulled high, as they are
+	   generally active low signals */
+	brcm,pull = <2 2 2 2 2 2 0 0 0 0 0 0 0 0>;
+};
+
diff --git a/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml b/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml
index c3d5e0230af1..3be3a53ef794 100644
--- a/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml
+++ b/Documentation/devicetree/bindings/mmc/snps,dwcmshc-sdhci.yaml
@@ -17,6 +17,7 @@ properties:
           - const: rockchip,rk3576-dwcmshc
           - const: rockchip,rk3588-dwcmshc
       - enum:
+          - raspberrypi,rp1-dwcmshc
           - rockchip,rk3568-dwcmshc
           - rockchip,rk3588-dwcmshc
           - snps,dwcmshc-sdhci
@@ -95,6 +96,8 @@ allOf:
             - description: axi clock for rockchip specified
             - description: block clock for rockchip specified
             - description: timer clock for rockchip specified
+            - description: timeout clock for rp1 specified
+            - description: sdio clock generator for rp1 specified
         clock-names:
           minItems: 1
           items:
@@ -103,6 +106,8 @@ allOf:
             - const: axi
             - const: block
             - const: timer
+            - const: timeout
+            - const: sdio
 
   - if:
       properties:
diff --git a/Documentation/devicetree/bindings/net/cdns,macb.yaml b/Documentation/devicetree/bindings/net/cdns,macb.yaml
index 3c30dd23cd4e..7c1dc7a7351a 100644
--- a/Documentation/devicetree/bindings/net/cdns,macb.yaml
+++ b/Documentation/devicetree/bindings/net/cdns,macb.yaml
@@ -54,6 +54,7 @@ properties:
           - cdns,np4-macb             # NP4 SoC devices
           - microchip,sama7g5-emac    # Microchip SAMA7G5 ethernet interface
           - microchip,sama7g5-gem     # Microchip SAMA7G5 gigabit ethernet interface
+          - raspberrypi,rp1-gem       # Raspberry Pi RP1 gigabit ethernet interface
           - sifive,fu540-c000-gem     # SiFive FU540-C000 SoC
           - cdns,emac                 # Generic
           - cdns,gem                  # Generic
@@ -136,6 +137,22 @@ properties:
       Node containing PHY children. If this node is not present, then PHYs will
       be direct children.
 
+  cdns,aw2w-max-pipe:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Maximum number of outstanding AXI write requests
+
+  cdns,ar2r-max-pipe:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description:
+      Maximum number of outstanding AXI read requests
+
+  cdns,use-aw2b-fill:
+    type: boolean
+    description:
+      If set, the maximum number of outstanding write transactions operates
+      between the AW to B AXI channel, instead of the AW to W AXI channel.
+
 patternProperties:
   "^ethernet-phy@[0-9a-f]$":
     type: object
diff --git a/Documentation/devicetree/bindings/net/microchip,lan78xx.txt b/Documentation/devicetree/bindings/net/microchip,lan78xx.txt
index 11a679530ae6..104768b85bbc 100644
--- a/Documentation/devicetree/bindings/net/microchip,lan78xx.txt
+++ b/Documentation/devicetree/bindings/net/microchip,lan78xx.txt
@@ -14,6 +14,9 @@ Optional properties of the embedded PHY:
 - microchip,led-modes: a 0..4 element vector, with each element configuring
   the operating mode of an LED. Omitted LEDs are turned off. Allowed values
   are defined in "include/dt-bindings/net/microchip-lan78xx.h".
+- microchip,downshift-after: sets the number of failed auto-negotiation
+  attempts after which the link is downgraded from 1000BASE-T. Should be one of
+  2, 3, 4, 5 or 0, where 0 means never downshift.
 
 Example:
 
diff --git a/Documentation/devicetree/bindings/pci/brcm,stb-pcie.yaml b/Documentation/devicetree/bindings/pci/brcm,stb-pcie.yaml
index 0925c520195a..081fe2185ce9 100644
--- a/Documentation/devicetree/bindings/pci/brcm,stb-pcie.yaml
+++ b/Documentation/devicetree/bindings/pci/brcm,stb-pcie.yaml
@@ -14,6 +14,7 @@ properties:
     items:
       - enum:
           - brcm,bcm2711-pcie # The Raspberry Pi 4
+          - brcm,bcm2712-pcie # Raspberry Pi 5
           - brcm,bcm4908-pcie
           - brcm,bcm7211-pcie # Broadcom STB version of RPi4
           - brcm,bcm7216-pcie # Broadcom 7216 Arm
@@ -83,6 +84,14 @@ properties:
     $ref: /schemas/types.yaml#/definitions/string
     enum: [ safe, no-l1ss, default ]
 
+  brcm,fifo-qos-map:
+    description: Array of u8 elements which assigns every per-TC FIFOs
+      an AXI priority based on fullness quartile (backpressure signalling).
+      Mutually exclusive with vdm-qos-map.
+    $ref: /schemas/types.yaml#/definitions/uint8-array
+    minItems: 4
+    maxItems: 4
+
   brcm,scb-sizes:
     description: u64 giving the 64bit PCIe memory
       viewport size of a memory controller.  There may be up to
@@ -96,13 +105,39 @@ properties:
       minItems: 1
       maxItems: 3
 
+  brcm,tperst-clk-ms:
+    description: u32 giving the number of milliseconds to extend
+      the time between internal release of fundamental reset and
+      the deassertion of the external PERST# pin. This has the
+      effect of increasing the Tperst_clk phase of link init.
+
+  brcm,vdm-qos-map:
+    description: Array of u8 elements which assigns each per-TC FIFO
+      a base AXI priority with automatic elevation depending on
+      Vendor Messages from the EP - specifically, RP1.
+      Mutually exclusive with fifo-qos-map.
+    $ref: /schemas/types.yaml#/definitions/uint8-array
+    minItems: 8
+    maxItems: 8
+
   resets:
     minItems: 1
     maxItems: 3
 
   reset-names:
     minItems: 1
-    maxItems: 3
+    items:
+      - enum: [perst, rescal]
+      - const: bridge
+      - const: swinit
+
+  brcm,tperst-clk-ms:
+    category: optional
+    type: int
+    description: u32 giving the number of milliseconds to extend
+      the time between internal release of fundamental reset and
+      the deassertion of the external PERST# pin. This has the
+      effect of increasing the Tperst_clk phase of link init.
 
 required:
   - compatible
@@ -175,6 +210,17 @@ allOf:
         - resets
         - reset-names
 
+  - if:
+      not:
+        properties:
+          compatible:
+            contains:
+              const: brcm,bcm2712-pcie
+    then:
+      properties:
+        brcm,fifo-qos-map: false
+        brcm,vdm-qos-map: false
+
 unevaluatedProperties: false
 
 examples:
diff --git a/Documentation/devicetree/bindings/pci/brcmstb-pcie.txt b/Documentation/devicetree/bindings/pci/brcmstb-pcie.txt
new file mode 100644
index 000000000000..a1a9ad5e70ca
--- /dev/null
+++ b/Documentation/devicetree/bindings/pci/brcmstb-pcie.txt
@@ -0,0 +1,59 @@
+Brcmstb PCIe Host Controller Device Tree Bindings
+
+Required Properties:
+- compatible
+  "brcm,bcm7425-pcie" -- for 7425 family MIPS-based SOCs.
+  "brcm,bcm7435-pcie" -- for 7435 family MIPS-based SOCs.
+  "brcm,bcm7445-pcie" -- for 7445 and later ARM based SOCs (not including
+      the 7278).
+  "brcm,bcm7278-pcie"  -- for 7278 family ARM-based SOCs.
+
+- reg -- the register start address and length for the PCIe reg block.
+- interrupts -- two interrupts are specified; the first interrupt is for
+     the PCI host controller and the second is for MSI if the built-in
+     MSI controller is to be used.
+- interrupt-names -- names of the interrupts (above): "pcie" and "msi".
+- #address-cells -- set to <3>.
+- #size-cells -- set to <2>.
+- #interrupt-cells: set to <1>.
+- interrupt-map-mask and interrupt-map, standard PCI properties to define the
+     mapping of the PCIe interface to interrupt numbers.
+- ranges: ranges for the PCI memory and I/O regions.
+- linux,pci-domain -- should be unique per host controller.
+
+Optional Properties:
+- clocks -- phandle of pcie clock.
+- clock-names -- set to "sw_pcie" if clocks is used.
+- dma-ranges -- Specifies the inbound memory mapping regions when
+     an "identity map" is not possible.
+- msi-controller -- this property is typically specified to have the
+     PCIe controller use its internal MSI controller.
+- msi-parent -- set to use an external MSI interrupt controller.
+- brcm,enable-ssc -- (boolean) indicates usage of spread-spectrum clocking.
+- max-link-speed --  (integer) indicates desired generation of link:
+     1 => 2.5 Gbps (gen1), 2 => 5.0 Gbps (gen2), 3 => 8.0 Gbps (gen3).
+
+Example Node:
+
+pcie0: pcie@f0460000 {
+		reg = <0x0 0xf0460000 0x0 0x9310>;
+		interrupts = <0x0 0x0 0x4>;
+		compatible = "brcm,bcm7445-pcie";
+		#address-cells = <3>;
+		#size-cells = <2>;
+		ranges = <0x02000000 0x00000000 0x00000000 0x00000000 0xc0000000 0x00000000 0x08000000
+			  0x02000000 0x00000000 0x08000000 0x00000000 0xc8000000 0x00000000 0x08000000>;
+		#interrupt-cells = <1>;
+		interrupt-map-mask = <0 0 0 7>;
+		interrupt-map = <0 0 0 1 &intc 0 47 3
+				 0 0 0 2 &intc 0 48 3
+				 0 0 0 3 &intc 0 49 3
+				 0 0 0 4 &intc 0 50 3>;
+		clocks = <&sw_pcie0>;
+		clock-names = "sw_pcie";
+		msi-parent = <&pcie0>;  /* use PCIe's internal MSI controller */
+		msi-controller;         /* use PCIe's internal MSI controller */
+		brcm,ssc;
+		max-link-speed = <1>;
+		linux,pci-domain = <0>;
+	};
diff --git a/Documentation/devicetree/bindings/power/reset/gpio-poweroff.yaml b/Documentation/devicetree/bindings/power/reset/gpio-poweroff.yaml
index a4b437fce37c..61df8d05e0bf 100644
--- a/Documentation/devicetree/bindings/power/reset/gpio-poweroff.yaml
+++ b/Documentation/devicetree/bindings/power/reset/gpio-poweroff.yaml
@@ -50,6 +50,10 @@ properties:
     default: 3000
     description: Time to wait before assuming the power off sequence failed.
 
+  export:
+    type: boolean
+    description: Export the GPIO line to the sysfs system
+
 required:
   - compatible
   - gpios
diff --git a/Documentation/devicetree/bindings/pwm/pwm-rp1.yaml b/Documentation/devicetree/bindings/pwm/pwm-rp1.yaml
new file mode 100644
index 000000000000..db9d7085f1c3
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/pwm-rp1.yaml
@@ -0,0 +1,38 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pwm/pwm-rp1.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Raspberry Pi RP1 PWM controller
+
+maintainers:
+  - Naushir Patuck <naush@raspberrypi.com>
+
+properties:
+  compatible:
+    enum:
+      - raspberrypi,rp1-pwm
+
+  reg:
+    maxItems: 1
+
+  "#pwm-cells":
+    const: 3
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - "#pwm-cells"
+
+additionalProperties: false
+
+examples:
+  - |
+    pwm0: pwm@98000 {
+      compatible = "raspberrypi,rp1-pwm";
+      reg = <0x0 0x98000  0x0 0x100>;
+      clocks = <&rp1_sys>;
+      #pwm-cells = <3>;
+    };
diff --git a/Documentation/devicetree/bindings/rtc/rtc-rpi.txt b/Documentation/devicetree/bindings/rtc/rtc-rpi.txt
new file mode 100644
index 000000000000..ed0d0d0a8464
--- /dev/null
+++ b/Documentation/devicetree/bindings/rtc/rtc-rpi.txt
@@ -0,0 +1,22 @@
+* Raspberry Pi RTC
+
+This is a Linux interface to an RTC managed by firmware, hence it's
+virtual from a Linux perspective.
+
+The interface uses the firmware mailbox api to access the RTC registers.
+
+Required properties:
+compatible: should be "raspberrypi,rpi-rtc"
+firmware:   Reference to the RPi firmware device node.
+
+Optional property:
+trickle-charge-microvolt: specify a trickle charge voltage for the backup
+                          battery in microvolts.
+
+Example:
+
+	rpi_rtc: rpi_rtc {
+		compatible = "raspberrypi,rpi-rtc";
+		firmware = <&firmware>;
+		trickle-charge-microvolt = <3000000>;
+	};
diff --git a/Documentation/devicetree/bindings/serial/pl011.yaml b/Documentation/devicetree/bindings/serial/pl011.yaml
index 9571041030b7..f34e2f66d1a3 100644
--- a/Documentation/devicetree/bindings/serial/pl011.yaml
+++ b/Documentation/devicetree/bindings/serial/pl011.yaml
@@ -101,6 +101,12 @@ properties:
       on the device.
     enum: [1, 4]
 
+  cts-event-workaround:
+    description:
+      Enables the (otherwise vendor-specific) workaround for the
+      CTS-induced TX lockup.
+    type: boolean
+
 required:
   - compatible
   - reg
diff --git a/Documentation/devicetree/bindings/sound/snps,designware-i2s.yaml b/Documentation/devicetree/bindings/sound/snps,designware-i2s.yaml
index a48d040b0a4f..6e50851ff70f 100644
--- a/Documentation/devicetree/bindings/sound/snps,designware-i2s.yaml
+++ b/Documentation/devicetree/bindings/sound/snps,designware-i2s.yaml
@@ -69,6 +69,10 @@ properties:
       - description: RX DMA Channel
     minItems: 1
 
+  dma-maxburst:
+    description: FIFO DMA burst threshold limit
+    maxItems: 1
+
   dma-names:
     items:
       - const: tx
diff --git a/Documentation/devicetree/bindings/sound/ti,pcm3168a.yaml b/Documentation/devicetree/bindings/sound/ti,pcm3168a.yaml
index 0b4f003989a4..d8ffcdd38281 100644
--- a/Documentation/devicetree/bindings/sound/ti,pcm3168a.yaml
+++ b/Documentation/devicetree/bindings/sound/ti,pcm3168a.yaml
@@ -58,6 +58,14 @@ properties:
   VCCDA2-supply:
     description: DAC power supply regulator 2 (+5V)
 
+  adc-force-cons:
+    description: Force ADC to operate in consumer mode. Useful if ADC and DAC
+      clock pins are tied together with DAC as producer.
+
+  dac-force-cons:
+    description: Force DAC to operate in consumer mode. Useful if ADC and DAC
+      clock pins are tied together with ADC as producer.
+
   ports:
     $ref: audio-graph-port.yaml#/definitions/port-base
     unevaluatedProperties: false
diff --git a/Documentation/devicetree/bindings/spi/raspberrypi,rp2040-gpio-bridge.yaml b/Documentation/devicetree/bindings/spi/raspberrypi,rp2040-gpio-bridge.yaml
new file mode 100644
index 000000000000..d6af6db169d5
--- /dev/null
+++ b/Documentation/devicetree/bindings/spi/raspberrypi,rp2040-gpio-bridge.yaml
@@ -0,0 +1,77 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/spi/raspberrypi,rp2040-gpio-bridge.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Raspberry Pi RP2040 GPIO Bridge
+
+maintainers:
+  - Raspberry Pi <kernel-list@raspberrypi.com>
+
+description: |-
+  The Raspberry Pi PR2040 GPIO bridge can be used as a GPIO expander and
+  Tx-only SPI master.
+
+properties:
+  reg:
+    description: I2C slave address
+    const: 0x40
+
+  compatible:
+    const: raspberrypi,rp2040-gpio-bridge
+
+  power-supply:
+    description: Phandle to the regulator that powers the RP2040.
+
+  '#address-cells':
+    const: 1
+
+  '#size-cells':
+    const: 0
+
+  '#gpio-cells':
+    const: 2
+
+  gpio-controller: true
+
+  fast_xfer_requires_i2c_lock:
+    description: Set if I2C bus should be locked during fast transfer.
+
+  fast_xfer_recv_gpio_base:
+    description: RP2040 GPIO base for fast transfer pair.
+
+  fast_xfer-gpios:
+    description: RP1 GPIOs to use for fast transfer clock and data.
+
+required:
+  - reg
+  - compatible
+  - power-supply
+  - '#gpio-cells'
+  - gpio-controller
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      spi@40 {
+        reg = <0x40>;
+        compatible = "raspberrypi,rp2040-gpio-bridge";
+        status = "disabled";
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        power-supply = <&cam_dummy_reg>;
+
+        #gpio-cells = <2>;
+        gpio-controller;
+      };
+    };
+
+...
+
diff --git a/Documentation/devicetree/bindings/spi/spi-gpio.yaml b/Documentation/devicetree/bindings/spi/spi-gpio.yaml
index 9ce1df93d4c3..d911c203fa45 100644
--- a/Documentation/devicetree/bindings/spi/spi-gpio.yaml
+++ b/Documentation/devicetree/bindings/spi/spi-gpio.yaml
@@ -43,6 +43,10 @@ properties:
       with no chip select is connected.
     $ref: /schemas/types.yaml#/definitions/uint32
 
+  sck-idle-input:
+    description: Make SCK an input when inactive.
+    type: boolean
+
   # Deprecated properties
   gpio-sck: false
   gpio-miso: false
diff --git a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml
index 1cd0ca90127d..272896bc233c 100644
--- a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml
+++ b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml
@@ -232,14 +232,29 @@ properties:
     description: When set, disable u2mac linestate check during HS transmit
     type: boolean
 
+  snps,enhanced-nak-fs-quirk:
+    description:
+      When set, the controller schedules many more handshakes to Async FS
+      endpoints, improving throughput when they frequently respond with NAKs.
+
+  snps,enhanced-nak-hs-quirk:
+    description:
+      When set, the controller schedules many more handshakes to Async HS
+      endpoints, improving throughput when they frequently respond with NAKs.
+
   snps,parkmode-disable-ss-quirk:
     description:
-      When set, all SuperSpeed bus instances in park mode are disabled.
+      When set, disable park mode for all Superspeed bus instances.
     type: boolean
 
   snps,parkmode-disable-hs-quirk:
     description:
-      When set, all HighSpeed bus instances in park mode are disabled.
+      When set, disable park mode for all Highspeed bus instances.
+    type: boolean
+
+  snps,parkmode-disable-fsls-quirk:
+    description:
+      When set, disable park mode for all Full/Lowspeed bus instances.
     type: boolean
 
   snps,dis_metastability_quirk:
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.txt b/Documentation/devicetree/bindings/vendor-prefixes.txt
new file mode 100644
index 000000000000..7bc73e57add3
--- /dev/null
+++ b/Documentation/devicetree/bindings/vendor-prefixes.txt
@@ -0,0 +1,464 @@
+Device tree binding vendor prefix registry.  Keep list in alphabetical order.
+
+This isn't an exhaustive list, but you should add new prefixes to it before
+using them to avoid name-space collisions.
+
+abilis	Abilis Systems
+abracon	Abracon Corporation
+actions	Actions Semiconductor Co., Ltd.
+active-semi	Active-Semi International Inc
+ad	Avionic Design GmbH
+adafruit	Adafruit Industries, LLC
+adapteva	Adapteva, Inc.
+adaptrum	Adaptrum, Inc.
+adh	AD Holdings Plc.
+adi	Analog Devices, Inc.
+advantech	Advantech Corporation
+aeroflexgaisler	Aeroflex Gaisler AB
+al	Annapurna Labs
+allo	Allo.com
+allwinner	Allwinner Technology Co., Ltd.
+alphascale	AlphaScale Integrated Circuits Systems, Inc.
+altr	Altera Corp.
+amarula	Amarula Solutions
+amazon	Amazon.com, Inc.
+amcc	Applied Micro Circuits Corporation (APM, formally AMCC)
+amd	Advanced Micro Devices (AMD), Inc.
+amediatech	Shenzhen Amediatech Technology Co., Ltd
+amlogic	Amlogic, Inc.
+ampire	Ampire Co., Ltd.
+ams	AMS AG
+amstaos	AMS-Taos Inc.
+analogix	Analogix Semiconductor, Inc.
+andestech	Andes Technology Corporation
+apm	Applied Micro Circuits Corporation (APM)
+aptina	Aptina Imaging
+arasan	Arasan Chip Systems
+archermind ArcherMind Technology (Nanjing) Co., Ltd.
+arctic	Arctic Sand
+aries	Aries Embedded GmbH
+arm	ARM Ltd.
+armadeus	ARMadeus Systems SARL
+arrow	Arrow Electronics
+artesyn	Artesyn Embedded Technologies Inc.
+asahi-kasei	Asahi Kasei Corp.
+aspeed	ASPEED Technology Inc.
+asus	AsusTek Computer Inc.
+atlas	Atlas Scientific LLC
+atmel	Atmel Corporation
+auo	AU Optronics Corporation
+auvidea Auvidea GmbH
+avago	Avago Technologies
+avia	avia semiconductor
+avic	Shanghai AVIC Optoelectronics Co., Ltd.
+avnet	Avnet, Inc.
+axentia	Axentia Technologies AB
+axis	Axis Communications AB
+bananapi BIPAI KEJI LIMITED
+bhf	Beckhoff Automation GmbH & Co. KG
+bitmain	Bitmain Technologies
+blokas	Vilniaus Blokas UAB
+blokaslabs	Vilniaus Blokas UAB
+boe	BOE Technology Group Co., Ltd.
+bosch	Bosch Sensortec GmbH
+boundary	Boundary Devices Inc.
+brcm	Broadcom Corporation
+buffalo	Buffalo, Inc.
+bticino Bticino International
+calxeda	Calxeda
+capella	Capella Microsystems, Inc
+cascoda	Cascoda, Ltd.
+catalyst	Catalyst Semiconductor, Inc.
+cavium	Cavium, Inc.
+cdns	Cadence Design Systems Inc.
+cdtech	CDTech(H.K.) Electronics Limited
+ceva	Ceva, Inc.
+chipidea	Chipidea, Inc
+chipone		ChipOne
+chipspark	ChipSPARK
+chrp	Common Hardware Reference Platform
+chunghwa	Chunghwa Picture Tubes Ltd.
+ciaa	Computadora Industrial Abierta Argentina
+cirrus	Cirrus Logic, Inc.
+cloudengines	Cloud Engines, Inc.
+cnm	Chips&Media, Inc.
+cnxt	Conexant Systems, Inc.
+compulab	CompuLab Ltd.
+cortina	Cortina Systems, Inc.
+cosmic	Cosmic Circuits
+crane	Crane Connectivity Solutions
+creative	Creative Technology Ltd
+crystalfontz	Crystalfontz America, Inc.
+csky	Hangzhou C-SKY Microsystems Co., Ltd
+cubietech	Cubietech, Ltd.
+cypress	Cypress Semiconductor Corporation
+cznic	CZ.NIC, z.s.p.o.
+dallas	Maxim Integrated Products (formerly Dallas Semiconductor)
+dataimage	DataImage, Inc.
+davicom	DAVICOM Semiconductor, Inc.
+delta	Delta Electronics, Inc.
+denx	Denx Software Engineering
+devantech	Devantech, Ltd.
+dh	DH electronics GmbH
+digi	Digi International Inc.
+digilent	Diglent, Inc.
+dioo	Dioo Microcircuit Co., Ltd
+dlc	DLC Display Co., Ltd.
+dlg	Dialog Semiconductor
+dlink	D-Link Corporation
+dmo	Data Modul AG
+domintech	Domintech Co., Ltd.
+dongwoon	Dongwoon Anatech
+dptechnics	DPTechnics
+dragino	Dragino Technology Co., Limited
+ea	Embedded Artists AB
+ebs-systart EBS-SYSTART GmbH
+ebv	EBV Elektronik
+eckelmann	Eckelmann AG
+edt	Emerging Display Technologies
+eeti	eGalax_eMPIA Technology Inc
+elan	Elan Microelectronic Corp.
+elgin	Elgin S/A.
+embest	Shenzhen Embest Technology Co., Ltd.
+emlid	Emlid, Ltd.
+emmicro	EM Microelectronic
+emtrion	emtrion GmbH
+endless	Endless Mobile, Inc.
+energymicro	Silicon Laboratories (formerly Energy Micro AS)
+engicam	Engicam S.r.l.
+epcos	EPCOS AG
+epfl	Ecole Polytechnique Fédérale de Lausanne
+epson	Seiko Epson Corp.
+est	ESTeem Wireless Modems
+ettus	NI Ettus Research
+eukrea  Eukréa Electromatique
+everest	Everest Semiconductor Co. Ltd.
+everspin	Everspin Technologies, Inc.
+exar	Exar Corporation
+excito	Excito
+ezchip	EZchip Semiconductor
+facebook	Facebook
+fairphone	Fairphone B.V.
+faraday	Faraday Technology Corporation
+fastrax	Fastrax Oy
+fcs	Fairchild Semiconductor
+feiyang	Shenzhen Fly Young Technology Co.,LTD.
+firefly	Firefly
+focaltech	FocalTech Systems Co.,Ltd
+friendlyarm	Guangzhou FriendlyARM Computer Tech Co., Ltd
+fsl	Freescale Semiconductor
+fujitsu	Fujitsu Ltd.
+gateworks	Gateworks Corporation
+gcw Game Consoles Worldwide
+ge	General Electric Company
+geekbuying	GeekBuying
+gef	GE Fanuc Intelligent Platforms Embedded Systems, Inc.
+GEFanuc	GE Fanuc Intelligent Platforms Embedded Systems, Inc.
+geniatech	Geniatech, Inc.
+giantec	Giantec Semiconductor, Inc.
+giantplus	Giantplus Technology Co., Ltd.
+globalscale	Globalscale Technologies, Inc.
+globaltop	GlobalTop Technology, Inc.
+gmt	Global Mixed-mode Technology, Inc.
+goodix	Shenzhen Huiding Technology Co., Ltd.
+google	Google, Inc.
+grinn	Grinn
+grmn	Garmin Limited
+gumstix	Gumstix, Inc.
+gw	Gateworks Corporation
+hannstar	HannStar Display Corporation
+haoyu	Haoyu Microelectronic Co. Ltd.
+hardkernel	Hardkernel Co., Ltd
+hideep	HiDeep Inc.
+himax	Himax Technologies, Inc.
+hisilicon	Hisilicon Limited.
+hit	Hitachi Ltd.
+hitex	Hitex Development Tools
+holt	Holt Integrated Circuits, Inc.
+honeywell	Honeywell
+hp	Hewlett Packard
+holtek	Holtek Semiconductor, Inc.
+hwacom	HwaCom Systems Inc.
+i2se	I2SE GmbH
+ibm	International Business Machines (IBM)
+icplus	IC Plus Corp.
+idt	Integrated Device Technologies, Inc.
+ifi	Ingenieurburo Fur Ic-Technologie (I/F/I)
+ilitek	ILI Technology Corporation (ILITEK)
+img	Imagination Technologies Ltd.
+infineon Infineon Technologies
+inforce	Inforce Computing
+ingenic	Ingenic Semiconductor
+innolux	Innolux Corporation
+inside-secure	INSIDE Secure
+intel	Intel Corporation
+intercontrol	Inter Control Group
+invensense	InvenSense Inc.
+inversepath	Inverse Path
+iom	Iomega Corporation
+isee	ISEE 2007 S.L.
+isil	Intersil
+issi	Integrated Silicon Solutions Inc.
+itead	ITEAD Intelligent Systems Co.Ltd
+iwave  iWave Systems Technologies Pvt. Ltd.
+jdi	Japan Display Inc.
+jedec	JEDEC Solid State Technology Association
+jianda	Jiandangjing Technology Co., Ltd.
+karo	Ka-Ro electronics GmbH
+keithkoep	Keith & Koep GmbH
+keymile	Keymile GmbH
+khadas	Khadas
+kiebackpeter    Kieback & Peter GmbH
+kinetic Kinetic Technologies
+kingdisplay	King & Display Technology Co., Ltd.
+kingnovel	Kingnovel Technology Co., Ltd.
+koe	Kaohsiung Opto-Electronics Inc.
+kosagi	Sutajio Ko-Usagi PTE Ltd.
+kyo	Kyocera Corporation
+lacie	LaCie
+laird	Laird PLC
+lantiq	Lantiq Semiconductor
+lattice	Lattice Semiconductor
+lego	LEGO Systems A/S
+lemaker	Shenzhen LeMaker Technology Co., Ltd.
+lenovo	Lenovo Group Ltd.
+lg	LG Corporation
+libretech	Shenzhen Libre Technology Co., Ltd
+licheepi	Lichee Pi
+linaro	Linaro Limited
+linksys	Belkin International, Inc. (Linksys)
+linux	Linux-specific binding
+linx	Linx Technologies
+lltc	Linear Technology Corporation
+logicpd	Logic PD, Inc.
+lsi	LSI Corp. (LSI Logic)
+lwn	Liebherr-Werk Nenzing GmbH
+macnica	Macnica Americas
+marvell	Marvell Technology Group Ltd.
+maxim	Maxim Integrated Products
+mbvl	Mobiveil Inc.
+mcube	mCube
+meas	Measurement Specialties
+mediatek	MediaTek Inc.
+megachips	MegaChips
+mele	Shenzhen MeLE Digital Technology Ltd.
+melexis	Melexis N.V.
+melfas	MELFAS Inc.
+mellanox	Mellanox Technologies
+memsic	MEMSIC Inc.
+merrii	Merrii Technology Co., Ltd.
+micrel	Micrel Inc.
+microchip	Microchip Technology Inc.
+microcrystal	Micro Crystal AG
+micron	Micron Technology Inc.
+mikroe		MikroElektronika d.o.o.
+minix	MINIX Technology Ltd.
+miramems	MiraMEMS Sensing Technology Co., Ltd.
+mitsubishi	Mitsubishi Electric Corporation
+mosaixtech	Mosaix Technologies, Inc.
+motorola	Motorola, Inc.
+moxa	Moxa Inc.
+mpl	MPL AG
+mqmaker	mqmaker Inc.
+mscc	Microsemi Corporation
+msi	Micro-Star International Co. Ltd.
+mti	Imagination Technologies Ltd. (formerly MIPS Technologies Inc.)
+multi-inno	Multi-Inno Technology Co.,Ltd
+mundoreader	Mundo Reader S.L.
+murata	Murata Manufacturing Co., Ltd.
+mxicy	Macronix International Co., Ltd.
+myir	MYIR Tech Limited
+national	National Semiconductor
+nec	NEC LCD Technologies, Ltd.
+neonode		Neonode Inc.
+netgear	NETGEAR
+netlogic	Broadcom Corporation (formerly NetLogic Microsystems)
+netron-dy	Netron DY
+netxeon		Shenzhen Netxeon Technology CO., LTD
+nexbox	Nexbox
+nextthing	Next Thing Co.
+newhaven	Newhaven Display International
+ni	National Instruments
+nintendo	Nintendo
+nlt	NLT Technologies, Ltd.
+nokia	Nokia
+nordic	Nordic Semiconductor
+novtech NovTech, Inc.
+nutsboard	NutsBoard
+nuvoton	Nuvoton Technology Corporation
+nvd	New Vision Display
+nvidia	NVIDIA
+nxp	NXP Semiconductors
+okaya	Okaya Electric America, Inc.
+oki	Oki Electric Industry Co., Ltd.
+olimex	OLIMEX Ltd.
+olpc	One Laptop Per Child
+onion	Onion Corporation
+onnn	ON Semiconductor Corp.
+ontat	On Tat Industrial Company
+opalkelly	Opal Kelly Incorporated
+opencores	OpenCores.org
+openrisc	OpenRISC.io
+option	Option NV
+oranth	Shenzhen Oranth Technology Co., Ltd.
+ORCL	Oracle Corporation
+orisetech	Orise Technology
+ortustech	Ortus Technology Co., Ltd.
+ovti	OmniVision Technologies
+oxsemi	Oxford Semiconductor, Ltd.
+panasonic	Panasonic Corporation
+parade	Parade Technologies Inc.
+pda	Precision Design Associates, Inc.
+pericom	Pericom Technology Inc.
+pervasive	Pervasive Displays, Inc.
+phicomm PHICOMM Co., Ltd.
+phytec	PHYTEC Messtechnik GmbH
+picochip	Picochip Ltd
+pine64	Pine64
+pixcir  PIXCIR MICROELECTRONICS Co., Ltd
+plantower Plantower Co., Ltd
+plathome	Plat'Home Co., Ltd.
+plda	PLDA
+plx	Broadcom Corporation (formerly PLX Technology)
+pni	PNI Sensor Corporation
+portwell	Portwell Inc.
+poslab	Poslab Technology Co., Ltd.
+powervr	PowerVR (deprecated, use img)
+probox2	PROBOX2 (by W2COMP Co., Ltd.)
+pulsedlight	PulsedLight, Inc
+qca	Qualcomm Atheros, Inc.
+qcom	Qualcomm Technologies, Inc
+qemu	QEMU, a generic and open source machine emulator and virtualizer
+qi	Qi Hardware
+qiaodian	QiaoDian XianShi Corporation
+qnap	QNAP Systems, Inc.
+radxa	Radxa
+raidsonic	RaidSonic Technology GmbH
+ralink	Mediatek/Ralink Technology Corp.
+ramtron	Ramtron International
+raspberrypi	Raspberry Pi Foundation
+raydium	Raydium Semiconductor Corp.
+rda	Unisoc Communications, Inc.
+realtek Realtek Semiconductor Corp.
+renesas	Renesas Electronics Corporation
+richtek	Richtek Technology Corporation
+ricoh	Ricoh Co. Ltd.
+rikomagic	Rikomagic Tech Corp. Ltd
+riscv	RISC-V Foundation
+rockchip	Fuzhou Rockchip Electronics Co., Ltd
+rohm	ROHM Semiconductor Co., Ltd
+roofull	Shenzhen Roofull Technology Co, Ltd
+samsung	Samsung Semiconductor
+samtec	Samtec/Softing company
+sancloud	Sancloud Ltd
+sandisk	Sandisk Corporation
+sbs	Smart Battery System
+schindler	Schindler
+seagate	Seagate Technology PLC
+semtech	Semtech Corporation
+sensirion	Sensirion AG
+sff	Small Form Factor Committee
+sgd	Solomon Goldentek Display Corporation
+sgx	SGX Sensortech
+sharp	Sharp Corporation
+shimafuji	Shimafuji Electric, Inc.
+si-en	Si-En Technology Ltd.
+sifive	SiFive, Inc.
+sigma	Sigma Designs, Inc.
+sii	Seiko Instruments, Inc.
+sil	Silicon Image
+silabs	Silicon Laboratories
+silead	Silead Inc.
+silergy	Silergy Corp.
+siliconmitus	Silicon Mitus, Inc.
+simtek
+sirf	SiRF Technology, Inc.
+sis	Silicon Integrated Systems Corp.
+sitronix	Sitronix Technology Corporation
+skyworks	Skyworks Solutions, Inc.
+smsc	Standard Microsystems Corporation
+snps	Synopsys, Inc.
+socionext	Socionext Inc.
+solidrun	SolidRun
+solomon        Solomon Systech Limited
+sony	Sony Corporation
+spansion	Spansion Inc.
+sprd	Spreadtrum Communications Inc.
+sst	Silicon Storage Technology, Inc.
+st	STMicroelectronics
+starry	Starry Electronic Technology (ShenZhen) Co., LTD
+startek	Startek
+ste	ST-Ericsson
+stericsson	ST-Ericsson
+summit	Summit microelectronics
+sunchip	Shenzhen Sunchip Technology Co., Ltd
+SUNW	Sun Microsystems, Inc
+swir	Sierra Wireless
+syna	Synaptics Inc.
+synology	Synology, Inc.
+tbs	TBS Technologies
+tbs-biometrics	Touchless Biometric Systems AG
+tcg	Trusted Computing Group
+tcl	Toby Churchill Ltd.
+technexion	TechNexion
+technologic	Technologic Systems
+tempo	Tempo Semiconductor
+techstar	Shenzhen Techstar Electronics Co., Ltd.
+terasic	Terasic Inc.
+thine	THine Electronics, Inc.
+ti	Texas Instruments
+tianma	Tianma Micro-electronics Co., Ltd.
+tlm	Trusted Logic Mobility
+tmt	Tecon Microprocessor Technologies, LLC.
+topeet  Topeet
+toradex	Toradex AG
+toshiba	Toshiba Corporation
+toumaz	Toumaz
+tpk	TPK U.S.A. LLC
+tplink	TP-LINK Technologies Co., Ltd.
+tpo	TPO
+tronfy	Tronfy
+tronsmart	Tronsmart
+truly	Truly Semiconductors Limited
+tsd	Theobroma Systems Design und Consulting GmbH
+tyan	Tyan Computer Corporation
+u-blox	u-blox
+ucrobotics	uCRobotics
+ubnt	Ubiquiti Networks
+udoo	Udoo
+uniwest	United Western Technologies Corp (UniWest)
+upisemi	uPI Semiconductor Corp.
+urt	United Radiant Technology Corporation
+usi	Universal Scientific Industrial Co., Ltd.
+v3	V3 Semiconductor
+vamrs	Vamrs Ltd.
+variscite	Variscite Ltd.
+via	VIA Technologies, Inc.
+virtio	Virtual I/O Device Specification, developed by the OASIS consortium
+vishay	Vishay Intertechnology, Inc
+vitesse	Vitesse Semiconductor Corporation
+vivante	Vivante Corporation
+vocore VoCore Studio
+voipac	Voipac Technologies s.r.o.
+vot	Vision Optical Technology Co., Ltd.
+wd	Western Digital Corp.
+wetek	WeTek Electronics, limited.
+wexler	Wexler
+whwave  Shenzhen whwave Electronics, Inc.
+wi2wi	Wi2Wi, Inc.
+winbond Winbond Electronics corp.
+winstar	Winstar Display Corp.
+wlf	Wolfson Microelectronics
+wm	Wondermedia Technologies, Inc.
+x-powers	X-Powers
+xes	Extreme Engineering Solutions (X-ES)
+xillybus	Xillybus Ltd.
+xlnx	Xilinx
+xunlong	Shenzhen Xunlong Software CO.,Limited
+ysoft	Y Soft Corporation a.s.
+zarlink	Zarlink Semiconductor
+zeitec	ZEITEC Semiconductor Co., LTD.
+zidoo	Shenzhen Zidoo Technology Co., Ltd.
+zii	Zodiac Inflight Innovations
+zte	ZTE Corp.
+zyxel	ZyXEL Communications Corp.
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index af9a8d43b247..a8e96b973fc5 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -147,6 +147,8 @@ patternProperties:
     description: arcx Inc. / Archronix Inc.
   "^aries,.*":
     description: Aries Embedded GmbH
+  "^arducam,.*":
+    description: Arducam Technology co., Ltd.
   "^arm,.*":
     description: ARM Ltd.
   "^armadeus,.*":
@@ -216,6 +218,8 @@ patternProperties:
     description: Shenzhen BigTree Tech Co., LTD
   "^bitmain,.*":
     description: Bitmain Technologies
+  "^blokas(labs)?,.*":
+    description: Vilniaus Blokas UAB
   "^blutek,.*":
     description: BluTek Power
   "^boe,.*":
@@ -557,6 +561,8 @@ patternProperties:
     description: General Electric Company
   "^geekbuying,.*":
     description: GeekBuying
+  "^geekworm,.*":
+    description: Geekworm
   "^gef,.*":
     description: GE Fanuc Intelligent Platforms Embedded Systems, Inc.
   "^GEFanuc,.*":
diff --git a/Documentation/devicetree/configfs-overlays.txt b/Documentation/devicetree/configfs-overlays.txt
new file mode 100644
index 000000000000..5fa43e064307
--- /dev/null
+++ b/Documentation/devicetree/configfs-overlays.txt
@@ -0,0 +1,31 @@
+Howto use the configfs overlay interface.
+
+A device-tree configfs entry is created in /config/device-tree/overlays
+and and it is manipulated using standard file system I/O.
+Note that this is a debug level interface, for use by developers and
+not necessarily something accessed by normal users due to the
+security implications of having direct access to the kernel's device tree.
+
+* To create an overlay you mkdir the directory:
+
+	# mkdir /config/device-tree/overlays/foo
+
+* Either you echo the overlay firmware file to the path property file.
+
+	# echo foo.dtbo >/config/device-tree/overlays/foo/path
+
+* Or you cat the contents of the overlay to the dtbo file
+
+	# cat foo.dtbo >/config/device-tree/overlays/foo/dtbo
+
+The overlay file will be applied, and devices will be created/destroyed
+as required.
+
+To remove it simply rmdir the directory.
+
+	# rmdir /config/device-tree/overlays/foo
+
+The rationalle of the dual interface (firmware & direct copy) is that each is
+better suited to different use patterns. The firmware interface is what's
+intended to be used by hardware managers in the kernel, while the copy interface
+make sense for developers (since it avoids problems with namespaces).
diff --git a/Documentation/hwmon/ina2xx.rst b/Documentation/hwmon/ina2xx.rst
index 7f1939b40f74..1ce161e6c0bf 100644
--- a/Documentation/hwmon/ina2xx.rst
+++ b/Documentation/hwmon/ina2xx.rst
@@ -53,6 +53,16 @@ Supported chips:
 
 	       https://www.ti.com/
 
+  * Texas Instruments INA260
+
+    Prefix: 'ina260'
+
+    Addresses: I2C 0x40 - 0x4f
+
+    Datasheet: Publicly available at the Texas Instruments website
+
+	       https://www.ti.com/
+
 Author: Lothar Felten <lothar.felten@gmail.com>
 
 Description
@@ -72,6 +82,9 @@ INA230 and INA231 are high or low side current shunt and power monitors
 with an I2C interface. The chips monitor both a shunt voltage drop and
 bus supply voltage.
 
+INA260 is a high or low side current and power monitor with integrated shunt
+resistor.
+
 The shunt value in micro-ohms can be set via platform data or device tree at
 compile-time or via the shunt_resistor attribute in sysfs at run-time. Please
 refer to the Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml for bindings
@@ -87,16 +100,16 @@ The actual programmed interval may vary from the desired value.
 General sysfs entries
 ---------------------
 
-======================= ===============================
+======================= ===============================================
 in0_input		Shunt voltage(mV) channel
 in1_input		Bus voltage(mV) channel
 curr1_input		Current(mA) measurement channel
 power1_input		Power(uW) measurement channel
-shunt_resistor		Shunt resistance(uOhm) channel
-======================= ===============================
+shunt_resistor		Shunt resistance(uOhm) channel (not for ina260)
+======================= ===============================================
 
-Sysfs entries for ina226, ina230 and ina231 only
-------------------------------------------------
+Additional sysfs entries for ina226, ina230, ina231, and ina260
+---------------------------------------------------------------
 
 ======================= ====================================================
 curr1_lcrit		Critical low current
diff --git a/Documentation/userspace-api/media/drivers/index.rst b/Documentation/userspace-api/media/drivers/index.rst
index d706cb47b112..65bcd99cb96f 100644
--- a/Documentation/userspace-api/media/drivers/index.rst
+++ b/Documentation/userspace-api/media/drivers/index.rst
@@ -32,6 +32,7 @@ For more details see the file COPYING in the source distribution of Linux.
 	cx2341x-uapi
 	dw100
 	imx-uapi
+	bcm2835-isp
 	max2175
 	npcm-video
 	omap3isp-uapi
diff --git a/Documentation/userspace-api/media/v4l/meta-formats.rst b/Documentation/userspace-api/media/v4l/meta-formats.rst
index c6e56b5888bc..69e09857d55e 100644
--- a/Documentation/userspace-api/media/v4l/meta-formats.rst
+++ b/Documentation/userspace-api/media/v4l/meta-formats.rst
@@ -12,11 +12,13 @@ These formats are used for the :ref:`metadata` interface only.
 .. toctree::
     :maxdepth: 1
 
+    metafmt-bcm2835-isp-stats
     metafmt-d4xx
     metafmt-generic
     metafmt-intel-ipu3
     metafmt-pisp-be
     metafmt-rkisp1
+    metafmt-sensor-data
     metafmt-uvc
     metafmt-vivid
     metafmt-vsp1-hgo
diff --git a/Documentation/userspace-api/media/v4l/pixfmt-meta-bcm2835-isp-stats.rst b/Documentation/userspace-api/media/v4l/pixfmt-meta-bcm2835-isp-stats.rst
new file mode 100644
index 000000000000..f974774c8252
--- /dev/null
+++ b/Documentation/userspace-api/media/v4l/pixfmt-meta-bcm2835-isp-stats.rst
@@ -0,0 +1,41 @@
+.. Permission is granted to copy, distribute and/or modify this
+.. document under the terms of the GNU Free Documentation License,
+.. Version 1.1 or any later version published by the Free Software
+.. Foundation, with no Invariant Sections, no Front-Cover Texts
+.. and no Back-Cover Texts. A copy of the license is included at
+.. Documentation/media/uapi/fdl-appendix.rst.
+..
+.. TODO: replace it to GFDL-1.1-or-later WITH no-invariant-sections
+
+.. _v4l2-meta-fmt-bcm2835-isp-stats:
+
+*****************************************
+V4L2_META_FMT_BCM2835_ISP_STATS  ('BSTA')
+*****************************************
+
+BCM2835 ISP Statistics
+
+Description
+===========
+
+The BCM2835 ISP hardware calculate image statistics for an input Bayer frame.
+These statistics are obtained from the "bcm2835-isp0-capture3" device node
+using the :c:type:`v4l2_meta_format` interface. They are formatted as described
+by the :c:type:`bcm2835_isp_stats` structure below.
+
+.. code-block:: c
+
+	#define DEFAULT_AWB_REGIONS_X 16
+	#define DEFAULT_AWB_REGIONS_Y 12
+
+	#define NUM_HISTOGRAMS 2
+	#define NUM_HISTOGRAM_BINS 128
+	#define AWB_REGIONS (DEFAULT_AWB_REGIONS_X * DEFAULT_AWB_REGIONS_Y)
+	#define FLOATING_REGIONS 16
+	#define AGC_REGIONS 16
+	#define FOCUS_REGIONS 12
+
+.. kernel-doc:: include/uapi/linux/bcm2835-isp.h
+   :functions: bcm2835_isp_stats_hist bcm2835_isp_stats_region
+	             bcm2835_isp_stats_focus bcm2835_isp_stats
+
diff --git a/Documentation/userspace-api/media/v4l/pixfmt-meta-sensor-data.rst b/Documentation/userspace-api/media/v4l/pixfmt-meta-sensor-data.rst
new file mode 100644
index 000000000000..4a67e204d08a
--- /dev/null
+++ b/Documentation/userspace-api/media/v4l/pixfmt-meta-sensor-data.rst
@@ -0,0 +1,32 @@
+.. Permission is granted to copy, distribute and/or modify this
+.. document under the terms of the GNU Free Documentation License,
+.. Version 1.1 or any later version published by the Free Software
+.. Foundation, with no Invariant Sections, no Front-Cover Texts
+.. and no Back-Cover Texts. A copy of the license is included at
+.. Documentation/media/uapi/fdl-appendix.rst.
+..
+.. TODO: replace it to GFDL-1.1-or-later WITH no-invariant-sections
+
+.. _v4l2-meta-fmt-sensor-data:
+
+***********************************
+V4L2_META_FMT_SENSOR_DATA  ('SENS')
+***********************************
+
+Sensor Ancillary Metadata
+
+Description
+===========
+
+This format describes ancillary data generated by a camera sensor and
+transmitted over a stream on the camera bus. Sensor vendors generally have their
+own custom format for this ancillary data. Some vendors follow a generic
+CSI-2/SMIA embedded data format as described in the `CSI-2 specification.
+<https://mipi.org/specifications/csi-2>`_
+
+The size of the embedded buffer is defined as a single line with a pixel width
+width specified in bytes. This is obtained by a call to the
+:c:type:`VIDIOC_SUBDEV_G_FMT` ioctl on the sensor subdevice where the ``pad``
+field in :c:type:`v4l2_subdev_format` is set to 1.  Note that this size is fixed
+and cannot be modified with a call to :c:type:`VIDIOC_SUBDEV_S_FMT`.
+
diff --git a/Documentation/userspace-api/media/v4l/pixfmt-yuv-planar.rst b/Documentation/userspace-api/media/v4l/pixfmt-yuv-planar.rst
index b788f6933855..b5b590f234b0 100644
--- a/Documentation/userspace-api/media/v4l/pixfmt-yuv-planar.rst
+++ b/Documentation/userspace-api/media/v4l/pixfmt-yuv-planar.rst
@@ -827,6 +827,48 @@ Data in the 12 high bits, zeros in the 4 low bits, arranged in little endian ord
       - Cb\ :sub:`11`
       - Cr\ :sub:`11`
 
+V4L2_PIX_FMT_NV12MT_COL128 and V4L2_PIX_FMT_NV12MT_10_COL128
+------------------------------------------------------------
+
+``V4L2_PIX_FMT_NV12MT_COL128`` is a tiled version of
+``V4L2_PIX_FMT_NV12M`` where the two planes are split into 128 byte wide columns
+of Y or interleaved CbCr.
+
+V4L2_PIX_FMT_NV12MT_10_COL128 expands that as a 10 bit format where 3 10 bit
+values are packed into a 32bit word. A 128 byte wide column therefore holds 96
+samples (either Y or interleaved CrCb). That effectively makes it 6 values in a
+64 bit word for the CbCr plane, as the values always go in pairs.
+
+Bit-packed representation.
+
+.. tabularcolumns:: |p{1.2cm}||p{1.2cm}||p{1.2cm}||p{1.2cm}|p{3.2cm}|p{3.2cm}|
+
+.. flat-table::
+    :header-rows:  0
+    :stub-columns: 0
+    :widths: 8 8 8 8
+
+    * - Y'\ :sub:`00[7:0]`
+      - Y'\ :sub:`01[5:0] (bits 7--2)` Y'\ :sub:`00[9:8]`\ (bits 1--0)
+      - Y'\ :sub:`02[3:0] (bits 7--4)` Y'\ :sub:`01[9:6]`\ (bits 3--0)
+      - unused (bits 7--6)` Y'\ :sub:`02[9:4]`\ (bits 5--0)
+
+.. tabularcolumns:: |p{1.2cm}||p{1.2cm}||p{1.2cm}||p{1.2cm}|p{3.2cm}|p{3.2cm}|
+
+.. flat-table::
+    :header-rows:  0
+    :stub-columns: 0
+    :widths: 12 12 12 12 12 12 12 12
+
+    * - Cb\ :sub:`00[7:0]`
+      - Cr\ :sub:`00[5:0]`\ (bits 7--2) Cb\ :sub:`00[9:8]`\ (bits 1--0)
+      - Cb\ :sub:`01[3:0]`\ (bits 7--4) Cr\ :sub:`00[9:6]`\ (bits 3--0)
+      - unused (bits 7--6) Cb\ :sub:`02[9:4]`\ (bits 5--0)
+      - Cr\ :sub:`01[7:0]`
+      - Cb\ :sub:`02[5:0]`\ (bits 7--2) Cr\ :sub:`01[9:8]`\ (bits 1--0)
+      - Cr\ :sub:`02[3:0]`\ (bits 7--4) Cb\ :sub:`02[9:6]`\ (bits 3--0)
+      - unused (bits 7--6) Cr\ :sub:`02[9:4]`\ (bits 5--0)
+
 
 Fully Planar YUV Formats
 ========================
diff --git a/Documentation/userspace-api/media/v4l/subdev-formats.rst b/Documentation/userspace-api/media/v4l/subdev-formats.rst
index d2a6cd2e1eb2..a179b428ebbb 100644
--- a/Documentation/userspace-api/media/v4l/subdev-formats.rst
+++ b/Documentation/userspace-api/media/v4l/subdev-formats.rst
@@ -160,12 +160,14 @@ correspondence between them.
 
 The media bus pixel codes document parallel formats. Should the pixel data be
 transported over a serial bus, the media bus pixel code that describes a
-parallel format that transfers a sample on a single clock cycle is used. For
-instance, both MEDIA_BUS_FMT_BGR888_1X24 and MEDIA_BUS_FMT_BGR888_3X8 are used
-on parallel busses for transferring an 8 bits per sample BGR data, whereas on
-serial busses the data in this format is only referred to using
-MEDIA_BUS_FMT_BGR888_1X24. This is because there is effectively only a single
-way to transport that format on the serial busses.
+parallel format that transfers a sample on a single clock cycle is used. The
+color component order used is the same used on the serial bus. For instance,
+both MEDIA_BUS_FMT_BGR888_1X24 and MEDIA_BUS_FMT_BGR888_3X8 are used on parallel
+busses for transferring an 8 bits per sample BGR data, whereas on serial busses
+the data in this format is only referred to using MEDIA_BUS_FMT_BGR888_1X24,
+with BGR meaning that the blue component is transmitted first, then green, then
+red. This is because there is effectively only a single way to transport that
+format on the serial busses.
 
 Packed RGB Formats
 ^^^^^^^^^^^^^^^^^^
@@ -625,6 +627,43 @@ The following tables list existing packed RGB formats.
       - b\ :sub:`2`
       - b\ :sub:`1`
       - b\ :sub:`0`
+    * .. _MEDIA_BUS_FMT_RGB565_1X24_CPADHI:
+
+      - MEDIA_BUS_FMT_RGB565_1X24_CPADHI
+      - 0x1022
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      - 0
+      - 0
+      - 0
+      - r\ :sub:`4`
+      - r\ :sub:`3`
+      - r\ :sub:`2`
+      - r\ :sub:`1`
+      - r\ :sub:`0`
+      - 0
+      - 0
+      - g\ :sub:`5`
+      - g\ :sub:`4`
+      - g\ :sub:`3`
+      - g\ :sub:`2`
+      - g\ :sub:`1`
+      - g\ :sub:`0`
+      - 0
+      - 0
+      - 0
+      - b\ :sub:`4`
+      - b\ :sub:`3`
+      - b\ :sub:`2`
+      - b\ :sub:`1`
+      - b\ :sub:`0`
     * .. _MEDIA-BUS-FMT-BGR565-2X8-BE:
 
       - MEDIA_BUS_FMT_BGR565_2X8_BE
@@ -913,6 +952,43 @@ The following tables list existing packed RGB formats.
       - g\ :sub:`5`
       - g\ :sub:`4`
       - g\ :sub:`3`
+    * .. _MEDIA-BUS-FMT-BGR666-1X18:
+
+      - MEDIA_BUS_FMT-BGR666_1X18
+      - 0x1023
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      - b\ :sub:`5`
+      - b\ :sub:`4`
+      - b\ :sub:`3`
+      - b\ :sub:`2`
+      - b\ :sub:`1`
+      - b\ :sub:`0`
+      - g\ :sub:`5`
+      - g\ :sub:`4`
+      - g\ :sub:`3`
+      - g\ :sub:`2`
+      - g\ :sub:`1`
+      - g\ :sub:`0`
+      - r\ :sub:`5`
+      - r\ :sub:`4`
+      - r\ :sub:`3`
+      - r\ :sub:`2`
+      - r\ :sub:`1`
+      - r\ :sub:`0`
     * .. _MEDIA-BUS-FMT-RGB666-1X18:
 
       - MEDIA_BUS_FMT_RGB666_1X18
@@ -1096,6 +1172,43 @@ The following tables list existing packed RGB formats.
       - g\ :sub:`2`
       - g\ :sub:`1`
       - g\ :sub:`0`
+    * .. _MEDIA-BUS-FMT-BGR666-1X24_CPADHI:
+
+      - MEDIA_BUS_FMT_BGR666_1X24_CPADHI
+      - 0x1024
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      -
+      - 0
+      - 0
+      - b\ :sub:`5`
+      - b\ :sub:`4`
+      - b\ :sub:`3`
+      - b\ :sub:`2`
+      - b\ :sub:`1`
+      - b\ :sub:`0`
+      - 0
+      - 0
+      - g\ :sub:`5`
+      - g\ :sub:`4`
+      - g\ :sub:`3`
+      - g\ :sub:`2`
+      - g\ :sub:`1`
+      - g\ :sub:`0`
+      - 0
+      - 0
+      - r\ :sub:`5`
+      - r\ :sub:`4`
+      - r\ :sub:`3`
+      - r\ :sub:`2`
+      - r\ :sub:`1`
+      - r\ :sub:`0`
     * .. _MEDIA-BUS-FMT-RGB666-1X24_CPADHI:
 
       - MEDIA_BUS_FMT_RGB666_1X24_CPADHI
@@ -8561,3 +8674,33 @@ and finally the bit number in subscript. "x" indicates a padding bit.
       - x
       - x
       - x
+
+.. _v4l2-mbus-sensor-data:
+
+Sensor Ancillary Metadata Formats
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+This section lists ancillary data generated by a camera sensor and
+transmitted over a stream on the camera bus.
+
+The following table lists the existing sensor ancillary metadata formats:
+
+
+.. _v4l2-mbus-pixelcode-sensor-metadata:
+
+.. tabularcolumns:: |p{8.0cm}|p{1.4cm}|p{7.7cm}|
+
+.. flat-table:: Sensor ancillary metadata formats
+    :header-rows:  1
+    :stub-columns: 0
+
+    * - Identifier
+      - Code
+      - Comments
+    * .. _MEDIA_BUS_FMT_SENSOR_DATA:
+
+      - MEDIA_BUS_FMT_SENSOR_DATA
+      - 0x7001
+      - Sensor vendor specific ancillary metadata. Some vendors follow a generic
+        CSI-2/SMIA embedded data format as described in the `CSI-2 specification.
+	<https://mipi.org/specifications/csi-2>`_
diff --git a/MAINTAINERS b/MAINTAINERS
index d765c62c80ea..3ff326fca129 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1752,6 +1752,22 @@ S:	Maintained
 F:	drivers/net/arcnet/
 F:	include/uapi/linux/if_arcnet.h
 
+ARDUCAM 64MP SENSOR DRIVER
+M:	Arducam Kernel Maintenance <info@arducam.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+T:	git git://linuxtv.org/media_tree.git
+F:	Documentation/devicetree/bindings/media/i2c/arducam,64mp.yaml
+F:	drivers/media/i2c/arducam_64mp.c
+
+ARDUCAM PIVARIETY SENSOR DRIVER
+M:	Arducam Kernel Maintenance <info@arducam.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+T:	git git://linuxtv.org/media_tree.git
+F:	Documentation/devicetree/bindings/media/i2c/arducam-pivariety.yaml
+F:	drivers/media/i2c/arducam-pivariety.c
+
 ARM AND ARM64 SoC SUB-ARCHITECTURES (COMMON PARTS)
 M:	Arnd Bergmann <arnd@arndb.de>
 M:	Olof Johansson <olof@lixom.net>
@@ -4374,6 +4390,22 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/media/brcm,bcm2835-unicam.yaml
 F:	drivers/media/platform/broadcom/bcm2835-unicam*
 
+BROADCOM BCM2835 ISP DRIVER
+M:	Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+F:	Documentation/media/uapi/v4l/pixfmt-meta-bcm2835-isp-stats.rst
+F:	Documentation/media/v4l-drivers/bcm2835-isp.rst
+F:	drivers/staging/vc04_services/bcm2835-isp
+F:	include/uapi/linux/bcm2835-isp.h
+
+BROADCOM BCM2711 HEVC DECODER
+M:	Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/media/rpivid_hevc.jaml
+F:	drivers/staging/media/rpivid
+
 BROADCOM BCM47XX MIPS ARCHITECTURE
 M:	Hauke Mehrtens <hauke@hauke-m.de>
 M:	Rafał Miłecki <zajec5@gmail.com>
@@ -19319,6 +19351,22 @@ L:	linux-edac@vger.kernel.org
 S:	Maintained
 F:	drivers/ras/amd/fmpm.c
 
+RASPBERRY PI RP2040 GPIO BRIDGE DRIVER
+M:	Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
+S:	Maintained
+F:	Documentation/devicetree/bindings/spi/raspberrypi,rp2040-gpio-bridge.yaml
+F:	drivers/spi/spi-rp2040-gpio-bridge.c
+
+RASPBERRY PI HEVC DECODER
+M:	John Cox <john.cox@raspberrypi.com>
+M:	Dom Cobley <dom@raspberrypi.com>
+M:	Dave Stevenson <dave.stevenson@raspberrypi.com>
+M:	Raspberry Pi Internal Kernel List <kernel-list@raspberrypi.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/media/raspberrypi,rpi_hevc_dec.yaml
+F:	drivers/media/platform/raspberrypi/hevc_dec
+
 RASPBERRY PI PISP BACK END
 M:	Jacopo Mondi <jacopo.mondi@ideasonboard.com>
 L:	Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
@@ -20026,6 +20074,13 @@ S:	Supported
 F:	drivers/iio/light/rohm-bu27008.c
 F:	drivers/iio/light/rohm-bu27034.c
 
+ROHM BU64754 MOTOR DRIVER FOR CAMERA AUTOFOCUS
+M:	Kieran Bingham <kieran.bingham@ideasonboard.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+T:	git git://linuxtv.org/media_tree.git
+F:	Documentation/devicetree/bindings/media/i2c/rohm,bu64754.yaml
+
 ROHM MULTIFUNCTION BD9571MWV-M PMIC DEVICE DRIVERS
 M:	Marek Vasut <marek.vasut+renesas@gmail.com>
 L:	linux-kernel@vger.kernel.org
@@ -21546,6 +21601,39 @@ T:	git git://linuxtv.org/media.git
 F:	Documentation/devicetree/bindings/media/i2c/sony,imx415.yaml
 F:	drivers/media/i2c/imx415.c
 
+SONY IMX477 SENSOR DRIVER
+M:	Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+T:	git git://linuxtv.org/media_tree.git
+F:	Documentation/devicetree/bindings/media/i2c/imx378.yaml
+F:	Documentation/devicetree/bindings/media/i2c/imx477.yaml
+F:	drivers/media/i2c/imx477.c
+
+SONY IMX500 SENSOR DRIVER
+M:	Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+T:	git git://linuxtv.org/media_tree.git
+F:	Documentation/devicetree/bindings/media/i2c/sony,imx500.yaml
+F:	drivers/media/i2c/imx500.c
+
+SONY IMX519 SENSOR DRIVER
+M:	Arducam Kernel Maintenance <info@arducam.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+T:	git git://linuxtv.org/media_tree.git
+F:	Documentation/devicetree/bindings/media/i2c/imx519.yaml
+F:	drivers/media/i2c/imx519.c
+
+SONY IMX708 SENSOR DRIVER
+M:	Raspberry Pi Kernel Maintenance <kernel-list@raspberrypi.com>
+L:	linux-media@vger.kernel.org
+S:	Maintained
+T:	git git://linuxtv.org/media_tree.git
+F:	Documentation/devicetree/bindings/media/i2c/sony,imx708.yaml
+F:	drivers/media/i2c/imx708.c
+
 SONY MEMORYSTICK SUBSYSTEM
 M:	Maxim Levitsky <maximlevitsky@gmail.com>
 M:	Alex Dubov <oakad@yahoo.com>
diff --git a/README.md b/README.md
new file mode 100644
index 000000000000..e26e19b02627
--- /dev/null
+++ b/README.md
@@ -0,0 +1,30 @@
+Linux kernel
+============
+
+There are several guides for kernel developers and users. These guides can
+be rendered in a number of formats, like HTML and PDF. Please read
+Documentation/admin-guide/README.rst first.
+
+In order to build the documentation, use ``make htmldocs`` or
+``make pdfdocs``.  The formatted documentation can also be read online at:
+
+    https://www.kernel.org/doc/html/latest/
+
+There are various text files in the Documentation/ subdirectory,
+several of them using the Restructured Text markup notation.
+
+Please read the Documentation/process/changes.rst file, as it contains the
+requirements for building and running the kernel, and information about
+the problems which may result by upgrading your kernel.
+
+Build status for rpi-6.1.y:
+[![Pi kernel build tests](https://github.com/raspberrypi/linux/actions/workflows/kernel-build.yml/badge.svg?branch=rpi-6.1.y)](https://github.com/raspberrypi/linux/actions/workflows/kernel-build.yml)
+[![dtoverlaycheck](https://github.com/raspberrypi/linux/actions/workflows/dtoverlaycheck.yml/badge.svg?branch=rpi-6.1.y)](https://github.com/raspberrypi/linux/actions/workflows/dtoverlaycheck.yml)
+
+Build status for rpi-6.6.y:
+[![Pi kernel build tests](https://github.com/raspberrypi/linux/actions/workflows/kernel-build.yml/badge.svg?branch=rpi-6.6.y)](https://github.com/raspberrypi/linux/actions/workflows/kernel-build.yml)
+[![dtoverlaycheck](https://github.com/raspberrypi/linux/actions/workflows/dtoverlaycheck.yml/badge.svg?branch=rpi-6.6.y)](https://github.com/raspberrypi/linux/actions/workflows/dtoverlaycheck.yml)
+
+Build status for rpi-6.12.y:
+[![Pi kernel build tests](https://github.com/raspberrypi/linux/actions/workflows/kernel-build.yml/badge.svg?branch=rpi-6.12.y)](https://github.com/raspberrypi/linux/actions/workflows/kernel-build.yml)
+[![dtoverlaycheck](https://github.com/raspberrypi/linux/actions/workflows/dtoverlaycheck.yml/badge.svg?branch=rpi-6.12.y)](https://github.com/raspberrypi/linux/actions/workflows/dtoverlaycheck.yml)
diff --git a/arch/arm/boot/dts/Makefile b/arch/arm/boot/dts/Makefile
index efe38eb25301..a2a407fb5b28 100644
--- a/arch/arm/boot/dts/Makefile
+++ b/arch/arm/boot/dts/Makefile
@@ -39,3 +39,8 @@ subdir-y += unisoc
 subdir-y += vt8500
 subdir-y += xen
 subdir-y += xilinx
+
+targets += dtbs dtbs_install
+targets += $(dtb-y)
+
+subdir-y += overlays
diff --git a/arch/arm/boot/dts/broadcom/Makefile b/arch/arm/boot/dts/broadcom/Makefile
index 5881bcc95eba..ac2e95af4011 100644
--- a/arch/arm/boot/dts/broadcom/Makefile
+++ b/arch/arm/boot/dts/broadcom/Makefile
@@ -35,6 +35,43 @@ dtb-$(CONFIG_ARCH_BCM2835) += \
 	bcm2711-rpi-cm4-io.dtb \
 	bcm2835-rpi-zero.dtb \
 	bcm2835-rpi-zero-w.dtb
+
+DTC_FLAGS_bcm2708-rpi-b := -@
+DTC_FLAGS_bcm2708-rpi-b-rev1 := -@
+DTC_FLAGS_bcm2708-rpi-b-plus := -@
+DTC_FLAGS_bcm2708-rpi-cm := -@
+DTC_FLAGS_bcm2708-rpi-zero := -@
+DTC_FLAGS_bcm2708-rpi-zero-w := -@
+DTC_FLAGS_bcm2710-rpi-zero-2 := -@
+DTC_FLAGS_bcm2710-rpi-zero-2-w := -@
+DTC_FLAGS_bcm2709-rpi-2-b := -@
+DTC_FLAGS_bcm2710-rpi-2-b := -@
+DTC_FLAGS_bcm2710-rpi-3-b := -@
+DTC_FLAGS_bcm2710-rpi-3-b-plus := -@
+DTC_FLAGS_bcm2709-rpi-cm2 := -@
+DTC_FLAGS_bcm2710-rpi-cm0 := -@
+DTC_FLAGS_bcm2710-rpi-cm3 := -@
+DTC_FLAGS_bcm2711-rpi-cm4 := -@
+DTC_FLAGS_bcm2711-rpi-cm4s := -@
+dtb-$(CONFIG_ARCH_BCM2835) += \
+        bcm2708-rpi-b.dtb \
+        bcm2708-rpi-b-rev1.dtb \
+        bcm2708-rpi-b-plus.dtb \
+        bcm2708-rpi-cm.dtb \
+        bcm2708-rpi-zero.dtb \
+        bcm2708-rpi-zero-w.dtb \
+        bcm2710-rpi-zero-2.dtb \
+        bcm2710-rpi-zero-2-w.dtb \
+        bcm2709-rpi-2-b.dtb \
+        bcm2710-rpi-2-b.dtb \
+        bcm2710-rpi-3-b.dtb \
+        bcm2710-rpi-3-b-plus.dtb \
+        bcm2709-rpi-cm2.dtb \
+        bcm2710-rpi-cm0.dtb \
+        bcm2710-rpi-cm3.dtb \
+        bcm2711-rpi-cm4.dtb \
+        bcm2711-rpi-cm4s.dtb
+
 dtb-$(CONFIG_ARCH_BCMBCA) += \
 	bcm947622.dtb \
 	bcm963138.dtb \
diff --git a/arch/arm/boot/dts/broadcom/bcm2708-rpi-b-plus.dts b/arch/arm/boot/dts/broadcom/bcm2708-rpi-b-plus.dts
new file mode 100644
index 000000000000..ee72fdac6663
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2708-rpi-b-plus.dts
@@ -0,0 +1,210 @@
+/dts-v1/;
+
+#include "bcm2708.dtsi"
+#include "bcm2708-rpi.dtsi"
+#include "bcm283x-rpi-smsc9514.dtsi"
+#include "bcm283x-rpi-csi1-2lane.dtsi"
+#include "bcm283x-rpi-i2c0mux_0_28.dtsi"
+#include "bcm283x-rpi-led-deprecated.dtsi"
+
+/ {
+	compatible = "raspberrypi,model-b-plus", "brcm,bcm2835";
+	model = "Raspberry Pi Model B+";
+};
+
+&gpio {
+	/*
+	 * Taken from Raspberry-Pi-B-Plus-V1.2-Schematics.pdf
+	 * RPI-BPLUS sheet 1
+	 *
+	 * Legend:
+	 * "NC" = not connected (no rail from the SoC)
+	 * "FOO" = GPIO line named "FOO" on the schematic
+	 * "FOO_N" = GPIO line named "FOO" on schematic, active low
+	 */
+	gpio-line-names = "ID_SDA",
+			  "ID_SCL",
+			  "GPIO2",
+			  "GPIO3",
+			  "GPIO4",
+			  "GPIO5",
+			  "GPIO6",
+			  "GPIO7",
+			  "GPIO8",
+			  "GPIO9",
+			  "GPIO10",
+			  "GPIO11",
+			  "GPIO12",
+			  "GPIO13",
+			  "GPIO14",
+			  "GPIO15",
+			  "GPIO16",
+			  "GPIO17",
+			  "GPIO18",
+			  "GPIO19",
+			  "GPIO20",
+			  "GPIO21",
+			  "GPIO22",
+			  "GPIO23",
+			  "GPIO24",
+			  "GPIO25",
+			  "GPIO26",
+			  "GPIO27",
+			  "SDA0",
+			  "SCL0",
+			  "NC", /* GPIO30 */
+			  "LAN_RUN", /* GPIO31 */
+			  "CAM_GPIO1", /* GPIO32 */
+			  "NC", /* GPIO33 */
+			  "NC", /* GPIO34 */
+			  "PWR_LOW_N", /* GPIO35 */
+			  "NC", /* GPIO36 */
+			  "NC", /* GPIO37 */
+			  "USB_LIMIT", /* GPIO38 */
+			  "NC", /* GPIO39 */
+			  "PWM0_OUT", /* GPIO40 */
+			  "CAM_GPIO0", /* GPIO41 */
+			  "NC", /* GPIO42 */
+			  "NC", /* GPIO43 */
+			  "ETH_CLK", /* GPIO44 */
+			  "PWM1_OUT", /* GPIO45 */
+			  "HDMI_HPD_N",
+			  "STATUS_LED",
+			  /* Used by SD Card */
+			  "SD_CLK_R",
+			  "SD_CMD_R",
+			  "SD_DATA0_R",
+			  "SD_DATA1_R",
+			  "SD_DATA2_R",
+			  "SD_DATA3_R";
+
+	spi0_pins: spi0_pins {
+		brcm,pins = <9 10 11>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	spi0_cs_pins: spi0_cs_pins {
+		brcm,pins = <8 7>;
+		brcm,function = <1>; /* output */
+	};
+
+	i2c0_pins: i2c0 {
+		brcm,pins = <0 1>;
+		brcm,function = <4>;
+	};
+
+	i2c1_pins: i2c1 {
+		brcm,pins = <2 3>;
+		brcm,function = <4>;
+	};
+
+	i2s_pins: i2s {
+		brcm,pins = <18 19 20 21>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	audio_pins: audio_pins {
+		brcm,pins = <40 45>;
+		brcm,function = <4>;
+		brcm,pull = <0>;
+	};
+};
+
+&uart0 {
+	status = "okay";
+};
+
+&spi0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
+	cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
+
+	spidev0: spidev@0{
+		compatible = "spidev";
+		reg = <0>;	/* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+
+	spidev1: spidev@1{
+		compatible = "spidev";
+		reg = <1>;	/* CE1 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+};
+
+&i2c0if {
+	clock-frequency = <100000>;
+};
+
+&i2c1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c1_pins>;
+	clock-frequency = <100000>;
+};
+
+&i2c2 {
+	clock-frequency = <100000>;
+};
+
+&i2s {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2s_pins>;
+};
+
+&led_act {
+	gpios = <&gpio 47 GPIO_ACTIVE_HIGH>;
+	default-state = "off";
+	linux,default-trigger = "mmc0";
+};
+
+&leds {
+	led_pwr: led-pwr {
+		label = "PWR";
+		gpios = <&gpio 35 GPIO_ACTIVE_HIGH>;
+		default-state = "off";
+		linux,default-trigger = "input";
+	};
+};
+
+&hdmi {
+	hpd-gpios = <&gpio 46 GPIO_ACTIVE_LOW>;
+};
+
+&vchiq {
+	pinctrl-names = "default";
+	pinctrl-0 = <&audio_pins>;
+};
+
+&cam1_reg {
+	gpio = <&gpio 41 GPIO_ACTIVE_HIGH>;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+i2c_arm: &i2c1 {
+};
+
+i2c_vc: &i2c0 {
+};
+
+i2c_csi_dsi0: &i2c0 {
+};
+
+/ {
+	__overrides__ {
+		audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_headphones=1 snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_headphones=0 snd_bcm2835.enable_hdmi=0'}";
+
+		act_led_gpio = <&led_act>,"gpios:4";
+		act_led_activelow = <&led_act>,"gpios:8";
+		act_led_trigger = <&led_act>,"linux,default-trigger";
+
+		pwr_led_gpio = <&led_pwr>,"gpios:4";
+		pwr_led_activelow = <&led_pwr>,"gpios:8";
+		pwr_led_trigger = <&led_pwr>,"linux,default-trigger";
+	};
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2708-rpi-b-rev1.dts b/arch/arm/boot/dts/broadcom/bcm2708-rpi-b-rev1.dts
new file mode 100644
index 000000000000..9301e345aea2
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2708-rpi-b-rev1.dts
@@ -0,0 +1,223 @@
+/dts-v1/;
+
+#include "bcm2708.dtsi"
+#include "bcm2708-rpi.dtsi"
+#include "bcm283x-rpi-smsc9512.dtsi"
+#include "bcm283x-rpi-csi1-2lane.dtsi"
+#include "bcm283x-rpi-led-deprecated.dtsi"
+
+/ {
+	compatible = "raspberrypi,model-b", "brcm,bcm2835";
+	model = "Raspberry Pi Model B";
+};
+
+&gpio {
+	/*
+	 * Taken from Raspberry-Pi-Rev-1.0-Model-AB-Schematics.pdf
+	 * RPI00021 sheet 02
+	 *
+	 * Legend:
+	 * "NC" = not connected (no rail from the SoC)
+	 * "FOO" = GPIO line named "FOO" on the schematic
+	 * "FOO_N" = GPIO line named "FOO" on schematic, active low
+	 */
+	gpio-line-names = "SDA0",
+			  "SCL0",
+			  "SDA1",
+			  "SCL1",
+			  "GPIO_GCLK",
+			  "CAM_GPIO1",
+			  "LAN_RUN",
+			  "SPI_CE1_N",
+			  "SPI_CE0_N",
+			  "SPI_MISO",
+			  "SPI_MOSI",
+			  "SPI_SCLK",
+			  "NC", /* GPIO12 */
+			  "NC", /* GPIO13 */
+			  /* Serial port */
+			  "TXD0",
+			  "RXD0",
+			  "STATUS_LED_N",
+			  "GPIO17",
+			  "GPIO18",
+			  "NC", /* GPIO19 */
+			  "NC", /* GPIO20 */
+			  "GPIO21",
+			  "GPIO22",
+			  "GPIO23",
+			  "GPIO24",
+			  "GPIO25",
+			  "NC", /* GPIO26 */
+			  "CAM_GPIO0",
+			  /* Binary number representing build/revision */
+			  "CONFIG0",
+			  "CONFIG1",
+			  "CONFIG2",
+			  "CONFIG3",
+			  "NC", /* GPIO32 */
+			  "NC", /* GPIO33 */
+			  "NC", /* GPIO34 */
+			  "NC", /* GPIO35 */
+			  "NC", /* GPIO36 */
+			  "NC", /* GPIO37 */
+			  "NC", /* GPIO38 */
+			  "NC", /* GPIO39 */
+			  "PWM0_OUT",
+			  "NC", /* GPIO41 */
+			  "NC", /* GPIO42 */
+			  "NC", /* GPIO43 */
+			  "NC", /* GPIO44 */
+			  "PWM1_OUT",
+			  "HDMI_HPD_P",
+			  "SD_CARD_DET",
+			  /* Used by SD Card */
+			  "SD_CLK_R",
+			  "SD_CMD_R",
+			  "SD_DATA0_R",
+			  "SD_DATA1_R",
+			  "SD_DATA2_R",
+			  "SD_DATA3_R";
+
+	spi0_pins: spi0_pins {
+		brcm,pins = <9 10 11>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	spi0_cs_pins: spi0_cs_pins {
+		brcm,pins = <8 7>;
+		brcm,function = <1>; /* output */
+	};
+
+	i2c0_pins: i2c0 {
+		brcm,pins = <0 1>;
+		brcm,function = <4>;
+	};
+
+	i2c1_pins: i2c1 {
+		brcm,pins = <2 3>;
+		brcm,function = <4>;
+	};
+
+	i2s_pins: i2s {
+		brcm,pins = <28 29 30 31>;
+		brcm,function = <6>; /* alt2 */
+	};
+
+	audio_pins: audio_pins {
+		brcm,pins = <40 45>;
+		brcm,function = <4>;
+		brcm,pull = <0>;
+	};
+};
+
+&uart0 {
+	status = "okay";
+};
+
+&spi0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
+	cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
+
+	spidev0: spidev@0{
+		compatible = "spidev";
+		reg = <0>;	/* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+
+	spidev1: spidev@1{
+		compatible = "spidev";
+		reg = <1>;	/* CE1 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+};
+
+/delete-node/ &i2c0mux;
+
+i2c0: &i2c0if {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c0_pins>;
+	clock-frequency = <100000>;
+};
+
+i2c_csi_dsi: &i2c1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c1_pins>;
+	clock-frequency = <100000>;
+};
+
+/ {
+	aliases {
+		i2c0 = &i2c0;
+	};
+
+	/* Provide an i2c0mux label to avoid undefined symbols in overlays */
+	i2c0mux: i2c0mux {
+	};
+
+	__overrides__ {
+		i2c0 = <&i2c0>, "status";
+	};
+};
+
+&i2c2 {
+	clock-frequency = <100000>;
+};
+
+&i2s {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2s_pins>;
+};
+
+&led_act {
+	gpios = <&gpio 16 GPIO_ACTIVE_LOW>;
+	default-state = "off";
+	linux,default-trigger = "mmc0";
+};
+
+&hdmi {
+	hpd-gpios = <&gpio 46 GPIO_ACTIVE_HIGH>;
+};
+
+&vchiq {
+	pinctrl-names = "default";
+	pinctrl-0 = <&audio_pins>;
+};
+
+&cam1_reg {
+	gpio = <&gpio 27 GPIO_ACTIVE_HIGH>;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+i2c_arm: &i2c0 {
+};
+
+i2c_vc: &i2c1 {
+};
+
+i2c_csi_dsi0: &i2c0 {
+};
+
+/ {
+	__overrides__ {
+		audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_headphones=1 snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_headphones=0 snd_bcm2835.enable_hdmi=0'}";
+
+		act_led_gpio = <&led_act>,"gpios:4";
+		act_led_activelow = <&led_act>,"gpios:8";
+		act_led_trigger = <&led_act>,"linux,default-trigger";
+
+		i2c = <&i2c0>,"status";
+		i2c_arm = <&i2c0>,"status";
+		i2c_vc = <&i2c1>,"status";
+		i2c_baudrate = <&i2c0>,"clock-frequency:0";
+		i2c_arm_baudrate = <&i2c0>,"clock-frequency:0";
+		i2c_vc_baudrate = <&i2c1>,"clock-frequency:0";
+	};
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2708-rpi-b.dts b/arch/arm/boot/dts/broadcom/bcm2708-rpi-b.dts
new file mode 100644
index 000000000000..b8459fd0f497
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2708-rpi-b.dts
@@ -0,0 +1,198 @@
+/dts-v1/;
+
+#include "bcm2708.dtsi"
+#include "bcm2708-rpi.dtsi"
+#include "bcm283x-rpi-smsc9512.dtsi"
+#include "bcm283x-rpi-csi1-2lane.dtsi"
+#include "bcm283x-rpi-i2c0mux_0_28.dtsi"
+#include "bcm283x-rpi-led-deprecated.dtsi"
+
+/ {
+	compatible = "raspberrypi,model-b", "brcm,bcm2835";
+	model = "Raspberry Pi Model B";
+};
+
+&gpio {
+	/*
+	 * Taken from Raspberry-Pi-Rev-2.0-Model-AB-Schematics.pdf
+	 * RPI00022 sheet 02
+	 *
+	 * Legend:
+	 * "NC" = not connected (no rail from the SoC)
+	 * "FOO" = GPIO line named "FOO" on the schematic
+	 * "FOO_N" = GPIO line named "FOO" on schematic, active low
+	 */
+	gpio-line-names = "SDA0",
+			  "SCL0",
+			  "SDA1",
+			  "SCL1",
+			  "GPIO_GCLK",
+			  "CAM_GPIO1",
+			  "LAN_RUN",
+			  "SPI_CE1_N",
+			  "SPI_CE0_N",
+			  "SPI_MISO",
+			  "SPI_MOSI",
+			  "SPI_SCLK",
+			  "NC", /* GPIO12 */
+			  "NC", /* GPIO13 */
+			  /* Serial port */
+			  "TXD0",
+			  "RXD0",
+			  "STATUS_LED_N",
+			  "GPIO17",
+			  "GPIO18",
+			  "NC", /* GPIO19 */
+			  "NC", /* GPIO20 */
+			  "CAM_GPIO0",
+			  "GPIO22",
+			  "GPIO23",
+			  "GPIO24",
+			  "GPIO25",
+			  "NC", /* GPIO26 */
+			  "GPIO27",
+			  "GPIO28",
+			  "GPIO29",
+			  "GPIO30",
+			  "GPIO31",
+			  "NC", /* GPIO32 */
+			  "NC", /* GPIO33 */
+			  "NC", /* GPIO34 */
+			  "NC", /* GPIO35 */
+			  "NC", /* GPIO36 */
+			  "NC", /* GPIO37 */
+			  "NC", /* GPIO38 */
+			  "NC", /* GPIO39 */
+			  "PWM0_OUT",
+			  "NC", /* GPIO41 */
+			  "NC", /* GPIO42 */
+			  "NC", /* GPIO43 */
+			  "NC", /* GPIO44 */
+			  "PWM1_OUT",
+			  "HDMI_HPD_P",
+			  "SD_CARD_DET",
+			  /* Used by SD Card */
+			  "SD_CLK_R",
+			  "SD_CMD_R",
+			  "SD_DATA0_R",
+			  "SD_DATA1_R",
+			  "SD_DATA2_R",
+			  "SD_DATA3_R";
+
+	spi0_pins: spi0_pins {
+		brcm,pins = <9 10 11>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	spi0_cs_pins: spi0_cs_pins {
+		brcm,pins = <8 7>;
+		brcm,function = <1>; /* output */
+	};
+
+	i2c0_pins: i2c0 {
+		brcm,pins = <0 1>;
+		brcm,function = <4>;
+	};
+
+	i2c1_pins: i2c1 {
+		brcm,pins = <2 3>;
+		brcm,function = <4>;
+	};
+
+	i2s_pins: i2s {
+		brcm,pins = <28 29 30 31>;
+		brcm,function = <6>; /* alt2 */
+	};
+
+	audio_pins: audio_pins {
+		brcm,pins = <40 45>;
+		brcm,function = <4>;
+		brcm,pull = <0>;
+	};
+};
+
+&uart0 {
+	status = "okay";
+};
+
+&spi0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
+	cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
+
+	spidev0: spidev@0{
+		compatible = "spidev";
+		reg = <0>;	/* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+
+	spidev1: spidev@1{
+		compatible = "spidev";
+		reg = <1>;	/* CE1 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+};
+
+&i2c0if {
+	clock-frequency = <100000>;
+};
+
+&i2c1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c1_pins>;
+	clock-frequency = <100000>;
+};
+
+&i2c2 {
+	clock-frequency = <100000>;
+};
+
+&i2s {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2s_pins>;
+};
+
+&led_act {
+	gpios = <&gpio 16 GPIO_ACTIVE_LOW>;
+	default-state = "off";
+	linux,default-trigger = "mmc0";
+};
+
+&hdmi {
+	hpd-gpios = <&gpio 46 GPIO_ACTIVE_HIGH>;
+};
+
+&vchiq {
+	pinctrl-names = "default";
+	pinctrl-0 = <&audio_pins>;
+};
+
+&cam1_reg {
+	gpio = <&gpio 21 GPIO_ACTIVE_HIGH>;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+i2c_arm: &i2c1 {
+};
+
+i2c_vc: &i2c0 {
+};
+
+i2c_csi_dsi0: &i2c0 {
+};
+
+/ {
+	__overrides__ {
+		audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_headphones=1 snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_headphones=0 snd_bcm2835.enable_hdmi=0'}";
+
+		act_led_gpio = <&led_act>,"gpios:4";
+		act_led_activelow = <&led_act>,"gpios:8";
+		act_led_trigger = <&led_act>,"linux,default-trigger";
+	};
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2708-rpi-bt.dtsi b/arch/arm/boot/dts/broadcom/bcm2708-rpi-bt.dtsi
new file mode 100644
index 000000000000..87a6c00bd056
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2708-rpi-bt.dtsi
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0
+
+&uart0 {
+	bt: bluetooth {
+		compatible = "brcm,bcm43438-bt";
+		max-speed = <3000000>;
+		shutdown-gpios = <&gpio 45 GPIO_ACTIVE_HIGH>;
+		local-bd-address = [ 00 00 00 00 00 00 ];
+		fallback-bd-address; // Don't override a valid address
+		status = "okay";
+	};
+};
+
+&uart1 {
+	minibt: bluetooth {
+		compatible = "brcm,bcm43438-bt";
+		max-speed = <230400>;
+		shutdown-gpios = <&gpio 45 GPIO_ACTIVE_HIGH>;
+		local-bd-address = [ 00 00 00 00 00 00 ];
+		fallback-bd-address; // Don't override a valid address
+		status = "disabled";
+	};
+};
+
+/ {
+	chosen {
+		bootargs = "coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_headphones=0 cgroup_disable=memory";
+	};
+
+	aliases {
+		bluetooth = &bt;
+	};
+
+	__overrides__ {
+		bdaddr = <&bt>,"local-bd-address[",
+		       <&bt>,"fallback-bd-address?=0",
+		       <&minibt>,"local-bd-address[",
+		       <&minibt>,"fallback-bd-address?=0";
+		krnbt = <&bt>,"status";
+		krnbt_baudrate = <&bt>,"max-speed:0", <&minibt>,"max-speed:0";
+	};
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2708-rpi-cm.dts b/arch/arm/boot/dts/broadcom/bcm2708-rpi-cm.dts
new file mode 100644
index 000000000000..fde85c8c7dca
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2708-rpi-cm.dts
@@ -0,0 +1,174 @@
+/dts-v1/;
+
+#include "bcm2708-rpi-cm.dtsi"
+#include "bcm283x-rpi-csi0-2lane.dtsi"
+#include "bcm283x-rpi-csi1-4lane.dtsi"
+#include "bcm283x-rpi-i2c0mux_0_28.dtsi"
+
+/ {
+	compatible = "raspberrypi,compute-module", "brcm,bcm2835";
+	model = "Raspberry Pi Compute Module";
+};
+
+&cam1_reg {
+	gpio = <&gpio 3 GPIO_ACTIVE_HIGH>;
+	status = "disabled";
+};
+
+cam0_reg: &cam0_regulator {
+	gpio = <&gpio 31 GPIO_ACTIVE_HIGH>;
+};
+
+i2c_csi_dsi0: &i2c0 {
+};
+
+&uart0 {
+	status = "okay";
+};
+
+&gpio {
+	/*
+	 * This is based on the official GPU firmware DT blob.
+	 *
+	 * Legend:
+	 * "NC" = not connected (no rail from the SoC)
+	 * "FOO" = GPIO line named "FOO" on the schematic
+	 * "FOO_N" = GPIO line named "FOO" on schematic, active low
+	 */
+	gpio-line-names = "GPIO0",
+			  "GPIO1",
+			  "GPIO2",
+			  "GPIO3",
+			  "GPIO4",
+			  "GPIO5",
+			  "GPIO6",
+			  "GPIO7",
+			  "GPIO8",
+			  "GPIO9",
+			  "GPIO10",
+			  "GPIO11",
+			  "GPIO12",
+			  "GPIO13",
+			  "GPIO14",
+			  "GPIO15",
+			  "GPIO16",
+			  "GPIO17",
+			  "GPIO18",
+			  "GPIO19",
+			  "GPIO20",
+			  "GPIO21",
+			  "GPIO22",
+			  "GPIO23",
+			  "GPIO24",
+			  "GPIO25",
+			  "GPIO26",
+			  "GPIO27",
+			  "GPIO28",
+			  "GPIO29",
+			  "GPIO30",
+			  "GPIO31",
+			  "GPIO32",
+			  "GPIO33",
+			  "GPIO34",
+			  "GPIO35",
+			  "GPIO36",
+			  "GPIO37",
+			  "GPIO38",
+			  "GPIO39",
+			  "GPIO40",
+			  "GPIO41",
+			  "GPIO42",
+			  "GPIO43",
+			  "GPIO44",
+			  "GPIO45",
+			  "HDMI_HPD_N",
+			  /* Also used as ACT LED */
+			  "EMMC_EN_N",
+			  /* Used by eMMC */
+			  "SD_CLK_R",
+			  "SD_CMD_R",
+			  "SD_DATA0_R",
+			  "SD_DATA1_R",
+			  "SD_DATA2_R",
+			  "SD_DATA3_R";
+
+	spi0_pins: spi0_pins {
+		brcm,pins = <9 10 11>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	spi0_cs_pins: spi0_cs_pins {
+		brcm,pins = <8 7>;
+		brcm,function = <1>; /* output */
+	};
+
+	i2c0_pins: i2c0 {
+		brcm,pins = <0 1>;
+		brcm,function = <4>;
+	};
+
+	i2c1_pins: i2c1 {
+		brcm,pins = <2 3>;
+		brcm,function = <4>;
+	};
+
+	i2s_pins: i2s {
+		brcm,pins = <18 19 20 21>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	audio_pins: audio_pins {
+		brcm,pins;
+		brcm,function;
+	};
+};
+
+&spi0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
+	cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
+
+	spidev0: spidev@0{
+		compatible = "spidev";
+		reg = <0>;	/* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+
+	spidev1: spidev@1{
+		compatible = "spidev";
+		reg = <1>;	/* CE1 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+};
+
+&i2c0if {
+	clock-frequency = <100000>;
+};
+
+&i2c1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c1_pins>;
+	clock-frequency = <100000>;
+};
+
+&i2c2 {
+	clock-frequency = <100000>;
+};
+
+&i2s {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2s_pins>;
+};
+
+&vchiq {
+	pinctrl-names = "default";
+	pinctrl-0 = <&audio_pins>;
+};
+
+&hdmi {
+	hpd-gpios = <&gpio 46 GPIO_ACTIVE_HIGH>;
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2708-rpi-cm.dtsi b/arch/arm/boot/dts/broadcom/bcm2708-rpi-cm.dtsi
new file mode 100644
index 000000000000..8d3e42bfe4f0
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2708-rpi-cm.dtsi
@@ -0,0 +1,23 @@
+#include "bcm2708.dtsi"
+#include "bcm2708-rpi.dtsi"
+#include "bcm283x-rpi-led-deprecated.dtsi"
+
+&led_act {
+	gpios = <&gpio 47 GPIO_ACTIVE_HIGH>;
+	default-state = "off";
+	linux,default-trigger = "mmc0";
+};
+
+i2c_arm: &i2c1 {
+};
+
+i2c_vc: &i2c0 {
+};
+
+/ {
+	__overrides__ {
+		act_led_gpio = <&led_act>,"gpios:4";
+		act_led_activelow = <&led_act>,"gpios:8";
+		act_led_trigger = <&led_act>,"linux,default-trigger";
+	};
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2708-rpi-zero-w.dts b/arch/arm/boot/dts/broadcom/bcm2708-rpi-zero-w.dts
new file mode 100644
index 000000000000..f6d4e2c73df9
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2708-rpi-zero-w.dts
@@ -0,0 +1,250 @@
+/dts-v1/;
+
+#include "bcm2708.dtsi"
+#include "bcm2708-rpi.dtsi"
+#include "bcm283x-rpi-csi1-2lane.dtsi"
+#include "bcm283x-rpi-i2c0mux_0_28.dtsi"
+#include "bcm2708-rpi-bt.dtsi"
+#include "bcm283x-rpi-led-deprecated.dtsi"
+
+/ {
+	compatible = "raspberrypi,model-zero-w", "brcm,bcm2835";
+	model = "Raspberry Pi Zero W";
+
+	aliases {
+		serial0 = &uart1;
+		serial1 = &uart0;
+		mmc1 = &mmcnr;
+	};
+};
+
+&gpio {
+	/*
+	 * This is based on the official GPU firmware DT blob.
+	 *
+	 * Legend:
+	 * "NC" = not connected (no rail from the SoC)
+	 * "FOO" = GPIO line named "FOO" on the schematic
+	 * "FOO_N" = GPIO line named "FOO" on schematic, active low
+	 */
+	gpio-line-names = "ID_SDA",
+			  "ID_SCL",
+			  "GPIO2",
+			  "GPIO3",
+			  "GPIO4",
+			  "GPIO5",
+			  "GPIO6",
+			  "GPIO7",
+			  "GPIO8",
+			  "GPIO9",
+			  "GPIO10",
+			  "GPIO11",
+			  "GPIO12",
+			  "GPIO13",
+			  "GPIO14",
+			  "GPIO15",
+			  "GPIO16",
+			  "GPIO17",
+			  "GPIO18",
+			  "GPIO19",
+			  "GPIO20",
+			  "GPIO21",
+			  "GPIO22",
+			  "GPIO23",
+			  "GPIO24",
+			  "GPIO25",
+			  "GPIO26",
+			  "GPIO27",
+			  "SDA0",
+			  "SCL0",
+			  /* Used by BT module */
+			  "CTS0",
+			  "RTS0",
+			  "TXD0",
+			  "RXD0",
+			  /* Used by Wifi */
+			  "SD1_CLK",
+			  "SD1_CMD",
+			  "SD1_DATA0",
+			  "SD1_DATA1",
+			  "SD1_DATA2",
+			  "SD1_DATA3",
+			  "CAM_GPIO1", /* GPIO40 */
+			  "WL_ON", /* GPIO41 */
+			  "NC", /* GPIO42 */
+			  "WIFI_CLK", /* GPIO43 */
+			  "CAM_GPIO0", /* GPIO44 */
+			  "BT_ON", /* GPIO45 */
+			  "HDMI_HPD_N",
+			  "STATUS_LED_N",
+			  /* Used by SD Card */
+			  "SD_CLK_R",
+			  "SD_CMD_R",
+			  "SD_DATA0_R",
+			  "SD_DATA1_R",
+			  "SD_DATA2_R",
+			  "SD_DATA3_R";
+
+	spi0_pins: spi0_pins {
+		brcm,pins = <9 10 11>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	spi0_cs_pins: spi0_cs_pins {
+		brcm,pins = <8 7>;
+		brcm,function = <1>; /* output */
+	};
+
+	i2c0_pins: i2c0 {
+		brcm,pins = <0 1>;
+		brcm,function = <4>;
+	};
+
+	i2c1_pins: i2c1 {
+		brcm,pins = <2 3>;
+		brcm,function = <4>;
+	};
+
+	i2s_pins: i2s {
+		brcm,pins = <18 19 20 21>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	sdio_pins: sdio_pins {
+		brcm,pins = <34 35 36 37 38 39>;
+		brcm,function = <7>; /* ALT3 = SD1 */
+		brcm,pull = <0 2 2 2 2 2>;
+	};
+
+	bt_pins: bt_pins {
+		brcm,pins = <43>;
+		brcm,function = <4>; /* alt0:GPCLK2 */
+		brcm,pull = <0>; /* none */
+	};
+
+	uart0_pins: uart0_pins {
+		brcm,pins = <30 31 32 33>;
+		brcm,function = <7>; /* alt3=UART0 */
+		brcm,pull = <2 0 0 2>; /* up none none up */
+	};
+
+	uart1_pins: uart1_pins {
+		brcm,pins;
+		brcm,function;
+		brcm,pull;
+	};
+
+	uart1_bt_pins: uart1_bt_pins {
+		brcm,pins = <32 33 30 31>;
+		brcm,function = <BCM2835_FSEL_ALT5>; /* alt5=UART1 */
+		brcm,pull = <0 2 2 0>;
+	};
+
+	audio_pins: audio_pins {
+		brcm,pins = <>;
+		brcm,function = <>;
+	};
+};
+
+&mmcnr {
+	pinctrl-names = "default";
+	pinctrl-0 = <&sdio_pins>;
+	bus-width = <4>;
+	status = "okay";
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	brcmf: wifi@1 {
+		reg = <1>;
+		compatible = "brcm,bcm4329-fmac";
+	};
+};
+
+&uart0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&uart0_pins &bt_pins>;
+	status = "okay";
+};
+
+&uart1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&uart1_pins>;
+	status = "okay";
+};
+
+&spi0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
+	cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
+
+	spidev0: spidev@0{
+		compatible = "spidev";
+		reg = <0>;	/* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+
+	spidev1: spidev@1{
+		compatible = "spidev";
+		reg = <1>;	/* CE1 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+};
+
+&i2c0if {
+	clock-frequency = <100000>;
+};
+
+&i2c1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c1_pins>;
+	clock-frequency = <100000>;
+};
+
+&i2c2 {
+	clock-frequency = <100000>;
+};
+
+&i2s {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2s_pins>;
+};
+
+&led_act {
+	gpios = <&gpio 47 GPIO_ACTIVE_LOW>;
+	default-state = "off";
+	linux,default-trigger = "actpwr";
+};
+
+&hdmi {
+	hpd-gpios = <&gpio 46 GPIO_ACTIVE_LOW>;
+};
+
+&vchiq {
+	pinctrl-names = "default";
+	pinctrl-0 = <&audio_pins>;
+};
+
+&cam1_reg {
+	gpio = <&gpio 44 GPIO_ACTIVE_HIGH>;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+i2c_arm: &i2c1 {};
+i2c_vc: &i2c0 {};
+i2c_csi_dsi0: &i2c0 {};
+
+/ {
+	__overrides__ {
+		audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_hdmi=0'}";
+
+		act_led_gpio = <&led_act>,"gpios:4";
+		act_led_activelow = <&led_act>,"gpios:8";
+		act_led_trigger = <&led_act>,"linux,default-trigger";
+	};
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2708-rpi-zero.dts b/arch/arm/boot/dts/broadcom/bcm2708-rpi-zero.dts
new file mode 100644
index 000000000000..1721be8dbe20
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2708-rpi-zero.dts
@@ -0,0 +1,189 @@
+/dts-v1/;
+
+#include "bcm2708.dtsi"
+#include "bcm2708-rpi.dtsi"
+#include "bcm283x-rpi-csi1-2lane.dtsi"
+#include "bcm283x-rpi-i2c0mux_0_28.dtsi"
+#include "bcm283x-rpi-led-deprecated.dtsi"
+
+/ {
+	compatible = "raspberrypi,model-zero", "brcm,bcm2835";
+	model = "Raspberry Pi Zero";
+};
+
+&gpio {
+	/*
+	 * This is based on the official GPU firmware DT blob.
+	 *
+	 * Legend:
+	 * "NC" = not connected (no rail from the SoC)
+	 * "FOO" = GPIO line named "FOO" on the schematic
+	 * "FOO_N" = GPIO line named "FOO" on schematic, active low
+	 */
+	gpio-line-names = "ID_SDA",
+			  "ID_SCL",
+			  "GPIO2",
+			  "GPIO3",
+			  "GPIO4",
+			  "GPIO5",
+			  "GPIO6",
+			  "GPIO7",
+			  "GPIO8",
+			  "GPIO9",
+			  "GPIO10",
+			  "GPIO11",
+			  "GPIO12",
+			  "GPIO13",
+			  "GPIO14",
+			  "GPIO15",
+			  "GPIO16",
+			  "GPIO17",
+			  "GPIO18",
+			  "GPIO19",
+			  "GPIO20",
+			  "GPIO21",
+			  "GPIO22",
+			  "GPIO23",
+			  "GPIO24",
+			  "GPIO25",
+			  "GPIO26",
+			  "GPIO27",
+			  "SDA0",
+			  "SCL0",
+			  "NC", /* GPIO30 */
+			  "NC", /* GPIO31 */
+			  "CAM_GPIO1", /* GPIO32 */
+			  "NC", /* GPIO33 */
+			  "NC", /* GPIO34 */
+			  "NC", /* GPIO35 */
+			  "NC", /* GPIO36 */
+			  "NC", /* GPIO37 */
+			  "NC", /* GPIO38 */
+			  "NC", /* GPIO39 */
+			  "NC", /* GPIO40 */
+			  "CAM_GPIO0", /* GPIO41 */
+			  "NC", /* GPIO42 */
+			  "NC", /* GPIO43 */
+			  "NC", /* GPIO44 */
+			  "NC", /* GPIO45 */
+			  "HDMI_HPD_N",
+			  "STATUS_LED_N",
+			  /* Used by SD Card */
+			  "SD_CLK_R",
+			  "SD_CMD_R",
+			  "SD_DATA0_R",
+			  "SD_DATA1_R",
+			  "SD_DATA2_R",
+			  "SD_DATA3_R";
+
+	spi0_pins: spi0_pins {
+		brcm,pins = <9 10 11>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	spi0_cs_pins: spi0_cs_pins {
+		brcm,pins = <8 7>;
+		brcm,function = <1>; /* output */
+	};
+
+	i2c0_pins: i2c0 {
+		brcm,pins = <0 1>;
+		brcm,function = <4>;
+	};
+
+	i2c1_pins: i2c1 {
+		brcm,pins = <2 3>;
+		brcm,function = <4>;
+	};
+
+	i2s_pins: i2s {
+		brcm,pins = <18 19 20 21>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	audio_pins: audio_pins {
+		brcm,pins = <>;
+		brcm,function = <>;
+	};
+};
+
+&uart0 {
+	status = "okay";
+};
+
+&spi0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
+	cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
+
+	spidev0: spidev@0{
+		compatible = "spidev";
+		reg = <0>;	/* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+
+	spidev1: spidev@1{
+		compatible = "spidev";
+		reg = <1>;	/* CE1 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+};
+
+&i2c0if {
+	clock-frequency = <100000>;
+};
+
+&i2c1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c1_pins>;
+	clock-frequency = <100000>;
+};
+
+&i2c2 {
+	clock-frequency = <100000>;
+};
+
+&i2s {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2s_pins>;
+};
+
+&led_act {
+	gpios = <&gpio 47 GPIO_ACTIVE_LOW>;
+	default-state = "off";
+	linux,default-trigger = "actpwr";
+};
+
+&hdmi {
+	hpd-gpios = <&gpio 46 GPIO_ACTIVE_LOW>;
+};
+
+&vchiq {
+	pinctrl-names = "default";
+	pinctrl-0 = <&audio_pins>;
+};
+
+&cam1_reg {
+	gpio = <&gpio 41 GPIO_ACTIVE_HIGH>;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+i2c_arm: &i2c1 {};
+i2c_vc: &i2c0 {};
+i2c_csi_dsi0: &i2c0 {};
+
+/ {
+	__overrides__ {
+		audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_hdmi=0'}";
+
+		act_led_gpio = <&led_act>,"gpios:4";
+		act_led_activelow = <&led_act>,"gpios:8";
+		act_led_trigger = <&led_act>,"linux,default-trigger";
+	};
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2708-rpi.dtsi b/arch/arm/boot/dts/broadcom/bcm2708-rpi.dtsi
new file mode 100644
index 000000000000..f4aedb5c532b
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2708-rpi.dtsi
@@ -0,0 +1,63 @@
+/* Downstream modifications common to bcm2835, bcm2836, bcm2837 */
+
+#define i2c0 i2c0mux
+#include "bcm2835-rpi.dtsi"
+#undef i2c0
+#include "bcm270x-rpi.dtsi"
+
+/ {
+	memory@0 {
+		device_type = "memory";
+		reg = <0x0 0x0>;
+	};
+
+	aliases {
+		i2c2 = &i2c2;
+	};
+
+	__overrides__ {
+		hdmi = <&hdmi>,"status";
+		i2c2_iknowwhatimdoing = <&i2c2>,"status";
+		i2c2_baudrate = <&i2c2>,"clock-frequency:0";
+		nvmem_cust_rw = <&nvmem_cust>,"rw?";
+		sd = <&sdhost>,"status";
+		sd_poll_once = <&sdhost>,"non-removable?";
+	};
+};
+
+&soc {
+	nvmem {
+		compatible = "simple-bus";
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		nvmem_otp: nvmem_otp {
+			compatible = "raspberrypi,rpi-otp";
+			firmware = <&firmware>;
+			reg = <0 192>;
+			status = "okay";
+		};
+
+		nvmem_cust: nvmem_cust {
+			compatible = "raspberrypi,rpi-otp";
+			firmware = <&firmware>;
+			reg = <1 8>;
+			status = "okay";
+		};
+	};
+};
+
+&sdhost {
+	pinctrl-names = "default";
+	pinctrl-0 = <&sdhost_gpio48>;
+	status = "okay";
+};
+
+&hdmi {
+	power-domains = <&power RPI_POWER_DOMAIN_HDMI>;
+	status = "disabled";
+};
+
+&i2c2 {
+	status = "disabled";
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2708.dtsi b/arch/arm/boot/dts/broadcom/bcm2708.dtsi
new file mode 100644
index 000000000000..fdc7f2423bbe
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2708.dtsi
@@ -0,0 +1,19 @@
+#define i2c0 i2c0if
+#include "bcm2835.dtsi"
+#undef i2c0
+#include "bcm270x.dtsi"
+
+/ {
+	__overrides__ {
+		arm_freq;
+	};
+};
+
+&soc {
+	dma-ranges = <0x80000000 0x00000000 0x20000000>,
+		     <0x7e000000 0x20000000 0x02000000>;
+};
+
+&vc4 {
+	status = "disabled";
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2709-rpi-2-b.dts b/arch/arm/boot/dts/broadcom/bcm2709-rpi-2-b.dts
new file mode 100644
index 000000000000..7796e545da43
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2709-rpi-2-b.dts
@@ -0,0 +1,204 @@
+/dts-v1/;
+
+#include "bcm2709.dtsi"
+#include "bcm2709-rpi.dtsi"
+#include "bcm283x-rpi-smsc9514.dtsi"
+#include "bcm283x-rpi-csi1-2lane.dtsi"
+#include "bcm283x-rpi-i2c0mux_0_28.dtsi"
+#include "bcm283x-rpi-led-deprecated.dtsi"
+
+/ {
+	compatible = "raspberrypi,2-model-b", "brcm,bcm2836";
+	model = "Raspberry Pi 2 Model B";
+};
+
+&gpio {
+	/*
+	 * Taken from rpi_SCH_2b_1p2_reduced.pdf and
+	 * the official GPU firmware DT blob.
+	 *
+	 * Legend:
+	 * "NC" = not connected (no rail from the SoC)
+	 * "FOO" = GPIO line named "FOO" on the schematic
+	 * "FOO_N" = GPIO line named "FOO" on schematic, active low
+	 */
+	gpio-line-names = "ID_SDA",
+			  "ID_SCL",
+			  "GPIO2",
+			  "GPIO3",
+			  "GPIO4",
+			  "GPIO5",
+			  "GPIO6",
+			  "GPIO7",
+			  "GPIO8",
+			  "GPIO9",
+			  "GPIO10",
+			  "GPIO11",
+			  "GPIO12",
+			  "GPIO13",
+			  "GPIO14",
+			  "GPIO15",
+			  "GPIO16",
+			  "GPIO17",
+			  "GPIO18",
+			  "GPIO19",
+			  "GPIO20",
+			  "GPIO21",
+			  "GPIO22",
+			  "GPIO23",
+			  "GPIO24",
+			  "GPIO25",
+			  "GPIO26",
+			  "GPIO27",
+			  "SDA0",
+			  "SCL0",
+			  "NC", /* GPIO30 */
+			  "LAN_RUN",
+			  "CAM_GPIO1",
+			  "NC", /* GPIO33 */
+			  "NC", /* GPIO34 */
+			  "PWR_LOW_N",
+			  "NC", /* GPIO36 */
+			  "NC", /* GPIO37 */
+			  "USB_LIMIT",
+			  "NC", /* GPIO39 */
+			  "PWM0_OUT",
+			  "CAM_GPIO0",
+			  "SMPS_SCL",
+			  "SMPS_SDA",
+			  "ETH_CLK",
+			  "PWM1_OUT",
+			  "HDMI_HPD_N",
+			  "STATUS_LED",
+			  /* Used by SD Card */
+			  "SD_CLK_R",
+			  "SD_CMD_R",
+			  "SD_DATA0_R",
+			  "SD_DATA1_R",
+			  "SD_DATA2_R",
+			  "SD_DATA3_R";
+
+	spi0_pins: spi0_pins {
+		brcm,pins = <9 10 11>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	spi0_cs_pins: spi0_cs_pins {
+		brcm,pins = <8 7>;
+		brcm,function = <1>; /* output */
+	};
+
+	i2c0_pins: i2c0 {
+		brcm,pins = <0 1>;
+		brcm,function = <4>;
+	};
+
+	i2c1_pins: i2c1 {
+		brcm,pins = <2 3>;
+		brcm,function = <4>;
+	};
+
+	i2s_pins: i2s {
+		brcm,pins = <18 19 20 21>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	audio_pins: audio_pins {
+		brcm,pins = <40 45>;
+		brcm,function = <4>;
+		brcm,pull = <0>;
+	};
+};
+
+&uart0 {
+	status = "okay";
+};
+
+&spi0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
+	cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
+
+	spidev0: spidev@0{
+		compatible = "spidev";
+		reg = <0>;	/* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+
+	spidev1: spidev@1{
+		compatible = "spidev";
+		reg = <1>;	/* CE1 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+};
+
+&i2c0if {
+	clock-frequency = <100000>;
+};
+
+&i2c1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c1_pins>;
+	clock-frequency = <100000>;
+};
+
+&i2c2 {
+	clock-frequency = <100000>;
+};
+
+&i2s {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2s_pins>;
+};
+
+&led_act {
+	gpios = <&gpio 47 GPIO_ACTIVE_HIGH>;
+	default-state = "off";
+	linux,default-trigger = "mmc0";
+};
+
+&leds {
+	led_pwr: led-pwr {
+		label = "PWR";
+		gpios = <&gpio 35 GPIO_ACTIVE_HIGH>;
+		default-state = "off";
+		linux,default-trigger = "input";
+	};
+};
+
+&hdmi {
+	hpd-gpios = <&gpio 46 GPIO_ACTIVE_LOW>;
+};
+
+&vchiq {
+	pinctrl-names = "default";
+	pinctrl-0 = <&audio_pins>;
+};
+
+&cam1_reg {
+	gpio = <&gpio 41 GPIO_ACTIVE_HIGH>;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+i2c_csi_dsi0: &i2c0 {
+};
+
+/ {
+	__overrides__ {
+		audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_headphones=1 snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_headphones=0 snd_bcm2835.enable_hdmi=0'}";
+
+		act_led_gpio = <&led_act>,"gpios:4";
+		act_led_activelow = <&led_act>,"gpios:8";
+		act_led_trigger = <&led_act>,"linux,default-trigger";
+
+		pwr_led_gpio = <&led_pwr>,"gpios:4";
+		pwr_led_activelow = <&led_pwr>,"gpios:8";
+		pwr_led_trigger = <&led_pwr>,"linux,default-trigger";
+	};
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2709-rpi-cm2.dts b/arch/arm/boot/dts/broadcom/bcm2709-rpi-cm2.dts
new file mode 100644
index 000000000000..36d00aa889a3
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2709-rpi-cm2.dts
@@ -0,0 +1,215 @@
+/dts-v1/;
+
+#include "bcm2709.dtsi"
+#include "bcm2709-rpi.dtsi"
+#include "bcm283x-rpi-csi0-2lane.dtsi"
+#include "bcm283x-rpi-csi1-4lane.dtsi"
+#include "bcm283x-rpi-i2c0mux_0_28.dtsi"
+#include "bcm283x-rpi-led-deprecated.dtsi"
+
+/ {
+	compatible = "raspberrypi,2-compute-module", "brcm,bcm2836";
+	model = "Raspberry Pi Compute Module 2";
+};
+
+&cam1_reg {
+	gpio = <&gpio 2 GPIO_ACTIVE_HIGH>;
+	status = "disabled";
+};
+
+cam0_reg: &cam0_regulator {
+	gpio = <&gpio 30 GPIO_ACTIVE_HIGH>;
+};
+
+i2c_csi_dsi0: &i2c0 {
+};
+
+&uart0 {
+	status = "okay";
+};
+
+&gpio {
+	/*
+	 * This is based on the official GPU firmware DT blob.
+	 *
+	 * Legend:
+	 * "NC" = not connected (no rail from the SoC)
+	 * "FOO" = GPIO line named "FOO" on the schematic
+	 * "FOO_N" = GPIO line named "FOO" on schematic, active low
+	 */
+	gpio-line-names = "GPIO0",
+			  "GPIO1",
+			  "GPIO2",
+			  "GPIO3",
+			  "GPIO4",
+			  "GPIO5",
+			  "GPIO6",
+			  "GPIO7",
+			  "GPIO8",
+			  "GPIO9",
+			  "GPIO10",
+			  "GPIO11",
+			  "GPIO12",
+			  "GPIO13",
+			  "GPIO14",
+			  "GPIO15",
+			  "GPIO16",
+			  "GPIO17",
+			  "GPIO18",
+			  "GPIO19",
+			  "GPIO20",
+			  "GPIO21",
+			  "GPIO22",
+			  "GPIO23",
+			  "GPIO24",
+			  "GPIO25",
+			  "GPIO26",
+			  "GPIO27",
+			  "GPIO28",
+			  "GPIO29",
+			  "GPIO30",
+			  "GPIO31",
+			  "GPIO32",
+			  "GPIO33",
+			  "GPIO34",
+			  "GPIO35",
+			  "GPIO36",
+			  "GPIO37",
+			  "GPIO38",
+			  "GPIO39",
+			  "GPIO40",
+			  "GPIO41",
+			  "GPIO42",
+			  "GPIO43",
+			  "GPIO44",
+			  "GPIO45",
+			  "SMPS_SCL",
+			  "SMPS_SDA",
+			  /* Used by eMMC */
+			  "SD_CLK_R",
+			  "SD_CMD_R",
+			  "SD_DATA0_R",
+			  "SD_DATA1_R",
+			  "SD_DATA2_R",
+			  "SD_DATA3_R";
+
+	spi0_pins: spi0_pins {
+		brcm,pins = <9 10 11>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	spi0_cs_pins: spi0_cs_pins {
+		brcm,pins = <8 7>;
+		brcm,function = <1>; /* output */
+	};
+
+	i2c0_pins: i2c0 {
+		brcm,pins = <0 1>;
+		brcm,function = <4>;
+	};
+
+	i2c1_pins: i2c1 {
+		brcm,pins = <2 3>;
+		brcm,function = <4>;
+	};
+
+	i2s_pins: i2s {
+		brcm,pins = <18 19 20 21>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	audio_pins: audio_pins {
+		brcm,pins;
+		brcm,function;
+	};
+};
+
+&firmware {
+	expgpio: expgpio {
+		compatible = "raspberrypi,firmware-gpio";
+		gpio-controller;
+		#gpio-cells = <2>;
+		gpio-line-names = "HDMI_HPD_N",
+				  "EMMC_EN_N",
+				  "NC",
+				  "NC",
+				  "NC",
+				  "NC",
+				  "NC",
+				  "NC";
+		status = "okay";
+	};
+
+	virtgpio: virtgpio {
+		compatible = "brcm,bcm2835-virtgpio";
+		gpio-controller;
+		#gpio-cells = <2>;
+		status = "okay";
+	};
+};
+
+&spi0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
+	cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
+
+	spidev0: spidev@0{
+		compatible = "spidev";
+		reg = <0>;	/* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+
+	spidev1: spidev@1{
+		compatible = "spidev";
+		reg = <1>;	/* CE1 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+};
+
+&i2c0if {
+	clock-frequency = <100000>;
+};
+
+&i2c1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c1_pins>;
+	clock-frequency = <100000>;
+};
+
+&i2c2 {
+	clock-frequency = <100000>;
+};
+
+&i2s {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2s_pins>;
+};
+
+&led_act {
+	gpios = <&virtgpio 0 GPIO_ACTIVE_HIGH>;
+	default-state = "off";
+	linux,default-trigger = "mmc0";
+};
+
+&hdmi {
+	hpd-gpios = <&expgpio 0 GPIO_ACTIVE_LOW>;
+};
+
+&vchiq {
+	pinctrl-names = "default";
+	pinctrl-0 = <&audio_pins>;
+};
+
+/ {
+	__overrides__ {
+		audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_hdmi=0'}";
+
+		act_led_gpio = <&led_act>,"gpios:4";
+		act_led_activelow = <&led_act>,"gpios:8";
+		act_led_trigger = <&led_act>,"linux,default-trigger";
+	};
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2709-rpi.dtsi b/arch/arm/boot/dts/broadcom/bcm2709-rpi.dtsi
new file mode 100644
index 000000000000..7335e7fbcb71
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2709-rpi.dtsi
@@ -0,0 +1,8 @@
+#include "bcm2708-rpi.dtsi"
+
+&vchiq {
+	compatible = "brcm,bcm2836-vchiq", "brcm,bcm2835-vchiq";
+};
+
+i2c_arm: &i2c1 {};
+i2c_vc: &i2c0 {};
diff --git a/arch/arm/boot/dts/broadcom/bcm2709.dtsi b/arch/arm/boot/dts/broadcom/bcm2709.dtsi
new file mode 100644
index 000000000000..868f65f922ff
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2709.dtsi
@@ -0,0 +1,29 @@
+#define i2c0 i2c0if
+#include "bcm2836.dtsi"
+#undef i2c0
+#include "bcm270x.dtsi"
+
+/ {
+	soc {
+		ranges = <0x7e000000 0x3f000000 0x01000000>,
+		         <0x40000000 0x40000000 0x00040000>;
+
+		dma-ranges = <0xc0000000 0x00000000 0x3f000000>,
+			     <0x7e000000 0x3f000000 0x01000000>;
+	};
+
+	__overrides__ {
+		arm_freq = <&v7_cpu0>, "clock-frequency:0",
+			   <&v7_cpu1>, "clock-frequency:0",
+			   <&v7_cpu2>, "clock-frequency:0",
+			   <&v7_cpu3>, "clock-frequency:0";
+	};
+};
+
+&system_timer {
+	status = "disabled";
+};
+
+&vc4 {
+	status = "disabled";
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm270x-rpi.dtsi b/arch/arm/boot/dts/broadcom/bcm270x-rpi.dtsi
new file mode 100644
index 000000000000..a678678d37c2
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm270x-rpi.dtsi
@@ -0,0 +1,201 @@
+/* Downstream modifications to bcm2835-rpi.dtsi */
+
+/ {
+	aliases: aliases {
+		aux = &aux;
+		sound = &sound;
+		soc = &soc;
+		dma = &dma;
+		intc = &intc;
+		watchdog = &watchdog;
+		random = &random;
+		mailbox = &mailbox;
+		gpio = &gpio;
+		uart0 = &uart0;
+		uart1 = &uart1;
+		sdhost = &sdhost;
+		mmc = &mmc;
+		mmc1 = &mmc;
+		mmc0 = &sdhost;
+		i2s = &i2s;
+		i2c0 = &i2c0;
+		i2c1 = &i2c1;
+		i2c10 = &i2c_csi_dsi;
+		i2c = &i2c_arm;
+		spi0 = &spi0;
+		spi1 = &spi1;
+		spi2 = &spi2;
+		usb = &usb;
+		leds = &leds;
+		fb = &fb;
+		thermal = &thermal;
+		axiperf = &axiperf;
+	};
+
+	/* Define these notional regulators for use by overlays */
+	vdd_3v3_reg: fixedregulator_3v3 {
+		compatible = "regulator-fixed";
+		regulator-always-on;
+		regulator-max-microvolt = <3300000>;
+		regulator-min-microvolt = <3300000>;
+		regulator-name = "3v3";
+	};
+
+	vdd_5v0_reg: fixedregulator_5v0 {
+		compatible = "regulator-fixed";
+		regulator-always-on;
+		regulator-max-microvolt = <5000000>;
+		regulator-min-microvolt = <5000000>;
+		regulator-name = "5v0";
+	};
+
+	soc {
+		gpiomem {
+			compatible = "brcm,bcm2835-gpiomem";
+			reg = <0x7e200000 0x1000>;
+		};
+
+		fb: fb {
+			compatible = "brcm,bcm2708-fb";
+			firmware = <&firmware>;
+			status = "okay";
+		};
+
+		/* External sound card */
+		sound: sound {
+			status = "disabled";
+		};
+	};
+
+	__overrides__ {
+		cache_line_size;
+
+		uart0 = <&uart0>,"status";
+		uart1 = <&uart1>,"status";
+		i2s = <&i2s>,"status";
+		spi = <&spi0>,"status";
+		i2c0 = <&i2c0if>,"status",<&i2c0mux>,"status";
+		i2c1 = <&i2c1>,"status";
+		i2c = <&i2c1>,"status";
+		i2c_arm = <&i2c1>,"status";
+		i2c_vc = <&i2c0if>,"status",<&i2c0mux>,"status";
+		i2c0_baudrate = <&i2c0if>,"clock-frequency:0";
+		i2c1_baudrate = <&i2c1>,"clock-frequency:0";
+		i2c_baudrate = <&i2c1>,"clock-frequency:0";
+		i2c_arm_baudrate = <&i2c1>,"clock-frequency:0";
+		i2c_vc_baudrate = <&i2c0if>,"clock-frequency:0";
+
+		watchdog = <&watchdog>,"early-watchdog?";
+		random = <&random>,"status";
+		sd_overclock = <&sdhost>,"brcm,overclock-50:0";
+		sd_force_pio = <&sdhost>,"brcm,force-pio?";
+		sd_pio_limit = <&sdhost>,"brcm,pio-limit:0";
+		sd_debug     = <&sdhost>,"brcm,debug";
+		sdio_overclock = <&mmc>,"brcm,overclock-50:0",
+				 <&mmcnr>,"brcm,overclock-50:0";
+		axiperf      = <&axiperf>,"status";
+		drm_fb0_vc4 = <&aliases>, "drm-fb0=",&vc4;
+		drm_fb1_vc4 = <&aliases>, "drm-fb1=",&vc4;
+		drm_fb2_vc4 = <&aliases>, "drm-fb2=",&vc4;
+
+		cam1_sync = <&csi1>, "sync-gpios:0=", <&gpio>,
+			    <&csi1>, "sync-gpios:4",
+			    <&csi1>, "sync-gpios:8=", <GPIO_ACTIVE_HIGH>;
+		cam1_sync_inverted = <&csi1>, "sync-gpios:0=", <&gpio>,
+			    <&csi1>, "sync-gpios:4",
+			    <&csi1>, "sync-gpios:8=", <GPIO_ACTIVE_LOW>;
+		cam0_sync = <&csi0>, "sync-gpios:0=", <&gpio>,
+			    <&csi0>, "sync-gpios:4",
+			    <&csi0>, "sync-gpios:8=", <GPIO_ACTIVE_HIGH>;
+		cam0_sync_inverted = <&csi0>, "sync-gpios:0=", <&gpio>,
+			    <&csi0>, "sync-gpios:4",
+			    <&csi0>, "sync-gpios:8=", <GPIO_ACTIVE_LOW>;
+
+		cam0_reg = <&cam0_reg>,"status";
+		cam0_reg_gpio = <&cam0_reg>,"gpio:4",
+				<&cam0_reg>,"gpio:0=", <&gpio>;
+		cam1_reg = <&cam1_reg>,"status";
+		cam1_reg_gpio = <&cam1_reg>,"gpio:4",
+				<&cam1_reg>,"gpio:0=", <&gpio>;
+
+		strict_gpiod = <&chosen>, "bootargs=pinctrl_bcm2835.persist_gpio_outputs=n";
+	};
+};
+
+&uart0 {
+	skip-init;
+};
+
+&uart1 {
+	skip-init;
+};
+
+&txp {
+	status = "disabled";
+};
+
+&i2c0if {
+	status = "disabled";
+};
+
+&i2c0mux {
+	pinctrl-names = "i2c0", "i2c_csi_dsi";
+	/delete-property/ clock-frequency;
+	status = "disabled";
+};
+
+&i2c1 {
+	status = "disabled";
+};
+
+i2s_clk_producer: &i2s {};
+i2s_clk_consumer: &i2s {};
+
+&clocks {
+	firmware = <&firmware>;
+};
+
+&sdhci {
+	pinctrl-names = "default";
+	pinctrl-0 = <&emmc_gpio48>;
+	bus-width = <4>;
+};
+
+&cpu_thermal {
+	// Add some labels
+	thermal_trips: trips {
+		cpu-crit {
+			// Raise upstream limit of 90C
+			temperature = <110000>;
+		};
+	};
+	cooling_maps: cooling-maps {
+	};
+};
+
+&vec {
+	clocks = <&firmware_clocks 15>;
+	status = "disabled";
+};
+
+&firmware {
+	vcio: vcio {
+		compatible = "raspberrypi,vcio";
+	};
+};
+
+&vc4 {
+	raspberrypi,firmware = <&firmware>;
+};
+
+#ifndef BCM2711
+
+&hdmi {
+	reg-names = "hdmi",
+		    "hd";
+	clocks = <&firmware_clocks 9>,
+		 <&firmware_clocks 13>;
+	dmas = <&dma (17|(1<<27)|(1<<24))>;
+};
+
+#endif
diff --git a/arch/arm/boot/dts/broadcom/bcm270x.dtsi b/arch/arm/boot/dts/broadcom/bcm270x.dtsi
new file mode 100644
index 000000000000..a3cb24cdcdb3
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm270x.dtsi
@@ -0,0 +1,265 @@
+/* Downstream bcm283x.dtsi diff */
+#include <dt-bindings/power/raspberrypi-power.h>
+
+/ {
+	chosen: chosen {
+		// Disable audio by default
+		bootargs = "coherent_pool=1M snd_bcm2835.enable_headphones=0 cgroup_disable=memory";
+		stdout-path = "serial0:115200n8";
+	};
+
+	soc: soc {
+		watchdog: watchdog@7e100000 {
+			/* Add label */
+		};
+
+		random: rng@7e104000 {
+			/* Add label */
+		};
+
+		spi0: spi@7e204000 {
+			/* Add label */
+		};
+
+#ifndef BCM2711
+		pixelvalve0: pixelvalve@7e206000 {
+			/* Add label */
+			status = "disabled";
+		};
+
+		pixelvalve1: pixelvalve@7e207000 {
+			/* Add label */
+			status = "disabled";
+		};
+#endif
+
+		/delete-node/ mmc@7e300000;
+
+		sdhci: mmc: mmc@7e300000 {
+			compatible = "brcm,bcm2835-mmc", "brcm,bcm2835-sdhci";
+			reg = <0x7e300000 0x100>;
+			interrupts = <2 30>;
+			clocks = <&clocks BCM2835_CLOCK_EMMC>;
+			dmas = <&dma 11>;
+			dma-names = "rx-tx";
+			brcm,overclock-50 = <0>;
+			status = "disabled";
+		};
+
+		/* A clone of mmc but with non-removable set */
+		mmcnr: mmcnr@7e300000 {
+			compatible = "brcm,bcm2835-mmc", "brcm,bcm2835-sdhci";
+			reg = <0x7e300000 0x100>;
+			interrupts = <2 30>;
+			clocks = <&clocks BCM2835_CLOCK_EMMC>;
+			dmas = <&dma 11>;
+			dma-names = "rx-tx";
+			brcm,overclock-50 = <0>;
+			non-removable;
+			status = "disabled";
+		};
+
+		hvs: hvs@7e400000 {
+			/* Add label */
+			status = "disabled";
+		};
+
+		firmwarekms: firmwarekms@7e600000 {
+			compatible = "raspberrypi,rpi-firmware-kms";
+			/* SMI interrupt reg */
+			reg = <0x7e600000 0x100>;
+			interrupts = <2 16>;
+			brcm,firmware = <&firmware>;
+			status = "disabled";
+		};
+
+		smi: smi@7e600000 {
+			compatible = "brcm,bcm2835-smi";
+			reg = <0x7e600000 0x100>;
+			interrupts = <2 16>;
+			clocks = <&clocks BCM2835_CLOCK_SMI>;
+			assigned-clocks = <&clocks BCM2835_CLOCK_SMI>;
+			assigned-clock-rates = <125000000>;
+			dmas = <&dma 4>;
+			dma-names = "rx-tx";
+			status = "disabled";
+		};
+
+#ifndef BCM2711
+		pixelvalve2: pixelvalve@7e807000 {
+			/* Add label */
+			status = "disabled";
+		};
+#endif
+
+		hdmi@7e902000 { /* hdmi */
+			status = "disabled";
+		};
+
+		usb@7e980000 { /* usb */
+			compatible = "brcm,bcm2708-usb";
+			reg = <0x7e980000 0x10000>,
+			      <0x7e006000 0x1000>;
+			interrupt-names = "usb",
+					  "soft";
+			interrupts = <1 9>,
+				     <2 0>;
+		};
+
+#ifndef BCM2711
+		v3d@7ec00000 { /* vd3 */
+			compatible = "brcm,vc4-v3d";
+			power-domains = <&power RPI_POWER_DOMAIN_V3D>;
+			status = "disabled";
+		};
+#endif
+
+		axiperf: axiperf {
+			compatible = "brcm,bcm2835-axiperf";
+			reg = <0x7e009800 0x100>,
+			      <0x7ee08000 0x100>;
+			firmware = <&firmware>;
+			status = "disabled";
+		};
+
+		i2c0mux: i2c0mux {
+			compatible = "i2c-mux-pinctrl";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			i2c-parent = <&i2c0if>;
+
+			status = "disabled";
+
+			i2c0: i2c@0 {
+				reg = <0>;
+				#address-cells = <1>;
+				#size-cells = <0>;
+			};
+
+			i2c_csi_dsi: i2c@1 {
+				reg = <1>;
+				#address-cells = <1>;
+				#size-cells = <0>;
+			};
+		};
+	};
+
+	cam1_reg: cam1_regulator {
+		compatible = "regulator-fixed";
+		regulator-name = "cam1-reg";
+		enable-active-high;
+		/* Needs to be enabled, as removing a regulator is very unsafe */
+		status = "okay";
+	};
+
+	cam1_clk: cam1_clk {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		status = "disabled";
+	};
+
+	cam0_regulator: cam0_regulator {
+		compatible = "regulator-fixed";
+		regulator-name = "cam0-reg";
+		enable-active-high;
+		status = "disabled";
+	};
+
+	cam0_clk: cam0_clk {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		status = "disabled";
+	};
+
+	cam_dummy_reg: cam_dummy_reg {
+		compatible = "regulator-fixed";
+		regulator-name = "cam-dummy-reg";
+		status = "okay";
+	};
+
+	__overrides__ {
+		cam0-pwdn-ctrl;
+		cam0-pwdn;
+		cam0-led-ctrl;
+		cam0-led;
+	};
+};
+
+&gpio {
+	interrupts = <2 17>, <2 18>;
+
+	dpi_18bit_cpadhi_gpio0: dpi_18bit_cpadhi_gpio0 {
+		brcm,pins = <0 1 2 3 4 5 6 7 8 9
+			     12 13 14 15 16 17
+			     20 21 22 23 24 25>;
+		brcm,function = <BCM2835_FSEL_ALT2>;
+		brcm,pull = <0>; /* no pull */
+	};
+	dpi_18bit_cpadhi_gpio2: dpi_18bit_cpadhi_gpio2 {
+		brcm,pins = <2 3 4 5 6 7 8 9
+			     12 13 14 15 16 17
+			     20 21 22 23 24 25>;
+		brcm,function = <BCM2835_FSEL_ALT2>;
+	};
+	dpi_18bit_gpio0: dpi_18bit_gpio0 {
+		brcm,pins = <0 1 2 3 4 5 6 7 8 9 10 11
+			     12 13 14 15 16 17 18 19
+			     20 21>;
+		brcm,function = <BCM2835_FSEL_ALT2>;
+	};
+	dpi_18bit_gpio2: dpi_18bit_gpio2 {
+		brcm,pins = <2 3 4 5 6 7 8 9 10 11
+			     12 13 14 15 16 17 18 19
+			     20 21>;
+		brcm,function = <BCM2835_FSEL_ALT2>;
+	};
+	dpi_16bit_gpio0: dpi_16bit_gpio0 {
+		brcm,pins = <0 1 2 3 4 5 6 7 8 9 10 11
+			     12 13 14 15 16 17 18 19>;
+		brcm,function = <BCM2835_FSEL_ALT2>;
+	};
+	dpi_16bit_gpio2: dpi_16bit_gpio2 {
+		brcm,pins = <2 3 4 5 6 7 8 9 10 11
+			     12 13 14 15 16 17 18 19>;
+		brcm,function = <BCM2835_FSEL_ALT2>;
+	};
+	dpi_16bit_cpadhi_gpio0: dpi_16bit_cpadhi_gpio0 {
+		brcm,pins = <0 1 2 3 4 5 6 7 8
+			     12 13 14 15 16 17
+			     20 21 22 23 24>;
+		brcm,function = <BCM2835_FSEL_ALT2>;
+	};
+	dpi_16bit_cpadhi_gpio2: dpi_16bit_cpadhi_gpio2 {
+		brcm,pins = <2 3 4 5 6 7 8
+			     12 13 14 15 16 17
+			     20 21 22 23 24>;
+		brcm,function = <BCM2835_FSEL_ALT2>;
+	};
+};
+
+&uart0 {
+	compatible = "arm,pl011-axi", "arm,pl011", "arm,primecell";
+	/* Enable CTS bug workaround */
+	cts-event-workaround;
+};
+
+&i2s {
+	#sound-dai-cells = <0>;
+	dmas = <&dma 2>, <&dma 3>;
+	dma-names = "tx", "rx";
+};
+
+&sdhost {
+	dmas = <&dma (13|(1<<29))>;
+	dma-names = "rx-tx";
+	bus-width = <4>;
+	brcm,overclock-50 = <0>;
+	brcm,pio-limit = <1>;
+	firmware = <&firmware>;
+};
+
+&spi0 {
+	dmas = <&dma 6>, <&dma 7>;
+	dma-names = "tx", "rx";
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2710-rpi-2-b.dts b/arch/arm/boot/dts/broadcom/bcm2710-rpi-2-b.dts
new file mode 100644
index 000000000000..ce48eb6073f0
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2710-rpi-2-b.dts
@@ -0,0 +1,204 @@
+/dts-v1/;
+
+#include "bcm2710.dtsi"
+#include "bcm2709-rpi.dtsi"
+#include "bcm283x-rpi-smsc9514.dtsi"
+#include "bcm283x-rpi-csi1-2lane.dtsi"
+#include "bcm283x-rpi-i2c0mux_0_28.dtsi"
+#include "bcm283x-rpi-led-deprecated.dtsi"
+
+/ {
+	compatible = "raspberrypi,2-model-b-rev2", "brcm,bcm2837";
+	model = "Raspberry Pi 2 Model B rev 1.2";
+};
+
+&gpio {
+	/*
+	 * Taken from rpi_SCH_2b_1p2_reduced.pdf and
+	 * the official GPU firmware DT blob.
+	 *
+	 * Legend:
+	 * "NC" = not connected (no rail from the SoC)
+	 * "FOO" = GPIO line named "FOO" on the schematic
+	 * "FOO_N" = GPIO line named "FOO" on schematic, active low
+	 */
+	gpio-line-names = "ID_SDA",
+			  "ID_SCL",
+			  "GPIO2",
+			  "GPIO3",
+			  "GPIO4",
+			  "GPIO5",
+			  "GPIO6",
+			  "GPIO7",
+			  "GPIO8",
+			  "GPIO9",
+			  "GPIO10",
+			  "GPIO11",
+			  "GPIO12",
+			  "GPIO13",
+			  "GPIO14",
+			  "GPIO15",
+			  "GPIO16",
+			  "GPIO17",
+			  "GPIO18",
+			  "GPIO19",
+			  "GPIO20",
+			  "GPIO21",
+			  "GPIO22",
+			  "GPIO23",
+			  "GPIO24",
+			  "GPIO25",
+			  "GPIO26",
+			  "GPIO27",
+			  "SDA0",
+			  "SCL0",
+			  "NC", /* GPIO30 */
+			  "LAN_RUN",
+			  "CAM_GPIO1",
+			  "NC", /* GPIO33 */
+			  "NC", /* GPIO34 */
+			  "PWR_LOW_N",
+			  "NC", /* GPIO36 */
+			  "NC", /* GPIO37 */
+			  "USB_LIMIT",
+			  "NC", /* GPIO39 */
+			  "PWM0_OUT",
+			  "CAM_GPIO0",
+			  "SMPS_SCL",
+			  "SMPS_SDA",
+			  "ETH_CLK",
+			  "PWM1_OUT",
+			  "HDMI_HPD_N",
+			  "STATUS_LED",
+			  /* Used by SD Card */
+			  "SD_CLK_R",
+			  "SD_CMD_R",
+			  "SD_DATA0_R",
+			  "SD_DATA1_R",
+			  "SD_DATA2_R",
+			  "SD_DATA3_R";
+
+	spi0_pins: spi0_pins {
+		brcm,pins = <9 10 11>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	spi0_cs_pins: spi0_cs_pins {
+		brcm,pins = <8 7>;
+		brcm,function = <1>; /* output */
+	};
+
+	i2c0_pins: i2c0 {
+		brcm,pins = <0 1>;
+		brcm,function = <4>;
+	};
+
+	i2c1_pins: i2c1 {
+		brcm,pins = <2 3>;
+		brcm,function = <4>;
+	};
+
+	i2s_pins: i2s {
+		brcm,pins = <18 19 20 21>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	audio_pins: audio_pins {
+		brcm,pins = <40 45>;
+		brcm,function = <4>;
+		brcm,pull = <0>;
+	};
+};
+
+&uart0 {
+	status = "okay";
+};
+
+&spi0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
+	cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
+
+	spidev0: spidev@0{
+		compatible = "spidev";
+		reg = <0>;	/* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+
+	spidev1: spidev@1{
+		compatible = "spidev";
+		reg = <1>;	/* CE1 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+};
+
+&i2c0if {
+	clock-frequency = <100000>;
+};
+
+&i2c1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c1_pins>;
+	clock-frequency = <100000>;
+};
+
+&i2c2 {
+	clock-frequency = <100000>;
+};
+
+&i2s {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2s_pins>;
+};
+
+&led_act {
+	gpios = <&gpio 47 GPIO_ACTIVE_HIGH>;
+	default-state = "off";
+	linux,default-trigger = "mmc0";
+};
+
+&leds {
+	led_pwr: led-pwr {
+		label = "PWR";
+		gpios = <&gpio 35 GPIO_ACTIVE_HIGH>;
+		default-state = "off";
+		linux,default-trigger = "input";
+	};
+};
+
+&hdmi {
+	hpd-gpios = <&gpio 46 GPIO_ACTIVE_LOW>;
+};
+
+&vchiq {
+	pinctrl-names = "default";
+	pinctrl-0 = <&audio_pins>;
+};
+
+&cam1_reg {
+	gpio = <&gpio 41 GPIO_ACTIVE_HIGH>;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+i2c_csi_dsi0: &i2c0 {
+};
+
+/ {
+	__overrides__ {
+		audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_headphones=1 snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_headphones=0 snd_bcm2835.enable_hdmi=0'}";
+
+		act_led_gpio = <&led_act>,"gpios:4";
+		act_led_activelow = <&led_act>,"gpios:8";
+		act_led_trigger = <&led_act>,"linux,default-trigger";
+
+		pwr_led_gpio = <&led_pwr>,"gpios:4";
+		pwr_led_activelow = <&led_pwr>,"gpios:8";
+		pwr_led_trigger = <&led_pwr>,"linux,default-trigger";
+	};
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2710-rpi-3-b-plus.dts b/arch/arm/boot/dts/broadcom/bcm2710-rpi-3-b-plus.dts
new file mode 100644
index 000000000000..8973985e9902
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2710-rpi-3-b-plus.dts
@@ -0,0 +1,295 @@
+/dts-v1/;
+
+#include "bcm2710.dtsi"
+#include "bcm2709-rpi.dtsi"
+#include "bcm283x-rpi-lan7515.dtsi"
+#include "bcm283x-rpi-csi1-2lane.dtsi"
+#include "bcm283x-rpi-i2c0mux_0_44.dtsi"
+#include "bcm271x-rpi-bt.dtsi"
+#include "bcm283x-rpi-led-deprecated.dtsi"
+
+/ {
+	compatible = "raspberrypi,3-model-b-plus", "brcm,bcm2837";
+	model = "Raspberry Pi 3 Model B+";
+
+	aliases {
+		serial0 = &uart1;
+		serial1 = &uart0;
+		mmc1 = &mmcnr;
+	};
+};
+
+&gpio {
+	/*
+	 * Taken from rpi_SCH_3bplus_1p0_reduced.pdf and
+	 * the official GPU firmware DT blob.
+	 *
+	 * Legend:
+	 * "NC" = not connected (no rail from the SoC)
+	 * "FOO" = GPIO line named "FOO" on the schematic
+	 * "FOO_N" = GPIO line named "FOO" on schematic, active low
+	 */
+	gpio-line-names = "ID_SDA",
+			  "ID_SCL",
+			  "GPIO2",
+			  "GPIO3",
+			  "GPIO4",
+			  "GPIO5",
+			  "GPIO6",
+			  "GPIO7",
+			  "GPIO8",
+			  "GPIO9",
+			  "GPIO10",
+			  "GPIO11",
+			  "GPIO12",
+			  "GPIO13",
+			  "GPIO14",
+			  "GPIO15",
+			  "GPIO16",
+			  "GPIO17",
+			  "GPIO18",
+			  "GPIO19",
+			  "GPIO20",
+			  "GPIO21",
+			  "GPIO22",
+			  "GPIO23",
+			  "GPIO24",
+			  "GPIO25",
+			  "GPIO26",
+			  "GPIO27",
+			  "HDMI_HPD_N",
+			  "STATUS_LED_G",
+			  /* Used by BT module */
+			  "CTS0",
+			  "RTS0",
+			  "TXD0",
+			  "RXD0",
+			  /* Used by Wifi */
+			  "SD1_CLK",
+			  "SD1_CMD",
+			  "SD1_DATA0",
+			  "SD1_DATA1",
+			  "SD1_DATA2",
+			  "SD1_DATA3",
+			  "PWM0_OUT",
+			  "PWM1_OUT",
+			  "ETH_CLK",
+			  "WIFI_CLK",
+			  "SDA0",
+			  "SCL0",
+			  "SMPS_SCL",
+			  "SMPS_SDA",
+			  /* Used by SD Card */
+			  "SD_CLK_R",
+			  "SD_CMD_R",
+			  "SD_DATA0_R",
+			  "SD_DATA1_R",
+			  "SD_DATA2_R",
+			  "SD_DATA3_R";
+
+	spi0_pins: spi0_pins {
+		brcm,pins = <9 10 11>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	spi0_cs_pins: spi0_cs_pins {
+		brcm,pins = <8 7>;
+		brcm,function = <1>; /* output */
+	};
+
+	i2c0_pins: i2c0 {
+		brcm,pins = <0 1>;
+		brcm,function = <4>;
+	};
+
+	i2c1_pins: i2c1 {
+		brcm,pins = <2 3>;
+		brcm,function = <4>;
+	};
+
+	i2s_pins: i2s {
+		brcm,pins = <18 19 20 21>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	sdio_pins: sdio_pins {
+		brcm,pins =     <34 35 36 37 38 39>;
+		brcm,function = <7>; // alt3 = SD1
+		brcm,pull =     <0 2 2 2 2 2>;
+	};
+
+	bt_pins: bt_pins {
+		brcm,pins = <43>;
+		brcm,function = <4>; /* alt0:GPCLK2 */
+		brcm,pull = <0>;
+	};
+
+	uart0_pins: uart0_pins {
+		brcm,pins = <32 33>;
+		brcm,function = <7>; /* alt3=UART0 */
+		brcm,pull = <0 2>;
+	};
+
+	uart1_pins: uart1_pins {
+		brcm,pins;
+		brcm,function;
+		brcm,pull;
+	};
+
+	uart1_bt_pins: uart1_bt_pins {
+		brcm,pins = <32 33 30 31>;
+		brcm,function = <BCM2835_FSEL_ALT5>; /* alt5=UART1 */
+		brcm,pull = <0 2 2 0>;
+	};
+
+	audio_pins: audio_pins {
+		brcm,pins = <40 41>;
+		brcm,function = <4>;
+		brcm,pull = <0>;
+	};
+};
+
+&mmcnr {
+	pinctrl-names = "default";
+	pinctrl-0 = <&sdio_pins>;
+	bus-width = <4>;
+	status = "okay";
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	brcmf: wifi@1 {
+		reg = <1>;
+		compatible = "brcm,bcm4329-fmac";
+	};
+};
+
+&firmware {
+	expgpio: expgpio {
+		compatible = "raspberrypi,firmware-gpio";
+		gpio-controller;
+		#gpio-cells = <2>;
+		gpio-line-names = "BT_ON",
+				  "WL_ON",
+				  "PWR_LED_R",
+				  "LAN_RUN",
+				  "NC",
+				  "CAM_GPIO0",
+				  "CAM_GPIO1",
+				  "NC";
+		status = "okay";
+	};
+};
+
+&uart0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&uart0_pins &bt_pins>;
+	status = "okay";
+};
+
+&uart1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&uart1_pins>;
+	status = "okay";
+};
+
+&spi0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
+	cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
+
+	spidev0: spidev@0{
+		compatible = "spidev";
+		reg = <0>;	/* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+
+	spidev1: spidev@1{
+		compatible = "spidev";
+		reg = <1>;	/* CE1 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+};
+
+&i2c0if {
+	clock-frequency = <100000>;
+};
+
+&i2c1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c1_pins>;
+	clock-frequency = <100000>;
+};
+
+&i2c2 {
+	clock-frequency = <100000>;
+};
+
+&i2s {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2s_pins>;
+};
+
+&led_act {
+	gpios = <&gpio 29 GPIO_ACTIVE_HIGH>;
+	default-state = "off";
+	linux,default-trigger = "mmc0";
+};
+
+&leds {
+	led_pwr: led-pwr {
+		label = "PWR";
+		gpios = <&expgpio 2 GPIO_ACTIVE_LOW>;
+		default-state = "off";
+		linux,default-trigger = "default-on";
+	};
+};
+
+&hdmi {
+	hpd-gpios = <&gpio 28 GPIO_ACTIVE_LOW>;
+};
+
+&vchiq {
+	pinctrl-names = "default";
+	pinctrl-0 = <&audio_pins>;
+};
+
+&eth_phy {
+	microchip,eee-enabled;
+	microchip,tx-lpi-timer = <600>; /* non-aggressive*/
+	microchip,downshift-after = <2>;
+};
+
+&cam1_reg {
+	gpio = <&expgpio 5 GPIO_ACTIVE_HIGH>;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+i2c_csi_dsi0: &i2c0 {
+};
+
+/ {
+	__overrides__ {
+		audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_headphones=1 snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_headphones=0 snd_bcm2835.enable_hdmi=0'}";
+
+		act_led_gpio = <&led_act>,"gpios:4";
+		act_led_activelow = <&led_act>,"gpios:8";
+		act_led_trigger = <&led_act>,"linux,default-trigger";
+
+		pwr_led_gpio = <&led_pwr>,"gpios:4";
+		pwr_led_activelow = <&led_pwr>,"gpios:8";
+		pwr_led_trigger = <&led_pwr>,"linux,default-trigger";
+
+		eee = <&eth_phy>,"microchip,eee-enabled?";
+		tx_lpi_timer = <&eth_phy>,"microchip,tx-lpi-timer:0";
+		eth_led0 = <&eth_phy>,"microchip,led-modes:0";
+		eth_led1 = <&eth_phy>,"microchip,led-modes:4";
+		eth_downshift_after = <&eth_phy>,"microchip,downshift-after:0";
+		eth_max_speed = <&eth_phy>,"max-speed:0";
+	};
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2710-rpi-3-b.dts b/arch/arm/boot/dts/broadcom/bcm2710-rpi-3-b.dts
new file mode 100644
index 000000000000..35e6e9900083
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2710-rpi-3-b.dts
@@ -0,0 +1,293 @@
+/dts-v1/;
+
+#include "bcm2710.dtsi"
+#include "bcm2709-rpi.dtsi"
+#include "bcm283x-rpi-smsc9514.dtsi"
+#include "bcm283x-rpi-csi1-2lane.dtsi"
+#include "bcm283x-rpi-i2c0mux_0_44.dtsi"
+#include "bcm271x-rpi-bt.dtsi"
+#include "bcm283x-rpi-led-deprecated.dtsi"
+
+/ {
+	compatible = "raspberrypi,3-model-b", "brcm,bcm2837";
+	model = "Raspberry Pi 3 Model B";
+
+	aliases {
+		serial0 = &uart1;
+		serial1 = &uart0;
+		mmc1 = &mmcnr;
+	};
+};
+
+&gpio {
+	/*
+	 * Taken from rpi_SCH_3b_1p2_reduced.pdf and
+	 * the official GPU firmware DT blob.
+	 *
+	 * Legend:
+	 * "NC" = not connected (no rail from the SoC)
+	 * "FOO" = GPIO line named "FOO" on the schematic
+	 * "FOO_N" = GPIO line named "FOO" on schematic, active low
+	 */
+	gpio-line-names = "ID_SDA",
+			  "ID_SCL",
+			  "GPIO2",
+			  "GPIO3",
+			  "GPIO4",
+			  "GPIO5",
+			  "GPIO6",
+			  "GPIO7",
+			  "GPIO8",
+			  "GPIO9",
+			  "GPIO10",
+			  "GPIO11",
+			  "GPIO12",
+			  "GPIO13",
+			  "GPIO14",
+			  "GPIO15",
+			  "GPIO16",
+			  "GPIO17",
+			  "GPIO18",
+			  "GPIO19",
+			  "GPIO20",
+			  "GPIO21",
+			  "GPIO22",
+			  "GPIO23",
+			  "GPIO24",
+			  "GPIO25",
+			  "GPIO26",
+			  "GPIO27",
+			  "NC", /* GPIO 28 */
+			  "LAN_RUN_BOOT",
+			  /* Used by BT module */
+			  "CTS0",
+			  "RTS0",
+			  "TXD0",
+			  "RXD0",
+			  /* Used by Wifi */
+			  "SD1_CLK",
+			  "SD1_CMD",
+			  "SD1_DATA0",
+			  "SD1_DATA1",
+			  "SD1_DATA2",
+			  "SD1_DATA3",
+			  "PWM0_OUT",
+			  "PWM1_OUT",
+			  "ETH_CLK",
+			  "WIFI_CLK",
+			  "SDA0",
+			  "SCL0",
+			  "SMPS_SCL",
+			  "SMPS_SDA",
+			  /* Used by SD Card */
+			  "SD_CLK_R",
+			  "SD_CMD_R",
+			  "SD_DATA0_R",
+			  "SD_DATA1_R",
+			  "SD_DATA2_R",
+			  "SD_DATA3_R";
+
+	spi0_pins: spi0_pins {
+		brcm,pins = <9 10 11>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	spi0_cs_pins: spi0_cs_pins {
+		brcm,pins = <8 7>;
+		brcm,function = <1>; /* output */
+	};
+
+	i2c0_pins: i2c0 {
+		brcm,pins = <0 1>;
+		brcm,function = <4>;
+	};
+
+	i2c1_pins: i2c1 {
+		brcm,pins = <2 3>;
+		brcm,function = <4>;
+	};
+
+	i2s_pins: i2s {
+		brcm,pins = <18 19 20 21>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	sdio_pins: sdio_pins {
+		brcm,pins =     <34 35 36 37 38 39>;
+		brcm,function = <7>; // alt3 = SD1
+		brcm,pull =     <0 2 2 2 2 2>;
+	};
+
+	bt_pins: bt_pins {
+		brcm,pins = <43>;
+		brcm,function = <4>; /* alt0:GPCLK2 */
+		brcm,pull = <0>;
+	};
+
+	uart0_pins: uart0_pins {
+		brcm,pins = <32 33>;
+		brcm,function = <7>; /* alt3=UART0 */
+		brcm,pull = <0 2>;
+	};
+
+	uart1_pins: uart1_pins {
+		brcm,pins;
+		brcm,function;
+		brcm,pull;
+	};
+
+	uart1_bt_pins: uart1_bt_pins {
+		brcm,pins = <32 33>;
+		brcm,function = <BCM2835_FSEL_ALT5>; /* alt5=UART1 */
+		brcm,pull = <0 2>;
+	};
+
+	audio_pins: audio_pins {
+		brcm,pins = <40 41>;
+		brcm,function = <4>;
+		brcm,pull = <0>;
+	};
+};
+
+&mmcnr {
+	pinctrl-names = "default";
+	pinctrl-0 = <&sdio_pins>;
+	bus-width = <4>;
+	status = "okay";
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	brcmf: wifi@1 {
+		reg = <1>;
+		compatible = "brcm,bcm4329-fmac";
+	};
+};
+
+&firmware {
+	expgpio: expgpio {
+		compatible = "raspberrypi,firmware-gpio";
+		gpio-controller;
+		#gpio-cells = <2>;
+		gpio-line-names = "BT_ON",
+				  "WL_ON",
+				  "STATUS_LED",
+				  "LAN_RUN",
+				  "HDMI_HPD_N",
+				  "CAM_GPIO0",
+				  "CAM_GPIO1",
+				  "PWR_LOW_N";
+		status = "okay";
+	};
+
+	virtgpio: virtgpio {
+		compatible = "brcm,bcm2835-virtgpio";
+		gpio-controller;
+		#gpio-cells = <2>;
+		status = "okay";
+	};
+};
+
+&uart0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&uart0_pins &bt_pins>;
+	status = "okay";
+};
+
+&uart1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&uart1_pins>;
+	status = "okay";
+};
+
+&bt {
+	max-speed = <921600>;
+};
+
+&spi0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
+	cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
+
+	spidev0: spidev@0{
+		compatible = "spidev";
+		reg = <0>;	/* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+
+	spidev1: spidev@1{
+		compatible = "spidev";
+		reg = <1>;	/* CE1 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+};
+
+&i2c0if {
+	clock-frequency = <100000>;
+};
+
+&i2c1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c1_pins>;
+	clock-frequency = <100000>;
+};
+
+&i2c2 {
+	clock-frequency = <100000>;
+};
+
+&i2s {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2s_pins>;
+};
+
+&led_act {
+	gpios = <&virtgpio 0 GPIO_ACTIVE_HIGH>;
+	default-state = "off";
+	linux,default-trigger = "mmc0";
+};
+
+&leds {
+	led_pwr: led-pwr {
+		label = "PWR";
+		gpios = <&expgpio 7 GPIO_ACTIVE_HIGH>;
+		default-state = "off";
+		linux,default-trigger = "input";
+	};
+};
+
+&hdmi {
+	hpd-gpios = <&expgpio 4 GPIO_ACTIVE_LOW>;
+};
+
+&vchiq {
+	pinctrl-names = "default";
+	pinctrl-0 = <&audio_pins>;
+};
+
+&cam1_reg {
+	gpio = <&expgpio 5 GPIO_ACTIVE_HIGH>;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+i2c_csi_dsi0: &i2c0 {
+};
+
+/ {
+	__overrides__ {
+		audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_headphones=1 snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_headphones=0 snd_bcm2835.enable_hdmi=0'}";
+
+		act_led_gpio = <&led_act>,"gpios:4";
+		act_led_activelow = <&led_act>,"gpios:8";
+		act_led_trigger = <&led_act>,"linux,default-trigger";
+
+		pwr_led_gpio = <&led_pwr>,"gpios:4";
+		pwr_led_activelow = <&led_pwr>,"gpios:8";
+		pwr_led_trigger = <&led_pwr>,"linux,default-trigger";
+	};
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2710-rpi-cm0.dts b/arch/arm/boot/dts/broadcom/bcm2710-rpi-cm0.dts
new file mode 100644
index 000000000000..b575de6b6ce5
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2710-rpi-cm0.dts
@@ -0,0 +1,252 @@
+/dts-v1/;
+
+#include "bcm2710.dtsi"
+#include "bcm2709-rpi.dtsi"
+#include "bcm283x-rpi-csi1-2lane.dtsi"
+#include "bcm283x-rpi-i2c0mux_0_44.dtsi"
+#include "bcm2708-rpi-bt.dtsi"
+#include "bcm283x-rpi-led-deprecated.dtsi"
+
+/ {
+	compatible = "raspberrypi,0-compute-module", "brcm,bcm2837";
+	model = "Raspberry Pi Compute Module 0";
+
+	aliases {
+		serial0 = &uart1;
+		serial1 = &uart0;
+		mmc1 = &mmcnr;
+	};
+};
+
+&gpio {
+	/*
+	 * These names are (mostly) from the schematic.
+	 */
+	gpio-line-names = "ID_SDA",
+			  "ID_SCL",
+			  "GPIO2",
+			  "GPIO3",
+			  "GPIO4",
+			  "GPIO5",
+			  "GPIO6",
+			  "GPIO7",
+			  "GPIO8",
+			  "GPIO9",
+			  "GPIO10",
+			  "GPIO11",
+			  "GPIO12",
+			  "GPIO13",
+			  "GPIO14",
+			  "GPIO15",
+			  "GPIO16",
+			  "GPIO17",
+			  "GPIO18",
+			  "GPIO19",
+			  "GPIO20",
+			  "GPIO21",
+			  "GPIO22",
+			  "GPIO23",
+			  "GPIO24",
+			  "GPIO25",
+			  "GPIO26",
+			  "GPIO27",
+			  "HDMI_HPD_N",
+			  "STATUS_LED_N",
+			  /* Used by BT module */
+			  "BT_CTS",
+			  "BT_RTS",
+			  "BT_TXD",
+			  "BT_RXD",
+			  /* Used by Wifi */
+			  "WIFI_SD_CLK",
+			  "WIFI_SD_CMD",
+			  "WIFI_SD_D0",
+			  "WIFI_SD_D1",
+			  "WIFI_SD_D2",
+			  "WIFI_SD_D3",
+			  "CAM_GPIO", /* GPIO40 */
+			  "WL_ON", /* GPIO41 */
+			  "BT_ON", /* GPIO42 */
+			  "EMMC_REENABLE_N", /* GPIO43 */
+			  "SDA0", /* GPIO44 */
+			  "SCL0", /* GPIO45 */
+			  "INT_SDA", /* GPIO46 */
+			  "INT_SCL", /* GPIO47 */
+			  /* Used by SD Card */
+			  "SD_CLK",
+			  "SD_CMD",
+			  "SD_D0",
+			  "SD_D1",
+			  "SD_D2",
+			  "SD_D3";
+
+	spi0_pins: spi0_pins {
+		brcm,pins = <9 10 11>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	spi0_cs_pins: spi0_cs_pins {
+		brcm,pins = <8 7>;
+		brcm,function = <1>; /* output */
+	};
+
+	i2c0_pins: i2c0 {
+		brcm,pins = <0 1>;
+		brcm,function = <4>;
+	};
+
+	i2c1_pins: i2c1 {
+		brcm,pins = <2 3>;
+		brcm,function = <4>;
+	};
+
+	i2s_pins: i2s {
+		brcm,pins = <18 19 20 21>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	sdio_pins: sdio_pins {
+		brcm,pins =     <34 35 36 37 38 39>;
+		brcm,function = <7>; // alt3 = SD1
+		brcm,pull =     <0 2 2 2 2 2>;
+	};
+
+	bt_pins: bt_pins {
+		brcm,pins = <43>;
+		brcm,function = <4>; /* alt0:GPCLK2 */
+		brcm,pull = <0>;
+	};
+
+	uart0_pins: uart0_pins {
+		brcm,pins = <30 31 32 33>;
+		brcm,function = <7>; /* alt3=UART0 */
+		brcm,pull = <2 0 0 2>; /* up none none up */
+	};
+
+	uart1_pins: uart1_pins {
+		brcm,pins;
+		brcm,function;
+		brcm,pull;
+	};
+
+	uart1_bt_pins: uart1_bt_pins {
+		brcm,pins = <32 33 30 31>;
+		brcm,function = <BCM2835_FSEL_ALT5>; /* alt5=UART1 */
+		brcm,pull = <0 2 2 0>;
+	};
+
+	audio_pins: audio_pins {
+		brcm,pins = <>;
+		brcm,function = <>;
+	};
+};
+
+&mmcnr {
+	pinctrl-names = "default";
+	pinctrl-0 = <&sdio_pins>;
+	bus-width = <4>;
+	status = "okay";
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	brcmf: wifi@1 {
+		reg = <1>;
+		compatible = "brcm,bcm4329-fmac";
+	};
+};
+
+&uart0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&uart0_pins &bt_pins>;
+	status = "okay";
+};
+
+&uart1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&uart1_pins>;
+	status = "okay";
+};
+
+&spi0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
+	cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
+
+	spidev0: spidev@0{
+		compatible = "spidev";
+		reg = <0>;	/* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+
+	spidev1: spidev@1{
+		compatible = "spidev";
+		reg = <1>;	/* CE1 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+};
+
+&i2c0if {
+	clock-frequency = <100000>;
+};
+
+&i2c1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c1_pins>;
+	clock-frequency = <100000>;
+};
+
+&i2c2 {
+	clock-frequency = <100000>;
+};
+
+&i2s {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2s_pins>;
+};
+
+&led_act {
+	gpios = <&gpio 29 GPIO_ACTIVE_LOW>;
+	default-state = "off";
+	linux,default-trigger = "actpwr";
+};
+
+&hdmi {
+	hpd-gpios = <&gpio 28 GPIO_ACTIVE_LOW>;
+};
+
+&vchiq {
+	pinctrl-names = "default";
+	pinctrl-0 = <&audio_pins>;
+};
+
+&bt {
+	shutdown-gpios = <&gpio 42 GPIO_ACTIVE_HIGH>;
+};
+
+&minibt {
+	shutdown-gpios = <&gpio 42 GPIO_ACTIVE_HIGH>;
+};
+
+&cam1_reg {
+	gpio = <&gpio 40 GPIO_ACTIVE_HIGH>;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+i2c_csi_dsi0: &i2c0 {
+};
+
+/ {
+	__overrides__ {
+		audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_hdmi=0'}";
+
+		act_led_gpio = <&led_act>,"gpios:4";
+		act_led_activelow = <&led_act>,"gpios:8";
+		act_led_trigger = <&led_act>,"linux,default-trigger";
+	};
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2710-rpi-cm3.dts b/arch/arm/boot/dts/broadcom/bcm2710-rpi-cm3.dts
new file mode 100644
index 000000000000..0d6e9e61f877
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2710-rpi-cm3.dts
@@ -0,0 +1,215 @@
+/dts-v1/;
+
+#include "bcm2710.dtsi"
+#include "bcm2709-rpi.dtsi"
+#include "bcm283x-rpi-csi0-2lane.dtsi"
+#include "bcm283x-rpi-csi1-4lane.dtsi"
+#include "bcm283x-rpi-i2c0mux_0_28.dtsi"
+#include "bcm283x-rpi-led-deprecated.dtsi"
+
+/ {
+	compatible = "raspberrypi,3-compute-module", "brcm,bcm2837";
+	model = "Raspberry Pi Compute Module 3";
+};
+
+&cam1_reg {
+	gpio = <&gpio 3 GPIO_ACTIVE_HIGH>;
+	status = "disabled";
+};
+
+cam0_reg: &cam0_regulator {
+	gpio = <&gpio 31 GPIO_ACTIVE_HIGH>;
+};
+
+i2c_csi_dsi0: &i2c0 {
+};
+
+&uart0 {
+	status = "okay";
+};
+
+&gpio {
+	/*
+	 * This is based on the official GPU firmware DT blob.
+	 *
+	 * Legend:
+	 * "NC" = not connected (no rail from the SoC)
+	 * "FOO" = GPIO line named "FOO" on the schematic
+	 * "FOO_N" = GPIO line named "FOO" on schematic, active low
+	 */
+	gpio-line-names = "GPIO0",
+			  "GPIO1",
+			  "GPIO2",
+			  "GPIO3",
+			  "GPIO4",
+			  "GPIO5",
+			  "GPIO6",
+			  "GPIO7",
+			  "GPIO8",
+			  "GPIO9",
+			  "GPIO10",
+			  "GPIO11",
+			  "GPIO12",
+			  "GPIO13",
+			  "GPIO14",
+			  "GPIO15",
+			  "GPIO16",
+			  "GPIO17",
+			  "GPIO18",
+			  "GPIO19",
+			  "GPIO20",
+			  "GPIO21",
+			  "GPIO22",
+			  "GPIO23",
+			  "GPIO24",
+			  "GPIO25",
+			  "GPIO26",
+			  "GPIO27",
+			  "GPIO28",
+			  "GPIO29",
+			  "GPIO30",
+			  "GPIO31",
+			  "GPIO32",
+			  "GPIO33",
+			  "GPIO34",
+			  "GPIO35",
+			  "GPIO36",
+			  "GPIO37",
+			  "GPIO38",
+			  "GPIO39",
+			  "GPIO40",
+			  "GPIO41",
+			  "GPIO42",
+			  "GPIO43",
+			  "GPIO44",
+			  "GPIO45",
+			  "SMPS_SCL",
+			  "SMPS_SDA",
+			  /* Used by eMMC */
+			  "SD_CLK_R",
+			  "SD_CMD_R",
+			  "SD_DATA0_R",
+			  "SD_DATA1_R",
+			  "SD_DATA2_R",
+			  "SD_DATA3_R";
+
+	spi0_pins: spi0_pins {
+		brcm,pins = <9 10 11>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	spi0_cs_pins: spi0_cs_pins {
+		brcm,pins = <8 7>;
+		brcm,function = <1>; /* output */
+	};
+
+	i2c0_pins: i2c0 {
+		brcm,pins = <0 1>;
+		brcm,function = <4>;
+	};
+
+	i2c1_pins: i2c1 {
+		brcm,pins = <2 3>;
+		brcm,function = <4>;
+	};
+
+	i2s_pins: i2s {
+		brcm,pins = <18 19 20 21>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	audio_pins: audio_pins {
+		brcm,pins;
+		brcm,function;
+	};
+};
+
+&firmware {
+	expgpio: expgpio {
+		compatible = "raspberrypi,firmware-gpio";
+		gpio-controller;
+		#gpio-cells = <2>;
+		gpio-line-names = "HDMI_HPD_N",
+				  "EMMC_EN_N",
+				  "NC",
+				  "NC",
+				  "NC",
+				  "NC",
+				  "NC",
+				  "NC";
+		status = "okay";
+	};
+
+	virtgpio: virtgpio {
+		compatible = "brcm,bcm2835-virtgpio";
+		gpio-controller;
+		#gpio-cells = <2>;
+		status = "okay";
+	};
+};
+
+&spi0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
+	cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
+
+	spidev0: spidev@0{
+		compatible = "spidev";
+		reg = <0>;	/* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+
+	spidev1: spidev@1{
+		compatible = "spidev";
+		reg = <1>;	/* CE1 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+};
+
+&i2c0if {
+	clock-frequency = <100000>;
+};
+
+&i2c1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c1_pins>;
+	clock-frequency = <100000>;
+};
+
+&i2c2 {
+	clock-frequency = <100000>;
+};
+
+&i2s {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2s_pins>;
+};
+
+&led_act {
+	gpios = <&virtgpio 0 GPIO_ACTIVE_HIGH>;
+	default-state = "off";
+	linux,default-trigger = "mmc0";
+};
+
+&hdmi {
+	hpd-gpios = <&expgpio 0 GPIO_ACTIVE_LOW>;
+};
+
+&vchiq {
+	pinctrl-names = "default";
+	pinctrl-0 = <&audio_pins>;
+};
+
+/ {
+	__overrides__ {
+		audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_hdmi=0'}";
+
+		act_led_gpio = <&led_act>,"gpios:4";
+		act_led_activelow = <&led_act>,"gpios:8";
+		act_led_trigger = <&led_act>,"linux,default-trigger";
+	};
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2710-rpi-zero-2-w.dts b/arch/arm/boot/dts/broadcom/bcm2710-rpi-zero-2-w.dts
new file mode 100644
index 000000000000..16971e50229f
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2710-rpi-zero-2-w.dts
@@ -0,0 +1,257 @@
+/dts-v1/;
+
+#include "bcm2710.dtsi"
+#include "bcm2709-rpi.dtsi"
+#include "bcm283x-rpi-csi1-2lane.dtsi"
+#include "bcm283x-rpi-i2c0mux_0_44.dtsi"
+#include "bcm2708-rpi-bt.dtsi"
+#include "bcm283x-rpi-led-deprecated.dtsi"
+
+/ {
+	compatible = "raspberrypi,model-zero-2-w", "brcm,bcm2837";
+	model = "Raspberry Pi Zero 2 W";
+
+	aliases {
+		serial0 = &uart1;
+		serial1 = &uart0;
+		mmc1 = &mmcnr;
+	};
+};
+
+&gpio {
+	/*
+	 * This is based on the official GPU firmware DT blob.
+	 *
+	 * Legend:
+	 * "NC" = not connected (no rail from the SoC)
+	 * "FOO" = GPIO line named "FOO" on the schematic
+	 * "FOO_N" = GPIO line named "FOO" on schematic, active low
+	 */
+	gpio-line-names = "ID_SDA",
+			  "ID_SCL",
+			  "GPIO2",
+			  "GPIO3",
+			  "GPIO4",
+			  "GPIO5",
+			  "GPIO6",
+			  "GPIO7",
+			  "GPIO8",
+			  "GPIO9",
+			  "GPIO10",
+			  "GPIO11",
+			  "GPIO12",
+			  "GPIO13",
+			  "GPIO14",
+			  "GPIO15",
+			  "GPIO16",
+			  "GPIO17",
+			  "GPIO18",
+			  "GPIO19",
+			  "GPIO20",
+			  "GPIO21",
+			  "GPIO22",
+			  "GPIO23",
+			  "GPIO24",
+			  "GPIO25",
+			  "GPIO26",
+			  "GPIO27",
+			  "HDMI_HPD_N",
+			  "STATUS_LED_N",
+			  /* Used by BT module */
+			  "CTS0",
+			  "RTS0",
+			  "TXD0",
+			  "RXD0",
+			  /* Used by Wifi */
+			  "SD1_CLK",
+			  "SD1_CMD",
+			  "SD1_DATA0",
+			  "SD1_DATA1",
+			  "SD1_DATA2",
+			  "SD1_DATA3",
+			  "CAM_GPIO1", /* GPIO40 */
+			  "WL_ON", /* GPIO41 */
+			  "BT_ON", /* GPIO42 */
+			  "WIFI_CLK", /* GPIO43 */
+			  "SDA0", /* GPIO44 */
+			  "SCL0", /* GPIO45 */
+			  "SMPS_SCL", /* GPIO46 */
+			  "SMPS_SDA", /* GPIO47 */
+			  /* Used by SD Card */
+			  "SD_CLK_R",
+			  "SD_CMD_R",
+			  "SD_DATA0_R",
+			  "SD_DATA1_R",
+			  "SD_DATA2_R",
+			  "SD_DATA3_R";
+
+	spi0_pins: spi0_pins {
+		brcm,pins = <9 10 11>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	spi0_cs_pins: spi0_cs_pins {
+		brcm,pins = <8 7>;
+		brcm,function = <1>; /* output */
+	};
+
+	i2c0_pins: i2c0 {
+		brcm,pins = <0 1>;
+		brcm,function = <4>;
+	};
+
+	i2c1_pins: i2c1 {
+		brcm,pins = <2 3>;
+		brcm,function = <4>;
+	};
+
+	i2s_pins: i2s {
+		brcm,pins = <18 19 20 21>;
+		brcm,function = <4>; /* alt0 */
+	};
+
+	sdio_pins: sdio_pins {
+		brcm,pins =     <34 35 36 37 38 39>;
+		brcm,function = <7>; // alt3 = SD1
+		brcm,pull =     <0 2 2 2 2 2>;
+	};
+
+	bt_pins: bt_pins {
+		brcm,pins = <43>;
+		brcm,function = <4>; /* alt0:GPCLK2 */
+		brcm,pull = <0>;
+	};
+
+	uart0_pins: uart0_pins {
+		brcm,pins = <30 31 32 33>;
+		brcm,function = <7>; /* alt3=UART0 */
+		brcm,pull = <2 0 0 2>; /* up none none up */
+	};
+
+	uart1_pins: uart1_pins {
+		brcm,pins;
+		brcm,function;
+		brcm,pull;
+	};
+
+	uart1_bt_pins: uart1_bt_pins {
+		brcm,pins = <32 33 30 31>;
+		brcm,function = <BCM2835_FSEL_ALT5>; /* alt5=UART1 */
+		brcm,pull = <0 2 2 0>;
+	};
+
+	audio_pins: audio_pins {
+		brcm,pins = <>;
+		brcm,function = <>;
+	};
+};
+
+&mmcnr {
+	pinctrl-names = "default";
+	pinctrl-0 = <&sdio_pins>;
+	bus-width = <4>;
+	status = "okay";
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	brcmf: wifi@1 {
+		reg = <1>;
+		compatible = "brcm,bcm4329-fmac";
+	};
+};
+
+&uart0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&uart0_pins &bt_pins>;
+	status = "okay";
+};
+
+&uart1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&uart1_pins>;
+	status = "okay";
+};
+
+&spi0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
+	cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
+
+	spidev0: spidev@0{
+		compatible = "spidev";
+		reg = <0>;	/* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+
+	spidev1: spidev@1{
+		compatible = "spidev";
+		reg = <1>;	/* CE1 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+};
+
+&i2c0if {
+	clock-frequency = <100000>;
+};
+
+&i2c1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c1_pins>;
+	clock-frequency = <100000>;
+};
+
+&i2c2 {
+	clock-frequency = <100000>;
+};
+
+&i2s {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2s_pins>;
+};
+
+&led_act {
+	gpios = <&gpio 29 GPIO_ACTIVE_LOW>;
+	default-state = "off";
+	linux,default-trigger = "actpwr";
+};
+
+&hdmi {
+	hpd-gpios = <&gpio 28 GPIO_ACTIVE_LOW>;
+};
+
+&vchiq {
+	pinctrl-names = "default";
+	pinctrl-0 = <&audio_pins>;
+};
+
+&bt {
+	shutdown-gpios = <&gpio 42 GPIO_ACTIVE_HIGH>;
+};
+
+&minibt {
+	shutdown-gpios = <&gpio 42 GPIO_ACTIVE_HIGH>;
+};
+
+&cam1_reg {
+	gpio = <&gpio 40 GPIO_ACTIVE_HIGH>;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+i2c_csi_dsi0: &i2c0 {
+};
+
+/ {
+	__overrides__ {
+		audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_hdmi=0'}";
+
+		act_led_gpio = <&led_act>,"gpios:4";
+		act_led_activelow = <&led_act>,"gpios:8";
+		act_led_trigger = <&led_act>,"linux,default-trigger";
+	};
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2710-rpi-zero-2.dts b/arch/arm/boot/dts/broadcom/bcm2710-rpi-zero-2.dts
new file mode 100644
index 000000000000..daa12bd30d6b
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2710-rpi-zero-2.dts
@@ -0,0 +1 @@
+#include "bcm2710-rpi-zero-2-w.dts"
diff --git a/arch/arm/boot/dts/broadcom/bcm2710.dtsi b/arch/arm/boot/dts/broadcom/bcm2710.dtsi
new file mode 100644
index 000000000000..bdcdbb51fab8
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2710.dtsi
@@ -0,0 +1,32 @@
+#define i2c0 i2c0if
+#include "bcm2837.dtsi"
+#undef i2c0
+#include "bcm270x.dtsi"
+
+/ {
+	compatible = "brcm,bcm2837", "brcm,bcm2836";
+
+	arm-pmu {
+		compatible = "arm,cortex-a53-pmu", "arm,cortex-a7-pmu";
+	};
+
+	soc {
+		dma-ranges = <0xc0000000 0x00000000 0x3f000000>,
+			     <0x7e000000 0x3f000000 0x01000000>;
+	};
+
+	__overrides__ {
+		arm_freq = <&cpu0>, "clock-frequency:0",
+		       <&cpu1>, "clock-frequency:0",
+		       <&cpu2>, "clock-frequency:0",
+		       <&cpu3>, "clock-frequency:0";
+	};
+};
+
+&system_timer {
+	status = "disabled";
+};
+
+&vc4 {
+	status = "disabled";
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts b/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts
index 353bb50ce542..e376a5bf8913 100644
--- a/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts
+++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi-4-b.dts
@@ -1,11 +1,19 @@
 // SPDX-License-Identifier: GPL-2.0
 /dts-v1/;
+#define BCM2711
+#define i2c0 i2c0if
 #include "bcm2711.dtsi"
 #include "bcm2711-rpi.dtsi"
+/delete-node/&i2c0mux;
 #include "bcm283x-rpi-led-deprecated.dtsi"
-#include "bcm283x-rpi-usb-peripheral.dtsi"
 #include "bcm283x-rpi-wifi-bt.dtsi"
 #include <dt-bindings/leds/common.h>
+#undef i2c0
+#include "bcm270x.dtsi"
+#define i2c0 i2c0mux
+#undef i2c0
+
+/delete-node/ &cam1_reg;
 
 / {
 	compatible = "raspberrypi,4-model-b", "brcm,bcm2711";
@@ -68,7 +76,7 @@ &expgpio {
 			  "VDD_SD_IO_SEL",
 			  "CAM_GPIO",		/*  5 */
 			  "SD_PWR_ON",
-			  "";
+			  "SD_OC_N";
 };
 
 &gpio {
@@ -82,21 +90,21 @@ &gpio {
 	 */
 	gpio-line-names = "ID_SDA",		/*  0 */
 			  "ID_SCL",
-			  "SDA1",
-			  "SCL1",
-			  "GPIO_GCLK",
+			  "GPIO2",
+			  "GPIO3",
+			  "GPIO4",
 			  "GPIO5",		/*  5 */
 			  "GPIO6",
-			  "SPI_CE1_N",
-			  "SPI_CE0_N",
-			  "SPI_MISO",
-			  "SPI_MOSI",		/* 10 */
-			  "SPI_SCLK",
+			  "GPIO7",
+			  "GPIO8",
+			  "GPIO9",
+			  "GPIO10",		/* 10 */
+			  "GPIO11",
 			  "GPIO12",
 			  "GPIO13",
 			  /* Serial port */
-			  "TXD1",
-			  "RXD1",		/* 15 */
+			  "GPIO14",
+			  "GPIO15",		/* 15 */
 			  "GPIO16",
 			  "GPIO17",
 			  "GPIO18",
@@ -214,7 +222,7 @@ leds {
 			led@0 {
 				reg = <0>;
 				color = <LED_COLOR_ID_GREEN>;
-				function = LED_FUNCTION_LAN;
+				function = "lan";//LED_FUNCTION_LAN;
 				default-state = "keep";
 			};
 
@@ -222,7 +230,7 @@ led@0 {
 			led@1 {
 				reg = <1>;
 				color = <LED_COLOR_ID_AMBER>;
-				function = LED_FUNCTION_LAN;
+				function = "lan";//LED_FUNCTION_LAN;
 				default-state = "keep";
 			};
 		};
@@ -270,3 +278,234 @@ &vec {
 &wifi_pwrseq {
 	reset-gpios = <&expgpio 1 GPIO_ACTIVE_LOW>;
 };
+
+// =============================================
+// Downstream rpi- changes
+
+#include "bcm271x-rpi-bt.dtsi"
+
+/ {
+	soc {
+		/delete-node/ pixelvalve@7e807000;
+		/delete-node/ hdmi@7e902000;
+	};
+};
+
+&phy1 {
+	/delete-node/ leds;
+};
+
+#include "bcm2711-rpi-ds.dtsi"
+#include "bcm283x-rpi-csi1-2lane.dtsi"
+#include "bcm283x-rpi-i2c0mux_0_44.dtsi"
+
+/ {
+	/delete-node/ wifi-pwrseq;
+};
+
+&mmcnr {
+	pinctrl-names = "default";
+	pinctrl-0 = <&sdio_pins>;
+	bus-width = <4>;
+	status = "okay";
+};
+
+&uart0 {
+	pinctrl-0 = <&uart0_pins &bt_pins>;
+	status = "okay";
+};
+
+&uart1 {
+	pinctrl-0 = <&uart1_pins>;
+};
+
+&spi0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
+	cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
+
+	spidev0: spidev@0{
+		compatible = "spidev";
+		reg = <0>;	/* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+
+	spidev1: spidev@1{
+		compatible = "spidev";
+		reg = <1>;	/* CE1 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+};
+
+&gpio {
+	gpio-line-names = "ID_SDA",
+			  "ID_SCL",
+			  "GPIO2",
+			  "GPIO3",
+			  "GPIO4",
+			  "GPIO5",
+			  "GPIO6",
+			  "GPIO7",
+			  "GPIO8",
+			  "GPIO9",
+			  "GPIO10",
+			  "GPIO11",
+			  "GPIO12",
+			  "GPIO13",
+			  "GPIO14",
+			  "GPIO15",
+			  "GPIO16",
+			  "GPIO17",
+			  "GPIO18",
+			  "GPIO19",
+			  "GPIO20",
+			  "GPIO21",
+			  "GPIO22",
+			  "GPIO23",
+			  "GPIO24",
+			  "GPIO25",
+			  "GPIO26",
+			  "GPIO27",
+			  "RGMII_MDIO",
+			  "RGMIO_MDC",
+			  /* Used by BT module */
+			  "CTS0",		/* 30 */
+			  "RTS0",
+			  "TXD0",
+			  "RXD0",
+			  /* Used by Wifi */
+			  "SD1_CLK",
+			  "SD1_CMD",		/* 35 */
+			  "SD1_DATA0",
+			  "SD1_DATA1",
+			  "SD1_DATA2",
+			  "SD1_DATA3",
+			  /* Shared with SPI flash */
+			  "PWM0_MISO",		/* 40 */
+			  "PWM1_MOSI",
+			  "STATUS_LED_G_CLK",
+			  "SPIFLASH_CE_N",
+			  "SDA0",
+			  "SCL0",		/* 45 */
+			  "RGMII_RXCLK",
+			  "RGMII_RXCTL",
+			  "RGMII_RXD0",
+			  "RGMII_RXD1",
+			  "RGMII_RXD2",		/* 50 */
+			  "RGMII_RXD3",
+			  "RGMII_TXCLK",
+			  "RGMII_TXCTL",
+			  "RGMII_TXD0",
+			  "RGMII_TXD1",		/* 55 */
+			  "RGMII_TXD2",
+			  "RGMII_TXD3";
+
+	bt_pins: bt_pins {
+		brcm,pins = "-"; // non-empty to keep btuart happy, //4 = 0
+				 // to fool pinctrl
+		brcm,function = <0>;
+		brcm,pull = <2>;
+	};
+
+	uart0_pins: uart0_pins {
+		brcm,pins = <32 33>;
+		brcm,function = <BCM2835_FSEL_ALT3>;
+		brcm,pull = <0 2>;
+	};
+
+	uart1_pins: uart1_pins {
+		brcm,pins;
+		brcm,function;
+		brcm,pull;
+	};
+
+	uart1_bt_pins: uart1_bt_pins {
+		brcm,pins = <32 33 30 31>;
+		brcm,function = <BCM2835_FSEL_ALT5>; /* alt5=UART1 */
+		brcm,pull = <0 2 2 0>;
+	};
+};
+
+&i2c0if {
+	clock-frequency = <100000>;
+};
+
+&i2c1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c1_pins>;
+	clock-frequency = <100000>;
+};
+
+&i2s {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2s_pins>;
+};
+
+// =============================================
+// Board specific stuff here
+
+&sdhost {
+	status = "disabled";
+};
+
+&phy1 {
+	led-modes = <0x00 0x08>; /* link/activity link */
+};
+
+&gpio {
+	audio_pins: audio_pins {
+		brcm,pins = <40 41>;
+		brcm,function = <4>;
+		brcm,pull = <0>;
+	};
+};
+
+&led_act {
+	default-state = "off";
+	linux,default-trigger = "mmc0";
+};
+
+&led_pwr {
+	default-state = "off";
+};
+
+&pwm1 {
+	status = "disabled";
+};
+
+&vchiq {
+	pinctrl-names = "default";
+	pinctrl-0 = <&audio_pins>;
+};
+
+&cam1_reg {
+	gpio = <&expgpio 5 GPIO_ACTIVE_HIGH>;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+i2c_csi_dsi0: &i2c0 {
+};
+
+/ {
+	__overrides__ {
+		audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_headphones=1 snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_headphones=0 snd_bcm2835.enable_hdmi=0'}";
+
+		act_led_gpio = <&led_act>,"gpios:4";
+		act_led_activelow = <&led_act>,"gpios:8";
+		act_led_trigger = <&led_act>,"linux,default-trigger";
+
+		pwr_led_gpio = <&led_pwr>,"gpios:4";
+		pwr_led_activelow = <&led_pwr>,"gpios:8";
+		pwr_led_trigger = <&led_pwr>,"linux,default-trigger";
+
+		eth_led0 = <&phy1>,"led-modes:0";
+		eth_led1 = <&phy1>,"led-modes:4";
+		eth_max_speed = <&phy1>,"max-speed:0";
+	};
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2711-rpi-400.dts b/arch/arm/boot/dts/broadcom/bcm2711-rpi-400.dts
index ca9be91b4f36..cd0bc647f7fc 100644
--- a/arch/arm/boot/dts/broadcom/bcm2711-rpi-400.dts
+++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi-400.dts
@@ -1,5 +1,4 @@
 // SPDX-License-Identifier: GPL-2.0
-/dts-v1/;
 #include "bcm2711-rpi-4-b.dts"
 
 / {
@@ -37,8 +36,54 @@ &led_pwr {
 	gpios = <&gpio 42 GPIO_ACTIVE_HIGH>;
 };
 
-/delete-node/ &led_act;
-
 &pm {
 	/delete-property/ system-power-controller;
 };
+
+// =============================================
+// Downstream rpi- changes
+
+/ {
+	chosen {
+		stdout-path = "serial0:115200n8";
+	};
+};
+
+&audio_pins {
+	brcm,pins = <>;
+	brcm,function = <>;
+};
+
+// Declare the LED but leave it disabled, in case a user wants to map it
+// to a GPIO on the header
+&led_act {
+	default-state = "off";
+	gpios = <&gpio 0 GPIO_ACTIVE_HIGH>;
+	status = "disabled";
+};
+
+&led_pwr {
+	default-state = "off";
+};
+
+&cam1_reg {
+	  /delete-property/ gpio;
+};
+
+cam0_reg: &cam_dummy_reg {
+};
+
+/ {
+	__overrides__ {
+		audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_hdmi=0'}";
+
+		act_led_gpio = <&led_act>,"gpios:4",
+			       <&led_act>,"status=okay";
+		act_led_activelow = <&led_act>,"gpios:8";
+		act_led_trigger = <&led_act>,"linux,default-trigger";
+		eth_max_speed = <&phy1>,"max-speed:0";
+		pwr_led_gpio = <&led_pwr>,"gpios:4";
+		pwr_led_activelow = <&led_pwr>,"gpios:8";
+		pwr_led_trigger = <&led_pwr>,"linux,default-trigger";
+	};
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2711-rpi-cm4.dts b/arch/arm/boot/dts/broadcom/bcm2711-rpi-cm4.dts
new file mode 100644
index 000000000000..14cdca845300
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi-cm4.dts
@@ -0,0 +1,498 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+#define BCM2711
+#define i2c0 i2c0if
+#include "bcm2711.dtsi"
+#include "bcm2711-rpi.dtsi"
+/delete-node/&i2c0mux;
+#include "bcm283x-rpi-led-deprecated.dtsi"
+#include "bcm283x-rpi-wifi-bt.dtsi"
+#undef i2c0
+#include "bcm270x.dtsi"
+#define i2c0 i2c0mux
+#undef i2c0
+
+/ {
+	compatible = "raspberrypi,4-compute-module", "brcm,bcm2711";
+	model = "Raspberry Pi Compute Module 4";
+
+	chosen {
+		/* 8250 auxiliary UART instead of pl011 */
+		stdout-path = "serial1:115200n8";
+	};
+
+	sd_io_1v8_reg: sd_io_1v8_reg {
+		compatible = "regulator-gpio";
+		regulator-name = "vdd-sd-io";
+		regulator-min-microvolt = <1800000>;
+		regulator-max-microvolt = <3300000>;
+		regulator-boot-on;
+		regulator-always-on;
+		regulator-settling-time-us = <5000>;
+		gpios = <&expgpio 4 GPIO_ACTIVE_HIGH>;
+		states = <1800000 0x1>,
+			 <3300000 0x0>;
+		status = "okay";
+	};
+
+	sd_vcc_reg: sd_vcc_reg {
+		compatible = "regulator-fixed";
+		regulator-name = "vcc-sd";
+		regulator-min-microvolt = <3300000>;
+		regulator-max-microvolt = <3300000>;
+		regulator-boot-on;
+		enable-active-high;
+		gpio = <&expgpio 6 GPIO_ACTIVE_HIGH>;
+	};
+};
+
+&bt {
+	shutdown-gpios = <&expgpio 0 GPIO_ACTIVE_HIGH>;
+};
+
+&ddc0 {
+	status = "okay";
+};
+
+&ddc1 {
+	status = "okay";
+};
+
+&expgpio {
+	gpio-line-names = "BT_ON",
+			  "WL_ON",
+			  "PWR_LED_OFF",
+			  "ANT1",
+			  "VDD_SD_IO_SEL",
+			  "CAM_GPIO",
+			  "SD_PWR_ON",
+			  "ANT2";
+
+	ant1: ant1 {
+		gpio-hog;
+		gpios = <3 GPIO_ACTIVE_HIGH>;
+		output-high;
+	};
+
+	ant2: ant2 {
+		gpio-hog;
+		gpios = <7 GPIO_ACTIVE_HIGH>;
+		output-low;
+	};
+};
+
+&gpio {
+	/*
+	 * Parts taken from rpi_SCH_4b_4p0_reduced.pdf and
+	 * the official GPU firmware DT blob.
+	 *
+	 * Legend:
+	 * "FOO" = GPIO line named "FOO" on the schematic
+	 * "FOO_N" = GPIO line named "FOO" on schematic, active low
+	 */
+	gpio-line-names = "ID_SDA",
+			  "ID_SCL",
+			  "SDA1",
+			  "SCL1",
+			  "GPIO_GCLK",
+			  "GPIO5",
+			  "GPIO6",
+			  "SPI_CE1_N",
+			  "SPI_CE0_N",
+			  "SPI_MISO",
+			  "SPI_MOSI",
+			  "SPI_SCLK",
+			  "GPIO12",
+			  "GPIO13",
+			  /* Serial port */
+			  "TXD1",
+			  "RXD1",
+			  "GPIO16",
+			  "GPIO17",
+			  "GPIO18",
+			  "GPIO19",
+			  "GPIO20",
+			  "GPIO21",
+			  "GPIO22",
+			  "GPIO23",
+			  "GPIO24",
+			  "GPIO25",
+			  "GPIO26",
+			  "GPIO27",
+			  "RGMII_MDIO",
+			  "RGMIO_MDC",
+			  /* Used by BT module */
+			  "CTS0",
+			  "RTS0",
+			  "TXD0",
+			  "RXD0",
+			  /* Used by Wifi */
+			  "SD1_CLK",
+			  "SD1_CMD",
+			  "SD1_DATA0",
+			  "SD1_DATA1",
+			  "SD1_DATA2",
+			  "SD1_DATA3",
+			  /* Shared with SPI flash */
+			  "PWM0_MISO",
+			  "PWM1_MOSI",
+			  "STATUS_LED_G_CLK",
+			  "SPIFLASH_CE_N",
+			  "SDA0",
+			  "SCL0",
+			  "RGMII_RXCLK",
+			  "RGMII_RXCTL",
+			  "RGMII_RXD0",
+			  "RGMII_RXD1",
+			  "RGMII_RXD2",
+			  "RGMII_RXD3",
+			  "RGMII_TXCLK",
+			  "RGMII_TXCTL",
+			  "RGMII_TXD0",
+			  "RGMII_TXD1",
+			  "RGMII_TXD2",
+			  "RGMII_TXD3";
+};
+
+&hdmi0 {
+	status = "okay";
+};
+
+&hdmi1 {
+	status = "okay";
+};
+
+&led_act {
+	gpios = <&gpio 42 GPIO_ACTIVE_HIGH>;
+};
+
+&leds {
+	led_pwr: led-pwr {
+		label = "PWR";
+		gpios = <&expgpio 2 GPIO_ACTIVE_LOW>;
+		default-state = "keep";
+		linux,default-trigger = "default-on";
+	};
+};
+
+&pixelvalve0 {
+	status = "okay";
+};
+
+&pixelvalve1 {
+	status = "okay";
+};
+
+&pixelvalve2 {
+	status = "okay";
+};
+
+&pixelvalve4 {
+	status = "okay";
+};
+
+&pwm1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&pwm1_0_gpio40 &pwm1_1_gpio41>;
+	status = "okay";
+};
+
+/* EMMC2 is used to drive the EMMC card */
+&emmc2 {
+	bus-width = <8>;
+	vqmmc-supply = <&sd_io_1v8_reg>;
+	vmmc-supply = <&sd_vcc_reg>;
+	broken-cd;
+	status = "okay";
+};
+
+&genet {
+	phy-handle = <&phy1>;
+	phy-mode = "rgmii-rxid";
+	status = "okay";
+};
+
+&genet_mdio {
+	phy1: ethernet-phy@0 {
+		/* No PHY interrupt */
+		reg = <0x0>;
+	};
+};
+
+&pcie0 {
+	pci@0,0 {
+		device_type = "pci";
+		#address-cells = <3>;
+		#size-cells = <2>;
+		ranges;
+
+		reg = <0 0 0 0 0>;
+	};
+};
+
+/* uart0 communicates with the BT module */
+&uart0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&uart0_ctsrts_gpio30 &uart0_gpio32>;
+	uart-has-rtscts;
+};
+
+/* uart1 is mapped to the pin header */
+&uart1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&uart1_gpio14>;
+	status = "okay";
+};
+
+&vc4 {
+	status = "okay";
+};
+
+&vec {
+	status = "disabled";
+};
+
+&wifi_pwrseq {
+	reset-gpios = <&expgpio 1 GPIO_ACTIVE_LOW>;
+};
+
+// =============================================
+// Downstream rpi- changes
+
+#include "bcm271x-rpi-bt.dtsi"
+
+/ {
+	soc {
+		/delete-node/ pixelvalve@7e807000;
+		/delete-node/ hdmi@7e902000;
+	};
+};
+
+#include "bcm2711-rpi-ds.dtsi"
+#include "bcm283x-rpi-csi0-2lane.dtsi"
+#include "bcm283x-rpi-csi1-4lane.dtsi"
+#include "bcm283x-rpi-i2c0mux_0_44.dtsi"
+
+/ {
+	/delete-node/ wifi-pwrseq;
+};
+
+&mmcnr {
+	pinctrl-names = "default";
+	pinctrl-0 = <&sdio_pins>;
+	bus-width = <4>;
+	status = "okay";
+};
+
+&uart0 {
+	pinctrl-0 = <&uart0_pins &bt_pins>;
+	status = "okay";
+};
+
+&uart1 {
+	pinctrl-0 = <&uart1_pins>;
+};
+
+&spi0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
+	cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
+
+	spidev0: spidev@0{
+		compatible = "spidev";
+		reg = <0>;	/* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+
+	spidev1: spidev@1{
+		compatible = "spidev";
+		reg = <1>;	/* CE1 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+};
+
+&gpio {
+	gpio-line-names = "ID_SDA",
+			  "ID_SCL",
+			  "GPIO2",
+			  "GPIO3",
+			  "GPIO4",
+			  "GPIO5",
+			  "GPIO6",
+			  "GPIO7",
+			  "GPIO8",
+			  "GPIO9",
+			  "GPIO10",
+			  "GPIO11",
+			  "GPIO12",
+			  "GPIO13",
+			  "GPIO14",
+			  "GPIO15",
+			  "GPIO16",
+			  "GPIO17",
+			  "GPIO18",
+			  "GPIO19",
+			  "GPIO20",
+			  "GPIO21",
+			  "GPIO22",
+			  "GPIO23",
+			  "GPIO24",
+			  "GPIO25",
+			  "GPIO26",
+			  "GPIO27",
+			  "RGMII_MDIO",
+			  "RGMIO_MDC",
+			  /* Used by BT module */
+			  "CTS0",
+			  "RTS0",
+			  "TXD0",
+			  "RXD0",
+			  /* Used by Wifi */
+			  "SD1_CLK",
+			  "SD1_CMD",
+			  "SD1_DATA0",
+			  "SD1_DATA1",
+			  "SD1_DATA2",
+			  "SD1_DATA3",
+			  /* Shared with SPI flash */
+			  "PWM0_MISO",
+			  "PWM1_MOSI",
+			  "STATUS_LED_G_CLK",
+			  "SPIFLASH_CE_N",
+			  "SDA0",
+			  "SCL0",
+			  "RGMII_RXCLK",
+			  "RGMII_RXCTL",
+			  "RGMII_RXD0",
+			  "RGMII_RXD1",
+			  "RGMII_RXD2",
+			  "RGMII_RXD3",
+			  "RGMII_TXCLK",
+			  "RGMII_TXCTL",
+			  "RGMII_TXD0",
+			  "RGMII_TXD1",
+			  "RGMII_TXD2",
+			  "RGMII_TXD3";
+
+	bt_pins: bt_pins {
+		brcm,pins = "-"; // non-empty to keep btuart happy, //4 = 0
+				 // to fool pinctrl
+		brcm,function = <0>;
+		brcm,pull = <2>;
+	};
+
+	uart0_pins: uart0_pins {
+		brcm,pins = <32 33>;
+		brcm,function = <BCM2835_FSEL_ALT3>;
+		brcm,pull = <0 2>;
+	};
+
+	uart1_pins: uart1_pins {
+		brcm,pins;
+		brcm,function;
+		brcm,pull;
+	};
+
+	uart1_bt_pins: uart1_bt_pins {
+		brcm,pins = <32 33 30 31>;
+		brcm,function = <BCM2835_FSEL_ALT5>; /* alt5=UART1 */
+		brcm,pull = <0 2 2 0>;
+	};
+};
+
+&i2c0if {
+	clock-frequency = <100000>;
+};
+
+&i2c1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c1_pins>;
+	clock-frequency = <100000>;
+};
+
+&i2s {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2s_pins>;
+};
+
+// =============================================
+// Board specific stuff here
+
+&sdhost {
+	status = "disabled";
+};
+
+&phy1 {
+	led-modes = <0x00 0x08>; /* link/activity link */
+};
+
+&gpio {
+	audio_pins: audio_pins {
+		brcm,pins = <>;
+		brcm,function = <>;
+	};
+};
+
+&led_act {
+	default-state = "off";
+	linux,default-trigger = "mmc0";
+};
+
+&led_pwr {
+	default-state = "off";
+};
+
+&pwm1 {
+	status = "disabled";
+};
+
+&vchiq {
+	pinctrl-names = "default";
+	pinctrl-0 = <&audio_pins>;
+};
+
+cam0_reg: &cam1_reg {
+	gpio = <&expgpio 5 GPIO_ACTIVE_HIGH>;
+};
+
+i2c_csi_dsi0: &i2c0 {
+};
+
+/ {
+	__overrides__ {
+		audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_hdmi=0'}";
+
+		act_led_gpio = <&led_act>,"gpios:4";
+		act_led_activelow = <&led_act>,"gpios:8";
+		act_led_trigger = <&led_act>,"linux,default-trigger";
+
+		pwr_led_gpio = <&led_pwr>,"gpios:4";
+		pwr_led_activelow = <&led_pwr>,"gpios:8";
+		pwr_led_trigger = <&led_pwr>,"linux,default-trigger";
+
+		eth_led0 = <&phy1>,"led-modes:0";
+		eth_led1 = <&phy1>,"led-modes:4";
+		eth_max_speed = <&phy1>,"max-speed:0";
+
+		ant1 =  <&ant1>,"output-high?=on",
+			<&ant1>, "output-low?=off",
+			<&ant2>, "output-high?=off",
+			<&ant2>, "output-low?=on";
+		ant2 =  <&ant1>,"output-high?=off",
+			<&ant1>, "output-low?=on",
+			<&ant2>, "output-high?=on",
+			<&ant2>, "output-low?=off";
+		noant = <&ant1>,"output-high?=off",
+			<&ant1>, "output-low?=on",
+			<&ant2>, "output-high?=off",
+			<&ant2>, "output-low?=on";
+		noanthogs = <&ant1>,"status=disabled",
+			<&ant2>, "status=disabled";
+
+		pcie_tperst_clk_ms = <&pcie0>,"brcm,tperst-clk-ms:0";
+	};
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2711-rpi-cm4s.dts b/arch/arm/boot/dts/broadcom/bcm2711-rpi-cm4s.dts
new file mode 100644
index 000000000000..47a5f5ee140c
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi-cm4s.dts
@@ -0,0 +1,314 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+#define BCM2711
+#define i2c0 i2c0if
+#include "bcm2711.dtsi"
+#include "bcm2711-rpi.dtsi"
+/delete-node/&i2c0mux;
+#include "bcm283x-rpi-led-deprecated.dtsi"
+#undef i2c0
+#include "bcm270x.dtsi"
+#define i2c0 i2c0mux
+#undef i2c0
+
+/ {
+	compatible = "raspberrypi,4-compute-module-s", "brcm,bcm2711";
+	model = "Raspberry Pi Compute Module 4S";
+};
+
+&ddc0 {
+	status = "okay";
+};
+
+&gpio {
+	/*
+	 * Parts taken from rpi_SCH_4b_4p0_reduced.pdf and
+	 * the official GPU firmware DT blob.
+	 *
+	 * Legend:
+	 * "FOO" = GPIO line named "FOO" on the schematic
+	 * "FOO_N" = GPIO line named "FOO" on schematic, active low
+	 */
+	gpio-line-names = "ID_SDA",
+			  "ID_SCL",
+			  "GPIO2",
+			  "GPIO3",
+			  "GPIO4",
+			  "GPIO5",
+			  "GPIO6",
+			  "GPIO7",
+			  "GPIO8",
+			  "GPIO9",
+			  "GPIO10",
+			  "GPIO11",
+			  "GPIO12",
+			  "GPIO13",
+			  "GPIO14",
+			  "GPIO15",
+			  "GPIO16",
+			  "GPIO17",
+			  "GPIO18",
+			  "GPIO19",
+			  "GPIO20",
+			  "GPIO21",
+			  "GPIO22",
+			  "GPIO23",
+			  "GPIO24",
+			  "GPIO25",
+			  "GPIO26",
+			  "GPIO27",
+			  "GPIO28",
+			  "GPIO29",
+			  "GPIO30",
+			  "GPIO31",
+			  "GPIO32",
+			  "GPIO33",
+			  "GPIO34",
+			  "GPIO35",
+			  "GPIO36",
+			  "GPIO37",
+			  "GPIO38",
+			  "GPIO39",
+			  "PWM0_MISO",
+			  "PWM1_MOSI",
+			  "GPIO42",
+			  "GPIO43",
+			  "GPIO44",
+			  "GPIO45",
+			  "-", // GPIO46
+			  "-", // GPIO47
+			  "-", // GPIO48
+			  "EMMC_DISABLE_N", // GPIO49
+			  "-", // GPIO50
+			  "-", // GPIO51
+			  "-", // GPIO52
+			  "-", // GPIO53
+			  "-", // GPIO54
+			  "-", // GPIO55
+			  "-", // GPIO56
+			  "GPIO_SEL"; // GPIO57
+};
+
+&hdmi0 {
+	status = "okay";
+};
+
+&led_act {
+	gpios = <&virtgpio 0 GPIO_ACTIVE_HIGH>;
+};
+
+&pixelvalve0 {
+	status = "okay";
+};
+
+&pixelvalve1 {
+	status = "okay";
+};
+
+&pixelvalve2 {
+	status = "okay";
+};
+
+&pixelvalve4 {
+	status = "okay";
+};
+
+&pwm1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&pwm1_0_gpio40 &pwm1_1_gpio41>;
+	status = "okay";
+};
+
+/* EMMC2 is used to drive the EMMC card */
+&emmc2 {
+	bus-width = <8>;
+	broken-cd;
+	status = "okay";
+};
+
+&pcie0 {
+	status = "disabled";
+};
+
+&vchiq {
+	interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>;
+};
+
+&vc4 {
+	status = "okay";
+};
+
+&vec {
+	status = "disabled";
+};
+
+// =============================================
+// Downstream rpi- changes
+
+#include "bcm2711-rpi-ds.dtsi"
+
+/ {
+	soc {
+		/delete-node/ pixelvalve@7e807000;
+		/delete-node/ hdmi@7e902000;
+	};
+};
+
+#include "bcm283x-rpi-csi0-2lane.dtsi"
+#include "bcm283x-rpi-csi1-4lane.dtsi"
+#include "bcm283x-rpi-i2c0mux_0_28.dtsi"
+
+/ {
+	chosen {
+		bootargs = "coherent_pool=1M snd_bcm2835.enable_headphones=0 cgroup_disable=memory numa_policy=interleave";
+	};
+
+	aliases {
+		serial0 = &uart0;
+		serial1 = &uart1;
+		/delete-property/ i2c20;
+		/delete-property/ i2c21;
+	};
+
+	/delete-node/ wifi-pwrseq;
+};
+
+&firmware {
+		virtgpio: virtgpio {
+			compatible = "brcm,bcm2835-virtgpio";
+			gpio-controller;
+			#gpio-cells = <2>;
+			status = "okay";
+		};
+};
+
+&uart0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&uart0_pins>;
+	status = "okay";
+};
+
+&spi0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
+	cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
+
+	spidev0: spidev@0{
+		compatible = "spidev";
+		reg = <0>;	/* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+
+	spidev1: spidev@1{
+		compatible = "spidev";
+		reg = <1>;	/* CE1 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+};
+
+&gpio {
+	uart0_pins: uart0_pins {
+		brcm,pins;
+		brcm,function;
+		brcm,pull;
+	};
+};
+
+&i2c0if {
+	clock-frequency = <100000>;
+};
+
+&i2c1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c1_pins>;
+	clock-frequency = <100000>;
+};
+
+&i2s {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2s_pins>;
+};
+
+// =============================================
+// Board specific stuff here
+
+/* Enable USB in OTG-aware mode */
+&usb {
+	compatible = "brcm,bcm2835-usb";
+	dr_mode = "otg";
+	g-np-tx-fifo-size = <32>;
+	g-rx-fifo-size = <558>;
+	g-tx-fifo-size = <512 512 512 512 512 256 256>;
+	status = "okay";
+};
+
+&sdhost {
+	status = "disabled";
+};
+
+&gpio {
+	audio_pins: audio_pins {
+		brcm,pins = <>;
+		brcm,function = <>;
+	};
+
+	enable_eeprom_hog: enable_eeprom_hog {
+		gpio-hog;
+		gpios = <57 GPIO_ACTIVE_HIGH>;
+		output-low;
+		status = "disabled";
+	};
+};
+
+/* Permanently disable HDMI1 */
+&hdmi1 {
+	compatible = "disabled";
+};
+
+/* Permanently disable DDC1 */
+&ddc1 {
+	compatible = "disabled";
+};
+
+&led_act {
+	default-state = "off";
+	linux,default-trigger = "mmc0";
+};
+
+&pwm1 {
+	status = "disabled";
+};
+
+&vchiq {
+	pinctrl-names = "default";
+	pinctrl-0 = <&audio_pins>;
+};
+
+&cam1_reg {
+	gpio = <&gpio 3 GPIO_ACTIVE_HIGH>;
+	status = "disabled";
+};
+
+cam0_reg: &cam0_regulator {
+	gpio = <&gpio 31 GPIO_ACTIVE_HIGH>;
+	status = "disabled";
+};
+
+i2c_csi_dsi0: &i2c0 {
+};
+
+/ {
+	__overrides__ {
+		audio = <&chosen>,"bootargs{on='snd_bcm2835.enable_hdmi=1',off='snd_bcm2835.enable_hdmi=0'}";
+
+		act_led_gpio = <&led_act>,"gpios:4";
+		act_led_activelow = <&led_act>,"gpios:8";
+		act_led_trigger = <&led_act>,"linux,default-trigger";
+
+		enable_eeprom = <&enable_eeprom_hog>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2711-rpi-ds.dtsi b/arch/arm/boot/dts/broadcom/bcm2711-rpi-ds.dtsi
new file mode 100644
index 000000000000..5f39465c95f2
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi-ds.dtsi
@@ -0,0 +1,570 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "bcm270x-rpi.dtsi"
+
+/ {
+	chosen {
+		bootargs = "coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_headphones=0 cgroup_disable=memory numa_policy=interleave nvme.max_host_mem_size_mb=0";
+	};
+
+	__overrides__ {
+		arm_freq;
+		eee = <&chosen>,"bootargs{on='',off='genet.eee=N'}";
+		hdmi = <&hdmi0>,"status",
+		       <&hdmi1>,"status";
+		nvmem_cust_rw = <&nvmem_cust>,"rw?";
+		nvmem_priv_rw = <&nvmem_priv>,"rw?";
+		pcie = <&pcie0>,"status";
+		sd = <&emmc2>,"status";
+
+		sd_poll_once = <&emmc2>, "non-removable?";
+		spi_dma4 = <&spi0>, "dmas:0=", <&dma40>,
+			   <&spi0>, "dmas:8=", <&dma40>;
+		i2s_dma4 = <&i2s>, "dmas:0=", <&dma40>,
+			   <&i2s>, "dmas:8=", <&dma40>;
+	};
+
+	scb: scb {
+	     /* Add a label */
+	};
+
+	soc: soc {
+	     /* Add a label */
+	};
+
+	chosen {
+		stdout-path = "serial0:115200n8";
+	};
+
+	aliases {
+		uart2 = &uart2;
+		uart3 = &uart3;
+		uart4 = &uart4;
+		uart5 = &uart5;
+		serial0 = &uart1;
+		serial1 = &uart0;
+		serial2 = &uart2;
+		serial3 = &uart3;
+		serial4 = &uart4;
+		serial5 = &uart5;
+		mmc0 = &emmc2;
+		mmc1 = &mmcnr;
+		mmc2 = &sdhost;
+		i2c3 = &i2c3;
+		i2c4 = &i2c4;
+		i2c5 = &i2c5;
+		i2c6 = &i2c6;
+		i2c20 = &ddc0;
+		i2c21 = &ddc1;
+		spi3 = &spi3;
+		spi4 = &spi4;
+		spi5 = &spi5;
+		spi6 = &spi6;
+		/delete-property/ intc;
+	};
+
+	/*
+	 * Add a node with a dma-ranges value that exists only to be found
+	 * by of_dma_get_max_cpu_address, and hence limit the DMA zone.
+	 */
+	zone_dma {
+		#address-cells = <1>;
+		#size-cells = <1>;
+		dma-ranges = <0x0  0x0 0x0  0x40000000>;
+	};
+};
+
+&vc4 {
+	raspberrypi,firmware = <&firmware>;
+};
+
+&cma {
+	/* Limit cma to the lower 768MB to allow room for HIGHMEM on 32-bit */
+	alloc-ranges = <0x0 0x00000000 0x30000000>;
+};
+
+&soc {
+	/* Add the physical <-> DMA mapping for the I/O space */
+	dma-ranges = <0xc0000000  0x0 0x00000000  0x40000000>,
+		     <0x7c000000  0x0 0xfc000000  0x03800000>;
+	nvmem {
+		compatible = "simple-bus";
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		nvmem_otp: nvmem_otp {
+			compatible = "raspberrypi,rpi-otp";
+			firmware = <&firmware>;
+			reg = <0 166>;
+			status = "okay";
+		};
+
+		nvmem_cust: nvmem_cust {
+			compatible = "raspberrypi,rpi-otp";
+			firmware = <&firmware>;
+			reg = <1 8>;
+			status = "okay";
+		};
+
+		nvmem_priv: nvmem_priv {
+			compatible = "raspberrypi,rpi-otp";
+			firmware = <&firmware>;
+			reg = <3 8>;
+			status = "okay";
+		};
+	};
+};
+
+&scb {
+	#size-cells = <2>;
+
+	ranges = <0x0 0x7c000000  0x0 0xfc000000  0x0 0x03800000>,
+		 <0x0 0x40000000  0x0 0xff800000  0x0 0x00800000>,
+		 <0x6 0x00000000  0x6 0x00000000  0x0 0x40000000>,
+		 <0x0 0x00000000  0x0 0x00000000  0x0 0xfc000000>;
+	dma-ranges = <0x4 0x7c000000  0x0 0xfc000000  0x0 0x03800000>,
+		     <0x0 0x00000000  0x0 0x00000000  0x4 0x00000000>;
+
+	dma40: dma@7e007b00 {
+		compatible = "brcm,bcm2711-dma";
+		reg = <0x0 0x7e007b00  0x0 0x400>;
+		interrupts =
+			<GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>, /* dma4 11 */
+			<GIC_SPI 90 IRQ_TYPE_LEVEL_HIGH>, /* dma4 12 */
+			<GIC_SPI 91 IRQ_TYPE_LEVEL_HIGH>, /* dma4 13 */
+			<GIC_SPI 92 IRQ_TYPE_LEVEL_HIGH>; /* dma4 14 */
+		interrupt-names = "dma11",
+			"dma12",
+			"dma13",
+			"dma14";
+		#dma-cells = <1>;
+		brcm,dma-channel-mask = <0x7800>;
+	};
+
+	xhci: xhci@7e9c0000 {
+		compatible = "generic-xhci";
+		status = "disabled";
+		reg = <0x0 0x7e9c0000  0x0 0x100000>;
+		interrupts = <GIC_SPI 176 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&power RPI_POWER_DOMAIN_USB>;
+	};
+};
+
+&hevc_dec {
+	reg = <0x0 0x7eb00000  0x0 0x10000>, /* HEVC */
+		<0x0 0x7eb10000  0x0 0x1000>;  /* INTC */
+};
+
+&pcie0 {
+	reg = <0x0 0x7d500000  0x0 0x9310>;
+	ranges = <0x02000000 0x0 0xc0000000 0x6 0x00000000
+		  0x0 0x40000000>;
+};
+
+&genet {
+	reg = <0x0 0x7d580000  0x0 0x10000>;
+};
+
+&dma40 {
+	/* The VPU firmware uses DMA channel 11 for VCHIQ */
+	brcm,dma-channel-mask = <0x7000>;
+};
+
+&vchiq {
+	compatible = "brcm,bcm2711-vchiq";
+};
+
+&firmwarekms {
+	compatible = "raspberrypi,rpi-firmware-kms-2711";
+	interrupts = <GIC_SPI 112 IRQ_TYPE_LEVEL_HIGH>;
+};
+
+&smi {
+	interrupts = <GIC_SPI 112 IRQ_TYPE_LEVEL_HIGH>;
+};
+
+&mmc {
+	interrupts = <GIC_SPI 126 IRQ_TYPE_LEVEL_HIGH>;
+};
+
+&mmcnr {
+	interrupts = <GIC_SPI 126 IRQ_TYPE_LEVEL_HIGH>;
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	brcmf: wifi@1 {
+		reg = <1>;
+		compatible = "brcm,bcm4329-fmac";
+	};
+};
+
+&csi0 {
+	interrupts = <GIC_SPI 102 IRQ_TYPE_LEVEL_HIGH>;
+};
+
+&csi1 {
+	interrupts = <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>;
+};
+
+&random {
+	compatible = "brcm,bcm2711-rng200";
+	status = "okay";
+};
+
+&usb {
+	/* Enable the FIQ support */
+	reg = <0x7e980000 0x10000>,
+	      <0x7e00b200 0x200>;
+	interrupts = <GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>,
+		     <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>;
+	status = "disabled";
+};
+
+&gpio {
+	interrupts = <GIC_SPI 113 IRQ_TYPE_LEVEL_HIGH>,
+		     <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH>;
+
+	spi0_pins: spi0_pins {
+		brcm,pins = <9 10 11>;
+		brcm,function = <BCM2835_FSEL_ALT0>;
+	};
+
+	spi0_cs_pins: spi0_cs_pins {
+		brcm,pins = <8 7>;
+		brcm,function = <BCM2835_FSEL_GPIO_OUT>;
+	};
+
+	spi3_pins: spi3_pins {
+		brcm,pins = <1 2 3>;
+		brcm,function = <BCM2835_FSEL_ALT3>;
+	};
+
+	spi3_cs_pins: spi3_cs_pins {
+		brcm,pins = <0 24>;
+		brcm,function = <BCM2835_FSEL_GPIO_OUT>;
+	};
+
+	spi4_pins: spi4_pins {
+		brcm,pins = <5 6 7>;
+		brcm,function = <BCM2835_FSEL_ALT3>;
+	};
+
+	spi4_cs_pins: spi4_cs_pins {
+		brcm,pins = <4 25>;
+		brcm,function = <BCM2835_FSEL_GPIO_OUT>;
+	};
+
+	spi5_pins: spi5_pins {
+		brcm,pins = <13 14 15>;
+		brcm,function = <BCM2835_FSEL_ALT3>;
+	};
+
+	spi5_cs_pins: spi5_cs_pins {
+		brcm,pins = <12 26>;
+		brcm,function = <BCM2835_FSEL_GPIO_OUT>;
+	};
+
+	spi6_pins: spi6_pins {
+		brcm,pins = <19 20 21>;
+		brcm,function = <BCM2835_FSEL_ALT3>;
+	};
+
+	spi6_cs_pins: spi6_cs_pins {
+		brcm,pins = <18 27>;
+		brcm,function = <BCM2835_FSEL_GPIO_OUT>;
+	};
+
+	i2c0_pins: i2c0 {
+		brcm,pins = <0 1>;
+		brcm,function = <BCM2835_FSEL_ALT0>;
+		brcm,pull = <BCM2835_PUD_UP>;
+	};
+
+	i2c1_pins: i2c1 {
+		brcm,pins = <2 3>;
+		brcm,function = <BCM2835_FSEL_ALT0>;
+		brcm,pull = <BCM2835_PUD_UP>;
+	};
+
+	i2c3_pins: i2c3 {
+		brcm,pins = <4 5>;
+		brcm,function = <BCM2835_FSEL_ALT5>;
+		brcm,pull = <BCM2835_PUD_UP>;
+	};
+
+	i2c4_pins: i2c4 {
+		brcm,pins = <8 9>;
+		brcm,function = <BCM2835_FSEL_ALT5>;
+		brcm,pull = <BCM2835_PUD_UP>;
+	};
+
+	i2c5_pins: i2c5 {
+		brcm,pins = <12 13>;
+		brcm,function = <BCM2835_FSEL_ALT5>;
+		brcm,pull = <BCM2835_PUD_UP>;
+	};
+
+	i2c6_pins: i2c6 {
+		brcm,pins = <22 23>;
+		brcm,function = <BCM2835_FSEL_ALT5>;
+		brcm,pull = <BCM2835_PUD_UP>;
+	};
+
+	i2s_pins: i2s {
+		brcm,pins = <18 19 20 21>;
+		brcm,function = <BCM2835_FSEL_ALT0>;
+	};
+
+	sdio_pins: sdio_pins {
+		brcm,pins =     <34 35 36 37 38 39>;
+		brcm,function = <BCM2835_FSEL_ALT3>; // alt3 = SD1
+		brcm,pull =     <0 2 2 2 2 2>;
+	};
+
+	uart2_pins: uart2_pins {
+		brcm,pins = <0 1>;
+		brcm,function = <BCM2835_FSEL_ALT4>;
+		brcm,pull = <0 2>;
+	};
+
+	uart3_pins: uart3_pins {
+		brcm,pins = <4 5>;
+		brcm,function = <BCM2835_FSEL_ALT4>;
+		brcm,pull = <0 2>;
+	};
+
+	uart4_pins: uart4_pins {
+		brcm,pins = <8 9>;
+		brcm,function = <BCM2835_FSEL_ALT4>;
+		brcm,pull = <0 2>;
+	};
+
+	uart5_pins: uart5_pins {
+		brcm,pins = <12 13>;
+		brcm,function = <BCM2835_FSEL_ALT4>;
+		brcm,pull = <0 2>;
+	};
+};
+
+&emmc2 {
+	mmc-ddr-3_3v;
+};
+
+&vc4 {
+	status = "disabled";
+};
+
+&pixelvalve0 {
+	status = "disabled";
+};
+
+&pixelvalve1 {
+	status = "disabled";
+};
+
+&pixelvalve2 {
+	status = "disabled";
+};
+
+&pixelvalve3 {
+	status = "disabled";
+};
+
+&pixelvalve4 {
+	status = "disabled";
+};
+
+&hdmi0 {
+	reg = <0x7ef00700 0x300>,
+	      <0x7ef00300 0x200>,
+	      <0x7ef00f00 0x80>,
+	      <0x7ef00f80 0x80>,
+	      <0x7ef01b00 0x200>,
+	      <0x7ef01f00 0x400>,
+	      <0x7ef00200 0x80>,
+	      <0x7ef04300 0x100>,
+	      <0x7ef20000 0x100>,
+	      <0x7ef00100 0x30>;
+	reg-names = "hdmi",
+		    "dvp",
+		    "phy",
+		    "rm",
+		    "packet",
+		    "metadata",
+		    "csc",
+		    "cec",
+		    "hd",
+		    "intr2";
+	clocks = <&firmware_clocks 13>,
+		 <&firmware_clocks 14>,
+		 <&dvp 0>,
+		 <&clk_27MHz>;
+	dmas = <&dma40 (10|(1<<30)|(1<<24)|(10<<16)|(15<<20))>;
+	status = "disabled";
+};
+
+&ddc0 {
+	status = "disabled";
+};
+
+&hdmi1 {
+	reg = <0x7ef05700 0x300>,
+	      <0x7ef05300 0x200>,
+	      <0x7ef05f00 0x80>,
+	      <0x7ef05f80 0x80>,
+	      <0x7ef06b00 0x200>,
+	      <0x7ef06f00 0x400>,
+	      <0x7ef00280 0x80>,
+	      <0x7ef09300 0x100>,
+	      <0x7ef20000 0x100>,
+	      <0x7ef00100 0x30>;
+	reg-names = "hdmi",
+		    "dvp",
+		    "phy",
+		    "rm",
+		    "packet",
+		    "metadata",
+		    "csc",
+		    "cec",
+		    "hd",
+		    "intr2";
+	clocks = <&firmware_clocks 13>,
+		 <&firmware_clocks 14>,
+		 <&dvp 1>,
+		 <&clk_27MHz>;
+	dmas = <&dma40 (17|(1<<30)|(1<<24)|(10<<16)|(15<<20))>;
+	status = "disabled";
+};
+
+&ddc1 {
+	status = "disabled";
+};
+
+&dvp {
+	status = "disabled";
+};
+
+&vec {
+	clocks = <&firmware_clocks 15>;
+};
+
+&aon_intr {
+	interrupts = <GIC_SPI 96 IRQ_TYPE_EDGE_RISING>;
+	status = "disabled";
+};
+
+&system_timer {
+	status = "disabled";
+};
+
+&i2c0 {
+      /delete-property/ compatible;
+      /delete-property/ interrupts;
+};
+
+&i2c0if {
+	compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c";
+	interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>;
+};
+
+i2c_arm: &i2c1 {};
+i2c_vc: &i2c0 {};
+
+&i2c3 {
+	pinctrl-0 = <&i2c3_pins>;
+	pinctrl-names = "default";
+};
+
+&i2c4 {
+	pinctrl-0 = <&i2c4_pins>;
+	pinctrl-names = "default";
+};
+
+&i2c5 {
+	pinctrl-0 = <&i2c5_pins>;
+	pinctrl-names = "default";
+};
+
+&i2c6 {
+	pinctrl-0 = <&i2c6_pins>;
+	pinctrl-names = "default";
+};
+
+&spi3 {
+	pinctrl-0 = <&spi3_pins &spi3_cs_pins>;
+	pinctrl-names = "default";
+};
+
+&spi4 {
+	pinctrl-0 = <&spi4_pins &spi4_cs_pins>;
+	pinctrl-names = "default";
+};
+
+&spi5 {
+	pinctrl-0 = <&spi5_pins &spi5_cs_pins>;
+	pinctrl-names = "default";
+};
+
+&spi6 {
+	pinctrl-0 = <&spi6_pins &spi6_cs_pins>;
+	pinctrl-names = "default";
+};
+
+&uart0 {
+	compatible = "arm,pl011-axi", "arm,pl011", "arm,primecell";
+};
+
+&uart2 {
+	compatible = "arm,pl011-axi", "arm,pl011", "arm,primecell";
+	pinctrl-0 = <&uart2_pins>;
+	pinctrl-names = "default";
+};
+
+&uart3 {
+	compatible = "arm,pl011-axi", "arm,pl011", "arm,primecell";
+	pinctrl-0 = <&uart3_pins>;
+	pinctrl-names = "default";
+};
+
+&uart4 {
+	compatible = "arm,pl011-axi", "arm,pl011", "arm,primecell";
+	pinctrl-0 = <&uart4_pins>;
+	pinctrl-names = "default";
+};
+
+&uart5 {
+	compatible = "arm,pl011-axi", "arm,pl011", "arm,primecell";
+	pinctrl-0 = <&uart5_pins>;
+	pinctrl-names = "default";
+};
+
+&axiperf {
+	compatible = "brcm,bcm2711-axiperf";
+};
+
+/delete-node/ &v3d;
+
+/ {
+	v3dbus: v3dbus {
+		compatible = "simple-bus";
+		#address-cells = <1>;
+		#size-cells = <2>;
+		ranges = <0x7c500000  0x0 0xfc500000  0x0 0x03300000>,
+			 <0x40000000  0x0 0xff800000  0x0 0x00800000>;
+		dma-ranges = <0x00000000  0x0 0x00000000  0x4 0x00000000>;
+
+		v3d: v3d@7ec04000 {
+			compatible = "brcm,2711-v3d";
+			reg =
+			    <0x7ec00000  0x0 0x4000>,
+			    <0x7ec04000  0x0 0x4000>;
+			reg-names = "hub", "core0";
+
+			power-domains = <&pm BCM2835_POWER_DOMAIN_GRAFX_V3D>;
+			resets = <&pm BCM2835_RESET_V3D>;
+			clocks = <&firmware_clocks 5>;
+			clocks-names = "v3d";
+			interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>;
+			status = "disabled";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2711-rpi.dtsi b/arch/arm/boot/dts/broadcom/bcm2711-rpi.dtsi
index c78ed064d166..0eaf232d4861 100644
--- a/arch/arm/boot/dts/broadcom/bcm2711-rpi.dtsi
+++ b/arch/arm/boot/dts/broadcom/bcm2711-rpi.dtsi
@@ -15,6 +15,7 @@ aliases {
 		ethernet0 = &genet;
 		pcie0 = &pcie0;
 		blconfig = &blconfig;
+		blpubkey = &blpubkey;
 	};
 
 	i2c0mux: i2c-mux0 {
@@ -91,6 +92,18 @@ blconfig: nvram@0 {
 		no-map;
 		status = "disabled";
 	};
+	/*
+	 * RPi4 will copy the binary public key blob (if present) from the bootloader
+	 * into memory for use by the OS.
+	 */
+	blpubkey: nvram@1 {
+		compatible = "raspberrypi,bootloader-public-key", "nvmem-rmem";
+		#address-cells = <1>;
+		#size-cells = <1>;
+		reg = <0x0 0x0 0x0>;
+		no-map;
+		status = "disabled";
+	};
 };
 
 &v3d {
@@ -100,3 +113,7 @@ &v3d {
 &vchiq {
 	interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>;
 };
+
+&hevc_dec {
+	clocks = <&firmware_clocks 11>;
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm2711.dtsi b/arch/arm/boot/dts/broadcom/bcm2711.dtsi
index c06d9f5e53c8..bb137d8da1a8 100644
--- a/arch/arm/boot/dts/broadcom/bcm2711.dtsi
+++ b/arch/arm/boot/dts/broadcom/bcm2711.dtsi
@@ -277,7 +277,7 @@ pwm1: pwm@7e20c800 {
 			reg = <0x7e20c800 0x28>;
 			clocks = <&clocks BCM2835_CLOCK_PWM>;
 			assigned-clocks = <&clocks BCM2835_CLOCK_PWM>;
-			assigned-clock-rates = <10000000>;
+			assigned-clock-rates = <50000000>;
 			#pwm-cells = <3>;
 			status = "disabled";
 		};
@@ -602,21 +602,6 @@ genet_mdio: mdio@e14 {
 			};
 		};
 
-		xhci: usb@7e9c0000 {
-			compatible = "brcm,bcm2711-xhci", "brcm,xhci-brcm-v2";
-			reg = <0x0 0x7e9c0000 0x100000>;
-			#address-cells = <1>;
-			#size-cells = <0>;
-			interrupts = <GIC_SPI 176 IRQ_TYPE_LEVEL_HIGH>;
-			power-domains = <&pm BCM2835_POWER_DOMAIN_USB>;
-			/* DWC2 and this IP block share the same USB PHY,
-			 * enabling both at the same time results in lockups.
-			 * So keep this node disabled and let the bootloader
-			 * decide which interface should be enabled.
-			 */
-			status = "disabled";
-		};
-
 		v3d: gpu@7ec00000 {
 			compatible = "brcm,2711-v3d";
 			reg = <0x0 0x7ec00000 0x4000>,
@@ -627,6 +612,15 @@ v3d: gpu@7ec00000 {
 			resets = <&pm BCM2835_RESET_V3D>;
 			interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>;
 		};
+
+		hevc_dec: codec@7eb10000 {
+			compatible = "brcm,bcm2711-hevc-dec", "raspberrypi,hevc-dec";
+			reg = <0x0 0x7eb00000  0x10000>, /* HEVC */
+			      <0x0 0x7eb10000  0x1000>;  /* INTC */
+			reg-names = "hevc",
+				    "intc";
+			interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;
+		};
 	};
 };
 
diff --git a/arch/arm/boot/dts/broadcom/bcm271x-rpi-bt.dtsi b/arch/arm/boot/dts/broadcom/bcm271x-rpi-bt.dtsi
new file mode 100644
index 000000000000..c77e280ccd16
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm271x-rpi-bt.dtsi
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0
+
+&uart0 {
+	bt: bluetooth {
+		compatible = "brcm,bcm43438-bt";
+		max-speed = <3000000>;
+		shutdown-gpios = <&expgpio 0 GPIO_ACTIVE_HIGH>;
+		local-bd-address = [ 00 00 00 00 00 00 ];
+		fallback-bd-address; // Don't override a valid address
+		status = "okay";
+	};
+};
+
+&uart1 {
+	minibt: bluetooth {
+		compatible = "brcm,bcm43438-bt";
+		max-speed = <230400>;
+		shutdown-gpios = <&expgpio 0 GPIO_ACTIVE_HIGH>;
+		local-bd-address = [ 00 00 00 00 00 00 ];
+		fallback-bd-address; // Don't override a valid address
+		status = "disabled";
+	};
+};
+
+/ {
+	chosen {
+		bootargs = "coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_headphones=0 cgroup_disable=memory";
+	};
+
+	aliases {
+		bluetooth = &bt;
+	};
+
+	__overrides__ {
+		bdaddr = <&bt>,"local-bd-address[",
+		       <&bt>,"fallback-bd-address?=0",
+		       <&minibt>,"local-bd-address[",
+		       <&minibt>,"fallback-bd-address?=0";
+		krnbt = <&bt>,"status";
+		krnbt_baudrate = <&bt>,"max-speed:0", <&minibt>,"max-speed:0";
+	};
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm283x-rpi-csi0-2lane.dtsi b/arch/arm/boot/dts/broadcom/bcm283x-rpi-csi0-2lane.dtsi
new file mode 100644
index 000000000000..6e4ce8622b47
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm283x-rpi-csi0-2lane.dtsi
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
+&csi0 {
+	brcm,num-data-lanes = <2>;
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm283x-rpi-csi1-2lane.dtsi b/arch/arm/boot/dts/broadcom/bcm283x-rpi-csi1-2lane.dtsi
new file mode 100644
index 000000000000..6938f4daacdc
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm283x-rpi-csi1-2lane.dtsi
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
+&csi1 {
+	brcm,num-data-lanes = <2>;
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm283x-rpi-csi1-4lane.dtsi b/arch/arm/boot/dts/broadcom/bcm283x-rpi-csi1-4lane.dtsi
new file mode 100644
index 000000000000..b37037437bee
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm283x-rpi-csi1-4lane.dtsi
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
+&csi1 {
+	brcm,num-data-lanes = <4>;
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm283x-rpi-i2c0mux_0_28.dtsi b/arch/arm/boot/dts/broadcom/bcm283x-rpi-i2c0mux_0_28.dtsi
new file mode 100644
index 000000000000..38f0074bce3f
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm283x-rpi-i2c0mux_0_28.dtsi
@@ -0,0 +1,4 @@
+&i2c0mux {
+	pinctrl-0 = <&i2c0_gpio0>;
+	pinctrl-1 = <&i2c0_gpio28>;
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm283x-rpi-i2c0mux_0_44.dtsi b/arch/arm/boot/dts/broadcom/bcm283x-rpi-i2c0mux_0_44.dtsi
new file mode 100644
index 000000000000..119946d878db
--- /dev/null
+++ b/arch/arm/boot/dts/broadcom/bcm283x-rpi-i2c0mux_0_44.dtsi
@@ -0,0 +1,4 @@
+&i2c0mux {
+	pinctrl-0 = <&i2c0_gpio0>;
+	pinctrl-1 = <&i2c0_gpio44>;
+};
diff --git a/arch/arm/boot/dts/broadcom/bcm283x.dtsi b/arch/arm/boot/dts/broadcom/bcm283x.dtsi
index 69b0919f1324..562c4e9d08cc 100644
--- a/arch/arm/boot/dts/broadcom/bcm283x.dtsi
+++ b/arch/arm/boot/dts/broadcom/bcm283x.dtsi
@@ -363,7 +363,7 @@ dsi0: dsi@7e209000 {
 			#size-cells = <0>;
 			#clock-cells = <1>;
 
-			clocks = <&clocks BCM2835_PLLA_DSI0>,
+			clocks = <&clocks BCM2835_PLLD_DSI0>,
 				 <&clocks BCM2835_CLOCK_DSI0E>,
 				 <&clocks BCM2835_CLOCK_DSI0P>;
 			clock-names = "phy", "escape", "pixel";
@@ -415,7 +415,7 @@ pwm: pwm@7e20c000 {
 			reg = <0x7e20c000 0x28>;
 			clocks = <&clocks BCM2835_CLOCK_PWM>;
 			assigned-clocks = <&clocks BCM2835_CLOCK_PWM>;
-			assigned-clock-rates = <10000000>;
+			assigned-clock-rates = <50000000>;
 			#pwm-cells = <3>;
 			status = "disabled";
 		};
@@ -502,6 +502,10 @@ usb: usb@7e980000 {
 	};
 
 	clocks {
+		compatible = "simple-bus";
+		#address-cells = <1>;
+		#size-cells = <0>;
+
 		/* The oscillator is the root of the clock tree. */
 		clk_osc: clk-osc {
 			compatible = "fixed-clock";
diff --git a/arch/arm/boot/dts/overlays/Makefile b/arch/arm/boot/dts/overlays/Makefile
new file mode 100644
index 000000000000..e32dd9dc89f1
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/Makefile
@@ -0,0 +1,374 @@
+# Overlays for the Raspberry Pi platform
+
+dtb-$(CONFIG_ARCH_BCM2835) += overlay_map.dtb hat_map.dtb
+
+dtbo-$(CONFIG_ARCH_BCM2835) += \
+	act-led.dtbo \
+	adafruit-st7735r.dtbo \
+	adafruit18.dtbo \
+	adau1977-adc.dtbo \
+	adau7002-simple.dtbo \
+	ads1015.dtbo \
+	ads1115.dtbo \
+	ads7846.dtbo \
+	adv7282m.dtbo \
+	adv728x-m.dtbo \
+	akkordion-iqdacplus.dtbo \
+	allo-boss-dac-pcm512x-audio.dtbo \
+	allo-boss2-dac-audio.dtbo \
+	allo-digione.dtbo \
+	allo-katana-dac-audio.dtbo \
+	allo-piano-dac-pcm512x-audio.dtbo \
+	allo-piano-dac-plus-pcm512x-audio.dtbo \
+	anyspi.dtbo \
+	apds9960.dtbo \
+	applepi-dac.dtbo \
+	arducam-64mp.dtbo \
+	arducam-pivariety.dtbo \
+	at86rf233.dtbo \
+	audioinjector-addons.dtbo \
+	audioinjector-bare-i2s.dtbo \
+	audioinjector-isolated-soundcard.dtbo \
+	audioinjector-ultra.dtbo \
+	audioinjector-wm8731-audio.dtbo \
+	audiosense-pi.dtbo \
+	audremap.dtbo \
+	audremap-pi5.dtbo \
+	balena-fin.dtbo \
+	bcm2712d0.dtbo \
+	camera-mux-2port.dtbo \
+	camera-mux-4port.dtbo \
+	cap1106.dtbo \
+	chipcap2.dtbo \
+	chipdip-dac.dtbo \
+	cirrus-wm5102.dtbo \
+	cm-swap-i2c0.dtbo \
+	cma.dtbo \
+	crystalfontz-cfa050_pi_m.dtbo \
+	cutiepi-panel.dtbo \
+	dacberry400.dtbo \
+	dht11.dtbo \
+	dionaudio-kiwi.dtbo \
+	dionaudio-loco.dtbo \
+	dionaudio-loco-v2.dtbo \
+	disable-bt.dtbo \
+	disable-bt-pi5.dtbo \
+	disable-emmc2.dtbo \
+	disable-wifi.dtbo \
+	disable-wifi-pi5.dtbo \
+	dpi18.dtbo \
+	dpi18cpadhi.dtbo \
+	dpi24.dtbo \
+	draws.dtbo \
+	dwc-otg-deprecated.dtbo \
+	dwc2.dtbo \
+	edt-ft5406.dtbo \
+	enc28j60.dtbo \
+	enc28j60-spi2.dtbo \
+	exc3000.dtbo \
+	ezsound-6x8iso.dtbo \
+	fbtft.dtbo \
+	fe-pi-audio.dtbo \
+	fsm-demo.dtbo \
+	gc9a01.dtbo \
+	ghost-amp.dtbo \
+	goodix.dtbo \
+	googlevoicehat-soundcard.dtbo \
+	gpio-charger.dtbo \
+	gpio-fan.dtbo \
+	gpio-hog.dtbo \
+	gpio-ir.dtbo \
+	gpio-ir-tx.dtbo \
+	gpio-key.dtbo \
+	gpio-led.dtbo \
+	gpio-no-bank0-irq.dtbo \
+	gpio-no-irq.dtbo \
+	gpio-poweroff.dtbo \
+	gpio-shutdown.dtbo \
+	hd44780-i2c-lcd.dtbo \
+	hd44780-lcd.dtbo \
+	hdmi-backlight-hwhack-gpio.dtbo \
+	hifiberry-adc.dtbo \
+	hifiberry-adc8x.dtbo \
+	hifiberry-amp.dtbo \
+	hifiberry-amp100.dtbo \
+	hifiberry-amp3.dtbo \
+	hifiberry-amp4pro.dtbo \
+	hifiberry-dac.dtbo \
+	hifiberry-dac8x.dtbo \
+	hifiberry-dacplus.dtbo \
+	hifiberry-dacplus-pro.dtbo \
+	hifiberry-dacplus-std.dtbo \
+	hifiberry-dacplusadc.dtbo \
+	hifiberry-dacplusadcpro.dtbo \
+	hifiberry-dacplusdsp.dtbo \
+	hifiberry-dacplushd.dtbo \
+	hifiberry-digi.dtbo \
+	hifiberry-digi-pro.dtbo \
+	hifiberry-studio-dac8x.dtbo \
+	hifiberry-studio-dac8x-pro.dtbo \
+	highperi.dtbo \
+	hy28a.dtbo \
+	hy28b.dtbo \
+	hy28b-2017.dtbo \
+	i-sabre-q2m.dtbo \
+	i2c-bcm2708.dtbo \
+	i2c-fan.dtbo \
+	i2c-gpio.dtbo \
+	i2c-mux.dtbo \
+	i2c-pwm-pca9685a.dtbo \
+	i2c-rtc.dtbo \
+	i2c-rtc-gpio.dtbo \
+	i2c-sensor.dtbo \
+	i2c0.dtbo \
+	i2c0-pi5.dtbo \
+	i2c1.dtbo \
+	i2c1-pi5.dtbo \
+	i2c2-pi5.dtbo \
+	i2c3.dtbo \
+	i2c3-pi5.dtbo \
+	i2c4.dtbo \
+	i2c5.dtbo \
+	i2c6.dtbo \
+	i2s-dac.dtbo \
+	i2s-gpio28-31.dtbo \
+	i2s-master-dac.dtbo \
+	ilitek-ts-i2c.dtbo \
+	ilitek251x.dtbo \
+	imx219.dtbo \
+	imx258.dtbo \
+	imx283.dtbo \
+	imx290.dtbo \
+	imx296.dtbo \
+	imx327.dtbo \
+	imx335.dtbo \
+	imx378.dtbo \
+	imx415.dtbo \
+	imx462.dtbo \
+	imx477.dtbo \
+	imx500.dtbo \
+	imx500-pi5.dtbo \
+	imx519.dtbo \
+	imx708.dtbo \
+	interludeaudio-analog.dtbo \
+	interludeaudio-digital.dtbo \
+	iqaudio-codec.dtbo \
+	iqaudio-dac.dtbo \
+	iqaudio-dacplus.dtbo \
+	iqaudio-digi-wm8804-audio.dtbo \
+	iqs550.dtbo \
+	irs1125.dtbo \
+	jedec-spi-nor.dtbo \
+	justboom-both.dtbo \
+	justboom-dac.dtbo \
+	justboom-digi.dtbo \
+	ltc294x.dtbo \
+	max98357a.dtbo \
+	maxtherm.dtbo \
+	mbed-dac.dtbo \
+	mcp23017.dtbo \
+	mcp23s17.dtbo \
+	mcp2515.dtbo \
+	mcp2515-can0.dtbo \
+	mcp2515-can1.dtbo \
+	mcp251xfd.dtbo \
+	mcp3008.dtbo \
+	mcp3202.dtbo \
+	mcp342x.dtbo \
+	media-center.dtbo \
+	merus-amp.dtbo \
+	midi-uart0.dtbo \
+	midi-uart0-pi5.dtbo \
+	midi-uart1.dtbo \
+	midi-uart1-pi5.dtbo \
+	midi-uart2.dtbo \
+	midi-uart2-pi5.dtbo \
+	midi-uart3.dtbo \
+	midi-uart3-pi5.dtbo \
+	midi-uart4.dtbo \
+	midi-uart4-pi5.dtbo \
+	midi-uart5.dtbo \
+	minipitft13.dtbo \
+	miniuart-bt.dtbo \
+	mipi-dbi-spi.dtbo \
+	mira220.dtbo \
+	mlx90640.dtbo \
+	mmc.dtbo \
+	mz61581.dtbo \
+	nospi10.dtbo \
+	ov2311.dtbo \
+	ov5647.dtbo \
+	ov64a40.dtbo \
+	ov7251.dtbo \
+	ov9281.dtbo \
+	papirus.dtbo \
+	pca953x.dtbo \
+	pcf857x.dtbo \
+	pcie-32bit-dma.dtbo \
+	pcie-32bit-dma-pi5.dtbo \
+	pciex1-compat-pi5.dtbo \
+	pibell.dtbo \
+	pifacedigital.dtbo \
+	pifi-40.dtbo \
+	pifi-dac-hd.dtbo \
+	pifi-dac-zero.dtbo \
+	pifi-mini-210.dtbo \
+	piglow.dtbo \
+	pimidi.dtbo \
+	pineboards-hat-ai.dtbo \
+	pineboards-hatdrive-poe-plus.dtbo \
+	piscreen.dtbo \
+	piscreen2r.dtbo \
+	pisound.dtbo \
+	pisound-micro.dtbo \
+	pisound-pi5.dtbo \
+	pitft22.dtbo \
+	pitft28-capacitive.dtbo \
+	pitft28-resistive.dtbo \
+	pitft35-resistive.dtbo \
+	pivision.dtbo \
+	pps-gpio.dtbo \
+	pps-rp1.dtbo \
+	proto-codec.dtbo \
+	pwm.dtbo \
+	pwm-2chan.dtbo \
+	pwm-gpio.dtbo \
+	pwm-gpio-fan.dtbo \
+	pwm-ir-tx.dtbo \
+	pwm-pio.dtbo \
+	pwm1.dtbo \
+	qca7000.dtbo \
+	qca7000-uart0.dtbo \
+	ramoops.dtbo \
+	ramoops-pi4.dtbo \
+	rootmaster.dtbo \
+	rotary-encoder.dtbo \
+	rpi-backlight.dtbo \
+	rpi-codeczero.dtbo \
+	rpi-dacplus.dtbo \
+	rpi-dacpro.dtbo \
+	rpi-digiampplus.dtbo \
+	rpi-ft5406.dtbo \
+	rpi-fw-uart.dtbo \
+	rpi-poe.dtbo \
+	rpi-poe-plus.dtbo \
+	rpi-power-hat-b.dtbo \
+	rpi-power-hat-t.dtbo \
+	rpi-sense.dtbo \
+	rpi-sense-v2.dtbo \
+	rpi-tv.dtbo \
+	rra-digidac1-wm8741-audio.dtbo \
+	sainsmart18.dtbo \
+	sc16is750-i2c.dtbo \
+	sc16is752-i2c.dtbo \
+	sc16is75x-spi.dtbo \
+	sdhost.dtbo \
+	sdio.dtbo \
+	sdio-pi5.dtbo \
+	seeed-can-fd-hat-v1.dtbo \
+	seeed-can-fd-hat-v2.dtbo \
+	sh1106-spi.dtbo \
+	si446x-spi0.dtbo \
+	smi.dtbo \
+	smi-dev.dtbo \
+	smi-nand.dtbo \
+	spi-gpio35-39.dtbo \
+	spi-gpio40-45.dtbo \
+	spi-rtc.dtbo \
+	spi0-0cs.dtbo \
+	spi0-1cs.dtbo \
+	spi0-1cs-inverted.dtbo \
+	spi0-2cs.dtbo \
+	spi1-1cs.dtbo \
+	spi1-2cs.dtbo \
+	spi1-3cs.dtbo \
+	spi2-1cs.dtbo \
+	spi2-1cs-pi5.dtbo \
+	spi2-2cs.dtbo \
+	spi2-2cs-pi5.dtbo \
+	spi2-3cs.dtbo \
+	spi3-1cs.dtbo \
+	spi3-1cs-pi5.dtbo \
+	spi3-2cs.dtbo \
+	spi3-2cs-pi5.dtbo \
+	spi4-1cs.dtbo \
+	spi4-2cs.dtbo \
+	spi5-1cs.dtbo \
+	spi5-1cs-pi5.dtbo \
+	spi5-2cs.dtbo \
+	spi5-2cs-pi5.dtbo \
+	spi6-1cs.dtbo \
+	spi6-2cs.dtbo \
+	ssd1306.dtbo \
+	ssd1306-spi.dtbo \
+	ssd1327-spi.dtbo \
+	ssd1331-spi.dtbo \
+	ssd1351-spi.dtbo \
+	sunfounder-pipower3.dtbo \
+	sunfounder-pironman5.dtbo \
+	superaudioboard.dtbo \
+	sx150x.dtbo \
+	tc358743.dtbo \
+	tc358743-audio.dtbo \
+	tc358743-pi5.dtbo \
+	tinylcd35.dtbo \
+	tpm-slb9670.dtbo \
+	tpm-slb9673.dtbo \
+	uart0.dtbo \
+	uart0-pi5.dtbo \
+	uart1.dtbo \
+	uart1-pi5.dtbo \
+	uart2.dtbo \
+	uart2-pi5.dtbo \
+	uart3.dtbo \
+	uart3-pi5.dtbo \
+	uart4.dtbo \
+	uart4-pi5.dtbo \
+	uart5.dtbo \
+	udrc.dtbo \
+	ugreen-dabboard.dtbo \
+	upstream.dtbo \
+	upstream-pi4.dtbo \
+	vc4-fkms-v3d.dtbo \
+	vc4-fkms-v3d-pi4.dtbo \
+	vc4-kms-dpi-generic.dtbo \
+	vc4-kms-dpi-hyperpixel2r.dtbo \
+	vc4-kms-dpi-hyperpixel4.dtbo \
+	vc4-kms-dpi-hyperpixel4sq.dtbo \
+	vc4-kms-dpi-panel.dtbo \
+	vc4-kms-dsi-7inch.dtbo \
+	vc4-kms-dsi-generic.dtbo \
+	vc4-kms-dsi-ili79600-10-1inch.dtbo \
+	vc4-kms-dsi-ili9881-5inch.dtbo \
+	vc4-kms-dsi-ili9881-7inch.dtbo \
+	vc4-kms-dsi-lt070me05000.dtbo \
+	vc4-kms-dsi-lt070me05000-v2.dtbo \
+	vc4-kms-dsi-waveshare-800x480.dtbo \
+	vc4-kms-dsi-waveshare-panel.dtbo \
+	vc4-kms-dsi-waveshare-panel-v2.dtbo \
+	vc4-kms-kippah-7inch.dtbo \
+	vc4-kms-v3d.dtbo \
+	vc4-kms-v3d-pi4.dtbo \
+	vc4-kms-v3d-pi5.dtbo \
+	vc4-kms-vga666.dtbo \
+	vec-gpio-pi5.dtbo \
+	vga666.dtbo \
+	vl805.dtbo \
+	w1-gpio.dtbo \
+	w1-gpio-pi5.dtbo \
+	w1-gpio-pullup.dtbo \
+	w1-gpio-pullup-pi5.dtbo \
+	w5500.dtbo \
+	watterott-display.dtbo \
+	waveshare-can-fd-hat-mode-a.dtbo \
+	waveshare-can-fd-hat-mode-b.dtbo \
+	wifimac.dtbo \
+	wittypi.dtbo \
+	wm8960-soundcard.dtbo \
+	ws2812-pio.dtbo
+
+targets += dtbs dtbs_install
+targets += $(dtbo-y)
+
+always-y	:= $(dtbo-y)
+clean-files	:= *.dtbo
diff --git a/arch/arm/boot/dts/overlays/README b/arch/arm/boot/dts/overlays/README
new file mode 100644
index 000000000000..28451981d0bd
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/README
@@ -0,0 +1,6202 @@
+Introduction
+============
+
+This directory contains Device Tree overlays. Device Tree makes it possible
+to support many hardware configurations with a single kernel and without the
+need to explicitly load or blacklist kernel modules. Note that this isn't a
+"pure" Device Tree configuration (c.f. MACH_BCM2835) - some on-board devices
+are still configured by the board support code, but the intention is to
+eventually reach that goal.
+
+On Raspberry Pi, Device Tree usage is controlled from /boot/config.txt. By
+default, the Raspberry Pi kernel boots with device tree enabled. You can
+completely disable DT usage (for now) by adding:
+
+    device_tree=
+
+to your config.txt, which should cause your Pi to revert to the old way of
+doing things after a reboot.
+
+In /boot you will find a .dtb for each base platform. This describes the
+hardware that is part of the Raspberry Pi board. The loader (start.elf and its
+siblings) selects the .dtb file appropriate for the platform by name, and reads
+it into memory. At this point, all of the optional interfaces (i2c, i2s, spi)
+are disabled, but they can be enabled using Device Tree parameters:
+
+    dtparam=i2c=on,i2s=on,spi=on
+
+However, this shouldn't be necessary in many use cases because loading an
+overlay that requires one of those interfaces will cause it to be enabled
+automatically, and it is advisable to only enable interfaces if they are
+needed.
+
+Configuring additional, optional hardware is done using Device Tree overlays
+(see below).
+
+GPIO numbering uses the hardware pin numbering scheme (aka BCM scheme) and
+not the physical pin numbers.
+
+raspi-config
+============
+
+The Advanced Options section of the raspi-config utility can enable and disable
+Device Tree use, as well as toggling the I2C and SPI interfaces. Note that it
+is possible to both enable an interface and blacklist the driver, if for some
+reason you should want to defer the loading.
+
+Modules
+=======
+
+As well as describing the hardware, Device Tree also gives enough information
+to allow suitable driver modules to be located and loaded, with the corollary
+that unneeded modules are not loaded. As a result it should be possible to
+remove lines from /etc/modules, and /etc/modprobe.d/raspi-blacklist.conf can
+have its contents deleted (or commented out).
+
+Using Overlays
+==============
+
+Overlays are loaded using the "dtoverlay" config.txt setting. As an example,
+consider I2C Real Time Clock drivers. In the pre-DT world these would be loaded
+by writing a magic string comprising a device identifier and an I2C address to
+a special file in /sys/bus/i2c/devices, having first loaded the driver for
+the I2C interface and the RTC device - something like this:
+
+    modprobe i2c-bcm2835
+    modprobe rtc-ds1307
+    echo ds1307 0x68 > /sys/bus/i2c/devices/i2c-1/new_device
+
+With DT enabled, this becomes a line in config.txt:
+
+    dtoverlay=i2c-rtc,ds1307
+
+This causes the file /boot/overlays/i2c-rtc.dtbo to be loaded and a "node"
+describing the DS1307 I2C device to be added to the Device Tree for the Pi. By
+default it usees address 0x68, but this can be modified with an additional DT
+parameter:
+
+    dtoverlay=i2c-rtc,ds1307,addr=0x68
+
+Parameters usually have default values, although certain parameters are
+mandatory. See the list of overlays below for a description of the parameters
+and their defaults.
+
+Making new Overlays based on existing Overlays
+==============================================
+
+Recent overlays have been designed in a more general way, so that they can be
+adapted to hardware by changing their parameters. When you have additional
+hardware with more than one device of a kind, you end up using the same overlay
+multiple times with other parameters, e.g.
+
+    # 2 CAN FD interfaces on spi but with different pins
+    dtoverlay=mcp251xfd,spi0-0,interrupt=25
+    dtoverlay=mcp251xfd,spi0-1,interrupt=24
+
+    # a realtime clock on i2c
+    dtoverlay=i2c-rtc,pcf85063
+
+While this approach does work, it requires knowledge about the hardware design.
+It is more feasible to simplify things for the end user by providing a single
+overlay as it is done the traditional way.
+
+A new overlay can be generated by using ovmerge utility.
+https://github.com/raspberrypi/utils/blob/master/ovmerge/ovmerge
+
+To generate an overlay for the above configuration we pass the configuration
+to ovmerge and add the -c flag.
+
+    ovmerge -c mcp251xfd-overlay.dts,spi0-0,interrupt=25 \
+               mcp251xfd-overlay.dts,spi0-1,interrupt=24 \
+               i2c-rtc-overlay.dts,pcf85063 \
+    >> merged-overlay.dts
+
+The -c option writes the command above as a comment into the overlay as
+a marker that this overlay is generated and how it was generated.
+After compiling the overlay it can be loaded in a single line.
+
+    dtoverlay=merged
+
+It does the same as the original configuration but without parameters.
+
+The Overlay and Parameter Reference
+===================================
+
+N.B. When editing this file, please preserve the indentation levels to make it
+simple to parse programmatically. NO HARD TABS.
+
+
+Name:   <The base DTB>
+Info:   Configures the base Raspberry Pi hardware
+Load:   <loaded automatically>
+Params:
+        act_led_trigger         Choose which activity the LED tracks.
+                                Use "heartbeat" for a nice load indicator.
+                                (default "mmc")
+
+        act_led_activelow       Set to "on" to invert the sense of the LED
+                                (default "off")
+                                N.B. For Pi 3B, 3B+, 3A+ and 4B, use the act-led
+                                overlay.
+
+        act_led_gpio            Set which GPIO to use for the activity LED
+                                (in case you want to connect it to an external
+                                device)
+                                (default "16" on a non-Plus board, "47" on a
+                                Plus or Pi 2)
+                                N.B. For Pi 3B, 3B+, 3A+ and 4B, use the act-led
+                                overlay.
+
+        ant1                    Select antenna 1 (default). CM4/5 only.
+
+        ant2                    Select antenna 2. CM4/5 only.
+
+        noant                   Disable both antennas. CM4/5 only.
+
+        noanthogs               Disable the GPIO hogs on the antenna controls
+                                so they can be controlled at runtime. Note that
+                                using this parameter without suitable OS
+                                support will result in attenuated WiFi and
+                                Bluetooth signals. CM4/5 only.
+
+        audio                   Set to "on" to enable the onboard ALSA audio
+                                interface (default "off")
+
+        axiperf                 Set to "on" to enable the AXI bus performance
+                                monitors.
+                                See /sys/kernel/debug/raspberrypi_axi_monitor
+                                for the results.
+
+        bdaddr                  Set an alternative Bluetooth address (BDADDR).
+                                The value should be a 6-byte hexadecimal value,
+                                with or without colon separators, written least-
+                                significant-byte first. For example,
+                                bdaddr=06:05:04:03:02:01
+                                will set the BDADDR to 01:02:03:04:05:06.
+
+        button_debounce         Set the debounce delay (in ms) on the power/
+                                shutdown button (default 50ms)
+
+        cam0_reg                Controls CAM 0 regulator.
+                                Disabled by default on CM1 & 3.
+                                Enabled by default on all other boards.
+
+        cam0_reg_gpio           Set GPIO for CAM 0 regulator.
+                                NB override switches to the normal GPIO driver,
+                                even if the original was on the GPIO expander.
+
+        cam1_reg                Controls CAM 1 regulator.
+                                Disabled by default on CM1 & 3.
+                                Enabled by default on all other boards.
+
+        cam1_reg_gpio           Set GPIO for CAM 1 regulator.
+                                NB override switches to the normal GPIO driver,
+                                even if the original was on the GPIO expander.
+
+        cam0_sync               Enable a GPIO to reflect frame sync from CSI0,
+                                going high on frame start, and low on frame end.
+
+        cam0_sync_inverted      Enable a GPIO to reflect frame sync from CSI0
+                                going low on frame start, and high on frame end.
+
+        cam1_sync               Enable a GPIO to reflect frame sync from CSI1,
+                                going high on frame start, and low on frame end.
+
+        cam1_sync_inverted      Enable a GPIO to reflect frame sync from CSI1
+                                going low on frame start, and high on frame end.
+
+        cooling_fan             Enables the Pi 5 cooling fan (enabled
+                                automatically by the firmware)
+
+        drm_fb0_rp1_dpi         Assign /dev/fb0 to the RP1 DPI output
+
+        drm_fb0_rp1_dsi0        Assign /dev/fb0 to the RP1 DSI0 output
+
+        drm_fb0_rp1_dsi1        Assign /dev/fb0 to the RP1 DSI1 output
+
+        drm_fb0_vc4             Assign /dev/fb0 to the vc4 outputs
+
+        drm_fb1_rp1_dpi         Assign /dev/fb1 to the RP1 DPI output
+
+        drm_fb1_rp1_dsi0        Assign /dev/fb1 to the RP1 DSI0 output
+
+        drm_fb1_rp1_dsi1        Assign /dev/fb1 to the RP1 DSI1 output
+
+        drm_fb1_vc4             Assign /dev/fb1 to the vc4 outputs
+
+        drm_fb2_rp1_dpi         Assign /dev/fb2 to the RP1 DPI output
+
+        drm_fb2_rp1_dsi0        Assign /dev/fb2 to the RP1 DSI0 output
+
+        drm_fb2_rp1_dsi1        Assign /dev/fb2 to the RP1 DSI1 output
+
+        drm_fb2_vc4             Assign /dev/fb2 to the vc4 outputs
+
+        eee                     Enable Energy Efficient Ethernet support for
+                                compatible devices (default "on"). See also
+                                "tx_lpi_timer". Pi3B+, Pi4 and CM4 only.
+
+        enable_eeprom           Set to "on" to enable the onboard bootloader
+                                EEPROM by driving GPIO 57 low
+                                (default "off"). CM4S only.
+
+        eth_downshift_after     Set the number of auto-negotiation failures
+                                after which the 1000Mbps modes are disabled.
+                                Legal values are 2, 3, 4, 5 and 0, where
+                                0 means never downshift (default 2). Pi3B+ only.
+
+        eth_led0                Set mode of LED0 - amber on Pi3B+ (default "1"),
+                                green on Pi4/5 (default "0").
+                                The legal values are:
+
+                                Pi3B+
+
+                                0=link/activity          1=link1000/activity
+                                2=link100/activity       3=link10/activity
+                                4=link100/1000/activity  5=link10/1000/activity
+                                6=link10/100/activity    14=off    15=on
+
+                                Pi4/5
+
+                                0=Speed/Activity         1=Speed
+                                2=Flash activity         3=FDX
+                                4=Off                    5=On
+                                6=Alt                    7=Speed/Flash
+                                8=Link                   9=Activity
+
+        eth_led1                Set mode of LED1 - green on Pi3B+ (default "6"),
+                                amber on Pi4/5 (default "8"). See eth_led0 for
+                                legal values.
+
+        eth_max_speed           Set the maximum speed a link is allowed
+                                to negotiate. Legal values are 10, 100 and
+                                1000 (default set by the platform). Pi3B+,
+                                Pi4, Pi400, CM4, Pi5, Pi500, and CM5 only.
+
+        fan_temp0               Temperature threshold (in millicelcius) for
+                                1st cooling level (default 50000). Pi5 only.
+        fan_temp0_hyst          Temperature hysteresis (in millicelcius) for
+                                1st cooling level (default 5000). Pi5 only.
+        fan_temp0_speed         Fan PWM setting for 1st cooling level (0-255,
+                                default 75). Pi5 only.
+        fan_temp1               Temperature threshold (in millicelcius) for
+                                2nd cooling level (default 60000). Pi5 only.
+        fan_temp1_hyst          Temperature hysteresis (in millicelcius) for
+                                2nd cooling level (default 5000). Pi5 only.
+        fan_temp1_speed         Fan PWM setting for 2nd cooling level (0-255,
+                                default 125). Pi5 only.
+        fan_temp2               Temperature threshold (in millicelcius) for
+                                3rd cooling level (default 67500). Pi5 only.
+        fan_temp2_hyst          Temperature hysteresis (in millicelcius) for
+                                3rd cooling level (default 5000). Pi5 only.
+        fan_temp2_speed         Fan PWM setting for 3rd cooling level (0-255,
+                                default 175). Pi5 only.
+        fan_temp3               Temperature threshold (in millicelcius) for
+                                4th cooling level (default 75000). Pi5 only.
+        fan_temp3_hyst          Temperature hysteresis (in millicelcius) for
+                                4th cooling level (default 5000). Pi5 only.
+        fan_temp3_speed         Fan PWM setting for 4th cooling level (0-255,
+                                default 250). Pi5 only.
+
+        hdmi                    Set to "off" to disable the HDMI interface
+                                (default "on")
+
+        i2c                     An alias for i2c_arm
+
+        i2c_arm                 Set to "on" to enable the ARM's i2c interface
+                                (default "off")
+
+        i2c_arm_baudrate        Set the baudrate of the ARM's i2c interface
+                                (default "100000")
+
+        i2c_baudrate            An alias for i2c_arm_baudrate
+
+        i2c_csi_dsi             Set to "on" to enable the i2c_csi_dsi interface
+                                The I2C bus and GPIOs are platform specific:
+                                  B rev 1:
+                                    i2c-1 on 2 & 3
+                                  B rev 2, B+, CM, Zero, Zero W, 2B, CM2, CM3,
+                                  CM4S:
+                                    i2c-0 on 28 & 29
+                                  3B, 3B+, Zero 2W, 4B, 400, CM4:
+                                    i2c-0 on 44 & 45
+                                  5, 500:
+                                    i2c-11/i2c-4 on 40 & 41
+                                  CM5 on CM5IO:
+                                    i2c-0 on 0 & 1
+                                  CM5 on CM4IO:
+                                    i2c-10/i2c-6 on 38 & 39
+
+        i2c_csi_dsi0            Set to "on" to enable the i2c_csi_dsi0 interface
+                                The I2C bus and GPIOs are platform specific:
+                                  B rev 1 & 2, B+, CM, Zero, Zero W, 2B, CM2,
+                                  CM3, CM4S, 3B, 3B+, Zero 2W, 4B, 400, CM4,
+                                  CM5 on CM4IO:
+                                    i2c-0 on 0 & 1
+                                  5, 500, CM5 on CM5IO:
+                                    i2c-10/i2c-6 on 38 & 39
+
+        i2c_csi_dsi1            A Pi 5 family-specific alias for i2c_csi_dsi.
+
+        i2c_vc                  Set to "on" to enable the i2c interface
+                                usually reserved for the VideoCore processor
+                                (default "off")
+
+        i2c_vc_baudrate         Set the baudrate of the VideoCore i2c interface
+                                (default "100000")
+
+        i2s                     Set to "on" to enable the i2s interface
+                                (default "off")
+
+        i2s_dma4                Use to enable 40-bit DMA on the i2s interface
+                                (the assigned value doesn't matter)
+                                (2711 only)
+
+        krnbt                   Set to "off" to disable autoprobing of Bluetooth
+                                driver without need of hciattach/btattach
+                                (default "on")
+
+        krnbt_baudrate          Set the baudrate of the PL011 UART when used
+                                with krnbt=on
+
+        nvme                    Alias for "pciex1" (2712 only)
+
+        nvmem_cust_rw           Allow read/write access to customer otp
+
+        nvmem_mac_rw            Allow read/write access to mac addresses otp
+
+        nvmem_priv_rw           Allow read/write access to customer private otp
+
+        pcie                    Set to "off" to disable the PCIe interface
+                                (default "on")
+                                (2711 only, but not applicable on CM4S)
+                                N.B. USB-A ports on 4B are subsequently disabled
+
+        pcie_tperst_clk_ms      Add N milliseconds between PCIe reference clock
+                                activation and PERST# deassertion
+                                (CM4 and 2712, default "0")
+
+        pciex1                  Set to "on" to enable the external PCIe link
+                                (2712 only, default "off")
+
+        pciex1_gen              Sets the PCIe "GEN"/speed for the external PCIe
+                                link (2712 only, default "2")
+
+        pciex1_no_l0s           Set to "on" to disable ASPM L0s on the external
+                                PCIe link for devices that have broken
+                                implementations (2712 only, default "off")
+
+        pciex1_tperst_clk_ms    Alias for pcie_tperst_clk_ms
+                                (2712 only, default "0")
+
+        pwr_led_trigger
+        pwr_led_activelow
+        pwr_led_gpio
+                                As for act_led_*, but using the PWR LED.
+                                Not available on Model A/B boards.
+
+        random                  Set to "on" to enable the hardware random
+                                number generator (default "on")
+
+        rtc                     Set to "off" to disable the onboard Real Time
+                                Clock (2712 only, default "on")
+
+        rtc_bbat_vchg           Set the RTC backup battery charging voltage in
+                                microvolts. If set to 0 or not specified, the
+                                trickle charger is disabled.
+                                (2712 only, default "0")
+
+        sd                      Set to "off" to disable the SD card (or eMMC on
+                                non-lite SKU of CM4/5).
+                                (default "on")
+
+        sd_cqe                  Modify Command Queuing behaviour on the main SD
+                                interface. Legal values are:
+                                0: disable CQ
+                                1: allow CQ for known-good SD A2 cards, and all
+                                   eMMC cards
+                                2: allow CQ for all SD A2 cards that aren't
+                                   known-bad, and all eMMC cards.
+                                (2712 only, default "1")
+
+        sd_overclock            Clock (in MHz) to use when the MMC framework
+                                requests 50MHz
+
+        sd_poll_once            Looks for a card once after booting. Useful
+                                for network booting scenarios to avoid the
+                                overhead of continuous polling. N.B. Using
+                                this option restricts the system to using a
+                                single card per boot (or none at all).
+                                (default off)
+
+        sd_force_pio            Disable DMA support for SD driver (default off)
+
+        sd_pio_limit            Number of blocks above which to use DMA for
+                                SD card (default 1)
+
+        sd_debug                Enable debug output from SD driver (default off)
+
+        sdio_overclock          Clock (in MHz) to use when the MMC framework
+                                requests 50MHz for the SDIO/WLAN interface.
+
+        spi                     Set to "on" to enable the spi interfaces
+                                (default "off")
+
+        spi_dma4                Use to enable 40-bit DMA on spi interfaces
+                                (the assigned value doesn't matter)
+                                (2711 only)
+
+        strict_gpiod            Return GPIOs to inputs when they are released.
+                                If using the gpiod utilities, it is necessary
+                                to keep a gpioset running (e.g. with
+                                --mode=wait) in order for an output value to
+                                persist.
+
+        suspend                 Make the power button trigger a suspend rather
+                                than a power-off (2712 only, default "off")
+
+        tx_lpi_timer            Set the delay in microseconds between going idle
+                                and entering the low power state (default 600).
+                                Requires EEE to be enabled - see "eee".
+
+        uart0                   Set to "off" to disable uart0 (default "on")
+
+        uart0_console           Move the kernel boot console to UART0 on pins
+                                6, 8 and 10 of the 40-way header (2712 only,
+                                default "off")
+
+        uart0_dma               Enable DMA usage on UART0 (2712 only, default
+                                "off")
+
+        uart1                   Set to "on" or "off" to enable or disable uart1
+                                (default varies)
+
+        watchdog                Set to "off" to disable the hardware watchdog
+                                (default "on")
+
+        wifiaddr                Set an alternative WiFi MAC address.
+                                The value should be a 6-byte hexadecimal value,
+                                with or without colon separators, written in the
+                                natural (big-endian) order.
+
+        N.B. It is recommended to only enable those interfaces that are needed.
+        Leaving all interfaces enabled can lead to unwanted behaviour (i2c_vc
+        interfering with Pi Camera, I2S and SPI hogging GPIO pins, etc.)
+        Note also that i2c, i2c_arm and i2c_vc are aliases for the physical
+        interfaces i2c0 and i2c1. Use of the numeric variants is still possible
+        but deprecated because the ARM/VC assignments differ between board
+        revisions. The same board-specific mapping applies to i2c_baudrate,
+        and the other i2c baudrate parameters.
+
+
+Name:   act-led
+Info:   Pi 3B, 3B+, 3A+ and 4B use a GPIO expander to drive the LEDs which can
+        only be accessed from the VPU. There is a special driver for this with a
+        separate DT node, which has the unfortunate consequence of breaking the
+        act_led_gpio and act_led_activelow dtparams.
+        This overlay changes the GPIO controller back to the standard one and
+        restores the dtparams.
+Load:   dtoverlay=act-led,<param>=<val>
+Params: activelow               Set to "on" to invert the sense of the LED
+                                (default "off")
+
+        gpio                    Set which GPIO to use for the activity LED
+                                (in case you want to connect it to an external
+                                device)
+                                REQUIRED
+
+
+Name:   adafruit-st7735r
+Info:   Overlay for the SPI-connected Adafruit 1.8" 160x128 or 128x128 displays,
+        based on the ST7735R chip.
+        This overlay uses the newer DRM/KMS "Tiny" driver.
+Load:   dtoverlay=adafruit-st7735r,<param>=<val>
+Params: 128x128                 Select the 128x128 driver (default 160x128)
+        rotate                  Display rotation {0,90,180,270} (default 90)
+        speed                   SPI bus speed in Hz (default 4000000)
+        dc_pin                  GPIO pin for D/C (default 24)
+        reset_pin               GPIO pin for RESET (default 25)
+        led_pin                 GPIO used to control backlight (default 18)
+
+
+Name:   adafruit18
+Info:   Overlay for the SPI-connected Adafruit 1.8" display (based on the
+        ST7735R chip). It includes support for the "green tab" version.
+        This overlay uses the older fbtft driver.
+Load:   dtoverlay=adafruit18,<param>=<val>
+Params: green                   Use the adafruit18_green variant.
+        rotate                  Display rotation {0,90,180,270}
+        speed                   SPI bus speed in Hz (default 4000000)
+        fps                     Display frame rate in Hz
+        bgr                     Enable BGR mode (default off)
+        debug                   Debug output level {0-7}
+        dc_pin                  GPIO pin for D/C (default 24)
+        reset_pin               GPIO pin for RESET (default 25)
+        led_pin                 GPIO used to control backlight (default 18)
+
+
+Name:   adau1977-adc
+Info:   Overlay for activation of ADAU1977 ADC codec over I2C for control
+        and I2S for data.
+Load:   dtoverlay=adau1977-adc
+Params: <None>
+
+
+Name:   adau7002-simple
+Info:   Overlay for the activation of ADAU7002 stereo PDM to I2S converter.
+Load:   dtoverlay=adau7002-simple,<param>=<val>
+Params: card-name               Override the default, "adau7002", card name.
+
+
+Name:   ads1015
+Info:   Overlay for activation of Texas Instruments ADS1015 ADC over I2C
+Load:   dtoverlay=ads1015,<param>=<val>
+Params: addr                    I2C bus address of device. Set based on how the
+                                addr pin is wired. (default=0x48 assumes addr
+                                is pulled to GND)
+        cha_enable              Enable virtual channel a. (default=true)
+        cha_cfg                 Set the configuration for virtual channel a.
+                                (default=4 configures this channel for the
+                                voltage at A0 with respect to GND)
+        cha_datarate            Set the datarate (samples/sec) for this channel.
+                                (default=4 sets 1600 sps)
+        cha_gain                Set the gain of the Programmable Gain
+                                Amplifier for this channel. (default=2 sets the
+                                full scale of the channel to 2.048 Volts)
+
+        Channel (ch) parameters can be set for each enabled channel.
+        A maximum of 4 channels can be enabled (letters a thru d).
+        For more information refer to the device datasheet at:
+        http://www.ti.com/lit/ds/symlink/ads1015.pdf
+
+
+Name:   ads1115
+Info:   Texas Instruments ADS1115 ADC
+Load:   dtoverlay=ads1115,<param>[=<val>]
+Params: addr                    I2C bus address of device. Set based on how the
+                                addr pin is wired. (default=0x48 assumes addr
+                                is pulled to GND)
+        i2c-bus                 Supports all the standard I2C bus selection
+                                parameters - see "dtoverlay -h i2c-bus"
+        cha_enable              Enable virtual channel a.
+        cha_cfg                 Set the configuration for virtual channel a.
+                                (default=4 configures this channel for the
+                                voltage at A0 with respect to GND)
+        cha_datarate            Set the datarate (samples/sec) for this channel.
+                                (default=7 sets 860 sps)
+        cha_gain                Set the gain of the Programmable Gain
+                                Amplifier for this channel. (Default 1 sets the
+                                full scale of the channel to 4.096 Volts)
+
+        Channel parameters can be set for each enabled channel.
+        A maximum of 4 channels can be enabled (letters a thru d).
+        For more information refer to the device datasheet at:
+        http://www.ti.com/lit/ds/symlink/ads1115.pdf
+
+
+Name:   ads7846
+Info:   ADS7846 Touch controller
+Load:   dtoverlay=ads7846,<param>=<val>
+Params: cs                      SPI bus Chip Select (default 1)
+        speed                   SPI bus speed (default 2MHz, max 3.25MHz)
+        penirq                  GPIO used for PENIRQ. REQUIRED
+        penirq_pull             Set GPIO pull (default 0=none, 2=pullup)
+        swapxy                  Swap x and y axis
+        xmin                    Minimum value on the X axis (default 0)
+        ymin                    Minimum value on the Y axis (default 0)
+        xmax                    Maximum value on the X axis (default 4095)
+        ymax                    Maximum value on the Y axis (default 4095)
+        pmin                    Minimum reported pressure value (default 0)
+        pmax                    Maximum reported pressure value (default 65535)
+        xohms                   Touchpanel sensitivity (X-plate resistance)
+                                (default 400)
+        invx                    Invert x axis
+        invy                    Invert y axis
+
+        penirq is required and usually xohms (60-100) has to be set as well.
+        Apart from that, pmax (255) and swapxy are also common.
+        The rest of the calibration can be done with xinput-calibrator.
+        See: github.com/notro/fbtft/wiki/FBTFT-on-Raspian
+        Device Tree binding document:
+        www.kernel.org/doc/Documentation/devicetree/bindings/input/ads7846.txt
+
+
+Name:   adv7282m
+Info:   Analog Devices ADV7282M analogue video to CSI2 bridge.
+        Uses Unicam1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=adv7282m,<param>=<val>
+Params: addr                    Overrides the I2C address (default 0x21)
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default off)
+
+
+Name:   adv728x-m
+Info:   Analog Devices ADV728[0|1|2]-M analogue video to CSI2 bridges.
+        This is a wrapper for adv7282m, and defaults to ADV7282M.
+Load:   dtoverlay=adv728x-m,<param>=<val>
+Params: addr                    Overrides the I2C address (default 0x21)
+        adv7280m                Select ADV7280-M.
+        adv7281m                Select ADV7281-M.
+        adv7281ma               Select ADV7281-MA.
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default off)
+
+
+Name:   akkordion-iqdacplus
+Info:   Configures the Digital Dreamtime Akkordion Music Player (based on the
+        OEM IQAudIO DAC+ or DAC Zero module).
+Load:   dtoverlay=akkordion-iqdacplus,<param>=<val>
+Params: 24db_digital_gain       Allow gain to be applied via the PCM512x codec
+                                Digital volume control. Enable with
+                                dtoverlay=akkordion-iqdacplus,24db_digital_gain
+                                (The default behaviour is that the Digital
+                                volume control is limited to a maximum of
+                                0dB. ie. it can attenuate but not provide
+                                gain. For most users, this will be desired
+                                as it will prevent clipping. By appending
+                                the 24db_digital_gain parameter, the Digital
+                                volume control will allow up to 24dB of
+                                gain. If this parameter is enabled, it is the
+                                responsibility of the user to ensure that
+                                the Digital volume control is set to a value
+                                that does not result in clipping/distortion!)
+
+
+Name:   allo-boss-dac-pcm512x-audio
+Info:   Configures the Allo Boss DAC audio cards.
+Load:   dtoverlay=allo-boss-dac-pcm512x-audio,<param>
+Params: 24db_digital_gain       Allow gain to be applied via the PCM512x codec
+                                Digital volume control. Enable with
+                                "dtoverlay=allo-boss-dac-pcm512x-audio,
+                                24db_digital_gain"
+                                (The default behaviour is that the Digital
+                                volume control is limited to a maximum of
+                                0dB. ie. it can attenuate but not provide
+                                gain. For most users, this will be desired
+                                as it will prevent clipping. By appending
+                                the 24db_digital_gain parameter, the Digital
+                                volume control will allow up to 24dB of
+                                gain. If this parameter is enabled, it is the
+                                responsibility of the user to ensure that
+                                the Digital volume control is set to a value
+                                that does not result in clipping/distortion!)
+        slave                   Force Boss DAC into slave mode, using Pi a
+                                master for bit clock and frame clock. Enable
+                                with "dtoverlay=allo-boss-dac-pcm512x-audio,
+                                slave"
+
+
+Name:   allo-boss2-dac-audio
+Info:   Configures the Allo Boss2 DAC audio card
+Load:   dtoverlay=allo-boss2-dac-audio
+Params: <None>
+
+
+Name:   allo-digione
+Info:   Configures the Allo Digione audio card
+Load:   dtoverlay=allo-digione
+Params: <None>
+
+
+Name:   allo-katana-dac-audio
+Info:   Configures the Allo Katana DAC audio card
+Load:   dtoverlay=allo-katana-dac-audio
+Params: <None>
+
+
+Name:   allo-piano-dac-pcm512x-audio
+Info:   Configures the Allo Piano DAC (2.0/2.1) audio cards.
+        (NB. This initial support is for 2.0 channel audio ONLY! ie. stereo.
+        The subwoofer outputs on the Piano 2.1 are not currently supported!)
+Load:   dtoverlay=allo-piano-dac-pcm512x-audio,<param>
+Params: 24db_digital_gain       Allow gain to be applied via the PCM512x codec
+                                Digital volume control.
+                                (The default behaviour is that the Digital
+                                volume control is limited to a maximum of
+                                0dB. ie. it can attenuate but not provide
+                                gain. For most users, this will be desired
+                                as it will prevent clipping. By appending
+                                the 24db_digital_gain parameter, the Digital
+                                volume control will allow up to 24dB of
+                                gain. If this parameter is enabled, it is the
+                                responsibility of the user to ensure that
+                                the Digital volume control is set to a value
+                                that does not result in clipping/distortion!)
+
+
+Name:   allo-piano-dac-plus-pcm512x-audio
+Info:   Configures the Allo Piano DAC (2.1) audio cards.
+Load:   dtoverlay=allo-piano-dac-plus-pcm512x-audio,<param>
+Params: 24db_digital_gain       Allow gain to be applied via the PCM512x codec
+                                Digital volume control.
+                                (The default behaviour is that the Digital
+                                volume control is limited to a maximum of
+                                0dB. ie. it can attenuate but not provide
+                                gain. For most users, this will be desired
+                                as it will prevent clipping. By appending
+                                the 24db_digital_gain parameter, the Digital
+                                volume control will allow up to 24dB of
+                                gain. If this parameter is enabled, it is the
+                                responsibility of the user to ensure that
+                                the Digital volume control is set to a value
+                                that does not result in clipping/distortion!)
+        glb_mclk                This option is only with Kali board. If enabled,
+                                MCLK for Kali is used and PLL is disabled for
+                                better voice quality. (default Off)
+
+
+Name:   anyspi
+Info:   Universal device tree overlay for SPI devices
+
+        Just specify the SPI address and device name ("compatible" property).
+        This overlay lacks any device-specific parameter support!
+
+        For devices on spi1 or spi2, the interfaces should be enabled
+        with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays.
+
+        Examples:
+        1. SPI NOR flash on spi0.1, maximum SPI clock frequency 45MHz:
+            dtoverlay=anyspi:spi0-1,dev="jedec,spi-nor",speed=45000000
+        2. MCP3204 ADC on spi1.2, maximum SPI clock frequency 500kHz:
+            dtoverlay=anyspi:spi1-2,dev="microchip,mcp3204"
+Load:   dtoverlay=anyspi,<param>=<val>
+Params: spi<n>-<m>              Configure device at spi<n>, cs<m>
+                                (boolean, required)
+        dev                     Set device name to search compatible module
+                                (string, required)
+        speed                   Set SPI clock frequency in Hz
+                                (integer, optional, default 500000)
+
+
+Name:   apds9960
+Info:   Configures the AVAGO APDS9960 digital proximity, ambient light, RGB and
+        gesture sensor
+Load:   dtoverlay=apds9960,<param>=<val>
+Params: gpiopin                 GPIO used for INT (default 4)
+        noints                  Disable the interrupt GPIO line.
+
+
+Name:   applepi-dac
+Info:   Configures the Orchard Audio ApplePi-DAC audio card
+Load:   dtoverlay=applepi-dac
+Params: <None>
+
+
+Name:   arducam-64mp
+Info:   Arducam 64MP camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=arducam-64mp,<param>=<val>
+Params: rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 0)
+        orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default on)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+        vcm                     Select lens driver state. Default is enabled,
+                                but vcm=off will disable.
+
+
+Name:   arducam-pivariety
+Info:   Arducam Pivariety camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=arducam-pivariety,<param>=<val>
+Params: rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 0)
+        orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default on)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+
+
+Name:   at86rf233
+Info:   Configures the Atmel AT86RF233 802.15.4 low-power WPAN transceiver,
+        connected to spi0.0
+Load:   dtoverlay=at86rf233,<param>=<val>
+Params: interrupt               GPIO used for INT (default 23)
+        reset                   GPIO used for Reset (default 24)
+        sleep                   GPIO used for Sleep (default 25)
+        speed                   SPI bus speed in Hz (default 3000000)
+        trim                    Fine tuning of the internal capacitance
+                                arrays (0=+0pF, 15=+4.5pF, default 15)
+
+
+Name:   audioinjector-addons
+Info:   Configures the audioinjector.net audio add on soundcards
+Load:   dtoverlay=audioinjector-addons,<param>=<val>
+Params: non-stop-clocks         Keeps the clocks running even when the stream
+                                is paused or stopped (default off)
+
+
+Name:   audioinjector-bare-i2s
+Info:   Configures the audioinjector.net audio bare i2s soundcard
+Load:   dtoverlay=audioinjector-bare-i2s
+Params: <None>
+
+
+Name:   audioinjector-isolated-soundcard
+Info:   Configures the audioinjector.net isolated soundcard
+Load:   dtoverlay=audioinjector-isolated-soundcard
+Params: <None>
+
+
+Name:   audioinjector-ultra
+Info:   Configures the audioinjector.net ultra soundcard
+Load:   dtoverlay=audioinjector-ultra
+Params: <None>
+
+
+Name:   audioinjector-wm8731-audio
+Info:   Configures the audioinjector.net audio add on soundcard
+Load:   dtoverlay=audioinjector-wm8731-audio
+Params: <None>
+
+
+Name:   audiosense-pi
+Info:   Configures the audiosense-pi add on soundcard
+        For more information refer to
+        https://gitlab.com/kakar0t/audiosense-pi
+Load:   dtoverlay=audiosense-pi
+Params: <None>
+
+
+Name:   audremap
+Info:   Switches PWM sound output to GPIOs on the 40-pin header
+Load:   dtoverlay=audremap,<param>=<val>
+Params: swap_lr                 Reverse the channel allocation, which will also
+                                swap the audio jack outputs (default off)
+        enable_jack             Don't switch off the audio jack output. Does
+                                nothing on BCM2711 (default off)
+        pins_12_13              Select GPIOs 12 & 13 (default)
+        pins_18_19              Select GPIOs 18 & 19
+        pins_40_41              Select GPIOs 40 & 41 (not available on CM4, used
+                                for other purposes)
+        pins_40_45              Select GPIOs 40 & 45 (don't use on BCM2711 - the
+                                pins are on different controllers)
+        pins_none               Effectively disable audio, useful if the pins
+                                are needed for something else.
+
+
+Name:   audremap-pi5
+Info:   On Raspberry Pi 5 / 500 /CM5, enable digital audio output
+        and route it to GPIOs 12 & 13 on the 40-pin header
+Load:   dtoverlay=audremap-pi5,<param>=<val>
+Params: swap_lr                 Reverse the channel allocation (default off)
+        pins_12_13              Select GPIOs 12 & 13 (default)
+        pins_18_19              Not available; this will not enable audio out
+        pins_40_41              Not available; this will not enable audio out
+        pins_40_45              Not available; this will not enable audio out
+
+
+Name:   balena-fin
+Info:   Overlay that enables WLAN, Bluetooth and the GPIO expander on the
+        balenaFin carrier board for the Raspberry Pi Compute Module 3/3+ Lite.
+Load:   dtoverlay=balena-fin
+Params: <None>
+
+
+Name:   bcm2712d0
+Info:   Overlay encapsulating the BCM2712 C0->D0 differences
+Load:   dtoverlay=bcm2712d0
+Params: <None>
+
+
+Name:   bmp085_i2c-sensor
+Info:   This overlay is now deprecated - see i2c-sensor
+Load:   <Deprecated>
+
+
+Name:   camera-mux-2port
+Info:   Configures a 2 port camera multiplexer
+        Note that currently ALL IMX290 modules share a common clock, therefore
+        all modules will need to have the same clock frequency.
+Load:   dtoverlay=camera-mux-2port,<param>=<val>
+Params: cam0-arducam-64mp       Select Arducam64MP for camera on port 0
+        cam0-imx219             Select IMX219 for camera on port 0
+        cam0-imx258             Select IMX258 for camera on port 0
+        cam0-imx290             Select IMX290 for camera on port 0
+        cam0-imx477             Select IMX477 for camera on port 0
+        cam0-imx519             Select IMX519 for camera on port 0
+        cam0-imx708             Select IMX708 for camera on port 0
+        cam0-ov2311             Select OV2311 for camera on port 0
+        cam0-ov5647             Select OV5647 for camera on port 0
+        cam0-ov64a40            Select OV64A40 for camera on port 0
+        cam0-ov7251             Select OV7251 for camera on port 0
+        cam0-ov9281             Select OV9281 for camera on port 0
+        cam0-imx290-clk-freq    Set clock frequency for an IMX290 on port 0
+        cam1-arducam-64mp       Select Arducam64MP for camera on port 1
+        cam1-imx219             Select IMX219 for camera on port 1
+        cam1-imx258             Select IMX258 for camera on port 1
+        cam1-imx290             Select IMX290 for camera on port 1
+        cam1-imx477             Select IMX477 for camera on port 1
+        cam1-imx519             Select IMX519 for camera on port 1
+        cam1-imx708             Select IMX708 for camera on port 1
+        cam1-ov2311             Select OV2311 for camera on port 1
+        cam1-ov5647             Select OV5647 for camera on port 1
+        cam1-ov64a40            Select OV64A40 for camera on port 1
+        cam1-ov7251             Select OV7251 for camera on port 1
+        cam1-ov9281             Select OV9281 for camera on port 1
+        cam1-imx290-clk-freq    Set clock frequency for an IMX290 on port 1
+        cam0-sync-source        Set camera on port 0 as vsync source
+        cam0-sync-sink          Set camera on port 0 as vsync sink
+        cam1-sync-source        Set camera on port 1 as vsync source
+        cam1-sync-sink          Set camera on port 1 as vsync sink
+
+        cam0                    Connect the mux to CAM0 port (default is CAM1)
+
+
+Name:   camera-mux-4port
+Info:   Configures a 4 port camera multiplexer
+        Note that currently ALL IMX290 modules share a common clock, therefore
+        all modules will need to have the same clock frequency.
+Load:   dtoverlay=camera-mux-4port,<param>=<val>
+Params: cam0-arducam-64mp       Select Arducam64MP for camera on port 0
+        cam0-imx219             Select IMX219 for camera on port 0
+        cam0-imx258             Select IMX258 for camera on port 0
+        cam0-imx290             Select IMX290 for camera on port 0
+        cam0-imx477             Select IMX477 for camera on port 0
+        cam0-imx519             Select IMX519 for camera on port 0
+        cam0-imx708             Select IMX708 for camera on port 0
+        cam0-ov2311             Select OV2311 for camera on port 0
+        cam0-ov5647             Select OV5647 for camera on port 0
+        cam0-ov64a40            Select OV64A40 for camera on port 0
+        cam0-ov7251             Select OV7251 for camera on port 0
+        cam0-ov9281             Select OV9281 for camera on port 0
+        cam0-imx290-clk-freq    Set clock frequency for an IMX290 on port 0
+        cam1-arducam-64mp       Select Arducam64MP for camera on port 1
+        cam1-imx219             Select IMX219 for camera on port 1
+        cam1-imx258             Select IMX258 for camera on port 1
+        cam1-imx290             Select IMX290 for camera on port 1
+        cam1-imx477             Select IMX477 for camera on port 1
+        cam1-imx519             Select IMX519 for camera on port 1
+        cam1-imx708             Select IMX708 for camera on port 1
+        cam1-ov2311             Select OV2311 for camera on port 1
+        cam1-ov5647             Select OV5647 for camera on port 1
+        cam1-ov64a40            Select OV64A40 for camera on port 1
+        cam1-ov7251             Select OV7251 for camera on port 1
+        cam1-ov9281             Select OV9281 for camera on port 1
+        cam1-imx290-clk-freq    Set clock frequency for an IMX290 on port 1
+        cam2-arducam-64mp       Select Arducam64MP for camera on port 2
+        cam2-imx219             Select IMX219 for camera on port 2
+        cam2-imx258             Select IMX258 for camera on port 2
+        cam2-imx290             Select IMX290 for camera on port 2
+        cam2-imx477             Select IMX477 for camera on port 2
+        cam2-imx519             Select IMX519 for camera on port 2
+        cam2-imx708             Select IMX708 for camera on port 2
+        cam2-ov2311             Select OV2311 for camera on port 2
+        cam2-ov5647             Select OV5647 for camera on port 2
+        cam2-ov64a40            Select OV64A40 for camera on port 2
+        cam2-ov7251             Select OV7251 for camera on port 2
+        cam2-ov9281             Select OV9281 for camera on port 2
+        cam2-imx290-clk-freq    Set clock frequency for an IMX290 on port 2
+        cam3-arducam-64mp       Select Arducam64MP for camera on port 3
+        cam3-imx219             Select IMX219 for camera on port 3
+        cam3-imx258             Select IMX258 for camera on port 3
+        cam3-imx290             Select IMX290 for camera on port 3
+        cam3-imx477             Select IMX477 for camera on port 3
+        cam3-imx519             Select IMX519 for camera on port 3
+        cam3-imx708             Select IMX708 for camera on port 3
+        cam3-ov2311             Select OV2311 for camera on port 3
+        cam3-ov5647             Select OV5647 for camera on port 3
+        cam3-ov64a40            Select OV64A40 for camera on port 3
+        cam3-ov7251             Select OV7251 for camera on port 3
+        cam3-ov9281             Select OV9281 for camera on port 3
+        cam3-imx290-clk-freq    Set clock frequency for an IMX290 on port 3
+        cam0-sync-source        Set camera on port 0 as vsync source
+        cam0-sync-sink          Set camera on port 0 as vsync sink
+        cam1-sync-source        Set camera on port 1 as vsync source
+        cam1-sync-sink          Set camera on port 1 as vsync sink
+        cam2-sync-source        Set camera on port 2 as vsync source
+        cam2-sync-sink          Set camera on port 2 as vsync sink
+        cam3-sync-source        Set camera on port 3 as vsync source
+        cam3-sync-sink          Set camera on port 3 as vsync sink
+
+        cam0                    Connect the mux to CAM0 port (default is CAM1)
+
+
+Name:   cap1106
+Info:   Enables the ability to use the cap1106 touch sensor as a keyboard
+Load:   dtoverlay=cap1106,<param>=<val>
+Params: int_pin                 GPIO pin for interrupt signal (default 23)
+
+
+Name:   chipcap2
+Info:   Enables the Chipcap 2 humidity and temperature sensor. This device
+        provides low and high humidity alarms and requires a 3V3 dedicated
+        regulator to operate.
+Load:   dtoverlay=chipcap2,<param>=<val>
+Params: ready_pin               GPIO pin for ready signal (default 4)
+
+        low_pin                 GPIO pin for low humidity signal (default 5)
+
+        high_pin                GPIO pin for high humidity signal (default 6)
+
+        reg_pin                 GPIO pin to control the dedicated regulator
+                                that powers the device (default 26)
+
+
+Name:   chipdip-dac
+Info:   Configures Chip Dip audio cards.
+Load:   dtoverlay=chipdip-dac
+Params: <None>
+
+
+Name:   cirrus-wm5102
+Info:   Configures the Cirrus Logic Audio Card
+Load:   dtoverlay=cirrus-wm5102
+Params: <None>
+
+
+Name:   cm-swap-i2c0
+Info:   Largely for Compute Modules 1&3 where the original instructions for
+        adding a camera used GPIOs 0&1 for CAM1 and 28&29 for CAM0, whilst all
+        other platforms use 28&29 (or 44&45) for CAM1.
+        The default assignment through using this overlay is for
+        i2c0 to use 28&29, and i2c10 (aka i2c_csi_dsi) to use 28&29, but the
+        overrides allow this to be changed.
+Load:   dtoverlay=cm-swap-i2c0,<param>=<val>
+Params: i2c0-gpio0              Use GPIOs 0&1 for i2c0
+        i2c0-gpio28             Use GPIOs 28&29 for i2c0 (default)
+        i2c0-gpio44             Use GPIOs 44&45 for i2c0
+        i2c10-gpio0             Use GPIOs 0&1 for i2c0 (default)
+        i2c10-gpio28            Use GPIOs 28&29 for i2c0
+        i2c10-gpio44            Use GPIOs 44&45 for i2c0
+
+
+Name:   cma
+Info:   Set custom CMA sizes, only use if you know what you are doing, might
+        clash with other overlays like vc4-fkms-v3d and vc4-kms-v3d.
+Load:   dtoverlay=cma,<param>=<val>
+Params: cma-512                 CMA is 512MB (needs 1GB)
+        cma-448                 CMA is 448MB (needs 1GB)
+        cma-384                 CMA is 384MB (needs 1GB)
+        cma-320                 CMA is 320MB (needs 1GB)
+        cma-256                 CMA is 256MB (needs 1GB)
+        cma-192                 CMA is 192MB (needs 1GB)
+        cma-128                 CMA is 128MB
+        cma-96                  CMA is 96MB
+        cma-64                  CMA is 64MB
+        cma-size                CMA size in bytes, 4MB aligned
+        cma-default             Use upstream's default value
+
+
+Name:   crystalfontz-cfa050_pi_m
+Info:   Configures the Crystalfontz CFA050-PI-M series of Raspberry Pi CM4
+        and CM5 based modules using the CFA7201280A0_050Tx 7" TFT LCD displays,
+        with or without capacitive touch screen.
+        Requires use of vc4-kms-v3d.
+Load:   dtoverlay=crystalfontz-cfa050_pi_m,<param>=<val>
+Params: captouch                Enable capacitive touch display
+        cm5                     Enable support for the Raspberry Pi CM5
+
+
+Name:   cutiepi-panel
+Info:   8" TFT LCD display and touch panel used by cutiepi.io
+Load:   dtoverlay=cutiepi-panel
+Params: <None>
+
+
+Name:   dacberry400
+Info:   Configures the dacberry400 add on soundcard
+Load:   dtoverlay=dacberry400
+Params: <None>
+
+
+Name:   dht11
+Info:   Overlay for the DHT11/DHT21/DHT22 humidity/temperature sensors
+        Also sometimes found with the part number(s) AM230x.
+Load:   dtoverlay=dht11,<param>=<val>
+Params: gpiopin                 GPIO connected to the sensor's DATA output.
+                                (default 4)
+
+
+Name:   dionaudio-kiwi
+Info:   Configures the Dion Audio KIWI STREAMER
+Load:   dtoverlay=dionaudio-kiwi
+Params: <None>
+
+
+Name:   dionaudio-loco
+Info:   Configures the Dion Audio LOCO DAC-AMP
+Load:   dtoverlay=dionaudio-loco
+Params: <None>
+
+
+Name:   dionaudio-loco-v2
+Info:   Configures the Dion Audio LOCO-V2 DAC-AMP
+Load:   dtoverlay=dionaudio-loco-v2,<param>=<val>
+Params: 24db_digital_gain       Allow gain to be applied via the PCM512x codec
+                                Digital volume control. Enable with
+                                "dtoverlay=hifiberry-dacplus,24db_digital_gain"
+                                (The default behaviour is that the Digital
+                                volume control is limited to a maximum of
+                                0dB. ie. it can attenuate but not provide
+                                gain. For most users, this will be desired
+                                as it will prevent clipping. By appending
+                                the 24dB_digital_gain parameter, the Digital
+                                volume control will allow up to 24dB of
+                                gain. If this parameter is enabled, it is the
+                                responsibility of the user to ensure that
+                                the Digital volume control is set to a value
+                                that does not result in clipping/distortion!)
+
+
+Name:   disable-bt
+Info:   Disable onboard Bluetooth on Bluetooth-capable Raspberry Pis. On Pis
+        prior to Pi 5 this restores UART0/ttyAMA0 over GPIOs 14 & 15.
+Load:   dtoverlay=disable-bt
+Params: <None>
+
+
+Name:   disable-bt-pi5
+Info:   See disable-bt
+
+
+Name:   disable-emmc2
+Info:   Disable EMMC2 controller on BCM2711.
+        The allows the onboard EMMC storage on Compute Module 4 to be disabled
+        e.g. if a fault has occurred.
+Load:   dtoverlay=disable-emmc2
+Params: <None>
+
+
+Name:   disable-wifi
+Info:   Disable onboard WLAN on WiFi-capable Raspberry Pis.
+Load:   dtoverlay=disable-wifi
+Params: <None>
+
+
+Name:   disable-wifi-pi5
+Info:   See disable-wifi
+
+
+Name:   dpi18
+Info:   Overlay for a generic 18-bit DPI display
+        This uses GPIOs 0-21 (so no I2C, uart etc.), and activates the output
+        2-3 seconds after the kernel has started.
+Load:   dtoverlay=dpi18
+Params: <None>
+
+
+Name:   dpi18cpadhi
+Info:   Overlay for a generic 18-bit DPI display (in 'mode 6' connection scheme)
+        This uses GPIOs 0-9,12-17,20-25 (so no I2C, uart etc.), and activates
+        the output 3-3 seconds after the kernel has started.
+Load:   dtoverlay=dpi18cpadhi
+Params: <None>
+
+
+Name:   dpi24
+Info:   Overlay for a generic 24-bit DPI display
+        This uses GPIOs 0-27 (so no I2C, uart etc.), and activates the output
+        2-3 seconds after the kernel has started.
+Load:   dtoverlay=dpi24
+Params: <None>
+
+
+Name:   draws
+Info:   Configures the NW Digital Radio DRAWS Hat
+
+        The board includes an ADC to measure various board values and also
+        provides two analog user inputs on the expansion header.  The ADC
+        can be configured for various sample rates and gain values to adjust
+        the input range.  Tables describing the two parameters follow.
+
+        ADC Gain Values:
+            0 = +/- 6.144V
+            1 = +/- 4.096V
+            2 = +/- 2.048V
+            3 = +/- 1.024V
+            4 = +/- 0.512V
+            5 = +/- 0.256V
+            6 = +/- 0.256V
+            7 = +/- 0.256V
+
+        ADC Datarate Values:
+            0 = 128sps
+            1 = 250sps
+            2 = 490sps
+            3 = 920sps
+            4 = 1600sps (default)
+            5 = 2400sps
+            6 = 3300sps
+            7 = 3300sps
+Load:   dtoverlay=draws,<param>=<val>
+Params: draws_adc_ch4_gain      Sets the full scale resolution of the ADCs
+                                input voltage sensor (default 1)
+
+        draws_adc_ch4_datarate  Sets the datarate of the ADCs input voltage
+                                sensor
+
+        draws_adc_ch5_gain      Sets the full scale resolution of the ADCs
+                                5V rail voltage sensor (default 1)
+
+        draws_adc_ch5_datarate  Sets the datarate of the ADCs 4V rail voltage
+                                sensor
+
+        draws_adc_ch6_gain      Sets the full scale resolution of the ADCs
+                                AIN2 input (default 2)
+
+        draws_adc_ch6_datarate  Sets the datarate of the ADCs AIN2 input
+
+        draws_adc_ch7_gain      Sets the full scale resolution of the ADCs
+                                AIN3 input (default 2)
+
+        draws_adc_ch7_datarate  Sets the datarate of the ADCs AIN3 input
+
+        alsaname                Name of the ALSA audio device (default "draws")
+
+
+Name:   dwc-otg
+Info:   The dwc-otg has been deprecated.
+Load:   <Deprecated>
+
+
+Name:   dwc-otg-deprecated
+Info:   The dwc-otg has been deprecated.
+Load:   <Deprecated>
+
+
+Name:   dwc2
+Info:   Selects the dwc2 USB controller driver
+Load:   dtoverlay=dwc2,<param>=<val>
+Params: dr_mode                 Dual role mode: "host", "peripheral" or "otg"
+
+        g-rx-fifo-size          Size of rx fifo size in gadget mode
+
+        g-np-tx-fifo-size       Size of non-periodic tx fifo size in gadget
+                                mode
+
+
+[ The ds1307-rtc overlay has been deleted. See i2c-rtc. ]
+
+
+Name:   edt-ft5406
+Info:   Overlay for the EDT FT5406 touchscreen.
+        This works with the Raspberry Pi 7" touchscreen when not being polled
+        by the firmware.
+        By default the overlay uses the i2c_csi_dsi I2C interface, but this
+        can be overridden
+        You MUST use either "disable_touchscreen=1" or "ignore_lcd=1" in
+        config.txt to stop the firmware polling the touchscreen.
+Load:   dtoverlay=edt-ft5406,<param>=<val>
+Params: sizex                   Touchscreen size x (default 800)
+        sizey                   Touchscreen size y (default 480)
+        invx                    Touchscreen inverted x axis
+        invy                    Touchscreen inverted y axis
+        swapxy                  Touchscreen swapped x y axis
+        addr                    Sets the address for the touch controller. Note
+                                that the device must be configured to use the
+                                specified address.
+        i2c-bus                 Supports all the standard I2C bus selection
+                                parameters - see "dtoverlay -h i2c-bus"
+
+
+Name:   enc28j60
+Info:   Overlay for the Microchip ENC28J60 Ethernet Controller on SPI0
+Load:   dtoverlay=enc28j60,<param>=<val>
+Params: int_pin                 GPIO used for INT (default 25)
+
+        speed                   SPI bus speed (default 12000000)
+
+
+Name:   enc28j60-spi2
+Info:   Overlay for the Microchip ENC28J60 Ethernet Controller on SPI2
+Load:   dtoverlay=enc28j60-spi2,<param>=<val>
+Params: int_pin                 GPIO used for INT (default 39)
+
+        speed                   SPI bus speed (default 12000000)
+
+
+Name:   exc3000
+Info:   Enables I2C connected EETI EXC3000 multiple touch controller using
+        GPIO 4 (pin 7 on GPIO header) for interrupt.
+Load:   dtoverlay=exc3000,<param>=<val>
+Params: interrupt               GPIO used for interrupt (default 4)
+        sizex                   Touchscreen size x (default 4096)
+        sizey                   Touchscreen size y (default 4096)
+        invx                    Touchscreen inverted x axis
+        invy                    Touchscreen inverted y axis
+        swapxy                  Touchscreen swapped x y axis
+
+
+Name:   ezsound-6x8iso
+Info:   Overlay for the ezsound 6x8 isolated soundcard.
+Load:   dtoverlay=ezsound-6x8iso
+Params: <None>
+
+
+Name:   fbtft
+Info:   Overlay for SPI-connected displays using the fbtft drivers.
+
+        This overlay seeks to replace the functionality provided by fbtft_device
+        which is now gone from the kernel.
+
+        Most displays from fbtft_device have been ported over.
+        Example:
+          dtoverlay=fbtft,spi0-0,rpi-display,reset_pin=23,dc_pin=24,led_pin=18,rotate=270
+
+        It is also possible to specify the controller (this will use the default
+        init sequence in the driver).
+        Example:
+          dtoverlay=fbtft,spi0-0,ili9341,bgr,reset_pin=23,dc_pin=24,led_pin=18,rotate=270
+
+        For devices on spi1 or spi2, the interfaces should be enabled
+        with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays.
+
+        The following features of fbtft_device have not been ported over:
+        - parallel bus is not supported
+        - the init property which overrides the controller initialization
+          sequence is not supported as a parameter due to memory limitations in
+          the bootloader responsible for applying the overlay.
+
+        See https://github.com/notro/fbtft/wiki/FBTFT-RPI-overlays for how to
+        create an overlay.
+
+Load:   dtoverlay=fbtft,<param>=<val>
+Params:
+        spi<n>-<m>              Configure device at spi<n>, cs<m>
+                                (boolean, required)
+        speed                   SPI bus speed in Hz (default 32000000)
+        cpha                    Shifted clock phase (CPHA) mode
+        cpol                    Inverse clock polarity (CPOL) mode
+
+        adafruit18              Adafruit 1.8
+        adafruit22              Adafruit 2.2 (old)
+        adafruit22a             Adafruit 2.2
+        adafruit28              Adafruit 2.8
+        adafruit13m             Adafruit 1.3 OLED
+        admatec_c-berry28       C-Berry28
+        dogs102                 EA DOGS102
+        er_tftm050_2            ER-TFTM070-2
+        er_tftm070_5            ER-TFTM070-5
+        ew24ha0                 EW24HA0
+        ew24ha0_9bit            EW24HA0 in 9-bit mode
+        freetronicsoled128      Freetronics OLED128
+        hy28a                   HY28A
+        hy28b                   HY28B
+        itdb28_spi              ITDB02-2.8 with SPI interface circuit
+        mi0283qt-2              Watterott MI0283QT-2
+        mi0283qt-9a             Watterott MI0283QT-9A
+        nokia3310               Nokia 3310
+        nokia3310a              Nokia 3310a
+        nokia5110               Nokia 5110
+        piscreen                PiScreen
+        pitft                   Adafruit PiTFT 2.8
+        pioled                  ILSoft OLED
+        rpi-display             Watterott rpi-display
+        sainsmart18             Sainsmart 1.8
+        sainsmart32_spi         Sainsmart 3.2 with SPI interfce circuit
+        tinylcd35               TinyLCD 3.5
+        tm022hdh26              Tianma TM022HDH26
+        tontec35_9481           Tontect 3.5 with ILI9481 controller
+        tontec35_9486           Tontect 3.5 with ILI9486 controller
+        waveshare32b            Waveshare 3.2
+        waveshare22             Waveshare 2.2
+
+        bd663474                BD663474 display controller
+        hx8340bn                HX8340BN display controller
+        hx8347d                 HX8347D display controller
+        hx8353d                 HX8353D display controller
+        hx8357d                 HX8357D display controller
+        ili9163                 ILI9163 display controller
+        ili9320                 ILI9320 display controller
+        ili9325                 ILI9325 display controller
+        ili9340                 ILI9340 display controller
+        ili9341                 ILI9341 display controller
+        ili9481                 ILI9481 display controller
+        ili9486                 ILI9486 display controller
+        pcd8544                 PCD8544 display controller
+        ra8875                  RA8875 display controller
+        s6d02a1                 S6D02A1 display controller
+        s6d1121                 S6D1121 display controller
+        seps525                 SEPS525 display controller
+        sh1106                  SH1106 display controller
+        ssd1289                 SSD1289 display controller
+        ssd1305                 SSD1305 display controller
+        ssd1306                 SSD1306 display controller
+        ssd1325                 SSD1325 display controller
+        ssd1331                 SSD1331 display controller
+        ssd1351                 SSD1351 display controller
+        st7735r                 ST7735R display controller
+        st7789v                 ST7789V display controller
+        tls8204                 TLS8204 display controller
+        uc1611                  UC1611 display controller
+        uc1701                  UC1701 display controller
+        upd161704               UPD161704 display controller
+
+        width                   Display width in pixels
+        height                  Display height in pixels
+        regwidth                Display controller register width (default is
+                                driver specific)
+        buswidth                Display bus interface width (default 8)
+        debug                   Debug output level {0-7}
+        rotate                  Display rotation {0, 90, 180, 270} (counter
+                                clockwise). Not supported by all drivers.
+        bgr                     Enable BGR mode (default off). Use if Red and
+                                Blue are swapped. Not supported by all drivers.
+        fps                     Frames per second (default 30). In effect this
+                                states how long the driver will wait after video
+                                memory has been changed until display update
+                                transfer is started.
+        txbuflen                Length of the FBTFT transmit buffer
+                                (default 4096)
+        startbyte               Sets the Start byte used by fb_ili9320,
+                                fb_ili9325 and fb_hx8347d. Common value is 0x70.
+        gamma                   String representation of Gamma Curve(s). Driver
+                                specific. Not supported by all drivers.
+        reset_pin               GPIO pin for RESET
+        dc_pin                  GPIO pin for D/C
+        led_pin                 GPIO pin for LED backlight
+
+
+Name:   fe-pi-audio
+Info:   Configures the Fe-Pi Audio Sound Card
+Load:   dtoverlay=fe-pi-audio
+Params: <None>
+
+
+Name:   fsm-demo
+Info:   A demonstration of the gpio-fsm driver. The GPIOs are chosen to work
+        nicely with a "traffic-light" display of red, amber and green LEDs on
+        GPIOs 7, 8 and 25 respectively.
+Load:   dtoverlay=fsm-demo,<param>=<val>
+Params: fsm_debug               Enable debug logging (default off)
+
+
+Name:   gc9a01
+Info:   Enables GalaxyCore's GC9A01 single chip driver based displays on
+        SPI0 as fb1, using GPIOs DC=25, RST=27 and BL=18 (physical
+        GPIO header pins 22, 13 and 12 respectively) in addition to the
+        SPI0 pins DIN=10, CLK=11 and CS=8 (physical GPIO header pins 19,
+        23 and 24 respectively).
+Load:   dtoverlay=gc9a01,<param>=<val>
+Params: speed                   Display SPI bus speed
+
+        rotate                  Display rotation {0,90,180,270}
+
+        width                   Width of the display
+
+        height                  Height of the display
+
+        fps                     Delay between frame updates
+
+        debug                   Debug output level {0-7}
+
+
+Name:   ghost-amp
+Info:   An overlay for the Ghost amplifier.
+Load:   dtoverlay=ghost-amp,<param>=<val>
+Params: fsm_debug               Enable debug logging of the GPIO FSM (default
+                                off)
+
+
+Name:   goodix
+Info:   Enables I2C connected Goodix gt9271 multiple touch controller using
+        GPIOs 4 and 17 (pins 7 and 11 on GPIO header) for interrupt and reset.
+Load:   dtoverlay=goodix,<param>=<val>
+Params: addr                    I2C address (default 0x14)
+        interrupt               GPIO used for interrupt (default 4)
+        reset                   GPIO used for reset (default 17)
+        i2c-path                Override I2C path to allow for i2c-gpio buses
+
+
+Name:   googlevoicehat-soundcard
+Info:   Configures the Google voiceHAT soundcard
+Load:   dtoverlay=googlevoicehat-soundcard
+Params: <None>
+
+
+Name:   gpio-charger
+Info:   This is a generic overlay for detecting charger with GPIO.
+Load:   dtoverlay=gpio-charger,<param>=<val>
+Params: gpio                    GPIO pin to trigger on (default 4)
+        active_low              When this is 1 (active low), a falling
+                                edge generates a charging event and a
+                                rising edge generates a discharging event.
+                                When this is 0 (active high), this is
+                                reversed. The default is 0 (active high)
+        gpio_pull               Desired pull-up/down state (off, down, up)
+                                Default is "down".
+        type                    Set a charger type for the pin. (Default: mains)
+
+
+Name:   gpio-fan
+Info:   Configure a GPIO pin to control a cooling fan.
+Load:   dtoverlay=gpio-fan,<param>=<val>
+Params: gpiopin                 GPIO used to control the fan (default 12)
+        temp                    Temperature at which the fan switches on, in
+                                millicelcius (default 55000)
+        hyst                    Temperature delta (in millicelcius) below
+                                temp at which the fan will drop to minrpm
+                                (default 10000)
+
+
+Name:   gpio-hog
+Info:   Activate a "hog" for a GPIO - request that the kernel configures it as
+        an output, driven low or high as indicated by the presence or absence
+        of the active_low parameter. Note that a hogged GPIO is not available
+        to other drivers or for gpioset/gpioget.
+Load:   dtoverlay=gpio-hog,<param>=<val>
+Params: gpio                    GPIO pin to hog (default 26)
+        active_low              If set, the hog drives the GPIO low (defaults
+                                to off - the GPIO is driven high)
+
+
+Name:   gpio-ir
+Info:   Use GPIO pin as rc-core style infrared receiver input. The rc-core-
+        based gpio_ir_recv driver maps received keys directly to a
+        /dev/input/event* device, all decoding is done by the kernel - LIRC is
+        not required! The key mapping and other decoding parameters can be
+        configured by "ir-keytable" tool.
+Load:   dtoverlay=gpio-ir,<param>=<val>
+Params: gpio_pin                Input pin number. Default is 18.
+
+        gpio_pull               Desired pull-up/down state (off, down, up)
+                                Default is "up".
+
+        invert                  "1" = invert the input (active-low signalling).
+                                "0" = non-inverted input (active-high
+                                signalling). Default is "1".
+
+        rc-map-name             Default rc keymap (can also be changed by
+                                ir-keytable), defaults to "rc-rc6-mce"
+
+
+Name:   gpio-ir-tx
+Info:   Use GPIO pin as bit-banged infrared transmitter output.
+        This is an alternative to "pwm-ir-tx". gpio-ir-tx doesn't require
+        a PWM so it can be used together with onboard analog audio.
+Load:   dtoverlay=gpio-ir-tx,<param>=<val>
+Params: gpio_pin                Output GPIO (default 18)
+
+        invert                  "1" = invert the output (make it active-low).
+                                Default is "0" (active-high).
+
+
+Name:   gpio-key
+Info:   This is a generic overlay for activating GPIO keypresses using
+        the gpio-keys library and this dtoverlay. Multiple keys can be
+        set up using multiple calls to the overlay for configuring
+        additional buttons or joysticks. You can see available keycodes
+        at https://github.com/torvalds/linux/blob/v4.12/include/uapi/
+        linux/input-event-codes.h#L64
+Load:   dtoverlay=gpio-key,<param>=<val>
+Params: gpio                    GPIO pin to trigger on (default 3)
+        active_low              When this is 1 (active low), a falling
+                                edge generates a key down event and a
+                                rising edge generates a key up event.
+                                When this is 0 (active high), this is
+                                reversed. The default is 1 (active low)
+        gpio_pull               Desired pull-up/down state (off, down, up)
+                                Default is "up". Note that the default pin
+                                (GPIO3) has an external pullup
+        label                   Set a label for the key
+        keycode                 Set the key code for the button
+        autorepeat              Set the key to autorepeat
+
+
+
+Name:   gpio-led
+Info:   This is a generic overlay for activating LEDs (or any other component)
+        by a GPIO pin. Multiple LEDs can be set up using multiple calls to the
+        overlay. While there are many existing methods to activate LEDs on the
+        RPi, this method offers some advantages:
+        1) Does not require any userspace programs.
+        2) LEDs can be connected to the kernel's led-trigger framework,
+           and drive the LED based on triggers such as cpu load, heartbeat,
+           kernel panic, key input, timers and others.
+        3) LED can be tied to the input state of another GPIO pin.
+        4) The LED is setup early during the kernel boot process (useful
+           for cpu/heartbeat/panic triggers).
+
+        Typical electrical connection is:
+           RPI-GPIO.19  ->  LED  -> 300ohm resister  -> RPI-GND
+        The GPIO pin number can be changed with the 'gpio=' parameter.
+
+        To control an LED from userspace, write a 0 or 1 value:
+           echo 1 > /sys/class/leds/myled1/brightness
+        The 'myled1' name can be changed with the 'label=' parameter.
+
+        To connect the LED to a kernel trigger from userspace:
+           echo cpu > /sys/class/leds/myled1/trigger
+           echo heartbeat > /sys/class/leds/myled1/trigger
+           echo none > /sys/class/leds/myled1/trigger
+        To connect the LED to GPIO.26 pin (physical pin 37):
+           echo gpio > /sys/class/leds/myled1/trigger
+           echo 26 > /sys/class/leds/myled1/gpio
+        Available triggers:
+           cat /sys/class/leds/myled1/trigger
+
+        More information about the Linux kernel LED/Trigger system:
+           https://www.kernel.org/doc/Documentation/leds/leds-class.rst
+           https://www.kernel.org/doc/Documentation/leds/ledtrig-oneshot.rst
+Load:   dtoverlay=gpio-led,<param>=<val>
+Params: gpio                    GPIO pin connected to the LED (default 19)
+        label                   The label for this LED. It will appear under
+                                /sys/class/leds/<label> . Default 'myled1'.
+        trigger                 Set the led-trigger to connect to this LED.
+                                default 'none' (LED is user-controlled).
+                                Some possible triggers:
+                                 cpu - CPU load (all CPUs)
+                                 cpu0 - CPU load of first CPU.
+                                 mmc - disk activity (all disks)
+                                 panic - turn on on kernel panic
+                                 heartbeat - indicate system health
+                                 gpio - connect to a GPIO input pin (note:
+                                        currently the GPIO PIN can not be set
+                                        using overlay parameters, must be
+                                        done in userspace, see examples above.
+        active_low              Set to 1 to turn invert the LED control
+                                (writing 0 to /sys/class/leds/XXX/brightness
+                                will turn on the GPIO/LED). Default '0'.
+
+
+Name:   gpio-no-bank0-irq
+Info:   Use this overlay to disable GPIO interrupts for GPIOs in bank 0 (0-27),
+        which can be useful for UIO drivers.
+        N.B. Using this overlay will trigger a kernel WARN during booting, but
+        this can safely be ignored - the system should work as expected.
+Load:   dtoverlay=gpio-no-bank0-irq
+Params: <None>
+
+
+Name:   gpio-no-irq
+Info:   Use this overlay to disable all GPIO interrupts, which can be useful
+        for user-space GPIO edge detection systems.
+Load:   dtoverlay=gpio-no-irq
+Params: <None>
+
+
+Name:   gpio-poweroff
+Info:   Drives a GPIO high or low on poweroff (including halt). Using this
+        overlay interferes with the normal power-down sequence, preventing the
+        kernel from resetting the SoC (a necessary step in a normal power-off
+        or reboot). This also disables the ability to trigger a boot by driving
+        GPIO3 low.
+
+        The GPIO starts in an inactive state. At poweroff time it is driven
+        active for 100ms, then inactive for 100ms, then active again. It is
+        safe to remove the power at any point after the initial activation of
+        the GPIO.
+
+        Users of this overlay are required to provide an external mechanism to
+        switch off the power supply when signalled - failure to do so results
+        in a kernel BUG, increased power consumption and undefined behaviour.
+Load:   dtoverlay=gpio-poweroff,<param>=<val>
+Params: gpiopin                 GPIO for signalling (default 26)
+
+        active_low              Set if the power control device requires a
+                                high->low transition to trigger a power-down.
+                                Note that this will require the support of a
+                                custom dt-blob.bin to prevent a power-down
+                                during the boot process, and that a reboot
+                                will also cause the pin to go low.
+        input                   Set if the gpio pin should be configured as
+                                an input.
+        export                  Set to export the configured pin to sysfs
+        active_delay_ms         Initial GPIO active period (default 100)
+        inactive_delay_ms       Subsequent GPIO inactive period (default 100)
+        timeout_ms              Specify (in ms) how long the kernel waits for
+                                power-down before issuing a WARN (default 3000).
+
+
+Name:   gpio-shutdown
+Info:   Initiates a shutdown when GPIO pin changes. The given GPIO pin
+        is configured as an input key that generates KEY_POWER events.
+
+        This event is handled by systemd-logind by initiating a
+        shutdown. Systemd versions older than 225 need an udev rule
+        enable listening to the input device:
+
+                ACTION!="REMOVE", SUBSYSTEM=="input", KERNEL=="event*", \
+                        SUBSYSTEMS=="platform", DRIVERS=="gpio-keys", \
+                        ATTRS{keys}=="116", TAG+="power-switch"
+
+        Alternatively this event can be handled also on systems without
+        systemd, just by traditional SysV init daemon. KEY_POWER event
+        (keycode 116) needs to be mapped to KeyboardSignal on console
+        and then kb::kbrequest inittab action which is triggered by
+        KeyboardSignal from console can be configured to issue system
+        shutdown. Steps for this configuration are:
+
+            Add following lines to the /etc/console-setup/remap.inc file:
+
+                # Key Power as special keypress
+                keycode 116 = KeyboardSignal
+
+            Then add following lines to /etc/inittab file:
+
+                # Action on special keypress (Key Power)
+                kb::kbrequest:/sbin/shutdown -t1 -a -h -P now
+
+            And finally reload configuration by calling following commands:
+
+                # dpkg-reconfigure console-setup
+                # service console-setup reload
+                # init q
+
+        This overlay only handles shutdown. After shutdown, the system
+        can be powered up again by driving GPIO3 low. The default
+        configuration uses GPIO3 with a pullup, so if you connect a
+        button between GPIO3 and GND (pin 5 and 6 on the 40-pin header),
+        you get a shutdown and power-up button. Please note that
+        Raspberry Pi 1 Model B rev 1 uses GPIO1 instead of GPIO3.
+Load:   dtoverlay=gpio-shutdown,<param>=<val>
+Params: gpio_pin                GPIO pin to trigger on (default 3)
+                                For Raspberry Pi 1 Model B rev 1 set this
+                                explicitly to value 1, e.g.:
+
+                                    dtoverlay=gpio-shutdown,gpio_pin=1
+
+        active_low              When this is 1 (active low), a falling
+                                edge generates a key down event and a
+                                rising edge generates a key up event.
+                                When this is 0 (active high), this is
+                                reversed. The default is 1 (active low).
+
+        gpio_pull               Desired pull-up/down state (off, down, up)
+                                Default is "up".
+
+                                Note that the default pin (GPIO3) has an
+                                external pullup. Same applies for GPIO1
+                                on Raspberry Pi 1 Model B rev 1.
+
+        debounce                Specify the debounce interval in milliseconds
+                                (default 100)
+
+
+Name:   hd44780-i2c-lcd
+Info:   Configures an HD44780 compatible LCD display connected via a PCF8574 as
+        is often found as a backpack interface for these displays.
+Load:   dtoverlay=hd44780-i2c-lcd,<param>=<val>
+Params: addr                    I2C address of PCF8574
+        pin_d4                  GPIO pin for data pin D4 (default 4)
+
+        pin_d5                  GPIO pin for data pin D5 (default 5)
+
+        pin_d6                  GPIO pin for data pin D6 (default 6)
+
+        pin_d7                  GPIO pin for data pin D7 (default 7)
+
+        pin_en                  GPIO pin for "Enable" (default 2)
+
+        pin_rs                  GPIO pin for "Register Select" (default 0)
+
+        pin_rw                  GPIO pin for R/W select (default 1)
+
+        pin_bl                  GPIO pin for enabling/disabling the display
+                                backlight. (default 3)
+
+        display_height          Height of the display in characters (default 2)
+
+        display_width           Width of the display in characters (default 16)
+        i2c-path                Override I2C path to allow for i2c-gpio buses
+
+
+Name:   hd44780-lcd
+Info:   Configures an HD44780 compatible LCD display. Uses 4 gpio pins for
+        data, 2 gpio pins for enable and register select and 1 optional pin
+        for enabling/disabling the backlight display.
+Load:   dtoverlay=hd44780-lcd,<param>=<val>
+Params: pin_d4                  GPIO pin for data pin D4 (default 6)
+
+        pin_d5                  GPIO pin for data pin D5 (default 13)
+
+        pin_d6                  GPIO pin for data pin D6 (default 19)
+
+        pin_d7                  GPIO pin for data pin D7 (default 26)
+
+        pin_en                  GPIO pin for "Enable" (default 21)
+
+        pin_rs                  GPIO pin for "Register Select" (default 20)
+
+        pin_bl                  Optional pin for enabling/disabling the
+                                display backlight. (default disabled)
+
+        display_height          Height of the display in characters (default 2)
+
+        display_width           Width of the display in characters (default 16)
+
+
+Name:   hdmi-backlight-hwhack-gpio
+Info:   Devicetree overlay for GPIO based backlight on/off capability.
+        Use this if you have one of those HDMI displays whose backlight cannot
+        be controlled via DPMS over HDMI and plan to do a little soldering to
+        use an RPi gpio pin for on/off switching. See:
+        https://www.waveshare.com/wiki/7inch_HDMI_LCD_(C)#Backlight_Control
+Load:   dtoverlay=hdmi-backlight-hwhack-gpio,<param>=<val>
+Params: gpio_pin                GPIO pin used (default 17)
+        active_low              Set this to 1 if the display backlight is
+                                switched on when the wire goes low.
+                                Leave the default (value 0) if the backlight
+                                expects a high to switch it on.
+
+
+Name:   hifiberry-adc
+Info:   Configures the HifiBerry ADC audio card
+Load:   dtoverlay=hifiberry-adc,<param>=<val>
+Params: leds_off                If set to 'true' the onboard indicator LED
+                                is switched off at all times.
+
+
+Name:   hifiberry-adc8x
+Info:   Configures the HifiBerry ADC8X audio card (only on Pi5)
+Load:   dtoverlay=hifiberry-adc8x
+Params: <None>
+
+
+Name:   hifiberry-amp
+Info:   Configures the HifiBerry Amp and Amp+ audio cards
+Load:   dtoverlay=hifiberry-amp
+Params: <None>
+
+
+Name:   hifiberry-amp100
+Info:   Configures the HifiBerry AMP100 audio card
+Load:   dtoverlay=hifiberry-amp100,<param>=<val>
+Params: 24db_digital_gain       Allow gain to be applied via the PCM512x codec
+                                Digital volume control. Enable with
+                                "dtoverlay=hifiberry-amp100,24db_digital_gain"
+                                (The default behaviour is that the Digital
+                                volume control is limited to a maximum of
+                                0dB. ie. it can attenuate but not provide
+                                gain. For most users, this will be desired
+                                as it will prevent clipping. By appending
+                                the 24dB_digital_gain parameter, the Digital
+                                volume control will allow up to 24dB of
+                                gain. If this parameter is enabled, it is the
+                                responsibility of the user to ensure that
+                                the Digital volume control is set to a value
+                                that does not result in clipping/distortion!)
+        slave                   Force AMP100 into slave mode, using Pi as
+                                master for bit clock and frame clock.
+        leds_off                If set to 'true' the onboard indicator LEDs
+                                are switched off at all times.
+        auto_mute               If set to 'true' the amplifier is automatically
+                                muted when the DAC is not playing.
+        mute_ext_ctl            The amplifier's HW mute control is enabled
+                                in ALSA mixer and set to <val>.
+                                Will be overwritten by ALSA user settings.
+
+
+Name:   hifiberry-amp3
+Info:   Configures the HifiBerry Amp3 audio card
+Load:   dtoverlay=hifiberry-amp3
+Params: <None>
+
+
+Name:   hifiberry-amp4pro
+Info:   Configures the HifiBerry AMP4 Pro audio card
+Load:   dtoverlay=hifiberry-amp4pro,<param>=<val>
+Params: 24db_digital_gain       Allow gain to be applied via the TAS5756
+                                Digital volume control. Enable with
+                                "dtoverlay=hifiberry-amp4pro,24db_digital_gain"
+                                (The default behaviour is that the Digital
+                                volume control is limited to a maximum of
+                                0dB. ie. it can attenuate but not provide
+                                gain. For most users, this will be desired
+                                as it will prevent clipping. By appending
+                                the 24dB_digital_gain parameter, the Digital
+                                volume control will allow up to 24dB of
+                                gain. If this parameter is enabled, it is the
+                                responsibility of the user to ensure that
+                                the Digital volume control is set to a value
+                                that does not result in clipping/distortion!)
+        slave                   Force the amp into slave mode, using Pi as
+                                master for bit clock and frame clock.
+        leds_off                If set to 'true' the onboard indicator LEDs
+                                are switched off at all times.
+        auto_mute               If set to 'true' the amplifier is automatically
+                                muted when it is not playing.
+        mute_ext_ctl            The amplifier's HW mute control is enabled
+                                in ALSA mixer and set to <val>.
+                                Will be overwritten by ALSA user settings.
+
+
+Name:   hifiberry-dac
+Info:   Configures the HifiBerry DAC audio cards
+Load:   dtoverlay=hifiberry-dac
+Params: <None>
+
+
+Name:   hifiberry-dac8x
+Info:   Configures the HifiBerry DAC8X audio cards (only on Pi5)
+        This driver also detects a stacked ADC8x and activates the
+        capture capabilities.
+        Note: for standalone use of the ADC8x activate the ADC8x module.
+Load:   dtoverlay=hifiberry-dac8x
+Params: <None>
+
+
+Name:   hifiberry-dacplus
+Info:   Configures the HifiBerry DAC+ audio card
+Load:   dtoverlay=hifiberry-dacplus,<param>=<val>
+Params: 24db_digital_gain       Allow gain to be applied via the PCM512x codec
+                                Digital volume control. Enable with
+                                "dtoverlay=hifiberry-dacplus,24db_digital_gain"
+                                (The default behaviour is that the Digital
+                                volume control is limited to a maximum of
+                                0dB. ie. it can attenuate but not provide
+                                gain. For most users, this will be desired
+                                as it will prevent clipping. By appending
+                                the 24dB_digital_gain parameter, the Digital
+                                volume control will allow up to 24dB of
+                                gain. If this parameter is enabled, it is the
+                                responsibility of the user to ensure that
+                                the Digital volume control is set to a value
+                                that does not result in clipping/distortion!)
+        slave                   Force DAC+ into slave mode, using Pi as
+                                master for bit clock and frame clock.
+        leds_off                If set to 'true' the onboard indicator LEDs
+                                are switched off at all times.
+
+
+Name:   hifiberry-dacplus-pro
+Info:   Configures the HifiBerry DAC+ PRO audio card (onboard clocks)
+Load:   dtoverlay=hifiberry-dacplus-pro,<param>=<val>
+Params: 24db_digital_gain       Allow gain to be applied via the PCM512x codec
+                                Digital volume control. Enable with
+                                "dtoverlay=hifiberry-dacplus,24db_digital_gain"
+                                (The default behaviour is that the Digital
+                                volume control is limited to a maximum of
+                                0dB. ie. it can attenuate but not provide
+                                gain. For most users, this will be desired
+                                as it will prevent clipping. By appending
+                                the 24dB_digital_gain parameter, the Digital
+                                volume control will allow up to 24dB of
+                                gain. If this parameter is enabled, it is the
+                                responsibility of the user to ensure that
+                                the Digital volume control is set to a value
+                                that does not result in clipping/distortion!)
+        leds_off                If set to 'true' the onboard indicator LEDs
+                                are switched off at all times.
+
+
+Name:   hifiberry-dacplus-std
+Info:   Configures the HifiBerry DAC+ standard audio card (no onboard clocks)
+Load:   dtoverlay=hifiberry-dacplus-std,<param>=<val>
+Params: 24db_digital_gain       Allow gain to be applied via the PCM512x codec
+                                Digital volume control. Enable with
+                                "dtoverlay=hifiberry-dacplus,24db_digital_gain"
+                                (The default behaviour is that the Digital
+                                volume control is limited to a maximum of
+                                0dB. ie. it can attenuate but not provide
+                                gain. For most users, this will be desired
+                                as it will prevent clipping. By appending
+                                the 24dB_digital_gain parameter, the Digital
+                                volume control will allow up to 24dB of
+                                gain. If this parameter is enabled, it is the
+                                responsibility of the user to ensure that
+                                the Digital volume control is set to a value
+                                that does not result in clipping/distortion!)
+        leds_off                If set to 'true' the onboard indicator LEDs
+                                are switched off at all times.
+
+
+Name:   hifiberry-dacplusadc
+Info:   Configures the HifiBerry DAC+ADC audio card
+Load:   dtoverlay=hifiberry-dacplusadc,<param>=<val>
+Params: 24db_digital_gain       Allow gain to be applied via the PCM512x codec
+                                Digital volume control. Enable with
+                                "dtoverlay=hifiberry-dacplus,24db_digital_gain"
+                                (The default behaviour is that the Digital
+                                volume control is limited to a maximum of
+                                0dB. ie. it can attenuate but not provide
+                                gain. For most users, this will be desired
+                                as it will prevent clipping. By appending
+                                the 24dB_digital_gain parameter, the Digital
+                                volume control will allow up to 24dB of
+                                gain. If this parameter is enabled, it is the
+                                responsibility of the user to ensure that
+                                the Digital volume control is set to a value
+                                that does not result in clipping/distortion!)
+        leds_off                If set to 'true' the onboard indicator LEDs
+                                are switched off at all times.
+
+
+Name:   hifiberry-dacplusadcpro
+Info:   Configures the HifiBerry DAC+ADC PRO audio card
+Load:   dtoverlay=hifiberry-dacplusadcpro,<param>=<val>
+Params: 24db_digital_gain       Allow gain to be applied via the PCM512x codec
+                                Digital volume control. Enable with
+                                "dtoverlay=hifiberry-dacplusadcpro,24db_digital_gain"
+                                (The default behaviour is that the Digital
+                                volume control is limited to a maximum of
+                                0dB. ie. it can attenuate but not provide
+                                gain. For most users, this will be desired
+                                as it will prevent clipping. By appending
+                                the 24dB_digital_gain parameter, the Digital
+                                volume control will allow up to 24dB of
+                                gain. If this parameter is enabled, it is the
+                                responsibility of the user to ensure that
+                                the Digital volume control is set to a value
+                                that does not result in clipping/distortion!)
+        slave                   Force DAC+ADC Pro into slave mode, using Pi as
+                                master for bit clock and frame clock.
+        leds_off                If set to 'true' the onboard indicator LEDs
+                                are switched off at all times.
+
+
+Name:   hifiberry-dacplusdsp
+Info:   Configures the HifiBerry DAC+DSP audio card
+Load:   dtoverlay=hifiberry-dacplusdsp
+Params: <None>
+
+
+Name:   hifiberry-dacplushd
+Info:   Configures the HifiBerry DAC+ HD audio card
+Load:   dtoverlay=hifiberry-dacplushd
+Params: <None>
+
+
+Name:   hifiberry-digi
+Info:   Configures the HifiBerry Digi and Digi+ audio card
+Load:   dtoverlay=hifiberry-digi
+Params: <None>
+
+
+Name:   hifiberry-digi-pro
+Info:   Configures the HifiBerry Digi+ Pro and Digi2 Pro audio card
+Load:   dtoverlay=hifiberry-digi-pro
+Params: <None>
+
+
+Name:   hifiberry-studio-dac8x
+Info:   Configures the HifiBerry Studio DAC8x audio card
+Load:   dtoverlay=hifiberry-studio-dac8x
+Params: <None>
+
+
+Name:   hifiberry-studio-dac8x-pro
+Info:   Configures the HifiBerry Studio DAC8x PRO audio card
+Load:   dtoverlay=hifiberry-studio-dac8x-pro
+Params: <None>
+
+
+Name:   highperi
+Info:   Enables "High Peripheral" mode
+Load:   dtoverlay=highperi
+Params: <None>
+
+
+Name:   hy28a
+Info:   HY28A - 2.8" TFT LCD Display Module by HAOYU Electronics
+        Default values match Texy's display shield
+Load:   dtoverlay=hy28a,<param>=<val>
+Params: speed                   Display SPI bus speed
+
+        rotate                  Display rotation {0,90,180,270}
+
+        fps                     Delay between frame updates
+
+        debug                   Debug output level {0-7}
+
+        xohms                   Touchpanel sensitivity (X-plate resistance)
+
+        resetgpio               GPIO used to reset controller
+
+        ledgpio                 GPIO used to control backlight
+
+
+Name:   hy28b
+Info:   HY28B - 2.8" TFT LCD Display Module by HAOYU Electronics
+        Default values match Texy's display shield
+Load:   dtoverlay=hy28b,<param>=<val>
+Params: speed                   Display SPI bus speed
+
+        rotate                  Display rotation {0,90,180,270}
+
+        fps                     Delay between frame updates
+
+        debug                   Debug output level {0-7}
+
+        xohms                   Touchpanel sensitivity (X-plate resistance)
+
+        resetgpio               GPIO used to reset controller
+
+        ledgpio                 GPIO used to control backlight
+
+
+Name:   hy28b-2017
+Info:   HY28B 2017 version - 2.8" TFT LCD Display Module by HAOYU Electronics
+        Default values match Texy's display shield
+Load:   dtoverlay=hy28b-2017,<param>=<val>
+Params: speed                   Display SPI bus speed
+
+        rotate                  Display rotation {0,90,180,270}
+
+        fps                     Delay between frame updates
+
+        debug                   Debug output level {0-7}
+
+        xohms                   Touchpanel sensitivity (X-plate resistance)
+
+        resetgpio               GPIO used to reset controller
+
+        ledgpio                 GPIO used to control backlight
+
+
+Name:   i-sabre-q2m
+Info:   Configures the Audiophonics I-SABRE Q2M DAC
+Load:   dtoverlay=i-sabre-q2m
+Params: <None>
+
+
+Name:   i2c-bcm2708
+Info:   Fall back to the i2c_bcm2708 driver for the i2c_arm bus.
+Load:   dtoverlay=i2c-bcm2708
+Params: <None>
+
+
+Name:   i2c-bus
+Info:   This is not a real overlay. Many overlays support the use of a variety
+        of I2C buses, and this is where the relevant parameters are documented.
+Load:   <Documentation>
+Params: i2c0                    Choose the I2C0 bus on GPIOs 0&1
+        i2c1                    Choose the I2C1 bus on GPIOs 2&3
+        i2c2                    Choose the I2C2 bus (configure with the i2c2
+                                overlay - BCM2711 only)
+        i2c3                    Choose the I2C3 bus (configure with the i2c3
+                                overlay - BCM2711 only)
+        i2c4                    Choose the I2C4 bus (configure with the i2c4
+                                overlay - BCM2711 only)
+        i2c5                    Choose the I2C5 bus (configure with the i2c5
+                                overlay - BCM2711 only)
+        i2c6                    Choose the I2C6 bus (configure with the i2c6
+                                overlay - BCM2711 only)
+        i2c_csi_dsi             Choose the I2C bus connected to the main
+                                camera/display connector.
+                                See "dtparam -h i2c_csi_dsi" for details.
+        i2c_csi_dsi0            Choose the I2C bus connected to the second
+                                camera/display connector, if present.
+                                See "dtparam -h i2c_csi_dsi0" for details.
+        i2c-path                Override I2C path to allow for i2c-gpio buses
+
+
+Name:   i2c-fan
+Info:   Adds support for a number of I2C fan controllers
+Load:   dtoverlay=i2c-fan,<param>=<val>
+Params: addr                    Sets the address for the fan controller. Note
+                                that the device must be configured to use the
+                                specified address.
+
+        i2c-bus                 Supports all the standard I2C bus selection
+                                parameters - see "dtoverlay -h i2c-bus"
+
+        minpwm                  PWM setting for the fan when the SoC is below
+                                mintemp (range 0-255. default 0)
+        maxpwm                  PWM setting for the fan when the SoC is above
+                                maxtemp (range 0-255. default 255)
+        midtemp                 Temperature (in millicelcius) at which the fan
+                                begins to speed up (default 50000)
+
+        midtemp_hyst            Temperature delta (in millicelcius) below
+                                mintemp at which the fan will drop to minrpm
+                                (default 2000)
+
+        maxtemp                 Temperature (in millicelcius) at which the fan
+                                will be held at maxrpm (default 70000)
+
+        maxtemp_hyst            Temperature delta (in millicelcius) below
+                                maxtemp at which the fan begins to slow down
+                                (default 2000)
+
+        emc2301                 Select the Microchip EMC230x controller family
+                                - EMC2301, EMC2302, EMC2303, EMC2305.
+
+
+Name:   i2c-gpio
+Info:   Adds support for software i2c controller on gpio pins
+Load:   dtoverlay=i2c-gpio,<param>=<val>
+Params: i2c_gpio_sda            GPIO used for I2C data (default "23")
+
+        i2c_gpio_scl            GPIO used for I2C clock (default "24")
+
+        i2c_gpio_delay_us       Clock delay in microseconds
+                                (default "2" = ~100kHz)
+
+        bus                     Set to a unique, non-zero value if wanting
+                                multiple i2c-gpio busses. If set, will be used
+                                as the preferred bus number (/dev/i2c-<n>). If
+                                not set, the default value is 0, but the bus
+                                number will be dynamically assigned - probably
+                                3.
+
+
+Name:   i2c-mux
+Info:   Adds support for a number of I2C bus multiplexers on i2c_arm
+Load:   dtoverlay=i2c-mux,<param>=<val>
+Params: pca9542                 Select the NXP PCA9542 device
+
+        pca9545                 Select the NXP PCA9545 device
+
+        pca9548                 Select the NXP PCA9548 device
+
+        addr                    Change I2C address of the device (default 0x70)
+
+        i2c-bus                 Supports all the standard I2C bus selection
+                                parameters - see "dtoverlay -h i2c-bus"
+
+        base                    Set an explicit base value for the channel bus
+                                numbers
+
+        disconnect_on_idle      Force the mux to disconnect all child buses
+                                after every transaction.
+
+
+[ The i2c-mux-pca9548a overlay has been deleted. See i2c-mux. ]
+
+
+Name:   i2c-pwm-pca9685a
+Info:   Adds support for an NXP PCA9685A I2C PWM controller on i2c_arm
+Load:   dtoverlay=i2c-pwm-pca9685a,<param>=<val>
+Params: addr                    I2C address of PCA9685A (default 0x40)
+        i2c-bus                 Supports all the standard I2C bus selection
+                                parameters - see "dtoverlay -h i2c-bus"
+
+
+Name:   i2c-rtc
+Info:   Adds support for a number of I2C Real Time Clock devices
+Load:   dtoverlay=i2c-rtc,<param>=<val>
+Params: abx80x                  Select one of the ABx80x family:
+                                  AB0801, AB0803, AB0804, AB0805,
+                                  AB1801, AB1803, AB1804, AB1805
+
+        bq32000                 Select the TI BQ32000 device
+
+        ds1307                  Select the DS1307 device
+
+        ds1339                  Select the DS1339 device
+
+        ds1340                  Select the DS1340 device
+
+        ds3231                  Select the DS3231 device
+
+        m41t62                  Select the M41T62 device
+
+        m41t80                  Select the M41T80 device
+
+        mcp7940x                Select the MCP7940x device
+
+        mcp7941x                Select the MCP7941x device
+
+        pcf2127                 Select the PCF2127 device
+
+        pcf2129                 Select the PCF2129 device
+
+        pcf2131                 Select the PCF2131 device
+
+        pcf85063                Select the PCF85063 device
+
+        pcf85063a               Select the PCF85063A device
+
+        pcf8523                 Select the PCF8523 device
+
+        pcf85363                Select the PCF85363 device
+
+        pcf8563                 Select the PCF8563 device
+
+        rv1805                  Select the Micro Crystal RV1805 device
+
+        rv3028                  Select the Micro Crystal RV3028 device
+
+        rv3032                  Select the Micro Crystal RV3032 device
+
+        rv8803                  Select the Micro Crystal RV8803 device
+
+        sd3078                  Select the ZXW Shenzhen whwave SD3078 device
+
+        s35390a                 Select the ABLIC S35390A device
+
+        i2c-bus                 Supports all the standard I2C bus selection
+                                parameters - see "dtoverlay -h i2c-bus"
+
+        addr                    Sets the address for the RTC. Note that the
+                                device must be configured to use the specified
+                                address.
+
+        trickle-diode-disable   Do not use the internal trickle charger diode
+                                (BQ32000 only)
+
+        trickle-diode-type      Diode type for trickle charge - "standard" or
+                                "schottky" (ABx80x and RV1805 only)
+
+        trickle-resistor-ohms   Resistor value for trickle charge (DS1339,
+                                ABx80x, BQ32000, RV1805, RV3028, RV3032)
+
+        trickle-voltage-mv      Charge pump voltage for trickle charge (RV3032)
+
+        wakeup-source           Specify that the RTC can be used as a wakeup
+                                source
+
+        backup-switchover-mode  Backup power supply switch mode. Must be 0 for
+                                "Switchover disabled", 1 for "Direct Switching"
+                                (if Vdd < VBackup), 2 for "Standby
+                                Mode" (if Vdd < Vbackup,
+                                does not draw current) or 3 for
+                                "Level Switching" (if Vdd < Vbackup
+                                and Vdd < Vddsw and Vbackup > Vddsw)
+                                (RV3028, RV3032)
+
+
+Name:   i2c-rtc-gpio
+Info:   Adds support for a number of I2C Real Time Clock devices
+        using the software i2c controller
+Load:   dtoverlay=i2c-rtc-gpio,<param>=<val>
+Params: abx80x                  Select one of the ABx80x family:
+                                  AB0801, AB0803, AB0804, AB0805,
+                                  AB1801, AB1803, AB1804, AB1805
+
+        bq32000                 Select the TI BQ32000 device
+
+        ds1307                  Select the DS1307 device
+
+        ds1339                  Select the DS1339 device
+
+        ds1340                  Select the DS1340 device
+
+        ds3231                  Select the DS3231 device
+
+        m41t62                  Select the M41T62 device
+
+        m41t80                  Select the M41T80 device
+
+        mcp7940x                Select the MCP7940x device
+
+        mcp7941x                Select the MCP7941x device
+
+        pcf2127                 Select the PCF2127 device
+
+        pcf2129                 Select the PCF2129 device
+
+        pcf2131                 Select the PCF2131 device
+
+        pcf85063                Select the PCF85063 device
+
+        pcf85063a               Select the PCF85063A device
+
+        pcf8523                 Select the PCF8523 device
+
+        pcf85363                Select the PCF85363 device
+
+        pcf8563                 Select the PCF8563 device
+
+        rv1805                  Select the Micro Crystal RV1805 device
+
+        rv3028                  Select the Micro Crystal RV3028 device
+
+        rv3032                  Select the Micro Crystal RV3032 device
+
+        rv8803                  Select the Micro Crystal RV8803 device
+
+        sd3078                  Select the ZXW Shenzhen whwave SD3078 device
+
+        s35390a                 Select the ABLIC S35390A device
+
+        addr                    Sets the address for the RTC. Note that the
+                                device must be configured to use the specified
+                                address.
+
+        trickle-diode-disable   Do not use the internal trickle charger diode
+                                (BQ32000 only)
+
+        trickle-diode-type      Diode type for trickle charge - "standard" or
+                                "schottky" (ABx80x and RV1805 only)
+
+        trickle-resistor-ohms   Resistor value for trickle charge (DS1339,
+                                ABx80x, BQ32000, RV1805, RV3028, RV3032)
+
+        trickle-voltage-mv      Charge pump voltage for trickle charge (RV3032)
+
+        wakeup-source           Specify that the RTC can be used as a wakeup
+                                source
+
+        backup-switchover-mode  Backup power supply switch mode. Must be 0 for
+                                "Switchover disabled", 1 for "Direct Switching"
+                                (if Vdd < VBackup), 2 for "Standby
+                                Mode" (if Vdd < Vbackup,
+                                does not draw current) or 3 for
+                                "Level Switching" (if Vdd < Vbackup
+                                and Vdd < Vddsw and Vbackup > Vddsw)
+                                (RV3028, RV3032)
+
+        i2c_gpio_sda            GPIO used for I2C data (default "23")
+
+        i2c_gpio_scl            GPIO used for I2C clock (default "24")
+
+        i2c_gpio_delay_us       Clock delay in microseconds
+                                (default "2" = ~100kHz)
+
+
+Name:   i2c-sensor
+Info:   Adds support for a number of I2C barometric pressure, temperature,
+        light level and chemical sensors on i2c_arm
+Load:   dtoverlay=i2c-sensor,<param>=<val>
+Params: addr                    Set the address for the AD799X, ADS7828,
+                                ADS7830, ADT7410, AS73211, AS7331, BH1750,
+                                BME280, BME680, BMP280, BMP380, BMP58x, CCS811,
+                                DS1621, HDC100X, HDC3020, JC42, LM75, MCP980x,
+                                MPU6050, MPU9250, MS5637, MS5803, MS5805,
+                                MS5837, MS8607, SHT3x, TMP102 or VEML6030.
+
+        i2c-bus                 Supports all the standard I2C bus selection
+                                parameters - see "dtoverlay -h i2c-bus"
+
+        ad7991                  Select the Analog Devices AD7991 ADC
+                                Valid addresses 0x28-0x29, default 0x28
+
+        ad7992                  Select the Analog Devices AD7992 ADC
+                                Valid addresses 0x20-0x24, default 0x20
+
+        ad7993                  Select the Analog Devices AD7993 ADC
+                                Valid addresses 0x20-0x24, default 0x20
+
+        ad7994                  Select the Analog Devices AD7994 ADC
+                                Valid addresses 0x20-0x24, default 0x20
+
+        ad7995                  Select the Analog Devices AD7995 ADC
+                                Valid addresses 0x28-0x29, default 0x28
+
+        ad7997                  Select the Analog Devices AD7997 ADC
+                                Valid addresses 0x20-0x24, default 0x20
+
+        ad7998                  Select the Analog Devices AD7998 ADC
+                                Valid addresses 0x20-0x24, default 0x20
+
+        ad7999                  Select the Analog Devices AD7999 ADC
+                                Valid address 0x29 (the default)
+
+        ads7828                 Select the TI ADS7828 50kHz 8-channel 12bit ADC
+                                Valid addresses 0x48-0x4b, default 0x48
+
+        ads7830                 Select the TI ADS7830 70kHz 8-channel 8bit ADC
+                                Valid addresses 0x48-0x4b, default 0x48
+
+        adt7410                 Select the Analog Devices ADT7410 and ADT7420
+                                temperature sensors
+                                Valid address 0x48-0x4b, default 0x48
+
+        adxl345                 Select the Analog Devices ADXL345 3-axis
+                                accelerometer
+
+        adxl355                 Select the Analog Devices ADXL355 3-axis
+                                accelerometer
+
+        aht10                   Select the Aosong AHT10 temperature and humidity
+                                sensor
+
+        aht20                   Select the Aosong AHT20 temperature and humidity
+                                sensor
+
+        as73211                 Select the AMS AS73211 XYZ true color sensor
+                                Valid addresses 0x74-0x77, default 0x74
+
+        as7331                  Select the AMS AS7331 spectral UV sensor
+                                Valid addresses 0x74-0x77, default 0x74
+
+        bh1750                  Select the Rohm BH1750 ambient light sensor
+                                Valid addresses 0x23 or 0x5c, default 0x23
+
+        bme280                  Select the Bosch Sensortronic BME280
+                                Valid addresses 0x76-0x77, default 0x76
+
+        bme680                  Select the Bosch Sensortronic BME680
+                                Valid addresses 0x76-0x77, default 0x76
+
+        bmp085                  Select the Bosch Sensortronic BMP085
+
+        bmp180                  Select the Bosch Sensortronic BMP180
+
+        bmp280                  Select the Bosch Sensortronic BMP280
+                                Valid addresses 0x76-0x77, default 0x76
+
+        bmp380                  Select the Bosch Sensortronic BMP380
+                                Valid addresses 0x76-0x77, default 0x76
+
+        bmp58x                  Select the Bosch Sensortronic BMP580, BMP581,
+                                or BMP585
+                                Valid addresses 0x46-0x47, default 0x46
+
+        bno055                  Select the Bosch Sensortronic BNO055 IMU
+                                Valid address 0x28-0x29, default 0x29
+
+        ccs811                  Select the AMS CCS811 digital gas sensor
+                                Valid addresses 0x5a-0x5b, default 0x5b
+
+        ds1621                  Select the Dallas Semiconductors DS1621 temp
+                                sensor. Valid addresses 0x48-0x4f, default 0x48
+
+        gain                    Gain used for measuring shunt resistor current.
+                                Valid values 1 or 4, default 1. (ina238 only,
+                                disabled by default)
+
+        hdc100x                 Select the Texas Instruments HDC100x temp sensor
+                                Valid addresses 0x40-0x43, default 0x40
+
+        hdc3020                 Select the Texas Instruments HDC3020 temp sensor
+                                Valid addresses 0x44-0x47, default 0x44
+
+        hts221                  Select the HTS221 temperature and humidity
+                                sensor
+
+        htu21                   Select the HTU21 temperature and humidity sensor
+
+        ina238                  Select the TI INA238 power monitor. Valid
+                                addresses 0x40-0x4F, default 0x40.
+                                Uses parameters shunt-resistor and
+                                ti,shunt-gain for configuration
+
+        int_pin                 Set the GPIO to use for interrupts (as73211,
+                                as7331, hdc3020, hts221, max30102, mpu6050 and
+                                mpu9250 only)
+
+        jc42                    Select any of the many JEDEC JC42.4-compliant
+                                temperature sensors, including:
+                                  ADT7408, AT30TS00, CAT34TS02, CAT6095,
+                                  MAX6604, MCP9804, MCP9805, MCP9808,
+                                  MCP98242, MCP98243, MCP98244, MCP9843,
+                                  SE97, SE98, STTS424(E), STTS2002, STTS3000,
+                                  TSE2002, TSE2004, TS3000, and TS3001.
+                                The default address is 0x18.
+
+        label                   Set the device label (ADS7828 and ADS7830 only)
+
+        lm75                    Select the Maxim LM75 temperature sensor
+                                Valid addresses 0x48-0x4f, default 0x4f
+
+        lm75addr                Deprecated - use addr parameter instead
+
+        max17040                Select the Maxim Integrated MAX17040 battery
+                                monitor
+
+        max30102                Select the Maxim Integrated MAX30102 heart-rate
+                                and blood-oxygen sensor
+
+        mcp980x                 Select the Maxim MCP980x range of temperature
+                                sensors (i.e. MCP9800, MCP9801, MCP9802 and
+                                MCP9803). N.B. For MCP9804, MCP9805 and MCP9808,
+                                use the "jc42" option.
+                                Valid addresses are 0x18-0x1f (default 0x18)
+
+        mpu6050                 Select the InvenSense MPU6050 IMU. Valid
+                                valid addresses are 0x68 and 0x69 (default 0x68)
+
+        mpu9250                 Select the InvenSense MPU9250 IMU. Valid
+                                valid addresses are 0x68 and 0x69 (default 0x68)
+
+        ms5637                  Select the Measurement Specialities MS5637
+                                pressure and temperature sensor.
+
+        ms5803                  Select the Measurement Specialities MS5803
+                                pressure and temperature sensor.
+
+        ms5805                  Select the Measurement Specialities MS5805
+                                pressure and temperature sensor.
+
+        ms5837                  Select the Measurement Specialities MS5837
+                                pressure and temperature sensor.
+
+        ms8607                  Select the Measurement Specialities MS8607
+                                pressure and temperature sensor.
+
+        no_timeout              Disable the SMBUS timeout. N.B. Only supported
+                                by some jc42 devices - using with an
+                                incompatible device can stop it from being
+                                activated.
+
+        reset_pin               GPIO to be used to reset the device (bno055
+                                only, disabled by default)
+
+        shunt_resistor          Value of shunt resistor used for current
+                                measurement in uOhms.  (ina238 only, disabled
+                                by default)
+
+        scd4x                   Select the Sensirion SCD40/41 CO2 sensors.
+
+        sht3x                   Select the Sensirion SHT3x temperature and
+                                humidity sensors. Valid addresses 0x44-0x45,
+                                default 0x44
+
+        sht4x                   Select the Sensirion SHT4x temperature and
+                                humidity sensors. Valid addresses 0x44-0x45,
+                                default 0x44
+
+        shtc3                   Select the Sensirion SHTC3 temperature and
+                                humidity sensors.
+
+        si7020                  Select the Silicon Labs Si7013/20/21 humidity/
+                                temperature sensor
+
+        sps30                   Select the Sensirion SPS30 particulate matter
+                                sensor. Fixed address 0x69.
+
+        sgp30                   Select the Sensirion SGP30 VOC sensor.
+                                Fixed address 0x58.
+
+        tcs3472                 Select the AMS TAOS TCS3472 family of RGBW light
+                                sensors.
+                                Valid addresses 0x48-0x4b, default 0x48
+
+        tmp102                  Select the Texas Instruments TMP102 temp sensor
+                                Valid addresses 0x48-0x4b, default 0x48
+
+        tmp117                  Select the Texas Instruments TMP117 temp sensor
+                                Valid addresses 0x48-0x4b, default 0x48
+
+        tsl4531                 Select the AMS TSL4531 digital ambient light
+                                sensor
+
+        veml6030                Select the Vishay VEML6030 ambient light sensor
+
+        veml6040                Select the Vishay VEML6040 RGBW light sensor
+
+        veml6070                Select the Vishay VEML6070 ultraviolet light
+                                sensor
+
+        veml6075                Select the Vishay VEML6075 UVA and UVB light
+                                sensor
+
+
+Name:   i2c0
+Info:   Change i2c0 pin usage. Not all pin combinations are usable on all
+        platforms - platforms other then Compute Modules can only use this
+        to disable transaction combining.
+        Do NOT use in conjunction with dtparam=i2c_vc=on. From the 5.4 kernel
+        onwards the base DT includes the use of i2c_mux_pinctrl to expose two
+        muxings of BSC0 - GPIOs 0&1, and whichever combination is used for the
+        camera and display connectors. This overlay disables that mux and
+        configures /dev/i2c0 to point at whichever set of pins is requested.
+        dtparam=i2c_vc=on will try and enable the mux, so combining the two
+        will cause conflicts.
+Load:   dtoverlay=i2c0,<param>=<val>
+Params: pins_0_1                Use pins 0 and 1 (default)
+        pins_28_29              Use pins 28 and 29
+        pins_44_45              Use pins 44 and 45
+        pins_46_47              Use pins 46 and 47
+        combine                 Allow transactions to be combined (default
+                                "yes")
+
+
+Name:   i2c0-bcm2708
+Info:   Deprecated, legacy version of i2c0.
+Load:   <Deprecated>
+
+
+Name:   i2c0-pi5
+Info:   Enable i2c0 (Pi 5 only)
+Load:   dtoverlay=i2c0-pi5,<param>=<val>
+Params: pins_0_1                Use GPIOs 0 and 1 (default)
+        pins_8_9                Use GPIOs 8 and 9
+        baudrate                Set the baudrate for the interface (default
+                                "100000")
+
+
+Name:   i2c1
+Info:   Change i2c1 pin usage. Not all pin combinations are usable on all
+        platforms - platforms other then Compute Modules can only use this
+        to disable transaction combining.
+Load:   dtoverlay=i2c1,<param>=<val>
+Params: pins_2_3                Use pins 2 and 3 (default)
+        pins_44_45              Use pins 44 and 45
+        combine                 Allow transactions to be combined (default
+                                "yes")
+
+
+Name:   i2c1-bcm2708
+Info:   Deprecated, legacy version of i2c1.
+Load:   <Deprecated>
+
+
+Name:   i2c1-pi5
+Info:   Enable i2c1 (Pi 5 only)
+Load:   dtoverlay=i2c1-pi5,<param>=<val>
+Params: pins_2_3                Use GPIOs 2 and 3 (default)
+        pins_10_11              Use GPIOs 10 and 11
+        baudrate                Set the baudrate for the interface (default
+                                "100000")
+
+
+Name:   i2c2-pi5
+Info:   Enable i2c2 (Pi 5 only)
+Load:   dtoverlay=i2c2-pi5,<param>=<val>
+Params: pins_4_5                Use GPIOs 4 and 5 (default)
+        pins_12_13              Use GPIOs 12 and 13
+        baudrate                Set the baudrate for the interface (default
+                                "100000")
+
+
+Name:   i2c3
+Info:   Enable the i2c3 bus. BCM2711 only.
+Load:   dtoverlay=i2c3,<param>
+Params: pins_2_3                Use GPIOs 2 and 3
+        pins_4_5                Use GPIOs 4 and 5 (default)
+        baudrate                Set the baudrate for the interface (default
+                                "100000")
+
+
+Name:   i2c3-pi5
+Info:   Enable i2c3 (Pi 5 only)
+Load:   dtoverlay=i2c3-pi5,<param>=<val>
+Params: pins_6_7                Use GPIOs 6 and 7 (default)
+        pins_14_15              Use GPIOs 14 and 15
+        pins_22_23              Use GPIOs 22 and 23
+        baudrate                Set the baudrate for the interface (default
+                                "100000")
+
+
+Name:   i2c4
+Info:   Enable the i2c4 bus. BCM2711 only.
+Load:   dtoverlay=i2c4,<param>
+Params: pins_6_7                Use GPIOs 6 and 7
+        pins_8_9                Use GPIOs 8 and 9 (default)
+        baudrate                Set the baudrate for the interface (default
+                                "100000")
+
+
+Name:   i2c5
+Info:   Enable the i2c5 bus. BCM2711 only.
+Load:   dtoverlay=i2c5,<param>
+Params: pins_10_11              Use GPIOs 10 and 11
+        pins_12_13              Use GPIOs 12 and 13 (default)
+        baudrate                Set the baudrate for the interface (default
+                                "100000")
+
+
+Name:   i2c6
+Info:   Enable the i2c6 bus. BCM2711 only.
+Load:   dtoverlay=i2c6,<param>
+Params: pins_0_1                Use GPIOs 0 and 1
+        pins_22_23              Use GPIOs 22 and 23 (default)
+        baudrate                Set the baudrate for the interface (default
+                                "100000")
+
+
+Name:   i2s-dac
+Info:   Configures any passive I2S DAC soundcard.
+Load:   dtoverlay=i2s-dac
+Params: <None>
+
+
+Name:   i2s-gpio28-31
+Info:   move I2S function block to GPIO 28 to 31
+Load:   dtoverlay=i2s-gpio28-31
+Params: <None>
+
+
+Name:   i2s-master-dac
+Info:   Configures a generic I2S DAC soundcard that acts as a clock master.
+Load:   dtoverlay=i2s-master-dac
+Params: <None>
+
+
+Name:   ilitek-ts-i2c
+Info:   Enables I2C connected Ilitek 23XX, 25XX, and 213X touch controllers
+        using GPIO 4 (pin 7 on GPIO header) for interrupt.
+Load:   dtoverlay=ilitek-ts-i2c,<param>=<val>
+Params: interrupt               GPIO used for interrupt (default 4)
+        invx                    Touchscreen inverted x axis
+        invy                    Touchscreen inverted y axis
+        swapxy                  Touchscreen swapped x y axis
+        i2c-path                Override I2C path to allow for i2c-gpio buses
+
+
+Name:   ilitek251x
+Info:   Enables I2C connected Ilitek 251x multiple touch controller using
+        GPIO 4 (pin 7 on GPIO header) for interrupt.
+Load:   dtoverlay=ilitek251x,<param>=<val>
+Params: interrupt               GPIO used for interrupt (default 4)
+        sizex                   Touchscreen size x, horizontal resolution of
+                                touchscreen (in pixels)
+        sizey                   Touchscreen size y, vertical resolution of
+                                touchscreen (in pixels)
+        i2c-path                Override I2C path to allow for i2c-gpio buses
+
+
+Name:   imx219
+Info:   Sony IMX219 camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=imx219,<param>=<val>
+Params: rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 180)
+        orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default on)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+        vcm                     Configure a VCM focus drive on the sensor.
+        4lane                   Enable 4 CSI2 lanes. This requires a Compute
+                                Module (1, 3, 4, or 5) or Pi 5.
+
+
+Name:   imx258
+Info:   Sony IMX258 camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=imx258,<param>=<val>
+Params: rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 180)
+        orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default on)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+        vcm                     Configure a VCM focus drive on the sensor.
+        4lane                   Enable 4 CSI2 lanes. This requires a Compute
+                                Module (1, 3, 4, or 5) or Pi 5.
+
+
+Name:   imx283
+Info:   Sony IMX283 camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants. This sensor only supports 4 lane operation and therefore
+        requires a Compute Module (1, 3, 4, or 5) or Pi 5.
+Load:   dtoverlay=imx283,<param>=<val>
+Params: rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 180)
+        orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        clock-frequency         Sets the clock frequency to match that used on
+                                the board.
+                                Known tested values include 24MHz (the default)
+                                and 12MHz.
+        link-frequency          Allowable link frequency values to use in Hz:
+                                720000000 (default), 360000000.
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default on)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module or Pi5 (CSI0, i2c_vc, and
+                                cam0_reg).
+
+
+Name:   imx290
+Info:   Sony IMX290 camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=imx290,<param>
+Params: 4lane                   Enable 4 CSI2 lanes. This requires a Compute
+                                Module (1, 3, 4, or 5) or Pi 5.
+        clock-frequency         Sets the clock frequency to match that used on
+                                the board.
+                                Modules from Vision Components use 37.125MHz
+                                (the default), whilst those from Innomaker use
+                                74.25MHz.
+        mono                    Denote that the module is a mono sensor.
+        orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 0)
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default on)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+
+
+Name:   imx296
+Info:   Sony IMX296 camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=imx296,<param>=<val>
+Params: rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 180)
+        orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default on)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+        clock-frequency         Sets the clock frequency to match that used on
+                                the board, which should be one of 54000000
+                                (the default), 37125000 or 74250000.
+        always-on               Leave the regulator powered up, to stop the
+                                camera clamping I/Os such as XTRIG to 0V.
+        sync-sink               Configure as synchronisation sink.
+
+
+Name:   imx327
+Info:   Sony IMX327 camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=imx327,<param>
+Params: 4lane                   Enable 4 CSI2 lanes. This requires a Compute
+                                Module (1, 3, 4, or 5) or Pi 5.
+        clock-frequency         Sets the clock frequency to match that used on
+                                the board.
+                                Modules from Vision Components use 37.125MHz
+                                (the default), whilst those from Innomaker use
+                                74.25MHz.
+        mono                    Denote that the module is a mono sensor.
+        orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 0)
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default on)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+
+
+Name:   imx335
+Info:   Sony IMX335 camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=imx335,<param>
+Params: orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 0)
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default on)
+        link-frequency          Allowable link frequency values to use in Hz:
+                                594000000 (default), 445500000.
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+        4lane                   Enable 4 CSI2 lanes. This requires a Compute
+                                Module (1, 3, 4, or 5) or Pi 5.
+
+
+Name:   imx378
+Info:   Sony IMX378 camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=imx378,<param>=<val>
+Params: rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 180)
+        orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default on)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+        always-on               Leave the regulator powered up, to stop the
+                                camera clamping I/Os such as XVS to 0V.
+        sync-source             Configure as vsync source
+        sync-sink               Configure as vsync sink
+        link-frequency          Allowable link frequency values to use in Hz:
+                                450000000 (default), 453000000, 456000000.
+
+
+Name:   imx415
+Info:   Sony IMX415 camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants. By default this uses 4 CSI2 data lanes, so requires a
+        Compute Module or Pi5.
+Load:   dtoverlay=imx415,<param>
+Params: addr                    Set I2C address of sensor. Valid values are
+                                0x10, 0x1a, 0x36 and 0x37. Default is 0x37.
+        4lane                   Enable 4 CSI2 data lanes.
+        clock-frequency         Sets the clock frequency to match that used on
+                                the board.
+                                Valid values are 24, 27, 37.125, 72, or
+                                74.25MHz.
+                                The default is 24MHz.
+                                Note that the link frequencies permitted vary
+                                based on the oscillator used.
+        link-frequency          Confgures the link frequency to be used. Note
+                                that the permitted values vary based on
+                                clock-frequency and number of lanes.
+                                The default is 360MHz for 720Mbit/s.
+        orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 0)
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default on)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+        vcm                     Enable ad5398 VCM associated with the sensor.
+        clk-37125               Alternative to clock-frequency=37125000 and
+                                link-frequency=445500000 for Waveshare modules
+                                with 37.125MHz clock sources.
+
+
+Name:   imx462
+Info:   Sony IMX462 camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=imx462,<param>
+Params: 4lane                   Enable 4 CSI2 lanes. This requires a Compute
+                                Module (1, 3, or 4).
+        clock-frequency         Sets the clock frequency to match that used on
+                                the board.
+                                Modules from Vision Components use 37.125MHz
+                                (the default), whilst those from Innomaker use
+                                74.25MHz.
+        mono                    Denote that the module is a mono sensor.
+        orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 0)
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default on)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+
+
+Name:   imx477
+Info:   Sony IMX477 camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=imx477,<param>=<val>
+Params: rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 180)
+        orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default on)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+        always-on               Leave the regulator powered up, to stop the
+                                camera clamping I/Os such as XVS to 0V.
+        sync-source             Configure as vsync source
+        sync-sink               Configure as vsync sink
+        link-frequency          Allowable link frequency values to use in Hz:
+                                450000000 (default), 453000000, 456000000.
+
+
+Name:   imx500
+Info:   Sony IMX500 camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=imx500,<param>=<val>
+Params: rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 0)
+        orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default on)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+        bypass-cache            Do save blocks of data to flash when using
+                                rp2040-gpio-bridge for SPI transfers.
+
+
+Name:   imx500-pi5
+Info:   See imx500 (this is the Pi 5 version)
+
+
+Name:   imx519
+Info:   Sony IMX519 camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=imx519,<param>=<val>
+Params: rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 0)
+        orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default on)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+        vcm                     Select lens driver state. Default is enabled,
+                                but vcm=off will disable.
+
+
+Name:   imx708
+Info:   Sony IMX708 camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=imx708,<param>=<val>
+Params: rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 180)
+        orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        vcm                     Select lens driver state. Default is enabled,
+                                but vcm=off will disable.
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default on)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+        link-frequency          Allowable link frequency values to use in Hz:
+                                450000000 (default), 447000000, 453000000.
+
+
+Name:   interludeaudio-analog
+Info:   Configures Interlude Audio Analog Hat audio card
+Load:   dtoverlay=interludeaudio-analog,<param>=<val>
+Params: gpiopin                 GPIO pin for codec reset
+
+
+Name:   interludeaudio-digital
+Info:   Configures Interlude Audio Digital Hat audio card
+Load:   dtoverlay=interludeaudio-digital
+Params: <None>
+
+
+Name:   iqaudio-codec
+Info:   Configures the IQaudio Codec audio card
+Load:   dtoverlay=iqaudio-codec
+Params: <None>
+
+
+Name:   iqaudio-dac
+Info:   Configures the IQaudio DAC audio card
+Load:   dtoverlay=iqaudio-dac,<param>
+Params: 24db_digital_gain       Allow gain to be applied via the PCM512x codec
+                                Digital volume control. Enable with
+                                "dtoverlay=iqaudio-dac,24db_digital_gain"
+                                (The default behaviour is that the Digital
+                                volume control is limited to a maximum of
+                                0dB. ie. it can attenuate but not provide
+                                gain. For most users, this will be desired
+                                as it will prevent clipping. By appending
+                                the 24db_digital_gain parameter, the Digital
+                                volume control will allow up to 24dB of
+                                gain. If this parameter is enabled, it is the
+                                responsibility of the user to ensure that
+                                the Digital volume control is set to a value
+                                that does not result in clipping/distortion!)
+
+
+Name:   iqaudio-dacplus
+Info:   Configures the IQaudio DAC+ audio card
+Load:   dtoverlay=iqaudio-dacplus,<param>=<val>
+Params: 24db_digital_gain       Allow gain to be applied via the PCM512x codec
+                                Digital volume control. Enable with
+                                "dtoverlay=iqaudio-dacplus,24db_digital_gain"
+                                (The default behaviour is that the Digital
+                                volume control is limited to a maximum of
+                                0dB. ie. it can attenuate but not provide
+                                gain. For most users, this will be desired
+                                as it will prevent clipping. By appending
+                                the 24db_digital_gain parameter, the Digital
+                                volume control will allow up to 24dB of
+                                gain. If this parameter is enabled, it is the
+                                responsibility of the user to ensure that
+                                the Digital volume control is set to a value
+                                that does not result in clipping/distortion!)
+        auto_mute_amp           If specified, unmute/mute the IQaudIO amp when
+                                starting/stopping audio playback.
+        unmute_amp              If specified, unmute the IQaudIO amp once when
+                                the DAC driver module loads.
+
+
+Name:   iqaudio-digi-wm8804-audio
+Info:   Configures the IQAudIO Digi WM8804 audio card
+Load:   dtoverlay=iqaudio-digi-wm8804-audio,<param>=<val>
+Params: card_name               Override the default, "IQAudIODigi", card name.
+        dai_name                Override the default, "IQAudIO Digi", dai name.
+        dai_stream_name         Override the default, "IQAudIO Digi HiFi",
+                                dai stream name.
+
+
+Name:   iqs550
+Info:   Enables I2C connected Azoteq IQS550 trackpad/touchscreen controller
+        using GPIO 4 (pin 7 on GPIO header) for interrupt.
+Load:   dtoverlay=iqs550,<param>=<val>
+Params: interrupt               GPIO used for interrupt (default 4)
+        reset                   GPIO used for reset (optional)
+        sizex                   Touchscreen size x (default 800)
+        sizey                   Touchscreen size y (default 480)
+        invx                    Touchscreen inverted x axis
+        invy                    Touchscreen inverted y axis
+        swapxy                  Touchscreen swapped x y axis
+
+
+Name:   irs1125
+Info:   Infineon irs1125 TOF camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=irs1125,<param>=<val>
+Params: media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default off)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+
+
+Name:   jedec-spi-nor
+Info:   Adds support for JEDEC-compliant SPI NOR flash devices.  (Note: The
+        "jedec,spi-nor" kernel driver was formerly known as "m25p80".)
+Load:   dtoverlay=jedec-spi-nor,<param>=<val>
+Params: spi<n>-<m>              Enable flash device on SPI<n>, CS#<m>
+        fastr                   Add fast read capability to the flash device
+        speed                   Maximum SPI frequency (Hz)
+        flash-spi<n>-<m>        Same as spi<n>-<m> (deprecated)
+        flash-fastr-spi<n>-<m>  Same as spi<n>->m>,fastr (deprecated)
+
+
+Name:   justboom-both
+Info:   Simultaneous usage of an justboom-dac and justboom-digi based
+        card
+Load:   dtoverlay=justboom-both,<param>=<val>
+Params: 24db_digital_gain       Allow gain to be applied via the PCM512x codec
+                                Digital volume control. Enable with
+                                "dtoverlay=justboom-dac,24db_digital_gain"
+                                (The default behaviour is that the Digital
+                                volume control is limited to a maximum of
+                                0dB. ie. it can attenuate but not provide
+                                gain. For most users, this will be desired
+                                as it will prevent clipping. By appending
+                                the 24dB_digital_gain parameter, the Digital
+                                volume control will allow up to 24dB of
+                                gain. If this parameter is enabled, it is the
+                                responsibility of the user to ensure that
+                                the Digital volume control is set to a value
+                                that does not result in clipping/distortion!)
+
+
+Name:   justboom-dac
+Info:   Configures the JustBoom DAC HAT, Amp HAT, DAC Zero and Amp Zero audio
+        cards
+Load:   dtoverlay=justboom-dac,<param>=<val>
+Params: 24db_digital_gain       Allow gain to be applied via the PCM512x codec
+                                Digital volume control. Enable with
+                                "dtoverlay=justboom-dac,24db_digital_gain"
+                                (The default behaviour is that the Digital
+                                volume control is limited to a maximum of
+                                0dB. ie. it can attenuate but not provide
+                                gain. For most users, this will be desired
+                                as it will prevent clipping. By appending
+                                the 24dB_digital_gain parameter, the Digital
+                                volume control will allow up to 24dB of
+                                gain. If this parameter is enabled, it is the
+                                responsibility of the user to ensure that
+                                the Digital volume control is set to a value
+                                that does not result in clipping/distortion!)
+
+
+Name:   justboom-digi
+Info:   Configures the JustBoom Digi HAT and Digi Zero audio cards
+Load:   dtoverlay=justboom-digi
+Params: <None>
+
+
+Name:   lirc-rpi
+Info:   This overlay has been deprecated and removed - see gpio-ir
+Load:   <Deprecated>
+
+
+Name:   ltc294x
+Info:   Adds support for the ltc294x family of battery gauges
+Load:   dtoverlay=ltc294x,<param>=<val>
+Params: ltc2941                 Select the ltc2941 device
+
+        ltc2942                 Select the ltc2942 device
+
+        ltc2943                 Select the ltc2943 device
+
+        ltc2944                 Select the ltc2944 device
+
+        resistor-sense          The sense resistor value in milli-ohms.
+                                Can be a 32-bit negative value when the battery
+                                has been connected to the wrong end of the
+                                resistor.
+
+        prescaler-exponent      Range and accuracy of the gauge. The value is
+                                programmed into the chip only if it differs
+                                from the current setting.
+                                For LTC2941 only:
+                                - Default value is 128
+                                - the exponent is in the range 0-7 (default 7)
+                                See the datasheet for more information.
+
+
+Name:   max98357a
+Info:   Configures the Maxim MAX98357A I2S DAC
+Load:   dtoverlay=max98357a,<param>=<val>
+Params: no-sdmode               Driver does not manage the state of the DAC's
+                                SD_MODE pin (i.e. chip is always on).
+        sdmode-pin              integer, GPIO pin connected to the SD_MODE input
+                                of the DAC (default GPIO4 if parameter omitted).
+
+
+Name:   maxtherm
+Info:   Configure a MAX6675, MAX31855 or MAX31856 thermocouple as an IIO device.
+
+        For devices on spi1 or spi2, the interfaces should be enabled
+        with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays.
+        The overlay expects to disable the relevant spidev node, so also using
+        e.g. cs0_spidev=off is unnecessary.
+
+        Example:
+        MAX31855 on /dev/spidev0.0
+            dtoverlay=maxtherm,spi0-0,max31855
+        MAX31856 using a type J thermocouple on /dev/spidev2.1
+            dtoverlay=spi2-2cs
+            dtoverlay=maxtherm,spi2-1,max31856,type_j
+
+Load:   dtoverlay=maxtherm,<param>=<val>
+Params: spi<n>-<m>              Configure device at spi<n>, cs<m>
+                                (boolean, required)
+        max6675                 Enable support for the MAX6675 (default)
+        max31855                Enable support for the MAX31855
+        max31855e               Enable support for the MAX31855E
+        max31855j               Enable support for the MAX31855J
+        max31855k               Enable support for the MAX31855K
+        max31855n               Enable support for the MAX31855N
+        max31855r               Enable support for the MAX31855R
+        max31855s               Enable support for the MAX31855S
+        max31855t               Enable support for the MAX31855T
+        max31856                Enable support for the MAX31856 (with type K)
+        type_b                  Select a type B sensor for max31856
+        type_e                  Select a type E sensor for max31856
+        type_j                  Select a type J sensor for max31856
+        type_k                  Select a type K sensor for max31856
+        type_n                  Select a type N sensor for max31856
+        type_r                  Select a type R sensor for max31856
+        type_s                  Select a type S sensor for max31856
+        type_t                  Select a type T sensor for max31856
+
+
+Name:   mbed-dac
+Info:   Configures the mbed AudioCODEC (TLV320AIC23B)
+Load:   dtoverlay=mbed-dac
+Params: <None>
+
+
+Name:   mcp23017
+Info:   Configures the MCP23017 I2C GPIO expander
+Load:   dtoverlay=mcp23017,<param>=<val>
+Params: gpiopin                 Gpio pin connected to the INTA output of the
+                                MCP23017 (default: 4)
+
+        addr                    I2C address of the MCP23017 (default: 0x20)
+
+        i2c-bus                 Supports all the standard I2C bus selection
+                                parameters - see "dtoverlay -h i2c-bus"
+
+        mcp23008                Configure an MCP23008 instead.
+        noints                  Disable the interrupt GPIO line.
+
+
+Name:   mcp23s17
+Info:   Configures the MCP23S08/17 SPI GPIO expanders.
+        If devices are present on SPI1 or SPI2, those interfaces must be enabled
+        with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays.
+        If interrupts are enabled for a device on a given CS# on a SPI bus, that
+        device must be the only one present on that SPI bus/CS#.
+Load:   dtoverlay=mcp23s17,<param>=<val>
+Params: s08-spi<n>-<m>-present  4-bit integer, bitmap indicating MCP23S08
+                                devices present on SPI<n>, CS#<m>
+
+        s17-spi<n>-<m>-present  8-bit integer, bitmap indicating MCP23S17
+                                devices present on SPI<n>, CS#<m>
+
+        s08-spi<n>-<m>-int-gpio integer, enables interrupts on a single
+                                MCP23S08 device on SPI<n>, CS#<m>, specifies
+                                the GPIO pin to which INT output of MCP23S08
+                                is connected.
+
+        s17-spi<n>-<m>-int-gpio integer, enables mirrored interrupts on a
+                                single MCP23S17 device on SPI<n>, CS#<m>,
+                                specifies the GPIO pin to which either INTA
+                                or INTB output of MCP23S17 is connected.
+
+
+Name:   mcp2515
+Info:   Configures the MCP2515 CAN controller on spi0/1/2/3/5
+        For devices on spi1, spi2, spi3 or spi5, the interfaces should be
+        enabled with one of the spi<n>-1/2/3cs overlays.
+Load:   dtoverlay=mcp2515,<param>=<val>
+Params: spi<n>-<m>              Configure device at spi<n>, cs<m>
+                                (boolean, required)
+
+        oscillator              Clock frequency for the CAN controller (Hz)
+
+        speed                   Maximum SPI frequence (Hz)
+
+        interrupt               GPIO for interrupt signal
+
+
+Name:   mcp2515-can0
+Info:   Configures the MCP2515 CAN controller on spi0.0
+Load:   dtoverlay=mcp2515-can0,<param>=<val>
+Params: oscillator              Clock frequency for the CAN controller (Hz)
+
+        spimaxfrequency         Maximum SPI frequence (Hz)
+
+        interrupt               GPIO for interrupt signal
+
+
+Name:   mcp2515-can1
+Info:   Configures the MCP2515 CAN controller on spi0.1
+Load:   dtoverlay=mcp2515-can1,<param>=<val>
+Params: oscillator              Clock frequency for the CAN controller (Hz)
+
+        spimaxfrequency         Maximum SPI frequence (Hz)
+
+        interrupt               GPIO for interrupt signal
+
+
+Name:   mcp251xfd
+Info:   Configures the MCP251XFD CAN controller family
+        For devices on spi1 or spi2, the interfaces should be enabled
+        with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays.
+Load:   dtoverlay=mcp251xfd,<param>=<val>
+Params: spi<n>-<m>              Configure device at spi<n>, cs<m>
+                                (boolean, required)
+
+        oscillator              Clock frequency for the CAN controller (Hz)
+
+        speed                   Maximum SPI frequence (Hz)
+
+        interrupt               GPIO for interrupt signal
+
+        rx_interrupt            GPIO for RX interrupt signal (nINT1) (optional)
+
+        xceiver_enable          GPIO for CAN transceiver enable (optional)
+
+        xceiver_active_high     specifiy if CAN transceiver enable pin is
+                                active high (optional, default: active low)
+
+
+Name:   mcp3008
+Info:   Configures MCP3008 A/D converters
+        For devices on spi1 or spi2, the interfaces should be enabled
+        with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays.
+Load:   dtoverlay=mcp3008,<param>[=<val>]
+Params: spi<n>-<m>-present      boolean, configure device at spi<n>, cs<m>
+        spi<n>-<m>-speed        integer, set the spi bus speed for this device
+
+
+Name:   mcp3202
+Info:   Configures MCP3202 A/D converters
+        For devices on spi1 or spi2, the interfaces should be enabled
+        with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays.
+Load:   dtoverlay=mcp3202,<param>[=<val>]
+Params: spi<n>-<m>-present      boolean, configure device at spi<n>, cs<m>
+        spi<n>-<m>-speed        integer, set the spi bus speed for this device
+
+
+Name:   mcp342x
+Info:   Overlay for activation of Microchip MCP3421-3428 ADCs over I2C
+Load:   dtoverlay=mcp342x,<param>=<val>
+Params: addr                    I2C bus address of device, for devices with
+                                addresses that are configurable, e.g. by
+                                hardware links (default=0x68)
+        mcp3421                 The device is an MCP3421
+        mcp3422                 The device is an MCP3422
+        mcp3423                 The device is an MCP3423
+        mcp3424                 The device is an MCP3424
+        mcp3425                 The device is an MCP3425
+        mcp3426                 The device is an MCP3426
+        mcp3427                 The device is an MCP3427
+        mcp3428                 The device is an MCP3428
+
+
+Name:   media-center
+Info:   Media Center HAT - 2.83" Touch Display + extras by Pi Supply
+Load:   dtoverlay=media-center,<param>=<val>
+Params: speed                   Display SPI bus speed
+        rotate                  Display rotation {0,90,180,270}
+        fps                     Delay between frame updates
+        xohms                   Touchpanel sensitivity (X-plate resistance)
+        swapxy                  Swap x and y axis
+        backlight               Change backlight GPIO pin {e.g. 12, 18}
+        debug                   "on" = enable additional debug messages
+                                (default "off")
+
+
+Name:   merus-amp
+Info:   Configures the merus-amp audio card
+Load:   dtoverlay=merus-amp
+Params: <None>
+
+
+Name:   midi-uart0
+Info:   Configures UART0 (ttyAMA0) so that a requested 38.4kbaud actually gets
+        31.25kbaud, the frequency required for MIDI
+Load:   dtoverlay=midi-uart0
+Params: <None>
+
+
+Name:   midi-uart0-pi5
+Info:   See midi-uart0 (this is the Pi 5 version)
+
+
+Name:   midi-uart1
+Info:   Configures UART1 (ttyS0) so that a requested 38.4kbaud actually gets
+        31.25kbaud, the frequency required for MIDI
+Load:   dtoverlay=midi-uart1
+Params: <None>
+
+
+Name:   midi-uart1-pi5
+Info:   See midi-uart1 (this is the Pi 5 version)
+
+
+Name:   midi-uart2
+Info:   Configures UART2 (ttyAMA2) so that a requested 38.4kbaud actually gets
+        31.25kbaud, the frequency required for MIDI
+Load:   dtoverlay=midi-uart2
+Params: <None>
+
+
+Name:   midi-uart2-pi5
+Info:   See midi-uart2 (this is the Pi 5 version)
+
+
+Name:   midi-uart3
+Info:   Configures UART3 (ttyAMA3) so that a requested 38.4kbaud actually gets
+        31.25kbaud, the frequency required for MIDI
+Load:   dtoverlay=midi-uart3
+Params: <None>
+
+
+Name:   midi-uart3-pi5
+Info:   See midi-uart3 (this is the Pi 5 version)
+
+
+Name:   midi-uart4
+Info:   Configures UART4 (ttyAMA4) so that a requested 38.4kbaud actually gets
+        31.25kbaud, the frequency required for MIDI
+Load:   dtoverlay=midi-uart4
+Params: <None>
+
+
+Name:   midi-uart4-pi5
+Info:   See midi-uart4 (this is the Pi 5 version)
+
+
+Name:   midi-uart5
+Info:   Configures UART5 (ttyAMA5) so that a requested 38.4kbaud actually gets
+        31.25kbaud, the frequency required for MIDI
+Load:   dtoverlay=midi-uart5
+Params: <None>
+
+
+Name:   minipitft13
+Info:   Overlay for AdaFruit Mini Pi 1.3" TFT via SPI using fbtft driver.
+Load:   dtoverlay=minipitft13,<param>=<val>
+Params: speed                   SPI bus speed (default 32000000)
+        rotate                  Display rotation (0, 90, 180 or 270; default 0)
+        width                   Display width (default 240)
+        height                  Display height (default 240)
+        fps                     Delay between frame updates (default 25)
+        debug                   Debug output level (0-7; default 0)
+
+
+Name:   miniuart-bt
+Info:   Switch the onboard Bluetooth function of a BT-equipped Raspberry Pi
+        to use the mini-UART (ttyS0) and restore UART0/ttyAMA0 over GPIOs 14 &
+        15. Note that this option uses a lower baudrate, and should only be used
+        with low-bandwidth peripherals.
+Load:   dtoverlay=miniuart-bt,<param>=<val>
+Params: krnbt                   Set to "off" to disable autoprobing of Bluetooth
+                                driver without need of hciattach/btattach
+
+
+Name:   mipi-dbi-spi
+Info:   Overlay for SPI-connected MIPI DBI displays using the panel-mipi-dbi
+        driver. The driver will load a file /lib/firmware/panel.bin containing
+        the initialisation commands.
+
+        Example:
+          dtoverlay=mipi-dbi-spi,spi0-0,speed=70000000
+          dtparam=width=320,height=240
+          dtparam=reset-gpio=23,dc-gpio=24
+          dtparam=backlight-gpio=18
+
+        Compared to fbtft panel-mipi-dbi runs pixel data at spi-max-frequency
+        and init commands at 10MHz. This makes it possible to push the envelope
+        without messing up the controller configuration due to command
+        transmission errors.
+
+        For devices on spi1 or spi2, the interfaces should be enabled
+        with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays.
+
+        See https://github.com/notro/panel-mipi-dbi/wiki for more info.
+
+Load:   dtoverlay=mipi-dbi-spi,<param>=<val>
+Params:
+        compatible              Set the compatible string to load a different
+                                firmware file. Both the panel compatible value
+                                used to load the firmware file and the value
+                                used to load the driver has to be set having a
+                                NUL (\0) separator between them.
+                                Example:
+                                dtparam=compatible=mypanel\0panel-mipi-dbi-spi
+        spi<n>-<m>              Configure device at spi<n>, cs<m>
+                                (boolean, required)
+        speed                   SPI bus speed in Hz (default 32000000)
+        cpha                    Shifted SPI clock phase (CPHA) mode
+        cpol                    Inverse SPI clock polarity (CPOL) mode
+        write-only              Controller is not readable
+                                (ie. MISO is not wired up).
+
+        width                   Panel width in pixels (required)
+        height                  Panel height in pixels (required)
+        width-mm                Panel width in mm
+        height-mm               Panel height in mm
+        x-offset                Panel x-offset in controller RAM
+        y-offset                Panel y-offset in controller RAM
+
+        clock-frequency         Panel clock frequency in Hz
+                                (optional, just informational).
+
+        reset-gpio              GPIO pin to be used for RESET
+        dc-gpio                 GPIO pin to be used for D/C
+
+        backlight-gpio          GPIO pin to be used for backlight control
+                                (default of none).
+        backlight-pwm           PWM channel to be used for backlight control
+                                (default of none). NB Disables audio headphone
+                                output as that also uses PWM.
+        backlight-pwm-chan      Choose channel on &pwm node for backlight
+                                control (default 0).
+        backlight-pwm-gpio      GPIO pin to be used for the PWM backlight. See
+                                pwm-2chan for valid options (default 18).
+        backlight-pwm-func      Pin function of GPIO used for the PWM backlight.
+                                See pwm-2chan for valid options (default 2).
+        backlight-def-brightness
+                                Set the default brightness. Normal range 1-16.
+                                (default 16).
+
+
+Name:   mira220
+Info:   ams mira220 camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=mira220,<param>=<val>
+Params: rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 0)
+        orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default on)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+
+
+Name:   mlx90640
+Info:   Overlay for i2c connected mlx90640 thermal camera
+Load:   dtoverlay=mlx90640
+Params: <None>
+
+
+Name:   mmc
+Info:   Selects the bcm2835-mmc SD/MMC driver, optionally with overclock
+Load:   dtoverlay=mmc,<param>=<val>
+Params: overclock_50            Clock (in MHz) to use when the MMC framework
+                                requests 50MHz
+
+
+Name:   mpu6050
+Info:   This overlay has been deprecated - use "dtoverlay=i2c-sensor,mpu6050"
+        instead. Note that "int_pin" is the new name for the "interrupt"
+        parameter.
+Load:   <Deprecated>
+
+
+Name:   mz61581
+Info:   MZ61581 display by Tontec
+Load:   dtoverlay=mz61581,<param>=<val>
+Params: speed                   Display SPI bus speed
+
+        rotate                  Display rotation {0,90,180,270}
+
+        fps                     Delay between frame updates
+
+        txbuflen                Transmit buffer length (default 32768)
+
+        debug                   Debug output level {0-7}
+
+        xohms                   Touchpanel sensitivity (X-plate resistance)
+
+
+Name:   nospi10
+Info:   Disable the spi10 device on Pi5.
+Load:   dtoverlay=nospi10
+Params: <None>
+
+
+Name:   ov2311
+Info:   Omnivision OV2311 camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=ov2311,<param>=<val>
+Params: rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 0)
+        orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default on)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+
+
+Name:   ov5647
+Info:   Omnivision OV5647 camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=ov5647,<param>=<val>
+Params: rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 0)
+        orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default on)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+        vcm                     Configure a VCM focus drive on the sensor.
+
+
+Name:   ov64a40
+Info:   Arducam OV64A40 camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=ov64a40,<param>=<val>
+Params: rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 0)
+        orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default on)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+        vcm                     Select lens driver state. Default is enabled,
+                                but vcm=off will disable.
+        link-frequency          Allowable link frequency values to use in Hz:
+                                456000000 (default), 360000000
+
+
+Name:   ov7251
+Info:   Omnivision OV7251 camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=ov7251,<param>=<val>
+Params: rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 0)
+        orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default off)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+
+
+Name:   ov9281
+Info:   Omnivision OV9281 camera module.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=ov9281,<param>=<val>
+Params: rotation                Mounting rotation of the camera sensor (0 or
+                                180, default 0)
+        orientation             Sensor orientation (0 = front, 1 = rear,
+                                2 = external, default external)
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default on)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+        arducam                 Slow down the regulator for slow Arducam
+                                modules.
+        clk-continuous          Switch to continuous mode on the CSI clock lane,
+                                which increases the maximum frame rate slightly.
+                                Appears not to work on Pi3.
+        trigger-mode            Enable external trigger mode (0 = normal,
+                                1 = triggered). In this mode, the sensor outputs
+                                a frame only when triggered by a rising edge
+                                on the FSIN input pin.
+
+
+Name:   papirus
+Info:   PaPiRus ePaper Screen by Pi Supply (both HAT and pHAT)
+Load:   dtoverlay=papirus,<param>=<val>
+Params: panel                   Display panel (required):
+                                1.44": e1144cs021
+                                2.0":  e2200cs021
+                                2.7":  e2271cs021
+
+        speed                   Display SPI bus speed
+
+
+Name:   pca953x
+Info:   TI PCA953x family of I2C GPIO expanders. Default is for NXP PCA9534.
+Load:   dtoverlay=pca953x,<param>=<val>
+Params: addr                    I2C address of expander. Default 0x20.
+        i2c-bus                 Supports all the standard I2C bus selection
+                                parameters - see "dtoverlay -h i2c-bus"
+        pca6416                 Select the NXP PCA6416 (16 bit)
+        pca9505                 Select the NXP PCA9505 (40 bit)
+        pca9535                 Select the NXP PCA9535 (16 bit)
+        pca9536                 Select the NXP PCA9536 or TI PCA9536 (4 bit)
+        pca9537                 Select the NXP PCA9537 (4 bit)
+        pca9538                 Select the NXP PCA9538 (8 bit)
+        pca9539                 Select the NXP PCA9539 (16 bit)
+        pca9554                 Select the NXP PCA9554 (8 bit)
+        pca9555                 Select the NXP PCA9555 (16 bit)
+        pca9556                 Select the NXP PCA9556 (8 bit)
+        pca9557                 Select the NXP PCA9557 (8 bit)
+        pca9574                 Select the NXP PCA9574 (8 bit)
+        pca9575                 Select the NXP PCA9575 (16 bit)
+        pca9698                 Select the NXP PCA9698 (40 bit)
+        pcal6416                Select the NXP PCAL6416 (16 bit)
+        pcal6524                Select the NXP PCAL6524 (24 bit)
+        pcal9555a               Select the NXP PCAL9555A (16 bit)
+        max7310                 Select the Maxim MAX7310 (8 bit)
+        max7312                 Select the Maxim MAX7312 (16 bit)
+        max7313                 Select the Maxim MAX7313 (16 bit)
+        max7315                 Select the Maxim MAX7315 (8 bit)
+        pca6107                 Select the TI PCA6107 (8 bit)
+        tca6408                 Select the TI TCA6408 (8 bit)
+        tca6416                 Select the TI TCA6416 (16 bit)
+        tca6424                 Select the TI TCA6424 (24 bit)
+        tca9539                 Select the TI TCA9539 (16 bit)
+        tca9554                 Select the TI TCA9554 (8 bit)
+        cat9554                 Select the Onnn CAT9554 (8 bit)
+        pca9654                 Select the Onnn PCA9654 (8 bit)
+        xra1202                 Select the Exar XRA1202 (8 bit)
+
+
+Name:   pcf857x
+Info:   NXP PCF857x family of I2C GPIO expanders.
+Load:   dtoverlay=pcf857x,<param>=<val>
+Params: addr                    I2C address of expander. Default
+                                depends on model selected.
+        i2c-bus                 Supports all the standard I2C bus selection
+                                parameters - see "dtoverlay -h i2c-bus"
+        pcf8574                 Select the NXP PCF8574 (8 bit)
+        pcf8574a                Select the NXP PCF8574A (8 bit)
+        pcf8575                 Select the NXP PCF8575 (16 bit)
+        pca8574                 Select the NXP PCA8574 (8 bit)
+
+
+Name:   pcie-32bit-dma
+Info:   Force PCIe config to support 32bit DMA addresses at the expense of
+        having to bounce buffers.
+Load:   dtoverlay=pcie-32bit-dma
+Params: <None>
+
+
+Name:   pcie-32bit-dma-pi5
+Info:   Force PCIe config to support 32bit DMA addresses at the expense of
+        having to bounce buffers (on the Pi 5).
+Load:   dtoverlay=pcie-32bit-dma-pi5
+Params: <None>
+
+
+Name:   pciex1-compat-pi5
+Info:   Compatibility features for pciex1 on Pi 5.
+Load:   dtoverlay=pciex1-compat-pi5,<param>=<val>
+Params: l1ss                    Enable RC ASPM L1 sub-state support. Requires
+                                that the CLKREQ# pin is connected to the
+                                endpoint.
+        no-l0s                  Disable ASPM L0s
+        no-mip                  Revert to the MSI target in the RC, instead of
+                                the MSI-MIP peripheral. Use if a) more than 8
+                                interrupt vectors are required or b) the EP
+                                requires DMA and MSI addresses to be 32bit.
+        mmio-hi                 Move the start of outbound 32bit addresses to
+                                2GB and expand 64bit outbound space to 14GB.
+
+
+[ The pcf2127-rtc overlay has been deleted. See i2c-rtc. ]
+
+
+[ The pcf8523-rtc overlay has been deleted. See i2c-rtc. ]
+
+
+[ The pcf8563-rtc overlay has been deleted. See i2c-rtc. ]
+
+
+Name:   pi3-act-led
+Info:   This overlay has been renamed act-led, keeping pi3-act-led as an alias
+        for backwards compatibility.
+Load:   <Deprecated>
+
+
+Name:   pi3-disable-bt
+Info:   This overlay has been renamed disable-bt, keeping pi3-disable-bt as an
+        alias for backwards compatibility.
+Load:   <Deprecated>
+
+
+Name:   pi3-disable-wifi
+Info:   This overlay has been renamed disable-wifi, keeping pi3-disable-wifi as
+        an alias for backwards compatibility.
+Load:   <Deprecated>
+
+
+Name:   pi3-miniuart-bt
+Info:   This overlay has been renamed miniuart-bt, keeping pi3-miniuart-bt as
+        an alias for backwards compatibility.
+Load:   <Deprecated>
+
+
+Name:   pibell
+Info:   Configures the pibell audio card.
+Load:   dtoverlay=pibell,<param>=<val>
+Params: alsaname                Set the name as it appears in ALSA (default
+                                "PiBell")
+
+
+Name:   pifacedigital
+Info:   Configures the PiFace Digital mcp23s17 GPIO port expander.
+Load:   dtoverlay=pifacedigital,<param>=<val>
+Params: spi-present-mask        8-bit integer, bitmap indicating MCP23S17 SPI0
+                                CS0 address. PiFace Digital supports addresses
+                                0-3, which can be configured with JP1 and JP2.
+
+
+Name:   pifi-40
+Info:   Configures the PiFi 40W stereo amplifier
+Load:   dtoverlay=pifi-40
+Params: <None>
+
+
+Name:   pifi-dac-hd
+Info:   Configures the PiFi DAC HD
+Load:   dtoverlay=pifi-dac-hd
+Params: <None>
+
+
+Name:   pifi-dac-zero
+Info:   Configures the PiFi DAC Zero
+Load:   dtoverlay=pifi-dac-zero
+Params: <None>
+
+
+Name:   pifi-mini-210
+Info:   Configures the PiFi Mini stereo amplifier
+Load:   dtoverlay=pifi-mini-210
+Params: <None>
+
+
+Name:   piglow
+Info:   Configures the PiGlow by pimoroni.com
+Load:   dtoverlay=piglow
+Params: <None>
+
+
+Name:   pimidi
+Info:   Configures the Blokas Labs Pimidi card
+Load:   dtoverlay=pimidi,<param>=<val>
+Params: sel                     The position used for the sel rotary switch.
+                                Each unit in the stack must be set on a unique
+                                position. If param is omitted, sel=0 is assumed.
+
+
+Name:   pineboards-hat-ai
+Info:   Pineboards Hat Ai! overlay for the Google Coral Edge TPU
+Load:   dtoverlay=pineboards-hat-ai
+Params: <None>
+
+
+Name:   pineboards-hatdrive-poe-plus
+Info:   Configures the Pineboards HatDrive! PoE+
+Load:   dtoverlay=pineboards-hatdrive-poe-plus
+Params: <None>
+
+
+Name:   piscreen
+Info:   PiScreen display by OzzMaker.com
+Load:   dtoverlay=piscreen,<param>=<val>
+Params: speed                   Display SPI bus speed
+
+        rotate                  Display rotation {0,90,180,270}
+
+        fps                     Delay between frame updates
+
+        debug                   Debug output level {0-7}
+
+        xohms                   Touchpanel sensitivity (X-plate resistance)
+
+        drm                     Select the DRM/KMS driver instead of the FBTFT
+                                one
+
+        invx                    Touchscreen inverted x axis
+
+        invy                    Touchscreen inverted y axis
+
+        swapxy                  Touchscreen swapped x y axis
+
+
+Name:   piscreen2r
+Info:   PiScreen 2 with resistive TP display by OzzMaker.com
+Load:   dtoverlay=piscreen2r,<param>=<val>
+Params: speed                   Display SPI bus speed
+
+        rotate                  Display rotation {0,90,180,270}
+
+        fps                     Delay between frame updates
+
+        debug                   Debug output level {0-7}
+
+        xohms                   Touchpanel sensitivity (X-plate resistance)
+
+
+Name:   pisound
+Info:   Configures the Blokas Labs Pisound card
+Load:   dtoverlay=pisound
+Params: <None>
+
+
+Name:   pisound-micro
+Info:   Configures the Blokas Labs Pisound Micro card
+Load:   dtoverlay=pisound-micro,<param>=<val>
+Params: input-mode              'differential' (default) or 'single-ended'.
+                                Configures the input pin mode.
+        hp-out-mode             'capless-headphone' (default), 'headphone' or
+                                'line-out'. Configures the HP output mode.
+        line-out-mode           'line-out' (default) or 'headphone'. Configures
+                                the LINE_OUT pins for different impedances.
+        mono-out-mode           'line-out' (default) or 'headphone'. Configures
+                                the MONO_OUT pin mode for different impedances.
+                                Only available when hp-out-mode is not
+                                'capless-headphone'.
+
+
+Name:   pisound-pi5
+Info:   Pi 5 specific overlay override for Blokas Labs Pisound card, see pisound
+Load:   dtoverlay=pisound-pi5
+Params: <None>
+
+
+Name:   pitft22
+Info:   Adafruit PiTFT 2.2" screen
+Load:   dtoverlay=pitft22,<param>=<val>
+Params: speed                   Display SPI bus speed
+
+        rotate                  Display rotation {0,90,180,270}
+
+        fps                     Delay between frame updates
+
+        debug                   Debug output level {0-7}
+
+        drm                     Force the use of the mi0283qt DRM driver (by
+                                default the ili9340 framebuffer driver will
+                                be used in preference if available)
+
+
+Name:   pitft28-capacitive
+Info:   Adafruit PiTFT 2.8" capacitive touch screen
+Load:   dtoverlay=pitft28-capacitive,<param>=<val>
+Params: speed                   Display SPI bus speed
+
+        rotate                  Display rotation {0,90,180,270}
+
+        fps                     Delay between frame updates
+
+        debug                   Debug output level {0-7}
+
+        drm                     Force the use of the mi0283qt DRM driver (by
+                                default the ili9340 framebuffer driver will
+                                be used in preference if available)
+
+        touch-sizex             Touchscreen size x (default 240)
+
+        touch-sizey             Touchscreen size y (default 320)
+
+        touch-invx              Touchscreen inverted x axis
+
+        touch-invy              Touchscreen inverted y axis
+
+        touch-swapxy            Touchscreen swapped x y axis
+
+
+Name:   pitft28-resistive
+Info:   Adafruit PiTFT 2.8" resistive touch screen
+Load:   dtoverlay=pitft28-resistive,<param>=<val>
+Params: speed                   Display SPI bus speed
+
+        rotate                  Display rotation {0,90,180,270}
+
+        fps                     Delay between frame updates
+
+        debug                   Debug output level {0-7}
+
+        drm                     Force the use of the mi0283qt DRM driver (by
+                                default the ili9340 framebuffer driver will
+                                be used in preference if available)
+
+        touch-invx              Touchscreen inverted x axis
+
+        touch-invy              Touchscreen inverted y axis
+
+        touch-swapxy            Touchscreen swapped x y axis
+
+
+Name:   pitft35-resistive
+Info:   Adafruit PiTFT 3.5" resistive touch screen
+Load:   dtoverlay=pitft35-resistive,<param>=<val>
+Params: speed                   Display SPI bus speed
+
+        rotate                  Display rotation {0,90,180,270}
+
+        fps                     Delay between frame updates
+
+        debug                   Debug output level {0-7}
+
+        drm                     Force the use of the hx8357d DRM driver (by
+                                default the fb_hx8357d framebuffer driver will
+                                be used in preference if available)
+
+        touch-invx              Touchscreen inverted x axis
+
+        touch-invy              Touchscreen inverted y axis
+
+        touch-swapxy            Touchscreen swapped x y axis
+
+
+Name:   pivision
+Info:   Overlay to enable the SECO Pi Vision 10.1 HMI hardware for Compute
+        Module 5. Will be loaded automatically by up-to-date firmware via
+        EEPROM detection.
+Load:   dtoverlay=pivision
+Params: <None>
+
+
+Name:   pps-gpio
+Info:   Configures the pps-gpio (pulse-per-second time signal via GPIO).
+Load:   dtoverlay=pps-gpio,<param>=<val>
+Params: gpiopin                 Input GPIO (default "18")
+        assert_falling_edge     When present, assert is indicated by a falling
+                                edge, rather than by a rising edge (default
+                                off)
+        capture_clear           Generate clear events on the trailing edge
+                                (default off)
+        pull                    Desired pull-up/down state (off, down, up)
+                                Default is "off".
+
+
+Name:   pps-rp1
+
+Info:   Configures the pps-gpio kernel module for use with the RP1 found on the
+        RPi 5 (pulse-per-second time signal via GPIO).
+
+Load:   dtoverlay=pps-rp1,<param>=<val>
+
+Params: pin                     (integer) Specifies the PPS input pin.
+                                Takes the pin number correlating to the RPi
+                                40-pin GPIO header.
+                                Defaults to GPIO 18.
+
+        pull-none               (boolean) Disable pull-up/down resistors on the
+                                PPS input pin.
+
+        pull-up                 (boolean) Enable pull-up resistor on the PPS
+                                input pin.
+
+        pull-down               (boolean) Enable pull-down resistor on the PPS
+                                input pin.
+
+        schmitt-trigger         (boolean) Enable the Schmitt-trigger on the PPS
+                                input pin.
+                                Defaults to off.
+
+        echo-pin                (integer) Specifies the PPS echo output pin.
+                                Takes the pin number correlating to the RPi
+                                40-pin GPIO header.
+                                Defaults to GPIO 17.
+
+        echo-ms                 (integer) Specifies the pulse-width in ms of the
+                                echo signal on the PPS echo output pin.
+                                Must be less than 999.
+                                Defaults to 10ms.
+
+        echo                    (boolean) Enables the PPS echo output signal.
+                                Defaults to off.
+
+        capture-clear           (boolean) Captures the clear event time.
+                                Defaults to off.
+
+        assert-falling-edge     (boolean) Trigger on the falling signal edge
+                                instead of the rising signal edge.
+                                Defaults to off.
+
+
+Name:   proto-codec
+Info:   Configures the PROTO Audio Codec card
+Load:   dtoverlay=proto-codec
+Params: <None>
+
+
+Name:   pwm
+Info:   Configures a single PWM channel
+        Legal pin,function combinations for each channel:
+          PWM0: 12,4(Alt0) 18,2(Alt5) 40,4(Alt0)            52,5(Alt1)
+          PWM1: 13,4(Alt0) 19,2(Alt5) 41,4(Alt0) 45,4(Alt0) 53,5(Alt1)
+        N.B.:
+          1) Pin 18 is the only one available on all platforms, and
+             it is the one used by the I2S audio interface.
+             Pins 12 and 13 might be better choices on an A+, B+ or Pi2.
+          2) The onboard analogue audio output uses both PWM channels.
+          3) So be careful mixing audio and PWM.
+          4) Currently the clock must have been enabled and configured
+             by other means.
+Load:   dtoverlay=pwm,<param>=<val>
+Params: pin                     Output pin (default 18) - see table
+        func                    Pin function (default 2 = Alt5) - see above
+        clock                   PWM clock frequency (informational)
+
+
+Name:   pwm-2chan
+Info:   Configures both PWM channels
+        Legal pin,function combinations for each channel:
+          PWM0: 12,4(Alt0) 18,2(Alt5) 40,4(Alt0)            52,5(Alt1)
+          PWM1: 13,4(Alt0) 19,2(Alt5) 41,4(Alt0) 45,4(Alt0) 53,5(Alt1)
+        N.B.:
+          1) Pin 18 is the only one available on all platforms, and
+             it is the one used by the I2S audio interface.
+             Pins 12 and 13 might be better choices on an A+, B+ or Pi2.
+          2) The onboard analogue audio output uses both PWM channels.
+          3) So be careful mixing audio and PWM.
+          4) Currently the clock must have been enabled and configured
+             by other means.
+Load:   dtoverlay=pwm-2chan,<param>=<val>
+Params: pin                     Output pin (default 18) - see table
+        pin2                    Output pin for other channel (default 19)
+        func                    Pin function (default 2 = Alt5) - see above
+        func2                   Function for pin2 (default 2 = Alt5)
+        clock                   PWM clock frequency (informational)
+
+
+Name:   pwm-gpio
+Info:   Configures the software PWM GPIO driver
+Load:   dtoverlay=pwm-gpio,<param>=<val>
+Params: gpio                    Output pin (default 4)
+
+
+Name:   pwm-gpio-fan
+Info:   Configure a GPIO connected PWM cooling fan controlled by the
+        software-based GPIO PWM kernel module
+Load:   dtoverlay=pwm-gpio-fan,<param>=<val>
+Params: fan_gpio                BCM number of the pin driving the fan,
+                                default 18 (GPIO 18)
+        fan_temp0               CPU temperature at which fan is started with
+                                low speed in millicelsius,
+                                default 55000 (55 °C)
+        fan_temp1               CPU temperature at which fan is switched
+                                to medium speed in millicelsius,
+                                default 60000 (60 °C)
+        fan_temp2               CPU temperature at which fan is switched
+                                to high speed in millicelsius,
+                                default 67500 (67.5 °C)
+        fan_temp3               CPU temperature at which fan is switched
+                                to max speed in millicelsius,
+                                default 75000 (75 °C)
+        fan_temp0_hyst          Temperature hysteris at which fan is stopped
+                                in millicelsius,default 5000 (resulting
+                                in 50 °C)
+        fan_temp1_hyst          Temperature hysteris at which fan is switched
+                                back to low speed in millicelsius,
+                                default 5000 (resulting in 55 °C)
+        fan_temp2_hyst          Temperature hysteris at which fan is switched
+                                back to medium speed in millicelsius,
+                                default 5000 (resulting in 62.5 °C)
+        fan_temp3_hyst          Temperature hysteris at which fan is switched
+                                back to high speed in millicelsius,
+                                default 5000 (resulting in 70 °C)
+        fan_temp0_speed         Fan speed for low cooling state in range
+                                0 to 255, default 114 (45% PWM duty cycle)
+        fan_temp1_speed         Fan speed for medium cooling state in range
+                                0 to 255, default 152 (60% PWM duty cycle)
+        fan_temp2_speed         Fan speed for high cooling state in range
+                                0 to 255, default 204 (80% PWM duty cycle)
+        fan_temp3_speed         Fan speed for max cooling state in range
+                                0 to 255, default 255 (100% PWM duty cycle)
+
+
+Name:   pwm-ir-tx
+Info:   Use GPIO pin as pwm-assisted infrared transmitter output.
+        This is an alternative to "gpio-ir-tx". pwm-ir-tx makes use
+        of PWM0 to reduce the CPU load during transmission compared to
+        gpio-ir-tx which uses bit-banging.
+        Legal pin,function combinations are:
+          12,4(Alt0) 18,2(Alt5) 40,4(Alt0) 52,5(Alt1)
+Load:   dtoverlay=pwm-ir-tx,<param>=<val>
+Params: gpio_pin                Output GPIO (default 18)
+
+        func                    Pin function (default 2 = Alt5)
+
+
+Name:   pwm-pio
+Info:   Configures a GPIO pin as PIO-assisted PWM output. Unlike hardware PWM,
+        this can be used on any RP1 GPIO in bank 0 (0-27). Up to 4 are
+        supported, assuming nothing else is using PIO. Pi 5 only.
+Load:   dtoverlay=pwm-pio,<param>=<val>
+Params: gpio                    Output GPIO (0-27, default 4)
+
+
+Name:   pwm1
+Info:   Configures one or two PWM channel on PWM1 (BCM2711 only)
+        N.B.:
+          1) The onboard analogue audio output uses both PWM channels.
+          2) So be careful mixing audio and PWM.
+        Note that even when only one pin is enabled, both channels are available
+        from the PWM driver, so be careful to use the correct one.
+Load:   dtoverlay=pwm1,<param>=<val>
+Params: clock                   PWM clock frequency (informational)
+        pins_40                 Enable channel 0 (PWM1_0) on GPIO 40
+        pins_41                 Enable channel 1 (PWM1_1) on GPIO 41
+        pins_40_41              Enable channels 0 (PWM1_0) and 1 (PW1_1) on
+                                GPIOs 40 and 41 (default)
+        pull_up                 Enable pull-ups on the PWM pins (default)
+        pull_down               Enable pull-downs on the PWM pins
+        pull_off                Disable pulls on the PWM pins
+
+
+Name:   qca7000
+Info:   Evaluation Board for PLC Stamp micro
+        This uses spi0 and a separate GPIO interrupt to connect the QCA7000.
+Load:   dtoverlay=qca7000,<param>=<val>
+Params: int_pin                 GPIO pin for interrupt signal (default 23)
+
+        speed                   SPI bus speed (default 12 MHz)
+
+
+Name:   qca7000-uart0
+Info:   Evaluation Board for PLC Stamp micro (UART)
+        This uses uart0/ttyAMA0 over GPIOs 14 & 15 to connect the QCA7000.
+        But it requires disabling of onboard Bluetooth on
+        Pi 3B, 3B+, 3A+, 4B and Zero W.
+Load:   dtoverlay=qca7000-uart0,<param>=<val>
+Params: baudrate                Set the baudrate for the UART (default
+                                "115200")
+
+
+Name:   ramoops
+Info:   Enable the preservation of crash logs across a reboot. With
+        systemd-pstore enabled (as it is on Raspberry Pi OS) the crash logs
+        are moved to /var/lib/systemd/pstore/ on reboot.
+Load:   dtoverlay=ramoops,<param>=<val>
+Params: base-addr               Where to place the capture buffer (default
+                                0x0b000000)
+        total-size              How much memory to allocate altogether (in
+                                bytes - default 64kB)
+        record-size             How much space to use for each capture, i.e.
+                                total-size / record-size = number of captures
+                                (default 16kB)
+        console-size            Size of non-panic dmesg captures (default 0)
+
+
+Name:   ramoops-pi4
+Info:   The version of the ramoops overlay for the Pi 4 family. It should be
+        loaded automatically if dtoverlay=ramoops is specified on a Pi 4.
+Load:   dtoverlay=ramoops-pi4,<param>=<val>
+Params: base-addr               Where to place the capture buffer (default
+                                0x0b000000)
+        total-size              How much memory to allocate altogether (in
+                                bytes - default 64kB)
+        record-size             How much space to use for each capture, i.e.
+                                total-size / record-size = number of captures
+                                (default 16kB)
+        console-size            Size of non-panic dmesg captures (default 0)
+
+
+Name:   rootmaster
+Info:   Overlay for OpenHydroponics RootMaster board.
+        https://openhydroponics.com/hw/rootmaster
+Load:   dtoverlay=rootmaster
+Params: <None>
+
+
+Name:   rotary-encoder
+Info:   Overlay for GPIO connected rotary encoder.
+Load:   dtoverlay=rotary-encoder,<param>=<val>
+Params: pin_a                   GPIO connected to rotary encoder channel A
+                                (default 4).
+        pin_b                   GPIO connected to rotary encoder channel B
+                                (default 17).
+        relative_axis           register a relative axis rather than an
+                                absolute one. Relative axis will only
+                                generate +1/-1 events on the input device,
+                                hence no steps need to be passed.
+        linux_axis              the input subsystem axis to map to this
+                                rotary encoder. Defaults to 0 (ABS_X / REL_X)
+        rollover                Automatic rollover when the rotary value
+                                becomes greater than the specified steps or
+                                smaller than 0. For absolute axis only.
+        steps-per-period        Number of steps (stable states) per period.
+                                The values have the following meaning:
+                                1: Full-period mode (default)
+                                2: Half-period mode
+                                4: Quarter-period mode
+        steps                   Number of steps in a full turnaround of the
+                                encoder. Only relevant for absolute axis.
+                                Defaults to 24 which is a typical value for
+                                such devices.
+        wakeup                  Boolean, rotary encoder can wake up the
+                                system.
+        encoding                String, the method used to encode steps.
+                                Supported are "gray" (the default and more
+                                common) and "binary".
+
+
+Name:   rpi-backlight
+Info:   Raspberry Pi official display backlight driver
+Load:   dtoverlay=rpi-backlight
+Params: <None>
+
+
+Name:   rpi-cirrus-wm5102
+Info:   This overlay has been renamed to cirrus-wm5102
+Load:   <Deprecated>
+
+
+Name:   rpi-codeczero
+Info:   Configures the Raspberry Pi Codec Zero sound card
+Load:   dtoverlay=rpi-codeczero
+Params: <None>
+
+
+Name:   rpi-dac
+Info:   This overlay has been renamed to i2s-dac.
+Load:   <Deprecated>
+
+
+Name:   rpi-dacplus
+Info:   Configures the Raspberry Pi DAC+ card
+Load:   dtoverlay=rpi-dacplus,<param>=<val>
+Params: 24db_digital_gain       Allow gain to be applied via the PCM512x codec
+                                digital volume control. Enable by adding
+                                "dtparam=24db_digital_gain" to config.txt
+                                before any "dtoverlay" lines.
+                                The default behaviour is that the digital
+                                volume control is limited to a maximum of
+                                0dB. ie. it can attenuate but not provide
+                                gain. For most users, this will be desired
+                                as it will prevent clipping. By appending
+                                the 24db_digital_gain parameter, the digital
+                                volume control will allow up to 24dB of
+                                gain. If this parameter is enabled, it is the
+                                responsibility of the user to ensure that
+                                the digital volume control is set to a value
+                                that does not result in clipping/distortion!
+
+
+Name:   rpi-dacpro
+Info:   Configures the Raspberry Pi DAC Pro sound card
+Load:   dtoverlay=rpi-dacpro,<param>=<val>
+Params: 24db_digital_gain       Allow gain to be applied via the PCM512x codec
+                                digital volume control. Enable by adding
+                                "dtparam=24db_digital_gain" to config.txt
+                                before any "dtoverlay" lines.
+                                The default behaviour is that the digital
+                                volume control is limited to a maximum of
+                                0dB. ie. it can attenuate but not provide
+                                gain. For most users, this will be desired
+                                as it will prevent clipping. By appending
+                                the 24db_digital_gain parameter, the digital
+                                volume control will allow up to 24dB of
+                                gain. If this parameter is enabled, it is the
+                                responsibility of the user to ensure that
+                                the digital volume control is set to a value
+                                that does not result in clipping/distortion!
+
+
+Name:   rpi-digiampplus
+Info:   Configures the Raspberry Pi DigiAMP+ sound card
+Load:   dtoverlay=rpi-digiampplus,<param>=<val>
+Params: 24db_digital_gain       Allow gain to be applied via the PCM512x codec
+                                digital volume control. Enable by adding
+                                "dtparam=24db_digital_gain" to config.txt
+                                before any "dtoverlay" lines.
+                                The default behaviour is that the digital
+                                volume control is limited to a maximum of
+                                0dB. ie. it can attenuate but not provide
+                                gain. For most users, this will be desired
+                                as it will prevent clipping. By appending
+                                the 24db_digital_gain parameter, the digital
+                                volume control will allow up to 24dB of
+                                gain. If this parameter is enabled, it is the
+                                responsibility of the user to ensure that
+                                the digital volume control is set to a value
+                                that does not result in clipping/distortion!
+        auto_mute_amp           If specified, unmute/mute the DigiAMP+ when
+                                starting/stopping audio playback (default "on").
+        unmute_amp              If specified, unmute the DigiAMP+ amp once when
+                                the DAC driver module loads (default "off").
+
+
+Name:   rpi-display
+Info:   This overlay has been renamed to watterott-display
+Load:   <Deprecated>
+
+
+Name:   rpi-ft5406
+Info:   Official Raspberry Pi display touchscreen
+Load:   dtoverlay=rpi-ft5406,<param>=<val>
+Params: touchscreen-size-x      Touchscreen X resolution (default 800)
+        touchscreen-size-y      Touchscreen Y resolution (default 480);
+        touchscreen-inverted-x  Invert touchscreen X coordinates (default 0);
+        touchscreen-inverted-y  Invert touchscreen Y coordinates (default 0);
+        touchscreen-swapped-x-y Swap X and Y cordinates (default 0);
+
+
+Name:   rpi-fw-uart
+Info:   Configures the firmware software UART driver.
+        This driver requires exclusive usage of the second VPU core. The
+        following config.txt entries should be set when this driver is used.
+        dtparam=audio=off
+        isp_use_vpu0=1
+Load:   dtoverlay=rpi-fw-uart,<param>[=<val>]
+Params: txd0_pin                GPIO pin for TXD0 (any free - default 20)
+
+        rxd0_pin                GPIO pin for RXD0 (any free - default 21)
+
+
+Name:   rpi-poe
+Info:   Raspberry Pi PoE HAT fan
+Load:   dtoverlay=rpi-poe,<param>[=<val>]
+Params: poe_fan_temp0           Temperature (in millicelcius) at which the fan
+                                turns on (default 40000)
+        poe_fan_temp0_hyst      Temperature delta (in millicelcius) at which
+                                the fan turns off (default 2000)
+        poe_fan_temp1           Temperature (in millicelcius) at which the fan
+                                speeds up (default 45000)
+        poe_fan_temp1_hyst      Temperature delta (in millicelcius) at which
+                                the fan slows down (default 2000)
+        poe_fan_temp2           Temperature (in millicelcius) at which the fan
+                                speeds up (default 50000)
+        poe_fan_temp2_hyst      Temperature delta (in millicelcius) at which
+                                the fan slows down (default 2000)
+        poe_fan_temp3           Temperature (in millicelcius) at which the fan
+                                speeds up (default 55000)
+        poe_fan_temp3_hyst      Temperature delta (in millicelcius) at which
+                                the fan slows down (default 5000)
+        i2c                     Control the fan via Linux I2C drivers instead of
+                                the firmware.
+
+
+Name:   rpi-poe-plus
+Info:   Raspberry Pi PoE+ HAT fan
+Load:   dtoverlay=rpi-poe-plus,<param>[=<val>]
+Params: poe_fan_temp0           Temperature (in millicelcius) at which the fan
+                                turns on (default 40000)
+        poe_fan_temp0_hyst      Temperature delta (in millicelcius) at which
+                                the fan turns off (default 2000)
+        poe_fan_temp1           Temperature (in millicelcius) at which the fan
+                                speeds up (default 45000)
+        poe_fan_temp1_hyst      Temperature delta (in millicelcius) at which
+                                the fan slows down (default 2000)
+        poe_fan_temp2           Temperature (in millicelcius) at which the fan
+                                speeds up (default 50000)
+        poe_fan_temp2_hyst      Temperature delta (in millicelcius) at which
+                                the fan slows down (default 2000)
+        poe_fan_temp3           Temperature (in millicelcius) at which the fan
+                                speeds up (default 55000)
+        poe_fan_temp3_hyst      Temperature delta (in millicelcius) at which
+                                the fan slows down (default 5000)
+        i2c                     Control the fan via Linux I2C drivers instead of
+                                the firmware.
+
+
+Name:   rpi-power-hat-b
+Info:   Raspberry Pi Power HAT Bottom
+Load:   dtoverlay=rpi-power-hat-b
+Params: <None>
+
+
+Name:   rpi-power-hat-t
+Info:   Raspberry Pi Power HAT Top
+Load:   dtoverlay=rpi-power-hat-t
+Params: <None>
+
+
+Name:   rpi-proto
+Info:   This overlay has been renamed to proto-codec.
+Load:   <Deprecated>
+
+
+Name:   rpi-sense
+Info:   Raspberry Pi Sense HAT
+Load:   dtoverlay=rpi-sense
+Params: <None>
+
+
+Name:   rpi-sense-v2
+Info:   Raspberry Pi Sense HAT v2
+Load:   dtoverlay=rpi-sense-v2
+Params: <None>
+
+
+Name:   rpi-tv
+Info:   Raspberry Pi TV HAT
+Load:   dtoverlay=rpi-tv
+Params: <None>
+
+
+Name:   rpivid-v4l2
+Info:   This overlay has been deprecated and deleted as the V4L2 stateless
+        video decoder driver is enabled by default.
+Load:   <Deprecated>
+
+
+Name:   rra-digidac1-wm8741-audio
+Info:   Configures the Red Rocks Audio DigiDAC1 soundcard
+Load:   dtoverlay=rra-digidac1-wm8741-audio
+Params: <None>
+
+
+Name:   sainsmart18
+Info:   Overlay for the SPI-connected Sainsmart 1.8" display (based on the
+        ST7735R chip).
+Load:   dtoverlay=sainsmart18,<param>=<val>
+Params: rotate                  Display rotation {0,90,180,270}
+        speed                   SPI bus speed in Hz (default 4000000)
+        fps                     Display frame rate in Hz
+        bgr                     Enable BGR mode (default off)
+        debug                   Debug output level {0-7}
+        dc_pin                  GPIO pin for D/C (default 24)
+        reset_pin               GPIO pin for RESET (default 25)
+
+
+Name:   sc16is750-i2c
+Info:   Overlay for the NXP SC16IS750 UART with I2C Interface
+        Enables the chip on I2C1 at 0x48 (or the "addr" parameter value). To
+        select another address, please refer to table 10 in reference manual.
+Load:   dtoverlay=sc16is750-i2c,<param>=<val>
+Params: int_pin                 GPIO used for IRQ (default 24)
+        addr                    Address (default 0x48)
+        i2c-bus                 Supports all the standard I2C bus selection
+                                parameters - see "dtoverlay -h i2c-bus"
+        xtal                    On-board crystal frequency (default 14745600)
+
+
+Name:   sc16is750-spi0
+Info:   This overlay is now deprecated. Use
+        "dtoverlay=sc16is75x-spi,sc16is750,spi0-0,..." instead.
+Load:   <Deprecated>
+
+
+Name:   sc16is752-i2c
+Info:   Overlay for the NXP SC16IS752 dual UART with I2C Interface
+        Enables the chip on I2C1 at 0x48 (or the "addr" parameter value). To
+        select another address, please refer to table 10 in reference manual.
+Load:   dtoverlay=sc16is752-i2c,<param>=<val>
+Params: int_pin                 GPIO used for IRQ (default 24)
+        addr                    Address (default 0x48)
+        i2c-bus                 Supports all the standard I2C bus selection
+                                parameters - see "dtoverlay -h i2c-bus"
+        xtal                    On-board crystal frequency (default 14745600)
+
+
+Name:   sc16is752-spi0
+Info:   This overlay is now deprecated. Use
+        "dtoverlay=sc16is75x-spi,sc16is752,spi0-0,..." instead.
+Load:   <Deprecated>
+
+
+Name:   sc16is752-spi1
+Info:   This overlay is now deprecated. Use
+        "dtoverlay=sc16is75x-spi,sc16is752,spi1-1cs,spi1-0,..." instead.
+Load:   <Deprecated>
+
+
+Name:   sc16is75x-spi
+Info:   Overlay for the NXP SC16IS750/2 (Dual) UART with SPI Interface
+        Enables the chip on SPI.
+
+Load:   dtoverlay=sc16is75x-spi,<param>=<val>
+Params: sc16is750               Device is a SC16IS750 UART (default on)
+        sc16is752               Device is a SC16IS752 Dual UART
+        spi<n>-<m>              Configure device at spi<n>, cs<m>
+                                (boolean, required)
+        int_pin                 GPIO used for IRQ (default 24)
+        xtal                    On-board crystal frequency (default 14745600)
+        spi1-1cs                A special case to support sc16is752-spi1. Not
+                                intended for general use.
+
+
+Name:   sdhost
+Info:   Selects the bcm2835-sdhost SD/MMC driver, optionally with overclock.
+        N.B. This overlay is designed for situations where the mmc driver is
+        the default, so it disables the other (mmc) interface - this will kill
+        WLAN on a Pi3. If this isn't what you want, either use the sdtweak
+        overlay or the new sd_* dtparams of the base DTBs.
+Load:   dtoverlay=sdhost,<param>=<val>
+Params: overclock_50            Clock (in MHz) to use when the MMC framework
+                                requests 50MHz
+
+        force_pio               Disable DMA support (default off)
+
+        pio_limit               Number of blocks above which to use DMA
+                                (default 1)
+
+        debug                   Enable debug output (default off)
+
+
+Name:   sdio
+Info:   Selects the bcm2835-sdhost SD/MMC driver, optionally with overclock,
+        and enables SDIO via GPIOs 22-27. An example of use in 1-bit mode is
+        "dtoverlay=sdio,bus_width=1,gpios_22_25"
+Load:   dtoverlay=sdio,<param>=<val>
+Params: sdio_overclock          SDIO Clock (in MHz) to use when the MMC
+                                framework requests 50MHz
+
+        poll_once               Disable SDIO-device polling every second
+                                (default on: polling once at boot-time)
+
+        bus_width               Set the SDIO host bus width (default 4 bits)
+
+        gpios_22_25             Select GPIOs 22-25 for 1-bit mode. Must be used
+                                with bus_width=1. This replaces the sdio-1bit
+                                overlay, which is now deprecated.
+
+        gpios_34_37             Select GPIOs 34-37 for 1-bit mode. Must be used
+                                with bus_width=1.
+
+        gpios_34_39             Select GPIOs 34-39 for 4-bit mode. Must be used
+                                with bus_width=4 (the default).
+
+
+Name:   sdio-1bit
+Info:   This overlay is now deprecated. Use
+        "dtoverlay=sdio,bus_width=1,gpios_22_25" instead.
+Load:   <Deprecated>
+
+
+Name:   sdio-pi5
+Info:   Selects the rp1_mmc0 interface and enables it on GPIOs 22-27.
+        Pi 5 only.
+Load:   dtoverlay=sdio-pi5
+Params: <None>
+
+
+Name:   sdtweak
+Info:   This overlay is now deprecated. Use the sd_* dtparams in the
+        base DTB, e.g. "dtoverlay=sdtweak,poll_once" becomes
+        "dtparam=sd_poll_once".
+Load:   <Deprecated>
+
+
+Name:   seeed-can-fd-hat-v1
+Info:   Overlay for Seeed Studio CAN BUS FD HAT with two CAN FD
+        channels without RTC. Use this overlay if your HAT has no
+        battery holder.
+        https://www.seeedstudio.com/2-Channel-CAN-BUS-FD-Shield-for-Raspberry-Pi-p-4072.html
+Load:   dtoverlay=seeed-can-fd-hat-v1
+Params: <None>
+
+
+Name:   seeed-can-fd-hat-v2
+Info:   Overlay for Seeed Studio CAN BUS FD HAT with two CAN FD
+        channels and an RTC. Use this overlay if your HAT has a
+        battery holder.
+        https://www.seeedstudio.com/CAN-BUS-FD-HAT-for-Raspberry-Pi-p-4742.html
+Load:   dtoverlay=seeed-can-fd-hat-v2
+Params: <None>
+
+
+Name:   sh1106-spi
+Info:   Overlay for SH1106 OLED via SPI using fbtft staging driver.
+Load:   dtoverlay=sh1106-spi,<param>=<val>
+Params: speed                   SPI bus speed (default 4000000)
+        rotate                  Display rotation (0, 90, 180 or 270; default 0)
+        fps                     Delay between frame updates (default 25)
+        debug                   Debug output level (0-7; default 0)
+        dc_pin                  GPIO pin for D/C (default 24)
+        reset_pin               GPIO pin for RESET (default 25)
+        height                  Display height (32 or 64; default 64)
+
+
+Name:   si446x-spi0
+Info:   Overlay for Si446x UHF Transceiver via SPI using si446x driver.
+        The driver is currently out-of-tree at
+        https://github.com/sunipkmukherjee/silabs.git
+Load:   dtoverlay=si446x-spi0,<param>=<val>
+Params: speed                   SPI bus speed (default 4000000)
+        int_pin                 GPIO pin for interrupts (default 17)
+        reset_pin               GPIO pin for RESET (default 27)
+
+
+Name:   smi
+Info:   Enables the Secondary Memory Interface peripheral. Uses GPIOs 2-25!
+Load:   dtoverlay=smi
+Params: <None>
+
+
+Name:   smi-dev
+Info:   Enables the userspace interface for the SMI driver
+Load:   dtoverlay=smi-dev
+Params: <None>
+
+
+Name:   smi-nand
+Info:   Enables access to NAND flash via the SMI interface
+Load:   dtoverlay=smi-nand
+Params: <None>
+
+
+Name:   spi-gpio35-39
+Info:   Move SPI function block to GPIO 35 to 39
+Load:   dtoverlay=spi-gpio35-39
+Params: <None>
+
+
+Name:   spi-gpio40-45
+Info:   Move SPI function block to GPIOs 40 to 45
+Load:   dtoverlay=spi-gpio40-45
+Params: <None>
+
+
+Name:   spi-rtc
+Info:   Adds support for a number of SPI Real Time Clock devices
+Load:   dtoverlay=spi-rtc,<param>=<val>
+Params: ds3232                  Select the DS3232 device
+        ds3234                  Select the DS3234 device
+        pcf2123                 Select the PCF2123 device
+
+        spi0_0                  Use spi0.0 (default)
+        spi0_1                  Use spi0.1
+        spi1_0                  Use spi1.0
+        spi1_1                  Use spi1.1
+        spi2_0                  Use spi2.0
+        spi2_1                  Use spi2.1
+        cs_high                 This device requires an active-high CS
+
+
+Name:   spi0-0cs
+Info:   Don't claim any CS pins for SPI0. Requires a device with its chip
+        select permanently enabled, but frees a GPIO for e.g. a DPI display.
+Load:   dtoverlay=spi0-0cs,<param>=<val>
+Params: no_miso                 Don't claim and use the MISO pin (9), freeing
+                                it for other uses.
+
+
+Name:   spi0-1cs
+Info:   Only use one CS pin for SPI0
+Load:   dtoverlay=spi0-1cs,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 8)
+        no_miso                 Don't claim and use the MISO pin (9), freeing
+                                it for other uses.
+
+
+Name:   spi0-1cs-inverted
+Info:   Only use one CS pin for SPI0 and set to active-high
+Load:   dtoverlay=spi0-1cs-inverted,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 8)
+        no_miso                 Don't claim and use the MISO pin (9), freeing
+                                it for other uses.
+
+
+Name:   spi0-2cs
+Info:   Change the CS pins for SPI0
+Load:   dtoverlay=spi0-2cs,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 8)
+        cs1_pin                 GPIO pin for CS1 (default 7)
+        no_miso                 Don't claim and use the MISO pin (9), freeing
+                                it for other uses.
+
+
+Name:   spi0-cs
+Info:   This overlay has been renamed spi0-2cs, keeping spi0-cs as an
+        alias for backwards compatibility.
+Load:   <Deprecated>
+
+
+Name:   spi0-hw-cs
+Info:   This overlay has been deprecated and removed because it is no longer
+        necessary and has been seen to prevent spi0 from working.
+Load:   <Deprecated>
+
+
+Name:   spi1-1cs
+Info:   Enables spi1 with a single chip select (CS) line and associated spidev
+        dev node. The gpio pin number for the CS line and spidev device node
+        creation are configurable.
+        N.B.: spi1 is not accessible on old Pis without a 40-pin header.
+Load:   dtoverlay=spi1-1cs,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 18 - BCM SPI1_CE0).
+        cs0_spidev              Set to 'off' to stop the creation of a
+                                userspace device node /dev/spidev1.0 (default
+                                is 'on' or enabled).
+
+
+Name:   spi1-2cs
+Info:   Enables spi1 with two chip select (CS) lines and associated spidev
+        dev nodes. The gpio pin numbers for the CS lines and spidev device node
+        creation are configurable.
+        N.B.: spi1 is not accessible on old Pis without a 40-pin header.
+Load:   dtoverlay=spi1-2cs,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 18 - BCM SPI1_CE0).
+        cs1_pin                 GPIO pin for CS1 (default 17 - BCM SPI1_CE1).
+        cs0_spidev              Set to 'off' to stop the creation of a
+                                userspace device node /dev/spidev1.0 (default
+                                is 'on' or enabled).
+        cs1_spidev              Set to 'off' to stop the creation of a
+                                userspace device node /dev/spidev1.1 (default
+                                is 'on' or enabled).
+
+
+Name:   spi1-3cs
+Info:   Enables spi1 with three chip select (CS) lines and associated spidev
+        dev nodes. The gpio pin numbers for the CS lines and spidev device node
+        creation are configurable.
+        N.B.: spi1 is not accessible on old Pis without a 40-pin header.
+Load:   dtoverlay=spi1-3cs,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 18 - BCM SPI1_CE0).
+        cs1_pin                 GPIO pin for CS1 (default 17 - BCM SPI1_CE1).
+        cs2_pin                 GPIO pin for CS2 (default 16 - BCM SPI1_CE2).
+        cs0_spidev              Set to 'off' to stop the creation of a
+                                userspace device node /dev/spidev1.0 (default
+                                is 'on' or enabled).
+        cs1_spidev              Set to 'off' to stop the creation of a
+                                userspace device node /dev/spidev1.1 (default
+                                is 'on' or enabled).
+        cs2_spidev              Set to 'off' to stop the creation of a
+                                userspace device node /dev/spidev1.2 (default
+                                is 'on' or enabled).
+
+
+Name:   spi2-1cs
+Info:   Enables spi2 on GPIOs 40-42 with a single chip select (CS) line and
+        associated spidev dev node. The gpio pin number for the CS line and
+        spidev device node creation are configurable. spi2-2cs-pi5 is
+        substituted on a Pi 5.
+        N.B.: spi2 is only accessible with the Compute Module or Pi 5.
+Load:   dtoverlay=spi2-1cs,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 43 - BCM SPI2_CE0).
+        cs0_spidev              Set to 'off' to stop the creation of a
+                                userspace device node /dev/spidev2.0 (default
+                                is 'on' or enabled).
+
+
+Name:   spi2-1cs-pi5
+Info:   Enables spi2 on GPIOs 1-3 with a single chip select (CS) line and
+        associated spidev dev node. The gpio pin number for the CS line and
+        spidev device node creation are configurable. Pi 5 only.
+Load:   dtoverlay=spi2-1cs-pi5,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 0).
+        cs0_spidev              Set to 'off' to stop the creation of a
+                                userspace device node /dev/spidev2.0 (default
+                                is 'on' or enabled).
+
+
+Name:   spi2-2cs
+Info:   Enables spi2 on GPIOs 40-42 with two chip select (CS) lines and
+        associated spidev dev nodes. The gpio pin numbers for the CS lines and
+        spidev device node creation are configurable. spi2-2cs-pi5 is
+        substituted on a Pi 5.
+        N.B.: spi2 is only accessible with the Compute Module or Pi 5.
+Load:   dtoverlay=spi2-2cs,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 43 - BCM SPI2_CE0).
+        cs1_pin                 GPIO pin for CS1 (default 44 - BCM SPI2_CE1).
+        cs0_spidev              Set to 'off' to stop the creation of a
+                                userspace device node /dev/spidev2.0 (default
+                                is 'on' or enabled).
+        cs1_spidev              Set to 'off' to stop the creation of a
+                                userspace device node /dev/spidev2.1 (default
+                                is 'on' or enabled).
+
+
+Name:   spi2-2cs-pi5
+Info:   Enables spi2 on GPIOs 1-3 with two chip select (CS) lines and
+        associated spidev dev nodes. The gpio pin numbers for the CS lines and
+        spidev device node creation are configurable. Pi 5 only.
+Load:   dtoverlay=spi2-2cs-pi5,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 0).
+        cs1_pin                 GPIO pin for CS1 (default 24).
+        cs0_spidev              Set to 'off' to stop the creation of a
+                                userspace device node /dev/spidev2.0 (default
+                                is 'on' or enabled).
+        cs1_spidev              Set to 'off' to stop the creation of a
+                                userspace device node /dev/spidev2.1 (default
+                                is 'on' or enabled).
+
+
+Name:   spi2-3cs
+Info:   Enables spi2 on GPIOs 40-42 with three chip select (CS) lines and
+        associated spidev dev nodes. The gpio pin numbers for the CS lines and
+        spidev device node creation are configurable.
+        N.B.: spi2 is only accessible with the Compute Module or Pi 5.
+Load:   dtoverlay=spi2-3cs,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 43 - BCM SPI2_CE0).
+        cs1_pin                 GPIO pin for CS1 (default 44 - BCM SPI2_CE1).
+        cs2_pin                 GPIO pin for CS2 (default 45 - BCM SPI2_CE2).
+        cs0_spidev              Set to 'off' to stop the creation of a
+                                userspace device node /dev/spidev2.0 (default
+                                is 'on' or enabled).
+        cs1_spidev              Set to 'off' to stop the creation of a
+                                userspace device node /dev/spidev2.1 (default
+                                is 'on' or enabled).
+        cs2_spidev              Set to 'off' to stop the creation of a
+                                userspace device node /dev/spidev2.2 (default
+                                is 'on' or enabled).
+
+
+Name:   spi3-1cs
+Info:   Enables spi3 on GPIOs 1-3 with a single chip select (CS) line and
+        associated spidev dev node. The gpio pin number for the CS line and
+        spidev device node creation are configurable. BCM2711 only,
+        spi3-1cs-pi5 is substituted on Pi 5.
+Load:   dtoverlay=spi3-1cs,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 0 - BCM SPI3_CE0).
+        cs0_spidev              Set to 'off' to prevent the creation of a
+                                userspace device node /dev/spidev3.0 (default
+                                is 'on' or enabled).
+
+
+Name:   spi3-1cs-pi5
+Info:   Enables spi3 on GPIOs 5-7 with a single chip select (CS) line and
+        associated spidev dev node. The gpio pin number for the CS line and
+        spidev device node creation are configurable. Pi 5 only.
+Load:   dtoverlay=spi3-1cs-pi5,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 4).
+        cs0_spidev              Set to 'off' to prevent the creation of a
+                                userspace device node /dev/spidev3.0 (default
+                                is 'on' or enabled).
+
+
+Name:   spi3-2cs
+Info:   Enables spi3 on GPIO2 1-3 with two chip select (CS) lines and
+        associated spidev dev nodes. The gpio pin numbers for the CS lines and
+        spidev device node creation are configurable. BCM2711 only,
+        spi3-2cs-pi5 is substituted on Pi 5.
+Load:   dtoverlay=spi3-2cs,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 0 - BCM SPI3_CE0).
+        cs1_pin                 GPIO pin for CS1 (default 24 - BCM SPI3_CE1).
+        cs0_spidev              Set to 'off' to prevent the creation of a
+                                userspace device node /dev/spidev3.0 (default
+                                is 'on' or enabled).
+        cs1_spidev              Set to 'off' to prevent the creation of a
+                                userspace device node /dev/spidev3.1 (default
+                                is 'on' or enabled).
+
+
+Name:   spi3-2cs-pi5
+Info:   Enables spi3 on GPIOs 5-7 with two chip select (CS) lines and
+        associated spidev dev nodes. The gpio pin numbers for the CS lines and
+        spidev device node creation are configurable. Pi 5 only.
+Load:   dtoverlay=spi3-2cs-pi5,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 4).
+        cs1_pin                 GPIO pin for CS1 (default 25).
+        cs0_spidev              Set to 'off' to prevent the creation of a
+                                userspace device node /dev/spidev3.0 (default
+                                is 'on' or enabled).
+        cs1_spidev              Set to 'off' to prevent the creation of a
+                                userspace device node /dev/spidev3.1 (default
+                                is 'on' or enabled).
+
+
+Name:   spi4-1cs
+Info:   Enables spi4 on GPIOs 5-7 with a single chip select (CS) line and
+        associated spidev dev node. The gpio pin number for the CS line and
+        spidev device node creation are configurable. BCM2711 only.
+Load:   dtoverlay=spi4-1cs,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 4 - BCM SPI4_CE0).
+        cs0_spidev              Set to 'off' to prevent the creation of a
+                                userspace device node /dev/spidev4.0 (default
+                                is 'on' or enabled).
+
+
+Name:   spi4-2cs
+Info:   Enables spi4 on GPIOs 5-6 with two chip select (CS) lines and
+        associated spidev dev nodes. The gpio pin numbers for the CS lines and
+        spidev device node creation are configurable. BCM2711 only.
+Load:   dtoverlay=spi4-2cs,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 4 - BCM SPI4_CE0).
+        cs1_pin                 GPIO pin for CS1 (default 25 - BCM SPI4_CE1).
+        cs0_spidev              Set to 'off' to prevent the creation of a
+                                userspace device node /dev/spidev4.0 (default
+                                is 'on' or enabled).
+        cs1_spidev              Set to 'off' to prevent the creation of a
+                                userspace device node /dev/spidev4.1 (default
+                                is 'on' or enabled).
+
+
+Name:   spi5-1cs
+Info:   Enables spi5 on GPIOs 13-15 with a single chip select (CS) line and
+        associated spidev dev node. The gpio pin numbers for the CS lines and
+        spidev device node creation are configurable. BCM2711 and Pi 5.
+Load:   dtoverlay=spi5-1cs,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 12).
+        cs0_spidev              Set to 'off' to prevent the creation of a
+                                userspace device node /dev/spidev5.0 (default
+                                is 'on' or enabled).
+
+
+Name:   spi5-1cs-pi5
+Info:   See spi5-1cs
+
+
+Name:   spi5-2cs
+Info:   Enables spi5 on GPIOs 13-15 with two chip select (CS) lines and
+        associated spidev dev nodes. The gpio pin numbers for the CS lines and
+        spidev device node creation are configurable. BCM2711 and Pi 5.
+Load:   dtoverlay=spi5-2cs,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 12).
+        cs1_pin                 GPIO pin for CS1 (default 26).
+        cs0_spidev              Set to 'off' to prevent the creation of a
+                                userspace device node /dev/spidev5.0 (default
+                                is 'on' or enabled).
+        cs1_spidev              Set to 'off' to prevent the creation of a
+                                userspace device node /dev/spidev5.1 (default
+                                is 'on' or enabled).
+
+
+Name:   spi5-2cs-pi5
+Info:   See spi5-2cs
+
+
+Name:   spi6-1cs
+Info:   Enables spi6 with a single chip select (CS) line and associated spidev
+        dev node. The gpio pin number for the CS line and spidev device node
+        creation are configurable. BCM2711 only.
+Load:   dtoverlay=spi6-1cs,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 18 - BCM SPI6_CE0).
+        cs0_spidev              Set to 'off' to prevent the creation of a
+                                userspace device node /dev/spidev6.0 (default
+                                is 'on' or enabled).
+
+
+Name:   spi6-2cs
+Info:   Enables spi6 with two chip select (CS) lines and associated spidev
+        dev nodes. The gpio pin numbers for the CS lines and spidev device node
+        creation are configurable. BCM2711 only.
+Load:   dtoverlay=spi6-2cs,<param>=<val>
+Params: cs0_pin                 GPIO pin for CS0 (default 18 - BCM SPI6_CE0).
+        cs1_pin                 GPIO pin for CS1 (default 27 - BCM SPI6_CE1).
+        cs0_spidev              Set to 'off' to prevent the creation of a
+                                userspace device node /dev/spidev6.0 (default
+                                is 'on' or enabled).
+        cs1_spidev              Set to 'off' to prevent the creation of a
+                                userspace device node /dev/spidev6.1 (default
+                                is 'on' or enabled).
+
+
+Name:   ssd1306
+Info:   Overlay for activation of SSD1306 over I2C OLED display framebuffer.
+Load:   dtoverlay=ssd1306,<param>=<val>
+Params: address                 Location in display memory of first character.
+                                (default=0)
+        width                   Width of display. (default=128)
+        height                  Height of display. (default=64)
+        offset                  virtual channel a. (default=0)
+        normal                  Has no effect on displays tested. (default=not
+                                set)
+        sequential              Set this if every other scan line is missing.
+                                (default=not set)
+        remapped                Set this if display is garbled. (default=not
+                                set)
+        inverted                Set this if display is inverted and mirrored.
+                                (default=not set)
+
+        Examples:
+        Typical usage for 128x64 display: dtoverlay=ssd1306,inverted
+
+        Typical usage for 128x32 display: dtoverlay=ssd1306,inverted,sequential
+
+        i2c_baudrate=400000 will speed up the display.
+
+        i2c_baudrate=1000000 seems to work even though it's not officially
+        supported by the hardware, and is faster still.
+
+        For more information refer to the device datasheet at:
+        https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf
+
+
+Name:   ssd1306-spi
+Info:   Overlay for SSD1306 OLED via SPI using fbtft staging driver.
+Load:   dtoverlay=ssd1306-spi,<param>=<val>
+Params: speed                   SPI bus speed (default 10000000)
+        rotate                  Display rotation (0, 90, 180 or 270; default 0)
+        fps                     Delay between frame updates (default 25)
+        debug                   Debug output level (0-7; default 0)
+        dc_pin                  GPIO pin for D/C (default 24)
+        reset_pin               GPIO pin for RESET (default 25)
+        height                  Display height (32 or 64; default 64)
+        inverted                Set this if display is inverted and mirrored.
+                                (default=not set)
+
+
+Name:   ssd1327-spi
+Info:   Overlay for SSD1327 OLED via SPI using the DRM ssd130x driver.
+Load:   dtoverlay=ssd1327-spi,<param>=<val>
+Params: speed                   SPI bus speed (default 4500000)
+        dc_pin                  GPIO pin for D/C (default 24)
+        reset_pin               GPIO pin for RESET (default 25)
+
+
+Name:   ssd1331-spi
+Info:   Overlay for SSD1331 OLED via SPI using fbtft staging driver.
+Load:   dtoverlay=ssd1331-spi,<param>=<val>
+Params: speed                   SPI bus speed (default 4500000)
+        rotate                  Display rotation (0, 90, 180 or 270; default 0)
+        fps                     Delay between frame updates (default 25)
+        debug                   Debug output level (0-7; default 0)
+        dc_pin                  GPIO pin for D/C (default 24)
+        reset_pin               GPIO pin for RESET (default 25)
+
+
+Name:   ssd1351-spi
+Info:   Overlay for SSD1351 OLED via SPI using fbtft staging driver.
+Load:   dtoverlay=ssd1351-spi,<param>=<val>
+Params: speed                   SPI bus speed (default 4500000)
+        rotate                  Display rotation (0, 90, 180 or 270; default 0)
+        fps                     Delay between frame updates (default 25)
+        debug                   Debug output level (0-7; default 0)
+        dc_pin                  GPIO pin for D/C (default 24)
+        reset_pin               GPIO pin for RESET (default 25)
+
+
+Name:   sunfounder-pipower3
+Info:   Overlay for SunFounder PiPower 3
+Load:   dtoverlay=sunfounder-pipower3,<param>=<val>
+Params: poweroff_pin            Change poweroff pin (default 26)
+
+
+Name:   sunfounder-pironman5
+Info:   Overlay for SunFounder Pironman 5
+Load:   dtoverlay=sunfounder-pironman5,<param>=<val>
+Params: ir                      Enable IR or not (on or off, default on)
+        ir_pins                 Change IR receiver pin (default 13)
+
+
+Name:   superaudioboard
+Info:   Configures the SuperAudioBoard sound card
+Load:   dtoverlay=superaudioboard,<param>=<val>
+Params: gpiopin                 GPIO pin for codec reset
+
+
+Name:   sx150x
+Info:   Configures the Semtech SX150X I2C GPIO expanders.
+Load:   dtoverlay=sx150x,<param>=<val>
+Params: sx150<x>-<n>-<m>        Enables SX150X device on I2C#<n> with slave
+                                address <m>. <x> may be 1-9. <n> may be 0 or 1.
+                                Permissible values of <m> (which is denoted in
+                                hex) depend on the device variant. For SX1501,
+                                SX1502, SX1504 and SX1505, <m> may be 20 or 21.
+                                For SX1503 and SX1506, <m> may be 20. For
+                                SX1507 and SX1509, <m> may be 3E, 3F, 70 or 71.
+                                For SX1508, <m> may be 20, 21, 22 or 23.
+
+        sx150<x>-<n>-<m>-int-gpio
+                                Integer, enables interrupts on SX150X device on
+                                I2C#<n> with slave address <m>, specifies
+                                the GPIO pin to which NINT output of SX150X is
+                                connected.
+
+
+Name:   tc358743
+Info:   Toshiba TC358743 HDMI to CSI-2 bridge chip.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=tc358743,<param>=<val>
+Params: 4lane                   Use 4 lanes (only applicable to Compute Modules
+                                CAM1 connector).
+
+        link-frequency          Set the link frequency. Only values of 297000000
+                                (574Mbit/s) and 486000000 (972Mbit/s - default)
+                                are supported by the driver.
+        media-controller        Configure use of Media Controller API for
+                                configuring the sensor (default off)
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+
+
+Name:   tc358743-audio
+Info:   Used in combination with the tc358743-fast overlay to route the audio
+        from the TC358743 over I2S to the Pi.
+        Wiring is LRCK/WFS to GPIO 19, BCK/SCK to GPIO 18, and DATA/SD to GPIO
+        20.
+Load:   dtoverlay=tc358743-audio,<param>=<val>
+Params: card-name               Override the default, "tc358743", card name.
+
+
+Name:   tc358743-pi5
+Info:   Toshiba TC358743 HDMI to CSI-2 bridge chip for Pi5.
+        Uses Unicam 1, which is the standard camera connector on most Pi
+        variants.
+Load:   dtoverlay=tc358743-pi5,<param>=<val>
+Params: 4lane                   Use 4 lanes (only applicable to Compute Modules
+                                CAM1 connector).
+
+        link-frequency          Set the link frequency. Only values of 297000000
+                                (574Mbit/s) and 486000000 (972Mbit/s - default)
+                                are supported by the driver.
+        cam0                    Adopt the default configuration for CAM0 on a
+                                Compute Module (CSI0, i2c_vc, and cam0_reg).
+
+
+Name:   tinylcd35
+Info:   3.5" Color TFT Display by www.tinylcd.com
+        Options: Touch, RTC, keypad
+Load:   dtoverlay=tinylcd35,<param>=<val>
+Params: speed                   Display SPI bus speed
+
+        rotate                  Display rotation {0,90,180,270}
+
+        fps                     Delay between frame updates
+
+        debug                   Debug output level {0-7}
+
+        touch                   Enable touch panel
+
+        touchgpio               Touch controller IRQ GPIO
+
+        xohms                   Touchpanel: Resistance of X-plate in ohms
+
+        rtc-pcf                 PCF8563 Real Time Clock
+
+        rtc-ds                  DS1307 Real Time Clock
+
+        keypad                  Enable keypad
+
+        Examples:
+            Display with touchpanel, PCF8563 RTC and keypad:
+                dtoverlay=tinylcd35,touch,rtc-pcf,keypad
+            Old touch display:
+                dtoverlay=tinylcd35,touch,touchgpio=3
+
+
+Name:   tpm-slb9670
+Info:   Enables support for Infineon SLB9670 Trusted Platform Module add-on
+        boards, which can be used as a secure key storage and hwrng,
+        available as "Iridium SLB9670" by Infineon and "LetsTrust TPM" by pi3g.
+Load:   dtoverlay=tpm-slb9670
+Params: <None>
+
+
+Name:   tpm-slb9673
+Info:   Enables support for Infineon SLB9673 Trusted Platform Module add-on
+        boards, which can be used as a secure key storage and hwrng
+        via the I2C protocol.
+Load:   dtoverlay=tpm-slb9673
+Params: <None>
+
+
+Name:   uart0
+Info:   Change the pin usage of uart0
+Load:   dtoverlay=uart0,<param>=<val>
+Params: txd0_pin                GPIO pin for TXD0 (14, 32 or 36 - default 14)
+
+        rxd0_pin                GPIO pin for RXD0 (15, 33 or 37 - default 15)
+
+        pin_func                Alternative pin function - 4(Alt0) for 14&15,
+                                7(Alt3) for 32&33, 6(Alt2) for 36&37
+
+
+Name:   uart0-pi5
+Info:   Enable uart 0 on GPIOs 14-15. Pi 5 only.
+Load:   dtoverlay=uart0-pi5,<param>
+Params: ctsrts                  Enable CTS/RTS on GPIOs 16-17 (default off)
+        rs485                   Enable RS485 mode for using the RTS line to
+                                drive the OE pin of an RS485 transceiver (i.e.
+                                MAX3078E); also enables the UARTx ctsrts
+                                parameter, as RTS is required (default off).
+        rs485_full_duplex       When RS485 mode is enabled, enables receiving of
+                                data while sending data (default off)
+        rs485_invert_rts        When RS485 mode is enabled, inverts the RTS
+                                line from active-high (default) to active-low.
+        rs485_rts_on_delay      When RS485 mode is enabled, sets the delay (in
+                                milliseconds) between data transmission starting
+                                and the RTS line being asserted (default 0).
+        rs485_rts_off_delay     When RS485 mode is enabled, sets the delay (in
+                                milliseconds) between data transmission ending
+                                and the RTS line being deasserted (default 0).
+
+
+Name:   uart1
+Info:   Change the pin usage of uart1
+Load:   dtoverlay=uart1,<param>=<val>
+Params: txd1_pin                GPIO pin for TXD1 (14, 32 or 40 - default 14)
+
+        rxd1_pin                GPIO pin for RXD1 (15, 33 or 41 - default 15)
+
+
+Name:   uart1-pi5
+Info:   Enable uart 1 on GPIOs 0-1. Pi 5 only.
+Load:   dtoverlay=uart1-pi5,<param>
+Params: ctsrts                  Enable CTS/RTS on GPIOs 2-3 (default off)
+        rs485                   Enable RS485 mode for using the RTS line to
+                                drive the OE pin of an RS485 transceiver (i.e.
+                                MAX3078E); also enables the UARTx ctsrts
+                                parameter, as RTS is required (default off).
+        rs485_full_duplex       When RS485 mode is enabled, enables receiving of
+                                data while sending data (default off)
+        rs485_invert_rts        When RS485 mode is enabled, inverts the RTS
+                                line from active-high (default) to active-low.
+        rs485_rts_on_delay      When RS485 mode is enabled, sets the delay (in
+                                milliseconds) between data transmission starting
+                                and the RTS line being asserted (default 0).
+        rs485_rts_off_delay     When RS485 mode is enabled, sets the delay (in
+                                milliseconds) between data transmission ending
+                                and the RTS line being deasserted (default 0).
+
+
+Name:   uart2
+Info:   Enable uart 2 on GPIOs 0-3. BCM2711 only.
+Load:   dtoverlay=uart2,<param>
+Params: ctsrts                  Enable CTS/RTS on GPIOs 2-3 (default off)
+        rs485                   Enable RS485 mode for using the RTS line to
+                                drive the OE pin of an RS485 transceiver (i.e.
+                                MAX3078E); also enables the UARTx ctsrts
+                                parameter, as RTS is required (default off).
+        rs485_full_duplex       When RS485 mode is enabled, enables receiving of
+                                data while sending data (default off)
+        rs485_invert_rts        When RS485 mode is enabled, inverts the RTS
+                                line from active-high (default) to active-low.
+        rs485_rts_on_delay      When RS485 mode is enabled, sets the delay (in
+                                milliseconds) between data transmission starting
+                                and the RTS line being asserted (default 0).
+        rs485_rts_off_delay     When RS485 mode is enabled, sets the delay (in
+                                milliseconds) between data transmission ending
+                                and the RTS line being deasserted (default 0).
+
+
+Name:   uart2-pi5
+Info:   Enable uart 2 on GPIOs 4-5. Pi 5 only.
+Load:   dtoverlay=uart2-pi5,<param>
+Params: ctsrts                  Enable CTS/RTS on GPIOs 6-7 (default off)
+        rs485                   Enable RS485 mode for using the RTS line to
+                                drive the OE pin of an RS485 transceiver (i.e.
+                                MAX3078E); also enables the UARTx ctsrts
+                                parameter, as RTS is required (default off).
+        rs485_full_duplex       When RS485 mode is enabled, enables receiving of
+                                data while sending data (default off)
+        rs485_invert_rts        When RS485 mode is enabled, inverts the RTS
+                                line from active-high (default) to active-low.
+        rs485_rts_on_delay      When RS485 mode is enabled, sets the delay (in
+                                milliseconds) between data transmission starting
+                                and the RTS line being asserted (default 0).
+        rs485_rts_off_delay     When RS485 mode is enabled, sets the delay (in
+                                milliseconds) between data transmission ending
+                                and the RTS line being deasserted (default 0).
+
+
+Name:   uart3
+Info:   Enable uart 3 on GPIOs 4-7. BCM2711 only.
+Load:   dtoverlay=uart3,<param>
+Params: ctsrts                  Enable CTS/RTS on GPIOs 6-7 (default off)
+        rs485                   Enable RS485 mode for using the RTS line to
+                                drive the OE pin of an RS485 transceiver (i.e.
+                                MAX3078E); also enables the UARTx ctsrts
+                                parameter, as RTS is required (default off).
+        rs485_full_duplex       When RS485 mode is enabled, enables receiving of
+                                data while sending data (default off)
+        rs485_invert_rts        When RS485 mode is enabled, inverts the RTS
+                                line from active-high (default) to active-low.
+        rs485_rts_on_delay      When RS485 mode is enabled, sets the delay (in
+                                milliseconds) between data transmission starting
+                                and the RTS line being asserted (default 0).
+        rs485_rts_off_delay     When RS485 mode is enabled, sets the delay (in
+                                milliseconds) between data transmission ending
+                                and the RTS line being deasserted (default 0).
+
+
+Name:   uart3-pi5
+Info:   Enable uart 3 on GPIOs 8-9. Pi 5 only.
+Load:   dtoverlay=uart3-pi5,<param>
+Params: ctsrts                  Enable CTS/RTS on GPIOs 10-11 (default off)
+        rs485                   Enable RS485 mode for using the RTS line to
+                                drive the OE pin of an RS485 transceiver (i.e.
+                                MAX3078E); also enables the UARTx ctsrts
+                                parameter, as RTS is required (default off).
+        rs485_full_duplex       When RS485 mode is enabled, enables receiving of
+                                data while sending data (default off)
+        rs485_invert_rts        When RS485 mode is enabled, inverts the RTS
+                                line from active-high (default) to active-low.
+        rs485_rts_on_delay      When RS485 mode is enabled, sets the delay (in
+                                milliseconds) between data transmission starting
+                                and the RTS line being asserted (default 0).
+        rs485_rts_off_delay     When RS485 mode is enabled, sets the delay (in
+                                milliseconds) between data transmission ending
+                                and the RTS line being deasserted (default 0).
+
+
+Name:   uart4
+Info:   Enable uart 4 on GPIOs 8-11. BCM2711 only.
+Load:   dtoverlay=uart4,<param>
+Params: ctsrts                  Enable CTS/RTS on GPIOs 10-11 (default off)
+        rs485                   Enable RS485 mode for using the RTS line to
+                                drive the OE pin of an RS485 transceiver (i.e.
+                                MAX3078E); also enables the UARTx ctsrts
+                                parameter, as RTS is required (default off).
+        rs485_full_duplex       When RS485 mode is enabled, enables receiving of
+                                data while sending data (default off)
+        rs485_invert_rts        When RS485 mode is enabled, inverts the RTS
+                                line from active-high (default) to active-low.
+        rs485_rts_on_delay      When RS485 mode is enabled, sets the delay (in
+                                milliseconds) between data transmission starting
+                                and the RTS line being asserted (default 0).
+        rs485_rts_off_delay     When RS485 mode is enabled, sets the delay (in
+                                milliseconds) between data transmission ending
+                                and the RTS line being deasserted (default 0).
+
+
+Name:   uart4-pi5
+Info:   Enable uart 4 on GPIOs 12-13. Pi 5 only.
+Load:   dtoverlay=uart4-pi5,<param>
+Params: ctsrts                  Enable CTS/RTS on GPIOs 14-15 (default off)
+        rs485                   Enable RS485 mode for using the RTS line to
+                                drive the OE pin of an RS485 transceiver (i.e.
+                                MAX3078E); also enables the UARTx ctsrts
+                                parameter, as RTS is required (default off).
+        rs485_full_duplex       When RS485 mode is enabled, enables receiving of
+                                data while sending data (default off)
+        rs485_invert_rts        When RS485 mode is enabled, inverts the RTS
+                                line from active-high (default) to active-low.
+        rs485_rts_on_delay      When RS485 mode is enabled, sets the delay (in
+                                milliseconds) between data transmission starting
+                                and the RTS line being asserted (default 0).
+        rs485_rts_off_delay     When RS485 mode is enabled, sets the delay (in
+                                milliseconds) between data transmission ending
+                                and the RTS line being deasserted (default 0).
+
+
+Name:   uart5
+Info:   Enable uart 5 on GPIOs 12-15. BCM2711 only.
+Load:   dtoverlay=uart5,<param>
+Params: ctsrts                  Enable CTS/RTS on GPIOs 14-15 (default off)
+        rs485                   Enable RS485 mode for using the RTS line to
+                                drive the OE pin of an RS485 transceiver (i.e.
+                                MAX3078E); also enables the UARTx ctsrts
+                                parameter, as RTS is required (default off).
+        rs485_full_duplex       When RS485 mode is enabled, enables receiving of
+                                data while sending data (default off)
+        rs485_invert_rts        When RS485 mode is enabled, inverts the RTS
+                                line from active-high (default) to active-low.
+        rs485_rts_on_delay      When RS485 mode is enabled, sets the delay (in
+                                milliseconds) between data transmission starting
+                                and the RTS line being asserted (default 0).
+        rs485_rts_off_delay     When RS485 mode is enabled, sets the delay (in
+                                milliseconds) between data transmission ending
+                                and the RTS line being deasserted (default 0).
+
+
+Name:   udrc
+Info:   Configures the NW Digital Radio UDRC Hat
+Load:   dtoverlay=udrc,<param>=<val>
+Params: alsaname                Name of the ALSA audio device (default "udrc")
+
+
+Name:   ugreen-dabboard
+Info:   Configures the ugreen-dabboard I2S overlay
+        This is a simple overlay based on the simple-audio-card and the dmic
+        codec. It has the speciality that it is configured to use the codec
+        as a master I2S device. It works for example with the Si468x DAB
+        receiver on the uGreen DABBoard.
+Load:   dtoverlay=ugreen-dabboard,<param>=<val>
+Params: card-name               Override the default, "dabboard", card name.
+
+
+Name:   upstream
+Info:   Allow usage of downstream .dtb with upstream kernel. Comprises the
+        vc4-kms-v3d and dwc2 overlays.
+Load:   dtoverlay=upstream
+Params: <None>
+
+
+Name:   upstream-aux-interrupt
+Info:   This overlay has been deprecated and removed because it is no longer
+        necessary.
+Load:   <Deprecated>
+
+
+Name:   upstream-pi4
+Info:   Allow usage of downstream .dtb with upstream kernel on Pi 4. Comprises
+        the vc4-kms-v3d-pi4 and dwc2 overlays.
+Load:   dtoverlay=upstream-pi4
+Params: <None>
+
+
+Name:   vc4-fkms-v3d
+Info:   Enable the kernel DRM VC4 V3D driver on top of the dispmanx
+        display stack.
+        NB The firmware will not allow this overlay to load on a Pi with less
+        than 512MB as memory is too tight.
+Load:   dtoverlay=vc4-fkms-v3d,<param>
+Params: cma-512                 CMA is 512MB (needs 1GB)
+        cma-448                 CMA is 448MB (needs 1GB)
+        cma-384                 CMA is 384MB (needs 1GB)
+        cma-320                 CMA is 320MB (needs 1GB)
+        cma-256                 CMA is 256MB (needs 1GB)
+        cma-192                 CMA is 192MB (needs 1GB)
+        cma-128                 CMA is 128MB
+        cma-96                  CMA is 96MB
+        cma-64                  CMA is 64MB
+        cma-size                CMA size in bytes, 4MB aligned
+        cma-default             Use upstream's default value
+
+
+Name:   vc4-fkms-v3d-pi4
+Info:   Enable the kernel DRM VC4 V3D driver on top of the dispmanx
+        display stack.
+Load:   dtoverlay=vc4-fkms-v3d-pi4,<param>
+Params: cma-512                 CMA is 512MB (needs 1GB)
+        cma-448                 CMA is 448MB (needs 1GB)
+        cma-384                 CMA is 384MB (needs 1GB)
+        cma-320                 CMA is 320MB (needs 1GB)
+        cma-256                 CMA is 256MB (needs 1GB)
+        cma-192                 CMA is 192MB (needs 1GB)
+        cma-128                 CMA is 128MB
+        cma-96                  CMA is 96MB
+        cma-64                  CMA is 64MB
+        cma-size                CMA size in bytes, 4MB aligned
+        cma-default             Use upstream's default value
+
+
+Name:   vc4-kms-dpi-at056tn53v1
+Info:   This overlay is now deprecated - see vc4-kms-dpi-panel,at056tn53v1
+Load:   <Deprecated>
+
+
+Name:   vc4-kms-dpi-generic
+Info:   Enable a generic DPI display under KMS. Default timings are for the
+        Adafruit Kippah with 800x480 panel and RGB666 (GPIOs 0-21)
+        Requires vc4-kms-v3d to be loaded.
+Load:   dtoverlay=vc4-kms-dpi-generic,<param>=<val>
+Params: clock-frequency         Display clock frequency (Hz)
+        hactive                 Horizontal active pixels
+        hfp                     Horizontal front porch
+        hsync                   Horizontal sync pulse width
+        hbp                     Horizontal back porch
+        vactive                 Vertical active lines
+        vfp                     Vertical front porch
+        vsync                   Vertical sync pulse width
+        vbp                     Vertical back porch
+        hsync-invert            Horizontal sync active low
+        vsync-invert            Vertical sync active low
+        de-invert               Data Enable active low
+        pixclk-invert           Negative edge pixel clock
+        interlaced              Use an interlaced mode (where supported)
+        width-mm                Define the screen width in mm
+        height-mm               Define the screen height in mm
+        rgb565                  Change to RGB565 output on GPIOs 0-19
+        rgb565-padhi            Change to RGB565 output on GPIOs 0-8, 12-17, and
+                                20-24
+        bgr666                  Change to BGR666 output on GPIOs 0-21.
+        bgr666-padhi            Change to BGR666 output on GPIOs 0-9, 12-17, and
+                                20-25
+        rgb666-padhi            Change to RGB666 output on GPIOs 0-9, 12-17, and
+                                20-25
+        bgr888                  Change to BGR888 output on GPIOs 0-27
+        rgb888                  Change to RGB888 output on GPIOs 0-27
+        bus-format              Override the bus format for a MEDIA_BUS_FMT_*
+                                value. NB also overridden by rgbXXX overrides.
+        backlight-gpio          Defines a GPIO to be used for backlight control
+                                (default of none).
+        backlight-pwm           Defines a PWM channel to be used for backlight
+                                control (default of none). NB Disables audio
+                                headphone output as that also uses PWM.
+        backlight-pwm-chan      Choose channel on &pwm node for backlight
+                                control.
+                                (default 0).
+        backlight-pwm-gpio      GPIO pin to be used for the PWM backlight. See
+                                pwm-2chan for valid options.
+                                (default 18 - note this can only work with
+                                 rgb666-padhi).
+        backlight-pwm-func      Pin function of GPIO used for the PWM
+                                backlight.
+                                See pwm-2chan for valid options.
+                                (default 2).
+        backlight-def-brightness
+                                Set the default brightness. Normal range 1-16.
+                                (default 16).
+        rotate                  Display rotation {0,90,180,270} (default 0)
+        rgb-order               Allow override of RGB order from DPI.
+                                Options for vc4 are "rgb", "bgr", "grb", and
+                                "brg". Other values will be ignored.
+
+
+
+Name:   vc4-kms-dpi-hyperpixel2r
+Info:   Enable the KMS drivers for the Pimoroni HyperPixel2 Round DPI display.
+        Requires vc4-kms-v3d to be loaded.
+Load:   dtoverlay=vc4-kms-dpi-hyperpixel2r,<param>=<val>
+Params: disable-touch           Disables the touch controller
+        touchscreen-inverted-x  Inverts X direction of touch controller
+        touchscreen-inverted-y  Inverts Y direction of touch controller
+        touchscreen-swapped-x-y Swaps X & Y axes of touch controller
+        rotate                  Display rotation {0,90,180,270} (default 0)
+
+
+Name:   vc4-kms-dpi-hyperpixel4
+Info:   Enable the KMS drivers for the Pimoroni HyperPixel4 DPI display.
+        Requires vc4-kms-v3d to be loaded.
+Load:   dtoverlay=vc4-kms-dpi-hyperpixel4,<param>=<val>
+Params: disable-touch           Disables the touch controller
+        touchscreen-inverted-x  Inverts X direction of touch controller
+        touchscreen-inverted-y  Inverts Y direction of touch controller
+        touchscreen-swapped-x-y Swaps X & Y axes of touch controller
+        rotate                  Display rotation {0,90,180,270} (default 0)
+
+
+Name:   vc4-kms-dpi-hyperpixel4sq
+Info:   Enable the KMS drivers for the Pimoroni HyperPixel4 Square DPI display.
+        Requires vc4-kms-v3d to be loaded.
+Load:   dtoverlay=vc4-kms-dpi-hyperpixel4sq,<param>=<val>
+Params: disable-touch           Disables the touch controller
+        touchscreen-inverted-x  Inverts X direction of touch controller
+        touchscreen-inverted-y  Inverts Y direction of touch controller
+        touchscreen-swapped-x-y Swaps X & Y axes of touch controller
+        rotate                  Display rotation {0,90,180,270} (default 0)
+
+
+Name:   vc4-kms-dpi-panel
+Info:   Enable a preconfigured KMS DPI panel.
+        Requires vc4-kms-v3d to be loaded.
+Load:   dtoverlay=vc4-kms-dpi-panel,<param>=<val>
+Params: at056tn53v1             Enable an Innolux 5.6in VGA TFT
+        kippah-7inch            Enable an Adafruit Kippah with 7inch panel.
+        mzp280                  Enable a Geekworm MZP280 panel.
+        backlight-gpio          Defines a GPIO to be used for backlight control
+                                (default of none).
+        backlight-pwm           Defines a PWM channel to be used for backlight
+                                control (default of none). NB Disables audio
+                                headphone output as that also uses PWM.
+        backlight-pwm-chan      Choose channel on &pwm node for backlight
+                                control.
+                                (default 0).
+        backlight-pwm-gpio      GPIO pin to be used for the PWM backlight. See
+                                pwm-2chan for valid options.
+                                (default 18 - note this can only work with
+                                 rgb666-padhi).
+        backlight-pwm-func      Pin function of GPIO used for the PWM
+                                backlight.
+                                See pwm-2chan for valid options.
+                                (default 2).
+        backlight-def-brightness
+                                Set the default brightness. Normal range 1-16.
+                                (default 16).
+        rotate                  Display rotation {0,90,180,270} (default 0)
+
+
+Name:   vc4-kms-dsi-7inch
+Info:   Enable the Raspberry Pi DSI 7" screen.
+        Includes the edt-ft5406 for the touchscreen element.
+        Requires vc4-kms-v3d to be loaded.
+Load:   dtoverlay=vc4-kms-dsi-7inch,<param>=<val>
+Params: sizex                   Touchscreen size x (default 800)
+        sizey                   Touchscreen size y (default 480)
+        invx                    Touchscreen inverted x axis
+        invy                    Touchscreen inverted y axis
+        swapxy                  Touchscreen swapped x y axis
+        disable_touch           Disables the touch screen overlay driver
+        dsi0                    Use DSI0 and i2c_csi_dsi0 (rather than
+                                the default DSI1 and i2c_csi_dsi).
+
+
+Name:   vc4-kms-dsi-generic
+Info:   Enable a generic DSI display under KMS.
+        Default timings are for a 840x480 RGB888 panel.
+        Requires vc4-kms-v3d to be loaded.
+Load:   dtoverlay=vc4-kms-dsi-generic,<param>=<val>
+Params: clock-frequency         Display clock frequency (Hz)
+        hactive                 Horizontal active pixels
+        hfp                     Horizontal front porch
+        hsync                   Horizontal sync pulse width
+        hbp                     Horizontal back porch
+        vactive                 Vertical active lines
+        vfp                     Vertical front porch
+        vsync                   Vertical sync pulse width
+        vbp                     Vertical back porch
+        width-mm                Define the screen width in mm
+        height-mm               Define the screen height in mm
+        rgb565                  Change to RGB565 output
+        rgb666                  Change to RGB666 output
+        rgb666p                 Change to RGB666 output with pixel packing
+        rgb888                  Change to RGB888 output, this is the default
+        one-lane                Use one DSI lane for data transmission
+                                This is the default
+        two-lane                Use two DSI lanes for data transmission
+        three-lane              Use three DSI lanes for data transmission
+                                Only supported on Pi5 and CM
+        four-lane               Use four DSI lanes for data transmission
+                                Only supported on Pi5 and CM
+        dsi0                    Switch DSI port to DSI0
+                                Only supported on Pi5 and CM
+
+
+Name:   vc4-kms-dsi-ili79600-10-1inch
+Info:   Enable the Raspberry Pi 10" ILI79600 based touchscreen panel.
+        Requires vc4-kms-v3d to be loaded.
+Load:   dtoverlay=vc4-kms-dsi-ili79600-10-1inch,<param>
+Params: sizex                   Touchscreen size x (default 1200)
+        sizey                   Touchscreen size y (default 1920)
+        invx                    Touchscreen inverted x axis
+        invy                    Touchscreen inverted y axis
+        swapxy                  Touchscreen swapped x y axis
+        disable_touch           Disables the touch screen overlay driver
+        rotation                Display rotation {0,90,180,270} (default 0)
+        dsi0                    Use DSI0 and i2c_csi_dsi0 (rather than
+                                the default DSI1 and i2c_csi_dsi).
+
+
+Name:   vc4-kms-dsi-ili9881-5inch
+Info:   Enable the Raspberry Pi 5" ILI9881 based touchscreen panel.
+        Requires vc4-kms-v3d to be loaded.
+Load:   dtoverlay=vc4-kms-dsi-ili9881-5inch,<param>
+Params: sizex                   Touchscreen size x (default 720)
+        sizey                   Touchscreen size y (default 1280)
+        invx                    Touchscreen inverted x axis
+        invy                    Touchscreen inverted y axis
+        swapxy                  Touchscreen swapped x y axis
+        disable_touch           Disables the touch screen overlay driver
+        rotation                Display rotation {0,90,180,270} (default 0)
+        dsi0                    Use DSI0 and i2c_csi_dsi0 (rather than
+                                the default DSI1 and i2c_csi_dsi).
+
+
+Name:   vc4-kms-dsi-ili9881-7inch
+Info:   Enable the Raspberry Pi 7" ILI9881 based touchscreen panel.
+        Requires vc4-kms-v3d to be loaded.
+Load:   dtoverlay=vc4-kms-dsi-ili9881-7inch,<param>
+Params: sizex                   Touchscreen size x (default 720)
+        sizey                   Touchscreen size y (default 1280)
+        invx                    Touchscreen inverted x axis
+        invy                    Touchscreen inverted y axis
+        swapxy                  Touchscreen swapped x y axis
+        disable_touch           Disables the touch screen overlay driver
+        rotation                Display rotation {0,90,180,270} (default 0)
+        dsi0                    Use DSI0 and i2c_csi_dsi0 (rather than
+                                the default DSI1 and i2c_csi_dsi).
+
+
+Name:   vc4-kms-dsi-lt070me05000
+Info:   Enable a JDI LT070ME05000 DSI display on DSI1.
+        Note that this is a 4 lane DSI device, so it will only work on a Compute
+        Module.
+        Requires vc4-kms-v3d to be loaded.
+Load:   dtoverlay=vc4-kms-dsi-lt070me05000,<param>
+Params: reset                   GPIO for the reset signal (default 17)
+        enable                  GPIO for the enable signal (default 4)
+        dcdc-en                 GPIO for the DC-DC converter enable (default 5)
+
+
+Name:   vc4-kms-dsi-lt070me05000-v2
+Info:   Enable a JDI LT070ME05000 DSI display on DSI1 using Harlab's V2
+        interface board.
+        Note that this is a 4 lane DSI device, so it will only work on a Compute
+        Module.
+        Requires vc4-kms-v3d to be loaded.
+Load:   dtoverlay=vc4-kms-dsi-lt070me05000-v2
+Params: <None>
+
+
+Name:   vc4-kms-dsi-waveshare-800x480
+Info:   Enable the Waveshare 4.3" 800x480 DSI screen.
+        It tries to look like the Pi 7" display, but won't accept some of the
+        timings.
+        Includes the edt-ft5406 for the touchscreen element.
+        Requires vc4-kms-v3d to be loaded.
+Load:   dtoverlay=vc4-kms-dsi-waveshare-800x480,<param>=<val>
+Params: sizex                   Touchscreen size x (default 800)
+        sizey                   Touchscreen size y (default 480)
+        invx                    Touchscreen inverted x axis
+        invy                    Touchscreen inverted y axis
+        swapxy                  Touchscreen swapped x y axis
+        disable_touch           Disables the touch screen overlay driver
+        dsi0                    Use DSI0 and i2c_csi_dsi0 (rather than
+                                the default DSI1 and i2c_csi_dsi).
+
+
+Name:   vc4-kms-dsi-waveshare-panel
+Info:   Enable a Waveshare DSI touchscreen
+        Includes the Goodix driver for the touchscreen element.
+        The default is for the display to be using the I2C0 option for control.
+        Use the i2c1 override if using the I2C1 wiring with jumper wires from
+        GPIOs 2&3 (pins 3&5).
+        invx/invy/swapxy should be used with caution as the panel specifier will
+        set the default inversions for that panel. Always use them after the
+        panel specifier, and be aware that you may need to set them as =0, not
+        just adding it.
+        Requires vc4-kms-v3d to be loaded.
+Load:   dtoverlay=vc4-kms-dsi-waveshare-panel,<param>=<val>
+Params: 2_8_inch                2.8" 480x640
+        3_4_inch                3.4" 800x800 round
+        4_0_inch                4.0" 480x800
+        4_0_inchC               4.0" 720x720
+        5_0_inch                5.0" 720x1280
+        6_25_inch               6.25" 720x1560
+        8_8_inch                8.8" 480x1920
+        7_0_inchC               7.0" C 1024x600
+        7_0_inchH               7.0" H 1280x720
+        7_9_inch                7.9" 400x1280
+        8_0_inch                8.0" 1280x800
+        10_1_inch               10.1" 1280x800
+        11_9_inch               11.9" 320x1480
+        13_3_inch_4lane         13.3" 1920x1080 4lane
+        13_3_inch_2lane         13.3" 1920x1080 2lane
+        i2c1                    Use i2c-1 with jumper wires from GPIOs 2&3
+        disable_touch           Disable the touch controller
+        rotation                Set the panel orientation property
+        invx                    Touchscreen inverted x axis
+        invy                    Touchscreen inverted y axis
+        swapxy                  Touchscreen swapped x y axis
+        dsi0                    Use DSI0 and i2c_csi_dsi0 (rather than
+                                the default DSI1 and i2c_csi_dsi).
+
+
+Name:   vc4-kms-dsi-waveshare-panel-v2
+Info:   Enable the Waveshare DSI-TOUCH series screen
+        Requires vc4-kms-v3d to be loaded.
+Load:   dtoverlay=vc4-kms-dsi-waveshare-panel-v2,<param>
+Params: 3_4_inch_c              3.4" 800x800 2lane
+        4_0_inch_c              4.0" 720x720 2lane
+        5_0_inch_a              5.0" 720x1280 2lane
+        5_5_inch_a              5.5" 720x1280 2lane
+        7_0_inch_a              7.0" A 720x1280 2lane
+        7_0_inch_b              7.0" B 720x1280 2lane
+        7_0_inch_c              7.0" C 1024x600 2lane
+        8_0_inch_a              8.0" 800x1280 2lane
+        8_0_inch_a_4lane        8.0" 800x1280 4lane
+        8_8_inch_a              8.8" 480x1920 2lane
+        9_0_inch_b              9.0" 720x1280 2lane
+        9_0_inch_b_4lane        9.0" 720x1280 4lane
+        10_1_inch_a             10.1" 800x1280 2lane
+        10_1_inch_a_4lane       10.1" 800x1280 4lane
+        10_1_inch_b             10.1" 720x1280 2lane
+        10_1_inch_b_4lane       10.1" 720x1280 4lane
+        12_3_inch_a_4lane       12.3" 720x1920 4lane
+        disable_touch           Disable the touch controller
+        invx                    Touchscreen inverted x axis
+        invy                    Touchscreen inverted y axis
+        swapxy                  Touchscreen swapped x y axis
+        dsi0                    Use DSI0 and i2c_csi_dsi0 (rather than
+                                the default DSI1 and i2c_csi_dsi).
+
+
+Name:   vc4-kms-kippah-7inch
+Info:   This overlay is now deprecated - see vc4-kms-dpi-panel,kippah-7inch
+Load:   <Deprecated>
+
+
+Name:   vc4-kms-v3d
+Info:   Enable the kernel DRM VC4 HDMI/HVS/V3D driver.
+        NB The firmware will not allow this overlay to load on a Pi with less
+        than 512MB as memory is too tight.
+Load:   dtoverlay=vc4-kms-v3d,<param>
+Params: cma-512                 CMA is 512MB (needs 1GB)
+        cma-448                 CMA is 448MB (needs 1GB)
+        cma-384                 CMA is 384MB (needs 1GB)
+        cma-320                 CMA is 320MB (needs 1GB)
+        cma-256                 CMA is 256MB (needs 1GB)
+        cma-192                 CMA is 192MB (needs 1GB)
+        cma-128                 CMA is 128MB
+        cma-96                  CMA is 96MB
+        cma-64                  CMA is 64MB
+        cma-size                CMA size in bytes, 4MB aligned
+        cma-default             Use upstream's default value
+        audio                   Enable or disable audio over HDMI (default "on")
+        noaudio                 Disable all HDMI audio (default "off")
+        composite               Enable the composite output (default "off")
+                                N.B. Disables all other outputs on a Pi 4.
+        nohdmi                  Disable HDMI output
+
+
+Name:   vc4-kms-v3d-pi4
+Info:   Enable the kernel DRM VC4 HDMI/HVS/V3D driver for Pi4.
+Load:   dtoverlay=vc4-kms-v3d-pi4,<param>
+Params: cma-512                 CMA is 512MB
+        cma-448                 CMA is 448MB
+        cma-384                 CMA is 384MB
+        cma-320                 CMA is 320MB
+        cma-256                 CMA is 256MB
+        cma-192                 CMA is 192MB
+        cma-128                 CMA is 128MB
+        cma-96                  CMA is 96MB
+        cma-64                  CMA is 64MB
+        cma-size                CMA size in bytes, 4MB aligned
+        cma-default             Use upstream's default value
+        audio                   Enable or disable audio over HDMI0 (default
+                                "on")
+        audio1                  Enable or disable audio over HDMI1 (default
+                                "on")
+        noaudio                 Disable all HDMI audio (default "off")
+        composite               Enable the composite output (default "off")
+                                N.B. Disables all other outputs on a Pi 4.
+        nohdmi                  Disable both HDMI 0 & 1 outputs
+        nohdmi0                 Disable HDMI 0 output
+        nohdmi1                 Disable HDMI 1 output
+
+
+Name:   vc4-kms-v3d-pi5
+Info:   See vc4-kms-v3d-pi4 (this is the Pi 5 version)
+
+
+Name:   vc4-kms-vga666
+Info:   Enable the VGA666 (resistor ladder DAC) for the vc4-kms-v3d driver.
+        Requires vc4-kms-v3d to be loaded.
+Load:   dtoverlay=vc4-kms-vga666,<param>
+Params: ddc                     Enables GPIOs 0&1 as the I2C to read the EDID
+                                from the display. NB These are NOT 5V tolerant
+                                GPIOs, therefore level shifters are required.
+
+
+Name:   vec-gpio-pi5
+Info:   Overlay to output composite video as an 8-bit digital code over GPIO
+        pins 4-11, for Raspberry Pi 5 series only. Requires composite video
+        to be enabled; incompatible with any use of DPI or VGA666.
+Load:   dtoverlay=vec-gpio-pi5,<param>
+Params: clk                     Enable GPIO 0 (clock, 108 MHz)
+        gpio27                  Enable GPIO 27 (duplicate MSB)
+
+
+Name:   vga666
+Info:   Overlay for the Fen Logic VGA666 board
+        This uses GPIOs 2-21 (so no I2C), and activates the output 2-3 seconds
+        after the kernel has started.
+        NOT for use with vc4-kms-v3d.
+Load:   dtoverlay=vga666
+Params: <None>
+
+
+Name:   vl805
+Info:   Overlay to enable a VIA VL805 USB3 controller on CM4 carriers
+        Will be loaded automatically by up-to-date firmware if "VL805=1" is
+        set in the EEPROM config.
+Load:   dtoverlay=vl805
+Params: <None>
+
+
+Name:   w1-gpio
+Info:   Configures the w1-gpio Onewire interface module.
+        Use this overlay if you *don't* need a GPIO to drive an external pullup.
+Load:   dtoverlay=w1-gpio,<param>=<val>
+Params: gpiopin                 GPIO for I/O (default "4")
+        pullup                  Now enabled by default (ignored)
+
+
+Name:   w1-gpio-pi5
+Info:   See w1-gpio (this is the Pi 5 version)
+
+
+Name:   w1-gpio-pullup
+Info:   Configures the w1-gpio Onewire interface module.
+        Use this overlay if you *do* need a GPIO to drive an external pullup.
+Load:   dtoverlay=w1-gpio-pullup,<param>=<val>
+Params: gpiopin                 GPIO for I/O (default "4")
+        extpullup               GPIO for external pullup (default "5")
+        pullup                  Now enabled by default (ignored)
+
+
+Name:   w1-gpio-pullup-pi5
+Info:   See w1-gpio-pullup (this is the Pi 5 version)
+
+
+Name:   w5500
+Info:   Overlay for the Wiznet W5500 Ethernet Controller on SPI0
+Load:   dtoverlay=w5500,<param>=<val>
+Params: int_pin                 GPIO used for INT (default 25)
+
+        speed                   SPI bus speed (default 30000000)
+
+        cs                      SPI bus Chip Select (default 0)
+
+
+Name:   watterott-display
+Info:   Watterott RPi-Display - 2.8" Touch Display
+        Linux has 2 drivers that support this display and this overlay supports
+        both.
+
+        Examples:
+          fbtft/fb_ili9341: dtoverlay=watterott-display
+          drm/mi0283qt: dtoverlay=watterott-display,drm,backlight-pwm,rotate=180
+
+        Some notable differences with the DRM driver compared to fbtft:
+        - The display is turned on when it's first used and not on driver load
+          as with fbtft. So if nothing uses the display it stays off.
+        - Can run with a higher SPI clock increasing framerate. This is possible
+          since the driver avoids messing up the controller configuration due to
+          transmission errors by running config commands at 10MHz and only pixel
+          data at full speed (occasional pixel glitch might occur).
+        - PWM backlight is supported.
+
+Load:   dtoverlay=watterott-display,<param>=<val>
+Params: speed                   Display SPI bus speed
+        rotate                  Display rotation {0,90,180,270}
+        fps                     Delay between frame updates (fbtft only)
+        debug                   Debug output level {0-7} (fbtft only)
+        xohms                   Touchpanel sensitivity (X-plate resistance)
+        swapxy                  Swap x and y axis
+        backlight               Change backlight GPIO pin {e.g. 12, 18}
+                                (fbtft only)
+        drm                     Use DRM/KMS driver mi0283qt instead of fbtft.
+                                Set the SPI clock to 70MHz.
+                                This has to be the first parameter.
+        backlight-pwm           Use pwm for backlight (drm only). NB: Disables
+                                audio headphone output as that also uses PWM.
+
+
+Name:   waveshare-can-fd-hat-mode-a
+Info:   Overlay for the Waveshare 2-Channel Isolated CAN FD Expansion HAT
+        for Raspberry Pi, Multi Protections. Use this overlay when the
+        HAT is configured in Mode A (Default), with can0 on spi0.0
+        and can1 on spi1.0.
+        https://www.waveshare.com/2-ch-can-fd-hat.htm
+Load:   dtoverlay=waveshare-can-fd-hat-mode-a
+Params: <None>
+
+
+Name:   waveshare-can-fd-hat-mode-b
+Info:   Overlay for the Waveshare 2-Channel Isolated CAN FD Expansion HAT
+        for Raspberry Pi, Multi Protections. Use this overlay when the
+        HAT is configured in Mode B (requires hardware modification), with
+        can0 on spi0.0 and can1 on spi0.1.
+        https://www.waveshare.com/2-ch-can-fd-hat.htm
+Load:   dtoverlay=waveshare-can-fd-hat-mode-b
+Params: <None>
+
+
+Name:   wifimac
+Info:   Override the MAC address for the onboard WiFi. Not Pi 5.
+Load:   dtoverlay=wifimac,<param>=<val>
+Params: wifiaddr                Set an alternative WiFi MAC address.
+                                The value should be a 6-byte hexadecimal value,
+                                with or without colon separators, written in the
+                                natural (big-endian) order.
+
+
+Name:   wittypi
+Info:   Configures the wittypi RTC module.
+Load:   dtoverlay=wittypi,<param>=<val>
+Params: led_gpio                GPIO for LED (default "17")
+        led_trigger             Choose which activity the LED tracks (default
+                                "default-on")
+
+
+Name:   wm8960-soundcard
+Info:   Overlay for the Waveshare wm8960 soundcard
+Load:   dtoverlay=wm8960-soundcard,<param>=<val>
+Params: alsaname                Changes the card name in ALSA
+        compatible              Changes the codec compatibility
+
+
+Name:   ws2812-pio
+Info:   Configures a GPIO pin to drive a string of WS2812 LEDS using pio. It
+        can be enabled on any RP1 GPIO in bank 0 (0-27). Up to 4 are supported,
+        assuming nothing else is using PIO. Pi 5 only.
+Load:   dtoverlay=ws2812-pio,<param>=<val>
+Params: brightness              Set the initial brightness for the LEDs. The
+                                brightness can be changed at runtime by writing
+                                a single byte to offset 0 of the device. Note
+                                that brightness is a multiplier for the pixel
+                                values, and only white pixels can reach the
+                                maximum visible brightness. N.B. Setting
+                                brightness to 0 acctivates pass-through mode,
+                                disabling all brightness and gamma processing.
+                                (range 0-255, default 255)
+        dev_name                The name for the /dev/ device entry. Note that
+                                if the name includes '%d' it will be replaced
+                                by the instance number. (default 'leds%d')
+        gpio                    Output GPIO (0-27, default 4)
+        num_leds                Number of LEDs (default 60)
+        rgbw                    'rgbw=on' (or 'rgbw') indicates that each pixel
+                                includes a white LED as well as the usual red,
+                                green and blue. (default 'off')
+
+
+Troubleshooting
+===============
+
+If you are experiencing problems that you think are DT-related, first check for
+error messages from the firmware by running:
+
+    sudo vclog -m
+
+To see more details about the changes to the Device Tree, add the following to
+/boot/firmware/config.txt:
+
+    dtdebug=on
+
+and reboot, then use vclog as above.
+
+Further reading
+===============
+
+This is only meant to be a quick introduction to the subject of Device Tree on
+Raspberry Pi. There is a more complete explanation here:
+
+https://www.raspberrypi.com/documentation/computers/configuration.html#device-trees-overlays-and-parameters
diff --git a/arch/arm/boot/dts/overlays/act-led-overlay.dts b/arch/arm/boot/dts/overlays/act-led-overlay.dts
new file mode 100644
index 000000000000..685e354923a0
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/act-led-overlay.dts
@@ -0,0 +1,28 @@
+/dts-v1/;
+/plugin/;
+
+/* Pi3 uses a GPIO expander to drive the LEDs which can only be accessed
+   from the VPU. There is a special driver for this with a separate DT node,
+   which has the unfortunate consequence of breaking the act_led_gpio and
+   act_led_activelow dtparams.
+
+   This overlay changes the GPIO controller back to the standard one and
+   restores the dtparams.
+*/
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&led_act>;
+		frag0: __overlay__ {
+			gpios = <&gpio 0 0>;
+		};
+	};
+
+	__overrides__ {
+		gpio = <&frag0>,"gpios:4",
+		       <&frag0>,"status=okay";
+		activelow = <&frag0>,"gpios:8";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/adafruit-st7735r-overlay.dts b/arch/arm/boot/dts/overlays/adafruit-st7735r-overlay.dts
new file mode 100644
index 000000000000..6e69bd7fa031
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/adafruit-st7735r-overlay.dts
@@ -0,0 +1,83 @@
+/*
+ * adafruit-st7735r-overlay.dts
+ *
+ * ST7735R based SPI LCD displays. Either
+ * Adafruit 1.8" 160x128
+ *   or
+ * Okaya 1.44" 128x128
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			adafruit_pins: adafruit_pins {
+				brcm,pins = <25 24>;
+				brcm,function = <1>; /* out */
+			};
+			backlight_pins: backlight_pins {
+				brcm,pins = <18>;
+				brcm,function = <1>; /* out */
+			};
+		};
+	};
+
+	fragment@2 {
+		target-path = "/";
+		__overlay__ {
+			af18_backlight: backlight {
+				compatible = "gpio-backlight";
+				gpios = <&gpio 18 GPIO_ACTIVE_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&backlight_pins>;
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			af18: adafruit18@0 {
+				compatible = "jianda,jd-t18003-t01";
+				reg = <0>;
+				spi-max-frequency = <32000000>;
+				dc-gpios = <&gpio 24 GPIO_ACTIVE_HIGH>;
+				reset-gpios = <&gpio 25 GPIO_ACTIVE_HIGH>;
+				rotation = <90>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&adafruit_pins>;
+				backlight = <&af18_backlight>;
+			};
+		};
+	};
+
+	__overrides__ {
+		128x128 = <&af18>, "compatible=okaya,rh128128t";
+		speed = <&af18>,"spi-max-frequency:0";
+		rotate = <&af18>,"rotation:0";
+		dc_pin = <&af18>,"dc-gpios:4", <&adafruit_pins>,"brcm,pins:4";
+		reset_pin = <&af18>,"reset-gpios:4",
+			    <&adafruit_pins>,"brcm,pins:0";
+		led_pin = <&af18_backlight>,"gpios:4",
+			  <&backlight_pins>,"brcm,pins:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/adafruit18-overlay.dts b/arch/arm/boot/dts/overlays/adafruit18-overlay.dts
new file mode 100644
index 000000000000..e1ce94a8cd3e
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/adafruit18-overlay.dts
@@ -0,0 +1,55 @@
+/*
+ * Device Tree overlay for Adafruit 1.8" TFT LCD with ST7735R chip 160x128
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@1 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			af18: adafruit18@0 {
+				compatible = "fbtft,adafruit18";
+				reg = <0>;
+				pinctrl-names = "default";
+				spi-max-frequency = <40000000>;
+				rotate = <90>;
+				buswidth = <8>;
+				fps = <50>;
+				height = <160>;
+				width = <128>;
+				reset-gpios = <&gpio 25 1>;
+				dc-gpios = <&gpio 24 0>;
+				led-gpios = <&gpio 18 0>;
+				debug = <0>;
+			};
+		};
+	};
+
+	__overrides__ {
+		green = <&af18>, "compatible=fbtft,adafruit18_green";
+		speed     = <&af18>,"spi-max-frequency:0";
+		rotate    = <&af18>,"rotate:0";
+		fps       = <&af18>,"fps:0";
+		bgr       = <&af18>,"bgr?";
+		debug     = <&af18>,"debug:0";
+		dc_pin    = <&af18>,"dc-gpios:4";
+		reset_pin = <&af18>,"reset-gpios:4";
+		led_pin   = <&af18>,"led-gpios:4";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/adau1977-adc-overlay.dts b/arch/arm/boot/dts/overlays/adau1977-adc-overlay.dts
new file mode 100644
index 000000000000..cf6d1ef3bfff
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/adau1977-adc-overlay.dts
@@ -0,0 +1,40 @@
+// Definitions for ADAU1977 ADC
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+    
+	fragment@0 {
+		target = <&i2c1>;
+        	
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+			
+			adau1977: codec@11 {
+                        	compatible = "adi,adau1977";
+                        	reg = <0x11>;
+                        	reset-gpios = <&gpio 5 0>;
+				AVDD-supply = <&vdd_3v3_reg>;
+                	};
+        	};
+	};
+
+	fragment@1 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "adi,adau1977-adc";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/adau7002-simple-overlay.dts b/arch/arm/boot/dts/overlays/adau7002-simple-overlay.dts
new file mode 100644
index 000000000000..62e92bd8f952
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/adau7002-simple-overlay.dts
@@ -0,0 +1,52 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+    compatible = "brcm,bcm2835";
+
+    fragment@0 {
+        target = <&i2s_clk_producer>;
+        __overlay__ {
+            status = "okay";
+        };
+    };
+
+    fragment@1 {
+        target-path = "/";
+        __overlay__ {
+                adau7002_codec: adau7002-codec {
+                #sound-dai-cells = <0>;
+                compatible = "adi,adau7002";
+/*                IOVDD-supply = <&supply>;*/
+                status = "okay";
+            };
+        };
+    };
+
+    fragment@2 {
+        target = <&sound>;
+            sound_overlay: __overlay__ {
+            compatible = "simple-audio-card";
+            simple-audio-card,format = "i2s";
+            simple-audio-card,name = "adau7002";
+            simple-audio-card,bitclock-slave = <&dailink0_slave>;
+            simple-audio-card,frame-slave = <&dailink0_slave>;
+            simple-audio-card,widgets =
+                    "Microphone", "Microphone Jack";
+            simple-audio-card,routing =
+                    "PDM_DAT", "Microphone Jack";
+            status = "okay";
+            simple-audio-card,cpu {
+                sound-dai = <&i2s_clk_producer>;
+            };
+            dailink0_slave: simple-audio-card,codec {
+                sound-dai = <&adau7002_codec>;
+            };
+        };
+    };
+
+
+    __overrides__ {
+        card-name = <&sound_overlay>,"simple-audio-card,name";
+    };
+};
diff --git a/arch/arm/boot/dts/overlays/ads1015-overlay.dts b/arch/arm/boot/dts/overlays/ads1015-overlay.dts
new file mode 100644
index 000000000000..dc1764613a8b
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ads1015-overlay.dts
@@ -0,0 +1,98 @@
+/*
+ * 2016 - Erik Sejr
+ */
+/dts-v1/;
+/plugin/;
+
+/ {
+    compatible = "brcm,bcm2835";
+    /* ----------- ADS1015 ------------ */
+    fragment@0 {
+        target = <&i2c_arm>;
+        __overlay__ {
+            #address-cells = <1>;
+            #size-cells = <0>;
+            status = "okay";
+            ads1015: ads1015@48 {
+                compatible = "ti,ads1015";
+                status = "okay";
+                #address-cells = <1>;
+                #size-cells = <0>;
+                reg = <0x48>;
+            };
+        };
+    };
+
+    fragment@1 {
+        target = <&ads1015>;
+        __overlay__ {
+            #address-cells = <1>;
+            #size-cells = <0>;
+            channel_a: channel_a {
+                reg = <4>;
+                ti,gain = <2>;
+                ti,datarate = <4>;
+            };
+        };
+    };
+
+    fragment@2 {
+        target = <&ads1015>;
+        __dormant__ {
+            #address-cells = <1>;
+            #size-cells = <0>;
+            channel_b: channel_b {
+                reg = <5>;
+                ti,gain = <2>;
+                ti,datarate = <4>;
+            };
+        };
+    };
+
+    fragment@3 {
+        target = <&ads1015>;
+        __dormant__ {
+            #address-cells = <1>;
+            #size-cells = <0>;
+            channel_c: channel_c {
+                reg = <6>;
+                ti,gain = <2>;
+                ti,datarate = <4>;
+            };
+        };
+    };
+
+    fragment@4 {
+        target = <&ads1015>;
+        __dormant__ {
+            #address-cells = <1>;
+            #size-cells = <0>;
+            channel_d: channel_d {
+                reg = <7>;
+                ti,gain = <2>;
+                ti,datarate = <4>;
+            };
+        };
+    };
+
+    __overrides__ {
+        addr =            <&ads1015>,"reg:0";
+        cha_enable =      <0>,"=1";
+        cha_cfg =         <&channel_a>,"reg:0";
+        cha_gain =        <&channel_a>,"ti,gain:0";
+        cha_datarate =    <&channel_a>,"ti,datarate:0";
+        chb_enable =      <0>,"=2";
+        chb_cfg =         <&channel_b>,"reg:0";
+        chb_gain =        <&channel_b>,"ti,gain:0";
+        chb_datarate =    <&channel_b>,"ti,datarate:0";
+        chc_enable =      <0>,"=3";
+        chc_cfg =         <&channel_c>,"reg:0";
+        chc_gain =        <&channel_c>,"ti,gain:0";
+        chc_datarate =    <&channel_c>,"ti,datarate:0";
+        chd_enable =      <0>,"=4";
+        chd_cfg =         <&channel_d>,"reg:0";
+        chd_gain =        <&channel_d>,"ti,gain:0";
+        chd_datarate =    <&channel_d>,"ti,datarate:0";
+   };
+
+};
diff --git a/arch/arm/boot/dts/overlays/ads1115-overlay.dts b/arch/arm/boot/dts/overlays/ads1115-overlay.dts
new file mode 100644
index 000000000000..6a1d86929add
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ads1115-overlay.dts
@@ -0,0 +1,105 @@
+/*
+ * TI ADS1115 multi-channel ADC overlay
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include "i2c-buses.dtsi"
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&ads1115>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			channel_a: channel_a {
+				reg = <4>;
+				ti,gain = <1>;
+				ti,datarate = <7>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&ads1115>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			channel_b: channel_b {
+				reg = <5>;
+				ti,gain = <1>;
+				ti,datarate = <7>;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&ads1115>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			channel_c: channel_c {
+				reg = <6>;
+				ti,gain = <1>;
+				ti,datarate = <7>;
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&ads1115>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			channel_d: channel_d {
+				reg = <7>;
+				ti,gain = <1>;
+				ti,datarate = <7>;
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&i2cbus>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			ads1115: ads1115@48 {
+				compatible = "ti,ads1115";
+				status = "okay";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				reg = <0x48>;
+			};
+		};
+	};
+
+	__overrides__ {
+		addr =            <&ads1115>,"reg:0";
+		cha_enable =      <0>,"=0";
+		cha_cfg =         <&channel_a>,"reg:0";
+		cha_gain =        <&channel_a>,"ti,gain:0";
+		cha_datarate =    <&channel_a>,"ti,datarate:0";
+		chb_enable =      <0>,"=1";
+		chb_cfg =         <&channel_b>,"reg:0";
+		chb_gain =        <&channel_b>,"ti,gain:0";
+		chb_datarate =    <&channel_b>,"ti,datarate:0";
+		chc_enable =      <0>,"=2";
+		chc_cfg =         <&channel_c>,"reg:0";
+		chc_gain =        <&channel_c>,"ti,gain:0";
+		chc_datarate =    <&channel_c>,"ti,datarate:0";
+		chd_enable =      <0>,"=3";
+		chd_cfg =         <&channel_d>,"reg:0";
+		chd_gain =        <&channel_d>,"ti,gain:0";
+		chd_datarate =    <&channel_d>,"ti,datarate:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/ads7846-overlay.dts b/arch/arm/boot/dts/overlays/ads7846-overlay.dts
new file mode 100644
index 000000000000..cb3e3bf6c098
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ads7846-overlay.dts
@@ -0,0 +1,91 @@
+/*
+ * Generic Device Tree overlay for the ADS7846 touch controller
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&gpio>;
+		__overlay__ {
+			ads7846_pins: ads7846_pins {
+				brcm,pins = <255>; /* illegal default value */
+				brcm,function = <0>; /* in */
+				brcm,pull = <0>; /* none */
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			ads7846: ads7846@1 {
+				compatible = "ti,ads7846";
+				reg = <1>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&ads7846_pins>;
+
+				spi-max-frequency = <2000000>;
+				interrupts = <255 2>; /* high-to-low edge triggered */
+				interrupt-parent = <&gpio>;
+				pendown-gpio = <&gpio 255 1>;
+
+				/* driver defaults */
+				ti,x-min = /bits/ 16 <0>;
+				ti,y-min = /bits/ 16 <0>;
+				ti,x-max = /bits/ 16 <0x0FFF>;
+				ti,y-max = /bits/ 16 <0x0FFF>;
+				ti,pressure-min = /bits/ 16 <0>;
+				ti,pressure-max = /bits/ 16 <0xFFFF>;
+				ti,x-plate-ohms = /bits/ 16 <400>;
+			};
+		};
+	};
+	__overrides__ {
+		cs =     <&ads7846>,"reg:0";
+		speed =  <&ads7846>,"spi-max-frequency:0";
+		penirq = <&ads7846_pins>,"brcm,pins:0", /* REQUIRED */
+			 <&ads7846>,"interrupts:0",
+			 <&ads7846>,"pendown-gpio:4";
+		penirq_pull = <&ads7846_pins>,"brcm,pull:0";
+		swapxy = <&ads7846>,"touchscreen-swapped-x-y?";
+		xmin =   <&ads7846>,"ti,x-min;0";
+		ymin =   <&ads7846>,"ti,y-min;0";
+		xmax =   <&ads7846>,"ti,x-max;0";
+		ymax =   <&ads7846>,"ti,y-max;0";
+		pmin =   <&ads7846>,"ti,pressure-min;0";
+		pmax =   <&ads7846>,"ti,pressure-max;0";
+		xohms =  <&ads7846>,"ti,x-plate-ohms;0";
+		invx =   <&ads7846>,"touchscreen-inverted-x?";
+		invy =   <&ads7846>,"touchscreen-inverted-y?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/adv7282m-overlay.dts b/arch/arm/boot/dts/overlays/adv7282m-overlay.dts
new file mode 100644
index 000000000000..a9eb75a30825
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/adv7282m-overlay.dts
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for Analog Devices ADV7282-M video to CSI2 bridge on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			adv728x: adv728x@21 {
+				compatible = "adi,adv7282-m";
+				reg = <0x21>;
+				status = "okay";
+				clock-frequency = <24000000>;
+				port {
+					adv728x_0: endpoint {
+						remote-endpoint = <&csi1_ep>;
+						clock-lanes = <0>;
+						data-lanes = <1>;
+						link-frequencies =
+							/bits/ 64 <297000000>;
+
+						mclk-frequency = <12000000>;
+					};
+				};
+			};
+		};
+	};
+	fragment@1 {
+		target = <&csi1>;
+		__overlay__ {
+			status = "okay";
+
+			port {
+				csi1_ep: endpoint {
+					remote-endpoint = <&adv728x_0>;
+					data-lanes = <1>;
+				};
+			};
+		};
+	};
+	fragment@2 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@3 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@4 {
+		target = <&csi1>;
+		__overlay__ {
+			compatible = "brcm,bcm2835-unicam-legacy";
+		};
+	};
+
+	__overrides__ {
+		addr = <&adv728x>,"reg:0";
+		media-controller = <0>,"!4";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/adv728x-m-overlay.dts b/arch/arm/boot/dts/overlays/adv728x-m-overlay.dts
new file mode 100644
index 000000000000..ea392e886984
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/adv728x-m-overlay.dts
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for Analog Devices ADV728[0|1|2]-M video to CSI2 bridges on VC
+// I2C bus
+
+#include "adv7282m-overlay.dts"
+
+/{
+	compatible = "brcm,bcm2835";
+
+	// Fragment numbers deliberately high to avoid conflicts with the
+	// included adv7282m overlay file.
+
+	fragment@101 {
+		target = <&adv728x>;
+		__dormant__ {
+			compatible = "adi,adv7280-m";
+		};
+	};
+	fragment@102 {
+		target = <&adv728x>;
+		__dormant__ {
+			compatible = "adi,adv7281-m";
+		};
+	};
+	fragment@103 {
+		target = <&adv728x>;
+		__dormant__ {
+			compatible = "adi,adv7281-ma";
+		};
+	};
+
+	__overrides__ {
+		adv7280m = <0>, "+101";
+		adv7281m = <0>, "+102";
+		adv7281ma = <0>, "+103";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/akkordion-iqdacplus-overlay.dts b/arch/arm/boot/dts/overlays/akkordion-iqdacplus-overlay.dts
new file mode 100644
index 000000000000..d867146bcb8f
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/akkordion-iqdacplus-overlay.dts
@@ -0,0 +1,49 @@
+// Definitions for Digital Dreamtime Akkordion using IQaudIO DAC+ or DACZero
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			pcm5122@4c {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5122";
+				reg = <0x4c>;
+				AVDD-supply = <&vdd_3v3_reg>;
+				DVDD-supply = <&vdd_3v3_reg>;
+				CPVDD-supply = <&vdd_3v3_reg>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		frag2: __overlay__ {
+			compatible = "iqaudio,iqaudio-dac";
+			card_name = "Akkordion";
+			dai_name = "IQaudIO DAC";
+			dai_stream_name = "IQaudIO DAC HiFi";
+			i2s-controller = <&i2s_clk_producer>;
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		24db_digital_gain = <&frag2>,"iqaudio,24db_digital_gain?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/allo-boss-dac-pcm512x-audio-overlay.dts b/arch/arm/boot/dts/overlays/allo-boss-dac-pcm512x-audio-overlay.dts
new file mode 100644
index 000000000000..16806945890b
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/allo-boss-dac-pcm512x-audio-overlay.dts
@@ -0,0 +1,61 @@
+/*
+ * Definitions for Allo Boss DAC board
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			boss_osc: boss_osc {
+				compatible = "allo,dac-clk";
+				#clock-cells = <0>;
+			};
+		};
+	};
+
+	frag1: fragment@1 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			pcm5122@4d {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5122";
+				clocks = <&boss_osc>;
+				reg = <0x4d>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&sound>;
+		boss_dac: __overlay__ {
+			compatible = "allo,boss-dac";
+			i2s-controller = <&i2s_clk_consumer>;
+			mute-gpios = <&gpio 6 1>;
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		24db_digital_gain = <&boss_dac>,"allo,24db_digital_gain?";
+		slave = <&boss_dac>,"allo,slave?",
+			<&frag1>,"target:0=",<&i2s_clk_producer>,
+			<&boss_dac>,"i2s-controller:0=",<&i2s_clk_producer>;
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/allo-boss2-dac-audio-overlay.dts b/arch/arm/boot/dts/overlays/allo-boss2-dac-audio-overlay.dts
new file mode 100644
index 000000000000..feac2b091b36
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/allo-boss2-dac-audio-overlay.dts
@@ -0,0 +1,57 @@
+/* * Definitions for Allo Boss2 DAC boards
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			#sound-dai-cells = <0>;
+			status = "okay";
+			cpu_port: port {
+				cpu_endpoint: endpoint {
+					remote-endpoint = <&codec_endpoint>;
+					bitclock-master = <&codec_endpoint>;
+					frame-master = <&codec_endpoint>;
+					dai-format = "i2s";
+				};
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+			allo-cs43130@30 {
+				#sound-dai-cells = <0>;
+				compatible = "allo,allo-cs43198";
+				clock44-gpio = <&gpio 5 0>;
+				clock48-gpio = <&gpio 6 0>;
+				reg = <0x30>;
+				port {
+					codec_endpoint: endpoint {
+					remote-endpoint = <&cpu_endpoint>;
+					};
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		boss2_dac: __overlay__ {
+			compatible = "audio-graph-card";
+			label = "Allo Boss2";
+			dais = <&cpu_port>;
+			status = "okay";
+		};
+	};
+};
+
diff --git a/arch/arm/boot/dts/overlays/allo-digione-overlay.dts b/arch/arm/boot/dts/overlays/allo-digione-overlay.dts
new file mode 100644
index 000000000000..61c3c2e9fbd8
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/allo-digione-overlay.dts
@@ -0,0 +1,44 @@
+// Definitions for Allo DigiOne
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			wm8804@3b {
+				#sound-dai-cells = <0>;
+				compatible = "wlf,wm8804";
+				reg = <0x3b>;
+				PVDD-supply = <&vdd_3v3_reg>;
+				DVDD-supply = <&vdd_3v3_reg>;
+				status = "okay";
+				wlf,reset-gpio = <&gpio 17 0>;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "allo,allo-digione";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+			clock44-gpio = <&gpio 5 0>;
+			clock48-gpio = <&gpio 6 0>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/allo-katana-dac-audio-overlay.dts b/arch/arm/boot/dts/overlays/allo-katana-dac-audio-overlay.dts
new file mode 100644
index 000000000000..1ebb6bc6b907
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/allo-katana-dac-audio-overlay.dts
@@ -0,0 +1,58 @@
+/*
+ * Definitions for Allo Katana DAC boards
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			#sound-dai-cells = <0>;
+			status = "okay";
+			cpu_port: port {
+				cpu_endpoint: endpoint {
+					remote-endpoint = <&codec_endpoint>;
+					bitclock-master = <&codec_endpoint>;
+					frame-master = <&codec_endpoint>;
+					dai-format = "i2s";
+				};
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+			clock-frequency = <50000>;
+
+			allo-katana-codec@30 {
+				#sound-dai-cells = <0>;
+				compatible = "allo,allo-katana-codec";
+				reg = <0x30>;
+				port {
+					codec_endpoint: endpoint {
+					remote-endpoint = <&cpu_endpoint>;
+					};
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		katana_dac: __overlay__ {
+			compatible = "audio-graph-card";
+			label = "Allo Katana";
+			dais = <&cpu_port>;
+			status = "okay";
+		};
+	};
+};
+
diff --git a/arch/arm/boot/dts/overlays/allo-piano-dac-pcm512x-audio-overlay.dts b/arch/arm/boot/dts/overlays/allo-piano-dac-pcm512x-audio-overlay.dts
new file mode 100644
index 000000000000..1b79ef1df2a1
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/allo-piano-dac-pcm512x-audio-overlay.dts
@@ -0,0 +1,54 @@
+/*
+ * Definitions for Allo Piano DAC (2.0/2.1) boards
+ *
+ * NB. The Piano DAC 2.1 board contains 2x TI PCM5142 DAC's. One DAC is stereo
+ * (left/right) and the other provides a subwoofer output, using DSP on the
+ * chip for digital high/low pass crossover.
+ * The initial support for this hardware, that doesn't require any codec driver
+ * modifications, uses only one DAC chip for stereo (left/right) output, the
+ * chip with 0x4c slave address. The other chip at 0x4d is currently ignored!
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			pcm5142@4c {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5142";
+				reg = <0x4c>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		piano_dac: __overlay__ {
+			compatible = "allo,piano-dac";
+			i2s-controller = <&i2s_clk_producer>;
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		24db_digital_gain =
+			<&piano_dac>,"allo,24db_digital_gain?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/allo-piano-dac-plus-pcm512x-audio-overlay.dts b/arch/arm/boot/dts/overlays/allo-piano-dac-plus-pcm512x-audio-overlay.dts
new file mode 100644
index 000000000000..d17c9c10df39
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/allo-piano-dac-plus-pcm512x-audio-overlay.dts
@@ -0,0 +1,57 @@
+// Definitions for Piano DAC
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			allo_pcm5122_4c: pcm5122@4c {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5122";
+				reg = <0x4c>;
+				sound-name-prefix = "Main";
+				status = "okay";
+			};
+			allo_pcm5122_4d: pcm5122@4d {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5122";
+				reg = <0x4d>;
+				sound-name-prefix = "Sub";
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		piano_dac: __overlay__ {
+			compatible = "allo,piano-dac-plus";
+			audio-codec = <&allo_pcm5122_4c &allo_pcm5122_4d>;
+			i2s-controller = <&i2s_clk_producer>;
+			mute1-gpios = <&gpio 6 1>;
+			mute2-gpios = <&gpio 25 1>;
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		24db_digital_gain =
+			<&piano_dac>,"allo,24db_digital_gain?";
+		glb_mclk =
+			<&piano_dac>,"allo,glb_mclk?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/anyspi-overlay.dts b/arch/arm/boot/dts/overlays/anyspi-overlay.dts
new file mode 100755
index 000000000000..87523dcca318
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/anyspi-overlay.dts
@@ -0,0 +1,205 @@
+/*
+ * Universal device tree overlay for SPI devices
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spidev0>;
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev1>;
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target-path = "spi1/spidev@0";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target-path = "spi1/spidev@1";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@4 {
+		target-path = "spi1/spidev@2";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@5 {
+		target-path = "spi2/spidev@0";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@6 {
+		target-path = "spi2/spidev@1";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@7 {
+		target-path = "spi2/spidev@2";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@8 {
+		target = <&spi0>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			anyspi_00: anyspi@0 {
+				reg = <0>;
+				spi-max-frequency = <500000>;
+			};
+		};
+	};
+
+	fragment@9 {
+		target = <&spi0>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			anyspi_01: anyspi@1 {
+				reg = <1>;
+				spi-max-frequency = <500000>;
+			};
+		};
+	};
+
+	fragment@10 {
+		target = <&spi1>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			anyspi_10: anyspi@0 {
+				reg = <0>;
+				spi-max-frequency = <500000>;
+			};
+		};
+	};
+
+	fragment@11 {
+		target = <&spi1>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			anyspi_11: anyspi@1 {
+				reg = <1>;
+				spi-max-frequency = <500000>;
+			};
+		};
+	};
+
+	fragment@12 {
+		target = <&spi1>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			anyspi_12: anyspi@2 {
+				reg = <2>;
+				spi-max-frequency = <500000>;
+			};
+		};
+	};
+
+	fragment@13 {
+		target = <&spi2>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			anyspi_20: anyspi@0 {
+				reg = <0>;
+				spi-max-frequency = <500000>;
+			};
+		};
+	};
+
+	fragment@14 {
+		target = <&spi2>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			anyspi_21: anyspi@1 {
+				reg = <1>;
+				spi-max-frequency = <500000>;
+			};
+		};
+	};
+
+	fragment@15 {
+		target = <&spi2>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			anyspi_22: anyspi@2 {
+				reg = <2>;
+				spi-max-frequency = <500000>;
+			};
+		};
+	};
+
+	__overrides__ {
+		spi0-0 = <0>, "+0+8";
+		spi0-1 = <0>, "+1+9";
+		spi1-0 = <0>, "+2+10";
+		spi1-1 = <0>, "+3+11";
+		spi1-2 = <0>, "+4+12";
+		spi2-0 = <0>, "+5+13";
+		spi2-1 = <0>, "+6+14";
+		spi2-2 = <0>, "+7+15";
+		dev = <&anyspi_00>,"compatible",
+		      <&anyspi_01>,"compatible",
+		      <&anyspi_10>,"compatible",
+		      <&anyspi_11>,"compatible",
+		      <&anyspi_12>,"compatible",
+		      <&anyspi_20>,"compatible",
+		      <&anyspi_21>,"compatible",
+		      <&anyspi_22>,"compatible";
+		speed = <&anyspi_00>, "spi-max-frequency:0",
+		        <&anyspi_01>, "spi-max-frequency:0",
+		        <&anyspi_10>, "spi-max-frequency:0",
+		        <&anyspi_11>, "spi-max-frequency:0",
+		        <&anyspi_12>, "spi-max-frequency:0",
+		        <&anyspi_20>, "spi-max-frequency:0",
+		        <&anyspi_21>, "spi-max-frequency:0",
+		        <&anyspi_22>, "spi-max-frequency:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/apds9960-overlay.dts b/arch/arm/boot/dts/overlays/apds9960-overlay.dts
new file mode 100644
index 000000000000..bb18cca1ac66
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/apds9960-overlay.dts
@@ -0,0 +1,55 @@
+// Definitions for APDS-9960 ambient light and gesture sensor
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c1>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			apds9960_pins: apds9960_pins@39 {
+				brcm,pins = <4>;
+				brcm,function = <0>;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&apds9960>;
+		apds9960_irq: __overlay__ {
+			#interrupt-cells = <2>;
+			interrupt-parent = <&gpio>;
+			interrupts = <4 1>;
+		};
+	};
+
+	fragment@3 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			apds9960: apds@39 {
+				compatible = "avago,apds9960";
+				reg = <0x39>;
+				status = "okay";
+			};
+		};
+	};
+
+	__overrides__ {
+		gpiopin = <&apds9960_pins>,"brcm,pins:0",
+				<&apds9960_irq>,"interrupts:0";
+		noints = <0>,"!1!2";
+	};
+};
+
diff --git a/arch/arm/boot/dts/overlays/applepi-dac-overlay.dts b/arch/arm/boot/dts/overlays/applepi-dac-overlay.dts
new file mode 100644
index 000000000000..cb7649d3a613
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/applepi-dac-overlay.dts
@@ -0,0 +1,57 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+    compatible = "brcm,bcm2835";
+
+    fragment@0 {
+        target = <&sound>;
+        __overlay__ {
+            compatible = "simple-audio-card";
+            simple-audio-card,name = "ApplePi-DAC";
+
+            status = "okay";
+
+            playback_link: simple-audio-card,dai-link@1 {
+                format = "i2s";
+
+                p_cpu_dai: cpu {
+                    sound-dai = <&i2s_clk_producer>;
+                    dai-tdm-slot-num = <2>;
+                    dai-tdm-slot-width = <32>;
+                };
+
+                p_codec_dai: codec {
+                    sound-dai = <&codec_out>;
+                };
+            };
+        };
+    };
+
+    fragment@1 {
+        target-path = "/";
+        __overlay__ {
+            codec_out: pcm1794a-codec {
+                #sound-dai-cells = <0>;
+                compatible = "ti,pcm1794a";
+                status = "okay";
+            };
+        };
+    };
+
+    fragment@2 {
+        target = <&i2s_clk_producer>;
+        __overlay__ {
+            #sound-dai-cells = <0>;
+            status = "okay";
+        };
+    };
+};
+
+/*
+   Written by: Leonid Ayzenshtat
+   Company: Orchard Audio (www.orchardaudio.com)
+
+   compile with:
+   dtc -@ -H epapr -O dtb -o ApplePi-DAC.dtbo -W no-unit_address_vs_reg ApplePi-DAC.dts
+*/
diff --git a/arch/arm/boot/dts/overlays/arducam-64mp-overlay.dts b/arch/arm/boot/dts/overlays/arducam-64mp-overlay.dts
new file mode 100644
index 000000000000..3f3d7858f2fd
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/arducam-64mp-overlay.dts
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for Arducam 64MP camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	i2c_frag: fragment@0 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			#include "arducam-64mp.dtsi"
+		};
+	};
+
+	csi_frag: fragment@1 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+
+			port{
+				csi_ep: endpoint{
+					remote-endpoint = <&cam_endpoint>;
+					clock-lanes = <0>;
+					data-lanes = <1 2>;
+					clock-noncontinuous;
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	clk_frag: fragment@3 {
+		target = <&cam1_clk>;
+		__overlay__ {
+			clock-frequency = <24000000>;
+			status = "okay";
+		};
+	};
+
+	fragment@4 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@5 {
+		target = <&cam_node>;
+		__overlay__ {
+			lens-focus = <&vcm_node>;
+		};
+	};
+
+	fragment@6 {
+		target = <&csi1>;
+		__dormant__ {
+			compatible = "brcm,bcm2835-unicam-legacy";
+		};
+	};
+
+	__overrides__ {
+		rotation = <&cam_node>,"rotation:0";
+		orientation = <&cam_node>,"orientation:0";
+		media-controller = <0>,"!6";
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+		       <&clk_frag>, "target:0=",<&cam0_clk>,
+		       <&cam_node>, "clocks:0=",<&cam0_clk>,
+		       <&cam_node>, "VANA-supply:0=",<&cam0_reg>,
+		       <&vcm_node>, "VDD-supply:0=", <&cam0_reg>;
+		vcm = <&vcm_node>, "status",
+		      <0>, "=5";
+	};
+};
+
+&cam_node {
+	status = "okay";
+};
+
+&cam_endpoint {
+	remote-endpoint = <&csi_ep>;
+};
+
+&vcm_node {
+	status = "okay";
+};
diff --git a/arch/arm/boot/dts/overlays/arducam-64mp.dtsi b/arch/arm/boot/dts/overlays/arducam-64mp.dtsi
new file mode 100644
index 000000000000..ed9f2e50c287
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/arducam-64mp.dtsi
@@ -0,0 +1,34 @@
+// Fragment that configures a Arducam64MP
+
+cam_node: arducam_64mp@1a {
+	compatible = "arducam,64mp";
+	reg = <0x1a>;
+	status = "disabled";
+
+	clocks = <&cam1_clk>;
+	clock-names = "xclk";
+
+	VANA-supply = <&cam1_reg>;	/* 2.8v */
+	VDIG-supply = <&cam_dummy_reg>;	/* 1.8v */
+	VDDL-supply = <&cam_dummy_reg>;	/* 1.2v */
+
+	rotation = <0>;
+	orientation = <2>;
+
+	port {
+		cam_endpoint: endpoint {
+			clock-lanes = <0>;
+			data-lanes = <1 2>;
+			clock-noncontinuous;
+			link-frequencies =
+				/bits/ 64 <456000000>;
+		};
+	};
+};
+
+vcm_node: dw9817_arducam64mp@c {
+	compatible = "dongwoon,dw9817-vcm";
+	reg = <0x0c>;
+	status = "disabled";
+	VDD-supply = <&cam1_reg>;
+};
diff --git a/arch/arm/boot/dts/overlays/arducam-pivariety-overlay.dts b/arch/arm/boot/dts/overlays/arducam-pivariety-overlay.dts
new file mode 100644
index 000000000000..752022786f70
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/arducam-pivariety-overlay.dts
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for Arducam Pivariety camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	i2c_frag: fragment@0 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			arducam_pivariety: arducam_pivariety@c {
+				compatible = "arducam,arducam-pivariety";
+				reg = <0x0c>;
+				status = "okay";
+
+				clocks = <&cam1_clk>;
+				clock-names = "xclk";
+
+				VANA-supply = <&cam1_reg>;	/* 2.8v */
+				VDIG-supply = <&cam_dummy_reg>;	/* 1.8v */
+				VDDL-supply = <&cam_dummy_reg>;	/* 1.2v */
+
+				rotation = <0>;
+				orientation = <2>;
+
+				port {
+					arducam_pivariety_0: endpoint {
+						remote-endpoint = <&csi1_ep>;
+						clock-lanes = <0>;
+						data-lanes = <1 2>;
+						clock-noncontinuous;
+						link-frequencies =
+							/bits/ 64 <493500000>;
+					};
+				};
+			};
+		};
+	};
+
+	csi_frag: fragment@1 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+
+			port{
+				csi1_ep: endpoint{
+					remote-endpoint = <&arducam_pivariety_0>;
+					clock-lanes = <0>;
+					data-lanes = <1 2>;
+					clock-noncontinuous;
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	clk_frag: fragment@3 {
+		target = <&cam1_clk>;
+		__overlay__ {
+			clock-frequency = <24000000>;
+			status = "okay";
+		};
+	};
+
+	fragment@4 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@5 {
+		target = <&csi1>;
+		__dormant__ {
+			compatible = "brcm,bcm2835-unicam-legacy";
+		};
+	};
+
+	__overrides__ {
+		rotation = <&arducam_pivariety>,"rotation:0";
+		orientation = <&arducam_pivariety>,"orientation:0";
+		media-controller = <0>,"!5";
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+		       <&clk_frag>, "target:0=",<&cam0_clk>,
+		       <&arducam_pivariety>, "clocks:0=",<&cam0_clk>,
+		       <&arducam_pivariety>, "VANA-supply:0=",<&cam0_reg>;
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/at86rf233-overlay.dts b/arch/arm/boot/dts/overlays/at86rf233-overlay.dts
new file mode 100644
index 000000000000..2caae2e21328
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/at86rf233-overlay.dts
@@ -0,0 +1,44 @@
+/dts-v1/;
+/plugin/;
+
+/* Overlay for Atmel AT86RF233 IEEE 802.15.4 WPAN transceiver on spi0.0 */
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			status = "okay";
+
+			lowpan0: at86rf233@0 {
+				compatible = "atmel,at86rf233";
+				reg = <0>;
+				interrupt-parent = <&gpio>;
+				interrupts = <23 4>; /* active high */
+				reset-gpio = <&gpio 24 1>;
+				sleep-gpio = <&gpio 25 1>;
+				spi-max-frequency = <3000000>;
+				xtal-trim = /bits/ 8 <0xf>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	__overrides__ {
+		interrupt = <&lowpan0>, "interrupts:0";
+		reset     = <&lowpan0>, "reset-gpio:4";
+		sleep     = <&lowpan0>, "sleep-gpio:4";
+		speed     = <&lowpan0>, "spi-max-frequency:0";
+		trim      = <&lowpan0>, "xtal-trim.0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/audioinjector-addons-overlay.dts b/arch/arm/boot/dts/overlays/audioinjector-addons-overlay.dts
new file mode 100644
index 000000000000..af72ea0b706a
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/audioinjector-addons-overlay.dts
@@ -0,0 +1,60 @@
+// Definitions for audioinjector.net audio add on soundcard
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			cs42448_mclk: codec-mclk {
+				compatible = "fixed-clock";
+				#clock-cells = <0>;
+				clock-frequency = <49152000>;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			cs42448: cs42448@48 {
+				#sound-dai-cells = <0>;
+				compatible = "cirrus,cs42448";
+				reg = <0x48>;
+				clocks = <&cs42448_mclk>;
+				clock-names = "mclk";
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&sound>;
+		snd: __overlay__ {
+			compatible = "ai,audioinjector-octo-soundcard";
+			mult-gpios = <&gpio 27 0>, <&gpio 22 0>, <&gpio 23 0>,
+				     <&gpio 24 0>;
+			reset-gpios = <&gpio 5 0>;
+			i2s-controller = <&i2s_clk_producer>;
+			codec = <&cs42448>;
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		non-stop-clocks = <&snd>, "non-stop-clocks?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/audioinjector-bare-i2s-overlay.dts b/arch/arm/boot/dts/overlays/audioinjector-bare-i2s-overlay.dts
new file mode 100644
index 000000000000..a536fbb1a985
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/audioinjector-bare-i2s-overlay.dts
@@ -0,0 +1,50 @@
+// Definitions for audioinjector.net audio soundcard
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			codec_bare: codec_bare {
+				compatible = "linux,spdif-dit";
+				#sound-dai-cells = <0>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "simple-audio-card";
+			i2s-controller = <&i2s_clk_producer>;
+			status = "okay";
+
+			simple-audio-card,name = "audioinjector-bare";
+			simple-audio-card,format = "i2s";
+
+			simple-audio-card,bitclock-master = <&dailink0_master>;
+			simple-audio-card,frame-master = <&dailink0_master>;
+
+			dailink0_master: simple-audio-card,cpu {
+				sound-dai = <&i2s_clk_producer>;
+				dai-tdm-slot-num = <2>;
+				dai-tdm-slot-width = <32>;
+			};
+
+			snd_codec: simple-audio-card,codec {
+					sound-dai = <&codec_bare>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/audioinjector-isolated-soundcard-overlay.dts b/arch/arm/boot/dts/overlays/audioinjector-isolated-soundcard-overlay.dts
new file mode 100644
index 000000000000..89faed778fcb
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/audioinjector-isolated-soundcard-overlay.dts
@@ -0,0 +1,55 @@
+// Definitions for audioinjector.net audio isolated soundcard
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			cs4272_mclk: codec-mclk {
+				compatible = "fixed-clock";
+				#clock-cells = <0>;
+				clock-frequency = <24576000>;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			cs4272: cs4271@10 {
+				#sound-dai-cells = <0>;
+				compatible = "cirrus,cs4271";
+				reg = <0x10>;
+				reset-gpio = <&gpio 5 0>;
+				clocks = <&cs4272_mclk>;
+				clock-names = "mclk";
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&sound>;
+		snd: __overlay__ {
+			compatible = "ai,audioinjector-isolated-soundcard";
+			mute-gpios = <&gpio 17 0>;
+			i2s-controller = <&i2s_clk_consumer>;
+			codec = <&cs4272>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/audioinjector-ultra-overlay.dts b/arch/arm/boot/dts/overlays/audioinjector-ultra-overlay.dts
new file mode 100644
index 000000000000..ee79441187bd
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/audioinjector-ultra-overlay.dts
@@ -0,0 +1,71 @@
+// Definitions for audioinjector.net audio add on soundcard
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			cs4265: cs4265@4e {
+				#sound-dai-cells = <0>;
+				compatible = "cirrus,cs4265";
+				reg = <0x4e>;
+				reset-gpios = <&gpio 5 0>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "simple-audio-card";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+
+			simple-audio-card,name = "audioinjector-ultra";
+
+			simple-audio-card,widgets =
+				"Line", "OUTPUTS",
+				"Line", "INPUTS";
+
+			simple-audio-card,routing =
+				"OUTPUTS","LINEOUTL",
+				"OUTPUTS","LINEOUTR",
+				"OUTPUTS","SPDIFOUT",
+				"LINEINL","INPUTS",
+				"LINEINR","INPUTS",
+				"MICL","INPUTS",
+				"MICR","INPUTS";
+
+			simple-audio-card,format = "i2s";
+
+			simple-audio-card,bitclock-master = <&sound_master>;
+			simple-audio-card,frame-master = <&sound_master>;
+
+			simple-audio-card,cpu {
+				sound-dai = <&i2s_clk_consumer>;
+				dai-tdm-slot-num = <2>;
+				dai-tdm-slot-width = <32>;
+			};
+
+			sound_master: simple-audio-card,codec {
+				sound-dai = <&cs4265>;
+				system-clock-frequency = <12288000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/audioinjector-wm8731-audio-overlay.dts b/arch/arm/boot/dts/overlays/audioinjector-wm8731-audio-overlay.dts
new file mode 100644
index 000000000000..417353b2798e
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/audioinjector-wm8731-audio-overlay.dts
@@ -0,0 +1,39 @@
+// Definitions for audioinjector.net audio add on soundcard
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			wm8731@1a {
+				#sound-dai-cells = <0>;
+				compatible = "wlf,wm8731";
+				reg = <0x1a>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "ai,audioinjector-pi-soundcard";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/audiosense-pi-overlay.dts b/arch/arm/boot/dts/overlays/audiosense-pi-overlay.dts
new file mode 100644
index 000000000000..34b1f883f80d
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/audiosense-pi-overlay.dts
@@ -0,0 +1,72 @@
+// Definitions for audiosense add on soundcard
+/dts-v1/;
+/plugin/;
+#include <dt-bindings/pinctrl/bcm2835.h>
+#include <dt-bindings/gpio/gpio.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			codec_reg_1v8: codec-reg-1v8 {
+				compatible = "regulator-fixed";
+				regulator-name = "tlv320aic3204_1v8";
+				regulator-min-microvolt = <1800000>;
+				regulator-max-microvolt = <1800000>;
+				regulator-always-on;
+			};
+
+			/* audio external oscillator */
+			codec_osc: codec_osc {
+				compatible = "fixed-clock";
+				#clock-cells = <0>;
+				clock-frequency = <12000000>;	/* 12 MHz */
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			codec: tlv320aic32x4@18 {
+				#sound-dai-cells = <0>;
+				compatible = "ti,tlv320aic32x4";
+				reg = <0x18>;
+
+				clocks = <&codec_osc>;
+				clock-names = "mclk";
+
+				iov-supply = <&vdd_3v3_reg>;
+				ldoin-supply = <&vdd_3v3_reg>;
+
+				gpio-controller;
+				#gpio-cells = <2>;
+				reset-gpios = <&gpio 26 GPIO_ACTIVE_HIGH>;
+
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "as,audiosense-pi";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/audremap-overlay.dts b/arch/arm/boot/dts/overlays/audremap-overlay.dts
new file mode 100644
index 000000000000..d72d2698f146
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/audremap-overlay.dts
@@ -0,0 +1,41 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+        compatible = "brcm,bcm2835";
+
+        fragment@0 {
+                target = <&audio_pins>;
+                frag0: __overlay__ {
+                        brcm,pins = <12 13>;
+                        brcm,function = <4>; /* alt0 alt0 */
+                };
+        };
+
+	fragment@1 {
+		target = <&chosen>;
+		__overlay__  {
+			bootargs = "snd_bcm2835.enable_headphones=1";
+		};
+	};
+
+	__overrides__ {
+		swap_lr = <&frag0>, "swap_lr?";
+		enable_jack = <&frag0>, "enable_jack?";
+		pins_12_13 = <&frag0>,"brcm,pins:0=12",
+		             <&frag0>,"brcm,pins:4=13",
+			     <&frag0>,"brcm,function:0=4";
+		pins_18_19 = <&frag0>,"brcm,pins:0=18",
+		             <&frag0>,"brcm,pins:4=19",
+			     <&frag0>,"brcm,function:0=2";
+		pins_40_41 = <&frag0>,"brcm,pins:0=40",
+		             <&frag0>,"brcm,pins:4=41",
+			     <&frag0>,"brcm,function:0=4";
+		pins_40_45 = <&frag0>,"brcm,pins:0=40",
+		             <&frag0>,"brcm,pins:4=45",
+			     <&frag0>,"brcm,function:0=4";
+		pins_none =  <0>,"-1",
+			     <&frag0>,"brcm,pins?=1",
+			     <&frag0>,"brcm,function?=1";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/audremap-pi5-overlay.dts b/arch/arm/boot/dts/overlays/audremap-pi5-overlay.dts
new file mode 100644
index 000000000000..aa74ecd26f15
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/audremap-pi5-overlay.dts
@@ -0,0 +1,65 @@
+/dts-v1/;
+/plugin/;
+
+/*
+ * Raspberry Pi 5 has a different Audio Out hardware from earlier Raspberry Pis.
+ * It can output only to GPIOs 12 and 13. We therefore enable it *only* when
+ * that particular GPIO mapping is requested here. To make it work with ASOC
+ * we must also define a "dummy" codec and a generic audio card.
+ */
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&rp1_audio_out>;
+		frag0: __overlay__ {
+			pinctrl-0 = <&rp1_audio_out_12_13>;
+			pinctrl-names = "default";
+			status = "ok";
+		};
+	};
+
+	fragment@1 {
+		 target-path = "/";
+		 __overlay__ {
+
+			 rp1_audio_out_codec: rp1_audio_out_codec@0 {
+			     compatible = "linux,spdif-dit";
+			     #sound-dai-cells = <0>;
+			     status = "ok";
+			 };
+
+			 rp1_audio_out_simple_card@0 {
+			     compatible = "simple-audio-card";
+			     simple-audio-card,name = "RP1-Audio-Out";
+			     #address-cells = <1>;
+			     #size-cells = <0>;
+			     status = "ok";
+
+			     simple-audio-card,dai-link@0 {
+				 reg = <0>;
+				 format = "left_j";
+				 bitclock-master = <&sndcpu0>;
+				 frame-master = <&sndcpu0>;
+
+				sndcpu0: cpu {
+					sound-dai = <&rp1_audio_out>;
+				};
+
+				codec {
+				    sound-dai = <&rp1_audio_out_codec>;
+				};
+			    };
+			};
+		};
+	};
+
+	__overrides__ {
+		swap_lr = <&frag0>, "swap_lr?";
+		pins_12_13 = <0>, "+0+1"; /* this is the default */
+		pins_18_19 = <0>, "-0-1"; /* sorry not supported */
+		pins_40_41 = <0>, "-0-1"; /* sorry not supported */
+		pins_40_45 = <0>, "-0-1"; /* sorry not supported */
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/balena-fin-overlay.dts b/arch/arm/boot/dts/overlays/balena-fin-overlay.dts
new file mode 100644
index 000000000000..8fc22587e69c
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/balena-fin-overlay.dts
@@ -0,0 +1,125 @@
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&mmcnr>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&sdio_pins>;
+			bus-width = <4>;
+			brcm,overclock-50 = <35>;
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			sdio_pins: sdio_ovl_pins {
+				brcm,pins = <34 35 36 37 38 39>;
+				brcm,function = <7>; /* ALT3 = SD1 */
+				brcm,pull = <0 2 2 2 2 2>;
+			};
+
+			power_ctrl_pins: power_ctrl_pins {
+				brcm,pins = <40>;
+				brcm,function = <1>; // out
+			};
+		};
+	};
+
+	fragment@2 {
+		target-path = "/";
+		__overlay__ {
+			// We should switch to mmc-pwrseq-sd8787 after making it
+			// compatible with sd8887
+			// Currently that module requires two GPIOs to function since it
+			// targets a slightly different chip
+			power_ctrl: power_ctrl {
+				compatible = "gpio-poweroff";
+				gpios = <&gpio 40 1>;
+				force;
+				pinctrl-names = "default";
+				pinctrl-0 = <&power_ctrl_pins>;
+			};
+
+			i2c_soft: i2c@0 {
+				compatible = "i2c-gpio";
+				gpios = <&gpio 43 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN) /* sda */
+				         &gpio 42 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN) /* scl */>;
+				i2c-gpio,delay-us = <5>;
+				i2c-gpio,scl-open-drain;
+				i2c-gpio,sda-open-drain;
+				#address-cells = <1>;
+				#size-cells = <0>;
+			};
+
+			sd8xxx-wlan {
+				drvdbg = <0x6>;
+				drv_mode = <0x1>;
+				cfg80211_wext = <0xf>;
+				sta_name = "wlan";
+				wfd_name = "p2p";
+				cal_data_cfg = "none";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&i2c_soft>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			gpio_expander: gpio_expander@20 {
+				compatible = "nxp,pca9554";
+				gpio-controller;
+				#gpio-cells = <2>;
+				reg = <0x20>;
+				status = "okay";
+			};
+
+			// rtc clock
+			ds1307: ds1307@68 {
+				compatible = "dallas,ds1307";
+				reg = <0x68>;
+				status = "okay";
+			};
+
+			// RGB LEDs (>= v1.1.0)
+			pca9633: pca9633@62 {
+				compatible = "nxp,pca9633";
+				reg = <0x62>;
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				red@0 {
+					label = "red";
+					reg = <0>;
+					linux,default-trigger = "none";
+				};
+				green@1 {
+					label = "green";
+					reg = <1>;
+					linux,default-trigger = "none";
+				};
+				blue@2 {
+					label = "blue";
+					reg = <2>;
+					linux,default-trigger = "none";
+				};
+				unused@3 {
+					label = "unused";
+					reg = <3>;
+					linux,default-trigger = "none";
+				};
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/bcm2712d0-overlay.dts b/arch/arm/boot/dts/overlays/bcm2712d0-overlay.dts
new file mode 100644
index 000000000000..1ee74234e805
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/bcm2712d0-overlay.dts
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&gio>;
+		__overlay__ {
+			brcm,gpio-bank-widths = <32 4>;
+		};
+	};
+
+	fragment@1 {
+		target = <&gio_aon>;
+		__overlay__ {
+			brcm,gpio-bank-widths = <15 6>;
+		};
+	};
+
+	fragment@2 {
+		target = <&pinctrl>;
+		__overlay__ {
+			compatible = "brcm,bcm2712d0-pinctrl";
+			reg = <0x7d504100 0x20>;
+		};
+	};
+
+	fragment@3 {
+		target = <&pinctrl_aon>;
+		__overlay__ {
+			compatible = "brcm,bcm2712d0-aon-pinctrl";
+			reg = <0x7d510700 0x1c>;
+		};
+	};
+
+	fragment@4 {
+		target = <&uart10>;
+		__overlay__ {
+			interrupts = <GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>;
+		};
+	};
+
+	fragment@5 {
+		target = <&spi10>;
+		__overlay__ {
+			dmas = <&dma40 3>, <&dma40 4>;
+		};
+	};
+
+	fragment@6 {
+		target = <&hdmi0>;
+		__overlay__ {
+			dmas = <&dma40 (12|(1<<30)|(1<<24)|(10<<16)|(15<<20))>;
+		};
+	};
+
+	fragment@7 {
+		target = <&hdmi1>;
+		__overlay__ {
+			dmas = <&dma40 (13|(1<<30)|(1<<24)|(10<<16)|(15<<20))>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/camera-mux-2port-overlay.dts b/arch/arm/boot/dts/overlays/camera-mux-2port-overlay.dts
new file mode 100644
index 000000000000..97d1988dd984
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/camera-mux-2port-overlay.dts
@@ -0,0 +1,545 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Overlay to configure a 2 port camera multiplexer
+//
+// Configuration is based on the Arducam Doubleplexer
+// which uses a PCA9543 I2C multiplexer to handle the
+// I2C, and GPIO 4 to control the MIPI mux, and GPIO 17
+// to enable the CSI-2 mux output (gpio-hog).
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2835";
+
+	/* Fragments that complete the individual sensor fragments */
+	/* IMX290 */
+	fragment@0 {
+		target = <&imx290_0_ep>;
+		__overlay__ {
+			data-lanes = <1 2>;
+			link-frequencies =
+				/bits/ 64 <445500000 297000000>;
+		};
+	};
+
+	fragment@1 {
+		target = <&imx290_1_ep>;
+		__overlay__ {
+			data-lanes = <1 2>;
+			link-frequencies =
+				/bits/ 64 <445500000 297000000>;
+		};
+	};
+
+	/* IMX477 */
+	fragment@10 {
+		target = <&imx477_0>;
+		__overlay__ {
+			compatible = "sony,imx477";
+		};
+	};
+
+	fragment@11 {
+		target = <&imx477_1>;
+		__overlay__ {
+			compatible = "sony,imx477";
+		};
+	};
+
+	/* Additional fragments affecting the mux nodes */
+	fragment@100 {
+		target = <&mux_in0>;
+		__dormant__ {
+			data-lanes = <1>;
+		};
+	};
+	fragment@101 {
+		target = <&mux_in0>;
+		__overlay__ {
+			data-lanes = <1 2>;
+		};
+	};
+
+	fragment@102 {
+		target = <&mux_in1>;
+		__dormant__ {
+			data-lanes = <1>;
+		};
+	};
+	fragment@103 {
+		target = <&mux_in1>;
+		__overlay__ {
+			data-lanes = <1 2>;
+		};
+	};
+
+	/* Mux define */
+	i2c_frag: fragment@200 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			pca@70 {
+				reg = <0x70>;
+				compatible = "nxp,pca9543";
+
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				i2c@0 {
+					reg = <0>;
+					#address-cells = <1>;
+					#size-cells = <0>;
+
+					#define cam_node arducam_64mp_0
+					#define cam_endpoint arducam_64mp_0_ep
+					#define vcm_node arducam_64mp_0_vcm
+					#define cam1_clk clk_24mhz
+					#include "arducam-64mp.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+
+					#define cam_node imx219_0
+					#define cam_endpoint imx219_0_ep
+					#define cam1_clk clk_24mhz
+					#include "imx219.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx477_0
+					#define cam_endpoint imx477_0_ep
+					#define cam1_clk clk_24mhz
+					#include "imx477_378.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx519_0
+					#define cam_endpoint imx519_0_ep
+					#define vcm_node imx519_0_vcm
+					#define cam1_clk clk_24mhz
+					#include "imx519.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+
+					#define cam_node imx708_0
+					#define cam_endpoint imx708_0_ep
+					#define vcm_node imx708_0_vcm
+					#define cam1_clk clk_24mhz
+					#include "imx708.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+
+					#define cam_node ov5647_0
+					#define cam_endpoint ov5647_0_ep
+					#define cam1_clk clk_25mhz
+					#include "ov5647.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov7251_0
+					#define cam_endpoint ov7251_0_ep
+					#define cam1_clk clk_24mhz
+					#include "ov7251.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov9281_0
+					#define cam_endpoint ov9281_0_ep
+					#define cam1_clk clk_24mhz
+					#include "ov9281.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx258_0
+					#define cam_endpoint imx258_0_ep
+					#define cam1_clk clk_24mhz
+					#include "imx258.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx290_0
+					#define cam_endpoint imx290_0_ep
+					#define cam1_clk clk_imx290
+					#include "imx290_327.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov2311_0
+					#define cam_endpoint ov2311_0_ep
+					#define cam1_clk clk_24mhz
+					#include "ov2311.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov64a40_0
+					#define cam_endpoint ov64a40_0_ep
+					#define vcm_node ov64a40_0_vcm
+					#define cam1_clk clk_24mhz
+					#include "ov64a40.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+				};
+
+				i2c@1 {
+					reg = <1>;
+					#address-cells = <1>;
+					#size-cells = <0>;
+
+					#define cam_node arducam_64mp_1
+					#define cam_endpoint arducam_64mp_1_ep
+					#define vcm_node arducam_64mp_1_vcm
+					#define cam1_clk clk_24mhz
+					#include "arducam-64mp.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+
+					#define cam_node imx219_1
+					#define cam_endpoint imx219_1_ep
+					#define cam1_clk clk_24mhz
+					#include "imx219.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx477_1
+					#define cam_endpoint imx477_1_ep
+					#define cam1_clk clk_24mhz
+					#include "imx477_378.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx519_1
+					#define cam_endpoint imx519_1_ep
+					#define vcm_node imx519_1_vcm
+					#define cam1_clk clk_24mhz
+					#include "imx519.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+
+					#define cam_node imx708_1
+					#define cam_endpoint imx708_1_ep
+					#define vcm_node imx708_1_vcm
+					#define cam1_clk clk_24mhz
+					#include "imx708.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+
+					#define cam_node ov5647_1
+					#define cam_endpoint ov5647_1_ep
+					#define cam1_clk clk_25mhz
+					#include "ov5647.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov7251_1
+					#define cam_endpoint ov7251_1_ep
+					#define cam1_clk clk_24mhz
+					#include "ov7251.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov9281_1
+					#define cam_endpoint ov9281_1_ep
+					#define cam1_clk clk_24mhz
+					#include "ov9281.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx258_1
+					#define cam_endpoint imx258_1_ep
+					#define cam1_clk clk_24mhz
+					#include "imx258.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx290_1
+					#define cam_endpoint imx290_1_ep
+					#define cam1_clk clk_imx290
+					#include "imx290_327.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov2311_1
+					#define cam_endpoint ov2311_1_ep
+					#define cam1_clk clk_24mhz
+					#include "ov2311.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov64a40_1
+					#define cam_endpoint ov64a40_1_ep
+					#define vcm_node ov64a40_1_vcm
+					#define cam1_clk clk_24mhz
+					#include "ov64a40.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+				};
+			};
+		};
+	};
+
+	csi_frag: fragment@201 {
+		target = <&csi1>;
+		__overlay__ {
+			status = "okay";
+
+			port {
+				csi1_ep: endpoint {
+					remote-endpoint = <&mux_out>;
+					clock-lanes = <0>;
+					data-lanes = <1 2>;
+				};
+			};
+		};
+	};
+
+	fragment@202 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@203 {
+		target-path="/";
+		__overlay__ {
+			mux: mux-controller {
+				compatible = "gpio-mux";
+				#mux-control-cells = <0>;
+
+				mux-gpios = <&gpio 4 GPIO_ACTIVE_HIGH>;
+			};
+
+			video-mux {
+				compatible = "video-mux";
+				mux-controls = <&mux>;
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				port@0 {
+					reg = <0>;
+
+					mux_in0: endpoint {
+						clock-lanes = <0>;
+					};
+				};
+
+				port@1 {
+					reg = <1>;
+
+					mux_in1: endpoint {
+						clock-lanes = <0>;
+					};
+				};
+
+				port@2 {
+					reg = <2>;
+
+					mux_out: endpoint {
+						remote-endpoint = <&csi1_ep>;
+						clock-lanes = <0>;
+					};
+				};
+			};
+
+			clk_24mhz: clk_24mhz {
+				compatible = "fixed-clock";
+				#clock-cells = <0>;
+
+				clock-frequency = <24000000>;
+				status = "okay";
+			};
+
+			clk_25mhz: clk_25mhz {
+				compatible = "fixed-clock";
+				#clock-cells = <0>;
+
+				clock-frequency = <25000000>;
+				status = "okay";
+			};
+
+			clk_imx290: clk_imx290 {
+				compatible = "fixed-clock";
+				#clock-cells = <0>;
+
+				clock-frequency = <37125000>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@204 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@205 {
+		target = <&gpio>;
+		__overlay__ {
+			mipi_sw_oe_hog {
+				gpio-hog;
+				gpios = <17 GPIO_ACTIVE_LOW>;
+				output-high;
+			};
+		};
+	};
+
+	__overrides__ {
+		cam0-arducam-64mp = <&mux_in0>, "remote-endpoint:0=",<&arducam_64mp_0_ep>,
+				    <&arducam_64mp_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+				    <&mux_in0>, "clock-noncontinuous?",
+				    <&arducam_64mp_0>, "status=okay",
+				    <&arducam_64mp_0_vcm>, "status=okay",
+				    <&arducam_64mp_0>,"lens-focus:0=", <&arducam_64mp_0_vcm>;
+		cam0-imx219 = <&mux_in0>, "remote-endpoint:0=",<&imx219_0_ep>,
+			      <&imx219_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&mux_in0>, "clock-noncontinuous?",
+			      <&imx219_0>, "status=okay";
+		cam0-imx477 = <&mux_in0>, "remote-endpoint:0=",<&imx477_0_ep>,
+			      <&imx477_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&mux_in0>, "clock-noncontinuous?",
+			      <&imx477_0>, "status=okay";
+		cam0-imx519 = <&mux_in0>, "remote-endpoint:0=",<&imx519_0_ep>,
+			      <&imx519_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&mux_in0>, "clock-noncontinuous?",
+			      <&imx519_0>, "status=okay",
+				  <&imx519_0_vcm>, "status=okay",
+			      <&imx519_0>,"lens-focus:0=", <&imx519_0_vcm>;
+		cam0-imx708 = <&mux_in0>, "remote-endpoint:0=",<&imx708_0_ep>,
+			      <&imx708_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&mux_in0>, "clock-noncontinuous?",
+			      <&imx708_0>, "status=okay",
+			      <&imx708_0_vcm>, "status=okay",
+			      <&imx708_0>,"lens-focus:0=", <&imx708_0_vcm>;
+		cam0-ov5647 = <&mux_in0>, "remote-endpoint:0=",<&ov5647_0_ep>,
+			      <&ov5647_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&mux_in0>, "clock-noncontinuous?",
+			      <&ov5647_0>, "status=okay";
+		cam0-ov7251 = <&mux_in0>, "remote-endpoint:0=",<&ov7251_0_ep>,
+			      <&ov7251_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&ov7251_0>, "status=okay",
+			      <0>,"+100-101";
+		cam0-ov9281 = <&mux_in0>, "remote-endpoint:0=",<&ov9281_0_ep>,
+			      <&ov9281_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&ov9281_0>, "status=okay";
+		cam0-imx258 = <&mux_in0>, "remote-endpoint:0=",<&imx258_0_ep>,
+			      <&imx258_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&imx258_0>, "status=okay";
+		cam0-imx290 = <&mux_in0>, "remote-endpoint:0=",<&imx290_0_ep>,
+			      <&imx290_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&imx290_0>, "status=okay";
+		cam0-ov2311 = <&mux_in0>, "remote-endpoint:0=",<&ov2311_0_ep>,
+			      <&ov2311_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&ov2311_0>, "status=okay";
+		cam0-ov64a40 = <&mux_in0>, "remote-endpoint:0=",<&ov64a40_0_ep>,
+			      <&ov64a40_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&mux_in0>, "clock-noncontinuous?",
+			      <&ov64a40_0>, "status=okay",
+			      <&ov64a40_0_vcm>, "status=okay",
+			      <&ov64a40_0>,"lens-focus:0=", <&ov64a40_0_vcm>;
+
+		cam1-arducam-64mp = <&mux_in1>, "remote-endpoint:0=",<&arducam_64mp_1_ep>,
+				    <&arducam_64mp_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+				    <&mux_in1>, "clock-noncontinuous?",
+				    <&arducam_64mp_1>, "status=okay",
+				    <&arducam_64mp_1_vcm>, "status=okay",
+				    <&arducam_64mp_1>,"lens-focus:0=", <&arducam_64mp_1_vcm>;
+		cam1-imx219 = <&mux_in1>, "remote-endpoint:0=",<&imx219_1_ep>,
+			      <&imx219_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&mux_in1>, "clock-noncontinuous?",
+			      <&imx219_1>, "status=okay";
+		cam1-imx477 = <&mux_in1>, "remote-endpoint:0=",<&imx477_1_ep>,
+			      <&imx477_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&mux_in1>, "clock-noncontinuous?",
+			      <&imx477_1>, "status=okay";
+		cam1-imx519 = <&mux_in1>, "remote-endpoint:0=",<&imx519_1_ep>,
+			      <&imx519_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&mux_in1>, "clock-noncontinuous?",
+			      <&imx519_1>, "status=okay",
+			      <&imx519_1_vcm>, "status=okay",
+			      <&imx519_1>,"lens-focus:0=", <&imx519_1_vcm>;
+		cam1-imx708 = <&mux_in1>, "remote-endpoint:0=",<&imx708_1_ep>,
+			      <&imx708_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&mux_in1>, "clock-noncontinuous?",
+			      <&imx708_1>, "status=okay",
+			      <&imx708_1_vcm>, "status=okay",
+			      <&imx708_1>,"lens-focus:0=", <&imx708_1_vcm>;
+		cam1-ov5647 = <&mux_in1>, "remote-endpoint:0=",<&ov5647_1_ep>,
+			      <&ov5647_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&mux_in1>, "clock-noncontinuous?",
+			      <&ov5647_1>, "status=okay";
+		cam1-ov7251 = <&mux_in1>, "remote-endpoint:0=",<&ov7251_1_ep>,
+			      <&ov7251_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&ov7251_1>, "status=okay",
+			      <0>,"+102-103";
+		cam1-ov9281 = <&mux_in1>, "remote-endpoint:0=",<&ov9281_1_ep>,
+			      <&ov9281_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&ov9281_1>, "status=okay";
+		cam1-imx258 = <&mux_in1>, "remote-endpoint:0=",<&imx258_1_ep>,
+			      <&imx258_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&imx258_1>, "status=okay";
+		cam1-imx290 = <&mux_in1>, "remote-endpoint:0=",<&imx290_1_ep>,
+			      <&imx290_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&imx290_1>, "status=okay";
+		cam1-ov2311 = <&mux_in1>, "remote-endpoint:0=",<&ov2311_1_ep>,
+			      <&ov2311_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&ov2311_1>, "status=okay";
+		cam1-ov64a40 = <&mux_in1>, "remote-endpoint:0=",<&ov64a40_1_ep>,
+			      <&ov64a40_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&mux_in1>, "clock-noncontinuous?",
+			      <&ov64a40_1>, "status=okay",
+			      <&ov64a40_1_vcm>, "status=okay",
+			      <&ov64a40_1>,"lens-focus:0=", <&ov64a40_1_vcm>;
+
+		cam0-imx290-clk-freq = <&clk_imx290>,"clock-frequency:0",
+				       <&imx290_0>,"clock-frequency:0";
+		cam1-imx290-clk-freq = <&clk_imx290>,"clock-frequency:0",
+				       <&imx290_1>,"clock-frequency:0";
+
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>;
+
+		cam0-sync-source = <&imx477_0>, "trigger-mode:0=1";
+		cam0-sync-sink = <&imx477_0>, "trigger-mode:0=2";
+		cam1-sync-source = <&imx477_1>, "trigger-mode:0=1";
+		cam1-sync-sink = <&imx477_1>, "trigger-mode:0=2";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/camera-mux-4port-overlay.dts b/arch/arm/boot/dts/overlays/camera-mux-4port-overlay.dts
new file mode 100644
index 000000000000..dbbb476f51e7
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/camera-mux-4port-overlay.dts
@@ -0,0 +1,954 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+// Overlay to configure a 4 port camera multiplexer
+//
+// Configuration is based on the Arducam 4 channel multiplexer
+// which uses a PCA9543 I2C multiplexer to handle the
+// I2C, and GPIOs 4, 17, and 18 to control the MIPI muxes.
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2835";
+
+	/* Fragments that complete the individual sensor fragments */
+	/* IMX290 */
+	fragment@0 {
+		target = <&imx290_0_ep>;
+		__overlay__ {
+			data-lanes = <1 2>;
+			link-frequencies =
+				/bits/ 64 <445500000 297000000>;
+		};
+	};
+
+	fragment@1 {
+		target = <&imx290_1_ep>;
+		__overlay__ {
+			data-lanes = <1 2>;
+			link-frequencies =
+				/bits/ 64 <445500000 297000000>;
+		};
+	};
+
+	fragment@2 {
+		target = <&imx290_2_ep>;
+		__overlay__ {
+			data-lanes = <1 2>;
+			link-frequencies =
+				/bits/ 64 <445500000 297000000>;
+		};
+	};
+
+	fragment@3 {
+		target = <&imx290_3_ep>;
+		__overlay__ {
+			data-lanes = <1 2>;
+			link-frequencies =
+				/bits/ 64 <445500000 297000000>;
+		};
+	};
+
+	/* IMX477 */
+	fragment@10 {
+		target = <&imx477_0>;
+		__overlay__ {
+			compatible = "sony,imx477";
+		};
+	};
+
+	fragment@11 {
+		target = <&imx477_1>;
+		__overlay__ {
+			compatible = "sony,imx477";
+		};
+	};
+
+	fragment@12 {
+		target = <&imx477_2>;
+		__overlay__ {
+			compatible = "sony,imx477";
+		};
+	};
+
+	fragment@13 {
+		target = <&imx477_3>;
+		__overlay__ {
+			compatible = "sony,imx477";
+		};
+	};
+
+	/* Additional fragments affecting the mux nodes */
+	fragment@100 {
+		target = <&mux_in0>;
+		__dormant__ {
+			data-lanes = <1>;
+		};
+	};
+	fragment@101 {
+		target = <&mux_in0>;
+		__overlay__ {
+			data-lanes = <1 2>;
+		};
+	};
+
+	fragment@102 {
+		target = <&mux_in1>;
+		__dormant__ {
+			data-lanes = <1>;
+		};
+	};
+	fragment@103 {
+		target = <&mux_in1>;
+		__overlay__ {
+			data-lanes = <1 2>;
+		};
+	};
+
+	fragment@104 {
+		target = <&mux_in2>;
+		__dormant__ {
+			data-lanes = <1>;
+		};
+	};
+	fragment@105 {
+		target = <&mux_in2>;
+		__overlay__ {
+			data-lanes = <1 2>;
+		};
+	};
+
+	fragment@106 {
+		target = <&mux_in3>;
+		__dormant__ {
+			data-lanes = <1>;
+		};
+	};
+	fragment@107 {
+		target = <&mux_in3>;
+		__overlay__ {
+			data-lanes = <1 2>;
+		};
+	};
+
+	/* Mux define */
+	i2c_frag: fragment@200 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			pca@70 {
+				reg = <0x70>;
+				compatible = "nxp,pca9544";
+
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				i2c@0 {
+					reg = <0>;
+					#address-cells = <1>;
+					#size-cells = <0>;
+
+					#define cam_node arducam_64mp_0
+					#define cam_endpoint arducam_64mp_0_ep
+					#define vcm_node arducam_64mp_0_vcm
+					#define cam1_clk clk_24mhz
+					#include "arducam-64mp.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+
+					#define cam_node imx219_0
+					#define cam_endpoint imx219_0_ep
+					#define cam1_clk clk_24mhz
+					#include "imx219.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx477_0
+					#define cam_endpoint imx477_0_ep
+					#define cam1_clk clk_24mhz
+					#include "imx477_378.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx519_0
+					#define cam_endpoint imx519_0_ep
+					#define vcm_node imx519_0_vcm
+					#define cam1_clk clk_24mhz
+					#include "imx519.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+
+					#define cam_node imx708_0
+					#define cam_endpoint imx708_0_ep
+					#define vcm_node imx708_0_vcm
+					#define cam1_clk clk_24mhz
+					#include "imx708.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+
+					#define cam_node ov5647_0
+					#define cam_endpoint ov5647_0_ep
+					#define cam1_clk clk_25mhz
+					#include "ov5647.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov7251_0
+					#define cam_endpoint ov7251_0_ep
+					#define cam1_clk clk_24mhz
+					#include "ov7251.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov9281_0
+					#define cam_endpoint ov9281_0_ep
+					#define cam1_clk clk_24mhz
+					#include "ov9281.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx258_0
+					#define cam_endpoint imx258_0_ep
+					#define cam1_clk clk_24mhz
+					#include "imx258.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx290_0
+					#define cam_endpoint imx290_0_ep
+					#define cam1_clk clk_imx290
+					#include "imx290_327.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov2311_0
+					#define cam_endpoint ov2311_0_ep
+					#define cam1_clk clk_24mhz
+					#include "ov2311.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov64a40_0
+					#define cam_endpoint ov64a40_0_ep
+					#define vcm_node ov64a40_0_vcm
+					#define cam1_clk clk_24mhz
+					#include "ov64a40.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+				};
+
+				i2c@1 {
+					reg = <1>;
+					#address-cells = <1>;
+					#size-cells = <0>;
+
+					#define cam_node arducam_64mp_1
+					#define cam_endpoint arducam_64mp_1_ep
+					#define vcm_node arducam_64mp_1_vcm
+					#define cam1_clk clk_24mhz
+					#include "arducam-64mp.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+
+					#define cam_node imx219_1
+					#define cam_endpoint imx219_1_ep
+					#define cam1_clk clk_24mhz
+					#include "imx219.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx477_1
+					#define cam_endpoint imx477_1_ep
+					#define cam1_clk clk_24mhz
+					#include "imx477_378.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx519_1
+					#define cam_endpoint imx519_1_ep
+					#define vcm_node imx519_1_vcm
+					#define cam1_clk clk_24mhz
+					#include "imx519.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+
+					#define cam_node imx708_1
+					#define cam_endpoint imx708_1_ep
+					#define vcm_node imx708_1_vcm
+					#define cam1_clk clk_24mhz
+					#include "imx708.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+
+					#define cam_node ov5647_1
+					#define cam_endpoint ov5647_1_ep
+					#define cam1_clk clk_25mhz
+					#include "ov5647.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov7251_1
+					#define cam_endpoint ov7251_1_ep
+					#define cam1_clk clk_24mhz
+					#include "ov7251.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov9281_1
+					#define cam_endpoint ov9281_1_ep
+					#define cam1_clk clk_24mhz
+					#include "ov9281.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx258_1
+					#define cam_endpoint imx258_1_ep
+					#define cam1_clk clk_24mhz
+					#include "imx258.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx290_1
+					#define cam_endpoint imx290_1_ep
+					#define cam1_clk clk_imx290
+					#include "imx290_327.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov2311_1
+					#define cam_endpoint ov2311_1_ep
+					#define cam1_clk clk_24mhz
+					#include "ov2311.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov64a40_1
+					#define cam_endpoint ov64a40_1_ep
+					#define vcm_node ov64a40_1_vcm
+					#define cam1_clk clk_24mhz
+					#include "ov64a40.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+				};
+
+				i2c@2 {
+					reg = <2>;
+					#address-cells = <1>;
+					#size-cells = <0>;
+
+					#define cam_node arducam_64mp_2
+					#define cam_endpoint arducam_64mp_2_ep
+					#define vcm_node arducam_64mp_2_vcm
+					#define cam1_clk clk_24mhz
+					#include "arducam-64mp.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+
+					#define cam_node imx219_2
+					#define cam_endpoint imx219_2_ep
+					#define cam1_clk clk_24mhz
+					#include "imx219.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx477_2
+					#define cam_endpoint imx477_2_ep
+					#define cam1_clk clk_24mhz
+					#include "imx477_378.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx519_2
+					#define cam_endpoint imx519_2_ep
+					#define vcm_node imx519_2_vcm
+					#define cam1_clk clk_24mhz
+					#include "imx519.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+
+					#define cam_node imx708_2
+					#define cam_endpoint imx708_2_ep
+					#define vcm_node imx708_2_vcm
+					#define cam1_clk clk_24mhz
+					#include "imx708.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+
+					#define cam_node ov5647_2
+					#define cam_endpoint ov5647_2_ep
+					#define cam1_clk clk_25mhz
+					#include "ov5647.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov7251_2
+					#define cam_endpoint ov7251_2_ep
+					#define cam1_clk clk_24mhz
+					#include "ov7251.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov9281_2
+					#define cam_endpoint ov9281_2_ep
+					#define cam1_clk clk_24mhz
+					#include "ov9281.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx258_2
+					#define cam_endpoint imx258_2_ep
+					#define cam1_clk clk_24mhz
+					#include "imx258.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx290_2
+					#define cam_endpoint imx290_2_ep
+					#define cam1_clk clk_imx290
+					#include "imx290_327.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov2311_2
+					#define cam_endpoint ov2311_2_ep
+					#define cam1_clk clk_24mhz
+					#include "ov2311.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov64a40_2
+					#define cam_endpoint ov64a40_2_ep
+					#define vcm_node ov64a40_2_vcm
+					#define cam1_clk clk_24mhz
+					#include "ov64a40.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+				};
+
+				i2c@3 {
+					reg = <3>;
+					#address-cells = <1>;
+					#size-cells = <0>;
+
+					#define cam_node arducam_64mp_3
+					#define cam_endpoint arducam_64mp_3_ep
+					#define vcm_node arducam_64mp_3_vcm
+					#define cam1_clk clk_24mhz
+					#include "arducam-64mp.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+
+					#define cam_node imx219_3
+					#define cam_endpoint imx219_3_ep
+					#define cam1_clk clk_24mhz
+					#include "imx219.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx477_3
+					#define cam_endpoint imx477_3_ep
+					#define cam1_clk clk_24mhz
+					#include "imx477_378.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx519_3
+					#define cam_endpoint imx519_3_ep
+					#define vcm_node imx519_3_vcm
+					#define cam1_clk clk_24mhz
+					#include "imx519.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+
+					#define cam_node imx708_3
+					#define cam_endpoint imx708_3_ep
+					#define vcm_node imx708_3_vcm
+					#define cam1_clk clk_24mhz
+					#include "imx708.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+
+					#define cam_node ov5647_3
+					#define cam_endpoint ov5647_3_ep
+					#define cam1_clk clk_25mhz
+					#include "ov5647.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov7251_3
+					#define cam_endpoint ov7251_3_ep
+					#define cam1_clk clk_24mhz
+					#include "ov7251.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov9281_3
+					#define cam_endpoint ov9281_3_ep
+					#define cam1_clk clk_24mhz
+					#include "ov9281.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx258_3
+					#define cam_endpoint imx258_3_ep
+					#define cam1_clk clk_24mhz
+					#include "imx258.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node imx290_3
+					#define cam_endpoint imx290_3_ep
+					#define cam1_clk clk_imx290
+					#include "imx290_327.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov2311_3
+					#define cam_endpoint ov2311_3_ep
+					#define cam1_clk clk_24mhz
+					#include "ov2311.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef cam1_clk
+
+					#define cam_node ov64a40_3
+					#define cam_endpoint ov64a40_3_ep
+					#define vcm_node ov64a40_3_vcm
+					#define cam1_clk clk_24mhz
+					#include "ov64a40.dtsi"
+					#undef cam_node
+					#undef cam_endpoint
+					#undef vcm_node
+					#undef cam1_clk
+				};
+			};
+		};
+	};
+
+	csi_frag: fragment@201 {
+		target = <&csi1>;
+		__overlay__ {
+			status = "okay";
+
+			port {
+				csi1_ep: endpoint {
+					remote-endpoint = <&mux_out>;
+					clock-lanes = <0>;
+					data-lanes = <1 2>;
+				};
+			};
+		};
+	};
+
+	fragment@202 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@203 {
+		target-path="/";
+		__overlay__ {
+			mux: mux-controller {
+				compatible = "gpio-mux";
+				#mux-control-cells = <0>;
+
+				/* SEL, En2, En1 */
+				mux-gpios = <&gpio 4 GPIO_ACTIVE_HIGH>,
+					    <&gpio 18 GPIO_ACTIVE_HIGH>,
+					    <&gpio 17 GPIO_ACTIVE_HIGH>;
+			};
+
+			video-mux {
+				compatible = "video-mux";
+				mux-controls = <&mux>;
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				/* GPIO mappings settings for selecting the different
+				 * camera connectors are not direct, hence port@ values
+				 * are not straight forward.
+				 */
+				port@2 {
+					/* Port A - GPIO 17 = 0, GPIO 18 = 1,GPIO 4 = 0 */
+					reg = <2>;
+
+					mux_in0: endpoint {
+						clock-lanes = <0>;
+					};
+				};
+
+				port@3 {
+					/* Port B - GPIO 17 = 0, GPIO 18 = 1,GPIO 4 = 1 */
+					reg = <3>;
+
+					mux_in1: endpoint {
+						clock-lanes = <0>;
+					};
+				};
+
+				port@4 {
+					/* Port C - GPIO 17 = 1, GPIO 18 = 0, GPIO 4 = 0 */
+					reg = <4>;
+
+					mux_in2: endpoint {
+						clock-lanes = <0>;
+					};
+				};
+
+				port@5 {
+					/* Port D - GPIO 17 = 1, GPIO 18 = 0, GPIO 4 = 1 */
+					reg = <5>;
+
+					mux_in3: endpoint {
+						clock-lanes = <0>;
+					};
+				};
+
+				port@6 {
+					/* Output port needs to be the highest port number */
+					reg = <6>;
+
+					mux_out: endpoint {
+						remote-endpoint = <&csi1_ep>;
+						clock-lanes = <0>;
+					};
+				};
+			};
+
+			clk_24mhz: clk_24mhz {
+				compatible = "fixed-clock";
+				#clock-cells = <0>;
+
+				clock-frequency = <24000000>;
+				status = "okay";
+			};
+
+			clk_25mhz: clk_25mhz {
+				compatible = "fixed-clock";
+				#clock-cells = <0>;
+
+				clock-frequency = <25000000>;
+				status = "okay";
+			};
+
+			clk_imx290: clk_imx290 {
+				compatible = "fixed-clock";
+				#clock-cells = <0>;
+
+				clock-frequency = <37125000>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@204 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		cam0-arducam-64mp = <&mux_in0>, "remote-endpoint:0=",<&arducam_64mp_0_ep>,
+				    <&arducam_64mp_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+				    <&mux_in0>, "clock-noncontinuous?",
+				    <&arducam_64mp_0>, "status=okay",
+				    <&arducam_64mp_0_vcm>, "status=okay",
+				    <&arducam_64mp_0>,"lens-focus:0=", <&arducam_64mp_0_vcm>;
+		cam0-imx219 = <&mux_in0>, "remote-endpoint:0=",<&imx219_0_ep>,
+			      <&imx219_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&mux_in0>, "clock-noncontinuous?",
+			      <&imx219_0>, "status=okay";
+		cam0-imx477 = <&mux_in0>, "remote-endpoint:0=",<&imx477_0_ep>,
+			      <&imx477_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&mux_in0>, "clock-noncontinuous?",
+			      <&imx477_0>, "status=okay";
+		cam0-imx519 = <&mux_in0>, "remote-endpoint:0=",<&imx519_0_ep>,
+			      <&imx519_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&mux_in0>, "clock-noncontinuous?",
+			      <&imx519_0>, "status=okay",
+			      <&imx519_0_vcm>, "status=okay",
+			      <&imx519_0>,"lens-focus:0=", <&imx519_0_vcm>;
+		cam0-imx708 = <&mux_in0>, "remote-endpoint:0=",<&imx708_0_ep>,
+			      <&imx708_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&mux_in0>, "clock-noncontinuous?",
+			      <&imx708_0>, "status=okay",
+			      <&imx708_0_vcm>, "status=okay",
+			      <&imx708_0>,"lens-focus:0=", <&imx708_0_vcm>;
+		cam0-ov5647 = <&mux_in0>, "remote-endpoint:0=",<&ov5647_0_ep>,
+			      <&ov5647_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&mux_in0>, "clock-noncontinuous?",
+			      <&ov5647_0>, "status=okay";
+		cam0-ov7251 = <&mux_in0>, "remote-endpoint:0=",<&ov7251_0_ep>,
+			      <&ov7251_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&ov7251_0>, "status=okay",
+			      <0>,"+100-101";
+		cam0-ov9281 = <&mux_in0>, "remote-endpoint:0=",<&ov9281_0_ep>,
+			      <&ov9281_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&ov9281_0>, "status=okay";
+		cam0-imx258 = <&mux_in0>, "remote-endpoint:0=",<&imx258_0_ep>,
+			      <&imx258_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&imx258_0>, "status=okay";
+		cam0-imx290 = <&mux_in0>, "remote-endpoint:0=",<&imx290_0_ep>,
+			      <&imx290_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&imx290_0>, "status=okay";
+		cam0-ov2311 = <&mux_in0>, "remote-endpoint:0=",<&ov2311_0_ep>,
+			      <&ov2311_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&ov2311_0>, "status=okay";
+		cam0-ov64a40 = <&mux_in0>, "remote-endpoint:0=",<&ov64a40_0_ep>,
+			      <&ov64a40_0_ep>, "remote-endpoint:0=",<&mux_in0>,
+			      <&mux_in0>, "clock-noncontinuous?",
+			      <&ov64a40_0>, "status=okay",
+			      <&ov64a40_0_vcm>, "status=okay",
+			      <&ov64a40_0>,"lens-focus:0=", <&ov64a40_0_vcm>;
+
+		cam1-arducam-64mp = <&mux_in1>, "remote-endpoint:0=",<&arducam_64mp_1_ep>,
+				    <&arducam_64mp_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+				    <&mux_in1>, "clock-noncontinuous?",
+				    <&arducam_64mp_1>, "status=okay",
+				    <&arducam_64mp_1_vcm>, "status=okay",
+				    <&arducam_64mp_1>,"lens-focus:0=", <&arducam_64mp_1_vcm>;
+		cam1-imx219 = <&mux_in1>, "remote-endpoint:0=",<&imx219_1_ep>,
+			      <&imx219_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&mux_in1>, "clock-noncontinuous?",
+			      <&imx219_1>, "status=okay";
+		cam1-imx477 = <&mux_in1>, "remote-endpoint:0=",<&imx477_1_ep>,
+			      <&imx477_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&mux_in1>, "clock-noncontinuous?",
+			      <&imx477_1>, "status=okay";
+		cam1-imx519 = <&mux_in1>, "remote-endpoint:0=",<&imx519_1_ep>,
+			      <&imx519_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&mux_in1>, "clock-noncontinuous?",
+			      <&imx519_1>, "status=okay",
+			      <&imx519_1_vcm>, "status=okay",
+			      <&imx519_1>,"lens-focus:0=", <&imx519_1_vcm>;
+		cam1-imx708 = <&mux_in1>, "remote-endpoint:0=",<&imx708_1_ep>,
+			      <&imx708_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&mux_in1>, "clock-noncontinuous?",
+			      <&imx708_1>, "status=okay",
+			      <&imx708_1_vcm>, "status=okay",
+			      <&imx708_1>,"lens-focus:0=", <&imx708_1_vcm>;
+		cam1-ov5647 = <&mux_in1>, "remote-endpoint:0=",<&ov5647_1_ep>,
+			      <&ov5647_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&mux_in1>, "clock-noncontinuous?",
+			      <&ov5647_1>, "status=okay";
+		cam1-ov7251 = <&mux_in1>, "remote-endpoint:0=",<&ov7251_1_ep>,
+			      <&ov7251_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&ov7251_1>, "status=okay",
+			      <0>,"+102-103";
+		cam1-ov9281 = <&mux_in1>, "remote-endpoint:0=",<&ov9281_1_ep>,
+			      <&ov9281_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&ov9281_1>, "status=okay";
+		cam1-imx258 = <&mux_in1>, "remote-endpoint:0=",<&imx258_1_ep>,
+			      <&imx258_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&imx258_1>, "status=okay";
+		cam1-imx290 = <&mux_in1>, "remote-endpoint:0=",<&imx290_1_ep>,
+			      <&imx290_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&imx290_1>, "status=okay";
+		cam1-ov2311 = <&mux_in1>, "remote-endpoint:0=",<&ov2311_1_ep>,
+			      <&ov2311_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&ov2311_1>, "status=okay";
+		cam1-ov64a40 = <&mux_in1>, "remote-endpoint:0=",<&ov64a40_1_ep>,
+			      <&ov64a40_1_ep>, "remote-endpoint:0=",<&mux_in1>,
+			      <&mux_in1>, "clock-noncontinuous?",
+			      <&ov64a40_1>, "status=okay",
+			      <&ov64a40_1_vcm>, "status=okay",
+			      <&ov64a40_1>,"lens-focus:0=", <&ov64a40_1_vcm>;
+
+		cam2-arducam-64mp = <&mux_in2>, "remote-endpoint:0=",<&arducam_64mp_2_ep>,
+				    <&arducam_64mp_2_ep>, "remote-endpoint:0=",<&mux_in2>,
+				    <&mux_in2>, "clock-noncontinuous?",
+				    <&arducam_64mp_2>, "status=okay",
+				    <&arducam_64mp_2_vcm>, "status=okay",
+				    <&arducam_64mp_2>,"lens-focus:0=", <&arducam_64mp_2_vcm>;
+		cam2-imx219 = <&mux_in2>, "remote-endpoint:0=",<&imx219_2_ep>,
+			      <&imx219_2_ep>, "remote-endpoint:0=",<&mux_in2>,
+			      <&mux_in2>, "clock-noncontinuous?",
+			      <&imx219_2>, "status=okay";
+		cam2-imx477 = <&mux_in2>, "remote-endpoint:0=",<&imx477_2_ep>,
+			      <&imx477_2_ep>, "remote-endpoint:0=",<&mux_in2>,
+			      <&mux_in2>, "clock-noncontinuous?",
+			      <&imx477_2>, "status=okay";
+		cam2-imx519 = <&mux_in2>, "remote-endpoint:0=",<&imx519_2_ep>,
+			      <&imx519_2_ep>, "remote-endpoint:0=",<&mux_in2>,
+			      <&mux_in2>, "clock-noncontinuous?",
+			      <&imx519_2>, "status=okay",
+			      <&imx519_2_vcm>, "status=okay",
+			      <&imx519_2>,"lens-focus:0=", <&imx519_2_vcm>;
+		cam2-imx708 = <&mux_in2>, "remote-endpoint:0=",<&imx708_2_ep>,
+			      <&imx708_2_ep>, "remote-endpoint:0=",<&mux_in2>,
+			      <&mux_in2>, "clock-noncontinuous?",
+			      <&imx708_2>, "status=okay",
+			      <&imx708_2_vcm>, "status=okay",
+			      <&imx708_2>,"lens-focus:0=", <&imx708_2_vcm>;
+		cam2-ov5647 = <&mux_in2>, "remote-endpoint:0=",<&ov5647_2_ep>,
+			      <&ov5647_2_ep>, "remote-endpoint:0=",<&mux_in2>,
+			      <&mux_in2>, "clock-noncontinuous?",
+			      <&ov5647_2>, "status=okay";
+		cam2-ov7251 = <&mux_in2>, "remote-endpoint:0=",<&ov7251_2_ep>,
+			      <&ov7251_2_ep>, "remote-endpoint:0=",<&mux_in2>,
+			      <&ov7251_2>, "status=okay",
+			      <0>,"+104-105";
+		cam2-ov9281 = <&mux_in2>, "remote-endpoint:0=",<&ov9281_2_ep>,
+			      <&ov9281_2_ep>, "remote-endpoint:0=",<&mux_in2>,
+			      <&ov9281_2>, "status=okay";
+		cam2-imx258 = <&mux_in2>, "remote-endpoint:0=",<&imx258_2_ep>,
+			      <&imx258_2>, "status=okay",
+			      <&imx258_2>, "remote-endpoint:0=",<&mux_in2>;
+		cam2-imx290 = <&mux_in2>, "remote-endpoint:0=",<&imx290_2_ep>,
+			      <&imx290_2_ep>, "remote-endpoint:0=",<&mux_in2>,
+			      <&imx290_2>, "status=okay";
+		cam2-ov2311 = <&mux_in2>, "remote-endpoint:0=",<&ov2311_2_ep>,
+			      <&ov2311_2_ep>, "remote-endpoint:0=",<&mux_in2>,
+			      <&ov2311_2>, "status=okay";
+		cam2-ov64a40 = <&mux_in2>, "remote-endpoint:0=",<&ov64a40_2_ep>,
+			      <&ov64a40_2_ep>, "remote-endpoint:0=",<&mux_in2>,
+			      <&mux_in2>, "clock-noncontinuous?",
+			      <&ov64a40_2>, "status=okay",
+			      <&ov64a40_2_vcm>, "status=okay",
+			      <&ov64a40_2>,"lens-focus:0=", <&ov64a40_2_vcm>;
+
+		cam3-arducam-64mp = <&mux_in3>, "remote-endpoint:0=",<&arducam_64mp_3_ep>,
+				    <&arducam_64mp_3_ep>, "remote-endpoint:0=",<&mux_in3>,
+				    <&mux_in3>, "clock-noncontinuous?",
+				    <&arducam_64mp_3>, "status=okay",
+				    <&arducam_64mp_3_vcm>, "status=okay",
+				    <&arducam_64mp_3>,"lens-focus:0=", <&arducam_64mp_3_vcm>;
+		cam3-imx219 = <&mux_in3>, "remote-endpoint:0=",<&imx219_3_ep>,
+			      <&imx219_3_ep>, "remote-endpoint:0=",<&mux_in3>,
+			      <&mux_in3>, "clock-noncontinuous?",
+			      <&imx219_3>, "status=okay";
+		cam3-imx477 = <&mux_in3>, "remote-endpoint:0=",<&imx477_3_ep>,
+			      <&imx477_3_ep>, "remote-endpoint:0=",<&mux_in3>,
+			      <&mux_in3>, "clock-noncontinuous?",
+			      <&imx477_3>, "status=okay";
+		cam3-imx519 = <&mux_in3>, "remote-endpoint:0=",<&imx519_3_ep>,
+			      <&imx519_3_ep>, "remote-endpoint:0=",<&mux_in3>,
+			      <&mux_in3>, "clock-noncontinuous?",
+			      <&imx519_3>, "status=okay",
+			      <&imx519_3_vcm>, "status=okay",
+			      <&imx519_3>,"lens-focus:0=", <&imx519_3_vcm>;
+		cam3-imx708 = <&mux_in3>, "remote-endpoint:0=",<&imx708_3_ep>,
+			      <&imx708_3_ep>, "remote-endpoint:0=",<&mux_in3>,
+			      <&mux_in3>, "clock-noncontinuous?",
+			      <&imx708_3>, "status=okay",
+			      <&imx708_3_vcm>, "status=okay",
+			      <&imx708_3>,"lens-focus:0=", <&imx708_3_vcm>;
+		cam3-ov5647 = <&mux_in3>, "remote-endpoint:0=",<&ov5647_3_ep>,
+			      <&ov5647_3_ep>, "remote-endpoint:0=",<&mux_in3>,
+			      <&mux_in3>, "clock-noncontinuous?",
+			      <&ov5647_3>, "status=okay";
+		cam3-ov7251 = <&mux_in3>, "remote-endpoint:0=",<&ov7251_3_ep>,
+			      <&ov7251_3_ep>, "remote-endpoint:0=",<&mux_in3>,
+			      <&ov7251_3>, "status=okay",
+			      <0>,"+106-107";
+		cam3-ov9281 = <&mux_in3>, "remote-endpoint:0=",<&ov9281_3_ep>,
+			      <&ov9281_3_ep>, "remote-endpoint:0=",<&mux_in3>,
+			      <&ov9281_3>, "status=okay";
+		cam3-imx258 = <&mux_in3>, "remote-endpoint:0=",<&imx258_3_ep>,
+			      <&imx258_3_ep>, "remote-endpoint:0=",<&mux_in3>,
+			      <&imx258_3>, "status=okay";
+		cam3-imx290 = <&mux_in3>, "remote-endpoint:0=",<&imx290_3_ep>,
+			      <&imx290_3_ep>, "remote-endpoint:0=",<&mux_in3>,
+			      <&imx290_3>, "status=okay";
+		cam3-ov2311 = <&mux_in3>, "remote-endpoint:0=",<&ov2311_3_ep>,
+			      <&ov2311_3_ep>, "remote-endpoint:0=",<&mux_in3>,
+			      <&ov2311_3>, "status=okay";
+		cam3-ov64a40 = <&mux_in3>, "remote-endpoint:0=",<&ov64a40_3_ep>,
+			      <&ov64a40_3_ep>, "remote-endpoint:0=",<&mux_in3>,
+			      <&mux_in3>, "clock-noncontinuous?",
+			      <&ov64a40_3>, "status=okay",
+			      <&ov64a40_3_vcm>, "status=okay",
+			      <&ov64a40_3>,"lens-focus:0=", <&ov64a40_3_vcm>;
+
+		cam0-imx290-clk-freq = <&clk_imx290>,"clock-frequency:0",
+				       <&imx290_0>,"clock-frequency:0";
+		cam1-imx290-clk-freq = <&clk_imx290>,"clock-frequency:0",
+				       <&imx290_1>,"clock-frequency:0";
+		cam2-imx290-clk-freq = <&clk_imx290>,"clock-frequency:0",
+				       <&imx290_2>,"clock-frequency:0";
+		cam3-imx290-clk-freq = <&clk_imx290>,"clock-frequency:0",
+				       <&imx290_3>,"clock-frequency:0";
+
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>;
+
+		cam0-sync-source = <&imx477_0>, "trigger-mode:0=1";
+		cam0-sync-sink = <&imx477_0>, "trigger-mode:0=2";
+		cam1-sync-source = <&imx477_1>, "trigger-mode:0=1";
+		cam1-sync-sink = <&imx477_1>, "trigger-mode:0=2";
+		cam2-sync-source = <&imx477_2>, "trigger-mode:0=1";
+		cam2-sync-sink = <&imx477_2>, "trigger-mode:0=2";
+		cam3-sync-source = <&imx477_3>, "trigger-mode:0=1";
+		cam3-sync-sink = <&imx477_3>, "trigger-mode:0=2";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/cap1106-overlay.dts b/arch/arm/boot/dts/overlays/cap1106-overlay.dts
new file mode 100644
index 000000000000..0a585e725f84
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/cap1106-overlay.dts
@@ -0,0 +1,52 @@
+// Overlay for cap1106 from  Microchip Semiconductor
+// add CONFIG_KEYBOARD_CAP11XX=y
+
+/dts-v1/;
+/plugin/;
+
+/ {
+        compatible = "brcm,bcm2835";
+        fragment@0 {
+                target = <&i2c1>;
+                __overlay__{
+                        status = "okay";
+                        cap1106: cap1106@28 {
+                                compatible = "microchip,cap1106";
+                                pinctrl-0 = <&cap1106_pins>;
+                                pinctrl-names = "default";
+                                interrupt-parent = <&gpio>;
+                                interrupts = <4 2>;
+                                reg = <0x28>;
+                                autorepeat;
+                                microchip,sensor-gain = <2>;
+
+                                linux,keycodes = <2>,           /* KEY_1 */
+                                                <3>,            /* KEY_2 */
+                                                <4>,            /* KEY_3 */
+                                                <5>,            /* KEY_4 */
+                                                <6>,            /* KEY_5 */
+                                                <7>;            /* KEY_6 */
+
+                                #address-cells = <1>;
+                                #size-cells = <0>;
+                                status = "okay";
+
+                        };
+                };
+        };
+        fragment@1 {
+                target = <&gpio>;
+                __overlay__ {
+                        cap1106_pins: cap1106_pins {
+                                brcm,pins = <4>;
+                                brcm,function = <0>; /* in */
+                                brcm,pull = <0>; /* none */
+                        };
+                };
+        };
+
+        __overrides__ {
+                int_pin = <&cap1106>, "interrupts:0",
+                          <&cap1106_pins>, "brcm,pins:0";
+        };
+};
diff --git a/arch/arm/boot/dts/overlays/chipcap2-overlay.dts b/arch/arm/boot/dts/overlays/chipcap2-overlay.dts
new file mode 100644
index 000000000000..221b2541c909
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/chipcap2-overlay.dts
@@ -0,0 +1,52 @@
+/dts-v1/;
+/plugin/;
+
+/* Overlay for ChipCap 2 on i2c */
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			chipcap2: chipcap2@28 {
+				compatible = "amphenol,cc2d23s";
+				reg = <0x28>;
+				interrupt-parent = <&gpio>;
+				interrupts = <4 IRQ_TYPE_EDGE_RISING>,
+					     <5 IRQ_TYPE_EDGE_RISING>,
+					     <6 IRQ_TYPE_EDGE_RISING>;
+				interrupt-names = "ready", "low", "high";
+				vdd-supply = <&chipcap2_reg>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			chipcap2_reg: chipcap2_reg {
+				compatible = "regulator-fixed";
+				regulator-name = "chipcap2_reg";
+				regulator-min-microvolt = <3300000>;
+				regulator-max-microvolt = <3300000>;
+				gpio = <&gpio 26 GPIO_ACTIVE_HIGH>;
+				enable-active-high;
+			};
+		};
+	};
+
+	__overrides__ {
+		ready_pin = <&chipcap2>, "interrupts:0";
+		low_pin = <&chipcap2>, "interrupts:8";
+		high_pin = <&chipcap2>, "interrupts:16";
+		reg_pin = <&chipcap2_reg>, "gpio:4";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/chipdip-dac-overlay.dts b/arch/arm/boot/dts/overlays/chipdip-dac-overlay.dts
new file mode 100644
index 000000000000..3ef7565a9312
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/chipdip-dac-overlay.dts
@@ -0,0 +1,46 @@
+/*
+ * Device Tree overlay for ChipDip DAC
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			spdif-transmitter {
+				#address-cells = <0>;
+				#size-cells = <0>;
+				#sound-dai-cells = <0>;
+				compatible = "linux,spdif-dit";
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "chipdip,chipdip-dac";
+			i2s-controller = <&i2s_clk_consumer>;
+			sr0-gpios = <&gpio 5 0>;
+			sr1-gpios = <&gpio 6 0>;
+			sr2-gpios = <&gpio 12 0>;
+			res0-gpios = <&gpio 24 0>;
+			res1-gpios = <&gpio 27 0>;
+			mute-gpios = <&gpio 4 0>;
+			sdwn-gpios = <&gpio 13 0>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/cirrus-wm5102-overlay.dts b/arch/arm/boot/dts/overlays/cirrus-wm5102-overlay.dts
new file mode 100644
index 000000000000..a82b422ba16e
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/cirrus-wm5102-overlay.dts
@@ -0,0 +1,172 @@
+// Definitions for the Cirrus Logic Audio Card
+/dts-v1/;
+/plugin/;
+#include <dt-bindings/pinctrl/bcm2835.h>
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/mfd/arizona.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			wlf_5102_pins: wlf_5102_pins {
+				brcm,pins = <17 22 27>;
+				brcm,function = <
+					BCM2835_FSEL_GPIO_OUT
+					BCM2835_FSEL_GPIO_OUT
+					BCM2835_FSEL_GPIO_IN
+				>;
+			};
+			wlf_8804_pins: wlf_8804_pins {
+				brcm,pins = <8>;
+				brcm,function = <BCM2835_FSEL_GPIO_OUT>;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&spi0_cs_pins>;
+		__overlay__ {
+			brcm,pins = <7>;
+			brcm,function = <BCM2835_FSEL_GPIO_OUT>;
+		};
+	};
+
+
+	fragment@3 {
+		target-path = "/";
+		__overlay__ {
+			rpi_cirrus_reg_1v8: rpi_cirrus_reg_1v8 {
+				compatible = "regulator-fixed";
+				regulator-name = "RPi-Cirrus 1v8";
+				regulator-min-microvolt = <1800000>;
+				regulator-max-microvolt = <1800000>;
+				regulator-always-on;
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@5 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@6 {
+		target = <&spi0>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+			cs-gpios = <&gpio 7 GPIO_ACTIVE_LOW>;
+
+			wm5102@0{
+				compatible = "wlf,wm5102";
+				reg = <0>;
+
+				pinctrl-names = "default";
+				pinctrl-0 = <&wlf_5102_pins>;
+
+				spi-max-frequency = <500000>;
+
+				interrupt-parent = <&gpio>;
+				interrupts = <27 8>;
+				interrupt-controller;
+				#interrupt-cells = <2>;
+
+				gpio-controller;
+				#gpio-cells = <2>;
+
+				LDOVDD-supply = <&rpi_cirrus_reg_1v8>;
+				AVDD-supply = <&rpi_cirrus_reg_1v8>;
+				DBVDD1-supply = <&rpi_cirrus_reg_1v8>;
+				DBVDD2-supply = <&vdd_3v3_reg>;
+				DBVDD3-supply = <&vdd_3v3_reg>;
+				CPVDD-supply = <&rpi_cirrus_reg_1v8>;
+				SPKVDDL-supply = <&vdd_5v0_reg>;
+				SPKVDDR-supply = <&vdd_5v0_reg>;
+				DCVDD-supply = <&arizona_ldo1>;
+
+				reset-gpios = <&gpio 17 GPIO_ACTIVE_HIGH>;
+				wlf,ldoena = <&gpio 22 GPIO_ACTIVE_HIGH>;
+				wlf,gpio-defaults = <
+					ARIZONA_GP_DEFAULT
+					ARIZONA_GP_DEFAULT
+					ARIZONA_GP_DEFAULT
+					ARIZONA_GP_DEFAULT
+					ARIZONA_GP_DEFAULT
+				>;
+				wlf,micd-configs = <0 1 0>;
+				wlf,dmic-ref = <
+					ARIZONA_DMIC_MICVDD
+					ARIZONA_DMIC_MICBIAS2
+					ARIZONA_DMIC_MICVDD
+					ARIZONA_DMIC_MICVDD
+				>;
+				wlf,inmode = <
+					ARIZONA_INMODE_DIFF
+					ARIZONA_INMODE_DMIC
+					ARIZONA_INMODE_SE
+					ARIZONA_INMODE_DIFF
+				>;
+				status = "okay";
+
+				arizona_ldo1: ldo1 {
+					regulator-name = "LDO1";
+					// default constraints as in
+					// arizona-ldo1.c
+					regulator-min-microvolt = <1200000>;
+					regulator-max-microvolt = <1800000>;
+				};
+			};
+		};
+	};
+
+	fragment@7 {
+		target = <&i2c1>;
+		__overlay__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			wm8804@3b {
+				compatible = "wlf,wm8804";
+				reg = <0x3b>;
+				status = "okay";
+
+				pinctrl-names = "default";
+				pinctrl-0 = <&wlf_8804_pins>;
+
+				PVDD-supply = <&vdd_3v3_reg>;
+				DVDD-supply = <&vdd_3v3_reg>;
+				wlf,reset-gpio = <&gpio 8 GPIO_ACTIVE_HIGH>;
+			};
+		};
+	};
+
+	fragment@8 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "wlf,rpi-cirrus";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/cm-swap-i2c0-overlay.dts b/arch/arm/boot/dts/overlays/cm-swap-i2c0-overlay.dts
new file mode 100644
index 000000000000..6b7f599f7611
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/cm-swap-i2c0-overlay.dts
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for IMX708 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c0mux>;
+		i2c0mux_frag: __overlay__ {
+			pinctrl-0 = <&i2c0_gpio28>;
+			pinctrl-1 = <&i2c0_gpio0>;
+		};
+	};
+
+	__overrides__ {
+		i2c0-gpio0 = <&i2c0mux_frag>, "pinctrl-0:0=",<&i2c0_gpio0>;
+		i2c0-gpio28 = <&i2c0mux_frag>, "pinctrl-0:0=",<&i2c0_gpio28>;
+		i2c0-gpio44 = <&i2c0mux_frag>, "pinctrl-0:0=",<&i2c0_gpio44>;
+		i2c10-gpio0 = <&i2c0mux_frag>, "pinctrl-1:0=",<&i2c0_gpio0>;
+		i2c10-gpio28 = <&i2c0mux_frag>, "pinctrl-1:0=",<&i2c0_gpio28>;
+		i2c10-gpio44 = <&i2c0mux_frag>, "pinctrl-1:0=",<&i2c0_gpio44>;
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/cma-overlay.dts b/arch/arm/boot/dts/overlays/cma-overlay.dts
new file mode 100644
index 000000000000..1d87c599f909
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/cma-overlay.dts
@@ -0,0 +1,36 @@
+/*
+ * cma.dts
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&cma>;
+		frag0: __overlay__ {
+			/*
+			 * The default size when using this overlay is 256 MB
+			 * and should be kept as is for backwards
+			 * compatibility.
+			 */
+			size = <0x10000000>;
+		};
+	};
+
+	__overrides__ {
+		cma-512 = <&frag0>,"size:0=",<0x20000000>;
+		cma-448 = <&frag0>,"size:0=",<0x1c000000>;
+		cma-384 = <&frag0>,"size:0=",<0x18000000>;
+		cma-320 = <&frag0>,"size:0=",<0x14000000>;
+		cma-256 = <&frag0>,"size:0=",<0x10000000>;
+		cma-192 = <&frag0>,"size:0=",<0xC000000>;
+		cma-128 = <&frag0>,"size:0=",<0x8000000>;
+		cma-96  = <&frag0>,"size:0=",<0x6000000>;
+		cma-64  = <&frag0>,"size:0=",<0x4000000>;
+		cma-size = <&frag0>,"size:0"; /* in bytes, 4MB aligned */
+		cma-default = <0>,"-0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/crystalfontz-cfa050_pi_m-overlay.dts b/arch/arm/boot/dts/overlays/crystalfontz-cfa050_pi_m-overlay.dts
new file mode 100644
index 000000000000..fe8f97a57278
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/crystalfontz-cfa050_pi_m-overlay.dts
@@ -0,0 +1,133 @@
+/*
+ * crystalfontz-cfa050_pi_m-overlay.dts
+ * Configures the Crystalfontz CFA050-PI-M series of modules
+ * using CFAF7201280A0-050TC/TN panels with RaspberryPi CM4 DSI1
+ */
+/dts-v1/;
+/plugin/;
+/{
+// RaspberryPi CM4
+	compatible = "brcm,bcm2835";
+// PCF8574 I2C GPIO EXPANDER
+	frag0: fragment@0 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+			pcf8574a: pcf8574a@38 {
+				reg = <0x38>;
+				compatible = "nxp,pcf8574";
+				gpio-controller;
+				#gpio-cells = <2>;
+				ngpios = <8>;
+				gpio-line-names = "TFT_RESET", "TOUCH_RESET", "EXT_P2", "EXT_P3",
+					"EXT_P4", "EXT_P5", "EXT_P6", "EXT_P7";
+			};
+		};
+	};
+// LM3630a BACKLIGHT LED CONTROLLER
+	frag1: fragment@1 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+			lm3630a: backlight@36 {
+				reg = <0x36>;
+				compatible = "ti,lm3630a";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				led@0 {
+					reg = <0>;
+					led-sources = <0 1>;
+					label = "lcd-backlight";
+					default-brightness = <255>;
+					max-brightness = <255>;
+				};
+			};
+		};
+	};
+// PCF85063A RTC on I2C
+	frag2: fragment@2 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+			pcf85063a@51 {
+				compatible = "nxp,pcf85063a";
+				reg = <0x51>;
+			};
+		};
+	};
+
+// CFAF7201280A0_050Tx TFT DSI PANEL
+	fragment@3 {
+		target = <&dsi1>;
+		__overlay__  {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+			port {
+				dsi_out: endpoint {
+					remote-endpoint = <&panel_in>;
+				};
+			};
+			dsi_panel: dsi_panel@0 {
+				compatible = "crystalfontz,cfaf7201280a0_050tx";
+				reg = <0>;
+				reset-gpios = <&pcf8574a 0 1>;
+				backlight = <&lm3630a>;
+				fps = <60>;
+				port {
+					panel_in: endpoint {
+						remote-endpoint = <&dsi_out>;
+					};
+				};
+			};
+		};
+	};
+// rPI GPIO INPUT FOR TOUCH IC IRQ
+	fragment@4 {
+		target = <&gpio>;
+		__dormant__ {
+			gt928intpins: gt928intpins {
+				brcm,pins = <26>; //gpio pin
+				brcm,function = <0>; //input
+				brcm,pull = <2>; //2=pull-up
+			};
+		};
+	};
+// GT928 TOUCH CONTROLLER IC
+	frag5: fragment@5 {
+		target = <&i2c_csi_dsi>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+			gt928: gt928@5d {
+				compatible = "goodix,gt928";
+				reg = <0x5d>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&gt928intpins>;
+				interrupt-parent = <&gpio>;
+				interrupts = <26 2>; //gpio 26, 2=high-to-low trigger
+				irq-gpios = <&gpio 26 0>; //gpio 26, 0=active-high
+				reset-gpios = <&pcf8574a 1 1>;
+				touchscreen-inverted-x;
+				touchscreen-inverted-y;
+			};
+		};
+	};
+//OVERLAY OPTIONS
+	__overrides__ {
+		//enables captouch
+		captouch = <0>,"+4+5";
+		//changes options to support CM5 (default is CM4)
+		cm5 = <&frag0>, "target:0=",<&i2c_csi_dsi0>,
+			<&frag1>, "target:0=",<&i2c_csi_dsi0>,
+			<&frag2>, "target:0=",<&i2c_csi_dsi0>,
+			<&frag5>, "target:0=",<&i2c_csi_dsi0>;
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/cutiepi-panel-overlay.dts b/arch/arm/boot/dts/overlays/cutiepi-panel-overlay.dts
new file mode 100644
index 000000000000..d14c3698eb75
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/cutiepi-panel-overlay.dts
@@ -0,0 +1,117 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+    compatible = "brcm,bcm2711";
+
+    fragment@0 {
+        target=<&dsi1>;
+
+        __overlay__ {
+            status = "okay";
+
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            port {
+                dsi1_out_port: endpoint {
+                    remote-endpoint = <&panel_dsi_in1>;
+                };
+            };
+
+            display1: panel@0 {
+                compatible = "nwe,nwe080";
+                reg=<0>;
+                backlight = <&rpi_backlight>;
+                reset-gpios = <&gpio 20 0>;
+                port {
+                    panel_dsi_in1: endpoint {
+                        remote-endpoint = <&dsi1_out_port>;
+                    };
+                };
+            };
+        };
+    };
+
+    fragment@1 {
+        target = <&gpio>;
+        __overlay__ {
+            pwm_pins: pwm_pins {
+                brcm,pins = <12>;
+                brcm,function = <4>; // ALT0
+            };
+        };
+    };
+
+    fragment@2 {
+        target = <&pwm>;
+        frag1: __overlay__ {
+            pinctrl-names = "default";
+            pinctrl-0 = <&pwm_pins>;
+            assigned-clock-rates = <1000000>;
+            status = "okay";
+        };
+    };
+
+    fragment@3 {
+        target-path = "/";
+        __overlay__ {
+            rpi_backlight: rpi_backlight {
+                compatible = "pwm-backlight";
+                brightness-levels = <0 6 8 12 16 24 32 40 48 64 96 128 160 192 224 255>;
+                default-brightness-level = <6>;
+                pwms = <&pwm 0 200000 0>;
+                power-supply = <&vdd_3v3_reg>;
+                status = "okay";
+            };
+        };
+    };
+
+    fragment@4 {
+        target = <&i2c6>;
+        frag0: __overlay__ {
+            status = "okay";
+            pinctrl-names = "default";
+            pinctrl-0 = <&i2c6_pins>;
+            clock-frequency = <100000>;
+        };
+    };
+
+    fragment@5 {
+        target = <&i2c6_pins>;
+        __overlay__ {
+            brcm,pins = <22 23>;
+        };
+    };
+
+    fragment@6 {
+            target = <&gpio>;
+            __overlay__ {
+                goodix_pins: goodix_pins {
+                    brcm,pins = <21 26>; // interrupt and reset
+                    brcm,function = <0 0>; // in
+                    brcm,pull = <2 2>; // pull-up
+                };
+            };
+    };
+
+    fragment@7 {
+        target = <&i2c6>;
+        __overlay__ {
+            #address-cells = <1>;
+            #size-cells = <0>;
+            status = "okay";
+
+            gt9xx: gt9xx@5d {
+                compatible = "goodix,gt9271"; 
+                reg = <0x5D>;
+                pinctrl-names = "default";
+                pinctrl-0 = <&goodix_pins>;
+                interrupt-parent = <&gpio>;
+                interrupts = <21 2>; // high-to-low edge triggered
+                irq-gpios = <&gpio 21 0>; 
+                reset-gpios = <&gpio 26 0>;
+            };
+        };
+    };
+};
diff --git a/arch/arm/boot/dts/overlays/dacberry400-overlay.dts b/arch/arm/boot/dts/overlays/dacberry400-overlay.dts
new file mode 100644
index 000000000000..dfc357102c00
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/dacberry400-overlay.dts
@@ -0,0 +1,61 @@
+// Definitions for DACberry400
+/dts-v1/;
+/plugin/;
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			codec_1v8_reg: codec-1v8-reg {
+			compatible = "regulator-fixed";
+			regulator-name = "tlv320aic3104_1v8";
+			regulator-min-microvolt = <1800000>;
+			regulator-max-microvolt = <1800000>;
+			regulator-always-on;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			tlv320aic3104@18 {
+				#sound-dai-cells = <0>;
+				reg = <0x18>;
+
+				compatible = "ti,tlv320aic3x";
+				AVDD-supply = <&vdd_3v3_reg>;
+				DRVDD-supply = <&vdd_3v3_reg>;
+				DVDD-supply = <&codec_1v8_reg>;
+				IOVDD-supply = <&codec_1v8_reg>;
+
+				gpio-controller;
+				reset-gpios = <&gpio 26 1>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "osaelectronics,dacberry400";
+			i2s-controller = <&i2s_clk_producer>;
+			status = "okay";
+		};
+	};
+};
+
+
diff --git a/arch/arm/boot/dts/overlays/dht11-overlay.dts b/arch/arm/boot/dts/overlays/dht11-overlay.dts
new file mode 100644
index 000000000000..8b0fc6b7a3cb
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/dht11-overlay.dts
@@ -0,0 +1,48 @@
+/*
+ * Overlay for the DHT11/21/22 humidity/temperature sensor modules.
+ */
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			dht11: dht11@4 {
+				compatible = "dht11";
+				pinctrl-names = "default";
+				pinctrl-0 = <&dht11_pins>;
+				gpios = <&gpio 4 0>;
+				status = "okay";
+				#io-channel-cells = <1>;
+			};
+
+			iio: iio-hwmon@4 {
+				compatible = "iio-hwmon";
+				status = "okay";
+				io-channels = <&dht11 0>, <&dht11 1>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			dht11_pins: dht11_pins@4 {
+				brcm,pins = <4>;
+				brcm,function = <0>; // in
+				brcm,pull = <0>; // off
+			};
+		};
+	};
+
+	__overrides__ {
+		gpiopin = <&dht11_pins>,"brcm,pins:0",
+			<&dht11_pins>, "reg:0",
+			<&dht11>,"gpios:4",
+			<&dht11>,"reg:0",
+			<&iio>,"reg:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/dionaudio-kiwi-overlay.dts b/arch/arm/boot/dts/overlays/dionaudio-kiwi-overlay.dts
new file mode 100644
index 000000000000..ab0144cd17dc
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/dionaudio-kiwi-overlay.dts
@@ -0,0 +1,39 @@
+// Definitions for Dion Audio KIWI streamer
+
+/*
+ * PCM1794 DAC (in hardware mode).
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			pcm1794a-codec {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm1794a";
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "dionaudio,dionaudio-kiwi";
+			i2s-controller = <&i2s_clk_producer>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/dionaudio-loco-overlay.dts b/arch/arm/boot/dts/overlays/dionaudio-loco-overlay.dts
new file mode 100644
index 000000000000..6f4a9c1a8243
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/dionaudio-loco-overlay.dts
@@ -0,0 +1,39 @@
+// Definitions for Dion Audio LOCO DAC-AMP
+
+/*
+ * PCM5242 DAC (in hardware mode) and TPA3118 AMP.
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			pcm5102a-codec {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5102a";
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "dionaudio,loco-pcm5242-tpa3118";
+			i2s-controller = <&i2s_clk_producer>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/dionaudio-loco-v2-overlay.dts b/arch/arm/boot/dts/overlays/dionaudio-loco-v2-overlay.dts
new file mode 100644
index 000000000000..975a844eb272
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/dionaudio-loco-v2-overlay.dts
@@ -0,0 +1,49 @@
+/*
+ * Definitions for Dion Audio LOCO-V2 DAC-AMP
+ *  eg. dtoverlay=dionaudio-loco-v2
+ *
+ * PCM5242 DAC (in software mode) and TPA3255 AMP.
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&sound>;
+		frag0: __overlay__ {
+			compatible = "dionaudio,dionaudio-loco-v2";
+			i2s-controller = <&i2s_clk_producer>;
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			pcm5122@4c {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5122";
+				reg = <0x4d>;
+				status = "okay";
+			};
+		};
+	};
+
+	__overrides__ {
+		24db_digital_gain = <&frag0>,"dionaudio,24db_digital_gain?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/disable-bt-overlay.dts b/arch/arm/boot/dts/overlays/disable-bt-overlay.dts
new file mode 100644
index 000000000000..f3a8af1375f0
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/disable-bt-overlay.dts
@@ -0,0 +1,59 @@
+/dts-v1/;
+/plugin/;
+
+/* Disable Bluetooth and restore UART0/ttyAMA0 over GPIOs 14 & 15. */
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&uart1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@1 {
+		target = <&uart0>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&uart0_pins>;
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&bt>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&uart0_pins>;
+		__overlay__ {
+			brcm,pins;
+			brcm,function;
+			brcm,pull;
+		};
+	};
+
+	fragment@4 {
+		target = <&bt_pins>;
+		__overlay__ {
+			brcm,pins;
+			brcm,function;
+			brcm,pull;
+		};
+	};
+
+	fragment@5 {
+		target-path = "/aliases";
+		__overlay__ {
+			serial0 = "/soc/serial@7e201000";
+			serial1 = "/soc/serial@7e215040";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/disable-bt-pi5-overlay.dts b/arch/arm/boot/dts/overlays/disable-bt-pi5-overlay.dts
new file mode 100644
index 000000000000..6e23b64d44e7
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/disable-bt-pi5-overlay.dts
@@ -0,0 +1,17 @@
+/dts-v1/;
+/plugin/;
+
+/* Disable Bluetooth */
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&bluetooth>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/disable-emmc2-overlay.dts b/arch/arm/boot/dts/overlays/disable-emmc2-overlay.dts
new file mode 100644
index 000000000000..8cd1d7fa4a90
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/disable-emmc2-overlay.dts
@@ -0,0 +1,13 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target = <&emmc2>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/disable-wifi-overlay.dts b/arch/arm/boot/dts/overlays/disable-wifi-overlay.dts
new file mode 100644
index 000000000000..75e046463900
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/disable-wifi-overlay.dts
@@ -0,0 +1,20 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&mmc>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@1 {
+		target = <&mmcnr>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/disable-wifi-pi5-overlay.dts b/arch/arm/boot/dts/overlays/disable-wifi-pi5-overlay.dts
new file mode 100644
index 000000000000..d5389c5dbb69
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/disable-wifi-pi5-overlay.dts
@@ -0,0 +1,13 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&sdio2>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/dpi18-overlay.dts b/arch/arm/boot/dts/overlays/dpi18-overlay.dts
new file mode 100644
index 000000000000..4abe5be744db
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/dpi18-overlay.dts
@@ -0,0 +1,39 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	// There is no DPI driver module, but we need a platform device
+	// node (that doesn't already use pinctrl) to hang the pinctrl
+	// reference on - leds will do
+
+	fragment@0 {
+		target = <&fb>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&dpi18_pins>;
+		};
+	};
+
+	fragment@1 {
+		target = <&vc4>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&dpi18_pins>;
+		};
+	};
+
+	fragment@2 {
+		target = <&gpio>;
+		__overlay__ {
+			dpi18_pins: dpi18_pins {
+				brcm,pins = <0 1 2 3 4 5 6 7 8 9 10 11
+					     12 13 14 15 16 17 18 19 20
+					     21>;
+				brcm,function = <6>; /* alt2 */
+				brcm,pull = <0>; /* no pull */
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/dpi18cpadhi-overlay.dts b/arch/arm/boot/dts/overlays/dpi18cpadhi-overlay.dts
new file mode 100644
index 000000000000..50c88a1ed299
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/dpi18cpadhi-overlay.dts
@@ -0,0 +1,26 @@
+/*
+ * dpi18cpadhi-overlay.dts
+ */
+
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&fb>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&dpi_18bit_cpadhi_gpio0>;
+		};
+	};
+
+	fragment@1 {
+		target = <&vc4>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&dpi_18bit_cpadhi_gpio0>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/dpi24-overlay.dts b/arch/arm/boot/dts/overlays/dpi24-overlay.dts
new file mode 100644
index 000000000000..44335cc81277
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/dpi24-overlay.dts
@@ -0,0 +1,39 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	// There is no DPI driver module, but we need a platform device
+	// node (that doesn't already use pinctrl) to hang the pinctrl
+	// reference on - leds will do
+
+	fragment@0 {
+		target = <&fb>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&dpi24_pins>;
+		};
+	};
+
+	fragment@1 {
+		target = <&vc4>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&dpi24_pins>;
+		};
+	};
+
+	fragment@2 {
+		target = <&gpio>;
+		__overlay__ {
+			dpi24_pins: dpi24_pins {
+				brcm,pins = <0 1 2 3 4 5 6 7 8 9 10 11
+					     12 13 14 15 16 17 18 19 20
+					     21 22 23 24 25 26 27>;
+				brcm,function = <6>; /* alt2 */
+				brcm,pull = <0>; /* no pull */
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/draws-overlay.dts b/arch/arm/boot/dts/overlays/draws-overlay.dts
new file mode 100644
index 000000000000..d2a8513d4cc9
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/draws-overlay.dts
@@ -0,0 +1,204 @@
+#include <dt-bindings/clock/bcm2835.h>
+/*
+ * Device tree overlay for the DRAWS Hardware
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+    compatible = "brcm,bcm2835";
+    fragment@0 {
+        target = <&i2s_clk_producer>;
+        __overlay__ {
+            status = "okay";
+        };
+    };
+
+    fragment@1 {
+        target-path = "/";
+        __overlay__ {
+            regulators {
+                compatible = "simple-bus";
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                udrc0_ldoin: udrc0_ldoin {
+                    compatible = "regulator-fixed";
+                    regulator-name = "ldoin";
+                    regulator-min-microvolt = <3300000>;
+                    regulator-max-microvolt = <3300000>;
+                    regulator-always-on;
+                };
+
+                sc16is752_clk: sc16is752_draws_clk {
+                    compatible = "fixed-clock";
+                    #clock-cells = <0>;
+                    clock-frequency = <1843200>;
+                };
+            };
+
+            pps: pps {
+                compatible = "pps-gpio";
+                pinctrl-names = "default";
+                pinctrl-0 = <&pps_pins>;
+                gpios = <&gpio 7 0>;
+                status = "okay";
+            };
+
+            iio-hwmon {
+                compatible = "iio-hwmon";
+                status = "okay";
+                io-channels = <&tla2024 4>, <&tla2024 5>, <&tla2024 6>,
+                              <&tla2024 7>;
+            };
+        };
+    };
+
+    fragment@2 {
+        target = <&i2c_arm>;
+        __overlay__ {
+            #address-cells = <1>;
+            #size-cells = <0>;
+            status = "okay";
+
+            tlv320aic32x4: tlv320aic32x4@18 {
+                compatible = "ti,tlv320aic32x4";
+                reg = <0x18>;
+                #sound-dai-cells = <0>;
+                status = "okay";
+
+                clocks = <&clocks BCM2835_CLOCK_GP0>;
+                clock-names = "mclk";
+                assigned-clocks = <&clocks BCM2835_CLOCK_GP0>;
+                assigned-clock-rates = <25000000>;
+
+                pinctrl-names = "default";
+                pinctrl-0 = <&gpclk0_pin &aic3204_reset>;
+
+                reset-gpios = <&gpio 13 0>;
+
+                iov-supply = <&udrc0_ldoin>;
+                ldoin-supply = <&udrc0_ldoin>;
+            };
+
+            sc16is752: sc16is752@50 {
+                compatible = "nxp,sc16is752";
+                reg = <0x50>;
+                clocks = <&sc16is752_clk>;
+                interrupt-parent = <&gpio>;
+                interrupts = <17 2>; /* IRQ_TYPE_EDGE_FALLING */
+
+                pinctrl-names = "default";
+                pinctrl-0 = <&sc16is752_irq>;
+            };
+
+            tla2024: tla2024@48 {
+                compatible = "ti,ads1015";
+                reg = <0x48>;
+                #address-cells = <1>;
+                #size-cells = <0>;
+                #io-channel-cells = <1>;
+
+                adc_ch4: channel@4 {
+                    reg = <4>;
+                    ti,gain = <1>;
+                    ti,datarate = <4>;
+                };
+
+                adc_ch5: channel@5 {
+                    reg = <5>;
+                    ti,gain = <1>;
+                    ti,datarate = <4>;
+                };
+
+                adc_ch6: channel@6 {
+                    reg = <6>;
+                    ti,gain = <2>;
+                    ti,datarate = <4>;
+                };
+
+                adc_ch7: channel@7 {
+                    reg = <7>;
+                    ti,gain = <2>;
+                    ti,datarate = <4>;
+                };
+            };
+        };
+    };
+
+    fragment@3 {
+        target = <&sound>;
+        snd: __overlay__ {
+            compatible = "simple-audio-card";
+            i2s-controller = <&i2s_clk_producer>;
+            status = "okay";
+
+            simple-audio-card,name = "draws";
+            simple-audio-card,format = "i2s";
+
+            simple-audio-card,bitclock-master = <&dailink0_master>;
+            simple-audio-card,frame-master = <&dailink0_master>;
+
+            simple-audio-card,widgets =
+                "Line", "Line In",
+                "Line", "Line Out";
+
+            simple-audio-card,routing =
+                "IN1_R", "Line In",
+                "IN1_L", "Line In",
+                "CM_L", "Line In",
+                "CM_R", "Line In",
+                "Line Out", "LOR",
+                "Line Out", "LOL";
+
+            dailink0_master: simple-audio-card,cpu {
+                sound-dai = <&i2s_clk_producer>;
+            };
+
+            simple-audio-card,codec {
+                sound-dai = <&tlv320aic32x4>;
+            };
+        };
+    };
+
+    fragment@4 {
+        target = <&gpio>;
+        __overlay__ {
+            gpclk0_pin: gpclk0_pin {
+                brcm,pins = <4>;
+                brcm,function = <4>;
+            };
+
+            aic3204_reset: aic3204_reset {
+                brcm,pins = <13>;
+                brcm,function = <1>;
+                brcm,pull = <1>;
+            };
+
+            sc16is752_irq: sc16is752_irq {
+                brcm,pins = <17>;
+                brcm,function = <0>;
+                brcm,pull = <2>;
+            };
+
+            pps_pins: pps_pins {
+                brcm,pins = <7>;
+                brcm,function = <0>;
+                brcm,pull = <0>;
+            };
+        };
+    };
+
+    __overrides__ {
+        draws_adc_ch4_gain = <&adc_ch4>,"ti,gain:0";
+        draws_adc_ch4_datarate = <&adc_ch4>,"ti,datarate:0";
+        draws_adc_ch5_gain = <&adc_ch5>,"ti,gain:0";
+        draws_adc_ch5_datarate = <&adc_ch5>,"ti,datarate:0";
+        draws_adc_ch6_gain = <&adc_ch6>,"ti,gain:0";
+        draws_adc_ch6_datarate = <&adc_ch6>,"ti,datarate:0";
+        draws_adc_ch7_gain = <&adc_ch7>,"ti,gain:0";
+        draws_adc_ch7_datarate = <&adc_ch7>,"ti,datarate:0";
+        alsaname = <&snd>, "simple-audio-card,name";
+    };
+};
diff --git a/arch/arm/boot/dts/overlays/dwc-otg-deprecated-overlay.dts b/arch/arm/boot/dts/overlays/dwc-otg-deprecated-overlay.dts
new file mode 100644
index 000000000000..78c5e9f85048
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/dwc-otg-deprecated-overlay.dts
@@ -0,0 +1,14 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&usb>;
+		__overlay__ {
+			compatible = "brcm,bcm2708-usb";
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/dwc2-overlay.dts b/arch/arm/boot/dts/overlays/dwc2-overlay.dts
new file mode 100644
index 000000000000..0d83e344ad97
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/dwc2-overlay.dts
@@ -0,0 +1,26 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&usb>;
+		#address-cells = <1>;
+		#size-cells = <1>;
+		dwc2_usb: __overlay__ {
+			compatible = "brcm,bcm2835-usb";
+			dr_mode = "otg";
+			g-np-tx-fifo-size = <32>;
+			g-rx-fifo-size = <558>;
+			g-tx-fifo-size = <512 512 512 512 512 256 256>;
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		dr_mode = <&dwc2_usb>, "dr_mode";
+		g-np-tx-fifo-size = <&dwc2_usb>,"g-np-tx-fifo-size:0";
+		g-rx-fifo-size = <&dwc2_usb>,"g-rx-fifo-size:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/edt-ft5406-overlay.dts b/arch/arm/boot/dts/overlays/edt-ft5406-overlay.dts
new file mode 100644
index 000000000000..7cbc3fa673a3
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/edt-ft5406-overlay.dts
@@ -0,0 +1,22 @@
+/*
+ * Device Tree overlay for EDT 5406 touchscreen controller, as used on the
+ * Raspberry Pi 7" panel
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+#define ENABLE_I2C0_MUX
+#include "i2c-buses.dtsi"
+#include "edt-ft5406.dtsi"
+
+&busfrag {
+	target = <&i2c_csi_dsi>;
+};
+
+/ {
+	__overrides__ {
+		addr = <&ft5406>,"reg:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/edt-ft5406.dtsi b/arch/arm/boot/dts/overlays/edt-ft5406.dtsi
new file mode 100644
index 000000000000..3be2f18e898f
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/edt-ft5406.dtsi
@@ -0,0 +1,48 @@
+/*
+ * Device Tree overlay for an EDT FT5406 touchscreen
+ *
+ * Note that this is included from vc4-kms-dsi-7inch, hence the
+ * fragment numbers not starting at 0.
+ */
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@10 {
+		target = <&ft5406>;
+		__overlay__ {
+			touchscreen-inverted-x;
+		};
+	};
+
+	fragment@11 {
+		target = <&ft5406>;
+		__overlay__ {
+			touchscreen-inverted-y;
+		};
+	};
+
+	fragment@12 {
+		target = <&i2cbus>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			ft5406: ts@38 {
+				compatible = "edt,edt-ft5506";
+				reg = <0x38>;
+
+				touchscreen-size-x = < 800 >;
+				touchscreen-size-y = < 480 >;
+			};
+		};
+	};
+
+	__overrides__ {
+		sizex = <&ft5406>,"touchscreen-size-x:0";
+		sizey = <&ft5406>,"touchscreen-size-y:0";
+		invx = <0>, "-10";
+		invy = <0>, "-11";
+		swapxy = <&ft5406>,"touchscreen-swapped-x-y?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/enc28j60-overlay.dts b/arch/arm/boot/dts/overlays/enc28j60-overlay.dts
new file mode 100644
index 000000000000..7af5c2e607ea
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/enc28j60-overlay.dts
@@ -0,0 +1,53 @@
+// Overlay for the Microchip ENC28J60 Ethernet Controller
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			status = "okay";
+
+			eth1: enc28j60@0{
+				compatible = "microchip,enc28j60";
+				reg = <0>; /* CE0 */
+				pinctrl-names = "default";
+				pinctrl-0 = <&eth1_pins>;
+				interrupt-parent = <&gpio>;
+				interrupts = <25 0x2>; /* falling edge */
+				spi-max-frequency = <12000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&gpio>;
+		__overlay__ {
+			eth1_pins: eth1_pins {
+				brcm,pins = <25>;
+				brcm,function = <0>; /* in */
+				brcm,pull = <0>; /* none */
+			};
+		};
+	};
+
+	__overrides__ {
+		int_pin = <&eth1>, "interrupts:0",
+		          <&eth1_pins>, "brcm,pins:0";
+		speed   = <&eth1>, "spi-max-frequency:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/enc28j60-spi2-overlay.dts b/arch/arm/boot/dts/overlays/enc28j60-spi2-overlay.dts
new file mode 100644
index 000000000000..17cb5b8fa485
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/enc28j60-spi2-overlay.dts
@@ -0,0 +1,47 @@
+// Overlay for the Microchip ENC28J60 Ethernet Controller - SPI2 Compute Module
+// Interrupt pin: 39
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi2>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			status = "okay";
+
+			eth1: enc28j60@0{
+				compatible = "microchip,enc28j60";
+				reg = <0>; /* CE0 */
+				pinctrl-names = "default";
+				pinctrl-0 = <&eth1_pins>;
+				interrupt-parent = <&gpio>;
+				interrupts = <39 0x2>; /* falling edge */
+				spi-max-frequency = <12000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			eth1_pins: eth1_pins {
+				brcm,pins = <39>;
+				brcm,function = <0>; /* in */
+				brcm,pull = <0>; /* none */
+			};
+		};
+	};
+
+	__overrides__ {
+		int_pin = <&eth1>, "interrupts:0",
+		          <&eth1_pins>, "brcm,pins:0";
+		speed   = <&eth1>, "spi-max-frequency:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/exc3000-overlay.dts b/arch/arm/boot/dts/overlays/exc3000-overlay.dts
new file mode 100644
index 000000000000..6f087fb20661
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/exc3000-overlay.dts
@@ -0,0 +1,48 @@
+// Device tree overlay for I2C connected EETI EXC3000 multiple touch controller
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			exc3000_pins: exc3000_pins {
+				brcm,pins = <4>; // interrupt
+				brcm,function = <0>; // in
+				brcm,pull = <2>; // pull-up
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			exc3000: exc3000@2a {
+				compatible = "eeti,exc3000";
+				reg = <0x2a>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&exc3000_pins>;
+				interrupt-parent = <&gpio>;
+				interrupts = <4 8>; // active low level-sensitive
+				touchscreen-size-x = <4096>;
+				touchscreen-size-y = <4096>;
+			};
+		};
+	};
+
+	__overrides__ {
+		interrupt = <&exc3000_pins>,"brcm,pins:0",
+			<&exc3000>,"interrupts:0";
+		sizex = <&exc3000>,"touchscreen-size-x:0";
+		sizey = <&exc3000>,"touchscreen-size-y:0";
+		invx = <&exc3000>,"touchscreen-inverted-x?";
+		invy = <&exc3000>,"touchscreen-inverted-y?";
+		swapxy = <&exc3000>,"touchscreen-swapped-x-y?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/ezsound-6x8iso-overlay.dts b/arch/arm/boot/dts/overlays/ezsound-6x8iso-overlay.dts
new file mode 100644
index 000000000000..764ad9a56edc
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ezsound-6x8iso-overlay.dts
@@ -0,0 +1,117 @@
+//Device tree overlay for ezsound 6x8 isolated card
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&rp1_i2s0_18_21>;
+		__overlay__ {
+			pins = "gpio18", "gpio19", "gpio20", "gpio22", "gpio24",
+			"gpio26", "gpio21", "gpio23", "gpio25", "gpio27";
+		};
+	};
+
+	fragment@1 {
+		target = <&rp1_i2s1_18_21>;
+		__overlay__ {
+			pins = "gpio18", "gpio19", "gpio20", "gpio22", "gpio24",
+			"gpio26", "gpio21", "gpio23", "gpio25", "gpio27";
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "simple-audio-card";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			i2s-controller = <&i2s_clk_consumer>;
+			status="okay";
+
+			simple-audio-card,name = "ezsound-6x8";
+
+			dailink_out_master: simple-audio-card,dai-link@0 {
+				reg = <0>;
+				format = "i2s";
+				bitclock-master = <&pcm3168_playback>;
+				frame-master = <&pcm3168_playback>;
+				cpu {
+					sound-dai = <&i2s_clk_consumer>;
+					dai-tdm-slot-num = <2>;
+					dai-tdm-slot-width = <32>;
+				};
+				pcm3168_playback: codec {
+					system-clock-fixed;
+					mclk-fs = <256>;
+					sound-dai = <&pcm3168a 0>;
+					dai-tdm-slot-num = <2>;
+					dai-tdm-slot-width = <32>;
+				};
+			};
+			dailink_in_slave: simple-audio-card,dai-link@1 {
+				reg = <1>;
+				format = "i2s";
+				bitclock-master = <&pcm3168_capture>;
+				frame-master = <&pcm3168_capture>;
+				cpu {
+					sound-dai = <&i2s_clk_consumer>;
+					dai-tdm-slot-num = <2>;
+					dai-tdm-slot-width = <32>;
+				};
+				pcm3168_capture: codec {
+					system-clock-fixed;
+					mclk-fs = <256>;
+					sound-dai = <&pcm3168a 1>;
+					dai-tdm-slot-num = <2>;
+					dai-tdm-slot-width = <32>;
+				};
+			};
+		};
+	};
+
+	fragment@3 {
+		target-path = "/";
+		__overlay__ {
+			scki_clk: scki-clock {
+				compatible = "fixed-clock";
+				#clock-cells = <0>;
+				clock-frequency = <24576000>;
+			};
+		};
+	};
+
+	// Bring the I2S clock consumer block up
+	fragment@4 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			#sound-dai-cells = <0>;
+			status = "okay";
+		};
+	};
+
+	fragment@5 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			pcm3168a: audio-codec@45 {
+				#sound-dai-cells = <1>;
+				compatible = "ti,pcm3168a";
+				status = "okay";
+				reg = <0x45>;
+				clocks = <&scki_clk>;
+				clock-names = "scki";
+				dac-force-cons;
+				VDD1-supply = <&vdd_3v3_reg>;
+				VDD2-supply = <&vdd_3v3_reg>;
+				VCCAD1-supply = <&vdd_5v0_reg>;
+				VCCAD2-supply = <&vdd_5v0_reg>;
+				VCCDA1-supply = <&vdd_5v0_reg>;
+				VCCDA2-supply = <&vdd_5v0_reg>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/fbtft-overlay.dts b/arch/arm/boot/dts/overlays/fbtft-overlay.dts
new file mode 100644
index 000000000000..db45f8c53bcc
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/fbtft-overlay.dts
@@ -0,0 +1,611 @@
+/*
+ * Device Tree overlay for fbtft drivers
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	/* adafruit18 */
+	fragment@0 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "sitronix,st7735r";
+			spi-max-frequency = <32000000>;
+			gamma = "02 1c 07 12 37 32 29 2d 29 25 2B 39 00 01 03 10\n03 1d 07 06 2E 2C 29 2D 2E 2E 37 3F 00 00 02 10";
+		};
+	};
+
+	/* adafruit22 */
+	fragment@1 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "himax,hx8340bn";
+			spi-max-frequency = <32000000>;
+			buswidth = <9>;
+			bgr;
+		};
+	};
+
+	/* adafruit22a */
+	fragment@2 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "ilitek,ili9340";
+			spi-max-frequency = <32000000>;
+			bgr;
+		};
+	};
+
+	/* adafruit28 */
+	fragment@3 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "ilitek,ili9341";
+			spi-max-frequency = <32000000>;
+			bgr;
+		};
+	};
+
+	/* adafruit13m */
+	fragment@4 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "solomon,ssd1306";
+			spi-max-frequency = <16000000>;
+		};
+	};
+
+	/* admatec_c-berry28 */
+	fragment@5 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "sitronix,st7789v";
+			spi-max-frequency = <48000000>;
+			init = <0x01000011
+				0x02000078
+				0x0100003A 0x05
+				0x010000B2 0x0C 0x0C 0x00 0x33 0x33
+				0x010000B7 0x35
+				0x010000C2 0x01 0xFF
+				0x010000C3 0x17
+				0x010000C4 0x20
+				0x010000BB 0x17
+				0x010000C5 0x20
+				0x010000D0 0xA4 0xA1
+				0x01000029>;
+			gamma = "D0 00 14 15 13 2C 42 43 4E 09 16 14 18 21\nD0 00 14 15 13 0B 43 55 53 0C 17 14 23 20";
+		};
+	};
+
+	/* dogs102 */
+	fragment@6 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "UltraChip,uc1701";
+			spi-max-frequency = <8000000>;
+			bgr;
+		};
+	};
+
+	/* er_tftm050_2 */
+	fragment@7 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "raio,ra8875";
+			spi-max-frequency = <5000000>;
+			spi-cpha;
+			spi-cpol;
+			width = <480>;
+			height = <272>;
+			bgr;
+		};
+	};
+
+	/* er_tftm070_5 */
+	fragment@8 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "raio,ra8875";
+			spi-max-frequency = <5000000>;
+			spi-cpha;
+			spi-cpol;
+			width = <800>;
+			height = <480>;
+			bgr;
+		};
+	};
+
+	/* ew24ha0 */
+	fragment@9 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "ultrachip,uc1611";
+			spi-max-frequency = <32000000>;
+			spi-cpha;
+			spi-cpol;
+		};
+	};
+
+	/* ew24ha0_9bit */
+	fragment@10 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "ultrachip,uc1611";
+			spi-max-frequency = <32000000>;
+			spi-cpha;
+			spi-cpol;
+			buswidth = <9>;
+		};
+	};
+
+	/* freetronicsoled128 */
+	fragment@11 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "solomon,ssd1351";
+			spi-max-frequency = <20000000>;
+			backlight = <2>; /* FBTFT_ONBOARD_BACKLIGHT */
+			bgr;
+		};
+	};
+
+	/* hy28a */
+	fragment@12 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "ilitek,ili9320";
+			spi-max-frequency = <32000000>;
+			spi-cpha;
+			spi-cpol;
+			startbyte = <0x70>;
+			bgr;
+		};
+	};
+
+	/* hy28b */
+	fragment@13 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "ilitek,ili9325";
+			spi-max-frequency = <48000000>;
+			spi-cpha;
+			spi-cpol;
+			init = <0x010000e7 0x0010
+				0x01000000 0x0001
+				0x01000001 0x0100
+				0x01000002 0x0700
+				0x01000003 0x1030
+				0x01000004 0x0000
+				0x01000008 0x0207
+				0x01000009 0x0000
+				0x0100000a 0x0000
+				0x0100000c 0x0001
+				0x0100000d 0x0000
+				0x0100000f 0x0000
+				0x01000010 0x0000
+				0x01000011 0x0007
+				0x01000012 0x0000
+				0x01000013 0x0000
+				0x02000032
+				0x01000010 0x1590
+				0x01000011 0x0227
+				0x02000032
+				0x01000012 0x009c
+				0x02000032
+				0x01000013 0x1900
+				0x01000029 0x0023
+				0x0100002b 0x000e
+				0x02000032
+				0x01000020 0x0000
+				0x01000021 0x0000
+				0x02000032
+				0x01000050 0x0000
+				0x01000051 0x00ef
+				0x01000052 0x0000
+				0x01000053 0x013f
+				0x01000060 0xa700
+				0x01000061 0x0001
+				0x0100006a 0x0000
+				0x01000080 0x0000
+				0x01000081 0x0000
+				0x01000082 0x0000
+				0x01000083 0x0000
+				0x01000084 0x0000
+				0x01000085 0x0000
+				0x01000090 0x0010
+				0x01000092 0x0000
+				0x01000093 0x0003
+				0x01000095 0x0110
+				0x01000097 0x0000
+				0x01000098 0x0000
+				0x01000007 0x0133
+				0x01000020 0x0000
+				0x01000021 0x0000
+				0x02000064>;
+			startbyte = <0x70>;
+			bgr;
+			fps = <50>;
+			gamma = "04 1F 4 7 7 0 7 7 6 0\n0F 00 1 7 4 0 0 0 6 7";
+		};
+	};
+
+	/* itdb28_spi */
+	fragment@14 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "ilitek,ili9325";
+			spi-max-frequency = <32000000>;
+			bgr;
+		};
+	};
+
+	/* mi0283qt-2 */
+	fragment@15 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "himax,hx8347d";
+			spi-max-frequency = <32000000>;
+			startbyte = <0x70>;
+			bgr;
+		};
+	};
+
+	/* mi0283qt-9a */
+	fragment@16 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "ilitek,ili9341";
+			spi-max-frequency = <32000000>;
+			buswidth = <9>;
+			bgr;
+		};
+	};
+
+	/* nokia3310 */
+	fragment@17 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "philips,pcd8544";
+			spi-max-frequency = <400000>;
+		};
+	};
+
+	/* nokia3310a */
+	fragment@18 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "teralane,tls8204";
+			spi-max-frequency = <1000000>;
+		};
+	};
+
+	/* nokia5110 */
+	fragment@19 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "ilitek,ili9163";
+			spi-max-frequency = <12000000>;
+			bgr;
+		};
+	};
+
+	/* piscreen */
+	fragment@20 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "ilitek,ili9486";
+			spi-max-frequency = <32000000>;
+			regwidth = <16>;
+			bgr;
+		};
+	};
+
+	/* pitft */
+	fragment@21 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "ilitek,ili9340";
+			spi-max-frequency = <32000000>;
+			init = <0x01000001
+				0x02000005
+				0x01000028
+				0x010000EF 0x03 0x80 0x02
+				0x010000CF 0x00 0xC1 0x30
+				0x010000ED 0x64 0x03 0x12 0x81
+				0x010000E8 0x85 0x00 0x78
+				0x010000CB 0x39 0x2C 0x00 0x34 0x02
+				0x010000F7 0x20
+				0x010000EA 0x00 0x00
+				0x010000C0 0x23
+				0x010000C1 0x10
+				0x010000C5 0x3E 0x28
+				0x010000C7 0x86
+				0x0100003A 0x55
+				0x010000B1 0x00 0x18
+				0x010000B6 0x08 0x82 0x27
+				0x010000F2 0x00
+				0x01000026 0x01
+				0x010000E0 0x0F 0x31 0x2B 0x0C 0x0E 0x08 0x4E 0xF1 0x37 0x07 0x10 0x03 0x0E 0x09 0x00
+				0x010000E1 0x00 0x0E 0x14 0x03 0x11 0x07 0x31 0xC1 0x48 0x08 0x0F 0x0C 0x31 0x36 0x0F
+				0x01000011
+				0x02000064
+				0x01000029
+				0x02000014>;
+			bgr;
+		};
+	};
+
+	/* pioled */
+	fragment@22 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "solomon,ssd1351";
+			spi-max-frequency = <20000000>;
+			bgr;
+			gamma = "0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4 4 4 4 4 4 4";
+		};
+	};
+
+	/* rpi-display */
+	fragment@23 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "ilitek,ili9341";
+			spi-max-frequency = <32000000>;
+			bgr;
+		};
+	};
+
+	/* sainsmart18 */
+	fragment@24 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "sitronix,st7735r";
+			spi-max-frequency = <32000000>;
+		};
+	};
+
+	/* sainsmart32_spi */
+	fragment@25 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "solomon,ssd1289";
+			spi-max-frequency = <16000000>;
+			bgr;
+		};
+	};
+
+	/* tinylcd35 */
+	fragment@26 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "neosec,tinylcd";
+			spi-max-frequency = <32000000>;
+			bgr;
+		};
+	};
+
+	/* tm022hdh26 */
+	fragment@27 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "ilitek,ili9341";
+			spi-max-frequency = <32000000>;
+			bgr;
+		};
+	};
+
+	/* tontec35_9481 - boards before 02 July 2014 */
+	fragment@28 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "ilitek,ili9481";
+			spi-max-frequency = <128000000>;
+			spi-cpha;
+			spi-cpol;
+			bgr;
+		};
+	};
+
+	/* tontec35_9486 - boards after 02 July 2014 */
+	fragment@29 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "ilitek,ili9486";
+			spi-max-frequency = <128000000>;
+			spi-cpha;
+			spi-cpol;
+			bgr;
+		};
+	};
+
+	/* waveshare32b */
+	fragment@30 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "ilitek,ili9340";
+			spi-max-frequency = <48000000>;
+			init = <0x010000CB 0x39 0x2C 0x00 0x34 0x02
+				0x010000CF 0x00 0xC1 0x30
+				0x010000E8 0x85 0x00 0x78
+				0x010000EA 0x00 0x00
+				0x010000ED 0x64 0x03 0x12 0x81
+				0x010000F7 0x20
+				0x010000C0 0x23
+				0x010000C1 0x10
+				0x010000C5 0x3E 0x28
+				0x010000C7 0x86
+				0x01000036 0x28
+				0x0100003A 0x55
+				0x010000B1 0x00 0x18
+				0x010000B6 0x08 0x82 0x27
+				0x010000F2 0x00
+				0x01000026 0x01
+				0x010000E0 0x0F 0x31 0x2B 0x0C 0x0E 0x08 0x4E 0xF1 0x37 0x07 0x10 0x03 0x0E 0x09 0x00
+				0x010000E1 0x00 0x0E 0x14 0x03 0x11 0x07 0x31 0xC1 0x48 0x08 0x0F 0x0C 0x31 0x36 0x0F
+				0x01000011
+				0x02000078
+				0x01000029
+				0x0100002C>;
+			bgr;
+		};
+	};
+
+	/* waveshare22 */
+	fragment@31 {
+		target = <&display>;
+		__dormant__ {
+			compatible = "hitachi,bd663474";
+			spi-max-frequency = <32000000>;
+			spi-cpha;
+			spi-cpol;
+		};
+	};
+
+	spidev_fragment: fragment@100 {
+		target-path = "spi0/spidev@0";
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	display_fragment: fragment@101 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			status = "okay";
+
+			display: display@0{
+				reg = <0>;
+				spi-max-frequency = <32000000>;
+				fps = <30>;
+				buswidth = <8>;
+			};
+		};
+	};
+
+	__overrides__ {
+		spi0-0        = <&display_fragment>, "target:0=",<&spi0>,
+				<&spidev_fragment>, "target-path=spi0/spidev@0",
+				<&display>, "reg:0=0";
+		spi0-1        = <&display_fragment>, "target:0=",<&spi0>,
+				<&spidev_fragment>, "target-path=spi0/spidev@1",
+				<&display>, "reg:0=1";
+		spi1-0        = <&display_fragment>, "target:0=",<&spi1>,
+				<&spidev_fragment>, "target-path=spi1/spidev@0",
+				<&display>, "reg:0=0";
+		spi1-1        = <&display_fragment>, "target:0=",<&spi1>,
+				<&spidev_fragment>, "target-path=spi1/spidev@1",
+				<&display>, "reg:0=1";
+		spi1-2        = <&display_fragment>, "target:0=",<&spi1>,
+				<&spidev_fragment>, "target-path=spi1/spidev@2",
+				<&display>, "reg:0=2";
+		spi2-0        = <&display_fragment>, "target:0=",<&spi2>,
+				<&spidev_fragment>, "target-path=spi2/spidev@0",
+				<&display>, "reg:0=0";
+		spi2-1        = <&display_fragment>, "target:0=",<&spi2>,
+				<&spidev_fragment>, "target-path=spi2/spidev@1",
+				<&display>, "reg:0=1";
+		spi2-2        = <&display_fragment>, "target:0=",<&spi2>,
+				<&spidev_fragment>, "target-path=spi2/spidev@2",
+				<&display>, "reg:0=2";
+
+		speed         = <&display>, "spi-max-frequency:0";
+		cpha          = <&display>, "spi-cpha?";
+		cpol          = <&display>, "spi-cpol?";
+
+		/* Displays */
+		adafruit18    = <0>, "+0";
+		adafruit22    = <0>, "+1";
+		adafruit22a   = <0>, "+2";
+		adafruit28    = <0>, "+3";
+		adafruit13m   = <0>, "+4";
+		admatec_c-berry28 = <0>, "+5";
+		dogs102       = <0>, "+6";
+		er_tftm050_2  = <0>, "+7";
+		er_tftm070_5  = <0>, "+8";
+		ew24ha0       = <0>, "+9";
+		ew24ha0_9bit  = <0>, "+10";
+		freetronicsoled128 = <0>, "+11";
+		hy28a         = <0>, "+12";
+		hy28b         = <0>, "+13";
+		itdb28_spi    = <0>, "+14";
+		mi0283qt-2    = <0>, "+15";
+		mi0283qt-9a   = <0>, "+16";
+		nokia3310     = <0>, "+17";
+		nokia3310a    = <0>, "+18";
+		nokia5110     = <0>, "+19";
+		piscreen      = <0>, "+20";
+		pitft         = <0>, "+21";
+		pioled        = <0>, "+22";
+		rpi-display   = <0>, "+23";
+		sainsmart18   = <0>, "+24";
+		sainsmart32_spi = <0>, "+25";
+		tinylcd35     = <0>, "+26";
+		tm022hdh26    = <0>, "+27";
+		tontec35_9481 = <0>, "+28";
+		tontec35_9486 = <0>, "+29";
+		waveshare32b  = <0>, "+30";
+		waveshare22   = <0>, "+31";
+
+		/* Controllers */
+		bd663474      = <&display>, "compatible=hitachi,bd663474";
+		hx8340bn      = <&display>, "compatible=himax,hx8340bn";
+		hx8347d       = <&display>, "compatible=himax,hx8347d";
+		hx8353d       = <&display>, "compatible=himax,hx8353d";
+		hx8357d       = <&display>, "compatible=himax,hx8357d";
+		ili9163       = <&display>, "compatible=ilitek,ili9163";
+		ili9320       = <&display>, "compatible=ilitek,ili9320";
+		ili9325       = <&display>, "compatible=ilitek,ili9325";
+		ili9340       = <&display>, "compatible=ilitek,ili9340";
+		ili9341       = <&display>, "compatible=ilitek,ili9341";
+		ili9481       = <&display>, "compatible=ilitek,ili9481";
+		ili9486       = <&display>, "compatible=ilitek,ili9486";
+		pcd8544       = <&display>, "compatible=philips,pcd8544";
+		ra8875        = <&display>, "compatible=raio,ra8875";
+		s6d02a1       = <&display>, "compatible=samsung,s6d02a1";
+		s6d1121       = <&display>, "compatible=samsung,s6d1121";
+		seps525       = <&display>, "compatible=syncoam,seps525";
+		sh1106        = <&display>, "compatible=sinowealth,sh1106";
+		ssd1289       = <&display>, "compatible=solomon,ssd1289";
+		ssd1305       = <&display>, "compatible=solomon,ssd1305";
+		ssd1306       = <&display>, "compatible=solomon,ssd1306";
+		ssd1325       = <&display>, "compatible=solomon,ssd1325";
+		ssd1331       = <&display>, "compatible=solomon,ssd1331";
+		ssd1351       = <&display>, "compatible=solomon,ssd1351";
+		st7735r       = <&display>, "compatible=sitronix,st7735r";
+		st7789v       = <&display>, "compatible=sitronix,st7789v";
+		tls8204       = <&display>, "compatible=teralane,tls8204";
+		uc1611        = <&display>, "compatible=ultrachip,uc1611";
+		uc1701        = <&display>, "compatible=UltraChip,uc1701";
+		upd161704     = <&display>, "compatible=nec,upd161704";
+
+		width         = <&display>, "width:0";
+		height        = <&display>, "height:0";
+		regwidth      = <&display>, "regwidth:0";
+		buswidth      = <&display>, "buswidth:0";
+		debug         = <&display>, "debug:0";
+		rotate        = <&display>, "rotate:0";
+		bgr           = <&display>, "bgr?";
+		fps           = <&display>, "fps:0";
+		txbuflen      = <&display>, "txbuflen:0";
+		startbyte     = <&display>, "startbyte:0";
+		gamma         = <&display>, "gamma";
+
+		reset_pin     = <&display>, "reset-gpios:0=", <&gpio>,
+				<&display>, "reset-gpios:4",
+				<&display>, "reset-gpios:8=1"; /* GPIO_ACTIVE_LOW */
+		dc_pin        = <&display>, "dc-gpios:0=", <&gpio>,
+				<&display>, "dc-gpios:4",
+				<&display>, "dc-gpios:8=0"; /* GPIO_ACTIVE_HIGH */
+		led_pin       = <&display>, "led-gpios:0=", <&gpio>,
+				<&display>, "led-gpios:4",
+				<&display>, "led-gpios:8=0"; /* GPIO_ACTIVE_HIGH */
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/fe-pi-audio-overlay.dts b/arch/arm/boot/dts/overlays/fe-pi-audio-overlay.dts
new file mode 100644
index 000000000000..10624fe4f5ac
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/fe-pi-audio-overlay.dts
@@ -0,0 +1,70 @@
+// Definitions for Fe-Pi Audio
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			sgtl5000_mclk: sgtl5000_mclk {
+				compatible = "fixed-clock";
+				#clock-cells = <0>;
+				clock-frequency = <12288000>;
+				clock-output-names = "sgtl5000-mclk";
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&soc>;
+		__overlay__ {
+			reg_1v8: reg_1v8@0 {
+				compatible = "regulator-fixed";
+				regulator-name = "1V8";
+				regulator-min-microvolt = <1800000>;
+				regulator-max-microvolt = <1800000>;
+				regulator-always-on;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			sgtl5000@a {
+				#sound-dai-cells = <0>;
+				compatible = "fsl,sgtl5000";
+				reg = <0x0a>;
+				clocks = <&sgtl5000_mclk>;
+				micbias-resistor-k-ohms = <2>;
+				micbias-voltage-m-volts = <3000>;
+				VDDA-supply = <&vdd_3v3_reg>;
+				VDDIO-supply = <&vdd_3v3_reg>;
+				VDDD-supply = <&reg_1v8>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@4 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "fe-pi,fe-pi-audio";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/fsm-demo-overlay.dts b/arch/arm/boot/dts/overlays/fsm-demo-overlay.dts
new file mode 100644
index 000000000000..e9944f5cd258
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/fsm-demo-overlay.dts
@@ -0,0 +1,104 @@
+// Demo overlay for the gpio-fsm driver
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio-fsm.h>
+
+#define BUTTON1 GF_IP(0)
+#define BUTTON2 GF_SW(0)
+#define RED   GF_OP(0) // GPIO7
+#define AMBER GF_OP(1) // GPIO8
+#define GREEN GF_OP(2) // GPIO25
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			fsm_demo: fsm-demo {
+				compatible = "rpi,gpio-fsm";
+
+				debug = <0>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				num-swgpios = <1>;
+				gpio-line-names = "button2";
+				input-gpios  = <&gpio 6 1>;  // BUTTON1 (active-low)
+				output-gpios = <&gpio 7 0>,  // RED
+					       <&gpio 8 0>,  // AMBER
+					       <&gpio 25 0>; // GREEN
+				shutdown-timeout-ms = <2000>;
+
+				start {
+					start_state;
+					set = <RED 1>, <AMBER 0>, <GREEN 0>;
+					start2 = <GF_DELAY 250>;
+				};
+
+				start2 {
+					set = <RED 0>, <AMBER 1>;
+					go = <GF_DELAY 250>;
+				};
+
+				go {
+					set = <RED 0>, <AMBER 0>, <GREEN 1>;
+					ready_wait = <BUTTON1 0>;
+					shutdown1 = <GF_SHUTDOWN 0>;
+				};
+
+				ready_wait {
+					// Clear the soft GPIO
+					set = <BUTTON2 0>;
+					ready = <GF_DELAY 1000>;
+					shutdown1 = <GF_SHUTDOWN 0>;
+				};
+
+				ready {
+					stopping = <BUTTON1 1>, <BUTTON2 1>;
+					shutdown1 = <GF_SHUTDOWN 0>;
+				};
+
+				stopping {
+					set = <GREEN 0>, <AMBER 1>;
+					stopped = <GF_DELAY 1000>;
+				};
+
+				stopped {
+					set = <AMBER 0>, <RED 1>;
+					get_set = <GF_DELAY 3000>;
+					shutdown1 = <GF_SHUTDOWN 0>;
+				};
+
+				get_set {
+					set = <AMBER 1>;
+					go = <GF_DELAY 1000>;
+				};
+
+				shutdown1 {
+					set = <RED 0>, <AMBER 0>, <GREEN 1>;
+					shutdown2 = <GF_SHUTDOWN 250>;
+				};
+
+				shutdown2 {
+					set = <AMBER 1>, <GREEN 0>;
+					shutdown3 = <GF_SHUTDOWN 250>;
+				};
+
+				shutdown3 {
+					set = <RED 1>, <AMBER 0>;
+					shutdown4 = <GF_SHUTDOWN 250>;
+				};
+
+				shutdown4 {
+					shutdown_state;
+					set = <RED 0>, <AMBER 0>, <GREEN 0>;
+				};
+			};
+	       };
+        };
+
+	__overrides__ {
+		fsm_debug = <&fsm_demo>,"debug:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/gc9a01-overlay.dts b/arch/arm/boot/dts/overlays/gc9a01-overlay.dts
new file mode 100644
index 000000000000..3d31030c5564
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/gc9a01-overlay.dts
@@ -0,0 +1,151 @@
+/*
+    Device Tree overlay for Galaxycore GC9A01A single chip driver
+    for use on SPI TFT LCD, 240x240 65K RGB
+    Based on Galaxycore's GC9A01A datasheet Rev.1.0 (2019/07/02)
+    Copyright (C) 2022, Julianno F. C. Silva (@juliannojungle)
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/agpl-3.0.html>.
+
+    Init sequence partially based on Waveshare team's Arduino LCD_Driver V1.0 (2020/12/09).
+
+    Permission is hereby granted, free of UBYTEge, to any person obtaining a copy
+    of this software and associated documnetation files (the "Software"), to deal
+    in the Software without restriction, including without limitation the rights
+    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+    copies of the Software, and to permit persons to whom the Software is
+    furished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in
+    all copies or substantial portions of the Software.
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+    compatible = "brcm,bcm2835";
+
+    fragment@0 {
+        target = <&spidev0>;
+        __overlay__ {
+            status = "disabled";
+        };
+    };
+
+    fragment@1 {
+        target = <&gpio>;
+        __overlay__ {
+            gc9a01_pins: gc9a01_pins {
+                brcm,pins = <25 27>;
+                brcm,function = <1 1>; /* out */
+                brcm,pull = <0 0>; /* none */
+            };
+        };
+    };
+
+    fragment@2 {
+        target = <&spi0>;
+        __overlay__ {
+            /* needed to avoid dtc warning */
+            #address-cells = <1>;
+            #size-cells = <0>;
+            status = "okay";
+
+            gc9a01: gc9a01@0 {
+                compatible = "ilitek,ili9340";
+                reg = <0>;
+                pinctrl-names = "default";
+                pinctrl-0 = <&gc9a01_pins>;
+                reset-gpios = <&gpio 27 1>;
+                dc-gpios = <&gpio 25 0>;
+                led-gpios = <&gpio 18 0>;
+                spi-max-frequency = <40000000>;
+                buswidth = <8>;
+                width = <240>;
+                height = <240>;
+                rotate = <0>;
+                fps = <50>;
+                bgr;
+                debug = <0>;
+                init = <
+                    0x01000011 /* Sleep mode OFF */
+                    0x02000078 /* Delay 120ms */
+                    0x010000EF /* Inter register enable 2 */
+                    0x010000EB 0x14
+                    /* BEGIN set inter_command HIGH */
+                    0x010000FE /* Inter register enable 1 */
+                    0x010000EF /* Inter register enable 2 */
+                    /* END set inter_command HIGH */
+                    0x010000EB 0x14
+                    0x01000084 0x40
+                    0x01000085 0xFF
+                    0x01000086 0xFF
+                    0x01000087 0xFF
+                    0x01000088 0x0A
+                    0x01000089 0x21
+                    0x0100008A 0x00
+                    0x0100008B 0x80
+                    0x0100008C 0x01
+                    0x0100008D 0x01
+                    0x0100008E 0xFF
+                    0x0100008F 0xFF
+                    0x010000B6 0x00 0x00 /* Display function control */
+                    0x01000036 0x08 /* Memory access control */
+                    0x0100003A 0x05 /* Pixel format */
+                    0x01000090 0x08 0x08 0x08 0x08
+                    0x010000BD 0x06
+                    0x010000BC 0x00
+                    0x010000FF 0x60 0x01 0x04
+                    0x010000C3 0x13 /* Voltage regulator 1a */
+                    0x010000C4 0x13 /* Voltage regulator 1b */
+                    0x010000C9 0x22 /* Voltage regulator 2a */
+                    0x010000BE 0x11
+                    0x010000E1 0x10 0x0E
+                    0x010000DF 0x21 0x0c 0x02
+                    0x010000F0 0x45 0x09 0x08 0x08 0x26 0x2A /* Set gamma1 */
+                    0x010000F1 0x43 0x70 0x72 0x36 0x37 0x6F /* Set gamma2 */
+                    0x010000F2 0x45 0x09 0x08 0x08 0x26 0x2A /* Set gamma3 */
+                    0x010000F3 0x43 0x70 0x72 0x36 0x37 0x6F /* Set gamma4 */
+                    0x010000ED 0x1B 0x0B
+                    0x010000AE 0x77
+                    0x010000CD 0x63
+                    0x01000070 0x07 0x07 0x04 0x0E 0x0F 0x09 0x07 0x08 0x03
+                    0x010000E8 0x34 /* Frame rate */
+                    0x01000062 0x18 0x0D 0x71 0xED 0x70 0x70 0x18 0x0F 0x71 0xEF 0x70 0x70
+                    0x01000063 0x18 0x11 0x71 0xF1 0x70 0x70 0x18 0x13 0x71 0xF3 0x70 0x70
+                    0x01000064 0x28 0x29 0xF1 0x01 0xF1 0x00 0x07
+                    0x01000066 0x3C 0x00 0xCD 0x67 0x45 0x45 0x10 0x00 0x00 0x00
+                    0x01000067 0x00 0x3C 0x00 0x00 0x00 0x01 0x54 0x10 0x32 0x98
+                    0x01000074 0x10 0x85 0x80 0x00 0x00 0x4E 0x00
+                    0x01000098 0x3e 0x07
+                    0x01000035 /* Tearing effect ON */
+                    0x01000021 /* Display inversion ON */
+                    0x01000011 /* Sleep mode OFF */
+                    0x0200000C /* Delay 12ms */
+                    0x01000029 /* Display ON */
+                    0x02000014 /* Delay 20ms */
+                    >;
+            };
+        };
+    };
+
+    __overrides__ {
+        speed = <&gc9a01>,"spi-max-frequency:0";
+        rotate = <&gc9a01>,"rotate:0";
+        width = <&gc9a01>,"width:0";
+        height = <&gc9a01>,"height:0";
+        fps = <&gc9a01>,"fps:0";
+        debug = <&gc9a01>,"debug:0";
+    };
+};
diff --git a/arch/arm/boot/dts/overlays/ghost-amp-overlay.dts b/arch/arm/boot/dts/overlays/ghost-amp-overlay.dts
new file mode 100644
index 000000000000..d2f1e9a888e0
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ghost-amp-overlay.dts
@@ -0,0 +1,145 @@
+// Overlay for the PCM5122-based Ghost amplifier using gpio-fsm
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio-fsm.h>
+
+#define ENABLE   GF_SW(0)
+#define FAULT    GF_IP(0) // GPIO5
+#define RELAY1   GF_OP(0) // GPIO22
+#define RELAY2   GF_OP(1) // GPIO23
+#define RELAYSSR GF_OP(2) // GPIO24
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			pcm5122@4c {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5122";
+				reg = <0x4c>;
+				AVDD-supply = <&vdd_3v3_reg>;
+				DVDD-supply = <&vdd_3v3_reg>;
+				CPVDD-supply = <&vdd_3v3_reg>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		iqaudio_dac: __overlay__ {
+			compatible = "iqaudio,iqaudio-dac";
+			i2s-controller = <&i2s_clk_producer>;
+			mute-gpios = <&amp 0 0>;
+			iqaudio-dac,auto-mute-amp;
+			status = "okay";
+		};
+	};
+
+	fragment@3 {
+		target-path = "/";
+		__overlay__ {
+			amp: ghost-amp {
+				compatible = "rpi,gpio-fsm";
+				pinctrl-names = "default";
+				pinctrl-0 = <&ghost_amp_pins>;
+
+				debug = <0>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				num-swgpios = <1>;
+				gpio-line-names = "enable";
+				input-gpios  = <&gpio 5 1>;  // FAULT (active low)
+				output-gpios = <&gpio 22 0>, // RELAY1
+					       <&gpio 23 0>, // RELAY2
+					       <&gpio 24 0>; // RELAYSSR
+				shutdown-timeout-ms = <1000>;
+
+				amp_off {
+					start_state;
+					shutdown_state;
+
+					set = <RELAYSSR 0>,
+					      <RELAY2 0>,
+					      <RELAY1 0>;
+					amp_on_1 = <ENABLE 1>;
+					fault = <FAULT 1>;
+				};
+
+				amp_on_1 {
+					set = <RELAY1 1>;
+					amp_on_2 = <GF_DELAY 1000>;
+					amp_off = <GF_SHUTDOWN 0>;
+					fault = <FAULT 1>;
+				};
+
+				amp_on_2 {
+					set = <RELAY2 1>;
+					amp_on_wait = <ENABLE 0>;
+					amp_on = <GF_DELAY 1>;
+					fault = <FAULT 1>;
+				};
+
+				amp_on {
+					set = <RELAYSSR 1>;
+					amp_on_wait = <ENABLE 0>;
+					fault = <FAULT 1>;
+				};
+
+				amp_on_wait {
+					set = <RELAYSSR 0>;
+					amp_off_1 = <GF_DELAY (30*60*1000)>,
+						    <GF_SHUTDOWN 0>;
+					amp_on = <ENABLE 1>;
+					fault = <FAULT 1>;
+				};
+
+				amp_off_1 {
+					set = <RELAY2 0>;
+					amp_on = <ENABLE 1>;
+					amp_off = <GF_DELAY 100>;
+					fault = <FAULT 1>;
+				};
+
+				// Keep this a distinct state to prevent
+				// changes and for the diagnostic output
+				fault {
+					set = <RELAYSSR 0>,
+					      <RELAY2 0>,
+					      <RELAY1 0>;
+					amp_off = <FAULT 0>;
+					shutdown_state;
+				};
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&gpio>;
+		__overlay__ {
+			ghost_amp_pins: ghost_amp_pins {
+				brcm,pins = <5 22 23 24>;
+				brcm,function = <0 1 1 1>; /* in out out out */
+				brcm,pull = <2 0 0 0>; /* up none none none */
+			};
+		};
+	};
+
+	__overrides__ {
+		fsm_debug = <&amp>,"debug:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/goodix-overlay.dts b/arch/arm/boot/dts/overlays/goodix-overlay.dts
new file mode 100644
index 000000000000..8a5035df9daa
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/goodix-overlay.dts
@@ -0,0 +1,49 @@
+// Device tree overlay for I2C connected Goodix gt9271 multiple touch controller
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			goodix_pins: goodix_pins {
+				brcm,pins = <4 17>; // interrupt and reset
+				brcm,function = <0 0>; // in
+				brcm,pull = <2 2>; // pull-up
+			};
+		};
+	};
+
+	i2c_frag: fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			gt9271: gt9271@14 {
+				compatible = "goodix,gt9271";
+				reg = <0x14>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&goodix_pins>;
+				interrupt-parent = <&gpio>;
+				interrupts = <4 2>; // high-to-low edge triggered
+				irq-gpios = <&gpio 4 0>; // Pin7 on GPIO header
+				reset-gpios = <&gpio 17 0>; // Pin11 on GPIO header
+			};
+		};
+	};
+
+	__overrides__ {
+		addr = <&gt9271>,"reg:0";
+		interrupt = <&goodix_pins>,"brcm,pins:0",
+			<&gt9271>,"interrupts:0",
+			<&gt9271>,"irq-gpios:4";
+		reset = <&goodix_pins>,"brcm,pins:4",
+			<&gt9271>,"reset-gpios:4";
+		i2c-path = <&i2c_frag>, "target?=0",
+			   <&i2c_frag>, "target-path";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/googlevoicehat-soundcard-overlay.dts b/arch/arm/boot/dts/overlays/googlevoicehat-soundcard-overlay.dts
new file mode 100644
index 000000000000..1063f1898562
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/googlevoicehat-soundcard-overlay.dts
@@ -0,0 +1,49 @@
+// Definitions for Google voiceHAT v1 soundcard overlay
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			googlevoicehat_pins: googlevoicehat_pins {
+				brcm,pins = <16>;
+				brcm,function = <1>; /* out */
+				brcm,pull = <0>; /* up */
+			};
+		};
+	};
+
+
+	fragment@2 {
+		target-path = "/";
+		__overlay__ {
+			voicehat-codec {
+				#sound-dai-cells = <0>;
+				compatible = "google,voicehat";
+				pinctrl-names = "default";
+				pinctrl-0 = <&googlevoicehat_pins>;
+				sdmode-gpios= <&gpio 16 0>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "googlevoicehat,googlevoicehat-soundcard";
+			i2s-controller = <&i2s_clk_producer>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/gpio-charger-overlay.dts b/arch/arm/boot/dts/overlays/gpio-charger-overlay.dts
new file mode 100644
index 000000000000..2868aa06dd6d
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/gpio-charger-overlay.dts
@@ -0,0 +1,42 @@
+// Definitions for gpio-charger module
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		// Configure the gpio pin controller
+		target = <&gpio>;
+		__overlay__ {
+			pin_state: charger_pins@0 {
+				brcm,pins = <4>; // gpio number
+				brcm,function = <0>; // 0 = input, 1 = output
+				brcm,pull = <1>; // 0 = none, 1 = pull down, 2 = pull up
+			};
+		};
+	};
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			charger: charger@0 {
+				compatible = "gpio-charger";
+				pinctrl-0 = <&pin_state>;
+				status = "okay";
+				gpios = <&gpio 4 0>;
+				charger-type = "mains";
+			};
+		};
+	};
+
+	__overrides__ {
+		gpio =       <&charger>,"reg:0",
+			     <&charger>,"gpios:4",
+			     <&pin_state>,"reg:0",
+			     <&pin_state>,"brcm,pins:0";
+		type =       <&charger>,"charger-type";
+		gpio_pull =  <&pin_state>,"brcm,pull:0";
+		active_low = <&charger>,"gpios:8";
+	};
+
+};
diff --git a/arch/arm/boot/dts/overlays/gpio-fan-overlay.dts b/arch/arm/boot/dts/overlays/gpio-fan-overlay.dts
new file mode 100644
index 000000000000..17b77bb27931
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/gpio-fan-overlay.dts
@@ -0,0 +1,89 @@
+/*
+ * Overlay for the Raspberry Pi GPIO Fan @ BCM GPIO12.
+ * References: 
+ *	- https://www.raspberrypi.org/forums/viewtopic.php?f=107&p=1367135#p1365084
+ *
+ * Optional parameters:
+ *	- "gpiopin"	- BCM number of the pin driving the fan, default 12 (GPIO12);
+ * 	- "temp"	- CPU temperature at which fan is started in millicelsius, default 55000;
+ *
+ * Requires:
+ *	- kernel configurations: CONFIG_SENSORS_GPIO_FAN=m;
+ *	- kernel rebuild;
+ *	- N-MOSFET connected to gpiopin, 2N7002-[https://en.wikipedia.org/wiki/2N7000];
+ *	- DC Fan connected to N-MOSFET Drain terminal, a 12V fan is working fine and quite silently;
+ *	  [https://www.tme.eu/en/details/ee40101s1-999-a/dc12v-fans/sunon/ee40101s1-1000u-999/]
+ *
+ *                   ┌─────────────────────┐
+ *                   │Fan negative terminal│
+ *                   └┬────────────────────┘
+ *                    │D
+ *             G   │──┘
+ * [GPIO12]──────┤ │<─┐  2N7002
+ *                 │──┤
+ *                    │S
+ *                   ─┴─
+ *                   GND
+ *
+ * Build:
+ * 	- `sudo dtc -W no-unit_address_vs_reg -@ -I dts -O dtb -o /boot/overlays/gpio-fan.dtbo gpio-fan-overlay.dts`
+ * Activate:
+ *	- sudo nano /boot/config.txt add "dtoverlay=gpio-fan" or "dtoverlay=gpio-fan,gpiopin=12,temp=45000"
+ *	 or
+ *	- sudo sh -c 'printf "\n# Enable PI GPIO-Fan Default\ndtoverlay=gpio-fan\n" >> /boot/config.txt'
+ *	- sudo sh -c 'printf "\n# Enable PI GPIO-Fan Custom\ndtoverlay=gpio-fan,gpiopin=12,temp=45000\n" >> /boot/config.txt'
+ *
+ */
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			fan0: gpio-fan@0 {
+				compatible = "gpio-fan";
+				gpios = <&gpio 12 0>;
+				gpio-fan,speed-map = <0    0>,
+									 <5000 1>;
+				#cooling-cells = <2>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&cpu_thermal>;
+		__overlay__ {
+			polling-delay = <2000>;	/* milliseconds */
+		};
+	};
+
+	fragment@2 {
+		target = <&thermal_trips>;
+		__overlay__ {
+			cpu_hot: trip-point@0 {
+				temperature = <55000>;	/* (millicelsius) Fan started at 55°C */
+				hysteresis = <10000>;	/* (millicelsius) Fan stopped at 45°C */
+				type = "active";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&cooling_maps>;
+		__overlay__ {
+			map0 {
+				trip = <&cpu_hot>;
+				cooling-device = <&fan0 1 1>;
+			};
+		};
+	};
+
+	__overrides__ {
+		gpiopin = <&fan0>,"gpios:4", <&fan0>,"brcm,pins:0";
+		temp = <&cpu_hot>,"temperature:0";
+		hyst = <&cpu_hot>,"hysteresis:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/gpio-hog-overlay.dts b/arch/arm/boot/dts/overlays/gpio-hog-overlay.dts
new file mode 100644
index 000000000000..c9e39046fed9
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/gpio-hog-overlay.dts
@@ -0,0 +1,27 @@
+// Configure a "hog" on the specified GPIO
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			hog: hog@1a {
+			     gpio-hog;
+			     gpios = <26 GPIO_ACTIVE_HIGH>;
+			     output-high;
+			};
+		};
+	};
+
+	__overrides__ {
+		gpio =       <&hog>,"reg:0",
+		             <&hog>,"gpios:0";
+		active_low = <&hog>,"output-high!",
+			     <&hog>,"output-low?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/gpio-ir-overlay.dts b/arch/arm/boot/dts/overlays/gpio-ir-overlay.dts
new file mode 100644
index 000000000000..162b6ce07dc9
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/gpio-ir-overlay.dts
@@ -0,0 +1,49 @@
+// Definitions for ir-gpio module
+/dts-v1/;
+/plugin/;
+
+/ {
+        compatible = "brcm,bcm2835";
+
+        fragment@0 {
+                target-path = "/";
+                __overlay__ {
+                        gpio_ir: ir-receiver@12 {
+                                compatible = "gpio-ir-receiver";
+                                pinctrl-names = "default";
+                                pinctrl-0 = <&gpio_ir_pins>;
+
+                                // pin number, high or low
+                                gpios = <&gpio 18 1>;
+
+                                // parameter for keymap name
+                                linux,rc-map-name = "rc-rc6-mce";
+
+                                status = "okay";
+                        };
+                };
+        };
+
+        fragment@1 {
+                target = <&gpio>;
+                __overlay__ {
+                        gpio_ir_pins: gpio_ir_pins@12 {
+                                brcm,pins = <18>;                       // pin 18
+                                brcm,function = <0>;                    // in
+                                brcm,pull = <2>;                        // up
+                        };
+                };
+        };
+
+        __overrides__ {
+                // parameters
+                gpio_pin =      <&gpio_ir>,"gpios:4",           // pin number
+                                <&gpio_ir>,"reg:0",
+                                <&gpio_ir_pins>,"brcm,pins:0",
+                                <&gpio_ir_pins>,"reg:0";
+                gpio_pull = <&gpio_ir_pins>,"brcm,pull:0";              // pull-up/down state
+                invert = <&gpio_ir>,"gpios:8";                          // 0 = active high input
+
+                rc-map-name = <&gpio_ir>,"linux,rc-map-name";           // default rc map
+        };
+};
diff --git a/arch/arm/boot/dts/overlays/gpio-ir-tx-overlay.dts b/arch/arm/boot/dts/overlays/gpio-ir-tx-overlay.dts
new file mode 100644
index 000000000000..3625431b7560
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/gpio-ir-tx-overlay.dts
@@ -0,0 +1,36 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			gpio_ir_tx_pins: gpio_ir_tx_pins@12 {
+				brcm,pins = <18>;
+				brcm,function = <1>;	// out
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			gpio_ir_tx: gpio-ir-transmitter@12 {
+				compatible = "gpio-ir-tx";
+				pinctrl-names = "default";
+				pinctrl-0 = <&gpio_ir_tx_pins>;
+				gpios = <&gpio 18 0>;
+			};
+		};
+	};
+
+	__overrides__ {
+		gpio_pin = <&gpio_ir_tx>, "gpios:4",           	// pin number
+			   <&gpio_ir_tx>, "reg:0",
+			   <&gpio_ir_tx_pins>, "brcm,pins:0",
+			   <&gpio_ir_tx_pins>, "reg:0";
+		invert = <&gpio_ir_tx>, "gpios:8";		// 1 = active low
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/gpio-key-overlay.dts b/arch/arm/boot/dts/overlays/gpio-key-overlay.dts
new file mode 100644
index 000000000000..eb5968cbc07f
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/gpio-key-overlay.dts
@@ -0,0 +1,49 @@
+// Definitions for gpio-key module
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		// Configure the gpio pin controller
+		target = <&gpio>;
+		__overlay__ {
+			pin_state: button_pins@0 {
+				brcm,pins = <3>; // gpio number
+				brcm,function = <0>; // 0 = input, 1 = output
+				brcm,pull = <2>; // 0 = none, 1 = pull down, 2 = pull up
+			};
+		};
+	};
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			button: button@0 {
+				compatible = "gpio-keys";
+				pinctrl-names = "default";
+				pinctrl-0 = <&pin_state>;
+				status = "okay";
+
+				key: key {
+					linux,code = <116>;
+					gpios = <&gpio 3 1>;
+					label = "KEY_POWER";
+				};
+			};
+		};
+	};
+
+	__overrides__ {
+		gpio =       <&key>,"gpios:4",
+		             <&button>,"reg:0",
+		             <&pin_state>,"brcm,pins:0",
+		             <&pin_state>,"reg:0";
+		label =      <&key>,"label";
+		keycode =    <&key>,"linux,code:0";
+		gpio_pull =  <&pin_state>,"brcm,pull:0";
+		active_low = <&key>,"gpios:8";
+		autorepeat = <&button>,"autorepeat?";
+	};
+
+};
diff --git a/arch/arm/boot/dts/overlays/gpio-led-overlay.dts b/arch/arm/boot/dts/overlays/gpio-led-overlay.dts
new file mode 100755
index 000000000000..d8e9d53f1b61
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/gpio-led-overlay.dts
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * gpio-led - generic connection of kernel's LED framework to the RPI's GPIO.
+ * Copyright (C) 2021 House Gordon Software Company Ltd. <assafgordon@gmail.com>
+ *
+ * Based on information from:
+ *   https://mjoldfield.com/atelier/2017/03/rpi-devicetree.html
+ *   https://www.raspberrypi.org/documentation/configuration/device-tree.md
+ *   https://www.kernel.org/doc/html/latest/leds/index.html
+ *
+ * compile with:
+ *   dtc -@ -Hepapr -I dts -O dtb -o gpio-led.dtbo gpio-led-overlay.dts
+ *
+ * There will be some warnings (can be ignored):
+ *  Warning (label_is_string): /__overrides__:label: property is not a string
+ *  Warning (unit_address_vs_reg): /fragment@0/__overlay__/led_pins@0:
+ *                                 node has a unit name, but no reg property
+ *  Warning (unit_address_vs_reg): /fragment@1/__overlay__/leds@0:
+ *                                 node has a unit name, but no reg property
+ *  Warning (gpios_property): /__overrides__: Missing property
+ *                 '#gpio-cells' in node /fragment@1/__overlay__/leds@0/led
+ *                  or bad phandle (referred from gpio[0])
+ *
+ * Typical electrical connection is:
+ *    RPI-GPIO.19  ->  LED  -> 300ohm resister  -> RPI-GND
+ *    The GPIO pin number can be changed with the 'gpio=' parameter.
+ *
+ * Test from user-space with:
+ *   # if nothing is shown, the overlay file isn't found in /boot/overlays
+ *   dtoverlay -a | grep gpio-led
+ *
+ *   # Load the overlay
+ *   dtoverlay gpio-led label=moo gpio=19
+ *
+ *   # if nothing is shown, the overlay wasn't loaded successfully
+ *   dtoverlay -l | grep gpio-led
+ *
+ *   echo 1 > /sys/class/leds/moo/brightness
+ *   echo 0 > /sys/class/leds/moo/brightness
+ *   echo cpu > /sys/class/leds/moo/trigger
+ *   echo heartbeat > /sys/class/leds/moo/trigger
+ *
+ *   # unload the overlay
+ *   dtoverlay -r gpio-led
+ *
+ * To load in /boot/config.txt add lines such as:
+ *   dtoverlay=gpio-led,gpio=19,label=heart,trigger=heartbeat
+ *   dtoverlay=gpio-led,gpio=26,label=brain,trigger=cpu
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		// Configure the gpio pin controller
+		target = <&gpio>;
+		__overlay__ {
+			led_pin: led_pins@19 {
+				brcm,pins = <19>; // gpio number
+				brcm,function = <1>; // 0 = input, 1 = output
+				brcm,pull = <0>; // 0 = none, 1 = pull down, 2 = pull up
+			};
+		};
+	};
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			leds: leds@0 {
+				compatible = "gpio-leds";
+				pinctrl-names = "default";
+				pinctrl-0 = <&led_pin>;
+				status = "okay";
+
+				led: led {
+			                label = "myled1";
+					gpios = <&gpio 19 0>;
+			                linux,default-trigger = "none";
+				};
+			};
+		};
+	};
+
+	__overrides__ {
+		gpio =       <&led>,"gpios:4",
+		             <&leds>,"reg:0",
+		             <&led_pin>,"brcm,pins:0",
+		             <&led_pin>,"reg:0";
+		label =      <&led>,"label";
+		active_low = <&led>,"gpios:8";
+		trigger =    <&led>,"linux,default-trigger";
+	};
+
+};
+
diff --git a/arch/arm/boot/dts/overlays/gpio-no-bank0-irq-overlay.dts b/arch/arm/boot/dts/overlays/gpio-no-bank0-irq-overlay.dts
new file mode 100755
index 000000000000..96cbe80820b7
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/gpio-no-bank0-irq-overlay.dts
@@ -0,0 +1,14 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		// Configure the gpio pin controller
+		target = <&gpio>;
+		__overlay__ {
+			    interrupts = <255 255>, <2 18>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/gpio-no-irq-overlay.dts b/arch/arm/boot/dts/overlays/gpio-no-irq-overlay.dts
new file mode 100644
index 000000000000..55f9bff3a8f6
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/gpio-no-irq-overlay.dts
@@ -0,0 +1,14 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		// Configure the gpio pin controller
+		target = <&gpio>;
+		__overlay__ {
+			    interrupts;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/gpio-poweroff-overlay.dts b/arch/arm/boot/dts/overlays/gpio-poweroff-overlay.dts
new file mode 100644
index 000000000000..0af2b0d55c63
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/gpio-poweroff-overlay.dts
@@ -0,0 +1,28 @@
+// Definitions for gpio-poweroff module
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			power_ctrl: power_ctrl {
+				compatible = "gpio-poweroff";
+				gpios = <&gpio 26 0>;
+				force;
+			};
+		};
+	};
+
+	__overrides__ {
+		gpiopin =       <&power_ctrl>,"gpios:4";
+		active_low =    <&power_ctrl>,"gpios:8";
+		input =         <&power_ctrl>,"input?";
+		export =        <&power_ctrl>,"export?";
+		timeout_ms =    <&power_ctrl>,"timeout-ms:0";
+		active_delay_ms = <&power_ctrl>,"active-delay-ms:0";
+		inactive_delay_ms = <&power_ctrl>,"inactive-delay-ms:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/gpio-shutdown-overlay.dts b/arch/arm/boot/dts/overlays/gpio-shutdown-overlay.dts
new file mode 100644
index 000000000000..da148064aedd
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/gpio-shutdown-overlay.dts
@@ -0,0 +1,86 @@
+// Definitions for gpio-poweroff module
+/dts-v1/;
+/plugin/;
+
+// This overlay sets up an input device that generates KEY_POWER events
+// when a given GPIO pin changes. It defaults to using GPIO3, which can
+// also be used to wake up (start) the Rpi again after shutdown.
+// Raspberry Pi 1 Model B rev 1 can be wake up only by GPIO1 pin, so for
+// these boards change default GPIO pin to 1 via gpio_pin parameter. Since
+// wakeup is active-low, this defaults to active-low with a pullup
+// enabled, but all of this can be changed using overlay parameters (but
+// note that GPIO3 has an external pullup on at least some boards).
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		// Configure the gpio pin controller
+		target = <&gpio>;
+		__overlay__ {
+			// Define a pinctrl state, that sets up the gpio
+			// as an input with a pullup enabled. This does
+			// not take effect by itself, only when referenced
+			// by a "pinctrl client", as is done below. See:
+			//   https://www.kernel.org/doc/Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt
+			//   https://www.kernel.org/doc/Documentation/devicetree/bindings/pinctrl/brcm,bcm2835-gpio.txt
+			pin_state: shutdown_button_pins@3 {
+				brcm,pins = <3>; // gpio number
+				brcm,function = <0>; // 0 = input, 1 = output
+				brcm,pull = <2>; // 0 = none, 1 = pull down, 2 = pull up
+			};
+		};
+	};
+	fragment@1 {
+		// Add a new device to the /soc devicetree node
+		target-path = "/soc";
+		__overlay__ {
+			shutdown_button: shutdown_button@3 {
+				// Let the gpio-keys driver handle this device. See:
+				// https://www.kernel.org/doc/Documentation/devicetree/bindings/input/gpio-keys.txt
+				compatible = "gpio-keys";
+
+				// Declare a single pinctrl state (referencing the one declared above) and name it
+				// default, so it is activated automatically.
+				pinctrl-names = "default";
+				pinctrl-0 = <&pin_state>;
+
+				// Enable this device
+				status = "okay";
+
+				// Define a single key, called "shutdown" that monitors the gpio and sends KEY_POWER
+				// (keycode 116, see
+				// https://github.com/torvalds/linux/blob/v4.12/include/uapi/linux/input-event-codes.h#L190)
+				button: shutdown {
+					label = "shutdown";
+					linux,code = <116>; // KEY_POWER
+					gpios = <&gpio 3 1>;
+					debounce-interval = <100>; // ms
+				};
+			};
+		};
+	};
+
+	// This defines parameters that can be specified when loading
+	// the overlay. Each foo = line specifies one parameter, named
+	// foo. The rest of the specification gives properties where the
+	// parameter value is inserted into (changing the values above
+	// or adding new ones).
+	__overrides__ {
+		// Allow overriding the GPIO number.
+		gpio_pin = <&button>,"gpios:4",
+			   <&shutdown_button>,"reg:0",
+			   <&pin_state>,"reg:0",
+		           <&pin_state>,"brcm,pins:0";
+
+		// Allow changing the internal pullup/down state. 0 = none, 1 = pulldown, 2 = pullup
+		// Note that GPIO3 and GPIO2 are the I2c pins and have an external pullup (at least
+		// on some boards). Same applies for GPIO1 on Raspberry Pi 1 Model B rev 1.
+		gpio_pull = <&pin_state>,"brcm,pull:0";
+
+		// Allow setting the active_low flag. 0 = active high, 1 = active low
+		active_low = <&button>,"gpios:8";
+		debounce = <&button>,"debounce-interval:0";
+	};
+
+};
diff --git a/arch/arm/boot/dts/overlays/hat_map.dts b/arch/arm/boot/dts/overlays/hat_map.dts
new file mode 100644
index 000000000000..0b5d902e85b8
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hat_map.dts
@@ -0,0 +1,124 @@
+/dts-v1/;
+
+/ {
+	hailo-8 {
+		product = "Raspberry Pi AI Hat, Model Hailo-8";
+		vendor = "Hailo Technologies";
+		overlay = "none,pciex1,pciex1_gen=3";
+	};
+
+	hailo-8l {
+		product = "Raspberry Pi AI Hat, Model Hailo-8L";
+		vendor = "Hailo Technologies";
+		overlay = "none,pciex1,pciex1_gen=3";
+	};
+
+	hifiberry-amp100-1 {
+		uuid = [ 5eb863b8 12f9 41ad 978f 4cee1b3eca62 ];
+		overlay = "hifiberry-amp100";
+	};
+
+	hifiberry-amp100-2 {
+		uuid = [ b1a57dbe 8b52 447f 939e 1baf72157d79 ];
+		overlay = "hifiberry-amp100";
+	};
+
+	hifiberry-amp4pro {
+		uuid = [ 3619722a c92d 4092 95bd 493a2903e933 ];
+		overlay = "hifiberry-amp4pro";
+	};
+
+	hifiberry-amp4 {
+		uuid = [ fcb6ec42 a182 419d a314 7eeae416f608 ];
+		overlay = "hifiberry-dacplus-std";
+	};
+
+	hifiberry-dac2proadc {
+		uuid = [ 30660215 dbb2 4c57 953f 099370b63e2e ];
+		overlay = "hifiberry-dacplusadcpro";
+	};
+
+	hifiberry-dac2hd {
+		uuid = [ 482ad277 5586 480c 88e7 85ae89c4e501 ];
+		overlay = "hifiberry-dacplushd";
+	};
+
+	hifiberry-dac2pro {
+		uuid = [ ebf9cfc4 6d77 4880 89fd 353690467dfc ];
+		overlay = "hifiberry-dacplus-pro";
+	};
+
+	hifiberry-dac8x {
+		uuid = [ f65985f9 5354 4457 ae3b 3da39ba2cf6d ];
+		overlay = "hifiberry-dac8x";
+	};
+
+	hifiberry-dacplus-amp2-1 {
+		uuid = [ 81cac43d 27c6 4a1e a0b2 c70b4e608ab6 ];
+		overlay = "hifiberry-dacplus-std";
+	};
+
+	hifiberry-dacplus-amp2-2 {
+		uuid = [ ef586afc 2efa 47a0 be2e 95a7d952fe98 ];
+		overlay = "hifiberry-dacplus-std";
+	};
+
+	hifiberry-digiplus-pro {
+		uuid = [ 2154f80b 0f92 45e4 96db c1643ec2b46b ];
+		overlay = "hifiberry-digi-pro";
+	};
+
+	hifiberry-dacplusadcpro {
+		uuid = [ 36e3d3da 1ed9 468b aea3 cd165f6820f0 ];
+		overlay = "hifiberry-dacplusadcpro";
+	};
+
+	hifiberry-digi2pro {
+		uuid = [ 5af941bb 4dcf 4eac 82a8 e36e84fcabef ];
+		overlay = "hifiberry-digi-pro";
+	};
+
+	hifiberry-digi2standard {
+		uuid = [ 7c980a0e 9d15 40af 9f40 bddfbd3aee8c ];
+		overlay = "hifiberry-digi";
+	};
+
+	hifiberry-dsp2x4 {
+		uuid = [ 8f287583 429d 4206 a751 862264bbda63 ];
+		overlay = "hifiberry-dacplus-dsp";
+	};
+
+	iqaudio-pi-codecplus {
+		uuid = [ dc1c9594 c1ab 4c6c acda a88dc59a3c5b ];
+		overlay = "iqaudio-codec";
+	};
+
+	iqaudio-pi-codeczero {
+		uuid = [ e15c739c 877d 4e29 ab36 4dc73c21127c ];
+		overlay = "iqaudio-codec";
+	};
+
+	pisound {
+		uuid = [ a7ee5d28 da03 41f5 bbd7 20438a4bec5d ];
+		overlay = "pisound";
+	};
+
+	recalbox-rgbdual {
+		uuid = [ 1c955808 681f 4bbc a2ef b7ea47cd388e ];
+		overlay = "recalboxrgbdual";
+	};
+
+	sensehat-v1 {
+		product = "Sense HAT";
+		vendor = "Raspberry Pi";
+		pver = < 0x0001 >;
+		overlay = "rpi-sense";
+	};
+
+	sensehat-v2 {
+		product = "Sense HAT";
+		vendor = "Raspberry Pi";
+		pver = < 0x0002 >;
+		overlay = "rpi-sense-v2";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/hd44780-i2c-lcd-overlay.dts b/arch/arm/boot/dts/overlays/hd44780-i2c-lcd-overlay.dts
new file mode 100644
index 000000000000..d5bfd762fe2e
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hd44780-i2c-lcd-overlay.dts
@@ -0,0 +1,59 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	i2c_frag: fragment@0 {
+		target = <&i2c_arm>;
+		__overlay__ {
+			status = "okay";
+
+			pcf857x: pcf857x@27 {
+				compatible = "nxp,pcf8574";
+				reg = <0x27>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			lcd_screen: auxdisplay {
+				compatible = "hit,hd44780";
+
+				data-gpios = <&pcf857x 4 0>,
+					     <&pcf857x 5 0>,
+					     <&pcf857x 6 0>,
+					     <&pcf857x 7 0>;
+				enable-gpios = <&pcf857x 2 0>;
+				rs-gpios = <&pcf857x 0 0>;
+				rw-gpios = <&pcf857x 1 0>;
+				backlight-gpios = <&pcf857x 3 0>;
+
+				display-width-chars = <16>;
+				display-height-chars = <2>;
+			};
+		};
+	};
+
+	__overrides__ {
+		pin_d4 = <&lcd_screen>,"data-gpios:4";
+		pin_d5 = <&lcd_screen>,"data-gpios:16";
+		pin_d6 = <&lcd_screen>,"data-gpios:28";
+		pin_d7 = <&lcd_screen>,"data-gpios:40";
+		pin_en = <&lcd_screen>,"enable-gpios:4";
+		pin_rs = <&lcd_screen>,"rs-gpios:4";
+		pin_rw = <&lcd_screen>,"rw-gpios:4";
+		pin_bl = <&lcd_screen>,"backlight-gpios:4";
+		display_height = <&lcd_screen>,"display-height-chars:0";
+		display_width = <&lcd_screen>,"display-width-chars:0";
+		addr = <&pcf857x>,"reg:0";
+		i2c-path = <&i2c_frag>, "target?=0",
+			   <&i2c_frag>, "target-path";
+	};
+
+};
diff --git a/arch/arm/boot/dts/overlays/hd44780-lcd-overlay.dts b/arch/arm/boot/dts/overlays/hd44780-lcd-overlay.dts
new file mode 100644
index 000000000000..ee726669ff51
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hd44780-lcd-overlay.dts
@@ -0,0 +1,46 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+    compatible = "brcm,bcm2835";
+
+    fragment@0 {
+        target-path = "/";
+        __overlay__ {
+            lcd_screen: auxdisplay {
+                compatible = "hit,hd44780";
+
+                data-gpios = <&gpio 6 0>,
+                             <&gpio 13 0>,
+                             <&gpio 19 0>,
+                             <&gpio 26 0>;
+                enable-gpios = <&gpio 21 0>;
+                rs-gpios = <&gpio 20 0>;
+
+                display-height-chars = <2>;
+                display-width-chars = <16>;
+            };
+
+        };
+    };
+
+    fragment@1 {
+       target = <&lcd_screen>;
+        __dormant__ {
+            backlight-gpios = <&gpio 12 0>;
+        };
+    };
+
+    __overrides__ {
+        pin_d4 = <&lcd_screen>,"data-gpios:4";
+        pin_d5 = <&lcd_screen>,"data-gpios:16";
+        pin_d6 = <&lcd_screen>,"data-gpios:28";
+        pin_d7 = <&lcd_screen>,"data-gpios:40";
+        pin_en = <&lcd_screen>,"enable-gpios:4";
+        pin_rs = <&lcd_screen>,"rs-gpios:4";
+        pin_bl = <0>,"+1", <&lcd_screen>,"backlight-gpios:4";
+        display_height = <&lcd_screen>,"display-height-chars:0";
+        display_width = <&lcd_screen>,"display-width-chars:0";
+    };
+
+};
diff --git a/arch/arm/boot/dts/overlays/hdmi-backlight-hwhack-gpio-overlay.dts b/arch/arm/boot/dts/overlays/hdmi-backlight-hwhack-gpio-overlay.dts
new file mode 100644
index 000000000000..50b9a2665c80
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hdmi-backlight-hwhack-gpio-overlay.dts
@@ -0,0 +1,47 @@
+/*
+ * Devicetree overlay for GPIO based backlight on/off capability.
+ *
+ * Use this if you have one of those HDMI displays whose backlight cannot be
+ * controlled via DPMS over HDMI and plan to do a little soldering to use an
+ * RPi gpio pin for on/off switching.
+ *
+ * See: https://www.waveshare.com/wiki/7inch_HDMI_LCD_(C)#Backlight_Control
+ *
+ */
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			hdmi_backlight_hwhack_gpio_pins: hdmi_backlight_hwhack_gpio_pins {
+				brcm,pins = <17>;
+				brcm,function = <1>; /* out */
+			};
+		};
+	};
+
+	fragment@2 {
+		target-path = "/";
+		__overlay__ {
+			hdmi_backlight_hwhack_gpio: hdmi_backlight_hwhack_gpio {
+				compatible = "gpio-backlight";
+
+				pinctrl-names = "default";
+				pinctrl-0 = <&hdmi_backlight_hwhack_gpio_pins>;
+
+				gpios = <&gpio 17 0>;
+				default-on;
+			};
+		};
+	};
+
+	__overrides__ {
+		gpio_pin   = <&hdmi_backlight_hwhack_gpio>,"gpios:4",
+		             <&hdmi_backlight_hwhack_gpio_pins>,"brcm,pins:0";
+		active_low = <&hdmi_backlight_hwhack_gpio>,"gpios:8";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/hifiberry-adc-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-adc-overlay.dts
new file mode 100644
index 000000000000..2658f3242556
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hifiberry-adc-overlay.dts
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-2.0
+// Definitions for HiFiBerry ADC, no onboard clocks
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			hb_adc: pcm186x@4a {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm1863";
+				reg = <0x4a>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		hifiberry_adc: __overlay__ {
+			compatible = "hifiberry,hifiberry-adc";
+			audio-codec = <&hb_adc>;
+			i2s-controller = <&i2s_clk_producer>;
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		leds_off = <&hifiberry_adc>,"hifiberry-adc,leds_off?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/hifiberry-adc8x-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-adc8x-overlay.dts
new file mode 100644
index 000000000000..e0432115dc39
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hifiberry-adc8x-overlay.dts
@@ -0,0 +1,50 @@
+// Definitions for HiFiBerry ADC8x
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			rp1_i2s0_adc8x: rp1_i2s0_adc8x {
+				function = "i2s0";
+				pins = "gpio18", "gpio19", "gpio20",
+				       "gpio22", "gpio24", "gpio26";
+				bias-disable;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&rp1_i2s0_adc8x>;
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target-path = "/";
+		__overlay__ {
+			dummy-codec {
+				#sound-dai-cells = <0>;
+				compatible = "snd-soc-dummy";
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "hifiberry,hifiberry-adc8x";
+			i2s-controller = <&i2s_clk_producer>;
+			status = "okay";
+		};
+	};
+
+};
diff --git a/arch/arm/boot/dts/overlays/hifiberry-amp-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-amp-overlay.dts
new file mode 100644
index 000000000000..667cd2601806
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hifiberry-amp-overlay.dts
@@ -0,0 +1,39 @@
+// Definitions for HiFiBerry Amp/Amp+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			tas5713@1b {
+				#sound-dai-cells = <0>;
+				compatible = "ti,tas5713";
+				reg = <0x1b>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "hifiberry,hifiberry-amp";
+			i2s-controller = <&i2s_clk_producer>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/hifiberry-amp100-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-amp100-overlay.dts
new file mode 100644
index 000000000000..b38e6631a572
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hifiberry-amp100-overlay.dts
@@ -0,0 +1,67 @@
+// Definitions for HiFiBerry AMP100
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			dacpro_osc: dacpro_osc {
+				compatible = "hifiberry,dacpro-clk";
+				#clock-cells = <0>;
+			};
+		};
+	};
+
+	frag1: fragment@1 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			pcm5122@4d {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5122";
+				reg = <0x4d>;
+				clocks = <&dacpro_osc>;
+				AVDD-supply = <&vdd_3v3_reg>;
+				DVDD-supply = <&vdd_3v3_reg>;
+				CPVDD-supply = <&vdd_3v3_reg>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&sound>;
+		hifiberry_dacplus: __overlay__ {
+			compatible = "hifiberry,hifiberry-dacplus";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+			mute-gpio = <&gpio 4 0>;
+			reset-gpio = <&gpio 17 0x11>;
+		};
+	};
+
+	__overrides__ {
+		24db_digital_gain =
+			<&hifiberry_dacplus>,"hifiberry,24db_digital_gain?";
+		slave = <&hifiberry_dacplus>,"hifiberry-dacplus,slave?",
+			<&frag1>,"target:0=",<&i2s_clk_producer>,
+			<&hifiberry_dacplus>,"i2s-controller:0=",<&i2s_clk_producer>;
+
+		leds_off = <&hifiberry_dacplus>,"hifiberry-dacplus,leds_off?";
+		mute_ext_ctl = <&hifiberry_dacplus>,"hifiberry-dacplus,mute_ext_ctl:0";
+		auto_mute = <&hifiberry_dacplus>,"hifiberry-dacplus,auto_mute?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/hifiberry-amp3-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-amp3-overlay.dts
new file mode 100644
index 000000000000..fc8f11b6294e
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hifiberry-amp3-overlay.dts
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for HiFiBerry's Amp3
+/dts-v1/;
+/plugin/;
+#include <dt-bindings/pinctrl/bcm2835.h>
+#include <dt-bindings/gpio/gpio.h>
+
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			hifiberry_amp3_pins: hifiberry_amp3_pins {
+				brcm,pins = <23 17>;
+				brcm,function = <0 1>;
+				brcm,pull = <2 1>;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			hifiberry_amp2: ma120x0p@20 {
+				#sound-dai-cells = <0>;
+				compatible = "ma,ma120x0p";
+				reg = <0x20>;
+				status = "okay";
+				pinctrl-names = "default";
+				pinctrl-0 = <&hifiberry_amp3_pins>;
+				error_gp-gpios = <&gpio 23 GPIO_ACTIVE_HIGH>;
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "hifiberry,hifiberry-amp3";
+			i2s-controller = <&i2s_clk_producer>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/hifiberry-amp4pro-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-amp4pro-overlay.dts
new file mode 100644
index 000000000000..6b211c2932dd
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hifiberry-amp4pro-overlay.dts
@@ -0,0 +1,63 @@
+// Definitions for HiFiBerry AMP4PRO
+/dts-v1/;
+/plugin/;
+#include <dt-bindings/gpio/gpio.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			dacpro_osc: dacpro_osc {
+				compatible = "hifiberry,dacpro-clk";
+				#clock-cells = <0>;
+			};
+		};
+	};
+
+	frag1: fragment@1 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			tas5756@4d {
+				#sound-dai-cells = <0>;
+				compatible = "ti,tas5756";
+				reg = <0x4d>;
+				clocks = <&dacpro_osc>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&sound>;
+		hifiberry_dacplus: __overlay__ {
+			compatible = "hifiberry,hifiberry-dacplus";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+			mute-gpio = <&gpio 4 GPIO_ACTIVE_LOW>;
+		};
+	};
+
+	__overrides__ {
+		24db_digital_gain =
+			<&hifiberry_dacplus>,"hifiberry-amp4,24db_digital_gain?";
+		leds_off = <&hifiberry_dacplus>,"hifiberry-amp4,leds_off?";
+		mute_ext_ctl = <&hifiberry_dacplus>,"hifiberry-amp4,mute_ext_ctl:0";
+		auto_mute = <&hifiberry_dacplus>,"hifiberry-amp4,auto_mute?";
+		slave = <&hifiberry_dacplus>,"hifiberry-dacplus,slave?",
+			<&frag1>,"target:0=",<&i2s_clk_producer>,
+			<&hifiberry_dacplus>,"i2s-controller:0=",<&i2s_clk_producer>;
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/hifiberry-dac-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-dac-overlay.dts
new file mode 100644
index 000000000000..efb0e18dbdc4
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hifiberry-dac-overlay.dts
@@ -0,0 +1,34 @@
+// Definitions for HiFiBerry DAC
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			pcm5102a-codec {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5102a";
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "hifiberry,hifiberry-dac";
+			i2s-controller = <&i2s_clk_producer>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/hifiberry-dac8x-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-dac8x-overlay.dts
new file mode 100644
index 000000000000..1f4bcfc2328e
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hifiberry-dac8x-overlay.dts
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0
+// Definitions for HiFiBerry DAC8x
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			rp1_i2s0_dac8x: rp1_i2s0_dac8x {
+				function = "i2s0";
+				pins = "gpio18", "gpio19", "gpio20",
+				       "gpio21", "gpio22", "gpio23",
+				       "gpio24", "gpio25", "gpio26",
+				       "gpio27";
+				bias-disable;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&rp1_i2s0_dac8x>;
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target-path = "/";
+		__overlay__ {
+			dummy-codec {
+				#sound-dai-cells = <0>;
+				compatible = "snd-soc-dummy";
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "hifiberry,hifiberry-dac8x";
+			i2s-controller = <&i2s_clk_producer>;
+			hasadc-gpio = <&gpio 5 GPIO_ACTIVE_LOW>;
+			status = "okay";
+		};
+	};
+
+};
diff --git a/arch/arm/boot/dts/overlays/hifiberry-dacplus-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-dacplus-overlay.dts
new file mode 100644
index 000000000000..0d0ab068112f
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hifiberry-dacplus-overlay.dts
@@ -0,0 +1,68 @@
+// Definitions for HiFiBerry DAC+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			dacpro_osc: dacpro_osc {
+				compatible = "hifiberry,dacpro-clk";
+				#clock-cells = <0>;
+			};
+		};
+	};
+
+	frag1: fragment@1 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			pcm5122@4d {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5122";
+				reg = <0x4d>;
+				clocks = <&dacpro_osc>;
+				AVDD-supply = <&vdd_3v3_reg>;
+				DVDD-supply = <&vdd_3v3_reg>;
+				CPVDD-supply = <&vdd_3v3_reg>;
+				status = "okay";
+			};
+			hpamp: hpamp@60 {
+				compatible = "ti,tpa6130a2";
+				reg = <0x60>;
+				status = "disabled";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&sound>;
+		hifiberry_dacplus: __overlay__ {
+			compatible = "hifiberry,hifiberry-dacplus";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		24db_digital_gain =
+			<&hifiberry_dacplus>,"hifiberry,24db_digital_gain?";
+		slave = <&hifiberry_dacplus>,"hifiberry-dacplus,slave?",
+			<&frag1>,"target:0=",<&i2s_clk_producer>,
+			<&hifiberry_dacplus>,"i2s-controller:0=",<&i2s_clk_producer>;
+
+		leds_off = <&hifiberry_dacplus>,"hifiberry-dacplus,leds_off?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/hifiberry-dacplus-pro-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-dacplus-pro-overlay.dts
new file mode 100644
index 000000000000..28b1c2f2f1a8
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hifiberry-dacplus-pro-overlay.dts
@@ -0,0 +1,64 @@
+// Definitions for HiFiBerry DAC+ PRO, with onboard clocks
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			dacpro_osc: dacpro_osc {
+				compatible = "hifiberry,dacpro-clk";
+				#clock-cells = <0>;
+			};
+		};
+	};
+
+	frag1: fragment@1 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			pcm5122@4d {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5122";
+				reg = <0x4d>;
+				clocks = <&dacpro_osc>;
+				AVDD-supply = <&vdd_3v3_reg>;
+				DVDD-supply = <&vdd_3v3_reg>;
+				CPVDD-supply = <&vdd_3v3_reg>;
+				status = "okay";
+			};
+			hpamp: hpamp@60 {
+				compatible = "ti,tpa6130a2";
+				reg = <0x60>;
+				status = "disabled";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&sound>;
+		hifiberry_dacplus: __overlay__ {
+			compatible = "hifiberry,hifiberry-dacplus";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		24db_digital_gain =
+			<&hifiberry_dacplus>,"hifiberry,24db_digital_gain?";
+		leds_off = <&hifiberry_dacplus>,"hifiberry-dacplus,leds_off?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/hifiberry-dacplus-std-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-dacplus-std-overlay.dts
new file mode 100644
index 000000000000..8872e3aa348d
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hifiberry-dacplus-std-overlay.dts
@@ -0,0 +1,65 @@
+// Definitions for HiFiBerry DAC+ Standard w/o onboard clocks
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			dacpro_osc: dacpro_osc {
+				compatible = "hifiberry,dacpro-clk";
+				#clock-cells = <0>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			pcm5122@4d {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5122";
+				reg = <0x4d>;
+				clocks = <&dacpro_osc>;
+				AVDD-supply = <&vdd_3v3_reg>;
+				DVDD-supply = <&vdd_3v3_reg>;
+				CPVDD-supply = <&vdd_3v3_reg>;
+				status = "okay";
+			};
+			hpamp: hpamp@60 {
+				compatible = "ti,tpa6130a2";
+				reg = <0x60>;
+				status = "disabled";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&sound>;
+		hifiberry_dacplus: __overlay__ {
+			compatible = "hifiberry,hifiberry-dacplus";
+			i2s-controller = <&i2s_clk_producer>;
+			hifiberry-dacplus,slave;
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		24db_digital_gain =
+			<&hifiberry_dacplus>,"hifiberry,24db_digital_gain?";
+		leds_off = <&hifiberry_dacplus>,"hifiberry-dacplus,leds_off?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/hifiberry-dacplusadc-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-dacplusadc-overlay.dts
new file mode 100644
index 000000000000..ea4c3572826f
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hifiberry-dacplusadc-overlay.dts
@@ -0,0 +1,72 @@
+// Definitions for HiFiBerry DAC+ADC, no onboard clocks
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			dacpro_osc: dacpro_osc {
+				compatible = "hifiberry,dacpro-clk";
+				#clock-cells = <0>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			pcm_codec: pcm5122@4d {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5122";
+				reg = <0x4d>;
+				clocks = <&dacpro_osc>;
+				AVDD-supply = <&vdd_3v3_reg>;
+				DVDD-supply = <&vdd_3v3_reg>;
+				CPVDD-supply = <&vdd_3v3_reg>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@3 {
+		target-path = "/";
+		__overlay__ {
+			dmic {
+				#sound-dai-cells = <0>;
+				compatible = "dmic-codec";
+				num-channels = <2>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&sound>;
+		hifiberry_dacplusadc: __overlay__ {
+			compatible = "hifiberry,hifiberry-dacplusadc";
+			i2s-controller = <&i2s_clk_producer>;
+			hifiberry-dacplusadc,slave;
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		24db_digital_gain =
+			<&hifiberry_dacplusadc>,"hifiberry,24db_digital_gain?";
+		leds_off = <&hifiberry_dacplusadc>,"hifiberry-dacplusadc,leds_off?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/hifiberry-dacplusadcpro-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-dacplusadcpro-overlay.dts
new file mode 100644
index 000000000000..a4268bd72477
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hifiberry-dacplusadcpro-overlay.dts
@@ -0,0 +1,72 @@
+// Definitions for HiFiBerry DAC+ADC PRO
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			dacpro_osc: dacpro_osc {
+				compatible = "hifiberry,dacpro-clk";
+				#clock-cells = <0>;
+			};
+		};
+	};
+
+	frag1: fragment@1 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			hb_dac: pcm5122@4d {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5122";
+				reg = <0x4d>;
+				clocks = <&dacpro_osc>;
+				status = "okay";
+			};
+			hb_adc: pcm186x@4a {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm1863";
+				reg = <0x4a>;
+				clocks = <&dacpro_osc>;
+				status = "okay";
+			};
+			hpamp: hpamp@60 {
+				compatible = "ti,tpa6130a2";
+				reg = <0x60>;
+				status = "disabled";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&sound>;
+		hifiberry_dacplusadcpro: __overlay__ {
+			compatible = "hifiberry,hifiberry-dacplusadcpro";
+			audio-codec = <&hb_dac &hb_adc>;
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		24db_digital_gain =
+			<&hifiberry_dacplusadcpro>,"hifiberry-dacplusadcpro,24db_digital_gain?";
+		slave = <&hifiberry_dacplusadcpro>,"hifiberry-dacplusadcpro,slave?",
+			<&frag1>,"target:0=",<&i2s_clk_producer>,
+			<&hifiberry_dacplusadcpro>,"i2s-controller:0=",<&i2s_clk_producer>;
+		leds_off = <&hifiberry_dacplusadcpro>,"hifiberry-dacplusadcpro,leds_off?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/hifiberry-dacplusdsp-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-dacplusdsp-overlay.dts
new file mode 100644
index 000000000000..e916485f737e
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hifiberry-dacplusdsp-overlay.dts
@@ -0,0 +1,34 @@
+// Definitions for hifiberry DAC+DSP soundcard overlay
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			dacplusdsp-codec {
+				#sound-dai-cells = <0>;
+				compatible = "hifiberry,dacplusdsp";
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "hifiberrydacplusdsp,hifiberrydacplusdsp-soundcard";
+			i2s-controller = <&i2s_clk_producer>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/hifiberry-dacplushd-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-dacplushd-overlay.dts
new file mode 100644
index 000000000000..1856ac19793b
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hifiberry-dacplushd-overlay.dts
@@ -0,0 +1,94 @@
+// Definitions for HiFiBerry DAC+ HD
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			pcm1792a@4c {
+				compatible = "ti,pcm1792a";
+				#sound-dai-cells = <0>;
+				#clock-cells = <0>;
+				reg = <0x4c>;
+				status = "okay";
+			};
+			pll: pll@62 {
+				compatible = "hifiberry,dachd-clk";
+				#clock-cells = <0>;
+				reg = <0x62>;
+				status = "okay";
+				common_pll_regs = [
+					02 53 03 00 07 20 0F 00
+					10 0D 11 1D 12 0D 13 8C
+					14 8C 15 8C 16 8C 17 8C
+					18 2A 1C 00 1D 0F 1F 00
+					2A 00 2C 00 2F 00 30 00
+					31 00 32 00 34 00 37 00
+					38 00 39 00 3A 00 3B 01
+					3E 00 3F 00 40 00 41 00
+					5A 00 5B 00 95 00 96 00
+					97 00 98 00 99 00 9A 00
+					9B 00 A2 00 A3 00 A4 00
+					B7 92 ];
+				192k_pll_regs = [
+					1A 0C 1B 35 1E F0 20 09
+					21 50 2B 02 2D 10 2E 40
+					33 01 35 22 36 80 3C 22
+					3D 46 ];
+				96k_pll_regs = [
+					1A 0C 1B 35 1E F0 20 09
+					21 50 2B 02 2D 10 2E 40
+					33 01 35 47 36 00 3C 32
+					3D 46 ];
+				48k_pll_regs = [
+					1A 0C 1B 35 1E F0 20 09
+					21 50 2B 02 2D 10 2E 40
+					33 01 35 90 36 00 3C 42
+					3D 46 ];
+				176k4_pll_regs = [
+					1A 3D 1B 09 1E F3 20 13
+					21 75 2B 04 2D 11 2E E0
+					33 02 35 25 36 C0 3C 22
+					3D 7A ];
+				88k2_pll_regs = [
+					1A 3D 1B 09 1E F3 20 13
+					21 75 2B 04 2D 11 2E E0
+					33 01 35 4D 36 80 3C 32
+					3D 7A ];
+				44k1_pll_regs = [
+					1A 3D 1B 09 1E F3 20 13
+					21 75 2B 04 2D 11 2E E0
+					33 01 35 9D 36 00 3C 42
+					3D 7A ];
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "hifiberry,hifiberry-dacplushd";
+			i2s-controller = <&i2s_clk_consumer>;
+			clocks = <&pll 0>;
+			reset-gpio = <&gpio 16 GPIO_ACTIVE_LOW>;
+			status = "okay";
+		};
+	};
+
+};
diff --git a/arch/arm/boot/dts/overlays/hifiberry-digi-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-digi-overlay.dts
new file mode 100644
index 000000000000..eb68f117a92a
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hifiberry-digi-overlay.dts
@@ -0,0 +1,41 @@
+// Definitions for HiFiBerry Digi
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			wm8804@3b {
+				#sound-dai-cells = <0>;
+				compatible = "wlf,wm8804";
+				reg = <0x3b>;
+				PVDD-supply = <&vdd_3v3_reg>;
+				DVDD-supply = <&vdd_3v3_reg>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "hifiberry,hifiberry-digi";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/hifiberry-digi-pro-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-digi-pro-overlay.dts
new file mode 100644
index 000000000000..18d16276e120
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hifiberry-digi-pro-overlay.dts
@@ -0,0 +1,43 @@
+// Definitions for HiFiBerry Digi Pro
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			wm8804@3b {
+				#sound-dai-cells = <0>;
+				compatible = "wlf,wm8804";
+				reg = <0x3b>;
+				PVDD-supply = <&vdd_3v3_reg>;
+				DVDD-supply = <&vdd_3v3_reg>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "hifiberry,hifiberry-digi";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+			clock44-gpio = <&gpio 5 0>;
+			clock48-gpio = <&gpio 6 0>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/hifiberry-studio-dac8x-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-studio-dac8x-overlay.dts
new file mode 100644
index 000000000000..b19b7d359ffd
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hifiberry-studio-dac8x-overlay.dts
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0
+// Definitions for HiFiBerry Studio DAC8x soundcard
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&i2c1>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			rp1_i2s0_dac8x: rp1_i2s0_dac8x {
+				function = "i2s0";
+				pins = "gpio18", "gpio19", "gpio20",
+				       "gpio21", "gpio22", "gpio23",
+				       "gpio24", "gpio25", "gpio26",
+				       "gpio27";
+				bias-disable;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&rp1_i2s0_dac8x>;
+			status = "okay";
+		};
+	};
+
+	fragment@3 {
+		target-path = "/";
+		__overlay__ {
+			dummy-codec {
+				#sound-dai-cells = <0>;
+				compatible = "snd-soc-dummy";
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "hifiberry,hifiberry-studio-dac8x";
+			i2s-controller = <&i2s_clk_producer>;
+			status = "okay";
+		};
+	};
+
+};
diff --git a/arch/arm/boot/dts/overlays/hifiberry-studio-dac8x-pro-overlay.dts b/arch/arm/boot/dts/overlays/hifiberry-studio-dac8x-pro-overlay.dts
new file mode 100644
index 000000000000..fd4f0a3485ba
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hifiberry-studio-dac8x-pro-overlay.dts
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0
+// Definitions for HiFiBerry Studio DAC8x PRO soundcard
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&i2c1>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			rp1_i2s1_dac8x: rp1_i2s1_dac8x {
+				function = "i2s1";
+				pins = "gpio18", "gpio19", "gpio20",
+				       "gpio21", "gpio22", "gpio23",
+				       "gpio24", "gpio25", "gpio26",
+				       "gpio27";
+				bias-disable;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&rp1_i2s1_dac8x>;
+			status = "okay";
+		};
+	};
+
+	fragment@3 {
+		target-path = "/";
+		__overlay__ {
+			dummy-codec {
+				#sound-dai-cells = <0>;
+				compatible = "snd-soc-dummy";
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "hifiberry,hifiberry-studio-dac8x";
+			i2s-controller = <&i2s_clk_consumer>;
+			clk-provider;
+			status = "okay";
+		};
+	};
+
+};
diff --git a/arch/arm/boot/dts/overlays/highperi-overlay.dts b/arch/arm/boot/dts/overlays/highperi-overlay.dts
new file mode 100644
index 000000000000..46cb76c2d34f
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/highperi-overlay.dts
@@ -0,0 +1,63 @@
+/*
+ * highperi.dts
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target = <&soc>;
+		#address-cells = <2>;
+		#size-cells = <1>;
+
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <1>;
+			ranges = <0x7c000000  0x4 0x7c000000  0x04000000>,
+				 <0x40000000  0x4 0xc0000000  0x00800000>;
+		};
+	};
+
+	fragment@1 {
+		target = <&scb>;
+		#address-cells = <2>;
+		#size-cells = <1>;
+
+		__overlay__ {
+			#address-cells = <2>;
+			#size-cells = <2>;
+			ranges = <0x0 0x7c000000  0x4 0x7c000000  0x0 0x04000000>,
+				 <0x0 0x40000000  0x4 0xc0000000  0x0 0x00800000>,
+				 <0x6 0x00000000  0x6 0x00000000  0x0 0x40000000>;
+			dma-ranges = <0x0 0x00000000  0x0 0x00000000  0x2 0x00000000>;
+		};
+	};
+
+	fragment@2 {
+		target = <&v3dbus>;
+		#address-cells = <2>;
+		#size-cells = <1>;
+
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <2>;
+			ranges = <0x7c500000  0x4 0x7c500000  0x0 0x03300000>,
+				 <0x40000000  0x4 0xc0000000  0x0 0x00800000>;
+		};
+	};
+
+	fragment@3 {
+		target = <&emmc2bus>;
+		#address-cells = <2>;
+		#size-cells = <1>;
+
+		__overlay__ {
+			#address-cells = <2>;
+			#size-cells = <1>;
+			ranges = <0x0 0x7e000000  0x4 0x7e000000  0x01800000>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/hy28a-overlay.dts b/arch/arm/boot/dts/overlays/hy28a-overlay.dts
new file mode 100644
index 000000000000..d0d52ebd9bd5
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hy28a-overlay.dts
@@ -0,0 +1,93 @@
+/*
+ * Device Tree overlay for HY28A display
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&gpio>;
+		__overlay__ {
+			hy28a_pins: hy28a_pins {
+				brcm,pins = <17 25 18>;
+				brcm,function = <0 1 1>; /* in out out */
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			hy28a: hy28a@0{
+				compatible = "ilitek,ili9320";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&hy28a_pins>;
+
+				spi-max-frequency = <32000000>;
+				spi-cpol;
+				spi-cpha;
+				rotate = <270>;
+				bgr;
+				fps = <50>;
+				buswidth = <8>;
+				startbyte = <0x70>;
+				reset-gpios = <&gpio 25 1>;
+				led-gpios = <&gpio 18 1>;
+				debug = <0>;
+			};
+
+			hy28a_ts: hy28a-ts@1 {
+				compatible = "ti,ads7846";
+				reg = <1>;
+
+				spi-max-frequency = <2000000>;
+				interrupts = <17 2>; /* high-to-low edge triggered */
+				interrupt-parent = <&gpio>;
+				pendown-gpio = <&gpio 17 1>;
+				ti,x-plate-ohms = /bits/ 16 <100>;
+				ti,pressure-max = /bits/ 16 <255>;
+			};
+		};
+	};
+	__overrides__ {
+		speed =		<&hy28a>,"spi-max-frequency:0";
+		rotate =	<&hy28a>,"rotate:0";
+		fps =		<&hy28a>,"fps:0";
+		debug =		<&hy28a>,"debug:0";
+		xohms =		<&hy28a_ts>,"ti,x-plate-ohms;0";
+		resetgpio =	<&hy28a>,"reset-gpios:4",
+				<&hy28a_pins>, "brcm,pins:4";
+		ledgpio =	<&hy28a>,"led-gpios:4",
+				<&hy28a_pins>, "brcm,pins:8";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/hy28b-2017-overlay.dts b/arch/arm/boot/dts/overlays/hy28b-2017-overlay.dts
new file mode 100644
index 000000000000..9df33c5d95bb
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hy28b-2017-overlay.dts
@@ -0,0 +1,152 @@
+/*
+ * Device Tree overlay for HY28b display shield by Texy.
+ * Modified for 2017 version with ILI9325 D chip
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&gpio>;
+		__overlay__ {
+			hy28b_pins: hy28b_pins {
+				brcm,pins = <17 25 18>;
+				brcm,function = <0 1 1>; /* in out out */
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			hy28b: hy28b@0{
+				compatible = "ilitek,ili9325";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&hy28b_pins>;
+
+				spi-max-frequency = <48000000>;
+				spi-cpol;
+				spi-cpha;
+				rotate = <270>;
+				bgr;
+				fps = <50>;
+				buswidth = <8>;
+				startbyte = <0x70>;
+				reset-gpios = <&gpio 25 1>;
+				led-gpios = <&gpio 18 1>;
+
+				init = <0x10000e5 0x78F0
+					0x1000001 0x0100
+					0x1000002 0x0700
+				        0x1000003 0x1030
+					0x1000004 0x0000
+					0x1000008 0x0207
+					0x1000009 0x0000
+				        0x100000a 0x0000
+					0x100000c 0x0000
+					0x100000d 0x0000
+					0x100000f 0x0000
+				        0x1000010 0x0000
+					0x1000011 0x0007
+					0x1000012 0x0000
+					0x1000013 0x0000
+					0x1000007 0x0001
+				        0x2000032
+				        0x2000032
+				        0x2000032
+				        0x2000032
+					0x1000010 0x1090
+					0x1000011 0x0227
+				        0x2000032
+					0x1000012 0x001f
+				        0x2000032
+				        0x1000013 0x1500
+					0x1000029 0x0027
+					0x100002b 0x000d
+				        0x2000032
+				        0x1000020 0x0000
+					0x1000021 0x0000
+				        0x2000032
+					0x1000030 0x0000
+					0x1000031 0x0707
+					0x1000032 0x0307
+					0x1000035 0x0200
+					0x1000036 0x0008
+					0x1000037 0x0004
+					0x1000038 0x0000
+					0x1000039 0x0707
+					0x100003c 0x0002
+					0x100003d 0x1d04
+					0x1000050 0x0000
+				        0x1000051 0x00ef
+					0x1000052 0x0000
+					0x1000053 0x013f
+					0x1000060 0xa700
+				        0x1000061 0x0001
+					0x100006a 0x0000
+					0x1000080 0x0000
+					0x1000081 0x0000
+				        0x1000082 0x0000
+					0x1000083 0x0000
+					0x1000084 0x0000
+					0x1000085 0x0000
+				        0x1000090 0x0010
+					0x1000092 0x0600
+					0x1000007 0x0133>;
+				debug = <0>;
+			};
+
+			hy28b_ts: hy28b-ts@1 {
+				compatible = "ti,ads7846";
+				reg = <1>;
+
+				spi-max-frequency = <2000000>;
+				interrupts = <17 2>; /* high-to-low edge triggered */
+				interrupt-parent = <&gpio>;
+				pendown-gpio = <&gpio 17 1>;
+				ti,x-plate-ohms = /bits/ 16 <100>;
+				ti,pressure-max = /bits/ 16 <255>;
+			};
+		};
+	};
+	__overrides__ {
+		speed = 	<&hy28b>,"spi-max-frequency:0";
+		rotate = 	<&hy28b>,"rotate:0";
+		fps = 		<&hy28b>,"fps:0";
+		debug = 	<&hy28b>,"debug:0";
+		xohms =		<&hy28b_ts>,"ti,x-plate-ohms;0";
+		resetgpio =	<&hy28b>,"reset-gpios:4",
+				<&hy28b_pins>, "brcm,pins:4";
+		ledgpio =	<&hy28b>,"led-gpios:4",
+				<&hy28b_pins>, "brcm,pins:8";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/hy28b-overlay.dts b/arch/arm/boot/dts/overlays/hy28b-overlay.dts
new file mode 100644
index 000000000000..421bde94a4a0
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/hy28b-overlay.dts
@@ -0,0 +1,148 @@
+/*
+ * Device Tree overlay for HY28b display shield by Texy
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&gpio>;
+		__overlay__ {
+			hy28b_pins: hy28b_pins {
+				brcm,pins = <17 25 18>;
+				brcm,function = <0 1 1>; /* in out out */
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			hy28b: hy28b@0{
+				compatible = "ilitek,ili9325";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&hy28b_pins>;
+
+				spi-max-frequency = <48000000>;
+				spi-cpol;
+				spi-cpha;
+				rotate = <270>;
+				bgr;
+				fps = <50>;
+				buswidth = <8>;
+				startbyte = <0x70>;
+				reset-gpios = <&gpio 25 1>;
+				led-gpios = <&gpio 18 1>;
+
+				gamma = "04 1F 4 7 7 0 7 7 6 0\n0F 00 1 7 4 0 0 0 6 7";
+
+				init = <0x10000e7 0x0010
+					0x1000000 0x0001
+					0x1000001 0x0100
+					0x1000002 0x0700
+				        0x1000003 0x1030
+					0x1000004 0x0000
+					0x1000008 0x0207
+					0x1000009 0x0000
+				        0x100000a 0x0000
+					0x100000c 0x0001
+					0x100000d 0x0000
+					0x100000f 0x0000
+				        0x1000010 0x0000
+					0x1000011 0x0007
+					0x1000012 0x0000
+					0x1000013 0x0000
+				        0x2000032
+					0x1000010 0x1590
+					0x1000011 0x0227
+				        0x2000032
+					0x1000012 0x009c
+				        0x2000032
+				        0x1000013 0x1900
+					0x1000029 0x0023
+					0x100002b 0x000e
+				        0x2000032
+				        0x1000020 0x0000
+					0x1000021 0x0000
+				        0x2000032
+					0x1000050 0x0000
+				        0x1000051 0x00ef
+					0x1000052 0x0000
+					0x1000053 0x013f
+					0x1000060 0xa700
+				        0x1000061 0x0001
+					0x100006a 0x0000
+					0x1000080 0x0000
+					0x1000081 0x0000
+				        0x1000082 0x0000
+					0x1000083 0x0000
+					0x1000084 0x0000
+					0x1000085 0x0000
+				        0x1000090 0x0010
+					0x1000092 0x0000
+					0x1000093 0x0003
+					0x1000095 0x0110
+				        0x1000097 0x0000
+					0x1000098 0x0000
+					0x1000007 0x0133
+					0x1000020 0x0000
+				        0x1000021 0x0000
+				        0x2000064>;
+				debug = <0>;
+			};
+
+			hy28b_ts: hy28b-ts@1 {
+				compatible = "ti,ads7846";
+				reg = <1>;
+
+				spi-max-frequency = <2000000>;
+				interrupts = <17 2>; /* high-to-low edge triggered */
+				interrupt-parent = <&gpio>;
+				pendown-gpio = <&gpio 17 1>;
+				ti,x-plate-ohms = /bits/ 16 <100>;
+				ti,pressure-max = /bits/ 16 <255>;
+			};
+		};
+	};
+	__overrides__ {
+		speed = 	<&hy28b>,"spi-max-frequency:0";
+		rotate = 	<&hy28b>,"rotate:0";
+		fps = 		<&hy28b>,"fps:0";
+		debug = 	<&hy28b>,"debug:0";
+		xohms =		<&hy28b_ts>,"ti,x-plate-ohms;0";
+		resetgpio =	<&hy28b>,"reset-gpios:4",
+				<&hy28b_pins>, "brcm,pins:4";
+		ledgpio =	<&hy28b>,"led-gpios:4",
+				<&hy28b_pins>, "brcm,pins:8";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i-sabre-q2m-overlay.dts b/arch/arm/boot/dts/overlays/i-sabre-q2m-overlay.dts
new file mode 100644
index 000000000000..6db52955a8f8
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i-sabre-q2m-overlay.dts
@@ -0,0 +1,39 @@
+// Definitions for I-Sabre Q2M
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&sound>;
+		frag0: __overlay__ {
+			compatible = "audiophonics,i-sabre-q2m";
+			i2s-controller = <&i2s_clk_producer>;
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			i-sabre-codec@48 {
+				#sound-dai-cells = <0>;
+				compatible = "audiophonics,i-sabre-codec";
+				reg = <0x48>;
+				status = "okay";
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2c-bcm2708-overlay.dts b/arch/arm/boot/dts/overlays/i2c-bcm2708-overlay.dts
new file mode 100644
index 000000000000..8204b6b3aef8
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2c-bcm2708-overlay.dts
@@ -0,0 +1,13 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c_arm>;
+		__overlay__ {
+			compatible = "brcm,bcm2708-i2c";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2c-buses.dtsi b/arch/arm/boot/dts/overlays/i2c-buses.dtsi
new file mode 100644
index 000000000000..5c7699a8ac66
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2c-buses.dtsi
@@ -0,0 +1,67 @@
+// Common i2c buses, and dtparams to select them
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	busfrag: fragment@100 {
+		target = <&i2c_arm>;
+		i2cbus: __overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@101 {
+		target = <&i2c0if>;
+#ifdef ENABLE_I2C0_MUX
+		__overlay__ {
+#else
+		__dormant__ {
+#endif
+			status = "okay";
+		};
+	};
+
+	fragment@102 {
+		target = <&i2c0mux>;
+#ifdef ENABLE_I2C0_MUX
+		__overlay__ {
+#else
+		__dormant__ {
+#endif
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		i2c0 = <&busfrag>,"target:0=",<&i2c0>,
+			      <&busfrag>, "target-path?=0",
+			      <0>,"+101+102";
+		i2c_csi_dsi = <&busfrag>,"target:0=",<&i2c_csi_dsi>,
+			      <&busfrag>, "target-path?=0",
+			      <0>,"+101+102";
+		i2c_csi_dsi0 = <&busfrag>, "target:0=",<&i2c_csi_dsi0>,
+			       <&busfrag>, "target-path?=0",
+			       <0>,"+101+102";
+		i2c1 = <&busfrag>,"target:0=",<&i2c1>,
+		       <&busfrag>, "target-path?=0",
+		       <0>,"-101-102";
+		i2c2 = <&busfrag>, "target?=0",
+		       <&busfrag>, "target-path=i2c2",
+		       <0>,"-101-102";
+		i2c3 = <&busfrag>, "target?=0",
+		       <&busfrag>, "target-path=i2c3",
+		       <0>,"-101-102";
+		i2c4 = <&busfrag>, "target?=0",
+		       <&busfrag>, "target-path=i2c4",
+		       <0>,"-101-102";
+		i2c5 = <&busfrag>, "target?=0",
+		       <&busfrag>, "target-path=i2c5",
+		       <0>,"-101-102";
+		i2c6 = <&busfrag>, "target?=0",
+		       <&busfrag>, "target-path=i2c6",
+		       <0>,"-101-102";
+		i2c-path = <&busfrag>, "target?=0",
+			   <&busfrag>, "target-path",
+			   <0>,"-101-102";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2c-fan-overlay.dts b/arch/arm/boot/dts/overlays/i2c-fan-overlay.dts
new file mode 100644
index 000000000000..70e204cb81e6
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2c-fan-overlay.dts
@@ -0,0 +1,77 @@
+// Definitions for I2C based sensors using the Industrial IO or HWMON interface.
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/thermal/thermal.h>
+
+#include "i2c-buses.dtsi"
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			emc2301: emc2301@2f {
+				compatible = "microchip,emc2301";
+				reg = <0x2f>;
+				#cooling-cells = <0x02>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&cpu_thermal>;
+		__overlay__ {
+			polling-delay = <2000>; /* milliseconds */
+		};
+	};
+
+	fragment@2 {
+		target = <&thermal_trips>;
+		__overlay__ {
+			fanmid0: fanmid0 {
+				temperature = <50000>;
+				hysteresis = <2000>;
+				type = "active";
+			};
+			fanmax0: fanmax0 {
+				temperature = <75000>;
+				hysteresis = <2000>;
+				type = "active";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&cooling_maps>;
+		__overlay__ {
+			map0: map0 {
+				trip = <&fanmid0>;
+				cooling-device = <&emc2301 2 6>;
+			};
+			map1: map1 {
+				trip = <&fanmax0>;
+				cooling-device = <&emc2301 7 THERMAL_NO_LIMIT>;
+			};
+		};
+	};
+
+	__overrides__ {
+		addr =		<&emc2301>,"reg:0";
+		minpwm =	<&emc2301>,"emc2305,pwm-min.0";
+		maxpwm =	<&emc2301>,"emc2305,pwm-max.0";
+		midtemp =	<&fanmid0>,"temperature:0";
+		midtemp_hyst =	<&fanmid0>,"hysteresis:0";
+		maxtemp =	<&fanmax0>,"temperature:0";
+		maxtemp_hyst =	<&fanmax0>,"hysteresis:0";
+
+		emc2301 =	<0>,"+0",
+				<&map0>,"cooling-device:0=",<&emc2301>,
+				<&map1>,"cooling-device:0=",<&emc2301>;
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2c-gpio-overlay.dts b/arch/arm/boot/dts/overlays/i2c-gpio-overlay.dts
new file mode 100644
index 000000000000..63231b5d7c0c
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2c-gpio-overlay.dts
@@ -0,0 +1,47 @@
+// Overlay for i2c_gpio bitbanging host bus.
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+
+		__overlay__ {
+			i2c_gpio: i2c@0 {
+				reg = <0xffffffff>;
+				compatible = "i2c-gpio";
+				gpios = <&gpio 23 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN) /* sda */
+					 &gpio 24 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN) /* scl */
+					>;
+				i2c-gpio,delay-us = <2>;        /* ~100 kHz */
+				#address-cells = <1>;
+				#size-cells = <0>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/aliases";
+		__overlay__ {
+			i2c_gpio = "/i2c@0";
+		};
+	};
+
+	fragment@2 {
+		target-path = "/__symbols__";
+		__overlay__ {
+			i2c_gpio = "/i2c@0";
+		};
+	};
+
+	__overrides__ {
+		i2c_gpio_sda = <&i2c_gpio>,"gpios:4";
+		i2c_gpio_scl = <&i2c_gpio>,"gpios:16";
+		i2c_gpio_delay_us = <&i2c_gpio>,"i2c-gpio,delay-us:0";
+		bus = <&i2c_gpio>, "reg:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2c-mux-overlay.dts b/arch/arm/boot/dts/overlays/i2c-mux-overlay.dts
new file mode 100644
index 000000000000..be79831ea8d2
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2c-mux-overlay.dts
@@ -0,0 +1,149 @@
+// Umbrella I2C Mux overlay
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/mux/mux.h>
+
+#include "i2c-buses.dtsi"
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			pca9542: mux@70 {
+				compatible = "nxp,pca9542";
+				reg = <0x70>;
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				i2c@0 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0>;
+				};
+				i2c@1 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <1>;
+				};
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			pca9545: mux@70 {
+				compatible = "nxp,pca9545";
+				reg = <0x70>;
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				i2c@0 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0>;
+				};
+				i2c@1 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <1>;
+				};
+				i2c@2 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <2>;
+				};
+				i2c@3 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <3>;
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			pca9548: mux@70 {
+				compatible = "nxp,pca9548";
+				reg = <0x70>;
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				i2c@0 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <0>;
+				};
+				i2c@1 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <1>;
+				};
+				i2c@2 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <2>;
+				};
+				i2c@3 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <3>;
+				};
+				i2c@4 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <4>;
+				};
+				i2c@5 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <5>;
+				};
+				i2c@6 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <6>;
+				};
+				i2c@7 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					reg = <7>;
+				};
+			};
+		};
+	};
+
+	__overrides__ {
+		pca9542 = <0>, "+0";
+		pca9545 = <0>, "+1";
+		pca9548 = <0>, "+2";
+
+		addr =  <&pca9542>,"reg:0",
+			<&pca9545>,"reg:0",
+			<&pca9548>,"reg:0";
+
+		base =  <&pca9542>,"base-nr:0",
+			<&pca9545>,"base-nr:0",
+			<&pca9548>,"base-nr:0";
+
+		disconnect_on_idle =
+			<&pca9542>,"idle-state:0=", <MUX_IDLE_DISCONNECT>,
+			<&pca9545>,"idle-state:0=", <MUX_IDLE_DISCONNECT>,
+			<&pca9548>,"idle-state:0=", <MUX_IDLE_DISCONNECT>;
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2c-pwm-pca9685a-overlay.dts b/arch/arm/boot/dts/overlays/i2c-pwm-pca9685a-overlay.dts
new file mode 100644
index 000000000000..4941e78a3d93
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2c-pwm-pca9685a-overlay.dts
@@ -0,0 +1,28 @@
+// Definitions for NXP PCA9685A I2C PWM controller on ARM I2C bus.
+/dts-v1/;
+/plugin/;
+
+#include "i2c-buses.dtsi"
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2cbus>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			pca: pca@40 {
+				compatible = "nxp,pca9685-pwm";
+				#pwm-cells = <2>;
+				reg = <0x40>;
+				status = "okay";
+			};
+		};
+	};
+
+	__overrides__ {
+		addr = <&pca>,"reg:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2c-rtc-common.dtsi b/arch/arm/boot/dts/overlays/i2c-rtc-common.dtsi
new file mode 100644
index 000000000000..70d286211359
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2c-rtc-common.dtsi
@@ -0,0 +1,382 @@
+// Definitions for several I2C based Real Time Clocks
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			abx80x: abx80x@69 {
+				compatible = "abracon,abx80x";
+				reg = <0x69>;
+				abracon,tc-diode = "standard";
+				abracon,tc-resistor = <0>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			ds1307: ds1307@68 {
+				compatible = "dallas,ds1307";
+				reg = <0x68>;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			ds1339: ds1339@68 {
+				compatible = "dallas,ds1339";
+				trickle-resistor-ohms = <0>;
+				reg = <0x68>;
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			ds3231: ds3231@68 {
+				compatible = "maxim,ds3231";
+				reg = <0x68>;
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mcp7940x: mcp7940x@6f {
+				compatible = "microchip,mcp7940x";
+				reg = <0x6f>;
+			};
+		};
+	};
+
+	fragment@5 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mcp7941x: mcp7941x@6f {
+				compatible = "microchip,mcp7941x";
+				reg = <0x6f>;
+			};
+		};
+	};
+
+	fragment@6 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			pcf2127@51 {
+				compatible = "nxp,pcf2127";
+				reg = <0x51>;
+			};
+		};
+	};
+
+	fragment@7 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			pcf8523: pcf8523@68 {
+				compatible = "nxp,pcf8523";
+				reg = <0x68>;
+			};
+		};
+	};
+
+	fragment@8 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			pcf8563: pcf8563@51 {
+				compatible = "nxp,pcf8563";
+				reg = <0x51>;
+			};
+		};
+	};
+
+	fragment@9 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			m41t62: m41t62@68 {
+				compatible = "st,m41t62";
+				reg = <0x68>;
+			};
+		};
+	};
+
+	fragment@10 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			rv3028: rv3028@52 {
+				compatible = "microcrystal,rv3028";
+				reg = <0x52>;
+			};
+		};
+	};
+
+	fragment@11 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			pcf2129@51 {
+				compatible = "nxp,pcf2129";
+				reg = <0x51>;
+			};
+		};
+	};
+
+	fragment@12 {
+		target = <&i2cbus>;
+	       __dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			pcf85363@51 {
+				compatible = "nxp,pcf85363";
+				reg = <0x51>;
+			};
+		};
+	};
+
+	fragment@13 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			rv1805: rv1805@69 {
+				compatible = "microcrystal,rv1805";
+				reg = <0x69>;
+				abracon,tc-diode = "standard";
+				abracon,tc-resistor = <0>;
+			};
+		};
+	};
+
+	fragment@14 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sd3078: sd3078@32 {
+				compatible = "whwave,sd3078";
+				reg = <0x32>;
+			};
+		};
+	};
+
+	fragment@15 {
+		target = <&i2cbus>;
+	       __dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			pcf85063@51 {
+				compatible = "nxp,pcf85063";
+				reg = <0x51>;
+			};
+		};
+	};
+
+	fragment@16 {
+		target = <&i2cbus>;
+	       __dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			pcf85063a@51 {
+				compatible = "nxp,pcf85063a";
+				reg = <0x51>;
+			};
+		};
+	};
+
+	fragment@17 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			ds1340: ds1340@68 {
+				compatible = "dallas,ds1340";
+				trickle-resistor-ohms = <0>;
+				reg = <0x68>;
+			};
+		};
+	};
+
+	fragment@18 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			s35390a: s35390a@30 {
+				compatible = "sii,s35390a";
+				reg = <0x30>;
+			};
+		};
+	};
+
+	fragment@19 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			bq32000: bq32000@68 {
+				compatible = "ti,bq32000";
+				trickle-resistor-ohms = <0>;
+				reg = <0x68>;
+			};
+		};
+	};
+
+	fragment@20 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			rv8803: rv8803@32 {
+				compatible = "microcrystal,rv8803";
+				reg = <0x32>;
+			};
+		};
+	};
+
+	fragment@21 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			rv3032: rv3032@51 {
+				compatible = "microcrystal,rv3032";
+				reg = <0x51>;
+			};
+		};
+	};
+
+	fragment@22 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			pcf2131@53 {
+				compatible = "nxp,pcf2131";
+				reg = <0x53>;
+			};
+		};
+	};
+
+	fragment@23 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			m41t80: m41t80@68 {
+				compatible = "st,m41t80";
+				reg = <0x68>;
+			};
+		};
+	};
+
+	__overrides__ {
+		abx80x = <0>,"+0";
+		ds1307 = <0>,"+1";
+		ds1339 = <0>,"+2";
+		ds1340 = <0>,"+17";
+		ds3231 = <0>,"+3";
+		mcp7940x = <0>,"+4";
+		mcp7941x = <0>,"+5";
+		pcf2127 = <0>,"+6";
+		pcf8523 = <0>,"+7";
+		pcf8563 = <0>,"+8";
+		m41t62 = <0>,"+9";
+		rv3028 = <0>,"+10";
+		pcf2129 = <0>,"+11";
+		pcf85363 = <0>,"+12";
+		rv1805 = <0>,"+13";
+		sd3078 = <0>,"+14";
+		pcf85063 = <0>,"+15";
+		pcf85063a = <0>,"+16";
+		s35390a = <0>,"+18";
+		bq32000 = <0>,"+19";
+		rv8803 = <0>,"+20";
+		rv3032 = <0>,"+21";
+		pcf2131 = <0>,"+22";
+		m41t80 = <0>,"+23";
+
+		addr = <&abx80x>, "reg:0",
+		       <&ds1307>, "reg:0",
+		       <&ds1339>, "reg:0",
+		       <&ds3231>, "reg:0",
+		       <&mcp7940x>, "reg:0",
+		       <&mcp7941x>, "reg:0",
+		       <&pcf8523>, "reg:0",
+		       <&pcf8563>, "reg:0",
+		       <&m41t62>, "reg:0",
+		       <&rv1805>, "reg:0",
+		       <&s35390a>, "reg:0";
+		trickle-diode-disable = <&bq32000>,"trickle-diode-disable?";
+		trickle-diode-type = <&abx80x>,"abracon,tc-diode",
+				     <&rv1805>,"abracon,tc-diode";
+		trickle-resistor-ohms = <&ds1339>,"trickle-resistor-ohms:0",
+					<&ds1340>,"trickle-resistor-ohms:0",
+					<&abx80x>,"abracon,tc-resistor:0",
+					<&rv3028>,"trickle-resistor-ohms:0",
+					<&rv3032>,"trickle-resistor-ohms:0",
+					<&rv1805>,"abracon,tc-resistor:0",
+					<&bq32000>,"trickle-resistor-ohms:0";
+		trickle-voltage-mv = <&rv3032>,"trickle-voltage-millivolts:0";
+		backup-switchover-mode = <&rv3028>,"backup-switchover-mode:0";
+		wakeup-source = <&ds1339>,"wakeup-source?",
+				<&ds3231>,"wakeup-source?",
+				<&mcp7940x>,"wakeup-source?",
+				<&mcp7941x>,"wakeup-source?",
+				<&m41t62>,"wakeup-source?",
+				<&m41t80>,"wakeup-source?",
+				<&pcf8563>,"wakeup-source?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2c-rtc-gpio-overlay.dts b/arch/arm/boot/dts/overlays/i2c-rtc-gpio-overlay.dts
new file mode 100644
index 000000000000..c83480c1c327
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2c-rtc-gpio-overlay.dts
@@ -0,0 +1,31 @@
+// Definitions for several I2C based Real Time Clocks
+// Available through i2c-gpio
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+#include "i2c-rtc-common.dtsi"
+
+/ {
+	fragment@100 {
+		target-path = "/";
+		__overlay__ {
+			i2cbus: i2c-gpio-rtc@0 {
+				compatible = "i2c-gpio";
+				gpios = <&gpio 23 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN) /* sda */
+					 &gpio 24 (GPIO_ACTIVE_HIGH|GPIO_OPEN_DRAIN) /* scl */
+					>;
+				i2c-gpio,delay-us = <2>;        /* ~100 kHz */
+				#address-cells = <1>;
+				#size-cells = <0>;
+			};
+		};
+	};
+
+	__overrides__ {
+		i2c_gpio_sda = <&i2cbus>,"gpios:4";
+		i2c_gpio_scl = <&i2cbus>,"gpios:16";
+		i2c_gpio_delay_us = <&i2cbus>,"i2c-gpio,delay-us:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2c-rtc-overlay.dts b/arch/arm/boot/dts/overlays/i2c-rtc-overlay.dts
new file mode 100644
index 000000000000..e003244d894c
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2c-rtc-overlay.dts
@@ -0,0 +1,6 @@
+// Definitions for several I2C based Real Time Clocks
+/dts-v1/;
+/plugin/;
+
+#include "i2c-rtc-common.dtsi"
+#include "i2c-buses.dtsi"
diff --git a/arch/arm/boot/dts/overlays/i2c-sensor-common.dtsi b/arch/arm/boot/dts/overlays/i2c-sensor-common.dtsi
new file mode 100755
index 000000000000..5d52b16c8d47
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2c-sensor-common.dtsi
@@ -0,0 +1,956 @@
+// Definitions for I2C based sensors using the Industrial IO or HWMON interface.
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			bme280: bme280@76 {
+				compatible = "bosch,bme280";
+				reg = <0x76>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			bmp085: bmp085@77 {
+				compatible = "bosch,bmp085";
+				reg = <0x77>;
+				default-oversampling = <3>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			bmp180: bmp180@77 {
+				compatible = "bosch,bmp180";
+				reg = <0x77>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			bmp280: bmp280@76 {
+				compatible = "bosch,bmp280";
+				reg = <0x76>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			htu21: htu21@40 {
+				compatible = "meas,htu21";
+				reg = <0x40>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@5 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			lm75: lm75@4f {
+				compatible = "national,lm75";
+				reg = <0x4f>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@6 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			si7020: si7020@40 {
+				compatible = "silabs,si7020";
+				reg = <0x40>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@7 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			tmp102: tmp102@48 {
+				compatible = "ti,tmp102";
+				reg = <0x48>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@8 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			hdc100x: hdc100x@40 {
+				compatible = "ti,hdc1000";
+				reg = <0x40>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@9 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			tsl4531: tsl4531@29 {
+				compatible = "amstaos,tsl4531";
+				reg = <0x29>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@10 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			veml6070: veml6070@38 {
+				compatible = "vishay,veml6070";
+				reg = <0x38>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@11 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			sht3x: sht3x@44 {
+				compatible = "sensirion,sht3x";
+				reg = <0x44>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@12 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			ds1621: ds1621@48 {
+				compatible = "dallas,ds1621";
+				reg = <0x48>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@13 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			max17040: max17040@36 {
+				compatible = "maxim,max17040";
+				reg = <0x36>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@14 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			bme680: bme680@76 {
+				compatible = "bosch,bme680";
+				reg = <0x76>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@15 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			sps30: sps30@69 {
+				compatible = "sensirion,sps30";
+				reg = <0x69>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@16 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			sgp30: sgp30@58 {
+				compatible = "sensirion,sgp30";
+				reg = <0x58>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@17 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			ccs811: ccs811@5b {
+				compatible = "ams,ccs811";
+				reg = <0x5b>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@18 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			bh1750: bh1750@23 {
+				compatible = "rohm,bh1750";
+				reg = <0x23>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@19 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			max30102: max30102@57 {
+				compatible = "maxim,max30102";
+				reg = <0x57>;
+				maxim,red-led-current-microamp = <7000>;
+				maxim,ir-led-current-microamp  = <7000>;
+				interrupt-parent = <&gpio>;
+				interrupts = <4 IRQ_TYPE_EDGE_FALLING>;
+				pinctrl-0 = <&int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	fragment@20 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			aht10: aht10@38 {
+				compatible = "aosong,aht10";
+				reg = <0x38>;
+			};
+		};
+	};
+
+	fragment@21 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			mcp980x: mcp980x@18 {
+				compatible = "maxim,mcp980x";
+				reg = <0x18>;
+			};
+		};
+	};
+
+	fragment@22 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			jc42: jc42@18 {
+				compatible = "jedec,jc-42.4-temp";
+				reg = <0x18>;
+			};
+		};
+	};
+
+	fragment@23 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			ms5637: ms5637@76 {
+				compatible = "meas,ms5637";
+				reg = <0x76>;
+			};
+		};
+	};
+
+	fragment@24 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			ms5803: ms5803@76 {
+				compatible = "meas,ms5803";
+				reg = <0x76>;
+			};
+		};
+	};
+
+	fragment@25 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			ms5805: ms5805@76 {
+				compatible = "meas,ms5805";
+				reg = <0x76>;
+			};
+		};
+	};
+
+	fragment@26 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			ms5837: ms5837@76 {
+				compatible = "meas,ms5837";
+				reg = <0x76>;
+			};
+		};
+	};
+
+	fragment@27 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			ms8607: ms8607@76 {
+				compatible = "meas,ms8607-temppressure";
+				reg = <0x76>;
+			};
+		};
+	};
+
+	fragment@28 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+			clock-frequency = <400000>;
+
+			mpu6050: mpu6050@68 {
+				compatible = "invensense,mpu6050";
+				reg = <0x68>;
+				interrupt-parent = <&gpio>;
+				interrupts = <4 IRQ_TYPE_EDGE_FALLING>;
+				pinctrl-0 = <&int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	fragment@29 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+			clock-frequency = <400000>;
+
+			mpu9250: mpu9250@68 {
+				compatible = "invensense,mpu9250";
+				reg = <0x68>;
+				interrupt-parent = <&gpio>;
+				interrupts = <4 IRQ_TYPE_EDGE_FALLING>;
+				pinctrl-0 = <&int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	fragment@30 {
+		target = <&bno055>;
+		__dormant__ {
+			reset-gpios = <&gpio 5 GPIO_ACTIVE_LOW>;
+		};
+	};
+
+	fragment@31 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			bno055: bno055@29 {
+				compatible = "bosch,bno055";
+				reg = <0x29>;
+			};
+		};
+	};
+
+	fragment@32 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			sht4x: sht4x@44 {
+				compatible = "sensirion,sht4x";
+				reg = <0x44>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@33 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			bmp380: bmp380@76 {
+				compatible = "bosch,bmp380";
+				reg = <0x76>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@34 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			adt7410: adt7410@48 {
+				compatible = "adi,adt7410", "adi,adt7420";
+				reg = <0x48>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@35 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			ina238: ina238@48 {
+				compatible = "ti,ina238";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				reg = <0x40>;
+				/* uOhms, uint32_t */
+				shunt-resistor = <1000>;
+				/* 1 or 4, (±40.96 mV or ±163.84 mV) */
+				ti,shunt-gain = <1>;
+			};
+		};
+	};
+
+	fragment@36 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			shtc3: shtc3@70 {
+				compatible = "sensirion,shtc3";
+				reg = <0x70>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@37 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			hts221: hts221@5f {
+				compatible = "st,hts221-humid", "st,hts221";
+				reg = <0x5f>;
+				interrupt-parent = <&gpio>;
+				interrupts = <4 IRQ_TYPE_EDGE_RISING>;
+				pinctrl-0 = <&int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	fragment@38 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			veml6075: veml6075@10 {
+				compatible = "vishay,veml6075";
+				reg = <0x10>;
+			};
+		};
+	};
+
+	fragment@39 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			hdc3020: hdc3020@44 {
+				compatible = "ti,hdc3020";
+				reg = <0x44>;
+				interrupt-parent = <&gpio>;
+				interrupts = <4 IRQ_TYPE_EDGE_RISING>;
+				pinctrl-0 = <&int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	fragment@40 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			as73211: as73211@74 {
+				compatible = "ams,as73211";
+				reg = <0x74>;
+				interrupt-parent = <&gpio>;
+				interrupts = <4 IRQ_TYPE_EDGE_RISING>;
+				pinctrl-0 = <&int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	fragment@41 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			as7331: as7331@74 {
+				compatible = "ams,as7331";
+				reg = <0x74>;
+				interrupt-parent = <&gpio>;
+				interrupts = <4 IRQ_TYPE_EDGE_RISING>;
+				pinctrl-0 = <&int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	fragment@42 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			adxl345: adxl345@53 {
+				compatible = "adi,adxl345";
+				reg = <0x53>;
+				interrupt-parent = <&gpio>;
+				interrupts = <4 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-0 = <&int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	fragment@43 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			aht20: aht20@38 {
+				compatible = "aosong,aht20";
+				reg = <0x38>;
+			};
+		};
+	};
+
+	fragment@44 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			tcs3472: tcs3472@29 {
+				compatible = "amstaos,tcs3472";
+				reg = <0x29>;
+				interrupt-parent = <&gpio>;
+				interrupts = <4 IRQ_TYPE_EDGE_RISING>;
+				pinctrl-0 = <&int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	fragment@45 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			veml6040: veml6040@10 {
+				compatible = "vishay,veml6040";
+				reg = <0x10>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@46 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			ads7828: ads7828@48 {
+				compatible = "ti,ads7828";
+				reg = <0x48>;
+				label = "ads7828";
+			};
+		};
+	};
+
+	fragment@47 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			ads7830: ads7830@48 {
+				compatible = "ti,ads7830";
+				reg = <0x48>;
+				label = "ads7830";
+			};
+		};
+	};
+
+	fragment@48 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+			adxl355: adxl355@1d {
+				compatible = "adi,adxl355";
+				reg = <0x1d>;
+			};
+		};
+	};
+
+	fragment@49 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			bmp58x: bmp58x@46 {
+				compatible = "bosch,bmp580";
+				reg = <0x46>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@50 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			veml6030: veml6030@48 {
+				compatible = "vishay,veml6030";
+				reg = <0x48>;
+				interrupt-parent = <&gpio>;
+				interrupts = <4 IRQ_TYPE_LEVEL_LOW>;
+				pinctrl-0 = <&int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	fragment@51 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			tmp117: tmp117@48 {
+				compatible = "ti,tmp117";
+				reg = <0x48>;
+				vcc-supply = <&reg_3v3>;
+			};
+		};
+	};
+
+	fragment@52 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			scd4x: scd4x@62 {
+				compatible = "sensirion,scd40";
+				reg = <0x62>;
+			};
+		};
+	};
+
+	fragment@56 {
+		target = <&i2cbus>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			ad799x: ad799x@20 {
+				compatible = "adi,ad799x"; // To be overridden
+				reg = <0x20>;
+			};
+		};
+	};
+
+	fragment@98 {
+		target-path = "/";
+		__dormant__ {
+			reg_3v3: i2c-sensor-reg-3v3 {
+				compatible = "regulator-fixed";
+				regulator-name = "i2c_sensor_reg_3v3";
+				regulator-min-microvolt = <3300000>;
+				regulator-max-microvolt = <3300000>;
+				regulator-always-on;
+			};
+		};
+	};
+
+	fragment@99 {
+		target = <&gpio>;
+		__dormant__ {
+			int_pins: int_pins@4 {
+					brcm,pins = <4>;
+					brcm,function = <0>; /* in */
+					brcm,pull = <0>; /* none */
+			};
+		};
+	};
+
+	__overrides__ {
+		bme280 = <0>,"+0";
+		bmp085 = <0>,"+1";
+		bmp180 = <0>,"+2";
+		bmp280 = <0>,"+3";
+		bmp380 = <0>,"+33";
+		htu21 = <0>,"+4";
+		lm75 = <0>,"+5";
+		lm75addr = <&lm75>,"reg:0";
+		si7020 = <0>,"+6";
+		tmp102 = <0>,"+7";
+		hdc100x = <0>,"+8";
+		tsl4531 = <0>,"+9";
+		veml6070 = <0>,"+10";
+		sht3x = <0>,"+11";
+		ds1621 = <0>,"+12";
+		max17040 = <0>,"+13";
+		bme680 = <0>,"+14";
+		sps30 = <0>,"+15";
+		sgp30 = <0>,"+16";
+		ccs811 = <0>, "+17";
+		bh1750 = <0>, "+18";
+		max30102 = <0>,"+19+99";
+		aht10 = <0>,"+20";
+		mcp980x = <0>,"+21";
+		jc42 = <0>,"+22";
+		ms5637 = <0>,"+23";
+		ms5803 = <0>,"+24";
+		ms5805 = <0>,"+25";
+		ms5837 = <0>,"+26";
+		ms8607 = <0>,"+27";
+		mpu6050 = <0>,"+28+99";
+		mpu9250 = <0>,"+29+99";
+		bno055 = <0>,"+31";
+		sht4x = <0>,"+32";
+		adt7410 = <0>,"+34";
+		ina238 = <0>,"+35";
+		shtc3 = <0>,"+36";
+		hts221 = <0>,"+37+99";
+		veml6075 = <0>,"+38";
+		hdc3020 = <0>,"+39+99";
+		as73211 = <0>,"+40+99";
+		as7331 = <0>,"+41+99";
+		adxl345 = <0>,"+42+99";
+		aht20 = <0>,"+43";
+		tcs3472 = <0>,"+44+99";
+		veml6040 = <0>,"+45";
+		ads7828 = <0>,"+46";
+		ads7830 = <0>,"+47";
+		adxl355 = <0>,"+48";
+		bmp58x = <0>,"+49";
+		veml6030 = <0>,"+50+99";
+		tmp117 = <0>,"+51+98";
+		scd4x = <0>,"+52";
+		ad7991 = <0>,"+56", <&ad799x>,"compatible=adi,ad7991", <&ad799x>,"reg:0=0x28";
+		ad7992 = <0>,"+56", <&ad799x>,"compatible=adi,ad7992";
+		ad7993 = <0>,"+56", <&ad799x>,"compatible=adi,ad7993";
+		ad7994 = <0>,"+56", <&ad799x>,"compatible=adi,ad7994";
+		ad7995 = <0>,"+56", <&ad799x>,"compatible=adi,ad7995", <&ad799x>,"reg:0=0x28";
+		ad7997 = <0>,"+56", <&ad799x>,"compatible=adi,ad7997";
+		ad7998 = <0>,"+56", <&ad799x>,"compatible=adi,ad7998";
+		ad7999 = <0>,"+56", <&ad799x>,"compatible=adi,ad7999", <&ad799x>,"reg:0=0x29";
+
+		addr =	<&bme280>,"reg:0", <&bmp280>,"reg:0", <&tmp102>,"reg:0",
+			<&lm75>,"reg:0", <&hdc100x>,"reg:0", <&sht3x>,"reg:0",
+			<&ds1621>,"reg:0", <&bme680>,"reg:0", <&ccs811>,"reg:0",
+			<&bh1750>,"reg:0", <&mcp980x>,"reg:0", <&jc42>,"reg:0",
+			<&ms5637>,"reg:0", <&ms5803>,"reg:0", <&ms5805>,"reg:0",
+			<&ms5837>,"reg:0", <&ms8607>,"reg:0",
+			<&mpu6050>,"reg:0", <&mpu9250>,"reg:0",
+			<&bno055>,"reg:0", <&sht4x>,"reg:0",
+			<&bmp380>,"reg:0", <&adt7410>,"reg:0", <&ina238>,"reg:0",
+			<&hdc3020>,"reg:0", <&as73211>,"reg:0",
+			<&as7331>,"reg:0", <&adxl345>,"reg:0",
+			<&ads7828>,"reg:0", <&ads7830>,"reg:0",
+			<&adxl355>,"reg:0", <&bmp58x>,"reg:0",
+			<&veml6030>,"reg:0", <&tmp117>,"reg:0",
+			<&ad799x>,"reg:0";
+		int_pin = <&int_pins>, "brcm,pins:0",
+			<&int_pins>, "reg:0",
+			<&max30102>, "interrupts:0",
+			<&mpu6050>, "interrupts:0",
+			<&mpu9250>, "interrupts:0",
+			<&hts221>, "interrupts:0",
+			<&hdc3020>, "interrupts:0",
+			<&as73211>, "interrupts:0",
+			<&as7331>, "interrupts:0",
+			<&adxl345>, "interrupts:0",
+			<&tcs3472>, "interrupts:0",
+			<&veml6030>, "interrupts:0";
+		label = <&ads7828>, "label",
+			<&ads7830>, "label";
+		no_timeout = <&jc42>, "smbus-timeout-disable?";
+		reset_pin = <&bno055>,"reset-gpios:4", <0>,"+30";
+		shunt_resistor = <&ina238>,"shunt-resistor:0";
+		gain = <&ina238>,"ti,shunt-gain:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2c-sensor-overlay.dts b/arch/arm/boot/dts/overlays/i2c-sensor-overlay.dts
new file mode 100755
index 000000000000..29522e666908
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2c-sensor-overlay.dts
@@ -0,0 +1,6 @@
+// Definitions for I2C based sensors using the Industrial IO or HWMON interface.
+/dts-v1/;
+/plugin/;
+
+#include "i2c-sensor-common.dtsi"
+#include "i2c-buses.dtsi"
diff --git a/arch/arm/boot/dts/overlays/i2c0-overlay.dts b/arch/arm/boot/dts/overlays/i2c0-overlay.dts
new file mode 100644
index 000000000000..46bf1bf2dc5c
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2c0-overlay.dts
@@ -0,0 +1,83 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+			pinctrl-names = "default";
+			pinctrl-0 = <&i2c0_pins>;
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c0_pins>;
+		pins1: __overlay__ {
+			brcm,pins = <0 1>;
+			brcm,function = <4>; /* alt0 */
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0_pins>;
+		pins2: __dormant__ {
+			brcm,pins = <28 29>;
+			brcm,function = <4>; /* alt0 */
+		};
+	};
+
+	fragment@3 {
+		target = <&i2c0_pins>;
+		pins3: __dormant__ {
+			brcm,pins = <44 45>;
+			brcm,function = <5>; /* alt1 */
+		};
+	};
+
+	fragment@4 {
+		target = <&i2c0_pins>;
+		pins4: __dormant__ {
+			brcm,pins = <46 47>;
+			brcm,function = <4>; /* alt0 */
+		};
+	};
+
+	fragment@5 {
+		target = <&i2c0>;
+		__dormant__ {
+			compatible = "brcm,bcm2708-i2c";
+		};
+	};
+
+	fragment@6 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@7 {
+		target-path = "/aliases";
+		__overlay__ {
+			i2c0 = "/soc/i2c@7e205000";
+		};
+	};
+
+	fragment@8 {
+		target-path = "/__symbols__";
+		__overlay__ {
+			i2c0 = "/soc/i2c@7e205000";
+		};
+	};
+
+	__overrides__ {
+		pins_0_1   = <0>,"+1-2-3-4";
+		pins_28_29 = <0>,"-1+2-3-4";
+		pins_44_45 = <0>,"-1-2+3-4";
+		pins_46_47 = <0>,"-1-2-3+4";
+		combine = <0>, "!5";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2c0-pi5-overlay.dts b/arch/arm/boot/dts/overlays/i2c0-pi5-overlay.dts
new file mode 100644
index 000000000000..152794822552
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2c0-pi5-overlay.dts
@@ -0,0 +1,34 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&i2c0>;
+		frag0: __overlay__ {
+			status = "okay";
+			clock-frequency = <100000>;
+		};
+	};
+
+	fragment@1 {
+		target = <&frag0>;
+		__overlay__ {
+			pinctrl-0 = <&rp1_i2c0_0_1>;
+		};
+	};
+
+	fragment@2 {
+		target = <&frag0>;
+		__dormant__ {
+			pinctrl-0 = <&rp1_i2c0_8_9>;
+		};
+	};
+
+	__overrides__ {
+		pins_0_1 = <0>,"+1-2";
+		pins_8_9 = <0>,"-1+2";
+		baudrate = <&frag0>, "clock-frequency:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2c1-overlay.dts b/arch/arm/boot/dts/overlays/i2c1-overlay.dts
new file mode 100644
index 000000000000..addaed73e665
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2c1-overlay.dts
@@ -0,0 +1,44 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c1>;
+		__overlay__ {
+			status = "okay";
+			pinctrl-names = "default";
+			pinctrl-0 = <&i2c1_pins>;
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1_pins>;
+		pins1: __overlay__ {
+			brcm,pins = <2 3>;
+			brcm,function = <4>; /* alt 0 */
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c1_pins>;
+		pins2: __dormant__ {
+			brcm,pins = <44 45>;
+			brcm,function = <6>; /* alt 2 */
+		};
+	};
+
+	fragment@3 {
+		target = <&i2c1>;
+		__dormant__ {
+			compatible = "brcm,bcm2708-i2c";
+		};
+	};
+
+	__overrides__ {
+		pins_2_3   = <0>,"=1!2";
+		pins_44_45 = <0>,"!1=2";
+		combine = <0>, "!3";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2c1-pi5-overlay.dts b/arch/arm/boot/dts/overlays/i2c1-pi5-overlay.dts
new file mode 100644
index 000000000000..719966ceb59a
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2c1-pi5-overlay.dts
@@ -0,0 +1,34 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&i2c1>;
+		frag0: __overlay__ {
+			status = "okay";
+			clock-frequency = <100000>;
+		};
+	};
+
+	fragment@1 {
+		target = <&frag0>;
+		__overlay__ {
+			pinctrl-0 = <&rp1_i2c1_2_3>;
+		};
+	};
+
+	fragment@2 {
+		target = <&frag0>;
+		__dormant__ {
+			pinctrl-0 = <&rp1_i2c1_10_11>;
+		};
+	};
+
+	__overrides__ {
+		pins_2_3 = <0>,"+1-2";
+		pins_10_11 = <0>,"-1+2";
+		baudrate = <&frag0>, "clock-frequency:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2c2-pi5-overlay.dts b/arch/arm/boot/dts/overlays/i2c2-pi5-overlay.dts
new file mode 100644
index 000000000000..324d344052b8
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2c2-pi5-overlay.dts
@@ -0,0 +1,21 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&i2c2>;
+		frag0: __overlay__ {
+			status = "okay";
+			clock-frequency = <100000>;
+			pinctrl-0 = <&rp1_i2c2_4_5>;
+		};
+	};
+
+	__overrides__ {
+		pins_4_5 = <&frag0>,"pinctrl-0:0=", <&rp1_i2c2_4_5>;
+		pins_12_13 = <&frag0>,"pinctrl-0:0=", <&rp1_i2c2_12_13>;
+		baudrate = <&frag0>, "clock-frequency:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2c3-overlay.dts b/arch/arm/boot/dts/overlays/i2c3-overlay.dts
new file mode 100644
index 000000000000..663d4f060ee8
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2c3-overlay.dts
@@ -0,0 +1,34 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target = <&i2c3>;
+		frag0: __overlay__ {
+			status = "okay";
+			clock-frequency = <100000>;
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c3_pins>;
+		__dormant__ {
+			brcm,pins = <2 3>;
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c3_pins>;
+		__overlay__ {
+			brcm,pins = <4 5>;
+		};
+	};
+
+	__overrides__ {
+		pins_2_3 = <0>,"=1!2";
+		pins_4_5 = <0>,"!1=2";
+		baudrate = <&frag0>, "clock-frequency:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2c3-pi5-overlay.dts b/arch/arm/boot/dts/overlays/i2c3-pi5-overlay.dts
new file mode 100644
index 000000000000..cbd1f9ff650d
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2c3-pi5-overlay.dts
@@ -0,0 +1,22 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&i2c3>;
+		frag0: __overlay__ {
+			status = "okay";
+			clock-frequency = <100000>;
+			pinctrl-0 = <&rp1_i2c3_6_7>;
+		};
+	};
+
+	__overrides__ {
+		pins_6_7 = <&frag0>,"pinctrl-0:0=", <&rp1_i2c3_6_7>;
+		pins_14_15 = <&frag0>,"pinctrl-0:0=", <&rp1_i2c3_14_15>;
+		pins_22_23 = <&frag0>,"pinctrl-0:0=", <&rp1_i2c3_22_23>;
+		baudrate = <&frag0>, "clock-frequency:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2c4-overlay.dts b/arch/arm/boot/dts/overlays/i2c4-overlay.dts
new file mode 100644
index 000000000000..495de00f7aa1
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2c4-overlay.dts
@@ -0,0 +1,34 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target = <&i2c4>;
+		frag0: __overlay__ {
+			status = "okay";
+			clock-frequency = <100000>;
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c4_pins>;
+		__dormant__ {
+			brcm,pins = <6 7>;
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c4_pins>;
+		__overlay__ {
+			brcm,pins = <8 9>;
+		};
+	};
+
+	__overrides__ {
+		pins_6_7 = <0>,"=1!2";
+		pins_8_9 = <0>,"!1=2";
+		baudrate = <&frag0>, "clock-frequency:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2c5-overlay.dts b/arch/arm/boot/dts/overlays/i2c5-overlay.dts
new file mode 100644
index 000000000000..d498ebc72de6
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2c5-overlay.dts
@@ -0,0 +1,34 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target = <&i2c5>;
+		frag0: __overlay__ {
+			status = "okay";
+			clock-frequency = <100000>;
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c5_pins>;
+		__dormant__ {
+			brcm,pins = <10 11>;
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c5_pins>;
+		__overlay__ {
+			brcm,pins = <12 13>;
+		};
+	};
+
+	__overrides__ {
+		pins_10_11 = <0>,"=1!2";
+		pins_12_13 = <0>,"!1=2";
+		baudrate = <&frag0>, "clock-frequency:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2c6-overlay.dts b/arch/arm/boot/dts/overlays/i2c6-overlay.dts
new file mode 100644
index 000000000000..4d26178a73ca
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2c6-overlay.dts
@@ -0,0 +1,34 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target = <&i2c6>;
+		frag0: __overlay__ {
+			status = "okay";
+			clock-frequency = <100000>;
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c6_pins>;
+		__dormant__ {
+			brcm,pins = <0 1>;
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c6_pins>;
+		__overlay__ {
+			brcm,pins = <22 23>;
+		};
+	};
+
+	__overrides__ {
+		pins_0_1 = <0>,"=1!2";
+		pins_22_23 = <0>,"!1=2";
+		baudrate = <&frag0>, "clock-frequency:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2s-dac-overlay.dts b/arch/arm/boot/dts/overlays/i2s-dac-overlay.dts
new file mode 100644
index 000000000000..1d8874a18860
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2s-dac-overlay.dts
@@ -0,0 +1,34 @@
+// Definitions for RPi DAC
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			pcm1794a-codec {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm1794a";
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "rpi,rpi-dac";
+			i2s-controller = <&i2s_clk_producer>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2s-gpio28-31-overlay.dts b/arch/arm/boot/dts/overlays/i2s-gpio28-31-overlay.dts
new file mode 100644
index 000000000000..cf43094c6ff4
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2s-gpio28-31-overlay.dts
@@ -0,0 +1,18 @@
+/*
+ * Device tree overlay to move i2s to gpio 28 to 31 on CM
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_pins>;
+		__overlay__ {
+			brcm,pins = <28 29 30 31>;
+			brcm,function = <6>; /* alt2 */
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/i2s-master-dac-overlay.dts b/arch/arm/boot/dts/overlays/i2s-master-dac-overlay.dts
new file mode 100644
index 000000000000..8b46067858d7
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/i2s-master-dac-overlay.dts
@@ -0,0 +1,50 @@
+// Definitions for a generic I2S DAC that acts as clock master on the bus.
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			codec_bare: codec_bare {
+				compatible = "linux,spdif-dit";
+				#sound-dai-cells = <0>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "simple-audio-card";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+
+			simple-audio-card,name = "i2s-master-dac";
+			simple-audio-card,format = "i2s";
+
+			simple-audio-card,bitclock-master = <&snd_codec>;
+			simple-audio-card,frame-master = <&snd_codec>;
+
+			simple-audio-card,cpu {
+				sound-dai = <&i2s_clk_consumer>;
+				dai-tdm-slot-num = <2>;
+				dai-tdm-slot-width = <32>;
+			};
+
+			snd_codec: simple-audio-card,codec {
+				sound-dai = <&codec_bare>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/ilitek-ts-i2c-overlay.dts b/arch/arm/boot/dts/overlays/ilitek-ts-i2c-overlay.dts
new file mode 100644
index 000000000000..673d5294d7ee
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ilitek-ts-i2c-overlay.dts
@@ -0,0 +1,62 @@
+// Device tree overlay for I2C connected Ilitek multiple touch controller
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/interrupt-controller/irq.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			ili2130_pins: ili2130_pins {
+				brcm,pins = <4>; // interrupt
+				brcm,function = <0>; // in
+				brcm,pull = <2>; // pull-up
+			};
+		};
+	};
+
+	frag1: fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			ili2130: ili2130@41 {
+				compatible = "ilitek,ili2130";
+				reg = <0x41>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&ili2130_pins>;
+				interrupt-parent = <&gpio>;
+				interrupts = <4 IRQ_TYPE_LEVEL_LOW>;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&ili2130>;
+		__overlay__ {
+			touchscreen-inverted-x;
+		};
+	};
+
+	fragment@3 {
+		target = <&ili2130>;
+		__overlay__ {
+			touchscreen-inverted-y;
+		};
+	};
+
+	__overrides__ {
+		interrupt = <&ili2130_pins>,"brcm,pins:0",
+			<&ili2130>,"interrupts:0";
+		i2c-path = <&frag1>, "target?=0",
+			   <&frag1>, "target-path";
+		invx = <0>, "-2";
+		invy = <0>, "-3";
+		swapxy = <&ili2130>,"touchscreen-swapped-x-y?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/ilitek251x-overlay.dts b/arch/arm/boot/dts/overlays/ilitek251x-overlay.dts
new file mode 100644
index 000000000000..5383ef160511
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ilitek251x-overlay.dts
@@ -0,0 +1,49 @@
+// Device tree overlay for I2C connected Ilitek multiple touch controller
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/interrupt-controller/irq.h>
+
+ / {
+	compatible = "brcm,bcm2835";
+
+ 	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {		
+			ili251x_pins: ili251x_pins {
+				brcm,pins = <4>; // interrupt
+				brcm,function = <0>; // in
+				brcm,pull = <2>; // pull-up //
+			};
+		};
+	};
+
+	frag1: fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+ 			ili251x: ili251x@41 {
+				compatible = "ilitek,ili251x";
+				reg = <0x41>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&ili251x_pins>;
+				interrupt-parent = <&gpio>;
+				interrupts = <4 IRQ_TYPE_LEVEL_LOW>;
+				touchscreen-size-x = <16384>;
+				touchscreen-size-y = <9600>;
+			};
+		};
+	};
+
+ 	__overrides__ {
+		interrupt = <&ili251x_pins>,"brcm,pins:0",
+			<&ili251x>,"interrupts:0";
+		sizex = <&ili251x>,"touchscreen-size-x:0";
+		sizey = <&ili251x>,"touchscreen-size-y:0";
+		i2c-path = <&frag1>, "target?=0",
+			   <&frag1>, "target-path";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/imx219-overlay.dts b/arch/arm/boot/dts/overlays/imx219-overlay.dts
new file mode 100644
index 000000000000..c855b14ff220
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx219-overlay.dts
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for IMX219 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	clk_frag: fragment@1 {
+		target = <&cam1_clk>;
+		__overlay__ {
+			status = "okay";
+			clock-frequency = <24000000>;
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	i2c_frag: fragment@100 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			#include "imx219.dtsi"
+
+			vcm: ad5398@c {
+				compatible = "adi,ad5398";
+				reg = <0x0c>;
+				status = "disabled";
+				VANA-supply = <&cam1_reg>;
+			};
+		};
+	};
+
+	csi_frag: fragment@101 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+
+			port {
+				csi_ep: endpoint {
+					remote-endpoint = <&cam_endpoint>;
+					clock-lanes = <0>;
+					data-lanes = <1 2>;
+					clock-noncontinuous;
+				};
+			};
+		};
+	};
+
+	fragment@102 {
+		target = <&csi1>;
+		__dormant__ {
+			compatible = "brcm,bcm2835-unicam-legacy";
+		};
+	};
+
+	fragment@201 {
+		target = <&csi_ep>;
+		__dormant__ {
+			data-lanes = <1 2 3 4>;
+		};
+	};
+
+	fragment@202 {
+		target = <&cam_endpoint>;
+		__dormant__ {
+			data-lanes = <1 2 3 4>;
+			link-frequencies =
+					/bits/ 64 <363000000>;
+		};
+	};
+
+	__overrides__ {
+		rotation = <&cam_node>,"rotation:0";
+		orientation = <&cam_node>,"orientation:0";
+		media-controller = <0>,"!102";
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+		       <&clk_frag>, "target:0=",<&cam0_clk>,
+		       <&cam_node>, "clocks:0=",<&cam0_clk>,
+		       <&cam_node>, "VANA-supply:0=",<&cam0_reg>,
+		       <&vcm>, "VANA-supply:0=", <&cam0_reg>;
+		vcm = <&vcm>, "status=okay",
+		      <&cam_node>,"lens-focus:0=", <&vcm>;
+		4lane = <0>, "+201+202";
+	};
+};
+
+&cam_node {
+	status = "okay";
+};
+
+&cam_endpoint {
+	remote-endpoint = <&csi_ep>;
+};
diff --git a/arch/arm/boot/dts/overlays/imx219.dtsi b/arch/arm/boot/dts/overlays/imx219.dtsi
new file mode 100644
index 000000000000..fa870f77ef07
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx219.dtsi
@@ -0,0 +1,27 @@
+// Fragment that configures an imx219
+
+cam_node: imx219@10 {
+	compatible = "sony,imx219";
+	reg = <0x10>;
+	status = "disabled";
+
+	clocks = <&cam1_clk>;
+	clock-names = "xclk";
+
+	VANA-supply = <&cam1_reg>;	/* 2.8v */
+	VDIG-supply = <&cam_dummy_reg>;	/* 1.8v */
+	VDDL-supply = <&cam_dummy_reg>;	/* 1.2v */
+
+	rotation = <180>;
+	orientation = <2>;
+
+	port {
+		cam_endpoint: endpoint {
+			clock-lanes = <0>;
+			data-lanes = <1 2>;
+			clock-noncontinuous;
+			link-frequencies =
+				/bits/ 64 <456000000>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/imx258-overlay.dts b/arch/arm/boot/dts/overlays/imx258-overlay.dts
new file mode 100644
index 000000000000..bf06d681d5dc
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx258-overlay.dts
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for IMX258 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	clk_frag: fragment@1 {
+		target = <&cam1_clk>;
+		cam_clk: __overlay__ {
+			clock-frequency = <24000000>;
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@11 {
+		target = <&cam_endpoint>;
+		__overlay__ {
+			data-lanes = <1 2>;
+			link-frequencies = /bits/ 64 <633600000
+						      320000000>;
+		};
+	};
+
+	fragment@12 {
+		target = <&cam_endpoint>;
+		__dormant__ {
+			data-lanes = <1 2 3 4>;
+			link-frequencies =
+				/bits/ 64 <633600000 320000000>;
+		};
+	};
+
+	fragment@13 {
+		target = <&csi_ep>;
+		__overlay__ {
+			data-lanes = <1 2>;
+		};
+	};
+
+	fragment@14 {
+		target = <&csi_ep>;
+		__dormant__ {
+			data-lanes = <1 2 3 4>;
+		};
+	};
+
+	csi_frag: fragment@101 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+
+			port {
+				csi_ep: endpoint {
+					remote-endpoint = <&cam_endpoint>;
+					clock-lanes = <0>;
+					clock-noncontinuous;
+				};
+			};
+		};
+	};
+
+	reg_frag: fragment@5 {
+		target = <&cam1_reg>;
+		cam_reg: __overlay__ {
+			regulator-name = "imx258_vana";
+			startup-delay-us = <300000>;
+			regulator-min-microvolt = <2700000>;
+			regulator-max-microvolt = <2700000>;
+		};
+	};
+
+	i2c_frag: fragment@100 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			#include "imx258.dtsi"
+
+			vcm: ad5398@c {
+				compatible = "adi,ad5398";
+				reg = <0x0c>;
+				status = "disabled";
+				VANA-supply = <&cam1_reg>;
+			};
+		};
+	};
+
+	fragment@102 {
+		target = <&csi1>;
+		__dormant__ {
+			compatible = "brcm,bcm2835-unicam-legacy";
+		};
+	};
+
+	__overrides__ {
+		rotation = <&cam_node>,"rotation:0";
+		orientation = <&cam_node>,"orientation:0";
+		media-controller = <0>,"!102";
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+		       <&clk_frag>, "target:0=",<&cam0_clk>,
+		       <&reg_frag>, "target:0=",<&cam0_reg>,
+		       <&cam_node>, "clocks:0=",<&cam0_clk>,
+		       <&cam_node>, "vana-supply:0=",<&cam0_reg>;
+		vcm = <&vcm>, "status=okay",
+		      <&cam_node>,"lens-focus:0=", <&vcm>;
+		4lane = <0>, "-11+12-13+14";
+	};
+};
+
+&cam_node {
+	status = "okay";
+};
+
+&cam_endpoint {
+	remote-endpoint = <&csi_ep>;
+};
diff --git a/arch/arm/boot/dts/overlays/imx258.dtsi b/arch/arm/boot/dts/overlays/imx258.dtsi
new file mode 100644
index 000000000000..cca81e1aa8b3
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx258.dtsi
@@ -0,0 +1,27 @@
+// Fragment that configures a Sony IMX258
+
+cam_node: imx258@10 {
+	compatible = "sony,imx258";
+	reg = <0x10>;
+	status = "disabled";
+
+	clocks = <&cam1_clk>;
+	clock-names = "xclk";
+
+	vana-supply = <&cam1_reg>;	/* 2.8v */
+	vdig-supply = <&cam_dummy_reg>;	/* 1.05v */
+	vif-supply = <&cam_dummy_reg>;	/* 1.8v */
+
+	rotation = <180>;
+	orientation = <2>;
+
+	port {
+		cam_endpoint: endpoint {
+			clock-lanes = <0>;
+			clock-noncontinuous;
+			link-frequencies =
+				/bits/ 64 <633600000
+					320000000>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/imx283-overlay.dts b/arch/arm/boot/dts/overlays/imx283-overlay.dts
new file mode 100644
index 000000000000..8445cf8f2856
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx283-overlay.dts
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for IMX283 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	clk_frag: fragment@1 {
+		target = <&cam1_clk>;
+		cam_clk: __overlay__ {
+			status = "okay";
+			clock-frequency = <24000000>;
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	reg_frag: fragment@3 {
+		target = <&cam1_reg>;
+		cam_reg: __overlay__ {
+			startup-delay-us = <200000>;
+			off-on-delay-us = <30000>;
+			regulator-min-microvolt = <2900000>;
+			regulator-max-microvolt = <2900000>;
+		};
+	};
+
+	i2c_frag: fragment@100 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			#include "imx283.dtsi"
+		};
+	};
+
+	csi_frag: fragment@101 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+			brcm,media-controller;
+
+			port {
+				csi_ep: endpoint {
+					remote-endpoint = <&cam_endpoint>;
+					clock-lanes = <0>;
+					data-lanes = <1 2 3 4>;
+					clock-noncontinuous;
+				};
+			};
+		};
+	};
+
+	__overrides__ {
+		clock-frequency = <&cam_clk>,"clock-frequency:0";
+		rotation = <&cam_node>,"rotation:0";
+		orientation = <&cam_node>,"orientation:0";
+		media-controller = <&csi>,"brcm,media-controller?";
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+		       <&clk_frag>, "target:0=",<&cam0_clk>,
+		       <&reg_frag>, "target:0=",<&cam0_reg>,
+		       <&cam_node>, "clocks:0=",<&cam0_clk>,
+		       <&cam_node>, "vadd-supply:0=",<&cam0_reg>;
+		link-frequency = <&cam_endpoint>,"link-frequencies#0";
+	};
+};
+
+&cam_node {
+	status = "okay";
+};
+
+&cam_endpoint {
+	remote-endpoint = <&csi_ep>;
+};
diff --git a/arch/arm/boot/dts/overlays/imx283.dtsi b/arch/arm/boot/dts/overlays/imx283.dtsi
new file mode 100644
index 000000000000..07ba72379f36
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx283.dtsi
@@ -0,0 +1,26 @@
+// Fragment that configures a Sony IMX283
+
+cam_node: imx283@1a {
+	compatible = "sony,imx283";
+	reg = <0x1a>;
+	status = "disabled";
+
+	clocks = <&cam1_clk>;
+	clock-names = "xclk";
+
+	vadd-supply = <&cam1_reg>;	/* Analog power supply (2.9v) */
+	vdd1-supply = <&cam_dummy_reg>;	/* Interface power supply (1.8V) */
+	vdd2-supply = <&cam_dummy_reg>;	/* Digital power supply (1.2V) */
+
+	rotation = <180>;
+	orientation = <2>;
+
+	port {
+		cam_endpoint: endpoint {
+			clock-lanes = <0>;
+			data-lanes = <1 2 3 4>;
+			clock-noncontinuous;
+			link-frequencies = /bits/ 64 <720000000>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/imx290-overlay.dts b/arch/arm/boot/dts/overlays/imx290-overlay.dts
new file mode 100644
index 000000000000..3de3c3910d90
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx290-overlay.dts
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for IMX290 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include "imx290_327-overlay.dtsi"
+
+/{
+	compatible = "brcm,bcm2835";
+
+	// Fragment numbers deliberately high to avoid conflicts with the
+	// included imx290_327 overlay file.
+
+	fragment@101 {
+		target = <&cam_node>;
+		__overlay__ {
+			compatible = "sony,imx290lqr";
+		};
+	};
+
+	fragment@102 {
+		target = <&cam_node>;
+		__dormant__ {
+			compatible = "sony,imx290llr";
+		};
+	};
+
+	__overrides__ {
+		mono = <0>, "-101+102";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/imx290_327-overlay.dtsi b/arch/arm/boot/dts/overlays/imx290_327-overlay.dtsi
new file mode 100644
index 000000000000..55f047e315bc
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx290_327-overlay.dtsi
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Partial definitions for IMX290 or IMX327 camera module on VC I2C bus
+// The compatible string should be set in an overlay that then includes this one
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2835";
+
+	i2c_frag: fragment@0 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			#include "imx290_327.dtsi"
+		};
+	};
+
+	csi_frag: fragment@1 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+
+			port {
+				csi_ep: endpoint {
+					remote-endpoint = <&cam_endpoint>;
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	clk_frag: fragment@3 {
+		target = <&cam1_clk>;
+		cam_clk: __overlay__ {
+			status = "okay";
+			clock-frequency = <37125000>;
+		};
+	};
+
+	fragment@4 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@6 {
+		target = <&cam_endpoint>;
+		__overlay__ {
+			data-lanes = <1 2>;
+			link-frequencies =
+				/bits/ 64 <445500000 297000000>;
+		};
+	};
+
+	fragment@7 {
+		target = <&cam_endpoint>;
+		__dormant__ {
+			data-lanes = <1 2 3 4>;
+			link-frequencies =
+				/bits/ 64 <222750000 148500000>;
+		};
+	};
+
+	fragment@8 {
+		target = <&csi_ep>;
+		__overlay__ {
+			data-lanes = <1 2>;
+		};
+	};
+
+	fragment@9 {
+		target = <&csi_ep>;
+		__dormant__ {
+			data-lanes = <1 2 3 4>;
+		};
+	};
+
+	fragment@10 {
+		target = <&csi1>;
+		__dormant__ {
+			compatible = "brcm,bcm2835-unicam-legacy";
+		};
+	};
+
+	__overrides__ {
+		4lane = <0>, "-6+7-8+9";
+		clock-frequency = <&cam_clk>,"clock-frequency:0",
+				  <&cam_node>,"clock-frequency:0";
+		rotation = <&cam_node>,"rotation:0";
+		orientation = <&cam_node>,"orientation:0";
+		media-controller = <0>,"!10";
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+		       <&clk_frag>, "target:0=",<&cam0_clk>,
+		       <&cam_node>, "clocks:0=",<&cam0_clk>,
+		       <&cam_node>, "vdda-supply:0=",<&cam0_reg>;
+	};
+};
+
+&cam_node {
+	status = "okay";
+};
+
+&cam_endpoint {
+	remote-endpoint = <&csi_ep>;
+};
diff --git a/arch/arm/boot/dts/overlays/imx290_327.dtsi b/arch/arm/boot/dts/overlays/imx290_327.dtsi
new file mode 100644
index 000000000000..14d1f0b95bb3
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx290_327.dtsi
@@ -0,0 +1,24 @@
+// Fragment to configure and IMX290 / IMX327 / IMX462 image sensor
+
+cam_node: imx290@1a {
+	compatible = "sony,imx290lqr";
+	reg = <0x1a>;
+	status = "disabled";
+
+	clocks = <&cam1_clk>;
+	clock-names = "xclk";
+	clock-frequency = <37125000>;
+
+	rotation = <0>;
+	orientation = <2>;
+
+	vdda-supply = <&cam1_reg>;	/* 2.8v */
+	vdddo-supply = <&cam_dummy_reg>;	/* 1.8v */
+	vddd-supply = <&cam_dummy_reg>;	/* 1.5v */
+
+	port {
+		cam_endpoint: endpoint {
+			clock-lanes = <0>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/imx296-overlay.dts b/arch/arm/boot/dts/overlays/imx296-overlay.dts
new file mode 100644
index 000000000000..b40abb237f6f
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx296-overlay.dts
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for IMX296 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	clk_frag: fragment@1 {
+		target = <&cam1_clk>;
+		clk_over: __overlay__ {
+			status = "okay";
+			clock-frequency = <54000000>;
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	reg_frag: fragment@5 {
+		target = <&cam1_reg>;
+		cam_reg: __overlay__ {
+			startup-delay-us = <500000>;
+		};
+	};
+
+	reg_alwayson_frag: fragment@99 {
+		target = <&cam1_reg>;
+		__dormant__ {
+			regulator-always-on;
+		};
+	};
+
+	i2c_frag: fragment@100 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			imx296: imx296@1a {
+				compatible = "sony,imx296";
+				reg = <0x1a>;
+				status = "okay";
+
+				clocks = <&cam1_clk>;
+				clock-names = "inck";
+
+				avdd-supply = <&cam1_reg>;	/* 3.3v */
+				dvdd-supply = <&cam_dummy_reg>;	/* 1.8v */
+				ovdd-supply = <&cam_dummy_reg>;	/* 1.2v */
+
+				rotation = <180>;
+				orientation = <2>;
+
+				port {
+					imx296_0: endpoint {
+						remote-endpoint = <&csi_ep>;
+						clock-lanes = <0>;
+						data-lanes = <1>;
+						clock-noncontinuous;
+						link-frequencies =
+							/bits/ 64 <594000000>;
+					};
+				};
+			};
+		};
+	};
+
+	csi_frag: fragment@101 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+
+			port {
+				csi_ep: endpoint {
+					remote-endpoint = <&imx296_0>;
+					clock-lanes = <0>;
+					data-lanes = <1>;
+					clock-noncontinuous;
+				};
+			};
+		};
+	};
+
+	fragment@102 {
+		target = <&csi1>;
+		__dormant__ {
+			compatible = "brcm,bcm2835-unicam-legacy";
+		};
+	};
+
+	__overrides__ {
+		rotation = <&imx296>,"rotation:0";
+		orientation = <&imx296>,"orientation:0";
+		media-controller = <0>,"!102";
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+		       <&clk_frag>, "target:0=",<&cam0_clk>,
+		       <&reg_frag>, "target:0=",<&cam0_reg>,
+		       <&reg_alwayson_frag>, "target:0=",<&cam0_reg>,
+		       <&imx296>, "clocks:0=",<&cam0_clk>,
+		       <&imx296>, "avdd-supply:0=",<&cam0_reg>;
+		clock-frequency = <&clk_over>, "clock-frequency:0";
+		always-on = <0>, "+99";
+		sync-sink = <&imx296>,"trigger-mode:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/imx327-overlay.dts b/arch/arm/boot/dts/overlays/imx327-overlay.dts
new file mode 100644
index 000000000000..0776954bdba2
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx327-overlay.dts
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for IMX327 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include "imx290_327-overlay.dtsi"
+
+/{
+	compatible = "brcm,bcm2835";
+
+	// Fragment numbers deliberately high to avoid conflicts with the
+	// included imx290_327 overlay file.
+
+	fragment@101 {
+		target = <&cam_node>;
+		__overlay__ {
+			compatible = "sony,imx327lqr";
+		};
+	};
+
+	fragment@102 {
+		target = <&cam_node>;
+		__dormant__ {
+			// No mono IMX327 is currently defined. Use IMX290.
+			compatible = "sony,imx290llr";
+		};
+	};
+
+	__overrides__ {
+		mono = <0>, "-101+102";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/imx335-overlay.dts b/arch/arm/boot/dts/overlays/imx335-overlay.dts
new file mode 100644
index 000000000000..dfe207faecbe
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx335-overlay.dts
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for IMX335 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	clk_frag: fragment@1 {
+		target = <&cam1_clk>;
+		__overlay__ {
+			status = "okay";
+			clock-frequency = <24000000>;
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	reg_frag: fragment@3 {
+		target = <&cam1_reg>;
+		cam_reg: __overlay__ {
+			startup-delay-us = <200000>;
+			off-on-delay-us = <30000>;
+			regulator-min-microvolt = <2900000>;
+			regulator-max-microvolt = <2900000>;
+		};
+	};
+
+	i2c_frag: fragment@100 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			#include "imx335.dtsi"
+		};
+	};
+
+	csi_frag: fragment@101 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+
+			port {
+				csi_ep: endpoint {
+					remote-endpoint = <&cam_endpoint>;
+					clock-lanes = <0>;
+					data-lanes = <1 2>;
+				};
+			};
+		};
+	};
+
+	fragment@102 {
+		target = <&csi1>;
+		__dormant__ {
+			compatible = "brcm,bcm2835-unicam-legacy";
+		};
+	};
+
+	fragment@201 {
+		target = <&csi_ep>;
+		__dormant__ {
+			data-lanes = <1 2 3 4>;
+		};
+	};
+
+	fragment@202 {
+		target = <&cam_endpoint>;
+		__dormant__ {
+			data-lanes = <1 2 3 4>;
+		};
+	};
+
+	__overrides__ {
+		rotation = <&cam_node>,"rotation:0";
+		orientation = <&cam_node>,"orientation:0";
+		media-controller = <0>,"!102";
+
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+		       <&clk_frag>, "target:0=",<&cam0_clk>,
+		       <&reg_frag>, "target:0=",<&cam0_reg>,
+		       <&cam_node>, "clocks:0=",<&cam0_clk>,
+		       <&cam_node>, "avdd-supply:0=",<&cam0_reg>;
+		link-frequency = <&cam_endpoint>,"link-frequencies#0";
+		4lane = <0>, "+201+202";
+	};
+};
+
+&cam_node {
+	status = "okay";
+};
+
+&cam_endpoint {
+	remote-endpoint = <&csi_ep>;
+};
diff --git a/arch/arm/boot/dts/overlays/imx335.dtsi b/arch/arm/boot/dts/overlays/imx335.dtsi
new file mode 100644
index 000000000000..1d4b07da3b14
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx335.dtsi
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Fragment that configures a Sony IMX335
+
+cam_node: imx335@1a {
+	compatible = "sony,imx335";
+	reg = <0x1a>;
+	status = "disabled";
+
+	clocks = <&cam1_clk>;
+	clock-names = "xclk";
+
+	avdd-supply = <&cam1_reg>;		/* Analog 2.9v */
+	iovdd-supply = <&cam_dummy_reg>;	/* Digital I/O 1.8v */
+	dvdd-supply = <&cam_dummy_reg>;		/* Digital Core 1.2v */
+
+	rotation = <180>;
+	orientation = <2>;
+
+	port {
+		cam_endpoint: endpoint {
+			clock-lanes = <0>;
+			data-lanes = <1 2>;
+			link-frequencies = /bits/ 64 <594000000 445500000>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/imx378-overlay.dts b/arch/arm/boot/dts/overlays/imx378-overlay.dts
new file mode 100644
index 000000000000..4a5072489a34
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx378-overlay.dts
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for IMX378 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+#include "imx477_378-overlay.dtsi"
+
+&cam_node {
+	compatible = "sony,imx378";
+};
+
+/{
+	__overrides__ {
+		sync-sink = <&cam_node>,"trigger-mode:0=2";
+		sync-source = <&cam_node>,"trigger-mode:0=1";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/imx415-overlay.dts b/arch/arm/boot/dts/overlays/imx415-overlay.dts
new file mode 100644
index 000000000000..e44c032dfcec
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx415-overlay.dts
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for IMX415 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	clk_frag: fragment@1 {
+		target = <&cam1_clk>;
+		cam_clk: __overlay__ {
+			status = "okay";
+			clock-frequency = <24000000>;
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	reg_frag: fragment@3 {
+		target = <&cam1_reg>;
+		cam_reg: __overlay__ {
+			startup-delay-us = <100000>;
+		};
+	};
+
+	i2c_frag: fragment@100 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			#include "imx415.dtsi"
+
+			vcm: ad5398@c {
+				compatible = "adi,ad5398";
+				reg = <0x0c>;
+				status = "disabled";
+				VANA-supply = <&cam1_reg>;
+			};
+		};
+	};
+
+	csi_frag: fragment@101 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+			brcm,media-controller;
+
+			port {
+				csi_ep: endpoint {
+					remote-endpoint = <&cam_endpoint>;
+					clock-lanes = <0>;
+					data-lanes = <1 2>;
+					clock-noncontinuous;
+				};
+			};
+		};
+	};
+
+	fragment@201 {
+		target = <&cam_endpoint>;
+		__dormant__ {
+			data-lanes = <1 2 3 4>;
+		};
+	};
+
+	fragment@202 {
+		target = <&csi_ep>;
+		__dormant__ {
+			data-lanes = <1 2 3 4>;
+		};
+	};
+
+
+	__overrides__ {
+		addr = <&cam_node>, "reg:0";
+		rotation = <&cam_node>,"rotation:0";
+		orientation = <&cam_node>,"orientation:0";
+		media-controller = <&csi>,"brcm,media-controller?";
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+		       <&reg_frag>, "target:0=",<&cam0_reg>,
+		       <&clk_frag>, "target:0=",<&cam0_clk>,
+		       <&cam_node>, "clocks:0=",<&cam0_clk>,
+		       <&cam_node>, "avdd-supply:0=",<&cam0_reg>,
+		       <&vcm>, "VANA-supply:0=", <&cam0_reg>;
+		vcm = <&vcm>, "status=okay",
+		      <&cam_node>,"lens-focus:0=", <&vcm>;
+		clock-frequency = <&cam_clk>, "clock-frequency:0";
+		link-frequency = <&cam_endpoint>,"link-frequencies#0";
+		clk-37125 = <&cam_endpoint>,"link-frequencies#0=445500000",
+			   <&cam_clk>, "clock-frequency:0=37125000";
+		4lane = <0>, "+201+202";
+	};
+};
+
+&cam_node {
+	status = "okay";
+};
+
+&cam_endpoint {
+	remote-endpoint = <&csi_ep>;
+};
diff --git a/arch/arm/boot/dts/overlays/imx415.dtsi b/arch/arm/boot/dts/overlays/imx415.dtsi
new file mode 100644
index 000000000000..ba18550ddec7
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx415.dtsi
@@ -0,0 +1,27 @@
+// Fragment that configures an imx415
+
+cam_node: imx415@37 {
+	compatible = "sony,imx415";
+	reg = <0x37>;
+	status = "disabled";
+
+	clocks = <&cam1_clk>;
+	clock-names = "inck";
+
+	avdd-supply = <&cam1_reg>;	/* 2.8v */
+	dvdd-supply = <&cam_dummy_reg>;	/* 1.8v */
+	ovdd-supply = <&cam_dummy_reg>;	/* 1.2v */
+
+	rotation = <180>;
+	orientation = <2>;
+
+	port {
+		cam_endpoint: endpoint {
+			clock-lanes = <0>;
+			data-lanes = <1 2>;
+			//clock-noncontinuous;
+			link-frequencies =
+				/bits/ 64 <360000000>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/imx462-overlay.dts b/arch/arm/boot/dts/overlays/imx462-overlay.dts
new file mode 100644
index 000000000000..6e4c5aaddff5
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx462-overlay.dts
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for IMX462 camera module on VC I2C bus
+
+// IMX462 is the successor to IMX290.
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include "imx290_327-overlay.dtsi"
+
+/{
+	compatible = "brcm,bcm2835";
+
+	// Fragment numbers deliberately high to avoid conflicts with the
+	// included imx290_327 overlay file.
+
+	fragment@101 {
+		target = <&cam_node>;
+		__overlay__ {
+			compatible = "sony,imx462lqr";
+		};
+	};
+
+	fragment@102 {
+		target = <&cam_node>;
+		__dormant__ {
+			compatible = "sony,imx462llr";
+		};
+	};
+
+	__overrides__ {
+		mono = <0>, "-101+102";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/imx477-overlay.dts b/arch/arm/boot/dts/overlays/imx477-overlay.dts
new file mode 100644
index 000000000000..8645162682f4
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx477-overlay.dts
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for IMX477 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+#include "imx477_378-overlay.dtsi"
+
+&cam_node {
+	compatible = "sony,imx477";
+};
+
+/{
+	__overrides__ {
+		sync-sink = <&cam_node>,"trigger-mode:0=2";
+		sync-source = <&cam_node>,"trigger-mode:0=1";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/imx477_378-overlay.dtsi b/arch/arm/boot/dts/overlays/imx477_378-overlay.dtsi
new file mode 100644
index 000000000000..686289f55029
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx477_378-overlay.dtsi
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for IMX477 camera module on VC I2C bus
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	clk_frag: fragment@1 {
+		target = <&cam1_clk>;
+		cam_clk: __overlay__ {
+			clock-frequency = <24000000>;
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	reg_frag: fragment@3 {
+		target = <&cam1_reg>;
+		cam_reg: __overlay__ {
+			startup-delay-us = <300000>;
+		};
+	};
+
+	reg_alwayson_frag: fragment@99 {
+		target = <&cam1_reg>;
+		__dormant__ {
+			regulator-always-on;
+		};
+	};
+
+	i2c_frag: fragment@100 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			#include "imx477_378.dtsi"
+		};
+	};
+
+	csi_frag: fragment@101 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+
+			port {
+				csi_ep: endpoint {
+					remote-endpoint = <&cam_endpoint>;
+					clock-lanes = <0>;
+					data-lanes = <1 2>;
+					clock-noncontinuous;
+				};
+			};
+		};
+	};
+
+	fragment@102 {
+		target = <&csi1>;
+		__dormant__ {
+			compatible = "brcm,bcm2835-unicam-legacy";
+		};
+	};
+
+	__overrides__ {
+		rotation = <&cam_node>,"rotation:0";
+		orientation = <&cam_node>,"orientation:0";
+		media-controller = <0>,"!102";
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+		       <&clk_frag>, "target:0=",<&cam0_clk>,
+		       <&reg_frag>, "target:0=",<&cam0_reg>,
+		       <&reg_alwayson_frag>, "target:0=",<&cam0_reg>,
+		       <&cam_node>, "clocks:0=",<&cam0_clk>,
+		       <&cam_node>, "VANA-supply:0=",<&cam0_reg>;
+		always-on = <0>, "+99";
+		link-frequency = <&cam_endpoint>,"link-frequencies#0";
+	};
+};
+
+&cam_node {
+	status = "okay";
+};
+
+&cam_endpoint {
+	remote-endpoint = <&csi_ep>;
+};
diff --git a/arch/arm/boot/dts/overlays/imx477_378.dtsi b/arch/arm/boot/dts/overlays/imx477_378.dtsi
new file mode 100644
index 000000000000..a0c154c2a11f
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx477_378.dtsi
@@ -0,0 +1,24 @@
+cam_node: imx477@1a {
+	reg = <0x1a>;
+	status = "disabled";
+
+	clocks = <&cam1_clk>;
+	clock-names = "xclk";
+
+	VANA-supply = <&cam1_reg>;	/* 2.8v */
+	VDIG-supply = <&cam_dummy_reg>;	/* 1.05v */
+	VDDL-supply = <&cam_dummy_reg>;	/* 1.8v */
+
+	rotation = <180>;
+	orientation = <2>;
+
+	port {
+		cam_endpoint: endpoint {
+			clock-lanes = <0>;
+			data-lanes = <1 2>;
+			clock-noncontinuous;
+			link-frequencies =
+				/bits/ 64 <450000000>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/imx500-overlay.dts b/arch/arm/boot/dts/overlays/imx500-overlay.dts
new file mode 100644
index 000000000000..3ed8c8cd660c
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx500-overlay.dts
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for IMX500 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	reg_frag: fragment@2 {
+		target = <&cam1_reg>;
+		cam_reg: __overlay__ {
+			startup-delay-us = <300000>;
+		};
+	};
+
+	i2c_frag: fragment@100 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			#include "imx500.dtsi"
+			#include "rpi-rp2040-gpio-bridge.dtsi"
+		};
+	};
+
+	csi_frag: fragment@101 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+			brcm,media-controller;
+
+			port {
+				csi_ep: endpoint {
+					remote-endpoint = <&cam_endpoint>;
+					clock-lanes = <0>;
+					data-lanes = <1 2>;
+					clock-noncontinuous;
+				};
+			};
+		};
+	};
+
+	spi_frag: fragment@102 {
+		target = <&spi_bridgedev0>;
+		__overlay__ {
+			compatible = "sony,imx500";
+		};
+	};
+
+	chosen_frag: fragment@103 {
+		target = <&chosen>;
+		__overlay__ {
+			core_freq_fixed;
+		};
+	};
+
+	fragment@104 {
+		target-path = "/clocks";
+		__overlay__ {
+			clk_aicam: clk-aicam1 {
+				compatible = "fixed-clock";
+				#clock-cells = <0>;
+				clock-frequency = <24000000>;
+			};
+
+			clk_aicam_gated: clk-aicam-gated1 {
+				compatible = "gpio-gate-clock-releasing";
+				clocks = <&clk_aicam>;
+				#clock-cells = <0>;
+				enable-gpios = <&spi_bridge 21 GPIO_ACTIVE_HIGH>;
+			};
+		};
+	};
+
+	__overrides__ {
+		rotation = <&cam_node>,"rotation:0";
+		orientation = <&cam_node>,"orientation:0";
+		media-controller = <&csi>,"brcm,media-controller?";
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+			   <&spi_bridge>, "power-supply:0=",<&cam0_reg>,
+		       <&reg_frag>, "target:0=",<&cam0_reg>,
+		       <&cam_node>, "VANA-supply:0=",<&cam0_reg>,
+		       <&clk_aicam>,"name=clk-aicam0",
+		       <&clk_aicam_gated>,"name=clk-aicam-gated0";
+		bypass-cache = <&spi_bridge>,"bypass-cache?";
+	};
+};
+
+&cam_node {
+	status = "okay";
+	led-gpios = <&spi_bridge 19 GPIO_ACTIVE_HIGH>;
+	reset-gpios = <&spi_bridge 20 GPIO_ACTIVE_HIGH>;
+	clocks = <&clk_aicam_gated>;
+	spi = <&spi_bridgedev0>;
+};
+
+&spi_bridge {
+	status = "okay";
+};
+
+&cam_endpoint {
+	remote-endpoint = <&csi_ep>;
+};
diff --git a/arch/arm/boot/dts/overlays/imx500-pi5-overlay.dts b/arch/arm/boot/dts/overlays/imx500-pi5-overlay.dts
new file mode 100644
index 000000000000..a512d4b76ede
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx500-pi5-overlay.dts
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for IMX500 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	reg_frag: fragment@2 {
+		target = <&cam1_reg>;
+		cam_reg: __overlay__ {
+			startup-delay-us = <300000>;
+		};
+	};
+
+	i2c_frag: fragment@100 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			#include "imx500.dtsi"
+			#include "rpi-rp2040-gpio-bridge.dtsi"
+		};
+	};
+
+	csi_frag: fragment@101 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+			brcm,media-controller;
+
+			port {
+				csi_ep: endpoint {
+					remote-endpoint = <&cam_endpoint>;
+					clock-lanes = <0>;
+					data-lanes = <1 2>;
+					clock-noncontinuous;
+				};
+			};
+		};
+	};
+
+	spi_frag: fragment@102 {
+		target = <&spi_bridge>;
+		spi_frag_overlay: __overlay__ {
+			fast_xfer_requires_i2c_lock = <1>;
+			fast_xfer_recv_gpio_base = <11>;
+			fast_xfer-gpios = <&rp1_gpio 40 0>, // CD1_SDA (used as data)
+					  <&rp1_gpio 48 0>; // CD1_IO1_MICDAT1 (clock)
+		};
+	};
+
+	spi_bridge_frag: fragment@103 {
+		target = <&spi_bridgedev0>;
+		__overlay__ {
+			compatible = "sony,imx500";
+		};
+	};
+
+	fragment@104 {
+		target-path = "/clocks";
+		__overlay__ {
+			clk_aicam: clk-aicam1 {
+				compatible = "fixed-clock";
+				#clock-cells = <0>;
+				clock-frequency = <24000000>;
+			};
+
+			clk_aicam_gated: clk-aicam-gated1 {
+				compatible = "gpio-gate-clock-releasing";
+				clocks = <&clk_aicam>;
+				#clock-cells = <0>;
+				enable-gpios = <&spi_bridge 21 GPIO_ACTIVE_HIGH>;
+			};
+		};
+	};
+
+	__overrides__ {
+		rotation = <&cam_node>,"rotation:0";
+		orientation = <&cam_node>,"orientation:0";
+		media-controller = <&csi>,"brcm,media-controller?";
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+			   <&spi_frag_overlay>, "fast_xfer-gpios:4=38", // CD0_SDA (data)
+			   <&spi_frag_overlay>, "fast_xfer-gpios:16=35", // CD0_IO1_MICDAT0 (clock)
+			   <&spi_bridge>, "power-supply:0=",<&cam0_reg>,
+		       <&reg_frag>, "target:0=",<&cam0_reg>,
+		       <&cam_node>, "VANA-supply:0=",<&cam0_reg>,
+		       <&clk_aicam>,"name=clk-aicam0",
+		       <&clk_aicam_gated>,"name=clk-aicam-gated0";
+		bypass-cache = <&spi_bridge>,"bypass-cache?";
+	};
+};
+
+&cam_node {
+	status = "okay";
+	led-gpios = <&spi_bridge 19 GPIO_ACTIVE_HIGH>;
+	reset-gpios = <&spi_bridge 20 GPIO_ACTIVE_HIGH>;
+	clocks = <&clk_aicam_gated>;
+	spi = <&spi_bridgedev0>;
+};
+
+&spi_bridge {
+	status = "okay";
+};
+
+&cam_endpoint {
+	remote-endpoint = <&csi_ep>;
+};
diff --git a/arch/arm/boot/dts/overlays/imx500.dtsi b/arch/arm/boot/dts/overlays/imx500.dtsi
new file mode 100644
index 000000000000..a931aa9941e6
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx500.dtsi
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0-only
+cam_node: imx500@1a {
+	reg = <0x1a>;
+	compatible = "sony,imx500";
+	status = "disabled";
+
+	clocks = <&cam1_clk>;
+	clock-names = "inck";
+
+	vana-supply = <&cam1_reg>;	/* 2.7v */
+	vdig-supply = <&cam_dummy_reg>;	/* 0.84v */
+	vif-supply = <&cam_dummy_reg>;	/* 1.8v */
+
+	reset-gpios = <&gpio 255 GPIO_ACTIVE_HIGH>;
+
+	rotation = <0>;
+	orientation = <2>;
+
+	port {
+		cam_endpoint: endpoint {
+			clock-lanes = <0>;
+			data-lanes = <1 2>;
+			clock-noncontinuous;
+			link-frequencies =
+				/bits/ 64 <444000000>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/imx519-overlay.dts b/arch/arm/boot/dts/overlays/imx519-overlay.dts
new file mode 100644
index 000000000000..f1bcd782b99f
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx519-overlay.dts
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for imx519 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2835";
+
+	i2c_frag: fragment@0 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			#include "imx519.dtsi"
+		};
+	};
+
+	csi_frag: fragment@1 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+
+			port{
+				csi_ep: endpoint{
+					remote-endpoint = <&cam_endpoint>;
+					clock-lanes = <0>;
+					data-lanes = <1 2>;
+					clock-noncontinuous;
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	clk_frag: fragment@3 {
+		target = <&cam1_clk>;
+		__overlay__ {
+			clock-frequency = <24000000>;
+			status = "okay";
+		};
+	};
+
+	fragment@4 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@5 {
+		target = <&cam_node>;
+		__overlay__ {
+			lens-focus = <&vcm_node>;
+		};
+	};
+
+	fragment@6 {
+		target = <&csi1>;
+		__dormant__ {
+			compatible = "brcm,bcm2835-unicam-legacy";
+		};
+	};
+
+	__overrides__ {
+		rotation = <&cam_node>,"rotation:0";
+		orientation = <&cam_node>,"orientation:0";
+		media-controller = <0>,"!6";
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+		       <&clk_frag>, "target:0=",<&cam0_clk>,
+		       <&cam_node>, "clocks:0=",<&cam0_clk>,
+		       <&cam_node>, "VANA-supply:0=",<&cam0_reg>,
+		       <&vcm_node>, "vdd-supply:0=",<&cam0_reg>;
+		vcm = <&vcm_node>, "status",
+		      <0>, "=5";
+	};
+};
+
+&cam_node {
+	status = "okay";
+};
+
+&cam_endpoint {
+	remote-endpoint = <&csi_ep>;
+};
+
+&vcm_node {
+	status = "okay";
+};
diff --git a/arch/arm/boot/dts/overlays/imx519.dtsi b/arch/arm/boot/dts/overlays/imx519.dtsi
new file mode 100644
index 000000000000..18cba1781ec4
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx519.dtsi
@@ -0,0 +1,34 @@
+// Fragment that configures a Sony IMX519
+
+cam_node: imx519@1a {
+	compatible = "sony,imx519";
+	reg = <0x1a>;
+	status = "disabled";
+
+	clocks = <&cam1_clk>;
+	clock-names = "xclk";
+
+	VANA-supply = <&cam1_reg>;	/* 2.8v */
+	VDIG-supply = <&cam_dummy_reg>;	/* 1.8v */
+	VDDL-supply = <&cam_dummy_reg>;	/* 1.2v */
+
+	rotation = <0>;
+	orientation = <2>;
+
+	port {
+		cam_endpoint: endpoint {
+			clock-lanes = <0>;
+			data-lanes = <1 2>;
+			clock-noncontinuous;
+			link-frequencies =
+				/bits/ 64 <408000000>;
+		};
+	};
+};
+
+vcm_node: ak7375@c {
+	compatible = "asahi-kasei,ak7375";
+	reg = <0x0c>;
+	status = "disabled";
+	vdd-supply = <&cam1_reg>;
+};
diff --git a/arch/arm/boot/dts/overlays/imx708-overlay.dts b/arch/arm/boot/dts/overlays/imx708-overlay.dts
new file mode 100644
index 000000000000..3cbec474ce3e
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx708-overlay.dts
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for IMX708 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	clk_frag: fragment@1 {
+		target = <&cam1_clk>;
+		__overlay__ {
+			status = "okay";
+			clock-frequency = <24000000>;
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	reg_frag: fragment@3 {
+		target = <&cam1_reg>;
+		cam_reg: __overlay__ {
+			startup-delay-us = <70000>;
+			off-on-delay-us = <30000>;
+			regulator-min-microvolt = <2700000>;
+			regulator-max-microvolt = <2700000>;
+		};
+	};
+
+	fragment@4 {
+		target = <&cam_node>;
+		__overlay__ {
+			lens-focus = <&vcm_node>;
+		};
+	};
+
+	i2c_frag: fragment@100 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			#include "imx708.dtsi"
+		};
+	};
+
+	csi_frag: fragment@101 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+
+			port {
+				csi_ep: endpoint {
+					remote-endpoint = <&cam_endpoint>;
+					clock-lanes = <0>;
+					data-lanes = <1 2>;
+					clock-noncontinuous;
+				};
+			};
+		};
+	};
+
+	fragment@102 {
+		target = <&csi1>;
+		__dormant__ {
+			compatible = "brcm,bcm2835-unicam-legacy";
+		};
+	};
+
+	__overrides__ {
+		rotation = <&cam_node>,"rotation:0";
+		orientation = <&cam_node>,"orientation:0";
+		media-controller = <0>,"!102";
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+		       <&clk_frag>, "target:0=",<&cam0_clk>,
+		       <&reg_frag>, "target:0=",<&cam0_reg>,
+		       <&cam_node>, "clocks:0=",<&cam0_clk>,
+		       <&cam_node>, "vana1-supply:0=",<&cam0_reg>,
+		       <&vcm_node>, "VDD-supply:0=",<&cam0_reg>;
+		vcm = <&vcm_node>, "status",
+		      <0>, "=4";
+		link-frequency = <&cam_endpoint>,"link-frequencies#0";
+	};
+};
+
+&cam_node {
+	status = "okay";
+};
+
+&cam_endpoint {
+	remote-endpoint = <&csi_ep>;
+};
+
+&vcm_node {
+	status = "okay";
+};
diff --git a/arch/arm/boot/dts/overlays/imx708.dtsi b/arch/arm/boot/dts/overlays/imx708.dtsi
new file mode 100644
index 000000000000..1558458d58ec
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/imx708.dtsi
@@ -0,0 +1,35 @@
+// Fragment that configures a Sony IMX708
+
+cam_node: imx708@1a {
+	compatible = "sony,imx708";
+	reg = <0x1a>;
+	status = "disabled";
+
+	clocks = <&cam1_clk>;
+	clock-names = "inclk";
+
+	vana1-supply = <&cam1_reg>;	/* 2.8v */
+	vana2-supply = <&cam_dummy_reg>;/* 1.8v */
+	vdig-supply = <&cam_dummy_reg>;	/* 1.1v */
+	vddl-supply = <&cam_dummy_reg>;	/* 1.8v */
+
+	rotation = <180>;
+	orientation = <2>;
+
+	port {
+		cam_endpoint: endpoint {
+			clock-lanes = <0>;
+			data-lanes = <1 2>;
+			clock-noncontinuous;
+			link-frequencies =
+				/bits/ 64 <450000000>;
+		};
+	};
+};
+
+vcm_node: dw9817@c {
+	compatible = "dongwoon,dw9817-vcm";
+	reg = <0x0c>;
+	status = "disabled";
+	VDD-supply = <&cam1_reg>;
+};
diff --git a/arch/arm/boot/dts/overlays/interludeaudio-analog-overlay.dts b/arch/arm/boot/dts/overlays/interludeaudio-analog-overlay.dts
new file mode 100644
index 000000000000..e2590135f919
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/interludeaudio-analog-overlay.dts
@@ -0,0 +1,73 @@
+// Definitions for Interlude audio analog hat
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "simple-audio-card";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+
+			simple-audio-card,name = "snd_IA_Analog_Hat";
+
+			simple-audio-card,widgets =
+				"Line", "Line In",
+				"Line", "Line Out";
+
+			simple-audio-card,routing =
+				"Line Out","AOUTA+",
+				"Line Out","AOUTA-",
+				"Line Out","AOUTB+",
+				"Line Out","AOUTB-",
+				"AINA","Line In",
+				"AINB","Line In";
+
+			simple-audio-card,format = "i2s";
+
+			simple-audio-card,bitclock-master = <&sound_master>;
+			simple-audio-card,frame-master = <&sound_master>;
+
+			simple-audio-card,cpu {
+				sound-dai = <&i2s>;
+				dai-tdm-slot-num = <2>;
+				dai-tdm-slot-width = <32>;
+			};
+
+			sound_master: simple-audio-card,codec {
+				sound-dai = <&cs4271>;
+				system-clock-frequency = <24576000>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			cs4271: cs4271@10 {
+				#sound-dai-cells = <0>;
+				compatible = "cirrus,cs4271";
+				reg = <0x10>;
+				status = "okay";
+				reset-gpio = <&gpio 24 0>; /* Pin 26, active high */
+			};
+		};
+	};
+	__overrides__ {
+		gpiopin = <&cs4271>,"reset-gpio:4";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/interludeaudio-digital-overlay.dts b/arch/arm/boot/dts/overlays/interludeaudio-digital-overlay.dts
new file mode 100644
index 000000000000..24be00860310
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/interludeaudio-digital-overlay.dts
@@ -0,0 +1,49 @@
+// Definitions for Interlude Audio Digital Hat
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			wm8804@3b {
+				#sound-dai-cells = <0>;
+				compatible = "wlf,wm8804";
+				reg = <0x3b>;
+				PVDD-supply = <&vdd_3v3_reg>;
+				DVDD-supply = <&vdd_3v3_reg>;
+				status = "okay";
+			};
+		};
+	};
+
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "interludeaudio,interludeaudio-digital";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+			clock44-gpio = <&gpio 22 0>;
+			clock48-gpio = <&gpio 27 0>;
+			led1-gpio = <&gpio 13 0>;
+			led2-gpio = <&gpio 12 0>;
+			led3-gpio = <&gpio 6 0>;
+			reset-gpio = <&gpio 23 0>;
+		};
+	};
+
+};
diff --git a/arch/arm/boot/dts/overlays/iqaudio-codec-overlay.dts b/arch/arm/boot/dts/overlays/iqaudio-codec-overlay.dts
new file mode 100644
index 000000000000..bffff5a4d64c
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/iqaudio-codec-overlay.dts
@@ -0,0 +1,42 @@
+// Definitions for IQaudIO CODEC
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			da2713@1a {
+				#sound-dai-cells = <0>;
+				compatible = "dlg,da7213";
+				reg = <0x1a>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		iqaudio_dac: __overlay__ {
+			compatible = "iqaudio,iqaudio-codec";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/iqaudio-dac-overlay.dts b/arch/arm/boot/dts/overlays/iqaudio-dac-overlay.dts
new file mode 100644
index 000000000000..05d348f5e58a
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/iqaudio-dac-overlay.dts
@@ -0,0 +1,46 @@
+// Definitions for IQaudIO DAC
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			pcm5122@4c {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5122";
+				reg = <0x4c>;
+				AVDD-supply = <&vdd_3v3_reg>;
+				DVDD-supply = <&vdd_3v3_reg>;
+				CPVDD-supply = <&vdd_3v3_reg>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		frag2: __overlay__ {
+			compatible = "iqaudio,iqaudio-dac";
+			i2s-controller = <&i2s_clk_producer>;
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		24db_digital_gain = <&frag2>,"iqaudio,24db_digital_gain?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/iqaudio-dacplus-overlay.dts b/arch/arm/boot/dts/overlays/iqaudio-dacplus-overlay.dts
new file mode 100644
index 000000000000..3993580f7ac1
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/iqaudio-dacplus-overlay.dts
@@ -0,0 +1,49 @@
+// Definitions for IQaudIO DAC+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			pcm5122@4c {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5122";
+				reg = <0x4c>;
+				AVDD-supply = <&vdd_3v3_reg>;
+				DVDD-supply = <&vdd_3v3_reg>;
+				CPVDD-supply = <&vdd_3v3_reg>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		iqaudio_dac: __overlay__ {
+			compatible = "iqaudio,iqaudio-dac";
+			i2s-controller = <&i2s_clk_producer>;
+			mute-gpios = <&gpio 22 0>;
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		24db_digital_gain = <&iqaudio_dac>,"iqaudio,24db_digital_gain?";
+		auto_mute_amp = <&iqaudio_dac>,"iqaudio-dac,auto-mute-amp?";
+		unmute_amp = <&iqaudio_dac>,"iqaudio-dac,unmute-amp?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/iqaudio-digi-wm8804-audio-overlay.dts b/arch/arm/boot/dts/overlays/iqaudio-digi-wm8804-audio-overlay.dts
new file mode 100644
index 000000000000..f24faf11ecfa
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/iqaudio-digi-wm8804-audio-overlay.dts
@@ -0,0 +1,47 @@
+// Definitions for IQAudIO Digi WM8804 audio board
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			wm8804@3b {
+				#sound-dai-cells = <0>;
+				compatible = "wlf,wm8804";
+				reg = <0x3b>;
+				status = "okay";
+				DVDD-supply = <&vdd_3v3_reg>;
+				PVDD-supply = <&vdd_3v3_reg>;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		wm8804_digi: __overlay__ {
+			compatible = "iqaudio,wm8804-digi";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		card_name = <&wm8804_digi>,"wm8804-digi,card-name";
+		dai_name = <&wm8804_digi>,"wm8804-digi,dai-name";
+		dai_stream_name = <&wm8804_digi>,"wm8804-digi,dai-stream-name";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/iqs550-overlay.dts b/arch/arm/boot/dts/overlays/iqs550-overlay.dts
new file mode 100644
index 000000000000..c3956937055f
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/iqs550-overlay.dts
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// Definitions for Azoteq IQS550 trackpad/touchscreen controller
+/dts-v1/;
+/plugin/;
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			iqs550: iqs550@74 {
+				compatible = "azoteq,iqs550";
+				reg = <0x74>;
+				interrupt-parent = <&gpio>;
+				interrupts = <4 IRQ_TYPE_LEVEL_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&iqs550_pins>;
+				touchscreen-size-x = <800>;
+				touchscreen-size-y = <480>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&iqs550>;
+		iqs550_reset: __dormant__ {
+			reset-gpios = <&gpio 255 (GPIO_ACTIVE_LOW |
+						  GPIO_PUSH_PULL)>;
+		};
+	};
+
+	fragment@2 {
+		target = <&gpio>;
+		__overlay__ {
+			iqs550_pins: iqs550_pins {
+				brcm,pins = <4>;
+				brcm,pull = <1>;
+			};
+		};
+	};
+
+	__overrides__ {
+		interrupt = <&iqs550>,"interrupts:0",
+			    <&iqs550_pins>,"brcm,pins:0";
+		reset = <0>,"+1", <&iqs550_reset>,"reset-gpios:4";
+		sizex = <&iqs550>,"touchscreen-size-x:0";
+		sizey = <&iqs550>,"touchscreen-size-y:0";
+		invx = <&iqs550>,"touchscreen-inverted-x?";
+		invy = <&iqs550>,"touchscreen-inverted-y?";
+		swapxy = <&iqs550>,"touchscreen-swapped-x-y?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/irs1125-overlay.dts b/arch/arm/boot/dts/overlays/irs1125-overlay.dts
new file mode 100644
index 000000000000..07996247a7be
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/irs1125-overlay.dts
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for IRS1125 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	i2c_frag: fragment@0 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			irs1125: irs1125@3d {
+				compatible = "infineon,irs1125";
+				reg = <0x3d>;
+				status = "okay";
+
+				pwdn-gpios = <&gpio 5 0>;
+				clocks = <&cam1_clk>;
+
+				port {
+					irs1125_0: endpoint {
+						remote-endpoint = <&csi1_ep>;
+						clock-lanes = <0>;
+						data-lanes = <1 2>;
+						clock-noncontinuous;
+						link-frequencies =
+							/bits/ 64 <297000000>;
+					};
+				};
+			};
+		};
+	};
+
+	csi_frag: fragment@1 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+
+			port {
+				csi1_ep: endpoint {
+					remote-endpoint = <&irs1125_0>;
+					data-lanes = <1 2>;
+					clock-noncontinuous;
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@3 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@4 {
+		target-path="/__overrides__";
+		__overlay__ {
+			cam0-pwdn-ctrl = <&irs1125>,"pwdn-gpios:0";
+			cam0-pwdn      = <&irs1125>,"pwdn-gpios:4";
+		};
+	};
+
+	clk_frag: fragment@5 {
+		target = <&cam1_clk>;
+		__overlay__ {
+			status = "okay";
+			clock-frequency = <26000000>;
+		};
+	};
+
+	fragment@6 {
+		target = <&csi1>;
+		__overlay__ {
+			compatible = "brcm,bcm2835-unicam-legacy";
+		};
+	};
+
+	__overrides__ {
+		media-controller = <0>,"!6";
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+		       <&clk_frag>, "target:0=",<&cam0_clk>,
+		       <&irs1125>, "clocks:0=",<&cam0_clk>;
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/jedec-spi-nor-overlay.dts b/arch/arm/boot/dts/overlays/jedec-spi-nor-overlay.dts
new file mode 100644
index 000000000000..fb6d4bc91bf3
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/jedec-spi-nor-overlay.dts
@@ -0,0 +1,136 @@
+// Overlay for JEDEC SPI-NOR Flash Devices (aka m25p80)
+
+// dtparams:
+//     flash-spi<n>-<m>        - Enables flash device on SPI<n>, CS#<m>.
+//     flash-fastr-spi<n>-<m>  - Enables flash device with fast read capability on SPI<n>, CS#<m>.
+//     speed                   - Set the SPI clock speed in Hz
+//
+// If devices are present on SPI1 or SPI2, those interfaces must be enabled with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays.
+//
+// Example: A single flash device with fast read capability on SPI0, CS#0:
+// dtoverlay=jedec-spi-nor:flash-fastr-spi0-0
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	// disable spi-dev on spi0.0
+	fragment@0 {
+		target = <&spidev0>;
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	// disable spi-dev on spi0.1
+	fragment@1 {
+		target = <&spidev1>;
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	// disable spi-dev on spi1.0
+	fragment@2 {
+		target-path = "spi1/spidev@0";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	// disable spi-dev on spi1.1
+	fragment@3 {
+		target-path = "spi1/spidev@1";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	// disable spi-dev on spi1.2
+	fragment@4 {
+		target-path = "spi1/spidev@2";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	// disable spi-dev on spi2.0
+	fragment@5 {
+		target-path = "spi2/spidev@0";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	// disable spi-dev on spi2.1
+	fragment@6 {
+		target-path = "spi2/spidev@1";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	// disable spi-dev on spi2.2
+	fragment@7 {
+		target-path = "spi2/spidev@2";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	// Enable fast read for device
+	// Use default active low interrupt signalling.
+	fragment@8 {
+		target = <&spi_nor>;
+		__dormant__ {
+			m25p,fast-read;
+		};
+	};
+
+	payload: fragment@100 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			spi_nor: spi_nor@0 {
+				compatible = "jedec,spi-nor";
+				reg = <0>;
+				spi-max-frequency = <500000>;
+			};
+		};
+	};
+
+	__overrides__ {
+		spi0-0             = <0>,"+0", <&payload>,"target:0=",<&spi0>, <&spi_nor>,"reg:0=0";
+		spi0-1             = <0>,"+1", <&payload>,"target:0=",<&spi0>, <&spi_nor>,"reg:0=1";
+		spi1-0             = <0>,"+2", <&payload>,"target:0=",<&spi1>, <&spi_nor>,"reg:0=0";
+		spi1-1             = <0>,"+3", <&payload>,"target:0=",<&spi1>, <&spi_nor>,"reg:0=1";
+		spi1-2             = <0>,"+4", <&payload>,"target:0=",<&spi1>, <&spi_nor>,"reg:0=2";
+		spi2-0             = <0>,"+5", <&payload>,"target:0=",<&spi2>, <&spi_nor>,"reg:0=0";
+		spi2-1             = <0>,"+6", <&payload>,"target:0=",<&spi2>, <&spi_nor>,"reg:0=1";
+		spi2-2             = <0>,"+7", <&payload>,"target:0=",<&spi2>, <&spi_nor>,"reg:0=2";
+		flash-spi0-0       = <0>,"+0", <&payload>,"target:0=",<&spi0>, <&spi_nor>,"reg:0=0";
+		flash-spi0-1       = <0>,"+1", <&payload>,"target:0=",<&spi0>, <&spi_nor>,"reg:0=1";
+		flash-spi1-0       = <0>,"+2", <&payload>,"target:0=",<&spi1>, <&spi_nor>,"reg:0=0";
+		flash-spi1-1       = <0>,"+3", <&payload>,"target:0=",<&spi1>, <&spi_nor>,"reg:0=1";
+		flash-spi1-2       = <0>,"+4", <&payload>,"target:0=",<&spi1>, <&spi_nor>,"reg:0=2";
+		flash-spi2-0       = <0>,"+5", <&payload>,"target:0=",<&spi2>, <&spi_nor>,"reg:0=0";
+		flash-spi2-1       = <0>,"+6", <&payload>,"target:0=",<&spi2>, <&spi_nor>,"reg:0=1";
+		flash-spi2-2       = <0>,"+7", <&payload>,"target:0=",<&spi2>, <&spi_nor>,"reg:0=2";
+		flash-fastr-spi0-0 = <0>,"+0+8", <&payload>,"target:0=",<&spi0>, <&spi_nor>,"reg:0=0";
+		flash-fastr-spi0-1 = <0>,"+1+8", <&payload>,"target:0=",<&spi0>, <&spi_nor>,"reg:0=1";
+		flash-fastr-spi1-0 = <0>,"+2+8", <&payload>,"target:0=",<&spi1>, <&spi_nor>,"reg:0=0";
+		flash-fastr-spi1-1 = <0>,"+3+8", <&payload>,"target:0=",<&spi1>, <&spi_nor>,"reg:0=1";
+		flash-fastr-spi1-2 = <0>,"+4+8", <&payload>,"target:0=",<&spi1>, <&spi_nor>,"reg:0=2";
+		flash-fastr-spi2-0 = <0>,"+5+8", <&payload>,"target:0=",<&spi2>, <&spi_nor>,"reg:0=0";
+		flash-fastr-spi2-1 = <0>,"+6+8", <&payload>,"target:0=",<&spi2>, <&spi_nor>,"reg:0=1";
+		flash-fastr-spi2-2 = <0>,"+7+8", <&payload>,"target:0=",<&spi2>, <&spi_nor>,"reg:0=2";
+		fastr              = <0>,"+8";
+		speed              = <&spi_nor>, "spi-max-frequency:0";
+	};
+};
+
diff --git a/arch/arm/boot/dts/overlays/justboom-both-overlay.dts b/arch/arm/boot/dts/overlays/justboom-both-overlay.dts
new file mode 100644
index 000000000000..9185d668d1d5
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/justboom-both-overlay.dts
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0
+// Definitions for JustBoom Both (Digi+DAC)
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			wm8804@3b {
+				#sound-dai-cells = <0>;
+				compatible = "wlf,wm8804";
+				reg = <0x3b>;
+				PVDD-supply = <&vdd_3v3_reg>;
+				DVDD-supply = <&vdd_3v3_reg>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			pcm5122@4d {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5122";
+				reg = <0x4d>;
+				AVDD-supply = <&vdd_3v3_reg>;
+				DVDD-supply = <&vdd_3v3_reg>;
+				CPVDD-supply = <&vdd_3v3_reg>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&sound>;
+		frag3: __overlay__ {
+			compatible = "justboom,justboom-both";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		24db_digital_gain = <&frag3>,"justboom,24db_digital_gain?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/justboom-dac-overlay.dts b/arch/arm/boot/dts/overlays/justboom-dac-overlay.dts
new file mode 100644
index 000000000000..901a6aaba4bc
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/justboom-dac-overlay.dts
@@ -0,0 +1,46 @@
+// Definitions for JustBoom DAC
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			pcm5122@4d {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5122";
+				reg = <0x4d>;
+				AVDD-supply = <&vdd_3v3_reg>;
+				DVDD-supply = <&vdd_3v3_reg>;
+				CPVDD-supply = <&vdd_3v3_reg>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		frag2: __overlay__ {
+			compatible = "justboom,justboom-dac";
+			i2s-controller = <&i2s_clk_producer>;
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		24db_digital_gain = <&frag2>,"justboom,24db_digital_gain?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/justboom-digi-overlay.dts b/arch/arm/boot/dts/overlays/justboom-digi-overlay.dts
new file mode 100644
index 000000000000..c4c968200a4c
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/justboom-digi-overlay.dts
@@ -0,0 +1,41 @@
+// Definitions for JustBoom Digi
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			wm8804@3b {
+				#sound-dai-cells = <0>;
+				compatible = "wlf,wm8804";
+				reg = <0x3b>;
+				PVDD-supply = <&vdd_3v3_reg>;
+				DVDD-supply = <&vdd_3v3_reg>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "justboom,justboom-digi";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/ltc294x-overlay.dts b/arch/arm/boot/dts/overlays/ltc294x-overlay.dts
new file mode 100644
index 000000000000..6d971f3649ca
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ltc294x-overlay.dts
@@ -0,0 +1,86 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c_arm>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			ltc2941: ltc2941@64 {
+				compatible = "lltc,ltc2941";
+				reg = <0x64>;
+				lltc,resistor-sense = <50>;
+				lltc,prescaler-exponent = <7>; 
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c_arm>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			ltc2942: ltc2942@64 {
+				compatible = "lltc,ltc2942";
+				reg = <0x64>;
+				lltc,resistor-sense = <50>;
+				lltc,prescaler-exponent = <7>; 
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c_arm>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			ltc2943: ltc2943@64 {
+				compatible = "lltc,ltc2943";
+				reg = <0x64>;
+				lltc,resistor-sense = <50>;
+				lltc,prescaler-exponent = <7>; 
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&i2c_arm>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			ltc2944: ltc2944@64 {
+				compatible = "lltc,ltc2944";
+				reg = <0x64>;
+				lltc,resistor-sense = <50>;
+				lltc,prescaler-exponent = <7>; 
+			};
+		};
+	};
+
+	__overrides__ {
+		ltc2941 = <0>,"+0";
+		ltc2942 = <0>,"+1";
+		ltc2943 = <0>,"+2";
+		ltc2944 = <0>,"+3";
+		resistor-sense = <&ltc2941>, "lltc,resistor-sense:0",
+			         <&ltc2942>, "lltc,resistor-sense:0",
+				 <&ltc2943>, "lltc,resistor-sense:0",
+				 <&ltc2944>, "lltc,resistor-sense:0";
+		prescaler-exponent = <&ltc2941>, "lltc,prescaler-exponent:0",
+			         <&ltc2942>, "lltc,prescaler-exponent:0",
+				 <&ltc2943>, "lltc,prescaler-exponent:0",
+				 <&ltc2944>, "lltc,prescaler-exponent:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/max98357a-overlay.dts b/arch/arm/boot/dts/overlays/max98357a-overlay.dts
new file mode 100644
index 000000000000..263d071fe977
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/max98357a-overlay.dts
@@ -0,0 +1,84 @@
+// Overlay for Maxim MAX98357A audio DAC
+
+// dtparams:
+//     no-sdmode  - SD_MODE pin not managed by driver.
+//     sdmode-pin - Specify GPIO pin to which SD_MODE is connected (default 4).
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	/* Enable I2S */
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	/* DAC whose SD_MODE pin is managed by driver (via GPIO pin) */
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			max98357a_dac: max98357a {
+				compatible = "maxim,max98357a";
+				#sound-dai-cells = <0>;
+				sdmode-gpios = <&gpio 4 0>;   /* 2nd word overwritten by sdmode-pin parameter */
+				status = "okay";
+			};
+		};
+	};
+
+	/* DAC whose SD_MODE pin is not managed by driver */
+	fragment@2 {
+		target-path = "/";
+		__dormant__ {
+			max98357a_nsd: max98357a {
+				compatible = "maxim,max98357a";
+				#sound-dai-cells = <0>;
+				status = "okay";
+			};
+		};
+	};
+
+	/* Soundcard connecting I2S to DAC with SD_MODE */
+	fragment@3 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "simple-audio-card";
+			simple-audio-card,format = "i2s";
+			simple-audio-card,name = "MAX98357A";
+			status = "okay";
+			simple-audio-card,cpu {
+				sound-dai = <&i2s_clk_producer>;
+			};
+			simple-audio-card,codec {
+				sound-dai = <&max98357a_dac>;
+			};
+		};
+	};
+
+	/* Soundcard connecting I2S to DAC without SD_MODE */
+	fragment@4 {
+		target = <&sound>;
+		__dormant__ {
+			compatible = "simple-audio-card";
+			simple-audio-card,format = "i2s";
+			simple-audio-card,name = "MAX98357A";
+			status = "okay";
+			simple-audio-card,cpu {
+				sound-dai = <&i2s_clk_producer>;
+			};
+			simple-audio-card,codec {
+				sound-dai = <&max98357a_nsd>;
+			};
+		};
+	};
+
+	__overrides__ {
+		no-sdmode  = <0>,"-1+2-3+4";
+		sdmode-pin = <&max98357a_dac>,"sdmode-gpios:4";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/maxtherm-overlay.dts b/arch/arm/boot/dts/overlays/maxtherm-overlay.dts
new file mode 100644
index 000000000000..9964e246c14f
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/maxtherm-overlay.dts
@@ -0,0 +1,186 @@
+/*
+ * Universal device tree overlay for SPI devices
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/iio/temperature/thermocouple.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spidev0>;
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev1>;
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target-path = "spi1/spidev@0";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target-path = "spi1/spidev@1";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@4 {
+		target-path = "spi1/spidev@2";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@5 {
+		target-path = "spi2/spidev@0";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@6 {
+		target-path = "spi2/spidev@1";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@7 {
+		target-path = "spi2/spidev@2";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	maxfrag: fragment@8 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			max: maxtherm@0 {
+				compatible = "maxim,max6675";
+				reg = <0>;
+				spi-max-frequency = <500000>;
+			};
+		};
+	};
+
+	fragment@9 {
+		target = <&max>;
+		__dormant__ {
+			compatible = "maxim,max31855e", "maxim,max31855";
+		};
+	};
+
+	fragment@10 {
+		target = <&max>;
+		__dormant__ {
+			compatible = "maxim,max31855j", "maxim,max31855";
+		};
+	};
+
+	fragment@11 {
+		target = <&max>;
+		__dormant__ {
+			compatible = "maxim,max31855k", "maxim,max31855";
+		};
+	};
+
+	fragment@12 {
+		target = <&max>;
+		__dormant__ {
+			compatible = "maxim,max31855n", "maxim,max31855";
+		};
+	};
+
+	fragment@13 {
+		target = <&max>;
+		__dormant__ {
+			compatible = "maxim,max31855r", "maxim,max31855";
+		};
+	};
+
+	fragment@14 {
+		target = <&max>;
+		__dormant__ {
+			compatible = "maxim,max31855s", "maxim,max31855";
+		};
+	};
+
+	fragment@15 {
+		target = <&max>;
+		__dormant__ {
+			compatible = "maxim,max31855t", "maxim,max31855";
+		};
+	};
+
+	fragment@16 {
+		target = <&max>;
+		__dormant__ {
+			compatible = "maxim,max31856";
+			spi-cpha;
+			thermocouple-type = <THERMOCOUPLE_TYPE_K>;
+		};
+	};
+
+	__overrides__ {
+		spi0-0 = <0>, "+0",
+			 <&maxfrag>,"target:0=",<&spi0>,
+			 <&max>,"reg:0=0";
+		spi0-1 = <0>, "+1",
+			 <&maxfrag>,"target:0=",<&spi0>,
+			 <&max>,"reg:0=1";
+		spi1-0 = <0>, "+2",
+			 <&maxfrag>,"target:0=",<&spi1>,
+			 <&max>,"reg:0=0";
+		spi1-1 = <0>, "+3",
+			 <&maxfrag>,"target:0=",<&spi1>,
+			 <&max>,"reg:0=1";
+		spi1-2 = <0>, "+4",
+			 <&maxfrag>,"target:0=",<&spi1>,
+			 <&max>,"reg:0=2";
+		spi2-0 = <0>, "+5",
+			 <&maxfrag>,"target:0=",<&spi2>,
+			 <&max>,"reg:0=0";
+		spi2-1 = <0>, "+6",
+			 <&maxfrag>,"target:0=",<&spi2>,
+			 <&max>,"reg:0=1";
+		spi2-2 = <0>, "+7",
+			 <&maxfrag>,"target:0=",<&spi2>,
+			 <&max>,"reg:0=2";
+		max6675 = <&max>,"compatible=maxim,max6675";
+		max31855 = <&max>,"compatible=maxim,max31855";
+		max31855e = <0>,"+9";
+		max31855j = <0>,"+10";
+		max31855k = <0>,"+11";
+		max31855n = <0>,"+12";
+		max31855r = <0>,"+13";
+		max31855s = <0>,"+14";
+		max31855t = <0>,"+15";
+		max31856  = <0>,"+16";
+		type_b    = <&max>,"thermocouple-type:0=",<THERMOCOUPLE_TYPE_B>;
+		type_e    = <&max>,"thermocouple-type:0=",<THERMOCOUPLE_TYPE_E>;
+		type_j    = <&max>,"thermocouple-type:0=",<THERMOCOUPLE_TYPE_J>;
+		type_k    = <&max>,"thermocouple-type:0=",<THERMOCOUPLE_TYPE_K>;
+		type_n    = <&max>,"thermocouple-type:0=",<THERMOCOUPLE_TYPE_N>;
+		type_r    = <&max>,"thermocouple-type:0=",<THERMOCOUPLE_TYPE_R>;
+		type_s    = <&max>,"thermocouple-type:0=",<THERMOCOUPLE_TYPE_S>;
+		type_t    = <&max>,"thermocouple-type:0=",<THERMOCOUPLE_TYPE_T>;
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/mbed-dac-overlay.dts b/arch/arm/boot/dts/overlays/mbed-dac-overlay.dts
new file mode 100644
index 000000000000..e3f56608c643
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/mbed-dac-overlay.dts
@@ -0,0 +1,64 @@
+// Definitions for mbed DAC
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+                        #size-cells = <0>;
+			status = "okay";
+
+			tlv320aic23: codec@1a {
+				#sound-dai-cells = <0>;
+				reg = <0x1a>;
+				compatible = "ti,tlv320aic23";
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "simple-audio-card";
+			i2s-controller = <&i2s_clk_producer>;
+			status = "okay";
+
+			simple-audio-card,name = "mbed-DAC";
+
+			simple-audio-card,widgets =
+				"Microphone", "Mic Jack",
+				"Line", "Line In",
+				"Headphone", "Headphone Jack";
+
+			simple-audio-card,routing =
+				"Headphone Jack", "LHPOUT",
+				"Headphone Jack", "RHPOUT",
+				"LLINEIN", "Line In",
+				"RLINEIN", "Line In",
+				"MICIN", "Mic Jack";
+
+			simple-audio-card,format = "i2s";
+
+			simple-audio-card,cpu {
+				sound-dai = <&i2s_clk_producer>;
+			};
+
+			sound_master: simple-audio-card,codec {
+				sound-dai = <&tlv320aic23>;
+				system-clock-frequency = <12288000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/mcp23017-overlay.dts b/arch/arm/boot/dts/overlays/mcp23017-overlay.dts
new file mode 100644
index 000000000000..e23780b985a3
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/mcp23017-overlay.dts
@@ -0,0 +1,65 @@
+// Definitions for MCP23017 Gpio Extender from Microchip Semiconductor
+
+/dts-v1/;
+/plugin/;
+
+#include "i2c-buses.dtsi"
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			mcp23017_pins: mcp23017_pins@20 {
+				brcm,pins = <4>;
+				brcm,function = <0>;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&mcp23017>;
+		__dormant__ {
+			compatible = "microchip,mcp23008";
+		};
+	};
+
+	fragment@3 {
+		target = <&mcp23017>;
+		mcp23017_irq: __overlay__ {
+			#interrupt-cells=<2>;
+			pinctrl-names = "default";
+			pinctrl-0 = <&mcp23017_pins>;
+			interrupt-parent = <&gpio>;
+			interrupts = <4 2>;
+			interrupt-controller;
+			microchip,irq-mirror;
+		};
+	};
+
+	fragment@4 {
+		target = <&i2cbus>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mcp23017: mcp@20 {
+				compatible = "microchip,mcp23017";
+				reg = <0x20>;
+				gpio-controller;
+				#gpio-cells = <2>;
+
+				status = "okay";
+			};
+		};
+	};
+
+	__overrides__ {
+		gpiopin = <&mcp23017_pins>,"brcm,pins:0",
+				<&mcp23017_irq>,"interrupts:0";
+		addr = <&mcp23017>,"reg:0", <&mcp23017_pins>,"reg:0";
+		mcp23008 = <0>,"=2";
+		noints = <0>,"!1!3";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/mcp23s17-overlay.dts b/arch/arm/boot/dts/overlays/mcp23s17-overlay.dts
new file mode 100644
index 000000000000..d10e3ef5d446
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/mcp23s17-overlay.dts
@@ -0,0 +1,764 @@
+// Overlay for MCP23S08/17 GPIO Extenders from Microchip Semiconductor
+
+// dtparams:
+//     s08-spi<n>-<m>-present  - 4-bit integer, bitmap indicating MCP23S08 devices present on SPI<n>, CS#<m>.
+//     s17-spi<n>-<m>-present  - 8-bit integer, bitmap indicating MCP23S17 devices present on SPI<n>, CS#<m>.
+//     s08-spi<n>-<m>-int-gpio - integer, enables interrupts on a single MCP23S08 device on SPI<n>, CS#<m>, specifies the GPIO pin to which INT output is connected.
+//     s17-spi<n>-<m>-int-gpio - integer, enables mirrored interrupts on a single MCP23S17 device on SPI<n>, CS#<m>, specifies the GPIO pin to which either INTA or INTB output is connected.
+//
+// If devices are present on SPI1 or SPI2, those interfaces must be enabled with one of the spi1-1/2/3cs and/or spi2-1/2/3cs overlays.
+// If interrupts are enabled for a device on a given CS# on a SPI bus, that device must be the only one present on that SPI bus/CS#.
+//
+// Example 1: A single MCP23S17 device on SPI0, CS#0 with its SPI addr set to 0 and INTA output connected to GPIO25:
+// dtoverlay=mcp23s17:s17-spi0-0-present=1,s17-spi0-0-int-gpio=25
+//
+// Example 2: Two MCP23S08 devices on SPI1, CS#0 with their addrs set to 2 and 3. Three MCP23S17 devices on SPI1, CS#1 with their addrs set to 0, 1 and 7:
+// dtoverlay=spi1-2cs
+// dtoverlay=mcp23s17:s08-spi1-0-present=12,s17-spi1-1-present=131
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	// disable spi-dev on spi0.0
+	fragment@0 {
+		target = <&spidev0>;
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	// disable spi-dev on spi0.1
+	fragment@1 {
+		target = <&spidev1>;
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	// disable spi-dev on spi1.0
+	fragment@2 {
+		target-path = "spi1/spidev@0";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	// disable spi-dev on spi1.1
+	fragment@3 {
+		target-path = "spi1/spidev@1";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	// disable spi-dev on spi1.2
+	fragment@4 {
+		target-path = "spi1/spidev@2";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	// disable spi-dev on spi2.0
+	fragment@5 {
+		target-path = "spi2/spidev@0";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	// disable spi-dev on spi2.1
+	fragment@6 {
+		target-path = "spi2/spidev@1";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	// disable spi-dev on spi2.2
+	fragment@7 {
+		target-path = "spi2/spidev@2";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	// enable one or more mcp23s08s on spi0.0
+	fragment@8 {
+		target = <&spi0>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp23s08_00: mcp23s08@0 {
+				compatible = "microchip,mcp23s08";
+				gpio-controller;
+				#gpio-cells = <2>;
+				microchip,spi-present-mask = <0x00>;  /* overwritten by mcp23s08-spi0-0-present parameter */
+				reg = <0>;
+				spi-max-frequency = <500000>;
+				status = "okay";
+				#interrupt-cells=<2>;
+				interrupts = <0 2>;  /* 1st word overwritten by mcp23s08-spi0-0-int-gpio parameter */
+				pinctrl-0 = <&spi0_0_int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	// enable one or more mcp23s08s on spi0.1
+	fragment@9 {
+		target = <&spi0>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp23s08_01: mcp23s08@1 {
+				compatible = "microchip,mcp23s08";
+				gpio-controller;
+				#gpio-cells = <2>;
+				microchip,spi-present-mask = <0x00>;  /* overwritten by mcp23s08-spi0-1-present parameter */
+				reg = <1>;
+				spi-max-frequency = <500000>;
+				status = "okay";
+				#interrupt-cells=<2>;
+				interrupts = <0 2>;  /* 1st word overwritten by mcp23s08-spi0-1-int-gpio parameter */
+				pinctrl-0 = <&spi0_1_int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	// enable one or more mcp23s08s on spi1.0
+	fragment@10 {
+		target = <&spi1>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp23s08_10: mcp23s08@0 {
+				compatible = "microchip,mcp23s08";
+				gpio-controller;
+				#gpio-cells = <2>;
+				microchip,spi-present-mask = <0x00>;  /* overwritten by mcp23s08-spi1-0-present parameter */
+				reg = <0>;
+				spi-max-frequency = <500000>;
+				status = "okay";
+				#interrupt-cells=<2>;
+				interrupts = <0 2>;  /* 1st word overwritten by mcp23s08-spi1-0-int-gpio parameter */
+				pinctrl-0 = <&spi1_0_int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	// enable one or more mcp23s08s on spi1.1
+	fragment@11 {
+		target = <&spi1>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp23s08_11: mcp23s08@1 {
+				compatible = "microchip,mcp23s08";
+				gpio-controller;
+				#gpio-cells = <2>;
+				microchip,spi-present-mask = <0x00>;  /* overwritten by mcp23s08-spi1-1-present parameter */
+				reg = <1>;
+				spi-max-frequency = <500000>;
+				status = "okay";
+				#interrupt-cells=<2>;
+				interrupts = <0 2>;  /* 1st word overwritten by mcp23s08-spi1-1-int-gpio parameter */
+				pinctrl-0 = <&spi1_1_int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	// enable one or more mcp23s08s on spi1.2
+	fragment@12 {
+		target = <&spi1>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp23s08_12: mcp23s08@2 {
+				compatible = "microchip,mcp23s08";
+				gpio-controller;
+				#gpio-cells = <2>;
+				microchip,spi-present-mask = <0x00>;  /* overwritten by mcp23s08-spi1-2-present parameter */
+				reg = <2>;
+				spi-max-frequency = <500000>;
+				status = "okay";
+				#interrupt-cells=<2>;
+				interrupts = <0 2>;  /* 1st word overwritten by mcp23s08-spi1-2-int-gpio parameter */
+				pinctrl-0 = <&spi1_2_int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	// enable one or more mcp23s08s on spi2.0
+	fragment@13 {
+		target = <&spi2>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp23s08_20: mcp23s08@0 {
+				compatible = "microchip,mcp23s08";
+				gpio-controller;
+				#gpio-cells = <2>;
+				microchip,spi-present-mask = <0x00>;  /* overwritten by mcp23s08-spi2-0-present parameter */
+				reg = <0>;
+				spi-max-frequency = <500000>;
+				status = "okay";
+				#interrupt-cells=<2>;
+				interrupts = <0 2>;  /* 1st word overwritten by mcp23s08-spi2-0-int-gpio parameter */
+				pinctrl-0 = <&spi2_0_int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	// enable one or more mcp23s08s on spi2.1
+	fragment@14 {
+		target = <&spi2>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp23s08_21: mcp23s08@1 {
+				compatible = "microchip,mcp23s08";
+				gpio-controller;
+				#gpio-cells = <2>;
+				microchip,spi-present-mask = <0x00>;  /* overwritten by mcp23s08-spi2-1-present parameter */
+				reg = <1>;
+				spi-max-frequency = <500000>;
+				status = "okay";
+				#interrupt-cells=<2>;
+				interrupts = <0 2>;  /* 1st word overwritten by mcp23s08-spi2-1-int-gpio parameter */
+				pinctrl-0 = <&spi2_1_int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	// enable one or more mcp23s08s on spi2.2
+	fragment@15 {
+		target = <&spi2>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp23s08_22: mcp23s08@2 {
+				compatible = "microchip,mcp23s08";
+				gpio-controller;
+				#gpio-cells = <2>;
+				microchip,spi-present-mask = <0x00>;  /* overwritten by mcp23s08-spi2-2-present parameter */
+				reg = <2>;
+				spi-max-frequency = <500000>;
+				status = "okay";
+				#interrupt-cells=<2>;
+				interrupts = <0 2>;  /* 1st word overwritten by mcp23s08-spi2-2-int-gpio parameter */
+				pinctrl-0 = <&spi2_2_int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	// enable one or more mcp23s17s on spi0.0
+	fragment@16 {
+		target = <&spi0>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp23s17_00: mcp23s17@0 {
+				compatible = "microchip,mcp23s17";
+				gpio-controller;
+				#gpio-cells = <2>;
+				microchip,spi-present-mask = <0x00>;  /* overwritten by mcp23s17-spi0-0-present parameter */
+				reg = <0>;
+				spi-max-frequency = <500000>;
+				status = "okay";
+				#interrupt-cells=<2>;
+				interrupts = <0 2>;  /* 1st word overwritten by mcp23s17-spi0-0-int-gpio parameter */
+				pinctrl-0 = <&spi0_0_int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	// enable one or more mcp23s17s on spi0.1
+	fragment@17 {
+		target = <&spi0>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp23s17_01: mcp23s17@1 {
+				compatible = "microchip,mcp23s17";
+				gpio-controller;
+				#gpio-cells = <2>;
+				microchip,spi-present-mask = <0x00>;  /* overwritten by mcp23s17-spi0-1-present parameter */
+				reg = <1>;
+				spi-max-frequency = <500000>;
+				status = "okay";
+				#interrupt-cells=<2>;
+				interrupts = <0 2>;  /* 1st word overwritten by mcp23s17-spi0-1-int-gpio parameter */
+				pinctrl-0 = <&spi0_1_int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	// enable one or more mcp23s17s on spi1.0
+	fragment@18 {
+		target = <&spi1>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp23s17_10: mcp23s17@0 {
+				compatible = "microchip,mcp23s17";
+				gpio-controller;
+				#gpio-cells = <2>;
+				microchip,spi-present-mask = <0x00>;  /* overwritten by mcp23s17-spi1-0-present parameter */
+				reg = <0>;
+				spi-max-frequency = <500000>;
+				status = "okay";
+				#interrupt-cells=<2>;
+				interrupts = <0 2>;  /* 1st word overwritten by mcp23s17-spi1-0-int-gpio parameter */
+				pinctrl-0 = <&spi1_0_int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	// enable one or more mcp23s17s on spi1.1
+	fragment@19 {
+		target = <&spi1>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp23s17_11: mcp23s17@1 {
+				compatible = "microchip,mcp23s17";
+				gpio-controller;
+				#gpio-cells = <2>;
+				microchip,spi-present-mask = <0x00>;  /* overwritten by mcp23s17-spi1-1-present parameter */
+				reg = <1>;
+				spi-max-frequency = <500000>;
+				status = "okay";
+				#interrupt-cells=<2>;
+				interrupts = <0 2>;  /* 1st word overwritten by mcp23s17-spi1-1-int-gpio parameter */
+				pinctrl-0 = <&spi1_1_int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	// enable one or more mcp23s17s on spi1.2
+	fragment@20 {
+		target = <&spi1>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp23s17_12: mcp23s17@2 {
+				compatible = "microchip,mcp23s17";
+				gpio-controller;
+				#gpio-cells = <2>;
+				microchip,spi-present-mask = <0x00>;  /* overwritten by mcp23s17-spi1-2-present parameter */
+				reg = <2>;
+				spi-max-frequency = <500000>;
+				status = "okay";
+				#interrupt-cells=<2>;
+				interrupts = <0 2>;  /* 1st word overwritten by mcp23s17-spi1-2-int-gpio parameter */
+				pinctrl-0 = <&spi1_2_int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	// enable one or more mcp23s17s on spi2.0
+	fragment@21 {
+		target = <&spi2>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp23s17_20: mcp23s17@0 {
+				compatible = "microchip,mcp23s17";
+				gpio-controller;
+				#gpio-cells = <2>;
+				microchip,spi-present-mask = <0x00>;  /* overwritten by mcp23s17-spi2-0-present parameter */
+				reg = <0>;
+				spi-max-frequency = <500000>;
+				status = "okay";
+				#interrupt-cells=<2>;
+				interrupts = <0 2>;  /* 1st word overwritten by mcp23s17-spi2-0-int-gpio parameter */
+				pinctrl-0 = <&spi2_0_int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	// enable one or more mcp23s17s on spi2.1
+	fragment@22 {
+		target = <&spi2>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp23s17_21: mcp23s17@1 {
+				compatible = "microchip,mcp23s17";
+				gpio-controller;
+				#gpio-cells = <2>;
+				microchip,spi-present-mask = <0x00>;  /* overwritten by mcp23s17-spi2-1-present parameter */
+				reg = <1>;
+				spi-max-frequency = <500000>;
+				status = "okay";
+				#interrupt-cells=<2>;
+				interrupts = <0 2>;  /* 1st word overwritten by mcp23s17-spi2-1-int-gpio parameter */
+				pinctrl-0 = <&spi2_1_int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	// enable one or more mcp23s17s on spi2.2
+	fragment@23 {
+		target = <&spi2>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp23s17_22: mcp23s17@2 {
+				compatible = "microchip,mcp23s17";
+				gpio-controller;
+				#gpio-cells = <2>;
+				microchip,spi-present-mask = <0x00>;  /* overwritten by mcp23s17-spi2-2-present parameter */
+				reg = <2>;
+				spi-max-frequency = <500000>;
+				status = "okay";
+				#interrupt-cells=<2>;
+				interrupts = <0 2>;  /* 1st word overwritten by mcp23s17-spi2-2-int-gpio parameter */
+				pinctrl-0 = <&spi2_2_int_pins>;
+				pinctrl-names = "default";
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to INT(A/B) output of mcp23s08/17 on spi0.0 as a input with no pull-up/down
+	fragment@24 {
+		target = <&gpio>;
+		__dormant__ {
+			spi0_0_int_pins: spi0_0_int_pins {
+				brcm,pins = <0>;  /* overwritten by mcp23s08/17-spi0-0-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to INT(A/B) output of mcp23s08/17 on spi0.1 as a input with no pull-up/down
+	fragment@25 {
+		target = <&gpio>;
+		__dormant__ {
+			spi0_1_int_pins: spi0_1_int_pins {
+				brcm,pins = <0>;  /* overwritten by mcp23s08/17-spi0-1-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to INT(A/B) output of mcp23s08/17 on spi1.0 as a input with no pull-up/down
+	fragment@26 {
+		target = <&gpio>;
+		__dormant__ {
+			spi1_0_int_pins: spi1_0_int_pins {
+				brcm,pins = <0>;  /* overwritten by mcp23s08/17-spi1-0-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to INT(A/B) output of mcp23s08/17 on spi1.1 as a input with no pull-up/down
+	fragment@27 {
+		target = <&gpio>;
+		__dormant__ {
+			spi1_1_int_pins: spi1_1_int_pins {
+				brcm,pins = <0>;  /* overwritten by mcp23s08/17-spi1-1-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to INT(A/B) output of mcp23s08/17 on spi1.2 as a input with no pull-up/down
+	fragment@28 {
+		target = <&gpio>;
+		__dormant__ {
+			spi1_2_int_pins: spi1_2_int_pins {
+				brcm,pins = <0>;  /* overwritten by mcp23s08/17-spi1-2-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to INT(A/B) output of mcp23s08/17 on spi2.0 as a input with no pull-up/down
+	fragment@29 {
+		target = <&gpio>;
+		__dormant__ {
+			spi2_0_int_pins: spi2_0_int_pins {
+				brcm,pins = <0>;  /* overwritten by mcp23s08/17-spi2-0-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to INT(A/B) output of mcp23s08/17 on spi2.1 as a input with no pull-up/down
+	fragment@30 {
+		target = <&gpio>;
+		__dormant__ {
+			spi2_1_int_pins: spi2_1_int_pins {
+				brcm,pins = <0>;  /* overwritten by mcp23s08/17-spi2-1-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to INT(A/B) output of mcp23s08/17 on spi2.2 as a input with no pull-up/down
+	fragment@31 {
+		target = <&gpio>;
+		__dormant__ {
+			spi2_2_int_pins: spi2_2_int_pins {
+				brcm,pins = <0>;  /* overwritten by mcp23s08/17-spi2-2-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Enable interrupts for a mcp23s08 on spi0.0.
+	// Use default active low interrupt signalling.
+	fragment@32 {
+		target = <&mcp23s08_00>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+		};
+	};
+
+	// Enable interrupts for a mcp23s08 on spi0.1.
+	// Use default active low interrupt signalling.
+	fragment@33 {
+		target = <&mcp23s08_01>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+		};
+	};
+
+	// Enable interrupts for a mcp23s08 on spi1.0.
+	// Use default active low interrupt signalling.
+	fragment@34 {
+		target = <&mcp23s08_10>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+		};
+	};
+
+	// Enable interrupts for a mcp23s08 on spi1.1.
+	// Use default active low interrupt signalling.
+	fragment@35 {
+		target = <&mcp23s08_11>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+		};
+	};
+
+	// Enable interrupts for a mcp23s08 on spi1.2.
+	// Use default active low interrupt signalling.
+	fragment@36 {
+		target = <&mcp23s08_12>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+		};
+	};
+
+	// Enable interrupts for a mcp23s08 on spi2.0.
+	// Use default active low interrupt signalling.
+	fragment@37 {
+		target = <&mcp23s08_20>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+		};
+	};
+
+	// Enable interrupts for a mcp23s08 on spi2.1.
+	// Use default active low interrupt signalling.
+	fragment@38 {
+		target = <&mcp23s08_21>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+		};
+	};
+
+	// Enable interrupts for a mcp23s08 on spi2.2.
+	// Use default active low interrupt signalling.
+	fragment@39 {
+		target = <&mcp23s08_22>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+		};
+	};
+
+	// Enable interrupts for a mcp23s17 on spi0.0.
+	// Enable mirroring so that either INTA or INTB output of mcp23s17 can be connected to the GPIO pin.
+	// Use default active low interrupt signalling.
+	fragment@40 {
+		target = <&mcp23s17_00>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			microchip,irq-mirror;
+		};
+	};
+
+	// Enable interrupts for a mcp23s17 on spi0.1.
+	// Enable mirroring so that either INTA or INTB output of mcp23s17 can be connected to the GPIO pin.
+	// Configure INTA/B outputs of mcp23s08/17 as active low.
+	fragment@41 {
+		target = <&mcp23s17_01>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			microchip,irq-mirror;
+		};
+	};
+
+	// Enable interrupts for a mcp23s17 on spi1.0.
+	// Enable mirroring so that either INTA or INTB output of mcp23s17 can be connected to the GPIO pin.
+	// Configure INTA/B outputs of mcp23s08/17 as active low.
+	fragment@42 {
+		target = <&mcp23s17_10>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			microchip,irq-mirror;
+		};
+	};
+
+	// Enable interrupts for a mcp23s17 on spi1.1.
+	// Enable mirroring so that either INTA or INTB output of mcp23s17 can be connected to the GPIO pin.
+	// Configure INTA/B outputs of mcp23s08/17 as active low.
+	fragment@43 {
+		target = <&mcp23s17_11>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			microchip,irq-mirror;
+		};
+	};
+
+	// Enable interrupts for a mcp23s17 on spi1.2.
+	// Enable mirroring so that either INTA or INTB output of mcp23s17 can be connected to the GPIO pin.
+	// Configure INTA/B outputs of mcp23s08/17 as active low.
+	fragment@44 {
+		target = <&mcp23s17_12>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			microchip,irq-mirror;
+		};
+	};
+
+	// Enable interrupts for a mcp23s17 on spi2.0.
+	// Enable mirroring so that either INTA or INTB output of mcp23s17 can be connected to the GPIO pin.
+	// Configure INTA/B outputs of mcp23s08/17 as active low.
+	fragment@45 {
+		target = <&mcp23s17_20>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			microchip,irq-mirror;
+		};
+	};
+
+	// Enable interrupts for a mcp23s17 on spi2.1.
+	// Enable mirroring so that either INTA or INTB output of mcp23s17 can be connected to the GPIO pin.
+	// Configure INTA/B outputs of mcp23s08/17 as active low.
+	fragment@46 {
+		target = <&mcp23s17_21>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			microchip,irq-mirror;
+		};
+	};
+
+	// Enable interrupts for a mcp23s17 on spi2.2.
+	// Enable mirroring so that either INTA or INTB output of mcp23s17 can be connected to the GPIO pin.
+	// Configure INTA/B outputs of mcp23s08/17 as active low.
+	fragment@47 {
+		target = <&mcp23s17_22>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			microchip,irq-mirror;
+		};
+	};
+
+	__overrides__ {
+		s08-spi0-0-present = <0>,"+0+8",  <&mcp23s08_00>,"microchip,spi-present-mask:0";
+		s08-spi0-1-present = <0>,"+1+9",  <&mcp23s08_01>,"microchip,spi-present-mask:0";
+		s08-spi1-0-present = <0>,"+2+10", <&mcp23s08_10>,"microchip,spi-present-mask:0";
+		s08-spi1-1-present = <0>,"+3+11", <&mcp23s08_11>,"microchip,spi-present-mask:0";
+		s08-spi1-2-present = <0>,"+4+12", <&mcp23s08_12>,"microchip,spi-present-mask:0";
+		s08-spi2-0-present = <0>,"+5+13", <&mcp23s08_20>,"microchip,spi-present-mask:0";
+		s08-spi2-1-present = <0>,"+6+14", <&mcp23s08_21>,"microchip,spi-present-mask:0";
+		s08-spi2-2-present = <0>,"+7+15", <&mcp23s08_22>,"microchip,spi-present-mask:0";
+		s17-spi0-0-present = <0>,"+0+16", <&mcp23s17_00>,"microchip,spi-present-mask:0";
+		s17-spi0-1-present = <0>,"+1+17", <&mcp23s17_01>,"microchip,spi-present-mask:0";
+		s17-spi1-0-present = <0>,"+2+18", <&mcp23s17_10>,"microchip,spi-present-mask:0";
+		s17-spi1-1-present = <0>,"+3+19", <&mcp23s17_11>,"microchip,spi-present-mask:0";
+		s17-spi1-2-present = <0>,"+4+20", <&mcp23s17_12>,"microchip,spi-present-mask:0";
+		s17-spi2-0-present = <0>,"+5+21", <&mcp23s17_20>,"microchip,spi-present-mask:0";
+		s17-spi2-1-present = <0>,"+6+22", <&mcp23s17_21>,"microchip,spi-present-mask:0";
+		s17-spi2-2-present = <0>,"+7+23", <&mcp23s17_22>,"microchip,spi-present-mask:0";
+		s08-spi0-0-int-gpio = <0>,"+24+32", <&spi0_0_int_pins>,"brcm,pins:0", <&mcp23s08_00>,"interrupts:0";
+		s08-spi0-1-int-gpio = <0>,"+25+33", <&spi0_1_int_pins>,"brcm,pins:0", <&mcp23s08_01>,"interrupts:0";
+		s08-spi1-0-int-gpio = <0>,"+26+34", <&spi1_0_int_pins>,"brcm,pins:0", <&mcp23s08_10>,"interrupts:0";
+		s08-spi1-1-int-gpio = <0>,"+27+35", <&spi1_1_int_pins>,"brcm,pins:0", <&mcp23s08_11>,"interrupts:0";
+		s08-spi1-2-int-gpio = <0>,"+28+36", <&spi1_2_int_pins>,"brcm,pins:0", <&mcp23s08_12>,"interrupts:0";
+		s08-spi2-0-int-gpio = <0>,"+29+37", <&spi2_0_int_pins>,"brcm,pins:0", <&mcp23s08_20>,"interrupts:0";
+		s08-spi2-1-int-gpio = <0>,"+30+38", <&spi2_1_int_pins>,"brcm,pins:0", <&mcp23s08_21>,"interrupts:0";
+		s08-spi2-2-int-gpio = <0>,"+31+39", <&spi2_2_int_pins>,"brcm,pins:0", <&mcp23s08_22>,"interrupts:0";
+		s17-spi0-0-int-gpio = <0>,"+24+40", <&spi0_0_int_pins>,"brcm,pins:0", <&mcp23s17_00>,"interrupts:0";
+		s17-spi0-1-int-gpio = <0>,"+25+41", <&spi0_1_int_pins>,"brcm,pins:0", <&mcp23s17_01>,"interrupts:0";
+		s17-spi1-0-int-gpio = <0>,"+26+42", <&spi1_0_int_pins>,"brcm,pins:0", <&mcp23s17_10>,"interrupts:0";
+		s17-spi1-1-int-gpio = <0>,"+27+43", <&spi1_1_int_pins>,"brcm,pins:0", <&mcp23s17_11>,"interrupts:0";
+		s17-spi1-2-int-gpio = <0>,"+28+44", <&spi1_2_int_pins>,"brcm,pins:0", <&mcp23s17_12>,"interrupts:0";
+		s17-spi2-0-int-gpio = <0>,"+29+45", <&spi2_0_int_pins>,"brcm,pins:0", <&mcp23s17_20>,"interrupts:0";
+		s17-spi2-1-int-gpio = <0>,"+30+46", <&spi2_1_int_pins>,"brcm,pins:0", <&mcp23s17_21>,"interrupts:0";
+		s17-spi2-2-int-gpio = <0>,"+31+47", <&spi2_2_int_pins>,"brcm,pins:0", <&mcp23s17_22>,"interrupts:0";
+	};
+};
+
diff --git a/arch/arm/boot/dts/overlays/mcp2515-can0-overlay.dts b/arch/arm/boot/dts/overlays/mcp2515-can0-overlay.dts
new file mode 100755
index 000000000000..46f143d809cc
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/mcp2515-can0-overlay.dts
@@ -0,0 +1,73 @@
+/*
+ * Device tree overlay for mcp251x/can0 on spi0.0
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+    compatible = "brcm,bcm2835";
+    /* disable spi-dev for spi0.0 */
+    fragment@0 {
+        target = <&spi0>;
+        __overlay__ {
+            status = "okay";
+        };
+    };
+
+    fragment@1 {
+	target = <&spidev0>;
+	__overlay__ {
+	    status = "disabled";
+	};
+    };
+
+    /* the interrupt pin of the can-controller */
+    fragment@2 {
+        target = <&gpio>;
+        __overlay__ {
+            can0_pins: can0_pins {
+                brcm,pins = <25>;
+                brcm,function = <0>; /* input */
+            };
+        };
+    };
+
+    /* the clock/oscillator of the can-controller */
+    fragment@3 {
+        target-path = "/";
+        __overlay__ {
+            /* external oscillator of mcp2515 on SPI0.0 */
+            can0_osc: can0_osc {
+                compatible = "fixed-clock";
+                #clock-cells = <0>;
+                clock-frequency  = <16000000>;
+            };
+        };
+    };
+
+    /* the spi config of the can-controller itself binding everything together */
+    fragment@4 {
+        target = <&spi0>;
+        __overlay__ {
+            /* needed to avoid dtc warning */
+            #address-cells = <1>;
+            #size-cells = <0>;
+            can0: mcp2515@0 {
+                reg = <0>;
+                compatible = "microchip,mcp2515";
+                pinctrl-names = "default";
+                pinctrl-0 = <&can0_pins>;
+                spi-max-frequency = <10000000>;
+                interrupt-parent = <&gpio>;
+                interrupts = <25 8>; /* IRQ_TYPE_LEVEL_LOW */
+                clocks = <&can0_osc>;
+            };
+        };
+    };
+    __overrides__ {
+        oscillator = <&can0_osc>,"clock-frequency:0";
+        spimaxfrequency = <&can0>,"spi-max-frequency:0";
+        interrupt = <&can0_pins>,"brcm,pins:0",<&can0>,"interrupts:0";
+    };
+};
diff --git a/arch/arm/boot/dts/overlays/mcp2515-can1-overlay.dts b/arch/arm/boot/dts/overlays/mcp2515-can1-overlay.dts
new file mode 100644
index 000000000000..0a8dd576818e
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/mcp2515-can1-overlay.dts
@@ -0,0 +1,73 @@
+/*
+ * Device tree overlay for mcp251x/can1 on spi0.1 edited by petit_miner
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+    compatible = "brcm,bcm2835";
+    /* disable spi-dev for spi0.1 */
+    fragment@0 {
+        target = <&spi0>;
+        __overlay__ {
+            status = "okay";
+        };
+    };
+
+    fragment@1 {
+	target = <&spidev1>;
+	__overlay__ {
+	    status = "disabled";
+	};
+    };
+
+    /* the interrupt pin of the can-controller */
+    fragment@2 {
+        target = <&gpio>;
+        __overlay__ {
+            can1_pins: can1_pins {
+                brcm,pins = <25>;
+                brcm,function = <0>; /* input */
+            };
+        };
+    };
+
+    /* the clock/oscillator of the can-controller */
+    fragment@3 {
+        target-path = "/";
+        __overlay__ {
+            /* external oscillator of mcp2515 on spi0.1 */
+            can1_osc: can1_osc {
+                compatible = "fixed-clock";
+                #clock-cells = <0>;
+                clock-frequency  = <16000000>;
+            };
+        };
+    };
+
+    /* the spi config of the can-controller itself binding everything together */
+    fragment@4 {
+        target = <&spi0>;
+        __overlay__ {
+            /* needed to avoid dtc warning */
+            #address-cells = <1>;
+            #size-cells = <0>;
+            can1: mcp2515@1 {
+                reg = <1>;
+                compatible = "microchip,mcp2515";
+                pinctrl-names = "default";
+                pinctrl-0 = <&can1_pins>;
+                spi-max-frequency = <10000000>;
+                interrupt-parent = <&gpio>;
+                interrupts = <25 8>; /* IRQ_TYPE_LEVEL_LOW */
+                clocks = <&can1_osc>;
+            };
+        };
+    };
+    __overrides__ {
+        oscillator = <&can1_osc>,"clock-frequency:0";
+        spimaxfrequency = <&can1>,"spi-max-frequency:0";
+        interrupt = <&can1_pins>,"brcm,pins:0",<&can1>,"interrupts:0";
+    };
+};
diff --git a/arch/arm/boot/dts/overlays/mcp2515-overlay.dts b/arch/arm/boot/dts/overlays/mcp2515-overlay.dts
new file mode 100644
index 000000000000..36b5c4688871
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/mcp2515-overlay.dts
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/pinctrl/bcm2835.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spidev0>;
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev1>;
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target-path = "spi1/spidev@0";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target-path = "spi1/spidev@1";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@4 {
+		target-path = "spi1/spidev@2";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@5 {
+		target-path = "spi2/spidev@0";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@6 {
+		target-path = "spi2/spidev@1";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@7 {
+		target-path = "spi2/spidev@2";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@8 {
+		target-path = "spi3/spidev@0";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@9 {
+		target-path = "spi3/spidev@1";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@10 {
+		target-path = "spi5/spidev@0";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@11 {
+		target-path = "spi5/spidev@1";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@12 {
+		target = <&gpio>;
+		__overlay__ {
+			mcp2515_pins: mcp2515_pins {
+				brcm,pins = <25>;
+				brcm,function = <BCM2835_FSEL_GPIO_IN>;
+			};
+		};
+	};
+
+	fragment@13 {
+		target-path = "/clocks";
+		__overlay__ {
+			clk_mcp2515_osc: mcp2515-osc {
+				#clock-cells = <0>;
+				compatible = "fixed-clock";
+				clock-frequency = <16000000>;
+			};
+		};
+	};
+
+	mcp2515_frag: fragment@14 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mcp2515: mcp2515@0 {
+				compatible = "microchip,mcp2515";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&mcp2515_pins>;
+				spi-max-frequency = <10000000>;
+				interrupt-parent = <&gpio>;
+				interrupts = <25 IRQ_TYPE_LEVEL_LOW>;
+				clocks = <&clk_mcp2515_osc>;
+			};
+		};
+	};
+
+	__overrides__ {
+		spi0-0 = <0>, "+0",
+			<&mcp2515_frag>, "target:0=", <&spi0>,
+			<&mcp2515>, "reg:0=0",
+			<&mcp2515_pins>, "name=mcp2515_spi0_0_pins",
+			<&clk_mcp2515_osc>, "name=mcp2515-spi0-0-osc";
+		spi0-1 = <0>, "+1",
+			<&mcp2515_frag>, "target:0=", <&spi0>,
+			<&mcp2515>, "reg:0=1",
+			<&mcp2515_pins>, "name=mcp2515_spi0_1_pins",
+			<&clk_mcp2515_osc>, "name=mcp2515-spi0-1-osc";
+
+		spi1-0 = <0>, "+2",
+			<&mcp2515_frag>, "target:0=", <&spi1>,
+			<&mcp2515>, "reg:0=0",
+			<&mcp2515_pins>, "name=mcp2515_spi1_0_pins",
+			<&clk_mcp2515_osc>, "name=mcp2515-spi1-0-osc";
+		spi1-1 = <0>, "+3",
+			<&mcp2515_frag>, "target:0=", <&spi1>,
+			<&mcp2515>, "reg:0=1",
+			<&mcp2515_pins>, "name=mcp2515_spi1_1_pins",
+			<&clk_mcp2515_osc>, "name=mcp2515-spi1-1-osc";
+		spi1-2 = <0>, "+4",
+			<&mcp2515_frag>, "target:0=", <&spi1>,
+			<&mcp2515>, "reg:0=2",
+			<&mcp2515_pins>, "name=mcp2515_spi1_2_pins",
+			<&clk_mcp2515_osc>, "name=mcp2515-spi1-2-osc";
+
+		spi2-0 = <0>, "+5",
+			<&mcp2515_frag>, "target:0=", <&spi2>,
+			<&mcp2515>, "reg:0=0",
+			<&mcp2515_pins>, "name=mcp2515_spi2_0_pins",
+			<&clk_mcp2515_osc>, "name=mcp2515-spi2-0-osc";
+		spi2-1 = <0>, "+6",
+			<&mcp2515_frag>, "target:0=", <&spi2>,
+			<&mcp2515>, "reg:0=1",
+			<&mcp2515_pins>, "name=mcp2515_spi2_1_pins",
+			<&clk_mcp2515_osc>, "name=mcp2515-spi2-1-osc";
+		spi2-2 = <0>, "+7",
+			<&mcp2515_frag>, "target:0=", <&spi2>,
+			<&mcp2515>, "reg:0=2",
+			<&mcp2515_pins>, "name=mcp2515_spi2_2_pins",
+			<&clk_mcp2515_osc>, "name=mcp2515-spi2-2-osc";
+
+		spi3-0 = <0>, "+8",
+			<&mcp2515_frag>, "target?=0",
+			<&mcp2515_frag>, "target-path=spi3",
+			<&mcp2515>, "reg:0=0",
+			<&mcp2515_pins>, "name=mcp2515_spi3_0_pins",
+			<&clk_mcp2515_osc>, "name=mcp2515-spi3-0-osc";
+		spi3-1 = <0>, "+9",
+			<&mcp2515_frag>, "target?=0",
+			<&mcp2515_frag>, "target-path=spi3",
+			<&mcp2515>, "reg:0=1",
+			<&mcp2515_pins>, "name=mcp2515_spi3_1_pins",
+			<&clk_mcp2515_osc>, "name=mcp2515-spi3-1-osc";
+
+		spi5-0 = <0>, "+10",
+			<&mcp2515_frag>, "target?=0",
+			<&mcp2515_frag>, "target-path=spi5",
+			<&mcp2515>, "reg:0=0",
+			<&mcp2515_pins>, "name=mcp2515_spi5_0_pins",
+			<&clk_mcp2515_osc>, "name=mcp2515-spi5-0-osc";
+		spi5-1 = <0>, "+11",
+			<&mcp2515_frag>, "target?=0",
+			<&mcp2515_frag>, "target-path=spi5",
+			<&mcp2515>, "reg:0=1",
+			<&mcp2515_pins>, "name=mcp2515_spi5_1_pins",
+			<&clk_mcp2515_osc>, "name=mcp2515-spi5-1-osc";
+
+		oscillator = <&clk_mcp2515_osc>, "clock-frequency:0";
+		speed = <&mcp2515>, "spi-max-frequency:0";
+		interrupt = <&mcp2515_pins>, "brcm,pins:0",
+			<&mcp2515>, "interrupts:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/mcp251xfd-overlay.dts b/arch/arm/boot/dts/overlays/mcp251xfd-overlay.dts
new file mode 100644
index 000000000000..65c861bbd340
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/mcp251xfd-overlay.dts
@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/pinctrl/bcm2835.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spidev0>;
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev1>;
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target-path = "spi1/spidev@0";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target-path = "spi1/spidev@1";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@4 {
+		target-path = "spi1/spidev@2";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@5 {
+		target-path = "spi2/spidev@0";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@6 {
+		target-path = "spi2/spidev@1";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@7 {
+		target-path = "spi2/spidev@2";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@8 {
+		target = <&gpio>;
+		__overlay__ {
+			mcp251xfd_pins: mcp251xfd_pins {
+				brcm,pins = <25>;
+				brcm,function = <BCM2835_FSEL_GPIO_IN>;
+			};
+		};
+	};
+
+	fragment@9 {
+		target-path = "/clocks";
+		__overlay__ {
+			clk_mcp251xfd_osc: mcp251xfd-osc {
+				#clock-cells = <0>;
+				compatible = "fixed-clock";
+				clock-frequency = <40000000>;
+			};
+		};
+	};
+
+	mcp251xfd_frag: fragment@10 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mcp251xfd: mcp251xfd@0 {
+				compatible = "microchip,mcp251xfd";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&mcp251xfd_pins>;
+				spi-max-frequency = <20000000>;
+				interrupt-parent = <&gpio>;
+				interrupts = <25 IRQ_TYPE_LEVEL_LOW>;
+				clocks = <&clk_mcp251xfd_osc>;
+			};
+		};
+	};
+
+	fragment@11 {
+		target = <&mcp251xfd>;
+		mcp251xfd_rx_int_gpios: __dormant__ {
+			microchip,rx-int-gpios = <&gpio 255 GPIO_ACTIVE_LOW>;
+		};
+	};
+
+	fragment@12 {
+		target = <&gpio>;
+		__dormant__ {
+			mcp251xfd_xceiver_pins: mcp251xfd_xceiver_pins {
+				brcm,pins = <255>;
+				brcm,function = <BCM2835_FSEL_GPIO_OUT>;
+			};
+		};
+	};
+
+	fragment@13 {
+		target-path = "/";
+		__dormant__ {
+			reg_mcp251xfd_xceiver: reg_mcp251xfd_xceiver {
+				compatible = "regulator-fixed";
+				regulator-name = "mcp251xfd_xceiver";
+				regulator-min-microvolt = <3300000>;
+				regulator-max-microvolt = <3300000>;
+				gpio = <&gpio 4 GPIO_ACTIVE_HIGH>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&mcp251xfd_xceiver_pins>;
+			};
+		};
+	};
+
+	fragment@14 {
+		target = <&mcp251xfd>;
+		__dormant__ {
+			xceiver-supply = <&reg_mcp251xfd_xceiver>;
+		};
+	};
+
+	__overrides__ {
+		spi0-0 = <0>, "+0",
+			<&mcp251xfd_frag>, "target:0=", <&spi0>,
+			<&mcp251xfd>, "reg:0=0",
+			<&mcp251xfd_pins>, "name=mcp251xfd_spi0_0_pins",
+			<&clk_mcp251xfd_osc>, "name=mcp251xfd-spi0-0-osc",
+			<&mcp251xfd_xceiver_pins>, "name=mcp251xfd_spi0_0_xceiver_pins",
+			<&reg_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi0-0-xceiver",
+			<&reg_mcp251xfd_xceiver>, "regulator-name=mcp251xfd-spi0-0-xceiver";
+		spi0-1 = <0>, "+1",
+			<&mcp251xfd_frag>, "target:0=", <&spi0>,
+			<&mcp251xfd>, "reg:0=1",
+			<&mcp251xfd_pins>, "name=mcp251xfd_spi0_1_pins",
+			<&clk_mcp251xfd_osc>, "name=mcp251xfd-spi0-1-osc",
+			<&mcp251xfd_xceiver_pins>, "name=mcp251xfd_spi0_1_xceiver_pins",
+			<&reg_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi0-1-xceiver",
+			<&reg_mcp251xfd_xceiver>, "regulator-name=mcp251xfd-spi0-1-xceiver";
+		spi1-0 = <0>, "+2",
+			<&mcp251xfd_frag>, "target:0=", <&spi1>,
+			<&mcp251xfd>, "reg:0=0",
+			<&mcp251xfd_pins>, "name=mcp251xfd_spi1_0_pins",
+			<&clk_mcp251xfd_osc>, "name=mcp251xfd-spi1-0-osc",
+			<&mcp251xfd_xceiver_pins>, "name=mcp251xfd_spi1_0_xceiver_pins",
+			<&reg_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi1-0-xceiver",
+			<&reg_mcp251xfd_xceiver>, "regulator-name=mcp251xfd-spi1-0-xceiver";
+		spi1-1 = <0>, "+3",
+			<&mcp251xfd_frag>, "target:0=", <&spi1>,
+			<&mcp251xfd>, "reg:0=1",
+			<&mcp251xfd_pins>, "name=mcp251xfd_spi1_1_pins",
+			<&clk_mcp251xfd_osc>, "name=mcp251xfd-spi1-1-osc",
+			<&mcp251xfd_xceiver_pins>, "name=mcp251xfd_spi1_1_xceiver_pins",
+			<&reg_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi1-1-xceiver",
+			<&reg_mcp251xfd_xceiver>, "regulator-name=mcp251xfd-spi1-1-xceiver";
+		spi1-2 = <0>, "+4",
+			<&mcp251xfd_frag>, "target:0=", <&spi1>,
+			<&mcp251xfd>, "reg:0=2",
+			<&mcp251xfd_pins>, "name=mcp251xfd_spi1_2_pins",
+			<&clk_mcp251xfd_osc>, "name=mcp251xfd-spi1-2-osc",
+			<&mcp251xfd_xceiver_pins>, "name=mcp251xfd_spi1_2_xceiver_pins",
+			<&reg_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi1-2-xceiver",
+			<&reg_mcp251xfd_xceiver>, "regulator-name=mcp251xfd-spi1-2-xceiver";
+		spi2-0 = <0>, "+5",
+			<&mcp251xfd_frag>, "target:0=", <&spi2>,
+			<&mcp251xfd>, "reg:0=0",
+			<&mcp251xfd_pins>, "name=mcp251xfd_spi2_0_pins",
+			<&clk_mcp251xfd_osc>, "name=mcp251xfd-spi2-0-osc",
+			<&mcp251xfd_xceiver_pins>, "name=mcp251xfd_spi2_0_xceiver_pins",
+			<&reg_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi2-0-xceiver",
+			<&reg_mcp251xfd_xceiver>, "regulator-name=mcp251xfd-spi2-0-xceiver";
+		spi2-1 = <0>, "+6",
+			<&mcp251xfd_frag>, "target:0=", <&spi2>,
+			<&mcp251xfd>, "reg:0=1",
+			<&mcp251xfd_pins>, "name=mcp251xfd_spi2_1_pins",
+			<&clk_mcp251xfd_osc>, "name=mcp251xfd-spi2-1-osc",
+			<&mcp251xfd_xceiver_pins>, "name=mcp251xfd_spi2_1_xceiver_pins",
+			<&reg_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi2-1-xceiver",
+			<&reg_mcp251xfd_xceiver>, "regulator-name=mcp251xfd-spi2-1-xceiver";
+		spi2-2 = <0>, "+7",
+			<&mcp251xfd_frag>, "target:0=", <&spi2>,
+			<&mcp251xfd>, "reg:0=2",
+			<&mcp251xfd_pins>, "name=mcp251xfd_spi2_2_pins",
+			<&clk_mcp251xfd_osc>, "name=mcp251xfd-spi2-2-osc",
+			<&mcp251xfd_xceiver_pins>, "name=mcp251xfd_spi2_2_xceiver_pins",
+			<&reg_mcp251xfd_xceiver>, "name=reg-mcp251xfd-spi2-2-xceiver",
+			<&reg_mcp251xfd_xceiver>, "regulator-name=mcp251xfd-spi2-2-xceiver";
+		oscillator = <&clk_mcp251xfd_osc>, "clock-frequency:0";
+		speed = <&mcp251xfd>, "spi-max-frequency:0";
+		interrupt = <&mcp251xfd_pins>, "brcm,pins:0",
+			<&mcp251xfd>, "interrupts:0";
+		rx_interrupt = <0>, "+11",
+			<&mcp251xfd_pins>, "brcm,pins:4",
+			<&mcp251xfd_rx_int_gpios>, "microchip,rx-int-gpios:4";
+		xceiver_enable = <0>, "+12+13+14",
+			<&mcp251xfd_xceiver_pins>, "brcm,pins:0",
+			<&reg_mcp251xfd_xceiver>, "gpio:4";
+		xceiver_active_high = <&reg_mcp251xfd_xceiver>, "enable-active-high?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/mcp3008-overlay.dts b/arch/arm/boot/dts/overlays/mcp3008-overlay.dts
new file mode 100755
index 000000000000..957fdb9310af
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/mcp3008-overlay.dts
@@ -0,0 +1,205 @@
+/*
+ * Device tree overlay for Microchip mcp3008 10-Bit A/D Converters
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spidev0>;
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev1>;
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target-path = "spi1/spidev@0";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target-path = "spi1/spidev@1";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@4 {
+		target-path = "spi1/spidev@2";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@5 {
+		target-path = "spi2/spidev@0";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@6 {
+		target-path = "spi2/spidev@1";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@7 {
+		target-path = "spi2/spidev@2";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@8 {
+		target = <&spi0>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mcp3008_00: mcp3008@0 {
+				compatible = "microchip,mcp3008";
+				reg = <0>;
+				spi-max-frequency = <1600000>;
+			};
+		};
+	};
+
+	fragment@9 {
+		target = <&spi0>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mcp3008_01: mcp3008@1 {
+				compatible = "microchip,mcp3008";
+				reg = <1>;
+				spi-max-frequency = <1600000>;
+			};
+		};
+	};
+
+	fragment@10 {
+		target = <&spi1>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mcp3008_10: mcp3008@0 {
+				compatible = "microchip,mcp3008";
+				reg = <0>;
+				spi-max-frequency = <1600000>;
+			};
+		};
+	};
+
+	fragment@11 {
+		target = <&spi1>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mcp3008_11: mcp3008@1 {
+				compatible = "microchip,mcp3008";
+				reg = <1>;
+				spi-max-frequency = <1600000>;
+			};
+		};
+	};
+
+	fragment@12 {
+		target = <&spi1>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mcp3008_12: mcp3008@2 {
+				compatible = "microchip,mcp3008";
+				reg = <2>;
+				spi-max-frequency = <1600000>;
+			};
+		};
+	};
+
+	fragment@13 {
+		target = <&spi2>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mcp3008_20: mcp3008@0 {
+				compatible = "microchip,mcp3008";
+				reg = <0>;
+				spi-max-frequency = <1600000>;
+			};
+		};
+	};
+
+	fragment@14 {
+		target = <&spi2>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mcp3008_21: mcp3008@1 {
+				compatible = "microchip,mcp3008";
+				reg = <1>;
+				spi-max-frequency = <1600000>;
+			};
+		};
+	};
+
+	fragment@15 {
+		target = <&spi2>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mcp3008_22: mcp3008@2 {
+				compatible = "microchip,mcp3008";
+				reg = <2>;
+				spi-max-frequency = <1600000>;
+			};
+		};
+	};
+
+	__overrides__ {
+		spi0-0-present = <0>, "+0+8";
+		spi0-1-present = <0>, "+1+9";
+		spi1-0-present = <0>, "+2+10";
+		spi1-1-present = <0>, "+3+11";
+		spi1-2-present = <0>, "+4+12";
+		spi2-0-present = <0>, "+5+13";
+		spi2-1-present = <0>, "+6+14";
+		spi2-2-present = <0>, "+7+15";
+		spi0-0-speed = <&mcp3008_00>, "spi-max-frequency:0";
+		spi0-1-speed = <&mcp3008_01>, "spi-max-frequency:0";
+		spi1-0-speed = <&mcp3008_10>, "spi-max-frequency:0";
+		spi1-1-speed = <&mcp3008_11>, "spi-max-frequency:0";
+		spi1-2-speed = <&mcp3008_12>, "spi-max-frequency:0";
+		spi2-0-speed = <&mcp3008_20>, "spi-max-frequency:0";
+		spi2-1-speed = <&mcp3008_21>, "spi-max-frequency:0";
+		spi2-2-speed = <&mcp3008_22>, "spi-max-frequency:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/mcp3202-overlay.dts b/arch/arm/boot/dts/overlays/mcp3202-overlay.dts
new file mode 100755
index 000000000000..8e4e9f60f285
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/mcp3202-overlay.dts
@@ -0,0 +1,205 @@
+/*
+ * Device tree overlay for Microchip mcp3202 12-Bit A/D Converters
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spidev0>;
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev1>;
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target-path = "spi1/spidev@0";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target-path = "spi1/spidev@1";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@4 {
+		target-path = "spi1/spidev@2";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@5 {
+		target-path = "spi2/spidev@0";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@6 {
+		target-path = "spi2/spidev@1";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@7 {
+		target-path = "spi2/spidev@2";
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@8 {
+		target = <&spi0>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mcp3202_00: mcp3202@0 {
+				compatible = "mcp3202";
+				reg = <0>;
+				spi-max-frequency = <1600000>;
+			};
+		};
+	};
+
+	fragment@9 {
+		target = <&spi0>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mcp3202_01: mcp3202@1 {
+				compatible = "mcp3202";
+				reg = <1>;
+				spi-max-frequency = <1600000>;
+			};
+		};
+	};
+
+	fragment@10 {
+		target = <&spi1>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mcp3202_10: mcp3202@0 {
+				compatible = "mcp3202";
+				reg = <0>;
+				spi-max-frequency = <1600000>;
+			};
+		};
+	};
+
+	fragment@11 {
+		target = <&spi1>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mcp3202_11: mcp3202@1 {
+				compatible = "mcp3202";
+				reg = <1>;
+				spi-max-frequency = <1600000>;
+			};
+		};
+	};
+
+	fragment@12 {
+		target = <&spi1>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mcp3202_12: mcp3202@2 {
+				compatible = "mcp3202";
+				reg = <2>;
+				spi-max-frequency = <1600000>;
+			};
+		};
+	};
+
+	fragment@13 {
+		target = <&spi2>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mcp3202_20: mcp3202@0 {
+				compatible = "mcp3202";
+				reg = <0>;
+				spi-max-frequency = <1600000>;
+			};
+		};
+	};
+
+	fragment@14 {
+		target = <&spi2>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mcp3202_21: mcp3202@1 {
+				compatible = "mcp3202";
+				reg = <1>;
+				spi-max-frequency = <1600000>;
+			};
+		};
+	};
+
+	fragment@15 {
+		target = <&spi2>;
+		__dormant__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mcp3202_22: mcp3202@2 {
+				compatible = "mcp3202";
+				reg = <2>;
+				spi-max-frequency = <1600000>;
+			};
+		};
+	};
+
+	__overrides__ {
+		spi0-0-present = <0>, "+0+8";
+		spi0-1-present = <0>, "+1+9";
+		spi1-0-present = <0>, "+2+10";
+		spi1-1-present = <0>, "+3+11";
+		spi1-2-present = <0>, "+4+12";
+		spi2-0-present = <0>, "+5+13";
+		spi2-1-present = <0>, "+6+14";
+		spi2-2-present = <0>, "+7+15";
+		spi0-0-speed = <&mcp3202_00>, "spi-max-frequency:0";
+		spi0-1-speed = <&mcp3202_01>, "spi-max-frequency:0";
+		spi1-0-speed = <&mcp3202_10>, "spi-max-frequency:0";
+		spi1-1-speed = <&mcp3202_11>, "spi-max-frequency:0";
+		spi1-2-speed = <&mcp3202_12>, "spi-max-frequency:0";
+		spi2-0-speed = <&mcp3202_20>, "spi-max-frequency:0";
+		spi2-1-speed = <&mcp3202_21>, "spi-max-frequency:0";
+		spi2-2-speed = <&mcp3202_22>, "spi-max-frequency:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/mcp342x-overlay.dts b/arch/arm/boot/dts/overlays/mcp342x-overlay.dts
new file mode 100644
index 000000000000..714eca5a4b5e
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/mcp342x-overlay.dts
@@ -0,0 +1,164 @@
+// Overlay for MCP3421-8 ADCs from Microchip Semiconductor
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			status = "okay";
+
+			mcp3421: mcp@68 {
+				reg = <0x68>;
+				compatible = "microchip,mcp3421";
+
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			status = "okay";
+
+			mcp3422: mcp@68 {
+				reg = <0x68>;
+				compatible = "microchip,mcp3422";
+
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			status = "okay";
+
+			mcp3423: mcp@68 {
+				reg = <0x68>;
+				compatible = "microchip,mcp3423";
+
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			status = "okay";
+
+			mcp3424: mcp@68 {
+				reg = <0x68>;
+				compatible = "microchip,mcp3424";
+
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			status = "okay";
+
+			mcp3425: mcp@68 {
+				reg = <0x68>;
+				compatible = "microchip,mcp3425","mcp3425";
+
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@5 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			status = "okay";
+
+			mcp3426: mcp@68 {
+				reg = <0x68>;
+				compatible = "microchip,mcp3426";
+
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@6 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			status = "okay";
+
+			mcp3427: mcp@68 {
+				reg = <0x68>;
+				compatible = "microchip,mcp3427";
+
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@7 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			status = "okay";
+
+			mcp3428: mcp@68 {
+				reg = <0x68>;
+				compatible = "microchip,mcp3428";
+
+				status = "okay";
+			};
+		};
+	};
+
+	__overrides__ {
+		addr = <&mcp3421>,"reg:0",
+		       <&mcp3422>,"reg:0",
+		       <&mcp3423>,"reg:0",
+		       <&mcp3424>,"reg:0",
+		       <&mcp3425>,"reg:0",
+		       <&mcp3426>,"reg:0",
+		       <&mcp3427>,"reg:0",
+		       <&mcp3428>,"reg:0";
+		mcp3421 = <0>,"=0";
+		mcp3422 = <0>,"=1";
+		mcp3423 = <0>,"=2";
+		mcp3424 = <0>,"=3";
+		mcp3425 = <0>,"=4";
+		mcp3426 = <0>,"=5";
+		mcp3427 = <0>,"=6";
+		mcp3428 = <0>,"=7";
+	};
+};
+
diff --git a/arch/arm/boot/dts/overlays/media-center-overlay.dts b/arch/arm/boot/dts/overlays/media-center-overlay.dts
new file mode 100644
index 000000000000..4bc2eaa1f215
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/media-center-overlay.dts
@@ -0,0 +1,86 @@
+/*
+ * Device Tree overlay for Media Center HAT by Pi Supply
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&gpio>;
+		__overlay__ {
+			rpi_display_pins: rpi_display_pins {
+				brcm,pins = <12 23 24 25>;
+				brcm,function = <1 1 1 0>; /* out out out in */
+				brcm,pull = <0 0 0 2>; /* - - - up */
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			rpidisplay: rpi-display@0{
+				compatible = "ilitek,ili9341";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&rpi_display_pins>;
+
+				spi-max-frequency = <32000000>;
+				rotate = <90>;
+				bgr;
+				fps = <30>;
+				buswidth = <8>;
+				reset-gpios = <&gpio 23 1>;
+				dc-gpios = <&gpio 24 0>;
+				led-gpios = <&gpio 12 0>;
+				debug = <0>;
+			};
+
+			rpidisplay_ts: rpi-display-ts@1 {
+				compatible = "ti,ads7846";
+				reg = <1>;
+
+				spi-max-frequency = <2000000>;
+				interrupts = <25 2>; /* high-to-low edge triggered */
+				interrupt-parent = <&gpio>;
+				pendown-gpio = <&gpio 25 1>;
+				ti,x-plate-ohms = /bits/ 16 <60>;
+				ti,pressure-max = /bits/ 16 <255>;
+			};
+		};
+	};
+
+	__overrides__ {
+		speed =     <&rpidisplay>,"spi-max-frequency:0";
+		rotate =    <&rpidisplay>,"rotate:0";
+		fps =       <&rpidisplay>,"fps:0";
+		debug =     <&rpidisplay>,"debug:0";
+		xohms =     <&rpidisplay_ts>,"ti,x-plate-ohms;0";
+		swapxy =    <&rpidisplay_ts>,"ti,swap-xy?";
+		backlight = <&rpidisplay>,"led-gpios:4",
+		            <&rpi_display_pins>,"brcm,pins:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/merus-amp-overlay.dts b/arch/arm/boot/dts/overlays/merus-amp-overlay.dts
new file mode 100644
index 000000000000..96159a48d33f
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/merus-amp-overlay.dts
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for Infineon Merus-Amp
+/dts-v1/;
+/plugin/;
+#include <dt-bindings/pinctrl/bcm2835.h>
+#include <dt-bindings/gpio/gpio.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			merus_amp_pins: merus_amp_pins {
+				brcm,pins = <23 8>;
+				brcm,function = <0 0>;
+				brcm,pull = <2 0>;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			merus_amp: ma120x0p@20 {
+				#sound-dai-cells = <0>;
+				compatible = "ma,ma120x0p";
+				reg = <0x20>;
+				status = "okay";
+				pinctrl-names = "default";
+				pinctrl-0 = <&merus_amp_pins>;
+				enable_gp-gpios = <&gpio 14 GPIO_ACTIVE_HIGH>;
+				mute_gp-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>;
+				booster_gp-gpios = <&gpio 17 GPIO_ACTIVE_HIGH>;
+				error_gp-gpios = <&gpio 23 GPIO_ACTIVE_HIGH>;
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "merus,merus-amp";
+			i2s-controller = <&i2s_clk_producer>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/midi-uart0-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart0-overlay.dts
new file mode 100644
index 000000000000..f7e44d29e101
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/midi-uart0-overlay.dts
@@ -0,0 +1,36 @@
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/clock/bcm2835.h>
+
+/*
+ * Fake a higher clock rate to get a larger divisor, and thereby a lower
+ * baudrate. The real clock is 48MHz, which we scale so that requesting
+ * 38.4kHz results in an actual 31.25kHz.
+ *
+ *   48000000*38400/31250 = 58982400
+ */
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			midi_clk: midi_clk {
+				compatible = "fixed-clock";
+				#clock-cells = <0>;
+				clock-output-names = "uart0_pclk";
+				clock-frequency = <58982400>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&uart0>;
+		__overlay__ {
+			clocks = <&midi_clk>,
+			         <&clocks BCM2835_CLOCK_VPU>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/midi-uart0-pi5-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart0-pi5-overlay.dts
new file mode 100644
index 000000000000..4efa46853bbb
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/midi-uart0-pi5-overlay.dts
@@ -0,0 +1,37 @@
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/clock/rp1.h>
+
+/*
+ * Fake a higher clock rate to get a larger divisor, and thereby a lower
+ * baudrate. Create an intermediary clock that reports the real clock frequency
+ * multiplied by 38400/31250. This will result in the UART's clock divisor being
+ * greater by a factor of 38400/31250, such that requesting 38.4kHz results in
+ * an actual 31.25kHz.
+ */
+
+/{
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target-path = "/clocks";
+		__overlay__ {
+			midi_clk: midiclock_0 {
+				compatible = "fixed-factor-clock";
+				#clock-cells = <0>;
+				clocks = <&rp1_clocks RP1_CLK_UART>;
+				clock-mult = <38400>;
+				clock-div  = <31250>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&uart0>;
+		__overlay__ {
+			status = "okay";
+			clocks = <&midi_clk &rp1_clocks RP1_PLL_SYS_PRI_PH>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/midi-uart1-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart1-overlay.dts
new file mode 100644
index 000000000000..e0bc410acbff
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/midi-uart1-overlay.dts
@@ -0,0 +1,43 @@
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/clock/bcm2835-aux.h>
+
+/*
+ * Fake a higher clock rate to get a larger divisor, and thereby a lower
+ * baudrate. The real clock is 48MHz, which we scale so that requesting
+ * 38.4kHz results in an actual 31.25kHz.
+ *
+ *   48000000*38400/31250 = 58982400
+ */
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/clocks";
+		__overlay__ {
+			midi_clk: clock@5 {
+				compatible = "fixed-factor-clock";
+				#clock-cells = <0>;
+				clocks = <&aux BCM2835_AUX_CLOCK_UART>;
+				clock-mult = <38400>;
+				clock-div  = <31250>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&uart1>;
+		__overlay__ {
+			clocks = <&midi_clk>;
+		};
+	};
+
+	fragment@2 {
+		target = <&aux>;
+		__overlay__ {
+			clock-output-names = "aux_uart", "aux_spi1", "aux_spi2";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/midi-uart1-pi5-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart1-pi5-overlay.dts
new file mode 100644
index 000000000000..40948fdb82d6
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/midi-uart1-pi5-overlay.dts
@@ -0,0 +1,37 @@
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/clock/rp1.h>
+
+/*
+ * Fake a higher clock rate to get a larger divisor, and thereby a lower
+ * baudrate. Create an intermediary clock that reports the real clock frequency
+ * multiplied by 38400/31250. This will result in the UART's clock divisor being
+ * greater by a factor of 38400/31250, such that requesting 38.4kHz results in
+ * an actual 31.25kHz.
+ */
+
+/{
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target-path = "/clocks";
+		__overlay__ {
+			midi_clk: midiclock_1 {
+				compatible = "fixed-factor-clock";
+				#clock-cells = <0>;
+				clocks = <&rp1_clocks RP1_CLK_UART>;
+				clock-mult = <38400>;
+				clock-div  = <31250>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&uart1>;
+		__overlay__ {
+			status = "okay";
+			clocks = <&midi_clk &rp1_clocks RP1_PLL_SYS_PRI_PH>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/midi-uart2-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart2-overlay.dts
new file mode 100644
index 000000000000..5c6985f41ea2
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/midi-uart2-overlay.dts
@@ -0,0 +1,37 @@
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/clock/bcm2835.h>
+
+/*
+ * Fake a higher clock rate to get a larger divisor, and thereby a lower
+ * baudrate. The real clock is 48MHz, which we scale so that requesting
+ * 38.4kHz results in an actual 31.25kHz.
+ *
+ *   48000000*38400/31250 = 58982400
+ */
+
+/{
+        compatible = "brcm,bcm2711";
+
+        fragment@0 {
+                target-path = "/";
+                __overlay__ {
+                        midi_clk: midi_clk2 {
+                                compatible = "fixed-clock";
+                                #clock-cells = <0>;
+                                clock-output-names = "uart2_pclk";
+                                clock-frequency = <58982400>;
+                        };
+                };
+        };
+
+        fragment@1 {
+                target = <&uart2>;
+                __overlay__ {
+                        clocks = <&midi_clk>,
+                                 <&clocks BCM2835_CLOCK_VPU>;
+                };
+        };
+};
+
diff --git a/arch/arm/boot/dts/overlays/midi-uart2-pi5-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart2-pi5-overlay.dts
new file mode 100644
index 000000000000..ef80ce214015
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/midi-uart2-pi5-overlay.dts
@@ -0,0 +1,37 @@
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/clock/rp1.h>
+
+/*
+ * Fake a higher clock rate to get a larger divisor, and thereby a lower
+ * baudrate. Create an intermediary clock that reports the real clock frequency
+ * multiplied by 38400/31250. This will result in the UART's clock divisor being
+ * greater by a factor of 38400/31250, such that requesting 38.4kHz results in
+ * an actual 31.25kHz.
+ */
+
+/{
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target-path = "/clocks";
+		__overlay__ {
+			midi_clk: midiclock_2 {
+				compatible = "fixed-factor-clock";
+				#clock-cells = <0>;
+				clocks = <&rp1_clocks RP1_CLK_UART>;
+				clock-mult = <38400>;
+				clock-div  = <31250>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&uart2>;
+		__overlay__ {
+			status = "okay";
+			clocks = <&midi_clk &rp1_clocks RP1_PLL_SYS_PRI_PH>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/midi-uart3-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart3-overlay.dts
new file mode 100644
index 000000000000..052027db0564
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/midi-uart3-overlay.dts
@@ -0,0 +1,38 @@
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/clock/bcm2835.h>
+
+/*
+ * Fake a higher clock rate to get a larger divisor, and thereby a lower
+ * baudrate. The real clock is 48MHz, which we scale so that requesting
+ * 38.4kHz results in an actual 31.25kHz.
+ *
+ *   48000000*38400/31250 = 58982400
+ */
+
+/{
+        compatible = "brcm,bcm2711";
+
+        fragment@0 {
+                target-path = "/";
+                __overlay__ {
+                        midi_clk: midi_clk3 {
+                                compatible = "fixed-clock";
+                                #clock-cells = <0>;
+                                clock-output-names = "uart3_pclk";
+                                clock-frequency = <58982400>;
+                        };
+                };
+        };
+
+        fragment@1 {
+                target = <&uart3>;
+                __overlay__ {
+                        clocks = <&midi_clk>,
+                                 <&clocks BCM2835_CLOCK_VPU>;
+                };
+        };
+};
+
+
diff --git a/arch/arm/boot/dts/overlays/midi-uart3-pi5-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart3-pi5-overlay.dts
new file mode 100644
index 000000000000..ac59efe68bf9
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/midi-uart3-pi5-overlay.dts
@@ -0,0 +1,37 @@
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/clock/rp1.h>
+
+/*
+ * Fake a higher clock rate to get a larger divisor, and thereby a lower
+ * baudrate. Create an intermediary clock that reports the real clock frequency
+ * multiplied by 38400/31250. This will result in the UART's clock divisor being
+ * greater by a factor of 38400/31250, such that requesting 38.4kHz results in
+ * an actual 31.25kHz.
+ */
+
+/{
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target-path = "/clocks";
+		__overlay__ {
+			midi_clk: midiclock_3 {
+				compatible = "fixed-factor-clock";
+				#clock-cells = <0>;
+				clocks = <&rp1_clocks RP1_CLK_UART>;
+				clock-mult = <38400>;
+				clock-div  = <31250>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&uart3>;
+		__overlay__ {
+			status = "okay";
+			clocks = <&midi_clk &rp1_clocks RP1_PLL_SYS_PRI_PH>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/midi-uart4-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart4-overlay.dts
new file mode 100644
index 000000000000..5f09a7ccd675
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/midi-uart4-overlay.dts
@@ -0,0 +1,38 @@
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/clock/bcm2835.h>
+
+/*
+ * Fake a higher clock rate to get a larger divisor, and thereby a lower
+ * baudrate. The real clock is 48MHz, which we scale so that requesting
+ * 38.4kHz results in an actual 31.25kHz.
+ *
+ *   48000000*38400/31250 = 58982400
+ */
+
+/{
+        compatible = "brcm,bcm2711";
+
+        fragment@0 {
+                target-path = "/";
+                __overlay__ {
+                        midi_clk: midi_clk4 {
+                                compatible = "fixed-clock";
+                                #clock-cells = <0>;
+                                clock-output-names = "uart4_pclk";
+                                clock-frequency = <58982400>;
+                        };
+                };
+        };
+
+        fragment@1 {
+                target = <&uart4>;
+                __overlay__ {
+                        clocks = <&midi_clk>,
+                                 <&clocks BCM2835_CLOCK_VPU>;
+                };
+        };
+};
+
+
diff --git a/arch/arm/boot/dts/overlays/midi-uart4-pi5-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart4-pi5-overlay.dts
new file mode 100644
index 000000000000..959e114d0f43
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/midi-uart4-pi5-overlay.dts
@@ -0,0 +1,37 @@
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/clock/rp1.h>
+
+/*
+ * Fake a higher clock rate to get a larger divisor, and thereby a lower
+ * baudrate. Create an intermediary clock that reports the real clock frequency
+ * multiplied by 38400/31250. This will result in the UART's clock divisor being
+ * greater by a factor of 38400/31250, such that requesting 38.4kHz results in
+ * an actual 31.25kHz.
+ */
+
+/{
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target-path = "/clocks";
+		__overlay__ {
+			midi_clk: midiclock_4 {
+				compatible = "fixed-factor-clock";
+				#clock-cells = <0>;
+				clocks = <&rp1_clocks RP1_CLK_UART>;
+				clock-mult = <38400>;
+				clock-div  = <31250>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&uart4>;
+		__overlay__ {
+			status = "okay";
+			clocks = <&midi_clk &rp1_clocks RP1_PLL_SYS_PRI_PH>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/midi-uart5-overlay.dts b/arch/arm/boot/dts/overlays/midi-uart5-overlay.dts
new file mode 100644
index 000000000000..74551ec2a672
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/midi-uart5-overlay.dts
@@ -0,0 +1,38 @@
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/clock/bcm2835.h>
+
+/*
+ * Fake a higher clock rate to get a larger divisor, and thereby a lower
+ * baudrate. The real clock is 48MHz, which we scale so that requesting
+ * 38.4kHz results in an actual 31.25kHz.
+ *
+ *   48000000*38400/31250 = 58982400
+ */
+
+/{
+        compatible = "brcm,bcm2711";
+
+        fragment@0 {
+                target-path = "/";
+                __overlay__ {
+                        midi_clk: midi_clk5 {
+                                compatible = "fixed-clock";
+                                #clock-cells = <0>;
+                                clock-output-names = "uart5_pclk";
+                                clock-frequency = <58982400>;
+                        };
+                };
+        };
+
+        fragment@1 {
+                target = <&uart5>;
+                __overlay__ {
+                        clocks = <&midi_clk>,
+                                 <&clocks BCM2835_CLOCK_VPU>;
+                };
+        };
+};
+
+
diff --git a/arch/arm/boot/dts/overlays/minipitft13-overlay.dts b/arch/arm/boot/dts/overlays/minipitft13-overlay.dts
new file mode 100644
index 000000000000..5e0941e8ba54
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/minipitft13-overlay.dts
@@ -0,0 +1,70 @@
+/*
+ * Device Tree overlay for Adafruit Mini PiTFT 1.3" and 1.5" 240x240 Display
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+        compatible = "brcm,bcm2835";
+
+        fragment@0 {
+                target = <&spidev0>;
+                __overlay__ {
+                        status = "disabled";
+                };
+        };
+
+        fragment@1 {
+                target = <&spidev1>;
+                __overlay__ {
+                        status = "disabled";
+                };
+        };
+
+        fragment@2 {
+                target = <&gpio>;
+                __overlay__ {
+                        pitft_pins: pitft_pins {
+                                brcm,pins = <25>;
+                                brcm,function = <1>; /* out */
+                                brcm,pull = <0>; /* none */
+                        };
+                };
+        };
+
+        fragment@3 {
+                target = <&spi0>;
+                __overlay__ {
+                        /* needed to avoid dtc warning */
+                        #address-cells = <1>;
+                        #size-cells = <0>;
+                        status = "okay";
+
+                        pitft: pitft@0 {
+                                compatible = "fbtft,minipitft13";
+                                reg = <0>;
+                                pinctrl-names = "default";
+                                pinctrl-0 = <&pitft_pins>;
+                                spi-max-frequency = <32000000>;
+                                rotate = <0>;
+                                width = <240>;
+                                height = <240>;
+                                buswidth = <8>;
+                                dc-gpios = <&gpio 25 0>;
+                                led-gpios = <&gpio 26 0>;
+                                debug = <0>;
+                        };
+                };
+        };
+
+        __overrides__ {
+                speed =   <&pitft>,"spi-max-frequency:0";
+                rotate =  <&pitft>,"rotate:0";
+                width =   <&pitft>,"width:0";
+                height =  <&pitft>,"height:0";
+                fps =     <&pitft>,"fps:0";
+                debug =   <&pitft>,"debug:0";
+        };
+};
diff --git a/arch/arm/boot/dts/overlays/miniuart-bt-overlay.dts b/arch/arm/boot/dts/overlays/miniuart-bt-overlay.dts
new file mode 100644
index 000000000000..757e5cd3c4e8
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/miniuart-bt-overlay.dts
@@ -0,0 +1,83 @@
+/dts-v1/;
+/plugin/;
+
+/* Switch Pi3 Bluetooth function to use the mini-UART (ttyS0) and restore
+   UART0/ttyAMA0 over GPIOs 14 & 15. Note that this may reduce the maximum
+   usable baudrate.
+
+   It is also necessary to edit /lib/systemd/system/hciuart.service and
+   replace ttyAMA0 with ttyS0, unless you have a system with udev rules
+   that create /dev/serial0 and /dev/serial1, in which case use /dev/serial1
+   instead because it will always be correct.
+
+   If cmdline.txt uses the alias serial0 to refer to the user-accessable port
+   then the firmware will replace with the appropriate port whether or not
+   this overlay is used.
+*/
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&uart0>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&uart0_pins>;
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&bt>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&uart1>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&uart1_pins>;
+			status = "okay";
+		};
+	};
+
+	fragment@3 {
+		target = <&uart0_pins>;
+		__overlay__ {
+			brcm,pins;
+			brcm,function;
+			brcm,pull;
+		};
+	};
+
+	fragment@4 {
+		target = <&uart1>;
+		__overlay__ {
+			pinctrl-0 = <&uart1_bt_pins>;
+		};
+	};
+
+	fragment@5 {
+		target-path = "/aliases";
+		__overlay__ {
+			serial0 = "/soc/serial@7e201000";
+			serial1 = "/soc/serial@7e215040";
+			bluetooth = "/soc/serial@7e215040/bluetooth";
+		};
+	};
+
+	fragment@6 {
+		target = <&minibt>;
+		minibt_frag: __overlay__ {
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		krnbt = <&minibt_frag>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/mipi-dbi-spi-overlay.dts b/arch/arm/boot/dts/overlays/mipi-dbi-spi-overlay.dts
new file mode 100644
index 000000000000..63fb3a5f2388
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/mipi-dbi-spi-overlay.dts
@@ -0,0 +1,175 @@
+/*
+ * mipi-dbi-spi-overlay.dts
+ */
+
+#include <dt-bindings/gpio/gpio.h>
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	spidev_fragment: fragment@0 {
+		target-path = "spi0/spidev@0";
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	panel_fragment: fragment@1 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			status = "okay";
+
+			panel: panel@0 {
+				compatible = "panel", "panel-mipi-dbi-spi";
+				reg = <0>;
+				spi-max-frequency = <32000000>;
+
+				width-mm = <0>;
+				height-mm = <0>;
+
+				timing: panel-timing {
+					hactive = <320>;
+					vactive = <240>;
+					hback-porch = <0>;
+					vback-porch = <0>;
+
+					clock-frequency = <0>;
+					hfront-porch = <0>;
+					hsync-len = <0>;
+					vfront-porch = <0>;
+					vsync-len = <0>;
+				};
+			};
+		};
+	};
+
+	fragment@10 {
+		target = <&panel>;
+		__dormant__  {
+			backlight = <&backlight_gpio>;
+		};
+	};
+
+	fragment@11 {
+		target-path = "/";
+		__dormant__  {
+			backlight_gpio: backlight_gpio {
+				compatible = "gpio-backlight";
+				gpios = <&gpio 255 GPIO_ACTIVE_HIGH>;
+			};
+		};
+	};
+
+	fragment@20 {
+		target = <&panel>;
+		__dormant__  {
+			backlight = <&backlight_pwm>;
+		};
+	};
+
+	fragment@21 {
+		target-path = "/";
+		__dormant__  {
+			backlight_pwm: backlight_pwm {
+				compatible = "pwm-backlight";
+				brightness-levels = <0 6 8 12 16 24 32 40 48 64 96 128 160 192 224 255>;
+				default-brightness-level = <15>;
+				pwms = <&pwm 0 200000 0>;
+			};
+		};
+	};
+
+	fragment@22 {
+		target = <&pwm>;
+		__dormant__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&pwm_pins>;
+			assigned-clock-rates = <1000000>;
+			status = "okay";
+		};
+	};
+
+	fragment@23 {
+		target = <&gpio>;
+		__dormant__ {
+			pwm_pins: pwm_pins {
+				brcm,pins = <18>;
+				brcm,function = <2>; /* Alt5 */
+			};
+		};
+	};
+
+	fragment@24 {
+		target = <&chosen>;
+		__dormant__  {
+			bootargs = "snd_bcm2835.enable_headphones=0";
+		};
+	};
+
+	__overrides__ {
+		compatible    = <&panel>, "compatible";
+
+		spi0-0        = <&panel_fragment>, "target:0=",<&spi0>,
+				<&spidev_fragment>, "target-path=spi0/spidev@0",
+				<&panel>, "reg:0=0";
+		spi0-1        = <&panel_fragment>, "target:0=",<&spi0>,
+				<&spidev_fragment>, "target-path=spi0/spidev@1",
+				<&panel>, "reg:0=1";
+		spi1-0        = <&panel_fragment>, "target:0=",<&spi1>,
+				<&spidev_fragment>, "target-path=spi1/spidev@0",
+				<&panel>, "reg:0=0";
+		spi1-1        = <&panel_fragment>, "target:0=",<&spi1>,
+				<&spidev_fragment>, "target-path=spi1/spidev@1",
+				<&panel>, "reg:0=1";
+		spi1-2        = <&panel_fragment>, "target:0=",<&spi1>,
+				<&spidev_fragment>, "target-path=spi1/spidev@2",
+				<&panel>, "reg:0=2";
+		spi2-0        = <&panel_fragment>, "target:0=",<&spi2>,
+				<&spidev_fragment>, "target-path=spi2/spidev@0",
+				<&panel>, "reg:0=0";
+		spi2-1        = <&panel_fragment>, "target:0=",<&spi2>,
+				<&spidev_fragment>, "target-path=spi2/spidev@1",
+				<&panel>, "reg:0=1";
+		spi2-2        = <&panel_fragment>, "target:0=",<&spi2>,
+				<&spidev_fragment>, "target-path=spi2/spidev@2",
+				<&panel>, "reg:0=2";
+
+		speed         = <&panel>, "spi-max-frequency:0";
+		cpha          = <&panel>, "spi-cpha?";
+		cpol          = <&panel>, "spi-cpol?";
+
+		write-only    = <&panel>, "write-only?";
+
+		width         = <&timing>, "hactive:0";
+		height        = <&timing>, "vactive:0";
+		x-offset      = <&timing>, "hback-porch:0";
+		y-offset      = <&timing>, "vback-porch:0";
+		clock-frequency = <&timing>, "clock-frequency:0";
+
+		width-mm      = <&panel>, "width-mm:0";
+		height-mm     = <&panel>, "height-mm:0";
+
+		/* optional gpios */
+		reset-gpio    = <&panel>, "reset-gpios:0=", <&gpio>,
+				<&panel>, "reset-gpios:4",
+				<&panel>, "reset-gpios:8=0"; /* GPIO_ACTIVE_HIGH */
+		dc-gpio       = <&panel>, "dc-gpios:0=", <&gpio>,
+				<&panel>, "dc-gpios:4",
+				<&panel>, "dc-gpios:8=0"; /* GPIO_ACTIVE_HIGH */
+
+		backlight-gpio        = <0>, "+10+11",
+					<&backlight_gpio>, "gpios:4";
+		backlight-pwm         = <0>, "+20+21+22+23+24";
+		backlight-pwm-chan    = <&backlight_pwm>, "pwms:4";
+		backlight-pwm-gpio    = <&pwm_pins>, "brcm,pins:0";
+		backlight-pwm-func    = <&pwm_pins>, "brcm,function:0";
+		backlight-def-brightness = <&backlight_pwm>, "default-brightness-level:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/mira220-overlay.dts b/arch/arm/boot/dts/overlays/mira220-overlay.dts
new file mode 100644
index 000000000000..c969e55f4ba9
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/mira220-overlay.dts
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for MIRA220 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	clk_frag: fragment@1 {
+		target = <&cam1_clk>;
+		__overlay__ {
+			status = "okay";
+			clock-frequency = <38400000>;
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	i2c_frag: fragment@100 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			#include "mira220.dtsi"
+
+		};
+	};
+
+	csi_frag: fragment@101 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+
+			port {
+				csi_ep: endpoint {
+					remote-endpoint = <&cam_endpoint>;
+					clock-lanes = <0>;
+					data-lanes = <1 2>;
+					// clock-noncontinuous; // mira220 use cont clk
+				};
+			};
+		};
+	};
+
+	fragment@102 {
+		target = <&csi1>;
+		__dormant__ {
+			compatible = "brcm,bcm2835-unicam-legacy";
+		};
+	};
+
+
+	__overrides__ {
+		rotation = <&cam_node>,"rotation:0";
+		orientation = <&cam_node>,"orientation:0";
+		media-controller = <0>,"!102";
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+		       <&clk_frag>, "target:0=",<&cam0_clk>,
+		       <&cam_node>, "clocks:0=",<&cam0_clk>,
+		       <&cam_node>, "vana-supply:0=",<&cam0_reg>;
+	};
+};
+
+&cam_node {
+	status = "okay";
+};
+
+&cam_endpoint {
+	remote-endpoint = <&csi_ep>;
+};
diff --git a/arch/arm/boot/dts/overlays/mira220.dtsi b/arch/arm/boot/dts/overlays/mira220.dtsi
new file mode 100644
index 000000000000..4e171657a3a6
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/mira220.dtsi
@@ -0,0 +1,27 @@
+// Fragment that configures an mira220
+
+cam_node: mira220@10 {
+	compatible = "ams,mira220";
+	reg = <0x54>;
+	status = "disabled";
+
+	clocks = <&cam1_clk>;
+	clock-names = "xclk";
+
+	vana-supply = <&cam1_reg>;	/* 2.8v */
+	vdig-supply = <&cam_dummy_reg>;	/* 1.8v */
+	vddl-supply = <&cam_dummy_reg>;	/* 1.2v */
+
+	rotation = <0>;
+	orientation = <2>;
+
+	port {
+		cam_endpoint: endpoint {
+			clock-lanes = <0>;
+			data-lanes = <1 2>;
+			// clock-noncontinuous;
+			link-frequencies =
+				/bits/ 64 <750000000>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/mlx90640-overlay.dts b/arch/arm/boot/dts/overlays/mlx90640-overlay.dts
new file mode 100644
index 000000000000..a2655ed82585
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/mlx90640-overlay.dts
@@ -0,0 +1,22 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c_arm>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+			clock-frequency = <400000>;
+
+			mlx90640: mlx90640@33 {
+				compatible = "melexis,mlx90640";
+				reg = <0x33>;
+				status = "okay";
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/mmc-overlay.dts b/arch/arm/boot/dts/overlays/mmc-overlay.dts
new file mode 100644
index 000000000000..c1a2f691aa1e
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/mmc-overlay.dts
@@ -0,0 +1,46 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&mmc>;
+		frag0: __overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&mmc_pins>;
+			bus-width = <4>;
+			brcm,overclock-50 = <0>;
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			mmc_pins: mmc_pins {
+				brcm,pins = <48 49 50 51 52 53>;
+				brcm,function = <7>; /* alt3 */
+				brcm,pull = <0 2 2 2 2 2>;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sdhost>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&mmcnr>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	__overrides__ {
+		overclock_50     = <&frag0>,"brcm,overclock-50:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/mz61581-overlay.dts b/arch/arm/boot/dts/overlays/mz61581-overlay.dts
new file mode 100644
index 000000000000..101ad21d8093
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/mz61581-overlay.dts
@@ -0,0 +1,117 @@
+/*
+ * Device Tree overlay for MZ61581-PI-EXT 2014.12.28 by Tontec
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&gpio>;
+		__overlay__ {
+			mz61581_pins: mz61581_pins {
+				brcm,pins = <4 15 18 25>;
+				brcm,function = <0 1 1 1>; /* in out out out */
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			mz61581: mz61581@0{
+				compatible = "samsung,s6d02a1";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&mz61581_pins>;
+
+				spi-max-frequency = <128000000>;
+				spi-cpol;
+				spi-cpha;
+
+				width = <320>;
+				height = <480>;
+				rotate = <270>;
+				bgr;
+				fps = <30>;
+				buswidth = <8>;
+				txbuflen = <32768>;
+
+				reset-gpios = <&gpio 15 1>;
+				dc-gpios = <&gpio 25 0>;
+				led-gpios = <&gpio 18 0>;
+
+				init = <0x10000b0 00
+					0x1000011
+					0x20000ff
+					0x10000b3 0x02 0x00 0x00 0x00
+					0x10000c0 0x13 0x3b 0x00 0x02 0x00 0x01 0x00 0x43
+					0x10000c1 0x08 0x16 0x08 0x08
+					0x10000c4 0x11 0x07 0x03 0x03
+					0x10000c6 0x00
+					0x10000c8 0x03 0x03 0x13 0x5c 0x03 0x07 0x14 0x08 0x00 0x21 0x08 0x14 0x07 0x53 0x0c 0x13 0x03 0x03 0x21 0x00
+					0x1000035 0x00
+					0x1000036 0xa0
+					0x100003a 0x55
+					0x1000044 0x00 0x01
+					0x10000d0 0x07 0x07 0x1d 0x03
+					0x10000d1 0x03 0x30 0x10
+					0x10000d2 0x03 0x14 0x04
+					0x1000029
+					0x100002c>;
+
+				/* This is a workaround to make sure the init sequence slows down and doesn't fail */
+				debug = <3>;
+			};
+
+			mz61581_ts: mz61581_ts@1 {
+				compatible = "ti,ads7846";
+				reg = <1>;
+
+				spi-max-frequency = <2000000>;
+				interrupts = <4 2>; /* high-to-low edge triggered */
+				interrupt-parent = <&gpio>;
+				pendown-gpio = <&gpio 4 1>;
+
+				ti,x-plate-ohms = /bits/ 16 <60>;
+				ti,pressure-max = /bits/ 16 <255>;
+			};
+		};
+	};
+	__overrides__ {
+		speed =   <&mz61581>, "spi-max-frequency:0";
+		rotate =  <&mz61581>, "rotate:0";
+		fps =     <&mz61581>, "fps:0";
+		txbuflen = <&mz61581>, "txbuflen:0";
+		debug =   <&mz61581>, "debug:0";
+		xohms =   <&mz61581_ts>,"ti,x-plate-ohms;0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/nospi10-overlay.dts b/arch/arm/boot/dts/overlays/nospi10-overlay.dts
new file mode 100644
index 000000000000..7cf1a325dae8
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/nospi10-overlay.dts
@@ -0,0 +1,13 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&spi10>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/ov2311-overlay.dts b/arch/arm/boot/dts/overlays/ov2311-overlay.dts
new file mode 100644
index 000000000000..4dad303c1768
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ov2311-overlay.dts
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for OV2311 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2835";
+
+	i2c_frag: fragment@0 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			#include "ov2311.dtsi"
+		};
+	};
+
+	csi_frag: fragment@1 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+
+			port {
+				csi_ep: endpoint {
+					remote-endpoint = <&cam_endpoint>;
+					data-lanes = <1 2>;
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@3 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	clk_frag: fragment@4{
+		target = <&cam1_clk>;
+		__overlay__ {
+			status = "okay";
+			clock-frequency = <24000000>;
+		};
+	};
+
+	fragment@5 {
+		target = <&csi1>;
+		__dormant__ {
+			compatible = "brcm,bcm2835-unicam-legacy";
+		};
+	};
+
+	__overrides__ {
+		rotation = <&cam_node>,"rotation:0";
+		orientation = <&cam_node>,"orientation:0";
+		media-controller = <0>,"!5";
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+		       <&clk_frag>, "target:0=",<&cam0_clk>,
+		       <&cam_node>, "clocks:0=",<&cam0_clk>,
+		       <&cam_node>, "avdd-supply:0=",<&cam0_reg>;
+	};
+};
+
+&cam_node {
+	status = "okay";
+};
+
+&cam_endpoint {
+	remote-endpoint = <&csi_ep>;
+};
diff --git a/arch/arm/boot/dts/overlays/ov2311.dtsi b/arch/arm/boot/dts/overlays/ov2311.dtsi
new file mode 100644
index 000000000000..a1714d6941c3
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ov2311.dtsi
@@ -0,0 +1,26 @@
+// Fragment that configures an ov2311
+
+cam_node: ov2311@60 {
+	compatible = "ovti,ov2311";
+	reg = <0x60>;
+	status = "disabled";
+
+	clocks = <&cam1_clk>;
+	clock-names = "xvclk";
+
+	avdd-supply = <&cam1_reg>;
+	dovdd-supply = <&cam_dummy_reg>;
+	dvdd-supply = <&cam_dummy_reg>;
+
+	rotation = <0>;
+	orientation = <2>;
+
+	port {
+		cam_endpoint: endpoint {
+			clock-lanes = <0>;
+			data-lanes = <1 2>;
+			link-frequencies =
+				/bits/ 64 <400000000>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/ov5647-overlay.dts b/arch/arm/boot/dts/overlays/ov5647-overlay.dts
new file mode 100644
index 000000000000..e2d40dac80c9
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ov5647-overlay.dts
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for OV5647 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	i2c_frag: fragment@0 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			#include "ov5647.dtsi"
+
+			vcm_node: ad5398@c {
+				compatible = "adi,ad5398";
+				reg = <0x0c>;
+				status = "disabled";
+				VANA-supply = <&cam1_reg>;
+			};
+		};
+	};
+
+	csi_frag: fragment@1 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+
+			port {
+				csi_ep: endpoint {
+					remote-endpoint = <&cam_endpoint>;
+					data-lanes = <1 2>;
+					clock-noncontinuous;
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@3 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	reg_frag: fragment@4 {
+		target = <&cam1_reg>;
+		__overlay__ {
+			startup-delay-us = <20000>;
+		};
+	};
+
+	clk_frag: fragment@5 {
+		target = <&cam1_clk>;
+		__overlay__ {
+			status = "okay";
+			clock-frequency = <25000000>;
+		};
+	};
+
+	fragment@6 {
+		target = <&csi1>;
+		__dormant__ {
+			compatible = "brcm,bcm2835-unicam-legacy";
+		};
+	};
+
+	__overrides__ {
+		rotation = <&cam_node>,"rotation:0";
+		orientation = <&cam_node>,"orientation:0";
+		media-controller = <0>,"!6";
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+		       <&reg_frag>, "target:0=",<&cam0_reg>,
+		       <&clk_frag>, "target:0=",<&cam0_clk>,
+		       <&cam_node>, "clocks:0=",<&cam0_clk>,
+		       <&cam_node>, "avdd-supply:0=",<&cam0_reg>,
+		       <&vcm_node>, "VANA-supply:0=",<&cam0_reg>;
+		vcm = <&vcm_node>, "status=okay",
+		       <&cam_node>,"lens-focus:0=", <&vcm_node>;
+	};
+};
+
+&cam_node {
+	status = "okay";
+};
+
+&cam_endpoint {
+	remote-endpoint = <&csi_ep>;
+};
diff --git a/arch/arm/boot/dts/overlays/ov5647.dtsi b/arch/arm/boot/dts/overlays/ov5647.dtsi
new file mode 100644
index 000000000000..6455a191a394
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ov5647.dtsi
@@ -0,0 +1,25 @@
+cam_node: ov5647@36 {
+	compatible = "ovti,ov5647";
+	reg = <0x36>;
+	status = "disabled";
+
+	clocks = <&cam1_clk>;
+
+	avdd-supply = <&cam1_reg>;
+	dovdd-supply = <&cam_dummy_reg>;
+	dvdd-supply = <&cam_dummy_reg>;
+
+	rotation = <0>;
+	orientation = <2>;
+
+	port {
+		cam_endpoint: endpoint {
+			clock-lanes = <0>;
+			data-lanes = <1 2>;
+			clock-noncontinuous;
+			link-frequencies =
+				/bits/ 64 <297000000>;
+		};
+	};
+};
+
diff --git a/arch/arm/boot/dts/overlays/ov64a40-overlay.dts b/arch/arm/boot/dts/overlays/ov64a40-overlay.dts
new file mode 100644
index 000000000000..a765483aaaca
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ov64a40-overlay.dts
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for OV64A40 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	i2c_frag: fragment@0 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			#include "ov64a40.dtsi"
+		};
+	};
+
+	csi_frag: fragment@1 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+
+			port{
+				csi_ep: endpoint{
+					remote-endpoint = <&cam_endpoint>;
+					clock-lanes = <0>;
+					data-lanes = <1 2>;
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	clk_frag: fragment@3 {
+		target = <&cam1_clk>;
+		__overlay__ {
+			clock-frequency = <24000000>;
+			status = "okay";
+		};
+	};
+
+	fragment@4 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@5 {
+		target = <&cam_node>;
+		__overlay__ {
+			lens-focus = <&vcm_node>;
+		};
+	};
+
+	fragment@6 {
+		target = <&csi1>;
+		__dormant__ {
+			compatible = "brcm,bcm2835-unicam-legacy";
+		};
+	};
+
+	__overrides__ {
+		rotation = <&cam_node>,"rotation:0";
+		orientation = <&cam_node>,"orientation:0";
+		media-controller = <0>,"!6";
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+		       <&clk_frag>, "target:0=",<&cam0_clk>,
+		       <&cam_node>, "clocks:0=",<&cam0_clk>,
+		       <&cam_node>, "avdd-supply:0=",<&cam0_reg>,
+		       <&vcm_node>, "vdd-supply:0=",<&cam0_reg>;
+		vcm = <&vcm_node>, "status",
+		      <0>, "=5";
+		link-frequency = <&cam_endpoint>,"link-frequencies#0";
+	};
+};
+
+&cam_node {
+	status = "okay";
+};
+
+&cam_endpoint {
+	remote-endpoint = <&csi_ep>;
+};
+
+&vcm_node {
+	status = "okay";
+};
diff --git a/arch/arm/boot/dts/overlays/ov64a40.dtsi b/arch/arm/boot/dts/overlays/ov64a40.dtsi
new file mode 100644
index 000000000000..471b383fa151
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ov64a40.dtsi
@@ -0,0 +1,34 @@
+// Fragment that configures an OV64A40
+
+cam_node: ov64a40@36 {
+	compatible = "ovti,ov64a40";
+	reg = <0x36>;
+	status = "disabled";
+
+	clocks = <&cam1_clk>;
+	clock-names = "xclk";
+
+	avdd-supply = <&cam1_reg>;	/* 2.8v */
+	dovdd-supply = <&cam_dummy_reg>;/* 1.8v */
+	dvdd-supply = <&cam_dummy_reg>;	/* 1.1v */
+
+	rotation = <180>;
+	orientation = <2>;
+
+	port {
+		cam_endpoint: endpoint {
+			bus-type = <4>;
+			clock-lanes = <0>;
+			data-lanes = <1 2>;
+			link-frequencies =
+				/bits/ 64 <456000000>;
+		};
+	};
+};
+
+vcm_node: bu64754@76 {
+	compatible = "rohm,bu64754";
+	reg = <0x76>;
+	status = "disabled";
+	vdd-supply = <&cam1_reg>;
+};
diff --git a/arch/arm/boot/dts/overlays/ov7251-overlay.dts b/arch/arm/boot/dts/overlays/ov7251-overlay.dts
new file mode 100644
index 000000000000..e12953809690
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ov7251-overlay.dts
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for OV7251 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2835";
+
+	i2c_frag: fragment@0 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			#include "ov7251.dtsi"
+		};
+	};
+
+	csi_frag: fragment@1 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+
+			port {
+				csi_ep: endpoint {
+					remote-endpoint = <&cam_endpoint>;
+					data-lanes = <1>;
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@3 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	clk_frag: fragment@4 {
+		target = <&cam1_clk>;
+		__overlay__ {
+			status = "okay";
+			clock-frequency = <24000000>;
+		};
+	};
+
+	fragment@5 {
+		target = <&csi1>;
+		__dormant__ {
+			compatible = "brcm,bcm2835-unicam-legacy";
+		};
+	};
+
+	__overrides__ {
+		rotation = <&cam_node>,"rotation:0";
+		orientation = <&cam_node>,"orientation:0";
+		media-controller = <0>,"!5";
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+		       <&clk_frag>, "target:0=",<&cam0_clk>,
+		       <&cam_node>, "clocks:0=",<&cam0_clk>,
+		       <&cam_node>, "vdda-supply:0=",<&cam0_reg>;
+	};
+};
+
+&cam_node {
+	status = "okay";
+};
+
+&cam_endpoint {
+	remote-endpoint = <&csi_ep>;
+};
diff --git a/arch/arm/boot/dts/overlays/ov7251.dtsi b/arch/arm/boot/dts/overlays/ov7251.dtsi
new file mode 100644
index 000000000000..561fed1db837
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ov7251.dtsi
@@ -0,0 +1,28 @@
+// Fragment that configures an ov7251
+
+cam_node: ov7251@60 {
+	compatible = "ovti,ov7251";
+	reg = <0x60>;
+	status = "disabled";
+
+	clocks = <&cam1_clk>;
+	clock-names = "xclk";
+	clock-frequency = <24000000>;
+
+	vdddo-supply = <&cam_dummy_reg>;
+	vdda-supply = <&cam1_reg>;
+	vddd-supply = <&cam_dummy_reg>;
+
+	rotation = <0>;
+	orientation = <2>;
+
+	port {
+		cam_endpoint: endpoint {
+			clock-lanes = <0>;
+			data-lanes = <1>;
+			clock-noncontinuous;
+			link-frequencies =
+				/bits/ 64 <240000000>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/ov9281-overlay.dts b/arch/arm/boot/dts/overlays/ov9281-overlay.dts
new file mode 100644
index 000000000000..d132c6b83611
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ov9281-overlay.dts
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for OV9281 camera module on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/{
+	compatible = "brcm,bcm2835";
+
+	i2c_frag: fragment@0 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			#include "ov9281.dtsi"
+		};
+	};
+
+	csi_frag: fragment@1 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+
+			port {
+				csi_ep: endpoint {
+					remote-endpoint = <&cam_endpoint>;
+					data-lanes = <1 2>;
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@3 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	clk_frag: fragment@4 {
+		target = <&cam1_clk>;
+		__overlay__ {
+			status = "okay";
+			clock-frequency = <24000000>;
+		};
+	};
+
+	fragment@5 {
+		target = <&csi1>;
+		__dormant__ {
+			compatible = "brcm,bcm2835-unicam-legacy";
+		};
+	};
+
+	reg_frag: fragment@6 {
+		target = <&cam1_reg>;
+		__dormant__ {
+			startup-delay-us = <20000>;
+			off-on-delay-us = <30000>;
+		};
+	};
+
+	fragment@7 {
+		target = <&csi_ep>;
+		__overlay__ {
+			clock-noncontinuous;
+		};
+	};
+
+	fragment@8 {
+		target = <&cam_endpoint>;
+		__overlay__ {
+			clock-noncontinuous;
+		};
+	};
+
+	__overrides__ {
+		rotation = <&cam_node>,"rotation:0";
+		orientation = <&cam_node>,"orientation:0";
+		media-controller = <0>,"!5";
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+		       <&clk_frag>, "target:0=",<&cam0_clk>,
+		       <&cam_node>, "clocks:0=",<&cam0_clk>,
+		       <&cam_node>, "avdd-supply:0=",<&cam0_reg>,
+		       <&reg_frag>, "target:0=",<&cam0_reg>;
+		arducam = <0>, "+6";
+		clk-continuous = <0>, "-7-8";
+		trigger-mode = <&cam_node>, "trigger-mode:0";
+	};
+};
+
+&cam_node {
+	status = "okay";
+};
+
+&cam_endpoint {
+	remote-endpoint = <&csi_ep>;
+};
diff --git a/arch/arm/boot/dts/overlays/ov9281.dtsi b/arch/arm/boot/dts/overlays/ov9281.dtsi
new file mode 100644
index 000000000000..80f9da98ee8a
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ov9281.dtsi
@@ -0,0 +1,27 @@
+// Fragment that configures an ov9281
+
+cam_node: ov9281@60 {
+	compatible = "ovti,ov9281";
+	reg = <0x60>;
+	status = "disabled";
+
+	clocks = <&cam1_clk>;
+	clock-names = "xvclk";
+
+	avdd-supply = <&cam1_reg>;
+	dovdd-supply = <&cam_dummy_reg>;
+	dvdd-supply = <&cam_dummy_reg>;
+
+	rotation = <0>;
+	orientation = <2>;
+	trigger-mode = <0>;
+
+	port {
+		cam_endpoint: endpoint {
+			clock-lanes = <0>;
+			data-lanes = <1 2>;
+			link-frequencies =
+				/bits/ 64 <400000000>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/overlay_map.dts b/arch/arm/boot/dts/overlays/overlay_map.dts
new file mode 100644
index 000000000000..6d0e12773ca9
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/overlay_map.dts
@@ -0,0 +1,557 @@
+/dts-v1/;
+
+/ {
+	audremap {
+		bcm2835;
+		bcm2711;
+		bcm2712 = "audremap-pi5";
+	};
+
+	audremap-pi5 {
+		bcm2712;
+	};
+
+	balena-fin {
+		bcm2835;
+		bcm2711;
+	};
+
+	bmp085_i2c-sensor {
+		deprecated = "use i2c-sensor,bmp085";
+	};
+
+	cm-swap-i2c0 {
+		bcm2835;
+		bcm2711;
+	};
+
+	cutiepi-panel {
+		bcm2711;
+	};
+
+	disable-bt {
+		bcm2835;
+		bcm2711;
+		bcm2712 = "disable-bt-pi5";
+	};
+
+	disable-bt-pi5 {
+		bcm2712;
+	};
+
+	disable-emmc2 {
+		bcm2711;
+	};
+
+	disable-wifi {
+		bcm2835;
+		bcm2711;
+		bcm2712 = "disable-wifi-pi5";
+	};
+
+	disable-wifi-pi5 {
+		bcm2712;
+	};
+
+	dwc-otg {
+		renamed = "dwc-otg-deprecated";
+	};
+
+	hifiberry-adc8x {
+		bcm2712;
+	};
+
+	hifiberry-dac8x {
+		bcm2712;
+	};
+
+	highperi {
+		bcm2711;
+	};
+
+	i2c0 {
+		bcm2835;
+		bcm2711;
+		bcm2712 = "i2c0-pi5";
+	};
+
+	i2c0-bcm2708 {
+		deprecated = "use i2c0";
+	};
+
+	i2c0-pi5 {
+		bcm2712;
+	};
+
+	i2c1 {
+		bcm2835;
+		bcm2711;
+		bcm2712 = "i2c1-pi5";
+	};
+
+	i2c1-bcm2708 {
+		deprecated = "use i2c1";
+	};
+
+	i2c1-pi5 {
+		bcm2712;
+	};
+
+	i2c2 {
+		bcm2712 = "i2c2-pi5";
+	};
+
+	i2c2-pi5 {
+		bcm2712;
+	};
+
+	i2c3 {
+		bcm2711;
+		bcm2712 = "i2c3-pi5";
+	};
+
+	i2c3-pi5 {
+		bcm2712;
+	};
+
+	i2c4 {
+		bcm2711;
+	};
+
+	i2c5 {
+		bcm2711;
+	};
+
+	i2c6 {
+		bcm2711;
+	};
+
+	i2s-gpio28-31 {
+		bcm2835;
+		bcm2711;
+	};
+
+	imx500 {
+		bcm2835;
+		bcm2711;
+		bcm2712 = "imx500-pi5";
+	};
+
+	imx500-pi5 {
+		bcm2712;
+	};
+
+	lirc-rpi {
+		deprecated = "use gpio-ir";
+	};
+
+	midi-uart0 {
+		bcm2835;
+		bcm2711;
+		bcm2712 = "midi-uart0-pi5";
+	};
+
+	midi-uart0-pi5 {
+		bcm2712;
+	};
+
+	midi-uart1 {
+		bcm2835;
+		bcm2711;
+		bcm2712 = "midi-uart1-pi5";
+	};
+
+	midi-uart1-pi5 {
+		bcm2712;
+	};
+
+	midi-uart2 {
+		bcm2711;
+		bcm2712 = "midi-uart2-pi5";
+	};
+
+	midi-uart2-pi5 {
+		bcm2712;
+	};
+
+	midi-uart3 {
+		bcm2711;
+		bcm2712 = "midi-uart3-pi5";
+	};
+
+	midi-uart3-pi5 {
+		bcm2712;
+	};
+
+	midi-uart4 {
+		bcm2711;
+		bcm2712 = "midi-uart4-pi5";
+	};
+
+	midi-uart4-pi5 {
+		bcm2712;
+	};
+
+	midi-uart5 {
+		bcm2711;
+	};
+
+	miniuart-bt {
+		bcm2835;
+		bcm2711;
+	};
+
+	mmc {
+		bcm2835;
+		bcm2711;
+	};
+
+	mpu6050 {
+		deprecated = "use i2c-sensor,mpu6050";
+	};
+
+	pcie-32bit-dma {
+		bcm2711;
+		bcm2712 = "pcie-32bit-dma-pi5";
+	};
+
+	pcie-32bit-dma-pi5 {
+		bcm2712;
+	};
+
+	pcie-compat-pi5 {
+		bcm2712;
+	};
+
+	pi3-act-led {
+		renamed = "act-led";
+	};
+
+	pi3-disable-bt {
+		renamed = "disable-bt";
+	};
+
+	pi3-disable-wifi {
+		renamed = "disable-wifi";
+	};
+
+	pi3-miniuart-bt {
+		renamed = "miniuart-bt";
+	};
+
+	pisound {
+		bcm2835;
+		bcm2711;
+		bcm2712 = "pisound-pi5";
+	};
+
+	pisound-pi5 {
+		bcm2712;
+	};
+
+	pwm-pio {
+		bcm2712;
+	};
+
+	pwm1 {
+		bcm2711;
+	};
+
+	ramoops {
+		bcm2835;
+		bcm2711 = "ramoops-pi4";
+		bcm2712 = "ramoops-pi4";
+	};
+
+	ramoops-pi4 {
+		bcm2711;
+		bcm2712;
+	};
+
+	rpi-cirrus-wm5102 {
+		renamed = "cirrus-wm5102";
+	};
+
+	rpi-dac {
+		renamed = "i2s-dac";
+	};
+
+	rpi-display {
+		renamed = "watterott-display";
+	};
+
+	rpi-proto {
+		renamed = "proto-codec";
+	};
+
+	rpivid-v4l2 {
+		deprecated = "no longer necessary";
+	};
+
+	sc16is750-spi0 {
+		renamed = "sc16is75x-spi,sc16is750,spi0-0";
+	};
+
+	sc16is752-spi0 {
+		renamed = "sc16is75x-spi,sc16is752,spi0-0";
+	};
+
+	sc16is752-spi1 {
+		renamed = "sc16is75x-spi,sc16is752,spi1-1cs,spi1-0";
+	};
+
+	sdhost {
+		bcm2835;
+		bcm2711;
+	};
+
+	sdio {
+		bcm2835;
+		bcm2711;
+		bcm2712 = "sdio-pi5";
+	};
+
+	sdio-1bit {
+		deprecated = "use sdio,bus_width=1,gpios_22_25";
+	};
+
+	sdio-pi5 {
+		bcm2712;
+	};
+
+	sdtweak {
+		deprecated = "use 'dtparam=sd_poll_once' etc.";
+	};
+
+	smi {
+		bcm2835;
+		bcm2711;
+	};
+
+	smi-dev {
+		bcm2835;
+		bcm2711;
+	};
+
+	smi-nand {
+		bcm2835;
+		bcm2711;
+	};
+
+	spi0-cs {
+		renamed = "spi0-2cs";
+	};
+
+	spi0-hw-cs {
+		deprecated = "no longer necessary";
+	};
+
+	spi2-1cs {
+		bcm2835;
+		bcm2711;
+		bcm2712 = "spi2-1cs-pi5";
+	};
+
+	spi2-1cs-pi5 {
+		bcm2712;
+	};
+
+	spi2-2cs {
+		bcm2835;
+		bcm2711;
+		bcm2712 = "spi2-2cs-pi5";
+	};
+
+	spi2-2cs-pi5 {
+		bcm2712;
+	};
+
+	spi3-1cs {
+		bcm2711;
+		bcm2712 = "spi3-1cs-pi5";
+	};
+
+	spi3-1cs-pi5 {
+		bcm2712;
+	};
+
+	spi3-2cs {
+		bcm2711;
+		bcm2712 = "spi3-2cs-pi5";
+	};
+
+	spi3-2cs-pi5 {
+		bcm2712;
+	};
+
+	spi4-1cs {
+		bcm2711;
+	};
+
+	spi4-2cs {
+		bcm2711;
+	};
+
+	spi5-1cs {
+		bcm2711;
+		bcm2712 = "spi5-1cs-pi5";
+	};
+
+	spi5-1cs-pi5 {
+		bcm2712;
+	};
+
+	spi5-2cs {
+		bcm2711;
+		bcm2712 = "spi5-2cs-pi5";
+	};
+
+	spi5-2cs-pi5 {
+		bcm2712;
+	};
+
+	spi6-1cs {
+		bcm2711;
+	};
+
+	spi6-2cs {
+		bcm2711;
+	};
+
+	tc358743 {
+		bcm2835;
+		bcm2711;
+		bcm2712 = "tc358743-pi5";
+	};
+
+	uart0 {
+		bcm2835;
+		bcm2711;
+		bcm2712 = "uart0-pi5";
+	};
+
+	uart0-pi5 {
+		bcm2712;
+	};
+
+	uart1 {
+		bcm2835;
+		bcm2711;
+		bcm2712 = "uart1-pi5";
+	};
+
+	uart1-pi5 {
+		bcm2712;
+	};
+
+	uart2 {
+		bcm2711;
+		bcm2712 = "uart2-pi5";
+	};
+
+	uart2-pi5 {
+		bcm2712;
+	};
+
+	uart3 {
+		bcm2711;
+		bcm2712 = "uart3-pi5";
+	};
+
+	uart3-pi5 {
+		bcm2712;
+	};
+
+	uart4 {
+		bcm2711;
+		bcm2712 = "uart4-pi5";
+	};
+
+	uart4-pi5 {
+		bcm2712;
+	};
+
+	uart5 {
+		bcm2711;
+	};
+
+	upstream {
+		bcm2835;
+		bcm2711 = "upstream-pi4";
+	};
+
+	upstream-aux-interrupt {
+		deprecated = "no longer necessary";
+	};
+
+	upstream-pi4 {
+		bcm2711;
+	};
+
+	vc4-fkms-v3d {
+		bcm2835;
+		bcm2711 = "vc4-fkms-v3d-pi4";
+		bcm2712 = "vc4-fkms-v3d-pi4";
+	};
+
+	vc4-fkms-v3d-pi4 {
+		bcm2711;
+		bcm2712;
+	};
+
+	vc4-kms-dpi-at056tn53v1 {
+		deprecated = "use vc4-kms-dpi-panel,at056tn53v1";
+	};
+
+	vc4-kms-v3d {
+		bcm2835;
+		bcm2711 = "vc4-kms-v3d-pi4";
+		bcm2712 = "vc4-kms-v3d-pi5";
+	};
+
+	vc4-kms-v3d-pi4 {
+		bcm2711;
+		bcm2712 = "vc4-kms-v3d-pi5";
+	};
+
+	vc4-kms-v3d-pi5 {
+		bcm2712;
+	};
+
+	vec-gpio {
+		bcm2712 = "vec-gpio-pi5";
+	};
+
+	vec-gpio-pi5 {
+		bcm2712;
+	};
+
+	vl805 {
+		bcm2711;
+	};
+
+	w1-gpio {
+		bcm2835;
+		bcm2711;
+		bcm2712 = "w1-gpio-pi5";
+	};
+
+	w1-gpio-pi5 {
+		bcm2712;
+	};
+
+	w1-gpio-pullup {
+		bcm2835;
+		bcm2711;
+		bcm2712 = "w1-gpio-pullup-pi5";
+	};
+
+	w1-gpio-pullup-pi5 {
+		bcm2712;
+	};
+
+	wifimac {
+		bcm2835;
+		bcm2711;
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/papirus-overlay.dts b/arch/arm/boot/dts/overlays/papirus-overlay.dts
new file mode 100644
index 000000000000..67052b53a59c
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/papirus-overlay.dts
@@ -0,0 +1,84 @@
+/* PaPiRus ePaper Screen by Pi Supply */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c_arm>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			display_temp: lm75@48 {
+				compatible = "national,lm75b";
+				reg = <0x48>;
+				status = "okay";
+				#thermal-sensor-cells = <0>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/thermal-zones";
+		__overlay__ {
+			display {
+				polling-delay-passive = <0>;
+				polling-delay = <0>;
+				thermal-sensors = <&display_temp>;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&gpio>;
+		__overlay__ {
+			repaper_pins: repaper_pins {
+				brcm,pins = <14 15 23 24 25>;
+				brcm,function = <1 1 1 1 0>; /* out out out out in */
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			repaper: repaper@0{
+				compatible = "not_set";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&repaper_pins>;
+
+				spi-max-frequency = <8000000>;
+
+				panel-on-gpios = <&gpio 23 0>;
+				border-gpios = <&gpio 14 0>;
+				discharge-gpios = <&gpio 15 0>;
+				reset-gpios = <&gpio 24 0>;
+				busy-gpios = <&gpio 25 0>;
+
+				repaper-thermal-zone = "display";
+			};
+		};
+	};
+
+	__overrides__ {
+		panel = <&repaper>, "compatible";
+		speed = <&repaper>, "spi-max-frequency:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pca953x-overlay.dts b/arch/arm/boot/dts/overlays/pca953x-overlay.dts
new file mode 100644
index 000000000000..50ff90d7c072
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pca953x-overlay.dts
@@ -0,0 +1,59 @@
+// Definitions for NXP PCA953x family of I2C GPIO controllers on ARM I2C bus.
+/dts-v1/;
+/plugin/;
+
+#include "i2c-buses.dtsi"
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2cbus>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			pca: pca@20 {
+				compatible = "nxp,pca9534";
+				reg = <0x20>;
+				gpio-controller;
+				#gpio-cells = <2>;
+			};
+		};
+	};
+
+	__overrides__ {
+		addr = <&pca>,"reg:0";
+		pca6416 = <&pca>,"compatible=nxp,pca6416";
+		pca9505 = <&pca>,"compatible=nxp,pca9505";
+		pca9535 = <&pca>,"compatible=nxp,pca9535";
+		pca9536 = <&pca>,"compatible=nxp,pca9536";
+		pca9537 = <&pca>,"compatible=nxp,pca9537";
+		pca9538 = <&pca>,"compatible=nxp,pca9538";
+		pca9539 = <&pca>,"compatible=nxp,pca9539";
+		pca9554 = <&pca>,"compatible=nxp,pca9554";
+		pca9555 = <&pca>,"compatible=nxp,pca9555";
+		pca9556 = <&pca>,"compatible=nxp,pca9556";
+		pca9557 = <&pca>,"compatible=nxp,pca9557";
+		pca9574 = <&pca>,"compatible=nxp,pca9574";
+		pca9575 = <&pca>,"compatible=nxp,pca9575";
+		pca9698 = <&pca>,"compatible=nxp,pca9698";
+		pcal6416 = <&pca>,"compatible=nxp,pcal6416";
+		pcal6524 = <&pca>,"compatible=nxp,pcal6524";
+		pcal9555a = <&pca>,"compatible=nxp,pcal9555a";
+		max7310 = <&pca>,"compatible=maxim,max7310";
+		max7312 = <&pca>,"compatible=maxim,max7312";
+		max7313 = <&pca>,"compatible=maxim,max7313";
+		max7315 = <&pca>,"compatible=maxim,max7315";
+		pca6107 = <&pca>,"compatible=ti,pca6107";
+		tca6408 = <&pca>,"compatible=ti,tca6408";
+		tca6416 = <&pca>,"compatible=ti,tca6416";
+		tca6424 = <&pca>,"compatible=ti,tca6424";
+		tca9539 = <&pca>,"compatible=ti,tca9539";
+		tca9554 = <&pca>,"compatible=ti,tca9554";
+		cat9554 = <&pca>,"compatible=onnn,cat9554";
+		pca9654 = <&pca>,"compatible=onnn,pca9654";
+		xra1202 = <&pca>,"compatible=exar,xra1202";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pcf857x-overlay.dts b/arch/arm/boot/dts/overlays/pcf857x-overlay.dts
new file mode 100644
index 000000000000..6b81d32c3818
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pcf857x-overlay.dts
@@ -0,0 +1,34 @@
+// Definitions for PCF857X GPIO Extender from NXP
+
+/dts-v1/;
+/plugin/;
+
+#include "i2c-buses.dtsi"
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2cbus>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			pcf857x: pcf857x@0 {
+				compatible = "";
+				reg = <0x00>;
+				gpio-controller;
+				#gpio-cells = <2>;
+			};
+		};
+	};
+
+	__overrides__ {
+		pcf8574  = <&pcf857x>,"compatible=nxp,pcf8574",  <&pcf857x>,"reg:0=0x20";
+		pcf8574a = <&pcf857x>,"compatible=nxp,pcf8574a", <&pcf857x>,"reg:0=0x38";
+		pcf8575  = <&pcf857x>,"compatible=nxp,pcf8575",  <&pcf857x>,"reg:0=0x20";
+		pca8574  = <&pcf857x>,"compatible=nxp,pca8574",  <&pcf857x>,"reg:0=0x20";
+		addr = <&pcf857x>,"reg:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pcie-32bit-dma-overlay.dts b/arch/arm/boot/dts/overlays/pcie-32bit-dma-overlay.dts
new file mode 100644
index 000000000000..955703563df7
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pcie-32bit-dma-overlay.dts
@@ -0,0 +1,38 @@
+/*
+ * pcie-32bit-dma-overlay.dts
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target-path = "/aliases";
+		__overlay__ {
+			/*
+			 * Removing this alias stops the firmware patching the
+			 * PCIE DT dma-ranges based on the detected chip
+			 * revision.
+			 */
+			pcie0 = "";
+		};
+	};
+
+	fragment@1 {
+		target = <&pcie0>;
+		__overlay__ {
+			/*
+			 * The size of the range is rounded up to a power of 2,
+			 * so the range ends up being 0-4GB, and the MSI vector
+			 * gets pushed beyond 4GB.
+			 */
+			#address-cells = <3>;
+			#size-cells = <2>;
+			dma-ranges = <0x02000000 0x0 0x00000000 0x0 0x00000000
+				      0x0 0x80000000>;
+		};
+	};
+
+};
diff --git a/arch/arm/boot/dts/overlays/pcie-32bit-dma-pi5-overlay.dts b/arch/arm/boot/dts/overlays/pcie-32bit-dma-pi5-overlay.dts
new file mode 100644
index 000000000000..b8ce3bc53b80
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pcie-32bit-dma-pi5-overlay.dts
@@ -0,0 +1,27 @@
+/*
+ * pcie-32bit-dma-pi5-overlay.dts
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&pcie1>;
+		__overlay__ {
+			/*
+			 * The size of the range is rounded up to a power of 2,
+			 * so the range ends up being 0-4GB, and the MSI vector
+			 * gets pushed beyond 4GB.
+			 */
+			#address-cells = <3>;
+			#size-cells = <2>;
+			msi-parent = <&pciex1>;
+			dma-ranges = <0x02000000 0x0 0x00000000 0x0 0x00000000
+				      0x0 0x80000000>;
+		};
+	};
+
+};
diff --git a/arch/arm/boot/dts/overlays/pciex1-compat-pi5-overlay.dts b/arch/arm/boot/dts/overlays/pciex1-compat-pi5-overlay.dts
new file mode 100644
index 000000000000..1f5adfc4add1
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pciex1-compat-pi5-overlay.dts
@@ -0,0 +1,60 @@
+/*
+ * Various feature switches for the 1-lane PCIe controller on Pi 5
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	/* Enable L1 sub-state support */
+	fragment@0 {
+		target = <&pciex1>;
+		__dormant__ {
+			brcm,clkreq-mode = "default";
+		};
+	};
+
+	/* Disable ASPM L0s */
+	fragment@1 {
+		target = <&pciex1>;
+		__dormant__ {
+			aspm-no-l0s;
+		};
+	};
+
+	/* Use RC MSI target instead of MIP MSIx target */
+	fragment@2 {
+		target = <&pciex1>;
+		__dormant__ {
+			msi-parent = <&pciex1>;
+		};
+	};
+
+	/*
+	 * Shift the start of the 32bit outbound window to 2GB,
+	 * so there are no BARs starting at 0x0. Expand the 64bit
+	 * outbound window to use the spare 2GB.
+	 */
+	fragment@3 {
+		target = <&pciex1>;
+		__dormant__ {
+			#address-cells = <3>;
+			#size-cells = <2>;
+			ranges = <0x02000000 0x00 0x80000000
+				  0x1b 0x80000000
+				  0x00 0x7ffffffc>,
+				 <0x43000000 0x04 0x00000000
+				  0x18 0x00000000
+				  0x03 0x80000000>;
+		};
+	};
+
+	__overrides__ {
+		l1ss = <0>, "+0";
+		no-l0s = <0>, "+1";
+		no-mip = <0>, "+2";
+		mmio-hi = <0>, "+3";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pibell-overlay.dts b/arch/arm/boot/dts/overlays/pibell-overlay.dts
new file mode 100644
index 000000000000..99d4b6d97969
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pibell-overlay.dts
@@ -0,0 +1,81 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+    compatible = "brcm,bcm2835";
+
+    fragment@0 {
+        target-path = "/";
+        __overlay__ {
+            codec_out: spdif-transmitter {
+                #address-cells = <0>;
+                #size-cells = <0>;
+                #sound-dai-cells = <0>;
+                compatible = "linux,spdif-dit";
+                status = "okay";
+            };
+
+            codec_in: card-codec {
+                #sound-dai-cells = <0>;
+                compatible = "invensense,ics43432";
+                status = "okay";
+            };
+        };
+    };
+
+    fragment@1 {
+        target = <&i2s_clk_producer>;
+        __overlay__ {
+            #sound-dai-cells = <0>;
+            status = "okay";
+        };
+    };
+
+    fragment@2 {
+        target = <&sound>;
+        snd: __overlay__ {
+            compatible = "simple-audio-card";
+            simple-audio-card,name = "PiBell";
+
+            status="okay";
+
+            capture_link: simple-audio-card,dai-link@0 {
+                format = "i2s";
+
+                r_cpu_dai: cpu {
+                    sound-dai = <&i2s_clk_producer>;
+
+/* example TDM slot configuration
+                    dai-tdm-slot-num = <2>;
+                    dai-tdm-slot-width = <32>;
+*/
+                };
+
+                r_codec_dai: codec {
+                    sound-dai = <&codec_in>;
+                };
+            };
+
+            playback_link: simple-audio-card,dai-link@1 {
+                format = "i2s";
+
+                p_cpu_dai: cpu {
+                    sound-dai = <&i2s_clk_producer>;
+
+/* example TDM slot configuration
+                    dai-tdm-slot-num = <2>;
+                    dai-tdm-slot-width = <32>;
+*/
+                };
+
+                p_codec_dai: codec {
+                    sound-dai = <&codec_out>;
+                };
+            };
+        };
+    };
+
+    __overrides__ {
+        alsaname = <&snd>, "simple-audio-card,name";
+    };
+};
diff --git a/arch/arm/boot/dts/overlays/pifacedigital-overlay.dts b/arch/arm/boot/dts/overlays/pifacedigital-overlay.dts
new file mode 100644
index 000000000000..532a858683d6
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pifacedigital-overlay.dts
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PiFace Digital, Device Tree Overlay.
+ * Copyright (C) 2020 Thomas Preston <thomas.preston@codethink.co.uk>
+ *
+ * The PiFace Digital is a convenient breakout board for the Microchip mcp23s17
+ * SPI GPIO port expander.
+ *
+ * The first eight GPIOs 0..7 (bank A) are connected to eight output terminals
+ * and LEDs, plus two relays on the first two outputs. These output loads are
+ * active-high.
+ *
+ * The next eight GPIOs 8..15 (bank B) are connected to eight input terminals
+ * with four on-board switches connecting them to ground. Inputs devices are
+ * therefore expected to bridge terminals to ground, so the mcp23s17 pullups are
+ * activated for GPIO bank B.
+ *
+ * On PiFace Digital, the mcp23s17 is connected to the Raspberry Pi's SPI0 CS0
+ * bus. Each SPI bus supports up to eight addressable child devices. The PiFace
+ * Digital only supports addresses 0-4, which can be configured by jumpers JP1
+ * and JP2.
+ *
+ * You can tell the driver about these jumper configurations with the
+ * spi-present-mask bitmask:
+ *
+ *     | JP1 | JP2 | dtoverlay line in /boot/config.txt         |
+ *     | --- | --- | ------------------------------------------ |
+ *     |  0  |  0  | dtoverlay=pifacedigital                    |
+ *     |  0  |  0  | dtoverlay=pifacedigital:spi-present-mask=1 |
+ *     |  0  |  1  | dtoverlay=pifacedigital:spi-present-mask=2 |
+ *     |  1  |  0  | dtoverlay=pifacedigital:spi-present-mask=4 |
+ *     |  1  |  1  | dtoverlay=pifacedigital:spi-present-mask=8 |
+ *
+ * # Example
+ * Set the dtoverlay config in /boot/config.txt and power off the Raspberry Pi:
+ *
+ *     $ grep pifacedigital /boot/config.txt
+ *     dtoverlay=pifacedigital
+ *     $ sudo systemctl poweroff
+ *
+ * Attach the PiFace Digital and power on the Raspberry Pi.
+ * Then use the libgpiod tools to query the device:
+ *
+ *     $ sudo apt install gpiod
+ *     $ gpiodetect | grep mcp23s17
+ *     gpiochip2 [mcp23s17.0] (16 lines)
+ *
+ * Set GPIO outputs 0, 2 and 5:
+ *
+ *     $ gpioset gpiochip2 0=1 2=1 5=1
+ *
+ * Get GPIO status (input GPIO 8..15 are high, because they are active-low):
+ *
+ *     $ gpioget gpiochip2 {8..15}
+ *     1 1 1 1 1 1 1 1
+ *
+ * And even monitor interrupts:
+ *
+ *     $ gpiomon gpiochip2 {8..15}
+ *     event: FALLING EDGE offset: 11 timestamp: [1597361662.926741667]
+ *     event:  RISING EDGE offset: 11 timestamp: [1597361663.062555051]
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	/* Disable exposing /dev/spidev0.0 */
+	fragment@0 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	/* Add the PiFace Digital device node to the spi0.0 device. */
+	fragment@1 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			pfdigital: pifacedigital@0 {
+				compatible = "microchip,mcp23s17";
+				reg = <0>;
+
+				/* Set devices present with 8-bit mask. */
+				microchip,spi-present-mask = <0x01>;
+				spi-max-frequency = <500000>;
+
+				gpio-controller;
+				#gpio-cells = <2>;
+
+				/* This device can pass through interrupts. */
+				interrupt-controller;
+				#interrupt-cells = <2>;
+
+				/* INTB is connected to GPIO 25.
+				 * 0x8 active-low level-sensitive
+				 */
+				interrupts = <25 0x8>;
+				interrupt-parent = <&gpio>;
+
+				/* Configure pull-ups on bank B GPIOs */
+				pinctrl-0 = <&pfdigital_irq &pfdigital_pullups>;
+				pinctrl-names = "default";
+				pfdigital_pullups: pinmux {
+					pins =
+						"gpio8",
+						"gpio9",
+						"gpio10",
+						"gpio11",
+						"gpio12",
+						"gpio13",
+						"gpio14",
+						"gpio15";
+					bias-pull-up;
+				};
+			};
+		};
+	};
+
+	/* PiFace Digital mcp23s17 INTB pin is connected to GPIO 25. The INTB
+	 * pin is configured active-low (0 on interrupt), so expect to see
+	 * FALLING_EDGE when inputs are bridged to ground (switch is pressed).
+	 */
+	fragment@3 {
+		target = <&gpio>;
+		__overlay__ {
+			pfdigital_irq: pifacedigital_irq {
+				brcm,pins = <25>;
+				brcm,function = <0>; /* input */
+			};
+		};
+	};
+
+	__overrides__ {
+		spi-present-mask = <&pfdigital>, "microchip,spi-present-mask:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pifi-40-overlay.dts b/arch/arm/boot/dts/overlays/pifi-40-overlay.dts
new file mode 100644
index 000000000000..d9ef4ea4097e
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pifi-40-overlay.dts
@@ -0,0 +1,50 @@
+// Definitions for PiFi-40 Amp
+/dts-v1/;
+/plugin/;
+#include <dt-bindings/gpio/gpio.h>
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			tas5711l: audio-codec@1a {
+				compatible = "ti,tas5711";
+				reg = <0x1a>;
+				#sound-dai-cells = <0>;
+				sound-name-prefix = "Left";
+				status = "okay";
+			};
+
+			tas5711r: audio-codec@1b {
+				compatible = "ti,tas5711";
+				reg = <0x1b>;
+				#sound-dai-cells = <0>;
+				sound-name-prefix = "Right";
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		pifi_40: __overlay__ {
+			compatible = "pifi,pifi-40";
+			audio-codec = <&tas5711l &tas5711r>;
+			i2s-controller = <&i2s_clk_producer>;
+			pdn-gpios = <&gpio 23 1>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pifi-dac-hd-overlay.dts b/arch/arm/boot/dts/overlays/pifi-dac-hd-overlay.dts
new file mode 100644
index 000000000000..236098365dc2
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pifi-dac-hd-overlay.dts
@@ -0,0 +1,49 @@
+// Overlay for PiFi-DAC-HD
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells =<0>;
+
+			pcm5142: pcm5142@4c {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5142";
+				reg = <0x4c>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "simple-audio-card";
+			simple-audio-card,name = "PiFi-DAC-HD";
+			status = "okay";
+
+			simple-audio-card,dai-link@1 {
+				format = "i2s";
+				cpu {
+					sound-dai = <&i2s_clk_producer>;
+				};
+				codec {
+					sound-dai = <&pcm5142>;
+				};
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pifi-dac-zero-overlay.dts b/arch/arm/boot/dts/overlays/pifi-dac-zero-overlay.dts
new file mode 100644
index 000000000000..dd272388779e
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pifi-dac-zero-overlay.dts
@@ -0,0 +1,49 @@
+// Overlay for PiFi-DAC-Zero
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "simple-audio-card";
+			simple-audio-card,name = "PiFi-DAC-Zero";
+			status = "okay";
+
+			simple-audio-card,dai-link@1 {
+				format = "i2s";
+
+				cpu {
+					sound-dai = <&i2s_clk_producer>;
+					dai-tdm-slot-num = <2>;
+					dai-tdm-slot-width = <32>;
+				};
+
+				codec {
+					sound-dai = <&codec_out>;
+				};
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			codec_out: pcm5102a-codec {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5102a";
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			#sound-dai-cells = <0>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pifi-mini-210-overlay.dts b/arch/arm/boot/dts/overlays/pifi-mini-210-overlay.dts
new file mode 100644
index 000000000000..a7b857144a48
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pifi-mini-210-overlay.dts
@@ -0,0 +1,42 @@
+// Definitions for PiFi Mini 210
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			tas5711@1a {
+				#sound-dai-cells = <0>;
+				compatible = "ti,tas5711";
+				reg = <0x1a>;
+				status = "okay";
+				pdn-gpios = <&gpio 23 1>;
+				reset-gpios = <&gpio 24 1>;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "pifi,pifi-mini-210";
+			i2s-controller = <&i2s_clk_producer>;
+
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/piglow-overlay.dts b/arch/arm/boot/dts/overlays/piglow-overlay.dts
new file mode 100644
index 000000000000..075bceef158c
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/piglow-overlay.dts
@@ -0,0 +1,97 @@
+// Definitions for SN3218 LED driver from Si-En Technology on PiGlow
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c_arm>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			sn3218@54 {
+				compatible = "si-en,sn3218";
+				reg = <0x54>;
+				#address-cells = <1>;
+				#size-cells = <0>;
+				status = "okay";
+
+				led@1 {
+					reg = <1>;
+					label = "piglow:red:led1";
+				};
+				led@2 {
+					reg = <2>;
+					label = "piglow:orange:led2";
+				};
+				led@3 {
+					reg = <3>;
+					label = "piglow:yellow:led3";
+				};
+				led@4 {
+					reg = <4>;
+					label = "piglow:green:led4";
+				};
+				led@5 {
+					reg = <5>;
+					label = "piglow:blue:led5";
+				};
+				led@6 {
+					reg = <6>;
+					label = "piglow:green:led6";
+				};
+				led@7 {
+					reg = <7>;
+					label = "piglow:red:led7";
+				};
+				led@8 {
+					reg = <8>;
+					label = "piglow:orange:led8";
+				};
+				led@9 {
+					reg = <9>;
+					label = "piglow:yellow:led9";
+				};
+				led@10 {
+					reg = <10>;
+					label = "piglow:white:led10";
+				};
+				led@11 {
+					reg = <11>;
+					label = "piglow:white:led11";
+				};
+				led@12 {
+					reg = <12>;
+					label = "piglow:blue:led12";
+				};
+				led@13 {
+					reg = <13>;
+					label = "piglow:white:led13";
+				};
+				led@14 {
+					reg = <14>;
+					label = "piglow:green:led14";
+				};
+				led@15 {
+					reg = <15>;
+					label = "piglow:blue:led15";
+				};
+				led@16 {
+					reg = <16>;
+					label = "piglow:yellow:led16";
+				};
+				led@17 {
+					reg = <17>;
+					label = "piglow:orange:led17";
+				};
+				led@18 {
+					reg = <18>;
+					label = "piglow:red:led18";
+				};
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pimidi-overlay.dts b/arch/arm/boot/dts/overlays/pimidi-overlay.dts
new file mode 100644
index 000000000000..183738474afb
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pimidi-overlay.dts
@@ -0,0 +1,53 @@
+/*
+ * Pimidi Linux kernel module.
+ * Copyright (C) 2017-2024  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c_arm>;
+		__overlay__ {
+			status = "okay";
+
+			pimidi_ctrl: pimidi_ctrl@20 {
+				compatible = "blokaslabs,pimidi";
+
+				reg = <0x20>;
+				status = "okay";
+
+				interrupt-parent = <&gpio>;
+				interrupts = <23 IRQ_TYPE_LEVEL_LOW>;
+				interrupt-names = "data_ready";
+				interrupt-controller;
+				#interrupt-cells = <2>;
+
+				data-ready-gpios = <&gpio 23 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
+				reset-gpios = <&gpio 22 (GPIO_ACTIVE_LOW | GPIO_PULL_UP)>;
+			};
+		};
+	};
+
+	__overrides__ {
+		sel = <&pimidi_ctrl>,"reg:0{0=0x20,1=0x21,2=0x22,3=0x23}",
+			<&pimidi_ctrl>,"data-ready-gpios:4{0=23,1=5,2=6,3=27}",
+			<&pimidi_ctrl>,"interrupts:0{0=23,1=5,2=6,3=27}";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pineboards-hat-ai-overlay.dts b/arch/arm/boot/dts/overlays/pineboards-hat-ai-overlay.dts
new file mode 100644
index 000000000000..8160272f4705
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pineboards-hat-ai-overlay.dts
@@ -0,0 +1,18 @@
+/*
+ * Device Tree overlay for Pineboards Hat Ai!.
+ * Compatible with the Google Coral Edge TPU (Single and Dual Edge).
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&pcie1>;
+		__overlay__ {
+			msi-parent = <&pcie1>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pineboards-hatdrive-poe-plus-overlay.dts b/arch/arm/boot/dts/overlays/pineboards-hatdrive-poe-plus-overlay.dts
new file mode 100644
index 000000000000..77b8e0d3be31
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pineboards-hatdrive-poe-plus-overlay.dts
@@ -0,0 +1,19 @@
+/*
+ * Device Tree overlay for Pineboards HatDrive! PoE+.
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target-path = "/chosen";
+		__overlay__ {
+			power: power {
+				hat_current_supply = <5000>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/piscreen-overlay.dts b/arch/arm/boot/dts/overlays/piscreen-overlay.dts
new file mode 100644
index 000000000000..24302314323b
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/piscreen-overlay.dts
@@ -0,0 +1,110 @@
+/*
+ * Device Tree overlay for PiScreen 3.5" display shield by Ozzmaker
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&gpio>;
+		__overlay__ {
+			piscreen_pins: piscreen_pins {
+				brcm,pins = <17 25 24 22>;
+				brcm,function = <0 1 1 1>; /* in out out out */
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			piscreen: piscreen@0{
+				compatible = "ilitek,ili9486";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&piscreen_pins>;
+
+				spi-max-frequency = <24000000>;
+				rotate = <270>;
+				bgr;
+				fps = <30>;
+				buswidth = <8>;
+				regwidth = <16>;
+				reset-gpios = <&gpio 25 GPIO_ACTIVE_LOW>;
+				dc-gpios = <&gpio 24 GPIO_ACTIVE_HIGH>;
+				led-gpios = <&gpio 22 GPIO_ACTIVE_HIGH>;
+				debug = <0>;
+
+				init = <0x10000b0 0x00
+				        0x1000011
+					0x20000ff
+					0x100003a 0x55
+					0x1000036 0x28
+					0x10000c2 0x44
+					0x10000c5 0x00 0x00 0x00 0x00
+					0x10000e0 0x0f 0x1f 0x1c 0x0c 0x0f 0x08 0x48 0x98 0x37 0x0a 0x13 0x04 0x11 0x0d 0x00
+					0x10000e1 0x0f 0x32 0x2e 0x0b 0x0d 0x05 0x47 0x75 0x37 0x06 0x10 0x03 0x24 0x20 0x00
+					0x10000e2 0x0f 0x32 0x2e 0x0b 0x0d 0x05 0x47 0x75 0x37 0x06 0x10 0x03 0x24 0x20 0x00
+					0x1000011
+					0x1000029>;
+			};
+
+			piscreen_ts: piscreen-ts@1 {
+				compatible = "ti,ads7846";
+				reg = <1>;
+
+				spi-max-frequency = <2000000>;
+				interrupts = <17 2>; /* high-to-low edge triggered */
+				interrupt-parent = <&gpio>;
+				pendown-gpio = <&gpio 17 GPIO_ACTIVE_LOW>;
+				touchscreen-swapped-x-y;
+				ti,x-plate-ohms = /bits/ 16 <100>;
+				ti,pressure-max = /bits/ 16 <255>;
+			};
+		};
+	};
+	__overrides__ {
+		speed =		<&piscreen>,"spi-max-frequency:0";
+		rotate =	<&piscreen>,"rotate:0",
+				<&piscreen>,"rotation:0";
+		fps =		<&piscreen>,"fps:0";
+		debug =		<&piscreen>,"debug:0";
+		xohms =		<&piscreen_ts>,"ti,x-plate-ohms;0";
+		drm =		<&piscreen>,"compatible=waveshare,rpi-lcd-35",
+				<&piscreen>,"reset-gpios:8=",<GPIO_ACTIVE_HIGH>;
+		invx =		<&piscreen_ts>,"touchscreen-inverted-x?";
+		invy =		<&piscreen_ts>,"touchscreen-inverted-y?";
+		swapxy =	<&piscreen_ts>,"touchscreen-swapped-x-y!";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/piscreen2r-overlay.dts b/arch/arm/boot/dts/overlays/piscreen2r-overlay.dts
new file mode 100644
index 000000000000..4468f4a54bf7
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/piscreen2r-overlay.dts
@@ -0,0 +1,106 @@
+ /*
+ * Device Tree overlay for PiScreen2 3.5" TFT with resistive touch  by Ozzmaker.com
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&gpio>;
+		__overlay__ {
+			piscreen2_pins: piscreen2_pins {
+				brcm,pins = <17 25 24 22>;
+				brcm,function = <0 1 1 1>; /* in out out out */
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			piscreen2: piscreen2@0{
+				compatible = "ilitek,ili9486";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&piscreen2_pins>;
+				bgr;
+				spi-max-frequency = <64000000>;
+				rotate = <90>;
+				fps = <30>;
+				buswidth = <8>;
+				regwidth = <16>;
+				txbuflen = <32768>;
+				reset-gpios = <&gpio 25 1>;
+				dc-gpios = <&gpio 24 0>;
+				led-gpios = <&gpio 22 0>;
+				debug = <0>;
+
+                                init = <0x10000b0 0x00
+                                        0x1000011
+                                        0x20000ff
+                                        0x100003a 0x55
+                                        0x1000036 0x28
+                                        0x10000c0 0x11 0x09
+                                        0x10000c1 0x41
+                                        0x10000c5 0x00 0x00 0x00 0x00
+                                        0x10000b6 0x00 0x02
+                                        0x10000f7 0xa9 0x51 0x2c 0x2
+                                        0x10000be 0x00 0x04
+                                        0x10000e9 0x00
+                                        0x1000011
+                                        0x1000029>;
+
+			};
+
+			piscreen2_ts: piscreen2-ts@1 {
+				compatible = "ti,ads7846";
+				reg = <1>;
+
+				spi-max-frequency = <2000000>;
+				interrupts = <17 2>; /* high-to-low edge triggered */
+				interrupt-parent = <&gpio>;
+				pendown-gpio = <&gpio 17 1>;
+				ti,swap-xy;
+				ti,x-plate-ohms = /bits/ 16 <100>;
+				ti,pressure-max = /bits/ 16 <255>;
+			};
+		};
+	};
+	__overrides__ {
+		speed =		<&piscreen2>,"spi-max-frequency:0";
+		rotate =	<&piscreen2>,"rotate:0";
+		fps =		<&piscreen2>,"fps:0";
+		debug =		<&piscreen2>,"debug:0";
+		xohms =		<&piscreen2_ts>,"ti,x-plate-ohms;0";
+	};
+};
+
diff --git a/arch/arm/boot/dts/overlays/pisound-micro-overlay.dts b/arch/arm/boot/dts/overlays/pisound-micro-overlay.dts
new file mode 100644
index 000000000000..84562df6ca2b
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pisound-micro-overlay.dts
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/pinctrl/bcm2835.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			pisound_micro_pins: pisound_micro_pins {
+				brcm,pins = <16 26>;
+				brcm,function = <BCM2835_FSEL_GPIO_IN BCM2835_FSEL_GPIO_IN>;
+				brcm,pull = <BCM2835_PUD_UP BCM2835_PUD_UP>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			clk_codec: clk-codec {
+				compatible = "fixed-clock";
+				#clock-cells = <0>;
+				clock-frequency = <12000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c_arm>;
+		__overlay__ {
+			status = "okay";
+
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			pisound_micro_ctrl: pisound_micro_ctrl@12 {
+				compatible = "blokas,upisnd-ctrl";
+
+				pinctrl-names = "default";
+				pinctrl-0 = <&pisound_micro_pins>;
+
+				reg = <0x12>;
+				status = "okay";
+
+				interrupt-parent = <&gpio>;
+				interrupt-controller;
+				interrupts = <26 IRQ_TYPE_LEVEL_LOW>;
+				interrupt-names = "data_available";
+
+				gpio-controller;
+				#gpio-cells = <2>;
+
+				data_available-gpios = <&gpio 26 GPIO_ACTIVE_LOW>;
+				reset-gpios = <&gpio 16 GPIO_ACTIVE_LOW>;
+			};
+
+			pisound_micro_codec: upisnd_codec@3b {
+				#sound-dai-cells = <0>;
+				compatible = "blokas,upisnd-codec";
+				reg = <0x3b>;
+
+				clock-names = "mclk";
+				clocks = <&clk_codec>;
+
+				// Additional options with default values and alternative values:
+
+				//input-mode    = "differential"; // Or "single-ended"
+
+				//hp-out-mode   = "headphone";    // Or "capless-headphone"
+								  // Or "line-out"
+
+				//line-out-mode = "line-out";     // Or "headphone"
+				//mono-out-mode = "line-out";     // Or "headphone"
+
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "blokas,pisound-micro";
+			codec = <&pisound_micro_codec>;
+			ctrl = <&pisound_micro_ctrl>;
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+		};
+	};
+
+	fragment@4 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		input-mode=<&pisound_micro_codec>,"input-mode{single-ended,differential}";
+		hp-out-mode=<&pisound_micro_codec>,
+			"hp-out-mode{capless-headphone,headphone,line-out}";
+		line-out-mode=<&pisound_micro_codec>,"line-out-mode{line-out,headphone}";
+		mono-out-mode=<&pisound_micro_codec>,"mono-out-mode{line-out,headphone}";
+	};
+
+	__exports__ {
+		pisound_micro_ctrl;
+	};
+};
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/arch/arm/boot/dts/overlays/pisound-overlay.dts b/arch/arm/boot/dts/overlays/pisound-overlay.dts
new file mode 100644
index 000000000000..226bcbdf8a09
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pisound-overlay.dts
@@ -0,0 +1,118 @@
+/*
+ * Pisound Linux kernel module.
+ * Copyright (C) 2016-2024  Vilniaus Blokas UAB, https://blokas.io/pisound
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@3 {
+		target = <&spi0>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			pisound_spi: pisound_spi@0{
+				compatible = "blokaslabs,pisound-spi";
+				reg = <0>;
+				spi-max-frequency = <1000000>;
+				spi-speed-hz = <150000>;
+			};
+		};
+	};
+
+	fragment@4 {
+		target-path = "/";
+		__overlay__ {
+			pcm5102a-codec {
+				#sound-dai-cells = <0>;
+				compatible = "ti,pcm5102a";
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@5 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "blokaslabs,pisound";
+			i2s-controller = <&i2s_clk_consumer>;
+			spi-controller = <&pisound_spi>;
+			status = "okay";
+
+			pinctrl-names = "default";
+			pinctrl-0 = <&pisound_button_pins>;
+
+			osr-gpios =
+				<&gpio 13 GPIO_ACTIVE_HIGH>,
+				<&gpio 26 GPIO_ACTIVE_HIGH>,
+				<&gpio 16 GPIO_ACTIVE_HIGH>;
+
+			reset-gpios =
+				<&gpio 12 GPIO_ACTIVE_HIGH>,
+				<&gpio 24 GPIO_ACTIVE_HIGH>;
+
+			data_available-gpios = <&gpio 25 GPIO_ACTIVE_HIGH>;
+
+			button-gpios = <&gpio 17 GPIO_ACTIVE_LOW>;
+		};
+	};
+
+	fragment@6 {
+		target = <&gpio>;
+		__overlay__ {
+			pisound_button_pins: pisound_button_pins {
+				brcm,pins = <17>;
+				brcm,function = <0>; // Input
+				brcm,pull = <2>; // Pull-Up
+			};
+		};
+	};
+
+	fragment@7 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pisound-pi5-overlay.dts b/arch/arm/boot/dts/overlays/pisound-pi5-overlay.dts
new file mode 100644
index 000000000000..0b07a6d03694
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pisound-pi5-overlay.dts
@@ -0,0 +1,28 @@
+/*
+ * Pisound Linux kernel module.
+ * Copyright (C) 2016-2024  Vilniaus Blokas UAB, https://blokas.io/pisound
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#include "pisound-overlay.dts"
+
+&pisound_spi {
+	spi-speed-hz = <100000>;
+};
+
+/ {
+	compatible = "brcm,bcm2712";
+};
diff --git a/arch/arm/boot/dts/overlays/pitft22-overlay.dts b/arch/arm/boot/dts/overlays/pitft22-overlay.dts
new file mode 100644
index 000000000000..5759d48aed57
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pitft22-overlay.dts
@@ -0,0 +1,71 @@
+/*
+ * Device Tree overlay for pitft by Adafruit
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+        };
+
+	fragment@1 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&gpio>;
+		__overlay__ {
+			pitft_pins: pitft_pins {
+				brcm,pins = <25>;
+				brcm,function = <1>; /* out */
+				brcm,pull = <0>; /* none */
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			pitft: pitft@0{
+				compatible = "ilitek,ili9340";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&pitft_pins>;
+
+				spi-max-frequency = <32000000>;
+				rotate = <90>;
+				fps = <25>;
+				bgr;
+				buswidth = <8>;
+				dc-gpios = <&gpio 25 0>;
+				debug = <0>;
+			};
+
+		};
+	};
+
+	__overrides__ {
+		speed =   <&pitft>,"spi-max-frequency:0";
+		rotate =  <&pitft>,"rotate:0", /* fbtft */
+				<&pitft>,"rotation:0"; /* drm */
+		fps =     <&pitft>,"fps:0";
+		debug =   <&pitft>,"debug:0";
+		drm =     <&pitft>,"compatible=adafruit,yx240qv29";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pitft28-capacitive-overlay.dts b/arch/arm/boot/dts/overlays/pitft28-capacitive-overlay.dts
new file mode 100644
index 000000000000..de98ee7b4496
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pitft28-capacitive-overlay.dts
@@ -0,0 +1,93 @@
+/*
+ * Device Tree overlay for Adafruit PiTFT 2.8" capacitive touch screen
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&gpio>;
+		__overlay__ {
+			pitft_pins: pitft_pins {
+				brcm,pins = <24 25>;
+				brcm,function = <0 1>; /* in out */
+				brcm,pull = <2 0>; /* pullup none */
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			pitft: pitft@0{
+				compatible = "ilitek,ili9340";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&pitft_pins>;
+
+				spi-max-frequency = <32000000>;
+				rotate = <90>;
+				fps = <25>;
+				bgr;
+				buswidth = <8>;
+				dc-gpios = <&gpio 25 0>;
+				debug = <0>;
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&i2c1>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			ft6236: ft6236@38 {
+				compatible = "focaltech,ft6236";
+				reg = <0x38>;
+
+				interrupt-parent = <&gpio>;
+				interrupts = <24 2>;
+				touchscreen-size-x = <240>;
+				touchscreen-size-y = <320>;
+			};
+		};
+	};
+
+	__overrides__ {
+		speed =   <&pitft>,"spi-max-frequency:0";
+		rotate =  <&pitft>,"rotate:0", /* fbtft */
+				<&pitft>,"rotation:0"; /* drm */
+		fps =     <&pitft>,"fps:0";
+		debug =   <&pitft>,"debug:0";
+		drm =     <&pitft>,"compatible=adafruit,yx240qv29";
+		touch-sizex = <&ft6236>,"touchscreen-size-x:0";
+		touch-sizey = <&ft6236>,"touchscreen-size-y:0";
+		touch-invx  = <&ft6236>,"touchscreen-inverted-x?";
+		touch-invy  = <&ft6236>,"touchscreen-inverted-y?";
+		touch-swapxy = <&ft6236>,"touchscreen-swapped-x-y?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pitft28-resistive-overlay.dts b/arch/arm/boot/dts/overlays/pitft28-resistive-overlay.dts
new file mode 100644
index 000000000000..bc2597179b9c
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pitft28-resistive-overlay.dts
@@ -0,0 +1,126 @@
+/*
+ * Device Tree overlay for Adafruit PiTFT 2.8" resistive touch screen
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&gpio>;
+		__overlay__ {
+			pitft_pins: pitft_pins {
+				brcm,pins = <24 25>;
+				brcm,function = <0 1>; /* in out */
+				brcm,pull = <2 0>; /* pullup none */
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			pitft: pitft@0{
+				compatible = "ilitek,ili9340";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&pitft_pins>;
+
+				spi-max-frequency = <32000000>;
+				rotate = <90>;
+				fps = <25>;
+				bgr;
+				buswidth = <8>;
+				dc-gpios = <&gpio 25 0>;
+				debug = <0>;
+			};
+
+			pitft_ts@1 {
+				/* needed to avoid dtc warning */
+				#address-cells = <1>;
+				#interrupt-cells = <1>;
+				compatible = "st,stmpe610";
+				reg = <1>;
+
+				spi-max-frequency = <500000>;
+				interrupts = <24 2>; /* high-to-low edge triggered */
+				interrupt-parent = <&gpio>;
+				interrupt-controller;
+
+				stmpe_touchscreen: stmpe_touchscreen {
+					compatible = "st,stmpe-ts";
+					st,sample-time = <4>;
+					st,mod-12b = <1>;
+					st,ref-sel = <0>;
+					st,adc-freq = <2>;
+					st,ave-ctrl = <3>;
+					st,touch-det-delay = <4>;
+					st,settling = <2>;
+					st,fraction-z = <7>;
+					st,i-drive = <0>;
+				};
+
+				stmpe_gpio: stmpe_gpio {
+					#gpio-cells = <2>;
+					compatible = "st,stmpe-gpio";
+					/*
+					 * only GPIO2 is wired/available
+					 * and it is wired to the backlight
+					 */
+					st,norequest-mask = <0x7b>;
+				};
+			};
+		};
+	};
+
+	fragment@5 {
+		target-path = "/soc";
+		__overlay__ {
+			backlight {
+				compatible = "gpio-backlight";
+				gpios = <&stmpe_gpio 2 0>;
+				default-on;
+			};
+		};
+	};
+
+	__overrides__ {
+		speed =   <&pitft>,"spi-max-frequency:0";
+		rotate =  <&pitft>,"rotate:0", /* fbtft */
+			  <&pitft>,"rotation:0"; /* drm */
+		fps =     <&pitft>,"fps:0";
+		debug =   <&pitft>,"debug:0";
+		drm =     <&pitft>,"compatible=adafruit,yx240qv29";
+		touch-invx  = <&stmpe_touchscreen>,"touchscreen-inverted-x?";
+		touch-invy  = <&stmpe_touchscreen>,"touchscreen-inverted-y?";
+		touch-swapxy = <&stmpe_touchscreen>,"touchscreen-swapped-x-y?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pitft35-resistive-overlay.dts b/arch/arm/boot/dts/overlays/pitft35-resistive-overlay.dts
new file mode 100644
index 000000000000..c3e81ef6003a
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pitft35-resistive-overlay.dts
@@ -0,0 +1,127 @@
+/*
+ * Device Tree overlay for Adafruit PiTFT 3.5" resistive touch screen
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&gpio>;
+		__overlay__ {
+			pitft_pins: pitft_pins {
+				brcm,pins = <24 25>;
+				brcm,function = <0 1>; /* in out */
+				brcm,pull = <2 0>; /* pullup none */
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			pitft: pitft@0{
+				compatible = "himax,hx8357d";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&pitft_pins>;
+
+				spi-max-frequency = <32000000>;
+				rotate = <90>;
+				fps = <25>;
+				bgr;
+				buswidth = <8>;
+				dc-gpios = <&gpio 25 0>;
+				debug = <0>;
+			};
+
+			pitft_ts@1 {
+				/* needed to avoid dtc warning */
+				#address-cells = <1>;
+				#interrupt-cells = <1>;
+				compatible = "st,stmpe610";
+				reg = <1>;
+
+				spi-max-frequency = <500000>;
+				interrupts = <24 2>; /* high-to-low edge triggered */
+				interrupt-parent = <&gpio>;
+				interrupt-controller;
+
+				stmpe_touchscreen: stmpe_touchscreen {
+					compatible = "st,stmpe-ts";
+					st,sample-time = <4>;
+					st,mod-12b = <1>;
+					st,ref-sel = <0>;
+					st,adc-freq = <2>;
+					st,ave-ctrl = <3>;
+					st,touch-det-delay = <4>;
+					st,settling = <2>;
+					st,fraction-z = <7>;
+					st,i-drive = <0>;
+				};
+
+				stmpe_gpio: stmpe_gpio {
+					#gpio-cells = <2>;
+					compatible = "st,stmpe-gpio";
+					/*
+					 * only GPIO2 is wired/available
+					 * and it is wired to the backlight
+					 */
+					st,norequest-mask = <0x7b>;
+				};
+			};
+		};
+	};
+
+	fragment@5 {
+		target-path = "/soc";
+		__overlay__ {
+			backlight: backlight {
+				compatible = "gpio-backlight";
+				gpios = <&stmpe_gpio 2 0>;
+				default-on;
+			};
+		};
+	};
+
+	__overrides__ {
+		speed =   <&pitft>,"spi-max-frequency:0";
+		rotate =  <&pitft>,"rotate:0", /* fbtft */
+			  <&pitft>,"rotation:0"; /* drm */
+		fps =     <&pitft>,"fps:0";
+		debug =   <&pitft>,"debug:0";
+		drm =     <&pitft>,"compatible=adafruit,yx350hv15",
+			  <&pitft>,"backlight:0=",<&backlight>;
+		touch-invx  = <&stmpe_touchscreen>,"touchscreen-inverted-x?";
+		touch-invy  = <&stmpe_touchscreen>,"touchscreen-inverted-y?";
+		touch-swapxy = <&stmpe_touchscreen>,"touchscreen-swapped-x-y?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pivision-overlay.dts b/arch/arm/boot/dts/overlays/pivision-overlay.dts
new file mode 100644
index 000000000000..496c726e0140
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pivision-overlay.dts
@@ -0,0 +1,155 @@
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&i2c_csi_dsi0>;
+		__overlay__ {
+			status = "okay";
+			pi4io: pi4io@44 {
+				compatible = "fcs,fxl6408";
+				reg = <0x44>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				status = "okay";
+			};
+
+			sn65dsi83: bridge@2c {
+				compatible = "ti,sn65dsi83";
+				reg = <0x2c>;
+				status = "okay";
+
+				ports {
+					#address-cells = <1>;
+					#size-cells = <0>;
+
+					port@0 {
+						reg = <0>;
+
+						in_dsi: endpoint {
+							remote-endpoint = <&dsi_out>;
+							data-lanes = <1 2 3 4>;
+						};
+					};
+
+					port@2 {
+						reg = <2>;
+
+						lvds_out: endpoint {
+							remote-endpoint = <&in_lvds>;
+						};
+					};
+				};
+			};
+
+			mxt1066td: touch@4a {
+				compatible = "atmel,maxtouch";
+				reg = <0x4a>;
+				interrupt-parent = <&rp1_gpio>;
+				interrupts = <34 IRQ_TYPE_EDGE_FALLING>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&rp1_gpio_34>;
+				reset-gpios = <&pi4io 1 GPIO_ACTIVE_LOW>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&dsi0>;
+		__overlay__{
+			status = "okay";
+			port {
+				dsi_out: endpoint {
+					remote-endpoint = <&in_dsi>;
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target-path = "/";
+		__overlay__ {
+			panel: panel {
+				compatible = "auo,g101ean02-2", "panel-lvds";
+				width-mm = <217>;
+				height-mm = <136>;
+				data-mapping = "vesa-24";
+				backlight = <&backlight>;
+				status = "okay";
+
+				panel-timing {
+					clock-frequency = <71100000>;
+					hactive = <1280>;
+					vactive = <800>;
+					hfront-porch = <50>;
+					hsync-len = <60>;
+					hback-porch = <50>;
+					vfront-porch = <7>;
+					vsync-len = <9>;
+					vback-porch = <7>;
+				};
+
+				port {
+					in_lvds: endpoint {
+						remote-endpoint = <&lvds_out>;
+					};
+				};
+			};
+
+			display_enable: display_enable {
+				compatible = "regulator-fixed";
+				regulator-name = "display_enable";
+				regulator-always-on;
+				enable-active-high;
+				gpio = <&pi4io 0 GPIO_ACTIVE_HIGH>;
+				status = "okay";
+			};
+
+			backlight: backlight {
+				compatible = "pwm-backlight";
+				pwms = <&pwm1 0 5000000 0>;
+				brightness-levels = <0 65535>;
+				num-interpolated-steps = <4096>;
+				default-brightness-level = <3000>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&rp1_gpio>;
+		__overlay__ {
+			rp1_gpio_34: rp1_gpio_34 {
+				function = "gpio";
+				pins = "gpio34";
+				bias-pull-up;
+			};
+			rp1_gpio_35: rp1_gpio_35 {
+				function = "pwm1";
+				pins = "gpio35";
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&cam0_reg>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@5 {
+		target = <&pwm1>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&rp1_gpio_35>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pps-gpio-overlay.dts b/arch/arm/boot/dts/overlays/pps-gpio-overlay.dts
new file mode 100644
index 000000000000..a4f6b868aad8
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pps-gpio-overlay.dts
@@ -0,0 +1,39 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			pps: pps@12 {
+				compatible = "pps-gpio";
+				pinctrl-names = "default";
+				pinctrl-0 = <&pps_pins>;
+				gpios = <&gpio 18 0>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			pps_pins: pps_pins@12 {
+				brcm,pins =     <18>;
+				brcm,function = <0>;    // in
+				brcm,pull =     <0>;    // off
+			};
+		};
+	};
+
+	__overrides__ {
+		gpiopin = <&pps>,"gpios:4",
+			  <&pps>,"reg:0",
+			  <&pps_pins>,"brcm,pins:0",
+			  <&pps_pins>,"reg:0";
+		assert_falling_edge = <&pps>,"assert-falling-edge?";
+		capture_clear = <&pps>,"capture-clear?";
+		pull = <&pps_pins>,"brcm,pull:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pps-rp1-overlay.dts b/arch/arm/boot/dts/overlays/pps-rp1-overlay.dts
new file mode 100644
index 000000000000..d0c9ade22e06
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pps-rp1-overlay.dts
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// This file is released under the GPL v2.0 License, and comes without warranty, express or implied.
+// (C) Josh Blake, 2026
+
+/*
+ *	dtparams:
+ *	pin: (integer) Specifies the PPS Input pin.
+ *		Takes the pin number correlating to the RPi 40-pin GPIO.
+ *		Defaults to GPIO 18.
+ *	pull-none: (boolean) Disable pull-up/down resistors on the PPS Input pin.
+ *	pull-up: (boolean) Enable pull-up resistor on the PPS Input pin.
+ *	pull-down: (boolean) Enable pull-down resistor on the PPS Input pin.
+ *	schmitt-trigger: (boolean) Enable the Schmitt-trigger on the PPS Input pin.
+ *		Defaults to off.
+ *	echo-pin: (integer) Specifies the PPS Echo Output pin.
+ *		Takes the pin number correlating to the RPi 40-pin GPIO.
+ *		Defaults to GPIO 17.
+ *	echo-ms: (integer) Specifies the pulse-width in ms that the echo signal outputs
+ *		on the PPS Echo Output pin.
+ *		Must be less than 999.
+ *		Defaults to 10ms.
+ *	echo: (boolean) Enables the PPS Echo Output signal.
+ *		Defaults to off.
+ *	capture-clear: (boolean) Captures the clear event time.
+ *		Defaults to off.
+ *	assert-falling-edge: (boolean) Uses the falling signal edge instead of the
+ *		rising signal edge.
+ *		Defaults to off.
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			pps: pps@ {
+				compatible = "pps-gpio";
+				pinctrl-names = "default";
+				pinctrl-0 = <&pps_gpios>;
+				gpios = <&gpio 18 0>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			pps_gpios: pps_gpios@ {
+				pps_in: pps_in@ {
+					function = "gpio";
+					input-enable;
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&pps_gpios>;
+		__dormant__ {
+			pps_out: pps_out@ {
+				function = "gpio";
+				output-enable;
+				drive-push-pull;
+				bias-disable;
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&pps>;
+		pps_echo: __dormant__ {
+			echo-gpios = <&gpio 17 0>;
+			echo-active-ms = <10>;
+		};
+	};
+
+	__overrides__ {
+	// A very annoying and inelegant pinctrl to pin number LUT
+		pin =
+			<&pps>,"reg:0{,=18}",
+			<&pps>,"gpios:4{,=18}",
+			<&pps_gpios>,"reg:0{,=18}",
+			<&pps_in>,"reg:0{,=18}",
+			<&pps_in>,"pins{2=gpio2,3=gpio3,4=gpio4,17=gpio17,27=gpio27,22=gpio22,10=gpio10,9=gpio9,11=gpio11,0=gpio0,5=gpio5,6=gpio6,13=gpio13,19=gpio19,26=gpio26,14=gpio14,15=gpio15,18=gpio18,23=gpio23,24=gpio24,25=gpio25,8=gpio8,7=gpio7,1=gpio1,12=gpio12,16=gpio16,20=gpio20,21=gpio21,=gpio18}";
+		pull-none = <&pps_in>,"bias-disable?";
+		pull-up = <&pps_in>,"bias-pull-up?";
+		pull-down = <&pps_in>,"bias-pull-down?";
+		schmitt-trigger = <&pps_in>,"input-enable!",<&pps_in>,"input-schmitt-enable?";
+		assert-falling-edge = <&pps>,"assert-falling-edge?";
+		capture-clear = <&pps>,"capture-clear?";
+		echo-pin =
+			<&pps_out>,"reg:0{,=17}",
+			<&pps_out>,"pins{2=gpio2,3=gpio3,4=gpio4,17=gpio17,27=gpio27,22=gpio22,10=gpio10,9=gpio9,11=gpio11,0=gpio0,5=gpio5,6=gpio6,13=gpio13,19=gpio19,26=gpio26,14=gpio14,15=gpio15,18=gpio18,23=gpio23,24=gpio24,25=gpio25,8=gpio8,7=gpio7,1=gpio1,12=gpio12,16=gpio16,20=gpio20,21=gpio21,=gpio17}",
+			<&pps_echo>,"echo-gpios:4{,=17}";
+		echo-ms = <&pps_echo>,"echo-active-ms:0";
+		echo = <0>,"+2+3";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/proto-codec-overlay.dts b/arch/arm/boot/dts/overlays/proto-codec-overlay.dts
new file mode 100644
index 000000000000..92f6ed158923
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/proto-codec-overlay.dts
@@ -0,0 +1,39 @@
+// Definitions for Rpi-Proto
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			wm8731@1a {
+				#sound-dai-cells = <0>;
+				compatible = "wlf,wm8731";
+				reg = <0x1a>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "rpi,rpi-proto";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pwm-2chan-overlay.dts b/arch/arm/boot/dts/overlays/pwm-2chan-overlay.dts
new file mode 100644
index 000000000000..823c8b4126d1
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pwm-2chan-overlay.dts
@@ -0,0 +1,48 @@
+/dts-v1/;
+/plugin/;
+
+/*
+This is the 2-channel overlay - only use it if you need both channels.
+
+Legal pin,function combinations for each channel:
+  PWM0: 12,4(Alt0) 18,2(Alt5) 40,4(Alt0)            52,5(Alt1)
+  PWM1: 13,4(Alt0) 19,2(Alt5) 41,4(Alt0) 45,4(Alt0) 53,5(Alt1)
+
+N.B.:
+  1) Pin 18 is the only one available on all platforms, and
+     it is the one used by the I2S audio interface.
+     Pins 12 and 13 might be better choices on an A+, B+ or Pi2.
+  2) The onboard analogue audio output uses both PWM channels.
+  3) So be careful mixing audio and PWM.
+*/
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			pwm_pins: pwm_pins {
+				brcm,pins = <18 19>;
+				brcm,function = <2 2>; /* Alt5 */
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&pwm>;
+		frag1: __overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&pwm_pins>;
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		pin   = <&pwm_pins>,"brcm,pins:0";
+		pin2  = <&pwm_pins>,"brcm,pins:4";
+		func  = <&pwm_pins>,"brcm,function:0";
+		func2 = <&pwm_pins>,"brcm,function:4";
+		clock = <&frag1>,"assigned-clock-rates:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pwm-gpio-fan-overlay.dts b/arch/arm/boot/dts/overlays/pwm-gpio-fan-overlay.dts
new file mode 100644
index 000000000000..dc5e586a3251
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pwm-gpio-fan-overlay.dts
@@ -0,0 +1,170 @@
+/*
+ * Overlay for a GPIO connected PWM cooling fan controlled by software GPIO PWM
+ *
+ * Optional parameters:
+ *  - "fan_gpio"	BCM number of the pin driving the fan, default 18 (GPIO18)
+ *
+ *  - "fan_temp0"	CPU temperature at which fan is started with low speed in millicelsius,
+ *			default 55000 (55 °C)
+ *  - "fan_temp1"	CPU temperature at which fan is switched to medium speed in millicelsius,
+ *			default 60000  (60 °C)
+ *  - "fan_temp2"	CPU temperature at which fan is switched to high speed in millicelsius,
+ *			default 67500  (67.5 °C)
+ *  - "fan_temp3"	CPU temperature at which fan is switched to max speed in millicelsius,
+ *			default 75000  (75 °C)
+ *  - "fan_temp0_hyst"	Temperature hysteris at which fan is stopped in millicelsius,
+ *			default 5000 (resulting in 50 °C)
+ *  - "fan_temp1_hyst"	Temperature hysteris at which fan is switched back to low speed
+ *			in millicelsius, default 5000 (resulting in 55 °C)
+ *  - "fan_temp2_hyst"	Temperature hysteris at which fan is switched back to medium speed
+ *			in millicelsius, default 5000 (resulting in 62.5 °C)
+ *  - "fan_temp3_hyst"	Temperature hysteris at which fan is switched back to high speed
+ *			in millicelsius, default 5000 (resulting in 70 °C)
+ *  - "fan_temp0_speed"	Fan speed for low cooling state in range 0 to 255,
+ *			default 114 (45% PWM duty cycle)
+ *  - "fan_temp1_speed"	Fan speed for medium cooling state in range 0 to 255,
+ *			default 152 (60% PWM duty cycle)
+ *  - "fan_temp2_speed"	Fan speed for high cooling state in range 0 to 255,
+ *			default 204 (80% PWM duty cycle)
+ *  - "fan_temp3_speed"	Fan speed for max cooling state in range 0 to 255,
+ *			default 255 (100% PWM duty cycle)
+ *
+ * N.B.
+ *  - Uses the software GPIO PWM kernel module instead of the Pis hardware PWMs (PWM0/PWM1).
+ *    This will allow for an undisturbed concurrent usage of the Pis analogue audio output.
+ *
+ * Requires:
+ *  - A PWM controlled cooling fan connected to the GPIO, such as an
+ *    Argon mini-fan, HighPi Pro Fan or Waveshare FAN-4020-PWM-5V
+ *  - Raspberry Pi OS Bookworm with kernel 6.6.62 or above
+ *
+ * Build:
+ *  - sudo dtc -I dts -O dtb -o /boot/firmware/overlays/pwm-gpiofan.dtbo pwm-gpiofan-overlay.dts
+ *
+ * Activate:
+ *  - sudo nano /boot/firmware/config.txt add "dtoverlay=pwm-gpiofan"
+ *
+ */
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			pwm_gpio_pins: pwm_gpio_pins {
+				brcm,pins = <18>; /* gpio-pin = 18 */
+				brcm,function = <1>; /* gpio function = output */
+				brcm,pull = <0>; /* gpio pull up/down = off */
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			pwm_gpio: pwm_gpio {
+				compatible="pwm-gpio";
+				#pwm-cells = <2>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&pwm_gpio_pins>;
+				gpios = <&gpio 18 0>; /* gpio-pin = 18 */
+			};
+		};
+	};
+
+	fragment@2 {
+		target-path = "/";
+		__overlay__ {
+			fan0: pwm-fan {
+				compatible = "pwm-fan";
+				#cooling-cells = <2>;
+				/* in ns = 20ms = 50 Hz */
+				pwms = <&pwm_gpio 0 20000000 0>;
+
+				cooling-min-state = <0>;
+				cooling-max-state = <4>;
+				/* PWM duty cycle values in a range from 0 to 255 */
+				/* which correspond to thermal cooling states 0 to 4 */
+				cooling-levels = <0 114 152 204 255>;
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&cpu_thermal>;
+		__overlay__ {
+			/* in ms = poll every 2s */
+			polling-delay = <2000>;
+		};
+	};
+
+	fragment@4 {
+		target = <&thermal_trips>;
+		__overlay__ {
+			/* below temperatures in millicelsius */
+			trip0: trip0 {
+				temperature = <55000>; /* 55 °C */
+				hysteresis = <5000>;   /*  5 °C */
+				type = "active";
+			};
+			trip1: trip1 {
+				temperature = <60000>; /* 60 °C */
+				hysteresis = <5000>;   /*  5 °C */
+				type = "active";
+			};
+			trip2: trip2 {
+				temperature = <67500>; /* 67.5 °C */
+				hysteresis = <5000>;   /*  5 °C */
+				type = "active";
+			};
+			trip3: trip3 {
+				temperature = <75000>; /* 75 °C */
+				hysteresis = <5000>;   /*  5 °C */
+				type = "active";
+			};
+		};
+	};
+
+	fragment@5 {
+		target = <&cooling_maps>;
+		__overlay__ {
+			map0 {
+				cooling-device = <&fan0 0 1>;
+				trip = <&trip0>;
+			};
+			map1 {
+				cooling-device = <&fan0 1 2>;
+				trip = <&trip1>;
+			};
+			map2 {
+				cooling-device = <&fan0 2 3>;
+				trip = <&trip2>;
+			};
+			map3 {
+				cooling-device = <&fan0 3 4>;
+				trip = <&trip3>;
+			};
+		};
+	};
+
+	__overrides__ {
+		fan_gpio =		<&pwm_gpio>,"gpios:4",
+					<&pwm_gpio_pins>,"brcm,pins:0";
+		fan_temp0 =		<&trip0>,"temperature:0";
+		fan_temp0_hyst =	<&trip0>,"hysteresis:0";
+		fan_temp0_speed =	<&fan0>,"cooling-levels:4";
+		fan_temp1 =		<&trip1>,"temperature:0";
+		fan_temp1_hyst =	<&trip1>,"hysteresis:0";
+		fan_temp1_speed =	<&fan0>,"cooling-levels:8";
+		fan_temp2 =		<&trip2>,"temperature:0";
+		fan_temp2_hyst =	<&trip2>,"hysteresis:0";
+		fan_temp2_speed =	<&fan0>,"cooling-levels:12";
+		fan_temp3 =		<&trip3>,"temperature:0";
+		fan_temp3_hyst =	<&trip3>,"hysteresis:0";
+		fan_temp3_speed =	<&fan0>,"cooling-levels:16";
+	};
+
+};
diff --git a/arch/arm/boot/dts/overlays/pwm-gpio-overlay.dts b/arch/arm/boot/dts/overlays/pwm-gpio-overlay.dts
new file mode 100644
index 000000000000..f5a1fb38e257
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pwm-gpio-overlay.dts
@@ -0,0 +1,38 @@
+// Device tree overlay for software GPIO PWM.
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			pwm_gpio_pins: pwm_gpio_pins@4 {
+				brcm,pins = <4>; /* gpio 4 */
+				brcm,function = <1>; /* output */
+				brcm,pull = <0>; /* pull-none */
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			pwm_gpio: pwm_gpio@4 {
+				  compatible = "pwm-gpio";
+				  pinctrl-names = "default";
+				  pinctrl-0 = <&pwm_gpio_pins>;
+				  gpios = <&gpio 4 0>;
+			};
+		};
+	};
+
+	__overrides__ {
+		gpio = <&pwm_gpio>,"gpios:4",
+		       <&pwm_gpio_pins>,"brcm,pins:0",
+		       /* modify reg values to allow multiple instantiation */
+		       <&pwm_gpio>,"reg:0",
+		       <&pwm_gpio_pins>,"reg:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pwm-ir-tx-overlay.dts b/arch/arm/boot/dts/overlays/pwm-ir-tx-overlay.dts
new file mode 100644
index 000000000000..33597eb79729
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pwm-ir-tx-overlay.dts
@@ -0,0 +1,40 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			pwm0_pins: pwm0_pins {
+				brcm,pins = <18>;
+				brcm,function = <2>; /* Alt5 */
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&pwm>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&pwm0_pins>;
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target-path = "/";
+		__overlay__ {
+			pwm-ir-transmitter {
+				compatible = "pwm-ir-tx";
+				pwms = <&pwm 0 100 0>;
+			};
+		};
+	};
+
+	__overrides__ {
+		gpio_pin = <&pwm0_pins>, "brcm,pins:0";
+		func = <&pwm0_pins>,"brcm,function:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pwm-overlay.dts b/arch/arm/boot/dts/overlays/pwm-overlay.dts
new file mode 100644
index 000000000000..32853492aaea
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pwm-overlay.dts
@@ -0,0 +1,44 @@
+/dts-v1/;
+/plugin/;
+
+/*
+Legal pin,function combinations for each channel:
+  PWM0: 12,4(Alt0) 18,2(Alt5) 40,4(Alt0)            52,5(Alt1)
+  PWM1: 13,4(Alt0) 19,2(Alt5) 41,4(Alt0) 45,4(Alt0) 53,5(Alt1)
+
+N.B.:
+  1) Pin 18 is the only one available on all platforms, and
+     it is the one used by the I2S audio interface.
+     Pins 12 and 13 might be better choices on an A+, B+ or Pi2.
+  2) The onboard analogue audio output uses both PWM channels.
+  3) So be careful mixing audio and PWM.
+*/
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			pwm_pins: pwm_pins {
+				brcm,pins = <18>;
+				brcm,function = <2>; /* Alt5 */
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&pwm>;
+		frag1: __overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&pwm_pins>;
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		pin   = <&pwm_pins>,"brcm,pins:0";
+		func  = <&pwm_pins>,"brcm,function:0";
+		clock = <&frag1>,"assigned-clock-rates:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pwm-pio-overlay.dts b/arch/arm/boot/dts/overlays/pwm-pio-overlay.dts
new file mode 100644
index 000000000000..3f105a8c21ce
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pwm-pio-overlay.dts
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0
+// Device tree overlay for RP1 PIO PWM.
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			pwm_pio_pins: pwm_pio_pins@4 {
+				brcm,pins = <4>; /* gpio 4 */
+				function = "pio";
+				bias-disable;
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			pwm_pio: pwm_pio@4 {
+				  compatible = "raspberrypi,pwm-pio-rp1";
+				  pinctrl-names = "default";
+				  pinctrl-0 = <&pwm_pio_pins>;
+				  gpios = <&gpio 4 0>;
+			};
+		};
+	};
+
+	__overrides__ {
+		gpio = <&pwm_pio>,"gpios:4",
+		       <&pwm_pio_pins>,"brcm,pins:0",
+		       /* modify reg values to allow multiple instantiation */
+		       <&pwm_pio>,"reg:0",
+		       <&pwm_pio_pins>,"reg:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/pwm1-overlay.dts b/arch/arm/boot/dts/overlays/pwm1-overlay.dts
new file mode 100644
index 000000000000..3324d4160653
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/pwm1-overlay.dts
@@ -0,0 +1,59 @@
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/pinctrl/bcm2835.h>
+
+/ {
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target = <&pins>;
+		__overlay__ {
+			brcm,pins = <40 41>;
+		};
+	};
+
+	fragment@1 {
+		target = <&pins>;
+		__dormant__ {
+			brcm,pins = <40>;
+		};
+	};
+
+	fragment@2 {
+		target = <&pins>;
+		__dormant__ {
+			brcm,pins = <41>;
+		};
+	};
+
+	fragment@3 {
+		target = <&gpio>;
+		__overlay__ {
+			pins: pwm1_overlay_pins {
+				brcm,pins = <40 41>;
+				brcm,function = <BCM2835_FSEL_ALT0>;
+				brcm,pull = <BCM2835_PUD_UP>;
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&pwm1>;
+		pwm: __overlay__ {
+			status = "okay";
+			pinctrl-names = "default";
+			pinctrl-0 = <&pins>;
+		};
+	};
+
+	__overrides__ {
+		clock = <&pwm>, "assigned-clock-rates:0";
+		pins_40_41 = <0>,"+0-1-2";
+		pins_40 = <0>,"-0+1-2";
+		pins_41 = <0>,"-0-1+2";
+		pull_up = <&pins>, "brcm,pull:0=", <BCM2835_PUD_UP>;
+		pull_down = <&pins>, "brcm,pull:0=", <BCM2835_PUD_DOWN>;
+		pull_off = <&pins>, "brcm,pull:0=", <BCM2835_PUD_OFF>;
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/qca7000-overlay.dts b/arch/arm/boot/dts/overlays/qca7000-overlay.dts
new file mode 100644
index 000000000000..0184c53801db
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/qca7000-overlay.dts
@@ -0,0 +1,55 @@
+// Overlay for the Qualcomm Atheros QCA7000 on PLC Stamp micro EVK
+// Visit: https://chargebyte.com -> Controllers & Modules -> Evaluation Tools -> PLC Stamp Micro 2 Evaluation Board for details
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@1 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			status = "okay";
+
+			eth1: qca7000@0 {
+				compatible = "qca,qca7000";
+				reg = <0>; /* CE0 */
+				pinctrl-names = "default";
+				pinctrl-0 = <&eth1_pins>;
+				interrupt-parent = <&gpio>;
+				interrupts = <23 0x1>; /* rising edge */
+				spi-max-frequency = <12000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&gpio>;
+		__overlay__ {
+			eth1_pins: eth1_pins {
+				brcm,pins = <23>;
+				brcm,function = <0>; /* in */
+				brcm,pull = <0>; /* none */
+			};
+		};
+	};
+
+	__overrides__ {
+		int_pin = <&eth1>, "interrupts:0",
+		          <&eth1_pins>, "brcm,pins:0";
+		speed   = <&eth1>, "spi-max-frequency:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/qca7000-uart0-overlay.dts b/arch/arm/boot/dts/overlays/qca7000-uart0-overlay.dts
new file mode 100644
index 000000000000..3e4af02d91e2
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/qca7000-uart0-overlay.dts
@@ -0,0 +1,46 @@
+// Overlay for the Qualcomm Atheros QCA7000 on PLC Stamp micro EVK
+// Visit: https://chargebyte.com -> Controllers & Modules -> Evaluation Tools -> PLC Stamp Micro 2 Evaluation Board for details
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&uart0>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&uart0_pins>;
+			status = "okay";
+
+			eth2: qca7000 {
+				compatible = "qca,qca7000";
+				current-speed = <115200>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			uart0_pins: uart0_ovl_pins {
+				brcm,pins = <14 15>;
+				brcm,function = <4>; /* alt0 */
+				brcm,pull = <0 2>;
+			};
+		};
+	};
+
+	fragment@2 {
+		target-path = "/aliases";
+		__overlay__ {
+			serial0 = "/soc/serial@7e201000";
+			serial1 = "/soc/serial@7e215040";
+		};
+	};
+
+	__overrides__ {
+		baudrate = <&eth2>, "current-speed:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/ramoops-overlay.dts b/arch/arm/boot/dts/overlays/ramoops-overlay.dts
new file mode 100644
index 000000000000..e5038658138d
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ramoops-overlay.dts
@@ -0,0 +1,25 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&rmem>;
+		__overlay__ {
+			ramoops: ramoops@b000000 {
+				compatible = "ramoops";
+				reg = <0x0b000000 0x10000>; /* 64kB */
+				record-size = <0x4000>; /* 16kB */
+				console-size = <0>; /* disabled by default */
+			};
+		};
+	};
+
+	__overrides__ {
+		base-addr = <&ramoops>,"reg:0";
+		total-size = <&ramoops>,"reg:4";
+		record-size = <&ramoops>,"record-size:0";
+		console-size = <&ramoops>,"console-size:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/ramoops-pi4-overlay.dts b/arch/arm/boot/dts/overlays/ramoops-pi4-overlay.dts
new file mode 100644
index 000000000000..1737e37f5724
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ramoops-pi4-overlay.dts
@@ -0,0 +1,25 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target = <&rmem>;
+		__overlay__ {
+			ramoops: ramoops@b000000 {
+				compatible = "ramoops";
+				reg = <0x0 0x0b000000 0x10000>; /* 64kB */
+				record-size = <0x4000>; /* 16kB */
+				console-size = <0>; /* disabled by default */
+			};
+		};
+	};
+
+	__overrides__ {
+		base-addr = <&ramoops>,"reg#0";
+		total-size = <&ramoops>,"reg:8";
+		record-size = <&ramoops>,"record-size:0";
+		console-size = <&ramoops>,"console-size:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/rootmaster-overlay.dts b/arch/arm/boot/dts/overlays/rootmaster-overlay.dts
new file mode 100644
index 000000000000..f5ebaea2abb5
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/rootmaster-overlay.dts
@@ -0,0 +1,77 @@
+// redo: ovmerge -c mcp251xfd-overlay.dts,spi0-0,interrupt=25 w1-gpio-overlay.dts,gpiopin=4
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/pinctrl/bcm2835.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+	fragment@0 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			mcp251xfd_pins: mcp251xfd_spi0_0_pins {
+				brcm,pins = <25>;
+				brcm,function = <BCM2835_FSEL_GPIO_IN>;
+			};
+		};
+	};
+	fragment@2 {
+		target-path = "/clocks";
+		__overlay__ {
+			clk_mcp251xfd_osc: mcp251xfd-spi0-0-osc {
+				#clock-cells = <0>;
+				compatible = "fixed-clock";
+				clock-frequency = <40000000>;
+			};
+		};
+	};
+	fragment@3 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp251xfd@0 {
+				compatible = "microchip,mcp251xfd";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&mcp251xfd_pins>;
+				spi-max-frequency = <20000000>;
+				interrupt-parent = <&gpio>;
+				interrupts = <25 IRQ_TYPE_LEVEL_LOW>;
+				clocks = <&clk_mcp251xfd_osc>;
+			};
+		};
+	};
+	fragment@4 {
+		target-path = "/";
+		__overlay__ {
+			onewire@4 {
+				compatible = "w1-gpio";
+				pinctrl-names = "default";
+				pinctrl-0 = <&w1_pins>;
+				gpios = <&gpio 4 0>;
+				status = "okay";
+			};
+		};
+	};
+	fragment@5 {
+		target = <&gpio>;
+		__overlay__ {
+			w1_pins: w1_pins@4 {
+				brcm,pins = <4>;
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/rotary-encoder-overlay.dts b/arch/arm/boot/dts/overlays/rotary-encoder-overlay.dts
new file mode 100644
index 000000000000..ea1d952734e9
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/rotary-encoder-overlay.dts
@@ -0,0 +1,59 @@
+// Device tree overlay for GPIO connected rotary encoder.
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			rotary_pins: rotary_pins@4 {
+				brcm,pins = <4 17>; /* gpio 4 17 */
+				brcm,function = <0 0>; /* input */
+				brcm,pull = <2 2>; /* pull-up */
+			};
+
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			rotary: rotary@4 {
+				compatible = "rotary-encoder";
+				status = "okay";
+				pinctrl-names = "default";
+				pinctrl-0 = <&rotary_pins>;
+				gpios = <&gpio 4 0>, <&gpio 17 0>;
+				linux,axis = <0>; /* REL_X */
+				rotary-encoder,encoding = "gray";
+				rotary-encoder,steps = <24>; /* 24 default */
+				rotary-encoder,steps-per-period = <1>; /* corresponds to full period mode. See README */
+			};
+		};
+
+	};  
+
+	__overrides__ {
+		pin_a =		    <&rotary>,"gpios:4",
+				    <&rotary_pins>,"brcm,pins:0",
+				    /* modify reg values to allow multiple instantiation */
+				    <&rotary>,"reg:0",
+				    <&rotary_pins>,"reg:0";
+		pin_b =		    <&rotary>,"gpios:16",
+				    <&rotary_pins>,"brcm,pins:4";
+		relative_axis =     <&rotary>,"rotary-encoder,relative-axis?";
+		linux_axis =        <&rotary>,"linux,axis:0";
+		rollover =          <&rotary>,"rotary-encoder,rollover?";
+		steps-per-period =  <&rotary>,"rotary-encoder,steps-per-period:0";
+		steps =             <&rotary>,"rotary-encoder,steps:0";
+		wakeup =            <&rotary>,"wakeup-source?";
+		encoding =          <&rotary>,"rotary-encoder,encoding";
+                /* legacy parameters*/
+		rotary0_pin_a =     <&rotary>,"gpios:4",
+		                    <&rotary_pins>,"brcm,pins:0";
+		rotary0_pin_b =     <&rotary>,"gpios:16",
+		                    <&rotary_pins>,"brcm,pins:4";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/rpi-backlight-overlay.dts b/arch/arm/boot/dts/overlays/rpi-backlight-overlay.dts
new file mode 100644
index 000000000000..cac5e44c6ec5
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/rpi-backlight-overlay.dts
@@ -0,0 +1,21 @@
+/*
+ * Devicetree overlay for mailbox-driven Raspberry Pi DSI Display
+ * backlight controller
+ */
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			rpi_backlight: rpi_backlight {
+				compatible = "raspberrypi,rpi-backlight";
+				firmware = <&firmware>;
+				status = "okay";
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/rpi-codeczero-overlay.dts b/arch/arm/boot/dts/overlays/rpi-codeczero-overlay.dts
new file mode 100644
index 000000000000..c3b0564b2fb2
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/rpi-codeczero-overlay.dts
@@ -0,0 +1,9 @@
+// Overlay for the Raspberry Pi Codec Zero soundcard
+
+#include "iqaudio-codec-overlay.dts"
+
+&iqaudio_dac {
+	card_name = "RPi Codec Zero";
+	dai_name = "Raspberry Pi Codec Zero";
+	dai_stream_name = "Raspberry Pi Codec Zero HiFi";
+};
diff --git a/arch/arm/boot/dts/overlays/rpi-dacplus-overlay.dts b/arch/arm/boot/dts/overlays/rpi-dacplus-overlay.dts
new file mode 100644
index 000000000000..47557aa17f19
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/rpi-dacplus-overlay.dts
@@ -0,0 +1,17 @@
+// Overlay for the Raspberry Pi DAC Plus soundcard
+
+#include "iqaudio-dacplus-overlay.dts"
+
+&iqaudio_dac {
+	card_name = "RPi DAC+";
+	dai_name = "Raspberry Pi DAC+";
+	dai_stream_name = "Raspberry Pi DAC+ HiFi";
+	/delete-property/ mute-gpios;
+};
+
+/ {
+	__overrides__ {
+		/delete-property/ auto_mute_amp;
+		/delete-property/ unmute_amp;
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/rpi-dacpro-overlay.dts b/arch/arm/boot/dts/overlays/rpi-dacpro-overlay.dts
new file mode 100644
index 000000000000..412260c64edf
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/rpi-dacpro-overlay.dts
@@ -0,0 +1,17 @@
+// Overlay for the Raspberry Pi DAC Pro soundcard
+
+#include "iqaudio-dacplus-overlay.dts"
+
+&iqaudio_dac {
+	card_name = "RPi DAC Pro";
+	dai_name = "Raspberry Pi DAC Pro";
+	dai_stream_name = "Raspberry Pi DAC Pro HiFi";
+	/delete-property/ mute-gpios;
+};
+
+/ {
+	__overrides__ {
+		/delete-property/ auto_mute_amp;
+		/delete-property/ unmute_amp;
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/rpi-digiampplus-overlay.dts b/arch/arm/boot/dts/overlays/rpi-digiampplus-overlay.dts
new file mode 100644
index 000000000000..5e73d6c1bf42
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/rpi-digiampplus-overlay.dts
@@ -0,0 +1,17 @@
+// Overlay for the Raspberry Pi DAC Plus soundcard
+
+#include "iqaudio-dacplus-overlay.dts"
+
+&iqaudio_dac {
+	card_name = "RPi DigiAMP+";
+	dai_name = "Raspberry Pi DigiAMP+";
+	dai_stream_name = "Raspberry Pi DigiAMP+ HiFi";
+	iqaudio-dac,auto-mute-amp;
+};
+
+/ {
+	__overrides__ {
+		unmute_amp = <&iqaudio_dac>,"iqaudio-dac,unmute-amp?",
+			     <&iqaudio_dac>,"iqaudio-dac,auto-mute-amp!";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/rpi-ft5406-overlay.dts b/arch/arm/boot/dts/overlays/rpi-ft5406-overlay.dts
new file mode 100644
index 000000000000..8483c4f4b2eb
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/rpi-ft5406-overlay.dts
@@ -0,0 +1,25 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/soc/firmware";
+		__overlay__ {
+			ts: touchscreen {
+				compatible = "raspberrypi,firmware-ts";
+				touchscreen-size-x = <800>;
+				touchscreen-size-y = <480>;
+			};
+		};
+	};
+
+	__overrides__ {
+		touchscreen-size-x = <&ts>,"touchscreen-size-x:0";
+		touchscreen-size-y = <&ts>,"touchscreen-size-y:0";
+		touchscreen-inverted-x = <&ts>,"touchscreen-inverted-x?";
+		touchscreen-inverted-y = <&ts>,"touchscreen-inverted-y?";
+		touchscreen-swapped-x-y = <&ts>,"touchscreen-swapped-x-y?";
+        };
+};
diff --git a/arch/arm/boot/dts/overlays/rpi-fw-uart-overlay.dts b/arch/arm/boot/dts/overlays/rpi-fw-uart-overlay.dts
new file mode 100644
index 000000000000..c17556e91105
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/rpi-fw-uart-overlay.dts
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0
+// Overlay for the Raspberry Pi Firmware UART driver
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			rpi_fw_uart_pins: rpi_fw_uart_pins@4 {
+				brcm,pins = <20 21>;
+				brcm,function = <1 0>; /* output input */
+				brcm,pull = <0 2>; /* none pull-up */
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&soc>;
+		__overlay__ {
+			rpi_fw_uart: rpi_fw_uart@7e000000 {
+			compatible = "raspberrypi,firmware-uart";
+			reg = <0x7e000000 0x100>; /* VideoCore MS sync regs */
+			firmware = <&firmware>;
+			pinctrl-names = "default";
+			pinctrl-0 = <&rpi_fw_uart_pins>;
+			tx-gpios = <&gpio 20 0>;
+			rx-gpios = <&gpio 21 0>;
+			};
+		};
+	};
+
+	__overrides__ {
+		txd0_pin = <&rpi_fw_uart>,"tx-gpios:4",
+			 <&rpi_fw_uart_pins>, "brcm,pins:0";
+		rxd0_pin = <&rpi_fw_uart>,"rx-gpios:4",
+			 <&rpi_fw_uart_pins>, "brcm,pins:4";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/rpi-poe-overlay.dts b/arch/arm/boot/dts/overlays/rpi-poe-overlay.dts
new file mode 100644
index 000000000000..6b9ac9bcf6b8
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/rpi-poe-overlay.dts
@@ -0,0 +1,154 @@
+/*
+ * Overlay for the Raspberry Pi POE HAT.
+ */
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			fan: pwm-fan {
+				compatible = "pwm-fan";
+				cooling-levels = <0 1 10 100 255>;
+				#cooling-cells = <2>;
+				pwms = <&fwpwm 0 80000 0>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&cpu_thermal>;
+		__overlay__ {
+			polling-delay = <2000>; /* milliseconds */
+		};
+	};
+
+	fragment@2 {
+		target = <&thermal_trips>;
+		__overlay__ {
+			trip0: trip0 {
+				temperature = <40000>;
+				hysteresis = <2000>;
+				type = "active";
+			};
+			trip1: trip1 {
+				temperature = <45000>;
+				hysteresis = <2000>;
+				type = "active";
+			};
+			trip2: trip2 {
+				temperature = <50000>;
+				hysteresis = <2000>;
+				type = "active";
+			};
+			trip3: trip3 {
+				temperature = <55000>;
+				hysteresis = <5000>;
+				type = "active";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&cooling_maps>;
+		__overlay__ {
+			map0 {
+				trip = <&trip0>;
+				cooling-device = <&fan 0 1>;
+			};
+			map1 {
+				trip = <&trip1>;
+				cooling-device = <&fan 1 2>;
+			};
+			map2 {
+				trip = <&trip2>;
+				cooling-device = <&fan 2 3>;
+			};
+			map3 {
+				trip = <&trip3>;
+				cooling-device = <&fan 3 4>;
+			};
+		};
+	};
+
+	fragment@4 {
+		target-path = "/__overrides__";
+		params: __overlay__ {
+			poe_fan_temp0 =		<&trip0>,"temperature:0";
+			poe_fan_temp0_hyst =	<&trip0>,"hysteresis:0";
+			poe_fan_temp1 =		<&trip1>,"temperature:0";
+			poe_fan_temp1_hyst =	<&trip1>,"hysteresis:0";
+			poe_fan_temp2 =		<&trip2>,"temperature:0";
+			poe_fan_temp2_hyst =	<&trip2>,"hysteresis:0";
+			poe_fan_temp3 =		<&trip3>,"temperature:0";
+			poe_fan_temp3_hyst =	<&trip3>,"hysteresis:0";
+			poe_fan_i2c =		<&fwpwm>,"status=disabled",
+						<&poe_mfd>,"status=okay",
+						<&fan>,"pwms:0=",<&poe_mfd_pwm>;
+		};
+	};
+
+	fragment@5 {
+		target = <&firmware>;
+		__overlay__ {
+			fwpwm: pwm {
+				compatible = "raspberrypi,firmware-poe-pwm";
+				#pwm-cells = <2>;
+			};
+		};
+	};
+
+	fragment@6 {
+		target = <&i2c0>;
+		i2c_bus: __overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			poe_mfd: poe@51 {
+				compatible = "raspberrypi,poe-core";
+				reg = <0x51>;
+				status = "disabled";
+
+				poe_mfd_pwm: poe_pwm@f0 {
+					compatible = "raspberrypi,poe-pwm";
+					reg = <0xf0>;
+					status = "okay";
+					#pwm-cells = <2>;
+				};
+			};
+		};
+	};
+
+	fragment@7 {
+		target = <&i2c0if>;
+		__dormant__ {
+			status = "okay";
+		};
+	};
+
+	fragment@8 {
+		target = <&i2c0mux>;
+		__dormant__ {
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		poe_fan_temp0 =		<&trip0>,"temperature:0";
+		poe_fan_temp0_hyst =	<&trip0>,"hysteresis:0";
+		poe_fan_temp1 =		<&trip1>,"temperature:0";
+		poe_fan_temp1_hyst =	<&trip1>,"hysteresis:0";
+		poe_fan_temp2 =		<&trip2>,"temperature:0";
+		poe_fan_temp2_hyst =	<&trip2>,"hysteresis:0";
+		poe_fan_temp3 =		<&trip3>,"temperature:0";
+		poe_fan_temp3_hyst =	<&trip3>,"hysteresis:0";
+		i2c =			<0>, "+7+8",
+					<&fwpwm>,"status=disabled",
+					<&i2c_bus>,"status=okay",
+					<&poe_mfd>,"status=okay",
+					<&fan>,"pwms:0=",<&poe_mfd_pwm>;
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/rpi-poe-plus-overlay.dts b/arch/arm/boot/dts/overlays/rpi-poe-plus-overlay.dts
new file mode 100644
index 000000000000..494af0c2a20a
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/rpi-poe-plus-overlay.dts
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+// Overlay for the Raspberry Pi PoE+ HAT.
+
+#include "rpi-poe-overlay.dts"
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@10 {
+		target-path = "/";
+		__overlay__ {
+			rpi_poe_power_supply: rpi-poe-power-supply {
+				compatible = "raspberrypi,rpi-poe-power-supply";
+				firmware = <&firmware>;
+				status = "okay";
+			};
+		};
+	};
+	fragment@11 {
+		target = <&poe_mfd>;
+		__overlay__ {
+			rpi-poe-power-supply@f2 {
+				compatible = "raspberrypi,rpi-poe-power-supply";
+				reg = <0xf2>;
+				status = "okay";
+			};
+		};
+	};
+
+	__overrides__ {
+		i2c =	<0>, "+7+8",
+			<&fwpwm>,"status=disabled",
+			<&rpi_poe_power_supply>,"status=disabled",
+			<&i2c_bus>,"status=okay",
+			<&poe_mfd>,"status=okay",
+			<&fan>,"pwms:0=",<&poe_mfd_pwm>;
+	};
+};
+
+&fan {
+	cooling-levels = <0 32 64 128 255>;
+};
+
+&params {
+	poe_fan_i2c = <&fwpwm>,"status=disabled",
+		      <&rpi_poe_power_supply>,"status=disabled",
+		      <&poe_mfd>,"status=okay",
+		      <&fan>,"pwms:0=",<&poe_mfd_pwm>;
+};
diff --git a/arch/arm/boot/dts/overlays/rpi-power-hat-b-overlay.dts b/arch/arm/boot/dts/overlays/rpi-power-hat-b-overlay.dts
new file mode 100644
index 000000000000..2d87f2ab5a2a
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/rpi-power-hat-b-overlay.dts
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/dts-v1/;
+/plugin/;
+
+// Overlay for the Raspberry Pi Power Hat.
+// 1 of 2 overlays required for top/bottom interchangeability
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c_arm>;
+		__overlay__ {
+
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			power_control: fxl6408@43 {
+				reg = <0x43>;
+				compatible = "fcs,fxl6408";
+				gpio-controller;
+				#gpio-cells = <2>;
+				gpio-line-names =
+					"B1_PWR",
+					"B2_PWR",
+					"B3",
+					"B4",
+					"B5",
+					"B6",
+					"B7",
+					"B8";
+			};
+
+			pwr1_monitor: ina260@42 {
+				reg = <0x42>;
+				compatible = "ti,ina260";
+			};
+
+			pwr2_monitor: ina260@4c {
+				reg = <0x4c>;
+				compatible = "ti,ina260";
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			rpi_power_hat_b: rpi-power-hat-b {
+				compatible = "raspberrypi,rpi-power-hat-b";
+
+				// References to the I2C devices
+				power-controller = <&power_control>;
+				power1-monitor = <&pwr1_monitor>;
+				power2-monitor = <&pwr2_monitor>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/rpi-power-hat-t-overlay.dts b/arch/arm/boot/dts/overlays/rpi-power-hat-t-overlay.dts
new file mode 100644
index 000000000000..f6a2453176b9
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/rpi-power-hat-t-overlay.dts
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/dts-v1/;
+/plugin/;
+
+// Overlay for the Raspberry Pi Power Hat.
+// 2 of 2 overlays required for top/bottom interchangeability
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c_arm>;
+		__overlay__ {
+
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			power_control: fxl6408@44 {
+				reg = <0x44>;
+				compatible = "fcs,fxl6408";
+				gpio-controller;
+				#gpio-cells = <2>;
+				gpio-line-names =
+					"T1_PWR",
+					"T2_PWR",
+					"T3",
+					"T4",
+					"T5",
+					"T6",
+					"T7",
+					"T8";
+			};
+
+			pwr1_monitor: ina260@46 {
+				reg = <0x46>;
+				compatible = "ti,ina260";
+			};
+
+			pwr2_monitor: ina260@4d {
+				reg = <0x4d>;
+				compatible = "ti,ina260";
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			rpi_power_hat_t: rpi-power-hat-t {
+				compatible = "raspberrypi,rpi-power-hat-t";
+
+				// References to the I2C devices
+				power-controller = <&power_control>;
+				power1-monitor = <&pwr1_monitor>;
+				power2-monitor = <&pwr2_monitor>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/rpi-rp2040-gpio-bridge.dtsi b/arch/arm/boot/dts/overlays/rpi-rp2040-gpio-bridge.dtsi
new file mode 100644
index 000000000000..2b7f670a1f6d
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/rpi-rp2040-gpio-bridge.dtsi
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0-only
+spi_bridge: spi@40 {
+	reg = <0x40>;
+	compatible = "raspberrypi,rp2040-gpio-bridge";
+	status = "disabled";
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	power-supply = <&cam1_reg>;
+
+	#gpio-cells = <2>;
+	gpio-controller;
+
+	spi_bridgedev0: spidev@0{
+		compatible = "spidev";
+		reg = <0>;	/* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <35000000>;
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/rpi-sense-overlay.dts b/arch/arm/boot/dts/overlays/rpi-sense-overlay.dts
new file mode 100644
index 000000000000..7789709b10e9
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/rpi-sense-overlay.dts
@@ -0,0 +1,69 @@
+// rpi-sense HAT
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			sensehat@46 {
+				compatible = "raspberrypi,sensehat";
+				reg = <0x46>;
+				interrupt-parent = <&gpio>;
+				status = "okay";
+
+				display {
+					compatible = "raspberrypi,rpi-sense-fb";
+					status = "okay";
+				};
+				joystick {
+					compatible = "raspberrypi,sensehat-joystick";
+					interrupts = <23 1>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&sensehat_pins>;
+					status = "okay";
+				};
+			};
+
+			lsm9ds1-magn@1c {
+				compatible = "st,lsm9ds1-magn";
+				reg = <0x1c>;
+				status = "okay";
+			};
+
+			lsm9ds1-accel6a {
+				compatible = "st,lsm9ds1-accel";
+				reg = <0x6a>;
+				status = "okay";
+			};
+
+			lps25h-press@5c {
+				compatible = "st,lps25h-press";
+				reg = <0x5c>;
+				status = "okay";
+			};
+
+			hts221-humid@5f {
+				compatible = "st,hts221-humid", "st,hts221";
+				reg = <0x5f>;
+				status = "disabled";
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			sensehat_pins: sensehat_pins {
+				brcm,pins = <23>;
+				brcm,function = <0>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/rpi-sense-v2-overlay.dts b/arch/arm/boot/dts/overlays/rpi-sense-v2-overlay.dts
new file mode 100644
index 000000000000..b24ad86cff02
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/rpi-sense-v2-overlay.dts
@@ -0,0 +1,69 @@
+// rpi-sense HAT
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			sensehat@46 {
+				compatible = "raspberrypi,sensehat";
+				reg = <0x46>;
+				interrupt-parent = <&gpio>;
+				status = "okay";
+
+				display {
+					compatible = "raspberrypi,rpi-sense-fb";
+					status = "okay";
+				};
+				joystick {
+					compatible = "raspberrypi,sensehat-joystick";
+					interrupts = <23 1>;
+					pinctrl-names = "default";
+					pinctrl-0 = <&sensehat_pins>;
+					status = "okay";
+				};
+			};
+
+			lsm9ds1-magn@1c {
+				compatible = "st,lsm9ds1-magn";
+				reg = <0x1c>;
+				status = "okay";
+			};
+
+			lps25h-press@5c {
+				compatible = "st,lps25h-press";
+				reg = <0x5c>;
+				status = "okay";
+			};
+
+			hts221-humid@5f {
+				compatible = "st,hts221-humid", "st,hts221";
+				reg = <0x5f>;
+				status = "disabled";
+			};
+
+			lsm9ds1-accel@6a {
+				compatible = "st,lsm9ds1-accel";
+				reg = <0x6a>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			sensehat_pins: sensehat_pins {
+				brcm,pins = <23>;
+				brcm,function = <0>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/rpi-tv-overlay.dts b/arch/arm/boot/dts/overlays/rpi-tv-overlay.dts
new file mode 100644
index 000000000000..3c97a545d820
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/rpi-tv-overlay.dts
@@ -0,0 +1,34 @@
+// rpi-tv HAT
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@1 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			status = "okay";
+
+			cxd2880@0 {
+				compatible = "sony,cxd2880";
+				reg = <0>; /* CE0 */
+				spi-max-frequency = <50000000>;
+				status = "okay";
+			};
+		};
+	};
+
+};
diff --git a/arch/arm/boot/dts/overlays/rra-digidac1-wm8741-audio-overlay.dts b/arch/arm/boot/dts/overlays/rra-digidac1-wm8741-audio-overlay.dts
new file mode 100644
index 000000000000..97db53a91fda
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/rra-digidac1-wm8741-audio-overlay.dts
@@ -0,0 +1,49 @@
+// Definitions for RRA DigiDAC1 Audio card
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			wm8804@3b {
+				#sound-dai-cells = <0>;
+				compatible = "wlf,wm8804";
+				reg = <0x3b>;
+				status = "okay";
+				PVDD-supply = <&vdd_3v3_reg>;
+				DVDD-supply = <&vdd_3v3_reg>;
+			};
+
+			wm8742: wm8741@1a {
+				compatible = "wlf,wm8741";
+				reg = <0x1a>;
+				status = "okay";
+				AVDD-supply = <&vdd_5v0_reg>;
+				DVDD-supply = <&vdd_3v3_reg>;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "rra,digidac1-soundcard";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/sainsmart18-overlay.dts b/arch/arm/boot/dts/overlays/sainsmart18-overlay.dts
new file mode 100644
index 000000000000..c51f1c030a55
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/sainsmart18-overlay.dts
@@ -0,0 +1,52 @@
+/*
+ * Device Tree overlay for the Sainsmart 1.8" TFT LCD with ST7735R chip 160x128
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@1 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			ss18: sainsmart18@0 {
+				compatible = "fbtft,sainsmart18";
+				reg = <0>;
+				pinctrl-names = "default";
+				spi-max-frequency = <40000000>;
+				rotate = <90>;
+				buswidth = <8>;
+				fps = <50>;
+				height = <160>;
+				width = <128>;
+				reset-gpios = <&gpio 25 1>;
+				dc-gpios = <&gpio 24 0>;
+				debug = <0>;
+			};
+		};
+	};
+
+	__overrides__ {
+		speed     = <&ss18>,"spi-max-frequency:0";
+		rotate    = <&ss18>,"rotate:0";
+		fps       = <&ss18>,"fps:0";
+		bgr       = <&ss18>,"bgr?";
+		debug     = <&ss18>,"debug:0";
+		dc_pin    = <&ss18>,"dc-gpios:4";
+		reset_pin = <&ss18>,"reset-gpios:4";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/sc16is750-i2c-overlay.dts b/arch/arm/boot/dts/overlays/sc16is750-i2c-overlay.dts
new file mode 100644
index 000000000000..700e8a60f182
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/sc16is750-i2c-overlay.dts
@@ -0,0 +1,58 @@
+/dts-v1/;
+/plugin/;
+
+#include "i2c-buses.dtsi"
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2cbus>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sc16is750: sc16is750@48 {
+				compatible = "nxp,sc16is750";
+				reg = <0x48>; /* i2c address */
+				clocks = <&sc16is750_clk>;
+				interrupt-parent = <&gpio>;
+				interrupts = <24 2>; /* IRQ_TYPE_EDGE_FALLING */
+				pinctrl-0 = <&int_pins>;
+				pinctrl-names = "default";
+				gpio-controller;
+				#gpio-cells = <2>;
+				i2c-max-frequency = <400000>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&clocks>;
+		__overlay__ {
+			sc16is750_clk: sc16is750_i2c_clk@48 {
+				compatible = "fixed-clock";
+				#clock-cells = <0>;
+				clock-frequency = <14745600>;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&gpio>;
+		__overlay__ {
+			int_pins: int_pins@18 {
+					brcm,pins = <24>;
+					brcm,function = <0>; /* in */
+					brcm,pull = <0>; /* none */
+			};
+		};
+	};
+
+	__overrides__ {
+		int_pin = <&sc16is750>,"interrupts:0", <&int_pins>,"brcm,pins:0",
+			  <&int_pins>,"reg:0";
+		addr = <&sc16is750>,"reg:0", <&sc16is750_clk>,"name";
+		xtal = <&sc16is750_clk>,"clock-frequency:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/sc16is752-i2c-overlay.dts b/arch/arm/boot/dts/overlays/sc16is752-i2c-overlay.dts
new file mode 100644
index 000000000000..d481d6aa8231
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/sc16is752-i2c-overlay.dts
@@ -0,0 +1,58 @@
+/dts-v1/;
+/plugin/;
+
+#include "i2c-buses.dtsi"
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2cbus>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sc16is752: sc16is752@48 {
+				compatible = "nxp,sc16is752";
+				reg = <0x48>; /* i2c address */
+				clocks = <&sc16is752_clk>;
+				interrupt-parent = <&gpio>;
+				interrupts = <24 2>; /* IRQ_TYPE_EDGE_FALLING */
+				pinctrl-0 = <&int_pins>;
+				pinctrl-names = "default";
+				gpio-controller;
+				#gpio-cells = <2>;
+				i2c-max-frequency = <400000>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&clocks>;
+		__overlay__ {
+			sc16is752_clk: sc16is752_i2c_clk@48 {
+				compatible = "fixed-clock";
+				#clock-cells = <0>;
+				clock-frequency = <14745600>;
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&gpio>;
+		__overlay__ {
+			int_pins: int_pins@18 {
+					brcm,pins = <24>;
+					brcm,function = <0>; /* in */
+					brcm,pull = <0>; /* none */
+			};
+		};
+	};
+
+	__overrides__ {
+		int_pin = <&sc16is752>,"interrupts:0", <&int_pins>,"brcm,pins:0",
+			  <&int_pins>,"reg:0";
+		addr = <&sc16is752>,"reg:0",<&sc16is752_clk>,"name";
+		xtal = <&sc16is752_clk>,"clock-frequency:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/sc16is75x-spi-overlay.dts b/arch/arm/boot/dts/overlays/sc16is75x-spi-overlay.dts
new file mode 100644
index 000000000000..ba5c7b5ffaff
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/sc16is75x-spi-overlay.dts
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/pinctrl/bcm2835.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	spidev_frag: fragment@0 {
+		target-path = "spi0/spidev@0";
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	sc16is75x_frag: fragment@1 {
+		target = <&spi0>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			sc16is75x: sc16is750@0 {
+				compatible = "nxp,sc16is750";
+				reg = <0>;
+				clocks = <&sc16is75x_clk>;
+				interrupt-parent = <&gpio>;
+				interrupts = <24 IRQ_TYPE_EDGE_FALLING>;
+				pinctrl-0 = <&sc16is75x_pins>;
+				pinctrl-names = "default";
+				gpio-controller;
+				#gpio-cells = <2>;
+				spi-max-frequency = <4000000>;
+			};
+		};
+	};
+
+	fragment@2 {
+		target-path = "/";
+		__overlay__ {
+			sc16is75x_clk: sc16is75x_clk {
+				compatible = "fixed-clock";
+				#clock-cells = <0>;
+				clock-frequency = <14745600>;
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&gpio>;
+		__overlay__ {
+			sc16is75x_pins: sc16is75x_pins@18 {
+				brcm,pins = <24>;
+				brcm,function = <BCM2835_FSEL_GPIO_IN>;
+				brcm,pull = <BCM2835_PUD_OFF>;
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&gpio>;
+		__dormant__ {
+			spi1_pins: spi1_pins {
+				brcm,pins = <19 20 21>;
+				brcm,function = <3>; /* alt4 */
+			};
+
+			spi1_cs_pins: spi1_cs_pins {
+				brcm,pins = <18>;
+				brcm,function = <1>; /* output */
+			};
+		};
+	};
+
+	fragment@5 {
+		target = <&spi1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			pinctrl-names = "default";
+			pinctrl-0 = <&spi1_pins &spi1_cs_pins>;
+			cs-gpios = <&gpio 18 1>;
+			status = "okay";
+		};
+	};
+
+	fragment@6 {
+		target = <&aux>;
+		__dormant__ {
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		sc16is750 = <&sc16is75x>,"compatible=nxp,sc16is750",
+			<&sc16is75x>, "name=sc16is750@0";
+		sc16is752 = <&sc16is75x>,"compatible=nxp,sc16is752",
+			<&sc16is75x>, "name=sc16is752@0";
+		int_pin = <&sc16is75x>,"interrupts:0",
+			<&sc16is75x_pins>,"brcm,pins:0",
+			<&sc16is75x_pins>,"reg:0";
+		xtal = <&sc16is75x_clk>,"clock-frequency:0";
+		spi0-0 = <&spidev_frag>, "target-path=spi0/spidev@0",
+			<&sc16is75x_frag>, "target:0=", <&spi0>,
+			<&sc16is75x_pins>, "name=sc16is75x_spi0_0_pins";
+		spi0-1 = <&spidev_frag>, "target-path=spi0/spidev@1",
+			<&sc16is75x_frag>, "target:0=", <&spi0>,
+			<&sc16is75x>, "reg:0=1",
+			<&sc16is75x_pins>, "name=sc16is75x_spi0_1_pins";
+		spi1-0 = <0>, "-0",
+			<&sc16is75x_frag>, "target:0=", <&spi1>,
+			<&sc16is75x_pins>, "name=sc16is75x_spi1_0_pins";
+		spi1-1cs = <0>,"+4+5+6";
+		spi1-1 = <0>, "-0",
+			<&sc16is75x_frag>, "target:0=", <&spi1>,
+			<&sc16is75x>, "reg:0=1",
+			<&sc16is75x_pins>, "name=sc16is75x_spi1_1_pins";
+		spi1-2 = <0>, "-0",
+			<&sc16is75x_frag>, "target:0=", <&spi1>,
+			<&sc16is75x>, "reg:0=2",
+			<&sc16is75x_pins>, "name=sc16is75x_spi1_2_pins";
+		spi2-0 = <0>, "-0",
+			<&sc16is75x_frag>, "target:0=", <&spi2>,
+			<&sc16is75x_pins>, "name=sc16is75x_spi2_0_pins";
+		spi2-1 = <0>, "-0",
+			<&sc16is75x_frag>, "target:0=", <&spi2>,
+			<&sc16is75x>, "reg:0=1",
+			<&sc16is75x_pins>, "name=sc16is75x_spi2_1_pins";
+		spi2-2 = <0>, "-0",
+			<&sc16is75x_frag>, "target:0=", <&spi2>,
+			<&sc16is75x>, "reg:0=2",
+			<&sc16is75x_pins>, "name=sc16is75x_spi2_2_pins";
+		spi3-0 = <0>, "-0",
+			<&sc16is75x_frag>, "target?=0",
+			<&sc16is75x_frag>, "target-path=spi3",
+			<&sc16is75x_pins>, "name=sc16is75x_spi3_0_pins";
+		spi3-1 = <0>, "-0",
+			<&sc16is75x_frag>, "target?=0",
+			<&sc16is75x_frag>, "target-path=spi3",
+			<&sc16is75x>, "reg:0=1",
+			<&sc16is75x_pins>, "name=sc16is75x_spi3_1_pins";
+		spi4-0 = <0>, "-0",
+			<&sc16is75x_frag>, "target?=0",
+			<&sc16is75x_frag>, "target-path=spi4",
+			<&sc16is75x_pins>, "name=sc16is75x_spi4_0_pins";
+		spi4-1 = <0>, "-0",
+			<&sc16is75x_frag>, "target?=0",
+			<&sc16is75x_frag>, "target-path=spi4",
+			<&sc16is75x>, "reg:0=1",
+			<&sc16is75x_pins>, "name=sc16is75x_spi4_1_pins";
+		spi5-0 = <0>, "-0",
+			<&sc16is75x_frag>, "target?=0",
+			<&sc16is75x_frag>, "target-path=spi5",
+			<&sc16is75x_pins>, "name=sc16is75x_spi5_0_pins";
+		spi5-1 = <0>, "-0",
+			<&sc16is75x_frag>, "target?=0",
+			<&sc16is75x_frag>, "target-path=spi5",
+			<&sc16is75x>, "reg:0=1",
+			<&sc16is75x_pins>, "name=sc16is75x_spi5_1_pins";
+		spi6-0 = <0>, "-0",
+			<&sc16is75x_frag>, "target?=0",
+			<&sc16is75x_frag>, "target-path=spi6",
+			<&sc16is75x_pins>, "name=sc16is75x_spi6_0_pins";
+		spi6-1 = <0>, "-0",
+			<&sc16is75x_frag>, "target?=0",
+			<&sc16is75x_frag>, "target-path=spi6",
+			<&sc16is75x>, "reg:0=1",
+			<&sc16is75x_pins>, "name=sc16is75x_spi6_1_pins";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/sdhost-overlay.dts b/arch/arm/boot/dts/overlays/sdhost-overlay.dts
new file mode 100644
index 000000000000..0b72b4eeac88
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/sdhost-overlay.dts
@@ -0,0 +1,38 @@
+/dts-v1/;
+/plugin/;
+
+/* Provide backwards compatible aliases for the old sdhost dtparams. */
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&sdhost>;
+		frag0: __overlay__ {
+			brcm,overclock-50 = <0>;
+			brcm,pio-limit = <1>;
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&mmc>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&mmcnr>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	__overrides__ {
+		overclock_50     = <&frag0>,"brcm,overclock-50:0";
+		force_pio        = <&frag0>,"brcm,force-pio?";
+		pio_limit        = <&frag0>,"brcm,pio-limit:0";
+		debug            = <&frag0>,"brcm,debug?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/sdio-overlay.dts b/arch/arm/boot/dts/overlays/sdio-overlay.dts
new file mode 100644
index 000000000000..873e49056379
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/sdio-overlay.dts
@@ -0,0 +1,77 @@
+/dts-v1/;
+/plugin/;
+
+/* Enable SDIO from MMC interface via various GPIO groups */
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&mmcnr>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@1 {
+		target = <&mmc>;
+		sdio_ovl: __overlay__ {
+			pinctrl-0 = <&sdio_ovl_pins>;
+			pinctrl-names = "default";
+			non-removable;
+			bus-width = <4>;
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&gpio>;
+		__overlay__ {
+			sdio_ovl_pins: sdio_ovl_pins {
+				brcm,pins = <22 23 24 25 26 27>;
+				brcm,function = <7>; /* ALT3 = SD1 */
+				brcm,pull = <0 2 2 2 2 2>;
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&sdio_ovl_pins>;
+		__dormant__ {
+			brcm,pins = <22 23 24 25>;
+			brcm,pull = <0 2 2 2>;
+		};
+	};
+
+	fragment@4 {
+		target = <&sdio_ovl_pins>;
+		__dormant__ {
+			brcm,pins = <34 35 36 37>;
+			brcm,pull = <0 2 2 2>;
+		};
+	};
+
+	fragment@5 {
+		target = <&sdio_ovl_pins>;
+		__dormant__ {
+			brcm,pins = <34 35 36 37 38 39>;
+			brcm,pull = <0 2 2 2 2 2>;
+		};
+	};
+
+	fragment@6 {
+		target-path = "/aliases";
+		__overlay__ {
+			mmc1 = "/soc/mmc@7e300000";
+		};
+	};
+
+	__overrides__ {
+		poll_once = <&sdio_ovl>,"non-removable?";
+		bus_width = <&sdio_ovl>,"bus-width:0";
+		sdio_overclock = <&sdio_ovl>,"brcm,overclock-50:0";
+		gpios_22_25 = <0>,"=3";
+		gpios_34_37 = <0>,"=4";
+		gpios_34_39 = <0>,"=5";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/sdio-pi5-overlay.dts b/arch/arm/boot/dts/overlays/sdio-pi5-overlay.dts
new file mode 100644
index 000000000000..4e42cb5c856e
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/sdio-pi5-overlay.dts
@@ -0,0 +1,24 @@
+/dts-v1/;
+/plugin/;
+
+/* SDIO/SD/MMC on RP1 bank 0 */
+
+/{
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&rp1_mmc0>;
+		frag0: __overlay__ {
+			status = "okay";
+			pinctrl-0 = <&rp1_sdio0_22_27>;
+			pinctrl-names = "default";
+		};
+	};
+
+	fragment@1 {
+		target = <&rp1_sdio_clk0>;
+		frag1: __overlay__ {
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/seeed-can-fd-hat-v1-overlay.dts b/arch/arm/boot/dts/overlays/seeed-can-fd-hat-v1-overlay.dts
new file mode 100644
index 000000000000..210d027a073e
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/seeed-can-fd-hat-v1-overlay.dts
@@ -0,0 +1,138 @@
+// redo: ovmerge -c spi1-1cs-overlay.dts,cs0_pin=18,cs0_spidev=false mcp251xfd-overlay.dts,spi0-0,interrupt=25 mcp251xfd-overlay.dts,spi1-0,interrupt=24
+
+// Device tree overlay for https://www.seeedstudio.com/2-Channel-CAN-BUS-FD-Shield-for-Raspberry-Pi-p-4072.html
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/pinctrl/bcm2835.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			spi1_pins: spi1_pins {
+				brcm,pins = <19 20 21>;
+				brcm,function = <3>;
+			};
+			spi1_cs_pins: spi1_cs_pins {
+				brcm,pins = <18>;
+				brcm,function = <1>;
+			};
+		};
+	};
+	fragment@1 {
+		target = <&spi1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			pinctrl-names = "default";
+			pinctrl-0 = <&spi1_pins &spi1_cs_pins>;
+			cs-gpios = <&gpio 18 1>;
+			status = "okay";
+			spidev@0 {
+				compatible = "spidev";
+				reg = <0>;
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "disabled";
+			};
+		};
+	};
+	fragment@2 {
+		target = <&aux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@3 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+	fragment@4 {
+		target = <&gpio>;
+		__overlay__ {
+			mcp251xfd_pins: mcp251xfd_spi0_0_pins {
+				brcm,pins = <25>;
+				brcm,function = <BCM2835_FSEL_GPIO_IN>;
+			};
+		};
+	};
+	fragment@5 {
+		target-path = "/clocks";
+		__overlay__ {
+			clk_mcp251xfd_osc: mcp251xfd-spi0-0-osc {
+				#clock-cells = <0>;
+				compatible = "fixed-clock";
+				clock-frequency = <40000000>;
+			};
+		};
+	};
+	fragment@6 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp251xfd@0 {
+				compatible = "microchip,mcp251xfd";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&mcp251xfd_pins>;
+				spi-max-frequency = <20000000>;
+				interrupt-parent = <&gpio>;
+				interrupts = <25 IRQ_TYPE_LEVEL_LOW>;
+				clocks = <&clk_mcp251xfd_osc>;
+			};
+		};
+	};
+	fragment@7 {
+		target-path = "spi1/spidev@0";
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+	fragment@8 {
+		target = <&gpio>;
+		__overlay__ {
+			mcp251xfd_pins_1: mcp251xfd_spi1_0_pins {
+				brcm,pins = <24>;
+				brcm,function = <BCM2835_FSEL_GPIO_IN>;
+			};
+		};
+	};
+	fragment@9 {
+		target-path = "/clocks";
+		__overlay__ {
+			clk_mcp251xfd_osc_1: mcp251xfd-spi1-0-osc {
+				#clock-cells = <0>;
+				compatible = "fixed-clock";
+				clock-frequency = <40000000>;
+			};
+		};
+	};
+	fragment@10 {
+		target = <&spi1>;
+		__overlay__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp251xfd@0 {
+				compatible = "microchip,mcp251xfd";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&mcp251xfd_pins_1>;
+				spi-max-frequency = <20000000>;
+				interrupt-parent = <&gpio>;
+				interrupts = <24 IRQ_TYPE_LEVEL_LOW>;
+				clocks = <&clk_mcp251xfd_osc_1>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/seeed-can-fd-hat-v2-overlay.dts b/arch/arm/boot/dts/overlays/seeed-can-fd-hat-v2-overlay.dts
new file mode 100644
index 000000000000..fc3c7794c28a
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/seeed-can-fd-hat-v2-overlay.dts
@@ -0,0 +1,112 @@
+// redo: ovmerge -c mcp251xfd-overlay.dts,spi0-0,interrupt=25 mcp251xfd-overlay.dts,spi0-1,interrupt=24 i2c-rtc-overlay.dts,pcf85063
+
+// Device tree overlay for https://www.seeedstudio.com/CAN-BUS-FD-HAT-for-Raspberry-Pi-p-4742.html 
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/pinctrl/bcm2835.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+	fragment@0 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			mcp251xfd_pins: mcp251xfd_spi0_0_pins {
+				brcm,pins = <25>;
+				brcm,function = <BCM2835_FSEL_GPIO_IN>;
+			};
+		};
+	};
+	fragment@2 {
+		target-path = "/clocks";
+		__overlay__ {
+			clk_mcp251xfd_osc: mcp251xfd-spi0-0-osc {
+				#clock-cells = <0>;
+				compatible = "fixed-clock";
+				clock-frequency = <40000000>;
+			};
+		};
+	};
+	fragment@3 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp251xfd@0 {
+				compatible = "microchip,mcp251xfd";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&mcp251xfd_pins>;
+				spi-max-frequency = <20000000>;
+				interrupt-parent = <&gpio>;
+				interrupts = <25 IRQ_TYPE_LEVEL_LOW>;
+				clocks = <&clk_mcp251xfd_osc>;
+			};
+		};
+	};
+	fragment@4 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+	fragment@5 {
+		target = <&gpio>;
+		__overlay__ {
+			mcp251xfd_pins_1: mcp251xfd_spi0_1_pins {
+				brcm,pins = <24>;
+				brcm,function = <BCM2835_FSEL_GPIO_IN>;
+			};
+		};
+	};
+	fragment@6 {
+		target-path = "/clocks";
+		__overlay__ {
+			clk_mcp251xfd_osc_1: mcp251xfd-spi0-1-osc {
+				#clock-cells = <0>;
+				compatible = "fixed-clock";
+				clock-frequency = <40000000>;
+			};
+		};
+	};
+	fragment@7 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp251xfd@1 {
+				compatible = "microchip,mcp251xfd";
+				reg = <1>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&mcp251xfd_pins_1>;
+				spi-max-frequency = <20000000>;
+				interrupt-parent = <&gpio>;
+				interrupts = <24 IRQ_TYPE_LEVEL_LOW>;
+				clocks = <&clk_mcp251xfd_osc_1>;
+			};
+		};
+	};
+	fragment@8 {
+		target = <&i2c_arm>;
+		__overlay__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			pcf85063@51 {
+				compatible = "nxp,pcf85063";
+				reg = <0x51>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/sh1106-spi-overlay.dts b/arch/arm/boot/dts/overlays/sh1106-spi-overlay.dts
new file mode 100644
index 000000000000..57a0cc9b1741
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/sh1106-spi-overlay.dts
@@ -0,0 +1,84 @@
+/*
+ * Device Tree overlay for SH1106 based SPI OLED display
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&gpio>;
+		__overlay__ {
+			sh1106_pins: sh1106_pins {
+                                brcm,pins = <25 24>;
+                                brcm,function = <1 1>; /* out out */
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sh1106: sh1106@0{
+				compatible = "sinowealth,sh1106";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&sh1106_pins>;
+
+				spi-max-frequency = <4000000>;
+				bgr = <0>;
+				bpp = <1>;
+				rotate = <0>;
+				fps = <25>;
+				buswidth = <8>;
+				reset-gpios = <&gpio 25 1>;
+				dc-gpios = <&gpio 24 0>;
+				debug = <0>;
+
+				sinowealth,height = <64>;
+				sinowealth,width = <128>;
+				sinowealth,page-offset = <0>;
+			};
+		};
+	};
+
+	__overrides__ {
+		speed     = <&sh1106>,"spi-max-frequency:0";
+		rotate    = <&sh1106>,"rotate:0";
+		fps       = <&sh1106>,"fps:0";
+		debug     = <&sh1106>,"debug:0";
+		dc_pin    = <&sh1106>,"dc-gpios:4",
+		            <&sh1106_pins>,"brcm,pins:4";
+		reset_pin = <&sh1106>,"reset-gpios:4",
+		            <&sh1106_pins>,"brcm,pins:0";
+		height    = <&sh1106>,"sinowealth,height:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/si446x-spi0-overlay.dts b/arch/arm/boot/dts/overlays/si446x-spi0-overlay.dts
new file mode 100644
index 000000000000..90495f0941fb
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/si446x-spi0-overlay.dts
@@ -0,0 +1,53 @@
+// Overlay for the SiLabs Si446X Controller - SPI0
+// Default Interrupt Pin: 17
+// Default SDN Pin: 27
+/dts-v1/;
+/plugin/;
+
+   / {
+    compatible = "brcm,bcm2835";
+
+    fragment@0 {
+        target = <&spi0>;
+        __overlay__ {
+            // needed to avoid dtc warning
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            status = "okay";
+
+            uhf0: si446x@0{
+                compatible = "silabs,si446x";
+                reg = <0>; // CE0
+                pinctrl-names = "default";
+                pinctrl-0 = <&uhf0_pins>;
+                interrupt-parent = <&gpio>;
+                interrupts = <17 0x2>; // falling edge
+                spi-max-frequency = <4000000>;
+                sdn_pin = <27>;
+                irq_pin = <17>;
+                status = "okay";
+            };
+        };
+    };
+
+    fragment@1 {
+        target = <&gpio>;
+        __overlay__ {
+            uhf0_pins: uhf0_pins {
+                brcm,pins = <17 27>;
+                brcm,function = <0 1>; // in, out
+                brcm,pull = <2 0>; // high, none
+            };
+        };
+    };
+
+    __overrides__ {
+        int_pin = <&uhf0>, "interrupts:0",
+                  <&uhf0>, "irq_pin:0",
+                  <&uhf0_pins>, "brcm,pins:0";
+        reset_pin = <&uhf0>, "sdn_pin:0",
+                    <&uhf0_pins>, "brcm,pins:4";
+        speed   = <&uhf0>, "spi-max-frequency:0";
+    };
+};
diff --git a/arch/arm/boot/dts/overlays/smi-dev-overlay.dts b/arch/arm/boot/dts/overlays/smi-dev-overlay.dts
new file mode 100644
index 000000000000..bafab6c92506
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/smi-dev-overlay.dts
@@ -0,0 +1,20 @@
+// Description: Overlay to enable character device interface for SMI.
+// Author:	Luke Wren <luke@raspberrypi.org>
+
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&soc>;
+		__overlay__ {
+			smi_dev {
+				compatible = "brcm,bcm2835-smi-dev";
+				smi_handle = <&smi>;
+				status = "okay";
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/smi-nand-overlay.dts b/arch/arm/boot/dts/overlays/smi-nand-overlay.dts
new file mode 100644
index 000000000000..ae1e50329d66
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/smi-nand-overlay.dts
@@ -0,0 +1,66 @@
+// Description: Overlay to enable NAND flash through
+// the secondary memory interface
+// Author:	Luke Wren
+
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&smi>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&smi_pins>;
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&soc>;
+		__overlay__ {
+			nand: flash@0 {
+				compatible = "brcm,bcm2835-smi-nand";
+				smi_handle = <&smi>;
+				#address-cells = <1>;
+				#size-cells = <1>;
+				status = "okay";
+
+				partition@0 {
+					label = "stage2";
+					// 128k
+					reg = <0 0x20000>;
+					read-only;
+				};
+				partition@1 {
+					label = "firmware";
+					// 16M
+					reg = <0x20000 0x1000000>;
+					read-only;
+				};
+				partition@2 {
+					label = "root";
+					// 2G (will need to use 64 bit for >=4G)
+					reg = <0x1020000 0x80000000>;
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&gpio>;
+		__overlay__ {
+			smi_pins: smi_pins {
+				brcm,pins = <0 1 2 3 4 5 6 7 8 9 10 11
+					12 13 14 15>;
+				/* Alt 1: SMI */
+				brcm,function = <5 5 5 5 5 5 5 5 5 5 5
+					5 5 5 5 5>;
+				/* /CS, /WE and /OE are pulled high, as they are
+				   generally active low signals */
+				brcm,pull = <2 2 2 2 2 2 2 2 0 0 0 0 0 0 0 0>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/smi-overlay.dts b/arch/arm/boot/dts/overlays/smi-overlay.dts
new file mode 100644
index 000000000000..bb8c7830df23
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/smi-overlay.dts
@@ -0,0 +1,37 @@
+// Description:	Overlay to enable the secondary memory interface peripheral
+// Author:	Luke Wren
+
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&smi>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&smi_pins>;
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			smi_pins: smi_pins {
+				/* Don't configure the top two address bits, as
+				   these are already used as ID_SD and ID_SC */
+				brcm,pins = <2 3 4 5 6 7 8 9 10 11 12 13 14 15
+					     16 17 18 19 20 21 22 23 24 25>;
+				/* Alt 1: SMI */
+				brcm,function = <5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
+						 5 5 5 5 5 5 5 5 5>;
+				/* /CS, /WE and /OE are pulled high, as they are
+				   generally active low signals */
+				brcm,pull = <2 2 2 2 2 2 0 0 0 0 0 0 0 0 0 0 0
+					     0 0 0 0 0 0 0>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi-gpio35-39-overlay.dts b/arch/arm/boot/dts/overlays/spi-gpio35-39-overlay.dts
new file mode 100644
index 000000000000..a132b8637c31
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi-gpio35-39-overlay.dts
@@ -0,0 +1,31 @@
+/*
+ * Device tree overlay to move spi0 to gpio 35 to 39 on CM
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			cs-gpios = <&gpio 36 1>, <&gpio 35 1>;
+		};
+	};
+
+	fragment@1 {
+		target = <&spi0_cs_pins>;
+		__overlay__ {
+			brcm,pins = <36 35>;
+		};
+	};
+
+	fragment@2 {
+		target = <&spi0_pins>;
+		__overlay__ {
+			brcm,pins = <37 38 39>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi-gpio40-45-overlay.dts b/arch/arm/boot/dts/overlays/spi-gpio40-45-overlay.dts
new file mode 100644
index 000000000000..9ebcaf1b5ea0
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi-gpio40-45-overlay.dts
@@ -0,0 +1,36 @@
+/*
+ * Boot EEPROM overlay
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			cs-gpios = <&gpio 43 1>, <&gpio 44 1>, <&gpio 45 1>;
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&spi0_cs_pins>;
+		__overlay__ {
+			brcm,pins = <45 44 43>;
+			brcm,function = <1>; /* output */
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&spi0_pins>;
+		__overlay__ {
+			brcm,pins = <40 41 42>;
+			brcm,function = <3>; /* alt4 */
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi-rtc-overlay.dts b/arch/arm/boot/dts/overlays/spi-rtc-overlay.dts
new file mode 100644
index 000000000000..df3286929c2e
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi-rtc-overlay.dts
@@ -0,0 +1,75 @@
+// Definitions for several SPI-based Real Time Clocks
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&rtc>;
+		__dormant__ {
+			compatible = "dallas,ds3232";
+		};
+	};
+
+	fragment@1 {
+		target = <&rtc>;
+		__dormant__ {
+			compatible = "dallas,ds3234";
+		};
+	};
+
+	fragment@2 {
+		target = <&rtc>;
+		__dormant__ {
+			compatible = "nxp,rtc-pcf2123";
+		};
+	};
+
+	spidev: fragment@100 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	frag101: fragment@101 {
+		target = <&spi0>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			rtc: rtc@0 {
+				reg = <0>;
+				spi-max-frequency = <5000000>;
+			};
+		};
+	};
+
+	__overrides__ {
+		spi0_0 = <&spidev>, "target:0=",<&spidev0>,
+		         <&frag101>, "target:0=",<&spi0>,
+		         <&rtc>, "reg:0=0";
+		spi0_1 = <&spidev>, "target:0=",<&spidev1>,
+		         <&frag101>, "target:0=",<&spi0>,
+		         <&rtc>, "reg:0=1";
+		spi1_0 = <0>,"-100",
+		         <&frag101>, "target:0=",<&spi1>,
+		         <&rtc>, "reg:0=0";
+		spi1_1 = <0>,"-100",
+		         <&frag101>, "target:0=",<&spi1>,
+		         <&rtc>, "reg:0=1";
+		spi2_0 = <0>,"-100",
+		         <&frag101>, "target:0=",<&spi2>,
+		         <&rtc>, "reg:0=0";
+		spi2_1 = <0>,"-100",
+		         <&frag101>, "target:0=",<&spi2>,
+		         <&rtc>, "reg:0=1";
+		cs_high = <&rtc>, "spi-cs-high?";
+
+		ds3232 = <0>,"+0";
+		ds3234 = <0>,"+1";
+		pcf2123 = <0>,"+2";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi0-0cs-overlay.dts b/arch/arm/boot/dts/overlays/spi0-0cs-overlay.dts
new file mode 100644
index 000000000000..0d2acabf56a4
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi0-0cs-overlay.dts
@@ -0,0 +1,39 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0_cs_pins>;
+		frag0: __overlay__ {
+			brcm,pins;
+		};
+	};
+
+	fragment@1 {
+		target = <&spi0>;
+		__overlay__ {
+			cs-gpios;
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&spi0_pins>;
+		__dormant__ {
+			brcm,pins = <10 11>;
+		};
+	};
+
+	__overrides__ {
+		no_miso = <0>,"=3";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi0-1cs-inverted-overlay.dts b/arch/arm/boot/dts/overlays/spi0-1cs-inverted-overlay.dts
new file mode 100644
index 000000000000..dc1db2184df4
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi0-1cs-inverted-overlay.dts
@@ -0,0 +1,59 @@
+/dts-v1/;
+/plugin/;
+
+/*
+ * There are some devices that need an inverted Chip Select (CS) to select the
+ * device signal, as an example the AZDelivery 12864 display. That means that
+ * the CS polarity is active-high. To invert the CS signal the DT needs to set
+ * the cs-gpio to GPIO_ACTIVE_HIGH (0) in the controller and set the
+ * spi-cs-high in the peripheral property. On top of that, since this is a
+ * display the DT also needs to specify the write-only property.
+*/
+
+#include <dt-bindings/gpio/gpio.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0_cs_pins>;
+		frag0: __overlay__ {
+			brcm,pins = <8>;
+		};
+	};
+
+	fragment@1 {
+		target = <&spi0>;
+		frag1: __overlay__ {
+			cs-gpios = <&gpio 8 GPIO_ACTIVE_HIGH>;
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&spi0_pins>;
+		__dormant__ {
+			brcm,pins = <10 11>;
+		};
+	};
+
+	fragment@4 {
+		target = <&spidev0>;
+		__overlay__ {
+			spi-cs-high;
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&frag0>,"brcm,pins:0",
+			   <&frag1>,"cs-gpios:4";
+		no_miso = <0>,"=3";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi0-1cs-overlay.dts b/arch/arm/boot/dts/overlays/spi0-1cs-overlay.dts
new file mode 100644
index 000000000000..e6eb66e2076a
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi0-1cs-overlay.dts
@@ -0,0 +1,42 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0_cs_pins>;
+		frag0: __overlay__ {
+			brcm,pins = <8>;
+		};
+	};
+
+	fragment@1 {
+		target = <&spi0>;
+		frag1: __overlay__ {
+			cs-gpios = <&gpio 8 1>;
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&spi0_pins>;
+		__dormant__ {
+			brcm,pins = <10 11>;
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&frag0>,"brcm,pins:0",
+			   <&frag1>,"cs-gpios:4";
+		no_miso = <0>,"=3";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi0-2cs-overlay.dts b/arch/arm/boot/dts/overlays/spi0-2cs-overlay.dts
new file mode 100644
index 000000000000..df6519537c3a
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi0-2cs-overlay.dts
@@ -0,0 +1,37 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0_cs_pins>;
+		frag0: __overlay__ {
+			brcm,pins = <8 7>;
+		};
+	};
+
+	fragment@1 {
+		target = <&spi0>;
+		frag1: __overlay__ {
+			cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&spi0_pins>;
+		__dormant__ {
+			brcm,pins = <10 11>;
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&frag0>,"brcm,pins:0",
+			   <&frag1>,"cs-gpios:4";
+		cs1_pin  = <&frag0>,"brcm,pins:4",
+			   <&frag1>,"cs-gpios:16";
+		no_miso = <0>,"=2";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi1-1cs-overlay.dts b/arch/arm/boot/dts/overlays/spi1-1cs-overlay.dts
new file mode 100644
index 000000000000..ea2794bc5fd5
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi1-1cs-overlay.dts
@@ -0,0 +1,57 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			spi1_pins: spi1_pins {
+				brcm,pins = <19 20 21>;
+				brcm,function = <3>; /* alt4 */
+			};
+
+			spi1_cs_pins: spi1_cs_pins {
+				brcm,pins = <18>;
+				brcm,function = <1>; /* output */
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&spi1>;
+		frag1: __overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+			pinctrl-names = "default";
+			pinctrl-0 = <&spi1_pins &spi1_cs_pins>;
+			cs-gpios = <&gpio 18 1>;
+			status = "okay";
+
+			spidev1_0: spidev@0 {
+				compatible = "spidev";
+				reg = <0>;      /* CE0 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&aux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&spi1_cs_pins>,"brcm,pins:0",
+			   <&frag1>,"cs-gpios:4";
+		cs0_spidev = <&spidev1_0>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi1-2cs-overlay.dts b/arch/arm/boot/dts/overlays/spi1-2cs-overlay.dts
new file mode 100644
index 000000000000..dab34ee79ae2
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi1-2cs-overlay.dts
@@ -0,0 +1,69 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			spi1_pins: spi1_pins {
+				brcm,pins = <19 20 21>;
+				brcm,function = <3>; /* alt4 */
+			};
+
+			spi1_cs_pins: spi1_cs_pins {
+				brcm,pins = <18 17>;
+				brcm,function = <1>; /* output */
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&spi1>;
+		frag1: __overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+			pinctrl-names = "default";
+			pinctrl-0 = <&spi1_pins &spi1_cs_pins>;
+			cs-gpios = <&gpio 18 1>, <&gpio 17 1>;
+			status = "okay";
+
+			spidev1_0: spidev@0 {
+				compatible = "spidev";
+				reg = <0>;      /* CE0 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+
+			spidev1_1: spidev@1 {
+				compatible = "spidev";
+				reg = <1>;      /* CE1 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&aux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&spi1_cs_pins>,"brcm,pins:0",
+			   <&frag1>,"cs-gpios:4";
+		cs1_pin  = <&spi1_cs_pins>,"brcm,pins:4",
+			   <&frag1>,"cs-gpios:16";
+		cs0_spidev = <&spidev1_0>,"status";
+		cs1_spidev = <&spidev1_1>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi1-3cs-overlay.dts b/arch/arm/boot/dts/overlays/spi1-3cs-overlay.dts
new file mode 100644
index 000000000000..bc7e7d04324b
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi1-3cs-overlay.dts
@@ -0,0 +1,81 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			spi1_pins: spi1_pins {
+				brcm,pins = <19 20 21>;
+				brcm,function = <3>; /* alt4 */
+			};
+
+			spi1_cs_pins: spi1_cs_pins {
+				brcm,pins = <18 17 16>;
+				brcm,function = <1>; /* output */
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&spi1>;
+		frag1: __overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+			pinctrl-names = "default";
+			pinctrl-0 = <&spi1_pins &spi1_cs_pins>;
+			cs-gpios = <&gpio 18 1>, <&gpio 17 1>, <&gpio 16 1>;
+			status = "okay";
+
+			spidev1_0: spidev@0 {
+				compatible = "spidev";
+				reg = <0>;      /* CE0 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+
+			spidev1_1: spidev@1 {
+				compatible = "spidev";
+				reg = <1>;      /* CE1 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+
+			spidev1_2: spidev@2 {
+				compatible = "spidev";
+				reg = <2>;      /* CE2 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&aux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&spi1_cs_pins>,"brcm,pins:0",
+			   <&frag1>,"cs-gpios:4";
+		cs1_pin  = <&spi1_cs_pins>,"brcm,pins:4",
+			   <&frag1>,"cs-gpios:16";
+		cs2_pin  = <&spi1_cs_pins>,"brcm,pins:8",
+			   <&frag1>,"cs-gpios:28";
+		cs0_spidev = <&spidev1_0>,"status";
+		cs1_spidev = <&spidev1_1>,"status";
+		cs2_spidev = <&spidev1_2>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi2-1cs-overlay.dts b/arch/arm/boot/dts/overlays/spi2-1cs-overlay.dts
new file mode 100644
index 000000000000..2a29750462af
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi2-1cs-overlay.dts
@@ -0,0 +1,57 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			spi2_pins: spi2_pins {
+				brcm,pins = <40 41 42>;
+				brcm,function = <3>; /* alt4 */
+			};
+
+			spi2_cs_pins: spi2_cs_pins {
+				brcm,pins = <43>;
+				brcm,function = <1>; /* output */
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&spi2>;
+		frag1: __overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+			pinctrl-names = "default";
+			pinctrl-0 = <&spi2_pins &spi2_cs_pins>;
+			cs-gpios = <&gpio 43 1>;
+			status = "okay";
+
+			spidev2_0: spidev@0 {
+				compatible = "spidev";
+				reg = <0>;      /* CE0 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&aux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&spi2_cs_pins>,"brcm,pins:0",
+			   <&frag1>,"cs-gpios:4";
+		cs0_spidev = <&spidev2_0>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi2-1cs-pi5-overlay.dts b/arch/arm/boot/dts/overlays/spi2-1cs-pi5-overlay.dts
new file mode 100644
index 000000000000..44382cc5a7c0
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi2-1cs-pi5-overlay.dts
@@ -0,0 +1,33 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&spi2>;
+		frag1: __overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			cs-gpios = <&gpio 0 1>;
+			status = "okay";
+
+			spidev2_0: spidev@0 {
+				compatible = "spidev";
+				reg = <0>;      /* CE0 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&frag1>,"cs-gpios:4";
+		cs0_spidev = <&spidev2_0>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi2-2cs-overlay.dts b/arch/arm/boot/dts/overlays/spi2-2cs-overlay.dts
new file mode 100644
index 000000000000..642678fc9ddd
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi2-2cs-overlay.dts
@@ -0,0 +1,69 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			spi2_pins: spi2_pins {
+				brcm,pins = <40 41 42>;
+				brcm,function = <3>; /* alt4 */
+			};
+
+			spi2_cs_pins: spi2_cs_pins {
+				brcm,pins = <43 44>;
+				brcm,function = <1>; /* output */
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&spi2>;
+		frag1: __overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+			pinctrl-names = "default";
+			pinctrl-0 = <&spi2_pins &spi2_cs_pins>;
+			cs-gpios = <&gpio 43 1>, <&gpio 44 1>;
+			status = "okay";
+
+			spidev2_0: spidev@0 {
+				compatible = "spidev";
+				reg = <0>;      /* CE0 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+
+			spidev2_1: spidev@1 {
+				compatible = "spidev";
+				reg = <1>;      /* CE1 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&aux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&spi2_cs_pins>,"brcm,pins:0",
+			   <&frag1>,"cs-gpios:4";
+		cs1_pin  = <&spi2_cs_pins>,"brcm,pins:4",
+			   <&frag1>,"cs-gpios:16";
+		cs0_spidev = <&spidev2_0>,"status";
+		cs1_spidev = <&spidev2_1>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi2-2cs-pi5-overlay.dts b/arch/arm/boot/dts/overlays/spi2-2cs-pi5-overlay.dts
new file mode 100644
index 000000000000..b37a2c21c7b4
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi2-2cs-pi5-overlay.dts
@@ -0,0 +1,44 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&spi2>;
+		frag1: __overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			cs-gpios = <&gpio 0 1>, <&gpio 24 1>;
+			status = "okay";
+
+			spidev2_0: spidev@0 {
+				compatible = "spidev";
+				reg = <0>;      /* CE0 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+
+			spidev2_1: spidev@1 {
+				compatible = "spidev";
+				reg = <1>;      /* CE1 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&frag1>,"cs-gpios:4";
+		cs1_pin  = <&frag1>,"cs-gpios:16";
+		cs0_spidev = <&spidev2_0>,"status";
+		cs1_spidev = <&spidev2_1>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi2-3cs-overlay.dts b/arch/arm/boot/dts/overlays/spi2-3cs-overlay.dts
new file mode 100644
index 000000000000..28d40c6c3c37
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi2-3cs-overlay.dts
@@ -0,0 +1,81 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			spi2_pins: spi2_pins {
+				brcm,pins = <40 41 42>;
+				brcm,function = <3>; /* alt4 */
+			};
+
+			spi2_cs_pins: spi2_cs_pins {
+				brcm,pins = <43 44 45>;
+				brcm,function = <1>; /* output */
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&spi2>;
+		frag1: __overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+			pinctrl-names = "default";
+			pinctrl-0 = <&spi2_pins &spi2_cs_pins>;
+			cs-gpios = <&gpio 43 1>, <&gpio 44 1>, <&gpio 45 1>;
+			status = "okay";
+
+			spidev2_0: spidev@0 {
+				compatible = "spidev";
+				reg = <0>;      /* CE0 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+
+			spidev2_1: spidev@1 {
+				compatible = "spidev";
+				reg = <1>;      /* CE1 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+
+			spidev2_2: spidev@2 {
+				compatible = "spidev";
+				reg = <2>;      /* CE2 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&aux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&spi2_cs_pins>,"brcm,pins:0",
+			   <&frag1>,"cs-gpios:4";
+		cs1_pin  = <&spi2_cs_pins>,"brcm,pins:4",
+			   <&frag1>,"cs-gpios:16";
+		cs2_pin  = <&spi2_cs_pins>,"brcm,pins:8",
+			   <&frag1>,"cs-gpios:28";
+		cs0_spidev = <&spidev2_0>,"status";
+		cs1_spidev = <&spidev2_1>,"status";
+		cs2_spidev = <&spidev2_2>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi3-1cs-overlay.dts b/arch/arm/boot/dts/overlays/spi3-1cs-overlay.dts
new file mode 100644
index 000000000000..7abea6d86fd0
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi3-1cs-overlay.dts
@@ -0,0 +1,42 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target = <&spi3_cs_pins>;
+		frag0: __overlay__ {
+			brcm,pins = <0>;
+			brcm,function = <1>; /* output */
+		};
+	};
+
+	fragment@1 {
+		target = <&spi3>;
+		frag1: __overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			cs-gpios = <&gpio 0 1>;
+			status = "okay";
+
+			spidev3_0: spidev@0 {
+				compatible = "spidev";
+				reg = <0>;      /* CE0 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&frag0>,"brcm,pins:0",
+			   <&frag1>,"cs-gpios:4";
+		cs0_spidev = <&spidev3_0>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi3-1cs-pi5-overlay.dts b/arch/arm/boot/dts/overlays/spi3-1cs-pi5-overlay.dts
new file mode 100644
index 000000000000..a94e3a9f35ce
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi3-1cs-pi5-overlay.dts
@@ -0,0 +1,33 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&spi3>;
+		frag1: __overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			cs-gpios = <&gpio 4 1>;
+			status = "okay";
+
+			spidev3_0: spidev@0 {
+				compatible = "spidev";
+				reg = <0>;      /* CE0 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&frag1>,"cs-gpios:4";
+		cs0_spidev = <&spidev3_0>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi3-2cs-overlay.dts b/arch/arm/boot/dts/overlays/spi3-2cs-overlay.dts
new file mode 100644
index 000000000000..2f474ac769f5
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi3-2cs-overlay.dts
@@ -0,0 +1,54 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target = <&spi3_cs_pins>;
+		frag0: __overlay__ {
+			brcm,pins = <0 24>;
+			brcm,function = <1>; /* output */
+		};
+	};
+
+	fragment@1 {
+		target = <&spi3>;
+		frag1: __overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			cs-gpios = <&gpio 0 1>, <&gpio 24 1>;
+			status = "okay";
+
+			spidev3_0: spidev@0 {
+				compatible = "spidev";
+				reg = <0>;      /* CE0 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+
+			spidev3_1: spidev@1 {
+				compatible = "spidev";
+				reg = <1>;      /* CE1 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&frag0>,"brcm,pins:0",
+			   <&frag1>,"cs-gpios:4";
+		cs1_pin  = <&frag0>,"brcm,pins:4",
+			   <&frag1>,"cs-gpios:16";
+		cs0_spidev = <&spidev3_0>,"status";
+		cs1_spidev = <&spidev3_1>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi3-2cs-pi5-overlay.dts b/arch/arm/boot/dts/overlays/spi3-2cs-pi5-overlay.dts
new file mode 100644
index 000000000000..259548b37d5c
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi3-2cs-pi5-overlay.dts
@@ -0,0 +1,44 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&spi3>;
+		frag1: __overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			cs-gpios = <&gpio 4 1>, <&gpio 25 1>;
+			status = "okay";
+
+			spidev3_0: spidev@0 {
+				compatible = "spidev";
+				reg = <0>;      /* CE0 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+
+			spidev3_1: spidev@1 {
+				compatible = "spidev";
+				reg = <1>;      /* CE1 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&frag1>,"cs-gpios:4";
+		cs1_pin  = <&frag1>,"cs-gpios:16";
+		cs0_spidev = <&spidev3_0>,"status";
+		cs1_spidev = <&spidev3_1>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi4-1cs-overlay.dts b/arch/arm/boot/dts/overlays/spi4-1cs-overlay.dts
new file mode 100644
index 000000000000..66d89521124a
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi4-1cs-overlay.dts
@@ -0,0 +1,42 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target = <&spi4_cs_pins>;
+		frag0: __overlay__ {
+			brcm,pins = <4>;
+			brcm,function = <1>; /* output */
+		};
+	};
+
+	fragment@1 {
+		target = <&spi4>;
+		frag1: __overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			cs-gpios = <&gpio 4 1>;
+			status = "okay";
+
+			spidev4_0: spidev@0 {
+				compatible = "spidev";
+				reg = <0>;      /* CE0 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&frag0>,"brcm,pins:0",
+			   <&frag1>,"cs-gpios:4";
+		cs0_spidev = <&spidev4_0>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi4-2cs-overlay.dts b/arch/arm/boot/dts/overlays/spi4-2cs-overlay.dts
new file mode 100644
index 000000000000..83d8cb8b918c
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi4-2cs-overlay.dts
@@ -0,0 +1,54 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target = <&spi4_cs_pins>;
+		frag0: __overlay__ {
+			brcm,pins = <4 25>;
+			brcm,function = <1>; /* output */
+		};
+	};
+
+	fragment@1 {
+		target = <&spi4>;
+		frag1: __overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			cs-gpios = <&gpio 4 1>, <&gpio 25 1>;
+			status = "okay";
+
+			spidev4_0: spidev@0 {
+				compatible = "spidev";
+				reg = <0>;      /* CE0 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+
+			spidev4_1: spidev@1 {
+				compatible = "spidev";
+				reg = <1>;      /* CE1 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&frag0>,"brcm,pins:0",
+			   <&frag1>,"cs-gpios:4";
+		cs1_pin  = <&frag0>,"brcm,pins:4",
+			   <&frag1>,"cs-gpios:16";
+		cs0_spidev = <&spidev4_0>,"status";
+		cs1_spidev = <&spidev4_1>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi5-1cs-overlay.dts b/arch/arm/boot/dts/overlays/spi5-1cs-overlay.dts
new file mode 100644
index 000000000000..168b4825de34
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi5-1cs-overlay.dts
@@ -0,0 +1,42 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target = <&spi5_cs_pins>;
+		frag0: __overlay__ {
+			brcm,pins = <12>;
+			brcm,function = <1>; /* output */
+		};
+	};
+
+	fragment@1 {
+		target = <&spi5>;
+		frag1: __overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			cs-gpios = <&gpio 12 1>;
+			status = "okay";
+
+			spidev5_0: spidev@0 {
+				compatible = "spidev";
+				reg = <0>;      /* CE0 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&frag0>,"brcm,pins:0",
+			   <&frag1>,"cs-gpios:4";
+		cs0_spidev = <&spidev5_0>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi5-1cs-pi5-overlay.dts b/arch/arm/boot/dts/overlays/spi5-1cs-pi5-overlay.dts
new file mode 100644
index 000000000000..bde1837f26c0
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi5-1cs-pi5-overlay.dts
@@ -0,0 +1,33 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&spi5>;
+		frag1: __overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			cs-gpios = <&gpio 12 1>;
+			status = "okay";
+
+			spidev5_0: spidev@0 {
+				compatible = "spidev";
+				reg = <0>;      /* CE0 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&frag1>,"cs-gpios:4";
+		cs0_spidev = <&spidev5_0>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi5-2cs-overlay.dts b/arch/arm/boot/dts/overlays/spi5-2cs-overlay.dts
new file mode 100644
index 000000000000..c2a239a34b35
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi5-2cs-overlay.dts
@@ -0,0 +1,54 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target = <&spi5_cs_pins>;
+		frag0: __overlay__ {
+			brcm,pins = <12 26>;
+			brcm,function = <1>; /* output */
+		};
+	};
+
+	fragment@1 {
+		target = <&spi5>;
+		frag1: __overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			cs-gpios = <&gpio 12 1>, <&gpio 26 1>;
+			status = "okay";
+
+			spidev5_0: spidev@0 {
+				compatible = "spidev";
+				reg = <0>;      /* CE0 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+
+			spidev5_1: spidev@1 {
+				compatible = "spidev";
+				reg = <1>;      /* CE1 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&frag0>,"brcm,pins:0",
+			   <&frag1>,"cs-gpios:4";
+		cs1_pin  = <&frag0>,"brcm,pins:4",
+			   <&frag1>,"cs-gpios:16";
+		cs0_spidev = <&spidev5_0>,"status";
+		cs1_spidev = <&spidev5_1>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi5-2cs-pi5-overlay.dts b/arch/arm/boot/dts/overlays/spi5-2cs-pi5-overlay.dts
new file mode 100644
index 000000000000..2c9eee2a9db8
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi5-2cs-pi5-overlay.dts
@@ -0,0 +1,44 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&spi5>;
+		frag1: __overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			cs-gpios = <&gpio 12 1>, <&gpio 26 1>;
+			status = "okay";
+
+			spidev5_0: spidev@0 {
+				compatible = "spidev";
+				reg = <0>;      /* CE0 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+
+			spidev5_1: spidev@1 {
+				compatible = "spidev";
+				reg = <1>;      /* CE1 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&frag1>,"cs-gpios:4";
+		cs1_pin  = <&frag1>,"cs-gpios:16";
+		cs0_spidev = <&spidev5_0>,"status";
+		cs1_spidev = <&spidev5_1>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi6-1cs-overlay.dts b/arch/arm/boot/dts/overlays/spi6-1cs-overlay.dts
new file mode 100644
index 000000000000..a784f8a17d23
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi6-1cs-overlay.dts
@@ -0,0 +1,42 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target = <&spi6_cs_pins>;
+		frag0: __overlay__ {
+			brcm,pins = <18>;
+			brcm,function = <1>; /* output */
+		};
+	};
+
+	fragment@1 {
+		target = <&spi6>;
+		frag1: __overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			cs-gpios = <&gpio 18 1>;
+			status = "okay";
+
+			spidev6_0: spidev@0 {
+				compatible = "spidev";
+				reg = <0>;      /* CE0 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&frag0>,"brcm,pins:0",
+			   <&frag1>,"cs-gpios:4";
+		cs0_spidev = <&spidev6_0>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/spi6-2cs-overlay.dts b/arch/arm/boot/dts/overlays/spi6-2cs-overlay.dts
new file mode 100644
index 000000000000..8ef513814d2b
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/spi6-2cs-overlay.dts
@@ -0,0 +1,54 @@
+/dts-v1/;
+/plugin/;
+
+
+/ {
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target = <&spi6_cs_pins>;
+		frag0: __overlay__ {
+			brcm,pins = <18 27>;
+			brcm,function = <1>; /* output */
+		};
+	};
+
+	fragment@1 {
+		target = <&spi6>;
+		frag1: __overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			cs-gpios = <&gpio 18 1>, <&gpio 27 1>;
+			status = "okay";
+
+			spidev6_0: spidev@0 {
+				compatible = "spidev";
+				reg = <0>;      /* CE0 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+
+			spidev6_1: spidev@1 {
+				compatible = "spidev";
+				reg = <1>;      /* CE1 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "okay";
+			};
+		};
+	};
+
+	__overrides__ {
+		cs0_pin  = <&frag0>,"brcm,pins:0",
+			   <&frag1>,"cs-gpios:4";
+		cs1_pin  = <&frag0>,"brcm,pins:4",
+			   <&frag1>,"cs-gpios:16";
+		cs0_spidev = <&spidev6_0>,"status";
+		cs1_spidev = <&spidev6_1>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/ssd1306-overlay.dts b/arch/arm/boot/dts/overlays/ssd1306-overlay.dts
new file mode 100644
index 000000000000..bd0abec11c25
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ssd1306-overlay.dts
@@ -0,0 +1,36 @@
+// Overlay for SSD1306 128x64 and 128x32 OLED displays
+/dts-v1/;
+/plugin/;
+
+/ {
+    compatible = "brcm,bcm2835";
+
+    fragment@0 {
+	target = <&i2c1>;
+	__overlay__ {
+	    status = "okay";
+
+	    #address-cells = <1>;
+	    #size-cells = <0>;
+
+	    ssd1306: oled@3c{
+		compatible = "solomon,ssd1306";
+		reg = <0x3c>;
+		solomon,width = <128>;
+		solomon,height = <64>;
+		solomon,page-offset = <0>;
+	    };
+	};
+    };
+
+    __overrides__ {
+	address = <&ssd1306>,"reg:0";
+	width = <&ssd1306>,"solomon,width:0";
+	height = <&ssd1306>,"solomon,height:0";
+	offset = <&ssd1306>,"solomon,page-offset:0";
+	normal = <&ssd1306>,"solomon,segment-no-remap?";
+	sequential = <&ssd1306>,"solomon,com-seq?";
+	remapped = <&ssd1306>,"solomon,com-lrremap?";
+	inverted = <&ssd1306>,"solomon,com-invdir?";
+    };
+};
diff --git a/arch/arm/boot/dts/overlays/ssd1306-spi-overlay.dts b/arch/arm/boot/dts/overlays/ssd1306-spi-overlay.dts
new file mode 100644
index 000000000000..679749fc3065
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ssd1306-spi-overlay.dts
@@ -0,0 +1,85 @@
+/*
+ * Device Tree overlay for SSD1306 based SPI OLED display
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&gpio>;
+		__overlay__ {
+			ssd1306_pins: ssd1306_pins {
+                                brcm,pins = <25 24>;
+                                brcm,function = <1 1>; /* out out */
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			ssd1306: ssd1306@0{
+				compatible = "solomon,ssd1306";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&ssd1306_pins>;
+
+				spi-max-frequency = <10000000>;
+				bgr = <0>;
+				bpp = <1>;
+				rotate = <0>;
+				fps = <25>;
+				buswidth = <8>;
+				reset-gpios = <&gpio 25 1>;
+				dc-gpios = <&gpio 24 0>;
+				debug = <0>;
+
+				solomon,height = <64>;
+				solomon,width = <128>;
+				solomon,page-offset = <0>;
+			};
+		};
+	};
+
+	__overrides__ {
+		speed     = <&ssd1306>,"spi-max-frequency:0";
+		rotate    = <&ssd1306>,"rotate:0";
+		fps       = <&ssd1306>,"fps:0";
+		debug     = <&ssd1306>,"debug:0";
+		dc_pin    = <&ssd1306>,"dc-gpios:4",
+		            <&ssd1306_pins>,"brcm,pins:4";
+		reset_pin = <&ssd1306>,"reset-gpios:4",
+		            <&ssd1306_pins>,"brcm,pins:0";
+		height    = <&ssd1306>,"solomon,height:0";
+		inverted = <&ssd1306>,"solomon,com-invdir?";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/ssd1327-spi-overlay.dts b/arch/arm/boot/dts/overlays/ssd1327-spi-overlay.dts
new file mode 100644
index 000000000000..c0770738c176
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ssd1327-spi-overlay.dts
@@ -0,0 +1,70 @@
+/*
+ * Device Tree overlay for SSD1327 based SPI OLED display
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&gpio>;
+		__overlay__ {
+			ssd1327_pins: ssd1327_pins {
+				brcm,pins = <25 24>;
+				brcm,function = <1 1>;
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			ssd1327: ssd1327@0{
+				compatible = "solomon,ssd1327";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&ssd1327_pins>;
+
+				spi-max-frequency = <4500000>;
+				reset-gpios = <&gpio 25 1>;
+				dc-gpios = <&gpio 24 0>;
+			};
+		};
+	};
+
+	__overrides__ {
+		speed     = <&ssd1327>,"spi-max-frequency:0";
+		dc_pin    = <&ssd1327>,"dc-gpios:4",
+			    <&ssd1327_pins>,"brcm,pins:4";
+		reset_pin = <&ssd1327>,"reset-gpios:4",
+			    <&ssd1327_pins>,"brcm,pins:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/ssd1331-spi-overlay.dts b/arch/arm/boot/dts/overlays/ssd1331-spi-overlay.dts
new file mode 100644
index 000000000000..9fd5ebf2feda
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ssd1331-spi-overlay.dts
@@ -0,0 +1,83 @@
+/*
+ * Device Tree overlay for SSD1331 based SPI OLED display
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+        compatible = "brcm,bcm2835";
+
+        fragment@0 {
+                target = <&spi0>;
+                __overlay__ {
+                        status = "okay";
+                };
+        };
+
+        fragment@1 {
+                target = <&spidev0>;
+                __overlay__ {
+                        status = "disabled";
+                };
+        };
+
+        fragment@2 {
+                target = <&spidev1>;
+                __overlay__ {
+                        status = "disabled";
+                };
+        };
+
+        fragment@3 {
+                target = <&gpio>;
+                __overlay__ {
+                        ssd1331_pins: ssd1331_pins {
+                                brcm,pins = <25 24>;
+                                brcm,function = <1 1>; /* out out */
+                        };
+                };
+        };
+
+        fragment@4 {
+                target = <&spi0>;
+                __overlay__ {
+                        /* needed to avoid dtc warning */
+                        #address-cells = <1>;
+                        #size-cells = <0>;
+
+                        ssd1331: ssd1331@0{
+                                compatible = "solomon,ssd1331";
+                                reg = <0>;
+                                pinctrl-names = "default";
+                                pinctrl-0 = <&ssd1331_pins>;
+
+                                spi-max-frequency = <4500000>;
+                                bgr = <0>;
+                                bpp = <16>;
+                                rotate = <0>;
+                                fps = <25>;
+                                buswidth = <8>;
+                                reset-gpios = <&gpio 25 1>;
+                                dc-gpios = <&gpio 24 0>;
+                                debug = <0>;
+
+                                solomon,height = <64>;
+                                solomon,width = <96>;
+                                solomon,page-offset = <0>;
+                        };
+                };
+        };
+
+        __overrides__ {
+                speed     = <&ssd1331>,"spi-max-frequency:0";
+                rotate    = <&ssd1331>,"rotate:0";
+                fps       = <&ssd1331>,"fps:0";
+                debug     = <&ssd1331>,"debug:0";
+                dc_pin    = <&ssd1331>,"dc-gpios:4",
+                            <&ssd1331_pins>,"brcm,pins:4";
+                reset_pin = <&ssd1331>,"reset-gpios:4",
+                            <&ssd1331_pins>,"brcm,pins:0";
+        };
+};
diff --git a/arch/arm/boot/dts/overlays/ssd1351-spi-overlay.dts b/arch/arm/boot/dts/overlays/ssd1351-spi-overlay.dts
new file mode 100644
index 000000000000..ffc872c60648
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ssd1351-spi-overlay.dts
@@ -0,0 +1,83 @@
+/*
+ * Device Tree overlay for SSD1351 based SPI OLED display
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&gpio>;
+		__overlay__ {
+			ssd1351_pins: ssd1351_pins {
+                                brcm,pins = <25 24>;
+                                brcm,function = <1 1>; /* out out */
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			ssd1351: ssd1351@0{
+				compatible = "solomon,ssd1351";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&ssd1351_pins>;
+
+				spi-max-frequency = <4500000>;
+				bgr = <0>;
+				bpp = <16>;
+				rotate = <0>;
+				fps = <25>;
+				buswidth = <8>;
+				reset-gpios = <&gpio 25 1>;
+				dc-gpios = <&gpio 24 0>;
+				debug = <0>;
+
+				solomon,height = <128>;
+				solomon,width = <128>;
+				solomon,page-offset = <0>;
+			};
+		};
+	};
+
+	__overrides__ {
+		speed     = <&ssd1351>,"spi-max-frequency:0";
+		rotate    = <&ssd1351>,"rotate:0";
+		fps       = <&ssd1351>,"fps:0";
+		debug     = <&ssd1351>,"debug:0";
+		dc_pin    = <&ssd1351>,"dc-gpios:4",
+		            <&ssd1351_pins>,"brcm,pins:4";
+		reset_pin = <&ssd1351>,"reset-gpios:4",
+		            <&ssd1351_pins>,"brcm,pins:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/sunfounder-pipower3-overlay.dts b/arch/arm/boot/dts/overlays/sunfounder-pipower3-overlay.dts
new file mode 100644
index 000000000000..071899b86b46
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/sunfounder-pipower3-overlay.dts
@@ -0,0 +1,34 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/chosen";
+		__overlay__ {
+			power: power {
+				hat_current_supply = <5000>;
+			};
+		};
+	};
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@2 {
+		target-path = "/";
+		__overlay__ {
+			power_ctrl: power_ctrl {
+				compatible = "gpio-poweroff";
+				gpios = <&gpio 26 0>;
+				force;
+			};
+		};
+	};
+	__overrides__ {
+		poweroff_pin =	<&power_ctrl>,"gpios:4";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/sunfounder-pironman5-overlay.dts b/arch/arm/boot/dts/overlays/sunfounder-pironman5-overlay.dts
new file mode 100644
index 000000000000..fad68ef1813f
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/sunfounder-pironman5-overlay.dts
@@ -0,0 +1,55 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c1>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@1 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@2 {
+		target-path = "/";
+		__overlay__ {
+			gpio_ir: ir-receiver@d {
+				compatible = "gpio-ir-receiver";
+				pinctrl-names = "default";
+				pinctrl-0 = <&gpio_ir_pins>;
+
+				// pin number, high or low
+				gpios = <&gpio 13 1>;
+
+				// parameter for keymap name
+				linux,rc-map-name = "rc-rc6-mce";
+
+				status = "okay";
+			};
+		};
+	};
+	fragment@3 {
+		target = <&gpio>;
+		__overlay__ {
+			gpio_ir_pins: gpio_ir_pins@d {
+				brcm,pins = <13>;
+				brcm,function = <0>;
+				brcm,pull = <2>;
+			};
+		};
+	};
+	__overrides__ {
+		ir = <&gpio_ir>,"status";
+		ir_pins =
+			<&gpio_ir>,"gpios:4",
+			<&gpio_ir>,"reg:0",
+			<&gpio_ir_pins>,"brcm,pins:0",
+			<&gpio_ir_pins>,"reg:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/superaudioboard-overlay.dts b/arch/arm/boot/dts/overlays/superaudioboard-overlay.dts
new file mode 100755
index 000000000000..1006d5fe9e06
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/superaudioboard-overlay.dts
@@ -0,0 +1,73 @@
+// Definitions for SuperAudioBoard
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&sound>;
+		__overlay__ {
+			compatible = "simple-audio-card";
+			i2s-controller = <&i2s_clk_consumer>;
+			status = "okay";
+
+			simple-audio-card,name = "SuperAudioBoard";
+
+			simple-audio-card,widgets =
+				"Line", "Line In",
+				"Line", "Line Out";
+
+			simple-audio-card,routing =
+				"Line Out","AOUTA+",
+				"Line Out","AOUTA-",
+				"Line Out","AOUTB+",
+				"Line Out","AOUTB-",
+				"AINA","Line In",
+				"AINB","Line In";
+
+			simple-audio-card,format = "i2s";
+
+			simple-audio-card,bitclock-master = <&sound_master>;
+			simple-audio-card,frame-master = <&sound_master>;
+
+			simple-audio-card,cpu {
+				sound-dai = <&i2s_clk_consumer>;
+				dai-tdm-slot-num = <2>;
+				dai-tdm-slot-width = <32>;
+			};
+
+			sound_master: simple-audio-card,codec {
+				sound-dai = <&cs4271>;
+				system-clock-frequency = <24576000>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+    
+	fragment@2 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			cs4271: cs4271@10 {
+				#sound-dai-cells = <0>;
+				compatible = "cirrus,cs4271";
+				reg = <0x10>;
+				status = "okay";
+				reset-gpio = <&gpio 26 0>; /* Pin 26, active high */
+			};
+		};
+	};
+	__overrides__ {
+		gpiopin = <&cs4271>,"reset-gpio:4";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/sx150x-overlay.dts b/arch/arm/boot/dts/overlays/sx150x-overlay.dts
new file mode 100644
index 000000000000..191ccd5f9eca
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/sx150x-overlay.dts
@@ -0,0 +1,1706 @@
+// Definitions for SX150x I2C GPIO Expanders from Semtech
+
+// dtparams:
+//     sx150<x>-<n>-<m>          - Enables SX150X device on I2C#<n> with slave address <m>. <x> may be 1-9.
+//                                 <n> may be 0 or 1.  Permissible values of <m> (which is denoted in hex)
+//                                 depend on the device variant.
+//                                 For SX1501, SX1502, SX1504 and SX1505, <m> may be 20 or 21.
+//                                 For SX1503 and SX1506, <m> may be 20.
+//                                 For SX1507 and SX1509, <m> may be 3E, 3F, 70 or 71.
+//                                 For SX1508, <m> may be 20, 21, 22 or 23.
+//     sx150<x>-<n>-<m>-int-gpio - Integer, enables interrupts on SX150X device on I2C#<n> with slave address <m>,
+//                                 specifies the GPIO pin to which NINT output of SX150X is connected.
+//
+//
+// Example 1: A single SX1505 device on I2C#1 with its slave address set to 0x20 and NINT output connected to GPIO25:
+// dtoverlay=sx150x:sx1505-1-20,sx1505-1-20-int-gpio=25
+//
+// Example 2: Two SX1507 devices on I2C#0 with their slave addresses set to 0x3E and 0x70 (interrupts not used):
+// dtoverlay=sx150x:sx1507-0-3E,sx1507-0-70
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	// Enable I2C#0 interface
+	fragment@0 {
+		target = <&i2c0>;
+		__dormant__ {
+			status = "okay";
+		};
+	};
+
+	// Enable I2C#1 interface
+	fragment@1 {
+		target = <&i2c1>;
+		__dormant__ {
+			status = "okay";
+		};
+	};
+
+	// Enable a SX1501 on I2C#0 at slave addr 0x20
+	fragment@2 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1501_0_20: sx150x@20 {
+				compatible = "semtech,sx1501q";
+				reg = <0x20>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1501-0-20-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1501 on I2C#1 at slave addr 0x20
+	fragment@3 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1501_1_20: sx150x@20 {
+				compatible = "semtech,sx1501q";
+				reg = <0x20>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1501-1-20-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1501 on I2C#0 at slave addr 0x21
+	fragment@4 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1501_0_21: sx150x@21 {
+				compatible = "semtech,sx1501q";
+				reg = <0x21>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1501-0-21-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1501 on I2C#1 at slave addr 0x21
+	fragment@5 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1501_1_21: sx150x@21 {
+				compatible = "semtech,sx1501q";
+				reg = <0x21>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1501-1-21-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1502 on I2C#0 at slave addr 0x20
+	fragment@6 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1502_0_20: sx150x@20 {
+				compatible = "semtech,sx1502q";
+				reg = <0x20>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1502-0-20-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1502 on I2C#1 at slave addr 0x20
+	fragment@7 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1502_1_20: sx150x@20 {
+				compatible = "semtech,sx1502q";
+				reg = <0x20>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1502-1-20-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1502 on I2C#0 at slave addr 0x21
+	fragment@8 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1502_0_21: sx150x@21 {
+				compatible = "semtech,sx1502q";
+				reg = <0x21>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1502-0-21-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1502 on I2C#1 at slave addr 0x21
+	fragment@9 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1502_1_21: sx150x@21 {
+				compatible = "semtech,sx1502q";
+				reg = <0x21>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1501-1-21-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1503 on I2C#0 at slave addr 0x20
+	fragment@10 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1503_0_20: sx150x@20 {
+				compatible = "semtech,sx1503q";
+				reg = <0x20>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1503-0-20-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1503 on I2C#1 at slave addr 0x20
+	fragment@11 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1503_1_20: sx150x@20 {
+				compatible = "semtech,sx1503q";
+				reg = <0x20>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1503-1-20-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1504 on I2C#0 at slave addr 0x20
+	fragment@12 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1504_0_20: sx150x@20 {
+				compatible = "semtech,sx1504q";
+				reg = <0x20>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1504-0-20-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1504 on I2C#1 at slave addr 0x20
+	fragment@13 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1504_1_20: sx150x@20 {
+				compatible = "semtech,sx1504q";
+				reg = <0x20>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1504-1-20-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1504 on I2C#0 at slave addr 0x21
+	fragment@14 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1504_0_21: sx150x@21 {
+				compatible = "semtech,sx1504q";
+				reg = <0x21>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1504-0-21-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1504 on I2C#1 at slave addr 0x21
+	fragment@15 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1504_1_21: sx150x@21 {
+				compatible = "semtech,sx1504q";
+				reg = <0x21>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1504-1-20-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1505 on I2C#0 at slave addr 0x20
+	fragment@16 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1505_0_20: sx150x@20 {
+				compatible = "semtech,sx1505q";
+				reg = <0x20>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1505-0-20-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1505 on I2C#1 at slave addr 0x20
+	fragment@17 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1505_1_20: sx150x@20 {
+				compatible = "semtech,sx1505q";
+				reg = <0x20>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1505-1-20-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1505 on I2C#0 at slave addr 0x21
+	fragment@18 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1505_0_21: sx150x@21 {
+				compatible = "semtech,sx1505q";
+				reg = <0x21>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1505-0-21-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1505 on I2C#1 at slave addr 0x21
+	fragment@19 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1505_1_21: sx150x@21 {
+				compatible = "semtech,sx1505q";
+				reg = <0x21>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1505-1-21-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1506 on I2C#0 at slave addr 0x20
+	fragment@20 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1506_0_20: sx150x@20 {
+				compatible = "semtech,sx1506q";
+				reg = <0x20>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1506-0-20-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1506 on I2C#1 at slave addr 0x20
+	fragment@21 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1506_1_20: sx150x@20 {
+				compatible = "semtech,sx1506q";
+				reg = <0x20>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1506-1-20-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1507 on I2C#0 at slave addr 0x3E
+	fragment@22 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1507_0_3E: sx150x@3E {
+				compatible = "semtech,sx1507q";
+				reg = <0x3E>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1507_0_3E-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1507 on I2C#1 at slave addr 0x3E
+	fragment@23 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1507_1_3E: sx150x@3E {
+				compatible = "semtech,sx1507q";
+				reg = <0x3E>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1507_1_3E-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1507 on I2C#0 at slave addr 0x3F
+	fragment@24 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1507_0_3F: sx150x@3F {
+				compatible = "semtech,sx1507q";
+				reg = <0x3F>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1507_0_3F-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1507 on I2C#1 at slave addr 0x3F
+	fragment@25 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1507_1_3F: sx150x@3F {
+				compatible = "semtech,sx1507q";
+				reg = <0x3F>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1507_1_3F-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1507 on I2C#0 at slave addr 0x70
+	fragment@26 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1507_0_70: sx150x@70 {
+				compatible = "semtech,sx1507q";
+				reg = <0x70>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1507-0-70-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1507 on I2C#1 at slave addr 0x70
+	fragment@27 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1507_1_70: sx150x@70 {
+				compatible = "semtech,sx1507q";
+				reg = <0x70>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1507-1-70-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1507 on I2C#0 at slave addr 0x71
+	fragment@28 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1507_0_71: sx150x@71 {
+				compatible = "semtech,sx1507q";
+				reg = <0x71>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1507-0-71-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1507 on I2C#1 at slave addr 0x71
+	fragment@29 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1507_1_71: sx150x@71 {
+				compatible = "semtech,sx1507q";
+				reg = <0x71>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1507-1-71-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1508 on I2C#0 at slave addr 0x20
+	fragment@30 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1508_0_20: sx150x@20 {
+				compatible = "semtech,sx1508q";
+				reg = <0x20>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1508-0-20-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1508 on I2C#1 at slave addr 0x20
+	fragment@31 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1508_1_20: sx150x@20 {
+				compatible = "semtech,sx1508q";
+				reg = <0x20>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1508-1-20-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1508 on I2C#0 at slave addr 0x21
+	fragment@32 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1508_0_21: sx150x@21 {
+				compatible = "semtech,sx1508q";
+				reg = <0x21>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1508-0-21-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1508 on I2C#1 at slave addr 0x21
+	fragment@33 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1508_1_21: sx150x@21 {
+				compatible = "semtech,sx1508q";
+				reg = <0x21>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1508-1-21-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1508 on I2C#0 at slave addr 0x22
+	fragment@34 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1508_0_22: sx150x@22 {
+				compatible = "semtech,sx1508q";
+				reg = <0x22>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1508-0-22-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1508 on I2C#1 at slave addr 0x22
+	fragment@35 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1508_1_22: sx150x@22 {
+				compatible = "semtech,sx1508q";
+				reg = <0x22>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1508-1-22-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1508 on I2C#0 at slave addr 0x23
+	fragment@36 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1508_0_23: sx150x@23 {
+				compatible = "semtech,sx1508q";
+				reg = <0x23>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1508-0-23-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1508 on I2C#1 at slave addr 0x23
+	fragment@37 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1508_1_23: sx150x@23 {
+				compatible = "semtech,sx1508q";
+				reg = <0x23>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1508-1-23-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1509 on I2C#0 at slave addr 0x3E
+	fragment@38 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1509_0_3E: sx150x@3E {
+				compatible = "semtech,sx1509q";
+				reg = <0x3E>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1509_0_3E-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1509 on I2C#1 at slave addr 0x3E
+	fragment@39 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1509_1_3E: sx150x@3E {
+				compatible = "semtech,sx1509q";
+				reg = <0x3E>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1509_1_3E-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1509 on I2C#0 at slave addr 0x3F
+	fragment@40 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1509_0_3F: sx150x@3F {
+				compatible = "semtech,sx1509q";
+				reg = <0x3F>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1509_0_3F-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1509 on I2C#1 at slave addr 0x3F
+	fragment@41 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1509_1_3F: sx150x@3F {
+				compatible = "semtech,sx1509q";
+				reg = <0x3F>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1509_1_3F-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1509 on I2C#0 at slave addr 0x70
+	fragment@42 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1509_0_70: sx150x@70 {
+				compatible = "semtech,sx1509q";
+				reg = <0x70>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1509-0-70-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1509 on I2C#1 at slave addr 0x70
+	fragment@43 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1509_1_70: sx150x@70 {
+				compatible = "semtech,sx1509q";
+				reg = <0x70>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1509-1-70-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1509 on I2C#0 at slave addr 0x71
+	fragment@44 {
+		target = <&i2c0>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1509_0_71: sx150x@71 {
+				compatible = "semtech,sx1509q";
+				reg = <0x71>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1509-0-71-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable a SX1509 on I2C#1 at slave addr 0x71
+	fragment@45 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			sx1509_1_71: sx150x@71 {
+				compatible = "semtech,sx1509q";
+				reg = <0x71>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				#interrupt-cells = <2>;
+				interrupts = <25 2>; /* 1st word overwritten by sx1509-1-71-int-gpio parameter
+				                        2nd word is 2 for falling-edge triggered */
+				status = "okay";
+			};
+		};
+	};
+
+	// Enable interrupts for a SX1501 on I2C#0 at slave addr 0x20
+	fragment@46 {
+		target = <&sx1501_0_20>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_0_20_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1501 on I2C#1 at slave addr 0x20
+	fragment@47 {
+		target = <&sx1501_1_20>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_20_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1501 on I2C#0 at slave addr 0x21
+	fragment@48 {
+		target = <&sx1501_0_21>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_0_21_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1501 on I2C#1 at slave addr 0x21
+	fragment@49 {
+		target = <&sx1501_1_21>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_21_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1502 on I2C#0 at slave addr 0x20
+	fragment@50 {
+		target = <&sx1502_0_20>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_0_20_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1502 on I2C#1 at slave addr 0x20
+	fragment@51 {
+		target = <&sx1502_1_20>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_20_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1502 on I2C#0 at slave addr 0x21
+	fragment@52 {
+		target = <&sx1502_0_21>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_0_21_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1502 on I2C#1 at slave addr 0x21
+	fragment@53 {
+		target = <&sx1502_1_21>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_21_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1503 on I2C#0 at slave addr 0x20
+	fragment@54 {
+		target = <&sx1503_0_20>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_0_20_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1503 on I2C#1 at slave addr 0x20
+	fragment@55 {
+		target = <&sx1503_1_20>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_20_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1504 on I2C#0 at slave addr 0x20
+	fragment@56 {
+		target = <&sx1504_0_20>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_0_20_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1504 on I2C#1 at slave addr 0x20
+	fragment@57 {
+		target = <&sx1504_1_20>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_20_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1504 on I2C#0 at slave addr 0x21
+	fragment@58 {
+		target = <&sx1504_0_21>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_0_21_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1504 on I2C#1 at slave addr 0x21
+	fragment@59 {
+		target = <&sx1504_1_21>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_21_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1505 on I2C#0 at slave addr 0x20
+	fragment@60 {
+		target = <&sx1505_0_20>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_0_20_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1505 on I2C#1 at slave addr 0x20
+	fragment@61 {
+		target = <&sx1505_1_20>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_20_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1505 on I2C#0 at slave addr 0x21
+	fragment@62 {
+		target = <&sx1505_0_21>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_0_21_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1505 on I2C#1 at slave addr 0x21
+	fragment@63 {
+		target = <&sx1505_1_21>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_21_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1506 on I2C#0 at slave addr 0x20
+	fragment@64 {
+		target = <&sx1506_0_20>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_0_20_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1506 on I2C#1 at slave addr 0x20
+	fragment@65 {
+		target = <&sx1506_1_20>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_20_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1507 on I2C#0 at slave addr 0x3E
+	fragment@66 {
+		target = <&sx1507_0_3E>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_0_3E_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1507 on I2C#1 at slave addr 0x3E
+	fragment@67 {
+		target = <&sx1507_1_3E>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_3E_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1507 on I2C#0 at slave addr 0x3F
+	fragment@68 {
+		target = <&sx1507_0_3F>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_0_3F_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1507 on I2C#1 at slave addr 0x3F
+	fragment@69 {
+		target = <&sx1507_1_3F>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_3F_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1507 on I2C#0 at slave addr 0x70
+	fragment@70 {
+		target = <&sx1507_0_70>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_70_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1507 on I2C#1 at slave addr 0x70
+	fragment@71 {
+		target = <&sx1507_1_70>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_70_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1507 on I2C#0 at slave addr 0x71
+	fragment@72 {
+		target = <&sx1507_0_71>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_0_71_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1507 on I2C#1 at slave addr 0x71
+	fragment@73 {
+		target = <&sx1507_1_71>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_71_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1508 on I2C#0 at slave addr 0x20
+	fragment@74 {
+		target = <&sx1508_0_20>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_0_20_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1508 on I2C#1 at slave addr 0x20
+	fragment@75 {
+		target = <&sx1508_1_20>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_20_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1508 on I2C#0 at slave addr 0x21
+	fragment@76 {
+		target = <&sx1508_0_21>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_0_21_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1508 on I2C#1 at slave addr 0x21
+	fragment@77 {
+		target = <&sx1508_1_21>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_21_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1508 on I2C#0 at slave addr 0x22
+	fragment@78 {
+		target = <&sx1508_0_22>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_0_22_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1508 on I2C#1 at slave addr 0x22
+	fragment@79 {
+		target = <&sx1508_1_22>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_22_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1508 on I2C#0 at slave addr 0x23
+	fragment@80 {
+		target = <&sx1508_0_23>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_0_23_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1508 on I2C#1 at slave addr 0x23
+	fragment@81 {
+		target = <&sx1508_1_23>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_23_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1509 on I2C#0 at slave addr 0x3E
+	fragment@82 {
+		target = <&sx1509_0_3E>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_0_3E_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1509 on I2C#1 at slave addr 0x3E
+	fragment@83 {
+		target = <&sx1509_1_3E>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_3E_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1509 on I2C#0 at slave addr 0x3F
+	fragment@84 {
+		target = <&sx1509_0_3F>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_0_3F_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1509 on I2C#1 at slave addr 0x3F
+	fragment@85 {
+		target = <&sx1509_1_3F>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_3F_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1509 on I2C#0 at slave addr 0x70
+	fragment@86 {
+		target = <&sx1509_0_70>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_0_70_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1509 on I2C#1 at slave addr 0x70
+	fragment@87 {
+		target = <&sx1509_1_70>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_70_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1509 on I2C#0 at slave addr 0x71
+	fragment@88 {
+		target = <&sx1509_0_71>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_0_71_pins>;
+		};
+	};
+
+	// Enable interrupts for a SX1509 on I2C#1 at slave addr 0x71
+	fragment@89 {
+		target = <&sx1509_1_71>;
+		__dormant__ {
+			interrupt-parent = <&gpio>;
+			interrupt-controller;
+			pinctrl-names = "default";
+			pinctrl-0 = <&sx150x_1_71_pins>;
+		};
+	};
+
+	// Configure GPIO pin connected to NINT output of a SX150x on I2C#0 interface at slave addr 0x20
+        // Configure as a input with no pull-up/down
+	fragment@90 {
+		target = <&gpio>;
+		__dormant__ {
+			sx150x_0_20_pins: sx150x_0_20_pins {
+				brcm,pins = <0>;  /* overwritten by sx150x-0-20-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to NINT output of a SX150x on I2C#1 interface at slave addr 0x20
+        // Configure as a input with no pull-up/down
+	fragment@91 {
+		target = <&gpio>;
+		__dormant__ {
+			sx150x_1_20_pins: sx150x_1_20_pins {
+				brcm,pins = <0>;  /* overwritten by sx150x-1-20-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to NINT output of a SX150x on I2C#0 interface at slave addr 0x21
+        // Configure as a input with no pull-up/down
+	fragment@92 {
+		target = <&gpio>;
+		__dormant__ {
+			sx150x_0_21_pins: sx150x_0_21_pins {
+				brcm,pins = <0>;  /* overwritten by sx150x-0-21-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to NINT output of a SX150x on I2C#1 interface at slave addr 0x21
+        // Configure as a input with no pull-up/down
+	fragment@93 {
+		target = <&gpio>;
+		__dormant__ {
+			sx150x_1_21_pins: sx150x_1_21_pins {
+				brcm,pins = <0>;  /* overwritten by sx150x-1-21-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to NINT output of a SX150x on I2C#0 interface at slave addr 0x22
+        // Configure as a input with no pull-up/down
+	fragment@94 {
+		target = <&gpio>;
+		__dormant__ {
+			sx150x_0_22_pins: sx150x_0_22_pins {
+				brcm,pins = <0>;  /* overwritten by sx150x-0-22-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to NINT output of a SX150x on I2C#1 interface at slave addr 0x22
+        // Configure as a input with no pull-up/down
+	fragment@95 {
+		target = <&gpio>;
+		__dormant__ {
+			sx150x_1_22_pins: sx150x_1_22_pins {
+				brcm,pins = <0>;  /* overwritten by sx150x-1-22-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to NINT output of a SX150x on I2C#0 interface at slave addr 0x23
+        // Configure as a input with no pull-up/down
+	fragment@96 {
+		target = <&gpio>;
+		__dormant__ {
+			sx150x_0_23_pins: sx150x_0_23_pins {
+				brcm,pins = <0>;  /* overwritten by sx150x-0-23-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to NINT output of a SX150x on I2C#1 interface at slave addr 0x23
+        // Configure as a input with no pull-up/down
+	fragment@97 {
+		target = <&gpio>;
+		__dormant__ {
+			sx150x_1_23_pins: sx150x_1_23_pins {
+				brcm,pins = <0>;  /* overwritten by sx150x-1-23-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to NINT output of a SX150x on I2C#0 interface at slave addr 0x3E
+        // Configure as a input with no pull-up/down
+	fragment@98 {
+		target = <&gpio>;
+		__dormant__ {
+			sx150x_0_3E_pins: sx150x_0_3E_pins {
+				brcm,pins = <0>;  /* overwritten by sx150x-0-3E-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to NINT output of a SX150x on I2C#1 interface at slave addr 0x3E
+        // Configure as a input with no pull-up/down
+	fragment@99 {
+		target = <&gpio>;
+		__dormant__ {
+			sx150x_1_3E_pins: sx150x_1_3E_pins {
+				brcm,pins = <0>;  /* overwritten by sx150x-1-3E-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to NINT output of a SX150x on I2C#0 interface at slave addr 0x3F
+        // Configure as a input with no pull-up/down
+	fragment@100 {
+		target = <&gpio>;
+		__dormant__ {
+			sx150x_0_3F_pins: sx150x_0_3F_pins {
+				brcm,pins = <0>;  /* overwritten by sx150x-0-3F-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to NINT output of a SX150x on I2C#1 interface at slave addr 0x3F
+        // Configure as a input with no pull-up/down
+	fragment@101 {
+		target = <&gpio>;
+		__dormant__ {
+			sx150x_1_3F_pins: sx150x_1_3F_pins {
+				brcm,pins = <0>;  /* overwritten by sx150x-1-3F-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to NINT output of a SX150x on I2C#0 interface at slave addr 0x70
+        // Configure as a input with no pull-up/down
+	fragment@102 {
+		target = <&gpio>;
+		__dormant__ {
+			sx150x_0_70_pins: sx150x_0_70_pins {
+				brcm,pins = <0>;  /* overwritten by sx150x-0-70-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to NINT output of a SX150x on I2C#1 interface at slave addr 0x70
+        // Configure as a input with no pull-up/down
+	fragment@103 {
+		target = <&gpio>;
+		__dormant__ {
+			sx150x_1_70_pins: sx150x_1_70_pins {
+				brcm,pins = <0>;  /* overwritten by sx150x-1-70-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to NINT output of a SX150x on I2C#0 interface at slave addr 0x71
+        // Configure as a input with no pull-up/down
+	fragment@104 {
+		target = <&gpio>;
+		__dormant__ {
+			sx150x_0_71_pins: sx150x_0_71_pins {
+				brcm,pins = <0>;  /* overwritten by sx150x-0-71-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	// Configure GPIO pin connected to NINT output of a SX150x on I2C#1 interface at slave addr 0x71
+        // Configure as a input with no pull-up/down
+	fragment@105 {
+		target = <&gpio>;
+		__dormant__ {
+			sx150x_1_71_pins: sx150x_1_71_pins {
+				brcm,pins = <0>;  /* overwritten by sx150x-1-71-int-gpio parameter */
+				brcm,function = <0>;
+				brcm,pull = <0>;
+			};
+		};
+	};
+
+	__overrides__ {
+		sx1501-0-20          = <0>,"+0+2";
+		sx1501-1-20          = <0>,"+1+3";
+		sx1501-0-21          = <0>,"+0+4";
+		sx1501-1-21          = <0>,"+1+5";
+		sx1502-0-20          = <0>,"+0+6";
+		sx1502-1-20          = <0>,"+1+7";
+		sx1502-0-21          = <0>,"+0+8";
+		sx1502-1-21          = <0>,"+1+9";
+		sx1503-0-20          = <0>,"+0+10";
+		sx1503-1-20          = <0>,"+1+11";
+		sx1504-0-20          = <0>,"+0+12";
+		sx1504-1-20          = <0>,"+1+13";
+		sx1504-0-21          = <0>,"+0+14";
+		sx1504-1-21          = <0>,"+1+15";
+		sx1505-0-20          = <0>,"+0+16";
+		sx1505-1-20          = <0>,"+1+17";
+		sx1505-0-21          = <0>,"+0+18";
+		sx1505-1-21          = <0>,"+1+19";
+		sx1506-0-20          = <0>,"+0+20";
+		sx1506-1-20          = <0>,"+1+21";
+		sx1507-0-3E          = <0>,"+0+22";
+		sx1507-1-3E          = <0>,"+1+23";
+		sx1507-0-3F          = <0>,"+0+24";
+		sx1507-1-3F          = <0>,"+1+25";
+		sx1507-0-70          = <0>,"+0+26";
+		sx1507-1-70          = <0>,"+1+27";
+		sx1507-0-71          = <0>,"+0+28";
+		sx1507-1-71          = <0>,"+1+29";
+		sx1508-0-20          = <0>,"+0+30";
+		sx1508-1-20          = <0>,"+1+31";
+		sx1508-0-21          = <0>,"+0+32";
+		sx1508-1-21          = <0>,"+1+33";
+		sx1508-0-22          = <0>,"+0+34";
+		sx1508-1-22          = <0>,"+1+35";
+		sx1508-0-23          = <0>,"+0+36";
+		sx1508-1-23          = <0>,"+1+37";
+		sx1509-0-3E          = <0>,"+0+38";
+		sx1509-1-3E          = <0>,"+1+39";
+		sx1509-0-3F          = <0>,"+0+40";
+		sx1509-1-3F          = <0>,"+1+41";
+		sx1509-0-70          = <0>,"+0+42";
+		sx1509-1-70          = <0>,"+1+43";
+		sx1509-0-71          = <0>,"+0+44";
+		sx1509-1-71          = <0>,"+1+45";
+		sx1501-0-20-int-gpio = <0>,"+46+90",  <&sx150x_0_20_pins>,"brcm,pins:0", <&sx1501_0_20>,"interrupts:0";
+		sx1501-1-20-int-gpio = <0>,"+47+91",  <&sx150x_1_20_pins>,"brcm,pins:0", <&sx1501_1_20>,"interrupts:0";
+		sx1501-0-21-int-gpio = <0>,"+48+92",  <&sx150x_0_21_pins>,"brcm,pins:0", <&sx1501_0_21>,"interrupts:0";
+		sx1501-1-21-int-gpio = <0>,"+49+93",  <&sx150x_1_21_pins>,"brcm,pins:0", <&sx1501_1_21>,"interrupts:0";
+		sx1502-0-20-int-gpio = <0>,"+50+90",  <&sx150x_0_20_pins>,"brcm,pins:0", <&sx1502_0_20>,"interrupts:0";
+		sx1502-1-20-int-gpio = <0>,"+51+91",  <&sx150x_1_20_pins>,"brcm,pins:0", <&sx1502_1_20>,"interrupts:0";
+		sx1502-0-21-int-gpio = <0>,"+52+92",  <&sx150x_0_21_pins>,"brcm,pins:0", <&sx1502_0_21>,"interrupts:0";
+		sx1502-1-21-int-gpio = <0>,"+53+93",  <&sx150x_1_21_pins>,"brcm,pins:0", <&sx1502_1_21>,"interrupts:0";
+		sx1503-0-20-int-gpio = <0>,"+54+90",  <&sx150x_0_20_pins>,"brcm,pins:0", <&sx1503_0_20>,"interrupts:0";
+		sx1503-1-20-int-gpio = <0>,"+55+91",  <&sx150x_1_20_pins>,"brcm,pins:0", <&sx1503_1_20>,"interrupts:0";
+		sx1504-0-20-int-gpio = <0>,"+56+90",  <&sx150x_0_20_pins>,"brcm,pins:0", <&sx1504_0_20>,"interrupts:0";
+		sx1504-1-20-int-gpio = <0>,"+57+91",  <&sx150x_1_20_pins>,"brcm,pins:0", <&sx1504_1_20>,"interrupts:0";
+		sx1504-0-21-int-gpio = <0>,"+58+92",  <&sx150x_0_21_pins>,"brcm,pins:0", <&sx1504_0_21>,"interrupts:0";
+		sx1504-1-21-int-gpio = <0>,"+59+93",  <&sx150x_1_21_pins>,"brcm,pins:0", <&sx1504_1_21>,"interrupts:0";
+		sx1505-0-20-int-gpio = <0>,"+60+90",  <&sx150x_0_20_pins>,"brcm,pins:0", <&sx1505_0_20>,"interrupts:0";
+		sx1505-1-20-int-gpio = <0>,"+61+91",  <&sx150x_1_20_pins>,"brcm,pins:0", <&sx1505_1_20>,"interrupts:0";
+		sx1505-0-21-int-gpio = <0>,"+62+92",  <&sx150x_0_21_pins>,"brcm,pins:0", <&sx1505_0_21>,"interrupts:0";
+		sx1505-1-21-int-gpio = <0>,"+63+93",  <&sx150x_1_21_pins>,"brcm,pins:0", <&sx1505_1_21>,"interrupts:0";
+		sx1506-0-20-int-gpio = <0>,"+64+90",  <&sx150x_0_20_pins>,"brcm,pins:0", <&sx1506_0_20>,"interrupts:0";
+		sx1506-1-20-int-gpio = <0>,"+65+91",  <&sx150x_1_20_pins>,"brcm,pins:0", <&sx1506_1_20>,"interrupts:0";
+		sx1507-0-3E-int-gpio = <0>,"+66+98",  <&sx150x_0_3E_pins>,"brcm,pins:0", <&sx1507_0_3E>,"interrupts:0";
+		sx1507-1-3E-int-gpio = <0>,"+67+99",  <&sx150x_1_3E_pins>,"brcm,pins:0", <&sx1507_1_3E>,"interrupts:0";
+		sx1507-0-3F-int-gpio = <0>,"+68+100", <&sx150x_0_3F_pins>,"brcm,pins:0", <&sx1507_0_3F>,"interrupts:0";
+		sx1507-1-3F-int-gpio = <0>,"+69+101", <&sx150x_1_3F_pins>,"brcm,pins:0", <&sx1507_1_3F>,"interrupts:0";
+		sx1507-0-70-int-gpio = <0>,"+70+102", <&sx150x_0_70_pins>,"brcm,pins:0", <&sx1507_0_70>,"interrupts:0";
+		sx1507-1-70-int-gpio = <0>,"+71+103", <&sx150x_1_70_pins>,"brcm,pins:0", <&sx1507_1_70>,"interrupts:0";
+		sx1507-0-71-int-gpio = <0>,"+72+104", <&sx150x_0_71_pins>,"brcm,pins:0", <&sx1507_0_71>,"interrupts:0";
+		sx1507-1-71-int-gpio = <0>,"+73+105", <&sx150x_1_71_pins>,"brcm,pins:0", <&sx1507_1_71>,"interrupts:0";
+		sx1508-0-20-int-gpio = <0>,"+74+90",  <&sx150x_0_20_pins>,"brcm,pins:0", <&sx1508_0_20>,"interrupts:0";
+		sx1508-1-20-int-gpio = <0>,"+75+91",  <&sx150x_1_20_pins>,"brcm,pins:0", <&sx1508_1_20>,"interrupts:0";
+		sx1508-0-21-int-gpio = <0>,"+76+92",  <&sx150x_0_21_pins>,"brcm,pins:0", <&sx1508_0_21>,"interrupts:0";
+		sx1508-1-21-int-gpio = <0>,"+77+93",  <&sx150x_1_21_pins>,"brcm,pins:0", <&sx1508_1_21>,"interrupts:0";
+		sx1508-0-22-int-gpio = <0>,"+78+94",  <&sx150x_0_22_pins>,"brcm,pins:0", <&sx1508_0_22>,"interrupts:0";
+		sx1508-1-22-int-gpio = <0>,"+79+95",  <&sx150x_1_22_pins>,"brcm,pins:0", <&sx1508_1_22>,"interrupts:0";
+		sx1508-0-23-int-gpio = <0>,"+80+96",  <&sx150x_0_23_pins>,"brcm,pins:0", <&sx1508_0_23>,"interrupts:0";
+		sx1508-1-23-int-gpio = <0>,"+81+97",  <&sx150x_1_23_pins>,"brcm,pins:0", <&sx1508_1_23>,"interrupts:0";
+		sx1509-0-3E-int-gpio = <0>,"+82+98",  <&sx150x_0_3E_pins>,"brcm,pins:0", <&sx1509_0_3E>,"interrupts:0";
+		sx1509-1-3E-int-gpio = <0>,"+83+99",  <&sx150x_1_3E_pins>,"brcm,pins:0", <&sx1509_1_3E>,"interrupts:0";
+		sx1509-0-3F-int-gpio = <0>,"+84+100", <&sx150x_0_3F_pins>,"brcm,pins:0", <&sx1509_0_3F>,"interrupts:0";
+		sx1509-1-3F-int-gpio = <0>,"+85+101", <&sx150x_1_3F_pins>,"brcm,pins:0", <&sx1509_1_3F>,"interrupts:0";
+		sx1509-0-70-int-gpio = <0>,"+86+102", <&sx150x_0_70_pins>,"brcm,pins:0", <&sx1509_0_70>,"interrupts:0";
+		sx1509-1-70-int-gpio = <0>,"+87+103", <&sx150x_1_70_pins>,"brcm,pins:0", <&sx1509_1_70>,"interrupts:0";
+		sx1509-0-71-int-gpio = <0>,"+88+104", <&sx150x_0_71_pins>,"brcm,pins:0", <&sx1509_0_71>,"interrupts:0";
+		sx1509-1-71-int-gpio = <0>,"+89+105", <&sx150x_1_71_pins>,"brcm,pins:0", <&sx1509_1_71>,"interrupts:0";
+	};
+};
+
diff --git a/arch/arm/boot/dts/overlays/tc358743-audio-overlay.dts b/arch/arm/boot/dts/overlays/tc358743-audio-overlay.dts
new file mode 100644
index 000000000000..6bb3dceb0df3
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/tc358743-audio-overlay.dts
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions to add I2S audio from the Toshiba TC358743 HDMI to CSI2 bridge.
+// Requires tc358743 overlay to have been loaded to actually function.
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			tc358743_codec: tc358743-codec {
+				#sound-dai-cells = <0>;
+				compatible = "linux,spdif-dir";
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		sound_overlay: __overlay__ {
+			compatible = "simple-audio-card";
+			simple-audio-card,format = "i2s";
+			simple-audio-card,name = "tc358743";
+			simple-audio-card,bitclock-master = <&dailink0_master>;
+			simple-audio-card,frame-master = <&dailink0_master>;
+			status = "okay";
+
+			simple-audio-card,cpu {
+				sound-dai = <&i2s_clk_consumer>;
+				dai-tdm-slot-num = <2>;
+				dai-tdm-slot-width = <32>;
+			};
+			dailink0_master: simple-audio-card,codec {
+				sound-dai = <&tc358743_codec>;
+			};
+		};
+	};
+
+	__overrides__ {
+		card-name = <&sound_overlay>,"simple-audio-card,name";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/tc358743-overlay.dts b/arch/arm/boot/dts/overlays/tc358743-overlay.dts
new file mode 100644
index 000000000000..8834c7cf0c4f
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/tc358743-overlay.dts
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for Toshiba TC358743 HDMI to CSI2 bridge on VC I2C bus
+/dts-v1/;
+/plugin/;
+
+#include "tc358743.dtsi"
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	legacy_frag: fragment@100 {
+		target = <&csi1>;
+		__overlay__ {
+			compatible = "brcm,bcm2835-unicam-legacy";
+		};
+	};
+
+	__overrides__ {
+		media-controller = <0>,"!100";
+		// Overwrites the cam0 override from tc358743.dtsi as we need to
+		// update legacy_frag.
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+		       <&clk_frag>, "target:0=",<&cam0_clk>,
+		       <&tc358743>, "clocks:0=",<&cam0_clk>,
+		       <&legacy_frag>, "target:0=",<&csi0>;
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/tc358743-pi5-overlay.dts b/arch/arm/boot/dts/overlays/tc358743-pi5-overlay.dts
new file mode 100644
index 000000000000..8c00138385a3
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/tc358743-pi5-overlay.dts
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Definitions for Toshiba TC358743 HDMI to CSI2 bridge on VC I2C bus. Pi5 variant
+/dts-v1/;
+/plugin/;
+
+#include "tc358743.dtsi"
+
+/ {
+	compatible = "brcm,bcm2712";
+};
diff --git a/arch/arm/boot/dts/overlays/tc358743.dtsi b/arch/arm/boot/dts/overlays/tc358743.dtsi
new file mode 100644
index 000000000000..809332e9a072
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/tc358743.dtsi
@@ -0,0 +1,101 @@
+/ {
+	i2c_frag: fragment@0 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			tc358743: tc358743@f {
+				compatible = "toshiba,tc358743";
+				reg = <0x0f>;
+				status = "okay";
+
+				clocks = <&cam1_clk>;
+				clock-names = "refclk";
+
+				port {
+					tc358743_0: endpoint {
+						remote-endpoint = <&csi1_ep>;
+						clock-lanes = <0>;
+						clock-noncontinuous;
+						link-frequencies =
+							/bits/ 64 <486000000>;
+					};
+				};
+			};
+		};
+	};
+
+	csi_frag: fragment@1 {
+		target = <&csi1>;
+		csi: __overlay__ {
+			status = "okay";
+
+			port {
+				csi1_ep: endpoint {
+					remote-endpoint = <&tc358743_0>;
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&tc358743_0>;
+		__overlay__ {
+			data-lanes = <1 2>;
+		};
+	};
+
+	fragment@3 {
+		target = <&tc358743_0>;
+		__dormant__ {
+			data-lanes = <1 2 3 4>;
+		};
+	};
+
+	fragment@4 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@5 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	clk_frag: fragment@6 {
+		target = <&cam1_clk>;
+		__overlay__ {
+			status = "okay";
+			clock-frequency = <27000000>;
+		};
+	};
+
+	fragment@7 {
+		target = <&csi1_ep>;
+		__overlay__ {
+			data-lanes = <1 2>;
+		};
+	};
+
+	fragment@8 {
+		target = <&csi1_ep>;
+		__dormant__ {
+			data-lanes = <1 2 3 4>;
+		};
+	};
+
+	__overrides__ {
+		4lane = <0>, "-2+3-7+8";
+		link-frequency = <&tc358743_0>,"link-frequencies#0";
+		cam0 = <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&csi_frag>, "target:0=",<&csi0>,
+		       <&clk_frag>, "target:0=",<&cam0_clk>,
+		       <&tc358743>, "clocks:0=",<&cam0_clk>;
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/tinylcd35-overlay.dts b/arch/arm/boot/dts/overlays/tinylcd35-overlay.dts
new file mode 100644
index 000000000000..043b25887f2d
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/tinylcd35-overlay.dts
@@ -0,0 +1,209 @@
+/*
+ * tinylcd35-overlay.dts
+ *
+ * -------------------------------------------------
+ * www.tinlylcd.com
+ * -------------------------------------------------
+ * Device---Driver-----BUS       GPIO's
+ * display  tinylcd35  spi0.0    25 24 18
+ * touch    ads7846    spi0.1    5
+ * rtc      ds1307     i2c1-0068
+ * rtc      pcf8563    i2c1-0051
+ * keypad   gpio-keys  --------- 17 22 27 23 28
+ *
+ *
+ * TinyLCD.com 3.5 inch TFT
+ *
+ *  Version 001
+ *  5/3/2015  -- Noralf Trønnes     Initial Device tree framework
+ *  10/3/2015 -- tinylcd@gmail.com  added ds1307 support.
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&gpio>;
+		__overlay__ {
+			keypad_pins: keypad_pins {
+				brcm,pins = <4 17 22 23 27>;
+				brcm,function = <0>; /* in */
+				brcm,pull = <1>; /* down */
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			tinylcd35: tinylcd35@0{
+				compatible = "neosec,tinylcd";
+				reg = <0>;
+				spi-max-frequency = <48000000>;
+				rotate = <270>;
+				fps = <20>;
+				bgr;
+				buswidth = <8>;
+				reset-gpios = <&gpio 25 1>;
+				dc-gpios = <&gpio 24 0>;
+				led-gpios = <&gpio 18 0>;
+				debug = <0>;
+
+				init = <0x10000B0 0x80
+					0x10000C0 0x0A 0x0A
+					0x10000C1 0x01 0x01
+					0x10000C2 0x33
+					0x10000C5 0x00 0x42 0x80
+					0x10000B1 0xD0 0x11
+					0x10000B4 0x02
+					0x10000B6 0x00 0x22 0x3B
+					0x10000B7 0x07
+					0x1000036 0x58
+					0x10000F0 0x36 0xA5 0xD3
+					0x10000E5 0x80
+					0x10000E5 0x01
+					0x10000B3 0x00
+					0x10000E5 0x00
+					0x10000F0 0x36 0xA5 0x53
+					0x10000E0 0x00 0x35 0x33 0x00 0x00 0x00 0x00 0x35 0x33 0x00 0x00 0x00
+					0x100003A 0x55
+					0x1000011
+					0x2000001
+					0x1000029>;
+			};
+
+			tinylcd35_ts: tinylcd35_ts@1 {
+				compatible = "ti,ads7846";
+				reg = <1>;
+				status = "disabled";
+
+				spi-max-frequency = <2000000>;
+				interrupts = <5 2>; /* high-to-low edge triggered */
+				interrupt-parent = <&gpio>;
+				pendown-gpio = <&gpio 5 1>;
+				ti,x-plate-ohms = /bits/ 16 <100>;
+				ti,pressure-max = /bits/ 16 <255>;
+			};
+		};
+	};
+
+	/*  RTC    */
+
+	fragment@5 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			status = "okay";
+
+			pcf8563: pcf8563@51 {
+				compatible = "nxp,pcf8563";
+				reg = <0x51>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@6 {
+		target = <&i2c1>;
+		__dormant__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			status = "okay";
+
+			ds1307: ds1307@68 {
+				compatible = "dallas,ds1307";
+				reg = <0x68>;
+				status = "okay";
+			};
+		};
+	};
+
+	/*
+	 * Values for input event code is found under the
+	 * 'Keys and buttons' heading in include/uapi/linux/input.h
+	 */
+	fragment@7 {
+		target-path = "/soc";
+		__overlay__ {
+			keypad: keypad {
+				compatible = "gpio-keys";
+				pinctrl-names = "default";
+				pinctrl-0 = <&keypad_pins>;
+				status = "disabled";
+				autorepeat;
+
+				button@17 {
+					label = "GPIO KEY_UP";
+					linux,code = <103>;
+					gpios = <&gpio 17 0>;
+				};
+				button@22 {
+					label = "GPIO KEY_DOWN";
+					linux,code = <108>;
+					gpios = <&gpio 22 0>;
+				};
+				button@27 {
+					label = "GPIO KEY_LEFT";
+					linux,code = <105>;
+					gpios = <&gpio 27 0>;
+				};
+				button@23 {
+					label = "GPIO KEY_RIGHT";
+					linux,code = <106>;
+					gpios = <&gpio 23 0>;
+				};
+				button@4 {
+					label = "GPIO KEY_ENTER";
+					linux,code = <28>;
+					gpios = <&gpio 4 0>;
+				};
+			};
+		};
+	};
+
+	__overrides__ {
+		speed =      <&tinylcd35>,"spi-max-frequency:0";
+		rotate =     <&tinylcd35>,"rotate:0";
+		fps =        <&tinylcd35>,"fps:0";
+		debug =      <&tinylcd35>,"debug:0";
+		touch =      <&tinylcd35_ts>,"status";
+		touchgpio =  <&tinylcd35_ts>,"interrupts:0",
+			     <&tinylcd35_ts>,"pendown-gpio:4";
+		xohms =      <&tinylcd35_ts>,"ti,x-plate-ohms;0";
+		rtc-pcf =    <0>,"=5";
+		rtc-ds =     <0>,"=6";
+		keypad =     <&keypad>,"status";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/tpm-slb9670-overlay.dts b/arch/arm/boot/dts/overlays/tpm-slb9670-overlay.dts
new file mode 100644
index 000000000000..e69188503ca3
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/tpm-slb9670-overlay.dts
@@ -0,0 +1,44 @@
+/*
+ * Device Tree overlay for the Infineon SLB9670 Trusted Platform Module add-on
+ * boards, which can be used as a secure key storage and hwrng.
+ * available as "Iridium SLB9670" by Infineon and "LetsTrust TPM" by pi3g.
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+			slb9670: slb9670@1 {
+				compatible = "infineon,slb9670";
+				reg = <1>;	/* CE1 */
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <32000000>;
+				status = "okay";
+			};
+
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/tpm-slb9673-overlay.dts b/arch/arm/boot/dts/overlays/tpm-slb9673-overlay.dts
new file mode 100644
index 000000000000..ed6de957e96c
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/tpm-slb9673-overlay.dts
@@ -0,0 +1,51 @@
+/*
+ * Device Tree overlay for the Infineon SLB9673 Trusted Platform Module add-on
+ * boards, which can be used as a secure key storage and hwrng.
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	/* Due to issue https://github.com/raspberrypi/linux/issues/4884 the
+	   hardware I2C needs to be disabled and software I2C enabled */
+	fragment@0 {
+		target = <&i2c_arm>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			i2c1: i2c-gpio@1 {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				compatible = "i2c-gpio";
+				gpios = <&gpio 2 6>, /* SDA GPIO_OPEN_DRAIN */
+				     	<&gpio 3 6>; /* CLK GPIO_OPEN_DRAIN */
+				clock-frequency = <400000>;
+				i2c-gpio,delay-us = <0x01>;
+				status = "okay";
+			};
+		};
+	};
+
+	/* Add the TPM */
+	fragment@2 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			slb9673: slb9673@2e {
+				compatible = "infineon,slb9673", "tcg,tpm-tis-i2c";
+				reg = <0x2e>;
+				status = "okay";
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/uart0-overlay.dts b/arch/arm/boot/dts/overlays/uart0-overlay.dts
new file mode 100755
index 000000000000..6bf2e0fd5c61
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/uart0-overlay.dts
@@ -0,0 +1,32 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&uart0>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&uart0_pins>;
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			uart0_pins: uart0_ovl_pins {
+				brcm,pins = <14 15>;
+				brcm,function = <4>; /* alt0 */
+				brcm,pull = <0 2>;
+			};
+		};
+	};
+
+	__overrides__ {
+		txd0_pin = <&uart0_pins>,"brcm,pins:0";
+		rxd0_pin = <&uart0_pins>,"brcm,pins:4";
+		pin_func = <&uart0_pins>,"brcm,function:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/uart0-pi5-overlay.dts b/arch/arm/boot/dts/overlays/uart0-pi5-overlay.dts
new file mode 100755
index 000000000000..1e378478b6c0
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/uart0-pi5-overlay.dts
@@ -0,0 +1,25 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&uart0>;
+		frag0: __overlay__ {
+			status = "okay";
+			pinctrl-0 = <&uart0_pins>;
+			rs485-rts-delay = <0 0>;
+		};
+	};
+
+	__overrides__ {
+		ctsrts = <&frag0>,"pinctrl-0:4=",<&uart0_ctsrts_pins>;
+		rs485 = <&frag0>,"pinctrl-0:4=",<&uart0_ctsrts_pins>,
+				<&frag0>,"linux,rs485-enabled-at-boot-time?";
+		rs485_full_duplex = <&frag0>,"rs485-rx-during-tx?";
+		rs485_invert_rts = <&frag0>,"rs485-rts-active-low?";
+		rs485_rts_on_delay = <&frag0>,"rs485-rts-delay:0";
+		rs485_rts_off_delay = <&frag0>,"rs485-rts-delay:4";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/uart1-overlay.dts b/arch/arm/boot/dts/overlays/uart1-overlay.dts
new file mode 100644
index 000000000000..64163bf932b7
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/uart1-overlay.dts
@@ -0,0 +1,38 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&uart1>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&uart1_pins>;
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			uart1_pins: uart1_ovl_pins {
+				brcm,pins = <14 15>;
+				brcm,function = <2>; /* alt5 */
+				brcm,pull = <0 2>;
+			};
+		};
+	};
+
+	fragment@2 {
+		target-path = "/chosen";
+		__overlay__ {
+			bootargs = "8250.nr_uarts=1";
+		};
+	};
+
+	__overrides__ {
+		txd1_pin = <&uart1_pins>,"brcm,pins:0";
+		rxd1_pin = <&uart1_pins>,"brcm,pins:4";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/uart1-pi5-overlay.dts b/arch/arm/boot/dts/overlays/uart1-pi5-overlay.dts
new file mode 100755
index 000000000000..0a26181b6e78
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/uart1-pi5-overlay.dts
@@ -0,0 +1,25 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&uart1>;
+		frag0: __overlay__ {
+			status = "okay";
+			pinctrl-0 = <&uart1_pins>;
+			rs485-rts-delay = <0 0>;
+		};
+	};
+
+	__overrides__ {
+		ctsrts = <&frag0>,"pinctrl-0:4=",<&uart1_ctsrts_pins>;
+		rs485 = <&frag0>,"pinctrl-0:4=",<&uart1_ctsrts_pins>,
+				<&frag0>,"linux,rs485-enabled-at-boot-time?";
+		rs485_full_duplex = <&frag0>,"rs485-rx-during-tx?";
+		rs485_invert_rts = <&frag0>,"rs485-rts-active-low?";
+		rs485_rts_on_delay = <&frag0>,"rs485-rts-delay:0";
+		rs485_rts_off_delay = <&frag0>,"rs485-rts-delay:4";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/uart2-overlay.dts b/arch/arm/boot/dts/overlays/uart2-overlay.dts
new file mode 100644
index 000000000000..ef472e2f4b09
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/uart2-overlay.dts
@@ -0,0 +1,38 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target = <&uart2>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&uart2_pins>;
+		__dormant__ {
+			brcm,pins = <0 1 2 3>;
+			brcm,pull = <0 2 2 0>;
+		};
+	};
+
+	fragment@2 {
+		target = <&uart2>;
+		rs485: __dormant__ {
+			linux,rs485-enabled-at-boot-time;
+			rs485-rts-delay = <0 0>;
+		};
+	};
+
+	__overrides__ {
+		ctsrts = <0>,"=1";
+		rs485 = <0>,"=1=2";
+		rs485_full_duplex = <&rs485>,"rs485-rx-during-tx?";
+		rs485_invert_rts = <&rs485>,"rs485-rts-active-low?";
+		rs485_rts_on_delay = <&rs485>, "rs485-rts-delay:0";
+		rs485_rts_off_delay = <&rs485>, "rs485-rts-delay:4";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/uart2-pi5-overlay.dts b/arch/arm/boot/dts/overlays/uart2-pi5-overlay.dts
new file mode 100755
index 000000000000..009bad568e0f
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/uart2-pi5-overlay.dts
@@ -0,0 +1,25 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&uart2>;
+		frag0: __overlay__ {
+			status = "okay";
+			pinctrl-0 = <&uart2_pins>;
+			rs485-rts-delay = <0 0>;
+		};
+	};
+
+	__overrides__ {
+		ctsrts = <&frag0>,"pinctrl-0:4=",<&uart2_ctsrts_pins>;
+		rs485 = <&frag0>,"pinctrl-0:4=",<&uart2_ctsrts_pins>,
+				<&frag0>,"linux,rs485-enabled-at-boot-time?";
+		rs485_full_duplex = <&frag0>,"rs485-rx-during-tx?";
+		rs485_invert_rts = <&frag0>,"rs485-rts-active-low?";
+		rs485_rts_on_delay = <&frag0>,"rs485-rts-delay:0";
+		rs485_rts_off_delay = <&frag0>,"rs485-rts-delay:4";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/uart3-overlay.dts b/arch/arm/boot/dts/overlays/uart3-overlay.dts
new file mode 100644
index 000000000000..68e6bc1d9e25
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/uart3-overlay.dts
@@ -0,0 +1,38 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target = <&uart3>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&uart3_pins>;
+		__dormant__ {
+			brcm,pins = <4 5 6 7>;
+			brcm,pull = <0 2 2 0>;
+		};
+	};
+
+	fragment@2 {
+		target = <&uart3>;
+		rs485: __dormant__ {
+			linux,rs485-enabled-at-boot-time;
+			rs485-rts-delay = <0 0>;
+		};
+	};
+
+	__overrides__ {
+		ctsrts = <0>,"=1";
+		rs485 = <0>,"=1=2";
+		rs485_full_duplex = <&rs485>,"rs485-rx-during-tx?";
+		rs485_invert_rts = <&rs485>,"rs485-rts-active-low?";
+		rs485_rts_on_delay = <&rs485>, "rs485-rts-delay:0";
+		rs485_rts_off_delay = <&rs485>, "rs485-rts-delay:4";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/uart3-pi5-overlay.dts b/arch/arm/boot/dts/overlays/uart3-pi5-overlay.dts
new file mode 100755
index 000000000000..4eec772e7811
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/uart3-pi5-overlay.dts
@@ -0,0 +1,25 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&uart3>;
+		frag0: __overlay__ {
+			status = "okay";
+			pinctrl-0 = <&uart3_pins>;
+			rs485-rts-delay = <0 0>;
+		};
+	};
+
+	__overrides__ {
+		ctsrts = <&frag0>,"pinctrl-0:4=",<&uart3_ctsrts_pins>;
+		rs485 = <&frag0>,"pinctrl-0:4=",<&uart3_ctsrts_pins>,
+				<&frag0>,"linux,rs485-enabled-at-boot-time?";
+		rs485_full_duplex = <&frag0>,"rs485-rx-during-tx?";
+		rs485_invert_rts = <&frag0>,"rs485-rts-active-low?";
+		rs485_rts_on_delay = <&frag0>,"rs485-rts-delay:0";
+		rs485_rts_off_delay = <&frag0>,"rs485-rts-delay:4";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/uart4-overlay.dts b/arch/arm/boot/dts/overlays/uart4-overlay.dts
new file mode 100644
index 000000000000..26db5d5ed0d9
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/uart4-overlay.dts
@@ -0,0 +1,38 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target = <&uart4>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&uart4_pins>;
+		__dormant__ {
+			brcm,pins = <8 9 10 11>;
+			brcm,pull = <0 2 2 0>;
+		};
+	};
+
+	fragment@2 {
+		target = <&uart4>;
+		rs485: __dormant__ {
+			linux,rs485-enabled-at-boot-time;
+			rs485-rts-delay = <0 0>;
+		};
+	};
+
+	__overrides__ {
+		ctsrts = <0>,"=1";
+		rs485 = <0>,"=1=2";
+		rs485_full_duplex = <&rs485>,"rs485-rx-during-tx?";
+		rs485_invert_rts = <&rs485>,"rs485-rts-active-low?";
+		rs485_rts_on_delay = <&rs485>, "rs485-rts-delay:0";
+		rs485_rts_off_delay = <&rs485>, "rs485-rts-delay:4";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/uart4-pi5-overlay.dts b/arch/arm/boot/dts/overlays/uart4-pi5-overlay.dts
new file mode 100755
index 000000000000..792d61beb398
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/uart4-pi5-overlay.dts
@@ -0,0 +1,25 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&uart4>;
+		frag0: __overlay__ {
+			status = "okay";
+			pinctrl-0 = <&uart4_pins>;
+			rs485-rts-delay = <0 0>;
+		};
+	};
+
+	__overrides__ {
+		ctsrts = <&frag0>,"pinctrl-0:4=",<&uart4_ctsrts_pins>;
+		rs485 = <&frag0>,"pinctrl-0:4=",<&uart4_ctsrts_pins>,
+				<&frag0>,"linux,rs485-enabled-at-boot-time?";
+		rs485_full_duplex = <&frag0>,"rs485-rx-during-tx?";
+		rs485_invert_rts = <&frag0>,"rs485-rts-active-low?";
+		rs485_rts_on_delay = <&frag0>,"rs485-rts-delay:0";
+		rs485_rts_off_delay = <&frag0>,"rs485-rts-delay:4";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/uart5-overlay.dts b/arch/arm/boot/dts/overlays/uart5-overlay.dts
new file mode 100644
index 000000000000..f90447c61913
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/uart5-overlay.dts
@@ -0,0 +1,38 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target = <&uart5>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&uart5_pins>;
+		__dormant__ {
+			brcm,pins = <12 13 14 15>;
+			brcm,pull = <0 2 2 0>;
+		};
+	};
+
+	fragment@2 {
+		target = <&uart5>;
+		rs485: __dormant__ {
+			linux,rs485-enabled-at-boot-time;
+			rs485-rts-delay = <0 0>;
+		};
+	};
+
+	__overrides__ {
+		ctsrts = <0>,"=1";
+		rs485 = <0>,"=1=2";
+		rs485_full_duplex = <&rs485>,"rs485-rx-during-tx?";
+		rs485_invert_rts = <&rs485>,"rs485-rts-active-low?";
+		rs485_rts_on_delay = <&rs485>, "rs485-rts-delay:0";
+		rs485_rts_off_delay = <&rs485>, "rs485-rts-delay:4";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/udrc-overlay.dts b/arch/arm/boot/dts/overlays/udrc-overlay.dts
new file mode 100644
index 000000000000..24a2432ccd29
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/udrc-overlay.dts
@@ -0,0 +1,124 @@
+#include <dt-bindings/clock/bcm2835.h>
+/*
+ * Device tree overlay for the Universal Digital Radio Controller
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+    compatible = "brcm,bcm2835";
+    fragment@0 {
+        target = <&i2s_clk_producer>;
+        __overlay__ {
+            clocks = <&clocks BCM2835_CLOCK_PCM>;
+            clock-names = "pcm";
+            status = "okay";
+        };
+    };
+
+    fragment@1 {
+        target-path = "/";
+        __overlay__ {
+            regulators {
+                compatible = "simple-bus";
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                udrc0_ldoin: udrc0_ldoin {
+                    compatible = "regulator-fixed";
+                    regulator-name = "ldoin";
+                    regulator-min-microvolt = <3300000>;
+                    regulator-max-microvolt = <3300000>;
+                    regulator-always-on;
+                };
+            };
+        };
+    };
+
+    fragment@2 {
+        target = <&i2c1>;
+        __overlay__ {
+            #address-cells = <1>;
+            #size-cells = <0>;
+            status = "okay";
+            clocks = <&clocks BCM2835_CLOCK_VPU>;
+            clock-frequency = <400000>;
+
+            tlv320aic32x4: tlv320aic32x4@18 {
+                compatible = "ti,tlv320aic32x4";
+                #sound-dai-cells = <0>;
+                reg = <0x18>;
+                status = "okay";
+
+                clocks = <&clocks BCM2835_CLOCK_GP0>;
+                clock-names = "mclk";
+                assigned-clocks = <&clocks BCM2835_CLOCK_GP0>;
+                assigned-clock-rates = <25000000>;
+
+                pinctrl-names = "default";
+                pinctrl-0 = <&gpclk0_pin &aic3204_reset>;
+
+                reset-gpios = <&gpio 13 0>;
+
+                iov-supply = <&udrc0_ldoin>;
+                ldoin-supply = <&udrc0_ldoin>;
+            };
+        };
+    };
+
+    fragment@3 {
+        target = <&sound>;
+        snd: __overlay__ {
+            compatible = "simple-audio-card";
+            i2s-controller = <&i2s_clk_producer>;
+            status = "okay";
+
+            simple-audio-card,name = "udrc";
+            simple-audio-card,format = "i2s";
+
+            simple-audio-card,bitclock-master = <&dailink0_master>;
+            simple-audio-card,frame-master = <&dailink0_master>;
+
+            simple-audio-card,widgets =
+                "Line", "Line In",
+                "Line", "Line Out";
+
+            simple-audio-card,routing =
+                "IN1_R", "Line In",
+                "IN1_L", "Line In",
+                "CM_L", "Line In",
+                "CM_R", "Line In",
+                "Line Out", "LOR",
+                "Line Out", "LOL";
+
+            dailink0_master: simple-audio-card,cpu {
+                sound-dai = <&i2s_clk_producer>;
+            };
+
+            simple-audio-card,codec {
+                sound-dai = <&tlv320aic32x4>;
+            };
+        };
+    };
+
+    fragment@4 {
+        target = <&gpio>;
+        __overlay__ {
+            gpclk0_pin: gpclk0_pin {
+                brcm,pins = <4>;
+                brcm,function = <4>;
+            };
+
+            aic3204_reset: aic3204_reset {
+                brcm,pins = <13>;
+                brcm,function = <1>;
+                brcm,pull = <1>;
+            };
+        };
+    };
+
+    __overrides__ {
+        alsaname = <&snd>, "simple-audio-card,name";
+    };
+};
diff --git a/arch/arm/boot/dts/overlays/ugreen-dabboard-overlay.dts b/arch/arm/boot/dts/overlays/ugreen-dabboard-overlay.dts
new file mode 100644
index 000000000000..234f1f38225b
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ugreen-dabboard-overlay.dts
@@ -0,0 +1,49 @@
+// Definitions for the ugreen dabboard I2S
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_consumer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			dmic_codec: dmic-codec {
+				#sound-dai-cells = <0>;
+				compatible = "dmic-codec";
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&sound>;
+		sound_overlay: __overlay__ {
+			compatible = "simple-audio-card";
+			simple-audio-card,format = "i2s";
+			simple-audio-card,name = "dabboard";
+			simple-audio-card,bitclock-master = <&dailink0_master>;
+			simple-audio-card,frame-master = <&dailink0_master>;
+			simple-audio-card,widgets = "Microphone", "Microphone Jack";
+			status = "okay";
+			simple-audio-card,cpu {
+				sound-dai = <&i2s_clk_consumer>;
+			};
+			dailink0_master: simple-audio-card,codec {
+				#sound-dai-cells = <0>;
+				sound-dai = <&dmic_codec>;
+			};
+		};
+	};
+
+	__overrides__ {
+		card-name = <&sound_overlay>,"simple-audio-card,name";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/upstream-overlay.dts b/arch/arm/boot/dts/overlays/upstream-overlay.dts
new file mode 100644
index 000000000000..55a99736a33b
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/upstream-overlay.dts
@@ -0,0 +1,101 @@
+// redo: ovmerge -c vc4-kms-v3d-overlay.dts,cma-default,composite dwc2-overlay.dts,dr_mode=otg
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/clock/bcm2835.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+	fragment@0 {
+		target = <&i2c2>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@1 {
+		target = <&fb>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+	fragment@2 {
+		target = <&pixelvalve0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@3 {
+		target = <&pixelvalve1>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@4 {
+		target = <&pixelvalve2>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@5 {
+		target = <&hvs>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@6 {
+		target = <&hdmi>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@7 {
+		target = <&v3d>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@8 {
+		target = <&vc4>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@9 {
+		target = <&clocks>;
+		__overlay__ {
+			claim-clocks = <BCM2835_PLLD_DSI0 BCM2835_PLLD_DSI1 BCM2835_PLLH_AUX BCM2835_PLLH_PIX>;
+		};
+	};
+	fragment@10 {
+		target = <&vec>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@11 {
+		target = <&txp>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@12 {
+		target = <&chosen>;
+		__overlay__ {
+			bootargs = "snd_bcm2835.enable_hdmi=0";
+		};
+	};
+	fragment@13 {
+		target = <&usb>;
+		#address-cells = <1>;
+		#size-cells = <1>;
+		__overlay__ {
+			compatible = "brcm,bcm2835-usb";
+			dr_mode = "otg";
+			g-np-tx-fifo-size = <32>;
+			g-rx-fifo-size = <558>;
+			g-tx-fifo-size = <512 512 512 512 512 256 256>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/upstream-pi4-overlay.dts b/arch/arm/boot/dts/overlays/upstream-pi4-overlay.dts
new file mode 100644
index 000000000000..1dc60ae6d967
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/upstream-pi4-overlay.dts
@@ -0,0 +1,137 @@
+// redo: ovmerge -c vc4-kms-v3d-pi4-overlay.dts,cma-default dwc2-overlay.dts,dr_mode=otg
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/clock/bcm2835.h>
+
+/ {
+	compatible = "brcm,bcm2711";
+	fragment@0 {
+		target = <&ddc0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@1 {
+		target = <&ddc1>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@2 {
+		target = <&hdmi0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@3 {
+		target = <&hdmi1>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@4 {
+		target = <&hvs>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@5 {
+		target = <&pixelvalve0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@6 {
+		target = <&pixelvalve1>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@7 {
+		target = <&pixelvalve2>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@8 {
+		target = <&pixelvalve3>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@9 {
+		target = <&pixelvalve4>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@10 {
+		target = <&v3d>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@11 {
+		target = <&vc4>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@12 {
+		target = <&txp>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@13 {
+		target = <&fb>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+	fragment@14 {
+		target = <&firmwarekms>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+	fragment@15 {
+		target = <&vec>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+	fragment@16 {
+		target-path = "/chosen";
+		__overlay__ {
+			bootargs = "snd_bcm2835.enable_hdmi=0";
+		};
+	};
+	fragment@17 {
+		target = <&dvp>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@18 {
+		target = <&aon_intr>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@19 {
+		target = <&usb>;
+		#address-cells = <1>;
+		#size-cells = <1>;
+		__overlay__ {
+			compatible = "brcm,bcm2835-usb";
+			dr_mode = "otg";
+			g-np-tx-fifo-size = <32>;
+			g-rx-fifo-size = <558>;
+			g-tx-fifo-size = <512 512 512 512 512 256 256>;
+			status = "okay";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-fkms-v3d-overlay.dts b/arch/arm/boot/dts/overlays/vc4-fkms-v3d-overlay.dts
new file mode 100644
index 000000000000..847a98f2a10c
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-fkms-v3d-overlay.dts
@@ -0,0 +1,43 @@
+/*
+ * vc4-fkms-v3d-overlay.dts
+ */
+
+#include "cma-overlay.dts"
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@1 {
+		target = <&fb>;
+		__overlay__  {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&firmwarekms>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@3 {
+		target = <&v3d>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@4 {
+		target = <&vc4>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+	fragment@5 {
+		target-path = "/chosen";
+		__overlay__  {
+			bootargs = "clk_ignore_unused";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-fkms-v3d-pi4-overlay.dts b/arch/arm/boot/dts/overlays/vc4-fkms-v3d-pi4-overlay.dts
new file mode 100644
index 000000000000..39e78280d475
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-fkms-v3d-pi4-overlay.dts
@@ -0,0 +1,47 @@
+/*
+ * vc4-fkms-v3d-overlay.dts
+ */
+
+#include "cma-overlay.dts"
+
+&frag0 {
+	size = <((512-4)*1024*1024)>;
+};
+
+/ {
+	compatible = "brcm,bcm2711";
+
+	fragment@1 {
+		target = <&fb>;
+		__overlay__  {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&firmwarekms>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@3 {
+		target = <&v3d>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@4 {
+		target = <&vc4>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+	fragment@5 {
+		target-path = "/chosen";
+		__overlay__  {
+			bootargs = "clk_ignore_unused";
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dpi-generic-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dpi-generic-overlay.dts
new file mode 100644
index 000000000000..8cf41167d4cc
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-dpi-generic-overlay.dts
@@ -0,0 +1,83 @@
+/*
+ * vc4-kms-dpi-generic-overlay.dts
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include "vc4-kms-dpi.dtsi"
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&panel>;
+		panel_generic: __overlay__  {
+			compatible = "panel-dpi";
+
+			width-mm = <154>;
+			height-mm = <83>;
+			bus-format = <0x1009>;
+
+			timing: panel-timing {
+				clock-frequency = <29500000>;
+				hactive = <800>;
+				hfront-porch = <24>;
+				hsync-len = <72>;
+				hback-porch = <96>;
+				hsync-active = <1>;
+				vactive = <480>;
+				vfront-porch = <3>;
+				vsync-len = <10>;
+				vback-porch = <7>;
+				vsync-active = <1>;
+
+				de-active = <1>;
+				pixelclk-active = <1>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&dpi>;
+		dpi_node_generic: __overlay__  {
+			pinctrl-0 = <&dpi_18bit_gpio0>;
+		};
+	};
+
+	__overrides__ {
+		clock-frequency = <&timing>, "clock-frequency:0";
+		hactive = <&timing>, "hactive:0";
+		hfp = <&timing>, "hfront-porch:0";
+		hsync = <&timing>, "hsync-len:0";
+		hbp = <&timing>, "hback-porch:0";
+		vactive = <&timing>, "vactive:0";
+		vfp = <&timing>, "vfront-porch:0";
+		vsync = <&timing>, "vsync-len:0";
+		vbp = <&timing>, "vback-porch:0";
+		hsync-invert = <&timing>, "hsync-active:0=0";
+		vsync-invert = <&timing>, "vsync-active:0=0";
+		de-invert = <&timing>, "de-active:0=0";
+		pixclk-invert = <&timing>, "pixelclk-active:0=0";
+		interlaced = <&timing>, "interlaced?";
+
+		width-mm = <&panel_generic>, "width-mm:0";
+		height-mm = <&panel_generic>, "height-mm:0";
+
+		rgb565 = <&panel_generic>, "bus-format:0=0x1017",
+			<&dpi_node_generic>, "pinctrl-0:0=",<&dpi_16bit_gpio0>;
+		rgb565-padhi = <&panel_generic>, "bus-format:0=0x1022",
+			<&dpi_node_generic>, "pinctrl-0:0=",<&dpi_16bit_cpadhi_gpio0>;
+		bgr666 = <&panel_generic>, "bus-format:0=0x1023";
+		bgr666-padhi = <&panel_generic>, "bus-format:0=0x1024",
+			<&dpi_node_generic>, "pinctrl-0:0=",<&dpi_18bit_cpadhi_gpio0>;
+		rgb666-padhi = <&panel_generic>, "bus-format:0=0x1015",
+			<&dpi_node_generic>, "pinctrl-0:0=",<&dpi_18bit_cpadhi_gpio0>;
+		bgr888 = <&panel_generic>, "bus-format:0=0x1013",
+			<&dpi_node_generic>, "pinctrl-0:0=",<&dpi_gpio0>;
+		rgb888 = <&panel_generic>, "bus-format:0=0x100a",
+			<&dpi_node_generic>, "pinctrl-0:0=",<&dpi_gpio0>;
+		bus-format = <&panel_generic>, "bus-format:0";
+		rgb-order = <&dpi_node_generic>, "rgb_order";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel.dtsi b/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel.dtsi
new file mode 100644
index 000000000000..585402a3b9b4
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel.dtsi
@@ -0,0 +1,94 @@
+/*
+ * vc4-kms-dpi-hyperpixel4.dtsi
+ * Commmon initialisation for HyperPixel DPI displays
+ */
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/pinctrl/bcm2835.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			spi {
+				compatible = "spi-gpio";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				pinctrl-0 = <&spi_pins>;
+				pinctrl-names = "default";
+
+				sck-gpios = <&gpio 27 GPIO_ACTIVE_HIGH>;
+				mosi-gpios = <&gpio 26 GPIO_ACTIVE_HIGH>;
+				cs-gpios = <&gpio 18 GPIO_ACTIVE_LOW>;
+				num-chipselects = <1>;
+				sck-idle-input;
+
+				panel: display@0 {
+					reg = <0>;
+					/* 100 kHz */
+					spi-max-frequency = <100000>;
+					backlight = <&backlight>;
+					rotation = <0>;
+
+					port {
+						panel_in: endpoint {
+							remote-endpoint = <&dpi_out>;
+						};
+					};
+				};
+			};
+
+			backlight: backlight {
+				compatible = "gpio-backlight";
+				gpios = <&gpio 19 0>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&dpi>;
+		__overlay__  {
+			status = "okay";
+
+			pinctrl-names = "default";
+			pinctrl-0 = <&dpi_18bit_cpadhi_gpio0>;
+
+			port {
+				dpi_out: endpoint {
+					remote-endpoint = <&panel_in>;
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&gpio>;
+		__overlay__ {
+			spi_pins: hyperpixel4_spi_pins {
+				brcm,pins = <27 18 26>;
+				brcm,pull = <BCM2835_PUD_UP BCM2835_PUD_UP BCM2835_PUD_OFF>;
+				brcm,function = <0>;
+			};
+		};
+	};
+
+	fragment@3 {
+		target-path = "/";
+		__overlay__ {
+			i2c_gpio: i2c@0 {
+				compatible = "i2c-gpio";
+				gpios = <&gpio 10 0 /* sda */
+					 &gpio 11 0>; /* scl */
+				i2c-gpio,delay-us = <4>;        /* ~100 kHz */
+				#address-cells = <1>;
+				#size-cells = <0>;
+			};
+		};
+	};
+
+	__overrides__ {
+		rotate = <&panel>, "rotation:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel2r-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel2r-overlay.dts
new file mode 100644
index 000000000000..4cd9d6a55c48
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel2r-overlay.dts
@@ -0,0 +1,114 @@
+/*
+ * vc4-kms-dpi-hyperpixel2r-overlay.dts
+ */
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/pinctrl/bcm2835.h>
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			spi {
+				compatible = "spi-gpio";
+				#address-cells = <1>;
+				#size-cells = <0>;
+				pinctrl-0 = <&spi_pins>;
+				pinctrl-names = "default";
+
+				sck-gpios = <&gpio 11 GPIO_ACTIVE_HIGH>;
+				mosi-gpios = <&gpio 10 GPIO_ACTIVE_HIGH>;
+				cs-gpios = <&gpio 18 GPIO_ACTIVE_LOW>;
+				num-chipselects = <1>;
+
+				panel: display@0 {
+					compatible = "pimoroni,hyperpixel2round";
+					reg = <0>;
+					/* 100 kHz */
+					spi-max-frequency = <100000>;
+					backlight = <&backlight>;
+					rotation = <0>;
+
+					port {
+						panel_in: endpoint {
+							remote-endpoint = <&dpi_out>;
+						};
+					};
+				};
+			};
+
+			backlight: backlight {
+				compatible = "gpio-backlight";
+				gpios = <&gpio 19 0>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&dpi>;
+		__overlay__  {
+			status = "okay";
+
+			pinctrl-names = "default";
+			pinctrl-0 = <&dpi_18bit_cpadhi_gpio0>;
+
+			port {
+				dpi_out: endpoint {
+					remote-endpoint = <&panel_in>;
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&gpio>;
+		__overlay__ {
+			spi_pins: hyperpixel4_spi_pins {
+				brcm,pins = <27 18 26>;
+				brcm,pull = <BCM2835_PUD_UP BCM2835_PUD_UP BCM2835_PUD_OFF>;
+				brcm,function = <0>;
+			};
+		};
+	};
+
+	fragment@3 {
+		target-path = "/";
+		__overlay__ {
+			i2c_gpio: i2c@0 {
+				compatible = "i2c-gpio";
+				status = "disabled";
+
+				gpios = <&gpio 10 GPIO_ACTIVE_HIGH /* sda */
+					 &gpio 11 GPIO_ACTIVE_HIGH>; /* scl */
+				i2c-gpio,delay-us = <4>;        /* ~100 kHz */
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				polytouch: edt-ft5x06@15 {
+					#address-cells = <1>;
+					#size-cells = <0>;
+					compatible = "edt,edt-ft5406";
+					reg = <0x15>;
+					interrupt-parent = <&gpio>;
+					interrupts = <27 0x02>;
+					touchscreen-size-x = <240>;
+					touchscreen-size-y = <240>;
+				};
+			};
+		};
+	};
+
+	__overrides__ {
+		disable-touch = <0>,"-3";
+		touchscreen-inverted-x = <&polytouch>,"touchscreen-inverted-x?";
+		touchscreen-inverted-y = <&polytouch>,"touchscreen-inverted-y!";
+		touchscreen-swapped-x-y = <&polytouch>,"touchscreen-swapped-x-y!";
+		rotate = <&panel>, "rotation:0";
+	};
+
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel4-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel4-overlay.dts
new file mode 100644
index 000000000000..eafc25ad79ff
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel4-overlay.dts
@@ -0,0 +1,57 @@
+/*
+ * vc4-kms-dpi-hyperpixel4sq-overlay.dts
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include "vc4-kms-dpi-hyperpixel.dtsi"
+
+&panel {
+	compatible = "pimoroni,hyperpixel4";
+};
+
+/ {
+	fragment@11 {
+		target = <&i2c_gpio>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+			ft6236_14: ft6236@14 {
+				compatible = "goodix,gt911";
+				reg = <0x14>;
+				interrupt-parent = <&gpio>;
+				interrupts = <27 2>;
+				touchscreen-size-x = <480>;
+				touchscreen-size-y = <800>;
+				touchscreen-x-mm = <51>;
+				touchscreen-y-mm = <85>;
+				touchscreen-inverted-y;
+				touchscreen-swapped-x-y;
+			};
+			ft6236_5d: ft6236@5d {
+				compatible = "goodix,gt911";
+				reg = <0x5d>;
+				interrupt-parent = <&gpio>;
+				interrupts = <27 2>;
+				touchscreen-size-x = <480>;
+				touchscreen-size-y = <800>;
+				touchscreen-x-mm = <51>;
+				touchscreen-y-mm = <85>;
+				touchscreen-inverted-y;
+				touchscreen-swapped-x-y;
+			};
+		};
+	};
+
+	__overrides__ {
+		disable-touch = <0>,"-3-11";
+		touchscreen-inverted-x = <&ft6236_14>,"touchscreen-inverted-x?",
+					 <&ft6236_5d>,"touchscreen-inverted-x?";
+		touchscreen-inverted-y = <&ft6236_14>,"touchscreen-inverted-y!",
+					 <&ft6236_5d>,"touchscreen-inverted-y!";
+		touchscreen-swapped-x-y = <&ft6236_14>,"touchscreen-swapped-x-y!",
+					  <&ft6236_5d>,"touchscreen-swapped-x-y!";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel4sq-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel4sq-overlay.dts
new file mode 100644
index 000000000000..700046348ecf
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-dpi-hyperpixel4sq-overlay.dts
@@ -0,0 +1,36 @@
+/*
+ * vc4-kms-dpi-hyperpixel4-overlay.dts
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include "vc4-kms-dpi-hyperpixel.dtsi"
+
+&panel {
+	compatible = "pimoroni,hyperpixel4square";
+};
+
+/ {
+	fragment@11 {
+		target = <&i2c_gpio>;
+		__overlay__ {
+			polytouch: edt-ft5x06@48 {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				compatible = "edt,edt-ft5406";
+				reg = <0x48>;
+				interrupt-parent = <&gpio>;
+				interrupts = <27 0x02>;
+				touchscreen-size-x = <720>;
+				touchscreen-size-y = <720>;
+			};
+		};
+	};
+	__overrides__ {
+		disable-touch = <0>,"-3-11";
+		touchscreen-inverted-x = <&polytouch>,"touchscreen-inverted-x?";
+		touchscreen-inverted-y = <&polytouch>,"touchscreen-inverted-y!";
+		touchscreen-swapped-x-y = <&polytouch>,"touchscreen-swapped-x-y!";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dpi-panel-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dpi-panel-overlay.dts
new file mode 100644
index 000000000000..a0fe8f810e01
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-dpi-panel-overlay.dts
@@ -0,0 +1,73 @@
+/*
+ * vc4-kms-dpi-panel-overlay.dts
+ * Support for any predefined DPI panel.
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include "vc4-kms-dpi.dtsi"
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&panel>;
+		__dormant__  {
+			compatible = "innolux,at056tn53v1", "simple-panel";
+		};
+	};
+	fragment@1 {
+		target = <&panel>;
+		__dormant__  {
+			compatible = "ontat,yx700wv03", "simple-panel";
+		};
+	};
+	fragment@2 {
+		target = <&panel>;
+		__dormant__  {
+			compatible = "geekworm,mzp280", "simple-panel";
+		};
+	};
+
+	fragment@90 {
+		target = <&dpi>;
+		__dormant__  {
+			pinctrl-0 = <&dpi_18bit_cpadhi_gpio0>;
+		};
+	};
+	fragment@91 {
+		target = <&dpi>;
+		__dormant__  {
+			pinctrl-0 = <&dpi_18bit_gpio0>;
+		};
+	};
+#if 0
+	fragment@92 {
+		target = <&dpi>;
+		__dormant__  {
+			pinctrl-0 = <&dpi_gpio0>;
+		};
+	};
+#endif
+	fragment@93 {
+		target = <&dpi>;
+		__dormant__  {
+			pinctrl-0 = <&dpi_16bit_cpadhi_gpio0>;
+		};
+	};
+#if 0
+	fragment@94 {
+		target = <&dpi>;
+		__dormant__  {
+			pinctrl-0 = <&dpi_16bit_gpio0>;
+		};
+	};
+#endif
+
+	__overrides__ {
+		at056tn53v1 = <0>, "+0+90";
+		kippah-7inch = <0>, "+1+91";
+		mzp280 = <0>, "+2+93";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dpi.dtsi b/arch/arm/boot/dts/overlays/vc4-kms-dpi.dtsi
new file mode 100644
index 000000000000..67c884de2a8d
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-dpi.dtsi
@@ -0,0 +1,111 @@
+/*
+ * vc4-kms-dpi.dtsi
+ */
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/pinctrl/bcm2835.h>
+
+/ {
+	fragment@100 {
+		target-path = "/";
+		__overlay__ {
+			panel: panel {
+				rotation = <0>;
+				port {
+					panel_in: endpoint {
+						remote-endpoint = <&dpi_out>;
+					};
+				};
+			};
+		};
+	};
+
+	fragment@101 {
+		target = <&dpi>;
+		dpi_node: __overlay__  {
+			status = "okay";
+
+			pinctrl-names = "default";
+
+			port {
+				dpi_out: endpoint {
+					remote-endpoint = <&panel_in>;
+				};
+			};
+		};
+	};
+
+	fragment@102 {
+		target = <&panel>;
+		__dormant__  {
+			backlight = <&backlight>;
+		};
+	};
+
+	fragment@103 {
+		target-path = "/";
+		__dormant__  {
+			backlight: backlight {
+				compatible = "gpio-backlight";
+				gpios = <&gpio 255 GPIO_ACTIVE_HIGH>;
+			};
+		};
+	};
+
+	fragment@104 {
+		target = <&panel>;
+		__dormant__  {
+			backlight = <&backlight_pwm>;
+		};
+	};
+
+	fragment@105 {
+		target-path = "/";
+		__dormant__  {
+			backlight_pwm: backlight_pwm {
+				compatible = "pwm-backlight";
+				brightness-levels = <0 6 8 12 16 24 32 40 48 64 96 128 160 192 224 255>;
+				default-brightness-level = <16>;
+				pwms = <&pwm 0 200000 0>;
+			};
+		};
+	};
+
+	fragment@106 {
+		target = <&pwm>;
+		__dormant__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&pwm_pins>;
+			assigned-clock-rates = <1000000>;
+			status = "okay";
+		};
+	};
+
+	fragment@107 {
+		target = <&gpio>;
+		__dormant__ {
+			pwm_pins: pwm_pins {
+				brcm,pins = <18>;
+				brcm,function = <2>; /* Alt5 */
+			};
+		};
+	};
+
+	fragment@108 {
+		target = <&chosen>;
+		__dormant__  {
+			bootargs = "snd_bcm2835.enable_headphones=0";
+		};
+	};
+
+	__overrides__ {
+		backlight-gpio = <0>, "+102+103",
+			<&backlight>, "gpios:4";
+		backlight-pwm = <0>, "+104+105+106+107+108";
+		backlight-pwm-chan = <&backlight_pwm>, "pwms:4";
+		backlight-pwm-gpio = <&pwm_pins>, "brcm,pins:0";
+		backlight-pwm-func = <&pwm_pins>, "brcm,function:0";
+		backlight-def-brightness = <&backlight_pwm>, "default-brightness-level:0";
+		rotate = <&panel>, "rotation:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dsi-7inch-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dsi-7inch-overlay.dts
new file mode 100644
index 000000000000..8d58d5e6067f
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-7inch-overlay.dts
@@ -0,0 +1,121 @@
+ /*
+ * Device Tree overlay for RaspberryPi 7" Touchscreen panel
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include "edt-ft5406.dtsi"
+
+&ft5406 {
+	vcc-supply = <&reg_display>;
+	reset-gpio = <&reg_display 1 1>;
+};
+
+/ {
+	/* No compatible as it will have come from edt-ft5406.dtsi */
+
+	dsi_frag: fragment@0 {
+		target = <&dsi1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+			port {
+				dsi_out: endpoint {
+					remote-endpoint = <&bridge_in>;
+				};
+			};
+			bridge@0 {
+				reg = <0>;
+				compatible = "toshiba,tc358762";
+				vddc-supply = <&reg_bridge>;
+				ports {
+					#address-cells = <1>;
+					#size-cells = <0>;
+
+					port@0 {
+						reg = <0>;
+						bridge_in: endpoint {
+							remote-endpoint = <&dsi_out>;
+						};
+					};
+
+					port@1 {
+						reg = <1>;
+						bridge_out: endpoint {
+							remote-endpoint = <&panel_in>;
+						};
+					};
+				};
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			panel_disp: panel_disp@1 {
+				reg = <1>;
+				compatible = "raspberrypi,7inch-dsi", "simple-panel";
+				backlight = <&reg_display>;
+				power-supply = <&reg_display>;
+
+				port {
+					panel_in: endpoint {
+						remote-endpoint = <&bridge_out>;
+					};
+				};
+			};
+
+			reg_bridge: reg_bridge@1 {
+				reg = <1>;
+				compatible = "regulator-fixed";
+				regulator-name = "bridge_reg";
+				gpio = <&reg_display 0 0>;
+				vin-supply = <&reg_display>;
+				enable-active-high;
+			};
+		};
+	};
+
+	i2c_frag: fragment@2 {
+		target = <&i2c_csi_dsi>;
+		i2cbus: __overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			reg_display: reg_display@45 {
+				compatible = "raspberrypi,7inch-touchscreen-panel-regulator";
+				reg = <0x45>;
+				gpio-controller;
+				#gpio-cells = <2>;
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@4 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		dsi0 = <&dsi_frag>, "target:0=",<&dsi0>,
+		       <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&panel_disp>, "reg:0=0",
+		       <&reg_bridge>, "reg:0=0",
+		       <&reg_bridge>, "regulator-name=bridge_reg_0";
+		disable_touch = <&ft5406>, "status=disabled";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dsi-generic-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dsi-generic-overlay.dts
new file mode 100644
index 000000000000..cf4ca5b6c75f
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-generic-overlay.dts
@@ -0,0 +1,106 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	dsi_frag: fragment@0 {
+		target = <&dsi1>;
+		__overlay__{
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			port {
+				dsi_out:endpoint {
+					remote-endpoint = <&panel_dsi_port>;
+				};
+			};
+			panel: panel-dsi-generic@0 {
+				// See panel-dsi.yaml binding
+				// Using dummy name for panel model
+				compatible = "Generic,panel-dsi","panel-dsi";
+				reg = <0>;
+				power-supply = <0>;
+				backlight = <0>;
+				dsi-color-format = "RGB888";
+				mode = "MODE_VIDEO";
+				width-mm = <0>;
+				height-mm = <0>;
+
+				port {
+					panel_dsi_port: endpoint {
+						data-lanes = <1>;
+						remote-endpoint = <&dsi_out>;
+					};
+				};
+
+				timing: panel-timing {
+					clock-frequency = <30000000>;
+					hactive = <840>;
+					vactive = <480>;
+					hback-porch = <44>;
+					hfront-porch = <46>;
+					hsync-len = <2>;
+					vback-porch = <18>;
+					vfront-porch = <16>;
+					vsync-len = <2>;
+				};
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&panel_dsi_port>;
+		__dormant__ {
+			data-lanes = <1>;
+		};
+	};
+
+	fragment@2 {
+		target = <&panel_dsi_port>;
+		__dormant__ {
+			data-lanes = <1 2>;
+		};
+	};
+
+	fragment@3 {
+		target = <&panel_dsi_port>;
+		__dormant__ {
+			data-lanes = <1 2 3>;
+		};
+	};
+
+	fragment@4 {
+		target = <&panel_dsi_port>;
+		__dormant__ {
+			data-lanes = <1 2 3 4>;
+		};
+	};
+
+	__overrides__ {
+		dsi0 = <&dsi_frag>, "target:0=",<&dsi0>;
+
+		clock-frequency = <&timing>, "clock-frequency:0";
+		hactive = <&timing>, "hactive:0";
+		hfp = <&timing>, "hfront-porch:0";
+		hsync = <&timing>, "hsync-len:0";
+		hbp = <&timing>, "hback-porch:0";
+		vactive = <&timing>, "vactive:0";
+		vfp = <&timing>, "vfront-porch:0";
+		vsync = <&timing>, "vsync-len:0";
+		vbp = <&timing>, "vback-porch:0";
+
+		width-mm = <&panel>, "width-mm:0";
+		height-mm = <&panel>, "height-mm:0";
+
+		rgb565 = <&panel>, "dsi-color-format=RGB565";
+		rgb666p = <&panel>, "dsi-color-format=RGB666_PACKED";
+		rgb666 = <&panel>, "dsi-color-format=RGB666";
+		rgb888 = <&panel>, "dsi-color-format=RGB888";
+		one-lane = <0>,"+1";
+		two-lane = <0>,"+2";
+		three-lane = <0>,"+3";
+		four-lane = <0>,"+4";
+	};
+
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dsi-ili79600-10-1inch-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dsi-ili79600-10-1inch-overlay.dts
new file mode 100644
index 000000000000..7aec9e59fcb8
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-ili79600-10-1inch-overlay.dts
@@ -0,0 +1,126 @@
+/*
+ * vc4-kms-dsi-ili79600-10-1inch-overlay.dts
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	i2c_frag: fragment@0 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			display_mcu: display_mcu@45
+			{
+				compatible = "raspberrypi,v2-touchscreen-panel-regulator";
+				reg = <0x45>;
+				gpio-controller;
+				#gpio-cells = <2>;
+			};
+
+			touch: ts@41 {
+				compatible = "ilitek,ili79600a";
+				reg = <0x41>;
+
+				reset-gpio = <&display_mcu 1 GPIO_ACTIVE_LOW>;
+
+				touchscreen-size-x = < 1200 >;
+				touchscreen-size-y = < 1920 >;
+				vcc-supply = <&display_reg>;
+
+				panel = <&dsi_panel>;
+			};
+		};
+	};
+
+	dsi_frag: fragment@1 {
+		target = <&dsi1>;
+		__overlay__  {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			port {
+				dsi_out: endpoint {
+					remote-endpoint = <&panel_in>;
+				};
+			};
+
+			dsi_panel: dsi_panel@0 {
+				reg = <0>;
+				compatible = "raspberrypi,dsi-10-1inch";
+				power-supply = <&display_reg>;
+				backlight = <&display_mcu>;
+
+				port {
+					panel_in: endpoint {
+						remote-endpoint = <&dsi_out>;
+					};
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@3 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@4 {
+		target-path = "/";
+		__overlay__ {
+			display_reg: display_reg@1 {
+				reg = <1>;
+				compatible = "regulator-fixed";
+				regulator-name = "display_reg_1";
+				gpio = <&display_mcu 0 GPIO_ACTIVE_HIGH>;
+				startup-delay-us = <50000>;
+				enable-active-high;
+			};
+		};
+	};
+
+	fragment@10 {
+		target = <&touch>;
+		__dormant__ {
+			touchscreen-inverted-x;
+		};
+	};
+
+	fragment@11 {
+		target = <&touch>;
+		__dormant__ {
+			touchscreen-inverted-y;
+		};
+	};
+
+	__overrides__ {
+		sizex = <&touch>,"touchscreen-size-x:0";
+		sizey = <&touch>,"touchscreen-size-y:0";
+		invx = <0>, "+10";
+		invy = <0>, "+11";
+		swapxy = <&touch>,"touchscreen-swapped-x-y?";
+		disable_touch = <&touch>, "status=disabled";
+		rotation = <&dsi_panel>, "rotation:0";
+		dsi0 = <&dsi_frag>, "target:0=",<&dsi0>,
+		       <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&display_reg>, "reg:0=0",
+		       <&display_reg>, "regulator-name=display_reg_0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dsi-ili9881-5inch-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dsi-ili9881-5inch-overlay.dts
new file mode 100644
index 000000000000..830e28703e57
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-ili9881-5inch-overlay.dts
@@ -0,0 +1,14 @@
+/*
+ * vc4-kms-dsi-ili9881-5inch-overlay.dts
+ */
+
+#include "vc4-kms-dsi-ili9881-7inch-overlay.dts"
+
+&gt911 {
+	touchscreen-x-mm = <62>;
+	touchscreen-y-mm = <110>;
+};
+
+&dsi_panel {
+	compatible = "raspberrypi,dsi-5inch";
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dsi-ili9881-7inch-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dsi-ili9881-7inch-overlay.dts
new file mode 100644
index 000000000000..92d28a34b344
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-ili9881-7inch-overlay.dts
@@ -0,0 +1,123 @@
+/*
+ * vc4-kms-dsi-ili9881-7inch-overlay.dts
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	i2c_frag: fragment@0 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			display_mcu: display_mcu@45
+			{
+				compatible = "raspberrypi,v2-touchscreen-panel-regulator";
+				reg = <0x45>;
+				gpio-controller;
+				#gpio-cells = <2>;
+			};
+
+			gt911: gt911@5d {
+				compatible = "goodix,gt911";
+				reg = <0x5d>;
+				AVDD28-supply = <&touch_reg>;
+				touchscreen-size-x = <720>;
+				touchscreen-size-y = <1280>;
+				touchscreen-x-mm = <90>;
+				touchscreen-y-mm = <151>;
+			};
+		};
+	};
+
+	dsi_frag: fragment@1 {
+		target = <&dsi1>;
+		__overlay__  {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			port {
+				dsi_out: endpoint {
+					remote-endpoint = <&panel_in>;
+				};
+			};
+
+			dsi_panel: dsi_panel@0 {
+				reg = <0>;
+				compatible = "raspberrypi,dsi-7inch";
+				reset-gpio = <&display_mcu 0 GPIO_ACTIVE_LOW>;
+				backlight = <&display_mcu>;
+
+				port {
+					panel_in: endpoint {
+						remote-endpoint = <&dsi_out>;
+					};
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@3 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@4 {
+		target-path = "/";
+		__overlay__ {
+			touch_reg: touch_reg@1 {
+				reg = <1>;
+				compatible = "regulator-fixed";
+				regulator-name = "touch_reg_1";
+				gpio = <&display_mcu 1 GPIO_ACTIVE_HIGH>;
+				startup-delay-us = <50000>;
+				enable-active-high;
+			};
+		};
+	};
+
+	fragment@10 {
+		target = <&gt911>;
+		__dormant__ {
+			touchscreen-inverted-x;
+		};
+	};
+
+	fragment@11 {
+		target = <&gt911>;
+		__dormant__ {
+			touchscreen-inverted-y;
+		};
+	};
+
+	__overrides__ {
+		dsi0 = <&dsi_frag>, "target:0=",<&dsi0>,
+		       <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&touch_reg>, "reg:0=0",
+		       <&touch_reg>, "regulator-name=touch_reg_0";
+		sizex = <&gt911>,"touchscreen-size-x:0";
+		sizey = <&gt911>,"touchscreen-size-y:0";
+		invx = <0>, "+10";
+		invy = <0>, "+11";
+		swapxy = <&gt911>,"touchscreen-swapped-x-y?";
+		disable_touch = <&gt911>, "status=disabled";
+		rotation = <&dsi_panel>, "rotation:0";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dsi-lt070me05000-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dsi-lt070me05000-overlay.dts
new file mode 100644
index 000000000000..9cc6f1ae628f
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-lt070me05000-overlay.dts
@@ -0,0 +1,71 @@
+/*
+ * Device Tree overlay to connect a JDI LT070ME05000 DSI panel to DSI1.
+ * This uses 4 DSI data lanes, so can only be used with a Compute Module.
+ *
+ * Credit to forum user gizmomouse on
+ * https://www.raspberrypi.org/forums/viewtopic.php?f=98&t=253912 and
+ * Andrey Vostrukhin of Harlab for the overlay.
+ *
+ * Refer to https://github.com/harlab/CM4_LCD_LT070ME05000 for schematics and
+ * other documentation.
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&dsi1>;
+		__overlay__{
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			port {
+				dsi_out_port:endpoint {
+					remote-endpoint = <&panel_dsi_port>;
+				};
+			};
+
+			lt070me05000:lt070me05000@0 {
+				compatible    = "jdi,lt070me05000";
+				status        = "okay";
+				reg           = <0>;
+				pinctrl-0     = <&lt070me05000_pins>;
+				pinctrl-names = "default";
+				reset-gpios   = <&gpio 17 1>;   // LCD RST
+				enable-gpios  = <&gpio 4 0>;    // LCD Enable
+				dcdc-en-gpios = <&gpio 5 0>;    // LCD DC-DC Enable
+				port {
+					panel_dsi_port: endpoint {
+						remote-endpoint = <&dsi_out_port>;
+					};
+				};
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			lt070me05000_pins: lt070me05000_pins {
+				brcm,pins = <4 5 17>;
+				brcm,function = <1 1 1>; // out
+				brcm,pull = <0 0 0>; // off
+			};
+		};
+
+	};
+
+	__overrides__ {
+		reset = <&lt070me05000_pins>,"brcm,pins:8",
+			<&lt070me05000>,"reset-gpios:4";
+
+		enable = <&lt070me05000_pins>,"brcm,pins:0",
+			<&lt070me05000>,"enable-gpios:4";
+
+		dcdc-en = <&lt070me05000_pins>,"brcm,pins:4",
+			<&lt070me05000>,"dcdc-en-gpios:4";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dsi-lt070me05000-v2-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dsi-lt070me05000-v2-overlay.dts
new file mode 100644
index 000000000000..5dcd0f2243e2
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-lt070me05000-v2-overlay.dts
@@ -0,0 +1,64 @@
+/*
+ * Device Tree overlay to connect a JDI LT070ME05000 DSI panel to DSI1.
+ * This uses 4 DSI data lanes, so can only be used with a Compute Module.
+ *
+ * The overlay is for V2 of Harlab's interface board that uses a PCA9536 to
+ * handle the panel's control GPIOs instead of wiring it back to Pi GPIOs.
+ *
+ * Credit to Andrey Vostrukhin of Harlab for the overlay.
+ *
+ * Refer to https://github.com/harlab/CM4_LCD_LT070ME05000 for schematics and
+ * other documentation.
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			pca: pca@41 {
+				compatible = "nxp,pca9536";
+				reg = <0x41>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&dsi1>;
+		__overlay__{
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			port {
+				dsi_out_port:endpoint {
+					remote-endpoint = <&panel_dsi_port>;
+				};
+			};
+
+			lt070me05000:lt070me05000@0 {
+				compatible    = "jdi,lt070me05000";
+				status        = "okay";
+				reg           = <0>;
+				reset-gpios   = <&pca 0 1>;
+				enable-gpios  = <&pca 2 0>;
+				dcdc-en-gpios = <&pca 1 0>;
+				port {
+					panel_dsi_port: endpoint {
+						remote-endpoint = <&dsi_out_port>;
+					};
+				};
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-800x480-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-800x480-overlay.dts
new file mode 100644
index 000000000000..7a7058d14d6c
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-800x480-overlay.dts
@@ -0,0 +1,116 @@
+/*
+ * Device Tree overlay for Waveshare 4.3" 800x480 panel.
+ * It tries to look like a Pi 7" panel, but fails with some of the timing
+ * options.
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include "edt-ft5406.dtsi"
+
+&ft5406 {
+	vcc-supply = <&reg_display>;
+	reset-gpio = <&reg_display 1 1>;
+};
+
+/ {
+	/* No compatible as it will have come from edt-ft5406.dtsi */
+
+	dsi_frag: fragment@0 {
+		target = <&dsi1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+			port {
+				dsi_out: endpoint {
+					remote-endpoint = <&panel_dsi_port>;
+				};
+			};
+
+			panel: panel-dsi-generic@0 {
+				// See panel-dsi.yaml binding
+				compatible = "waveshare,4-3-inch-dsi","panel-dsi";
+				reg = <0>;
+				power-supply = <&reg_display>;
+				backlight = <&reg_display>;
+				dsi-color-format = "RGB888";
+				mode = "MODE_VIDEO";
+				width-mm = <0>;
+				height-mm = <0>;
+
+				port {
+					panel_dsi_port: endpoint {
+						data-lanes = <1>;
+						remote-endpoint = <&dsi_out>;
+					};
+				};
+
+				timing: panel-timing {
+					clock-frequency = <27777000>;
+					hactive = <800>;
+					vactive = <480>;
+					hfront-porch = <59>;
+					hsync-len = <2>;
+					hback-porch = <45>;
+					vfront-porch = <7>;
+					vsync-len = <2>;
+					vback-porch = <22>;
+				};
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			reg_bridge: reg_bridge@1 {
+				reg = <1>;
+				compatible = "regulator-fixed";
+				regulator-name = "bridge_reg";
+				gpio = <&reg_display 0 0>;
+				vin-supply = <&reg_display>;
+				enable-active-high;
+			};
+		};
+	};
+
+	i2c_frag: fragment@2 {
+		target = <&i2c_csi_dsi>;
+		i2cbus: __overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			reg_display: reg_display@45 {
+				compatible = "raspberrypi,7inch-touchscreen-panel-regulator";
+				reg = <0x45>;
+				gpio-controller;
+				#gpio-cells = <2>;
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@4 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		dsi0 = <&dsi_frag>, "target:0=",<&dsi0>,
+		       <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>,
+		       <&reg_bridge>, "reg:0=0",
+		       <&reg_bridge>, "regulator-name=bridge_reg_0";
+		disable_touch = <&ft5406>, "status=disabled";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-panel-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-panel-overlay.dts
new file mode 100644
index 000000000000..c9faf02c907f
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-panel-overlay.dts
@@ -0,0 +1,146 @@
+/*
+ * Device Tree overlay for Waveshare DSI Touchscreens
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	dsi_frag: fragment@0 {
+		target = <&dsi1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+			port {
+				dsi_out: endpoint {
+					remote-endpoint = <&panel_in>;
+				};
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+		};
+	};
+
+	i2c_frag: fragment@2 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			panel: panel_disp1@45 {
+				reg = <0x45>;
+				compatible = "waveshare,10.1inch-panel";
+
+				port {
+					panel_in: endpoint {
+						remote-endpoint = <&dsi_out>;
+					};
+				};
+			};
+
+			touch: goodix@14 {
+				reg = <0x14>;
+				compatible = "goodix,gt911";
+			};
+
+			touch2: ilitek@41 {
+				compatible = "ilitek,ili251x";
+				reg = <0x41>;
+				status = "disabled";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@4 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@5 {
+		target = <&i2c_arm>;
+		__dormant__ {
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		2_8_inch = <&panel>, "compatible=waveshare,2.8inch-panel",
+				   <&touch>, "touchscreen-size-x:0=480",
+				   <&touch>, "touchscreen-size-y:0=640",
+				   <&touch>, "touchscreen-inverted-y?",
+				   <&touch>, "touchscreen-swapped-x-y?";
+		3_4_inch = <&panel>, "compatible=waveshare,3.4inch-panel",
+				   <&touch>, "touchscreen-size-x:0=800",
+				   <&touch>, "touchscreen-size-y:0=800";
+		4_0_inch = <&panel>, "compatible=waveshare,4.0inch-panel",
+				   <&touch>, "touchscreen-size-x:0=800",
+				   <&touch>, "touchscreen-size-y:0=480",
+				   <&touch>, "touchscreen-inverted-x?",
+				   <&touch>, "touchscreen-swapped-x-y?";
+		7_0_inchC = <&panel>, "compatible=waveshare,7.0inch-c-panel",
+				   <&touch>, "touchscreen-size-x:0=800",
+				   <&touch>, "touchscreen-size-y:0=480";
+		7_9_inch = <&panel>, "compatible=waveshare,7.9inch-panel",
+				   <&touch>, "touchscreen-size-x:0=4096",
+				   <&touch>, "touchscreen-size-y:0=4096",
+				   <&touch>, "touchscreen-inverted-x?",
+				   <&touch>, "touchscreen-swapped-x-y?";
+		8_0_inch = <&panel>, "compatible=waveshare,8.0inch-panel",
+				   <&touch>, "touchscreen-size-x:0=800",
+				   <&touch>, "touchscreen-size-y:0=1280",
+				   <&touch>, "touchscreen-inverted-x?",
+				   <&touch>, "touchscreen-swapped-x-y?";
+		10_1_inch = <&panel>, "compatible=waveshare,10.1inch-panel",
+				   <&touch>, "touchscreen-size-x:0=800",
+				   <&touch>, "touchscreen-size-y:0=1280",
+				   <&touch>, "touchscreen-inverted-x?",
+				   <&touch>, "touchscreen-swapped-x-y?";
+		11_9_inch = <&panel>, "compatible=waveshare,11.9inch-panel",
+				   <&touch>, "touchscreen-inverted-x?",
+				   <&touch>, "touchscreen-swapped-x-y?";
+		4_0_inchC = <&panel>, "compatible=waveshare,4inch-panel",
+				   <&touch>, "touchscreen-size-x:0=720",
+				   <&touch>, "touchscreen-size-y:0=720";
+		5_0_inch = <&panel>, "compatible=waveshare,5.0inch-panel",
+				   <&touch>, "touchscreen-inverted-x?",
+				   <&touch>, "touchscreen-inverted-y?";
+		6_25_inch = <&panel>, "compatible=waveshare,6.25inch-panel",
+				   <&touch>, "touchscreen-inverted-x?",
+				   <&touch>, "touchscreen-inverted-y?";
+		8_8_inch = <&panel>, "compatible=waveshare,8.8inch-panel";
+		13_3_inch_4lane = <&panel>, "compatible=waveshare,13.3inch-4lane-panel",
+				  <&touch2>, "status=okay";
+		13_3_inch_2lane = <&panel>, "compatible=waveshare,13.3inch-2lane-panel",
+				  <&touch2>, "status=okay";
+		7_0_inchH = <&panel>, "compatible=waveshare,7.0inch-h-panel",
+				  <&touch>, "touchscreen-swapped-x-y?",
+				  <&touch>, "touchscreen-inverted-x?";
+		i2c1 = <&i2c_frag>, "target:0=",<&i2c1>,
+		       <0>, "-3-4+5";
+		disable_touch = <&touch>, "status=disabled";
+		rotation = <&panel>, "rotation:0";
+		invx = <&touch>,"touchscreen-inverted-x?";
+		invy = <&touch>,"touchscreen-inverted-y?";
+		swapxy = <&touch>,"touchscreen-swapped-x-y?";
+		dsi0 = <&dsi_frag>, "target:0=",<&dsi0>,
+		       <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>;
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-panel-v2-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-panel-v2-overlay.dts
new file mode 100644
index 000000000000..ddb085d03cfa
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-dsi-waveshare-panel-v2-overlay.dts
@@ -0,0 +1,111 @@
+/*
+ * vc4-kms-dsi-waveshare-panel-v2-overlay.dts
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	i2c_frag: fragment@0 {
+		target = <&i2c_csi_dsi>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			display_mcu: display_mcu@45 {
+				compatible = "waveshare,touchscreen-panel-regulator";
+				reg = <0x45>;
+				gpio-controller;
+				#gpio-cells = <2>;
+				enable-gpio = <&display_mcu 2 GPIO_ACTIVE_HIGH>;
+			};
+
+			touch: goodix@5d {
+				compatible = "goodix,gt9271";
+				reg = <0x5d>;
+				reset-gpio = <&display_mcu 9 GPIO_ACTIVE_HIGH>;
+			};
+		};
+	};
+
+	dsi_frag: fragment@1 {
+		target = <&dsi1>;
+		__overlay__  {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			port {
+				dsi_out: endpoint {
+					remote-endpoint = <&panel_in>;
+				};
+			};
+
+			dsi_panel: dsi_panel@0 {
+				reg = <0>;
+				compatible = "waveshare,10.1-dsi-touch-a";
+				reset-gpio = <&display_mcu 1 GPIO_ACTIVE_HIGH>;
+				iovcc-gpio = <&display_mcu 4 GPIO_ACTIVE_HIGH>;
+				avdd-gpio = <&display_mcu 0 GPIO_ACTIVE_HIGH>;
+				backlight = <&display_mcu>;
+
+				port {
+					panel_in: endpoint {
+						remote-endpoint = <&dsi_out>;
+					};
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&i2c0if>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@3 {
+		target = <&i2c0mux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		3_4_inch_c = <&dsi_panel>, "compatible=waveshare,3.4-dsi-touch-c";
+		4_0_inch_c = <&dsi_panel>, "compatible=waveshare,4.0-dsi-touch-c";
+		5_0_inch_a = <&dsi_panel>, "compatible=waveshare,5.0-dsi-touch-a";
+		5_5_inch_a = <&dsi_panel>, "compatible=waveshare,5.5-dsi-touch-a";
+		7_0_inch_a = <&dsi_panel>, "compatible=waveshare,7.0-dsi-touch-a";
+		7_0_inch_b = <&dsi_panel>, "compatible=waveshare,7.0-dsi-touch-b";
+		7_0_inch_c = <&dsi_panel>, "compatible=waveshare,7.0-dsi-touch-c";
+		8_0_inch_a = <&dsi_panel>, "compatible=waveshare,8.0-dsi-touch-a";
+		8_0_inch_a_4lane = <&dsi_panel>, "compatible=waveshare,8.0-dsi-touch-a-4lane";
+		8_8_inch_a = <&dsi_panel>, "compatible=waveshare,8.8-dsi-touch-a";
+		9_0_inch_b = <&dsi_panel>, "compatible=waveshare,9.0-dsi-touch-b",
+			     <&touch>, "touchscreen-inverted-x";
+		9_0_inch_b_4lane = <&dsi_panel>, "compatible=waveshare,9.0-dsi-touch-b,4lane",
+				   <&touch>, "touchscreen-inverted-x";
+		10_1_inch_a = <&dsi_panel>, "compatible=waveshare,10.1-dsi-touch-a";
+		10_1_inch_a_4lane = <&dsi_panel>, "compatible=waveshare,10.1-dsi-touch-a-4lane";
+		10_1_inch_b = <&dsi_panel>, "compatible=waveshare,10.1-dsi-touch-b",
+			      <&touch>, "touchscreen-inverted-y";
+		10_1_inch_b_4lane = <&dsi_panel>, "compatible=waveshare,10.1-dsi-touch-b,4lane",
+				    <&touch>, "touchscreen-inverted-y";
+		12_3_inch_a_4lane = <&dsi_panel>, "compatible=waveshare,12.3-dsi-touch-a,4lane",
+				    <&touch>, "touchscreen-swapped-x-y",
+				    <&touch>, "touchscreen-inverted-y";
+		disable_touch = <&touch>, "status=disabled";
+		invx = <&touch>,"touchscreen-inverted-x?";
+		invy = <&touch>,"touchscreen-inverted-y?";
+		swapxy = <&touch>,"touchscreen-swapped-x-y?";
+		dsi0 = <&dsi_frag>, "target:0=",<&dsi0>,
+			   <&i2c_frag>, "target:0=",<&i2c_csi_dsi0>;
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-kippah-7inch-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-kippah-7inch-overlay.dts
new file mode 100644
index 000000000000..4c1aa1c70158
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-kippah-7inch-overlay.dts
@@ -0,0 +1,26 @@
+/*
+ * vc4-kms-kippah-7inch-overlay.dts
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include "vc4-kms-dpi.dtsi"
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&panel>;
+		__overlay__  {
+			compatible = "ontat,yx700wv03", "simple-panel";
+		};
+	};
+
+	fragment@1 {
+		target = <&dpi>;
+		__overlay__  {
+			pinctrl-0 = <&dpi_18bit_gpio0>;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-v3d-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-v3d-overlay.dts
new file mode 100644
index 000000000000..f0b531b18dfc
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-v3d-overlay.dts
@@ -0,0 +1,121 @@
+/*
+ * vc4-kms-v3d-overlay.dts
+ */
+
+#include <dt-bindings/clock/bcm2835.h>
+
+#include "cma-overlay.dts"
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@1 {
+		target = <&i2c2>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&fb>;
+		__overlay__  {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&pixelvalve0>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@4 {
+		target = <&pixelvalve1>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@5 {
+		target = <&pixelvalve2>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@6 {
+		target = <&hvs>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@7 {
+		target = <&hdmi>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@8 {
+		target = <&v3d>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@9 {
+		target = <&vc4>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@10 {
+		target = <&clocks>;
+		__overlay__  {
+			claim-clocks = <
+				BCM2835_PLLD_DSI0
+				BCM2835_PLLD_DSI1
+				BCM2835_PLLH_AUX
+				BCM2835_PLLH_PIX
+			>;
+		};
+	};
+
+	fragment@11 {
+		target = <&vec>;
+		__dormant__  {
+			status = "okay";
+		};
+	};
+
+	fragment@12 {
+		target = <&txp>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@13 {
+		target = <&hdmi>;
+		__dormant__  {
+			dmas;
+		};
+	};
+
+	fragment@14 {
+		target = <&chosen>;
+		__overlay__  {
+			bootargs = "snd_bcm2835.enable_hdmi=0";
+		};
+	};
+
+	__overrides__ {
+		audio   = <0>,"!13";
+		noaudio = <0>,"=13";
+		composite = <0>, "=11";
+		nohdmi = <0>, "-1-7";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-v3d-pi4-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-v3d-pi4-overlay.dts
new file mode 100644
index 000000000000..02bbad743d77
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-v3d-pi4-overlay.dts
@@ -0,0 +1,197 @@
+/*
+ * vc4-kms-v3d-pi4-overlay.dts
+ */
+
+#include <dt-bindings/clock/bcm2835.h>
+
+#include "cma-overlay.dts"
+
+&frag0 {
+	size = <((512-4)*1024*1024)>;
+};
+
+/ {
+	compatible = "brcm,bcm2711";
+
+	fragment@1 {
+		target = <&ddc0>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@2 {
+		target = <&ddc1>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@3 {
+		target = <&hdmi0>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@4 {
+		target = <&hdmi1>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@5 {
+		target = <&hvs>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@6 {
+		target = <&pixelvalve0>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@7 {
+		target = <&pixelvalve1>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@8 {
+		target = <&pixelvalve2>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@9 {
+		target = <&pixelvalve3>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@10 {
+		target = <&pixelvalve4>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@11 {
+		target = <&v3d>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@12 {
+		target = <&vc4>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@13 {
+		target = <&txp>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@14 {
+		target = <&fb>;
+		__overlay__  {
+			status = "disabled";
+		};
+	};
+
+	fragment@15 {
+		target = <&firmwarekms>;
+		__overlay__  {
+			status = "disabled";
+		};
+	};
+
+	fragment@16 {
+		target = <&vec>;
+		__overlay__  {
+			status = "disabled";
+		};
+	};
+
+	fragment@17 {
+		target = <&hdmi0>;
+		__dormant__  {
+			dmas;
+		};
+	};
+
+	fragment@18 {
+		target = <&hdmi1>;
+		__dormant__  {
+			dmas;
+		};
+	};
+
+	fragment@19 {
+		target-path = "/chosen";
+		__overlay__  {
+			bootargs = "snd_bcm2835.enable_hdmi=0";
+		};
+	};
+
+	fragment@20 {
+		target = <&dvp>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@21 {
+		target = <&pixelvalve3>;
+		__dormant__  {
+			status = "okay";
+		};
+	};
+
+	fragment@22 {
+		target = <&vec>;
+		__dormant__  {
+			status = "okay";
+		};
+	};
+
+	fragment@23 {
+		target = <&aon_intr>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		audio   = <0>,"!17";
+		audio1   = <0>,"!18";
+		noaudio = <0>,"=17", <0>,"=18";
+		composite = <0>, "!1",
+			    <0>, "!2",
+			    <0>, "!3",
+			    <0>, "!4",
+			    <0>, "!6",
+			    <0>, "!7",
+			    <0>, "!8",
+			    <0>, "!9",
+			    <0>, "!10",
+			    <0>, "!16",
+			    <0>, "=21",
+			    <0>, "=22";
+		nohdmi0 =   <0>, "-1-3-8";
+		nohdmi1 =   <0>, "-2-4-10";
+		nohdmi =    <0>, "-1-2-3-4-8-10";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-v3d-pi5-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-v3d-pi5-overlay.dts
new file mode 100644
index 000000000000..d469e717b570
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-v3d-pi5-overlay.dts
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "cma-overlay.dts"
+
+&frag0 {
+	size = <(64*1024*1024)>;
+};
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@1 {
+		target = <&fb>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&aon_intr>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@3 {
+		target = <&ddc0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@4 {
+		target = <&ddc1>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@5 {
+		target = <&hdmi0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@6 {
+		target = <&hdmi1>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@7 {
+		target = <&hvs>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@8 {
+		target = <&mop>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@9 {
+		target = <&moplet>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@10 {
+		target = <&pixelvalve0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@11 {
+		target = <&pixelvalve1>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@12 {
+		target = <&v3d>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@13 {
+		target = <&vec>;
+		frag13: __overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@14 {
+		target = <&hdmi0>;
+		__dormant__  {
+			dmas;
+		};
+	};
+
+	fragment@15 {
+		target = <&hdmi1>;
+		__dormant__  {
+			dmas;
+		};
+	};
+
+	fragment@16 {
+		target = <&disp_intr>;
+		__overlay__  {
+			status = "okay";
+		};
+	};
+
+	fragment@17 {
+		target = <&vc4>;
+		__overlay__  {
+			status = "okay";
+			/* IOMMU attaches here, where we allocate DMA buffers */
+			iommus = <&iommu4>;
+		};
+	};
+
+	__overrides__ {
+		audio   = <0>,"!14";
+		audio1   = <0>,"!15";
+		noaudio = <0>,"=14", <0>,"=15";
+		composite = <&frag13>, "status";
+		nohdmi0 =   <0>, "-3-5-10";
+		nohdmi1 =   <0>, "-4-6-11";
+		nohdmi =    <0>, "-3-4-5-6-10-11";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vc4-kms-vga666-overlay.dts b/arch/arm/boot/dts/overlays/vc4-kms-vga666-overlay.dts
new file mode 100644
index 000000000000..c3a682d5b7d9
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vc4-kms-vga666-overlay.dts
@@ -0,0 +1,107 @@
+/*
+ * vc4-kms-vga666-overlay.dts
+ * Configures a FenLogic or similar VGA666 DPI adapter when using the
+ * vc4-kms-v3d driver.
+ * If a suitable I2C level shifter is connected to GPIOs 0&1 and the VGA
+ * ID1/SDA (pin 12) and ID3/SCL (pin 15) lines, then there is the option to
+ * enable reading the EDID from the display.
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/pinctrl/bcm2835.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+			vga_connector: vga_connector {
+				compatible = "vga-connector";
+				label = "vga";
+
+				port {
+					vga_con_in: endpoint {
+						remote-endpoint = <&vga666_out>;
+					};
+				};
+			};
+
+			vga_dac {
+				compatible = "dumb-vga-dac";
+
+				ports {
+					#address-cells = <1>;
+					#size-cells = <0>;
+
+					port@0 {
+						reg = <0>;
+
+						vga666_in: endpoint {
+							remote-endpoint = <&dpi_out>;
+						};
+					};
+
+					port@1 {
+						reg = <1>;
+
+						vga666_out: endpoint {
+							remote-endpoint = <&vga_con_in>;
+						};
+					};
+				};
+			};
+
+		};
+	};
+
+	fragment@1 {
+		target = <&dpi>;
+		__overlay__  {
+			status = "okay";
+
+			pinctrl-names = "default";
+			pinctrl-0 = <&dpi_18bit_gpio2>;
+
+			port {
+				dpi_out: endpoint@0 {
+					remote-endpoint = <&vga666_in>;
+				};
+			};
+		};
+	};
+
+	fragment@2 {
+		target = <&vga_connector>;
+		__dormant__  {
+			ddc-i2c-bus = <&i2c_vc>;
+		};
+	};
+
+	fragment@3 {
+		target = <&i2c0if>;
+		__dormant__ {
+			status = "okay";
+		};
+	};
+
+	fragment@4 {
+		target = <&i2c0mux>;
+		__dormant__ {
+			status = "okay";
+		};
+	};
+
+	fragment@5 {
+		target = <&i2c_vc>;
+		__dormant__ {
+			status = "okay";
+		};
+	};
+
+	__overrides__ {
+		ddc = <0>,"=2", <0>,"=3", <0>,"=4", <0>,"=5";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vec-gpio-pi5-overlay.dts b/arch/arm/boot/dts/overlays/vec-gpio-pi5-overlay.dts
new file mode 100644
index 000000000000..6a99f0d0909b
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vec-gpio-pi5-overlay.dts
@@ -0,0 +1,48 @@
+/*
+ * vec-gpio-pi5-overlay.dts
+ */
+
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&rp1_vec>;
+		rp1vec_overlay: __overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&rp1vec_ch0pins &rp1vec_ch0pins>;
+		};
+	};
+
+	fragment@1 {
+		   target = <&gpio>;
+		   __overlay__ {
+			rp1vec_ch0pins: rp1vec_ch0pins {
+				function = "dpi";
+				pins = "gpio4", "gpio5", "gpio6", "gpio7",
+					"gpio8", "gpio9", "gpio10", "gpio11";
+				bias-disable;
+				drive-strength = <12>;
+			};
+			rp1vec_pin27: rp1vec_pin27 {
+				function = "dpi";
+				pins = "gpio27";
+				bias-disable;
+				drive-strength = <12>;
+			};
+			rp1vec_clkpin: rp1vec_clkpin {
+				function = "dpi";
+				pins = "gpio0";
+				bias-disable;
+				drive-strength = <12>;
+			};
+		};
+	};
+
+	__overrides__ {
+		gpio27 = <&rp1vec_overlay>, "pinctrl-0:4=", <&rp1vec_pin27>;
+		clk = <&rp1vec_overlay>, "pinctrl-0:8=", <&rp1vec_clkpin>;
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vga666-overlay.dts b/arch/arm/boot/dts/overlays/vga666-overlay.dts
new file mode 100644
index 000000000000..a4968d180a5d
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vga666-overlay.dts
@@ -0,0 +1,30 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	// There is no VGA driver module, but we need a platform device
+	// node (that doesn't already use pinctrl) to hang the pinctrl
+	// reference on - leds will do
+
+	fragment@0 {
+		target = <&leds>;
+		__overlay__ {
+			pinctrl-names = "default";
+			pinctrl-0 = <&vga666_pins>;
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			vga666_pins: vga666_pins {
+				brcm,pins = <2 3 4 5 6 7 8 9 10 11 12
+					     13 14 15 16 17 18 19 20 21>;
+				brcm,function = <6>; /* alt2 */
+				brcm,pull = <0>; /* no pull */
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/vl805-overlay.dts b/arch/arm/boot/dts/overlays/vl805-overlay.dts
new file mode 100644
index 000000000000..81adf34b29f2
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/vl805-overlay.dts
@@ -0,0 +1,18 @@
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/reset/raspberrypi,firmware-reset.h>
+
+/ {
+	compatible = "brcm,bcm2711";
+
+	fragment@0 {
+		target-path = "pcie0/pci@0,0";
+		__overlay__ {
+			usb@0,0 {
+				reg = <0 0 0 0 0>;
+				resets = <&reset RASPBERRYPI_FIRMWARE_RESET_ID_USB>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/w1-gpio-overlay.dts b/arch/arm/boot/dts/overlays/w1-gpio-overlay.dts
new file mode 100644
index 000000000000..f44e325bc1f2
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/w1-gpio-overlay.dts
@@ -0,0 +1,40 @@
+// Definitions for w1-gpio module (without external pullup)
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+
+			w1: onewire@0 {
+				compatible = "w1-gpio";
+				pinctrl-names = "default";
+				pinctrl-0 = <&w1_pins>;
+				gpios = <&gpio 4 0>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			w1_pins: w1_pins@0 {
+				brcm,pins = <4>;
+				brcm,function = <0>; // in (initially)
+				brcm,pull = <0>; // off
+			};
+		};
+	};
+
+	__overrides__ {
+		gpiopin =       <&w1>,"gpios:4",
+				<&w1>,"reg:0",
+				<&w1_pins>,"brcm,pins:0",
+				<&w1_pins>,"reg:0";
+		pullup;		// Silently ignore unneeded parameter
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/w1-gpio-pi5-overlay.dts b/arch/arm/boot/dts/overlays/w1-gpio-pi5-overlay.dts
new file mode 100644
index 000000000000..36f60548a7c8
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/w1-gpio-pi5-overlay.dts
@@ -0,0 +1,12 @@
+#include "w1-gpio-overlay.dts"
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@2 {
+		target = <&w1>;
+		__overlay__ {
+			raspberrypi,delay-needs-poll;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/w1-gpio-pullup-overlay.dts b/arch/arm/boot/dts/overlays/w1-gpio-pullup-overlay.dts
new file mode 100644
index 000000000000..953c6a1aeab9
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/w1-gpio-pullup-overlay.dts
@@ -0,0 +1,42 @@
+// Definitions for w1-gpio module (with external pullup)
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target-path = "/";
+		__overlay__ {
+
+			w1: onewire@0 {
+				compatible = "w1-gpio";
+				pinctrl-names = "default";
+				pinctrl-0 = <&w1_pins>;
+				gpios = <&gpio 4 0>, <&gpio 5 1>;
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			w1_pins: w1_pins@0 {
+				brcm,pins = <4 5>;
+				brcm,function = <0 1>; // in out
+				brcm,pull = <0 0>; // off off
+			};
+		};
+	};
+
+	__overrides__ {
+		gpiopin =       <&w1>,"gpios:4",
+				<&w1>,"reg:0",
+				<&w1_pins>,"brcm,pins:0",
+				<&w1_pins>,"reg:0";
+		extpullup =     <&w1>,"gpios:16",
+				<&w1_pins>,"brcm,pins:4";
+		pullup;		// Silently ignore unneeded parameter
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/w1-gpio-pullup-pi5-overlay.dts b/arch/arm/boot/dts/overlays/w1-gpio-pullup-pi5-overlay.dts
new file mode 100644
index 000000000000..bca44070cbac
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/w1-gpio-pullup-pi5-overlay.dts
@@ -0,0 +1,12 @@
+#include "w1-gpio-pullup-overlay.dts"
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@2 {
+		target = <&w1>;
+		__overlay__ {
+			raspberrypi,delay-needs-poll;
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/w5500-overlay.dts b/arch/arm/boot/dts/overlays/w5500-overlay.dts
new file mode 100644
index 000000000000..4d3e66296753
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/w5500-overlay.dts
@@ -0,0 +1,63 @@
+// Overlay for the Wiznet w5500 Ethernet Controller
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev1>;
+		__dormant__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			status = "okay";
+
+			eth1: w5500@0{
+				compatible = "wiznet,w5500";
+				reg = <0>; /* CE0 */
+				pinctrl-names = "default";
+				pinctrl-0 = <&eth1_pins>;
+				interrupt-parent = <&gpio>;
+				interrupts = <25 0x8>;
+				spi-max-frequency = <30000000>;
+//				local-mac-address = [aa bb cc dd ee ff];
+				status = "okay";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&gpio>;
+		__overlay__ {
+			eth1_pins: eth1_pins {
+				brcm,pins = <25>;
+				brcm,function = <0>; /* in */
+				brcm,pull = <0>; /* none */
+			};
+		};
+	};
+
+	__overrides__ {
+		int_pin = <&eth1>, "interrupts:0",
+		          <&eth1_pins>, "brcm,pins:0";
+		speed   = <&eth1>, "spi-max-frequency:0";
+		cs      = <&eth1>, "reg:0",
+			  <0>, "!0=1";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/watterott-display-overlay.dts b/arch/arm/boot/dts/overlays/watterott-display-overlay.dts
new file mode 100644
index 000000000000..4388706d2c38
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/watterott-display-overlay.dts
@@ -0,0 +1,150 @@
+/*
+ * Device Tree overlay for rpi-display by Watterott
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@2 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+
+	fragment@3 {
+		target = <&gpio>;
+		__overlay__ {
+			rpi_display_pins: rpi_display_pins {
+				brcm,pins = <18 23 24 25>;
+				brcm,function = <1 1 1 0>; /* out out out in */
+				brcm,pull = <0 0 0 2>; /* - - - up */
+			};
+		};
+	};
+
+	fragment@4 {
+		target = <&spi0>;
+		__overlay__ {
+			/* needed to avoid dtc warning */
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			rpidisplay: rpi-display@0{
+				compatible = "ilitek,ili9341";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&rpi_display_pins>;
+
+				spi-max-frequency = <32000000>;
+				rotate = <270>;
+				bgr;
+				fps = <30>;
+				buswidth = <8>;
+				reset-gpios = <&gpio 23 1>;
+				dc-gpios = <&gpio 24 0>;
+				led-gpios = <&gpio 18 0>;
+				debug = <0>;
+			};
+
+			rpidisplay_ts: rpi-display-ts@1 {
+				compatible = "ti,ads7846";
+				reg = <1>;
+
+				spi-max-frequency = <2000000>;
+				interrupts = <25 2>; /* high-to-low edge triggered */
+				interrupt-parent = <&gpio>;
+				pendown-gpio = <&gpio 25 1>;
+				ti,x-plate-ohms = /bits/ 16 <60>;
+				ti,pressure-max = /bits/ 16 <255>;
+			};
+		};
+	};
+
+	fragment@10 {
+		target = <&rpidisplay>;
+		__dormant__  {
+			backlight = <&backlight_gpio>;
+		};
+	};
+
+	fragment@11 {
+		target-path = "/";
+		__dormant__  {
+			backlight_gpio: backlight_gpio {
+				compatible = "gpio-backlight";
+				gpios = <&gpio 18 0>; /* GPIO_ACTIVE_HIGH */
+			};
+		};
+	};
+
+	fragment@20 {
+		target = <&rpidisplay>;
+		__dormant__  {
+			backlight = <&backlight_pwm>;
+		};
+	};
+
+	fragment@21 {
+		target-path = "/";
+		__dormant__  {
+			backlight_pwm: backlight_pwm {
+				compatible = "pwm-backlight";
+				brightness-levels = <0 6 8 12 16 24 32 40 48 64 96 128 160 192 224 255>;
+				default-brightness-level = <16>;
+				pwms = <&pwm 0 200000 0>;
+			};
+		};
+	};
+
+	fragment@22 {
+		target = <&pwm>;
+		__dormant__ {
+			assigned-clock-rates = <1000000>;
+			status = "okay";
+		};
+	};
+
+	fragment@23 {
+		target = <&chosen>;
+		__dormant__  {
+			bootargs = "snd_bcm2835.enable_headphones=0";
+		};
+	};
+
+	__overrides__ {
+		speed =     <&rpidisplay>,"spi-max-frequency:0";
+		rotate =    <&rpidisplay>,"rotate:0", /* fbtft */
+			    <&rpidisplay>,"rotation:0"; /* drm */
+		fps =       <&rpidisplay>,"fps:0";
+		debug =     <&rpidisplay>,"debug:0";
+		xohms =     <&rpidisplay_ts>,"ti,x-plate-ohms;0";
+		swapxy =    <&rpidisplay_ts>,"ti,swap-xy?";
+		backlight = <&rpidisplay>,"led-gpios:4",
+		            <&rpi_display_pins>,"brcm,pins:0";
+		drm =       <&rpidisplay>, "compatible=multi-inno,mi0283qt",
+			    <&rpidisplay>, "spi-max-frequency:0=70000000",
+			    <&rpidisplay>, "reset-gpios:8=0", /* GPIO_ACTIVE_HIGH */
+			    <0>, "+10+11";
+		backlight-pwm = <0>, "-10-11+20+21+22+23",
+				<&rpi_display_pins>, "brcm,function:0=2"; /* Alt5 */
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/waveshare-can-fd-hat-mode-a-overlay.dts b/arch/arm/boot/dts/overlays/waveshare-can-fd-hat-mode-a-overlay.dts
new file mode 100644
index 000000000000..59388cc3b0b9
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/waveshare-can-fd-hat-mode-a-overlay.dts
@@ -0,0 +1,140 @@
+// redo: ovmerge -c spi1-1cs-overlay.dts,cs0_pin=26,cs0_spidev=false mcp251xfd-overlay.dts,spi0-0,interrupt=25 mcp251xfd-overlay.dts,spi1-0,interrupt=16
+
+// Device tree overlay for https://www.waveshare.com/2-ch-can-fd-hat.htm
+// in "Mode A" (default) configuration
+// for details see https://www.waveshare.com/wiki/2-CH_CAN_FD_HAT
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/pinctrl/bcm2835.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			spi1_pins: spi1_pins {
+				brcm,pins = <19 20 21>;
+				brcm,function = <3>;
+			};
+			spi1_cs_pins: spi1_cs_pins {
+				brcm,pins = <26>;
+				brcm,function = <1>;
+			};
+		};
+	};
+	fragment@1 {
+		target = <&spi1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			pinctrl-names = "default";
+			pinctrl-0 = <&spi1_pins &spi1_cs_pins>;
+			cs-gpios = <&gpio 26 1>;
+			status = "okay";
+			spidev@0 {
+				compatible = "spidev";
+				reg = <0>;
+				#address-cells = <1>;
+				#size-cells = <0>;
+				spi-max-frequency = <125000000>;
+				status = "disabled";
+			};
+		};
+	};
+	fragment@2 {
+		target = <&aux>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+	fragment@3 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+	fragment@4 {
+		target = <&gpio>;
+		__overlay__ {
+			mcp251xfd_pins: mcp251xfd_spi0_0_pins {
+				brcm,pins = <25>;
+				brcm,function = <BCM2835_FSEL_GPIO_IN>;
+			};
+		};
+	};
+	fragment@5 {
+		target-path = "/clocks";
+		__overlay__ {
+			clk_mcp251xfd_osc: mcp251xfd-spi0-0-osc {
+				#clock-cells = <0>;
+				compatible = "fixed-clock";
+				clock-frequency = <40000000>;
+			};
+		};
+	};
+	fragment@6 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp251xfd@0 {
+				compatible = "microchip,mcp251xfd";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&mcp251xfd_pins>;
+				spi-max-frequency = <20000000>;
+				interrupt-parent = <&gpio>;
+				interrupts = <25 IRQ_TYPE_LEVEL_LOW>;
+				clocks = <&clk_mcp251xfd_osc>;
+			};
+		};
+	};
+	fragment@7 {
+		target-path = "spi1/spidev@0";
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+	fragment@8 {
+		target = <&gpio>;
+		__overlay__ {
+			mcp251xfd_pins_1: mcp251xfd_spi1_0_pins {
+				brcm,pins = <16>;
+				brcm,function = <BCM2835_FSEL_GPIO_IN>;
+			};
+		};
+	};
+	fragment@9 {
+		target-path = "/clocks";
+		__overlay__ {
+			clk_mcp251xfd_osc_1: mcp251xfd-spi1-0-osc {
+				#clock-cells = <0>;
+				compatible = "fixed-clock";
+				clock-frequency = <40000000>;
+			};
+		};
+	};
+	fragment@10 {
+		target = <&spi1>;
+		__overlay__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp251xfd@0 {
+				compatible = "microchip,mcp251xfd";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&mcp251xfd_pins_1>;
+				spi-max-frequency = <20000000>;
+				interrupt-parent = <&gpio>;
+				interrupts = <16 IRQ_TYPE_LEVEL_LOW>;
+				clocks = <&clk_mcp251xfd_osc_1>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/waveshare-can-fd-hat-mode-b-overlay.dts b/arch/arm/boot/dts/overlays/waveshare-can-fd-hat-mode-b-overlay.dts
new file mode 100644
index 000000000000..b2504922c8de
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/waveshare-can-fd-hat-mode-b-overlay.dts
@@ -0,0 +1,103 @@
+// redo: ovmerge -c mcp251xfd-overlay.dts,spi0-0,interrupt=25 mcp251xfd-overlay.dts,spi0-1,interrupt=16
+
+// Device tree overlay for https://www.waveshare.com/2-ch-can-fd-hat.htm
+// in "Mode B" (requried hardware modification) configuration
+// for details see https://www.waveshare.com/wiki/2-CH_CAN_FD_HAT
+
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/pinctrl/bcm2835.h>
+
+/ {
+	compatible = "brcm,bcm2835";
+	fragment@0 {
+		target = <&spidev0>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+	fragment@1 {
+		target = <&gpio>;
+		__overlay__ {
+			mcp251xfd_pins: mcp251xfd_spi0_0_pins {
+				brcm,pins = <25>;
+				brcm,function = <BCM2835_FSEL_GPIO_IN>;
+			};
+		};
+	};
+	fragment@2 {
+		target-path = "/clocks";
+		__overlay__ {
+			clk_mcp251xfd_osc: mcp251xfd-spi0-0-osc {
+				#clock-cells = <0>;
+				compatible = "fixed-clock";
+				clock-frequency = <40000000>;
+			};
+		};
+	};
+	fragment@3 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp251xfd@0 {
+				compatible = "microchip,mcp251xfd";
+				reg = <0>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&mcp251xfd_pins>;
+				spi-max-frequency = <20000000>;
+				interrupt-parent = <&gpio>;
+				interrupts = <25 IRQ_TYPE_LEVEL_LOW>;
+				clocks = <&clk_mcp251xfd_osc>;
+			};
+		};
+	};
+	fragment@4 {
+		target = <&spidev1>;
+		__overlay__ {
+			status = "disabled";
+		};
+	};
+	fragment@5 {
+		target = <&gpio>;
+		__overlay__ {
+			mcp251xfd_pins_1: mcp251xfd_spi0_1_pins {
+				brcm,pins = <16>;
+				brcm,function = <BCM2835_FSEL_GPIO_IN>;
+			};
+		};
+	};
+	fragment@6 {
+		target-path = "/clocks";
+		__overlay__ {
+			clk_mcp251xfd_osc_1: mcp251xfd-spi0-1-osc {
+				#clock-cells = <0>;
+				compatible = "fixed-clock";
+				clock-frequency = <40000000>;
+			};
+		};
+	};
+	fragment@7 {
+		target = <&spi0>;
+		__overlay__ {
+			status = "okay";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			mcp251xfd@1 {
+				compatible = "microchip,mcp251xfd";
+				reg = <1>;
+				pinctrl-names = "default";
+				pinctrl-0 = <&mcp251xfd_pins_1>;
+				spi-max-frequency = <20000000>;
+				interrupt-parent = <&gpio>;
+				interrupts = <16 IRQ_TYPE_LEVEL_LOW>;
+				clocks = <&clk_mcp251xfd_osc_1>;
+			};
+		};
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/wifimac-overlay.dts b/arch/arm/boot/dts/overlays/wifimac-overlay.dts
new file mode 100644
index 000000000000..505b9e3e4e3f
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/wifimac-overlay.dts
@@ -0,0 +1,21 @@
+/dts-v1/;
+/plugin/;
+
+/{
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&brcmf>;
+		wifi0: __overlay__ {
+			local-mac-address = [ 00 00 00 00 00 00 ];
+		};
+	};
+
+	__overrides__ {
+		wifiaddr = <&wifi0>, "local-mac-address[";
+	};
+
+	__exports__ {
+		wifi0;
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/wittypi-overlay.dts b/arch/arm/boot/dts/overlays/wittypi-overlay.dts
new file mode 100644
index 000000000000..71ce806186de
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/wittypi-overlay.dts
@@ -0,0 +1,44 @@
+/*
+ * Device Tree overlay for Witty Pi extension board by UUGear
+ *
+ */
+
+/dts-v1/;
+/plugin/;
+
+/ {
+
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&leds>;
+		__overlay__ {
+			compatible = "gpio-leds";
+			wittypi_led: wittypi_led {
+				label = "wittypi_led";
+				linux,default-trigger = "default-on";
+				gpios = <&gpio 17 0>;
+			};
+		};
+	};
+
+	fragment@1 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+
+			rtc: ds1337@68 {
+				compatible = "dallas,ds1337";
+				reg = <0x68>;
+				wakeup-source;
+			};
+		};
+	};
+
+	__overrides__ {
+		led_gpio =	<&wittypi_led>,"gpios:4";
+		led_trigger =	<&wittypi_led>,"linux,default-trigger";
+	};
+
+};
diff --git a/arch/arm/boot/dts/overlays/wm8960-soundcard-overlay.dts b/arch/arm/boot/dts/overlays/wm8960-soundcard-overlay.dts
new file mode 100644
index 000000000000..d896c59f469b
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/wm8960-soundcard-overlay.dts
@@ -0,0 +1,82 @@
+// Definitions for Waveshare WM8960 https://github.com/waveshare/WM8960-Audio-HAT
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2835";
+
+	fragment@0 {
+		target = <&i2s_clk_producer>;
+		__overlay__ {
+			status = "okay";
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			wm8960_mclk: wm8960_mclk {
+				compatible = "fixed-clock";
+				#clock-cells = <0>;
+				clock-frequency = <12288000>;
+			};
+		};
+	};
+	fragment@2 {
+		target = <&i2c1>;
+		__overlay__ {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "okay";
+
+			wm8960: wm8960@1a {
+				compatible = "wlf,wm8960";
+				reg = <0x1a>;
+				#sound-dai-cells = <0>;
+				AVDD-supply = <&vdd_5v0_reg>;
+				DVDD-supply = <&vdd_3v3_reg>;
+				clocks = <&wm8960_mclk>;
+				clock-names = "mclk";
+			};
+		};
+	};
+
+	fragment@3 {
+		target = <&sound>;
+		slave_overlay: __overlay__ {
+			compatible = "simple-audio-card";
+			simple-audio-card,format = "i2s";
+			simple-audio-card,name = "wm8960-soundcard"; 
+			status = "okay";
+
+			simple-audio-card,widgets =
+				"Microphone", "Mic Jack",
+				"Line", "Line In",
+				"Line", "Line Out",
+				"Speaker", "Speaker",
+				"Headphone", "Headphone Jack";
+			simple-audio-card,routing =
+				"Headphone Jack", "HP_L",
+				"Headphone Jack", "HP_R",
+				"Speaker", "SPK_LP",
+				"Speaker", "SPK_LN",
+				"LINPUT1", "Mic Jack",
+				"LINPUT3", "Mic Jack",
+				"RINPUT1", "Mic Jack",
+				"RINPUT2", "Mic Jack";
+
+			simple-audio-card,cpu {
+				sound-dai = <&i2s_clk_producer>;
+			};
+
+			dailink0_slave: simple-audio-card,codec {
+				sound-dai = <&wm8960>;
+			};
+		};
+	};
+
+	__overrides__ {
+		alsaname = <&slave_overlay>,"simple-audio-card,name";
+		compatible = <&wm8960>,"compatible";
+	};
+};
diff --git a/arch/arm/boot/dts/overlays/ws2812-pio-overlay.dts b/arch/arm/boot/dts/overlays/ws2812-pio-overlay.dts
new file mode 100644
index 000000000000..4550e16d44e1
--- /dev/null
+++ b/arch/arm/boot/dts/overlays/ws2812-pio-overlay.dts
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0
+// Device tree overlay for RP1 PIO WS2812 driver.
+/dts-v1/;
+/plugin/;
+
+/ {
+	compatible = "brcm,bcm2712";
+
+	fragment@0 {
+		target = <&gpio>;
+		__overlay__ {
+			ws2812_pio_pins: ws2812_pio_pins@4 {
+				brcm,pins = <4>; /* gpio 4 */
+				function = "pio";
+				bias-disable;
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/";
+		__overlay__ {
+			ws2812_pio: ws2812_pio@4 {
+				compatible = "raspberrypi,ws2812-pio-rp1";
+				pinctrl-names = "default";
+				pinctrl-0 = <&ws2812_pio_pins>;
+				dev-name = "leds%d";
+				leds-gpios = <&gpio 4 0>;
+				rpi,num-leds = <60>;
+				rpi,brightness = <255>;
+			};
+		};
+	};
+
+	__overrides__ {
+		brightness = <&ws2812_pio>, "rpi,brightness:0";
+		dev_name = <&ws2812_pio>, "dev-name";
+		gpio = <&ws2812_pio>,"leds-gpios:4",
+		       <&ws2812_pio_pins>,"brcm,pins:0",
+		       /* modify reg values to allow multiple instantiation */
+		       <&ws2812_pio>,"reg:0",
+		       <&ws2812_pio_pins>,"reg:0";
+		num_leds = <&ws2812_pio>, "rpi,num-leds:0";
+		rgbw = <&ws2812_pio>, "rpi,rgbw?";
+	};
+};
diff --git a/arch/arm/configs/bcm2709_defconfig b/arch/arm/configs/bcm2709_defconfig
new file mode 100644
index 000000000000..614e3002770b
--- /dev/null
+++ b/arch/arm/configs/bcm2709_defconfig
@@ -0,0 +1,1649 @@
+CONFIG_LOCALVERSION="-v7"
+# CONFIG_LOCALVERSION_AUTO is not set
+CONFIG_SYSVIPC=y
+CONFIG_POSIX_MQUEUE=y
+CONFIG_GENERIC_IRQ_DEBUGFS=y
+CONFIG_NO_HZ=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_BPF_SYSCALL=y
+CONFIG_PREEMPT_VOLUNTARY=y
+CONFIG_BSD_PROCESS_ACCT=y
+CONFIG_BSD_PROCESS_ACCT_V3=y
+CONFIG_TASKSTATS=y
+CONFIG_TASK_DELAY_ACCT=y
+CONFIG_TASK_XACCT=y
+CONFIG_TASK_IO_ACCOUNTING=y
+CONFIG_PSI=y
+CONFIG_PSI_DEFAULT_DISABLED=y
+CONFIG_IKCONFIG=m
+CONFIG_IKCONFIG_PROC=y
+CONFIG_MEMCG=y
+CONFIG_BLK_CGROUP=y
+CONFIG_CFS_BANDWIDTH=y
+CONFIG_CGROUP_PIDS=y
+CONFIG_CGROUP_FREEZER=y
+CONFIG_CPUSETS=y
+CONFIG_CPUSETS_V1=y
+CONFIG_CGROUP_DEVICE=y
+CONFIG_CGROUP_CPUACCT=y
+CONFIG_CGROUP_PERF=y
+CONFIG_CGROUP_BPF=y
+CONFIG_NAMESPACES=y
+CONFIG_USER_NS=y
+CONFIG_SCHED_AUTOGROUP=y
+CONFIG_BLK_DEV_INITRD=y
+CONFIG_EXPERT=y
+CONFIG_PROFILING=y
+CONFIG_ARCH_BCM=y
+CONFIG_ARCH_BCM2835=y
+# CONFIG_CACHE_L2X0 is not set
+CONFIG_SMP=y
+CONFIG_VMSPLIT_2G=y
+CONFIG_UACCESS_WITH_MEMCPY=y
+# CONFIG_ATAGS is not set
+CONFIG_CMDLINE="console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait"
+CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_STAT=y
+CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y
+CONFIG_CPU_FREQ_GOV_POWERSAVE=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
+CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y
+CONFIG_CPUFREQ_DT=y
+CONFIG_ARM_RASPBERRYPI_CPUFREQ=y
+CONFIG_VFP=y
+CONFIG_NEON=y
+CONFIG_KERNEL_MODE_NEON=y
+# CONFIG_SUSPEND is not set
+CONFIG_PM=y
+CONFIG_JUMP_LABEL=y
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_MODVERSIONS=y
+CONFIG_MODULE_SRCVERSION_ALL=y
+CONFIG_MODULE_COMPRESS=y
+CONFIG_MODULE_COMPRESS_XZ=y
+CONFIG_PARTITION_ADVANCED=y
+CONFIG_MAC_PARTITION=y
+CONFIG_BINFMT_MISC=m
+CONFIG_ZSWAP=y
+# CONFIG_COMPAT_BRK is not set
+CONFIG_CMA=y
+CONFIG_CMA_AREAS=7
+CONFIG_LRU_GEN=y
+CONFIG_LRU_GEN_ENABLED=y
+CONFIG_NET=y
+CONFIG_PACKET=y
+CONFIG_XFRM_USER=y
+CONFIG_XFRM_INTERFACE=m
+CONFIG_XFRM_SUB_POLICY=y
+CONFIG_XFRM_STATISTICS=y
+CONFIG_NET_KEY=m
+CONFIG_IP_MULTICAST=y
+CONFIG_IP_ADVANCED_ROUTER=y
+CONFIG_IP_MULTIPLE_TABLES=y
+CONFIG_IP_ROUTE_MULTIPATH=y
+CONFIG_IP_ROUTE_VERBOSE=y
+CONFIG_IP_PNP=y
+CONFIG_IP_PNP_DHCP=y
+CONFIG_IP_PNP_RARP=y
+CONFIG_NET_IPIP=m
+CONFIG_NET_IPGRE_DEMUX=m
+CONFIG_NET_IPGRE=m
+CONFIG_IP_MROUTE=y
+CONFIG_IP_MROUTE_MULTIPLE_TABLES=y
+CONFIG_IP_PIMSM_V1=y
+CONFIG_IP_PIMSM_V2=y
+CONFIG_NET_IPVTI=m
+CONFIG_NET_FOU_IP_TUNNELS=y
+CONFIG_INET_AH=m
+CONFIG_INET_ESP=m
+CONFIG_INET_IPCOMP=m
+CONFIG_INET_DIAG=m
+CONFIG_TCP_CONG_ADVANCED=y
+CONFIG_TCP_CONG_BBR=m
+CONFIG_IPV6=m
+CONFIG_IPV6_ROUTER_PREF=y
+CONFIG_IPV6_ROUTE_INFO=y
+CONFIG_INET6_AH=m
+CONFIG_INET6_ESP=m
+CONFIG_INET6_ESP_OFFLOAD=m
+CONFIG_INET6_IPCOMP=m
+CONFIG_IPV6_ILA=m
+CONFIG_IPV6_VTI=m
+CONFIG_IPV6_SIT_6RD=y
+CONFIG_IPV6_GRE=m
+CONFIG_IPV6_MULTIPLE_TABLES=y
+CONFIG_IPV6_SUBTREES=y
+CONFIG_IPV6_MROUTE=y
+CONFIG_IPV6_MROUTE_MULTIPLE_TABLES=y
+CONFIG_IPV6_PIMSM_V2=y
+CONFIG_NETFILTER=y
+CONFIG_BRIDGE_NETFILTER=m
+CONFIG_NF_CONNTRACK=m
+CONFIG_NF_CONNTRACK_ZONES=y
+CONFIG_NF_CONNTRACK_EVENTS=y
+CONFIG_NF_CONNTRACK_TIMESTAMP=y
+CONFIG_NF_CONNTRACK_AMANDA=m
+CONFIG_NF_CONNTRACK_FTP=m
+CONFIG_NF_CONNTRACK_H323=m
+CONFIG_NF_CONNTRACK_IRC=m
+CONFIG_NF_CONNTRACK_NETBIOS_NS=m
+CONFIG_NF_CONNTRACK_SNMP=m
+CONFIG_NF_CONNTRACK_PPTP=m
+CONFIG_NF_CONNTRACK_SANE=m
+CONFIG_NF_CONNTRACK_SIP=m
+CONFIG_NF_CONNTRACK_TFTP=m
+CONFIG_NF_CT_NETLINK=m
+CONFIG_NF_TABLES=m
+CONFIG_NF_TABLES_INET=y
+CONFIG_NF_TABLES_NETDEV=y
+CONFIG_NFT_NUMGEN=m
+CONFIG_NFT_CT=m
+CONFIG_NFT_FLOW_OFFLOAD=m
+CONFIG_NFT_CONNLIMIT=m
+CONFIG_NFT_LOG=m
+CONFIG_NFT_LIMIT=m
+CONFIG_NFT_MASQ=m
+CONFIG_NFT_REDIR=m
+CONFIG_NFT_NAT=m
+CONFIG_NFT_TUNNEL=m
+CONFIG_NFT_QUEUE=m
+CONFIG_NFT_QUOTA=m
+CONFIG_NFT_REJECT=m
+CONFIG_NFT_COMPAT=m
+CONFIG_NFT_HASH=m
+CONFIG_NFT_FIB_INET=m
+CONFIG_NFT_XFRM=m
+CONFIG_NFT_SOCKET=m
+CONFIG_NFT_OSF=m
+CONFIG_NFT_TPROXY=m
+CONFIG_NFT_SYNPROXY=m
+CONFIG_NFT_DUP_NETDEV=m
+CONFIG_NFT_FWD_NETDEV=m
+CONFIG_NFT_FIB_NETDEV=m
+CONFIG_NF_FLOW_TABLE_INET=m
+CONFIG_NF_FLOW_TABLE=m
+CONFIG_NETFILTER_XT_SET=m
+CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m
+CONFIG_NETFILTER_XT_TARGET_CLASSIFY=m
+CONFIG_NETFILTER_XT_TARGET_CONNMARK=m
+CONFIG_NETFILTER_XT_TARGET_DSCP=m
+CONFIG_NETFILTER_XT_TARGET_HMARK=m
+CONFIG_NETFILTER_XT_TARGET_IDLETIMER=m
+CONFIG_NETFILTER_XT_TARGET_LED=m
+CONFIG_NETFILTER_XT_TARGET_LOG=m
+CONFIG_NETFILTER_XT_TARGET_MARK=m
+CONFIG_NETFILTER_XT_TARGET_NFLOG=m
+CONFIG_NETFILTER_XT_TARGET_NFQUEUE=m
+CONFIG_NETFILTER_XT_TARGET_NOTRACK=m
+CONFIG_NETFILTER_XT_TARGET_TEE=m
+CONFIG_NETFILTER_XT_TARGET_TPROXY=m
+CONFIG_NETFILTER_XT_TARGET_TRACE=m
+CONFIG_NETFILTER_XT_TARGET_TCPMSS=m
+CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=m
+CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m
+CONFIG_NETFILTER_XT_MATCH_BPF=m
+CONFIG_NETFILTER_XT_MATCH_CLUSTER=m
+CONFIG_NETFILTER_XT_MATCH_COMMENT=m
+CONFIG_NETFILTER_XT_MATCH_CONNBYTES=m
+CONFIG_NETFILTER_XT_MATCH_CONNLABEL=m
+CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=m
+CONFIG_NETFILTER_XT_MATCH_CONNMARK=m
+CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m
+CONFIG_NETFILTER_XT_MATCH_CPU=m
+CONFIG_NETFILTER_XT_MATCH_DCCP=m
+CONFIG_NETFILTER_XT_MATCH_DEVGROUP=m
+CONFIG_NETFILTER_XT_MATCH_DSCP=m
+CONFIG_NETFILTER_XT_MATCH_ESP=m
+CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=m
+CONFIG_NETFILTER_XT_MATCH_HELPER=m
+CONFIG_NETFILTER_XT_MATCH_IPRANGE=m
+CONFIG_NETFILTER_XT_MATCH_IPVS=m
+CONFIG_NETFILTER_XT_MATCH_LENGTH=m
+CONFIG_NETFILTER_XT_MATCH_LIMIT=m
+CONFIG_NETFILTER_XT_MATCH_MAC=m
+CONFIG_NETFILTER_XT_MATCH_MARK=m
+CONFIG_NETFILTER_XT_MATCH_MULTIPORT=m
+CONFIG_NETFILTER_XT_MATCH_NFACCT=m
+CONFIG_NETFILTER_XT_MATCH_OSF=m
+CONFIG_NETFILTER_XT_MATCH_OWNER=m
+CONFIG_NETFILTER_XT_MATCH_POLICY=m
+CONFIG_NETFILTER_XT_MATCH_PHYSDEV=m
+CONFIG_NETFILTER_XT_MATCH_PKTTYPE=m
+CONFIG_NETFILTER_XT_MATCH_QUOTA=m
+CONFIG_NETFILTER_XT_MATCH_RATEEST=m
+CONFIG_NETFILTER_XT_MATCH_REALM=m
+CONFIG_NETFILTER_XT_MATCH_RECENT=m
+CONFIG_NETFILTER_XT_MATCH_SOCKET=m
+CONFIG_NETFILTER_XT_MATCH_STATE=m
+CONFIG_NETFILTER_XT_MATCH_STATISTIC=m
+CONFIG_NETFILTER_XT_MATCH_STRING=m
+CONFIG_NETFILTER_XT_MATCH_TCPMSS=m
+CONFIG_NETFILTER_XT_MATCH_TIME=m
+CONFIG_NETFILTER_XT_MATCH_U32=m
+CONFIG_IP_SET=m
+CONFIG_IP_SET_BITMAP_IP=m
+CONFIG_IP_SET_BITMAP_IPMAC=m
+CONFIG_IP_SET_BITMAP_PORT=m
+CONFIG_IP_SET_HASH_IP=m
+CONFIG_IP_SET_HASH_IPPORT=m
+CONFIG_IP_SET_HASH_IPPORTIP=m
+CONFIG_IP_SET_HASH_IPPORTNET=m
+CONFIG_IP_SET_HASH_NET=m
+CONFIG_IP_SET_HASH_NETPORT=m
+CONFIG_IP_SET_HASH_NETIFACE=m
+CONFIG_IP_SET_LIST_SET=m
+CONFIG_IP_VS=m
+CONFIG_IP_VS_IPV6=y
+CONFIG_IP_VS_PROTO_TCP=y
+CONFIG_IP_VS_PROTO_UDP=y
+CONFIG_IP_VS_PROTO_ESP=y
+CONFIG_IP_VS_PROTO_AH=y
+CONFIG_IP_VS_PROTO_SCTP=y
+CONFIG_IP_VS_RR=m
+CONFIG_IP_VS_WRR=m
+CONFIG_IP_VS_LC=m
+CONFIG_IP_VS_WLC=m
+CONFIG_IP_VS_LBLC=m
+CONFIG_IP_VS_LBLCR=m
+CONFIG_IP_VS_DH=m
+CONFIG_IP_VS_SH=m
+CONFIG_IP_VS_SED=m
+CONFIG_IP_VS_NQ=m
+CONFIG_IP_VS_FTP=m
+CONFIG_IP_VS_PE_SIP=m
+CONFIG_NFT_DUP_IPV4=m
+CONFIG_NFT_FIB_IPV4=m
+CONFIG_NF_TABLES_ARP=y
+CONFIG_NF_LOG_ARP=m
+CONFIG_NF_LOG_IPV4=m
+CONFIG_IP_NF_IPTABLES=m
+CONFIG_IP_NF_MATCH_AH=m
+CONFIG_IP_NF_MATCH_ECN=m
+CONFIG_IP_NF_MATCH_RPFILTER=m
+CONFIG_IP_NF_MATCH_TTL=m
+CONFIG_IP_NF_FILTER=m
+CONFIG_IP_NF_TARGET_REJECT=m
+CONFIG_IP_NF_TARGET_SYNPROXY=m
+CONFIG_IP_NF_NAT=m
+CONFIG_IP_NF_TARGET_MASQUERADE=m
+CONFIG_IP_NF_TARGET_NETMAP=m
+CONFIG_IP_NF_TARGET_REDIRECT=m
+CONFIG_IP_NF_MANGLE=m
+CONFIG_IP_NF_TARGET_ECN=m
+CONFIG_IP_NF_TARGET_TTL=m
+CONFIG_IP_NF_RAW=m
+CONFIG_IP_NF_ARPFILTER=m
+CONFIG_IP_NF_ARP_MANGLE=m
+CONFIG_NFT_DUP_IPV6=m
+CONFIG_NFT_FIB_IPV6=m
+CONFIG_IP6_NF_IPTABLES=m
+CONFIG_IP6_NF_MATCH_AH=m
+CONFIG_IP6_NF_MATCH_EUI64=m
+CONFIG_IP6_NF_MATCH_FRAG=m
+CONFIG_IP6_NF_MATCH_OPTS=m
+CONFIG_IP6_NF_MATCH_HL=m
+CONFIG_IP6_NF_MATCH_IPV6HEADER=m
+CONFIG_IP6_NF_MATCH_MH=m
+CONFIG_IP6_NF_MATCH_RPFILTER=m
+CONFIG_IP6_NF_MATCH_RT=m
+CONFIG_IP6_NF_MATCH_SRH=m
+CONFIG_IP6_NF_TARGET_HL=m
+CONFIG_IP6_NF_FILTER=m
+CONFIG_IP6_NF_TARGET_REJECT=m
+CONFIG_IP6_NF_TARGET_SYNPROXY=m
+CONFIG_IP6_NF_MANGLE=m
+CONFIG_IP6_NF_RAW=m
+CONFIG_IP6_NF_SECURITY=m
+CONFIG_IP6_NF_NAT=m
+CONFIG_IP6_NF_TARGET_MASQUERADE=m
+CONFIG_IP6_NF_TARGET_NPT=m
+CONFIG_NF_TABLES_BRIDGE=m
+CONFIG_NFT_BRIDGE_REJECT=m
+CONFIG_BRIDGE_NF_EBTABLES=m
+CONFIG_BRIDGE_EBT_BROUTE=m
+CONFIG_BRIDGE_EBT_T_FILTER=m
+CONFIG_BRIDGE_EBT_T_NAT=m
+CONFIG_BRIDGE_EBT_802_3=m
+CONFIG_BRIDGE_EBT_AMONG=m
+CONFIG_BRIDGE_EBT_ARP=m
+CONFIG_BRIDGE_EBT_IP=m
+CONFIG_BRIDGE_EBT_IP6=m
+CONFIG_BRIDGE_EBT_LIMIT=m
+CONFIG_BRIDGE_EBT_MARK=m
+CONFIG_BRIDGE_EBT_PKTTYPE=m
+CONFIG_BRIDGE_EBT_STP=m
+CONFIG_BRIDGE_EBT_VLAN=m
+CONFIG_BRIDGE_EBT_ARPREPLY=m
+CONFIG_BRIDGE_EBT_DNAT=m
+CONFIG_BRIDGE_EBT_MARK_T=m
+CONFIG_BRIDGE_EBT_REDIRECT=m
+CONFIG_BRIDGE_EBT_SNAT=m
+CONFIG_BRIDGE_EBT_LOG=m
+CONFIG_BRIDGE_EBT_NFLOG=m
+CONFIG_SCTP_COOKIE_HMAC_SHA1=y
+CONFIG_ATM=m
+CONFIG_L2TP=m
+CONFIG_L2TP_V3=y
+CONFIG_L2TP_IP=m
+CONFIG_L2TP_ETH=m
+CONFIG_BRIDGE=m
+CONFIG_BRIDGE_VLAN_FILTERING=y
+CONFIG_VLAN_8021Q=m
+CONFIG_VLAN_8021Q_GVRP=y
+CONFIG_ATALK=m
+CONFIG_6LOWPAN=m
+CONFIG_IEEE802154=m
+CONFIG_IEEE802154_6LOWPAN=m
+CONFIG_MAC802154=m
+CONFIG_NET_SCHED=y
+CONFIG_NET_SCH_HTB=m
+CONFIG_NET_SCH_HFSC=m
+CONFIG_NET_SCH_PRIO=m
+CONFIG_NET_SCH_MULTIQ=m
+CONFIG_NET_SCH_RED=m
+CONFIG_NET_SCH_SFB=m
+CONFIG_NET_SCH_SFQ=m
+CONFIG_NET_SCH_TEQL=m
+CONFIG_NET_SCH_TBF=m
+CONFIG_NET_SCH_GRED=m
+CONFIG_NET_SCH_NETEM=m
+CONFIG_NET_SCH_DRR=m
+CONFIG_NET_SCH_MQPRIO=m
+CONFIG_NET_SCH_CHOKE=m
+CONFIG_NET_SCH_QFQ=m
+CONFIG_NET_SCH_CODEL=m
+CONFIG_NET_SCH_FQ_CODEL=m
+CONFIG_NET_SCH_CAKE=m
+CONFIG_NET_SCH_FQ=m
+CONFIG_NET_SCH_HHF=m
+CONFIG_NET_SCH_PIE=m
+CONFIG_NET_SCH_INGRESS=m
+CONFIG_NET_SCH_PLUG=m
+CONFIG_NET_CLS_BASIC=m
+CONFIG_NET_CLS_ROUTE4=m
+CONFIG_NET_CLS_FW=m
+CONFIG_NET_CLS_U32=m
+CONFIG_CLS_U32_PERF=y
+CONFIG_CLS_U32_MARK=y
+CONFIG_NET_CLS_FLOW=m
+CONFIG_NET_CLS_CGROUP=m
+CONFIG_NET_CLS_FLOWER=m
+CONFIG_NET_CLS_MATCHALL=m
+CONFIG_NET_EMATCH=y
+CONFIG_NET_EMATCH_CMP=m
+CONFIG_NET_EMATCH_NBYTE=m
+CONFIG_NET_EMATCH_U32=m
+CONFIG_NET_EMATCH_META=m
+CONFIG_NET_EMATCH_TEXT=m
+CONFIG_NET_EMATCH_IPSET=m
+CONFIG_NET_CLS_ACT=y
+CONFIG_NET_ACT_POLICE=m
+CONFIG_NET_ACT_GACT=m
+CONFIG_GACT_PROB=y
+CONFIG_NET_ACT_MIRRED=m
+CONFIG_NET_ACT_NAT=m
+CONFIG_NET_ACT_PEDIT=m
+CONFIG_NET_ACT_SIMP=m
+CONFIG_NET_ACT_SKBEDIT=m
+CONFIG_NET_ACT_CSUM=m
+CONFIG_BATMAN_ADV=m
+CONFIG_OPENVSWITCH=m
+CONFIG_CGROUP_NET_PRIO=y
+CONFIG_NET_PKTGEN=m
+CONFIG_HAMRADIO=y
+CONFIG_AX25=m
+CONFIG_NETROM=m
+CONFIG_ROSE=m
+CONFIG_MKISS=m
+CONFIG_6PACK=m
+CONFIG_BPQETHER=m
+CONFIG_BAYCOM_SER_FDX=m
+CONFIG_BAYCOM_SER_HDX=m
+CONFIG_YAM=m
+CONFIG_CAN=m
+CONFIG_CAN_J1939=m
+CONFIG_CAN_ISOTP=m
+CONFIG_BT=m
+CONFIG_BT_RFCOMM=m
+CONFIG_BT_RFCOMM_TTY=y
+CONFIG_BT_BNEP=m
+CONFIG_BT_BNEP_MC_FILTER=y
+CONFIG_BT_BNEP_PROTO_FILTER=y
+CONFIG_BT_HIDP=m
+CONFIG_BT_6LOWPAN=m
+CONFIG_BT_HCIBTUSB=m
+CONFIG_BT_HCIBTUSB_MTK=y
+CONFIG_BT_HCIUART=m
+CONFIG_BT_HCIUART_3WIRE=y
+CONFIG_BT_HCIUART_BCM=y
+CONFIG_BT_HCIBCM203X=m
+CONFIG_BT_HCIBPA10X=m
+CONFIG_BT_HCIBFUSB=m
+CONFIG_BT_HCIVHCI=m
+CONFIG_BT_MRVL=m
+CONFIG_BT_MRVL_SDIO=m
+CONFIG_BT_ATH3K=m
+CONFIG_CFG80211=m
+CONFIG_CFG80211_WEXT=y
+CONFIG_MAC80211=m
+CONFIG_MAC80211_MESH=y
+CONFIG_RFKILL=m
+CONFIG_RFKILL_INPUT=y
+CONFIG_NET_9P=m
+CONFIG_NFC=m
+CONFIG_UEVENT_HELPER=y
+CONFIG_DEVTMPFS=y
+CONFIG_DEVTMPFS_MOUNT=y
+CONFIG_RASPBERRYPI_FIRMWARE=y
+CONFIG_MTD=m
+CONFIG_MTD_BLOCK=m
+CONFIG_MTD_BLOCK2MTD=m
+CONFIG_MTD_SPI_NAND=m
+CONFIG_MTD_SPI_NOR=m
+CONFIG_MTD_UBI=m
+CONFIG_OF_CONFIGFS=y
+CONFIG_ZRAM=m
+CONFIG_ZRAM_BACKEND_LZ4=y
+CONFIG_ZRAM_BACKEND_ZSTD=y
+CONFIG_ZRAM_BACKEND_LZO=y
+CONFIG_ZRAM_DEF_COMP_ZSTD=y
+CONFIG_ZRAM_WRITEBACK=y
+CONFIG_ZRAM_MULTI_COMP=y
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BLK_DEV_DRBD=m
+CONFIG_BLK_DEV_NBD=m
+CONFIG_BLK_DEV_RAM=y
+CONFIG_ATA_OVER_ETH=m
+CONFIG_BLK_DEV_RBD=m
+CONFIG_EEPROM_AT24=m
+CONFIG_EEPROM_AT25=m
+CONFIG_TI_ST=m
+CONFIG_SCSI=y
+# CONFIG_SCSI_PROC_FS is not set
+CONFIG_BLK_DEV_SD=y
+CONFIG_CHR_DEV_ST=m
+CONFIG_BLK_DEV_SR=m
+CONFIG_CHR_DEV_SG=m
+CONFIG_SCSI_ISCSI_ATTRS=y
+CONFIG_ISCSI_TCP=m
+CONFIG_ISCSI_BOOT_SYSFS=m
+CONFIG_ATA=m
+CONFIG_MD=y
+CONFIG_BCACHE=m
+CONFIG_BLK_DEV_DM=m
+CONFIG_DM_CRYPT=m
+CONFIG_DM_SNAPSHOT=m
+CONFIG_DM_THIN_PROVISIONING=m
+CONFIG_DM_CACHE=m
+CONFIG_DM_MIRROR=m
+CONFIG_DM_LOG_USERSPACE=m
+CONFIG_DM_RAID=m
+CONFIG_DM_ZERO=m
+CONFIG_DM_MULTIPATH=m
+CONFIG_DM_DELAY=m
+CONFIG_DM_VERITY=m
+CONFIG_DM_INTEGRITY=m
+CONFIG_NETDEVICES=y
+CONFIG_BONDING=m
+CONFIG_DUMMY=m
+CONFIG_WIREGUARD=m
+CONFIG_IFB=m
+CONFIG_MACVLAN=m
+CONFIG_IPVLAN=m
+CONFIG_VXLAN=m
+CONFIG_NETCONSOLE=m
+CONFIG_TUN=m
+CONFIG_VETH=m
+CONFIG_NETKIT=y
+CONFIG_NET_VRF=m
+CONFIG_ENC28J60=m
+CONFIG_QCA7000_SPI=m
+CONFIG_QCA7000_UART=m
+CONFIG_MSE102X=m
+CONFIG_WIZNET_W5100=m
+CONFIG_WIZNET_W5100_SPI=m
+CONFIG_CAN_VCAN=m
+CONFIG_CAN_SLCAN=m
+CONFIG_CAN_MCP251X=m
+CONFIG_CAN_MCP251XFD=m
+CONFIG_CAN_8DEV_USB=m
+CONFIG_CAN_EMS_USB=m
+CONFIG_CAN_GS_USB=m
+CONFIG_CAN_PEAK_USB=m
+CONFIG_MDIO_BITBANG=m
+CONFIG_PPP=m
+CONFIG_PPP_BSDCOMP=m
+CONFIG_PPP_DEFLATE=m
+CONFIG_PPP_FILTER=y
+CONFIG_PPP_MPPE=m
+CONFIG_PPP_MULTILINK=y
+CONFIG_PPPOATM=m
+CONFIG_PPPOE=m
+CONFIG_PPPOL2TP=m
+CONFIG_PPP_ASYNC=m
+CONFIG_PPP_SYNC_TTY=m
+CONFIG_SLIP=m
+CONFIG_SLIP_COMPRESSED=y
+CONFIG_SLIP_SMART=y
+CONFIG_USB_CATC=m
+CONFIG_USB_KAWETH=m
+CONFIG_USB_PEGASUS=m
+CONFIG_USB_RTL8150=m
+CONFIG_USB_RTL8152=m
+CONFIG_USB_LAN78XX=y
+CONFIG_USB_USBNET=y
+CONFIG_USB_NET_AX8817X=m
+CONFIG_USB_NET_AX88179_178A=m
+CONFIG_USB_NET_CDCETHER=m
+CONFIG_USB_NET_CDC_EEM=m
+CONFIG_USB_NET_CDC_NCM=m
+CONFIG_USB_NET_HUAWEI_CDC_NCM=m
+CONFIG_USB_NET_CDC_MBIM=m
+CONFIG_USB_NET_DM9601=m
+CONFIG_USB_NET_SR9700=m
+CONFIG_USB_NET_SR9800=m
+CONFIG_USB_NET_SMSC75XX=m
+CONFIG_USB_NET_SMSC95XX=y
+CONFIG_USB_NET_GL620A=m
+CONFIG_USB_NET_NET1080=m
+CONFIG_USB_NET_PLUSB=m
+CONFIG_USB_NET_MCS7830=m
+CONFIG_USB_NET_RNDIS_HOST=m
+CONFIG_USB_NET_CDC_SUBSET=m
+CONFIG_USB_ALI_M5632=y
+CONFIG_USB_AN2720=y
+CONFIG_USB_EPSON2888=y
+CONFIG_USB_KC2190=y
+CONFIG_USB_NET_ZAURUS=m
+CONFIG_USB_NET_CX82310_ETH=m
+CONFIG_USB_NET_KALMIA=m
+CONFIG_USB_NET_QMI_WWAN=m
+CONFIG_USB_HSO=m
+CONFIG_USB_NET_INT51X1=m
+CONFIG_USB_IPHETH=m
+CONFIG_USB_SIERRA_NET=m
+CONFIG_USB_VL600=m
+CONFIG_USB_NET_AQC111=m
+CONFIG_ATH9K=m
+CONFIG_ATH9K_HTC=m
+CONFIG_CARL9170=m
+CONFIG_ATH6KL=m
+CONFIG_ATH6KL_USB=m
+CONFIG_AR5523=m
+CONFIG_AT76C50X_USB=m
+CONFIG_B43=m
+# CONFIG_B43_PHY_N is not set
+CONFIG_B43LEGACY=m
+CONFIG_BRCMFMAC=m
+CONFIG_BRCMFMAC_USB=y
+CONFIG_BRCMDBG=y
+CONFIG_P54_COMMON=m
+CONFIG_P54_USB=m
+CONFIG_LIBERTAS=m
+CONFIG_LIBERTAS_USB=m
+CONFIG_LIBERTAS_SDIO=m
+CONFIG_LIBERTAS_THINFIRM=m
+CONFIG_LIBERTAS_THINFIRM_USB=m
+CONFIG_MWIFIEX=m
+CONFIG_MWIFIEX_SDIO=m
+CONFIG_MT7601U=m
+CONFIG_MT76x0U=m
+CONFIG_MT76x2U=m
+CONFIG_MT7921U=m
+CONFIG_MT7925U=m
+CONFIG_RT2X00=m
+CONFIG_RT2500USB=m
+CONFIG_RT73USB=m
+CONFIG_RT2800USB=m
+CONFIG_RT2800USB_RT3573=y
+CONFIG_RT2800USB_RT53XX=y
+CONFIG_RT2800USB_RT55XX=y
+CONFIG_RT2800USB_UNKNOWN=y
+CONFIG_RTL8187=m
+CONFIG_RTL8192CU=m
+CONFIG_RTL8XXXU=m
+CONFIG_RTW88=m
+CONFIG_RTW88_8822BU=m
+CONFIG_RTW88_8822CU=m
+CONFIG_RTW88_8723DU=m
+CONFIG_RTW88_8821CU=m
+CONFIG_ZD1211RW=m
+CONFIG_MAC80211_HWSIM=m
+CONFIG_IEEE802154_AT86RF230=m
+CONFIG_IEEE802154_MRF24J40=m
+CONFIG_IEEE802154_CC2520=m
+CONFIG_INPUT_MOUSEDEV=y
+CONFIG_INPUT_JOYDEV=m
+CONFIG_INPUT_EVDEV=y
+# CONFIG_KEYBOARD_ATKBD is not set
+CONFIG_KEYBOARD_GPIO=m
+CONFIG_KEYBOARD_TCA6416=m
+CONFIG_KEYBOARD_TCA8418=m
+CONFIG_KEYBOARD_MATRIX=m
+CONFIG_KEYBOARD_CAP11XX=m
+# CONFIG_INPUT_MOUSE is not set
+CONFIG_INPUT_JOYSTICK=y
+CONFIG_JOYSTICK_IFORCE=m
+CONFIG_JOYSTICK_IFORCE_USB=m
+CONFIG_JOYSTICK_XPAD=m
+CONFIG_JOYSTICK_XPAD_FF=y
+CONFIG_JOYSTICK_XPAD_LEDS=y
+CONFIG_JOYSTICK_PSXPAD_SPI=m
+CONFIG_JOYSTICK_PSXPAD_SPI_FF=y
+CONFIG_JOYSTICK_FSIA6B=m
+CONFIG_JOYSTICK_SENSEHAT=m
+CONFIG_INPUT_TOUCHSCREEN=y
+CONFIG_TOUCHSCREEN_ADS7846=m
+CONFIG_TOUCHSCREEN_EGALAX=m
+CONFIG_TOUCHSCREEN_EXC3000=m
+CONFIG_TOUCHSCREEN_GOODIX=m
+CONFIG_TOUCHSCREEN_ILI210X=m
+CONFIG_TOUCHSCREEN_ILITEK=m
+CONFIG_TOUCHSCREEN_EDT_FT5X06=m
+CONFIG_TOUCHSCREEN_RASPBERRYPI_FW=m
+CONFIG_TOUCHSCREEN_USB_COMPOSITE=m
+CONFIG_TOUCHSCREEN_TSC2007=m
+CONFIG_TOUCHSCREEN_TSC2007_IIO=y
+CONFIG_TOUCHSCREEN_STMPE=m
+CONFIG_TOUCHSCREEN_IQS5XX=m
+CONFIG_INPUT_MISC=y
+CONFIG_INPUT_AD714X=m
+CONFIG_INPUT_ATI_REMOTE2=m
+CONFIG_INPUT_KEYSPAN_REMOTE=m
+CONFIG_INPUT_POWERMATE=m
+CONFIG_INPUT_YEALINK=m
+CONFIG_INPUT_CM109=m
+CONFIG_INPUT_UINPUT=m
+CONFIG_INPUT_PWM_BEEPER=m
+CONFIG_INPUT_GPIO_ROTARY_ENCODER=m
+CONFIG_INPUT_ADXL34X=m
+CONFIG_INPUT_CMA3000=m
+CONFIG_SERIO=m
+CONFIG_SERIO_RAW=m
+CONFIG_GAMEPORT=m
+CONFIG_BRCM_CHAR_DRIVERS=y
+CONFIG_BCM_VCIO=y
+# CONFIG_LEGACY_PTYS is not set
+CONFIG_SERIAL_8250=y
+# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
+CONFIG_SERIAL_8250_CONSOLE=y
+# CONFIG_SERIAL_8250_DMA is not set
+CONFIG_SERIAL_8250_NR_UARTS=1
+CONFIG_SERIAL_8250_RUNTIME_UARTS=0
+CONFIG_SERIAL_8250_EXTENDED=y
+CONFIG_SERIAL_8250_SHARE_IRQ=y
+CONFIG_SERIAL_8250_BCM2835AUX=y
+CONFIG_SERIAL_OF_PLATFORM=y
+CONFIG_SERIAL_AMBA_PL011=y
+CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
+CONFIG_SERIAL_SC16IS7XX=m
+CONFIG_SERIAL_RPI_FW=m
+CONFIG_SERIAL_DEV_BUS=y
+CONFIG_TTY_PRINTK=y
+CONFIG_HW_RANDOM=y
+CONFIG_TCG_TPM=m
+CONFIG_TCG_TIS_SPI=m
+CONFIG_TCG_TIS_I2C=m
+CONFIG_RASPBERRYPI_GPIOMEM=m
+CONFIG_I2C=y
+CONFIG_I2C_CHARDEV=m
+CONFIG_I2C_MUX_GPMUX=m
+CONFIG_I2C_MUX_PCA954x=m
+CONFIG_I2C_MUX_PINCTRL=m
+CONFIG_I2C_BCM2708=m
+CONFIG_I2C_BCM2835=m
+# CONFIG_I2C_BRCMSTB is not set
+CONFIG_I2C_GPIO=m
+CONFIG_I2C_ROBOTFUZZ_OSIF=m
+CONFIG_I2C_TINY_USB=m
+CONFIG_SPI=y
+CONFIG_SPI_BCM2835=m
+CONFIG_SPI_BCM2835AUX=m
+CONFIG_SPI_GPIO=m
+CONFIG_SPI_RP2040_GPIO_BRIDGE=m
+CONFIG_SPI_SPIDEV=m
+CONFIG_SPI_SLAVE=y
+CONFIG_PPS_CLIENT_LDISC=m
+CONFIG_PPS_CLIENT_GPIO=m
+CONFIG_PINCTRL_MCP23S08=m
+CONFIG_GPIO_SYSFS=y
+CONFIG_GPIO_BCM_VIRT=y
+CONFIG_GPIO_FXL6408=m
+CONFIG_GPIO_MAX7300=m
+CONFIG_GPIO_PCA953X=m
+CONFIG_GPIO_PCA953X_IRQ=y
+CONFIG_GPIO_PCF857X=m
+CONFIG_GPIO_ARIZONA=m
+CONFIG_GPIO_FSM=m
+CONFIG_GPIO_STMPE=y
+CONFIG_GPIO_MAX7301=m
+CONFIG_GPIO_MOCKUP=m
+CONFIG_W1=m
+CONFIG_W1_MASTER_DS2490=m
+CONFIG_W1_MASTER_DS2482=m
+CONFIG_W1_MASTER_GPIO=m
+CONFIG_W1_SLAVE_THERM=m
+CONFIG_W1_SLAVE_SMEM=m
+CONFIG_W1_SLAVE_DS2408=m
+CONFIG_W1_SLAVE_DS2413=m
+CONFIG_W1_SLAVE_DS2406=m
+CONFIG_W1_SLAVE_DS2423=m
+CONFIG_W1_SLAVE_DS2430=m
+CONFIG_W1_SLAVE_DS2431=m
+CONFIG_W1_SLAVE_DS2433=m
+CONFIG_W1_SLAVE_DS2438=m
+CONFIG_W1_SLAVE_DS2780=m
+CONFIG_W1_SLAVE_DS2781=m
+CONFIG_W1_SLAVE_DS28E04=m
+CONFIG_W1_SLAVE_DS28E17=m
+CONFIG_POWER_RESET=y
+CONFIG_POWER_RESET_GPIO=y
+CONFIG_RPI_POE_POWER=m
+CONFIG_BATTERY_DS2760=m
+CONFIG_BATTERY_MAX17040=m
+CONFIG_CHARGER_GPIO=m
+CONFIG_BATTERY_GAUGE_LTC2941=m
+CONFIG_SENSORS_ADT7410=m
+CONFIG_SENSORS_AHT10=m
+CONFIG_SENSORS_CHIPCAP2=m
+CONFIG_SENSORS_DRIVETEMP=m
+CONFIG_SENSORS_DS1621=m
+CONFIG_SENSORS_GPIO_FAN=m
+CONFIG_SENSORS_IIO_HWMON=m
+CONFIG_SENSORS_JC42=m
+CONFIG_SENSORS_POWERZ=m
+CONFIG_SENSORS_LM75=m
+CONFIG_SENSORS_PWM_FAN=m
+CONFIG_SENSORS_RASPBERRYPI_HWMON=m
+CONFIG_SENSORS_SHT21=m
+CONFIG_SENSORS_SHT3x=m
+CONFIG_SENSORS_SHT4x=m
+CONFIG_SENSORS_SHTC1=m
+CONFIG_SENSORS_EMC2305=m
+CONFIG_SENSORS_ADS7828=m
+CONFIG_SENSORS_INA2XX=m
+CONFIG_SENSORS_INA238=m
+CONFIG_SENSORS_TMP102=m
+CONFIG_BCM2835_THERMAL=y
+CONFIG_WATCHDOG=y
+CONFIG_WATCHDOG_SYSFS=y
+CONFIG_GPIO_WATCHDOG=m
+CONFIG_BCM2835_WDT=y
+CONFIG_MFD_RASPBERRYPI_POE_HAT=m
+CONFIG_MFD_STMPE=y
+CONFIG_STMPE_SPI=y
+CONFIG_MFD_ARIZONA_I2C=m
+CONFIG_MFD_ARIZONA_SPI=m
+CONFIG_MFD_WM5102=y
+CONFIG_REGULATOR=y
+CONFIG_REGULATOR_FIXED_VOLTAGE=m
+CONFIG_REGULATOR_ARIZONA_LDO1=m
+CONFIG_REGULATOR_ARIZONA_MICSUPP=m
+CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY=m
+CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_V2=m
+CONFIG_REGULATOR_WAVESHARE_TOUCHSCREEN=m
+CONFIG_RC_CORE=y
+CONFIG_BPF_LIRC_MODE2=y
+CONFIG_LIRC=y
+CONFIG_RC_DECODERS=y
+CONFIG_IR_IMON_DECODER=m
+CONFIG_IR_JVC_DECODER=m
+CONFIG_IR_MCE_KBD_DECODER=m
+CONFIG_IR_NEC_DECODER=m
+CONFIG_IR_RC5_DECODER=m
+CONFIG_IR_RC6_DECODER=m
+CONFIG_IR_SANYO_DECODER=m
+CONFIG_IR_SHARP_DECODER=m
+CONFIG_IR_SONY_DECODER=m
+CONFIG_IR_XMP_DECODER=m
+CONFIG_RC_DEVICES=y
+CONFIG_IR_GPIO_CIR=m
+CONFIG_IR_GPIO_TX=m
+CONFIG_IR_IGUANA=m
+CONFIG_IR_IMON=m
+CONFIG_IR_MCEUSB=m
+CONFIG_IR_PWM_TX=m
+CONFIG_IR_REDRAT3=m
+CONFIG_IR_STREAMZAP=m
+CONFIG_IR_TOY=m
+CONFIG_IR_TTUSBIR=m
+CONFIG_RC_ATI_REMOTE=m
+CONFIG_RC_LOOPBACK=m
+CONFIG_MEDIA_CEC_RC=y
+CONFIG_MEDIA_SUPPORT=m
+CONFIG_MEDIA_USB_SUPPORT=y
+CONFIG_USB_GSPCA=m
+CONFIG_USB_GSPCA_BENQ=m
+CONFIG_USB_GSPCA_CONEX=m
+CONFIG_USB_GSPCA_CPIA1=m
+CONFIG_USB_GSPCA_DTCS033=m
+CONFIG_USB_GSPCA_ETOMS=m
+CONFIG_USB_GSPCA_FINEPIX=m
+CONFIG_USB_GSPCA_JEILINJ=m
+CONFIG_USB_GSPCA_JL2005BCD=m
+CONFIG_USB_GSPCA_KINECT=m
+CONFIG_USB_GSPCA_KONICA=m
+CONFIG_USB_GSPCA_MARS=m
+CONFIG_USB_GSPCA_MR97310A=m
+CONFIG_USB_GSPCA_NW80X=m
+CONFIG_USB_GSPCA_OV519=m
+CONFIG_USB_GSPCA_OV534=m
+CONFIG_USB_GSPCA_OV534_9=m
+CONFIG_USB_GSPCA_PAC207=m
+CONFIG_USB_GSPCA_PAC7302=m
+CONFIG_USB_GSPCA_PAC7311=m
+CONFIG_USB_GSPCA_SE401=m
+CONFIG_USB_GSPCA_SN9C2028=m
+CONFIG_USB_GSPCA_SN9C20X=m
+CONFIG_USB_GSPCA_SONIXB=m
+CONFIG_USB_GSPCA_SONIXJ=m
+CONFIG_USB_GSPCA_SPCA1528=m
+CONFIG_USB_GSPCA_SPCA500=m
+CONFIG_USB_GSPCA_SPCA501=m
+CONFIG_USB_GSPCA_SPCA505=m
+CONFIG_USB_GSPCA_SPCA506=m
+CONFIG_USB_GSPCA_SPCA508=m
+CONFIG_USB_GSPCA_SPCA561=m
+CONFIG_USB_GSPCA_SQ905=m
+CONFIG_USB_GSPCA_SQ905C=m
+CONFIG_USB_GSPCA_SQ930X=m
+CONFIG_USB_GSPCA_STK014=m
+CONFIG_USB_GSPCA_STK1135=m
+CONFIG_USB_GSPCA_STV0680=m
+CONFIG_USB_GSPCA_SUNPLUS=m
+CONFIG_USB_GSPCA_T613=m
+CONFIG_USB_GSPCA_TOPRO=m
+CONFIG_USB_GSPCA_TOUPTEK=m
+CONFIG_USB_GSPCA_TV8532=m
+CONFIG_USB_GSPCA_VC032X=m
+CONFIG_USB_GSPCA_VICAM=m
+CONFIG_USB_GSPCA_XIRLINK_CIT=m
+CONFIG_USB_GSPCA_ZC3XX=m
+CONFIG_USB_GL860=m
+CONFIG_USB_M5602=m
+CONFIG_USB_STV06XX=m
+CONFIG_USB_PWC=m
+CONFIG_USB_S2255=m
+CONFIG_VIDEO_USBTV=m
+CONFIG_USB_VIDEO_CLASS=m
+CONFIG_VIDEO_GO7007=m
+CONFIG_VIDEO_GO7007_USB=m
+CONFIG_VIDEO_GO7007_USB_S2250_BOARD=m
+CONFIG_VIDEO_HDPVR=m
+CONFIG_VIDEO_PVRUSB2=m
+CONFIG_VIDEO_STK1160=m
+CONFIG_VIDEO_AU0828=m
+CONFIG_VIDEO_AU0828_RC=y
+CONFIG_VIDEO_CX231XX=m
+CONFIG_VIDEO_CX231XX_ALSA=m
+CONFIG_VIDEO_CX231XX_DVB=m
+CONFIG_DVB_AS102=m
+CONFIG_DVB_B2C2_FLEXCOP_USB=m
+CONFIG_DVB_USB_V2=m
+CONFIG_DVB_USB_AF9015=m
+CONFIG_DVB_USB_AF9035=m
+CONFIG_DVB_USB_ANYSEE=m
+CONFIG_DVB_USB_AU6610=m
+CONFIG_DVB_USB_AZ6007=m
+CONFIG_DVB_USB_CE6230=m
+CONFIG_DVB_USB_DVBSKY=m
+CONFIG_DVB_USB_EC168=m
+CONFIG_DVB_USB_GL861=m
+CONFIG_DVB_USB_LME2510=m
+CONFIG_DVB_USB_MXL111SF=m
+CONFIG_DVB_USB_RTL28XXU=m
+CONFIG_DVB_USB=m
+CONFIG_DVB_USB_A800=m
+CONFIG_DVB_USB_AF9005=m
+CONFIG_DVB_USB_AF9005_REMOTE=m
+CONFIG_DVB_USB_AZ6027=m
+CONFIG_DVB_USB_CINERGY_T2=m
+CONFIG_DVB_USB_CXUSB=m
+CONFIG_DVB_USB_DIB0700=m
+CONFIG_DVB_USB_DIBUSB_MB=m
+CONFIG_DVB_USB_DIBUSB_MB_FAULTY=y
+CONFIG_DVB_USB_DIBUSB_MC=m
+CONFIG_DVB_USB_DIGITV=m
+CONFIG_DVB_USB_DTT200U=m
+CONFIG_DVB_USB_DTV5100=m
+CONFIG_DVB_USB_DW2102=m
+CONFIG_DVB_USB_GP8PSK=m
+CONFIG_DVB_USB_M920X=m
+CONFIG_DVB_USB_NOVA_T_USB2=m
+CONFIG_DVB_USB_OPERA1=m
+CONFIG_DVB_USB_PCTV452E=m
+CONFIG_DVB_USB_TECHNISAT_USB2=m
+CONFIG_DVB_USB_TTUSB2=m
+CONFIG_DVB_USB_UMT_010=m
+CONFIG_DVB_USB_VP702X=m
+CONFIG_DVB_USB_VP7045=m
+CONFIG_SMS_USB_DRV=m
+CONFIG_VIDEO_EM28XX=m
+CONFIG_VIDEO_EM28XX_V4L2=m
+CONFIG_VIDEO_EM28XX_ALSA=m
+CONFIG_VIDEO_EM28XX_DVB=m
+CONFIG_RADIO_SAA7706H=m
+CONFIG_RADIO_SHARK=m
+CONFIG_RADIO_SHARK2=m
+CONFIG_RADIO_SI4713=m
+CONFIG_RADIO_TEA5764=m
+CONFIG_RADIO_TEF6862=m
+CONFIG_RADIO_WL1273=m
+CONFIG_USB_DSBR=m
+CONFIG_USB_KEENE=m
+CONFIG_USB_MA901=m
+CONFIG_USB_MR800=m
+CONFIG_RADIO_SI470X=m
+CONFIG_USB_SI470X=m
+CONFIG_I2C_SI470X=m
+CONFIG_I2C_SI4713=m
+CONFIG_RADIO_WL128X=m
+CONFIG_V4L_PLATFORM_DRIVERS=y
+CONFIG_VIDEO_MUX=m
+CONFIG_VIDEO_BCM2835_UNICAM_LEGACY=m
+CONFIG_VIDEO_BCM2835_UNICAM=m
+CONFIG_VIDEO_ARDUCAM_64MP=m
+CONFIG_VIDEO_ARDUCAM_PIVARIETY=m
+CONFIG_VIDEO_IMX219=m
+CONFIG_VIDEO_IMX258=m
+CONFIG_VIDEO_IMX283=m
+CONFIG_VIDEO_IMX290=m
+CONFIG_VIDEO_IMX296=m
+CONFIG_VIDEO_IMX335=m
+CONFIG_VIDEO_IMX415=m
+CONFIG_VIDEO_IMX477=m
+CONFIG_VIDEO_IMX500=m
+CONFIG_VIDEO_IMX519=m
+CONFIG_VIDEO_IMX708=m
+CONFIG_VIDEO_MIRA220=m
+CONFIG_VIDEO_MT9V011=m
+CONFIG_VIDEO_OV2311=m
+CONFIG_VIDEO_OV5647=m
+CONFIG_VIDEO_OV64A40=m
+CONFIG_VIDEO_OV7251=m
+CONFIG_VIDEO_OV7640=m
+CONFIG_VIDEO_OV9282=m
+CONFIG_VIDEO_AD5398=m
+CONFIG_VIDEO_AK7375=m
+CONFIG_VIDEO_BU64754=m
+CONFIG_VIDEO_DW9807_VCM=m
+CONFIG_VIDEO_SONY_BTF_MPX=m
+CONFIG_VIDEO_UDA1342=m
+CONFIG_VIDEO_ADV7180=m
+CONFIG_VIDEO_TC358743=m
+CONFIG_VIDEO_TVP5150=m
+CONFIG_VIDEO_TW2804=m
+CONFIG_VIDEO_TW9903=m
+CONFIG_VIDEO_TW9906=m
+CONFIG_VIDEO_IRS1125=m
+CONFIG_VIDEO_I2C=m
+CONFIG_AUXDISPLAY=y
+CONFIG_HD44780=m
+CONFIG_DRM=m
+CONFIG_DRM_LOAD_EDID_FIRMWARE=y
+CONFIG_DRM_UDL=m
+CONFIG_DRM_PANEL_ILITEK_ILI9806E=m
+CONFIG_DRM_PANEL_ILITEK_ILI9806E_SPI=m
+CONFIG_DRM_PANEL_ILITEK_ILI9881C=m
+CONFIG_DRM_PANEL_JDI_LT070ME05000=m
+CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN=m
+CONFIG_DRM_PANEL_SITRONIX_ST7701=m
+CONFIG_DRM_PANEL_SIMPLE=m
+CONFIG_DRM_PANEL_TPO_Y17P=m
+CONFIG_DRM_PANEL_WAVESHARE_TOUCHSCREEN=m
+CONFIG_DRM_PANEL_WAVESHARE_TOUCHSCREEN_V2=m
+CONFIG_DRM_DISPLAY_CONNECTOR=m
+CONFIG_DRM_SIMPLE_BRIDGE=m
+CONFIG_DRM_TOSHIBA_TC358762=m
+CONFIG_DRM_VC4=m
+CONFIG_DRM_VC4_HDMI_CEC=y
+CONFIG_DRM_PANEL_MIPI_DBI=m
+CONFIG_TINYDRM_HX8357D=m
+CONFIG_TINYDRM_ILI9225=m
+CONFIG_TINYDRM_ILI9341=m
+CONFIG_TINYDRM_ILI9486=m
+CONFIG_TINYDRM_MI0283QT=m
+CONFIG_TINYDRM_REPAPER=m
+CONFIG_TINYDRM_ST7586=m
+CONFIG_TINYDRM_ST7735R=m
+CONFIG_DRM_GUD=m
+CONFIG_DRM_SSD130X=m
+CONFIG_DRM_SSD130X_I2C=m
+CONFIG_DRM_SSD130X_SPI=m
+CONFIG_FB=y
+CONFIG_FB_BCM2708=y
+CONFIG_FB_SIMPLE=y
+CONFIG_FB_SSD1307=m
+CONFIG_FB_RPISENSE=m
+CONFIG_BACKLIGHT_PWM=m
+CONFIG_BACKLIGHT_RPI=m
+CONFIG_BACKLIGHT_LM3630A=m
+CONFIG_BACKLIGHT_GPIO=m
+CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y
+CONFIG_LOGO=y
+# CONFIG_LOGO_LINUX_MONO is not set
+# CONFIG_LOGO_LINUX_VGA16 is not set
+CONFIG_SOUND=y
+CONFIG_SND=m
+CONFIG_SND_OSSEMUL=y
+CONFIG_SND_PCM_OSS=m
+CONFIG_SND_HRTIMER=m
+CONFIG_SND_DYNAMIC_MINORS=y
+CONFIG_SND_SEQUENCER=m
+CONFIG_SND_SEQ_DUMMY=m
+CONFIG_SND_DUMMY=m
+CONFIG_SND_ALOOP=m
+CONFIG_SND_VIRMIDI=m
+CONFIG_SND_MTPAV=m
+CONFIG_SND_SERIAL_U16550=m
+CONFIG_SND_MPU401=m
+CONFIG_SND_PIMIDI=m
+CONFIG_SND_PISOUND_MICRO=m
+CONFIG_SND_USB_AUDIO=m
+CONFIG_SND_USB_UA101=m
+CONFIG_SND_USB_CAIAQ=m
+CONFIG_SND_USB_CAIAQ_INPUT=y
+CONFIG_SND_USB_6FIRE=m
+CONFIG_SND_USB_HIFACE=m
+CONFIG_SND_USB_TONEPORT=m
+CONFIG_SND_SOC=m
+CONFIG_SND_BCM2835_SOC_I2S=m
+CONFIG_SND_BCM2708_SOC_CHIPDIP_DAC=m
+CONFIG_SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_ADC=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DAC=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUS=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DIGI=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_AMP=m
+CONFIG_SND_BCM2708_SOC_PIFI_40=m
+CONFIG_SND_BCM2708_SOC_RPI_CIRRUS=m
+CONFIG_SND_BCM2708_SOC_RPI_DAC=m
+CONFIG_SND_BCM2708_SOC_RPI_PROTO=m
+CONFIG_SND_BCM2708_SOC_JUSTBOOM_BOTH=m
+CONFIG_SND_BCM2708_SOC_JUSTBOOM_DAC=m
+CONFIG_SND_BCM2708_SOC_JUSTBOOM_DIGI=m
+CONFIG_SND_BCM2708_SOC_IQAUDIO_CODEC=m
+CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC=m
+CONFIG_SND_BCM2708_SOC_IQAUDIO_DIGI=m
+CONFIG_SND_BCM2708_SOC_I_SABRE_Q2M=m
+CONFIG_SND_BCM2708_SOC_ADAU1977_ADC=m
+CONFIG_SND_AUDIOINJECTOR_PI_SOUNDCARD=m
+CONFIG_SND_AUDIOINJECTOR_OCTO_SOUNDCARD=m
+CONFIG_SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD=m
+CONFIG_SND_AUDIOSENSE_PI=m
+CONFIG_SND_DIGIDAC1_SOUNDCARD=m
+CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO=m
+CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO_V2=m
+CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC=m
+CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS=m
+CONFIG_SND_BCM2708_SOC_ALLO_BOSS_DAC=m
+CONFIG_SND_BCM2708_SOC_ALLO_BOSS2_DAC=m
+CONFIG_SND_BCM2708_SOC_ALLO_DIGIONE=m
+CONFIG_SND_BCM2708_SOC_ALLO_KATANA_DAC=m
+CONFIG_SND_BCM2708_SOC_FE_PI_AUDIO=m
+CONFIG_SND_PISOUND=m
+CONFIG_SND_DACBERRY400=m
+CONFIG_SND_SOC_AD193X_SPI=m
+CONFIG_SND_SOC_AD193X_I2C=m
+CONFIG_SND_SOC_ADAU1701=m
+CONFIG_SND_SOC_ADAU7002=m
+CONFIG_SND_SOC_AK4554=m
+CONFIG_SND_SOC_CS4265=m
+CONFIG_SND_SOC_ICS43432=m
+CONFIG_SND_SOC_MA120X0P=m
+CONFIG_SND_SOC_MAX98357A=m
+CONFIG_SND_SOC_SPDIF=m
+CONFIG_SND_SOC_TLV320AIC23_I2C=m
+CONFIG_SND_SOC_WM8804_I2C=m
+CONFIG_SND_SOC_WM8904=m
+CONFIG_SND_SOC_WM8960=m
+CONFIG_SND_SIMPLE_CARD=m
+CONFIG_HID_BATTERY_STRENGTH=y
+CONFIG_HIDRAW=y
+CONFIG_UHID=m
+CONFIG_HID_A4TECH=m
+CONFIG_HID_ACRUX=m
+CONFIG_HID_APPLE=m
+CONFIG_HID_ASUS=m
+CONFIG_HID_BELKIN=m
+CONFIG_HID_BETOP_FF=m
+CONFIG_HID_BIGBEN_FF=m
+CONFIG_HID_CHERRY=m
+CONFIG_HID_CHICONY=m
+CONFIG_HID_CP2112=m
+CONFIG_HID_CYPRESS=m
+CONFIG_HID_DRAGONRISE=m
+CONFIG_HID_EMS_FF=m
+CONFIG_HID_ELECOM=m
+CONFIG_HID_ELO=m
+CONFIG_HID_EZKEY=m
+CONFIG_HID_GEMBIRD=m
+CONFIG_HID_HOLTEK=m
+CONFIG_HID_KEYTOUCH=m
+CONFIG_HID_KYE=m
+CONFIG_HID_UCLOGIC=m
+CONFIG_HID_WALTOP=m
+CONFIG_HID_GYRATION=m
+CONFIG_HID_TWINHAN=m
+CONFIG_HID_KENSINGTON=m
+CONFIG_HID_LCPOWER=m
+CONFIG_HID_LOGITECH=m
+CONFIG_HID_LOGITECH_DJ=m
+CONFIG_LOGITECH_FF=y
+CONFIG_LOGIRUMBLEPAD2_FF=y
+CONFIG_LOGIG940_FF=y
+CONFIG_HID_MAGICMOUSE=m
+CONFIG_HID_MICROSOFT=m
+CONFIG_HID_MONTEREY=m
+CONFIG_HID_MULTITOUCH=m
+CONFIG_HID_NINTENDO=m
+CONFIG_NINTENDO_FF=y
+CONFIG_HID_NTRIG=m
+CONFIG_HID_ORTEK=m
+CONFIG_HID_PANTHERLORD=m
+CONFIG_HID_PETALYNX=m
+CONFIG_HID_PICOLCD=m
+CONFIG_HID_PLAYSTATION=m
+CONFIG_PLAYSTATION_FF=y
+CONFIG_HID_ROCCAT=m
+CONFIG_HID_SAMSUNG=m
+CONFIG_HID_SONY=m
+CONFIG_SONY_FF=y
+CONFIG_HID_SPEEDLINK=m
+CONFIG_HID_STEAM=m
+CONFIG_HID_SUNPLUS=m
+CONFIG_HID_GREENASIA=m
+CONFIG_HID_SMARTJOYPLUS=m
+CONFIG_HID_TOPSEED=m
+CONFIG_HID_THINGM=m
+CONFIG_HID_THRUSTMASTER=m
+CONFIG_HID_WACOM=m
+CONFIG_HID_WIIMOTE=m
+CONFIG_HID_XINMO=m
+CONFIG_HID_ZEROPLUS=m
+CONFIG_HID_ZYDACRON=m
+CONFIG_HID_PID=y
+CONFIG_USB_HIDDEV=y
+CONFIG_I2C_HID_OF=m
+CONFIG_USB=y
+CONFIG_USB_ANNOUNCE_NEW_DEVICES=y
+CONFIG_USB_MON=m
+CONFIG_USB_DWCOTG=y
+CONFIG_USB_PRINTER=m
+CONFIG_USB_TMC=m
+CONFIG_USB_STORAGE=y
+CONFIG_USB_STORAGE_REALTEK=m
+CONFIG_USB_STORAGE_DATAFAB=m
+CONFIG_USB_STORAGE_FREECOM=m
+CONFIG_USB_STORAGE_ISD200=m
+CONFIG_USB_STORAGE_USBAT=m
+CONFIG_USB_STORAGE_SDDR09=m
+CONFIG_USB_STORAGE_SDDR55=m
+CONFIG_USB_STORAGE_JUMPSHOT=m
+CONFIG_USB_STORAGE_ALAUDA=m
+CONFIG_USB_STORAGE_ONETOUCH=m
+CONFIG_USB_STORAGE_KARMA=m
+CONFIG_USB_STORAGE_CYPRESS_ATACB=m
+CONFIG_USB_STORAGE_ENE_UB6250=m
+CONFIG_USB_UAS=m
+CONFIG_USB_MDC800=m
+CONFIG_USB_MICROTEK=m
+CONFIG_USBIP_CORE=m
+CONFIG_USBIP_VHCI_HCD=m
+CONFIG_USBIP_HOST=m
+CONFIG_USBIP_VUDC=m
+CONFIG_USB_DWC2=m
+CONFIG_USB_SERIAL=m
+CONFIG_USB_SERIAL_GENERIC=y
+CONFIG_USB_SERIAL_SIMPLE=m
+CONFIG_USB_SERIAL_AIRCABLE=m
+CONFIG_USB_SERIAL_ARK3116=m
+CONFIG_USB_SERIAL_BELKIN=m
+CONFIG_USB_SERIAL_CH341=m
+CONFIG_USB_SERIAL_WHITEHEAT=m
+CONFIG_USB_SERIAL_DIGI_ACCELEPORT=m
+CONFIG_USB_SERIAL_CP210X=m
+CONFIG_USB_SERIAL_CYPRESS_M8=m
+CONFIG_USB_SERIAL_EMPEG=m
+CONFIG_USB_SERIAL_FTDI_SIO=m
+CONFIG_USB_SERIAL_VISOR=m
+CONFIG_USB_SERIAL_IPAQ=m
+CONFIG_USB_SERIAL_IR=m
+CONFIG_USB_SERIAL_EDGEPORT=m
+CONFIG_USB_SERIAL_EDGEPORT_TI=m
+CONFIG_USB_SERIAL_F81232=m
+CONFIG_USB_SERIAL_F8153X=m
+CONFIG_USB_SERIAL_GARMIN=m
+CONFIG_USB_SERIAL_IPW=m
+CONFIG_USB_SERIAL_IUU=m
+CONFIG_USB_SERIAL_KEYSPAN_PDA=m
+CONFIG_USB_SERIAL_KEYSPAN=m
+CONFIG_USB_SERIAL_KLSI=m
+CONFIG_USB_SERIAL_KOBIL_SCT=m
+CONFIG_USB_SERIAL_MCT_U232=m
+CONFIG_USB_SERIAL_METRO=m
+CONFIG_USB_SERIAL_MOS7720=m
+CONFIG_USB_SERIAL_MOS7840=m
+CONFIG_USB_SERIAL_MXUPORT=m
+CONFIG_USB_SERIAL_NAVMAN=m
+CONFIG_USB_SERIAL_PL2303=m
+CONFIG_USB_SERIAL_OTI6858=m
+CONFIG_USB_SERIAL_QCAUX=m
+CONFIG_USB_SERIAL_QUALCOMM=m
+CONFIG_USB_SERIAL_SPCP8X5=m
+CONFIG_USB_SERIAL_SAFE=m
+CONFIG_USB_SERIAL_SIERRAWIRELESS=m
+CONFIG_USB_SERIAL_SYMBOL=m
+CONFIG_USB_SERIAL_TI=m
+CONFIG_USB_SERIAL_CYBERJACK=m
+CONFIG_USB_SERIAL_OPTION=m
+CONFIG_USB_SERIAL_OMNINET=m
+CONFIG_USB_SERIAL_OPTICON=m
+CONFIG_USB_SERIAL_XSENS_MT=m
+CONFIG_USB_SERIAL_WISHBONE=m
+CONFIG_USB_SERIAL_SSU100=m
+CONFIG_USB_SERIAL_QT2=m
+CONFIG_USB_SERIAL_UPD78F0730=m
+CONFIG_USB_SERIAL_XR=m
+CONFIG_USB_SERIAL_DEBUG=m
+CONFIG_USB_EMI62=m
+CONFIG_USB_EMI26=m
+CONFIG_USB_ADUTUX=m
+CONFIG_USB_SEVSEG=m
+CONFIG_USB_LEGOTOWER=m
+CONFIG_USB_LCD=m
+CONFIG_USB_CYPRESS_CY7C63=m
+CONFIG_USB_CYTHERM=m
+CONFIG_USB_IDMOUSE=m
+CONFIG_USB_APPLEDISPLAY=m
+CONFIG_USB_LD=m
+CONFIG_USB_TRANCEVIBRATOR=m
+CONFIG_USB_IOWARRIOR=m
+CONFIG_USB_TEST=m
+CONFIG_USB_ISIGHTFW=m
+CONFIG_USB_YUREX=m
+CONFIG_USB_ATM=m
+CONFIG_USB_SPEEDTOUCH=m
+CONFIG_USB_CXACRU=m
+CONFIG_USB_UEAGLEATM=m
+CONFIG_USB_XUSBATM=m
+CONFIG_NOP_USB_XCEIV=y
+CONFIG_USB_GADGET=y
+CONFIG_USB_CONFIGFS=m
+CONFIG_USB_CONFIGFS_SERIAL=y
+CONFIG_USB_CONFIGFS_ACM=y
+CONFIG_USB_CONFIGFS_OBEX=y
+CONFIG_USB_CONFIGFS_NCM=y
+CONFIG_USB_CONFIGFS_ECM=y
+CONFIG_USB_CONFIGFS_ECM_SUBSET=y
+CONFIG_USB_CONFIGFS_RNDIS=y
+CONFIG_USB_CONFIGFS_EEM=y
+CONFIG_USB_CONFIGFS_MASS_STORAGE=y
+CONFIG_USB_CONFIGFS_F_LB_SS=y
+CONFIG_USB_CONFIGFS_F_FS=y
+CONFIG_USB_CONFIGFS_F_UAC1=y
+CONFIG_USB_CONFIGFS_F_UAC2=y
+CONFIG_USB_CONFIGFS_F_MIDI=y
+CONFIG_USB_CONFIGFS_F_HID=y
+CONFIG_USB_CONFIGFS_F_UVC=y
+CONFIG_USB_CONFIGFS_F_PRINTER=y
+CONFIG_USB_ZERO=m
+CONFIG_USB_AUDIO=m
+CONFIG_USB_ETH=m
+CONFIG_USB_GADGETFS=m
+CONFIG_USB_MASS_STORAGE=m
+CONFIG_USB_G_SERIAL=m
+CONFIG_USB_MIDI_GADGET=m
+CONFIG_USB_G_PRINTER=m
+CONFIG_USB_CDC_COMPOSITE=m
+CONFIG_USB_G_ACM_MS=m
+CONFIG_USB_G_MULTI=m
+CONFIG_USB_G_HID=m
+CONFIG_USB_G_WEBCAM=m
+CONFIG_MMC=y
+CONFIG_MMC_BLOCK_MINORS=32
+CONFIG_MMC_BCM2835_MMC=y
+CONFIG_MMC_BCM2835_DMA=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_MMC_SPI=m
+CONFIG_MMC_HSQ=y
+CONFIG_MMC_BCM2835=y
+CONFIG_LEDS_CLASS=y
+CONFIG_LEDS_CLASS_MULTICOLOR=m
+CONFIG_LEDS_PCA9532=m
+CONFIG_LEDS_GPIO=y
+CONFIG_LEDS_PCA955X=m
+CONFIG_LEDS_PCA963X=m
+CONFIG_LEDS_PWM=y
+CONFIG_LEDS_IS31FL32XX=m
+CONFIG_LEDS_TRIGGER_TIMER=y
+CONFIG_LEDS_TRIGGER_ONESHOT=y
+CONFIG_LEDS_TRIGGER_HEARTBEAT=y
+CONFIG_LEDS_TRIGGER_BACKLIGHT=y
+CONFIG_LEDS_TRIGGER_CPU=y
+CONFIG_LEDS_TRIGGER_DEFAULT_ON=y
+CONFIG_LEDS_TRIGGER_TRANSIENT=m
+CONFIG_LEDS_TRIGGER_CAMERA=m
+CONFIG_LEDS_TRIGGER_INPUT=y
+CONFIG_LEDS_TRIGGER_PANIC=y
+CONFIG_LEDS_TRIGGER_NETDEV=m
+CONFIG_LEDS_TRIGGER_PATTERN=m
+CONFIG_LEDS_TRIGGER_ACTPWR=y
+CONFIG_ACCESSIBILITY=y
+CONFIG_SPEAKUP=m
+CONFIG_SPEAKUP_SYNTH_SOFT=m
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_DRV_ABX80X=m
+CONFIG_RTC_DRV_DS1307=m
+CONFIG_RTC_DRV_DS1374=m
+CONFIG_RTC_DRV_DS1672=m
+CONFIG_RTC_DRV_MAX6900=m
+CONFIG_RTC_DRV_RS5C372=m
+CONFIG_RTC_DRV_ISL1208=m
+CONFIG_RTC_DRV_ISL12022=m
+CONFIG_RTC_DRV_X1205=m
+CONFIG_RTC_DRV_PCF8523=m
+CONFIG_RTC_DRV_PCF85063=m
+CONFIG_RTC_DRV_PCF85363=m
+CONFIG_RTC_DRV_PCF8563=m
+CONFIG_RTC_DRV_PCF8583=m
+CONFIG_RTC_DRV_M41T80=m
+CONFIG_RTC_DRV_BQ32K=m
+CONFIG_RTC_DRV_S35390A=m
+CONFIG_RTC_DRV_FM3130=m
+CONFIG_RTC_DRV_RX8581=m
+CONFIG_RTC_DRV_RX8025=m
+CONFIG_RTC_DRV_EM3027=m
+CONFIG_RTC_DRV_RV3028=m
+CONFIG_RTC_DRV_RV3032=m
+CONFIG_RTC_DRV_RV8803=m
+CONFIG_RTC_DRV_SD3078=m
+CONFIG_RTC_DRV_M41T93=m
+CONFIG_RTC_DRV_M41T94=m
+CONFIG_RTC_DRV_DS1302=m
+CONFIG_RTC_DRV_DS1305=m
+CONFIG_RTC_DRV_DS1390=m
+CONFIG_RTC_DRV_R9701=m
+CONFIG_RTC_DRV_RX4581=m
+CONFIG_RTC_DRV_RS5C348=m
+CONFIG_RTC_DRV_MAX6902=m
+CONFIG_RTC_DRV_PCF2123=m
+CONFIG_RTC_DRV_DS3232=m
+CONFIG_RTC_DRV_PCF2127=m
+CONFIG_RTC_DRV_RV3029C2=m
+CONFIG_DMADEVICES=y
+CONFIG_DMA_BCM2835=y
+CONFIG_DMA_BCM2708=y
+CONFIG_DMABUF_HEAPS=y
+CONFIG_DMABUF_HEAPS_SYSTEM=y
+CONFIG_DMABUF_HEAPS_CMA=y
+CONFIG_UIO=m
+CONFIG_UIO_PDRV_GENIRQ=m
+CONFIG_STAGING=y
+CONFIG_VT6656=m
+CONFIG_STAGING_MEDIA=y
+CONFIG_STAGING_MEDIA_DEPRECATED=y
+CONFIG_FB_TFT=m
+CONFIG_FB_TFT_AGM1264K_FL=m
+CONFIG_FB_TFT_BD663474=m
+CONFIG_FB_TFT_HX8340BN=m
+CONFIG_FB_TFT_HX8347D=m
+CONFIG_FB_TFT_HX8353D=m
+CONFIG_FB_TFT_HX8357D=m
+CONFIG_FB_TFT_ILI9163=m
+CONFIG_FB_TFT_ILI9320=m
+CONFIG_FB_TFT_ILI9325=m
+CONFIG_FB_TFT_ILI9340=m
+CONFIG_FB_TFT_ILI9341=m
+CONFIG_FB_TFT_ILI9481=m
+CONFIG_FB_TFT_ILI9486=m
+CONFIG_FB_TFT_PCD8544=m
+CONFIG_FB_TFT_RA8875=m
+CONFIG_FB_TFT_S6D02A1=m
+CONFIG_FB_TFT_S6D1121=m
+CONFIG_FB_TFT_SH1106=m
+CONFIG_FB_TFT_SSD1289=m
+CONFIG_FB_TFT_SSD1306=m
+CONFIG_FB_TFT_SSD1331=m
+CONFIG_FB_TFT_SSD1351=m
+CONFIG_FB_TFT_ST7735R=m
+CONFIG_FB_TFT_ST7789V=m
+CONFIG_FB_TFT_TINYLCD=m
+CONFIG_FB_TFT_TLS8204=m
+CONFIG_FB_TFT_UC1611=m
+CONFIG_FB_TFT_UC1701=m
+CONFIG_FB_TFT_UPD161704=m
+CONFIG_BCM2835_VCHIQ=y
+CONFIG_SND_BCM2835=m
+CONFIG_VIDEO_BCM2835=m
+CONFIG_VIDEO_CODEC_BCM2835=m
+CONFIG_VIDEO_ISP_BCM2835=m
+CONFIG_COMMON_CLK_SI5351=m
+CONFIG_CLK_RASPBERRYPI=y
+CONFIG_MAILBOX=y
+CONFIG_BCM2835_MBOX=y
+# CONFIG_IOMMU_SUPPORT is not set
+CONFIG_RASPBERRYPI_POWER=y
+CONFIG_IIO=m
+CONFIG_IIO_BUFFER_CB=m
+CONFIG_IIO_SW_TRIGGER=m
+CONFIG_ADXL355_I2C=m
+CONFIG_AD799X=m
+CONFIG_MCP320X=m
+CONFIG_MCP3422=m
+CONFIG_TI_ADS1015=m
+CONFIG_BME680=m
+CONFIG_CCS811=m
+CONFIG_SCD4X=m
+CONFIG_SENSIRION_SGP30=m
+CONFIG_SPS30_I2C=m
+CONFIG_MAX30102=m
+CONFIG_DHT11=m
+CONFIG_HDC100X=m
+CONFIG_HDC3020=m
+CONFIG_HTS221=m
+CONFIG_HTU21=m
+CONFIG_SI7020=m
+CONFIG_BOSCH_BNO055_I2C=m
+CONFIG_INV_MPU6050_I2C=m
+CONFIG_APDS9960=m
+CONFIG_AS73211=m
+CONFIG_BH1750=m
+CONFIG_TCS3472=m
+CONFIG_TSL4531=m
+CONFIG_VEML6030=m
+CONFIG_VEML6040=m
+CONFIG_VEML6070=m
+CONFIG_VEML6075=m
+CONFIG_IIO_HRTIMER_TRIGGER=m
+CONFIG_IIO_INTERRUPT_TRIGGER=m
+CONFIG_IIO_SYSFS_TRIGGER=m
+CONFIG_BMP280=m
+CONFIG_MS5637=m
+CONFIG_MAXIM_THERMOCOUPLE=m
+CONFIG_TMP117=m
+CONFIG_MAX31856=m
+CONFIG_PWM=y
+CONFIG_PWM_BCM2835=m
+CONFIG_PWM_GPIO=m
+CONFIG_PWM_PCA9685=m
+CONFIG_PWM_RASPBERRYPI_POE=m
+CONFIG_RPI_AXIPERF=m
+CONFIG_MUX_GPIO=m
+CONFIG_EXT4_FS=y
+CONFIG_EXT4_FS_POSIX_ACL=y
+CONFIG_EXT4_FS_SECURITY=y
+CONFIG_REISERFS_FS=m
+CONFIG_REISERFS_FS_XATTR=y
+CONFIG_REISERFS_FS_POSIX_ACL=y
+CONFIG_REISERFS_FS_SECURITY=y
+CONFIG_JFS_FS=m
+CONFIG_JFS_POSIX_ACL=y
+CONFIG_JFS_SECURITY=y
+CONFIG_JFS_STATISTICS=y
+CONFIG_XFS_FS=m
+CONFIG_XFS_QUOTA=y
+CONFIG_XFS_POSIX_ACL=y
+CONFIG_XFS_RT=y
+CONFIG_GFS2_FS=m
+CONFIG_OCFS2_FS=m
+CONFIG_BTRFS_FS=m
+CONFIG_BTRFS_FS_POSIX_ACL=y
+CONFIG_NILFS2_FS=m
+CONFIG_F2FS_FS=y
+CONFIG_F2FS_FS_SECURITY=y
+CONFIG_BCACHEFS_FS=m
+CONFIG_BCACHEFS_QUOTA=y
+CONFIG_BCACHEFS_POSIX_ACL=y
+CONFIG_FS_ENCRYPTION=y
+CONFIG_FANOTIFY=y
+CONFIG_QFMT_V1=m
+CONFIG_QFMT_V2=m
+CONFIG_AUTOFS_FS=y
+CONFIG_FUSE_FS=m
+CONFIG_CUSE=m
+CONFIG_OVERLAY_FS=m
+CONFIG_FSCACHE=y
+CONFIG_FSCACHE_STATS=y
+CONFIG_CACHEFILES=m
+CONFIG_ISO9660_FS=m
+CONFIG_JOLIET=y
+CONFIG_ZISOFS=y
+CONFIG_UDF_FS=m
+CONFIG_MSDOS_FS=y
+CONFIG_VFAT_FS=y
+CONFIG_FAT_DEFAULT_IOCHARSET="ascii"
+CONFIG_EXFAT_FS=m
+CONFIG_NTFS3_FS=m
+CONFIG_TMPFS=y
+CONFIG_TMPFS_POSIX_ACL=y
+CONFIG_ECRYPT_FS=m
+CONFIG_HFS_FS=m
+CONFIG_HFSPLUS_FS=m
+CONFIG_JFFS2_FS=m
+CONFIG_JFFS2_SUMMARY=y
+CONFIG_UBIFS_FS=m
+CONFIG_SQUASHFS=m
+CONFIG_SQUASHFS_XATTR=y
+CONFIG_SQUASHFS_LZO=y
+CONFIG_SQUASHFS_XZ=y
+CONFIG_SQUASHFS_ZSTD=y
+CONFIG_PSTORE=y
+CONFIG_PSTORE_CONSOLE=y
+CONFIG_PSTORE_RAM=y
+CONFIG_EROFS_FS=m
+CONFIG_EROFS_FS_ZIP_ZSTD=y
+CONFIG_NFS_FS=y
+CONFIG_NFS_V2=y
+CONFIG_NFS_V3_ACL=y
+CONFIG_NFS_V4=y
+CONFIG_NFS_SWAP=y
+CONFIG_NFS_V4_1=y
+CONFIG_NFS_V4_2=y
+CONFIG_ROOT_NFS=y
+CONFIG_NFSD=m
+CONFIG_NFSD_V2=y
+CONFIG_NFSD_V2_ACL=y
+CONFIG_NFSD_V3_ACL=y
+CONFIG_NFSD_V4=y
+CONFIG_CEPH_FS=m
+CONFIG_CIFS=m
+CONFIG_CIFS_UPCALL=y
+CONFIG_CIFS_XATTR=y
+CONFIG_CIFS_DFS_UPCALL=y
+CONFIG_CIFS_FSCACHE=y
+CONFIG_SMB_SERVER=m
+CONFIG_9P_FS=m
+CONFIG_9P_FS_POSIX_ACL=y
+CONFIG_NLS_DEFAULT="utf8"
+CONFIG_NLS_CODEPAGE_437=y
+CONFIG_NLS_CODEPAGE_737=m
+CONFIG_NLS_CODEPAGE_775=m
+CONFIG_NLS_CODEPAGE_850=m
+CONFIG_NLS_CODEPAGE_852=m
+CONFIG_NLS_CODEPAGE_855=m
+CONFIG_NLS_CODEPAGE_857=m
+CONFIG_NLS_CODEPAGE_860=m
+CONFIG_NLS_CODEPAGE_861=m
+CONFIG_NLS_CODEPAGE_862=m
+CONFIG_NLS_CODEPAGE_863=m
+CONFIG_NLS_CODEPAGE_864=m
+CONFIG_NLS_CODEPAGE_865=m
+CONFIG_NLS_CODEPAGE_866=m
+CONFIG_NLS_CODEPAGE_869=m
+CONFIG_NLS_CODEPAGE_936=m
+CONFIG_NLS_CODEPAGE_950=m
+CONFIG_NLS_CODEPAGE_932=m
+CONFIG_NLS_CODEPAGE_949=m
+CONFIG_NLS_CODEPAGE_874=m
+CONFIG_NLS_ISO8859_8=m
+CONFIG_NLS_CODEPAGE_1250=m
+CONFIG_NLS_CODEPAGE_1251=m
+CONFIG_NLS_ASCII=y
+CONFIG_NLS_ISO8859_1=m
+CONFIG_NLS_ISO8859_2=m
+CONFIG_NLS_ISO8859_3=m
+CONFIG_NLS_ISO8859_4=m
+CONFIG_NLS_ISO8859_5=m
+CONFIG_NLS_ISO8859_6=m
+CONFIG_NLS_ISO8859_7=m
+CONFIG_NLS_ISO8859_9=m
+CONFIG_NLS_ISO8859_13=m
+CONFIG_NLS_ISO8859_14=m
+CONFIG_NLS_ISO8859_15=m
+CONFIG_NLS_KOI8_R=m
+CONFIG_NLS_KOI8_U=m
+CONFIG_DLM=m
+CONFIG_UNICODE=m
+CONFIG_KEY_DH_OPERATIONS=y
+CONFIG_SECURITY=y
+CONFIG_SECURITY_APPARMOR=y
+CONFIG_LSM=""
+CONFIG_CRYPTO_USER=m
+CONFIG_CRYPTO_CAST5=m
+CONFIG_CRYPTO_DES=y
+CONFIG_CRYPTO_TWOFISH=m
+CONFIG_CRYPTO_ADIANTUM=m
+CONFIG_CRYPTO_CHACHA20POLY1305=m
+CONFIG_CRYPTO_MD4=m
+CONFIG_CRYPTO_SHA512=m
+CONFIG_CRYPTO_WP512=m
+CONFIG_CRYPTO_XCBC=m
+CONFIG_CRYPTO_LZ4=m
+CONFIG_CRYPTO_LZ4HC=m
+CONFIG_CRYPTO_USER_API_HASH=m
+CONFIG_CRYPTO_USER_API_SKCIPHER=m
+CONFIG_CRYPTO_USER_API_RNG=m
+CONFIG_CRYPTO_USER_API_AEAD=m
+CONFIG_CRYPTO_NHPOLY1305_NEON=m
+CONFIG_CRYPTO_SHA1_ARM_NEON=m
+CONFIG_CRYPTO_AES_ARM_BS=m
+# CONFIG_CRYPTO_HW is not set
+CONFIG_PKCS8_PRIVATE_KEY_PARSER=m
+CONFIG_CRC_ITU_T=y
+CONFIG_LIBCRC32C=y
+CONFIG_DMA_CMA=y
+CONFIG_CMA_SIZE_MBYTES=5
+CONFIG_PRINTK_TIME=y
+CONFIG_BOOT_PRINTK_DELAY=y
+CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1f6
+CONFIG_KGDB=y
+CONFIG_KGDB_KDB=y
+CONFIG_KDB_KEYBOARD=y
+CONFIG_DEBUG_MEMORY_INIT=y
+CONFIG_DETECT_HUNG_TASK=y
+# CONFIG_RCU_TRACE is not set
+CONFIG_LATENCYTOP=y
+CONFIG_FUNCTION_PROFILER=y
+CONFIG_STACK_TRACER=y
+CONFIG_SCHED_TRACER=y
+CONFIG_FTRACE_SYSCALLS=y
+CONFIG_BLK_DEV_IO_TRACE=y
+# CONFIG_UPROBE_EVENTS is not set
diff --git a/arch/arm/configs/bcm2711_defconfig b/arch/arm/configs/bcm2711_defconfig
new file mode 100644
index 000000000000..c5515d519c17
--- /dev/null
+++ b/arch/arm/configs/bcm2711_defconfig
@@ -0,0 +1,1696 @@
+CONFIG_LOCALVERSION="-v7l"
+# CONFIG_LOCALVERSION_AUTO is not set
+CONFIG_SYSVIPC=y
+CONFIG_POSIX_MQUEUE=y
+CONFIG_GENERIC_IRQ_DEBUGFS=y
+CONFIG_NO_HZ=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_BPF_SYSCALL=y
+CONFIG_PREEMPT_VOLUNTARY=y
+CONFIG_BSD_PROCESS_ACCT=y
+CONFIG_BSD_PROCESS_ACCT_V3=y
+CONFIG_TASKSTATS=y
+CONFIG_TASK_DELAY_ACCT=y
+CONFIG_TASK_XACCT=y
+CONFIG_TASK_IO_ACCOUNTING=y
+CONFIG_PSI=y
+CONFIG_PSI_DEFAULT_DISABLED=y
+CONFIG_IKCONFIG=m
+CONFIG_IKCONFIG_PROC=y
+CONFIG_MEMCG=y
+CONFIG_BLK_CGROUP=y
+CONFIG_CFS_BANDWIDTH=y
+CONFIG_CGROUP_PIDS=y
+CONFIG_CGROUP_FREEZER=y
+CONFIG_CPUSETS=y
+CONFIG_CPUSETS_V1=y
+CONFIG_CGROUP_DEVICE=y
+CONFIG_CGROUP_CPUACCT=y
+CONFIG_CGROUP_PERF=y
+CONFIG_CGROUP_BPF=y
+CONFIG_NAMESPACES=y
+CONFIG_USER_NS=y
+CONFIG_SCHED_AUTOGROUP=y
+CONFIG_BLK_DEV_INITRD=y
+CONFIG_EXPERT=y
+CONFIG_PROFILING=y
+CONFIG_ARCH_BCM=y
+CONFIG_ARCH_BCM2835=y
+CONFIG_ARM_LPAE=y
+# CONFIG_CACHE_L2X0 is not set
+CONFIG_SMP=y
+CONFIG_HIGHMEM=y
+CONFIG_UACCESS_WITH_MEMCPY=y
+# CONFIG_ATAGS is not set
+CONFIG_CMDLINE="console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait"
+CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_STAT=y
+CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y
+CONFIG_CPU_FREQ_GOV_POWERSAVE=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
+CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y
+CONFIG_CPUFREQ_DT=y
+CONFIG_ARM_RASPBERRYPI_CPUFREQ=y
+CONFIG_VFP=y
+CONFIG_NEON=y
+CONFIG_KERNEL_MODE_NEON=y
+# CONFIG_SUSPEND is not set
+CONFIG_PM=y
+CONFIG_JUMP_LABEL=y
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_MODVERSIONS=y
+CONFIG_MODULE_SRCVERSION_ALL=y
+CONFIG_MODULE_COMPRESS=y
+CONFIG_MODULE_COMPRESS_XZ=y
+CONFIG_BLK_DEV_THROTTLING=y
+CONFIG_PARTITION_ADVANCED=y
+CONFIG_MAC_PARTITION=y
+CONFIG_BINFMT_MISC=m
+CONFIG_ZSWAP=y
+# CONFIG_COMPAT_BRK is not set
+CONFIG_CMA=y
+CONFIG_CMA_AREAS=7
+CONFIG_LRU_GEN=y
+CONFIG_NET=y
+CONFIG_PACKET=y
+CONFIG_XFRM_USER=y
+CONFIG_XFRM_INTERFACE=m
+CONFIG_XFRM_SUB_POLICY=y
+CONFIG_XFRM_STATISTICS=y
+CONFIG_NET_KEY=m
+CONFIG_IP_MULTICAST=y
+CONFIG_IP_ADVANCED_ROUTER=y
+CONFIG_IP_MULTIPLE_TABLES=y
+CONFIG_IP_ROUTE_MULTIPATH=y
+CONFIG_IP_ROUTE_VERBOSE=y
+CONFIG_IP_PNP=y
+CONFIG_IP_PNP_DHCP=y
+CONFIG_IP_PNP_RARP=y
+CONFIG_NET_IPIP=m
+CONFIG_NET_IPGRE_DEMUX=m
+CONFIG_NET_IPGRE=m
+CONFIG_IP_MROUTE=y
+CONFIG_IP_MROUTE_MULTIPLE_TABLES=y
+CONFIG_IP_PIMSM_V1=y
+CONFIG_IP_PIMSM_V2=y
+CONFIG_NET_IPVTI=m
+CONFIG_NET_FOU_IP_TUNNELS=y
+CONFIG_INET_AH=m
+CONFIG_INET_ESP=m
+CONFIG_INET_IPCOMP=m
+CONFIG_INET_DIAG=m
+CONFIG_TCP_CONG_ADVANCED=y
+CONFIG_TCP_CONG_BBR=m
+CONFIG_IPV6=m
+CONFIG_IPV6_ROUTER_PREF=y
+CONFIG_IPV6_ROUTE_INFO=y
+CONFIG_INET6_AH=m
+CONFIG_INET6_ESP=m
+CONFIG_INET6_ESP_OFFLOAD=m
+CONFIG_INET6_IPCOMP=m
+CONFIG_IPV6_ILA=m
+CONFIG_IPV6_VTI=m
+CONFIG_IPV6_SIT_6RD=y
+CONFIG_IPV6_GRE=m
+CONFIG_IPV6_MULTIPLE_TABLES=y
+CONFIG_IPV6_SUBTREES=y
+CONFIG_IPV6_MROUTE=y
+CONFIG_IPV6_MROUTE_MULTIPLE_TABLES=y
+CONFIG_IPV6_PIMSM_V2=y
+CONFIG_NETWORK_PHY_TIMESTAMPING=y
+CONFIG_NETFILTER=y
+CONFIG_BRIDGE_NETFILTER=m
+CONFIG_NF_CONNTRACK=m
+CONFIG_NF_CONNTRACK_ZONES=y
+CONFIG_NF_CONNTRACK_EVENTS=y
+CONFIG_NF_CONNTRACK_TIMESTAMP=y
+CONFIG_NF_CONNTRACK_AMANDA=m
+CONFIG_NF_CONNTRACK_FTP=m
+CONFIG_NF_CONNTRACK_H323=m
+CONFIG_NF_CONNTRACK_IRC=m
+CONFIG_NF_CONNTRACK_NETBIOS_NS=m
+CONFIG_NF_CONNTRACK_SNMP=m
+CONFIG_NF_CONNTRACK_PPTP=m
+CONFIG_NF_CONNTRACK_SANE=m
+CONFIG_NF_CONNTRACK_SIP=m
+CONFIG_NF_CONNTRACK_TFTP=m
+CONFIG_NF_CT_NETLINK=m
+CONFIG_NF_TABLES=m
+CONFIG_NF_TABLES_INET=y
+CONFIG_NF_TABLES_NETDEV=y
+CONFIG_NFT_NUMGEN=m
+CONFIG_NFT_CT=m
+CONFIG_NFT_FLOW_OFFLOAD=m
+CONFIG_NFT_CONNLIMIT=m
+CONFIG_NFT_LOG=m
+CONFIG_NFT_LIMIT=m
+CONFIG_NFT_MASQ=m
+CONFIG_NFT_REDIR=m
+CONFIG_NFT_NAT=m
+CONFIG_NFT_TUNNEL=m
+CONFIG_NFT_QUEUE=m
+CONFIG_NFT_QUOTA=m
+CONFIG_NFT_REJECT=m
+CONFIG_NFT_COMPAT=m
+CONFIG_NFT_HASH=m
+CONFIG_NFT_FIB_INET=m
+CONFIG_NFT_XFRM=m
+CONFIG_NFT_SOCKET=m
+CONFIG_NFT_OSF=m
+CONFIG_NFT_TPROXY=m
+CONFIG_NFT_SYNPROXY=m
+CONFIG_NFT_DUP_NETDEV=m
+CONFIG_NFT_FWD_NETDEV=m
+CONFIG_NFT_FIB_NETDEV=m
+CONFIG_NF_FLOW_TABLE_INET=m
+CONFIG_NF_FLOW_TABLE=m
+CONFIG_NETFILTER_XT_SET=m
+CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m
+CONFIG_NETFILTER_XT_TARGET_CLASSIFY=m
+CONFIG_NETFILTER_XT_TARGET_CONNMARK=m
+CONFIG_NETFILTER_XT_TARGET_DSCP=m
+CONFIG_NETFILTER_XT_TARGET_HMARK=m
+CONFIG_NETFILTER_XT_TARGET_IDLETIMER=m
+CONFIG_NETFILTER_XT_TARGET_LED=m
+CONFIG_NETFILTER_XT_TARGET_LOG=m
+CONFIG_NETFILTER_XT_TARGET_MARK=m
+CONFIG_NETFILTER_XT_TARGET_NFLOG=m
+CONFIG_NETFILTER_XT_TARGET_NFQUEUE=m
+CONFIG_NETFILTER_XT_TARGET_NOTRACK=m
+CONFIG_NETFILTER_XT_TARGET_TEE=m
+CONFIG_NETFILTER_XT_TARGET_TPROXY=m
+CONFIG_NETFILTER_XT_TARGET_TRACE=m
+CONFIG_NETFILTER_XT_TARGET_TCPMSS=m
+CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=m
+CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m
+CONFIG_NETFILTER_XT_MATCH_BPF=m
+CONFIG_NETFILTER_XT_MATCH_CLUSTER=m
+CONFIG_NETFILTER_XT_MATCH_COMMENT=m
+CONFIG_NETFILTER_XT_MATCH_CONNBYTES=m
+CONFIG_NETFILTER_XT_MATCH_CONNLABEL=m
+CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=m
+CONFIG_NETFILTER_XT_MATCH_CONNMARK=m
+CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m
+CONFIG_NETFILTER_XT_MATCH_CPU=m
+CONFIG_NETFILTER_XT_MATCH_DCCP=m
+CONFIG_NETFILTER_XT_MATCH_DEVGROUP=m
+CONFIG_NETFILTER_XT_MATCH_DSCP=m
+CONFIG_NETFILTER_XT_MATCH_ESP=m
+CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=m
+CONFIG_NETFILTER_XT_MATCH_HELPER=m
+CONFIG_NETFILTER_XT_MATCH_IPRANGE=m
+CONFIG_NETFILTER_XT_MATCH_IPVS=m
+CONFIG_NETFILTER_XT_MATCH_LENGTH=m
+CONFIG_NETFILTER_XT_MATCH_LIMIT=m
+CONFIG_NETFILTER_XT_MATCH_MAC=m
+CONFIG_NETFILTER_XT_MATCH_MARK=m
+CONFIG_NETFILTER_XT_MATCH_MULTIPORT=m
+CONFIG_NETFILTER_XT_MATCH_NFACCT=m
+CONFIG_NETFILTER_XT_MATCH_OSF=m
+CONFIG_NETFILTER_XT_MATCH_OWNER=m
+CONFIG_NETFILTER_XT_MATCH_POLICY=m
+CONFIG_NETFILTER_XT_MATCH_PHYSDEV=m
+CONFIG_NETFILTER_XT_MATCH_PKTTYPE=m
+CONFIG_NETFILTER_XT_MATCH_QUOTA=m
+CONFIG_NETFILTER_XT_MATCH_RATEEST=m
+CONFIG_NETFILTER_XT_MATCH_REALM=m
+CONFIG_NETFILTER_XT_MATCH_RECENT=m
+CONFIG_NETFILTER_XT_MATCH_SOCKET=m
+CONFIG_NETFILTER_XT_MATCH_STATE=m
+CONFIG_NETFILTER_XT_MATCH_STATISTIC=m
+CONFIG_NETFILTER_XT_MATCH_STRING=m
+CONFIG_NETFILTER_XT_MATCH_TCPMSS=m
+CONFIG_NETFILTER_XT_MATCH_TIME=m
+CONFIG_NETFILTER_XT_MATCH_U32=m
+CONFIG_IP_SET=m
+CONFIG_IP_SET_BITMAP_IP=m
+CONFIG_IP_SET_BITMAP_IPMAC=m
+CONFIG_IP_SET_BITMAP_PORT=m
+CONFIG_IP_SET_HASH_IP=m
+CONFIG_IP_SET_HASH_IPPORT=m
+CONFIG_IP_SET_HASH_IPPORTIP=m
+CONFIG_IP_SET_HASH_IPPORTNET=m
+CONFIG_IP_SET_HASH_NET=m
+CONFIG_IP_SET_HASH_NETPORT=m
+CONFIG_IP_SET_HASH_NETIFACE=m
+CONFIG_IP_SET_LIST_SET=m
+CONFIG_IP_VS=m
+CONFIG_IP_VS_IPV6=y
+CONFIG_IP_VS_PROTO_TCP=y
+CONFIG_IP_VS_PROTO_UDP=y
+CONFIG_IP_VS_PROTO_ESP=y
+CONFIG_IP_VS_PROTO_AH=y
+CONFIG_IP_VS_PROTO_SCTP=y
+CONFIG_IP_VS_RR=m
+CONFIG_IP_VS_WRR=m
+CONFIG_IP_VS_LC=m
+CONFIG_IP_VS_WLC=m
+CONFIG_IP_VS_LBLC=m
+CONFIG_IP_VS_LBLCR=m
+CONFIG_IP_VS_DH=m
+CONFIG_IP_VS_SH=m
+CONFIG_IP_VS_SED=m
+CONFIG_IP_VS_NQ=m
+CONFIG_IP_VS_FTP=m
+CONFIG_IP_VS_PE_SIP=m
+CONFIG_NFT_DUP_IPV4=m
+CONFIG_NFT_FIB_IPV4=m
+CONFIG_NF_TABLES_ARP=y
+CONFIG_NF_LOG_ARP=m
+CONFIG_NF_LOG_IPV4=m
+CONFIG_IP_NF_IPTABLES=m
+CONFIG_IP_NF_MATCH_AH=m
+CONFIG_IP_NF_MATCH_ECN=m
+CONFIG_IP_NF_MATCH_RPFILTER=m
+CONFIG_IP_NF_MATCH_TTL=m
+CONFIG_IP_NF_FILTER=m
+CONFIG_IP_NF_TARGET_REJECT=m
+CONFIG_IP_NF_TARGET_SYNPROXY=m
+CONFIG_IP_NF_NAT=m
+CONFIG_IP_NF_TARGET_MASQUERADE=m
+CONFIG_IP_NF_TARGET_NETMAP=m
+CONFIG_IP_NF_TARGET_REDIRECT=m
+CONFIG_IP_NF_MANGLE=m
+CONFIG_IP_NF_TARGET_ECN=m
+CONFIG_IP_NF_TARGET_TTL=m
+CONFIG_IP_NF_RAW=m
+CONFIG_IP_NF_ARPFILTER=m
+CONFIG_IP_NF_ARP_MANGLE=m
+CONFIG_NFT_DUP_IPV6=m
+CONFIG_NFT_FIB_IPV6=m
+CONFIG_IP6_NF_IPTABLES=m
+CONFIG_IP6_NF_MATCH_AH=m
+CONFIG_IP6_NF_MATCH_EUI64=m
+CONFIG_IP6_NF_MATCH_FRAG=m
+CONFIG_IP6_NF_MATCH_OPTS=m
+CONFIG_IP6_NF_MATCH_HL=m
+CONFIG_IP6_NF_MATCH_IPV6HEADER=m
+CONFIG_IP6_NF_MATCH_MH=m
+CONFIG_IP6_NF_MATCH_RPFILTER=m
+CONFIG_IP6_NF_MATCH_RT=m
+CONFIG_IP6_NF_MATCH_SRH=m
+CONFIG_IP6_NF_TARGET_HL=m
+CONFIG_IP6_NF_FILTER=m
+CONFIG_IP6_NF_TARGET_REJECT=m
+CONFIG_IP6_NF_TARGET_SYNPROXY=m
+CONFIG_IP6_NF_MANGLE=m
+CONFIG_IP6_NF_RAW=m
+CONFIG_IP6_NF_SECURITY=m
+CONFIG_IP6_NF_NAT=m
+CONFIG_IP6_NF_TARGET_MASQUERADE=m
+CONFIG_IP6_NF_TARGET_NPT=m
+CONFIG_NF_TABLES_BRIDGE=m
+CONFIG_NFT_BRIDGE_REJECT=m
+CONFIG_BRIDGE_NF_EBTABLES=m
+CONFIG_BRIDGE_EBT_BROUTE=m
+CONFIG_BRIDGE_EBT_T_FILTER=m
+CONFIG_BRIDGE_EBT_T_NAT=m
+CONFIG_BRIDGE_EBT_802_3=m
+CONFIG_BRIDGE_EBT_AMONG=m
+CONFIG_BRIDGE_EBT_ARP=m
+CONFIG_BRIDGE_EBT_IP=m
+CONFIG_BRIDGE_EBT_IP6=m
+CONFIG_BRIDGE_EBT_LIMIT=m
+CONFIG_BRIDGE_EBT_MARK=m
+CONFIG_BRIDGE_EBT_PKTTYPE=m
+CONFIG_BRIDGE_EBT_STP=m
+CONFIG_BRIDGE_EBT_VLAN=m
+CONFIG_BRIDGE_EBT_ARPREPLY=m
+CONFIG_BRIDGE_EBT_DNAT=m
+CONFIG_BRIDGE_EBT_MARK_T=m
+CONFIG_BRIDGE_EBT_REDIRECT=m
+CONFIG_BRIDGE_EBT_SNAT=m
+CONFIG_BRIDGE_EBT_LOG=m
+CONFIG_BRIDGE_EBT_NFLOG=m
+CONFIG_SCTP_COOKIE_HMAC_SHA1=y
+CONFIG_ATM=m
+CONFIG_L2TP=m
+CONFIG_L2TP_V3=y
+CONFIG_L2TP_IP=m
+CONFIG_L2TP_ETH=m
+CONFIG_BRIDGE=m
+CONFIG_BRIDGE_VLAN_FILTERING=y
+CONFIG_VLAN_8021Q=m
+CONFIG_VLAN_8021Q_GVRP=y
+CONFIG_ATALK=m
+CONFIG_6LOWPAN=m
+CONFIG_IEEE802154=m
+CONFIG_IEEE802154_6LOWPAN=m
+CONFIG_MAC802154=m
+CONFIG_NET_SCHED=y
+CONFIG_NET_SCH_HTB=m
+CONFIG_NET_SCH_HFSC=m
+CONFIG_NET_SCH_PRIO=m
+CONFIG_NET_SCH_MULTIQ=m
+CONFIG_NET_SCH_RED=m
+CONFIG_NET_SCH_SFB=m
+CONFIG_NET_SCH_SFQ=m
+CONFIG_NET_SCH_TEQL=m
+CONFIG_NET_SCH_TBF=m
+CONFIG_NET_SCH_GRED=m
+CONFIG_NET_SCH_NETEM=m
+CONFIG_NET_SCH_DRR=m
+CONFIG_NET_SCH_MQPRIO=m
+CONFIG_NET_SCH_CHOKE=m
+CONFIG_NET_SCH_QFQ=m
+CONFIG_NET_SCH_CODEL=m
+CONFIG_NET_SCH_FQ_CODEL=m
+CONFIG_NET_SCH_CAKE=m
+CONFIG_NET_SCH_FQ=m
+CONFIG_NET_SCH_HHF=m
+CONFIG_NET_SCH_PIE=m
+CONFIG_NET_SCH_INGRESS=m
+CONFIG_NET_SCH_PLUG=m
+CONFIG_NET_CLS_BASIC=m
+CONFIG_NET_CLS_ROUTE4=m
+CONFIG_NET_CLS_FW=m
+CONFIG_NET_CLS_U32=m
+CONFIG_CLS_U32_PERF=y
+CONFIG_CLS_U32_MARK=y
+CONFIG_NET_CLS_FLOW=m
+CONFIG_NET_CLS_CGROUP=m
+CONFIG_NET_CLS_FLOWER=m
+CONFIG_NET_CLS_MATCHALL=m
+CONFIG_NET_EMATCH=y
+CONFIG_NET_EMATCH_CMP=m
+CONFIG_NET_EMATCH_NBYTE=m
+CONFIG_NET_EMATCH_U32=m
+CONFIG_NET_EMATCH_META=m
+CONFIG_NET_EMATCH_TEXT=m
+CONFIG_NET_EMATCH_IPSET=m
+CONFIG_NET_CLS_ACT=y
+CONFIG_NET_ACT_POLICE=m
+CONFIG_NET_ACT_GACT=m
+CONFIG_GACT_PROB=y
+CONFIG_NET_ACT_MIRRED=m
+CONFIG_NET_ACT_NAT=m
+CONFIG_NET_ACT_PEDIT=m
+CONFIG_NET_ACT_SIMP=m
+CONFIG_NET_ACT_SKBEDIT=m
+CONFIG_NET_ACT_CSUM=m
+CONFIG_BATMAN_ADV=m
+CONFIG_OPENVSWITCH=m
+CONFIG_CGROUP_NET_PRIO=y
+CONFIG_NET_PKTGEN=m
+CONFIG_HAMRADIO=y
+CONFIG_AX25=m
+CONFIG_NETROM=m
+CONFIG_ROSE=m
+CONFIG_MKISS=m
+CONFIG_6PACK=m
+CONFIG_BPQETHER=m
+CONFIG_BAYCOM_SER_FDX=m
+CONFIG_BAYCOM_SER_HDX=m
+CONFIG_YAM=m
+CONFIG_CAN=m
+CONFIG_CAN_J1939=m
+CONFIG_CAN_ISOTP=m
+CONFIG_BT=m
+CONFIG_BT_RFCOMM=m
+CONFIG_BT_RFCOMM_TTY=y
+CONFIG_BT_BNEP=m
+CONFIG_BT_BNEP_MC_FILTER=y
+CONFIG_BT_BNEP_PROTO_FILTER=y
+CONFIG_BT_HIDP=m
+CONFIG_BT_6LOWPAN=m
+CONFIG_BT_HCIBTUSB=m
+CONFIG_BT_HCIBTUSB_MTK=y
+CONFIG_BT_HCIUART=m
+CONFIG_BT_HCIUART_3WIRE=y
+CONFIG_BT_HCIUART_BCM=y
+CONFIG_BT_HCIBCM203X=m
+CONFIG_BT_HCIBPA10X=m
+CONFIG_BT_HCIBFUSB=m
+CONFIG_BT_HCIVHCI=m
+CONFIG_BT_MRVL=m
+CONFIG_BT_MRVL_SDIO=m
+CONFIG_BT_ATH3K=m
+CONFIG_CFG80211=m
+CONFIG_CFG80211_WEXT=y
+CONFIG_MAC80211=m
+CONFIG_MAC80211_MESH=y
+CONFIG_RFKILL=m
+CONFIG_RFKILL_INPUT=y
+CONFIG_NET_9P=m
+CONFIG_NFC=m
+CONFIG_PCI=y
+CONFIG_PCIEPORTBUS=y
+# CONFIG_PCIEASPM is not set
+CONFIG_PCI_MSI=y
+CONFIG_PCIE_BRCMSTB=y
+CONFIG_UEVENT_HELPER=y
+CONFIG_DEVTMPFS=y
+CONFIG_DEVTMPFS_MOUNT=y
+CONFIG_RASPBERRYPI_FIRMWARE=y
+CONFIG_MTD=m
+CONFIG_MTD_BLOCK=m
+CONFIG_MTD_BLOCK2MTD=m
+CONFIG_MTD_SPI_NAND=m
+CONFIG_MTD_SPI_NOR=m
+CONFIG_MTD_UBI=m
+CONFIG_OF_CONFIGFS=y
+CONFIG_ZRAM=m
+CONFIG_ZRAM_BACKEND_LZ4=y
+CONFIG_ZRAM_BACKEND_ZSTD=y
+CONFIG_ZRAM_BACKEND_LZO=y
+CONFIG_ZRAM_DEF_COMP_ZSTD=y
+CONFIG_ZRAM_WRITEBACK=y
+CONFIG_ZRAM_MULTI_COMP=y
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BLK_DEV_DRBD=m
+CONFIG_BLK_DEV_NBD=m
+CONFIG_BLK_DEV_RAM=y
+CONFIG_ATA_OVER_ETH=m
+CONFIG_BLK_DEV_RBD=m
+CONFIG_BLK_DEV_NVME=y
+CONFIG_EEPROM_AT24=m
+CONFIG_EEPROM_AT25=m
+CONFIG_TI_ST=m
+CONFIG_SCSI=y
+# CONFIG_SCSI_PROC_FS is not set
+CONFIG_BLK_DEV_SD=y
+CONFIG_CHR_DEV_ST=m
+CONFIG_BLK_DEV_SR=m
+CONFIG_CHR_DEV_SG=m
+CONFIG_SCSI_ISCSI_ATTRS=y
+CONFIG_ISCSI_TCP=m
+CONFIG_ISCSI_BOOT_SYSFS=m
+CONFIG_ATA=m
+CONFIG_SATA_AHCI=m
+CONFIG_SATA_MV=m
+CONFIG_MD=y
+CONFIG_BCACHE=m
+CONFIG_BLK_DEV_DM=m
+CONFIG_DM_CRYPT=m
+CONFIG_DM_SNAPSHOT=m
+CONFIG_DM_THIN_PROVISIONING=m
+CONFIG_DM_CACHE=m
+CONFIG_DM_WRITECACHE=m
+CONFIG_DM_MIRROR=m
+CONFIG_DM_LOG_USERSPACE=m
+CONFIG_DM_RAID=m
+CONFIG_DM_ZERO=m
+CONFIG_DM_MULTIPATH=m
+CONFIG_DM_DELAY=m
+CONFIG_DM_VERITY=m
+CONFIG_DM_INTEGRITY=m
+CONFIG_NETDEVICES=y
+CONFIG_BONDING=m
+CONFIG_DUMMY=m
+CONFIG_WIREGUARD=m
+CONFIG_IFB=m
+CONFIG_MACVLAN=m
+CONFIG_IPVLAN=m
+CONFIG_VXLAN=m
+CONFIG_NETCONSOLE=m
+CONFIG_TUN=m
+CONFIG_VETH=m
+CONFIG_NETKIT=y
+CONFIG_NET_VRF=m
+CONFIG_BCMGENET=y
+CONFIG_IGB=m
+CONFIG_IXGBE=m
+CONFIG_I40E=m
+CONFIG_IGC=m
+CONFIG_ENC28J60=m
+CONFIG_LAN743X=m
+CONFIG_QCA7000_SPI=m
+CONFIG_QCA7000_UART=m
+CONFIG_R8169=m
+CONFIG_MSE102X=m
+CONFIG_WIZNET_W5100=m
+CONFIG_WIZNET_W5100_SPI=m
+CONFIG_MICREL_PHY=y
+CONFIG_CAN_VCAN=m
+CONFIG_CAN_SLCAN=m
+CONFIG_CAN_MCP251X=m
+CONFIG_CAN_MCP251XFD=m
+CONFIG_CAN_8DEV_USB=m
+CONFIG_CAN_EMS_USB=m
+CONFIG_CAN_GS_USB=m
+CONFIG_CAN_PEAK_USB=m
+CONFIG_MDIO_BITBANG=m
+CONFIG_PPP=m
+CONFIG_PPP_BSDCOMP=m
+CONFIG_PPP_DEFLATE=m
+CONFIG_PPP_FILTER=y
+CONFIG_PPP_MPPE=m
+CONFIG_PPP_MULTILINK=y
+CONFIG_PPPOATM=m
+CONFIG_PPPOE=m
+CONFIG_PPPOL2TP=m
+CONFIG_PPP_ASYNC=m
+CONFIG_PPP_SYNC_TTY=m
+CONFIG_SLIP=m
+CONFIG_SLIP_COMPRESSED=y
+CONFIG_SLIP_SMART=y
+CONFIG_USB_CATC=m
+CONFIG_USB_KAWETH=m
+CONFIG_USB_PEGASUS=m
+CONFIG_USB_RTL8150=m
+CONFIG_USB_RTL8152=m
+CONFIG_USB_LAN78XX=y
+CONFIG_USB_USBNET=y
+CONFIG_USB_NET_AX8817X=m
+CONFIG_USB_NET_AX88179_178A=m
+CONFIG_USB_NET_CDCETHER=m
+CONFIG_USB_NET_CDC_EEM=m
+CONFIG_USB_NET_CDC_NCM=m
+CONFIG_USB_NET_HUAWEI_CDC_NCM=m
+CONFIG_USB_NET_CDC_MBIM=m
+CONFIG_USB_NET_DM9601=m
+CONFIG_USB_NET_SR9700=m
+CONFIG_USB_NET_SR9800=m
+CONFIG_USB_NET_SMSC75XX=m
+CONFIG_USB_NET_SMSC95XX=y
+CONFIG_USB_NET_GL620A=m
+CONFIG_USB_NET_NET1080=m
+CONFIG_USB_NET_PLUSB=m
+CONFIG_USB_NET_MCS7830=m
+CONFIG_USB_NET_RNDIS_HOST=m
+CONFIG_USB_NET_CDC_SUBSET=m
+CONFIG_USB_ALI_M5632=y
+CONFIG_USB_AN2720=y
+CONFIG_USB_EPSON2888=y
+CONFIG_USB_KC2190=y
+CONFIG_USB_NET_ZAURUS=m
+CONFIG_USB_NET_CX82310_ETH=m
+CONFIG_USB_NET_KALMIA=m
+CONFIG_USB_NET_QMI_WWAN=m
+CONFIG_USB_HSO=m
+CONFIG_USB_NET_INT51X1=m
+CONFIG_USB_IPHETH=m
+CONFIG_USB_SIERRA_NET=m
+CONFIG_USB_VL600=m
+CONFIG_USB_NET_AQC111=m
+CONFIG_ATH9K=m
+CONFIG_ATH9K_HTC=m
+CONFIG_CARL9170=m
+CONFIG_ATH6KL=m
+CONFIG_ATH6KL_USB=m
+CONFIG_AR5523=m
+CONFIG_AT76C50X_USB=m
+CONFIG_B43=m
+# CONFIG_B43_PHY_N is not set
+CONFIG_B43LEGACY=m
+CONFIG_BRCMFMAC=m
+CONFIG_BRCMFMAC_USB=y
+CONFIG_BRCMDBG=y
+CONFIG_IWLWIFI=m
+CONFIG_IWLDVM=m
+CONFIG_IWLMVM=m
+CONFIG_P54_COMMON=m
+CONFIG_P54_USB=m
+CONFIG_LIBERTAS=m
+CONFIG_LIBERTAS_USB=m
+CONFIG_LIBERTAS_SDIO=m
+CONFIG_LIBERTAS_THINFIRM=m
+CONFIG_LIBERTAS_THINFIRM_USB=m
+CONFIG_MWIFIEX=m
+CONFIG_MWIFIEX_SDIO=m
+CONFIG_MT7601U=m
+CONFIG_MT76x0U=m
+CONFIG_MT76x2U=m
+CONFIG_MT7921U=m
+CONFIG_MT7925U=m
+CONFIG_RT2X00=m
+CONFIG_RT2500USB=m
+CONFIG_RT73USB=m
+CONFIG_RT2800USB=m
+CONFIG_RT2800USB_RT3573=y
+CONFIG_RT2800USB_RT53XX=y
+CONFIG_RT2800USB_RT55XX=y
+CONFIG_RT2800USB_UNKNOWN=y
+CONFIG_RTL8187=m
+CONFIG_RTL8192CU=m
+CONFIG_RTL8XXXU=m
+CONFIG_RTW88=m
+CONFIG_RTW88_8822BE=m
+CONFIG_RTW88_8822BU=m
+CONFIG_RTW88_8822CE=m
+CONFIG_RTW88_8822CU=m
+CONFIG_RTW88_8723DE=m
+CONFIG_RTW88_8723DU=m
+CONFIG_RTW88_8821CE=m
+CONFIG_RTW88_8821CU=m
+CONFIG_RTW89=m
+CONFIG_RTW89_8851BE=m
+CONFIG_RTW89_8852AE=m
+CONFIG_RTW89_8852BE=m
+CONFIG_RTW89_8852BTE=m
+CONFIG_RTW89_8852CE=m
+CONFIG_RTW89_8922AE=m
+CONFIG_ZD1211RW=m
+CONFIG_MAC80211_HWSIM=m
+CONFIG_IEEE802154_AT86RF230=m
+CONFIG_IEEE802154_MRF24J40=m
+CONFIG_IEEE802154_CC2520=m
+CONFIG_INPUT_MOUSEDEV=y
+CONFIG_INPUT_JOYDEV=m
+CONFIG_INPUT_EVDEV=y
+# CONFIG_KEYBOARD_ATKBD is not set
+CONFIG_KEYBOARD_GPIO=m
+CONFIG_KEYBOARD_TCA6416=m
+CONFIG_KEYBOARD_TCA8418=m
+CONFIG_KEYBOARD_MATRIX=m
+CONFIG_KEYBOARD_CAP11XX=m
+# CONFIG_INPUT_MOUSE is not set
+CONFIG_INPUT_JOYSTICK=y
+CONFIG_JOYSTICK_IFORCE=m
+CONFIG_JOYSTICK_IFORCE_USB=m
+CONFIG_JOYSTICK_XPAD=m
+CONFIG_JOYSTICK_XPAD_FF=y
+CONFIG_JOYSTICK_XPAD_LEDS=y
+CONFIG_JOYSTICK_PSXPAD_SPI=m
+CONFIG_JOYSTICK_PSXPAD_SPI_FF=y
+CONFIG_JOYSTICK_FSIA6B=m
+CONFIG_JOYSTICK_SENSEHAT=m
+CONFIG_INPUT_TOUCHSCREEN=y
+CONFIG_TOUCHSCREEN_ADS7846=m
+CONFIG_TOUCHSCREEN_EGALAX=m
+CONFIG_TOUCHSCREEN_EXC3000=m
+CONFIG_TOUCHSCREEN_GOODIX=m
+CONFIG_TOUCHSCREEN_ILI210X=m
+CONFIG_TOUCHSCREEN_ILITEK=m
+CONFIG_TOUCHSCREEN_EDT_FT5X06=m
+CONFIG_TOUCHSCREEN_RASPBERRYPI_FW=m
+CONFIG_TOUCHSCREEN_USB_COMPOSITE=m
+CONFIG_TOUCHSCREEN_TSC2007=m
+CONFIG_TOUCHSCREEN_TSC2007_IIO=y
+CONFIG_TOUCHSCREEN_STMPE=m
+CONFIG_TOUCHSCREEN_IQS5XX=m
+CONFIG_INPUT_MISC=y
+CONFIG_INPUT_AD714X=m
+CONFIG_INPUT_ATI_REMOTE2=m
+CONFIG_INPUT_KEYSPAN_REMOTE=m
+CONFIG_INPUT_POWERMATE=m
+CONFIG_INPUT_YEALINK=m
+CONFIG_INPUT_CM109=m
+CONFIG_INPUT_UINPUT=m
+CONFIG_INPUT_PWM_BEEPER=m
+CONFIG_INPUT_GPIO_ROTARY_ENCODER=m
+CONFIG_INPUT_ADXL34X=m
+CONFIG_INPUT_CMA3000=m
+CONFIG_SERIO=m
+CONFIG_SERIO_RAW=m
+CONFIG_GAMEPORT=m
+CONFIG_BRCM_CHAR_DRIVERS=y
+CONFIG_BCM_VCIO=y
+# CONFIG_LEGACY_PTYS is not set
+CONFIG_SERIAL_8250=y
+# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
+CONFIG_SERIAL_8250_CONSOLE=y
+# CONFIG_SERIAL_8250_DMA is not set
+CONFIG_SERIAL_8250_NR_UARTS=5
+CONFIG_SERIAL_8250_RUNTIME_UARTS=0
+CONFIG_SERIAL_8250_EXTENDED=y
+CONFIG_SERIAL_8250_SHARE_IRQ=y
+CONFIG_SERIAL_8250_BCM2835AUX=y
+CONFIG_SERIAL_OF_PLATFORM=y
+CONFIG_SERIAL_AMBA_PL011=y
+CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
+CONFIG_SERIAL_SC16IS7XX=m
+CONFIG_SERIAL_RPI_FW=m
+CONFIG_SERIAL_DEV_BUS=y
+CONFIG_TTY_PRINTK=y
+CONFIG_HW_RANDOM=y
+CONFIG_TCG_TPM=m
+CONFIG_TCG_TIS_SPI=m
+CONFIG_TCG_TIS_I2C=m
+CONFIG_XILLYBUS=m
+CONFIG_XILLYBUS_PCIE=m
+CONFIG_XILLYUSB=m
+CONFIG_RASPBERRYPI_GPIOMEM=m
+CONFIG_I2C=y
+CONFIG_I2C_CHARDEV=m
+CONFIG_I2C_MUX_GPMUX=m
+CONFIG_I2C_MUX_PCA954x=m
+CONFIG_I2C_MUX_PINCTRL=m
+CONFIG_I2C_BCM2708=m
+CONFIG_I2C_BCM2835=m
+CONFIG_I2C_BRCMSTB=m
+CONFIG_I2C_GPIO=m
+CONFIG_I2C_ROBOTFUZZ_OSIF=m
+CONFIG_I2C_TINY_USB=m
+CONFIG_SPI=y
+CONFIG_SPI_BCM2835=m
+CONFIG_SPI_BCM2835AUX=m
+CONFIG_SPI_GPIO=m
+CONFIG_SPI_RP2040_GPIO_BRIDGE=m
+CONFIG_SPI_SPIDEV=m
+CONFIG_SPI_SLAVE=y
+CONFIG_PPS_CLIENT_LDISC=m
+CONFIG_PPS_CLIENT_GPIO=m
+CONFIG_PINCTRL_MCP23S08=m
+CONFIG_GPIO_SYSFS=y
+CONFIG_GPIO_BCM_VIRT=y
+CONFIG_GPIO_FXL6408=m
+CONFIG_GPIO_MAX7300=m
+CONFIG_GPIO_PCA953X=m
+CONFIG_GPIO_PCA953X_IRQ=y
+CONFIG_GPIO_PCF857X=m
+CONFIG_GPIO_ARIZONA=m
+CONFIG_GPIO_FSM=m
+CONFIG_GPIO_STMPE=y
+CONFIG_GPIO_MAX7301=m
+CONFIG_GPIO_MOCKUP=m
+CONFIG_W1=m
+CONFIG_W1_MASTER_DS2490=m
+CONFIG_W1_MASTER_DS2482=m
+CONFIG_W1_MASTER_GPIO=m
+CONFIG_W1_SLAVE_THERM=m
+CONFIG_W1_SLAVE_SMEM=m
+CONFIG_W1_SLAVE_DS2408=m
+CONFIG_W1_SLAVE_DS2413=m
+CONFIG_W1_SLAVE_DS2406=m
+CONFIG_W1_SLAVE_DS2423=m
+CONFIG_W1_SLAVE_DS2430=m
+CONFIG_W1_SLAVE_DS2431=m
+CONFIG_W1_SLAVE_DS2433=m
+CONFIG_W1_SLAVE_DS2438=m
+CONFIG_W1_SLAVE_DS2780=m
+CONFIG_W1_SLAVE_DS2781=m
+CONFIG_W1_SLAVE_DS28E04=m
+CONFIG_W1_SLAVE_DS28E17=m
+CONFIG_POWER_RESET=y
+CONFIG_POWER_RESET_GPIO=y
+CONFIG_RPI_POE_POWER=m
+CONFIG_BATTERY_DS2760=m
+CONFIG_BATTERY_MAX17040=m
+CONFIG_CHARGER_GPIO=m
+CONFIG_BATTERY_GAUGE_LTC2941=m
+CONFIG_SENSORS_ADT7410=m
+CONFIG_SENSORS_AHT10=m
+CONFIG_SENSORS_CHIPCAP2=m
+CONFIG_SENSORS_DRIVETEMP=m
+CONFIG_SENSORS_DS1621=m
+CONFIG_SENSORS_GPIO_FAN=m
+CONFIG_SENSORS_IIO_HWMON=m
+CONFIG_SENSORS_JC42=m
+CONFIG_SENSORS_POWERZ=m
+CONFIG_SENSORS_LM75=m
+CONFIG_SENSORS_PWM_FAN=m
+CONFIG_SENSORS_RASPBERRYPI_HWMON=m
+CONFIG_SENSORS_SHT21=m
+CONFIG_SENSORS_SHT3x=m
+CONFIG_SENSORS_SHT4x=m
+CONFIG_SENSORS_SHTC1=m
+CONFIG_SENSORS_EMC2305=m
+CONFIG_SENSORS_ADS7828=m
+CONFIG_SENSORS_INA2XX=m
+CONFIG_SENSORS_INA238=m
+CONFIG_SENSORS_TMP102=m
+CONFIG_BCM2711_THERMAL=y
+CONFIG_WATCHDOG=y
+CONFIG_WATCHDOG_SYSFS=y
+CONFIG_GPIO_WATCHDOG=m
+CONFIG_BCM2835_WDT=y
+CONFIG_MFD_RASPBERRYPI_POE_HAT=m
+CONFIG_MFD_STMPE=y
+CONFIG_STMPE_SPI=y
+CONFIG_MFD_ARIZONA_I2C=m
+CONFIG_MFD_ARIZONA_SPI=m
+CONFIG_MFD_WM5102=y
+CONFIG_REGULATOR=y
+CONFIG_REGULATOR_FIXED_VOLTAGE=y
+CONFIG_REGULATOR_ARIZONA_LDO1=m
+CONFIG_REGULATOR_ARIZONA_MICSUPP=m
+CONFIG_REGULATOR_GPIO=y
+CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY=m
+CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_V2=m
+CONFIG_REGULATOR_WAVESHARE_TOUCHSCREEN=m
+CONFIG_RC_CORE=y
+CONFIG_BPF_LIRC_MODE2=y
+CONFIG_LIRC=y
+CONFIG_RC_DECODERS=y
+CONFIG_IR_IMON_DECODER=m
+CONFIG_IR_JVC_DECODER=m
+CONFIG_IR_MCE_KBD_DECODER=m
+CONFIG_IR_NEC_DECODER=m
+CONFIG_IR_RC5_DECODER=m
+CONFIG_IR_RC6_DECODER=m
+CONFIG_IR_SANYO_DECODER=m
+CONFIG_IR_SHARP_DECODER=m
+CONFIG_IR_SONY_DECODER=m
+CONFIG_IR_XMP_DECODER=m
+CONFIG_RC_DEVICES=y
+CONFIG_IR_GPIO_CIR=m
+CONFIG_IR_GPIO_TX=m
+CONFIG_IR_IGUANA=m
+CONFIG_IR_IMON=m
+CONFIG_IR_MCEUSB=m
+CONFIG_IR_PWM_TX=m
+CONFIG_IR_REDRAT3=m
+CONFIG_IR_STREAMZAP=m
+CONFIG_IR_TOY=m
+CONFIG_IR_TTUSBIR=m
+CONFIG_RC_ATI_REMOTE=m
+CONFIG_RC_LOOPBACK=m
+CONFIG_MEDIA_CEC_RC=y
+CONFIG_MEDIA_SUPPORT=m
+CONFIG_MEDIA_USB_SUPPORT=y
+CONFIG_USB_GSPCA=m
+CONFIG_USB_GSPCA_BENQ=m
+CONFIG_USB_GSPCA_CONEX=m
+CONFIG_USB_GSPCA_CPIA1=m
+CONFIG_USB_GSPCA_DTCS033=m
+CONFIG_USB_GSPCA_ETOMS=m
+CONFIG_USB_GSPCA_FINEPIX=m
+CONFIG_USB_GSPCA_JEILINJ=m
+CONFIG_USB_GSPCA_JL2005BCD=m
+CONFIG_USB_GSPCA_KINECT=m
+CONFIG_USB_GSPCA_KONICA=m
+CONFIG_USB_GSPCA_MARS=m
+CONFIG_USB_GSPCA_MR97310A=m
+CONFIG_USB_GSPCA_NW80X=m
+CONFIG_USB_GSPCA_OV519=m
+CONFIG_USB_GSPCA_OV534=m
+CONFIG_USB_GSPCA_OV534_9=m
+CONFIG_USB_GSPCA_PAC207=m
+CONFIG_USB_GSPCA_PAC7302=m
+CONFIG_USB_GSPCA_PAC7311=m
+CONFIG_USB_GSPCA_SE401=m
+CONFIG_USB_GSPCA_SN9C2028=m
+CONFIG_USB_GSPCA_SN9C20X=m
+CONFIG_USB_GSPCA_SONIXB=m
+CONFIG_USB_GSPCA_SONIXJ=m
+CONFIG_USB_GSPCA_SPCA1528=m
+CONFIG_USB_GSPCA_SPCA500=m
+CONFIG_USB_GSPCA_SPCA501=m
+CONFIG_USB_GSPCA_SPCA505=m
+CONFIG_USB_GSPCA_SPCA506=m
+CONFIG_USB_GSPCA_SPCA508=m
+CONFIG_USB_GSPCA_SPCA561=m
+CONFIG_USB_GSPCA_SQ905=m
+CONFIG_USB_GSPCA_SQ905C=m
+CONFIG_USB_GSPCA_SQ930X=m
+CONFIG_USB_GSPCA_STK014=m
+CONFIG_USB_GSPCA_STK1135=m
+CONFIG_USB_GSPCA_STV0680=m
+CONFIG_USB_GSPCA_SUNPLUS=m
+CONFIG_USB_GSPCA_T613=m
+CONFIG_USB_GSPCA_TOPRO=m
+CONFIG_USB_GSPCA_TOUPTEK=m
+CONFIG_USB_GSPCA_TV8532=m
+CONFIG_USB_GSPCA_VC032X=m
+CONFIG_USB_GSPCA_VICAM=m
+CONFIG_USB_GSPCA_XIRLINK_CIT=m
+CONFIG_USB_GSPCA_ZC3XX=m
+CONFIG_USB_GL860=m
+CONFIG_USB_M5602=m
+CONFIG_USB_STV06XX=m
+CONFIG_USB_PWC=m
+CONFIG_USB_S2255=m
+CONFIG_VIDEO_USBTV=m
+CONFIG_USB_VIDEO_CLASS=m
+CONFIG_VIDEO_GO7007=m
+CONFIG_VIDEO_GO7007_USB=m
+CONFIG_VIDEO_GO7007_USB_S2250_BOARD=m
+CONFIG_VIDEO_HDPVR=m
+CONFIG_VIDEO_PVRUSB2=m
+CONFIG_VIDEO_STK1160=m
+CONFIG_VIDEO_AU0828=m
+CONFIG_VIDEO_AU0828_RC=y
+CONFIG_VIDEO_CX231XX=m
+CONFIG_VIDEO_CX231XX_ALSA=m
+CONFIG_VIDEO_CX231XX_DVB=m
+CONFIG_DVB_AS102=m
+CONFIG_DVB_B2C2_FLEXCOP_USB=m
+CONFIG_DVB_USB_V2=m
+CONFIG_DVB_USB_AF9015=m
+CONFIG_DVB_USB_AF9035=m
+CONFIG_DVB_USB_ANYSEE=m
+CONFIG_DVB_USB_AU6610=m
+CONFIG_DVB_USB_AZ6007=m
+CONFIG_DVB_USB_CE6230=m
+CONFIG_DVB_USB_DVBSKY=m
+CONFIG_DVB_USB_EC168=m
+CONFIG_DVB_USB_GL861=m
+CONFIG_DVB_USB_LME2510=m
+CONFIG_DVB_USB_MXL111SF=m
+CONFIG_DVB_USB_RTL28XXU=m
+CONFIG_DVB_USB=m
+CONFIG_DVB_USB_A800=m
+CONFIG_DVB_USB_AF9005=m
+CONFIG_DVB_USB_AF9005_REMOTE=m
+CONFIG_DVB_USB_AZ6027=m
+CONFIG_DVB_USB_CINERGY_T2=m
+CONFIG_DVB_USB_CXUSB=m
+CONFIG_DVB_USB_DIB0700=m
+CONFIG_DVB_USB_DIBUSB_MB=m
+CONFIG_DVB_USB_DIBUSB_MB_FAULTY=y
+CONFIG_DVB_USB_DIBUSB_MC=m
+CONFIG_DVB_USB_DIGITV=m
+CONFIG_DVB_USB_DTT200U=m
+CONFIG_DVB_USB_DTV5100=m
+CONFIG_DVB_USB_DW2102=m
+CONFIG_DVB_USB_GP8PSK=m
+CONFIG_DVB_USB_M920X=m
+CONFIG_DVB_USB_NOVA_T_USB2=m
+CONFIG_DVB_USB_OPERA1=m
+CONFIG_DVB_USB_PCTV452E=m
+CONFIG_DVB_USB_TECHNISAT_USB2=m
+CONFIG_DVB_USB_TTUSB2=m
+CONFIG_DVB_USB_UMT_010=m
+CONFIG_DVB_USB_VP702X=m
+CONFIG_DVB_USB_VP7045=m
+CONFIG_SMS_USB_DRV=m
+CONFIG_VIDEO_EM28XX=m
+CONFIG_VIDEO_EM28XX_V4L2=m
+CONFIG_VIDEO_EM28XX_ALSA=m
+CONFIG_VIDEO_EM28XX_DVB=m
+CONFIG_MEDIA_PCI_SUPPORT=y
+CONFIG_RADIO_SAA7706H=m
+CONFIG_RADIO_SHARK=m
+CONFIG_RADIO_SHARK2=m
+CONFIG_RADIO_SI4713=m
+CONFIG_RADIO_TEA5764=m
+CONFIG_RADIO_TEF6862=m
+CONFIG_RADIO_WL1273=m
+CONFIG_USB_DSBR=m
+CONFIG_USB_KEENE=m
+CONFIG_USB_MA901=m
+CONFIG_USB_MR800=m
+CONFIG_RADIO_SI470X=m
+CONFIG_USB_SI470X=m
+CONFIG_I2C_SI470X=m
+CONFIG_I2C_SI4713=m
+CONFIG_RADIO_WL128X=m
+CONFIG_V4L_PLATFORM_DRIVERS=y
+CONFIG_VIDEO_MUX=m
+CONFIG_VIDEO_BCM2835_UNICAM_LEGACY=m
+CONFIG_VIDEO_BCM2835_UNICAM=m
+CONFIG_VIDEO_RPI_HEVC_DEC=m
+CONFIG_VIDEO_ARDUCAM_64MP=m
+CONFIG_VIDEO_ARDUCAM_PIVARIETY=m
+CONFIG_VIDEO_IMX219=m
+CONFIG_VIDEO_IMX258=m
+CONFIG_VIDEO_IMX283=m
+CONFIG_VIDEO_IMX290=m
+CONFIG_VIDEO_IMX296=m
+CONFIG_VIDEO_IMX335=m
+CONFIG_VIDEO_IMX415=m
+CONFIG_VIDEO_IMX477=m
+CONFIG_VIDEO_IMX500=m
+CONFIG_VIDEO_IMX519=m
+CONFIG_VIDEO_IMX708=m
+CONFIG_VIDEO_MIRA220=m
+CONFIG_VIDEO_MT9V011=m
+CONFIG_VIDEO_OV2311=m
+CONFIG_VIDEO_OV5647=m
+CONFIG_VIDEO_OV64A40=m
+CONFIG_VIDEO_OV7251=m
+CONFIG_VIDEO_OV7640=m
+CONFIG_VIDEO_OV9282=m
+CONFIG_VIDEO_AD5398=m
+CONFIG_VIDEO_AK7375=m
+CONFIG_VIDEO_BU64754=m
+CONFIG_VIDEO_DW9807_VCM=m
+CONFIG_VIDEO_SONY_BTF_MPX=m
+CONFIG_VIDEO_UDA1342=m
+CONFIG_VIDEO_ADV7180=m
+CONFIG_VIDEO_TC358743=m
+CONFIG_VIDEO_TVP5150=m
+CONFIG_VIDEO_TW2804=m
+CONFIG_VIDEO_TW9903=m
+CONFIG_VIDEO_TW9906=m
+CONFIG_VIDEO_IRS1125=m
+CONFIG_VIDEO_I2C=m
+CONFIG_AUXDISPLAY=y
+CONFIG_HD44780=m
+CONFIG_DRM=m
+CONFIG_DRM_LOAD_EDID_FIRMWARE=y
+CONFIG_DRM_UDL=m
+CONFIG_DRM_PANEL_ILITEK_ILI9806E=m
+CONFIG_DRM_PANEL_ILITEK_ILI9806E_SPI=m
+CONFIG_DRM_PANEL_ILITEK_ILI9881C=m
+CONFIG_DRM_PANEL_JDI_LT070ME05000=m
+CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN=m
+CONFIG_DRM_PANEL_SITRONIX_ST7701=m
+CONFIG_DRM_PANEL_SIMPLE=m
+CONFIG_DRM_PANEL_TPO_Y17P=m
+CONFIG_DRM_PANEL_WAVESHARE_TOUCHSCREEN=m
+CONFIG_DRM_PANEL_WAVESHARE_TOUCHSCREEN_V2=m
+CONFIG_DRM_DISPLAY_CONNECTOR=m
+CONFIG_DRM_SIMPLE_BRIDGE=m
+CONFIG_DRM_TOSHIBA_TC358762=m
+CONFIG_DRM_V3D=m
+CONFIG_DRM_VC4=m
+CONFIG_DRM_VC4_HDMI_CEC=y
+CONFIG_DRM_PANEL_MIPI_DBI=m
+CONFIG_TINYDRM_HX8357D=m
+CONFIG_TINYDRM_ILI9225=m
+CONFIG_TINYDRM_ILI9341=m
+CONFIG_TINYDRM_ILI9486=m
+CONFIG_TINYDRM_MI0283QT=m
+CONFIG_TINYDRM_REPAPER=m
+CONFIG_TINYDRM_ST7586=m
+CONFIG_TINYDRM_ST7735R=m
+CONFIG_DRM_GUD=m
+CONFIG_DRM_SSD130X=m
+CONFIG_DRM_SSD130X_I2C=m
+CONFIG_DRM_SSD130X_SPI=m
+CONFIG_FB=y
+CONFIG_FB_BCM2708=y
+CONFIG_FB_SIMPLE=y
+CONFIG_FB_SSD1307=m
+CONFIG_FB_RPISENSE=m
+CONFIG_BACKLIGHT_PWM=m
+CONFIG_BACKLIGHT_RPI=m
+CONFIG_BACKLIGHT_LM3630A=m
+CONFIG_BACKLIGHT_GPIO=m
+CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y
+CONFIG_LOGO=y
+# CONFIG_LOGO_LINUX_MONO is not set
+# CONFIG_LOGO_LINUX_VGA16 is not set
+CONFIG_SOUND=y
+CONFIG_SND=m
+CONFIG_SND_OSSEMUL=y
+CONFIG_SND_PCM_OSS=m
+CONFIG_SND_HRTIMER=m
+CONFIG_SND_DYNAMIC_MINORS=y
+CONFIG_SND_SEQUENCER=m
+CONFIG_SND_SEQ_DUMMY=m
+CONFIG_SND_DUMMY=m
+CONFIG_SND_ALOOP=m
+CONFIG_SND_VIRMIDI=m
+CONFIG_SND_MTPAV=m
+CONFIG_SND_SERIAL_U16550=m
+CONFIG_SND_MPU401=m
+CONFIG_SND_PIMIDI=m
+CONFIG_SND_PISOUND_MICRO=m
+CONFIG_SND_USB_AUDIO=m
+CONFIG_SND_USB_UA101=m
+CONFIG_SND_USB_CAIAQ=m
+CONFIG_SND_USB_CAIAQ_INPUT=y
+CONFIG_SND_USB_6FIRE=m
+CONFIG_SND_USB_HIFACE=m
+CONFIG_SND_USB_TONEPORT=m
+CONFIG_SND_SOC=m
+CONFIG_SND_BCM2835_SOC_I2S=m
+CONFIG_SND_BCM2708_SOC_CHIPDIP_DAC=m
+CONFIG_SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_ADC=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DAC=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUS=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DIGI=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_AMP=m
+CONFIG_SND_BCM2708_SOC_PIFI_40=m
+CONFIG_SND_BCM2708_SOC_RPI_CIRRUS=m
+CONFIG_SND_BCM2708_SOC_RPI_DAC=m
+CONFIG_SND_BCM2708_SOC_RPI_PROTO=m
+CONFIG_SND_BCM2708_SOC_JUSTBOOM_BOTH=m
+CONFIG_SND_BCM2708_SOC_JUSTBOOM_DAC=m
+CONFIG_SND_BCM2708_SOC_JUSTBOOM_DIGI=m
+CONFIG_SND_BCM2708_SOC_IQAUDIO_CODEC=m
+CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC=m
+CONFIG_SND_BCM2708_SOC_IQAUDIO_DIGI=m
+CONFIG_SND_BCM2708_SOC_I_SABRE_Q2M=m
+CONFIG_SND_BCM2708_SOC_ADAU1977_ADC=m
+CONFIG_SND_AUDIOINJECTOR_PI_SOUNDCARD=m
+CONFIG_SND_AUDIOINJECTOR_OCTO_SOUNDCARD=m
+CONFIG_SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD=m
+CONFIG_SND_AUDIOSENSE_PI=m
+CONFIG_SND_DIGIDAC1_SOUNDCARD=m
+CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO=m
+CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO_V2=m
+CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC=m
+CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS=m
+CONFIG_SND_BCM2708_SOC_ALLO_BOSS_DAC=m
+CONFIG_SND_BCM2708_SOC_ALLO_BOSS2_DAC=m
+CONFIG_SND_BCM2708_SOC_ALLO_DIGIONE=m
+CONFIG_SND_BCM2708_SOC_ALLO_KATANA_DAC=m
+CONFIG_SND_BCM2708_SOC_FE_PI_AUDIO=m
+CONFIG_SND_PISOUND=m
+CONFIG_SND_DACBERRY400=m
+CONFIG_SND_SOC_AD193X_SPI=m
+CONFIG_SND_SOC_AD193X_I2C=m
+CONFIG_SND_SOC_ADAU1701=m
+CONFIG_SND_SOC_ADAU7002=m
+CONFIG_SND_SOC_AK4554=m
+CONFIG_SND_SOC_CS4265=m
+CONFIG_SND_SOC_ICS43432=m
+CONFIG_SND_SOC_MA120X0P=m
+CONFIG_SND_SOC_MAX98357A=m
+CONFIG_SND_SOC_SPDIF=m
+CONFIG_SND_SOC_TLV320AIC23_I2C=m
+CONFIG_SND_SOC_WM8804_I2C=m
+CONFIG_SND_SOC_WM8904=m
+CONFIG_SND_SOC_WM8960=m
+CONFIG_SND_SIMPLE_CARD=m
+CONFIG_HID_BATTERY_STRENGTH=y
+CONFIG_HIDRAW=y
+CONFIG_UHID=m
+CONFIG_HID_A4TECH=m
+CONFIG_HID_ACRUX=m
+CONFIG_HID_APPLE=m
+CONFIG_HID_ASUS=m
+CONFIG_HID_BELKIN=m
+CONFIG_HID_BETOP_FF=m
+CONFIG_HID_BIGBEN_FF=m
+CONFIG_HID_CHERRY=m
+CONFIG_HID_CHICONY=m
+CONFIG_HID_CP2112=m
+CONFIG_HID_CYPRESS=m
+CONFIG_HID_DRAGONRISE=m
+CONFIG_HID_EMS_FF=m
+CONFIG_HID_ELECOM=m
+CONFIG_HID_ELO=m
+CONFIG_HID_EZKEY=m
+CONFIG_HID_GEMBIRD=m
+CONFIG_HID_HOLTEK=m
+CONFIG_HID_KEYTOUCH=m
+CONFIG_HID_KYE=m
+CONFIG_HID_UCLOGIC=m
+CONFIG_HID_WALTOP=m
+CONFIG_HID_GYRATION=m
+CONFIG_HID_TWINHAN=m
+CONFIG_HID_KENSINGTON=m
+CONFIG_HID_LCPOWER=m
+CONFIG_HID_LOGITECH=m
+CONFIG_HID_LOGITECH_DJ=m
+CONFIG_LOGITECH_FF=y
+CONFIG_LOGIRUMBLEPAD2_FF=y
+CONFIG_LOGIG940_FF=y
+CONFIG_HID_MAGICMOUSE=m
+CONFIG_HID_MICROSOFT=m
+CONFIG_HID_MONTEREY=m
+CONFIG_HID_MULTITOUCH=m
+CONFIG_HID_NINTENDO=m
+CONFIG_NINTENDO_FF=y
+CONFIG_HID_NTRIG=m
+CONFIG_HID_ORTEK=m
+CONFIG_HID_PANTHERLORD=m
+CONFIG_HID_PETALYNX=m
+CONFIG_HID_PICOLCD=m
+CONFIG_HID_PLAYSTATION=m
+CONFIG_PLAYSTATION_FF=y
+CONFIG_HID_ROCCAT=m
+CONFIG_HID_SAMSUNG=m
+CONFIG_HID_SONY=m
+CONFIG_SONY_FF=y
+CONFIG_HID_SPEEDLINK=m
+CONFIG_HID_STEAM=m
+CONFIG_HID_SUNPLUS=m
+CONFIG_HID_GREENASIA=m
+CONFIG_HID_SMARTJOYPLUS=m
+CONFIG_HID_TOPSEED=m
+CONFIG_HID_THINGM=m
+CONFIG_HID_THRUSTMASTER=m
+CONFIG_HID_WACOM=m
+CONFIG_HID_WIIMOTE=m
+CONFIG_HID_XINMO=m
+CONFIG_HID_ZEROPLUS=m
+CONFIG_HID_ZYDACRON=m
+CONFIG_HID_PID=y
+CONFIG_USB_HIDDEV=y
+CONFIG_I2C_HID_OF=m
+CONFIG_USB=y
+CONFIG_USB_ANNOUNCE_NEW_DEVICES=y
+CONFIG_USB_MON=m
+CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_XHCI_PCI_RENESAS=m
+CONFIG_USB_XHCI_PLATFORM=y
+CONFIG_USB_DWCOTG=y
+CONFIG_USB_PRINTER=m
+CONFIG_USB_TMC=m
+CONFIG_USB_STORAGE=y
+CONFIG_USB_STORAGE_REALTEK=m
+CONFIG_USB_STORAGE_DATAFAB=m
+CONFIG_USB_STORAGE_FREECOM=m
+CONFIG_USB_STORAGE_ISD200=m
+CONFIG_USB_STORAGE_USBAT=m
+CONFIG_USB_STORAGE_SDDR09=m
+CONFIG_USB_STORAGE_SDDR55=m
+CONFIG_USB_STORAGE_JUMPSHOT=m
+CONFIG_USB_STORAGE_ALAUDA=m
+CONFIG_USB_STORAGE_ONETOUCH=m
+CONFIG_USB_STORAGE_KARMA=m
+CONFIG_USB_STORAGE_CYPRESS_ATACB=m
+CONFIG_USB_STORAGE_ENE_UB6250=m
+CONFIG_USB_UAS=y
+CONFIG_USB_MDC800=m
+CONFIG_USB_MICROTEK=m
+CONFIG_USBIP_CORE=m
+CONFIG_USBIP_VHCI_HCD=m
+CONFIG_USBIP_HOST=m
+CONFIG_USBIP_VUDC=m
+CONFIG_USB_DWC2=m
+CONFIG_USB_SERIAL=m
+CONFIG_USB_SERIAL_GENERIC=y
+CONFIG_USB_SERIAL_SIMPLE=m
+CONFIG_USB_SERIAL_AIRCABLE=m
+CONFIG_USB_SERIAL_ARK3116=m
+CONFIG_USB_SERIAL_BELKIN=m
+CONFIG_USB_SERIAL_CH341=m
+CONFIG_USB_SERIAL_WHITEHEAT=m
+CONFIG_USB_SERIAL_DIGI_ACCELEPORT=m
+CONFIG_USB_SERIAL_CP210X=m
+CONFIG_USB_SERIAL_CYPRESS_M8=m
+CONFIG_USB_SERIAL_EMPEG=m
+CONFIG_USB_SERIAL_FTDI_SIO=m
+CONFIG_USB_SERIAL_VISOR=m
+CONFIG_USB_SERIAL_IPAQ=m
+CONFIG_USB_SERIAL_IR=m
+CONFIG_USB_SERIAL_EDGEPORT=m
+CONFIG_USB_SERIAL_EDGEPORT_TI=m
+CONFIG_USB_SERIAL_F81232=m
+CONFIG_USB_SERIAL_F8153X=m
+CONFIG_USB_SERIAL_GARMIN=m
+CONFIG_USB_SERIAL_IPW=m
+CONFIG_USB_SERIAL_IUU=m
+CONFIG_USB_SERIAL_KEYSPAN_PDA=m
+CONFIG_USB_SERIAL_KEYSPAN=m
+CONFIG_USB_SERIAL_KLSI=m
+CONFIG_USB_SERIAL_KOBIL_SCT=m
+CONFIG_USB_SERIAL_MCT_U232=m
+CONFIG_USB_SERIAL_METRO=m
+CONFIG_USB_SERIAL_MOS7720=m
+CONFIG_USB_SERIAL_MOS7840=m
+CONFIG_USB_SERIAL_MXUPORT=m
+CONFIG_USB_SERIAL_NAVMAN=m
+CONFIG_USB_SERIAL_PL2303=m
+CONFIG_USB_SERIAL_OTI6858=m
+CONFIG_USB_SERIAL_QCAUX=m
+CONFIG_USB_SERIAL_QUALCOMM=m
+CONFIG_USB_SERIAL_SPCP8X5=m
+CONFIG_USB_SERIAL_SAFE=m
+CONFIG_USB_SERIAL_SIERRAWIRELESS=m
+CONFIG_USB_SERIAL_SYMBOL=m
+CONFIG_USB_SERIAL_TI=m
+CONFIG_USB_SERIAL_CYBERJACK=m
+CONFIG_USB_SERIAL_OPTION=m
+CONFIG_USB_SERIAL_OMNINET=m
+CONFIG_USB_SERIAL_OPTICON=m
+CONFIG_USB_SERIAL_XSENS_MT=m
+CONFIG_USB_SERIAL_WISHBONE=m
+CONFIG_USB_SERIAL_SSU100=m
+CONFIG_USB_SERIAL_QT2=m
+CONFIG_USB_SERIAL_UPD78F0730=m
+CONFIG_USB_SERIAL_XR=m
+CONFIG_USB_SERIAL_DEBUG=m
+CONFIG_USB_EMI62=m
+CONFIG_USB_EMI26=m
+CONFIG_USB_ADUTUX=m
+CONFIG_USB_SEVSEG=m
+CONFIG_USB_LEGOTOWER=m
+CONFIG_USB_LCD=m
+CONFIG_USB_CYPRESS_CY7C63=m
+CONFIG_USB_CYTHERM=m
+CONFIG_USB_IDMOUSE=m
+CONFIG_USB_APPLEDISPLAY=m
+CONFIG_USB_LD=m
+CONFIG_USB_TRANCEVIBRATOR=m
+CONFIG_USB_IOWARRIOR=m
+CONFIG_USB_TEST=m
+CONFIG_USB_ISIGHTFW=m
+CONFIG_USB_YUREX=m
+CONFIG_USB_ATM=m
+CONFIG_USB_SPEEDTOUCH=m
+CONFIG_USB_CXACRU=m
+CONFIG_USB_UEAGLEATM=m
+CONFIG_USB_XUSBATM=m
+CONFIG_NOP_USB_XCEIV=y
+CONFIG_USB_GADGET=y
+CONFIG_USB_CONFIGFS=m
+CONFIG_USB_CONFIGFS_SERIAL=y
+CONFIG_USB_CONFIGFS_ACM=y
+CONFIG_USB_CONFIGFS_OBEX=y
+CONFIG_USB_CONFIGFS_NCM=y
+CONFIG_USB_CONFIGFS_ECM=y
+CONFIG_USB_CONFIGFS_ECM_SUBSET=y
+CONFIG_USB_CONFIGFS_RNDIS=y
+CONFIG_USB_CONFIGFS_EEM=y
+CONFIG_USB_CONFIGFS_MASS_STORAGE=y
+CONFIG_USB_CONFIGFS_F_LB_SS=y
+CONFIG_USB_CONFIGFS_F_FS=y
+CONFIG_USB_CONFIGFS_F_UAC1=y
+CONFIG_USB_CONFIGFS_F_UAC2=y
+CONFIG_USB_CONFIGFS_F_MIDI=y
+CONFIG_USB_CONFIGFS_F_HID=y
+CONFIG_USB_CONFIGFS_F_UVC=y
+CONFIG_USB_CONFIGFS_F_PRINTER=y
+CONFIG_USB_ZERO=m
+CONFIG_USB_AUDIO=m
+CONFIG_USB_ETH=m
+CONFIG_USB_GADGETFS=m
+CONFIG_USB_MASS_STORAGE=m
+CONFIG_USB_G_SERIAL=m
+CONFIG_USB_MIDI_GADGET=m
+CONFIG_USB_G_PRINTER=m
+CONFIG_USB_CDC_COMPOSITE=m
+CONFIG_USB_G_ACM_MS=m
+CONFIG_USB_G_MULTI=m
+CONFIG_USB_G_HID=m
+CONFIG_USB_G_WEBCAM=m
+CONFIG_MMC=y
+CONFIG_MMC_BLOCK_MINORS=32
+CONFIG_MMC_BCM2835_MMC=y
+CONFIG_MMC_BCM2835_DMA=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_MMC_SDHCI_IPROC=y
+CONFIG_MMC_SPI=m
+CONFIG_MMC_HSQ=y
+CONFIG_MMC_BCM2835=y
+CONFIG_LEDS_CLASS=y
+CONFIG_LEDS_CLASS_MULTICOLOR=m
+CONFIG_LEDS_PCA9532=m
+CONFIG_LEDS_GPIO=y
+CONFIG_LEDS_PCA955X=m
+CONFIG_LEDS_PCA963X=m
+CONFIG_LEDS_PWM=y
+CONFIG_LEDS_IS31FL32XX=m
+CONFIG_LEDS_TRIGGER_TIMER=y
+CONFIG_LEDS_TRIGGER_ONESHOT=y
+CONFIG_LEDS_TRIGGER_HEARTBEAT=y
+CONFIG_LEDS_TRIGGER_BACKLIGHT=y
+CONFIG_LEDS_TRIGGER_CPU=y
+CONFIG_LEDS_TRIGGER_DEFAULT_ON=y
+CONFIG_LEDS_TRIGGER_TRANSIENT=m
+CONFIG_LEDS_TRIGGER_CAMERA=m
+CONFIG_LEDS_TRIGGER_INPUT=y
+CONFIG_LEDS_TRIGGER_PANIC=y
+CONFIG_LEDS_TRIGGER_NETDEV=m
+CONFIG_LEDS_TRIGGER_PATTERN=m
+CONFIG_LEDS_TRIGGER_ACTPWR=y
+CONFIG_ACCESSIBILITY=y
+CONFIG_SPEAKUP=m
+CONFIG_SPEAKUP_SYNTH_SOFT=m
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_DRV_ABX80X=m
+CONFIG_RTC_DRV_DS1307=m
+CONFIG_RTC_DRV_DS1374=m
+CONFIG_RTC_DRV_DS1672=m
+CONFIG_RTC_DRV_MAX6900=m
+CONFIG_RTC_DRV_RS5C372=m
+CONFIG_RTC_DRV_ISL1208=m
+CONFIG_RTC_DRV_ISL12022=m
+CONFIG_RTC_DRV_X1205=m
+CONFIG_RTC_DRV_PCF8523=m
+CONFIG_RTC_DRV_PCF85063=m
+CONFIG_RTC_DRV_PCF85363=m
+CONFIG_RTC_DRV_PCF8563=m
+CONFIG_RTC_DRV_PCF8583=m
+CONFIG_RTC_DRV_M41T80=m
+CONFIG_RTC_DRV_BQ32K=m
+CONFIG_RTC_DRV_S35390A=m
+CONFIG_RTC_DRV_FM3130=m
+CONFIG_RTC_DRV_RX8581=m
+CONFIG_RTC_DRV_RX8025=m
+CONFIG_RTC_DRV_EM3027=m
+CONFIG_RTC_DRV_RV3028=m
+CONFIG_RTC_DRV_RV3032=m
+CONFIG_RTC_DRV_RV8803=m
+CONFIG_RTC_DRV_SD3078=m
+CONFIG_RTC_DRV_M41T93=m
+CONFIG_RTC_DRV_M41T94=m
+CONFIG_RTC_DRV_DS1302=m
+CONFIG_RTC_DRV_DS1305=m
+CONFIG_RTC_DRV_DS1390=m
+CONFIG_RTC_DRV_R9701=m
+CONFIG_RTC_DRV_RX4581=m
+CONFIG_RTC_DRV_RS5C348=m
+CONFIG_RTC_DRV_MAX6902=m
+CONFIG_RTC_DRV_PCF2123=m
+CONFIG_RTC_DRV_DS3232=m
+CONFIG_RTC_DRV_PCF2127=m
+CONFIG_RTC_DRV_RV3029C2=m
+CONFIG_DMADEVICES=y
+CONFIG_DMA_BCM2835=y
+CONFIG_DMA_BCM2708=y
+CONFIG_DMABUF_HEAPS=y
+CONFIG_DMABUF_HEAPS_SYSTEM=y
+CONFIG_DMABUF_HEAPS_CMA=y
+CONFIG_UIO=m
+CONFIG_UIO_PDRV_GENIRQ=m
+CONFIG_STAGING=y
+CONFIG_VT6656=m
+CONFIG_STAGING_MEDIA=y
+CONFIG_STAGING_MEDIA_DEPRECATED=y
+CONFIG_FB_TFT=m
+CONFIG_FB_TFT_AGM1264K_FL=m
+CONFIG_FB_TFT_BD663474=m
+CONFIG_FB_TFT_HX8340BN=m
+CONFIG_FB_TFT_HX8347D=m
+CONFIG_FB_TFT_HX8353D=m
+CONFIG_FB_TFT_HX8357D=m
+CONFIG_FB_TFT_ILI9163=m
+CONFIG_FB_TFT_ILI9320=m
+CONFIG_FB_TFT_ILI9325=m
+CONFIG_FB_TFT_ILI9340=m
+CONFIG_FB_TFT_ILI9341=m
+CONFIG_FB_TFT_ILI9481=m
+CONFIG_FB_TFT_ILI9486=m
+CONFIG_FB_TFT_PCD8544=m
+CONFIG_FB_TFT_RA8875=m
+CONFIG_FB_TFT_S6D02A1=m
+CONFIG_FB_TFT_S6D1121=m
+CONFIG_FB_TFT_SH1106=m
+CONFIG_FB_TFT_SSD1289=m
+CONFIG_FB_TFT_SSD1306=m
+CONFIG_FB_TFT_SSD1331=m
+CONFIG_FB_TFT_SSD1351=m
+CONFIG_FB_TFT_ST7735R=m
+CONFIG_FB_TFT_ST7789V=m
+CONFIG_FB_TFT_TINYLCD=m
+CONFIG_FB_TFT_TLS8204=m
+CONFIG_FB_TFT_UC1611=m
+CONFIG_FB_TFT_UC1701=m
+CONFIG_FB_TFT_UPD161704=m
+CONFIG_BCM2835_VCHIQ=y
+CONFIG_SND_BCM2835=m
+CONFIG_VIDEO_BCM2835=m
+CONFIG_VIDEO_CODEC_BCM2835=m
+CONFIG_VIDEO_ISP_BCM2835=m
+CONFIG_COMMON_CLK_SI5351=m
+CONFIG_CLK_RASPBERRYPI=y
+CONFIG_MAILBOX=y
+CONFIG_BCM2835_MBOX=y
+# CONFIG_IOMMU_SUPPORT is not set
+CONFIG_RASPBERRYPI_POWER=y
+CONFIG_IIO=m
+CONFIG_IIO_BUFFER_CB=m
+CONFIG_IIO_SW_TRIGGER=m
+CONFIG_ADXL355_I2C=m
+CONFIG_AD799X=m
+CONFIG_MCP320X=m
+CONFIG_MCP3422=m
+CONFIG_TI_ADS1015=m
+CONFIG_BME680=m
+CONFIG_CCS811=m
+CONFIG_SCD4X=m
+CONFIG_SENSIRION_SGP30=m
+CONFIG_SPS30_I2C=m
+CONFIG_MAX30102=m
+CONFIG_DHT11=m
+CONFIG_HDC100X=m
+CONFIG_HDC3020=m
+CONFIG_HTS221=m
+CONFIG_HTU21=m
+CONFIG_SI7020=m
+CONFIG_BOSCH_BNO055_I2C=m
+CONFIG_INV_MPU6050_I2C=m
+CONFIG_APDS9960=m
+CONFIG_AS73211=m
+CONFIG_BH1750=m
+CONFIG_TCS3472=m
+CONFIG_TSL4531=m
+CONFIG_VEML6030=m
+CONFIG_VEML6040=m
+CONFIG_VEML6070=m
+CONFIG_VEML6075=m
+CONFIG_IIO_HRTIMER_TRIGGER=m
+CONFIG_IIO_INTERRUPT_TRIGGER=m
+CONFIG_IIO_SYSFS_TRIGGER=m
+CONFIG_BMP280=m
+CONFIG_MS5637=m
+CONFIG_MAXIM_THERMOCOUPLE=m
+CONFIG_TMP117=m
+CONFIG_MAX31856=m
+CONFIG_PWM=y
+CONFIG_PWM_BCM2835=m
+CONFIG_PWM_GPIO=m
+CONFIG_PWM_PCA9685=m
+CONFIG_PWM_RASPBERRYPI_POE=m
+CONFIG_RPI_AXIPERF=m
+CONFIG_NVMEM_RMEM=m
+CONFIG_MUX_GPIO=m
+CONFIG_EXT4_FS=y
+CONFIG_EXT4_FS_POSIX_ACL=y
+CONFIG_EXT4_FS_SECURITY=y
+CONFIG_REISERFS_FS=m
+CONFIG_REISERFS_FS_XATTR=y
+CONFIG_REISERFS_FS_POSIX_ACL=y
+CONFIG_REISERFS_FS_SECURITY=y
+CONFIG_JFS_FS=m
+CONFIG_JFS_POSIX_ACL=y
+CONFIG_JFS_SECURITY=y
+CONFIG_JFS_STATISTICS=y
+CONFIG_XFS_FS=m
+CONFIG_XFS_QUOTA=y
+CONFIG_XFS_POSIX_ACL=y
+CONFIG_XFS_RT=y
+CONFIG_GFS2_FS=m
+CONFIG_OCFS2_FS=m
+CONFIG_BTRFS_FS=m
+CONFIG_BTRFS_FS_POSIX_ACL=y
+CONFIG_NILFS2_FS=m
+CONFIG_F2FS_FS=y
+CONFIG_F2FS_FS_SECURITY=y
+CONFIG_BCACHEFS_FS=m
+CONFIG_BCACHEFS_QUOTA=y
+CONFIG_BCACHEFS_POSIX_ACL=y
+CONFIG_FS_ENCRYPTION=y
+CONFIG_FANOTIFY=y
+CONFIG_QFMT_V1=m
+CONFIG_QFMT_V2=m
+CONFIG_AUTOFS_FS=y
+CONFIG_FUSE_FS=m
+CONFIG_CUSE=m
+CONFIG_OVERLAY_FS=m
+CONFIG_FSCACHE=y
+CONFIG_FSCACHE_STATS=y
+CONFIG_CACHEFILES=m
+CONFIG_ISO9660_FS=m
+CONFIG_JOLIET=y
+CONFIG_ZISOFS=y
+CONFIG_UDF_FS=m
+CONFIG_MSDOS_FS=y
+CONFIG_VFAT_FS=y
+CONFIG_FAT_DEFAULT_IOCHARSET="ascii"
+CONFIG_EXFAT_FS=m
+CONFIG_NTFS3_FS=m
+CONFIG_TMPFS=y
+CONFIG_TMPFS_POSIX_ACL=y
+CONFIG_ECRYPT_FS=m
+CONFIG_HFS_FS=m
+CONFIG_HFSPLUS_FS=m
+CONFIG_JFFS2_FS=m
+CONFIG_JFFS2_SUMMARY=y
+CONFIG_UBIFS_FS=m
+CONFIG_SQUASHFS=m
+CONFIG_SQUASHFS_XATTR=y
+CONFIG_SQUASHFS_LZO=y
+CONFIG_SQUASHFS_XZ=y
+CONFIG_SQUASHFS_ZSTD=y
+CONFIG_PSTORE=y
+CONFIG_PSTORE_CONSOLE=y
+CONFIG_PSTORE_RAM=y
+CONFIG_EROFS_FS=m
+CONFIG_EROFS_FS_ZIP_ZSTD=y
+CONFIG_NFS_FS=y
+CONFIG_NFS_V2=y
+CONFIG_NFS_V3_ACL=y
+CONFIG_NFS_V4=y
+CONFIG_NFS_SWAP=y
+CONFIG_NFS_V4_1=y
+CONFIG_NFS_V4_2=y
+CONFIG_ROOT_NFS=y
+CONFIG_NFSD=m
+CONFIG_NFSD_V2=y
+CONFIG_NFSD_V2_ACL=y
+CONFIG_NFSD_V3_ACL=y
+CONFIG_NFSD_V4=y
+CONFIG_CEPH_FS=m
+CONFIG_CIFS=m
+CONFIG_CIFS_UPCALL=y
+CONFIG_CIFS_XATTR=y
+CONFIG_CIFS_POSIX=y
+CONFIG_CIFS_DFS_UPCALL=y
+CONFIG_CIFS_FSCACHE=y
+CONFIG_SMB_SERVER=m
+CONFIG_9P_FS=m
+CONFIG_9P_FS_POSIX_ACL=y
+CONFIG_NLS_DEFAULT="utf8"
+CONFIG_NLS_CODEPAGE_437=y
+CONFIG_NLS_CODEPAGE_737=m
+CONFIG_NLS_CODEPAGE_775=m
+CONFIG_NLS_CODEPAGE_850=m
+CONFIG_NLS_CODEPAGE_852=m
+CONFIG_NLS_CODEPAGE_855=m
+CONFIG_NLS_CODEPAGE_857=m
+CONFIG_NLS_CODEPAGE_860=m
+CONFIG_NLS_CODEPAGE_861=m
+CONFIG_NLS_CODEPAGE_862=m
+CONFIG_NLS_CODEPAGE_863=m
+CONFIG_NLS_CODEPAGE_864=m
+CONFIG_NLS_CODEPAGE_865=m
+CONFIG_NLS_CODEPAGE_866=m
+CONFIG_NLS_CODEPAGE_869=m
+CONFIG_NLS_CODEPAGE_936=m
+CONFIG_NLS_CODEPAGE_950=m
+CONFIG_NLS_CODEPAGE_932=m
+CONFIG_NLS_CODEPAGE_949=m
+CONFIG_NLS_CODEPAGE_874=m
+CONFIG_NLS_ISO8859_8=m
+CONFIG_NLS_CODEPAGE_1250=m
+CONFIG_NLS_CODEPAGE_1251=m
+CONFIG_NLS_ASCII=y
+CONFIG_NLS_ISO8859_1=m
+CONFIG_NLS_ISO8859_2=m
+CONFIG_NLS_ISO8859_3=m
+CONFIG_NLS_ISO8859_4=m
+CONFIG_NLS_ISO8859_5=m
+CONFIG_NLS_ISO8859_6=m
+CONFIG_NLS_ISO8859_7=m
+CONFIG_NLS_ISO8859_9=m
+CONFIG_NLS_ISO8859_13=m
+CONFIG_NLS_ISO8859_14=m
+CONFIG_NLS_ISO8859_15=m
+CONFIG_NLS_KOI8_R=m
+CONFIG_NLS_KOI8_U=m
+CONFIG_DLM=m
+CONFIG_UNICODE=m
+CONFIG_KEY_DH_OPERATIONS=y
+CONFIG_SECURITY=y
+CONFIG_SECURITY_APPARMOR=y
+CONFIG_LSM=""
+CONFIG_CRYPTO_USER=m
+CONFIG_CRYPTO_AES=m
+CONFIG_CRYPTO_CAST5=m
+CONFIG_CRYPTO_DES=y
+CONFIG_CRYPTO_TWOFISH=m
+CONFIG_CRYPTO_ADIANTUM=m
+CONFIG_CRYPTO_CHACHA20POLY1305=m
+CONFIG_CRYPTO_MD4=m
+CONFIG_CRYPTO_SHA512=m
+CONFIG_CRYPTO_WP512=m
+CONFIG_CRYPTO_XCBC=m
+CONFIG_CRYPTO_LZ4=m
+CONFIG_CRYPTO_LZ4HC=m
+CONFIG_CRYPTO_USER_API_HASH=m
+CONFIG_CRYPTO_USER_API_SKCIPHER=m
+CONFIG_CRYPTO_USER_API_RNG=m
+CONFIG_CRYPTO_USER_API_AEAD=m
+CONFIG_CRYPTO_NHPOLY1305_NEON=m
+CONFIG_CRYPTO_SHA1_ARM_NEON=m
+CONFIG_CRYPTO_AES_ARM_BS=m
+# CONFIG_CRYPTO_HW is not set
+CONFIG_PKCS8_PRIVATE_KEY_PARSER=m
+CONFIG_CRC_ITU_T=y
+CONFIG_LIBCRC32C=y
+CONFIG_DMA_CMA=y
+CONFIG_CMA_SIZE_MBYTES=5
+CONFIG_PRINTK_TIME=y
+CONFIG_BOOT_PRINTK_DELAY=y
+CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1f6
+CONFIG_KGDB=y
+CONFIG_KGDB_KDB=y
+CONFIG_KDB_KEYBOARD=y
+CONFIG_DEBUG_MEMORY_INIT=y
+CONFIG_DETECT_HUNG_TASK=y
+# CONFIG_RCU_TRACE is not set
+CONFIG_LATENCYTOP=y
+CONFIG_FUNCTION_PROFILER=y
+CONFIG_STACK_TRACER=y
+CONFIG_SCHED_TRACER=y
+CONFIG_FTRACE_SYSCALLS=y
+CONFIG_BLK_DEV_IO_TRACE=y
+# CONFIG_UPROBE_EVENTS is not set
diff --git a/arch/arm/configs/bcmrpi_defconfig b/arch/arm/configs/bcmrpi_defconfig
new file mode 100644
index 000000000000..f11a6bc5b5ac
--- /dev/null
+++ b/arch/arm/configs/bcmrpi_defconfig
@@ -0,0 +1,1641 @@
+# CONFIG_LOCALVERSION_AUTO is not set
+CONFIG_SYSVIPC=y
+CONFIG_POSIX_MQUEUE=y
+CONFIG_GENERIC_IRQ_DEBUGFS=y
+CONFIG_NO_HZ=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_BPF_SYSCALL=y
+CONFIG_PREEMPT_VOLUNTARY=y
+CONFIG_BSD_PROCESS_ACCT=y
+CONFIG_BSD_PROCESS_ACCT_V3=y
+CONFIG_TASKSTATS=y
+CONFIG_TASK_DELAY_ACCT=y
+CONFIG_TASK_XACCT=y
+CONFIG_TASK_IO_ACCOUNTING=y
+CONFIG_PSI=y
+CONFIG_PSI_DEFAULT_DISABLED=y
+CONFIG_IKCONFIG=m
+CONFIG_IKCONFIG_PROC=y
+CONFIG_MEMCG=y
+CONFIG_BLK_CGROUP=y
+CONFIG_CFS_BANDWIDTH=y
+CONFIG_CGROUP_PIDS=y
+CONFIG_CGROUP_FREEZER=y
+CONFIG_CGROUP_DEVICE=y
+CONFIG_CGROUP_CPUACCT=y
+CONFIG_CGROUP_PERF=y
+CONFIG_CGROUP_BPF=y
+CONFIG_NAMESPACES=y
+CONFIG_USER_NS=y
+CONFIG_SCHED_AUTOGROUP=y
+CONFIG_BLK_DEV_INITRD=y
+CONFIG_EXPERT=y
+CONFIG_PROFILING=y
+CONFIG_ARCH_MULTI_V6=y
+# CONFIG_ARCH_MULTI_V7 is not set
+CONFIG_ARCH_BCM=y
+CONFIG_ARCH_BCM2835=y
+# CONFIG_CACHE_L2X0 is not set
+CONFIG_UACCESS_WITH_MEMCPY=y
+# CONFIG_ATAGS is not set
+CONFIG_CMDLINE="console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait"
+CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_STAT=y
+CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y
+CONFIG_CPU_FREQ_GOV_POWERSAVE=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
+CONFIG_CPUFREQ_DT=y
+CONFIG_ARM_RASPBERRYPI_CPUFREQ=y
+CONFIG_VFP=y
+# CONFIG_SUSPEND is not set
+CONFIG_PM=y
+CONFIG_JUMP_LABEL=y
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_MODVERSIONS=y
+CONFIG_MODULE_SRCVERSION_ALL=y
+CONFIG_MODULE_COMPRESS=y
+CONFIG_MODULE_COMPRESS_XZ=y
+CONFIG_PARTITION_ADVANCED=y
+CONFIG_MAC_PARTITION=y
+CONFIG_BINFMT_MISC=m
+CONFIG_ZSWAP=y
+# CONFIG_COMPAT_BRK is not set
+CONFIG_CMA=y
+CONFIG_CMA_AREAS=7
+CONFIG_LRU_GEN=y
+CONFIG_LRU_GEN_ENABLED=y
+CONFIG_NET=y
+CONFIG_PACKET=y
+CONFIG_XFRM_USER=y
+CONFIG_XFRM_INTERFACE=m
+CONFIG_XFRM_SUB_POLICY=y
+CONFIG_XFRM_STATISTICS=y
+CONFIG_NET_KEY=m
+CONFIG_IP_MULTICAST=y
+CONFIG_IP_ADVANCED_ROUTER=y
+CONFIG_IP_MULTIPLE_TABLES=y
+CONFIG_IP_ROUTE_MULTIPATH=y
+CONFIG_IP_ROUTE_VERBOSE=y
+CONFIG_IP_PNP=y
+CONFIG_IP_PNP_DHCP=y
+CONFIG_IP_PNP_RARP=y
+CONFIG_NET_IPIP=m
+CONFIG_NET_IPGRE_DEMUX=m
+CONFIG_NET_IPGRE=m
+CONFIG_IP_MROUTE=y
+CONFIG_IP_MROUTE_MULTIPLE_TABLES=y
+CONFIG_IP_PIMSM_V1=y
+CONFIG_IP_PIMSM_V2=y
+CONFIG_NET_IPVTI=m
+CONFIG_NET_FOU_IP_TUNNELS=y
+CONFIG_INET_AH=m
+CONFIG_INET_ESP=m
+CONFIG_INET_IPCOMP=m
+CONFIG_INET_DIAG=m
+CONFIG_TCP_CONG_ADVANCED=y
+CONFIG_TCP_CONG_BBR=m
+CONFIG_IPV6=m
+CONFIG_IPV6_ROUTER_PREF=y
+CONFIG_IPV6_ROUTE_INFO=y
+CONFIG_INET6_AH=m
+CONFIG_INET6_ESP=m
+CONFIG_INET6_ESP_OFFLOAD=m
+CONFIG_INET6_IPCOMP=m
+CONFIG_IPV6_ILA=m
+CONFIG_IPV6_VTI=m
+CONFIG_IPV6_SIT_6RD=y
+CONFIG_IPV6_GRE=m
+CONFIG_IPV6_MULTIPLE_TABLES=y
+CONFIG_IPV6_SUBTREES=y
+CONFIG_IPV6_MROUTE=y
+CONFIG_IPV6_MROUTE_MULTIPLE_TABLES=y
+CONFIG_IPV6_PIMSM_V2=y
+CONFIG_NETFILTER=y
+CONFIG_BRIDGE_NETFILTER=m
+CONFIG_NF_CONNTRACK=m
+CONFIG_NF_CONNTRACK_ZONES=y
+CONFIG_NF_CONNTRACK_EVENTS=y
+CONFIG_NF_CONNTRACK_TIMESTAMP=y
+CONFIG_NF_CONNTRACK_AMANDA=m
+CONFIG_NF_CONNTRACK_FTP=m
+CONFIG_NF_CONNTRACK_H323=m
+CONFIG_NF_CONNTRACK_IRC=m
+CONFIG_NF_CONNTRACK_NETBIOS_NS=m
+CONFIG_NF_CONNTRACK_SNMP=m
+CONFIG_NF_CONNTRACK_PPTP=m
+CONFIG_NF_CONNTRACK_SANE=m
+CONFIG_NF_CONNTRACK_SIP=m
+CONFIG_NF_CONNTRACK_TFTP=m
+CONFIG_NF_CT_NETLINK=m
+CONFIG_NF_TABLES=m
+CONFIG_NF_TABLES_INET=y
+CONFIG_NF_TABLES_NETDEV=y
+CONFIG_NFT_NUMGEN=m
+CONFIG_NFT_CT=m
+CONFIG_NFT_FLOW_OFFLOAD=m
+CONFIG_NFT_CONNLIMIT=m
+CONFIG_NFT_LOG=m
+CONFIG_NFT_LIMIT=m
+CONFIG_NFT_MASQ=m
+CONFIG_NFT_REDIR=m
+CONFIG_NFT_NAT=m
+CONFIG_NFT_TUNNEL=m
+CONFIG_NFT_QUEUE=m
+CONFIG_NFT_QUOTA=m
+CONFIG_NFT_REJECT=m
+CONFIG_NFT_COMPAT=m
+CONFIG_NFT_HASH=m
+CONFIG_NFT_FIB_INET=m
+CONFIG_NFT_XFRM=m
+CONFIG_NFT_SOCKET=m
+CONFIG_NFT_OSF=m
+CONFIG_NFT_TPROXY=m
+CONFIG_NFT_SYNPROXY=m
+CONFIG_NFT_DUP_NETDEV=m
+CONFIG_NFT_FWD_NETDEV=m
+CONFIG_NFT_FIB_NETDEV=m
+CONFIG_NF_FLOW_TABLE_INET=m
+CONFIG_NF_FLOW_TABLE=m
+CONFIG_NETFILTER_XT_SET=m
+CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m
+CONFIG_NETFILTER_XT_TARGET_CLASSIFY=m
+CONFIG_NETFILTER_XT_TARGET_CONNMARK=m
+CONFIG_NETFILTER_XT_TARGET_DSCP=m
+CONFIG_NETFILTER_XT_TARGET_HMARK=m
+CONFIG_NETFILTER_XT_TARGET_IDLETIMER=m
+CONFIG_NETFILTER_XT_TARGET_LED=m
+CONFIG_NETFILTER_XT_TARGET_LOG=m
+CONFIG_NETFILTER_XT_TARGET_MARK=m
+CONFIG_NETFILTER_XT_TARGET_NFLOG=m
+CONFIG_NETFILTER_XT_TARGET_NFQUEUE=m
+CONFIG_NETFILTER_XT_TARGET_NOTRACK=m
+CONFIG_NETFILTER_XT_TARGET_TEE=m
+CONFIG_NETFILTER_XT_TARGET_TPROXY=m
+CONFIG_NETFILTER_XT_TARGET_TRACE=m
+CONFIG_NETFILTER_XT_TARGET_TCPMSS=m
+CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=m
+CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m
+CONFIG_NETFILTER_XT_MATCH_BPF=m
+CONFIG_NETFILTER_XT_MATCH_CLUSTER=m
+CONFIG_NETFILTER_XT_MATCH_COMMENT=m
+CONFIG_NETFILTER_XT_MATCH_CONNBYTES=m
+CONFIG_NETFILTER_XT_MATCH_CONNLABEL=m
+CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=m
+CONFIG_NETFILTER_XT_MATCH_CONNMARK=m
+CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m
+CONFIG_NETFILTER_XT_MATCH_CPU=m
+CONFIG_NETFILTER_XT_MATCH_DCCP=m
+CONFIG_NETFILTER_XT_MATCH_DEVGROUP=m
+CONFIG_NETFILTER_XT_MATCH_DSCP=m
+CONFIG_NETFILTER_XT_MATCH_ESP=m
+CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=m
+CONFIG_NETFILTER_XT_MATCH_HELPER=m
+CONFIG_NETFILTER_XT_MATCH_IPRANGE=m
+CONFIG_NETFILTER_XT_MATCH_IPVS=m
+CONFIG_NETFILTER_XT_MATCH_LENGTH=m
+CONFIG_NETFILTER_XT_MATCH_LIMIT=m
+CONFIG_NETFILTER_XT_MATCH_MAC=m
+CONFIG_NETFILTER_XT_MATCH_MARK=m
+CONFIG_NETFILTER_XT_MATCH_MULTIPORT=m
+CONFIG_NETFILTER_XT_MATCH_NFACCT=m
+CONFIG_NETFILTER_XT_MATCH_OSF=m
+CONFIG_NETFILTER_XT_MATCH_OWNER=m
+CONFIG_NETFILTER_XT_MATCH_POLICY=m
+CONFIG_NETFILTER_XT_MATCH_PHYSDEV=m
+CONFIG_NETFILTER_XT_MATCH_PKTTYPE=m
+CONFIG_NETFILTER_XT_MATCH_QUOTA=m
+CONFIG_NETFILTER_XT_MATCH_RATEEST=m
+CONFIG_NETFILTER_XT_MATCH_REALM=m
+CONFIG_NETFILTER_XT_MATCH_RECENT=m
+CONFIG_NETFILTER_XT_MATCH_SOCKET=m
+CONFIG_NETFILTER_XT_MATCH_STATE=m
+CONFIG_NETFILTER_XT_MATCH_STATISTIC=m
+CONFIG_NETFILTER_XT_MATCH_STRING=m
+CONFIG_NETFILTER_XT_MATCH_TCPMSS=m
+CONFIG_NETFILTER_XT_MATCH_TIME=m
+CONFIG_NETFILTER_XT_MATCH_U32=m
+CONFIG_IP_SET=m
+CONFIG_IP_SET_BITMAP_IP=m
+CONFIG_IP_SET_BITMAP_IPMAC=m
+CONFIG_IP_SET_BITMAP_PORT=m
+CONFIG_IP_SET_HASH_IP=m
+CONFIG_IP_SET_HASH_IPPORT=m
+CONFIG_IP_SET_HASH_IPPORTIP=m
+CONFIG_IP_SET_HASH_IPPORTNET=m
+CONFIG_IP_SET_HASH_NET=m
+CONFIG_IP_SET_HASH_NETPORT=m
+CONFIG_IP_SET_HASH_NETIFACE=m
+CONFIG_IP_SET_LIST_SET=m
+CONFIG_IP_VS=m
+CONFIG_IP_VS_IPV6=y
+CONFIG_IP_VS_PROTO_TCP=y
+CONFIG_IP_VS_PROTO_UDP=y
+CONFIG_IP_VS_PROTO_ESP=y
+CONFIG_IP_VS_PROTO_AH=y
+CONFIG_IP_VS_PROTO_SCTP=y
+CONFIG_IP_VS_RR=m
+CONFIG_IP_VS_WRR=m
+CONFIG_IP_VS_LC=m
+CONFIG_IP_VS_WLC=m
+CONFIG_IP_VS_LBLC=m
+CONFIG_IP_VS_LBLCR=m
+CONFIG_IP_VS_DH=m
+CONFIG_IP_VS_SH=m
+CONFIG_IP_VS_SED=m
+CONFIG_IP_VS_NQ=m
+CONFIG_IP_VS_FTP=m
+CONFIG_IP_VS_PE_SIP=m
+CONFIG_NFT_DUP_IPV4=m
+CONFIG_NFT_FIB_IPV4=m
+CONFIG_NF_TABLES_ARP=y
+CONFIG_NF_LOG_ARP=m
+CONFIG_NF_LOG_IPV4=m
+CONFIG_IP_NF_IPTABLES=m
+CONFIG_IP_NF_MATCH_AH=m
+CONFIG_IP_NF_MATCH_ECN=m
+CONFIG_IP_NF_MATCH_RPFILTER=m
+CONFIG_IP_NF_MATCH_TTL=m
+CONFIG_IP_NF_FILTER=m
+CONFIG_IP_NF_TARGET_REJECT=m
+CONFIG_IP_NF_TARGET_SYNPROXY=m
+CONFIG_IP_NF_NAT=m
+CONFIG_IP_NF_TARGET_MASQUERADE=m
+CONFIG_IP_NF_TARGET_NETMAP=m
+CONFIG_IP_NF_TARGET_REDIRECT=m
+CONFIG_IP_NF_MANGLE=m
+CONFIG_IP_NF_TARGET_ECN=m
+CONFIG_IP_NF_TARGET_TTL=m
+CONFIG_IP_NF_RAW=m
+CONFIG_IP_NF_ARPFILTER=m
+CONFIG_IP_NF_ARP_MANGLE=m
+CONFIG_NFT_DUP_IPV6=m
+CONFIG_NFT_FIB_IPV6=m
+CONFIG_IP6_NF_IPTABLES=m
+CONFIG_IP6_NF_MATCH_AH=m
+CONFIG_IP6_NF_MATCH_EUI64=m
+CONFIG_IP6_NF_MATCH_FRAG=m
+CONFIG_IP6_NF_MATCH_OPTS=m
+CONFIG_IP6_NF_MATCH_HL=m
+CONFIG_IP6_NF_MATCH_IPV6HEADER=m
+CONFIG_IP6_NF_MATCH_MH=m
+CONFIG_IP6_NF_MATCH_RPFILTER=m
+CONFIG_IP6_NF_MATCH_RT=m
+CONFIG_IP6_NF_MATCH_SRH=m
+CONFIG_IP6_NF_TARGET_HL=m
+CONFIG_IP6_NF_FILTER=m
+CONFIG_IP6_NF_TARGET_REJECT=m
+CONFIG_IP6_NF_TARGET_SYNPROXY=m
+CONFIG_IP6_NF_MANGLE=m
+CONFIG_IP6_NF_RAW=m
+CONFIG_IP6_NF_SECURITY=m
+CONFIG_IP6_NF_NAT=m
+CONFIG_IP6_NF_TARGET_MASQUERADE=m
+CONFIG_IP6_NF_TARGET_NPT=m
+CONFIG_NF_TABLES_BRIDGE=m
+CONFIG_NFT_BRIDGE_REJECT=m
+CONFIG_BRIDGE_NF_EBTABLES=m
+CONFIG_BRIDGE_EBT_BROUTE=m
+CONFIG_BRIDGE_EBT_T_FILTER=m
+CONFIG_BRIDGE_EBT_T_NAT=m
+CONFIG_BRIDGE_EBT_802_3=m
+CONFIG_BRIDGE_EBT_AMONG=m
+CONFIG_BRIDGE_EBT_ARP=m
+CONFIG_BRIDGE_EBT_IP=m
+CONFIG_BRIDGE_EBT_IP6=m
+CONFIG_BRIDGE_EBT_LIMIT=m
+CONFIG_BRIDGE_EBT_MARK=m
+CONFIG_BRIDGE_EBT_PKTTYPE=m
+CONFIG_BRIDGE_EBT_STP=m
+CONFIG_BRIDGE_EBT_VLAN=m
+CONFIG_BRIDGE_EBT_ARPREPLY=m
+CONFIG_BRIDGE_EBT_DNAT=m
+CONFIG_BRIDGE_EBT_MARK_T=m
+CONFIG_BRIDGE_EBT_REDIRECT=m
+CONFIG_BRIDGE_EBT_SNAT=m
+CONFIG_BRIDGE_EBT_LOG=m
+CONFIG_BRIDGE_EBT_NFLOG=m
+CONFIG_SCTP_COOKIE_HMAC_SHA1=y
+CONFIG_ATM=m
+CONFIG_L2TP=m
+CONFIG_L2TP_V3=y
+CONFIG_L2TP_IP=m
+CONFIG_L2TP_ETH=m
+CONFIG_BRIDGE=m
+CONFIG_BRIDGE_VLAN_FILTERING=y
+CONFIG_VLAN_8021Q=m
+CONFIG_VLAN_8021Q_GVRP=y
+CONFIG_ATALK=m
+CONFIG_6LOWPAN=m
+CONFIG_IEEE802154=m
+CONFIG_IEEE802154_6LOWPAN=m
+CONFIG_MAC802154=m
+CONFIG_NET_SCHED=y
+CONFIG_NET_SCH_HTB=m
+CONFIG_NET_SCH_HFSC=m
+CONFIG_NET_SCH_PRIO=m
+CONFIG_NET_SCH_MULTIQ=m
+CONFIG_NET_SCH_RED=m
+CONFIG_NET_SCH_SFB=m
+CONFIG_NET_SCH_SFQ=m
+CONFIG_NET_SCH_TEQL=m
+CONFIG_NET_SCH_TBF=m
+CONFIG_NET_SCH_GRED=m
+CONFIG_NET_SCH_NETEM=m
+CONFIG_NET_SCH_DRR=m
+CONFIG_NET_SCH_MQPRIO=m
+CONFIG_NET_SCH_CHOKE=m
+CONFIG_NET_SCH_QFQ=m
+CONFIG_NET_SCH_CODEL=m
+CONFIG_NET_SCH_FQ_CODEL=m
+CONFIG_NET_SCH_CAKE=m
+CONFIG_NET_SCH_FQ=m
+CONFIG_NET_SCH_HHF=m
+CONFIG_NET_SCH_PIE=m
+CONFIG_NET_SCH_INGRESS=m
+CONFIG_NET_SCH_PLUG=m
+CONFIG_NET_CLS_BASIC=m
+CONFIG_NET_CLS_ROUTE4=m
+CONFIG_NET_CLS_FW=m
+CONFIG_NET_CLS_U32=m
+CONFIG_CLS_U32_PERF=y
+CONFIG_CLS_U32_MARK=y
+CONFIG_NET_CLS_FLOW=m
+CONFIG_NET_CLS_CGROUP=m
+CONFIG_NET_CLS_FLOWER=m
+CONFIG_NET_CLS_MATCHALL=m
+CONFIG_NET_EMATCH=y
+CONFIG_NET_EMATCH_CMP=m
+CONFIG_NET_EMATCH_NBYTE=m
+CONFIG_NET_EMATCH_U32=m
+CONFIG_NET_EMATCH_META=m
+CONFIG_NET_EMATCH_TEXT=m
+CONFIG_NET_EMATCH_IPSET=m
+CONFIG_NET_CLS_ACT=y
+CONFIG_NET_ACT_POLICE=m
+CONFIG_NET_ACT_GACT=m
+CONFIG_GACT_PROB=y
+CONFIG_NET_ACT_MIRRED=m
+CONFIG_NET_ACT_NAT=m
+CONFIG_NET_ACT_PEDIT=m
+CONFIG_NET_ACT_SIMP=m
+CONFIG_NET_ACT_SKBEDIT=m
+CONFIG_NET_ACT_CSUM=m
+CONFIG_BATMAN_ADV=m
+CONFIG_OPENVSWITCH=m
+CONFIG_CGROUP_NET_PRIO=y
+CONFIG_NET_PKTGEN=m
+CONFIG_HAMRADIO=y
+CONFIG_AX25=m
+CONFIG_NETROM=m
+CONFIG_ROSE=m
+CONFIG_MKISS=m
+CONFIG_6PACK=m
+CONFIG_BPQETHER=m
+CONFIG_BAYCOM_SER_FDX=m
+CONFIG_BAYCOM_SER_HDX=m
+CONFIG_YAM=m
+CONFIG_CAN=m
+CONFIG_CAN_J1939=m
+CONFIG_CAN_ISOTP=m
+CONFIG_BT=m
+CONFIG_BT_RFCOMM=m
+CONFIG_BT_RFCOMM_TTY=y
+CONFIG_BT_BNEP=m
+CONFIG_BT_BNEP_MC_FILTER=y
+CONFIG_BT_BNEP_PROTO_FILTER=y
+CONFIG_BT_HIDP=m
+CONFIG_BT_6LOWPAN=m
+CONFIG_BT_HCIBTUSB=m
+CONFIG_BT_HCIBTUSB_MTK=y
+CONFIG_BT_HCIUART=m
+CONFIG_BT_HCIUART_3WIRE=y
+CONFIG_BT_HCIUART_BCM=y
+CONFIG_BT_HCIBCM203X=m
+CONFIG_BT_HCIBPA10X=m
+CONFIG_BT_HCIBFUSB=m
+CONFIG_BT_HCIVHCI=m
+CONFIG_BT_MRVL=m
+CONFIG_BT_MRVL_SDIO=m
+CONFIG_BT_ATH3K=m
+CONFIG_CFG80211=m
+CONFIG_CFG80211_WEXT=y
+CONFIG_MAC80211=m
+CONFIG_MAC80211_MESH=y
+CONFIG_RFKILL=m
+CONFIG_RFKILL_INPUT=y
+CONFIG_NET_9P=m
+CONFIG_NFC=m
+CONFIG_UEVENT_HELPER=y
+CONFIG_DEVTMPFS=y
+CONFIG_DEVTMPFS_MOUNT=y
+CONFIG_RASPBERRYPI_FIRMWARE=y
+CONFIG_MTD=m
+CONFIG_MTD_BLOCK=m
+CONFIG_MTD_BLOCK2MTD=m
+CONFIG_MTD_SPI_NAND=m
+CONFIG_MTD_SPI_NOR=m
+CONFIG_MTD_UBI=m
+CONFIG_OF_CONFIGFS=y
+CONFIG_ZRAM=m
+CONFIG_ZRAM_BACKEND_LZ4=y
+CONFIG_ZRAM_BACKEND_ZSTD=y
+CONFIG_ZRAM_BACKEND_LZO=y
+CONFIG_ZRAM_DEF_COMP_ZSTD=y
+CONFIG_ZRAM_WRITEBACK=y
+CONFIG_ZRAM_MULTI_COMP=y
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BLK_DEV_DRBD=m
+CONFIG_BLK_DEV_NBD=m
+CONFIG_BLK_DEV_RAM=y
+CONFIG_ATA_OVER_ETH=m
+CONFIG_BLK_DEV_RBD=m
+CONFIG_EEPROM_AT24=m
+CONFIG_EEPROM_AT25=m
+CONFIG_TI_ST=m
+CONFIG_SCSI=y
+# CONFIG_SCSI_PROC_FS is not set
+CONFIG_BLK_DEV_SD=y
+CONFIG_CHR_DEV_ST=m
+CONFIG_BLK_DEV_SR=m
+CONFIG_CHR_DEV_SG=m
+CONFIG_SCSI_ISCSI_ATTRS=y
+CONFIG_ISCSI_TCP=m
+CONFIG_ISCSI_BOOT_SYSFS=m
+CONFIG_ATA=m
+CONFIG_MD=y
+CONFIG_BCACHE=m
+CONFIG_BLK_DEV_DM=m
+CONFIG_DM_CRYPT=m
+CONFIG_DM_SNAPSHOT=m
+CONFIG_DM_THIN_PROVISIONING=m
+CONFIG_DM_CACHE=m
+CONFIG_DM_MIRROR=m
+CONFIG_DM_LOG_USERSPACE=m
+CONFIG_DM_RAID=m
+CONFIG_DM_ZERO=m
+CONFIG_DM_MULTIPATH=m
+CONFIG_DM_DELAY=m
+CONFIG_DM_VERITY=m
+CONFIG_DM_INTEGRITY=m
+CONFIG_NETDEVICES=y
+CONFIG_BONDING=m
+CONFIG_DUMMY=m
+CONFIG_WIREGUARD=m
+CONFIG_IFB=m
+CONFIG_MACVLAN=m
+CONFIG_IPVLAN=m
+CONFIG_VXLAN=m
+CONFIG_NETCONSOLE=m
+CONFIG_TUN=m
+CONFIG_VETH=m
+CONFIG_NETKIT=y
+CONFIG_NET_VRF=m
+CONFIG_ENC28J60=m
+CONFIG_QCA7000_SPI=m
+CONFIG_QCA7000_UART=m
+CONFIG_MSE102X=m
+CONFIG_WIZNET_W5100=m
+CONFIG_WIZNET_W5100_SPI=m
+CONFIG_CAN_VCAN=m
+CONFIG_CAN_SLCAN=m
+CONFIG_CAN_MCP251X=m
+CONFIG_CAN_MCP251XFD=m
+CONFIG_CAN_8DEV_USB=m
+CONFIG_CAN_EMS_USB=m
+CONFIG_CAN_GS_USB=m
+CONFIG_CAN_PEAK_USB=m
+CONFIG_MDIO_BITBANG=m
+CONFIG_PPP=m
+CONFIG_PPP_BSDCOMP=m
+CONFIG_PPP_DEFLATE=m
+CONFIG_PPP_FILTER=y
+CONFIG_PPP_MPPE=m
+CONFIG_PPP_MULTILINK=y
+CONFIG_PPPOATM=m
+CONFIG_PPPOE=m
+CONFIG_PPPOL2TP=m
+CONFIG_PPP_ASYNC=m
+CONFIG_PPP_SYNC_TTY=m
+CONFIG_SLIP=m
+CONFIG_SLIP_COMPRESSED=y
+CONFIG_SLIP_SMART=y
+CONFIG_USB_CATC=m
+CONFIG_USB_KAWETH=m
+CONFIG_USB_PEGASUS=m
+CONFIG_USB_RTL8150=m
+CONFIG_USB_RTL8152=m
+CONFIG_USB_LAN78XX=m
+CONFIG_USB_USBNET=y
+CONFIG_USB_NET_AX8817X=m
+CONFIG_USB_NET_AX88179_178A=m
+CONFIG_USB_NET_CDCETHER=m
+CONFIG_USB_NET_CDC_EEM=m
+CONFIG_USB_NET_CDC_NCM=m
+CONFIG_USB_NET_HUAWEI_CDC_NCM=m
+CONFIG_USB_NET_CDC_MBIM=m
+CONFIG_USB_NET_DM9601=m
+CONFIG_USB_NET_SR9700=m
+CONFIG_USB_NET_SR9800=m
+CONFIG_USB_NET_SMSC75XX=m
+CONFIG_USB_NET_SMSC95XX=y
+CONFIG_USB_NET_GL620A=m
+CONFIG_USB_NET_NET1080=m
+CONFIG_USB_NET_PLUSB=m
+CONFIG_USB_NET_MCS7830=m
+CONFIG_USB_NET_RNDIS_HOST=m
+CONFIG_USB_NET_CDC_SUBSET=m
+CONFIG_USB_ALI_M5632=y
+CONFIG_USB_AN2720=y
+CONFIG_USB_EPSON2888=y
+CONFIG_USB_KC2190=y
+CONFIG_USB_NET_ZAURUS=m
+CONFIG_USB_NET_CX82310_ETH=m
+CONFIG_USB_NET_KALMIA=m
+CONFIG_USB_NET_QMI_WWAN=m
+CONFIG_USB_HSO=m
+CONFIG_USB_NET_INT51X1=m
+CONFIG_USB_IPHETH=m
+CONFIG_USB_SIERRA_NET=m
+CONFIG_USB_VL600=m
+CONFIG_USB_NET_AQC111=m
+CONFIG_ATH9K=m
+CONFIG_ATH9K_HTC=m
+CONFIG_CARL9170=m
+CONFIG_ATH6KL=m
+CONFIG_ATH6KL_USB=m
+CONFIG_AR5523=m
+CONFIG_AT76C50X_USB=m
+CONFIG_B43=m
+# CONFIG_B43_PHY_N is not set
+CONFIG_B43LEGACY=m
+CONFIG_BRCMFMAC=m
+CONFIG_BRCMFMAC_USB=y
+CONFIG_BRCMDBG=y
+CONFIG_P54_COMMON=m
+CONFIG_P54_USB=m
+CONFIG_LIBERTAS=m
+CONFIG_LIBERTAS_USB=m
+CONFIG_LIBERTAS_SDIO=m
+CONFIG_LIBERTAS_THINFIRM=m
+CONFIG_LIBERTAS_THINFIRM_USB=m
+CONFIG_MWIFIEX=m
+CONFIG_MWIFIEX_SDIO=m
+CONFIG_MT7601U=m
+CONFIG_MT76x0U=m
+CONFIG_MT76x2U=m
+CONFIG_MT7921U=m
+CONFIG_MT7925U=m
+CONFIG_RT2X00=m
+CONFIG_RT2500USB=m
+CONFIG_RT73USB=m
+CONFIG_RT2800USB=m
+CONFIG_RT2800USB_RT3573=y
+CONFIG_RT2800USB_RT53XX=y
+CONFIG_RT2800USB_RT55XX=y
+CONFIG_RT2800USB_UNKNOWN=y
+CONFIG_RTL8187=m
+CONFIG_RTL8192CU=m
+CONFIG_RTL8XXXU=m
+CONFIG_RTW88=m
+CONFIG_RTW88_8822BU=m
+CONFIG_RTW88_8822CU=m
+CONFIG_RTW88_8723DU=m
+CONFIG_RTW88_8821CU=m
+CONFIG_ZD1211RW=m
+CONFIG_MAC80211_HWSIM=m
+CONFIG_IEEE802154_AT86RF230=m
+CONFIG_IEEE802154_MRF24J40=m
+CONFIG_IEEE802154_CC2520=m
+CONFIG_INPUT_MOUSEDEV=y
+CONFIG_INPUT_JOYDEV=m
+CONFIG_INPUT_EVDEV=y
+# CONFIG_KEYBOARD_ATKBD is not set
+CONFIG_KEYBOARD_GPIO=m
+CONFIG_KEYBOARD_TCA6416=m
+CONFIG_KEYBOARD_TCA8418=m
+CONFIG_KEYBOARD_MATRIX=m
+CONFIG_KEYBOARD_CAP11XX=m
+# CONFIG_INPUT_MOUSE is not set
+CONFIG_INPUT_JOYSTICK=y
+CONFIG_JOYSTICK_IFORCE=m
+CONFIG_JOYSTICK_IFORCE_USB=m
+CONFIG_JOYSTICK_XPAD=m
+CONFIG_JOYSTICK_XPAD_FF=y
+CONFIG_JOYSTICK_XPAD_LEDS=y
+CONFIG_JOYSTICK_PSXPAD_SPI=m
+CONFIG_JOYSTICK_PSXPAD_SPI_FF=y
+CONFIG_JOYSTICK_FSIA6B=m
+CONFIG_JOYSTICK_SENSEHAT=m
+CONFIG_INPUT_TOUCHSCREEN=y
+CONFIG_TOUCHSCREEN_ADS7846=m
+CONFIG_TOUCHSCREEN_EGALAX=m
+CONFIG_TOUCHSCREEN_EXC3000=m
+CONFIG_TOUCHSCREEN_GOODIX=m
+CONFIG_TOUCHSCREEN_ILI210X=m
+CONFIG_TOUCHSCREEN_ILITEK=m
+CONFIG_TOUCHSCREEN_EDT_FT5X06=m
+CONFIG_TOUCHSCREEN_RASPBERRYPI_FW=m
+CONFIG_TOUCHSCREEN_USB_COMPOSITE=m
+CONFIG_TOUCHSCREEN_TSC2007=m
+CONFIG_TOUCHSCREEN_TSC2007_IIO=y
+CONFIG_TOUCHSCREEN_STMPE=m
+CONFIG_TOUCHSCREEN_IQS5XX=m
+CONFIG_INPUT_MISC=y
+CONFIG_INPUT_AD714X=m
+CONFIG_INPUT_ATI_REMOTE2=m
+CONFIG_INPUT_KEYSPAN_REMOTE=m
+CONFIG_INPUT_POWERMATE=m
+CONFIG_INPUT_YEALINK=m
+CONFIG_INPUT_CM109=m
+CONFIG_INPUT_UINPUT=m
+CONFIG_INPUT_PWM_BEEPER=m
+CONFIG_INPUT_GPIO_ROTARY_ENCODER=m
+CONFIG_INPUT_ADXL34X=m
+CONFIG_INPUT_CMA3000=m
+CONFIG_SERIO=m
+CONFIG_SERIO_RAW=m
+CONFIG_GAMEPORT=m
+CONFIG_BRCM_CHAR_DRIVERS=y
+CONFIG_BCM_VCIO=y
+# CONFIG_LEGACY_PTYS is not set
+CONFIG_SERIAL_8250=y
+# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
+CONFIG_SERIAL_8250_CONSOLE=y
+# CONFIG_SERIAL_8250_DMA is not set
+CONFIG_SERIAL_8250_NR_UARTS=1
+CONFIG_SERIAL_8250_RUNTIME_UARTS=0
+CONFIG_SERIAL_8250_EXTENDED=y
+CONFIG_SERIAL_8250_SHARE_IRQ=y
+CONFIG_SERIAL_8250_BCM2835AUX=y
+CONFIG_SERIAL_OF_PLATFORM=y
+CONFIG_SERIAL_AMBA_PL011=y
+CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
+CONFIG_SERIAL_SC16IS7XX=m
+CONFIG_SERIAL_RPI_FW=m
+CONFIG_SERIAL_DEV_BUS=y
+CONFIG_TTY_PRINTK=y
+CONFIG_HW_RANDOM=y
+CONFIG_TCG_TPM=m
+CONFIG_TCG_TIS_SPI=m
+CONFIG_TCG_TIS_I2C=m
+CONFIG_RASPBERRYPI_GPIOMEM=m
+CONFIG_I2C=y
+CONFIG_I2C_CHARDEV=m
+CONFIG_I2C_MUX_GPMUX=m
+CONFIG_I2C_MUX_PCA954x=m
+CONFIG_I2C_MUX_PINCTRL=m
+CONFIG_I2C_BCM2708=m
+CONFIG_I2C_BCM2835=m
+# CONFIG_I2C_BRCMSTB is not set
+CONFIG_I2C_GPIO=m
+CONFIG_I2C_ROBOTFUZZ_OSIF=m
+CONFIG_I2C_TINY_USB=m
+CONFIG_SPI=y
+CONFIG_SPI_BCM2835=m
+CONFIG_SPI_BCM2835AUX=m
+CONFIG_SPI_GPIO=m
+CONFIG_SPI_RP2040_GPIO_BRIDGE=m
+CONFIG_SPI_SPIDEV=m
+CONFIG_SPI_SLAVE=y
+CONFIG_PPS_CLIENT_LDISC=m
+CONFIG_PPS_CLIENT_GPIO=m
+CONFIG_PINCTRL_MCP23S08=m
+CONFIG_GPIO_SYSFS=y
+CONFIG_GPIO_FXL6408=m
+CONFIG_GPIO_MAX7300=m
+CONFIG_GPIO_PCA953X=m
+CONFIG_GPIO_PCA953X_IRQ=y
+CONFIG_GPIO_PCF857X=m
+CONFIG_GPIO_ARIZONA=m
+CONFIG_GPIO_FSM=m
+CONFIG_GPIO_STMPE=y
+CONFIG_GPIO_MAX7301=m
+CONFIG_GPIO_MOCKUP=m
+CONFIG_W1=m
+CONFIG_W1_MASTER_DS2490=m
+CONFIG_W1_MASTER_DS2482=m
+CONFIG_W1_MASTER_GPIO=m
+CONFIG_W1_SLAVE_THERM=m
+CONFIG_W1_SLAVE_SMEM=m
+CONFIG_W1_SLAVE_DS2408=m
+CONFIG_W1_SLAVE_DS2413=m
+CONFIG_W1_SLAVE_DS2406=m
+CONFIG_W1_SLAVE_DS2423=m
+CONFIG_W1_SLAVE_DS2430=m
+CONFIG_W1_SLAVE_DS2431=m
+CONFIG_W1_SLAVE_DS2433=m
+CONFIG_W1_SLAVE_DS2438=m
+CONFIG_W1_SLAVE_DS2780=m
+CONFIG_W1_SLAVE_DS2781=m
+CONFIG_W1_SLAVE_DS28E04=m
+CONFIG_W1_SLAVE_DS28E17=m
+CONFIG_POWER_RESET=y
+CONFIG_POWER_RESET_GPIO=y
+CONFIG_RPI_POE_POWER=m
+CONFIG_BATTERY_DS2760=m
+CONFIG_BATTERY_MAX17040=m
+CONFIG_CHARGER_GPIO=m
+CONFIG_BATTERY_GAUGE_LTC2941=m
+CONFIG_SENSORS_ADT7410=m
+CONFIG_SENSORS_AHT10=m
+CONFIG_SENSORS_CHIPCAP2=m
+CONFIG_SENSORS_DRIVETEMP=m
+CONFIG_SENSORS_DS1621=m
+CONFIG_SENSORS_GPIO_FAN=m
+CONFIG_SENSORS_IIO_HWMON=m
+CONFIG_SENSORS_JC42=m
+CONFIG_SENSORS_POWERZ=m
+CONFIG_SENSORS_LM75=m
+CONFIG_SENSORS_PWM_FAN=m
+CONFIG_SENSORS_RASPBERRYPI_HWMON=m
+CONFIG_SENSORS_SHT21=m
+CONFIG_SENSORS_SHT3x=m
+CONFIG_SENSORS_SHT4x=m
+CONFIG_SENSORS_SHTC1=m
+CONFIG_SENSORS_EMC2305=m
+CONFIG_SENSORS_ADS7828=m
+CONFIG_SENSORS_INA2XX=m
+CONFIG_SENSORS_INA238=m
+CONFIG_SENSORS_TMP102=m
+CONFIG_BCM2835_THERMAL=y
+CONFIG_WATCHDOG=y
+CONFIG_WATCHDOG_SYSFS=y
+CONFIG_GPIO_WATCHDOG=m
+CONFIG_BCM2835_WDT=y
+CONFIG_MFD_RASPBERRYPI_POE_HAT=m
+CONFIG_MFD_STMPE=y
+CONFIG_STMPE_SPI=y
+CONFIG_MFD_ARIZONA_I2C=m
+CONFIG_MFD_ARIZONA_SPI=m
+CONFIG_MFD_WM5102=y
+CONFIG_REGULATOR=y
+CONFIG_REGULATOR_FIXED_VOLTAGE=m
+CONFIG_REGULATOR_ARIZONA_LDO1=m
+CONFIG_REGULATOR_ARIZONA_MICSUPP=m
+CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY=m
+CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_V2=m
+CONFIG_REGULATOR_WAVESHARE_TOUCHSCREEN=m
+CONFIG_RC_CORE=y
+CONFIG_BPF_LIRC_MODE2=y
+CONFIG_LIRC=y
+CONFIG_RC_DECODERS=y
+CONFIG_IR_IMON_DECODER=m
+CONFIG_IR_JVC_DECODER=m
+CONFIG_IR_MCE_KBD_DECODER=m
+CONFIG_IR_NEC_DECODER=m
+CONFIG_IR_RC5_DECODER=m
+CONFIG_IR_RC6_DECODER=m
+CONFIG_IR_SANYO_DECODER=m
+CONFIG_IR_SHARP_DECODER=m
+CONFIG_IR_SONY_DECODER=m
+CONFIG_IR_XMP_DECODER=m
+CONFIG_RC_DEVICES=y
+CONFIG_IR_GPIO_CIR=m
+CONFIG_IR_GPIO_TX=m
+CONFIG_IR_IGUANA=m
+CONFIG_IR_IMON=m
+CONFIG_IR_MCEUSB=m
+CONFIG_IR_PWM_TX=m
+CONFIG_IR_REDRAT3=m
+CONFIG_IR_STREAMZAP=m
+CONFIG_IR_TOY=m
+CONFIG_IR_TTUSBIR=m
+CONFIG_RC_ATI_REMOTE=m
+CONFIG_RC_LOOPBACK=m
+CONFIG_MEDIA_CEC_RC=y
+CONFIG_MEDIA_SUPPORT=m
+CONFIG_MEDIA_USB_SUPPORT=y
+CONFIG_USB_GSPCA=m
+CONFIG_USB_GSPCA_BENQ=m
+CONFIG_USB_GSPCA_CONEX=m
+CONFIG_USB_GSPCA_CPIA1=m
+CONFIG_USB_GSPCA_DTCS033=m
+CONFIG_USB_GSPCA_ETOMS=m
+CONFIG_USB_GSPCA_FINEPIX=m
+CONFIG_USB_GSPCA_JEILINJ=m
+CONFIG_USB_GSPCA_JL2005BCD=m
+CONFIG_USB_GSPCA_KINECT=m
+CONFIG_USB_GSPCA_KONICA=m
+CONFIG_USB_GSPCA_MARS=m
+CONFIG_USB_GSPCA_MR97310A=m
+CONFIG_USB_GSPCA_NW80X=m
+CONFIG_USB_GSPCA_OV519=m
+CONFIG_USB_GSPCA_OV534=m
+CONFIG_USB_GSPCA_OV534_9=m
+CONFIG_USB_GSPCA_PAC207=m
+CONFIG_USB_GSPCA_PAC7302=m
+CONFIG_USB_GSPCA_PAC7311=m
+CONFIG_USB_GSPCA_SE401=m
+CONFIG_USB_GSPCA_SN9C2028=m
+CONFIG_USB_GSPCA_SN9C20X=m
+CONFIG_USB_GSPCA_SONIXB=m
+CONFIG_USB_GSPCA_SONIXJ=m
+CONFIG_USB_GSPCA_SPCA1528=m
+CONFIG_USB_GSPCA_SPCA500=m
+CONFIG_USB_GSPCA_SPCA501=m
+CONFIG_USB_GSPCA_SPCA505=m
+CONFIG_USB_GSPCA_SPCA506=m
+CONFIG_USB_GSPCA_SPCA508=m
+CONFIG_USB_GSPCA_SPCA561=m
+CONFIG_USB_GSPCA_SQ905=m
+CONFIG_USB_GSPCA_SQ905C=m
+CONFIG_USB_GSPCA_SQ930X=m
+CONFIG_USB_GSPCA_STK014=m
+CONFIG_USB_GSPCA_STK1135=m
+CONFIG_USB_GSPCA_STV0680=m
+CONFIG_USB_GSPCA_SUNPLUS=m
+CONFIG_USB_GSPCA_T613=m
+CONFIG_USB_GSPCA_TOPRO=m
+CONFIG_USB_GSPCA_TOUPTEK=m
+CONFIG_USB_GSPCA_TV8532=m
+CONFIG_USB_GSPCA_VC032X=m
+CONFIG_USB_GSPCA_VICAM=m
+CONFIG_USB_GSPCA_XIRLINK_CIT=m
+CONFIG_USB_GSPCA_ZC3XX=m
+CONFIG_USB_GL860=m
+CONFIG_USB_M5602=m
+CONFIG_USB_STV06XX=m
+CONFIG_USB_PWC=m
+CONFIG_USB_S2255=m
+CONFIG_VIDEO_USBTV=m
+CONFIG_USB_VIDEO_CLASS=m
+CONFIG_VIDEO_GO7007=m
+CONFIG_VIDEO_GO7007_USB=m
+CONFIG_VIDEO_GO7007_USB_S2250_BOARD=m
+CONFIG_VIDEO_HDPVR=m
+CONFIG_VIDEO_PVRUSB2=m
+CONFIG_VIDEO_STK1160=m
+CONFIG_VIDEO_AU0828=m
+CONFIG_VIDEO_AU0828_RC=y
+CONFIG_VIDEO_CX231XX=m
+CONFIG_VIDEO_CX231XX_ALSA=m
+CONFIG_VIDEO_CX231XX_DVB=m
+CONFIG_DVB_AS102=m
+CONFIG_DVB_B2C2_FLEXCOP_USB=m
+CONFIG_DVB_USB_V2=m
+CONFIG_DVB_USB_AF9015=m
+CONFIG_DVB_USB_AF9035=m
+CONFIG_DVB_USB_ANYSEE=m
+CONFIG_DVB_USB_AU6610=m
+CONFIG_DVB_USB_AZ6007=m
+CONFIG_DVB_USB_CE6230=m
+CONFIG_DVB_USB_DVBSKY=m
+CONFIG_DVB_USB_EC168=m
+CONFIG_DVB_USB_GL861=m
+CONFIG_DVB_USB_LME2510=m
+CONFIG_DVB_USB_MXL111SF=m
+CONFIG_DVB_USB_RTL28XXU=m
+CONFIG_DVB_USB=m
+CONFIG_DVB_USB_A800=m
+CONFIG_DVB_USB_AF9005=m
+CONFIG_DVB_USB_AF9005_REMOTE=m
+CONFIG_DVB_USB_AZ6027=m
+CONFIG_DVB_USB_CINERGY_T2=m
+CONFIG_DVB_USB_CXUSB=m
+CONFIG_DVB_USB_DIB0700=m
+CONFIG_DVB_USB_DIBUSB_MB=m
+CONFIG_DVB_USB_DIBUSB_MB_FAULTY=y
+CONFIG_DVB_USB_DIBUSB_MC=m
+CONFIG_DVB_USB_DIGITV=m
+CONFIG_DVB_USB_DTT200U=m
+CONFIG_DVB_USB_DTV5100=m
+CONFIG_DVB_USB_DW2102=m
+CONFIG_DVB_USB_GP8PSK=m
+CONFIG_DVB_USB_M920X=m
+CONFIG_DVB_USB_NOVA_T_USB2=m
+CONFIG_DVB_USB_OPERA1=m
+CONFIG_DVB_USB_PCTV452E=m
+CONFIG_DVB_USB_TECHNISAT_USB2=m
+CONFIG_DVB_USB_TTUSB2=m
+CONFIG_DVB_USB_UMT_010=m
+CONFIG_DVB_USB_VP702X=m
+CONFIG_DVB_USB_VP7045=m
+CONFIG_SMS_USB_DRV=m
+CONFIG_VIDEO_EM28XX=m
+CONFIG_VIDEO_EM28XX_V4L2=m
+CONFIG_VIDEO_EM28XX_ALSA=m
+CONFIG_VIDEO_EM28XX_DVB=m
+CONFIG_RADIO_SAA7706H=m
+CONFIG_RADIO_SHARK=m
+CONFIG_RADIO_SHARK2=m
+CONFIG_RADIO_SI4713=m
+CONFIG_RADIO_TEA5764=m
+CONFIG_RADIO_TEF6862=m
+CONFIG_RADIO_WL1273=m
+CONFIG_USB_DSBR=m
+CONFIG_USB_KEENE=m
+CONFIG_USB_MA901=m
+CONFIG_USB_MR800=m
+CONFIG_RADIO_SI470X=m
+CONFIG_USB_SI470X=m
+CONFIG_I2C_SI470X=m
+CONFIG_I2C_SI4713=m
+CONFIG_RADIO_WL128X=m
+CONFIG_V4L_PLATFORM_DRIVERS=y
+CONFIG_VIDEO_MUX=m
+CONFIG_VIDEO_BCM2835_UNICAM_LEGACY=m
+CONFIG_VIDEO_BCM2835_UNICAM=m
+CONFIG_VIDEO_ARDUCAM_64MP=m
+CONFIG_VIDEO_ARDUCAM_PIVARIETY=m
+CONFIG_VIDEO_IMX219=m
+CONFIG_VIDEO_IMX258=m
+CONFIG_VIDEO_IMX283=m
+CONFIG_VIDEO_IMX290=m
+CONFIG_VIDEO_IMX296=m
+CONFIG_VIDEO_IMX335=m
+CONFIG_VIDEO_IMX415=m
+CONFIG_VIDEO_IMX477=m
+CONFIG_VIDEO_IMX500=m
+CONFIG_VIDEO_IMX519=m
+CONFIG_VIDEO_IMX708=m
+CONFIG_VIDEO_MIRA220=m
+CONFIG_VIDEO_MT9V011=m
+CONFIG_VIDEO_OV2311=m
+CONFIG_VIDEO_OV5647=m
+CONFIG_VIDEO_OV64A40=m
+CONFIG_VIDEO_OV7251=m
+CONFIG_VIDEO_OV7640=m
+CONFIG_VIDEO_OV9282=m
+CONFIG_VIDEO_AD5398=m
+CONFIG_VIDEO_AK7375=m
+CONFIG_VIDEO_BU64754=m
+CONFIG_VIDEO_DW9807_VCM=m
+CONFIG_VIDEO_SONY_BTF_MPX=m
+CONFIG_VIDEO_UDA1342=m
+CONFIG_VIDEO_ADV7180=m
+CONFIG_VIDEO_TC358743=m
+CONFIG_VIDEO_TVP5150=m
+CONFIG_VIDEO_TW2804=m
+CONFIG_VIDEO_TW9903=m
+CONFIG_VIDEO_TW9906=m
+CONFIG_VIDEO_IRS1125=m
+CONFIG_VIDEO_I2C=m
+CONFIG_AUXDISPLAY=y
+CONFIG_HD44780=m
+CONFIG_DRM=m
+CONFIG_DRM_LOAD_EDID_FIRMWARE=y
+CONFIG_DRM_UDL=m
+CONFIG_DRM_PANEL_ILITEK_ILI9806E=m
+CONFIG_DRM_PANEL_ILITEK_ILI9806E_SPI=m
+CONFIG_DRM_PANEL_ILITEK_ILI9881C=m
+CONFIG_DRM_PANEL_JDI_LT070ME05000=m
+CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN=m
+CONFIG_DRM_PANEL_SITRONIX_ST7701=m
+CONFIG_DRM_PANEL_SIMPLE=m
+CONFIG_DRM_PANEL_TPO_Y17P=m
+CONFIG_DRM_PANEL_WAVESHARE_TOUCHSCREEN=m
+CONFIG_DRM_PANEL_WAVESHARE_TOUCHSCREEN_V2=m
+CONFIG_DRM_DISPLAY_CONNECTOR=m
+CONFIG_DRM_SIMPLE_BRIDGE=m
+CONFIG_DRM_TOSHIBA_TC358762=m
+CONFIG_DRM_VC4=m
+CONFIG_DRM_VC4_HDMI_CEC=y
+CONFIG_DRM_PANEL_MIPI_DBI=m
+CONFIG_TINYDRM_HX8357D=m
+CONFIG_TINYDRM_ILI9225=m
+CONFIG_TINYDRM_ILI9341=m
+CONFIG_TINYDRM_ILI9486=m
+CONFIG_TINYDRM_MI0283QT=m
+CONFIG_TINYDRM_REPAPER=m
+CONFIG_TINYDRM_ST7586=m
+CONFIG_TINYDRM_ST7735R=m
+CONFIG_DRM_GUD=m
+CONFIG_DRM_SSD130X=m
+CONFIG_DRM_SSD130X_I2C=m
+CONFIG_DRM_SSD130X_SPI=m
+CONFIG_FB=y
+CONFIG_FB_BCM2708=y
+CONFIG_FB_SIMPLE=y
+CONFIG_FB_SSD1307=m
+CONFIG_FB_RPISENSE=m
+CONFIG_BACKLIGHT_PWM=m
+CONFIG_BACKLIGHT_RPI=m
+CONFIG_BACKLIGHT_LM3630A=m
+CONFIG_BACKLIGHT_GPIO=m
+CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y
+CONFIG_LOGO=y
+# CONFIG_LOGO_LINUX_MONO is not set
+# CONFIG_LOGO_LINUX_VGA16 is not set
+CONFIG_SOUND=y
+CONFIG_SND=m
+CONFIG_SND_OSSEMUL=y
+CONFIG_SND_PCM_OSS=m
+CONFIG_SND_HRTIMER=m
+CONFIG_SND_DYNAMIC_MINORS=y
+CONFIG_SND_SEQUENCER=m
+CONFIG_SND_SEQ_DUMMY=m
+CONFIG_SND_DUMMY=m
+CONFIG_SND_ALOOP=m
+CONFIG_SND_VIRMIDI=m
+CONFIG_SND_MTPAV=m
+CONFIG_SND_SERIAL_U16550=m
+CONFIG_SND_MPU401=m
+CONFIG_SND_PIMIDI=m
+CONFIG_SND_PISOUND_MICRO=m
+CONFIG_SND_USB_AUDIO=m
+CONFIG_SND_USB_UA101=m
+CONFIG_SND_USB_CAIAQ=m
+CONFIG_SND_USB_CAIAQ_INPUT=y
+CONFIG_SND_USB_6FIRE=m
+CONFIG_SND_USB_HIFACE=m
+CONFIG_SND_USB_TONEPORT=m
+CONFIG_SND_SOC=m
+CONFIG_SND_BCM2835_SOC_I2S=m
+CONFIG_SND_BCM2708_SOC_CHIPDIP_DAC=m
+CONFIG_SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_ADC=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DAC=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUS=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DIGI=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_AMP=m
+CONFIG_SND_BCM2708_SOC_PIFI_40=m
+CONFIG_SND_BCM2708_SOC_RPI_CIRRUS=m
+CONFIG_SND_BCM2708_SOC_RPI_DAC=m
+CONFIG_SND_BCM2708_SOC_RPI_PROTO=m
+CONFIG_SND_BCM2708_SOC_JUSTBOOM_BOTH=m
+CONFIG_SND_BCM2708_SOC_JUSTBOOM_DAC=m
+CONFIG_SND_BCM2708_SOC_JUSTBOOM_DIGI=m
+CONFIG_SND_BCM2708_SOC_IQAUDIO_CODEC=m
+CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC=m
+CONFIG_SND_BCM2708_SOC_IQAUDIO_DIGI=m
+CONFIG_SND_BCM2708_SOC_I_SABRE_Q2M=m
+CONFIG_SND_BCM2708_SOC_ADAU1977_ADC=m
+CONFIG_SND_AUDIOINJECTOR_PI_SOUNDCARD=m
+CONFIG_SND_AUDIOINJECTOR_OCTO_SOUNDCARD=m
+CONFIG_SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD=m
+CONFIG_SND_AUDIOSENSE_PI=m
+CONFIG_SND_DIGIDAC1_SOUNDCARD=m
+CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO=m
+CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO_V2=m
+CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC=m
+CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS=m
+CONFIG_SND_BCM2708_SOC_ALLO_BOSS_DAC=m
+CONFIG_SND_BCM2708_SOC_ALLO_BOSS2_DAC=m
+CONFIG_SND_BCM2708_SOC_ALLO_DIGIONE=m
+CONFIG_SND_BCM2708_SOC_ALLO_KATANA_DAC=m
+CONFIG_SND_BCM2708_SOC_FE_PI_AUDIO=m
+CONFIG_SND_PISOUND=m
+CONFIG_SND_SOC_AD193X_SPI=m
+CONFIG_SND_SOC_AD193X_I2C=m
+CONFIG_SND_SOC_ADAU1701=m
+CONFIG_SND_SOC_ADAU7002=m
+CONFIG_SND_SOC_AK4554=m
+CONFIG_SND_SOC_CS4265=m
+CONFIG_SND_SOC_ICS43432=m
+CONFIG_SND_SOC_MA120X0P=m
+CONFIG_SND_SOC_MAX98357A=m
+CONFIG_SND_SOC_SPDIF=m
+CONFIG_SND_SOC_TLV320AIC23_I2C=m
+CONFIG_SND_SOC_WM8804_I2C=m
+CONFIG_SND_SOC_WM8904=m
+CONFIG_SND_SOC_WM8960=m
+CONFIG_SND_SIMPLE_CARD=m
+CONFIG_HID_BATTERY_STRENGTH=y
+CONFIG_HIDRAW=y
+CONFIG_UHID=m
+CONFIG_HID_A4TECH=m
+CONFIG_HID_ACRUX=m
+CONFIG_HID_APPLE=m
+CONFIG_HID_ASUS=m
+CONFIG_HID_BELKIN=m
+CONFIG_HID_BETOP_FF=m
+CONFIG_HID_BIGBEN_FF=m
+CONFIG_HID_CHERRY=m
+CONFIG_HID_CHICONY=m
+CONFIG_HID_CP2112=m
+CONFIG_HID_CYPRESS=m
+CONFIG_HID_DRAGONRISE=m
+CONFIG_HID_EMS_FF=m
+CONFIG_HID_ELECOM=m
+CONFIG_HID_ELO=m
+CONFIG_HID_EZKEY=m
+CONFIG_HID_GEMBIRD=m
+CONFIG_HID_HOLTEK=m
+CONFIG_HID_KEYTOUCH=m
+CONFIG_HID_KYE=m
+CONFIG_HID_UCLOGIC=m
+CONFIG_HID_WALTOP=m
+CONFIG_HID_GYRATION=m
+CONFIG_HID_TWINHAN=m
+CONFIG_HID_KENSINGTON=m
+CONFIG_HID_LCPOWER=m
+CONFIG_HID_LOGITECH=m
+CONFIG_HID_LOGITECH_DJ=m
+CONFIG_LOGITECH_FF=y
+CONFIG_LOGIRUMBLEPAD2_FF=y
+CONFIG_LOGIG940_FF=y
+CONFIG_HID_MAGICMOUSE=m
+CONFIG_HID_MICROSOFT=m
+CONFIG_HID_MONTEREY=m
+CONFIG_HID_MULTITOUCH=m
+CONFIG_HID_NINTENDO=m
+CONFIG_NINTENDO_FF=y
+CONFIG_HID_NTRIG=m
+CONFIG_HID_ORTEK=m
+CONFIG_HID_PANTHERLORD=m
+CONFIG_HID_PETALYNX=m
+CONFIG_HID_PICOLCD=m
+CONFIG_HID_PLAYSTATION=m
+CONFIG_PLAYSTATION_FF=y
+CONFIG_HID_ROCCAT=m
+CONFIG_HID_SAMSUNG=m
+CONFIG_HID_SONY=m
+CONFIG_SONY_FF=y
+CONFIG_HID_SPEEDLINK=m
+CONFIG_HID_STEAM=m
+CONFIG_HID_SUNPLUS=m
+CONFIG_HID_GREENASIA=m
+CONFIG_HID_SMARTJOYPLUS=m
+CONFIG_HID_TOPSEED=m
+CONFIG_HID_THINGM=m
+CONFIG_HID_THRUSTMASTER=m
+CONFIG_HID_WACOM=m
+CONFIG_HID_WIIMOTE=m
+CONFIG_HID_XINMO=m
+CONFIG_HID_ZEROPLUS=m
+CONFIG_HID_ZYDACRON=m
+CONFIG_HID_PID=y
+CONFIG_USB_HIDDEV=y
+CONFIG_I2C_HID_OF=m
+CONFIG_USB=y
+CONFIG_USB_ANNOUNCE_NEW_DEVICES=y
+CONFIG_USB_MON=m
+CONFIG_USB_DWCOTG=y
+CONFIG_USB_PRINTER=m
+CONFIG_USB_TMC=m
+CONFIG_USB_STORAGE=y
+CONFIG_USB_STORAGE_REALTEK=m
+CONFIG_USB_STORAGE_DATAFAB=m
+CONFIG_USB_STORAGE_FREECOM=m
+CONFIG_USB_STORAGE_ISD200=m
+CONFIG_USB_STORAGE_USBAT=m
+CONFIG_USB_STORAGE_SDDR09=m
+CONFIG_USB_STORAGE_SDDR55=m
+CONFIG_USB_STORAGE_JUMPSHOT=m
+CONFIG_USB_STORAGE_ALAUDA=m
+CONFIG_USB_STORAGE_ONETOUCH=m
+CONFIG_USB_STORAGE_KARMA=m
+CONFIG_USB_STORAGE_CYPRESS_ATACB=m
+CONFIG_USB_STORAGE_ENE_UB6250=m
+CONFIG_USB_UAS=m
+CONFIG_USB_MDC800=m
+CONFIG_USB_MICROTEK=m
+CONFIG_USBIP_CORE=m
+CONFIG_USBIP_VHCI_HCD=m
+CONFIG_USBIP_HOST=m
+CONFIG_USBIP_VUDC=m
+CONFIG_USB_DWC2=m
+CONFIG_USB_SERIAL=m
+CONFIG_USB_SERIAL_GENERIC=y
+CONFIG_USB_SERIAL_SIMPLE=m
+CONFIG_USB_SERIAL_AIRCABLE=m
+CONFIG_USB_SERIAL_ARK3116=m
+CONFIG_USB_SERIAL_BELKIN=m
+CONFIG_USB_SERIAL_CH341=m
+CONFIG_USB_SERIAL_WHITEHEAT=m
+CONFIG_USB_SERIAL_DIGI_ACCELEPORT=m
+CONFIG_USB_SERIAL_CP210X=m
+CONFIG_USB_SERIAL_CYPRESS_M8=m
+CONFIG_USB_SERIAL_EMPEG=m
+CONFIG_USB_SERIAL_FTDI_SIO=m
+CONFIG_USB_SERIAL_VISOR=m
+CONFIG_USB_SERIAL_IPAQ=m
+CONFIG_USB_SERIAL_IR=m
+CONFIG_USB_SERIAL_EDGEPORT=m
+CONFIG_USB_SERIAL_EDGEPORT_TI=m
+CONFIG_USB_SERIAL_F81232=m
+CONFIG_USB_SERIAL_F8153X=m
+CONFIG_USB_SERIAL_GARMIN=m
+CONFIG_USB_SERIAL_IPW=m
+CONFIG_USB_SERIAL_IUU=m
+CONFIG_USB_SERIAL_KEYSPAN_PDA=m
+CONFIG_USB_SERIAL_KEYSPAN=m
+CONFIG_USB_SERIAL_KLSI=m
+CONFIG_USB_SERIAL_KOBIL_SCT=m
+CONFIG_USB_SERIAL_MCT_U232=m
+CONFIG_USB_SERIAL_METRO=m
+CONFIG_USB_SERIAL_MOS7720=m
+CONFIG_USB_SERIAL_MOS7840=m
+CONFIG_USB_SERIAL_MXUPORT=m
+CONFIG_USB_SERIAL_NAVMAN=m
+CONFIG_USB_SERIAL_PL2303=m
+CONFIG_USB_SERIAL_OTI6858=m
+CONFIG_USB_SERIAL_QCAUX=m
+CONFIG_USB_SERIAL_QUALCOMM=m
+CONFIG_USB_SERIAL_SPCP8X5=m
+CONFIG_USB_SERIAL_SAFE=m
+CONFIG_USB_SERIAL_SIERRAWIRELESS=m
+CONFIG_USB_SERIAL_SYMBOL=m
+CONFIG_USB_SERIAL_TI=m
+CONFIG_USB_SERIAL_CYBERJACK=m
+CONFIG_USB_SERIAL_OPTION=m
+CONFIG_USB_SERIAL_OMNINET=m
+CONFIG_USB_SERIAL_OPTICON=m
+CONFIG_USB_SERIAL_XSENS_MT=m
+CONFIG_USB_SERIAL_WISHBONE=m
+CONFIG_USB_SERIAL_SSU100=m
+CONFIG_USB_SERIAL_QT2=m
+CONFIG_USB_SERIAL_UPD78F0730=m
+CONFIG_USB_SERIAL_XR=m
+CONFIG_USB_SERIAL_DEBUG=m
+CONFIG_USB_EMI62=m
+CONFIG_USB_EMI26=m
+CONFIG_USB_ADUTUX=m
+CONFIG_USB_SEVSEG=m
+CONFIG_USB_LEGOTOWER=m
+CONFIG_USB_LCD=m
+CONFIG_USB_CYPRESS_CY7C63=m
+CONFIG_USB_CYTHERM=m
+CONFIG_USB_IDMOUSE=m
+CONFIG_USB_APPLEDISPLAY=m
+CONFIG_USB_LD=m
+CONFIG_USB_TRANCEVIBRATOR=m
+CONFIG_USB_IOWARRIOR=m
+CONFIG_USB_TEST=m
+CONFIG_USB_ISIGHTFW=m
+CONFIG_USB_YUREX=m
+CONFIG_USB_ATM=m
+CONFIG_USB_SPEEDTOUCH=m
+CONFIG_USB_CXACRU=m
+CONFIG_USB_UEAGLEATM=m
+CONFIG_USB_XUSBATM=m
+CONFIG_NOP_USB_XCEIV=y
+CONFIG_USB_GADGET=y
+CONFIG_USB_CONFIGFS=m
+CONFIG_USB_CONFIGFS_SERIAL=y
+CONFIG_USB_CONFIGFS_ACM=y
+CONFIG_USB_CONFIGFS_OBEX=y
+CONFIG_USB_CONFIGFS_NCM=y
+CONFIG_USB_CONFIGFS_ECM=y
+CONFIG_USB_CONFIGFS_ECM_SUBSET=y
+CONFIG_USB_CONFIGFS_RNDIS=y
+CONFIG_USB_CONFIGFS_EEM=y
+CONFIG_USB_CONFIGFS_MASS_STORAGE=y
+CONFIG_USB_CONFIGFS_F_LB_SS=y
+CONFIG_USB_CONFIGFS_F_FS=y
+CONFIG_USB_CONFIGFS_F_UAC1=y
+CONFIG_USB_CONFIGFS_F_UAC2=y
+CONFIG_USB_CONFIGFS_F_MIDI=y
+CONFIG_USB_CONFIGFS_F_HID=y
+CONFIG_USB_CONFIGFS_F_UVC=y
+CONFIG_USB_CONFIGFS_F_PRINTER=y
+CONFIG_USB_ZERO=m
+CONFIG_USB_AUDIO=m
+CONFIG_USB_ETH=m
+CONFIG_USB_GADGETFS=m
+CONFIG_USB_MASS_STORAGE=m
+CONFIG_USB_G_SERIAL=m
+CONFIG_USB_MIDI_GADGET=m
+CONFIG_USB_G_PRINTER=m
+CONFIG_USB_CDC_COMPOSITE=m
+CONFIG_USB_G_ACM_MS=m
+CONFIG_USB_G_MULTI=m
+CONFIG_USB_G_HID=m
+CONFIG_USB_G_WEBCAM=m
+CONFIG_MMC=y
+CONFIG_MMC_BLOCK_MINORS=32
+CONFIG_MMC_BCM2835_MMC=y
+CONFIG_MMC_BCM2835_DMA=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_MMC_SPI=m
+CONFIG_MMC_HSQ=y
+CONFIG_MMC_BCM2835=y
+CONFIG_LEDS_CLASS=y
+CONFIG_LEDS_CLASS_MULTICOLOR=m
+CONFIG_LEDS_PCA9532=m
+CONFIG_LEDS_GPIO=y
+CONFIG_LEDS_PCA955X=m
+CONFIG_LEDS_PCA963X=m
+CONFIG_LEDS_PWM=y
+CONFIG_LEDS_IS31FL32XX=m
+CONFIG_LEDS_TRIGGER_TIMER=y
+CONFIG_LEDS_TRIGGER_ONESHOT=y
+CONFIG_LEDS_TRIGGER_HEARTBEAT=y
+CONFIG_LEDS_TRIGGER_BACKLIGHT=y
+CONFIG_LEDS_TRIGGER_CPU=y
+CONFIG_LEDS_TRIGGER_DEFAULT_ON=y
+CONFIG_LEDS_TRIGGER_TRANSIENT=m
+CONFIG_LEDS_TRIGGER_CAMERA=m
+CONFIG_LEDS_TRIGGER_INPUT=y
+CONFIG_LEDS_TRIGGER_PANIC=y
+CONFIG_LEDS_TRIGGER_NETDEV=m
+CONFIG_LEDS_TRIGGER_PATTERN=m
+CONFIG_LEDS_TRIGGER_ACTPWR=y
+CONFIG_ACCESSIBILITY=y
+CONFIG_SPEAKUP=m
+CONFIG_SPEAKUP_SYNTH_SOFT=m
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_DRV_ABX80X=m
+CONFIG_RTC_DRV_DS1307=m
+CONFIG_RTC_DRV_DS1374=m
+CONFIG_RTC_DRV_DS1672=m
+CONFIG_RTC_DRV_MAX6900=m
+CONFIG_RTC_DRV_RS5C372=m
+CONFIG_RTC_DRV_ISL1208=m
+CONFIG_RTC_DRV_ISL12022=m
+CONFIG_RTC_DRV_X1205=m
+CONFIG_RTC_DRV_PCF8523=m
+CONFIG_RTC_DRV_PCF85063=m
+CONFIG_RTC_DRV_PCF85363=m
+CONFIG_RTC_DRV_PCF8563=m
+CONFIG_RTC_DRV_PCF8583=m
+CONFIG_RTC_DRV_M41T80=m
+CONFIG_RTC_DRV_BQ32K=m
+CONFIG_RTC_DRV_S35390A=m
+CONFIG_RTC_DRV_FM3130=m
+CONFIG_RTC_DRV_RX8581=m
+CONFIG_RTC_DRV_RX8025=m
+CONFIG_RTC_DRV_EM3027=m
+CONFIG_RTC_DRV_RV3028=m
+CONFIG_RTC_DRV_RV3032=m
+CONFIG_RTC_DRV_RV8803=m
+CONFIG_RTC_DRV_SD3078=m
+CONFIG_RTC_DRV_M41T93=m
+CONFIG_RTC_DRV_M41T94=m
+CONFIG_RTC_DRV_DS1302=m
+CONFIG_RTC_DRV_DS1305=m
+CONFIG_RTC_DRV_DS1390=m
+CONFIG_RTC_DRV_R9701=m
+CONFIG_RTC_DRV_RX4581=m
+CONFIG_RTC_DRV_RS5C348=m
+CONFIG_RTC_DRV_MAX6902=m
+CONFIG_RTC_DRV_PCF2123=m
+CONFIG_RTC_DRV_DS3232=m
+CONFIG_RTC_DRV_PCF2127=m
+CONFIG_RTC_DRV_RV3029C2=m
+CONFIG_DMADEVICES=y
+CONFIG_DMA_BCM2835=y
+CONFIG_DMA_BCM2708=y
+CONFIG_DMABUF_HEAPS=y
+CONFIG_DMABUF_HEAPS_SYSTEM=y
+CONFIG_DMABUF_HEAPS_CMA=y
+CONFIG_UIO=m
+CONFIG_UIO_PDRV_GENIRQ=m
+CONFIG_STAGING=y
+CONFIG_VT6656=m
+CONFIG_STAGING_MEDIA=y
+CONFIG_STAGING_MEDIA_DEPRECATED=y
+CONFIG_FB_TFT=m
+CONFIG_FB_TFT_AGM1264K_FL=m
+CONFIG_FB_TFT_BD663474=m
+CONFIG_FB_TFT_HX8340BN=m
+CONFIG_FB_TFT_HX8347D=m
+CONFIG_FB_TFT_HX8353D=m
+CONFIG_FB_TFT_HX8357D=m
+CONFIG_FB_TFT_ILI9163=m
+CONFIG_FB_TFT_ILI9320=m
+CONFIG_FB_TFT_ILI9325=m
+CONFIG_FB_TFT_ILI9340=m
+CONFIG_FB_TFT_ILI9341=m
+CONFIG_FB_TFT_ILI9481=m
+CONFIG_FB_TFT_ILI9486=m
+CONFIG_FB_TFT_PCD8544=m
+CONFIG_FB_TFT_RA8875=m
+CONFIG_FB_TFT_S6D02A1=m
+CONFIG_FB_TFT_S6D1121=m
+CONFIG_FB_TFT_SH1106=m
+CONFIG_FB_TFT_SSD1289=m
+CONFIG_FB_TFT_SSD1306=m
+CONFIG_FB_TFT_SSD1331=m
+CONFIG_FB_TFT_SSD1351=m
+CONFIG_FB_TFT_ST7735R=m
+CONFIG_FB_TFT_ST7789V=m
+CONFIG_FB_TFT_TINYLCD=m
+CONFIG_FB_TFT_TLS8204=m
+CONFIG_FB_TFT_UC1611=m
+CONFIG_FB_TFT_UC1701=m
+CONFIG_FB_TFT_UPD161704=m
+CONFIG_BCM2835_VCHIQ=y
+CONFIG_SND_BCM2835=m
+CONFIG_VIDEO_BCM2835=m
+CONFIG_VIDEO_CODEC_BCM2835=m
+CONFIG_VIDEO_ISP_BCM2835=m
+CONFIG_COMMON_CLK_SI5351=m
+CONFIG_CLK_RASPBERRYPI=y
+CONFIG_MAILBOX=y
+CONFIG_BCM2835_MBOX=y
+# CONFIG_IOMMU_SUPPORT is not set
+CONFIG_RASPBERRYPI_POWER=y
+CONFIG_IIO=m
+CONFIG_IIO_BUFFER_CB=m
+CONFIG_IIO_SW_TRIGGER=m
+CONFIG_ADXL355_I2C=m
+CONFIG_AD799X=m
+CONFIG_MCP320X=m
+CONFIG_MCP3422=m
+CONFIG_TI_ADS1015=m
+CONFIG_BME680=m
+CONFIG_CCS811=m
+CONFIG_SCD4X=m
+CONFIG_SENSIRION_SGP30=m
+CONFIG_SPS30_I2C=m
+CONFIG_MAX30102=m
+CONFIG_DHT11=m
+CONFIG_HDC100X=m
+CONFIG_HDC3020=m
+CONFIG_HTS221=m
+CONFIG_HTU21=m
+CONFIG_SI7020=m
+CONFIG_BOSCH_BNO055_I2C=m
+CONFIG_INV_MPU6050_I2C=m
+CONFIG_APDS9960=m
+CONFIG_AS73211=m
+CONFIG_BH1750=m
+CONFIG_TCS3472=m
+CONFIG_TSL4531=m
+CONFIG_VEML6030=m
+CONFIG_VEML6040=m
+CONFIG_VEML6070=m
+CONFIG_VEML6075=m
+CONFIG_IIO_HRTIMER_TRIGGER=m
+CONFIG_IIO_INTERRUPT_TRIGGER=m
+CONFIG_IIO_SYSFS_TRIGGER=m
+CONFIG_BMP280=m
+CONFIG_MS5637=m
+CONFIG_MAXIM_THERMOCOUPLE=m
+CONFIG_TMP117=m
+CONFIG_MAX31856=m
+CONFIG_PWM=y
+CONFIG_PWM_BCM2835=m
+CONFIG_PWM_GPIO=m
+CONFIG_PWM_PCA9685=m
+CONFIG_PWM_RASPBERRYPI_POE=m
+CONFIG_RPI_AXIPERF=m
+CONFIG_MUX_GPIO=m
+CONFIG_EXT4_FS=y
+CONFIG_EXT4_FS_POSIX_ACL=y
+CONFIG_EXT4_FS_SECURITY=y
+CONFIG_REISERFS_FS=m
+CONFIG_REISERFS_FS_XATTR=y
+CONFIG_REISERFS_FS_POSIX_ACL=y
+CONFIG_REISERFS_FS_SECURITY=y
+CONFIG_JFS_FS=m
+CONFIG_JFS_POSIX_ACL=y
+CONFIG_JFS_SECURITY=y
+CONFIG_JFS_STATISTICS=y
+CONFIG_XFS_FS=m
+CONFIG_XFS_QUOTA=y
+CONFIG_XFS_POSIX_ACL=y
+CONFIG_XFS_RT=y
+CONFIG_GFS2_FS=m
+CONFIG_OCFS2_FS=m
+CONFIG_BTRFS_FS=m
+CONFIG_BTRFS_FS_POSIX_ACL=y
+CONFIG_NILFS2_FS=m
+CONFIG_F2FS_FS=y
+CONFIG_F2FS_FS_SECURITY=y
+CONFIG_BCACHEFS_FS=m
+CONFIG_BCACHEFS_QUOTA=y
+CONFIG_BCACHEFS_POSIX_ACL=y
+CONFIG_FANOTIFY=y
+CONFIG_QFMT_V1=m
+CONFIG_QFMT_V2=m
+CONFIG_AUTOFS_FS=y
+CONFIG_FUSE_FS=m
+CONFIG_CUSE=m
+CONFIG_OVERLAY_FS=m
+CONFIG_FSCACHE=y
+CONFIG_FSCACHE_STATS=y
+CONFIG_CACHEFILES=m
+CONFIG_ISO9660_FS=m
+CONFIG_JOLIET=y
+CONFIG_ZISOFS=y
+CONFIG_UDF_FS=m
+CONFIG_MSDOS_FS=y
+CONFIG_VFAT_FS=y
+CONFIG_FAT_DEFAULT_IOCHARSET="ascii"
+CONFIG_EXFAT_FS=m
+CONFIG_NTFS3_FS=m
+CONFIG_TMPFS=y
+CONFIG_TMPFS_POSIX_ACL=y
+CONFIG_ECRYPT_FS=m
+CONFIG_HFS_FS=m
+CONFIG_HFSPLUS_FS=m
+CONFIG_JFFS2_FS=m
+CONFIG_JFFS2_SUMMARY=y
+CONFIG_UBIFS_FS=m
+CONFIG_SQUASHFS=m
+CONFIG_SQUASHFS_XATTR=y
+CONFIG_SQUASHFS_LZO=y
+CONFIG_SQUASHFS_XZ=y
+CONFIG_SQUASHFS_ZSTD=y
+CONFIG_PSTORE=y
+CONFIG_PSTORE_CONSOLE=y
+CONFIG_PSTORE_RAM=y
+CONFIG_EROFS_FS=m
+CONFIG_EROFS_FS_ZIP_ZSTD=y
+CONFIG_NFS_FS=y
+CONFIG_NFS_V2=y
+CONFIG_NFS_V3_ACL=y
+CONFIG_NFS_V4=y
+CONFIG_NFS_SWAP=y
+CONFIG_NFS_V4_1=y
+CONFIG_NFS_V4_2=y
+CONFIG_ROOT_NFS=y
+CONFIG_NFSD=m
+CONFIG_NFSD_V2=y
+CONFIG_NFSD_V2_ACL=y
+CONFIG_NFSD_V3_ACL=y
+CONFIG_NFSD_V4=y
+CONFIG_CEPH_FS=m
+CONFIG_CIFS=m
+CONFIG_CIFS_UPCALL=y
+CONFIG_CIFS_XATTR=y
+CONFIG_CIFS_DFS_UPCALL=y
+CONFIG_CIFS_FSCACHE=y
+CONFIG_SMB_SERVER=m
+CONFIG_9P_FS=m
+CONFIG_9P_FS_POSIX_ACL=y
+CONFIG_NLS_DEFAULT="utf8"
+CONFIG_NLS_CODEPAGE_437=y
+CONFIG_NLS_CODEPAGE_737=m
+CONFIG_NLS_CODEPAGE_775=m
+CONFIG_NLS_CODEPAGE_850=m
+CONFIG_NLS_CODEPAGE_852=m
+CONFIG_NLS_CODEPAGE_855=m
+CONFIG_NLS_CODEPAGE_857=m
+CONFIG_NLS_CODEPAGE_860=m
+CONFIG_NLS_CODEPAGE_861=m
+CONFIG_NLS_CODEPAGE_862=m
+CONFIG_NLS_CODEPAGE_863=m
+CONFIG_NLS_CODEPAGE_864=m
+CONFIG_NLS_CODEPAGE_865=m
+CONFIG_NLS_CODEPAGE_866=m
+CONFIG_NLS_CODEPAGE_869=m
+CONFIG_NLS_CODEPAGE_936=m
+CONFIG_NLS_CODEPAGE_950=m
+CONFIG_NLS_CODEPAGE_932=m
+CONFIG_NLS_CODEPAGE_949=m
+CONFIG_NLS_CODEPAGE_874=m
+CONFIG_NLS_ISO8859_8=m
+CONFIG_NLS_CODEPAGE_1250=m
+CONFIG_NLS_CODEPAGE_1251=m
+CONFIG_NLS_ASCII=y
+CONFIG_NLS_ISO8859_1=m
+CONFIG_NLS_ISO8859_2=m
+CONFIG_NLS_ISO8859_3=m
+CONFIG_NLS_ISO8859_4=m
+CONFIG_NLS_ISO8859_5=m
+CONFIG_NLS_ISO8859_6=m
+CONFIG_NLS_ISO8859_7=m
+CONFIG_NLS_ISO8859_9=m
+CONFIG_NLS_ISO8859_13=m
+CONFIG_NLS_ISO8859_14=m
+CONFIG_NLS_ISO8859_15=m
+CONFIG_NLS_KOI8_R=m
+CONFIG_NLS_KOI8_U=m
+CONFIG_DLM=m
+CONFIG_UNICODE=m
+CONFIG_KEY_DH_OPERATIONS=y
+CONFIG_SECURITY=y
+CONFIG_SECURITY_APPARMOR=y
+CONFIG_LSM=""
+CONFIG_CRYPTO_USER=m
+CONFIG_CRYPTO_CRYPTD=m
+CONFIG_CRYPTO_CAST5=m
+CONFIG_CRYPTO_DES=y
+CONFIG_CRYPTO_TWOFISH=m
+CONFIG_CRYPTO_ADIANTUM=m
+CONFIG_CRYPTO_CBC=y
+CONFIG_CRYPTO_CTS=m
+CONFIG_CRYPTO_XTS=m
+CONFIG_CRYPTO_CHACHA20POLY1305=m
+CONFIG_CRYPTO_MD4=m
+CONFIG_CRYPTO_WP512=m
+CONFIG_CRYPTO_XCBC=m
+CONFIG_CRYPTO_LZ4=m
+CONFIG_CRYPTO_LZ4HC=m
+CONFIG_CRYPTO_USER_API_HASH=m
+CONFIG_CRYPTO_USER_API_SKCIPHER=m
+CONFIG_CRYPTO_USER_API_RNG=m
+CONFIG_CRYPTO_USER_API_AEAD=m
+CONFIG_CRYPTO_SHA1_ARM=m
+CONFIG_CRYPTO_AES_ARM=m
+# CONFIG_CRYPTO_HW is not set
+CONFIG_PKCS8_PRIVATE_KEY_PARSER=m
+CONFIG_CRC_ITU_T=y
+CONFIG_LIBCRC32C=y
+CONFIG_DMA_CMA=y
+CONFIG_CMA_SIZE_MBYTES=5
+CONFIG_PRINTK_TIME=y
+CONFIG_BOOT_PRINTK_DELAY=y
+CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1f6
+CONFIG_KGDB=y
+CONFIG_KGDB_KDB=y
+CONFIG_KDB_KEYBOARD=y
+CONFIG_DEBUG_MEMORY_INIT=y
+CONFIG_DETECT_HUNG_TASK=y
+CONFIG_LATENCYTOP=y
+CONFIG_FUNCTION_PROFILER=y
+CONFIG_STACK_TRACER=y
+CONFIG_SCHED_TRACER=y
+CONFIG_FTRACE_SYSCALLS=y
+CONFIG_BLK_DEV_IO_TRACE=y
+# CONFIG_UPROBE_EVENTS is not set
diff --git a/arch/arm/include/asm/irqflags.h b/arch/arm/include/asm/irqflags.h
index aeec7f24eb75..a3b186608c60 100644
--- a/arch/arm/include/asm/irqflags.h
+++ b/arch/arm/include/asm/irqflags.h
@@ -163,13 +163,23 @@ static inline unsigned long arch_local_save_flags(void)
 }
 
 /*
- * restore saved IRQ & FIQ state
+ * restore saved IRQ state
  */
 #define arch_local_irq_restore arch_local_irq_restore
 static inline void arch_local_irq_restore(unsigned long flags)
 {
-	asm volatile(
-		"	msr	" IRQMASK_REG_NAME_W ", %0	@ local_irq_restore"
+	unsigned long temp = 0;
+	flags &= ~(1 << 6);
+	asm volatile (
+		" mrs %0, cpsr"
+		: "=r" (temp)
+		:
+		: "memory", "cc");
+		/* Preserve FIQ bit */
+		temp &= (1 << 6);
+		flags = flags | temp;
+	asm volatile (
+		"    msr    cpsr_c, %0    @ local_irq_restore"
 		:
 		: "r" (flags)
 		: "memory", "cc");
diff --git a/arch/arm/include/asm/string.h b/arch/arm/include/asm/string.h
index 96fc6cf460ec..94f1abb48d49 100644
--- a/arch/arm/include/asm/string.h
+++ b/arch/arm/include/asm/string.h
@@ -72,4 +72,9 @@ static inline void *memset64(uint64_t *p, uint64_t v, __kernel_size_t n)
 
 #endif
 
+#ifdef CONFIG_BCM2835_FAST_MEMCPY
+#define __HAVE_ARCH_MEMCMP
+extern int memcmp(const void *, const void *, size_t);
+#endif
+
 #endif
diff --git a/arch/arm/include/asm/uaccess.h b/arch/arm/include/asm/uaccess.h
index f90be312418e..4fe53e1a647c 100644
--- a/arch/arm/include/asm/uaccess.h
+++ b/arch/arm/include/asm/uaccess.h
@@ -533,6 +533,9 @@ do {									\
 extern unsigned long __must_check
 arm_copy_from_user(void *to, const void __user *from, unsigned long n);
 
+extern unsigned long __must_check
+__copy_from_user_std(void *to, const void __user *from, unsigned long n);
+
 static inline unsigned long __must_check
 raw_copy_from_user(void *to, const void __user *from, unsigned long n)
 {
diff --git a/arch/arm/kernel/fiq.c b/arch/arm/kernel/fiq.c
index d2c8e5313539..bb320f08ad2f 100644
--- a/arch/arm/kernel/fiq.c
+++ b/arch/arm/kernel/fiq.c
@@ -57,6 +57,8 @@
 static unsigned long dfl_fiq_insn;
 static struct pt_regs dfl_fiq_regs;
 
+extern int irq_activate(struct irq_desc *desc);
+
 /* Default reacquire function
  * - we always relinquish FIQ control
  * - we always reacquire FIQ control
@@ -141,6 +143,8 @@ static int fiq_start;
 
 void enable_fiq(int fiq)
 {
+	struct irq_desc *desc = irq_to_desc(fiq + fiq_start);
+	irq_activate(desc);
 	enable_irq(fiq + fiq_start);
 }
 
diff --git a/arch/arm/kernel/fiqasm.S b/arch/arm/kernel/fiqasm.S
index 8dd26e1a9bd6..eef484756af2 100644
--- a/arch/arm/kernel/fiqasm.S
+++ b/arch/arm/kernel/fiqasm.S
@@ -47,3 +47,7 @@ ENTRY(__get_fiq_regs)
 	mov	r0, r0		@ avoid hazard prior to ARMv4
 	ret	lr
 ENDPROC(__get_fiq_regs)
+
+ENTRY(__FIQ_Branch)
+	mov pc, r8
+ENDPROC(__FIQ_Branch)
diff --git a/arch/arm/kernel/reboot.c b/arch/arm/kernel/reboot.c
index 3f0d5c3dae11..cfdbcc9826c0 100644
--- a/arch/arm/kernel/reboot.c
+++ b/arch/arm/kernel/reboot.c
@@ -102,9 +102,7 @@ void machine_shutdown(void)
  */
 void machine_halt(void)
 {
-	local_irq_disable();
-	smp_send_stop();
-	while (1);
+	machine_power_off();
 }
 
 /*
diff --git a/arch/arm/kernel/setup.c b/arch/arm/kernel/setup.c
index e6a857bf0ce6..6fa23db5ac5a 100644
--- a/arch/arm/kernel/setup.c
+++ b/arch/arm/kernel/setup.c
@@ -1266,6 +1266,8 @@ static int c_show(struct seq_file *m, void *v)
 {
 	int i, j;
 	u32 cpuid;
+	struct device_node *np;
+	const char *model;
 
 	for_each_online_cpu(i) {
 		/*
@@ -1325,6 +1327,14 @@ static int c_show(struct seq_file *m, void *v)
 	seq_printf(m, "Revision\t: %04x\n", system_rev);
 	seq_printf(m, "Serial\t\t: %s\n", system_serial);
 
+	np = of_find_node_by_path("/");
+	if (np) {
+		if (!of_property_read_string(np, "model",
+					     &model))
+			seq_printf(m, "Model\t\t: %s\n", model);
+		of_node_put(np);
+	}
+
 	return 0;
 }
 
diff --git a/arch/arm/lib/Makefile b/arch/arm/lib/Makefile
index 0ca5aae1bcc3..c3a0c40550ee 100644
--- a/arch/arm/lib/Makefile
+++ b/arch/arm/lib/Makefile
@@ -7,8 +7,8 @@
 
 lib-y		:= changebit.o csumipv6.o csumpartial.o               \
 		   csumpartialcopy.o csumpartialcopyuser.o clearbit.o \
-		   delay.o delay-loop.o findbit.o memchr.o memcpy.o   \
-		   memmove.o memset.o setbit.o                        \
+		   delay.o delay-loop.o findbit.o memchr.o            \
+		   setbit.o                                           \
 		   strchr.o strrchr.o                                 \
 		   testchangebit.o testclearbit.o testsetbit.o        \
 		   ashldi3.o ashrdi3.o lshrdi3.o muldi3.o             \
@@ -25,6 +25,16 @@ else
   lib-y	+= backtrace.o
 endif
 
+# Choose optimised implementations for Raspberry Pi
+ifeq ($(CONFIG_BCM2835_FAST_MEMCPY),y)
+  CFLAGS_uaccess_with_memcpy.o += -DCOPY_FROM_USER_THRESHOLD=1600
+  CFLAGS_uaccess_with_memcpy.o += -DCOPY_TO_USER_THRESHOLD=672
+  obj-$(CONFIG_MODULES) += exports_rpi.o
+  lib-y        += memcpy_rpi.o memmove_rpi.o memset_rpi.o memcmp_rpi.o
+else
+  lib-y        += memcpy.o memmove.o memset.o
+endif
+
 # using lib_ here won't override already available weak symbols
 obj-$(CONFIG_UACCESS_WITH_MEMCPY) += uaccess_with_memcpy.o
 
diff --git a/arch/arm/lib/arm-mem.h b/arch/arm/lib/arm-mem.h
new file mode 100644
index 000000000000..5d4bda19ad20
--- /dev/null
+++ b/arch/arm/lib/arm-mem.h
@@ -0,0 +1,159 @@
+/*
+Copyright (c) 2013, Raspberry Pi Foundation
+Copyright (c) 2013, RISC OS Open Ltd
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the copyright holder nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+.macro myfunc fname
+ .func fname
+ .global fname
+fname:
+.endm
+
+.macro preload_leading_step1  backwards, ptr, base
+/* If the destination is already 16-byte aligned, then we need to preload
+ * between 0 and prefetch_distance (inclusive) cache lines ahead so there
+ * are no gaps when the inner loop starts.
+ */
+ .if backwards
+        sub     ptr, base, #1
+        bic     ptr, ptr, #31
+ .else
+        bic     ptr, base, #31
+ .endif
+ .set OFFSET, 0
+ .rept prefetch_distance+1
+        pld     [ptr, #OFFSET]
+  .if backwards
+   .set OFFSET, OFFSET-32
+  .else
+   .set OFFSET, OFFSET+32
+  .endif
+ .endr
+.endm
+
+.macro preload_leading_step2  backwards, ptr, base, leading_bytes, tmp
+/* However, if the destination is not 16-byte aligned, we may need to
+ * preload one more cache line than that. The question we need to ask is:
+ * are the leading bytes more than the amount by which the source
+ * pointer will be rounded down for preloading, and if so, by how many
+ * cache lines?
+ */
+ .if backwards
+/* Here we compare against how many bytes we are into the
+ * cache line, counting down from the highest such address.
+ * Effectively, we want to calculate
+ *     leading_bytes = dst&15
+ *     cacheline_offset = 31-((src-leading_bytes-1)&31)
+ *     extra_needed = leading_bytes - cacheline_offset
+ * and test if extra_needed is <= 0, or rearranging:
+ *     leading_bytes + (src-leading_bytes-1)&31 <= 31
+ */
+        mov     tmp, base, lsl #32-5
+        sbc     tmp, tmp, leading_bytes, lsl #32-5
+        adds    tmp, tmp, leading_bytes, lsl #32-5
+        bcc     61f
+        pld     [ptr, #-32*(prefetch_distance+1)]
+ .else
+/* Effectively, we want to calculate
+ *     leading_bytes = (-dst)&15
+ *     cacheline_offset = (src+leading_bytes)&31
+ *     extra_needed = leading_bytes - cacheline_offset
+ * and test if extra_needed is <= 0.
+ */
+        mov     tmp, base, lsl #32-5
+        add     tmp, tmp, leading_bytes, lsl #32-5
+        rsbs    tmp, tmp, leading_bytes, lsl #32-5
+        bls     61f
+        pld     [ptr, #32*(prefetch_distance+1)]
+ .endif
+61:
+.endm
+
+.macro preload_trailing  backwards, base, remain, tmp
+        /* We need either 0, 1 or 2 extra preloads */
+ .if backwards
+        rsb     tmp, base, #0
+        mov     tmp, tmp, lsl #32-5
+ .else
+        mov     tmp, base, lsl #32-5
+ .endif
+        adds    tmp, tmp, remain, lsl #32-5
+        adceqs  tmp, tmp, #0
+        /* The instruction above has two effects: ensures Z is only
+         * set if C was clear (so Z indicates that both shifted quantities
+         * were 0), and clears C if Z was set (so C indicates that the sum
+         * of the shifted quantities was greater and not equal to 32) */
+        beq     82f
+ .if backwards
+        sub     tmp, base, #1
+        bic     tmp, tmp, #31
+ .else
+        bic     tmp, base, #31
+ .endif
+        bcc     81f
+ .if backwards
+        pld     [tmp, #-32*(prefetch_distance+1)]
+81:
+        pld     [tmp, #-32*prefetch_distance]
+ .else
+        pld     [tmp, #32*(prefetch_distance+2)]
+81:
+        pld     [tmp, #32*(prefetch_distance+1)]
+ .endif
+82:
+.endm
+
+.macro preload_all    backwards, narrow_case, shift, base, remain, tmp0, tmp1
+ .if backwards
+        sub     tmp0, base, #1
+        bic     tmp0, tmp0, #31
+        pld     [tmp0]
+        sub     tmp1, base, remain, lsl #shift
+ .else
+        bic     tmp0, base, #31
+        pld     [tmp0]
+        add     tmp1, base, remain, lsl #shift
+        sub     tmp1, tmp1, #1
+ .endif
+        bic     tmp1, tmp1, #31
+        cmp     tmp1, tmp0
+        beq     92f
+ .if narrow_case
+        /* In this case, all the data fits in either 1 or 2 cache lines */
+        pld     [tmp1]
+ .else
+91:
+  .if backwards
+        sub     tmp0, tmp0, #32
+  .else
+        add     tmp0, tmp0, #32
+  .endif
+        cmp     tmp0, tmp1
+        pld     [tmp0]
+        bne     91b
+ .endif
+92:
+.endm
diff --git a/arch/arm/lib/copy_from_user.S b/arch/arm/lib/copy_from_user.S
index 270de7debd0f..2eda93fc22e6 100644
--- a/arch/arm/lib/copy_from_user.S
+++ b/arch/arm/lib/copy_from_user.S
@@ -104,7 +104,8 @@ UNWIND( .save	{r0, r2, r3, \regs}		)
 
 	.text
 
-ENTRY(arm_copy_from_user)
+ENTRY(__copy_from_user_std)
+WEAK(arm_copy_from_user)
 #ifdef CONFIG_CPU_SPECTRE
 	ldr	r3, =TASK_SIZE
 	uaccess_mask_range_ptr r1, r2, r3, ip
@@ -113,6 +114,7 @@ ENTRY(arm_copy_from_user)
 #include "copy_template.S"
 
 ENDPROC(arm_copy_from_user)
+ENDPROC(__copy_from_user_std)
 
 	.pushsection .text.fixup,"ax"
 	.align 0
diff --git a/arch/arm/lib/exports_rpi.c b/arch/arm/lib/exports_rpi.c
new file mode 100644
index 000000000000..1f826047db75
--- /dev/null
+++ b/arch/arm/lib/exports_rpi.c
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2014, Raspberry Pi (Trading) Ltd.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The names of the above-listed copyright holders may not be used
+ *    to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * ALTERNATIVELY, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2, as published by the Free
+ * Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+EXPORT_SYMBOL(memcmp);
diff --git a/arch/arm/lib/memcmp_rpi.S b/arch/arm/lib/memcmp_rpi.S
new file mode 100644
index 000000000000..bf6e4edfc9d3
--- /dev/null
+++ b/arch/arm/lib/memcmp_rpi.S
@@ -0,0 +1,285 @@
+/*
+Copyright (c) 2013, Raspberry Pi Foundation
+Copyright (c) 2013, RISC OS Open Ltd
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the copyright holder nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <linux/linkage.h>
+#include "arm-mem.h"
+
+/* Prevent the stack from becoming executable */
+#if defined(__linux__) && defined(__ELF__)
+.section .note.GNU-stack,"",%progbits
+#endif
+
+    .text
+    .arch armv6
+    .object_arch armv4
+    .arm
+    .altmacro
+    .p2align 2
+
+.macro memcmp_process_head  unaligned
+ .if unaligned
+        ldr     DAT0, [S_1], #4
+        ldr     DAT1, [S_1], #4
+        ldr     DAT2, [S_1], #4
+        ldr     DAT3, [S_1], #4
+ .else
+        ldmia   S_1!, {DAT0, DAT1, DAT2, DAT3}
+ .endif
+        ldmia   S_2!, {DAT4, DAT5, DAT6, DAT7}
+.endm
+
+.macro memcmp_process_tail
+        cmp     DAT0, DAT4
+        cmpeq   DAT1, DAT5
+        cmpeq   DAT2, DAT6
+        cmpeq   DAT3, DAT7
+        bne     200f
+.endm
+
+.macro memcmp_leading_31bytes
+        movs    DAT0, OFF, lsl #31
+        ldrmib  DAT0, [S_1], #1
+        ldrcsh  DAT1, [S_1], #2
+        ldrmib  DAT4, [S_2], #1
+        ldrcsh  DAT5, [S_2], #2
+        movpl   DAT0, #0
+        movcc   DAT1, #0
+        movpl   DAT4, #0
+        movcc   DAT5, #0
+        submi   N, N, #1
+        subcs   N, N, #2
+        cmp     DAT0, DAT4
+        cmpeq   DAT1, DAT5
+        bne     200f
+        movs    DAT0, OFF, lsl #29
+        ldrmi   DAT0, [S_1], #4
+        ldrcs   DAT1, [S_1], #4
+        ldrcs   DAT2, [S_1], #4
+        ldrmi   DAT4, [S_2], #4
+        ldmcsia S_2!, {DAT5, DAT6}
+        movpl   DAT0, #0
+        movcc   DAT1, #0
+        movcc   DAT2, #0
+        movpl   DAT4, #0
+        movcc   DAT5, #0
+        movcc   DAT6, #0
+        submi   N, N, #4
+        subcs   N, N, #8
+        cmp     DAT0, DAT4
+        cmpeq   DAT1, DAT5
+        cmpeq   DAT2, DAT6
+        bne     200f
+        tst     OFF, #16
+        beq     105f
+        memcmp_process_head  1
+        sub     N, N, #16
+        memcmp_process_tail
+105:
+.endm
+
+.macro memcmp_trailing_15bytes  unaligned
+        movs    N, N, lsl #29
+ .if unaligned
+        ldrcs   DAT0, [S_1], #4
+        ldrcs   DAT1, [S_1], #4
+ .else
+        ldmcsia S_1!, {DAT0, DAT1}
+ .endif
+        ldrmi   DAT2, [S_1], #4
+        ldmcsia S_2!, {DAT4, DAT5}
+        ldrmi   DAT6, [S_2], #4
+        movcc   DAT0, #0
+        movcc   DAT1, #0
+        movpl   DAT2, #0
+        movcc   DAT4, #0
+        movcc   DAT5, #0
+        movpl   DAT6, #0
+        cmp     DAT0, DAT4
+        cmpeq   DAT1, DAT5
+        cmpeq   DAT2, DAT6
+        bne     200f
+        movs    N, N, lsl #2
+        ldrcsh  DAT0, [S_1], #2
+        ldrmib  DAT1, [S_1]
+        ldrcsh  DAT4, [S_2], #2
+        ldrmib  DAT5, [S_2]
+        movcc   DAT0, #0
+        movpl   DAT1, #0
+        movcc   DAT4, #0
+        movpl   DAT5, #0
+        cmp     DAT0, DAT4
+        cmpeq   DAT1, DAT5
+        bne     200f
+.endm
+
+.macro memcmp_long_inner_loop  unaligned
+110:
+        memcmp_process_head  unaligned
+        pld     [S_2, #prefetch_distance*32 + 16]
+        memcmp_process_tail
+        memcmp_process_head  unaligned
+        pld     [S_1, OFF]
+        memcmp_process_tail
+        subs    N, N, #32
+        bhs     110b
+        /* Just before the final (prefetch_distance+1) 32-byte blocks,
+         * deal with final preloads */
+        preload_trailing  0, S_1, N, DAT0
+        preload_trailing  0, S_2, N, DAT0
+        add     N, N, #(prefetch_distance+2)*32 - 16
+120:
+        memcmp_process_head  unaligned
+        memcmp_process_tail
+        subs    N, N, #16
+        bhs     120b
+        /* Trailing words and bytes */
+        tst     N, #15
+        beq     199f
+        memcmp_trailing_15bytes  unaligned
+199:    /* Reached end without detecting a difference */
+        mov     a1, #0
+        setend  le
+        pop     {DAT1-DAT6, pc}
+.endm
+
+.macro memcmp_short_inner_loop  unaligned
+        subs    N, N, #16     /* simplifies inner loop termination */
+        blo     122f
+120:
+        memcmp_process_head  unaligned
+        memcmp_process_tail
+        subs    N, N, #16
+        bhs     120b
+122:    /* Trailing words and bytes */
+        tst     N, #15
+        beq     199f
+        memcmp_trailing_15bytes  unaligned
+199:    /* Reached end without detecting a difference */
+        mov     a1, #0
+        setend  le
+        pop     {DAT1-DAT6, pc}
+.endm
+
+/*
+ * int memcmp(const void *s1, const void *s2, size_t n);
+ * On entry:
+ * a1 = pointer to buffer 1
+ * a2 = pointer to buffer 2
+ * a3 = number of bytes to compare (as unsigned chars)
+ * On exit:
+ * a1 = >0/=0/<0 if s1 >/=/< s2
+ */
+
+.set prefetch_distance, 2
+
+ENTRY(memcmp)
+        S_1     .req    a1
+        S_2     .req    a2
+        N       .req    a3
+        DAT0    .req    a4
+        DAT1    .req    v1
+        DAT2    .req    v2
+        DAT3    .req    v3
+        DAT4    .req    v4
+        DAT5    .req    v5
+        DAT6    .req    v6
+        DAT7    .req    ip
+        OFF     .req    lr
+
+        push    {DAT1-DAT6, lr}
+        setend  be /* lowest-addressed bytes are most significant */
+
+        /* To preload ahead as we go, we need at least (prefetch_distance+2) 32-byte blocks */
+        cmp     N, #(prefetch_distance+3)*32 - 1
+        blo     170f
+
+        /* Long case */
+        /* Adjust N so that the decrement instruction can also test for
+         * inner loop termination. We want it to stop when there are
+         * (prefetch_distance+1) complete blocks to go. */
+        sub     N, N, #(prefetch_distance+2)*32
+        preload_leading_step1  0, DAT0, S_1
+        preload_leading_step1  0, DAT1, S_2
+        tst     S_2, #31
+        beq     154f
+        rsb     OFF, S_2, #0 /* no need to AND with 15 here */
+        preload_leading_step2  0, DAT0, S_1, OFF, DAT2
+        preload_leading_step2  0, DAT1, S_2, OFF, DAT2
+        memcmp_leading_31bytes
+154:    /* Second source now cacheline (32-byte) aligned; we have at
+         * least one prefetch to go. */
+        /* Prefetch offset is best selected such that it lies in the
+         * first 8 of each 32 bytes - but it's just as easy to aim for
+         * the first one */
+        and     OFF, S_1, #31
+        rsb     OFF, OFF, #32*prefetch_distance
+        tst     S_1, #3
+        bne     140f
+        memcmp_long_inner_loop  0
+140:    memcmp_long_inner_loop  1
+
+170:    /* Short case */
+        teq     N, #0
+        beq     199f
+        preload_all 0, 0, 0, S_1, N, DAT0, DAT1
+        preload_all 0, 0, 0, S_2, N, DAT0, DAT1
+        tst     S_2, #3
+        beq     174f
+172:    subs    N, N, #1
+        blo     199f
+        ldrb    DAT0, [S_1], #1
+        ldrb    DAT4, [S_2], #1
+        cmp     DAT0, DAT4
+        bne     200f
+        tst     S_2, #3
+        bne     172b
+174:    /* Second source now 4-byte aligned; we have 0 or more bytes to go */
+        tst     S_1, #3
+        bne     140f
+        memcmp_short_inner_loop  0
+140:    memcmp_short_inner_loop  1
+
+200:    /* Difference found: determine sign. */
+        movhi   a1, #1
+        movlo   a1, #-1
+        setend  le
+        pop     {DAT1-DAT6, pc}
+
+        .unreq  S_1
+        .unreq  S_2
+        .unreq  N
+        .unreq  DAT0
+        .unreq  DAT1
+        .unreq  DAT2
+        .unreq  DAT3
+        .unreq  DAT4
+        .unreq  DAT5
+        .unreq  DAT6
+        .unreq  DAT7
+        .unreq  OFF
+ENDPROC(memcmp)
diff --git a/arch/arm/lib/memcpy_rpi.S b/arch/arm/lib/memcpy_rpi.S
new file mode 100644
index 000000000000..d246f9f3903a
--- /dev/null
+++ b/arch/arm/lib/memcpy_rpi.S
@@ -0,0 +1,65 @@
+/*
+Copyright (c) 2013, Raspberry Pi Foundation
+Copyright (c) 2013, RISC OS Open Ltd
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the copyright holder nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <linux/linkage.h>
+#include <asm/assembler.h>
+#include <asm/unwind.h>
+#include "arm-mem.h"
+#include "memcpymove.h"
+
+/* Prevent the stack from becoming executable */
+#if defined(__linux__) && defined(__ELF__)
+.section .note.GNU-stack,"",%progbits
+#endif
+
+    .text
+    .arch armv6
+    .object_arch armv4
+    .arm
+    .altmacro
+    .p2align 2
+
+/*
+ * void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
+ * On entry:
+ * a1 = pointer to destination
+ * a2 = pointer to source
+ * a3 = number of bytes to copy
+ * On exit:
+ * a1 preserved
+ */
+
+.set prefetch_distance, 3
+
+ENTRY(mmiocpy)
+ENTRY(memcpy)
+ENTRY(__memcpy)
+        memcpy  0
+ENDPROC(__memcpy)
+ENDPROC(memcpy)
+ENDPROC(mmiocpy)
diff --git a/arch/arm/lib/memcpymove.h b/arch/arm/lib/memcpymove.h
new file mode 100644
index 000000000000..65a6e065a7f2
--- /dev/null
+++ b/arch/arm/lib/memcpymove.h
@@ -0,0 +1,488 @@
+/*
+Copyright (c) 2013, Raspberry Pi Foundation
+Copyright (c) 2013, RISC OS Open Ltd
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the copyright holder nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+.macro unaligned_words  backwards, align, use_pld, words, r0, r1, r2, r3, r4, r5, r6, r7, r8
+ .if words == 1
+  .if backwards
+        mov     r1, r0, lsl #32-align*8
+        ldr     r0, [S, #-4]!
+        orr     r1, r1, r0, lsr #align*8
+        str     r1, [D, #-4]!
+  .else
+        mov     r0, r1, lsr #align*8
+        ldr     r1, [S, #4]!
+        orr     r0, r0, r1, lsl #32-align*8
+        str     r0, [D], #4
+  .endif
+ .elseif words == 2
+  .if backwards
+        ldr     r1, [S, #-4]!
+        mov     r2, r0, lsl #32-align*8
+        ldr     r0, [S, #-4]!
+        orr     r2, r2, r1, lsr #align*8
+        mov     r1, r1, lsl #32-align*8
+        orr     r1, r1, r0, lsr #align*8
+        stmdb   D!, {r1, r2}
+  .else
+        ldr     r1, [S, #4]!
+        mov     r0, r2, lsr #align*8
+        ldr     r2, [S, #4]!
+        orr     r0, r0, r1, lsl #32-align*8
+        mov     r1, r1, lsr #align*8
+        orr     r1, r1, r2, lsl #32-align*8
+        stmia   D!, {r0, r1}
+  .endif
+ .elseif words == 4
+  .if backwards
+        ldmdb   S!, {r2, r3}
+        mov     r4, r0, lsl #32-align*8
+        ldmdb   S!, {r0, r1}
+        orr     r4, r4, r3, lsr #align*8
+        mov     r3, r3, lsl #32-align*8
+        orr     r3, r3, r2, lsr #align*8
+        mov     r2, r2, lsl #32-align*8
+        orr     r2, r2, r1, lsr #align*8
+        mov     r1, r1, lsl #32-align*8
+        orr     r1, r1, r0, lsr #align*8
+        stmdb   D!, {r1, r2, r3, r4}
+  .else
+        ldmib   S!, {r1, r2}
+        mov     r0, r4, lsr #align*8
+        ldmib   S!, {r3, r4}
+        orr     r0, r0, r1, lsl #32-align*8
+        mov     r1, r1, lsr #align*8
+        orr     r1, r1, r2, lsl #32-align*8
+        mov     r2, r2, lsr #align*8
+        orr     r2, r2, r3, lsl #32-align*8
+        mov     r3, r3, lsr #align*8
+        orr     r3, r3, r4, lsl #32-align*8
+        stmia   D!, {r0, r1, r2, r3}
+  .endif
+ .elseif words == 8
+  .if backwards
+        ldmdb   S!, {r4, r5, r6, r7}
+        mov     r8, r0, lsl #32-align*8
+        ldmdb   S!, {r0, r1, r2, r3}
+   .if use_pld
+        pld     [S, OFF]
+   .endif
+        orr     r8, r8, r7, lsr #align*8
+        mov     r7, r7, lsl #32-align*8
+        orr     r7, r7, r6, lsr #align*8
+        mov     r6, r6, lsl #32-align*8
+        orr     r6, r6, r5, lsr #align*8
+        mov     r5, r5, lsl #32-align*8
+        orr     r5, r5, r4, lsr #align*8
+        mov     r4, r4, lsl #32-align*8
+        orr     r4, r4, r3, lsr #align*8
+        mov     r3, r3, lsl #32-align*8
+        orr     r3, r3, r2, lsr #align*8
+        mov     r2, r2, lsl #32-align*8
+        orr     r2, r2, r1, lsr #align*8
+        mov     r1, r1, lsl #32-align*8
+        orr     r1, r1, r0, lsr #align*8
+        stmdb   D!, {r5, r6, r7, r8}
+        stmdb   D!, {r1, r2, r3, r4}
+  .else
+        ldmib   S!, {r1, r2, r3, r4}
+        mov     r0, r8, lsr #align*8
+        ldmib   S!, {r5, r6, r7, r8}
+   .if use_pld
+        pld     [S, OFF]
+   .endif
+        orr     r0, r0, r1, lsl #32-align*8
+        mov     r1, r1, lsr #align*8
+        orr     r1, r1, r2, lsl #32-align*8
+        mov     r2, r2, lsr #align*8
+        orr     r2, r2, r3, lsl #32-align*8
+        mov     r3, r3, lsr #align*8
+        orr     r3, r3, r4, lsl #32-align*8
+        mov     r4, r4, lsr #align*8
+        orr     r4, r4, r5, lsl #32-align*8
+        mov     r5, r5, lsr #align*8
+        orr     r5, r5, r6, lsl #32-align*8
+        mov     r6, r6, lsr #align*8
+        orr     r6, r6, r7, lsl #32-align*8
+        mov     r7, r7, lsr #align*8
+        orr     r7, r7, r8, lsl #32-align*8
+        stmia   D!, {r0, r1, r2, r3}
+        stmia   D!, {r4, r5, r6, r7}
+  .endif
+ .endif
+.endm
+
+.macro memcpy_leading_15bytes  backwards, align
+        movs    DAT1, DAT2, lsl #31
+        sub     N, N, DAT2
+ .if backwards
+        ldrmib  DAT0, [S, #-1]!
+        ldrcsh  DAT1, [S, #-2]!
+        strmib  DAT0, [D, #-1]!
+        strcsh  DAT1, [D, #-2]!
+ .else
+        ldrmib  DAT0, [S], #1
+        ldrcsh  DAT1, [S], #2
+        strmib  DAT0, [D], #1
+        strcsh  DAT1, [D], #2
+ .endif
+        movs    DAT1, DAT2, lsl #29
+ .if backwards
+        ldrmi   DAT0, [S, #-4]!
+  .if align == 0
+        ldmcsdb S!, {DAT1, DAT2}
+  .else
+        ldrcs   DAT2, [S, #-4]!
+        ldrcs   DAT1, [S, #-4]!
+  .endif
+        strmi   DAT0, [D, #-4]!
+        stmcsdb D!, {DAT1, DAT2}
+ .else
+        ldrmi   DAT0, [S], #4
+  .if align == 0
+        ldmcsia S!, {DAT1, DAT2}
+  .else
+        ldrcs   DAT1, [S], #4
+        ldrcs   DAT2, [S], #4
+  .endif
+        strmi   DAT0, [D], #4
+        stmcsia D!, {DAT1, DAT2}
+ .endif
+.endm
+
+.macro memcpy_trailing_15bytes  backwards, align
+        movs    N, N, lsl #29
+ .if backwards
+  .if align == 0
+        ldmcsdb S!, {DAT0, DAT1}
+  .else
+        ldrcs   DAT1, [S, #-4]!
+        ldrcs   DAT0, [S, #-4]!
+  .endif
+        ldrmi   DAT2, [S, #-4]!
+        stmcsdb D!, {DAT0, DAT1}
+        strmi   DAT2, [D, #-4]!
+ .else
+  .if align == 0
+        ldmcsia S!, {DAT0, DAT1}
+  .else
+        ldrcs   DAT0, [S], #4
+        ldrcs   DAT1, [S], #4
+  .endif
+        ldrmi   DAT2, [S], #4
+        stmcsia D!, {DAT0, DAT1}
+        strmi   DAT2, [D], #4
+ .endif
+        movs    N, N, lsl #2
+ .if backwards
+        ldrcsh  DAT0, [S, #-2]!
+        ldrmib  DAT1, [S, #-1]
+        strcsh  DAT0, [D, #-2]!
+        strmib  DAT1, [D, #-1]
+ .else
+        ldrcsh  DAT0, [S], #2
+        ldrmib  DAT1, [S]
+        strcsh  DAT0, [D], #2
+        strmib  DAT1, [D]
+ .endif
+.endm
+
+.macro memcpy_long_inner_loop  backwards, align
+ .if align != 0
+  .if backwards
+        ldr     DAT0, [S, #-align]!
+  .else
+        ldr     LAST, [S, #-align]!
+  .endif
+ .endif
+110:
+ .if align == 0
+  .if backwards
+        ldmdb   S!, {DAT0, DAT1, DAT2, DAT3, DAT4, DAT5, DAT6, LAST}
+        pld     [S, OFF]
+        stmdb   D!, {DAT4, DAT5, DAT6, LAST}
+        stmdb   D!, {DAT0, DAT1, DAT2, DAT3}
+  .else
+        ldmia   S!, {DAT0, DAT1, DAT2, DAT3, DAT4, DAT5, DAT6, LAST}
+        pld     [S, OFF]
+        stmia   D!, {DAT0, DAT1, DAT2, DAT3}
+        stmia   D!, {DAT4, DAT5, DAT6, LAST}
+  .endif
+ .else
+        unaligned_words  backwards, align, 1, 8, DAT0, DAT1, DAT2, DAT3, DAT4, DAT5, DAT6, DAT7, LAST
+ .endif
+        subs    N, N, #32
+        bhs     110b
+        /* Just before the final (prefetch_distance+1) 32-byte blocks, deal with final preloads */
+        preload_trailing  backwards, S, N, OFF
+        add     N, N, #(prefetch_distance+2)*32 - 32
+120:
+ .if align == 0
+  .if backwards
+        ldmdb   S!, {DAT0, DAT1, DAT2, DAT3, DAT4, DAT5, DAT6, LAST}
+        stmdb   D!, {DAT4, DAT5, DAT6, LAST}
+        stmdb   D!, {DAT0, DAT1, DAT2, DAT3}
+  .else
+        ldmia   S!, {DAT0, DAT1, DAT2, DAT3, DAT4, DAT5, DAT6, LAST}
+        stmia   D!, {DAT0, DAT1, DAT2, DAT3}
+        stmia   D!, {DAT4, DAT5, DAT6, LAST}
+  .endif
+ .else
+        unaligned_words  backwards, align, 0, 8, DAT0, DAT1, DAT2, DAT3, DAT4, DAT5, DAT6, DAT7, LAST
+ .endif
+        subs    N, N, #32
+        bhs     120b
+        tst     N, #16
+ .if align == 0
+  .if backwards
+        ldmnedb S!, {DAT0, DAT1, DAT2, LAST}
+        stmnedb D!, {DAT0, DAT1, DAT2, LAST}
+  .else
+        ldmneia S!, {DAT0, DAT1, DAT2, LAST}
+        stmneia D!, {DAT0, DAT1, DAT2, LAST}
+  .endif
+ .else
+        beq     130f
+        unaligned_words  backwards, align, 0, 4, DAT0, DAT1, DAT2, DAT3, LAST
+130:
+ .endif
+        /* Trailing words and bytes */
+        tst      N, #15
+        beq      199f
+ .if align != 0
+        add     S, S, #align
+ .endif
+        memcpy_trailing_15bytes  backwards, align
+199:
+        pop     {DAT3, DAT4, DAT5, DAT6, DAT7}
+        pop     {D, DAT1, DAT2, pc}
+.endm
+
+.macro memcpy_medium_inner_loop  backwards, align
+120:
+ .if backwards
+  .if align == 0
+        ldmdb   S!, {DAT0, DAT1, DAT2, LAST}
+  .else
+        ldr     LAST, [S, #-4]!
+        ldr     DAT2, [S, #-4]!
+        ldr     DAT1, [S, #-4]!
+        ldr     DAT0, [S, #-4]!
+  .endif
+        stmdb   D!, {DAT0, DAT1, DAT2, LAST}
+ .else
+  .if align == 0
+        ldmia   S!, {DAT0, DAT1, DAT2, LAST}
+  .else
+        ldr     DAT0, [S], #4
+        ldr     DAT1, [S], #4
+        ldr     DAT2, [S], #4
+        ldr     LAST, [S], #4
+  .endif
+        stmia   D!, {DAT0, DAT1, DAT2, LAST}
+ .endif
+        subs     N, N, #16
+        bhs      120b
+        /* Trailing words and bytes */
+        tst      N, #15
+        beq      199f
+        memcpy_trailing_15bytes  backwards, align
+199:
+        pop     {D, DAT1, DAT2, pc}
+.endm
+
+.macro memcpy_short_inner_loop  backwards, align
+        tst     N, #16
+ .if backwards
+  .if align == 0
+        ldmnedb S!, {DAT0, DAT1, DAT2, LAST}
+  .else
+        ldrne   LAST, [S, #-4]!
+        ldrne   DAT2, [S, #-4]!
+        ldrne   DAT1, [S, #-4]!
+        ldrne   DAT0, [S, #-4]!
+  .endif
+        stmnedb D!, {DAT0, DAT1, DAT2, LAST}
+ .else
+  .if align == 0
+        ldmneia S!, {DAT0, DAT1, DAT2, LAST}
+  .else
+        ldrne   DAT0, [S], #4
+        ldrne   DAT1, [S], #4
+        ldrne   DAT2, [S], #4
+        ldrne   LAST, [S], #4
+  .endif
+        stmneia D!, {DAT0, DAT1, DAT2, LAST}
+ .endif
+        memcpy_trailing_15bytes  backwards, align
+199:
+        pop     {D, DAT1, DAT2, pc}
+.endm
+
+.macro memcpy backwards
+        D       .req    a1
+        S       .req    a2
+        N       .req    a3
+        DAT0    .req    a4
+        DAT1    .req    v1
+        DAT2    .req    v2
+        DAT3    .req    v3
+        DAT4    .req    v4
+        DAT5    .req    v5
+        DAT6    .req    v6
+        DAT7    .req    sl
+        LAST    .req    ip
+        OFF     .req    lr
+
+        UNWIND( .fnstart )
+
+        push    {D, DAT1, DAT2, lr}
+        UNWIND( .fnend )
+
+        UNWIND( .fnstart )
+        UNWIND( .save {D, DAT1, DAT2, lr} )
+
+ .if backwards
+        add     D, D, N
+        add     S, S, N
+ .endif
+
+        /* See if we're guaranteed to have at least one 16-byte aligned 16-byte write */
+        cmp     N, #31
+        blo     170f
+        /* To preload ahead as we go, we need at least (prefetch_distance+2) 32-byte blocks */
+        cmp     N, #(prefetch_distance+3)*32 - 1
+        blo     160f
+
+        /* Long case */
+        push    {DAT3, DAT4, DAT5, DAT6, DAT7}
+        UNWIND( .fnend )
+
+        UNWIND( .fnstart )
+        UNWIND( .save {D, DAT1, DAT2, lr} )
+        UNWIND( .save {DAT3, DAT4, DAT5, DAT6, DAT7} )
+
+        /* Adjust N so that the decrement instruction can also test for
+         * inner loop termination. We want it to stop when there are
+         * (prefetch_distance+1) complete blocks to go. */
+        sub     N, N, #(prefetch_distance+2)*32
+        preload_leading_step1  backwards, DAT0, S
+ .if backwards
+        /* Bug in GAS: it accepts, but mis-assembles the instruction
+         * ands    DAT2, D, #60, 2
+         * which sets DAT2 to the number of leading bytes until destination is aligned and also clears C (sets borrow)
+         */
+        .word   0xE210513C
+        beq     154f
+ .else
+        ands    DAT2, D, #15
+        beq     154f
+        rsb     DAT2, DAT2, #16 /* number of leading bytes until destination aligned */
+ .endif
+        preload_leading_step2  backwards, DAT0, S, DAT2, OFF
+        memcpy_leading_15bytes backwards, 1
+154:    /* Destination now 16-byte aligned; we have at least one prefetch as well as at least one 16-byte output block */
+        /* Prefetch offset is best selected such that it lies in the first 8 of each 32 bytes - but it's just as easy to aim for the first one */
+ .if backwards
+        rsb     OFF, S, #3
+        and     OFF, OFF, #28
+        sub     OFF, OFF, #32*(prefetch_distance+1)
+ .else
+        and     OFF, S, #28
+        rsb     OFF, OFF, #32*prefetch_distance
+ .endif
+        movs    DAT0, S, lsl #31
+        bhi     157f
+        bcs     156f
+        bmi     155f
+        memcpy_long_inner_loop  backwards, 0
+155:    memcpy_long_inner_loop  backwards, 1
+156:    memcpy_long_inner_loop  backwards, 2
+157:    memcpy_long_inner_loop  backwards, 3
+
+        UNWIND( .fnend )
+
+        UNWIND( .fnstart )
+        UNWIND( .save {D, DAT1, DAT2, lr} )
+
+160:    /* Medium case */
+        preload_all  backwards, 0, 0, S, N, DAT2, OFF
+        sub     N, N, #16     /* simplifies inner loop termination */
+ .if backwards
+        ands    DAT2, D, #15
+        beq     164f
+ .else
+        ands    DAT2, D, #15
+        beq     164f
+        rsb     DAT2, DAT2, #16
+ .endif
+        memcpy_leading_15bytes backwards, align
+164:    /* Destination now 16-byte aligned; we have at least one 16-byte output block */
+        tst     S, #3
+        bne     140f
+        memcpy_medium_inner_loop  backwards, 0
+140:    memcpy_medium_inner_loop  backwards, 1
+
+170:    /* Short case, less than 31 bytes, so no guarantee of at least one 16-byte block */
+        teq     N, #0
+        beq     199f
+        preload_all  backwards, 1, 0, S, N, DAT2, LAST
+        tst     D, #3
+        beq     174f
+172:    subs    N, N, #1
+        blo     199f
+ .if backwards
+        ldrb    DAT0, [S, #-1]!
+        strb    DAT0, [D, #-1]!
+ .else
+        ldrb    DAT0, [S], #1
+        strb    DAT0, [D], #1
+ .endif
+        tst     D, #3
+        bne     172b
+174:    /* Destination now 4-byte aligned; we have 0 or more output bytes to go */
+        tst     S, #3
+        bne     140f
+        memcpy_short_inner_loop  backwards, 0
+140:    memcpy_short_inner_loop  backwards, 1
+
+        UNWIND( .fnend )
+
+        .unreq  D
+        .unreq  S
+        .unreq  N
+        .unreq  DAT0
+        .unreq  DAT1
+        .unreq  DAT2
+        .unreq  DAT3
+        .unreq  DAT4
+        .unreq  DAT5
+        .unreq  DAT6
+        .unreq  DAT7
+        .unreq  LAST
+        .unreq  OFF
+.endm
diff --git a/arch/arm/lib/memmove_rpi.S b/arch/arm/lib/memmove_rpi.S
new file mode 100644
index 000000000000..5715dfd95859
--- /dev/null
+++ b/arch/arm/lib/memmove_rpi.S
@@ -0,0 +1,63 @@
+/*
+Copyright (c) 2013, Raspberry Pi Foundation
+Copyright (c) 2013, RISC OS Open Ltd
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the copyright holder nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <linux/linkage.h>
+#include <asm/assembler.h>
+#include <asm/unwind.h>
+#include "arm-mem.h"
+#include "memcpymove.h"
+
+/* Prevent the stack from becoming executable */
+#if defined(__linux__) && defined(__ELF__)
+.section .note.GNU-stack,"",%progbits
+#endif
+
+    .text
+    .arch armv6
+    .object_arch armv4
+    .arm
+    .altmacro
+    .p2align 2
+
+/*
+ * void *memmove(void *s1, const void *s2, size_t n);
+ * On entry:
+ * a1 = pointer to destination
+ * a2 = pointer to source
+ * a3 = number of bytes to copy
+ * On exit:
+ * a1 preserved
+ */
+
+.set prefetch_distance, 3
+
+ENTRY(memmove)
+        cmp     a2, a1
+        bpl     memcpy  /* pl works even over -1 - 0 and 0x7fffffff - 0x80000000 boundaries */
+        memcpy  1
+ENDPROC(memmove)
diff --git a/arch/arm/lib/memset_rpi.S b/arch/arm/lib/memset_rpi.S
new file mode 100644
index 000000000000..087d68ea5d18
--- /dev/null
+++ b/arch/arm/lib/memset_rpi.S
@@ -0,0 +1,132 @@
+/*
+Copyright (c) 2013, Raspberry Pi Foundation
+Copyright (c) 2013, RISC OS Open Ltd
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the copyright holder nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <linux/linkage.h>
+#include "arm-mem.h"
+
+/* Prevent the stack from becoming executable */
+#if defined(__linux__) && defined(__ELF__)
+.section .note.GNU-stack,"",%progbits
+#endif
+
+    .text
+    .arch armv6
+    .object_arch armv4
+    .arm
+    .altmacro
+    .p2align 2
+
+/*
+ *  void *memset(void *s, int c, size_t n);
+ *  On entry:
+ *  a1 = pointer to buffer to fill
+ *  a2 = byte pattern to fill with (caller-narrowed)
+ *  a3 = number of bytes to fill
+ *  On exit:
+ *  a1 preserved
+ */
+ENTRY(mmioset)
+ENTRY(memset)
+ENTRY(__memset)
+
+        S       .req    a1
+        DAT0    .req    a2
+        N       .req    a3
+        DAT1    .req    a4
+        DAT2    .req    ip
+        DAT3    .req    lr
+
+        orr     DAT0, DAT0, DAT0, lsl #8
+        orr     DAT0, DAT0, DAT0, lsl #16
+
+ENTRY(__memset32)
+        mov     DAT1, DAT0
+
+ENTRY(__memset64)
+        push    {S, lr}
+
+        /* See if we're guaranteed to have at least one 16-byte aligned 16-byte write */
+        cmp     N, #31
+        blo     170f
+
+161:    sub     N, N, #16     /* simplifies inner loop termination */
+        /* Leading words and bytes */
+        tst     S, #15
+        beq     164f
+        rsb     DAT3, S, #0   /* bits 0-3 = number of leading bytes until aligned */
+        movs    DAT2, DAT3, lsl #31
+        submi   N, N, #1
+        strmib  DAT0, [S], #1
+        subcs   N, N, #2
+        strcsh  DAT0, [S], #2
+        movs    DAT2, DAT3, lsl #29
+        submi   N, N, #4
+        strmi   DAT0, [S], #4
+        subcs   N, N, #8
+        stmcsia S!, {DAT0, DAT1}
+164:    /* Delayed set up of DAT2 and DAT3 so we could use them as scratch registers above */
+        mov     DAT2, DAT0
+        mov     DAT3, DAT1
+        /* Now the inner loop of 16-byte stores */
+165:    stmia   S!, {DAT0, DAT1, DAT2, DAT3}
+        subs    N, N, #16
+        bhs     165b
+166:    /* Trailing words and bytes */
+        movs    N, N, lsl #29
+        stmcsia S!, {DAT0, DAT1}
+        strmi   DAT0, [S], #4
+        movs    N, N, lsl #2
+        strcsh  DAT0, [S], #2
+        strmib  DAT0, [S]
+199:    pop     {S, pc}
+
+170:    /* Short case */
+        mov     DAT2, DAT0
+        mov     DAT3, DAT1
+        tst     S, #3
+        beq     174f
+172:    subs    N, N, #1
+        blo     199b
+        strb    DAT0, [S], #1
+        tst     S, #3
+        bne     172b
+174:    tst     N, #16
+        stmneia S!, {DAT0, DAT1, DAT2, DAT3}
+        b       166b
+
+        .unreq  S
+        .unreq  DAT0
+        .unreq  N
+        .unreq  DAT1
+        .unreq  DAT2
+        .unreq  DAT3
+ENDPROC(__memset64)
+ENDPROC(__memset32)
+ENDPROC(__memset)
+ENDPROC(memset)
+ENDPROC(mmioset)
diff --git a/arch/arm/lib/uaccess_with_memcpy.c b/arch/arm/lib/uaccess_with_memcpy.c
index c0ac7796d775..c416c7d3242f 100644
--- a/arch/arm/lib/uaccess_with_memcpy.c
+++ b/arch/arm/lib/uaccess_with_memcpy.c
@@ -19,6 +19,14 @@
 #include <asm/current.h>
 #include <asm/page.h>
 
+#ifndef COPY_FROM_USER_THRESHOLD
+#define COPY_FROM_USER_THRESHOLD 64
+#endif
+
+#ifndef COPY_TO_USER_THRESHOLD
+#define COPY_TO_USER_THRESHOLD 64
+#endif
+
 static int
 pin_page_for_write(const void __user *_addr, pte_t **ptep, spinlock_t **ptlp)
 {
@@ -43,7 +51,7 @@ pin_page_for_write(const void __user *_addr, pte_t **ptep, spinlock_t **ptlp)
 		return 0;
 
 	pmd = pmd_offset(pud, addr);
-	if (unlikely(pmd_none(*pmd)))
+	if (unlikely(pmd_none(*pmd) || pmd_bad(*pmd)))
 		return 0;
 
 	/*
@@ -89,6 +97,47 @@ pin_page_for_write(const void __user *_addr, pte_t **ptep, spinlock_t **ptlp)
 	return 1;
 }
 
+#ifdef CONFIG_BCM2835_FAST_MEMCPY
+static int
+pin_page_for_read(const void __user *_addr, pte_t **ptep, spinlock_t **ptlp)
+{
+	unsigned long addr = (unsigned long)_addr;
+	pgd_t *pgd;
+	p4d_t *p4d;
+	pmd_t *pmd;
+	pte_t *pte;
+	pud_t *pud;
+	spinlock_t *ptl;
+
+	pgd = pgd_offset(current->mm, addr);
+	if (unlikely(pgd_none(*pgd) || pgd_bad(*pgd)))
+		return 0;
+
+	p4d = p4d_offset(pgd, addr);
+	if (unlikely(p4d_none(*p4d) || p4d_bad(*p4d)))
+		return 0;
+
+	pud = pud_offset(p4d, addr);
+	if (unlikely(pud_none(*pud) || pud_bad(*pud)))
+		return 0;
+
+	pmd = pmd_offset(pud, addr);
+	if (unlikely(pmd_none(*pmd) || pmd_bad(*pmd)))
+		return 0;
+
+	pte = pte_offset_map_lock(current->mm, pmd, addr, &ptl);
+	if (unlikely(!pte_present(*pte) || !pte_young(*pte))) {
+		pte_unmap_unlock(pte, ptl);
+		return 0;
+	}
+
+	*ptep = pte;
+	*ptlp = ptl;
+
+	return 1;
+}
+#endif
+
 static unsigned long noinline
 __copy_to_user_memcpy(void __user *to, const void *from, unsigned long n)
 {
@@ -137,6 +186,54 @@ __copy_to_user_memcpy(void __user *to, const void *from, unsigned long n)
 	return n;
 }
 
+#ifdef CONFIG_BCM2835_FAST_MEMCPY
+static unsigned long noinline
+__copy_from_user_memcpy(void *to, const void __user *from, unsigned long n)
+{
+	unsigned long ua_flags;
+	int atomic;
+
+	/* the mmap semaphore is taken only if not in an atomic context */
+	atomic = in_atomic();
+
+	if (!atomic)
+		mmap_read_lock(current->mm);
+	while (n) {
+		pte_t *pte;
+		spinlock_t *ptl;
+		int tocopy;
+
+		while (!pin_page_for_read(from, &pte, &ptl)) {
+			char temp;
+			if (!atomic)
+				mmap_read_unlock(current->mm);
+			if (__get_user(temp, (char __user *)from))
+				goto out;
+			if (!atomic)
+				mmap_read_lock(current->mm);
+		}
+
+		tocopy = (~(unsigned long)from & ~PAGE_MASK) + 1;
+		if (tocopy > n)
+			tocopy = n;
+
+		ua_flags = uaccess_save_and_enable();
+		memcpy(to, (const void *)from, tocopy);
+		uaccess_restore(ua_flags);
+		to += tocopy;
+		from += tocopy;
+		n -= tocopy;
+
+		pte_unmap_unlock(pte, ptl);
+	}
+	if (!atomic)
+		mmap_read_unlock(current->mm);
+
+out:
+	return n;
+}
+#endif
+
 unsigned long
 arm_copy_to_user(void __user *to, const void *from, unsigned long n)
 {
@@ -147,7 +244,7 @@ arm_copy_to_user(void __user *to, const void *from, unsigned long n)
 	 * With frame pointer disabled, tail call optimization kicks in
 	 * as well making this test almost invisible.
 	 */
-	if (n < 64) {
+	if (n < COPY_TO_USER_THRESHOLD) {
 		unsigned long ua_flags = uaccess_save_and_enable();
 		n = __copy_to_user_std(to, from, n);
 		uaccess_restore(ua_flags);
@@ -157,6 +254,32 @@ arm_copy_to_user(void __user *to, const void *from, unsigned long n)
 	}
 	return n;
 }
+
+unsigned long __must_check
+arm_copy_from_user(void *to, const void __user *from, unsigned long n)
+{
+#ifdef CONFIG_BCM2835_FAST_MEMCPY
+	/*
+	 * This test is stubbed out of the main function above to keep
+	 * the overhead for small copies low by avoiding a large
+	 * register dump on the stack just to reload them right away.
+	 * With frame pointer disabled, tail call optimization kicks in
+	 * as well making this test almost invisible.
+	 */
+	if (n < COPY_TO_USER_THRESHOLD) {
+		unsigned long ua_flags = uaccess_save_and_enable();
+		n = __copy_from_user_std(to, from, n);
+		uaccess_restore(ua_flags);
+	} else {
+		n = __copy_from_user_memcpy(to, from, n);
+	}
+#else
+	unsigned long ua_flags = uaccess_save_and_enable();
+	n = __copy_from_user_std(to, from, n);
+	uaccess_restore(ua_flags);
+#endif
+	return n;
+}
 	
 static unsigned long noinline
 __clear_user_memset(void __user *addr, unsigned long n)
diff --git a/arch/arm/mach-bcm/Kconfig b/arch/arm/mach-bcm/Kconfig
index 24bc6e18d806..203be91af9eb 100644
--- a/arch/arm/mach-bcm/Kconfig
+++ b/arch/arm/mach-bcm/Kconfig
@@ -158,9 +158,11 @@ config ARCH_BCM2835
 	select ARM_TIMER_SP804
 	select HAVE_ARM_ARCH_TIMER if ARCH_MULTI_V7
 	select BCM2835_TIMER
+	select FIQ
 	select PINCTRL
 	select PINCTRL_BCM2835
 	select MFD_CORE
+	select MFD_SYSCON if ARCH_MULTI_V7
 	help
 	  This enables support for the Broadcom BCM2711 and BCM283x SoCs.
 	  This SoC is used in the Raspberry Pi and Roku 2 devices.
@@ -179,6 +181,13 @@ config ARCH_BCM_53573
 	  The base chip is BCM53573 and there are some packaging modifications
 	  like BCM47189 and BCM47452.
 
+config BCM2835_FAST_MEMCPY
+	bool "Enable optimized __copy_to_user and __copy_from_user"
+	depends on ARCH_BCM2835 && ARCH_MULTI_V6
+	default y
+	help
+	  Optimized versions of __copy_to_user and __copy_from_user for Pi1.
+
 config ARCH_BRCMSTB
 	bool "Broadcom BCM7XXX based boards"
 	depends on ARCH_MULTI_V7
diff --git a/arch/arm/mach-bcm/board_bcm2835.c b/arch/arm/mach-bcm/board_bcm2835.c
index bfc556f76720..869f22ef562a 100644
--- a/arch/arm/mach-bcm/board_bcm2835.c
+++ b/arch/arm/mach-bcm/board_bcm2835.c
@@ -5,13 +5,103 @@
 
 #include <linux/init.h>
 #include <linux/irqchip.h>
+#include <linux/mm.h>
 #include <linux/of_address.h>
+#include <linux/of_fdt.h>
+#include <asm/system_info.h>
 
 #include <asm/mach/arch.h>
 #include <asm/mach/map.h>
+#include <asm/memory.h>
+#include <asm/pgtable.h>
 
 #include "platsmp.h"
 
+#define BCM2835_USB_VIRT_BASE   (VMALLOC_START)
+#define BCM2835_USB_VIRT_MPHI   (VMALLOC_START + 0x10000)
+
+static void __init bcm2835_init(void)
+{
+	struct device_node *np = of_find_node_by_path("/system");
+	u32 val;
+	u64 val64;
+
+	if (!of_property_read_u32(np, "linux,revision", &val))
+		system_rev = val;
+	if (!of_property_read_u64(np, "linux,serial", &val64))
+		system_serial_low = val64;
+}
+
+/*
+ * We need to map registers that are going to be accessed by the FIQ
+ * very early, before any kernel threads are spawned. Because if done
+ * later, the mapping tables are not updated instantly but lazily upon
+ * first access through a data abort handler. While that is fine
+ * when executing regular kernel code, if the first access in a specific
+ * thread happens while running FIQ code this will result in a panic.
+ *
+ * For more background see the following old mailing list thread:
+ * https://www.spinics.net/lists/arm-kernel/msg325250.html
+ */
+static int __init bcm2835_map_usb(unsigned long node, const char *uname,
+					int depth, void *data)
+{
+	struct map_desc map[2];
+	const __be32 *reg;
+	int len;
+	unsigned long p2b_offset = *((unsigned long *) data);
+
+	if (!of_flat_dt_is_compatible(node, "brcm,bcm2708-usb"))
+		return 0;
+	reg = of_get_flat_dt_prop(node, "reg", &len);
+	if (!reg || len != (sizeof(unsigned long) * 4))
+		return 0;
+
+	/* Use information about the physical addresses of the
+	 * registers from the device tree, but use legacy
+	 * iotable_init() static mapping function to map them,
+	 * as ioremap() is not functional at this stage in boot.
+	 */
+	map[0].virtual = (unsigned long) BCM2835_USB_VIRT_BASE;
+	map[0].pfn = __phys_to_pfn(be32_to_cpu(reg[0]) - p2b_offset);
+	map[0].length = be32_to_cpu(reg[1]);
+	map[0].type = MT_DEVICE;
+	map[1].virtual = (unsigned long) BCM2835_USB_VIRT_MPHI;
+	map[1].pfn = __phys_to_pfn(be32_to_cpu(reg[2]) - p2b_offset);
+	map[1].length = be32_to_cpu(reg[3]);
+	map[1].type = MT_DEVICE;
+		iotable_init(map, 2);
+
+	return 1;
+}
+
+static void __init bcm2835_map_io(void)
+{
+	const __be32 *ranges, *address_cells;
+	unsigned long root, addr_cells;
+	int soc, len;
+	unsigned long p2b_offset;
+
+	debug_ll_io_init();
+
+	root = of_get_flat_dt_root();
+	/* Find out how to map bus to physical address first from soc/ranges */
+	soc = of_get_flat_dt_subnode_by_name(root, "soc");
+	if (soc < 0)
+		return;
+	address_cells = of_get_flat_dt_prop(root, "#address-cells", &len);
+	if (!address_cells || len < (sizeof(unsigned long)))
+		return;
+	addr_cells = be32_to_cpu(address_cells[0]);
+	ranges = of_get_flat_dt_prop(soc, "ranges", &len);
+	if (!ranges || len < (sizeof(unsigned long) * (2 + addr_cells)))
+		return;
+	p2b_offset = be32_to_cpu(ranges[0]) - be32_to_cpu(ranges[addr_cells]);
+
+	/* Now search for bcm2708-usb node in device tree */
+	of_scan_flat_dt(bcm2835_map_usb, &p2b_offset);
+}
+
 static const char * const bcm2835_compat[] = {
 #ifdef CONFIG_ARCH_MULTI_V6
 	"brcm,bcm2835",
@@ -19,11 +109,31 @@ static const char * const bcm2835_compat[] = {
 #ifdef CONFIG_ARCH_MULTI_V7
 	"brcm,bcm2836",
 	"brcm,bcm2837",
+	"brcm,bcm2711",
 #endif
 	NULL
 };
 
 DT_MACHINE_START(BCM2835, "BCM2835")
+	.map_io = bcm2835_map_io,
+	.init_machine = bcm2835_init,
 	.dt_compat = bcm2835_compat,
 	.smp = smp_ops(bcm2836_smp_ops),
 MACHINE_END
+
+static const char * const bcm2711_compat[] = {
+#ifdef CONFIG_ARCH_MULTI_V7
+	"brcm,bcm2711",
+#endif
+	NULL
+};
+
+DT_MACHINE_START(BCM2711, "BCM2711")
+#if defined(CONFIG_ZONE_DMA) && defined(CONFIG_ARM_LPAE)
+	.dma_zone_size	= SZ_1G,
+#endif
+	.map_io = bcm2835_map_io,
+	.init_machine = bcm2835_init,
+	.dt_compat = bcm2711_compat,
+	.smp = smp_ops(bcm2836_smp_ops),
+MACHINE_END
diff --git a/arch/arm/mm/cache-v6.S b/arch/arm/mm/cache-v6.S
index 9f415476e218..68f211eef60f 100644
--- a/arch/arm/mm/cache-v6.S
+++ b/arch/arm/mm/cache-v6.S
@@ -206,7 +206,7 @@ SYM_FUNC_END(v6_flush_kern_dcache_area)
  *	- start   - virtual start address of region
  *	- end     - virtual end address of region
  */
-v6_dma_inv_range:
+ENTRY(v6_dma_inv_range)
 	tst	r0, #D_CACHE_LINE_SIZE - 1
 	bic	r0, r0, #D_CACHE_LINE_SIZE - 1
 #ifdef HARVARD_CACHE
@@ -239,7 +239,7 @@ v6_dma_inv_range:
  *	- start   - virtual start address of region
  *	- end     - virtual end address of region
  */
-v6_dma_clean_range:
+ENTRY(v6_dma_clean_range)
 	bic	r0, r0, #D_CACHE_LINE_SIZE - 1
 1:
 #ifdef HARVARD_CACHE
diff --git a/arch/arm/mm/cache-v7.S b/arch/arm/mm/cache-v7.S
index 201ca05436fa..3c52c327d293 100644
--- a/arch/arm/mm/cache-v7.S
+++ b/arch/arm/mm/cache-v7.S
@@ -364,7 +364,8 @@ SYM_FUNC_END(v7_flush_kern_dcache_area)
  *	- start   - virtual start address of region
  *	- end     - virtual end address of region
  */
-v7_dma_inv_range:
+ENTRY(b15_dma_inv_range)
+ENTRY(v7_dma_inv_range)
 	dcache_line_size r2, r3
 	sub	r3, r2, #1
 	tst	r0, r3
@@ -394,7 +395,8 @@ ENDPROC(v7_dma_inv_range)
  *	- start   - virtual start address of region
  *	- end     - virtual end address of region
  */
-v7_dma_clean_range:
+ENTRY(b15_dma_clean_range)
+ENTRY(v7_dma_clean_range)
 	dcache_line_size r2, r3
 	sub	r3, r2, #1
 	bic	r0, r0, r3
diff --git a/arch/arm/mm/proc-v6.S b/arch/arm/mm/proc-v6.S
index 90a01f5950b9..3ff019fb1f43 100644
--- a/arch/arm/mm/proc-v6.S
+++ b/arch/arm/mm/proc-v6.S
@@ -75,10 +75,19 @@ SYM_FUNC_END(cpu_v6_reset)
  *
  *	IRQs are already disabled.
  */
+
+/* See jira SW-5991 for details of this workaround */
 SYM_TYPED_FUNC_START(cpu_v6_do_idle)
-	mov	r1, #0
-	mcr	p15, 0, r1, c7, c10, 4		@ DWB - WFI may enter a low-power mode
-	mcr	p15, 0, r1, c7, c0, 4		@ wait for interrupt
+	.align 5
+	mov     r1, #2
+1:	subs	r1, #1
+	nop
+	mcreq	p15, 0, r1, c7, c10, 4		@ DWB - WFI may enter a low-power mode
+	mcreq	p15, 0, r1, c7, c0, 4		@ wait for interrupt
+	nop
+	nop
+	nop
+	bne 1b
 	ret	lr
 SYM_FUNC_END(cpu_v6_do_idle)
 
diff --git a/arch/arm/vfp/vfpmodule.c b/arch/arm/vfp/vfpmodule.c
index b68efe643a12..030359368455 100644
--- a/arch/arm/vfp/vfpmodule.c
+++ b/arch/arm/vfp/vfpmodule.c
@@ -176,8 +176,11 @@ static int vfp_notifier(struct notifier_block *self, unsigned long cmd, void *v)
 		 * case the thread migrates to a different CPU. The
 		 * restoring is done lazily.
 		 */
-		if ((fpexc & FPEXC_EN) && vfp_current_hw_state[cpu])
+		if ((fpexc & FPEXC_EN) && vfp_current_hw_state[cpu]) {
+			/* vfp_save_state oopses on VFP11 if EX bit set */
+			fmxr(FPEXC, fpexc & ~FPEXC_EX);
 			vfp_save_state(vfp_current_hw_state[cpu], fpexc);
+		}
 #endif
 
 		/*
@@ -451,13 +454,16 @@ static int vfp_pm_suspend(void)
 	/* if vfp is on, then save state for resumption */
 	if (fpexc & FPEXC_EN) {
 		pr_debug("%s: saving vfp state\n", __func__);
+		/* vfp_save_state oopses on VFP11 if EX bit set */
+		fmxr(FPEXC, fpexc & ~FPEXC_EX);
 		vfp_save_state(&ti->vfpstate, fpexc);
 
 		/* disable, just in case */
 		fmxr(FPEXC, fmrx(FPEXC) & ~FPEXC_EN);
 	} else if (vfp_current_hw_state[ti->cpu]) {
 #ifndef CONFIG_SMP
-		fmxr(FPEXC, fpexc | FPEXC_EN);
+		/* vfp_save_state oopses on VFP11 if EX bit set */
+		fmxr(FPEXC, (fpexc & ~FPEXC_EX) | FPEXC_EN);
 		vfp_save_state(vfp_current_hw_state[ti->cpu], fpexc);
 		fmxr(FPEXC, fpexc);
 #endif
@@ -522,7 +528,8 @@ void vfp_sync_hwstate(struct thread_info *thread)
 		/*
 		 * Save the last VFP state on this CPU.
 		 */
-		fmxr(FPEXC, fpexc | FPEXC_EN);
+		/* vfp_save_state oopses on VFP11 if EX bit set */
+		fmxr(FPEXC, (fpexc & ~FPEXC_EX) | FPEXC_EN);
 		vfp_save_state(&thread->vfpstate, fpexc | FPEXC_EN);
 		fmxr(FPEXC, fpexc);
 	}
@@ -589,6 +596,7 @@ int vfp_restore_user_hwstate(struct user_vfp *ufp, struct user_vfp_exc *ufp_exc)
 	struct thread_info *thread = current_thread_info();
 	struct vfp_hard_struct *hwstate = &thread->vfpstate.hard;
 	unsigned long fpexc;
+	u32 fpsid = fmrx(FPSID);
 
 	/* Disable VFP to avoid corrupting the new thread state. */
 	vfp_flush_hwstate(thread);
@@ -611,8 +619,12 @@ int vfp_restore_user_hwstate(struct user_vfp *ufp, struct user_vfp_exc *ufp_exc)
 	/* Ensure the VFP is enabled. */
 	fpexc |= FPEXC_EN;
 
-	/* Ensure FPINST2 is invalid and the exception flag is cleared. */
-	fpexc &= ~(FPEXC_EX | FPEXC_FP2V);
+	/* Mask FPXEC_EX and FPEXC_FP2V if not required by VFP arch */
+	if ((fpsid & FPSID_ARCH_MASK) != (1 << FPSID_ARCH_BIT)) {
+		/* Ensure FPINST2 is invalid and the exception flag is cleared. */
+		fpexc &= ~(FPEXC_EX | FPEXC_FP2V);
+	}
+
 	hwstate->fpexc = fpexc;
 
 	hwstate->fpinst = ufp_exc->fpinst;
@@ -848,7 +860,8 @@ void kernel_neon_begin(void)
 	cpu = __smp_processor_id();
 
 	fpexc = fmrx(FPEXC) | FPEXC_EN;
-	fmxr(FPEXC, fpexc);
+	/* vfp_save_state oopses on VFP11 if EX bit set */
+	fmxr(FPEXC, fpexc & ~FPEXC_EX);
 
 	/*
 	 * Save the userland NEON/VFP state. Under UP,
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index f487c5e21e2f..804bcdeed463 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -130,7 +130,8 @@ config ARM64
 	select CRC32
 	select DCACHE_WORD_ACCESS
 	select DYNAMIC_FTRACE if FUNCTION_TRACER
-	select DMA_BOUNCE_UNALIGNED_KMALLOC
+	# Disable this to save 64MB when DMA controllers can reach all of RAM
+	# select DMA_BOUNCE_UNALIGNED_KMALLOC
 	select DMA_DIRECT_REMAP
 	select EDAC_SUPPORT
 	select FRAME_POINTER
diff --git a/arch/arm64/boot/dts/Makefile b/arch/arm64/boot/dts/Makefile
index 21cd3a87f385..4b79ed838b12 100644
--- a/arch/arm64/boot/dts/Makefile
+++ b/arch/arm64/boot/dts/Makefile
@@ -34,3 +34,5 @@ subdir-y += tesla
 subdir-y += ti
 subdir-y += toshiba
 subdir-y += xilinx
+
+subdir-y += overlays
diff --git a/arch/arm64/boot/dts/broadcom/Makefile b/arch/arm64/boot/dts/broadcom/Makefile
index 92565e9781ad..25e417844736 100644
--- a/arch/arm64/boot/dts/broadcom/Makefile
+++ b/arch/arm64/boot/dts/broadcom/Makefile
@@ -7,12 +7,35 @@ dtb-$(CONFIG_ARCH_BCM2835) += bcm2711-rpi-400.dtb \
 			      bcm2711-rpi-4-b.dtb \
 			      bcm2711-rpi-cm4-io.dtb \
 			      bcm2712-rpi-5-b.dtb \
+			      bcm2712-d-rpi-5-b.dtb \
 			      bcm2837-rpi-3-a-plus.dtb \
 			      bcm2837-rpi-3-b.dtb \
 			      bcm2837-rpi-3-b-plus.dtb \
 			      bcm2837-rpi-cm3-io3.dtb \
 			      bcm2837-rpi-zero-2-w.dtb
 
+dtb-$(CONFIG_ARCH_BCM2835) += bcm2710-rpi-zero-2.dtb
+dtb-$(CONFIG_ARCH_BCM2835) += bcm2710-rpi-zero-2-w.dtb
+dtb-$(CONFIG_ARCH_BCM2835) += bcm2710-rpi-2-b.dtb
+dtb-$(CONFIG_ARCH_BCM2835) += bcm2710-rpi-3-b.dtb
+dtb-$(CONFIG_ARCH_BCM2835) += bcm2710-rpi-3-b-plus.dtb
+dtb-$(CONFIG_ARCH_BCM2835) += bcm2710-rpi-cm0.dtb
+dtb-$(CONFIG_ARCH_BCM2835) += bcm2710-rpi-cm3.dtb
+dtb-$(CONFIG_ARCH_BCM2835) += bcm2711-rpi-cm4.dtb
+dtb-$(CONFIG_ARCH_BCM2835) += bcm2711-rpi-cm4s.dtb
+dtb-$(CONFIG_ARCH_BCM2835) += bcm2712-rpi-5-b.dtb
+dtb-$(CONFIG_ARCH_BCM2835) += bcm2712d0-rpi-5-b.dtb
+dtb-$(CONFIG_ARCH_BCM2835) += bcm2712-rpi-500.dtb
+dtb-$(CONFIG_ARCH_BCM2835) += bcm2712-rpi-cm5-cm5io.dtb
+dtb-$(CONFIG_ARCH_BCM2835) += bcm2712-rpi-cm5-cm4io.dtb
+dtb-$(CONFIG_ARCH_BCM2835) += bcm2712-rpi-cm5l-cm5io.dtb
+dtb-$(CONFIG_ARCH_BCM2835) += bcm2712-rpi-cm5l-cm4io.dtb
+
 subdir-y	+= bcmbca
 subdir-y	+= northstar2
 subdir-y	+= stingray
+
+# Enable fixups to support overlays on BCM2835 platforms
+ifeq ($(CONFIG_ARCH_BCM2835),y)
+	DTC_FLAGS += -@
+endif
diff --git a/arch/arm64/boot/dts/broadcom/bcm2710-rpi-2-b.dts b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-2-b.dts
new file mode 100644
index 000000000000..9b2c0120842a
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-2-b.dts
@@ -0,0 +1 @@
+#include "arm/broadcom/bcm2710-rpi-2-b.dts"
diff --git a/arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b-plus.dts b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b-plus.dts
new file mode 100644
index 000000000000..bc869aeaee9b
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b-plus.dts
@@ -0,0 +1 @@
+#include "arm/broadcom/bcm2710-rpi-3-b-plus.dts"
diff --git a/arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b.dts b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b.dts
new file mode 100644
index 000000000000..263fc8db863a
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b.dts
@@ -0,0 +1 @@
+#include "arm/broadcom/bcm2710-rpi-3-b.dts"
diff --git a/arch/arm64/boot/dts/broadcom/bcm2710-rpi-cm0.dts b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-cm0.dts
new file mode 100644
index 000000000000..1cbe2880d792
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-cm0.dts
@@ -0,0 +1 @@
+#include "arm/broadcom/bcm2710-rpi-cm0.dts"
diff --git a/arch/arm64/boot/dts/broadcom/bcm2710-rpi-cm3.dts b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-cm3.dts
new file mode 100644
index 000000000000..6beee41b0077
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-cm3.dts
@@ -0,0 +1 @@
+#include "arm/broadcom/bcm2710-rpi-cm3.dts"
diff --git a/arch/arm64/boot/dts/broadcom/bcm2710-rpi-zero-2-w.dts b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-zero-2-w.dts
new file mode 100644
index 000000000000..65fa59a939b7
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-zero-2-w.dts
@@ -0,0 +1 @@
+#include "arm/broadcom/bcm2710-rpi-zero-2-w.dts"
diff --git a/arch/arm64/boot/dts/broadcom/bcm2710-rpi-zero-2.dts b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-zero-2.dts
new file mode 100644
index 000000000000..65fa59a939b7
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2710-rpi-zero-2.dts
@@ -0,0 +1 @@
+#include "arm/broadcom/bcm2710-rpi-zero-2-w.dts"
diff --git a/arch/arm64/boot/dts/broadcom/bcm2711-rpi-cm4.dts b/arch/arm64/boot/dts/broadcom/bcm2711-rpi-cm4.dts
new file mode 100644
index 000000000000..3e25a0e1797f
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2711-rpi-cm4.dts
@@ -0,0 +1 @@
+#include "arm/broadcom/bcm2711-rpi-cm4.dts"
diff --git a/arch/arm64/boot/dts/broadcom/bcm2711-rpi-cm4s.dts b/arch/arm64/boot/dts/broadcom/bcm2711-rpi-cm4s.dts
new file mode 100644
index 000000000000..c72d752e7400
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2711-rpi-cm4s.dts
@@ -0,0 +1 @@
+#include "arm/broadcom/bcm2711-rpi-cm4s.dts"
diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-d-rpi-5-b.dts b/arch/arm64/boot/dts/broadcom/bcm2712-d-rpi-5-b.dts
new file mode 100644
index 000000000000..7de24d60bcd1
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2712-d-rpi-5-b.dts
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/dts-v1/;
+
+#include "bcm2712-rpi-5-b.dts"
+
+&gio_aon {
+	brcm,gpio-bank-widths = <15 6>;
+
+	gpio-line-names =
+		"RP1_SDA", // AON_GPIO_00
+		"RP1_SCL", // AON_GPIO_01
+		"RP1_RUN", // AON_GPIO_02
+		"SD_IOVDD_SEL", // AON_GPIO_03
+		"SD_PWR_ON", // AON_GPIO_04
+		"SD_CDET_N", // AON_GPIO_05
+		"SD_FLG_N", // AON_GPIO_06
+		"", // AON_GPIO_07
+		"2712_WAKE", // AON_GPIO_08
+		"2712_STAT_LED", // AON_GPIO_09
+		"", // AON_GPIO_10
+		"", // AON_GPIO_11
+		"PMIC_INT", // AON_GPIO_12
+		"UART_TX_FS", // AON_GPIO_13
+		"UART_RX_FS", // AON_GPIO_14
+		"", // AON_GPIO_15
+		"", // AON_GPIO_16
+
+		// Pad bank0 out to 32 entries
+		"", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
+
+		"HDMI0_SCL", // AON_SGPIO_00
+		"HDMI0_SDA", // AON_SGPIO_01
+		"HDMI1_SCL", // AON_SGPIO_02
+		"HDMI1_SDA", // AON_SGPIO_03
+		"PMIC_SCL", // AON_SGPIO_04
+		"PMIC_SDA"; // AON_SGPIO_05
+};
diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-ds.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712-ds.dtsi
new file mode 100644
index 000000000000..53d1bf3ace69
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2712-ds.dtsi
@@ -0,0 +1,501 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <dt-bindings/soc/bcm2835-pm.h>
+
+#include "bcm2712.dtsi"
+
+/ {
+#ifndef FIRMWARE_UPDATED
+	#size-cells = <1>;
+
+	reserved-memory {
+		ranges;
+		#address-cells = <2>;
+		#size-cells = <1>;
+
+		atf@0 {
+			reg = <0x0 0x0 0x80000>;
+		};
+
+		linux,cma {
+			size = <0x4000000>; /* 64MB */
+			alloc-ranges = <0x0 0x00000000 0x40000000>;
+		};
+	};
+#endif
+
+	arm-pmu {
+		compatible = "arm,cortex-a76-pmu";
+		interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH>,
+			<GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>,
+			<GIC_SPI 18 IRQ_TYPE_LEVEL_HIGH>,
+			<GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>;
+		interrupt-affinity = <&cpu0>, <&cpu1>, <&cpu2>, <&cpu3>;
+	};
+
+	clocks: clocks {
+		compatible = "simple-bus";
+		#address-cells = <1>;
+		#size-cells = <0>;
+
+		clk_usb: clk-usb {
+			compatible = "fixed-clock";
+			#clock-cells = <0>;
+			clock-output-names = "otg";
+			clock-frequency = <480000000>;
+		};
+	};
+
+	thermal-zones {
+		cpu_thermal: cpu-thermal {
+			polling-delay-passive = <1000>;
+			polling-delay = <1000>;
+			coefficients = <(-550) 450000>;
+			thermal-sensors = <&thermal>;
+
+			thermal_trips: trips {
+				cpu_crit: cpu-crit {
+					temperature	= <110000>;
+					hysteresis	= <0>;
+					type		= "critical";
+				};
+			};
+
+			cooling_maps: cooling-maps {
+			};
+		};
+	};
+
+	firmwarekms: firmwarekms {
+		compatible = "raspberrypi,rpi-firmware-kms-2712";
+		interrupt-parent = <&cpu_l2_irq>;
+		interrupts = <19>;
+		brcm,firmware = <&firmware>;
+		status = "disabled";
+	};
+
+	usbphy: phy {
+		compatible = "usb-nop-xceiv";
+		#phy-cells = <0>;
+	};
+};
+
+&soc {
+	system_timer: timer@7c003000 {
+		compatible = "brcm,bcm2835-system-timer";
+		reg = <0x7c003000 0x1000>;
+		interrupts = <GIC_SPI 64 IRQ_TYPE_LEVEL_HIGH>,
+					<GIC_SPI 65 IRQ_TYPE_LEVEL_HIGH>,
+						<GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
+						<GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
+		clock-frequency = <1000000>;
+	};
+
+	axiperf: axiperf@7c012800 {
+		compatible = "brcm,bcm2712-axiperf";
+		reg = <0x7c012800 0x100>,
+			<0x7e000000 0x100>;
+		firmware = <&firmware>;
+		status = "disabled";
+	};
+
+	spi10: spi@7d004000 {
+		compatible = "brcm,bcm2835-spi";
+		reg = <0x7d004000 0x200>;
+		interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>;
+		clocks = <&clk_vpu>;
+		num-cs = <1>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		status = "disabled";
+	};
+
+	i2c10: i2c@7d005600 {
+		compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c";
+		reg = <0x7d005600 0x20>;
+		interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>;
+		clocks = <&clk_vpu>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		status = "disabled";
+	};
+
+	pm: watchdog@7d200000 {
+		compatible = "brcm,bcm2712-pm";
+		reg = <0x7d200000 0x308>;
+		reg-names = "pm";
+		#power-domain-cells = <1>;
+		#reset-cells = <1>;
+		system-power-controller;
+	};
+
+	random: rng@7d208000 {
+		compatible = "brcm,bcm2711-rng200";
+		reg = <0x7d208000 0x28>;
+		status = "okay";
+	};
+
+	cpu_l2_irq: intc@7d503000 {
+		compatible = "brcm,l2-intc";
+		reg = <0x7d503000 0x18>;
+		interrupts = <GIC_SPI 238 IRQ_TYPE_LEVEL_HIGH>;
+		interrupt-controller;
+		#interrupt-cells = <1>;
+	};
+
+	pinctrl: pinctrl@7d504100 {
+		compatible = "brcm,bcm2712-pinctrl";
+		reg = <0x7d504100 0x30>;
+
+		uarta_24_pins: uarta_24_pins {
+			pin_rts {
+				function = "uart0";
+				pins = "gpio24";
+				bias-disable;
+			};
+			pin_cts {
+				function = "uart0";
+				pins = "gpio25";
+				bias-pull-up;
+			};
+			pin_txd {
+				function = "uart0";
+				pins = "gpio26";
+				bias-disable;
+			};
+			pin_rxd {
+				function = "uart0";
+				pins = "gpio27";
+				bias-pull-up;
+			};
+		};
+
+		sdio2_30_pins: sdio2_30_pins {
+			pin_clk {
+				function = "sd2";
+				pins = "gpio30";
+				bias-disable;
+			};
+			pin_cmd {
+				function = "sd2";
+				pins = "gpio31";
+				bias-pull-up;
+			};
+			pins_dat {
+				function = "sd2";
+				pins = "gpio32", "gpio33", "gpio34", "gpio35";
+				bias-pull-up;
+			};
+		};
+	};
+
+	gio: gpio@7d508500 {
+		compatible = "brcm,brcmstb-gpio";
+		reg = <0x7d508500 0x40>;
+		interrupt-parent = <&main_irq>;
+		interrupts = <0>;
+		gpio-controller;
+		#gpio-cells = <2>;
+		interrupt-controller;
+		#interrupt-cells = <2>;
+		brcm,gpio-bank-widths = <32 22>;
+		brcm,gpio-direct;
+	};
+
+	uarta: serial@7d50c000 {
+		compatible = "brcm,bcm7271-uart";
+		reg = <0x7d50c000 0x20>;
+		reg-names = "uart";
+		reg-shift = <2>;
+		reg-io-width = <4>;
+		interrupts = <GIC_SPI 276 IRQ_TYPE_LEVEL_HIGH>;
+		skip-init;
+		status = "disabled";
+	};
+
+	pinctrl_aon: pinctrl@7d510700 {
+		compatible = "brcm,bcm2712-aon-pinctrl";
+		reg = <0x7d510700 0x20>;
+
+		i2c3_m4_agpio0_pins: i2c3_m4_agpio0_pins {
+			function = "vc_i2c3";
+			pins = "aon_gpio0", "aon_gpio1";
+			bias-pull-up;
+		};
+
+		bsc_m1_agpio13_pins: bsc_m1_agpio13_pins {
+			function = "bsc_m1";
+			pins = "aon_gpio13", "aon_gpio14";
+			bias-pull-up;
+		};
+
+		bsc_pmu_sgpio4_pins: bsc_pmu_sgpio4_pins {
+			function = "avs_pmu_bsc";
+			pins = "aon_sgpio4", "aon_sgpio5";
+		};
+
+		bsc_m2_sgpio4_pins: bsc_m2_sgpio4_pins {
+			function = "bsc_m2";
+			pins = "aon_sgpio4", "aon_sgpio5";
+		};
+
+		pwm_aon_agpio1_pins: pwm_aon_agpio1_pins {
+			function = "aon_pwm";
+			pins = "aon_gpio1", "aon_gpio2";
+		};
+
+		pwm_aon_agpio4_pins: pwm_aon_agpio4_pins {
+			function = "vc_pwm0";
+			pins = "aon_gpio4", "aon_gpio5";
+		};
+
+		pwm_aon_agpio7_pins: pwm_aon_agpio7_pins {
+			function = "aon_pwm";
+			pins = "aon_gpio7", "aon_gpio9";
+		};
+	};
+
+	interrupt-controller@7d517000 {
+		status = "disabled";
+	};
+
+	main_aon_irq: intc@7d517ac0 {
+		compatible = "brcm,bcm7271-l2-intc";
+		reg = <0x7d517ac0 0x10>;
+		interrupts = <GIC_SPI 245 IRQ_TYPE_LEVEL_HIGH>;
+		interrupt-controller;
+		#interrupt-cells = <1>;
+	};
+
+	avs_monitor: avs-monitor@7d542000 {
+		compatible = "brcm,bcm2711-avs-monitor",
+					"syscon", "simple-mfd";
+		reg = <0x7d542000 0xf00>;
+		status = "okay";
+
+		thermal: thermal {
+			compatible = "brcm,bcm2711-thermal";
+			#thermal-sensor-cells = <0>;
+		};
+	};
+};
+
+&axi {
+	iommu2: iommu@5100 {
+		/* IOMMU2 for PISP-BE, HEVC; and (unused) H264 accelerators */
+		compatible = "brcm,bcm2712-iommu";
+		reg = <0x10 0x5100  0x0 0x80>;
+		cache = <&iommuc>;
+		aperture-size = <0x1 0x00000000>; // Increase IOVA aperture size to 4GBytes
+		#iommu-cells = <0>;
+	};
+
+	iommu4: iommu@5200 {
+		/* IOMMU4 for HVS, MPL/TXP; and (unused) Unicam, PISP-FE, MiniBVN */
+		compatible = "brcm,bcm2712-iommu";
+		reg = <0x10 0x5200  0x0 0x80>;
+		cache = <&iommuc>;
+		#iommu-cells = <0>;
+		#interconnect-cells = <0>;
+	};
+
+	iommu5: iommu@5280 {
+		/* IOMMU5 for PCIe2 (RP1); and (unused) BSTM */
+		compatible = "brcm,bcm2712-iommu";
+		reg = <0x10 0x5280  0x0 0x80>;
+		cache = <&iommuc>;
+		#iommu-cells = <0>;
+		dma-iova-offset = <0x10 0x00000000>; // HACK for RP1 masters over PCIe
+	};
+
+	iommuc: iommuc@5b00 {
+		compatible = "brcm,bcm2712-iommuc";
+		reg = <0x10 0x5b00  0x0 0x80>;
+	};
+
+	dma32: dma@10000 {
+		compatible = "brcm,bcm2712-dma";
+		reg = <0x10 0x00010000 0 0x600>;
+		interrupts = <GIC_SPI 80 IRQ_TYPE_LEVEL_HIGH>,
+					<GIC_SPI 81 IRQ_TYPE_LEVEL_HIGH>,
+					<GIC_SPI 82 IRQ_TYPE_LEVEL_HIGH>,
+					<GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>,
+					<GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>,
+					<GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>;
+		interrupt-names = "dma0",
+					"dma1",
+					"dma2",
+					"dma3",
+					"dma4",
+					"dma5";
+		#dma-cells = <1>;
+		brcm,dma-channel-mask = <0x0035>;
+	};
+
+	dma40: dma@10600 {
+		compatible = "brcm,bcm2712-dma";
+		reg = <0x10 0x00010600 0 0x600>;
+		interrupts =
+			<GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH>, /* dma4 6 */
+			<GIC_SPI 87 IRQ_TYPE_LEVEL_HIGH>, /* dma4 7 */
+			<GIC_SPI 88 IRQ_TYPE_LEVEL_HIGH>, /* dma4 8 */
+			<GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>, /* dma4 9 */
+			<GIC_SPI 90 IRQ_TYPE_LEVEL_HIGH>, /* dma4 10 */
+			<GIC_SPI 91 IRQ_TYPE_LEVEL_HIGH>; /* dma4 11 */
+		interrupt-names = "dma6",
+					"dma7",
+					"dma8",
+					"dma9",
+					"dma10",
+					"dma11";
+		#dma-cells = <1>;
+		brcm,dma-channel-mask = <0x0fc0>;
+	};
+
+	syscon_piarbctl: syscon@400018 {
+		compatible = "brcm,syscon-piarbctl", "syscon", "simple-mfd";
+		reg = <0x10 0x00400018  0x0 0x18>;
+	};
+
+	usb: usb@480000 {
+		compatible = "brcm,bcm2835-usb";
+		reg = <0x10 0x00480000  0x0 0x10000>;
+		interrupts = <GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		clocks = <&clk_usb>;
+		clock-names = "otg";
+		phys = <&usbphy>;
+		phy-names = "usb2-phy";
+		status = "disabled";
+	};
+
+	hevc_dec: codec@800000 {
+		compatible = "brcm,bcm2712-hevc-dec", "raspberrypi,hevc-dec";
+		reg = <0x10 0x00800000  0x0 0x10000>, /* HEVC */
+				<0x10 0x00840000  0x0 0x1000>;  /* INTC */
+		reg-names = "hevc",
+				"intc";
+
+		interrupts = <GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;
+
+		clocks = <&firmware_clocks 11>;
+		clock-names = "hevc";
+		iommus = <&iommu2>;
+	};
+
+	pisp_be: pisp_be@880000  {
+		compatible = "raspberrypi,pispbe";
+		reg = <0x10 0x00880000  0x0 0x4000>;
+		interrupts = <GIC_SPI 72 IRQ_TYPE_LEVEL_HIGH>;
+		clocks = <&firmware_clocks 7>;
+		clocks-names = "isp_be";
+		status = "okay";
+		iommus = <&iommu2>;
+	};
+
+	sdio2: mmc@1100000 {
+		compatible = "brcm,bcm2712-sdhci";
+		reg = <0x10 0x01100000  0x0 0x260>,
+				<0x10 0x01100400  0x0 0x200>;
+		reg-names = "host", "cfg";
+		interrupts = <GIC_SPI 274 IRQ_TYPE_LEVEL_HIGH>;
+		clocks = <&clk_emmc2>;
+		sdhci-caps-mask = <0x0000C000 0x0>;
+		sdhci-caps = <0x0 0x0>;
+		supports-cqe = <1>;
+		mmc-ddr-3_3v;
+		status = "disabled";
+	};
+
+	v3d: v3d@2000000 {
+		compatible = "brcm,2712-v3d";
+		reg = <0x10 0x02000000  0x0 0x4000>,
+			  <0x10 0x02008000  0x0 0x6000>,
+			  <0x10 0x02030800  0x0 0x0700>;
+		reg-names = "hub", "core0", "sms";
+
+		power-domains = <&pm BCM2835_POWER_DOMAIN_GRAFX_V3D>;
+		resets = <&pm BCM2835_RESET_V3D>;
+		clocks = <&firmware_clocks 5>;
+		clocks-names = "v3d";
+		interrupts = <GIC_SPI 250 IRQ_TYPE_LEVEL_HIGH>,
+					<GIC_SPI 249 IRQ_TYPE_LEVEL_HIGH>;
+		status = "disabled";
+	};
+};
+
+&gicv2 {
+	interrupts = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(4) |
+				 IRQ_TYPE_LEVEL_HIGH)>;
+};
+
+&uart10 {
+	compatible = "arm,pl011-axi", "arm,pl011", "arm,primecell";
+};
+
+&aon_intr {
+	status = "disabled";
+};
+
+&gio_aon {
+	compatible = "brcm,brcmstb-gpio";
+	brcm,gpio-direct;
+};
+
+&hvs {
+	status = "disabled";
+};
+
+&hdmi0 {
+	status = "disabled";
+};
+
+&hdmi1 {
+	status = "disabled";
+};
+
+&pixelvalve0 {
+	status = "disabled";
+};
+
+&pixelvalve1 {
+	status = "disabled";
+};
+
+&mop {
+	status = "disabled";
+};
+
+&moplet {
+	status = "disabled";
+};
+
+&ddc0 {
+	status = "disabled";
+};
+
+&ddc1 {
+	status = "disabled";
+};
+
+&disp_intr {
+	status = "disabled";
+};
+
+&vc4 {
+	status = "disabled";
+};
+
+&pcie1 {
+	brcm,fifo-qos-map = /bits/ 8 <3 3 3 3>;
+	status = "disabled";
+	ranges =
+		/* 2GiB, 32-bit, non-prefetchable at PCIe 00_80000000 */
+		<0x02000000 0x00 0x80000000 0x1b 0x80000000 0x00 0x80000000>,
+		/* 14GiB, 64-bit, prefetchable at PCIe 04_00000000 */
+		<0x43000000 0x04 0x00000000 0x18 0x00000000 0x03 0x80000000>;
+};
+
+&pcie2 {
+	brcm,vdm-qos-map = /bits/ 8 <8 8 8 9 10 10 11 11>;
+};
diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts
index 2bdbb6780242..2d56aa951fd2 100644
--- a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts
+++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts
@@ -2,24 +2,44 @@
 /dts-v1/;
 
 #include <dt-bindings/gpio/gpio.h>
-#include "bcm2712.dtsi"
+#include <dt-bindings/clock/rp1.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/mfd/rp1.h>
+#include <dt-bindings/pwm/pwm.h>
+#include <dt-bindings/reset/raspberrypi,firmware-reset.h>
+
+#include "bcm2712-ds.dtsi"
 
 / {
 	compatible = "raspberrypi,5-model-b", "brcm,bcm2712";
 	model = "Raspberry Pi 5";
 
-	aliases {
-		serial10 = &uart10;
-	};
-
-	chosen: chosen {
-		stdout-path = "serial10:115200n8";
-	};
-
 	/* Will be filled by the bootloader */
 	memory@0 {
 		device_type = "memory";
+#ifndef FIRMWARE_UPDATED
+		reg = <0 0 0x28000000>;
+#else
 		reg = <0 0 0 0x28000000>;
+#endif
+	};
+
+	leds: leds {
+		compatible = "gpio-leds";
+
+		led_pwr: led-pwr {
+			label = "PWR";
+			gpios = <&rp1_gpio 44 GPIO_ACTIVE_LOW>;
+			default-state = "off";
+			linux,default-trigger = "none";
+		};
+
+		led_act: led-act {
+			label = "ACT";
+			gpios = <&gio_aon 9 GPIO_ACTIVE_LOW>;
+			default-state = "off";
+			linux,default-trigger = "mmc0";
+		};
 	};
 
 	sd_io_1v8_reg: sd-io-1v8-reg {
@@ -44,6 +64,120 @@ sd_vcc_reg: sd-vcc-reg {
 		enable-active-high;
 		gpios = <&gio_aon 4 GPIO_ACTIVE_HIGH>;
 	};
+
+	wl_on_reg: wl-on-reg {
+		compatible = "regulator-fixed";
+		regulator-name = "wl-on-regulator";
+		regulator-min-microvolt = <3300000>;
+		regulator-max-microvolt = <3300000>;
+		pinctrl-0 = <&wl_on_pins>;
+		pinctrl-names = "default";
+
+		gpio = <&gio 28 GPIO_ACTIVE_HIGH>;
+
+		startup-delay-us = <150000>;
+		enable-active-high;
+	};
+
+	cam1_clk: cam1_clk {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		status = "disabled";
+	};
+
+	cam0_clk: cam0_clk {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		status = "disabled";
+	};
+
+	cam0_reg: cam0_reg {
+		compatible = "regulator-fixed";
+		regulator-name = "cam0_reg";
+		enable-active-high;
+		gpio = <&rp1_gpio 34 0>;  // CD0_IO0_MICCLK, to MIPI 0 connector
+	};
+
+	cam1_reg: cam1_reg {
+		compatible = "regulator-fixed";
+		regulator-name = "cam1_reg";
+		enable-active-high;
+		gpio = <&rp1_gpio 46 0>;  // CD1_IO0_MICCLK, to MIPI 1 connector
+	};
+
+	cam_dummy_reg: cam_dummy_reg {
+		compatible = "regulator-fixed";
+		regulator-name = "cam-dummy-reg";
+	};
+
+	dummy: dummy {
+		// A target for unwanted overlay fragments
+	};
+
+
+	// A few extra labels to keep overlays happy
+
+	i2c0if: i2c0if {};
+	i2c0mux: i2c0mux {};
+};
+
+rp1_target: &pcie2 {
+	aspm-no-l0s;
+	status = "okay";
+};
+
+// The system SPI for the bootloader EEPROM
+&spi10 { status = "okay"; };
+
+#include "rp1.dtsi"
+
+&rp1 {
+	// PCIe address space layout:
+	// 00_00000000-00_00xxxxxx = RP1 peripherals
+	// 10_00000000-1x_xxxxxxxx = up to 64GB system RAM
+
+	// outbound access aimed at PCIe 0_00xxxxxx -> RP1 c0_40xxxxxx
+	// This is the RP1 peripheral space
+	ranges = <0xc0 0x40000000
+		  0x02000000 0x00 0x00000000
+		  0x00 0x00410000>;
+
+	dma-ranges =
+	// inbound RP1 1x_xxxxxxxx -> PCIe 1x_xxxxxxxx
+		     <0x10 0x00000000
+		      0x43000000 0x10 0x00000000
+		      0x10 0x00000000>,
+
+	// inbound RP1 c0_40xxxxxx -> PCIe 00_00xxxxxx
+	// This allows the RP1 DMA controller to address RP1 hardware
+		     <0xc0 0x40000000
+		      0x02000000 0x0 0x00000000
+		      0x0 0x00410000>,
+
+	// inbound RP1 0x_xxxxxxxx -> PCIe 1x_xxxxxxxx
+		     <0x00 0x00000000
+		      0x02000000 0x10 0x00000000
+		      0x10 0x00000000>;
+};
+
+// Expose RP1 nodes as system nodes with labels
+
+&rp1_dma  {
+	status = "okay";
+};
+
+&rp1_eth {
+	status = "okay";
+	phy-handle = <&phy1>;
+	phy-reset-gpios = <&rp1_gpio 32 GPIO_ACTIVE_LOW>;
+	phy-reset-duration = <5>;
+
+	phy1: ethernet-phy@1 {
+		reg = <0x1>;
+		brcm,powerdown-enable;
+		eee-broken-1000t;
+		eee-broken-100tx;
+	};
 };
 
 /* The Debug UART, on Rpi5 it's on JST-SH 1.0mm 3-pin connector
@@ -53,12 +187,538 @@ &uart10 {
 	status = "okay";
 };
 
+gpio: &rp1_gpio {
+	status = "okay";
+};
+
+aux: &dummy {};
+
+&rp1_usb0 {
+	pinctrl-0 = <&usb_vbus_pins>;
+	pinctrl-names = "default";
+	status = "okay";
+};
+
+&rp1_usb1 {
+	status = "okay";
+};
+
+#include "bcm2712-rpi.dtsi"
+
+i2c_csi_dsi0: &i2c6 { // Note: This is for MIPI0 connector only
+	pinctrl-0 = <&rp1_i2c6_38_39>;
+	pinctrl-names = "default";
+	clock-frequency = <100000>;
+	symlink = "i2c-6";
+};
+
+i2c_csi_dsi1: &i2c4 { // Note: This is for MIPI1 connector only
+	pinctrl-0 = <&rp1_i2c4_40_41>;
+	pinctrl-names = "default";
+	clock-frequency = <100000>;
+	symlink = "i2c-4";
+};
+
+i2c_csi_dsi: &i2c_csi_dsi1 { }; // An alias for compatibility
+
+csi0: &rp1_csi0 { };
+csi1: &rp1_csi1 { };
+dsi0: &rp1_dsi0 { };
+dsi1: &rp1_dsi1 { };
+dpi: &rp1_dpi { };
+vec: &rp1_vec { };
+dpi_gpio0:              &rp1_dpi_24bit_gpio0        { };
+dpi_gpio1:              &rp1_dpi_24bit_gpio2        { };
+dpi_18bit_cpadhi_gpio0: &rp1_dpi_18bit_cpadhi_gpio0 { };
+dpi_18bit_cpadhi_gpio2: &rp1_dpi_18bit_cpadhi_gpio2 { };
+dpi_18bit_gpio0:        &rp1_dpi_18bit_gpio0        { };
+dpi_18bit_gpio2:        &rp1_dpi_18bit_gpio2        { };
+dpi_16bit_cpadhi_gpio0: &rp1_dpi_16bit_cpadhi_gpio0 { };
+dpi_16bit_cpadhi_gpio2: &rp1_dpi_16bit_cpadhi_gpio2 { };
+dpi_16bit_gpio0:        &rp1_dpi_16bit_gpio0        { };
+dpi_16bit_gpio2:        &rp1_dpi_16bit_gpio2        { };
+
+/* Add the IOMMUs for some RP1 bus masters */
+
+&csi0 {
+	iommus = <&iommu5>;
+};
+
+&csi1 {
+	iommus = <&iommu5>;
+};
+
+&dsi0 {
+	iommus = <&iommu5>;
+};
+
+&dsi1 {
+	iommus = <&iommu5>;
+};
+
+&dpi {
+	iommus = <&iommu5>;
+};
+
+&vec {
+	iommus = <&iommu5>;
+};
+
+&ddc0 {
+	status = "disabled";
+};
+
+&ddc1 {
+	status = "disabled";
+};
+
+&hdmi0 {
+	clocks = <&firmware_clocks 13>, <&firmware_clocks 14>, <&dvp 0>, <&clk_27MHz>;
+	clock-names = "hdmi", "bvb", "audio", "cec";
+	status = "disabled";
+};
+
+&hdmi1 {
+	clocks = <&firmware_clocks 13>, <&firmware_clocks 14>, <&dvp 1>, <&clk_27MHz>;
+	clock-names = "hdmi", "bvb", "audio", "cec";
+	status = "disabled";
+};
+
+&hvs {
+	clocks = <&firmware_clocks 4>, <&firmware_clocks 16>;
+	clock-names = "core", "disp";
+};
+
+&mop {
+	status = "disabled";
+};
+
+&moplet {
+	status = "disabled";
+};
+
+&pixelvalve0 {
+	status = "disabled";
+};
+
+&pixelvalve1 {
+	status = "disabled";
+};
+
+&disp_intr {
+	status = "disabled";
+};
+
 /* SDIO1 is used to drive the SD card */
 &sdio1 {
+	pinctrl-0 = <&emmc_sd_pulls>, <&emmc_aon_cd_pins>;
+	pinctrl-names = "default";
 	vqmmc-supply = <&sd_io_1v8_reg>;
 	vmmc-supply = <&sd_vcc_reg>;
 	bus-width = <4>;
 	sd-uhs-sdr50;
 	sd-uhs-ddr50;
 	sd-uhs-sdr104;
+	supports-cqe = <1>;
+	cd-gpios = <&gio_aon 5 GPIO_ACTIVE_LOW>;
+	//no-1-8-v;
+	status = "okay";
+};
+
+&pinctrl_aon {
+	emmc_aon_cd_pins: emmc_aon_cd_pins {
+		function = "sd_card_g";
+		pins = "aon_gpio5";
+		bias-pull-up;
+	};
+
+	/* Slight hack - only one PWM pin (status LED) is usable */
+	aon_pwm_1pin: aon_pwm_1pin {
+		function = "aon_pwm";
+		pins = "aon_gpio9";
+	};
+};
+
+&pinctrl {
+	pwr_button_pins: pwr_button_pins {
+		function = "gpio";
+		pins = "gpio20";
+		bias-pull-up;
+	};
+
+	wl_on_pins: wl_on_pins {
+		function = "gpio";
+		pins = "gpio28";
+	};
+
+	bt_shutdown_pins: bt_shutdown_pins {
+		function = "gpio";
+		pins = "gpio29";
+	};
+
+	emmc_sd_pulls: emmc_sd_pulls {
+		pins = "emmc_cmd", "emmc_dat0", "emmc_dat1", "emmc_dat2", "emmc_dat3";
+		bias-pull-up;
+	};
+};
+
+/* uarta communicates with the BT module */
+&uarta {
+	uart-has-rtscts;
+	auto-flow-control;
+	status = "okay";
+	clock-frequency = <96000000>;
+	pinctrl-0 = <&uarta_24_pins &bt_shutdown_pins>;
+	pinctrl-names = "default";
+
+	bluetooth: bluetooth {
+		compatible = "brcm,bcm43438-bt";
+		max-speed = <3000000>;
+		shutdown-gpios = <&gio 29 GPIO_ACTIVE_HIGH>;
+		local-bd-address = [ 00 00 00 00 00 00 ];
+	};
+};
+
+&i2c10 {
+	clock-frequency = <400000>;
+	pinctrl-0 = <&i2c3_m4_agpio0_pins>;
+	pinctrl-names = "default";
+};
+
+/ {
+	fan: cooling_fan {
+		status = "disabled";
+		compatible = "pwm-fan";
+		#cooling-cells = <2>;
+		cooling-min-state = <0>;
+		cooling-max-state = <3>;
+		cooling-levels = <0 75 125 175 250>;
+		pwms = <&rp1_pwm1 3 41566 PWM_POLARITY_INVERTED>;
+		rpm-regmap = <&rp1_pwm1>;
+		rpm-offset = <0x3c>;
+	};
+
+	pwr_button {
+		compatible = "gpio-keys";
+
+		pinctrl-names = "default";
+		pinctrl-0 = <&pwr_button_pins>;
+		status = "okay";
+
+		pwr_key: pwr {
+			label = "pwr_button";
+			// linux,code = <205>; // KEY_SUSPEND
+			linux,code = <116>; // KEY_POWER
+			gpios = <&gio 20 GPIO_ACTIVE_LOW>;
+			debounce-interval = <50>; // ms
+		};
+	};
+};
+
+&usb {
+	power-domains = <&power RPI_POWER_DOMAIN_USB>;
+};
+
+/* SDIO2 drives the WLAN interface */
+&sdio2 {
+	pinctrl-0 = <&sdio2_30_pins>;
+	pinctrl-names = "default";
+	bus-width = <4>;
+	vmmc-supply = <&wl_on_reg>;
+	sd-uhs-ddr50;
+	non-removable;
+	status = "okay";
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	wifi: wifi@1 {
+		reg = <1>;
+		compatible = "brcm,bcm4329-fmac";
+		local-mac-address = [00 00 00 00 00 00];
+	};
+};
+
+&pinctrl {
+	spi10_gpio2: spi10_gpio2 {
+		function = "vc_spi0";
+		pins = "gpio2", "gpio3", "gpio4";
+		bias-disable;
+	};
+
+	spi10_cs_gpio1: spi10_cs_gpio1 {
+		function = "gpio";
+		pins = "gpio1";
+		bias-pull-up;
+	};
+};
+
+spi10_pins: &spi10_gpio2 {};
+spi10_cs_pins: &spi10_cs_gpio1 {};
+
+&spi10 {
+	pinctrl-names = "default";
+	cs-gpios = <&gio 1 1>;
+	pinctrl-0 = <&spi10_pins &spi10_cs_pins>;
+
+	spidev10: spidev@0 {
+		compatible = "spidev";
+		reg = <0>;	/* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <20000000>;
+		status = "okay";
+	};
+};
+
+// =============================================
+// Board specific stuff here
+
+&gio_aon {
+	// Don't use GIO_AON as an interrupt controller because it will
+	// clash with the firmware monitoring the PMIC interrupt via the VPU.
+
+	/delete-property/ interrupt-controller;
+	/delete-property/ #interrupt-cells;
+};
+
+&main_aon_irq {
+	// Don't use the MAIN_AON_IRQ interrupt controller because it will
+	// clash with the firmware monitoring the PMIC interrupt via the VPU.
+
+	status = "disabled";
+};
+
+&rp1_pwm1 {
+	status = "disabled";
+	pinctrl-0 = <&rp1_pwm1_gpio45>;
+	pinctrl-names = "default";
+};
+
+&thermal_trips {
+	cpu_tepid: cpu-tepid {
+		temperature = <50000>;
+		hysteresis = <5000>;
+		type = "active";
+	};
+
+	cpu_warm: cpu-warm {
+		temperature = <60000>;
+		hysteresis = <5000>;
+		type = "active";
+	};
+
+	cpu_hot: cpu-hot {
+		temperature = <67500>;
+		hysteresis = <5000>;
+		type = "active";
+	};
+
+	cpu_vhot: cpu-vhot {
+		temperature = <75000>;
+		hysteresis = <5000>;
+		type = "active";
+	};
+};
+
+&cooling_maps {
+	tepid {
+		trip = <&cpu_tepid>;
+		cooling-device = <&fan 1 1>;
+	};
+
+	warm {
+		trip = <&cpu_warm>;
+		cooling-device = <&fan 2 2>;
+	};
+
+	hot {
+		trip = <&cpu_hot>;
+		cooling-device = <&fan 3 3>;
+	};
+
+	vhot {
+		trip = <&cpu_vhot>;
+		cooling-device = <&fan 4 4>;
+	};
+
+	melt {
+		trip = <&cpu_crit>;
+		cooling-device = <&fan 4 4>;
+	};
+};
+
+&gio {
+	// The GPIOs above 35 are not used on Pi 5, so shrink the upper bank
+	// to reduce the clutter in gpioinfo/pinctrl
+	brcm,gpio-bank-widths = <32 4>;
+
+	gpio-line-names =
+		"-", // GPIO_000
+		"2712_BOOT_CS_N", // GPIO_001
+		"2712_BOOT_MISO", // GPIO_002
+		"2712_BOOT_MOSI", // GPIO_003
+		"2712_BOOT_SCLK", // GPIO_004
+		"-", // GPIO_005
+		"-", // GPIO_006
+		"-", // GPIO_007
+		"-", // GPIO_008
+		"-", // GPIO_009
+		"-", // GPIO_010
+		"-", // GPIO_011
+		"-", // GPIO_012
+		"-", // GPIO_013
+		"PCIE_SDA", // GPIO_014
+		"PCIE_SCL", // GPIO_015
+		"-", // GPIO_016
+		"-", // GPIO_017
+		"-", // GPIO_018
+		"-", // GPIO_019
+		"PWR_GPIO", // GPIO_020
+		"2712_G21_FS", // GPIO_021
+		"-", // GPIO_022
+		"-", // GPIO_023
+		"BT_RTS", // GPIO_024
+		"BT_CTS", // GPIO_025
+		"BT_TXD", // GPIO_026
+		"BT_RXD", // GPIO_027
+		"WL_ON", // GPIO_028
+		"BT_ON", // GPIO_029
+		"WIFI_SDIO_CLK", // GPIO_030
+		"WIFI_SDIO_CMD", // GPIO_031
+		"WIFI_SDIO_D0", // GPIO_032
+		"WIFI_SDIO_D1", // GPIO_033
+		"WIFI_SDIO_D2", // GPIO_034
+		"WIFI_SDIO_D3"; // GPIO_035
+};
+
+&gio_aon {
+	gpio-line-names =
+		"RP1_SDA", // AON_GPIO_00
+		"RP1_SCL", // AON_GPIO_01
+		"RP1_RUN", // AON_GPIO_02
+		"SD_IOVDD_SEL", // AON_GPIO_03
+		"SD_PWR_ON", // AON_GPIO_04
+		"SD_CDET_N", // AON_GPIO_05
+		"SD_FLG_N", // AON_GPIO_06
+		"-", // AON_GPIO_07
+		"2712_WAKE", // AON_GPIO_08
+		"2712_STAT_LED", // AON_GPIO_09
+		"-", // AON_GPIO_10
+		"-", // AON_GPIO_11
+		"PMIC_INT", // AON_GPIO_12
+		"UART_TX_FS", // AON_GPIO_13
+		"UART_RX_FS", // AON_GPIO_14
+		"-", // AON_GPIO_15
+		"-", // AON_GPIO_16
+
+		// Pad bank0 out to 32 entries
+		"", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
+
+		"HDMI0_SCL", // AON_SGPIO_00
+		"HDMI0_SDA", // AON_SGPIO_01
+		"HDMI1_SCL", // AON_SGPIO_02
+		"HDMI1_SDA", // AON_SGPIO_03
+		"PMIC_SCL", // AON_SGPIO_04
+		"PMIC_SDA"; // AON_SGPIO_05
+
+	rp1_run_hog {
+		gpio-hog;
+		gpios = <2 GPIO_ACTIVE_HIGH>;
+		output-high;
+		line-name = "RP1 RUN pin";
+	};
+};
+
+&rp1_gpio {
+	gpio-line-names =
+		"ID_SDA", // GPIO0
+		"ID_SCL", // GPIO1
+		"GPIO2", // GPIO2
+		"GPIO3", // GPIO3
+		"GPIO4", // GPIO4
+		"GPIO5", // GPIO5
+		"GPIO6", // GPIO6
+		"GPIO7", // GPIO7
+		"GPIO8", // GPIO8
+		"GPIO9", // GPIO9
+		"GPIO10", // GPIO10
+		"GPIO11", // GPIO11
+		"GPIO12", // GPIO12
+		"GPIO13", // GPIO13
+		"GPIO14", // GPIO14
+		"GPIO15", // GPIO15
+		"GPIO16", // GPIO16
+		"GPIO17", // GPIO17
+		"GPIO18", // GPIO18
+		"GPIO19", // GPIO19
+		"GPIO20", // GPIO20
+		"GPIO21", // GPIO21
+		"GPIO22", // GPIO22
+		"GPIO23", // GPIO23
+		"GPIO24", // GPIO24
+		"GPIO25", // GPIO25
+		"GPIO26", // GPIO26
+		"GPIO27", // GPIO27
+
+		"PCIE_RP1_WAKE", // GPIO28
+		"FAN_TACH", // GPIO29
+		"HOST_SDA", // GPIO30
+		"HOST_SCL", // GPIO31
+		"ETH_RST_N", // GPIO32
+		"-", // GPIO33
+
+		"CD0_IO0_MICCLK", // GPIO34
+		"CD0_IO0_MICDAT0", // GPIO35
+		"RP1_PCIE_CLKREQ_N", // GPIO36
+		"-", // GPIO37
+		"CD0_SDA", // GPIO38
+		"CD0_SCL", // GPIO39
+		"CD1_SDA", // GPIO40
+		"CD1_SCL", // GPIO41
+		"USB_VBUS_EN", // GPIO42
+		"USB_OC_N", // GPIO43
+		"RP1_STAT_LED", // GPIO44
+		"FAN_PWM", // GPIO45
+		"CD1_IO0_MICCLK", // GPIO46
+		"2712_WAKE", // GPIO47
+		"CD1_IO1_MICDAT1", // GPIO48
+		"EN_MAX_USB_CUR", // GPIO49
+		"-", // GPIO50
+		"-", // GPIO51
+		"-", // GPIO52
+		"-"; // GPIO53
+
+	usb_vbus_pins: usb_vbus_pins {
+		function = "vbus1";
+		pins = "gpio42", "gpio43";
+	};
+};
+
+/ {
+	__overrides__ {
+		sd_cqe = <&sdio1>, "supports-cqe:0";
+	};
+};
+
+&hvs {
+	clocks = <&firmware_clocks 4>, <&firmware_clocks 16>;
+	clock-names = "core", "disp";
+};
+
+&hdmi0 {
+	clocks = <&firmware_clocks 13>, <&firmware_clocks 14>, <&dvp 0>, <&clk_27MHz>;
+	clock-names = "hdmi", "bvb", "audio", "cec";
+};
+
+&hdmi1 {
+	clocks = <&firmware_clocks 13>, <&firmware_clocks 14>, <&dvp 1>, <&clk_27MHz>;
+	clock-names = "hdmi", "bvb", "audio", "cec";
+};
+
+&pcie1 {
+	brcm,clkreq-mode = "safe";
+};
+
+&pcie2 {
+	status = "okay";
 };
diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-500.dts b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-500.dts
new file mode 100644
index 000000000000..f543ff64200d
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-500.dts
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "bcm2712d0-rpi-5-b.dts"
+
+/ {
+	compatible = "raspberrypi,500", "brcm,bcm2712";
+	model = "Raspberry Pi 500";
+};
+
+&pwr_key {
+	debounce-interval = <0>;
+};
+
+&gio {
+	gpio-line-names =
+		"", // GPIO_000
+		"2712_BOOT_CS_N", // GPIO_001
+		"2712_BOOT_MISO", // GPIO_002
+		"2712_BOOT_MOSI", // GPIO_003
+		"2712_BOOT_SCLK", // GPIO_004
+		"", // GPIO_005
+		"", // GPIO_006
+		"", // GPIO_007
+		"", // GPIO_008
+		"", // GPIO_009
+		"-", // GPIO_010
+		"-", // GPIO_011
+		"-", // GPIO_012
+		"-", // GPIO_013
+		"M2_DET_WAKE", // GPIO_014
+		"M2_PWR_EN", // GPIO_015
+		"", // GPIO_016
+		"", // GPIO_017
+		"KEYB_BOOTSEL", // GPIO_018
+		"-", // GPIO_019
+		"PWR_GPIO", // GPIO_020
+		"KEYB_RUN", // GPIO_021
+		"-", // GPIO_022
+		"USER_LED", // GPIO_023
+		"BT_RTS", // GPIO_024
+		"BT_CTS", // GPIO_025
+		"BT_TXD", // GPIO_026
+		"BT_RXD", // GPIO_027
+		"WL_ON", // GPIO_028
+		"BT_ON", // GPIO_029
+		"WIFI_SDIO_CLK", // GPIO_030
+		"WIFI_SDIO_CMD", // GPIO_031
+		"WIFI_SDIO_D0", // GPIO_032
+		"WIFI_SDIO_D1", // GPIO_033
+		"WIFI_SDIO_D2", // GPIO_034
+		"WIFI_SDIO_D3"; // GPIO_035
+};
+
+&gio_aon {
+	gpio-line-names =
+		"RP1_SDA", // AON_GPIO_00
+		"RP1_SCL", // AON_GPIO_01
+		"RP1_RUN", // AON_GPIO_02
+		"SD_IOVDD_SEL", // AON_GPIO_03
+		"SD_PWR_ON", // AON_GPIO_04
+		"SD_CDET_N", // AON_GPIO_05
+		"SD_FLG_N", // AON_GPIO_06
+		"", // AON_GPIO_07
+		"2712_WAKE", // AON_GPIO_08
+		"2712_STAT_LED", // AON_GPIO_09
+		"", // AON_GPIO_10
+		"", // AON_GPIO_11
+		"PMIC_INT", // AON_GPIO_12
+		"UART_TX_FS", // AON_GPIO_13
+		"UART_RX_FS", // AON_GPIO_14
+		"", // AON_GPIO_15
+		"", // AON_GPIO_16
+
+		// Pad bank0 out to 32 entries
+		"", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
+
+		"HDMI0_SCL", // AON_SGPIO_00
+		"HDMI0_SDA", // AON_SGPIO_01
+		"HDMI1_SCL", // AON_SGPIO_02
+		"HDMI1_SDA", // AON_SGPIO_03
+		"PMIC_SCL", // AON_SGPIO_04
+		"PMIC_SDA"; // AON_SGPIO_05
+};
+
+&rp1_gpio {
+	gpio-line-names =
+		"ID_SDA", // GPIO0
+		"ID_SCL", // GPIO1
+		"GPIO2", // GPIO2
+		"GPIO3", // GPIO3
+		"GPIO4", // GPIO4
+		"GPIO5", // GPIO5
+		"GPIO6", // GPIO6
+		"GPIO7", // GPIO7
+		"GPIO8", // GPIO8
+		"GPIO9", // GPIO9
+		"GPIO10", // GPIO10
+		"GPIO11", // GPIO11
+		"GPIO12", // GPIO12
+		"GPIO13", // GPIO13
+		"GPIO14", // GPIO14
+		"GPIO15", // GPIO15
+		"GPIO16", // GPIO16
+		"GPIO17", // GPIO17
+		"GPIO18", // GPIO18
+		"GPIO19", // GPIO19
+		"GPIO20", // GPIO20
+		"GPIO21", // GPIO21
+		"GPIO22", // GPIO22
+		"GPIO23", // GPIO23
+		"GPIO24", // GPIO24
+		"GPIO25", // GPIO25
+		"GPIO26", // GPIO26
+		"GPIO27", // GPIO27
+
+		"PCIE_RP1_WAKE", // GPIO28
+		"-", // GPIO29
+		"HOST_SDA", // GPIO30
+		"HOST_SCL", // GPIO31
+		"ETH_RST_N", // GPIO32
+		"PCIE_DET_WAKE", // GPIO33
+
+		"-", // GPIO34
+		"-", // GPIO35
+		"RP1_PCIE_CLKREQ_N", // GPIO36
+		"-", // GPIO37
+		"-", // GPIO38
+		"-", // GPIO39
+		"CD1_SDA", // GPIO40
+		"CD1_SCL", // GPIO41
+		"USB_VBUS_EN", // GPIO42
+		"USB_OC_N", // GPIO43
+		"RP1_STAT_LED", // GPIO44
+		"-", // GPIO45
+		"-", // GPIO46
+		"HOST_WAKE", // GPIO47
+		"-", // GPIO48
+		"EN_MAX_USB_CUR", // GPIO49
+		"-", // GPIO50
+		"-", // GPIO51
+		"-", // GPIO52
+		"-"; // GPIO53
+};
diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm4io.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm4io.dtsi
new file mode 100644
index 000000000000..1b4c42a61817
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm4io.dtsi
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0
+
+i2c_csi_dsi0: &i2c0 { // Note: For CAM0 and DISP0 connectors
+};
+
+i2c_csi_dsi1: &i2c6 { // Note: For CAM1, DISP1, on-board RTC, and fan controller
+	pinctrl-0 = <&rp1_i2c6_38_39>;
+	pinctrl-names = "default";
+	clock-frequency = <100000>;
+	symlink = "i2c-6";
+};
+
+i2c_csi_dsi: &i2c_csi_dsi1 { }; // The connector that needs no jumper to enable
+
+&aliases {
+    /delete-property/ i2c11;
+    i2c10 = &i2c_csi_dsi;
+};
+
+// The RP1 USB3 interfaces are not usable on CM4IO
+
+&rp1_usb0 {
+	status = "disabled";
+};
+
+&rp1_usb1 {
+	status = "disabled";
+};
diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5-cm4io.dts b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5-cm4io.dts
new file mode 100644
index 000000000000..96cd7cf735d5
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5-cm4io.dts
@@ -0,0 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+
+#include "bcm2712-rpi-cm5.dtsi"
+#include "bcm2712-rpi-cm4io.dtsi"
diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5-cm5io.dts b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5-cm5io.dts
new file mode 100644
index 000000000000..6b5e147d569d
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5-cm5io.dts
@@ -0,0 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+
+#include "bcm2712-rpi-cm5.dtsi"
+#include "bcm2712-rpi-cm5io.dtsi"
diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi
new file mode 100644
index 000000000000..e35d3abba20b
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5.dtsi
@@ -0,0 +1,738 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/clock/rp1.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/mfd/rp1.h>
+#include <dt-bindings/pwm/pwm.h>
+#include <dt-bindings/reset/raspberrypi,firmware-reset.h>
+
+#include "bcm2712-ds.dtsi"
+
+/ {
+	compatible = "raspberrypi,5-compute-module", "brcm,bcm2712";
+	model = "Raspberry Pi Compute Module 5";
+
+	/* Will be filled by the bootloader */
+	memory@0 {
+		device_type = "memory";
+#ifndef FIRMWARE_UPDATED
+		reg = <0 0 0x28000000>;
+#else
+		reg = <0 0 0 0x28000000>;
+#endif
+	};
+
+	leds: leds {
+		compatible = "gpio-leds";
+
+		led_pwr: led-pwr {
+			label = "PWR";
+			gpios = <&rp1_gpio 44 GPIO_ACTIVE_LOW>;
+			default-state = "off";
+			linux,default-trigger = "none";
+		};
+
+		led_act: led-act {
+			label = "ACT";
+			gpios = <&gio_aon 9 GPIO_ACTIVE_LOW>;
+			default-state = "off";
+			linux,default-trigger = "mmc0";
+		};
+	};
+
+	sd_io_1v8_reg: sd-io-1v8-reg {
+		compatible = "regulator-fixed";
+		regulator-name = "vdd-sd-io";
+		regulator-min-microvolt = <1800000>;
+		regulator-max-microvolt = <1800000>;
+		regulator-always-on;
+	};
+
+	sd_vcc_reg: sd-vcc-reg {
+		compatible = "regulator-fixed";
+		regulator-name = "vcc-sd";
+		regulator-min-microvolt = <3300000>;
+		regulator-max-microvolt = <3300000>;
+		regulator-boot-on;
+		enable-active-high;
+		gpios = <&gio_aon 4 GPIO_ACTIVE_HIGH>;
+	};
+
+	wl_on_reg: wl-on-reg {
+		compatible = "regulator-fixed";
+		regulator-name = "wl-on-regulator";
+		regulator-min-microvolt = <3300000>;
+		regulator-max-microvolt = <3300000>;
+		pinctrl-0 = <&wl_on_pins>;
+		pinctrl-names = "default";
+
+		gpio = <&gio 28 GPIO_ACTIVE_HIGH>;
+
+		startup-delay-us = <150000>;
+		enable-active-high;
+	};
+
+	cam1_clk: cam1_clk {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		status = "disabled";
+	};
+
+	cam0_clk: cam0_clk {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		status = "disabled";
+	};
+
+	cam0_reg: cam0_reg {
+		compatible = "regulator-fixed";
+		regulator-name = "cam0_reg";
+		enable-active-high;
+		gpio = <&rp1_gpio 34 0>;  // CD0_IO0_MICCLK, to CAM_GPIO on connector
+	};
+
+	cam_dummy_reg: cam_dummy_reg {
+		compatible = "regulator-fixed";
+		regulator-name = "cam-dummy-reg";
+	};
+
+	dummy: dummy {
+		// A target for unwanted overlay fragments
+	};
+
+
+	// A few extra labels to keep overlays happy
+
+	i2c0if: i2c0if {};
+	i2c0mux: i2c0mux {};
+};
+
+rp1_target: &pcie2 {
+	aspm-no-l0s;
+	status = "okay";
+};
+
+// The system SPI for the bootloader EEPROM
+&spi10 { status = "okay"; };
+
+#include "rp1.dtsi"
+
+&rp1 {
+	// PCIe address space layout:
+	// 00_00000000-00_00xxxxxx = RP1 peripherals
+	// 10_00000000-1x_xxxxxxxx = up to 64GB system RAM
+
+	// outbound access aimed at PCIe 0_00xxxxxx -> RP1 c0_40xxxxxx
+	// This is the RP1 peripheral space
+	ranges = <0xc0 0x40000000
+		  0x02000000 0x00 0x00000000
+		  0x00 0x00410000>;
+
+	dma-ranges =
+	// inbound RP1 1x_xxxxxxxx -> PCIe 1x_xxxxxxxx
+		     <0x10 0x00000000
+		      0x43000000 0x10 0x00000000
+		      0x10 0x00000000>,
+
+	// inbound RP1 c0_40xxxxxx -> PCIe 00_00xxxxxx
+	// This allows the RP1 DMA controller to address RP1 hardware
+		     <0xc0 0x40000000
+		      0x02000000 0x0 0x00000000
+		      0x0 0x00410000>,
+
+	// inbound RP1 0x_xxxxxxxx -> PCIe 1x_xxxxxxxx
+		     <0x00 0x00000000
+		      0x02000000 0x10 0x00000000
+		      0x10 0x00000000>;
+};
+
+// Expose RP1 nodes as system nodes with labels
+
+&rp1_dma  {
+	status = "okay";
+};
+
+&rp1_eth {
+	status = "okay";
+	phy-handle = <&phy1>;
+	phy-reset-gpios = <&rp1_gpio 32 GPIO_ACTIVE_LOW>;
+	phy-reset-duration = <5>;
+	pinctrl-names = "default";
+	pinctrl-0 = <&eth0_irq_pins>;
+
+	phy1: ethernet-phy@0 {
+		reg = <0x0>;
+		brcm,powerdown-enable;
+		interrupt-parent = <&rp1_gpio>;
+		interrupts = <37 IRQ_TYPE_LEVEL_LOW>;
+		eee-broken-1000t;
+		eee-broken-100tx;
+	};
+};
+
+/* The Debug UART, on Rpi5 it's on JST-SH 1.0mm 3-pin connector
+ * labeled "UART", i.e. the interface with the system console.
+ */
+&uart10 {
+	status = "okay";
+};
+
+gpio: &rp1_gpio {
+	status = "okay";
+};
+
+aux: &dummy {};
+
+&rp1_usb0 {
+	pinctrl-0 = <&usb_vbus_pins>;
+	pinctrl-names = "default";
+	status = "okay";
+};
+
+&rp1_usb1 {
+	status = "okay";
+};
+
+#include "bcm2712-rpi.dtsi"
+
+cam1_reg: &cam0_reg { // Shares CAM_GPIO with cam0_reg
+};
+
+csi0: &rp1_csi0 { };
+csi1: &rp1_csi1 { };
+dsi0: &rp1_dsi0 { };
+dsi1: &rp1_dsi1 { };
+dpi: &rp1_dpi { };
+vec: &rp1_vec { };
+dpi_gpio0:              &rp1_dpi_24bit_gpio0        { };
+dpi_gpio1:              &rp1_dpi_24bit_gpio2        { };
+dpi_18bit_cpadhi_gpio0: &rp1_dpi_18bit_cpadhi_gpio0 { };
+dpi_18bit_cpadhi_gpio2: &rp1_dpi_18bit_cpadhi_gpio2 { };
+dpi_18bit_gpio0:        &rp1_dpi_18bit_gpio0        { };
+dpi_18bit_gpio2:        &rp1_dpi_18bit_gpio2        { };
+dpi_16bit_cpadhi_gpio0: &rp1_dpi_16bit_cpadhi_gpio0 { };
+dpi_16bit_cpadhi_gpio2: &rp1_dpi_16bit_cpadhi_gpio2 { };
+dpi_16bit_gpio0:        &rp1_dpi_16bit_gpio0        { };
+dpi_16bit_gpio2:        &rp1_dpi_16bit_gpio2        { };
+
+/* Add the IOMMUs for some RP1 bus masters */
+
+&csi0 {
+	iommus = <&iommu5>;
+};
+
+&csi1 {
+	iommus = <&iommu5>;
+};
+
+&dsi0 {
+	iommus = <&iommu5>;
+};
+
+&dsi1 {
+	iommus = <&iommu5>;
+};
+
+&dpi {
+	iommus = <&iommu5>;
+};
+
+&vec {
+	iommus = <&iommu5>;
+};
+
+&ddc0 {
+	status = "disabled";
+};
+
+&ddc1 {
+	status = "disabled";
+};
+
+&hdmi0 {
+	clocks = <&firmware_clocks 13>, <&firmware_clocks 14>, <&dvp 0>, <&clk_27MHz>;
+	clock-names = "hdmi", "bvb", "audio", "cec";
+	status = "disabled";
+};
+
+&hdmi1 {
+	clocks = <&firmware_clocks 13>, <&firmware_clocks 14>, <&dvp 1>, <&clk_27MHz>;
+	clock-names = "hdmi", "bvb", "audio", "cec";
+	status = "disabled";
+};
+
+&hvs {
+	clocks = <&firmware_clocks 4>, <&firmware_clocks 16>;
+	clock-names = "core", "disp";
+};
+
+&mop {
+	status = "disabled";
+};
+
+&moplet {
+	status = "disabled";
+};
+
+&pixelvalve0 {
+	status = "disabled";
+};
+
+&pixelvalve1 {
+	status = "disabled";
+};
+
+&disp_intr {
+	status = "disabled";
+};
+
+/* SDIO1 is used to drive the eMMC/SD card */
+&sdio1 {
+	pinctrl-0 = <&emmc_cmddat_pulls>, <&emmc_ds_pull>;
+	pinctrl-names = "default";
+	vqmmc-supply = <&sd_io_1v8_reg>;
+	vmmc-supply = <&sd_vcc_reg>;
+	bus-width = <8>;
+	sd-uhs-sdr50;
+	sd-uhs-ddr50;
+	sd-uhs-sdr104;
+	mmc-hs200-1_8v;
+	mmc-hs400-1_8v;
+	mmc-hs400-enhanced-strobe;
+	broken-cd;
+	supports-cqe = <1>;
+	status = "okay";
+};
+
+&pinctrl_aon {
+	ant_pins: ant_pins {
+		function = "gpio";
+		pins = "aon_gpio5", "aon_gpio6";
+	};
+
+	/* Slight hack - only one PWM pin (status LED) is usable */
+	aon_pwm_1pin: aon_pwm_1pin {
+		function = "aon_pwm";
+		pins = "aon_gpio9";
+	};
+};
+
+&pinctrl {
+	pwr_button_pins: pwr_button_pins {
+		function = "gpio";
+		pins = "gpio20";
+		bias-pull-up;
+	};
+
+	wl_on_pins: wl_on_pins {
+		function = "gpio";
+		pins = "gpio28";
+	};
+
+	bt_shutdown_pins: bt_shutdown_pins {
+		function = "gpio";
+		pins = "gpio29";
+	};
+
+	emmc_ds_pull: emmc_ds_pull {
+		pins = "emmc_ds";
+		bias-pull-down;
+	};
+
+	emmc_cmddat_pulls: emmc_cmddat_pulls {
+		pins = "emmc_cmd", "emmc_dat0", "emmc_dat1", "emmc_dat2", "emmc_dat3",
+		       "emmc_dat4", "emmc_dat5", "emmc_dat6", "emmc_dat7";
+		bias-pull-up;
+	};
+};
+
+/* uarta communicates with the BT module */
+&uarta {
+	uart-has-rtscts;
+	auto-flow-control;
+	status = "okay";
+	clock-frequency = <96000000>;
+	pinctrl-0 = <&uarta_24_pins &bt_shutdown_pins>;
+	pinctrl-names = "default";
+
+	bluetooth: bluetooth {
+		compatible = "brcm,bcm43438-bt";
+		max-speed = <3000000>;
+		shutdown-gpios = <&gio 29 GPIO_ACTIVE_HIGH>;
+		local-bd-address = [ 00 00 00 00 00 00 ];
+	};
+};
+
+&i2c10 {
+	clock-frequency = <400000>;
+	pinctrl-0 = <&i2c3_m4_agpio0_pins>;
+	pinctrl-names = "default";
+};
+
+/ {
+	fan: cooling_fan {
+		status = "disabled";
+		compatible = "pwm-fan";
+		#cooling-cells = <2>;
+		cooling-min-state = <0>;
+		cooling-max-state = <3>;
+		cooling-levels = <0 75 125 175 250>;
+		pwms = <&rp1_pwm1 3 41566 PWM_POLARITY_INVERTED>;
+		rpm-regmap = <&rp1_pwm1>;
+		rpm-offset = <0x3c>;
+	};
+
+	pwr_button {
+		compatible = "gpio-keys";
+
+		pinctrl-names = "default";
+		pinctrl-0 = <&pwr_button_pins>;
+		status = "okay";
+
+		pwr_key: pwr {
+			label = "pwr_button";
+			// linux,code = <205>; // KEY_SUSPEND
+			linux,code = <116>; // KEY_POWER
+			gpios = <&gio 20 GPIO_ACTIVE_LOW>;
+			debounce-interval = <50>; // ms
+		};
+	};
+};
+
+&usb {
+	power-domains = <&power RPI_POWER_DOMAIN_USB>;
+};
+
+/* SDIO2 drives the WLAN interface */
+&sdio2 {
+	pinctrl-0 = <&sdio2_30_pins>, <&ant_pins>;
+	pinctrl-names = "default";
+	bus-width = <4>;
+	vmmc-supply = <&wl_on_reg>;
+	sd-uhs-ddr50;
+	non-removable;
+	status = "okay";
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	wifi: wifi@1 {
+		reg = <1>;
+		compatible = "brcm,bcm4329-fmac";
+		local-mac-address = [00 00 00 00 00 00];
+	};
+};
+
+&pinctrl {
+	spi10_gpio2: spi10_gpio2 {
+		function = "vc_spi0";
+		pins = "gpio2", "gpio3", "gpio4";
+		bias-disable;
+	};
+
+	spi10_cs_gpio1: spi10_cs_gpio1 {
+		function = "gpio";
+		pins = "gpio1";
+		bias-pull-up;
+	};
+};
+
+spi10_pins: &spi10_gpio2 {};
+spi10_cs_pins: &spi10_cs_gpio1 {};
+
+&spi10 {
+	pinctrl-names = "default";
+	cs-gpios = <&gio 1 1>;
+	pinctrl-0 = <&spi10_pins &spi10_cs_pins>;
+
+	spidev10: spidev@0 {
+		compatible = "spidev";
+		reg = <0>;	/* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <20000000>;
+		status = "okay";
+	};
+};
+
+// =============================================
+// Board specific stuff here
+
+&gio_aon {
+	// Don't use GIO_AON as an interrupt controller because it will
+	// clash with the firmware monitoring the PMIC interrupt via the VPU.
+
+	/delete-property/ interrupt-controller;
+	/delete-property/ #interrupt-cells;
+};
+
+&main_aon_irq {
+	// Don't use the MAIN_AON_IRQ interrupt controller because it will
+	// clash with the firmware monitoring the PMIC interrupt via the VPU.
+
+	status = "disabled";
+};
+
+&rp1_pwm1 {
+	status = "disabled";
+	pinctrl-0 = <&rp1_pwm1_gpio45>;
+	pinctrl-names = "default";
+};
+
+&thermal_trips {
+	cpu_tepid: cpu-tepid {
+		temperature = <50000>;
+		hysteresis = <5000>;
+		type = "active";
+	};
+
+	cpu_warm: cpu-warm {
+		temperature = <60000>;
+		hysteresis = <5000>;
+		type = "active";
+	};
+
+	cpu_hot: cpu-hot {
+		temperature = <67500>;
+		hysteresis = <5000>;
+		type = "active";
+	};
+
+	cpu_vhot: cpu-vhot {
+		temperature = <75000>;
+		hysteresis = <5000>;
+		type = "active";
+	};
+};
+
+&cooling_maps {
+	tepid {
+		trip = <&cpu_tepid>;
+		cooling-device = <&fan 1 1>;
+	};
+
+	warm {
+		trip = <&cpu_warm>;
+		cooling-device = <&fan 2 2>;
+	};
+
+	hot {
+		trip = <&cpu_hot>;
+		cooling-device = <&fan 3 3>;
+	};
+
+	vhot {
+		trip = <&cpu_vhot>;
+		cooling-device = <&fan 4 4>;
+	};
+
+	melt {
+		trip = <&cpu_crit>;
+		cooling-device = <&fan 4 4>;
+	};
+};
+
+&gio {
+	// The GPIOs above 35 are not used on Pi 5, so shrink the upper bank
+	// to reduce the clutter in gpioinfo/pinctrl
+	brcm,gpio-bank-widths = <32 4>;
+
+	gpio-line-names =
+		"-", // GPIO_000
+		"2712_BOOT_CS_N", // GPIO_001
+		"2712_BOOT_MISO", // GPIO_002
+		"2712_BOOT_MOSI", // GPIO_003
+		"2712_BOOT_SCLK", // GPIO_004
+		"-", // GPIO_005
+		"-", // GPIO_006
+		"-", // GPIO_007
+		"-", // GPIO_008
+		"-", // GPIO_009
+		"-", // GPIO_010
+		"-", // GPIO_011
+		"-", // GPIO_012
+		"-", // GPIO_013
+		"-", // GPIO_014
+		"-", // GPIO_015
+		"-", // GPIO_016
+		"-", // GPIO_017
+		"-", // GPIO_018
+		"-", // GPIO_019
+		"PWR_GPIO", // GPIO_020
+		"2712_G21_FS", // GPIO_021
+		"-", // GPIO_022
+		"-", // GPIO_023
+		"BT_RTS", // GPIO_024
+		"BT_CTS", // GPIO_025
+		"BT_TXD", // GPIO_026
+		"BT_RXD", // GPIO_027
+		"WL_ON", // GPIO_028
+		"BT_ON", // GPIO_029
+		"WIFI_SDIO_CLK", // GPIO_030
+		"WIFI_SDIO_CMD", // GPIO_031
+		"WIFI_SDIO_D0", // GPIO_032
+		"WIFI_SDIO_D1", // GPIO_033
+		"WIFI_SDIO_D2", // GPIO_034
+		"WIFI_SDIO_D3"; // GPIO_035
+};
+
+&gio_aon {
+	gpio-line-names =
+		"RP1_SDA", // AON_GPIO_00
+		"RP1_SCL", // AON_GPIO_01
+		"RP1_RUN", // AON_GPIO_02
+		"SD_IOVDD_SEL", // AON_GPIO_03
+		"SD_PWR_ON", // AON_GPIO_04
+		"ANT1", // AON_GPIO_05
+		"ANT2", // AON_GPIO_06
+		"-", // AON_GPIO_07
+		"2712_WAKE", // AON_GPIO_08
+		"2712_STAT_LED", // AON_GPIO_09
+		"-", // AON_GPIO_10
+		"-", // AON_GPIO_11
+		"PMIC_INT", // AON_GPIO_12
+		"UART_TX_FS", // AON_GPIO_13
+		"UART_RX_FS", // AON_GPIO_14
+		"-", // AON_GPIO_15
+		"-", // AON_GPIO_16
+
+		// Pad bank0 out to 32 entries
+		"", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
+
+		"HDMI0_SCL", // AON_SGPIO_00
+		"HDMI0_SDA", // AON_SGPIO_01
+		"HDMI1_SCL", // AON_SGPIO_02
+		"HDMI1_SDA", // AON_SGPIO_03
+		"PMIC_SCL", // AON_SGPIO_04
+		"PMIC_SDA"; // AON_SGPIO_05
+
+	rp1_run_hog {
+		gpio-hog;
+		gpios = <2 GPIO_ACTIVE_HIGH>;
+		output-high;
+		line-name = "RP1 RUN pin";
+	};
+
+	ant1: ant1-hog {
+		gpio-hog;
+		gpios = <5 GPIO_ACTIVE_HIGH>;
+		/* internal antenna enabled */
+		output-high;
+		line-name = "ant1";
+	};
+
+	ant2: ant2-hog {
+		gpio-hog;
+		gpios = <6 GPIO_ACTIVE_HIGH>;
+		/* external antenna disabled */
+		output-low;
+		line-name = "ant2";
+	};
+};
+
+&rp1_gpio {
+	gpio-line-names =
+		"ID_SDA", // GPIO0
+		"ID_SCL", // GPIO1
+		"GPIO2", // GPIO2
+		"GPIO3", // GPIO3
+		"GPIO4", // GPIO4
+		"GPIO5", // GPIO5
+		"GPIO6", // GPIO6
+		"GPIO7", // GPIO7
+		"GPIO8", // GPIO8
+		"GPIO9", // GPIO9
+		"GPIO10", // GPIO10
+		"GPIO11", // GPIO11
+		"GPIO12", // GPIO12
+		"GPIO13", // GPIO13
+		"GPIO14", // GPIO14
+		"GPIO15", // GPIO15
+		"GPIO16", // GPIO16
+		"GPIO17", // GPIO17
+		"GPIO18", // GPIO18
+		"GPIO19", // GPIO19
+		"GPIO20", // GPIO20
+		"GPIO21", // GPIO21
+		"GPIO22", // GPIO22
+		"GPIO23", // GPIO23
+		"GPIO24", // GPIO24
+		"GPIO25", // GPIO25
+		"GPIO26", // GPIO26
+		"GPIO27", // GPIO27
+
+		"PCIE_PWR_EN", // GPIO28
+		"FAN_TACH", // GPIO29
+		"HOST_SDA", // GPIO30
+		"HOST_SCL", // GPIO31
+		"ETH_RST_N", // GPIO32
+		"PCIE_DET_WAKE", // GPIO33
+
+		"CD0_IO0_MICCLK", // GPIO34
+		"CD0_IO0_MICDAT0", // GPIO35
+		"RP1_PCIE_CLKREQ_N", // GPIO36
+		"ETH_IRQ_N", // GPIO37
+		"SDA0", // GPIO38
+		"SCL0", // GPIO39
+		"-", // GPIO40
+		"-", // GPIO41
+		"USB_VBUS_EN", // GPIO42
+		"-", // GPIO43
+		"RP1_STAT_LED", // GPIO44
+		"FAN_PWM", // GPIO45
+		"-", // GPIO46
+		"2712_WAKE", // GPIO47
+		"-", // GPIO48
+		"-", // GPIO49
+		"-", // GPIO50
+		"-", // GPIO51
+		"-", // GPIO52
+		"-"; // GPIO53
+
+	usb_vbus_pins: usb_vbus_pins {
+		function = "vbus1";
+		pins = "gpio42", "gpio43";
+	};
+
+	micclk1_hog {
+		gpio-hog;
+		gpios = <46 GPIO_ACTIVE_HIGH>;
+		output-high;
+	};
+
+	micdat1_hog {
+		gpio-hog;
+		gpios = <48 GPIO_ACTIVE_HIGH>;
+		output-high;
+	};
+
+	eth0_irq_pins: eth0_irq_pins {
+		pins = "gpio37";
+		function = "gpio";
+		// workaround missing hardware PU
+		bias-pull-up;
+		input-enable;
+	};
+};
+
+/ {
+	__overrides__ {
+		ant1 =  <&ant1>,"output-high?=on",
+			<&ant1>, "output-low?=off",
+			<&ant2>, "output-high?=off",
+			<&ant2>, "output-low?=on";
+		ant2 =  <&ant1>,"output-high?=off",
+			<&ant1>, "output-low?=on",
+			<&ant2>, "output-high?=on",
+			<&ant2>, "output-low?=off";
+		noant = <&ant1>,"output-high?=off",
+			<&ant1>, "output-low?=on",
+			<&ant2>, "output-high?=off",
+			<&ant2>, "output-low?=on";
+		noanthogs = <&ant1>,"status=disabled",
+			<&ant2>, "status=disabled";
+		sd = <&sdio1>, "status";
+		sd_poll_once = <&sdio1>, "non-removable?";
+		sd_cqe = <&sdio1>, "supports-cqe:0";
+	};
+};
diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5io.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5io.dtsi
new file mode 100644
index 000000000000..4c616a21dc76
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5io.dtsi
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0
+
+i2c_csi_dsi1: &i2c0 { // Note: This is for CAM/DISP 1 connector
+	symlink = "i2c-11";
+};
+
+i2c_csi_dsi0: &i2c6 { // Note: This is for CAM/DISP 0 connector
+	pinctrl-0 = <&rp1_i2c6_38_39>;
+	pinctrl-names = "default";
+	clock-frequency = <100000>;
+	symlink = "i2c-6";
+};
+
+i2c_csi_dsi: &i2c_csi_dsi1 { }; // The connector that needs no jumper to enable
diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5l-cm4io.dts b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5l-cm4io.dts
new file mode 100644
index 000000000000..71259a673d99
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5l-cm4io.dts
@@ -0,0 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+
+#include "bcm2712-rpi-cm5l.dtsi"
+#include "bcm2712-rpi-cm4io.dtsi"
diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5l-cm5io.dts b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5l-cm5io.dts
new file mode 100644
index 000000000000..11a56dfb7b48
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5l-cm5io.dts
@@ -0,0 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0
+/dts-v1/;
+
+#include "bcm2712-rpi-cm5l.dtsi"
+#include "bcm2712-rpi-cm5io.dtsi"
diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5l.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5l.dtsi
new file mode 100644
index 000000000000..6ba1a3efdd87
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi-cm5l.dtsi
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "bcm2712-rpi-cm5.dtsi"
+
+/ {
+	model = "Raspberry Pi Compute Module 5 Lite";
+};
+
+&sd_io_1v8_reg {
+	compatible = "regulator-gpio";
+	regulator-max-microvolt = <3300000>;
+	regulator-settling-time-us = <5000>;
+	gpios = <&gio_aon 3 GPIO_ACTIVE_HIGH>;
+	states = <1800000 0x1
+		  3300000 0x0>;
+};
+
+&sdio1 {
+	/delete-property/ mmc-hs400-1_8v;
+	/delete-property/ mmc-hs400-enhanced-strobe;
+};
diff --git a/arch/arm64/boot/dts/broadcom/bcm2712-rpi.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712-rpi.dtsi
new file mode 100644
index 000000000000..52452d0bec59
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2712-rpi.dtsi
@@ -0,0 +1,503 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <dt-bindings/power/raspberrypi-power.h>
+
+#define DMA_FLAG_HEAVY       (1 << 8)
+
+&soc {
+	firmware: firmware {
+		compatible = "raspberrypi,bcm2835-firmware", "simple-mfd";
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		mboxes = <&mailbox>;
+		dma-ranges;
+
+		firmware_clocks: clocks {
+			compatible = "raspberrypi,firmware-clocks";
+			#clock-cells = <1>;
+		};
+
+		reset: reset {
+			compatible = "raspberrypi,firmware-reset";
+			#reset-cells = <1>;
+		};
+
+		vcio: vcio {
+			compatible = "raspberrypi,vcio";
+		};
+	};
+
+	power: power {
+		compatible = "raspberrypi,bcm2835-power";
+		firmware = <&firmware>;
+		#power-domain-cells = <1>;
+	};
+
+	fb: fb {
+		compatible = "brcm,bcm2708-fb";
+		firmware = <&firmware>;
+	};
+
+	rpi_rtc: rpi_rtc {
+		compatible = "raspberrypi,rpi-rtc";
+		firmware = <&firmware>;
+		trickle-charge-microvolt = <0>;
+	};
+
+	nvmem {
+		compatible = "simple-bus";
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		nvmem_otp: nvmem_otp {
+			compatible = "raspberrypi,rpi-otp";
+			firmware = <&firmware>;
+			reg = <0 192>;
+		};
+
+		nvmem_cust: nvmem_cust {
+			compatible = "raspberrypi,rpi-otp";
+			firmware = <&firmware>;
+			reg = <1 8>;
+		};
+
+		nvmem_mac: nvmem_mac {
+			compatible = "raspberrypi,rpi-otp";
+			firmware = <&firmware>;
+			reg = <2 6>;
+		};
+
+		nvmem_priv: nvmem_priv {
+			compatible = "raspberrypi,rpi-otp";
+			firmware = <&firmware>;
+			reg = <3 16>;
+		};
+	};
+
+	/* Define these notional regulators for use by overlays, etc. */
+	vdd_3v3_reg: fixedregulator_3v3 {
+		compatible = "regulator-fixed";
+		regulator-always-on;
+		regulator-max-microvolt = <3300000>;
+		regulator-min-microvolt = <3300000>;
+		regulator-name = "3v3";
+	};
+
+	vdd_5v0_reg: fixedregulator_5v0 {
+		compatible = "regulator-fixed";
+		regulator-always-on;
+		regulator-max-microvolt = <5000000>;
+		regulator-min-microvolt = <5000000>;
+		regulator-name = "5v0";
+	};
+};
+
+&rp1_dma {
+	snps,chan-flags = <DMA_FLAG_HEAVY DMA_FLAG_HEAVY 0 0 0 0 0 0>;
+};
+
+pio: &rp1_pio {
+	dmas = <&rp1_dma (RP1_DMA_PIO_CH0_TX | DMA_FLAG_HEAVY)>,
+		<&rp1_dma (RP1_DMA_PIO_CH0_RX | DMA_FLAG_HEAVY)>,
+		<&rp1_dma (RP1_DMA_PIO_CH1_TX | DMA_FLAG_HEAVY)>,
+		<&rp1_dma (RP1_DMA_PIO_CH1_RX | DMA_FLAG_HEAVY)>,
+		<&rp1_dma (RP1_DMA_PIO_CH2_TX | DMA_FLAG_HEAVY)>,
+		<&rp1_dma (RP1_DMA_PIO_CH2_RX | DMA_FLAG_HEAVY)>,
+		<&rp1_dma (RP1_DMA_PIO_CH3_TX | DMA_FLAG_HEAVY)>,
+		<&rp1_dma (RP1_DMA_PIO_CH3_RX | DMA_FLAG_HEAVY)>;
+	status = "okay";
+};
+
+watchdog: &pm {};
+
+/ {
+	chosen: chosen {
+		bootargs = "reboot=w coherent_pool=1M 8250.nr_uarts=1 pci=pcie_bus_safe cgroup_disable=memory numa_policy=interleave nvme.max_host_mem_size_mb=0";
+		stdout-path = "serial10:115200n8";
+	};
+
+	aliases: aliases {
+		blconfig = &blconfig;
+		blpubkey = &blpubkey;
+		bluetooth = &bluetooth;
+		console = &uart10;
+		drm-dsi1 = &dsi0;
+		drm-dsi2 = &dsi1;
+		ethernet0 = &rp1_eth;
+		fb = &fb;
+		gpio0 = &gpio;
+		gpio1 = &gio;
+		gpio2 = &gio_aon;
+		gpio3 = &pinctrl;
+		gpio4 = &pinctrl_aon;
+		gpiochip0 = &gpio;
+		gpiochip10 = &gio;
+		i2c = &i2c_arm;
+		i2c0 = &i2c0;
+		i2c1 = &i2c1;
+		i2c2 = &i2c2;
+		i2c3 = &i2c3;
+		i2c10 = &i2c_csi_dsi0;
+		i2c11 = &i2c_csi_dsi1;
+		i2c12 = &i2c10;
+		mailbox = &mailbox;
+		mmc0 = &sdio1;
+		pio0 = &pio;
+		serial0 = &uart0;
+		serial1 = &uart1;
+		serial10 = &uart10;
+		serial2 = &uart2;
+		serial3 = &uart3;
+		serial4 = &uart4;
+		spi0 = &spi0;
+		spi1 = &spi1;
+		spi10 = &spi10;
+		spi2 = &spi2;
+		spi3 = &spi3;
+		spi4 = &spi4;
+		spi5 = &spi5;
+		uart0 = &uart0;
+		uart1 = &uart1;
+		uart10 = &uart10;
+		uart2 = &uart2;
+		uart3 = &uart3;
+		uart4 = &uart4;
+		usb0 = &rp1_usb0;
+		usb1 = &rp1_usb1;
+		watchdog = &watchdog;
+		wifi0 = &wifi;
+	};
+
+	__overrides__ {
+		act_led_gpio = <&led_act>,"gpios:4",<&led_act>,"gpios:0=",<&gpio>;
+		act_led_activelow = <&led_act>, "gpios:8";
+		act_led_trigger = <&led_act>, "linux,default-trigger";
+		axiperf = <&axiperf>,"status";
+		bdaddr = <&bluetooth>, "local-bd-address[";
+		button_debounce = <&pwr_key>, "debounce-interval:0";
+		cooling_fan = <&fan>, "status", <&rp1_pwm1>, "status";
+		drm_fb0_rp1_dpi = <&aliases>, "drm-fb0=",&dpi;
+		drm_fb0_rp1_dsi0 = <&aliases>, "drm-fb0=",&dsi0;
+		drm_fb0_rp1_dsi1 = <&aliases>, "drm-fb0=",&dsi1;
+		drm_fb0_vc4 = <&aliases>, "drm-fb0=",&vc4;
+		drm_fb1_rp1_dpi = <&aliases>, "drm-fb1=",&dpi;
+		drm_fb1_rp1_dsi0 = <&aliases>, "drm-fb1=",&dsi0;
+		drm_fb1_rp1_dsi1 = <&aliases>, "drm-fb1=",&dsi1;
+		drm_fb1_vc4 = <&aliases>, "drm-fb1=",&vc4;
+		drm_fb2_rp1_dpi = <&aliases>, "drm-fb2=",&dpi;
+		drm_fb2_rp1_dsi0 = <&aliases>, "drm-fb2=",&dsi0;
+		drm_fb2_rp1_dsi1 = <&aliases>, "drm-fb2=",&dsi1;
+		drm_fb2_vc4 = <&aliases>, "drm-fb2=",&vc4;
+		eth_led0 = <&phy1>,"led-modes:0";
+		eth_led1 = <&phy1>,"led-modes:4";
+		eth_max_speed = <&phy1>,"max-speed:0";
+		fan_temp0 = <&cpu_tepid>,"temperature:0";
+		fan_temp0_hyst = <&cpu_tepid>,"hysteresis:0";
+		fan_temp0_speed = <&fan>, "cooling-levels:4";
+		fan_temp1 = <&cpu_warm>,"temperature:0";
+		fan_temp1_hyst = <&cpu_warm>,"hysteresis:0";
+		fan_temp1_speed = <&fan>, "cooling-levels:8";
+		fan_temp2 = <&cpu_hot>,"temperature:0";
+		fan_temp2_hyst = <&cpu_hot>,"hysteresis:0";
+		fan_temp2_speed = <&fan>, "cooling-levels:12";
+		fan_temp3 = <&cpu_vhot>,"temperature:0";
+		fan_temp3_hyst = <&cpu_vhot>,"hysteresis:0";
+		fan_temp3_speed = <&fan>, "cooling-levels:16";
+		i2c = <&i2c1>, "status";
+		i2c_arm = <&i2c_arm>, "status";
+		i2c_arm_baudrate = <&i2c_arm>, "clock-frequency:0";
+		i2c_baudrate = <&i2c_arm>, "clock-frequency:0";
+		i2c_csi_dsi = <&i2c_csi_dsi>, "status";
+		i2c_csi_dsi0 = <&i2c_csi_dsi0>, "status";
+		i2c_csi_dsi1 = <&i2c_csi_dsi1>, "status";
+		i2c_vc = <&i2c_vc>, "status";
+		i2c_vc_baudrate = <&i2c_vc>, "clock-frequency:0";
+		i2c0 = <&i2c0>, "status";
+		i2c0_baudrate = <&i2c0>, "clock-frequency:0";
+		i2c1 = <&i2c1>, "status";
+		i2c1_baudrate = <&i2c1>, "clock-frequency:0";
+		krnbt = <&bluetooth>, "status";
+		nvme = <&pciex1>, "status";
+		nvmem_cust_rw = <&nvmem_cust>,"rw?";
+		nvmem_mac_rw = <&nvmem_mac>,"rw?";
+		nvmem_priv_rw = <&nvmem_priv>,"rw?";
+		pcie_tperst_clk_ms = <&pciex1>, "brcm,tperst-clk-ms:0";
+		pciex1 = <&pciex1>, "status";
+		pciex1_gen = <&pciex1> , "max-link-speed:0";
+		pciex1_no_l0s = <&pciex1>, "aspm-no-l0s?";
+		pciex1_tperst_clk_ms = <&pciex1>, "brcm,tperst-clk-ms:0";
+		pwr_led_gpio = <&led_pwr>, "gpios:4";
+		pwr_led_activelow = <&led_pwr>, "gpios:8";
+		pwr_led_trigger = <&led_pwr>, "linux,default-trigger";
+		random = <&random>, "status";
+		rtc = <&rpi_rtc>, "status";
+		rtc_bbat_vchg = <&rpi_rtc>, "trickle-charge-microvolt:0";
+		spi = <&spi0>, "status";
+		strict_gpiod = <&chosen>, "bootargs=pinctrl_rp1.persist_gpio_outputs=n";
+		suspend = <&pwr_key>, "linux,code:0=205";
+		uart0 = <&uart0>, "status";
+		uart0_console = <&uart0>,"status", <&aliases>, "console=",&uart0;
+		uart0_dma = <&uart0>,"dmas:0=",<&rp1_dma>,
+			    <&uart0>,"dmas:4=",<RP1_DMA_UART0_TX>,
+			    <&uart0>,"dmas:8=",<&rp1_dma>,
+			    <&uart0>,"dmas:12=",<RP1_DMA_UART0_RX>,
+			    <&uart0>,"dma-names[=747800727800"; // "tx", "rx"
+		watchdog = <&watchdog>, "early-watchdog?";
+		wifiaddr = <&wifi>, "local-mac-address[";
+
+		cam0_reg = <&cam0_reg>,"status";
+		cam0_reg_gpio = <&cam0_reg>,"gpio:4",
+				<&cam0_reg>,"gpio:0=", <&gpio>;
+		cam1_reg = <&cam1_reg>,"status";
+		cam1_reg_gpio = <&cam1_reg>,"gpio:4",
+				<&cam1_reg>,"gpio:0=", <&gpio>;
+	};
+};
+
+pciex1: &pcie1 { };
+pciex4: &pcie2 { };
+
+&dma32 {
+	/* The VPU firmware uses DMA channel 11 for VCHIQ */
+	brcm,dma-channel-mask = <0x03f>;
+};
+
+&dma40 {
+	/* The VPU firmware DMA channel 11 for VCHIQ */
+	brcm,dma-channel-mask = <0x07c0>;
+};
+
+&hdmi0 {
+	dmas = <&dma40 (10|(1<<30)|(1<<24)|(10<<16)|(15<<20))>;
+	dma-names = "audio-rx";
+};
+
+&hdmi1 {
+	dmas = <&dma40 (17|(1<<30)|(1<<24)|(10<<16)|(15<<20))>;
+	dma-names = "audio-rx";
+};
+
+&spi10 {
+	dmas = <&dma40 6>, <&dma40 7>;
+	dma-names = "tx", "rx";
+};
+
+&usb {
+	power-domains = <&power RPI_POWER_DOMAIN_USB>;
+};
+
+&rmem {
+	/*
+	 * RPi5's co-processor will copy the board's bootloader configuration
+	 * into memory for the OS to consume. It'll also update this node with
+	 * its placement information.
+	 */
+	blconfig: nvram@0 {
+		compatible = "raspberrypi,bootloader-config", "nvmem-rmem";
+		#address-cells = <1>;
+		#size-cells = <1>;
+#ifndef FIRMWARE_UPDATED
+		reg = <0x0 0x0 0x0>;
+#else
+		reg = <0x0 0x0 0x0 0x0>;
+#endif
+		no-map;
+		status = "disabled";
+	};
+	/*
+	 * RPi5 will copy the binary public key blob (if present) from the bootloader
+	 * into memory for use by the OS.
+	 */
+	blpubkey: nvram@1 {
+		compatible = "raspberrypi,bootloader-public-key", "nvmem-rmem";
+		#address-cells = <1>;
+		#size-cells = <1>;
+#ifndef FIRMWARE_UPDATED
+		reg = <0x0 0x0 0x0>;
+#else
+		reg = <0x0 0x0 0x0 0x0>;
+#endif
+		no-map;
+		status = "disabled";
+	};
+};
+
+&rp1_adc {
+	status = "okay";
+};
+
+&rp1_mbox {
+	status = "okay";
+};
+
+/* Add some gpiomem nodes to make the devices accessible to userspace.
+ * /dev/gpiomem<n> should expose the registers for the interface with DT alias
+ * gpio<n>.
+ */
+
+&rp1 {
+	gpiomem@d0000 {
+		/* Export IO_BANKs, RIO_BANKs and PADS_BANKs to userspace */
+		compatible = "raspberrypi,gpiomem";
+		reg = <0xc0 0x400d0000  0x0 0x30000>;
+		chardev-name = "gpiomem0";
+	};
+};
+
+&soc {
+	gpiomem@7d508500 {
+		compatible = "raspberrypi,gpiomem";
+		reg = <0x7d508500 0x40>;
+		chardev-name = "gpiomem1";
+	};
+
+	gpiomem@7d517c00 {
+		compatible = "raspberrypi,gpiomem";
+		reg = <0x7d517c00 0x40>;
+		chardev-name = "gpiomem2";
+	};
+
+	gpiomem@7d504100 {
+		compatible = "raspberrypi,gpiomem";
+		reg = <0x7d504100 0x20>;
+		chardev-name = "gpiomem3";
+	};
+
+	gpiomem@7d510700 {
+		compatible = "raspberrypi,gpiomem";
+		reg = <0x7d510700 0x20>;
+		chardev-name = "gpiomem4";
+	};
+
+	sound: sound {
+		status = "disabled";
+	};
+};
+
+i2c0: &rp1_i2c0 { };
+i2c1: &rp1_i2c1 { };
+i2c2: &rp1_i2c2 { };
+i2c3: &rp1_i2c3 { };
+i2c4: &rp1_i2c4 { };
+i2c5: &rp1_i2c5 { };
+i2c6: &rp1_i2c6 { };
+i2s:  &rp1_i2s0 { };
+i2s_clk_producer: &rp1_i2s0 { };
+i2s_clk_consumer: &rp1_i2s1 { };
+pwm0: &rp1_pwm0 { };
+pwm1: &rp1_pwm1 { };
+pwm: &pwm0 { };
+spi0: &rp1_spi0 { };
+spi1: &rp1_spi1 { };
+spi2: &rp1_spi2 { };
+spi3: &rp1_spi3 { };
+spi4: &rp1_spi4 { };
+spi5: &rp1_spi5 { };
+
+uart0_pins: &rp1_uart0_14_15 {};
+uart0_ctsrts_pins: &rp1_uart0_ctsrts_16_17 {};
+uart0: &rp1_uart0 {
+	pinctrl-0 = <&uart0_pins>;
+};
+
+uart1_pins: &rp1_uart1_0_1 {};
+uart1_ctsrts_pins: &rp1_uart1_ctsrts_2_3 {};
+uart1: &rp1_uart1 { };
+
+uart2_pins: &rp1_uart2_4_5 {};
+uart2_ctsrts_pins: &rp1_uart2_ctsrts_6_7 {};
+uart2: &rp1_uart2 { };
+
+uart3_pins: &rp1_uart3_8_9 {};
+uart3_ctsrts_pins: &rp1_uart3_ctsrts_10_11 {};
+uart3: &rp1_uart3 { };
+
+uart4_pins: &rp1_uart4_12_13 {};
+uart4_ctsrts_pins: &rp1_uart4_ctsrts_14_15 {};
+uart4: &rp1_uart4 { };
+
+i2c0_pins: &rp1_i2c0_0_1 {};
+i2c_vc: &i2c0 {      // This is pins 27,28 on the header (not MIPI)
+	pinctrl-0 = <&i2c0_pins>;
+	pinctrl-names = "default";
+	clock-frequency = <100000>;
+};
+
+i2c1_pins: &rp1_i2c1_2_3 {};
+i2c_arm: &i2c1 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c1_pins>;
+	clock-frequency = <100000>;
+};
+
+i2c2_pins: &rp1_i2c2_4_5 {};
+&i2c2 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c2_pins>;
+};
+
+i2c3_pins: &rp1_i2c3_6_7 {};
+&i2c3 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&i2c3_pins>;
+};
+
+&i2s_clk_producer {
+	pinctrl-names = "default";
+	pinctrl-0 = <&rp1_i2s0_18_21>;
+};
+
+&i2s_clk_consumer {
+	pinctrl-names = "default";
+	pinctrl-0 = <&rp1_i2s1_18_21>;
+};
+
+spi0_pins: &rp1_spi0_gpio9 {};
+spi0_cs_pins: &rp1_spi0_cs_gpio7 {};
+
+&spi0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
+	cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
+
+	spidev0: spidev@0 {
+		compatible = "spidev";
+		reg = <0>;	/* CE0 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+
+	spidev1: spidev@1 {
+		compatible = "spidev";
+		reg = <1>;	/* CE1 */
+		#address-cells = <1>;
+		#size-cells = <0>;
+		spi-max-frequency = <125000000>;
+	};
+};
+
+spi2_pins: &rp1_spi2_gpio1 {};
+&spi2 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi2_pins>;
+};
+
+spi3_pins: &rp1_spi3_gpio5 {};
+&spi3 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi3_pins>;
+};
+
+spi4_pins: &rp1_spi4_gpio9 {};
+&spi4 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi4_pins>;
+};
+
+spi5_pins: &rp1_spi5_gpio13 {};
+&spi5 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&spi5_pins>;
+};
diff --git a/arch/arm64/boot/dts/broadcom/bcm2712.dtsi b/arch/arm64/boot/dts/broadcom/bcm2712.dtsi
index 209f99b1ceae..59a4b224f7f0 100644
--- a/arch/arm64/boot/dts/broadcom/bcm2712.dtsi
+++ b/arch/arm64/boot/dts/broadcom/bcm2712.dtsi
@@ -192,6 +192,12 @@ soc: soc@107c000000 {
 		#address-cells = <1>;
 		#size-cells = <1>;
 
+		pcie_rescal: reset-controller@119500 {
+			compatible = "brcm,bcm7216-pcie-sata-rescal";
+			reg = <0x00119500 0x10>;
+			#reset-cells = <0>;
+		};
+
 		sdio1: mmc@fff000 {
 			compatible = "brcm,bcm2712-sdhci",
 				     "brcm,sdhci-brcmstb";
@@ -204,6 +210,12 @@ sdio1: mmc@fff000 {
 			mmc-ddr-3_3v;
 		};
 
+		bcm_reset: reset-controller@1504318 {
+			compatible = "brcm,brcmstb-reset";
+			reg = <0x01504318 0x30>;
+			#reset-cells = <1>;
+		};
+
 		system_timer: timer@7c003000 {
 			compatible = "brcm,bcm2835-system-timer";
 			reg = <0x7c003000 0x1000>;
@@ -268,6 +280,307 @@ gicv2: interrupt-controller@7fff9000 {
 					IRQ_TYPE_LEVEL_HIGH)>;
 			#interrupt-cells = <3>;
 		};
+
+		aon_intr: interrupt-controller@7d510600 {
+			compatible = "brcm,bcm2711-l2-intc", "brcm,l2-intc";
+			reg = <0x7d510600 0x30>;
+			interrupts = <GIC_SPI 239 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-controller;
+			#interrupt-cells = <1>;
+		};
+
+		pixelvalve0: pixelvalve@7c410000 {
+			compatible = "brcm,bcm2712-pixelvalve0";
+			reg = <0x7c410000 0x100>;
+			interrupts = <GIC_SPI 101 IRQ_TYPE_LEVEL_HIGH>;
+		};
+
+		pixelvalve1: pixelvalve@7c411000 {
+			compatible = "brcm,bcm2712-pixelvalve1";
+			reg = <0x7c411000 0x100>;
+			interrupts = <GIC_SPI 110 IRQ_TYPE_LEVEL_HIGH>;
+		};
+
+		mop: mop@7c500000 {
+			compatible = "brcm,bcm2712-mop";
+			reg = <0x7c500000 0x28>;
+			interrupt-parent = <&disp_intr>;
+			interrupts = <1>;
+		};
+
+		moplet: moplet@7c501000 {
+			compatible = "brcm,bcm2712-moplet";
+			reg = <0x7c501000 0x20>;
+			interrupt-parent = <&disp_intr>;
+			interrupts = <0>;
+		};
+
+		disp_intr: interrupt-controller@7c502000 {
+			compatible = "brcm,bcm2711-l2-intc", "brcm,l2-intc";
+			reg = <0x7c502000 0x30>;
+			interrupts = <GIC_SPI 97 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-controller;
+			#interrupt-cells = <1>;
+		};
+
+		dvp: clock@7c700000 {
+			compatible = "brcm,brcm2711-dvp";
+			reg = <0x7c700000 0x10>;
+			clocks = <&clk_108MHz>;
+			#clock-cells = <1>;
+			#reset-cells = <1>;
+		};
+
+		ddc0: i2c@7d508200 {
+			compatible = "brcm,brcmstb-i2c";
+			reg = <0x7d508200 0x58>;
+			interrupt-parent = <&bsc_irq>;
+			interrupts = <1>;
+			clock-frequency = <97500>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+		};
+
+		ddc1: i2c@7d508280 {
+			compatible = "brcm,brcmstb-i2c";
+			reg = <0x7d508280 0x58>;
+			interrupt-parent = <&bsc_irq>;
+			interrupts = <2>;
+			clock-frequency = <97500>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+		};
+
+		bsc_irq: intc@7d508380 {
+			compatible = "brcm,bcm7271-l2-intc";
+			reg = <0x7d508380 0x10>;
+			interrupts = <GIC_SPI 242 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-controller;
+			#interrupt-cells = <1>;
+		};
+
+		main_irq: intc@7d508400 {
+			compatible = "brcm,bcm7271-l2-intc";
+			reg = <0x7d508400 0x10>;
+			interrupts = <GIC_SPI 244 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-controller;
+			#interrupt-cells = <1>;
+		};
+
+		hdmi0: hdmi@7ef00700 {
+			compatible = "brcm,bcm2712-hdmi0";
+			reg = <0x7c701400 0x300>,
+			      <0x7c701000 0x200>,
+			      <0x7c701d00 0x300>,
+			      <0x7c702000 0x80>,
+			      <0x7c703800 0x200>,
+			      <0x7c704000 0x800>,
+			      <0x7c700100 0x80>,
+			      <0x7d510800 0x100>,
+			      <0x7c720000 0x100>;
+			reg-names = "hdmi",
+				    "dvp",
+				    "phy",
+				    "rm",
+				    "packet",
+				    "metadata",
+				    "csc",
+				    "cec",
+				    "hd";
+			resets = <&dvp 1>;
+			interrupt-parent = <&aon_intr>;
+			interrupts = <1>, <2>, <3>,
+				     <7>, <8>;
+			interrupt-names = "cec-tx", "cec-rx", "cec-low",
+					  "hpd-connected", "hpd-removed";
+			ddc = <&ddc0>;
+		};
+
+		hdmi1: hdmi@7ef05700 {
+			compatible = "brcm,bcm2712-hdmi1";
+			reg = <0x7c706400 0x300>,
+			      <0x7c706000 0x200>,
+			      <0x7c706d00 0x300>,
+			      <0x7c707000 0x80>,
+			      <0x7c708800 0x200>,
+			      <0x7c709000 0x800>,
+			      <0x7c700180 0x80>,
+			      <0x7d511000 0x100>,
+			      <0x7c720000 0x100>;
+			reg-names = "hdmi",
+				    "dvp",
+				    "phy",
+				    "rm",
+				    "packet",
+				    "metadata",
+				    "csc",
+				    "cec",
+				    "hd";
+			resets = <&dvp 2>;
+			interrupt-parent = <&aon_intr>;
+			interrupts = <11>, <12>, <13>,
+				     <14>, <15>;
+			interrupt-names = "cec-tx", "cec-rx", "cec-low",
+					  "hpd-connected", "hpd-removed";
+			ddc = <&ddc1>;
+		};
+	};
+
+	axi: axi {
+		compatible = "simple-bus";
+		#address-cells = <2>;
+		#size-cells = <2>;
+
+		ranges = <0x00 0x00000000  0x00 0x00000000  0x10 0x00000000>,
+			 <0x10 0x00000000  0x10 0x00000000  0x01 0x00000000>,
+			 <0x14 0x00000000  0x14 0x00000000  0x04 0x00000000>,
+			 <0x18 0x00000000  0x18 0x00000000  0x04 0x00000000>,
+			 <0x1c 0x00000000  0x1c 0x00000000  0x04 0x00000000>;
+
+		dma-ranges = <0x00 0x00000000  0x00 0x00000000  0x10 0x00000000>,
+			     <0x10 0x00000000  0x10 0x00000000  0x01 0x00000000>,
+			     <0x14 0x00000000  0x14 0x00000000  0x04 0x00000000>,
+			     <0x18 0x00000000  0x18 0x00000000  0x04 0x00000000>,
+			     <0x1c 0x00000000  0x1c 0x00000000  0x04 0x00000000>;
+
+		vc4: gpu {
+			compatible = "brcm,bcm2712-vc6";
+		};
+
+		pcie0: pcie@1000100000 {
+			compatible = "brcm,bcm2712-pcie";
+			reg = <0x10 0x00100000 0x00 0x9310>;
+			device_type = "pci";
+			linux,pci-domain = <0>;
+			max-link-speed = <2>;
+			num-lanes = <1>;
+			#address-cells = <3>;
+			#interrupt-cells = <1>;
+			#size-cells = <2>;
+			interrupt-parent = <&gicv2>;
+			interrupts = <GIC_SPI 213 IRQ_TYPE_LEVEL_HIGH>,
+				     <GIC_SPI 214 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "pcie", "msi";
+			interrupt-map-mask = <0x0 0x0 0x0 0x7>;
+			interrupt-map = <0 0 0 1 &gicv2 GIC_SPI 209 IRQ_TYPE_LEVEL_HIGH>,
+					<0 0 0 2 &gicv2 GIC_SPI 210 IRQ_TYPE_LEVEL_HIGH>,
+					<0 0 0 3 &gicv2 GIC_SPI 211 IRQ_TYPE_LEVEL_HIGH>,
+					<0 0 0 4 &gicv2 GIC_SPI 212 IRQ_TYPE_LEVEL_HIGH>;
+			resets = <&pcie_rescal>, <&bcm_reset 42>;
+			reset-names = "rescal", "bridge";
+			msi-controller;
+			msi-parent = <&pcie0>;
+
+			ranges =
+				/* ~4GiB, 32-bit, non-prefetchable at PCIe 00_0000_0000 */
+				<0x02000000 0x00 0x00000000 0x17 0x00000000 0x00 0xfffffffc>,
+				/* 12GiB, 64-bit, prefetchable at PCIe 04_0000_0000 */
+				<0x43000000 0x04 0x00000000 0x14 0x00000000 0x03 0x00000000>;
+
+			dma-ranges =
+				/* 64GiB, 64-bit, prefetchable at PCIe 10_0000_0000 */
+				<0x43000000 0x10 0x00000000 0x00 0x00000000 0x10 0x00000000>;
+
+			status = "disabled";
+		};
+
+		pcie1: pcie@1000110000 {
+			compatible = "brcm,bcm2712-pcie";
+			reg = <0x10 0x00110000 0x00 0x9310>;
+			device_type = "pci";
+			linux,pci-domain = <1>;
+			max-link-speed = <2>;
+			num-lanes = <1>;
+			#address-cells = <3>;
+			#interrupt-cells = <1>;
+			#size-cells = <2>;
+			interrupt-parent = <&gicv2>;
+			interrupts = <GIC_SPI 223 IRQ_TYPE_LEVEL_HIGH>,
+				     <GIC_SPI 224 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "pcie", "msi";
+			interrupt-map-mask = <0x0 0x0 0x0 0x7>;
+			interrupt-map = <0 0 0 1 &gicv2 GIC_SPI 219 IRQ_TYPE_LEVEL_HIGH>,
+					<0 0 0 2 &gicv2 GIC_SPI 220 IRQ_TYPE_LEVEL_HIGH>,
+					<0 0 0 3 &gicv2 GIC_SPI 221 IRQ_TYPE_LEVEL_HIGH>,
+					<0 0 0 4 &gicv2 GIC_SPI 222 IRQ_TYPE_LEVEL_HIGH>;
+			resets = <&pcie_rescal>, <&bcm_reset 43>;
+			reset-names = "rescal", "bridge";
+			msi-controller;
+			msi-parent = <&mip1>;
+
+			ranges =
+				/* ~4GiB, 32-bit, non-prefetchable at PCIe 00_0000_0000 */
+				<0x02000000 0x00 0x00000000 0x1b 0x00000000 0x00 0xfffffffc>,
+				/* 12GiB, 64-bit, prefetchable at PCIe 04_0000_0000 */
+				<0x43000000 0x04 0x00000000 0x18 0x00000000 0x03 0x00000000>;
+
+			dma-ranges =
+				/* 64GiB, 64-bit, non-prefetchable at PCIe 10_0000_0000 */
+				<0x03000000 0x10 0x00000000 0x00 0x00000000 0x10 0x00000000>,
+				/* 4KiB, 64-bit, non-prefetchable at PCIe ff_ffff_f000 MIP1 */
+				<0x03000000 0xff 0xfffff000 0x10 0x00131000 0x00 0x00001000>;
+
+			status = "disabled";
+		};
+
+		pcie2: pcie@1000120000 {
+			compatible = "brcm,bcm2712-pcie";
+			reg = <0x10 0x00120000 0x00 0x9310>;
+			device_type = "pci";
+			linux,pci-domain = <2>;
+			max-link-speed = <2>;
+			num-lanes = <4>;
+			#address-cells = <3>;
+			#interrupt-cells = <1>;
+			#size-cells = <2>;
+			interrupt-parent = <&gicv2>;
+			interrupts = <GIC_SPI 233 IRQ_TYPE_LEVEL_HIGH>,
+				     <GIC_SPI 234 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "pcie", "msi";
+			interrupt-map-mask = <0x0 0x0 0x0 0x7>;
+			interrupt-map = <0 0 0 1 &gicv2 GIC_SPI 229 IRQ_TYPE_LEVEL_HIGH>,
+					<0 0 0 2 &gicv2 GIC_SPI 230 IRQ_TYPE_LEVEL_HIGH>,
+					<0 0 0 3 &gicv2 GIC_SPI 231 IRQ_TYPE_LEVEL_HIGH>,
+					<0 0 0 4 &gicv2 GIC_SPI 232 IRQ_TYPE_LEVEL_HIGH>;
+			resets = <&pcie_rescal>, <&bcm_reset 44>;
+			reset-names = "rescal", "bridge";
+			msi-controller;
+			msi-parent = <&mip0>;
+
+			ranges =
+				/* ~4GiB, 32-bit, non-prefetchable at PCIe 00_0000_0000 */
+				<0x02000000 0x00 0x00000000 0x1f 0x00000000 0x00 0xfffffffc>,
+				/* 12GiB, 64-bit, prefetchable at PCIe 04_0000_0000 */
+				<0x43000000 0x04 0x00000000 0x1c 0x00000000 0x03 0x00000000>;
+
+			dma-ranges =
+				/* 4MiB, 32-bit, non-prefetchable at PCIe 00_0000_0000 */
+				<0x02000000 0x00 0x00000000 0x1f 0x00000000 0x00 0x00400000>,
+				/* 64GiB, 64-bit, prefetchable at PCIe 10_0000_0000 */
+				<0x43000000 0x10 0x00000000 0x00 0x00000000 0x10 0x00000000>,
+				/* 4KiB, 64-bit, non-prefetchable at PCIe ff_ffff_f000 MIP0 */
+				<0x03000000 0xff 0xfffff000 0x10 0x00130000 0x00 0x00001000>;
+
+			status = "disabled";
+		};
+
+		mip0: msi-controller@1000130000 {
+			compatible = "brcm,bcm2712-mip";
+			reg = <0x10 0x00130000 0x00 0xc0>,
+			      <0xff 0xfffff000 0x00 0x1000>;
+			msi-controller;
+			msi-ranges = <&gicv2 GIC_SPI 128 IRQ_TYPE_EDGE_RISING 64>;
+			brcm,msi-offset = <0>;
+		};
+
+		mip1: msi-controller@1000131000 {
+			compatible = "brcm,bcm2712-mip";
+			reg = <0x10 0x00131000 0x00 0xc0>,
+			      <0xff 0xfffff000 0x00 0x1000>;
+			msi-controller;
+			msi-ranges = <&gicv2 GIC_SPI 247 IRQ_TYPE_EDGE_RISING 8>;
+			brcm,msi-offset = <8>;
+		};
 	};
 
 	timer {
@@ -283,4 +596,31 @@ IRQ_TYPE_LEVEL_LOW)>,
 			     <GIC_PPI 12 (GIC_CPU_MASK_SIMPLE(4) |
 					  IRQ_TYPE_LEVEL_LOW)>;
 	};
+
+	clk_27MHz: clk-27M {
+		#clock-cells = <0>;
+		compatible = "fixed-clock";
+		clock-frequency = <27000000>;
+		clock-output-names = "27MHz-clock";
+	};
+
+	clk_108MHz: clk-108M {
+		#clock-cells = <0>;
+		compatible = "fixed-clock";
+		clock-frequency = <108000000>;
+		clock-output-names = "108MHz-clock";
+	};
+
+	hvs: hvs@107c580000 {
+		compatible = "brcm,bcm2712-hvs";
+#ifndef FIRMWARE_UPDATED
+		reg = <0x10 0x7c580000 0x1a000>;
+#else
+		reg = <0x10 0x7c580000 0x0 0x1a000>;
+#endif
+		interrupt-parent = <&disp_intr>;
+		interrupts = <2>, <9>, <16>;
+		interrupt-names = "ch0-eof", "ch1-eof", "ch2-eof";
+		iommus = <&iommu4>;
+	};
 };
diff --git a/arch/arm64/boot/dts/broadcom/bcm2712d0-rpi-5-b.dts b/arch/arm64/boot/dts/broadcom/bcm2712d0-rpi-5-b.dts
new file mode 100644
index 000000000000..d06536bc7592
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/bcm2712d0-rpi-5-b.dts
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "bcm2712-rpi-5-b.dts"
+
+&gio {
+	brcm,gpio-bank-widths = <32 4>;
+
+	gpio-line-names =
+		"", // GPIO_000
+		"2712_BOOT_CS_N", // GPIO_001
+		"2712_BOOT_MISO", // GPIO_002
+		"2712_BOOT_MOSI", // GPIO_003
+		"2712_BOOT_SCLK", // GPIO_004
+		"", // GPIO_005
+		"", // GPIO_006
+		"", // GPIO_007
+		"", // GPIO_008
+		"", // GPIO_009
+		"-", // GPIO_010
+		"-", // GPIO_011
+		"-", // GPIO_012
+		"-", // GPIO_013
+		"PCIE_SDA", // GPIO_014
+		"PCIE_SCL", // GPIO_015
+		"", // GPIO_016
+		"", // GPIO_017
+		"-", // GPIO_018
+		"-", // GPIO_019
+		"PWR_GPIO", // GPIO_020
+		"2712_G21_FS", // GPIO_021
+		"-", // GPIO_022
+		"-", // GPIO_023
+		"BT_RTS", // GPIO_024
+		"BT_CTS", // GPIO_025
+		"BT_TXD", // GPIO_026
+		"BT_RXD", // GPIO_027
+		"WL_ON", // GPIO_028
+		"BT_ON", // GPIO_029
+		"WIFI_SDIO_CLK", // GPIO_030
+		"WIFI_SDIO_CMD", // GPIO_031
+		"WIFI_SDIO_D0", // GPIO_032
+		"WIFI_SDIO_D1", // GPIO_033
+		"WIFI_SDIO_D2", // GPIO_034
+		"WIFI_SDIO_D3"; // GPIO_035
+};
+
+&gio_aon {
+	brcm,gpio-bank-widths = <15 6>;
+
+	gpio-line-names =
+		"RP1_SDA", // AON_GPIO_00
+		"RP1_SCL", // AON_GPIO_01
+		"RP1_RUN", // AON_GPIO_02
+		"SD_IOVDD_SEL", // AON_GPIO_03
+		"SD_PWR_ON", // AON_GPIO_04
+		"SD_CDET_N", // AON_GPIO_05
+		"SD_FLG_N", // AON_GPIO_06
+		"", // AON_GPIO_07
+		"2712_WAKE", // AON_GPIO_08
+		"2712_STAT_LED", // AON_GPIO_09
+		"", // AON_GPIO_10
+		"", // AON_GPIO_11
+		"PMIC_INT", // AON_GPIO_12
+		"UART_TX_FS", // AON_GPIO_13
+		"UART_RX_FS", // AON_GPIO_14
+		"", // AON_GPIO_15
+		"", // AON_GPIO_16
+
+		// Pad bank0 out to 32 entries
+		"", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
+
+		"HDMI0_SCL", // AON_SGPIO_00
+		"HDMI0_SDA", // AON_SGPIO_01
+		"HDMI1_SCL", // AON_SGPIO_02
+		"HDMI1_SDA", // AON_SGPIO_03
+		"PMIC_SCL", // AON_SGPIO_04
+		"PMIC_SDA"; // AON_SGPIO_05
+};
+
+&pinctrl {
+	compatible = "brcm,bcm2712d0-pinctrl";
+	reg = <0x7d504100 0x20>;
+};
+
+&pinctrl_aon {
+	compatible = "brcm,bcm2712d0-aon-pinctrl";
+	reg = <0x7d510700 0x1c>;
+};
+
+&vc4 {
+	compatible = "brcm,bcm2712d0-vc6", "brcm,bcm2712-vc6";
+};
+
+&uart10 {
+	interrupts = <GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>;
+};
+
+&spi10 {
+	dmas = <&dma40 3>, <&dma40 4>;
+};
+
+&hdmi0 {
+	dmas = <&dma40 (12|(1<<30)|(1<<24)|(10<<16)|(15<<20))>;
+};
+
+&hdmi1 {
+	dmas = <&dma40 (13|(1<<30)|(1<<24)|(10<<16)|(15<<20))>;
+};
diff --git a/arch/arm64/boot/dts/broadcom/rp1.dtsi b/arch/arm64/boot/dts/broadcom/rp1.dtsi
new file mode 100644
index 000000000000..6a29960db576
--- /dev/null
+++ b/arch/arm64/boot/dts/broadcom/rp1.dtsi
@@ -0,0 +1,1343 @@
+#include <dt-bindings/clock/rp1.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/mfd/rp1.h>
+
+&rp1_target {
+	rp1: rp1 {
+		compatible = "simple-bus";
+		#address-cells = <2>;
+		#size-cells = <2>;
+		#interrupt-cells = <2>;
+		interrupt-controller;
+		interrupt-parent = <&rp1>;
+
+		// ranges and dma-ranges must be provided by the includer
+
+		rp1_mbox: mailbox@8000 {
+			compatible = "raspberrypi,rp1-mbox";
+			status = "disabled";
+			reg = <0xc0 0x40008000  0x0 0x4000>;  // SYSCFG
+			interrupts = <RP1_INT_SYSCFG IRQ_TYPE_LEVEL_HIGH>;
+			#mbox-cells = <1>;
+		};
+
+		rp1_clocks: clocks@18000 {
+			compatible = "raspberrypi,rp1-clocks";
+			#clock-cells = <1>;
+			reg = <0xc0 0x40018000 0x0 0x10038>;
+			clocks = <&clk_xosc>;
+
+			assigned-clocks = <&rp1_clocks RP1_PLL_SYS_CORE>,
+					  <&rp1_clocks RP1_PLL_AUDIO_CORE>,
+					  // RP1_PLL_VIDEO_CORE and dividers are now managed by VEC,DPI drivers
+					  <&rp1_clocks RP1_PLL_SYS>,
+					  <&rp1_clocks RP1_PLL_SYS_SEC>,
+					  <&rp1_clocks RP1_CLK_ETH>,
+					  <&rp1_clocks RP1_PLL_AUDIO>,
+					  <&rp1_clocks RP1_PLL_AUDIO_SEC>,
+					  <&rp1_clocks RP1_CLK_SYS>,
+					  <&rp1_clocks RP1_PLL_SYS_PRI_PH>,
+					  // RP1_CLK_SLOW_SYS is used for the frequency counter (FC0)
+					  <&rp1_clocks RP1_CLK_SLOW_SYS>,
+					  <&rp1_clocks RP1_CLK_SDIO_TIMER>,
+					  <&rp1_clocks RP1_CLK_SDIO_ALT_SRC>,
+					  <&rp1_clocks RP1_CLK_ETH_TSU>;
+
+			assigned-clock-rates = <1000000000>, // RP1_PLL_SYS_CORE
+					       <1536000000>, // RP1_PLL_AUDIO_CORE
+					       <200000000>,  // RP1_PLL_SYS
+					       <125000000>,  // RP1_PLL_SYS_SEC
+					       <125000000>,  // RP1_CLK_ETH
+					       <61440000>,   // RP1_PLL_AUDIO
+					       <153600000>,  // RP1_PLL_AUDIO_SEC
+					       <200000000>,  // RP1_CLK_SYS
+					       <100000000>,  // RP1_PLL_SYS_PRI_PH
+					       // Must match the XOSC frequency
+					       <50000000>, // RP1_CLK_SLOW_SYS
+					       <1000000>, // RP1_CLK_SDIO_TIMER
+					       <200000000>, // RP1_CLK_SDIO_ALT_SRC
+					       <50000000>; // RP1_CLK_ETH_TSU
+		};
+
+		rp1_uart0: serial@30000 {
+			compatible = "arm,pl011-axi";
+			reg = <0xc0 0x40030000  0x0 0x100>;
+			interrupts = <RP1_INT_UART0 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_UART &rp1_clocks RP1_PLL_SYS_PRI_PH>;
+			clock-names = "uartclk", "apb_pclk";
+			// dmas = <&rp1_dma RP1_DMA_UART0_TX>,
+			//        <&rp1_dma RP1_DMA_UART0_RX>;
+			// dma-names = "tx", "rx";
+			pinctrl-names = "default";
+			arm,primecell-periphid = <0x00341011>;
+			uart-has-rtscts;
+			cts-event-workaround;
+			skip-init;
+			status = "disabled";
+		};
+
+		rp1_uart1: serial@34000 {
+			compatible = "arm,pl011-axi";
+			reg = <0xc0 0x40034000  0x0 0x100>;
+			interrupts = <RP1_INT_UART1 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_UART &rp1_clocks RP1_PLL_SYS_PRI_PH>;
+			clock-names = "uartclk", "apb_pclk";
+			// dmas = <&rp1_dma RP1_DMA_UART1_TX>,
+			//        <&rp1_dma RP1_DMA_UART1_RX>;
+			// dma-names = "tx", "rx";
+			pinctrl-names = "default";
+			arm,primecell-periphid = <0x00341011>;
+			uart-has-rtscts;
+			cts-event-workaround;
+			skip-init;
+			status = "disabled";
+		};
+
+		rp1_uart2: serial@38000 {
+			compatible = "arm,pl011-axi";
+			reg = <0xc0 0x40038000  0x0 0x100>;
+			interrupts = <RP1_INT_UART2 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_UART &rp1_clocks RP1_PLL_SYS_PRI_PH>;
+			clock-names = "uartclk", "apb_pclk";
+			// dmas = <&rp1_dma RP1_DMA_UART2_TX>,
+			//        <&rp1_dma RP1_DMA_UART2_RX>;
+			// dma-names = "tx", "rx";
+			pinctrl-names = "default";
+			arm,primecell-periphid = <0x00341011>;
+			uart-has-rtscts;
+			cts-event-workaround;
+			skip-init;
+			status = "disabled";
+		};
+
+		rp1_uart3: serial@3c000 {
+			compatible = "arm,pl011-axi";
+			reg = <0xc0 0x4003c000  0x0 0x100>;
+			interrupts = <RP1_INT_UART3 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_UART &rp1_clocks RP1_PLL_SYS_PRI_PH>;
+			clock-names = "uartclk", "apb_pclk";
+			// dmas = <&rp1_dma RP1_DMA_UART3_TX>,
+			//        <&rp1_dma RP1_DMA_UART3_RX>;
+			// dma-names = "tx", "rx";
+			pinctrl-names = "default";
+			arm,primecell-periphid = <0x00341011>;
+			uart-has-rtscts;
+			cts-event-workaround;
+			skip-init;
+			status = "disabled";
+		};
+
+		rp1_uart4: serial@40000 {
+			compatible = "arm,pl011-axi";
+			reg = <0xc0 0x40040000  0x0 0x100>;
+			interrupts = <RP1_INT_UART4 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_UART &rp1_clocks RP1_PLL_SYS_PRI_PH>;
+			clock-names = "uartclk", "apb_pclk";
+			// dmas = <&rp1_dma RP1_DMA_UART4_TX>,
+			//        <&rp1_dma RP1_DMA_UART4_RX>;
+			// dma-names = "tx", "rx";
+			pinctrl-names = "default";
+			arm,primecell-periphid = <0x00341011>;
+			uart-has-rtscts;
+			cts-event-workaround;
+			skip-init;
+			status = "disabled";
+		};
+
+		rp1_uart5: serial@44000 {
+			compatible = "arm,pl011-axi";
+			reg = <0xc0 0x40044000  0x0 0x100>;
+			interrupts = <RP1_INT_UART5 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_UART &rp1_clocks RP1_PLL_SYS_PRI_PH>;
+			clock-names = "uartclk", "apb_pclk";
+			// dmas = <&rp1_dma RP1_DMA_UART5_TX>,
+			//        <&rp1_dma RP1_DMA_UART5_RX>;
+			// dma-names = "tx", "rx";
+			pinctrl-names = "default";
+			arm,primecell-periphid = <0x00341011>;
+			uart-has-rtscts;
+			cts-event-workaround;
+			skip-init;
+			status = "disabled";
+		};
+
+		rp1_spi8: spi@4c000 {
+			reg = <0xc0 0x4004c000  0x0 0x130>;
+			compatible = "snps,dw-apb-ssi";
+			interrupts = <RP1_INT_SPI8 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_SYS>;
+			clock-names = "ssi_clk";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			num-cs = <2>;
+			dmas = <&rp1_dma RP1_DMA_SPI8_TX>,
+			       <&rp1_dma RP1_DMA_SPI8_RX>;
+			dma-names = "tx", "rx";
+			status = "disabled";
+		};
+
+		rp1_spi0: spi@50000 {
+			reg = <0xc0 0x40050000  0x0 0x130>;
+			compatible = "snps,dw-apb-ssi";
+			interrupts = <RP1_INT_SPI0 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_SYS>;
+			clock-names = "ssi_clk";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			num-cs = <2>;
+			dmas = <&rp1_dma RP1_DMA_SPI0_TX>,
+			       <&rp1_dma RP1_DMA_SPI0_RX>;
+			dma-names = "tx", "rx";
+			status = "disabled";
+		};
+
+		rp1_spi1: spi@54000 {
+			reg = <0xc0 0x40054000  0x0 0x130>;
+			compatible = "snps,dw-apb-ssi";
+			interrupts = <RP1_INT_SPI1 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_SYS>;
+			clock-names = "ssi_clk";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			num-cs = <2>;
+			dmas = <&rp1_dma RP1_DMA_SPI1_TX>,
+			       <&rp1_dma RP1_DMA_SPI1_RX>;
+			dma-names = "tx", "rx";
+			status = "disabled";
+		};
+
+		rp1_spi2: spi@58000 {
+			reg = <0xc0 0x40058000  0x0 0x130>;
+			compatible = "snps,dw-apb-ssi";
+			interrupts = <RP1_INT_SPI2 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_SYS>;
+			clock-names = "ssi_clk";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			num-cs = <2>;
+			dmas = <&rp1_dma RP1_DMA_SPI2_TX>,
+			       <&rp1_dma RP1_DMA_SPI2_RX>;
+			dma-names = "tx", "rx";
+			status = "disabled";
+		};
+
+		rp1_spi3: spi@5c000 {
+			reg = <0xc0 0x4005c000  0x0 0x130>;
+			compatible = "snps,dw-apb-ssi";
+			interrupts = <RP1_INT_SPI3 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_SYS>;
+			clock-names = "ssi_clk";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			num-cs = <2>;
+			dmas = <&rp1_dma RP1_DMA_SPI3_TX>,
+			       <&rp1_dma RP1_DMA_SPI3_RX>;
+			dma-names = "tx", "rx";
+			status = "disabled";
+		};
+
+		// SPI4 is a target/slave interface
+		rp1_spi4: spi@60000 {
+			reg = <0xc0 0x40060000  0x0 0x130>;
+			compatible = "snps,dw-apb-ssi";
+			interrupts = <RP1_INT_SPI4 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_SYS>;
+			clock-names = "ssi_clk";
+			#address-cells = <0>;
+			#size-cells = <0>;
+			num-cs = <1>;
+			spi-slave;
+			dmas = <&rp1_dma RP1_DMA_SPI4_TX>,
+			       <&rp1_dma RP1_DMA_SPI4_RX>;
+			dma-names = "tx", "rx";
+			status = "disabled";
+
+			slave {
+				compatible = "spidev";
+				spi-max-frequency = <1000000>;
+			};
+		};
+
+		rp1_spi5: spi@64000 {
+			reg = <0xc0 0x40064000  0x0 0x130>;
+			compatible = "snps,dw-apb-ssi";
+			interrupts = <RP1_INT_SPI5 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_SYS>;
+			clock-names = "ssi_clk";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			num-cs = <2>;
+			dmas = <&rp1_dma RP1_DMA_SPI5_TX>,
+			       <&rp1_dma RP1_DMA_SPI5_RX>;
+			dma-names = "tx", "rx";
+			status = "disabled";
+		};
+
+		rp1_spi6: spi@68000 {
+			reg = <0xc0 0x40068000  0x0 0x130>;
+			compatible = "snps,dw-apb-ssi";
+			interrupts = <RP1_INT_SPI6 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_SYS>;
+			clock-names = "ssi_clk";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			num-cs = <2>;
+			dmas = <&rp1_dma RP1_DMA_SPI6_TX>,
+			       <&rp1_dma RP1_DMA_SPI6_RX>;
+			dma-names = "tx", "rx";
+			status = "disabled";
+		};
+
+		// SPI7 is a target/slave interface
+		rp1_spi7: spi@6c000 {
+			reg = <0xc0 0x4006c000  0x0 0x130>;
+			compatible = "snps,dw-apb-ssi";
+			interrupts = <RP1_INT_SPI7 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_SYS>;
+			clock-names = "ssi_clk";
+			#address-cells = <0>;
+			#size-cells = <0>;
+			num-cs = <1>;
+			spi-slave;
+			dmas = <&rp1_dma RP1_DMA_SPI7_TX>,
+			       <&rp1_dma RP1_DMA_SPI7_RX>;
+			dma-names = "tx", "rx";
+			status = "disabled";
+
+			slave {
+				compatible = "spidev";
+				spi-max-frequency = <1000000>;
+			};
+		};
+
+		rp1_i2c0: i2c@70000 {
+			reg = <0xc0 0x40070000  0x0 0x1000>;
+			compatible = "snps,designware-i2c";
+			interrupts = <RP1_INT_I2C0 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_SYS>;
+			i2c-scl-rising-time-ns = <65>;
+			i2c-scl-falling-time-ns = <100>;
+			status = "disabled";
+		};
+
+		rp1_i2c1: i2c@74000 {
+			reg = <0xc0 0x40074000  0x0 0x1000>;
+			compatible = "snps,designware-i2c";
+			interrupts = <RP1_INT_I2C1 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_SYS>;
+			i2c-scl-rising-time-ns = <65>;
+			i2c-scl-falling-time-ns = <100>;
+			status = "disabled";
+		};
+
+		rp1_i2c2: i2c@78000 {
+			reg = <0xc0 0x40078000  0x0 0x1000>;
+			compatible = "snps,designware-i2c";
+			interrupts = <RP1_INT_I2C2 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_SYS>;
+			i2c-scl-rising-time-ns = <65>;
+			i2c-scl-falling-time-ns = <100>;
+			status = "disabled";
+		};
+
+		rp1_i2c3: i2c@7c000 {
+			reg = <0xc0 0x4007c000  0x0 0x1000>;
+			compatible = "snps,designware-i2c";
+			interrupts = <RP1_INT_I2C3 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_SYS>;
+			i2c-scl-rising-time-ns = <65>;
+			i2c-scl-falling-time-ns = <100>;
+			status = "disabled";
+		};
+
+		rp1_i2c4: i2c@80000 {
+			reg = <0xc0 0x40080000  0x0 0x1000>;
+			compatible = "snps,designware-i2c";
+			interrupts = <RP1_INT_I2C4 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_SYS>;
+			i2c-scl-rising-time-ns = <65>;
+			i2c-scl-falling-time-ns = <100>;
+			status = "disabled";
+		};
+
+		rp1_i2c5: i2c@84000 {
+			reg = <0xc0 0x40084000  0x0 0x1000>;
+			compatible = "snps,designware-i2c";
+			interrupts = <RP1_INT_I2C5 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_SYS>;
+			i2c-scl-rising-time-ns = <65>;
+			i2c-scl-falling-time-ns = <100>;
+			status = "disabled";
+		};
+
+		rp1_i2c6: i2c@88000 {
+			reg = <0xc0 0x40088000  0x0 0x1000>;
+			compatible = "snps,designware-i2c";
+			interrupts = <RP1_INT_I2C6 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_SYS>;
+			i2c-scl-rising-time-ns = <65>;
+			i2c-scl-falling-time-ns = <100>;
+			status = "disabled";
+		};
+
+		rp1_audio_out: audio_out@94000 {
+			compatible = "raspberrypi,rp1-audio-out";
+			reg = <0xc0 0x40094000 0x0 0x4000>;
+			clocks = <&rp1_clocks RP1_CLK_AUDIO_OUT>;
+			assigned-clocks = <&rp1_clocks RP1_CLK_AUDIO_OUT>;
+			assigned-clock-rates = <153600000>;
+			assigned-clock-parents = <&rp1_clocks RP1_PLL_AUDIO_SEC>;
+			dmas = <&rp1_dma RP1_DMA_AUDIO_OUT>;
+			dma-maxburst = <4>;
+			dma-names = "tx";
+			#sound-dai-cells = <0>;
+			status = "disabled";
+		};
+
+		rp1_pwm0: pwm@98000 {
+			compatible = "raspberrypi,rp1-pwm";
+			reg = <0xc0 0x40098000  0x0 0x100>;
+			#pwm-cells = <3>;
+			clocks = <&rp1_clocks RP1_CLK_PWM0>;
+			assigned-clocks = <&rp1_clocks RP1_CLK_PWM0>;
+			assigned-clock-rates = <50000000>;
+			status = "disabled";
+		};
+
+		rp1_pwm1: pwm@9c000 {
+			compatible = "raspberrypi,rp1-pwm";
+			reg = <0xc0 0x4009c000  0x0 0x100>;
+			#pwm-cells = <3>;
+			clocks = <&rp1_clocks RP1_CLK_PWM1>;
+			assigned-clocks = <&rp1_clocks RP1_CLK_PWM1>;
+			assigned-clock-rates = <50000000>;
+			status = "disabled";
+		};
+
+		rp1_i2s0: i2s@a0000 {
+			reg = <0xc0 0x400a0000  0x0 0x1000>;
+			compatible = "snps,designware-i2s";
+			// Providing an interrupt disables DMA
+			// interrupts = <RP1_INT_I2S0 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_I2S>;
+			clock-names = "i2sclk";
+			#sound-dai-cells = <0>;
+			dmas = <&rp1_dma RP1_DMA_I2S0_TX>,<&rp1_dma RP1_DMA_I2S0_RX>;
+			dma-names = "tx", "rx";
+			dma-maxburst = <4>;
+			status = "disabled";
+		};
+
+		rp1_i2s1: i2s@a4000 {
+			reg = <0xc0 0x400a4000  0x0 0x1000>;
+			compatible = "snps,designware-i2s";
+			// Providing an interrupt disables DMA
+			// interrupts = <RP1_INT_I2S1 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_I2S>;
+			clock-names = "i2sclk";
+			#sound-dai-cells = <0>;
+			dmas = <&rp1_dma RP1_DMA_I2S1_TX>,<&rp1_dma RP1_DMA_I2S1_RX>;
+			dma-names = "tx", "rx";
+			dma-maxburst = <4>;
+			status = "disabled";
+		};
+
+		rp1_i2s2: i2s@a8000 {
+			reg = <0xc0 0x400a8000  0x0 0x1000>;
+			compatible = "snps,designware-i2s";
+			// Providing an interrupt disables DMA
+			// interrupts = <RP1_INT_I2S2 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_I2S>;
+			status = "disabled";
+		};
+
+		rp1_sdio_clk0: sdio_clk0@b0004 {
+			compatible = "raspberrypi,rp1-sdio-clk";
+			reg = <0xc0 0x400b0004 0x0 0x1c>;
+			clocks = <&sdio_src &sdhci_core>;
+			clock-names = "src", "base";
+			#clock-cells = <0>;
+			status = "disabled";
+		};
+
+		rp1_sdio_clk1: sdio_clk1@b4004 {
+			compatible = "raspberrypi,rp1-sdio-clk";
+			reg = <0xc0 0x400b4004 0x0 0x1c>;
+			clocks = <&sdio_src &sdhci_core>;
+			clock-names = "src", "base";
+			#clock-cells = <0>;
+			status = "disabled";
+		};
+
+		rp1_adc: adc@c8000 {
+			compatible = "raspberrypi,rp1-adc";
+			reg = <0xc0 0x400c8000 0x0 0x4000>;
+			clocks = <&rp1_clocks RP1_CLK_ADC>;
+			clock-names = "adcclk";
+			#clock-cells = <0>;
+			vref-supply = <&rp1_vdd_3v3>;
+			status = "disabled";
+		};
+
+		rp1_gpio: gpio@d0000 {
+			reg = <0xc0 0x400d0000  0x0 0xc000>,
+			      <0xc0 0x400e0000  0x0 0xc000>,
+			      <0xc0 0x400f0000  0x0 0xc000>;
+			compatible = "raspberrypi,rp1-gpio";
+			interrupts = <RP1_INT_IO_BANK0 IRQ_TYPE_LEVEL_HIGH>,
+				     <RP1_INT_IO_BANK1 IRQ_TYPE_LEVEL_HIGH>,
+			             <RP1_INT_IO_BANK2 IRQ_TYPE_LEVEL_HIGH>;
+			gpio-controller;
+			#gpio-cells = <2>;
+			interrupt-controller;
+			#interrupt-cells = <2>;
+			gpio-ranges = <&rp1_gpio 0 0 54>;
+
+			rp1_uart0_14_15: rp1_uart0_14_15 {
+				pin_txd {
+					function = "uart0";
+					pins = "gpio14";
+					bias-disable;
+				};
+				pin_rxd {
+					function = "uart0";
+					pins = "gpio15";
+					bias-pull-up;
+				};
+			};
+			rp1_uart0_ctsrts_16_17: rp1_uart0_ctsrts_16_17 {
+				pin_cts {
+					function = "uart0";
+					pins = "gpio16";
+					bias-pull-up;
+				};
+				pin_rts {
+					function = "uart0";
+					pins = "gpio17";
+					bias-disable;
+				};
+			};
+			rp1_uart1_0_1: rp1_uart1_0_1 {
+				pin_txd {
+					function = "uart1";
+					pins = "gpio0";
+					bias-disable;
+				};
+				pin_rxd {
+					function = "uart1";
+					pins = "gpio1";
+					bias-pull-up;
+				};
+			};
+			rp1_uart1_ctsrts_2_3: rp1_uart1_ctsrts_2_3 {
+				pin_cts {
+					function = "uart1";
+					pins = "gpio2";
+					bias-pull-up;
+				};
+				pin_rts {
+					function = "uart1";
+					pins = "gpio3";
+					bias-disable;
+				};
+			};
+			rp1_uart2_4_5: rp1_uart2_4_5 {
+				pin_txd {
+					function = "uart2";
+					pins = "gpio4";
+					bias-disable;
+				};
+				pin_rxd {
+					function = "uart2";
+					pins = "gpio5";
+					bias-pull-up;
+				};
+			};
+			rp1_uart2_ctsrts_6_7: rp1_uart2_ctsrts_6_7 {
+				pin_cts {
+					function = "uart2";
+					pins = "gpio6";
+					bias-pull-up;
+				};
+				pin_rts {
+					function = "uart2";
+					pins = "gpio7";
+					bias-disable;
+				};
+			};
+			rp1_uart3_8_9: rp1_uart3_8_9 {
+				pin_txd {
+					function = "uart3";
+					pins = "gpio8";
+					bias-disable;
+				};
+				pin_rxd {
+					function = "uart3";
+					pins = "gpio9";
+					bias-pull-up;
+				};
+			};
+			rp1_uart3_ctsrts_10_11: rp1_uart3_ctsrts_10_11 {
+				pin_cts {
+					function = "uart3";
+					pins = "gpio10";
+					bias-pull-up;
+				};
+				pin_rts {
+					function = "uart3";
+					pins = "gpio11";
+					bias-disable;
+				};
+			};
+			rp1_uart4_12_13: rp1_uart4_12_13 {
+				pin_txd {
+					function = "uart4";
+					pins = "gpio12";
+					bias-disable;
+				};
+				pin_rxd {
+					function = "uart4";
+					pins = "gpio13";
+					bias-pull-up;
+				};
+			};
+			rp1_uart4_ctsrts_14_15: rp1_uart4_ctsrts_14_15 {
+				pin_cts {
+					function = "uart4";
+					pins = "gpio14";
+					bias-pull-up;
+				};
+				pin_rts {
+					function = "uart4";
+					pins = "gpio15";
+					bias-disable;
+				};
+			};
+
+			rp1_sdio0_22_27: rp1_sdio0_22_27 {
+				pin_clk {
+					function = "sd0";
+					pins = "gpio22";
+					bias-disable;
+					drive-strength = <12>;
+					slew-rate = <1>;
+				};
+				pin_cmd {
+					function = "sd0";
+					pins = "gpio23";
+					bias-pull-up;
+					drive-strength = <12>;
+					slew-rate = <1>;
+				};
+				pins_dat {
+					function = "sd0";
+					pins = "gpio24", "gpio25", "gpio26", "gpio27";
+					bias-pull-up;
+					drive-strength = <12>;
+					slew-rate = <1>;
+				};
+			};
+
+			rp1_sdio1_28_33: rp1_sdio1_28_33 {
+				pin_clk {
+					function = "sd1";
+					pins = "gpio28";
+					bias-disable;
+					drive-strength = <12>;
+					slew-rate = <1>;
+				};
+				pin_cmd {
+					function = "sd1";
+					pins = "gpio29";
+					bias-pull-up;
+					drive-strength = <12>;
+					slew-rate = <1>;
+				};
+				pins_dat {
+					function = "sd1";
+					pins = "gpio30", "gpio31", "gpio32", "gpio33";
+					bias-pull-up;
+					drive-strength = <12>;
+					slew-rate = <1>;
+				};
+			};
+
+			rp1_i2s0_18_21: rp1_i2s0_18_21 {
+				function = "i2s0";
+				pins = "gpio18", "gpio19", "gpio20", "gpio21";
+				bias-disable;
+			};
+
+			rp1_i2s1_18_21: rp1_i2s1_18_21 {
+				function = "i2s1";
+				pins = "gpio18", "gpio19", "gpio20", "gpio21";
+				bias-disable;
+			};
+
+			rp1_i2c4_34_35: rp1_i2c4_34_35 {
+				function = "i2c4";
+				pins = "gpio34", "gpio35";
+				drive-strength = <12>;
+				bias-pull-up;
+			};
+			rp1_i2c6_38_39: rp1_i2c6_38_39 {
+				function = "i2c6";
+				pins = "gpio38", "gpio39";
+				drive-strength = <12>;
+				bias-pull-up;
+			};
+			rp1_i2c4_40_41: rp1_i2c4_40_41 {
+				function = "i2c4";
+				pins = "gpio40", "gpio41";
+				drive-strength = <12>;
+				bias-pull-up;
+			};
+			rp1_i2c5_44_45: rp1_i2c5_44_45 {
+				function = "i2c5";
+				pins = "gpio44", "gpio45";
+				drive-strength = <12>;
+				bias-pull-up;
+			};
+			rp1_i2c0_0_1: rp1_i2c0_0_1 {
+				function = "i2c0";
+				pins = "gpio0", "gpio1";
+				drive-strength = <12>;
+				bias-pull-up;
+			};
+			rp1_i2c0_8_9: rp1_i2c0_8_9 {
+				function = "i2c0";
+				pins = "gpio8", "gpio9";
+				drive-strength = <12>;
+				bias-pull-up;
+			};
+			rp1_i2c1_2_3: rp1_i2c1_2_3 {
+				function = "i2c1";
+				pins = "gpio2", "gpio3";
+				drive-strength = <12>;
+				bias-pull-up;
+			};
+			rp1_i2c1_10_11: rp1_i2c1_10_11 {
+				function = "i2c1";
+				pins = "gpio10", "gpio11";
+				drive-strength = <12>;
+				bias-pull-up;
+			};
+			rp1_i2c2_4_5: rp1_i2c2_4_5 {
+				function = "i2c2";
+				pins = "gpio4", "gpio5";
+				drive-strength = <12>;
+				bias-pull-up;
+			};
+			rp1_i2c2_12_13: rp1_i2c2_12_13 {
+				function = "i2c2";
+				pins = "gpio12", "gpio13";
+				drive-strength = <12>;
+				bias-pull-up;
+			};
+			rp1_i2c3_6_7: rp1_i2c3_6_7 {
+				function = "i2c3";
+				pins = "gpio6", "gpio7";
+				drive-strength = <12>;
+				bias-pull-up;
+			};
+			rp1_i2c3_14_15: rp1_i2c3_14_15 {
+				function = "i2c3";
+				pins = "gpio14", "gpio15";
+				drive-strength = <12>;
+				bias-pull-up;
+			};
+			rp1_i2c3_22_23: rp1_i2c3_22_23 {
+				function = "i2c3";
+				pins = "gpio22", "gpio23";
+				drive-strength = <12>;
+				bias-pull-up;
+			};
+
+			// DPI mappings with HSYNC,VSYNC but without PIXCLK,DE
+			rp1_dpi_16bit_gpio2: rp1_dpi_16bit_gpio2 { /* Mode 2, not fully supported by RP1 */
+				function = "dpi";
+				pins = "gpio2", "gpio3", "gpio4", "gpio5",
+				       "gpio6", "gpio7", "gpio8", "gpio9",
+				       "gpio10", "gpio11", "gpio12", "gpio13",
+				       "gpio14", "gpio15", "gpio16", "gpio17",
+				       "gpio18", "gpio19";
+				bias-disable;
+			};
+			rp1_dpi_16bit_cpadhi_gpio2: rp1_dpi_16bit_cpadhi_gpio2 { /* Mode 3 */
+				function = "dpi";
+				pins = "gpio2", "gpio3", "gpio4", "gpio5",
+				       "gpio6", "gpio7", "gpio8",
+				       "gpio12", "gpio13", "gpio14", "gpio15",
+				       "gpio16", "gpio17",
+				       "gpio20", "gpio21", "gpio22", "gpio23",
+				       "gpio24";
+				bias-disable;
+			};
+			rp1_dpi_16bit_pad666_gpio2: rp1_dpi_16bit_pad666_gpio2 { /* Mode 4 */
+				function = "dpi";
+				pins = "gpio2", "gpio3",
+				       "gpio5", "gpio6", "gpio7", "gpio8",
+				       "gpio9",
+				       "gpio12", "gpio13", "gpio14", "gpio15",
+				       "gpio16", "gpio17",
+				       "gpio21", "gpio22", "gpio23", "gpio24",
+				       "gpio25";
+				bias-disable;
+			};
+			rp1_dpi_18bit_gpio2: rp1_dpi_18bit_gpio2 { /* Mode 5, not fully supported by RP1 */
+				function = "dpi";
+				pins = "gpio2", "gpio3", "gpio4", "gpio5",
+				       "gpio6", "gpio7", "gpio8", "gpio9",
+				       "gpio10", "gpio11", "gpio12", "gpio13",
+				       "gpio14", "gpio15", "gpio16", "gpio17",
+				       "gpio18", "gpio19", "gpio20", "gpio21";
+				bias-disable;
+			};
+			rp1_dpi_18bit_cpadhi_gpio2: rp1_dpi_18bit_cpadhi_gpio2 { /* Mode 6 */
+				function = "dpi";
+				pins = "gpio2", "gpio3", "gpio4", "gpio5",
+				       "gpio6", "gpio7", "gpio8", "gpio9",
+				       "gpio12", "gpio13", "gpio14", "gpio15",
+				       "gpio16", "gpio17",
+				       "gpio20", "gpio21", "gpio22", "gpio23",
+				       "gpio24", "gpio25";
+				bias-disable;
+			};
+			rp1_dpi_24bit_gpio2: rp1_dpi_24bit_gpio2 { /* Mode 7 */
+				function = "dpi";
+				pins = "gpio2", "gpio3", "gpio4", "gpio5",
+				       "gpio6", "gpio7", "gpio8", "gpio9",
+				       "gpio10", "gpio11", "gpio12", "gpio13",
+				       "gpio14", "gpio15", "gpio16", "gpio17",
+				       "gpio18", "gpio19", "gpio20", "gpio21",
+				       "gpio22", "gpio23", "gpio24", "gpio25",
+				       "gpio26", "gpio27";
+				bias-disable;
+			};
+			rp1_dpi_hvsync: rp1_dpi_hvsync { /* Sync only, for use with int VDAC */
+				function = "dpi";
+				pins = "gpio2", "gpio3";
+				bias-disable;
+			};
+
+			// More DPI mappings, including PIXCLK,DE on GPIOs 0,1
+			rp1_dpi_16bit_gpio0: rp1_dpi_16bit_gpio0 { /* Mode 2, not fully supported by RP1 */
+				function = "dpi";
+				pins = "gpio0", "gpio1", "gpio2", "gpio3",
+				       "gpio4", "gpio5", "gpio6", "gpio7",
+				       "gpio8", "gpio9", "gpio10", "gpio11",
+				       "gpio12", "gpio13", "gpio14", "gpio15",
+				       "gpio16", "gpio17", "gpio18", "gpio19";
+				bias-disable;
+			};
+			rp1_dpi_16bit_cpadhi_gpio0: rp1_dpi_16bit_cpadhi_gpio0 { /* Mode 3 */
+				function = "dpi";
+				pins = "gpio0", "gpio1", "gpio2", "gpio3",
+				       "gpio4", "gpio5", "gpio6", "gpio7",
+				       "gpio8",
+				       "gpio12", "gpio13", "gpio14", "gpio15",
+				       "gpio16", "gpio17",
+				       "gpio20", "gpio21", "gpio22", "gpio23",
+				       "gpio24";
+				bias-disable;
+			};
+			rp1_dpi_16bit_pad666_gpio0: rp1_dpi_16bit_pad666_gpio0 { /* Mode 4 */
+				function = "dpi";
+				pins = "gpio0", "gpio1", "gpio2", "gpio3",
+				       "gpio5", "gpio6", "gpio7", "gpio8",
+				       "gpio9",
+				       "gpio12", "gpio13", "gpio14", "gpio15",
+				       "gpio16", "gpio17",
+				       "gpio21", "gpio22", "gpio23", "gpio24",
+				       "gpio25";
+				bias-disable;
+			};
+			rp1_dpi_18bit_gpio0: rp1_dpi_18bit_gpio0 { /* Mode 5, not fully supported by RP1 */
+				function = "dpi";
+				pins = "gpio0", "gpio1", "gpio2", "gpio3",
+				       "gpio4", "gpio5", "gpio6", "gpio7",
+				       "gpio8", "gpio9", "gpio10", "gpio11",
+				       "gpio12", "gpio13", "gpio14", "gpio15",
+				       "gpio16", "gpio17", "gpio18", "gpio19",
+				       "gpio20", "gpio21";
+				bias-disable;
+			};
+			rp1_dpi_18bit_cpadhi_gpio0: rp1_dpi_18bit_cpadhi_gpio0 { /* Mode 6 */
+				function = "dpi";
+				pins = "gpio0", "gpio1", "gpio2", "gpio3",
+				       "gpio4", "gpio5", "gpio6", "gpio7",
+				       "gpio8", "gpio9",
+				       "gpio12", "gpio13", "gpio14", "gpio15",
+				       "gpio16", "gpio17",
+				       "gpio20", "gpio21", "gpio22", "gpio23",
+				       "gpio24", "gpio25";
+				bias-disable;
+			};
+			rp1_dpi_24bit_gpio0: rp1_dpi_24bit_gpio0 { /* Mode 7 -- All GPIOs used! */
+				function = "dpi";
+				pins = "gpio0", "gpio1", "gpio2", "gpio3",
+				       "gpio4", "gpio5", "gpio6", "gpio7",
+				       "gpio8", "gpio9", "gpio10", "gpio11",
+				       "gpio12", "gpio13", "gpio14", "gpio15",
+				       "gpio16", "gpio17", "gpio18", "gpio19",
+				       "gpio20", "gpio21", "gpio22", "gpio23",
+				       "gpio24", "gpio25", "gpio26", "gpio27";
+				bias-disable;
+			};
+
+			rp1_gpclksrc0_gpio4: rp1_gpclksrc0_gpio4 {
+				function = "gpclk0";
+				pins = "gpio4";
+				bias-disable;
+			};
+
+			rp1_gpclksrc0_gpio20: rp1_gpclksrc0_gpio20 {
+				function = "gpclk0";
+				pins = "gpio20";
+				bias-disable;
+			};
+
+			rp1_gpclksrc1_gpio5: rp1_gpclksrc1_gpio5 {
+				function = "gpclk1";
+				pins = "gpio5";
+				bias-disable;
+			};
+
+			rp1_gpclksrc1_gpio18: rp1_gpclksrc1_gpio18 {
+				function = "gpclk1";
+				pins = "gpio18";
+				bias-disable;
+			};
+
+			rp1_gpclksrc1_gpio21: rp1_gpclksrc1_gpio21 {
+				function = "gpclk1";
+				pins = "gpio21";
+				bias-disable;
+			};
+
+			rp1_pwm1_gpio45: rp1_pwm1_gpio45 {
+				function = "pwm1";
+				pins = "gpio45";
+				bias-pull-down;
+			};
+
+			rp1_spi0_gpio9: rp1_spi0_gpio9 {
+				function = "spi0";
+				pins = "gpio9", "gpio10", "gpio11";
+				bias-disable;
+				drive-strength = <12>;
+				slew-rate = <1>;
+			};
+
+			rp1_spi0_cs_gpio7: rp1_spi0_cs_gpio7 {
+				function = "spi0";
+				pins = "gpio7", "gpio8";
+				bias-pull-up;
+			};
+
+			rp1_spi1_gpio19: rp1_spi1_gpio19 {
+				function = "spi1";
+				pins = "gpio19", "gpio20", "gpio21";
+				bias-disable;
+				drive-strength = <12>;
+				slew-rate = <1>;
+			};
+
+			rp1_spi2_gpio1: rp1_spi2_gpio1 {
+				function = "spi2";
+				pins = "gpio1", "gpio2", "gpio3";
+				bias-disable;
+				drive-strength = <12>;
+				slew-rate = <1>;
+			};
+
+			rp1_spi3_gpio5: rp1_spi3_gpio5 {
+				function = "spi3";
+				pins = "gpio5", "gpio6", "gpio7";
+				bias-disable;
+				drive-strength = <12>;
+				slew-rate = <1>;
+			};
+
+			rp1_spi4_gpio9: rp1_spi4_gpio9 {
+				function = "spi4";
+				pins = "gpio9", "gpio10", "gpio11";
+				bias-disable;
+				drive-strength = <12>;
+				slew-rate = <1>;
+			};
+
+			rp1_spi5_gpio13: rp1_spi5_gpio13 {
+				function = "spi5";
+				pins = "gpio13", "gpio14", "gpio15";
+				bias-disable;
+				drive-strength = <12>;
+				slew-rate = <1>;
+			};
+
+			rp1_spi8_gpio49: rp1_spi8_gpio49 {
+				function = "spi8";
+				pins = "gpio49", "gpio50", "gpio51";
+				bias-disable;
+				drive-strength = <12>;
+				slew-rate = <1>;
+			};
+
+			rp1_spi8_cs_gpio52: rp1_spi8_cs_gpio52 {
+				function = "spi0";
+				pins = "gpio52", "gpio53";
+				bias-pull-up;
+			};
+
+			rp1_audio_out_12_13: rp1_audio_out_12_13 {
+				function = "aaud";
+				pins = "gpio12", "gpio13";
+				bias-disable;
+			};
+		};
+
+		rp1_eth: ethernet@100000 {
+			reg = <0xc0 0x40100000  0x0 0x4000>;
+			compatible = "raspberrypi,rp1-gem", "cdns,macb";
+			#address-cells = <1>;
+			#size-cells = <0>;
+			interrupts = <RP1_INT_ETH IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_SYS
+				  &rp1_clocks RP1_CLK_SYS
+				  &rp1_clocks RP1_CLK_ETH_TSU
+				  &rp1_clocks RP1_CLK_ETH>;
+			clock-names = "pclk", "hclk", "tsu_clk", "tx_clk";
+			phy-mode = "rgmii-id";
+			cdns,aw2w-max-pipe = /bits/ 8 <8>;
+			cdns,ar2r-max-pipe = /bits/ 8 <8>;
+			cdns,use-aw2b-fill;
+			local-mac-address = [00 00 00 00 00 00];
+			status = "disabled";
+		};
+
+		rp1_csi0: csi@110000 {
+			compatible = "raspberrypi,rp1-cfe";
+			reg = <0xc0 0x40110000  0x0 0x100>, // CSI2 DMA address
+			      <0xc0 0x40114000  0x0 0x100>, // PHY/CSI Host address
+			      <0xc0 0x40120000  0x0 0x100>, // MIPI CFG address
+			      <0xc0 0x40124000  0x0 0x1000>; // PiSP FE address
+
+			// interrupts must match rp1_pisp_fe setup
+			interrupts = <RP1_INT_MIPI0 IRQ_TYPE_LEVEL_HIGH>;
+
+			clocks = <&rp1_clocks RP1_CLK_MIPI0_CFG>;
+			assigned-clocks = <&rp1_clocks RP1_CLK_MIPI0_CFG>;
+			assigned-clock-rates = <25000000>;
+
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "disabled";
+		};
+
+		rp1_csi1: csi@128000 {
+			compatible = "raspberrypi,rp1-cfe";
+			reg = <0xc0 0x40128000  0x0 0x100>, // CSI2 DMA address
+			      <0xc0 0x4012c000  0x0 0x100>, // PHY/CSI Host address
+			      <0xc0 0x40138000  0x0 0x100>, // MIPI CFG address
+			      <0xc0 0x4013c000  0x0 0x1000>; // PiSP FE address
+
+			// interrupts must match rp1_pisp_fe setup
+			interrupts = <RP1_INT_MIPI1 IRQ_TYPE_LEVEL_HIGH>;
+
+			clocks = <&rp1_clocks RP1_CLK_MIPI1_CFG>;
+			assigned-clocks = <&rp1_clocks RP1_CLK_MIPI1_CFG>;
+			assigned-clock-rates = <25000000>;
+
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "disabled";
+		};
+
+		rp1_pio: pio@178000 {
+			reg = <0xc0 0x40178000  0x0 0x20>;
+			compatible = "raspberrypi,rp1-pio";
+			firmware = <&rp1_firmware>;
+			dmas = <&rp1_dma RP1_DMA_PIO_CH0_TX>, <&rp1_dma RP1_DMA_PIO_CH0_RX>,
+				   <&rp1_dma RP1_DMA_PIO_CH1_TX>, <&rp1_dma RP1_DMA_PIO_CH1_RX>,
+				   <&rp1_dma RP1_DMA_PIO_CH2_TX>, <&rp1_dma RP1_DMA_PIO_CH2_RX>,
+				   <&rp1_dma RP1_DMA_PIO_CH3_TX>, <&rp1_dma RP1_DMA_PIO_CH3_RX>;
+			dma-names = "tx0", "rx0", "tx1", "rx1", "tx2", "rx2", "tx3", "rx3";
+			status = "disabled";
+		};
+
+		rp1_mmc0: mmc@180000 {
+			reg = <0xc0 0x40180000  0x0 0x100>;
+			compatible = "raspberrypi,rp1-dwcmshc";
+			interrupts = <RP1_INT_SDIO0 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_SYS &sdhci_core
+			          &rp1_clocks RP1_CLK_SDIO_TIMER
+			          &rp1_sdio_clk0>;
+			clock-names = "bus", "core", "timeout", "sdio";
+			/* Bank 0 VDDIO is fixed */
+			no-1-8-v;
+			bus-width = <4>;
+			vmmc-supply = <&rp1_vdd_3v3>;
+			broken-cd;
+			status = "disabled";
+		};
+
+		rp1_mmc1: mmc@184000 {
+			reg = <0xc0 0x40184000  0x0 0x100>;
+			compatible = "raspberrypi,rp1-dwcmshc";
+			interrupts = <RP1_INT_SDIO1 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_SYS &sdhci_core
+			          &rp1_clocks RP1_CLK_SDIO_TIMER
+			          &rp1_sdio_clk1>;
+			clock-names = "bus", "core", "timeout", "sdio";
+			bus-width = <4>;
+			vmmc-supply = <&rp1_vdd_3v3>;
+			/* Nerf SDR speeds */
+			sdhci-caps-mask = <0x3 0x0>;
+			broken-cd;
+			status = "disabled";
+		};
+
+		rp1_dma: dma@188000 {
+			reg = <0xc0 0x40188000  0x0 0x1000>;
+			compatible = "snps,axi-dma-1.01a";
+			interrupts = <RP1_INT_DMA IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&rp1_clocks RP1_CLK_DMA &rp1_clocks RP1_CLK_SYS>;
+			clock-names = "core-clk", "cfgr-clk";
+
+			#dma-cells = <1>;
+			dma-channels = <8>;
+			snps,dma-masters = <1>;
+			snps,dma-targets = <64>;
+			snps,data-width = <4>; // (8 << 4) == 128 bits
+			snps,block-size = <0x40000 0x40000 0x40000 0x40000 0x40000 0x40000 0x40000 0x40000>;
+			snps,priority = <0 1 2 3 4 5 6 7>;
+			snps,axi-max-burst-len = <8 8 4 4 4 4 4 4>;
+			status = "disabled";
+		};
+
+		rp1_usb0: usb@200000 {
+			reg = <0xc0 0x40200000  0x0 0x100000>;
+			compatible = "snps,dwc3";
+			dr_mode = "host";
+			usb3-lpm-capable;
+			snps,axi-pipe-limit = /bits/ 8 <8>;
+			snps,dis_rxdet_inp3_quirk;
+			snps,enhanced-nak-fs-quirk;
+			snps,parkmode-disable-ss-quirk;
+			snps,parkmode-disable-hs-quirk;
+			snps,parkmode-disable-fsls-quirk;
+			snps,tx-max-burst = /bits/ 8 <8>;
+			snps,tx-thr-num-pkt = /bits/ 8 <2>;
+			interrupts = <RP1_INT_USBHOST0_0 IRQ_TYPE_EDGE_RISING>;
+			status = "disabled";
+		};
+
+		rp1_usb1: usb@300000 {
+			reg = <0xc0 0x40300000  0x0 0x100000>;
+			compatible = "snps,dwc3";
+			dr_mode = "host";
+			usb3-lpm-capable;
+			snps,axi-pipe-limit = /bits/ 8 <8>;
+			snps,dis_rxdet_inp3_quirk;
+			snps,enhanced-nak-fs-quirk;
+			snps,parkmode-disable-ss-quirk;
+			snps,parkmode-disable-hs-quirk;
+			snps,parkmode-disable-fsls-quirk;
+			snps,tx-max-burst = /bits/ 8 <8>;
+			snps,tx-thr-num-pkt = /bits/ 8 <2>;
+			interrupts = <RP1_INT_USBHOST1_0 IRQ_TYPE_EDGE_RISING>;
+			status = "disabled";
+		};
+
+		rp1_dsi0: dsi@110000 {
+			compatible = "raspberrypi,rp1dsi";
+			status = "disabled";
+			reg = <0xc0 0x40118000  0x0 0x1000>,  // MIPI0 DSI DMA (ArgonDPI)
+			      <0xc0 0x4011c000  0x0 0x1000>,  // MIPI0 DSI Host (SNPS)
+			      <0xc0 0x40120000  0x0 0x1000>;  // MIPI0 CFG
+
+			interrupts = <RP1_INT_MIPI0 IRQ_TYPE_LEVEL_HIGH>;
+
+			clocks = <&rp1_clocks RP1_CLK_MIPI0_CFG>,
+				 <&rp1_clocks RP1_CLK_MIPI0_DPI>,
+				 <&rp1_clocks RP1_CLK_MIPI0_DSI_BYTECLOCK>,
+				 <&clk_xosc>,                // hardwired to DSI "refclk"
+				 <&rp1_clocks RP1_PLL_SYS>;  // alternate parent for divide
+			clock-names = "cfgclk", "dpiclk", "byteclk", "refclk", "pllsys";
+
+			assigned-clocks = <&rp1_clocks RP1_CLK_MIPI0_CFG>;
+			assigned-clock-rates = <25000000>;
+		};
+
+		rp1_dsi1: dsi@128000 {
+			compatible = "raspberrypi,rp1dsi";
+			status = "disabled";
+			reg = <0xc0 0x40130000  0x0 0x1000>,  // MIPI1 DSI DMA (ArgonDPI)
+		              <0xc0 0x40134000  0x0 0x1000>,  // MIPI1 DSI Host (SNPS)
+		              <0xc0 0x40138000  0x0 0x1000>;  // MIPI1 CFG
+
+			interrupts = <RP1_INT_MIPI1 IRQ_TYPE_LEVEL_HIGH>;
+
+			clocks = <&rp1_clocks RP1_CLK_MIPI1_CFG>,
+				 <&rp1_clocks RP1_CLK_MIPI1_DPI>,
+				 <&rp1_clocks RP1_CLK_MIPI1_DSI_BYTECLOCK>,
+				 <&clk_xosc>,               // hardwired to DSI "refclk"
+				 <&rp1_clocks RP1_PLL_SYS>; // alternate parent for divide
+			clock-names = "cfgclk", "dpiclk", "byteclk", "refclk", "pllsys";
+
+			assigned-clocks = <&rp1_clocks RP1_CLK_MIPI1_CFG>;
+			assigned-clock-rates = <25000000>;
+		};
+
+		/* VEC and DPI both need to control PLL_VIDEO and cannot work together;   */
+		/* config.txt should enable one or other using dtparam=vec or an overlay. */
+		rp1_vec: vec@144000 {
+			compatible = "raspberrypi,rp1vec";
+			status = "disabled";
+			reg = <0xc0 0x40144000  0x0 0x1000>, // VIDEO_OUT_VEC
+			      <0xc0 0x40140000  0x0 0x1000>; // VIDEO_OUT_CFG
+
+			interrupts = <RP1_INT_VIDEO_OUT IRQ_TYPE_LEVEL_HIGH>;
+
+			clocks = <&rp1_clocks RP1_CLK_VEC>;
+
+			assigned-clocks = <&rp1_clocks RP1_PLL_VIDEO_CORE>,
+					  <&rp1_clocks RP1_PLL_VIDEO_SEC>,
+					  <&rp1_clocks RP1_CLK_VEC>;
+			assigned-clock-rates = <1188000000>,
+					       <108000000>,
+					       <108000000>;
+			assigned-clock-parents = <0>,
+						 <&rp1_clocks RP1_PLL_VIDEO_CORE>,
+						 <&rp1_clocks RP1_PLL_VIDEO_SEC>;
+		};
+
+		rp1_dpi: dpi@148000 {
+			compatible = "raspberrypi,rp1dpi";
+			status = "disabled";
+			reg = <0xc0 0x40148000  0x0 0x1000>, // VIDEO_OUT DPI
+			      <0xc0 0x40140000  0x0 0x1000>; // VIDEO_OUT_CFG
+
+			interrupts = <RP1_INT_VIDEO_OUT IRQ_TYPE_LEVEL_HIGH>;
+
+			clocks = <&rp1_clocks RP1_CLK_DPI>,        // DPI pixel clock
+				 <&rp1_clocks RP1_PLL_VIDEO>,      // PLL primary divider, and
+				 <&rp1_clocks RP1_PLL_VIDEO_CORE>; // VCO, which we also control
+			clock-names = "dpiclk", "plldiv", "pllcore";
+
+			assigned-clocks        = <&rp1_clocks RP1_CLK_DPI>;
+			assigned-clock-parents = <&rp1_clocks RP1_PLL_VIDEO>;
+		};
+
+		sram: sram@400000 {
+			compatible = "mmio-sram";
+			reg = <0xc0 0x40400000  0x0 0x10000>;
+			#address-cells = <1>;
+			#size-cells = <1>;
+			ranges = <0  0xc0 0x40400000  0x10000>;
+
+			rp1_fw_shmem: shmem@ff00 {
+				compatible = "raspberrypi,rp1-shmem";
+				reg = <0xff00 0x100>; // firmware mailbox buffer
+			};
+		};
+	};
+};
+
+&clocks {
+	clk_xosc: clk_xosc {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-output-names = "xosc";
+		clock-frequency = <50000000>;
+	};
+	sdio_src: sdio_src {
+		// 400 MHz on FPGA. PLL sys VCO on asic
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-output-names = "src";
+		clock-frequency = <1000000000>;
+	};
+	sdhci_core: sdhci_core {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-output-names = "core";
+		clock-frequency = <50000000>;
+	};
+	/* GPIO derived clock sources. Each GPIO with a GPCLK function
+	 * can drive its output from the respective GPCLK
+	 * generator, and provide a clock source to other internal
+	 * dividers. Add dummy sources here so that they can be overridden
+	 * with overlays.
+	 */
+	clksrc_gp0: clksrc_gp0 {
+		status = "disabled";
+		compatible = "fixed-factor-clock";
+		#clock-cells = <0>;
+		clock-div = <1>;
+		clock-mult = <1>;
+		clocks = <&rp1_clocks RP1_CLK_GP0>;
+		clock-output-names = "clksrc_gp0";
+	};
+	clksrc_gp1: clksrc_gp1 {
+		status = "disabled";
+		compatible = "fixed-factor-clock";
+		#clock-cells = <0>;
+		clock-div = <1>;
+		clock-mult = <1>;
+		clocks = <&rp1_clocks RP1_CLK_GP1>;
+		clock-output-names = "clksrc_gp1";
+	};
+	clksrc_gp2: clksrc_gp2 {
+		status = "disabled";
+		compatible = "fixed-factor-clock";
+		clock-div = <1>;
+		clock-mult = <1>;
+		#clock-cells = <0>;
+		clocks = <&rp1_clocks RP1_CLK_GP2>;
+		clock-output-names = "clksrc_gp2";
+	};
+	clksrc_gp3: clksrc_gp3 {
+		status = "disabled";
+		compatible = "fixed-factor-clock";
+		clock-div = <1>;
+		clock-mult = <1>;
+		#clock-cells = <0>;
+		clocks = <&rp1_clocks RP1_CLK_GP3>;
+		clock-output-names = "clksrc_gp3";
+	};
+	clksrc_gp4: clksrc_gp4 {
+		status = "disabled";
+		compatible = "fixed-factor-clock";
+		#clock-cells = <0>;
+		clock-div = <1>;
+		clock-mult = <1>;
+		clocks = <&rp1_clocks RP1_CLK_GP4>;
+		clock-output-names = "clksrc_gp4";
+	};
+	clksrc_gp5: clksrc_gp5 {
+		status = "disabled";
+		compatible = "fixed-factor-clock";
+		#clock-cells = <0>;
+		clock-div = <1>;
+		clock-mult = <1>;
+		clocks = <&rp1_clocks RP1_CLK_GP5>;
+		clock-output-names = "clksrc_gp5";
+	};
+};
+
+/ {
+	rp1_firmware: rp1_firmware {
+		compatible = "raspberrypi,rp1-firmware", "simple-mfd";
+		mboxes = <&rp1_mbox 0>;
+		shmem = <&rp1_fw_shmem>;
+	};
+
+	rp1_vdd_3v3: rp1_vdd_3v3 {
+		compatible = "regulator-fixed";
+		regulator-name = "vdd-3v3";
+		regulator-min-microvolt = <3300000>;
+		regulator-max-microvolt = <3300000>;
+		regulator-always-on;
+	};
+};
diff --git a/arch/arm64/boot/dts/overlays b/arch/arm64/boot/dts/overlays
new file mode 120000
index 000000000000..ded08646b6f6
--- /dev/null
+++ b/arch/arm64/boot/dts/overlays
@@ -0,0 +1 @@
+../../../arm/boot/dts/overlays
\ No newline at end of file
diff --git a/arch/arm64/configs/bcm2711_defconfig b/arch/arm64/configs/bcm2711_defconfig
new file mode 100644
index 000000000000..ddbc08e8edc0
--- /dev/null
+++ b/arch/arm64/configs/bcm2711_defconfig
@@ -0,0 +1,1784 @@
+CONFIG_LOCALVERSION="-v8"
+# CONFIG_LOCALVERSION_AUTO is not set
+CONFIG_SYSVIPC=y
+CONFIG_POSIX_MQUEUE=y
+CONFIG_GENERIC_IRQ_DEBUGFS=y
+CONFIG_NO_HZ=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_BPF_SYSCALL=y
+CONFIG_BPF_JIT=y
+CONFIG_PREEMPT=y
+CONFIG_BSD_PROCESS_ACCT=y
+CONFIG_BSD_PROCESS_ACCT_V3=y
+CONFIG_TASKSTATS=y
+CONFIG_TASK_DELAY_ACCT=y
+CONFIG_TASK_XACCT=y
+CONFIG_TASK_IO_ACCOUNTING=y
+CONFIG_PSI=y
+CONFIG_PSI_DEFAULT_DISABLED=y
+CONFIG_IKCONFIG=m
+CONFIG_IKCONFIG_PROC=y
+CONFIG_MEMCG=y
+CONFIG_BLK_CGROUP=y
+CONFIG_CFS_BANDWIDTH=y
+CONFIG_CGROUP_PIDS=y
+CONFIG_CGROUP_FREEZER=y
+CONFIG_CPUSETS=y
+CONFIG_CPUSETS_V1=y
+CONFIG_CGROUP_DEVICE=y
+CONFIG_CGROUP_CPUACCT=y
+CONFIG_CGROUP_PERF=y
+CONFIG_CGROUP_BPF=y
+CONFIG_NAMESPACES=y
+CONFIG_USER_NS=y
+CONFIG_CHECKPOINT_RESTORE=y
+CONFIG_SCHED_AUTOGROUP=y
+CONFIG_BLK_DEV_INITRD=y
+CONFIG_EXPERT=y
+CONFIG_PROFILING=y
+CONFIG_ARCH_BCM=y
+CONFIG_ARCH_BCM2835=y
+CONFIG_ARCH_BRCMSTB=y
+CONFIG_ARM64_ERRATUM_834220=y
+CONFIG_ARM64_ERRATUM_2441007=y
+CONFIG_ARM64_ERRATUM_1286807=y
+CONFIG_ARM64_ERRATUM_1542419=y
+CONFIG_ARM64_ERRATUM_2441009=y
+# CONFIG_CAVIUM_ERRATUM_22375 is not set
+# CONFIG_CAVIUM_ERRATUM_23154 is not set
+# CONFIG_CAVIUM_ERRATUM_27456 is not set
+CONFIG_ARM64_VA_BITS_39=y
+CONFIG_NR_CPUS=4
+CONFIG_HOTPLUG_CPU=y
+CONFIG_NUMA=y
+CONFIG_COMPAT=y
+CONFIG_ARMV8_DEPRECATED=y
+CONFIG_SWP_EMULATION=y
+CONFIG_CP15_BARRIER_EMULATION=y
+CONFIG_SETEND_EMULATION=y
+CONFIG_RANDOMIZE_BASE=y
+CONFIG_CMDLINE="console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait"
+# CONFIG_SUSPEND is not set
+CONFIG_PM=y
+CONFIG_PM_DEBUG=y
+CONFIG_CPU_IDLE=y
+CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_STAT=y
+CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y
+CONFIG_CPU_FREQ_GOV_POWERSAVE=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
+CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y
+CONFIG_CPUFREQ_DT=y
+CONFIG_ARM_RASPBERRYPI_CPUFREQ=y
+CONFIG_VIRTUALIZATION=y
+CONFIG_KVM=y
+CONFIG_JUMP_LABEL=y
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_MODVERSIONS=y
+CONFIG_MODULE_SRCVERSION_ALL=y
+CONFIG_MODULE_COMPRESS=y
+CONFIG_MODULE_COMPRESS_XZ=y
+CONFIG_BLK_DEV_THROTTLING=y
+CONFIG_PARTITION_ADVANCED=y
+CONFIG_MAC_PARTITION=y
+CONFIG_BINFMT_MISC=m
+CONFIG_ZSWAP=y
+# CONFIG_COMPAT_BRK is not set
+CONFIG_CMA=y
+CONFIG_CMA_AREAS=7
+CONFIG_LRU_GEN=y
+CONFIG_LRU_GEN_ENABLED=y
+CONFIG_NUMA_EMU=y
+CONFIG_NET=y
+CONFIG_PACKET=y
+CONFIG_XFRM_USER=m
+CONFIG_XFRM_INTERFACE=m
+CONFIG_XFRM_SUB_POLICY=y
+CONFIG_XFRM_STATISTICS=y
+CONFIG_NET_KEY=m
+CONFIG_IP_MULTICAST=y
+CONFIG_IP_ADVANCED_ROUTER=y
+CONFIG_IP_MULTIPLE_TABLES=y
+CONFIG_IP_ROUTE_MULTIPATH=y
+CONFIG_IP_ROUTE_VERBOSE=y
+CONFIG_IP_PNP=y
+CONFIG_IP_PNP_DHCP=y
+CONFIG_IP_PNP_RARP=y
+CONFIG_NET_IPIP=m
+CONFIG_NET_IPGRE_DEMUX=m
+CONFIG_NET_IPGRE=m
+CONFIG_IP_MROUTE=y
+CONFIG_IP_MROUTE_MULTIPLE_TABLES=y
+CONFIG_IP_PIMSM_V1=y
+CONFIG_IP_PIMSM_V2=y
+CONFIG_NET_IPVTI=m
+CONFIG_NET_FOU_IP_TUNNELS=y
+CONFIG_INET_AH=m
+CONFIG_INET_ESP=m
+CONFIG_INET_IPCOMP=m
+CONFIG_INET_DIAG=m
+CONFIG_TCP_CONG_ADVANCED=y
+CONFIG_TCP_CONG_BBR=m
+CONFIG_IPV6=m
+CONFIG_IPV6_ROUTER_PREF=y
+CONFIG_IPV6_ROUTE_INFO=y
+CONFIG_INET6_AH=m
+CONFIG_INET6_ESP=m
+CONFIG_INET6_ESP_OFFLOAD=m
+CONFIG_INET6_IPCOMP=m
+CONFIG_IPV6_ILA=m
+CONFIG_IPV6_VTI=m
+CONFIG_IPV6_SIT_6RD=y
+CONFIG_IPV6_GRE=m
+CONFIG_IPV6_MULTIPLE_TABLES=y
+CONFIG_IPV6_SUBTREES=y
+CONFIG_IPV6_MROUTE=y
+CONFIG_IPV6_MROUTE_MULTIPLE_TABLES=y
+CONFIG_IPV6_PIMSM_V2=y
+CONFIG_MPTCP=y
+CONFIG_NETWORK_PHY_TIMESTAMPING=y
+CONFIG_NETFILTER=y
+CONFIG_BRIDGE_NETFILTER=m
+CONFIG_NF_CONNTRACK=m
+CONFIG_NF_CONNTRACK_ZONES=y
+CONFIG_NF_CONNTRACK_EVENTS=y
+CONFIG_NF_CONNTRACK_TIMESTAMP=y
+CONFIG_NF_CONNTRACK_AMANDA=m
+CONFIG_NF_CONNTRACK_FTP=m
+CONFIG_NF_CONNTRACK_H323=m
+CONFIG_NF_CONNTRACK_IRC=m
+CONFIG_NF_CONNTRACK_NETBIOS_NS=m
+CONFIG_NF_CONNTRACK_SNMP=m
+CONFIG_NF_CONNTRACK_PPTP=m
+CONFIG_NF_CONNTRACK_SANE=m
+CONFIG_NF_CONNTRACK_SIP=m
+CONFIG_NF_CONNTRACK_TFTP=m
+CONFIG_NF_CT_NETLINK=m
+CONFIG_NF_TABLES=m
+CONFIG_NF_TABLES_INET=y
+CONFIG_NF_TABLES_NETDEV=y
+CONFIG_NFT_NUMGEN=m
+CONFIG_NFT_CT=m
+CONFIG_NFT_FLOW_OFFLOAD=m
+CONFIG_NFT_CONNLIMIT=m
+CONFIG_NFT_LOG=m
+CONFIG_NFT_LIMIT=m
+CONFIG_NFT_MASQ=m
+CONFIG_NFT_REDIR=m
+CONFIG_NFT_NAT=m
+CONFIG_NFT_TUNNEL=m
+CONFIG_NFT_QUEUE=m
+CONFIG_NFT_QUOTA=m
+CONFIG_NFT_REJECT=m
+CONFIG_NFT_COMPAT=m
+CONFIG_NFT_HASH=m
+CONFIG_NFT_FIB_INET=m
+CONFIG_NFT_XFRM=m
+CONFIG_NFT_SOCKET=m
+CONFIG_NFT_OSF=m
+CONFIG_NFT_TPROXY=m
+CONFIG_NFT_SYNPROXY=m
+CONFIG_NFT_DUP_NETDEV=m
+CONFIG_NFT_FWD_NETDEV=m
+CONFIG_NFT_FIB_NETDEV=m
+CONFIG_NF_FLOW_TABLE_INET=m
+CONFIG_NF_FLOW_TABLE=m
+CONFIG_NETFILTER_XTABLES_COMPAT=y
+CONFIG_NETFILTER_XT_SET=m
+CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m
+CONFIG_NETFILTER_XT_TARGET_CLASSIFY=m
+CONFIG_NETFILTER_XT_TARGET_CONNMARK=m
+CONFIG_NETFILTER_XT_TARGET_DSCP=m
+CONFIG_NETFILTER_XT_TARGET_HMARK=m
+CONFIG_NETFILTER_XT_TARGET_IDLETIMER=m
+CONFIG_NETFILTER_XT_TARGET_LED=m
+CONFIG_NETFILTER_XT_TARGET_LOG=m
+CONFIG_NETFILTER_XT_TARGET_MARK=m
+CONFIG_NETFILTER_XT_TARGET_NFLOG=m
+CONFIG_NETFILTER_XT_TARGET_NFQUEUE=m
+CONFIG_NETFILTER_XT_TARGET_NOTRACK=m
+CONFIG_NETFILTER_XT_TARGET_TEE=m
+CONFIG_NETFILTER_XT_TARGET_TPROXY=m
+CONFIG_NETFILTER_XT_TARGET_TRACE=m
+CONFIG_NETFILTER_XT_TARGET_TCPMSS=m
+CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=m
+CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m
+CONFIG_NETFILTER_XT_MATCH_BPF=m
+CONFIG_NETFILTER_XT_MATCH_CLUSTER=m
+CONFIG_NETFILTER_XT_MATCH_COMMENT=m
+CONFIG_NETFILTER_XT_MATCH_CONNBYTES=m
+CONFIG_NETFILTER_XT_MATCH_CONNLABEL=m
+CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=m
+CONFIG_NETFILTER_XT_MATCH_CONNMARK=m
+CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m
+CONFIG_NETFILTER_XT_MATCH_CPU=m
+CONFIG_NETFILTER_XT_MATCH_DCCP=m
+CONFIG_NETFILTER_XT_MATCH_DEVGROUP=m
+CONFIG_NETFILTER_XT_MATCH_DSCP=m
+CONFIG_NETFILTER_XT_MATCH_ESP=m
+CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=m
+CONFIG_NETFILTER_XT_MATCH_HELPER=m
+CONFIG_NETFILTER_XT_MATCH_IPRANGE=m
+CONFIG_NETFILTER_XT_MATCH_IPVS=m
+CONFIG_NETFILTER_XT_MATCH_LENGTH=m
+CONFIG_NETFILTER_XT_MATCH_LIMIT=m
+CONFIG_NETFILTER_XT_MATCH_MAC=m
+CONFIG_NETFILTER_XT_MATCH_MARK=m
+CONFIG_NETFILTER_XT_MATCH_MULTIPORT=m
+CONFIG_NETFILTER_XT_MATCH_NFACCT=m
+CONFIG_NETFILTER_XT_MATCH_OSF=m
+CONFIG_NETFILTER_XT_MATCH_OWNER=m
+CONFIG_NETFILTER_XT_MATCH_POLICY=m
+CONFIG_NETFILTER_XT_MATCH_PHYSDEV=m
+CONFIG_NETFILTER_XT_MATCH_PKTTYPE=m
+CONFIG_NETFILTER_XT_MATCH_QUOTA=m
+CONFIG_NETFILTER_XT_MATCH_RATEEST=m
+CONFIG_NETFILTER_XT_MATCH_REALM=m
+CONFIG_NETFILTER_XT_MATCH_RECENT=m
+CONFIG_NETFILTER_XT_MATCH_SOCKET=m
+CONFIG_NETFILTER_XT_MATCH_STATE=m
+CONFIG_NETFILTER_XT_MATCH_STATISTIC=m
+CONFIG_NETFILTER_XT_MATCH_STRING=m
+CONFIG_NETFILTER_XT_MATCH_TCPMSS=m
+CONFIG_NETFILTER_XT_MATCH_TIME=m
+CONFIG_NETFILTER_XT_MATCH_U32=m
+CONFIG_IP_SET=m
+CONFIG_IP_SET_BITMAP_IP=m
+CONFIG_IP_SET_BITMAP_IPMAC=m
+CONFIG_IP_SET_BITMAP_PORT=m
+CONFIG_IP_SET_HASH_IP=m
+CONFIG_IP_SET_HASH_IPPORT=m
+CONFIG_IP_SET_HASH_IPPORTIP=m
+CONFIG_IP_SET_HASH_IPPORTNET=m
+CONFIG_IP_SET_HASH_NET=m
+CONFIG_IP_SET_HASH_NETPORT=m
+CONFIG_IP_SET_HASH_NETIFACE=m
+CONFIG_IP_SET_LIST_SET=m
+CONFIG_IP_VS=m
+CONFIG_IP_VS_IPV6=y
+CONFIG_IP_VS_PROTO_TCP=y
+CONFIG_IP_VS_PROTO_UDP=y
+CONFIG_IP_VS_PROTO_ESP=y
+CONFIG_IP_VS_PROTO_AH=y
+CONFIG_IP_VS_PROTO_SCTP=y
+CONFIG_IP_VS_RR=m
+CONFIG_IP_VS_WRR=m
+CONFIG_IP_VS_LC=m
+CONFIG_IP_VS_WLC=m
+CONFIG_IP_VS_LBLC=m
+CONFIG_IP_VS_LBLCR=m
+CONFIG_IP_VS_DH=m
+CONFIG_IP_VS_SH=m
+CONFIG_IP_VS_SED=m
+CONFIG_IP_VS_NQ=m
+CONFIG_IP_VS_FTP=m
+CONFIG_IP_VS_PE_SIP=m
+CONFIG_NFT_DUP_IPV4=m
+CONFIG_NFT_FIB_IPV4=m
+CONFIG_NF_TABLES_ARP=y
+CONFIG_NF_LOG_ARP=m
+CONFIG_NF_LOG_IPV4=m
+CONFIG_IP_NF_IPTABLES=m
+CONFIG_IP_NF_MATCH_AH=m
+CONFIG_IP_NF_MATCH_ECN=m
+CONFIG_IP_NF_MATCH_RPFILTER=m
+CONFIG_IP_NF_MATCH_TTL=m
+CONFIG_IP_NF_FILTER=m
+CONFIG_IP_NF_TARGET_REJECT=m
+CONFIG_IP_NF_TARGET_SYNPROXY=m
+CONFIG_IP_NF_NAT=m
+CONFIG_IP_NF_TARGET_MASQUERADE=m
+CONFIG_IP_NF_TARGET_NETMAP=m
+CONFIG_IP_NF_TARGET_REDIRECT=m
+CONFIG_IP_NF_MANGLE=m
+CONFIG_IP_NF_TARGET_ECN=m
+CONFIG_IP_NF_TARGET_TTL=m
+CONFIG_IP_NF_RAW=m
+CONFIG_IP_NF_ARPFILTER=m
+CONFIG_IP_NF_ARP_MANGLE=m
+CONFIG_NFT_DUP_IPV6=m
+CONFIG_NFT_FIB_IPV6=m
+CONFIG_IP6_NF_IPTABLES=m
+CONFIG_IP6_NF_MATCH_AH=m
+CONFIG_IP6_NF_MATCH_EUI64=m
+CONFIG_IP6_NF_MATCH_FRAG=m
+CONFIG_IP6_NF_MATCH_OPTS=m
+CONFIG_IP6_NF_MATCH_HL=m
+CONFIG_IP6_NF_MATCH_IPV6HEADER=m
+CONFIG_IP6_NF_MATCH_MH=m
+CONFIG_IP6_NF_MATCH_RPFILTER=m
+CONFIG_IP6_NF_MATCH_RT=m
+CONFIG_IP6_NF_MATCH_SRH=m
+CONFIG_IP6_NF_TARGET_HL=m
+CONFIG_IP6_NF_FILTER=m
+CONFIG_IP6_NF_TARGET_REJECT=m
+CONFIG_IP6_NF_TARGET_SYNPROXY=m
+CONFIG_IP6_NF_MANGLE=m
+CONFIG_IP6_NF_RAW=m
+CONFIG_IP6_NF_SECURITY=m
+CONFIG_IP6_NF_NAT=m
+CONFIG_IP6_NF_TARGET_MASQUERADE=m
+CONFIG_IP6_NF_TARGET_NPT=m
+CONFIG_NF_TABLES_BRIDGE=m
+CONFIG_NFT_BRIDGE_REJECT=m
+CONFIG_BRIDGE_NF_EBTABLES=m
+CONFIG_BRIDGE_EBT_BROUTE=m
+CONFIG_BRIDGE_EBT_T_FILTER=m
+CONFIG_BRIDGE_EBT_T_NAT=m
+CONFIG_BRIDGE_EBT_802_3=m
+CONFIG_BRIDGE_EBT_AMONG=m
+CONFIG_BRIDGE_EBT_ARP=m
+CONFIG_BRIDGE_EBT_IP=m
+CONFIG_BRIDGE_EBT_IP6=m
+CONFIG_BRIDGE_EBT_LIMIT=m
+CONFIG_BRIDGE_EBT_MARK=m
+CONFIG_BRIDGE_EBT_PKTTYPE=m
+CONFIG_BRIDGE_EBT_STP=m
+CONFIG_BRIDGE_EBT_VLAN=m
+CONFIG_BRIDGE_EBT_ARPREPLY=m
+CONFIG_BRIDGE_EBT_DNAT=m
+CONFIG_BRIDGE_EBT_MARK_T=m
+CONFIG_BRIDGE_EBT_REDIRECT=m
+CONFIG_BRIDGE_EBT_SNAT=m
+CONFIG_BRIDGE_EBT_LOG=m
+CONFIG_BRIDGE_EBT_NFLOG=m
+CONFIG_SCTP_COOKIE_HMAC_SHA1=y
+CONFIG_ATM=m
+CONFIG_L2TP=m
+CONFIG_L2TP_V3=y
+CONFIG_L2TP_IP=m
+CONFIG_L2TP_ETH=m
+CONFIG_BRIDGE=m
+CONFIG_BRIDGE_VLAN_FILTERING=y
+CONFIG_NET_DSA=m
+CONFIG_VLAN_8021Q=m
+CONFIG_VLAN_8021Q_GVRP=y
+CONFIG_ATALK=m
+CONFIG_6LOWPAN=m
+CONFIG_IEEE802154=m
+CONFIG_IEEE802154_6LOWPAN=m
+CONFIG_MAC802154=m
+CONFIG_NET_SCHED=y
+CONFIG_NET_SCH_HTB=m
+CONFIG_NET_SCH_HFSC=m
+CONFIG_NET_SCH_PRIO=m
+CONFIG_NET_SCH_MULTIQ=m
+CONFIG_NET_SCH_RED=m
+CONFIG_NET_SCH_SFB=m
+CONFIG_NET_SCH_SFQ=m
+CONFIG_NET_SCH_TEQL=m
+CONFIG_NET_SCH_TBF=m
+CONFIG_NET_SCH_GRED=m
+CONFIG_NET_SCH_NETEM=m
+CONFIG_NET_SCH_DRR=m
+CONFIG_NET_SCH_MQPRIO=m
+CONFIG_NET_SCH_CHOKE=m
+CONFIG_NET_SCH_QFQ=m
+CONFIG_NET_SCH_CODEL=m
+CONFIG_NET_SCH_FQ_CODEL=m
+CONFIG_NET_SCH_CAKE=m
+CONFIG_NET_SCH_FQ=m
+CONFIG_NET_SCH_HHF=m
+CONFIG_NET_SCH_PIE=m
+CONFIG_NET_SCH_INGRESS=m
+CONFIG_NET_SCH_PLUG=m
+CONFIG_NET_CLS_BASIC=m
+CONFIG_NET_CLS_ROUTE4=m
+CONFIG_NET_CLS_FW=m
+CONFIG_NET_CLS_U32=m
+CONFIG_CLS_U32_PERF=y
+CONFIG_CLS_U32_MARK=y
+CONFIG_NET_CLS_FLOW=m
+CONFIG_NET_CLS_CGROUP=m
+CONFIG_NET_CLS_BPF=y
+CONFIG_NET_CLS_FLOWER=m
+CONFIG_NET_CLS_MATCHALL=m
+CONFIG_NET_EMATCH=y
+CONFIG_NET_EMATCH_CMP=m
+CONFIG_NET_EMATCH_NBYTE=m
+CONFIG_NET_EMATCH_U32=m
+CONFIG_NET_EMATCH_META=m
+CONFIG_NET_EMATCH_TEXT=m
+CONFIG_NET_EMATCH_IPSET=m
+CONFIG_NET_CLS_ACT=y
+CONFIG_NET_ACT_POLICE=m
+CONFIG_NET_ACT_GACT=m
+CONFIG_GACT_PROB=y
+CONFIG_NET_ACT_MIRRED=m
+CONFIG_NET_ACT_NAT=m
+CONFIG_NET_ACT_PEDIT=m
+CONFIG_NET_ACT_SIMP=m
+CONFIG_NET_ACT_SKBEDIT=m
+CONFIG_NET_ACT_CSUM=m
+CONFIG_BATMAN_ADV=m
+CONFIG_OPENVSWITCH=m
+CONFIG_VSOCKETS=m
+CONFIG_CGROUP_NET_PRIO=y
+CONFIG_NET_PKTGEN=m
+CONFIG_HAMRADIO=y
+CONFIG_AX25=m
+CONFIG_NETROM=m
+CONFIG_ROSE=m
+CONFIG_MKISS=m
+CONFIG_6PACK=m
+CONFIG_BPQETHER=m
+CONFIG_BAYCOM_SER_FDX=m
+CONFIG_BAYCOM_SER_HDX=m
+CONFIG_YAM=m
+CONFIG_CAN=m
+CONFIG_CAN_J1939=m
+CONFIG_CAN_ISOTP=m
+CONFIG_BT=m
+CONFIG_BT_RFCOMM=m
+CONFIG_BT_RFCOMM_TTY=y
+CONFIG_BT_BNEP=m
+CONFIG_BT_BNEP_MC_FILTER=y
+CONFIG_BT_BNEP_PROTO_FILTER=y
+CONFIG_BT_HIDP=m
+CONFIG_BT_6LOWPAN=m
+CONFIG_BT_HCIBTUSB=m
+CONFIG_BT_HCIBTUSB_MTK=y
+CONFIG_BT_HCIUART=m
+CONFIG_BT_HCIUART_3WIRE=y
+CONFIG_BT_HCIUART_BCM=y
+CONFIG_BT_HCIBCM203X=m
+CONFIG_BT_HCIBPA10X=m
+CONFIG_BT_HCIBFUSB=m
+CONFIG_BT_HCIVHCI=m
+CONFIG_BT_MRVL=m
+CONFIG_BT_MRVL_SDIO=m
+CONFIG_BT_ATH3K=m
+CONFIG_CFG80211=m
+CONFIG_CFG80211_WEXT=y
+CONFIG_MAC80211=m
+CONFIG_MAC80211_MESH=y
+CONFIG_RFKILL=m
+CONFIG_RFKILL_INPUT=y
+CONFIG_NET_9P=m
+CONFIG_NFC=m
+CONFIG_PCI=y
+CONFIG_PCIEPORTBUS=y
+CONFIG_PCIEAER=y
+CONFIG_PCIEASPM_POWERSAVE=y
+CONFIG_PCIE_DPC=y
+CONFIG_UEVENT_HELPER=y
+CONFIG_DEVTMPFS=y
+CONFIG_DEVTMPFS_MOUNT=y
+# CONFIG_BRCMSTB_GISB_ARB is not set
+CONFIG_RASPBERRYPI_FIRMWARE=y
+CONFIG_FIRMWARE_RP1=m
+# CONFIG_EFI_VARS_PSTORE is not set
+CONFIG_MTD=m
+CONFIG_MTD_BLOCK=m
+CONFIG_MTD_BLOCK2MTD=m
+CONFIG_MTD_SPI_NAND=m
+CONFIG_MTD_SPI_NOR=m
+CONFIG_MTD_UBI=m
+CONFIG_OF_CONFIGFS=y
+CONFIG_ZRAM=m
+CONFIG_ZRAM_BACKEND_LZ4=y
+CONFIG_ZRAM_BACKEND_ZSTD=y
+CONFIG_ZRAM_BACKEND_LZO=y
+CONFIG_ZRAM_DEF_COMP_ZSTD=y
+CONFIG_ZRAM_WRITEBACK=y
+CONFIG_ZRAM_MULTI_COMP=y
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BLK_DEV_DRBD=m
+CONFIG_BLK_DEV_NBD=m
+CONFIG_BLK_DEV_RAM=y
+CONFIG_ATA_OVER_ETH=m
+CONFIG_BLK_DEV_RBD=m
+CONFIG_BLK_DEV_NVME=y
+CONFIG_NVME_HWMON=y
+CONFIG_RP1_PIO=m
+CONFIG_WS2812_PIO_RP1=m
+CONFIG_SRAM=y
+CONFIG_EEPROM_AT24=m
+CONFIG_EEPROM_AT25=m
+CONFIG_TI_ST=m
+CONFIG_SCSI=y
+# CONFIG_SCSI_PROC_FS is not set
+CONFIG_BLK_DEV_SD=y
+CONFIG_CHR_DEV_ST=m
+CONFIG_BLK_DEV_SR=m
+CONFIG_CHR_DEV_SG=m
+CONFIG_SCSI_ISCSI_ATTRS=y
+CONFIG_ISCSI_TCP=m
+CONFIG_ISCSI_BOOT_SYSFS=m
+CONFIG_ATA=m
+CONFIG_SATA_AHCI=m
+CONFIG_SATA_MV=m
+CONFIG_MD=y
+CONFIG_BCACHE=m
+CONFIG_BLK_DEV_DM=m
+CONFIG_DM_CRYPT=m
+CONFIG_DM_SNAPSHOT=m
+CONFIG_DM_THIN_PROVISIONING=m
+CONFIG_DM_CACHE=m
+CONFIG_DM_WRITECACHE=m
+CONFIG_DM_MIRROR=m
+CONFIG_DM_LOG_USERSPACE=m
+CONFIG_DM_RAID=m
+CONFIG_DM_ZERO=m
+CONFIG_DM_MULTIPATH=m
+CONFIG_DM_DELAY=m
+CONFIG_DM_VERITY=m
+CONFIG_DM_INTEGRITY=m
+CONFIG_NETDEVICES=y
+CONFIG_BONDING=m
+CONFIG_DUMMY=m
+CONFIG_WIREGUARD=m
+CONFIG_IFB=m
+CONFIG_MACVLAN=m
+CONFIG_MACVTAP=m
+CONFIG_IPVLAN=m
+CONFIG_VXLAN=m
+CONFIG_NETCONSOLE=m
+CONFIG_TUN=m
+CONFIG_VETH=m
+CONFIG_NETKIT=y
+CONFIG_NET_VRF=m
+CONFIG_VSOCKMON=m
+CONFIG_BCMGENET=y
+CONFIG_MACB=y
+CONFIG_IGB=m
+CONFIG_IXGBE=m
+CONFIG_I40E=m
+CONFIG_IGC=m
+CONFIG_ENC28J60=m
+CONFIG_LAN743X=m
+CONFIG_QCA7000_SPI=m
+CONFIG_QCA7000_UART=m
+CONFIG_R8169=m
+CONFIG_MSE102X=m
+CONFIG_WIZNET_W5100=m
+CONFIG_WIZNET_W5100_SPI=m
+CONFIG_MICREL_PHY=y
+CONFIG_CAN_VCAN=m
+CONFIG_CAN_SLCAN=m
+CONFIG_CAN_MCP251X=m
+CONFIG_CAN_MCP251XFD=m
+CONFIG_CAN_8DEV_USB=m
+CONFIG_CAN_EMS_USB=m
+CONFIG_CAN_GS_USB=m
+CONFIG_CAN_PEAK_USB=m
+CONFIG_MDIO_BITBANG=m
+CONFIG_PPP=m
+CONFIG_PPP_BSDCOMP=m
+CONFIG_PPP_DEFLATE=m
+CONFIG_PPP_FILTER=y
+CONFIG_PPP_MPPE=m
+CONFIG_PPP_MULTILINK=y
+CONFIG_PPPOATM=m
+CONFIG_PPPOE=m
+CONFIG_PPPOL2TP=m
+CONFIG_PPP_ASYNC=m
+CONFIG_PPP_SYNC_TTY=m
+CONFIG_SLIP=m
+CONFIG_SLIP_COMPRESSED=y
+CONFIG_SLIP_SMART=y
+CONFIG_USB_CATC=m
+CONFIG_USB_KAWETH=m
+CONFIG_USB_PEGASUS=m
+CONFIG_USB_RTL8150=m
+CONFIG_USB_RTL8152=m
+CONFIG_USB_LAN78XX=y
+CONFIG_USB_USBNET=y
+CONFIG_USB_NET_AX8817X=m
+CONFIG_USB_NET_AX88179_178A=m
+CONFIG_USB_NET_CDCETHER=m
+CONFIG_USB_NET_CDC_EEM=m
+CONFIG_USB_NET_CDC_NCM=m
+CONFIG_USB_NET_HUAWEI_CDC_NCM=m
+CONFIG_USB_NET_CDC_MBIM=m
+CONFIG_USB_NET_DM9601=m
+CONFIG_USB_NET_SR9700=m
+CONFIG_USB_NET_SR9800=m
+CONFIG_USB_NET_SMSC75XX=m
+CONFIG_USB_NET_SMSC95XX=y
+CONFIG_USB_NET_GL620A=m
+CONFIG_USB_NET_NET1080=m
+CONFIG_USB_NET_PLUSB=m
+CONFIG_USB_NET_MCS7830=m
+CONFIG_USB_NET_RNDIS_HOST=m
+CONFIG_USB_NET_CDC_SUBSET=m
+CONFIG_USB_ALI_M5632=y
+CONFIG_USB_AN2720=y
+CONFIG_USB_EPSON2888=y
+CONFIG_USB_KC2190=y
+CONFIG_USB_NET_ZAURUS=m
+CONFIG_USB_NET_CX82310_ETH=m
+CONFIG_USB_NET_KALMIA=m
+CONFIG_USB_NET_QMI_WWAN=m
+CONFIG_USB_HSO=m
+CONFIG_USB_NET_INT51X1=m
+CONFIG_USB_IPHETH=m
+CONFIG_USB_SIERRA_NET=m
+CONFIG_USB_VL600=m
+CONFIG_USB_NET_AQC111=m
+CONFIG_ATH9K=m
+CONFIG_ATH9K_HTC=m
+CONFIG_CARL9170=m
+CONFIG_ATH6KL=m
+CONFIG_ATH6KL_USB=m
+CONFIG_AR5523=m
+CONFIG_AT76C50X_USB=m
+CONFIG_B43=m
+# CONFIG_B43_PHY_N is not set
+CONFIG_B43LEGACY=m
+CONFIG_BRCMFMAC=m
+CONFIG_BRCMFMAC_USB=y
+CONFIG_BRCMDBG=y
+CONFIG_IWLWIFI=m
+CONFIG_IWLDVM=m
+CONFIG_IWLMVM=m
+CONFIG_P54_COMMON=m
+CONFIG_P54_USB=m
+CONFIG_LIBERTAS=m
+CONFIG_LIBERTAS_USB=m
+CONFIG_LIBERTAS_SDIO=m
+CONFIG_LIBERTAS_THINFIRM=m
+CONFIG_LIBERTAS_THINFIRM_USB=m
+CONFIG_MWIFIEX=m
+CONFIG_MWIFIEX_SDIO=m
+CONFIG_MT7601U=m
+CONFIG_MT76x0U=m
+CONFIG_MT76x2U=m
+CONFIG_MT7921U=m
+CONFIG_MT7925E=m
+CONFIG_MT7925U=m
+CONFIG_RT2X00=m
+CONFIG_RT2500USB=m
+CONFIG_RT73USB=m
+CONFIG_RT2800USB=m
+CONFIG_RT2800USB_RT3573=y
+CONFIG_RT2800USB_RT53XX=y
+CONFIG_RT2800USB_RT55XX=y
+CONFIG_RT2800USB_UNKNOWN=y
+CONFIG_RTL8187=m
+CONFIG_RTL8192CU=m
+CONFIG_RTL8XXXU=m
+CONFIG_RTW88=m
+CONFIG_RTW88_8822BE=m
+CONFIG_RTW88_8822BU=m
+CONFIG_RTW88_8822CE=m
+CONFIG_RTW88_8822CU=m
+CONFIG_RTW88_8723DE=m
+CONFIG_RTW88_8723DU=m
+CONFIG_RTW88_8821CE=m
+CONFIG_RTW88_8821CU=m
+CONFIG_RTW89=m
+CONFIG_RTW89_8851BE=m
+CONFIG_RTW89_8852AE=m
+CONFIG_RTW89_8852BE=m
+CONFIG_RTW89_8852BTE=m
+CONFIG_RTW89_8852CE=m
+CONFIG_RTW89_8922AE=m
+CONFIG_ZD1211RW=m
+CONFIG_MAC80211_HWSIM=m
+CONFIG_IEEE802154_AT86RF230=m
+CONFIG_IEEE802154_MRF24J40=m
+CONFIG_IEEE802154_CC2520=m
+CONFIG_INPUT_MOUSEDEV=y
+CONFIG_INPUT_JOYDEV=m
+CONFIG_INPUT_EVDEV=y
+# CONFIG_KEYBOARD_ATKBD is not set
+CONFIG_KEYBOARD_GPIO=m
+CONFIG_KEYBOARD_TCA6416=m
+CONFIG_KEYBOARD_TCA8418=m
+CONFIG_KEYBOARD_MATRIX=m
+CONFIG_KEYBOARD_CAP11XX=m
+# CONFIG_INPUT_MOUSE is not set
+CONFIG_INPUT_JOYSTICK=y
+CONFIG_JOYSTICK_IFORCE=m
+CONFIG_JOYSTICK_IFORCE_USB=m
+CONFIG_JOYSTICK_XPAD=m
+CONFIG_JOYSTICK_XPAD_FF=y
+CONFIG_JOYSTICK_XPAD_LEDS=y
+CONFIG_JOYSTICK_PSXPAD_SPI=m
+CONFIG_JOYSTICK_PSXPAD_SPI_FF=y
+CONFIG_JOYSTICK_FSIA6B=m
+CONFIG_JOYSTICK_SENSEHAT=m
+CONFIG_INPUT_TOUCHSCREEN=y
+CONFIG_TOUCHSCREEN_ADS7846=m
+CONFIG_TOUCHSCREEN_ATMEL_MXT=m
+CONFIG_TOUCHSCREEN_EGALAX=m
+CONFIG_TOUCHSCREEN_EXC3000=m
+CONFIG_TOUCHSCREEN_GOODIX=m
+CONFIG_TOUCHSCREEN_ILI210X=m
+CONFIG_TOUCHSCREEN_ILITEK=m
+CONFIG_TOUCHSCREEN_ILITEK_V3=m
+CONFIG_TOUCHSCREEN_EDT_FT5X06=m
+CONFIG_TOUCHSCREEN_RASPBERRYPI_FW=m
+CONFIG_TOUCHSCREEN_USB_COMPOSITE=m
+CONFIG_TOUCHSCREEN_TSC2007=m
+CONFIG_TOUCHSCREEN_TSC2007_IIO=y
+CONFIG_TOUCHSCREEN_STMPE=m
+CONFIG_TOUCHSCREEN_IQS5XX=m
+CONFIG_INPUT_MISC=y
+CONFIG_INPUT_AD714X=m
+CONFIG_INPUT_ATI_REMOTE2=m
+CONFIG_INPUT_KEYSPAN_REMOTE=m
+CONFIG_INPUT_POWERMATE=m
+CONFIG_INPUT_YEALINK=m
+CONFIG_INPUT_CM109=m
+CONFIG_INPUT_UINPUT=m
+CONFIG_INPUT_PWM_BEEPER=m
+CONFIG_INPUT_GPIO_ROTARY_ENCODER=m
+CONFIG_INPUT_ADXL34X=m
+CONFIG_INPUT_CMA3000=m
+CONFIG_SERIO=m
+CONFIG_SERIO_RAW=m
+CONFIG_GAMEPORT=m
+CONFIG_BRCM_CHAR_DRIVERS=y
+CONFIG_BCM_VCIO=y
+# CONFIG_LEGACY_PTYS is not set
+CONFIG_SERIAL_8250=y
+# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
+CONFIG_SERIAL_8250_CONSOLE=y
+# CONFIG_SERIAL_8250_DMA is not set
+CONFIG_SERIAL_8250_NR_UARTS=5
+CONFIG_SERIAL_8250_RUNTIME_UARTS=0
+CONFIG_SERIAL_8250_EXTENDED=y
+CONFIG_SERIAL_8250_SHARE_IRQ=y
+CONFIG_SERIAL_8250_BCM2835AUX=y
+CONFIG_SERIAL_OF_PLATFORM=y
+CONFIG_SERIAL_AMBA_PL011=y
+CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
+CONFIG_SERIAL_SC16IS7XX=m
+CONFIG_SERIAL_RPI_FW=m
+CONFIG_SERIAL_DEV_BUS=y
+CONFIG_TTY_PRINTK=y
+CONFIG_HW_RANDOM=y
+CONFIG_TCG_TPM=m
+CONFIG_TCG_TIS_SPI=m
+CONFIG_TCG_TIS_I2C=m
+CONFIG_XILLYBUS=m
+CONFIG_XILLYBUS_PCIE=m
+CONFIG_XILLYUSB=m
+CONFIG_RASPBERRYPI_GPIOMEM=m
+CONFIG_I2C=y
+CONFIG_I2C_CHARDEV=m
+CONFIG_I2C_MUX_GPMUX=m
+CONFIG_I2C_MUX_PCA954x=m
+CONFIG_I2C_MUX_PINCTRL=m
+CONFIG_I2C_BCM2708=m
+CONFIG_I2C_BCM2835=m
+CONFIG_I2C_BRCMSTB=m
+CONFIG_I2C_DESIGNWARE_CORE=m
+CONFIG_I2C_GPIO=m
+CONFIG_I2C_ROBOTFUZZ_OSIF=m
+CONFIG_I2C_TINY_USB=m
+CONFIG_SPI=y
+CONFIG_SPI_BCM2835=m
+CONFIG_SPI_BCM2835AUX=m
+CONFIG_SPI_DESIGNWARE=m
+CONFIG_SPI_DW_DMA=y
+CONFIG_SPI_DW_MMIO=m
+CONFIG_SPI_GPIO=m
+CONFIG_SPI_RP2040_GPIO_BRIDGE=m
+CONFIG_SPI_SPIDEV=m
+CONFIG_SPI_SLAVE=y
+CONFIG_PPS_CLIENT_LDISC=m
+CONFIG_PPS_CLIENT_GPIO=m
+CONFIG_PINCTRL_MCP23S08=m
+CONFIG_PINCTRL_RP1=y
+CONFIG_PINCTRL_BCM2712=y
+CONFIG_GPIO_SYSFS=y
+CONFIG_GPIO_BCM_VIRT=y
+CONFIG_GPIO_FXL6408=m
+CONFIG_GPIO_MAX7300=m
+CONFIG_GPIO_PCA953X=m
+CONFIG_GPIO_PCA953X_IRQ=y
+CONFIG_GPIO_PCF857X=m
+CONFIG_GPIO_ARIZONA=m
+CONFIG_GPIO_FSM=m
+CONFIG_GPIO_STMPE=y
+CONFIG_GPIO_MAX7301=m
+CONFIG_GPIO_MOCKUP=m
+CONFIG_W1=m
+CONFIG_W1_MASTER_DS2490=m
+CONFIG_W1_MASTER_DS2482=m
+CONFIG_W1_MASTER_GPIO=m
+CONFIG_W1_SLAVE_THERM=m
+CONFIG_W1_SLAVE_SMEM=m
+CONFIG_W1_SLAVE_DS2408=m
+CONFIG_W1_SLAVE_DS2413=m
+CONFIG_W1_SLAVE_DS2406=m
+CONFIG_W1_SLAVE_DS2423=m
+CONFIG_W1_SLAVE_DS2430=m
+CONFIG_W1_SLAVE_DS2431=m
+CONFIG_W1_SLAVE_DS2433=m
+CONFIG_W1_SLAVE_DS2438=m
+CONFIG_W1_SLAVE_DS2780=m
+CONFIG_W1_SLAVE_DS2781=m
+CONFIG_W1_SLAVE_DS28E04=m
+CONFIG_W1_SLAVE_DS28E17=m
+# CONFIG_POWER_RESET_BRCMSTB is not set
+CONFIG_POWER_RESET_GPIO=y
+CONFIG_RPI_POE_POWER=m
+CONFIG_BATTERY_DS2760=m
+CONFIG_BATTERY_MAX17040=m
+CONFIG_CHARGER_GPIO=m
+CONFIG_BATTERY_GAUGE_LTC2941=m
+CONFIG_SENSORS_ADT7410=m
+CONFIG_SENSORS_AHT10=m
+CONFIG_SENSORS_CHIPCAP2=m
+CONFIG_SENSORS_DRIVETEMP=m
+CONFIG_SENSORS_DS1621=m
+CONFIG_SENSORS_GPIO_FAN=m
+CONFIG_SENSORS_IIO_HWMON=m
+CONFIG_SENSORS_JC42=m
+CONFIG_SENSORS_POWERZ=m
+CONFIG_SENSORS_LM75=m
+CONFIG_SENSORS_PWM_FAN=m
+CONFIG_SENSORS_RASPBERRYPI_HWMON=m
+CONFIG_SENSORS_SHT21=m
+CONFIG_SENSORS_SHT3x=m
+CONFIG_SENSORS_SHT4x=m
+CONFIG_SENSORS_SHTC1=m
+CONFIG_SENSORS_EMC2305=m
+CONFIG_SENSORS_ADS7828=m
+CONFIG_SENSORS_INA2XX=m
+CONFIG_SENSORS_INA238=m
+CONFIG_SENSORS_TMP102=m
+CONFIG_SENSORS_RP1_ADC=m
+CONFIG_BCM2711_THERMAL=y
+CONFIG_BCM2835_THERMAL=y
+CONFIG_WATCHDOG=y
+CONFIG_WATCHDOG_SYSFS=y
+CONFIG_GPIO_WATCHDOG=m
+CONFIG_BCM2835_WDT=y
+CONFIG_MFD_RASPBERRYPI_POE_HAT=m
+CONFIG_MFD_STMPE=y
+CONFIG_STMPE_SPI=y
+CONFIG_MFD_SYSCON=y
+CONFIG_MFD_ARIZONA_I2C=m
+CONFIG_MFD_ARIZONA_SPI=m
+CONFIG_MFD_WM5102=y
+CONFIG_MFD_RP1=y
+CONFIG_REGULATOR=y
+CONFIG_REGULATOR_FIXED_VOLTAGE=y
+CONFIG_REGULATOR_ARIZONA_LDO1=m
+CONFIG_REGULATOR_ARIZONA_MICSUPP=m
+CONFIG_REGULATOR_GPIO=y
+CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY=m
+CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_V2=m
+CONFIG_REGULATOR_WAVESHARE_TOUCHSCREEN=m
+CONFIG_RC_CORE=y
+CONFIG_BPF_LIRC_MODE2=y
+CONFIG_LIRC=y
+CONFIG_RC_DECODERS=y
+CONFIG_IR_IMON_DECODER=m
+CONFIG_IR_JVC_DECODER=m
+CONFIG_IR_MCE_KBD_DECODER=m
+CONFIG_IR_NEC_DECODER=m
+CONFIG_IR_RC5_DECODER=m
+CONFIG_IR_RC6_DECODER=m
+CONFIG_IR_SANYO_DECODER=m
+CONFIG_IR_SHARP_DECODER=m
+CONFIG_IR_SONY_DECODER=m
+CONFIG_IR_XMP_DECODER=m
+CONFIG_RC_DEVICES=y
+CONFIG_IR_GPIO_CIR=m
+CONFIG_IR_GPIO_TX=m
+CONFIG_IR_IGUANA=m
+CONFIG_IR_IMON=m
+CONFIG_IR_MCEUSB=m
+CONFIG_IR_PWM_TX=m
+CONFIG_IR_REDRAT3=m
+CONFIG_IR_STREAMZAP=m
+CONFIG_IR_TOY=m
+CONFIG_IR_TTUSBIR=m
+CONFIG_RC_ATI_REMOTE=m
+CONFIG_RC_LOOPBACK=m
+CONFIG_MEDIA_CEC_RC=y
+CONFIG_MEDIA_SUPPORT=m
+CONFIG_MEDIA_USB_SUPPORT=y
+CONFIG_USB_GSPCA=m
+CONFIG_USB_GSPCA_BENQ=m
+CONFIG_USB_GSPCA_CONEX=m
+CONFIG_USB_GSPCA_CPIA1=m
+CONFIG_USB_GSPCA_DTCS033=m
+CONFIG_USB_GSPCA_ETOMS=m
+CONFIG_USB_GSPCA_FINEPIX=m
+CONFIG_USB_GSPCA_JEILINJ=m
+CONFIG_USB_GSPCA_JL2005BCD=m
+CONFIG_USB_GSPCA_KINECT=m
+CONFIG_USB_GSPCA_KONICA=m
+CONFIG_USB_GSPCA_MARS=m
+CONFIG_USB_GSPCA_MR97310A=m
+CONFIG_USB_GSPCA_NW80X=m
+CONFIG_USB_GSPCA_OV519=m
+CONFIG_USB_GSPCA_OV534=m
+CONFIG_USB_GSPCA_OV534_9=m
+CONFIG_USB_GSPCA_PAC207=m
+CONFIG_USB_GSPCA_PAC7302=m
+CONFIG_USB_GSPCA_PAC7311=m
+CONFIG_USB_GSPCA_SE401=m
+CONFIG_USB_GSPCA_SN9C2028=m
+CONFIG_USB_GSPCA_SN9C20X=m
+CONFIG_USB_GSPCA_SONIXB=m
+CONFIG_USB_GSPCA_SONIXJ=m
+CONFIG_USB_GSPCA_SPCA1528=m
+CONFIG_USB_GSPCA_SPCA500=m
+CONFIG_USB_GSPCA_SPCA501=m
+CONFIG_USB_GSPCA_SPCA505=m
+CONFIG_USB_GSPCA_SPCA506=m
+CONFIG_USB_GSPCA_SPCA508=m
+CONFIG_USB_GSPCA_SPCA561=m
+CONFIG_USB_GSPCA_SQ905=m
+CONFIG_USB_GSPCA_SQ905C=m
+CONFIG_USB_GSPCA_SQ930X=m
+CONFIG_USB_GSPCA_STK014=m
+CONFIG_USB_GSPCA_STK1135=m
+CONFIG_USB_GSPCA_STV0680=m
+CONFIG_USB_GSPCA_SUNPLUS=m
+CONFIG_USB_GSPCA_T613=m
+CONFIG_USB_GSPCA_TOPRO=m
+CONFIG_USB_GSPCA_TOUPTEK=m
+CONFIG_USB_GSPCA_TV8532=m
+CONFIG_USB_GSPCA_VC032X=m
+CONFIG_USB_GSPCA_VICAM=m
+CONFIG_USB_GSPCA_XIRLINK_CIT=m
+CONFIG_USB_GSPCA_ZC3XX=m
+CONFIG_USB_GL860=m
+CONFIG_USB_M5602=m
+CONFIG_USB_STV06XX=m
+CONFIG_USB_PWC=m
+CONFIG_USB_S2255=m
+CONFIG_VIDEO_USBTV=m
+CONFIG_USB_VIDEO_CLASS=m
+CONFIG_VIDEO_GO7007=m
+CONFIG_VIDEO_GO7007_USB=m
+CONFIG_VIDEO_GO7007_USB_S2250_BOARD=m
+CONFIG_VIDEO_HDPVR=m
+CONFIG_VIDEO_PVRUSB2=m
+CONFIG_VIDEO_STK1160=m
+CONFIG_VIDEO_AU0828=m
+CONFIG_VIDEO_AU0828_RC=y
+CONFIG_VIDEO_CX231XX=m
+CONFIG_VIDEO_CX231XX_ALSA=m
+CONFIG_VIDEO_CX231XX_DVB=m
+CONFIG_DVB_AS102=m
+CONFIG_DVB_B2C2_FLEXCOP_USB=m
+CONFIG_DVB_USB_V2=m
+CONFIG_DVB_USB_AF9015=m
+CONFIG_DVB_USB_AF9035=m
+CONFIG_DVB_USB_ANYSEE=m
+CONFIG_DVB_USB_AU6610=m
+CONFIG_DVB_USB_AZ6007=m
+CONFIG_DVB_USB_CE6230=m
+CONFIG_DVB_USB_DVBSKY=m
+CONFIG_DVB_USB_EC168=m
+CONFIG_DVB_USB_GL861=m
+CONFIG_DVB_USB_LME2510=m
+CONFIG_DVB_USB_MXL111SF=m
+CONFIG_DVB_USB_RTL28XXU=m
+CONFIG_DVB_USB=m
+CONFIG_DVB_USB_A800=m
+CONFIG_DVB_USB_AF9005=m
+CONFIG_DVB_USB_AF9005_REMOTE=m
+CONFIG_DVB_USB_AZ6027=m
+CONFIG_DVB_USB_CINERGY_T2=m
+CONFIG_DVB_USB_CXUSB=m
+CONFIG_DVB_USB_DIB0700=m
+CONFIG_DVB_USB_DIBUSB_MB=m
+CONFIG_DVB_USB_DIBUSB_MB_FAULTY=y
+CONFIG_DVB_USB_DIBUSB_MC=m
+CONFIG_DVB_USB_DIGITV=m
+CONFIG_DVB_USB_DTT200U=m
+CONFIG_DVB_USB_DTV5100=m
+CONFIG_DVB_USB_DW2102=m
+CONFIG_DVB_USB_GP8PSK=m
+CONFIG_DVB_USB_M920X=m
+CONFIG_DVB_USB_NOVA_T_USB2=m
+CONFIG_DVB_USB_OPERA1=m
+CONFIG_DVB_USB_PCTV452E=m
+CONFIG_DVB_USB_TECHNISAT_USB2=m
+CONFIG_DVB_USB_TTUSB2=m
+CONFIG_DVB_USB_UMT_010=m
+CONFIG_DVB_USB_VP702X=m
+CONFIG_DVB_USB_VP7045=m
+CONFIG_SMS_USB_DRV=m
+CONFIG_VIDEO_EM28XX=m
+CONFIG_VIDEO_EM28XX_V4L2=m
+CONFIG_VIDEO_EM28XX_ALSA=m
+CONFIG_VIDEO_EM28XX_DVB=m
+CONFIG_MEDIA_PCI_SUPPORT=y
+CONFIG_RADIO_SAA7706H=m
+CONFIG_RADIO_SHARK=m
+CONFIG_RADIO_SHARK2=m
+CONFIG_RADIO_SI4713=m
+CONFIG_RADIO_TEA5764=m
+CONFIG_RADIO_TEF6862=m
+CONFIG_RADIO_WL1273=m
+CONFIG_USB_DSBR=m
+CONFIG_USB_KEENE=m
+CONFIG_USB_MA901=m
+CONFIG_USB_MR800=m
+CONFIG_RADIO_SI470X=m
+CONFIG_USB_SI470X=m
+CONFIG_I2C_SI470X=m
+CONFIG_I2C_SI4713=m
+CONFIG_RADIO_WL128X=m
+CONFIG_V4L_PLATFORM_DRIVERS=y
+CONFIG_VIDEO_MUX=m
+CONFIG_VIDEO_BCM2835_UNICAM_LEGACY=m
+CONFIG_VIDEO_BCM2835_UNICAM=m
+CONFIG_VIDEO_RPI_HEVC_DEC=m
+CONFIG_VIDEO_RASPBERRYPI_PISP_BE=m
+CONFIG_VIDEO_RP1_CFE=m
+CONFIG_V4L_TEST_DRIVERS=y
+CONFIG_VIDEO_VIM2M=m
+CONFIG_VIDEO_VICODEC=m
+CONFIG_VIDEO_VIMC=m
+CONFIG_VIDEO_VIVID=m
+CONFIG_VIDEO_ARDUCAM_64MP=m
+CONFIG_VIDEO_ARDUCAM_PIVARIETY=m
+CONFIG_VIDEO_IMX219=m
+CONFIG_VIDEO_IMX258=m
+CONFIG_VIDEO_IMX283=m
+CONFIG_VIDEO_IMX290=m
+CONFIG_VIDEO_IMX296=m
+CONFIG_VIDEO_IMX335=m
+CONFIG_VIDEO_IMX415=m
+CONFIG_VIDEO_IMX477=m
+CONFIG_VIDEO_IMX500=m
+CONFIG_VIDEO_IMX519=m
+CONFIG_VIDEO_IMX708=m
+CONFIG_VIDEO_MIRA220=m
+CONFIG_VIDEO_MT9V011=m
+CONFIG_VIDEO_OV2311=m
+CONFIG_VIDEO_OV5647=m
+CONFIG_VIDEO_OV64A40=m
+CONFIG_VIDEO_OV7251=m
+CONFIG_VIDEO_OV7640=m
+CONFIG_VIDEO_OV9282=m
+CONFIG_VIDEO_AD5398=m
+CONFIG_VIDEO_AK7375=m
+CONFIG_VIDEO_BU64754=m
+CONFIG_VIDEO_DW9807_VCM=m
+CONFIG_VIDEO_SONY_BTF_MPX=m
+CONFIG_VIDEO_UDA1342=m
+CONFIG_VIDEO_ADV7180=m
+CONFIG_VIDEO_TC358743=m
+CONFIG_VIDEO_TVP5150=m
+CONFIG_VIDEO_TW2804=m
+CONFIG_VIDEO_TW9903=m
+CONFIG_VIDEO_TW9906=m
+CONFIG_VIDEO_IRS1125=m
+CONFIG_VIDEO_I2C=m
+CONFIG_AUXDISPLAY=y
+CONFIG_HD44780=m
+CONFIG_DRM=m
+CONFIG_DRM_LOAD_EDID_FIRMWARE=y
+CONFIG_DRM_UDL=m
+CONFIG_DRM_PANEL_LVDS=m
+CONFIG_DRM_PANEL_ILITEK_IL79600A=m
+CONFIG_DRM_PANEL_ILITEK_ILI9806E=m
+CONFIG_DRM_PANEL_ILITEK_ILI9806E_SPI=m
+CONFIG_DRM_PANEL_ILITEK_ILI9881C=m
+CONFIG_DRM_PANEL_JDI_LT070ME05000=m
+CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN=m
+CONFIG_DRM_PANEL_SITRONIX_ST7701=m
+CONFIG_DRM_PANEL_SIMPLE=m
+CONFIG_DRM_PANEL_TPO_Y17P=m
+CONFIG_DRM_PANEL_WAVESHARE_TOUCHSCREEN=m
+CONFIG_DRM_PANEL_WAVESHARE_TOUCHSCREEN_V2=m
+CONFIG_DRM_DISPLAY_CONNECTOR=m
+CONFIG_DRM_SIMPLE_BRIDGE=m
+CONFIG_DRM_TOSHIBA_TC358762=m
+CONFIG_DRM_TI_SN65DSI83=m
+CONFIG_DRM_V3D=m
+CONFIG_DRM_VC4=m
+CONFIG_DRM_VC4_HDMI_CEC=y
+CONFIG_DRM_RP1_DSI=m
+CONFIG_DRM_RP1_DPI=m
+CONFIG_DRM_RP1_VEC=m
+CONFIG_DRM_PANEL_MIPI_DBI=m
+CONFIG_TINYDRM_HX8357D=m
+CONFIG_TINYDRM_ILI9225=m
+CONFIG_TINYDRM_ILI9341=m
+CONFIG_TINYDRM_ILI9486=m
+CONFIG_TINYDRM_MI0283QT=m
+CONFIG_TINYDRM_REPAPER=m
+CONFIG_TINYDRM_ST7586=m
+CONFIG_TINYDRM_ST7735R=m
+CONFIG_DRM_GUD=m
+CONFIG_DRM_SSD130X=m
+CONFIG_DRM_SSD130X_I2C=m
+CONFIG_DRM_SSD130X_SPI=m
+CONFIG_FB=y
+CONFIG_FB_BCM2708=y
+CONFIG_FB_SIMPLE=y
+CONFIG_FB_SSD1307=m
+CONFIG_FB_RPISENSE=m
+CONFIG_BACKLIGHT_PWM=m
+CONFIG_BACKLIGHT_RPI=m
+CONFIG_BACKLIGHT_LM3630A=m
+CONFIG_BACKLIGHT_GPIO=m
+CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y
+CONFIG_LOGO=y
+# CONFIG_LOGO_LINUX_MONO is not set
+# CONFIG_LOGO_LINUX_VGA16 is not set
+CONFIG_SOUND=y
+CONFIG_SND=m
+CONFIG_SND_OSSEMUL=y
+CONFIG_SND_PCM_OSS=m
+CONFIG_SND_HRTIMER=m
+CONFIG_SND_DYNAMIC_MINORS=y
+CONFIG_SND_SEQUENCER=m
+CONFIG_SND_SEQ_DUMMY=m
+CONFIG_SND_DUMMY=m
+CONFIG_SND_ALOOP=m
+CONFIG_SND_VIRMIDI=m
+CONFIG_SND_MTPAV=m
+CONFIG_SND_SERIAL_U16550=m
+CONFIG_SND_MPU401=m
+CONFIG_SND_PIMIDI=m
+CONFIG_SND_PISOUND_MICRO=m
+CONFIG_SND_USB_AUDIO=m
+CONFIG_SND_USB_UA101=m
+CONFIG_SND_USB_CAIAQ=m
+CONFIG_SND_USB_CAIAQ_INPUT=y
+CONFIG_SND_USB_6FIRE=m
+CONFIG_SND_USB_HIFACE=m
+CONFIG_SND_USB_TONEPORT=m
+CONFIG_SND_SOC=m
+CONFIG_SND_BCM2835_SOC_I2S=m
+CONFIG_SND_BCM2708_SOC_CHIPDIP_DAC=m
+CONFIG_SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_ADC=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DAC=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUS=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_STUDIO_DAC8X=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DIGI=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_AMP=m
+CONFIG_SND_BCM2708_SOC_PIFI_40=m
+CONFIG_SND_BCM2708_SOC_RPI_CIRRUS=m
+CONFIG_SND_BCM2708_SOC_RPI_DAC=m
+CONFIG_SND_BCM2708_SOC_RPI_PROTO=m
+CONFIG_SND_BCM2708_SOC_JUSTBOOM_BOTH=m
+CONFIG_SND_BCM2708_SOC_JUSTBOOM_DAC=m
+CONFIG_SND_BCM2708_SOC_JUSTBOOM_DIGI=m
+CONFIG_SND_BCM2708_SOC_IQAUDIO_CODEC=m
+CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC=m
+CONFIG_SND_BCM2708_SOC_IQAUDIO_DIGI=m
+CONFIG_SND_BCM2708_SOC_I_SABRE_Q2M=m
+CONFIG_SND_BCM2708_SOC_ADAU1977_ADC=m
+CONFIG_SND_AUDIOINJECTOR_PI_SOUNDCARD=m
+CONFIG_SND_AUDIOINJECTOR_OCTO_SOUNDCARD=m
+CONFIG_SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD=m
+CONFIG_SND_AUDIOSENSE_PI=m
+CONFIG_SND_DIGIDAC1_SOUNDCARD=m
+CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO=m
+CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO_V2=m
+CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC=m
+CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS=m
+CONFIG_SND_BCM2708_SOC_ALLO_BOSS_DAC=m
+CONFIG_SND_BCM2708_SOC_ALLO_BOSS2_DAC=m
+CONFIG_SND_BCM2708_SOC_ALLO_DIGIONE=m
+CONFIG_SND_BCM2708_SOC_ALLO_KATANA_DAC=m
+CONFIG_SND_BCM2708_SOC_FE_PI_AUDIO=m
+CONFIG_SND_PISOUND=m
+CONFIG_SND_DACBERRY400=m
+CONFIG_SND_DESIGNWARE_I2S=m
+CONFIG_SND_DESIGNWARE_PCM=y
+CONFIG_SND_RP1_AUDIO_OUT=m
+CONFIG_SND_SOC_AD193X_SPI=m
+CONFIG_SND_SOC_AD193X_I2C=m
+CONFIG_SND_SOC_ADAU1701=m
+CONFIG_SND_SOC_ADAU7002=m
+CONFIG_SND_SOC_AK4554=m
+CONFIG_SND_SOC_CS4265=m
+CONFIG_SND_SOC_ICS43432=m
+CONFIG_SND_SOC_MA120X0P=m
+CONFIG_SND_SOC_MAX98357A=m
+CONFIG_SND_SOC_PCM3168A_I2C=m
+CONFIG_SND_SOC_TLV320AIC23_I2C=m
+CONFIG_SND_SOC_WM8804_I2C=m
+CONFIG_SND_SOC_WM8904=m
+CONFIG_SND_SOC_WM8960=m
+CONFIG_SND_SIMPLE_CARD=m
+CONFIG_HID_BATTERY_STRENGTH=y
+CONFIG_HIDRAW=y
+CONFIG_UHID=m
+CONFIG_HID_A4TECH=m
+CONFIG_HID_ACRUX=m
+CONFIG_HID_APPLE=m
+CONFIG_HID_ASUS=m
+CONFIG_HID_BELKIN=m
+CONFIG_HID_BETOP_FF=m
+CONFIG_HID_BIGBEN_FF=m
+CONFIG_HID_CHERRY=m
+CONFIG_HID_CHICONY=m
+CONFIG_HID_CP2112=m
+CONFIG_HID_CYPRESS=m
+CONFIG_HID_DRAGONRISE=m
+CONFIG_HID_EMS_FF=m
+CONFIG_HID_ELECOM=m
+CONFIG_HID_ELO=m
+CONFIG_HID_EZKEY=m
+CONFIG_HID_GEMBIRD=m
+CONFIG_HID_HOLTEK=m
+CONFIG_HID_KEYTOUCH=m
+CONFIG_HID_KYE=m
+CONFIG_HID_UCLOGIC=m
+CONFIG_HID_WALTOP=m
+CONFIG_HID_GYRATION=m
+CONFIG_HID_TWINHAN=m
+CONFIG_HID_KENSINGTON=m
+CONFIG_HID_LCPOWER=m
+CONFIG_HID_LOGITECH=m
+CONFIG_HID_LOGITECH_DJ=m
+CONFIG_LOGITECH_FF=y
+CONFIG_LOGIRUMBLEPAD2_FF=y
+CONFIG_LOGIG940_FF=y
+CONFIG_HID_MAGICMOUSE=m
+CONFIG_HID_MICROSOFT=m
+CONFIG_HID_MONTEREY=m
+CONFIG_HID_MULTITOUCH=m
+CONFIG_HID_NINTENDO=m
+CONFIG_NINTENDO_FF=y
+CONFIG_HID_NTRIG=m
+CONFIG_HID_ORTEK=m
+CONFIG_HID_PANTHERLORD=m
+CONFIG_HID_PETALYNX=m
+CONFIG_HID_PICOLCD=m
+CONFIG_HID_PLAYSTATION=m
+CONFIG_PLAYSTATION_FF=y
+CONFIG_HID_ROCCAT=m
+CONFIG_HID_SAMSUNG=m
+CONFIG_HID_SONY=m
+CONFIG_SONY_FF=y
+CONFIG_HID_SPEEDLINK=m
+CONFIG_HID_STEAM=m
+CONFIG_HID_SUNPLUS=m
+CONFIG_HID_GREENASIA=m
+CONFIG_HID_SMARTJOYPLUS=m
+CONFIG_HID_TOPSEED=m
+CONFIG_HID_THINGM=m
+CONFIG_HID_THRUSTMASTER=m
+CONFIG_HID_WACOM=m
+CONFIG_HID_WIIMOTE=m
+CONFIG_HID_XINMO=m
+CONFIG_HID_ZEROPLUS=m
+CONFIG_HID_ZYDACRON=m
+CONFIG_HID_PID=y
+CONFIG_USB_HIDDEV=y
+CONFIG_I2C_HID_OF=m
+CONFIG_USB=y
+CONFIG_USB_ANNOUNCE_NEW_DEVICES=y
+CONFIG_USB_MON=m
+CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_XHCI_PCI_RENESAS=m
+CONFIG_USB_DWCOTG=y
+CONFIG_USB_PRINTER=m
+CONFIG_USB_TMC=m
+CONFIG_USB_STORAGE=y
+CONFIG_USB_STORAGE_REALTEK=m
+CONFIG_USB_STORAGE_DATAFAB=m
+CONFIG_USB_STORAGE_FREECOM=m
+CONFIG_USB_STORAGE_ISD200=m
+CONFIG_USB_STORAGE_USBAT=m
+CONFIG_USB_STORAGE_SDDR09=m
+CONFIG_USB_STORAGE_SDDR55=m
+CONFIG_USB_STORAGE_JUMPSHOT=m
+CONFIG_USB_STORAGE_ALAUDA=m
+CONFIG_USB_STORAGE_ONETOUCH=m
+CONFIG_USB_STORAGE_KARMA=m
+CONFIG_USB_STORAGE_CYPRESS_ATACB=m
+CONFIG_USB_STORAGE_ENE_UB6250=m
+CONFIG_USB_UAS=y
+CONFIG_USB_MDC800=m
+CONFIG_USB_MICROTEK=m
+CONFIG_USBIP_CORE=m
+CONFIG_USBIP_VHCI_HCD=m
+CONFIG_USBIP_HOST=m
+CONFIG_USBIP_VUDC=m
+CONFIG_USB_DWC3=y
+CONFIG_USB_DWC2=y
+CONFIG_USB_SERIAL=m
+CONFIG_USB_SERIAL_GENERIC=y
+CONFIG_USB_SERIAL_SIMPLE=m
+CONFIG_USB_SERIAL_AIRCABLE=m
+CONFIG_USB_SERIAL_ARK3116=m
+CONFIG_USB_SERIAL_BELKIN=m
+CONFIG_USB_SERIAL_CH341=m
+CONFIG_USB_SERIAL_WHITEHEAT=m
+CONFIG_USB_SERIAL_DIGI_ACCELEPORT=m
+CONFIG_USB_SERIAL_CP210X=m
+CONFIG_USB_SERIAL_CYPRESS_M8=m
+CONFIG_USB_SERIAL_EMPEG=m
+CONFIG_USB_SERIAL_FTDI_SIO=m
+CONFIG_USB_SERIAL_VISOR=m
+CONFIG_USB_SERIAL_IPAQ=m
+CONFIG_USB_SERIAL_IR=m
+CONFIG_USB_SERIAL_EDGEPORT=m
+CONFIG_USB_SERIAL_EDGEPORT_TI=m
+CONFIG_USB_SERIAL_F81232=m
+CONFIG_USB_SERIAL_F8153X=m
+CONFIG_USB_SERIAL_GARMIN=m
+CONFIG_USB_SERIAL_IPW=m
+CONFIG_USB_SERIAL_IUU=m
+CONFIG_USB_SERIAL_KEYSPAN_PDA=m
+CONFIG_USB_SERIAL_KEYSPAN=m
+CONFIG_USB_SERIAL_KLSI=m
+CONFIG_USB_SERIAL_KOBIL_SCT=m
+CONFIG_USB_SERIAL_MCT_U232=m
+CONFIG_USB_SERIAL_METRO=m
+CONFIG_USB_SERIAL_MOS7720=m
+CONFIG_USB_SERIAL_MOS7840=m
+CONFIG_USB_SERIAL_MXUPORT=m
+CONFIG_USB_SERIAL_NAVMAN=m
+CONFIG_USB_SERIAL_PL2303=m
+CONFIG_USB_SERIAL_OTI6858=m
+CONFIG_USB_SERIAL_QCAUX=m
+CONFIG_USB_SERIAL_QUALCOMM=m
+CONFIG_USB_SERIAL_SPCP8X5=m
+CONFIG_USB_SERIAL_SAFE=m
+CONFIG_USB_SERIAL_SIERRAWIRELESS=m
+CONFIG_USB_SERIAL_SYMBOL=m
+CONFIG_USB_SERIAL_TI=m
+CONFIG_USB_SERIAL_CYBERJACK=m
+CONFIG_USB_SERIAL_OPTION=m
+CONFIG_USB_SERIAL_OMNINET=m
+CONFIG_USB_SERIAL_OPTICON=m
+CONFIG_USB_SERIAL_XSENS_MT=m
+CONFIG_USB_SERIAL_WISHBONE=m
+CONFIG_USB_SERIAL_SSU100=m
+CONFIG_USB_SERIAL_QT2=m
+CONFIG_USB_SERIAL_UPD78F0730=m
+CONFIG_USB_SERIAL_XR=m
+CONFIG_USB_SERIAL_DEBUG=m
+CONFIG_USB_EMI62=m
+CONFIG_USB_EMI26=m
+CONFIG_USB_ADUTUX=m
+CONFIG_USB_SEVSEG=m
+CONFIG_USB_LEGOTOWER=m
+CONFIG_USB_LCD=m
+CONFIG_USB_CYPRESS_CY7C63=m
+CONFIG_USB_CYTHERM=m
+CONFIG_USB_IDMOUSE=m
+CONFIG_USB_APPLEDISPLAY=m
+CONFIG_USB_LD=m
+CONFIG_USB_TRANCEVIBRATOR=m
+CONFIG_USB_IOWARRIOR=m
+CONFIG_USB_TEST=m
+CONFIG_USB_ISIGHTFW=m
+CONFIG_USB_YUREX=m
+CONFIG_USB_ATM=m
+CONFIG_USB_SPEEDTOUCH=m
+CONFIG_USB_CXACRU=m
+CONFIG_USB_UEAGLEATM=m
+CONFIG_USB_XUSBATM=m
+CONFIG_NOP_USB_XCEIV=y
+CONFIG_USB_GADGET=y
+CONFIG_USB_CONFIGFS=m
+CONFIG_USB_CONFIGFS_SERIAL=y
+CONFIG_USB_CONFIGFS_ACM=y
+CONFIG_USB_CONFIGFS_OBEX=y
+CONFIG_USB_CONFIGFS_NCM=y
+CONFIG_USB_CONFIGFS_ECM=y
+CONFIG_USB_CONFIGFS_ECM_SUBSET=y
+CONFIG_USB_CONFIGFS_RNDIS=y
+CONFIG_USB_CONFIGFS_EEM=y
+CONFIG_USB_CONFIGFS_MASS_STORAGE=y
+CONFIG_USB_CONFIGFS_F_LB_SS=y
+CONFIG_USB_CONFIGFS_F_FS=y
+CONFIG_USB_CONFIGFS_F_UAC1=y
+CONFIG_USB_CONFIGFS_F_UAC2=y
+CONFIG_USB_CONFIGFS_F_MIDI=y
+CONFIG_USB_CONFIGFS_F_HID=y
+CONFIG_USB_CONFIGFS_F_UVC=y
+CONFIG_USB_CONFIGFS_F_PRINTER=y
+CONFIG_USB_ZERO=m
+CONFIG_USB_AUDIO=m
+CONFIG_USB_ETH=m
+CONFIG_USB_GADGETFS=m
+CONFIG_USB_MASS_STORAGE=m
+CONFIG_USB_G_SERIAL=m
+CONFIG_USB_MIDI_GADGET=m
+CONFIG_USB_G_PRINTER=m
+CONFIG_USB_CDC_COMPOSITE=m
+CONFIG_USB_G_ACM_MS=m
+CONFIG_USB_G_MULTI=m
+CONFIG_USB_G_HID=m
+CONFIG_USB_G_WEBCAM=m
+CONFIG_MMC=y
+CONFIG_MMC_BLOCK_MINORS=32
+CONFIG_MMC_BCM2835_MMC=y
+CONFIG_MMC_BCM2835_DMA=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_MMC_SDHCI_OF_DWCMSHC=m
+CONFIG_MMC_SDHCI_IPROC=y
+CONFIG_MMC_SPI=m
+CONFIG_MMC_HSQ=y
+CONFIG_MMC_BCM2835=y
+CONFIG_LEDS_CLASS=y
+CONFIG_LEDS_CLASS_MULTICOLOR=m
+CONFIG_LEDS_PCA9532=m
+CONFIG_LEDS_GPIO=y
+CONFIG_LEDS_PCA955X=m
+CONFIG_LEDS_PCA963X=m
+CONFIG_LEDS_PWM=y
+CONFIG_LEDS_IS31FL32XX=m
+CONFIG_LEDS_TRIGGER_TIMER=y
+CONFIG_LEDS_TRIGGER_ONESHOT=y
+CONFIG_LEDS_TRIGGER_HEARTBEAT=y
+CONFIG_LEDS_TRIGGER_BACKLIGHT=y
+CONFIG_LEDS_TRIGGER_CPU=y
+CONFIG_LEDS_TRIGGER_DEFAULT_ON=y
+CONFIG_LEDS_TRIGGER_TRANSIENT=m
+CONFIG_LEDS_TRIGGER_CAMERA=m
+CONFIG_LEDS_TRIGGER_INPUT=y
+CONFIG_LEDS_TRIGGER_PANIC=y
+CONFIG_LEDS_TRIGGER_NETDEV=m
+CONFIG_LEDS_TRIGGER_PATTERN=m
+CONFIG_LEDS_TRIGGER_ACTPWR=y
+CONFIG_ACCESSIBILITY=y
+CONFIG_SPEAKUP=m
+CONFIG_SPEAKUP_SYNTH_SOFT=m
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_DRV_ABX80X=m
+CONFIG_RTC_DRV_DS1307=m
+CONFIG_RTC_DRV_DS1374=m
+CONFIG_RTC_DRV_DS1672=m
+CONFIG_RTC_DRV_MAX6900=m
+CONFIG_RTC_DRV_RS5C372=m
+CONFIG_RTC_DRV_ISL1208=m
+CONFIG_RTC_DRV_ISL12022=m
+CONFIG_RTC_DRV_X1205=m
+CONFIG_RTC_DRV_PCF8523=m
+CONFIG_RTC_DRV_PCF85063=m
+CONFIG_RTC_DRV_PCF85363=m
+CONFIG_RTC_DRV_PCF8563=m
+CONFIG_RTC_DRV_PCF8583=m
+CONFIG_RTC_DRV_M41T80=m
+CONFIG_RTC_DRV_BQ32K=m
+CONFIG_RTC_DRV_S35390A=m
+CONFIG_RTC_DRV_FM3130=m
+CONFIG_RTC_DRV_RX8581=m
+CONFIG_RTC_DRV_RX8025=m
+CONFIG_RTC_DRV_EM3027=m
+CONFIG_RTC_DRV_RV3028=m
+CONFIG_RTC_DRV_RV3032=m
+CONFIG_RTC_DRV_RV8803=m
+CONFIG_RTC_DRV_SD3078=m
+CONFIG_RTC_DRV_M41T93=m
+CONFIG_RTC_DRV_M41T94=m
+CONFIG_RTC_DRV_DS1302=m
+CONFIG_RTC_DRV_DS1305=m
+CONFIG_RTC_DRV_DS1390=m
+CONFIG_RTC_DRV_R9701=m
+CONFIG_RTC_DRV_RX4581=m
+CONFIG_RTC_DRV_RS5C348=m
+CONFIG_RTC_DRV_MAX6902=m
+CONFIG_RTC_DRV_PCF2123=m
+CONFIG_RTC_DRV_DS3232=m
+CONFIG_RTC_DRV_PCF2127=m
+CONFIG_RTC_DRV_RV3029C2=m
+CONFIG_DMADEVICES=y
+CONFIG_DMA_BCM2835=y
+CONFIG_DW_AXI_DMAC=y
+CONFIG_DMA_BCM2708=y
+CONFIG_UDMABUF=y
+CONFIG_DMABUF_HEAPS=y
+CONFIG_DMABUF_HEAPS_SYSTEM=y
+CONFIG_DMABUF_HEAPS_CMA=y
+CONFIG_UIO=m
+CONFIG_UIO_PDRV_GENIRQ=m
+CONFIG_VHOST_NET=m
+CONFIG_VHOST_VSOCK=m
+CONFIG_VHOST_CROSS_ENDIAN_LEGACY=y
+CONFIG_STAGING=y
+CONFIG_VT6656=m
+CONFIG_STAGING_MEDIA=y
+CONFIG_STAGING_MEDIA_DEPRECATED=y
+CONFIG_FB_TFT=m
+CONFIG_FB_TFT_AGM1264K_FL=m
+CONFIG_FB_TFT_BD663474=m
+CONFIG_FB_TFT_HX8340BN=m
+CONFIG_FB_TFT_HX8347D=m
+CONFIG_FB_TFT_HX8353D=m
+CONFIG_FB_TFT_HX8357D=m
+CONFIG_FB_TFT_ILI9163=m
+CONFIG_FB_TFT_ILI9320=m
+CONFIG_FB_TFT_ILI9325=m
+CONFIG_FB_TFT_ILI9340=m
+CONFIG_FB_TFT_ILI9341=m
+CONFIG_FB_TFT_ILI9481=m
+CONFIG_FB_TFT_ILI9486=m
+CONFIG_FB_TFT_PCD8544=m
+CONFIG_FB_TFT_RA8875=m
+CONFIG_FB_TFT_S6D02A1=m
+CONFIG_FB_TFT_S6D1121=m
+CONFIG_FB_TFT_SH1106=m
+CONFIG_FB_TFT_SSD1289=m
+CONFIG_FB_TFT_SSD1306=m
+CONFIG_FB_TFT_SSD1331=m
+CONFIG_FB_TFT_SSD1351=m
+CONFIG_FB_TFT_ST7735R=m
+CONFIG_FB_TFT_ST7789V=m
+CONFIG_FB_TFT_TINYLCD=m
+CONFIG_FB_TFT_TLS8204=m
+CONFIG_FB_TFT_UC1611=m
+CONFIG_FB_TFT_UC1701=m
+CONFIG_FB_TFT_UPD161704=m
+CONFIG_BCM2835_VCHIQ=y
+CONFIG_SND_BCM2835=m
+CONFIG_VIDEO_BCM2835=m
+CONFIG_VIDEO_CODEC_BCM2835=m
+CONFIG_VIDEO_ISP_BCM2835=m
+CONFIG_COMMON_CLK_RP1=y
+CONFIG_COMMON_CLK_RP1_SDIO=y
+CONFIG_COMMON_CLK_SI5351=m
+CONFIG_CLK_RASPBERRYPI=y
+CONFIG_MAILBOX=y
+CONFIG_BCM2835_MBOX=y
+CONFIG_MBOX_RP1=m
+CONFIG_BCM2712_IOMMU=y
+CONFIG_RASPBERRYPI_POWER=y
+CONFIG_IIO=m
+CONFIG_IIO_BUFFER_CB=m
+CONFIG_IIO_SW_TRIGGER=m
+CONFIG_ADXL355_I2C=m
+CONFIG_AD799X=m
+CONFIG_MCP320X=m
+CONFIG_MCP3422=m
+CONFIG_TI_ADS1015=m
+CONFIG_BME680=m
+CONFIG_CCS811=m
+CONFIG_SCD4X=m
+CONFIG_SENSIRION_SGP30=m
+CONFIG_SPS30_I2C=m
+CONFIG_MAX30102=m
+CONFIG_DHT11=m
+CONFIG_HDC100X=m
+CONFIG_HDC3020=m
+CONFIG_HTS221=m
+CONFIG_HTU21=m
+CONFIG_SI7020=m
+CONFIG_BOSCH_BNO055_I2C=m
+CONFIG_INV_MPU6050_I2C=m
+CONFIG_APDS9960=m
+CONFIG_AS73211=m
+CONFIG_BH1750=m
+CONFIG_TCS3472=m
+CONFIG_TSL4531=m
+CONFIG_VEML6030=m
+CONFIG_VEML6040=m
+CONFIG_VEML6070=m
+CONFIG_VEML6075=m
+CONFIG_IIO_HRTIMER_TRIGGER=m
+CONFIG_IIO_INTERRUPT_TRIGGER=m
+CONFIG_IIO_SYSFS_TRIGGER=m
+CONFIG_BMP280=m
+CONFIG_MS5637=m
+CONFIG_MAXIM_THERMOCOUPLE=m
+CONFIG_TMP117=m
+CONFIG_MAX31856=m
+CONFIG_PWM=y
+CONFIG_PWM_BCM2835=m
+CONFIG_PWM_BRCMSTB=y
+CONFIG_PWM_GPIO=m
+CONFIG_PWM_PCA9685=m
+CONFIG_PWM_PIO_RP1=m
+CONFIG_PWM_RASPBERRYPI_POE=m
+CONFIG_PWM_RP1=y
+CONFIG_BCM2712_MIP=y
+CONFIG_RPI_AXIPERF=m
+CONFIG_ANDROID_BINDER_IPC=y
+CONFIG_ANDROID_BINDERFS=y
+CONFIG_NVMEM_RMEM=m
+CONFIG_MUX_GPIO=m
+CONFIG_EXT4_FS=y
+CONFIG_EXT4_FS_POSIX_ACL=y
+CONFIG_EXT4_FS_SECURITY=y
+CONFIG_REISERFS_FS=m
+CONFIG_REISERFS_FS_XATTR=y
+CONFIG_REISERFS_FS_POSIX_ACL=y
+CONFIG_REISERFS_FS_SECURITY=y
+CONFIG_JFS_FS=m
+CONFIG_JFS_POSIX_ACL=y
+CONFIG_JFS_SECURITY=y
+CONFIG_JFS_STATISTICS=y
+CONFIG_XFS_FS=m
+CONFIG_XFS_QUOTA=y
+CONFIG_XFS_POSIX_ACL=y
+CONFIG_XFS_RT=y
+CONFIG_GFS2_FS=m
+CONFIG_OCFS2_FS=m
+CONFIG_BTRFS_FS=m
+CONFIG_BTRFS_FS_POSIX_ACL=y
+CONFIG_NILFS2_FS=m
+CONFIG_F2FS_FS=y
+CONFIG_F2FS_FS_SECURITY=y
+CONFIG_BCACHEFS_FS=m
+CONFIG_BCACHEFS_QUOTA=y
+CONFIG_BCACHEFS_POSIX_ACL=y
+CONFIG_FS_ENCRYPTION=y
+CONFIG_FANOTIFY=y
+CONFIG_QFMT_V1=m
+CONFIG_QFMT_V2=m
+CONFIG_AUTOFS_FS=y
+CONFIG_FUSE_FS=m
+CONFIG_CUSE=m
+CONFIG_OVERLAY_FS=m
+CONFIG_FSCACHE=y
+CONFIG_FSCACHE_STATS=y
+CONFIG_CACHEFILES=m
+CONFIG_ISO9660_FS=m
+CONFIG_JOLIET=y
+CONFIG_ZISOFS=y
+CONFIG_UDF_FS=m
+CONFIG_MSDOS_FS=y
+CONFIG_VFAT_FS=y
+CONFIG_FAT_DEFAULT_IOCHARSET="ascii"
+CONFIG_EXFAT_FS=m
+CONFIG_NTFS3_FS=m
+CONFIG_TMPFS=y
+CONFIG_TMPFS_POSIX_ACL=y
+CONFIG_ECRYPT_FS=m
+CONFIG_HFS_FS=m
+CONFIG_HFSPLUS_FS=m
+CONFIG_JFFS2_FS=m
+CONFIG_JFFS2_SUMMARY=y
+CONFIG_UBIFS_FS=m
+CONFIG_SQUASHFS=m
+CONFIG_SQUASHFS_XATTR=y
+CONFIG_SQUASHFS_LZO=y
+CONFIG_SQUASHFS_XZ=y
+CONFIG_SQUASHFS_ZSTD=y
+CONFIG_PSTORE=y
+CONFIG_PSTORE_CONSOLE=y
+CONFIG_PSTORE_RAM=y
+CONFIG_EROFS_FS=m
+CONFIG_EROFS_FS_ZIP_ZSTD=y
+CONFIG_NFS_FS=y
+CONFIG_NFS_V2=y
+CONFIG_NFS_V3_ACL=y
+CONFIG_NFS_V4=y
+CONFIG_NFS_SWAP=y
+CONFIG_NFS_V4_1=y
+CONFIG_NFS_V4_2=y
+CONFIG_ROOT_NFS=y
+CONFIG_NFSD=m
+CONFIG_NFSD_V2=y
+CONFIG_NFSD_V2_ACL=y
+CONFIG_NFSD_V3_ACL=y
+CONFIG_NFSD_V4=y
+CONFIG_CEPH_FS=m
+CONFIG_CIFS=m
+CONFIG_CIFS_UPCALL=y
+CONFIG_CIFS_XATTR=y
+CONFIG_CIFS_POSIX=y
+CONFIG_CIFS_DFS_UPCALL=y
+CONFIG_CIFS_FSCACHE=y
+CONFIG_SMB_SERVER=m
+CONFIG_9P_FS=m
+CONFIG_9P_FS_POSIX_ACL=y
+CONFIG_NLS_DEFAULT="utf8"
+CONFIG_NLS_CODEPAGE_437=y
+CONFIG_NLS_CODEPAGE_737=m
+CONFIG_NLS_CODEPAGE_775=m
+CONFIG_NLS_CODEPAGE_850=m
+CONFIG_NLS_CODEPAGE_852=m
+CONFIG_NLS_CODEPAGE_855=m
+CONFIG_NLS_CODEPAGE_857=m
+CONFIG_NLS_CODEPAGE_860=m
+CONFIG_NLS_CODEPAGE_861=m
+CONFIG_NLS_CODEPAGE_862=m
+CONFIG_NLS_CODEPAGE_863=m
+CONFIG_NLS_CODEPAGE_864=m
+CONFIG_NLS_CODEPAGE_865=m
+CONFIG_NLS_CODEPAGE_866=m
+CONFIG_NLS_CODEPAGE_869=m
+CONFIG_NLS_CODEPAGE_936=m
+CONFIG_NLS_CODEPAGE_950=m
+CONFIG_NLS_CODEPAGE_932=m
+CONFIG_NLS_CODEPAGE_949=m
+CONFIG_NLS_CODEPAGE_874=m
+CONFIG_NLS_ISO8859_8=m
+CONFIG_NLS_CODEPAGE_1250=m
+CONFIG_NLS_CODEPAGE_1251=m
+CONFIG_NLS_ASCII=y
+CONFIG_NLS_ISO8859_1=m
+CONFIG_NLS_ISO8859_2=m
+CONFIG_NLS_ISO8859_3=m
+CONFIG_NLS_ISO8859_4=m
+CONFIG_NLS_ISO8859_5=m
+CONFIG_NLS_ISO8859_6=m
+CONFIG_NLS_ISO8859_7=m
+CONFIG_NLS_ISO8859_9=m
+CONFIG_NLS_ISO8859_13=m
+CONFIG_NLS_ISO8859_14=m
+CONFIG_NLS_ISO8859_15=m
+CONFIG_NLS_KOI8_R=m
+CONFIG_NLS_KOI8_U=m
+CONFIG_DLM=m
+CONFIG_UNICODE=m
+CONFIG_KEY_DH_OPERATIONS=y
+CONFIG_SECURITY=y
+CONFIG_SECURITY_APPARMOR=y
+CONFIG_LSM=""
+CONFIG_CRYPTO_USER=m
+CONFIG_CRYPTO_CRYPTD=m
+CONFIG_CRYPTO_AES=m
+CONFIG_CRYPTO_CAST5=m
+CONFIG_CRYPTO_DES=y
+CONFIG_CRYPTO_TWOFISH=m
+CONFIG_CRYPTO_ADIANTUM=m
+CONFIG_CRYPTO_CBC=m
+CONFIG_CRYPTO_CHACHA20POLY1305=m
+CONFIG_CRYPTO_MD4=m
+CONFIG_CRYPTO_SHA512=m
+CONFIG_CRYPTO_WP512=m
+CONFIG_CRYPTO_XCBC=m
+CONFIG_CRYPTO_LZ4=m
+CONFIG_CRYPTO_LZ4HC=m
+CONFIG_CRYPTO_USER_API_HASH=m
+CONFIG_CRYPTO_USER_API_SKCIPHER=m
+CONFIG_CRYPTO_USER_API_RNG=m
+CONFIG_CRYPTO_USER_API_AEAD=m
+CONFIG_CRYPTO_NHPOLY1305_NEON=m
+CONFIG_CRYPTO_GHASH_ARM64_CE=m
+CONFIG_CRYPTO_SHA1_ARM64_CE=m
+CONFIG_CRYPTO_SHA2_ARM64_CE=m
+CONFIG_CRYPTO_SHA512_ARM64_CE=m
+CONFIG_CRYPTO_SHA3_ARM64=m
+CONFIG_CRYPTO_SM3_ARM64_CE=m
+CONFIG_CRYPTO_AES_ARM64=m
+CONFIG_CRYPTO_AES_ARM64_BS=m
+CONFIG_CRYPTO_SM4_ARM64_CE=m
+CONFIG_CRYPTO_AES_ARM64_CE_CCM=m
+# CONFIG_CRYPTO_HW is not set
+CONFIG_PKCS8_PRIVATE_KEY_PARSER=m
+CONFIG_CRC_ITU_T=y
+CONFIG_LIBCRC32C=y
+CONFIG_DMA_CMA=y
+CONFIG_CMA_SIZE_MBYTES=5
+CONFIG_PRINTK_TIME=y
+CONFIG_BOOT_PRINTK_DELAY=y
+CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1f6
+CONFIG_KGDB=y
+CONFIG_KGDB_KDB=y
+CONFIG_KDB_KEYBOARD=y
+CONFIG_DEBUG_MEMORY_INIT=y
+CONFIG_DETECT_HUNG_TASK=y
+CONFIG_LATENCYTOP=y
+CONFIG_FUNCTION_PROFILER=y
+CONFIG_STACK_TRACER=y
+CONFIG_SCHED_TRACER=y
+CONFIG_FTRACE_SYSCALLS=y
+CONFIG_BLK_DEV_IO_TRACE=y
+# CONFIG_UPROBE_EVENTS is not set
+# CONFIG_STRICT_DEVMEM is not set
diff --git a/arch/arm64/configs/bcm2711_rt_defconfig b/arch/arm64/configs/bcm2711_rt_defconfig
new file mode 100644
index 000000000000..cf78cbc6fc02
--- /dev/null
+++ b/arch/arm64/configs/bcm2711_rt_defconfig
@@ -0,0 +1,1782 @@
+CONFIG_LOCALVERSION="-v8-rt"
+# CONFIG_LOCALVERSION_AUTO is not set
+CONFIG_SYSVIPC=y
+CONFIG_POSIX_MQUEUE=y
+CONFIG_GENERIC_IRQ_DEBUGFS=y
+CONFIG_NO_HZ=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_BPF_SYSCALL=y
+CONFIG_BPF_JIT=y
+CONFIG_PREEMPT_RT=y
+CONFIG_BSD_PROCESS_ACCT=y
+CONFIG_BSD_PROCESS_ACCT_V3=y
+CONFIG_TASKSTATS=y
+CONFIG_TASK_DELAY_ACCT=y
+CONFIG_TASK_XACCT=y
+CONFIG_TASK_IO_ACCOUNTING=y
+CONFIG_PSI=y
+CONFIG_PSI_DEFAULT_DISABLED=y
+CONFIG_IKCONFIG=m
+CONFIG_IKCONFIG_PROC=y
+CONFIG_MEMCG=y
+CONFIG_BLK_CGROUP=y
+CONFIG_CFS_BANDWIDTH=y
+CONFIG_CGROUP_PIDS=y
+CONFIG_CGROUP_FREEZER=y
+CONFIG_CPUSETS=y
+CONFIG_CPUSETS_V1=y
+CONFIG_CGROUP_DEVICE=y
+CONFIG_CGROUP_CPUACCT=y
+CONFIG_CGROUP_PERF=y
+CONFIG_CGROUP_BPF=y
+CONFIG_NAMESPACES=y
+CONFIG_USER_NS=y
+CONFIG_CHECKPOINT_RESTORE=y
+CONFIG_SCHED_AUTOGROUP=y
+CONFIG_BLK_DEV_INITRD=y
+CONFIG_EXPERT=y
+CONFIG_PROFILING=y
+CONFIG_ARCH_BCM=y
+CONFIG_ARCH_BCM2835=y
+CONFIG_ARCH_BRCMSTB=y
+CONFIG_ARM64_ERRATUM_834220=y
+CONFIG_ARM64_ERRATUM_2441007=y
+CONFIG_ARM64_ERRATUM_1286807=y
+CONFIG_ARM64_ERRATUM_1542419=y
+CONFIG_ARM64_ERRATUM_2441009=y
+# CONFIG_CAVIUM_ERRATUM_22375 is not set
+# CONFIG_CAVIUM_ERRATUM_23154 is not set
+# CONFIG_CAVIUM_ERRATUM_27456 is not set
+CONFIG_ARM64_VA_BITS_39=y
+CONFIG_NR_CPUS=4
+CONFIG_HOTPLUG_CPU=y
+CONFIG_NUMA=y
+CONFIG_COMPAT=y
+CONFIG_ARMV8_DEPRECATED=y
+CONFIG_SWP_EMULATION=y
+CONFIG_CP15_BARRIER_EMULATION=y
+CONFIG_SETEND_EMULATION=y
+CONFIG_RANDOMIZE_BASE=y
+CONFIG_CMDLINE="console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait"
+# CONFIG_SUSPEND is not set
+CONFIG_PM=y
+CONFIG_PM_DEBUG=y
+CONFIG_CPU_IDLE=y
+CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_STAT=y
+CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y
+CONFIG_CPU_FREQ_GOV_POWERSAVE=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
+CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y
+CONFIG_CPUFREQ_DT=y
+CONFIG_ARM_RASPBERRYPI_CPUFREQ=y
+CONFIG_VIRTUALIZATION=y
+CONFIG_KVM=y
+CONFIG_JUMP_LABEL=y
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_MODVERSIONS=y
+CONFIG_MODULE_SRCVERSION_ALL=y
+CONFIG_MODULE_COMPRESS=y
+CONFIG_MODULE_COMPRESS_XZ=y
+CONFIG_BLK_DEV_THROTTLING=y
+CONFIG_PARTITION_ADVANCED=y
+CONFIG_MAC_PARTITION=y
+CONFIG_BINFMT_MISC=m
+CONFIG_ZSWAP=y
+# CONFIG_COMPAT_BRK is not set
+CONFIG_CMA=y
+CONFIG_CMA_AREAS=7
+CONFIG_LRU_GEN=y
+CONFIG_LRU_GEN_ENABLED=y
+CONFIG_NUMA_EMU=y
+CONFIG_NET=y
+CONFIG_PACKET=y
+CONFIG_XFRM_USER=m
+CONFIG_XFRM_INTERFACE=m
+CONFIG_XFRM_SUB_POLICY=y
+CONFIG_XFRM_STATISTICS=y
+CONFIG_NET_KEY=m
+CONFIG_IP_MULTICAST=y
+CONFIG_IP_ADVANCED_ROUTER=y
+CONFIG_IP_MULTIPLE_TABLES=y
+CONFIG_IP_ROUTE_MULTIPATH=y
+CONFIG_IP_ROUTE_VERBOSE=y
+CONFIG_IP_PNP=y
+CONFIG_IP_PNP_DHCP=y
+CONFIG_IP_PNP_RARP=y
+CONFIG_NET_IPIP=m
+CONFIG_NET_IPGRE_DEMUX=m
+CONFIG_NET_IPGRE=m
+CONFIG_IP_MROUTE=y
+CONFIG_IP_MROUTE_MULTIPLE_TABLES=y
+CONFIG_IP_PIMSM_V1=y
+CONFIG_IP_PIMSM_V2=y
+CONFIG_NET_IPVTI=m
+CONFIG_NET_FOU_IP_TUNNELS=y
+CONFIG_INET_AH=m
+CONFIG_INET_ESP=m
+CONFIG_INET_IPCOMP=m
+CONFIG_INET_DIAG=m
+CONFIG_TCP_CONG_ADVANCED=y
+CONFIG_TCP_CONG_BBR=m
+CONFIG_IPV6=m
+CONFIG_IPV6_ROUTER_PREF=y
+CONFIG_IPV6_ROUTE_INFO=y
+CONFIG_INET6_AH=m
+CONFIG_INET6_ESP=m
+CONFIG_INET6_ESP_OFFLOAD=m
+CONFIG_INET6_IPCOMP=m
+CONFIG_IPV6_ILA=m
+CONFIG_IPV6_VTI=m
+CONFIG_IPV6_SIT_6RD=y
+CONFIG_IPV6_GRE=m
+CONFIG_IPV6_MULTIPLE_TABLES=y
+CONFIG_IPV6_SUBTREES=y
+CONFIG_IPV6_MROUTE=y
+CONFIG_IPV6_MROUTE_MULTIPLE_TABLES=y
+CONFIG_IPV6_PIMSM_V2=y
+CONFIG_MPTCP=y
+CONFIG_NETWORK_PHY_TIMESTAMPING=y
+CONFIG_NETFILTER=y
+CONFIG_BRIDGE_NETFILTER=m
+CONFIG_NF_CONNTRACK=m
+CONFIG_NF_CONNTRACK_ZONES=y
+CONFIG_NF_CONNTRACK_EVENTS=y
+CONFIG_NF_CONNTRACK_TIMESTAMP=y
+CONFIG_NF_CONNTRACK_AMANDA=m
+CONFIG_NF_CONNTRACK_FTP=m
+CONFIG_NF_CONNTRACK_H323=m
+CONFIG_NF_CONNTRACK_IRC=m
+CONFIG_NF_CONNTRACK_NETBIOS_NS=m
+CONFIG_NF_CONNTRACK_SNMP=m
+CONFIG_NF_CONNTRACK_PPTP=m
+CONFIG_NF_CONNTRACK_SANE=m
+CONFIG_NF_CONNTRACK_SIP=m
+CONFIG_NF_CONNTRACK_TFTP=m
+CONFIG_NF_CT_NETLINK=m
+CONFIG_NF_TABLES=m
+CONFIG_NF_TABLES_INET=y
+CONFIG_NF_TABLES_NETDEV=y
+CONFIG_NFT_NUMGEN=m
+CONFIG_NFT_CT=m
+CONFIG_NFT_FLOW_OFFLOAD=m
+CONFIG_NFT_CONNLIMIT=m
+CONFIG_NFT_LOG=m
+CONFIG_NFT_LIMIT=m
+CONFIG_NFT_MASQ=m
+CONFIG_NFT_REDIR=m
+CONFIG_NFT_NAT=m
+CONFIG_NFT_TUNNEL=m
+CONFIG_NFT_QUEUE=m
+CONFIG_NFT_QUOTA=m
+CONFIG_NFT_REJECT=m
+CONFIG_NFT_COMPAT=m
+CONFIG_NFT_HASH=m
+CONFIG_NFT_FIB_INET=m
+CONFIG_NFT_XFRM=m
+CONFIG_NFT_SOCKET=m
+CONFIG_NFT_OSF=m
+CONFIG_NFT_TPROXY=m
+CONFIG_NFT_SYNPROXY=m
+CONFIG_NFT_DUP_NETDEV=m
+CONFIG_NFT_FWD_NETDEV=m
+CONFIG_NFT_FIB_NETDEV=m
+CONFIG_NF_FLOW_TABLE_INET=m
+CONFIG_NF_FLOW_TABLE=m
+CONFIG_NETFILTER_XTABLES_COMPAT=y
+CONFIG_NETFILTER_XT_SET=m
+CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m
+CONFIG_NETFILTER_XT_TARGET_CLASSIFY=m
+CONFIG_NETFILTER_XT_TARGET_CONNMARK=m
+CONFIG_NETFILTER_XT_TARGET_DSCP=m
+CONFIG_NETFILTER_XT_TARGET_HMARK=m
+CONFIG_NETFILTER_XT_TARGET_IDLETIMER=m
+CONFIG_NETFILTER_XT_TARGET_LED=m
+CONFIG_NETFILTER_XT_TARGET_LOG=m
+CONFIG_NETFILTER_XT_TARGET_MARK=m
+CONFIG_NETFILTER_XT_TARGET_NFLOG=m
+CONFIG_NETFILTER_XT_TARGET_NFQUEUE=m
+CONFIG_NETFILTER_XT_TARGET_NOTRACK=m
+CONFIG_NETFILTER_XT_TARGET_TEE=m
+CONFIG_NETFILTER_XT_TARGET_TPROXY=m
+CONFIG_NETFILTER_XT_TARGET_TRACE=m
+CONFIG_NETFILTER_XT_TARGET_TCPMSS=m
+CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=m
+CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m
+CONFIG_NETFILTER_XT_MATCH_BPF=m
+CONFIG_NETFILTER_XT_MATCH_CLUSTER=m
+CONFIG_NETFILTER_XT_MATCH_COMMENT=m
+CONFIG_NETFILTER_XT_MATCH_CONNBYTES=m
+CONFIG_NETFILTER_XT_MATCH_CONNLABEL=m
+CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=m
+CONFIG_NETFILTER_XT_MATCH_CONNMARK=m
+CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m
+CONFIG_NETFILTER_XT_MATCH_CPU=m
+CONFIG_NETFILTER_XT_MATCH_DCCP=m
+CONFIG_NETFILTER_XT_MATCH_DEVGROUP=m
+CONFIG_NETFILTER_XT_MATCH_DSCP=m
+CONFIG_NETFILTER_XT_MATCH_ESP=m
+CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=m
+CONFIG_NETFILTER_XT_MATCH_HELPER=m
+CONFIG_NETFILTER_XT_MATCH_IPRANGE=m
+CONFIG_NETFILTER_XT_MATCH_IPVS=m
+CONFIG_NETFILTER_XT_MATCH_LENGTH=m
+CONFIG_NETFILTER_XT_MATCH_LIMIT=m
+CONFIG_NETFILTER_XT_MATCH_MAC=m
+CONFIG_NETFILTER_XT_MATCH_MARK=m
+CONFIG_NETFILTER_XT_MATCH_MULTIPORT=m
+CONFIG_NETFILTER_XT_MATCH_NFACCT=m
+CONFIG_NETFILTER_XT_MATCH_OSF=m
+CONFIG_NETFILTER_XT_MATCH_OWNER=m
+CONFIG_NETFILTER_XT_MATCH_POLICY=m
+CONFIG_NETFILTER_XT_MATCH_PHYSDEV=m
+CONFIG_NETFILTER_XT_MATCH_PKTTYPE=m
+CONFIG_NETFILTER_XT_MATCH_QUOTA=m
+CONFIG_NETFILTER_XT_MATCH_RATEEST=m
+CONFIG_NETFILTER_XT_MATCH_REALM=m
+CONFIG_NETFILTER_XT_MATCH_RECENT=m
+CONFIG_NETFILTER_XT_MATCH_SOCKET=m
+CONFIG_NETFILTER_XT_MATCH_STATE=m
+CONFIG_NETFILTER_XT_MATCH_STATISTIC=m
+CONFIG_NETFILTER_XT_MATCH_STRING=m
+CONFIG_NETFILTER_XT_MATCH_TCPMSS=m
+CONFIG_NETFILTER_XT_MATCH_TIME=m
+CONFIG_NETFILTER_XT_MATCH_U32=m
+CONFIG_IP_SET=m
+CONFIG_IP_SET_BITMAP_IP=m
+CONFIG_IP_SET_BITMAP_IPMAC=m
+CONFIG_IP_SET_BITMAP_PORT=m
+CONFIG_IP_SET_HASH_IP=m
+CONFIG_IP_SET_HASH_IPPORT=m
+CONFIG_IP_SET_HASH_IPPORTIP=m
+CONFIG_IP_SET_HASH_IPPORTNET=m
+CONFIG_IP_SET_HASH_NET=m
+CONFIG_IP_SET_HASH_NETPORT=m
+CONFIG_IP_SET_HASH_NETIFACE=m
+CONFIG_IP_SET_LIST_SET=m
+CONFIG_IP_VS=m
+CONFIG_IP_VS_IPV6=y
+CONFIG_IP_VS_PROTO_TCP=y
+CONFIG_IP_VS_PROTO_UDP=y
+CONFIG_IP_VS_PROTO_ESP=y
+CONFIG_IP_VS_PROTO_AH=y
+CONFIG_IP_VS_PROTO_SCTP=y
+CONFIG_IP_VS_RR=m
+CONFIG_IP_VS_WRR=m
+CONFIG_IP_VS_LC=m
+CONFIG_IP_VS_WLC=m
+CONFIG_IP_VS_LBLC=m
+CONFIG_IP_VS_LBLCR=m
+CONFIG_IP_VS_DH=m
+CONFIG_IP_VS_SH=m
+CONFIG_IP_VS_SED=m
+CONFIG_IP_VS_NQ=m
+CONFIG_IP_VS_FTP=m
+CONFIG_IP_VS_PE_SIP=m
+CONFIG_NFT_DUP_IPV4=m
+CONFIG_NFT_FIB_IPV4=m
+CONFIG_NF_TABLES_ARP=y
+CONFIG_NF_LOG_ARP=m
+CONFIG_NF_LOG_IPV4=m
+CONFIG_IP_NF_IPTABLES=m
+CONFIG_IP_NF_MATCH_AH=m
+CONFIG_IP_NF_MATCH_ECN=m
+CONFIG_IP_NF_MATCH_RPFILTER=m
+CONFIG_IP_NF_MATCH_TTL=m
+CONFIG_IP_NF_FILTER=m
+CONFIG_IP_NF_TARGET_REJECT=m
+CONFIG_IP_NF_TARGET_SYNPROXY=m
+CONFIG_IP_NF_NAT=m
+CONFIG_IP_NF_TARGET_MASQUERADE=m
+CONFIG_IP_NF_TARGET_NETMAP=m
+CONFIG_IP_NF_TARGET_REDIRECT=m
+CONFIG_IP_NF_MANGLE=m
+CONFIG_IP_NF_TARGET_ECN=m
+CONFIG_IP_NF_TARGET_TTL=m
+CONFIG_IP_NF_RAW=m
+CONFIG_IP_NF_ARPFILTER=m
+CONFIG_IP_NF_ARP_MANGLE=m
+CONFIG_NFT_DUP_IPV6=m
+CONFIG_NFT_FIB_IPV6=m
+CONFIG_IP6_NF_IPTABLES=m
+CONFIG_IP6_NF_MATCH_AH=m
+CONFIG_IP6_NF_MATCH_EUI64=m
+CONFIG_IP6_NF_MATCH_FRAG=m
+CONFIG_IP6_NF_MATCH_OPTS=m
+CONFIG_IP6_NF_MATCH_HL=m
+CONFIG_IP6_NF_MATCH_IPV6HEADER=m
+CONFIG_IP6_NF_MATCH_MH=m
+CONFIG_IP6_NF_MATCH_RPFILTER=m
+CONFIG_IP6_NF_MATCH_RT=m
+CONFIG_IP6_NF_MATCH_SRH=m
+CONFIG_IP6_NF_TARGET_HL=m
+CONFIG_IP6_NF_FILTER=m
+CONFIG_IP6_NF_TARGET_REJECT=m
+CONFIG_IP6_NF_TARGET_SYNPROXY=m
+CONFIG_IP6_NF_MANGLE=m
+CONFIG_IP6_NF_RAW=m
+CONFIG_IP6_NF_SECURITY=m
+CONFIG_IP6_NF_NAT=m
+CONFIG_IP6_NF_TARGET_MASQUERADE=m
+CONFIG_IP6_NF_TARGET_NPT=m
+CONFIG_NF_TABLES_BRIDGE=m
+CONFIG_NFT_BRIDGE_REJECT=m
+CONFIG_BRIDGE_NF_EBTABLES=m
+CONFIG_BRIDGE_EBT_BROUTE=m
+CONFIG_BRIDGE_EBT_T_FILTER=m
+CONFIG_BRIDGE_EBT_T_NAT=m
+CONFIG_BRIDGE_EBT_802_3=m
+CONFIG_BRIDGE_EBT_AMONG=m
+CONFIG_BRIDGE_EBT_ARP=m
+CONFIG_BRIDGE_EBT_IP=m
+CONFIG_BRIDGE_EBT_IP6=m
+CONFIG_BRIDGE_EBT_LIMIT=m
+CONFIG_BRIDGE_EBT_MARK=m
+CONFIG_BRIDGE_EBT_PKTTYPE=m
+CONFIG_BRIDGE_EBT_STP=m
+CONFIG_BRIDGE_EBT_VLAN=m
+CONFIG_BRIDGE_EBT_ARPREPLY=m
+CONFIG_BRIDGE_EBT_DNAT=m
+CONFIG_BRIDGE_EBT_MARK_T=m
+CONFIG_BRIDGE_EBT_REDIRECT=m
+CONFIG_BRIDGE_EBT_SNAT=m
+CONFIG_BRIDGE_EBT_LOG=m
+CONFIG_BRIDGE_EBT_NFLOG=m
+CONFIG_SCTP_COOKIE_HMAC_SHA1=y
+CONFIG_ATM=m
+CONFIG_L2TP=m
+CONFIG_L2TP_V3=y
+CONFIG_L2TP_IP=m
+CONFIG_L2TP_ETH=m
+CONFIG_BRIDGE=m
+CONFIG_BRIDGE_VLAN_FILTERING=y
+CONFIG_NET_DSA=m
+CONFIG_VLAN_8021Q=m
+CONFIG_VLAN_8021Q_GVRP=y
+CONFIG_ATALK=m
+CONFIG_6LOWPAN=m
+CONFIG_IEEE802154=m
+CONFIG_IEEE802154_6LOWPAN=m
+CONFIG_MAC802154=m
+CONFIG_NET_SCHED=y
+CONFIG_NET_SCH_HTB=m
+CONFIG_NET_SCH_HFSC=m
+CONFIG_NET_SCH_PRIO=m
+CONFIG_NET_SCH_MULTIQ=m
+CONFIG_NET_SCH_RED=m
+CONFIG_NET_SCH_SFB=m
+CONFIG_NET_SCH_SFQ=m
+CONFIG_NET_SCH_TEQL=m
+CONFIG_NET_SCH_TBF=m
+CONFIG_NET_SCH_GRED=m
+CONFIG_NET_SCH_NETEM=m
+CONFIG_NET_SCH_DRR=m
+CONFIG_NET_SCH_MQPRIO=m
+CONFIG_NET_SCH_CHOKE=m
+CONFIG_NET_SCH_QFQ=m
+CONFIG_NET_SCH_CODEL=m
+CONFIG_NET_SCH_FQ_CODEL=m
+CONFIG_NET_SCH_CAKE=m
+CONFIG_NET_SCH_FQ=m
+CONFIG_NET_SCH_HHF=m
+CONFIG_NET_SCH_PIE=m
+CONFIG_NET_SCH_INGRESS=m
+CONFIG_NET_SCH_PLUG=m
+CONFIG_NET_CLS_BASIC=m
+CONFIG_NET_CLS_ROUTE4=m
+CONFIG_NET_CLS_FW=m
+CONFIG_NET_CLS_U32=m
+CONFIG_CLS_U32_PERF=y
+CONFIG_CLS_U32_MARK=y
+CONFIG_NET_CLS_FLOW=m
+CONFIG_NET_CLS_CGROUP=m
+CONFIG_NET_CLS_BPF=y
+CONFIG_NET_CLS_FLOWER=m
+CONFIG_NET_CLS_MATCHALL=m
+CONFIG_NET_EMATCH=y
+CONFIG_NET_EMATCH_CMP=m
+CONFIG_NET_EMATCH_NBYTE=m
+CONFIG_NET_EMATCH_U32=m
+CONFIG_NET_EMATCH_META=m
+CONFIG_NET_EMATCH_TEXT=m
+CONFIG_NET_EMATCH_IPSET=m
+CONFIG_NET_CLS_ACT=y
+CONFIG_NET_ACT_POLICE=m
+CONFIG_NET_ACT_GACT=m
+CONFIG_GACT_PROB=y
+CONFIG_NET_ACT_MIRRED=m
+CONFIG_NET_ACT_NAT=m
+CONFIG_NET_ACT_PEDIT=m
+CONFIG_NET_ACT_SIMP=m
+CONFIG_NET_ACT_SKBEDIT=m
+CONFIG_NET_ACT_CSUM=m
+CONFIG_BATMAN_ADV=m
+CONFIG_OPENVSWITCH=m
+CONFIG_VSOCKETS=m
+CONFIG_CGROUP_NET_PRIO=y
+CONFIG_NET_PKTGEN=m
+CONFIG_HAMRADIO=y
+CONFIG_AX25=m
+CONFIG_NETROM=m
+CONFIG_ROSE=m
+CONFIG_MKISS=m
+CONFIG_6PACK=m
+CONFIG_BPQETHER=m
+CONFIG_BAYCOM_SER_FDX=m
+CONFIG_BAYCOM_SER_HDX=m
+CONFIG_YAM=m
+CONFIG_CAN=m
+CONFIG_CAN_J1939=m
+CONFIG_CAN_ISOTP=m
+CONFIG_BT=m
+CONFIG_BT_RFCOMM=m
+CONFIG_BT_RFCOMM_TTY=y
+CONFIG_BT_BNEP=m
+CONFIG_BT_BNEP_MC_FILTER=y
+CONFIG_BT_BNEP_PROTO_FILTER=y
+CONFIG_BT_HIDP=m
+CONFIG_BT_6LOWPAN=m
+CONFIG_BT_HCIBTUSB=m
+CONFIG_BT_HCIBTUSB_MTK=y
+CONFIG_BT_HCIUART=m
+CONFIG_BT_HCIUART_3WIRE=y
+CONFIG_BT_HCIUART_BCM=y
+CONFIG_BT_HCIBCM203X=m
+CONFIG_BT_HCIBPA10X=m
+CONFIG_BT_HCIBFUSB=m
+CONFIG_BT_HCIVHCI=m
+CONFIG_BT_MRVL=m
+CONFIG_BT_MRVL_SDIO=m
+CONFIG_BT_ATH3K=m
+CONFIG_CFG80211=m
+CONFIG_CFG80211_WEXT=y
+CONFIG_MAC80211=m
+CONFIG_MAC80211_MESH=y
+CONFIG_RFKILL=m
+CONFIG_RFKILL_INPUT=y
+CONFIG_NET_9P=m
+CONFIG_NFC=m
+CONFIG_PCI=y
+CONFIG_PCIEPORTBUS=y
+CONFIG_PCIEAER=y
+CONFIG_PCIEASPM_POWERSAVE=y
+CONFIG_PCIE_DPC=y
+CONFIG_UEVENT_HELPER=y
+CONFIG_DEVTMPFS=y
+CONFIG_DEVTMPFS_MOUNT=y
+# CONFIG_BRCMSTB_GISB_ARB is not set
+CONFIG_RASPBERRYPI_FIRMWARE=y
+CONFIG_FIRMWARE_RP1=m
+# CONFIG_EFI_VARS_PSTORE is not set
+CONFIG_MTD=m
+CONFIG_MTD_BLOCK=m
+CONFIG_MTD_BLOCK2MTD=m
+CONFIG_MTD_SPI_NAND=m
+CONFIG_MTD_SPI_NOR=m
+CONFIG_MTD_UBI=m
+CONFIG_OF_CONFIGFS=y
+CONFIG_ZRAM=m
+CONFIG_ZRAM_BACKEND_LZ4=y
+CONFIG_ZRAM_BACKEND_ZSTD=y
+CONFIG_ZRAM_BACKEND_LZO=y
+CONFIG_ZRAM_DEF_COMP_ZSTD=y
+CONFIG_ZRAM_WRITEBACK=y
+CONFIG_ZRAM_MULTI_COMP=y
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BLK_DEV_DRBD=m
+CONFIG_BLK_DEV_NBD=m
+CONFIG_BLK_DEV_RAM=y
+CONFIG_ATA_OVER_ETH=m
+CONFIG_BLK_DEV_RBD=m
+CONFIG_BLK_DEV_NVME=y
+CONFIG_NVME_HWMON=y
+CONFIG_RP1_PIO=m
+CONFIG_WS2812_PIO_RP1=m
+CONFIG_SRAM=y
+CONFIG_EEPROM_AT24=m
+CONFIG_EEPROM_AT25=m
+CONFIG_TI_ST=m
+CONFIG_SCSI=y
+# CONFIG_SCSI_PROC_FS is not set
+CONFIG_BLK_DEV_SD=y
+CONFIG_CHR_DEV_ST=m
+CONFIG_BLK_DEV_SR=m
+CONFIG_CHR_DEV_SG=m
+CONFIG_SCSI_ISCSI_ATTRS=y
+CONFIG_ISCSI_TCP=m
+CONFIG_ISCSI_BOOT_SYSFS=m
+CONFIG_ATA=m
+CONFIG_SATA_AHCI=m
+CONFIG_SATA_MV=m
+CONFIG_MD=y
+CONFIG_BCACHE=m
+CONFIG_BLK_DEV_DM=m
+CONFIG_DM_CRYPT=m
+CONFIG_DM_SNAPSHOT=m
+CONFIG_DM_THIN_PROVISIONING=m
+CONFIG_DM_CACHE=m
+CONFIG_DM_WRITECACHE=m
+CONFIG_DM_MIRROR=m
+CONFIG_DM_LOG_USERSPACE=m
+CONFIG_DM_RAID=m
+CONFIG_DM_ZERO=m
+CONFIG_DM_MULTIPATH=m
+CONFIG_DM_DELAY=m
+CONFIG_DM_VERITY=m
+CONFIG_DM_INTEGRITY=m
+CONFIG_NETDEVICES=y
+CONFIG_BONDING=m
+CONFIG_DUMMY=m
+CONFIG_WIREGUARD=m
+CONFIG_IFB=m
+CONFIG_MACVLAN=m
+CONFIG_MACVTAP=m
+CONFIG_IPVLAN=m
+CONFIG_VXLAN=m
+CONFIG_NETCONSOLE=m
+CONFIG_TUN=m
+CONFIG_VETH=m
+CONFIG_NETKIT=y
+CONFIG_NET_VRF=m
+CONFIG_VSOCKMON=m
+CONFIG_BCMGENET=y
+CONFIG_MACB=y
+CONFIG_IGB=m
+CONFIG_IXGBE=m
+CONFIG_I40E=m
+CONFIG_IGC=m
+CONFIG_ENC28J60=m
+CONFIG_LAN743X=m
+CONFIG_QCA7000_SPI=m
+CONFIG_QCA7000_UART=m
+CONFIG_R8169=m
+CONFIG_MSE102X=m
+CONFIG_WIZNET_W5100=m
+CONFIG_WIZNET_W5100_SPI=m
+CONFIG_MICREL_PHY=y
+CONFIG_CAN_VCAN=m
+CONFIG_CAN_SLCAN=m
+CONFIG_CAN_MCP251X=m
+CONFIG_CAN_MCP251XFD=m
+CONFIG_CAN_8DEV_USB=m
+CONFIG_CAN_EMS_USB=m
+CONFIG_CAN_GS_USB=m
+CONFIG_CAN_PEAK_USB=m
+CONFIG_MDIO_BITBANG=m
+CONFIG_PPP=m
+CONFIG_PPP_BSDCOMP=m
+CONFIG_PPP_DEFLATE=m
+CONFIG_PPP_FILTER=y
+CONFIG_PPP_MPPE=m
+CONFIG_PPP_MULTILINK=y
+CONFIG_PPPOATM=m
+CONFIG_PPPOE=m
+CONFIG_PPPOL2TP=m
+CONFIG_PPP_ASYNC=m
+CONFIG_PPP_SYNC_TTY=m
+CONFIG_SLIP=m
+CONFIG_SLIP_COMPRESSED=y
+CONFIG_SLIP_SMART=y
+CONFIG_USB_CATC=m
+CONFIG_USB_KAWETH=m
+CONFIG_USB_PEGASUS=m
+CONFIG_USB_RTL8150=m
+CONFIG_USB_RTL8152=m
+CONFIG_USB_LAN78XX=y
+CONFIG_USB_USBNET=y
+CONFIG_USB_NET_AX8817X=m
+CONFIG_USB_NET_AX88179_178A=m
+CONFIG_USB_NET_CDCETHER=m
+CONFIG_USB_NET_CDC_EEM=m
+CONFIG_USB_NET_CDC_NCM=m
+CONFIG_USB_NET_HUAWEI_CDC_NCM=m
+CONFIG_USB_NET_CDC_MBIM=m
+CONFIG_USB_NET_DM9601=m
+CONFIG_USB_NET_SR9700=m
+CONFIG_USB_NET_SR9800=m
+CONFIG_USB_NET_SMSC75XX=m
+CONFIG_USB_NET_SMSC95XX=y
+CONFIG_USB_NET_GL620A=m
+CONFIG_USB_NET_NET1080=m
+CONFIG_USB_NET_PLUSB=m
+CONFIG_USB_NET_MCS7830=m
+CONFIG_USB_NET_RNDIS_HOST=m
+CONFIG_USB_NET_CDC_SUBSET=m
+CONFIG_USB_ALI_M5632=y
+CONFIG_USB_AN2720=y
+CONFIG_USB_EPSON2888=y
+CONFIG_USB_KC2190=y
+CONFIG_USB_NET_ZAURUS=m
+CONFIG_USB_NET_CX82310_ETH=m
+CONFIG_USB_NET_KALMIA=m
+CONFIG_USB_NET_QMI_WWAN=m
+CONFIG_USB_HSO=m
+CONFIG_USB_NET_INT51X1=m
+CONFIG_USB_IPHETH=m
+CONFIG_USB_SIERRA_NET=m
+CONFIG_USB_VL600=m
+CONFIG_USB_NET_AQC111=m
+CONFIG_ATH9K=m
+CONFIG_ATH9K_HTC=m
+CONFIG_CARL9170=m
+CONFIG_ATH6KL=m
+CONFIG_ATH6KL_USB=m
+CONFIG_AR5523=m
+CONFIG_AT76C50X_USB=m
+CONFIG_B43=m
+# CONFIG_B43_PHY_N is not set
+CONFIG_B43LEGACY=m
+CONFIG_BRCMFMAC=m
+CONFIG_BRCMFMAC_USB=y
+CONFIG_BRCMDBG=y
+CONFIG_IWLWIFI=m
+CONFIG_IWLDVM=m
+CONFIG_IWLMVM=m
+CONFIG_P54_COMMON=m
+CONFIG_P54_USB=m
+CONFIG_LIBERTAS=m
+CONFIG_LIBERTAS_USB=m
+CONFIG_LIBERTAS_SDIO=m
+CONFIG_LIBERTAS_THINFIRM=m
+CONFIG_LIBERTAS_THINFIRM_USB=m
+CONFIG_MWIFIEX=m
+CONFIG_MWIFIEX_SDIO=m
+CONFIG_MT7601U=m
+CONFIG_MT76x0U=m
+CONFIG_MT76x2U=m
+CONFIG_MT7921U=m
+CONFIG_MT7925E=m
+CONFIG_MT7925U=m
+CONFIG_RT2X00=m
+CONFIG_RT2500USB=m
+CONFIG_RT73USB=m
+CONFIG_RT2800USB=m
+CONFIG_RT2800USB_RT3573=y
+CONFIG_RT2800USB_RT53XX=y
+CONFIG_RT2800USB_RT55XX=y
+CONFIG_RT2800USB_UNKNOWN=y
+CONFIG_RTL8187=m
+CONFIG_RTL8192CU=m
+CONFIG_RTL8XXXU=m
+CONFIG_RTW88=m
+CONFIG_RTW88_8822BE=m
+CONFIG_RTW88_8822BU=m
+CONFIG_RTW88_8822CE=m
+CONFIG_RTW88_8822CU=m
+CONFIG_RTW88_8723DE=m
+CONFIG_RTW88_8723DU=m
+CONFIG_RTW88_8821CE=m
+CONFIG_RTW88_8821CU=m
+CONFIG_RTW89=m
+CONFIG_RTW89_8851BE=m
+CONFIG_RTW89_8852AE=m
+CONFIG_RTW89_8852BE=m
+CONFIG_RTW89_8852BTE=m
+CONFIG_RTW89_8852CE=m
+CONFIG_RTW89_8922AE=m
+CONFIG_ZD1211RW=m
+CONFIG_MAC80211_HWSIM=m
+CONFIG_IEEE802154_AT86RF230=m
+CONFIG_IEEE802154_MRF24J40=m
+CONFIG_IEEE802154_CC2520=m
+CONFIG_INPUT_MOUSEDEV=y
+CONFIG_INPUT_JOYDEV=m
+CONFIG_INPUT_EVDEV=y
+# CONFIG_KEYBOARD_ATKBD is not set
+CONFIG_KEYBOARD_GPIO=m
+CONFIG_KEYBOARD_TCA6416=m
+CONFIG_KEYBOARD_TCA8418=m
+CONFIG_KEYBOARD_MATRIX=m
+CONFIG_KEYBOARD_CAP11XX=m
+# CONFIG_INPUT_MOUSE is not set
+CONFIG_INPUT_JOYSTICK=y
+CONFIG_JOYSTICK_IFORCE=m
+CONFIG_JOYSTICK_IFORCE_USB=m
+CONFIG_JOYSTICK_XPAD=m
+CONFIG_JOYSTICK_XPAD_FF=y
+CONFIG_JOYSTICK_XPAD_LEDS=y
+CONFIG_JOYSTICK_PSXPAD_SPI=m
+CONFIG_JOYSTICK_PSXPAD_SPI_FF=y
+CONFIG_JOYSTICK_FSIA6B=m
+CONFIG_JOYSTICK_SENSEHAT=m
+CONFIG_INPUT_TOUCHSCREEN=y
+CONFIG_TOUCHSCREEN_ADS7846=m
+CONFIG_TOUCHSCREEN_ATMEL_MXT=m
+CONFIG_TOUCHSCREEN_EGALAX=m
+CONFIG_TOUCHSCREEN_EXC3000=m
+CONFIG_TOUCHSCREEN_GOODIX=m
+CONFIG_TOUCHSCREEN_ILI210X=m
+CONFIG_TOUCHSCREEN_ILITEK=m
+CONFIG_TOUCHSCREEN_EDT_FT5X06=m
+CONFIG_TOUCHSCREEN_RASPBERRYPI_FW=m
+CONFIG_TOUCHSCREEN_USB_COMPOSITE=m
+CONFIG_TOUCHSCREEN_TSC2007=m
+CONFIG_TOUCHSCREEN_TSC2007_IIO=y
+CONFIG_TOUCHSCREEN_STMPE=m
+CONFIG_TOUCHSCREEN_IQS5XX=m
+CONFIG_INPUT_MISC=y
+CONFIG_INPUT_AD714X=m
+CONFIG_INPUT_ATI_REMOTE2=m
+CONFIG_INPUT_KEYSPAN_REMOTE=m
+CONFIG_INPUT_POWERMATE=m
+CONFIG_INPUT_YEALINK=m
+CONFIG_INPUT_CM109=m
+CONFIG_INPUT_UINPUT=m
+CONFIG_INPUT_PWM_BEEPER=m
+CONFIG_INPUT_GPIO_ROTARY_ENCODER=m
+CONFIG_INPUT_ADXL34X=m
+CONFIG_INPUT_CMA3000=m
+CONFIG_SERIO=m
+CONFIG_SERIO_RAW=m
+CONFIG_GAMEPORT=m
+CONFIG_BRCM_CHAR_DRIVERS=y
+CONFIG_BCM_VCIO=y
+# CONFIG_LEGACY_PTYS is not set
+CONFIG_SERIAL_8250=y
+# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
+CONFIG_SERIAL_8250_CONSOLE=y
+# CONFIG_SERIAL_8250_DMA is not set
+CONFIG_SERIAL_8250_NR_UARTS=5
+CONFIG_SERIAL_8250_RUNTIME_UARTS=0
+CONFIG_SERIAL_8250_EXTENDED=y
+CONFIG_SERIAL_8250_SHARE_IRQ=y
+CONFIG_SERIAL_8250_BCM2835AUX=y
+CONFIG_SERIAL_OF_PLATFORM=y
+CONFIG_SERIAL_AMBA_PL011=y
+CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
+CONFIG_SERIAL_SC16IS7XX=m
+CONFIG_SERIAL_RPI_FW=m
+CONFIG_SERIAL_DEV_BUS=y
+CONFIG_TTY_PRINTK=y
+CONFIG_HW_RANDOM=y
+CONFIG_TCG_TPM=m
+CONFIG_TCG_TIS_SPI=m
+CONFIG_TCG_TIS_I2C=m
+CONFIG_XILLYBUS=m
+CONFIG_XILLYBUS_PCIE=m
+CONFIG_XILLYUSB=m
+CONFIG_RASPBERRYPI_GPIOMEM=m
+CONFIG_I2C=y
+CONFIG_I2C_CHARDEV=m
+CONFIG_I2C_MUX_GPMUX=m
+CONFIG_I2C_MUX_PCA954x=m
+CONFIG_I2C_MUX_PINCTRL=m
+CONFIG_I2C_BCM2708=m
+CONFIG_I2C_BCM2835=m
+CONFIG_I2C_BRCMSTB=m
+CONFIG_I2C_DESIGNWARE_CORE=m
+CONFIG_I2C_GPIO=m
+CONFIG_I2C_ROBOTFUZZ_OSIF=m
+CONFIG_I2C_TINY_USB=m
+CONFIG_SPI=y
+CONFIG_SPI_BCM2835=m
+CONFIG_SPI_BCM2835AUX=m
+CONFIG_SPI_DESIGNWARE=m
+CONFIG_SPI_DW_DMA=y
+CONFIG_SPI_DW_MMIO=m
+CONFIG_SPI_GPIO=m
+CONFIG_SPI_RP2040_GPIO_BRIDGE=m
+CONFIG_SPI_SPIDEV=m
+CONFIG_SPI_SLAVE=y
+CONFIG_PPS_CLIENT_LDISC=m
+CONFIG_PPS_CLIENT_GPIO=m
+CONFIG_PINCTRL_MCP23S08=m
+CONFIG_PINCTRL_RP1=y
+CONFIG_PINCTRL_BCM2712=y
+CONFIG_GPIO_SYSFS=y
+CONFIG_GPIO_BCM_VIRT=y
+CONFIG_GPIO_FXL6408=m
+CONFIG_GPIO_MAX7300=m
+CONFIG_GPIO_PCA953X=m
+CONFIG_GPIO_PCA953X_IRQ=y
+CONFIG_GPIO_PCF857X=m
+CONFIG_GPIO_ARIZONA=m
+CONFIG_GPIO_FSM=m
+CONFIG_GPIO_STMPE=y
+CONFIG_GPIO_MAX7301=m
+CONFIG_GPIO_MOCKUP=m
+CONFIG_W1=m
+CONFIG_W1_MASTER_DS2490=m
+CONFIG_W1_MASTER_DS2482=m
+CONFIG_W1_MASTER_GPIO=m
+CONFIG_W1_SLAVE_THERM=m
+CONFIG_W1_SLAVE_SMEM=m
+CONFIG_W1_SLAVE_DS2408=m
+CONFIG_W1_SLAVE_DS2413=m
+CONFIG_W1_SLAVE_DS2406=m
+CONFIG_W1_SLAVE_DS2423=m
+CONFIG_W1_SLAVE_DS2430=m
+CONFIG_W1_SLAVE_DS2431=m
+CONFIG_W1_SLAVE_DS2433=m
+CONFIG_W1_SLAVE_DS2438=m
+CONFIG_W1_SLAVE_DS2780=m
+CONFIG_W1_SLAVE_DS2781=m
+CONFIG_W1_SLAVE_DS28E04=m
+CONFIG_W1_SLAVE_DS28E17=m
+# CONFIG_POWER_RESET_BRCMSTB is not set
+CONFIG_POWER_RESET_GPIO=y
+CONFIG_RPI_POE_POWER=m
+CONFIG_BATTERY_DS2760=m
+CONFIG_BATTERY_MAX17040=m
+CONFIG_CHARGER_GPIO=m
+CONFIG_BATTERY_GAUGE_LTC2941=m
+CONFIG_SENSORS_ADT7410=m
+CONFIG_SENSORS_AHT10=m
+CONFIG_SENSORS_CHIPCAP2=m
+CONFIG_SENSORS_DRIVETEMP=m
+CONFIG_SENSORS_DS1621=m
+CONFIG_SENSORS_GPIO_FAN=m
+CONFIG_SENSORS_IIO_HWMON=m
+CONFIG_SENSORS_JC42=m
+CONFIG_SENSORS_POWERZ=m
+CONFIG_SENSORS_LM75=m
+CONFIG_SENSORS_PWM_FAN=m
+CONFIG_SENSORS_RASPBERRYPI_HWMON=m
+CONFIG_SENSORS_SHT21=m
+CONFIG_SENSORS_SHT3x=m
+CONFIG_SENSORS_SHT4x=m
+CONFIG_SENSORS_SHTC1=m
+CONFIG_SENSORS_EMC2305=m
+CONFIG_SENSORS_ADS7828=m
+CONFIG_SENSORS_INA2XX=m
+CONFIG_SENSORS_INA238=m
+CONFIG_SENSORS_TMP102=m
+CONFIG_SENSORS_RP1_ADC=m
+CONFIG_BCM2711_THERMAL=y
+CONFIG_BCM2835_THERMAL=y
+CONFIG_WATCHDOG=y
+CONFIG_WATCHDOG_SYSFS=y
+CONFIG_GPIO_WATCHDOG=m
+CONFIG_BCM2835_WDT=y
+CONFIG_MFD_RASPBERRYPI_POE_HAT=m
+CONFIG_MFD_STMPE=y
+CONFIG_STMPE_SPI=y
+CONFIG_MFD_SYSCON=y
+CONFIG_MFD_ARIZONA_I2C=m
+CONFIG_MFD_ARIZONA_SPI=m
+CONFIG_MFD_WM5102=y
+CONFIG_MFD_RP1=y
+CONFIG_REGULATOR=y
+CONFIG_REGULATOR_FIXED_VOLTAGE=y
+CONFIG_REGULATOR_ARIZONA_LDO1=m
+CONFIG_REGULATOR_ARIZONA_MICSUPP=m
+CONFIG_REGULATOR_GPIO=y
+CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY=m
+CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_V2=m
+CONFIG_REGULATOR_WAVESHARE_TOUCHSCREEN=m
+CONFIG_RC_CORE=y
+CONFIG_BPF_LIRC_MODE2=y
+CONFIG_LIRC=y
+CONFIG_RC_DECODERS=y
+CONFIG_IR_IMON_DECODER=m
+CONFIG_IR_JVC_DECODER=m
+CONFIG_IR_MCE_KBD_DECODER=m
+CONFIG_IR_NEC_DECODER=m
+CONFIG_IR_RC5_DECODER=m
+CONFIG_IR_RC6_DECODER=m
+CONFIG_IR_SANYO_DECODER=m
+CONFIG_IR_SHARP_DECODER=m
+CONFIG_IR_SONY_DECODER=m
+CONFIG_IR_XMP_DECODER=m
+CONFIG_RC_DEVICES=y
+CONFIG_IR_GPIO_CIR=m
+CONFIG_IR_GPIO_TX=m
+CONFIG_IR_IGUANA=m
+CONFIG_IR_IMON=m
+CONFIG_IR_MCEUSB=m
+CONFIG_IR_PWM_TX=m
+CONFIG_IR_REDRAT3=m
+CONFIG_IR_STREAMZAP=m
+CONFIG_IR_TOY=m
+CONFIG_IR_TTUSBIR=m
+CONFIG_RC_ATI_REMOTE=m
+CONFIG_RC_LOOPBACK=m
+CONFIG_MEDIA_CEC_RC=y
+CONFIG_MEDIA_SUPPORT=m
+CONFIG_MEDIA_USB_SUPPORT=y
+CONFIG_USB_GSPCA=m
+CONFIG_USB_GSPCA_BENQ=m
+CONFIG_USB_GSPCA_CONEX=m
+CONFIG_USB_GSPCA_CPIA1=m
+CONFIG_USB_GSPCA_DTCS033=m
+CONFIG_USB_GSPCA_ETOMS=m
+CONFIG_USB_GSPCA_FINEPIX=m
+CONFIG_USB_GSPCA_JEILINJ=m
+CONFIG_USB_GSPCA_JL2005BCD=m
+CONFIG_USB_GSPCA_KINECT=m
+CONFIG_USB_GSPCA_KONICA=m
+CONFIG_USB_GSPCA_MARS=m
+CONFIG_USB_GSPCA_MR97310A=m
+CONFIG_USB_GSPCA_NW80X=m
+CONFIG_USB_GSPCA_OV519=m
+CONFIG_USB_GSPCA_OV534=m
+CONFIG_USB_GSPCA_OV534_9=m
+CONFIG_USB_GSPCA_PAC207=m
+CONFIG_USB_GSPCA_PAC7302=m
+CONFIG_USB_GSPCA_PAC7311=m
+CONFIG_USB_GSPCA_SE401=m
+CONFIG_USB_GSPCA_SN9C2028=m
+CONFIG_USB_GSPCA_SN9C20X=m
+CONFIG_USB_GSPCA_SONIXB=m
+CONFIG_USB_GSPCA_SONIXJ=m
+CONFIG_USB_GSPCA_SPCA1528=m
+CONFIG_USB_GSPCA_SPCA500=m
+CONFIG_USB_GSPCA_SPCA501=m
+CONFIG_USB_GSPCA_SPCA505=m
+CONFIG_USB_GSPCA_SPCA506=m
+CONFIG_USB_GSPCA_SPCA508=m
+CONFIG_USB_GSPCA_SPCA561=m
+CONFIG_USB_GSPCA_SQ905=m
+CONFIG_USB_GSPCA_SQ905C=m
+CONFIG_USB_GSPCA_SQ930X=m
+CONFIG_USB_GSPCA_STK014=m
+CONFIG_USB_GSPCA_STK1135=m
+CONFIG_USB_GSPCA_STV0680=m
+CONFIG_USB_GSPCA_SUNPLUS=m
+CONFIG_USB_GSPCA_T613=m
+CONFIG_USB_GSPCA_TOPRO=m
+CONFIG_USB_GSPCA_TOUPTEK=m
+CONFIG_USB_GSPCA_TV8532=m
+CONFIG_USB_GSPCA_VC032X=m
+CONFIG_USB_GSPCA_VICAM=m
+CONFIG_USB_GSPCA_XIRLINK_CIT=m
+CONFIG_USB_GSPCA_ZC3XX=m
+CONFIG_USB_GL860=m
+CONFIG_USB_M5602=m
+CONFIG_USB_STV06XX=m
+CONFIG_USB_PWC=m
+CONFIG_USB_S2255=m
+CONFIG_VIDEO_USBTV=m
+CONFIG_USB_VIDEO_CLASS=m
+CONFIG_VIDEO_GO7007=m
+CONFIG_VIDEO_GO7007_USB=m
+CONFIG_VIDEO_GO7007_USB_S2250_BOARD=m
+CONFIG_VIDEO_HDPVR=m
+CONFIG_VIDEO_PVRUSB2=m
+CONFIG_VIDEO_STK1160=m
+CONFIG_VIDEO_AU0828=m
+CONFIG_VIDEO_AU0828_RC=y
+CONFIG_VIDEO_CX231XX=m
+CONFIG_VIDEO_CX231XX_ALSA=m
+CONFIG_VIDEO_CX231XX_DVB=m
+CONFIG_DVB_AS102=m
+CONFIG_DVB_B2C2_FLEXCOP_USB=m
+CONFIG_DVB_USB_V2=m
+CONFIG_DVB_USB_AF9015=m
+CONFIG_DVB_USB_AF9035=m
+CONFIG_DVB_USB_ANYSEE=m
+CONFIG_DVB_USB_AU6610=m
+CONFIG_DVB_USB_AZ6007=m
+CONFIG_DVB_USB_CE6230=m
+CONFIG_DVB_USB_DVBSKY=m
+CONFIG_DVB_USB_EC168=m
+CONFIG_DVB_USB_GL861=m
+CONFIG_DVB_USB_LME2510=m
+CONFIG_DVB_USB_MXL111SF=m
+CONFIG_DVB_USB_RTL28XXU=m
+CONFIG_DVB_USB=m
+CONFIG_DVB_USB_A800=m
+CONFIG_DVB_USB_AF9005=m
+CONFIG_DVB_USB_AF9005_REMOTE=m
+CONFIG_DVB_USB_AZ6027=m
+CONFIG_DVB_USB_CINERGY_T2=m
+CONFIG_DVB_USB_CXUSB=m
+CONFIG_DVB_USB_DIB0700=m
+CONFIG_DVB_USB_DIBUSB_MB=m
+CONFIG_DVB_USB_DIBUSB_MB_FAULTY=y
+CONFIG_DVB_USB_DIBUSB_MC=m
+CONFIG_DVB_USB_DIGITV=m
+CONFIG_DVB_USB_DTT200U=m
+CONFIG_DVB_USB_DTV5100=m
+CONFIG_DVB_USB_DW2102=m
+CONFIG_DVB_USB_GP8PSK=m
+CONFIG_DVB_USB_M920X=m
+CONFIG_DVB_USB_NOVA_T_USB2=m
+CONFIG_DVB_USB_OPERA1=m
+CONFIG_DVB_USB_PCTV452E=m
+CONFIG_DVB_USB_TECHNISAT_USB2=m
+CONFIG_DVB_USB_TTUSB2=m
+CONFIG_DVB_USB_UMT_010=m
+CONFIG_DVB_USB_VP702X=m
+CONFIG_DVB_USB_VP7045=m
+CONFIG_SMS_USB_DRV=m
+CONFIG_VIDEO_EM28XX=m
+CONFIG_VIDEO_EM28XX_V4L2=m
+CONFIG_VIDEO_EM28XX_ALSA=m
+CONFIG_VIDEO_EM28XX_DVB=m
+CONFIG_MEDIA_PCI_SUPPORT=y
+CONFIG_RADIO_SAA7706H=m
+CONFIG_RADIO_SHARK=m
+CONFIG_RADIO_SHARK2=m
+CONFIG_RADIO_SI4713=m
+CONFIG_RADIO_TEA5764=m
+CONFIG_RADIO_TEF6862=m
+CONFIG_RADIO_WL1273=m
+CONFIG_USB_DSBR=m
+CONFIG_USB_KEENE=m
+CONFIG_USB_MA901=m
+CONFIG_USB_MR800=m
+CONFIG_RADIO_SI470X=m
+CONFIG_USB_SI470X=m
+CONFIG_I2C_SI470X=m
+CONFIG_I2C_SI4713=m
+CONFIG_RADIO_WL128X=m
+CONFIG_V4L_PLATFORM_DRIVERS=y
+CONFIG_VIDEO_MUX=m
+CONFIG_VIDEO_BCM2835_UNICAM_LEGACY=m
+CONFIG_VIDEO_BCM2835_UNICAM=m
+CONFIG_VIDEO_RPI_HEVC_DEC=m
+CONFIG_VIDEO_RASPBERRYPI_PISP_BE=m
+CONFIG_VIDEO_RP1_CFE=m
+CONFIG_V4L_TEST_DRIVERS=y
+CONFIG_VIDEO_VIM2M=m
+CONFIG_VIDEO_VICODEC=m
+CONFIG_VIDEO_VIMC=m
+CONFIG_VIDEO_VIVID=m
+CONFIG_VIDEO_ARDUCAM_64MP=m
+CONFIG_VIDEO_ARDUCAM_PIVARIETY=m
+CONFIG_VIDEO_IMX219=m
+CONFIG_VIDEO_IMX258=m
+CONFIG_VIDEO_IMX283=m
+CONFIG_VIDEO_IMX290=m
+CONFIG_VIDEO_IMX296=m
+CONFIG_VIDEO_IMX335=m
+CONFIG_VIDEO_IMX415=m
+CONFIG_VIDEO_IMX477=m
+CONFIG_VIDEO_IMX500=m
+CONFIG_VIDEO_IMX519=m
+CONFIG_VIDEO_IMX708=m
+CONFIG_VIDEO_MIRA220=m
+CONFIG_VIDEO_MT9V011=m
+CONFIG_VIDEO_OV2311=m
+CONFIG_VIDEO_OV5647=m
+CONFIG_VIDEO_OV64A40=m
+CONFIG_VIDEO_OV7251=m
+CONFIG_VIDEO_OV7640=m
+CONFIG_VIDEO_OV9282=m
+CONFIG_VIDEO_AD5398=m
+CONFIG_VIDEO_AK7375=m
+CONFIG_VIDEO_BU64754=m
+CONFIG_VIDEO_DW9807_VCM=m
+CONFIG_VIDEO_SONY_BTF_MPX=m
+CONFIG_VIDEO_UDA1342=m
+CONFIG_VIDEO_ADV7180=m
+CONFIG_VIDEO_TC358743=m
+CONFIG_VIDEO_TVP5150=m
+CONFIG_VIDEO_TW2804=m
+CONFIG_VIDEO_TW9903=m
+CONFIG_VIDEO_TW9906=m
+CONFIG_VIDEO_IRS1125=m
+CONFIG_VIDEO_I2C=m
+CONFIG_AUXDISPLAY=y
+CONFIG_HD44780=m
+CONFIG_DRM=m
+CONFIG_DRM_LOAD_EDID_FIRMWARE=y
+CONFIG_DRM_UDL=m
+CONFIG_DRM_PANEL_LVDS=m
+CONFIG_DRM_PANEL_ILITEK_ILI9806E=m
+CONFIG_DRM_PANEL_ILITEK_ILI9806E_SPI=m
+CONFIG_DRM_PANEL_ILITEK_ILI9881C=m
+CONFIG_DRM_PANEL_JDI_LT070ME05000=m
+CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN=m
+CONFIG_DRM_PANEL_SITRONIX_ST7701=m
+CONFIG_DRM_PANEL_SIMPLE=m
+CONFIG_DRM_PANEL_TPO_Y17P=m
+CONFIG_DRM_PANEL_WAVESHARE_TOUCHSCREEN=m
+CONFIG_DRM_PANEL_WAVESHARE_TOUCHSCREEN_V2=m
+CONFIG_DRM_DISPLAY_CONNECTOR=m
+CONFIG_DRM_SIMPLE_BRIDGE=m
+CONFIG_DRM_TOSHIBA_TC358762=m
+CONFIG_DRM_TI_SN65DSI83=m
+CONFIG_DRM_V3D=m
+CONFIG_DRM_VC4=m
+CONFIG_DRM_VC4_HDMI_CEC=y
+CONFIG_DRM_RP1_DSI=m
+CONFIG_DRM_RP1_DPI=m
+CONFIG_DRM_RP1_VEC=m
+CONFIG_DRM_PANEL_MIPI_DBI=m
+CONFIG_TINYDRM_HX8357D=m
+CONFIG_TINYDRM_ILI9225=m
+CONFIG_TINYDRM_ILI9341=m
+CONFIG_TINYDRM_ILI9486=m
+CONFIG_TINYDRM_MI0283QT=m
+CONFIG_TINYDRM_REPAPER=m
+CONFIG_TINYDRM_ST7586=m
+CONFIG_TINYDRM_ST7735R=m
+CONFIG_DRM_GUD=m
+CONFIG_DRM_SSD130X=m
+CONFIG_DRM_SSD130X_I2C=m
+CONFIG_DRM_SSD130X_SPI=m
+CONFIG_FB=y
+CONFIG_FB_BCM2708=y
+CONFIG_FB_SIMPLE=y
+CONFIG_FB_SSD1307=m
+CONFIG_FB_RPISENSE=m
+CONFIG_BACKLIGHT_PWM=m
+CONFIG_BACKLIGHT_RPI=m
+CONFIG_BACKLIGHT_LM3630A=m
+CONFIG_BACKLIGHT_GPIO=m
+CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y
+CONFIG_LOGO=y
+# CONFIG_LOGO_LINUX_MONO is not set
+# CONFIG_LOGO_LINUX_VGA16 is not set
+CONFIG_SOUND=y
+CONFIG_SND=m
+CONFIG_SND_OSSEMUL=y
+CONFIG_SND_PCM_OSS=m
+CONFIG_SND_HRTIMER=m
+CONFIG_SND_DYNAMIC_MINORS=y
+CONFIG_SND_SEQUENCER=m
+CONFIG_SND_SEQ_DUMMY=m
+CONFIG_SND_DUMMY=m
+CONFIG_SND_ALOOP=m
+CONFIG_SND_VIRMIDI=m
+CONFIG_SND_MTPAV=m
+CONFIG_SND_SERIAL_U16550=m
+CONFIG_SND_MPU401=m
+CONFIG_SND_PIMIDI=m
+CONFIG_SND_PISOUND_MICRO=m
+CONFIG_SND_USB_AUDIO=m
+CONFIG_SND_USB_UA101=m
+CONFIG_SND_USB_CAIAQ=m
+CONFIG_SND_USB_CAIAQ_INPUT=y
+CONFIG_SND_USB_6FIRE=m
+CONFIG_SND_USB_HIFACE=m
+CONFIG_SND_USB_TONEPORT=m
+CONFIG_SND_SOC=m
+CONFIG_SND_BCM2835_SOC_I2S=m
+CONFIG_SND_BCM2708_SOC_CHIPDIP_DAC=m
+CONFIG_SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_ADC=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DAC=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUS=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_STUDIO_DAC8X=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DIGI=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_AMP=m
+CONFIG_SND_BCM2708_SOC_PIFI_40=m
+CONFIG_SND_BCM2708_SOC_RPI_CIRRUS=m
+CONFIG_SND_BCM2708_SOC_RPI_DAC=m
+CONFIG_SND_BCM2708_SOC_RPI_PROTO=m
+CONFIG_SND_BCM2708_SOC_JUSTBOOM_BOTH=m
+CONFIG_SND_BCM2708_SOC_JUSTBOOM_DAC=m
+CONFIG_SND_BCM2708_SOC_JUSTBOOM_DIGI=m
+CONFIG_SND_BCM2708_SOC_IQAUDIO_CODEC=m
+CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC=m
+CONFIG_SND_BCM2708_SOC_IQAUDIO_DIGI=m
+CONFIG_SND_BCM2708_SOC_I_SABRE_Q2M=m
+CONFIG_SND_BCM2708_SOC_ADAU1977_ADC=m
+CONFIG_SND_AUDIOINJECTOR_PI_SOUNDCARD=m
+CONFIG_SND_AUDIOINJECTOR_OCTO_SOUNDCARD=m
+CONFIG_SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD=m
+CONFIG_SND_AUDIOSENSE_PI=m
+CONFIG_SND_DIGIDAC1_SOUNDCARD=m
+CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO=m
+CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO_V2=m
+CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC=m
+CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS=m
+CONFIG_SND_BCM2708_SOC_ALLO_BOSS_DAC=m
+CONFIG_SND_BCM2708_SOC_ALLO_BOSS2_DAC=m
+CONFIG_SND_BCM2708_SOC_ALLO_DIGIONE=m
+CONFIG_SND_BCM2708_SOC_ALLO_KATANA_DAC=m
+CONFIG_SND_BCM2708_SOC_FE_PI_AUDIO=m
+CONFIG_SND_PISOUND=m
+CONFIG_SND_DACBERRY400=m
+CONFIG_SND_DESIGNWARE_I2S=m
+CONFIG_SND_DESIGNWARE_PCM=y
+CONFIG_SND_RP1_AUDIO_OUT=m
+CONFIG_SND_SOC_AD193X_SPI=m
+CONFIG_SND_SOC_AD193X_I2C=m
+CONFIG_SND_SOC_ADAU1701=m
+CONFIG_SND_SOC_ADAU7002=m
+CONFIG_SND_SOC_AK4554=m
+CONFIG_SND_SOC_CS4265=m
+CONFIG_SND_SOC_ICS43432=m
+CONFIG_SND_SOC_MA120X0P=m
+CONFIG_SND_SOC_MAX98357A=m
+CONFIG_SND_SOC_PCM3168A_I2C=m
+CONFIG_SND_SOC_TLV320AIC23_I2C=m
+CONFIG_SND_SOC_WM8804_I2C=m
+CONFIG_SND_SOC_WM8904=m
+CONFIG_SND_SOC_WM8960=m
+CONFIG_SND_SIMPLE_CARD=m
+CONFIG_HID_BATTERY_STRENGTH=y
+CONFIG_HIDRAW=y
+CONFIG_UHID=m
+CONFIG_HID_A4TECH=m
+CONFIG_HID_ACRUX=m
+CONFIG_HID_APPLE=m
+CONFIG_HID_ASUS=m
+CONFIG_HID_BELKIN=m
+CONFIG_HID_BETOP_FF=m
+CONFIG_HID_BIGBEN_FF=m
+CONFIG_HID_CHERRY=m
+CONFIG_HID_CHICONY=m
+CONFIG_HID_CP2112=m
+CONFIG_HID_CYPRESS=m
+CONFIG_HID_DRAGONRISE=m
+CONFIG_HID_EMS_FF=m
+CONFIG_HID_ELECOM=m
+CONFIG_HID_ELO=m
+CONFIG_HID_EZKEY=m
+CONFIG_HID_GEMBIRD=m
+CONFIG_HID_HOLTEK=m
+CONFIG_HID_KEYTOUCH=m
+CONFIG_HID_KYE=m
+CONFIG_HID_UCLOGIC=m
+CONFIG_HID_WALTOP=m
+CONFIG_HID_GYRATION=m
+CONFIG_HID_TWINHAN=m
+CONFIG_HID_KENSINGTON=m
+CONFIG_HID_LCPOWER=m
+CONFIG_HID_LOGITECH=m
+CONFIG_HID_LOGITECH_DJ=m
+CONFIG_LOGITECH_FF=y
+CONFIG_LOGIRUMBLEPAD2_FF=y
+CONFIG_LOGIG940_FF=y
+CONFIG_HID_MAGICMOUSE=m
+CONFIG_HID_MICROSOFT=m
+CONFIG_HID_MONTEREY=m
+CONFIG_HID_MULTITOUCH=m
+CONFIG_HID_NINTENDO=m
+CONFIG_NINTENDO_FF=y
+CONFIG_HID_NTRIG=m
+CONFIG_HID_ORTEK=m
+CONFIG_HID_PANTHERLORD=m
+CONFIG_HID_PETALYNX=m
+CONFIG_HID_PICOLCD=m
+CONFIG_HID_PLAYSTATION=m
+CONFIG_PLAYSTATION_FF=y
+CONFIG_HID_ROCCAT=m
+CONFIG_HID_SAMSUNG=m
+CONFIG_HID_SONY=m
+CONFIG_SONY_FF=y
+CONFIG_HID_SPEEDLINK=m
+CONFIG_HID_STEAM=m
+CONFIG_HID_SUNPLUS=m
+CONFIG_HID_GREENASIA=m
+CONFIG_HID_SMARTJOYPLUS=m
+CONFIG_HID_TOPSEED=m
+CONFIG_HID_THINGM=m
+CONFIG_HID_THRUSTMASTER=m
+CONFIG_HID_WACOM=m
+CONFIG_HID_WIIMOTE=m
+CONFIG_HID_XINMO=m
+CONFIG_HID_ZEROPLUS=m
+CONFIG_HID_ZYDACRON=m
+CONFIG_HID_PID=y
+CONFIG_USB_HIDDEV=y
+CONFIG_I2C_HID_OF=m
+CONFIG_USB=y
+CONFIG_USB_ANNOUNCE_NEW_DEVICES=y
+CONFIG_USB_MON=m
+CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_XHCI_PCI_RENESAS=m
+CONFIG_USB_DWCOTG=y
+CONFIG_USB_PRINTER=m
+CONFIG_USB_TMC=m
+CONFIG_USB_STORAGE=y
+CONFIG_USB_STORAGE_REALTEK=m
+CONFIG_USB_STORAGE_DATAFAB=m
+CONFIG_USB_STORAGE_FREECOM=m
+CONFIG_USB_STORAGE_ISD200=m
+CONFIG_USB_STORAGE_USBAT=m
+CONFIG_USB_STORAGE_SDDR09=m
+CONFIG_USB_STORAGE_SDDR55=m
+CONFIG_USB_STORAGE_JUMPSHOT=m
+CONFIG_USB_STORAGE_ALAUDA=m
+CONFIG_USB_STORAGE_ONETOUCH=m
+CONFIG_USB_STORAGE_KARMA=m
+CONFIG_USB_STORAGE_CYPRESS_ATACB=m
+CONFIG_USB_STORAGE_ENE_UB6250=m
+CONFIG_USB_UAS=y
+CONFIG_USB_MDC800=m
+CONFIG_USB_MICROTEK=m
+CONFIG_USBIP_CORE=m
+CONFIG_USBIP_VHCI_HCD=m
+CONFIG_USBIP_HOST=m
+CONFIG_USBIP_VUDC=m
+CONFIG_USB_DWC3=y
+CONFIG_USB_DWC2=y
+CONFIG_USB_SERIAL=m
+CONFIG_USB_SERIAL_GENERIC=y
+CONFIG_USB_SERIAL_SIMPLE=m
+CONFIG_USB_SERIAL_AIRCABLE=m
+CONFIG_USB_SERIAL_ARK3116=m
+CONFIG_USB_SERIAL_BELKIN=m
+CONFIG_USB_SERIAL_CH341=m
+CONFIG_USB_SERIAL_WHITEHEAT=m
+CONFIG_USB_SERIAL_DIGI_ACCELEPORT=m
+CONFIG_USB_SERIAL_CP210X=m
+CONFIG_USB_SERIAL_CYPRESS_M8=m
+CONFIG_USB_SERIAL_EMPEG=m
+CONFIG_USB_SERIAL_FTDI_SIO=m
+CONFIG_USB_SERIAL_VISOR=m
+CONFIG_USB_SERIAL_IPAQ=m
+CONFIG_USB_SERIAL_IR=m
+CONFIG_USB_SERIAL_EDGEPORT=m
+CONFIG_USB_SERIAL_EDGEPORT_TI=m
+CONFIG_USB_SERIAL_F81232=m
+CONFIG_USB_SERIAL_F8153X=m
+CONFIG_USB_SERIAL_GARMIN=m
+CONFIG_USB_SERIAL_IPW=m
+CONFIG_USB_SERIAL_IUU=m
+CONFIG_USB_SERIAL_KEYSPAN_PDA=m
+CONFIG_USB_SERIAL_KEYSPAN=m
+CONFIG_USB_SERIAL_KLSI=m
+CONFIG_USB_SERIAL_KOBIL_SCT=m
+CONFIG_USB_SERIAL_MCT_U232=m
+CONFIG_USB_SERIAL_METRO=m
+CONFIG_USB_SERIAL_MOS7720=m
+CONFIG_USB_SERIAL_MOS7840=m
+CONFIG_USB_SERIAL_MXUPORT=m
+CONFIG_USB_SERIAL_NAVMAN=m
+CONFIG_USB_SERIAL_PL2303=m
+CONFIG_USB_SERIAL_OTI6858=m
+CONFIG_USB_SERIAL_QCAUX=m
+CONFIG_USB_SERIAL_QUALCOMM=m
+CONFIG_USB_SERIAL_SPCP8X5=m
+CONFIG_USB_SERIAL_SAFE=m
+CONFIG_USB_SERIAL_SIERRAWIRELESS=m
+CONFIG_USB_SERIAL_SYMBOL=m
+CONFIG_USB_SERIAL_TI=m
+CONFIG_USB_SERIAL_CYBERJACK=m
+CONFIG_USB_SERIAL_OPTION=m
+CONFIG_USB_SERIAL_OMNINET=m
+CONFIG_USB_SERIAL_OPTICON=m
+CONFIG_USB_SERIAL_XSENS_MT=m
+CONFIG_USB_SERIAL_WISHBONE=m
+CONFIG_USB_SERIAL_SSU100=m
+CONFIG_USB_SERIAL_QT2=m
+CONFIG_USB_SERIAL_UPD78F0730=m
+CONFIG_USB_SERIAL_XR=m
+CONFIG_USB_SERIAL_DEBUG=m
+CONFIG_USB_EMI62=m
+CONFIG_USB_EMI26=m
+CONFIG_USB_ADUTUX=m
+CONFIG_USB_SEVSEG=m
+CONFIG_USB_LEGOTOWER=m
+CONFIG_USB_LCD=m
+CONFIG_USB_CYPRESS_CY7C63=m
+CONFIG_USB_CYTHERM=m
+CONFIG_USB_IDMOUSE=m
+CONFIG_USB_APPLEDISPLAY=m
+CONFIG_USB_LD=m
+CONFIG_USB_TRANCEVIBRATOR=m
+CONFIG_USB_IOWARRIOR=m
+CONFIG_USB_TEST=m
+CONFIG_USB_ISIGHTFW=m
+CONFIG_USB_YUREX=m
+CONFIG_USB_ATM=m
+CONFIG_USB_SPEEDTOUCH=m
+CONFIG_USB_CXACRU=m
+CONFIG_USB_UEAGLEATM=m
+CONFIG_USB_XUSBATM=m
+CONFIG_NOP_USB_XCEIV=y
+CONFIG_USB_GADGET=y
+CONFIG_USB_CONFIGFS=m
+CONFIG_USB_CONFIGFS_SERIAL=y
+CONFIG_USB_CONFIGFS_ACM=y
+CONFIG_USB_CONFIGFS_OBEX=y
+CONFIG_USB_CONFIGFS_NCM=y
+CONFIG_USB_CONFIGFS_ECM=y
+CONFIG_USB_CONFIGFS_ECM_SUBSET=y
+CONFIG_USB_CONFIGFS_RNDIS=y
+CONFIG_USB_CONFIGFS_EEM=y
+CONFIG_USB_CONFIGFS_MASS_STORAGE=y
+CONFIG_USB_CONFIGFS_F_LB_SS=y
+CONFIG_USB_CONFIGFS_F_FS=y
+CONFIG_USB_CONFIGFS_F_UAC1=y
+CONFIG_USB_CONFIGFS_F_UAC2=y
+CONFIG_USB_CONFIGFS_F_MIDI=y
+CONFIG_USB_CONFIGFS_F_HID=y
+CONFIG_USB_CONFIGFS_F_UVC=y
+CONFIG_USB_CONFIGFS_F_PRINTER=y
+CONFIG_USB_ZERO=m
+CONFIG_USB_AUDIO=m
+CONFIG_USB_ETH=m
+CONFIG_USB_GADGETFS=m
+CONFIG_USB_MASS_STORAGE=m
+CONFIG_USB_G_SERIAL=m
+CONFIG_USB_MIDI_GADGET=m
+CONFIG_USB_G_PRINTER=m
+CONFIG_USB_CDC_COMPOSITE=m
+CONFIG_USB_G_ACM_MS=m
+CONFIG_USB_G_MULTI=m
+CONFIG_USB_G_HID=m
+CONFIG_USB_G_WEBCAM=m
+CONFIG_MMC=y
+CONFIG_MMC_BLOCK_MINORS=32
+CONFIG_MMC_BCM2835_MMC=y
+CONFIG_MMC_BCM2835_DMA=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_MMC_SDHCI_OF_DWCMSHC=m
+CONFIG_MMC_SDHCI_IPROC=y
+CONFIG_MMC_SPI=m
+CONFIG_MMC_HSQ=y
+CONFIG_MMC_BCM2835=y
+CONFIG_LEDS_CLASS=y
+CONFIG_LEDS_CLASS_MULTICOLOR=m
+CONFIG_LEDS_PCA9532=m
+CONFIG_LEDS_GPIO=y
+CONFIG_LEDS_PCA955X=m
+CONFIG_LEDS_PCA963X=m
+CONFIG_LEDS_PWM=y
+CONFIG_LEDS_IS31FL32XX=m
+CONFIG_LEDS_TRIGGER_TIMER=y
+CONFIG_LEDS_TRIGGER_ONESHOT=y
+CONFIG_LEDS_TRIGGER_HEARTBEAT=y
+CONFIG_LEDS_TRIGGER_BACKLIGHT=y
+CONFIG_LEDS_TRIGGER_DEFAULT_ON=y
+CONFIG_LEDS_TRIGGER_TRANSIENT=m
+CONFIG_LEDS_TRIGGER_CAMERA=m
+CONFIG_LEDS_TRIGGER_INPUT=y
+CONFIG_LEDS_TRIGGER_PANIC=y
+CONFIG_LEDS_TRIGGER_NETDEV=m
+CONFIG_LEDS_TRIGGER_PATTERN=m
+CONFIG_LEDS_TRIGGER_ACTPWR=y
+CONFIG_ACCESSIBILITY=y
+CONFIG_SPEAKUP=m
+CONFIG_SPEAKUP_SYNTH_SOFT=m
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_DRV_ABX80X=m
+CONFIG_RTC_DRV_DS1307=m
+CONFIG_RTC_DRV_DS1374=m
+CONFIG_RTC_DRV_DS1672=m
+CONFIG_RTC_DRV_MAX6900=m
+CONFIG_RTC_DRV_RS5C372=m
+CONFIG_RTC_DRV_ISL1208=m
+CONFIG_RTC_DRV_ISL12022=m
+CONFIG_RTC_DRV_X1205=m
+CONFIG_RTC_DRV_PCF8523=m
+CONFIG_RTC_DRV_PCF85063=m
+CONFIG_RTC_DRV_PCF85363=m
+CONFIG_RTC_DRV_PCF8563=m
+CONFIG_RTC_DRV_PCF8583=m
+CONFIG_RTC_DRV_M41T80=m
+CONFIG_RTC_DRV_BQ32K=m
+CONFIG_RTC_DRV_S35390A=m
+CONFIG_RTC_DRV_FM3130=m
+CONFIG_RTC_DRV_RX8581=m
+CONFIG_RTC_DRV_RX8025=m
+CONFIG_RTC_DRV_EM3027=m
+CONFIG_RTC_DRV_RV3028=m
+CONFIG_RTC_DRV_RV3032=m
+CONFIG_RTC_DRV_RV8803=m
+CONFIG_RTC_DRV_SD3078=m
+CONFIG_RTC_DRV_M41T93=m
+CONFIG_RTC_DRV_M41T94=m
+CONFIG_RTC_DRV_DS1302=m
+CONFIG_RTC_DRV_DS1305=m
+CONFIG_RTC_DRV_DS1390=m
+CONFIG_RTC_DRV_R9701=m
+CONFIG_RTC_DRV_RX4581=m
+CONFIG_RTC_DRV_RS5C348=m
+CONFIG_RTC_DRV_MAX6902=m
+CONFIG_RTC_DRV_PCF2123=m
+CONFIG_RTC_DRV_DS3232=m
+CONFIG_RTC_DRV_PCF2127=m
+CONFIG_RTC_DRV_RV3029C2=m
+CONFIG_DMADEVICES=y
+CONFIG_DMA_BCM2835=y
+CONFIG_DW_AXI_DMAC=y
+CONFIG_DMA_BCM2708=y
+CONFIG_UDMABUF=y
+CONFIG_DMABUF_HEAPS=y
+CONFIG_DMABUF_HEAPS_SYSTEM=y
+CONFIG_DMABUF_HEAPS_CMA=y
+CONFIG_UIO=m
+CONFIG_UIO_PDRV_GENIRQ=m
+CONFIG_VHOST_NET=m
+CONFIG_VHOST_VSOCK=m
+CONFIG_VHOST_CROSS_ENDIAN_LEGACY=y
+CONFIG_STAGING=y
+CONFIG_VT6656=m
+CONFIG_STAGING_MEDIA=y
+CONFIG_STAGING_MEDIA_DEPRECATED=y
+CONFIG_FB_TFT=m
+CONFIG_FB_TFT_AGM1264K_FL=m
+CONFIG_FB_TFT_BD663474=m
+CONFIG_FB_TFT_HX8340BN=m
+CONFIG_FB_TFT_HX8347D=m
+CONFIG_FB_TFT_HX8353D=m
+CONFIG_FB_TFT_HX8357D=m
+CONFIG_FB_TFT_ILI9163=m
+CONFIG_FB_TFT_ILI9320=m
+CONFIG_FB_TFT_ILI9325=m
+CONFIG_FB_TFT_ILI9340=m
+CONFIG_FB_TFT_ILI9341=m
+CONFIG_FB_TFT_ILI9481=m
+CONFIG_FB_TFT_ILI9486=m
+CONFIG_FB_TFT_PCD8544=m
+CONFIG_FB_TFT_RA8875=m
+CONFIG_FB_TFT_S6D02A1=m
+CONFIG_FB_TFT_S6D1121=m
+CONFIG_FB_TFT_SH1106=m
+CONFIG_FB_TFT_SSD1289=m
+CONFIG_FB_TFT_SSD1306=m
+CONFIG_FB_TFT_SSD1331=m
+CONFIG_FB_TFT_SSD1351=m
+CONFIG_FB_TFT_ST7735R=m
+CONFIG_FB_TFT_ST7789V=m
+CONFIG_FB_TFT_TINYLCD=m
+CONFIG_FB_TFT_TLS8204=m
+CONFIG_FB_TFT_UC1611=m
+CONFIG_FB_TFT_UC1701=m
+CONFIG_FB_TFT_UPD161704=m
+CONFIG_BCM2835_VCHIQ=y
+CONFIG_SND_BCM2835=m
+CONFIG_VIDEO_BCM2835=m
+CONFIG_VIDEO_CODEC_BCM2835=m
+CONFIG_VIDEO_ISP_BCM2835=m
+CONFIG_COMMON_CLK_RP1=y
+CONFIG_COMMON_CLK_RP1_SDIO=y
+CONFIG_COMMON_CLK_SI5351=m
+CONFIG_CLK_RASPBERRYPI=y
+CONFIG_MAILBOX=y
+CONFIG_BCM2835_MBOX=y
+CONFIG_MBOX_RP1=m
+CONFIG_BCM2712_IOMMU=y
+CONFIG_RASPBERRYPI_POWER=y
+CONFIG_IIO=m
+CONFIG_IIO_BUFFER_CB=m
+CONFIG_IIO_SW_TRIGGER=m
+CONFIG_ADXL355_I2C=m
+CONFIG_AD799X=m
+CONFIG_MCP320X=m
+CONFIG_MCP3422=m
+CONFIG_TI_ADS1015=m
+CONFIG_BME680=m
+CONFIG_CCS811=m
+CONFIG_SCD4X=m
+CONFIG_SENSIRION_SGP30=m
+CONFIG_SPS30_I2C=m
+CONFIG_MAX30102=m
+CONFIG_DHT11=m
+CONFIG_HDC100X=m
+CONFIG_HDC3020=m
+CONFIG_HTS221=m
+CONFIG_HTU21=m
+CONFIG_SI7020=m
+CONFIG_BOSCH_BNO055_I2C=m
+CONFIG_INV_MPU6050_I2C=m
+CONFIG_APDS9960=m
+CONFIG_AS73211=m
+CONFIG_BH1750=m
+CONFIG_TCS3472=m
+CONFIG_TSL4531=m
+CONFIG_VEML6030=m
+CONFIG_VEML6040=m
+CONFIG_VEML6070=m
+CONFIG_VEML6075=m
+CONFIG_IIO_HRTIMER_TRIGGER=m
+CONFIG_IIO_INTERRUPT_TRIGGER=m
+CONFIG_IIO_SYSFS_TRIGGER=m
+CONFIG_BMP280=m
+CONFIG_MS5637=m
+CONFIG_MAXIM_THERMOCOUPLE=m
+CONFIG_TMP117=m
+CONFIG_MAX31856=m
+CONFIG_PWM=y
+CONFIG_PWM_BCM2835=m
+CONFIG_PWM_BRCMSTB=y
+CONFIG_PWM_GPIO=m
+CONFIG_PWM_PCA9685=m
+CONFIG_PWM_PIO_RP1=m
+CONFIG_PWM_RASPBERRYPI_POE=m
+CONFIG_PWM_RP1=y
+CONFIG_BCM2712_MIP=y
+CONFIG_RPI_AXIPERF=m
+CONFIG_ANDROID_BINDER_IPC=y
+CONFIG_ANDROID_BINDERFS=y
+CONFIG_NVMEM_RMEM=m
+CONFIG_MUX_GPIO=m
+CONFIG_EXT4_FS=y
+CONFIG_EXT4_FS_POSIX_ACL=y
+CONFIG_EXT4_FS_SECURITY=y
+CONFIG_REISERFS_FS=m
+CONFIG_REISERFS_FS_XATTR=y
+CONFIG_REISERFS_FS_POSIX_ACL=y
+CONFIG_REISERFS_FS_SECURITY=y
+CONFIG_JFS_FS=m
+CONFIG_JFS_POSIX_ACL=y
+CONFIG_JFS_SECURITY=y
+CONFIG_JFS_STATISTICS=y
+CONFIG_XFS_FS=m
+CONFIG_XFS_QUOTA=y
+CONFIG_XFS_POSIX_ACL=y
+CONFIG_XFS_RT=y
+CONFIG_GFS2_FS=m
+CONFIG_OCFS2_FS=m
+CONFIG_BTRFS_FS=m
+CONFIG_BTRFS_FS_POSIX_ACL=y
+CONFIG_NILFS2_FS=m
+CONFIG_F2FS_FS=y
+CONFIG_F2FS_FS_SECURITY=y
+CONFIG_BCACHEFS_FS=m
+CONFIG_BCACHEFS_QUOTA=y
+CONFIG_BCACHEFS_POSIX_ACL=y
+CONFIG_FS_ENCRYPTION=y
+CONFIG_FANOTIFY=y
+CONFIG_QFMT_V1=m
+CONFIG_QFMT_V2=m
+CONFIG_AUTOFS_FS=y
+CONFIG_FUSE_FS=m
+CONFIG_CUSE=m
+CONFIG_OVERLAY_FS=m
+CONFIG_FSCACHE=y
+CONFIG_FSCACHE_STATS=y
+CONFIG_CACHEFILES=m
+CONFIG_ISO9660_FS=m
+CONFIG_JOLIET=y
+CONFIG_ZISOFS=y
+CONFIG_UDF_FS=m
+CONFIG_MSDOS_FS=y
+CONFIG_VFAT_FS=y
+CONFIG_FAT_DEFAULT_IOCHARSET="ascii"
+CONFIG_EXFAT_FS=m
+CONFIG_NTFS3_FS=m
+CONFIG_TMPFS=y
+CONFIG_TMPFS_POSIX_ACL=y
+CONFIG_ECRYPT_FS=m
+CONFIG_HFS_FS=m
+CONFIG_HFSPLUS_FS=m
+CONFIG_JFFS2_FS=m
+CONFIG_JFFS2_SUMMARY=y
+CONFIG_UBIFS_FS=m
+CONFIG_SQUASHFS=m
+CONFIG_SQUASHFS_XATTR=y
+CONFIG_SQUASHFS_LZO=y
+CONFIG_SQUASHFS_XZ=y
+CONFIG_SQUASHFS_ZSTD=y
+CONFIG_PSTORE=y
+CONFIG_PSTORE_CONSOLE=y
+CONFIG_PSTORE_RAM=y
+CONFIG_EROFS_FS=m
+CONFIG_EROFS_FS_ZIP_ZSTD=y
+CONFIG_NFS_FS=y
+CONFIG_NFS_V2=y
+CONFIG_NFS_V3_ACL=y
+CONFIG_NFS_V4=y
+CONFIG_NFS_SWAP=y
+CONFIG_NFS_V4_1=y
+CONFIG_NFS_V4_2=y
+CONFIG_ROOT_NFS=y
+CONFIG_NFSD=m
+CONFIG_NFSD_V2=y
+CONFIG_NFSD_V2_ACL=y
+CONFIG_NFSD_V3_ACL=y
+CONFIG_NFSD_V4=y
+CONFIG_CEPH_FS=m
+CONFIG_CIFS=m
+CONFIG_CIFS_UPCALL=y
+CONFIG_CIFS_XATTR=y
+CONFIG_CIFS_POSIX=y
+CONFIG_CIFS_DFS_UPCALL=y
+CONFIG_CIFS_FSCACHE=y
+CONFIG_SMB_SERVER=m
+CONFIG_9P_FS=m
+CONFIG_9P_FS_POSIX_ACL=y
+CONFIG_NLS_DEFAULT="utf8"
+CONFIG_NLS_CODEPAGE_437=y
+CONFIG_NLS_CODEPAGE_737=m
+CONFIG_NLS_CODEPAGE_775=m
+CONFIG_NLS_CODEPAGE_850=m
+CONFIG_NLS_CODEPAGE_852=m
+CONFIG_NLS_CODEPAGE_855=m
+CONFIG_NLS_CODEPAGE_857=m
+CONFIG_NLS_CODEPAGE_860=m
+CONFIG_NLS_CODEPAGE_861=m
+CONFIG_NLS_CODEPAGE_862=m
+CONFIG_NLS_CODEPAGE_863=m
+CONFIG_NLS_CODEPAGE_864=m
+CONFIG_NLS_CODEPAGE_865=m
+CONFIG_NLS_CODEPAGE_866=m
+CONFIG_NLS_CODEPAGE_869=m
+CONFIG_NLS_CODEPAGE_936=m
+CONFIG_NLS_CODEPAGE_950=m
+CONFIG_NLS_CODEPAGE_932=m
+CONFIG_NLS_CODEPAGE_949=m
+CONFIG_NLS_CODEPAGE_874=m
+CONFIG_NLS_ISO8859_8=m
+CONFIG_NLS_CODEPAGE_1250=m
+CONFIG_NLS_CODEPAGE_1251=m
+CONFIG_NLS_ASCII=y
+CONFIG_NLS_ISO8859_1=m
+CONFIG_NLS_ISO8859_2=m
+CONFIG_NLS_ISO8859_3=m
+CONFIG_NLS_ISO8859_4=m
+CONFIG_NLS_ISO8859_5=m
+CONFIG_NLS_ISO8859_6=m
+CONFIG_NLS_ISO8859_7=m
+CONFIG_NLS_ISO8859_9=m
+CONFIG_NLS_ISO8859_13=m
+CONFIG_NLS_ISO8859_14=m
+CONFIG_NLS_ISO8859_15=m
+CONFIG_NLS_KOI8_R=m
+CONFIG_NLS_KOI8_U=m
+CONFIG_DLM=m
+CONFIG_UNICODE=m
+CONFIG_KEY_DH_OPERATIONS=y
+CONFIG_SECURITY=y
+CONFIG_SECURITY_APPARMOR=y
+CONFIG_LSM=""
+CONFIG_CRYPTO_USER=m
+CONFIG_CRYPTO_CRYPTD=m
+CONFIG_CRYPTO_AES=m
+CONFIG_CRYPTO_CAST5=m
+CONFIG_CRYPTO_DES=y
+CONFIG_CRYPTO_TWOFISH=m
+CONFIG_CRYPTO_ADIANTUM=m
+CONFIG_CRYPTO_CBC=m
+CONFIG_CRYPTO_CHACHA20POLY1305=m
+CONFIG_CRYPTO_MD4=m
+CONFIG_CRYPTO_SHA512=m
+CONFIG_CRYPTO_WP512=m
+CONFIG_CRYPTO_XCBC=m
+CONFIG_CRYPTO_LZ4=m
+CONFIG_CRYPTO_LZ4HC=m
+CONFIG_CRYPTO_USER_API_HASH=m
+CONFIG_CRYPTO_USER_API_SKCIPHER=m
+CONFIG_CRYPTO_USER_API_RNG=m
+CONFIG_CRYPTO_USER_API_AEAD=m
+CONFIG_CRYPTO_NHPOLY1305_NEON=m
+CONFIG_CRYPTO_GHASH_ARM64_CE=m
+CONFIG_CRYPTO_SHA1_ARM64_CE=m
+CONFIG_CRYPTO_SHA2_ARM64_CE=m
+CONFIG_CRYPTO_SHA512_ARM64_CE=m
+CONFIG_CRYPTO_SHA3_ARM64=m
+CONFIG_CRYPTO_SM3_ARM64_CE=m
+CONFIG_CRYPTO_AES_ARM64=m
+CONFIG_CRYPTO_AES_ARM64_BS=m
+CONFIG_CRYPTO_SM4_ARM64_CE=m
+CONFIG_CRYPTO_AES_ARM64_CE_CCM=m
+# CONFIG_CRYPTO_HW is not set
+CONFIG_PKCS8_PRIVATE_KEY_PARSER=m
+CONFIG_CRC_ITU_T=y
+CONFIG_LIBCRC32C=y
+CONFIG_DMA_CMA=y
+CONFIG_CMA_SIZE_MBYTES=5
+CONFIG_PRINTK_TIME=y
+CONFIG_BOOT_PRINTK_DELAY=y
+CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1f6
+CONFIG_KGDB=y
+CONFIG_KGDB_KDB=y
+CONFIG_KDB_KEYBOARD=y
+CONFIG_DEBUG_MEMORY_INIT=y
+CONFIG_DETECT_HUNG_TASK=y
+CONFIG_LATENCYTOP=y
+CONFIG_FUNCTION_PROFILER=y
+CONFIG_STACK_TRACER=y
+CONFIG_SCHED_TRACER=y
+CONFIG_TIMERLAT_TRACER=y
+CONFIG_FTRACE_SYSCALLS=y
+CONFIG_BLK_DEV_IO_TRACE=y
+# CONFIG_UPROBE_EVENTS is not set
+# CONFIG_STRICT_DEVMEM is not set
diff --git a/arch/arm64/configs/bcm2712_defconfig b/arch/arm64/configs/bcm2712_defconfig
new file mode 100644
index 000000000000..1a3be7797999
--- /dev/null
+++ b/arch/arm64/configs/bcm2712_defconfig
@@ -0,0 +1,1786 @@
+CONFIG_LOCALVERSION="-v8-16k"
+# CONFIG_LOCALVERSION_AUTO is not set
+CONFIG_SYSVIPC=y
+CONFIG_POSIX_MQUEUE=y
+CONFIG_GENERIC_IRQ_DEBUGFS=y
+CONFIG_NO_HZ=y
+CONFIG_HIGH_RES_TIMERS=y
+CONFIG_BPF_SYSCALL=y
+CONFIG_BPF_JIT=y
+CONFIG_PREEMPT=y
+CONFIG_BSD_PROCESS_ACCT=y
+CONFIG_BSD_PROCESS_ACCT_V3=y
+CONFIG_TASKSTATS=y
+CONFIG_TASK_DELAY_ACCT=y
+CONFIG_TASK_XACCT=y
+CONFIG_TASK_IO_ACCOUNTING=y
+CONFIG_PSI=y
+CONFIG_PSI_DEFAULT_DISABLED=y
+CONFIG_IKCONFIG=m
+CONFIG_IKCONFIG_PROC=y
+CONFIG_MEMCG=y
+CONFIG_BLK_CGROUP=y
+CONFIG_CFS_BANDWIDTH=y
+CONFIG_CGROUP_PIDS=y
+CONFIG_CGROUP_FREEZER=y
+CONFIG_CPUSETS=y
+CONFIG_CPUSETS_V1=y
+CONFIG_CGROUP_DEVICE=y
+CONFIG_CGROUP_CPUACCT=y
+CONFIG_CGROUP_PERF=y
+CONFIG_CGROUP_BPF=y
+CONFIG_NAMESPACES=y
+CONFIG_USER_NS=y
+CONFIG_CHECKPOINT_RESTORE=y
+CONFIG_SCHED_AUTOGROUP=y
+CONFIG_BLK_DEV_INITRD=y
+CONFIG_EXPERT=y
+CONFIG_PROFILING=y
+CONFIG_ARCH_BCM=y
+CONFIG_ARCH_BCM2835=y
+CONFIG_ARCH_BRCMSTB=y
+CONFIG_ARM64_ERRATUM_834220=y
+CONFIG_ARM64_ERRATUM_2441007=y
+CONFIG_ARM64_ERRATUM_1286807=y
+CONFIG_ARM64_ERRATUM_1542419=y
+CONFIG_ARM64_ERRATUM_2441009=y
+# CONFIG_CAVIUM_ERRATUM_22375 is not set
+# CONFIG_CAVIUM_ERRATUM_23154 is not set
+# CONFIG_CAVIUM_ERRATUM_27456 is not set
+CONFIG_ARM64_16K_PAGES=y
+CONFIG_ARM64_VA_BITS_47=y
+CONFIG_NR_CPUS=4
+CONFIG_HOTPLUG_CPU=y
+CONFIG_NUMA=y
+CONFIG_COMPAT=y
+CONFIG_ARMV8_DEPRECATED=y
+CONFIG_SWP_EMULATION=y
+CONFIG_CP15_BARRIER_EMULATION=y
+CONFIG_SETEND_EMULATION=y
+CONFIG_RANDOMIZE_BASE=y
+CONFIG_CMDLINE="console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rootwait"
+# CONFIG_SUSPEND is not set
+CONFIG_PM=y
+CONFIG_PM_DEBUG=y
+CONFIG_CPU_IDLE=y
+CONFIG_CPU_FREQ=y
+CONFIG_CPU_FREQ_STAT=y
+CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND=y
+CONFIG_CPU_FREQ_GOV_POWERSAVE=y
+CONFIG_CPU_FREQ_GOV_USERSPACE=y
+CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
+CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y
+CONFIG_CPUFREQ_DT=y
+CONFIG_ARM_RASPBERRYPI_CPUFREQ=y
+CONFIG_VIRTUALIZATION=y
+CONFIG_KVM=y
+CONFIG_JUMP_LABEL=y
+CONFIG_ARCH_MMAP_RND_BITS=18
+CONFIG_ARCH_MMAP_RND_COMPAT_BITS=11
+CONFIG_MODULES=y
+CONFIG_MODULE_UNLOAD=y
+CONFIG_MODVERSIONS=y
+CONFIG_MODULE_SRCVERSION_ALL=y
+CONFIG_MODULE_COMPRESS=y
+CONFIG_MODULE_COMPRESS_XZ=y
+CONFIG_BLK_DEV_THROTTLING=y
+CONFIG_PARTITION_ADVANCED=y
+CONFIG_MAC_PARTITION=y
+CONFIG_BINFMT_MISC=m
+CONFIG_ZSWAP=y
+# CONFIG_COMPAT_BRK is not set
+CONFIG_CMA=y
+CONFIG_CMA_AREAS=7
+CONFIG_LRU_GEN=y
+CONFIG_LRU_GEN_ENABLED=y
+CONFIG_NUMA_EMU=y
+CONFIG_NET=y
+CONFIG_PACKET=y
+CONFIG_XFRM_USER=m
+CONFIG_XFRM_INTERFACE=m
+CONFIG_XFRM_SUB_POLICY=y
+CONFIG_XFRM_STATISTICS=y
+CONFIG_NET_KEY=m
+CONFIG_IP_MULTICAST=y
+CONFIG_IP_ADVANCED_ROUTER=y
+CONFIG_IP_MULTIPLE_TABLES=y
+CONFIG_IP_ROUTE_MULTIPATH=y
+CONFIG_IP_ROUTE_VERBOSE=y
+CONFIG_IP_PNP=y
+CONFIG_IP_PNP_DHCP=y
+CONFIG_IP_PNP_RARP=y
+CONFIG_NET_IPIP=m
+CONFIG_NET_IPGRE_DEMUX=m
+CONFIG_NET_IPGRE=m
+CONFIG_IP_MROUTE=y
+CONFIG_IP_MROUTE_MULTIPLE_TABLES=y
+CONFIG_IP_PIMSM_V1=y
+CONFIG_IP_PIMSM_V2=y
+CONFIG_NET_IPVTI=m
+CONFIG_NET_FOU_IP_TUNNELS=y
+CONFIG_INET_AH=m
+CONFIG_INET_ESP=m
+CONFIG_INET_IPCOMP=m
+CONFIG_INET_DIAG=m
+CONFIG_TCP_CONG_ADVANCED=y
+CONFIG_TCP_CONG_BBR=m
+CONFIG_IPV6=m
+CONFIG_IPV6_ROUTER_PREF=y
+CONFIG_IPV6_ROUTE_INFO=y
+CONFIG_INET6_AH=m
+CONFIG_INET6_ESP=m
+CONFIG_INET6_ESP_OFFLOAD=m
+CONFIG_INET6_IPCOMP=m
+CONFIG_IPV6_ILA=m
+CONFIG_IPV6_VTI=m
+CONFIG_IPV6_SIT_6RD=y
+CONFIG_IPV6_GRE=m
+CONFIG_IPV6_MULTIPLE_TABLES=y
+CONFIG_IPV6_SUBTREES=y
+CONFIG_IPV6_MROUTE=y
+CONFIG_IPV6_MROUTE_MULTIPLE_TABLES=y
+CONFIG_IPV6_PIMSM_V2=y
+CONFIG_MPTCP=y
+CONFIG_NETWORK_PHY_TIMESTAMPING=y
+CONFIG_NETFILTER=y
+CONFIG_BRIDGE_NETFILTER=m
+CONFIG_NF_CONNTRACK=m
+CONFIG_NF_CONNTRACK_ZONES=y
+CONFIG_NF_CONNTRACK_EVENTS=y
+CONFIG_NF_CONNTRACK_TIMESTAMP=y
+CONFIG_NF_CONNTRACK_AMANDA=m
+CONFIG_NF_CONNTRACK_FTP=m
+CONFIG_NF_CONNTRACK_H323=m
+CONFIG_NF_CONNTRACK_IRC=m
+CONFIG_NF_CONNTRACK_NETBIOS_NS=m
+CONFIG_NF_CONNTRACK_SNMP=m
+CONFIG_NF_CONNTRACK_PPTP=m
+CONFIG_NF_CONNTRACK_SANE=m
+CONFIG_NF_CONNTRACK_SIP=m
+CONFIG_NF_CONNTRACK_TFTP=m
+CONFIG_NF_CT_NETLINK=m
+CONFIG_NF_TABLES=m
+CONFIG_NF_TABLES_INET=y
+CONFIG_NF_TABLES_NETDEV=y
+CONFIG_NFT_NUMGEN=m
+CONFIG_NFT_CT=m
+CONFIG_NFT_FLOW_OFFLOAD=m
+CONFIG_NFT_CONNLIMIT=m
+CONFIG_NFT_LOG=m
+CONFIG_NFT_LIMIT=m
+CONFIG_NFT_MASQ=m
+CONFIG_NFT_REDIR=m
+CONFIG_NFT_NAT=m
+CONFIG_NFT_TUNNEL=m
+CONFIG_NFT_QUEUE=m
+CONFIG_NFT_QUOTA=m
+CONFIG_NFT_REJECT=m
+CONFIG_NFT_COMPAT=m
+CONFIG_NFT_HASH=m
+CONFIG_NFT_FIB_INET=m
+CONFIG_NFT_XFRM=m
+CONFIG_NFT_SOCKET=m
+CONFIG_NFT_OSF=m
+CONFIG_NFT_TPROXY=m
+CONFIG_NFT_SYNPROXY=m
+CONFIG_NFT_DUP_NETDEV=m
+CONFIG_NFT_FWD_NETDEV=m
+CONFIG_NFT_FIB_NETDEV=m
+CONFIG_NF_FLOW_TABLE_INET=m
+CONFIG_NF_FLOW_TABLE=m
+CONFIG_NETFILTER_XTABLES_COMPAT=y
+CONFIG_NETFILTER_XT_SET=m
+CONFIG_NETFILTER_XT_TARGET_CHECKSUM=m
+CONFIG_NETFILTER_XT_TARGET_CLASSIFY=m
+CONFIG_NETFILTER_XT_TARGET_CONNMARK=m
+CONFIG_NETFILTER_XT_TARGET_DSCP=m
+CONFIG_NETFILTER_XT_TARGET_HMARK=m
+CONFIG_NETFILTER_XT_TARGET_IDLETIMER=m
+CONFIG_NETFILTER_XT_TARGET_LED=m
+CONFIG_NETFILTER_XT_TARGET_LOG=m
+CONFIG_NETFILTER_XT_TARGET_MARK=m
+CONFIG_NETFILTER_XT_TARGET_NFLOG=m
+CONFIG_NETFILTER_XT_TARGET_NFQUEUE=m
+CONFIG_NETFILTER_XT_TARGET_NOTRACK=m
+CONFIG_NETFILTER_XT_TARGET_TEE=m
+CONFIG_NETFILTER_XT_TARGET_TPROXY=m
+CONFIG_NETFILTER_XT_TARGET_TRACE=m
+CONFIG_NETFILTER_XT_TARGET_TCPMSS=m
+CONFIG_NETFILTER_XT_TARGET_TCPOPTSTRIP=m
+CONFIG_NETFILTER_XT_MATCH_ADDRTYPE=m
+CONFIG_NETFILTER_XT_MATCH_BPF=m
+CONFIG_NETFILTER_XT_MATCH_CLUSTER=m
+CONFIG_NETFILTER_XT_MATCH_COMMENT=m
+CONFIG_NETFILTER_XT_MATCH_CONNBYTES=m
+CONFIG_NETFILTER_XT_MATCH_CONNLABEL=m
+CONFIG_NETFILTER_XT_MATCH_CONNLIMIT=m
+CONFIG_NETFILTER_XT_MATCH_CONNMARK=m
+CONFIG_NETFILTER_XT_MATCH_CONNTRACK=m
+CONFIG_NETFILTER_XT_MATCH_CPU=m
+CONFIG_NETFILTER_XT_MATCH_DCCP=m
+CONFIG_NETFILTER_XT_MATCH_DEVGROUP=m
+CONFIG_NETFILTER_XT_MATCH_DSCP=m
+CONFIG_NETFILTER_XT_MATCH_ESP=m
+CONFIG_NETFILTER_XT_MATCH_HASHLIMIT=m
+CONFIG_NETFILTER_XT_MATCH_HELPER=m
+CONFIG_NETFILTER_XT_MATCH_IPRANGE=m
+CONFIG_NETFILTER_XT_MATCH_IPVS=m
+CONFIG_NETFILTER_XT_MATCH_LENGTH=m
+CONFIG_NETFILTER_XT_MATCH_LIMIT=m
+CONFIG_NETFILTER_XT_MATCH_MAC=m
+CONFIG_NETFILTER_XT_MATCH_MARK=m
+CONFIG_NETFILTER_XT_MATCH_MULTIPORT=m
+CONFIG_NETFILTER_XT_MATCH_NFACCT=m
+CONFIG_NETFILTER_XT_MATCH_OSF=m
+CONFIG_NETFILTER_XT_MATCH_OWNER=m
+CONFIG_NETFILTER_XT_MATCH_POLICY=m
+CONFIG_NETFILTER_XT_MATCH_PHYSDEV=m
+CONFIG_NETFILTER_XT_MATCH_PKTTYPE=m
+CONFIG_NETFILTER_XT_MATCH_QUOTA=m
+CONFIG_NETFILTER_XT_MATCH_RATEEST=m
+CONFIG_NETFILTER_XT_MATCH_REALM=m
+CONFIG_NETFILTER_XT_MATCH_RECENT=m
+CONFIG_NETFILTER_XT_MATCH_SOCKET=m
+CONFIG_NETFILTER_XT_MATCH_STATE=m
+CONFIG_NETFILTER_XT_MATCH_STATISTIC=m
+CONFIG_NETFILTER_XT_MATCH_STRING=m
+CONFIG_NETFILTER_XT_MATCH_TCPMSS=m
+CONFIG_NETFILTER_XT_MATCH_TIME=m
+CONFIG_NETFILTER_XT_MATCH_U32=m
+CONFIG_IP_SET=m
+CONFIG_IP_SET_BITMAP_IP=m
+CONFIG_IP_SET_BITMAP_IPMAC=m
+CONFIG_IP_SET_BITMAP_PORT=m
+CONFIG_IP_SET_HASH_IP=m
+CONFIG_IP_SET_HASH_IPPORT=m
+CONFIG_IP_SET_HASH_IPPORTIP=m
+CONFIG_IP_SET_HASH_IPPORTNET=m
+CONFIG_IP_SET_HASH_NET=m
+CONFIG_IP_SET_HASH_NETPORT=m
+CONFIG_IP_SET_HASH_NETIFACE=m
+CONFIG_IP_SET_LIST_SET=m
+CONFIG_IP_VS=m
+CONFIG_IP_VS_IPV6=y
+CONFIG_IP_VS_PROTO_TCP=y
+CONFIG_IP_VS_PROTO_UDP=y
+CONFIG_IP_VS_PROTO_ESP=y
+CONFIG_IP_VS_PROTO_AH=y
+CONFIG_IP_VS_PROTO_SCTP=y
+CONFIG_IP_VS_RR=m
+CONFIG_IP_VS_WRR=m
+CONFIG_IP_VS_LC=m
+CONFIG_IP_VS_WLC=m
+CONFIG_IP_VS_LBLC=m
+CONFIG_IP_VS_LBLCR=m
+CONFIG_IP_VS_DH=m
+CONFIG_IP_VS_SH=m
+CONFIG_IP_VS_SED=m
+CONFIG_IP_VS_NQ=m
+CONFIG_IP_VS_FTP=m
+CONFIG_IP_VS_PE_SIP=m
+CONFIG_NFT_DUP_IPV4=m
+CONFIG_NFT_FIB_IPV4=m
+CONFIG_NF_TABLES_ARP=y
+CONFIG_NF_LOG_ARP=m
+CONFIG_NF_LOG_IPV4=m
+CONFIG_IP_NF_IPTABLES=m
+CONFIG_IP_NF_MATCH_AH=m
+CONFIG_IP_NF_MATCH_ECN=m
+CONFIG_IP_NF_MATCH_RPFILTER=m
+CONFIG_IP_NF_MATCH_TTL=m
+CONFIG_IP_NF_FILTER=m
+CONFIG_IP_NF_TARGET_REJECT=m
+CONFIG_IP_NF_TARGET_SYNPROXY=m
+CONFIG_IP_NF_NAT=m
+CONFIG_IP_NF_TARGET_MASQUERADE=m
+CONFIG_IP_NF_TARGET_NETMAP=m
+CONFIG_IP_NF_TARGET_REDIRECT=m
+CONFIG_IP_NF_MANGLE=m
+CONFIG_IP_NF_TARGET_ECN=m
+CONFIG_IP_NF_TARGET_TTL=m
+CONFIG_IP_NF_RAW=m
+CONFIG_IP_NF_ARPFILTER=m
+CONFIG_IP_NF_ARP_MANGLE=m
+CONFIG_NFT_DUP_IPV6=m
+CONFIG_NFT_FIB_IPV6=m
+CONFIG_IP6_NF_IPTABLES=m
+CONFIG_IP6_NF_MATCH_AH=m
+CONFIG_IP6_NF_MATCH_EUI64=m
+CONFIG_IP6_NF_MATCH_FRAG=m
+CONFIG_IP6_NF_MATCH_OPTS=m
+CONFIG_IP6_NF_MATCH_HL=m
+CONFIG_IP6_NF_MATCH_IPV6HEADER=m
+CONFIG_IP6_NF_MATCH_MH=m
+CONFIG_IP6_NF_MATCH_RPFILTER=m
+CONFIG_IP6_NF_MATCH_RT=m
+CONFIG_IP6_NF_MATCH_SRH=m
+CONFIG_IP6_NF_TARGET_HL=m
+CONFIG_IP6_NF_FILTER=m
+CONFIG_IP6_NF_TARGET_REJECT=m
+CONFIG_IP6_NF_TARGET_SYNPROXY=m
+CONFIG_IP6_NF_MANGLE=m
+CONFIG_IP6_NF_RAW=m
+CONFIG_IP6_NF_SECURITY=m
+CONFIG_IP6_NF_NAT=m
+CONFIG_IP6_NF_TARGET_MASQUERADE=m
+CONFIG_IP6_NF_TARGET_NPT=m
+CONFIG_NF_TABLES_BRIDGE=m
+CONFIG_NFT_BRIDGE_REJECT=m
+CONFIG_BRIDGE_NF_EBTABLES=m
+CONFIG_BRIDGE_EBT_BROUTE=m
+CONFIG_BRIDGE_EBT_T_FILTER=m
+CONFIG_BRIDGE_EBT_T_NAT=m
+CONFIG_BRIDGE_EBT_802_3=m
+CONFIG_BRIDGE_EBT_AMONG=m
+CONFIG_BRIDGE_EBT_ARP=m
+CONFIG_BRIDGE_EBT_IP=m
+CONFIG_BRIDGE_EBT_IP6=m
+CONFIG_BRIDGE_EBT_LIMIT=m
+CONFIG_BRIDGE_EBT_MARK=m
+CONFIG_BRIDGE_EBT_PKTTYPE=m
+CONFIG_BRIDGE_EBT_STP=m
+CONFIG_BRIDGE_EBT_VLAN=m
+CONFIG_BRIDGE_EBT_ARPREPLY=m
+CONFIG_BRIDGE_EBT_DNAT=m
+CONFIG_BRIDGE_EBT_MARK_T=m
+CONFIG_BRIDGE_EBT_REDIRECT=m
+CONFIG_BRIDGE_EBT_SNAT=m
+CONFIG_BRIDGE_EBT_LOG=m
+CONFIG_BRIDGE_EBT_NFLOG=m
+CONFIG_SCTP_COOKIE_HMAC_SHA1=y
+CONFIG_ATM=m
+CONFIG_L2TP=m
+CONFIG_L2TP_V3=y
+CONFIG_L2TP_IP=m
+CONFIG_L2TP_ETH=m
+CONFIG_BRIDGE=m
+CONFIG_BRIDGE_VLAN_FILTERING=y
+CONFIG_NET_DSA=m
+CONFIG_VLAN_8021Q=m
+CONFIG_VLAN_8021Q_GVRP=y
+CONFIG_ATALK=m
+CONFIG_6LOWPAN=m
+CONFIG_IEEE802154=m
+CONFIG_IEEE802154_6LOWPAN=m
+CONFIG_MAC802154=m
+CONFIG_NET_SCHED=y
+CONFIG_NET_SCH_HTB=m
+CONFIG_NET_SCH_HFSC=m
+CONFIG_NET_SCH_PRIO=m
+CONFIG_NET_SCH_MULTIQ=m
+CONFIG_NET_SCH_RED=m
+CONFIG_NET_SCH_SFB=m
+CONFIG_NET_SCH_SFQ=m
+CONFIG_NET_SCH_TEQL=m
+CONFIG_NET_SCH_TBF=m
+CONFIG_NET_SCH_GRED=m
+CONFIG_NET_SCH_NETEM=m
+CONFIG_NET_SCH_DRR=m
+CONFIG_NET_SCH_MQPRIO=m
+CONFIG_NET_SCH_CHOKE=m
+CONFIG_NET_SCH_QFQ=m
+CONFIG_NET_SCH_CODEL=m
+CONFIG_NET_SCH_FQ_CODEL=m
+CONFIG_NET_SCH_CAKE=m
+CONFIG_NET_SCH_FQ=m
+CONFIG_NET_SCH_HHF=m
+CONFIG_NET_SCH_PIE=m
+CONFIG_NET_SCH_INGRESS=m
+CONFIG_NET_SCH_PLUG=m
+CONFIG_NET_CLS_BASIC=m
+CONFIG_NET_CLS_ROUTE4=m
+CONFIG_NET_CLS_FW=m
+CONFIG_NET_CLS_U32=m
+CONFIG_CLS_U32_PERF=y
+CONFIG_CLS_U32_MARK=y
+CONFIG_NET_CLS_FLOW=m
+CONFIG_NET_CLS_CGROUP=m
+CONFIG_NET_CLS_BPF=y
+CONFIG_NET_CLS_FLOWER=m
+CONFIG_NET_CLS_MATCHALL=m
+CONFIG_NET_EMATCH=y
+CONFIG_NET_EMATCH_CMP=m
+CONFIG_NET_EMATCH_NBYTE=m
+CONFIG_NET_EMATCH_U32=m
+CONFIG_NET_EMATCH_META=m
+CONFIG_NET_EMATCH_TEXT=m
+CONFIG_NET_EMATCH_IPSET=m
+CONFIG_NET_CLS_ACT=y
+CONFIG_NET_ACT_POLICE=m
+CONFIG_NET_ACT_GACT=m
+CONFIG_GACT_PROB=y
+CONFIG_NET_ACT_MIRRED=m
+CONFIG_NET_ACT_NAT=m
+CONFIG_NET_ACT_PEDIT=m
+CONFIG_NET_ACT_SIMP=m
+CONFIG_NET_ACT_SKBEDIT=m
+CONFIG_NET_ACT_CSUM=m
+CONFIG_BATMAN_ADV=m
+CONFIG_OPENVSWITCH=m
+CONFIG_VSOCKETS=m
+CONFIG_CGROUP_NET_PRIO=y
+CONFIG_NET_PKTGEN=m
+CONFIG_HAMRADIO=y
+CONFIG_AX25=m
+CONFIG_NETROM=m
+CONFIG_ROSE=m
+CONFIG_MKISS=m
+CONFIG_6PACK=m
+CONFIG_BPQETHER=m
+CONFIG_BAYCOM_SER_FDX=m
+CONFIG_BAYCOM_SER_HDX=m
+CONFIG_YAM=m
+CONFIG_CAN=m
+CONFIG_CAN_J1939=m
+CONFIG_CAN_ISOTP=m
+CONFIG_BT=m
+CONFIG_BT_RFCOMM=m
+CONFIG_BT_RFCOMM_TTY=y
+CONFIG_BT_BNEP=m
+CONFIG_BT_BNEP_MC_FILTER=y
+CONFIG_BT_BNEP_PROTO_FILTER=y
+CONFIG_BT_HIDP=m
+CONFIG_BT_6LOWPAN=m
+CONFIG_BT_HCIBTUSB=m
+CONFIG_BT_HCIBTUSB_MTK=y
+CONFIG_BT_HCIUART=m
+CONFIG_BT_HCIUART_3WIRE=y
+CONFIG_BT_HCIUART_BCM=y
+CONFIG_BT_HCIBCM203X=m
+CONFIG_BT_HCIBPA10X=m
+CONFIG_BT_HCIBFUSB=m
+CONFIG_BT_HCIVHCI=m
+CONFIG_BT_MRVL=m
+CONFIG_BT_MRVL_SDIO=m
+CONFIG_BT_ATH3K=m
+CONFIG_CFG80211=m
+CONFIG_CFG80211_WEXT=y
+CONFIG_MAC80211=m
+CONFIG_MAC80211_MESH=y
+CONFIG_RFKILL=m
+CONFIG_RFKILL_INPUT=y
+CONFIG_NET_9P=m
+CONFIG_NFC=m
+CONFIG_PCI=y
+CONFIG_PCIEPORTBUS=y
+CONFIG_PCIEAER=y
+CONFIG_PCIEASPM_POWERSAVE=y
+CONFIG_PCIE_DPC=y
+CONFIG_UEVENT_HELPER=y
+CONFIG_DEVTMPFS=y
+CONFIG_DEVTMPFS_MOUNT=y
+# CONFIG_BRCMSTB_GISB_ARB is not set
+CONFIG_RASPBERRYPI_FIRMWARE=y
+CONFIG_FIRMWARE_RP1=m
+# CONFIG_EFI_VARS_PSTORE is not set
+CONFIG_MTD=m
+CONFIG_MTD_BLOCK=m
+CONFIG_MTD_BLOCK2MTD=m
+CONFIG_MTD_SPI_NAND=m
+CONFIG_MTD_SPI_NOR=m
+CONFIG_MTD_UBI=m
+CONFIG_OF_CONFIGFS=y
+CONFIG_ZRAM=m
+CONFIG_ZRAM_BACKEND_LZ4=y
+CONFIG_ZRAM_BACKEND_ZSTD=y
+CONFIG_ZRAM_BACKEND_LZO=y
+CONFIG_ZRAM_DEF_COMP_ZSTD=y
+CONFIG_ZRAM_WRITEBACK=y
+CONFIG_ZRAM_MULTI_COMP=y
+CONFIG_BLK_DEV_LOOP=y
+CONFIG_BLK_DEV_DRBD=m
+CONFIG_BLK_DEV_NBD=m
+CONFIG_BLK_DEV_RAM=y
+CONFIG_ATA_OVER_ETH=m
+CONFIG_BLK_DEV_RBD=m
+CONFIG_BLK_DEV_NVME=y
+CONFIG_NVME_HWMON=y
+CONFIG_RP1_PIO=m
+CONFIG_WS2812_PIO_RP1=m
+CONFIG_SRAM=y
+CONFIG_EEPROM_AT24=m
+CONFIG_EEPROM_AT25=m
+CONFIG_TI_ST=m
+CONFIG_SCSI=y
+# CONFIG_SCSI_PROC_FS is not set
+CONFIG_BLK_DEV_SD=y
+CONFIG_CHR_DEV_ST=m
+CONFIG_BLK_DEV_SR=m
+CONFIG_CHR_DEV_SG=m
+CONFIG_SCSI_ISCSI_ATTRS=y
+CONFIG_ISCSI_TCP=m
+CONFIG_ISCSI_BOOT_SYSFS=m
+CONFIG_ATA=m
+CONFIG_SATA_AHCI=m
+CONFIG_SATA_MV=m
+CONFIG_MD=y
+CONFIG_BCACHE=m
+CONFIG_BLK_DEV_DM=m
+CONFIG_DM_CRYPT=m
+CONFIG_DM_SNAPSHOT=m
+CONFIG_DM_THIN_PROVISIONING=m
+CONFIG_DM_CACHE=m
+CONFIG_DM_WRITECACHE=m
+CONFIG_DM_MIRROR=m
+CONFIG_DM_LOG_USERSPACE=m
+CONFIG_DM_RAID=m
+CONFIG_DM_ZERO=m
+CONFIG_DM_MULTIPATH=m
+CONFIG_DM_DELAY=m
+CONFIG_DM_VERITY=m
+CONFIG_DM_INTEGRITY=m
+CONFIG_NETDEVICES=y
+CONFIG_BONDING=m
+CONFIG_DUMMY=m
+CONFIG_WIREGUARD=m
+CONFIG_IFB=m
+CONFIG_MACVLAN=m
+CONFIG_MACVTAP=m
+CONFIG_IPVLAN=m
+CONFIG_VXLAN=m
+CONFIG_NETCONSOLE=m
+CONFIG_TUN=m
+CONFIG_VETH=m
+CONFIG_NETKIT=y
+CONFIG_NET_VRF=m
+CONFIG_VSOCKMON=m
+CONFIG_BCMGENET=y
+CONFIG_MACB=y
+CONFIG_IGB=m
+CONFIG_IXGBE=m
+CONFIG_I40E=m
+CONFIG_IGC=m
+CONFIG_ENC28J60=m
+CONFIG_LAN743X=m
+CONFIG_QCA7000_SPI=m
+CONFIG_QCA7000_UART=m
+CONFIG_R8169=m
+CONFIG_MSE102X=m
+CONFIG_WIZNET_W5100=m
+CONFIG_WIZNET_W5100_SPI=m
+CONFIG_MICREL_PHY=y
+CONFIG_CAN_VCAN=m
+CONFIG_CAN_SLCAN=m
+CONFIG_CAN_MCP251X=m
+CONFIG_CAN_MCP251XFD=m
+CONFIG_CAN_8DEV_USB=m
+CONFIG_CAN_EMS_USB=m
+CONFIG_CAN_GS_USB=m
+CONFIG_CAN_PEAK_USB=m
+CONFIG_MDIO_BITBANG=m
+CONFIG_PPP=m
+CONFIG_PPP_BSDCOMP=m
+CONFIG_PPP_DEFLATE=m
+CONFIG_PPP_FILTER=y
+CONFIG_PPP_MPPE=m
+CONFIG_PPP_MULTILINK=y
+CONFIG_PPPOATM=m
+CONFIG_PPPOE=m
+CONFIG_PPPOL2TP=m
+CONFIG_PPP_ASYNC=m
+CONFIG_PPP_SYNC_TTY=m
+CONFIG_SLIP=m
+CONFIG_SLIP_COMPRESSED=y
+CONFIG_SLIP_SMART=y
+CONFIG_USB_CATC=m
+CONFIG_USB_KAWETH=m
+CONFIG_USB_PEGASUS=m
+CONFIG_USB_RTL8150=m
+CONFIG_USB_RTL8152=m
+CONFIG_USB_LAN78XX=y
+CONFIG_USB_USBNET=y
+CONFIG_USB_NET_AX8817X=m
+CONFIG_USB_NET_AX88179_178A=m
+CONFIG_USB_NET_CDCETHER=m
+CONFIG_USB_NET_CDC_EEM=m
+CONFIG_USB_NET_CDC_NCM=m
+CONFIG_USB_NET_HUAWEI_CDC_NCM=m
+CONFIG_USB_NET_CDC_MBIM=m
+CONFIG_USB_NET_DM9601=m
+CONFIG_USB_NET_SR9700=m
+CONFIG_USB_NET_SR9800=m
+CONFIG_USB_NET_SMSC75XX=m
+CONFIG_USB_NET_SMSC95XX=y
+CONFIG_USB_NET_GL620A=m
+CONFIG_USB_NET_NET1080=m
+CONFIG_USB_NET_PLUSB=m
+CONFIG_USB_NET_MCS7830=m
+CONFIG_USB_NET_RNDIS_HOST=m
+CONFIG_USB_NET_CDC_SUBSET=m
+CONFIG_USB_ALI_M5632=y
+CONFIG_USB_AN2720=y
+CONFIG_USB_EPSON2888=y
+CONFIG_USB_KC2190=y
+CONFIG_USB_NET_ZAURUS=m
+CONFIG_USB_NET_CX82310_ETH=m
+CONFIG_USB_NET_KALMIA=m
+CONFIG_USB_NET_QMI_WWAN=m
+CONFIG_USB_HSO=m
+CONFIG_USB_NET_INT51X1=m
+CONFIG_USB_IPHETH=m
+CONFIG_USB_SIERRA_NET=m
+CONFIG_USB_VL600=m
+CONFIG_USB_NET_AQC111=m
+CONFIG_ATH9K=m
+CONFIG_ATH9K_HTC=m
+CONFIG_CARL9170=m
+CONFIG_ATH6KL=m
+CONFIG_ATH6KL_USB=m
+CONFIG_AR5523=m
+CONFIG_AT76C50X_USB=m
+CONFIG_B43=m
+# CONFIG_B43_PHY_N is not set
+CONFIG_B43LEGACY=m
+CONFIG_BRCMFMAC=m
+CONFIG_BRCMFMAC_USB=y
+CONFIG_BRCMDBG=y
+CONFIG_IWLWIFI=m
+CONFIG_IWLDVM=m
+CONFIG_IWLMVM=m
+CONFIG_P54_COMMON=m
+CONFIG_P54_USB=m
+CONFIG_LIBERTAS=m
+CONFIG_LIBERTAS_USB=m
+CONFIG_LIBERTAS_SDIO=m
+CONFIG_LIBERTAS_THINFIRM=m
+CONFIG_LIBERTAS_THINFIRM_USB=m
+CONFIG_MWIFIEX=m
+CONFIG_MWIFIEX_SDIO=m
+CONFIG_MT7601U=m
+CONFIG_MT76x0U=m
+CONFIG_MT76x2U=m
+CONFIG_MT7921U=m
+CONFIG_MT7925E=m
+CONFIG_MT7925U=m
+CONFIG_RT2X00=m
+CONFIG_RT2500USB=m
+CONFIG_RT73USB=m
+CONFIG_RT2800USB=m
+CONFIG_RT2800USB_RT3573=y
+CONFIG_RT2800USB_RT53XX=y
+CONFIG_RT2800USB_RT55XX=y
+CONFIG_RT2800USB_UNKNOWN=y
+CONFIG_RTL8187=m
+CONFIG_RTL8192CU=m
+CONFIG_RTL8XXXU=m
+CONFIG_RTW88=m
+CONFIG_RTW88_8822BE=m
+CONFIG_RTW88_8822BU=m
+CONFIG_RTW88_8822CE=m
+CONFIG_RTW88_8822CU=m
+CONFIG_RTW88_8723DE=m
+CONFIG_RTW88_8723DU=m
+CONFIG_RTW88_8821CE=m
+CONFIG_RTW88_8821CU=m
+CONFIG_RTW89=m
+CONFIG_RTW89_8851BE=m
+CONFIG_RTW89_8852AE=m
+CONFIG_RTW89_8852BE=m
+CONFIG_RTW89_8852BTE=m
+CONFIG_RTW89_8852CE=m
+CONFIG_RTW89_8922AE=m
+CONFIG_ZD1211RW=m
+CONFIG_MAC80211_HWSIM=m
+CONFIG_IEEE802154_AT86RF230=m
+CONFIG_IEEE802154_MRF24J40=m
+CONFIG_IEEE802154_CC2520=m
+CONFIG_INPUT_MOUSEDEV=y
+CONFIG_INPUT_JOYDEV=m
+CONFIG_INPUT_EVDEV=y
+# CONFIG_KEYBOARD_ATKBD is not set
+CONFIG_KEYBOARD_GPIO=m
+CONFIG_KEYBOARD_TCA6416=m
+CONFIG_KEYBOARD_TCA8418=m
+CONFIG_KEYBOARD_MATRIX=m
+CONFIG_KEYBOARD_CAP11XX=m
+# CONFIG_INPUT_MOUSE is not set
+CONFIG_INPUT_JOYSTICK=y
+CONFIG_JOYSTICK_IFORCE=m
+CONFIG_JOYSTICK_IFORCE_USB=m
+CONFIG_JOYSTICK_XPAD=m
+CONFIG_JOYSTICK_XPAD_FF=y
+CONFIG_JOYSTICK_XPAD_LEDS=y
+CONFIG_JOYSTICK_PSXPAD_SPI=m
+CONFIG_JOYSTICK_PSXPAD_SPI_FF=y
+CONFIG_JOYSTICK_FSIA6B=m
+CONFIG_JOYSTICK_SENSEHAT=m
+CONFIG_INPUT_TOUCHSCREEN=y
+CONFIG_TOUCHSCREEN_ADS7846=m
+CONFIG_TOUCHSCREEN_ATMEL_MXT=m
+CONFIG_TOUCHSCREEN_EGALAX=m
+CONFIG_TOUCHSCREEN_EXC3000=m
+CONFIG_TOUCHSCREEN_GOODIX=m
+CONFIG_TOUCHSCREEN_ILI210X=m
+CONFIG_TOUCHSCREEN_ILITEK=m
+CONFIG_TOUCHSCREEN_ILITEK_V3=m
+CONFIG_TOUCHSCREEN_EDT_FT5X06=m
+CONFIG_TOUCHSCREEN_RASPBERRYPI_FW=m
+CONFIG_TOUCHSCREEN_USB_COMPOSITE=m
+CONFIG_TOUCHSCREEN_TSC2007=m
+CONFIG_TOUCHSCREEN_TSC2007_IIO=y
+CONFIG_TOUCHSCREEN_STMPE=m
+CONFIG_TOUCHSCREEN_IQS5XX=m
+CONFIG_INPUT_MISC=y
+CONFIG_INPUT_AD714X=m
+CONFIG_INPUT_ATI_REMOTE2=m
+CONFIG_INPUT_KEYSPAN_REMOTE=m
+CONFIG_INPUT_POWERMATE=m
+CONFIG_INPUT_YEALINK=m
+CONFIG_INPUT_CM109=m
+CONFIG_INPUT_UINPUT=m
+CONFIG_INPUT_PWM_BEEPER=m
+CONFIG_INPUT_GPIO_ROTARY_ENCODER=m
+CONFIG_INPUT_ADXL34X=m
+CONFIG_INPUT_CMA3000=m
+CONFIG_SERIO=m
+CONFIG_SERIO_RAW=m
+CONFIG_GAMEPORT=m
+CONFIG_BRCM_CHAR_DRIVERS=y
+CONFIG_BCM_VCIO=y
+# CONFIG_LEGACY_PTYS is not set
+CONFIG_SERIAL_8250=y
+# CONFIG_SERIAL_8250_DEPRECATED_OPTIONS is not set
+CONFIG_SERIAL_8250_CONSOLE=y
+# CONFIG_SERIAL_8250_DMA is not set
+CONFIG_SERIAL_8250_NR_UARTS=5
+CONFIG_SERIAL_8250_RUNTIME_UARTS=0
+CONFIG_SERIAL_8250_EXTENDED=y
+CONFIG_SERIAL_8250_SHARE_IRQ=y
+CONFIG_SERIAL_8250_BCM2835AUX=y
+CONFIG_SERIAL_OF_PLATFORM=y
+CONFIG_SERIAL_AMBA_PL011=y
+CONFIG_SERIAL_AMBA_PL011_CONSOLE=y
+CONFIG_SERIAL_SC16IS7XX=m
+CONFIG_SERIAL_DEV_BUS=y
+CONFIG_TTY_PRINTK=y
+CONFIG_HW_RANDOM=y
+CONFIG_TCG_TPM=m
+CONFIG_TCG_TIS_SPI=m
+CONFIG_TCG_TIS_I2C=m
+CONFIG_XILLYBUS=m
+CONFIG_XILLYBUS_PCIE=m
+CONFIG_XILLYUSB=m
+CONFIG_RASPBERRYPI_GPIOMEM=m
+CONFIG_I2C=y
+CONFIG_I2C_CHARDEV=m
+CONFIG_I2C_MUX_GPMUX=m
+CONFIG_I2C_MUX_PCA954x=m
+CONFIG_I2C_MUX_PINCTRL=m
+CONFIG_I2C_BCM2708=m
+CONFIG_I2C_BCM2835=m
+CONFIG_I2C_BRCMSTB=m
+CONFIG_I2C_DESIGNWARE_CORE=m
+CONFIG_I2C_GPIO=m
+CONFIG_I2C_ROBOTFUZZ_OSIF=m
+CONFIG_I2C_TINY_USB=m
+CONFIG_SPI=y
+CONFIG_SPI_BCM2835=m
+CONFIG_SPI_BCM2835AUX=m
+CONFIG_SPI_DESIGNWARE=m
+CONFIG_SPI_DW_DMA=y
+CONFIG_SPI_DW_MMIO=m
+CONFIG_SPI_GPIO=m
+CONFIG_SPI_RP2040_GPIO_BRIDGE=m
+CONFIG_SPI_SPIDEV=m
+CONFIG_SPI_SLAVE=y
+CONFIG_PPS_CLIENT_LDISC=m
+CONFIG_PPS_CLIENT_GPIO=m
+CONFIG_PINCTRL_MCP23S08=m
+CONFIG_PINCTRL_RP1=y
+CONFIG_PINCTRL_BCM2712=y
+CONFIG_GPIO_SYSFS=y
+CONFIG_GPIO_BCM_VIRT=y
+CONFIG_GPIO_FXL6408=m
+CONFIG_GPIO_MAX7300=m
+CONFIG_GPIO_PCA953X=m
+CONFIG_GPIO_PCA953X_IRQ=y
+CONFIG_GPIO_PCF857X=m
+CONFIG_GPIO_ARIZONA=m
+CONFIG_GPIO_FSM=m
+CONFIG_GPIO_STMPE=y
+CONFIG_GPIO_MAX7301=m
+CONFIG_GPIO_MOCKUP=m
+CONFIG_W1=m
+CONFIG_W1_MASTER_DS2490=m
+CONFIG_W1_MASTER_DS2482=m
+CONFIG_W1_MASTER_GPIO=m
+CONFIG_W1_SLAVE_THERM=m
+CONFIG_W1_SLAVE_SMEM=m
+CONFIG_W1_SLAVE_DS2408=m
+CONFIG_W1_SLAVE_DS2413=m
+CONFIG_W1_SLAVE_DS2406=m
+CONFIG_W1_SLAVE_DS2423=m
+CONFIG_W1_SLAVE_DS2430=m
+CONFIG_W1_SLAVE_DS2431=m
+CONFIG_W1_SLAVE_DS2433=m
+CONFIG_W1_SLAVE_DS2438=m
+CONFIG_W1_SLAVE_DS2780=m
+CONFIG_W1_SLAVE_DS2781=m
+CONFIG_W1_SLAVE_DS28E04=m
+CONFIG_W1_SLAVE_DS28E17=m
+# CONFIG_POWER_RESET_BRCMSTB is not set
+CONFIG_POWER_RESET_GPIO=y
+CONFIG_RPI_POE_POWER=m
+CONFIG_BATTERY_DS2760=m
+CONFIG_BATTERY_MAX17040=m
+CONFIG_CHARGER_GPIO=m
+CONFIG_BATTERY_GAUGE_LTC2941=m
+CONFIG_SENSORS_ADT7410=m
+CONFIG_SENSORS_AHT10=m
+CONFIG_SENSORS_CHIPCAP2=m
+CONFIG_SENSORS_DRIVETEMP=m
+CONFIG_SENSORS_DS1621=m
+CONFIG_SENSORS_GPIO_FAN=m
+CONFIG_SENSORS_IIO_HWMON=m
+CONFIG_SENSORS_JC42=m
+CONFIG_SENSORS_POWERZ=m
+CONFIG_SENSORS_LM75=m
+CONFIG_SENSORS_PWM_FAN=m
+CONFIG_SENSORS_RASPBERRYPI_HWMON=m
+CONFIG_SENSORS_SHT21=m
+CONFIG_SENSORS_SHT3x=m
+CONFIG_SENSORS_SHT4x=m
+CONFIG_SENSORS_SHTC1=m
+CONFIG_SENSORS_EMC2305=m
+CONFIG_SENSORS_ADS7828=m
+CONFIG_SENSORS_INA2XX=m
+CONFIG_SENSORS_INA238=m
+CONFIG_SENSORS_TMP102=m
+CONFIG_SENSORS_RP1_ADC=m
+CONFIG_BCM2711_THERMAL=y
+CONFIG_BCM2835_THERMAL=y
+CONFIG_WATCHDOG=y
+CONFIG_WATCHDOG_SYSFS=y
+CONFIG_GPIO_WATCHDOG=m
+CONFIG_BCM2835_WDT=y
+CONFIG_MFD_RASPBERRYPI_POE_HAT=m
+CONFIG_MFD_STMPE=y
+CONFIG_STMPE_SPI=y
+CONFIG_MFD_SYSCON=y
+CONFIG_MFD_ARIZONA_I2C=m
+CONFIG_MFD_ARIZONA_SPI=m
+CONFIG_MFD_WM5102=y
+CONFIG_MFD_RP1=y
+CONFIG_REGULATOR=y
+CONFIG_REGULATOR_FIXED_VOLTAGE=y
+CONFIG_REGULATOR_ARIZONA_LDO1=m
+CONFIG_REGULATOR_ARIZONA_MICSUPP=m
+CONFIG_REGULATOR_GPIO=y
+CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY=m
+CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_V2=m
+CONFIG_REGULATOR_WAVESHARE_TOUCHSCREEN=m
+CONFIG_RC_CORE=y
+CONFIG_BPF_LIRC_MODE2=y
+CONFIG_LIRC=y
+CONFIG_RC_DECODERS=y
+CONFIG_IR_IMON_DECODER=m
+CONFIG_IR_JVC_DECODER=m
+CONFIG_IR_MCE_KBD_DECODER=m
+CONFIG_IR_NEC_DECODER=m
+CONFIG_IR_RC5_DECODER=m
+CONFIG_IR_RC6_DECODER=m
+CONFIG_IR_SANYO_DECODER=m
+CONFIG_IR_SHARP_DECODER=m
+CONFIG_IR_SONY_DECODER=m
+CONFIG_IR_XMP_DECODER=m
+CONFIG_RC_DEVICES=y
+CONFIG_IR_GPIO_CIR=m
+CONFIG_IR_GPIO_TX=m
+CONFIG_IR_IGUANA=m
+CONFIG_IR_IMON=m
+CONFIG_IR_MCEUSB=m
+CONFIG_IR_PWM_TX=m
+CONFIG_IR_REDRAT3=m
+CONFIG_IR_STREAMZAP=m
+CONFIG_IR_TOY=m
+CONFIG_IR_TTUSBIR=m
+CONFIG_RC_ATI_REMOTE=m
+CONFIG_RC_LOOPBACK=m
+CONFIG_MEDIA_CEC_RC=y
+CONFIG_MEDIA_SUPPORT=m
+CONFIG_MEDIA_USB_SUPPORT=y
+CONFIG_USB_GSPCA=m
+CONFIG_USB_GSPCA_BENQ=m
+CONFIG_USB_GSPCA_CONEX=m
+CONFIG_USB_GSPCA_CPIA1=m
+CONFIG_USB_GSPCA_DTCS033=m
+CONFIG_USB_GSPCA_ETOMS=m
+CONFIG_USB_GSPCA_FINEPIX=m
+CONFIG_USB_GSPCA_JEILINJ=m
+CONFIG_USB_GSPCA_JL2005BCD=m
+CONFIG_USB_GSPCA_KINECT=m
+CONFIG_USB_GSPCA_KONICA=m
+CONFIG_USB_GSPCA_MARS=m
+CONFIG_USB_GSPCA_MR97310A=m
+CONFIG_USB_GSPCA_NW80X=m
+CONFIG_USB_GSPCA_OV519=m
+CONFIG_USB_GSPCA_OV534=m
+CONFIG_USB_GSPCA_OV534_9=m
+CONFIG_USB_GSPCA_PAC207=m
+CONFIG_USB_GSPCA_PAC7302=m
+CONFIG_USB_GSPCA_PAC7311=m
+CONFIG_USB_GSPCA_SE401=m
+CONFIG_USB_GSPCA_SN9C2028=m
+CONFIG_USB_GSPCA_SN9C20X=m
+CONFIG_USB_GSPCA_SONIXB=m
+CONFIG_USB_GSPCA_SONIXJ=m
+CONFIG_USB_GSPCA_SPCA1528=m
+CONFIG_USB_GSPCA_SPCA500=m
+CONFIG_USB_GSPCA_SPCA501=m
+CONFIG_USB_GSPCA_SPCA505=m
+CONFIG_USB_GSPCA_SPCA506=m
+CONFIG_USB_GSPCA_SPCA508=m
+CONFIG_USB_GSPCA_SPCA561=m
+CONFIG_USB_GSPCA_SQ905=m
+CONFIG_USB_GSPCA_SQ905C=m
+CONFIG_USB_GSPCA_SQ930X=m
+CONFIG_USB_GSPCA_STK014=m
+CONFIG_USB_GSPCA_STK1135=m
+CONFIG_USB_GSPCA_STV0680=m
+CONFIG_USB_GSPCA_SUNPLUS=m
+CONFIG_USB_GSPCA_T613=m
+CONFIG_USB_GSPCA_TOPRO=m
+CONFIG_USB_GSPCA_TOUPTEK=m
+CONFIG_USB_GSPCA_TV8532=m
+CONFIG_USB_GSPCA_VC032X=m
+CONFIG_USB_GSPCA_VICAM=m
+CONFIG_USB_GSPCA_XIRLINK_CIT=m
+CONFIG_USB_GSPCA_ZC3XX=m
+CONFIG_USB_GL860=m
+CONFIG_USB_M5602=m
+CONFIG_USB_STV06XX=m
+CONFIG_USB_PWC=m
+CONFIG_USB_S2255=m
+CONFIG_VIDEO_USBTV=m
+CONFIG_USB_VIDEO_CLASS=m
+CONFIG_VIDEO_GO7007=m
+CONFIG_VIDEO_GO7007_USB=m
+CONFIG_VIDEO_GO7007_USB_S2250_BOARD=m
+CONFIG_VIDEO_HDPVR=m
+CONFIG_VIDEO_PVRUSB2=m
+CONFIG_VIDEO_STK1160=m
+CONFIG_VIDEO_AU0828=m
+CONFIG_VIDEO_AU0828_RC=y
+CONFIG_VIDEO_CX231XX=m
+CONFIG_VIDEO_CX231XX_ALSA=m
+CONFIG_VIDEO_CX231XX_DVB=m
+CONFIG_DVB_AS102=m
+CONFIG_DVB_B2C2_FLEXCOP_USB=m
+CONFIG_DVB_USB_V2=m
+CONFIG_DVB_USB_AF9015=m
+CONFIG_DVB_USB_AF9035=m
+CONFIG_DVB_USB_ANYSEE=m
+CONFIG_DVB_USB_AU6610=m
+CONFIG_DVB_USB_AZ6007=m
+CONFIG_DVB_USB_CE6230=m
+CONFIG_DVB_USB_DVBSKY=m
+CONFIG_DVB_USB_EC168=m
+CONFIG_DVB_USB_GL861=m
+CONFIG_DVB_USB_LME2510=m
+CONFIG_DVB_USB_MXL111SF=m
+CONFIG_DVB_USB_RTL28XXU=m
+CONFIG_DVB_USB=m
+CONFIG_DVB_USB_A800=m
+CONFIG_DVB_USB_AF9005=m
+CONFIG_DVB_USB_AF9005_REMOTE=m
+CONFIG_DVB_USB_AZ6027=m
+CONFIG_DVB_USB_CINERGY_T2=m
+CONFIG_DVB_USB_CXUSB=m
+CONFIG_DVB_USB_DIB0700=m
+CONFIG_DVB_USB_DIBUSB_MB=m
+CONFIG_DVB_USB_DIBUSB_MB_FAULTY=y
+CONFIG_DVB_USB_DIBUSB_MC=m
+CONFIG_DVB_USB_DIGITV=m
+CONFIG_DVB_USB_DTT200U=m
+CONFIG_DVB_USB_DTV5100=m
+CONFIG_DVB_USB_DW2102=m
+CONFIG_DVB_USB_GP8PSK=m
+CONFIG_DVB_USB_M920X=m
+CONFIG_DVB_USB_NOVA_T_USB2=m
+CONFIG_DVB_USB_OPERA1=m
+CONFIG_DVB_USB_PCTV452E=m
+CONFIG_DVB_USB_TECHNISAT_USB2=m
+CONFIG_DVB_USB_TTUSB2=m
+CONFIG_DVB_USB_UMT_010=m
+CONFIG_DVB_USB_VP702X=m
+CONFIG_DVB_USB_VP7045=m
+CONFIG_SMS_USB_DRV=m
+CONFIG_VIDEO_EM28XX=m
+CONFIG_VIDEO_EM28XX_V4L2=m
+CONFIG_VIDEO_EM28XX_ALSA=m
+CONFIG_VIDEO_EM28XX_DVB=m
+CONFIG_MEDIA_PCI_SUPPORT=y
+CONFIG_RADIO_SAA7706H=m
+CONFIG_RADIO_SHARK=m
+CONFIG_RADIO_SHARK2=m
+CONFIG_RADIO_SI4713=m
+CONFIG_RADIO_TEA5764=m
+CONFIG_RADIO_TEF6862=m
+CONFIG_RADIO_WL1273=m
+CONFIG_USB_DSBR=m
+CONFIG_USB_KEENE=m
+CONFIG_USB_MA901=m
+CONFIG_USB_MR800=m
+CONFIG_RADIO_SI470X=m
+CONFIG_USB_SI470X=m
+CONFIG_I2C_SI470X=m
+CONFIG_I2C_SI4713=m
+CONFIG_RADIO_WL128X=m
+CONFIG_V4L_PLATFORM_DRIVERS=y
+CONFIG_VIDEO_MUX=m
+CONFIG_VIDEO_BCM2835_UNICAM_LEGACY=m
+CONFIG_VIDEO_BCM2835_UNICAM=m
+CONFIG_VIDEO_RPI_HEVC_DEC=m
+CONFIG_VIDEO_RASPBERRYPI_PISP_BE=m
+CONFIG_VIDEO_RP1_CFE=m
+CONFIG_V4L_TEST_DRIVERS=y
+CONFIG_VIDEO_VIM2M=m
+CONFIG_VIDEO_VICODEC=m
+CONFIG_VIDEO_VIMC=m
+CONFIG_VIDEO_VIVID=m
+CONFIG_VIDEO_ARDUCAM_64MP=m
+CONFIG_VIDEO_ARDUCAM_PIVARIETY=m
+CONFIG_VIDEO_IMX219=m
+CONFIG_VIDEO_IMX258=m
+CONFIG_VIDEO_IMX283=m
+CONFIG_VIDEO_IMX290=m
+CONFIG_VIDEO_IMX296=m
+CONFIG_VIDEO_IMX335=m
+CONFIG_VIDEO_IMX415=m
+CONFIG_VIDEO_IMX477=m
+CONFIG_VIDEO_IMX500=m
+CONFIG_VIDEO_IMX519=m
+CONFIG_VIDEO_IMX708=m
+CONFIG_VIDEO_MIRA220=m
+CONFIG_VIDEO_MT9V011=m
+CONFIG_VIDEO_OV2311=m
+CONFIG_VIDEO_OV5647=m
+CONFIG_VIDEO_OV64A40=m
+CONFIG_VIDEO_OV7251=m
+CONFIG_VIDEO_OV7640=m
+CONFIG_VIDEO_OV9282=m
+CONFIG_VIDEO_AD5398=m
+CONFIG_VIDEO_AK7375=m
+CONFIG_VIDEO_BU64754=m
+CONFIG_VIDEO_DW9807_VCM=m
+CONFIG_VIDEO_SONY_BTF_MPX=m
+CONFIG_VIDEO_UDA1342=m
+CONFIG_VIDEO_ADV7180=m
+CONFIG_VIDEO_TC358743=m
+CONFIG_VIDEO_TVP5150=m
+CONFIG_VIDEO_TW2804=m
+CONFIG_VIDEO_TW9903=m
+CONFIG_VIDEO_TW9906=m
+CONFIG_VIDEO_IRS1125=m
+CONFIG_VIDEO_I2C=m
+CONFIG_AUXDISPLAY=y
+CONFIG_HD44780=m
+CONFIG_DRM=m
+CONFIG_DRM_LOAD_EDID_FIRMWARE=y
+CONFIG_DRM_UDL=m
+CONFIG_DRM_PANEL_LVDS=m
+CONFIG_DRM_PANEL_ILITEK_IL79600A=m
+CONFIG_DRM_PANEL_ILITEK_ILI9806E=m
+CONFIG_DRM_PANEL_ILITEK_ILI9806E_SPI=m
+CONFIG_DRM_PANEL_ILITEK_ILI9881C=m
+CONFIG_DRM_PANEL_JDI_LT070ME05000=m
+CONFIG_DRM_PANEL_RASPBERRYPI_TOUCHSCREEN=m
+CONFIG_DRM_PANEL_SITRONIX_ST7701=m
+CONFIG_DRM_PANEL_SIMPLE=m
+CONFIG_DRM_PANEL_TPO_Y17P=m
+CONFIG_DRM_PANEL_WAVESHARE_TOUCHSCREEN=m
+CONFIG_DRM_PANEL_WAVESHARE_TOUCHSCREEN_V2=m
+CONFIG_DRM_DISPLAY_CONNECTOR=m
+CONFIG_DRM_SIMPLE_BRIDGE=m
+CONFIG_DRM_TOSHIBA_TC358762=m
+CONFIG_DRM_TI_SN65DSI83=m
+CONFIG_DRM_V3D=m
+CONFIG_DRM_VC4=m
+CONFIG_DRM_VC4_HDMI_CEC=y
+CONFIG_DRM_RP1_DSI=m
+CONFIG_DRM_RP1_DPI=m
+CONFIG_DRM_RP1_VEC=m
+CONFIG_DRM_PANEL_MIPI_DBI=m
+CONFIG_TINYDRM_HX8357D=m
+CONFIG_TINYDRM_ILI9225=m
+CONFIG_TINYDRM_ILI9341=m
+CONFIG_TINYDRM_ILI9486=m
+CONFIG_TINYDRM_MI0283QT=m
+CONFIG_TINYDRM_REPAPER=m
+CONFIG_TINYDRM_ST7586=m
+CONFIG_TINYDRM_ST7735R=m
+CONFIG_DRM_GUD=m
+CONFIG_DRM_SSD130X=m
+CONFIG_DRM_SSD130X_I2C=m
+CONFIG_DRM_SSD130X_SPI=m
+CONFIG_FB=y
+CONFIG_FB_BCM2708=y
+CONFIG_FB_SIMPLE=y
+CONFIG_FB_SSD1307=m
+CONFIG_FB_RPISENSE=m
+CONFIG_BACKLIGHT_PWM=m
+CONFIG_BACKLIGHT_RPI=m
+CONFIG_BACKLIGHT_LM3630A=m
+CONFIG_BACKLIGHT_GPIO=m
+CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y
+CONFIG_LOGO=y
+# CONFIG_LOGO_LINUX_MONO is not set
+# CONFIG_LOGO_LINUX_VGA16 is not set
+CONFIG_SOUND=y
+CONFIG_SND=m
+CONFIG_SND_OSSEMUL=y
+CONFIG_SND_PCM_OSS=m
+CONFIG_SND_HRTIMER=m
+CONFIG_SND_DYNAMIC_MINORS=y
+CONFIG_SND_SEQUENCER=m
+CONFIG_SND_SEQ_DUMMY=m
+CONFIG_SND_DUMMY=m
+CONFIG_SND_ALOOP=m
+CONFIG_SND_VIRMIDI=m
+CONFIG_SND_MTPAV=m
+CONFIG_SND_SERIAL_U16550=m
+CONFIG_SND_MPU401=m
+CONFIG_SND_PIMIDI=m
+CONFIG_SND_PISOUND_MICRO=m
+CONFIG_SND_USB_AUDIO=m
+CONFIG_SND_USB_UA101=m
+CONFIG_SND_USB_CAIAQ=m
+CONFIG_SND_USB_CAIAQ_INPUT=y
+CONFIG_SND_USB_6FIRE=m
+CONFIG_SND_USB_HIFACE=m
+CONFIG_SND_USB_TONEPORT=m
+CONFIG_SND_SOC=m
+CONFIG_SND_BCM2835_SOC_I2S=m
+CONFIG_SND_BCM2708_SOC_CHIPDIP_DAC=m
+CONFIG_SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_ADC=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DAC=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUS=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_STUDIO_DAC8X=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_DIGI=m
+CONFIG_SND_BCM2708_SOC_HIFIBERRY_AMP=m
+CONFIG_SND_BCM2708_SOC_PIFI_40=m
+CONFIG_SND_BCM2708_SOC_RPI_CIRRUS=m
+CONFIG_SND_BCM2708_SOC_RPI_DAC=m
+CONFIG_SND_BCM2708_SOC_RPI_PROTO=m
+CONFIG_SND_BCM2708_SOC_JUSTBOOM_BOTH=m
+CONFIG_SND_BCM2708_SOC_JUSTBOOM_DAC=m
+CONFIG_SND_BCM2708_SOC_JUSTBOOM_DIGI=m
+CONFIG_SND_BCM2708_SOC_IQAUDIO_CODEC=m
+CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC=m
+CONFIG_SND_BCM2708_SOC_IQAUDIO_DIGI=m
+CONFIG_SND_BCM2708_SOC_I_SABRE_Q2M=m
+CONFIG_SND_BCM2708_SOC_ADAU1977_ADC=m
+CONFIG_SND_AUDIOINJECTOR_PI_SOUNDCARD=m
+CONFIG_SND_AUDIOINJECTOR_OCTO_SOUNDCARD=m
+CONFIG_SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD=m
+CONFIG_SND_AUDIOSENSE_PI=m
+CONFIG_SND_DIGIDAC1_SOUNDCARD=m
+CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO=m
+CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO_V2=m
+CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC=m
+CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS=m
+CONFIG_SND_BCM2708_SOC_ALLO_BOSS_DAC=m
+CONFIG_SND_BCM2708_SOC_ALLO_BOSS2_DAC=m
+CONFIG_SND_BCM2708_SOC_ALLO_DIGIONE=m
+CONFIG_SND_BCM2708_SOC_ALLO_KATANA_DAC=m
+CONFIG_SND_BCM2708_SOC_FE_PI_AUDIO=m
+CONFIG_SND_PISOUND=m
+CONFIG_SND_DACBERRY400=m
+CONFIG_SND_DESIGNWARE_I2S=m
+CONFIG_SND_DESIGNWARE_PCM=y
+CONFIG_SND_RP1_AUDIO_OUT=m
+CONFIG_SND_SOC_AD193X_SPI=m
+CONFIG_SND_SOC_AD193X_I2C=m
+CONFIG_SND_SOC_ADAU1701=m
+CONFIG_SND_SOC_ADAU7002=m
+CONFIG_SND_SOC_AK4554=m
+CONFIG_SND_SOC_CS4265=m
+CONFIG_SND_SOC_ICS43432=m
+CONFIG_SND_SOC_MA120X0P=m
+CONFIG_SND_SOC_MAX98357A=m
+CONFIG_SND_SOC_PCM3168A_I2C=m
+CONFIG_SND_SOC_TLV320AIC23_I2C=m
+CONFIG_SND_SOC_WM8804_I2C=m
+CONFIG_SND_SOC_WM8904=m
+CONFIG_SND_SOC_WM8960=m
+CONFIG_SND_SIMPLE_CARD=m
+CONFIG_HID_BATTERY_STRENGTH=y
+CONFIG_HIDRAW=y
+CONFIG_UHID=m
+CONFIG_HID_A4TECH=m
+CONFIG_HID_ACRUX=m
+CONFIG_HID_APPLE=m
+CONFIG_HID_ASUS=m
+CONFIG_HID_BELKIN=m
+CONFIG_HID_BETOP_FF=m
+CONFIG_HID_BIGBEN_FF=m
+CONFIG_HID_CHERRY=m
+CONFIG_HID_CHICONY=m
+CONFIG_HID_CP2112=m
+CONFIG_HID_CYPRESS=m
+CONFIG_HID_DRAGONRISE=m
+CONFIG_HID_EMS_FF=m
+CONFIG_HID_ELECOM=m
+CONFIG_HID_ELO=m
+CONFIG_HID_EZKEY=m
+CONFIG_HID_GEMBIRD=m
+CONFIG_HID_HOLTEK=m
+CONFIG_HID_KEYTOUCH=m
+CONFIG_HID_KYE=m
+CONFIG_HID_UCLOGIC=m
+CONFIG_HID_WALTOP=m
+CONFIG_HID_GYRATION=m
+CONFIG_HID_TWINHAN=m
+CONFIG_HID_KENSINGTON=m
+CONFIG_HID_LCPOWER=m
+CONFIG_HID_LOGITECH=m
+CONFIG_HID_LOGITECH_DJ=m
+CONFIG_LOGITECH_FF=y
+CONFIG_LOGIRUMBLEPAD2_FF=y
+CONFIG_LOGIG940_FF=y
+CONFIG_HID_MAGICMOUSE=m
+CONFIG_HID_MICROSOFT=m
+CONFIG_HID_MONTEREY=m
+CONFIG_HID_MULTITOUCH=m
+CONFIG_HID_NINTENDO=m
+CONFIG_NINTENDO_FF=y
+CONFIG_HID_NTRIG=m
+CONFIG_HID_ORTEK=m
+CONFIG_HID_PANTHERLORD=m
+CONFIG_HID_PETALYNX=m
+CONFIG_HID_PICOLCD=m
+CONFIG_HID_PLAYSTATION=m
+CONFIG_PLAYSTATION_FF=y
+CONFIG_HID_ROCCAT=m
+CONFIG_HID_SAMSUNG=m
+CONFIG_HID_SONY=m
+CONFIG_SONY_FF=y
+CONFIG_HID_SPEEDLINK=m
+CONFIG_HID_STEAM=m
+CONFIG_HID_SUNPLUS=m
+CONFIG_HID_GREENASIA=m
+CONFIG_HID_SMARTJOYPLUS=m
+CONFIG_HID_TOPSEED=m
+CONFIG_HID_THINGM=m
+CONFIG_HID_THRUSTMASTER=m
+CONFIG_HID_WACOM=m
+CONFIG_HID_WIIMOTE=m
+CONFIG_HID_XINMO=m
+CONFIG_HID_ZEROPLUS=m
+CONFIG_HID_ZYDACRON=m
+CONFIG_HID_PID=y
+CONFIG_USB_HIDDEV=y
+CONFIG_I2C_HID_OF=m
+CONFIG_USB=y
+CONFIG_USB_ANNOUNCE_NEW_DEVICES=y
+CONFIG_USB_MON=m
+CONFIG_USB_XHCI_HCD=y
+CONFIG_USB_XHCI_PCI_RENESAS=m
+CONFIG_USB_DWCOTG=y
+CONFIG_USB_PRINTER=m
+CONFIG_USB_TMC=m
+CONFIG_USB_STORAGE=y
+CONFIG_USB_STORAGE_REALTEK=m
+CONFIG_USB_STORAGE_DATAFAB=m
+CONFIG_USB_STORAGE_FREECOM=m
+CONFIG_USB_STORAGE_ISD200=m
+CONFIG_USB_STORAGE_USBAT=m
+CONFIG_USB_STORAGE_SDDR09=m
+CONFIG_USB_STORAGE_SDDR55=m
+CONFIG_USB_STORAGE_JUMPSHOT=m
+CONFIG_USB_STORAGE_ALAUDA=m
+CONFIG_USB_STORAGE_ONETOUCH=m
+CONFIG_USB_STORAGE_KARMA=m
+CONFIG_USB_STORAGE_CYPRESS_ATACB=m
+CONFIG_USB_STORAGE_ENE_UB6250=m
+CONFIG_USB_UAS=y
+CONFIG_USB_MDC800=m
+CONFIG_USB_MICROTEK=m
+CONFIG_USBIP_CORE=m
+CONFIG_USBIP_VHCI_HCD=m
+CONFIG_USBIP_HOST=m
+CONFIG_USBIP_VUDC=m
+CONFIG_USB_DWC3=y
+CONFIG_USB_DWC2=y
+CONFIG_USB_SERIAL=m
+CONFIG_USB_SERIAL_GENERIC=y
+CONFIG_USB_SERIAL_SIMPLE=m
+CONFIG_USB_SERIAL_AIRCABLE=m
+CONFIG_USB_SERIAL_ARK3116=m
+CONFIG_USB_SERIAL_BELKIN=m
+CONFIG_USB_SERIAL_CH341=m
+CONFIG_USB_SERIAL_WHITEHEAT=m
+CONFIG_USB_SERIAL_DIGI_ACCELEPORT=m
+CONFIG_USB_SERIAL_CP210X=m
+CONFIG_USB_SERIAL_CYPRESS_M8=m
+CONFIG_USB_SERIAL_EMPEG=m
+CONFIG_USB_SERIAL_FTDI_SIO=m
+CONFIG_USB_SERIAL_VISOR=m
+CONFIG_USB_SERIAL_IPAQ=m
+CONFIG_USB_SERIAL_IR=m
+CONFIG_USB_SERIAL_EDGEPORT=m
+CONFIG_USB_SERIAL_EDGEPORT_TI=m
+CONFIG_USB_SERIAL_F81232=m
+CONFIG_USB_SERIAL_F8153X=m
+CONFIG_USB_SERIAL_GARMIN=m
+CONFIG_USB_SERIAL_IPW=m
+CONFIG_USB_SERIAL_IUU=m
+CONFIG_USB_SERIAL_KEYSPAN_PDA=m
+CONFIG_USB_SERIAL_KEYSPAN=m
+CONFIG_USB_SERIAL_KLSI=m
+CONFIG_USB_SERIAL_KOBIL_SCT=m
+CONFIG_USB_SERIAL_MCT_U232=m
+CONFIG_USB_SERIAL_METRO=m
+CONFIG_USB_SERIAL_MOS7720=m
+CONFIG_USB_SERIAL_MOS7840=m
+CONFIG_USB_SERIAL_MXUPORT=m
+CONFIG_USB_SERIAL_NAVMAN=m
+CONFIG_USB_SERIAL_PL2303=m
+CONFIG_USB_SERIAL_OTI6858=m
+CONFIG_USB_SERIAL_QCAUX=m
+CONFIG_USB_SERIAL_QUALCOMM=m
+CONFIG_USB_SERIAL_SPCP8X5=m
+CONFIG_USB_SERIAL_SAFE=m
+CONFIG_USB_SERIAL_SIERRAWIRELESS=m
+CONFIG_USB_SERIAL_SYMBOL=m
+CONFIG_USB_SERIAL_TI=m
+CONFIG_USB_SERIAL_CYBERJACK=m
+CONFIG_USB_SERIAL_OPTION=m
+CONFIG_USB_SERIAL_OMNINET=m
+CONFIG_USB_SERIAL_OPTICON=m
+CONFIG_USB_SERIAL_XSENS_MT=m
+CONFIG_USB_SERIAL_WISHBONE=m
+CONFIG_USB_SERIAL_SSU100=m
+CONFIG_USB_SERIAL_QT2=m
+CONFIG_USB_SERIAL_UPD78F0730=m
+CONFIG_USB_SERIAL_XR=m
+CONFIG_USB_SERIAL_DEBUG=m
+CONFIG_USB_EMI62=m
+CONFIG_USB_EMI26=m
+CONFIG_USB_ADUTUX=m
+CONFIG_USB_SEVSEG=m
+CONFIG_USB_LEGOTOWER=m
+CONFIG_USB_LCD=m
+CONFIG_USB_CYPRESS_CY7C63=m
+CONFIG_USB_CYTHERM=m
+CONFIG_USB_IDMOUSE=m
+CONFIG_USB_APPLEDISPLAY=m
+CONFIG_USB_LD=m
+CONFIG_USB_TRANCEVIBRATOR=m
+CONFIG_USB_IOWARRIOR=m
+CONFIG_USB_TEST=m
+CONFIG_USB_ISIGHTFW=m
+CONFIG_USB_YUREX=m
+CONFIG_USB_ATM=m
+CONFIG_USB_SPEEDTOUCH=m
+CONFIG_USB_CXACRU=m
+CONFIG_USB_UEAGLEATM=m
+CONFIG_USB_XUSBATM=m
+CONFIG_NOP_USB_XCEIV=y
+CONFIG_USB_GADGET=y
+CONFIG_USB_CONFIGFS=m
+CONFIG_USB_CONFIGFS_SERIAL=y
+CONFIG_USB_CONFIGFS_ACM=y
+CONFIG_USB_CONFIGFS_OBEX=y
+CONFIG_USB_CONFIGFS_NCM=y
+CONFIG_USB_CONFIGFS_ECM=y
+CONFIG_USB_CONFIGFS_ECM_SUBSET=y
+CONFIG_USB_CONFIGFS_RNDIS=y
+CONFIG_USB_CONFIGFS_EEM=y
+CONFIG_USB_CONFIGFS_MASS_STORAGE=y
+CONFIG_USB_CONFIGFS_F_LB_SS=y
+CONFIG_USB_CONFIGFS_F_FS=y
+CONFIG_USB_CONFIGFS_F_UAC1=y
+CONFIG_USB_CONFIGFS_F_UAC2=y
+CONFIG_USB_CONFIGFS_F_MIDI=y
+CONFIG_USB_CONFIGFS_F_HID=y
+CONFIG_USB_CONFIGFS_F_UVC=y
+CONFIG_USB_CONFIGFS_F_PRINTER=y
+CONFIG_USB_ZERO=m
+CONFIG_USB_AUDIO=m
+CONFIG_USB_ETH=m
+CONFIG_USB_GADGETFS=m
+CONFIG_USB_MASS_STORAGE=m
+CONFIG_USB_G_SERIAL=m
+CONFIG_USB_MIDI_GADGET=m
+CONFIG_USB_G_PRINTER=m
+CONFIG_USB_CDC_COMPOSITE=m
+CONFIG_USB_G_ACM_MS=m
+CONFIG_USB_G_MULTI=m
+CONFIG_USB_G_HID=m
+CONFIG_USB_G_WEBCAM=m
+CONFIG_MMC=y
+CONFIG_MMC_BLOCK_MINORS=32
+CONFIG_MMC_BCM2835_MMC=y
+CONFIG_MMC_BCM2835_DMA=y
+CONFIG_MMC_SDHCI=y
+CONFIG_MMC_SDHCI_PLTFM=y
+CONFIG_MMC_SDHCI_OF_DWCMSHC=m
+CONFIG_MMC_SDHCI_IPROC=y
+CONFIG_MMC_SPI=m
+CONFIG_MMC_HSQ=y
+CONFIG_MMC_BCM2835=y
+CONFIG_LEDS_CLASS=y
+CONFIG_LEDS_CLASS_MULTICOLOR=m
+CONFIG_LEDS_PCA9532=m
+CONFIG_LEDS_GPIO=y
+CONFIG_LEDS_PCA955X=m
+CONFIG_LEDS_PCA963X=m
+CONFIG_LEDS_PWM=y
+CONFIG_LEDS_IS31FL32XX=m
+CONFIG_LEDS_TRIGGER_TIMER=y
+CONFIG_LEDS_TRIGGER_ONESHOT=y
+CONFIG_LEDS_TRIGGER_HEARTBEAT=y
+CONFIG_LEDS_TRIGGER_BACKLIGHT=y
+CONFIG_LEDS_TRIGGER_CPU=y
+CONFIG_LEDS_TRIGGER_DEFAULT_ON=y
+CONFIG_LEDS_TRIGGER_TRANSIENT=m
+CONFIG_LEDS_TRIGGER_CAMERA=m
+CONFIG_LEDS_TRIGGER_INPUT=y
+CONFIG_LEDS_TRIGGER_PANIC=y
+CONFIG_LEDS_TRIGGER_NETDEV=m
+CONFIG_LEDS_TRIGGER_PATTERN=m
+CONFIG_LEDS_TRIGGER_ACTPWR=y
+CONFIG_ACCESSIBILITY=y
+CONFIG_SPEAKUP=m
+CONFIG_SPEAKUP_SYNTH_SOFT=m
+CONFIG_RTC_CLASS=y
+CONFIG_RTC_DRV_ABX80X=m
+CONFIG_RTC_DRV_DS1307=m
+CONFIG_RTC_DRV_DS1374=m
+CONFIG_RTC_DRV_DS1672=m
+CONFIG_RTC_DRV_MAX6900=m
+CONFIG_RTC_DRV_RS5C372=m
+CONFIG_RTC_DRV_ISL1208=m
+CONFIG_RTC_DRV_ISL12022=m
+CONFIG_RTC_DRV_X1205=m
+CONFIG_RTC_DRV_PCF8523=m
+CONFIG_RTC_DRV_PCF85063=m
+CONFIG_RTC_DRV_PCF85363=m
+CONFIG_RTC_DRV_PCF8563=m
+CONFIG_RTC_DRV_PCF8583=m
+CONFIG_RTC_DRV_M41T80=m
+CONFIG_RTC_DRV_BQ32K=m
+CONFIG_RTC_DRV_S35390A=m
+CONFIG_RTC_DRV_FM3130=m
+CONFIG_RTC_DRV_RX8581=m
+CONFIG_RTC_DRV_RX8025=m
+CONFIG_RTC_DRV_EM3027=m
+CONFIG_RTC_DRV_RV3028=m
+CONFIG_RTC_DRV_RV3032=m
+CONFIG_RTC_DRV_RV8803=m
+CONFIG_RTC_DRV_SD3078=m
+CONFIG_RTC_DRV_M41T93=m
+CONFIG_RTC_DRV_M41T94=m
+CONFIG_RTC_DRV_DS1302=m
+CONFIG_RTC_DRV_DS1305=m
+CONFIG_RTC_DRV_DS1390=m
+CONFIG_RTC_DRV_R9701=m
+CONFIG_RTC_DRV_RX4581=m
+CONFIG_RTC_DRV_RS5C348=m
+CONFIG_RTC_DRV_MAX6902=m
+CONFIG_RTC_DRV_PCF2123=m
+CONFIG_RTC_DRV_DS3232=m
+CONFIG_RTC_DRV_PCF2127=m
+CONFIG_RTC_DRV_RV3029C2=m
+CONFIG_DMADEVICES=y
+CONFIG_DMA_BCM2835=y
+CONFIG_DW_AXI_DMAC=y
+CONFIG_DMA_BCM2708=y
+CONFIG_UDMABUF=y
+CONFIG_DMABUF_HEAPS=y
+CONFIG_DMABUF_HEAPS_SYSTEM=y
+CONFIG_DMABUF_HEAPS_CMA=y
+CONFIG_UIO=m
+CONFIG_UIO_PDRV_GENIRQ=m
+CONFIG_VHOST_NET=m
+CONFIG_VHOST_VSOCK=m
+CONFIG_VHOST_CROSS_ENDIAN_LEGACY=y
+CONFIG_STAGING=y
+CONFIG_VT6656=m
+CONFIG_STAGING_MEDIA=y
+CONFIG_STAGING_MEDIA_DEPRECATED=y
+CONFIG_FB_TFT=m
+CONFIG_FB_TFT_AGM1264K_FL=m
+CONFIG_FB_TFT_BD663474=m
+CONFIG_FB_TFT_HX8340BN=m
+CONFIG_FB_TFT_HX8347D=m
+CONFIG_FB_TFT_HX8353D=m
+CONFIG_FB_TFT_HX8357D=m
+CONFIG_FB_TFT_ILI9163=m
+CONFIG_FB_TFT_ILI9320=m
+CONFIG_FB_TFT_ILI9325=m
+CONFIG_FB_TFT_ILI9340=m
+CONFIG_FB_TFT_ILI9341=m
+CONFIG_FB_TFT_ILI9481=m
+CONFIG_FB_TFT_ILI9486=m
+CONFIG_FB_TFT_PCD8544=m
+CONFIG_FB_TFT_RA8875=m
+CONFIG_FB_TFT_S6D02A1=m
+CONFIG_FB_TFT_S6D1121=m
+CONFIG_FB_TFT_SH1106=m
+CONFIG_FB_TFT_SSD1289=m
+CONFIG_FB_TFT_SSD1306=m
+CONFIG_FB_TFT_SSD1331=m
+CONFIG_FB_TFT_SSD1351=m
+CONFIG_FB_TFT_ST7735R=m
+CONFIG_FB_TFT_ST7789V=m
+CONFIG_FB_TFT_TINYLCD=m
+CONFIG_FB_TFT_TLS8204=m
+CONFIG_FB_TFT_UC1611=m
+CONFIG_FB_TFT_UC1701=m
+CONFIG_FB_TFT_UPD161704=m
+CONFIG_BCM2835_VCHIQ=y
+CONFIG_SND_BCM2835=m
+CONFIG_VIDEO_BCM2835=m
+CONFIG_VIDEO_CODEC_BCM2835=m
+CONFIG_VIDEO_ISP_BCM2835=m
+CONFIG_COMMON_CLK_RP1=y
+CONFIG_COMMON_CLK_RP1_SDIO=y
+CONFIG_COMMON_CLK_SI5351=m
+CONFIG_CLK_RASPBERRYPI=y
+CONFIG_MAILBOX=y
+CONFIG_BCM2835_MBOX=y
+CONFIG_MBOX_RP1=m
+CONFIG_BCM2712_IOMMU=y
+CONFIG_RASPBERRYPI_POWER=y
+CONFIG_IIO=m
+CONFIG_IIO_BUFFER_CB=m
+CONFIG_IIO_SW_TRIGGER=m
+CONFIG_ADXL355_I2C=m
+CONFIG_AD799X=m
+CONFIG_MCP320X=m
+CONFIG_MCP3422=m
+CONFIG_TI_ADS1015=m
+CONFIG_BME680=m
+CONFIG_CCS811=m
+CONFIG_SCD4X=m
+CONFIG_SENSIRION_SGP30=m
+CONFIG_SPS30_I2C=m
+CONFIG_MAX30102=m
+CONFIG_DHT11=m
+CONFIG_HDC100X=m
+CONFIG_HDC3020=m
+CONFIG_HTS221=m
+CONFIG_HTU21=m
+CONFIG_SI7020=m
+CONFIG_BOSCH_BNO055_I2C=m
+CONFIG_INV_MPU6050_I2C=m
+CONFIG_APDS9960=m
+CONFIG_AS73211=m
+CONFIG_BH1750=m
+CONFIG_TCS3472=m
+CONFIG_TSL4531=m
+CONFIG_VEML6030=m
+CONFIG_VEML6040=m
+CONFIG_VEML6070=m
+CONFIG_VEML6075=m
+CONFIG_IIO_HRTIMER_TRIGGER=m
+CONFIG_IIO_INTERRUPT_TRIGGER=m
+CONFIG_IIO_SYSFS_TRIGGER=m
+CONFIG_BMP280=m
+CONFIG_MS5637=m
+CONFIG_MAXIM_THERMOCOUPLE=m
+CONFIG_TMP117=m
+CONFIG_MAX31856=m
+CONFIG_PWM=y
+CONFIG_PWM_BCM2835=m
+CONFIG_PWM_BRCMSTB=y
+CONFIG_PWM_GPIO=m
+CONFIG_PWM_PCA9685=m
+CONFIG_PWM_PIO_RP1=m
+CONFIG_PWM_RASPBERRYPI_POE=m
+CONFIG_PWM_RP1=y
+CONFIG_BCM2712_MIP=y
+CONFIG_RPI_AXIPERF=m
+CONFIG_ANDROID_BINDER_IPC=y
+CONFIG_ANDROID_BINDERFS=y
+CONFIG_NVMEM_RMEM=m
+CONFIG_MUX_GPIO=m
+CONFIG_EXT4_FS=y
+CONFIG_EXT4_FS_POSIX_ACL=y
+CONFIG_EXT4_FS_SECURITY=y
+CONFIG_REISERFS_FS=m
+CONFIG_REISERFS_FS_XATTR=y
+CONFIG_REISERFS_FS_POSIX_ACL=y
+CONFIG_REISERFS_FS_SECURITY=y
+CONFIG_JFS_FS=m
+CONFIG_JFS_POSIX_ACL=y
+CONFIG_JFS_SECURITY=y
+CONFIG_JFS_STATISTICS=y
+CONFIG_XFS_FS=m
+CONFIG_XFS_QUOTA=y
+CONFIG_XFS_POSIX_ACL=y
+CONFIG_XFS_RT=y
+CONFIG_GFS2_FS=m
+CONFIG_OCFS2_FS=m
+CONFIG_BTRFS_FS=m
+CONFIG_BTRFS_FS_POSIX_ACL=y
+CONFIG_NILFS2_FS=m
+CONFIG_F2FS_FS=y
+CONFIG_F2FS_FS_SECURITY=y
+CONFIG_BCACHEFS_FS=m
+CONFIG_BCACHEFS_QUOTA=y
+CONFIG_BCACHEFS_POSIX_ACL=y
+CONFIG_FS_ENCRYPTION=y
+CONFIG_FANOTIFY=y
+CONFIG_QFMT_V1=m
+CONFIG_QFMT_V2=m
+CONFIG_AUTOFS_FS=y
+CONFIG_FUSE_FS=m
+CONFIG_CUSE=m
+CONFIG_OVERLAY_FS=m
+CONFIG_FSCACHE=y
+CONFIG_FSCACHE_STATS=y
+CONFIG_CACHEFILES=m
+CONFIG_ISO9660_FS=m
+CONFIG_JOLIET=y
+CONFIG_ZISOFS=y
+CONFIG_UDF_FS=m
+CONFIG_MSDOS_FS=y
+CONFIG_VFAT_FS=y
+CONFIG_FAT_DEFAULT_IOCHARSET="ascii"
+CONFIG_EXFAT_FS=m
+CONFIG_NTFS3_FS=m
+CONFIG_TMPFS=y
+CONFIG_TMPFS_POSIX_ACL=y
+CONFIG_ECRYPT_FS=m
+CONFIG_HFS_FS=m
+CONFIG_HFSPLUS_FS=m
+CONFIG_JFFS2_FS=m
+CONFIG_JFFS2_SUMMARY=y
+CONFIG_UBIFS_FS=m
+CONFIG_SQUASHFS=m
+CONFIG_SQUASHFS_XATTR=y
+CONFIG_SQUASHFS_LZO=y
+CONFIG_SQUASHFS_XZ=y
+CONFIG_SQUASHFS_ZSTD=y
+CONFIG_PSTORE=y
+CONFIG_PSTORE_CONSOLE=y
+CONFIG_PSTORE_RAM=y
+CONFIG_EROFS_FS=m
+CONFIG_EROFS_FS_ZIP_ZSTD=y
+CONFIG_NFS_FS=y
+CONFIG_NFS_V2=y
+CONFIG_NFS_V3_ACL=y
+CONFIG_NFS_V4=y
+CONFIG_NFS_SWAP=y
+CONFIG_NFS_V4_1=y
+CONFIG_NFS_V4_2=y
+CONFIG_ROOT_NFS=y
+CONFIG_NFSD=m
+CONFIG_NFSD_V2=y
+CONFIG_NFSD_V2_ACL=y
+CONFIG_NFSD_V3_ACL=y
+CONFIG_NFSD_V4=y
+CONFIG_CEPH_FS=m
+CONFIG_CIFS=m
+CONFIG_CIFS_UPCALL=y
+CONFIG_CIFS_XATTR=y
+CONFIG_CIFS_POSIX=y
+CONFIG_CIFS_DFS_UPCALL=y
+CONFIG_CIFS_FSCACHE=y
+CONFIG_SMB_SERVER=m
+CONFIG_9P_FS=m
+CONFIG_9P_FS_POSIX_ACL=y
+CONFIG_NLS_DEFAULT="utf8"
+CONFIG_NLS_CODEPAGE_437=y
+CONFIG_NLS_CODEPAGE_737=m
+CONFIG_NLS_CODEPAGE_775=m
+CONFIG_NLS_CODEPAGE_850=m
+CONFIG_NLS_CODEPAGE_852=m
+CONFIG_NLS_CODEPAGE_855=m
+CONFIG_NLS_CODEPAGE_857=m
+CONFIG_NLS_CODEPAGE_860=m
+CONFIG_NLS_CODEPAGE_861=m
+CONFIG_NLS_CODEPAGE_862=m
+CONFIG_NLS_CODEPAGE_863=m
+CONFIG_NLS_CODEPAGE_864=m
+CONFIG_NLS_CODEPAGE_865=m
+CONFIG_NLS_CODEPAGE_866=m
+CONFIG_NLS_CODEPAGE_869=m
+CONFIG_NLS_CODEPAGE_936=m
+CONFIG_NLS_CODEPAGE_950=m
+CONFIG_NLS_CODEPAGE_932=m
+CONFIG_NLS_CODEPAGE_949=m
+CONFIG_NLS_CODEPAGE_874=m
+CONFIG_NLS_ISO8859_8=m
+CONFIG_NLS_CODEPAGE_1250=m
+CONFIG_NLS_CODEPAGE_1251=m
+CONFIG_NLS_ASCII=y
+CONFIG_NLS_ISO8859_1=m
+CONFIG_NLS_ISO8859_2=m
+CONFIG_NLS_ISO8859_3=m
+CONFIG_NLS_ISO8859_4=m
+CONFIG_NLS_ISO8859_5=m
+CONFIG_NLS_ISO8859_6=m
+CONFIG_NLS_ISO8859_7=m
+CONFIG_NLS_ISO8859_9=m
+CONFIG_NLS_ISO8859_13=m
+CONFIG_NLS_ISO8859_14=m
+CONFIG_NLS_ISO8859_15=m
+CONFIG_NLS_KOI8_R=m
+CONFIG_NLS_KOI8_U=m
+CONFIG_DLM=m
+CONFIG_UNICODE=m
+CONFIG_KEY_DH_OPERATIONS=y
+CONFIG_SECURITY=y
+CONFIG_SECURITY_APPARMOR=y
+CONFIG_LSM=""
+CONFIG_CRYPTO_USER=m
+CONFIG_CRYPTO_CRYPTD=m
+CONFIG_CRYPTO_AES=m
+CONFIG_CRYPTO_CAST5=m
+CONFIG_CRYPTO_DES=y
+CONFIG_CRYPTO_TWOFISH=m
+CONFIG_CRYPTO_ADIANTUM=m
+CONFIG_CRYPTO_CBC=m
+CONFIG_CRYPTO_CHACHA20POLY1305=m
+CONFIG_CRYPTO_MD4=m
+CONFIG_CRYPTO_SHA512=m
+CONFIG_CRYPTO_WP512=m
+CONFIG_CRYPTO_XCBC=m
+CONFIG_CRYPTO_LZ4=m
+CONFIG_CRYPTO_LZ4HC=m
+CONFIG_CRYPTO_USER_API_HASH=m
+CONFIG_CRYPTO_USER_API_SKCIPHER=m
+CONFIG_CRYPTO_USER_API_RNG=m
+CONFIG_CRYPTO_USER_API_AEAD=m
+CONFIG_CRYPTO_NHPOLY1305_NEON=m
+CONFIG_CRYPTO_GHASH_ARM64_CE=m
+CONFIG_CRYPTO_SHA1_ARM64_CE=m
+CONFIG_CRYPTO_SHA2_ARM64_CE=m
+CONFIG_CRYPTO_SHA512_ARM64_CE=m
+CONFIG_CRYPTO_SHA3_ARM64=m
+CONFIG_CRYPTO_SM3_ARM64_CE=m
+CONFIG_CRYPTO_AES_ARM64=m
+CONFIG_CRYPTO_AES_ARM64_BS=m
+CONFIG_CRYPTO_SM4_ARM64_CE=m
+CONFIG_CRYPTO_AES_ARM64_CE_CCM=m
+# CONFIG_CRYPTO_HW is not set
+CONFIG_PKCS8_PRIVATE_KEY_PARSER=m
+CONFIG_CRC_ITU_T=y
+CONFIG_LIBCRC32C=y
+CONFIG_DMA_CMA=y
+CONFIG_CMA_SIZE_MBYTES=5
+CONFIG_PRINTK_TIME=y
+CONFIG_BOOT_PRINTK_DELAY=y
+CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE=0x1f6
+CONFIG_KGDB=y
+CONFIG_KGDB_KDB=y
+CONFIG_KDB_KEYBOARD=y
+CONFIG_DEBUG_MEMORY_INIT=y
+CONFIG_DETECT_HUNG_TASK=y
+CONFIG_LATENCYTOP=y
+CONFIG_FUNCTION_PROFILER=y
+CONFIG_STACK_TRACER=y
+CONFIG_SCHED_TRACER=y
+CONFIG_FTRACE_SYSCALLS=y
+CONFIG_BLK_DEV_IO_TRACE=y
+# CONFIG_UPROBE_EVENTS is not set
+# CONFIG_STRICT_DEVMEM is not set
diff --git a/arch/arm64/crypto/aes-cipher-glue.c b/arch/arm64/crypto/aes-cipher-glue.c
index 4ec55e568941..bfaa0f1d3cc6 100644
--- a/arch/arm64/crypto/aes-cipher-glue.c
+++ b/arch/arm64/crypto/aes-cipher-glue.c
@@ -9,6 +9,17 @@
 #include <crypto/algapi.h>
 #include <linux/module.h>
 
+MODULE_ALIAS_CRYPTO("ecb(aes)");
+MODULE_ALIAS_CRYPTO("cbc(aes)");
+MODULE_ALIAS_CRYPTO("ctr(aes)");
+MODULE_ALIAS_CRYPTO("xts(aes)");
+MODULE_ALIAS_CRYPTO("xctr(aes)");
+MODULE_ALIAS_CRYPTO("cts(cbc(aes))");
+MODULE_ALIAS_CRYPTO("essiv(cbc(aes),sha256)");
+MODULE_ALIAS_CRYPTO("cmac(aes)");
+MODULE_ALIAS_CRYPTO("xcbc(aes)");
+MODULE_ALIAS_CRYPTO("cbcmac(aes)");
+
 asmlinkage void __aes_arm64_encrypt(u32 *rk, u8 *out, const u8 *in, int rounds);
 asmlinkage void __aes_arm64_decrypt(u32 *rk, u8 *out, const u8 *in, int rounds);
 
diff --git a/arch/arm64/crypto/aes-glue.c b/arch/arm64/crypto/aes-glue.c
index a147e847a5a1..1cbf1577c43d 100644
--- a/arch/arm64/crypto/aes-glue.c
+++ b/arch/arm64/crypto/aes-glue.c
@@ -57,18 +57,18 @@ MODULE_DESCRIPTION("AES-ECB/CBC/CTR/XTS/XCTR using ARMv8 Crypto Extensions");
 #define aes_mac_update		neon_aes_mac_update
 MODULE_DESCRIPTION("AES-ECB/CBC/CTR/XTS/XCTR using ARMv8 NEON");
 #endif
-#if defined(USE_V8_CRYPTO_EXTENSIONS) || !IS_ENABLED(CONFIG_CRYPTO_AES_ARM64_BS)
+#if defined(USE_V8_CRYPTO_EXTENSIONS)
 MODULE_ALIAS_CRYPTO("ecb(aes)");
 MODULE_ALIAS_CRYPTO("cbc(aes)");
 MODULE_ALIAS_CRYPTO("ctr(aes)");
 MODULE_ALIAS_CRYPTO("xts(aes)");
 MODULE_ALIAS_CRYPTO("xctr(aes)");
-#endif
 MODULE_ALIAS_CRYPTO("cts(cbc(aes))");
 MODULE_ALIAS_CRYPTO("essiv(cbc(aes),sha256)");
 MODULE_ALIAS_CRYPTO("cmac(aes)");
 MODULE_ALIAS_CRYPTO("xcbc(aes)");
 MODULE_ALIAS_CRYPTO("cbcmac(aes)");
+#endif
 
 MODULE_AUTHOR("Ard Biesheuvel <ard.biesheuvel@linaro.org>");
 MODULE_LICENSE("GPL v2");
diff --git a/arch/arm64/crypto/aes-neonbs-glue.c b/arch/arm64/crypto/aes-neonbs-glue.c
index 46425e7b9755..2809349884da 100644
--- a/arch/arm64/crypto/aes-neonbs-glue.c
+++ b/arch/arm64/crypto/aes-neonbs-glue.c
@@ -19,11 +19,6 @@ MODULE_AUTHOR("Ard Biesheuvel <ard.biesheuvel@linaro.org>");
 MODULE_DESCRIPTION("Bit sliced AES using NEON instructions");
 MODULE_LICENSE("GPL v2");
 
-MODULE_ALIAS_CRYPTO("ecb(aes)");
-MODULE_ALIAS_CRYPTO("cbc(aes)");
-MODULE_ALIAS_CRYPTO("ctr(aes)");
-MODULE_ALIAS_CRYPTO("xts(aes)");
-
 asmlinkage void aesbs_convert_key(u8 out[], u32 const rk[], int rounds);
 
 asmlinkage void aesbs_ecb_encrypt(u8 out[], u8 const in[], u8 const rk[],
diff --git a/arch/arm64/kernel/armv8_deprecated.c b/arch/arm64/kernel/armv8_deprecated.c
index e737c6295ec7..abb590819ac0 100644
--- a/arch/arm64/kernel/armv8_deprecated.c
+++ b/arch/arm64/kernel/armv8_deprecated.c
@@ -540,9 +540,14 @@ static void __init register_insn_emulation(struct insn_emulation *insn)
 
 	switch (insn->status) {
 	case INSN_DEPRECATED:
+#if 0
 		insn->current_mode = INSN_EMULATE;
 		/* Disable the HW mode if it was turned on at early boot time */
 		run_all_cpu_set_hw_mode(insn, false);
+#else
+		insn->current_mode = INSN_HW;
+		run_all_cpu_set_hw_mode(insn, true);
+#endif
 		insn->max = INSN_HW;
 		break;
 	case INSN_OBSOLETE:
diff --git a/arch/arm64/kernel/cpuinfo.c b/arch/arm64/kernel/cpuinfo.c
index aec5e3947c78..1534bbbcd0d0 100644
--- a/arch/arm64/kernel/cpuinfo.c
+++ b/arch/arm64/kernel/cpuinfo.c
@@ -17,6 +17,8 @@
 #include <linux/elf.h>
 #include <linux/init.h>
 #include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
 #include <linux/personality.h>
 #include <linux/preempt.h>
 #include <linux/printk.h>
@@ -195,6 +197,10 @@ static int c_show(struct seq_file *m, void *v)
 {
 	int i, j;
 	bool compat = personality(current->personality) == PER_LINUX32;
+	struct device_node *np;
+	const char *model;
+	const char *serial;
+	u32 revision;
 
 	for_each_online_cpu(i) {
 		struct cpuinfo_arm64 *cpuinfo = &per_cpu(cpu_data, i);
@@ -255,6 +261,24 @@ static int c_show(struct seq_file *m, void *v)
 		seq_printf(m, "CPU revision\t: %d\n\n", MIDR_REVISION(midr));
 	}
 
+	np = of_find_node_by_path("/system");
+	if (np) {
+		if (!of_property_read_u32(np, "linux,revision", &revision))
+			seq_printf(m, "Revision\t: %04x\n", revision);
+		of_node_put(np);
+	}
+
+	np = of_find_node_by_path("/");
+	if (np) {
+		if (!of_property_read_string(np, "serial-number",
+					     &serial))
+			seq_printf(m, "Serial\t\t: %s\n", serial);
+		if (!of_property_read_string(np, "model",
+					     &model))
+			seq_printf(m, "Model\t\t: %s\n", model);
+		of_node_put(np);
+	}
+
 	return 0;
 }
 
diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c
index 2edf88c1c695..ceaa7b833d80 100644
--- a/arch/arm64/kernel/process.c
+++ b/arch/arm64/kernel/process.c
@@ -97,9 +97,7 @@ void machine_shutdown(void)
  */
 void machine_halt(void)
 {
-	local_irq_disable();
-	smp_send_stop();
-	while (1);
+	machine_power_off();
 }
 
 /*
diff --git a/arch/arm64/mm/dma-mapping.c b/arch/arm64/mm/dma-mapping.c
index b2b5792b2caa..51c43c1f5630 100644
--- a/arch/arm64/mm/dma-mapping.c
+++ b/arch/arm64/mm/dma-mapping.c
@@ -16,8 +16,22 @@ void arch_sync_dma_for_device(phys_addr_t paddr, size_t size,
 			      enum dma_data_direction dir)
 {
 	unsigned long start = (unsigned long)phys_to_virt(paddr);
+	unsigned long end = start + size;
 
-	dcache_clean_poc(start, start + size);
+	switch (dir) {
+	case DMA_BIDIRECTIONAL:
+		dcache_clean_inval_poc(start, end);
+		break;
+	case DMA_TO_DEVICE:
+		dcache_clean_poc(start, end);
+		break;
+	case DMA_FROM_DEVICE:
+		dcache_inval_poc(start, end);
+		break;
+	case DMA_NONE:
+	default:
+		break;
+	}
 }
 
 void arch_sync_dma_for_cpu(phys_addr_t paddr, size_t size,
diff --git a/drivers/bluetooth/btbcm.c b/drivers/bluetooth/btbcm.c
index 0a60660fc8ce..dd1e6f21d555 100644
--- a/drivers/bluetooth/btbcm.c
+++ b/drivers/bluetooth/btbcm.c
@@ -25,12 +25,15 @@
 #define BDADDR_BCM20702A1 (&(bdaddr_t) {{0x00, 0x00, 0xa0, 0x02, 0x70, 0x20}})
 #define BDADDR_BCM2076B1 (&(bdaddr_t) {{0x79, 0x56, 0x00, 0xa0, 0x76, 0x20}})
 #define BDADDR_BCM43430A0 (&(bdaddr_t) {{0xac, 0x1f, 0x12, 0xa0, 0x43, 0x43}})
-#define BDADDR_BCM43430A1 (&(bdaddr_t) {{0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa}})
+#define BDADDR_BCM43430A1 (&(bdaddr_t) {{0xac, 0x1f, 0x12, 0xa1, 0x43, 0x43}})
+#define BDADDR_BCM43430B0 (&(bdaddr_t) {{0xac, 0x1f, 0x37, 0xb0, 0x43, 0x43}})
 #define BDADDR_BCM4324B3 (&(bdaddr_t) {{0x00, 0x00, 0x00, 0xb3, 0x24, 0x43}})
 #define BDADDR_BCM4330B1 (&(bdaddr_t) {{0x00, 0x00, 0x00, 0xb1, 0x30, 0x43}})
 #define BDADDR_BCM4334B0 (&(bdaddr_t) {{0x00, 0x00, 0x00, 0xb0, 0x34, 0x43}})
+#define BDADDR_BCM4345C0 (&(bdaddr_t) {{0xac, 0x1f, 0x00, 0xc0, 0x45, 0x43}})
 #define BDADDR_BCM4345C5 (&(bdaddr_t) {{0xac, 0x1f, 0x00, 0xc5, 0x45, 0x43}})
 #define BDADDR_BCM43341B (&(bdaddr_t) {{0xac, 0x1f, 0x00, 0x1b, 0x34, 0x43}})
+#define BDADDR_BCM43438 (&(bdaddr_t) {{0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa}})
 
 #define BCM_FW_NAME_LEN			64
 #define BCM_FW_NAME_COUNT_MAX		4
@@ -127,9 +130,12 @@ int btbcm_check_bdaddr(struct hci_dev *hdev)
 	    !bacmp(&bda->bdaddr, BDADDR_BCM4324B3) ||
 	    !bacmp(&bda->bdaddr, BDADDR_BCM4330B1) ||
 	    !bacmp(&bda->bdaddr, BDADDR_BCM4334B0) ||
+	    !bacmp(&bda->bdaddr, BDADDR_BCM4345C0) ||
 	    !bacmp(&bda->bdaddr, BDADDR_BCM4345C5) ||
 	    !bacmp(&bda->bdaddr, BDADDR_BCM43430A0) ||
 	    !bacmp(&bda->bdaddr, BDADDR_BCM43430A1) ||
+	    !bacmp(&bda->bdaddr, BDADDR_BCM43430B0) ||
+	    !bacmp(&bda->bdaddr, BDADDR_BCM43438) ||
 	    !bacmp(&bda->bdaddr, BDADDR_BCM43341B)) {
 		/* Try falling back to BDADDR EFI variable */
 		if (btbcm_set_bdaddr_from_efi(hdev) != 0) {
@@ -515,6 +521,7 @@ static const struct bcm_subver_table bcm_uart_subver_table[] = {
 	{ 0x4106, "BCM4335A0"	},	/* 002.001.006 */
 	{ 0x410c, "BCM43430B0"	},	/* 002.001.012 */
 	{ 0x2119, "BCM4373A0"	},	/* 001.001.025 */
+	{ 0x2310, "BCM4343A2"	},	/* 001.003.016 */
 	{ }
 };
 
diff --git a/drivers/bluetooth/hci_h5.c b/drivers/bluetooth/hci_h5.c
index c0436881a533..0717d12f972a 100644
--- a/drivers/bluetooth/hci_h5.c
+++ b/drivers/bluetooth/hci_h5.c
@@ -358,7 +358,8 @@ static void h5_handle_internal_rx(struct hci_uart *hu)
 		h5_link_control(hu, conf_req, 3);
 	} else if (memcmp(data, conf_req, 2) == 0) {
 		h5_link_control(hu, conf_rsp, 2);
-		h5_link_control(hu, conf_req, 3);
+		if (h5->state != H5_ACTIVE)
+		    h5_link_control(hu, conf_req, 3);
 	} else if (memcmp(data, conf_rsp, 2) == 0) {
 		if (H5_HDR_LEN(hdr) > 2)
 			h5->tx_win = (data[2] & 0x07);
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
index 7c8dd0abcfdf..8b38fd39bba6 100644
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -5,6 +5,8 @@
 
 menu "Character devices"
 
+source "drivers/char/broadcom/Kconfig"
+
 source "drivers/tty/Kconfig"
 
 config TTY_PRINTK
@@ -422,4 +424,12 @@ config ADI
 	  and SSM (Silicon Secured Memory).  Intended consumers of this
 	  driver include crash and makedumpfile.
 
+config RASPBERRYPI_GPIOMEM
+        tristate "Rootless GPIO access via mmap() on Raspberry Pi boards"
+        default n
+        help
+                Provides users with root-free access to the GPIO registers
+                on the board. Calling mmap(/dev/gpiomem) will map the GPIO
+                register page to the user's pointer.
+
 endmenu
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index e9b360cdc99a..c8a078fa4912 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -43,3 +43,5 @@ obj-$(CONFIG_PS3_FLASH)		+= ps3flash.o
 obj-$(CONFIG_XILLYBUS_CLASS)	+= xillybus/
 obj-$(CONFIG_POWERNV_OP_PANEL)	+= powernv-op-panel.o
 obj-$(CONFIG_ADI)		+= adi.o
+obj-$(CONFIG_BRCM_CHAR_DRIVERS) += broadcom/
+obj-$(CONFIG_RASPBERRYPI_GPIOMEM) += raspberrypi-gpiomem.o
diff --git a/drivers/char/broadcom/Kconfig b/drivers/char/broadcom/Kconfig
new file mode 100644
index 000000000000..29d880d47282
--- /dev/null
+++ b/drivers/char/broadcom/Kconfig
@@ -0,0 +1,33 @@
+#
+# Broadcom char driver config
+#
+
+menuconfig BRCM_CHAR_DRIVERS
+	bool "Broadcom Char Drivers"
+	help
+	  Broadcom's char drivers
+
+if BRCM_CHAR_DRIVERS
+
+config BCM2708_VCMEM
+	bool "Videocore Memory"
+        default y
+        help
+          Helper for videocore memory access and total size allocation.
+
+config BCM_VCIO
+	tristate "Mailbox userspace access"
+	depends on BCM2835_MBOX
+	help
+	  Gives access to the mailbox property channel from userspace.
+
+endif
+
+config BCM2835_SMI_DEV
+	tristate "Character device driver for BCM2835 Secondary Memory Interface"
+	depends on BCM2835_SMI
+	default m
+	help
+		This driver provides a character device interface (ioctl + read/write) to
+		Broadcom's Secondary Memory interface. The low-level functionality is provided
+		by the SMI driver itself.
diff --git a/drivers/char/broadcom/Makefile b/drivers/char/broadcom/Makefile
new file mode 100644
index 000000000000..2ae3e9d411e9
--- /dev/null
+++ b/drivers/char/broadcom/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_BCM2708_VCMEM)	+= vc_mem.o
+obj-$(CONFIG_BCM_VCIO)		+= vcio.o
+obj-$(CONFIG_BCM2835_SMI_DEV)	+= bcm2835_smi_dev.o
diff --git a/drivers/char/broadcom/bcm2835_smi_dev.c b/drivers/char/broadcom/bcm2835_smi_dev.c
new file mode 100644
index 000000000000..ae0c0d24fad9
--- /dev/null
+++ b/drivers/char/broadcom/bcm2835_smi_dev.c
@@ -0,0 +1,408 @@
+/**
+ * Character device driver for Broadcom Secondary Memory Interface
+ *
+ * Written by Luke Wren <luke@raspberrypi.org>
+ * Copyright (c) 2015, Raspberry Pi (Trading) Ltd.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The names of the above-listed copyright holders may not be used
+ *    to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * ALTERNATIVELY, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2, as published by the Free
+ * Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/pagemap.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/fs.h>
+
+#include <linux/broadcom/bcm2835_smi.h>
+
+#define DEVICE_NAME "bcm2835-smi-dev"
+#define DRIVER_NAME "smi-dev-bcm2835"
+#define DEVICE_MINOR 0
+
+static struct cdev bcm2835_smi_cdev;
+static dev_t bcm2835_smi_devid;
+static struct class *bcm2835_smi_class;
+static struct device *bcm2835_smi_dev;
+
+struct bcm2835_smi_dev_instance {
+	struct device *dev;
+};
+
+static struct bcm2835_smi_instance *smi_inst;
+static struct bcm2835_smi_dev_instance *inst;
+
+static const char *const ioctl_names[] = {
+	"READ_SETTINGS",
+	"WRITE_SETTINGS",
+	"ADDRESS"
+};
+
+/****************************************************************************
+*
+*   SMI chardev file ops
+*
+***************************************************************************/
+static long
+bcm2835_smi_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	long ret = 0;
+
+	dev_info(inst->dev, "serving ioctl...");
+
+	switch (cmd) {
+	case BCM2835_SMI_IOC_GET_SETTINGS:{
+		struct smi_settings *settings;
+
+		dev_info(inst->dev, "Reading SMI settings to user.");
+		settings = bcm2835_smi_get_settings_from_regs(smi_inst);
+		if (copy_to_user((void *)arg, settings,
+				 sizeof(struct smi_settings)))
+			dev_err(inst->dev, "settings copy failed.");
+		break;
+	}
+	case BCM2835_SMI_IOC_WRITE_SETTINGS:{
+		struct smi_settings *settings;
+
+		dev_info(inst->dev, "Setting user's SMI settings.");
+		settings = bcm2835_smi_get_settings_from_regs(smi_inst);
+		if (copy_from_user(settings, (void *)arg,
+				   sizeof(struct smi_settings)))
+			dev_err(inst->dev, "settings copy failed.");
+		else
+			bcm2835_smi_set_regs_from_settings(smi_inst);
+		break;
+	}
+	case BCM2835_SMI_IOC_ADDRESS:
+		dev_info(inst->dev, "SMI address set: 0x%02x", (int)arg);
+		bcm2835_smi_set_address(smi_inst, arg);
+		break;
+	default:
+		dev_err(inst->dev, "invalid ioctl cmd: %d", cmd);
+		ret = -ENOTTY;
+		break;
+	}
+
+	return ret;
+}
+
+static int bcm2835_smi_open(struct inode *inode, struct file *file)
+{
+	int dev = iminor(inode);
+
+	dev_dbg(inst->dev, "SMI device opened.");
+
+	if (dev != DEVICE_MINOR) {
+		dev_err(inst->dev,
+			"bcm2835_smi_release: Unknown minor device: %d",
+			dev);
+		return -ENXIO;
+	}
+
+	return 0;
+}
+
+static int bcm2835_smi_release(struct inode *inode, struct file *file)
+{
+	int dev = iminor(inode);
+
+	if (dev != DEVICE_MINOR) {
+		dev_err(inst->dev,
+			"bcm2835_smi_release: Unknown minor device %d", dev);
+		return -ENXIO;
+	}
+
+	return 0;
+}
+
+static ssize_t dma_bounce_user(
+	enum dma_transfer_direction dma_dir,
+	char __user *user_ptr,
+	size_t count,
+	struct bcm2835_smi_bounce_info *bounce)
+{
+	int chunk_size;
+	int chunk_no = 0;
+	int count_left = count;
+
+	while (count_left) {
+		int rv;
+		void *buf;
+
+		/* Wait for current chunk to complete: */
+		if (down_timeout(&bounce->callback_sem,
+			msecs_to_jiffies(1000))) {
+			dev_err(inst->dev, "DMA bounce timed out");
+			count -= (count_left);
+			break;
+		}
+
+		if (bounce->callback_sem.count >= DMA_BOUNCE_BUFFER_COUNT - 1)
+			dev_err(inst->dev, "WARNING: Ring buffer overflow");
+		chunk_size = count_left > DMA_BOUNCE_BUFFER_SIZE ?
+			DMA_BOUNCE_BUFFER_SIZE : count_left;
+		buf = bounce->buffer[chunk_no % DMA_BOUNCE_BUFFER_COUNT];
+		if (dma_dir == DMA_DEV_TO_MEM)
+			rv = copy_to_user(user_ptr, buf, chunk_size);
+		else
+			rv = copy_from_user(buf, user_ptr, chunk_size);
+		if (rv)
+			dev_err(inst->dev, "copy_*_user() failed!: %d", rv);
+		user_ptr += chunk_size;
+		count_left -= chunk_size;
+		chunk_no++;
+	}
+	return count;
+}
+
+static ssize_t
+bcm2835_read_file(struct file *f, char __user *user_ptr,
+		  size_t count, loff_t *offs)
+{
+	int odd_bytes;
+	size_t count_check;
+
+	dev_dbg(inst->dev, "User reading %zu bytes from SMI.", count);
+	/* We don't want to DMA a number of bytes % 4 != 0 (32 bit FIFO) */
+	if (count > DMA_THRESHOLD_BYTES)
+		odd_bytes = count & 0x3;
+	else
+		odd_bytes = count;
+	count -= odd_bytes;
+	count_check = count;
+	if (count) {
+		struct bcm2835_smi_bounce_info *bounce;
+
+		count = bcm2835_smi_user_dma(smi_inst,
+			DMA_DEV_TO_MEM, user_ptr, count,
+			&bounce);
+		if (count)
+			count = dma_bounce_user(DMA_DEV_TO_MEM, user_ptr,
+				count, bounce);
+	}
+	if (odd_bytes && (count == count_check)) {
+		/* Read from FIFO directly if not using DMA */
+		uint8_t buf[DMA_THRESHOLD_BYTES];
+		unsigned long bytes_not_transferred;
+
+		bcm2835_smi_read_buf(smi_inst, buf, odd_bytes);
+		bytes_not_transferred = copy_to_user(user_ptr + count, buf, odd_bytes);
+		if (bytes_not_transferred)
+			dev_err(inst->dev, "copy_to_user() failed.");
+		count += odd_bytes - bytes_not_transferred;
+	}
+	return count;
+}
+
+static ssize_t
+bcm2835_write_file(struct file *f, const char __user *user_ptr,
+		   size_t count, loff_t *offs)
+{
+	int odd_bytes;
+	size_t count_check;
+
+	dev_dbg(inst->dev, "User writing %zu bytes to SMI.", count);
+	if (count > DMA_THRESHOLD_BYTES)
+		odd_bytes = count & 0x3;
+	else
+		odd_bytes = count;
+	count -= odd_bytes;
+	count_check = count;
+	if (count) {
+		struct bcm2835_smi_bounce_info *bounce;
+
+		count = bcm2835_smi_user_dma(smi_inst,
+			DMA_MEM_TO_DEV, (char __user *)user_ptr, count,
+			&bounce);
+		if (count)
+			count = dma_bounce_user(DMA_MEM_TO_DEV,
+				(char __user *)user_ptr,
+				count, bounce);
+	}
+	if (odd_bytes && (count == count_check)) {
+		uint8_t buf[DMA_THRESHOLD_BYTES];
+		unsigned long bytes_not_transferred;
+
+		bytes_not_transferred = copy_from_user(buf, user_ptr + count, odd_bytes);
+		if (bytes_not_transferred)
+			dev_err(inst->dev, "copy_from_user() failed.");
+		else
+			bcm2835_smi_write_buf(smi_inst, buf, odd_bytes);
+		count += odd_bytes - bytes_not_transferred;
+	}
+	return count;
+}
+
+static const struct file_operations
+bcm2835_smi_fops = {
+	.owner = THIS_MODULE,
+	.unlocked_ioctl = bcm2835_smi_ioctl,
+	.open = bcm2835_smi_open,
+	.release = bcm2835_smi_release,
+	.read = bcm2835_read_file,
+	.write = bcm2835_write_file,
+};
+
+
+/****************************************************************************
+*
+*   bcm2835_smi_probe - called when the driver is loaded.
+*
+***************************************************************************/
+
+static int bcm2835_smi_dev_probe(struct platform_device *pdev)
+{
+	int err;
+	void *ptr_err;
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node, *smi_node;
+
+	if (!node) {
+		dev_err(dev, "No device tree node supplied!");
+		return -EINVAL;
+	}
+
+	smi_node = of_parse_phandle(node, "smi_handle", 0);
+
+	if (!smi_node) {
+		dev_err(dev, "No such property: smi_handle");
+		return -ENXIO;
+	}
+
+	smi_inst = bcm2835_smi_get(smi_node);
+
+	if (!smi_inst)
+		return -EPROBE_DEFER;
+
+	/* Allocate buffers and instance data */
+
+	inst = devm_kzalloc(dev, sizeof(*inst), GFP_KERNEL);
+
+	if (!inst)
+		return -ENOMEM;
+
+	inst->dev = dev;
+
+	/* Create character device entries */
+
+	err = alloc_chrdev_region(&bcm2835_smi_devid,
+				  DEVICE_MINOR, 1, DEVICE_NAME);
+	if (err != 0) {
+		dev_err(inst->dev, "unable to allocate device number");
+		return -ENOMEM;
+	}
+	cdev_init(&bcm2835_smi_cdev, &bcm2835_smi_fops);
+	bcm2835_smi_cdev.owner = THIS_MODULE;
+	err = cdev_add(&bcm2835_smi_cdev, bcm2835_smi_devid, 1);
+	if (err != 0) {
+		dev_err(inst->dev, "unable to register device");
+		err = -ENOMEM;
+		goto failed_cdev_add;
+	}
+
+	/* Create sysfs entries */
+
+	bcm2835_smi_class = class_create(DEVICE_NAME);
+	ptr_err = bcm2835_smi_class;
+	if (IS_ERR(ptr_err))
+		goto failed_class_create;
+
+	bcm2835_smi_dev = device_create(bcm2835_smi_class, NULL,
+					bcm2835_smi_devid, NULL,
+					"smi");
+	ptr_err = bcm2835_smi_dev;
+	if (IS_ERR(ptr_err))
+		goto failed_device_create;
+
+	dev_info(inst->dev, "initialised");
+
+	return 0;
+
+failed_device_create:
+	class_destroy(bcm2835_smi_class);
+failed_class_create:
+	cdev_del(&bcm2835_smi_cdev);
+	err = PTR_ERR(ptr_err);
+failed_cdev_add:
+	unregister_chrdev_region(bcm2835_smi_devid, 1);
+	dev_err(dev, "could not load bcm2835_smi_dev");
+	return err;
+}
+
+/****************************************************************************
+*
+*   bcm2835_smi_remove - called when the driver is unloaded.
+*
+***************************************************************************/
+
+static void bcm2835_smi_dev_remove(struct platform_device *pdev)
+{
+	device_destroy(bcm2835_smi_class, bcm2835_smi_devid);
+	class_destroy(bcm2835_smi_class);
+	cdev_del(&bcm2835_smi_cdev);
+	unregister_chrdev_region(bcm2835_smi_devid, 1);
+
+	dev_info(inst->dev, "SMI character dev removed - OK");
+}
+
+/****************************************************************************
+*
+*   Register the driver with device tree
+*
+***************************************************************************/
+
+static const struct of_device_id bcm2835_smi_dev_of_match[] = {
+	{.compatible = "brcm,bcm2835-smi-dev",},
+	{ /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, bcm2835_smi_dev_of_match);
+
+static struct platform_driver bcm2835_smi_dev_driver = {
+	.probe = bcm2835_smi_dev_probe,
+	.remove = bcm2835_smi_dev_remove,
+	.driver = {
+		   .name = DRIVER_NAME,
+		   .owner = THIS_MODULE,
+		   .of_match_table = bcm2835_smi_dev_of_match,
+		   },
+};
+
+module_platform_driver(bcm2835_smi_dev_driver);
+
+MODULE_ALIAS("platform:smi-dev-bcm2835");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION(
+	"Character device driver for BCM2835's secondary memory interface");
+MODULE_AUTHOR("Luke Wren <luke@raspberrypi.org>");
diff --git a/drivers/char/broadcom/vc_mem.c b/drivers/char/broadcom/vc_mem.c
new file mode 100644
index 000000000000..4b7faf084e0c
--- /dev/null
+++ b/drivers/char/broadcom/vc_mem.c
@@ -0,0 +1,635 @@
+/*
+ * Copyright 2010 - 2011 Broadcom Corporation.  All rights reserved.
+ *
+ * Unless you and Broadcom execute a separate written software license
+ * agreement governing use of this software, this software is licensed to you
+ * under the terms of the GNU General Public License version 2, available at
+ * http://www.broadcom.com/licenses/GPLv2.php (the "GPL").
+ *
+ * Notwithstanding the above, under no circumstances may you combine this
+ * software in any way with any other Broadcom software provided under a
+ * license other than the GPL, without Broadcom's express prior written
+ * consent.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/device.h>
+#include <linux/cdev.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+#include <linux/dma-mapping.h>
+#include <linux/broadcom/vc_mem.h>
+#include <linux/compat.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/dma-bcm2708.h>
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
+#define DRIVER_NAME  "vc-mem"
+
+/* N.B. These use a different magic value for compatibility with bmc7208_fb */
+#define VC_MEM_IOC_DMACOPY   _IOW('z', 0x22, struct vc_mem_dmacopy)
+#define VC_MEM_IOC_DMACOPY32 _IOW('z', 0x22, struct vc_mem_dmacopy32)
+
+/* address with no aliases */
+#define INTALIAS_NORMAL(x) ((x) & ~0xc0000000)
+/* cache coherent but non-allocating in L1 and L2 */
+#define INTALIAS_L1L2_NONALLOCATING(x) (((x) & ~0xc0000000) | 0x80000000)
+
+/* Device (/dev) related variables */
+static dev_t vc_mem_devnum;
+static struct class *vc_mem_class;
+static struct cdev vc_mem_cdev;
+static int vc_mem_inited;
+
+#ifdef CONFIG_DEBUG_FS
+static struct dentry *vc_mem_debugfs_entry;
+#endif
+
+struct vc_mem_dmacopy {
+	void *dst;
+	__u32 src;
+	__u32 length;
+};
+
+#ifdef CONFIG_COMPAT
+struct vc_mem_dmacopy32 {
+	compat_uptr_t dst;
+	__u32 src;
+	__u32 length;
+};
+#endif
+
+/*
+ * Videocore memory addresses and size
+ *
+ * Drivers that wish to know the videocore memory addresses and sizes should
+ * use these variables instead of the MM_IO_BASE and MM_ADDR_IO defines in
+ * headers. This allows the other drivers to not be tied down to a a certain
+ * address/size at compile time.
+ *
+ * In the future, the goal is to have the videocore memory virtual address and
+ * size be calculated at boot time rather than at compile time. The decision of
+ * where the videocore memory resides and its size would be in the hands of the
+ * bootloader (and/or kernel). When that happens, the values of these variables
+ * would be calculated and assigned in the init function.
+ */
+/* In the 2835 VC in mapped above ARM, but ARM has full access to VC space */
+unsigned long mm_vc_mem_phys_addr;
+EXPORT_SYMBOL(mm_vc_mem_phys_addr);
+unsigned int mm_vc_mem_size;
+EXPORT_SYMBOL(mm_vc_mem_size);
+unsigned int mm_vc_mem_base;
+EXPORT_SYMBOL(mm_vc_mem_base);
+
+static uint phys_addr;
+static uint mem_size;
+static uint mem_base;
+
+struct vc_mem_dma {
+	struct device *dev;
+	int dma_chan;
+	int dma_irq;
+	void __iomem *dma_chan_base;
+	wait_queue_head_t dma_waitq;
+	void *cb_base;	/* DMA control blocks */
+	dma_addr_t cb_handle;
+};
+
+struct { u32 base, length; } gpu_mem;
+static struct mutex dma_mutex;
+static struct vc_mem_dma vc_mem_dma;
+
+static int
+vc_mem_open(struct inode *inode, struct file *file)
+{
+	(void)inode;
+
+	pr_debug("%s: called file = 0x%p\n", __func__, file);
+
+	return 0;
+}
+
+static int
+vc_mem_release(struct inode *inode, struct file *file)
+{
+	(void)inode;
+
+	pr_debug("%s: called file = 0x%p\n", __func__, file);
+
+	return 0;
+}
+
+static void
+vc_mem_get_size(void)
+{
+}
+
+static void
+vc_mem_get_base(void)
+{
+}
+
+int
+vc_mem_get_current_size(void)
+{
+	return mm_vc_mem_size;
+}
+EXPORT_SYMBOL_GPL(vc_mem_get_current_size);
+
+static int
+vc_mem_dma_init(void)
+{
+	struct vc_mem_dma *vcdma = &vc_mem_dma;
+	struct platform_device *pdev;
+	struct device_node *fwnode;
+	struct rpi_firmware *fw;
+	struct device *dev;
+	u32 revision;
+	int rc;
+
+	if (vcdma->dev)
+		return 0;
+
+	fwnode = of_find_node_by_path("/system");
+	rc = of_property_read_u32(fwnode, "linux,revision", &revision);
+	revision = (revision >> 12) & 0xf;
+	if (revision != 1 && revision != 2) {
+		/* Only BCM2709 and BCM2710 may have logs where the ARMs
+		 * can't see them.
+		 */
+		return -ENXIO;
+	}
+
+	fwnode = rpi_firmware_find_node();
+	if (!fwnode)
+		return -ENXIO;
+
+	pdev = of_find_device_by_node(fwnode);
+	dev = &pdev->dev;
+
+	rc = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32));
+	if (rc)
+		return rc;
+
+	fw = rpi_firmware_get(fwnode);
+	if (!fw)
+		return -ENXIO;
+	rc = rpi_firmware_property(fw, RPI_FIRMWARE_GET_VC_MEMORY,
+				   &gpu_mem, sizeof(gpu_mem));
+	if (rc)
+		return rc;
+
+	gpu_mem.base = INTALIAS_NORMAL(gpu_mem.base);
+
+	if (!gpu_mem.base || !gpu_mem.length) {
+		dev_err(dev, "%s: unable to determine gpu memory (%x,%x)\n",
+			__func__, gpu_mem.base, gpu_mem.length);
+		return -EFAULT;
+	}
+
+	vcdma->cb_base = dma_alloc_wc(dev, SZ_4K, &vcdma->cb_handle, GFP_KERNEL);
+	if (!vcdma->cb_base) {
+		dev_err(dev, "failed to allocate DMA CBs\n");
+		return -ENOMEM;
+	}
+
+	rc = bcm_dma_chan_alloc(BCM_DMA_FEATURE_BULK,
+				&vcdma->dma_chan_base,
+				&vcdma->dma_irq);
+	if (rc < 0) {
+		dev_err(dev, "failed to allocate a DMA channel\n");
+		goto free_cb;
+	}
+
+	vcdma->dma_chan = rc;
+
+	init_waitqueue_head(&vcdma->dma_waitq);
+
+	vcdma->dev = dev;
+
+	return 0;
+
+free_cb:
+	dma_free_wc(dev, SZ_4K, vcdma->cb_base, vcdma->cb_handle);
+
+	return rc;
+}
+
+static void
+vc_mem_dma_uninit(void)
+{
+	struct vc_mem_dma *vcdma = &vc_mem_dma;
+
+	if (vcdma->dev) {
+		bcm_dma_chan_free(vcdma->dma_chan);
+		dma_free_wc(vcdma->dev, SZ_4K, vcdma->cb_base, vcdma->cb_handle);
+		vcdma->dev = NULL;
+	}
+}
+
+static int dma_memcpy(struct vc_mem_dma *vcdma, dma_addr_t dst, dma_addr_t src,
+		      int size)
+{
+	struct bcm2708_dma_cb *cb = vcdma->cb_base;
+	int burst_size = (vcdma->dma_chan == 0) ? 8 : 2;
+
+	cb->info = BCM2708_DMA_BURST(burst_size) | BCM2708_DMA_S_WIDTH |
+		   BCM2708_DMA_S_INC | BCM2708_DMA_D_WIDTH |
+		   BCM2708_DMA_D_INC;
+	cb->dst = dst;
+	cb->src = src;
+	cb->length = size;
+	cb->stride = 0;
+	cb->pad[0] = 0;
+	cb->pad[1] = 0;
+	cb->next = 0;
+
+	bcm_dma_start(vcdma->dma_chan_base, vcdma->cb_handle);
+	bcm_dma_wait_idle(vcdma->dma_chan_base);
+
+	return 0;
+}
+
+static long vc_mem_copy(struct vc_mem_dmacopy *ioparam)
+{
+	struct vc_mem_dma *vcdma = &vc_mem_dma;
+	size_t size = PAGE_SIZE;
+	const u32 dma_xfer_chunk = 256;
+	u32 *buf = NULL;
+	dma_addr_t bus_addr;
+	long rc = 0;
+	size_t offset;
+
+	/* restrict this to root user */
+	if (!uid_eq(current_euid(), GLOBAL_ROOT_UID))
+		return -EFAULT;
+
+	if (mutex_lock_interruptible(&dma_mutex))
+		return -EINTR;
+
+	rc = vc_mem_dma_init();
+	if (rc)
+		goto out;
+
+	vcdma = &vc_mem_dma;
+
+	if (INTALIAS_NORMAL(ioparam->src) < gpu_mem.base ||
+	    INTALIAS_NORMAL(ioparam->src) >= gpu_mem.base + gpu_mem.length) {
+		pr_err("%s: invalid memory access %x (%x-%x)", __func__,
+		       INTALIAS_NORMAL(ioparam->src), gpu_mem.base,
+		       gpu_mem.base + gpu_mem.length);
+		rc = -EFAULT;
+		goto out;
+	}
+
+	buf = dma_alloc_coherent(vcdma->dev, PAGE_ALIGN(size), &bus_addr,
+				 GFP_ATOMIC);
+	if (!buf) {
+		rc = -ENOMEM;
+		goto out;
+	}
+
+	for (offset = 0; offset < ioparam->length; offset += size) {
+		size_t remaining = ioparam->length - offset;
+		size_t s = min(size, remaining);
+		u8 *p = (u8 *)((uintptr_t)ioparam->src + offset);
+		u8 *q = (u8 *)ioparam->dst + offset;
+
+		rc = dma_memcpy(vcdma, bus_addr,
+				INTALIAS_L1L2_NONALLOCATING((u32)(uintptr_t)p),
+				(s + dma_xfer_chunk - 1) & ~(dma_xfer_chunk - 1));
+		if (rc) {
+			dev_err(vcdma->dev, "dma_memcpy failed\n");
+			break;
+		}
+		if (copy_to_user(q, buf, s) != 0) {
+			pr_err("%s: copy_to_user failed\n", __func__);
+			rc = -EFAULT;
+			break;
+		}
+	}
+
+out:
+	if (buf)
+		dma_free_coherent(vcdma->dev, PAGE_ALIGN(size), buf,
+				  bus_addr);
+
+	mutex_unlock(&dma_mutex);
+
+	return rc;
+}
+
+static long
+vc_mem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	int rc = 0;
+
+	(void) cmd;
+	(void) arg;
+
+	pr_debug("%s: called file = 0x%p, cmd %08x\n", __func__, file, cmd);
+
+	switch (cmd) {
+	case VC_MEM_IOC_MEM_PHYS_ADDR:
+		{
+			pr_debug("%s: VC_MEM_IOC_MEM_PHYS_ADDR=0x%p\n",
+				__func__, (void *)mm_vc_mem_phys_addr);
+
+			if (copy_to_user((void *)arg, &mm_vc_mem_phys_addr,
+					 sizeof(mm_vc_mem_phys_addr))) {
+				rc = -EFAULT;
+			}
+			break;
+		}
+	case VC_MEM_IOC_MEM_SIZE:
+		{
+			/* Get the videocore memory size first */
+			vc_mem_get_size();
+
+			pr_debug("%s: VC_MEM_IOC_MEM_SIZE=%x\n", __func__,
+				 mm_vc_mem_size);
+
+			if (copy_to_user((void *)arg, &mm_vc_mem_size,
+					 sizeof(mm_vc_mem_size))) {
+				rc = -EFAULT;
+			}
+			break;
+		}
+	case VC_MEM_IOC_MEM_BASE:
+		{
+			/* Get the videocore memory base */
+			vc_mem_get_base();
+
+			pr_debug("%s: VC_MEM_IOC_MEM_BASE=%x\n", __func__,
+				 mm_vc_mem_base);
+
+			if (copy_to_user((void *)arg, &mm_vc_mem_base,
+					 sizeof(mm_vc_mem_base))) {
+				rc = -EFAULT;
+			}
+			break;
+		}
+	case VC_MEM_IOC_MEM_LOAD:
+		{
+			/* Get the videocore memory base */
+			vc_mem_get_base();
+
+			pr_debug("%s: VC_MEM_IOC_MEM_LOAD=%x\n", __func__,
+				mm_vc_mem_base);
+
+			if (copy_to_user((void *)arg, &mm_vc_mem_base,
+					 sizeof(mm_vc_mem_base))) {
+				rc = -EFAULT;
+			}
+			break;
+		}
+	case VC_MEM_IOC_DMACOPY:
+		{
+			struct vc_mem_dmacopy ioparam;
+			/* Get the parameter data.
+			 */
+			if (copy_from_user
+			    (&ioparam, (void *)arg, sizeof(ioparam))) {
+				pr_err("%s: copy_from_user failed\n", __func__);
+				rc = -EFAULT;
+				break;
+			}
+
+			rc = vc_mem_copy(&ioparam);
+			break;
+		}
+	default:
+		{
+			return -ENOTTY;
+		}
+	}
+	pr_debug("%s: file = 0x%p returning %d\n", __func__, file, rc);
+
+	return rc;
+}
+
+#ifdef CONFIG_COMPAT
+static long
+vc_mem_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	int rc = 0;
+
+	switch (cmd) {
+	case VC_MEM_IOC_MEM_PHYS_ADDR32:
+		pr_debug("%s: VC_MEM_IOC_MEM_PHYS_ADDR32=0x%p\n",
+			 __func__, (void *)mm_vc_mem_phys_addr);
+
+		/* This isn't correct, but will cover us for now as
+		 * VideoCore is 32bit only.
+		 */
+		if (copy_to_user((void *)arg, &mm_vc_mem_phys_addr,
+				 sizeof(compat_ulong_t)))
+			rc = -EFAULT;
+
+		break;
+
+	case VC_MEM_IOC_DMACOPY32:
+	{
+		struct vc_mem_dmacopy32 param32;
+		struct vc_mem_dmacopy param;
+		/* Get the parameter data.
+		 */
+		if (copy_from_user(&param32, (void *)arg, sizeof(param32))) {
+			pr_err("%s: copy_from_user failed\n", __func__);
+			rc = -EFAULT;
+			break;
+		}
+		param.dst = compat_ptr(param32.dst);
+		param.src = param32.src;
+		param.length = param32.length;
+		rc = vc_mem_copy(&param);
+		break;
+	}
+
+	default:
+		rc = vc_mem_ioctl(file, cmd, arg);
+		break;
+	}
+
+	return rc;
+}
+#endif
+
+static int
+vc_mem_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+	int rc = 0;
+	unsigned long length = vma->vm_end - vma->vm_start;
+	unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+
+	pr_debug("%s: vm_start = 0x%08lx vm_end = 0x%08lx vm_pgoff = 0x%08lx\n",
+		 __func__, (long)vma->vm_start, (long)vma->vm_end,
+		 (long)vma->vm_pgoff);
+
+	if (offset + length > mm_vc_mem_size) {
+		pr_err("%s: length %ld is too big\n", __func__, length);
+		return -EINVAL;
+	}
+	/* Do not cache the memory map */
+	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+
+	rc = remap_pfn_range(vma, vma->vm_start,
+			     (mm_vc_mem_phys_addr >> PAGE_SHIFT) +
+			     vma->vm_pgoff, length, vma->vm_page_prot);
+	if (rc)
+		pr_err("%s: remap_pfn_range failed (rc=%d)\n", __func__, rc);
+
+	return rc;
+}
+
+/* File Operations for the driver. */
+static const struct file_operations vc_mem_fops = {
+	.owner = THIS_MODULE,
+	.open = vc_mem_open,
+	.release = vc_mem_release,
+	.unlocked_ioctl = vc_mem_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = vc_mem_compat_ioctl,
+#endif
+	.mmap = vc_mem_mmap,
+};
+
+#ifdef CONFIG_DEBUG_FS
+static void vc_mem_debugfs_deinit(void)
+{
+	debugfs_remove_recursive(vc_mem_debugfs_entry);
+	vc_mem_debugfs_entry = NULL;
+}
+
+
+static int vc_mem_debugfs_init(
+	struct device *dev)
+{
+	vc_mem_debugfs_entry = debugfs_create_dir(DRIVER_NAME, NULL);
+	if (!vc_mem_debugfs_entry) {
+		dev_warn(dev, "could not create debugfs entry\n");
+		return -EFAULT;
+	}
+
+	debugfs_create_x32("vc_mem_phys_addr",
+				0444,
+				vc_mem_debugfs_entry,
+				(u32 *)&mm_vc_mem_phys_addr);
+	debugfs_create_x32("vc_mem_size",
+				0444,
+				vc_mem_debugfs_entry,
+				(u32 *)&mm_vc_mem_size);
+	debugfs_create_x32("vc_mem_base",
+				0444,
+				vc_mem_debugfs_entry,
+				(u32 *)&mm_vc_mem_base);
+
+	return 0;
+}
+
+#endif /* CONFIG_DEBUG_FS */
+
+/* Module load/unload functions */
+
+static int __init
+vc_mem_init(void)
+{
+	int rc = -EFAULT;
+	struct device *dev;
+
+	pr_debug("%s: called\n", __func__);
+
+	mm_vc_mem_phys_addr = phys_addr;
+	mm_vc_mem_size = mem_size;
+	mm_vc_mem_base = mem_base;
+
+	vc_mem_get_size();
+
+	pr_info("vc-mem: phys_addr:0x%08lx mem_base=0x%08x mem_size:0x%08x(%u MiB)\n",
+		mm_vc_mem_phys_addr, mm_vc_mem_base, mm_vc_mem_size,
+		mm_vc_mem_size / (1024 * 1024));
+
+	rc = alloc_chrdev_region(&vc_mem_devnum, 0, 1, DRIVER_NAME);
+	if (rc < 0) {
+		pr_err("%s: alloc_chrdev_region failed (rc=%d)\n",
+		       __func__, rc);
+		goto out_err;
+	}
+
+	cdev_init(&vc_mem_cdev, &vc_mem_fops);
+	rc = cdev_add(&vc_mem_cdev, vc_mem_devnum, 1);
+	if (rc) {
+		pr_err("%s: cdev_add failed (rc=%d)\n", __func__, rc);
+		goto out_unregister;
+	}
+
+	vc_mem_class = class_create(DRIVER_NAME);
+	if (IS_ERR(vc_mem_class)) {
+		rc = PTR_ERR(vc_mem_class);
+		pr_err("%s: class_create failed (rc=%d)\n", __func__, rc);
+		goto out_cdev_del;
+	}
+
+	dev = device_create(vc_mem_class, NULL, vc_mem_devnum, NULL,
+			    DRIVER_NAME);
+	if (IS_ERR(dev)) {
+		rc = PTR_ERR(dev);
+		pr_err("%s: device_create failed (rc=%d)\n", __func__, rc);
+		goto out_class_destroy;
+	}
+
+#ifdef CONFIG_DEBUG_FS
+	/* don't fail if the debug entries cannot be created */
+	vc_mem_debugfs_init(dev);
+#endif
+
+	mutex_init(&dma_mutex);
+	vc_mem_inited = 1;
+	return 0;
+
+out_class_destroy:
+	class_destroy(vc_mem_class);
+	vc_mem_class = NULL;
+
+out_cdev_del:
+	cdev_del(&vc_mem_cdev);
+
+out_unregister:
+	unregister_chrdev_region(vc_mem_devnum, 1);
+
+out_err:
+	return -1;
+}
+
+static void __exit
+vc_mem_exit(void)
+{
+	pr_debug("%s: called\n", __func__);
+
+	vc_mem_dma_uninit();
+	if (vc_mem_inited) {
+#ifdef CONFIG_DEBUG_FS
+		vc_mem_debugfs_deinit();
+#endif
+		device_destroy(vc_mem_class, vc_mem_devnum);
+		class_destroy(vc_mem_class);
+		cdev_del(&vc_mem_cdev);
+		unregister_chrdev_region(vc_mem_devnum, 1);
+		vc_mem_inited = 0;
+	}
+}
+
+module_init(vc_mem_init);
+module_exit(vc_mem_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Broadcom Corporation");
+
+module_param(phys_addr, uint, 0644);
+module_param(mem_size, uint, 0644);
+module_param(mem_base, uint, 0644);
diff --git a/drivers/char/broadcom/vcio.c b/drivers/char/broadcom/vcio.c
new file mode 100644
index 000000000000..9db2408c781a
--- /dev/null
+++ b/drivers/char/broadcom/vcio.c
@@ -0,0 +1,187 @@
+/*
+ *  Copyright (C) 2010 Broadcom
+ *  Copyright (C) 2015 Noralf Trønnes
+ *  Copyright (C) 2021 Raspberry Pi (Trading) Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/ioctl.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/compat.h>
+#include <linux/miscdevice.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
+#define MODULE_NAME "vcio"
+#define VCIO_IOC_MAGIC 100
+#define IOCTL_MBOX_PROPERTY _IOWR(VCIO_IOC_MAGIC, 0, char *)
+#ifdef CONFIG_COMPAT
+#define IOCTL_MBOX_PROPERTY32 _IOWR(VCIO_IOC_MAGIC, 0, compat_uptr_t)
+#endif
+
+struct vcio_data {
+	struct rpi_firmware *fw;
+	struct miscdevice misc_dev;
+};
+
+static int vcio_user_property_list(struct vcio_data *vcio, void *user)
+{
+	u32 *buf, size;
+	int ret;
+
+	/* The first 32-bit is the size of the buffer */
+	if (copy_from_user(&size, user, sizeof(size)))
+		return -EFAULT;
+
+	buf = kmalloc(size, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	if (copy_from_user(buf, user, size)) {
+		kfree(buf);
+		return -EFAULT;
+	}
+
+	/* Strip off protocol encapsulation */
+	ret = rpi_firmware_property_list(vcio->fw, &buf[2], size - 12);
+	if (ret) {
+		kfree(buf);
+		return ret;
+	}
+
+	buf[1] = RPI_FIRMWARE_STATUS_SUCCESS;
+	if (copy_to_user(user, buf, size))
+		ret = -EFAULT;
+
+	kfree(buf);
+
+	return ret;
+}
+
+static int vcio_device_open(struct inode *inode, struct file *file)
+{
+	try_module_get(THIS_MODULE);
+
+	return 0;
+}
+
+static int vcio_device_release(struct inode *inode, struct file *file)
+{
+	module_put(THIS_MODULE);
+
+	return 0;
+}
+
+static long vcio_device_ioctl(struct file *file, unsigned int ioctl_num,
+			      unsigned long ioctl_param)
+{
+	struct vcio_data *vcio = container_of(file->private_data,
+					      struct vcio_data, misc_dev);
+
+	switch (ioctl_num) {
+	case IOCTL_MBOX_PROPERTY:
+		return vcio_user_property_list(vcio, (void *)ioctl_param);
+	default:
+		pr_err("unknown ioctl: %x\n", ioctl_num);
+		return -EINVAL;
+	}
+}
+
+#ifdef CONFIG_COMPAT
+static long vcio_device_compat_ioctl(struct file *file, unsigned int ioctl_num,
+				     unsigned long ioctl_param)
+{
+	struct vcio_data *vcio = container_of(file->private_data,
+					      struct vcio_data, misc_dev);
+
+	switch (ioctl_num) {
+	case IOCTL_MBOX_PROPERTY32:
+		return vcio_user_property_list(vcio, compat_ptr(ioctl_param));
+	default:
+		pr_err("unknown ioctl: %x\n", ioctl_num);
+		return -EINVAL;
+	}
+}
+#endif
+
+const struct file_operations vcio_fops = {
+	.unlocked_ioctl = vcio_device_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = vcio_device_compat_ioctl,
+#endif
+	.open = vcio_device_open,
+	.release = vcio_device_release,
+};
+
+static int vcio_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	struct device_node *fw_node;
+	struct rpi_firmware *fw;
+	struct vcio_data *vcio;
+
+	fw_node = of_get_parent(np);
+	if (!fw_node) {
+		dev_err(dev, "Missing firmware node\n");
+		return -ENOENT;
+	}
+
+	fw = rpi_firmware_get(fw_node);
+	of_node_put(fw_node);
+	if (!fw)
+		return -EPROBE_DEFER;
+
+	vcio = devm_kzalloc(dev, sizeof(struct vcio_data), GFP_KERNEL);
+	if (!vcio)
+		return -ENOMEM;
+
+	vcio->fw = fw;
+	vcio->misc_dev.fops = &vcio_fops;
+	vcio->misc_dev.minor = MISC_DYNAMIC_MINOR;
+	vcio->misc_dev.name = "vcio";
+	vcio->misc_dev.parent = dev;
+
+	return misc_register(&vcio->misc_dev);
+}
+
+static void vcio_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+
+	misc_deregister(dev_get_drvdata(dev));
+}
+
+static const struct of_device_id vcio_ids[] = {
+	{ .compatible = "raspberrypi,vcio" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, vcio_ids);
+
+static struct platform_driver vcio_driver = {
+	.driver	= {
+		.name		= MODULE_NAME,
+		.of_match_table	= of_match_ptr(vcio_ids),
+	},
+	.probe	= vcio_probe,
+	.remove = vcio_remove,
+};
+
+module_platform_driver(vcio_driver);
+
+MODULE_AUTHOR("Gray Girling");
+MODULE_AUTHOR("Noralf Trønnes");
+MODULE_DESCRIPTION("Mailbox userspace access");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:rpi-vcio");
diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig
index f0dde77f50b4..26ca14ad1989 100644
--- a/drivers/char/hw_random/Kconfig
+++ b/drivers/char/hw_random/Kconfig
@@ -105,7 +105,7 @@ config HW_RANDOM_IPROC_RNG200
 	default HW_RANDOM
 	help
 	  This driver provides kernel-side support for the RNG200
-	  hardware found on the Broadcom iProc and STB SoCs.
+	  hardware found on the Broadcom iProc, BCM2711 and STB SoCs.
 
 	  To compile this driver as a module, choose M here: the
 	  module will be called iproc-rng200
diff --git a/drivers/char/hw_random/bcm2835-rng.c b/drivers/char/hw_random/bcm2835-rng.c
index aa2b135e3ee2..05a4569ce28f 100644
--- a/drivers/char/hw_random/bcm2835-rng.c
+++ b/drivers/char/hw_random/bcm2835-rng.c
@@ -13,6 +13,7 @@
 #include <linux/printk.h>
 #include <linux/clk.h>
 #include <linux/reset.h>
+#include <linux/delay.h>
 
 #define RNG_CTRL	0x0
 #define RNG_STATUS	0x4
@@ -27,6 +28,9 @@
 
 #define RNG_INT_OFF	0x1
 
+#define RNG_FIFO_WORDS	4
+#define RNG_US_PER_WORD	34 /* Tuned for throughput */
+
 struct bcm2835_rng_priv {
 	struct hwrng rng;
 	void __iomem *base;
@@ -63,19 +67,23 @@ static inline void rng_writel(struct bcm2835_rng_priv *priv, u32 val,
 static int bcm2835_rng_read(struct hwrng *rng, void *buf, size_t max,
 			       bool wait)
 {
+	u32 retries = 1000000/(RNG_FIFO_WORDS * RNG_US_PER_WORD);
 	struct bcm2835_rng_priv *priv = to_rng_priv(rng);
 	u32 max_words = max / sizeof(u32);
 	u32 num_words, count;
 
-	while ((rng_readl(priv, RNG_STATUS) >> 24) == 0) {
-		if (!wait)
+	num_words = rng_readl(priv, RNG_STATUS) >> 24;
+
+	while (!num_words) {
+		if (!wait || !retries)
 			return 0;
-		hwrng_yield(rng);
+		retries--;
+		usleep_range((u32)RNG_US_PER_WORD,
+			     (u32)RNG_US_PER_WORD * RNG_FIFO_WORDS);
+		num_words = rng_readl(priv, RNG_STATUS) >> 24;
 	}
 
-	num_words = rng_readl(priv, RNG_STATUS) >> 24;
-	if (num_words > max_words)
-		num_words = max_words;
+	num_words = min(num_words, max_words);
 
 	for (count = 0; count < num_words; count++)
 		((u32 *)buf)[count] = rng_readl(priv, RNG_DATA);
@@ -107,8 +115,10 @@ static int bcm2835_rng_init(struct hwrng *rng)
 	}
 
 	/* set warm-up count & enable */
-	rng_writel(priv, RNG_WARMUP_COUNT, RNG_STATUS);
-	rng_writel(priv, RNG_RBGEN, RNG_CTRL);
+	if (!(rng_readl(priv, RNG_CTRL) & RNG_RBGEN)) {
+		rng_writel(priv, RNG_WARMUP_COUNT, RNG_STATUS);
+		rng_writel(priv, RNG_RBGEN, RNG_CTRL);
+	}
 
 	return ret;
 }
diff --git a/drivers/char/hw_random/iproc-rng200.c b/drivers/char/hw_random/iproc-rng200.c
index 440fe28bddc0..33bc28f429f6 100644
--- a/drivers/char/hw_random/iproc-rng200.c
+++ b/drivers/char/hw_random/iproc-rng200.c
@@ -13,6 +13,7 @@
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/mod_devicetable.h>
+#include <linux/of.h>
 #include <linux/platform_device.h>
 #include <linux/delay.h>
 
@@ -20,6 +21,7 @@
 #define RNG_CTRL_OFFSET					0x00
 #define RNG_CTRL_RNG_RBGEN_MASK				0x00001FFF
 #define RNG_CTRL_RNG_RBGEN_ENABLE			0x00000001
+#define RNG_CTRL_RNG_DIV_CTRL_SHIFT			13
 
 #define RNG_SOFT_RESET_OFFSET				0x04
 #define RNG_SOFT_RESET					0x00000001
@@ -27,16 +29,23 @@
 #define RBG_SOFT_RESET_OFFSET				0x08
 #define RBG_SOFT_RESET					0x00000001
 
+#define RNG_TOTAL_BIT_COUNT_OFFSET			0x0C
+
+#define RNG_TOTAL_BIT_COUNT_THRESHOLD_OFFSET		0x10
+
 #define RNG_INT_STATUS_OFFSET				0x18
 #define RNG_INT_STATUS_MASTER_FAIL_LOCKOUT_IRQ_MASK	0x80000000
 #define RNG_INT_STATUS_STARTUP_TRANSITIONS_MET_IRQ_MASK	0x00020000
 #define RNG_INT_STATUS_NIST_FAIL_IRQ_MASK		0x00000020
 #define RNG_INT_STATUS_TOTAL_BITS_COUNT_IRQ_MASK	0x00000001
 
+#define RNG_INT_ENABLE_OFFSET				0x1C
+
 #define RNG_FIFO_DATA_OFFSET				0x20
 
 #define RNG_FIFO_COUNT_OFFSET				0x24
 #define RNG_FIFO_COUNT_RNG_FIFO_COUNT_MASK		0x000000FF
+#define RNG_FIFO_COUNT_RNG_FIFO_THRESHOLD_SHIFT		8
 
 struct iproc_rng200_dev {
 	struct hwrng rng;
@@ -157,6 +166,64 @@ static int iproc_rng200_init(struct hwrng *rng)
 	return 0;
 }
 
+static int bcm2711_rng200_read(struct hwrng *rng, void *buf, size_t max,
+			       bool wait)
+{
+	struct iproc_rng200_dev *priv = to_rng_priv(rng);
+	u32 max_words = max / sizeof(u32);
+	u32 num_words, count, val;
+
+	/* ensure warm up period has elapsed */
+	while (1) {
+		val = ioread32(priv->base + RNG_TOTAL_BIT_COUNT_OFFSET);
+		if (val > 16)
+			break;
+		cpu_relax();
+	}
+
+	/* ensure fifo is not empty */
+	while (1) {
+		num_words = ioread32(priv->base + RNG_FIFO_COUNT_OFFSET) &
+			    RNG_FIFO_COUNT_RNG_FIFO_COUNT_MASK;
+		if (num_words)
+			break;
+		if (!wait)
+			return 0;
+		cpu_relax();
+	}
+
+	if (num_words > max_words)
+		num_words = max_words;
+
+	for (count = 0; count < num_words; count++) {
+		((u32 *)buf)[count] = ioread32(priv->base +
+					       RNG_FIFO_DATA_OFFSET);
+	}
+
+	return num_words * sizeof(u32);
+}
+
+static int bcm2711_rng200_init(struct hwrng *rng)
+{
+	struct iproc_rng200_dev *priv = to_rng_priv(rng);
+	uint32_t val;
+
+	if (ioread32(priv->base + RNG_CTRL_OFFSET) & RNG_CTRL_RNG_RBGEN_MASK)
+		return 0;
+
+	/* initial numbers generated are "less random" so will be discarded */
+	val = 0x40000;
+	iowrite32(val, priv->base + RNG_TOTAL_BIT_COUNT_THRESHOLD_OFFSET);
+	/* min fifo count to generate full interrupt */
+	val = 2 << RNG_FIFO_COUNT_RNG_FIFO_THRESHOLD_SHIFT;
+	iowrite32(val, priv->base + RNG_FIFO_COUNT_OFFSET);
+	/* enable the rng - 1Mhz sample rate */
+	val = (0x3 << RNG_CTRL_RNG_DIV_CTRL_SHIFT) | RNG_CTRL_RNG_RBGEN_MASK;
+	iowrite32(val, priv->base + RNG_CTRL_OFFSET);
+
+	return 0;
+}
+
 static void iproc_rng200_cleanup(struct hwrng *rng)
 {
 	struct iproc_rng200_dev *priv = to_rng_priv(rng);
@@ -183,11 +250,17 @@ static int iproc_rng200_probe(struct platform_device *pdev)
 
 	dev_set_drvdata(dev, priv);
 
-	priv->rng.name = "iproc-rng200";
-	priv->rng.read = iproc_rng200_read;
-	priv->rng.init = iproc_rng200_init;
+	priv->rng.name = pdev->name;
 	priv->rng.cleanup = iproc_rng200_cleanup;
 
+	if (of_device_is_compatible(dev->of_node, "brcm,bcm2711-rng200")) {
+		priv->rng.init = bcm2711_rng200_init;
+		priv->rng.read = bcm2711_rng200_read;
+	} else {
+		priv->rng.init = iproc_rng200_init;
+		priv->rng.read = iproc_rng200_read;
+	}
+
 	/* Register driver */
 	ret = devm_hwrng_register(dev, &priv->rng);
 	if (ret) {
diff --git a/drivers/char/random.c b/drivers/char/random.c
index a23c8b2feab6..d18818b5749a 100644
--- a/drivers/char/random.c
+++ b/drivers/char/random.c
@@ -857,6 +857,14 @@ void __init random_init_early(const char *command_line)
 	unsigned long entropy[BLAKE2S_BLOCK_SIZE / sizeof(long)];
 	size_t i, longs, arch_bits;
 
+	/*
+	 * If we were initialized by the bootloader before jump labels are
+	 * initialized, then we should enable the static branch here, where
+	 * it's guaranteed that jump labels have been initialized.
+	 */
+	if (!static_branch_likely(&crng_is_ready) && crng_init >= CRNG_READY)
+		crng_set_ready(NULL);
+
 #if defined(LATENT_ENTROPY_PLUGIN)
 	static const u8 compiletime_seed[BLAKE2S_BLOCK_SIZE] __initconst __latent_entropy;
 	_mix_pool_bytes(compiletime_seed, sizeof(compiletime_seed));
diff --git a/drivers/char/raspberrypi-gpiomem.c b/drivers/char/raspberrypi-gpiomem.c
new file mode 100644
index 000000000000..0f9b15bc14a8
--- /dev/null
+++ b/drivers/char/raspberrypi-gpiomem.c
@@ -0,0 +1,275 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/**
+ * raspberrypi-gpiomem.c
+ *
+ * Provides MMIO access to discontiguous section of Device memory as a linear
+ * user mapping. Successor to bcm2835-gpiomem.c.
+ *
+ * Copyright (c) 2023, Raspberry Pi Ltd.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/cdev.h>
+#include <linux/pagemap.h>
+#include <linux/io.h>
+
+#define DRIVER_NAME "rpi-gpiomem"
+#define DEVICE_MINOR 0
+
+/*
+ * Sensible max for a hypothetical "gpio" controller that splits pads,
+ * IO controls, GPIO in/out/enable, and function selection into different
+ * ranges. Most use only one or two.
+ */
+#define MAX_RANGES 4
+
+struct io_windows {
+	unsigned long phys_base;
+	unsigned long len;
+};
+
+struct rpi_gpiomem_priv {
+	dev_t devid;
+	struct class *class;
+	struct cdev rpi_gpiomem_cdev;
+	struct device *dev;
+	const char *name;
+	unsigned int nr_wins;
+	struct io_windows iowins[4];
+};
+
+static int rpi_gpiomem_open(struct inode *inode, struct file *file)
+{
+	int dev = iminor(inode);
+	int ret = 0;
+	struct rpi_gpiomem_priv *priv;
+
+	if (dev != DEVICE_MINOR)
+		ret = -ENXIO;
+
+	priv = container_of(inode->i_cdev, struct rpi_gpiomem_priv,
+				rpi_gpiomem_cdev);
+	if (!priv)
+		return -EINVAL;
+	file->private_data = priv;
+	return ret;
+}
+
+static int rpi_gpiomem_release(struct inode *inode, struct file *file)
+{
+	int dev = iminor(inode);
+	int ret = 0;
+
+	if (dev != DEVICE_MINOR)
+		ret = -ENXIO;
+
+	return ret;
+}
+
+static const struct vm_operations_struct rpi_gpiomem_vm_ops = {
+#ifdef CONFIG_HAVE_IOREMAP_PROT
+	.access = generic_access_phys
+#endif
+};
+
+static int rpi_gpiomem_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	int i;
+	struct rpi_gpiomem_priv *priv;
+	unsigned long base;
+	unsigned long len = 0;
+	unsigned long offset;
+
+	priv = file->private_data;
+	/*
+	 * Userspace must provide a virtual address space at least
+	 * the size of the concatenated ranges.
+	 */
+	for (i = 0; i < priv->nr_wins; i++)
+		len += priv->iowins[i].len;
+	if (len > vma->vm_end - vma->vm_start + 1)
+		return -EINVAL;
+
+	vma->vm_ops = &rpi_gpiomem_vm_ops;
+	offset = vma->vm_start;
+	for (i = 0; i < priv->nr_wins; i++) {
+		base = priv->iowins[i].phys_base >> PAGE_SHIFT;
+		len = priv->iowins[i].len;
+		vma->vm_page_prot = phys_mem_access_prot(file, base, len,
+							 vma->vm_page_prot);
+		if (remap_pfn_range(vma, offset,
+			    base, len,
+			    vma->vm_page_prot))
+			break;
+		offset += len;
+	}
+
+	if (i < priv->nr_wins)
+		return -EAGAIN;
+
+	return 0;
+}
+
+static const struct file_operations rpi_gpiomem_fops = {
+	.owner = THIS_MODULE,
+	.open = rpi_gpiomem_open,
+	.release = rpi_gpiomem_release,
+	.mmap = rpi_gpiomem_mmap,
+};
+
+static const struct of_device_id rpi_gpiomem_of_match[];
+
+static int rpi_gpiomem_probe(struct platform_device *pdev)
+{
+	int err, i;
+	const struct of_device_id *id;
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct resource *ioresource;
+	struct rpi_gpiomem_priv *priv;
+
+	/* Allocate buffers and instance data */
+
+	priv = kzalloc(sizeof(struct rpi_gpiomem_priv), GFP_KERNEL);
+
+	if (!priv) {
+		err = -ENOMEM;
+		goto failed_inst_alloc;
+	}
+	platform_set_drvdata(pdev, priv);
+
+	priv->dev = dev;
+	id = of_match_device(rpi_gpiomem_of_match, dev);
+	if (!id)
+		return -EINVAL;
+
+	/*
+	 * Device node naming - for legacy (bcm2835) DT bindings, the driver
+	 * created the node based on a hardcoded name - for new bindings,
+	 * take the node name from DT.
+	 */
+	if (id == &rpi_gpiomem_of_match[0]) {
+		priv->name = "gpiomem";
+	} else {
+		err = of_property_read_string(node, "chardev-name", &priv->name);
+		if (err)
+			return -EINVAL;
+	}
+
+	/*
+	 * Go find the register ranges associated with this instance
+	 */
+	for (i = 0; i < MAX_RANGES; i++) {
+		ioresource = platform_get_resource(pdev, IORESOURCE_MEM, i);
+		if (!ioresource && i == 0) {
+			dev_err(priv->dev, "failed to get IO resource - no ranges available\n");
+			err = -ENOENT;
+			goto failed_get_resource;
+		}
+		if (!ioresource)
+			break;
+
+		priv->iowins[i].phys_base = ioresource->start;
+		priv->iowins[i].len = (ioresource->end + 1) - ioresource->start;
+		dev_info(&pdev->dev, "window base 0x%08lx size 0x%08lx\n",
+			 priv->iowins[i].phys_base, priv->iowins[i].len);
+		priv->nr_wins++;
+	}
+
+	/* Create character device entries */
+
+	err = alloc_chrdev_region(&priv->devid,
+				  DEVICE_MINOR, 1, priv->name);
+	if (err != 0) {
+		dev_err(priv->dev, "unable to allocate device number");
+		goto failed_alloc_chrdev;
+	}
+	cdev_init(&priv->rpi_gpiomem_cdev, &rpi_gpiomem_fops);
+	priv->rpi_gpiomem_cdev.owner = THIS_MODULE;
+	err = cdev_add(&priv->rpi_gpiomem_cdev, priv->devid, 1);
+	if (err != 0) {
+		dev_err(priv->dev, "unable to register device");
+		goto failed_cdev_add;
+	}
+
+	/* Create sysfs entries */
+
+	priv->class = class_create(priv->name);
+	if (IS_ERR(priv->class)) {
+		err = PTR_ERR(priv->class);
+		goto failed_class_create;
+	}
+
+	dev = device_create(priv->class, NULL, priv->devid, NULL, priv->name);
+	if (IS_ERR(dev)) {
+		err = PTR_ERR(dev);
+		goto failed_device_create;
+	}
+
+	dev_info(priv->dev, "initialised %u regions as /dev/%s\n",
+		 priv->nr_wins, priv->name);
+
+	return 0;
+
+failed_device_create:
+	class_destroy(priv->class);
+failed_class_create:
+	cdev_del(&priv->rpi_gpiomem_cdev);
+failed_cdev_add:
+	unregister_chrdev_region(priv->devid, 1);
+failed_alloc_chrdev:
+failed_get_resource:
+	kfree(priv);
+failed_inst_alloc:
+	dev_err(&pdev->dev, "could not load rpi_gpiomem");
+	return err;
+}
+
+static void rpi_gpiomem_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rpi_gpiomem_priv *priv = platform_get_drvdata(pdev);
+
+	device_destroy(priv->class, priv->devid);
+	class_destroy(priv->class);
+	cdev_del(&priv->rpi_gpiomem_cdev);
+	unregister_chrdev_region(priv->devid, 1);
+	kfree(priv);
+
+	dev_info(dev, "%s driver removed - OK", priv->name);
+}
+
+static const struct of_device_id rpi_gpiomem_of_match[] = {
+	{
+		.compatible = "brcm,bcm2835-gpiomem",
+	},
+	{
+		.compatible = "raspberrypi,gpiomem",
+	},
+	{ /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, rpi_gpiomem_of_match);
+
+static struct platform_driver rpi_gpiomem_driver = {
+	.probe = rpi_gpiomem_probe,
+	.remove = rpi_gpiomem_remove,
+	.driver = {
+		   .name = DRIVER_NAME,
+		   .owner = THIS_MODULE,
+		   .of_match_table = rpi_gpiomem_of_match,
+		   },
+};
+
+module_platform_driver(rpi_gpiomem_driver);
+
+MODULE_ALIAS("platform:rpi-gpiomem");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("Driver for accessing GPIOs from userspace");
+MODULE_AUTHOR("Jonathan Bell <jonathan@raspberrypi.com>");
diff --git a/drivers/char/tpm/tpm_tis_spi_main.c b/drivers/char/tpm/tpm_tis_spi_main.c
index 61b42c83ced8..2a7dee1e1807 100644
--- a/drivers/char/tpm/tpm_tis_spi_main.c
+++ b/drivers/char/tpm/tpm_tis_spi_main.c
@@ -350,7 +350,11 @@ static struct spi_driver tpm_tis_spi_driver = {
 		.pm = &tpm_tis_pm,
 		.of_match_table = of_match_ptr(of_tis_spi_match),
 		.acpi_match_table = ACPI_PTR(acpi_tis_spi_match),
+#ifdef CONFIG_IMA
+		.probe_type = PROBE_FORCE_SYNCHRONOUS,
+#else
 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+#endif
 	},
 	.probe = tpm_tis_spi_driver_probe,
 	.remove = tpm_tis_spi_remove,
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 0fe07a594b4e..8a59e6e1cf58 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -88,6 +88,19 @@ config COMMON_CLK_RK808
 	  These multi-function devices have two fixed-rate oscillators, clocked at 32KHz each.
 	  Clkout1 is always on, Clkout2 can off by control register.
 
+config COMMON_CLK_RP1
+	tristate "Raspberry Pi RP1-based clock support"
+	depends on PCI || COMPILE_TEST
+	depends on COMMON_CLK
+	help
+	  Enable common clock framework support for Raspberry Pi RP1
+
+config COMMON_CLK_RP1_SDIO
+	tristate "Clock driver for the RP1 SDIO interfaces"
+	depends on MFD_RP1
+	help
+	  SDIO clock driver for the RP1 support chip
+
 config COMMON_CLK_HI655X
 	tristate "Clock driver for Hi655x" if EXPERT
 	depends on (MFD_HI655X_PMIC || COMPILE_TEST)
@@ -98,6 +111,12 @@ config COMMON_CLK_HI655X
 	  multi-function device has one fixed-rate oscillator, clocked
 	  at 32KHz.
 
+config COMMON_CLK_HIFIBERRY_DACPLUSHD
+	tristate
+
+config COMMON_CLK_HIFIBERRY_DACPRO
+	tristate
+
 config COMMON_CLK_SCMI
 	tristate "Clock driver controlled via SCMI interface"
 	depends on ARM_SCMI_PROTOCOL || COMPILE_TEST
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index db202b614017..1251f236e040 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -56,6 +56,8 @@ obj-$(CONFIG_COMMON_CLK_LAN966X)	+= clk-lan966x.o
 obj-$(CONFIG_COMMON_CLK_LOCHNAGAR)	+= clk-lochnagar.o
 obj-$(CONFIG_MACH_LOONGSON32)		+= clk-loongson1.o
 obj-$(CONFIG_COMMON_CLK_LOONGSON2)	+= clk-loongson2.o
+obj-$(CONFIG_COMMON_CLK_HIFIBERRY_DACPRO)	+= clk-hifiberry-dacpro.o
+obj-$(CONFIG_COMMON_CLK_HIFIBERRY_DACPLUSHD)	+= clk-hifiberry-dachd.o
 obj-$(CONFIG_COMMON_CLK_MAX77686)	+= clk-max77686.o
 obj-$(CONFIG_COMMON_CLK_MAX9485)	+= clk-max9485.o
 obj-$(CONFIG_ARCH_MILBEAUT_M10V)	+= clk-milbeaut.o
@@ -68,6 +70,8 @@ obj-$(CONFIG_CLK_LS1028A_PLLDIG)	+= clk-plldig.o
 obj-$(CONFIG_COMMON_CLK_PWM)		+= clk-pwm.o
 obj-$(CONFIG_CLK_QORIQ)			+= clk-qoriq.o
 obj-$(CONFIG_COMMON_CLK_RK808)		+= clk-rk808.o
+obj-$(CONFIG_COMMON_CLK_RP1)		+= clk-rp1.o
+obj-$(CONFIG_COMMON_CLK_RP1_SDIO)	+= clk-rp1-sdio.o
 obj-$(CONFIG_COMMON_CLK_HI655X)		+= clk-hi655x.o
 obj-$(CONFIG_COMMON_CLK_S2MPS11)	+= clk-s2mps11.o
 obj-$(CONFIG_COMMON_CLK_SCMI)           += clk-scmi.o
diff --git a/drivers/clk/bcm/clk-bcm2835.c b/drivers/clk/bcm/clk-bcm2835.c
index fb04734afc80..0ae57e1529f6 100644
--- a/drivers/clk/bcm/clk-bcm2835.c
+++ b/drivers/clk/bcm/clk-bcm2835.c
@@ -36,6 +36,7 @@
 #include <linux/platform_device.h>
 #include <linux/slab.h>
 #include <dt-bindings/clock/bcm2835.h>
+#include <soc/bcm2835/raspberrypi-firmware.h>
 
 #define CM_PASSWORD		0x5a000000
 
@@ -106,6 +107,7 @@
 #define CM_UARTDIV		0x0f4
 #define CM_VECCTL		0x0f8
 #define CM_VECDIV		0x0fc
+#define CM_DSI0HSCK		0x120
 #define CM_PULSECTL		0x190
 #define CM_PULSEDIV		0x194
 #define CM_SDCCTL		0x1a8
@@ -296,6 +298,8 @@
 #define SOC_BCM2711		BIT(1)
 #define SOC_ALL			(SOC_BCM2835 | SOC_BCM2711)
 
+#define VCMSG_ID_CORE_CLOCK     4
+
 /*
  * Names of clocks used within the driver that need to be replaced
  * with an external parent's name.  This array is in the order that
@@ -314,6 +318,7 @@ static const char *const cprman_parent_names[] = {
 struct bcm2835_cprman {
 	struct device *dev;
 	void __iomem *regs;
+	struct rpi_firmware *fw;
 	spinlock_t regs_lock; /* spinlock for all clocks */
 	unsigned int soc;
 
@@ -643,15 +648,17 @@ static int bcm2835_pll_on(struct clk_hw *hw)
 	spin_unlock(&cprman->regs_lock);
 
 	/* Wait for the PLL to lock. */
-	timeout = ktime_add_ns(ktime_get(), LOCK_TIMEOUT_NS);
-	while (!(cprman_read(cprman, CM_LOCK) & data->lock_mask)) {
-		if (ktime_after(ktime_get(), timeout)) {
-			dev_err(cprman->dev, "%s: couldn't lock PLL\n",
-				clk_hw_get_name(hw));
-			return -ETIMEDOUT;
+	if (strcmp(data->name, "pllh")) {
+		timeout = ktime_add_ns(ktime_get(), LOCK_TIMEOUT_NS);
+		while (!(cprman_read(cprman, CM_LOCK) & data->lock_mask)) {
+			if (ktime_after(ktime_get(), timeout)) {
+				dev_err(cprman->dev, "%s: couldn't lock PLL\n",
+					clk_hw_get_name(hw));
+				return -ETIMEDOUT;
+			}
+
+			cpu_relax();
 		}
-
-		cpu_relax();
 	}
 
 	cprman_write(cprman, data->a2w_ctrl_reg,
@@ -1039,6 +1046,30 @@ static unsigned long bcm2835_clock_get_rate(struct clk_hw *hw,
 	return rate;
 }
 
+static unsigned long bcm2835_clock_get_rate_vpu(struct clk_hw *hw,
+						unsigned long parent_rate)
+{
+	struct bcm2835_clock *clock = bcm2835_clock_from_hw(hw);
+	struct bcm2835_cprman *cprman = clock->cprman;
+
+	if (cprman->fw) {
+		struct {
+			u32 id;
+			u32 val;
+		} packet;
+
+		packet.id = VCMSG_ID_CORE_CLOCK;
+		packet.val = 0;
+
+		if (!rpi_firmware_property(cprman->fw,
+					   RPI_FIRMWARE_GET_MAX_CLOCK_RATE,
+					   &packet, sizeof(packet)))
+			return packet.val;
+	}
+
+	return bcm2835_clock_get_rate(hw, parent_rate);
+}
+
 static void bcm2835_clock_wait_busy(struct bcm2835_clock *clock)
 {
 	struct bcm2835_cprman *cprman = clock->cprman;
@@ -1097,8 +1128,10 @@ static int bcm2835_clock_on(struct clk_hw *hw)
 	return 0;
 }
 
-static int bcm2835_clock_set_rate(struct clk_hw *hw,
-				  unsigned long rate, unsigned long parent_rate)
+static int bcm2835_clock_set_rate_and_parent(struct clk_hw *hw,
+					     unsigned long rate,
+					     unsigned long parent_rate,
+					     u8 parent)
 {
 	struct bcm2835_clock *clock = bcm2835_clock_from_hw(hw);
 	struct bcm2835_cprman *cprman = clock->cprman;
@@ -1108,15 +1141,24 @@ static int bcm2835_clock_set_rate(struct clk_hw *hw,
 
 	spin_lock(&cprman->regs_lock);
 
-	/*
-	 * Setting up frac support
-	 *
-	 * In principle it is recommended to stop/start the clock first,
-	 * but as we set CLK_SET_RATE_GATE during registration of the
-	 * clock this requirement should be take care of by the
-	 * clk-framework.
+	ctl = cprman_read(cprman, data->ctl_reg);
+
+	/* If the clock is running, we have to pause clock generation while
+	 * updating the control and div regs.  This is glitchless (no clock
+	 * signals generated faster than the rate) but each reg access is two
+	 * OSC cycles so the clock will slow down for a moment.
 	 */
-	ctl = cprman_read(cprman, data->ctl_reg) & ~CM_FRAC;
+	if (ctl & CM_ENABLE) {
+		cprman_write(cprman, data->ctl_reg, ctl & ~CM_ENABLE);
+		bcm2835_clock_wait_busy(clock);
+	}
+
+	if (parent != 0xff) {
+		ctl &= ~(CM_SRC_MASK << CM_SRC_SHIFT);
+		ctl |= parent << CM_SRC_SHIFT;
+	}
+
+	ctl &= ~CM_FRAC;
 	ctl |= (div & CM_DIV_FRAC_MASK) ? CM_FRAC : 0;
 	cprman_write(cprman, data->ctl_reg, ctl);
 
@@ -1127,6 +1169,12 @@ static int bcm2835_clock_set_rate(struct clk_hw *hw,
 	return 0;
 }
 
+static int bcm2835_clock_set_rate(struct clk_hw *hw,
+				  unsigned long rate, unsigned long parent_rate)
+{
+	return bcm2835_clock_set_rate_and_parent(hw, rate, parent_rate, 0xff);
+}
+
 static bool
 bcm2835_clk_is_pllc(struct clk_hw *hw)
 {
@@ -1209,6 +1257,8 @@ static unsigned long bcm2835_clock_choose_div_and_prate(struct clk_hw *hw,
 static int bcm2835_clock_determine_rate(struct clk_hw *hw,
 					struct clk_rate_request *req)
 {
+	struct bcm2835_clock *clock = bcm2835_clock_from_hw(hw);
+	struct bcm2835_cprman *cprman = clock->cprman;
 	struct clk_hw *parent, *best_parent = NULL;
 	bool current_parent_is_pllc;
 	unsigned long rate, best_rate = 0;
@@ -1228,13 +1278,17 @@ static int bcm2835_clock_determine_rate(struct clk_hw *hw,
 			continue;
 
 		/*
+		 * BCM2835 only.
 		 * Don't choose a PLLC-derived clock as our parent
 		 * unless it had been manually set that way.  PLLC's
 		 * frequency gets adjusted by the firmware due to
 		 * over-temp or under-voltage conditions, without
 		 * prior notification to our clock consumer.
+		 * (BCM2711 doesn't do this, therefore it is safe to
+		 * use PLLC)
 		 */
-		if (bcm2835_clk_is_pllc(parent) && !current_parent_is_pllc)
+		if ((cprman->soc & SOC_BCM2835) && bcm2835_clk_is_pllc(parent) &&
+		    !current_parent_is_pllc)
 			continue;
 
 		rate = bcm2835_clock_choose_div_and_prate(hw, i, req->rate,
@@ -1310,6 +1364,7 @@ static const struct clk_ops bcm2835_clock_clk_ops = {
 	.unprepare = bcm2835_clock_off,
 	.recalc_rate = bcm2835_clock_get_rate,
 	.set_rate = bcm2835_clock_set_rate,
+	.set_rate_and_parent = bcm2835_clock_set_rate_and_parent,
 	.determine_rate = bcm2835_clock_determine_rate,
 	.set_parent = bcm2835_clock_set_parent,
 	.get_parent = bcm2835_clock_get_parent,
@@ -1327,7 +1382,7 @@ static int bcm2835_vpu_clock_is_on(struct clk_hw *hw)
  */
 static const struct clk_ops bcm2835_vpu_clock_clk_ops = {
 	.is_prepared = bcm2835_vpu_clock_is_on,
-	.recalc_rate = bcm2835_clock_get_rate,
+	.recalc_rate = bcm2835_clock_get_rate_vpu,
 	.set_rate = bcm2835_clock_set_rate,
 	.determine_rate = bcm2835_clock_determine_rate,
 	.set_parent = bcm2835_clock_set_parent,
@@ -1335,6 +1390,8 @@ static const struct clk_ops bcm2835_vpu_clock_clk_ops = {
 	.debug_init = bcm2835_clock_debug_init,
 };
 
+static bool bcm2835_clk_is_claimed(const char *name);
+
 static struct clk_hw *bcm2835_register_pll(struct bcm2835_cprman *cprman,
 					   const void *data)
 {
@@ -1352,6 +1409,9 @@ static struct clk_hw *bcm2835_register_pll(struct bcm2835_cprman *cprman,
 	init.ops = &bcm2835_pll_clk_ops;
 	init.flags = pll_data->flags | CLK_IGNORE_UNUSED;
 
+	if (!bcm2835_clk_is_claimed(pll_data->name))
+		init.flags |= CLK_IS_CRITICAL;
+
 	pll = kzalloc(sizeof(*pll), GFP_KERNEL);
 	if (!pll)
 		return NULL;
@@ -1407,6 +1467,13 @@ bcm2835_register_pll_divider(struct bcm2835_cprman *cprman,
 	divider->div.hw.init = &init;
 	divider->div.table = NULL;
 
+	if (!(cprman_read(cprman, divider_data->cm_reg) & divider_data->hold_mask)) {
+		if (!bcm2835_clk_is_claimed(divider_data->source_pll))
+			init.flags |= CLK_IS_CRITICAL;
+		if (!bcm2835_clk_is_claimed(divider_data->name))
+			divider->div.flags |= CLK_IS_CRITICAL;
+	}
+
 	divider->cprman = cprman;
 	divider->data = divider_data;
 
@@ -1460,6 +1527,15 @@ static struct clk_hw *bcm2835_register_clock(struct bcm2835_cprman *cprman,
 	init.name = clock_data->name;
 	init.flags = clock_data->flags | CLK_IGNORE_UNUSED;
 
+	/*
+	 * Some GPIO clocks for ethernet/wifi PLLs are marked as
+	 * critical (since some platforms use them), but if the
+	 * firmware didn't have them turned on then they clearly
+	 * aren't actually critical.
+	 */
+	if ((cprman_read(cprman, clock_data->ctl_reg) & CM_ENABLE) == 0)
+		init.flags &= ~CLK_IS_CRITICAL;
+
 	/*
 	 * Pass the CLK_SET_RATE_PARENT flag if we are allowed to propagate
 	 * rate changes on at least of the parents.
@@ -1471,7 +1547,6 @@ static struct clk_hw *bcm2835_register_clock(struct bcm2835_cprman *cprman,
 		init.ops = &bcm2835_vpu_clock_clk_ops;
 	} else {
 		init.ops = &bcm2835_clock_clk_ops;
-		init.flags |= CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
 
 		/* If the clock wasn't actually enabled at boot, it's not
 		 * critical.
@@ -1696,16 +1771,12 @@ static const struct bcm2835_clk_desc clk_desc_array[] = {
 		.hold_mask = CM_PLLA_HOLDCORE,
 		.fixed_divider = 1,
 		.flags = CLK_SET_RATE_PARENT),
-	[BCM2835_PLLA_PER]	= REGISTER_PLL_DIV(
-		SOC_ALL,
-		.name = "plla_per",
-		.source_pll = "plla",
-		.cm_reg = CM_PLLA,
-		.a2w_reg = A2W_PLLA_PER,
-		.load_mask = CM_PLLA_LOADPER,
-		.hold_mask = CM_PLLA_HOLDPER,
-		.fixed_divider = 1,
-		.flags = CLK_SET_RATE_PARENT),
+
+	/*
+	 * PLLA_PER is used for gpu clocks. Controlled by firmware, see
+	 * clk-raspberrypi.c.
+	 */
+
 	[BCM2835_PLLA_DSI0]	= REGISTER_PLL_DIV(
 		SOC_ALL,
 		.name = "plla_dsi0",
@@ -2006,14 +2077,12 @@ static const struct bcm2835_clk_desc clk_desc_array[] = {
 		.int_bits = 6,
 		.frac_bits = 0,
 		.tcnt_mux = 3),
-	[BCM2835_CLOCK_V3D]	= REGISTER_VPU_CLK(
-		SOC_ALL,
-		.name = "v3d",
-		.ctl_reg = CM_V3DCTL,
-		.div_reg = CM_V3DDIV,
-		.int_bits = 4,
-		.frac_bits = 8,
-		.tcnt_mux = 4),
+
+	/*
+	 * CLOCK_V3D is used for v3d clock. Controlled by firmware, see
+	 * clk-raspberrypi.c.
+	 */
+
 	/*
 	 * VPU clock.  This doesn't have an enable bit, since it drives
 	 * the bus for everything else, and is special so it doesn't need
@@ -2176,21 +2245,6 @@ static const struct bcm2835_clk_desc clk_desc_array[] = {
 		.tcnt_mux = 28,
 		.round_up = true),
 
-	/* TV encoder clock.  Only operating frequency is 108Mhz.  */
-	[BCM2835_CLOCK_VEC]	= REGISTER_PER_CLK(
-		SOC_ALL,
-		.name = "vec",
-		.ctl_reg = CM_VECCTL,
-		.div_reg = CM_VECDIV,
-		.int_bits = 4,
-		.frac_bits = 0,
-		/*
-		 * Allow rate change propagation only on PLLH_AUX which is
-		 * assigned index 7 in the parent array.
-		 */
-		.set_rate_parent = BIT(7),
-		.tcnt_mux = 29),
-
 	/* dsi clocks */
 	[BCM2835_CLOCK_DSI0E]	= REGISTER_PER_CLK(
 		SOC_ALL,
@@ -2240,6 +2294,8 @@ static const struct bcm2835_clk_desc clk_desc_array[] = {
 		.ctl_reg = CM_PERIICTL),
 };
 
+static bool bcm2835_clk_claimed[ARRAY_SIZE(clk_desc_array)];
+
 /*
  * Permanently take a reference on the parent of the SDRAM clock.
  *
@@ -2259,6 +2315,21 @@ static int bcm2835_mark_sdc_parent_critical(struct clk *sdc)
 	return clk_prepare_enable(parent);
 }
 
+static bool bcm2835_clk_is_claimed(const char *name)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(clk_desc_array); i++) {
+		if (clk_desc_array[i].data) {
+			const char *clk_name = *(const char **)(clk_desc_array[i].data);
+			if (!strcmp(name, clk_name))
+				return bcm2835_clk_claimed[i];
+		}
+	}
+
+	return false;
+}
+
 static int bcm2835_clk_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
@@ -2267,7 +2338,9 @@ static int bcm2835_clk_probe(struct platform_device *pdev)
 	const struct bcm2835_clk_desc *desc;
 	const size_t asize = ARRAY_SIZE(clk_desc_array);
 	const struct cprman_plat_data *pdata;
+	struct device_node *fw_node;
 	size_t i;
+	u32 clk_id;
 	int ret;
 
 	pdata = of_device_get_match_data(&pdev->dev);
@@ -2286,6 +2359,24 @@ static int bcm2835_clk_probe(struct platform_device *pdev)
 	if (IS_ERR(cprman->regs))
 		return PTR_ERR(cprman->regs);
 
+	/* Mux DSI0 clock to PLLD */
+	cprman_write(cprman, CM_DSI0HSCK, 1);
+
+	fw_node = of_parse_phandle(dev->of_node, "firmware", 0);
+	if (fw_node) {
+		struct rpi_firmware *fw = rpi_firmware_get(fw_node);
+		if (!fw)
+			return -EPROBE_DEFER;
+		cprman->fw = fw;
+	}
+
+	memset(bcm2835_clk_claimed, 0, sizeof(bcm2835_clk_claimed));
+	for (i = 0;
+	     !of_property_read_u32_index(pdev->dev.of_node, "claim-clocks",
+					 i, &clk_id);
+	     i++)
+		bcm2835_clk_claimed[clk_id]= true;
+
 	memcpy(cprman->real_parent_names, cprman_parent_names,
 	       sizeof(cprman_parent_names));
 	of_clk_parent_fill(dev->of_node, cprman->real_parent_names,
@@ -2319,8 +2410,15 @@ static int bcm2835_clk_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
-	return of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get,
+	ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get,
 				      &cprman->onecell);
+	if (ret)
+		return ret;
+
+	/* note that we have registered all the clocks */
+	dev_dbg(dev, "registered %zd clocks\n", asize);
+
+	return 0;
 }
 
 static const struct cprman_plat_data cprman_bcm2835_plat_data = {
@@ -2346,7 +2444,15 @@ static struct platform_driver bcm2835_clk_driver = {
 	.probe          = bcm2835_clk_probe,
 };
 
-builtin_platform_driver(bcm2835_clk_driver);
+static int __init __bcm2835_clk_driver_init(void)
+{
+	return platform_driver_register(&bcm2835_clk_driver);
+}
+#ifdef CONFIG_IMA
+subsys_initcall(__bcm2835_clk_driver_init);
+#else
+postcore_initcall(__bcm2835_clk_driver_init);
+#endif
 
 MODULE_AUTHOR("Eric Anholt <eric@anholt.net>");
 MODULE_DESCRIPTION("BCM2835 clock driver");
diff --git a/drivers/clk/bcm/clk-raspberrypi.c b/drivers/clk/bcm/clk-raspberrypi.c
index 6cb26b6e7347..641e956424a3 100644
--- a/drivers/clk/bcm/clk-raspberrypi.c
+++ b/drivers/clk/bcm/clk-raspberrypi.c
@@ -34,6 +34,7 @@ static char *rpi_firmware_clk_names[] = {
 	[RPI_FIRMWARE_M2MC_CLK_ID]	= "m2mc",
 	[RPI_FIRMWARE_PIXEL_BVB_CLK_ID]	= "pixel-bvb",
 	[RPI_FIRMWARE_VEC_CLK_ID]	= "vec",
+	[RPI_FIRMWARE_DISP_CLK_ID]	= "disp",
 };
 
 #define RPI_FIRMWARE_STATE_ENABLE_BIT	BIT(0)
@@ -56,6 +57,12 @@ struct raspberrypi_clk_data {
 	struct raspberrypi_clk *rpi;
 };
 
+static inline
+const struct raspberrypi_clk_data *clk_hw_to_data(const struct clk_hw *hw)
+{
+	return container_of(hw, struct raspberrypi_clk_data, hw);
+}
+
 struct raspberrypi_clk_variant {
 	bool		export;
 	char		*clkdev;
@@ -111,18 +118,31 @@ raspberrypi_clk_variants[RPI_FIRMWARE_NUM_CLK_ID] = {
 	},
 	[RPI_FIRMWARE_V3D_CLK_ID] = {
 		.export = true,
+		.minimize = true,
 	},
 	[RPI_FIRMWARE_PIXEL_CLK_ID] = {
 		.export = true,
+		.minimize = true,
 	},
 	[RPI_FIRMWARE_HEVC_CLK_ID] = {
 		.export = true,
+		.minimize = true,
+	},
+	[RPI_FIRMWARE_ISP_CLK_ID] = {
+		.export = true,
+		.minimize = true,
 	},
 	[RPI_FIRMWARE_PIXEL_BVB_CLK_ID] = {
 		.export = true,
+		.minimize = true,
 	},
 	[RPI_FIRMWARE_VEC_CLK_ID] = {
 		.export = true,
+		.minimize = true,
+	},
+	[RPI_FIRMWARE_DISP_CLK_ID] = {
+		.export = true,
+		.minimize = true,
 	},
 };
 
@@ -153,7 +173,7 @@ static int raspberrypi_clock_property(struct rpi_firmware *firmware,
 	struct raspberrypi_firmware_prop msg = {
 		.id = cpu_to_le32(data->id),
 		.val = cpu_to_le32(*val),
-		.disable_turbo = cpu_to_le32(1),
+		.disable_turbo = cpu_to_le32(0),
 	};
 	int ret;
 
@@ -168,8 +188,7 @@ static int raspberrypi_clock_property(struct rpi_firmware *firmware,
 
 static int raspberrypi_fw_is_prepared(struct clk_hw *hw)
 {
-	struct raspberrypi_clk_data *data =
-		container_of(hw, struct raspberrypi_clk_data, hw);
+	const struct raspberrypi_clk_data *data = clk_hw_to_data(hw);
 	struct raspberrypi_clk *rpi = data->rpi;
 	u32 val = 0;
 	int ret;
@@ -186,8 +205,7 @@ static int raspberrypi_fw_is_prepared(struct clk_hw *hw)
 static unsigned long raspberrypi_fw_get_rate(struct clk_hw *hw,
 					     unsigned long parent_rate)
 {
-	struct raspberrypi_clk_data *data =
-		container_of(hw, struct raspberrypi_clk_data, hw);
+	const struct raspberrypi_clk_data *data = clk_hw_to_data(hw);
 	struct raspberrypi_clk *rpi = data->rpi;
 	u32 val = 0;
 	int ret;
@@ -203,8 +221,7 @@ static unsigned long raspberrypi_fw_get_rate(struct clk_hw *hw,
 static int raspberrypi_fw_set_rate(struct clk_hw *hw, unsigned long rate,
 				   unsigned long parent_rate)
 {
-	struct raspberrypi_clk_data *data =
-		container_of(hw, struct raspberrypi_clk_data, hw);
+	const struct raspberrypi_clk_data *data = clk_hw_to_data(hw);
 	struct raspberrypi_clk *rpi = data->rpi;
 	u32 _rate = rate;
 	int ret;
@@ -221,8 +238,7 @@ static int raspberrypi_fw_set_rate(struct clk_hw *hw, unsigned long rate,
 static int raspberrypi_fw_dumb_determine_rate(struct clk_hw *hw,
 					      struct clk_rate_request *req)
 {
-	struct raspberrypi_clk_data *data =
-		container_of(hw, struct raspberrypi_clk_data, hw);
+	const struct raspberrypi_clk_data *data = clk_hw_to_data(hw);
 	struct raspberrypi_clk_variant *variant = data->variant;
 
 	/*
diff --git a/drivers/clk/clk-gpio.c b/drivers/clk/clk-gpio.c
index 5b114043771d..ff34276aef7b 100644
--- a/drivers/clk/clk-gpio.c
+++ b/drivers/clk/clk-gpio.c
@@ -33,6 +33,7 @@
  *
  * @hw:		handle between common and hardware-specific interfaces
  * @gpiod:	gpio descriptor
+ * @dev:	device pointer for acquire/release operations
  *
  * Clock with a gpio control for enabling and disabling the parent clock
  * or switching between two parents by asserting or deasserting the gpio.
@@ -44,9 +45,41 @@
 struct clk_gpio {
 	struct clk_hw	hw;
 	struct gpio_desc *gpiod;
+	struct device *dev;
 };
 
 #define to_clk_gpio(_hw) container_of(_hw, struct clk_gpio, hw)
+static int clk_gpio_gate_acquire(struct clk_hw *hw)
+{
+	struct clk_gpio *clk = to_clk_gpio(hw);
+	struct device *dev = clk->dev;
+
+	clk->gpiod = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
+	if (IS_ERR(clk->gpiod)) {
+		int ret = PTR_ERR(clk->gpiod);
+
+		clk->gpiod = NULL;
+		return ret;
+	}
+
+	return 0;
+}
+
+static bool clk_gpio_gate_is_acquired(struct clk_hw *hw)
+{
+	struct clk_gpio *clk = to_clk_gpio(hw);
+
+	return !!clk->gpiod;
+}
+
+static void clk_gpio_gate_release(struct clk_hw *hw)
+{
+	struct clk_gpio *clk = to_clk_gpio(hw);
+	struct device *dev = clk->dev;
+
+	devm_gpiod_put(dev, clk->gpiod);
+	clk->gpiod = NULL;
+}
 
 static int clk_gpio_gate_enable(struct clk_hw *hw)
 {
@@ -77,6 +110,37 @@ static const struct clk_ops clk_gpio_gate_ops = {
 	.is_enabled = clk_gpio_gate_is_enabled,
 };
 
+static int clk_gpio_gate_releasing_enable(struct clk_hw *hw)
+{
+	int ret;
+
+	ret = clk_gpio_gate_acquire(hw);
+	if (ret)
+		return ret;
+
+	return clk_gpio_gate_enable(hw);
+}
+
+static void clk_gpio_gate_releasing_disable(struct clk_hw *hw)
+{
+	clk_gpio_gate_disable(hw);
+	clk_gpio_gate_release(hw);
+}
+
+static int clk_gpio_gate_releasing_is_enabled(struct clk_hw *hw)
+{
+	if (!clk_gpio_gate_is_acquired(hw))
+		return 0;
+
+	return clk_gpio_gate_is_enabled(hw);
+}
+
+static const struct clk_ops clk_gpio_gate_releasing_ops = {
+	.enable = clk_gpio_gate_releasing_enable,
+	.disable = clk_gpio_gate_releasing_disable,
+	.is_enabled = clk_gpio_gate_releasing_is_enabled,
+};
+
 static int clk_sleeping_gpio_gate_prepare(struct clk_hw *hw)
 {
 	struct clk_gpio *clk = to_clk_gpio(hw);
@@ -106,6 +170,37 @@ static const struct clk_ops clk_sleeping_gpio_gate_ops = {
 	.is_prepared = clk_sleeping_gpio_gate_is_prepared,
 };
 
+static int clk_sleeping_gpio_gate_releasing_prepare(struct clk_hw *hw)
+{
+	int ret;
+
+	ret = clk_gpio_gate_acquire(hw);
+	if (ret)
+		return ret;
+
+	return clk_sleeping_gpio_gate_prepare(hw);
+}
+
+static void clk_sleeping_gpio_gate_releasing_unprepare(struct clk_hw *hw)
+{
+	clk_sleeping_gpio_gate_unprepare(hw);
+	clk_gpio_gate_release(hw);
+}
+
+static int clk_sleeping_gpio_gate_releasing_is_prepared(struct clk_hw *hw)
+{
+	if (!clk_gpio_gate_is_acquired(hw))
+		return 0;
+
+	return clk_sleeping_gpio_gate_is_prepared(hw);
+}
+
+static const struct clk_ops clk_sleeping_gpio_gate_releasing_ops = {
+	.prepare = clk_sleeping_gpio_gate_releasing_prepare,
+	.unprepare = clk_sleeping_gpio_gate_releasing_unprepare,
+	.is_prepared = clk_sleeping_gpio_gate_releasing_is_prepared,
+};
+
 /**
  * DOC: basic clock multiplexer which can be controlled with a gpio output
  * Traits of this clock:
@@ -160,6 +255,7 @@ static struct clk_hw *clk_register_gpio(struct device *dev, u8 num_parents,
 	init.flags = CLK_SET_RATE_PARENT;
 
 	clk_gpio->gpiod = gpiod;
+	clk_gpio->dev = dev;
 	clk_gpio->hw.init = &init;
 
 	hw = &clk_gpio->hw;
@@ -172,14 +268,29 @@ static struct clk_hw *clk_register_gpio(struct device *dev, u8 num_parents,
 
 static struct clk_hw *clk_hw_register_gpio_gate(struct device *dev,
 						int num_parents,
-						struct gpio_desc *gpiod)
+						struct gpio_desc *gpiod,
+						bool releasing)
 {
 	const struct clk_ops *ops;
 
-	if (gpiod_cansleep(gpiod))
-		ops = &clk_sleeping_gpio_gate_ops;
-	else
-		ops = &clk_gpio_gate_ops;
+	if (releasing) {
+		/* For releasing variant, confirm GPIO works then release it
+		 * for acquire/release semantics
+		 */
+		if (gpiod_cansleep(gpiod))
+			ops = &clk_sleeping_gpio_gate_releasing_ops;
+		else
+			ops = &clk_gpio_gate_releasing_ops;
+
+		devm_gpiod_put(dev, gpiod);
+		gpiod = NULL;
+	} else {
+		/* Regular variant - keep GPIO and choose appropriate ops */
+		if (gpiod_cansleep(gpiod))
+			ops = &clk_sleeping_gpio_gate_ops;
+		else
+			ops = &clk_gpio_gate_ops;
+	}
 
 	return clk_register_gpio(dev, num_parents, gpiod, ops);
 }
@@ -199,9 +310,12 @@ static int gpio_clk_driver_probe(struct platform_device *pdev)
 	struct gpio_desc *gpiod;
 	struct clk_hw *hw;
 	bool is_mux;
+	bool is_releasing;
 	int ret;
 
 	is_mux = of_device_is_compatible(node, "gpio-mux-clock");
+	is_releasing =
+		of_device_is_compatible(node, "gpio-gate-clock-releasing");
 
 	num_parents = of_clk_get_parent_count(node);
 	if (is_mux && num_parents != 2) {
@@ -226,7 +340,9 @@ static int gpio_clk_driver_probe(struct platform_device *pdev)
 	if (is_mux)
 		hw = clk_hw_register_gpio_mux(dev, gpiod);
 	else
-		hw = clk_hw_register_gpio_gate(dev, num_parents, gpiod);
+		hw = clk_hw_register_gpio_gate(dev, num_parents, gpiod,
+					       is_releasing);
+
 	if (IS_ERR(hw))
 		return PTR_ERR(hw);
 
@@ -236,6 +352,7 @@ static int gpio_clk_driver_probe(struct platform_device *pdev)
 static const struct of_device_id gpio_clk_match_table[] = {
 	{ .compatible = "gpio-mux-clock" },
 	{ .compatible = "gpio-gate-clock" },
+	{ .compatible = "gpio-gate-clock-releasing" },
 	{ }
 };
 
diff --git a/drivers/clk/clk-hifiberry-dachd.c b/drivers/clk/clk-hifiberry-dachd.c
new file mode 100644
index 000000000000..5280b5100559
--- /dev/null
+++ b/drivers/clk/clk-hifiberry-dachd.c
@@ -0,0 +1,331 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Clock Driver for HiFiBerry DAC+ HD
+ *
+ * Author: Joerg Schambacher, i2Audio GmbH for HiFiBerry
+ *         Copyright 2020
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+
+#define NO_PLL_RESET			0
+#define PLL_RESET			1
+#define HIFIBERRY_PLL_MAX_REGISTER	256
+#define DEFAULT_RATE			44100
+
+static struct reg_default hifiberry_pll_reg_defaults[] = {
+	{0x02, 0x53}, {0x03, 0x00}, {0x07, 0x20}, {0x0F, 0x00},
+	{0x10, 0x0D}, {0x11, 0x1D}, {0x12, 0x0D}, {0x13, 0x8C},
+	{0x14, 0x8C}, {0x15, 0x8C}, {0x16, 0x8C}, {0x17, 0x8C},
+	{0x18, 0x2A}, {0x1C, 0x00}, {0x1D, 0x0F}, {0x1F, 0x00},
+	{0x2A, 0x00}, {0x2C, 0x00}, {0x2F, 0x00}, {0x30, 0x00},
+	{0x31, 0x00}, {0x32, 0x00}, {0x34, 0x00}, {0x37, 0x00},
+	{0x38, 0x00}, {0x39, 0x00}, {0x3A, 0x00}, {0x3B, 0x01},
+	{0x3E, 0x00}, {0x3F, 0x00}, {0x40, 0x00}, {0x41, 0x00},
+	{0x5A, 0x00}, {0x5B, 0x00}, {0x95, 0x00}, {0x96, 0x00},
+	{0x97, 0x00}, {0x98, 0x00}, {0x99, 0x00}, {0x9A, 0x00},
+	{0x9B, 0x00}, {0xA2, 0x00}, {0xA3, 0x00}, {0xA4, 0x00},
+	{0xB7, 0x92},
+	{0x1A, 0x3D}, {0x1B, 0x09}, {0x1E, 0xF3}, {0x20, 0x13},
+	{0x21, 0x75}, {0x2B, 0x04}, {0x2D, 0x11}, {0x2E, 0xE0},
+	{0x3D, 0x7A},
+	{0x35, 0x9D}, {0x36, 0x00}, {0x3C, 0x42},
+	{ 177, 0xAC},
+};
+static struct reg_default common_pll_regs[HIFIBERRY_PLL_MAX_REGISTER];
+static int num_common_pll_regs;
+static struct reg_default dedicated_192k_pll_regs[HIFIBERRY_PLL_MAX_REGISTER];
+static int num_dedicated_192k_pll_regs;
+static struct reg_default dedicated_96k_pll_regs[HIFIBERRY_PLL_MAX_REGISTER];
+static int num_dedicated_96k_pll_regs;
+static struct reg_default dedicated_48k_pll_regs[HIFIBERRY_PLL_MAX_REGISTER];
+static int num_dedicated_48k_pll_regs;
+static struct reg_default dedicated_176k4_pll_regs[HIFIBERRY_PLL_MAX_REGISTER];
+static int num_dedicated_176k4_pll_regs;
+static struct reg_default dedicated_88k2_pll_regs[HIFIBERRY_PLL_MAX_REGISTER];
+static int num_dedicated_88k2_pll_regs;
+static struct reg_default dedicated_44k1_pll_regs[HIFIBERRY_PLL_MAX_REGISTER];
+static int num_dedicated_44k1_pll_regs;
+
+/**
+ * struct clk_hifiberry_drvdata - Common struct to the HiFiBerry DAC HD Clk
+ * @hw: clk_hw for the common clk framework
+ */
+struct clk_hifiberry_drvdata {
+	struct regmap *regmap;
+	struct clk *clk;
+	struct clk_hw hw;
+	unsigned long rate;
+};
+
+#define to_hifiberry_clk(_hw) \
+	container_of(_hw, struct clk_hifiberry_drvdata, hw)
+
+static int clk_hifiberry_dachd_write_pll_regs(struct regmap *regmap,
+				struct reg_default *regs,
+				int num, int do_pll_reset)
+{
+	int i;
+	int ret = 0;
+	char pll_soft_reset[] = { 177, 0xAC, };
+
+	for (i = 0; i < num; i++) {
+		ret |= regmap_write(regmap, regs[i].reg, regs[i].def);
+		if (ret)
+			return ret;
+	}
+	if (do_pll_reset) {
+		ret |= regmap_write(regmap, pll_soft_reset[0],
+						pll_soft_reset[1]);
+		mdelay(10);
+	}
+	return ret;
+}
+
+static unsigned long clk_hifiberry_dachd_recalc_rate(struct clk_hw *hw,
+	unsigned long parent_rate)
+{
+	return to_hifiberry_clk(hw)->rate;
+}
+
+static long clk_hifiberry_dachd_round_rate(struct clk_hw *hw,
+	unsigned long rate, unsigned long *parent_rate)
+{
+	return rate;
+}
+
+static int clk_hifiberry_dachd_set_rate(struct clk_hw *hw,
+	unsigned long rate, unsigned long parent_rate)
+{
+	int ret;
+	struct clk_hifiberry_drvdata *drvdata = to_hifiberry_clk(hw);
+
+	switch (rate) {
+	case 44100:
+		ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap,
+			dedicated_44k1_pll_regs, num_dedicated_44k1_pll_regs,
+			PLL_RESET);
+		break;
+	case 88200:
+		ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap,
+			dedicated_88k2_pll_regs, num_dedicated_88k2_pll_regs,
+			PLL_RESET);
+		break;
+	case 176400:
+		ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap,
+			dedicated_176k4_pll_regs, num_dedicated_176k4_pll_regs,
+			PLL_RESET);
+		break;
+	case 48000:
+		ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap,
+			dedicated_48k_pll_regs,	num_dedicated_48k_pll_regs,
+			PLL_RESET);
+		break;
+	case 96000:
+		ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap,
+			dedicated_96k_pll_regs,	num_dedicated_96k_pll_regs,
+			PLL_RESET);
+		break;
+	case 192000:
+		ret = clk_hifiberry_dachd_write_pll_regs(drvdata->regmap,
+			dedicated_192k_pll_regs, num_dedicated_192k_pll_regs,
+			PLL_RESET);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+	to_hifiberry_clk(hw)->rate = rate;
+
+	return ret;
+}
+
+const struct clk_ops clk_hifiberry_dachd_rate_ops = {
+	.recalc_rate = clk_hifiberry_dachd_recalc_rate,
+	.round_rate = clk_hifiberry_dachd_round_rate,
+	.set_rate = clk_hifiberry_dachd_set_rate,
+};
+
+static int clk_hifiberry_get_prop_values(struct device *dev,
+					char *prop_name,
+					struct reg_default *regs)
+{
+	int ret;
+	int i;
+	u8 tmp[2 * HIFIBERRY_PLL_MAX_REGISTER];
+
+	ret = of_property_read_variable_u8_array(dev->of_node, prop_name,
+			tmp, 0, 2 * HIFIBERRY_PLL_MAX_REGISTER);
+	if (ret < 0)
+		return ret;
+	if (ret & 1) {
+		dev_err(dev,
+			"%s <%s> -> #%i odd number of bytes for reg/val pairs!",
+			__func__,
+			prop_name,
+			ret);
+		return -EINVAL;
+	}
+	ret /= 2;
+	for (i = 0; i < ret; i++) {
+		regs[i].reg = (u32)tmp[2 * i];
+		regs[i].def = (u32)tmp[2 * i + 1];
+	}
+	return ret;
+}
+
+
+static int clk_hifiberry_dachd_dt_parse(struct device *dev)
+{
+	num_common_pll_regs = clk_hifiberry_get_prop_values(dev,
+				"common_pll_regs", common_pll_regs);
+	num_dedicated_44k1_pll_regs = clk_hifiberry_get_prop_values(dev,
+				"44k1_pll_regs", dedicated_44k1_pll_regs);
+	num_dedicated_88k2_pll_regs = clk_hifiberry_get_prop_values(dev,
+				"88k2_pll_regs", dedicated_88k2_pll_regs);
+	num_dedicated_176k4_pll_regs = clk_hifiberry_get_prop_values(dev,
+				"176k4_pll_regs", dedicated_176k4_pll_regs);
+	num_dedicated_48k_pll_regs = clk_hifiberry_get_prop_values(dev,
+				"48k_pll_regs", dedicated_48k_pll_regs);
+	num_dedicated_96k_pll_regs = clk_hifiberry_get_prop_values(dev,
+				"96k_pll_regs", dedicated_96k_pll_regs);
+	num_dedicated_192k_pll_regs = clk_hifiberry_get_prop_values(dev,
+				"192k_pll_regs", dedicated_192k_pll_regs);
+	return 0;
+}
+
+
+static int clk_hifiberry_dachd_remove(struct device *dev)
+{
+	of_clk_del_provider(dev->of_node);
+	return 0;
+}
+
+const struct regmap_config hifiberry_pll_regmap = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = HIFIBERRY_PLL_MAX_REGISTER,
+	.reg_defaults = hifiberry_pll_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(hifiberry_pll_reg_defaults),
+	.cache_type = REGCACHE_RBTREE,
+};
+EXPORT_SYMBOL_GPL(hifiberry_pll_regmap);
+
+
+static int clk_hifiberry_dachd_i2c_probe(struct i2c_client *i2c)
+{
+	struct clk_hifiberry_drvdata *hdclk;
+	int ret = 0;
+	struct clk_init_data init;
+	struct device *dev = &i2c->dev;
+	struct device_node *dev_node = dev->of_node;
+	struct regmap_config config = hifiberry_pll_regmap;
+
+	hdclk = devm_kzalloc(&i2c->dev,
+			sizeof(struct clk_hifiberry_drvdata), GFP_KERNEL);
+	if (!hdclk)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, hdclk);
+
+	hdclk->regmap = devm_regmap_init_i2c(i2c, &config);
+
+	if (IS_ERR(hdclk->regmap))
+		return PTR_ERR(hdclk->regmap);
+
+	/* start PLL to allow detection of DAC */
+	ret = clk_hifiberry_dachd_write_pll_regs(hdclk->regmap,
+				hifiberry_pll_reg_defaults,
+				ARRAY_SIZE(hifiberry_pll_reg_defaults),
+				PLL_RESET);
+	if (ret)
+		return ret;
+
+	clk_hifiberry_dachd_dt_parse(dev);
+
+	/* restart PLL with configs from DTB */
+	ret = clk_hifiberry_dachd_write_pll_regs(hdclk->regmap, common_pll_regs,
+					num_common_pll_regs, PLL_RESET);
+	if (ret)
+		return ret;
+
+	init.name = "clk-hifiberry-dachd";
+	init.ops = &clk_hifiberry_dachd_rate_ops;
+	init.flags = 0;
+	init.parent_names = NULL;
+	init.num_parents = 0;
+
+	hdclk->hw.init = &init;
+
+	hdclk->clk = devm_clk_register(dev, &hdclk->hw);
+	if (IS_ERR(hdclk->clk)) {
+		dev_err(dev, "unable to register %s\n",	init.name);
+		return PTR_ERR(hdclk->clk);
+	}
+
+	ret = of_clk_add_provider(dev_node, of_clk_src_simple_get, hdclk->clk);
+	if (ret != 0) {
+		dev_err(dev, "Cannot of_clk_add_provider");
+		return ret;
+	}
+
+	ret = clk_set_rate(hdclk->hw.clk, DEFAULT_RATE);
+	if (ret != 0) {
+		dev_err(dev, "Cannot set rate : %d\n",	ret);
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static void clk_hifiberry_dachd_i2c_remove(struct i2c_client *i2c)
+{
+	clk_hifiberry_dachd_remove(&i2c->dev);
+}
+
+static const struct i2c_device_id clk_hifiberry_dachd_i2c_id[] = {
+	{ "dachd-clk", },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, clk_hifiberry_dachd_i2c_id);
+
+static const struct of_device_id clk_hifiberry_dachd_of_match[] = {
+	{ .compatible = "hifiberry,dachd-clk", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, clk_hifiberry_dachd_of_match);
+
+static struct i2c_driver clk_hifiberry_dachd_i2c_driver = {
+	.probe		= clk_hifiberry_dachd_i2c_probe,
+	.remove		= clk_hifiberry_dachd_i2c_remove,
+	.id_table	= clk_hifiberry_dachd_i2c_id,
+	.driver		= {
+		.name	= "dachd-clk",
+		.of_match_table = of_match_ptr(clk_hifiberry_dachd_of_match),
+	},
+};
+
+module_i2c_driver(clk_hifiberry_dachd_i2c_driver);
+
+
+MODULE_DESCRIPTION("HiFiBerry DAC+ HD clock driver");
+MODULE_AUTHOR("Joerg Schambacher <joerg@i2audio.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:clk-hifiberry-dachd");
diff --git a/drivers/clk/clk-hifiberry-dacpro.c b/drivers/clk/clk-hifiberry-dacpro.c
new file mode 100644
index 000000000000..0cbc0349f9f6
--- /dev/null
+++ b/drivers/clk/clk-hifiberry-dacpro.c
@@ -0,0 +1,180 @@
+/*
+ * Clock Driver for HiFiBerry DAC Pro
+ *
+ * Author: Stuart MacLean
+ *         Copyright 2015
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+
+struct ext_clk_rates {
+	/* Clock rate of CLK44EN attached to GPIO6 pin */
+	unsigned long clk_44en;
+	/* Clock rate of CLK48EN attached to GPIO3 pin */
+	unsigned long clk_48en;
+};
+
+/**
+ * struct hifiberry_dacpro_clk - Common struct to the HiFiBerry DAC Pro
+ * @hw: clk_hw for the common clk framework
+ * @mode: 0 => CLK44EN, 1 => CLK48EN
+ */
+struct clk_hifiberry_hw {
+	struct clk_hw hw;
+	uint8_t mode;
+	struct ext_clk_rates clk_rates;
+};
+
+#define to_hifiberry_clk(_hw) container_of(_hw, struct clk_hifiberry_hw, hw)
+
+static const struct ext_clk_rates hifiberry_dacpro_clks = {
+	.clk_44en = 22579200UL,
+	.clk_48en = 24576000UL,
+};
+
+static const struct ext_clk_rates allo_dac_clks = {
+	.clk_44en = 45158400UL,
+	.clk_48en = 49152000UL,
+};
+
+static const struct of_device_id clk_hifiberry_dacpro_dt_ids[] = {
+	{ .compatible = "hifiberry,dacpro-clk", &hifiberry_dacpro_clks },
+	{ .compatible = "allo,dac-clk", &allo_dac_clks },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, clk_hifiberry_dacpro_dt_ids);
+
+static unsigned long clk_hifiberry_dacpro_recalc_rate(struct clk_hw *hw,
+	unsigned long parent_rate)
+{
+	struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw);
+	return (clk->mode == 0) ? clk->clk_rates.clk_44en :
+		clk->clk_rates.clk_48en;
+}
+
+static long clk_hifiberry_dacpro_round_rate(struct clk_hw *hw,
+	unsigned long rate, unsigned long *parent_rate)
+{
+	struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw);
+	long actual_rate;
+
+	if (rate <= clk->clk_rates.clk_44en) {
+		actual_rate = (long)clk->clk_rates.clk_44en;
+	} else if (rate >= clk->clk_rates.clk_48en) {
+		actual_rate = (long)clk->clk_rates.clk_48en;
+	} else {
+		long diff44Rate = (long)(rate - clk->clk_rates.clk_44en);
+		long diff48Rate = (long)(clk->clk_rates.clk_48en - rate);
+
+		if (diff44Rate < diff48Rate)
+			actual_rate = (long)clk->clk_rates.clk_44en;
+		else
+			actual_rate = (long)clk->clk_rates.clk_48en;
+	}
+	return actual_rate;
+}
+
+
+static int clk_hifiberry_dacpro_set_rate(struct clk_hw *hw,
+	unsigned long rate, unsigned long parent_rate)
+{
+	struct clk_hifiberry_hw *clk = to_hifiberry_clk(hw);
+	unsigned long actual_rate;
+
+	actual_rate = (unsigned long)clk_hifiberry_dacpro_round_rate(hw, rate,
+		&parent_rate);
+	clk->mode = (actual_rate == clk->clk_rates.clk_44en) ? 0 : 1;
+	return 0;
+}
+
+
+const struct clk_ops clk_hifiberry_dacpro_rate_ops = {
+	.recalc_rate = clk_hifiberry_dacpro_recalc_rate,
+	.round_rate = clk_hifiberry_dacpro_round_rate,
+	.set_rate = clk_hifiberry_dacpro_set_rate,
+};
+
+static int clk_hifiberry_dacpro_probe(struct platform_device *pdev)
+{
+	const struct of_device_id *of_id;
+	struct clk_hifiberry_hw *proclk;
+	struct clk *clk;
+	struct device *dev;
+	struct clk_init_data init;
+	int ret;
+
+	dev = &pdev->dev;
+	of_id = of_match_node(clk_hifiberry_dacpro_dt_ids, dev->of_node);
+	if (!of_id)
+		return -EINVAL;
+
+	proclk = kzalloc(sizeof(struct clk_hifiberry_hw), GFP_KERNEL);
+	if (!proclk)
+		return -ENOMEM;
+
+	init.name = "clk-hifiberry-dacpro";
+	init.ops = &clk_hifiberry_dacpro_rate_ops;
+	init.flags = 0;
+	init.parent_names = NULL;
+	init.num_parents = 0;
+
+	proclk->mode = 0;
+	proclk->hw.init = &init;
+	memcpy(&proclk->clk_rates, of_id->data, sizeof(proclk->clk_rates));
+
+	clk = devm_clk_register(dev, &proclk->hw);
+	if (!IS_ERR(clk)) {
+		ret = of_clk_add_provider(dev->of_node, of_clk_src_simple_get,
+			clk);
+	} else {
+		dev_err(dev, "Fail to register clock driver\n");
+		kfree(proclk);
+		ret = PTR_ERR(clk);
+	}
+	return ret;
+}
+
+static void clk_hifiberry_dacpro_remove(struct platform_device *pdev)
+{
+	of_clk_del_provider(pdev->dev.of_node);
+}
+
+static struct platform_driver clk_hifiberry_dacpro_driver = {
+	.probe = clk_hifiberry_dacpro_probe,
+	.remove = clk_hifiberry_dacpro_remove,
+	.driver = {
+		.name = "clk-hifiberry-dacpro",
+		.of_match_table = clk_hifiberry_dacpro_dt_ids,
+	},
+};
+
+static int __init clk_hifiberry_dacpro_init(void)
+{
+	return platform_driver_register(&clk_hifiberry_dacpro_driver);
+}
+core_initcall(clk_hifiberry_dacpro_init);
+
+static void __exit clk_hifiberry_dacpro_exit(void)
+{
+	platform_driver_unregister(&clk_hifiberry_dacpro_driver);
+}
+module_exit(clk_hifiberry_dacpro_exit);
+
+MODULE_DESCRIPTION("HiFiBerry DAC Pro clock driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:clk-hifiberry-dacpro");
diff --git a/drivers/clk/clk-rp1-sdio.c b/drivers/clk/clk-rp1-sdio.c
new file mode 100644
index 000000000000..c459bab0575c
--- /dev/null
+++ b/drivers/clk/clk-rp1-sdio.c
@@ -0,0 +1,599 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SDIO clock driver for RP1
+ *
+ * Copyright (C) 2023 Raspberry Pi Ltd.
+ */
+
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+// Register    : MODE
+#define MODE        0x00000000
+#define MODE_BITS   0x70030000
+#define MODE_RESET  0x00000000
+// Field       : MODE_STEPS_PER_CYCLE
+#define MODE_STEPS_PER_CYCLE_RESET          0x0
+#define MODE_STEPS_PER_CYCLE_BITS           0x70000000
+#define MODE_STEPS_PER_CYCLE_MSB            30
+#define MODE_STEPS_PER_CYCLE_LSB            28
+#define MODE_STEPS_PER_CYCLE_VALUE_STEPS_20 0x0
+#define MODE_STEPS_PER_CYCLE_VALUE_STEPS_10 0x1
+#define MODE_STEPS_PER_CYCLE_VALUE_STEPS_16 0x2
+#define MODE_STEPS_PER_CYCLE_VALUE_STEPS_8  0x3
+#define MODE_STEPS_PER_CYCLE_VALUE_STEPS_12 0x4
+#define MODE_STEPS_PER_CYCLE_VALUE_STEPS_6  0x5
+#define MODE_STEPS_PER_CYCLE_VALUE_STEPS_5  0x6
+#define MODE_STEPS_PER_CYCLE_VALUE_STEPS_4  0x7
+// Field       : MODE_SRC_SEL
+#define MODE_SRC_SEL_RESET                   0x0
+#define MODE_SRC_SEL_BITS                    0x00030000
+#define MODE_SRC_SEL_MSB                     17
+#define MODE_SRC_SEL_LSB                     16
+#define MODE_SRC_SEL_VALUE_STOP              0x0
+#define MODE_SRC_SEL_VALUE_CLK_ALT_SRC       0x1
+#define MODE_SRC_SEL_VALUE_PLL_SYS_VCO       0x2
+#define MODE_SRC_SEL_VALUE_PLL_SYS_VCO_AGAIN 0x3
+// Register    : FROMIP
+#define FROMIP        0x00000004
+#define FROMIP_BITS   0x0f9713ff
+#define FROMIP_RESET  0x00000000
+// Field       : FROMIP_TUNING_CCLK_SEL
+#define FROMIP_TUNING_CCLK_SEL_RESET  0x0
+#define FROMIP_TUNING_CCLK_SEL_BITS   0x0f000000
+#define FROMIP_TUNING_CCLK_SEL_MSB    27
+#define FROMIP_TUNING_CCLK_SEL_LSB    24
+// Field       : FROMIP_TUNING_CCLK_UPDATE
+#define FROMIP_TUNING_CCLK_UPDATE_RESET  0x0
+#define FROMIP_TUNING_CCLK_UPDATE_BITS   0x00800000
+#define FROMIP_TUNING_CCLK_UPDATE_MSB    23
+#define FROMIP_TUNING_CCLK_UPDATE_LSB    23
+// Field       : FROMIP_SAMPLE_CCLK_SEL
+#define FROMIP_SAMPLE_CCLK_SEL_RESET  0x0
+#define FROMIP_SAMPLE_CCLK_SEL_BITS   0x00100000
+#define FROMIP_SAMPLE_CCLK_SEL_MSB    20
+#define FROMIP_SAMPLE_CCLK_SEL_LSB    20
+// Field       : FROMIP_CLK2CARD_ON
+#define FROMIP_CLK2CARD_ON_RESET  0x0
+#define FROMIP_CLK2CARD_ON_BITS   0x00040000
+#define FROMIP_CLK2CARD_ON_MSB    18
+#define FROMIP_CLK2CARD_ON_LSB    18
+// Field       : FROMIP_CARD_CLK_STABLE
+#define FROMIP_CARD_CLK_STABLE_RESET  0x0
+#define FROMIP_CARD_CLK_STABLE_BITS   0x00020000
+#define FROMIP_CARD_CLK_STABLE_MSB    17
+#define FROMIP_CARD_CLK_STABLE_LSB    17
+// Field       : FROMIP_CARD_CLK_EN
+#define FROMIP_CARD_CLK_EN_RESET  0x0
+#define FROMIP_CARD_CLK_EN_BITS   0x00010000
+#define FROMIP_CARD_CLK_EN_MSB    16
+#define FROMIP_CARD_CLK_EN_LSB    16
+// Field       : FROMIP_CLK_GEN_SEL
+#define FROMIP_CLK_GEN_SEL_RESET  0x0
+#define FROMIP_CLK_GEN_SEL_BITS   0x00001000
+#define FROMIP_CLK_GEN_SEL_MSB    12
+#define FROMIP_CLK_GEN_SEL_LSB    12
+// Field       : FROMIP_FREQ_SEL
+#define FROMIP_FREQ_SEL_RESET  0x000
+#define FROMIP_FREQ_SEL_BITS   0x000003ff
+#define FROMIP_FREQ_SEL_MSB    9
+#define FROMIP_FREQ_SEL_LSB    0
+// Register    : LOCAL
+#define LOCAL        0x00000008
+#define LOCAL_BITS   0x1f9713ff
+#define LOCAL_RESET  0x00000000
+// Field       : LOCAL_TUNING_CCLK_SEL
+#define LOCAL_TUNING_CCLK_SEL_RESET  0x00
+#define LOCAL_TUNING_CCLK_SEL_BITS   0x1f000000
+#define LOCAL_TUNING_CCLK_SEL_MSB    28
+#define LOCAL_TUNING_CCLK_SEL_LSB    24
+// Field       : LOCAL_TUNING_CCLK_UPDATE
+#define LOCAL_TUNING_CCLK_UPDATE_RESET  0x0
+#define LOCAL_TUNING_CCLK_UPDATE_BITS   0x00800000
+#define LOCAL_TUNING_CCLK_UPDATE_MSB    23
+#define LOCAL_TUNING_CCLK_UPDATE_LSB    23
+// Field       : LOCAL_SAMPLE_CCLK_SEL
+#define LOCAL_SAMPLE_CCLK_SEL_RESET  0x0
+#define LOCAL_SAMPLE_CCLK_SEL_BITS   0x00100000
+#define LOCAL_SAMPLE_CCLK_SEL_MSB    20
+#define LOCAL_SAMPLE_CCLK_SEL_LSB    20
+// Field       : LOCAL_CLK2CARD_ON
+#define LOCAL_CLK2CARD_ON_RESET  0x0
+#define LOCAL_CLK2CARD_ON_BITS   0x00040000
+#define LOCAL_CLK2CARD_ON_MSB    18
+#define LOCAL_CLK2CARD_ON_LSB    18
+// Field       : LOCAL_CARD_CLK_STABLE
+#define LOCAL_CARD_CLK_STABLE_RESET  0x0
+#define LOCAL_CARD_CLK_STABLE_BITS   0x00020000
+#define LOCAL_CARD_CLK_STABLE_MSB    17
+#define LOCAL_CARD_CLK_STABLE_LSB    17
+// Field       : LOCAL_CARD_CLK_EN
+#define LOCAL_CARD_CLK_EN_RESET  0x0
+#define LOCAL_CARD_CLK_EN_BITS   0x00010000
+#define LOCAL_CARD_CLK_EN_MSB    16
+#define LOCAL_CARD_CLK_EN_LSB    16
+// Field       : LOCAL_CLK_GEN_SEL
+#define LOCAL_CLK_GEN_SEL_RESET               0x0
+#define LOCAL_CLK_GEN_SEL_BITS                0x00001000
+#define LOCAL_CLK_GEN_SEL_MSB                 12
+#define LOCAL_CLK_GEN_SEL_LSB                 12
+#define LOCAL_CLK_GEN_SEL_VALUE_PROGCLOCKMODE 0x0
+#define LOCAL_CLK_GEN_SEL_VALUE_DIVCLOCKMODE  0x1
+// Field       : LOCAL_FREQ_SEL
+#define LOCAL_FREQ_SEL_RESET  0x000
+#define LOCAL_FREQ_SEL_BITS   0x000003ff
+#define LOCAL_FREQ_SEL_MSB    9
+#define LOCAL_FREQ_SEL_LSB    0
+// Register    : USE_LOCAL
+#define USE_LOCAL        0x0000000c
+#define USE_LOCAL_BITS   0x01951001
+#define USE_LOCAL_RESET  0x00000000
+// Field       : USE_LOCAL_TUNING_CCLK_SEL
+#define USE_LOCAL_TUNING_CCLK_SEL_RESET  0x0
+#define USE_LOCAL_TUNING_CCLK_SEL_BITS   0x01000000
+#define USE_LOCAL_TUNING_CCLK_SEL_MSB    24
+#define USE_LOCAL_TUNING_CCLK_SEL_LSB    24
+// Field       : USE_LOCAL_TUNING_CCLK_UPDATE
+#define USE_LOCAL_TUNING_CCLK_UPDATE_RESET  0x0
+#define USE_LOCAL_TUNING_CCLK_UPDATE_BITS   0x00800000
+#define USE_LOCAL_TUNING_CCLK_UPDATE_MSB    23
+#define USE_LOCAL_TUNING_CCLK_UPDATE_LSB    23
+// Field       : USE_LOCAL_SAMPLE_CCLK_SEL
+#define USE_LOCAL_SAMPLE_CCLK_SEL_RESET  0x0
+#define USE_LOCAL_SAMPLE_CCLK_SEL_BITS   0x00100000
+#define USE_LOCAL_SAMPLE_CCLK_SEL_MSB    20
+#define USE_LOCAL_SAMPLE_CCLK_SEL_LSB    20
+// Field       : USE_LOCAL_CLK2CARD_ON
+#define USE_LOCAL_CLK2CARD_ON_RESET  0x0
+#define USE_LOCAL_CLK2CARD_ON_BITS   0x00040000
+#define USE_LOCAL_CLK2CARD_ON_MSB    18
+#define USE_LOCAL_CLK2CARD_ON_LSB    18
+// Field       : USE_LOCAL_CARD_CLK_EN
+#define USE_LOCAL_CARD_CLK_EN_RESET  0x0
+#define USE_LOCAL_CARD_CLK_EN_BITS   0x00010000
+#define USE_LOCAL_CARD_CLK_EN_MSB    16
+#define USE_LOCAL_CARD_CLK_EN_LSB    16
+// Field       : USE_LOCAL_CLK_GEN_SEL
+#define USE_LOCAL_CLK_GEN_SEL_RESET  0x0
+#define USE_LOCAL_CLK_GEN_SEL_BITS   0x00001000
+#define USE_LOCAL_CLK_GEN_SEL_MSB    12
+#define USE_LOCAL_CLK_GEN_SEL_LSB    12
+// Field       : USE_LOCAL_FREQ_SEL
+#define USE_LOCAL_FREQ_SEL_RESET  0x0
+#define USE_LOCAL_FREQ_SEL_BITS   0x00000001
+#define USE_LOCAL_FREQ_SEL_MSB    0
+#define USE_LOCAL_FREQ_SEL_LSB    0
+// Register    : SD_DELAY
+#define SD_DELAY        0x00000010
+#define SD_DELAY_BITS   0x0000001f
+#define SD_DELAY_RESET  0x00000000
+// Field       : SD_DELAY_STEPS
+#define SD_DELAY_STEPS_RESET  0x00
+#define SD_DELAY_STEPS_BITS   0x0000001f
+#define SD_DELAY_STEPS_MSB    4
+#define SD_DELAY_STEPS_LSB    0
+// Register    : RX_DELAY
+#define RX_DELAY        0x00000014
+#define RX_DELAY_BITS   0x19f3331f
+#define RX_DELAY_RESET  0x00000000
+// Field       : RX_DELAY_BYPASS
+#define RX_DELAY_BYPASS_RESET  0x0
+#define RX_DELAY_BYPASS_BITS   0x10000000
+#define RX_DELAY_BYPASS_MSB    28
+#define RX_DELAY_BYPASS_LSB    28
+// Field       : RX_DELAY_FAIL_ACTUAL
+#define RX_DELAY_FAIL_ACTUAL_RESET  0x0
+#define RX_DELAY_FAIL_ACTUAL_BITS   0x08000000
+#define RX_DELAY_FAIL_ACTUAL_MSB    27
+#define RX_DELAY_FAIL_ACTUAL_LSB    27
+// Field       : RX_DELAY_ACTUAL
+#define RX_DELAY_ACTUAL_RESET  0x00
+#define RX_DELAY_ACTUAL_BITS   0x01f00000
+#define RX_DELAY_ACTUAL_MSB    24
+#define RX_DELAY_ACTUAL_LSB    20
+// Field       : RX_DELAY_OFFSET
+#define RX_DELAY_OFFSET_RESET  0x0
+#define RX_DELAY_OFFSET_BITS   0x00030000
+#define RX_DELAY_OFFSET_MSB    17
+#define RX_DELAY_OFFSET_LSB    16
+// Field       : RX_DELAY_OVERFLOW
+#define RX_DELAY_OVERFLOW_RESET       0x0
+#define RX_DELAY_OVERFLOW_BITS        0x00003000
+#define RX_DELAY_OVERFLOW_MSB         13
+#define RX_DELAY_OVERFLOW_LSB         12
+#define RX_DELAY_OVERFLOW_VALUE_ALLOW 0x0
+#define RX_DELAY_OVERFLOW_VALUE_CLAMP 0x1
+#define RX_DELAY_OVERFLOW_VALUE_FAIL  0x2
+// Field       : RX_DELAY_MAP
+#define RX_DELAY_MAP_RESET         0x0
+#define RX_DELAY_MAP_BITS          0x00000300
+#define RX_DELAY_MAP_MSB           9
+#define RX_DELAY_MAP_LSB           8
+#define RX_DELAY_MAP_VALUE_DIRECT  0x0
+#define RX_DELAY_MAP_VALUE         0x1
+#define RX_DELAY_MAP_VALUE_STRETCH 0x2
+// Field       : RX_DELAY_FIXED
+#define RX_DELAY_FIXED_RESET  0x00
+#define RX_DELAY_FIXED_BITS   0x0000001f
+#define RX_DELAY_FIXED_MSB    4
+#define RX_DELAY_FIXED_LSB    0
+// Register    : NDIV
+#define NDIV        0x00000018
+#define NDIV_BITS   0x1fff0000
+#define NDIV_RESET  0x00110000
+// Field       : NDIV_DIVB
+#define NDIV_DIVB_RESET  0x001
+#define NDIV_DIVB_BITS   0x1ff00000
+#define NDIV_DIVB_MSB    28
+#define NDIV_DIVB_LSB    20
+// Field       : NDIV_DIVA
+#define NDIV_DIVA_RESET  0x1
+#define NDIV_DIVA_BITS   0x000f0000
+#define NDIV_DIVA_MSB    19
+#define NDIV_DIVA_LSB    16
+// Register    : CS
+#define CS        0x0000001c
+#define CS_BITS   0x00111101
+#define CS_RESET  0x00000001
+// Field       : CS_RX_DEL_UPDATED
+#define CS_RX_DEL_UPDATED_RESET  0x0
+#define CS_RX_DEL_UPDATED_BITS   0x00100000
+#define CS_RX_DEL_UPDATED_MSB    20
+#define CS_RX_DEL_UPDATED_LSB    20
+// Field       : CS_RX_CLK_RUNNING
+#define CS_RX_CLK_RUNNING_RESET  0x0
+#define CS_RX_CLK_RUNNING_BITS   0x00010000
+#define CS_RX_CLK_RUNNING_MSB    16
+#define CS_RX_CLK_RUNNING_LSB    16
+// Field       : CS_SD_CLK_RUNNING
+#define CS_SD_CLK_RUNNING_RESET  0x0
+#define CS_SD_CLK_RUNNING_BITS   0x00001000
+#define CS_SD_CLK_RUNNING_MSB    12
+#define CS_SD_CLK_RUNNING_LSB    12
+// Field       : CS_TX_CLK_RUNNING
+#define CS_TX_CLK_RUNNING_RESET  0x0
+#define CS_TX_CLK_RUNNING_BITS   0x00000100
+#define CS_TX_CLK_RUNNING_MSB    8
+#define CS_TX_CLK_RUNNING_LSB    8
+// Field       : CS_RESET
+#define CS_RESET_RESET  0x1
+#define CS_RESET_BITS   0x00000001
+#define CS_RESET_MSB    0
+#define CS_RESET_LSB    0
+
+#define FPGA_SRC_RATE 400000000
+
+/* Base number of steps to delay in relation to tx clk.
+ * The relationship of the 3 clocks are as follows:
+ * tx_clk: This clock is provided to the controller. Data is sent out
+ * to the pads using this clock.
+ * sd_clk: This clock is sent out to the card.
+ * rx_clk: This clock is used to sample the data coming back from the card.
+ * This may need to be several steps ahead of the tx_clk. The default rx delay
+ * is used as a base delay, and can be further adjusted by the sd host
+ * controller during the tuning process if using a DDR50 or faster SD card
+ */
+/*
+ * PRJY-1813 - the default SD clock delay needs to be set to ~60% of the total
+ * number of steps to meet tISU (>6ns) and tIH (>2ns) in high-speed mode.
+ * On FPGA this means delay SDCLK by 5, and sample RX with a delay of 6.
+ */
+#define DEFAULT_RX_DELAY 6
+#define DEFAULT_SD_DELAY 5
+
+struct rp1_sdio_clkgen {
+	struct device *dev;
+
+	/* Source clock. Either PLL VCO or fixed freq on FPGA */
+	struct clk *src_clk;
+	/* Desired base frequency. Max freq card can go */
+	struct clk *base_clk;
+
+	struct clk_hw hw;
+	void __iomem *regs;
+
+	/* Starting value of local register before changing freq */
+	u32 local_base;
+};
+
+static inline void clkgen_write(struct rp1_sdio_clkgen *clkgen, u32 reg, u32 val)
+{
+	dev_dbg(clkgen->dev, "%s: write reg 0x%x: 0x%x\n", __func__, reg, val);
+	writel(val, clkgen->regs + reg);
+}
+
+static inline u32 clkgen_read(struct rp1_sdio_clkgen *clkgen, u32 reg)
+{
+	u32 val = readl(clkgen->regs + reg);
+
+	dev_dbg(clkgen->dev, "%s: read reg 0x%x: 0x%x\n", __func__, reg, val);
+	return val;
+}
+
+static int get_steps(unsigned int steps)
+{
+	int ret = -1;
+
+	if (steps == 4)
+		ret = MODE_STEPS_PER_CYCLE_VALUE_STEPS_4;
+	else if (steps == 5)
+		ret = MODE_STEPS_PER_CYCLE_VALUE_STEPS_5;
+	else if (steps == 6)
+		ret = MODE_STEPS_PER_CYCLE_VALUE_STEPS_6;
+	else if (steps == 8)
+		ret = MODE_STEPS_PER_CYCLE_VALUE_STEPS_8;
+	else if (steps == 10)
+		ret = MODE_STEPS_PER_CYCLE_VALUE_STEPS_10;
+	else if (steps == 12)
+		ret = MODE_STEPS_PER_CYCLE_VALUE_STEPS_12;
+	else if (steps == 16)
+		ret = MODE_STEPS_PER_CYCLE_VALUE_STEPS_16;
+	else if (steps == 20)
+		ret = MODE_STEPS_PER_CYCLE_VALUE_STEPS_20;
+	return ret;
+}
+
+static int rp1_sdio_clk_init(struct rp1_sdio_clkgen *clkgen)
+{
+	unsigned long src_rate = clk_get_rate(clkgen->src_clk);
+	unsigned long base_rate = clk_get_rate(clkgen->base_clk);
+	unsigned int steps = src_rate / base_rate;
+	u32 reg = 0;
+	int steps_value = 0;
+
+	dev_dbg(clkgen->dev, "init: src_rate %lu, base_rate %lu, steps %d\n",
+		src_rate, base_rate, steps);
+
+	/* Assert reset while we set up clkgen */
+	clkgen_write(clkgen, CS, CS_RESET_BITS);
+
+	/* Pick clock source */
+	if (src_rate == FPGA_SRC_RATE) {
+		/* Using ALT SRC */
+		reg |= MODE_SRC_SEL_VALUE_CLK_ALT_SRC << MODE_SRC_SEL_LSB;
+	} else {
+		/* Assume we are using PLL SYS VCO */
+		reg |= MODE_SRC_SEL_VALUE_PLL_SYS_VCO << MODE_SRC_SEL_LSB;
+	}
+
+	/* How many delay steps are available in one cycle for this source */
+	steps_value = get_steps(steps);
+	if (steps_value < 0) {
+		dev_err(clkgen->dev, "Invalid step value: %d\n", steps);
+		return -EINVAL;
+	}
+	reg |= steps_value << MODE_STEPS_PER_CYCLE_LSB;
+
+	/* Mode register is done now*/
+	clkgen_write(clkgen, MODE, reg);
+
+	/* Now set delay mode */
+	/* Clamp value if out of range rx delay is used */
+	reg = RX_DELAY_OVERFLOW_VALUE_CLAMP << RX_DELAY_OVERFLOW_LSB;
+	/* SD tuning bus goes from 0x0 to 0xf but we don't necessarily have that
+	 * many steps available depending on the source so map 0x0 -> 0xf to one
+	 * cycle of rx delay
+	 */
+	reg |= RX_DELAY_MAP_VALUE_STRETCH << RX_DELAY_MAP_LSB;
+
+	/* Default RX delay */
+	dev_dbg(clkgen->dev, "default rx delay %d\n", DEFAULT_RX_DELAY);
+	reg |= (DEFAULT_RX_DELAY & RX_DELAY_FIXED_BITS) << RX_DELAY_FIXED_LSB;
+	clkgen_write(clkgen, RX_DELAY, reg);
+
+	/* Default SD delay */
+	dev_dbg(clkgen->dev, "default sd delay %d\n", DEFAULT_SD_DELAY);
+	reg = (DEFAULT_SD_DELAY & SD_DELAY_STEPS_BITS) << SD_DELAY_STEPS_LSB;
+	clkgen_write(clkgen, SD_DELAY, reg);
+
+	/* We select freq, we turn on tx clock, we turn on sd clk,
+	 * we pick clock generator mode
+	 */
+	reg = USE_LOCAL_FREQ_SEL_BITS | USE_LOCAL_CARD_CLK_EN_BITS |
+	      USE_LOCAL_CLK2CARD_ON_BITS | USE_LOCAL_CLK_GEN_SEL_BITS;
+	clkgen_write(clkgen, USE_LOCAL, reg);
+
+	/* Deassert reset. Reset bit is only writable bit of CS
+	 * reg so fine to write a 0.
+	 */
+	clkgen_write(clkgen, CS, 0);
+
+	return 0;
+}
+
+#define RUNNING	\
+	(CS_TX_CLK_RUNNING_BITS | CS_RX_CLK_RUNNING_BITS | \
+	 CS_SD_CLK_RUNNING_BITS)
+static int rp1_sdio_clk_is_prepared(struct clk_hw *hw)
+{
+	struct rp1_sdio_clkgen *clkgen =
+		container_of(hw, struct rp1_sdio_clkgen, hw);
+	u32 status;
+
+	dev_dbg(clkgen->dev, "is_prepared\n");
+	status = clkgen_read(clkgen, CS);
+	return ((status & RUNNING) == RUNNING);
+}
+
+/* Can define an additional divider if an sd card isn't working at full speed */
+/* #define SLOWDOWN 3 */
+
+static unsigned long rp1_sdio_clk_get_rate(struct clk_hw *hw,
+					   unsigned long parent_rate)
+{
+	/* Get the current rate */
+	struct rp1_sdio_clkgen *clkgen =
+		container_of(hw, struct rp1_sdio_clkgen, hw);
+	unsigned long actual_rate = 0;
+	u32 ndiv_diva;
+	u32 ndiv_divb;
+	u32 tmp;
+	u32 div;
+
+	tmp = clkgen_read(clkgen, LOCAL);
+	if ((tmp & LOCAL_CLK2CARD_ON_BITS) == 0) {
+		dev_dbg(clkgen->dev, "get_rate 0\n");
+		return 0;
+	}
+
+	tmp = clkgen_read(clkgen, NDIV);
+	ndiv_diva = (tmp & NDIV_DIVA_BITS) >> NDIV_DIVA_LSB;
+	ndiv_divb = (tmp & NDIV_DIVB_BITS) >> NDIV_DIVB_LSB;
+	div = ndiv_diva * ndiv_divb;
+	actual_rate = (clk_get_rate(clkgen->base_clk) / div);
+
+#ifdef SLOWDOWN
+	actual_rate *= SLOWDOWN;
+#endif
+
+	dev_dbg(clkgen->dev, "get_rate. ndiv_diva %d, ndiv_divb %d = %lu\n",
+		ndiv_diva, ndiv_divb, actual_rate);
+
+	return actual_rate;
+}
+
+static int rp1_sdio_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+				 unsigned long parent_rate)
+{
+	struct rp1_sdio_clkgen *clkgen =
+		container_of(hw, struct rp1_sdio_clkgen, hw);
+	u32 div;
+	u32 reg;
+
+	dev_dbg(clkgen->dev, "set_rate %lu\n", rate);
+
+	if (rate == 0) {
+		/* Keep tx clock running */
+		clkgen_write(clkgen, LOCAL, LOCAL_CARD_CLK_EN_BITS);
+		return 0;
+	}
+
+#ifdef SLOWDOWN
+	rate /= SLOWDOWN;
+#endif
+
+	div = (clk_get_rate(clkgen->base_clk) / rate) - 1;
+	reg = LOCAL_CLK_GEN_SEL_BITS | LOCAL_CARD_CLK_EN_BITS |
+	      LOCAL_CLK2CARD_ON_BITS | (div << LOCAL_FREQ_SEL_LSB);
+	clkgen_write(clkgen, LOCAL, reg);
+
+	return 0;
+}
+
+#define MAX_NDIV (256 * 8)
+static int rp1_sdio_clk_determine_rate(struct clk_hw *hw,
+				       struct clk_rate_request *req)
+{
+	unsigned long rate;
+	struct rp1_sdio_clkgen *clkgen =
+		container_of(hw, struct rp1_sdio_clkgen, hw);
+	unsigned long base_rate = clk_get_rate(clkgen->base_clk);
+	u32 div;
+
+	/* What is the actual rate I can get if I request xyz */
+	if (req->rate) {
+		div = min((u32)(base_rate / req->rate), (u32)MAX_NDIV);
+		rate = base_rate / div;
+		req->rate = rate;
+		dev_dbg(clkgen->dev, "determine_rate %lu: %lu / %d = %lu\n",
+			req->rate, base_rate, div, rate);
+	} else {
+		rate = 0;
+		dev_dbg(clkgen->dev, "determine_rate %lu: %lu\n", req->rate,
+			rate);
+	}
+
+	return 0;
+}
+
+static const struct clk_ops rp1_sdio_clk_ops = {
+	.is_prepared    = rp1_sdio_clk_is_prepared,
+	.recalc_rate    = rp1_sdio_clk_get_rate,
+	.set_rate       = rp1_sdio_clk_set_rate,
+	.determine_rate = rp1_sdio_clk_determine_rate,
+};
+
+static int rp1_sdio_clk_probe(struct platform_device *pdev)
+{
+	struct device_node *node = pdev->dev.of_node;
+	struct rp1_sdio_clkgen *clkgen;
+	void __iomem *regs;
+	struct clk_init_data init = {};
+	int ret;
+
+	clkgen = devm_kzalloc(&pdev->dev, sizeof(*clkgen), GFP_KERNEL);
+	if (!clkgen)
+		return -ENOMEM;
+	platform_set_drvdata(pdev, clkgen);
+
+	clkgen->dev = &pdev->dev;
+
+	/* Source freq */
+	clkgen->src_clk = devm_clk_get(&pdev->dev, "src");
+	if (IS_ERR(clkgen->src_clk)) {
+		int err = PTR_ERR(clkgen->src_clk);
+
+		dev_err(&pdev->dev, "failed to get src clk: %d\n", err);
+		return err;
+	}
+
+	/* Desired maximum output freq (i.e. base freq) */
+	clkgen->base_clk = devm_clk_get(&pdev->dev, "base");
+	if (IS_ERR(clkgen->base_clk)) {
+		int err = PTR_ERR(clkgen->base_clk);
+
+		dev_err(&pdev->dev, "failed to get base clk: %d\n", err);
+		return err;
+	}
+
+	regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(regs))
+		return PTR_ERR(regs);
+
+	init.name = node->name;
+	init.ops = &rp1_sdio_clk_ops;
+	init.flags = CLK_GET_RATE_NOCACHE;
+
+	clkgen->hw.init = &init;
+	clkgen->regs = regs;
+
+	dev_info(&pdev->dev, "loaded %s\n", init.name);
+
+	ret = devm_clk_hw_register(&pdev->dev, &clkgen->hw);
+	if (ret)
+		return ret;
+
+	ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, &clkgen->hw);
+	if (ret)
+		return ret;
+
+	ret = rp1_sdio_clk_init(clkgen);
+	return ret;
+}
+
+static void rp1_sdio_clk_remove(struct platform_device *pdev)
+{
+}
+
+static const struct of_device_id rp1_sdio_clk_dt_ids[] = {
+	{ .compatible = "raspberrypi,rp1-sdio-clk", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rp1_sdio_clk_dt_ids);
+
+static struct platform_driver rp1_sdio_clk_driver = {
+	.probe	= rp1_sdio_clk_probe,
+	.remove	= rp1_sdio_clk_remove,
+	.driver	= {
+		.name		= "rp1-sdio-clk",
+		.of_match_table	= rp1_sdio_clk_dt_ids,
+	},
+};
+module_platform_driver(rp1_sdio_clk_driver);
+
+MODULE_AUTHOR("Liam Fraser <liam@raspberrypi.com>");
+MODULE_DESCRIPTION("RP1 SDIO clock driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/clk/clk-rp1.c b/drivers/clk/clk-rp1.c
new file mode 100644
index 000000000000..a04d218dd384
--- /dev/null
+++ b/drivers/clk/clk-rp1.c
@@ -0,0 +1,2494 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2023 Raspberry Pi Ltd.
+ *
+ * Clock driver for RP1 PCIe multifunction chip.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clk.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/rp1_platform.h>
+#include <linux/slab.h>
+
+#include <asm/div64.h>
+
+#include <dt-bindings/clock/rp1.h>
+
+#define PLL_SYS_CS			0x08000
+#define PLL_SYS_PWR			0x08004
+#define PLL_SYS_FBDIV_INT		0x08008
+#define PLL_SYS_FBDIV_FRAC		0x0800c
+#define PLL_SYS_PRIM			0x08010
+#define PLL_SYS_SEC			0x08014
+
+#define PLL_AUDIO_CS			0x0c000
+#define PLL_AUDIO_PWR			0x0c004
+#define PLL_AUDIO_FBDIV_INT		0x0c008
+#define PLL_AUDIO_FBDIV_FRAC		0x0c00c
+#define PLL_AUDIO_PRIM			0x0c010
+#define PLL_AUDIO_SEC			0x0c014
+#define PLL_AUDIO_TERN			0x0c018
+
+#define PLL_VIDEO_CS			0x10000
+#define PLL_VIDEO_PWR			0x10004
+#define PLL_VIDEO_FBDIV_INT		0x10008
+#define PLL_VIDEO_FBDIV_FRAC		0x1000c
+#define PLL_VIDEO_PRIM			0x10010
+#define PLL_VIDEO_SEC			0x10014
+
+#define GPCLK_OE_CTRL			0x00000
+
+#define CLK_SYS_CTRL			0x00014
+#define CLK_SYS_DIV_INT			0x00018
+#define CLK_SYS_SEL			0x00020
+
+#define CLK_SLOW_SYS_CTRL		0x00024
+#define CLK_SLOW_SYS_DIV_INT		0x00028
+#define CLK_SLOW_SYS_SEL		0x00030
+
+#define CLK_DMA_CTRL			0x00044
+#define CLK_DMA_DIV_INT			0x00048
+#define CLK_DMA_SEL			0x00050
+
+#define CLK_UART_CTRL			0x00054
+#define CLK_UART_DIV_INT		0x00058
+#define CLK_UART_SEL			0x00060
+
+#define CLK_ETH_CTRL			0x00064
+#define CLK_ETH_DIV_INT			0x00068
+#define CLK_ETH_SEL			0x00070
+
+#define CLK_PWM0_CTRL			0x00074
+#define CLK_PWM0_DIV_INT		0x00078
+#define CLK_PWM0_DIV_FRAC		0x0007c
+#define CLK_PWM0_SEL			0x00080
+
+#define CLK_PWM1_CTRL			0x00084
+#define CLK_PWM1_DIV_INT		0x00088
+#define CLK_PWM1_DIV_FRAC		0x0008c
+#define CLK_PWM1_SEL			0x00090
+
+#define CLK_AUDIO_IN_CTRL		0x00094
+#define CLK_AUDIO_IN_DIV_INT		0x00098
+#define CLK_AUDIO_IN_SEL		0x000a0
+
+#define CLK_AUDIO_OUT_CTRL		0x000a4
+#define CLK_AUDIO_OUT_DIV_INT		0x000a8
+#define CLK_AUDIO_OUT_SEL		0x000b0
+
+#define CLK_I2S_CTRL			0x000b4
+#define CLK_I2S_DIV_INT			0x000b8
+#define CLK_I2S_SEL			0x000c0
+
+#define CLK_MIPI0_CFG_CTRL		0x000c4
+#define CLK_MIPI0_CFG_DIV_INT		0x000c8
+#define CLK_MIPI0_CFG_SEL		0x000d0
+
+#define CLK_MIPI1_CFG_CTRL		0x000d4
+#define CLK_MIPI1_CFG_DIV_INT		0x000d8
+#define CLK_MIPI1_CFG_SEL		0x000e0
+
+#define CLK_PCIE_AUX_CTRL		0x000e4
+#define CLK_PCIE_AUX_DIV_INT		0x000e8
+#define CLK_PCIE_AUX_SEL		0x000f0
+
+#define CLK_USBH0_MICROFRAME_CTRL	0x000f4
+#define CLK_USBH0_MICROFRAME_DIV_INT	0x000f8
+#define CLK_USBH0_MICROFRAME_SEL	0x00100
+
+#define CLK_USBH1_MICROFRAME_CTRL	0x00104
+#define CLK_USBH1_MICROFRAME_DIV_INT	0x00108
+#define CLK_USBH1_MICROFRAME_SEL	0x00110
+
+#define CLK_USBH0_SUSPEND_CTRL		0x00114
+#define CLK_USBH0_SUSPEND_DIV_INT	0x00118
+#define CLK_USBH0_SUSPEND_SEL		0x00120
+
+#define CLK_USBH1_SUSPEND_CTRL		0x00124
+#define CLK_USBH1_SUSPEND_DIV_INT	0x00128
+#define CLK_USBH1_SUSPEND_SEL		0x00130
+
+#define CLK_ETH_TSU_CTRL		0x00134
+#define CLK_ETH_TSU_DIV_INT		0x00138
+#define CLK_ETH_TSU_SEL			0x00140
+
+#define CLK_ADC_CTRL			0x00144
+#define CLK_ADC_DIV_INT			0x00148
+#define CLK_ADC_SEL			0x00150
+
+#define CLK_SDIO_TIMER_CTRL		0x00154
+#define CLK_SDIO_TIMER_DIV_INT		0x00158
+#define CLK_SDIO_TIMER_SEL		0x00160
+
+#define CLK_SDIO_ALT_SRC_CTRL		0x00164
+#define CLK_SDIO_ALT_SRC_DIV_INT	0x00168
+#define CLK_SDIO_ALT_SRC_SEL		0x00170
+
+#define CLK_GP0_CTRL			0x00174
+#define CLK_GP0_DIV_INT			0x00178
+#define CLK_GP0_DIV_FRAC		0x0017c
+#define CLK_GP0_SEL			0x00180
+
+#define CLK_GP1_CTRL			0x00184
+#define CLK_GP1_DIV_INT			0x00188
+#define CLK_GP1_DIV_FRAC		0x0018c
+#define CLK_GP1_SEL			0x00190
+
+#define CLK_GP2_CTRL			0x00194
+#define CLK_GP2_DIV_INT			0x00198
+#define CLK_GP2_DIV_FRAC		0x0019c
+#define CLK_GP2_SEL			0x001a0
+
+#define CLK_GP3_CTRL			0x001a4
+#define CLK_GP3_DIV_INT			0x001a8
+#define CLK_GP3_DIV_FRAC		0x001ac
+#define CLK_GP3_SEL			0x001b0
+
+#define CLK_GP4_CTRL			0x001b4
+#define CLK_GP4_DIV_INT			0x001b8
+#define CLK_GP4_DIV_FRAC		0x001bc
+#define CLK_GP4_SEL			0x001c0
+
+#define CLK_GP5_CTRL			0x001c4
+#define CLK_GP5_DIV_INT			0x001c8
+#define CLK_GP5_DIV_FRAC		0x001cc
+#define CLK_GP5_SEL			0x001d0
+
+#define CLK_SYS_RESUS_CTRL		0x0020c
+
+#define CLK_SLOW_SYS_RESUS_CTRL		0x00214
+
+#define FC0_REF_KHZ			0x0021c
+#define FC0_MIN_KHZ			0x00220
+#define FC0_MAX_KHZ			0x00224
+#define FC0_DELAY			0x00228
+#define FC0_INTERVAL			0x0022c
+#define FC0_SRC				0x00230
+#define FC0_STATUS			0x00234
+#define FC0_RESULT			0x00238
+#define FC_SIZE				0x20
+#define FC_COUNT			8
+#define FC_NUM(idx, off)		((idx) * 32 + (off))
+
+#define AUX_SEL				1
+
+#define VIDEO_CLOCKS_OFFSET		0x4000
+#define VIDEO_CLK_VEC_CTRL		(VIDEO_CLOCKS_OFFSET + 0x0000)
+#define VIDEO_CLK_VEC_DIV_INT		(VIDEO_CLOCKS_OFFSET + 0x0004)
+#define VIDEO_CLK_VEC_SEL		(VIDEO_CLOCKS_OFFSET + 0x000c)
+#define VIDEO_CLK_DPI_CTRL		(VIDEO_CLOCKS_OFFSET + 0x0010)
+#define VIDEO_CLK_DPI_DIV_INT		(VIDEO_CLOCKS_OFFSET + 0x0014)
+#define VIDEO_CLK_DPI_SEL		(VIDEO_CLOCKS_OFFSET + 0x001c)
+#define VIDEO_CLK_MIPI0_DPI_CTRL	(VIDEO_CLOCKS_OFFSET + 0x0020)
+#define VIDEO_CLK_MIPI0_DPI_DIV_INT	(VIDEO_CLOCKS_OFFSET + 0x0024)
+#define VIDEO_CLK_MIPI0_DPI_DIV_FRAC	(VIDEO_CLOCKS_OFFSET + 0x0028)
+#define VIDEO_CLK_MIPI0_DPI_SEL		(VIDEO_CLOCKS_OFFSET + 0x002c)
+#define VIDEO_CLK_MIPI1_DPI_CTRL	(VIDEO_CLOCKS_OFFSET + 0x0030)
+#define VIDEO_CLK_MIPI1_DPI_DIV_INT	(VIDEO_CLOCKS_OFFSET + 0x0034)
+#define VIDEO_CLK_MIPI1_DPI_DIV_FRAC	(VIDEO_CLOCKS_OFFSET + 0x0038)
+#define VIDEO_CLK_MIPI1_DPI_SEL		(VIDEO_CLOCKS_OFFSET + 0x003c)
+
+#define DIV_INT_8BIT_MAX		0x000000ffu /* max divide for most clocks */
+#define DIV_INT_16BIT_MAX		0x0000ffffu /* max divide for GPx, PWM */
+#define DIV_INT_24BIT_MAX               0x00ffffffu /* max divide for CLK_SYS */
+
+#define FC0_STATUS_DONE			BIT(4)
+#define FC0_STATUS_RUNNING		BIT(8)
+#define FC0_RESULT_FRAC_SHIFT		5
+
+#define PLL_PRIM_DIV1_SHIFT		16
+#define PLL_PRIM_DIV1_MASK		0x00070000
+#define PLL_PRIM_DIV2_SHIFT		12
+#define PLL_PRIM_DIV2_MASK		0x00007000
+
+#define PLL_SEC_DIV_SHIFT		8
+#define PLL_SEC_DIV_WIDTH		5
+#define PLL_SEC_DIV_MASK		0x00001f00
+
+#define PLL_CS_LOCK			BIT(31)
+#define PLL_CS_REFDIV_SHIFT		0
+
+#define PLL_PWR_PD			BIT(0)
+#define PLL_PWR_DACPD			BIT(1)
+#define PLL_PWR_DSMPD			BIT(2)
+#define PLL_PWR_POSTDIVPD		BIT(3)
+#define PLL_PWR_4PHASEPD		BIT(4)
+#define PLL_PWR_VCOPD			BIT(5)
+#define PLL_PWR_MASK			0x0000003f
+
+#define PLL_SEC_RST			BIT(16)
+#define PLL_SEC_IMPL			BIT(31)
+
+/* PLL phase output for both PRI and SEC */
+#define PLL_PH_EN			BIT(4)
+#define PLL_PH_PHASE_SHIFT		0
+
+#define RP1_PLL_PHASE_0			0
+#define RP1_PLL_PHASE_90		1
+#define RP1_PLL_PHASE_180		2
+#define RP1_PLL_PHASE_270		3
+
+/* Clock fields for all clocks */
+#define CLK_CTRL_ENABLE			BIT(11)
+#define CLK_CTRL_AUXSRC_MASK		0x000003e0
+#define CLK_CTRL_AUXSRC_SHIFT		5
+#define CLK_CTRL_SRC_SHIFT		0
+#define CLK_DIV_FRAC_BITS		16
+
+#define KHz				1000
+#define MHz				(KHz * KHz)
+#define LOCK_TIMEOUT_NS			100000000
+#define FC_TIMEOUT_NS			100000000
+
+#define MAX_CLK_PARENTS	16
+
+#define MEASURE_CLOCK_RATE
+const char * const fc0_ref_clk_name = "clk_slow_sys";
+
+#define ABS_DIFF(a, b) ((a) > (b) ? (a) - (b) : (b) - (a))
+#define DIV_NEAREST(a, b) (((a) + ((b) >> 1)) / (b))
+#define DIV_U64_NEAREST(a, b) div_u64(((a) + ((b) >> 1)), (b))
+
+/*
+ * Names of the reference clock for the pll cores.  This name must match
+ * the DT reference clock-output-name.
+ */
+static const char *const ref_clock = "xosc";
+
+/*
+ * Secondary PLL channel output divider table.
+ * Divider values range from 8 to 19.
+ * Invalid values default to 19
+ */
+static const struct clk_div_table pll_sec_div_table[] = {
+	{ 0x00, 19 },
+	{ 0x01, 19 },
+	{ 0x02, 19 },
+	{ 0x03, 19 },
+	{ 0x04, 19 },
+	{ 0x05, 19 },
+	{ 0x06, 19 },
+	{ 0x07, 19 },
+	{ 0x08,  8 },
+	{ 0x09,  9 },
+	{ 0x0a, 10 },
+	{ 0x0b, 11 },
+	{ 0x0c, 12 },
+	{ 0x0d, 13 },
+	{ 0x0e, 14 },
+	{ 0x0f, 15 },
+	{ 0x10, 16 },
+	{ 0x11, 17 },
+	{ 0x12, 18 },
+	{ 0x13, 19 },
+	{ 0x14, 19 },
+	{ 0x15, 19 },
+	{ 0x16, 19 },
+	{ 0x17, 19 },
+	{ 0x18, 19 },
+	{ 0x19, 19 },
+	{ 0x1a, 19 },
+	{ 0x1b, 19 },
+	{ 0x1c, 19 },
+	{ 0x1d, 19 },
+	{ 0x1e, 19 },
+	{ 0x1f, 19 },
+	{ 0 }
+};
+
+struct rp1_clockman {
+	struct device *dev;
+	void __iomem *regs;
+	spinlock_t regs_lock; /* spinlock for all clocks */
+
+	/* Must be last */
+	struct clk_hw_onecell_data onecell;
+};
+
+struct rp1_pll_core_data {
+	const char *name;
+	u32 cs_reg;
+	u32 pwr_reg;
+	u32 fbdiv_int_reg;
+	u32 fbdiv_frac_reg;
+	unsigned long flags;
+	u32 fc0_src;
+};
+
+struct rp1_pll_data {
+	const char *name;
+	const char *source_pll;
+	u32 ctrl_reg;
+	unsigned long flags;
+	u32 fc0_src;
+};
+
+struct rp1_pll_ph_data {
+	const char *name;
+	const char *source_pll;
+	unsigned int phase;
+	unsigned int fixed_divider;
+	u32 ph_reg;
+	unsigned long flags;
+	u32 fc0_src;
+};
+
+struct rp1_pll_divider_data {
+	const char *name;
+	const char *source_pll;
+	u32 sec_reg;
+	unsigned long flags;
+	u32 fc0_src;
+};
+
+struct rp1_clock_data {
+	const char *name;
+	const char *const parents[MAX_CLK_PARENTS];
+	int num_std_parents;
+	int num_aux_parents;
+	unsigned long flags;
+	u32 oe_mask;
+	u32 clk_src_mask;
+	u32 ctrl_reg;
+	u32 div_int_reg;
+	u32 div_frac_reg;
+	u32 sel_reg;
+	u32 div_int_max;
+	unsigned long max_freq;
+	u32 fc0_src;
+};
+
+struct rp1_pll_core {
+	struct clk_hw hw;
+	struct rp1_clockman *clockman;
+	const struct rp1_pll_core_data *data;
+	unsigned long cached_rate;
+};
+
+struct rp1_pll {
+	struct clk_hw hw;
+	struct clk_divider div;
+	struct rp1_clockman *clockman;
+	const struct rp1_pll_data *data;
+	unsigned long cached_rate;
+};
+
+struct rp1_pll_ph {
+	struct clk_hw hw;
+	struct rp1_clockman *clockman;
+	const struct rp1_pll_ph_data *data;
+};
+
+struct rp1_clock {
+	struct clk_hw hw;
+	struct rp1_clockman *clockman;
+	const struct rp1_clock_data *data;
+	unsigned long cached_rate;
+};
+
+struct rp1_varsrc {
+	struct clk_hw hw;
+	struct rp1_clockman *clockman;
+	unsigned long rate;
+};
+
+struct rp1_clk_change {
+	struct clk_hw *hw;
+	unsigned long new_rate;
+};
+
+struct rp1_clk_change rp1_clk_chg_tree[3];
+
+static struct clk_hw *clk_xosc;
+static struct clk_hw *clk_audio;
+static struct clk_hw *clk_i2s;
+
+static void rp1_debugfs_regset(struct rp1_clockman *clockman, u32 base,
+			       const struct debugfs_reg32 *regs,
+			       size_t nregs, struct dentry *dentry)
+{
+	struct debugfs_regset32 *regset;
+
+	regset = devm_kzalloc(clockman->dev, sizeof(*regset), GFP_KERNEL);
+	if (!regset)
+		return;
+
+	regset->regs = regs;
+	regset->nregs = nregs;
+	regset->base = clockman->regs + base;
+
+	debugfs_create_regset32("regdump", 0444, dentry, regset);
+}
+
+static inline u32 set_register_field(u32 reg, u32 val, u32 mask, u32 shift)
+{
+	reg &= ~mask;
+	reg |= (val << shift) & mask;
+	return reg;
+}
+
+static inline
+void clockman_write(struct rp1_clockman *clockman, u32 reg, u32 val)
+{
+	writel(val, clockman->regs + reg);
+}
+
+static inline u32 clockman_read(struct rp1_clockman *clockman, u32 reg)
+{
+	return readl(clockman->regs + reg);
+}
+
+#ifdef MEASURE_CLOCK_RATE
+static unsigned long clockman_measure_clock(struct rp1_clockman *clockman,
+					    const char *clk_name,
+					    unsigned int fc0_src)
+{
+	struct clk *ref_clk = __clk_lookup(fc0_ref_clk_name);
+	unsigned long result;
+	ktime_t timeout;
+	unsigned int fc_idx, fc_offset, fc_src;
+
+	fc_idx = fc0_src / 32;
+	fc_src = fc0_src % 32;
+
+	/* fc_src == 0 is invalid. */
+	if (!fc_src || fc_idx >= FC_COUNT)
+		return 0;
+
+	fc_offset = fc_idx * FC_SIZE;
+
+	/* Ensure the frequency counter is idle. */
+	timeout = ktime_add_ns(ktime_get(), FC_TIMEOUT_NS);
+	while (clockman_read(clockman, fc_offset + FC0_STATUS) & FC0_STATUS_RUNNING) {
+		if (ktime_after(ktime_get(), timeout)) {
+			dev_err(clockman->dev, "%s: FC0 busy timeout\n",
+				clk_name);
+			return 0;
+		}
+		cpu_relax();
+	}
+
+	spin_lock(&clockman->regs_lock);
+	clockman_write(clockman, fc_offset + FC0_REF_KHZ,
+		       clk_get_rate(ref_clk) / KHz);
+	clockman_write(clockman, fc_offset + FC0_MIN_KHZ, 0);
+	clockman_write(clockman, fc_offset + FC0_MAX_KHZ, 0x1ffffff);
+	clockman_write(clockman, fc_offset + FC0_INTERVAL, 8);
+	clockman_write(clockman, fc_offset + FC0_DELAY, 7);
+	clockman_write(clockman, fc_offset + FC0_SRC, fc_src);
+	spin_unlock(&clockman->regs_lock);
+
+	/* Ensure the frequency counter is idle. */
+	timeout = ktime_add_ns(ktime_get(), FC_TIMEOUT_NS);
+	while (!(clockman_read(clockman, fc_offset + FC0_STATUS) & FC0_STATUS_DONE)) {
+		if (ktime_after(ktime_get(), timeout)) {
+			dev_err(clockman->dev, "%s: FC0 wait timeout\n",
+				clk_name);
+			return 0;
+		}
+		cpu_relax();
+	}
+
+	result = clockman_read(clockman, fc_offset + FC0_RESULT);
+
+	/* Disable FC0 */
+	spin_lock(&clockman->regs_lock);
+	clockman_write(clockman, fc_offset + FC0_SRC, 0);
+	spin_unlock(&clockman->regs_lock);
+
+	return result;
+}
+#endif
+
+static int rp1_pll_core_is_on(struct clk_hw *hw)
+{
+	struct rp1_pll_core *pll_core = container_of(hw, struct rp1_pll_core, hw);
+	struct rp1_clockman *clockman = pll_core->clockman;
+	const struct rp1_pll_core_data *data = pll_core->data;
+	u32 pwr = clockman_read(clockman, data->pwr_reg);
+
+	return (pwr & PLL_PWR_PD) || (pwr & PLL_PWR_POSTDIVPD);
+}
+
+static int rp1_pll_core_on(struct clk_hw *hw)
+{
+	struct rp1_pll_core *pll_core = container_of(hw, struct rp1_pll_core, hw);
+	struct rp1_clockman *clockman = pll_core->clockman;
+	const struct rp1_pll_core_data *data = pll_core->data;
+	u32 fbdiv_frac;
+	ktime_t timeout;
+
+	spin_lock(&clockman->regs_lock);
+
+	if (!(clockman_read(clockman, data->cs_reg) & PLL_CS_LOCK)) {
+		/* Reset to a known state. */
+		clockman_write(clockman, data->pwr_reg, PLL_PWR_MASK);
+		clockman_write(clockman, data->fbdiv_int_reg, 20);
+		clockman_write(clockman, data->fbdiv_frac_reg, 0);
+		clockman_write(clockman, data->cs_reg, 1 << PLL_CS_REFDIV_SHIFT);
+	}
+
+	/* Come out of reset. */
+	fbdiv_frac = clockman_read(clockman, data->fbdiv_frac_reg);
+	clockman_write(clockman, data->pwr_reg, fbdiv_frac ? 0 : PLL_PWR_DSMPD);
+	spin_unlock(&clockman->regs_lock);
+
+	/* Wait for the PLL to lock. */
+	timeout = ktime_add_ns(ktime_get(), LOCK_TIMEOUT_NS);
+	while (!(clockman_read(clockman, data->cs_reg) & PLL_CS_LOCK)) {
+		if (ktime_after(ktime_get(), timeout)) {
+			dev_err(clockman->dev, "%s: can't lock PLL\n",
+				clk_hw_get_name(hw));
+			return -ETIMEDOUT;
+		}
+		cpu_relax();
+	}
+
+	return 0;
+}
+
+static void rp1_pll_core_off(struct clk_hw *hw)
+{
+	struct rp1_pll_core *pll_core = container_of(hw, struct rp1_pll_core, hw);
+	struct rp1_clockman *clockman = pll_core->clockman;
+	const struct rp1_pll_core_data *data = pll_core->data;
+
+	spin_lock(&clockman->regs_lock);
+	clockman_write(clockman, data->pwr_reg, 0);
+	spin_unlock(&clockman->regs_lock);
+}
+
+static inline unsigned long get_pll_core_divider(struct clk_hw *hw,
+						 unsigned long rate,
+						 unsigned long parent_rate,
+						 u32 *div_int, u32 *div_frac)
+{
+	unsigned long calc_rate;
+	u32 fbdiv_int, fbdiv_frac;
+	u64 div_fp64; /* 32.32 fixed point fraction. */
+
+	/* Factor of reference clock to VCO frequency. */
+	div_fp64 = (u64)(rate) << 32;
+	div_fp64 = DIV_U64_NEAREST(div_fp64, parent_rate);
+
+	/* Round the fractional component at 24 bits. */
+	div_fp64 += 1 << (32 - 24 - 1);
+
+	fbdiv_int = div_fp64 >> 32;
+	fbdiv_frac = (div_fp64 >> (32 - 24)) & 0xffffff;
+
+	calc_rate =
+		((u64)parent_rate * (((u64)fbdiv_int << 24) + fbdiv_frac) + (1 << 23)) >> 24;
+
+	*div_int = fbdiv_int;
+	*div_frac = fbdiv_frac;
+
+	return calc_rate;
+}
+
+static int rp1_pll_core_set_rate(struct clk_hw *hw,
+				 unsigned long rate, unsigned long parent_rate)
+{
+	struct rp1_pll_core *pll_core = container_of(hw, struct rp1_pll_core, hw);
+	struct rp1_clockman *clockman = pll_core->clockman;
+	const struct rp1_pll_core_data *data = pll_core->data;
+	unsigned long calc_rate;
+	u32 fbdiv_int, fbdiv_frac;
+
+	// todo: is this needed??
+	//rp1_pll_off(hw);
+
+	/* Disable dividers to start with. */
+	spin_lock(&clockman->regs_lock);
+	clockman_write(clockman, data->fbdiv_int_reg, 0);
+	clockman_write(clockman, data->fbdiv_frac_reg, 0);
+	spin_unlock(&clockman->regs_lock);
+
+	calc_rate = get_pll_core_divider(hw, rate, parent_rate,
+					 &fbdiv_int, &fbdiv_frac);
+
+	spin_lock(&clockman->regs_lock);
+	clockman_write(clockman, data->pwr_reg, fbdiv_frac ? 0 : PLL_PWR_DSMPD);
+	clockman_write(clockman, data->fbdiv_int_reg, fbdiv_int);
+	clockman_write(clockman, data->fbdiv_frac_reg, fbdiv_frac);
+	spin_unlock(&clockman->regs_lock);
+
+	/* Check that reference frequency is no greater than VCO / 16. */
+	BUG_ON(parent_rate > (rate / 16));
+
+	pll_core->cached_rate = calc_rate;
+
+	spin_lock(&clockman->regs_lock);
+	/* Don't need to divide ref unless parent_rate > (output freq / 16) */
+	clockman_write(clockman, data->cs_reg,
+		       clockman_read(clockman, data->cs_reg) |
+				     (1 << PLL_CS_REFDIV_SHIFT));
+	spin_unlock(&clockman->regs_lock);
+
+	return 0;
+}
+
+static unsigned long rp1_pll_core_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct rp1_pll_core *pll_core = container_of(hw, struct rp1_pll_core, hw);
+	struct rp1_clockman *clockman = pll_core->clockman;
+	const struct rp1_pll_core_data *data = pll_core->data;
+	u32 fbdiv_int, fbdiv_frac;
+	unsigned long calc_rate;
+
+	fbdiv_int = clockman_read(clockman, data->fbdiv_int_reg);
+	fbdiv_frac = clockman_read(clockman, data->fbdiv_frac_reg);
+	calc_rate =
+		((u64)parent_rate * (((u64)fbdiv_int << 24) + fbdiv_frac) + (1 << 23)) >> 24;
+
+	return calc_rate;
+}
+
+static long rp1_pll_core_round_rate(struct clk_hw *hw, unsigned long rate,
+				    unsigned long *parent_rate)
+{
+	u32 fbdiv_int, fbdiv_frac;
+	long calc_rate;
+
+	calc_rate = get_pll_core_divider(hw, rate, *parent_rate,
+					 &fbdiv_int, &fbdiv_frac);
+	return calc_rate;
+}
+
+static void rp1_pll_core_debug_init(struct clk_hw *hw, struct dentry *dentry)
+{
+	struct rp1_pll_core *pll_core = container_of(hw, struct rp1_pll_core, hw);
+	struct rp1_clockman *clockman = pll_core->clockman;
+	const struct rp1_pll_core_data *data = pll_core->data;
+	struct debugfs_reg32 *regs;
+
+	regs = devm_kcalloc(clockman->dev, 4, sizeof(*regs), GFP_KERNEL);
+	if (!regs)
+		return;
+
+	regs[0].name = "cs";
+	regs[0].offset = data->cs_reg;
+	regs[1].name = "pwr";
+	regs[1].offset = data->pwr_reg;
+	regs[2].name = "fbdiv_int";
+	regs[2].offset = data->fbdiv_int_reg;
+	regs[3].name = "fbdiv_frac";
+	regs[3].offset = data->fbdiv_frac_reg;
+
+	rp1_debugfs_regset(clockman, 0, regs, 4, dentry);
+}
+
+static void get_pll_prim_dividers(unsigned long rate, unsigned long parent_rate,
+				  u32 *divider1, u32 *divider2)
+{
+	unsigned int div1, div2;
+	unsigned int best_div1 = 7, best_div2 = 7;
+	unsigned long best_rate_diff =
+		ABS_DIFF(DIV_ROUND_CLOSEST(parent_rate, best_div1 * best_div2), rate);
+	long rate_diff, calc_rate;
+
+	for (div1 = 1; div1 <= 7; div1++) {
+		for (div2 = 1; div2 <= div1; div2++) {
+			calc_rate = DIV_ROUND_CLOSEST(parent_rate, div1 * div2);
+			rate_diff = ABS_DIFF(calc_rate, rate);
+
+			if (calc_rate == rate) {
+				best_div1 = div1;
+				best_div2 = div2;
+				goto done;
+			} else if (rate_diff < best_rate_diff) {
+				best_div1 = div1;
+				best_div2 = div2;
+				best_rate_diff = rate_diff;
+			}
+		}
+	}
+
+done:
+	*divider1 = best_div1;
+	*divider2 = best_div2;
+}
+
+static int rp1_pll_set_rate(struct clk_hw *hw,
+			    unsigned long rate, unsigned long parent_rate)
+{
+	struct rp1_pll *pll = container_of(hw, struct rp1_pll, hw);
+	struct rp1_clockman *clockman = pll->clockman;
+	const struct rp1_pll_data *data = pll->data;
+	u32 prim, prim_div1, prim_div2;
+
+	get_pll_prim_dividers(rate, parent_rate, &prim_div1, &prim_div2);
+
+	spin_lock(&clockman->regs_lock);
+	prim = clockman_read(clockman, data->ctrl_reg);
+	prim = set_register_field(prim, prim_div1, PLL_PRIM_DIV1_MASK,
+				  PLL_PRIM_DIV1_SHIFT);
+	prim = set_register_field(prim, prim_div2, PLL_PRIM_DIV2_MASK,
+				  PLL_PRIM_DIV2_SHIFT);
+	clockman_write(clockman, data->ctrl_reg, prim);
+	spin_unlock(&clockman->regs_lock);
+
+#ifdef MEASURE_CLOCK_RATE
+	clockman_measure_clock(clockman, data->name, data->fc0_src);
+#endif
+	return 0;
+}
+
+static unsigned long rp1_pll_recalc_rate(struct clk_hw *hw,
+					 unsigned long parent_rate)
+{
+	struct rp1_pll *pll = container_of(hw, struct rp1_pll, hw);
+	struct rp1_clockman *clockman = pll->clockman;
+	const struct rp1_pll_data *data = pll->data;
+	u32 prim, prim_div1, prim_div2;
+
+	prim = clockman_read(clockman, data->ctrl_reg);
+	prim_div1 = (prim & PLL_PRIM_DIV1_MASK) >> PLL_PRIM_DIV1_SHIFT;
+	prim_div2 = (prim & PLL_PRIM_DIV2_MASK) >> PLL_PRIM_DIV2_SHIFT;
+
+	if (!prim_div1 || !prim_div2) {
+		dev_err(clockman->dev, "%s: (%s) zero divider value\n",
+			__func__, data->name);
+		return 0;
+	}
+
+	return DIV_ROUND_CLOSEST(parent_rate, prim_div1 * prim_div2);
+}
+
+static long rp1_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long *parent_rate)
+{
+	const struct rp1_clk_change *chg = &rp1_clk_chg_tree[1];
+	u32 div1, div2;
+
+	if (chg->hw == hw && chg->new_rate == rate)
+		*parent_rate = chg[1].new_rate;
+
+	get_pll_prim_dividers(rate, *parent_rate, &div1, &div2);
+
+	return DIV_ROUND_CLOSEST(*parent_rate, div1 * div2);
+}
+
+static void rp1_pll_debug_init(struct clk_hw *hw,
+			       struct dentry *dentry)
+{
+	struct rp1_pll *pll = container_of(hw, struct rp1_pll, hw);
+	struct rp1_clockman *clockman = pll->clockman;
+	const struct rp1_pll_data *data = pll->data;
+	struct debugfs_reg32 *regs;
+
+	regs = devm_kcalloc(clockman->dev, 1, sizeof(*regs), GFP_KERNEL);
+	if (!regs)
+		return;
+
+	regs[0].name = "prim";
+	regs[0].offset = data->ctrl_reg;
+
+	rp1_debugfs_regset(clockman, 0, regs, 1, dentry);
+}
+
+static int rp1_pll_ph_is_on(struct clk_hw *hw)
+{
+	struct rp1_pll_ph *pll = container_of(hw, struct rp1_pll_ph, hw);
+	struct rp1_clockman *clockman = pll->clockman;
+	const struct rp1_pll_ph_data *data = pll->data;
+
+	return !!(clockman_read(clockman, data->ph_reg) & PLL_PH_EN);
+}
+
+static int rp1_pll_ph_on(struct clk_hw *hw)
+{
+	struct rp1_pll_ph *pll_ph = container_of(hw, struct rp1_pll_ph, hw);
+	struct rp1_clockman *clockman = pll_ph->clockman;
+	const struct rp1_pll_ph_data *data = pll_ph->data;
+	u32 ph_reg;
+
+	/* todo: ensure pri/sec is enabled! */
+	spin_lock(&clockman->regs_lock);
+	ph_reg = clockman_read(clockman, data->ph_reg);
+	ph_reg |= data->phase << PLL_PH_PHASE_SHIFT;
+	ph_reg |= PLL_PH_EN;
+	clockman_write(clockman, data->ph_reg, ph_reg);
+	spin_unlock(&clockman->regs_lock);
+
+#ifdef MEASURE_CLOCK_RATE
+	clockman_measure_clock(clockman, data->name, data->fc0_src);
+#endif
+	return 0;
+}
+
+static void rp1_pll_ph_off(struct clk_hw *hw)
+{
+	struct rp1_pll_ph *pll_ph = container_of(hw, struct rp1_pll_ph, hw);
+	struct rp1_clockman *clockman = pll_ph->clockman;
+	const struct rp1_pll_ph_data *data = pll_ph->data;
+
+	spin_lock(&clockman->regs_lock);
+	clockman_write(clockman, data->ph_reg,
+		       clockman_read(clockman, data->ph_reg) & ~PLL_PH_EN);
+	spin_unlock(&clockman->regs_lock);
+}
+
+static int rp1_pll_ph_set_rate(struct clk_hw *hw,
+			       unsigned long rate, unsigned long parent_rate)
+{
+	struct rp1_pll_ph *pll_ph = container_of(hw, struct rp1_pll_ph, hw);
+	const struct rp1_pll_ph_data *data = pll_ph->data;
+	struct rp1_clockman *clockman = pll_ph->clockman;
+
+	/* Nothing really to do here! */
+	WARN_ON(data->fixed_divider != 1 && data->fixed_divider != 2);
+	WARN_ON(rate != parent_rate / data->fixed_divider);
+
+#ifdef MEASURE_CLOCK_RATE
+	if (rp1_pll_ph_is_on(hw))
+		clockman_measure_clock(clockman, data->name, data->fc0_src);
+#endif
+	return 0;
+}
+
+static unsigned long rp1_pll_ph_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct rp1_pll_ph *pll_ph = container_of(hw, struct rp1_pll_ph, hw);
+	const struct rp1_pll_ph_data *data = pll_ph->data;
+
+	return parent_rate / data->fixed_divider;
+}
+
+static long rp1_pll_ph_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	struct rp1_pll_ph *pll_ph = container_of(hw, struct rp1_pll_ph, hw);
+	const struct rp1_pll_ph_data *data = pll_ph->data;
+
+	return *parent_rate / data->fixed_divider;
+}
+
+static void rp1_pll_ph_debug_init(struct clk_hw *hw,
+				  struct dentry *dentry)
+{
+	struct rp1_pll_ph *pll_ph = container_of(hw, struct rp1_pll_ph, hw);
+	const struct rp1_pll_ph_data *data = pll_ph->data;
+	struct rp1_clockman *clockman = pll_ph->clockman;
+	struct debugfs_reg32 *regs;
+
+	regs = devm_kcalloc(clockman->dev, 1, sizeof(*regs), GFP_KERNEL);
+	if (!regs)
+		return;
+
+	regs[0].name = "ph_reg";
+	regs[0].offset = data->ph_reg;
+
+	rp1_debugfs_regset(clockman, 0, regs, 1, dentry);
+}
+
+static int rp1_pll_divider_is_on(struct clk_hw *hw)
+{
+	struct rp1_pll *divider = container_of(hw, struct rp1_pll, div.hw);
+	struct rp1_clockman *clockman = divider->clockman;
+	const struct rp1_pll_data *data = divider->data;
+
+	return !(clockman_read(clockman, data->ctrl_reg) & PLL_SEC_RST);
+}
+
+static int rp1_pll_divider_on(struct clk_hw *hw)
+{
+	struct rp1_pll *divider = container_of(hw, struct rp1_pll, div.hw);
+	struct rp1_clockman *clockman = divider->clockman;
+	const struct rp1_pll_data *data = divider->data;
+
+	spin_lock(&clockman->regs_lock);
+	/* Check the implementation bit is set! */
+	WARN_ON(!(clockman_read(clockman, data->ctrl_reg) & PLL_SEC_IMPL));
+	clockman_write(clockman, data->ctrl_reg,
+		       clockman_read(clockman, data->ctrl_reg) & ~PLL_SEC_RST);
+	spin_unlock(&clockman->regs_lock);
+
+#ifdef MEASURE_CLOCK_RATE
+	clockman_measure_clock(clockman, data->name, data->fc0_src);
+#endif
+	return 0;
+}
+
+static void rp1_pll_divider_off(struct clk_hw *hw)
+{
+	struct rp1_pll *divider = container_of(hw, struct rp1_pll, div.hw);
+	struct rp1_clockman *clockman = divider->clockman;
+	const struct rp1_pll_data *data = divider->data;
+
+	spin_lock(&clockman->regs_lock);
+	clockman_write(clockman, data->ctrl_reg,
+		       clockman_read(clockman, data->ctrl_reg) | PLL_SEC_RST);
+	spin_unlock(&clockman->regs_lock);
+}
+
+static int rp1_pll_divider_set_rate(struct clk_hw *hw,
+				    unsigned long rate,
+				    unsigned long parent_rate)
+{
+	struct rp1_pll *divider = container_of(hw, struct rp1_pll, div.hw);
+	struct rp1_clockman *clockman = divider->clockman;
+	const struct rp1_pll_data *data = divider->data;
+	u32 div, sec;
+
+	div = DIV_ROUND_UP_ULL(parent_rate, rate);
+	div = clamp(div, 8u, 19u);
+
+	spin_lock(&clockman->regs_lock);
+	sec = clockman_read(clockman, data->ctrl_reg);
+	sec = set_register_field(sec, div, PLL_SEC_DIV_MASK, PLL_SEC_DIV_SHIFT);
+
+	/* Must keep the divider in reset to change the value. */
+	sec |= PLL_SEC_RST;
+	clockman_write(clockman, data->ctrl_reg, sec);
+
+	// todo: must sleep 10 pll vco cycles
+	sec &= ~PLL_SEC_RST;
+	clockman_write(clockman, data->ctrl_reg, sec);
+	spin_unlock(&clockman->regs_lock);
+
+#ifdef MEASURE_CLOCK_RATE
+	if (rp1_pll_divider_is_on(hw))
+		clockman_measure_clock(clockman, data->name, data->fc0_src);
+#endif
+	return 0;
+}
+
+static unsigned long rp1_pll_divider_recalc_rate(struct clk_hw *hw,
+						 unsigned long parent_rate)
+{
+	return clk_divider_ops.recalc_rate(hw, parent_rate);
+}
+
+static long rp1_pll_divider_round_rate(struct clk_hw *hw,
+				       unsigned long rate,
+				       unsigned long *parent_rate)
+{
+	return clk_divider_ops.round_rate(hw, rate, parent_rate);
+}
+
+static void rp1_pll_divider_debug_init(struct clk_hw *hw, struct dentry *dentry)
+{
+	struct rp1_pll *divider = container_of(hw, struct rp1_pll, div.hw);
+	struct rp1_clockman *clockman = divider->clockman;
+	const struct rp1_pll_data *data = divider->data;
+	struct debugfs_reg32 *regs;
+
+	regs = devm_kcalloc(clockman->dev, 1, sizeof(*regs), GFP_KERNEL);
+	if (!regs)
+		return;
+
+	regs[0].name = "sec";
+	regs[0].offset = data->ctrl_reg;
+
+	rp1_debugfs_regset(clockman, 0, regs, 1, dentry);
+}
+
+static int rp1_clock_is_on(struct clk_hw *hw)
+{
+	struct rp1_clock *clock = container_of(hw, struct rp1_clock, hw);
+	struct rp1_clockman *clockman = clock->clockman;
+	const struct rp1_clock_data *data = clock->data;
+
+	return !!(clockman_read(clockman, data->ctrl_reg) & CLK_CTRL_ENABLE);
+}
+
+static unsigned long rp1_clock_recalc_rate(struct clk_hw *hw,
+					   unsigned long parent_rate)
+{
+	struct rp1_clock *clock = container_of(hw, struct rp1_clock, hw);
+	struct rp1_clockman *clockman = clock->clockman;
+	const struct rp1_clock_data *data = clock->data;
+	u64 calc_rate;
+	u64 div;
+
+	u32 frac;
+
+	div = clockman_read(clockman, data->div_int_reg);
+	frac = (data->div_frac_reg != 0) ?
+		clockman_read(clockman, data->div_frac_reg) : 0;
+
+	/* If the integer portion of the divider is 0, treat it as 2^16 */
+	if (!div)
+		div = 1 << 16;
+
+	div = (div << CLK_DIV_FRAC_BITS) | (frac >> (32 - CLK_DIV_FRAC_BITS));
+
+	calc_rate = (u64)parent_rate << CLK_DIV_FRAC_BITS;
+	calc_rate = div64_u64(calc_rate, div);
+
+	return calc_rate;
+}
+
+static int rp1_clock_on(struct clk_hw *hw)
+{
+	struct rp1_clock *clock = container_of(hw, struct rp1_clock, hw);
+	struct rp1_clockman *clockman = clock->clockman;
+	const struct rp1_clock_data *data = clock->data;
+
+	spin_lock(&clockman->regs_lock);
+	clockman_write(clockman, data->ctrl_reg,
+		       clockman_read(clockman, data->ctrl_reg) | CLK_CTRL_ENABLE);
+	/* If this is a GPCLK, turn on the output-enable */
+	if (data->oe_mask)
+		clockman_write(clockman, GPCLK_OE_CTRL,
+			       clockman_read(clockman, GPCLK_OE_CTRL) | data->oe_mask);
+	spin_unlock(&clockman->regs_lock);
+
+#ifdef MEASURE_CLOCK_RATE
+	clockman_measure_clock(clockman, data->name, data->fc0_src);
+#endif
+	return 0;
+}
+
+static void rp1_clock_off(struct clk_hw *hw)
+{
+	struct rp1_clock *clock = container_of(hw, struct rp1_clock, hw);
+	struct rp1_clockman *clockman = clock->clockman;
+	const struct rp1_clock_data *data = clock->data;
+
+	spin_lock(&clockman->regs_lock);
+	clockman_write(clockman, data->ctrl_reg,
+		       clockman_read(clockman, data->ctrl_reg) & ~CLK_CTRL_ENABLE);
+	/* If this is a GPCLK, turn off the output-enable */
+	if (data->oe_mask)
+		clockman_write(clockman, GPCLK_OE_CTRL,
+			       clockman_read(clockman, GPCLK_OE_CTRL) & ~data->oe_mask);
+	spin_unlock(&clockman->regs_lock);
+}
+
+static u32 rp1_clock_choose_div(unsigned long rate, unsigned long parent_rate,
+				const struct rp1_clock_data *data)
+{
+	u64 div;
+
+	/*
+	 * Due to earlier rounding, calculated parent_rate may differ from
+	 * expected value. Don't fail on a small discrepancy near unity divide.
+	 */
+	if (!rate || rate > parent_rate + (parent_rate >> CLK_DIV_FRAC_BITS))
+		return 0;
+
+	/*
+	 * Always express div in fixed-point format for fractional division;
+	 * If no fractional divider is present, the fraction part will be zero.
+	 */
+	if (data->div_frac_reg) {
+		div = (u64)parent_rate << CLK_DIV_FRAC_BITS;
+		div = DIV_U64_NEAREST(div, rate);
+	} else {
+		div = DIV_U64_NEAREST(parent_rate, rate);
+		div <<= CLK_DIV_FRAC_BITS;
+	}
+
+	div = clamp(div,
+		    1ull << CLK_DIV_FRAC_BITS,
+		    (u64)data->div_int_max << CLK_DIV_FRAC_BITS);
+
+	return div;
+}
+
+static u8 rp1_clock_get_parent(struct clk_hw *hw)
+{
+	struct rp1_clock *clock = container_of(hw, struct rp1_clock, hw);
+	struct rp1_clockman *clockman = clock->clockman;
+	const struct rp1_clock_data *data = clock->data;
+	u32 sel, ctrl;
+	u8 parent;
+
+	/* Sel is one-hot, so find the first bit set */
+	sel = clockman_read(clockman, data->sel_reg);
+	parent = ffs(sel) - 1;
+
+	/* sel == 0 implies the parent clock is not enabled yet. */
+	if (!sel) {
+		/* Read the clock src from the CTRL register instead */
+		ctrl = clockman_read(clockman, data->ctrl_reg);
+		parent = (ctrl & data->clk_src_mask) >> CLK_CTRL_SRC_SHIFT;
+	}
+
+	if (parent >= data->num_std_parents)
+		parent = AUX_SEL;
+
+	if (parent == AUX_SEL) {
+		/*
+		 * Clock parent is an auxiliary source, so get the parent from
+		 * the AUXSRC register field.
+		 */
+		ctrl = clockman_read(clockman, data->ctrl_reg);
+		parent = (ctrl & CLK_CTRL_AUXSRC_MASK) >> CLK_CTRL_AUXSRC_SHIFT;
+		parent += data->num_std_parents;
+	}
+
+	return parent;
+}
+
+static int rp1_clock_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct rp1_clock *clock = container_of(hw, struct rp1_clock, hw);
+	struct rp1_clockman *clockman = clock->clockman;
+	const struct rp1_clock_data *data = clock->data;
+	u32 ctrl, sel;
+
+	spin_lock(&clockman->regs_lock);
+	ctrl = clockman_read(clockman, data->ctrl_reg);
+
+	if (index >= data->num_std_parents) {
+		/* This is an aux source request */
+		if (index >= data->num_std_parents + data->num_aux_parents)
+			return -EINVAL;
+
+		/* Select parent from aux list */
+		ctrl = set_register_field(ctrl, index - data->num_std_parents,
+					  CLK_CTRL_AUXSRC_MASK,
+					  CLK_CTRL_AUXSRC_SHIFT);
+		/* Set src to aux list */
+		ctrl = set_register_field(ctrl, AUX_SEL, data->clk_src_mask,
+					  CLK_CTRL_SRC_SHIFT);
+	} else {
+		ctrl = set_register_field(ctrl, index, data->clk_src_mask,
+					  CLK_CTRL_SRC_SHIFT);
+	}
+
+	clockman_write(clockman, data->ctrl_reg, ctrl);
+	spin_unlock(&clockman->regs_lock);
+
+	sel = rp1_clock_get_parent(hw);
+	WARN(sel != index, "(%s): Parent index req %u returned back %u\n",
+	     data->name, index, sel);
+
+	return 0;
+}
+
+static int rp1_clock_set_rate_and_parent(struct clk_hw *hw,
+					 unsigned long rate,
+					 unsigned long parent_rate,
+					 u8 parent)
+{
+	struct rp1_clock *clock = container_of(hw, struct rp1_clock, hw);
+	struct rp1_clockman *clockman = clock->clockman;
+	const struct rp1_clock_data *data = clock->data;
+	u32 div = rp1_clock_choose_div(rate, parent_rate, data);
+
+	WARN(rate > 4000000000ll, "rate is -ve (%d)\n", (int)rate);
+
+	if (WARN(!div,
+		 "clk divider calculated as 0! (%s, rate %ld, parent rate %ld)\n",
+		 data->name, rate, parent_rate))
+		div = 1 << CLK_DIV_FRAC_BITS;
+
+	spin_lock(&clockman->regs_lock);
+
+	clockman_write(clockman, data->div_int_reg, div >> CLK_DIV_FRAC_BITS);
+	if (data->div_frac_reg)
+		clockman_write(clockman, data->div_frac_reg, div << (32 - CLK_DIV_FRAC_BITS));
+
+	spin_unlock(&clockman->regs_lock);
+
+	if (parent != 0xff)
+		rp1_clock_set_parent(hw, parent);
+
+#ifdef MEASURE_CLOCK_RATE
+	if (rp1_clock_is_on(hw))
+		clockman_measure_clock(clockman, data->name, data->fc0_src);
+#endif
+	return 0;
+}
+
+static int rp1_clock_set_rate(struct clk_hw *hw, unsigned long rate,
+			      unsigned long parent_rate)
+{
+	return rp1_clock_set_rate_and_parent(hw, rate, parent_rate, 0xff);
+}
+
+static unsigned long calc_core_pll_rate(struct clk_hw *pll_hw,
+					unsigned long target_rate,
+					int *pdiv_prim, int *pdiv_clk)
+{
+	static const int prim_divs[] = {
+		2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 15, 16,
+		18, 20, 21, 24, 25, 28, 30, 35, 36, 42, 49,
+	};
+	const unsigned long xosc_rate = clk_hw_get_rate(clk_xosc);
+	const unsigned long core_max = 2400000000;
+	const unsigned long core_min = xosc_rate * 16;
+	unsigned long best_rate = core_max + 1;
+	int best_div_prim = 1, best_div_clk = 1;
+	unsigned long core_rate = 0;
+	int div_int, div_frac;
+	u64 div;
+	int i;
+
+	/* Given the target rate, choose a set of divisors/multipliers */
+	for (i = 0; i < ARRAY_SIZE(prim_divs); i++) {
+		int div_prim = prim_divs[i];
+		int div_clk;
+
+		for (div_clk = 1; div_clk <= 256; div_clk++) {
+			core_rate = target_rate * div_clk * div_prim;
+			if (core_rate >= core_min) {
+				if (core_rate < best_rate) {
+					best_rate = core_rate;
+					best_div_prim = div_prim;
+					best_div_clk = div_clk;
+				}
+				break;
+			}
+		}
+	}
+
+	if (best_rate < core_max) {
+		div = ((best_rate << 24) + xosc_rate / 2) / xosc_rate;
+		div_int = div >> 24;
+		div_frac = div % (1 << 24);
+		core_rate = (xosc_rate * ((div_int << 24) + div_frac) + (1 << 23)) >> 24;
+	} else {
+		core_rate = 0;
+	}
+
+	if (pdiv_prim)
+		*pdiv_prim = best_div_prim;
+	if (pdiv_clk)
+		*pdiv_clk = best_div_clk;
+
+	return core_rate;
+}
+
+static void rp1_clock_choose_div_and_prate(struct clk_hw *hw,
+					   int parent_idx,
+					   unsigned long rate,
+					   unsigned long *prate,
+					   unsigned long *calc_rate)
+{
+	struct rp1_clock *clock = container_of(hw, struct rp1_clock, hw);
+	const struct rp1_clock_data *data = clock->data;
+	struct clk_hw *parent;
+	u32 div;
+	u64 tmp;
+	int i;
+
+	parent = clk_hw_get_parent_by_index(hw, parent_idx);
+
+	for (i = 0; i < ARRAY_SIZE(rp1_clk_chg_tree); i++) {
+		const struct rp1_clk_change *chg = &rp1_clk_chg_tree[i];
+
+		if (chg->hw == hw && chg->new_rate == rate) {
+			if (i == 2)
+				*prate = clk_hw_get_rate(clk_xosc);
+			else if (parent == rp1_clk_chg_tree[i + 1].hw)
+				*prate = rp1_clk_chg_tree[i + 1].new_rate;
+			else
+				continue;
+			*calc_rate = chg->new_rate;
+			return;
+		}
+	}
+
+	if (hw == clk_i2s && parent == clk_audio) {
+		unsigned long core_rate, audio_rate, i2s_rate;
+		int div_prim, div_clk;
+
+		core_rate = calc_core_pll_rate(parent, rate, &div_prim, &div_clk);
+		audio_rate = DIV_NEAREST(core_rate, div_prim);
+		i2s_rate = DIV_NEAREST(audio_rate, div_clk);
+		rp1_clk_chg_tree[2].hw = clk_hw_get_parent(parent);
+		rp1_clk_chg_tree[2].new_rate = core_rate;
+		rp1_clk_chg_tree[1].hw = clk_audio;
+		rp1_clk_chg_tree[1].new_rate = audio_rate;
+		rp1_clk_chg_tree[0].hw = clk_i2s;
+		rp1_clk_chg_tree[0].new_rate = i2s_rate;
+		*prate = audio_rate;
+		*calc_rate = i2s_rate;
+		return;
+	}
+
+	*prate = clk_hw_get_rate(parent);
+	div = rp1_clock_choose_div(rate, *prate, data);
+
+	if (!div) {
+		*calc_rate = 0;
+		return;
+	}
+
+	/* Recalculate to account for rounding errors */
+	tmp = (u64)*prate << CLK_DIV_FRAC_BITS;
+	tmp = div_u64(tmp, div);
+	/*
+	 * Prevent overclocks - if all parent choices result in
+	 * a downstream clock in excess of the maximum, then the
+	 * call to set the clock will fail. But due to round-to-
+	 * nearest in the PLL core (which has 24 fractional bits),
+	 * it's expedient to tolerate a tiny error (1Hz/33MHz).
+	 */
+	if (tmp > clock->data->max_freq + (clock->data->max_freq >> 25))
+		*calc_rate = 0;
+	else
+		*calc_rate = tmp;
+}
+
+static int rp1_clock_determine_rate(struct clk_hw *hw,
+				    struct clk_rate_request *req)
+{
+	struct clk_hw *parent, *best_parent = NULL;
+	unsigned long best_rate = 0;
+	unsigned long best_prate = 0;
+	unsigned long best_rate_diff = ULONG_MAX;
+	unsigned long prate, calc_rate;
+	size_t i;
+
+	/*
+	 * If the NO_REPARENT flag is set, try to use existing parent.
+	 */
+	if ((clk_hw_get_flags(hw) & CLK_SET_RATE_NO_REPARENT)) {
+		i = rp1_clock_get_parent(hw);
+		parent = clk_hw_get_parent_by_index(hw, i);
+		if (parent) {
+			rp1_clock_choose_div_and_prate(hw, i, req->rate, &prate,
+						       &calc_rate);
+			if (calc_rate > 0) {
+				req->best_parent_hw = parent;
+				req->best_parent_rate = prate;
+				req->rate = calc_rate;
+				return 0;
+			}
+		}
+	}
+
+	/*
+	 * Select parent clock that results in the closest rate (lower or
+	 * higher)
+	 */
+	for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
+		parent = clk_hw_get_parent_by_index(hw, i);
+		if (!parent)
+			continue;
+
+		rp1_clock_choose_div_and_prate(hw, i, req->rate, &prate,
+					       &calc_rate);
+
+		if (ABS_DIFF(calc_rate, req->rate) < best_rate_diff) {
+			best_parent = parent;
+			best_prate = prate;
+			best_rate = calc_rate;
+			best_rate_diff = ABS_DIFF(calc_rate, req->rate);
+
+			if (best_rate_diff == 0)
+				break;
+		}
+	}
+
+	if (best_rate == 0)
+		return -EINVAL;
+
+	req->best_parent_hw = best_parent;
+	req->best_parent_rate = best_prate;
+	req->rate = best_rate;
+
+	return 0;
+}
+
+static void rp1_clk_debug_init(struct clk_hw *hw, struct dentry *dentry)
+{
+	struct rp1_clock *clock = container_of(hw, struct rp1_clock, hw);
+	struct rp1_clockman *clockman = clock->clockman;
+	const struct rp1_clock_data *data = clock->data;
+	struct debugfs_reg32 *regs;
+	int i;
+
+	regs = devm_kcalloc(clockman->dev, 4, sizeof(*regs), GFP_KERNEL);
+	if (!regs)
+		return;
+
+	i = 0;
+	regs[i].name = "ctrl";
+	regs[i++].offset = data->ctrl_reg;
+	regs[i].name = "div_int";
+	regs[i++].offset = data->div_int_reg;
+	regs[i].name = "div_frac";
+	regs[i++].offset = data->div_frac_reg;
+	regs[i].name = "sel";
+	regs[i++].offset = data->sel_reg;
+
+	rp1_debugfs_regset(clockman, 0, regs, i, dentry);
+}
+
+static int rp1_varsrc_set_rate(struct clk_hw *hw,
+			       unsigned long rate, unsigned long parent_rate)
+{
+	struct rp1_varsrc *varsrc = container_of(hw, struct rp1_varsrc, hw);
+
+	/*
+	 * "varsrc" exists purely to let clock dividers know the frequency
+	 * of an externally-managed clock source (such as MIPI DSI byte-clock)
+	 * which may change at run-time as a side-effect of some other driver.
+	 */
+	varsrc->rate = rate;
+	return 0;
+}
+
+static unsigned long rp1_varsrc_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct rp1_varsrc *varsrc = container_of(hw, struct rp1_varsrc, hw);
+
+	return varsrc->rate;
+}
+
+static long rp1_varsrc_round_rate(struct clk_hw *hw, unsigned long rate,
+				  unsigned long *parent_rate)
+{
+	return rate;
+}
+
+static const struct clk_ops rp1_pll_core_ops = {
+	.is_prepared = rp1_pll_core_is_on,
+	.prepare = rp1_pll_core_on,
+	.unprepare = rp1_pll_core_off,
+	.set_rate = rp1_pll_core_set_rate,
+	.recalc_rate = rp1_pll_core_recalc_rate,
+	.round_rate = rp1_pll_core_round_rate,
+	.debug_init = rp1_pll_core_debug_init,
+};
+
+static const struct clk_ops rp1_pll_ops = {
+	.set_rate = rp1_pll_set_rate,
+	.recalc_rate = rp1_pll_recalc_rate,
+	.round_rate = rp1_pll_round_rate,
+	.debug_init = rp1_pll_debug_init,
+};
+
+static const struct clk_ops rp1_pll_ph_ops = {
+	.is_prepared = rp1_pll_ph_is_on,
+	.prepare = rp1_pll_ph_on,
+	.unprepare = rp1_pll_ph_off,
+	.set_rate = rp1_pll_ph_set_rate,
+	.recalc_rate = rp1_pll_ph_recalc_rate,
+	.round_rate = rp1_pll_ph_round_rate,
+	.debug_init = rp1_pll_ph_debug_init,
+};
+
+static const struct clk_ops rp1_pll_divider_ops = {
+	.is_prepared = rp1_pll_divider_is_on,
+	.prepare = rp1_pll_divider_on,
+	.unprepare = rp1_pll_divider_off,
+	.set_rate = rp1_pll_divider_set_rate,
+	.recalc_rate = rp1_pll_divider_recalc_rate,
+	.round_rate = rp1_pll_divider_round_rate,
+	.debug_init = rp1_pll_divider_debug_init,
+};
+
+static const struct clk_ops rp1_clk_ops = {
+	.is_prepared = rp1_clock_is_on,
+	.prepare = rp1_clock_on,
+	.unprepare = rp1_clock_off,
+	.recalc_rate = rp1_clock_recalc_rate,
+	.get_parent = rp1_clock_get_parent,
+	.set_parent = rp1_clock_set_parent,
+	.set_rate_and_parent = rp1_clock_set_rate_and_parent,
+	.set_rate = rp1_clock_set_rate,
+	.determine_rate = rp1_clock_determine_rate,
+	.debug_init = rp1_clk_debug_init,
+};
+
+static const struct clk_ops rp1_varsrc_ops = {
+	.set_rate = rp1_varsrc_set_rate,
+	.recalc_rate = rp1_varsrc_recalc_rate,
+	.round_rate = rp1_varsrc_round_rate,
+};
+
+static struct clk_hw *rp1_register_pll_core(struct rp1_clockman *clockman,
+					    const void *data)
+{
+	const struct rp1_pll_core_data *pll_core_data = data;
+	struct rp1_pll_core *pll_core;
+	struct clk_init_data init;
+	int ret;
+
+	memset(&init, 0, sizeof(init));
+
+	/* All of the PLL cores derive from the external oscillator. */
+	init.parent_names = &ref_clock;
+	init.num_parents = 1;
+	init.name = pll_core_data->name;
+	init.ops = &rp1_pll_core_ops;
+	init.flags = pll_core_data->flags | CLK_IS_CRITICAL;
+
+	pll_core = kzalloc(sizeof(*pll_core), GFP_KERNEL);
+	if (!pll_core)
+		return NULL;
+
+	pll_core->clockman = clockman;
+	pll_core->data = pll_core_data;
+	pll_core->hw.init = &init;
+
+	ret = devm_clk_hw_register(clockman->dev, &pll_core->hw);
+	if (ret) {
+		kfree(pll_core);
+		return NULL;
+	}
+
+	return &pll_core->hw;
+}
+
+static struct clk_hw *rp1_register_pll(struct rp1_clockman *clockman,
+				       const void *data)
+{
+	const struct rp1_pll_data *pll_data = data;
+	struct rp1_pll *pll;
+	struct clk_init_data init;
+	int ret;
+
+	memset(&init, 0, sizeof(init));
+
+	init.parent_names = &pll_data->source_pll;
+	init.num_parents = 1;
+	init.name = pll_data->name;
+	init.ops = &rp1_pll_ops;
+	init.flags = pll_data->flags;
+
+	pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+	if (!pll)
+		return NULL;
+
+	pll->clockman = clockman;
+	pll->data = pll_data;
+	pll->hw.init = &init;
+
+	ret = devm_clk_hw_register(clockman->dev, &pll->hw);
+	if (ret) {
+		kfree(pll);
+		return NULL;
+	}
+
+	return &pll->hw;
+}
+
+static struct clk_hw *rp1_register_pll_ph(struct rp1_clockman *clockman,
+					  const void *data)
+{
+	const struct rp1_pll_ph_data *ph_data = data;
+	struct rp1_pll_ph *ph;
+	struct clk_init_data init;
+	int ret;
+
+	memset(&init, 0, sizeof(init));
+
+	/* All of the PLLs derive from the external oscillator. */
+	init.parent_names = &ph_data->source_pll;
+	init.num_parents = 1;
+	init.name = ph_data->name;
+	init.ops = &rp1_pll_ph_ops;
+	init.flags = ph_data->flags;
+
+	ph = kzalloc(sizeof(*ph), GFP_KERNEL);
+	if (!ph)
+		return NULL;
+
+	ph->clockman = clockman;
+	ph->data = ph_data;
+	ph->hw.init = &init;
+
+	ret = devm_clk_hw_register(clockman->dev, &ph->hw);
+	if (ret) {
+		kfree(ph);
+		return NULL;
+	}
+
+	return &ph->hw;
+}
+
+static struct clk_hw *rp1_register_pll_divider(struct rp1_clockman *clockman,
+					       const void *data)
+{
+	const struct rp1_pll_data *divider_data = data;
+	struct rp1_pll *divider;
+	struct clk_init_data init;
+	int ret;
+
+	memset(&init, 0, sizeof(init));
+
+	init.parent_names = &divider_data->source_pll;
+	init.num_parents = 1;
+	init.name = divider_data->name;
+	init.ops = &rp1_pll_divider_ops;
+	init.flags = divider_data->flags;
+
+	divider = devm_kzalloc(clockman->dev, sizeof(*divider), GFP_KERNEL);
+	if (!divider)
+		return NULL;
+
+	divider->div.reg = clockman->regs + divider_data->ctrl_reg;
+	divider->div.shift = PLL_SEC_DIV_SHIFT;
+	divider->div.width = PLL_SEC_DIV_WIDTH;
+	divider->div.flags = CLK_DIVIDER_ROUND_CLOSEST;
+	divider->div.lock = &clockman->regs_lock;
+	divider->div.hw.init = &init;
+	divider->div.table = pll_sec_div_table;
+
+	divider->clockman = clockman;
+	divider->data = divider_data;
+
+	ret = devm_clk_hw_register(clockman->dev, &divider->div.hw);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return &divider->div.hw;
+}
+
+static struct clk_hw *rp1_register_clock(struct rp1_clockman *clockman,
+					 const void *data)
+{
+	const struct rp1_clock_data *clock_data = data;
+	struct rp1_clock *clock;
+	struct clk_init_data init;
+	int ret;
+
+	BUG_ON(MAX_CLK_PARENTS <
+	       clock_data->num_std_parents + clock_data->num_aux_parents);
+	/* There must be a gap for the AUX selector */
+	BUG_ON((clock_data->num_std_parents > AUX_SEL) &&
+	       strcmp("-", clock_data->parents[AUX_SEL]));
+
+	memset(&init, 0, sizeof(init));
+	init.parent_names = clock_data->parents;
+	init.num_parents =
+		clock_data->num_std_parents + clock_data->num_aux_parents;
+	init.name = clock_data->name;
+	init.flags = clock_data->flags;
+	init.ops = &rp1_clk_ops;
+
+	clock = devm_kzalloc(clockman->dev, sizeof(*clock), GFP_KERNEL);
+	if (!clock)
+		return NULL;
+
+	clock->clockman = clockman;
+	clock->data = clock_data;
+	clock->hw.init = &init;
+
+	ret = devm_clk_hw_register(clockman->dev, &clock->hw);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return &clock->hw;
+}
+
+static struct clk_hw *rp1_register_varsrc(struct rp1_clockman *clockman,
+					  const void *data)
+{
+	const char *name = *(char const * const *)data;
+	struct rp1_varsrc *clock;
+	struct clk_init_data init;
+	int ret;
+
+	memset(&init, 0, sizeof(init));
+	init.parent_names = &ref_clock;
+	init.num_parents = 1;
+	init.name = name;
+	init.ops = &rp1_varsrc_ops;
+
+	clock = devm_kzalloc(clockman->dev, sizeof(*clock), GFP_KERNEL);
+	if (!clock)
+		return NULL;
+
+	clock->clockman = clockman;
+	clock->hw.init = &init;
+
+	ret = devm_clk_hw_register(clockman->dev, &clock->hw);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return &clock->hw;
+}
+
+struct rp1_clk_desc {
+	struct clk_hw *(*clk_register)(struct rp1_clockman *clockman,
+				       const void *data);
+	const void *data;
+};
+
+/* Assignment helper macros for different clock types. */
+#define _REGISTER(f, ...) { .clk_register = f, .data = __VA_ARGS__ }
+
+#define REGISTER_PLL_CORE(...)	_REGISTER(&rp1_register_pll_core,	\
+					  &(struct rp1_pll_core_data)	\
+					  {__VA_ARGS__})
+
+#define REGISTER_PLL(...)	_REGISTER(&rp1_register_pll,		\
+					  &(struct rp1_pll_data)		\
+					  {__VA_ARGS__})
+
+#define REGISTER_PLL_PH(...)	_REGISTER(&rp1_register_pll_ph,		\
+					  &(struct rp1_pll_ph_data)	\
+					  {__VA_ARGS__})
+
+#define REGISTER_PLL_DIV(...)	_REGISTER(&rp1_register_pll_divider,	\
+					  &(struct rp1_pll_data)	\
+					  {__VA_ARGS__})
+
+#define REGISTER_CLK(...)	_REGISTER(&rp1_register_clock,		\
+					  &(struct rp1_clock_data)	\
+					  {__VA_ARGS__})
+
+#define REGISTER_VARSRC(n)	_REGISTER(&rp1_register_varsrc,	&(const char *){n})
+
+static const struct rp1_clk_desc clk_desc_array[] = {
+	[RP1_PLL_SYS_CORE] = REGISTER_PLL_CORE(
+				.name = "pll_sys_core",
+				.cs_reg = PLL_SYS_CS,
+				.pwr_reg = PLL_SYS_PWR,
+				.fbdiv_int_reg = PLL_SYS_FBDIV_INT,
+				.fbdiv_frac_reg = PLL_SYS_FBDIV_FRAC,
+				),
+
+	[RP1_PLL_AUDIO_CORE] = REGISTER_PLL_CORE(
+				.name = "pll_audio_core",
+				.cs_reg = PLL_AUDIO_CS,
+				.pwr_reg = PLL_AUDIO_PWR,
+				.fbdiv_int_reg = PLL_AUDIO_FBDIV_INT,
+				.fbdiv_frac_reg = PLL_AUDIO_FBDIV_FRAC,
+				),
+
+	[RP1_PLL_VIDEO_CORE] = REGISTER_PLL_CORE(
+				.name = "pll_video_core",
+				.cs_reg = PLL_VIDEO_CS,
+				.pwr_reg = PLL_VIDEO_PWR,
+				.fbdiv_int_reg = PLL_VIDEO_FBDIV_INT,
+				.fbdiv_frac_reg = PLL_VIDEO_FBDIV_FRAC,
+				),
+
+	[RP1_PLL_SYS] = REGISTER_PLL(
+				.name = "pll_sys",
+				.source_pll = "pll_sys_core",
+				.ctrl_reg = PLL_SYS_PRIM,
+				.fc0_src = FC_NUM(0, 2),
+				),
+
+	[RP1_PLL_AUDIO] = REGISTER_PLL(
+				.name = "pll_audio",
+				.source_pll = "pll_audio_core",
+				.ctrl_reg = PLL_AUDIO_PRIM,
+				.fc0_src = FC_NUM(4, 2),
+				.flags = CLK_SET_RATE_PARENT,
+				),
+
+	[RP1_PLL_VIDEO] = REGISTER_PLL(
+				.name = "pll_video",
+				.source_pll = "pll_video_core",
+				.ctrl_reg = PLL_VIDEO_PRIM,
+				.fc0_src = FC_NUM(3, 2),
+				),
+
+	[RP1_PLL_SYS_PRI_PH] = REGISTER_PLL_PH(
+				.name = "pll_sys_pri_ph",
+				.source_pll = "pll_sys",
+				.ph_reg = PLL_SYS_PRIM,
+				.fixed_divider = 2,
+				.phase = RP1_PLL_PHASE_0,
+				.fc0_src = FC_NUM(1, 2),
+				),
+
+	[RP1_PLL_AUDIO_PRI_PH] = REGISTER_PLL_PH(
+				.name = "pll_audio_pri_ph",
+				.source_pll = "pll_audio",
+				.ph_reg = PLL_AUDIO_PRIM,
+				.fixed_divider = 2,
+				.phase = RP1_PLL_PHASE_0,
+				.fc0_src = FC_NUM(5, 1),
+				),
+
+	[RP1_PLL_VIDEO_PRI_PH] = REGISTER_PLL_PH(
+				.name = "pll_video_pri_ph",
+				.source_pll = "pll_video",
+				.ph_reg = PLL_VIDEO_PRIM,
+				.fixed_divider = 2,
+				.phase = RP1_PLL_PHASE_0,
+				.fc0_src = FC_NUM(4, 3),
+				),
+
+	[RP1_PLL_SYS_SEC] = REGISTER_PLL_DIV(
+				.name = "pll_sys_sec",
+				.source_pll = "pll_sys_core",
+				.ctrl_reg = PLL_SYS_SEC,
+				.fc0_src = FC_NUM(2, 2),
+				),
+
+	[RP1_PLL_AUDIO_SEC] = REGISTER_PLL_DIV(
+				.name = "pll_audio_sec",
+				.source_pll = "pll_audio_core",
+				.ctrl_reg = PLL_AUDIO_SEC,
+				.fc0_src = FC_NUM(6, 2),
+				),
+
+	[RP1_PLL_VIDEO_SEC] = REGISTER_PLL_DIV(
+				.name = "pll_video_sec",
+				.source_pll = "pll_video_core",
+				.ctrl_reg = PLL_VIDEO_SEC,
+				.fc0_src = FC_NUM(5, 3),
+				),
+
+	[RP1_PLL_AUDIO_TERN] = REGISTER_PLL_DIV(
+				.name = "pll_audio_tern",
+				.source_pll = "pll_audio_core",
+				.ctrl_reg = PLL_AUDIO_TERN,
+				.fc0_src = FC_NUM(6, 2),
+				),
+
+	[RP1_CLK_SYS] = REGISTER_CLK(
+				.name = "clk_sys",
+				.parents = {"xosc", "-", "pll_sys"},
+				.num_std_parents = 3,
+				.num_aux_parents = 0,
+				.ctrl_reg = CLK_SYS_CTRL,
+				.div_int_reg = CLK_SYS_DIV_INT,
+				.sel_reg = CLK_SYS_SEL,
+				.div_int_max = DIV_INT_24BIT_MAX,
+				.max_freq = 200 * MHz,
+				.fc0_src = FC_NUM(0, 4),
+				.clk_src_mask = 0x3,
+				/* Always enabled in hardware */
+				.flags = CLK_IS_CRITICAL,
+				),
+
+	[RP1_CLK_SLOW_SYS] = REGISTER_CLK(
+				.name = "clk_slow_sys",
+				.parents = {"xosc"},
+				.num_std_parents = 1,
+				.num_aux_parents = 0,
+				.ctrl_reg = CLK_SLOW_SYS_CTRL,
+				.div_int_reg = CLK_SLOW_SYS_DIV_INT,
+				.sel_reg = CLK_SLOW_SYS_SEL,
+				.div_int_max = DIV_INT_8BIT_MAX,
+				.max_freq = 50 * MHz,
+				.fc0_src = FC_NUM(1, 4),
+				.clk_src_mask = 0x1,
+				/* Always enabled in hardware */
+				.flags = CLK_IS_CRITICAL,
+				),
+
+	[RP1_CLK_DMA] = REGISTER_CLK(
+				.name = "clk_dma",
+				.parents = {"pll_sys_pri_ph",
+					    "pll_video",
+					    "xosc",
+					    "clksrc_gp0",
+					    "clksrc_gp1",
+					    "clksrc_gp2",
+					    "clksrc_gp3",
+					    "clksrc_gp4",
+					    "clksrc_gp5"},
+				.num_std_parents = 0,
+				.num_aux_parents = 9,
+				.ctrl_reg = CLK_DMA_CTRL,
+				.div_int_reg = CLK_DMA_DIV_INT,
+				.sel_reg = CLK_DMA_SEL,
+				.div_int_max = DIV_INT_8BIT_MAX,
+				.max_freq = 100 * MHz,
+				.fc0_src = FC_NUM(2, 2),
+				),
+
+	[RP1_CLK_UART] = REGISTER_CLK(
+				.name = "clk_uart",
+				.parents = {"pll_sys_pri_ph",
+					    "pll_video",
+					    "xosc",
+					    "clksrc_gp0",
+					    "clksrc_gp1",
+					    "clksrc_gp2",
+					    "clksrc_gp3",
+					    "clksrc_gp4",
+					    "clksrc_gp5"},
+				.num_std_parents = 0,
+				.num_aux_parents = 9,
+				.ctrl_reg = CLK_UART_CTRL,
+				.div_int_reg = CLK_UART_DIV_INT,
+				.sel_reg = CLK_UART_SEL,
+				.div_int_max = DIV_INT_8BIT_MAX,
+				.max_freq = 100 * MHz,
+				.fc0_src = FC_NUM(6, 7),
+				),
+
+	[RP1_CLK_ETH] = REGISTER_CLK(
+				.name = "clk_eth",
+				.parents = {"pll_sys_sec",
+					    "pll_sys",
+					    "pll_video_sec",
+					    "clksrc_gp0",
+					    "clksrc_gp1",
+					    "clksrc_gp2",
+					    "clksrc_gp3",
+					    "clksrc_gp4",
+					    "clksrc_gp5"},
+				.num_std_parents = 0,
+				.num_aux_parents = 9,
+				.ctrl_reg = CLK_ETH_CTRL,
+				.div_int_reg = CLK_ETH_DIV_INT,
+				.sel_reg = CLK_ETH_SEL,
+				.div_int_max = DIV_INT_8BIT_MAX,
+				.max_freq = 125 * MHz,
+				.fc0_src = FC_NUM(4, 6),
+				),
+
+	[RP1_CLK_PWM0] = REGISTER_CLK(
+				.name = "clk_pwm0",
+				.parents = {"", // "pll_audio_pri_ph",
+					    "pll_video_sec",
+					    "xosc",
+					    "clksrc_gp0",
+					    "clksrc_gp1",
+					    "clksrc_gp2",
+					    "clksrc_gp3",
+					    "clksrc_gp4",
+					    "clksrc_gp5"},
+				.num_std_parents = 0,
+				.num_aux_parents = 9,
+				.ctrl_reg = CLK_PWM0_CTRL,
+				.div_int_reg = CLK_PWM0_DIV_INT,
+				.div_frac_reg = CLK_PWM0_DIV_FRAC,
+				.sel_reg = CLK_PWM0_SEL,
+				.div_int_max = DIV_INT_16BIT_MAX,
+				.max_freq = 76800 * KHz,
+				.fc0_src = FC_NUM(0, 5),
+				),
+
+	[RP1_CLK_PWM1] = REGISTER_CLK(
+				.name = "clk_pwm1",
+				.parents = {"", // "pll_audio_pri_ph",
+					    "pll_video_sec",
+					    "xosc",
+					    "clksrc_gp0",
+					    "clksrc_gp1",
+					    "clksrc_gp2",
+					    "clksrc_gp3",
+					    "clksrc_gp4",
+					    "clksrc_gp5"},
+				.num_std_parents = 0,
+				.num_aux_parents = 9,
+				.ctrl_reg = CLK_PWM1_CTRL,
+				.div_int_reg = CLK_PWM1_DIV_INT,
+				.div_frac_reg = CLK_PWM1_DIV_FRAC,
+				.sel_reg = CLK_PWM1_SEL,
+				.div_int_max = DIV_INT_16BIT_MAX,
+				.max_freq = 76800 * KHz,
+				.fc0_src = FC_NUM(1, 5),
+				),
+
+	[RP1_CLK_AUDIO_IN] = REGISTER_CLK(
+				.name = "clk_audio_in",
+				.parents = {"", //"pll_audio",
+					    "", //"pll_audio_pri_ph",
+					    "", //"pll_audio_sec",
+					    "pll_video_sec",
+					    "xosc",
+					    "clksrc_gp0",
+					    "clksrc_gp1",
+					    "clksrc_gp2",
+					    "clksrc_gp3",
+					    "clksrc_gp4",
+					    "clksrc_gp5"},
+				.num_std_parents = 0,
+				.num_aux_parents = 11,
+				.ctrl_reg = CLK_AUDIO_IN_CTRL,
+				.div_int_reg = CLK_AUDIO_IN_DIV_INT,
+				.sel_reg = CLK_AUDIO_IN_SEL,
+				.div_int_max = DIV_INT_8BIT_MAX,
+				.max_freq = 76800 * KHz,
+				.fc0_src = FC_NUM(2, 5),
+				),
+
+	[RP1_CLK_AUDIO_OUT] = REGISTER_CLK(
+				.name = "clk_audio_out",
+				.parents = {"", //"pll_audio",
+					    "pll_audio_sec",
+					    "pll_video_sec",
+					    "xosc",
+					    "clksrc_gp0",
+					    "clksrc_gp1",
+					    "clksrc_gp2",
+					    "clksrc_gp3",
+					    "clksrc_gp4",
+					    "clksrc_gp5"},
+				.num_std_parents = 0,
+				.num_aux_parents = 10,
+				.ctrl_reg = CLK_AUDIO_OUT_CTRL,
+				.div_int_reg = CLK_AUDIO_OUT_DIV_INT,
+				.sel_reg = CLK_AUDIO_OUT_SEL,
+				.div_int_max = DIV_INT_8BIT_MAX,
+				.max_freq = 153600 * KHz,
+				.fc0_src = FC_NUM(3, 5),
+				),
+
+	[RP1_CLK_I2S] = REGISTER_CLK(
+				.name = "clk_i2s",
+				.parents = {"xosc",
+					    "pll_audio",
+					    "pll_audio_sec",
+					    "clksrc_gp0",
+					    "clksrc_gp1",
+					    "clksrc_gp2",
+					    "clksrc_gp3",
+					    "clksrc_gp4",
+					    "clksrc_gp5"},
+				.num_std_parents = 0,
+				.num_aux_parents = 9,
+				.ctrl_reg = CLK_I2S_CTRL,
+				.div_int_reg = CLK_I2S_DIV_INT,
+				.sel_reg = CLK_I2S_SEL,
+				.div_int_max = DIV_INT_8BIT_MAX,
+				.max_freq = 50 * MHz,
+				.fc0_src = FC_NUM(4, 4),
+				.flags = CLK_SET_RATE_PARENT,
+				),
+
+	[RP1_CLK_MIPI0_CFG] = REGISTER_CLK(
+				.name = "clk_mipi0_cfg",
+				.parents = {"xosc"},
+				.num_std_parents = 0,
+				.num_aux_parents = 1,
+				.ctrl_reg = CLK_MIPI0_CFG_CTRL,
+				.div_int_reg = CLK_MIPI0_CFG_DIV_INT,
+				.sel_reg = CLK_MIPI0_CFG_SEL,
+				.div_int_max = DIV_INT_8BIT_MAX,
+				.max_freq = 50 * MHz,
+				.fc0_src = FC_NUM(4, 5),
+				),
+
+	[RP1_CLK_MIPI1_CFG] = REGISTER_CLK(
+				.name = "clk_mipi1_cfg",
+				.parents = {"xosc"},
+				.num_std_parents = 0,
+				.num_aux_parents = 1,
+				.ctrl_reg = CLK_MIPI1_CFG_CTRL,
+				.div_int_reg = CLK_MIPI1_CFG_DIV_INT,
+				.sel_reg = CLK_MIPI1_CFG_SEL,
+				.clk_src_mask = 1,
+				.div_int_max = DIV_INT_8BIT_MAX,
+				.max_freq = 50 * MHz,
+				.fc0_src = FC_NUM(5, 6),
+				),
+
+	[RP1_CLK_ETH_TSU] = REGISTER_CLK(
+				.name = "clk_eth_tsu",
+				.parents = {"xosc",
+					    "pll_video_sec",
+					    "clksrc_gp0",
+					    "clksrc_gp1",
+					    "clksrc_gp2",
+					    "clksrc_gp3",
+					    "clksrc_gp4",
+					    "clksrc_gp5"},
+				.num_std_parents = 0,
+				.num_aux_parents = 8,
+				.ctrl_reg = CLK_ETH_TSU_CTRL,
+				.div_int_reg = CLK_ETH_TSU_DIV_INT,
+				.sel_reg = CLK_ETH_TSU_SEL,
+				.div_int_max = DIV_INT_8BIT_MAX,
+				.max_freq = 50 * MHz,
+				.fc0_src = FC_NUM(5, 7),
+				),
+
+	[RP1_CLK_ADC] = REGISTER_CLK(
+				.name = "clk_adc",
+				.parents = {"xosc",
+					    "", //"pll_audio_tern",
+					    "clksrc_gp0",
+					    "clksrc_gp1",
+					    "clksrc_gp2",
+					    "clksrc_gp3",
+					    "clksrc_gp4",
+					    "clksrc_gp5"},
+				.num_std_parents = 0,
+				.num_aux_parents = 8,
+				.ctrl_reg = CLK_ADC_CTRL,
+				.div_int_reg = CLK_ADC_DIV_INT,
+				.sel_reg = CLK_ADC_SEL,
+				.div_int_max = DIV_INT_8BIT_MAX,
+				.max_freq = 50 * MHz,
+				.fc0_src = FC_NUM(5, 5),
+				),
+
+	[RP1_CLK_SDIO_TIMER] = REGISTER_CLK(
+				.name = "clk_sdio_timer",
+				.parents = {"xosc"},
+				.num_std_parents = 0,
+				.num_aux_parents = 1,
+				.ctrl_reg = CLK_SDIO_TIMER_CTRL,
+				.div_int_reg = CLK_SDIO_TIMER_DIV_INT,
+				.sel_reg = CLK_SDIO_TIMER_SEL,
+				.div_int_max = DIV_INT_8BIT_MAX,
+				.max_freq = 50 * MHz,
+				.fc0_src = FC_NUM(3, 4),
+				),
+
+	[RP1_CLK_SDIO_ALT_SRC] = REGISTER_CLK(
+				.name = "clk_sdio_alt_src",
+				.parents = {"pll_sys"},
+				.num_std_parents = 0,
+				.num_aux_parents = 1,
+				.ctrl_reg = CLK_SDIO_ALT_SRC_CTRL,
+				.div_int_reg = CLK_SDIO_ALT_SRC_DIV_INT,
+				.sel_reg = CLK_SDIO_ALT_SRC_SEL,
+				.div_int_max = DIV_INT_8BIT_MAX,
+				.max_freq = 200 * MHz,
+				.fc0_src = FC_NUM(5, 4),
+				),
+
+	[RP1_CLK_GP0] = REGISTER_CLK(
+				.name = "clk_gp0",
+				.parents = {"xosc",
+					    "clksrc_gp1",
+					    "clksrc_gp2",
+					    "clksrc_gp3",
+					    "clksrc_gp4",
+					    "clksrc_gp5",
+					    "pll_sys",
+					    "", //"pll_audio",
+					    "",
+					    "",
+					    "clk_i2s",
+					    "clk_adc",
+					    "",
+					    "",
+					    "",
+					    "clk_sys"},
+				.num_std_parents = 0,
+				.num_aux_parents = 16,
+				.oe_mask = BIT(0),
+				.ctrl_reg = CLK_GP0_CTRL,
+				.div_int_reg = CLK_GP0_DIV_INT,
+				.div_frac_reg = CLK_GP0_DIV_FRAC,
+				.sel_reg = CLK_GP0_SEL,
+				.div_int_max = DIV_INT_16BIT_MAX,
+				.max_freq = 100 * MHz,
+				.fc0_src = FC_NUM(0, 1),
+				),
+
+	[RP1_CLK_GP1] = REGISTER_CLK(
+				.name = "clk_gp1",
+				.parents = {"clk_sdio_timer",
+					    "clksrc_gp0",
+					    "clksrc_gp2",
+					    "clksrc_gp3",
+					    "clksrc_gp4",
+					    "clksrc_gp5",
+					    "pll_sys_pri_ph",
+					    "", //"pll_audio_pri_ph",
+					    "",
+					    "",
+					    "clk_adc",
+					    "clk_dpi",
+					    "clk_pwm0",
+					    "",
+					    "",
+					    ""},
+				.num_std_parents = 0,
+				.num_aux_parents = 16,
+				.oe_mask = BIT(1),
+				.ctrl_reg = CLK_GP1_CTRL,
+				.div_int_reg = CLK_GP1_DIV_INT,
+				.div_frac_reg = CLK_GP1_DIV_FRAC,
+				.sel_reg = CLK_GP1_SEL,
+				.div_int_max = DIV_INT_16BIT_MAX,
+				.max_freq = 100 * MHz,
+				.fc0_src = FC_NUM(1, 1),
+				),
+
+	[RP1_CLK_GP2] = REGISTER_CLK(
+				.name = "clk_gp2",
+				.parents = {"clk_sdio_alt_src",
+					    "clksrc_gp0",
+					    "clksrc_gp1",
+					    "clksrc_gp3",
+					    "clksrc_gp4",
+					    "clksrc_gp5",
+					    "pll_sys_sec",
+					    "", //"pll_audio_sec",
+					    "pll_video",
+					    "clk_audio_in",
+					    "clk_dpi",
+					    "clk_pwm0",
+					    "clk_pwm1",
+					    "clk_mipi0_dpi",
+					    "clk_mipi1_cfg",
+					    "clk_sys"},
+				.num_std_parents = 0,
+				.num_aux_parents = 16,
+				.oe_mask = BIT(2),
+				.ctrl_reg = CLK_GP2_CTRL,
+				.div_int_reg = CLK_GP2_DIV_INT,
+				.div_frac_reg = CLK_GP2_DIV_FRAC,
+				.sel_reg = CLK_GP2_SEL,
+				.div_int_max = DIV_INT_16BIT_MAX,
+				.max_freq = 100 * MHz,
+				.fc0_src = FC_NUM(2, 1),
+				),
+
+	[RP1_CLK_GP3] = REGISTER_CLK(
+				.name = "clk_gp3",
+				.parents = {"xosc",
+					    "clksrc_gp0",
+					    "clksrc_gp1",
+					    "clksrc_gp2",
+					    "clksrc_gp4",
+					    "clksrc_gp5",
+					    "",
+					    "",
+					    "pll_video_pri_ph",
+					    "clk_audio_out",
+					    "",
+					    "",
+					    "clk_mipi1_dpi",
+					    "",
+					    "",
+					    ""},
+				.num_std_parents = 0,
+				.num_aux_parents = 16,
+				.oe_mask = BIT(3),
+				.ctrl_reg = CLK_GP3_CTRL,
+				.div_int_reg = CLK_GP3_DIV_INT,
+				.div_frac_reg = CLK_GP3_DIV_FRAC,
+				.sel_reg = CLK_GP3_SEL,
+				.div_int_max = DIV_INT_16BIT_MAX,
+				.max_freq = 100 * MHz,
+				.fc0_src = FC_NUM(3, 1),
+				),
+
+	[RP1_CLK_GP4] = REGISTER_CLK(
+				.name = "clk_gp4",
+				.parents = {"xosc",
+					    "clksrc_gp0",
+					    "clksrc_gp1",
+					    "clksrc_gp2",
+					    "clksrc_gp3",
+					    "clksrc_gp5",
+					    "", //"pll_audio_tern",
+					    "pll_video_sec",
+					    "",
+					    "",
+					    "",
+					    "clk_mipi0_cfg",
+					    "clk_uart",
+					    "",
+					    "",
+					    "clk_sys",
+					    },
+				.num_std_parents = 0,
+				.num_aux_parents = 16,
+				.oe_mask = BIT(4),
+				.ctrl_reg = CLK_GP4_CTRL,
+				.div_int_reg = CLK_GP4_DIV_INT,
+				.div_frac_reg = CLK_GP4_DIV_FRAC,
+				.sel_reg = CLK_GP4_SEL,
+				.div_int_max = DIV_INT_16BIT_MAX,
+				.max_freq = 100 * MHz,
+				.fc0_src = FC_NUM(4, 1),
+				),
+
+	[RP1_CLK_GP5] = REGISTER_CLK(
+				.name = "clk_gp5",
+				.parents = {"xosc",
+					    "clksrc_gp0",
+					    "clksrc_gp1",
+					    "clksrc_gp2",
+					    "clksrc_gp3",
+					    "clksrc_gp4",
+					    "", //"pll_audio_tern",
+					    "pll_video_sec",
+					    "clk_eth_tsu",
+					    "",
+					    "clk_vec",
+					    "",
+					    "",
+					    "",
+					    "",
+					    ""},
+				.num_std_parents = 0,
+				.num_aux_parents = 16,
+				.oe_mask = BIT(5),
+				.ctrl_reg = CLK_GP5_CTRL,
+				.div_int_reg = CLK_GP5_DIV_INT,
+				.div_frac_reg = CLK_GP5_DIV_FRAC,
+				.sel_reg = CLK_GP5_SEL,
+				.div_int_max = DIV_INT_16BIT_MAX,
+				.max_freq = 100 * MHz,
+				.fc0_src = FC_NUM(5, 1),
+				),
+
+	[RP1_CLK_VEC] = REGISTER_CLK(
+				.name = "clk_vec",
+				.parents = {"pll_sys_pri_ph",
+					    "pll_video_sec",
+					    "pll_video",
+					    "clksrc_gp0",
+					    "clksrc_gp1",
+					    "clksrc_gp2",
+					    "clksrc_gp3",
+					    "clksrc_gp4"},
+				.num_std_parents = 0,
+				.num_aux_parents = 8, /* XXX in fact there are more than 8 */
+				.ctrl_reg = VIDEO_CLK_VEC_CTRL,
+				.div_int_reg = VIDEO_CLK_VEC_DIV_INT,
+				.sel_reg = VIDEO_CLK_VEC_SEL,
+				.flags = CLK_SET_RATE_NO_REPARENT, /* Let VEC driver set parent */
+				.div_int_max = DIV_INT_8BIT_MAX,
+				.max_freq = 108 * MHz,
+				.fc0_src = FC_NUM(0, 6),
+				),
+
+	[RP1_CLK_DPI] = REGISTER_CLK(
+				.name = "clk_dpi",
+				.parents = {"pll_sys",
+					    "pll_video_sec",
+					    "pll_video",
+					    "clksrc_gp0",
+					    "clksrc_gp1",
+					    "clksrc_gp2",
+					    "clksrc_gp3",
+					    "clksrc_gp4"},
+				.num_std_parents = 0,
+				.num_aux_parents = 8, /* XXX in fact there are more than 8 */
+				.ctrl_reg = VIDEO_CLK_DPI_CTRL,
+				.div_int_reg = VIDEO_CLK_DPI_DIV_INT,
+				.sel_reg = VIDEO_CLK_DPI_SEL,
+				.flags = CLK_SET_RATE_NO_REPARENT, /* Let DPI driver set parent */
+				.div_int_max = DIV_INT_8BIT_MAX,
+				.max_freq = 200 * MHz,
+				.fc0_src = FC_NUM(1, 6),
+				),
+
+	[RP1_CLK_MIPI0_DPI] = REGISTER_CLK(
+				.name = "clk_mipi0_dpi",
+				.parents = {"pll_sys",
+					    "pll_video_sec",
+					    "pll_video",
+					    "clksrc_mipi0_dsi_byteclk",
+					    "clksrc_gp0",
+					    "clksrc_gp1",
+					    "clksrc_gp2",
+					    "clksrc_gp3"},
+				.num_std_parents = 0,
+				.num_aux_parents = 8, /* XXX in fact there are more than 8 */
+				.ctrl_reg = VIDEO_CLK_MIPI0_DPI_CTRL,
+				.div_int_reg = VIDEO_CLK_MIPI0_DPI_DIV_INT,
+				.div_frac_reg = VIDEO_CLK_MIPI0_DPI_DIV_FRAC,
+				.sel_reg = VIDEO_CLK_MIPI0_DPI_SEL,
+				.flags = CLK_SET_RATE_NO_REPARENT, /* Let DSI driver set parent */
+				.div_int_max = DIV_INT_8BIT_MAX,
+				.max_freq = 200 * MHz,
+				.fc0_src = FC_NUM(2, 6),
+				),
+
+	[RP1_CLK_MIPI1_DPI] = REGISTER_CLK(
+				.name = "clk_mipi1_dpi",
+				.parents = {"pll_sys",
+					    "pll_video_sec",
+					    "pll_video",
+					    "clksrc_mipi1_dsi_byteclk",
+					    "clksrc_gp0",
+					    "clksrc_gp1",
+					    "clksrc_gp2",
+					    "clksrc_gp3"},
+				.num_std_parents = 0,
+				.num_aux_parents = 8, /* XXX in fact there are more than 8 */
+				.ctrl_reg = VIDEO_CLK_MIPI1_DPI_CTRL,
+				.div_int_reg = VIDEO_CLK_MIPI1_DPI_DIV_INT,
+				.div_frac_reg = VIDEO_CLK_MIPI1_DPI_DIV_FRAC,
+				.sel_reg = VIDEO_CLK_MIPI1_DPI_SEL,
+				.flags = CLK_SET_RATE_NO_REPARENT, /* Let DSI driver set parent */
+				.div_int_max = DIV_INT_8BIT_MAX,
+				.max_freq = 200 * MHz,
+				.fc0_src = FC_NUM(3, 6),
+				),
+
+	[RP1_CLK_MIPI0_DSI_BYTECLOCK] = REGISTER_VARSRC("clksrc_mipi0_dsi_byteclk"),
+	[RP1_CLK_MIPI1_DSI_BYTECLOCK] = REGISTER_VARSRC("clksrc_mipi1_dsi_byteclk"),
+};
+
+static int rp1_clk_probe(struct platform_device *pdev)
+{
+	const struct rp1_clk_desc *desc;
+	struct device *dev = &pdev->dev;
+	struct rp1_clockman *clockman;
+	struct resource *res;
+	struct clk_hw **hws;
+	const size_t asize = ARRAY_SIZE(clk_desc_array);
+	u32 chip_id, platform;
+	unsigned int i;
+	int ret;
+
+	clockman = devm_kzalloc(dev, struct_size(clockman, onecell.hws, asize),
+				GFP_KERNEL);
+	if (!clockman)
+		return -ENOMEM;
+
+	rp1_get_platform(&chip_id, &platform);
+
+	spin_lock_init(&clockman->regs_lock);
+	clockman->dev = dev;
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	clockman->regs = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(clockman->regs))
+		return PTR_ERR(clockman->regs);
+
+	platform_set_drvdata(pdev, clockman);
+
+	clockman->onecell.num = asize;
+	hws = clockman->onecell.hws;
+
+	for (i = 0; i < asize; i++) {
+		desc = &clk_desc_array[i];
+		if (desc->clk_register && desc->data) {
+			hws[i] = desc->clk_register(clockman, desc->data);
+			if (IS_ERR_OR_NULL(hws[i])) {
+				pr_err("Failed to register RP1 clock '%s' (%ld) - wrong dtbs?\n", *(char **)desc->data, PTR_ERR(hws[i]));
+				hws[i] = NULL;
+				continue;
+			}
+			if (!strcmp(clk_hw_get_name(hws[i]), "clk_i2s")) {
+				clk_i2s = hws[i];
+				clk_xosc = clk_hw_get_parent_by_index(clk_i2s, 0);
+				clk_audio = clk_hw_get_parent_by_index(clk_i2s, 1);
+			}
+		}
+	}
+
+	ret = of_clk_add_hw_provider(dev->of_node, of_clk_hw_onecell_get,
+				     &clockman->onecell);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static const struct of_device_id rp1_clk_of_match[] = {
+	{ .compatible = "raspberrypi,rp1-clocks" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, rp1_clk_of_match);
+
+static struct platform_driver rp1_clk_driver = {
+	.driver = {
+		.name = "rp1-clk",
+		.of_match_table = rp1_clk_of_match,
+	},
+	.probe = rp1_clk_probe,
+};
+
+static int __init __rp1_clk_driver_init(void)
+{
+	return platform_driver_register(&rp1_clk_driver);
+}
+postcore_initcall(__rp1_clk_driver_init);
+
+MODULE_AUTHOR("Naushir Patuck <naush@raspberrypi.com>");
+MODULE_DESCRIPTION("RP1 clock driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/dma-buf/heaps/system_heap.c b/drivers/dma-buf/heaps/system_heap.c
index d78cdb9d01e5..d8ec1121f09d 100644
--- a/drivers/dma-buf/heaps/system_heap.c
+++ b/drivers/dma-buf/heaps/system_heap.c
@@ -51,9 +51,15 @@ static gfp_t order_flags[] = {HIGH_ORDER_GFP, HIGH_ORDER_GFP, LOW_ORDER_GFP};
  * of order 0 pages can significantly improve the performance of many IOMMUs
  * by reducing TLB pressure and time spent updating page tables.
  */
-static const unsigned int orders[] = {8, 4, 0};
+#define MAX_ORDERS_VALUE 8
+static const unsigned int orders[] = {MAX_ORDERS_VALUE, 4, 0};
 #define NUM_ORDERS ARRAY_SIZE(orders)
 
+static unsigned int module_max_order = MAX_ORDERS_VALUE;
+
+module_param_named(max_order, module_max_order, uint, 0400);
+MODULE_PARM_DESC(max_order, "Maximum allocation order override.");
+
 static struct sg_table *dup_sg_table(struct sg_table *table)
 {
 	struct sg_table *new_table;
@@ -339,7 +345,7 @@ static struct dma_buf *system_heap_allocate(struct dma_heap *heap,
 	struct system_heap_buffer *buffer;
 	DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
 	unsigned long size_remaining = len;
-	unsigned int max_order = orders[0];
+	unsigned int max_order = module_max_order;
 	struct dma_buf *dmabuf;
 	struct sg_table *table;
 	struct scatterlist *sg;
@@ -433,6 +439,9 @@ static int system_heap_create(void)
 	if (IS_ERR(sys_heap))
 		return PTR_ERR(sys_heap);
 
+	if (module_max_order > orders[0])
+		module_max_order = orders[0];
+
 	return 0;
 }
 module_init(system_heap_create);
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index d9ec1e69e428..c4376c21d1d4 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -669,6 +669,10 @@ config UNIPHIER_XDMAC
 	  UniPhier platform. This DMA controller can transfer data from
 	  memory to memory, memory to peripheral and peripheral to memory.
 
+config DMA_BCM2708
+	tristate "BCM2708 DMA legacy API support"
+	depends on DMA_BCM2835
+
 config XGENE_DMA
 	tristate "APM X-Gene DMA support"
 	depends on ARCH_XGENE || COMPILE_TEST
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index ad6a03c052ec..86314bce68e6 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_AT_HDMAC) += at_hdmac.o
 obj-$(CONFIG_AT_XDMAC) += at_xdmac.o
 obj-$(CONFIG_AXI_DMAC) += dma-axi-dmac.o
 obj-$(CONFIG_BCM_SBA_RAID) += bcm-sba-raid.o
+obj-$(CONFIG_DMA_BCM2708) += bcm2708-dmaengine.o
 obj-$(CONFIG_DMA_BCM2835) += bcm2835-dma.o
 obj-$(CONFIG_DMA_JZ4780) += dma-jz4780.o
 obj-$(CONFIG_DMA_SA11X0) += sa11x0-dma.o
diff --git a/drivers/dma/bcm2708-dmaengine.c b/drivers/dma/bcm2708-dmaengine.c
new file mode 100644
index 000000000000..a9a7f92584c8
--- /dev/null
+++ b/drivers/dma/bcm2708-dmaengine.c
@@ -0,0 +1,281 @@
+/*
+ * BCM2708 legacy DMA API
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/platform_data/dma-bcm2708.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/spinlock.h>
+
+#include "virt-dma.h"
+
+#define CACHE_LINE_MASK 31
+#define DEFAULT_DMACHAN_BITMAP 0x10  /* channel 4 only */
+
+/* valid only for channels 0 - 14, 15 has its own base address */
+#define BCM2708_DMA_CHAN(n)	((n) << 8) /* base address */
+#define BCM2708_DMA_CHANIO(dma_base, n) \
+	((void __iomem *)((char *)(dma_base) + BCM2708_DMA_CHAN(n)))
+
+struct vc_dmaman {
+	void __iomem *dma_base;
+	u32 chan_available; /* bitmap of available channels */
+	u32 has_feature[BCM_DMA_FEATURE_COUNT]; /* bitmap of feature presence */
+	struct mutex lock;
+};
+
+static struct device *dmaman_dev;	/* we assume there's only one! */
+static struct vc_dmaman *g_dmaman;	/* DMA manager */
+
+/* DMA Auxiliary Functions */
+
+/* A DMA buffer on an arbitrary boundary may separate a cache line into a
+   section inside the DMA buffer and another section outside it.
+   Even if we flush DMA buffers from the cache there is always the chance that
+   during a DMA someone will access the part of a cache line that is outside
+   the DMA buffer - which will then bring in unwelcome data.
+   Without being able to dictate our own buffer pools we must insist that
+   DMA buffers consist of a whole number of cache lines.
+*/
+extern int bcm_sg_suitable_for_dma(struct scatterlist *sg_ptr, int sg_len)
+{
+	int i;
+
+	for (i = 0; i < sg_len; i++) {
+		if (sg_ptr[i].offset & CACHE_LINE_MASK ||
+		    sg_ptr[i].length & CACHE_LINE_MASK)
+			return 0;
+	}
+
+	return 1;
+}
+EXPORT_SYMBOL_GPL(bcm_sg_suitable_for_dma);
+
+extern void bcm_dma_start(void __iomem *dma_chan_base,
+			  dma_addr_t control_block)
+{
+	dsb(sy);	/* ARM data synchronization (push) operation */
+
+	writel(control_block, dma_chan_base + BCM2708_DMA_ADDR);
+	writel(BCM2708_DMA_ACTIVE, dma_chan_base + BCM2708_DMA_CS);
+}
+EXPORT_SYMBOL_GPL(bcm_dma_start);
+
+extern void bcm_dma_wait_idle(void __iomem *dma_chan_base)
+{
+	dsb(sy);
+
+	/* ugly busy wait only option for now */
+	while (readl(dma_chan_base + BCM2708_DMA_CS) & BCM2708_DMA_ACTIVE)
+		cpu_relax();
+}
+EXPORT_SYMBOL_GPL(bcm_dma_wait_idle);
+
+extern bool bcm_dma_is_busy(void __iomem *dma_chan_base)
+{
+	dsb(sy);
+
+	return readl(dma_chan_base + BCM2708_DMA_CS) & BCM2708_DMA_ACTIVE;
+}
+EXPORT_SYMBOL_GPL(bcm_dma_is_busy);
+
+/* Complete an ongoing DMA (assuming its results are to be ignored)
+   Does nothing if there is no DMA in progress.
+   This routine waits for the current AXI transfer to complete before
+   terminating the current DMA. If the current transfer is hung on a DREQ used
+   by an uncooperative peripheral the AXI transfer may never complete.	In this
+   case the routine times out and return a non-zero error code.
+   Use of this routine doesn't guarantee that the ongoing or aborted DMA
+   does not produce an interrupt.
+*/
+extern int bcm_dma_abort(void __iomem *dma_chan_base)
+{
+	unsigned long int cs;
+	int rc = 0;
+
+	cs = readl(dma_chan_base + BCM2708_DMA_CS);
+
+	if (BCM2708_DMA_ACTIVE & cs) {
+		long int timeout = 10000;
+
+		/* write 0 to the active bit - pause the DMA */
+		writel(0, dma_chan_base + BCM2708_DMA_CS);
+
+		/* wait for any current AXI transfer to complete */
+		while (0 != (cs & BCM2708_DMA_ISPAUSED) && --timeout >= 0)
+			cs = readl(dma_chan_base + BCM2708_DMA_CS);
+
+		if (0 != (cs & BCM2708_DMA_ISPAUSED)) {
+			/* we'll un-pause when we set of our next DMA */
+			rc = -ETIMEDOUT;
+
+		} else if (BCM2708_DMA_ACTIVE & cs) {
+			/* terminate the control block chain */
+			writel(0, dma_chan_base + BCM2708_DMA_NEXTCB);
+
+			/* abort the whole DMA */
+			writel(BCM2708_DMA_ABORT | BCM2708_DMA_ACTIVE,
+			       dma_chan_base + BCM2708_DMA_CS);
+		}
+	}
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(bcm_dma_abort);
+
+ /* DMA Manager Device Methods */
+
+static void vc_dmaman_init(struct vc_dmaman *dmaman, void __iomem *dma_base,
+			   u32 chans_available)
+{
+	dmaman->dma_base = dma_base;
+	dmaman->chan_available = chans_available;
+	dmaman->has_feature[BCM_DMA_FEATURE_FAST_ORD] = 0x0c;  /* 2 & 3 */
+	dmaman->has_feature[BCM_DMA_FEATURE_BULK_ORD] = 0x01;  /* 0 */
+	dmaman->has_feature[BCM_DMA_FEATURE_NORMAL_ORD] = 0xfe;  /* 1 to 7 */
+	dmaman->has_feature[BCM_DMA_FEATURE_LITE_ORD] = 0x7f00;  /* 8 to 14 */
+}
+
+static int vc_dmaman_chan_alloc(struct vc_dmaman *dmaman,
+				unsigned required_feature_set)
+{
+	u32 chans;
+	int chan = 0;
+	int feature;
+
+	chans = dmaman->chan_available;
+	for (feature = 0; feature < BCM_DMA_FEATURE_COUNT; feature++)
+		/* select the subset of available channels with the desired
+		   features */
+		if (required_feature_set & (1 << feature))
+			chans &= dmaman->has_feature[feature];
+
+	if (!chans)
+		return -ENOENT;
+
+	/* return the ordinal of the first channel in the bitmap */
+	while (chans != 0 && (chans & 1) == 0) {
+		chans >>= 1;
+		chan++;
+	}
+	/* claim the channel */
+	dmaman->chan_available &= ~(1 << chan);
+
+	return chan;
+}
+
+static int vc_dmaman_chan_free(struct vc_dmaman *dmaman, int chan)
+{
+	if (chan < 0)
+		return -EINVAL;
+
+	if ((1 << chan) & dmaman->chan_available)
+		return -EIDRM;
+
+	dmaman->chan_available |= (1 << chan);
+
+	return 0;
+}
+
+/* DMA Manager Monitor */
+
+extern int bcm_dma_chan_alloc(unsigned required_feature_set,
+			      void __iomem **out_dma_base, int *out_dma_irq)
+{
+	struct vc_dmaman *dmaman = g_dmaman;
+	struct platform_device *pdev = to_platform_device(dmaman_dev);
+	int chan;
+	int irq;
+
+	if (!dmaman_dev)
+		return -ENODEV;
+
+	mutex_lock(&dmaman->lock);
+	chan = vc_dmaman_chan_alloc(dmaman, required_feature_set);
+	if (chan < 0)
+		goto out;
+
+	irq = platform_get_irq(pdev, (unsigned int)chan);
+	if (irq < 0) {
+		dev_err(dmaman_dev, "failed to get irq for DMA channel %d\n",
+			chan);
+		vc_dmaman_chan_free(dmaman, chan);
+		chan = -ENOENT;
+		goto out;
+	}
+
+	*out_dma_base = BCM2708_DMA_CHANIO(dmaman->dma_base, chan);
+	*out_dma_irq = irq;
+	dev_dbg(dmaman_dev,
+		"Legacy API allocated channel=%d, base=%p, irq=%i\n",
+		chan, *out_dma_base, *out_dma_irq);
+
+out:
+	mutex_unlock(&dmaman->lock);
+
+	return chan;
+}
+EXPORT_SYMBOL_GPL(bcm_dma_chan_alloc);
+
+extern int bcm_dma_chan_free(int channel)
+{
+	struct vc_dmaman *dmaman = g_dmaman;
+	int rc;
+
+	if (!dmaman_dev)
+		return -ENODEV;
+
+	mutex_lock(&dmaman->lock);
+	rc = vc_dmaman_chan_free(dmaman, channel);
+	mutex_unlock(&dmaman->lock);
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(bcm_dma_chan_free);
+
+int bcm_dmaman_probe(struct platform_device *pdev, void __iomem *base,
+		     u32 chans_available)
+{
+	struct device *dev = &pdev->dev;
+	struct vc_dmaman *dmaman;
+
+	dmaman = devm_kzalloc(dev, sizeof(*dmaman), GFP_KERNEL);
+	if (!dmaman)
+		return -ENOMEM;
+
+	mutex_init(&dmaman->lock);
+	vc_dmaman_init(dmaman, base, chans_available);
+	g_dmaman = dmaman;
+	dmaman_dev = dev;
+
+	dev_info(dev, "DMA legacy API manager, dmachans=0x%x\n",
+		 chans_available);
+
+	return 0;
+}
+EXPORT_SYMBOL(bcm_dmaman_probe);
+
+int bcm_dmaman_remove(struct platform_device *pdev)
+{
+	dmaman_dev = NULL;
+
+	return 0;
+}
+EXPORT_SYMBOL(bcm_dmaman_remove);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/dma/bcm2835-dma.c b/drivers/dma/bcm2835-dma.c
index e1b92b4d7b05..f0f17bc9373f 100644
--- a/drivers/dma/bcm2835-dma.c
+++ b/drivers/dma/bcm2835-dma.c
@@ -18,6 +18,7 @@
  *	Copyright 2012 Marvell International Ltd.
  */
 #include <linux/dmaengine.h>
+#include <linux/dma-direct.h>
 #include <linux/dma-mapping.h>
 #include <linux/dmapool.h>
 #include <linux/err.h>
@@ -25,6 +26,7 @@
 #include <linux/interrupt.h>
 #include <linux/list.h>
 #include <linux/module.h>
+#include <linux/platform_data/dma-bcm2708.h>
 #include <linux/platform_device.h>
 #include <linux/slab.h>
 #include <linux/io.h>
@@ -36,6 +38,13 @@
 
 #define BCM2835_DMA_MAX_DMA_CHAN_SUPPORTED 14
 #define BCM2835_DMA_CHAN_NAME_SIZE 8
+#define BCM2835_DMA_BULK_MASK  BIT(0)
+#define BCM2711_DMA_MEMCPY_CHAN 14
+
+struct bcm2835_dma_cfg_data {
+	u64	dma_mask;
+	u32	chan_40bit_mask;
+};
 
 /**
  * struct bcm2835_dmadev - BCM2835 DMA controller
@@ -48,6 +57,7 @@ struct bcm2835_dmadev {
 	struct dma_device ddev;
 	void __iomem *base;
 	dma_addr_t zero_page;
+	const struct bcm2835_dma_cfg_data *cfg_data;
 };
 
 struct bcm2835_dma_cb {
@@ -60,6 +70,17 @@ struct bcm2835_dma_cb {
 	uint32_t pad[2];
 };
 
+struct bcm2711_dma40_scb {
+	uint32_t ti;
+	uint32_t src;
+	uint32_t srci;
+	uint32_t dst;
+	uint32_t dsti;
+	uint32_t len;
+	uint32_t next_cb;
+	uint32_t rsvd;
+};
+
 struct bcm2835_cb_entry {
 	struct bcm2835_dma_cb *cb;
 	dma_addr_t paddr;
@@ -80,6 +101,8 @@ struct bcm2835_chan {
 	unsigned int irq_flags;
 
 	bool is_lite_channel;
+	bool is_40bit_channel;
+	bool is_2712;
 };
 
 struct bcm2835_desc {
@@ -136,11 +159,37 @@ struct bcm2835_desc {
 #define BCM2835_DMA_S_WIDTH	BIT(9) /* 128bit writes if set */
 #define BCM2835_DMA_S_DREQ	BIT(10) /* enable SREQ for source */
 #define BCM2835_DMA_S_IGNORE	BIT(11) /* ignore source reads - read 0 */
-#define BCM2835_DMA_BURST_LENGTH(x) ((x & 15) << 12)
+#define BCM2835_DMA_BURST_LENGTH(x) (((x) & 15) << 12)
+#define BCM2835_DMA_GET_BURST_LENGTH(x) (((x) >> 12) & 15)
+#define BCM2835_DMA_CS_FLAGS(x) (x & (BCM2835_DMA_PRIORITY(15) | \
+				      BCM2835_DMA_PANIC_PRIORITY(15) | \
+				      BCM2835_DMA_WAIT_FOR_WRITES | \
+				      BCM2835_DMA_DIS_DEBUG))
 #define BCM2835_DMA_PER_MAP(x)	((x & 31) << 16) /* REQ source */
 #define BCM2835_DMA_WAIT(x)	((x & 31) << 21) /* add DMA-wait cycles */
 #define BCM2835_DMA_NO_WIDE_BURSTS BIT(26) /* no 2 beat write bursts */
 
+/* A fake bit to request that the driver doesn't set the WAIT_RESP bit. */
+#define BCM2835_DMA_NO_WAIT_RESP BIT(27)
+#define WAIT_RESP(x) ((x & BCM2835_DMA_NO_WAIT_RESP) ? \
+		      0 : BCM2835_DMA_WAIT_RESP)
+
+/* A fake bit to request that the driver requires wide reads */
+#define BCM2835_DMA_WIDE_SOURCE BIT(24)
+#define WIDE_SOURCE(x) ((x & BCM2835_DMA_WIDE_SOURCE) ? \
+		      BCM2835_DMA_S_WIDTH : 0)
+
+/* A fake bit to request that the driver requires wide writes */
+#define BCM2835_DMA_WIDE_DEST BIT(25)
+#define WIDE_DEST(x) ((x & BCM2835_DMA_WIDE_DEST) ? \
+		      BCM2835_DMA_D_WIDTH : 0)
+
+/* A fake bit to request that the driver requires multi-beat burst */
+#define BCM2835_DMA_BURST BIT(30)
+#define BURST_LENGTH(x) ((x & BCM2835_DMA_BURST) ? \
+		      BCM2835_DMA_BURST_LENGTH(3) : 0)
+
+
 /* debug register bits */
 #define BCM2835_DMA_DEBUG_LAST_NOT_SET_ERR	BIT(0)
 #define BCM2835_DMA_DEBUG_FIFO_ERR		BIT(1)
@@ -165,13 +214,130 @@ struct bcm2835_desc {
 #define BCM2835_DMA_DATA_TYPE_S128	16
 
 /* Valid only for channels 0 - 14, 15 has its own base address */
-#define BCM2835_DMA_CHAN(n)	((n) << 8) /* Base address */
+#define BCM2835_DMA_CHAN_SIZE	0x100
+#define BCM2835_DMA_CHAN(n)	((n) * BCM2835_DMA_CHAN_SIZE) /* Base address */
 #define BCM2835_DMA_CHANIO(base, n) ((base) + BCM2835_DMA_CHAN(n))
 
 /* the max dma length for different channels */
 #define MAX_DMA_LEN SZ_1G
 #define MAX_LITE_DMA_LEN (SZ_64K - 4)
 
+/* 40-bit DMA support */
+#define BCM2711_DMA40_CS	0x00
+#define BCM2711_DMA40_CB	0x04
+#define BCM2711_DMA40_DEBUG	0x0c
+#define BCM2711_DMA40_TI	0x10
+#define BCM2711_DMA40_SRC	0x14
+#define BCM2711_DMA40_SRCI	0x18
+#define BCM2711_DMA40_DEST	0x1c
+#define BCM2711_DMA40_DESTI	0x20
+#define BCM2711_DMA40_LEN	0x24
+#define BCM2711_DMA40_NEXT_CB	0x28
+#define BCM2711_DMA40_DEBUG2	0x2c
+
+#define BCM2711_DMA40_ACTIVE		BIT(0)
+#define BCM2711_DMA40_END		BIT(1)
+#define BCM2711_DMA40_INT		BIT(2)
+#define BCM2711_DMA40_DREQ		BIT(3)  /* DREQ state */
+#define BCM2711_DMA40_RD_PAUSED		BIT(4)  /* Reading is paused */
+#define BCM2711_DMA40_WR_PAUSED		BIT(5)  /* Writing is paused */
+#define BCM2711_DMA40_DREQ_PAUSED	BIT(6)  /* Is paused by DREQ flow control */
+#define BCM2711_DMA40_WAITING_FOR_WRITES BIT(7)  /* Waiting for last write */
+// we always want to run in supervisor mode
+#define BCM2711_DMA40_PROT		(BIT(8)|BIT(9))
+#define BCM2711_DMA40_ERR		BIT(10)
+#define BCM2711_DMA40_QOS(x)		(((x) & 0x1f) << 16)
+#define BCM2711_DMA40_PANIC_QOS(x)	(((x) & 0x1f) << 20)
+#define BCM2711_DMA40_TRANSACTIONS	BIT(25)
+#define BCM2711_DMA40_WAIT_FOR_WRITES	BIT(28)
+#define BCM2711_DMA40_DISDEBUG		BIT(29)
+#define BCM2711_DMA40_ABORT		BIT(30)
+#define BCM2711_DMA40_HALT		BIT(31)
+
+#define BCM2711_DMA40_CS_FLAGS(x) (x & (BCM2711_DMA40_QOS(15) | \
+					BCM2711_DMA40_PANIC_QOS(15) | \
+					BCM2711_DMA40_WAIT_FOR_WRITES |	\
+					BCM2711_DMA40_DISDEBUG))
+
+/* Transfer information bits */
+#define BCM2711_DMA40_INTEN		BIT(0)
+#define BCM2711_DMA40_TDMODE		BIT(1) /* 2D-Mode */
+#define BCM2711_DMA40_WAIT_RESP		BIT(2) /* wait for AXI write to be acked */
+#define BCM2711_DMA40_WAIT_RD_RESP	BIT(3) /* wait for AXI read to complete */
+#define BCM2711_DMA40_PER_MAP(x)	((x & 31) << 9) /* REQ source */
+#define BCM2711_DMA40_S_DREQ		BIT(14) /* enable SREQ for source */
+#define BCM2711_DMA40_D_DREQ		BIT(15) /* enable DREQ for destination */
+#define BCM2711_DMA40_S_WAIT(x)		((x & 0xff) << 16) /* add DMA read-wait cycles */
+#define BCM2711_DMA40_D_WAIT(x)		((x & 0xff) << 24) /* add DMA write-wait cycles */
+
+/* debug register bits */
+#define BCM2711_DMA40_DEBUG_WRITE_ERR		BIT(0)
+#define BCM2711_DMA40_DEBUG_FIFO_ERR		BIT(1)
+#define BCM2711_DMA40_DEBUG_READ_ERR		BIT(2)
+#define BCM2711_DMA40_DEBUG_READ_CB_ERR		BIT(3)
+#define BCM2711_DMA40_DEBUG_IN_ON_ERR		BIT(8)
+#define BCM2711_DMA40_DEBUG_ABORT_ON_ERR	BIT(9)
+#define BCM2711_DMA40_DEBUG_HALT_ON_ERR		BIT(10)
+#define BCM2711_DMA40_DEBUG_DISABLE_CLK_GATE	BIT(11)
+#define BCM2711_DMA40_DEBUG_RSTATE_SHIFT	14
+#define BCM2711_DMA40_DEBUG_RSTATE_BITS		4
+#define BCM2711_DMA40_DEBUG_WSTATE_SHIFT	18
+#define BCM2711_DMA40_DEBUG_WSTATE_BITS		4
+#define BCM2711_DMA40_DEBUG_RESET		BIT(23)
+#define BCM2711_DMA40_DEBUG_ID_SHIFT		24
+#define BCM2711_DMA40_DEBUG_ID_BITS		4
+#define BCM2711_DMA40_DEBUG_VERSION_SHIFT	28
+#define BCM2711_DMA40_DEBUG_VERSION_BITS	4
+
+/* Valid only for channels 0 - 3 (11 - 14) */
+#define BCM2711_DMA40_CHAN(n)	(((n) + 11) << 8) /* Base address */
+#define BCM2711_DMA40_CHANIO(base, n) ((base) + BCM2711_DMA_CHAN(n))
+
+/* the max dma length for different channels */
+#define MAX_DMA40_LEN SZ_1G
+
+#define BCM2711_DMA40_BURST_LEN(x)	(((x) & 15) << 8)
+#define BCM2711_DMA40_INC		BIT(12)
+#define BCM2711_DMA40_SIZE_32		(0 << 13)
+#define BCM2711_DMA40_SIZE_64		(1 << 13)
+#define BCM2711_DMA40_SIZE_128		(2 << 13)
+#define BCM2711_DMA40_SIZE_256		(3 << 13)
+#define BCM2711_DMA40_IGNORE		BIT(15)
+#define BCM2711_DMA40_STRIDE(x)		((x) << 16) /* For 2D mode */
+
+#define BCM2711_DMA40_MEMCPY_FLAGS \
+	(BCM2711_DMA40_QOS(0) | \
+	 BCM2711_DMA40_PANIC_QOS(0) | \
+	 BCM2711_DMA40_WAIT_FOR_WRITES | \
+	 BCM2711_DMA40_DISDEBUG)
+
+#define BCM2711_DMA40_MEMCPY_XFER_INFO \
+	(BCM2711_DMA40_SIZE_128 | \
+	 BCM2711_DMA40_INC | \
+	 BCM2711_DMA40_BURST_LEN(16))
+
+struct bcm2835_dmadev *memcpy_parent;
+static void __iomem *memcpy_chan;
+static struct bcm2711_dma40_scb *memcpy_scb;
+static dma_addr_t memcpy_scb_dma;
+DEFINE_SPINLOCK(memcpy_lock);
+
+static const struct bcm2835_dma_cfg_data bcm2835_dma_cfg = {
+	.chan_40bit_mask = 0,
+	.dma_mask = DMA_BIT_MASK(32),
+};
+
+static const struct bcm2835_dma_cfg_data bcm2711_dma_cfg = {
+	.chan_40bit_mask = BIT(11) | BIT(12) | BIT(13) | BIT(14),
+	.dma_mask = DMA_BIT_MASK(36),
+};
+
+static const struct bcm2835_dma_cfg_data bcm2712_dma_cfg = {
+	.chan_40bit_mask = BIT(6) | BIT(7) | BIT(8) | BIT(9) |
+				 BIT(10) | BIT(11),
+	.dma_mask = DMA_BIT_MASK(40),
+};
+
 static inline size_t bcm2835_dma_max_frame_length(struct bcm2835_chan *c)
 {
 	/* lite and normal channels have different max frame length */
@@ -201,6 +367,36 @@ static inline struct bcm2835_desc *to_bcm2835_dma_desc(
 	return container_of(t, struct bcm2835_desc, vd.tx);
 }
 
+static inline uint32_t to_bcm2711_ti(uint32_t info)
+{
+	return ((info & BCM2835_DMA_INT_EN) ? BCM2711_DMA40_INTEN : 0) |
+		((info & BCM2835_DMA_WAIT_RESP) ? BCM2711_DMA40_WAIT_RESP : 0) |
+		((info & BCM2835_DMA_S_DREQ) ?
+		 (BCM2711_DMA40_S_DREQ | BCM2711_DMA40_WAIT_RD_RESP) : 0) |
+		((info & BCM2835_DMA_D_DREQ) ? BCM2711_DMA40_D_DREQ : 0) |
+		BCM2711_DMA40_PER_MAP((info >> 16) & 0x1f);
+}
+
+static inline uint32_t to_bcm2711_srci(uint32_t info)
+{
+	return ((info & BCM2835_DMA_S_INC) ? BCM2711_DMA40_INC : 0) |
+	       ((info & BCM2835_DMA_S_WIDTH) ? BCM2711_DMA40_SIZE_128 : 0) |
+	       BCM2711_DMA40_BURST_LEN(BCM2835_DMA_GET_BURST_LENGTH(info));
+}
+
+static inline uint32_t to_bcm2711_dsti(uint32_t info)
+{
+	return ((info & BCM2835_DMA_D_INC) ? BCM2711_DMA40_INC : 0) |
+	       ((info & BCM2835_DMA_D_WIDTH) ? BCM2711_DMA40_SIZE_128 : 0) |
+	       BCM2711_DMA40_BURST_LEN(BCM2835_DMA_GET_BURST_LENGTH(info));
+}
+
+static inline uint32_t to_40bit_cbaddr(dma_addr_t addr)
+{
+	BUG_ON(addr & 0x1f);
+	return (addr >> 5);
+}
+
 static void bcm2835_dma_free_cb_chain(struct bcm2835_desc *desc)
 {
 	size_t i;
@@ -219,45 +415,53 @@ static void bcm2835_dma_desc_free(struct virt_dma_desc *vd)
 }
 
 static void bcm2835_dma_create_cb_set_length(
-	struct bcm2835_chan *chan,
+	struct bcm2835_chan *c,
 	struct bcm2835_dma_cb *control_block,
 	size_t len,
 	size_t period_len,
 	size_t *total_len,
 	u32 finalextrainfo)
 {
-	size_t max_len = bcm2835_dma_max_frame_length(chan);
+	size_t max_len = bcm2835_dma_max_frame_length(c);
+	uint32_t cb_len;
 
 	/* set the length taking lite-channel limitations into account */
-	control_block->length = min_t(u32, len, max_len);
+	cb_len = min_t(u32, len, max_len);
 
-	/* finished if we have no period_length */
-	if (!period_len)
-		return;
+	if (period_len) {
+		/*
+		 * period_len means: that we need to generate
+		 * transfers that are terminating at every
+		 * multiple of period_len - this is typically
+		 * used to set the interrupt flag in info
+		 * which is required during cyclic transfers
+		 */
 
-	/*
-	 * period_len means: that we need to generate
-	 * transfers that are terminating at every
-	 * multiple of period_len - this is typically
-	 * used to set the interrupt flag in info
-	 * which is required during cyclic transfers
-	 */
+		/* have we filled in period_length yet? */
+		if (*total_len + cb_len < period_len) {
+			/* update number of bytes in this period so far */
+			*total_len += cb_len;
+		} else {
+			/* calculate the length that remains to reach period_len */
+			cb_len = period_len - *total_len;
 
-	/* have we filled in period_length yet? */
-	if (*total_len + control_block->length < period_len) {
-		/* update number of bytes in this period so far */
-		*total_len += control_block->length;
-		return;
+			/* reset total_length for next period */
+			*total_len = 0;
+		}
 	}
 
-	/* calculate the length that remains to reach period_length */
-	control_block->length = period_len - *total_len;
+	if (c->is_40bit_channel) {
+		struct bcm2711_dma40_scb *scb =
+			(struct bcm2711_dma40_scb *)control_block;
 
-	/* reset total_length for next period */
-	*total_len = 0;
-
-	/* add extrainfo bits in info */
-	control_block->info |= finalextrainfo;
+		scb->len = cb_len;
+		/* add extrainfo bits to ti */
+		scb->ti |= to_bcm2711_ti(finalextrainfo);
+	} else {
+		control_block->length = cb_len;
+		/* add extrainfo bits to info */
+		control_block->info |= finalextrainfo;
+	}
 }
 
 static inline size_t bcm2835_dma_count_frames_for_sg(
@@ -280,7 +484,7 @@ static inline size_t bcm2835_dma_count_frames_for_sg(
 /**
  * bcm2835_dma_create_cb_chain - create a control block and fills data in
  *
- * @chan:           the @dma_chan for which we run this
+ * @c:              the @bcm2835_chan for which we run this
  * @direction:      the direction in which we transfer
  * @cyclic:         it is a cyclic transfer
  * @info:           the default info bits to apply per controlblock
@@ -298,12 +502,11 @@ static inline size_t bcm2835_dma_count_frames_for_sg(
  * @gfp:            the GFP flag to use for allocation
  */
 static struct bcm2835_desc *bcm2835_dma_create_cb_chain(
-	struct dma_chan *chan, enum dma_transfer_direction direction,
+	struct bcm2835_chan *c, enum dma_transfer_direction direction,
 	bool cyclic, u32 info, u32 finalextrainfo, size_t frames,
 	dma_addr_t src, dma_addr_t dst, size_t buf_len,
 	size_t period_len, gfp_t gfp)
 {
-	struct bcm2835_chan *c = to_bcm2835_dma_chan(chan);
 	size_t len = buf_len, total_len;
 	size_t frame;
 	struct bcm2835_desc *d;
@@ -335,11 +538,27 @@ static struct bcm2835_desc *bcm2835_dma_create_cb_chain(
 
 		/* fill in the control block */
 		control_block = cb_entry->cb;
-		control_block->info = info;
-		control_block->src = src;
-		control_block->dst = dst;
-		control_block->stride = 0;
-		control_block->next = 0;
+		if (c->is_40bit_channel) {
+			struct bcm2711_dma40_scb *scb =
+				(struct bcm2711_dma40_scb *)control_block;
+			scb->ti = to_bcm2711_ti(info);
+			scb->src = lower_32_bits(src);
+			scb->srci= upper_32_bits(src) | to_bcm2711_srci(info);
+			scb->dst = lower_32_bits(dst);
+			scb->dsti = upper_32_bits(dst) | to_bcm2711_dsti(info);
+			scb->next_cb = 0;
+		} else {
+			control_block->info = info;
+			control_block->src = src;
+			control_block->dst = dst;
+			if (c->is_2712)
+				control_block->stride = (upper_32_bits(dst) << 8) |
+							upper_32_bits(src);
+			else
+				control_block->stride = 0;
+			control_block->next = 0;
+		}
+
 		/* set up length in control_block if requested */
 		if (buf_len) {
 			/* calculate length honoring period_length */
@@ -349,25 +568,52 @@ static struct bcm2835_desc *bcm2835_dma_create_cb_chain(
 				cyclic ? finalextrainfo : 0);
 
 			/* calculate new remaining length */
-			len -= control_block->length;
+			if (c->is_40bit_channel)
+				len -= ((struct bcm2711_dma40_scb *)control_block)->len;
+			else
+				len -= control_block->length;
 		}
 
 		/* link this the last controlblock */
-		if (frame)
-			d->cb_list[frame - 1].cb->next = cb_entry->paddr;
+		if (frame && c->is_40bit_channel)
+			((struct bcm2711_dma40_scb *)
+			 d->cb_list[frame - 1].cb)->next_cb =
+				to_40bit_cbaddr(cb_entry->paddr);
+		if (frame && !c->is_40bit_channel)
+			d->cb_list[frame - 1].cb->next = c->is_2712 ?
+			to_40bit_cbaddr(cb_entry->paddr) : cb_entry->paddr;
 
 		/* update src and dst and length */
-		if (src && (info & BCM2835_DMA_S_INC))
-			src += control_block->length;
-		if (dst && (info & BCM2835_DMA_D_INC))
-			dst += control_block->length;
+		if (src && (info & BCM2835_DMA_S_INC)) {
+			if (c->is_40bit_channel)
+				src += ((struct bcm2711_dma40_scb *)control_block)->len;
+			else
+				src += control_block->length;
+		}
+
+		if (dst && (info & BCM2835_DMA_D_INC)) {
+			if (c->is_40bit_channel)
+				dst += ((struct bcm2711_dma40_scb *)control_block)->len;
+			else
+				dst += control_block->length;
+		}
 
 		/* Length of total transfer */
-		d->size += control_block->length;
+		if (c->is_40bit_channel)
+			d->size += ((struct bcm2711_dma40_scb *)control_block)->len;
+		else
+			d->size += control_block->length;
 	}
 
 	/* the last frame requires extra flags */
-	d->cb_list[d->frames - 1].cb->info |= finalextrainfo;
+	if (c->is_40bit_channel) {
+		struct bcm2711_dma40_scb *scb =
+			(struct bcm2711_dma40_scb *)d->cb_list[d->frames-1].cb;
+
+		scb->ti |= to_bcm2711_ti(finalextrainfo);
+	} else {
+		d->cb_list[d->frames - 1].cb->info |= finalextrainfo;
+	}
 
 	/* detect a size mismatch */
 	if (buf_len && (d->size != buf_len))
@@ -381,13 +627,12 @@ static struct bcm2835_desc *bcm2835_dma_create_cb_chain(
 }
 
 static void bcm2835_dma_fill_cb_chain_with_sg(
-	struct dma_chan *chan,
+	struct bcm2835_chan *c,
 	enum dma_transfer_direction direction,
 	struct bcm2835_cb_entry *cb,
 	struct scatterlist *sgl,
 	unsigned int sg_len)
 {
-	struct bcm2835_chan *c = to_bcm2835_dma_chan(chan);
 	size_t len, max_len;
 	unsigned int i;
 	dma_addr_t addr;
@@ -395,14 +640,35 @@ static void bcm2835_dma_fill_cb_chain_with_sg(
 
 	max_len = bcm2835_dma_max_frame_length(c);
 	for_each_sg(sgl, sgent, sg_len, i) {
-		for (addr = sg_dma_address(sgent), len = sg_dma_len(sgent);
-		     len > 0;
-		     addr += cb->cb->length, len -= cb->cb->length, cb++) {
-			if (direction == DMA_DEV_TO_MEM)
-				cb->cb->dst = addr;
-			else
-				cb->cb->src = addr;
-			cb->cb->length = min(len, max_len);
+		if (c->is_40bit_channel) {
+			struct bcm2711_dma40_scb *scb;
+
+			for (addr = sg_dma_address(sgent),
+				     len = sg_dma_len(sgent);
+				     len > 0;
+			     addr += scb->len, len -= scb->len, cb++) {
+				scb = (struct bcm2711_dma40_scb *)cb->cb;
+				if (direction == DMA_DEV_TO_MEM) {
+					scb->dst = lower_32_bits(addr);
+					scb->dsti = upper_32_bits(addr) | BCM2711_DMA40_INC;
+				} else {
+					scb->src = lower_32_bits(addr);
+					scb->srci = upper_32_bits(addr) | BCM2711_DMA40_INC;
+				}
+				scb->len = min(len, max_len);
+			}
+		} else {
+			for (addr = sg_dma_address(sgent),
+				     len = sg_dma_len(sgent);
+			     len > 0;
+			     addr += cb->cb->length, len -= cb->cb->length,
+			     cb++) {
+				if (direction == DMA_DEV_TO_MEM)
+					cb->cb->dst = addr;
+				else
+					cb->cb->src = addr;
+				cb->cb->length = min(len, max_len);
+			}
 		}
 	}
 }
@@ -410,29 +676,74 @@ static void bcm2835_dma_fill_cb_chain_with_sg(
 static void bcm2835_dma_abort(struct bcm2835_chan *c)
 {
 	void __iomem *chan_base = c->chan_base;
-	long int timeout = 10000;
+	long timeout = 100;
 
-	/*
-	 * A zero control block address means the channel is idle.
-	 * (The ACTIVE flag in the CS register is not a reliable indicator.)
-	 */
-	if (!readl(chan_base + BCM2835_DMA_ADDR))
-		return;
+	if (c->is_40bit_channel) {
+		/*
+		 * A zero control block address means the channel is idle.
+		 * (The ACTIVE flag in the CS register is not a reliable indicator.)
+		 */
+		if (!readl(chan_base + BCM2711_DMA40_CB))
+			return;
+
+		/* Pause the current DMA */
+		writel(readl(chan_base + BCM2711_DMA40_CS) & ~BCM2711_DMA40_ACTIVE,
+			     chan_base + BCM2711_DMA40_CS);
+
+		/* wait for outstanding transactions to complete */
+		while ((readl(chan_base + BCM2711_DMA40_CS) & BCM2711_DMA40_TRANSACTIONS) &&
+			--timeout)
+			cpu_relax();
+
+		/* Peripheral might be stuck and fail to complete */
+		if (!timeout)
+			dev_err(c->vc.chan.device->dev,
+				"failed to complete pause on dma %d (CS:%08x)\n", c->ch,
+				readl(chan_base + BCM2711_DMA40_CS));
+
+		/* Set CS back to default state */
+		writel(BCM2711_DMA40_PROT, chan_base + BCM2711_DMA40_CS);
+
+		/* Reset the DMA */
+		writel(readl(chan_base + BCM2711_DMA40_DEBUG) | BCM2711_DMA40_DEBUG_RESET,
+		       chan_base + BCM2711_DMA40_DEBUG);
+	} else {
+		/*
+		 * A zero control block address means the channel is idle.
+		 * (The ACTIVE flag in the CS register is not a reliable indicator.)
+		 */
+		if (!readl(chan_base + BCM2835_DMA_ADDR))
+			return;
+
+		/* We need to clear the next DMA block pending */
+		writel(0, chan_base + BCM2835_DMA_NEXTCB);
 
-	/* Write 0 to the active bit - Pause the DMA */
-	writel(0, chan_base + BCM2835_DMA_CS);
+		/* Abort the DMA, which needs to be enabled to complete */
+		writel(readl(chan_base + BCM2835_DMA_CS) | BCM2835_DMA_ABORT | BCM2835_DMA_ACTIVE,
+		      chan_base + BCM2835_DMA_CS);
 
-	/* Wait for any current AXI transfer to complete */
-	while ((readl(chan_base + BCM2835_DMA_CS) &
-		BCM2835_DMA_WAITING_FOR_WRITES) && --timeout)
-		cpu_relax();
+		/* wait for DMA to be aborted */
+		while ((readl(chan_base + BCM2835_DMA_CS) & BCM2835_DMA_ABORT) && --timeout)
+			cpu_relax();
 
-	/* Peripheral might be stuck and fail to signal AXI write responses */
-	if (!timeout)
-		dev_err(c->vc.chan.device->dev,
-			"failed to complete outstanding writes\n");
+		/* Write 0 to the active bit - Pause the DMA */
+		writel(readl(chan_base + BCM2835_DMA_CS) & ~BCM2835_DMA_ACTIVE,
+		       chan_base + BCM2835_DMA_CS);
 
-	writel(BCM2835_DMA_RESET, chan_base + BCM2835_DMA_CS);
+		/*
+		 * Peripheral might be stuck and fail to complete
+		 * This is expected when dreqs are enabled but not asserted
+		 * so only report error in non dreq case
+		 */
+		if (!timeout && !(readl(chan_base + BCM2835_DMA_TI) &
+		   (BCM2835_DMA_S_DREQ | BCM2835_DMA_D_DREQ)))
+			dev_err(c->vc.chan.device->dev,
+				"failed to complete pause on dma %d (CS:%08x)\n", c->ch,
+				readl(chan_base + BCM2835_DMA_CS));
+
+		/* Set CS back to default state and reset the DMA */
+		writel(BCM2835_DMA_RESET, chan_base + BCM2835_DMA_CS);
+	}
 }
 
 static void bcm2835_dma_start_desc(struct bcm2835_chan *c)
@@ -449,8 +760,19 @@ static void bcm2835_dma_start_desc(struct bcm2835_chan *c)
 
 	c->desc = d = to_bcm2835_dma_desc(&vd->tx);
 
-	writel(d->cb_list[0].paddr, c->chan_base + BCM2835_DMA_ADDR);
-	writel(BCM2835_DMA_ACTIVE, c->chan_base + BCM2835_DMA_CS);
+	if (c->is_40bit_channel) {
+		writel(to_40bit_cbaddr(d->cb_list[0].paddr),
+		       c->chan_base + BCM2711_DMA40_CB);
+		writel(BCM2711_DMA40_ACTIVE | BCM2711_DMA40_PROT | BCM2711_DMA40_CS_FLAGS(c->dreq),
+		       c->chan_base + BCM2711_DMA40_CS);
+	} else {
+		writel(BIT(31), c->chan_base + BCM2835_DMA_CS);
+
+		writel(c->is_2712 ? to_40bit_cbaddr(d->cb_list[0].paddr) : d->cb_list[0].paddr,
+		       c->chan_base + BCM2835_DMA_ADDR);
+		writel(BCM2835_DMA_ACTIVE | BCM2835_DMA_CS_FLAGS(c->dreq),
+		       c->chan_base + BCM2835_DMA_CS);
+	}
 }
 
 static irqreturn_t bcm2835_dma_callback(int irq, void *data)
@@ -477,8 +799,13 @@ static irqreturn_t bcm2835_dma_callback(int irq, void *data)
 	 * if this IRQ handler is threaded.) If the channel is finished, it
 	 * will remain idle despite the ACTIVE flag being set.
 	 */
-	writel(BCM2835_DMA_INT | BCM2835_DMA_ACTIVE,
-	       c->chan_base + BCM2835_DMA_CS);
+	if (c->is_40bit_channel)
+		writel(BCM2835_DMA_INT | BCM2711_DMA40_ACTIVE | BCM2711_DMA40_PROT |
+		       BCM2711_DMA40_CS_FLAGS(c->dreq),
+		       c->chan_base + BCM2711_DMA40_CS);
+	else
+		writel(BCM2835_DMA_INT | BCM2835_DMA_ACTIVE | BCM2835_DMA_CS_FLAGS(c->dreq),
+		       c->chan_base + BCM2835_DMA_CS);
 
 	d = c->desc;
 
@@ -540,20 +867,39 @@ static size_t bcm2835_dma_desc_size_pos(struct bcm2835_desc *d, dma_addr_t addr)
 	unsigned int i;
 	size_t size;
 
-	for (size = i = 0; i < d->frames; i++) {
-		struct bcm2835_dma_cb *control_block = d->cb_list[i].cb;
-		size_t this_size = control_block->length;
-		dma_addr_t dma;
+	if (d->c->is_40bit_channel) {
+		for (size = i = 0; i < d->frames; i++) {
+			struct bcm2711_dma40_scb *control_block =
+				(struct bcm2711_dma40_scb *)d->cb_list[i].cb;
+			size_t this_size = control_block->len;
+			dma_addr_t dma;
 
-		if (d->dir == DMA_DEV_TO_MEM)
-			dma = control_block->dst;
-		else
-			dma = control_block->src;
+			if (d->dir == DMA_DEV_TO_MEM)
+				dma = control_block->dst;
+			else
+				dma = control_block->src;
+
+			if (size)
+				size += this_size;
+			else if (addr >= dma && addr < dma + this_size)
+				size += dma + this_size - addr;
+		}
+	} else {
+		for (size = i = 0; i < d->frames; i++) {
+			struct bcm2835_dma_cb *control_block = d->cb_list[i].cb;
+			size_t this_size = control_block->length;
+			dma_addr_t dma;
+
+			if (d->dir == DMA_DEV_TO_MEM)
+				dma = control_block->dst;
+			else
+				dma = control_block->src;
 
-		if (size)
-			size += this_size;
-		else if (addr >= dma && addr < dma + this_size)
-			size += dma + this_size - addr;
+			if (size)
+				size += this_size;
+			else if (addr >= dma && addr < dma + this_size)
+				size += dma + this_size - addr;
+		}
 	}
 
 	return size;
@@ -580,12 +926,25 @@ static enum dma_status bcm2835_dma_tx_status(struct dma_chan *chan,
 		struct bcm2835_desc *d = c->desc;
 		dma_addr_t pos;
 
-		if (d->dir == DMA_MEM_TO_DEV)
+		if (d->dir == DMA_MEM_TO_DEV && c->is_40bit_channel) {
+			u64 lo_bits, hi_bits;
+
+			lo_bits = readl(c->chan_base + BCM2711_DMA40_SRC);
+			hi_bits = readl(c->chan_base + BCM2711_DMA40_SRCI) & 0xff;
+			pos = (hi_bits << 32) | lo_bits;
+		} else if (d->dir == DMA_MEM_TO_DEV && !c->is_40bit_channel) {
 			pos = readl(c->chan_base + BCM2835_DMA_SOURCE_AD);
-		else if (d->dir == DMA_DEV_TO_MEM)
+		} else if (d->dir == DMA_DEV_TO_MEM && c->is_40bit_channel) {
+			u64 lo_bits, hi_bits;
+
+			lo_bits = readl(c->chan_base + BCM2711_DMA40_DEST);
+			hi_bits = readl(c->chan_base + BCM2711_DMA40_DESTI) & 0xff;
+			pos = (hi_bits << 32) | lo_bits;
+		} else if (d->dir == DMA_DEV_TO_MEM && !c->is_40bit_channel) {
 			pos = readl(c->chan_base + BCM2835_DMA_DEST_AD);
-		else
+		} else {
 			pos = 0;
+		}
 
 		txstate->residue = bcm2835_dma_desc_size_pos(d, pos);
 	} else {
@@ -615,8 +974,10 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_memcpy(
 {
 	struct bcm2835_chan *c = to_bcm2835_dma_chan(chan);
 	struct bcm2835_desc *d;
-	u32 info = BCM2835_DMA_D_INC | BCM2835_DMA_S_INC;
-	u32 extra = BCM2835_DMA_INT_EN | BCM2835_DMA_WAIT_RESP;
+	u32 info = BCM2835_DMA_D_INC | BCM2835_DMA_S_INC |
+		   WAIT_RESP(c->dreq) | WIDE_SOURCE(c->dreq) |
+		   WIDE_DEST(c->dreq) | BURST_LENGTH(c->dreq);
+	u32 extra = BCM2835_DMA_INT_EN;
 	size_t max_len = bcm2835_dma_max_frame_length(c);
 	size_t frames;
 
@@ -628,7 +989,7 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_memcpy(
 	frames = bcm2835_dma_frames_for_length(len, max_len);
 
 	/* allocate the CB chain - this also fills in the pointers */
-	d = bcm2835_dma_create_cb_chain(chan, DMA_MEM_TO_MEM, false,
+	d = bcm2835_dma_create_cb_chain(c, DMA_MEM_TO_MEM, false,
 					info, extra, frames,
 					src, dst, len, 0, GFP_KERNEL);
 	if (!d)
@@ -646,7 +1007,8 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_slave_sg(
 	struct bcm2835_chan *c = to_bcm2835_dma_chan(chan);
 	struct bcm2835_desc *d;
 	dma_addr_t src = 0, dst = 0;
-	u32 info = BCM2835_DMA_WAIT_RESP;
+	u32 info = WAIT_RESP(c->dreq) | WIDE_SOURCE(c->dreq) |
+		   WIDE_DEST(c->dreq) | BURST_LENGTH(c->dreq);
 	u32 extra = BCM2835_DMA_INT_EN;
 	size_t frames;
 
@@ -662,12 +1024,12 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_slave_sg(
 	if (direction == DMA_DEV_TO_MEM) {
 		if (c->cfg.src_addr_width != DMA_SLAVE_BUSWIDTH_4_BYTES)
 			return NULL;
-		src = c->cfg.src_addr;
+		src = phys_to_dma(chan->device->dev, c->cfg.src_addr);
 		info |= BCM2835_DMA_S_DREQ | BCM2835_DMA_D_INC;
 	} else {
 		if (c->cfg.dst_addr_width != DMA_SLAVE_BUSWIDTH_4_BYTES)
 			return NULL;
-		dst = c->cfg.dst_addr;
+		dst = phys_to_dma(chan->device->dev, c->cfg.dst_addr);
 		info |= BCM2835_DMA_D_DREQ | BCM2835_DMA_S_INC;
 	}
 
@@ -675,7 +1037,7 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_slave_sg(
 	frames = bcm2835_dma_count_frames_for_sg(c, sgl, sg_len);
 
 	/* allocate the CB chain */
-	d = bcm2835_dma_create_cb_chain(chan, direction, false,
+	d = bcm2835_dma_create_cb_chain(c, direction, false,
 					info, extra,
 					frames, src, dst, 0, 0,
 					GFP_NOWAIT);
@@ -683,7 +1045,7 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_slave_sg(
 		return NULL;
 
 	/* fill in frames with scatterlist pointers */
-	bcm2835_dma_fill_cb_chain_with_sg(chan, direction, d->cb_list,
+	bcm2835_dma_fill_cb_chain_with_sg(c, direction, d->cb_list,
 					  sgl, sg_len);
 
 	return vchan_tx_prep(&c->vc, &d->vd, flags);
@@ -698,7 +1060,8 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_cyclic(
 	struct bcm2835_chan *c = to_bcm2835_dma_chan(chan);
 	struct bcm2835_desc *d;
 	dma_addr_t src, dst;
-	u32 info = BCM2835_DMA_WAIT_RESP;
+	u32 info = WAIT_RESP(c->dreq) | WIDE_SOURCE(c->dreq) |
+		   WIDE_DEST(c->dreq) | BURST_LENGTH(c->dreq);
 	u32 extra = 0;
 	size_t max_len = bcm2835_dma_max_frame_length(c);
 	size_t frames;
@@ -736,13 +1099,13 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_cyclic(
 	if (direction == DMA_DEV_TO_MEM) {
 		if (c->cfg.src_addr_width != DMA_SLAVE_BUSWIDTH_4_BYTES)
 			return NULL;
-		src = c->cfg.src_addr;
+		src = phys_to_dma(chan->device->dev, c->cfg.src_addr);
 		dst = buf_addr;
 		info |= BCM2835_DMA_S_DREQ | BCM2835_DMA_D_INC;
 	} else {
 		if (c->cfg.dst_addr_width != DMA_SLAVE_BUSWIDTH_4_BYTES)
 			return NULL;
-		dst = c->cfg.dst_addr;
+		dst = phys_to_dma(chan->device->dev, c->cfg.dst_addr);
 		src = buf_addr;
 		info |= BCM2835_DMA_D_DREQ | BCM2835_DMA_S_INC;
 
@@ -762,7 +1125,7 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_cyclic(
 	 * note that we need to use GFP_NOWAIT, as the ALSA i2s dmaengine
 	 * implementation calls prep_dma_cyclic with interrupts disabled.
 	 */
-	d = bcm2835_dma_create_cb_chain(chan, direction, true,
+	d = bcm2835_dma_create_cb_chain(c, direction, true,
 					info, extra,
 					frames, src, dst, buf_len,
 					period_len, GFP_NOWAIT);
@@ -770,7 +1133,13 @@ static struct dma_async_tx_descriptor *bcm2835_dma_prep_dma_cyclic(
 		return NULL;
 
 	/* wrap around into a loop */
-	d->cb_list[d->frames - 1].cb->next = d->cb_list[0].paddr;
+	if (c->is_40bit_channel)
+		((struct bcm2711_dma40_scb *)
+		 d->cb_list[frames - 1].cb)->next_cb =
+			to_40bit_cbaddr(d->cb_list[0].paddr);
+	else
+		d->cb_list[d->frames - 1].cb->next = c->is_2712 ?
+		to_40bit_cbaddr(d->cb_list[0].paddr) : d->cb_list[0].paddr;
 
 	return vchan_tx_prep(&c->vc, &d->vd, flags);
 }
@@ -831,10 +1200,14 @@ static int bcm2835_dma_chan_init(struct bcm2835_dmadev *d, int chan_id,
 	c->irq_number = irq;
 	c->irq_flags = irq_flags;
 
-	/* check in DEBUG register if this is a LITE channel */
-	if (readl(c->chan_base + BCM2835_DMA_DEBUG) &
-		BCM2835_DMA_DEBUG_LITE)
+	/* check for 40bit and lite channels */
+	if (d->cfg_data->chan_40bit_mask & BIT(chan_id))
+		c->is_40bit_channel = true;
+	else if (readl(c->chan_base + BCM2835_DMA_DEBUG) &
+		 BCM2835_DMA_DEBUG_LITE)
 		c->is_lite_channel = true;
+	if (d->cfg_data->dma_mask == DMA_BIT_MASK(40))
+		c->is_2712 = true;
 
 	return 0;
 }
@@ -854,7 +1227,9 @@ static void bcm2835_dma_free(struct bcm2835_dmadev *od)
 }
 
 static const struct of_device_id bcm2835_dma_of_match[] = {
-	{ .compatible = "brcm,bcm2835-dma", },
+	{ .compatible = "brcm,bcm2835-dma", .data = &bcm2835_dma_cfg },
+	{ .compatible = "brcm,bcm2711-dma", .data = &bcm2711_dma_cfg },
+	{ .compatible = "brcm,bcm2712-dma", .data = &bcm2712_dma_cfg },
 	{},
 };
 MODULE_DEVICE_TABLE(of, bcm2835_dma_of_match);
@@ -877,7 +1252,10 @@ static struct dma_chan *bcm2835_dma_xlate(struct of_phandle_args *spec,
 
 static int bcm2835_dma_probe(struct platform_device *pdev)
 {
+	const struct bcm2835_dma_cfg_data *cfg_data;
+	const struct of_device_id *of_id;
 	struct bcm2835_dmadev *od;
+	struct resource *res;
 	void __iomem *base;
 	int rc;
 	int i, j;
@@ -885,11 +1263,20 @@ static int bcm2835_dma_probe(struct platform_device *pdev)
 	int irq_flags;
 	uint32_t chans_available;
 	char chan_name[BCM2835_DMA_CHAN_NAME_SIZE];
+	int chan_count, chan_start, chan_end;
+
+	of_id = of_match_node(bcm2835_dma_of_match, pdev->dev.of_node);
+	if (!of_id) {
+		dev_err(&pdev->dev, "Failed to match compatible string\n");
+		return -EINVAL;
+	}
+
+	cfg_data = of_id->data;
 
 	if (!pdev->dev.dma_mask)
 		pdev->dev.dma_mask = &pdev->dev.coherent_dma_mask;
 
-	rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+	rc = dma_set_mask_and_coherent(&pdev->dev, cfg_data->dma_mask);
 	if (rc) {
 		dev_err(&pdev->dev, "Unable to set DMA mask\n");
 		return rc;
@@ -901,10 +1288,17 @@ static int bcm2835_dma_probe(struct platform_device *pdev)
 
 	dma_set_max_seg_size(&pdev->dev, 0x3FFFFFFF);
 
-	base = devm_platform_ioremap_resource(pdev, 0);
+	base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
 	if (IS_ERR(base))
 		return PTR_ERR(base);
 
+	/* The set of channels can be split across multiple instances. */
+	chan_start = ((u32)(uintptr_t)base / BCM2835_DMA_CHAN_SIZE) & 0xf;
+	base -= BCM2835_DMA_CHAN(chan_start);
+	chan_count = resource_size(res) / BCM2835_DMA_CHAN_SIZE;
+	chan_end = min(chan_start + chan_count,
+			 BCM2835_DMA_MAX_DMA_CHAN_SUPPORTED + 1);
+
 	od->base = base;
 
 	dma_cap_set(DMA_SLAVE, od->ddev.cap_mask);
@@ -940,6 +1334,14 @@ static int bcm2835_dma_probe(struct platform_device *pdev)
 		return -ENOMEM;
 	}
 
+	of_id = of_match_node(bcm2835_dma_of_match, pdev->dev.of_node);
+	if (!of_id) {
+		dev_err(&pdev->dev, "Failed to match compatible string\n");
+		return -EINVAL;
+	}
+
+	od->cfg_data = cfg_data;
+
 	/* Request DMA channel mask from device tree */
 	if (of_property_read_u32(pdev->dev.of_node,
 			"brcm,dma-channel-mask",
@@ -949,8 +1351,36 @@ static int bcm2835_dma_probe(struct platform_device *pdev)
 		goto err_no_dma;
 	}
 
+#ifdef CONFIG_DMA_BCM2708
+	/* One channel is reserved for the legacy API */
+	if (chans_available & BCM2835_DMA_BULK_MASK) {
+		rc = bcm_dmaman_probe(pdev, base,
+				      chans_available & BCM2835_DMA_BULK_MASK);
+		if (rc)
+			dev_err(&pdev->dev,
+				"Failed to initialize the legacy API\n");
+
+		chans_available &= ~BCM2835_DMA_BULK_MASK;
+	}
+#endif
+
+	/* And possibly one for the 40-bit DMA memcpy API */
+	if (chans_available & od->cfg_data->chan_40bit_mask &
+	    BIT(BCM2711_DMA_MEMCPY_CHAN)) {
+		memcpy_parent = od;
+		memcpy_chan = BCM2835_DMA_CHANIO(base, BCM2711_DMA_MEMCPY_CHAN);
+		memcpy_scb = dma_alloc_coherent(memcpy_parent->ddev.dev,
+						sizeof(*memcpy_scb),
+						&memcpy_scb_dma, GFP_KERNEL);
+		if (!memcpy_scb)
+			dev_warn(&pdev->dev,
+				 "Failed to allocated memcpy scb\n");
+
+		chans_available &= ~BIT(BCM2711_DMA_MEMCPY_CHAN);
+	}
+
 	/* get irqs for each channel that we support */
-	for (i = 0; i <= BCM2835_DMA_MAX_DMA_CHAN_SUPPORTED; i++) {
+	for (i = chan_start; i < chan_end; i++) {
 		/* skip masked out channels */
 		if (!(chans_available & (1 << i))) {
 			irq[i] = -1;
@@ -973,13 +1403,17 @@ static int bcm2835_dma_probe(struct platform_device *pdev)
 		irq[i] = platform_get_irq(pdev, i < 11 ? i : 11);
 	}
 
+	chan_count = 0;
+
 	/* get irqs for each channel */
-	for (i = 0; i <= BCM2835_DMA_MAX_DMA_CHAN_SUPPORTED; i++) {
+	for (i = chan_start; i < chan_end; i++) {
 		/* skip channels without irq */
 		if (irq[i] < 0)
 			continue;
 
 		/* check if there are other channels that also use this irq */
+		/* FIXME: This will fail if interrupts are shared across
+		   instances */
 		irq_flags = 0;
 		for (j = 0; j <= BCM2835_DMA_MAX_DMA_CHAN_SUPPORTED; j++)
 			if ((i != j) && (irq[j] == irq[i])) {
@@ -991,9 +1425,10 @@ static int bcm2835_dma_probe(struct platform_device *pdev)
 		rc = bcm2835_dma_chan_init(od, i, irq[i], irq_flags);
 		if (rc)
 			goto err_no_dma;
+		chan_count++;
 	}
 
-	dev_dbg(&pdev->dev, "Initialized %i DMA channels\n", i);
+	dev_dbg(&pdev->dev, "Initialized %i DMA channels\n", chan_count);
 
 	/* Device-tree DMA controller registration */
 	rc = of_dma_controller_register(pdev->dev.of_node,
@@ -1023,7 +1458,15 @@ static void bcm2835_dma_remove(struct platform_device *pdev)
 {
 	struct bcm2835_dmadev *od = platform_get_drvdata(pdev);
 
+	bcm_dmaman_remove(pdev);
 	dma_async_device_unregister(&od->ddev);
+	if (memcpy_parent == od) {
+		dma_free_coherent(&pdev->dev, sizeof(*memcpy_scb), memcpy_scb,
+				  memcpy_scb_dma);
+		memcpy_parent = NULL;
+		memcpy_scb = NULL;
+		memcpy_chan = NULL;
+	}
 	bcm2835_dma_free(od);
 }
 
@@ -1036,7 +1479,22 @@ static struct platform_driver bcm2835_dma_driver = {
 	},
 };
 
-module_platform_driver(bcm2835_dma_driver);
+static int bcm2835_dma_init(void)
+{
+	return platform_driver_register(&bcm2835_dma_driver);
+}
+
+static void bcm2835_dma_exit(void)
+{
+	platform_driver_unregister(&bcm2835_dma_driver);
+}
+
+/*
+ * Load after serial driver (arch_initcall) so we see the messages if it fails,
+ * but before drivers (module_init) that need a DMA channel.
+ */
+subsys_initcall(bcm2835_dma_init);
+module_exit(bcm2835_dma_exit);
 
 MODULE_ALIAS("platform:bcm2835-dma");
 MODULE_DESCRIPTION("BCM2835 DMA engine driver");
diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
index fffafa86d964..a82f5ce5e39f 100644
--- a/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
+++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac-platform.c
@@ -12,6 +12,7 @@
 #include <linux/device.h>
 #include <linux/dmaengine.h>
 #include <linux/dmapool.h>
+#include <linux/dma-direct.h>
 #include <linux/dma-mapping.h>
 #include <linux/err.h>
 #include <linux/interrupt.h>
@@ -95,6 +96,17 @@ axi_chan_iowrite64(struct axi_dma_chan *chan, u32 reg, u64 val)
 	iowrite32(upper_32_bits(val), chan->chan_regs + reg + 4);
 }
 
+static inline u64
+axi_chan_ioread64(struct axi_dma_chan *chan, u32 reg)
+{
+	/*
+	 * We split one 64 bit read into two 32 bit reads as some HW doesn't
+	 * support 64 bit access.
+	 */
+	return ((u64)ioread32(chan->chan_regs + reg + 4) << 32) +
+		ioread32(chan->chan_regs + reg);
+}
+
 static inline void axi_chan_config_write(struct axi_dma_chan *chan,
 					 struct axi_dma_chan_config *config)
 {
@@ -266,7 +278,18 @@ static void axi_dma_hw_init(struct axi_dma_chip *chip)
 {
 	int ret;
 	u32 i;
-
+	int retries = 1000;
+
+	axi_dma_iowrite32(chip, DMAC_RESET, 1);
+	while (axi_dma_ioread32(chip, DMAC_RESET)) {
+		retries--;
+		if (!retries) {
+			dev_err(chip->dev, "%s: DMAC failed to reset\n",
+				__func__);
+			return;
+		}
+		cpu_relax();
+	}
 	for (i = 0; i < chip->dw->hdata->nr_channels; i++) {
 		axi_chan_irq_disable(&chip->dw->chan[i], DWAXIDMAC_IRQ_ALL);
 		axi_chan_disable(&chip->dw->chan[i]);
@@ -284,6 +307,15 @@ static u32 axi_chan_get_xfer_width(struct axi_dma_chan *chan, dma_addr_t src,
 	return __ffs(src | dst | len | BIT(max_width));
 }
 
+static u32 axi_dma_encode_msize(u32 max_burst)
+{
+	if (max_burst <= 1)
+		return DWAXIDMAC_BURST_TRANS_LEN_1;
+	if (max_burst > 1024)
+		return DWAXIDMAC_BURST_TRANS_LEN_1024;
+	return fls(max_burst) - 2;
+}
+
 static inline const char *axi_chan_name(struct axi_dma_chan *chan)
 {
 	return dma_chan_name(&chan->vc.chan);
@@ -335,7 +367,8 @@ static void axi_desc_put(struct axi_dma_desc *desc)
 
 	for (descs_put = 0; descs_put < count; descs_put++) {
 		hw_desc = &desc->hw_desc[descs_put];
-		dma_pool_free(chan->desc_pool, hw_desc->lli, hw_desc->llp);
+		if (hw_desc->lli)
+			dma_pool_free(chan->desc_pool, hw_desc->lli, hw_desc->llp);
 	}
 
 	kfree(desc->hw_desc);
@@ -351,6 +384,48 @@ static void vchan_desc_put(struct virt_dma_desc *vdesc)
 	axi_desc_put(vd_to_axi_desc(vdesc));
 }
 
+static u32 axi_dma_desc_src_pos(struct axi_dma_desc *desc, dma_addr_t addr)
+{
+	unsigned int idx = 0;
+	u32 pos = 0;
+
+	while (pos < desc->length) {
+		struct axi_dma_hw_desc *hw_desc = &desc->hw_desc[idx++];
+		u32 len = hw_desc->len;
+		dma_addr_t start = le64_to_cpu(hw_desc->lli->sar);
+
+		if (addr >= start && addr <= (start + len)) {
+			pos += addr - start;
+			break;
+		}
+
+		pos += len;
+	}
+
+	return pos;
+}
+
+static u32 axi_dma_desc_dst_pos(struct axi_dma_desc *desc, dma_addr_t addr)
+{
+	unsigned int idx = 0;
+	u32 pos = 0;
+
+	while (pos < desc->length) {
+		struct axi_dma_hw_desc *hw_desc = &desc->hw_desc[idx++];
+		u32 len = hw_desc->len;
+		dma_addr_t start = le64_to_cpu(hw_desc->lli->dar);
+
+		if (addr >= start && addr <= (start + len)) {
+			pos += addr - start;
+			break;
+		}
+
+		pos += len;
+	}
+
+	return pos;
+}
+
 static enum dma_status
 dma_chan_tx_status(struct dma_chan *dchan, dma_cookie_t cookie,
 		  struct dma_tx_state *txstate)
@@ -360,10 +435,7 @@ dma_chan_tx_status(struct dma_chan *dchan, dma_cookie_t cookie,
 	enum dma_status status;
 	u32 completed_length;
 	unsigned long flags;
-	u32 completed_blocks;
 	size_t bytes = 0;
-	u32 length;
-	u32 len;
 
 	status = dma_cookie_status(dchan, cookie, txstate);
 	if (status == DMA_COMPLETE || !txstate)
@@ -372,16 +444,31 @@ dma_chan_tx_status(struct dma_chan *dchan, dma_cookie_t cookie,
 	spin_lock_irqsave(&chan->vc.lock, flags);
 
 	vdesc = vchan_find_desc(&chan->vc, cookie);
-	if (vdesc) {
-		length = vd_to_axi_desc(vdesc)->length;
-		completed_blocks = vd_to_axi_desc(vdesc)->completed_blocks;
-		len = vd_to_axi_desc(vdesc)->hw_desc[0].len;
-		completed_length = completed_blocks * len;
-		bytes = length - completed_length;
+	if (vdesc && vdesc == vchan_next_desc(&chan->vc)) {
+		/* This descriptor is in-progress */
+		struct axi_dma_desc *desc = vd_to_axi_desc(vdesc);
+		dma_addr_t addr;
+
+		if (chan->direction == DMA_MEM_TO_DEV) {
+			addr = axi_chan_ioread64(chan, CH_SAR);
+			completed_length = axi_dma_desc_src_pos(desc, addr);
+		} else if (chan->direction == DMA_DEV_TO_MEM) {
+			addr = axi_chan_ioread64(chan, CH_DAR);
+			completed_length = axi_dma_desc_dst_pos(desc, addr);
+		} else {
+			completed_length = 0;
+		}
+		bytes = desc->length - completed_length;
+	} else if (vdesc) {
+		/* Still in the queue so not started */
+		bytes = vd_to_axi_desc(vdesc)->length;
 	}
 
-	spin_unlock_irqrestore(&chan->vc.lock, flags);
+	if (chan->is_paused && status == DMA_IN_PROGRESS)
+		status = DMA_PAUSED;
+
 	dma_set_residue(txstate, bytes);
+	spin_unlock_irqrestore(&chan->vc.lock, flags);
 
 	return status;
 }
@@ -435,8 +522,6 @@ static void axi_chan_block_xfer_start(struct axi_dma_chan *chan,
 		return;
 	}
 
-	axi_dma_enable(chan->chip);
-
 	config.dst_multblk_type = DWAXIDMAC_MBLK_TYPE_LL;
 	config.src_multblk_type = DWAXIDMAC_MBLK_TYPE_LL;
 	config.tt_fc = DWAXIDMAC_TT_FC_MEM_TO_MEM_DMAC;
@@ -499,9 +584,11 @@ static void dma_chan_issue_pending(struct dma_chan *dchan)
 {
 	struct axi_dma_chan *chan = dchan_to_axi_dma_chan(dchan);
 	unsigned long flags;
+	bool was_empty;
 
 	spin_lock_irqsave(&chan->vc.lock, flags);
-	if (vchan_issue_pending(&chan->vc))
+	was_empty = list_empty(&chan->vc.desc_issued);
+	if (vchan_issue_pending(&chan->vc) && was_empty)
 		axi_chan_start_first_queued(chan);
 	spin_unlock_irqrestore(&chan->vc.lock, flags);
 }
@@ -569,7 +656,7 @@ static void dw_axi_dma_set_hw_channel(struct axi_dma_chan *chan, bool set)
 	unsigned long reg_value, val;
 
 	if (!chip->apb_regs) {
-		dev_err(chip->dev, "apb_regs not initialized\n");
+		dev_dbg(chip->dev, "apb_regs not initialized\n");
 		return;
 	}
 
@@ -657,34 +744,48 @@ static int dw_axi_dma_set_hw_desc(struct axi_dma_chan *chan,
 	size_t axi_block_ts;
 	size_t block_ts;
 	u32 ctllo, ctlhi;
-	u32 burst_len;
+	u32 burst_len = 0, mem_burst_msize, reg_burst_msize;
 
 	axi_block_ts = chan->chip->dw->hdata->block_size[chan->id];
 
 	mem_width = __ffs(data_width | mem_addr | len);
-	if (mem_width > DWAXIDMAC_TRANS_WIDTH_32)
-		mem_width = DWAXIDMAC_TRANS_WIDTH_32;
 
-	if (!IS_ALIGNED(mem_addr, 4)) {
-		dev_err(chan->chip->dev, "invalid buffer alignment\n");
-		return -EINVAL;
-	}
+	/* Use a reasonable upper limit otherwise residue reporting granularity grows large */
+	mem_burst_msize = axi_dma_encode_msize(16);
 
 	switch (chan->direction) {
 	case DMA_MEM_TO_DEV:
+		reg_burst_msize = axi_dma_encode_msize(chan->config.dst_maxburst);
 		reg_width = __ffs(chan->config.dst_addr_width);
-		device_addr = chan->config.dst_addr;
+		device_addr = phys_to_dma(chan->chip->dev, chan->config.dst_addr);
 		ctllo = reg_width << CH_CTL_L_DST_WIDTH_POS |
 			mem_width << CH_CTL_L_SRC_WIDTH_POS |
+			reg_burst_msize << CH_CTL_L_DST_MSIZE_POS |
+			mem_burst_msize << CH_CTL_L_SRC_MSIZE_POS |
 			DWAXIDMAC_CH_CTL_L_NOINC << CH_CTL_L_DST_INC_POS |
 			DWAXIDMAC_CH_CTL_L_INC << CH_CTL_L_SRC_INC_POS;
 		block_ts = len >> mem_width;
 		break;
 	case DMA_DEV_TO_MEM:
+		reg_burst_msize = axi_dma_encode_msize(chan->config.src_maxburst);
 		reg_width = __ffs(chan->config.src_addr_width);
-		device_addr = chan->config.src_addr;
+		/*
+		 * For devices where transfer lengths are not known upfront,
+		 * there is a danger when the destination is wider than the
+		 * source that partial words can be lost at the end of a transfer.
+		 * Ideally the controller would be able to flush the residue, but
+		 * it can't - it's not even possible to tell that there is any.
+		 * Instead, allow the client driver to avoid the problem by setting
+		 * a smaller width.
+		 */
+		if (chan->config.dst_addr_width &&
+		    (chan->config.dst_addr_width < mem_width))
+			mem_width = chan->config.dst_addr_width;
+		device_addr = phys_to_dma(chan->chip->dev, chan->config.src_addr);
 		ctllo = reg_width << CH_CTL_L_SRC_WIDTH_POS |
 			mem_width << CH_CTL_L_DST_WIDTH_POS |
+			mem_burst_msize << CH_CTL_L_DST_MSIZE_POS |
+			reg_burst_msize << CH_CTL_L_SRC_MSIZE_POS |
 			DWAXIDMAC_CH_CTL_L_INC << CH_CTL_L_DST_INC_POS |
 			DWAXIDMAC_CH_CTL_L_NOINC << CH_CTL_L_SRC_INC_POS;
 		block_ts = len >> reg_width;
@@ -693,6 +794,11 @@ static int dw_axi_dma_set_hw_desc(struct axi_dma_chan *chan,
 		return -EINVAL;
 	}
 
+	if (len % (1 << reg_width)) {
+		dev_err_ratelimited(chan->chip->dev, "length %ld not aligned to device width %d\n", len, 1 << reg_width);
+		return -EINVAL;
+	}
+
 	if (block_ts > axi_block_ts)
 		return -EINVAL;
 
@@ -703,7 +809,7 @@ static int dw_axi_dma_set_hw_desc(struct axi_dma_chan *chan,
 	ctlhi = CH_CTL_H_LLI_VALID;
 
 	if (chan->chip->dw->hdata->restrict_axi_burst_len) {
-		burst_len = chan->chip->dw->hdata->axi_rw_burst_len;
+		burst_len = chan->chip->dw->hdata->axi_rw_burst_len[chan->id];
 		ctlhi |= CH_CTL_H_ARLEN_EN | CH_CTL_H_AWLEN_EN |
 			 burst_len << CH_CTL_H_ARLEN_POS |
 			 burst_len << CH_CTL_H_AWLEN_POS;
@@ -720,14 +826,17 @@ static int dw_axi_dma_set_hw_desc(struct axi_dma_chan *chan,
 	}
 
 	hw_desc->lli->block_ts_lo = cpu_to_le32(block_ts - 1);
-
-	ctllo |= DWAXIDMAC_BURST_TRANS_LEN_4 << CH_CTL_L_DST_MSIZE_POS |
-		 DWAXIDMAC_BURST_TRANS_LEN_4 << CH_CTL_L_SRC_MSIZE_POS;
 	hw_desc->lli->ctl_lo = cpu_to_le32(ctllo);
 
 	set_desc_src_master(hw_desc);
 
 	hw_desc->len = len;
+
+	if (burst_len && (chan->config.src_maxburst > burst_len))
+		dev_warn_ratelimited(chan2dev(chan),
+				     "%s: requested source burst length %u exceeds supported burst length %u - data may be lost\n",
+				     axi_chan_name(chan), chan->config.src_maxburst, burst_len);
+
 	return 0;
 }
 
@@ -744,9 +853,6 @@ static size_t calculate_block_len(struct axi_dma_chan *chan,
 	case DMA_MEM_TO_DEV:
 		data_width = BIT(chan->chip->dw->hdata->m_data_width);
 		mem_width = __ffs(data_width | dma_addr | buf_len);
-		if (mem_width > DWAXIDMAC_TRANS_WIDTH_32)
-			mem_width = DWAXIDMAC_TRANS_WIDTH_32;
-
 		block_len = axi_block_ts << mem_width;
 		break;
 	case DMA_DEV_TO_MEM:
@@ -817,6 +923,8 @@ dw_axi_dma_chan_prep_cyclic(struct dma_chan *dchan, dma_addr_t dma_addr,
 		src_addr += segment_len;
 	}
 
+	desc->nr_hw_descs = total_segments;
+
 	llp = desc->hw_desc[0].llp;
 
 	/* Managed transfer list */
@@ -882,6 +990,9 @@ dw_axi_dma_chan_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl,
 		mem = sg_dma_address(sg);
 		len = sg_dma_len(sg);
 		num_segments = DIV_ROUND_UP(sg_dma_len(sg), axi_block_len);
+		if (!num_segments)
+			continue;
+
 		segment_len = DIV_ROUND_UP(sg_dma_len(sg), num_segments);
 
 		do {
@@ -896,6 +1007,8 @@ dw_axi_dma_chan_prep_slave_sg(struct dma_chan *dchan, struct scatterlist *sgl,
 		} while (len >= segment_len);
 	}
 
+	desc->nr_hw_descs = loop;
+
 	/* Set end-of-link to the last link descriptor of list */
 	set_desc_last(&desc->hw_desc[num_sgs - 1]);
 
@@ -974,7 +1087,7 @@ dma_chan_prep_dma_memcpy(struct dma_chan *dchan, dma_addr_t dst_adr,
 
 		reg = CH_CTL_H_LLI_VALID;
 		if (chan->chip->dw->hdata->restrict_axi_burst_len) {
-			u32 burst_len = chan->chip->dw->hdata->axi_rw_burst_len;
+			u32 burst_len = chan->chip->dw->hdata->axi_rw_burst_len[chan->id];
 
 			reg |= (CH_CTL_H_ARLEN_EN |
 				burst_len << CH_CTL_H_ARLEN_POS |
@@ -1003,6 +1116,8 @@ dma_chan_prep_dma_memcpy(struct dma_chan *dchan, dma_addr_t dst_adr,
 		num++;
 	}
 
+	desc->nr_hw_descs = num;
+
 	/* Set end-of-link to the last link descriptor of list */
 	set_desc_last(&desc->hw_desc[num - 1]);
 	/* Managed transfer list */
@@ -1051,7 +1166,7 @@ static void axi_chan_dump_lli(struct axi_dma_chan *chan,
 static void axi_chan_list_dump_lli(struct axi_dma_chan *chan,
 				   struct axi_dma_desc *desc_head)
 {
-	int count = atomic_read(&chan->descs_allocated);
+	int count = desc_head->nr_hw_descs;
 	int i;
 
 	for (i = 0; i < count; i++)
@@ -1094,11 +1209,11 @@ static noinline void axi_chan_handle_err(struct axi_dma_chan *chan, u32 status)
 
 static void axi_chan_block_xfer_complete(struct axi_dma_chan *chan)
 {
-	int count = atomic_read(&chan->descs_allocated);
 	struct axi_dma_hw_desc *hw_desc;
 	struct axi_dma_desc *desc;
 	struct virt_dma_desc *vd;
 	unsigned long flags;
+	int count;
 	u64 llp;
 	int i;
 
@@ -1120,6 +1235,7 @@ static void axi_chan_block_xfer_complete(struct axi_dma_chan *chan)
 	if (chan->cyclic) {
 		desc = vd_to_axi_desc(vd);
 		if (desc) {
+			count = desc->nr_hw_descs;
 			llp = lo_hi_readq(chan->chan_regs + CH_LLP);
 			for (i = 0; i < count; i++) {
 				hw_desc = &desc->hw_desc[i];
@@ -1140,6 +1256,9 @@ static void axi_chan_block_xfer_complete(struct axi_dma_chan *chan)
 		/* Remove the completed descriptor from issued list before completing */
 		list_del(&vd->node);
 		vchan_cookie_complete(vd);
+
+		/* Submit queued descriptors after processing the completed ones */
+		axi_chan_start_first_queued(chan);
 	}
 
 out:
@@ -1357,19 +1476,85 @@ static int __maybe_unused axi_dma_runtime_resume(struct device *dev)
 	return axi_dma_resume(chip);
 }
 
+static void dw_axi_dma_device_caps(struct dma_chan *dchan,
+				   struct dma_slave_caps *caps)
+{
+	struct axi_dma_chan *chan = dchan_to_axi_dma_chan(dchan);
+	struct dw_axi_dma *dw = chan->chip->dw;
+
+	if (dw->hdata->restrict_axi_burst_len)
+		caps->max_burst = dw->hdata->axi_rw_burst_len[chan->id];
+}
+
+static bool dw_axi_dma_filter_fn(struct dma_chan *dchan, void *filter_param)
+{
+	struct axi_dma_chan *chan = dchan_to_axi_dma_chan(dchan);
+	uint32_t selector = *(const uint32_t *)filter_param;
+
+	return !!(selector & (1 << chan->id));
+}
+
 static struct dma_chan *dw_axi_dma_of_xlate(struct of_phandle_args *dma_spec,
 					    struct of_dma *ofdma)
 {
 	struct dw_axi_dma *dw = ofdma->of_dma_data;
 	struct axi_dma_chan *chan;
+	uint32_t chan_flags_all;
+	uint32_t busy_channels;
 	struct dma_chan *dchan;
+	dma_cap_mask_t mask;
+	uint32_t chan_mask;
+	uint32_t chan_sel;
+	int max_score;
+	int score;
+	int i;
 
-	dchan = dma_get_any_slave_channel(&dw->dma);
-	if (!dchan)
-		return NULL;
+	for (i = 0; i < dw->hdata->nr_channels; i++)
+		chan_flags_all |= dw->chan_flags[i];
+
+	dma_cap_zero(mask);
+	dma_cap_set(DMA_SLAVE, mask);
+
+	chan_sel = dma_spec->args[0];
+	busy_channels = 0;
+	dchan = NULL;
+
+	while (1) {
+		max_score = 0;
+		chan_mask = 0;
+
+		for (i = 0; i < dw->hdata->nr_channels; i++) {
+			if (busy_channels & (1 << i))
+				continue;
+			/*
+			 * Positive matches (wanted flags that match) score twice that of
+			 * negetive matches (not wanted flags that are not present).
+			 */
+			score = 2 * hweight32(chan_sel & dw->chan_flags[i]) +
+				1 * hweight32(~chan_sel & ~dw->chan_flags[i] & chan_flags_all);
+			if (score > max_score) {
+				max_score = score;
+				chan_mask = (1 << i);
+			} else if (score == max_score) {
+				chan_mask |= (1 << i);
+			}
+		}
+
+		if (!chan_mask)
+			return NULL;
+
+		dchan = __dma_request_channel(&mask, dw_axi_dma_filter_fn,
+					      &chan_mask, ofdma->of_node);
+		if (dchan)
+			break;
+
+		/* Repeat, after first marking this group of channels as busy */
+		busy_channels |= chan_mask;
+	}
 
 	chan = dchan_to_axi_dma_chan(dchan);
-	chan->hw_handshake_num = dma_spec->args[0];
+	chan->hw_handshake_num = (u8)chan_sel;
+
 	return dchan;
 }
 
@@ -1377,6 +1562,7 @@ static int parse_device_properties(struct axi_dma_chip *chip)
 {
 	struct device *dev = chip->dev;
 	u32 tmp, carr[DMAC_MAX_CHANNELS];
+	u32 val;
 	int ret;
 
 	ret = device_property_read_u32(dev, "dma-channels", &tmp);
@@ -1397,6 +1583,10 @@ static int parse_device_properties(struct axi_dma_chip *chip)
 
 	chip->dw->hdata->nr_masters = tmp;
 
+	ret = device_property_read_u32(dev, "snps,dma-targets", &tmp);
+	if (!ret && tmp > 16)
+		chip->dw->hdata->use_cfg2 = true;
+
 	ret = device_property_read_u32(dev, "snps,data-width", &tmp);
 	if (ret)
 		return ret;
@@ -1429,17 +1619,32 @@ static int parse_device_properties(struct axi_dma_chip *chip)
 	}
 
 	/* axi-max-burst-len is optional property */
-	ret = device_property_read_u32(dev, "snps,axi-max-burst-len", &tmp);
-	if (!ret) {
-		if (tmp > DWAXIDMAC_ARWLEN_MAX + 1)
-			return -EINVAL;
-		if (tmp < DWAXIDMAC_ARWLEN_MIN + 1)
-			return -EINVAL;
-
+	ret = device_property_read_u32_array(dev, "snps,axi-max-burst-len", NULL,
+					     chip->dw->hdata->nr_channels);
+	if ((ret > 0) &&
+	    !device_property_read_u32_array(dev, "snps,axi-max-burst-len",
+					    carr, ret)) {
 		chip->dw->hdata->restrict_axi_burst_len = true;
-		chip->dw->hdata->axi_rw_burst_len = tmp;
+		for (tmp = 0; tmp < chip->dw->hdata->nr_channels; tmp++) {
+			// Replicate the last value to any remaining channels
+			val = carr[min(tmp, (u32)ret - 1)];
+			if (val > DWAXIDMAC_ARWLEN_MAX + 1)
+				return -EINVAL;
+			if (val < DWAXIDMAC_ARWLEN_MIN + 1)
+				return -EINVAL;
+			chip->dw->hdata->axi_rw_burst_len[tmp] = val;
+		}
 	}
 
+	/* snps,chan-flags is optional */
+	memset(chip->dw->chan_flags, 0, sizeof(chip->dw->chan_flags));
+	if (device_property_read_u32_array(dev, "snps,chan-flags",
+				       chip->dw->chan_flags,
+				       chip->dw->hdata->nr_channels) < 0)
+		device_property_read_u32_array(dev, "snps,sel-require",
+					       chip->dw->chan_flags,
+					       chip->dw->hdata->nr_channels);
+
 	return 0;
 }
 
@@ -1467,6 +1672,7 @@ static int dw_probe(struct platform_device *pdev)
 	struct dw_axi_dma *dw;
 	struct dw_axi_dma_hcfg *hdata;
 	struct reset_control *resets;
+	unsigned int max_seg_size;
 	unsigned int flags;
 	u32 i;
 	int ret;
@@ -1550,7 +1756,7 @@ static int dw_probe(struct platform_device *pdev)
 	dma_cap_set(DMA_CYCLIC, dw->dma.cap_mask);
 
 	/* DMA capabilities */
-	dw->dma.max_burst = hdata->axi_rw_burst_len;
+	dw->dma.device_caps = dw_axi_dma_device_caps;
 	dw->dma.src_addr_widths = AXI_DMA_BUSWIDTHS;
 	dw->dma.dst_addr_widths = AXI_DMA_BUSWIDTHS;
 	dw->dma.directions = BIT(DMA_MEM_TO_MEM);
@@ -1577,9 +1783,21 @@ static int dw_probe(struct platform_device *pdev)
 	 * Synopsis DesignWare AxiDMA datasheet mentioned Maximum
 	 * supported blocks is 1024. Device register width is 4 bytes.
 	 * Therefore, set constraint to 1024 * 4.
+	 * However, if all channels specify a greater value, use that instead.
 	 */
+
 	dw->dma.dev->dma_parms = &dw->dma_parms;
-	dma_set_max_seg_size(&pdev->dev, MAX_BLOCK_SIZE);
+	max_seg_size = UINT_MAX;
+	for (i = 0; i < dw->hdata->nr_channels; i++) {
+		unsigned int block_size = chip->dw->hdata->block_size[i];
+
+		if (!block_size)
+			block_size = MAX_BLOCK_SIZE;
+		max_seg_size = min(block_size, max_seg_size);
+	}
+
+	dma_set_max_seg_size(&pdev->dev, max_seg_size);
+
 	platform_set_drvdata(pdev, chip);
 
 	pm_runtime_enable(chip->dev);
diff --git a/drivers/dma/dw-axi-dmac/dw-axi-dmac.h b/drivers/dma/dw-axi-dmac/dw-axi-dmac.h
index b842e6a8d90d..ab868c16d6b5 100644
--- a/drivers/dma/dw-axi-dmac/dw-axi-dmac.h
+++ b/drivers/dma/dw-axi-dmac/dw-axi-dmac.h
@@ -29,7 +29,7 @@ struct dw_axi_dma_hcfg {
 	u32	block_size[DMAC_MAX_CHANNELS];
 	u32	priority[DMAC_MAX_CHANNELS];
 	/* maximum supported axi burst length */
-	u32	axi_rw_burst_len;
+	u32	axi_rw_burst_len[DMAC_MAX_CHANNELS];
 	/* Register map for DMAX_NUM_CHANNELS <= 8 */
 	bool	reg_map_8_channels;
 	bool	restrict_axi_burst_len;
@@ -58,6 +58,7 @@ struct dw_axi_dma {
 	struct dma_device	dma;
 	struct dw_axi_dma_hcfg	*hdata;
 	struct device_dma_parameters	dma_parms;
+	u32	chan_flags[DMAC_MAX_CHANNELS];
 
 	/* channels */
 	struct axi_dma_chan	*chan;
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig
index f7044bf53d1f..4b560c3278ad 100644
--- a/drivers/firmware/Kconfig
+++ b/drivers/firmware/Kconfig
@@ -119,6 +119,15 @@ config RASPBERRYPI_FIRMWARE
 	  This option enables support for communicating with the firmware on the
 	  Raspberry Pi.
 
+config FIRMWARE_RP1
+	tristate "RP1 Firmware Driver"
+	depends on MBOX_RP1
+	help
+	  The Raspberry Pi RP1 processor presents a firmware
+	  interface using shared memory and a mailbox. To enable
+	  the driver that communicates with it, say Y. Otherwise,
+	  say N.
+
 config FW_CFG_SYSFS
 	tristate "QEMU fw_cfg device support in sysfs"
 	depends on SYSFS && (ARM || ARM64 || PARISC || PPC_PMAC || RISCV || SPARC || X86)
diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile
index 7a8d486e718f..d22f83f107de 100644
--- a/drivers/firmware/Makefile
+++ b/drivers/firmware/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_ISCSI_IBFT)	+= iscsi_ibft.o
 obj-$(CONFIG_FIRMWARE_MEMMAP)	+= memmap.o
 obj-$(CONFIG_MTK_ADSP_IPC)	+= mtk-adsp-ipc.o
 obj-$(CONFIG_RASPBERRYPI_FIRMWARE) += raspberrypi.o
+obj-$(CONFIG_FIRMWARE_RP1)	+= rp1-fw.o
 obj-$(CONFIG_FW_CFG_SYSFS)	+= qemu_fw_cfg.o
 obj-$(CONFIG_SYSFB)		+= sysfb.o
 obj-$(CONFIG_SYSFB_SIMPLEFB)	+= sysfb_simplefb.o
diff --git a/drivers/firmware/psci/psci.c b/drivers/firmware/psci/psci.c
index d6701d81cf68..b62a53d6f2b5 100644
--- a/drivers/firmware/psci/psci.c
+++ b/drivers/firmware/psci/psci.c
@@ -315,7 +315,14 @@ static int psci_sys_reset(struct notifier_block *nb, unsigned long action,
 		 * reset_type[30:0] = 0 (SYSTEM_WARM_RESET)
 		 * cookie = 0 (ignored by the implementation)
 		 */
-		invoke_psci_fn(PSCI_FN_NATIVE(1_1, SYSTEM_RESET2), 0, 0, 0);
+		// Allow extra arguments separated by spaces after
+		// the partition number.
+		unsigned long val;
+		u8 partition = 0;
+
+		if (data && sscanf(data, "%lu", &val) == 1 && val < 63)
+			partition = val;
+		invoke_psci_fn(PSCI_FN_NATIVE(1_1, SYSTEM_RESET2), 0, partition, 0);
 	} else {
 		invoke_psci_fn(PSCI_0_2_FN_SYSTEM_RESET, 0, 0, 0);
 	}
diff --git a/drivers/firmware/raspberrypi.c b/drivers/firmware/raspberrypi.c
index 18cc34987108..97349eb790f4 100644
--- a/drivers/firmware/raspberrypi.c
+++ b/drivers/firmware/raspberrypi.c
@@ -14,6 +14,7 @@
 #include <linux/of.h>
 #include <linux/of_platform.h>
 #include <linux/platform_device.h>
+#include <linux/reboot.h>
 #include <linux/slab.h>
 #include <soc/bcm2835/raspberrypi-firmware.h>
 
@@ -34,6 +35,8 @@ struct rpi_firmware {
 	struct kref consumers;
 };
 
+static struct platform_device *g_pdev;
+
 static DEFINE_MUTEX(transaction_lock);
 
 static void response_callback(struct mbox_client *cl, void *msg)
@@ -58,7 +61,7 @@ rpi_firmware_transaction(struct rpi_firmware *fw, u32 chan, u32 data)
 	reinit_completion(&fw->c);
 	ret = mbox_send_message(fw->chan, &message);
 	if (ret >= 0) {
-		if (wait_for_completion_timeout(&fw->c, HZ)) {
+		if (wait_for_completion_timeout(&fw->c, 3*HZ)) {
 			ret = 0;
 		} else {
 			ret = -ETIMEDOUT;
@@ -180,11 +183,61 @@ int rpi_firmware_property(struct rpi_firmware *fw,
 }
 EXPORT_SYMBOL_GPL(rpi_firmware_property);
 
+static int rpi_firmware_notify_reboot(struct notifier_block *nb,
+				      unsigned long action,
+				      void *data)
+{
+	struct rpi_firmware *fw;
+	struct platform_device *pdev = g_pdev;
+	u32 reboot_flags = 0;
+
+	if (!pdev)
+		return 0;
+
+	fw = platform_get_drvdata(pdev);
+	if (!fw)
+		return 0;
+
+	// The partition id is the first parameter followed by zero or
+	// more flags separated by spaces indicating the reason for the reboot.
+	//
+	// 'tryboot': Sets a one-shot flag which is cleared upon reboot and
+	//            causes the tryboot.txt to be loaded instead of config.txt
+	//            by the bootloader and the start.elf firmware.
+	//
+	//            This is intended to allow automatic fallback to a known
+	//            good image if an OS/FW upgrade fails.
+	//
+	// N.B. The firmware mechanism for storing reboot flags may vary
+	// on different Raspberry Pi models.
+	if (data && strstr(data, " tryboot"))
+		reboot_flags |= 0x1;
+
+	// The mailbox might have been called earlier, directly via vcmailbox
+	// so only overwrite if reboot flags are passed to the reboot command.
+	if (reboot_flags)
+		(void)rpi_firmware_property(fw, RPI_FIRMWARE_SET_REBOOT_FLAGS,
+				&reboot_flags, sizeof(reboot_flags));
+
+	(void)rpi_firmware_property(fw, RPI_FIRMWARE_NOTIFY_REBOOT, NULL, 0);
+
+	return 0;
+}
+
 static void
 rpi_firmware_print_firmware_revision(struct rpi_firmware *fw)
 {
 	time64_t date_and_time;
 	u32 packet;
+	static const char * const variant_strs[] = {
+		"unknown",
+		"start",
+		"start_x",
+		"start_db",
+		"start_cd",
+	};
+	const char *variant_str = "cmd unsupported";
+	u32 variant;
 	int ret = rpi_firmware_property(fw,
 					RPI_FIRMWARE_GET_FIRMWARE_REVISION,
 					&packet, sizeof(packet));
@@ -194,7 +247,35 @@ rpi_firmware_print_firmware_revision(struct rpi_firmware *fw)
 
 	/* This is not compatible with y2038 */
 	date_and_time = packet;
-	dev_info(fw->cl.dev, "Attached to firmware from %ptT\n", &date_and_time);
+
+	ret = rpi_firmware_property(fw, RPI_FIRMWARE_GET_FIRMWARE_VARIANT,
+				    &variant, sizeof(variant));
+
+	if (!ret) {
+		if (variant >= ARRAY_SIZE(variant_strs))
+			variant = 0;
+		variant_str = variant_strs[variant];
+	}
+
+	dev_info(fw->cl.dev,
+		 "Attached to firmware from %ptT, variant %s\n",
+		 &date_and_time, variant_str);
+}
+
+static void
+rpi_firmware_print_firmware_hash(struct rpi_firmware *fw)
+{
+	u32 hash[5];
+	int ret = rpi_firmware_property(fw,
+					RPI_FIRMWARE_GET_FIRMWARE_HASH,
+					hash, sizeof(hash));
+
+	if (ret)
+		return;
+
+	dev_info(fw->cl.dev,
+		 "Firmware hash is %08x%08x%08x%08x%08x\n",
+		 hash[0], hash[1], hash[2], hash[3], hash[4]);
 }
 
 static void
@@ -301,8 +382,10 @@ static int rpi_firmware_probe(struct platform_device *pdev)
 	kref_init(&fw->consumers);
 
 	platform_set_drvdata(pdev, fw);
+	g_pdev = pdev;
 
 	rpi_firmware_print_firmware_revision(fw);
+	rpi_firmware_print_firmware_hash(fw);
 	rpi_register_hwmon_driver(dev, fw);
 	rpi_register_clk_driver(dev);
 
@@ -329,6 +412,7 @@ static void rpi_firmware_remove(struct platform_device *pdev)
 	rpi_clk = NULL;
 
 	rpi_firmware_put(fw);
+	g_pdev = NULL;
 }
 
 static const struct of_device_id rpi_firmware_of_match[] = {
@@ -408,7 +492,35 @@ static struct platform_driver rpi_firmware_driver = {
 	.shutdown	= rpi_firmware_shutdown,
 	.remove_new	= rpi_firmware_remove,
 };
-module_platform_driver(rpi_firmware_driver);
+
+static struct notifier_block rpi_firmware_reboot_notifier = {
+	.notifier_call = rpi_firmware_notify_reboot,
+};
+
+static int __init rpi_firmware_init(void)
+{
+	int ret = register_reboot_notifier(&rpi_firmware_reboot_notifier);
+	if (ret)
+		goto out1;
+	ret = platform_driver_register(&rpi_firmware_driver);
+	if (ret)
+		goto out2;
+
+	return 0;
+
+out2:
+	unregister_reboot_notifier(&rpi_firmware_reboot_notifier);
+out1:
+	return ret;
+}
+core_initcall(rpi_firmware_init);
+
+static void __init rpi_firmware_exit(void)
+{
+	platform_driver_unregister(&rpi_firmware_driver);
+	unregister_reboot_notifier(&rpi_firmware_reboot_notifier);
+}
+module_exit(rpi_firmware_exit);
 
 MODULE_AUTHOR("Eric Anholt <eric@anholt.net>");
 MODULE_DESCRIPTION("Raspberry Pi firmware driver");
diff --git a/drivers/firmware/rp1-fw.c b/drivers/firmware/rp1-fw.c
new file mode 100644
index 000000000000..be33ac6ed218
--- /dev/null
+++ b/drivers/firmware/rp1-fw.c
@@ -0,0 +1,308 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *  Copyright (C) 2023-24 Raspberry Pi Ltd.
+ *
+ * Parts of this driver are based on:
+ *  - raspberrypi.c, by Eric Anholt <eric@anholt.net>
+ *    Copyright (C) 2015 Broadcom
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/kref.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/rp1-firmware.h>
+
+#define RP1_MAILBOX_FIRMWARE		0
+
+enum rp1_firmware_ops {
+	MBOX_SUCCESS		= 0x0000,
+	GET_FIRMWARE_VERSION	= 0x0001, // na -> 160-bit version
+	GET_FEATURE		= 0x0002, // FOURCC -> op base (0 == unsupported), op count
+
+	COMMON_COUNT
+};
+
+struct rp1_firmware {
+	struct mbox_client cl;
+	struct mbox_chan *chan;	/* The doorbell channel */
+	uint32_t __iomem *buf;	/* The shared buffer */
+	u32 buf_size;		/* The size of the shared buffer */
+	struct completion c;
+
+	struct kref consumers;
+};
+
+struct rp1_get_feature_resp {
+	uint32_t op_base;
+	uint32_t op_count;
+};
+
+static DEFINE_MUTEX(transaction_lock);
+
+static const struct of_device_id rp1_firmware_of_match[] = {
+	{ .compatible = "raspberrypi,rp1-firmware", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, rp1_firmware_of_match);
+
+static void response_callback(struct mbox_client *cl, void *msg)
+{
+	struct rp1_firmware *fw = container_of(cl, struct rp1_firmware, cl);
+
+	complete(&fw->c);
+}
+
+/*
+ * Sends a request to the RP1 firmware and synchronously waits for the reply.
+ * Returns zero or a positive count of response bytes on success, negative on
+ * error.
+ */
+
+int rp1_firmware_message(struct rp1_firmware *fw, uint16_t op,
+			 const void *data, unsigned int data_len,
+			 void *resp, unsigned int resp_space)
+{
+	int ret;
+	u32 rc;
+
+	if (data_len + 4 > fw->buf_size)
+		return -EINVAL;
+
+	mutex_lock(&transaction_lock);
+
+	memcpy_toio(&fw->buf[1], data, data_len);
+	writel((op << 16) | data_len, fw->buf);
+
+	reinit_completion(&fw->c);
+	ret = mbox_send_message(fw->chan, NULL);
+	if (ret >= 0) {
+		if (wait_for_completion_timeout(&fw->c, HZ))
+			ret = 0;
+		else
+			ret = -ETIMEDOUT;
+	} else {
+		dev_err(fw->cl.dev, "mbox_send_message returned %d\n", ret);
+	}
+
+	if (ret == 0) {
+		rc = readl(fw->buf);
+		if (rc & 0x80000000) {
+			ret = (int32_t)rc;
+		} else {
+			ret = min(rc, resp_space);
+			memcpy_fromio(resp, &fw->buf[1], ret);
+		}
+	}
+
+	mutex_unlock(&transaction_lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(rp1_firmware_message);
+
+static void rp1_firmware_delete(struct kref *kref)
+{
+	struct rp1_firmware *fw = container_of(kref, struct rp1_firmware, consumers);
+
+	mbox_free_channel(fw->chan);
+	kfree(fw);
+}
+
+void rp1_firmware_put(struct rp1_firmware *fw)
+{
+	if (!IS_ERR_OR_NULL(fw))
+		kref_put(&fw->consumers, rp1_firmware_delete);
+}
+EXPORT_SYMBOL_GPL(rp1_firmware_put);
+
+int rp1_firmware_get_feature(struct rp1_firmware *fw, uint32_t fourcc,
+			     uint32_t *op_base, uint32_t *op_count)
+{
+	struct rp1_get_feature_resp resp;
+	int ret;
+
+	memset(&resp, 0, sizeof(resp));
+	ret = rp1_firmware_message(fw, GET_FEATURE,
+				   &fourcc, sizeof(fourcc),
+				   &resp, sizeof(resp));
+	*op_base = resp.op_base;
+	*op_count = resp.op_count;
+	if (ret < 0)
+		return ret;
+	if (ret < sizeof(resp) || !resp.op_base)
+		return -EOPNOTSUPP;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(rp1_firmware_get_feature);
+
+static void devm_rp1_firmware_put(void *data)
+{
+	struct rp1_firmware *fw = data;
+
+	rp1_firmware_put(fw);
+}
+
+/**
+ * rp1_firmware_get - Get pointer to rp1_firmware structure.
+ *
+ * The reference to rp1_firmware has to be released with rp1_firmware_put().
+ *
+ * Returns an error pointer on failure.
+ */
+struct rp1_firmware *rp1_firmware_get(struct device_node *client)
+{
+	const char *match = rp1_firmware_of_match[0].compatible;
+	struct platform_device *pdev;
+	struct device_node *fwnode;
+	struct rp1_firmware *fw = NULL;
+
+	if (!client)
+		return NULL;
+	fwnode = of_parse_phandle(client, "firmware", 0);
+	if (!fwnode)
+		return NULL;
+	if (!of_device_is_compatible(fwnode, match)) {
+		of_node_put(fwnode);
+		return ERR_PTR(-ENXIO);
+	}
+
+	pdev = of_find_device_by_node(fwnode);
+	of_node_put(fwnode);
+
+	if (!pdev)
+		return ERR_PTR(-ENXIO);
+
+	fw = platform_get_drvdata(pdev);
+	if (IS_ERR_OR_NULL(fw))
+		goto err_exit;
+
+	if (!kref_get_unless_zero(&fw->consumers))
+		goto err_exit;
+
+	put_device(&pdev->dev);
+
+	return fw;
+
+err_exit:
+	put_device(&pdev->dev);
+	return fw;
+}
+EXPORT_SYMBOL_GPL(rp1_firmware_get);
+
+/**
+ * devm_rp1_firmware_get - Get pointer to rp1_firmware structure.
+ * @firmware_node:    Pointer to the firmware Device Tree node.
+ *
+ * Returns NULL is the firmware device is not ready.
+ */
+struct rp1_firmware *devm_rp1_firmware_get(struct device *dev, struct device_node *client)
+{
+	struct rp1_firmware *fw;
+	int ret;
+
+	fw = rp1_firmware_get(client);
+	if (IS_ERR_OR_NULL(fw))
+		return fw;
+
+	ret = devm_add_action_or_reset(dev, devm_rp1_firmware_put, fw);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return fw;
+}
+EXPORT_SYMBOL_GPL(devm_rp1_firmware_get);
+
+static int rp1_firmware_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *shmem;
+	struct rp1_firmware *fw;
+	struct resource res;
+	uint32_t version[5];
+	int ret;
+
+	shmem = of_parse_phandle(dev->of_node, "shmem", 0);
+	if (!of_device_is_compatible(shmem, "raspberrypi,rp1-shmem")) {
+		of_node_put(shmem);
+		return -ENXIO;
+	}
+
+	ret = of_address_to_resource(shmem, 0, &res);
+	of_node_put(shmem);
+	if (ret) {
+		dev_err(dev, "failed to get shared memory (%pOF) - %d\n", shmem, ret);
+		return ret;
+	}
+
+	/*
+	 * Memory will be freed by rp1_firmware_delete() once all users have
+	 * released their firmware handles. Don't use devm_kzalloc() here.
+	 */
+	fw = kzalloc(sizeof(*fw), GFP_KERNEL);
+	if (!fw)
+		return -ENOMEM;
+
+	fw->buf_size = resource_size(&res);
+	fw->buf = devm_ioremap(dev, res.start, fw->buf_size);
+	if (!fw->buf) {
+		dev_err(dev, "failed to ioremap shared memory\n");
+		kfree(fw);
+		return -EADDRNOTAVAIL;
+	}
+
+	fw->cl.dev = dev;
+	fw->cl.rx_callback = response_callback;
+	fw->cl.tx_block = false;
+
+	fw->chan = mbox_request_channel(&fw->cl, RP1_MAILBOX_FIRMWARE);
+	if (IS_ERR(fw->chan)) {
+		int ret = PTR_ERR(fw->chan);
+
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "Failed to get mbox channel: %d\n", ret);
+		kfree(fw);
+		return ret;
+	}
+
+	init_completion(&fw->c);
+	kref_init(&fw->consumers);
+
+	ret = rp1_firmware_message(fw, GET_FIRMWARE_VERSION,
+				   NULL, 0, &version, sizeof(version));
+	if (ret == sizeof(version)) {
+		dev_info(dev, "RP1 Firmware version %08x%08x%08x%08x%08x\n",
+			 version[0], version[1], version[2], version[3], version[4]);
+		platform_set_drvdata(pdev, fw);
+	} else {
+		rp1_firmware_put(fw);
+		platform_set_drvdata(pdev, ERR_PTR(-ENOENT));
+	}
+
+	return 0;
+}
+
+static void rp1_firmware_remove(struct platform_device *pdev)
+{
+	struct rp1_firmware *fw = platform_get_drvdata(pdev);
+
+	rp1_firmware_put(fw);
+}
+
+static struct platform_driver rp1_firmware_driver = {
+	.driver = {
+		.name = "rp1-firmware",
+		.of_match_table = rp1_firmware_of_match,
+	},
+	.probe		= rp1_firmware_probe,
+	.remove		= rp1_firmware_remove,
+};
+
+module_platform_driver(rp1_firmware_driver);
+
+MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>");
+MODULE_DESCRIPTION("RP1 firmware driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index d93cd4f722b4..1962af5ef512 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -216,6 +216,12 @@ config GPIO_BCM_XGS_IPROC
 	help
 	  Say yes here to enable GPIO support for Broadcom XGS iProc SoCs.
 
+config GPIO_BCM_VIRT
+	bool "Broadcom Virt GPIO"
+	depends on OF_GPIO && RASPBERRYPI_FIRMWARE && (ARCH_BCM2835 || COMPILE_TEST)
+	help
+	  Turn on virtual GPIO support for Broadcom BCM283X chips.
+
 config GPIO_BRCMSTB
 	tristate "BRCMSTB GPIO support"
 	default y if (ARCH_BRCMSTB || BMIPS_GENERIC)
@@ -549,6 +555,14 @@ config GPIO_PL061
 	help
 	  Say yes here to support the PrimeCell PL061 GPIO device.
 
+config GPIO_PWM
+	tristate "PWM chip GPIO"
+	depends on OF_GPIO
+	depends on PWM
+	help
+	  Turn on support for exposing a PWM chip as a GPIO
+	  driver.
+
 config GPIO_PXA
 	bool "PXA GPIO support"
 	depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST
@@ -1369,6 +1383,15 @@ config GPIO_ELKHARTLAKE
 	  To compile this driver as a module, choose M here: the module will
 	  be called gpio-elkhartlake.
 
+config GPIO_FSM
+	tristate "GPIO FSM support"
+	help
+	  The GPIO FSM driver allows the creation of state machines for
+	  manipulating GPIOs (both real and virtual), with state transitions
+	  triggered by GPIO edges or delays.
+
+	  If unsure, say N.
+
 config GPIO_JANZ_TTL
 	tristate "Janz VMOD-TTL Digital IO Module"
 	depends on MFD_JANZ_CMODIO
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 9fe91a876ec5..8c14a6efc7e0 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -40,6 +40,7 @@ obj-$(CONFIG_GPIO_ASPEED_SGPIO)		+= gpio-aspeed-sgpio.o
 obj-$(CONFIG_GPIO_ATH79)		+= gpio-ath79.o
 obj-$(CONFIG_GPIO_BCM_KONA)		+= gpio-bcm-kona.o
 obj-$(CONFIG_GPIO_BCM_XGS_IPROC)	+= gpio-xgs-iproc.o
+obj-$(CONFIG_GPIO_BCM_VIRT)		+= gpio-bcm-virt.o
 obj-$(CONFIG_GPIO_BD71815)		+= gpio-bd71815.o
 obj-$(CONFIG_GPIO_BD71828)		+= gpio-bd71828.o
 obj-$(CONFIG_GPIO_BD9571MWV)		+= gpio-bd9571mwv.o
@@ -64,6 +65,7 @@ obj-$(CONFIG_GPIO_EN7523)		+= gpio-en7523.o
 obj-$(CONFIG_GPIO_EP93XX)		+= gpio-ep93xx.o
 obj-$(CONFIG_GPIO_EXAR)			+= gpio-exar.o
 obj-$(CONFIG_GPIO_F7188X)		+= gpio-f7188x.o
+obj-$(CONFIG_GPIO_FSM)			+= gpio-fsm.o
 obj-$(CONFIG_GPIO_FTGPIO010)		+= gpio-ftgpio010.o
 obj-$(CONFIG_GPIO_FXL6408)		+= gpio-fxl6408.o
 obj-$(CONFIG_GPIO_GE_FPGA)		+= gpio-ge.o
@@ -134,6 +136,7 @@ obj-$(CONFIG_GPIO_PCI_IDIO_16)		+= gpio-pci-idio-16.o
 obj-$(CONFIG_GPIO_PISOSR)		+= gpio-pisosr.o
 obj-$(CONFIG_GPIO_PL061)		+= gpio-pl061.o
 obj-$(CONFIG_GPIO_PMIC_EIC_SPRD)	+= gpio-pmic-eic-sprd.o
+obj-$(CONFIG_GPIO_PWM)			+= gpio-pwm.o
 obj-$(CONFIG_GPIO_PXA)			+= gpio-pxa.o
 obj-$(CONFIG_GPIO_RASPBERRYPI_EXP)	+= gpio-raspberrypi-exp.o
 obj-$(CONFIG_GPIO_RC5T583)		+= gpio-rc5t583.o
diff --git a/drivers/gpio/gpio-bcm-virt.c b/drivers/gpio/gpio-bcm-virt.c
new file mode 100644
index 000000000000..45069c7f0b5a
--- /dev/null
+++ b/drivers/gpio/gpio-bcm-virt.c
@@ -0,0 +1,212 @@
+/*
+ *  brcmvirt GPIO driver
+ *
+ *  Copyright (C) 2012,2013 Dom Cobley <popcornmix@gmail.com>
+ *  Based on gpio-clps711x.c by Alexander Shiyan <shc_work@mail.ru>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
+#define MODULE_NAME "brcmvirt-gpio"
+#define NUM_GPIO 2
+
+struct brcmvirt_gpio {
+	struct gpio_chip	gc;
+	u32 __iomem		*ts_base;
+	/* two packed 16-bit counts of enabled and disables
+           Allows host to detect a brief enable that was missed */
+	u32			enables_disables[NUM_GPIO];
+	dma_addr_t		bus_addr;
+};
+
+static int brcmvirt_gpio_dir_in(struct gpio_chip *gc, unsigned off)
+{
+	struct brcmvirt_gpio *gpio;
+	gpio = container_of(gc, struct brcmvirt_gpio, gc);
+	return -EINVAL;
+}
+
+static int brcmvirt_gpio_dir_out(struct gpio_chip *gc, unsigned off, int val)
+{
+	struct brcmvirt_gpio *gpio;
+	gpio = container_of(gc, struct brcmvirt_gpio, gc);
+	return 0;
+}
+
+static int brcmvirt_gpio_get(struct gpio_chip *gc, unsigned off)
+{
+	struct brcmvirt_gpio *gpio;
+	unsigned v;
+	gpio = container_of(gc, struct brcmvirt_gpio, gc);
+	v = readl(gpio->ts_base + off);
+	return (s16)((v >> 16) - v) > 0;
+}
+
+static void brcmvirt_gpio_set(struct gpio_chip *gc, unsigned off, int val)
+{
+	struct brcmvirt_gpio *gpio;
+	u16 enables, disables;
+	s16 diff;
+	bool lit;
+	gpio = container_of(gc, struct brcmvirt_gpio, gc);
+	enables  = gpio->enables_disables[off] >> 16;
+	disables = gpio->enables_disables[off] >>  0;
+	diff = (s16)(enables - disables);
+	lit = diff > 0;
+	if ((val && lit) || (!val && !lit))
+		return;
+	if (val)
+		enables++;
+	else
+		disables++;
+	diff = (s16)(enables - disables);
+	BUG_ON(diff != 0 && diff != 1);
+	gpio->enables_disables[off] = (enables << 16) | (disables << 0);
+	writel(gpio->enables_disables[off], gpio->ts_base + off);
+}
+
+static int brcmvirt_gpio_probe(struct platform_device *pdev)
+{
+	int err = 0;
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev_of_node(dev);
+	struct device_node *fw_node;
+	struct rpi_firmware *fw;
+	struct brcmvirt_gpio *ucb;
+	u32 gpiovirtbuf;
+
+	fw_node = of_get_parent(np);
+	if (!fw_node) {
+		dev_err(dev, "Missing firmware node\n");
+		return -ENOENT;
+	}
+
+	fw = devm_rpi_firmware_get(&pdev->dev, fw_node);
+	of_node_put(fw_node);
+	if (!fw)
+		return -EPROBE_DEFER;
+
+	ucb = devm_kzalloc(dev, sizeof *ucb, GFP_KERNEL);
+	if (!ucb) {
+		err = -EINVAL;
+		goto out;
+	}
+
+	ucb->ts_base = dma_alloc_coherent(dev, PAGE_SIZE, &ucb->bus_addr, GFP_KERNEL);
+	if (!ucb->ts_base) {
+		pr_err("[%s]: failed to dma_alloc_coherent(%ld)\n",
+				__func__, PAGE_SIZE);
+		err = -ENOMEM;
+		goto out;
+	}
+
+	gpiovirtbuf = (u32)ucb->bus_addr;
+	err = rpi_firmware_property(fw, RPI_FIRMWARE_FRAMEBUFFER_SET_GPIOVIRTBUF,
+				    &gpiovirtbuf, sizeof(gpiovirtbuf));
+
+	if (err || gpiovirtbuf != 0) {
+		dev_warn(dev, "Failed to set gpiovirtbuf, trying to get err:%x\n", err);
+		dma_free_coherent(dev, PAGE_SIZE, ucb->ts_base, ucb->bus_addr);
+		ucb->ts_base = 0;
+		ucb->bus_addr = 0;
+	}
+
+	if (!ucb->ts_base) {
+		err = rpi_firmware_property(fw, RPI_FIRMWARE_FRAMEBUFFER_GET_GPIOVIRTBUF,
+					    &gpiovirtbuf, sizeof(gpiovirtbuf));
+
+		if (err) {
+			dev_err(dev, "Failed to get gpiovirtbuf\n");
+			goto out;
+		}
+
+		if (!gpiovirtbuf) {
+			dev_err(dev, "No virtgpio buffer\n");
+			err = -ENOENT;
+			goto out;
+		}
+
+		// mmap the physical memory
+		gpiovirtbuf &= ~0xc0000000;
+		ucb->ts_base = ioremap(gpiovirtbuf, 4096);
+		if (ucb->ts_base == NULL) {
+			dev_err(dev, "Failed to map physical address\n");
+			err = -ENOENT;
+			goto out;
+		}
+		ucb->bus_addr = 0;
+	}
+	ucb->gc.parent = dev;
+	ucb->gc.label = MODULE_NAME;
+	ucb->gc.owner = THIS_MODULE;
+	ucb->gc.base = -1;
+	ucb->gc.ngpio = NUM_GPIO;
+
+	ucb->gc.direction_input = brcmvirt_gpio_dir_in;
+	ucb->gc.direction_output = brcmvirt_gpio_dir_out;
+	ucb->gc.get = brcmvirt_gpio_get;
+	ucb->gc.set = brcmvirt_gpio_set;
+	ucb->gc.can_sleep = true;
+
+	err = gpiochip_add_data(&ucb->gc, NULL);
+	if (err)
+		goto out;
+
+	platform_set_drvdata(pdev, ucb);
+
+	return 0;
+out:
+	if (ucb->bus_addr) {
+		dma_free_coherent(dev, PAGE_SIZE, ucb->ts_base, ucb->bus_addr);
+		ucb->bus_addr = 0;
+		ucb->ts_base = NULL;
+	} else if (ucb->ts_base) {
+		iounmap(ucb->ts_base);
+		ucb->ts_base = NULL;
+	}
+	return err;
+}
+
+static void brcmvirt_gpio_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct brcmvirt_gpio *ucb = platform_get_drvdata(pdev);
+
+	gpiochip_remove(&ucb->gc);
+	if (ucb->bus_addr)
+		dma_free_coherent(dev, PAGE_SIZE, ucb->ts_base, ucb->bus_addr);
+	else if (ucb->ts_base)
+		iounmap(ucb->ts_base);
+}
+
+static const struct of_device_id __maybe_unused brcmvirt_gpio_ids[] = {
+	{ .compatible = "brcm,bcm2835-virtgpio" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, brcmvirt_gpio_ids);
+
+static struct platform_driver brcmvirt_gpio_driver = {
+	.driver	= {
+		.name		= MODULE_NAME,
+		.owner		= THIS_MODULE,
+		.of_match_table	= of_match_ptr(brcmvirt_gpio_ids),
+	},
+	.probe	= brcmvirt_gpio_probe,
+	.remove	= brcmvirt_gpio_remove,
+};
+module_platform_driver(brcmvirt_gpio_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Dom Cobley <popcornmix@gmail.com>");
+MODULE_DESCRIPTION("brcmvirt GPIO driver");
+MODULE_ALIAS("platform:brcmvirt-gpio");
diff --git a/drivers/gpio/gpio-brcmstb.c b/drivers/gpio/gpio-brcmstb.c
index 5762e517338e..b48431c2ec01 100644
--- a/drivers/gpio/gpio-brcmstb.c
+++ b/drivers/gpio/gpio-brcmstb.c
@@ -633,6 +633,8 @@ static int brcmstb_gpio_probe(struct platform_device *pdev)
 #if defined(CONFIG_MIPS) && defined(__BIG_ENDIAN)
 	flags = BGPIOF_BIG_ENDIAN_BYTE_ORDER;
 #endif
+	if (of_property_read_bool(np, "brcm,gpio-direct"))
+	    flags |= BGPIOF_REG_DIRECT;
 
 	of_property_for_each_u32(np, "brcm,gpio-bank-widths", bank_width) {
 		struct brcmstb_gpio_bank *bank;
@@ -681,7 +683,9 @@ static int brcmstb_gpio_probe(struct platform_device *pdev)
 		}
 
 		gc->owner = THIS_MODULE;
-		gc->label = devm_kasprintf(dev, GFP_KERNEL, "%pOF", np);
+		gc->label = devm_kasprintf(dev, GFP_KERNEL, "gpio-brcmstb@%zx",
+					   (size_t)res->start +
+					   GIO_BANK_OFF(bank->id, 0));
 		if (!gc->label) {
 			err = -ENOMEM;
 			goto fail;
@@ -689,7 +693,7 @@ static int brcmstb_gpio_probe(struct platform_device *pdev)
 		gc->of_gpio_n_cells = 2;
 		gc->of_xlate = brcmstb_gpio_of_xlate;
 		/* not all ngpio lines are valid, will use bank width later */
-		gc->ngpio = MAX_GPIO_PER_BANK;
+		gc->ngpio = bank_width;
 		gc->offset = bank->id * MAX_GPIO_PER_BANK;
 		gc->request = gpiochip_generic_request;
 		gc->free = gpiochip_generic_free;
@@ -700,8 +704,10 @@ static int brcmstb_gpio_probe(struct platform_device *pdev)
 		 * Mask all interrupts by default, since wakeup interrupts may
 		 * be retained from S5 cold boot
 		 */
-		need_wakeup_event |= !!__brcmstb_gpio_get_active_irqs(bank);
-		gc->write_reg(reg_base + GIO_MASK(bank->id), 0);
+		if (priv->parent_irq > 0) {
+			need_wakeup_event |= !!__brcmstb_gpio_get_active_irqs(bank);
+			gc->write_reg(reg_base + GIO_MASK(bank->id), 0);
+		}
 
 		err = gpiochip_add_data(gc, bank);
 		if (err) {
diff --git a/drivers/gpio/gpio-fsm.c b/drivers/gpio/gpio-fsm.c
new file mode 100644
index 000000000000..785827c70ed7
--- /dev/null
+++ b/drivers/gpio/gpio-fsm.c
@@ -0,0 +1,1210 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  GPIO FSM driver
+ *
+ *  This driver implements simple state machines that allow real GPIOs to be
+ *  controlled in response to inputs from other GPIOs - real and soft/virtual -
+ *  and time delays. It can:
+ *  + create dummy GPIOs for drivers that demand them
+ *  + drive multiple GPIOs from a single input,  with optional delays
+ *  + add a debounce circuit to an input
+ *  + drive pattern sequences onto LEDs
+ *  etc.
+ *
+ *  Copyright (C) 2020 Raspberry Pi (Trading) Ltd.
+ */
+
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/gpio/driver.h>
+#include <linux/interrupt.h>
+#include <linux/kdev_t.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/sysfs.h>
+
+#include <dt-bindings/gpio/gpio-fsm.h>
+
+#define MODULE_NAME "gpio-fsm"
+
+#define GF_IO_TYPE(x) ((u32)(x) & 0xffff)
+#define GF_IO_INDEX(x) ((u32)(x) >> 16)
+
+enum {
+	SIGNAL_GPIO,
+	SIGNAL_SOFT
+};
+
+enum {
+	INPUT_GPIO,
+	INPUT_SOFT
+};
+
+enum {
+	SYM_UNDEFINED,
+	SYM_NAME,
+	SYM_SET,
+	SYM_START,
+	SYM_SHUTDOWN,
+
+	SYM_MAX
+};
+
+struct soft_gpio {
+	int dir;
+	int value;
+};
+
+struct input_gpio_state {
+	struct gpio_fsm *gf;
+	struct gpio_desc  *desc;
+	struct fsm_state *target;
+	int index;
+	int value;
+	int irq;
+	bool enabled;
+	bool active_low;
+};
+
+struct gpio_event {
+	int index;
+	int value;
+	struct fsm_state *target;
+};
+
+struct symtab_entry {
+	const char *name;
+	void *value;
+	struct symtab_entry *next;
+};
+
+struct output_signal {
+	u8 type;
+	u8 value;
+	u16 index;
+};
+
+struct fsm_state {
+	const char *name;
+	struct output_signal *signals;
+	struct gpio_event *gpio_events;
+	struct gpio_event *soft_events;
+	struct fsm_state *delay_target;
+	struct fsm_state *shutdown_target;
+	unsigned int num_signals;
+	unsigned int num_gpio_events;
+	unsigned int num_soft_events;
+	unsigned int delay_ms;
+	unsigned int shutdown_ms;
+};
+
+struct gpio_fsm {
+	struct gpio_chip gc;
+	struct device *dev;
+	spinlock_t spinlock;
+	struct work_struct work;
+	struct timer_list timer;
+	wait_queue_head_t shutdown_event;
+	struct fsm_state *states;
+	struct input_gpio_state *input_gpio_states;
+	struct gpio_descs *input_gpios;
+	struct gpio_descs *output_gpios;
+	struct soft_gpio *soft_gpios;
+	struct fsm_state *start_state;
+	struct fsm_state *shutdown_state;
+	unsigned int num_states;
+	unsigned int num_output_gpios;
+	unsigned int num_input_gpios;
+	unsigned int num_soft_gpios;
+	unsigned int shutdown_timeout_ms;
+	unsigned int shutdown_jiffies;
+
+	struct fsm_state *current_state;
+	struct fsm_state *next_state;
+	struct fsm_state *delay_target_state;
+	unsigned long delay_jiffies;
+	int delay_ms;
+	unsigned int debug;
+	bool shutting_down;
+	struct symtab_entry *symtab;
+};
+
+static struct symtab_entry *do_add_symbol(struct symtab_entry **symtab,
+					  const char *name, void *value)
+{
+	struct symtab_entry **p = symtab;
+
+	while (*p && strcmp((*p)->name, name))
+		p = &(*p)->next;
+
+	if (*p) {
+		/* This is an existing symbol */
+		if ((*p)->value) {
+			/* Already defined */
+			if (value) {
+				if ((uintptr_t)value < SYM_MAX)
+					return ERR_PTR(-EINVAL);
+				else
+					return ERR_PTR(-EEXIST);
+			}
+		} else {
+			/* Undefined */
+			(*p)->value = value;
+		}
+	} else {
+		/* This is a new symbol */
+		*p = kmalloc(sizeof(struct symtab_entry), GFP_KERNEL);
+		if (*p) {
+			(*p)->name = name;
+			(*p)->value = value;
+			(*p)->next = NULL;
+		}
+	}
+	return *p;
+}
+
+static int add_symbol(struct symtab_entry **symtab,
+		      const char *name, void *value)
+{
+	struct symtab_entry *sym = do_add_symbol(symtab, name, value);
+
+	return PTR_ERR_OR_ZERO(sym);
+}
+
+static struct symtab_entry *get_symbol(struct symtab_entry **symtab,
+				       const char *name)
+{
+	struct symtab_entry *sym = do_add_symbol(symtab, name, NULL);
+
+	if (IS_ERR(sym))
+		return NULL;
+	return sym;
+}
+
+static void free_symbols(struct symtab_entry **symtab)
+{
+	struct symtab_entry *sym = *symtab;
+	void *p;
+
+	*symtab = NULL;
+	while (sym) {
+		p = sym;
+		sym = sym->next;
+		kfree(p);
+	}
+}
+
+static void gpio_fsm_set_soft(struct gpio_fsm *gf,
+			      unsigned int off, int val);
+
+static void gpio_fsm_enter_state(struct gpio_fsm *gf,
+				 struct fsm_state *state)
+{
+	struct input_gpio_state *inp_state;
+	struct output_signal *signal;
+	struct gpio_event *event;
+	struct gpio_desc *gpiod;
+	struct soft_gpio *soft;
+	int value;
+	int i;
+
+	dev_dbg(gf->dev, "enter_state(%s)\n", state->name);
+
+	gf->current_state = state;
+	gf->delay_target_state = NULL;
+
+	// 1. Apply any listed signals
+	for (i = 0; i < state->num_signals; i++) {
+		signal = &state->signals[i];
+
+		if (gf->debug)
+			dev_info(gf->dev, "  set %s %d->%d\n",
+				 (signal->type == SIGNAL_GPIO) ? "GF_OUT" :
+				 "GF_SOFT",
+				 signal->index, signal->value);
+		switch (signal->type) {
+		case SIGNAL_GPIO:
+			gpiod = gf->output_gpios->desc[signal->index];
+			gpiod_set_value_cansleep(gpiod, signal->value);
+			break;
+		case SIGNAL_SOFT:
+			soft = &gf->soft_gpios[signal->index];
+			gpio_fsm_set_soft(gf, signal->index, signal->value);
+			break;
+		}
+	}
+
+	// 2. Exit if successfully reached shutdown state
+	if (gf->shutting_down && state == state->shutdown_target) {
+		wake_up(&gf->shutdown_event);
+		return;
+	}
+
+	// 3. Schedule a timer callback if shutting down
+	if (state->shutdown_target) {
+		// Remember the absolute shutdown time in case remove is called
+		// at a later time.
+		gf->shutdown_jiffies =
+			jiffies + msecs_to_jiffies(state->shutdown_ms);
+
+		if (gf->shutting_down) {
+			gf->delay_jiffies = gf->shutdown_jiffies;
+			gf->delay_target_state = state->shutdown_target;
+			gf->delay_ms = state->shutdown_ms;
+			mod_timer(&gf->timer, gf->delay_jiffies);
+		}
+	}
+
+	// During shutdown, skip everything else
+	if (gf->shutting_down)
+		return;
+
+	// Otherwise record what the shutdown time would be
+	gf->shutdown_jiffies = jiffies + msecs_to_jiffies(state->shutdown_ms);
+
+	// 4. Check soft inputs for transitions to take
+	for (i = 0; i < state->num_soft_events; i++) {
+		event = &state->soft_events[i];
+		if (gf->soft_gpios[event->index].value == event->value) {
+			if (gf->debug)
+				dev_info(gf->dev,
+					 "GF_SOFT %d=%d -> %s\n", event->index,
+					 event->value, event->target->name);
+			gpio_fsm_enter_state(gf, event->target);
+			return;
+		}
+	}
+
+	// 5. Check GPIOs for transitions to take, enabling the IRQs
+	for (i = 0; i < state->num_gpio_events; i++) {
+		event = &state->gpio_events[i];
+		inp_state = &gf->input_gpio_states[event->index];
+		inp_state->target = event->target;
+		inp_state->value = event->value;
+		inp_state->enabled = true;
+
+		value = gpiod_get_value_cansleep(gf->input_gpios->desc[event->index]);
+
+		// Clear stale event state
+		disable_irq(inp_state->irq);
+
+		irq_set_irq_type(inp_state->irq,
+				 (inp_state->value ^ inp_state->active_low) ?
+				 IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING);
+		enable_irq(inp_state->irq);
+
+		if (value == event->value && inp_state->target) {
+			if (gf->debug)
+				dev_info(gf->dev,
+					 "GF_IN %d=%d -> %s\n", event->index,
+					 event->value, event->target->name);
+			gpio_fsm_enter_state(gf, event->target);
+			return;
+		}
+	}
+
+	// 6. Schedule a timer callback if delay_target
+	if (state->delay_target) {
+		gf->delay_target_state = state->delay_target;
+		gf->delay_jiffies = jiffies +
+			msecs_to_jiffies(state->delay_ms);
+		gf->delay_ms = state->delay_ms;
+		mod_timer(&gf->timer, gf->delay_jiffies);
+	}
+}
+
+static void gpio_fsm_go_to_state(struct gpio_fsm *gf,
+				 struct fsm_state *new_state)
+{
+	struct input_gpio_state *inp_state;
+	struct gpio_event *gp_ev;
+	struct fsm_state *state;
+	int i;
+
+	dev_dbg(gf->dev, "go_to_state(%s)\n",
+		  new_state ? new_state->name : "<unset>");
+
+	state = gf->current_state;
+
+	/* Disable any enabled GPIO IRQs */
+	for (i = 0; i < state->num_gpio_events; i++) {
+		gp_ev = &state->gpio_events[i];
+		inp_state = &gf->input_gpio_states[gp_ev->index];
+		if (inp_state->enabled) {
+			inp_state->enabled = false;
+			irq_set_irq_type(inp_state->irq,
+					 IRQF_TRIGGER_NONE);
+		}
+	}
+
+	gpio_fsm_enter_state(gf, new_state);
+}
+
+static void gpio_fsm_go_to_state_deferred(struct gpio_fsm *gf,
+					  struct fsm_state *new_state)
+{
+	struct input_gpio_state *inp_state;
+	struct gpio_event *gp_ev;
+	struct fsm_state *state;
+	int i;
+
+	dev_dbg(gf->dev, "go_to_state_deferred(%s)\n",
+		  new_state ? new_state->name : "<unset>");
+
+	spin_lock(&gf->spinlock);
+
+	if (gf->next_state) {
+		/* Something else has already requested a transition */
+		spin_unlock(&gf->spinlock);
+		return;
+	}
+
+	gf->next_state = new_state;
+	state = gf->current_state;
+
+	/* Disarm any GPIO IRQs */
+	for (i = 0; i < state->num_gpio_events; i++) {
+		gp_ev = &state->gpio_events[i];
+		inp_state = &gf->input_gpio_states[gp_ev->index];
+		inp_state->target = NULL;
+	}
+
+	spin_unlock(&gf->spinlock);
+
+	schedule_work(&gf->work);
+}
+
+static void gpio_fsm_work(struct work_struct *work)
+{
+	struct fsm_state *new_state;
+	struct gpio_fsm *gf;
+
+	gf = container_of(work, struct gpio_fsm, work);
+	spin_lock(&gf->spinlock);
+	new_state = gf->next_state;
+	gf->next_state = NULL;
+	spin_unlock(&gf->spinlock);
+
+	gpio_fsm_go_to_state(gf, new_state);
+}
+
+static irqreturn_t gpio_fsm_gpio_irq_handler(int irq, void *dev_id)
+{
+	struct input_gpio_state *inp_state = dev_id;
+	struct gpio_fsm *gf = inp_state->gf;
+	struct fsm_state *target;
+
+	target = inp_state->target;
+	if (!target)
+		return IRQ_NONE;
+
+	/* If the IRQ has fired then the desired state _must_ have occurred */
+	inp_state->enabled = false;
+	irq_set_irq_type(inp_state->irq, IRQF_TRIGGER_NONE);
+	if (gf->debug)
+		dev_info(gf->dev, "GF_IN %d->%d -> %s\n",
+			 inp_state->index, inp_state->value, target->name);
+	gpio_fsm_go_to_state_deferred(gf, target);
+	return IRQ_HANDLED;
+}
+
+static void gpio_fsm_timer(struct timer_list *timer)
+{
+	struct gpio_fsm *gf = container_of(timer, struct gpio_fsm, timer);
+	struct fsm_state *target;
+
+	target = gf->delay_target_state;
+	if (!target)
+		return;
+	if (gf->debug)
+		dev_info(gf->dev, "GF_DELAY %d -> %s\n", gf->delay_ms,
+			 target->name);
+
+	gpio_fsm_go_to_state_deferred(gf, target);
+}
+
+static int gpio_fsm_parse_signals(struct gpio_fsm *gf, struct fsm_state *state,
+				  struct property *prop)
+{
+	const __be32 *cells = prop->value;
+	struct output_signal *signal;
+	u32 io;
+	u32 type;
+	u32 index;
+	u32 value;
+	int ret = 0;
+	int i;
+
+	if (prop->length % 8) {
+		dev_err(gf->dev, "malformed set in state %s\n",
+			state->name);
+		return -EINVAL;
+	}
+
+	state->num_signals = prop->length/8;
+	state->signals = devm_kcalloc(gf->dev, state->num_signals,
+				      sizeof(struct output_signal),
+				      GFP_KERNEL);
+	for (i = 0; i < state->num_signals; i++) {
+		signal = &state->signals[i];
+		io = be32_to_cpu(cells[0]);
+		type = GF_IO_TYPE(io);
+		index = GF_IO_INDEX(io);
+		value = be32_to_cpu(cells[1]);
+
+		if (type != GF_OUT && type != GF_SOFT) {
+			dev_err(gf->dev,
+				"invalid set type %d in state %s\n",
+				type, state->name);
+			ret = -EINVAL;
+			break;
+		}
+		if (type == GF_OUT && index >= gf->num_output_gpios) {
+			dev_err(gf->dev,
+				"invalid GF_OUT number %d in state %s\n",
+				index, state->name);
+			ret = -EINVAL;
+			break;
+		}
+		if (type == GF_SOFT && index >= gf->num_soft_gpios) {
+			dev_err(gf->dev,
+				"invalid GF_SOFT number %d in state %s\n",
+				index, state->name);
+			ret = -EINVAL;
+			break;
+		}
+		if (value != 0 && value != 1) {
+			dev_err(gf->dev,
+				"invalid set value %d in state %s\n",
+				value, state->name);
+			ret = -EINVAL;
+			break;
+		}
+		signal->type = (type == GF_OUT) ? SIGNAL_GPIO : SIGNAL_SOFT;
+		signal->index = index;
+		signal->value = value;
+		cells += 2;
+	}
+
+	return ret;
+}
+
+static struct gpio_event *new_event(struct gpio_event **events, int *num_events)
+{
+	int num = ++(*num_events);
+	*events = krealloc(*events, num * sizeof(struct gpio_event),
+			   GFP_KERNEL);
+	return *events ? *events + (num - 1) : NULL;
+}
+
+static int gpio_fsm_parse_events(struct gpio_fsm *gf, struct fsm_state *state,
+			    struct property *prop)
+{
+	const __be32 *cells = prop->value;
+	struct symtab_entry *sym;
+	int num_cells;
+	int ret = 0;
+	int i;
+
+	if (prop->length % 8) {
+		dev_err(gf->dev,
+			"malformed transitions from state %s to state %s\n",
+			state->name, prop->name);
+		return -EINVAL;
+	}
+
+	sym = get_symbol(&gf->symtab, prop->name);
+	num_cells = prop->length / 4;
+	i = 0;
+	while (i < num_cells) {
+		struct gpio_event *gp_ev;
+		u32 event, param;
+		u32 index;
+
+		event = be32_to_cpu(cells[i++]);
+		param = be32_to_cpu(cells[i++]);
+		index = GF_IO_INDEX(event);
+
+		switch (GF_IO_TYPE(event)) {
+		case GF_IN:
+			if (index >= gf->num_input_gpios) {
+				dev_err(gf->dev,
+					"invalid GF_IN %d in transitions from state %s to state %s\n",
+					index, state->name, prop->name);
+				return -EINVAL;
+			}
+			if (param > 1) {
+				dev_err(gf->dev,
+					"invalid GF_IN value %d in transitions from state %s to state %s\n",
+					param, state->name, prop->name);
+				return -EINVAL;
+			}
+			gp_ev = new_event(&state->gpio_events,
+					  &state->num_gpio_events);
+			if (!gp_ev)
+				return -ENOMEM;
+			gp_ev->index = index;
+			gp_ev->value = param;
+			gp_ev->target = (struct fsm_state *)sym;
+			break;
+
+		case GF_SOFT:
+			if (index >= gf->num_soft_gpios) {
+				dev_err(gf->dev,
+					"invalid GF_SOFT %d in transitions from state %s to state %s\n",
+					index, state->name, prop->name);
+				return -EINVAL;
+			}
+			if (param > 1) {
+				dev_err(gf->dev,
+					"invalid GF_SOFT value %d in transitions from state %s to state %s\n",
+					param, state->name, prop->name);
+				return -EINVAL;
+			}
+			gp_ev = new_event(&state->soft_events,
+					  &state->num_soft_events);
+			if (!gp_ev)
+				return -ENOMEM;
+			gp_ev->index = index;
+			gp_ev->value = param;
+			gp_ev->target = (struct fsm_state *)sym;
+			break;
+
+		case GF_DELAY:
+			if (state->delay_target) {
+				dev_err(gf->dev,
+					"state %s has multiple GF_DELAYs\n",
+					state->name);
+				return -EINVAL;
+			}
+			state->delay_target = (struct fsm_state *)sym;
+			state->delay_ms = param;
+			break;
+
+		case GF_SHUTDOWN:
+			if (state->shutdown_target == state) {
+				dev_err(gf->dev,
+					"shutdown state %s has GF_SHUTDOWN\n",
+					state->name);
+				return -EINVAL;
+			} else if (state->shutdown_target) {
+				dev_err(gf->dev,
+					"state %s has multiple GF_SHUTDOWNs\n",
+					state->name);
+				return -EINVAL;
+			}
+			state->shutdown_target =
+				(struct fsm_state *)sym;
+			state->shutdown_ms = param;
+			break;
+
+		default:
+			dev_err(gf->dev,
+				"invalid event %08x in transitions from state %s to state %s\n",
+				event, state->name, prop->name);
+			return -EINVAL;
+		}
+	}
+	if (i != num_cells) {
+		dev_err(gf->dev,
+			"malformed transitions from state %s to state %s\n",
+			state->name, prop->name);
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static int gpio_fsm_parse_state(struct gpio_fsm *gf,
+			   struct fsm_state *state,
+			   struct device_node *np)
+{
+	struct symtab_entry *sym;
+	struct property *prop;
+	int ret;
+
+	state->name = np->name;
+	ret = add_symbol(&gf->symtab, np->name, state);
+	if (ret) {
+		switch (ret) {
+		case -EINVAL:
+			dev_err(gf->dev, "'%s' is not a valid state name\n",
+				np->name);
+			break;
+		case -EEXIST:
+			dev_err(gf->dev, "state %s already defined\n",
+				np->name);
+			break;
+		default:
+			dev_err(gf->dev, "error %d adding state %s symbol\n",
+				ret, np->name);
+			break;
+		}
+		return ret;
+	}
+
+	for_each_property_of_node(np, prop) {
+		sym = get_symbol(&gf->symtab, prop->name);
+		if (!sym) {
+			ret = -ENOMEM;
+			break;
+		}
+
+		switch ((uintptr_t)sym->value) {
+		case SYM_SET:
+			ret = gpio_fsm_parse_signals(gf, state, prop);
+			break;
+		case SYM_START:
+			if (gf->start_state) {
+				dev_err(gf->dev, "multiple start states\n");
+				ret = -EINVAL;
+			} else {
+				gf->start_state = state;
+			}
+			break;
+		case SYM_SHUTDOWN:
+			state->shutdown_target = state;
+			gf->shutdown_state = state;
+			break;
+		case SYM_NAME:
+			/* Ignore */
+			break;
+		default:
+			/* A set of transition events to this state */
+			ret = gpio_fsm_parse_events(gf, state, prop);
+			break;
+		}
+	}
+
+	return ret;
+}
+
+static void dump_all(struct gpio_fsm *gf)
+{
+	int i, j;
+
+	dev_info(gf->dev, "Input GPIOs:\n");
+	for (i = 0; i < gf->num_input_gpios; i++)
+		dev_info(gf->dev, "  %d: %p\n", i,
+			 gf->input_gpios->desc[i]);
+
+	dev_info(gf->dev, "Output GPIOs:\n");
+	for (i = 0; i < gf->num_output_gpios; i++)
+		dev_info(gf->dev, "  %d: %p\n", i,
+			 gf->output_gpios->desc[i]);
+
+	dev_info(gf->dev, "Soft GPIOs:\n");
+	for (i = 0; i < gf->num_soft_gpios; i++)
+		dev_info(gf->dev, "  %d: %s %d\n", i,
+			 (gf->soft_gpios[i].dir == GPIOF_IN) ? "IN" : "OUT",
+			 gf->soft_gpios[i].value);
+
+	dev_info(gf->dev, "Start state: %s\n",
+		 gf->start_state ? gf->start_state->name : "-");
+
+	dev_info(gf->dev, "Shutdown timeout: %d ms\n",
+		 gf->shutdown_timeout_ms);
+
+	for (i = 0; i < gf->num_states; i++) {
+		struct fsm_state *state = &gf->states[i];
+
+		dev_info(gf->dev, "State %s:\n", state->name);
+
+		if (state->shutdown_target == state)
+			dev_info(gf->dev, "  Shutdown state\n");
+
+		dev_info(gf->dev, "  Signals:\n");
+		for (j = 0; j < state->num_signals; j++) {
+			struct output_signal *signal = &state->signals[j];
+
+			dev_info(gf->dev, "    %d: %s %d=%d\n", j,
+				 (signal->type == SIGNAL_GPIO) ? "GPIO" :
+								 "SOFT",
+				 signal->index, signal->value);
+		}
+
+		dev_info(gf->dev, "  GPIO events:\n");
+		for (j = 0; j < state->num_gpio_events; j++) {
+			struct gpio_event *event = &state->gpio_events[j];
+
+			dev_info(gf->dev, "    %d: %d=%d -> %s\n", j,
+				 event->index, event->value,
+				 event->target->name);
+		}
+
+		dev_info(gf->dev, "  Soft events:\n");
+		for (j = 0; j < state->num_soft_events; j++) {
+			struct gpio_event *event = &state->soft_events[j];
+
+			dev_info(gf->dev, "    %d: %d=%d -> %s\n", j,
+				 event->index, event->value,
+				 event->target->name);
+		}
+
+		if (state->delay_target)
+			dev_info(gf->dev, "  Delay: %d ms -> %s\n",
+				 state->delay_ms, state->delay_target->name);
+
+		if (state->shutdown_target && state->shutdown_target != state)
+			dev_info(gf->dev, "  Shutdown: %d ms -> %s\n",
+				 state->shutdown_ms,
+				 state->shutdown_target->name);
+	}
+	dev_info(gf->dev, "\n");
+}
+
+static int resolve_sym_to_state(struct gpio_fsm *gf, struct fsm_state **pstate)
+{
+	struct symtab_entry *sym = (struct symtab_entry *)*pstate;
+
+	if (!sym)
+		return -ENOMEM;
+
+	*pstate = sym->value;
+
+	if (!*pstate) {
+		dev_err(gf->dev, "state %s not defined\n",
+			sym->name);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void gpio_fsm_set_soft(struct gpio_fsm *gf,
+			      unsigned int off, int val)
+{
+	struct soft_gpio *sg = &gf->soft_gpios[off];
+	struct gpio_event *gp_ev;
+	struct fsm_state *state;
+	int i;
+
+	dev_dbg(gf->dev, "set(%d,%d)\n", off, val);
+	state = gf->current_state;
+	sg->value = val;
+	for (i = 0; i < state->num_soft_events; i++) {
+		gp_ev = &state->soft_events[i];
+		if (gp_ev->index == off && gp_ev->value == val) {
+			if (gf->debug)
+				dev_info(gf->dev,
+					 "GF_SOFT %d->%d -> %s\n", gp_ev->index,
+					 gp_ev->value, gp_ev->target->name);
+			gpio_fsm_go_to_state(gf, gp_ev->target);
+			break;
+		}
+	}
+}
+
+static int gpio_fsm_get(struct gpio_chip *gc, unsigned int off)
+{
+	struct gpio_fsm *gf = gpiochip_get_data(gc);
+	struct soft_gpio *sg;
+
+	if (off >= gf->num_soft_gpios)
+		return -EINVAL;
+	sg = &gf->soft_gpios[off];
+
+	return sg->value;
+}
+
+static void gpio_fsm_set(struct gpio_chip *gc, unsigned int off, int val)
+{
+	struct gpio_fsm *gf;
+
+	gf = gpiochip_get_data(gc);
+	if (off < gf->num_soft_gpios)
+		gpio_fsm_set_soft(gf, off, val);
+}
+
+static int gpio_fsm_get_direction(struct gpio_chip *gc, unsigned int off)
+{
+	struct gpio_fsm *gf = gpiochip_get_data(gc);
+	struct soft_gpio *sg;
+
+	if (off >= gf->num_soft_gpios)
+		return -EINVAL;
+	sg = &gf->soft_gpios[off];
+
+	return sg->dir;
+}
+
+static int gpio_fsm_direction_input(struct gpio_chip *gc, unsigned int off)
+{
+	struct gpio_fsm *gf = gpiochip_get_data(gc);
+	struct soft_gpio *sg;
+
+	if (off >= gf->num_soft_gpios)
+		return -EINVAL;
+	sg = &gf->soft_gpios[off];
+	sg->dir = GPIOF_IN;
+
+	return 0;
+}
+
+static int gpio_fsm_direction_output(struct gpio_chip *gc, unsigned int off,
+				       int value)
+{
+	struct gpio_fsm *gf = gpiochip_get_data(gc);
+	struct soft_gpio *sg;
+
+	if (off >= gf->num_soft_gpios)
+		return -EINVAL;
+	sg = &gf->soft_gpios[off];
+	sg->dir = GPIOF_OUT_INIT_LOW;
+	gpio_fsm_set_soft(gf, off, value);
+
+	return 0;
+}
+
+/*
+ * /sys/class/gpio-fsm/<fsm-name>/
+ *   /state ... the current state
+ */
+
+static ssize_t state_show(struct device *dev,
+			  struct device_attribute *attr, char *buf)
+{
+	const struct gpio_fsm *gf = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%s\n", gf->current_state->name);
+}
+static DEVICE_ATTR_RO(state);
+
+static ssize_t delay_state_show(struct device *dev,
+			  struct device_attribute *attr, char *buf)
+{
+	const struct gpio_fsm *gf = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%s\n",
+		       gf->delay_target_state ? gf->delay_target_state->name :
+		       "-");
+}
+
+static DEVICE_ATTR_RO(delay_state);
+
+static ssize_t delay_ms_show(struct device *dev,
+			     struct device_attribute *attr, char *buf)
+{
+	const struct gpio_fsm *gf = dev_get_drvdata(dev);
+	int jiffies_left;
+
+	jiffies_left = max((int)(gf->delay_jiffies - jiffies), 0);
+	return sprintf(buf,
+		       gf->delay_target_state ? "%u\n" : "-\n",
+		       jiffies_to_msecs(jiffies_left));
+}
+static DEVICE_ATTR_RO(delay_ms);
+
+static struct attribute *gpio_fsm_attrs[] = {
+	&dev_attr_state.attr,
+	&dev_attr_delay_state.attr,
+	&dev_attr_delay_ms.attr,
+	NULL,
+};
+
+static const struct attribute_group gpio_fsm_group = {
+	.attrs = gpio_fsm_attrs,
+	//.is_visible = gpio_is_visible,
+};
+
+static const struct attribute_group *gpio_fsm_groups[] = {
+	&gpio_fsm_group,
+	NULL
+};
+
+static struct attribute *gpio_fsm_class_attrs[] = {
+	// There are no top-level attributes
+	NULL,
+};
+ATTRIBUTE_GROUPS(gpio_fsm_class);
+
+static struct class gpio_fsm_class = {
+	.name =		MODULE_NAME,
+
+	.class_groups = gpio_fsm_class_groups,
+};
+
+static int gpio_fsm_probe(struct platform_device *pdev)
+{
+	struct input_gpio_state *inp_state;
+	struct device *dev = &pdev->dev;
+	struct device *sysfs_dev;
+	struct device_node *np = dev_of_node(dev);
+	struct device_node *cp;
+	struct gpio_fsm *gf;
+	u32 debug = 0;
+	int num_states;
+	u32 num_soft_gpios;
+	int ret;
+	int i;
+	static const char *const reserved_symbols[] = {
+		[SYM_NAME] = "name",
+		[SYM_SET] = "set",
+		[SYM_START] = "start_state",
+		[SYM_SHUTDOWN] = "shutdown_state",
+	};
+
+	if (of_property_read_u32(np, "num-swgpios", &num_soft_gpios) &&
+	    of_property_read_u32(np, "num-soft-gpios", &num_soft_gpios)) {
+		dev_err(dev, "missing 'num-swgpios' property\n");
+		return -EINVAL;
+	}
+
+	of_property_read_u32(np, "debug", &debug);
+
+	gf = devm_kzalloc(dev, sizeof(*gf), GFP_KERNEL);
+	if (!gf)
+		return -ENOMEM;
+
+	gf->dev = dev;
+	gf->debug = debug;
+
+	if (of_property_read_u32(np, "shutdown-timeout-ms",
+				 &gf->shutdown_timeout_ms))
+		gf->shutdown_timeout_ms = 5000;
+
+	gf->num_soft_gpios = num_soft_gpios;
+	gf->soft_gpios = devm_kcalloc(dev, num_soft_gpios,
+				      sizeof(struct soft_gpio), GFP_KERNEL);
+	if (!gf->soft_gpios)
+		return -ENOMEM;
+	for (i = 0; i < num_soft_gpios; i++) {
+		struct soft_gpio *sg = &gf->soft_gpios[i];
+
+		sg->dir = GPIOF_IN;
+		sg->value = 0;
+	}
+
+	gf->input_gpios = devm_gpiod_get_array_optional(dev, "input", GPIOD_IN);
+	if (IS_ERR(gf->input_gpios)) {
+		ret = PTR_ERR(gf->input_gpios);
+		dev_err(dev, "failed to get input gpios from DT - %d\n", ret);
+		return ret;
+	}
+	gf->num_input_gpios = (gf->input_gpios ? gf->input_gpios->ndescs : 0);
+
+	gf->input_gpio_states = devm_kcalloc(dev, gf->num_input_gpios,
+					     sizeof(struct input_gpio_state),
+					     GFP_KERNEL);
+	if (!gf->input_gpio_states)
+		return -ENOMEM;
+	for (i = 0; i < gf->num_input_gpios; i++) {
+		inp_state = &gf->input_gpio_states[i];
+		inp_state->desc = gf->input_gpios->desc[i];
+		inp_state->gf = gf;
+		inp_state->index = i;
+		inp_state->irq = gpiod_to_irq(inp_state->desc);
+		inp_state->active_low = gpiod_is_active_low(inp_state->desc);
+		if (inp_state->irq >= 0)
+			ret = devm_request_irq(gf->dev, inp_state->irq,
+					       gpio_fsm_gpio_irq_handler,
+					       IRQF_TRIGGER_NONE,
+					       dev_name(dev),
+					       inp_state);
+		else
+			ret = inp_state->irq;
+
+		if (ret) {
+			dev_err(dev,
+				"failed to get IRQ for input gpio - %d\n",
+				ret);
+			return ret;
+		}
+	}
+
+	gf->output_gpios = devm_gpiod_get_array_optional(dev, "output",
+							 GPIOD_OUT_LOW);
+	if (IS_ERR(gf->output_gpios)) {
+		ret = PTR_ERR(gf->output_gpios);
+		dev_err(dev, "failed to get output gpios from DT - %d\n", ret);
+		return ret;
+	}
+	gf->num_output_gpios = (gf->output_gpios ? gf->output_gpios->ndescs :
+				0);
+
+	num_states = of_get_child_count(np);
+	if (!num_states) {
+		dev_err(dev, "no states declared\n");
+		return -EINVAL;
+	}
+	gf->states = devm_kcalloc(dev, num_states,
+				  sizeof(struct fsm_state), GFP_KERNEL);
+	if (!gf->states)
+		return -ENOMEM;
+
+	// add reserved words to the symbol table
+	for (i = 0; i < ARRAY_SIZE(reserved_symbols); i++) {
+		if (reserved_symbols[i])
+			add_symbol(&gf->symtab, reserved_symbols[i],
+				   (void *)(uintptr_t)i);
+	}
+
+	// parse the state
+	for_each_child_of_node(np, cp) {
+		struct fsm_state *state = &gf->states[gf->num_states];
+
+		ret = gpio_fsm_parse_state(gf, state, cp);
+		if (ret)
+			return ret;
+		gf->num_states++;
+	}
+
+	if (!gf->start_state) {
+		dev_err(gf->dev, "no start state defined\n");
+		return -EINVAL;
+	}
+
+	// resolve symbol pointers into state pointers
+	for (i = 0; !ret && i < gf->num_states; i++) {
+		struct fsm_state *state = &gf->states[i];
+		int j;
+
+		for (j = 0; !ret && j < state->num_gpio_events; j++) {
+			struct gpio_event *ev = &state->gpio_events[j];
+
+			ret = resolve_sym_to_state(gf, &ev->target);
+		}
+
+		for (j = 0; !ret && j < state->num_soft_events; j++) {
+			struct gpio_event *ev = &state->soft_events[j];
+
+			ret = resolve_sym_to_state(gf, &ev->target);
+		}
+
+		if (!ret) {
+			resolve_sym_to_state(gf, &state->delay_target);
+			if (state->shutdown_target != state)
+				resolve_sym_to_state(gf,
+						     &state->shutdown_target);
+		}
+	}
+
+	if (!ret && gf->debug > 1)
+		dump_all(gf);
+
+	free_symbols(&gf->symtab);
+
+	if (ret)
+		return ret;
+
+	gf->gc.parent = dev;
+	gf->gc.label = np->name;
+	gf->gc.owner = THIS_MODULE;
+	gf->gc.base = -1;
+	gf->gc.ngpio = num_soft_gpios;
+
+	gf->gc.get_direction = gpio_fsm_get_direction;
+	gf->gc.direction_input = gpio_fsm_direction_input;
+	gf->gc.direction_output = gpio_fsm_direction_output;
+	gf->gc.get = gpio_fsm_get;
+	gf->gc.set = gpio_fsm_set;
+	gf->gc.can_sleep = true;
+	spin_lock_init(&gf->spinlock);
+	INIT_WORK(&gf->work, gpio_fsm_work);
+	timer_setup(&gf->timer, gpio_fsm_timer, 0);
+	init_waitqueue_head(&gf->shutdown_event);
+
+	platform_set_drvdata(pdev, gf);
+
+	sysfs_dev = device_create_with_groups(&gpio_fsm_class, dev,
+					      MKDEV(0, 0), gf,
+					      gpio_fsm_groups,
+					      "%s", np->name);
+	if (IS_ERR(sysfs_dev))
+		dev_err(gf->dev, "Error creating sysfs entry\n");
+
+	if (gf->debug)
+		dev_info(gf->dev, "Start -> %s\n", gf->start_state->name);
+
+	gpio_fsm_enter_state(gf, gf->start_state);
+
+	return devm_gpiochip_add_data(dev, &gf->gc, gf);
+}
+
+static void gpio_fsm_remove(struct platform_device *pdev)
+{
+	struct gpio_fsm *gf = platform_get_drvdata(pdev);
+	int i;
+
+	if (gf->shutdown_state) {
+		if (gf->debug)
+			dev_info(gf->dev, "Shutting down...\n");
+
+		spin_lock(&gf->spinlock);
+		gf->shutting_down = true;
+		if (gf->current_state->shutdown_target &&
+		    gf->current_state->shutdown_target != gf->current_state) {
+			gf->delay_target_state =
+				gf->current_state->shutdown_target;
+			mod_timer(&gf->timer, gf->shutdown_jiffies);
+		}
+		spin_unlock(&gf->spinlock);
+
+		wait_event_timeout(gf->shutdown_event,
+				   gf->current_state->shutdown_target ==
+				   gf->current_state,
+				   msecs_to_jiffies(gf->shutdown_timeout_ms));
+		/* On failure to reach a shutdown state, jump to one */
+		if (gf->current_state->shutdown_target != gf->current_state)
+			gpio_fsm_enter_state(gf, gf->shutdown_state);
+	}
+	cancel_work_sync(&gf->work);
+	del_timer_sync(&gf->timer);
+
+	/* Events aren't allocated from managed storage */
+	for (i = 0; i < gf->num_states; i++) {
+		kfree(gf->states[i].gpio_events);
+		kfree(gf->states[i].soft_events);
+	}
+	if (gf->debug)
+		dev_info(gf->dev, "Exiting\n");
+}
+
+static void gpio_fsm_shutdown(struct platform_device *pdev)
+{
+	gpio_fsm_remove(pdev);
+}
+
+static const struct of_device_id gpio_fsm_ids[] = {
+	{ .compatible = "rpi,gpio-fsm" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, gpio_fsm_ids);
+
+static struct platform_driver gpio_fsm_driver = {
+	.driver	= {
+		.name		= MODULE_NAME,
+		.of_match_table	= of_match_ptr(gpio_fsm_ids),
+	},
+	.probe = gpio_fsm_probe,
+	.remove = gpio_fsm_remove,
+	.shutdown = gpio_fsm_shutdown,
+};
+
+static int gpio_fsm_init(void)
+{
+	int ret;
+
+	ret = class_register(&gpio_fsm_class);
+	if (ret)
+		return ret;
+
+	ret = platform_driver_register(&gpio_fsm_driver);
+	if (ret)
+		class_unregister(&gpio_fsm_class);
+
+	return ret;
+}
+module_init(gpio_fsm_init);
+
+static void gpio_fsm_exit(void)
+{
+	platform_driver_unregister(&gpio_fsm_driver);
+	class_unregister(&gpio_fsm_class);
+}
+module_exit(gpio_fsm_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>");
+MODULE_DESCRIPTION("GPIO FSM driver");
+MODULE_ALIAS("platform:gpio-fsm");
diff --git a/drivers/gpio/gpio-mmio.c b/drivers/gpio/gpio-mmio.c
index d89e78f0ead3..36bb04afa563 100644
--- a/drivers/gpio/gpio-mmio.c
+++ b/drivers/gpio/gpio-mmio.c
@@ -231,6 +231,25 @@ static void bgpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
 	raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
 }
 
+static void bgpio_set_direct(struct gpio_chip *gc, unsigned int gpio, int val)
+{
+	unsigned long mask = bgpio_line2mask(gc, gpio);
+	unsigned long flags;
+
+	raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+
+	gc->bgpio_data = gc->read_reg(gc->reg_dat);
+
+	if (val)
+		gc->bgpio_data |= mask;
+	else
+		gc->bgpio_data &= ~mask;
+
+	gc->write_reg(gc->reg_dat, gc->bgpio_data);
+
+	raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+}
+
 static void bgpio_set_with_clear(struct gpio_chip *gc, unsigned int gpio,
 				 int val)
 {
@@ -323,6 +342,27 @@ static void bgpio_set_multiple_with_clear(struct gpio_chip *gc,
 		gc->write_reg(gc->reg_clr, clear_mask);
 }
 
+static void bgpio_set_multiple_direct(struct gpio_chip *gc,
+				      unsigned long *mask,
+				      unsigned long *bits)
+{
+	unsigned long flags;
+	unsigned long set_mask, clear_mask;
+
+	raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+
+	bgpio_multiple_get_masks(gc, mask, bits, &set_mask, &clear_mask);
+
+	gc->bgpio_data = gc->read_reg(gc->reg_dat);
+
+	gc->bgpio_data |= set_mask;
+	gc->bgpio_data &= ~clear_mask;
+
+	gc->write_reg(gc->reg_dat, gc->bgpio_data);
+
+	raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+}
+
 static int bgpio_simple_dir_in(struct gpio_chip *gc, unsigned int gpio)
 {
 	return 0;
@@ -360,6 +400,29 @@ static int bgpio_dir_in(struct gpio_chip *gc, unsigned int gpio)
 	return 0;
 }
 
+static int bgpio_dir_in_direct(struct gpio_chip *gc, unsigned int gpio)
+{
+	unsigned long flags;
+
+	raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+
+	if (gc->reg_dir_in)
+		gc->bgpio_dir = ~gc->read_reg(gc->reg_dir_in);
+	if (gc->reg_dir_out)
+		gc->bgpio_dir = gc->read_reg(gc->reg_dir_out);
+
+	gc->bgpio_dir &= ~bgpio_line2mask(gc, gpio);
+
+	if (gc->reg_dir_in)
+		gc->write_reg(gc->reg_dir_in, ~gc->bgpio_dir);
+	if (gc->reg_dir_out)
+		gc->write_reg(gc->reg_dir_out, gc->bgpio_dir);
+
+	raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+
+	return 0;
+}
+
 static int bgpio_get_dir(struct gpio_chip *gc, unsigned int gpio)
 {
 	/* Return 0 if output, 1 if input */
@@ -398,6 +461,28 @@ static void bgpio_dir_out(struct gpio_chip *gc, unsigned int gpio, int val)
 	raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
 }
 
+static void bgpio_dir_out_direct(struct gpio_chip *gc, unsigned int gpio,
+				 int val)
+{
+	unsigned long flags;
+
+	raw_spin_lock_irqsave(&gc->bgpio_lock, flags);
+
+	if (gc->reg_dir_in)
+		gc->bgpio_dir = ~gc->read_reg(gc->reg_dir_in);
+	if (gc->reg_dir_out)
+		gc->bgpio_dir = gc->read_reg(gc->reg_dir_out);
+
+	gc->bgpio_dir |= bgpio_line2mask(gc, gpio);
+
+	if (gc->reg_dir_in)
+		gc->write_reg(gc->reg_dir_in, ~gc->bgpio_dir);
+	if (gc->reg_dir_out)
+		gc->write_reg(gc->reg_dir_out, gc->bgpio_dir);
+
+	raw_spin_unlock_irqrestore(&gc->bgpio_lock, flags);
+}
+
 static int bgpio_dir_out_dir_first(struct gpio_chip *gc, unsigned int gpio,
 				   int val)
 {
@@ -414,6 +499,22 @@ static int bgpio_dir_out_val_first(struct gpio_chip *gc, unsigned int gpio,
 	return 0;
 }
 
+static int bgpio_dir_out_dir_first_direct(struct gpio_chip *gc,
+					  unsigned int gpio, int val)
+{
+	bgpio_dir_out_direct(gc, gpio, val);
+	gc->set(gc, gpio, val);
+	return 0;
+}
+
+static int bgpio_dir_out_val_first_direct(struct gpio_chip *gc,
+					  unsigned int gpio, int val)
+{
+	gc->set(gc, gpio, val);
+	bgpio_dir_out_direct(gc, gpio, val);
+	return 0;
+}
+
 static int bgpio_setup_accessors(struct device *dev,
 				 struct gpio_chip *gc,
 				 bool byte_be)
@@ -507,6 +608,9 @@ static int bgpio_setup_io(struct gpio_chip *gc,
 	} else if (flags & BGPIOF_NO_OUTPUT) {
 		gc->set = bgpio_set_none;
 		gc->set_multiple = NULL;
+	} else if (flags & BGPIOF_REG_DIRECT) {
+		gc->set = bgpio_set_direct;
+		gc->set_multiple = bgpio_set_multiple_direct;
 	} else {
 		gc->set = bgpio_set;
 		gc->set_multiple = bgpio_set_multiple;
@@ -543,11 +647,21 @@ static int bgpio_setup_direction(struct gpio_chip *gc,
 	if (dirout || dirin) {
 		gc->reg_dir_out = dirout;
 		gc->reg_dir_in = dirin;
-		if (flags & BGPIOF_NO_SET_ON_INPUT)
-			gc->direction_output = bgpio_dir_out_dir_first;
-		else
-			gc->direction_output = bgpio_dir_out_val_first;
-		gc->direction_input = bgpio_dir_in;
+		if (flags & BGPIOF_REG_DIRECT) {
+			if (flags & BGPIOF_NO_SET_ON_INPUT)
+				gc->direction_output =
+					bgpio_dir_out_dir_first_direct;
+			else
+				gc->direction_output =
+					bgpio_dir_out_val_first_direct;
+			gc->direction_input = bgpio_dir_in_direct;
+		} else {
+			if (flags & BGPIOF_NO_SET_ON_INPUT)
+				gc->direction_output = bgpio_dir_out_dir_first;
+			else
+				gc->direction_output = bgpio_dir_out_val_first;
+			gc->direction_input = bgpio_dir_in;
+		}
 		gc->get_direction = bgpio_get_dir;
 	} else {
 		if (flags & BGPIOF_NO_OUTPUT)
diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c
index 489c7ebe87c8..722b1fc47c95 100644
--- a/drivers/gpio/gpio-pca953x.c
+++ b/drivers/gpio/gpio-pca953x.c
@@ -1353,6 +1353,7 @@ static const struct of_device_id pca953x_dt_ids[] = {
 	{ .compatible = "ti,tca9535", .data = OF_953X(16, PCA_INT), },
 	{ .compatible = "ti,tca9538", .data = OF_953X( 8, PCA_INT), },
 	{ .compatible = "ti,tca9539", .data = OF_953X(16, PCA_INT), },
+	{ .compatible = "ti,tca9554", .data = OF_953X( 8, PCA_INT), },
 
 	{ .compatible = "onnn,cat9554", .data = OF_953X( 8, PCA_INT), },
 	{ .compatible = "onnn,pca9654", .data = OF_953X( 8, PCA_INT), },
diff --git a/drivers/gpio/gpio-pwm.c b/drivers/gpio/gpio-pwm.c
new file mode 100644
index 000000000000..01a2d404d4a3
--- /dev/null
+++ b/drivers/gpio/gpio-pwm.c
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * GPIO driver wrapping PWM API
+ *
+ * PWM 0% and PWM 100% are equivalent to digital GPIO
+ * outputs, and there are times where it is useful to use
+ * PWM outputs as straight GPIOs (eg outputs of NXP PCA9685
+ * I2C PWM chip). This driver wraps the PWM API as a GPIO
+ * controller.
+ *
+ * Copyright (C) 2021 Raspberry Pi (Trading) Ltd.
+ */
+
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+
+struct pwm_gpio {
+	struct gpio_chip gc;
+	struct pwm_device **pwm;
+};
+
+static int pwm_gpio_get_direction(struct gpio_chip *gc, unsigned int off)
+{
+	return GPIO_LINE_DIRECTION_OUT;
+}
+
+static void pwm_gpio_set(struct gpio_chip *gc, unsigned int off, int val)
+{
+	struct pwm_gpio *pwm_gpio = gpiochip_get_data(gc);
+	struct pwm_state state;
+
+	pwm_get_state(pwm_gpio->pwm[off], &state);
+	state.duty_cycle = val ? state.period : 0;
+	pwm_apply_might_sleep(pwm_gpio->pwm[off], &state);
+}
+
+static int pwm_gpio_parse_dt(struct pwm_gpio *pwm_gpio,
+			     struct device *dev)
+{
+	struct device_node *node = dev->of_node;
+	struct pwm_state state;
+	int ret = 0, i, num_gpios;
+	const char *pwm_name;
+
+	if (!node)
+		return -ENODEV;
+
+	num_gpios = of_property_count_strings(node, "pwm-names");
+	if (num_gpios <= 0)
+		return 0;
+
+	pwm_gpio->pwm = devm_kzalloc(dev,
+				     sizeof(*pwm_gpio->pwm) * num_gpios,
+				     GFP_KERNEL);
+	if (!pwm_gpio->pwm)
+		return -ENOMEM;
+
+	for (i = 0; i < num_gpios; i++) {
+		ret = of_property_read_string_index(node, "pwm-names", i,
+						    &pwm_name);
+		if (ret) {
+			dev_err(dev, "unable to get pwm device index %d, name %s",
+				i, pwm_name);
+			goto error;
+		}
+
+		pwm_gpio->pwm[i] = devm_pwm_get(dev, pwm_name);
+		if (IS_ERR(pwm_gpio->pwm[i])) {
+			ret = PTR_ERR(pwm_gpio->pwm[i]);
+			if (ret != -EPROBE_DEFER)
+				dev_err(dev, "unable to request PWM\n");
+			goto error;
+		}
+
+		/* Sync up PWM state. */
+		pwm_init_state(pwm_gpio->pwm[i], &state);
+
+		state.duty_cycle = 0;
+		pwm_apply_might_sleep(pwm_gpio->pwm[i], &state);
+	}
+
+	pwm_gpio->gc.ngpio = num_gpios;
+
+error:
+	return ret;
+}
+
+static int pwm_gpio_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct pwm_gpio *pwm_gpio;
+	int ret;
+
+	pwm_gpio = devm_kzalloc(dev, sizeof(*pwm_gpio), GFP_KERNEL);
+	if (!pwm_gpio)
+		return -ENOMEM;
+
+	pwm_gpio->gc.parent = dev;
+	pwm_gpio->gc.label = "pwm-gpio";
+	pwm_gpio->gc.owner = THIS_MODULE;
+	pwm_gpio->gc.fwnode = dev->fwnode;
+	pwm_gpio->gc.base = -1;
+
+	pwm_gpio->gc.get_direction = pwm_gpio_get_direction;
+	pwm_gpio->gc.set = pwm_gpio_set;
+	pwm_gpio->gc.can_sleep = true;
+
+	ret = pwm_gpio_parse_dt(pwm_gpio, dev);
+	if (ret)
+		return ret;
+
+	if (!pwm_gpio->gc.ngpio)
+		return 0;
+
+	return devm_gpiochip_add_data(dev, &pwm_gpio->gc, pwm_gpio);
+}
+
+static void pwm_gpio_remove(struct platform_device *pdev)
+{
+}
+
+static const struct of_device_id pwm_gpio_of_match[] = {
+	{ .compatible = "pwm-gpio" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, pwm_gpio_of_match);
+
+static struct platform_driver pwm_gpio_driver = {
+	.driver	= {
+		.name		= "pwm-gpio",
+		.of_match_table	= of_match_ptr(pwm_gpio_of_match),
+	},
+	.probe	= pwm_gpio_probe,
+	.remove	= pwm_gpio_remove,
+};
+module_platform_driver(pwm_gpio_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>");
+MODULE_DESCRIPTION("PWM GPIO driver");
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 967ff661e4c9..ceaf91a6a600 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -51,6 +51,44 @@
  * GPIOs can sometimes cost only an instruction or two per bit.
  */
 
+#define dont_test_bit(b,d) (0)
+
+#define REJECT_HOTPLUG_TAX 1
+
+#if REJECT_HOTPLUG_TAX
+
+#undef srcu_dereference
+#define srcu_dereference(obj, srcu) obj
+
+#undef srcu_dereference_check
+#define srcu_dereference_check(obj, srcu, held) obj
+
+#undef guard
+#define guard(srcu) (void)
+
+#undef scoped_guard
+#define scoped_guard(x, y)
+#undef list_for_each_entry_srcu
+#define list_for_each_entry_srcu(pos, head, member, srcu) \
+	list_for_each_entry(pos, head, member)
+
+#undef CLASS
+#define CLASS(gcg, gd) \
+	struct gcg gd = gcg ## _init
+
+static struct gpio_chip_guard gpio_chip_guard_init(struct gpio_desc *desc)
+{
+	struct gpio_chip_guard _guard;
+
+	_guard.gdev = desc->gdev;
+	_guard.idx = 0;
+	_guard.gc = _guard.gdev->chip;
+
+	return _guard;
+}
+
+#endif
+
 /* Device and char device-related information */
 static DEFINE_IDA(gpio_ida);
 static dev_t gpio_devt;
@@ -103,6 +141,7 @@ static int gpiochip_irqchip_init_valid_mask(struct gpio_chip *gc);
 static void gpiochip_irqchip_free_valid_mask(struct gpio_chip *gc);
 
 static bool gpiolib_initialized;
+static int first_dynamic_gpiochip_num = -1;
 
 const char *gpiod_get_label(struct gpio_desc *desc)
 {
@@ -925,6 +964,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
 	unsigned int desc_index;
 	int base = 0;
 	int ret = 0;
+	int id;
 
 	/*
 	 * First: allocate and populate the internal stat container, and
@@ -951,7 +991,16 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
 	else if (gc->parent)
 		device_set_node(&gdev->dev, dev_fwnode(gc->parent));
 
-	gdev->id = ida_alloc(&gpio_ida, GFP_KERNEL);
+	if (first_dynamic_gpiochip_num < 0) {
+		id = of_alias_get_highest_id("gpiochip");
+		first_dynamic_gpiochip_num = (id >= 0) ? (id + 1) : 0;
+	}
+
+	id = of_alias_get_id(gdev->dev.of_node, "gpiochip");
+	if (id < 0)
+		id = first_dynamic_gpiochip_num;
+
+	gdev->id = ida_alloc_range(&gpio_ida, id, ~0, GFP_KERNEL);
 	if (gdev->id < 0) {
 		ret = gdev->id;
 		goto err_free_gdev;
@@ -2814,8 +2863,8 @@ int gpiod_direction_output(struct gpio_desc *desc, int value)
 		value = !!value;
 
 	/* GPIOs used for enabled IRQs shall not be set as output */
-	if (test_bit(FLAG_USED_AS_IRQ, &flags) &&
-	    test_bit(FLAG_IRQ_IS_ENABLED, &flags)) {
+	if (dont_test_bit(FLAG_USED_AS_IRQ, &flags) &&
+	    dont_test_bit(FLAG_IRQ_IS_ENABLED, &flags)) {
 		gpiod_err(desc,
 			  "%s: tried to set a GPIO tied to an IRQ as output\n",
 			  __func__);
@@ -3766,8 +3815,8 @@ int gpiochip_lock_as_irq(struct gpio_chip *gc, unsigned int offset)
 	}
 
 	/* To be valid for IRQ the line needs to be input or open drain */
-	if (test_bit(FLAG_IS_OUT, &desc->flags) &&
-	    !test_bit(FLAG_OPEN_DRAIN, &desc->flags)) {
+	if (dont_test_bit(FLAG_IS_OUT, &desc->flags) &&
+	    !dont_test_bit(FLAG_OPEN_DRAIN, &desc->flags)) {
 		chip_err(gc,
 			 "%s: tried to flag a GPIO set as output for IRQ\n",
 			 __func__);
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 0dd0d996e53e..d7a3fc0e8ee0 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -441,6 +441,8 @@ source "drivers/gpu/drm/v3d/Kconfig"
 
 source "drivers/gpu/drm/vc4/Kconfig"
 
+source "drivers/gpu/drm/rp1/Kconfig"
+
 source "drivers/gpu/drm/loongson/Kconfig"
 
 source "drivers/gpu/drm/etnaviv/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index f4a5edf746d2..07e9e07ad01c 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -225,3 +225,4 @@ obj-y			+= solomon/
 obj-$(CONFIG_DRM_SPRD) += sprd/
 obj-$(CONFIG_DRM_LOONGSON) += loongson/
 obj-$(CONFIG_DRM_POWERVR) += imagination/
+obj-y			+= rp1/
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index 66369b6a023f..b5dc6d086acd 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -73,6 +73,7 @@ config DRM_CROS_EC_ANX7688
 config DRM_DISPLAY_CONNECTOR
 	tristate "Display connector support"
 	depends on OF
+	select DRM_KMS_HELPER
 	help
 	  Driver for display connectors with support for DDC and hot-plug
 	  detection. Most display controllers handle display connectors
diff --git a/drivers/gpu/drm/bridge/panel.c b/drivers/gpu/drm/bridge/panel.c
index 6e88339dec0f..718c86c47615 100644
--- a/drivers/gpu/drm/bridge/panel.c
+++ b/drivers/gpu/drm/bridge/panel.c
@@ -16,6 +16,7 @@
 #include <drm/drm_panel.h>
 #include <drm/drm_print.h>
 #include <drm/drm_probe_helper.h>
+#include <linux/backlight.h>
 
 struct panel_bridge {
 	struct drm_bridge bridge;
@@ -78,11 +79,18 @@ static int panel_bridge_attach(struct drm_bridge *bridge,
 		return ret;
 	}
 
+	connector->interlace_allowed = true;
+
 	drm_panel_bridge_set_orientation(connector, bridge);
 
 	drm_connector_attach_encoder(&panel_bridge->connector,
 					  bridge->encoder);
 
+#if IS_REACHABLE(CONFIG_BACKLIGHT_CLASS_DEVICE)
+	backlight_set_display_name(panel_bridge->panel->backlight,
+				   panel_bridge->connector.name);
+#endif
+
 	if (bridge->dev->registered) {
 		if (connector->funcs->reset)
 			connector->funcs->reset(connector);
diff --git a/drivers/gpu/drm/bridge/tc358762.c b/drivers/gpu/drm/bridge/tc358762.c
index 46198af9eebb..3f6db0f66812 100644
--- a/drivers/gpu/drm/bridge/tc358762.c
+++ b/drivers/gpu/drm/bridge/tc358762.c
@@ -53,6 +53,12 @@
 #define LCDCTRL_VSPOL		BIT(19) /* Polarity of VSYNC signal */
 #define LCDCTRL_VSDELAY(v)	(((v) & 0xfff) << 20) /* VSYNC delay */
 
+/* First parameter is in the 16bits, second is in the top 16bits */
+#define LCD_HS_HBP		0x0424
+#define LCD_HDISP_HFP		0x0428
+#define LCD_VS_VBP		0x042c
+#define LCD_VDISP_VFP		0x0430
+
 /* SPI Master Registers */
 #define SPICMR			0x0450
 #define SPITCR			0x0454
@@ -139,6 +145,15 @@ static int tc358762_init(struct tc358762 *ctx)
 	tc358762_write(ctx, LCDCTRL, lcdctrl);
 
 	tc358762_write(ctx, SYSCTRL, 0x040f);
+
+	tc358762_write(ctx, LCD_HS_HBP, (ctx->mode.hsync_end - ctx->mode.hsync_start) |
+		       ((ctx->mode.htotal - ctx->mode.hsync_end) << 16));
+	tc358762_write(ctx, LCD_HDISP_HFP, ctx->mode.hdisplay |
+		       ((ctx->mode.hsync_start - ctx->mode.hdisplay) << 16));
+	tc358762_write(ctx, LCD_VS_VBP, (ctx->mode.vsync_end - ctx->mode.vsync_start) |
+		       ((ctx->mode.vtotal - ctx->mode.vsync_end) << 16));
+	tc358762_write(ctx, LCD_VDISP_VFP, ctx->mode.vdisplay |
+		       ((ctx->mode.vsync_start - ctx->mode.vdisplay) << 16));
 	msleep(100);
 
 	tc358762_write(ctx, PPI_STARTPPI, PPI_START_FUNCTION);
@@ -185,14 +200,6 @@ static void tc358762_pre_enable(struct drm_bridge *bridge, struct drm_bridge_sta
 		usleep_range(5000, 10000);
 	}
 
-	ctx->pre_enabled = true;
-}
-
-static void tc358762_enable(struct drm_bridge *bridge, struct drm_bridge_state *state)
-{
-	struct tc358762 *ctx = bridge_to_tc358762(bridge);
-	int ret;
-
 	ret = tc358762_init(ctx);
 	if (ret < 0)
 		dev_err(ctx->dev, "error initializing bridge (%d)\n", ret);
@@ -219,7 +226,6 @@ static void tc358762_bridge_mode_set(struct drm_bridge *bridge,
 static const struct drm_bridge_funcs tc358762_bridge_funcs = {
 	.atomic_post_disable = tc358762_post_disable,
 	.atomic_pre_enable = tc358762_pre_enable,
-	.atomic_enable = tc358762_enable,
 	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
 	.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
 	.atomic_reset = drm_atomic_helper_bridge_reset,
@@ -294,7 +300,7 @@ static int tc358762_probe(struct mipi_dsi_device *dsi)
 	ret = mipi_dsi_attach(dsi);
 	if (ret < 0) {
 		drm_bridge_remove(&ctx->bridge);
-		dev_err(dev, "failed to attach dsi\n");
+		dev_err_probe(dev, ret, "failed to attach dsi\n");
 	}
 
 	return ret;
diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
index 936a8f95d80f..34d411245fe8 100644
--- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c
+++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c
@@ -24,7 +24,7 @@ void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector,
 	unsigned int max_bpc = connector->max_bpc;
 
 	new_conn_state->max_bpc = max_bpc;
-	new_conn_state->max_requested_bpc = max_bpc;
+	new_conn_state->max_requested_bpc = 8;
 	new_conn_state->hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO;
 }
 EXPORT_SYMBOL(__drm_atomic_helper_connector_hdmi_reset);
@@ -294,6 +294,11 @@ hdmi_compute_format(const struct drm_connector *connector,
 		return 0;
 	}
 
+	if (hdmi_try_format_bpc(connector, conn_state, mode, bpc, HDMI_COLORSPACE_YUV422)) {
+		conn_state->hdmi.output_format = HDMI_COLORSPACE_YUV422;
+		return 0;
+	}
+
 	drm_dbg_kms(dev, "Failed. No Format Supported for that bpc count.\n");
 
 	return -EINVAL;
diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c
index 0fc99da93afe..5427d67d51e6 100644
--- a/drivers/gpu/drm/drm_atomic.c
+++ b/drivers/gpu/drm/drm_atomic.c
@@ -449,7 +449,7 @@ static void drm_atomic_crtc_print_state(struct drm_printer *p,
 	drm_printf(p, "\tactive_changed=%d\n", state->active_changed);
 	drm_printf(p, "\tconnectors_changed=%d\n", state->connectors_changed);
 	drm_printf(p, "\tcolor_mgmt_changed=%d\n", state->color_mgmt_changed);
-	drm_printf(p, "\tplane_mask=%x\n", state->plane_mask);
+	drm_printf(p, "\tplane_mask=%llx\n", state->plane_mask);
 	drm_printf(p, "\tconnector_mask=%x\n", state->connector_mask);
 	drm_printf(p, "\tencoder_mask=%x\n", state->encoder_mask);
 	drm_printf(p, "\tmode: " DRM_MODE_FMT "\n", DRM_MODE_ARG(&state->mode));
diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
index 40e4e1b6c911..340a116ba125 100644
--- a/drivers/gpu/drm/drm_atomic_helper.c
+++ b/drivers/gpu/drm/drm_atomic_helper.c
@@ -444,6 +444,11 @@ mode_fixup(struct drm_atomic_state *state)
 		new_crtc_state =
 			drm_atomic_get_new_crtc_state(state, new_conn_state->crtc);
 
+		if (!new_crtc_state->mode_changed &&
+		    !new_crtc_state->connectors_changed) {
+			continue;
+		}
+
 		/*
 		 * Each encoder has at most one connector (since we always steal
 		 * it away), so we won't call ->mode_fixup twice.
@@ -1679,13 +1684,6 @@ drm_atomic_helper_wait_for_vblanks(struct drm_device *dev,
 	int i, ret;
 	unsigned int crtc_mask = 0;
 
-	 /*
-	  * Legacy cursor ioctls are completely unsynced, and userspace
-	  * relies on that (by doing tons of cursor updates).
-	  */
-	if (old_state->legacy_cursor_update)
-		return;
-
 	for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) {
 		if (!new_crtc_state->active)
 			continue;
@@ -2335,12 +2333,6 @@ int drm_atomic_helper_setup_commit(struct drm_atomic_state *state,
 			continue;
 		}
 
-		/* Legacy cursor updates are fully unsynced. */
-		if (state->legacy_cursor_update) {
-			complete_all(&commit->flip_done);
-			continue;
-		}
-
 		if (!new_crtc_state->event) {
 			commit->event = kzalloc(sizeof(*commit->event),
 						GFP_KERNEL);
diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c
index 519228eb1095..c7ff21237806 100644
--- a/drivers/gpu/drm/drm_atomic_state_helper.c
+++ b/drivers/gpu/drm/drm_atomic_state_helper.c
@@ -267,6 +267,20 @@ void __drm_atomic_helper_plane_state_reset(struct drm_plane_state *plane_state,
 			plane_state->color_range = val;
 	}
 
+	if (plane->chroma_siting_h_property) {
+		if (!drm_object_property_get_default_value(&plane->base,
+							   plane->chroma_siting_h_property,
+							   &val))
+			plane_state->chroma_siting_h = val;
+	}
+
+	if (plane->chroma_siting_v_property) {
+		if (!drm_object_property_get_default_value(&plane->base,
+							   plane->chroma_siting_v_property,
+							   &val))
+			plane_state->chroma_siting_v = val;
+	}
+
 	if (plane->zpos_property) {
 		if (!drm_object_property_get_default_value(&plane->base,
 							   plane->zpos_property,
diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
index fd36b8fd54e9..4f8bf87c1949 100644
--- a/drivers/gpu/drm/drm_atomic_uapi.c
+++ b/drivers/gpu/drm/drm_atomic_uapi.c
@@ -538,6 +538,10 @@ static int drm_atomic_plane_set_property(struct drm_plane *plane,
 		state->color_encoding = val;
 	} else if (property == plane->color_range_property) {
 		state->color_range = val;
+	} else if (property == plane->chroma_siting_h_property) {
+		state->chroma_siting_h = val;
+	} else if (property == plane->chroma_siting_v_property) {
+		state->chroma_siting_v = val;
 	} else if (property == config->prop_fb_damage_clips) {
 		ret = drm_property_replace_blob_from_id(dev,
 					&state->fb_damage_clips,
@@ -620,6 +624,10 @@ drm_atomic_plane_get_property(struct drm_plane *plane,
 		*val = state->color_encoding;
 	} else if (property == plane->color_range_property) {
 		*val = state->color_range;
+	} else if (property == plane->chroma_siting_h_property) {
+		*val = state->chroma_siting_h;
+	} else if (property == plane->chroma_siting_v_property) {
+		*val = state->chroma_siting_v;
 	} else if (property == config->prop_fb_damage_clips) {
 		*val = (state->fb_damage_clips) ?
 			state->fb_damage_clips->base.id : 0;
@@ -671,6 +679,7 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
 {
 	struct drm_device *dev = connector->dev;
 	struct drm_mode_config *config = &dev->mode_config;
+	bool margins_updated = false;
 	bool replaced = false;
 	int ret;
 
@@ -699,12 +708,16 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
 		state->tv.subconnector = val;
 	} else if (property == config->tv_left_margin_property) {
 		state->tv.margins.left = val;
+		margins_updated = true;
 	} else if (property == config->tv_right_margin_property) {
 		state->tv.margins.right = val;
+		margins_updated = true;
 	} else if (property == config->tv_top_margin_property) {
 		state->tv.margins.top = val;
+		margins_updated = true;
 	} else if (property == config->tv_bottom_margin_property) {
 		state->tv.margins.bottom = val;
+		margins_updated = true;
 	} else if (property == config->legacy_tv_mode_property) {
 		state->tv.legacy_mode = val;
 	} else if (property == config->tv_mode_property) {
@@ -778,6 +791,14 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
 		state->privacy_screen_sw_state = val;
 	} else if (property == connector->broadcast_rgb_property) {
 		state->hdmi.broadcast_rgb = val;
+	} else if (property == connector->rotation_property) {
+		if (!is_power_of_2(val & DRM_MODE_ROTATE_MASK)) {
+			drm_dbg_atomic(connector->dev,
+				       "[CONNECTOR:%d:%s] bad rotation bitmask: 0x%llx\n",
+				       connector->base.id, connector->name, val);
+			return -EINVAL;
+		}
+		state->rotation = val;
 	} else if (connector->funcs->atomic_set_property) {
 		return connector->funcs->atomic_set_property(connector,
 				state, property, val);
@@ -789,6 +810,12 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
 		return -EINVAL;
 	}
 
+	if (margins_updated && state->crtc) {
+		ret = drm_atomic_add_affected_planes(state->state, state->crtc);
+
+		return ret;
+	}
+
 	return 0;
 }
 
@@ -863,6 +890,8 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
 		*val = state->privacy_screen_sw_state;
 	} else if (property == connector->broadcast_rgb_property) {
 		*val = state->hdmi.broadcast_rgb;
+	} else if (property == connector->rotation_property) {
+		*val = state->rotation;
 	} else if (connector->funcs->atomic_get_property) {
 		return connector->funcs->atomic_get_property(connector,
 				state, property, val);
diff --git a/drivers/gpu/drm/drm_blend.c b/drivers/gpu/drm/drm_blend.c
index 6e74de833466..4e63000713db 100644
--- a/drivers/gpu/drm/drm_blend.c
+++ b/drivers/gpu/drm/drm_blend.c
@@ -235,6 +235,16 @@ int drm_plane_create_alpha_property(struct drm_plane *plane)
 }
 EXPORT_SYMBOL(drm_plane_create_alpha_property);
 
+static const struct drm_prop_enum_list drm_rotate_props[] = {
+	{ __builtin_ffs(DRM_MODE_ROTATE_0) - 1,   "rotate-0" },
+	{ __builtin_ffs(DRM_MODE_ROTATE_90) - 1,  "rotate-90" },
+	{ __builtin_ffs(DRM_MODE_ROTATE_180) - 1, "rotate-180" },
+	{ __builtin_ffs(DRM_MODE_ROTATE_270) - 1, "rotate-270" },
+	{ __builtin_ffs(DRM_MODE_REFLECT_X) - 1,  "reflect-x" },
+	{ __builtin_ffs(DRM_MODE_REFLECT_Y) - 1,  "reflect-y" },
+	{ __builtin_ffs(DRM_MODE_TRANSPOSE) - 1,  "transpose" },
+};
+
 /**
  * drm_plane_create_rotation_property - create a new rotation property
  * @plane: drm plane
@@ -263,6 +273,8 @@ EXPORT_SYMBOL(drm_plane_create_alpha_property);
  * 	"reflect-x"
  * DRM_MODE_REFLECT_Y:
  * 	"reflect-y"
+ * DRM_MODE_TRANSPOSE:
+ * 	"transpose"
  *
  * Rotation is the specified amount in degrees in counter clockwise direction,
  * the X and Y axis are within the source rectangle, i.e.  the X/Y axis before
@@ -273,14 +285,6 @@ int drm_plane_create_rotation_property(struct drm_plane *plane,
 				       unsigned int rotation,
 				       unsigned int supported_rotations)
 {
-	static const struct drm_prop_enum_list props[] = {
-		{ __builtin_ffs(DRM_MODE_ROTATE_0) - 1,   "rotate-0" },
-		{ __builtin_ffs(DRM_MODE_ROTATE_90) - 1,  "rotate-90" },
-		{ __builtin_ffs(DRM_MODE_ROTATE_180) - 1, "rotate-180" },
-		{ __builtin_ffs(DRM_MODE_ROTATE_270) - 1, "rotate-270" },
-		{ __builtin_ffs(DRM_MODE_REFLECT_X) - 1,  "reflect-x" },
-		{ __builtin_ffs(DRM_MODE_REFLECT_Y) - 1,  "reflect-y" },
-	};
 	struct drm_property *prop;
 
 	WARN_ON((supported_rotations & DRM_MODE_ROTATE_MASK) == 0);
@@ -288,7 +292,8 @@ int drm_plane_create_rotation_property(struct drm_plane *plane,
 	WARN_ON(rotation & ~supported_rotations);
 
 	prop = drm_property_create_bitmask(plane->dev, 0, "rotation",
-					   props, ARRAY_SIZE(props),
+					   drm_rotate_props,
+					   ARRAY_SIZE(drm_rotate_props),
 					   supported_rotations);
 	if (!prop)
 		return -ENOMEM;
@@ -304,6 +309,34 @@ int drm_plane_create_rotation_property(struct drm_plane *plane,
 }
 EXPORT_SYMBOL(drm_plane_create_rotation_property);
 
+int drm_connector_create_rotation_property(struct drm_connector *conn,
+					   unsigned int rotation,
+					   unsigned int supported_rotations)
+{
+	struct drm_property *prop;
+
+	WARN_ON((supported_rotations & DRM_MODE_ROTATE_MASK) == 0);
+	WARN_ON(!is_power_of_2(rotation & DRM_MODE_ROTATE_MASK));
+	WARN_ON(rotation & ~supported_rotations);
+
+	prop = drm_property_create_bitmask(conn->dev, 0, "rotation",
+					   drm_rotate_props,
+					   ARRAY_SIZE(drm_rotate_props),
+					   supported_rotations);
+	if (!prop)
+		return -ENOMEM;
+
+	drm_object_attach_property(&conn->base, prop, rotation);
+
+	if (conn->state)
+		conn->state->rotation = rotation;
+
+	conn->rotation_property = prop;
+
+	return 0;
+}
+EXPORT_SYMBOL(drm_connector_create_rotation_property);
+
 /**
  * drm_rotation_simplify() - Try to simplify the rotation
  * @rotation: Rotation to be simplified
diff --git a/drivers/gpu/drm/drm_color_mgmt.c b/drivers/gpu/drm/drm_color_mgmt.c
index 3969dc548cff..c183da01b8fb 100644
--- a/drivers/gpu/drm/drm_color_mgmt.c
+++ b/drivers/gpu/drm/drm_color_mgmt.c
@@ -330,7 +330,9 @@ static int drm_crtc_legacy_gamma_set(struct drm_crtc *crtc,
 	replaced = drm_property_replace_blob(&crtc_state->degamma_lut,
 					     use_gamma_lut ? NULL : blob);
 	replaced |= drm_property_replace_blob(&crtc_state->ctm, NULL);
-	replaced |= drm_property_replace_blob(&crtc_state->gamma_lut,
+	if (!crtc_state->gamma_lut || !crtc_state->gamma_lut->data ||
+	    memcmp(crtc_state->gamma_lut->data, blob_data, blob->length))
+		replaced |= drm_property_replace_blob(&crtc_state->gamma_lut,
 					      use_gamma_lut ? blob : NULL);
 	crtc_state->color_mgmt_changed |= replaced;
 
@@ -588,6 +590,42 @@ int drm_plane_create_color_properties(struct drm_plane *plane,
 }
 EXPORT_SYMBOL(drm_plane_create_color_properties);
 
+/**
+ * drm_plane_create_chroma_siting_properties - chroma siting related plane properties
+ * @plane: plane object
+ *
+ * Create and attach plane specific CHROMA_SITING
+ * properties to @plane.
+ */
+int drm_plane_create_chroma_siting_properties(struct drm_plane *plane,
+						int32_t default_chroma_siting_h,
+						int32_t default_chroma_siting_v)
+{
+	struct drm_device *dev = plane->dev;
+	struct drm_property *prop;
+
+	prop = drm_property_create_range(dev, 0, "CHROMA_SITING_H",
+					0, 1<<16);
+	if (!prop)
+		return -ENOMEM;
+	plane->chroma_siting_h_property = prop;
+	drm_object_attach_property(&plane->base, prop, default_chroma_siting_h);
+
+	prop = drm_property_create_range(dev, 0, "CHROMA_SITING_V",
+					0, 1<<16);
+	if (!prop)
+		return -ENOMEM;
+	plane->chroma_siting_v_property = prop;
+	drm_object_attach_property(&plane->base, prop, default_chroma_siting_v);
+
+	if (plane->state) {
+		plane->state->chroma_siting_h = default_chroma_siting_h;
+		plane->state->chroma_siting_v = default_chroma_siting_v;
+	}
+	return 0;
+}
+EXPORT_SYMBOL(drm_plane_create_chroma_siting_properties);
+
 /**
  * drm_color_lut_check - check validity of lookup table
  * @lut: property blob containing LUT to check
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
index 994afa5a0ffb..e5ef46f8ed7c 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -33,6 +33,7 @@
 #include <drm/drm_sysfs.h>
 #include <drm/drm_utils.h>
 
+#include <linux/of.h>
 #include <linux/property.h>
 #include <linux/uaccess.h>
 
@@ -83,6 +84,7 @@ struct drm_conn_prop_enum_list {
 	int type;
 	const char *name;
 	struct ida ida;
+	int first_dyn_num;
 };
 
 /*
@@ -112,12 +114,41 @@ static struct drm_conn_prop_enum_list drm_connector_enum_list[] = {
 	{ DRM_MODE_CONNECTOR_USB, "USB" },
 };
 
+#define MAX_DT_NODE_NAME_LEN	20
+#define DT_DRM_NODE_PREFIX	"drm-"
+
+static void drm_connector_get_of_name(int type, char *node_name, int length)
+{
+	int i = 0;
+
+	strcpy(node_name, DT_DRM_NODE_PREFIX);
+
+	do {
+		node_name[i + strlen(DT_DRM_NODE_PREFIX)] =
+				tolower(drm_connector_enum_list[type].name[i]);
+
+	} while (drm_connector_enum_list[type].name[i++] &&
+		 i < length);
+
+	node_name[length - 1] = '\0';
+}
+
 void drm_connector_ida_init(void)
 {
-	int i;
+	int i, id;
+	char node_name[MAX_DT_NODE_NAME_LEN];
 
-	for (i = 0; i < ARRAY_SIZE(drm_connector_enum_list); i++)
+	for (i = 0; i < ARRAY_SIZE(drm_connector_enum_list); i++) {
 		ida_init(&drm_connector_enum_list[i].ida);
+
+		drm_connector_get_of_name(i, node_name, MAX_DT_NODE_NAME_LEN);
+
+		id = of_alias_get_highest_id(node_name);
+		if (id > 0)
+			drm_connector_enum_list[i].first_dyn_num = id + 1;
+		else
+			drm_connector_enum_list[i].first_dyn_num = 1;
+	}
 }
 
 void drm_connector_ida_destroy(void)
@@ -225,7 +256,9 @@ static int __drm_connector_init(struct drm_device *dev,
 				struct i2c_adapter *ddc)
 {
 	struct drm_mode_config *config = &dev->mode_config;
+	char node_name[MAX_DT_NODE_NAME_LEN];
 	int ret;
+	int id;
 	struct ida *connector_ida =
 		&drm_connector_enum_list[connector_type].ida;
 
@@ -255,8 +288,28 @@ static int __drm_connector_init(struct drm_device *dev,
 	ret = 0;
 
 	connector->connector_type = connector_type;
-	connector->connector_type_id =
-		ida_alloc_min(connector_ida, 1, GFP_KERNEL);
+	connector->connector_type_id = 0;
+
+	drm_connector_get_of_name(connector_type, node_name, MAX_DT_NODE_NAME_LEN);
+	id = of_alias_get_id(dev->dev->of_node, node_name);
+	if (id > 0) {
+		/* Try and allocate the requested ID
+		 * Valid range is 1 to 31, hence ignoring 0 as an error
+		 */
+		int type_id = ida_alloc_range(connector_ida, id, id, GFP_KERNEL);
+
+		if (type_id > 0)
+			connector->connector_type_id = type_id;
+		else
+			drm_err(dev, "Failed to acquire type ID %d for interface type %s, ret %d\n",
+				id, drm_connector_enum_list[connector_type].name,
+				type_id);
+	}
+	if (!connector->connector_type_id)
+		connector->connector_type_id =
+				ida_alloc_min(connector_ida,
+					      drm_connector_enum_list[connector_type].first_dyn_num,
+					      GFP_KERNEL);
 	if (connector->connector_type_id < 0) {
 		ret = connector->connector_type_id;
 		goto out_put_id;
@@ -310,7 +363,8 @@ static int __drm_connector_init(struct drm_device *dev,
 
 	drm_object_attach_property(&connector->base,
 				   config->non_desktop_property,
-				   0);
+				   (connector_type != DRM_MODE_CONNECTOR_VIRTUAL &&
+				   connector_type != DRM_MODE_CONNECTOR_WRITEBACK) ? 0 : 1);
 	drm_object_attach_property(&connector->base,
 				   config->tile_property,
 				   0);
@@ -2677,8 +2731,8 @@ int drm_connector_attach_max_bpc_property(struct drm_connector *connector,
 	}
 
 	drm_object_attach_property(&connector->base, prop, max);
-	connector->state->max_requested_bpc = max;
-	connector->state->max_bpc = max;
+	connector->state->max_requested_bpc = min;
+	connector->state->max_bpc = min;
 
 	return 0;
 }
@@ -2912,10 +2966,15 @@ int drm_connector_set_orientation_from_panel(
 {
 	enum drm_panel_orientation orientation;
 
-	if (panel && panel->funcs && panel->funcs->get_orientation)
+	if (panel && panel->funcs && panel->funcs->get_orientation) {
 		orientation = panel->funcs->get_orientation(panel);
-	else
+	} else {
 		orientation = DRM_MODE_PANEL_ORIENTATION_UNKNOWN;
+		if (panel) {
+			of_drm_get_panel_orientation(panel->dev->of_node,
+						     &orientation);
+		}
+	}
 
 	return drm_connector_set_panel_orientation(connector, orientation);
 }
diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
index 9edb3247c767..c51d2e708241 100644
--- a/drivers/gpu/drm/drm_edid.c
+++ b/drivers/gpu/drm/drm_edid.c
@@ -6587,7 +6587,11 @@ static void drm_reset_display_info(struct drm_connector *connector)
 	info->height_mm = 0;
 
 	info->bpc = 0;
-	info->color_formats = 0;
+	if (connector->connector_type == DRM_MODE_CONNECTOR_HDMIA ||
+	    connector->connector_type == DRM_MODE_CONNECTOR_HDMIB)
+		info->color_formats = DRM_COLOR_FORMAT_RGB444;
+	else
+		info->color_formats = 0;
 	info->cea_rev = 0;
 	info->max_tmds_clock = 0;
 	info->dvi_dual = false;
diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c
index a8971c4eb9f0..6135ee0f01c5 100644
--- a/drivers/gpu/drm/drm_fb_helper.c
+++ b/drivers/gpu/drm/drm_fb_helper.c
@@ -1797,7 +1797,7 @@ __drm_fb_helper_initial_config_and_unlock(struct drm_fb_helper *fb_helper)
 	struct drm_device *dev = fb_helper->dev;
 	struct fb_info *info;
 	unsigned int width, height;
-	int ret;
+	int ret, id;
 
 	width = dev->mode_config.max_width;
 	height = dev->mode_config.max_height;
@@ -1825,6 +1825,15 @@ __drm_fb_helper_initial_config_and_unlock(struct drm_fb_helper *fb_helper)
 	 * register the fbdev emulation instance in kernel_fb_helper_list. */
 	mutex_unlock(&fb_helper->lock);
 
+	id = of_alias_get_highest_id("drm-fb");
+	if (id >= 0)
+		fb_set_lowest_dynamic_fb(id + 1);
+
+	id = of_alias_get_id(dev->dev->of_node, "drm-fb");
+	if (id >= 0) {
+		info->node = id;
+		info->custom_fb_num = true;
+	}
 	ret = register_framebuffer(info);
 	if (ret < 0)
 		return ret;
diff --git a/drivers/gpu/drm/drm_fourcc.c b/drivers/gpu/drm/drm_fourcc.c
index 3a94ca211f9c..55ddd99fd7f6 100644
--- a/drivers/gpu/drm/drm_fourcc.c
+++ b/drivers/gpu/drm/drm_fourcc.c
@@ -346,6 +346,33 @@ const struct drm_format_info *__drm_format_info(u32 format)
 		{ .format = DRM_FORMAT_P030,            .depth = 0,  .num_planes = 2,
 		  .char_per_block = { 4, 8, 0 }, .block_w = { 3, 3, 0 }, .block_h = { 1, 1, 0 },
 		  .hsub = 2, .vsub = 2, .is_yuv = true},
+		{ .format = DRM_FORMAT_S010,            .depth = 0,  .num_planes = 3,
+		  .char_per_block = { 2, 2, 2 }, .block_w = { 1, 1, 1 }, .block_h = { 1, 1, 1 },
+		  .hsub = 2, .vsub = 2, .is_yuv = true},
+		{ .format = DRM_FORMAT_S210,            .depth = 0,  .num_planes = 3,
+		  .char_per_block = { 2, 2, 2 }, .block_w = { 1, 1, 1 }, .block_h = { 1, 1, 1 },
+		  .hsub = 2, .vsub = 1, .is_yuv = true},
+		{ .format = DRM_FORMAT_S410,            .depth = 0,  .num_planes = 3,
+		  .char_per_block = { 2, 2, 2 }, .block_w = { 1, 1, 1 }, .block_h = { 1, 1, 1 },
+		  .hsub = 1, .vsub = 1, .is_yuv = true},
+		{ .format = DRM_FORMAT_S012,            .depth = 0,  .num_planes = 3,
+		  .char_per_block = { 2, 2, 2 }, .block_w = { 1, 1, 1 }, .block_h = { 1, 1, 1 },
+		  .hsub = 2, .vsub = 2, .is_yuv = true},
+		{ .format = DRM_FORMAT_S212,            .depth = 0,  .num_planes = 3,
+		  .char_per_block = { 2, 2, 2 }, .block_w = { 1, 1, 1 }, .block_h = { 1, 1, 1 },
+		  .hsub = 2, .vsub = 1, .is_yuv = true},
+		{ .format = DRM_FORMAT_S412,            .depth = 0,  .num_planes = 3,
+		  .char_per_block = { 2, 2, 2 }, .block_w = { 1, 1, 1 }, .block_h = { 1, 1, 1 },
+		  .hsub = 1, .vsub = 1, .is_yuv = true},
+		{ .format = DRM_FORMAT_S016,            .depth = 0,  .num_planes = 3,
+		  .char_per_block = { 2, 2, 2 }, .block_w = { 1, 1, 1 }, .block_h = { 1, 1, 1 },
+		  .hsub = 2, .vsub = 2, .is_yuv = true},
+		{ .format = DRM_FORMAT_S216,            .depth = 0,  .num_planes = 3,
+		  .char_per_block = { 2, 2, 2 }, .block_w = { 1, 1, 1 }, .block_h = { 1, 1, 1 },
+		  .hsub = 2, .vsub = 1, .is_yuv = true},
+		{ .format = DRM_FORMAT_S416,            .depth = 0,  .num_planes = 3,
+		  .char_per_block = { 2, 2, 2 }, .block_w = { 1, 1, 1 }, .block_h = { 1, 1, 1 },
+		  .hsub = 1, .vsub = 1, .is_yuv = true},
 	};
 
 	unsigned int i;
diff --git a/drivers/gpu/drm/drm_framebuffer.c b/drivers/gpu/drm/drm_framebuffer.c
index d6550b54fac1..9558304351e4 100644
--- a/drivers/gpu/drm/drm_framebuffer.c
+++ b/drivers/gpu/drm/drm_framebuffer.c
@@ -199,7 +199,8 @@ static int framebuffer_check(struct drm_device *dev,
 		if ((uint64_t) height * r->pitches[i] + r->offsets[i] > UINT_MAX)
 			return -ERANGE;
 
-		if (block_size && r->pitches[i] < min_pitch) {
+		if (r->modifier[i] == DRM_FORMAT_MOD_LINEAR && block_size &&
+		    r->pitches[i] < min_pitch) {
 			drm_dbg_kms(dev, "bad pitch %u for plane %d\n", r->pitches[i], i);
 			return -EINVAL;
 		}
@@ -1002,7 +1003,7 @@ static int atomic_remove_fb(struct drm_framebuffer *fb)
 	struct drm_connector *conn __maybe_unused;
 	struct drm_connector_state *conn_state;
 	int i, ret;
-	unsigned plane_mask;
+	u64 plane_mask;
 	bool disable_crtcs = false;
 
 retry_disable:
diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c
index 9e8a4da313a0..84f5ea77f2df 100644
--- a/drivers/gpu/drm/drm_gem.c
+++ b/drivers/gpu/drm/drm_gem.c
@@ -114,22 +114,32 @@ drm_gem_init(struct drm_device *dev)
 }
 
 /**
- * drm_gem_object_init - initialize an allocated shmem-backed GEM object
+ * drm_gem_object_init_with_mnt - initialize an allocated shmem-backed GEM
+ * object in a given shmfs mountpoint
+ *
  * @dev: drm_device the object should be initialized for
  * @obj: drm_gem_object to initialize
  * @size: object size
+ * @gemfs: tmpfs mount where the GEM object will be created. If NULL, use
+ * the usual tmpfs mountpoint (`shm_mnt`).
  *
  * Initialize an already allocated GEM object of the specified size with
  * shmfs backing store.
  */
-int drm_gem_object_init(struct drm_device *dev,
-			struct drm_gem_object *obj, size_t size)
+int drm_gem_object_init_with_mnt(struct drm_device *dev,
+				 struct drm_gem_object *obj, size_t size,
+				 struct vfsmount *gemfs)
 {
 	struct file *filp;
 
 	drm_gem_private_object_init(dev, obj, size);
 
-	filp = shmem_file_setup("drm mm object", size, VM_NORESERVE);
+	if (gemfs)
+		filp = shmem_file_setup_with_mnt(gemfs, "drm mm object", size,
+						 VM_NORESERVE);
+	else
+		filp = shmem_file_setup("drm mm object", size, VM_NORESERVE);
+
 	if (IS_ERR(filp))
 		return PTR_ERR(filp);
 
@@ -137,6 +147,22 @@ int drm_gem_object_init(struct drm_device *dev,
 
 	return 0;
 }
+EXPORT_SYMBOL(drm_gem_object_init_with_mnt);
+
+/**
+ * drm_gem_object_init - initialize an allocated shmem-backed GEM object
+ * @dev: drm_device the object should be initialized for
+ * @obj: drm_gem_object to initialize
+ * @size: object size
+ *
+ * Initialize an already allocated GEM object of the specified size with
+ * shmfs backing store.
+ */
+int drm_gem_object_init(struct drm_device *dev, struct drm_gem_object *obj,
+			size_t size)
+{
+	return drm_gem_object_init_with_mnt(dev, obj, size, NULL);
+}
 EXPORT_SYMBOL(drm_gem_object_init);
 
 /**
diff --git a/drivers/gpu/drm/drm_gem_shmem_helper.c b/drivers/gpu/drm/drm_gem_shmem_helper.c
index 53c003983ad1..8508060a1a95 100644
--- a/drivers/gpu/drm/drm_gem_shmem_helper.c
+++ b/drivers/gpu/drm/drm_gem_shmem_helper.c
@@ -49,7 +49,8 @@ static const struct drm_gem_object_funcs drm_gem_shmem_funcs = {
 };
 
 static struct drm_gem_shmem_object *
-__drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private)
+__drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private,
+		       struct vfsmount *gemfs)
 {
 	struct drm_gem_shmem_object *shmem;
 	struct drm_gem_object *obj;
@@ -76,7 +77,7 @@ __drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private)
 		drm_gem_private_object_init(dev, obj, size);
 		shmem->map_wc = false; /* dma-buf mappings use always writecombine */
 	} else {
-		ret = drm_gem_object_init(dev, obj, size);
+		ret = drm_gem_object_init_with_mnt(dev, obj, size, gemfs);
 	}
 	if (ret) {
 		drm_gem_private_object_fini(obj);
@@ -123,10 +124,31 @@ __drm_gem_shmem_create(struct drm_device *dev, size_t size, bool private)
  */
 struct drm_gem_shmem_object *drm_gem_shmem_create(struct drm_device *dev, size_t size)
 {
-	return __drm_gem_shmem_create(dev, size, false);
+	return __drm_gem_shmem_create(dev, size, false, NULL);
 }
 EXPORT_SYMBOL_GPL(drm_gem_shmem_create);
 
+/**
+ * drm_gem_shmem_create_with_mnt - Allocate an object with the given size in a
+ * given mountpoint
+ * @dev: DRM device
+ * @size: Size of the object to allocate
+ * @gemfs: tmpfs mount where the GEM object will be created
+ *
+ * This function creates a shmem GEM object in a given tmpfs mountpoint.
+ *
+ * Returns:
+ * A struct drm_gem_shmem_object * on success or an ERR_PTR()-encoded negative
+ * error code on failure.
+ */
+struct drm_gem_shmem_object *drm_gem_shmem_create_with_mnt(struct drm_device *dev,
+							   size_t size,
+							   struct vfsmount *gemfs)
+{
+	return __drm_gem_shmem_create(dev, size, false, gemfs);
+}
+EXPORT_SYMBOL_GPL(drm_gem_shmem_create_with_mnt);
+
 /**
  * drm_gem_shmem_free - Free resources associated with a shmem GEM object
  * @shmem: shmem GEM object to free
@@ -765,7 +787,7 @@ drm_gem_shmem_prime_import_sg_table(struct drm_device *dev,
 	size_t size = PAGE_ALIGN(attach->dmabuf->size);
 	struct drm_gem_shmem_object *shmem;
 
-	shmem = __drm_gem_shmem_create(dev, size, true);
+	shmem = __drm_gem_shmem_create(dev, size, true, NULL);
 	if (IS_ERR(shmem))
 		return ERR_CAST(shmem);
 
diff --git a/drivers/gpu/drm/drm_mode_config.c b/drivers/gpu/drm/drm_mode_config.c
index 502d88ca9ffa..71ff54869a78 100644
--- a/drivers/gpu/drm/drm_mode_config.c
+++ b/drivers/gpu/drm/drm_mode_config.c
@@ -646,7 +646,7 @@ void drm_mode_config_validate(struct drm_device *dev)
 	struct drm_encoder *encoder;
 	struct drm_crtc *crtc;
 	struct drm_plane *plane;
-	u32 primary_with_crtc = 0, cursor_with_crtc = 0;
+	u64 primary_with_crtc = 0, cursor_with_crtc = 0;
 	unsigned int num_primary = 0;
 
 	if (!drm_core_check_feature(dev, DRIVER_MODESET))
diff --git a/drivers/gpu/drm/drm_modes.c b/drivers/gpu/drm/drm_modes.c
index 71573b85d924..c21acedc0f54 100644
--- a/drivers/gpu/drm/drm_modes.c
+++ b/drivers/gpu/drm/drm_modes.c
@@ -2173,12 +2173,15 @@ static int drm_mode_parse_cmdline_options(const char *str,
 
 	option = str;
 	do {
+		sep = strchr(option, ',');
 		delim = strchr(option, '=');
 		if (!delim) {
-			delim = strchr(option, ',');
-
-			if (!delim)
+			if (!sep)
 				delim = option + strlen(option);
+			else
+				delim = sep;
+		} else if (sep && sep < delim) {
+			delim = sep;
 		}
 
 		if (!strncmp(option, "rotate", delim - option)) {
diff --git a/drivers/gpu/drm/drm_plane.c b/drivers/gpu/drm/drm_plane.c
index 4fcb5d486de6..3696b10fd969 100644
--- a/drivers/gpu/drm/drm_plane.c
+++ b/drivers/gpu/drm/drm_plane.c
@@ -365,7 +365,7 @@ static int __drm_universal_plane_init(struct drm_device *dev,
 	int ret;
 
 	/* plane index is used with 32bit bitmasks */
-	if (WARN_ON(config->num_total_plane >= 32))
+	if (WARN_ON(config->num_total_plane >= 64))
 		return -EINVAL;
 
 	/*
diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c
index 92f21764246f..8a23abb9e4b3 100644
--- a/drivers/gpu/drm/drm_probe_helper.c
+++ b/drivers/gpu/drm/drm_probe_helper.c
@@ -163,6 +163,10 @@ static int drm_helper_probe_add_cmdline_mode(struct drm_connector *connector)
 				continue;
 		}
 
+		if (cmdline_mode->interlace !=
+		    !!(mode->flags & DRM_MODE_FLAG_INTERLACE))
+			continue;
+
 		/* Mark the matching mode as being preferred by the user */
 		mode->type |= DRM_MODE_TYPE_USERDEF;
 		return 0;
diff --git a/drivers/gpu/drm/i915/display/intel_display.c b/drivers/gpu/drm/i915/display/intel_display.c
index 2b4552cd1eed..8d96fec56fd2 100644
--- a/drivers/gpu/drm/i915/display/intel_display.c
+++ b/drivers/gpu/drm/i915/display/intel_display.c
@@ -7651,6 +7651,19 @@ int intel_atomic_commit(struct drm_device *dev, struct drm_atomic_state *_state,
 				state->base.legacy_cursor_update = false;
 	}
 
+	/*
+	 * FIXME: Cut over to (async) commit helpers instead of hand-rolling
+	 * everything.
+	 */
+	if (state->base.legacy_cursor_update) {
+		struct intel_crtc_state *new_crtc_state;
+		struct intel_crtc *crtc;
+		int i;
+
+		for_each_new_intel_crtc_in_state(state, crtc, new_crtc_state, i)
+			complete_all(&new_crtc_state->uapi.commit->flip_done);
+	}
+
 	ret = intel_atomic_prepare_commit(state);
 	if (ret) {
 		drm_dbg_atomic(&dev_priv->drm,
diff --git a/drivers/gpu/drm/imx/ipuv3/ipuv3-crtc.c b/drivers/gpu/drm/imx/ipuv3/ipuv3-crtc.c
index 99db53e167bd..43ee5f55d69b 100644
--- a/drivers/gpu/drm/imx/ipuv3/ipuv3-crtc.c
+++ b/drivers/gpu/drm/imx/ipuv3/ipuv3-crtc.c
@@ -230,7 +230,7 @@ static int ipu_crtc_atomic_check(struct drm_crtc *crtc,
 {
 	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
 									  crtc);
-	u32 primary_plane_mask = drm_plane_mask(crtc->primary);
+	u64 primary_plane_mask = drm_plane_mask(crtc->primary);
 
 	if (crtc_state->active && (primary_plane_mask & crtc_state->plane_mask) == 0)
 		return -EINVAL;
diff --git a/drivers/gpu/drm/msm/msm_atomic.c b/drivers/gpu/drm/msm/msm_atomic.c
index 9c45d641b521..5c8e5611304f 100644
--- a/drivers/gpu/drm/msm/msm_atomic.c
+++ b/drivers/gpu/drm/msm/msm_atomic.c
@@ -242,6 +242,8 @@ void msm_atomic_commit_tail(struct drm_atomic_state *state)
 		/* async updates are limited to single-crtc updates: */
 		WARN_ON(crtc_mask != drm_crtc_mask(async_crtc));
 
+		complete_all(&async_crtc->state->commit->flip_done);
+
 		/*
 		 * Start timer if we don't already have an update pending
 		 * on this crtc:
diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig
index d3a9a9fafe4e..c093242c26a7 100644
--- a/drivers/gpu/drm/panel/Kconfig
+++ b/drivers/gpu/drm/panel/Kconfig
@@ -185,6 +185,15 @@ config DRM_PANEL_HIMAX_HX8394
 
 	  If M is selected the module will be called panel-himax-hx8394.
 
+config DRM_PANEL_ILITEK_IL79600A
+	tristate "Ilitek ILI79960A-based panels"
+	depends on OF
+	depends on DRM_MIPI_DSI
+	depends on BACKLIGHT_CLASS_DEVICE
+	help
+	  Say Y if you want to enable support for panels based on the
+	  Ilitek ILI79960A controller.
+
 config DRM_PANEL_ILITEK_IL9322
 	tristate "Ilitek ILI9322 320x240 QVGA panels"
 	depends on OF && SPI
@@ -223,6 +232,17 @@ config DRM_PANEL_ILITEK_ILI9806E
 	  Say Y if you want to enable support for panels based on the
 	  Ilitek ILI9806E controller.
 
+config DRM_PANEL_ILITEK_ILI9806E_SPI
+	tristate "Ilitek ILI9806E-based panels on SPI/DPI"
+	depends on OF && SPI
+	select DRM_KMS_HELPER
+	depends on BACKLIGHT_CLASS_DEVICE
+	select DRM_MIPI_DBI
+	help
+	  Say Y if you want to enable support for panels based on the
+	  Ilitek ILI9806e controller using DPI for video and SPI for
+	  configuration.
+
 config DRM_PANEL_ILITEK_ILI9881C
 	tristate "Ilitek ILI9881C-based panels"
 	depends on OF
@@ -909,6 +929,17 @@ config DRM_PANEL_TDO_TL070WSH30
 	  24 bit RGB per pixel. It provides a MIPI DSI interface to
 	  the host, a built-in LED backlight and touch controller.
 
+config DRM_PANEL_TPO_Y17P
+	tristate "TDO Y17P-based panels"
+	depends on OF && SPI
+	select DRM_KMS_HELPER
+	depends on DRM_GEM_DMA_HELPER
+	depends on BACKLIGHT_CLASS_DEVICE
+	select DRM_MIPI_DBI
+	help
+	  Say Y if you want to enable support for panels based on the
+	  TDO Y17P controller.
+
 config DRM_PANEL_TPO_TD028TTEC1
 	tristate "Toppoly (TPO) TD028TTEC1 panel driver"
 	depends on OF && SPI
@@ -969,6 +1000,26 @@ config DRM_PANEL_VISIONOX_VTDR6130
 	  Say Y here if you want to enable support for Visionox
 	  VTDR6130 1080x2400 AMOLED DSI panel.
 
+config DRM_PANEL_WAVESHARE_TOUCHSCREEN
+	tristate "Waveshare touchscreen panels"
+	depends on DRM_MIPI_DSI
+	depends on I2C
+	depends on BACKLIGHT_CLASS_DEVICE
+	help
+	  Say Y here if you want to enable support for the Waveshare
+	  DSI Touchscreens.  To compile this driver as a module,
+	  choose M here.
+
+config DRM_PANEL_WAVESHARE_TOUCHSCREEN_V2
+	tristate "Waveshare touchscreen panels V2"
+	depends on DRM_MIPI_DSI
+	depends on I2C
+	depends on BACKLIGHT_CLASS_DEVICE
+	help
+	  Say Y here if you want to enable support for the Waveshare
+	  DSI Touchscreens.  To compile this driver as a module,
+	  choose M here.
+
 config DRM_PANEL_WIDECHIPS_WS2401
 	tristate "Widechips WS2401 DPI panel driver"
 	depends on SPI && GPIOLIB
diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile
index 987a08702410..5760902b7a55 100644
--- a/drivers/gpu/drm/panel/Makefile
+++ b/drivers/gpu/drm/panel/Makefile
@@ -19,10 +19,12 @@ obj-$(CONFIG_DRM_PANEL_FEIYANG_FY07024DI26A30D) += panel-feiyang-fy07024di26a30d
 obj-$(CONFIG_DRM_PANEL_HIMAX_HX83102) += panel-himax-hx83102.o
 obj-$(CONFIG_DRM_PANEL_HIMAX_HX83112A) += panel-himax-hx83112a.o
 obj-$(CONFIG_DRM_PANEL_HIMAX_HX8394) += panel-himax-hx8394.o
+obj-$(CONFIG_DRM_PANEL_ILITEK_IL79600A) += panel-ilitek-ili79600a.o
 obj-$(CONFIG_DRM_PANEL_ILITEK_IL9322) += panel-ilitek-ili9322.o
 obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9341) += panel-ilitek-ili9341.o
 obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9805) += panel-ilitek-ili9805.o
 obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9806E) += panel-ilitek-ili9806e.o
+obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9806E_SPI) += panel-ilitek-ili9806e-spi.o
 obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9881C) += panel-ilitek-ili9881c.o
 obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9882T) += panel-ilitek-ili9882t.o
 obj-$(CONFIG_DRM_PANEL_INNOLUX_EJ030NA) += panel-innolux-ej030na.o
@@ -91,6 +93,7 @@ obj-$(CONFIG_DRM_PANEL_SONY_TD4353_JDI) += panel-sony-td4353-jdi.o
 obj-$(CONFIG_DRM_PANEL_SONY_TULIP_TRULY_NT35521) += panel-sony-tulip-truly-nt35521.o
 obj-$(CONFIG_DRM_PANEL_STARTEK_KD070FHFID015) += panel-startek-kd070fhfid015.o
 obj-$(CONFIG_DRM_PANEL_TDO_TL070WSH30) += panel-tdo-tl070wsh30.o
+obj-$(CONFIG_DRM_PANEL_TPO_Y17P) += panel-tdo-y17p.o
 obj-$(CONFIG_DRM_PANEL_TPO_TD028TTEC1) += panel-tpo-td028ttec1.o
 obj-$(CONFIG_DRM_PANEL_TPO_TD043MTEA1) += panel-tpo-td043mtea1.o
 obj-$(CONFIG_DRM_PANEL_TPO_TPG110) += panel-tpo-tpg110.o
@@ -98,5 +101,7 @@ obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o
 obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o
 obj-$(CONFIG_DRM_PANEL_VISIONOX_VTDR6130) += panel-visionox-vtdr6130.o
 obj-$(CONFIG_DRM_PANEL_VISIONOX_R66451) += panel-visionox-r66451.o
+obj-$(CONFIG_DRM_PANEL_WAVESHARE_TOUCHSCREEN) += panel-waveshare-dsi.o
+obj-$(CONFIG_DRM_PANEL_WAVESHARE_TOUCHSCREEN_V2) += panel-waveshare-dsi-v2.o
 obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o
 obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) += panel-xinpeng-xpp055c272.o
diff --git a/drivers/gpu/drm/panel/panel-ilitek-ili79600a.c b/drivers/gpu/drm/panel/panel-ilitek-ili79600a.c
new file mode 100644
index 000000000000..b91ba5511284
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-ilitek-ili79600a.c
@@ -0,0 +1,467 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2025, Raspberry Pi
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+
+#include <linux/gpio/consumer.h>
+#include <linux/regulator/consumer.h>
+
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+#include <video/mipi_display.h>
+
+struct ili79600a;
+
+struct ili79600a_desc {
+	int (*init)(struct ili79600a *ili);
+	const struct drm_display_mode *mode;
+	const unsigned long mode_flags;
+	unsigned int lanes;
+};
+
+struct ili79600a {
+	struct drm_panel	panel;
+	struct mipi_dsi_device	*dsi;
+	const struct ili79600a_desc	*desc;
+
+	struct regulator	*power;
+	struct gpio_desc	*reset;
+
+	enum drm_panel_orientation	orientation;
+};
+
+static inline struct ili79600a *panel_to_ili79600a(struct drm_panel *panel)
+{
+	return container_of(panel, struct ili79600a, panel);
+}
+
+/*
+ * The panel seems to accept some private DCS commands that map
+ * directly to registers.
+ *
+ * It is organised by page, with each page having its own set of
+ * registers, and the first page looks like it's holding the standard
+ * DCS commands.
+ *
+ * So before any attempt at sending a command or data, we have to be
+ * sure if we're in the right page or not.
+ */
+#define ILI79600A_DCS_SWITCH_PAGE	0xFF
+#define ili79600a_switch_page(ctx, page) \
+	mipi_dsi_dcs_write_seq_multi(ctx, ILI79600A_DCS_SWITCH_PAGE, \
+				     0x5A, 0xA5, (page))
+
+static int rpi_10_1_inch_init(struct ili79600a *ili)
+{
+	struct mipi_dsi_multi_context ctx = { .dsi = ili->dsi };
+
+	ili79600a_switch_page(&ctx, 0x01);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x00, 0x42);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x01, 0x11);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x02, 0x00);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x03, 0x00);
+
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x04, 0x01);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x05, 0x11);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x06, 0x00);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x07, 0x00);
+
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x08, 0x80);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x09, 0x01);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x0a, 0x71);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x0c, 0x00);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x0e, 0x00);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x0d, 0x00);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x0f, 0x00);
+
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x30, 0x07);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x31, 0x07);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x32, 0x07);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x33, 0x07);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x34, 0x07);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x35, 0x01);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x36, 0x00);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x37, 0x28);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x38, 0x29);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x39, 0x11);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x3A, 0x13);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x3B, 0x15);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x3C, 0x17);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x3D, 0x09);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x3E, 0x0D);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x3F, 0x02);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x40, 0x02);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x41, 0x02);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x42, 0x02);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x43, 0x02);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x44, 0x02);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x45, 0x02);
+
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x48, 0x07);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x49, 0x07);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x4A, 0x07);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x4B, 0x07);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x4C, 0x07);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x4D, 0x01);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x4E, 0x00);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x4F, 0x28);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x50, 0x29);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x51, 0x10);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x52, 0x12);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x53, 0x14);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x54, 0x16);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x55, 0x08);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x56, 0x0C);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x57, 0x02);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x58, 0x02);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x59, 0x02);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x5A, 0x02);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x5B, 0x02);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x5C, 0x02);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x5D, 0x02);
+
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x2C, 0xB0);
+
+	ili79600a_switch_page(&ctx, 0x02);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x07, 0x00);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x06, 0xD2);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x53, 0x1F);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x5E, 0x40);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x84, 0x00);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x29, 0x3A);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x2A, 0x3B);
+
+	ili79600a_switch_page(&ctx, 0x03);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x20, 0x01);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x21, 0x3C);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x22, 0xFA);
+
+	ili79600a_switch_page(&ctx, 0x12);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x87, 0x2C);
+
+	ili79600a_switch_page(&ctx, 0x05);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x61, 0x8F);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x6D, 0xBB);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x5A, 0x7C);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x67, 0x7C);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x54, 0x92);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x55, 0x92);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x40, 0xD2);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x46, 0x70);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x30, 0x47);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x7A, 0x89);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0xA4, 0x0B);
+
+	ili79600a_switch_page(&ctx, 0x06);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0xC0, 0x80);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0xC1, 0x7);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0xCA, 0x58);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0xCB, 0x2);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0xCE, 0x58);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0xCF, 0x2);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x10, 0x0);
+
+	ili79600a_switch_page(&ctx, 0x0b);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x76, 0x55);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x78, 0x68);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x77, 0x0D);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x79, 0x60);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x7A, 0x10);
+
+	ili79600a_switch_page(&ctx, 0x08);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0xE0, 0x00, 0x1E, 0x3F, 0x68, 0x7D,
+				     0x72, 0x86, 0x89, 0x92, 0x8E, 0xA5, 0xA8,
+				     0xAA, 0xBD, 0xB8, 0xBE, 0xC5, 0xD6, 0xCD,
+				     0xC3, 0xDA, 0xEB, 0xED);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0xE1, 0x00, 0x1E, 0x3F, 0x68, 0x7D,
+				     0x72, 0x86, 0x89, 0x92, 0x8E, 0xA5, 0xA8,
+				     0xAA, 0xBD, 0xB8, 0xBE, 0xC5, 0xD6, 0xCD,
+				     0xC3, 0xDA, 0xEB, 0xED);
+
+	ili79600a_switch_page(&ctx, 0x14);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x85, 0xCC);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x87, 0x91);
+
+	ili79600a_switch_page(&ctx, 0x10);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x11, 0x3);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x12, 0x34);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x10, 0x1);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x13, 0x4);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x14, 0xBB);
+
+	ili79600a_switch_page(&ctx, 0x0b);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x94, 0x88);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x95, 0x23);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x96, 0x0B);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x97, 0x0B);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x98, 0xC6);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x99, 0xC6);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0xA6, 0x47);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0xA7, 0x77);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0xA8, 0x5);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0xA9, 0x5);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0xAA, 0xBA);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0xAB, 0xBA);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0xBD, 0x92);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0xBE, 0xE0);
+
+	ili79600a_switch_page(&ctx, 0x0e);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x0, 0x80);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x14, 0x3);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x12, 0x0);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x13, 0x4);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x11, 0x1);
+
+	ili79600a_switch_page(&ctx, 0x02);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x80, 0x11);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x82, 0x11);
+	ili79600a_switch_page(&ctx, 0x12);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x82, 0x2C);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x85, 0x2C);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x87, 0x2C);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x88, 0xEC);
+	ili79600a_switch_page(&ctx, 0x10);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x4, 0x1);
+	ili79600a_switch_page(&ctx, 0x11);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x0C, 0x80);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x3C, 0x80);
+
+	ili79600a_switch_page(&ctx, 0x06);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x0E, 0x0);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x0E, 0x3F);
+	mipi_dsi_dcs_write_seq_multi(&ctx, 0x10, 0x0);
+
+	ili79600a_switch_page(&ctx, 0x00);
+	mipi_dsi_dcs_write_seq_multi(&ctx, MIPI_DCS_SET_TEAR_ON, 0x0);
+
+	return ctx.accum_err;
+};
+
+static int ili79600a_prepare(struct drm_panel *panel)
+{
+	struct ili79600a *ili = panel_to_ili79600a(panel);
+	struct mipi_dsi_multi_context ctx = { .dsi = ili->dsi };
+	ktime_t reset_time, end;
+	s64 remaining;
+	int ret;
+
+	/* Power the panel */
+	ret = regulator_enable(ili->power);
+	if (ret)
+		return ret;
+	fsleep(20);
+
+	/* And reset it */
+	gpiod_set_value_cansleep(ili->reset, 1);
+	fsleep(20);
+
+	gpiod_set_value_cansleep(ili->reset, 0);
+	fsleep(20);
+
+	/* Software reset */
+	ili79600a_switch_page(&ctx, 0x00);
+
+	reset_time = ktime_get();
+	mipi_dsi_dcs_write(ili->dsi, MIPI_DCS_SOFT_RESET, NULL, 0);
+	/* Must wait 5ms from releasing RESX or s/w reset) before sending other
+	 * commands.
+	 */
+	fsleep(5);
+
+	ret = ili->desc->init(ili);
+	if (ret < 0)
+		return ret;
+
+	/* Minimum 120ms between exiting reset and EXIT_SLEEP_MODE */
+	end = ktime_add_us(reset_time, 120000);
+	remaining = ktime_us_delta(end, ktime_get());
+
+	if (remaining > 0)
+		usleep_range(remaining, remaining + 1000);
+
+	mipi_dsi_dcs_write_seq_multi(&ctx, MIPI_DCS_EXIT_SLEEP_MODE, 0x0);
+	mipi_dsi_msleep(&ctx, 100);
+
+	if (ctx.accum_err)
+		return ctx.accum_err;
+
+	ret = mipi_dsi_dcs_set_display_on(ili->dsi);
+
+	return ret;
+}
+
+static int ili79600a_unprepare(struct drm_panel *panel)
+{
+	struct ili79600a *ctx = panel_to_ili79600a(panel);
+
+	mipi_dsi_dcs_set_display_off(ctx->dsi);
+	mipi_dsi_dcs_enter_sleep_mode(ctx->dsi);
+
+	regulator_disable(ctx->power);
+	gpiod_set_value_cansleep(ctx->reset, 1);
+
+	return 0;
+}
+
+static const struct drm_display_mode rpi_10_1_inch_default_mode = {
+	.clock = 166666,
+
+	.hdisplay	= 1200,
+	.hsync_start	= 1200 + 28,
+	.hsync_end	= 1200 + 28 + 10,
+	.htotal		= 1200 + 28 + 10 + 30,
+
+	.vdisplay	= 1920,
+	.vsync_start	= 1920 + 238,
+	.vsync_end	= 1920 + 238 + 4,
+	.vtotal		= 1920 + 238 + 4 + 28,
+
+	.width_mm	= 135,
+	.height_mm	= 216,
+};
+
+static int ili79600a_get_modes(struct drm_panel *panel,
+			       struct drm_connector *connector)
+{
+	struct ili79600a *ctx = panel_to_ili79600a(panel);
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_duplicate(connector->dev, ctx->desc->mode);
+	if (!mode) {
+		dev_err(&ctx->dsi->dev, "failed to add mode %ux%ux@%u\n",
+			ctx->desc->mode->hdisplay,
+			ctx->desc->mode->vdisplay,
+			drm_mode_vrefresh(ctx->desc->mode));
+		return -ENOMEM;
+	}
+
+	drm_mode_set_name(mode);
+
+	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+	drm_mode_probed_add(connector, mode);
+
+	connector->display_info.width_mm = mode->width_mm;
+	connector->display_info.height_mm = mode->height_mm;
+
+	/*
+	 * TODO: Remove once all drm drivers call
+	 * drm_connector_set_orientation_from_panel()
+	 */
+	drm_connector_set_panel_orientation(connector, ctx->orientation);
+
+	return 1;
+}
+
+static enum drm_panel_orientation ili79600a_get_orientation(struct drm_panel *panel)
+{
+	struct ili79600a *ctx = panel_to_ili79600a(panel);
+
+	return ctx->orientation;
+}
+
+static const struct drm_panel_funcs ili79600a_funcs = {
+	.prepare	= ili79600a_prepare,
+	.unprepare	= ili79600a_unprepare,
+	.get_modes	= ili79600a_get_modes,
+	.get_orientation = ili79600a_get_orientation,
+};
+
+static int ili79600a_dsi_probe(struct mipi_dsi_device *dsi)
+{
+	struct ili79600a *ctx;
+	int ret;
+
+	ctx = devm_kzalloc(&dsi->dev, sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+	mipi_dsi_set_drvdata(dsi, ctx);
+	ctx->dsi = dsi;
+	ctx->desc = of_device_get_match_data(&dsi->dev);
+
+	ctx->panel.prepare_prev_first = true;
+	drm_panel_init(&ctx->panel, &dsi->dev, &ili79600a_funcs,
+		       DRM_MODE_CONNECTOR_DSI);
+
+	ctx->power = devm_regulator_get(&dsi->dev, "power");
+	if (IS_ERR(ctx->power))
+		return dev_err_probe(&dsi->dev, PTR_ERR(ctx->power),
+				     "Couldn't get our power regulator\n");
+
+	ctx->reset = devm_gpiod_get_optional(&dsi->dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(ctx->reset))
+		return dev_err_probe(&dsi->dev, PTR_ERR(ctx->reset),
+				     "Couldn't get our reset GPIO\n");
+
+	ret = of_drm_get_panel_orientation(dsi->dev.of_node, &ctx->orientation);
+	if (ret) {
+		dev_err(&dsi->dev, "%pOF: failed to get orientation: %d\n",
+			dsi->dev.of_node, ret);
+		return ret;
+	}
+
+	ctx->panel.prepare_prev_first = true;
+
+	ret = drm_panel_of_backlight(&ctx->panel);
+	if (ret)
+		return ret;
+
+	drm_panel_add(&ctx->panel);
+
+	dsi->mode_flags = ctx->desc->mode_flags;
+	dsi->format = MIPI_DSI_FMT_RGB888;
+	dsi->lanes = ctx->desc->lanes;
+
+	ret = mipi_dsi_attach(dsi);
+	if (ret)
+		drm_panel_remove(&ctx->panel);
+
+	return ret;
+}
+
+static void ili79600a_dsi_remove(struct mipi_dsi_device *dsi)
+{
+	struct ili79600a *ctx = mipi_dsi_get_drvdata(dsi);
+
+	mipi_dsi_detach(dsi);
+	drm_panel_remove(&ctx->panel);
+
+	gpiod_set_value_cansleep(ctx->reset, 1);
+	regulator_disable(ctx->power);
+}
+
+static const struct ili79600a_desc rpi_10_1_inch_desc = {
+	.init = rpi_10_1_inch_init,
+	.mode = &rpi_10_1_inch_default_mode,
+	.mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE,
+	.lanes = 4,
+};
+
+static const struct of_device_id ili79600a_of_match[] = {
+	{ .compatible = "raspberrypi,dsi-10-1inch", &rpi_10_1_inch_desc },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ili79600a_of_match);
+
+static struct mipi_dsi_driver ili79600a_dsi_driver = {
+	.probe	= ili79600a_dsi_probe,
+	.remove	= ili79600a_dsi_remove,
+	.driver = {
+		.name		= "ili79600a-dsi",
+		.of_match_table	= ili79600a_of_match,
+	},
+};
+module_mipi_dsi_driver(ili79600a_dsi_driver);
+
+MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>");
+MODULE_DESCRIPTION("Ilitek ILI79600a Controller Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/panel/panel-ilitek-ili9806e-spi.c b/drivers/gpu/drm/panel/panel-ilitek-ili9806e-spi.c
new file mode 100644
index 000000000000..dd5fbd23ba26
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-ilitek-ili9806e-spi.c
@@ -0,0 +1,479 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Ilitek ILI9806E TFT LCD drm_panel driver.
+ *
+ * Copyright (C) 2022 Raspberry Pi Ltd
+ *
+ * Derived from drivers/drm/gpu/panel/panel-sitronix-st7789v.c
+ * Copyright (C) 2017 Free Electrons
+ */
+
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+#include <linux/bitops.h>
+#include <linux/gpio/consumer.h>
+#include <linux/media-bus-format.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+
+#include <video/mipi_display.h>
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+struct ili9806 {
+	struct drm_panel panel;
+	struct spi_device *spi;
+	struct gpio_desc *reset;
+	struct regulator *power;
+	u32 bus_format;
+};
+
+#define ILI9806_DATA		BIT(8)
+
+#define ILI9806_MAX_MSG_LEN	6
+
+struct ili9806e_msg {
+	unsigned int len;
+	u16 msg[ILI9806_MAX_MSG_LEN];
+};
+
+#define ILI9806_SET_PAGE(page)	\
+	{				\
+		.len = 6,		\
+		.msg = {		\
+			0xFF,			\
+			ILI9806_DATA | 0xFF,	\
+			ILI9806_DATA | 0x98,	\
+			ILI9806_DATA | 0x06,	\
+			ILI9806_DATA | 0x04,	\
+			ILI9806_DATA | (page)	\
+		},				\
+	}
+
+#define ILI9806_SET_REG_PARAM(reg, data)	\
+	{					\
+		.len = 2,			\
+		.msg = {			\
+			(reg),			\
+			ILI9806_DATA | (data),	\
+		},				\
+	}
+
+#define ILI9806_SET_REG(reg)	\
+	{				\
+		.len = 1,		\
+		.msg = { (reg) },		\
+	}
+
+static const struct ili9806e_msg panel_init[] = {
+	ILI9806_SET_PAGE(1),
+
+	/* interface mode
+	 *   SEPT_SDIO = 0 (spi interface transfer through SDA pin)
+	 *   SDO_STATUS = 1 (always output, but without output tri-state)
+	 */
+	ILI9806_SET_REG_PARAM(0x08, 0x10),
+	/* display control
+	 * VSPL = 1 (vertical sync polarity)
+	 * HSPL = 0 (horizontal sync polarity)
+	 * DPL = 0 (PCLK polarity)
+	 * EPL = 1 (data enable polarity)
+	 */
+	ILI9806_SET_REG_PARAM(0x21, 0x0d),
+	/* resolution control (0x02 = 480x800) */
+	ILI9806_SET_REG_PARAM(0x30, 0x02),
+	/* display inversion control (0x00 = column inversion) */
+	ILI9806_SET_REG_PARAM(0x31, 0x00),
+	/* power control
+	 *  EXB1T = 0 (internal charge pump)
+	 *  EXT_CPCK_SEL = 1 (pump clock control signal = output 2 x waveform)
+	 *  BT = 0 (DDVDH / DDVDL voltage = VCI x 2 / VCI x -2)
+	 */
+	ILI9806_SET_REG_PARAM(0x40, 0x10),
+	/* power control
+	 *  DDVDH_CLP = 5.6 (DDVDH clamp leve)
+	 *  DDVDL_CLP = -5.6 (DDVDL clamp leve)
+	 */
+	ILI9806_SET_REG_PARAM(0x41, 0x55),
+	/* power control
+	 *  VGH_CP = 2DDVDH - DDVDL (step up factor for VGH)
+	 *  VGL_CP = DDVDL + VCL - VCIP (step up factor for VGL)
+	 */
+	ILI9806_SET_REG_PARAM(0x42, 0x02),
+	/* power control
+	 *  VGH_CLPEN = 0 (disable VGH clamp level)
+	 *  VGH_CLP = 9 (15.0 VGH clamp level - but this is disabled so not used?)
+	 */
+	ILI9806_SET_REG_PARAM(0x43, 0x84),
+	/* power control
+	 *  VGL_CLPEN = 0 (disable VGL clamp level)
+	 *  VGL_CLP = 9 (-11.0 VGL clamp level - but this is disabled so not used?)
+	 */
+	ILI9806_SET_REG_PARAM(0x44, 0x84),
+
+	/* power control
+	 *  VREG1OUT voltage for positive gamma?
+	 */
+	ILI9806_SET_REG_PARAM(0x50, 0x78),
+	/* power control
+	 *  VREG2OUT voltage for negative gamma?
+	 */
+	ILI9806_SET_REG_PARAM(0x51, 0x78),
+
+	ILI9806_SET_REG_PARAM(0x52, 0x00),
+	ILI9806_SET_REG_PARAM(0x53, 0x77),
+	ILI9806_SET_REG_PARAM(0x57, 0x60),
+	ILI9806_SET_REG_PARAM(0x60, 0x07),
+	ILI9806_SET_REG_PARAM(0x61, 0x00),
+	ILI9806_SET_REG_PARAM(0x62, 0x08),
+	ILI9806_SET_REG_PARAM(0x63, 0x00),
+	ILI9806_SET_REG_PARAM(0xA0, 0x00),
+	ILI9806_SET_REG_PARAM(0xA1, 0x07),
+	ILI9806_SET_REG_PARAM(0xA2, 0x0C),
+	ILI9806_SET_REG_PARAM(0xA3, 0x0B),
+	ILI9806_SET_REG_PARAM(0xA4, 0x03),
+	ILI9806_SET_REG_PARAM(0xA5, 0x07),
+	ILI9806_SET_REG_PARAM(0xA6, 0x06),
+	ILI9806_SET_REG_PARAM(0xA7, 0x04),
+	ILI9806_SET_REG_PARAM(0xA8, 0x08),
+	ILI9806_SET_REG_PARAM(0xA9, 0x0C),
+	ILI9806_SET_REG_PARAM(0xAA, 0x13),
+	ILI9806_SET_REG_PARAM(0xAB, 0x06),
+	ILI9806_SET_REG_PARAM(0xAC, 0x0D),
+	ILI9806_SET_REG_PARAM(0xAD, 0x19),
+	ILI9806_SET_REG_PARAM(0xAE, 0x10),
+	ILI9806_SET_REG_PARAM(0xAF, 0x00),
+	/* negative gamma control
+	 * set the gray scale voltage to adjust the gamma characteristics of the panel
+	 */
+	ILI9806_SET_REG_PARAM(0xC0, 0x00),
+	ILI9806_SET_REG_PARAM(0xC1, 0x07),
+	ILI9806_SET_REG_PARAM(0xC2, 0x0C),
+	ILI9806_SET_REG_PARAM(0xC3, 0x0B),
+	ILI9806_SET_REG_PARAM(0xC4, 0x03),
+	ILI9806_SET_REG_PARAM(0xC5, 0x07),
+	ILI9806_SET_REG_PARAM(0xC6, 0x07),
+	ILI9806_SET_REG_PARAM(0xC7, 0x04),
+	ILI9806_SET_REG_PARAM(0xC8, 0x08),
+	ILI9806_SET_REG_PARAM(0xC9, 0x0C),
+	ILI9806_SET_REG_PARAM(0xCA, 0x13),
+	ILI9806_SET_REG_PARAM(0xCB, 0x06),
+	ILI9806_SET_REG_PARAM(0xCC, 0x0D),
+	ILI9806_SET_REG_PARAM(0xCD, 0x18),
+	ILI9806_SET_REG_PARAM(0xCE, 0x10),
+	ILI9806_SET_REG_PARAM(0xCF, 0x00),
+
+	ILI9806_SET_PAGE(6),
+
+	ILI9806_SET_REG_PARAM(0x00, 0x20),
+	ILI9806_SET_REG_PARAM(0x01, 0x0A),
+	ILI9806_SET_REG_PARAM(0x02, 0x00),
+	ILI9806_SET_REG_PARAM(0x03, 0x00),
+	ILI9806_SET_REG_PARAM(0x04, 0x01),
+	ILI9806_SET_REG_PARAM(0x05, 0x01),
+	ILI9806_SET_REG_PARAM(0x06, 0x98),
+	ILI9806_SET_REG_PARAM(0x07, 0x06),
+	ILI9806_SET_REG_PARAM(0x08, 0x01),
+	ILI9806_SET_REG_PARAM(0x09, 0x80),
+	ILI9806_SET_REG_PARAM(0x0A, 0x00),
+	ILI9806_SET_REG_PARAM(0x0B, 0x00),
+	ILI9806_SET_REG_PARAM(0x0C, 0x01),
+	ILI9806_SET_REG_PARAM(0x0D, 0x01),
+	ILI9806_SET_REG_PARAM(0x0E, 0x00),
+	ILI9806_SET_REG_PARAM(0x0F, 0x00),
+	ILI9806_SET_REG_PARAM(0x10, 0xF0),
+	ILI9806_SET_REG_PARAM(0x11, 0xF4),
+	ILI9806_SET_REG_PARAM(0x12, 0x01),
+	ILI9806_SET_REG_PARAM(0x13, 0x00),
+	ILI9806_SET_REG_PARAM(0x14, 0x00),
+	ILI9806_SET_REG_PARAM(0x15, 0xC0),
+	ILI9806_SET_REG_PARAM(0x16, 0x08),
+	ILI9806_SET_REG_PARAM(0x17, 0x00),
+	ILI9806_SET_REG_PARAM(0x18, 0x00),
+	ILI9806_SET_REG_PARAM(0x19, 0x00),
+	ILI9806_SET_REG_PARAM(0x1A, 0x00),
+	ILI9806_SET_REG_PARAM(0x1B, 0x00),
+	ILI9806_SET_REG_PARAM(0x1C, 0x00),
+	ILI9806_SET_REG_PARAM(0x1D, 0x00),
+	ILI9806_SET_REG_PARAM(0x20, 0x01),
+	ILI9806_SET_REG_PARAM(0x21, 0x23),
+	ILI9806_SET_REG_PARAM(0x22, 0x45),
+	ILI9806_SET_REG_PARAM(0x23, 0x67),
+	ILI9806_SET_REG_PARAM(0x24, 0x01),
+	ILI9806_SET_REG_PARAM(0x25, 0x23),
+	ILI9806_SET_REG_PARAM(0x26, 0x45),
+	ILI9806_SET_REG_PARAM(0x27, 0x67),
+	ILI9806_SET_REG_PARAM(0x30, 0x11),
+	ILI9806_SET_REG_PARAM(0x31, 0x11),
+	ILI9806_SET_REG_PARAM(0x32, 0x00),
+	ILI9806_SET_REG_PARAM(0x33, 0xEE),
+	ILI9806_SET_REG_PARAM(0x34, 0xFF),
+	ILI9806_SET_REG_PARAM(0x35, 0xBB),
+	ILI9806_SET_REG_PARAM(0x36, 0xAA),
+	ILI9806_SET_REG_PARAM(0x37, 0xDD),
+	ILI9806_SET_REG_PARAM(0x38, 0xCC),
+	ILI9806_SET_REG_PARAM(0x39, 0x66),
+	ILI9806_SET_REG_PARAM(0x3A, 0x77),
+	ILI9806_SET_REG_PARAM(0x3B, 0x22),
+	ILI9806_SET_REG_PARAM(0x3C, 0x22),
+	ILI9806_SET_REG_PARAM(0x3D, 0x22),
+	ILI9806_SET_REG_PARAM(0x3E, 0x22),
+	ILI9806_SET_REG_PARAM(0x3F, 0x22),
+	ILI9806_SET_REG_PARAM(0x40, 0x22),
+	/* register doesn't exist on page 6? */
+	ILI9806_SET_REG_PARAM(0x52, 0x10),
+	/* doesn't make sense, not valid according to datasheet */
+	ILI9806_SET_REG_PARAM(0x53, 0x10),
+	/* doesn't make sense, not valid according to datasheet */
+	ILI9806_SET_REG_PARAM(0x54, 0x13),
+
+	ILI9806_SET_PAGE(7),
+
+	/* enable VREG */
+	ILI9806_SET_REG_PARAM(0x18, 0x1D),
+	/* enable VGL_REG */
+	ILI9806_SET_REG_PARAM(0x17, 0x22),
+	/* register doesn't exist on page 7? */
+	ILI9806_SET_REG_PARAM(0x02, 0x77),
+	/* register doesn't exist on page 7? */
+	ILI9806_SET_REG_PARAM(0x26, 0xB2),
+	/* register doesn't exist on page 7? */
+	ILI9806_SET_REG_PARAM(0xE1, 0x79),
+
+	ILI9806_SET_PAGE(0),
+
+	ILI9806_SET_REG_PARAM(MIPI_DCS_SET_PIXEL_FORMAT,
+			      MIPI_DCS_PIXEL_FMT_18BIT << 4),
+	ILI9806_SET_REG_PARAM(MIPI_DCS_SET_TEAR_ON, 0x00),
+	ILI9806_SET_REG(MIPI_DCS_EXIT_SLEEP_MODE),
+};
+
+#define NUM_INIT_REGS ARRAY_SIZE(panel_init)
+
+static inline struct ili9806 *panel_to_ili9806(struct drm_panel *panel)
+{
+	return container_of(panel, struct ili9806, panel);
+}
+
+static int ili9806_write_msg(struct ili9806 *ctx, const struct ili9806e_msg *msg)
+{
+	struct spi_transfer xfer = { };
+	struct spi_message spi;
+	//u16 txbuf[] = { msg->, ILI9806_DATA | data };
+
+	spi_message_init(&spi);
+
+	xfer.tx_buf = msg->msg;
+	xfer.bits_per_word = 9;
+	xfer.len = sizeof(u16) * msg->len;
+
+	spi_message_add_tail(&xfer, &spi);
+	return spi_sync(ctx->spi, &spi);
+}
+
+static int ili9806e_write_msg_list(struct ili9806 *ctx,
+				   const struct ili9806e_msg msgs[],
+				   unsigned int num_msgs)
+{
+	int ret, i;
+
+	for (i = 0; i < num_msgs; i++) {
+		ret = ili9806_write_msg(ctx, &msgs[i]);
+		if (ret)
+			break;
+	}
+
+	return ret;
+}
+
+static const struct drm_display_mode ili9806e_480x800_mode = {
+	.clock = 32000,
+	.hdisplay = 480,
+	.hsync_start = 480 + 10,
+	.hsync_end = 480 + 10 + 16,
+	.htotal = 480 + 10 + 16 + 59,
+	.vdisplay = 800,
+	.vsync_start = 800 + 15,
+	.vsync_end = 800 + 15 + 113,
+	.vtotal = 800 + 15 + 113 + 15,
+	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+};
+
+static int ili9806_get_modes(struct drm_panel *panel,
+			     struct drm_connector *connector)
+{
+	struct ili9806 *ctx = panel_to_ili9806(panel);
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_duplicate(connector->dev, &ili9806e_480x800_mode);
+	if (!mode) {
+		dev_err(panel->dev, "failed to add mode %ux%ux@%u\n",
+			ili9806e_480x800_mode.hdisplay,
+			ili9806e_480x800_mode.vdisplay,
+			drm_mode_vrefresh(&ili9806e_480x800_mode));
+		return -ENOMEM;
+	}
+
+	drm_mode_set_name(mode);
+
+	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+	drm_mode_probed_add(connector, mode);
+
+	connector->display_info.width_mm = 61;
+	connector->display_info.height_mm = 103;
+	drm_display_info_set_bus_formats(&connector->display_info,
+					 &ctx->bus_format, 1);
+	connector->display_info.bus_flags =
+					DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE;
+
+	return 1;
+}
+
+static int ili9806_prepare(struct drm_panel *panel)
+{
+	struct ili9806 *ctx = panel_to_ili9806(panel);
+	int ret;
+
+	ret = regulator_enable(ctx->power);
+	if (ret)
+		return ret;
+
+	ret = ili9806e_write_msg_list(ctx, panel_init, NUM_INIT_REGS);
+
+	return ret;
+}
+
+static int ili9806_enable(struct drm_panel *panel)
+{
+	struct ili9806 *ctx = panel_to_ili9806(panel);
+	const struct ili9806e_msg msg = ILI9806_SET_REG(MIPI_DCS_SET_DISPLAY_ON);
+	int ret;
+
+	ret = ili9806_write_msg(ctx, &msg);
+
+	return ret;
+}
+
+static int ili9806_disable(struct drm_panel *panel)
+{
+	struct ili9806 *ctx = panel_to_ili9806(panel);
+	const struct ili9806e_msg msg = ILI9806_SET_REG(MIPI_DCS_SET_DISPLAY_OFF);
+	int ret;
+
+	ret = ili9806_write_msg(ctx, &msg);
+
+	return ret;
+}
+
+static int ili9806_unprepare(struct drm_panel *panel)
+{
+	struct ili9806 *ctx = panel_to_ili9806(panel);
+	const struct ili9806e_msg msg = ILI9806_SET_REG(MIPI_DCS_ENTER_SLEEP_MODE);
+	int ret;
+
+	ret = ili9806_write_msg(ctx, &msg);
+
+	return ret;
+}
+
+static const struct drm_panel_funcs ili9806_drm_funcs = {
+	.disable	= ili9806_disable,
+	.enable		= ili9806_enable,
+	.get_modes	= ili9806_get_modes,
+	.prepare	= ili9806_prepare,
+	.unprepare	= ili9806_unprepare,
+};
+
+static const struct of_device_id ili9806_of_match[] = {
+	{	.compatible = "txw,txw397017s2",
+		.data = (void *)MEDIA_BUS_FMT_RGB888_1X24,
+	}, {
+		.compatible = "pimoroni,hyperpixel4",
+		.data = (void *)MEDIA_BUS_FMT_RGB666_1X24_CPADHI,
+	}, {
+		.compatible = "ilitek,ili9806e",
+		.data = (void *)MEDIA_BUS_FMT_RGB888_1X24,
+	}, {
+		/* sentinel */
+	}
+};
+MODULE_DEVICE_TABLE(of, ili9806_of_match);
+
+static int ili9806_probe(struct spi_device *spi)
+{
+	const struct ili9806e_msg panel_reset[] = {
+		ILI9806_SET_PAGE(0),
+		ILI9806_SET_REG_PARAM(0x01, 0x00)
+	};
+	struct ili9806 *ctx;
+	int ret;
+
+	ctx = devm_kzalloc(&spi->dev, sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	ctx->bus_format = (u32)(uintptr_t)of_device_get_match_data(&spi->dev);
+
+	spi_set_drvdata(spi, ctx);
+	ctx->spi = spi;
+
+	drm_panel_init(&ctx->panel, &spi->dev, &ili9806_drm_funcs,
+		       DRM_MODE_CONNECTOR_DPI);
+
+	ctx->power = devm_regulator_get(&spi->dev, "power");
+	if (IS_ERR(ctx->power))
+		return PTR_ERR(ctx->power);
+
+	ctx->reset = devm_gpiod_get_optional(&spi->dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(ctx->reset)) {
+		dev_err(&spi->dev, "Couldn't get our reset line\n");
+		return PTR_ERR(ctx->reset);
+	}
+
+	/* Soft reset */
+	ili9806e_write_msg_list(ctx, panel_reset, ARRAY_SIZE(panel_reset));
+	msleep(200);
+
+	ret = drm_panel_of_backlight(&ctx->panel);
+	if (ret)
+		return ret;
+
+	drm_panel_add(&ctx->panel);
+
+	return 0;
+}
+
+static void ili9806_remove(struct spi_device *spi)
+{
+	struct ili9806 *ctx = spi_get_drvdata(spi);
+
+	drm_panel_remove(&ctx->panel);
+}
+
+static const struct spi_device_id ili9806_ids[] = {
+	{ "txw397017s2", 0 },
+	{ "ili9806e", 0 },
+	{ "hyperpixel4", 0 },
+	{ /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(spi, ili9806_ids);
+
+static struct spi_driver ili9806_driver = {
+	.probe = ili9806_probe,
+	.remove = ili9806_remove,
+	.driver = {
+		.name = "ili9806e",
+		.of_match_table = ili9806_of_match,
+	},
+	.id_table = ili9806_ids,
+};
+module_spi_driver(ili9806_driver);
+
+MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>");
+MODULE_DESCRIPTION("ili9806 LCD panel driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c b/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c
index 084c37fa7348..416ff6fe6d89 100644
--- a/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c
+++ b/drivers/gpu/drm/panel/panel-ilitek-ili9881c.c
@@ -1,6 +1,9 @@
 // SPDX-License-Identifier: GPL-2.0
 /*
  * Copyright (C) 2017-2018, Bootlin
+ * Copyright (C) 2021, Henson Li <henson@cutiepi.io>
+ * Copyright (C) 2021, Penk Chen <penk@cutiepi.io>
+ * Copyright (C) 2022, Mark Williams <mark@crystalfontz.com>
  */
 
 #include <linux/delay.h>
@@ -37,11 +40,19 @@ struct ili9881c_instr {
 	} arg;
 };
 
+enum ili9881_desc_flags {
+	ILI9881_FLAGS_NO_SHUTDOWN_CMDS = BIT(0),
+	ILI9881_FLAGS_PANEL_ON_IN_PREPARE = BIT(1),
+	ILI9881_FLAGS_MAX = BIT(31),
+};
+
 struct ili9881c_desc {
 	const struct ili9881c_instr *init;
 	const size_t init_length;
 	const struct drm_display_mode *mode;
 	const unsigned long mode_flags;
+	unsigned int lanes;
+	enum ili9881_desc_flags flags;
 };
 
 struct ili9881c {
@@ -1223,153 +1234,979 @@ static const struct ili9881c_instr am8001280g_init[] = {
 	ILI9881C_COMMAND_INSTR(MIPI_DCS_WRITE_POWER_SAVE, 0x00),
 };
 
-static inline struct ili9881c *panel_to_ili9881c(struct drm_panel *panel)
-{
-	return container_of(panel, struct ili9881c, panel);
-}
-
-/*
- * The panel seems to accept some private DCS commands that map
- * directly to registers.
- *
- * It is organised by page, with each page having its own set of
- * registers, and the first page looks like it's holding the standard
- * DCS commands.
- *
- * So before any attempt at sending a command or data, we have to be
- * sure if we're in the right page or not.
- */
-static int ili9881c_switch_page(struct ili9881c *ctx, u8 page)
-{
-	u8 buf[4] = { 0xff, 0x98, 0x81, page };
-	int ret;
-
-	ret = mipi_dsi_dcs_write_buffer(ctx->dsi, buf, sizeof(buf));
-	if (ret < 0)
-		return ret;
-
-	return 0;
-}
-
-static int ili9881c_send_cmd_data(struct ili9881c *ctx, u8 cmd, u8 data)
-{
-	u8 buf[2] = { cmd, data };
-	int ret;
-
-	ret = mipi_dsi_dcs_write_buffer(ctx->dsi, buf, sizeof(buf));
-	if (ret < 0)
-		return ret;
-
-	return 0;
-}
-
-static int ili9881c_prepare(struct drm_panel *panel)
-{
-	struct ili9881c *ctx = panel_to_ili9881c(panel);
-	unsigned int i;
-	int ret;
-
-	/* Power the panel */
-	ret = regulator_enable(ctx->power);
-	if (ret)
-		return ret;
-	msleep(5);
-
-	/* And reset it */
-	gpiod_set_value_cansleep(ctx->reset, 1);
-	msleep(20);
-
-	gpiod_set_value_cansleep(ctx->reset, 0);
-	msleep(20);
-
-	for (i = 0; i < ctx->desc->init_length; i++) {
-		const struct ili9881c_instr *instr = &ctx->desc->init[i];
-
-		if (instr->op == ILI9881C_SWITCH_PAGE)
-			ret = ili9881c_switch_page(ctx, instr->arg.page);
-		else if (instr->op == ILI9881C_COMMAND)
-			ret = ili9881c_send_cmd_data(ctx, instr->arg.cmd.cmd,
-						      instr->arg.cmd.data);
-
-		if (ret)
-			return ret;
-	}
-
-	ret = ili9881c_switch_page(ctx, 0);
-	if (ret)
-		return ret;
-
-	ret = mipi_dsi_dcs_set_tear_on(ctx->dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
-	if (ret)
-		return ret;
-
-	ret = mipi_dsi_dcs_exit_sleep_mode(ctx->dsi);
-	if (ret)
-		return ret;
-
-	return 0;
-}
-
-static int ili9881c_enable(struct drm_panel *panel)
-{
-	struct ili9881c *ctx = panel_to_ili9881c(panel);
+static const struct ili9881c_instr nwe080_init[] = {
+	ILI9881C_SWITCH_PAGE_INSTR(3),
+	//GIP_1
+	ILI9881C_COMMAND_INSTR(0x01, 0x00),
+	ILI9881C_COMMAND_INSTR(0x02, 0x00),
+	ILI9881C_COMMAND_INSTR(0x03, 0x73),
+	ILI9881C_COMMAND_INSTR(0x04, 0x00),
+	ILI9881C_COMMAND_INSTR(0x05, 0x00),
+	ILI9881C_COMMAND_INSTR(0x06, 0x0A),
+	ILI9881C_COMMAND_INSTR(0x07, 0x00),
+	ILI9881C_COMMAND_INSTR(0x08, 0x00),
+	ILI9881C_COMMAND_INSTR(0x09, 0x20),
+	ILI9881C_COMMAND_INSTR(0x0a, 0x20),
+	ILI9881C_COMMAND_INSTR(0x0b, 0x00),
+	ILI9881C_COMMAND_INSTR(0x0c, 0x00),
+	ILI9881C_COMMAND_INSTR(0x0d, 0x00),
+	ILI9881C_COMMAND_INSTR(0x0e, 0x00),
+	ILI9881C_COMMAND_INSTR(0x0f, 0x1E),
+	ILI9881C_COMMAND_INSTR(0x10, 0x1E),
+	ILI9881C_COMMAND_INSTR(0x11, 0x00),
+	ILI9881C_COMMAND_INSTR(0x12, 0x00),
+	ILI9881C_COMMAND_INSTR(0x13, 0x00),
+	ILI9881C_COMMAND_INSTR(0x14, 0x00),
+	ILI9881C_COMMAND_INSTR(0x15, 0x00),
+	ILI9881C_COMMAND_INSTR(0x16, 0x00),
+	ILI9881C_COMMAND_INSTR(0x17, 0x00),
+	ILI9881C_COMMAND_INSTR(0x18, 0x00),
+	ILI9881C_COMMAND_INSTR(0x19, 0x00),
+	ILI9881C_COMMAND_INSTR(0x1A, 0x00),
+	ILI9881C_COMMAND_INSTR(0x1B, 0x00),
+	ILI9881C_COMMAND_INSTR(0x1C, 0x00),
+	ILI9881C_COMMAND_INSTR(0x1D, 0x00),
+	ILI9881C_COMMAND_INSTR(0x1E, 0x40),
+	ILI9881C_COMMAND_INSTR(0x1F, 0x80),
+	ILI9881C_COMMAND_INSTR(0x20, 0x06),
+	ILI9881C_COMMAND_INSTR(0x21, 0x01),
+	ILI9881C_COMMAND_INSTR(0x22, 0x00),
+	ILI9881C_COMMAND_INSTR(0x23, 0x00),
+	ILI9881C_COMMAND_INSTR(0x24, 0x00),
+	ILI9881C_COMMAND_INSTR(0x25, 0x00),
+	ILI9881C_COMMAND_INSTR(0x26, 0x00),
+	ILI9881C_COMMAND_INSTR(0x27, 0x00),
+	ILI9881C_COMMAND_INSTR(0x28, 0x33),
+	ILI9881C_COMMAND_INSTR(0x29, 0x03),
+	ILI9881C_COMMAND_INSTR(0x2A, 0x00),
+	ILI9881C_COMMAND_INSTR(0x2B, 0x00),
+	ILI9881C_COMMAND_INSTR(0x2C, 0x00),
+	ILI9881C_COMMAND_INSTR(0x2D, 0x00),
+	ILI9881C_COMMAND_INSTR(0x2E, 0x00),
+	ILI9881C_COMMAND_INSTR(0x2F, 0x00),
 
-	msleep(120);
+	ILI9881C_COMMAND_INSTR(0x30, 0x00),
+	ILI9881C_COMMAND_INSTR(0x31, 0x00),
+	ILI9881C_COMMAND_INSTR(0x32, 0x00),
+	ILI9881C_COMMAND_INSTR(0x33, 0x00),
+	ILI9881C_COMMAND_INSTR(0x34, 0x04),
+	ILI9881C_COMMAND_INSTR(0x35, 0x00),
+	ILI9881C_COMMAND_INSTR(0x36, 0x00),
+	ILI9881C_COMMAND_INSTR(0x37, 0x00),
+	ILI9881C_COMMAND_INSTR(0x38, 0x3C),
+	ILI9881C_COMMAND_INSTR(0x39, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3A, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3B, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3C, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3D, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3E, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3F, 0x00),
 
-	mipi_dsi_dcs_set_display_on(ctx->dsi);
+	ILI9881C_COMMAND_INSTR(0x40, 0x00),
+	ILI9881C_COMMAND_INSTR(0x41, 0x00),
+	ILI9881C_COMMAND_INSTR(0x42, 0x00),
+	ILI9881C_COMMAND_INSTR(0x43, 0x00),
+	ILI9881C_COMMAND_INSTR(0x44, 0x00),
 
-	return 0;
-}
+	ILI9881C_COMMAND_INSTR(0x50, 0x10),
+	ILI9881C_COMMAND_INSTR(0x51, 0x32),
+	ILI9881C_COMMAND_INSTR(0x52, 0x54),
+	ILI9881C_COMMAND_INSTR(0x53, 0x76),
+	ILI9881C_COMMAND_INSTR(0x54, 0x98),
+	ILI9881C_COMMAND_INSTR(0x55, 0xba),
+	ILI9881C_COMMAND_INSTR(0x56, 0x10),
+	ILI9881C_COMMAND_INSTR(0x57, 0x32),
+	ILI9881C_COMMAND_INSTR(0x58, 0x54),
+	ILI9881C_COMMAND_INSTR(0x59, 0x76),
+	ILI9881C_COMMAND_INSTR(0x5A, 0x98),
+	ILI9881C_COMMAND_INSTR(0x5B, 0xba),
+	ILI9881C_COMMAND_INSTR(0x5C, 0xdc),
+	ILI9881C_COMMAND_INSTR(0x5D, 0xfe),
+
+	//GIP_3
+	ILI9881C_COMMAND_INSTR(0x5E, 0x00),
+	ILI9881C_COMMAND_INSTR(0x5F, 0x01),
+	ILI9881C_COMMAND_INSTR(0x60, 0x00),
+	ILI9881C_COMMAND_INSTR(0x61, 0x15),
+	ILI9881C_COMMAND_INSTR(0x62, 0x14),
+	ILI9881C_COMMAND_INSTR(0x63, 0x0E),
+	ILI9881C_COMMAND_INSTR(0x64, 0x0F),
+	ILI9881C_COMMAND_INSTR(0x65, 0x0C),
+	ILI9881C_COMMAND_INSTR(0x66, 0x0D),
+	ILI9881C_COMMAND_INSTR(0x67, 0x06),
+	ILI9881C_COMMAND_INSTR(0x68, 0x02),
+	ILI9881C_COMMAND_INSTR(0x69, 0x02),
+	ILI9881C_COMMAND_INSTR(0x6A, 0x02),
+	ILI9881C_COMMAND_INSTR(0x6B, 0x02),
+	ILI9881C_COMMAND_INSTR(0x6C, 0x02),
+	ILI9881C_COMMAND_INSTR(0x6D, 0x02),
+	ILI9881C_COMMAND_INSTR(0x6E, 0x07),
+	ILI9881C_COMMAND_INSTR(0x6F, 0x02),
 
-static int ili9881c_disable(struct drm_panel *panel)
-{
-	struct ili9881c *ctx = panel_to_ili9881c(panel);
+	ILI9881C_COMMAND_INSTR(0x70, 0x02),
+	ILI9881C_COMMAND_INSTR(0x71, 0x02),
+	ILI9881C_COMMAND_INSTR(0x72, 0x02),
+	ILI9881C_COMMAND_INSTR(0x73, 0x02),
+	ILI9881C_COMMAND_INSTR(0x74, 0x02),
+	ILI9881C_COMMAND_INSTR(0x75, 0x01),
+	ILI9881C_COMMAND_INSTR(0x76, 0x00),
+	ILI9881C_COMMAND_INSTR(0x77, 0x14),
+	ILI9881C_COMMAND_INSTR(0x78, 0x15),
+	ILI9881C_COMMAND_INSTR(0x79, 0x0E),
+	ILI9881C_COMMAND_INSTR(0x7A, 0x0F),
+	ILI9881C_COMMAND_INSTR(0x7B, 0x0C),
+	ILI9881C_COMMAND_INSTR(0x7C, 0x0D),
+	ILI9881C_COMMAND_INSTR(0x7D, 0x06),
+	ILI9881C_COMMAND_INSTR(0x7E, 0x02),
+	ILI9881C_COMMAND_INSTR(0x7F, 0x02),
 
-	return mipi_dsi_dcs_set_display_off(ctx->dsi);
-}
+	ILI9881C_COMMAND_INSTR(0x80, 0x02),
+	ILI9881C_COMMAND_INSTR(0x81, 0x02),
+	ILI9881C_COMMAND_INSTR(0x82, 0x02),
+	ILI9881C_COMMAND_INSTR(0x83, 0x02),
+	ILI9881C_COMMAND_INSTR(0x84, 0x07),
+	ILI9881C_COMMAND_INSTR(0x85, 0x02),
+	ILI9881C_COMMAND_INSTR(0x86, 0x02),
+	ILI9881C_COMMAND_INSTR(0x87, 0x02),
+	ILI9881C_COMMAND_INSTR(0x88, 0x02),
+	ILI9881C_COMMAND_INSTR(0x89, 0x02),
+	ILI9881C_COMMAND_INSTR(0x8A, 0x02),
 
-static int ili9881c_unprepare(struct drm_panel *panel)
-{
-	struct ili9881c *ctx = panel_to_ili9881c(panel);
+	ILI9881C_SWITCH_PAGE_INSTR(4),
+	ILI9881C_COMMAND_INSTR(0x6C, 0x15),
+	ILI9881C_COMMAND_INSTR(0x6E, 0x2A),
 
-	mipi_dsi_dcs_enter_sleep_mode(ctx->dsi);
-	regulator_disable(ctx->power);
-	gpiod_set_value_cansleep(ctx->reset, 1);
+	//clamp 15V
+	ILI9881C_COMMAND_INSTR(0x6F, 0x35),
+	ILI9881C_COMMAND_INSTR(0x3A, 0x92),
+	ILI9881C_COMMAND_INSTR(0x8D, 0x1F),
+	ILI9881C_COMMAND_INSTR(0x87, 0xBA),
+	ILI9881C_COMMAND_INSTR(0x26, 0x76),
+	ILI9881C_COMMAND_INSTR(0xB2, 0xD1),
+	ILI9881C_COMMAND_INSTR(0xB5, 0x27),
+	ILI9881C_COMMAND_INSTR(0x31, 0x75),
+	ILI9881C_COMMAND_INSTR(0x30, 0x03),
+	ILI9881C_COMMAND_INSTR(0x3B, 0x98),
+	ILI9881C_COMMAND_INSTR(0x35, 0x17),
+	ILI9881C_COMMAND_INSTR(0x33, 0x14),
+	ILI9881C_COMMAND_INSTR(0x38, 0x01),
+	ILI9881C_COMMAND_INSTR(0x39, 0x00),
 
-	return 0;
-}
+	ILI9881C_SWITCH_PAGE_INSTR(1),
+	// direction rotate
+	//ILI9881C_COMMAND_INSTR(0x22, 0x0B),
+	ILI9881C_COMMAND_INSTR(0x22, 0x0A),
+	ILI9881C_COMMAND_INSTR(0x31, 0x00),
+	ILI9881C_COMMAND_INSTR(0x53, 0x63),
+	ILI9881C_COMMAND_INSTR(0x55, 0x69),
+	ILI9881C_COMMAND_INSTR(0x50, 0xC7),
+	ILI9881C_COMMAND_INSTR(0x51, 0xC2),
+	ILI9881C_COMMAND_INSTR(0x60, 0x26),
 
-static const struct drm_display_mode lhr050h41_default_mode = {
-	.clock		= 62000,
+	ILI9881C_COMMAND_INSTR(0xA0, 0x08),
+	ILI9881C_COMMAND_INSTR(0xA1, 0x0F),
+	ILI9881C_COMMAND_INSTR(0xA2, 0x25),
+	ILI9881C_COMMAND_INSTR(0xA3, 0x01),
+	ILI9881C_COMMAND_INSTR(0xA4, 0x23),
+	ILI9881C_COMMAND_INSTR(0xA5, 0x18),
+	ILI9881C_COMMAND_INSTR(0xA6, 0x11),
+	ILI9881C_COMMAND_INSTR(0xA7, 0x1A),
+	ILI9881C_COMMAND_INSTR(0xA8, 0x81),
+	ILI9881C_COMMAND_INSTR(0xA9, 0x19),
+	ILI9881C_COMMAND_INSTR(0xAA, 0x26),
+	ILI9881C_COMMAND_INSTR(0xAB, 0x7C),
+	ILI9881C_COMMAND_INSTR(0xAC, 0x24),
+	ILI9881C_COMMAND_INSTR(0xAD, 0x1E),
+	ILI9881C_COMMAND_INSTR(0xAE, 0x5C),
+	ILI9881C_COMMAND_INSTR(0xAF, 0x2A),
+	ILI9881C_COMMAND_INSTR(0xB0, 0x2B),
+	ILI9881C_COMMAND_INSTR(0xB1, 0x50),
+	ILI9881C_COMMAND_INSTR(0xB2, 0x5C),
+	ILI9881C_COMMAND_INSTR(0xB3, 0x39),
 
-	.hdisplay	= 720,
-	.hsync_start	= 720 + 10,
-	.hsync_end	= 720 + 10 + 20,
-	.htotal		= 720 + 10 + 20 + 30,
+	ILI9881C_COMMAND_INSTR(0xC0, 0x08),
+	ILI9881C_COMMAND_INSTR(0xC1, 0x1F),
+	ILI9881C_COMMAND_INSTR(0xC2, 0x24),
+	ILI9881C_COMMAND_INSTR(0xC3, 0x1D),
+	ILI9881C_COMMAND_INSTR(0xC4, 0x04),
+	ILI9881C_COMMAND_INSTR(0xC5, 0x32),
+	ILI9881C_COMMAND_INSTR(0xC6, 0x24),
+	ILI9881C_COMMAND_INSTR(0xC7, 0x1F),
+	ILI9881C_COMMAND_INSTR(0xC8, 0x90),
+	ILI9881C_COMMAND_INSTR(0xC9, 0x20),
+	ILI9881C_COMMAND_INSTR(0xCA, 0x2C),
+	ILI9881C_COMMAND_INSTR(0xCB, 0x82),
+	ILI9881C_COMMAND_INSTR(0xCC, 0x19),
+	ILI9881C_COMMAND_INSTR(0xCD, 0x22),
+	ILI9881C_COMMAND_INSTR(0xCE, 0x4E),
+	ILI9881C_COMMAND_INSTR(0xCF, 0x28),
+	ILI9881C_COMMAND_INSTR(0xD0, 0x2D),
+	ILI9881C_COMMAND_INSTR(0xD1, 0x51),
+	ILI9881C_COMMAND_INSTR(0xD2, 0x5D),
+	ILI9881C_COMMAND_INSTR(0xD3, 0x39),
 
-	.vdisplay	= 1280,
-	.vsync_start	= 1280 + 10,
-	.vsync_end	= 1280 + 10 + 10,
-	.vtotal		= 1280 + 10 + 10 + 20,
+	ILI9881C_SWITCH_PAGE_INSTR(0),
+	//PWM
+	ILI9881C_COMMAND_INSTR(0x51, 0x0F),
+	ILI9881C_COMMAND_INSTR(0x52, 0xFF),
+	ILI9881C_COMMAND_INSTR(0x53, 0x2C),
 
-	.width_mm	= 62,
-	.height_mm	= 110,
+	ILI9881C_COMMAND_INSTR(0x11, 0x00),
+	ILI9881C_COMMAND_INSTR(0x29, 0x00),
+	ILI9881C_COMMAND_INSTR(0x35, 0x00),
 };
 
-static const struct drm_display_mode k101_im2byl02_default_mode = {
-	.clock		= 69700,
-
-	.hdisplay	= 800,
-	.hsync_start	= 800 + 52,
-	.hsync_end	= 800 + 52 + 8,
-	.htotal		= 800 + 52 + 8 + 48,
-
-	.vdisplay	= 1280,
-	.vsync_start	= 1280 + 16,
-	.vsync_end	= 1280 + 16 + 6,
-	.vtotal		= 1280 + 16 + 6 + 15,
-
-	.width_mm	= 135,
+static const struct ili9881c_instr cfaf7201280a0_050tx_init[] = {
+	//ILI9881C PAGE3
+	ILI9881C_SWITCH_PAGE_INSTR(3),
+	//GIP_1
+	ILI9881C_COMMAND_INSTR(0x01, 0x00), //added
+	ILI9881C_COMMAND_INSTR(0x02, 0x00),
+	ILI9881C_COMMAND_INSTR(0x03, 0x73),
+	ILI9881C_COMMAND_INSTR(0x04, 0x00),
+	ILI9881C_COMMAND_INSTR(0x05, 0x00),
+	ILI9881C_COMMAND_INSTR(0x06, 0x0A),
+	ILI9881C_COMMAND_INSTR(0x07, 0x00),
+	ILI9881C_COMMAND_INSTR(0x08, 0x00),
+	ILI9881C_COMMAND_INSTR(0x09, 0x01),
+	ILI9881C_COMMAND_INSTR(0x0A, 0x00),
+	ILI9881C_COMMAND_INSTR(0x0B, 0x00),
+	ILI9881C_COMMAND_INSTR(0x0C, 0x01),
+	ILI9881C_COMMAND_INSTR(0x0D, 0x00),
+	ILI9881C_COMMAND_INSTR(0x0E, 0x00),
+	ILI9881C_COMMAND_INSTR(0x0F, 0x1D),
+	ILI9881C_COMMAND_INSTR(0x10, 0x1D),
+	ILI9881C_COMMAND_INSTR(0x11, 0x00),
+	ILI9881C_COMMAND_INSTR(0x12, 0x00),
+	ILI9881C_COMMAND_INSTR(0x13, 0x00),
+	ILI9881C_COMMAND_INSTR(0x14, 0x00),
+	ILI9881C_COMMAND_INSTR(0x15, 0x00),
+	ILI9881C_COMMAND_INSTR(0x16, 0x00),
+	ILI9881C_COMMAND_INSTR(0x17, 0x00),
+	ILI9881C_COMMAND_INSTR(0x18, 0x00),
+	ILI9881C_COMMAND_INSTR(0x19, 0x00),
+	ILI9881C_COMMAND_INSTR(0x1A, 0x00),
+	ILI9881C_COMMAND_INSTR(0x1B, 0x00),
+	ILI9881C_COMMAND_INSTR(0x1C, 0x00),
+	ILI9881C_COMMAND_INSTR(0x1D, 0x00),
+	ILI9881C_COMMAND_INSTR(0x1E, 0x40),
+	ILI9881C_COMMAND_INSTR(0x1F, 0x80),
+	ILI9881C_COMMAND_INSTR(0x20, 0x06),
+	ILI9881C_COMMAND_INSTR(0x21, 0x02),
+	ILI9881C_COMMAND_INSTR(0x22, 0x00),
+	ILI9881C_COMMAND_INSTR(0x23, 0x00),
+	ILI9881C_COMMAND_INSTR(0x24, 0x00),
+	ILI9881C_COMMAND_INSTR(0x25, 0x00),
+	ILI9881C_COMMAND_INSTR(0x26, 0x00),
+	ILI9881C_COMMAND_INSTR(0x27, 0x00),
+	ILI9881C_COMMAND_INSTR(0x28, 0x33),
+	ILI9881C_COMMAND_INSTR(0x29, 0x03),
+	ILI9881C_COMMAND_INSTR(0x2A, 0x00),
+	ILI9881C_COMMAND_INSTR(0x2B, 0x00),
+	ILI9881C_COMMAND_INSTR(0x2C, 0x00),
+	ILI9881C_COMMAND_INSTR(0x2D, 0x00),
+	ILI9881C_COMMAND_INSTR(0x2E, 0x00),
+	ILI9881C_COMMAND_INSTR(0x2F, 0x00),
+	ILI9881C_COMMAND_INSTR(0x30, 0x00),
+	ILI9881C_COMMAND_INSTR(0x31, 0x00),
+	ILI9881C_COMMAND_INSTR(0x32, 0x00),
+	ILI9881C_COMMAND_INSTR(0x33, 0x00),
+	ILI9881C_COMMAND_INSTR(0x34, 0x04),
+	ILI9881C_COMMAND_INSTR(0x35, 0x00),
+	ILI9881C_COMMAND_INSTR(0x36, 0x00),
+	ILI9881C_COMMAND_INSTR(0x37, 0x00),
+	ILI9881C_COMMAND_INSTR(0x38, 0x3C),
+	ILI9881C_COMMAND_INSTR(0x39, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3A, 0x40),
+	ILI9881C_COMMAND_INSTR(0x3B, 0x40),
+	ILI9881C_COMMAND_INSTR(0x3C, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3D, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3E, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3F, 0x00),
+	ILI9881C_COMMAND_INSTR(0x40, 0x00),
+	ILI9881C_COMMAND_INSTR(0x41, 0x00),
+	ILI9881C_COMMAND_INSTR(0x42, 0x00),
+	ILI9881C_COMMAND_INSTR(0x43, 0x00),
+	ILI9881C_COMMAND_INSTR(0x44, 0x00),
+	//GIP_2
+	ILI9881C_COMMAND_INSTR(0x50, 0x01),
+	ILI9881C_COMMAND_INSTR(0x51, 0x23),
+	ILI9881C_COMMAND_INSTR(0x52, 0x45),
+	ILI9881C_COMMAND_INSTR(0x53, 0x67),
+	ILI9881C_COMMAND_INSTR(0x54, 0x89),
+	ILI9881C_COMMAND_INSTR(0x55, 0xAB),
+	ILI9881C_COMMAND_INSTR(0x56, 0x01),
+	ILI9881C_COMMAND_INSTR(0x57, 0x23),
+	ILI9881C_COMMAND_INSTR(0x58, 0x45),
+	ILI9881C_COMMAND_INSTR(0x59, 0x67),
+	ILI9881C_COMMAND_INSTR(0x5A, 0x89),
+	ILI9881C_COMMAND_INSTR(0x5B, 0xAB),
+	ILI9881C_COMMAND_INSTR(0x5C, 0xCD),
+	ILI9881C_COMMAND_INSTR(0x5D, 0xEF),
+	//GIP_3
+	ILI9881C_COMMAND_INSTR(0x5E, 0x11),
+	ILI9881C_COMMAND_INSTR(0x5F, 0x01),
+	ILI9881C_COMMAND_INSTR(0x60, 0x00),
+	ILI9881C_COMMAND_INSTR(0x61, 0x15),
+	ILI9881C_COMMAND_INSTR(0x62, 0x14),
+	ILI9881C_COMMAND_INSTR(0x63, 0x0E),
+	ILI9881C_COMMAND_INSTR(0x64, 0x0F),
+	ILI9881C_COMMAND_INSTR(0x65, 0x0C),
+	ILI9881C_COMMAND_INSTR(0x66, 0x0D),
+	ILI9881C_COMMAND_INSTR(0x67, 0x06),
+	ILI9881C_COMMAND_INSTR(0x68, 0x02),
+	ILI9881C_COMMAND_INSTR(0x69, 0x07),
+	ILI9881C_COMMAND_INSTR(0x6A, 0x02),
+	ILI9881C_COMMAND_INSTR(0x6B, 0x02),
+	ILI9881C_COMMAND_INSTR(0x6C, 0x02),
+	ILI9881C_COMMAND_INSTR(0x6D, 0x02),
+	ILI9881C_COMMAND_INSTR(0x6E, 0x02),
+	ILI9881C_COMMAND_INSTR(0x6F, 0x02),
+	ILI9881C_COMMAND_INSTR(0x70, 0x02),
+	ILI9881C_COMMAND_INSTR(0x71, 0x02),
+	ILI9881C_COMMAND_INSTR(0x72, 0x02),
+	ILI9881C_COMMAND_INSTR(0x73, 0x02),
+	ILI9881C_COMMAND_INSTR(0x74, 0x02),
+	ILI9881C_COMMAND_INSTR(0x75, 0x01),
+	ILI9881C_COMMAND_INSTR(0x76, 0x00),
+	ILI9881C_COMMAND_INSTR(0x77, 0x14),
+	ILI9881C_COMMAND_INSTR(0x78, 0x15),
+	ILI9881C_COMMAND_INSTR(0x79, 0x0E),
+	ILI9881C_COMMAND_INSTR(0x7A, 0x0F),
+	ILI9881C_COMMAND_INSTR(0x7B, 0x0C),
+	ILI9881C_COMMAND_INSTR(0x7C, 0x0D),
+	ILI9881C_COMMAND_INSTR(0x7D, 0x06),
+	ILI9881C_COMMAND_INSTR(0x7E, 0x02),
+	ILI9881C_COMMAND_INSTR(0x7F, 0x07),
+	ILI9881C_COMMAND_INSTR(0x80, 0x02),
+	ILI9881C_COMMAND_INSTR(0x81, 0x02),
+	ILI9881C_COMMAND_INSTR(0x82, 0x02),
+	ILI9881C_COMMAND_INSTR(0x83, 0x02),
+	ILI9881C_COMMAND_INSTR(0x84, 0x02),
+	ILI9881C_COMMAND_INSTR(0x85, 0x02),
+	ILI9881C_COMMAND_INSTR(0x86, 0x02),
+	ILI9881C_COMMAND_INSTR(0x87, 0x02),
+	ILI9881C_COMMAND_INSTR(0x88, 0x02),
+	ILI9881C_COMMAND_INSTR(0x89, 0x02),
+	ILI9881C_COMMAND_INSTR(0x8A, 0x02),
+	//ILI9881C PAGE4
+	ILI9881C_SWITCH_PAGE_INSTR(4),
+	ILI9881C_COMMAND_INSTR(0x6C, 0x15),
+	ILI9881C_COMMAND_INSTR(0x6E, 0x2B),
+	// VGH & VGL OUTPUT
+	ILI9881C_COMMAND_INSTR(0x6F, 0x33),
+	ILI9881C_COMMAND_INSTR(0x8D, 0x18),
+	ILI9881C_COMMAND_INSTR(0x87, 0xBA),
+	ILI9881C_COMMAND_INSTR(0x26, 0x76),
+	//Reload Gamma setting
+	ILI9881C_COMMAND_INSTR(0xB2, 0xD1),
+	ILI9881C_COMMAND_INSTR(0xB5, 0x06),
+	ILI9881C_COMMAND_INSTR(0x3A, 0x24),
+	ILI9881C_COMMAND_INSTR(0x35, 0x1F),
+
+	//ILI9881C PAGE1
+	ILI9881C_SWITCH_PAGE_INSTR(1),
+	ILI9881C_COMMAND_INSTR(0x22, 0x09),
+	//Column inversion
+	ILI9881C_COMMAND_INSTR(0x31, 0x00),
+	ILI9881C_COMMAND_INSTR(0x40, 0x33),
+	ILI9881C_COMMAND_INSTR(0x53, 0xA2),
+	ILI9881C_COMMAND_INSTR(0x55, 0x92),
+	ILI9881C_COMMAND_INSTR(0x50, 0x96),
+	ILI9881C_COMMAND_INSTR(0x51, 0x96),
+	ILI9881C_COMMAND_INSTR(0x60, 0x22),
+	ILI9881C_COMMAND_INSTR(0x61, 0x00),
+	ILI9881C_COMMAND_INSTR(0x62, 0x19),
+	ILI9881C_COMMAND_INSTR(0x63, 0x00),
+	//---P-GAMMA START---
+	ILI9881C_COMMAND_INSTR(0xA0, 0x08),
+	ILI9881C_COMMAND_INSTR(0xA1, 0x11),
+	ILI9881C_COMMAND_INSTR(0xA2, 0x19),
+	ILI9881C_COMMAND_INSTR(0xA3, 0x0D),
+	ILI9881C_COMMAND_INSTR(0xA4, 0x0D),
+	ILI9881C_COMMAND_INSTR(0xA5, 0x1E),
+	ILI9881C_COMMAND_INSTR(0xA6, 0x14),
+	ILI9881C_COMMAND_INSTR(0xA7, 0x17),
+	ILI9881C_COMMAND_INSTR(0xA8, 0x4F),
+	ILI9881C_COMMAND_INSTR(0xA9, 0x1A),
+	ILI9881C_COMMAND_INSTR(0xAA, 0x27),
+	ILI9881C_COMMAND_INSTR(0xAB, 0x49),
+	ILI9881C_COMMAND_INSTR(0xAC, 0x1A),
+	ILI9881C_COMMAND_INSTR(0xAD, 0x18),
+	ILI9881C_COMMAND_INSTR(0xAE, 0x4C),
+	ILI9881C_COMMAND_INSTR(0xAF, 0x22),
+	ILI9881C_COMMAND_INSTR(0xB0, 0x27),
+	ILI9881C_COMMAND_INSTR(0xB1, 0x4B),
+	ILI9881C_COMMAND_INSTR(0xB2, 0x60),
+	ILI9881C_COMMAND_INSTR(0xB3, 0x39),
+	//--- N-GAMMA START---
+	ILI9881C_COMMAND_INSTR(0xC0, 0x08),
+	ILI9881C_COMMAND_INSTR(0xC1, 0x11),
+	ILI9881C_COMMAND_INSTR(0xC2, 0x19),
+	ILI9881C_COMMAND_INSTR(0xC3, 0x0D),
+	ILI9881C_COMMAND_INSTR(0xC4, 0x0D),
+	ILI9881C_COMMAND_INSTR(0xC5, 0x1E),
+	ILI9881C_COMMAND_INSTR(0xC6, 0x14),
+	ILI9881C_COMMAND_INSTR(0xC7, 0x17),
+	ILI9881C_COMMAND_INSTR(0xC8, 0x4F),
+	ILI9881C_COMMAND_INSTR(0xC9, 0x1A),
+	ILI9881C_COMMAND_INSTR(0xCA, 0x27),
+	ILI9881C_COMMAND_INSTR(0xCB, 0x49),
+	ILI9881C_COMMAND_INSTR(0xCC, 0x1A),
+	ILI9881C_COMMAND_INSTR(0xCD, 0x18),
+	ILI9881C_COMMAND_INSTR(0xCE, 0x4C),
+	ILI9881C_COMMAND_INSTR(0xCF, 0x33),
+	ILI9881C_COMMAND_INSTR(0xD0, 0x27),
+	ILI9881C_COMMAND_INSTR(0xD1, 0x4B),
+	ILI9881C_COMMAND_INSTR(0xD2, 0x60),
+	ILI9881C_COMMAND_INSTR(0xD3, 0x39),
+};
+
+static const struct ili9881c_instr rpi_5inch_init[] = {
+	ILI9881C_SWITCH_PAGE_INSTR(3),
+	ILI9881C_COMMAND_INSTR(0x01, 0x00),
+	ILI9881C_COMMAND_INSTR(0x02, 0x00),
+	ILI9881C_COMMAND_INSTR(0x03, 0x73),
+	ILI9881C_COMMAND_INSTR(0x04, 0x73),
+	ILI9881C_COMMAND_INSTR(0x05, 0x00),
+	ILI9881C_COMMAND_INSTR(0x06, 0x06),
+	ILI9881C_COMMAND_INSTR(0x07, 0x02),
+	ILI9881C_COMMAND_INSTR(0x08, 0x00),
+	ILI9881C_COMMAND_INSTR(0x09, 0x01),
+	ILI9881C_COMMAND_INSTR(0x0a, 0x01),
+	ILI9881C_COMMAND_INSTR(0x0b, 0x01),
+	ILI9881C_COMMAND_INSTR(0x0c, 0x01),
+	ILI9881C_COMMAND_INSTR(0x0d, 0x01),
+	ILI9881C_COMMAND_INSTR(0x0e, 0x01),
+	ILI9881C_COMMAND_INSTR(0x0f, 0x01),
+	ILI9881C_COMMAND_INSTR(0x10, 0x01),
+	ILI9881C_COMMAND_INSTR(0x11, 0x00),
+	ILI9881C_COMMAND_INSTR(0x12, 0x00),
+	ILI9881C_COMMAND_INSTR(0x13, 0x01),
+	ILI9881C_COMMAND_INSTR(0x14, 0x00),
+	ILI9881C_COMMAND_INSTR(0x15, 0x00),
+	ILI9881C_COMMAND_INSTR(0x16, 0x00),
+	ILI9881C_COMMAND_INSTR(0x17, 0x00),
+	ILI9881C_COMMAND_INSTR(0x18, 0x00),
+	ILI9881C_COMMAND_INSTR(0x19, 0x00),
+	ILI9881C_COMMAND_INSTR(0x1a, 0x00),
+	ILI9881C_COMMAND_INSTR(0x1b, 0x00),
+	ILI9881C_COMMAND_INSTR(0x1c, 0x00),
+	ILI9881C_COMMAND_INSTR(0x1d, 0x00),
+	ILI9881C_COMMAND_INSTR(0x1e, 0xc0),
+	ILI9881C_COMMAND_INSTR(0x1f, 0x80),
+	ILI9881C_COMMAND_INSTR(0x20, 0x04),
+	ILI9881C_COMMAND_INSTR(0x21, 0x03),
+	ILI9881C_COMMAND_INSTR(0x22, 0x00),
+	ILI9881C_COMMAND_INSTR(0x23, 0x00),
+	ILI9881C_COMMAND_INSTR(0x24, 0x00),
+	ILI9881C_COMMAND_INSTR(0x25, 0x00),
+	ILI9881C_COMMAND_INSTR(0x26, 0x00),
+	ILI9881C_COMMAND_INSTR(0x27, 0x00),
+	ILI9881C_COMMAND_INSTR(0x28, 0x33),
+	ILI9881C_COMMAND_INSTR(0x29, 0x03),
+	ILI9881C_COMMAND_INSTR(0x2a, 0x00),
+	ILI9881C_COMMAND_INSTR(0x2b, 0x00),
+	ILI9881C_COMMAND_INSTR(0x2c, 0x00),
+	ILI9881C_COMMAND_INSTR(0x2d, 0x00),
+	ILI9881C_COMMAND_INSTR(0x2e, 0x00),
+	ILI9881C_COMMAND_INSTR(0x2f, 0x00),
+	ILI9881C_COMMAND_INSTR(0x30, 0x00),
+	ILI9881C_COMMAND_INSTR(0x31, 0x00),
+	ILI9881C_COMMAND_INSTR(0x32, 0x00),
+	ILI9881C_COMMAND_INSTR(0x33, 0x00),
+	ILI9881C_COMMAND_INSTR(0x34, 0x03),
+	ILI9881C_COMMAND_INSTR(0x35, 0x00),
+	ILI9881C_COMMAND_INSTR(0x36, 0x03),
+	ILI9881C_COMMAND_INSTR(0x37, 0x00),
+	ILI9881C_COMMAND_INSTR(0x38, 0x00),
+	ILI9881C_COMMAND_INSTR(0x39, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3a, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3b, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3c, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3d, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3e, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3f, 0x00),
+	ILI9881C_COMMAND_INSTR(0x40, 0x00),
+	ILI9881C_COMMAND_INSTR(0x41, 0x00),
+	ILI9881C_COMMAND_INSTR(0x42, 0x00),
+	ILI9881C_COMMAND_INSTR(0x43, 0x00),
+	ILI9881C_COMMAND_INSTR(0x44, 0x00),
+	ILI9881C_COMMAND_INSTR(0x50, 0x01),
+	ILI9881C_COMMAND_INSTR(0x51, 0x23),
+	ILI9881C_COMMAND_INSTR(0x52, 0x45),
+	ILI9881C_COMMAND_INSTR(0x53, 0x67),
+	ILI9881C_COMMAND_INSTR(0x54, 0x89),
+	ILI9881C_COMMAND_INSTR(0x55, 0xab),
+	ILI9881C_COMMAND_INSTR(0x56, 0x01),
+	ILI9881C_COMMAND_INSTR(0x57, 0x23),
+	ILI9881C_COMMAND_INSTR(0x58, 0x45),
+	ILI9881C_COMMAND_INSTR(0x59, 0x67),
+	ILI9881C_COMMAND_INSTR(0x5a, 0x89),
+	ILI9881C_COMMAND_INSTR(0x5b, 0xab),
+	ILI9881C_COMMAND_INSTR(0x5c, 0xcd),
+	ILI9881C_COMMAND_INSTR(0x5d, 0xef),
+	ILI9881C_COMMAND_INSTR(0x5e, 0x10),
+	ILI9881C_COMMAND_INSTR(0x5f, 0x09),
+	ILI9881C_COMMAND_INSTR(0x60, 0x08),
+	ILI9881C_COMMAND_INSTR(0x61, 0x0f),
+	ILI9881C_COMMAND_INSTR(0x62, 0x0e),
+	ILI9881C_COMMAND_INSTR(0x63, 0x0d),
+	ILI9881C_COMMAND_INSTR(0x64, 0x0c),
+	ILI9881C_COMMAND_INSTR(0x65, 0x02),
+	ILI9881C_COMMAND_INSTR(0x66, 0x02),
+	ILI9881C_COMMAND_INSTR(0x67, 0x02),
+	ILI9881C_COMMAND_INSTR(0x68, 0x02),
+	ILI9881C_COMMAND_INSTR(0x69, 0x02),
+	ILI9881C_COMMAND_INSTR(0x6a, 0x02),
+	ILI9881C_COMMAND_INSTR(0x6b, 0x02),
+	ILI9881C_COMMAND_INSTR(0x6c, 0x02),
+	ILI9881C_COMMAND_INSTR(0x6d, 0x02),
+	ILI9881C_COMMAND_INSTR(0x6e, 0x02),
+	ILI9881C_COMMAND_INSTR(0x6f, 0x02),
+	ILI9881C_COMMAND_INSTR(0x70, 0x02),
+	ILI9881C_COMMAND_INSTR(0x71, 0x06),
+	ILI9881C_COMMAND_INSTR(0x72, 0x07),
+	ILI9881C_COMMAND_INSTR(0x73, 0x02),
+	ILI9881C_COMMAND_INSTR(0x74, 0x02),
+	ILI9881C_COMMAND_INSTR(0x75, 0x06),
+	ILI9881C_COMMAND_INSTR(0x76, 0x07),
+	ILI9881C_COMMAND_INSTR(0x77, 0x0e),
+	ILI9881C_COMMAND_INSTR(0x78, 0x0f),
+	ILI9881C_COMMAND_INSTR(0x79, 0x0c),
+	ILI9881C_COMMAND_INSTR(0x7a, 0x0d),
+	ILI9881C_COMMAND_INSTR(0x7b, 0x02),
+	ILI9881C_COMMAND_INSTR(0x7c, 0x02),
+	ILI9881C_COMMAND_INSTR(0x7d, 0x02),
+	ILI9881C_COMMAND_INSTR(0x7e, 0x02),
+	ILI9881C_COMMAND_INSTR(0x7f, 0x02),
+	ILI9881C_COMMAND_INSTR(0x80, 0x02),
+	ILI9881C_COMMAND_INSTR(0x81, 0x02),
+	ILI9881C_COMMAND_INSTR(0x82, 0x02),
+	ILI9881C_COMMAND_INSTR(0x83, 0x02),
+	ILI9881C_COMMAND_INSTR(0x84, 0x02),
+	ILI9881C_COMMAND_INSTR(0x85, 0x02),
+	ILI9881C_COMMAND_INSTR(0x86, 0x02),
+	ILI9881C_COMMAND_INSTR(0x87, 0x09),
+	ILI9881C_COMMAND_INSTR(0x88, 0x08),
+	ILI9881C_COMMAND_INSTR(0x89, 0x02),
+	ILI9881C_COMMAND_INSTR(0x8A, 0x02),
+	ILI9881C_SWITCH_PAGE_INSTR(4),
+	ILI9881C_COMMAND_INSTR(0x6C, 0x15),
+	ILI9881C_COMMAND_INSTR(0x6E, 0x2a),
+	ILI9881C_COMMAND_INSTR(0x6F, 0x57),
+	ILI9881C_COMMAND_INSTR(0x3A, 0xa4),
+	ILI9881C_COMMAND_INSTR(0x8D, 0x1a),
+	ILI9881C_COMMAND_INSTR(0x87, 0xba),
+	ILI9881C_COMMAND_INSTR(0x26, 0x76),
+	ILI9881C_COMMAND_INSTR(0xB2, 0xd1),
+	ILI9881C_SWITCH_PAGE_INSTR(1),
+	ILI9881C_COMMAND_INSTR(0x22, 0x0A),
+	ILI9881C_COMMAND_INSTR(0x31, 0x00),
+	ILI9881C_COMMAND_INSTR(0x53, 0x35),
+	ILI9881C_COMMAND_INSTR(0x55, 0x50),
+	ILI9881C_COMMAND_INSTR(0x50, 0xaf),
+	ILI9881C_COMMAND_INSTR(0x51, 0xaf),
+	ILI9881C_COMMAND_INSTR(0x60, 0x14),
+	ILI9881C_COMMAND_INSTR(0xA0, 0x08),
+	ILI9881C_COMMAND_INSTR(0xA1, 0x1d),
+	ILI9881C_COMMAND_INSTR(0xA2, 0x2c),
+	ILI9881C_COMMAND_INSTR(0xA3, 0x14),
+	ILI9881C_COMMAND_INSTR(0xA4, 0x19),
+	ILI9881C_COMMAND_INSTR(0xA5, 0x2e),
+	ILI9881C_COMMAND_INSTR(0xA6, 0x22),
+	ILI9881C_COMMAND_INSTR(0xA7, 0x23),
+	ILI9881C_COMMAND_INSTR(0xA8, 0x97),
+	ILI9881C_COMMAND_INSTR(0xA9, 0x1e),
+	ILI9881C_COMMAND_INSTR(0xAA, 0x29),
+	ILI9881C_COMMAND_INSTR(0xAB, 0x7b),
+	ILI9881C_COMMAND_INSTR(0xAC, 0x18),
+	ILI9881C_COMMAND_INSTR(0xAD, 0x17),
+	ILI9881C_COMMAND_INSTR(0xAE, 0x4b),
+	ILI9881C_COMMAND_INSTR(0xAF, 0x1f),
+	ILI9881C_COMMAND_INSTR(0xB0, 0x27),
+	ILI9881C_COMMAND_INSTR(0xB1, 0x52),
+	ILI9881C_COMMAND_INSTR(0xB2, 0x63),
+	ILI9881C_COMMAND_INSTR(0xB3, 0x39),
+	ILI9881C_COMMAND_INSTR(0xC0, 0x08),
+	ILI9881C_COMMAND_INSTR(0xC1, 0x1d),
+	ILI9881C_COMMAND_INSTR(0xC2, 0x2c),
+	ILI9881C_COMMAND_INSTR(0xC3, 0x14),
+	ILI9881C_COMMAND_INSTR(0xC4, 0x19),
+	ILI9881C_COMMAND_INSTR(0xC5, 0x2e),
+	ILI9881C_COMMAND_INSTR(0xC6, 0x22),
+	ILI9881C_COMMAND_INSTR(0xC7, 0x23),
+	ILI9881C_COMMAND_INSTR(0xC8, 0x97),
+	ILI9881C_COMMAND_INSTR(0xC9, 0x1e),
+	ILI9881C_COMMAND_INSTR(0xCA, 0x29),
+	ILI9881C_COMMAND_INSTR(0xCB, 0x7b),
+	ILI9881C_COMMAND_INSTR(0xCC, 0x18),
+	ILI9881C_COMMAND_INSTR(0xCD, 0x17),
+	ILI9881C_COMMAND_INSTR(0xCE, 0x4b),
+	ILI9881C_COMMAND_INSTR(0xCF, 0x1f),
+	ILI9881C_COMMAND_INSTR(0xD0, 0x27),
+	ILI9881C_COMMAND_INSTR(0xD1, 0x52),
+	ILI9881C_COMMAND_INSTR(0xD2, 0x63),
+	ILI9881C_COMMAND_INSTR(0xD3, 0x39),
+};
+
+static const struct ili9881c_instr rpi_7inch_init[] = {
+	ILI9881C_SWITCH_PAGE_INSTR(3),
+	ILI9881C_COMMAND_INSTR(0x01, 0x00),
+	ILI9881C_COMMAND_INSTR(0x02, 0x00),
+	ILI9881C_COMMAND_INSTR(0x03, 0x73),
+	ILI9881C_COMMAND_INSTR(0x04, 0x00),
+	ILI9881C_COMMAND_INSTR(0x05, 0x00),
+	ILI9881C_COMMAND_INSTR(0x06, 0x0a),
+	ILI9881C_COMMAND_INSTR(0x07, 0x00),
+	ILI9881C_COMMAND_INSTR(0x08, 0x00),
+	ILI9881C_COMMAND_INSTR(0x09, 0x61),
+	ILI9881C_COMMAND_INSTR(0x0a, 0x00),
+	ILI9881C_COMMAND_INSTR(0x0b, 0x00),
+	ILI9881C_COMMAND_INSTR(0x0c, 0x01),
+	ILI9881C_COMMAND_INSTR(0x0d, 0x00),
+	ILI9881C_COMMAND_INSTR(0x0e, 0x00),
+	ILI9881C_COMMAND_INSTR(0x0f, 0x61),
+	ILI9881C_COMMAND_INSTR(0x10, 0x61),
+	ILI9881C_COMMAND_INSTR(0x11, 0x00),
+	ILI9881C_COMMAND_INSTR(0x12, 0x00),
+	ILI9881C_COMMAND_INSTR(0x13, 0x00),
+	ILI9881C_COMMAND_INSTR(0x14, 0x00),
+	ILI9881C_COMMAND_INSTR(0x15, 0x00),
+	ILI9881C_COMMAND_INSTR(0x16, 0x00),
+	ILI9881C_COMMAND_INSTR(0x17, 0x00),
+	ILI9881C_COMMAND_INSTR(0x18, 0x00),
+	ILI9881C_COMMAND_INSTR(0x19, 0x00),
+	ILI9881C_COMMAND_INSTR(0x1a, 0x00),
+	ILI9881C_COMMAND_INSTR(0x1b, 0x00),
+	ILI9881C_COMMAND_INSTR(0x1c, 0x00),
+	ILI9881C_COMMAND_INSTR(0x1d, 0x00),
+	ILI9881C_COMMAND_INSTR(0x1e, 0x40),
+	ILI9881C_COMMAND_INSTR(0x1f, 0x80),
+	ILI9881C_COMMAND_INSTR(0x20, 0x06),
+	ILI9881C_COMMAND_INSTR(0x21, 0x01),
+	ILI9881C_COMMAND_INSTR(0x22, 0x00),
+	ILI9881C_COMMAND_INSTR(0x23, 0x00),
+	ILI9881C_COMMAND_INSTR(0x24, 0x00),
+	ILI9881C_COMMAND_INSTR(0x25, 0x00),
+	ILI9881C_COMMAND_INSTR(0x26, 0x00),
+	ILI9881C_COMMAND_INSTR(0x27, 0x00),
+	ILI9881C_COMMAND_INSTR(0x28, 0x33),
+	ILI9881C_COMMAND_INSTR(0x29, 0x03),
+	ILI9881C_COMMAND_INSTR(0x2a, 0x00),
+	ILI9881C_COMMAND_INSTR(0x2b, 0x00),
+	ILI9881C_COMMAND_INSTR(0x2c, 0x00),
+	ILI9881C_COMMAND_INSTR(0x2d, 0x00),
+	ILI9881C_COMMAND_INSTR(0x2e, 0x00),
+	ILI9881C_COMMAND_INSTR(0x2f, 0x00),
+	ILI9881C_COMMAND_INSTR(0x30, 0x00),
+	ILI9881C_COMMAND_INSTR(0x31, 0x00),
+	ILI9881C_COMMAND_INSTR(0x32, 0x00),
+	ILI9881C_COMMAND_INSTR(0x33, 0x00),
+	ILI9881C_COMMAND_INSTR(0x34, 0x04),
+	ILI9881C_COMMAND_INSTR(0x35, 0x00),
+	ILI9881C_COMMAND_INSTR(0x36, 0x00),
+	ILI9881C_COMMAND_INSTR(0x37, 0x00),
+	ILI9881C_COMMAND_INSTR(0x38, 0x3c),
+	ILI9881C_COMMAND_INSTR(0x39, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3a, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3b, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3c, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3d, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3e, 0x00),
+	ILI9881C_COMMAND_INSTR(0x3f, 0x00),
+	ILI9881C_COMMAND_INSTR(0x40, 0x00),
+	ILI9881C_COMMAND_INSTR(0x41, 0x00),
+	ILI9881C_COMMAND_INSTR(0x42, 0x00),
+	ILI9881C_COMMAND_INSTR(0x43, 0x00),
+	ILI9881C_COMMAND_INSTR(0x44, 0x00),
+	ILI9881C_COMMAND_INSTR(0x50, 0x10),
+	ILI9881C_COMMAND_INSTR(0x51, 0x32),
+	ILI9881C_COMMAND_INSTR(0x52, 0x54),
+	ILI9881C_COMMAND_INSTR(0x53, 0x76),
+	ILI9881C_COMMAND_INSTR(0x54, 0x98),
+	ILI9881C_COMMAND_INSTR(0x55, 0xba),
+	ILI9881C_COMMAND_INSTR(0x56, 0x10),
+	ILI9881C_COMMAND_INSTR(0x57, 0x32),
+	ILI9881C_COMMAND_INSTR(0x58, 0x54),
+	ILI9881C_COMMAND_INSTR(0x59, 0x76),
+	ILI9881C_COMMAND_INSTR(0x5a, 0x98),
+	ILI9881C_COMMAND_INSTR(0x5b, 0xba),
+	ILI9881C_COMMAND_INSTR(0x5c, 0xdc),
+	ILI9881C_COMMAND_INSTR(0x5d, 0xfe),
+	ILI9881C_COMMAND_INSTR(0x5e, 0x00),
+	ILI9881C_COMMAND_INSTR(0x5f, 0x0e),
+	ILI9881C_COMMAND_INSTR(0x60, 0x0f),
+	ILI9881C_COMMAND_INSTR(0x61, 0x0c),
+	ILI9881C_COMMAND_INSTR(0x62, 0x0d),
+	ILI9881C_COMMAND_INSTR(0x63, 0x06),
+	ILI9881C_COMMAND_INSTR(0x64, 0x07),
+	ILI9881C_COMMAND_INSTR(0x65, 0x02),
+	ILI9881C_COMMAND_INSTR(0x66, 0x02),
+	ILI9881C_COMMAND_INSTR(0x67, 0x02),
+	ILI9881C_COMMAND_INSTR(0x68, 0x02),
+	ILI9881C_COMMAND_INSTR(0x69, 0x01),
+	ILI9881C_COMMAND_INSTR(0x6a, 0x00),
+	ILI9881C_COMMAND_INSTR(0x6b, 0x02),
+	ILI9881C_COMMAND_INSTR(0x6c, 0x15),
+	ILI9881C_COMMAND_INSTR(0x6d, 0x14),
+	ILI9881C_COMMAND_INSTR(0x6e, 0x02),
+	ILI9881C_COMMAND_INSTR(0x6f, 0x02),
+	ILI9881C_COMMAND_INSTR(0x70, 0x02),
+	ILI9881C_COMMAND_INSTR(0x71, 0x02),
+	ILI9881C_COMMAND_INSTR(0x72, 0x02),
+	ILI9881C_COMMAND_INSTR(0x73, 0x02),
+	ILI9881C_COMMAND_INSTR(0x74, 0x02),
+	ILI9881C_COMMAND_INSTR(0x75, 0x0e),
+	ILI9881C_COMMAND_INSTR(0x76, 0x0f),
+	ILI9881C_COMMAND_INSTR(0x77, 0x0c),
+	ILI9881C_COMMAND_INSTR(0x78, 0x0d),
+	ILI9881C_COMMAND_INSTR(0x79, 0x06),
+	ILI9881C_COMMAND_INSTR(0x7a, 0x07),
+	ILI9881C_COMMAND_INSTR(0x7b, 0x02),
+	ILI9881C_COMMAND_INSTR(0x7c, 0x02),
+	ILI9881C_COMMAND_INSTR(0x7d, 0x02),
+	ILI9881C_COMMAND_INSTR(0x7e, 0x02),
+	ILI9881C_COMMAND_INSTR(0x7f, 0x01),
+	ILI9881C_COMMAND_INSTR(0x80, 0x00),
+	ILI9881C_COMMAND_INSTR(0x81, 0x02),
+	ILI9881C_COMMAND_INSTR(0x82, 0x14),
+	ILI9881C_COMMAND_INSTR(0x83, 0x15),
+	ILI9881C_COMMAND_INSTR(0x84, 0x02),
+	ILI9881C_COMMAND_INSTR(0x85, 0x02),
+	ILI9881C_COMMAND_INSTR(0x86, 0x02),
+	ILI9881C_COMMAND_INSTR(0x87, 0x02),
+	ILI9881C_COMMAND_INSTR(0x88, 0x02),
+	ILI9881C_COMMAND_INSTR(0x89, 0x02),
+	ILI9881C_COMMAND_INSTR(0x8A, 0x02),
+	ILI9881C_SWITCH_PAGE_INSTR(4),
+	ILI9881C_COMMAND_INSTR(0x6C, 0x15),
+	ILI9881C_COMMAND_INSTR(0x6E, 0x2A),
+	ILI9881C_COMMAND_INSTR(0x6F, 0x33),
+	ILI9881C_COMMAND_INSTR(0x3B, 0x98),
+	ILI9881C_COMMAND_INSTR(0x3a, 0x94),
+	ILI9881C_COMMAND_INSTR(0x8D, 0x14),
+	ILI9881C_COMMAND_INSTR(0x87, 0xBA),
+	ILI9881C_COMMAND_INSTR(0x26, 0x76),
+	ILI9881C_COMMAND_INSTR(0xB2, 0xD1),
+	ILI9881C_COMMAND_INSTR(0xB5, 0x06),
+	ILI9881C_COMMAND_INSTR(0x38, 0x01),
+	ILI9881C_COMMAND_INSTR(0x39, 0x00),
+	ILI9881C_SWITCH_PAGE_INSTR(1),
+	ILI9881C_COMMAND_INSTR(0x22, 0x0A),
+	ILI9881C_COMMAND_INSTR(0x31, 0x00),
+	ILI9881C_COMMAND_INSTR(0x53, 0x7d),
+	ILI9881C_COMMAND_INSTR(0x55, 0x8f),
+	ILI9881C_COMMAND_INSTR(0x40, 0x33),
+	ILI9881C_COMMAND_INSTR(0x50, 0x96),
+	ILI9881C_COMMAND_INSTR(0x51, 0x96),
+	ILI9881C_COMMAND_INSTR(0x60, 0x23),
+	ILI9881C_COMMAND_INSTR(0xA0, 0x08),
+	ILI9881C_COMMAND_INSTR(0xA1, 0x1d),
+	ILI9881C_COMMAND_INSTR(0xA2, 0x2a),
+	ILI9881C_COMMAND_INSTR(0xA3, 0x10),
+	ILI9881C_COMMAND_INSTR(0xA4, 0x15),
+	ILI9881C_COMMAND_INSTR(0xA5, 0x28),
+	ILI9881C_COMMAND_INSTR(0xA6, 0x1c),
+	ILI9881C_COMMAND_INSTR(0xA7, 0x1d),
+	ILI9881C_COMMAND_INSTR(0xA8, 0x7e),
+	ILI9881C_COMMAND_INSTR(0xA9, 0x1d),
+	ILI9881C_COMMAND_INSTR(0xAA, 0x29),
+	ILI9881C_COMMAND_INSTR(0xAB, 0x6b),
+	ILI9881C_COMMAND_INSTR(0xAC, 0x1a),
+	ILI9881C_COMMAND_INSTR(0xAD, 0x18),
+	ILI9881C_COMMAND_INSTR(0xAE, 0x4b),
+	ILI9881C_COMMAND_INSTR(0xAF, 0x20),
+	ILI9881C_COMMAND_INSTR(0xB0, 0x27),
+	ILI9881C_COMMAND_INSTR(0xB1, 0x50),
+	ILI9881C_COMMAND_INSTR(0xB2, 0x64),
+	ILI9881C_COMMAND_INSTR(0xB3, 0x39),
+	ILI9881C_COMMAND_INSTR(0xC0, 0x08),
+	ILI9881C_COMMAND_INSTR(0xC1, 0x1d),
+	ILI9881C_COMMAND_INSTR(0xC2, 0x2a),
+	ILI9881C_COMMAND_INSTR(0xC3, 0x10),
+	ILI9881C_COMMAND_INSTR(0xC4, 0x15),
+	ILI9881C_COMMAND_INSTR(0xC5, 0x28),
+	ILI9881C_COMMAND_INSTR(0xC6, 0x1c),
+	ILI9881C_COMMAND_INSTR(0xC7, 0x1d),
+	ILI9881C_COMMAND_INSTR(0xC8, 0x7e),
+	ILI9881C_COMMAND_INSTR(0xC9, 0x1d),
+	ILI9881C_COMMAND_INSTR(0xCA, 0x29),
+	ILI9881C_COMMAND_INSTR(0xCB, 0x6b),
+	ILI9881C_COMMAND_INSTR(0xCC, 0x1a),
+	ILI9881C_COMMAND_INSTR(0xCD, 0x18),
+	ILI9881C_COMMAND_INSTR(0xCE, 0x4b),
+	ILI9881C_COMMAND_INSTR(0xCF, 0x20),
+	ILI9881C_COMMAND_INSTR(0xD0, 0x27),
+	ILI9881C_COMMAND_INSTR(0xD1, 0x50),
+	ILI9881C_COMMAND_INSTR(0xD2, 0x64),
+	ILI9881C_COMMAND_INSTR(0xD3, 0x39),
+};
+
+static inline struct ili9881c *panel_to_ili9881c(struct drm_panel *panel)
+{
+	return container_of(panel, struct ili9881c, panel);
+}
+
+/*
+ * The panel seems to accept some private DCS commands that map
+ * directly to registers.
+ *
+ * It is organised by page, with each page having its own set of
+ * registers, and the first page looks like it's holding the standard
+ * DCS commands.
+ *
+ * So before any attempt at sending a command or data, we have to be
+ * sure if we're in the right page or not.
+ */
+static int ili9881c_switch_page(struct ili9881c *ctx, u8 page)
+{
+	u8 buf[4] = { 0xff, 0x98, 0x81, page };
+	int ret;
+
+	ret = mipi_dsi_dcs_write_buffer(ctx->dsi, buf, sizeof(buf));
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int ili9881c_send_cmd_data(struct ili9881c *ctx, u8 cmd, u8 data)
+{
+	u8 buf[2] = { cmd, data };
+	int ret;
+
+	ret = mipi_dsi_dcs_write_buffer(ctx->dsi, buf, sizeof(buf));
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int ili9881c_prepare(struct drm_panel *panel)
+{
+	struct ili9881c *ctx = panel_to_ili9881c(panel);
+	unsigned int i;
+	int ret;
+
+	/* Power the panel */
+	ret = regulator_enable(ctx->power);
+	if (ret)
+		return ret;
+	msleep(5);
+
+	/* And reset it */
+	gpiod_set_value_cansleep(ctx->reset, 1);
+	msleep(20);
+
+	gpiod_set_value_cansleep(ctx->reset, 0);
+	msleep(20);
+
+	for (i = 0; i < ctx->desc->init_length; i++) {
+		const struct ili9881c_instr *instr = &ctx->desc->init[i];
+
+		if (instr->op == ILI9881C_SWITCH_PAGE)
+			ret = ili9881c_switch_page(ctx, instr->arg.page);
+		else if (instr->op == ILI9881C_COMMAND)
+			ret = ili9881c_send_cmd_data(ctx, instr->arg.cmd.cmd,
+						      instr->arg.cmd.data);
+
+		if (ret)
+			return ret;
+	}
+
+	ret = ili9881c_switch_page(ctx, 0);
+	if (ret)
+		return ret;
+
+	ret = mipi_dsi_dcs_set_tear_on(ctx->dsi, MIPI_DSI_DCS_TEAR_MODE_VBLANK);
+	if (ret)
+		return ret;
+
+	ret = mipi_dsi_dcs_exit_sleep_mode(ctx->dsi);
+	if (ret)
+		return ret;
+
+	if (ctx->desc->flags & ILI9881_FLAGS_PANEL_ON_IN_PREPARE) {
+		msleep(120);
+
+		ret = mipi_dsi_dcs_set_display_on(ctx->dsi);
+	}
+
+	return 0;
+}
+
+static int ili9881c_enable(struct drm_panel *panel)
+{
+	struct ili9881c *ctx = panel_to_ili9881c(panel);
+
+	if (!(ctx->desc->flags & ILI9881_FLAGS_PANEL_ON_IN_PREPARE)) {
+		msleep(120);
+
+		mipi_dsi_dcs_set_display_on(ctx->dsi);
+	}
+
+	return 0;
+}
+
+static int ili9881c_disable(struct drm_panel *panel)
+{
+	struct ili9881c *ctx = panel_to_ili9881c(panel);
+
+	if (!(ctx->desc->flags & ILI9881_FLAGS_PANEL_ON_IN_PREPARE))
+		mipi_dsi_dcs_set_display_off(ctx->dsi);
+
+	return 0;
+}
+
+static int ili9881c_unprepare(struct drm_panel *panel)
+{
+	struct ili9881c *ctx = panel_to_ili9881c(panel);
+
+	if (!(ctx->desc->flags & ILI9881_FLAGS_NO_SHUTDOWN_CMDS)) {
+		if (ctx->desc->flags & ILI9881_FLAGS_PANEL_ON_IN_PREPARE)
+			mipi_dsi_dcs_set_display_off(ctx->dsi);
+
+		mipi_dsi_dcs_enter_sleep_mode(ctx->dsi);
+	}
+
+	regulator_disable(ctx->power);
+	gpiod_set_value_cansleep(ctx->reset, 1);
+
+	return 0;
+}
+
+static const struct drm_display_mode lhr050h41_default_mode = {
+	.clock		= 62000,
+
+	.hdisplay	= 720,
+	.hsync_start	= 720 + 10,
+	.hsync_end	= 720 + 10 + 20,
+	.htotal		= 720 + 10 + 20 + 30,
+
+	.vdisplay	= 1280,
+	.vsync_start	= 1280 + 10,
+	.vsync_end	= 1280 + 10 + 10,
+	.vtotal		= 1280 + 10 + 10 + 20,
+
+	.width_mm	= 62,
+	.height_mm	= 110,
+};
+
+static const struct drm_display_mode k101_im2byl02_default_mode = {
+	.clock		= 69700,
+
+	.hdisplay	= 800,
+	.hsync_start	= 800 + 52,
+	.hsync_end	= 800 + 52 + 8,
+	.htotal		= 800 + 52 + 8 + 48,
+
+	.vdisplay	= 1280,
+	.vsync_start	= 1280 + 16,
+	.vsync_end	= 1280 + 16 + 6,
+	.vtotal		= 1280 + 16 + 6 + 15,
+
+	.width_mm	= 135,
 	.height_mm	= 217,
 };
 
@@ -1441,6 +2278,75 @@ static const struct drm_display_mode am8001280g_default_mode = {
 	.height_mm	= 151,
 };
 
+static const struct drm_display_mode nwe080_default_mode = {
+	.clock          = 71750,
+
+	.hdisplay       = 800,
+	.hsync_start    = 800 + 52,
+	.hsync_end      = 800 + 52 + 8,
+	.htotal         = 800 + 52 + 8 + 48,
+
+	.vdisplay       = 1280,
+	.vsync_start    = 1280 + 16,
+	.vsync_end      = 1280 + 16 + 6,
+	.vtotal         = 1280 + 16 + 6 + 15,
+
+	.width_mm       = 107,
+	.height_mm      = 170,
+};
+
+static const struct drm_display_mode cfaf7201280a0_050tx_default_mode = {
+	/*
+	 * These timings are a compromise so the panel will work with
+	 * both the CM4 and CM5.
+	 */
+	.clock          = 78000,
+	.hdisplay       = 720,
+	.hsync_start    = 720 + 120,
+	.hsync_end      = 720 + 120 + 2,
+	.htotal         = 720 + 120 + 2 + 80,
+	.vdisplay       = 1280,
+	.vsync_start    = 1280 + 60,
+	.vsync_end      = 1280 + 60 + 2,
+	.vtotal         = 1280 + 60 + 2 + 90,
+	.width_mm       = 62,
+	.height_mm      = 110,
+};
+
+static const struct drm_display_mode rpi_5inch_default_mode = {
+	.clock		= 83333,
+
+	.hdisplay	= 720,
+	.hsync_start	= 720 + 110,
+	.hsync_end	= 720 + 110 + 2,
+	.htotal		= 720 + 110 + 2 + 105,
+
+	.vdisplay	= 1280,
+	.vsync_start	= 1280 + 100,
+	.vsync_end	= 1280 + 100 + 2,
+	.vtotal		= 1280 + 100 + 2 + 100,
+
+	.width_mm	= 62,
+	.height_mm	= 110,
+};
+
+static const struct drm_display_mode rpi_7inch_default_mode = {
+	.clock          = 83330,
+
+	.hdisplay	= 720,
+	.hsync_start	= 720 + 239,
+	.hsync_end	= 720 + 239 + 33,
+	.htotal		= 720 + 239 + 33 + 50,
+
+	.vdisplay	= 1280,
+	.vsync_start	= 1280 + 20,
+	.vsync_end	= 1280 + 20 + 2,
+	.vtotal		= 1280 + 20 + 2 + 30,
+
+	.width_mm	= 90,
+	.height_mm	= 151,
+};
+
 static int ili9881c_get_modes(struct drm_panel *panel,
 			      struct drm_connector *connector)
 {
@@ -1501,6 +2407,7 @@ static int ili9881c_dsi_probe(struct mipi_dsi_device *dsi)
 	ctx->dsi = dsi;
 	ctx->desc = of_device_get_match_data(&dsi->dev);
 
+	ctx->panel.prepare_prev_first = true;
 	drm_panel_init(&ctx->panel, &dsi->dev, &ili9881c_funcs,
 		       DRM_MODE_CONNECTOR_DSI);
 
@@ -1531,9 +2438,13 @@ static int ili9881c_dsi_probe(struct mipi_dsi_device *dsi)
 
 	dsi->mode_flags = ctx->desc->mode_flags;
 	dsi->format = MIPI_DSI_FMT_RGB888;
-	dsi->lanes = 4;
+	dsi->lanes = ctx->desc->lanes;
+
+	ret = mipi_dsi_attach(dsi);
+	if (ret)
+		drm_panel_remove(&ctx->panel);
 
-	return mipi_dsi_attach(dsi);
+	return ret;
 }
 
 static void ili9881c_dsi_remove(struct mipi_dsi_device *dsi)
@@ -1542,6 +2453,9 @@ static void ili9881c_dsi_remove(struct mipi_dsi_device *dsi)
 
 	mipi_dsi_detach(dsi);
 	drm_panel_remove(&ctx->panel);
+
+	gpiod_set_value_cansleep(ctx->reset, 1);
+	regulator_disable(ctx->power);
 }
 
 static const struct ili9881c_desc lhr050h41_desc = {
@@ -1549,6 +2463,7 @@ static const struct ili9881c_desc lhr050h41_desc = {
 	.init_length = ARRAY_SIZE(lhr050h41_init),
 	.mode = &lhr050h41_default_mode,
 	.mode_flags = MIPI_DSI_MODE_VIDEO_SYNC_PULSE,
+	.lanes = 4,
 };
 
 static const struct ili9881c_desc k101_im2byl02_desc = {
@@ -1556,6 +2471,7 @@ static const struct ili9881c_desc k101_im2byl02_desc = {
 	.init_length = ARRAY_SIZE(k101_im2byl02_init),
 	.mode = &k101_im2byl02_default_mode,
 	.mode_flags = MIPI_DSI_MODE_VIDEO_SYNC_PULSE,
+	.lanes = 4,
 };
 
 static const struct ili9881c_desc kd050hdfia020_desc = {
@@ -1580,6 +2496,7 @@ static const struct ili9881c_desc w552946aba_desc = {
 	.mode = &w552946aba_default_mode,
 	.mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
 		      MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET,
+	.lanes = 4,
 };
 
 static const struct ili9881c_desc am8001280g_desc = {
@@ -1590,6 +2507,40 @@ static const struct ili9881c_desc am8001280g_desc = {
 		      MIPI_DSI_CLOCK_NON_CONTINUOUS | MIPI_DSI_MODE_LPM,
 };
 
+static const struct ili9881c_desc nwe080_desc = {
+	.init = nwe080_init,
+	.init_length = ARRAY_SIZE(nwe080_init),
+	.mode = &nwe080_default_mode,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_SYNC_PULSE | MIPI_DSI_MODE_VIDEO,
+	.lanes = 4,
+};
+
+static const struct ili9881c_desc cfaf7201280a0_050tx_desc = {
+	.init = cfaf7201280a0_050tx_init,
+	.init_length = ARRAY_SIZE(cfaf7201280a0_050tx_init),
+	.mode = &cfaf7201280a0_050tx_default_mode,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_SYNC_PULSE | MIPI_DSI_MODE_VIDEO,
+	.lanes = 4,
+};
+
+static const struct ili9881c_desc rpi_5inch_desc = {
+	.init = rpi_5inch_init,
+	.init_length = ARRAY_SIZE(rpi_5inch_init),
+	.mode = &rpi_5inch_default_mode,
+	.mode_flags =  MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM,
+	.lanes = 2,
+};
+
+static const struct ili9881c_desc rpi_7inch_desc = {
+	.init = rpi_7inch_init,
+	.init_length = ARRAY_SIZE(rpi_7inch_init),
+	.mode = &rpi_7inch_default_mode,
+	.mode_flags =  MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM,
+	.lanes = 2,
+	.flags = ILI9881_FLAGS_NO_SHUTDOWN_CMDS |
+		 ILI9881_FLAGS_PANEL_ON_IN_PREPARE,
+};
+
 static const struct of_device_id ili9881c_of_match[] = {
 	{ .compatible = "bananapi,lhr050h41", .data = &lhr050h41_desc },
 	{ .compatible = "feixin,k101-im2byl02", .data = &k101_im2byl02_desc },
@@ -1597,6 +2548,10 @@ static const struct of_device_id ili9881c_of_match[] = {
 	{ .compatible = "tdo,tl050hdv35", .data = &tl050hdv35_desc },
 	{ .compatible = "wanchanglong,w552946aba", .data = &w552946aba_desc },
 	{ .compatible = "ampire,am8001280g", .data = &am8001280g_desc },
+	{ .compatible = "crystalfontz,cfaf7201280a0_050tx", .data = &cfaf7201280a0_050tx_desc },
+	{ .compatible = "nwe,nwe080", .data = &nwe080_desc },
+	{ .compatible = "raspberrypi,dsi-5inch", &rpi_5inch_desc },
+	{ .compatible = "raspberrypi,dsi-7inch", &rpi_7inch_desc },
 	{ }
 };
 MODULE_DEVICE_TABLE(of, ili9881c_of_match);
diff --git a/drivers/gpu/drm/panel/panel-jdi-lt070me05000.c b/drivers/gpu/drm/panel/panel-jdi-lt070me05000.c
index b1ce186de261..e3076a2d8b8e 100644
--- a/drivers/gpu/drm/panel/panel-jdi-lt070me05000.c
+++ b/drivers/gpu/drm/panel/panel-jdi-lt070me05000.c
@@ -190,11 +190,11 @@ static int jdi_panel_unprepare(struct drm_panel *panel)
 	if (ret < 0)
 		dev_err(dev, "regulator disable failed, %d\n", ret);
 
-	gpiod_set_value(jdi->enable_gpio, 0);
+	gpiod_set_value_cansleep(jdi->enable_gpio, 0);
 
-	gpiod_set_value(jdi->reset_gpio, 1);
+	gpiod_set_value_cansleep(jdi->reset_gpio, 1);
 
-	gpiod_set_value(jdi->dcdc_en_gpio, 0);
+	gpiod_set_value_cansleep(jdi->dcdc_en_gpio, 0);
 
 	return 0;
 }
@@ -213,13 +213,13 @@ static int jdi_panel_prepare(struct drm_panel *panel)
 
 	msleep(20);
 
-	gpiod_set_value(jdi->dcdc_en_gpio, 1);
+	gpiod_set_value_cansleep(jdi->dcdc_en_gpio, 1);
 	usleep_range(10, 20);
 
-	gpiod_set_value(jdi->reset_gpio, 0);
+	gpiod_set_value_cansleep(jdi->reset_gpio, 0);
 	usleep_range(10, 20);
 
-	gpiod_set_value(jdi->enable_gpio, 1);
+	gpiod_set_value_cansleep(jdi->enable_gpio, 1);
 	usleep_range(10, 20);
 
 	ret = jdi_panel_init(jdi);
@@ -241,11 +241,11 @@ static int jdi_panel_prepare(struct drm_panel *panel)
 	if (ret < 0)
 		dev_err(dev, "regulator disable failed, %d\n", ret);
 
-	gpiod_set_value(jdi->enable_gpio, 0);
+	gpiod_set_value_cansleep(jdi->enable_gpio, 0);
 
-	gpiod_set_value(jdi->reset_gpio, 1);
+	gpiod_set_value_cansleep(jdi->reset_gpio, 1);
 
-	gpiod_set_value(jdi->dcdc_en_gpio, 0);
+	gpiod_set_value_cansleep(jdi->dcdc_en_gpio, 0);
 
 	return ret;
 }
@@ -402,6 +402,7 @@ static int jdi_panel_add(struct jdi_panel *jdi)
 		return dev_err_probe(dev, PTR_ERR(jdi->backlight),
 				     "failed to register backlight %d\n", ret);
 
+	jdi->base.prepare_prev_first = true;
 	drm_panel_init(&jdi->base, &jdi->dsi->dev, &jdi_panel_funcs,
 		       DRM_MODE_CONNECTOR_DSI);
 
diff --git a/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c b/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c
index 4618c892cdd6..4c418962aa9b 100644
--- a/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c
+++ b/drivers/gpu/drm/panel/panel-raspberrypi-touchscreen.c
@@ -218,7 +218,35 @@ static struct rpi_touchscreen *panel_to_ts(struct drm_panel *panel)
 
 static int rpi_touchscreen_i2c_read(struct rpi_touchscreen *ts, u8 reg)
 {
-	return i2c_smbus_read_byte_data(ts->i2c, reg);
+	struct i2c_client *client = ts->i2c;
+	struct i2c_msg msgs[1];
+	u8 addr_buf[1] = { reg };
+	u8 data_buf[1] = { 0, };
+	int ret;
+
+	/* Write register address */
+	msgs[0].addr = client->addr;
+	msgs[0].flags = 0;
+	msgs[0].len = ARRAY_SIZE(addr_buf);
+	msgs[0].buf = addr_buf;
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret != ARRAY_SIZE(msgs))
+		return -EIO;
+
+	usleep_range(100, 300);
+
+	/* Read data from register */
+	msgs[0].addr = client->addr;
+	msgs[0].flags = I2C_M_RD;
+	msgs[0].len = 1;
+	msgs[0].buf = data_buf;
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret != ARRAY_SIZE(msgs))
+		return -EIO;
+
+	return data_buf[0];
 }
 
 static void rpi_touchscreen_i2c_write(struct rpi_touchscreen *ts,
@@ -267,12 +295,21 @@ static int rpi_touchscreen_noop(struct drm_panel *panel)
 static int rpi_touchscreen_prepare(struct drm_panel *panel)
 {
 	struct rpi_touchscreen *ts = panel_to_ts(panel);
-	int i;
+	int i, data;
 
+	/*
+	 * Power up the Toshiba bridge. The Atmel device can misbehave
+	 * over I2C for a few ms after writes to REG_POWERON (including the
+	 * write in rpi_touchscreen_disable()), so sleep before and after.
+	 * Also to ensure that the bridge has been off for at least 100ms.
+	 */
+	msleep(100);
 	rpi_touchscreen_i2c_write(ts, REG_POWERON, 1);
+	usleep_range(20000, 25000);
 	/* Wait for nPWRDWN to go low to indicate poweron is done. */
 	for (i = 0; i < 100; i++) {
-		if (rpi_touchscreen_i2c_read(ts, REG_PORTB) & 1)
+		data = rpi_touchscreen_i2c_read(ts, REG_PORTB);
+		if (data >= 0 && (data & 1))
 			break;
 	}
 
@@ -398,6 +435,7 @@ static int rpi_touchscreen_probe(struct i2c_client *i2c)
 
 	/* Turn off at boot, so we can cleanly sequence powering on. */
 	rpi_touchscreen_i2c_write(ts, REG_POWERON, 0);
+	usleep_range(20000, 25000);
 
 	/* Look up the DSI host.  It needs to probe before we do. */
 	endpoint = of_graph_get_next_endpoint(dev->of_node, NULL);
diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c
index 32d876f6684e..1e086ba6bb82 100644
--- a/drivers/gpu/drm/panel/panel-simple.c
+++ b/drivers/gpu/drm/panel/panel-simple.c
@@ -151,8 +151,6 @@ struct panel_simple {
 	const struct drm_edid *drm_edid;
 
 	struct drm_display_mode override_mode;
-
-	enum drm_panel_orientation orientation;
 };
 
 static inline struct panel_simple *to_panel_simple(struct drm_panel *panel)
@@ -387,12 +385,6 @@ static int panel_simple_get_modes(struct drm_panel *panel,
 	/* add hard-coded panel modes */
 	num += panel_simple_get_non_edid_modes(p, connector);
 
-	/*
-	 * TODO: Remove once all drm drivers call
-	 * drm_connector_set_orientation_from_panel()
-	 */
-	drm_connector_set_panel_orientation(connector, p->orientation);
-
 	return num;
 }
 
@@ -413,20 +405,12 @@ static int panel_simple_get_timings(struct drm_panel *panel,
 	return p->desc->num_timings;
 }
 
-static enum drm_panel_orientation panel_simple_get_orientation(struct drm_panel *panel)
-{
-	struct panel_simple *p = to_panel_simple(panel);
-
-	return p->orientation;
-}
-
 static const struct drm_panel_funcs panel_simple_funcs = {
 	.disable = panel_simple_disable,
 	.unprepare = panel_simple_unprepare,
 	.prepare = panel_simple_prepare,
 	.enable = panel_simple_enable,
 	.get_modes = panel_simple_get_modes,
-	.get_orientation = panel_simple_get_orientation,
 	.get_timings = panel_simple_get_timings,
 };
 
@@ -463,6 +447,7 @@ static int panel_dpi_probe(struct device *dev,
 
 	of_property_read_u32(np, "width-mm", &desc->size.width);
 	of_property_read_u32(np, "height-mm", &desc->size.height);
+	of_property_read_u32(np, "bus-format", &desc->bus_format);
 
 	/* Extract bus_flags from display_timing */
 	bus_flags = 0;
@@ -472,6 +457,8 @@ static int panel_dpi_probe(struct device *dev,
 
 	/* We do not know the connector for the DT node, so guess it */
 	desc->connector_type = DRM_MODE_CONNECTOR_DPI;
+	/* Likewise for the bit depth. */
+	desc->bpc = 8;
 
 	panel->desc = desc;
 
@@ -595,12 +582,6 @@ static int panel_simple_probe(struct device *dev, const struct panel_desc *desc)
 		return dev_err_probe(dev, PTR_ERR(panel->enable_gpio),
 				     "failed to request GPIO\n");
 
-	err = of_drm_get_panel_orientation(dev->of_node, &panel->orientation);
-	if (err) {
-		dev_err(dev, "%pOF: failed to get orientation %d\n", dev->of_node, err);
-		return err;
-	}
-
 	ddc = of_parse_phandle(dev->of_node, "ddc-i2c-bus", 0);
 	if (ddc) {
 		panel->ddc = of_find_i2c_adapter_by_node(ddc);
@@ -2264,6 +2245,32 @@ static const struct panel_desc friendlyarm_hd702e = {
 	},
 };
 
+static const struct drm_display_mode geekworm_mzp280_mode = {
+	.clock = 32000,
+	.hdisplay = 480,
+	.hsync_start = 480 + 41,
+	.hsync_end = 480 + 41 + 20,
+	.htotal = 480 + 41 + 20 + 60,
+	.vdisplay = 640,
+	.vsync_start = 640 + 5,
+	.vsync_end = 640 + 5 + 10,
+	.vtotal = 640 + 5 + 10 + 10,
+	.flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
+};
+
+static const struct panel_desc geekworm_mzp280 = {
+	.modes = &geekworm_mzp280_mode,
+	.num_modes = 1,
+	.bpc = 6,
+	.size = {
+		.width = 47,
+		.height = 61,
+	},
+	.bus_format = MEDIA_BUS_FMT_RGB565_1X24_CPADHI,
+	.bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE,
+	.connector_type = DRM_MODE_CONNECTOR_DPI,
+};
+
 static const struct drm_display_mode giantplus_gpg482739qs5_mode = {
 	.clock = 9000,
 	.hdisplay = 480,
@@ -2444,6 +2451,38 @@ static const struct panel_desc innolux_at043tn24 = {
 	.bus_flags = DRM_BUS_FLAG_DE_HIGH | DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE,
 };
 
+static const struct display_timing innolux_at056tn53v1_timing = {
+	.pixelclock = { 39700000, 39700000, 39700000},
+	.hactive = { 640, 640, 640 },
+	.hfront_porch = { 16, 16, 16 },
+	.hback_porch = { 134, 134, 134 },
+	.hsync_len = { 10, 10, 10},
+	.vactive = { 480, 480, 480 },
+	.vfront_porch = { 32, 32, 32},
+	.vback_porch = { 11, 11, 11 },
+	.vsync_len = { 2, 2, 2 },
+	.flags = DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_PHSYNC,
+};
+
+static const struct panel_desc innolux_at056tn53v1 = {
+	.timings = &innolux_at056tn53v1_timing,
+	.num_timings = 1,
+	.bpc = 6,
+	.size = {
+		.width = 112,
+		.height = 84,
+	},
+	.delay = {
+		.prepare = 50,
+		.enable = 200,
+		.disable = 110,
+		.unprepare = 200,
+	},
+	.bus_format = MEDIA_BUS_FMT_BGR666_1X24_CPADHI,
+	.bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE,
+	.connector_type = DRM_MODE_CONNECTOR_DPI,
+};
+
 static const struct drm_display_mode innolux_at070tn92_mode = {
 	.clock = 33333,
 	.hdisplay = 800,
@@ -3857,6 +3896,31 @@ static const struct panel_desc rocktech_rk043fn48h = {
 	.connector_type = DRM_MODE_CONNECTOR_DPI,
 };
 
+static const struct drm_display_mode raspberrypi_7inch_mode = {
+	.clock = 30000,
+	.hdisplay = 800,
+	.hsync_start = 800 + 131,
+	.hsync_end = 800 + 131 + 2,
+	.htotal = 800 + 131 + 2 + 45,
+	.vdisplay = 480,
+	.vsync_start = 480 + 7,
+	.vsync_end = 480 + 7 + 2,
+	.vtotal = 480 + 7 + 2 + 22,
+	.flags = DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC,
+};
+
+static const struct panel_desc raspberrypi_7inch = {
+	.modes = &raspberrypi_7inch_mode,
+	.num_modes = 1,
+	.bpc = 8,
+	.size = {
+		.width = 154,
+		.height = 86,
+	},
+	.bus_format = MEDIA_BUS_FMT_RGB888_1X24,
+	.connector_type = DRM_MODE_CONNECTOR_DSI,
+};
+
 static const struct display_timing rocktech_rk070er9427_timing = {
 	.pixelclock = { 26400000, 33300000, 46800000 },
 	.hactive = { 800, 800, 800 },
@@ -4800,6 +4864,9 @@ static const struct of_device_id platform_of_match[] = {
 	}, {
 		.compatible = "friendlyarm,hd702e",
 		.data = &friendlyarm_hd702e,
+	}, {
+		.compatible = "geekworm,mzp280",
+		.data = &geekworm_mzp280,
 	}, {
 		.compatible = "giantplus,gpg482739qs5",
 		.data = &giantplus_gpg482739qs5
@@ -4821,6 +4888,9 @@ static const struct of_device_id platform_of_match[] = {
 	}, {
 		.compatible = "innolux,at043tn24",
 		.data = &innolux_at043tn24,
+	}, {
+		.compatible = "innolux,at056tn53v1",
+		.data = &innolux_at056tn53v1,
 	}, {
 		.compatible = "innolux,at070tn92",
 		.data = &innolux_at070tn92,
@@ -4980,6 +5050,9 @@ static const struct of_device_id platform_of_match[] = {
 	}, {
 		.compatible = "rocktech,rk043fn48h",
 		.data = &rocktech_rk043fn48h,
+	}, {
+		.compatible = "raspberrypi,7inch-dsi",
+		.data = &raspberrypi_7inch,
 	}, {
 		.compatible = "rocktech,rk070er9427",
 		.data = &rocktech_rk070er9427,
@@ -5337,6 +5410,9 @@ static const struct panel_desc_dsi osd101t2045_53ts = {
 	.lanes = 4,
 };
 
+// for panels using generic panel-dsi binding
+static struct panel_desc_dsi panel_dsi;
+
 static const struct of_device_id dsi_of_match[] = {
 	{
 		.compatible = "auo,b080uan01",
@@ -5359,21 +5435,138 @@ static const struct of_device_id dsi_of_match[] = {
 	}, {
 		.compatible = "osddisplays,osd101t2045-53ts",
 		.data = &osd101t2045_53ts
+	}, {
+		/* Must be the last entry */
+		.compatible = "panel-dsi",
+		.data = &panel_dsi,
 	}, {
 		/* sentinel */
 	}
 };
 MODULE_DEVICE_TABLE(of, dsi_of_match);
 
+
+/* Checks for DSI panel definition in device-tree, analog to panel_dpi */
+static int panel_dsi_dt_probe(struct device *dev,
+			  struct panel_desc_dsi *desc_dsi)
+{
+	struct panel_desc *desc;
+	struct display_timing *timing;
+	const struct device_node *np;
+	const char *dsi_color_format;
+	const char *dsi_mode_flags;
+	struct property *prop;
+	int dsi_lanes, ret;
+
+	np = dev->of_node;
+
+	desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
+	if (!desc)
+		return -ENOMEM;
+
+	timing = devm_kzalloc(dev, sizeof(*timing), GFP_KERNEL);
+	if (!timing)
+		return -ENOMEM;
+
+	ret = of_get_display_timing(np, "panel-timing", timing);
+	if (ret < 0) {
+		dev_err(dev, "%pOF: no panel-timing node found for \"panel-dsi\" binding\n",
+			np);
+		return ret;
+	}
+
+	desc->timings = timing;
+	desc->num_timings = 1;
+
+	of_property_read_u32(np, "width-mm", &desc->size.width);
+	of_property_read_u32(np, "height-mm", &desc->size.height);
+
+	dsi_lanes = drm_of_get_data_lanes_count_ep(np, 0, 0, 1, 4);
+
+	if (dsi_lanes < 0) {
+		dev_err(dev, "%pOF: no or too many data-lanes defined", np);
+		return dsi_lanes;
+	}
+
+	desc_dsi->lanes = dsi_lanes;
+
+	of_property_read_string(np, "dsi-color-format", &dsi_color_format);
+	if (!strcmp(dsi_color_format, "RGB888")) {
+		desc_dsi->format = MIPI_DSI_FMT_RGB888;
+		desc->bpc = 8;
+	} else if (!strcmp(dsi_color_format, "RGB565")) {
+		desc_dsi->format = MIPI_DSI_FMT_RGB565;
+		desc->bpc = 6;
+	} else if (!strcmp(dsi_color_format, "RGB666")) {
+		desc_dsi->format = MIPI_DSI_FMT_RGB666;
+		desc->bpc = 6;
+	} else if (!strcmp(dsi_color_format, "RGB666_PACKED")) {
+		desc_dsi->format = MIPI_DSI_FMT_RGB666_PACKED;
+		desc->bpc = 6;
+	} else {
+		dev_err(dev, "%pOF: no valid dsi-color-format defined", np);
+		return -EINVAL;
+	}
+
+
+	of_property_for_each_string(np, "mode", prop, dsi_mode_flags) {
+		if (!strcmp(dsi_mode_flags, "MODE_VIDEO"))
+			desc_dsi->flags |= MIPI_DSI_MODE_VIDEO;
+		else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_BURST"))
+			desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_BURST;
+		else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_SYNC_PULSE"))
+			desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
+		else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_AUTO_VERT"))
+			desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_AUTO_VERT;
+		else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_HSE"))
+			desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_HSE;
+		else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_NO_HFP"))
+			desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_NO_HFP;
+		else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_NO_HBP"))
+			desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_NO_HBP;
+		else if (!strcmp(dsi_mode_flags, "MODE_VIDEO_NO_HSA"))
+			desc_dsi->flags |= MIPI_DSI_MODE_VIDEO_NO_HSA;
+		else if (!strcmp(dsi_mode_flags, "MODE_VSYNC_FLUSH"))
+			desc_dsi->flags |= MIPI_DSI_MODE_VSYNC_FLUSH;
+		else if (!strcmp(dsi_mode_flags, "MODE_NO_EOT_PACKET"))
+			desc_dsi->flags |= MIPI_DSI_MODE_NO_EOT_PACKET;
+		else if (!strcmp(dsi_mode_flags, "CLOCK_NON_CONTINUOUS"))
+			desc_dsi->flags |= MIPI_DSI_CLOCK_NON_CONTINUOUS;
+		else if (!strcmp(dsi_mode_flags, "MODE_LPM"))
+			desc_dsi->flags |= MIPI_DSI_MODE_LPM;
+		else if (!strcmp(dsi_mode_flags, "HS_PKT_END_ALIGNED"))
+			desc_dsi->flags |= MIPI_DSI_HS_PKT_END_ALIGNED;
+	}
+
+	desc->connector_type = DRM_MODE_CONNECTOR_DSI;
+	desc_dsi->desc = *desc;
+
+	return 0;
+}
+
 static int panel_simple_dsi_probe(struct mipi_dsi_device *dsi)
 {
 	const struct panel_desc_dsi *desc;
+	struct panel_desc_dsi *dt_desc;
 	int err;
 
 	desc = of_device_get_match_data(&dsi->dev);
 	if (!desc)
 		return -ENODEV;
 
+	if (desc == &panel_dsi) {
+		/* Handle the generic panel-dsi binding */
+		dt_desc = devm_kzalloc(&dsi->dev, sizeof(*dt_desc), GFP_KERNEL);
+		if (!dt_desc)
+			return -ENOMEM;
+
+		err = panel_dsi_dt_probe(&dsi->dev, dt_desc);
+		if (err < 0)
+			return err;
+
+		desc = dt_desc;
+	}
+
 	err = panel_simple_probe(&dsi->dev, &desc->desc);
 	if (err < 0)
 		return err;
diff --git a/drivers/gpu/drm/panel/panel-sitronix-st7701.c b/drivers/gpu/drm/panel/panel-sitronix-st7701.c
index 1f72ef7ca74c..b8daf67e773d 100644
--- a/drivers/gpu/drm/panel/panel-sitronix-st7701.c
+++ b/drivers/gpu/drm/panel/panel-sitronix-st7701.c
@@ -12,6 +12,7 @@
 #include <linux/bitfield.h>
 #include <linux/gpio/consumer.h>
 #include <linux/delay.h>
+#include <linux/media-bus-format.h>
 #include <linux/module.h>
 #include <linux/of.h>
 #include <linux/regulator/consumer.h>
@@ -106,6 +107,7 @@ struct st7701_panel_desc {
 	const struct drm_display_mode *mode;
 	unsigned int lanes;
 	enum mipi_dsi_pixel_format format;
+	u32 mediabus_format;
 	unsigned int panel_sleep_delay;
 
 	/* TFT matrix driver configuration, panel specific. */
@@ -520,6 +522,94 @@ static void rg28xx_gip_sequence(struct st7701 *st7701)
 	st7701_switch_cmd_bkx(st7701, false, 0);
 }
 
+static void txw210001b0_gip_sequence(struct st7701 *st7701)
+{
+	ST7701_WRITE(st7701, MIPI_DCS_SOFT_RESET);
+
+	usleep_range(5000, 7000);
+
+	st7701_switch_cmd_bkx(st7701, true, 0);
+
+	ST7701_WRITE(st7701, ST7701_CMD2_BK0_LNESET, 0x3B, 0x0);
+
+	ST7701_WRITE(st7701, ST7701_CMD2_BK0_PORCTRL, 0xB, 0x2);
+
+	ST7701_WRITE(st7701, ST7701_CMD2_BK0_INVSEL, 0x0, 0x2);
+
+	ST7701_WRITE(st7701, 0xCC, 0x10);
+
+	st7701_switch_cmd_bkx(st7701, true, 1);
+
+	ST7701_WRITE(st7701, ST7701_CMD2_BK1_VRHS, 0x5D);
+
+	ST7701_WRITE(st7701, ST7701_CMD2_BK1_VCOM, 0x43);
+
+	ST7701_WRITE(st7701, ST7701_CMD2_BK1_VGHSS, 0x81);
+
+	ST7701_WRITE(st7701, ST7701_CMD2_BK1_TESTCMD, 0x80);
+
+	ST7701_WRITE(st7701, ST7701_CMD2_BK1_VGLS, 0x43);
+
+	ST7701_WRITE(st7701, ST7701_CMD2_BK1_PWCTLR1, 0x85);
+
+	ST7701_WRITE(st7701, ST7701_CMD2_BK1_PWCTLR2, 0x20);
+
+	ST7701_WRITE(st7701, ST7701_CMD2_BK1_SPD1, 0x78);
+
+	ST7701_WRITE(st7701, ST7701_CMD2_BK1_SPD2, 0x78);
+
+	ST7701_WRITE(st7701, ST7701_CMD2_BK1_MIPISET1, 0x88);
+
+	ST7701_WRITE(st7701, 0xE0, 0x0, 0x0, 0x2);
+
+	ST7701_WRITE(st7701, 0xE1,
+		     0x3, 0xA0, 0x0, 0x0, 0x4, 0xA0, 0x0, 0x0,
+		     0x0, 0x20, 0x20);
+
+	ST7701_WRITE(st7701, 0xE2,
+		     0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+		     0x0, 0x0, 0x0, 0x0, 0x0);
+
+	ST7701_WRITE(st7701, 0xE3, 0x0, 0x0, 0x11, 0x0);
+
+	ST7701_WRITE(st7701, 0xE4, 0x22, 0x0);
+
+	ST7701_WRITE(st7701, 0xE5,
+		     0x5, 0xEC, 0xA0, 0xA0, 0x7, 0xEE, 0xA0, 0xA0,
+		     0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0);
+
+	ST7701_WRITE(st7701, 0xE6, 0x0, 0x0, 0x11, 0x0);
+
+	ST7701_WRITE(st7701, 0xE7, 0x22, 0x0);
+
+	ST7701_WRITE(st7701, 0xE8,
+		     0x6, 0xED, 0xA0, 0xA0, 0x8, 0xEF, 0xA0, 0xA0,
+		     0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0);
+
+	ST7701_WRITE(st7701, 0xEB,
+		     0x0, 0x0, 0x40, 0x40, 0x0, 0x0, 0x0);
+
+	ST7701_WRITE(st7701, 0xED,
+		     0xFF, 0xFF, 0xFF, 0xBA, 0xA, 0xBF, 0x45, 0xFF,
+		     0xFF, 0x54, 0xFB, 0xA0, 0xAB, 0xFF, 0xFF, 0xFF);
+
+	ST7701_WRITE(st7701, 0xEF, 0x10, 0xD, 0x4, 0x8, 0x3F, 0x1F);
+
+	st7701_switch_cmd_bkx(st7701, true, 3);
+
+	ST7701_WRITE(st7701, 0xEF, 0x8);
+
+	st7701_switch_cmd_bkx(st7701, false, 0);
+
+	ST7701_WRITE(st7701, 0xCD, 0x8);  /* RGB format COLCTRL */
+
+	ST7701_WRITE(st7701, 0x36, 0x8); /* MadCtl */
+
+	ST7701_WRITE(st7701, 0x3A, 0x66);  /* Colmod */
+
+	ST7701_WRITE(st7701, MIPI_DCS_EXIT_SLEEP_MODE);
+}
+
 static int st7701_prepare(struct drm_panel *panel)
 {
 	struct st7701 *st7701 = panel_to_st7701(panel);
@@ -609,6 +699,11 @@ static int st7701_get_modes(struct drm_panel *panel,
 	drm_mode_set_name(mode);
 	drm_mode_probed_add(connector, mode);
 
+	if (st7701->desc->mediabus_format)
+		drm_display_info_set_bus_formats(&connector->display_info,
+						 &st7701->desc->mediabus_format,
+						 1);
+
 	connector->display_info.width_mm = desc_mode->width_mm;
 	connector->display_info.height_mm = desc_mode->height_mm;
 
@@ -1135,6 +1230,154 @@ static const struct st7701_panel_desc rg28xx_desc = {
 	.gip_sequence = rg28xx_gip_sequence,
 };
 
+static const struct drm_display_mode txw210001b0_mode = {
+	.clock		= 19200,
+
+	.hdisplay	= 480,
+	.hsync_start	= 480 + 10,
+	.hsync_end	= 480 + 10 + 16,
+	.htotal		= 480 + 10 + 16 + 56,
+
+	.vdisplay	= 480,
+	.vsync_start	= 480 + 15,
+	.vsync_end	= 480 + 15 + 60,
+	.vtotal		= 480 + 15 + 60 + 15,
+
+	.width_mm	= 53,
+	.height_mm	= 53,
+	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
+
+	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
+};
+
+static const struct st7701_panel_desc txw210001b0_desc = {
+	.mode = &txw210001b0_mode,
+	.mediabus_format = MEDIA_BUS_FMT_RGB888_1X24,
+	.pv_gamma = {
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC0_MASK, 0x2),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC4_MASK, 0x13),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC8_MASK, 0x1b),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC16_MASK, 0xd),
+
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC24_MASK, 0x10),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC52_MASK, 0x5),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC80_MASK, 0x8),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC108_MASK, 0x7),
+
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC147_MASK, 0x7),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC175_MASK, 0x24),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC203_MASK, 0x4),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC231_MASK, 0x11),
+
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC239_MASK, 0xe),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC247_MASK, 0x2c),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC251_MASK, 0x33),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC255_MASK, 0x1d)
+	},
+	.nv_gamma = {
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC0_MASK, 0x5),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC4_MASK, 0x13),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0x0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC8_MASK, 0x1b),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC16_MASK, 0xd),
+
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC24_MASK, 0x11),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC52_MASK, 0x5),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC80_MASK, 0x8),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC108_MASK, 0x7),
+
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC147_MASK, 0x7),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC175_MASK, 0x24),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC203_MASK, 0x4),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC231_MASK, 0x11),
+
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC239_MASK, 0xe),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC247_MASK, 0x2c),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC251_MASK, 0x33),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC255_MASK, 0x1d)
+	},
+	.gip_sequence = txw210001b0_gip_sequence,
+};
+
+static const struct st7701_panel_desc hyperpixel2r_desc = {
+	.mode = &txw210001b0_mode,
+	.mediabus_format = MEDIA_BUS_FMT_RGB666_1X24_CPADHI,
+	.pv_gamma = {
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC0_MASK, 0x2),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC4_MASK, 0x13),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC8_MASK, 0x1b),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC16_MASK, 0xd),
+
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC24_MASK, 0x10),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC52_MASK, 0x5),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC80_MASK, 0x8),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC108_MASK, 0x7),
+
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC147_MASK, 0x7),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC175_MASK, 0x24),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC203_MASK, 0x4),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC231_MASK, 0x11),
+
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC239_MASK, 0xe),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC247_MASK, 0x2c),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC251_MASK, 0x33),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC255_MASK, 0x1d)
+	},
+	.nv_gamma = {
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC0_MASK, 0x5),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC4_MASK, 0x13),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0x0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC8_MASK, 0x1b),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC16_MASK, 0xd),
+
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC24_MASK, 0x11),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC52_MASK, 0x5),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC80_MASK, 0x8),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC108_MASK, 0x7),
+
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC147_MASK, 0x7),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC175_MASK, 0x24),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC203_MASK, 0x4),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC231_MASK, 0x11),
+
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC239_MASK, 0xe),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC247_MASK, 0x2c),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC251_MASK, 0x33),
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_AJ_MASK, 0) |
+		CFIELD_PREP(ST7701_CMD2_BK0_GAMCTRL_VC255_MASK, 0x1d)
+	},
+	.gip_sequence = txw210001b0_gip_sequence,
+};
+
 static void st7701_cleanup(void *data)
 {
 	struct st7701 *st7701 = (struct st7701 *)data;
@@ -1166,7 +1409,7 @@ static int st7701_probe(struct device *dev, int connector_type)
 	if (ret < 0)
 		return ret;
 
-	st7701->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+	st7701->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
 	if (IS_ERR(st7701->reset)) {
 		dev_err(dev, "Couldn't get our reset GPIO\n");
 		return PTR_ERR(st7701->reset);
@@ -1271,12 +1514,16 @@ MODULE_DEVICE_TABLE(of, st7701_dsi_of_match);
 
 static const struct of_device_id st7701_spi_of_match[] = {
 	{ .compatible = "anbernic,rg28xx-panel", .data = &rg28xx_desc },
+	{ .compatible = "txw,txw210001b0", .data = &txw210001b0_desc },
+	{ .compatible = "pimoroni,hyperpixel2round", .data = &hyperpixel2r_desc },
 	{ /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, st7701_spi_of_match);
 
 static const struct spi_device_id st7701_spi_ids[] = {
 	{ "rg28xx-panel" },
+	{ "txw210001b0" },
+	{ "hyperpixel2round" },
 	{ /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(spi, st7701_spi_ids);
diff --git a/drivers/gpu/drm/panel/panel-tdo-y17p.c b/drivers/gpu/drm/panel/panel-tdo-y17p.c
new file mode 100644
index 000000000000..ca6818dc465d
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-tdo-y17p.c
@@ -0,0 +1,278 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * TDO Y17P TFT LCD drm_panel driver.
+ *
+ * SPI configured DPI display controller
+ * Copyright (C) 2022 Raspberry Pi Ltd
+ *
+ * Derived from drivers/drm/gpu/panel/panel-sitronix-st7789v.c
+ * Copyright (C) 2017 Free Electrons
+ */
+
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+#include <linux/bitops.h>
+#include <linux/gpio/consumer.h>
+#include <linux/media-bus-format.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+
+#include <video/mipi_display.h>
+#include <video/of_videomode.h>
+#include <video/videomode.h>
+
+struct tdo_y17p {
+	struct drm_panel panel;
+	struct spi_device *spi;
+	struct gpio_desc *reset;
+	struct regulator *power;
+	u32 bus_format;
+};
+
+static const u16 panel_init[] = {
+	0x0ff, 0x1ff, 0x198, 0x106, 0x104, 0x101, 0x008, 0x110,
+	0x021, 0x109, 0x030, 0x102, 0x031, 0x100, 0x040, 0x110,
+	0x041, 0x155, 0x042, 0x102, 0x043, 0x109, 0x044, 0x107,
+	0x050, 0x178, 0x051, 0x178, 0x052, 0x100, 0x053, 0x16d,
+	0x060, 0x107, 0x061, 0x100, 0x062, 0x108, 0x063, 0x100,
+	0x0a0, 0x100, 0x0a1, 0x107, 0x0a2, 0x10c, 0x0a3, 0x10b,
+	0x0a4, 0x103, 0x0a5, 0x107, 0x0a6, 0x106, 0x0a7, 0x104,
+	0x0a8, 0x108, 0x0a9, 0x10c, 0x0aa, 0x113, 0x0ab, 0x106,
+	0x0ac, 0x10d, 0x0ad, 0x119, 0x0ae, 0x110, 0x0af, 0x100,
+	0x0c0, 0x100, 0x0c1, 0x107, 0x0c2, 0x10c, 0x0c3, 0x10b,
+	0x0c4, 0x103, 0x0c5, 0x107, 0x0c6, 0x107, 0x0c7, 0x104,
+	0x0c8, 0x108, 0x0c9, 0x10c, 0x0ca, 0x113, 0x0cb, 0x106,
+	0x0cc, 0x10d, 0x0cd, 0x118, 0x0ce, 0x110, 0x0cf, 0x100,
+	0x0ff, 0x1ff, 0x198, 0x106, 0x104, 0x106, 0x000, 0x120,
+	0x001, 0x10a, 0x002, 0x100, 0x003, 0x100, 0x004, 0x101,
+	0x005, 0x101, 0x006, 0x198, 0x007, 0x106, 0x008, 0x101,
+	0x009, 0x180, 0x00a, 0x100, 0x00b, 0x100, 0x00c, 0x101,
+	0x00d, 0x101, 0x00e, 0x100, 0x00f, 0x100, 0x010, 0x1f0,
+	0x011, 0x1f4, 0x012, 0x101, 0x013, 0x100, 0x014, 0x100,
+	0x015, 0x1c0, 0x016, 0x108, 0x017, 0x100, 0x018, 0x100,
+	0x019, 0x100, 0x01a, 0x100, 0x01b, 0x100, 0x01c, 0x100,
+	0x01d, 0x100, 0x020, 0x101, 0x021, 0x123, 0x022, 0x145,
+	0x023, 0x167, 0x024, 0x101, 0x025, 0x123, 0x026, 0x145,
+	0x027, 0x167, 0x030, 0x111, 0x031, 0x111, 0x032, 0x100,
+	0x033, 0x1ee, 0x034, 0x1ff, 0x035, 0x1bb, 0x036, 0x1aa,
+	0x037, 0x1dd, 0x038, 0x1cc, 0x039, 0x166, 0x03a, 0x177,
+	0x03b, 0x122, 0x03c, 0x122, 0x03d, 0x122, 0x03e, 0x122,
+	0x03f, 0x122, 0x040, 0x122, 0x052, 0x110, 0x053, 0x110,
+	0x0ff, 0x1ff, 0x198, 0x106, 0x104, 0x107, 0x018, 0x11d,
+	0x017, 0x122, 0x002, 0x177, 0x026, 0x1b2, 0x0e1, 0x179,
+	0x0ff, 0x1ff, 0x198, 0x106, 0x104, 0x100, 0x03a, 0x160,
+	0x035, 0x100, 0x011, 0x100,
+};
+
+#define NUM_INIT_REGS ARRAY_SIZE(panel_init)
+
+static inline struct tdo_y17p *panel_to_tdo_y17p(struct drm_panel *panel)
+{
+	return container_of(panel, struct tdo_y17p, panel);
+}
+
+static int tdo_y17p_write_msg(struct tdo_y17p *ctx, const u16 *msg, unsigned int len)
+{
+	struct spi_transfer xfer = { };
+	struct spi_message spi;
+
+	spi_message_init(&spi);
+
+	xfer.tx_buf = msg;
+	xfer.bits_per_word = 9;
+	xfer.len = sizeof(u16) * len;
+
+	spi_message_add_tail(&xfer, &spi);
+	return spi_sync(ctx->spi, &spi);
+}
+
+static const struct drm_display_mode tdo_y17pe_720x720_mode = {
+	.clock = 36720,
+	.hdisplay = 720,
+	.hsync_start = 720 + 20,
+	.hsync_end = 720 + 20 + 20,
+	.htotal = 720 + 20 + 20 + 40,
+	.vdisplay = 720,
+	.vsync_start = 720 + 15,
+	.vsync_end = 720 + 15 + 15,
+	.vtotal = 720 + 15 + 15 + 15,
+	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_PVSYNC,
+};
+
+static int tdo_y17p_get_modes(struct drm_panel *panel,
+			      struct drm_connector *connector)
+{
+	struct tdo_y17p *ctx = panel_to_tdo_y17p(panel);
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_duplicate(connector->dev, &tdo_y17pe_720x720_mode);
+	if (!mode) {
+		dev_err(panel->dev, "failed to add mode %ux%ux@%u\n",
+			tdo_y17pe_720x720_mode.hdisplay,
+			tdo_y17pe_720x720_mode.vdisplay,
+			drm_mode_vrefresh(&tdo_y17pe_720x720_mode));
+		return -ENOMEM;
+	}
+
+	drm_mode_set_name(mode);
+
+	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+	drm_mode_probed_add(connector, mode);
+
+	connector->display_info.width_mm = 72;
+	connector->display_info.height_mm = 72;
+	drm_display_info_set_bus_formats(&connector->display_info,
+					 &ctx->bus_format, 1);
+	connector->display_info.bus_flags = 0;
+
+	return 1;
+}
+
+static int tdo_y17p_prepare(struct drm_panel *panel)
+{
+	struct tdo_y17p *ctx = panel_to_tdo_y17p(panel);
+	int ret;
+
+	ret = regulator_enable(ctx->power);
+	if (ret)
+		return ret;
+
+	ret = tdo_y17p_write_msg(ctx, panel_init, NUM_INIT_REGS);
+
+	msleep(120);
+
+	return ret;
+}
+
+static int tdo_y17p_enable(struct drm_panel *panel)
+{
+	struct tdo_y17p *ctx = panel_to_tdo_y17p(panel);
+	const u16 msg[] = { MIPI_DCS_SET_DISPLAY_ON };
+	int ret;
+
+	ret = tdo_y17p_write_msg(ctx, msg, ARRAY_SIZE(msg));
+
+	return ret;
+}
+
+static int tdo_y17p_disable(struct drm_panel *panel)
+{
+	struct tdo_y17p *ctx = panel_to_tdo_y17p(panel);
+	const u16 msg[] = { MIPI_DCS_SET_DISPLAY_OFF };
+	int ret;
+
+	ret = tdo_y17p_write_msg(ctx, msg, ARRAY_SIZE(msg));
+
+	return ret;
+}
+
+static int tdo_y17p_unprepare(struct drm_panel *panel)
+{
+	struct tdo_y17p *ctx = panel_to_tdo_y17p(panel);
+	const u16 msg[] = { MIPI_DCS_ENTER_SLEEP_MODE };
+	int ret;
+
+	ret = tdo_y17p_write_msg(ctx, msg, ARRAY_SIZE(msg));
+
+	return ret;
+}
+
+static const struct drm_panel_funcs tdo_y17p_drm_funcs = {
+	.disable	= tdo_y17p_disable,
+	.enable		= tdo_y17p_enable,
+	.get_modes	= tdo_y17p_get_modes,
+	.prepare	= tdo_y17p_prepare,
+	.unprepare	= tdo_y17p_unprepare,
+};
+
+static const struct of_device_id tdo_y17p_of_match[] = {
+	{	.compatible = "tdo,tl040hds20ct",
+		.data = (void *)MEDIA_BUS_FMT_BGR888_1X24,
+	}, {
+		.compatible = "pimoroni,hyperpixel4square",
+		.data = (void *)MEDIA_BUS_FMT_BGR666_1X24_CPADHI,
+	}, {
+		.compatible = "tdo,y17p",
+		.data = (void *)MEDIA_BUS_FMT_BGR888_1X24,
+	}, {
+		/* sentinel */
+	}
+};
+MODULE_DEVICE_TABLE(of, tdo_y17p_of_match);
+
+static int tdo_y17p_probe(struct spi_device *spi)
+{
+	const struct of_device_id *id;
+	struct tdo_y17p *ctx;
+	int ret;
+
+	ctx = devm_kzalloc(&spi->dev, sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	id = of_match_node(tdo_y17p_of_match, spi->dev.of_node);
+	if (!id)
+		return -ENODEV;
+
+	ctx->bus_format = (u32)(uintptr_t)id->data;
+
+	spi_set_drvdata(spi, ctx);
+	ctx->spi = spi;
+
+	drm_panel_init(&ctx->panel, &spi->dev, &tdo_y17p_drm_funcs,
+		       DRM_MODE_CONNECTOR_DPI);
+
+	ctx->power = devm_regulator_get(&spi->dev, "power");
+	if (IS_ERR(ctx->power))
+		return PTR_ERR(ctx->power);
+
+	ctx->reset = devm_gpiod_get_optional(&spi->dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(ctx->reset)) {
+		dev_err(&spi->dev, "Couldn't get our reset line\n");
+		return PTR_ERR(ctx->reset);
+	}
+
+	ret = drm_panel_of_backlight(&ctx->panel);
+	if (ret)
+		return ret;
+
+	drm_panel_add(&ctx->panel);
+
+	return 0;
+}
+
+static void tdo_y17p_remove(struct spi_device *spi)
+{
+	struct tdo_y17p *ctx = spi_get_drvdata(spi);
+
+	drm_panel_remove(&ctx->panel);
+}
+
+static const struct spi_device_id tdo_y17p_ids[] = {
+	{ "tl040hds20ct", 0 },
+	{ "hyperpixel4square", 0 },
+	{ "y17p", 0 },
+	{ /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(spi, tdo_y17p_ids);
+
+static struct spi_driver tdo_y17p_driver = {
+	.probe = tdo_y17p_probe,
+	.remove = tdo_y17p_remove,
+	.driver = {
+		.name = "tdo_y17p",
+		.of_match_table = tdo_y17p_of_match,
+	},
+	.id_table = tdo_y17p_ids,
+};
+module_spi_driver(tdo_y17p_driver);
+
+MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>");
+MODULE_DESCRIPTION("TDO Y17P LCD panel driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/panel/panel-waveshare-dsi-v2.c b/drivers/gpu/drm/panel/panel-waveshare-dsi-v2.c
new file mode 100644
index 000000000000..e15dae58a19d
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-waveshare-dsi-v2.c
@@ -0,0 +1,2327 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 Waveshare International Limited
+ *
+ * Based on panel-raspberrypi-touchscreen by Broadcom
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+
+#include <linux/gpio/consumer.h>
+#include <linux/regulator/consumer.h>
+
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_panel.h>
+
+#include <video/mipi_display.h>
+
+struct ws_panel_desc {
+	const struct panel_init_cmd *init;
+	const struct drm_display_mode *mode;
+	const unsigned long mode_flags;
+	unsigned int lanes;
+	enum mipi_dsi_pixel_format format;
+};
+
+struct ws_panel {
+	struct drm_panel panel;
+	struct mipi_dsi_device *dsi;
+	const struct ws_panel_desc *desc;
+
+	struct regulator *power;
+	struct gpio_desc *reset;
+	struct gpio_desc *iovcc;
+	struct gpio_desc *avdd;
+
+	enum drm_panel_orientation orientation;
+};
+
+enum dsi_cmd_type {
+	INIT_DCS_CMD,
+	DELAY_CMD,
+};
+
+struct panel_init_cmd {
+	enum dsi_cmd_type type;
+	size_t len;
+	const char *data;
+};
+
+#define _INIT_DCS_CMD(...)                                                    \
+	{                                                                     \
+		.type = INIT_DCS_CMD, .len = sizeof((char[]){ __VA_ARGS__ }), \
+		.data = (char[])                                              \
+		{                                                             \
+			__VA_ARGS__                                           \
+		}                                                             \
+	}
+
+#define _INIT_DELAY_CMD(...)                                               \
+	{                                                                  \
+		.type = DELAY_CMD, .len = sizeof((char[]){ __VA_ARGS__ }), \
+		.data = (char[])                                           \
+		{                                                          \
+			__VA_ARGS__                                        \
+		}                                                          \
+	}
+
+static const struct panel_init_cmd ws_panel_12_3_a_4lane_init[] = {
+	_INIT_DCS_CMD(0xB9, 0x83, 0x10, 0x2E),
+	_INIT_DCS_CMD(0xE9, 0xCD),
+	_INIT_DCS_CMD(0xBB, 0x01),
+	_INIT_DCS_CMD(0xE9, 0x00),
+	_INIT_DCS_CMD(0xD1, 0x67, 0x2C, 0xFF, 0x05),
+	_INIT_DCS_CMD(0xBE, 0x11, 0x96, 0x89),
+	_INIT_DCS_CMD(0xD9, 0x04, 0x03, 0x04),
+	_INIT_DCS_CMD(0xB1, 0x10, 0xFA, 0xAF, 0xAF, 0x33, 0x33, 0xB1, 0x4D,
+		      0x2F, 0x36, 0x36, 0x36, 0x36, 0x22, 0x21, 0x15, 0x00),
+	_INIT_DCS_CMD(0xB2, 0x00, 0xD0, 0x27, 0x80, 0x00, 0x14, 0x40, 0x2C,
+		      0x32, 0x02, 0x00, 0x00, 0x15, 0x20, 0xD7, 0x00),
+	_INIT_DCS_CMD(0xB4, 0x98, 0xA0, 0x01, 0x01, 0x98, 0xA0, 0x68, 0x50,
+		      0x01, 0xC7, 0x01, 0x58, 0x00, 0xFF, 0x00, 0xFF),
+	_INIT_DCS_CMD(0xB6, 0x4D, 0x4D, 0xE3),
+	_INIT_DCS_CMD(0xBF, 0xFC, 0x85, 0x80),
+	_INIT_DCS_CMD(0xD2, 0x33, 0x33),
+	_INIT_DCS_CMD(0xD3, 0x00, 0x00, 0x00, 0x00, 0x64, 0x04, 0x00, 0x08,
+		      0x08, 0x27, 0x27, 0x22, 0x2F, 0x15, 0x15, 0x04, 0x04,
+		      0x32, 0x10, 0x13, 0x00, 0x13, 0x32, 0x10, 0x1F, 0x00,
+		      0x02, 0x32, 0x17, 0xFD, 0x00, 0x10, 0x00, 0x00, 0x20,
+		      0x30, 0x01, 0x55, 0x21, 0x38, 0x01, 0x55, 0x0F),
+	_INIT_DCS_CMD(0xE0, 0x00, 0x0C, 0x1A, 0x23, 0x2B, 0x4F, 0x64, 0x69,
+		      0x6C, 0x64, 0x77, 0x77, 0x76, 0x80, 0x79, 0x7E, 0x85,
+		      0x9A, 0x97, 0x4D, 0x56, 0x64, 0x70, 0x00, 0x0C, 0x1A,
+		      0x23, 0x2B, 0x4F, 0x64, 0x69, 0x6C, 0x64, 0x77, 0x77,
+		      0x76, 0x80, 0x79, 0x7E, 0x85, 0x9A, 0x97, 0x4D, 0x56,
+		      0x64, 0x76),
+	_INIT_DCS_CMD(0xBD, 0x01),
+	_INIT_DCS_CMD(0xB1, 0x01, 0x9B, 0x01, 0x31),
+	_INIT_DCS_CMD(0xCB, 0x80, 0x36, 0x12, 0x16, 0xC0, 0x28, 0x40, 0x84,
+		      0x22),
+	_INIT_DCS_CMD(0xD3, 0x01, 0x00, 0xFC, 0x00, 0x00, 0x11, 0x10, 0x00,
+		      0x0E, 0x00, 0x01),
+	_INIT_DCS_CMD(0xBD, 0x02),
+	_INIT_DCS_CMD(0xB4, 0x4E, 0x00, 0x33, 0x11, 0x33, 0x88),
+	_INIT_DCS_CMD(0xBF, 0xF2, 0x00, 0x02),
+	_INIT_DCS_CMD(0xBD, 0x00),
+	_INIT_DCS_CMD(0xC0, 0x23, 0x23, 0x22, 0x11, 0xA2, 0x17, 0x00, 0x80,
+		      0x00, 0x00, 0x08, 0x00, 0x63, 0x63),
+	_INIT_DCS_CMD(0xC6, 0xF9),
+	_INIT_DCS_CMD(0xC7, 0x30),
+	_INIT_DCS_CMD(0xC8, 0x00, 0x04, 0x04, 0x00, 0x00, 0x82, 0x13, 0x01),
+	_INIT_DCS_CMD(0xD0, 0x07, 0x04, 0x05),
+	_INIT_DCS_CMD(0xD5, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x21, 0x20,
+		      0x21, 0x20, 0x01, 0x00, 0x03, 0x02, 0x05, 0x04, 0x07,
+		      0x06, 0x1A, 0x1A, 0x1A, 0x1A, 0x9A, 0x9A, 0x9A, 0x9A,
+		      0x18, 0x18, 0x18, 0x18, 0x21, 0x20, 0x21, 0x20, 0x18,
+		      0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18),
+	_INIT_DCS_CMD(0xD6, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x20, 0x21,
+		      0x20, 0x21, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+		      0x07, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A,
+		      0x18, 0x18, 0x18, 0x18, 0x20, 0x21, 0x20, 0x21, 0x98,
+		      0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98),
+	_INIT_DCS_CMD(0xBD, 0x01),
+	_INIT_DCS_CMD(0xE7, 0x00, 0x34, 0x01, 0x88, 0x0E, 0xBE, 0x0F),
+	_INIT_DCS_CMD(0xBD, 0x00),
+	_INIT_DCS_CMD(0xC2, 0x43, 0xFF, 0x10),
+	_INIT_DCS_CMD(0xCC, 0x02),
+	_INIT_DCS_CMD(0xBD, 0x03),
+	_INIT_DCS_CMD(0xB2, 0x80),
+	_INIT_DCS_CMD(0xBD, 0x00),
+	_INIT_DCS_CMD(0xD8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+		      0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x2A, 0xAA, 0xAA,
+		      0xAA, 0xAA, 0x80, 0x2A, 0xAA, 0xAA, 0xAA),
+	_INIT_DCS_CMD(0xBD, 0x01),
+	_INIT_DCS_CMD(0xD8, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+		      0xAA, 0xAA, 0xAA, 0xAA),
+	_INIT_DCS_CMD(0xBD, 0x02),
+	_INIT_DCS_CMD(0xD8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF,
+		      0xFF, 0xFF, 0xFF, 0xF0),
+	_INIT_DCS_CMD(0xBD, 0x00),
+	_INIT_DCS_CMD(0x11),
+	_INIT_DELAY_CMD(120),
+	_INIT_DCS_CMD(0x29),
+	_INIT_DELAY_CMD(20),
+	{},
+};
+
+static const struct panel_init_cmd ws_panel_10_1_a_4lane_init[] = {
+	_INIT_DCS_CMD(0xE0, 0x00),
+	_INIT_DCS_CMD(0xE1, 0x93),
+	_INIT_DCS_CMD(0xE2, 0x65),
+	_INIT_DCS_CMD(0xE3, 0xF8),
+	_INIT_DCS_CMD(0x80, 0x03),
+	_INIT_DCS_CMD(0xE0, 0x01),
+	_INIT_DCS_CMD(0x00, 0x00),
+	_INIT_DCS_CMD(0x01, 0x3B),
+	_INIT_DCS_CMD(0x0C, 0x74),
+	_INIT_DCS_CMD(0x17, 0x00),
+	_INIT_DCS_CMD(0x18, 0xAF),
+	_INIT_DCS_CMD(0x19, 0x00),
+	_INIT_DCS_CMD(0x1A, 0x00),
+	_INIT_DCS_CMD(0x1B, 0xAF),
+	_INIT_DCS_CMD(0x1C, 0x00),
+	_INIT_DCS_CMD(0x35, 0x26),
+	_INIT_DCS_CMD(0x37, 0x09),
+	_INIT_DCS_CMD(0x38, 0x04),
+	_INIT_DCS_CMD(0x39, 0x00),
+	_INIT_DCS_CMD(0x3A, 0x01),
+	_INIT_DCS_CMD(0x3C, 0x78),
+	_INIT_DCS_CMD(0x3D, 0xFF),
+	_INIT_DCS_CMD(0x3E, 0xFF),
+	_INIT_DCS_CMD(0x3F, 0x7F),
+	_INIT_DCS_CMD(0x40, 0x06),
+	_INIT_DCS_CMD(0x41, 0xA0),
+	_INIT_DCS_CMD(0x42, 0x81),
+	_INIT_DCS_CMD(0x43, 0x14),
+	_INIT_DCS_CMD(0x44, 0x23),
+	_INIT_DCS_CMD(0x45, 0x28),
+	_INIT_DCS_CMD(0x55, 0x02),
+	_INIT_DCS_CMD(0x57, 0x69),
+	_INIT_DCS_CMD(0x59, 0x0A),
+	_INIT_DCS_CMD(0x5A, 0x2A),
+	_INIT_DCS_CMD(0x5B, 0x17),
+	_INIT_DCS_CMD(0x5D, 0x7F),
+	_INIT_DCS_CMD(0x5E, 0x6B),
+	_INIT_DCS_CMD(0x5F, 0x5C),
+	_INIT_DCS_CMD(0x60, 0x4F),
+	_INIT_DCS_CMD(0x61, 0x4D),
+	_INIT_DCS_CMD(0x62, 0x3F),
+	_INIT_DCS_CMD(0x63, 0x42),
+	_INIT_DCS_CMD(0x64, 0x2B),
+	_INIT_DCS_CMD(0x65, 0x44),
+	_INIT_DCS_CMD(0x66, 0x43),
+	_INIT_DCS_CMD(0x67, 0x43),
+	_INIT_DCS_CMD(0x68, 0x63),
+	_INIT_DCS_CMD(0x69, 0x52),
+	_INIT_DCS_CMD(0x6A, 0x5A),
+	_INIT_DCS_CMD(0x6B, 0x4F),
+	_INIT_DCS_CMD(0x6C, 0x4E),
+	_INIT_DCS_CMD(0x6D, 0x20),
+	_INIT_DCS_CMD(0x6E, 0x0F),
+	_INIT_DCS_CMD(0x6F, 0x00),
+	_INIT_DCS_CMD(0x70, 0x7F),
+	_INIT_DCS_CMD(0x71, 0x6B),
+	_INIT_DCS_CMD(0x72, 0x5C),
+	_INIT_DCS_CMD(0x73, 0x4F),
+	_INIT_DCS_CMD(0x74, 0x4D),
+	_INIT_DCS_CMD(0x75, 0x3F),
+	_INIT_DCS_CMD(0x76, 0x42),
+	_INIT_DCS_CMD(0x77, 0x2B),
+	_INIT_DCS_CMD(0x78, 0x44),
+	_INIT_DCS_CMD(0x79, 0x43),
+	_INIT_DCS_CMD(0x7A, 0x43),
+	_INIT_DCS_CMD(0x7B, 0x63),
+	_INIT_DCS_CMD(0x7C, 0x52),
+	_INIT_DCS_CMD(0x7D, 0x5A),
+	_INIT_DCS_CMD(0x7E, 0x4F),
+	_INIT_DCS_CMD(0x7F, 0x4E),
+	_INIT_DCS_CMD(0x80, 0x20),
+	_INIT_DCS_CMD(0x81, 0x0F),
+	_INIT_DCS_CMD(0x82, 0x00),
+	_INIT_DCS_CMD(0xE0, 0x02),
+	_INIT_DCS_CMD(0x00, 0x02),
+	_INIT_DCS_CMD(0x01, 0x02),
+	_INIT_DCS_CMD(0x02, 0x00),
+	_INIT_DCS_CMD(0x03, 0x00),
+	_INIT_DCS_CMD(0x04, 0x1E),
+	_INIT_DCS_CMD(0x05, 0x1E),
+	_INIT_DCS_CMD(0x06, 0x1F),
+	_INIT_DCS_CMD(0x07, 0x1F),
+	_INIT_DCS_CMD(0x08, 0x1F),
+	_INIT_DCS_CMD(0x09, 0x17),
+	_INIT_DCS_CMD(0x0A, 0x17),
+	_INIT_DCS_CMD(0x0B, 0x37),
+	_INIT_DCS_CMD(0x0C, 0x37),
+	_INIT_DCS_CMD(0x0D, 0x47),
+	_INIT_DCS_CMD(0x0E, 0x47),
+	_INIT_DCS_CMD(0x0F, 0x45),
+	_INIT_DCS_CMD(0x10, 0x45),
+	_INIT_DCS_CMD(0x11, 0x4B),
+	_INIT_DCS_CMD(0x12, 0x4B),
+	_INIT_DCS_CMD(0x13, 0x49),
+	_INIT_DCS_CMD(0x14, 0x49),
+	_INIT_DCS_CMD(0x15, 0x1F),
+	_INIT_DCS_CMD(0x16, 0x01),
+	_INIT_DCS_CMD(0x17, 0x01),
+	_INIT_DCS_CMD(0x18, 0x00),
+	_INIT_DCS_CMD(0x19, 0x00),
+	_INIT_DCS_CMD(0x1A, 0x1E),
+	_INIT_DCS_CMD(0x1B, 0x1E),
+	_INIT_DCS_CMD(0x1C, 0x1F),
+	_INIT_DCS_CMD(0x1D, 0x1F),
+	_INIT_DCS_CMD(0x1E, 0x1F),
+	_INIT_DCS_CMD(0x1F, 0x17),
+	_INIT_DCS_CMD(0x20, 0x17),
+	_INIT_DCS_CMD(0x21, 0x37),
+	_INIT_DCS_CMD(0x22, 0x37),
+	_INIT_DCS_CMD(0x23, 0x46),
+	_INIT_DCS_CMD(0x24, 0x46),
+	_INIT_DCS_CMD(0x25, 0x44),
+	_INIT_DCS_CMD(0x26, 0x44),
+	_INIT_DCS_CMD(0x27, 0x4A),
+	_INIT_DCS_CMD(0x28, 0x4A),
+	_INIT_DCS_CMD(0x29, 0x48),
+	_INIT_DCS_CMD(0x2A, 0x48),
+	_INIT_DCS_CMD(0x2B, 0x1F),
+	_INIT_DCS_CMD(0x2C, 0x01),
+	_INIT_DCS_CMD(0x2D, 0x01),
+	_INIT_DCS_CMD(0x2E, 0x00),
+	_INIT_DCS_CMD(0x2F, 0x00),
+	_INIT_DCS_CMD(0x30, 0x1F),
+	_INIT_DCS_CMD(0x31, 0x1F),
+	_INIT_DCS_CMD(0x32, 0x1E),
+	_INIT_DCS_CMD(0x33, 0x1E),
+	_INIT_DCS_CMD(0x34, 0x1F),
+	_INIT_DCS_CMD(0x35, 0x17),
+	_INIT_DCS_CMD(0x36, 0x17),
+	_INIT_DCS_CMD(0x37, 0x37),
+	_INIT_DCS_CMD(0x38, 0x37),
+	_INIT_DCS_CMD(0x39, 0x08),
+	_INIT_DCS_CMD(0x3A, 0x08),
+	_INIT_DCS_CMD(0x3B, 0x0A),
+	_INIT_DCS_CMD(0x3C, 0x0A),
+	_INIT_DCS_CMD(0x3D, 0x04),
+	_INIT_DCS_CMD(0x3E, 0x04),
+	_INIT_DCS_CMD(0x3F, 0x06),
+	_INIT_DCS_CMD(0x40, 0x06),
+	_INIT_DCS_CMD(0x41, 0x1F),
+	_INIT_DCS_CMD(0x42, 0x02),
+	_INIT_DCS_CMD(0x43, 0x02),
+	_INIT_DCS_CMD(0x44, 0x00),
+	_INIT_DCS_CMD(0x45, 0x00),
+	_INIT_DCS_CMD(0x46, 0x1F),
+	_INIT_DCS_CMD(0x47, 0x1F),
+	_INIT_DCS_CMD(0x48, 0x1E),
+	_INIT_DCS_CMD(0x49, 0x1E),
+	_INIT_DCS_CMD(0x4A, 0x1F),
+	_INIT_DCS_CMD(0x4B, 0x17),
+	_INIT_DCS_CMD(0x4C, 0x17),
+	_INIT_DCS_CMD(0x4D, 0x37),
+	_INIT_DCS_CMD(0x4E, 0x37),
+	_INIT_DCS_CMD(0x4F, 0x09),
+	_INIT_DCS_CMD(0x50, 0x09),
+	_INIT_DCS_CMD(0x51, 0x0B),
+	_INIT_DCS_CMD(0x52, 0x0B),
+	_INIT_DCS_CMD(0x53, 0x05),
+	_INIT_DCS_CMD(0x54, 0x05),
+	_INIT_DCS_CMD(0x55, 0x07),
+	_INIT_DCS_CMD(0x56, 0x07),
+	_INIT_DCS_CMD(0x57, 0x1F),
+	_INIT_DCS_CMD(0x58, 0x40),
+	_INIT_DCS_CMD(0x5B, 0x30),
+	_INIT_DCS_CMD(0x5C, 0x16),
+	_INIT_DCS_CMD(0x5D, 0x34),
+	_INIT_DCS_CMD(0x5E, 0x05),
+	_INIT_DCS_CMD(0x5F, 0x02),
+	_INIT_DCS_CMD(0x63, 0x00),
+	_INIT_DCS_CMD(0x64, 0x6A),
+	_INIT_DCS_CMD(0x67, 0x73),
+	_INIT_DCS_CMD(0x68, 0x1D),
+	_INIT_DCS_CMD(0x69, 0x08),
+	_INIT_DCS_CMD(0x6A, 0x6A),
+	_INIT_DCS_CMD(0x6B, 0x08),
+	_INIT_DCS_CMD(0x6C, 0x00),
+	_INIT_DCS_CMD(0x6D, 0x00),
+	_INIT_DCS_CMD(0x6E, 0x00),
+	_INIT_DCS_CMD(0x6F, 0x88),
+	_INIT_DCS_CMD(0x75, 0xFF),
+	_INIT_DCS_CMD(0x77, 0xDD),
+	_INIT_DCS_CMD(0x78, 0x3F),
+	_INIT_DCS_CMD(0x79, 0x15),
+	_INIT_DCS_CMD(0x7A, 0x17),
+	_INIT_DCS_CMD(0x7D, 0x14),
+	_INIT_DCS_CMD(0x7E, 0x82),
+	_INIT_DCS_CMD(0xE0, 0x04),
+	_INIT_DCS_CMD(0x00, 0x0E),
+	_INIT_DCS_CMD(0x02, 0xB3),
+	_INIT_DCS_CMD(0x09, 0x61),
+	_INIT_DCS_CMD(0x0E, 0x48),
+	_INIT_DCS_CMD(0xE0, 0x00),
+	_INIT_DCS_CMD(0xE6, 0x02),
+	_INIT_DCS_CMD(0xE7, 0x0C),
+	_INIT_DCS_CMD(0x11),
+	_INIT_DELAY_CMD(120),
+	_INIT_DCS_CMD(0x29),
+	_INIT_DELAY_CMD(60),
+	{},
+};
+
+static const struct panel_init_cmd ws_panel_10_1_a_init[] = {
+	_INIT_DCS_CMD(0xE0, 0x00),
+	_INIT_DCS_CMD(0xE1, 0x93),
+	_INIT_DCS_CMD(0xE2, 0x65),
+	_INIT_DCS_CMD(0xE3, 0xF8),
+	_INIT_DCS_CMD(0x80, 0x01),
+	_INIT_DCS_CMD(0xE0, 0x01),
+	_INIT_DCS_CMD(0x00, 0x00),
+	_INIT_DCS_CMD(0x01, 0x38),
+	_INIT_DCS_CMD(0x03, 0x10),
+	_INIT_DCS_CMD(0x04, 0x38),
+	_INIT_DCS_CMD(0x0C, 0x74),
+	_INIT_DCS_CMD(0x17, 0x00),
+	_INIT_DCS_CMD(0x18, 0xAF),
+	_INIT_DCS_CMD(0x19, 0x00),
+	_INIT_DCS_CMD(0x1A, 0x00),
+	_INIT_DCS_CMD(0x1B, 0xAF),
+	_INIT_DCS_CMD(0x1C, 0x00),
+	_INIT_DCS_CMD(0x35, 0x26),
+	_INIT_DCS_CMD(0x37, 0x09),
+	_INIT_DCS_CMD(0x38, 0x04),
+	_INIT_DCS_CMD(0x39, 0x00),
+	_INIT_DCS_CMD(0x3A, 0x01),
+	_INIT_DCS_CMD(0x3C, 0x78),
+	_INIT_DCS_CMD(0x3D, 0xFF),
+	_INIT_DCS_CMD(0x3E, 0xFF),
+	_INIT_DCS_CMD(0x3F, 0x7F),
+	_INIT_DCS_CMD(0x40, 0x06),
+	_INIT_DCS_CMD(0x41, 0xA0),
+	_INIT_DCS_CMD(0x42, 0x81),
+	_INIT_DCS_CMD(0x43, 0x1E),
+	_INIT_DCS_CMD(0x44, 0x0D),
+	_INIT_DCS_CMD(0x45, 0x28),
+	_INIT_DCS_CMD(0x55, 0x02),
+	_INIT_DCS_CMD(0x57, 0x69),
+	_INIT_DCS_CMD(0x59, 0x0A),
+	_INIT_DCS_CMD(0x5A, 0x2A),
+	_INIT_DCS_CMD(0x5B, 0x17),
+	_INIT_DCS_CMD(0x5D, 0x7F),
+	_INIT_DCS_CMD(0x5E, 0x6A),
+	_INIT_DCS_CMD(0x5F, 0x5B),
+	_INIT_DCS_CMD(0x60, 0x4F),
+	_INIT_DCS_CMD(0x61, 0x4A),
+	_INIT_DCS_CMD(0x62, 0x3D),
+	_INIT_DCS_CMD(0x63, 0x41),
+	_INIT_DCS_CMD(0x64, 0x2A),
+	_INIT_DCS_CMD(0x65, 0x44),
+	_INIT_DCS_CMD(0x66, 0x43),
+	_INIT_DCS_CMD(0x67, 0x44),
+	_INIT_DCS_CMD(0x68, 0x62),
+	_INIT_DCS_CMD(0x69, 0x52),
+	_INIT_DCS_CMD(0x6A, 0x59),
+	_INIT_DCS_CMD(0x6B, 0x4C),
+	_INIT_DCS_CMD(0x6C, 0x48),
+	_INIT_DCS_CMD(0x6D, 0x3A),
+	_INIT_DCS_CMD(0x6E, 0x26),
+	_INIT_DCS_CMD(0x6F, 0x00),
+	_INIT_DCS_CMD(0x70, 0x7F),
+	_INIT_DCS_CMD(0x71, 0x6A),
+	_INIT_DCS_CMD(0x72, 0x5B),
+	_INIT_DCS_CMD(0x73, 0x4F),
+	_INIT_DCS_CMD(0x74, 0x4A),
+	_INIT_DCS_CMD(0x75, 0x3D),
+	_INIT_DCS_CMD(0x76, 0x41),
+	_INIT_DCS_CMD(0x77, 0x2A),
+	_INIT_DCS_CMD(0x78, 0x44),
+	_INIT_DCS_CMD(0x79, 0x43),
+	_INIT_DCS_CMD(0x7A, 0x44),
+	_INIT_DCS_CMD(0x7B, 0x62),
+	_INIT_DCS_CMD(0x7C, 0x52),
+	_INIT_DCS_CMD(0x7D, 0x59),
+	_INIT_DCS_CMD(0x7E, 0x4C),
+	_INIT_DCS_CMD(0x7F, 0x48),
+	_INIT_DCS_CMD(0x80, 0x3A),
+	_INIT_DCS_CMD(0x81, 0x26),
+	_INIT_DCS_CMD(0x82, 0x00),
+	_INIT_DCS_CMD(0xE0, 0x02),
+	_INIT_DCS_CMD(0x00, 0x42),
+	_INIT_DCS_CMD(0x01, 0x42),
+	_INIT_DCS_CMD(0x02, 0x40),
+	_INIT_DCS_CMD(0x03, 0x40),
+	_INIT_DCS_CMD(0x04, 0x5E),
+	_INIT_DCS_CMD(0x05, 0x5E),
+	_INIT_DCS_CMD(0x06, 0x5F),
+	_INIT_DCS_CMD(0x07, 0x5F),
+	_INIT_DCS_CMD(0x08, 0x5F),
+	_INIT_DCS_CMD(0x09, 0x57),
+	_INIT_DCS_CMD(0x0A, 0x57),
+	_INIT_DCS_CMD(0x0B, 0x77),
+	_INIT_DCS_CMD(0x0C, 0x77),
+	_INIT_DCS_CMD(0x0D, 0x47),
+	_INIT_DCS_CMD(0x0E, 0x47),
+	_INIT_DCS_CMD(0x0F, 0x45),
+	_INIT_DCS_CMD(0x10, 0x45),
+	_INIT_DCS_CMD(0x11, 0x4B),
+	_INIT_DCS_CMD(0x12, 0x4B),
+	_INIT_DCS_CMD(0x13, 0x49),
+	_INIT_DCS_CMD(0x14, 0x49),
+	_INIT_DCS_CMD(0x15, 0x5F),
+	_INIT_DCS_CMD(0x16, 0x41),
+	_INIT_DCS_CMD(0x17, 0x41),
+	_INIT_DCS_CMD(0x18, 0x40),
+	_INIT_DCS_CMD(0x19, 0x40),
+	_INIT_DCS_CMD(0x1A, 0x5E),
+	_INIT_DCS_CMD(0x1B, 0x5E),
+	_INIT_DCS_CMD(0x1C, 0x5F),
+	_INIT_DCS_CMD(0x1D, 0x5F),
+	_INIT_DCS_CMD(0x1E, 0x5F),
+	_INIT_DCS_CMD(0x1F, 0x57),
+	_INIT_DCS_CMD(0x20, 0x57),
+	_INIT_DCS_CMD(0x21, 0x77),
+	_INIT_DCS_CMD(0x22, 0x77),
+	_INIT_DCS_CMD(0x23, 0x46),
+	_INIT_DCS_CMD(0x24, 0x46),
+	_INIT_DCS_CMD(0x25, 0x44),
+	_INIT_DCS_CMD(0x26, 0x44),
+	_INIT_DCS_CMD(0x27, 0x4A),
+	_INIT_DCS_CMD(0x28, 0x4A),
+	_INIT_DCS_CMD(0x29, 0x48),
+	_INIT_DCS_CMD(0x2A, 0x48),
+	_INIT_DCS_CMD(0x2B, 0x5F),
+	_INIT_DCS_CMD(0x2C, 0x01),
+	_INIT_DCS_CMD(0x2D, 0x01),
+	_INIT_DCS_CMD(0x2E, 0x00),
+	_INIT_DCS_CMD(0x2F, 0x00),
+	_INIT_DCS_CMD(0x30, 0x1F),
+	_INIT_DCS_CMD(0x31, 0x1F),
+	_INIT_DCS_CMD(0x32, 0x1E),
+	_INIT_DCS_CMD(0x33, 0x1E),
+	_INIT_DCS_CMD(0x34, 0x1F),
+	_INIT_DCS_CMD(0x35, 0x17),
+	_INIT_DCS_CMD(0x36, 0x17),
+	_INIT_DCS_CMD(0x37, 0x37),
+	_INIT_DCS_CMD(0x38, 0x37),
+	_INIT_DCS_CMD(0x39, 0x08),
+	_INIT_DCS_CMD(0x3A, 0x08),
+	_INIT_DCS_CMD(0x3B, 0x0A),
+	_INIT_DCS_CMD(0x3C, 0x0A),
+	_INIT_DCS_CMD(0x3D, 0x04),
+	_INIT_DCS_CMD(0x3E, 0x04),
+	_INIT_DCS_CMD(0x3F, 0x06),
+	_INIT_DCS_CMD(0x40, 0x06),
+	_INIT_DCS_CMD(0x41, 0x1F),
+	_INIT_DCS_CMD(0x42, 0x02),
+	_INIT_DCS_CMD(0x43, 0x02),
+	_INIT_DCS_CMD(0x44, 0x00),
+	_INIT_DCS_CMD(0x45, 0x00),
+	_INIT_DCS_CMD(0x46, 0x1F),
+	_INIT_DCS_CMD(0x47, 0x1F),
+	_INIT_DCS_CMD(0x48, 0x1E),
+	_INIT_DCS_CMD(0x49, 0x1E),
+	_INIT_DCS_CMD(0x4A, 0x1F),
+	_INIT_DCS_CMD(0x4B, 0x17),
+	_INIT_DCS_CMD(0x4C, 0x17),
+	_INIT_DCS_CMD(0x4D, 0x37),
+	_INIT_DCS_CMD(0x4E, 0x37),
+	_INIT_DCS_CMD(0x4F, 0x09),
+	_INIT_DCS_CMD(0x50, 0x09),
+	_INIT_DCS_CMD(0x51, 0x0B),
+	_INIT_DCS_CMD(0x52, 0x0B),
+	_INIT_DCS_CMD(0x53, 0x05),
+	_INIT_DCS_CMD(0x54, 0x05),
+	_INIT_DCS_CMD(0x55, 0x07),
+	_INIT_DCS_CMD(0x56, 0x07),
+	_INIT_DCS_CMD(0x57, 0x1F),
+	_INIT_DCS_CMD(0x58, 0x40),
+	_INIT_DCS_CMD(0x5B, 0x30),
+	_INIT_DCS_CMD(0x5C, 0x00),
+	_INIT_DCS_CMD(0x5D, 0x34),
+	_INIT_DCS_CMD(0x5E, 0x05),
+	_INIT_DCS_CMD(0x5F, 0x02),
+	_INIT_DCS_CMD(0x63, 0x00),
+	_INIT_DCS_CMD(0x64, 0x6A),
+	_INIT_DCS_CMD(0x67, 0x73),
+	_INIT_DCS_CMD(0x68, 0x07),
+	_INIT_DCS_CMD(0x69, 0x08),
+	_INIT_DCS_CMD(0x6A, 0x6A),
+	_INIT_DCS_CMD(0x6B, 0x08),
+	_INIT_DCS_CMD(0x6C, 0x00),
+	_INIT_DCS_CMD(0x6D, 0x00),
+	_INIT_DCS_CMD(0x6E, 0x00),
+	_INIT_DCS_CMD(0x6F, 0x88),
+	_INIT_DCS_CMD(0x75, 0xFF),
+	_INIT_DCS_CMD(0x77, 0xDD),
+	_INIT_DCS_CMD(0x78, 0x2C),
+	_INIT_DCS_CMD(0x79, 0x15),
+	_INIT_DCS_CMD(0x7A, 0x17),
+	_INIT_DCS_CMD(0x7D, 0x14),
+	_INIT_DCS_CMD(0x7E, 0x82),
+	_INIT_DCS_CMD(0xE0, 0x04),
+	_INIT_DCS_CMD(0x00, 0x0E),
+	_INIT_DCS_CMD(0x02, 0xB3),
+	_INIT_DCS_CMD(0x09, 0x61),
+	_INIT_DCS_CMD(0x0E, 0x48),
+	_INIT_DCS_CMD(0x37, 0x58),
+	_INIT_DCS_CMD(0x2B, 0x0F),
+	_INIT_DCS_CMD(0xE0, 0x00),
+	_INIT_DCS_CMD(0xE6, 0x02),
+	_INIT_DCS_CMD(0xE7, 0x0C),
+	_INIT_DCS_CMD(0x11),
+	_INIT_DELAY_CMD(120),
+	_INIT_DCS_CMD(0x29),
+	_INIT_DELAY_CMD(60),
+	{},
+};
+
+static const struct panel_init_cmd ws_panel_10_1_b_4lane_init[] = {
+	_INIT_DCS_CMD(0xE0, 0x00),
+	_INIT_DCS_CMD(0xE1, 0x93),
+	_INIT_DCS_CMD(0xE2, 0x65),
+	_INIT_DCS_CMD(0xE3, 0xF8),
+	_INIT_DCS_CMD(0x80, 0x03),
+	_INIT_DCS_CMD(0xE0, 0x01),
+	_INIT_DCS_CMD(0x00, 0x00),
+	_INIT_DCS_CMD(0x01, 0x3D),
+	_INIT_DCS_CMD(0x03, 0x10),
+	_INIT_DCS_CMD(0x04, 0x3F),
+	_INIT_DCS_CMD(0x17, 0x00),
+	_INIT_DCS_CMD(0x18, 0xBF),
+	_INIT_DCS_CMD(0x19, 0x01),
+	_INIT_DCS_CMD(0x1A, 0x00),
+	_INIT_DCS_CMD(0x1B, 0xBF),
+	_INIT_DCS_CMD(0x1C, 0x01),
+	_INIT_DCS_CMD(0x24, 0xFE),
+	_INIT_DCS_CMD(0x37, 0x19),
+	_INIT_DCS_CMD(0x38, 0x05),
+	_INIT_DCS_CMD(0x39, 0x08),
+	_INIT_DCS_CMD(0x3A, 0x1F),
+	_INIT_DCS_CMD(0x3C, 0x74),
+	_INIT_DCS_CMD(0x3D, 0xFF),
+	_INIT_DCS_CMD(0x3E, 0xFF),
+	_INIT_DCS_CMD(0x3F, 0xFF),
+	_INIT_DCS_CMD(0x40, 0x04),
+	_INIT_DCS_CMD(0x41, 0xA0),
+	_INIT_DCS_CMD(0x42, 0x7E),
+	_INIT_DCS_CMD(0x43, 0x1A),
+	_INIT_DCS_CMD(0x44, 0x0F),
+	_INIT_DCS_CMD(0x45, 0x24),
+	_INIT_DCS_CMD(0x55, 0x02),
+	_INIT_DCS_CMD(0x57, 0xA9),
+	_INIT_DCS_CMD(0x59, 0x0A),
+	_INIT_DCS_CMD(0x5A, 0x38),
+	_INIT_DCS_CMD(0x5B, 0x1A),
+	_INIT_DCS_CMD(0x5D, 0x7F),
+	_INIT_DCS_CMD(0x5E, 0x65),
+	_INIT_DCS_CMD(0x5F, 0x52),
+	_INIT_DCS_CMD(0x60, 0x44),
+	_INIT_DCS_CMD(0x61, 0x3D),
+	_INIT_DCS_CMD(0x62, 0x2D),
+	_INIT_DCS_CMD(0x63, 0x2D),
+	_INIT_DCS_CMD(0x64, 0x14),
+	_INIT_DCS_CMD(0x65, 0x28),
+	_INIT_DCS_CMD(0x66, 0x25),
+	_INIT_DCS_CMD(0x67, 0x23),
+	_INIT_DCS_CMD(0x68, 0x3F),
+	_INIT_DCS_CMD(0x69, 0x2D),
+	_INIT_DCS_CMD(0x6A, 0x34),
+	_INIT_DCS_CMD(0x6B, 0x27),
+	_INIT_DCS_CMD(0x6C, 0x24),
+	_INIT_DCS_CMD(0x6D, 0x18),
+	_INIT_DCS_CMD(0x6E, 0x0A),
+	_INIT_DCS_CMD(0x6F, 0x00),
+	_INIT_DCS_CMD(0x70, 0x7F),
+	_INIT_DCS_CMD(0x71, 0x65),
+	_INIT_DCS_CMD(0x72, 0x52),
+	_INIT_DCS_CMD(0x73, 0x44),
+	_INIT_DCS_CMD(0x74, 0x3D),
+	_INIT_DCS_CMD(0x75, 0x2D),
+	_INIT_DCS_CMD(0x76, 0x2D),
+	_INIT_DCS_CMD(0x77, 0x14),
+	_INIT_DCS_CMD(0x78, 0x28),
+	_INIT_DCS_CMD(0x79, 0x25),
+	_INIT_DCS_CMD(0x7A, 0x23),
+	_INIT_DCS_CMD(0x7B, 0x3F),
+	_INIT_DCS_CMD(0x7C, 0x2D),
+	_INIT_DCS_CMD(0x7D, 0x34),
+	_INIT_DCS_CMD(0x7E, 0x27),
+	_INIT_DCS_CMD(0x7F, 0x24),
+	_INIT_DCS_CMD(0x80, 0x18),
+	_INIT_DCS_CMD(0x81, 0x0A),
+	_INIT_DCS_CMD(0x82, 0x00),
+	_INIT_DCS_CMD(0xE0, 0x02),
+	_INIT_DCS_CMD(0x00, 0x51),
+	_INIT_DCS_CMD(0x01, 0x55),
+	_INIT_DCS_CMD(0x02, 0x50),
+	_INIT_DCS_CMD(0x03, 0x51),
+	_INIT_DCS_CMD(0x04, 0x5F),
+	_INIT_DCS_CMD(0x05, 0x77),
+	_INIT_DCS_CMD(0x06, 0x57),
+	_INIT_DCS_CMD(0x07, 0x5F),
+	_INIT_DCS_CMD(0x08, 0x47),
+	_INIT_DCS_CMD(0x09, 0x46),
+	_INIT_DCS_CMD(0x0A, 0x45),
+	_INIT_DCS_CMD(0x0B, 0x44),
+	_INIT_DCS_CMD(0x0C, 0x4B),
+	_INIT_DCS_CMD(0x0D, 0x4A),
+	_INIT_DCS_CMD(0x0E, 0x49),
+	_INIT_DCS_CMD(0x0F, 0x48),
+	_INIT_DCS_CMD(0x10, 0x40),
+	_INIT_DCS_CMD(0x11, 0x41),
+	_INIT_DCS_CMD(0x12, 0x5F),
+	_INIT_DCS_CMD(0x13, 0x5F),
+	_INIT_DCS_CMD(0x14, 0x5F),
+	_INIT_DCS_CMD(0x15, 0x5F),
+	_INIT_DCS_CMD(0x16, 0x51),
+	_INIT_DCS_CMD(0x17, 0x55),
+	_INIT_DCS_CMD(0x18, 0x50),
+	_INIT_DCS_CMD(0x19, 0x51),
+	_INIT_DCS_CMD(0x1A, 0x5F),
+	_INIT_DCS_CMD(0x1B, 0x77),
+	_INIT_DCS_CMD(0x1C, 0x57),
+	_INIT_DCS_CMD(0x1D, 0x5F),
+	_INIT_DCS_CMD(0x1E, 0x47),
+	_INIT_DCS_CMD(0x1F, 0x46),
+	_INIT_DCS_CMD(0x20, 0x45),
+	_INIT_DCS_CMD(0x21, 0x44),
+	_INIT_DCS_CMD(0x22, 0x4B),
+	_INIT_DCS_CMD(0x23, 0x4A),
+	_INIT_DCS_CMD(0x24, 0x49),
+	_INIT_DCS_CMD(0x25, 0x48),
+	_INIT_DCS_CMD(0x26, 0x40),
+	_INIT_DCS_CMD(0x27, 0x41),
+	_INIT_DCS_CMD(0x28, 0x5F),
+	_INIT_DCS_CMD(0x29, 0x5F),
+	_INIT_DCS_CMD(0x2A, 0x5F),
+	_INIT_DCS_CMD(0x2B, 0x5F),
+	_INIT_DCS_CMD(0x2C, 0x11),
+	_INIT_DCS_CMD(0x2D, 0x1F),
+	_INIT_DCS_CMD(0x2E, 0x01),
+	_INIT_DCS_CMD(0x2F, 0x00),
+	_INIT_DCS_CMD(0x30, 0x15),
+	_INIT_DCS_CMD(0x31, 0x17),
+	_INIT_DCS_CMD(0x32, 0x17),
+	_INIT_DCS_CMD(0x33, 0x1F),
+	_INIT_DCS_CMD(0x34, 0x08),
+	_INIT_DCS_CMD(0x35, 0x09),
+	_INIT_DCS_CMD(0x36, 0x0A),
+	_INIT_DCS_CMD(0x37, 0x0B),
+	_INIT_DCS_CMD(0x38, 0x04),
+	_INIT_DCS_CMD(0x39, 0x05),
+	_INIT_DCS_CMD(0x3A, 0x06),
+	_INIT_DCS_CMD(0x3B, 0x07),
+	_INIT_DCS_CMD(0x3C, 0x11),
+	_INIT_DCS_CMD(0x3D, 0x10),
+	_INIT_DCS_CMD(0x3E, 0x1F),
+	_INIT_DCS_CMD(0x3F, 0x1F),
+	_INIT_DCS_CMD(0x40, 0x1F),
+	_INIT_DCS_CMD(0x41, 0x1F),
+	_INIT_DCS_CMD(0x42, 0x11),
+	_INIT_DCS_CMD(0x43, 0x1F),
+	_INIT_DCS_CMD(0x44, 0x01),
+	_INIT_DCS_CMD(0x45, 0x00),
+	_INIT_DCS_CMD(0x46, 0x15),
+	_INIT_DCS_CMD(0x47, 0x17),
+	_INIT_DCS_CMD(0x48, 0x17),
+	_INIT_DCS_CMD(0x49, 0x1F),
+	_INIT_DCS_CMD(0x4A, 0x08),
+	_INIT_DCS_CMD(0x4B, 0x09),
+	_INIT_DCS_CMD(0x4C, 0x0A),
+	_INIT_DCS_CMD(0x4D, 0x0B),
+	_INIT_DCS_CMD(0x4E, 0x04),
+	_INIT_DCS_CMD(0x4F, 0x05),
+	_INIT_DCS_CMD(0x50, 0x06),
+	_INIT_DCS_CMD(0x51, 0x07),
+	_INIT_DCS_CMD(0x52, 0x11),
+	_INIT_DCS_CMD(0x53, 0x10),
+	_INIT_DCS_CMD(0x54, 0x1F),
+	_INIT_DCS_CMD(0x55, 0x1F),
+	_INIT_DCS_CMD(0x56, 0x1F),
+	_INIT_DCS_CMD(0x57, 0x1F),
+	_INIT_DCS_CMD(0x58, 0x40),
+	_INIT_DCS_CMD(0x5B, 0x10),
+	_INIT_DCS_CMD(0x5C, 0x07),
+	_INIT_DCS_CMD(0x5D, 0x40),
+	_INIT_DCS_CMD(0x5E, 0x01),
+	_INIT_DCS_CMD(0x5F, 0x02),
+	_INIT_DCS_CMD(0x60, 0x40),
+	_INIT_DCS_CMD(0x61, 0x01),
+	_INIT_DCS_CMD(0x62, 0x02),
+	_INIT_DCS_CMD(0x63, 0x06),
+	_INIT_DCS_CMD(0x64, 0x66),
+	_INIT_DCS_CMD(0x65, 0x55),
+	_INIT_DCS_CMD(0x66, 0x13),
+	_INIT_DCS_CMD(0x67, 0x73),
+	_INIT_DCS_CMD(0x68, 0x09),
+	_INIT_DCS_CMD(0x69, 0x06),
+	_INIT_DCS_CMD(0x6A, 0x66),
+	_INIT_DCS_CMD(0x6B, 0x08),
+	_INIT_DCS_CMD(0x6C, 0x00),
+	_INIT_DCS_CMD(0x6D, 0x04),
+	_INIT_DCS_CMD(0x6E, 0x04),
+	_INIT_DCS_CMD(0x6F, 0x88),
+	_INIT_DCS_CMD(0x75, 0xE3),
+	_INIT_DCS_CMD(0x76, 0x00),
+	_INIT_DCS_CMD(0x77, 0xD5),
+	_INIT_DCS_CMD(0x78, 0x2A),
+	_INIT_DCS_CMD(0x79, 0x21),
+	_INIT_DCS_CMD(0x7A, 0x00),
+	_INIT_DCS_CMD(0x7D, 0x06),
+	_INIT_DCS_CMD(0x7E, 0x66),
+	_INIT_DCS_CMD(0xE0, 0x04),
+	_INIT_DCS_CMD(0x00, 0x0E),
+	_INIT_DCS_CMD(0x02, 0xB3),
+	_INIT_DCS_CMD(0x09, 0x60),
+	_INIT_DCS_CMD(0x0E, 0x48),
+	_INIT_DCS_CMD(0x37, 0x58),
+	_INIT_DCS_CMD(0x2B, 0x0F),
+	_INIT_DCS_CMD(0xE0, 0x05),
+	_INIT_DCS_CMD(0x15, 0x1D),
+	_INIT_DCS_CMD(0xE0, 0x00),
+	_INIT_DCS_CMD(0x11),
+	_INIT_DELAY_CMD(120),
+	_INIT_DCS_CMD(0x29),
+	_INIT_DELAY_CMD(5),
+	_INIT_DCS_CMD(0x35),
+	{},
+};
+
+static const struct panel_init_cmd ws_panel_10_1_b_init[] = {
+	_INIT_DCS_CMD(0xE0, 0x00),
+	_INIT_DCS_CMD(0xE1, 0x93),
+	_INIT_DCS_CMD(0xE2, 0x65),
+	_INIT_DCS_CMD(0xE3, 0xF8),
+	_INIT_DCS_CMD(0x80, 0x01),
+	_INIT_DCS_CMD(0xE0, 0x01),
+	_INIT_DCS_CMD(0x00, 0x00),
+	_INIT_DCS_CMD(0x01, 0x3D),
+	_INIT_DCS_CMD(0x03, 0x10),
+	_INIT_DCS_CMD(0x04, 0x3F),
+	_INIT_DCS_CMD(0x17, 0x00),
+	_INIT_DCS_CMD(0x18, 0xBF),
+	_INIT_DCS_CMD(0x19, 0x01),
+	_INIT_DCS_CMD(0x1A, 0x00),
+	_INIT_DCS_CMD(0x1B, 0xBF),
+	_INIT_DCS_CMD(0x1C, 0x01),
+	_INIT_DCS_CMD(0x24, 0xFE),
+	_INIT_DCS_CMD(0x37, 0x19),
+	_INIT_DCS_CMD(0x38, 0x05),
+	_INIT_DCS_CMD(0x39, 0x08),
+	_INIT_DCS_CMD(0x3A, 0x1F),
+	_INIT_DCS_CMD(0x3C, 0x74),
+	_INIT_DCS_CMD(0x3D, 0xFF),
+	_INIT_DCS_CMD(0x3E, 0xFF),
+	_INIT_DCS_CMD(0x3F, 0xFF),
+	_INIT_DCS_CMD(0x40, 0x04),
+	_INIT_DCS_CMD(0x41, 0xA0),
+	_INIT_DCS_CMD(0x42, 0x7E),
+	_INIT_DCS_CMD(0x43, 0x1A),
+	_INIT_DCS_CMD(0x44, 0x0F),
+	_INIT_DCS_CMD(0x45, 0x24),
+	_INIT_DCS_CMD(0x55, 0x02),
+	_INIT_DCS_CMD(0x57, 0xA9),
+	_INIT_DCS_CMD(0x59, 0x0A),
+	_INIT_DCS_CMD(0x5A, 0x38),
+	_INIT_DCS_CMD(0x5B, 0x1A),
+	_INIT_DCS_CMD(0x5D, 0x7F),
+	_INIT_DCS_CMD(0x5E, 0x65),
+	_INIT_DCS_CMD(0x5F, 0x52),
+	_INIT_DCS_CMD(0x60, 0x44),
+	_INIT_DCS_CMD(0x61, 0x3D),
+	_INIT_DCS_CMD(0x62, 0x2D),
+	_INIT_DCS_CMD(0x63, 0x2D),
+	_INIT_DCS_CMD(0x64, 0x14),
+	_INIT_DCS_CMD(0x65, 0x28),
+	_INIT_DCS_CMD(0x66, 0x25),
+	_INIT_DCS_CMD(0x67, 0x23),
+	_INIT_DCS_CMD(0x68, 0x3F),
+	_INIT_DCS_CMD(0x69, 0x2D),
+	_INIT_DCS_CMD(0x6A, 0x34),
+	_INIT_DCS_CMD(0x6B, 0x27),
+	_INIT_DCS_CMD(0x6C, 0x24),
+	_INIT_DCS_CMD(0x6D, 0x18),
+	_INIT_DCS_CMD(0x6E, 0x0A),
+	_INIT_DCS_CMD(0x6F, 0x00),
+	_INIT_DCS_CMD(0x70, 0x7F),
+	_INIT_DCS_CMD(0x71, 0x65),
+	_INIT_DCS_CMD(0x72, 0x52),
+	_INIT_DCS_CMD(0x73, 0x44),
+	_INIT_DCS_CMD(0x74, 0x3D),
+	_INIT_DCS_CMD(0x75, 0x2D),
+	_INIT_DCS_CMD(0x76, 0x2D),
+	_INIT_DCS_CMD(0x77, 0x14),
+	_INIT_DCS_CMD(0x78, 0x28),
+	_INIT_DCS_CMD(0x79, 0x25),
+	_INIT_DCS_CMD(0x7A, 0x23),
+	_INIT_DCS_CMD(0x7B, 0x3F),
+	_INIT_DCS_CMD(0x7C, 0x2D),
+	_INIT_DCS_CMD(0x7D, 0x34),
+	_INIT_DCS_CMD(0x7E, 0x27),
+	_INIT_DCS_CMD(0x7F, 0x24),
+	_INIT_DCS_CMD(0x80, 0x18),
+	_INIT_DCS_CMD(0x81, 0x0A),
+	_INIT_DCS_CMD(0x82, 0x00),
+	_INIT_DCS_CMD(0xE0, 0x02),
+	_INIT_DCS_CMD(0x00, 0x51),
+	_INIT_DCS_CMD(0x01, 0x55),
+	_INIT_DCS_CMD(0x02, 0x50),
+	_INIT_DCS_CMD(0x03, 0x51),
+	_INIT_DCS_CMD(0x04, 0x5F),
+	_INIT_DCS_CMD(0x05, 0x77),
+	_INIT_DCS_CMD(0x06, 0x57),
+	_INIT_DCS_CMD(0x07, 0x5F),
+	_INIT_DCS_CMD(0x08, 0x47),
+	_INIT_DCS_CMD(0x09, 0x46),
+	_INIT_DCS_CMD(0x0A, 0x45),
+	_INIT_DCS_CMD(0x0B, 0x44),
+	_INIT_DCS_CMD(0x0C, 0x4B),
+	_INIT_DCS_CMD(0x0D, 0x4A),
+	_INIT_DCS_CMD(0x0E, 0x49),
+	_INIT_DCS_CMD(0x0F, 0x48),
+	_INIT_DCS_CMD(0x10, 0x40),
+	_INIT_DCS_CMD(0x11, 0x41),
+	_INIT_DCS_CMD(0x12, 0x5F),
+	_INIT_DCS_CMD(0x13, 0x5F),
+	_INIT_DCS_CMD(0x14, 0x5F),
+	_INIT_DCS_CMD(0x15, 0x5F),
+	_INIT_DCS_CMD(0x16, 0x51),
+	_INIT_DCS_CMD(0x17, 0x55),
+	_INIT_DCS_CMD(0x18, 0x50),
+	_INIT_DCS_CMD(0x19, 0x51),
+	_INIT_DCS_CMD(0x1A, 0x5F),
+	_INIT_DCS_CMD(0x1B, 0x77),
+	_INIT_DCS_CMD(0x1C, 0x57),
+	_INIT_DCS_CMD(0x1D, 0x5F),
+	_INIT_DCS_CMD(0x1E, 0x47),
+	_INIT_DCS_CMD(0x1F, 0x46),
+	_INIT_DCS_CMD(0x20, 0x45),
+	_INIT_DCS_CMD(0x21, 0x44),
+	_INIT_DCS_CMD(0x22, 0x4B),
+	_INIT_DCS_CMD(0x23, 0x4A),
+	_INIT_DCS_CMD(0x24, 0x49),
+	_INIT_DCS_CMD(0x25, 0x48),
+	_INIT_DCS_CMD(0x26, 0x40),
+	_INIT_DCS_CMD(0x27, 0x41),
+	_INIT_DCS_CMD(0x28, 0x5F),
+	_INIT_DCS_CMD(0x29, 0x5F),
+	_INIT_DCS_CMD(0x2A, 0x5F),
+	_INIT_DCS_CMD(0x2B, 0x5F),
+	_INIT_DCS_CMD(0x2C, 0x11),
+	_INIT_DCS_CMD(0x2D, 0x1F),
+	_INIT_DCS_CMD(0x2E, 0x01),
+	_INIT_DCS_CMD(0x2F, 0x00),
+	_INIT_DCS_CMD(0x30, 0x15),
+	_INIT_DCS_CMD(0x31, 0x17),
+	_INIT_DCS_CMD(0x32, 0x17),
+	_INIT_DCS_CMD(0x33, 0x1F),
+	_INIT_DCS_CMD(0x34, 0x08),
+	_INIT_DCS_CMD(0x35, 0x09),
+	_INIT_DCS_CMD(0x36, 0x0A),
+	_INIT_DCS_CMD(0x37, 0x0B),
+	_INIT_DCS_CMD(0x38, 0x04),
+	_INIT_DCS_CMD(0x39, 0x05),
+	_INIT_DCS_CMD(0x3A, 0x06),
+	_INIT_DCS_CMD(0x3B, 0x07),
+	_INIT_DCS_CMD(0x3C, 0x11),
+	_INIT_DCS_CMD(0x3D, 0x10),
+	_INIT_DCS_CMD(0x3E, 0x1F),
+	_INIT_DCS_CMD(0x3F, 0x1F),
+	_INIT_DCS_CMD(0x40, 0x1F),
+	_INIT_DCS_CMD(0x41, 0x1F),
+	_INIT_DCS_CMD(0x42, 0x11),
+	_INIT_DCS_CMD(0x43, 0x1F),
+	_INIT_DCS_CMD(0x44, 0x01),
+	_INIT_DCS_CMD(0x45, 0x00),
+	_INIT_DCS_CMD(0x46, 0x15),
+	_INIT_DCS_CMD(0x47, 0x17),
+	_INIT_DCS_CMD(0x48, 0x17),
+	_INIT_DCS_CMD(0x49, 0x1F),
+	_INIT_DCS_CMD(0x4A, 0x08),
+	_INIT_DCS_CMD(0x4B, 0x09),
+	_INIT_DCS_CMD(0x4C, 0x0A),
+	_INIT_DCS_CMD(0x4D, 0x0B),
+	_INIT_DCS_CMD(0x4E, 0x04),
+	_INIT_DCS_CMD(0x4F, 0x05),
+	_INIT_DCS_CMD(0x50, 0x06),
+	_INIT_DCS_CMD(0x51, 0x07),
+	_INIT_DCS_CMD(0x52, 0x11),
+	_INIT_DCS_CMD(0x53, 0x10),
+	_INIT_DCS_CMD(0x54, 0x1F),
+	_INIT_DCS_CMD(0x55, 0x1F),
+	_INIT_DCS_CMD(0x56, 0x1F),
+	_INIT_DCS_CMD(0x57, 0x1F),
+	_INIT_DCS_CMD(0x58, 0x40),
+	_INIT_DCS_CMD(0x5B, 0x10),
+	_INIT_DCS_CMD(0x5C, 0x07),
+	_INIT_DCS_CMD(0x5D, 0x40),
+	_INIT_DCS_CMD(0x5E, 0x01),
+	_INIT_DCS_CMD(0x5F, 0x02),
+	_INIT_DCS_CMD(0x60, 0x40),
+	_INIT_DCS_CMD(0x61, 0x01),
+	_INIT_DCS_CMD(0x62, 0x02),
+	_INIT_DCS_CMD(0x63, 0x06),
+	_INIT_DCS_CMD(0x64, 0x66),
+	_INIT_DCS_CMD(0x65, 0x55),
+	_INIT_DCS_CMD(0x66, 0x13),
+	_INIT_DCS_CMD(0x67, 0x73),
+	_INIT_DCS_CMD(0x68, 0x09),
+	_INIT_DCS_CMD(0x69, 0x06),
+	_INIT_DCS_CMD(0x6A, 0x66),
+	_INIT_DCS_CMD(0x6B, 0x08),
+	_INIT_DCS_CMD(0x6C, 0x00),
+	_INIT_DCS_CMD(0x6D, 0x04),
+	_INIT_DCS_CMD(0x6E, 0x04),
+	_INIT_DCS_CMD(0x6F, 0x88),
+	_INIT_DCS_CMD(0x75, 0xE3),
+	_INIT_DCS_CMD(0x76, 0x00),
+	_INIT_DCS_CMD(0x77, 0xD5),
+	_INIT_DCS_CMD(0x78, 0x2A),
+	_INIT_DCS_CMD(0x79, 0x21),
+	_INIT_DCS_CMD(0x7A, 0x00),
+	_INIT_DCS_CMD(0x7D, 0x06),
+	_INIT_DCS_CMD(0x7E, 0x66),
+	_INIT_DCS_CMD(0xE0, 0x04),
+	_INIT_DCS_CMD(0x00, 0x0E),
+	_INIT_DCS_CMD(0x02, 0xB3),
+	_INIT_DCS_CMD(0x09, 0x60),
+	_INIT_DCS_CMD(0x0E, 0x48),
+	_INIT_DCS_CMD(0x37, 0x58),
+	_INIT_DCS_CMD(0x2B, 0x0F),
+	_INIT_DCS_CMD(0xE0, 0x05),
+	_INIT_DCS_CMD(0x15, 0x1D),
+	_INIT_DCS_CMD(0xE0, 0x00),
+	_INIT_DCS_CMD(0x11),
+	_INIT_DELAY_CMD(120),
+	_INIT_DCS_CMD(0x29),
+	_INIT_DELAY_CMD(5),
+	_INIT_DCS_CMD(0x35),
+	{},
+};
+
+static const struct panel_init_cmd ws_panel_8_8_a_init[] = {
+	_INIT_DCS_CMD(0x11),
+	_INIT_DELAY_CMD(120),
+	_INIT_DCS_CMD(0x29),
+	_INIT_DELAY_CMD(50),
+	{},
+};
+
+static const struct panel_init_cmd ws_panel_8_a_4lane_init[] = {
+	_INIT_DCS_CMD(0xE0, 0x00), _INIT_DCS_CMD(0xE1, 0x93),
+	_INIT_DCS_CMD(0xE2, 0x65), _INIT_DCS_CMD(0xE3, 0xF8),
+	_INIT_DCS_CMD(0x80, 0x03), _INIT_DCS_CMD(0xE0, 0x01),
+	_INIT_DCS_CMD(0x00, 0x00), _INIT_DCS_CMD(0x01, 0x7E),
+	_INIT_DCS_CMD(0x03, 0x00), _INIT_DCS_CMD(0x04, 0x65),
+	_INIT_DCS_CMD(0x0C, 0x74), _INIT_DCS_CMD(0x17, 0x00),
+	_INIT_DCS_CMD(0x18, 0xB7), _INIT_DCS_CMD(0x19, 0x00),
+	_INIT_DCS_CMD(0x1A, 0x00), _INIT_DCS_CMD(0x1B, 0xB7),
+	_INIT_DCS_CMD(0x1C, 0x00), _INIT_DCS_CMD(0x24, 0xFE),
+	_INIT_DCS_CMD(0x37, 0x19), _INIT_DCS_CMD(0x38, 0x05),
+	_INIT_DCS_CMD(0x39, 0x00), _INIT_DCS_CMD(0x3A, 0x01),
+	_INIT_DCS_CMD(0x3B, 0x01), _INIT_DCS_CMD(0x3C, 0x70),
+	_INIT_DCS_CMD(0x3D, 0xFF), _INIT_DCS_CMD(0x3E, 0xFF),
+	_INIT_DCS_CMD(0x3F, 0xFF), _INIT_DCS_CMD(0x40, 0x06),
+	_INIT_DCS_CMD(0x41, 0xA0), _INIT_DCS_CMD(0x43, 0x1E),
+	_INIT_DCS_CMD(0x44, 0x0F), _INIT_DCS_CMD(0x45, 0x28),
+	_INIT_DCS_CMD(0x4B, 0x04), _INIT_DCS_CMD(0x55, 0x02),
+	_INIT_DCS_CMD(0x56, 0x01), _INIT_DCS_CMD(0x57, 0xA9),
+	_INIT_DCS_CMD(0x58, 0x0A), _INIT_DCS_CMD(0x59, 0x0A),
+	_INIT_DCS_CMD(0x5A, 0x37), _INIT_DCS_CMD(0x5B, 0x19),
+	_INIT_DCS_CMD(0x5D, 0x78), _INIT_DCS_CMD(0x5E, 0x63),
+	_INIT_DCS_CMD(0x5F, 0x54), _INIT_DCS_CMD(0x60, 0x49),
+	_INIT_DCS_CMD(0x61, 0x45), _INIT_DCS_CMD(0x62, 0x38),
+	_INIT_DCS_CMD(0x63, 0x3D), _INIT_DCS_CMD(0x64, 0x28),
+	_INIT_DCS_CMD(0x65, 0x43), _INIT_DCS_CMD(0x66, 0x41),
+	_INIT_DCS_CMD(0x67, 0x43), _INIT_DCS_CMD(0x68, 0x62),
+	_INIT_DCS_CMD(0x69, 0x50), _INIT_DCS_CMD(0x6A, 0x57),
+	_INIT_DCS_CMD(0x6B, 0x49), _INIT_DCS_CMD(0x6C, 0x44),
+	_INIT_DCS_CMD(0x6D, 0x37), _INIT_DCS_CMD(0x6E, 0x23),
+	_INIT_DCS_CMD(0x6F, 0x10), _INIT_DCS_CMD(0x70, 0x78),
+	_INIT_DCS_CMD(0x71, 0x63), _INIT_DCS_CMD(0x72, 0x54),
+	_INIT_DCS_CMD(0x73, 0x49), _INIT_DCS_CMD(0x74, 0x45),
+	_INIT_DCS_CMD(0x75, 0x38), _INIT_DCS_CMD(0x76, 0x3D),
+	_INIT_DCS_CMD(0x77, 0x28), _INIT_DCS_CMD(0x78, 0x43),
+	_INIT_DCS_CMD(0x79, 0x41), _INIT_DCS_CMD(0x7A, 0x43),
+	_INIT_DCS_CMD(0x7B, 0x62), _INIT_DCS_CMD(0x7C, 0x50),
+	_INIT_DCS_CMD(0x7D, 0x57), _INIT_DCS_CMD(0x7E, 0x49),
+	_INIT_DCS_CMD(0x7F, 0x44), _INIT_DCS_CMD(0x80, 0x37),
+	_INIT_DCS_CMD(0x81, 0x23), _INIT_DCS_CMD(0x82, 0x10),
+	_INIT_DCS_CMD(0xE0, 0x02), _INIT_DCS_CMD(0x00, 0x47),
+	_INIT_DCS_CMD(0x01, 0x47), _INIT_DCS_CMD(0x02, 0x45),
+	_INIT_DCS_CMD(0x03, 0x45), _INIT_DCS_CMD(0x04, 0x4B),
+	_INIT_DCS_CMD(0x05, 0x4B), _INIT_DCS_CMD(0x06, 0x49),
+	_INIT_DCS_CMD(0x07, 0x49), _INIT_DCS_CMD(0x08, 0x41),
+	_INIT_DCS_CMD(0x09, 0x1F), _INIT_DCS_CMD(0x0A, 0x1F),
+	_INIT_DCS_CMD(0x0B, 0x1F), _INIT_DCS_CMD(0x0C, 0x1F),
+	_INIT_DCS_CMD(0x0D, 0x1F), _INIT_DCS_CMD(0x0E, 0x1F),
+	_INIT_DCS_CMD(0x0F, 0x5F), _INIT_DCS_CMD(0x10, 0x5F),
+	_INIT_DCS_CMD(0x11, 0x57), _INIT_DCS_CMD(0x12, 0x77),
+	_INIT_DCS_CMD(0x13, 0x35), _INIT_DCS_CMD(0x14, 0x1F),
+	_INIT_DCS_CMD(0x15, 0x1F), _INIT_DCS_CMD(0x16, 0x46),
+	_INIT_DCS_CMD(0x17, 0x46), _INIT_DCS_CMD(0x18, 0x44),
+	_INIT_DCS_CMD(0x19, 0x44), _INIT_DCS_CMD(0x1A, 0x4A),
+	_INIT_DCS_CMD(0x1B, 0x4A), _INIT_DCS_CMD(0x1C, 0x48),
+	_INIT_DCS_CMD(0x1D, 0x48), _INIT_DCS_CMD(0x1E, 0x40),
+	_INIT_DCS_CMD(0x1F, 0x1F), _INIT_DCS_CMD(0x20, 0x1F),
+	_INIT_DCS_CMD(0x21, 0x1F), _INIT_DCS_CMD(0x22, 0x1F),
+	_INIT_DCS_CMD(0x23, 0x1F), _INIT_DCS_CMD(0x24, 0x1F),
+	_INIT_DCS_CMD(0x25, 0x5F), _INIT_DCS_CMD(0x26, 0x5F),
+	_INIT_DCS_CMD(0x27, 0x57), _INIT_DCS_CMD(0x28, 0x77),
+	_INIT_DCS_CMD(0x29, 0x35), _INIT_DCS_CMD(0x2A, 0x1F),
+	_INIT_DCS_CMD(0x2B, 0x1F), _INIT_DCS_CMD(0x58, 0x40),
+	_INIT_DCS_CMD(0x59, 0x00), _INIT_DCS_CMD(0x5A, 0x00),
+	_INIT_DCS_CMD(0x5B, 0x10), _INIT_DCS_CMD(0x5C, 0x06),
+	_INIT_DCS_CMD(0x5D, 0x40), _INIT_DCS_CMD(0x5E, 0x01),
+	_INIT_DCS_CMD(0x5F, 0x02), _INIT_DCS_CMD(0x60, 0x30),
+	_INIT_DCS_CMD(0x61, 0x01), _INIT_DCS_CMD(0x62, 0x02),
+	_INIT_DCS_CMD(0x63, 0x03), _INIT_DCS_CMD(0x64, 0x6B),
+	_INIT_DCS_CMD(0x65, 0x05), _INIT_DCS_CMD(0x66, 0x0C),
+	_INIT_DCS_CMD(0x67, 0x73), _INIT_DCS_CMD(0x68, 0x09),
+	_INIT_DCS_CMD(0x69, 0x03), _INIT_DCS_CMD(0x6A, 0x56),
+	_INIT_DCS_CMD(0x6B, 0x08), _INIT_DCS_CMD(0x6C, 0x00),
+	_INIT_DCS_CMD(0x6D, 0x04), _INIT_DCS_CMD(0x6E, 0x04),
+	_INIT_DCS_CMD(0x6F, 0x88), _INIT_DCS_CMD(0x70, 0x00),
+	_INIT_DCS_CMD(0x71, 0x00), _INIT_DCS_CMD(0x72, 0x06),
+	_INIT_DCS_CMD(0x73, 0x7B), _INIT_DCS_CMD(0x74, 0x00),
+	_INIT_DCS_CMD(0x75, 0xF8), _INIT_DCS_CMD(0x76, 0x00),
+	_INIT_DCS_CMD(0x77, 0xD5), _INIT_DCS_CMD(0x78, 0x2E),
+	_INIT_DCS_CMD(0x79, 0x12), _INIT_DCS_CMD(0x7A, 0x03),
+	_INIT_DCS_CMD(0x7B, 0x00), _INIT_DCS_CMD(0x7C, 0x00),
+	_INIT_DCS_CMD(0x7D, 0x03), _INIT_DCS_CMD(0x7E, 0x7B),
+	_INIT_DCS_CMD(0xE0, 0x04), _INIT_DCS_CMD(0x00, 0x0E),
+	_INIT_DCS_CMD(0x02, 0xB3), _INIT_DCS_CMD(0x09, 0x60),
+	_INIT_DCS_CMD(0x0E, 0x2A), _INIT_DCS_CMD(0x36, 0x59),
+	_INIT_DCS_CMD(0xE0, 0x00), _INIT_DCS_CMD(0x11),
+	_INIT_DELAY_CMD(120),	   _INIT_DCS_CMD(0x29),
+	_INIT_DELAY_CMD(60),	   {},
+};
+
+static const struct panel_init_cmd ws_panel_8_a_init[] = {
+	_INIT_DCS_CMD(0xE0, 0x00), _INIT_DCS_CMD(0xE1, 0x93),
+	_INIT_DCS_CMD(0xE2, 0x65), _INIT_DCS_CMD(0xE3, 0xF8),
+	_INIT_DCS_CMD(0x80, 0x01), _INIT_DCS_CMD(0xE0, 0x01),
+	_INIT_DCS_CMD(0x00, 0x00), _INIT_DCS_CMD(0x01, 0x4E),
+	_INIT_DCS_CMD(0x03, 0x00), _INIT_DCS_CMD(0x04, 0x65),
+	_INIT_DCS_CMD(0x0C, 0x74), _INIT_DCS_CMD(0x17, 0x00),
+	_INIT_DCS_CMD(0x18, 0xB7), _INIT_DCS_CMD(0x19, 0x00),
+	_INIT_DCS_CMD(0x1A, 0x00), _INIT_DCS_CMD(0x1B, 0xB7),
+	_INIT_DCS_CMD(0x1C, 0x00), _INIT_DCS_CMD(0x24, 0xFE),
+	_INIT_DCS_CMD(0x37, 0x19), _INIT_DCS_CMD(0x38, 0x05),
+	_INIT_DCS_CMD(0x39, 0x00), _INIT_DCS_CMD(0x3A, 0x01),
+	_INIT_DCS_CMD(0x3B, 0x01), _INIT_DCS_CMD(0x3C, 0x70),
+	_INIT_DCS_CMD(0x3D, 0xFF), _INIT_DCS_CMD(0x3E, 0xFF),
+	_INIT_DCS_CMD(0x3F, 0xFF), _INIT_DCS_CMD(0x40, 0x06),
+	_INIT_DCS_CMD(0x41, 0xA0), _INIT_DCS_CMD(0x43, 0x1E),
+	_INIT_DCS_CMD(0x44, 0x0F), _INIT_DCS_CMD(0x45, 0x28),
+	_INIT_DCS_CMD(0x4B, 0x04), _INIT_DCS_CMD(0x55, 0x02),
+	_INIT_DCS_CMD(0x56, 0x01), _INIT_DCS_CMD(0x57, 0xA9),
+	_INIT_DCS_CMD(0x58, 0x0A), _INIT_DCS_CMD(0x59, 0x0A),
+	_INIT_DCS_CMD(0x5A, 0x37), _INIT_DCS_CMD(0x5B, 0x19),
+	_INIT_DCS_CMD(0x5D, 0x78), _INIT_DCS_CMD(0x5E, 0x63),
+	_INIT_DCS_CMD(0x5F, 0x54), _INIT_DCS_CMD(0x60, 0x49),
+	_INIT_DCS_CMD(0x61, 0x45), _INIT_DCS_CMD(0x62, 0x38),
+	_INIT_DCS_CMD(0x63, 0x3D), _INIT_DCS_CMD(0x64, 0x28),
+	_INIT_DCS_CMD(0x65, 0x43), _INIT_DCS_CMD(0x66, 0x41),
+	_INIT_DCS_CMD(0x67, 0x43), _INIT_DCS_CMD(0x68, 0x62),
+	_INIT_DCS_CMD(0x69, 0x50), _INIT_DCS_CMD(0x6A, 0x57),
+	_INIT_DCS_CMD(0x6B, 0x49), _INIT_DCS_CMD(0x6C, 0x44),
+	_INIT_DCS_CMD(0x6D, 0x37), _INIT_DCS_CMD(0x6E, 0x23),
+	_INIT_DCS_CMD(0x6F, 0x10), _INIT_DCS_CMD(0x70, 0x78),
+	_INIT_DCS_CMD(0x71, 0x63), _INIT_DCS_CMD(0x72, 0x54),
+	_INIT_DCS_CMD(0x73, 0x49), _INIT_DCS_CMD(0x74, 0x45),
+	_INIT_DCS_CMD(0x75, 0x38), _INIT_DCS_CMD(0x76, 0x3D),
+	_INIT_DCS_CMD(0x77, 0x28), _INIT_DCS_CMD(0x78, 0x43),
+	_INIT_DCS_CMD(0x79, 0x41), _INIT_DCS_CMD(0x7A, 0x43),
+	_INIT_DCS_CMD(0x7B, 0x62), _INIT_DCS_CMD(0x7C, 0x50),
+	_INIT_DCS_CMD(0x7D, 0x57), _INIT_DCS_CMD(0x7E, 0x49),
+	_INIT_DCS_CMD(0x7F, 0x44), _INIT_DCS_CMD(0x80, 0x37),
+	_INIT_DCS_CMD(0x81, 0x23), _INIT_DCS_CMD(0x82, 0x10),
+	_INIT_DCS_CMD(0xE0, 0x02), _INIT_DCS_CMD(0x00, 0x47),
+	_INIT_DCS_CMD(0x01, 0x47), _INIT_DCS_CMD(0x02, 0x45),
+	_INIT_DCS_CMD(0x03, 0x45), _INIT_DCS_CMD(0x04, 0x4B),
+	_INIT_DCS_CMD(0x05, 0x4B), _INIT_DCS_CMD(0x06, 0x49),
+	_INIT_DCS_CMD(0x07, 0x49), _INIT_DCS_CMD(0x08, 0x41),
+	_INIT_DCS_CMD(0x09, 0x1F), _INIT_DCS_CMD(0x0A, 0x1F),
+	_INIT_DCS_CMD(0x0B, 0x1F), _INIT_DCS_CMD(0x0C, 0x1F),
+	_INIT_DCS_CMD(0x0D, 0x1F), _INIT_DCS_CMD(0x0E, 0x1F),
+	_INIT_DCS_CMD(0x0F, 0x5F), _INIT_DCS_CMD(0x10, 0x5F),
+	_INIT_DCS_CMD(0x11, 0x57), _INIT_DCS_CMD(0x12, 0x77),
+	_INIT_DCS_CMD(0x13, 0x35), _INIT_DCS_CMD(0x14, 0x1F),
+	_INIT_DCS_CMD(0x15, 0x1F), _INIT_DCS_CMD(0x16, 0x46),
+	_INIT_DCS_CMD(0x17, 0x46), _INIT_DCS_CMD(0x18, 0x44),
+	_INIT_DCS_CMD(0x19, 0x44), _INIT_DCS_CMD(0x1A, 0x4A),
+	_INIT_DCS_CMD(0x1B, 0x4A), _INIT_DCS_CMD(0x1C, 0x48),
+	_INIT_DCS_CMD(0x1D, 0x48), _INIT_DCS_CMD(0x1E, 0x40),
+	_INIT_DCS_CMD(0x1F, 0x1F), _INIT_DCS_CMD(0x20, 0x1F),
+	_INIT_DCS_CMD(0x21, 0x1F), _INIT_DCS_CMD(0x22, 0x1F),
+	_INIT_DCS_CMD(0x23, 0x1F), _INIT_DCS_CMD(0x24, 0x1F),
+	_INIT_DCS_CMD(0x25, 0x5F), _INIT_DCS_CMD(0x26, 0x5F),
+	_INIT_DCS_CMD(0x27, 0x57), _INIT_DCS_CMD(0x28, 0x77),
+	_INIT_DCS_CMD(0x29, 0x35), _INIT_DCS_CMD(0x2A, 0x1F),
+	_INIT_DCS_CMD(0x2B, 0x1F), _INIT_DCS_CMD(0x58, 0x40),
+	_INIT_DCS_CMD(0x59, 0x00), _INIT_DCS_CMD(0x5A, 0x00),
+	_INIT_DCS_CMD(0x5B, 0x10), _INIT_DCS_CMD(0x5C, 0x06),
+	_INIT_DCS_CMD(0x5D, 0x40), _INIT_DCS_CMD(0x5E, 0x01),
+	_INIT_DCS_CMD(0x5F, 0x02), _INIT_DCS_CMD(0x60, 0x30),
+	_INIT_DCS_CMD(0x61, 0x01), _INIT_DCS_CMD(0x62, 0x02),
+	_INIT_DCS_CMD(0x63, 0x03), _INIT_DCS_CMD(0x64, 0x6B),
+	_INIT_DCS_CMD(0x65, 0x05), _INIT_DCS_CMD(0x66, 0x0C),
+	_INIT_DCS_CMD(0x67, 0x73), _INIT_DCS_CMD(0x68, 0x09),
+	_INIT_DCS_CMD(0x69, 0x03), _INIT_DCS_CMD(0x6A, 0x56),
+	_INIT_DCS_CMD(0x6B, 0x08), _INIT_DCS_CMD(0x6C, 0x00),
+	_INIT_DCS_CMD(0x6D, 0x04), _INIT_DCS_CMD(0x6E, 0x04),
+	_INIT_DCS_CMD(0x6F, 0x88), _INIT_DCS_CMD(0x70, 0x00),
+	_INIT_DCS_CMD(0x71, 0x00), _INIT_DCS_CMD(0x72, 0x06),
+	_INIT_DCS_CMD(0x73, 0x7B), _INIT_DCS_CMD(0x74, 0x00),
+	_INIT_DCS_CMD(0x75, 0xF8), _INIT_DCS_CMD(0x76, 0x00),
+	_INIT_DCS_CMD(0x77, 0xD5), _INIT_DCS_CMD(0x78, 0x2E),
+	_INIT_DCS_CMD(0x79, 0x12), _INIT_DCS_CMD(0x7A, 0x03),
+	_INIT_DCS_CMD(0x7B, 0x00), _INIT_DCS_CMD(0x7C, 0x00),
+	_INIT_DCS_CMD(0x7D, 0x03), _INIT_DCS_CMD(0x7E, 0x7B),
+	_INIT_DCS_CMD(0xE0, 0x04), _INIT_DCS_CMD(0x00, 0x0E),
+	_INIT_DCS_CMD(0x02, 0xB3), _INIT_DCS_CMD(0x09, 0x60),
+	_INIT_DCS_CMD(0x0E, 0x2A), _INIT_DCS_CMD(0x36, 0x59),
+	_INIT_DCS_CMD(0x37, 0x58), _INIT_DCS_CMD(0x2B, 0x0F),
+	_INIT_DCS_CMD(0xE0, 0x00), _INIT_DCS_CMD(0x11),
+	_INIT_DELAY_CMD(120),	   _INIT_DCS_CMD(0x29),
+	_INIT_DELAY_CMD(60),	   {},
+};
+
+static const struct panel_init_cmd ws_panel_7_a_init[] = {
+	_INIT_DCS_CMD(0xFF, 0x98, 0x81, 0x03),
+	_INIT_DCS_CMD(0x01, 0x00),
+	_INIT_DCS_CMD(0x02, 0x00),
+	_INIT_DCS_CMD(0x03, 0x73),
+	_INIT_DCS_CMD(0x04, 0x00),
+	_INIT_DCS_CMD(0x05, 0x00),
+	_INIT_DCS_CMD(0x06, 0x0A),
+	_INIT_DCS_CMD(0x07, 0x00),
+	_INIT_DCS_CMD(0x08, 0x00),
+	_INIT_DCS_CMD(0x09, 0x61),
+	_INIT_DCS_CMD(0x0A, 0x00),
+	_INIT_DCS_CMD(0x0B, 0x00),
+	_INIT_DCS_CMD(0x0C, 0x01),
+	_INIT_DCS_CMD(0x0D, 0x00),
+	_INIT_DCS_CMD(0x0E, 0x00),
+	_INIT_DCS_CMD(0x0F, 0x61),
+	_INIT_DCS_CMD(0x10, 0x61),
+	_INIT_DCS_CMD(0x11, 0x00),
+	_INIT_DCS_CMD(0x12, 0x00),
+	_INIT_DCS_CMD(0x13, 0x00),
+	_INIT_DCS_CMD(0x14, 0x00),
+	_INIT_DCS_CMD(0x15, 0x00),
+	_INIT_DCS_CMD(0x16, 0x00),
+	_INIT_DCS_CMD(0x17, 0x00),
+	_INIT_DCS_CMD(0x18, 0x00),
+	_INIT_DCS_CMD(0x19, 0x00),
+	_INIT_DCS_CMD(0x1A, 0x00),
+	_INIT_DCS_CMD(0x1B, 0x00),
+	_INIT_DCS_CMD(0x1C, 0x00),
+	_INIT_DCS_CMD(0x1D, 0x00),
+	_INIT_DCS_CMD(0x1E, 0x40),
+	_INIT_DCS_CMD(0x1F, 0x80),
+	_INIT_DCS_CMD(0x20, 0x06),
+	_INIT_DCS_CMD(0x21, 0x01),
+	_INIT_DCS_CMD(0x22, 0x00),
+	_INIT_DCS_CMD(0x23, 0x00),
+	_INIT_DCS_CMD(0x24, 0x00),
+	_INIT_DCS_CMD(0x25, 0x00),
+	_INIT_DCS_CMD(0x26, 0x00),
+	_INIT_DCS_CMD(0x27, 0x00),
+	_INIT_DCS_CMD(0x28, 0x33),
+	_INIT_DCS_CMD(0x29, 0x03),
+	_INIT_DCS_CMD(0x2A, 0x00),
+	_INIT_DCS_CMD(0x2B, 0x00),
+	_INIT_DCS_CMD(0x2C, 0x00),
+	_INIT_DCS_CMD(0x2D, 0x00),
+	_INIT_DCS_CMD(0x2E, 0x00),
+	_INIT_DCS_CMD(0x2F, 0x00),
+	_INIT_DCS_CMD(0x30, 0x00),
+	_INIT_DCS_CMD(0x31, 0x00),
+	_INIT_DCS_CMD(0x32, 0x00),
+	_INIT_DCS_CMD(0x33, 0x00),
+	_INIT_DCS_CMD(0x34, 0x04),
+	_INIT_DCS_CMD(0x35, 0x00),
+	_INIT_DCS_CMD(0x36, 0x00),
+	_INIT_DCS_CMD(0x37, 0x00),
+	_INIT_DCS_CMD(0x38, 0x3C),
+	_INIT_DCS_CMD(0x39, 0x00),
+	_INIT_DCS_CMD(0x3A, 0x00),
+	_INIT_DCS_CMD(0x3B, 0x00),
+	_INIT_DCS_CMD(0x3C, 0x00),
+	_INIT_DCS_CMD(0x3D, 0x00),
+	_INIT_DCS_CMD(0x3E, 0x00),
+	_INIT_DCS_CMD(0x3F, 0x00),
+	_INIT_DCS_CMD(0x40, 0x00),
+	_INIT_DCS_CMD(0x41, 0x00),
+	_INIT_DCS_CMD(0x42, 0x00),
+	_INIT_DCS_CMD(0x43, 0x00),
+	_INIT_DCS_CMD(0x44, 0x00),
+	_INIT_DCS_CMD(0x50, 0x10),
+	_INIT_DCS_CMD(0x51, 0x32),
+	_INIT_DCS_CMD(0x52, 0x54),
+	_INIT_DCS_CMD(0x53, 0x76),
+	_INIT_DCS_CMD(0x54, 0x98),
+	_INIT_DCS_CMD(0x55, 0xBA),
+	_INIT_DCS_CMD(0x56, 0x10),
+	_INIT_DCS_CMD(0x57, 0x32),
+	_INIT_DCS_CMD(0x58, 0x54),
+	_INIT_DCS_CMD(0x59, 0x76),
+	_INIT_DCS_CMD(0x5A, 0x98),
+	_INIT_DCS_CMD(0x5B, 0xBA),
+	_INIT_DCS_CMD(0x5C, 0xDC),
+	_INIT_DCS_CMD(0x5D, 0xFE),
+	_INIT_DCS_CMD(0x5E, 0x00),
+	_INIT_DCS_CMD(0x5F, 0x0E),
+	_INIT_DCS_CMD(0x60, 0x0F),
+	_INIT_DCS_CMD(0x61, 0x0C),
+	_INIT_DCS_CMD(0x62, 0x0D),
+	_INIT_DCS_CMD(0x63, 0x06),
+	_INIT_DCS_CMD(0x64, 0x07),
+	_INIT_DCS_CMD(0x65, 0x02),
+	_INIT_DCS_CMD(0x66, 0x02),
+	_INIT_DCS_CMD(0x67, 0x02),
+	_INIT_DCS_CMD(0x68, 0x02),
+	_INIT_DCS_CMD(0x69, 0x01),
+	_INIT_DCS_CMD(0x6A, 0x00),
+	_INIT_DCS_CMD(0x6B, 0x02),
+	_INIT_DCS_CMD(0x6C, 0x15),
+	_INIT_DCS_CMD(0x6D, 0x14),
+	_INIT_DCS_CMD(0x6E, 0x02),
+	_INIT_DCS_CMD(0x6F, 0x02),
+	_INIT_DCS_CMD(0x70, 0x02),
+	_INIT_DCS_CMD(0x71, 0x02),
+	_INIT_DCS_CMD(0x72, 0x02),
+	_INIT_DCS_CMD(0x73, 0x02),
+	_INIT_DCS_CMD(0x74, 0x02),
+	_INIT_DCS_CMD(0x75, 0x0E),
+	_INIT_DCS_CMD(0x76, 0x0F),
+	_INIT_DCS_CMD(0x77, 0x0C),
+	_INIT_DCS_CMD(0x78, 0x0D),
+	_INIT_DCS_CMD(0x79, 0x06),
+	_INIT_DCS_CMD(0x7A, 0x07),
+	_INIT_DCS_CMD(0x7B, 0x02),
+	_INIT_DCS_CMD(0x7C, 0x02),
+	_INIT_DCS_CMD(0x7D, 0x02),
+	_INIT_DCS_CMD(0x7E, 0x02),
+	_INIT_DCS_CMD(0x7F, 0x01),
+	_INIT_DCS_CMD(0x80, 0x00),
+	_INIT_DCS_CMD(0x81, 0x02),
+	_INIT_DCS_CMD(0x82, 0x14),
+	_INIT_DCS_CMD(0x83, 0x15),
+	_INIT_DCS_CMD(0x84, 0x02),
+	_INIT_DCS_CMD(0x85, 0x02),
+	_INIT_DCS_CMD(0x86, 0x02),
+	_INIT_DCS_CMD(0x87, 0x02),
+	_INIT_DCS_CMD(0x88, 0x02),
+	_INIT_DCS_CMD(0x89, 0x02),
+	_INIT_DCS_CMD(0x8A, 0x02),
+	_INIT_DCS_CMD(0xFF, 0x98, 0x81, 0x04),
+	_INIT_DCS_CMD(0x38, 0x01),
+	_INIT_DCS_CMD(0x39, 0x00),
+	_INIT_DCS_CMD(0x6C, 0x15),
+	_INIT_DCS_CMD(0x6E, 0x2A),
+	_INIT_DCS_CMD(0x6F, 0x33),
+	_INIT_DCS_CMD(0x3A, 0x94),
+	_INIT_DCS_CMD(0x8D, 0x14),
+	_INIT_DCS_CMD(0x87, 0xBA),
+	_INIT_DCS_CMD(0x26, 0x76),
+	_INIT_DCS_CMD(0xB2, 0xD1),
+	_INIT_DCS_CMD(0xB5, 0x06),
+	_INIT_DCS_CMD(0x3B, 0x98),
+	_INIT_DCS_CMD(0xFF, 0x98, 0x81, 0x01),
+	_INIT_DCS_CMD(0x22, 0x0A),
+	_INIT_DCS_CMD(0x31, 0x00),
+	_INIT_DCS_CMD(0x53, 0x71),
+	_INIT_DCS_CMD(0x55, 0x8F),
+	_INIT_DCS_CMD(0x40, 0x33),
+	_INIT_DCS_CMD(0x50, 0x96),
+	_INIT_DCS_CMD(0x51, 0x96),
+	_INIT_DCS_CMD(0x60, 0x23),
+	_INIT_DCS_CMD(0xA0, 0x08),
+	_INIT_DCS_CMD(0xA1, 0x1D),
+	_INIT_DCS_CMD(0xA2, 0x2A),
+	_INIT_DCS_CMD(0xA3, 0x10),
+	_INIT_DCS_CMD(0xA4, 0x15),
+	_INIT_DCS_CMD(0xA5, 0x28),
+	_INIT_DCS_CMD(0xA6, 0x1C),
+	_INIT_DCS_CMD(0xA7, 0x1D),
+	_INIT_DCS_CMD(0xA8, 0x7E),
+	_INIT_DCS_CMD(0xA9, 0x1D),
+	_INIT_DCS_CMD(0xAA, 0x29),
+	_INIT_DCS_CMD(0xAB, 0x6B),
+	_INIT_DCS_CMD(0xAC, 0x1A),
+	_INIT_DCS_CMD(0xAD, 0x18),
+	_INIT_DCS_CMD(0xAE, 0x4B),
+	_INIT_DCS_CMD(0xAF, 0x20),
+	_INIT_DCS_CMD(0xB0, 0x27),
+	_INIT_DCS_CMD(0xB1, 0x50),
+	_INIT_DCS_CMD(0xB2, 0x64),
+	_INIT_DCS_CMD(0xB3, 0x39),
+	_INIT_DCS_CMD(0xC0, 0x08),
+	_INIT_DCS_CMD(0xC1, 0x1D),
+	_INIT_DCS_CMD(0xC2, 0x2A),
+	_INIT_DCS_CMD(0xC3, 0x10),
+	_INIT_DCS_CMD(0xC4, 0x15),
+	_INIT_DCS_CMD(0xC5, 0x28),
+	_INIT_DCS_CMD(0xC6, 0x1C),
+	_INIT_DCS_CMD(0xC7, 0x1D),
+	_INIT_DCS_CMD(0xC8, 0x7E),
+	_INIT_DCS_CMD(0xC9, 0x1D),
+	_INIT_DCS_CMD(0xCA, 0x29),
+	_INIT_DCS_CMD(0xCB, 0x6B),
+	_INIT_DCS_CMD(0xCC, 0x1A),
+	_INIT_DCS_CMD(0xCD, 0x18),
+	_INIT_DCS_CMD(0xCE, 0x4B),
+	_INIT_DCS_CMD(0xCF, 0x20),
+	_INIT_DCS_CMD(0xD0, 0x27),
+	_INIT_DCS_CMD(0xD1, 0x50),
+	_INIT_DCS_CMD(0xD2, 0x64),
+	_INIT_DCS_CMD(0xD3, 0x39),
+	_INIT_DCS_CMD(0xFF, 0x98, 0x81, 0x00),
+	_INIT_DCS_CMD(0x3A, 0x77),
+	_INIT_DCS_CMD(0x36, 0x00),
+	_INIT_DCS_CMD(0x35, 0x00),
+	_INIT_DCS_CMD(0x11),
+	_INIT_DELAY_CMD(120),
+	_INIT_DCS_CMD(0x29),
+	_INIT_DELAY_CMD(60),
+	{},
+};
+
+static const struct panel_init_cmd ws_panel_7_c_init[] = {
+	_INIT_DCS_CMD(0x80, 0x8B),
+	_INIT_DCS_CMD(0x81, 0x78),
+	_INIT_DCS_CMD(0x82, 0x84),
+	_INIT_DCS_CMD(0x83, 0x88),
+	_INIT_DCS_CMD(0x84, 0xA8),
+	_INIT_DCS_CMD(0x85, 0xE3),
+	_INIT_DCS_CMD(0x86, 0x88),
+	_INIT_DCS_CMD(0x87, 0x5A),
+	_INIT_DCS_CMD(0xB0, 0x80),
+	_INIT_DCS_CMD(0xB1, 0x30),
+	_INIT_DCS_CMD(0xB2, 0x50),
+	_INIT_DCS_CMD(0x11),
+	_INIT_DELAY_CMD(120),
+	_INIT_DCS_CMD(0x29),
+	_INIT_DELAY_CMD(20),
+	{},
+};
+
+static const struct panel_init_cmd ws_panel_5_5_a_init[] = {
+	_INIT_DCS_CMD(0xB9, 0xFF, 0x83, 0x94),
+	_INIT_DCS_CMD(0xBA, 0x61, 0x03, 0x68, 0x6B, 0xB2, 0xC0),
+	_INIT_DCS_CMD(0xB1, 0x48, 0x12, 0x72, 0x09, 0x32, 0x54, 0x71, 0x71,
+		      0x57, 0x47),
+	_INIT_DCS_CMD(0xB2, 0x00, 0x80, 0x64, 0x0C, 0x0D, 0x2F),
+	_INIT_DCS_CMD(0xB4, 0x73, 0x74, 0x73, 0x74, 0x73, 0x74, 0x01, 0x0C,
+		      0x86, 0x75, 0x00, 0x3F, 0x73, 0x74, 0x73, 0x74, 0x73,
+		      0x74, 0x01, 0x0C, 0x86),
+	_INIT_DCS_CMD(0xB6, 0x86, 0x86),
+	_INIT_DCS_CMD(0xD3, 0x00, 0x00, 0x07, 0x07, 0x40, 0x07, 0x0C, 0x00,
+		      0x08, 0x10, 0x08, 0x00, 0x08, 0x54, 0x15, 0x0A, 0x05,
+		      0x0A, 0x02, 0x15, 0x06, 0x05, 0x06, 0x47, 0x44, 0x0A,
+		      0x0A, 0x4B, 0x10, 0x07, 0x07, 0x0C, 0x40),
+	_INIT_DCS_CMD(0xD5, 0x1C, 0x1C, 0x1D, 0x1D, 0x00, 0x01, 0x02, 0x03,
+		      0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x24,
+		      0x25, 0x18, 0x18, 0x26, 0x27, 0x18, 0x18, 0x18, 0x18,
+		      0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+		      0x18, 0x18, 0x18, 0x20, 0x21, 0x18, 0x18, 0x18, 0x18),
+	_INIT_DCS_CMD(0xD6, 0x1C, 0x1C, 0x1D, 0x1D, 0x07, 0x06, 0x05, 0x04,
+		      0x03, 0x02, 0x01, 0x00, 0x0B, 0x0A, 0x09, 0x08, 0x21,
+		      0x20, 0x18, 0x18, 0x27, 0x26, 0x18, 0x18, 0x18, 0x18,
+		      0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+		      0x18, 0x18, 0x18, 0x25, 0x24, 0x18, 0x18, 0x18, 0x18),
+	_INIT_DCS_CMD(0xE0, 0x00, 0x13, 0x21, 0x28, 0x2B, 0x2E, 0x32, 0x2F,
+		      0x61, 0x6E, 0x7E, 0x7B, 0x80, 0x8F, 0x91, 0x93, 0x9D,
+		      0x9D, 0x97, 0xA4, 0xB1, 0x57, 0x55, 0x58, 0x5D, 0x60,
+		      0x67, 0x74, 0x7F, 0x00, 0x13, 0x21, 0x28, 0x2B, 0x2E,
+		      0x32, 0x2F, 0x61, 0x6E, 0x7D, 0x7B, 0x7F, 0x8E, 0x90,
+		      0x93, 0x9C, 0x9D, 0x98, 0xA4, 0xB1, 0x58, 0x55, 0x59,
+		      0x5E, 0x61, 0x68, 0x76, 0x7F),
+	_INIT_DCS_CMD(0xC0, 0x1F, 0x31),
+	_INIT_DCS_CMD(0xCC, 0x07),
+	_INIT_DCS_CMD(0xD4, 0x02),
+	_INIT_DCS_CMD(0xBD, 0x02),
+	_INIT_DCS_CMD(0xD8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+		      0xFF, 0xFF, 0xFF, 0xFF),
+	_INIT_DCS_CMD(0xBD, 0x00),
+	_INIT_DCS_CMD(0xBD, 0x01),
+	_INIT_DCS_CMD(0xB1, 0x00),
+	_INIT_DCS_CMD(0xBD, 0x00),
+	_INIT_DCS_CMD(0xC6, 0xED),
+	_INIT_DCS_CMD(0xC7, 0x00, 0xC0),
+	_INIT_DCS_CMD(0x11),
+	_INIT_DELAY_CMD(120),
+	_INIT_DCS_CMD(0x29),
+	_INIT_DELAY_CMD(50),
+	{},
+};
+
+static const struct panel_init_cmd ws_panel_5_a_init[] = {
+	_INIT_DCS_CMD(0xB9, 0xFF, 0x83, 0x94),
+	_INIT_DCS_CMD(0xB1, 0x48, 0x0A, 0x6A, 0x09, 0x33, 0x54, 0x71, 0x71,
+		      0x2E, 0x45),
+	_INIT_DCS_CMD(0xBA, 0x61, 0x03, 0x68, 0x6B, 0xB2, 0xC0),
+	_INIT_DCS_CMD(0xB2, 0x00, 0x80, 0x64, 0x0C, 0x06, 0x2F),
+	_INIT_DCS_CMD(0xB4, 0x1C, 0x78, 0x1C, 0x78, 0x1C, 0x78, 0x01, 0x0C,
+		      0x86, 0x75, 0x00, 0x3F, 0x1C, 0x78, 0x1C, 0x78, 0x1C,
+		      0x78, 0x01, 0x0C, 0x86),
+	_INIT_DCS_CMD(0xD3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08,
+		      0x32, 0x10, 0x05, 0x00, 0x05, 0x32, 0x13, 0xC1, 0x00,
+		      0x01, 0x32, 0x10, 0x08, 0x00, 0x00, 0x37, 0x03, 0x07,
+		      0x07, 0x37, 0x05, 0x05, 0x37, 0x0C, 0x40),
+	_INIT_DCS_CMD(0xD5, 0x18, 0x18, 0x18, 0x18, 0x22, 0x23, 0x20, 0x21,
+		      0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x18,
+		      0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+		      0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+		      0x18, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x19),
+	_INIT_DCS_CMD(0xD6, 0x18, 0x18, 0x19, 0x19, 0x21, 0x20, 0x23, 0x22,
+		      0x03, 0x02, 0x01, 0x00, 0x07, 0x06, 0x05, 0x04, 0x18,
+		      0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+		      0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+		      0x18, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x18, 0x18),
+	_INIT_DCS_CMD(0xE0, 0x07, 0x08, 0x09, 0x0D, 0x10, 0x14, 0x16, 0x13,
+		      0x24, 0x36, 0x48, 0x4A, 0x58, 0x6F, 0x76, 0x80, 0x97,
+		      0xA5, 0xA8, 0xB5, 0xC6, 0x62, 0x63, 0x68, 0x6F, 0x72,
+		      0x78, 0x7F, 0x7F, 0x00, 0x02, 0x08, 0x0D, 0x0C, 0x0E,
+		      0x0F, 0x10, 0x24, 0x36, 0x48, 0x4A, 0x58, 0x6F, 0x78,
+		      0x82, 0x99, 0xA4, 0xA0, 0xB1, 0xC0, 0x5E, 0x5E, 0x64,
+		      0x6B, 0x6C, 0x73, 0x7F, 0x7F),
+	_INIT_DCS_CMD(0xCC, 0x0B),
+	_INIT_DCS_CMD(0xC0, 0x1F, 0x73),
+	_INIT_DCS_CMD(0xB6, 0x6B, 0x6B),
+	_INIT_DCS_CMD(0xD4, 0x02),
+	_INIT_DCS_CMD(0xBD, 0x01),
+	_INIT_DCS_CMD(0xB1, 0x00),
+	_INIT_DCS_CMD(0xBD, 0x00),
+	_INIT_DCS_CMD(0xBF, 0x40, 0x81, 0x50, 0x00, 0x1A, 0xFC, 0x01),
+	_INIT_DCS_CMD(0x11),
+	_INIT_DELAY_CMD(200),
+	_INIT_DCS_CMD(0xB2, 0x00, 0x80, 0x64, 0x0C, 0x06, 0x2F, 0x00, 0x00,
+		      0x00, 0x00, 0xC0, 0x18),
+	_INIT_DCS_CMD(0x29),
+	_INIT_DELAY_CMD(80),
+	{},
+};
+
+static const struct panel_init_cmd ws_panel_4_c_init[] = {
+	_INIT_DCS_CMD(0xE0, 0x00), _INIT_DCS_CMD(0xE1, 0x93),
+	_INIT_DCS_CMD(0xE2, 0x65), _INIT_DCS_CMD(0xE3, 0xF8),
+	_INIT_DCS_CMD(0x80, 0x01), _INIT_DCS_CMD(0xE0, 0x01),
+	_INIT_DCS_CMD(0x00, 0x00), _INIT_DCS_CMD(0x01, 0x41),
+	_INIT_DCS_CMD(0x03, 0x10), _INIT_DCS_CMD(0x04, 0x44),
+	_INIT_DCS_CMD(0x17, 0x00), _INIT_DCS_CMD(0x18, 0xD0),
+	_INIT_DCS_CMD(0x19, 0x00), _INIT_DCS_CMD(0x1A, 0x00),
+	_INIT_DCS_CMD(0x1B, 0xD0), _INIT_DCS_CMD(0x1C, 0x00),
+	_INIT_DCS_CMD(0x24, 0xFE), _INIT_DCS_CMD(0x35, 0x26),
+	_INIT_DCS_CMD(0x37, 0x09), _INIT_DCS_CMD(0x38, 0x04),
+	_INIT_DCS_CMD(0x39, 0x08), _INIT_DCS_CMD(0x3A, 0x0A),
+	_INIT_DCS_CMD(0x3C, 0x78), _INIT_DCS_CMD(0x3D, 0xFF),
+	_INIT_DCS_CMD(0x3E, 0xFF), _INIT_DCS_CMD(0x3F, 0xFF),
+	_INIT_DCS_CMD(0x40, 0x04), _INIT_DCS_CMD(0x41, 0x64),
+	_INIT_DCS_CMD(0x42, 0xC7), _INIT_DCS_CMD(0x43, 0x18),
+	_INIT_DCS_CMD(0x44, 0x0B), _INIT_DCS_CMD(0x45, 0x14),
+	_INIT_DCS_CMD(0x55, 0x02), _INIT_DCS_CMD(0x57, 0x49),
+	_INIT_DCS_CMD(0x59, 0x0A), _INIT_DCS_CMD(0x5A, 0x1B),
+	_INIT_DCS_CMD(0x5B, 0x19), _INIT_DCS_CMD(0x5D, 0x7F),
+	_INIT_DCS_CMD(0x5E, 0x56), _INIT_DCS_CMD(0x5F, 0x43),
+	_INIT_DCS_CMD(0x60, 0x37), _INIT_DCS_CMD(0x61, 0x33),
+	_INIT_DCS_CMD(0x62, 0x25), _INIT_DCS_CMD(0x63, 0x2A),
+	_INIT_DCS_CMD(0x64, 0x16), _INIT_DCS_CMD(0x65, 0x30),
+	_INIT_DCS_CMD(0x66, 0x2F), _INIT_DCS_CMD(0x67, 0x32),
+	_INIT_DCS_CMD(0x68, 0x53), _INIT_DCS_CMD(0x69, 0x43),
+	_INIT_DCS_CMD(0x6A, 0x4C), _INIT_DCS_CMD(0x6B, 0x40),
+	_INIT_DCS_CMD(0x6C, 0x3D), _INIT_DCS_CMD(0x6D, 0x31),
+	_INIT_DCS_CMD(0x6E, 0x20), _INIT_DCS_CMD(0x6F, 0x0F),
+	_INIT_DCS_CMD(0x70, 0x7F), _INIT_DCS_CMD(0x71, 0x56),
+	_INIT_DCS_CMD(0x72, 0x43), _INIT_DCS_CMD(0x73, 0x37),
+	_INIT_DCS_CMD(0x74, 0x33), _INIT_DCS_CMD(0x75, 0x25),
+	_INIT_DCS_CMD(0x76, 0x2A), _INIT_DCS_CMD(0x77, 0x16),
+	_INIT_DCS_CMD(0x78, 0x30), _INIT_DCS_CMD(0x79, 0x2F),
+	_INIT_DCS_CMD(0x7A, 0x32), _INIT_DCS_CMD(0x7B, 0x53),
+	_INIT_DCS_CMD(0x7C, 0x43), _INIT_DCS_CMD(0x7D, 0x4C),
+	_INIT_DCS_CMD(0x7E, 0x40), _INIT_DCS_CMD(0x7F, 0x3D),
+	_INIT_DCS_CMD(0x80, 0x31), _INIT_DCS_CMD(0x81, 0x20),
+	_INIT_DCS_CMD(0x82, 0x0F), _INIT_DCS_CMD(0xE0, 0x02),
+	_INIT_DCS_CMD(0x00, 0x5F), _INIT_DCS_CMD(0x01, 0x5F),
+	_INIT_DCS_CMD(0x02, 0x5E), _INIT_DCS_CMD(0x03, 0x5E),
+	_INIT_DCS_CMD(0x04, 0x50), _INIT_DCS_CMD(0x05, 0x48),
+	_INIT_DCS_CMD(0x06, 0x48), _INIT_DCS_CMD(0x07, 0x4A),
+	_INIT_DCS_CMD(0x08, 0x4A), _INIT_DCS_CMD(0x09, 0x44),
+	_INIT_DCS_CMD(0x0A, 0x44), _INIT_DCS_CMD(0x0B, 0x46),
+	_INIT_DCS_CMD(0x0C, 0x46), _INIT_DCS_CMD(0x0D, 0x5F),
+	_INIT_DCS_CMD(0x0E, 0x5F), _INIT_DCS_CMD(0x0F, 0x57),
+	_INIT_DCS_CMD(0x10, 0x57), _INIT_DCS_CMD(0x11, 0x77),
+	_INIT_DCS_CMD(0x12, 0x77), _INIT_DCS_CMD(0x13, 0x40),
+	_INIT_DCS_CMD(0x14, 0x42), _INIT_DCS_CMD(0x15, 0x5F),
+	_INIT_DCS_CMD(0x16, 0x5F), _INIT_DCS_CMD(0x17, 0x5F),
+	_INIT_DCS_CMD(0x18, 0x5E), _INIT_DCS_CMD(0x19, 0x5E),
+	_INIT_DCS_CMD(0x1A, 0x50), _INIT_DCS_CMD(0x1B, 0x49),
+	_INIT_DCS_CMD(0x1C, 0x49), _INIT_DCS_CMD(0x1D, 0x4B),
+	_INIT_DCS_CMD(0x1E, 0x4B), _INIT_DCS_CMD(0x1F, 0x45),
+	_INIT_DCS_CMD(0x20, 0x45), _INIT_DCS_CMD(0x21, 0x47),
+	_INIT_DCS_CMD(0x22, 0x47), _INIT_DCS_CMD(0x23, 0x5F),
+	_INIT_DCS_CMD(0x24, 0x5F), _INIT_DCS_CMD(0x25, 0x57),
+	_INIT_DCS_CMD(0x26, 0x57), _INIT_DCS_CMD(0x27, 0x77),
+	_INIT_DCS_CMD(0x28, 0x77), _INIT_DCS_CMD(0x29, 0x41),
+	_INIT_DCS_CMD(0x2A, 0x43), _INIT_DCS_CMD(0x2B, 0x5F),
+	_INIT_DCS_CMD(0x2C, 0x1E), _INIT_DCS_CMD(0x2D, 0x1E),
+	_INIT_DCS_CMD(0x2E, 0x1F), _INIT_DCS_CMD(0x2F, 0x1F),
+	_INIT_DCS_CMD(0x30, 0x10), _INIT_DCS_CMD(0x31, 0x07),
+	_INIT_DCS_CMD(0x32, 0x07), _INIT_DCS_CMD(0x33, 0x05),
+	_INIT_DCS_CMD(0x34, 0x05), _INIT_DCS_CMD(0x35, 0x0B),
+	_INIT_DCS_CMD(0x36, 0x0B), _INIT_DCS_CMD(0x37, 0x09),
+	_INIT_DCS_CMD(0x38, 0x09), _INIT_DCS_CMD(0x39, 0x1F),
+	_INIT_DCS_CMD(0x3A, 0x1F), _INIT_DCS_CMD(0x3B, 0x17),
+	_INIT_DCS_CMD(0x3C, 0x17), _INIT_DCS_CMD(0x3D, 0x17),
+	_INIT_DCS_CMD(0x3E, 0x17), _INIT_DCS_CMD(0x3F, 0x03),
+	_INIT_DCS_CMD(0x40, 0x01), _INIT_DCS_CMD(0x41, 0x1F),
+	_INIT_DCS_CMD(0x42, 0x1E), _INIT_DCS_CMD(0x43, 0x1E),
+	_INIT_DCS_CMD(0x44, 0x1F), _INIT_DCS_CMD(0x45, 0x1F),
+	_INIT_DCS_CMD(0x46, 0x10), _INIT_DCS_CMD(0x47, 0x06),
+	_INIT_DCS_CMD(0x48, 0x06), _INIT_DCS_CMD(0x49, 0x04),
+	_INIT_DCS_CMD(0x4A, 0x04), _INIT_DCS_CMD(0x4B, 0x0A),
+	_INIT_DCS_CMD(0x4C, 0x0A), _INIT_DCS_CMD(0x4D, 0x08),
+	_INIT_DCS_CMD(0x4E, 0x08), _INIT_DCS_CMD(0x4F, 0x1F),
+	_INIT_DCS_CMD(0x50, 0x1F), _INIT_DCS_CMD(0x51, 0x17),
+	_INIT_DCS_CMD(0x52, 0x17), _INIT_DCS_CMD(0x53, 0x17),
+	_INIT_DCS_CMD(0x54, 0x17), _INIT_DCS_CMD(0x55, 0x02),
+	_INIT_DCS_CMD(0x56, 0x00), _INIT_DCS_CMD(0x57, 0x1F),
+	_INIT_DCS_CMD(0xE0, 0x02), _INIT_DCS_CMD(0x58, 0x40),
+	_INIT_DCS_CMD(0x59, 0x00), _INIT_DCS_CMD(0x5A, 0x00),
+	_INIT_DCS_CMD(0x5B, 0x30), _INIT_DCS_CMD(0x5C, 0x01),
+	_INIT_DCS_CMD(0x5D, 0x30), _INIT_DCS_CMD(0x5E, 0x01),
+	_INIT_DCS_CMD(0x5F, 0x02), _INIT_DCS_CMD(0x60, 0x30),
+	_INIT_DCS_CMD(0x61, 0x03), _INIT_DCS_CMD(0x62, 0x04),
+	_INIT_DCS_CMD(0x63, 0x04), _INIT_DCS_CMD(0x64, 0xA6),
+	_INIT_DCS_CMD(0x65, 0x43), _INIT_DCS_CMD(0x66, 0x30),
+	_INIT_DCS_CMD(0x67, 0x73), _INIT_DCS_CMD(0x68, 0x05),
+	_INIT_DCS_CMD(0x69, 0x04), _INIT_DCS_CMD(0x6A, 0x7F),
+	_INIT_DCS_CMD(0x6B, 0x08), _INIT_DCS_CMD(0x6C, 0x00),
+	_INIT_DCS_CMD(0x6D, 0x04), _INIT_DCS_CMD(0x6E, 0x04),
+	_INIT_DCS_CMD(0x6F, 0x88), _INIT_DCS_CMD(0x75, 0xD9),
+	_INIT_DCS_CMD(0x76, 0x00), _INIT_DCS_CMD(0x77, 0x33),
+	_INIT_DCS_CMD(0x78, 0x43), _INIT_DCS_CMD(0xE0, 0x00),
+	_INIT_DCS_CMD(0x11),	   _INIT_DELAY_CMD(120),
+	_INIT_DCS_CMD(0x29),	   _INIT_DELAY_CMD(5),
+	_INIT_DCS_CMD(0x35, 0x00), {},
+};
+
+static const struct panel_init_cmd ws_panel_3_4_c_init[] = {
+	_INIT_DCS_CMD(0xE0, 0x00), _INIT_DCS_CMD(0xE1, 0x93),
+	_INIT_DCS_CMD(0xE2, 0x65), _INIT_DCS_CMD(0xE3, 0xF8),
+	_INIT_DCS_CMD(0x80, 0x01), _INIT_DCS_CMD(0xE0, 0x01),
+	_INIT_DCS_CMD(0x00, 0x00), _INIT_DCS_CMD(0x01, 0x41),
+	_INIT_DCS_CMD(0x03, 0x10), _INIT_DCS_CMD(0x04, 0x44),
+	_INIT_DCS_CMD(0x17, 0x00), _INIT_DCS_CMD(0x18, 0xD0),
+	_INIT_DCS_CMD(0x19, 0x00), _INIT_DCS_CMD(0x1A, 0x00),
+	_INIT_DCS_CMD(0x1B, 0xD0), _INIT_DCS_CMD(0x1C, 0x00),
+	_INIT_DCS_CMD(0x24, 0xFE), _INIT_DCS_CMD(0x35, 0x26),
+	_INIT_DCS_CMD(0x37, 0x09), _INIT_DCS_CMD(0x38, 0x04),
+	_INIT_DCS_CMD(0x39, 0x08), _INIT_DCS_CMD(0x3A, 0x0A),
+	_INIT_DCS_CMD(0x3C, 0x78), _INIT_DCS_CMD(0x3D, 0xFF),
+	_INIT_DCS_CMD(0x3E, 0xFF), _INIT_DCS_CMD(0x3F, 0xFF),
+	_INIT_DCS_CMD(0x40, 0x00), _INIT_DCS_CMD(0x41, 0x64),
+	_INIT_DCS_CMD(0x42, 0xC7), _INIT_DCS_CMD(0x43, 0x18),
+	_INIT_DCS_CMD(0x44, 0x0B), _INIT_DCS_CMD(0x45, 0x14),
+	_INIT_DCS_CMD(0x55, 0x02), _INIT_DCS_CMD(0x57, 0x49),
+	_INIT_DCS_CMD(0x59, 0x0A), _INIT_DCS_CMD(0x5A, 0x1B),
+	_INIT_DCS_CMD(0x5B, 0x19), _INIT_DCS_CMD(0x5D, 0x7F),
+	_INIT_DCS_CMD(0x5E, 0x56), _INIT_DCS_CMD(0x5F, 0x43),
+	_INIT_DCS_CMD(0x60, 0x37), _INIT_DCS_CMD(0x61, 0x33),
+	_INIT_DCS_CMD(0x62, 0x25), _INIT_DCS_CMD(0x63, 0x2A),
+	_INIT_DCS_CMD(0x64, 0x16), _INIT_DCS_CMD(0x65, 0x30),
+	_INIT_DCS_CMD(0x66, 0x2F), _INIT_DCS_CMD(0x67, 0x32),
+	_INIT_DCS_CMD(0x68, 0x53), _INIT_DCS_CMD(0x69, 0x43),
+	_INIT_DCS_CMD(0x6A, 0x4C), _INIT_DCS_CMD(0x6B, 0x40),
+	_INIT_DCS_CMD(0x6C, 0x3D), _INIT_DCS_CMD(0x6D, 0x31),
+	_INIT_DCS_CMD(0x6E, 0x20), _INIT_DCS_CMD(0x6F, 0x0F),
+	_INIT_DCS_CMD(0x70, 0x7F), _INIT_DCS_CMD(0x71, 0x56),
+	_INIT_DCS_CMD(0x72, 0x43), _INIT_DCS_CMD(0x73, 0x37),
+	_INIT_DCS_CMD(0x74, 0x33), _INIT_DCS_CMD(0x75, 0x25),
+	_INIT_DCS_CMD(0x76, 0x2A), _INIT_DCS_CMD(0x77, 0x16),
+	_INIT_DCS_CMD(0x78, 0x30), _INIT_DCS_CMD(0x79, 0x2F),
+	_INIT_DCS_CMD(0x7A, 0x32), _INIT_DCS_CMD(0x7B, 0x53),
+	_INIT_DCS_CMD(0x7C, 0x43), _INIT_DCS_CMD(0x7D, 0x4C),
+	_INIT_DCS_CMD(0x7E, 0x40), _INIT_DCS_CMD(0x7F, 0x3D),
+	_INIT_DCS_CMD(0x80, 0x31), _INIT_DCS_CMD(0x81, 0x20),
+	_INIT_DCS_CMD(0x82, 0x0F), _INIT_DCS_CMD(0xE0, 0x02),
+	_INIT_DCS_CMD(0x00, 0x5F), _INIT_DCS_CMD(0x01, 0x5F),
+	_INIT_DCS_CMD(0x02, 0x5E), _INIT_DCS_CMD(0x03, 0x5E),
+	_INIT_DCS_CMD(0x04, 0x50), _INIT_DCS_CMD(0x05, 0x48),
+	_INIT_DCS_CMD(0x06, 0x48), _INIT_DCS_CMD(0x07, 0x4A),
+	_INIT_DCS_CMD(0x08, 0x4A), _INIT_DCS_CMD(0x09, 0x44),
+	_INIT_DCS_CMD(0x0A, 0x44), _INIT_DCS_CMD(0x0B, 0x46),
+	_INIT_DCS_CMD(0x0C, 0x46), _INIT_DCS_CMD(0x0D, 0x5F),
+	_INIT_DCS_CMD(0x0E, 0x5F), _INIT_DCS_CMD(0x0F, 0x57),
+	_INIT_DCS_CMD(0x10, 0x57), _INIT_DCS_CMD(0x11, 0x77),
+	_INIT_DCS_CMD(0x12, 0x77), _INIT_DCS_CMD(0x13, 0x40),
+	_INIT_DCS_CMD(0x14, 0x42), _INIT_DCS_CMD(0x15, 0x5F),
+	_INIT_DCS_CMD(0x16, 0x5F), _INIT_DCS_CMD(0x17, 0x5F),
+	_INIT_DCS_CMD(0x18, 0x5E), _INIT_DCS_CMD(0x19, 0x5E),
+	_INIT_DCS_CMD(0x1A, 0x50), _INIT_DCS_CMD(0x1B, 0x49),
+	_INIT_DCS_CMD(0x1C, 0x49), _INIT_DCS_CMD(0x1D, 0x4B),
+	_INIT_DCS_CMD(0x1E, 0x4B), _INIT_DCS_CMD(0x1F, 0x45),
+	_INIT_DCS_CMD(0x20, 0x45), _INIT_DCS_CMD(0x21, 0x47),
+	_INIT_DCS_CMD(0x22, 0x47), _INIT_DCS_CMD(0x23, 0x5F),
+	_INIT_DCS_CMD(0x24, 0x5F), _INIT_DCS_CMD(0x25, 0x57),
+	_INIT_DCS_CMD(0x26, 0x57), _INIT_DCS_CMD(0x27, 0x77),
+	_INIT_DCS_CMD(0x28, 0x77), _INIT_DCS_CMD(0x29, 0x41),
+	_INIT_DCS_CMD(0x2A, 0x43), _INIT_DCS_CMD(0x2B, 0x5F),
+	_INIT_DCS_CMD(0x2C, 0x1E), _INIT_DCS_CMD(0x2D, 0x1E),
+	_INIT_DCS_CMD(0x2E, 0x1F), _INIT_DCS_CMD(0x2F, 0x1F),
+	_INIT_DCS_CMD(0x30, 0x10), _INIT_DCS_CMD(0x31, 0x07),
+	_INIT_DCS_CMD(0x32, 0x07), _INIT_DCS_CMD(0x33, 0x05),
+	_INIT_DCS_CMD(0x34, 0x05), _INIT_DCS_CMD(0x35, 0x0B),
+	_INIT_DCS_CMD(0x36, 0x0B), _INIT_DCS_CMD(0x37, 0x09),
+	_INIT_DCS_CMD(0x38, 0x09), _INIT_DCS_CMD(0x39, 0x1F),
+	_INIT_DCS_CMD(0x3A, 0x1F), _INIT_DCS_CMD(0x3B, 0x17),
+	_INIT_DCS_CMD(0x3C, 0x17), _INIT_DCS_CMD(0x3D, 0x17),
+	_INIT_DCS_CMD(0x3E, 0x17), _INIT_DCS_CMD(0x3F, 0x03),
+	_INIT_DCS_CMD(0x40, 0x01), _INIT_DCS_CMD(0x41, 0x1F),
+	_INIT_DCS_CMD(0x42, 0x1E), _INIT_DCS_CMD(0x43, 0x1E),
+	_INIT_DCS_CMD(0x44, 0x1F), _INIT_DCS_CMD(0x45, 0x1F),
+	_INIT_DCS_CMD(0x46, 0x10), _INIT_DCS_CMD(0x47, 0x06),
+	_INIT_DCS_CMD(0x48, 0x06), _INIT_DCS_CMD(0x49, 0x04),
+	_INIT_DCS_CMD(0x4A, 0x04), _INIT_DCS_CMD(0x4B, 0x0A),
+	_INIT_DCS_CMD(0x4C, 0x0A), _INIT_DCS_CMD(0x4D, 0x08),
+	_INIT_DCS_CMD(0x4E, 0x08), _INIT_DCS_CMD(0x4F, 0x1F),
+	_INIT_DCS_CMD(0x50, 0x1F), _INIT_DCS_CMD(0x51, 0x17),
+	_INIT_DCS_CMD(0x52, 0x17), _INIT_DCS_CMD(0x53, 0x17),
+	_INIT_DCS_CMD(0x54, 0x17), _INIT_DCS_CMD(0x55, 0x02),
+	_INIT_DCS_CMD(0x56, 0x00), _INIT_DCS_CMD(0x57, 0x1F),
+	_INIT_DCS_CMD(0xE0, 0x02), _INIT_DCS_CMD(0x58, 0x40),
+	_INIT_DCS_CMD(0x59, 0x00), _INIT_DCS_CMD(0x5A, 0x00),
+	_INIT_DCS_CMD(0x5B, 0x30), _INIT_DCS_CMD(0x5C, 0x01),
+	_INIT_DCS_CMD(0x5D, 0x30), _INIT_DCS_CMD(0x5E, 0x01),
+	_INIT_DCS_CMD(0x5F, 0x02), _INIT_DCS_CMD(0x60, 0x30),
+	_INIT_DCS_CMD(0x61, 0x03), _INIT_DCS_CMD(0x62, 0x04),
+	_INIT_DCS_CMD(0x63, 0x04), _INIT_DCS_CMD(0x64, 0xA6),
+	_INIT_DCS_CMD(0x65, 0x43), _INIT_DCS_CMD(0x66, 0x30),
+	_INIT_DCS_CMD(0x67, 0x73), _INIT_DCS_CMD(0x68, 0x05),
+	_INIT_DCS_CMD(0x69, 0x04), _INIT_DCS_CMD(0x6A, 0x7F),
+	_INIT_DCS_CMD(0x6B, 0x08), _INIT_DCS_CMD(0x6C, 0x00),
+	_INIT_DCS_CMD(0x6D, 0x04), _INIT_DCS_CMD(0x6E, 0x04),
+	_INIT_DCS_CMD(0x6F, 0x88), _INIT_DCS_CMD(0x75, 0xD9),
+	_INIT_DCS_CMD(0x76, 0x00), _INIT_DCS_CMD(0x77, 0x33),
+	_INIT_DCS_CMD(0x78, 0x43), _INIT_DCS_CMD(0xE0, 0x00),
+	_INIT_DCS_CMD(0x11),	   _INIT_DELAY_CMD(120),
+	_INIT_DCS_CMD(0x29),	   _INIT_DELAY_CMD(5),
+	_INIT_DCS_CMD(0x35, 0x00), {},
+};
+
+static inline struct ws_panel *panel_to_ws(struct drm_panel *panel)
+{
+	return container_of(panel, struct ws_panel, panel);
+}
+
+static int ws_panel_init_dcs_cmd(struct ws_panel *ts)
+{
+	struct mipi_dsi_device *dsi = ts->dsi;
+	struct drm_panel *panel = &ts->panel;
+	int i, err = 0;
+
+	if (ts->desc->init) {
+		const struct panel_init_cmd *init_cmds = ts->desc->init;
+
+		for (i = 0; init_cmds[i].len != 0; i++) {
+			const struct panel_init_cmd *cmd = &init_cmds[i];
+
+			switch (cmd->type) {
+			case DELAY_CMD:
+				msleep(cmd->data[0]);
+				err = 0;
+				break;
+
+			case INIT_DCS_CMD:
+				err = mipi_dsi_dcs_write(
+					dsi, cmd->data[0],
+					cmd->len <= 1 ? NULL : &cmd->data[1],
+					cmd->len - 1);
+				break;
+
+			default:
+				err = -EINVAL;
+			}
+
+			if (err < 0) {
+				dev_err(panel->dev,
+					"failed to write command %u\n", i);
+				return err;
+			}
+		}
+	}
+	return 0;
+}
+
+static int ws_panel_prepare(struct drm_panel *panel)
+{
+	struct ws_panel *ctx = panel_to_ws(panel);
+	int ret;
+
+	if (ctx->iovcc) {
+		gpiod_set_value_cansleep(ctx->iovcc, 1);
+		msleep(20);
+	}
+
+	if (ctx->avdd) {
+		gpiod_set_value_cansleep(ctx->avdd, 1);
+		msleep(20);
+	}
+
+	/* And reset it */
+	if (ctx->reset) {
+		gpiod_set_value_cansleep(ctx->reset, 0);
+		msleep(60);
+		gpiod_set_value_cansleep(ctx->reset, 1);
+		msleep(60);
+	}
+
+	ret = ws_panel_init_dcs_cmd(ctx);
+	if (ret < 0)
+		dev_err(panel->dev, "failed to init panel: %d\n", ret);
+
+	return 0;
+}
+
+static int ws_panel_unprepare(struct drm_panel *panel)
+{
+	struct ws_panel *ctx = panel_to_ws(panel);
+
+	mipi_dsi_dcs_set_display_off(ctx->dsi);
+	mipi_dsi_dcs_enter_sleep_mode(ctx->dsi);
+
+	if (ctx->reset) {
+		gpiod_set_value_cansleep(ctx->reset, 0);
+		msleep(20);
+	}
+
+	if (ctx->avdd) {
+		gpiod_set_value_cansleep(ctx->avdd, 0);
+		msleep(20);
+	}
+
+	if (ctx->iovcc) {
+		gpiod_set_value_cansleep(ctx->iovcc, 0);
+		msleep(20);
+	}
+
+	return 0;
+}
+
+static const struct drm_display_mode ws_panel_12_3_a_4lane_mode = {
+	.clock = 95000,
+	.hdisplay = 720,
+	.hsync_start = 720 + 10,
+	.hsync_end = 720 + 10 + 10,
+	.htotal = 720 + 10 + 10 + 12,
+	.vdisplay = 1920,
+	.vsync_start = 1920 + 64,
+	.vsync_end = 1920 + 64 + 18,
+	.vtotal = 1920 + 64 + 18 + 4,
+	.width_mm = 109,
+	.height_mm = 292,
+};
+
+static const struct drm_display_mode ws_panel_10_1_a_mode = {
+	.clock = 70000,
+	.hdisplay = 800,
+	.hsync_start = 800 + 40,
+	.hsync_end = 800 + 40 + 20,
+	.htotal = 800 + 40 + 20 + 20,
+	.vdisplay = 1280,
+	.vsync_start = 1280 + 20,
+	.vsync_end = 1280 + 20 + 20,
+	.vtotal = 1280 + 20 + 20 + 4,
+	.width_mm = 135,
+	.height_mm = 216,
+};
+
+static const struct drm_display_mode ws_panel_10_1_b_4lane_mode = {
+	.clock = 66000,
+	.hdisplay = 720,
+	.hsync_start = 720 + 60,
+	.hsync_end = 720 + 60 + 60,
+	.htotal = 720 + 60 + 60 + 4,
+	.vdisplay = 1280,
+	.vsync_start = 1280 + 16,
+	.vsync_end = 1280 + 16 + 12,
+	.vtotal = 1280 + 16 + 12 + 4,
+	.width_mm = 125,
+	.height_mm = 222,
+};
+
+static const struct drm_display_mode ws_panel_10_1_b_mode = {
+	.clock = 69000,
+	.hdisplay = 720,
+	.hsync_start = 720 + 50,
+	.hsync_end = 720 + 50 + 50,
+	.htotal = 720 + 50 + 50 + 50,
+	.vdisplay = 1280,
+	.vsync_start = 1280 + 26,
+	.vsync_end = 1280 + 26 + 12,
+	.vtotal = 1280 + 26 + 12 + 4,
+	.width_mm = 125,
+	.height_mm = 222,
+};
+
+static const struct drm_display_mode ws_panel_9_b_4lane_mode = {
+	.clock = 66000,
+	.hdisplay = 720,
+	.hsync_start = 720 + 60,
+	.hsync_end = 720 + 60 + 60,
+	.htotal = 720 + 60 + 60 + 4,
+	.vdisplay = 1280,
+	.vsync_start = 1280 + 16,
+	.vsync_end = 1280 + 16 + 12,
+	.vtotal = 1280 + 16 + 12 + 4,
+	.width_mm = 114,
+	.height_mm = 196,
+};
+
+static const struct drm_display_mode ws_panel_9_b_mode = {
+	.clock = 69000,
+	.hdisplay = 720,
+	.hsync_start = 720 + 50,
+	.hsync_end = 720 + 50 + 50,
+	.htotal = 720 + 50 + 50 + 50,
+	.vdisplay = 1280,
+	.vsync_start = 1280 + 26,
+	.vsync_end = 1280 + 26 + 12,
+	.vtotal = 1280 + 26 + 12 + 4,
+	.width_mm = 114,
+	.height_mm = 196,
+};
+
+static const struct drm_display_mode ws_panel_8_8_a_mode = {
+	.clock = 75000,
+	.hdisplay = 480,
+	.hsync_start = 480 + 50,
+	.hsync_end = 480 + 50 + 50,
+	.htotal = 480 + 50 + 50 + 50,
+	.vdisplay = 1920,
+	.vsync_start = 1920 + 20,
+	.vsync_end = 1920 + 20 + 20,
+	.vtotal = 1920 + 20 + 20 + 20,
+	.width_mm = 68,
+	.height_mm = 219,
+};
+
+static const struct drm_display_mode ws_panel_8_a_mode = {
+	.clock = 70000,
+	.hdisplay = 800,
+	.hsync_start = 800 + 40,
+	.hsync_end = 800 + 40 + 20,
+	.htotal = 800 + 40 + 20 + 20,
+	.vdisplay = 1280,
+	.vsync_start = 1280 + 30,
+	.vsync_end = 1280 + 30 + 12,
+	.vtotal = 1280 + 30 + 12 + 4,
+	.width_mm = 107,
+	.height_mm = 172,
+};
+
+static const struct drm_display_mode ws_panel_7_a_mode = {
+	.clock = 83333,
+	.hdisplay = 720,
+	.hsync_start = 720 + 120,
+	.hsync_end = 720 + 120 + 100,
+	.htotal = 720 + 120 + 100 + 100,
+	.vdisplay = 1280,
+	.vsync_start = 1280 + 10,
+	.vsync_end = 1280 + 10 + 10,
+	.vtotal = 1280 + 10 + 10 + 10,
+	.width_mm = 85,
+	.height_mm = 154,
+};
+
+static const struct drm_display_mode ws_panel_7_c_mode = {
+	.clock = 51700,
+	.hdisplay = 1024,
+	.hsync_start = 1024 + 160,
+	.hsync_end = 1024 + 160 + 10,
+	.htotal = 1024 + 160 + 10 + 160,
+	.vdisplay = 600,
+	.vsync_start = 600 + 12,
+	.vsync_end = 600 + 12 + 1,
+	.vtotal = 600 + 12 + 1 + 23,
+	.width_mm = 154,
+	.height_mm = 85,
+};
+
+static const struct drm_display_mode ws_panel_5_5_a_mode = {
+	.clock = 65000,
+	.hdisplay = 720,
+	.hsync_start = 720 + 50,
+	.hsync_end = 720 + 50 + 50,
+	.htotal = 720 + 50 + 50 + 10,
+	.vdisplay = 1280,
+	.vsync_start = 1280 + 15,
+	.vsync_end = 1280 + 15 + 12,
+	.vtotal = 1280 + 15 + 12 + 4,
+	.width_mm = 62,
+	.height_mm = 110,
+};
+
+static const struct drm_display_mode ws_panel_5_a_mode = {
+	.clock = 70000,
+	.hdisplay = 720,
+	.hsync_start = 720 + 40,
+	.hsync_end = 720 + 40 + 20,
+	.htotal = 720 + 40 + 20 + 20,
+	.vdisplay = 1280,
+	.vsync_start = 1280 + 30,
+	.vsync_end = 1280 + 30 + 10,
+	.vtotal = 1280 + 30 + 10 + 4,
+	.width_mm = 62,
+	.height_mm = 110,
+};
+
+static const struct drm_display_mode ws_panel_4_c_mode = {
+	.clock = 36500,
+	.hdisplay = 720,
+	.hsync_start = 720 + 40,
+	.hsync_end = 720 + 40 + 20,
+	.htotal = 720 + 40 + 20 + 20,
+	.vdisplay = 720,
+	.vsync_start = 720 + 24,
+	.vsync_end = 720 + 24 + 4,
+	.vtotal = 720 + 24 + 4 + 12,
+	.width_mm = 88,
+	.height_mm = 88,
+};
+
+static const struct drm_display_mode ws_panel_3_4_c_mode = {
+	.clock = 44300,
+	.hdisplay = 800,
+	.hsync_start = 800 + 40,
+	.hsync_end = 800 + 40 + 20,
+	.htotal = 800 + 40 + 20 + 20,
+	.vdisplay = 800,
+	.vsync_start = 800 + 24,
+	.vsync_end = 800 + 24 + 4,
+	.vtotal = 800 + 24 + 4 + 12,
+	.width_mm = 88,
+	.height_mm = 88,
+};
+
+static int ws_panel_get_modes(struct drm_panel *panel,
+			      struct drm_connector *connector)
+{
+	struct ws_panel *ctx = panel_to_ws(panel);
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_duplicate(connector->dev, ctx->desc->mode);
+	if (!mode) {
+		dev_err(&ctx->dsi->dev, "failed to add mode %ux%ux@%u\n",
+			ctx->desc->mode->hdisplay, ctx->desc->mode->vdisplay,
+			drm_mode_vrefresh(ctx->desc->mode));
+		return -ENOMEM;
+	}
+
+	drm_mode_set_name(mode);
+
+	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+	drm_mode_probed_add(connector, mode);
+
+	connector->display_info.width_mm = mode->width_mm;
+	connector->display_info.height_mm = mode->height_mm;
+
+	drm_connector_set_panel_orientation(connector, ctx->orientation);
+
+	return 1;
+}
+
+static enum drm_panel_orientation
+ws_panel_get_orientation(struct drm_panel *panel)
+{
+	struct ws_panel *ctx = panel_to_ws(panel);
+
+	return ctx->orientation;
+}
+
+static const struct drm_panel_funcs ws_panel_funcs = {
+	.prepare = ws_panel_prepare,
+	.unprepare = ws_panel_unprepare,
+	.get_modes = ws_panel_get_modes,
+	.get_orientation = ws_panel_get_orientation,
+};
+
+static int ws_panel_dsi_probe(struct mipi_dsi_device *dsi)
+{
+	struct ws_panel *ctx;
+	int ret;
+
+	dev_info(&dsi->dev, "dsi panel: %s\n",
+		 (char *)of_get_property(dsi->dev.of_node, "compatible", NULL));
+
+	ctx = devm_kzalloc(&dsi->dev, sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+	mipi_dsi_set_drvdata(dsi, ctx);
+	ctx->dsi = dsi;
+	ctx->desc = of_device_get_match_data(&dsi->dev);
+
+	ctx->panel.prepare_prev_first = true;
+	drm_panel_init(&ctx->panel, &dsi->dev, &ws_panel_funcs,
+		       DRM_MODE_CONNECTOR_DSI);
+
+	ctx->reset = devm_gpiod_get_optional(&dsi->dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(ctx->reset))
+		return dev_err_probe(&dsi->dev, PTR_ERR(ctx->reset),
+				     "Couldn't get our reset GPIO\n");
+
+	ctx->iovcc = devm_gpiod_get_optional(&dsi->dev, "iovcc", GPIOD_OUT_LOW);
+	if (IS_ERR(ctx->iovcc))
+		return dev_err_probe(&dsi->dev, PTR_ERR(ctx->iovcc),
+				     "Couldn't get our iovcc GPIO\n");
+
+	ctx->avdd = devm_gpiod_get_optional(&dsi->dev, "avdd", GPIOD_OUT_LOW);
+	if (IS_ERR(ctx->avdd))
+		return dev_err_probe(&dsi->dev, PTR_ERR(ctx->avdd),
+				     "Couldn't get our avdd GPIO\n");
+
+	ret = of_drm_get_panel_orientation(dsi->dev.of_node, &ctx->orientation);
+	if (ret) {
+		dev_err(&dsi->dev, "%pOF: failed to get orientation: %d\n",
+			dsi->dev.of_node, ret);
+		return ret;
+	}
+
+	ret = drm_panel_of_backlight(&ctx->panel);
+	if (ret)
+		return ret;
+
+	drm_panel_add(&ctx->panel);
+
+	dsi->mode_flags = ctx->desc->mode_flags;
+	dsi->format = ctx->desc->format;
+	dsi->lanes = ctx->desc->lanes;
+	dev_info(&dsi->dev, "lanes: %d\n", dsi->lanes);
+
+	ret = mipi_dsi_attach(dsi);
+	if (ret)
+		drm_panel_remove(&ctx->panel);
+
+	return ret;
+}
+
+static void ws_panel_dsi_remove(struct mipi_dsi_device *dsi)
+{
+	struct ws_panel *ctx = mipi_dsi_get_drvdata(dsi);
+
+	mipi_dsi_detach(dsi);
+	drm_panel_remove(&ctx->panel);
+	if (ctx->reset) {
+		gpiod_set_value_cansleep(ctx->reset, 0);
+		msleep(20);
+	}
+
+	if (ctx->avdd) {
+		gpiod_set_value_cansleep(ctx->avdd, 0);
+		msleep(20);
+	}
+
+	if (ctx->iovcc) {
+		gpiod_set_value_cansleep(ctx->iovcc, 0);
+		msleep(20);
+	}
+}
+
+static void ws_panel_dsi_shutdown(struct mipi_dsi_device *dsi)
+{
+	struct ws_panel *ctx = mipi_dsi_get_drvdata(dsi);
+
+	if (ctx->reset) {
+		dev_info(&dsi->dev, "shutdown\n");
+		gpiod_set_value_cansleep(ctx->reset, 0);
+		msleep(20);
+	}
+
+	if (ctx->avdd) {
+		gpiod_set_value_cansleep(ctx->avdd, 0);
+		msleep(20);
+	}
+
+	if (ctx->iovcc) {
+		gpiod_set_value_cansleep(ctx->iovcc, 0);
+		msleep(20);
+	}
+}
+
+static const struct ws_panel_desc ws_panel_12_3_inch_a_4lane_desc = {
+	.init = ws_panel_12_3_a_4lane_init,
+	.mode = &ws_panel_12_3_a_4lane_mode,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO |
+		      MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+	.lanes = 4,
+	.format = MIPI_DSI_FMT_RGB888,
+};
+
+static const struct ws_panel_desc ws_panel_10_1_inch_a_4lane_desc = {
+	.init = ws_panel_10_1_a_4lane_init,
+	.mode = &ws_panel_10_1_a_mode,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO |
+		      MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+	.lanes = 4,
+	.format = MIPI_DSI_FMT_RGB888,
+};
+
+static const struct ws_panel_desc ws_panel_10_1_inch_a_desc = {
+	.init = ws_panel_10_1_a_init,
+	.mode = &ws_panel_10_1_a_mode,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO |
+		      MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+	.lanes = 2,
+	.format = MIPI_DSI_FMT_RGB888,
+};
+
+static const struct ws_panel_desc ws_panel_10_1_inch_b_4lane_desc = {
+	.init = ws_panel_10_1_b_4lane_init,
+	.mode = &ws_panel_10_1_b_4lane_mode,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO |
+		      MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+	.lanes = 4,
+	.format = MIPI_DSI_FMT_RGB888,
+};
+
+static const struct ws_panel_desc ws_panel_10_1_inch_b_desc = {
+	.init = ws_panel_10_1_b_init,
+	.mode = &ws_panel_10_1_b_mode,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO |
+		      MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+	.lanes = 2,
+	.format = MIPI_DSI_FMT_RGB888,
+};
+
+static const struct ws_panel_desc ws_panel_9_inch_b_4lane_desc = {
+	.init = ws_panel_10_1_b_4lane_init,
+	.mode = &ws_panel_9_b_4lane_mode,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO |
+		      MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+	.lanes = 4,
+	.format = MIPI_DSI_FMT_RGB888,
+};
+
+static const struct ws_panel_desc ws_panel_9_inch_b_desc = {
+	.init = ws_panel_10_1_b_init,
+	.mode = &ws_panel_9_b_mode,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO |
+		      MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+	.lanes = 2,
+	.format = MIPI_DSI_FMT_RGB888,
+};
+
+static const struct ws_panel_desc ws_panel_8_8_inch_a_desc = {
+	.init = ws_panel_8_8_a_init,
+	.mode = &ws_panel_8_8_a_mode,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO |
+		      MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+	.lanes = 2,
+	.format = MIPI_DSI_FMT_RGB888,
+};
+
+static const struct ws_panel_desc ws_panel_8_inch_a_4lane_desc = {
+	.init = ws_panel_8_a_4lane_init,
+	.mode = &ws_panel_8_a_mode,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO |
+		      MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+	.lanes = 4,
+	.format = MIPI_DSI_FMT_RGB888,
+};
+
+static const struct ws_panel_desc ws_panel_8_inch_a_desc = {
+	.init = ws_panel_8_a_init,
+	.mode = &ws_panel_8_a_mode,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO |
+		      MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+	.lanes = 2,
+	.format = MIPI_DSI_FMT_RGB888,
+};
+
+static const struct ws_panel_desc ws_panel_7_inch_a_desc = {
+	.init = ws_panel_7_a_init,
+	.mode = &ws_panel_7_a_mode,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO |
+		      MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+	.lanes = 2,
+	.format = MIPI_DSI_FMT_RGB888,
+};
+
+static const struct ws_panel_desc ws_panel_7_inch_c_desc = {
+	.init = ws_panel_7_c_init,
+	.mode = &ws_panel_7_c_mode,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO |
+		      MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+	.lanes = 2,
+	.format = MIPI_DSI_FMT_RGB888,
+};
+
+static const struct ws_panel_desc ws_panel_5_5_inch_a_desc = {
+	.init = ws_panel_5_5_a_init,
+	.mode = &ws_panel_5_5_a_mode,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO |
+		      MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+	.lanes = 2,
+	.format = MIPI_DSI_FMT_RGB888,
+};
+
+static const struct ws_panel_desc ws_panel_5_inch_a_desc = {
+	.init = ws_panel_5_a_init,
+	.mode = &ws_panel_5_a_mode,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO |
+		      MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+	.lanes = 2,
+	.format = MIPI_DSI_FMT_RGB888,
+};
+
+static const struct ws_panel_desc ws_panel_4_inch_c_desc = {
+	.init = ws_panel_4_c_init,
+	.mode = &ws_panel_4_c_mode,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO |
+		      MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+	.lanes = 2,
+	.format = MIPI_DSI_FMT_RGB888,
+};
+
+static const struct ws_panel_desc ws_panel_3_4_inch_c_desc = {
+	.init = ws_panel_3_4_c_init,
+	.mode = &ws_panel_3_4_c_mode,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO |
+		      MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+	.lanes = 2,
+	.format = MIPI_DSI_FMT_RGB888,
+};
+
+static const struct of_device_id ws_panel_of_match[] = {
+	{ .compatible = "waveshare,12.3-dsi-touch-a,4lane",
+	  &ws_panel_12_3_inch_a_4lane_desc },
+	{ .compatible = "waveshare,10.1-dsi-touch-a-4lane",
+	  &ws_panel_10_1_inch_a_4lane_desc },
+	{ .compatible = "waveshare,10.1-dsi-touch-a",
+	  &ws_panel_10_1_inch_a_desc },
+	{ .compatible = "waveshare,10.1-dsi-touch-b,4lane",
+	  &ws_panel_10_1_inch_b_4lane_desc },
+	{ .compatible = "waveshare,10.1-dsi-touch-b",
+	  &ws_panel_10_1_inch_b_desc },
+	{ .compatible = "waveshare,9.0-dsi-touch-b,4lane",
+	  &ws_panel_9_inch_b_4lane_desc },
+	{ .compatible = "waveshare,9.0-dsi-touch-b", &ws_panel_9_inch_b_desc },
+	{ .compatible = "waveshare,8.8-dsi-touch-a",
+	  &ws_panel_8_8_inch_a_desc },
+	{ .compatible = "waveshare,8.0-dsi-touch-a-4lane",
+	  &ws_panel_8_inch_a_4lane_desc },
+	{ .compatible = "waveshare,8.0-dsi-touch-a", &ws_panel_8_inch_a_desc },
+	{ .compatible = "waveshare,7.0-dsi-touch-a", &ws_panel_7_inch_a_desc },
+	{ .compatible = "waveshare,7.0-dsi-touch-b", &ws_panel_7_inch_a_desc },
+	{ .compatible = "waveshare,7.0-dsi-touch-c", &ws_panel_7_inch_c_desc },
+	{ .compatible = "waveshare,5.5-dsi-touch-a",
+	  &ws_panel_5_5_inch_a_desc },
+	{ .compatible = "waveshare,5.0-dsi-touch-a", &ws_panel_5_inch_a_desc },
+	{ .compatible = "waveshare,4.0-dsi-touch-c", &ws_panel_4_inch_c_desc },
+	{ .compatible = "waveshare,3.4-dsi-touch-c",
+	  &ws_panel_3_4_inch_c_desc },
+	{}
+};
+MODULE_DEVICE_TABLE(of, ws_panel_of_match);
+
+static struct mipi_dsi_driver ws_panel_dsi_driver = {
+	.probe		= ws_panel_dsi_probe,
+	.remove		= ws_panel_dsi_remove,
+	.shutdown	= ws_panel_dsi_shutdown,
+	.driver = {
+		.name		= "waveshare-dsi",
+		.of_match_table	= ws_panel_of_match,
+	},
+};
+module_mipi_dsi_driver(ws_panel_dsi_driver);
+
+MODULE_AUTHOR("Waveshare Team <support@waveshare.com>");
+MODULE_DESCRIPTION("Waveshare DSI panel driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/panel/panel-waveshare-dsi.c b/drivers/gpu/drm/panel/panel-waveshare-dsi.c
new file mode 100644
index 000000000000..711b4c36c62f
--- /dev/null
+++ b/drivers/gpu/drm/panel/panel-waveshare-dsi.c
@@ -0,0 +1,633 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright © 2023 Raspberry Pi Ltd
+ *
+ * Based on panel-raspberrypi-touchscreen by Broadcom
+ */
+
+#include <linux/backlight.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/fb.h>
+#include <linux/i2c.h>
+#include <linux/media-bus-format.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/pm.h>
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_device.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_panel.h>
+
+#define WS_DSI_DRIVER_NAME "ws-ts-dsi"
+
+struct ws_panel {
+	struct drm_panel base;
+	struct mipi_dsi_device *dsi;
+	struct i2c_client *i2c;
+	const struct drm_display_mode *mode;
+	enum drm_panel_orientation orientation;
+};
+
+struct ws_panel_data {
+	const struct drm_display_mode *mode;
+	int lanes;
+	unsigned long mode_flags;
+};
+
+/* 2.8inch 480x640
+ * https://www.waveshare.com/product/raspberry-pi/displays/2.8inch-dsi-lcd.htm
+ */
+static const struct drm_display_mode ws_panel_2_8_mode = {
+	.clock = 50000,
+	.hdisplay = 480,
+	.hsync_start = 480 + 150,
+	.hsync_end = 480 + 150 + 50,
+	.htotal = 480 + 150 + 50 + 150,
+	.vdisplay = 640,
+	.vsync_start = 640 + 150,
+	.vsync_end = 640 + 150 + 50,
+	.vtotal = 640 + 150 + 50 + 150,
+};
+
+static const struct ws_panel_data ws_panel_2_8_data = {
+	.mode = &ws_panel_2_8_mode,
+	.lanes = 2,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+};
+
+/* 3.4inch 800x800 Round
+ * https://www.waveshare.com/product/displays/lcd-oled/3.4inch-dsi-lcd-c.htm
+ */
+static const struct drm_display_mode ws_panel_3_4_mode = {
+	.clock = 50000,
+	.hdisplay = 800,
+	.hsync_start = 800 + 32,
+	.hsync_end = 800 + 32 + 6,
+	.htotal = 800 + 32 + 6 + 120,
+	.vdisplay = 800,
+	.vsync_start = 800 + 8,
+	.vsync_end = 800 + 8 + 4,
+	.vtotal = 800 + 8 + 4 + 16,
+};
+
+static const struct ws_panel_data ws_panel_3_4_data = {
+	.mode = &ws_panel_3_4_mode,
+	.lanes = 2,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+};
+
+/* 4.0inch 480x800
+ * https://www.waveshare.com/product/raspberry-pi/displays/4inch-dsi-lcd.htm
+ */
+static const struct drm_display_mode ws_panel_4_0_mode = {
+	.clock = 50000,
+	.hdisplay = 480,
+	.hsync_start = 480 + 150,
+	.hsync_end = 480 + 150 + 100,
+	.htotal = 480 + 150 + 100 + 150,
+	.vdisplay = 800,
+	.vsync_start = 800 + 20,
+	.vsync_end = 800 + 20 + 100,
+	.vtotal = 800 + 20 + 100 + 20,
+};
+
+static const struct ws_panel_data ws_panel_4_0_data = {
+	.mode = &ws_panel_4_0_mode,
+	.lanes = 2,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+};
+
+/* 7.0inch C 1024x600
+ * https://www.waveshare.com/product/raspberry-pi/displays/lcd-oled/7inch-dsi-lcd-c-with-case-a.htm
+ */
+static const struct drm_display_mode ws_panel_7_0_c_mode = {
+	.clock = 50000,
+	.hdisplay = 1024,
+	.hsync_start = 1024 + 100,
+	.hsync_end = 1024 + 100 + 100,
+	.htotal = 1024 + 100 + 100 + 100,
+	.vdisplay = 600,
+	.vsync_start = 600 + 10,
+	.vsync_end = 600 + 10 + 10,
+	.vtotal = 600 + 10 + 10 + 10,
+};
+
+static const struct ws_panel_data ws_panel_7_0_c_data = {
+	.mode = &ws_panel_7_0_c_mode,
+	.lanes = 2,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+};
+
+/* 7.9inch 400x1280
+ * https://www.waveshare.com/product/raspberry-pi/displays/7.9inch-dsi-lcd.htm
+ */
+static const struct drm_display_mode ws_panel_7_9_mode = {
+	.clock = 50000,
+	.hdisplay = 400,
+	.hsync_start = 400 + 40,
+	.hsync_end = 400 + 40 + 30,
+	.htotal = 400 + 40 + 30 + 40,
+	.vdisplay = 1280,
+	.vsync_start = 1280 + 20,
+	.vsync_end = 1280 + 20 + 10,
+	.vtotal = 1280 + 20 + 10 + 20,
+};
+
+static const struct ws_panel_data ws_panel_7_9_data = {
+	.mode = &ws_panel_7_9_mode,
+	.lanes = 2,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+};
+
+/* 8.0inch or 10.1inch 1280x800
+ * https://www.waveshare.com/product/raspberry-pi/displays/8inch-dsi-lcd-c.htm
+ * https://www.waveshare.com/product/raspberry-pi/displays/10.1inch-dsi-lcd-c.htm
+ */
+static const struct drm_display_mode ws_panel_10_1_mode = {
+	.clock = 83333,
+	.hdisplay = 1280,
+	.hsync_start = 1280 + 156,
+	.hsync_end = 1280 + 156 + 20,
+	.htotal = 1280 + 156 + 20 + 40,
+	.vdisplay = 800,
+	.vsync_start = 800 + 40,
+	.vsync_end = 800 + 40 + 48,
+	.vtotal = 800 + 40 + 48 + 40,
+};
+
+static const struct ws_panel_data ws_panel_10_1_data = {
+	.mode = &ws_panel_10_1_mode,
+	.lanes = 2,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+};
+
+/* 11.9inch 320x1480
+ * https://www.waveshare.com/product/raspberry-pi/displays/11.9inch-dsi-lcd.htm
+ */
+static const struct drm_display_mode ws_panel_11_9_mode = {
+	.clock = 50000,
+	.hdisplay = 320,
+	.hsync_start = 320 + 60,
+	.hsync_end = 320 + 60 + 60,
+	.htotal = 320 + 60 + 60 + 60,
+	.vdisplay = 1480,
+	.vsync_start = 1480 + 60,
+	.vsync_end = 1480 + 60 + 60,
+	.vtotal = 1480 + 60 + 60 + 60,
+};
+
+static const struct ws_panel_data ws_panel_11_9_data = {
+	.mode = &ws_panel_11_9_mode,
+	.lanes = 2,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+};
+
+static const struct drm_display_mode ws_panel_4_mode = {
+	.clock = 50000,
+	.hdisplay = 720,
+	.hsync_start = 720 + 32,
+	.hsync_end = 720 + 32 + 200,
+	.htotal = 720 + 32 + 200 + 120,
+	.vdisplay = 720,
+	.vsync_start = 720 + 8,
+	.vsync_end = 720 + 8 + 4,
+	.vtotal = 720 + 8 + 4 + 16,
+};
+
+static const struct ws_panel_data ws_panel_4_data = {
+	.mode = &ws_panel_4_mode,
+	.lanes = 2,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+};
+
+/* 5.0inch 720x1280
+ * https://www.waveshare.com/5inch-dsi-lcd-d.htm
+ */
+static const struct drm_display_mode ws_panel_5_0_mode = {
+	.clock = 83333,
+	.hdisplay = 720,
+	.hsync_start = 720 + 100,
+	.hsync_end = 720 + 100 + 80,
+	.htotal = 720 + 100 + 80 + 100,
+	.vdisplay = 1280,
+	.vsync_start = 1280 + 20,
+	.vsync_end = 1280 + 20 + 20,
+	.vtotal = 1280 + 20 + 20 + 20,
+};
+
+static const struct ws_panel_data ws_panel_5_0_data = {
+	.mode = &ws_panel_5_0_mode,
+	.lanes = 2,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+};
+
+/* 6.25inch 720x1560
+ * https://www.waveshare.com/6.25inch-dsi-lcd.htm
+ */
+static const struct drm_display_mode ws_panel_6_25_mode = {
+	.clock = 83333,
+	.hdisplay = 720,
+	.hsync_start = 720 + 50,
+	.hsync_end = 720 + 50 + 50,
+	.htotal = 720 + 50 + 50 + 50,
+	.vdisplay = 1560,
+	.vsync_start = 1560 + 20,
+	.vsync_end = 1560 + 20 + 20,
+	.vtotal = 1560 + 20 + 20 + 20,
+};
+
+static const struct ws_panel_data ws_panel_6_25_data = {
+	.mode = &ws_panel_6_25_mode,
+	.lanes = 2,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+};
+
+/* 8.8inch 480x1920
+ * https://www.waveshare.com/8.8inch-dsi-lcd.htm
+ */
+static const struct drm_display_mode ws_panel_8_8_mode = {
+	.clock = 83333,
+	.hdisplay = 480,
+	.hsync_start = 480 + 50,
+	.hsync_end = 480 + 50 + 50,
+	.htotal = 480 + 50 + 50 + 50,
+	.vdisplay = 1920,
+	.vsync_start = 1920 + 20,
+	.vsync_end = 1920 + 20 + 20,
+	.vtotal = 1920 + 20 + 20 + 20,
+};
+
+static const struct ws_panel_data ws_panel_8_8_data = {
+	.mode = &ws_panel_8_8_mode,
+	.lanes = 2,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+};
+
+static const struct drm_display_mode ws_panel_13_3_4lane_mode = {
+	.clock = 148500,
+	.hdisplay = 1920,
+	.hsync_start = 1920 + 88,
+	.hsync_end = 1920 + 88 + 44,
+	.htotal = 1920 + 88 + 44 + 148,
+	.vdisplay = 1080,
+	.vsync_start = 1080 + 4,
+	.vsync_end = 1080 + 4 + 5,
+	.vtotal = 1080 + 4 + 5 + 36,
+};
+
+static const struct ws_panel_data ws_panel_13_3_4lane_data = {
+	.mode = &ws_panel_13_3_4lane_mode,
+	.lanes = 4,
+	.mode_flags = MIPI_DSI_MODE_VIDEO  | MIPI_DSI_MODE_LPM,
+};
+
+static const struct drm_display_mode ws_panel_13_3_2lane_mode = {
+	.clock = 83333,
+	.hdisplay = 1920,
+	.hsync_start = 1920 + 88,
+	.hsync_end = 1920 + 88 + 44,
+	.htotal = 1920 + 88 + 44 + 148,
+	.vdisplay = 1080,
+	.vsync_start = 1080 + 4,
+	.vsync_end = 1080 + 4 + 5,
+	.vtotal = 1080 + 4 + 5 + 36,
+};
+
+static const struct ws_panel_data ws_panel_13_3_2lane_data = {
+	.mode = &ws_panel_13_3_2lane_mode,
+	.lanes = 2,
+	.mode_flags = MIPI_DSI_MODE_VIDEO  | MIPI_DSI_MODE_LPM,
+};
+
+/* 7.0inch 1280x720
+ * https://www.waveshare.com/7inch-dsi-lcd-h.htm
+ */
+static const struct drm_display_mode ws_panel_7_0_h_mode = {
+	.clock = 83333,
+	.hdisplay = 1280,
+	.hsync_start = 1280 + 64,
+	.hsync_end = 1280 + 64 + 64,
+	.htotal = 1280 + 64 + 64 + 64,
+	.vdisplay = 720,
+	.vsync_start = 720 + 64,
+	.vsync_end = 720 + 64 + 64,
+	.vtotal = 720 + 64 + 64 + 64,
+};
+
+static const struct ws_panel_data ws_panel_7_0_h_data = {
+	.mode = &ws_panel_7_0_h_mode,
+	.lanes = 2,
+	.mode_flags = MIPI_DSI_MODE_VIDEO_HSE | MIPI_DSI_MODE_VIDEO | MIPI_DSI_CLOCK_NON_CONTINUOUS,
+};
+
+static struct ws_panel *panel_to_ts(struct drm_panel *panel)
+{
+	return container_of(panel, struct ws_panel, base);
+}
+
+static void ws_panel_i2c_write(struct ws_panel *ts, u8 reg, u8 val)
+{
+	int ret;
+
+	ret = i2c_smbus_write_byte_data(ts->i2c, reg, val);
+	if (ret)
+		dev_err(&ts->i2c->dev, "I2C write failed: %d\n", ret);
+}
+
+static int ws_panel_disable(struct drm_panel *panel)
+{
+	struct ws_panel *ts = panel_to_ts(panel);
+
+	ws_panel_i2c_write(ts, 0xad, 0x00);
+
+	return 0;
+}
+
+static int ws_panel_unprepare(struct drm_panel *panel)
+{
+	return 0;
+}
+
+static int ws_panel_prepare(struct drm_panel *panel)
+{
+	return 0;
+}
+
+static int ws_panel_enable(struct drm_panel *panel)
+{
+	struct ws_panel *ts = panel_to_ts(panel);
+
+	if (ts->mode == &ws_panel_13_3_2lane_mode)
+		ws_panel_i2c_write(ts, 0xad, 0x02);
+	else
+		ws_panel_i2c_write(ts, 0xad, 0x01);
+
+	return 0;
+}
+
+static int ws_panel_get_modes(struct drm_panel *panel,
+			      struct drm_connector *connector)
+{
+	static const u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
+	struct ws_panel *ts = panel_to_ts(panel);
+	struct drm_display_mode *mode;
+
+	mode = drm_mode_duplicate(connector->dev, ts->mode);
+	if (!mode) {
+		dev_err(panel->dev, "failed to add mode %ux%u@%u\n",
+			ts->mode->hdisplay,
+			ts->mode->vdisplay,
+			drm_mode_vrefresh(ts->mode));
+	}
+
+	mode->type |= DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+
+	drm_mode_set_name(mode);
+
+	drm_mode_probed_add(connector, mode);
+
+	connector->display_info.bpc = 8;
+	connector->display_info.width_mm = 154;
+	connector->display_info.height_mm = 86;
+	drm_display_info_set_bus_formats(&connector->display_info,
+					 &bus_format, 1);
+
+	/*
+	 * TODO: Remove once all drm drivers call
+	 * drm_connector_set_orientation_from_panel()
+	 */
+	drm_connector_set_panel_orientation(connector, ts->orientation);
+
+	return 1;
+}
+
+static enum drm_panel_orientation ws_panel_get_orientation(struct drm_panel *panel)
+{
+	struct ws_panel *ts = panel_to_ts(panel);
+
+	return ts->orientation;
+}
+
+static const struct drm_panel_funcs ws_panel_funcs = {
+	.disable = ws_panel_disable,
+	.unprepare = ws_panel_unprepare,
+	.prepare = ws_panel_prepare,
+	.enable = ws_panel_enable,
+	.get_modes = ws_panel_get_modes,
+	.get_orientation = ws_panel_get_orientation,
+};
+
+static int ws_panel_bl_update_status(struct backlight_device *bl)
+{
+	struct ws_panel *ts = bl_get_data(bl);
+
+	ws_panel_i2c_write(ts, 0xab, 0xff - backlight_get_brightness(bl));
+	ws_panel_i2c_write(ts, 0xaa, 0x01);
+
+	return 0;
+}
+
+static const struct backlight_ops ws_panel_bl_ops = {
+	.update_status = ws_panel_bl_update_status,
+};
+
+static struct backlight_device *
+ws_panel_create_backlight(struct ws_panel *ts)
+{
+	struct device *dev = ts->base.dev;
+	const struct backlight_properties props = {
+		.type = BACKLIGHT_RAW,
+		.brightness = 255,
+		.max_brightness = 255,
+	};
+
+	return devm_backlight_device_register(dev, dev_name(dev), dev, ts,
+					      &ws_panel_bl_ops, &props);
+}
+
+static int ws_panel_probe(struct i2c_client *i2c)
+{
+	struct device *dev = &i2c->dev;
+	struct ws_panel *ts;
+	struct device_node *endpoint, *dsi_host_node;
+	struct mipi_dsi_host *host;
+	struct mipi_dsi_device_info info = {
+		.type = WS_DSI_DRIVER_NAME,
+		.channel = 0,
+		.node = NULL,
+	};
+	const struct ws_panel_data *_ws_panel_data;
+	int ret;
+
+	ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL);
+	if (!ts)
+		return -ENOMEM;
+
+	_ws_panel_data = of_device_get_match_data(dev);
+	if (!_ws_panel_data)
+		return -EINVAL;
+
+	ts->mode = _ws_panel_data->mode;
+	if (!ts->mode)
+		return -EINVAL;
+
+	i2c_set_clientdata(i2c, ts);
+
+	ts->i2c = i2c;
+
+	ws_panel_i2c_write(ts, 0xc0, 0x01);
+	ws_panel_i2c_write(ts, 0xc2, 0x01);
+	ws_panel_i2c_write(ts, 0xac, 0x01);
+
+	ret = of_drm_get_panel_orientation(dev->of_node, &ts->orientation);
+	if (ret) {
+		dev_err(dev, "%pOF: failed to get orientation %d\n", dev->of_node, ret);
+		return ret;
+	}
+
+	/* Look up the DSI host.  It needs to probe before we do. */
+	endpoint = of_graph_get_next_endpoint(dev->of_node, NULL);
+	if (!endpoint)
+		return -ENODEV;
+
+	dsi_host_node = of_graph_get_remote_port_parent(endpoint);
+	if (!dsi_host_node)
+		goto error;
+
+	host = of_find_mipi_dsi_host_by_node(dsi_host_node);
+	of_node_put(dsi_host_node);
+	if (!host) {
+		of_node_put(endpoint);
+		return -EPROBE_DEFER;
+	}
+
+	info.node = of_graph_get_remote_port(endpoint);
+	if (!info.node)
+		goto error;
+
+	of_node_put(endpoint);
+
+	ts->dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
+	if (IS_ERR(ts->dsi)) {
+		dev_err(dev, "DSI device registration failed: %ld\n",
+			PTR_ERR(ts->dsi));
+		return PTR_ERR(ts->dsi);
+	}
+
+	drm_panel_init(&ts->base, dev, &ws_panel_funcs,
+		       DRM_MODE_CONNECTOR_DSI);
+
+	ts->base.backlight = ws_panel_create_backlight(ts);
+	if (IS_ERR(ts->base.backlight)) {
+		ret = PTR_ERR(ts->base.backlight);
+		dev_err(dev, "Failed to create backlight: %d\n", ret);
+		return ret;
+	}
+
+	/* This appears last, as it's what will unblock the DSI host
+	 * driver's component bind function.
+	 */
+	drm_panel_add(&ts->base);
+
+	ts->dsi->mode_flags = _ws_panel_data->mode_flags;
+	ts->dsi->format = MIPI_DSI_FMT_RGB888;
+	ts->dsi->lanes = _ws_panel_data->lanes;
+
+	ret = devm_mipi_dsi_attach(dev, ts->dsi);
+
+	if (ret)
+		dev_err(dev, "failed to attach dsi to host: %d\n", ret);
+
+	return 0;
+
+error:
+	of_node_put(endpoint);
+	return -ENODEV;
+}
+
+static void ws_panel_remove(struct i2c_client *i2c)
+{
+	struct ws_panel *ts = i2c_get_clientdata(i2c);
+
+	ws_panel_disable(&ts->base);
+
+	drm_panel_remove(&ts->base);
+}
+
+static void ws_panel_shutdown(struct i2c_client *i2c)
+{
+	struct ws_panel *ts = i2c_get_clientdata(i2c);
+
+	ws_panel_disable(&ts->base);
+}
+
+static const struct of_device_id ws_panel_of_ids[] = {
+	{
+		.compatible = "waveshare,2.8inch-panel",
+		.data = &ws_panel_2_8_data,
+	}, {
+		.compatible = "waveshare,3.4inch-panel",
+		.data = &ws_panel_3_4_data,
+	}, {
+		.compatible = "waveshare,4.0inch-panel",
+		.data = &ws_panel_4_0_data,
+	}, {
+		.compatible = "waveshare,7.0inch-c-panel",
+		.data = &ws_panel_7_0_c_data,
+	}, {
+		.compatible = "waveshare,7.9inch-panel",
+		.data = &ws_panel_7_9_data,
+	}, {
+		.compatible = "waveshare,8.0inch-panel",
+		.data = &ws_panel_10_1_data,
+	}, {
+		.compatible = "waveshare,10.1inch-panel",
+		.data = &ws_panel_10_1_data,
+	}, {
+		.compatible = "waveshare,11.9inch-panel",
+		.data = &ws_panel_11_9_data,
+	}, {
+		.compatible = "waveshare,4inch-panel",
+		.data = &ws_panel_4_data,
+	}, {
+		.compatible = "waveshare,5.0inch-panel",
+		.data = &ws_panel_5_0_data,
+	}, {
+		.compatible = "waveshare,6.25inch-panel",
+		.data = &ws_panel_6_25_data,
+	}, {
+		.compatible = "waveshare,8.8inch-panel",
+		.data = &ws_panel_8_8_data,
+	}, {
+		.compatible = "waveshare,13.3inch-4lane-panel",
+		.data = &ws_panel_13_3_4lane_data,
+	}, {
+		.compatible = "waveshare,13.3inch-2lane-panel",
+		.data = &ws_panel_13_3_2lane_data,
+	}, {
+		.compatible = "waveshare,7.0inch-h-panel",
+		.data = &ws_panel_7_0_h_data,
+	}, {
+		/* sentinel */
+	}
+};
+MODULE_DEVICE_TABLE(of, ws_panel_of_ids);
+
+static struct i2c_driver ws_panel_driver = {
+	.driver = {
+		.name = "ws_touchscreen",
+		.of_match_table = ws_panel_of_ids,
+	},
+	.probe = ws_panel_probe,
+	.remove = ws_panel_remove,
+	.shutdown = ws_panel_shutdown,
+};
+module_i2c_driver(ws_panel_driver);
+
+MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>");
+MODULE_DESCRIPTION("Waveshare DSI panel driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/rp1/Kconfig b/drivers/gpu/drm/rp1/Kconfig
new file mode 100644
index 000000000000..d73c62e0ab64
--- /dev/null
+++ b/drivers/gpu/drm/rp1/Kconfig
@@ -0,0 +1,5 @@
+source "drivers/gpu/drm/rp1/rp1-dsi/Kconfig"
+
+source "drivers/gpu/drm/rp1/rp1-dpi/Kconfig"
+
+source "drivers/gpu/drm/rp1/rp1-vec/Kconfig"
diff --git a/drivers/gpu/drm/rp1/Makefile b/drivers/gpu/drm/rp1/Makefile
new file mode 100644
index 000000000000..0f915b158e96
--- /dev/null
+++ b/drivers/gpu/drm/rp1/Makefile
@@ -0,0 +1,4 @@
+obj-$(CONFIG_DRM_RP1_DSI) += rp1-dsi/
+obj-$(CONFIG_DRM_RP1_DPI) += rp1-dpi/
+obj-$(CONFIG_DRM_RP1_VEC) += rp1-vec/
+
diff --git a/drivers/gpu/drm/rp1/rp1-dpi/Kconfig b/drivers/gpu/drm/rp1/rp1-dpi/Kconfig
new file mode 100644
index 000000000000..95d17902094d
--- /dev/null
+++ b/drivers/gpu/drm/rp1/rp1-dpi/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config DRM_RP1_DPI
+	tristate "DRM Support for RP1 DPI"
+	depends on DRM && MFD_RP1
+	select DRM_GEM_DMA_HELPER
+	select DRM_KMS_HELPER
+	select DRM_VRAM_HELPER
+	select DRM_TTM
+	select DRM_TTM_HELPER
+	depends on RP1_PIO || !RP1_PIO
+	help
+	  Choose this option to enable DPI output on Raspberry Pi RP1
+
+	  There is an optional dependency on RP1_PIO, as the PIO block
+	  must be used to fix up interlaced sync. Interlaced DPI modes
+	  will be unavailable when RP1_PIO is not selected.
diff --git a/drivers/gpu/drm/rp1/rp1-dpi/Makefile b/drivers/gpu/drm/rp1/rp1-dpi/Makefile
new file mode 100644
index 000000000000..30d499c2959e
--- /dev/null
+++ b/drivers/gpu/drm/rp1/rp1-dpi/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+drm-rp1-dpi-y := rp1_dpi.o rp1_dpi_hw.o rp1_dpi_cfg.o rp1_dpi_pio.o
+
+obj-$(CONFIG_DRM_RP1_DPI) += drm-rp1-dpi.o
diff --git a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c
new file mode 100644
index 000000000000..d4406fb8394a
--- /dev/null
+++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.c
@@ -0,0 +1,496 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * DRM Driver for DPI output on Raspberry Pi RP1
+ *
+ * Copyright (c) 2023 Raspberry Pi Limited.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/list.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/printk.h>
+#include <linux/console.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <linux/dma-mapping.h>
+#include <linux/cred.h>
+#include <linux/media-bus-format.h>
+#include <linux/pinctrl/consumer.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_mm.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_managed.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_fbdev_ttm.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_vblank.h>
+#include <drm/drm_of.h>
+
+#include "rp1_dpi.h"
+
+/*
+ * Default bus format, where not specified by a connector/bridge
+ * and not overridden by the OF property "default_bus_fmt".
+ * This value is for compatibility with vc4 and VGA666-style boards,
+ * even though RP1 hardware cannot achieve the full 18-bit depth
+ * with that pinout (MEDIA_BUS_FMT_RGB666_1X24_CPADHI is preferred).
+ */
+static unsigned int default_bus_fmt = MEDIA_BUS_FMT_RGB666_1X18;
+module_param(default_bus_fmt, uint, 0644);
+
+/*
+ * Override DRM mode flags to force the use of Composite Sync on GPIO1.
+ * This is mostly for testing, as neither panel-timing nor command-line
+ * arguments nor utilities such as "kmstest" can set DRM_MODE_FLAG_CSYNC.
+ * Sampled on each enable/mode-switch. Default polarity will be -ve.
+ * (Setting this may break Vertical Sync on GPIO2 for interlaced modes.)
+ */
+static bool force_csync;
+module_param(force_csync, bool, 0644);
+
+/* -------------------------------------------------------------- */
+
+static void rp1dpi_pipe_update(struct drm_simple_display_pipe *pipe,
+			       struct drm_plane_state *old_state)
+{
+	struct drm_pending_vblank_event *event;
+	unsigned long flags;
+	struct drm_framebuffer *fb = pipe->plane.state->fb;
+	struct rp1_dpi *dpi = pipe->crtc.dev->dev_private;
+	struct drm_gem_object *gem = fb ? drm_gem_fb_get_obj(fb, 0) : NULL;
+	struct drm_gem_dma_object *dma_obj = gem ? to_drm_gem_dma_obj(gem) : NULL;
+	bool can_update = fb && dma_obj && dpi && dpi->pipe_enabled;
+
+	/* (Re-)start DPI-DMA where required; and update FB address */
+	if (can_update) {
+		if (!dpi->dpi_running || fb->format->format != dpi->cur_fmt) {
+			if (dpi->dpi_running &&
+			    fb->format->format != dpi->cur_fmt) {
+				rp1dpi_hw_stop(dpi);
+				rp1dpi_pio_stop(dpi);
+				dpi->dpi_running = false;
+			}
+			if (!dpi->dpi_running) {
+				rp1dpi_hw_setup(dpi,
+						fb->format->format,
+						dpi->bus_fmt,
+						dpi->de_inv,
+						&pipe->crtc.state->mode);
+				rp1dpi_pio_start(dpi, &pipe->crtc.state->mode,
+						 force_csync);
+				dpi->dpi_running = true;
+			}
+			dpi->cur_fmt = fb->format->format;
+			drm_crtc_vblank_on(&pipe->crtc);
+		}
+		rp1dpi_hw_update(dpi, dma_obj->dma_addr, fb->offsets[0], fb->pitches[0]);
+	}
+
+	/* Arm VBLANK event (or call it immediately in some error cases) */
+	spin_lock_irqsave(&pipe->crtc.dev->event_lock, flags);
+	event = pipe->crtc.state->event;
+	if (event) {
+		pipe->crtc.state->event = NULL;
+		if (can_update && drm_crtc_vblank_get(&pipe->crtc) == 0)
+			drm_crtc_arm_vblank_event(&pipe->crtc, event);
+		else
+			drm_crtc_send_vblank_event(&pipe->crtc, event);
+	}
+	spin_unlock_irqrestore(&pipe->crtc.dev->event_lock, flags);
+}
+
+static void rp1dpi_pipe_enable(struct drm_simple_display_pipe *pipe,
+			       struct drm_crtc_state *crtc_state,
+			      struct drm_plane_state *plane_state)
+{
+	static const unsigned int M = 1000000;
+	struct rp1_dpi *dpi = pipe->crtc.dev->dev_private;
+	struct drm_connector *conn;
+	struct drm_connector_list_iter conn_iter;
+	unsigned int fpix, fdiv, fvco;
+	int ret;
+
+	/* Look up the connector attached to DPI so we can get the
+	 * bus_format.  Ideally the bridge would tell us the
+	 * bus_format we want, but it doesn't yet, so assume that it's
+	 * uniform throughout the bridge chain.
+	 */
+	dev_info(&dpi->pdev->dev, __func__);
+	drm_connector_list_iter_begin(pipe->encoder.dev, &conn_iter);
+	drm_for_each_connector_iter(conn, &conn_iter) {
+		if (conn->encoder == &pipe->encoder) {
+			dpi->de_inv = !!(conn->display_info.bus_flags &
+							DRM_BUS_FLAG_DE_LOW);
+			dpi->clk_inv = !!(conn->display_info.bus_flags &
+						DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE);
+			if (conn->display_info.num_bus_formats)
+				dpi->bus_fmt = conn->display_info.bus_formats[0];
+			break;
+		}
+	}
+	drm_connector_list_iter_end(&conn_iter);
+
+	/* Set DPI clock to desired frequency. Currently (experimentally)
+	 * we take control of the VideoPLL, to ensure we can generate it
+	 * accurately. NB: this prevents concurrent use of DPI and VEC!
+	 * Magic numbers ensure the parent clock is within [100MHz, 200MHz]
+	 * with VCO in [1GHz, 1.33GHz]. The initial divide is by 6, 8 or 10.
+	 */
+	fpix = 1000 * pipe->crtc.state->mode.clock;
+	fpix = clamp(fpix, 1 * M, 200 * M);
+	fdiv = fpix;
+	while (fdiv < 100 * M)
+		fdiv *= 2;
+	fvco = fdiv * 2 * DIV_ROUND_UP(500 * M, fdiv);
+	ret = clk_set_rate(dpi->clocks[RP1DPI_CLK_PLLCORE], fvco);
+	if (ret)
+		dev_err(&dpi->pdev->dev, "Failed to set PLL VCO to %u (%d)", fvco, ret);
+	ret = clk_set_rate(dpi->clocks[RP1DPI_CLK_PLLDIV], fdiv);
+	if (ret)
+		dev_err(&dpi->pdev->dev, "Failed to set PLL output to %u (%d)", fdiv, ret);
+	ret = clk_set_rate(dpi->clocks[RP1DPI_CLK_DPI], fpix);
+	if (ret)
+		dev_err(&dpi->pdev->dev, "Failed to set DPI clock to %u (%d)", fpix, ret);
+
+	rp1dpi_vidout_setup(dpi, dpi->clk_inv);
+	clk_prepare_enable(dpi->clocks[RP1DPI_CLK_PLLCORE]);
+	clk_prepare_enable(dpi->clocks[RP1DPI_CLK_PLLDIV]);
+	pinctrl_pm_select_default_state(&dpi->pdev->dev);
+	clk_prepare_enable(dpi->clocks[RP1DPI_CLK_DPI]);
+	dev_info(&dpi->pdev->dev, "Want %u /%u %u /%u %u; got VCO=%lu DIV=%lu DPI=%lu",
+		 fvco, fvco / fdiv, fdiv, fdiv / fpix, fpix,
+		 clk_get_rate(dpi->clocks[RP1DPI_CLK_PLLCORE]),
+		 clk_get_rate(dpi->clocks[RP1DPI_CLK_PLLDIV]),
+		 clk_get_rate(dpi->clocks[RP1DPI_CLK_DPI]));
+
+	/* Start DPI-DMA. pipe already has the new crtc and plane state. */
+	dpi->pipe_enabled = true;
+	dpi->cur_fmt = 0xdeadbeef;
+	rp1dpi_pipe_update(pipe, 0);
+}
+
+static void rp1dpi_pipe_disable(struct drm_simple_display_pipe *pipe)
+{
+	struct rp1_dpi *dpi = pipe->crtc.dev->dev_private;
+
+	dev_info(&dpi->pdev->dev, __func__);
+	drm_crtc_vblank_off(&pipe->crtc);
+	if (dpi->dpi_running) {
+		rp1dpi_hw_stop(dpi);
+		rp1dpi_pio_stop(dpi);
+		dpi->dpi_running = false;
+	}
+	clk_disable_unprepare(dpi->clocks[RP1DPI_CLK_DPI]);
+	pinctrl_pm_select_sleep_state(&dpi->pdev->dev);
+	clk_disable_unprepare(dpi->clocks[RP1DPI_CLK_PLLDIV]);
+	clk_disable_unprepare(dpi->clocks[RP1DPI_CLK_PLLCORE]);
+	dpi->pipe_enabled = false;
+}
+
+static int rp1dpi_pipe_enable_vblank(struct drm_simple_display_pipe *pipe)
+{
+	struct rp1_dpi *dpi = pipe->crtc.dev->dev_private;
+
+	if (dpi)
+		rp1dpi_hw_vblank_ctrl(dpi, 1);
+
+	return 0;
+}
+
+static void rp1dpi_pipe_disable_vblank(struct drm_simple_display_pipe *pipe)
+{
+	struct rp1_dpi *dpi = pipe->crtc.dev->dev_private;
+
+	if (dpi)
+		rp1dpi_hw_vblank_ctrl(dpi, 0);
+}
+
+static enum drm_mode_status rp1dpi_pipe_mode_valid(struct drm_simple_display_pipe *pipe,
+						   const struct drm_display_mode *mode)
+{
+#if !IS_REACHABLE(CONFIG_RP1_PIO)
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+		return MODE_NO_INTERLACE;
+#endif
+	if (mode->clock < 1000) /* 1 MHz */
+		return MODE_CLOCK_LOW;
+	if (mode->clock > 200000) /* 200 MHz */
+		return MODE_CLOCK_HIGH;
+
+	return MODE_OK;
+}
+
+static const struct drm_simple_display_pipe_funcs rp1dpi_pipe_funcs = {
+	.enable	    = rp1dpi_pipe_enable,
+	.update	    = rp1dpi_pipe_update,
+	.disable    = rp1dpi_pipe_disable,
+	.enable_vblank	= rp1dpi_pipe_enable_vblank,
+	.disable_vblank = rp1dpi_pipe_disable_vblank,
+	.mode_valid = rp1dpi_pipe_mode_valid,
+};
+
+static const struct drm_mode_config_funcs rp1dpi_mode_funcs = {
+	.fb_create = drm_gem_fb_create,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+static void rp1dpi_stopall(struct drm_device *drm)
+{
+	if (drm->dev_private) {
+		struct rp1_dpi *dpi = drm->dev_private;
+
+		if (dpi->dpi_running || rp1dpi_hw_busy(dpi)) {
+			rp1dpi_hw_stop(dpi);
+			clk_disable_unprepare(dpi->clocks[RP1DPI_CLK_DPI]);
+			rp1dpi_pio_stop(dpi);
+			dpi->dpi_running = false;
+		}
+		rp1dpi_vidout_poweroff(dpi);
+		pinctrl_pm_select_sleep_state(&dpi->pdev->dev);
+	}
+}
+
+DEFINE_DRM_GEM_DMA_FOPS(rp1dpi_fops);
+
+static struct drm_driver rp1dpi_driver = {
+	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
+	.fops			= &rp1dpi_fops,
+	.name			= "drm-rp1-dpi",
+	.desc			= "drm-rp1-dpi",
+	.date			= "0",
+	.major			= 1,
+	.minor			= 0,
+	DRM_GEM_DMA_DRIVER_OPS,
+	.release		= rp1dpi_stopall,
+};
+
+static const u32 rp1dpi_formats[] = {
+	DRM_FORMAT_XRGB8888,
+	DRM_FORMAT_XBGR8888,
+	DRM_FORMAT_ARGB8888,
+	DRM_FORMAT_ABGR8888,
+	DRM_FORMAT_RGB888,
+	DRM_FORMAT_BGR888,
+	DRM_FORMAT_RGB565
+};
+
+static int rp1dpi_platform_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rp1_dpi *dpi;
+	struct drm_bridge *bridge = NULL;
+	const char *rgb_order = NULL;
+	struct drm_panel *panel;
+	u32 missing_gpios;
+	int i, j, ret;
+
+	dev_info(dev, __func__);
+	ret = drm_of_find_panel_or_bridge(pdev->dev.of_node, 0, 0,
+					  &panel, &bridge);
+	if (ret) {
+		dev_info(dev, "%s: bridge not found\n", __func__);
+		return -EPROBE_DEFER;
+	}
+	if (panel) {
+		bridge = devm_drm_panel_bridge_add(dev, panel);
+		if (IS_ERR(bridge))
+			return PTR_ERR(bridge);
+	}
+
+	dpi = devm_drm_dev_alloc(dev, &rp1dpi_driver, struct rp1_dpi, drm);
+	if (IS_ERR(dpi)) {
+		ret = PTR_ERR(dpi);
+		dev_err(dev, "%s devm_drm_dev_alloc %d", __func__, ret);
+		return ret;
+	}
+	dpi->pdev = pdev;
+	spin_lock_init(&dpi->hw_lock);
+
+	dpi->bus_fmt = default_bus_fmt;
+	ret = of_property_read_u32(dev->of_node, "default_bus_fmt", &dpi->bus_fmt);
+
+	for (i = 0; i < RP1DPI_NUM_HW_BLOCKS; i++) {
+		dpi->hw_base[i] =
+			devm_ioremap_resource(dev,
+					      platform_get_resource(dpi->pdev, IORESOURCE_MEM, i));
+		if (IS_ERR(dpi->hw_base[i])) {
+			dev_err(dev, "Error memory mapping regs[%d]\n", i);
+			return PTR_ERR(dpi->hw_base[i]);
+		}
+	}
+	ret = platform_get_irq(dpi->pdev, 0);
+	if (ret > 0)
+		ret = devm_request_irq(dev, ret, rp1dpi_hw_isr,
+				       IRQF_SHARED, "rp1-dpi", dpi);
+	if (ret) {
+		dev_err(dev, "Unable to request interrupt\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < RP1DPI_NUM_CLOCKS; i++) {
+		static const char * const myclocknames[RP1DPI_NUM_CLOCKS] = {
+			"dpiclk", "plldiv", "pllcore"
+		};
+		dpi->clocks[i] = devm_clk_get(dev, myclocknames[i]);
+		if (IS_ERR(dpi->clocks[i])) {
+			dev_err(dev, "Unable to request clock %s\n", myclocknames[i]);
+			return PTR_ERR(dpi->clocks[i]);
+		}
+	}
+
+	ret = drmm_mode_config_init(&dpi->drm);
+	if (ret)
+		goto done_err;
+
+	/* RGB order property - to match VC4 */
+	dpi->rgb_order_override = RP1DPI_ORDER_UNCHANGED;
+	if (!of_property_read_string(dev->of_node, "rgb_order", &rgb_order)) {
+		if (!strcmp(rgb_order, "rgb"))
+			dpi->rgb_order_override = RP1DPI_ORDER_RGB;
+		else if (!strcmp(rgb_order, "bgr"))
+			dpi->rgb_order_override = RP1DPI_ORDER_BGR;
+		else if (!strcmp(rgb_order, "grb"))
+			dpi->rgb_order_override = RP1DPI_ORDER_GRB;
+		else if (!strcmp(rgb_order, "brg"))
+			dpi->rgb_order_override = RP1DPI_ORDER_BRG;
+		else
+			DRM_ERROR("Invalid dpi order %s - ignored\n", rgb_order);
+	}
+
+	/* Check if all of GPIOs 1, 2 and 3 are assigned to DPI */
+	missing_gpios = BIT(1) | BIT(2) | BIT(3);
+	for (i = 0; missing_gpios; i++) {
+		u32 p = 0;
+		const char *str = NULL;
+		struct device_node *np1 = of_parse_phandle(dev->of_node, "pinctrl-0", i);
+
+		if (!np1)
+			break;
+
+		if (!of_property_read_string(np1, "function", &str) && !strcmp(str, "dpi")) {
+			for (j = 0; missing_gpios; j++) {
+				if (of_property_read_string_index(np1, "pins", j, &str))
+					break;
+				if (!strcmp(str, "gpio1"))
+					missing_gpios &= ~BIT(1);
+				else if (!strcmp(str, "gpio2"))
+					missing_gpios &= ~BIT(2);
+				else if (!strcmp(str, "gpio3"))
+					missing_gpios &= ~BIT(3);
+			}
+			for (j = 0; missing_gpios; j++) {
+				if (of_property_read_u32_index(np1, "brcm,pins", j, &p))
+					break;
+				if (p < 32)
+					missing_gpios &= ~(1 << p);
+			}
+		}
+		of_node_put(np1);
+	}
+	dpi->sync_gpios_mapped = !missing_gpios;
+
+	/* Now we have all our resources, finish driver initialization */
+	dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
+	init_completion(&dpi->finished);
+	dpi->drm.dev_private = dpi;
+	platform_set_drvdata(pdev, &dpi->drm);
+
+	dpi->drm.mode_config.max_width  = 4096;
+	dpi->drm.mode_config.max_height = 4096;
+	dpi->drm.mode_config.preferred_depth = 32;
+	dpi->drm.mode_config.prefer_shadow   = 0;
+	dpi->drm.mode_config.quirk_addfb_prefer_host_byte_order = true;
+	dpi->drm.mode_config.funcs = &rp1dpi_mode_funcs;
+	drm_vblank_init(&dpi->drm, 1);
+
+	ret = drm_simple_display_pipe_init(&dpi->drm,
+					   &dpi->pipe,
+					   &rp1dpi_pipe_funcs,
+					   rp1dpi_formats,
+					   ARRAY_SIZE(rp1dpi_formats),
+					   NULL, NULL);
+	if (!ret)
+		ret = drm_simple_display_pipe_attach_bridge(&dpi->pipe, bridge);
+	if (ret)
+		goto done_err;
+
+	drm_mode_config_reset(&dpi->drm);
+
+	ret = drm_dev_register(&dpi->drm, 0);
+	if (ret)
+		return ret;
+
+	drm_fbdev_ttm_setup(&dpi->drm, 32);
+	return ret;
+
+done_err:
+	dev_err(dev, "%s fail %d\n", __func__, ret);
+	return ret;
+}
+
+static void rp1dpi_platform_remove(struct platform_device *pdev)
+{
+	struct drm_device *drm = platform_get_drvdata(pdev);
+
+	rp1dpi_stopall(drm);
+	drm_dev_unregister(drm);
+	drm_atomic_helper_shutdown(drm);
+	drm_dev_put(drm);
+}
+
+static void rp1dpi_platform_shutdown(struct platform_device *pdev)
+{
+	struct drm_device *drm = platform_get_drvdata(pdev);
+
+	rp1dpi_stopall(drm);
+}
+
+static const struct of_device_id rp1dpi_of_match[] = {
+	{
+		.compatible = "raspberrypi,rp1dpi",
+	},
+	{ /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, rp1dpi_of_match);
+
+static struct platform_driver rp1dpi_platform_driver = {
+	.probe		= rp1dpi_platform_probe,
+	.remove		= rp1dpi_platform_remove,
+	.shutdown	= rp1dpi_platform_shutdown,
+	.driver		= {
+		.name	= DRIVER_NAME,
+		.owner	= THIS_MODULE,
+		.of_match_table = rp1dpi_of_match,
+	},
+};
+
+module_platform_driver(rp1dpi_platform_driver);
+
+MODULE_AUTHOR("Nick Hollinghurst");
+MODULE_DESCRIPTION("DRM driver for DPI output on Raspberry Pi RP1");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.h b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.h
new file mode 100644
index 000000000000..693042575a90
--- /dev/null
+++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi.h
@@ -0,0 +1,95 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * DRM Driver for DSI output on Raspberry Pi RP1
+ *
+ * Copyright (c) 2023 Raspberry Pi Limited.
+ */
+
+#include <linux/types.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <drm/drm_device.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#define MODULE_NAME "drm-rp1-dpi"
+#define DRIVER_NAME "drm-rp1-dpi"
+
+/* ---------------------------------------------------------------------- */
+
+#define RP1DPI_HW_BLOCK_DPI   0
+#define RP1DPI_HW_BLOCK_CFG   1
+#define RP1DPI_NUM_HW_BLOCKS  2
+
+#define RP1DPI_CLK_DPI      0
+#define RP1DPI_CLK_PLLDIV   1
+#define RP1DPI_CLK_PLLCORE  2
+#define RP1DPI_NUM_CLOCKS   3
+
+/* Codes (in LE byte order) used for S/W permutation */
+#define RP1DPI_ORDER_UNCHANGED 0
+#define RP1DPI_ORDER_RGB       0x020100
+#define RP1DPI_ORDER_BGR       0x000102
+#define RP1DPI_ORDER_GRB       0x020001
+#define RP1DPI_ORDER_BRG       0x010002
+
+/* ---------------------------------------------------------------------- */
+
+struct rp1_dpi {
+	/* DRM base and platform device pointer */
+	struct drm_device drm;
+	struct platform_device *pdev;
+
+	/* Framework and helper objects */
+	struct drm_simple_display_pipe pipe;
+	struct drm_connector connector;
+
+	/* Clocks: Video PLL, its primary divider, and DPI clock. */
+	struct clk *clocks[RP1DPI_NUM_CLOCKS];
+
+	/* Block (DPI, VOCFG) base addresses, and current state */
+	void __iomem *hw_base[RP1DPI_NUM_HW_BLOCKS];
+	u32 cur_fmt;
+	u32 bus_fmt;
+	bool de_inv, clk_inv;
+	bool dpi_running, pipe_enabled;
+	unsigned int rgb_order_override;
+	struct completion finished;
+
+	/* The following are for Interlace and CSYNC support using PIO */
+	struct rp1_pio_client *pio;
+	bool sync_gpios_mapped;
+
+	spinlock_t hw_lock; /* the following are used in line-match ISR */
+	dma_addr_t last_dma_addr;
+	u32 last_stride;
+	u32 shorter_front_porch;
+	bool interlaced;
+	bool lower_field_flag;
+};
+
+/* ---------------------------------------------------------------------- */
+/* Functions to control the DPI/DMA block				  */
+
+void rp1dpi_hw_setup(struct rp1_dpi *dpi,
+		     u32 in_format,
+		     u32 bus_format,
+		     bool de_inv,
+		     struct drm_display_mode const *mode);
+void rp1dpi_hw_update(struct rp1_dpi *dpi, dma_addr_t addr, u32 offset, u32 stride);
+void rp1dpi_hw_stop(struct rp1_dpi *dpi);
+int rp1dpi_hw_busy(struct rp1_dpi *dpi);
+irqreturn_t rp1dpi_hw_isr(int irq, void *dev);
+void rp1dpi_hw_vblank_ctrl(struct rp1_dpi *dpi, int enable);
+
+/* ---------------------------------------------------------------------- */
+/* Functions to control the VIDEO OUT CFG block and check RP1 platform	  */
+
+void rp1dpi_vidout_setup(struct rp1_dpi *dpi, bool drive_negedge);
+void rp1dpi_vidout_poweroff(struct rp1_dpi *dpi);
+
+/* ---------------------------------------------------------------------- */
+/* PIO control -- we need PIO to generate VSync (from DE) when interlaced */
+
+int rp1dpi_pio_start(struct rp1_dpi *dpi, const struct drm_display_mode *mode,
+		     bool force_csync);
+void rp1dpi_pio_stop(struct rp1_dpi *dpi);
diff --git a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_cfg.c b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_cfg.c
new file mode 100644
index 000000000000..6a384a31bb34
--- /dev/null
+++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_cfg.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * DRM Driver for DPI output on Raspberry Pi RP1
+ * Functions to set up VIDEO_OUT_CFG registers
+ * Copyright (c) 2023-2025 Raspberry Pi Limited.
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/printk.h>
+#include <linux/rp1_platform.h>
+
+#include "rp1_dpi.h"
+
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_SEL
+// Description : Selects source: 0 => DPI, 1 =>VEC; optionally invert clock
+#define VIDEO_OUT_CFG_SEL                    0x0000
+#define VIDEO_OUT_CFG_SEL_BITS               0x00000013
+#define VIDEO_OUT_CFG_SEL_RESET              0x00000000
+#define VIDEO_OUT_CFG_SEL_PCLK_INV           BIT(4)
+#define VIDEO_OUT_CFG_SEL_PAD_MUX            BIT(1)
+#define VIDEO_OUT_CFG_SEL_VDAC_MUX           BIT(0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_VDAC_CFG
+// Description : Configure SNPS VDAC
+#define VIDEO_OUT_CFG_VDAC_CFG               0x0004
+#define VIDEO_OUT_CFG_VDAC_CFG_BITS          0x1fffffff
+#define VIDEO_OUT_CFG_VDAC_CFG_RESET         0x0003ffff
+#define VIDEO_OUT_CFG_VDAC_CFG_ENCTR         GENMASK(28, 26)
+#define VIDEO_OUT_CFG_VDAC_CFG_ENSC          GENMASK(25, 23)
+#define VIDEO_OUT_CFG_VDAC_CFG_ENDAC         GENMASK(22, 20)
+#define VIDEO_OUT_CFG_VDAC_CFG_ENVBG         BIT(19)
+#define VIDEO_OUT_CFG_VDAC_CFG_ENEXTREF      BIT(18)
+#define VIDEO_OUT_CFG_VDAC_CFG_DAC2GC        GENMASK(17, 12)
+#define VIDEO_OUT_CFG_VDAC_CFG_DAC1GC        GENMASK(11, 6)
+#define VIDEO_OUT_CFG_VDAC_CFG_DAC0GC        GENMASK(5, 0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_VDAC_STATUS
+// Description : Read VDAC status
+#define VIDEO_OUT_CFG_VDAC_STATUS            0x0008
+#define VIDEO_OUT_CFG_VDAC_STATUS_BITS       0x00000017
+#define VIDEO_OUT_CFG_VDAC_STATUS_RESET      0x00000000
+#define VIDEO_OUT_CFG_VDAC_STATUS_ENCTR3     BIT(4)
+#define VIDEO_OUT_CFG_VDAC_STATUS_CABLEOUT   GENMASK(2, 0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_MEM_PD
+// Description : Control memory power down
+#define VIDEO_OUT_CFG_MEM_PD                 0x000c
+#define VIDEO_OUT_CFG_MEM_PD_BITS            0x00000003
+#define VIDEO_OUT_CFG_MEM_PD_RESET           0x00000000
+#define VIDEO_OUT_CFG_MEM_PD_VEC             BIT(1)
+#define VIDEO_OUT_CFG_MEM_PD_DPI             BIT(0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_TEST_OVERRIDE
+// Description : Allow forcing of output values
+#define VIDEO_OUT_CFG_TEST_OVERRIDE          0x0010
+#define VIDEO_OUT_CFG_TEST_OVERRIDE_BITS     0xffffffff
+#define VIDEO_OUT_CFG_TEST_OVERRIDE_RESET    0x00000000
+#define VIDEO_OUT_CFG_TEST_OVERRIDE_PAD      BIT(31)
+#define VIDEO_OUT_CFG_TEST_OVERRIDE_VDAC     BIT(30)
+#define VIDEO_OUT_CFG_TEST_OVERRIDE_RGBVAL   GENMASK(29, 0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_INTR
+// Description : Raw Interrupts
+#define VIDEO_OUT_CFG_INTR                   0x0014
+#define VIDEO_OUT_CFG_INTR_BITS              0x00000003
+#define VIDEO_OUT_CFG_INTR_RESET             0x00000000
+#define VIDEO_OUT_CFG_INTR_DPI               BIT(1)
+#define VIDEO_OUT_CFG_INTR_VEC               BIT(0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_INTE
+// Description : Interrupt Enable
+#define VIDEO_OUT_CFG_INTE                   0x0018
+#define VIDEO_OUT_CFG_INTE_BITS              0x00000003
+#define VIDEO_OUT_CFG_INTE_RESET             0x00000000
+#define VIDEO_OUT_CFG_INTE_DPI               BIT(1)
+#define VIDEO_OUT_CFG_INTE_VEC               BIT(0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_INTF
+// Description : Interrupt Force
+#define VIDEO_OUT_CFG_INTF                   0x001c
+#define VIDEO_OUT_CFG_INTF_BITS              0x00000003
+#define VIDEO_OUT_CFG_INTF_RESET             0x00000000
+#define VIDEO_OUT_CFG_INTF_DPI               BIT(1)
+#define VIDEO_OUT_CFG_INTF_VEC               BIT(0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_INTS
+// Description : Interrupt status after masking & forcing
+#define VIDEO_OUT_CFG_INTS                   0x0020
+#define VIDEO_OUT_CFG_INTS_BITS              0x00000003
+#define VIDEO_OUT_CFG_INTS_RESET             0x00000000
+#define VIDEO_OUT_CFG_INTS_DPI               BIT(1)
+#define VIDEO_OUT_CFG_INTS_VEC               BIT(0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_BLOCK_ID
+// Description : Block Identifier
+//               Hexadecimal representation of "VOCF"
+#define VIDEO_OUT_CFG_BLOCK_ID               0x0024
+#define VIDEO_OUT_CFG_BLOCK_ID_BITS          0xffffffff
+#define VIDEO_OUT_CFG_BLOCK_ID_RESET         0x564f4346
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_INSTANCE_ID
+// Description : Block Instance Identifier
+#define VIDEO_OUT_CFG_INSTANCE_ID            0x0028
+#define VIDEO_OUT_CFG_INSTANCE_ID_BITS       0x0000000f
+#define VIDEO_OUT_CFG_INSTANCE_ID_RESET      0x00000000
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_RSTSEQ_AUTO
+// Description : None
+#define VIDEO_OUT_CFG_RSTSEQ_AUTO            0x002c
+#define VIDEO_OUT_CFG_RSTSEQ_AUTO_BITS       0x00000007
+#define VIDEO_OUT_CFG_RSTSEQ_AUTO_RESET      0x00000007
+#define VIDEO_OUT_CFG_RSTSEQ_AUTO_VEC        BIT(2)
+#define VIDEO_OUT_CFG_RSTSEQ_AUTO_DPI        BIT(1)
+#define VIDEO_OUT_CFG_RSTSEQ_AUTO_BUSADAPTER BIT(0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_RSTSEQ_PARALLEL
+// Description : None
+#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL        0x0030
+#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_BITS   0x00000007
+#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_RESET  0x00000006
+#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_VEC    BIT(2)
+#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_DPI    BIT(1)
+#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_BUSADAPTER BIT(0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_RSTSEQ_CTRL
+// Description : None
+#define VIDEO_OUT_CFG_RSTSEQ_CTRL            0x0034
+#define VIDEO_OUT_CFG_RSTSEQ_CTRL_BITS       0x00000007
+#define VIDEO_OUT_CFG_RSTSEQ_CTRL_RESET      0x00000000
+#define VIDEO_OUT_CFG_RSTSEQ_CTRL_VEC        BIT(2)
+#define VIDEO_OUT_CFG_RSTSEQ_CTRL_DPI        BIT(1)
+#define VIDEO_OUT_CFG_RSTSEQ_CTRL_BUSADAPTER BIT(0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_RSTSEQ_TRIG
+// Description : None
+#define VIDEO_OUT_CFG_RSTSEQ_TRIG            0x0038
+#define VIDEO_OUT_CFG_RSTSEQ_TRIG_BITS       0x00000007
+#define VIDEO_OUT_CFG_RSTSEQ_TRIG_RESET      0x00000000
+#define VIDEO_OUT_CFG_RSTSEQ_TRIG_VEC        BIT(2)
+#define VIDEO_OUT_CFG_RSTSEQ_TRIG_DPI        BIT(1)
+#define VIDEO_OUT_CFG_RSTSEQ_TRIG_BUSADAPTER BIT(0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_RSTSEQ_DONE
+// Description : None
+#define VIDEO_OUT_CFG_RSTSEQ_DONE            0x003c
+#define VIDEO_OUT_CFG_RSTSEQ_DONE_BITS       0x00000007
+#define VIDEO_OUT_CFG_RSTSEQ_DONE_RESET      0x00000000
+#define VIDEO_OUT_CFG_RSTSEQ_DONE_VEC        BIT(2)
+#define VIDEO_OUT_CFG_RSTSEQ_DONE_DPI        BIT(1)
+#define VIDEO_OUT_CFG_RSTSEQ_DONE_BUSADAPTER BIT(0)
+// =============================================================================
+
+#define CFG_WRITE(reg, val)  writel((val),  dpi->hw_base[RP1DPI_HW_BLOCK_CFG] + (reg))
+#define CFG_READ(reg)        readl(dpi->hw_base[RP1DPI_HW_BLOCK_CFG] + (reg))
+
+void rp1dpi_vidout_setup(struct rp1_dpi *dpi, bool drive_negedge)
+{
+	/*
+	 * We assume DPI and VEC can't be used at the same time (due to
+	 * clashing requirements for PLL_VIDEO, and potentially for VDAC).
+	 * We therefore leave VEC memories powered down.
+	 */
+	CFG_WRITE(VIDEO_OUT_CFG_MEM_PD, VIDEO_OUT_CFG_MEM_PD_VEC);
+	CFG_WRITE(VIDEO_OUT_CFG_TEST_OVERRIDE,
+		  VIDEO_OUT_CFG_TEST_OVERRIDE_VDAC);
+
+	/* DPI->Pads; DPI->VDAC; optionally flip PCLK polarity */
+	CFG_WRITE(VIDEO_OUT_CFG_SEL,
+		  drive_negedge ? VIDEO_OUT_CFG_SEL_PCLK_INV : 0);
+
+	/* disable VDAC */
+	CFG_WRITE(VIDEO_OUT_CFG_VDAC_CFG, 0);
+
+	/* enable DPI interrupt */
+	CFG_WRITE(VIDEO_OUT_CFG_INTE, VIDEO_OUT_CFG_INTE_DPI);
+}
+
+void rp1dpi_vidout_poweroff(struct rp1_dpi *dpi)
+{
+	/* disable DPI interrupt */
+	CFG_WRITE(VIDEO_OUT_CFG_INTE, 0);
+
+	/* Ensure VDAC is turned off; power down DPI,VEC memories */
+	CFG_WRITE(VIDEO_OUT_CFG_VDAC_CFG, 0);
+	CFG_WRITE(VIDEO_OUT_CFG_MEM_PD, VIDEO_OUT_CFG_MEM_PD_BITS);
+}
diff --git a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c
new file mode 100644
index 000000000000..99d55e866e22
--- /dev/null
+++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_hw.c
@@ -0,0 +1,668 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * DRM Driver for DPI output on Raspberry Pi RP1
+ *
+ * Copyright (c) 2023 Raspberry Pi Limited.
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/media-bus-format.h>
+#include <linux/platform_device.h>
+#include <linux/printk.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_print.h>
+#include <drm/drm_vblank.h>
+
+#include "rp1_dpi.h"
+
+// --- DPI DMA REGISTERS ---
+
+// Control
+#define DPI_DMA_CONTROL				      0x0
+#define DPI_DMA_CONTROL_ARM_SHIFT		      0
+#define DPI_DMA_CONTROL_ARM_MASK		      BIT(DPI_DMA_CONTROL_ARM_SHIFT)
+#define DPI_DMA_CONTROL_ALIGN16_SHIFT		      2
+#define DPI_DMA_CONTROL_ALIGN16_MASK		      BIT(DPI_DMA_CONTROL_ALIGN16_SHIFT)
+#define DPI_DMA_CONTROL_AUTO_REPEAT_SHIFT	      1
+#define DPI_DMA_CONTROL_AUTO_REPEAT_MASK	      BIT(DPI_DMA_CONTROL_AUTO_REPEAT_SHIFT)
+#define DPI_DMA_CONTROL_HIGH_WATER_SHIFT	      3
+#define DPI_DMA_CONTROL_HIGH_WATER_MASK		      (0x1FF << DPI_DMA_CONTROL_HIGH_WATER_SHIFT)
+#define DPI_DMA_CONTROL_DEN_POL_SHIFT		      12
+#define DPI_DMA_CONTROL_DEN_POL_MASK		      BIT(DPI_DMA_CONTROL_DEN_POL_SHIFT)
+#define DPI_DMA_CONTROL_HSYNC_POL_SHIFT		      13
+#define DPI_DMA_CONTROL_HSYNC_POL_MASK		      BIT(DPI_DMA_CONTROL_HSYNC_POL_SHIFT)
+#define DPI_DMA_CONTROL_VSYNC_POL_SHIFT		      14
+#define DPI_DMA_CONTROL_VSYNC_POL_MASK		      BIT(DPI_DMA_CONTROL_VSYNC_POL_SHIFT)
+#define DPI_DMA_CONTROL_COLORM_SHIFT		      15
+#define DPI_DMA_CONTROL_COLORM_MASK		      BIT(DPI_DMA_CONTROL_COLORM_SHIFT)
+#define DPI_DMA_CONTROL_SHUTDN_SHIFT		      16
+#define DPI_DMA_CONTROL_SHUTDN_MASK		      BIT(DPI_DMA_CONTROL_SHUTDN_SHIFT)
+#define DPI_DMA_CONTROL_HBP_EN_SHIFT		      17
+#define DPI_DMA_CONTROL_HBP_EN_MASK		      BIT(DPI_DMA_CONTROL_HBP_EN_SHIFT)
+#define DPI_DMA_CONTROL_HFP_EN_SHIFT		      18
+#define DPI_DMA_CONTROL_HFP_EN_MASK		      BIT(DPI_DMA_CONTROL_HFP_EN_SHIFT)
+#define DPI_DMA_CONTROL_VBP_EN_SHIFT		      19
+#define DPI_DMA_CONTROL_VBP_EN_MASK		      BIT(DPI_DMA_CONTROL_VBP_EN_SHIFT)
+#define DPI_DMA_CONTROL_VFP_EN_SHIFT		      20
+#define DPI_DMA_CONTROL_VFP_EN_MASK		      BIT(DPI_DMA_CONTROL_VFP_EN_SHIFT)
+#define DPI_DMA_CONTROL_HSYNC_EN_SHIFT		      21
+#define DPI_DMA_CONTROL_HSYNC_EN_MASK		      BIT(DPI_DMA_CONTROL_HSYNC_EN_SHIFT)
+#define DPI_DMA_CONTROL_VSYNC_EN_SHIFT		      22
+#define DPI_DMA_CONTROL_VSYNC_EN_MASK		      BIT(DPI_DMA_CONTROL_VSYNC_EN_SHIFT)
+#define DPI_DMA_CONTROL_FORCE_IMMED_SHIFT	      23
+#define DPI_DMA_CONTROL_FORCE_IMMED_MASK	      BIT(DPI_DMA_CONTROL_FORCE_IMMED_SHIFT)
+#define DPI_DMA_CONTROL_FORCE_DRAIN_SHIFT	      24
+#define DPI_DMA_CONTROL_FORCE_DRAIN_MASK	      BIT(DPI_DMA_CONTROL_FORCE_DRAIN_SHIFT)
+#define DPI_DMA_CONTROL_FORCE_EMPTY_SHIFT	      25
+#define DPI_DMA_CONTROL_FORCE_EMPTY_MASK	      BIT(DPI_DMA_CONTROL_FORCE_EMPTY_SHIFT)
+
+// IRQ_ENABLES
+#define DPI_DMA_IRQ_EN				      0x04
+#define DPI_DMA_IRQ_EN_DMA_READY_SHIFT		      0
+#define DPI_DMA_IRQ_EN_DMA_READY_MASK		      BIT(DPI_DMA_IRQ_EN_DMA_READY_SHIFT)
+#define DPI_DMA_IRQ_EN_UNDERFLOW_SHIFT		      1
+#define DPI_DMA_IRQ_EN_UNDERFLOW_MASK		      BIT(DPI_DMA_IRQ_EN_UNDERFLOW_SHIFT)
+#define DPI_DMA_IRQ_EN_FRAME_START_SHIFT	      2
+#define DPI_DMA_IRQ_EN_FRAME_START_MASK		      BIT(DPI_DMA_IRQ_EN_FRAME_START_SHIFT)
+#define DPI_DMA_IRQ_EN_AFIFO_EMPTY_SHIFT	      3
+#define DPI_DMA_IRQ_EN_AFIFO_EMPTY_MASK		      BIT(DPI_DMA_IRQ_EN_AFIFO_EMPTY_SHIFT)
+#define DPI_DMA_IRQ_EN_TE_SHIFT			      4
+#define DPI_DMA_IRQ_EN_TE_MASK			      BIT(DPI_DMA_IRQ_EN_TE_SHIFT)
+#define DPI_DMA_IRQ_EN_ERROR_SHIFT		      5
+#define DPI_DMA_IRQ_EN_ERROR_MASK		      BIT(DPI_DMA_IRQ_EN_ERROR_SHIFT)
+#define DPI_DMA_IRQ_EN_MATCH_SHIFT		      6
+#define DPI_DMA_IRQ_EN_MATCH_MASK		      BIT(DPI_DMA_IRQ_EN_MATCH_SHIFT)
+#define DPI_DMA_IRQ_EN_MATCH_LINE_SHIFT		      16
+#define DPI_DMA_IRQ_EN_MATCH_LINE_MASK		      (0xFFF << DPI_DMA_IRQ_EN_MATCH_LINE_SHIFT)
+
+// IRQ_FLAGS
+#define DPI_DMA_IRQ_FLAGS			      0x08
+#define DPI_DMA_IRQ_FLAGS_DMA_READY_SHIFT	      0
+#define DPI_DMA_IRQ_FLAGS_DMA_READY_MASK	      BIT(DPI_DMA_IRQ_FLAGS_DMA_READY_SHIFT)
+#define DPI_DMA_IRQ_FLAGS_UNDERFLOW_SHIFT	      1
+#define DPI_DMA_IRQ_FLAGS_UNDERFLOW_MASK	      BIT(DPI_DMA_IRQ_FLAGS_UNDERFLOW_SHIFT)
+#define DPI_DMA_IRQ_FLAGS_FRAME_START_SHIFT	      2
+#define DPI_DMA_IRQ_FLAGS_FRAME_START_MASK	      BIT(DPI_DMA_IRQ_FLAGS_FRAME_START_SHIFT)
+#define DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_SHIFT	      3
+#define DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_MASK	      BIT(DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_SHIFT)
+#define DPI_DMA_IRQ_FLAGS_TE_SHIFT		      4
+#define DPI_DMA_IRQ_FLAGS_TE_MASK		      BIT(DPI_DMA_IRQ_FLAGS_TE_SHIFT)
+#define DPI_DMA_IRQ_FLAGS_ERROR_SHIFT		      5
+#define DPI_DMA_IRQ_FLAGS_ERROR_MASK		      BIT(DPI_DMA_IRQ_FLAGS_ERROR_SHIFT)
+#define DPI_DMA_IRQ_FLAGS_MATCH_SHIFT		      6
+#define DPI_DMA_IRQ_FLAGS_MATCH_MASK		      BIT(DPI_DMA_IRQ_FLAGS_MATCH_SHIFT)
+
+// QOS
+#define DPI_DMA_QOS				      0xC
+#define DPI_DMA_QOS_DQOS_SHIFT			      0
+#define DPI_DMA_QOS_DQOS_MASK			      (0xF << DPI_DMA_QOS_DQOS_SHIFT)
+#define DPI_DMA_QOS_ULEV_SHIFT			      4
+#define DPI_DMA_QOS_ULEV_MASK			      (0xF << DPI_DMA_QOS_ULEV_SHIFT)
+#define DPI_DMA_QOS_UQOS_SHIFT			      8
+#define DPI_DMA_QOS_UQOS_MASK			      (0xF << DPI_DMA_QOS_UQOS_SHIFT)
+#define DPI_DMA_QOS_LLEV_SHIFT			      12
+#define DPI_DMA_QOS_LLEV_MASK			      (0xF << DPI_DMA_QOS_LLEV_SHIFT)
+#define DPI_DMA_QOS_LQOS_SHIFT			      16
+#define DPI_DMA_QOS_LQOS_MASK			      (0xF << DPI_DMA_QOS_LQOS_SHIFT)
+
+// Panics
+#define DPI_DMA_PANICS				     0x38
+#define DPI_DMA_PANICS_UPPER_COUNT_SHIFT	     0
+#define DPI_DMA_PANICS_UPPER_COUNT_MASK		     \
+				(0x0000FFFF << DPI_DMA_PANICS_UPPER_COUNT_SHIFT)
+#define DPI_DMA_PANICS_LOWER_COUNT_SHIFT	     16
+#define DPI_DMA_PANICS_LOWER_COUNT_MASK		     \
+				(0x0000FFFF << DPI_DMA_PANICS_LOWER_COUNT_SHIFT)
+
+// DMA Address Lower:
+#define DPI_DMA_DMA_ADDR_L			     0x10
+
+// DMA Address Upper:
+#define DPI_DMA_DMA_ADDR_H			     0x40
+
+// DMA stride
+#define DPI_DMA_DMA_STRIDE			     0x14
+
+// Visible Area
+#define DPI_DMA_VISIBLE_AREA			     0x18
+#define DPI_DMA_VISIBLE_AREA_ROWSM1_SHIFT     0
+#define DPI_DMA_VISIBLE_AREA_ROWSM1_MASK     (0x0FFF << DPI_DMA_VISIBLE_AREA_ROWSM1_SHIFT)
+#define DPI_DMA_VISIBLE_AREA_COLSM1_SHIFT    16
+#define DPI_DMA_VISIBLE_AREA_COLSM1_MASK     (0x0FFF << DPI_DMA_VISIBLE_AREA_COLSM1_SHIFT)
+
+// Sync width
+#define DPI_DMA_SYNC_WIDTH   0x1C
+#define DPI_DMA_SYNC_WIDTH_ROWSM1_SHIFT	 0
+#define DPI_DMA_SYNC_WIDTH_ROWSM1_MASK	 (0x0FFF << DPI_DMA_SYNC_WIDTH_ROWSM1_SHIFT)
+#define DPI_DMA_SYNC_WIDTH_COLSM1_SHIFT	 16
+#define DPI_DMA_SYNC_WIDTH_COLSM1_MASK	 (0x0FFF << DPI_DMA_SYNC_WIDTH_COLSM1_SHIFT)
+
+// Back porch
+#define DPI_DMA_BACK_PORCH   0x20
+#define DPI_DMA_BACK_PORCH_ROWSM1_SHIFT	 0
+#define DPI_DMA_BACK_PORCH_ROWSM1_MASK	 (0x0FFF << DPI_DMA_BACK_PORCH_ROWSM1_SHIFT)
+#define DPI_DMA_BACK_PORCH_COLSM1_SHIFT	 16
+#define DPI_DMA_BACK_PORCH_COLSM1_MASK	 (0x0FFF << DPI_DMA_BACK_PORCH_COLSM1_SHIFT)
+
+// Front porch
+#define DPI_DMA_FRONT_PORCH  0x24
+#define DPI_DMA_FRONT_PORCH_ROWSM1_SHIFT     0
+#define DPI_DMA_FRONT_PORCH_ROWSM1_MASK	 (0x0FFF << DPI_DMA_FRONT_PORCH_ROWSM1_SHIFT)
+#define DPI_DMA_FRONT_PORCH_COLSM1_SHIFT     16
+#define DPI_DMA_FRONT_PORCH_COLSM1_MASK	 (0x0FFF << DPI_DMA_FRONT_PORCH_COLSM1_SHIFT)
+
+// Input masks
+#define DPI_DMA_IMASK	 0x2C
+#define DPI_DMA_IMASK_R_SHIFT	 0
+#define DPI_DMA_IMASK_R_MASK	 (0x3FF << DPI_DMA_IMASK_R_SHIFT)
+#define DPI_DMA_IMASK_G_SHIFT	 10
+#define DPI_DMA_IMASK_G_MASK	 (0x3FF << DPI_DMA_IMASK_G_SHIFT)
+#define DPI_DMA_IMASK_B_SHIFT	 20
+#define DPI_DMA_IMASK_B_MASK	 (0x3FF << DPI_DMA_IMASK_B_SHIFT)
+
+// Output Masks
+#define DPI_DMA_OMASK	 0x30
+#define DPI_DMA_OMASK_R_SHIFT	 0
+#define DPI_DMA_OMASK_R_MASK	 (0x3FF << DPI_DMA_OMASK_R_SHIFT)
+#define DPI_DMA_OMASK_G_SHIFT	 10
+#define DPI_DMA_OMASK_G_MASK	 (0x3FF << DPI_DMA_OMASK_G_SHIFT)
+#define DPI_DMA_OMASK_B_SHIFT	 20
+#define DPI_DMA_OMASK_B_MASK	 (0x3FF << DPI_DMA_OMASK_B_SHIFT)
+
+// Shifts
+#define DPI_DMA_SHIFT	 0x28
+#define DPI_DMA_SHIFT_IR_SHIFT	 0
+#define DPI_DMA_SHIFT_IR_MASK	 (0x1F << DPI_DMA_SHIFT_IR_SHIFT)
+#define DPI_DMA_SHIFT_IG_SHIFT	 5
+#define DPI_DMA_SHIFT_IG_MASK	 (0x1F << DPI_DMA_SHIFT_IG_SHIFT)
+#define DPI_DMA_SHIFT_IB_SHIFT	 10
+#define DPI_DMA_SHIFT_IB_MASK	 (0x1F << DPI_DMA_SHIFT_IB_SHIFT)
+#define DPI_DMA_SHIFT_OR_SHIFT	 15
+#define DPI_DMA_SHIFT_OR_MASK	 (0x1F << DPI_DMA_SHIFT_OR_SHIFT)
+#define DPI_DMA_SHIFT_OG_SHIFT	 20
+#define DPI_DMA_SHIFT_OG_MASK	 (0x1F << DPI_DMA_SHIFT_OG_SHIFT)
+#define DPI_DMA_SHIFT_OB_SHIFT	 25
+#define DPI_DMA_SHIFT_OB_MASK	 (0x1F << DPI_DMA_SHIFT_OB_SHIFT)
+
+// Scaling
+#define DPI_DMA_RGBSZ	 0x34
+#define DPI_DMA_RGBSZ_BPP_SHIFT	 16
+#define DPI_DMA_RGBSZ_BPP_MASK	 (0x3 << DPI_DMA_RGBSZ_BPP_SHIFT)
+#define DPI_DMA_RGBSZ_R_SHIFT	 0
+#define DPI_DMA_RGBSZ_R_MASK	 (0xF << DPI_DMA_RGBSZ_R_SHIFT)
+#define DPI_DMA_RGBSZ_G_SHIFT	 4
+#define DPI_DMA_RGBSZ_G_MASK	 (0xF << DPI_DMA_RGBSZ_G_SHIFT)
+#define DPI_DMA_RGBSZ_B_SHIFT	 8
+#define DPI_DMA_RGBSZ_B_MASK	 (0xF << DPI_DMA_RGBSZ_B_SHIFT)
+
+// Status
+#define DPI_DMA_STATUS  0x3c
+
+#define BITS(field, val) FIELD_PREP((field ## _MASK), val)
+
+static unsigned int rp1dpi_hw_read(struct rp1_dpi *dpi, unsigned int reg)
+{
+	void __iomem *addr = dpi->hw_base[RP1DPI_HW_BLOCK_DPI] + reg;
+
+	return readl(addr);
+}
+
+static void rp1dpi_hw_write(struct rp1_dpi *dpi, unsigned int reg, unsigned int val)
+{
+	void __iomem *addr = dpi->hw_base[RP1DPI_HW_BLOCK_DPI] + reg;
+
+	writel(val, addr);
+}
+
+int rp1dpi_hw_busy(struct rp1_dpi *dpi)
+{
+	return (rp1dpi_hw_read(dpi, DPI_DMA_STATUS) & 0xF8F) ? 1 : 0;
+}
+
+/*
+ * Table of supported input (in-memory/DMA) pixel formats.
+ *
+ * RP1 DPI describes RGB components in terms of their MS bit position, a 10-bit
+ * left-aligned bit-mask, and an optional right-shift-and-OR used for scaling.
+ * To make it easier to permute R, G and B components, we re-pack these fields
+ * into 32-bit code-words, which don't themselves correspond to any register.
+ */
+
+#define RGB_CODE(scale, shift, mask) (((scale) << 24) | ((shift) << 16) | (mask))
+#define RGB_SCALE(c) ((c) >> 24)
+#define RGB_SHIFT(c) (((c) >> 16) & 31)
+#define RGB_MASK(c) ((c) & 0x3ff)
+
+struct rp1dpi_ipixfmt {
+	u32 format;       /* DRM format code                          */
+	u32 rgb_code[3];  /* (width&7), MS bit position, 10-bit mask  */
+	u32 bpp;          /* Bytes per pixel minus one                */
+};
+
+static const struct rp1dpi_ipixfmt my_formats[] = {
+	{
+		.format = DRM_FORMAT_XRGB8888,
+		.rgb_code = {
+			RGB_CODE(0, 23, 0x3fc),
+			RGB_CODE(0, 15, 0x3fc),
+			RGB_CODE(0, 7, 0x3fc),
+		},
+		.bpp = 3,
+	},
+	{
+		.format = DRM_FORMAT_XBGR8888,
+		.rgb_code = {
+			RGB_CODE(0, 7, 0x3fc),
+			RGB_CODE(0, 15, 0x3fc),
+			RGB_CODE(0, 23, 0x3fc),
+		},
+		.bpp = 3,
+	},
+	{
+		.format = DRM_FORMAT_ARGB8888,
+		.rgb_code = {
+			RGB_CODE(0, 23, 0x3fc),
+			RGB_CODE(0, 15, 0x3fc),
+			RGB_CODE(0, 7, 0x3fc),
+		},
+		.bpp = 3,
+	},
+	{
+		.format = DRM_FORMAT_ABGR8888,
+		.rgb_code = {
+			RGB_CODE(0, 7, 0x3fc),
+			RGB_CODE(0, 15, 0x3fc),
+			RGB_CODE(0, 23, 0x3fc),
+		},
+		.bpp = 3,
+	},
+	{
+		.format = DRM_FORMAT_RGB888,
+		.rgb_code = {
+			RGB_CODE(0, 23, 0x3fc),
+			RGB_CODE(0, 15, 0x3fc),
+			RGB_CODE(0, 7, 0x3fc),
+		},
+		.bpp = 2,
+	},
+	{
+		.format = DRM_FORMAT_BGR888,
+		.rgb_code = {
+			RGB_CODE(0, 7, 0x3fc),
+			RGB_CODE(0, 15, 0x3fc),
+			RGB_CODE(0, 23, 0x3fc),
+		},
+		.bpp = 2,
+	},
+	{
+		.format = DRM_FORMAT_RGB565,
+		.rgb_code = {
+			RGB_CODE(5, 15, 0x3e0),
+			RGB_CODE(6, 10, 0x3f0),
+			RGB_CODE(5, 4, 0x3e0),
+		},
+		.bpp = 1,
+	},
+};
+
+#define IMASK_RGB(r, g, b)  (FIELD_PREP_CONST(DPI_DMA_IMASK_R_MASK, r)  | \
+			     FIELD_PREP_CONST(DPI_DMA_IMASK_G_MASK, g)  | \
+			     FIELD_PREP_CONST(DPI_DMA_IMASK_B_MASK, b))
+#define OMASK_RGB(r, g, b)  (FIELD_PREP_CONST(DPI_DMA_OMASK_R_MASK, r)  | \
+			     FIELD_PREP_CONST(DPI_DMA_OMASK_G_MASK, g)  | \
+			     FIELD_PREP_CONST(DPI_DMA_OMASK_B_MASK, b))
+#define ISHIFT_RGB(r, g, b) (FIELD_PREP_CONST(DPI_DMA_SHIFT_IR_MASK, r) | \
+			     FIELD_PREP_CONST(DPI_DMA_SHIFT_IG_MASK, g) | \
+			     FIELD_PREP_CONST(DPI_DMA_SHIFT_IB_MASK, b))
+#define OSHIFT_RGB(r, g, b) (FIELD_PREP_CONST(DPI_DMA_SHIFT_OR_MASK, r) | \
+			     FIELD_PREP_CONST(DPI_DMA_SHIFT_OG_MASK, g) | \
+			     FIELD_PREP_CONST(DPI_DMA_SHIFT_OB_MASK, b))
+
+/*
+ * Function to update *shift with output positions, and return output RGB masks.
+ * By the time we get here, RGB order has been normalized to RGB (R most significant).
+ * Note that an internal bus is 30 bits wide: bits [21:20], [11:10], [1:0] are dropped.
+ * This makes the packed RGB5656 and RGB666 formats problematic, as colour components
+ * need to straddle the gaps; we mitigate this by hijacking input masks and scaling.
+ */
+static u32 set_output_format(u32 bus_format, u32 *shift, u32 *imask, u32 *rgbsz)
+{
+	switch (bus_format) {
+	case MEDIA_BUS_FMT_RGB565_1X16:
+		if (*shift == ISHIFT_RGB(15, 10, 4)) {
+			/* When framebuffer is RGB565, we can output RGB565 */
+			*shift = ISHIFT_RGB(15, 7, 0) | OSHIFT_RGB(19, 9, 0);
+			*imask = IMASK_RGB(0x3fc, 0x3fc, 0);
+			*rgbsz &= DPI_DMA_RGBSZ_BPP_MASK;
+			return OMASK_RGB(0x3fc, 0x3fc, 0);
+		}
+
+		/* due to a HW limitation, bit-depth is effectively RGB535 */
+		*shift |= OSHIFT_RGB(19, 14, 6);
+		*imask &= IMASK_RGB(0x3e0, 0x380, 0x3e0);
+		*rgbsz = BITS(DPI_DMA_RGBSZ_G, 5) | (*rgbsz & DPI_DMA_RGBSZ_BPP_MASK);
+		return OMASK_RGB(0x3e0, 0x39c, 0x3e0);
+
+	case MEDIA_BUS_FMT_RGB666_1X18:
+	case MEDIA_BUS_FMT_BGR666_1X18:
+		/* due to a HW limitation, bit-depth is effectively RGB444 */
+		*shift |= OSHIFT_RGB(23, 15, 7);
+		*imask = IMASK_RGB(0x3c0, 0x3c0, 0x3c0);
+		*rgbsz = BITS(DPI_DMA_RGBSZ_R, 2) | (*rgbsz & DPI_DMA_RGBSZ_BPP_MASK);
+		return OMASK_RGB(0x330, 0x3c0, 0x3c0);
+
+	case MEDIA_BUS_FMT_RGB888_1X24:
+	case MEDIA_BUS_FMT_BGR888_1X24:
+	case MEDIA_BUS_FMT_RGB101010_1X30:
+		/* The full 24 bits can be output. Note that RP1's internal wiring means
+		 * that 8.8.8 to GPIO pads can share with 10.10.10 to the onboard VDAC.
+		 */
+		*shift |= OSHIFT_RGB(29, 19, 9);
+		return OMASK_RGB(0x3fc, 0x3fc, 0x3fc);
+
+	case MEDIA_BUS_FMT_RGB565_1X24_CPADHI:
+		/* This should match Raspberry Pi legacy "mode 3" */
+		*shift |= OSHIFT_RGB(26, 17, 6);
+		*rgbsz &= DPI_DMA_RGBSZ_BPP_MASK;
+		return OMASK_RGB(0x3e0, 0x3f0, 0x3e0);
+
+	default:
+		/* RGB666_1x24_CPADHI, BGR666_1X24_CPADHI and "mode 4" formats */
+		*shift |= OSHIFT_RGB(27, 17, 7);
+		*rgbsz &= DPI_DMA_RGBSZ_BPP_MASK;
+		return OMASK_RGB(0x3f0, 0x3f0, 0x3f0);
+	}
+}
+
+#define BUS_FMT_IS_BGR(fmt) (				       \
+		((fmt) == MEDIA_BUS_FMT_BGR666_1X18)        || \
+		((fmt) == MEDIA_BUS_FMT_BGR666_1X24_CPADHI) || \
+		((fmt) == MEDIA_BUS_FMT_BGR888_1X24))
+
+void rp1dpi_hw_setup(struct rp1_dpi *dpi,
+		     u32 in_format, u32 bus_format, bool de_inv,
+		    struct drm_display_mode const *mode)
+{
+	u32 shift, imask, omask, rgbsz, vctrl;
+	u32 rgb_code[3];
+	int order, i;
+
+	drm_info(&dpi->drm,
+		 "in_fmt=\'%c%c%c%c\' bus_fmt=0x%x mode=%dx%d total=%dx%d%s %dkHz %cH%cV%cDE%cCK",
+		 in_format, in_format >> 8, in_format >> 16, in_format >> 24, bus_format,
+		 mode->hdisplay, mode->vdisplay,
+		 mode->htotal, mode->vtotal,
+		 (mode->flags & DRM_MODE_FLAG_INTERLACE) ? "i" : "",
+		 mode->clock,
+		 (mode->flags & DRM_MODE_FLAG_NHSYNC) ? '-' : '+',
+		 (mode->flags & DRM_MODE_FLAG_NVSYNC) ? '-' : '+',
+		 de_inv ? '-' : '+',
+		 dpi->clk_inv ? '-' : '+');
+
+	/* Look up the input (in-memory) pixel format */
+	for (i = 0; i < ARRAY_SIZE(my_formats); ++i) {
+		if (my_formats[i].format == in_format)
+			break;
+	}
+	if (i >= ARRAY_SIZE(my_formats)) {
+		pr_err("%s: bad input format\n", __func__);
+		i = ARRAY_SIZE(my_formats) - 1;
+	}
+
+	/*
+	 * Although these RGB orderings refer to the output (DPI bus) format,
+	 * here we permute the *input* components. After this point, "Red"
+	 * will be most significant (highest numbered GPIOs), regardless
+	 * of rgb_order or bus_format. This simplifies later workarounds.
+	 */
+	order = dpi->rgb_order_override;
+	if (order == RP1DPI_ORDER_UNCHANGED)
+		order = BUS_FMT_IS_BGR(bus_format) ? RP1DPI_ORDER_BGR : RP1DPI_ORDER_RGB;
+	rgb_code[0] = my_formats[i].rgb_code[order & 3];
+	rgb_code[1] = my_formats[i].rgb_code[(order >> 8) & 3];
+	rgb_code[2] = my_formats[i].rgb_code[(order >> 16) & 3];
+	rgbsz = FIELD_PREP(DPI_DMA_RGBSZ_BPP_MASK, my_formats[i].bpp) |
+		FIELD_PREP(DPI_DMA_RGBSZ_R_MASK, RGB_SCALE(rgb_code[0])) |
+		FIELD_PREP(DPI_DMA_RGBSZ_G_MASK, RGB_SCALE(rgb_code[1])) |
+		FIELD_PREP(DPI_DMA_RGBSZ_B_MASK, RGB_SCALE(rgb_code[2]));
+	shift = FIELD_PREP(DPI_DMA_SHIFT_IR_MASK, RGB_SHIFT(rgb_code[0])) |
+		FIELD_PREP(DPI_DMA_SHIFT_IG_MASK, RGB_SHIFT(rgb_code[1])) |
+		FIELD_PREP(DPI_DMA_SHIFT_IB_MASK, RGB_SHIFT(rgb_code[2]));
+	imask = FIELD_PREP(DPI_DMA_IMASK_R_MASK, RGB_MASK(rgb_code[0])) |
+		FIELD_PREP(DPI_DMA_IMASK_G_MASK, RGB_MASK(rgb_code[1])) |
+		FIELD_PREP(DPI_DMA_IMASK_B_MASK, RGB_MASK(rgb_code[2]));
+	omask = set_output_format(bus_format, &shift, &imask, &rgbsz);
+
+	/*
+	 * Configure all DPI/DMA block registers, except base address.
+	 * DMA will not actually start until a FB base address is specified
+	 * using rp1dpi_hw_update().
+	 */
+	rp1dpi_hw_write(dpi, DPI_DMA_IMASK, imask);
+	rp1dpi_hw_write(dpi, DPI_DMA_OMASK, omask);
+	rp1dpi_hw_write(dpi, DPI_DMA_SHIFT, shift);
+	rp1dpi_hw_write(dpi, DPI_DMA_RGBSZ, rgbsz);
+
+	rp1dpi_hw_write(dpi, DPI_DMA_QOS,
+			BITS(DPI_DMA_QOS_DQOS, 0x0) |
+			BITS(DPI_DMA_QOS_ULEV, 0xb) |
+			BITS(DPI_DMA_QOS_UQOS, 0x2) |
+			BITS(DPI_DMA_QOS_LLEV, 0x8) |
+			BITS(DPI_DMA_QOS_LQOS, 0x7));
+
+	if (!(mode->flags & DRM_MODE_FLAG_INTERLACE)) {
+		rp1dpi_hw_write(dpi, DPI_DMA_VISIBLE_AREA,
+				BITS(DPI_DMA_VISIBLE_AREA_ROWSM1, mode->vdisplay - 1) |
+				BITS(DPI_DMA_VISIBLE_AREA_COLSM1, mode->hdisplay - 1));
+
+		rp1dpi_hw_write(dpi, DPI_DMA_SYNC_WIDTH,
+				BITS(DPI_DMA_SYNC_WIDTH_ROWSM1,
+				     mode->vsync_end - mode->vsync_start - 1) |
+				BITS(DPI_DMA_SYNC_WIDTH_COLSM1,
+				     mode->hsync_end - mode->hsync_start - 1));
+
+		/* In these registers, "back porch" time includes sync width */
+		rp1dpi_hw_write(dpi, DPI_DMA_BACK_PORCH,
+				BITS(DPI_DMA_BACK_PORCH_ROWSM1,
+				     mode->vtotal - mode->vsync_start - 1) |
+				BITS(DPI_DMA_BACK_PORCH_COLSM1,
+				     mode->htotal - mode->hsync_start - 1));
+
+		rp1dpi_hw_write(dpi, DPI_DMA_FRONT_PORCH,
+				BITS(DPI_DMA_FRONT_PORCH_ROWSM1,
+				     mode->vsync_start - mode->vdisplay - 1) |
+				BITS(DPI_DMA_FRONT_PORCH_COLSM1,
+				     mode->hsync_start - mode->hdisplay - 1));
+
+		vctrl = BITS(DPI_DMA_CONTROL_VSYNC_POL, !!(mode->flags & DRM_MODE_FLAG_NVSYNC)) |
+			BITS(DPI_DMA_CONTROL_VBP_EN, (mode->vtotal != mode->vsync_start))       |
+			BITS(DPI_DMA_CONTROL_VFP_EN, (mode->vsync_start != mode->vdisplay))     |
+			BITS(DPI_DMA_CONTROL_VSYNC_EN, (mode->vsync_end != mode->vsync_start));
+
+		dpi->interlaced = false;
+	} else {
+		/*
+		 * Experimental interlace support
+		 *
+		 * RP1 DPI hardware wasn't designed to support interlace, but lets us change
+		 * both the VFP line count and the next DMA address while running. That allows
+		 * pixel data to be correctly timed for interlace, but VSYNC remains wrong.
+		 *
+		 * It is necessary to use external hardware (such as PIO) to regenerate VSYNC
+		 * based on HSYNC, DE (which *must* both be mapped to GPIOs 1, 3 respectively).
+		 * This driver includes a PIO program to do that, when DE is enabled.
+		 *
+		 * An alternative fixup is to synthesize CSYNC from HSYNC and modified-VSYNC.
+		 * We can't do this and make VSYNC at the same time; DPI's VSYNC is replaced
+		 * by a "helper signal" that pulses low for 1 or 2 scan-lines, starting 2.0 or
+		 * 2.5 scan-lines respectively before nominal VSYNC start.
+		 */
+		int vact  = mode->vdisplay >> 1; /* visible lines per field. Can't do half-lines */
+		int vtot0 = mode->vtotal >> 1;   /* vtotal should always be odd when interlaced. */
+		int vfp0  = (mode->vsync_start >= mode->vdisplay + 4) ?
+			((mode->vsync_start - mode->vdisplay - 2) >> 1) : 1;
+		int vbp   = max(0, vtot0 - vact - vfp0);
+
+		rp1dpi_hw_write(dpi, DPI_DMA_VISIBLE_AREA,
+				BITS(DPI_DMA_VISIBLE_AREA_ROWSM1, vact - 1) |
+				BITS(DPI_DMA_VISIBLE_AREA_COLSM1, mode->hdisplay - 1));
+
+		rp1dpi_hw_write(dpi, DPI_DMA_SYNC_WIDTH,
+				BITS(DPI_DMA_SYNC_WIDTH_ROWSM1, vtot0 - 2) |
+				BITS(DPI_DMA_SYNC_WIDTH_COLSM1,
+				     mode->hsync_end - mode->hsync_start - 1));
+
+		rp1dpi_hw_write(dpi, DPI_DMA_BACK_PORCH,
+				BITS(DPI_DMA_BACK_PORCH_ROWSM1, vbp - 1) |
+				BITS(DPI_DMA_BACK_PORCH_COLSM1,
+				     mode->htotal - mode->hsync_start - 1));
+
+		dpi->shorter_front_porch =
+			BITS(DPI_DMA_FRONT_PORCH_ROWSM1, vfp0 - 1) |
+			BITS(DPI_DMA_FRONT_PORCH_COLSM1,
+			     mode->hsync_start - mode->hdisplay - 1);
+		rp1dpi_hw_write(dpi, DPI_DMA_FRONT_PORCH, dpi->shorter_front_porch);
+
+		vctrl = BITS(DPI_DMA_CONTROL_VSYNC_POL, 0)      |
+			BITS(DPI_DMA_CONTROL_VBP_EN, (vbp > 0)) |
+			BITS(DPI_DMA_CONTROL_VFP_EN, 1)         |
+			BITS(DPI_DMA_CONTROL_VSYNC_EN, 1);
+
+		dpi->interlaced = true;
+	}
+	dpi->lower_field_flag = false;
+	dpi->last_dma_addr = 0;
+
+	rp1dpi_hw_write(dpi, DPI_DMA_IRQ_FLAGS, -1);
+	rp1dpi_hw_vblank_ctrl(dpi, 1);
+
+	i = rp1dpi_hw_busy(dpi);
+	if (i)
+		pr_warn("%s: Unexpectedly busy at start!", __func__);
+
+	rp1dpi_hw_write(dpi, DPI_DMA_CONTROL,
+			vctrl                                  |
+			BITS(DPI_DMA_CONTROL_ARM,          !i) |
+			BITS(DPI_DMA_CONTROL_AUTO_REPEAT,   1) |
+			BITS(DPI_DMA_CONTROL_HIGH_WATER,  448) |
+			BITS(DPI_DMA_CONTROL_DEN_POL,  de_inv) |
+			BITS(DPI_DMA_CONTROL_HSYNC_POL, !!(mode->flags & DRM_MODE_FLAG_NHSYNC)) |
+			BITS(DPI_DMA_CONTROL_HBP_EN,    (mode->htotal != mode->hsync_end))      |
+			BITS(DPI_DMA_CONTROL_HFP_EN,    (mode->hsync_start != mode->hdisplay))  |
+			BITS(DPI_DMA_CONTROL_HSYNC_EN,  (mode->hsync_end != mode->hsync_start)));
+}
+
+void rp1dpi_hw_update(struct rp1_dpi *dpi, dma_addr_t addr, u32 offset, u32 stride)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dpi->hw_lock, flags);
+
+	/*
+	 * Update STRIDE, DMAH and DMAL only. When called after rp1dpi_hw_setup(),
+	 * DMA starts immediately; if already running, the buffer will flip at
+	 * the next vertical sync event. In interlaced mode, we need to adjust
+	 * the address and stride to display only the current field, saving
+	 * the original address (so it can be flipped for subsequent fields).
+	 */
+	addr += offset;
+	dpi->last_dma_addr = addr;
+	dpi->last_stride = stride;
+	if (dpi->interlaced) {
+		if (dpi->lower_field_flag)
+			addr += stride;
+		stride *= 2;
+	}
+	rp1dpi_hw_write(dpi, DPI_DMA_DMA_STRIDE, stride);
+	rp1dpi_hw_write(dpi, DPI_DMA_DMA_ADDR_H, addr >> 32);
+	rp1dpi_hw_write(dpi, DPI_DMA_DMA_ADDR_L, addr & 0xFFFFFFFFu);
+
+	spin_unlock_irqrestore(&dpi->hw_lock, flags);
+}
+
+void rp1dpi_hw_stop(struct rp1_dpi *dpi)
+{
+	u32 ctrl;
+	unsigned long flags;
+
+	/*
+	 * Stop DMA by turning off Auto-Repeat (and disable S/W field-flip),
+	 * then wait up to 100ms for the current and any queued frame to end.
+	 * (There is a "force drain" flag, but it can leave DPI in a broken
+	 * state which prevents it from restarting; it's safer to wait.)
+	 */
+	spin_lock_irqsave(&dpi->hw_lock, flags);
+	dpi->last_dma_addr = 0;
+	reinit_completion(&dpi->finished);
+	ctrl = rp1dpi_hw_read(dpi, DPI_DMA_CONTROL);
+	ctrl &= ~(DPI_DMA_CONTROL_ARM_MASK | DPI_DMA_CONTROL_AUTO_REPEAT_MASK);
+	rp1dpi_hw_write(dpi, DPI_DMA_CONTROL, ctrl);
+	spin_unlock_irqrestore(&dpi->hw_lock, flags);
+
+	if (!wait_for_completion_timeout(&dpi->finished, HZ / 10))
+		drm_err(&dpi->drm, "%s: timed out waiting for idle\n", __func__);
+	rp1dpi_hw_write(dpi, DPI_DMA_IRQ_EN, 0);
+}
+
+void rp1dpi_hw_vblank_ctrl(struct rp1_dpi *dpi, int enable)
+{
+	rp1dpi_hw_write(dpi, DPI_DMA_IRQ_EN,
+			BITS(DPI_DMA_IRQ_EN_AFIFO_EMPTY, 1)         |
+			BITS(DPI_DMA_IRQ_EN_UNDERFLOW, 1)           |
+			BITS(DPI_DMA_IRQ_EN_DMA_READY, !!enable)    |
+			BITS(DPI_DMA_IRQ_EN_MATCH, dpi->interlaced) |
+			BITS(DPI_DMA_IRQ_EN_MATCH_LINE, 32));
+}
+
+irqreturn_t rp1dpi_hw_isr(int irq, void *dev)
+{
+	struct rp1_dpi *dpi = dev;
+	u32 u = rp1dpi_hw_read(dpi, DPI_DMA_IRQ_FLAGS);
+
+	if (u) {
+		rp1dpi_hw_write(dpi, DPI_DMA_IRQ_FLAGS, u);
+		if (dpi) {
+			if (u & DPI_DMA_IRQ_FLAGS_UNDERFLOW_MASK)
+				drm_err_ratelimited(&dpi->drm,
+						    "Underflow! (panics=0x%08x)\n",
+						    rp1dpi_hw_read(dpi, DPI_DMA_PANICS));
+			if (u & DPI_DMA_IRQ_FLAGS_DMA_READY_MASK)
+				drm_crtc_handle_vblank(&dpi->pipe.crtc);
+			if (u & DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_MASK)
+				complete(&dpi->finished);
+
+			/*
+			 * Added for interlace support: We use this mid-frame interrupt to
+			 * wobble the VFP between fields, re-submitting the next-buffer address
+			 * with an offset to display the opposite field. NB: rp1dpi_hw_update()
+			 * may be called at any time, before or after, so locking is needed.
+			 * H/W Auto-update is no longer needed (unless this IRQ is lost).
+			 */
+			if ((u & DPI_DMA_IRQ_FLAGS_MATCH_MASK) && dpi->interlaced) {
+				unsigned long flags;
+				dma_addr_t a;
+
+				spin_lock_irqsave(&dpi->hw_lock, flags);
+				dpi->lower_field_flag = !dpi->lower_field_flag;
+				rp1dpi_hw_write(dpi, DPI_DMA_FRONT_PORCH,
+						dpi->shorter_front_porch +
+						BITS(DPI_DMA_FRONT_PORCH_ROWSM1,
+						     dpi->lower_field_flag));
+				a = dpi->last_dma_addr;
+				if (a) {
+					if (dpi->lower_field_flag)
+						a += dpi->last_stride;
+					rp1dpi_hw_write(dpi, DPI_DMA_DMA_ADDR_H, a >> 32);
+					rp1dpi_hw_write(dpi, DPI_DMA_DMA_ADDR_L, a & 0xFFFFFFFFu);
+				}
+				spin_unlock_irqrestore(&dpi->hw_lock, flags);
+			}
+		}
+	}
+
+	return u ? IRQ_HANDLED : IRQ_NONE;
+}
diff --git a/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c
new file mode 100644
index 000000000000..9533d60d7721
--- /dev/null
+++ b/drivers/gpu/drm/rp1/rp1-dpi/rp1_dpi_pio.c
@@ -0,0 +1,614 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PIO code for Raspberry Pi RP1 DPI driver
+ *
+ * Copyright (c) 2024 Raspberry Pi Limited.
+ */
+
+/*
+ * RP1 DPI can't generate any composite sync, and in interlaced modes
+ * its native vertical sync output will be an incorrect/modified signal.
+ *
+ * So we need to use PIO *either* to generate CSYNC in both progressive
+ * and interlaced modes, *or* to fix up VSYNC for interlaced modes only.
+ * It can't do both: interlaced modes can have only one of CSYNC, VSYNC.
+ * All these cases require GPIOs 1, 2 and 3 to be declared as outputs.
+ *
+ * Note that PIO's VSYNC or CSYNC output is not synchronous to DPICLK
+ * and may suffer up to +/-5ns of jitter.
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <drm/drm_print.h>
+
+#include "rp1_dpi.h"
+
+#if IS_REACHABLE(CONFIG_RP1_PIO)
+
+#include <linux/pio_rp1.h>
+
+/*
+ * Start a PIO SM to generate two interrupts for each horizontal line.
+ * The first occurs shortly before the middle of the line. The second
+ * is timed such that after receiving the IRQ plus 1 extra delay cycle,
+ * another SM's output will align with the next HSYNC within -5ns .. +10ns.
+ * To achieve this, we need an accurate measure of (cycles per line) / 2.
+ *
+ * Measured GPIO -> { wait gpio ; irq set | irq wait ; sideset } -> GPIO
+ * round-trip delay is about 8 cycles when pins are not heavily loaded.
+ *
+ * PIO code           ; Notional time % 1000-cycle period
+ * --------           ; ---------------------------------
+ * 0: wait 1 gpio 3   ;   0..  8
+ * 1: mov x, y        ;   8..  9
+ * 2: jmp x--, 2      ;   9..499    (Y should be T/2 - 11)
+ * 3: irq set 1       ; 499..500
+ * 4: mov x, y    [8] ; 500..509
+ * 5: jmp x--, 5      ; 509..999
+ * 6: irq set 1       ; 999..1000
+ */
+
+static int rp1dpi_pio_start_timer_both(struct rp1_dpi *dpi, u32 flags, u32 tc)
+{
+	static const u16 instructions[2][7] = {
+		{ 0x2083, 0xa022, 0x0042, 0xc001, 0xa822, 0x0045, 0xc001 }, /* +H */
+		{ 0x2003, 0xa022, 0x0042, 0xc001, 0xa822, 0x0045, 0xc001 }, /* -H */
+	};
+	const struct pio_program prog = {
+		.instructions = instructions[(flags & DRM_MODE_FLAG_NHSYNC) ? 1 : 0],
+		.length = ARRAY_SIZE(instructions[0]),
+		.origin = -1
+	};
+	int offset, sm;
+
+	sm = pio_claim_unused_sm(dpi->pio, true);
+	if (sm < 0)
+		return -EBUSY;
+
+	offset = pio_add_program(dpi->pio, &prog);
+	if (offset == PIO_ORIGIN_ANY) {
+		pio_sm_unclaim(dpi->pio, sm);
+		return -EBUSY;
+	}
+
+	pio_sm_config cfg = pio_get_default_sm_config();
+
+	pio_sm_set_enabled(dpi->pio, sm, false);
+	sm_config_set_wrap(&cfg, offset, offset + 6);
+	pio_sm_init(dpi->pio, sm, offset, &cfg);
+
+	pio_sm_put(dpi->pio, sm, tc - 11);
+	pio_sm_exec(dpi->pio, sm, pio_encode_pull(false, false));
+	pio_sm_exec(dpi->pio, sm, pio_encode_out(pio_y, 32));
+	pio_sm_set_enabled(dpi->pio, sm, true);
+
+	return 0;
+}
+
+/*
+ * Snoop on DE, HSYNC to count half-lines in the vertical blanking interval
+ * to determine when the VSYNC pulse should start and finish. Then, at a
+ * suitable moment (which should be an odd number of half-lines since the
+ * last active line), sample DE again to detect field phase.
+ *
+ * This version assumes VFP length is within 2..256 half-lines for any field
+ * (one half-line delay is needed to sample DE; we always wait for the next
+ * half-line boundary to improve VSync start accuracy) and VBP in 1..255.
+ */
+
+static int rp1dpi_pio_vsync_ilace(struct rp1_dpi *dpi,
+				  struct drm_display_mode const *mode)
+{
+	u16 instructions[] = {  /* This is mutable */
+		//      .wrap_target
+		0xa0e6, //  0: mov    osr, isr    side 0     ; top: rewind parameters
+		0x2081, //  1: wait   1 gpio, 1   side 0     ; main: while (!DE) wait;
+		0x2783, //  2: wait   1 gpio, 3   side 0 [7] ;  do { @HSync
+		0xc041, //  3: irq    clear 1     side 0     ;   flush stale IRQs
+		0x20c1, //  4: wait   1 irq, 1    side 0     ;   @midline
+		0x00c2, //  5: jmp    pin, 2      side 0     ;  } while (DE)
+		0x0007, //  6: jmp    7           side 0     ;  <modify for -DE fixup>
+		0x6028, //  7: out    x, 8        side 0     ;  x = VFPlen - 2
+		0x20c1, //  8: wait   1 irq, 1    side 0     ;  do { @halfline
+		0x0048, //  9: jmp    x--, 8      side 0     ;  } while (x--)
+		0xb022, // 10: mov    x, y        side 1     ;  VSYNC=1; x = VSyncLen
+		0x30c1, // 11: wait   1 irq, 1    side 1     ;  VSYNC=1; do { @halfline
+		0x104b, // 12: jmp    x--, 11     side 1     ;  VSYNC=1; } while (x--)
+		0x6028, // 13: out    x, 8        side 0     ;  VSYNC=0; x = VBPLen - 1
+		0x20c1, // 14: wait   1 irq, 1    side 0     ;  do { @halfline
+		0x004e, // 15: jmp    x--, 14     side 0     ;  } while (x--)
+		0x00c0, // 16: jmp    pin, 0      side 0     ;  if (DE) reset phase
+		0x0012, // 17: jmp    18          side 0     ;  <modify for -DE fixup>
+		0x00e1, // 18: jmp    !osre, 1    side 0     ;  if (!phase) goto main
+		//     .wrap                                 ;  goto top
+	};
+	struct pio_program prog = {
+		.instructions = instructions,
+		.length = ARRAY_SIZE(instructions),
+		.origin = -1
+	};
+	pio_sm_config cfg = pio_get_default_sm_config();
+	unsigned int i, offset;
+	u32 tc, vfp, vbp;
+	int sm = pio_claim_unused_sm(dpi->pio, true);
+
+	if (sm < 0)
+		return -EBUSY;
+
+	/*
+	 * Compute half-line time constant (round uppish so that VSync should
+	 * switch never > 5ns before DPICLK, while defeating roundoff errors)
+	 * and start the timer SM.
+	 */
+	tc = (u32)clk_get_rate(dpi->clocks[RP1DPI_CLK_DPI]);
+	if (!tc)
+		tc = 1000u * mode->clock;
+	tc = ((u64)mode->htotal * (u64)clock_get_hz(clk_sys) + ((7ul * tc) >> 2)) /
+		(u64)(2ul * tc);
+	if (rp1dpi_pio_start_timer_both(dpi, mode->flags, tc) < 0) {
+		pio_sm_unclaim(dpi->pio, sm);
+		return -EBUSY;
+	}
+
+	/* Adapt program code according to DE and Sync polarity; configure program */
+	pio_sm_set_enabled(dpi->pio, sm, false);
+	if (dpi->de_inv) {
+		instructions[1] ^= 0x0080;
+		instructions[5]  = 0x00c7;
+		instructions[6]  = 0x0002;
+		instructions[16] = 0x00d2;
+		instructions[17] = 0x0000;
+	}
+	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+		instructions[2] ^= 0x0080;
+	if (mode->flags & DRM_MODE_FLAG_NVSYNC) {
+		for (i = 0; i < ARRAY_SIZE(instructions); i++)
+			instructions[i] ^= 0x1000;
+	}
+	offset = pio_add_program(dpi->pio, &prog);
+	if (offset == PIO_ORIGIN_ANY)
+		return -EBUSY;
+
+	/* Configure pins and SM */
+	sm_config_set_wrap(&cfg, offset, offset + ARRAY_SIZE(instructions) - 1);
+	sm_config_set_sideset(&cfg, 1, false, false);
+	sm_config_set_sideset_pins(&cfg, 2); /* PIO produces VSync on GPIO2 */
+	pio_gpio_init(dpi->pio, 2);
+	sm_config_set_jmp_pin(&cfg, 1); /* DE on GPIO1 */
+	pio_sm_init(dpi->pio, sm, offset, &cfg);
+	pio_sm_set_consecutive_pindirs(dpi->pio, sm, 2, 1, true);
+
+	/* Compute vertical times, remembering how we rounded vdisplay, vtotal */
+	vfp = mode->vsync_start - (mode->vdisplay & ~1);
+	vbp = (mode->vtotal | 1) - mode->vsync_end;
+	if (vfp > 256) {
+		vbp += vfp - 256;
+		vfp = 256;
+	} else if (vfp < 3) {
+		vbp = (vbp > 3 - vfp) ? (vbp - 3 + vfp) : 1;
+		vfp = 3;
+	}
+
+	pio_sm_put(dpi->pio, sm,
+		   (vfp - 2) + ((vbp - 1) << 8) +
+		   ((vfp - 3) << 16) + (vbp << 24));
+	pio_sm_put(dpi->pio, sm, mode->vsync_end - mode->vsync_start - 1);
+	pio_sm_exec(dpi->pio, sm, pio_encode_pull(false, false));
+	pio_sm_exec(dpi->pio, sm, pio_encode_out(pio_y, 32));
+	pio_sm_exec(dpi->pio, sm, pio_encode_in(pio_y, 32));
+	pio_sm_exec(dpi->pio, sm, pio_encode_pull(false, false));
+	pio_sm_exec(dpi->pio, sm, pio_encode_out(pio_y, 32));
+	pio_sm_set_enabled(dpi->pio, sm, true);
+
+	return 0;
+}
+
+/*
+ * COMPOSITE SYNC FOR PROGRESSIVE
+ *
+ * Copy HSYNC pulses to CSYNC (adding 1 cycle); then when VSYNC
+ * is asserted, extend each pulse by an additional Y + 1 cycles.
+ *
+ * The following time constant should be written to the FIFO:
+ *    (htotal - 2 * hsync_width) * sys_clock / dpi_clock - 2.
+ *
+ * The default configuration is +HSync, +VSync, -CSync; other
+ * polarities can be made by modifying the PIO program code.
+ */
+
+static int rp1dpi_pio_csync_prog(struct rp1_dpi *dpi,
+				 struct drm_display_mode const *mode)
+{
+	unsigned int i, tc, offset;
+	unsigned short instructions[] = {  /* This is mutable */
+		0x90a0, //  0: pull   block      side 1
+		0x7040, //  1: out    y, 32      side 1
+		//     .wrap_target
+		0xb322, //  2: mov    x, y       side 1 [3]
+		0x3083, //  3: wait   1 gpio, 3  side 1
+		0xa422, //  4: mov    x, y       side 0 [4]
+		0x2003, //  5: wait   0 gpio, 3  side 0
+		0x00c7, //  6: jmp    pin, 7     side 0    ; modify to flip VSync polarity
+		//     .wrap                               ; modify to flip VSync polarity
+		0x0047, //  7: jmp    x--, 7     side 0
+		0x1002, //  8: jmp    2          side 1
+	};
+	struct pio_program prog = {
+		.instructions = instructions,
+		.length = ARRAY_SIZE(instructions),
+		.origin = -1
+	};
+	pio_sm_config cfg = pio_get_default_sm_config();
+	int sm = pio_claim_unused_sm(dpi->pio, true);
+
+	if (sm < 0)
+		return -EBUSY;
+
+	/* Adapt program code for sync polarity; configure program */
+	pio_sm_set_enabled(dpi->pio, sm, false);
+	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+		instructions[6] = 0x00c2; /* jmp pin, 2 side 0 */
+	if (mode->flags & DRM_MODE_FLAG_NHSYNC) {
+		instructions[3] ^= 0x80;
+		instructions[5] ^= 0x80;
+	}
+	if (mode->flags & DRM_MODE_FLAG_PCSYNC) {
+		for (i = 0; i < ARRAY_SIZE(instructions); i++)
+			instructions[i] ^= 0x1000;
+	}
+	offset = pio_add_program(dpi->pio, &prog);
+	if (offset == PIO_ORIGIN_ANY)
+		return -EBUSY;
+
+	/* Configure pins and SM */
+	sm_config_set_wrap(&cfg, offset + 2,
+			   offset + (mode->flags & DRM_MODE_FLAG_NVSYNC) ? 7 : 6);
+	sm_config_set_sideset(&cfg, 1, false, false);
+	sm_config_set_sideset_pins(&cfg, 1); /* PIO produces CSync on GPIO 1 */
+	pio_gpio_init(dpi->pio, 1);
+	sm_config_set_jmp_pin(&cfg, 2); /* VSync on GPIO 2 */
+	pio_sm_init(dpi->pio, sm, offset, &cfg);
+	pio_sm_set_consecutive_pindirs(dpi->pio, sm, 1, 1, true);
+
+	/* Place time constant into the FIFO; start the SM */
+	tc = (u32)clk_get_rate(dpi->clocks[RP1DPI_CLK_DPI]);
+	if (!tc)
+		tc = 1000u * mode->clock;
+	tc = ((u64)(mode->htotal - 2 * (mode->hsync_end - mode->hsync_start)) *
+	      (u64)clock_get_hz(clk_sys)) / (u64)tc;
+	pio_sm_put(dpi->pio, sm, tc - 2);
+	pio_sm_set_enabled(dpi->pio, sm, true);
+
+	return 0;
+}
+
+/*
+ * Claim all four SMs. Use SMs 1,2,3 to generate an interrupt:
+ * 1: At the end of the left "broad pulse"
+ * 2: In the middle of the scanline
+ * 3: At the end of the right "broad pulse"
+ */
+static int rp1dpi_pio_claim_all_start_timers(struct rp1_dpi *dpi,
+					     struct drm_display_mode const *mode)
+{
+	static const u16 instructions[2][4] = {
+		{ 0xa022, 0x2083, 0x0042, 0xc010 }, /* posedge */
+		{ 0xa022, 0x2003, 0x0042, 0xc010 }, /* negedge */
+	};
+	const struct pio_program prog = {
+		.instructions = instructions[(mode->flags & DRM_MODE_FLAG_NHSYNC) ? 1 : 0],
+		.length = ARRAY_SIZE(instructions[0]),
+		.origin = -1
+	};
+	u32 tc[3], sysclk, dpiclk;
+	int offset, i;
+
+	dpiclk = clk_get_rate(dpi->clocks[RP1DPI_CLK_DPI]);
+	if (!dpiclk)
+		dpiclk = 1000u * mode->clock;
+	sysclk = clock_get_hz(clk_sys);
+	tc[1] = ((u64)mode->htotal * (u64)sysclk) / (u64)(2ul * dpiclk);
+	tc[2] = ((u64)(mode->htotal + mode->hsync_start - mode->hsync_end) * (u64)sysclk) /
+		(u64)dpiclk;
+	tc[0] = tc[2] - tc[1];
+
+	i = pio_claim_sm_mask(dpi->pio, 0xF);
+	if (i != 0)
+		return -EBUSY;
+
+	offset = pio_add_program(dpi->pio, &prog);
+	if (offset == PIO_ORIGIN_ANY)
+		return -EBUSY;
+
+	for (i = 0; i < 3; i++) {
+		pio_sm_config cfg = pio_get_default_sm_config();
+
+		pio_sm_set_enabled(dpi->pio, i + 1, false);
+		sm_config_set_wrap(&cfg, offset, offset + 3);
+		pio_sm_init(dpi->pio, i + 1, offset, &cfg);
+
+		pio_sm_put(dpi->pio, i + 1, tc[i] - 4);
+		pio_sm_exec(dpi->pio, i + 1, pio_encode_pull(false, false));
+		pio_sm_exec(dpi->pio, i + 1, pio_encode_out(pio_y, 32));
+		pio_sm_set_enabled(dpi->pio, i + 1, true);
+	}
+
+	return 0;
+}
+
+/*
+ * COMPOSITE SYNC FOR INTERLACED
+ *
+ * DPI VSYNC (GPIO2) must be a modified signal which is always active-low.
+ * It should go low for 1 or 2 scanlines, 2 or 2.5 lines before Vsync-start.
+ * Desired VSync width minus 1 (in half-lines) should be written to the FIFO.
+ *
+ * Three PIO SMs will be configured as timers, to fire at the end of a left
+ * broad pulse, the middle of a scanline, and the end of a right broad pulse.
+ *
+ * HSYNC->CSYNC latency is about 5 cycles, with a jitter of up to 1 cycle.
+ *
+ * Default program is compiled for +HSync, -CSync. The program may be
+ * modified for other polarities. GPIO2 polarity is always active low.
+ */
+
+static int rp1dpi_pio_csync_ilace(struct rp1_dpi *dpi,
+				  struct drm_display_mode const *mode)
+{
+	static const int wrap_target = 2;
+	static const int wrap = 23;
+	unsigned short instructions[] = {  /* This is mutable */
+		0x90a0, //  0: pull   block        side 1
+		0x7040, //  1: out    y, 32        side 1
+		//     .wrap_target                           ; while (true) {
+		0x3083, //  2: wait   1 gpio, 3    side 1     ;   do { @HSync
+		0xa422, //  3: mov    x, y         side 0 [4] ;   CSYNC: x = VSW - 1
+		0x2003, //  4: wait   0 gpio, 3    side 0     ;   CSYNC: HSync->CSync
+		0x12c2, //  5: jmp    pin, 2       side 1 [2] ;   } while (VSync)
+		0x3083, //  6: wait   1 gpio, 3    side 1     ;   @HSync
+		0xc442, //  7: irq    clear 2      side 0 [4] ;   CSYNC: flush IRQ
+		0x2003, //  8: wait   0 gpio, 3    side 0     ;   CSYNC: Hsync->CSync
+		0x30c2, //  9: wait   1 irq, 2     side 1     ;   @midline
+		0x10d4, // 10: jmp    pin, 20      side 1     ;   if (!VSync) goto sync_left;
+		0x3083, // 11: wait   1 gpio, 3    side 1     ;   @HSync
+		0xa442, // 12: nop                 side 0 [4] ;   CSYNC: delay
+		0x2003, // 13: wait   0 gpio, 3    side 0     ;   CSYNC: Hsync->CSync
+		0xd042, // 14: irq    clear 2      side 1     ;   do { flush IRQ
+		0xd043, // 15: irq    clear 3      side 1     ;     flush IRQ
+		0x30c2, // 16: wait   1 irq, 2     side 1     ;     @midline
+		0x20c3, // 17: wait   1 irq, 3     side 0     ;     CSYNC: @BroadRight
+		0x1054, // 18: jmp    x--, 20      side 1     ;     if (x-- == 0)
+		0x1002, // 19: jmp    2            side 1     ;       break;
+		0xd041, // 20: irq    clear 1      side 1     ; sync_left: flush IRQ
+		0x3083, // 21: wait   1 gpio, 3    side 1     ;     @HSync
+		0x20c1, // 22: wait   1 irq, 1     side 0     ;     CSYNC: @BroadLeft
+		0x104e, // 23: jmp    x--, 14      side 1     ;   } while (x--);
+		//     .wrap                                  ; }
+	};
+	struct pio_program prog = {
+		.instructions = instructions,
+		.length = ARRAY_SIZE(instructions),
+		.origin = -1
+	};
+	pio_sm_config cfg = pio_get_default_sm_config();
+	unsigned int i, offset;
+
+	/* Claim SM 0 and start timers on the other three SMs. */
+	i = rp1dpi_pio_claim_all_start_timers(dpi, mode);
+	if (i < 0)
+		return -EBUSY;
+
+	/* Adapt program code according to CSync polarity; configure program */
+	pio_sm_set_enabled(dpi->pio, 0, false);
+	for (i = 0; i < prog.length; i++) {
+		if (mode->flags & DRM_MODE_FLAG_PCSYNC)
+			instructions[i] ^= 0x1000;
+		if ((mode->flags & DRM_MODE_FLAG_NHSYNC) && (instructions[i] & 0xe07f) == 0x2003)
+			instructions[i] ^= 0x0080;
+	}
+	offset = pio_add_program(dpi->pio, &prog);
+	if (offset == PIO_ORIGIN_ANY)
+		return -1;
+
+	/* Configure pins and SM; set VSync width; start the SM */
+	sm_config_set_wrap(&cfg, offset + wrap_target, offset + wrap);
+	sm_config_set_sideset(&cfg, 1, false, false);
+	sm_config_set_sideset_pins(&cfg, 1); /* PIO produces CSync on GPIO 1 */
+	pio_gpio_init(dpi->pio, 1);
+	sm_config_set_jmp_pin(&cfg, 2); /* DPI "helper signal" is GPIO 2 */
+	pio_sm_init(dpi->pio, 0, offset, &cfg);
+	pio_sm_set_consecutive_pindirs(dpi->pio, 0, 1, 1, true);
+	pio_sm_put(dpi->pio, 0, mode->vsync_end - mode->vsync_start - 1);
+	pio_sm_set_enabled(dpi->pio, 0, true);
+
+	return 0;
+}
+
+/*
+ * COMPOSITE SYNC (TV-STYLE) for 625/25i and 525/30i only.
+ *
+ * DPI VSYNC (GPIO2) must be a modified signal which is always active-low.
+ * It should go low for 1 or 2 scanlines, VSyncWidth/2.0 or (VSyncWidth+1)/2.0
+ * lines before Vsync-start, i.e. just after the last full active TV line
+ * (noting that RP1 DPI does not generate half-lines).
+ *
+ * This will push the image up by 1 line compared to customary DRM timings in
+ * "PAL" mode, or 2 lines in "NTSC" mode (which is arguably too low anyway),
+ * but avoids a collision between an active line and an equalizing pulse.
+ *
+ * Another wrinkle is that when the first equalizing pulse aligns with HSync,
+ * it becomes a normal-width sync pulse. This was a deliberate simplification.
+ * It is unlikely that any TV will notice or care.
+ */
+
+static int rp1dpi_pio_csync_tv(struct rp1_dpi *dpi,
+			       struct drm_display_mode const *mode)
+{
+	static const int wrap_target = 6;
+	static const int wrap = 27;
+	unsigned short instructions[] = {  /* This is mutable */
+		0x3703, //  0: wait  0 gpio, 3  side 1 [7] ; while (HSync) delay;
+		0x3083, //  1: wait  1 gpio, 3  side 1     ; do { @HSync
+		0xa7e6, //  2: mov   osr, isr   side 0 [7] ;   CSYNC: rewind sequence
+		0x2003, //  3: wait  0 gpio, 3  side 0     ;   CSYNC: HSync->CSync
+		0xb7e6, //  4: mov   osr, isr   side 1 [7] ;   delay
+		0x10c1, //  5: jmp   pin, 1     side 1     ; } while (VSync)
+		//     .wrap_target                        ; while (true) {
+		0xd042, //  6: irq   clear 2    side 1     ;   flush stale IRQ
+		0xd043, //  7: irq   clear 3    side 1     ;   flush stale IRQ
+		0xb022, //  8: mov   x, y       side 1     ;   X = EQWidth - 3
+		0x30c2, //  9: wait  1 irq, 2   side 1     ;   @midline
+		0x004a, // 10: jmp   x--, 10    side 0     ;   CSYNC: while (x--) ;
+		0x6021, // 11: out   x, 1       side 0     ;   CSYNC: next pulse broad?
+		0x002e, // 12: jmp   !x, 14     side 0     ;   CSYNC: if (broad)
+		0x20c3, // 13: wait  1 irq, 3   side 0     ;   CSYNC:   @BroadRight
+		0x7021, // 14: out   x, 1       side 1     ;   sequence not finished?
+		0x1020, // 15: jmp   !x, 0      side 1     ;   if (finished) break
+		0xd041, // 16: irq   clear 1    side 1     ;   flush stale IRQ
+		0xb022, // 17: mov   x, y       side 1     ;   X = EQWidth - 3
+		0x3083, // 18: wait  1 gpio, 3  side 1     ;   @HSync
+		0x0053, // 19: jmp   x--, 19    side 0     ;   CSYNC: while (x--) ;
+		0x6021, // 20: out   x, 1       side 0     ;   CSYNC: next pulse broad?
+		0x0037, // 21: jmp   !x, 23     side 0     ;   CSYNC: if (broad)
+		0x20c1, // 22: wait  1 irq, 1   side 0     ;   CSYNC:  @BroadLeft
+		0x7021, // 23: out   x, 1       side 1     ;   sequence not finished?
+		0x1020, // 24: jmp   !x, 0      side 1     ;   if (finished) break
+		0x10c6, // 25: jmp   pin, 6     side 1     ;   if (VSync) continue
+		0xb0e6, // 26: mov   osr, isr   side 1     ;   rewind sequence
+		0x7022, // 27: out   x, 2       side 1     ;   skip 2 bits
+		//     .wrap                               ; }
+	};
+	struct pio_program prog = {
+		.instructions = instructions,
+		.length = ARRAY_SIZE(instructions),
+		.origin = -1
+	};
+	pio_sm_config cfg = pio_get_default_sm_config();
+	unsigned int i, offset;
+
+	/* Claim SM 0 and start timers on the other three SMs. */
+	i = rp1dpi_pio_claim_all_start_timers(dpi, mode);
+	if (i < 0)
+		return -EBUSY;
+
+	/* Adapt program code according to CSync polarity; configure program */
+	pio_sm_set_enabled(dpi->pio, 0, false);
+	for (i = 0; i < ARRAY_SIZE(instructions); i++) {
+		if (mode->flags & DRM_MODE_FLAG_PCSYNC)
+			instructions[i] ^= 0x1000;
+		if ((mode->flags & DRM_MODE_FLAG_NHSYNC) && (instructions[i] & 0xe07f) == 0x2003)
+			instructions[i] ^= 0x0080;
+	}
+	offset = pio_add_program(dpi->pio, &prog);
+	if (offset == PIO_ORIGIN_ANY)
+		return -1;
+
+	/* Configure pins and SM */
+	sm_config_set_wrap(&cfg, offset + wrap_target, offset + wrap);
+	sm_config_set_sideset(&cfg, 1, false, false);
+	sm_config_set_sideset_pins(&cfg, 1); /* PIO produces CSync on GPIO 1 */
+	pio_gpio_init(dpi->pio, 1);
+	sm_config_set_jmp_pin(&cfg, 2); /* DPI VSync "helper" signal is GPIO 2 */
+	pio_sm_init(dpi->pio, 0, offset, &cfg);
+	pio_sm_set_consecutive_pindirs(dpi->pio, 0, 1, 1, true);
+
+	/* Load parameters (Vsync pattern; EQ pulse width) into ISR and Y */
+	i = (mode->vsync_end - mode->vsync_start <= 5);
+	pio_sm_put(dpi->pio, 0, i ? 0x02ABFFAA : 0xAABFFEAA);
+	pio_sm_put(dpi->pio, 0, clock_get_hz(clk_sys) / (i ? 425531 : 434782) - 3);
+	pio_sm_exec(dpi->pio, 0, pio_encode_pull(false, false));
+	pio_sm_exec(dpi->pio, 0, pio_encode_out(pio_y, 32));
+	pio_sm_exec(dpi->pio, 0, pio_encode_in(pio_y, 32));
+	pio_sm_exec(dpi->pio, 0, pio_encode_pull(false, false));
+	pio_sm_exec(dpi->pio, 0, pio_encode_out(pio_y, 32));
+
+	/* Start the SM */
+	pio_sm_set_enabled(dpi->pio, 0, true);
+
+	return 0;
+}
+
+int rp1dpi_pio_start(struct rp1_dpi *dpi, const struct drm_display_mode *mode,
+		     bool force_csync)
+{
+	int r;
+
+	/*
+	 * Check if PIO is needed *and* we have an appropriate pin mapping
+	 * that allows all three Sync GPIOs to be snooped on or overridden.
+	 */
+	if (!(mode->flags & (DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_CSYNC)) &&
+	    !force_csync)
+		return 0;
+	if (!dpi->sync_gpios_mapped) {
+		drm_warn(&dpi->drm, "DPI needs GPIOs 1-3 for Interlace or CSync\n");
+		return -EINVAL;
+	}
+
+	if (dpi->pio)
+		pio_close(dpi->pio);
+
+	dpi->pio = pio_open();
+	if (IS_ERR(dpi->pio)) {
+		drm_err(&dpi->drm, "Could not open PIO\n");
+		dpi->pio = NULL;
+		return -ENODEV;
+	}
+
+	if ((mode->flags & DRM_MODE_FLAG_CSYNC) || force_csync) {
+		drm_info(&dpi->drm, "Using PIO to generate CSync on GPIO1\n");
+		if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
+			if (mode->clock > 15 * mode->htotal &&
+			    mode->clock < 16 * mode->htotal &&
+			    (mode->vtotal == 525 || mode->vtotal == 625))
+				r = rp1dpi_pio_csync_tv(dpi, mode);
+			else
+				r = rp1dpi_pio_csync_ilace(dpi, mode);
+		} else {
+			r = rp1dpi_pio_csync_prog(dpi, mode);
+		}
+	} else {
+		drm_info(&dpi->drm, "Using PIO to generate VSync on GPIO2\n");
+		r = rp1dpi_pio_vsync_ilace(dpi, mode);
+	}
+	if (r) {
+		drm_err(&dpi->drm, "Failed to initialize PIO\n");
+		rp1dpi_pio_stop(dpi);
+	}
+
+	return r;
+}
+
+void rp1dpi_pio_stop(struct rp1_dpi *dpi)
+{
+	if (dpi->pio) {
+		/* Return any "stolen" pins to DPI function */
+		pio_gpio_set_function(dpi->pio, 1, GPIO_FUNC_FSEL1);
+		pio_gpio_set_function(dpi->pio, 2, GPIO_FUNC_FSEL1);
+		pio_close(dpi->pio);
+		dpi->pio = NULL;
+	}
+}
+
+#else /* !IS_REACHABLE(CONFIG_RP1_PIO) */
+
+int rp1dpi_pio_start(struct rp1_dpi *dpi, const struct drm_display_mode *mode,
+		     bool force_csync)
+{
+	if (mode->flags & (DRM_MODE_FLAG_CSYNC | DRM_MODE_FLAG_INTERLACE) || force_csync) {
+		drm_warn(&dpi->drm, "DPI needs PIO support for Interlace or CSync\n");
+		return -ENODEV;
+	} else {
+		return 0;
+	}
+}
+
+void rp1dpi_pio_stop(struct rp1_dpi *dpi)
+{
+}
+
+#endif
diff --git a/drivers/gpu/drm/rp1/rp1-dsi/Kconfig b/drivers/gpu/drm/rp1/rp1-dsi/Kconfig
new file mode 100644
index 000000000000..80c57bc48792
--- /dev/null
+++ b/drivers/gpu/drm/rp1/rp1-dsi/Kconfig
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config DRM_RP1_DSI
+	tristate "DRM Support for RP1 DSI"
+	depends on DRM && MFD_RP1
+	select DRM_GEM_DMA_HELPER
+	select DRM_KMS_HELPER
+	select DRM_MIPI_DSI
+	select DRM_VRAM_HELPER
+	select DRM_TTM
+	select DRM_TTM_HELPER
+	select GENERIC_PHY
+	select GENERIC_PHY_MIPI_DPHY
+	help
+	  Choose this option to enable DSI display on RP1
diff --git a/drivers/gpu/drm/rp1/rp1-dsi/Makefile b/drivers/gpu/drm/rp1/rp1-dsi/Makefile
new file mode 100644
index 000000000000..1a9672c7bda0
--- /dev/null
+++ b/drivers/gpu/drm/rp1/rp1-dsi/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+drm-rp1-dsi-y := rp1_dsi.o rp1_dsi_dma.o rp1_dsi_dsi.o
+
+obj-$(CONFIG_DRM_RP1_DSI) += drm-rp1-dsi.o
diff --git a/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi.c b/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi.c
new file mode 100644
index 000000000000..8984fe8bbea5
--- /dev/null
+++ b/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi.c
@@ -0,0 +1,548 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * DRM Driver for DSI output on Raspberry Pi RP1
+ *
+ * Copyright (c) 2023 Raspberry Pi Limited.
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/phy/phy-mipi-dphy.h>
+#include <linux/string.h>
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_fbdev_ttm.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_managed.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_of.h>
+#include <drm/drm_print.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/drm_vblank.h>
+
+#include "rp1_dsi.h"
+
+static inline struct rp1_dsi *
+bridge_to_rp1_dsi(struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct rp1_dsi, bridge);
+}
+
+static void rp1_dsi_bridge_pre_enable(struct drm_bridge *bridge,
+				      struct drm_bridge_state *old_state)
+{
+	struct rp1_dsi *dsi = bridge_to_rp1_dsi(bridge);
+
+	rp1dsi_dsi_setup(dsi, &dsi->pipe.crtc.state->adjusted_mode);
+	dsi->dsi_running = true;
+}
+
+static void rp1_dsi_bridge_enable(struct drm_bridge *bridge,
+				  struct drm_bridge_state *old_state)
+{
+}
+
+static void rp1_dsi_bridge_disable(struct drm_bridge *bridge,
+				   struct drm_bridge_state *state)
+{
+}
+
+static void rp1_dsi_bridge_post_disable(struct drm_bridge *bridge,
+					struct drm_bridge_state *state)
+{
+	struct rp1_dsi *dsi = bridge_to_rp1_dsi(bridge);
+
+	if (dsi->dsi_running) {
+		rp1dsi_dsi_stop(dsi);
+		dsi->dsi_running = false;
+	}
+}
+
+static int rp1_dsi_bridge_attach(struct drm_bridge *bridge,
+				 enum drm_bridge_attach_flags flags)
+{
+	struct rp1_dsi *dsi = bridge_to_rp1_dsi(bridge);
+
+	/* Attach the panel or bridge to the dsi bridge */
+	return drm_bridge_attach(bridge->encoder, dsi->out_bridge,
+				 &dsi->bridge, flags);
+	return 0;
+}
+
+static const struct drm_bridge_funcs rp1_dsi_bridge_funcs = {
+	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+	.atomic_reset = drm_atomic_helper_bridge_reset,
+	.atomic_pre_enable = rp1_dsi_bridge_pre_enable,
+	.atomic_enable = rp1_dsi_bridge_enable,
+	.atomic_disable = rp1_dsi_bridge_disable,
+	.atomic_post_disable = rp1_dsi_bridge_post_disable,
+	.attach = rp1_dsi_bridge_attach,
+};
+
+static void rp1dsi_pipe_update(struct drm_simple_display_pipe *pipe,
+			       struct drm_plane_state *old_state)
+{
+	struct drm_pending_vblank_event *event;
+	unsigned long flags;
+	struct drm_framebuffer *fb = pipe->plane.state->fb;
+	struct rp1_dsi *dsi = pipe->crtc.dev->dev_private;
+	struct drm_gem_object *gem = fb ? drm_gem_fb_get_obj(fb, 0) : NULL;
+	struct drm_gem_dma_object *dma_obj = gem ? to_drm_gem_dma_obj(gem) : NULL;
+	bool can_update = fb && dma_obj && dsi && dsi->pipe_enabled;
+
+	/* (Re-)start DSI,DMA where required; and update FB address */
+	if (can_update) {
+		if (!dsi->dma_running || fb->format->format != dsi->cur_fmt) {
+			if (dsi->dma_running && fb->format->format != dsi->cur_fmt) {
+				rp1dsi_dma_stop(dsi);
+				dsi->dma_running = false;
+			}
+			if (!dsi->dma_running) {
+				rp1dsi_dma_setup(dsi,
+						 fb->format->format, dsi->display_format,
+						&pipe->crtc.state->adjusted_mode);
+				dsi->dma_running = true;
+			}
+			dsi->cur_fmt  = fb->format->format;
+			drm_crtc_vblank_on(&pipe->crtc);
+		}
+		rp1dsi_dma_update(dsi, dma_obj->dma_addr, fb->offsets[0], fb->pitches[0]);
+	}
+
+	/* Arm VBLANK event (or call it immediately in some error cases) */
+	spin_lock_irqsave(&pipe->crtc.dev->event_lock, flags);
+	event = pipe->crtc.state->event;
+	if (event) {
+		pipe->crtc.state->event = NULL;
+		if (can_update && drm_crtc_vblank_get(&pipe->crtc) == 0)
+			drm_crtc_arm_vblank_event(&pipe->crtc, event);
+		else
+			drm_crtc_send_vblank_event(&pipe->crtc, event);
+	}
+	spin_unlock_irqrestore(&pipe->crtc.dev->event_lock, flags);
+}
+
+static inline struct rp1_dsi *
+encoder_to_rp1_dsi(struct drm_encoder *encoder)
+{
+	struct drm_simple_display_pipe *pipe =
+		container_of(encoder, struct drm_simple_display_pipe, encoder);
+	return container_of(pipe, struct rp1_dsi, pipe);
+}
+
+static void rp1dsi_encoder_enable(struct drm_encoder *encoder)
+{
+	struct rp1_dsi *dsi = encoder_to_rp1_dsi(encoder);
+
+	/* Put DSI into video mode before starting video */
+	rp1dsi_dsi_set_cmdmode(dsi, 0);
+
+	/* Start DMA -> DPI */
+	dsi->pipe_enabled = true;
+	dsi->cur_fmt = 0xdeadbeef;
+	rp1dsi_pipe_update(&dsi->pipe, 0);
+}
+
+static void rp1dsi_encoder_disable(struct drm_encoder *encoder)
+{
+	struct rp1_dsi *dsi = encoder_to_rp1_dsi(encoder);
+
+	drm_crtc_vblank_off(&dsi->pipe.crtc);
+	if (dsi->dma_running) {
+		rp1dsi_dma_stop(dsi);
+		dsi->dma_running = false;
+	}
+	dsi->pipe_enabled = false;
+
+	/* Return to command mode after stopping video */
+	rp1dsi_dsi_set_cmdmode(dsi, 1);
+}
+
+static const struct drm_encoder_helper_funcs rp1_dsi_encoder_funcs = {
+	.enable = rp1dsi_encoder_enable,
+	.disable = rp1dsi_encoder_disable,
+};
+
+static void rp1dsi_pipe_enable(struct drm_simple_display_pipe *pipe,
+			       struct drm_crtc_state *crtc_state,
+			       struct drm_plane_state *plane_state)
+{
+}
+
+static void rp1dsi_pipe_disable(struct drm_simple_display_pipe *pipe)
+{
+}
+
+static int rp1dsi_pipe_enable_vblank(struct drm_simple_display_pipe *pipe)
+{
+	struct rp1_dsi *dsi = pipe->crtc.dev->dev_private;
+
+	if (dsi)
+		rp1dsi_dma_vblank_ctrl(dsi, 1);
+
+	return 0;
+}
+
+static void rp1dsi_pipe_disable_vblank(struct drm_simple_display_pipe *pipe)
+{
+	struct rp1_dsi *dsi = pipe->crtc.dev->dev_private;
+
+	if (dsi)
+		rp1dsi_dma_vblank_ctrl(dsi, 0);
+}
+
+static const struct drm_simple_display_pipe_funcs rp1dsi_pipe_funcs = {
+	.enable	    = rp1dsi_pipe_enable,
+	.update	    = rp1dsi_pipe_update,
+	.disable    = rp1dsi_pipe_disable,
+	.enable_vblank  = rp1dsi_pipe_enable_vblank,
+	.disable_vblank = rp1dsi_pipe_disable_vblank,
+};
+
+static const struct drm_mode_config_funcs rp1dsi_mode_funcs = {
+	.fb_create = drm_gem_fb_create,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+static const u32 rp1dsi_formats[] = {
+	DRM_FORMAT_XRGB8888,
+	DRM_FORMAT_XBGR8888,
+	DRM_FORMAT_ARGB8888,
+	DRM_FORMAT_ABGR8888,
+	DRM_FORMAT_RGB888,
+	DRM_FORMAT_BGR888,
+	DRM_FORMAT_RGB565
+};
+
+static void rp1dsi_stopall(struct drm_device *drm)
+{
+	if (drm->dev_private) {
+		struct rp1_dsi *dsi = drm->dev_private;
+
+		if (dsi->dma_running || rp1dsi_dma_busy(dsi)) {
+			rp1dsi_dma_stop(dsi);
+			dsi->dma_running = false;
+		}
+		if (dsi->dsi_running) {
+			rp1dsi_dsi_stop(dsi);
+			dsi->dsi_running = false;
+		}
+		if (dsi->clocks[RP1DSI_CLOCK_CFG])
+			clk_disable_unprepare(dsi->clocks[RP1DSI_CLOCK_CFG]);
+	}
+}
+
+DEFINE_DRM_GEM_DMA_FOPS(rp1dsi_fops);
+
+static struct drm_driver rp1dsi_driver = {
+	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
+	.fops			= &rp1dsi_fops,
+	.name			= "drm-rp1-dsi",
+	.desc			= "drm-rp1-dsi",
+	.date			= "0",
+	.major			= 1,
+	.minor			= 0,
+	DRM_GEM_DMA_DRIVER_OPS,
+	.release		= rp1dsi_stopall,
+};
+
+static int rp1dsi_bind(struct rp1_dsi *dsi)
+{
+	struct platform_device *pdev = dsi->pdev;
+	struct drm_device *drm = dsi->drm;
+	int ret;
+
+	dsi->out_bridge = drmm_of_get_bridge(drm, pdev->dev.of_node, 0, 0);
+	if (IS_ERR(dsi->out_bridge))
+		return PTR_ERR(dsi->out_bridge);
+
+	ret = drmm_mode_config_init(drm);
+	if (ret)
+		goto rtn;
+
+	drm->mode_config.max_width  = 4096;
+	drm->mode_config.max_height = 4096;
+	drm->mode_config.preferred_depth = 32;
+	drm->mode_config.prefer_shadow	 = 0;
+	drm->mode_config.quirk_addfb_prefer_host_byte_order = true;
+	drm->mode_config.funcs = &rp1dsi_mode_funcs;
+	drm_vblank_init(drm, 1);
+
+	ret = drm_simple_display_pipe_init(drm,
+					   &dsi->pipe,
+					   &rp1dsi_pipe_funcs,
+					   rp1dsi_formats,
+					   ARRAY_SIZE(rp1dsi_formats),
+					   NULL, NULL);
+	if (ret)
+		goto rtn;
+
+	/* We need slightly more complex encoder handling (enabling/disabling
+	 * video mode), so add encoder helper functions.
+	 */
+	drm_encoder_helper_add(&dsi->pipe.encoder, &rp1_dsi_encoder_funcs);
+
+	ret = drm_simple_display_pipe_attach_bridge(&dsi->pipe, &dsi->bridge);
+	if (ret)
+		goto rtn;
+
+	drm_bridge_add(&dsi->bridge);
+
+	drm_mode_config_reset(drm);
+
+	if (dsi->clocks[RP1DSI_CLOCK_CFG])
+		clk_prepare_enable(dsi->clocks[RP1DSI_CLOCK_CFG]);
+
+	ret = drm_dev_register(drm, 0);
+
+	if (ret == 0)
+		drm_fbdev_ttm_setup(drm, 32);
+
+rtn:
+	if (ret)
+		dev_err(&pdev->dev, "%s returned %d\n", __func__, ret);
+	else
+		dev_info(&pdev->dev, "%s succeeded", __func__);
+
+	return ret;
+}
+
+static void rp1dsi_unbind(struct rp1_dsi *dsi)
+{
+	struct drm_device *drm = dsi->drm;
+
+	rp1dsi_stopall(drm);
+	drm_dev_unregister(drm);
+	drm_atomic_helper_shutdown(drm);
+}
+
+static int rp1dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_device *dsi_dev)
+{
+	struct rp1_dsi *dsi = container_of(host, struct rp1_dsi, dsi_host);
+
+	dev_info(&dsi->pdev->dev, "%s: Attach DSI device name=%s channel=%d lanes=%d format=%d flags=0x%lx hs_rate=%lu lp_rate=%lu",
+		 __func__, dsi_dev->name, dsi_dev->channel, dsi_dev->lanes,
+		 dsi_dev->format, dsi_dev->mode_flags, dsi_dev->hs_rate,
+		 dsi_dev->lp_rate);
+	dsi->vc              = dsi_dev->channel & 3;
+	dsi->lanes           = dsi_dev->lanes;
+
+	switch (dsi_dev->format) {
+	case MIPI_DSI_FMT_RGB666:
+	case MIPI_DSI_FMT_RGB666_PACKED:
+	case MIPI_DSI_FMT_RGB565:
+	case MIPI_DSI_FMT_RGB888:
+		break;
+	default:
+		return -EINVAL;
+	}
+	dsi->display_format  = dsi_dev->format;
+	dsi->display_flags   = dsi_dev->mode_flags;
+	dsi->display_hs_rate = dsi_dev->hs_rate;
+	dsi->display_lp_rate = dsi_dev->lp_rate;
+
+	/*
+	 * Previously, we added a separate component to handle panel/bridge
+	 * discovery and DRM registration, but now it's just a function call.
+	 * The downstream/attaching device should deal with -EPROBE_DEFER
+	 */
+	return rp1dsi_bind(dsi);
+}
+
+static int rp1dsi_host_detach(struct mipi_dsi_host *host, struct mipi_dsi_device *dsi_dev)
+{
+	struct rp1_dsi *dsi = container_of(host, struct rp1_dsi, dsi_host);
+
+	/*
+	 * Unregister the DRM driver.
+	 * TODO: Check we are cleaning up correctly and not doing things multiple times!
+	 */
+	rp1dsi_unbind(dsi);
+	return 0;
+}
+
+static ssize_t rp1dsi_host_transfer(struct mipi_dsi_host *host, const struct mipi_dsi_msg *msg)
+{
+	struct rp1_dsi *dsi = container_of(host, struct rp1_dsi, dsi_host);
+	struct mipi_dsi_packet packet;
+	int ret = 0;
+
+	/* Write */
+	ret = mipi_dsi_create_packet(&packet, msg);
+	if (ret) {
+		dev_err(dsi->drm->dev, "RP1DSI: failed to create packet: %d\n", ret);
+		return ret;
+	}
+
+	rp1dsi_dsi_send(dsi, *(u32 *)(&packet.header),
+			packet.payload_length, packet.payload,
+			!!(msg->flags & MIPI_DSI_MSG_USE_LPM),
+			!!(msg->flags & MIPI_DSI_MSG_REQ_ACK));
+
+	/* Optional read back */
+	if (msg->rx_len && msg->rx_buf)
+		ret = rp1dsi_dsi_recv(dsi, msg->rx_len, msg->rx_buf);
+
+	return (ssize_t)ret;
+}
+
+static const struct mipi_dsi_host_ops rp1dsi_mipi_dsi_host_ops = {
+	.attach = rp1dsi_host_attach,
+	.detach = rp1dsi_host_detach,
+	.transfer = rp1dsi_host_transfer
+};
+
+static int rp1dsi_platform_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct device_node *endpoint;
+	struct drm_device *drm;
+	struct rp1_dsi *dsi;
+	int i, nr_lanes, ret;
+
+	drm = drm_dev_alloc(&rp1dsi_driver, dev);
+	if (IS_ERR(drm)) {
+		ret = PTR_ERR(drm);
+		return ret;
+	}
+	dsi = drmm_kzalloc(drm, sizeof(*dsi), GFP_KERNEL);
+	if (!dsi) {
+		ret = -ENOMEM;
+		goto err_free_drm;
+	}
+	init_completion(&dsi->finished);
+	dsi->drm = drm;
+	dsi->pdev = pdev;
+	drm->dev_private = dsi;
+	platform_set_drvdata(pdev, drm);
+
+	dsi->bridge.funcs = &rp1_dsi_bridge_funcs;
+	dsi->bridge.of_node = dev->of_node;
+	dsi->bridge.type = DRM_MODE_CONNECTOR_DSI;
+
+	/* Safe default values for DSI mode */
+	dsi->lanes = 1;
+	dsi->display_format = MIPI_DSI_FMT_RGB888;
+	dsi->display_flags  = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_LPM;
+
+	/* Hardware resources */
+	for (i = 0; i < RP1DSI_NUM_CLOCKS; i++) {
+		static const char * const myclocknames[RP1DSI_NUM_CLOCKS] = {
+			"cfgclk", "dpiclk", "byteclk", "refclk", "pllsys"
+		};
+		dsi->clocks[i] = devm_clk_get(dev, myclocknames[i]);
+		if (IS_ERR(dsi->clocks[i])) {
+			ret = PTR_ERR(dsi->clocks[i]);
+			dev_err(dev, "Error getting clocks[%d]\n", i);
+			goto err_free_drm;
+		}
+	}
+
+	endpoint = of_graph_get_endpoint_by_regs(node, 0, -1);
+	nr_lanes = of_property_count_u32_elems(endpoint, "data-lanes");
+	if (nr_lanes > 0 && nr_lanes <= 4)
+		of_property_read_u32_array(endpoint, "lane-polarities",
+					   dsi->lane_polarities, nr_lanes + 1);
+
+	for (i = 0; i < RP1DSI_NUM_HW_BLOCKS; i++) {
+		dsi->hw_base[i] =
+			devm_ioremap_resource(dev,
+					      platform_get_resource(dsi->pdev,
+								    IORESOURCE_MEM,
+								    i));
+		if (IS_ERR(dsi->hw_base[i])) {
+			ret = PTR_ERR(dsi->hw_base[i]);
+			dev_err(dev, "Error memory mapping regs[%d]\n", i);
+			goto err_free_drm;
+		}
+	}
+	ret = platform_get_irq(dsi->pdev, 0);
+	if (ret > 0)
+		ret = devm_request_irq(dev, ret, rp1dsi_dma_isr,
+				       IRQF_SHARED, "rp1-dsi", dsi);
+	if (ret) {
+		dev_err(dev, "Unable to request interrupt\n");
+		ret = -EINVAL;
+		goto err_free_drm;
+	}
+	rp1dsi_mipicfg_setup(dsi);
+	dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
+
+	/* Create the MIPI DSI Host and wait for the panel/bridge to attach to it */
+	dsi->dsi_host.ops = &rp1dsi_mipi_dsi_host_ops;
+	dsi->dsi_host.dev = dev;
+	ret = mipi_dsi_host_register(&dsi->dsi_host);
+	if (ret)
+		goto err_free_drm;
+
+	return ret;
+
+err_free_drm:
+	dev_err(dev, "%s fail %d\n", __func__, ret);
+	drm_dev_put(drm);
+	return ret;
+}
+
+static void rp1dsi_platform_remove(struct platform_device *pdev)
+{
+	struct drm_device *drm = platform_get_drvdata(pdev);
+	struct rp1_dsi *dsi = drm->dev_private;
+
+	mipi_dsi_host_unregister(&dsi->dsi_host);
+}
+
+static void rp1dsi_platform_shutdown(struct platform_device *pdev)
+{
+	struct drm_device *drm = platform_get_drvdata(pdev);
+
+	rp1dsi_stopall(drm);
+}
+
+static const struct of_device_id rp1dsi_of_match[] = {
+	{
+		.compatible = "raspberrypi,rp1dsi",
+	},
+	{ /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, rp1dsi_of_match);
+
+static struct platform_driver rp1dsi_platform_driver = {
+	.probe		= rp1dsi_platform_probe,
+	.remove		= rp1dsi_platform_remove,
+	.shutdown       = rp1dsi_platform_shutdown,
+	.driver		= {
+		.name	= DRIVER_NAME,
+		.owner  = THIS_MODULE,
+		.of_match_table = rp1dsi_of_match,
+	},
+};
+
+module_platform_driver(rp1dsi_platform_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MIPI DSI driver for Raspberry Pi RP1");
+MODULE_AUTHOR("Nick Hollinghurst");
diff --git a/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi.h b/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi.h
new file mode 100644
index 000000000000..b4d8cf5b74b6
--- /dev/null
+++ b/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * DRM Driver for DSI output on Raspberry Pi RP1
+ *
+ * Copyright (c) 2023 Raspberry Pi Limited.
+ */
+#ifndef _RP1_DSI_H_
+#define _RP1_DSI_H_
+
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/types.h>
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_device.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#define MODULE_NAME "drm-rp1-dsi"
+#define DRIVER_NAME "drm-rp1-dsi"
+
+/* ---------------------------------------------------------------------- */
+
+#define RP1DSI_HW_BLOCK_DMA   0
+#define RP1DSI_HW_BLOCK_DSI   1
+#define RP1DSI_HW_BLOCK_CFG   2
+#define RP1DSI_NUM_HW_BLOCKS  3
+
+#define RP1DSI_CLOCK_CFG     0
+#define RP1DSI_CLOCK_DPI     1
+#define RP1DSI_CLOCK_BYTE    2
+#define RP1DSI_CLOCK_REF     3
+#define RP1DSI_CLOCK_PLLSYS  4
+#define RP1DSI_NUM_CLOCKS    5
+
+/* ---------------------------------------------------------------------- */
+
+struct rp1_dsi {
+	/* DRM and platform device pointers */
+	struct drm_device *drm;
+	struct platform_device *pdev;
+
+	/* Framework and helper objects */
+	struct drm_simple_display_pipe pipe;
+	struct drm_bridge bridge;
+	struct drm_bridge *out_bridge;
+	struct mipi_dsi_host dsi_host;
+
+	/* Clocks. We need DPI clock; the others are frequency references */
+	struct clk *clocks[RP1DSI_NUM_CLOCKS];
+
+	/* Device tree parsed information */
+	u32 lane_polarities[5];
+
+	/* Block (DSI DMA, DSI Host) base addresses, and current state */
+	void __iomem *hw_base[RP1DSI_NUM_HW_BLOCKS];
+	u32 cur_fmt;
+	bool dsi_running, dma_running, pipe_enabled;
+	struct completion finished;
+
+	/* Attached display parameters (from mipi_dsi_device) */
+	unsigned long display_flags, display_hs_rate, display_lp_rate;
+	enum mipi_dsi_pixel_format display_format;
+	u8 vc;
+	u8 lanes;
+
+	/* DPHY */
+	u8 hsfreq_index;
+};
+
+/* ---------------------------------------------------------------------- */
+/* Functions to control the DSI/DPI/DMA block				  */
+
+void rp1dsi_dma_setup(struct rp1_dsi *dsi,
+		      u32 in_format, enum mipi_dsi_pixel_format out_format,
+		      struct drm_display_mode const *mode);
+void rp1dsi_dma_update(struct rp1_dsi *dsi, dma_addr_t addr, u32 offset, u32 stride);
+void rp1dsi_dma_stop(struct rp1_dsi *dsi);
+int rp1dsi_dma_busy(struct rp1_dsi *dsi);
+irqreturn_t rp1dsi_dma_isr(int irq, void *dev);
+void rp1dsi_dma_vblank_ctrl(struct rp1_dsi *dsi, int enable);
+
+/* ---------------------------------------------------------------------- */
+/* Functions to control the MIPICFG block and check RP1 platform		  */
+
+void rp1dsi_mipicfg_setup(struct rp1_dsi *dsi);
+
+/* ---------------------------------------------------------------------- */
+/* Functions to control the SNPS D-PHY and DSI block setup		  */
+
+void rp1dsi_dsi_setup(struct rp1_dsi *dsi, struct drm_display_mode const *mode);
+void rp1dsi_dsi_send(struct rp1_dsi *dsi, u32 header, int len, const u8 *buf,
+		bool use_lpm, bool req_ack);
+int  rp1dsi_dsi_recv(struct rp1_dsi *dsi, int len, u8 *buf);
+void rp1dsi_dsi_set_cmdmode(struct rp1_dsi *dsi, int cmd_mode);
+void rp1dsi_dsi_stop(struct rp1_dsi *dsi);
+
+#endif
+
diff --git a/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi_dma.c b/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi_dma.c
new file mode 100644
index 000000000000..86fa351562b4
--- /dev/null
+++ b/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi_dma.c
@@ -0,0 +1,455 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * DRM Driver for DSI output on Raspberry Pi RP1
+ *
+ * Copyright (c) 2023 Raspberry Pi Limited.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+
+#include <drm/drm_fourcc.h>
+#include <drm/drm_print.h>
+#include <drm/drm_vblank.h>
+
+#include "rp1_dsi.h"
+
+// --- DPI DMA REGISTERS (derived from Argon firmware, via RP1 drivers/mipi, with corrections) ---
+
+// Control
+#define DPI_DMA_CONTROL				      0x0
+#define DPI_DMA_CONTROL_ARM_SHIFT		      0
+#define DPI_DMA_CONTROL_ARM_MASK		      BIT(DPI_DMA_CONTROL_ARM_SHIFT)
+#define DPI_DMA_CONTROL_ALIGN16_SHIFT		      2
+#define DPI_DMA_CONTROL_ALIGN16_MASK		      BIT(DPI_DMA_CONTROL_ALIGN16_SHIFT)
+#define DPI_DMA_CONTROL_AUTO_REPEAT_SHIFT	      1
+#define DPI_DMA_CONTROL_AUTO_REPEAT_MASK	      BIT(DPI_DMA_CONTROL_AUTO_REPEAT_SHIFT)
+#define DPI_DMA_CONTROL_HIGH_WATER_SHIFT	      3
+#define DPI_DMA_CONTROL_HIGH_WATER_MASK		      (0x1FF << DPI_DMA_CONTROL_HIGH_WATER_SHIFT)
+#define DPI_DMA_CONTROL_DEN_POL_SHIFT		      12
+#define DPI_DMA_CONTROL_DEN_POL_MASK		      BIT(DPI_DMA_CONTROL_DEN_POL_SHIFT)
+#define DPI_DMA_CONTROL_HSYNC_POL_SHIFT		      13
+#define DPI_DMA_CONTROL_HSYNC_POL_MASK		      BIT(DPI_DMA_CONTROL_HSYNC_POL_SHIFT)
+#define DPI_DMA_CONTROL_VSYNC_POL_SHIFT		      14
+#define DPI_DMA_CONTROL_VSYNC_POL_MASK		      BIT(DPI_DMA_CONTROL_VSYNC_POL_SHIFT)
+#define DPI_DMA_CONTROL_COLORM_SHIFT		      15
+#define DPI_DMA_CONTROL_COLORM_MASK		      BIT(DPI_DMA_CONTROL_COLORM_SHIFT)
+#define DPI_DMA_CONTROL_SHUTDN_SHIFT		      16
+#define DPI_DMA_CONTROL_SHUTDN_MASK		      BIT(DPI_DMA_CONTROL_SHUTDN_SHIFT)
+#define DPI_DMA_CONTROL_HBP_EN_SHIFT		      17
+#define DPI_DMA_CONTROL_HBP_EN_MASK		      BIT(DPI_DMA_CONTROL_HBP_EN_SHIFT)
+#define DPI_DMA_CONTROL_HFP_EN_SHIFT		      18
+#define DPI_DMA_CONTROL_HFP_EN_MASK		      BIT(DPI_DMA_CONTROL_HFP_EN_SHIFT)
+#define DPI_DMA_CONTROL_VBP_EN_SHIFT		      19
+#define DPI_DMA_CONTROL_VBP_EN_MASK		      BIT(DPI_DMA_CONTROL_VBP_EN_SHIFT)
+#define DPI_DMA_CONTROL_VFP_EN_SHIFT		      20
+#define DPI_DMA_CONTROL_VFP_EN_MASK		      BIT(DPI_DMA_CONTROL_VFP_EN_SHIFT)
+#define DPI_DMA_CONTROL_HSYNC_EN_SHIFT		      21
+#define DPI_DMA_CONTROL_HSYNC_EN_MASK		      BIT(DPI_DMA_CONTROL_HSYNC_EN_SHIFT)
+#define DPI_DMA_CONTROL_VSYNC_EN_SHIFT		      22
+#define DPI_DMA_CONTROL_VSYNC_EN_MASK		      BIT(DPI_DMA_CONTROL_VSYNC_EN_SHIFT)
+#define DPI_DMA_CONTROL_FORCE_IMMED_SHIFT	      23
+#define DPI_DMA_CONTROL_FORCE_IMMED_MASK	      BIT(DPI_DMA_CONTROL_FORCE_IMMED_SHIFT)
+#define DPI_DMA_CONTROL_FORCE_DRAIN_SHIFT	      24
+#define DPI_DMA_CONTROL_FORCE_DRAIN_MASK	      BIT(DPI_DMA_CONTROL_FORCE_DRAIN_SHIFT)
+#define DPI_DMA_CONTROL_FORCE_EMPTY_SHIFT	      25
+#define DPI_DMA_CONTROL_FORCE_EMPTY_MASK	      BIT(DPI_DMA_CONTROL_FORCE_EMPTY_SHIFT)
+
+// IRQ_ENABLES
+#define DPI_DMA_IRQ_EN				      0x04
+#define DPI_DMA_IRQ_EN_DMA_READY_SHIFT		      0
+#define DPI_DMA_IRQ_EN_DMA_READY_MASK		      BIT(DPI_DMA_IRQ_EN_DMA_READY_SHIFT)
+#define DPI_DMA_IRQ_EN_UNDERFLOW_SHIFT		      1
+#define DPI_DMA_IRQ_EN_UNDERFLOW_MASK		      BIT(DPI_DMA_IRQ_EN_UNDERFLOW_SHIFT)
+#define DPI_DMA_IRQ_EN_FRAME_START_SHIFT	      2
+#define DPI_DMA_IRQ_EN_FRAME_START_MASK		      BIT(DPI_DMA_IRQ_EN_FRAME_START_SHIFT)
+#define DPI_DMA_IRQ_EN_AFIFO_EMPTY_SHIFT	      3
+#define DPI_DMA_IRQ_EN_AFIFO_EMPTY_MASK		      BIT(DPI_DMA_IRQ_EN_AFIFO_EMPTY_SHIFT)
+#define DPI_DMA_IRQ_EN_TE_SHIFT			      4
+#define DPI_DMA_IRQ_EN_TE_MASK			      BIT(DPI_DMA_IRQ_EN_TE_SHIFT)
+#define DPI_DMA_IRQ_EN_ERROR_SHIFT		      5
+#define DPI_DMA_IRQ_EN_ERROR_MASK		      BIT(DPI_DMA_IRQ_EN_ERROR_SHIFT)
+#define DPI_DMA_IRQ_EN_MATCH_SHIFT		      6
+#define DPI_DMA_IRQ_EN_MATCH_MASK		      BIT(DPI_DMA_IRQ_EN_MATCH_SHIFT)
+#define DPI_DMA_IRQ_EN_MATCH_LINE_SHIFT		      16
+#define DPI_DMA_IRQ_EN_MATCH_LINE_MASK		      (0xFFF << DPI_DMA_IRQ_EN_MATCH_LINE_SHIFT)
+
+// IRQ_FLAGS
+#define DPI_DMA_IRQ_FLAGS			      0x08
+#define DPI_DMA_IRQ_FLAGS_DMA_READY_SHIFT	      0
+#define DPI_DMA_IRQ_FLAGS_DMA_READY_MASK	      BIT(DPI_DMA_IRQ_FLAGS_DMA_READY_SHIFT)
+#define DPI_DMA_IRQ_FLAGS_UNDERFLOW_SHIFT	      1
+#define DPI_DMA_IRQ_FLAGS_UNDERFLOW_MASK	      BIT(DPI_DMA_IRQ_FLAGS_UNDERFLOW_SHIFT)
+#define DPI_DMA_IRQ_FLAGS_FRAME_START_SHIFT	      2
+#define DPI_DMA_IRQ_FLAGS_FRAME_START_MASK	      BIT(DPI_DMA_IRQ_FLAGS_FRAME_START_SHIFT)
+#define DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_SHIFT	      3
+#define DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_MASK	      BIT(DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_SHIFT)
+#define DPI_DMA_IRQ_FLAGS_TE_SHIFT		      4
+#define DPI_DMA_IRQ_FLAGS_TE_MASK		      BIT(DPI_DMA_IRQ_FLAGS_TE_SHIFT)
+#define DPI_DMA_IRQ_FLAGS_ERROR_SHIFT		      5
+#define DPI_DMA_IRQ_FLAGS_ERROR_MASK		      BIT(DPI_DMA_IRQ_FLAGS_ERROR_SHIFT)
+#define DPI_DMA_IRQ_FLAGS_MATCH_SHIFT		      6
+#define DPI_DMA_IRQ_FLAGS_MATCH_MASK		      BIT(DPI_DMA_IRQ_FLAGS_MATCH_SHIFT)
+
+// QOS
+#define DPI_DMA_QOS				      0xC
+#define DPI_DMA_QOS_DQOS_SHIFT			      0
+#define DPI_DMA_QOS_DQOS_MASK			      (0xF << DPI_DMA_QOS_DQOS_SHIFT)
+#define DPI_DMA_QOS_ULEV_SHIFT			      4
+#define DPI_DMA_QOS_ULEV_MASK			      (0xF << DPI_DMA_QOS_ULEV_SHIFT)
+#define DPI_DMA_QOS_UQOS_SHIFT			      8
+#define DPI_DMA_QOS_UQOS_MASK			      (0xF << DPI_DMA_QOS_UQOS_SHIFT)
+#define DPI_DMA_QOS_LLEV_SHIFT			      12
+#define DPI_DMA_QOS_LLEV_MASK			      (0xF << DPI_DMA_QOS_LLEV_SHIFT)
+#define DPI_DMA_QOS_LQOS_SHIFT			      16
+#define DPI_DMA_QOS_LQOS_MASK			      (0xF << DPI_DMA_QOS_LQOS_SHIFT)
+
+// Panics
+#define DPI_DMA_PANICS				     0x38
+#define DPI_DMA_PANICS_UPPER_COUNT_SHIFT	     0
+#define DPI_DMA_PANICS_UPPER_COUNT_MASK		     \
+				(0x0000FFFF << DPI_DMA_PANICS_UPPER_COUNT_SHIFT)
+#define DPI_DMA_PANICS_LOWER_COUNT_SHIFT	     16
+#define DPI_DMA_PANICS_LOWER_COUNT_MASK		     \
+				(0x0000FFFF << DPI_DMA_PANICS_LOWER_COUNT_SHIFT)
+
+// DMA Address Lower:
+#define DPI_DMA_DMA_ADDR_L			     0x10
+
+// DMA Address Upper:
+#define DPI_DMA_DMA_ADDR_H			     0x40
+
+// DMA stride
+#define DPI_DMA_DMA_STRIDE			     0x14
+
+// Visible Area
+#define DPI_DMA_VISIBLE_AREA			     0x18
+#define DPI_DMA_VISIBLE_AREA_ROWSM1_SHIFT     0
+#define DPI_DMA_VISIBLE_AREA_ROWSM1_MASK     (0x0FFF << DPI_DMA_VISIBLE_AREA_ROWSM1_SHIFT)
+#define DPI_DMA_VISIBLE_AREA_COLSM1_SHIFT    16
+#define DPI_DMA_VISIBLE_AREA_COLSM1_MASK     (0x0FFF << DPI_DMA_VISIBLE_AREA_COLSM1_SHIFT)
+
+// Sync width
+#define DPI_DMA_SYNC_WIDTH   0x1C
+#define DPI_DMA_SYNC_WIDTH_ROWSM1_SHIFT	 0
+#define DPI_DMA_SYNC_WIDTH_ROWSM1_MASK	 (0x0FFF << DPI_DMA_SYNC_WIDTH_ROWSM1_SHIFT)
+#define DPI_DMA_SYNC_WIDTH_COLSM1_SHIFT	 16
+#define DPI_DMA_SYNC_WIDTH_COLSM1_MASK	 (0x0FFF << DPI_DMA_SYNC_WIDTH_COLSM1_SHIFT)
+
+// Back porch
+#define DPI_DMA_BACK_PORCH   0x20
+#define DPI_DMA_BACK_PORCH_ROWSM1_SHIFT	 0
+#define DPI_DMA_BACK_PORCH_ROWSM1_MASK	 (0x0FFF << DPI_DMA_BACK_PORCH_ROWSM1_SHIFT)
+#define DPI_DMA_BACK_PORCH_COLSM1_SHIFT	 16
+#define DPI_DMA_BACK_PORCH_COLSM1_MASK	 (0x0FFF << DPI_DMA_BACK_PORCH_COLSM1_SHIFT)
+
+// Front porch
+#define DPI_DMA_FRONT_PORCH  0x24
+#define DPI_DMA_FRONT_PORCH_ROWSM1_SHIFT     0
+#define DPI_DMA_FRONT_PORCH_ROWSM1_MASK	 (0x0FFF << DPI_DMA_FRONT_PORCH_ROWSM1_SHIFT)
+#define DPI_DMA_FRONT_PORCH_COLSM1_SHIFT     16
+#define DPI_DMA_FRONT_PORCH_COLSM1_MASK	 (0x0FFF << DPI_DMA_FRONT_PORCH_COLSM1_SHIFT)
+
+// Input masks
+#define DPI_DMA_IMASK	 0x2C
+#define DPI_DMA_IMASK_R_SHIFT	 0
+#define DPI_DMA_IMASK_R_MASK	 (0x3FF << DPI_DMA_IMASK_R_SHIFT)
+#define DPI_DMA_IMASK_G_SHIFT	 10
+#define DPI_DMA_IMASK_G_MASK	 (0x3FF << DPI_DMA_IMASK_G_SHIFT)
+#define DPI_DMA_IMASK_B_SHIFT	 20
+#define DPI_DMA_IMASK_B_MASK	 (0x3FF << DPI_DMA_IMASK_B_SHIFT)
+
+// Output Masks
+#define DPI_DMA_OMASK	 0x30
+#define DPI_DMA_OMASK_R_SHIFT	 0
+#define DPI_DMA_OMASK_R_MASK	 (0x3FF << DPI_DMA_OMASK_R_SHIFT)
+#define DPI_DMA_OMASK_G_SHIFT	 10
+#define DPI_DMA_OMASK_G_MASK	 (0x3FF << DPI_DMA_OMASK_G_SHIFT)
+#define DPI_DMA_OMASK_B_SHIFT	 20
+#define DPI_DMA_OMASK_B_MASK	 (0x3FF << DPI_DMA_OMASK_B_SHIFT)
+
+// Shifts
+#define DPI_DMA_SHIFT	 0x28
+#define DPI_DMA_SHIFT_IR_SHIFT	 0
+#define DPI_DMA_SHIFT_IR_MASK	 (0x1F << DPI_DMA_SHIFT_IR_SHIFT)
+#define DPI_DMA_SHIFT_IG_SHIFT	 5
+#define DPI_DMA_SHIFT_IG_MASK	 (0x1F << DPI_DMA_SHIFT_IG_SHIFT)
+#define DPI_DMA_SHIFT_IB_SHIFT	 10
+#define DPI_DMA_SHIFT_IB_MASK	 (0x1F << DPI_DMA_SHIFT_IB_SHIFT)
+#define DPI_DMA_SHIFT_OR_SHIFT	 15
+#define DPI_DMA_SHIFT_OR_MASK	 (0x1F << DPI_DMA_SHIFT_OR_SHIFT)
+#define DPI_DMA_SHIFT_OG_SHIFT	 20
+#define DPI_DMA_SHIFT_OG_MASK	 (0x1F << DPI_DMA_SHIFT_OG_SHIFT)
+#define DPI_DMA_SHIFT_OB_SHIFT	 25
+#define DPI_DMA_SHIFT_OB_MASK	 (0x1F << DPI_DMA_SHIFT_OB_SHIFT)
+
+// Scaling
+#define DPI_DMA_RGBSZ	 0x34
+#define DPI_DMA_RGBSZ_BPP_SHIFT	 16
+#define DPI_DMA_RGBSZ_BPP_MASK	 (0x3 << DPI_DMA_RGBSZ_BPP_SHIFT)
+#define DPI_DMA_RGBSZ_R_SHIFT	 0
+#define DPI_DMA_RGBSZ_R_MASK	 (0xF << DPI_DMA_RGBSZ_R_SHIFT)
+#define DPI_DMA_RGBSZ_G_SHIFT	 4
+#define DPI_DMA_RGBSZ_G_MASK	 (0xF << DPI_DMA_RGBSZ_G_SHIFT)
+#define DPI_DMA_RGBSZ_B_SHIFT	 8
+#define DPI_DMA_RGBSZ_B_MASK	 (0xF << DPI_DMA_RGBSZ_B_SHIFT)
+
+// Status
+#define DPI_DMA_STATUS  0x3c
+
+#define BITS(field, val) (((val) << (field ## _SHIFT)) & (field ## _MASK))
+
+static unsigned int rp1dsi_dma_read(struct rp1_dsi *dsi, unsigned int reg)
+{
+	void __iomem *addr = dsi->hw_base[RP1DSI_HW_BLOCK_DMA] + reg;
+
+	return readl(addr);
+}
+
+static void rp1dsi_dma_write(struct rp1_dsi *dsi, unsigned int reg, unsigned int val)
+{
+	void __iomem *addr = dsi->hw_base[RP1DSI_HW_BLOCK_DMA] + reg;
+
+	writel(val, addr);
+}
+
+int rp1dsi_dma_busy(struct rp1_dsi *dsi)
+{
+	return (rp1dsi_dma_read(dsi, DPI_DMA_STATUS) & 0xF8F) ? 1 : 0;
+}
+
+/* Table of supported input (in-memory/DMA) pixel formats. */
+struct rp1dsi_ipixfmt {
+	u32 format; /* DRM format code                           */
+	u32 mask;   /* RGB masks (10 bits each, left justified)  */
+	u32 shift;  /* RGB MSB positions in the memory word      */
+	u32 rgbsz;  /* Shifts used for scaling; also (BPP/8-1)   */
+};
+
+#define IMASK_RGB(r, g, b)	(BITS(DPI_DMA_IMASK_R, r) | \
+				 BITS(DPI_DMA_IMASK_G, g) |  \
+				 BITS(DPI_DMA_IMASK_B, b))
+#define ISHIFT_RGB(r, g, b)	(BITS(DPI_DMA_SHIFT_IR, r) | \
+				 BITS(DPI_DMA_SHIFT_IG, g) | \
+				 BITS(DPI_DMA_SHIFT_IB, b))
+
+static const struct rp1dsi_ipixfmt my_formats[] = {
+	{
+		.format = DRM_FORMAT_XRGB8888,
+		.mask   = IMASK_RGB(0x3fc, 0x3fc, 0x3fc),
+		.shift  = ISHIFT_RGB(23, 15, 7),
+		.rgbsz  = BITS(DPI_DMA_RGBSZ_BPP, 3),
+	},
+	{
+		.format = DRM_FORMAT_XBGR8888,
+		.mask   = IMASK_RGB(0x3fc, 0x3fc, 0x3fc),
+		.shift  = ISHIFT_RGB(7, 15, 23),
+		.rgbsz  = BITS(DPI_DMA_RGBSZ_BPP, 3),
+	},
+	{
+		.format = DRM_FORMAT_ARGB8888,
+		.mask   = IMASK_RGB(0x3fc, 0x3fc, 0x3fc),
+		.shift  = ISHIFT_RGB(23, 15, 7),
+		.rgbsz  = BITS(DPI_DMA_RGBSZ_BPP, 3),
+	},
+	{
+		.format = DRM_FORMAT_ABGR8888,
+		.mask   = IMASK_RGB(0x3fc, 0x3fc, 0x3fc),
+		.shift  = ISHIFT_RGB(7, 15, 23),
+		.rgbsz  = BITS(DPI_DMA_RGBSZ_BPP, 3),
+	},
+	{
+		.format = DRM_FORMAT_RGB888,
+		.mask   = IMASK_RGB(0x3fc, 0x3fc, 0x3fc),
+		.shift  = ISHIFT_RGB(23, 15, 7),
+		.rgbsz  = BITS(DPI_DMA_RGBSZ_BPP, 2),
+	},
+	{
+		.format = DRM_FORMAT_BGR888,
+		.mask   = IMASK_RGB(0x3fc, 0x3fc, 0x3fc),
+		.shift  = ISHIFT_RGB(7, 15, 23),
+		.rgbsz  = BITS(DPI_DMA_RGBSZ_BPP, 2),
+	},
+	{
+		.format = DRM_FORMAT_RGB565,
+		.mask   = IMASK_RGB(0x3e0, 0x3f0, 0x3e0),
+		.shift  = ISHIFT_RGB(15, 10, 4),
+		.rgbsz  = BITS(DPI_DMA_RGBSZ_R, 5) | BITS(DPI_DMA_RGBSZ_G, 6) |
+			  BITS(DPI_DMA_RGBSZ_B, 5) | BITS(DPI_DMA_RGBSZ_BPP, 1),
+	}
+};
+
+/* Choose the internal on-the-bus DPI format as expected by DSI Host. */
+static u32 get_omask_oshift(enum mipi_dsi_pixel_format fmt, u32 *oshift)
+{
+	switch (fmt) {
+	case MIPI_DSI_FMT_RGB565:
+		*oshift = BITS(DPI_DMA_SHIFT_OR, 15) |
+			  BITS(DPI_DMA_SHIFT_OG, 10) |
+			  BITS(DPI_DMA_SHIFT_OB, 4);
+		return BITS(DPI_DMA_OMASK_R, 0x3e0) |
+		       BITS(DPI_DMA_OMASK_G, 0x3f0) |
+		       BITS(DPI_DMA_OMASK_B, 0x3e0);
+	case MIPI_DSI_FMT_RGB666_PACKED:
+		*oshift = BITS(DPI_DMA_SHIFT_OR, 17) |
+			  BITS(DPI_DMA_SHIFT_OG, 11) |
+			  BITS(DPI_DMA_SHIFT_OB, 5);
+		return BITS(DPI_DMA_OMASK_R, 0x3f0) |
+		       BITS(DPI_DMA_OMASK_G, 0x3f0) |
+		       BITS(DPI_DMA_OMASK_B, 0x3f0);
+	case MIPI_DSI_FMT_RGB666:
+		*oshift = BITS(DPI_DMA_SHIFT_OR, 21) |
+			  BITS(DPI_DMA_SHIFT_OG, 13) |
+			  BITS(DPI_DMA_SHIFT_OB, 5);
+		return BITS(DPI_DMA_OMASK_R, 0x3f0) |
+		       BITS(DPI_DMA_OMASK_G, 0x3f0) |
+		       BITS(DPI_DMA_OMASK_B, 0x3f0);
+	default:
+		*oshift = BITS(DPI_DMA_SHIFT_OR, 23) |
+			  BITS(DPI_DMA_SHIFT_OG, 15) |
+			  BITS(DPI_DMA_SHIFT_OB, 7);
+		return BITS(DPI_DMA_OMASK_R, 0x3fc) |
+		       BITS(DPI_DMA_OMASK_G, 0x3fc) |
+		       BITS(DPI_DMA_OMASK_B, 0x3fc);
+	}
+}
+
+void rp1dsi_dma_setup(struct rp1_dsi *dsi,
+		      u32 in_format, enum mipi_dsi_pixel_format out_format,
+		     struct drm_display_mode const *mode)
+{
+	u32 oshift;
+	int i;
+
+	/*
+	 * Configure all DSI/DPI/DMA block registers, except base address.
+	 * DMA will not actually start until a FB base address is specified
+	 * using rp1dsi_dma_update().
+	 */
+
+	rp1dsi_dma_write(dsi, DPI_DMA_VISIBLE_AREA,
+			 BITS(DPI_DMA_VISIBLE_AREA_ROWSM1, mode->vdisplay - 1) |
+			 BITS(DPI_DMA_VISIBLE_AREA_COLSM1, mode->hdisplay - 1));
+
+	rp1dsi_dma_write(dsi, DPI_DMA_SYNC_WIDTH,
+			 BITS(DPI_DMA_SYNC_WIDTH_ROWSM1, mode->vsync_end - mode->vsync_start - 1) |
+			 BITS(DPI_DMA_SYNC_WIDTH_COLSM1, mode->hsync_end - mode->hsync_start - 1));
+
+	/* In the DPIDMA registers, "back porch" time includes sync width */
+	rp1dsi_dma_write(dsi, DPI_DMA_BACK_PORCH,
+			 BITS(DPI_DMA_BACK_PORCH_ROWSM1, mode->vtotal - mode->vsync_start - 1) |
+			 BITS(DPI_DMA_BACK_PORCH_COLSM1, mode->htotal - mode->hsync_start - 1));
+
+	rp1dsi_dma_write(dsi, DPI_DMA_FRONT_PORCH,
+			 BITS(DPI_DMA_FRONT_PORCH_ROWSM1, mode->vsync_start - mode->vdisplay - 1) |
+			 BITS(DPI_DMA_FRONT_PORCH_COLSM1, mode->hsync_start - mode->hdisplay - 1));
+
+	/* Input to output pixel format conversion */
+	for (i = 0; i < ARRAY_SIZE(my_formats); ++i) {
+		if (my_formats[i].format == in_format)
+			break;
+	}
+	if (i >= ARRAY_SIZE(my_formats)) {
+		drm_err(dsi->drm, "%s: bad input format\n", __func__);
+		i = 0;
+	}
+	rp1dsi_dma_write(dsi, DPI_DMA_IMASK, my_formats[i].mask);
+	rp1dsi_dma_write(dsi, DPI_DMA_OMASK, get_omask_oshift(out_format, &oshift));
+	rp1dsi_dma_write(dsi, DPI_DMA_SHIFT, my_formats[i].shift | oshift);
+	if (out_format == MIPI_DSI_FMT_RGB888)
+		rp1dsi_dma_write(dsi, DPI_DMA_RGBSZ, my_formats[i].rgbsz);
+	else
+		rp1dsi_dma_write(dsi, DPI_DMA_RGBSZ, my_formats[i].rgbsz & DPI_DMA_RGBSZ_BPP_MASK);
+
+	rp1dsi_dma_write(dsi, DPI_DMA_QOS,
+			 BITS(DPI_DMA_QOS_DQOS, 0x0) |
+			 BITS(DPI_DMA_QOS_ULEV, 0xb) |
+			 BITS(DPI_DMA_QOS_UQOS, 0x2) |
+			 BITS(DPI_DMA_QOS_LLEV, 0x8) |
+			 BITS(DPI_DMA_QOS_LQOS, 0x7));
+
+	rp1dsi_dma_write(dsi, DPI_DMA_IRQ_FLAGS, -1);
+	rp1dsi_dma_vblank_ctrl(dsi, 1);
+
+	i = rp1dsi_dma_busy(dsi);
+	if (i)
+		drm_err(dsi->drm, "RP1DSI: Unexpectedly busy at start!");
+
+	rp1dsi_dma_write(dsi, DPI_DMA_CONTROL,
+			 BITS(DPI_DMA_CONTROL_ARM, (i == 0)) |
+			 BITS(DPI_DMA_CONTROL_AUTO_REPEAT, 1) |
+			 BITS(DPI_DMA_CONTROL_HIGH_WATER, 448) |
+			 BITS(DPI_DMA_CONTROL_DEN_POL, 0) |
+			 BITS(DPI_DMA_CONTROL_HSYNC_POL, 0) |
+			 BITS(DPI_DMA_CONTROL_VSYNC_POL, 0) |
+			 BITS(DPI_DMA_CONTROL_COLORM, 0) |
+			 BITS(DPI_DMA_CONTROL_SHUTDN, 0) |
+			 BITS(DPI_DMA_CONTROL_HBP_EN, 1) |
+			 BITS(DPI_DMA_CONTROL_HFP_EN, 1) |
+			 BITS(DPI_DMA_CONTROL_VBP_EN, 1) |
+			 BITS(DPI_DMA_CONTROL_VFP_EN, 1) |
+			 BITS(DPI_DMA_CONTROL_HSYNC_EN, 1) |
+			 BITS(DPI_DMA_CONTROL_VSYNC_EN, 1));
+}
+
+void rp1dsi_dma_update(struct rp1_dsi *dsi, dma_addr_t addr, u32 offset, u32 stride)
+{
+	/*
+	 * Update STRIDE, DMAH and DMAL only. When called after rp1dsi_dma_setup(),
+	 * DMA starts immediately; if already running, the buffer will flip at
+	 * the next vertical sync event.
+	 */
+	u64 a = addr + offset;
+
+	rp1dsi_dma_write(dsi, DPI_DMA_DMA_STRIDE, stride);
+	rp1dsi_dma_write(dsi, DPI_DMA_DMA_ADDR_H, a >> 32);
+	rp1dsi_dma_write(dsi, DPI_DMA_DMA_ADDR_L, a & 0xFFFFFFFFu);
+}
+
+void rp1dsi_dma_stop(struct rp1_dsi *dsi)
+{
+	/*
+	 * Stop DMA by turning off the Auto-Repeat flag, and wait up to 100ms for
+	 * the current and any queued frame to end. "Force drain" flags are not used,
+	 * as they seem to prevent DMA from re-starting properly; it's safer to wait.
+	 */
+	u32 ctrl;
+
+	reinit_completion(&dsi->finished);
+	ctrl = rp1dsi_dma_read(dsi, DPI_DMA_CONTROL);
+	ctrl &= ~(DPI_DMA_CONTROL_ARM_MASK | DPI_DMA_CONTROL_AUTO_REPEAT_MASK);
+	rp1dsi_dma_write(dsi, DPI_DMA_CONTROL, ctrl);
+	if (!wait_for_completion_timeout(&dsi->finished, HZ / 10))
+		drm_err(dsi->drm, "%s: timed out waiting for idle\n", __func__);
+	rp1dsi_dma_write(dsi, DPI_DMA_IRQ_EN, 0);
+}
+
+void rp1dsi_dma_vblank_ctrl(struct rp1_dsi *dsi, int enable)
+{
+	rp1dsi_dma_write(dsi, DPI_DMA_IRQ_EN,
+			 BITS(DPI_DMA_IRQ_EN_AFIFO_EMPTY, 1)      |
+			 BITS(DPI_DMA_IRQ_EN_UNDERFLOW, 1)        |
+			 BITS(DPI_DMA_IRQ_EN_DMA_READY, !!enable) |
+			 BITS(DPI_DMA_IRQ_EN_MATCH_LINE, 4095));
+}
+
+irqreturn_t rp1dsi_dma_isr(int irq, void *dev)
+{
+	struct rp1_dsi *dsi = dev;
+	u32 u = rp1dsi_dma_read(dsi, DPI_DMA_IRQ_FLAGS);
+
+	if (u) {
+		rp1dsi_dma_write(dsi, DPI_DMA_IRQ_FLAGS, u);
+		if (dsi) {
+			if (u & DPI_DMA_IRQ_FLAGS_UNDERFLOW_MASK)
+				drm_err_ratelimited(dsi->drm,
+						    "Underflow! (panics=0x%08x)\n",
+						    rp1dsi_dma_read(dsi, DPI_DMA_PANICS));
+			if (u & DPI_DMA_IRQ_FLAGS_DMA_READY_MASK)
+				drm_crtc_handle_vblank(&dsi->pipe.crtc);
+			if (u & DPI_DMA_IRQ_FLAGS_AFIFO_EMPTY_MASK)
+				complete(&dsi->finished);
+		}
+	}
+	return u ? IRQ_HANDLED : IRQ_NONE;
+}
diff --git a/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi_dsi.c b/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi_dsi.c
new file mode 100644
index 000000000000..3ef6497086d2
--- /dev/null
+++ b/drivers/gpu/drm/rp1/rp1-dsi/rp1_dsi_dsi.c
@@ -0,0 +1,674 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * DRM Driver for DSI output on Raspberry Pi RP1
+ *
+ * Copyright (c) 2023 Raspberry Pi Limited.
+ */
+
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/math64.h>
+#include <linux/platform_device.h>
+#include <linux/rp1_platform.h>
+#include "drm/drm_print.h"
+
+#include "rp1_dsi.h"
+
+/* ------------------------------- Synopsis DSI ------------------------ */
+#define DSI_VERSION_CFG			0x000
+#define DSI_PWR_UP			0x004
+#define DSI_CLKMGR_CFG			0x008
+#define DSI_DPI_VCID			0x00C
+#define DSI_DPI_COLOR_CODING		0x010
+#define DSI_DPI_CFG_POL			0x014
+#define DSI_DPI_LP_CMD_TIM		0x018
+#define DSI_DBI_VCID			0x01C
+#define DSI_DBI_CFG			0x020
+#define DSI_DBI_PARTITIONING_EN		0x024
+#define DSI_DBI_CMDSIZE			0x028
+#define DSI_PCKHDL_CFG			0x02C
+ #define DSI_PCKHDL_EOTP_TX_EN		BIT(0)
+ #define DSI_PCKHDL_BTA_EN		BIT(2)
+#define DSI_GEN_VCID			0x030
+#define DSI_MODE_CFG			0x034
+#define DSI_VID_MODE_CFG		0x038
+ #define DSI_VID_MODE_LP_CMD_EN		BIT(15)
+ #define DSI_VID_MODE_FRAME_BTA_ACK_EN	BIT(14)
+ #define DSI_VID_MODE_LP_HFP_EN		BIT(13)
+ #define DSI_VID_MODE_LP_HBP_EN		BIT(12)
+ #define DSI_VID_MODE_LP_VACT_EN	BIT(11)
+ #define DSI_VID_MODE_LP_VFP_EN		BIT(10)
+ #define DSI_VID_MODE_LP_VBP_EN		BIT(9)
+ #define DSI_VID_MODE_LP_VSA_EN		BIT(8)
+ #define DSI_VID_MODE_SYNC_PULSES	0
+ #define DSI_VID_MODE_SYNC_EVENTS	1
+ #define DSI_VID_MODE_BURST		2
+#define DSI_VID_PKT_SIZE		0x03C
+#define DSI_VID_NUM_CHUNKS		0x040
+#define DSI_VID_NULL_SIZE		0x044
+#define DSI_VID_HSA_TIME		0x048
+#define DSI_VID_HBP_TIME		0x04C
+#define DSI_VID_HLINE_TIME		0x050
+#define DSI_VID_VSA_LINES		0x054
+#define DSI_VID_VBP_LINES		0x058
+#define DSI_VID_VFP_LINES		0x05C
+#define DSI_VID_VACTIVE_LINES		0x060
+#define DSI_EDPI_CMD_SIZE		0x064
+#define DSI_CMD_MODE_CFG		0x068
+ #define DSI_CMD_MODE_ALL_LP		0x10f7f00
+ #define DSI_CMD_MODE_ACK_RQST_EN	BIT(1)
+#define DSI_GEN_HDR			0x06C
+#define DSI_GEN_PLD_DATA		0x070
+#define DSI_CMD_PKT_STATUS		0x074
+#define DSI_TO_CNT_CFG			0x078
+#define DSI_HS_RD_TO_CNT		0x07C
+#define DSI_LP_RD_TO_CNT		0x080
+#define DSI_HS_WR_TO_CNT		0x084
+#define DSI_LP_WR_TO_CNT		0x088
+#define DSI_BTA_TO_CNT			0x08C
+#define DSI_SDF_3D			0x090
+#define DSI_LPCLK_CTRL			0x094
+#define DSI_PHY_TMR_LPCLK_CFG		0x098
+ #define DSI_PHY_TMR_HS2LP_LSB		16
+ #define DSI_PHY_TMR_LP2HS_LSB		0
+#define DSI_PHY_TMR_CFG			0x09C
+#define DSI_PHY_TMR_RD_CFG		0x0F4
+#define DSI_PHYRSTZ			0x0A0
+ #define DSI_PHYRSTZ_SHUTDOWNZ_LSB	0
+ #define DSI_PHYRSTZ_SHUTDOWNZ_BITS	BIT(DSI_PHYRSTZ_SHUTDOWNZ_LSB)
+ #define DSI_PHYRSTZ_RSTZ_LSB		1
+ #define DSI_PHYRSTZ_RSTZ_BITS		BIT(DSI_PHYRSTZ_RSTZ_LSB)
+ #define DSI_PHYRSTZ_ENABLECLK_LSB	2
+ #define DSI_PHYRSTZ_ENABLECLK_BITS	BIT(DSI_PHYRSTZ_ENABLECLK_LSB)
+ #define DSI_PHYRSTZ_FORCEPLL_LSB	3
+ #define DSI_PHYRSTZ_FORCEPLL_BITS	BIT(DSI_PHYRSTZ_FORCEPLL_LSB)
+#define DSI_PHY_IF_CFG			0x0A4
+#define DSI_PHY_ULPS_CTRL		0x0A8
+#define DSI_PHY_TX_TRIGGERS		0x0AC
+#define DSI_PHY_STATUS			0x0B0
+
+#define DSI_PHY_TST_CTRL0		0x0B4
+ #define DPHY_CTRL0_PHY_TESTCLK_LSB	1
+ #define DPHY_CTRL0_PHY_TESTCLK_BITS	BIT(DPHY_CTRL0_PHY_TESTCLK_LSB)
+ #define DPHY_CTRL0_PHY_TESTCLR_LSB	0
+ #define DPHY_CTRL0_PHY_TESTCLR_BITS	BIT(DPHY_CTRL0_PHY_TESTCLR_LSB)
+#define DSI_PHY_TST_CTRL1		0x0B8
+ #define DPHY_CTRL1_PHY_TESTDIN_LSB	0
+ #define DPHY_CTRL1_PHY_TESTDIN_BITS	(0xff << DPHY_CTRL1_PHY_TESTDIN_LSB)
+ #define DPHY_CTRL1_PHY_TESTDOUT_LSB	8
+ #define DPHY_CTRL1_PHY_TESTDOUT_BITS	(0xff << DPHY_CTRL1_PHY_TESTDOUT_LSB)
+ #define DPHY_CTRL1_PHY_TESTEN_LSB	16
+ #define DPHY_CTRL1_PHY_TESTEN_BITS	BIT(DPHY_CTRL1_PHY_TESTEN_LSB)
+#define DSI_INT_ST0			0x0BC
+#define DSI_INT_ST1			0x0C0
+#define DSI_INT_MASK0_CFG		0x0C4
+#define DSI_INT_MASK1_CFG		0x0C8
+#define DSI_PHY_CAL			0x0CC
+#define DSI_HEXP_NPKT_CLR		0x104
+#define DSI_HEXP_NPKT_SIZE		0x108
+#define DSI_VID_SHADOW_CTRL		0x100
+
+#define DSI_DPI_VCID_ACT		0x10C
+#define DSI_DPI_COLOR_CODING_ACT	0x110
+#define DSI_DPI_LP_CMD_TIM_ACT		0x118
+#define DSI_VID_MODE_CFG_ACT		0x138
+#define DSI_VID_PKT_SIZE_ACT		0x13C
+#define DSI_VID_NUM_CHUNKS_ACT		0x140
+#define DSI_VID_NULL_SIZE_ACT		0x144
+#define DSI_VID_HSA_TIME_ACT		0x148
+#define DSI_VID_HBP_TIME_ACT		0x14C
+#define DSI_VID_HLINE_TIME_ACT		0x150
+#define DSI_VID_VSA_LINES_ACT		0x154
+#define DSI_VID_VBP_LINES_ACT		0x158
+#define DSI_VID_VFP_LINES_ACT		0x15C
+#define DSI_VID_VACTIVE_LINES_ACT	0x160
+#define DSI_SDF_3D_CFG_ACT		0x190
+
+#define DSI_INT_FORCE0			0x0D8
+#define DSI_INT_FORCE1			0x0DC
+
+#define DSI_AUTO_ULPS_MODE		0x0E0
+#define DSI_AUTO_ULPS_ENTRY_DELAY       0x0E4
+#define DSI_AUTO_ULPS_WAKEUP_TIME       0x0E8
+#define DSI_EDPI_ADV_FEATURES		0x0EC
+
+#define DSI_DSC_PARAMETER		0x0F0
+
+/* PHY "test and control mode" registers */
+#define DPHY_PLL_BIAS_OFFSET		0x10
+ #define DPHY_PLL_BIAS_VCO_RANGE_LSB		3
+ #define DPHY_PLL_BIAS_USE_PROGRAMMED_VCO_RANGE	BIT(7)
+#define DPHY_PLL_CHARGE_PUMP_OFFSET	0x11
+#define DPHY_PLL_LPF_OFFSET		0x12
+#define DPHY_PLL_INPUT_DIV_OFFSET	0x17
+#define DPHY_PLL_LOOP_DIV_OFFSET	0x18
+#define DPHY_PLL_DIV_CTRL_OFFSET	0x19
+#define DPHY_CLK_PN_SWAP		0x35
+#define DPHY_HS_RX_CTRL_LANE0_OFFSET	0x44
+#define DPHY_D0_PN_SWAP			0x45
+#define DPHY_D1_PN_SWAP			0x55
+#define DPHY_D2_PN_SWAP			0x85
+#define DPHY_D3_PN_SWAP			0x95
+
+
+#define DSI_WRITE(reg, val)	writel((val), dsi->hw_base[RP1DSI_HW_BLOCK_DSI] + (reg))
+#define DSI_READ(reg)		readl(dsi->hw_base[RP1DSI_HW_BLOCK_DSI] + (reg))
+
+#define RPI_MIPICFG_CLK2FC_OFFSET		0x00000000
+#define RPI_MIPICFG_CFG_OFFSET			0x00000004
+#define RPI_MIPICFG_TE_OFFSET			0x00000008
+#define RPI_MIPICFG_DPHY_MONITOR_OFFSET		0x00000010
+#define RPI_MIPICFG_DPHY_CTRL_0_OFFSET		0x00000014
+#define RPI_MIPICFG_DPHY_CTRL_1_OFFSET		0x00000018
+#define RPI_MIPICFG_DPHY_CTRL_2_OFFSET		0x0000001c
+#define RPI_MIPICFG_DPHY_CTRL_3_OFFSET		0x00000020
+#define RPI_MIPICFG_DPHY_CTRL_4_OFFSET		0x00000024
+#define RPI_MIPICFG_INTR_OFFSET			0x00000028
+#define RPI_MIPICFG_INTE_OFFSET			0x0000002c
+ #define RPI_MIPICFG_INTE_DSI_DMA_BITS		0x00000002
+#define RPI_MIPICFG_INTF_OFFSET			0x00000030
+#define RPI_MIPICFG_INTS_OFFSET			0x00000034
+#define RPI_MIPICFG_BLOCK_ID_OFFSET		0x00000038
+#define RPI_MIPICFG_INSTANCE_ID_OFFSET		0x0000003c
+#define RPI_MIPICFG_RSTSEQ_AUTO_OFFSET		0x00000040
+#define RPI_MIPICFG_RSTSEQ_PARALLEL_OFFSET	0x00000044
+#define RPI_MIPICFG_RSTSEQ_CTRL_OFFSET		0x00000048
+#define RPI_MIPICFG_RSTSEQ_TRIG_OFFSET		0x0000004c
+#define RPI_MIPICFG_RSTSEQ_DONE_OFFSET		0x00000050
+#define RPI_MIPICFG_DFTSS_OFFSET		0x00000054
+
+#define CFG_WRITE(reg, val)	writel((val), dsi->hw_base[RP1DSI_HW_BLOCK_CFG] + (reg ## _OFFSET))
+#define CFG_READ(reg)		readl(dsi->hw_base[RP1DSI_HW_BLOCK_CFG] + (reg ## _OFFSET))
+
+/* ------------------------------- DPHY setup stuff ------------------------ */
+
+static void dphy_transaction(struct rp1_dsi *dsi, uint8_t test_code, uint8_t test_data)
+{
+	/*
+	 * See pg 101 of mipi dphy bidir databook
+	 * Assume we start with testclk high.
+	 * Each APB write takes at least 10ns and we ignore TESTDOUT
+	 * so there is no need for extra delays between the transitions.
+	 */
+
+	DSI_WRITE(DSI_PHY_TST_CTRL1, test_code | DPHY_CTRL1_PHY_TESTEN_BITS);
+	DSI_WRITE(DSI_PHY_TST_CTRL0, 0);
+	DSI_READ(DSI_PHY_TST_CTRL1); /* XXX possibly not needed */
+	DSI_WRITE(DSI_PHY_TST_CTRL1, test_data);
+	DSI_WRITE(DSI_PHY_TST_CTRL0, DPHY_CTRL0_PHY_TESTCLK_BITS);
+}
+
+static u64 dphy_get_div(u32 refclk, u64 vco_freq, u32 *ptr_m, u32 *ptr_n)
+{
+	/*
+	 * See pg 77-78 of dphy databook
+	 * fvco = m/n * refclk
+	 * with the limit
+	 * 40MHz >= fREFCLK / N >= 5MHz
+	 * M (multiplier) must be an even number between 2 and 300
+	 * N (input divider) must be an integer between 1 and 100
+	 *
+	 * In practice, given a 50MHz reference clock, it can produce any
+	 * multiple of 10MHz, 11.1111MHz, 12.5MHz, 14.286MHz or 16.667MHz
+	 * with < 1% error for all frequencies above 495MHz.
+	 *
+	 * vco_freq should be set to the lane bit rate (not the MIPI clock
+	 * which is half of this). These frequencies are now measured in Hz.
+	 * They should fit within u32, but u64 is needed for calculations.
+	 */
+
+	static const u32 REF_DIVN_MAX = 40000000;
+	static const u32 REF_DIVN_MIN =  5000000;
+	u32 n, best_n, best_m;
+	u64 best_err = vco_freq;
+
+	for (n = 1 + refclk / REF_DIVN_MAX; n * REF_DIVN_MIN <= refclk && n < 100; ++n) {
+		u32 half_m = DIV_U64_ROUND_CLOSEST(n * vco_freq, 2 * refclk);
+
+		if (half_m < 150) {
+			u64 f = div_u64(mul_u32_u32(2 * half_m, refclk), n);
+			u64 err = (f > vco_freq) ? f - vco_freq : vco_freq - f;
+
+			if (err < best_err) {
+				best_n = n;
+				best_m = 2 * half_m;
+				best_err = err;
+				if (err == 0)
+					break;
+			}
+		}
+	}
+
+	if (64 * best_err >= vco_freq)
+		return 0;
+
+	*ptr_n = best_n;
+	*ptr_m = best_m;
+	return div_u64(mul_u32_u32(best_m, refclk), best_n);
+}
+
+struct hsfreq_range {
+	u16 mhz_max;
+	u8  hsfreqrange;
+	u8  clk_lp2hs;
+	u8  clk_hs2lp;
+	u8  data_lp2hs; /* excluding clk lane entry */
+	u8  data_hs2lp;
+};
+
+/* See Table A-3 on page 258 of dphy databook */
+static const struct hsfreq_range hsfreq_table[] = {
+	{   89, 0b000000, 32, 20, 26, 13 },
+	{   99, 0b010000, 35, 23, 28, 14 },
+	{  109, 0b100000, 32, 22, 26, 13 },
+	{  129, 0b000001, 31, 20, 27, 13 },
+	{  139, 0b010001, 33, 22, 26, 14 },
+	{  149, 0b100001, 33, 21, 26, 14 },
+	{  169, 0b000010, 32, 20, 27, 13 },
+	{  179, 0b010010, 36, 23, 30, 15 },
+	{  199, 0b100010, 40, 22, 33, 15 },
+	{  219, 0b000011, 40, 22, 33, 15 },
+	{  239, 0b010011, 44, 24, 36, 16 },
+	{  249, 0b100011, 48, 24, 38, 17 },
+	{  269, 0b000100, 48, 24, 38, 17 },
+	{  299, 0b010100, 50, 27, 41, 18 },
+	{  329, 0b000101, 56, 28, 45, 18 },
+	{  359, 0b010101, 59, 28, 48, 19 },
+	{  399, 0b100101, 61, 30, 50, 20 },
+	{  449, 0b000110, 67, 31, 55, 21 },
+	{  499, 0b010110, 73, 31, 59, 22 },
+	{  549, 0b000111, 79, 36, 63, 24 },
+	{  599, 0b010111, 83, 37, 68, 25 },
+	{  649, 0b001000, 90, 38, 73, 27 },
+	{  699, 0b011000, 95, 40, 77, 28 },
+	{  749, 0b001001, 102, 40, 84, 28 },
+	{  799, 0b011001, 106, 42, 87, 30 },
+	{  849, 0b101001, 113, 44, 93, 31 },
+	{  899, 0b111001, 118, 47, 98, 32 },
+	{  949, 0b001010, 124, 47, 102, 34 },
+	{  999, 0b011010, 130, 49, 107, 35 },
+	{ 1049, 0b101010, 135, 51, 111, 37 },
+	{ 1099, 0b111010, 139, 51, 114, 38 },
+	{ 1149, 0b001011, 146, 54, 120, 40 },
+	{ 1199, 0b011011, 153, 57, 125, 41 },
+	{ 1249, 0b101011, 158, 58, 130, 42 },
+	{ 1299, 0b111011, 163, 58, 135, 44 },
+	{ 1349, 0b001100, 168, 60, 140, 45 },
+	{ 1399, 0b011100, 172, 64, 144, 47 },
+	{ 1449, 0b101100, 176, 65, 148, 48 },
+	{ 1500, 0b111100, 181, 66, 153, 50 },
+};
+
+static void dphy_set_hsfreqrange(struct rp1_dsi *dsi, u32 freq_mhz)
+{
+	unsigned int i;
+
+	if (freq_mhz < 80 || freq_mhz > 1500)
+		drm_err(dsi->drm, "DPHY: Frequency %u MHz out of range\n",
+			freq_mhz);
+
+	for (i = 0; i < ARRAY_SIZE(hsfreq_table) - 1; i++) {
+		if (freq_mhz <= hsfreq_table[i].mhz_max)
+			break;
+	}
+
+	dsi->hsfreq_index = i;
+	dphy_transaction(dsi, DPHY_HS_RX_CTRL_LANE0_OFFSET,
+			 hsfreq_table[i].hsfreqrange << 1);
+}
+
+static u32 dphy_configure_pll(struct rp1_dsi *dsi, u32 refclk, u32 vco_freq)
+{
+	u32 m = 0;
+	u32 n = 0;
+	u32 actual_vco_freq = dphy_get_div(refclk, vco_freq, &m, &n);
+
+	if (actual_vco_freq) {
+		dphy_set_hsfreqrange(dsi, actual_vco_freq / 1000000);
+		/* Program m,n from registers */
+		dphy_transaction(dsi, DPHY_PLL_DIV_CTRL_OFFSET, 0x30);
+		/* N (program N-1) */
+		dphy_transaction(dsi, DPHY_PLL_INPUT_DIV_OFFSET, n - 1);
+		/* M[8:5] ?? */
+		dphy_transaction(dsi, DPHY_PLL_LOOP_DIV_OFFSET, 0x80 | ((m - 1) >> 5));
+		/* M[4:0] (program M-1) */
+		dphy_transaction(dsi, DPHY_PLL_LOOP_DIV_OFFSET, ((m - 1) & 0x1F));
+		drm_dbg_driver(dsi->drm,
+			       "DPHY: vco freq want %uHz got %uHz = %d * (%uHz / %d), hsfreqrange = 0x%02x\n",
+			       vco_freq, actual_vco_freq, m, refclk, n,
+			       hsfreq_table[dsi->hsfreq_index].hsfreqrange);
+	} else {
+		drm_err(dsi->drm,
+			"rp1dsi: Error configuring DPHY PLL %uHz\n", vco_freq);
+	}
+
+	return actual_vco_freq;
+}
+
+static u32 dphy_init(struct rp1_dsi *dsi, u32 ref_freq, u32 vco_freq)
+{
+	u32 actual_vco_freq;
+
+	/* Reset the PHY */
+	DSI_WRITE(DSI_PHYRSTZ, 0);
+	DSI_WRITE(DSI_PHY_TST_CTRL0, DPHY_CTRL0_PHY_TESTCLK_BITS);
+	DSI_WRITE(DSI_PHY_TST_CTRL1, 0);
+	DSI_WRITE(DSI_PHY_TST_CTRL0, (DPHY_CTRL0_PHY_TESTCLK_BITS | DPHY_CTRL0_PHY_TESTCLR_BITS));
+	udelay(1);
+	DSI_WRITE(DSI_PHY_TST_CTRL0, DPHY_CTRL0_PHY_TESTCLK_BITS);
+	udelay(1);
+	/* Since we are in DSI (not CSI2) mode here, start the PLL */
+	actual_vco_freq = dphy_configure_pll(dsi, ref_freq, vco_freq);
+
+	dphy_transaction(dsi, DPHY_CLK_PN_SWAP, !!dsi->lane_polarities[0]);
+	dphy_transaction(dsi, DPHY_D0_PN_SWAP, !!dsi->lane_polarities[1]);
+	dphy_transaction(dsi, DPHY_D1_PN_SWAP, !!dsi->lane_polarities[2]);
+	dphy_transaction(dsi, DPHY_D2_PN_SWAP, !!dsi->lane_polarities[3]);
+	dphy_transaction(dsi, DPHY_D3_PN_SWAP, !!dsi->lane_polarities[4]);
+
+	udelay(1);
+	/* Unreset */
+	DSI_WRITE(DSI_PHYRSTZ, DSI_PHYRSTZ_SHUTDOWNZ_BITS);
+	udelay(1);
+	DSI_WRITE(DSI_PHYRSTZ, (DSI_PHYRSTZ_SHUTDOWNZ_BITS | DSI_PHYRSTZ_RSTZ_BITS));
+	udelay(1); /* so we can see PLL coming up? */
+
+	return actual_vco_freq;
+}
+
+void rp1dsi_mipicfg_setup(struct rp1_dsi *dsi)
+{
+	/* Select DSI rather than CSI-2 */
+	CFG_WRITE(RPI_MIPICFG_CFG, 0);
+	/* Enable DSIDMA interrupt only */
+	CFG_WRITE(RPI_MIPICFG_INTE, RPI_MIPICFG_INTE_DSI_DMA_BITS);
+}
+
+static unsigned long rp1dsi_refclk_freq(struct rp1_dsi *dsi)
+{
+	unsigned long u;
+
+	u = (dsi->clocks[RP1DSI_CLOCK_REF]) ? clk_get_rate(dsi->clocks[RP1DSI_CLOCK_REF]) : 0;
+	if (u < 1 || u >= (1ul << 30))
+		u = 50000000ul; /* default XOSC frequency */
+	return u;
+}
+
+static void rp1dsi_dpiclk_start(struct rp1_dsi *dsi, u32 byte_clock,
+				unsigned int bpp, unsigned int lanes)
+{
+	/* Dummy clk_set_rate() to declare the actual DSI byte-clock rate */
+	clk_set_rate(dsi->clocks[RP1DSI_CLOCK_BYTE], byte_clock);
+
+	/*
+	 * Prefer the DSI byte-clock source where possible, so that DSI and DPI
+	 * clocks will be in an exact ratio and downstream devices can recover
+	 * perfect timings. But when DPI clock is faster, fall back on PLL_SYS.
+	 * To defeat rounding errors, specify explicitly which source to use.
+	 */
+	if (bpp >= 8 * lanes)
+		clk_set_parent(dsi->clocks[RP1DSI_CLOCK_DPI], dsi->clocks[RP1DSI_CLOCK_BYTE]);
+	else if (dsi->clocks[RP1DSI_CLOCK_PLLSYS])
+		clk_set_parent(dsi->clocks[RP1DSI_CLOCK_DPI], dsi->clocks[RP1DSI_CLOCK_PLLSYS]);
+
+	clk_set_rate(dsi->clocks[RP1DSI_CLOCK_DPI], (4 * lanes * byte_clock) / (bpp >> 1));
+	clk_prepare_enable(dsi->clocks[RP1DSI_CLOCK_DPI]);
+	drm_info(dsi->drm,
+		 "rp1dsi: Nominal Byte clock %u DPI clock %lu (parent rate %lu)\n",
+		 byte_clock,
+		 clk_get_rate(dsi->clocks[RP1DSI_CLOCK_DPI]),
+		 clk_get_rate(clk_get_parent(dsi->clocks[RP1DSI_CLOCK_DPI])));
+}
+
+static void rp1dsi_dpiclk_stop(struct rp1_dsi *dsi)
+{
+	if (dsi->clocks[RP1DSI_CLOCK_DPI])
+		clk_disable_unprepare(dsi->clocks[RP1DSI_CLOCK_DPI]);
+}
+
+/* Choose the internal on-the-bus DPI format, and DSI packing flag. */
+static u32 get_colorcode(enum mipi_dsi_pixel_format fmt)
+{
+	switch (fmt) {
+	case MIPI_DSI_FMT_RGB666:
+		return 0x104;
+	case MIPI_DSI_FMT_RGB666_PACKED:
+		return 0x003;
+	case MIPI_DSI_FMT_RGB565:
+		return 0x000;
+	case MIPI_DSI_FMT_RGB888:
+		return 0x005;
+	}
+
+	/* This should be impossible as the format is validated in
+	 * rp1dsi_host_attach
+	 */
+	WARN_ONCE(1, "Invalid colour format configured for DSI");
+	return 0x005;
+}
+
+/* Frequency limits for DPI, HS and LP clocks, and some magic numbers */
+#define RP1DSI_DPI_MAX_KHZ     200000
+#define RP1DSI_BYTE_CLK_MIN  10000000
+#define RP1DSI_BYTE_CLK_MAX 187500000
+#define RP1DSI_ESC_CLK_MAX   20000000
+#define RP1DSI_TO_CLK_DIV        0x50
+#define RP1DSI_LPRX_TO_VAL       0x40
+#define RP1DSI_BTA_TO_VAL       0xd00
+
+void rp1dsi_dsi_setup(struct rp1_dsi *dsi, struct drm_display_mode const *mode)
+{
+	int cmdtim;
+	u32 timeout, mask, clkdiv;
+	unsigned int bpp = mipi_dsi_pixel_format_to_bpp(dsi->display_format);
+	u32 byte_clock = clamp((bpp * 125 * min(mode->clock, RP1DSI_DPI_MAX_KHZ)) / dsi->lanes,
+			       RP1DSI_BYTE_CLK_MIN, RP1DSI_BYTE_CLK_MAX);
+
+	DSI_WRITE(DSI_PHY_IF_CFG, dsi->lanes - 1);
+	DSI_WRITE(DSI_DPI_CFG_POL, 0);
+	DSI_WRITE(DSI_GEN_VCID, dsi->vc);
+	DSI_WRITE(DSI_DPI_COLOR_CODING, get_colorcode(dsi->display_format));
+
+	/*
+	 * Flags to configure use of LP, EoTp, Burst Mode, Sync Events/Pulses.
+	 * Note that Burst Mode implies Sync Events; the two flags need not be
+	 * set concurrently, and in this RP1 variant *should not* both be set:
+	 * doing so would (counter-intuitively) enable Sync Pulses and may fail
+	 * if there is not sufficient time to return to LP11 state during HBP.
+	 */
+	mask =  DSI_VID_MODE_LP_HFP_EN  | DSI_VID_MODE_LP_HBP_EN |
+		DSI_VID_MODE_LP_VACT_EN | DSI_VID_MODE_LP_VFP_EN |
+		DSI_VID_MODE_LP_VBP_EN  | DSI_VID_MODE_LP_VSA_EN;
+	if (dsi->display_flags & MIPI_DSI_MODE_LPM)
+		mask |= DSI_VID_MODE_LP_CMD_EN;
+	if (dsi->display_flags & MIPI_DSI_MODE_VIDEO_BURST)
+		mask |= DSI_VID_MODE_BURST;
+	else if (!(dsi->display_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE))
+		mask |= DSI_VID_MODE_SYNC_EVENTS;
+	else if (8 * dsi->lanes > bpp)
+		mask &= ~DSI_VID_MODE_LP_HBP_EN; /* PULSE && inexact DPICLK => fix HBP time */
+	DSI_WRITE(DSI_VID_MODE_CFG, mask);
+	DSI_WRITE(DSI_CMD_MODE_CFG,
+		  (dsi->display_flags & MIPI_DSI_MODE_LPM) ? DSI_CMD_MODE_ALL_LP : 0);
+	DSI_WRITE(DSI_PCKHDL_CFG,
+		  DSI_PCKHDL_BTA_EN |
+		  ((dsi->display_flags & MIPI_DSI_MODE_NO_EOT_PACKET) ? 0 : DSI_PCKHDL_EOTP_TX_EN));
+
+	/* Select Command Mode */
+	DSI_WRITE(DSI_MODE_CFG, 1);
+
+	/* Set timeouts and clock dividers */
+	timeout = (bpp * mode->htotal * mode->vdisplay) / (7 * RP1DSI_TO_CLK_DIV * dsi->lanes);
+	if (timeout > 0xFFFFu)
+		timeout = 0;
+	DSI_WRITE(DSI_TO_CNT_CFG, (timeout << 16) | RP1DSI_LPRX_TO_VAL);
+	DSI_WRITE(DSI_BTA_TO_CNT, RP1DSI_BTA_TO_VAL);
+	clkdiv = max(2u, 1u + byte_clock / RP1DSI_ESC_CLK_MAX); /* byte clocks per escape clock */
+	DSI_WRITE(DSI_CLKMGR_CFG,
+		  (RP1DSI_TO_CLK_DIV << 8) | clkdiv);
+
+	/* Configure video timings */
+	DSI_WRITE(DSI_VID_PKT_SIZE, mode->hdisplay);
+	DSI_WRITE(DSI_VID_NUM_CHUNKS, 0);
+	DSI_WRITE(DSI_VID_NULL_SIZE, 0);
+	DSI_WRITE(DSI_VID_HSA_TIME,
+		  (bpp * (mode->hsync_end - mode->hsync_start)) / (8 * dsi->lanes));
+	DSI_WRITE(DSI_VID_HBP_TIME,
+		  (bpp * (mode->htotal - mode->hsync_end)) / (8 * dsi->lanes));
+	DSI_WRITE(DSI_VID_HLINE_TIME, (bpp * mode->htotal) / (8 * dsi->lanes));
+	DSI_WRITE(DSI_VID_VSA_LINES, (mode->vsync_end - mode->vsync_start));
+	DSI_WRITE(DSI_VID_VBP_LINES, (mode->vtotal - mode->vsync_end));
+	DSI_WRITE(DSI_VID_VFP_LINES, (mode->vsync_start - mode->vdisplay));
+	DSI_WRITE(DSI_VID_VACTIVE_LINES, mode->vdisplay);
+
+	/* Init PHY */
+	byte_clock = dphy_init(dsi, rp1dsi_refclk_freq(dsi), 8 * byte_clock) >> 3;
+
+	DSI_WRITE(DSI_PHY_TMR_LPCLK_CFG,
+		  (hsfreq_table[dsi->hsfreq_index].clk_lp2hs << DSI_PHY_TMR_LP2HS_LSB) |
+		  (hsfreq_table[dsi->hsfreq_index].clk_hs2lp << DSI_PHY_TMR_HS2LP_LSB));
+	DSI_WRITE(DSI_PHY_TMR_CFG,
+		  (hsfreq_table[dsi->hsfreq_index].data_lp2hs << DSI_PHY_TMR_LP2HS_LSB) |
+		  (hsfreq_table[dsi->hsfreq_index].data_hs2lp << DSI_PHY_TMR_HS2LP_LSB));
+
+	/* Estimate how many LP bytes can be sent during vertical blanking (Databook 3.6.2.1) */
+	cmdtim = mode->htotal;
+	if (dsi->display_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
+		cmdtim -= mode->hsync_end - mode->hsync_start;
+	cmdtim = (bpp * cmdtim - 64) / (8 * dsi->lanes);      /* byte clocks after HSS and EoTp */
+	cmdtim -= hsfreq_table[dsi->hsfreq_index].data_hs2lp;
+	cmdtim -= hsfreq_table[dsi->hsfreq_index].data_lp2hs;
+	cmdtim = (cmdtim / clkdiv) - 24;                      /* escape clocks for commands */
+	cmdtim = max(0, cmdtim >> 4);                         /* bytes (at 2 clocks per bit) */
+	drm_info(dsi->drm, "rp1dsi: Command time (outvact): %d\n", cmdtim);
+	DSI_WRITE(DSI_DPI_LP_CMD_TIM, cmdtim << 16);
+
+	/* Wait for PLL lock */
+	for (timeout = (1 << 14); timeout != 0; --timeout) {
+		usleep_range(10, 50);
+		if (DSI_READ(DSI_PHY_STATUS) & (1 << 0))
+			break;
+	}
+	if (timeout == 0)
+		drm_err(dsi->drm, "RP1DSI: Time out waiting for PLL\n");
+
+	DSI_WRITE(DSI_LPCLK_CTRL,
+		  (dsi->display_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) ? 0x3 : 0x1);
+	DSI_WRITE(DSI_PHY_TST_CTRL0, 0x2);
+	DSI_WRITE(DSI_PWR_UP, 0x1);		/* power up */
+
+	/* Now it should be safe to start the external DPI clock divider */
+	rp1dsi_dpiclk_start(dsi, byte_clock, bpp, dsi->lanes);
+
+	/* Wait for all lane(s) to be in Stopstate */
+	mask = (1 << 4);
+	if (dsi->lanes >= 2)
+		mask |= (1 << 7);
+	if (dsi->lanes >= 3)
+		mask |= (1 << 9);
+	if (dsi->lanes >= 4)
+		mask |= (1 << 11);
+	for (timeout = (1 << 10); timeout != 0; --timeout) {
+		usleep_range(10, 50);
+		if ((DSI_READ(DSI_PHY_STATUS) & mask) == mask)
+			break;
+	}
+	if (timeout == 0)
+		drm_err(dsi->drm, "RP1DSI: Time out waiting for lanes (%x %x)\n",
+			mask, DSI_READ(DSI_PHY_STATUS));
+}
+
+void rp1dsi_dsi_send(struct rp1_dsi *dsi, u32 hdr, int len, const u8 *buf,
+		     bool use_lpm, bool req_ack)
+{
+	u32 val;
+
+	/* Wait for both FIFOs empty */
+	for (val = 256; val > 0; --val) {
+		if ((DSI_READ(DSI_CMD_PKT_STATUS) & 0xF) == 0x5)
+			break;
+		usleep_range(100, 150);
+	}
+
+	/*
+	 * Update global configuration flags for LP/HS and ACK options.
+	 * XXX It's not clear if having empty FIFOs (checked above and below) guarantees that
+	 * the last command has completed and been ACKed, or how closely these control registers
+	 * align with command/payload FIFO writes (as each is an independent clock-crossing)?
+	 */
+	val = DSI_READ(DSI_VID_MODE_CFG);
+	if (use_lpm)
+		val |= DSI_VID_MODE_LP_CMD_EN;
+	else
+		val &= ~DSI_VID_MODE_LP_CMD_EN;
+	DSI_WRITE(DSI_VID_MODE_CFG, val);
+	val = (use_lpm) ? DSI_CMD_MODE_ALL_LP : 0;
+	if (req_ack)
+		val |= DSI_CMD_MODE_ACK_RQST_EN;
+	DSI_WRITE(DSI_CMD_MODE_CFG, val);
+	(void)DSI_READ(DSI_CMD_MODE_CFG);
+
+	/* Write payload (in 32-bit words) and header */
+	for (; len > 0; len -= 4) {
+		val = *buf++;
+		if (len > 1)
+			val |= (*buf++) << 8;
+		if (len > 2)
+			val |= (*buf++) << 16;
+		if (len > 3)
+			val |= (*buf++) << 24;
+		DSI_WRITE(DSI_GEN_PLD_DATA, val);
+	}
+	DSI_WRITE(DSI_GEN_HDR, hdr);
+
+	/* Wait for both FIFOs empty */
+	for (val = 256; val > 0; --val) {
+		if ((DSI_READ(DSI_CMD_PKT_STATUS) & 0xF) == 0x5)
+			break;
+		usleep_range(100, 150);
+	}
+}
+
+int rp1dsi_dsi_recv(struct rp1_dsi *dsi, int len, u8 *buf)
+{
+	int i, j;
+	u32 val;
+
+	/* Wait until not busy and FIFO not empty */
+	for (i = 1024; i > 0; --i) {
+		val = DSI_READ(DSI_CMD_PKT_STATUS);
+		if ((val & ((1 << 6) | (1 << 4))) == 0)
+			break;
+		usleep_range(100, 150);
+	}
+	if (!i) {
+		drm_warn(dsi->drm, "Receive failed\n");
+		return -EIO;
+	}
+
+	for (i = 0; i < len; i += 4) {
+		/* Read fifo must not be empty before all bytes are read */
+		if (DSI_READ(DSI_CMD_PKT_STATUS) & (1 << 4))
+			break;
+
+		val = DSI_READ(DSI_GEN_PLD_DATA);
+		for (j = 0; j < 4 && j + i < len; j++)
+			*buf++ = val >> (8 * j);
+	}
+
+	return (i >= len) ? len : (i > 0) ? i : -EIO;
+}
+
+void rp1dsi_dsi_stop(struct rp1_dsi *dsi)
+{
+	DSI_WRITE(DSI_MODE_CFG, 1);	/* Return to Command Mode */
+	DSI_WRITE(DSI_LPCLK_CTRL, 2);	/* Stop the HS clock */
+	DSI_WRITE(DSI_PWR_UP, 0x0);     /* Power down host controller */
+	DSI_WRITE(DSI_PHYRSTZ, 0);      /* PHY into reset. */
+	rp1dsi_dpiclk_stop(dsi);
+}
+
+void rp1dsi_dsi_set_cmdmode(struct rp1_dsi *dsi, int mode)
+{
+	DSI_WRITE(DSI_MODE_CFG, mode);
+}
diff --git a/drivers/gpu/drm/rp1/rp1-vec/Kconfig b/drivers/gpu/drm/rp1/rp1-vec/Kconfig
new file mode 100644
index 000000000000..f646c01af5ae
--- /dev/null
+++ b/drivers/gpu/drm/rp1/rp1-vec/Kconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config DRM_RP1_VEC
+	tristate "DRM Support for RP1 VEC"
+	depends on DRM && MFD_RP1
+	select DRM_GEM_DMA_HELPER
+	select DRM_KMS_HELPER
+	select DRM_VRAM_HELPER
+	select DRM_TTM
+	select DRM_TTM_HELPER
+	help
+	  Choose this option to enable Video Out on RP1
diff --git a/drivers/gpu/drm/rp1/rp1-vec/Makefile b/drivers/gpu/drm/rp1/rp1-vec/Makefile
new file mode 100644
index 000000000000..7e941cad342e
--- /dev/null
+++ b/drivers/gpu/drm/rp1/rp1-vec/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+drm-rp1-vec-y := rp1_vec.o rp1_vec_hw.o rp1_vec_cfg.o
+
+obj-$(CONFIG_DRM_RP1_VEC) += drm-rp1-vec.o
diff --git a/drivers/gpu/drm/rp1/rp1-vec/rp1_vec.c b/drivers/gpu/drm/rp1/rp1-vec/rp1_vec.c
new file mode 100644
index 000000000000..99348cb713fb
--- /dev/null
+++ b/drivers/gpu/drm/rp1/rp1-vec/rp1_vec.c
@@ -0,0 +1,605 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * DRM Driver for VEC output on Raspberry Pi RP1
+ *
+ * Copyright (c) 2023 Raspberry Pi Limited.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/list.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/console.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <linux/dma-mapping.h>
+#include <linux/cred.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_mm.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_managed.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_fbdev_ttm.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_vblank.h>
+#include <drm/drm_of.h>
+
+#include "rp1_vec.h"
+
+/*
+ * Linux doesn't make it easy to create custom video modes for the console
+ * with non-CVT timings; so add a module parameter for it. The format is:
+ * "<pclk>,<hact>,<hfp>,<hsync>,<hbp>,<vact>,<vfp>,<vsync>,<vbp>[,i]"
+ * (where each comma may be replaced by any sequence of punctuation).
+ * pclk should be 108000/n for 5 <= n <= 16 (twice this for "fake" modes).
+ */
+
+static char *rp1vec_cmode_str;
+module_param_named(cmode, rp1vec_cmode_str, charp, 0600);
+MODULE_PARM_DESC(cmode, "Custom video mode:\n"
+		 "\t\t<pclk>,<hact>,<hfp>,<hsync>,<hbp>,<vact>,<vfp>,<vsync>,<vbp>[,i]\n");
+
+static struct drm_display_mode *rp1vec_parse_custom_mode(struct drm_device *dev)
+{
+	char const *p = rp1vec_cmode_str;
+	struct drm_display_mode *mode;
+	unsigned int n, vals[9];
+
+	if (!p)
+		return NULL;
+
+	for (n = 0; n < 9; n++) {
+		unsigned int v = 0;
+
+		if (!isdigit(*p))
+			return NULL;
+		do {
+			v = 10u * v + (*p - '0');
+		} while (isdigit(*++p));
+
+		vals[n] = v;
+		while (ispunct(*p))
+			p++;
+	}
+
+	mode = drm_mode_create(dev);
+	if (!mode)
+		return NULL;
+
+	mode->clock	  = vals[0];
+	mode->hdisplay	  = vals[1];
+	mode->hsync_start = mode->hdisplay + vals[2];
+	mode->hsync_end	  = mode->hsync_start + vals[3];
+	mode->htotal	  = mode->hsync_end + vals[4];
+	mode->vdisplay	  = vals[5];
+	mode->vsync_start = mode->vdisplay + vals[6];
+	mode->vsync_end	  = mode->vsync_start + vals[7];
+	mode->vtotal	  = mode->vsync_end + vals[8];
+	mode->type  = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+	mode->flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC;
+	if (strchr(p, 'i'))
+		mode->flags |= DRM_MODE_FLAG_INTERLACE;
+
+	return mode;
+}
+
+static void rp1vec_pipe_update(struct drm_simple_display_pipe *pipe,
+			       struct drm_plane_state *old_state)
+{
+	struct drm_pending_vblank_event *event;
+	unsigned long flags;
+	struct drm_framebuffer *fb = pipe->plane.state->fb;
+	struct rp1_vec *vec = pipe->crtc.dev->dev_private;
+	struct drm_gem_object *gem = fb ? drm_gem_fb_get_obj(fb, 0) : NULL;
+	struct drm_gem_dma_object *dma_obj = gem ? to_drm_gem_dma_obj(gem) : NULL;
+	bool can_update = fb && dma_obj && vec && vec->pipe_enabled;
+
+	/* (Re-)start VEC where required; and update FB address */
+	if (can_update) {
+		if (!vec->vec_running || fb->format->format != vec->cur_fmt) {
+			if (vec->vec_running && fb->format->format != vec->cur_fmt) {
+				rp1vec_hw_stop(vec);
+				vec->vec_running = false;
+			}
+			if (!vec->vec_running) {
+				rp1vec_hw_setup(vec,
+						fb->format->format,
+						&pipe->crtc.state->mode,
+						vec->connector.state->tv.mode);
+				vec->vec_running = true;
+			}
+			vec->cur_fmt  = fb->format->format;
+			drm_crtc_vblank_on(&pipe->crtc);
+		}
+		rp1vec_hw_update(vec, dma_obj->dma_addr, fb->offsets[0], fb->pitches[0]);
+	}
+
+	/* Check if VBLANK callback needs to be armed (or sent immediately in some error cases).
+	 * Note there is a tiny probability of a race between rp1vec_dma_update() and IRQ;
+	 * ordering it this way around is safe, but theoretically might delay an extra frame.
+	 */
+	spin_lock_irqsave(&pipe->crtc.dev->event_lock, flags);
+	event = pipe->crtc.state->event;
+	if (event) {
+		pipe->crtc.state->event = NULL;
+		if (can_update && drm_crtc_vblank_get(&pipe->crtc) == 0)
+			drm_crtc_arm_vblank_event(&pipe->crtc, event);
+		else
+			drm_crtc_send_vblank_event(&pipe->crtc, event);
+	}
+	spin_unlock_irqrestore(&pipe->crtc.dev->event_lock, flags);
+}
+
+static void rp1vec_pipe_enable(struct drm_simple_display_pipe *pipe,
+			       struct drm_crtc_state *crtc_state,
+			       struct drm_plane_state *plane_state)
+{
+	struct rp1_vec *vec = pipe->crtc.dev->dev_private;
+
+	dev_info(&vec->pdev->dev, __func__);
+	vec->pipe_enabled = true;
+	vec->cur_fmt = 0xdeadbeef;
+	rp1vec_vidout_setup(vec);
+	rp1vec_pipe_update(pipe, 0);
+}
+
+static void rp1vec_pipe_disable(struct drm_simple_display_pipe *pipe)
+{
+	struct rp1_vec *vec = pipe->crtc.dev->dev_private;
+
+	dev_info(&vec->pdev->dev, __func__);
+	drm_crtc_vblank_off(&pipe->crtc);
+	if (vec) {
+		if (vec->vec_running) {
+			rp1vec_hw_stop(vec);
+			vec->vec_running = false;
+		}
+		vec->pipe_enabled = false;
+	}
+}
+
+static int rp1vec_pipe_enable_vblank(struct drm_simple_display_pipe *pipe)
+{
+	if (pipe && pipe->crtc.dev) {
+		struct rp1_vec *vec = pipe->crtc.dev->dev_private;
+
+		if (vec)
+			rp1vec_hw_vblank_ctrl(vec, 1);
+	}
+	return 0;
+}
+
+static void rp1vec_pipe_disable_vblank(struct drm_simple_display_pipe *pipe)
+{
+	if (pipe && pipe->crtc.dev) {
+		struct rp1_vec *vec = pipe->crtc.dev->dev_private;
+
+		if (vec)
+			rp1vec_hw_vblank_ctrl(vec, 0);
+	}
+}
+
+static const struct drm_simple_display_pipe_funcs rp1vec_pipe_funcs = {
+	.enable	    = rp1vec_pipe_enable,
+	.update	    = rp1vec_pipe_update,
+	.disable    = rp1vec_pipe_disable,
+	.enable_vblank	= rp1vec_pipe_enable_vblank,
+	.disable_vblank = rp1vec_pipe_disable_vblank,
+};
+
+static void rp1vec_connector_destroy(struct drm_connector *connector)
+{
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+}
+
+/*
+ * Check the mode roughly matches something we can generate.
+ * The choice of hardware TV mode depends on total lines and frame rate.
+ * Within each hardware mode, allow pixel clock, image size and offsets
+ * to vary, up to a maximum horizontal active period and line count.
+ * Don't check sync timings here: the HW driver will sanitize them.
+ */
+
+static enum drm_mode_status rp1vec_mode_valid(struct drm_device *dev,
+					      const struct drm_display_mode *mode)
+{
+	int prog	  = !(mode->flags & DRM_MODE_FLAG_INTERLACE);
+	int fake_31khz	  = prog && mode->vtotal >= 500;
+	int vtotal_2fld	  = mode->vtotal << (prog && !fake_31khz);
+	int vdisplay_2fld = mode->vdisplay << (prog && !fake_31khz);
+	int real_clock	  = mode->clock >> fake_31khz;
+
+	/* Check pixel clock is in the permitted range */
+	if (real_clock < 6750)
+		return MODE_CLOCK_LOW;
+	else if (real_clock > 21600)
+		return MODE_CLOCK_HIGH;
+
+	/* Try to match against the 525-line 60Hz mode (System M) */
+	if (vtotal_2fld >= 524 && vtotal_2fld <= 526 && vdisplay_2fld <= 486 &&
+	    mode->htotal * vtotal_2fld > 32 * real_clock &&
+	    mode->htotal * vtotal_2fld < 34 * real_clock &&
+	    37 * mode->hdisplay <= 2 * real_clock) /* 54us */
+		return MODE_OK;
+
+	/* All other supported TV Systems (625-, 405-, 819-line) are 50Hz */
+	if (mode->htotal * vtotal_2fld > 39 * real_clock &&
+	    mode->htotal * vtotal_2fld < 41 * real_clock) {
+		if (vtotal_2fld >= 624 && vtotal_2fld <= 626 && vdisplay_2fld <= 576 &&
+		    37 * mode->hdisplay <= 2 * real_clock) /* 54us */
+			return MODE_OK;
+
+		if (vtotal_2fld == 405 && vdisplay_2fld <= 380 &&
+		    49 * mode->hdisplay <= 4 * real_clock) /* 81.6us */
+			return MODE_OK;
+
+		if (vtotal_2fld == 819 && vdisplay_2fld <= 738 &&
+		    25 * mode->hdisplay <= real_clock) /* 40us */
+			return MODE_OK;
+	}
+
+	return MODE_BAD;
+}
+
+static const struct drm_display_mode rp1vec_modes[6] = {
+	{ /* Full size 525/60i with Rec.601 pixel rate */
+		DRM_MODE("720x480i", DRM_MODE_TYPE_DRIVER, 13500,
+			 720, 720 + 16, 720 + 16 + 64, 858, 0,
+			 480, 480 + 6, 480 + 6 + 6, 525, 0,
+			 DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+			 DRM_MODE_FLAG_INTERLACE)
+	},
+	{ /* Cropped and horizontally squashed to be TV-safe */
+		DRM_MODE("704x432i", DRM_MODE_TYPE_DRIVER, 15429,
+			 704, 704 + 76, 704 + 76 + 72, 980, 0,
+			 432, 432 + 30, 432 + 30 + 6, 525, 0,
+			 DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+			 DRM_MODE_FLAG_INTERLACE)
+	},
+	{ /* Full size 625/50i with Rec.601 pixel rate */
+		DRM_MODE("720x576i", DRM_MODE_TYPE_DRIVER, 13500,
+			 720, 720 + 12, 720 + 12 + 64, 864, 0,
+			 576, 576 + 5, 576 + 5 + 5, 625, 0,
+			 DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+			 DRM_MODE_FLAG_INTERLACE)
+	},
+	{ /* Cropped and squashed, for square(ish) pixels */
+		DRM_MODE("704x512i", DRM_MODE_TYPE_DRIVER, 15429,
+			 704, 704 + 72, 704 + 72 + 72, 987, 0,
+			 512, 512 + 37, 512 + 37 + 5, 625, 0,
+			 DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+			 DRM_MODE_FLAG_INTERLACE)
+	},
+	{ /* System A (405 lines) */
+		DRM_MODE("544x380i", DRM_MODE_TYPE_DRIVER, 6750,
+			 544, 544 + 12, 544 + 12 + 60, 667, 0,
+			 380, 380 + 0,	380 + 0 + 8, 405, 0,
+			 DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+			 DRM_MODE_FLAG_INTERLACE)
+	},
+	{ /* System E (819 lines) */
+		DRM_MODE("848x738i", DRM_MODE_TYPE_DRIVER, 21600,
+			 848, 848 + 12, 848 + 12 + 54, 1055, 0,
+			 738, 738 + 6, 738 + 6 + 1, 819, 0,
+			 DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC |
+			 DRM_MODE_FLAG_INTERLACE)
+	}
+};
+
+/*
+ * Advertise a custom mode, if specified; then those from the table above.
+ * From each interlaced mode above, derive a half-height progressive one.
+ *
+ * This driver always supports all 525-line and 625-line standard modes
+ * regardless of connector's tv_mode; non-standard combinations generally
+ * default to PAL[-BDGHIK] or NTSC[-M] (with a special case for "PAL60").
+ *
+ * The "vintage" standards (System A, System E) are advertised only when
+ * the default tv_mode was DRM_MODE_TV_MODE_MONOCHROME, and only interlaced.
+ */
+
+static int rp1vec_connector_get_modes(struct drm_connector *connector)
+{
+	u64 tvstd;
+	int i, prog, limit, n = 0, preferred_lines = 525;
+	struct drm_display_mode *mode;
+
+	if (!drm_object_property_get_default_value(&connector->base,
+						   connector->dev->mode_config.tv_mode_property,
+						   &tvstd))
+		preferred_lines = (tvstd == DRM_MODE_TV_MODE_PAL   ||
+				   tvstd == DRM_MODE_TV_MODE_PAL_N ||
+				   tvstd >= DRM_MODE_TV_MODE_SECAM) ? 625 : 525;
+
+	mode = rp1vec_parse_custom_mode(connector->dev);
+	if (mode) {
+		if (rp1vec_mode_valid(connector->dev, mode) == 0) {
+			drm_mode_set_name(mode);
+			drm_mode_probed_add(connector, mode);
+			n++;
+			preferred_lines = 0;
+		} else {
+			drm_mode_destroy(connector->dev, mode);
+		}
+	}
+
+	limit = (tvstd < DRM_MODE_TV_MODE_MONOCHROME) ? 4 : ARRAY_SIZE(rp1vec_modes);
+	for (i = 0; i < limit; i++) {
+		for (prog = 0; prog < 2; prog++) {
+			mode = drm_mode_duplicate(connector->dev, &rp1vec_modes[i]);
+			if (!mode)
+				return n;
+
+			if (prog) {
+				mode->flags &= ~DRM_MODE_FLAG_INTERLACE;
+				mode->vdisplay	  >>= 1;
+				mode->vsync_start >>= 1;
+				mode->vsync_end	  >>= 1;
+				mode->vtotal      >>= 1;
+				if (mode->vtotal == 262 && tvstd < DRM_MODE_TV_MODE_PAL)
+					mode->vtotal++;
+			} else if (mode->hdisplay == 704 && mode->vtotal == preferred_lines) {
+				mode->type |= DRM_MODE_TYPE_PREFERRED;
+			}
+			drm_mode_set_name(mode);
+			drm_mode_probed_add(connector, mode);
+			n++;
+
+			if (mode->vtotal == 405 || mode->vtotal == 819)
+				break; /* Don't offer progressive for Systems A, E */
+		}
+	}
+
+	return n;
+}
+
+static void rp1vec_connector_reset(struct drm_connector *connector)
+{
+	drm_atomic_helper_connector_reset(connector);
+	drm_atomic_helper_connector_tv_reset(connector);
+}
+
+static int rp1vec_connector_atomic_check(struct drm_connector *conn,
+					 struct drm_atomic_state *state)
+{	struct drm_connector_state *old_state =
+		drm_atomic_get_old_connector_state(state, conn);
+	struct drm_connector_state *new_state =
+		drm_atomic_get_new_connector_state(state, conn);
+
+	if (new_state->crtc && old_state->tv.mode != new_state->tv.mode) {
+		struct drm_crtc_state *crtc_state =
+			drm_atomic_get_new_crtc_state(state, new_state->crtc);
+
+		crtc_state->mode_changed = true;
+	}
+
+	return 0;
+}
+
+static const struct drm_connector_helper_funcs rp1vec_connector_helper_funcs = {
+	.get_modes = rp1vec_connector_get_modes,
+	.atomic_check = rp1vec_connector_atomic_check,
+};
+
+static const struct drm_connector_funcs rp1vec_connector_funcs = {
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = rp1vec_connector_destroy,
+	.reset = rp1vec_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static const struct drm_mode_config_funcs rp1vec_mode_funcs = {
+	.fb_create = drm_gem_fb_create,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+	.mode_valid = rp1vec_mode_valid,
+};
+
+static const u32 rp1vec_formats[] = {
+	DRM_FORMAT_XRGB8888,
+	DRM_FORMAT_XBGR8888,
+	DRM_FORMAT_ARGB8888,
+	DRM_FORMAT_ABGR8888,
+	DRM_FORMAT_RGB888,
+	DRM_FORMAT_BGR888,
+	DRM_FORMAT_RGB565
+};
+
+static void rp1vec_stopall(struct drm_device *drm)
+{
+	if (drm->dev_private) {
+		struct rp1_vec *vec = drm->dev_private;
+
+		if (vec->vec_running || rp1vec_hw_busy(vec)) {
+			rp1vec_hw_stop(vec);
+			vec->vec_running = false;
+		}
+		rp1vec_vidout_poweroff(vec);
+	}
+}
+
+DEFINE_DRM_GEM_DMA_FOPS(rp1vec_fops);
+
+static struct drm_driver rp1vec_driver = {
+	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
+	.fops			= &rp1vec_fops,
+	.name			= "drm-rp1-vec",
+	.desc			= "drm-rp1-vec",
+	.date			= "0",
+	.major			= 1,
+	.minor			= 0,
+	DRM_GEM_DMA_DRIVER_OPS,
+	.release		= rp1vec_stopall,
+};
+
+static int rp1vec_platform_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rp1_vec *vec;
+	int i, ret;
+
+	dev_info(dev, __func__);
+	vec = devm_drm_dev_alloc(dev, &rp1vec_driver, struct rp1_vec, drm);
+	if (IS_ERR(vec)) {
+		ret = PTR_ERR(vec);
+		dev_err(dev, "%s devm_drm_dev_alloc %d", __func__, ret);
+		return ret;
+	}
+	vec->pdev = pdev;
+	spin_lock_init(&vec->hw_lock);
+
+	for (i = 0; i < RP1VEC_NUM_HW_BLOCKS; i++) {
+		vec->hw_base[i] =
+			devm_ioremap_resource(dev,
+					      platform_get_resource(vec->pdev, IORESOURCE_MEM, i));
+		if (IS_ERR(vec->hw_base[i])) {
+			ret = PTR_ERR(vec->hw_base[i]);
+			dev_err(dev, "Error memory mapping regs[%d]\n", i);
+			goto done_err;
+		}
+	}
+	ret = platform_get_irq(vec->pdev, 0);
+	if (ret > 0)
+		ret = devm_request_irq(dev, ret, rp1vec_hw_isr,
+				       IRQF_SHARED, "rp1-vec", vec);
+	if (ret) {
+		dev_err(dev, "Unable to request interrupt\n");
+		ret = -EINVAL;
+		goto done_err;
+	}
+
+	vec->vec_clock = devm_clk_get(dev, NULL);
+	if (IS_ERR(vec->vec_clock)) {
+		ret = PTR_ERR(vec->vec_clock);
+		goto done_err;
+	}
+	ret = clk_prepare_enable(vec->vec_clock);
+
+	ret = drmm_mode_config_init(&vec->drm);
+	if (ret)
+		goto done_err;
+
+	/* Now we have all our resources, finish driver initialization */
+	dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
+	init_completion(&vec->finished);
+	vec->drm.dev_private = vec;
+	platform_set_drvdata(pdev, &vec->drm);
+
+	vec->drm.mode_config.min_width	= 256;
+	vec->drm.mode_config.min_height = 128;
+	vec->drm.mode_config.max_width	= 960; /* for "widescreen" @ 18MHz */
+	vec->drm.mode_config.max_height = 738; /* for System E only */
+	vec->drm.mode_config.preferred_depth = 32;
+	vec->drm.mode_config.prefer_shadow = 0;
+	vec->drm.mode_config.quirk_addfb_prefer_host_byte_order = true;
+	vec->drm.mode_config.funcs = &rp1vec_mode_funcs;
+	drm_vblank_init(&vec->drm, 1);
+
+	ret = drm_mode_create_tv_properties(&vec->drm, RP1VEC_SUPPORTED_TV_MODES);
+	if (ret)
+		goto done_err;
+
+	drm_connector_init(&vec->drm, &vec->connector, &rp1vec_connector_funcs,
+			   DRM_MODE_CONNECTOR_Composite);
+	if (ret)
+		goto done_err;
+
+	vec->connector.interlace_allowed = true;
+	drm_connector_helper_add(&vec->connector, &rp1vec_connector_helper_funcs);
+
+	drm_object_attach_property(&vec->connector.base,
+				   vec->drm.mode_config.tv_mode_property,
+				   (vec->connector.cmdline_mode.tv_mode_specified) ?
+					   vec->connector.cmdline_mode.tv_mode :
+					   DRM_MODE_TV_MODE_NTSC);
+
+	ret = drm_simple_display_pipe_init(&vec->drm,
+					   &vec->pipe,
+					   &rp1vec_pipe_funcs,
+					   rp1vec_formats,
+					   ARRAY_SIZE(rp1vec_formats),
+					   NULL,
+					   &vec->connector);
+	if (ret)
+		goto done_err;
+
+	drm_mode_config_reset(&vec->drm);
+
+	ret = drm_dev_register(&vec->drm, 0);
+	if (ret)
+		goto done_err;
+
+	drm_fbdev_ttm_setup(&vec->drm, 32);
+	return ret;
+
+done_err:
+	dev_err(dev, "%s fail %d", __func__, ret);
+	return ret;
+}
+
+static void rp1vec_platform_remove(struct platform_device *pdev)
+{
+	struct drm_device *drm = platform_get_drvdata(pdev);
+
+	rp1vec_stopall(drm);
+	drm_dev_unregister(drm);
+	drm_atomic_helper_shutdown(drm);
+	drm_dev_put(drm);
+}
+
+static void rp1vec_platform_shutdown(struct platform_device *pdev)
+{
+	struct drm_device *drm = platform_get_drvdata(pdev);
+
+	rp1vec_stopall(drm);
+}
+
+static const struct of_device_id rp1vec_of_match[] = {
+	{
+		.compatible = "raspberrypi,rp1vec",
+	},
+	{ /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, rp1vec_of_match);
+
+static struct platform_driver rp1vec_platform_driver = {
+	.probe		= rp1vec_platform_probe,
+	.remove		= rp1vec_platform_remove,
+	.shutdown	= rp1vec_platform_shutdown,
+	.driver		= {
+		.name	= DRIVER_NAME,
+		.owner	= THIS_MODULE,
+		.of_match_table = rp1vec_of_match,
+	},
+};
+
+module_platform_driver(rp1vec_platform_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("DRM driver for Composite Video on Raspberry Pi RP1");
+MODULE_AUTHOR("Nick Hollinghurst");
diff --git a/drivers/gpu/drm/rp1/rp1-vec/rp1_vec.h b/drivers/gpu/drm/rp1/rp1-vec/rp1_vec.h
new file mode 100644
index 000000000000..c6c29723f85a
--- /dev/null
+++ b/drivers/gpu/drm/rp1/rp1-vec/rp1_vec.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * DRM Driver for DSI output on Raspberry Pi RP1
+ *
+ * Copyright (c) 2023 Raspberry Pi Limited.
+ */
+
+#include <linux/types.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+#include <drm/drm_device.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#define MODULE_NAME "drm-rp1-vec"
+#define DRIVER_NAME "drm-rp1-vec"
+
+/* ---------------------------------------------------------------------- */
+
+#define RP1VEC_HW_BLOCK_VEC   0
+#define RP1VEC_HW_BLOCK_CFG   1
+#define RP1VEC_NUM_HW_BLOCKS  2
+
+#define RP1VEC_SUPPORTED_TV_MODES	  \
+	(BIT(DRM_MODE_TV_MODE_NTSC)	| \
+	 BIT(DRM_MODE_TV_MODE_NTSC_443) | \
+	 BIT(DRM_MODE_TV_MODE_NTSC_J)	| \
+	 BIT(DRM_MODE_TV_MODE_PAL)	| \
+	 BIT(DRM_MODE_TV_MODE_PAL_M)	| \
+	 BIT(DRM_MODE_TV_MODE_PAL_N)	| \
+	 BIT(DRM_MODE_TV_MODE_MONOCHROME))
+
+#define RP1VEC_VDAC_KHZ 108000
+
+/* ---------------------------------------------------------------------- */
+
+struct rp1_vec {
+	/* DRM base and platform device pointer */
+	struct drm_device drm;
+	struct platform_device *pdev;
+
+	/* Framework and helper objects */
+	struct drm_simple_display_pipe pipe;
+	struct drm_connector connector;
+
+	/* Clock. We assume this is always at 108 MHz. */
+	struct clk *vec_clock;
+
+	/* Block (VCC, CFG) base addresses, and current state */
+	void __iomem *hw_base[RP1VEC_NUM_HW_BLOCKS];
+	u32 cur_fmt;
+	bool fake_31khz, vec_running, pipe_enabled;
+	struct completion finished;
+
+	spinlock_t hw_lock; /* the following are used in line-match ISR */
+	dma_addr_t last_dma_addr;
+	u32 last_stride;
+	bool field_flip;
+	bool lower_field_flag;
+};
+
+/* ---------------------------------------------------------------------- */
+/* Functions to control the VEC/DMA block				  */
+
+void rp1vec_hw_setup(struct rp1_vec *vec,
+		     u32 in_format,
+		     struct drm_display_mode const *mode,
+		     int tvstd);
+void rp1vec_hw_update(struct rp1_vec *vec, dma_addr_t addr, u32 offset, u32 stride);
+void rp1vec_hw_stop(struct rp1_vec *vec);
+int rp1vec_hw_busy(struct rp1_vec *vec);
+irqreturn_t rp1vec_hw_isr(int irq, void *dev);
+void rp1vec_hw_vblank_ctrl(struct rp1_vec *vec, int enable);
+
+/* ---------------------------------------------------------------------- */
+/* Functions to control the VIDEO OUT CFG block and check RP1 platform	  */
+
+void rp1vec_vidout_setup(struct rp1_vec *vec);
+void rp1vec_vidout_poweroff(struct rp1_vec *vec);
diff --git a/drivers/gpu/drm/rp1/rp1-vec/rp1_vec_cfg.c b/drivers/gpu/drm/rp1/rp1-vec/rp1_vec_cfg.c
new file mode 100644
index 000000000000..f3c8e273e083
--- /dev/null
+++ b/drivers/gpu/drm/rp1/rp1-vec/rp1_vec_cfg.c
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * DRM Driver for Video output on Raspberry Pi RP1
+ * Functions to set up VIDEO_OUT_CFG registers
+ * Copyright (c) 2023-2025 Raspberry Pi Limited.
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/printk.h>
+#include <linux/rp1_platform.h>
+
+#include "rp1_vec.h"
+
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_SEL
+// Description : Selects source: 0 => DPI, 1 =>VEC; optionally invert clock
+#define VIDEO_OUT_CFG_SEL                    0x0000
+#define VIDEO_OUT_CFG_SEL_BITS               0x00000013
+#define VIDEO_OUT_CFG_SEL_RESET              0x00000000
+#define VIDEO_OUT_CFG_SEL_PCLK_INV           BIT(4)
+#define VIDEO_OUT_CFG_SEL_PAD_MUX            BIT(1)
+#define VIDEO_OUT_CFG_SEL_VDAC_MUX           BIT(0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_VDAC_CFG
+// Description : Configure SNPS VDAC
+#define VIDEO_OUT_CFG_VDAC_CFG               0x0004
+#define VIDEO_OUT_CFG_VDAC_CFG_BITS          0x1fffffff
+#define VIDEO_OUT_CFG_VDAC_CFG_RESET         0x0003ffff
+#define VIDEO_OUT_CFG_VDAC_CFG_ENCTR         GENMASK(28, 26)
+#define VIDEO_OUT_CFG_VDAC_CFG_ENSC          GENMASK(25, 23)
+#define VIDEO_OUT_CFG_VDAC_CFG_ENDAC         GENMASK(22, 20)
+#define VIDEO_OUT_CFG_VDAC_CFG_ENVBG         BIT(19)
+#define VIDEO_OUT_CFG_VDAC_CFG_ENEXTREF      BIT(18)
+#define VIDEO_OUT_CFG_VDAC_CFG_DAC2GC        GENMASK(17, 12)
+#define VIDEO_OUT_CFG_VDAC_CFG_DAC1GC        GENMASK(11, 6)
+#define VIDEO_OUT_CFG_VDAC_CFG_DAC0GC        GENMASK(5, 0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_VDAC_STATUS
+// Description : Read VDAC status
+#define VIDEO_OUT_CFG_VDAC_STATUS            0x0008
+#define VIDEO_OUT_CFG_VDAC_STATUS_BITS       0x00000017
+#define VIDEO_OUT_CFG_VDAC_STATUS_RESET      0x00000000
+#define VIDEO_OUT_CFG_VDAC_STATUS_ENCTR3     BIT(4)
+#define VIDEO_OUT_CFG_VDAC_STATUS_CABLEOUT   GENMASK(2, 0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_MEM_PD
+// Description : Control memory power down
+#define VIDEO_OUT_CFG_MEM_PD                 0x000c
+#define VIDEO_OUT_CFG_MEM_PD_BITS            0x00000003
+#define VIDEO_OUT_CFG_MEM_PD_RESET           0x00000000
+#define VIDEO_OUT_CFG_MEM_PD_VEC             BIT(1)
+#define VIDEO_OUT_CFG_MEM_PD_DPI             BIT(0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_TEST_OVERRIDE
+// Description : Allow forcing of output values
+#define VIDEO_OUT_CFG_TEST_OVERRIDE          0x0010
+#define VIDEO_OUT_CFG_TEST_OVERRIDE_BITS     0xffffffff
+#define VIDEO_OUT_CFG_TEST_OVERRIDE_RESET    0x00000000
+#define VIDEO_OUT_CFG_TEST_OVERRIDE_PAD      BIT(31)
+#define VIDEO_OUT_CFG_TEST_OVERRIDE_VDAC     BIT(30)
+#define VIDEO_OUT_CFG_TEST_OVERRIDE_RGBVAL   GENMASK(29, 0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_INTR
+// Description : Raw Interrupts
+#define VIDEO_OUT_CFG_INTR                   0x0014
+#define VIDEO_OUT_CFG_INTR_BITS              0x00000003
+#define VIDEO_OUT_CFG_INTR_RESET             0x00000000
+#define VIDEO_OUT_CFG_INTR_DPI               BIT(1)
+#define VIDEO_OUT_CFG_INTR_VEC               BIT(0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_INTE
+// Description : Interrupt Enable
+#define VIDEO_OUT_CFG_INTE                   0x0018
+#define VIDEO_OUT_CFG_INTE_BITS              0x00000003
+#define VIDEO_OUT_CFG_INTE_RESET             0x00000000
+#define VIDEO_OUT_CFG_INTE_DPI               BIT(1)
+#define VIDEO_OUT_CFG_INTE_VEC               BIT(0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_INTF
+// Description : Interrupt Force
+#define VIDEO_OUT_CFG_INTF                   0x001c
+#define VIDEO_OUT_CFG_INTF_BITS              0x00000003
+#define VIDEO_OUT_CFG_INTF_RESET             0x00000000
+#define VIDEO_OUT_CFG_INTF_DPI               BIT(1)
+#define VIDEO_OUT_CFG_INTF_VEC               BIT(0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_INTS
+// Description : Interrupt status after masking & forcing
+#define VIDEO_OUT_CFG_INTS                   0x0020
+#define VIDEO_OUT_CFG_INTS_BITS              0x00000003
+#define VIDEO_OUT_CFG_INTS_RESET             0x00000000
+#define VIDEO_OUT_CFG_INTS_DPI               BIT(1)
+#define VIDEO_OUT_CFG_INTS_VEC               BIT(0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_BLOCK_ID
+// Description : Block Identifier
+//               Hexadecimal representation of "VOCF"
+#define VIDEO_OUT_CFG_BLOCK_ID               0x0024
+#define VIDEO_OUT_CFG_BLOCK_ID_BITS          0xffffffff
+#define VIDEO_OUT_CFG_BLOCK_ID_RESET         0x564f4346
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_INSTANCE_ID
+// Description : Block Instance Identifier
+#define VIDEO_OUT_CFG_INSTANCE_ID            0x0028
+#define VIDEO_OUT_CFG_INSTANCE_ID_BITS       0x0000000f
+#define VIDEO_OUT_CFG_INSTANCE_ID_RESET      0x00000000
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_RSTSEQ_AUTO
+// Description : None
+#define VIDEO_OUT_CFG_RSTSEQ_AUTO            0x002c
+#define VIDEO_OUT_CFG_RSTSEQ_AUTO_BITS       0x00000007
+#define VIDEO_OUT_CFG_RSTSEQ_AUTO_RESET      0x00000007
+#define VIDEO_OUT_CFG_RSTSEQ_AUTO_VEC        BIT(2)
+#define VIDEO_OUT_CFG_RSTSEQ_AUTO_DPI        BIT(1)
+#define VIDEO_OUT_CFG_RSTSEQ_AUTO_BUSADAPTER BIT(0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_RSTSEQ_PARALLEL
+// Description : None
+#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL        0x0030
+#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_BITS   0x00000007
+#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_RESET  0x00000006
+#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_VEC    BIT(2)
+#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_DPI    BIT(1)
+#define VIDEO_OUT_CFG_RSTSEQ_PARALLEL_BUSADAPTER BIT(0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_RSTSEQ_CTRL
+// Description : None
+#define VIDEO_OUT_CFG_RSTSEQ_CTRL            0x0034
+#define VIDEO_OUT_CFG_RSTSEQ_CTRL_BITS       0x00000007
+#define VIDEO_OUT_CFG_RSTSEQ_CTRL_RESET      0x00000000
+#define VIDEO_OUT_CFG_RSTSEQ_CTRL_VEC        BIT(2)
+#define VIDEO_OUT_CFG_RSTSEQ_CTRL_DPI        BIT(1)
+#define VIDEO_OUT_CFG_RSTSEQ_CTRL_BUSADAPTER BIT(0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_RSTSEQ_TRIG
+// Description : None
+#define VIDEO_OUT_CFG_RSTSEQ_TRIG            0x0038
+#define VIDEO_OUT_CFG_RSTSEQ_TRIG_BITS       0x00000007
+#define VIDEO_OUT_CFG_RSTSEQ_TRIG_RESET      0x00000000
+#define VIDEO_OUT_CFG_RSTSEQ_TRIG_VEC        BIT(2)
+#define VIDEO_OUT_CFG_RSTSEQ_TRIG_DPI        BIT(1)
+#define VIDEO_OUT_CFG_RSTSEQ_TRIG_BUSADAPTER BIT(0)
+// =============================================================================
+// Register    : VIDEO_OUT_CFG_RSTSEQ_DONE
+// Description : None
+#define VIDEO_OUT_CFG_RSTSEQ_DONE            0x003c
+#define VIDEO_OUT_CFG_RSTSEQ_DONE_BITS       0x00000007
+#define VIDEO_OUT_CFG_RSTSEQ_DONE_RESET      0x00000000
+#define VIDEO_OUT_CFG_RSTSEQ_DONE_VEC        BIT(2)
+#define VIDEO_OUT_CFG_RSTSEQ_DONE_DPI        BIT(1)
+#define VIDEO_OUT_CFG_RSTSEQ_DONE_BUSADAPTER BIT(0)
+// =============================================================================
+
+#define CFG_WRITE(reg, val)  writel((val),  vec->hw_base[RP1VEC_HW_BLOCK_CFG] + (reg))
+#define CFG_READ(reg)        readl(vec->hw_base[RP1VEC_HW_BLOCK_CFG] + (reg))
+
+void rp1vec_vidout_setup(struct rp1_vec *vec)
+{
+	/*
+	 * We assume DPI and VEC can't be used at the same time (due to
+	 * clashing requirements for PLL_VIDEO, and potentially for VDAC).
+	 * We therefore leave DPI memories powered down.
+	 */
+	CFG_WRITE(VIDEO_OUT_CFG_MEM_PD, VIDEO_OUT_CFG_MEM_PD_DPI);
+	CFG_WRITE(VIDEO_OUT_CFG_TEST_OVERRIDE, 0);
+
+	/* VEC->Pads (GPIOs 11:4 with clock on GPIO0); VEC->VDAC */
+	CFG_WRITE(VIDEO_OUT_CFG_SEL,
+		  VIDEO_OUT_CFG_SEL_VDAC_MUX | VIDEO_OUT_CFG_SEL_PAD_MUX);
+
+	/* configure VDAC for 1 channel, bandgap on, 1.28V swing */
+	CFG_WRITE(VIDEO_OUT_CFG_VDAC_CFG, 0x0019ffff);
+
+	/* enable VEC interrupt */
+	CFG_WRITE(VIDEO_OUT_CFG_INTE, VIDEO_OUT_CFG_INTE_VEC);
+}
+
+void rp1vec_vidout_poweroff(struct rp1_vec *vec)
+{
+	/* disable VEC interrupt */
+	CFG_WRITE(VIDEO_OUT_CFG_INTE, 0);
+
+	/* Ensure VDAC is turned off; power down DPI,VEC memories */
+	CFG_WRITE(VIDEO_OUT_CFG_VDAC_CFG, 0);
+	CFG_WRITE(VIDEO_OUT_CFG_MEM_PD, VIDEO_OUT_CFG_MEM_PD_BITS);
+}
diff --git a/drivers/gpu/drm/rp1/rp1-vec/rp1_vec_hw.c b/drivers/gpu/drm/rp1/rp1-vec/rp1_vec_hw.c
new file mode 100644
index 000000000000..c69dc031777d
--- /dev/null
+++ b/drivers/gpu/drm/rp1/rp1-vec/rp1_vec_hw.c
@@ -0,0 +1,634 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * DRM Driver for VEC output on Raspberry Pi RP1
+ *
+ * Copyright (c) 2023 Raspberry Pi Limited.
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_print.h>
+#include <drm/drm_vblank.h>
+
+#include "rp1_vec.h"
+#include "vec_regs.h"
+
+#define BITS(field, val)    (((val) << (field ## _LSB)) & (field ## _BITS))
+#define VEC_WRITE(reg, val) writel((val), vec->hw_base[RP1VEC_HW_BLOCK_VEC] + (reg ## _OFFSET))
+#define VEC_READ(reg)	    readl(vec->hw_base[RP1VEC_HW_BLOCK_VEC] + (reg ## _OFFSET))
+
+static void rp1vec_write_regs(struct rp1_vec *vec, u32 offset, u32 const *vals, u32 num)
+{
+	while (num--) {
+		writel(*vals++, vec->hw_base[RP1VEC_HW_BLOCK_VEC] + offset);
+		offset += 4;
+	}
+}
+
+int rp1vec_hw_busy(struct rp1_vec *vec)
+{
+	/* Read the undocumented "pline_busy" flag */
+	return VEC_READ(VEC_STATUS) & 1;
+}
+
+/* Table of supported input (in-memory/DMA) pixel formats. */
+struct rp1vec_ipixfmt {
+	u32 format; /* DRM format code				 */
+	u32 mask;   /* RGB masks (10 bits each, left justified)	 */
+	u32 shift;  /* RGB MSB positions in the memory word	 */
+	u32 rgbsz;  /* Shifts used for scaling; also (BPP/8-1)	 */
+};
+
+#define MASK_RGB(r, g, b) \
+	(BITS(VEC_IMASK_MASK_R, r) | BITS(VEC_IMASK_MASK_G, g) | BITS(VEC_IMASK_MASK_B, b))
+#define SHIFT_RGB(r, g, b) \
+	(BITS(VEC_SHIFT_SHIFT_R, r) | BITS(VEC_SHIFT_SHIFT_G, g) | BITS(VEC_SHIFT_SHIFT_B, b))
+
+static const struct rp1vec_ipixfmt my_formats[] = {
+	{
+		.format = DRM_FORMAT_XRGB8888,
+		.mask	= MASK_RGB(0x3fc, 0x3fc, 0x3fc),
+		.shift	= SHIFT_RGB(23, 15, 7),
+		.rgbsz	= BITS(VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1, 3),
+	},
+	{
+		.format = DRM_FORMAT_XBGR8888,
+		.mask	= MASK_RGB(0x3fc, 0x3fc, 0x3fc),
+		.shift	= SHIFT_RGB(7, 15, 23),
+		.rgbsz	= BITS(VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1, 3),
+	},
+	{
+		.format = DRM_FORMAT_ARGB8888,
+		.mask	= MASK_RGB(0x3fc, 0x3fc, 0x3fc),
+		.shift	= SHIFT_RGB(23, 15, 7),
+		.rgbsz	= BITS(VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1, 3),
+	},
+	{
+		.format = DRM_FORMAT_ABGR8888,
+		.mask	= MASK_RGB(0x3fc, 0x3fc, 0x3fc),
+		.shift	= SHIFT_RGB(7, 15, 23),
+		.rgbsz	= BITS(VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1, 3),
+	},
+	{
+		.format = DRM_FORMAT_RGB888,
+		.mask	= MASK_RGB(0x3fc, 0x3fc, 0x3fc),
+		.shift	= SHIFT_RGB(23, 15, 7),
+		.rgbsz	= BITS(VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1, 2),
+	},
+	{
+		.format = DRM_FORMAT_BGR888,
+		.mask	= MASK_RGB(0x3fc, 0x3fc, 0x3fc),
+		.shift	= SHIFT_RGB(7, 15, 23),
+		.rgbsz	= BITS(VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1, 2),
+	},
+	{
+		.format = DRM_FORMAT_RGB565,
+		.mask	= MASK_RGB(0x3e0, 0x3f0, 0x3e0),
+		.shift	= SHIFT_RGB(15, 10, 4),
+		.rgbsz	= BITS(VEC_RGBSZ_SCALE_R, 5) |
+			  BITS(VEC_RGBSZ_SCALE_G, 6) |
+			  BITS(VEC_RGBSZ_SCALE_B, 5) |
+			  BITS(VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1, 1),
+	}
+};
+
+/*
+ * Hardware mode descriptions (@ 108 MHz VDAC clock)
+ * See "vec_regs.h" for further descriptions of these registers and fields.
+ * Driver should adjust some values for other TV standards and for pixel rate,
+ * and must ensure that ((de_end - de_bgn) % rate) == 0.
+ *
+ * To support 60fps update in interlaced modes, we now do ISR-based field-flip.
+ * The FIELDS_PER_FRAME_MINUS1 flag in "misc" is no longer set. Some vertical
+ * timings have been rotated wrt conventional line-numbering (to ensure a gap
+ * between the last active line and nominal end-of-field).
+ */
+
+struct rp1vec_hwmode {
+	u16  max_rows_per_field;   /* active lines per field (including partial ones)  */
+	u16  ref_vfp;		   /* nominal (vsync_start - vdisplay) when max height */
+	bool interlaced;	   /* set for interlaced			       */
+	bool first_field_odd;	   /* true if odd-indexed scanlines go to first field  */
+	s16  scale_v;		   /* V scale in 2.8 format (for power-of-2 CIC rates) */
+	s16  scale_u;		   /* U scale in 2.8 format (for power-of-2 CIC rates) */
+	u16  scale_y;		   /* Y scale in 2.8 format (for power-of-2 CIC rates) */
+	u16  de_end;		   /* end of horizontal Data Active period at 108MHz   */
+	u16  de_bgn;		   /* start of horizontal Data Active period	       */
+	u16  half_lines_per_field; /* number of half lines per field		       */
+	s16  pedestal;		   /* pedestal (1024 = 100IRE) including FIR overshoot */
+	u16  scale_luma;	   /* back end luma scaling in 1.15 format wrt DAC FSD */
+	u16  scale_sync;	   /* back end sync scaling / blanking level as above  */
+	u32  scale_burst_chroma;   /* back end { burst, chroma } scaling	       */
+	u32  misc;		   /* Contents of the "EC" register except rate,shift  */
+	u64  nco_freq;		   /* colour carrier frequency * (2**64) / 108MHz      */
+	u32  timing_regs[14];	   /* other back end registers 0x84 .. 0xB8	       */
+};
+
+/* { NTSC, PAL, PAL-M } x { progressive, interlaced } */
+static const struct rp1vec_hwmode rp1vec_hwmodes[3][2] = {
+	{
+		/* NTSC */
+		{
+			.max_rows_per_field = 240,
+			.ref_vfp = 2,
+			.interlaced = false,
+			.first_field_odd = false,
+			.scale_v = 0x0cf,
+			.scale_u = 0x074,
+			.scale_y = 0x107,
+			.de_end	 = 0x1a4f,
+			.de_bgn	 = 0x038f,
+			.half_lines_per_field = 524, /* also works with 526/2 lines */
+			.pedestal = 0x04c,
+			.scale_luma = 0x8c9a,
+			.scale_sync = 0x3851,
+			.scale_burst_chroma = 0x11195561,
+			.misc = 0x00090c00, /* 5-tap FIR, SEQ_EN, 4 fld sync */
+			.nco_freq = 0x087c1f07c1f07c1f,
+			.timing_regs = {
+				0x03e10cc6, 0x0d6801fb, 0x023d034c, 0x00f80b6d,
+				0x00000005, 0x0006000b, 0x000c0011, 0x000a0106,
+				0x00000000, 0x00000000, 0x00000000, 0x00000000,
+				0x00170106, 0x00000000
+			},
+		}, {
+			.max_rows_per_field = 243,
+			.ref_vfp = 3,
+			.interlaced = true,
+			.first_field_odd = true,
+			.scale_v = 0x0cf,
+			.scale_u = 0x074,
+			.scale_y = 0x107,
+			.de_end = 0x1a4f,
+			.de_bgn = 0x038f,
+			.half_lines_per_field = 525,
+			.pedestal = 0x04c,
+			.scale_luma = 0x8c9a,
+			.scale_sync = 0x3851,
+			.scale_burst_chroma = 0x11195561,
+			.misc = 0x00094c00, /* 5-tap FIR, SEQ_EN, 2 flds, 4 fld sync */
+			.nco_freq = 0x087c1f07c1f07c1f,
+			.timing_regs = {
+				0x03e10cc6, 0x0d6801fb, 0x023d034c, 0x00f80b6d,
+				0x0207020c, 0x00000005, 0x0006000b, 0x00070104,
+				0x010e020a, 0x00000000, 0x00000000, 0x0119020a,
+				0x00120103, 0x01040118,
+			},
+		},
+	}, {
+		/* PAL */
+		{
+			.max_rows_per_field = 288,
+			.ref_vfp = 2,
+			.interlaced = false,
+			.first_field_odd = false,
+			.scale_v = 0x0e0,
+			.scale_u = 0x07e,
+			.scale_y = 0x11c,
+			.de_end = 0x1ab6,
+			.de_bgn = 0x03f6,
+			.half_lines_per_field = 624,
+			.pedestal = 0x00a, /* nonzero for max FIR overshoot after CIC */
+			.scale_luma = 0x89d8,
+			.scale_sync = 0x3c00,
+			.scale_burst_chroma = 0x0caf53b5,
+			.misc = 0x00091c01, /* 5-tap FIR, SEQ_EN, 8 fld sync, PAL */
+			.nco_freq = 0x0a8262b2cc48c1d1,
+			.timing_regs = {
+				0x04660cee, 0x0d8001fb, 0x025c034f, 0x00fd0b84,
+				0x026c0270, 0x00000004, 0x00050009, 0x00070135,
+				0x00000000, 0x00000000, 0x00000000, 0x00000000,
+				0x00170136, 0x00000000,
+			},
+		}, {
+			.max_rows_per_field = 288,
+			.ref_vfp = 5,
+			.interlaced = true,
+			.first_field_odd = false,
+			.scale_v = 0x0e0,
+			.scale_u = 0x07e,
+			.scale_y = 0x11c,
+			.de_end = 0x1ab6,
+			.de_bgn = 0x03f6,
+			.half_lines_per_field = 625,
+			.pedestal = 0x00a,
+			.scale_luma = 0x89d8,
+			.scale_sync = 0x3c00,
+			.scale_burst_chroma = 0x0caf53b5,
+			.misc = 0x0009dc01, /* 5-tap FIR, SEQ_EN, 4 flds, 8 fld sync, PAL */
+			.nco_freq = 0x0a8262b2cc48c1d1,
+			.timing_regs = {
+				0x04660cee, 0x0d8001fb, 0x025c034f, 0x00fd0b84,
+				0x026c0270, 0x00000004, 0x00050009, 0x00070135,
+				0x013f026d, 0x00060136, 0x0140026e, 0x0150026e,
+				0x00180136, 0x026f0017,
+			},
+		},
+	}, {
+		/* PAL-M */
+		{
+			.max_rows_per_field = 240,
+			.ref_vfp = 2,
+			.interlaced = false,
+			.first_field_odd = false,
+			.scale_v = 0x0e0,
+			.scale_u = 0x07e,
+			.scale_y = 0x11c,
+			.de_end = 0x1a4f,
+			.de_bgn = 0x038f,
+			.half_lines_per_field = 524,
+			.pedestal = 0x00a,
+			.scale_luma = 0x89d8,
+			.scale_sync = 0x3851,
+			.scale_burst_chroma = 0x0d5c53b5,
+			.misc = 0x00091c01, /* 5-tap FIR, SEQ_EN, 8 fld sync, PAL */
+			.nco_freq = 0x0879bbf8d6d33ea8,
+			.timing_regs = {
+				0x03e10cc6, 0x0d6801fb, 0x023c034c, 0x00f80b6e,
+				0x00000005, 0x0006000b, 0x000c0011, 0x000a0106,
+				0x00000000, 0x00000000, 0x00000000, 0x00000000,
+				0x00170106, 0x00000000,
+			},
+		}, {
+			.max_rows_per_field = 243,
+			.ref_vfp = 3,
+			.interlaced = true,
+			.first_field_odd = true,
+			.scale_v = 0x0e0,
+			.scale_u = 0x07e,
+			.scale_y = 0x11c,
+			.de_end = 0x1a4f,
+			.de_bgn = 0x038f,
+			.half_lines_per_field = 525,
+			.pedestal = 0x00a,
+			.scale_luma = 0x89d8,
+			.scale_sync = 0x3851,
+			.scale_burst_chroma = 0x0d5c53b5,
+			.misc = 0x0009dc01, /* 5-tap FIR, SEQ_EN, 4 flds, 8 fld sync, PAL */
+			.nco_freq = 0x0879bbf8d6d33ea8,
+			.timing_regs = {
+				0x03e10cc6, 0x0d6801fb, 0x023c034c, 0x00f80b6e,
+				0x0207020c, 0x00000005, 0x0006000b, 0x00090103,
+				0x010f0209, 0x00080102, 0x010e020a, 0x0119020a,
+				0x00120103, 0x01040118,
+			},
+		},
+	},
+};
+
+/* System A, System E */
+static const struct rp1vec_hwmode rp1vec_vintage_modes[2] = {
+	{
+		.max_rows_per_field = 190,
+		.ref_vfp = 0,
+		.interlaced = true,
+		.first_field_odd = true,
+		.scale_v = 0,
+		.scale_u = 0,
+		.scale_y = 0x11c,
+		.de_end = 0x2920,
+		.de_bgn = 0x06a0,
+		.half_lines_per_field = 405,
+		.pedestal = 0x00a,
+		.scale_luma = 0x89d8,
+		.scale_sync = 0x3c00,
+		.scale_burst_chroma = 0,
+		.misc = 0x00084000, /* 5-tap FIR, 2 fields */
+		.nco_freq = 0,
+		.timing_regs = {
+			0x06f01430, 0x14d503cc, 0x00000000, 0x000010de,
+			0x03000300, 0x018d0194, 0x03000300, 0x00000000,
+			0x00000000, 0x00000000, 0x00000000, 0x00d50191,
+			0x000a00c6, 0x00c700d4,
+		},
+	}, {
+		.max_rows_per_field = 369,
+		.ref_vfp = 6,
+		.interlaced = true,
+		.first_field_odd = true,
+		.scale_v = 0,
+		.scale_u = 0,
+		.scale_y = 0x11c,
+		.de_end = 0x145f,
+		.de_bgn = 0x03a7,
+		.half_lines_per_field = 819,
+		.pedestal = 0x0010,
+		.scale_luma = 0x89d8,
+		.scale_sync = 0x3b13,
+		.scale_burst_chroma = 0,
+		.misc = 0x00084000, /* 5-tap FIR, 2 fields */
+		.nco_freq = 0,
+		.timing_regs = {
+			0x03c10a08, 0x0a4d0114, 0x00000000, 0x000008a6,
+			0x00000000, 0x00000000, 0x00000000, 0x00000000,
+			0x00000000, 0x00000000, 0x00000000, 0x01c10330,
+			0x00270196, 0x019701c0,
+		},
+	},
+};
+
+static const u32 rp1vec_fir_regs[4] = {
+	0x00000000, 0x0be20200, 0x20f0f800, 0x265c7f00,
+};
+
+/*
+ * Correction for the 4th order CIC filter's gain of (rate ** 4)
+ * expressed as a right-shift and a reciprocal scale factor (Q12).
+ * These arrays are indexed by [rate - 4] where 4 <= rate <= 16.
+ */
+
+static const int rp1vec_scale_table[13] = {
+	4096, 6711, 6473, 6988,
+	4096, 5114, 6711, 4584,
+	6473, 4699, 6988, 5302,
+	4096
+};
+
+static const u32 rp1vec_rate_shift_table[13] = {
+	BITS(VEC_DAC_EC_INTERP_RATE_MINUS1,  3) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1,	7),
+	BITS(VEC_DAC_EC_INTERP_RATE_MINUS1,  4) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1,	9),
+	BITS(VEC_DAC_EC_INTERP_RATE_MINUS1,  5) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 10),
+	BITS(VEC_DAC_EC_INTERP_RATE_MINUS1,  6) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 11),
+	BITS(VEC_DAC_EC_INTERP_RATE_MINUS1,  7) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 11),
+	BITS(VEC_DAC_EC_INTERP_RATE_MINUS1,  8) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 12),
+	BITS(VEC_DAC_EC_INTERP_RATE_MINUS1,  9) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 13),
+	BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 10) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 13),
+	BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 11) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 14),
+	BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 12) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 14),
+	BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 13) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 15),
+	BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 14) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 15),
+	BITS(VEC_DAC_EC_INTERP_RATE_MINUS1, 15) | BITS(VEC_DAC_EC_INTERP_SHIFT_MINUS1, 15),
+};
+
+void rp1vec_hw_setup(struct rp1_vec *vec,
+		     u32 in_format,
+		     struct drm_display_mode const *mode,
+		     int tvstd)
+{
+	int i, mode_family, w, h;
+	const struct rp1vec_hwmode *hwm;
+	int wmax, hpad_r, vpad_b, rate, ref_2mid, usr_2mid;
+	u32 misc;
+
+	/* Input pixel format conversion */
+	for (i = 0; i < ARRAY_SIZE(my_formats); ++i) {
+		if (my_formats[i].format == in_format)
+			break;
+	}
+	if (i >= ARRAY_SIZE(my_formats)) {
+		dev_err(&vec->pdev->dev, "%s: bad input format\n", __func__);
+		i = 0;
+	}
+	VEC_WRITE(VEC_IMASK, my_formats[i].mask);
+	VEC_WRITE(VEC_SHIFT, my_formats[i].shift);
+	VEC_WRITE(VEC_RGBSZ, my_formats[i].rgbsz);
+
+	/* Pick an appropriate "base" mode, which we may modify.
+	 * Note that this driver supports a limited selection of video modes.
+	 * (A complete TV mode cannot be directly inferred from a DRM display mode:
+	 * features such as chroma burst sequence, half-lines and equalizing pulses
+	 * would be under-specified, and timings prone to rounding errors.)
+	 */
+	if (mode->vtotal == 405 || mode->vtotal == 819) {
+		/* Systems A and E (interlaced only) */
+		vec->fake_31khz = false;
+		mode_family = 1;
+		hwm = &rp1vec_vintage_modes[(mode->vtotal == 819) ? 1 : 0];
+	} else {
+		/* 525- and 625-line modes, with half-height and "fake" progressive variants */
+		vec->fake_31khz = mode->vtotal >= 500 && !(mode->flags & DRM_MODE_FLAG_INTERLACE);
+		h = (mode->vtotal >= 500) ? (mode->vtotal >> 1) : mode->vtotal;
+		if (h >= 272)
+			mode_family = 1; /* PAL-625 */
+		else if (tvstd == DRM_MODE_TV_MODE_PAL_M || tvstd == DRM_MODE_TV_MODE_PAL)
+			mode_family = 2; /* PAL-525 */
+		else
+			mode_family = 0; /* NTSC-525 */
+		hwm = &rp1vec_hwmodes[mode_family][(mode->flags & DRM_MODE_FLAG_INTERLACE) ? 1 : 0];
+	}
+
+	/*
+	 * Choose the upsampling rate (to 108MHz) in the range 4..16.
+	 * Clip dimensions to the limits of the chosen hardware mode, then add
+	 * padding as required, making some attempt to respect the DRM mode's
+	 * display position (relative to H and V sync start). Note that "wmax"
+	 * should be wider than the horizontal active region, to avoid boundary
+	 * artifacts (e.g. wmax = 728, w = 720, active ~= 704 in Rec.601 modes).
+	 */
+	i = (vec->fake_31khz) ? (mode->clock >> 1) : mode->clock;
+	rate = (i < (RP1VEC_VDAC_KHZ / 16)) ? 16 : max(4, (RP1VEC_VDAC_KHZ + 256) / i);
+	wmax = min((hwm->de_end - hwm->de_bgn) / rate, 1020);
+	w = min(mode->hdisplay, wmax);
+	ref_2mid = (hwm->de_bgn + hwm->de_end) / rate + 4; /* + 4 for FIR delay */
+	usr_2mid = (2 * (mode->htotal - mode->hsync_start) + w) * 2 * (hwm->timing_regs[1] >> 16) /
+		(rate * mode->htotal);
+	hpad_r = (wmax - w + ref_2mid - usr_2mid) >> 1;
+	hpad_r = min(max(0, hpad_r), wmax - w);
+	h = mode->vdisplay >> (hwm->interlaced || vec->fake_31khz);
+	h = min(h, 0 + hwm->max_rows_per_field);
+	vpad_b = ((mode->vsync_start - hwm->ref_vfp) >> (hwm->interlaced || vec->fake_31khz)) - h;
+	vpad_b = min(max(0, vpad_b), hwm->max_rows_per_field - h);
+
+	/*
+	 * Configure the hardware "front end" (in the sysclock domain).
+	 * Note: To support 60fps update (per-field buffer flips), we no longer
+	 * enable VEC's native interlaced mode (which can't flip in mid-frame).
+	 * Instead, send individual fields, using software to flip between them.
+	 */
+	VEC_WRITE(VEC_APB_TIMEOUT, 0x38);
+	VEC_WRITE(VEC_QOS,
+		  BITS(VEC_QOS_DQOS, 0x0) |
+		  BITS(VEC_QOS_ULEV, 0x8) |
+		  BITS(VEC_QOS_UQOS, 0x2) |
+		  BITS(VEC_QOS_LLEV, 0x4) |
+		  BITS(VEC_QOS_LQOS, 0x7));
+	VEC_WRITE(VEC_DMA_AREA,
+		  BITS(VEC_DMA_AREA_COLS_MINUS1, w - 1) |
+		  BITS(VEC_DMA_AREA_ROWS_PER_FIELD_MINUS1, h - 1));
+	VEC_WRITE(VEC_YUV_SCALING,
+		  BITS(VEC_YUV_SCALING_U10_SCALE_Y,
+		       (hwm->scale_y * rp1vec_scale_table[rate - 4] + 2048) >> 12) |
+		  BITS(VEC_YUV_SCALING_S10_SCALE_U,
+		       (hwm->scale_u * rp1vec_scale_table[rate - 4] + 2048) >> 12) |
+		  BITS(VEC_YUV_SCALING_S10_SCALE_V,
+		       (hwm->scale_v * rp1vec_scale_table[rate - 4] + 2048) >> 12));
+	VEC_WRITE(VEC_BACK_PORCH,
+		  BITS(VEC_BACK_PORCH_HBP_MINUS1, wmax - w - hpad_r - 1) |
+		  BITS(VEC_BACK_PORCH_VBP_MINUS1, hwm->max_rows_per_field - h - vpad_b - 1));
+	VEC_WRITE(VEC_FRONT_PORCH,
+		  BITS(VEC_FRONT_PORCH_HFP_MINUS1, hpad_r - 1) |
+		  BITS(VEC_FRONT_PORCH_VFP_MINUS1, vpad_b - 1));
+	VEC_WRITE(VEC_MODE,
+		  BITS(VEC_MODE_HIGH_WATER, 0xE0)				|
+		  BITS(VEC_MODE_ALIGN16, !((w | mode->hdisplay) & 15))		|
+		  BITS(VEC_MODE_VFP_EN, (vpad_b > 0))				|
+		  BITS(VEC_MODE_VBP_EN, (hwm->max_rows_per_field > h + vpad_b)) |
+		  BITS(VEC_MODE_HFP_EN, (hpad_r > 0))				|
+		  BITS(VEC_MODE_HBP_EN, (wmax > w + hpad_r)));
+
+	/* Configure the hardware "back end" (in the VDAC clock domain) */
+	VEC_WRITE(VEC_DAC_80,
+		  BITS(VEC_DAC_80_U14_DE_BGN, hwm->de_bgn) |
+		  BITS(VEC_DAC_80_U14_DE_END, hwm->de_bgn + wmax * rate));
+	rp1vec_write_regs(vec, 0x84, hwm->timing_regs, ARRAY_SIZE(hwm->timing_regs));
+	VEC_WRITE(VEC_DAC_C0, 0x0); /* DAC control/status -- not wired up in RP1 */
+	VEC_WRITE(VEC_DAC_C4, 0x007bffff); /* DAC control -- not wired up in RP1 */
+	misc = hwm->half_lines_per_field;
+	if (misc == 524 && (mode->vtotal >> vec->fake_31khz) == 263)
+		misc += 2;
+	if (tvstd == DRM_MODE_TV_MODE_NTSC_J && mode_family == 0) {
+		/* NTSC-J modification: reduce pedestal and increase gain */
+		VEC_WRITE(VEC_DAC_BC,
+			  BITS(VEC_DAC_BC_U11_HALF_LINES_PER_FIELD, misc) |
+			  BITS(VEC_DAC_BC_S11_PEDESTAL, 0x00a));
+		VEC_WRITE(VEC_DAC_C8,
+			  BITS(VEC_DAC_C8_U16_SCALE_LUMA, 0x9400) |
+			  BITS(VEC_DAC_C8_U16_SCALE_SYNC, hwm->scale_sync));
+	} else {
+		VEC_WRITE(VEC_DAC_BC,
+			  BITS(VEC_DAC_BC_U11_HALF_LINES_PER_FIELD, misc) |
+			  BITS(VEC_DAC_BC_S11_PEDESTAL, hwm->pedestal));
+		VEC_WRITE(VEC_DAC_C8,
+			  BITS(VEC_DAC_C8_U16_SCALE_LUMA, hwm->scale_luma) |
+			  BITS(VEC_DAC_C8_U16_SCALE_SYNC, hwm->scale_sync));
+	}
+	VEC_WRITE(VEC_DAC_CC, (tvstd >= DRM_MODE_TV_MODE_SECAM) ? 0 : hwm->scale_burst_chroma);
+	VEC_WRITE(VEC_DAC_D0, 0x02000000); /* ADC offsets -- not needed in RP1? */
+	misc = hwm->misc;
+	if ((tvstd == DRM_MODE_TV_MODE_NTSC_443 || tvstd == DRM_MODE_TV_MODE_PAL) &&
+	    mode_family != 1) {
+		/* Change colour carrier frequency to 4433618.75 Hz; disable hard sync */
+		VEC_WRITE(VEC_DAC_D4, 0xcc48c1d1);
+		VEC_WRITE(VEC_DAC_D8, 0x0a8262b2);
+		misc &= ~VEC_DAC_EC_SEQ_EN_BITS;
+	} else if (tvstd == DRM_MODE_TV_MODE_PAL_N && mode_family == 1) {
+		/* Change colour carrier frequency to 3582056.25 Hz */
+		VEC_WRITE(VEC_DAC_D4, 0x9ce075f7);
+		VEC_WRITE(VEC_DAC_D8, 0x087da511);
+	} else {
+		VEC_WRITE(VEC_DAC_D4, (u32)(hwm->nco_freq));
+		VEC_WRITE(VEC_DAC_D8, (u32)(hwm->nco_freq >> 32));
+	}
+	VEC_WRITE(VEC_DAC_EC, misc | rp1vec_rate_shift_table[rate - 4]);
+	rp1vec_write_regs(vec, 0xDC, rp1vec_fir_regs, ARRAY_SIZE(rp1vec_fir_regs));
+
+	/* State for software-based field flipping */
+	vec->field_flip = hwm->interlaced;
+	vec->lower_field_flag = hwm->first_field_odd;
+	vec->last_dma_addr = 0;
+
+	/* Set up interrupts and initialise VEC. It will start on the next rp1vec_hw_update() */
+	VEC_WRITE(VEC_IRQ_FLAGS, 0xFFFFFFFFu);
+	rp1vec_hw_vblank_ctrl(vec, 1);
+	i = rp1vec_hw_busy(vec);
+	if (i)
+		dev_warn(&vec->pdev->dev,
+			 "%s: VEC unexpectedly busy at start (0x%08x)",
+			__func__, VEC_READ(VEC_STATUS));
+
+	VEC_WRITE(VEC_CONTROL,
+		  BITS(VEC_CONTROL_START_ARM, (!i)) |
+		  BITS(VEC_CONTROL_AUTO_REPEAT, 1));
+}
+
+void rp1vec_hw_update(struct rp1_vec *vec, dma_addr_t addr, u32 offset, u32 stride)
+{
+	unsigned long flags;
+
+	addr += offset;
+
+	/*
+	 * Update STRIDE, DMAH and DMAL only. When called after rp1vec_hw_setup(),
+	 * DMA starts immediately; if already running, the buffer will flip at
+	 * the next vertical sync event. For field-rate update in interlaced modes,
+	 * we need to adjust the address and stride to display the current field,
+	 * saving the original address (so it can be flipped for subsequent fields).
+	 */
+	spin_lock_irqsave(&vec->hw_lock, flags);
+
+	vec->last_dma_addr = addr;
+	vec->last_stride = stride;
+	if (vec->field_flip || vec->fake_31khz) {
+		if (vec->fake_31khz || vec->lower_field_flag)
+			addr += stride;
+		stride *= 2;
+	}
+	VEC_WRITE(VEC_DMA_STRIDE, stride);
+	VEC_WRITE(VEC_DMA_ADDR_H, addr >> 32);
+	VEC_WRITE(VEC_DMA_ADDR_L, addr & 0xFFFFFFFFu);
+
+	spin_unlock_irqrestore(&vec->hw_lock, flags);
+}
+
+void rp1vec_hw_stop(struct rp1_vec *vec)
+{
+	unsigned long flags;
+
+	/*
+	 * Stop DMA by turning off the Auto-Repeat flag, and wait up to 100ms for
+	 * the current and any queued frame to end. "Force drain" flags are not used,
+	 * as they seem to prevent DMA from re-starting properly; it's safer to wait.
+	 */
+
+	spin_lock_irqsave(&vec->hw_lock, flags);
+	vec->last_dma_addr = 0;
+	reinit_completion(&vec->finished);
+	VEC_WRITE(VEC_CONTROL, 0);
+	spin_unlock_irqrestore(&vec->hw_lock, flags);
+
+	if (!wait_for_completion_timeout(&vec->finished, HZ / 10))
+		drm_err(&vec->drm, "%s: timed out waiting for idle\n", __func__);
+	VEC_WRITE(VEC_IRQ_ENABLES, 0);
+}
+
+void rp1vec_hw_vblank_ctrl(struct rp1_vec *vec, int enable)
+{
+	VEC_WRITE(VEC_IRQ_ENABLES,
+		  BITS(VEC_IRQ_ENABLES_DONE, 1)                |
+		  BITS(VEC_IRQ_ENABLES_DMA, (enable ? 1 : 0))  |
+		  BITS(VEC_IRQ_ENABLES_MATCH, vec->field_flip) |
+		  BITS(VEC_IRQ_ENABLES_MATCH_ROW, 32));
+}
+
+irqreturn_t rp1vec_hw_isr(int irq, void *dev)
+{
+	struct rp1_vec *vec = dev;
+	u32 u = VEC_READ(VEC_IRQ_FLAGS);
+
+	if (u) {
+		VEC_WRITE(VEC_IRQ_FLAGS, u);
+		if (u & VEC_IRQ_FLAGS_DMA_BITS)
+			drm_crtc_handle_vblank(&vec->pipe.crtc);
+		if (u & VEC_IRQ_FLAGS_DONE_BITS)
+			complete(&vec->finished);
+
+		/*
+		 * VEC has native support for interlaced modes, but that only
+		 * supports buffer-flips per frame (30fps), not field (60fps).
+		 * Instead, we always run the VEC front end in a "progressive"
+		 * mode and use the "field-flip" trick (see RP1 DPI driver).
+		 */
+		if ((u & VEC_IRQ_FLAGS_MATCH_BITS) && vec->field_flip) {
+			unsigned long flags;
+			dma_addr_t a;
+
+			spin_lock_irqsave(&vec->hw_lock, flags);
+			vec->lower_field_flag = !vec->lower_field_flag;
+			a = vec->last_dma_addr;
+			if (a) {
+				if (vec->lower_field_flag)
+					a += vec->last_stride;
+				VEC_WRITE(VEC_DMA_ADDR_H, a >> 32);
+				VEC_WRITE(VEC_DMA_ADDR_L, a & 0xFFFFFFFFu);
+			}
+			spin_unlock_irqrestore(&vec->hw_lock, flags);
+		}
+	}
+
+	return u ? IRQ_HANDLED : IRQ_NONE;
+}
diff --git a/drivers/gpu/drm/rp1/rp1-vec/vec_regs.h b/drivers/gpu/drm/rp1/rp1-vec/vec_regs.h
new file mode 100644
index 000000000000..03632f2e8d4b
--- /dev/null
+++ b/drivers/gpu/drm/rp1/rp1-vec/vec_regs.h
@@ -0,0 +1,1420 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+// =============================================================================
+// Copyright Raspberry Pi Ltd. 2023
+// vrbuild version: 56aac1a23c016cbbd229108f3b6efc1343842156-clean
+// THIS FILE IS GENERATED BY VRBUILD - DO NOT EDIT
+// =============================================================================
+// Register block : VEC
+// Version        : 1
+// Bus type       : apb
+// Description    : None
+// =============================================================================
+#ifndef VEC_REGS_DEFINED
+#define VEC_REGS_DEFINED
+#define VEC_REGS_RWTYPE_MSB 13
+#define VEC_REGS_RWTYPE_LSB 12
+// =============================================================================
+// Register    : VEC_CONTROL
+// JTAG access : synchronous
+// Description : None
+#define VEC_CONTROL_OFFSET 0x00000000
+#define VEC_CONTROL_BITS   0x00000007
+#define VEC_CONTROL_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_CONTROL_BARS
+// Description : Write '1' to display colour bar test pattern
+#define VEC_CONTROL_BARS_RESET  0x0
+#define VEC_CONTROL_BARS_BITS   0x00000004
+#define VEC_CONTROL_BARS_MSB    2
+#define VEC_CONTROL_BARS_LSB    2
+#define VEC_CONTROL_BARS_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_CONTROL_AUTO_REPEAT
+// Description : Write '1' to re-display same frame continuously
+#define VEC_CONTROL_AUTO_REPEAT_RESET  0x0
+#define VEC_CONTROL_AUTO_REPEAT_BITS   0x00000002
+#define VEC_CONTROL_AUTO_REPEAT_MSB    1
+#define VEC_CONTROL_AUTO_REPEAT_LSB    1
+#define VEC_CONTROL_AUTO_REPEAT_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_CONTROL_START_ARM
+// Description : Write '1' before first DMA address is written This bit always
+//               reads back as '0'
+#define VEC_CONTROL_START_ARM_RESET  0x0
+#define VEC_CONTROL_START_ARM_BITS   0x00000001
+#define VEC_CONTROL_START_ARM_MSB    0
+#define VEC_CONTROL_START_ARM_LSB    0
+#define VEC_CONTROL_START_ARM_ACCESS "SC"
+// =============================================================================
+// Register    : VEC_IRQ_ENABLES
+// JTAG access : synchronous
+// Description : None
+#define VEC_IRQ_ENABLES_OFFSET 0x00000004
+#define VEC_IRQ_ENABLES_BITS   0x03ff003f
+#define VEC_IRQ_ENABLES_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_IRQ_ENABLES_MATCH_ROW
+// Description : Raster line at which MATCH interrupt is signalled
+#define VEC_IRQ_ENABLES_MATCH_ROW_RESET  0x000
+#define VEC_IRQ_ENABLES_MATCH_ROW_BITS   0x03ff0000
+#define VEC_IRQ_ENABLES_MATCH_ROW_MSB    25
+#define VEC_IRQ_ENABLES_MATCH_ROW_LSB    16
+#define VEC_IRQ_ENABLES_MATCH_ROW_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_IRQ_ENABLES_MATCH
+// Description : Output raster == match_row reached
+#define VEC_IRQ_ENABLES_MATCH_RESET  0x0
+#define VEC_IRQ_ENABLES_MATCH_BITS   0x00000020
+#define VEC_IRQ_ENABLES_MATCH_MSB    5
+#define VEC_IRQ_ENABLES_MATCH_LSB    5
+#define VEC_IRQ_ENABLES_MATCH_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_IRQ_ENABLES_ERROR
+// Description : DMA address overwritten before it was taken
+#define VEC_IRQ_ENABLES_ERROR_RESET  0x0
+#define VEC_IRQ_ENABLES_ERROR_BITS   0x00000010
+#define VEC_IRQ_ENABLES_ERROR_MSB    4
+#define VEC_IRQ_ENABLES_ERROR_LSB    4
+#define VEC_IRQ_ENABLES_ERROR_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_IRQ_ENABLES_DONE
+// Description : Last word sent to DAC after end of video (= all clear)
+#define VEC_IRQ_ENABLES_DONE_RESET  0x0
+#define VEC_IRQ_ENABLES_DONE_BITS   0x00000008
+#define VEC_IRQ_ENABLES_DONE_MSB    3
+#define VEC_IRQ_ENABLES_DONE_LSB    3
+#define VEC_IRQ_ENABLES_DONE_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_IRQ_ENABLES_FRAME
+// Description : Start of frame
+#define VEC_IRQ_ENABLES_FRAME_RESET  0x0
+#define VEC_IRQ_ENABLES_FRAME_BITS   0x00000004
+#define VEC_IRQ_ENABLES_FRAME_MSB    2
+#define VEC_IRQ_ENABLES_FRAME_LSB    2
+#define VEC_IRQ_ENABLES_FRAME_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_IRQ_ENABLES_UNDERFLOW
+// Description : Underflow has occurred
+#define VEC_IRQ_ENABLES_UNDERFLOW_RESET  0x0
+#define VEC_IRQ_ENABLES_UNDERFLOW_BITS   0x00000002
+#define VEC_IRQ_ENABLES_UNDERFLOW_MSB    1
+#define VEC_IRQ_ENABLES_UNDERFLOW_LSB    1
+#define VEC_IRQ_ENABLES_UNDERFLOW_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_IRQ_ENABLES_DMA
+// Description : DMA ready to accept next frame start address
+#define VEC_IRQ_ENABLES_DMA_RESET  0x0
+#define VEC_IRQ_ENABLES_DMA_BITS   0x00000001
+#define VEC_IRQ_ENABLES_DMA_MSB    0
+#define VEC_IRQ_ENABLES_DMA_LSB    0
+#define VEC_IRQ_ENABLES_DMA_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_IRQ_FLAGS
+// JTAG access : synchronous
+// Description : None
+#define VEC_IRQ_FLAGS_OFFSET 0x00000008
+#define VEC_IRQ_FLAGS_BITS   0x0000003f
+#define VEC_IRQ_FLAGS_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_IRQ_FLAGS_MATCH
+// Description : Output raster == match_row reached
+#define VEC_IRQ_FLAGS_MATCH_RESET  0x0
+#define VEC_IRQ_FLAGS_MATCH_BITS   0x00000020
+#define VEC_IRQ_FLAGS_MATCH_MSB    5
+#define VEC_IRQ_FLAGS_MATCH_LSB    5
+#define VEC_IRQ_FLAGS_MATCH_ACCESS "WC"
+// -----------------------------------------------------------------------------
+// Field       : VEC_IRQ_FLAGS_ERROR
+// Description : DMA address overwritten before it was taken
+#define VEC_IRQ_FLAGS_ERROR_RESET  0x0
+#define VEC_IRQ_FLAGS_ERROR_BITS   0x00000010
+#define VEC_IRQ_FLAGS_ERROR_MSB    4
+#define VEC_IRQ_FLAGS_ERROR_LSB    4
+#define VEC_IRQ_FLAGS_ERROR_ACCESS "WC"
+// -----------------------------------------------------------------------------
+// Field       : VEC_IRQ_FLAGS_DONE
+// Description : Last word sent to DAC after end of video (= all clear)
+#define VEC_IRQ_FLAGS_DONE_RESET  0x0
+#define VEC_IRQ_FLAGS_DONE_BITS   0x00000008
+#define VEC_IRQ_FLAGS_DONE_MSB    3
+#define VEC_IRQ_FLAGS_DONE_LSB    3
+#define VEC_IRQ_FLAGS_DONE_ACCESS "WC"
+// -----------------------------------------------------------------------------
+// Field       : VEC_IRQ_FLAGS_FRAME
+// Description : Start of frame
+#define VEC_IRQ_FLAGS_FRAME_RESET  0x0
+#define VEC_IRQ_FLAGS_FRAME_BITS   0x00000004
+#define VEC_IRQ_FLAGS_FRAME_MSB    2
+#define VEC_IRQ_FLAGS_FRAME_LSB    2
+#define VEC_IRQ_FLAGS_FRAME_ACCESS "WC"
+// -----------------------------------------------------------------------------
+// Field       : VEC_IRQ_FLAGS_UNDERFLOW
+// Description : Underflow has occurred
+#define VEC_IRQ_FLAGS_UNDERFLOW_RESET  0x0
+#define VEC_IRQ_FLAGS_UNDERFLOW_BITS   0x00000002
+#define VEC_IRQ_FLAGS_UNDERFLOW_MSB    1
+#define VEC_IRQ_FLAGS_UNDERFLOW_LSB    1
+#define VEC_IRQ_FLAGS_UNDERFLOW_ACCESS "WC"
+// -----------------------------------------------------------------------------
+// Field       : VEC_IRQ_FLAGS_DMA
+// Description : DMA ready to accept next frame start address
+#define VEC_IRQ_FLAGS_DMA_RESET  0x0
+#define VEC_IRQ_FLAGS_DMA_BITS   0x00000001
+#define VEC_IRQ_FLAGS_DMA_MSB    0
+#define VEC_IRQ_FLAGS_DMA_LSB    0
+#define VEC_IRQ_FLAGS_DMA_ACCESS "WC"
+// =============================================================================
+// Register    : VEC_QOS
+// JTAG access : synchronous
+// Description : This register configures panic levels for the AXI ar_qos
+//               quality of service field. Panic status is driven by the number
+//               of rows held in the SRAM cache:
+#define VEC_QOS_OFFSET 0x0000000c
+#define VEC_QOS_BITS   0x000fffff
+#define VEC_QOS_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_QOS_UQOS
+// Description : Upper AXI QOS
+#define VEC_QOS_UQOS_RESET  0x0
+#define VEC_QOS_UQOS_BITS   0x000f0000
+#define VEC_QOS_UQOS_MSB    19
+#define VEC_QOS_UQOS_LSB    16
+#define VEC_QOS_UQOS_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_QOS_ULEV
+// Description : Upper trip level (resolution = 1 / 16 of cache size)
+#define VEC_QOS_ULEV_RESET  0x0
+#define VEC_QOS_ULEV_BITS   0x0000f000
+#define VEC_QOS_ULEV_MSB    15
+#define VEC_QOS_ULEV_LSB    12
+#define VEC_QOS_ULEV_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_QOS_LQOS
+// Description : Lower AXI QOS
+#define VEC_QOS_LQOS_RESET  0x0
+#define VEC_QOS_LQOS_BITS   0x00000f00
+#define VEC_QOS_LQOS_MSB    11
+#define VEC_QOS_LQOS_LSB    8
+#define VEC_QOS_LQOS_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_QOS_LLEV
+// Description : Lower trip level (resolution = 1 / 16 of cache size)
+#define VEC_QOS_LLEV_RESET  0x0
+#define VEC_QOS_LLEV_BITS   0x000000f0
+#define VEC_QOS_LLEV_MSB    7
+#define VEC_QOS_LLEV_LSB    4
+#define VEC_QOS_LLEV_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_QOS_DQOS
+// Description : Default QOS
+#define VEC_QOS_DQOS_RESET  0x0
+#define VEC_QOS_DQOS_BITS   0x0000000f
+#define VEC_QOS_DQOS_MSB    3
+#define VEC_QOS_DQOS_LSB    0
+#define VEC_QOS_DQOS_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DMA_ADDR_L
+// JTAG access : synchronous
+// Description : Lower 32-bits
+#define VEC_DMA_ADDR_L_OFFSET 0x00000010
+#define VEC_DMA_ADDR_L_BITS   0xffffffff
+#define VEC_DMA_ADDR_L_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DMA_ADDR_L_AXI_ADDR
+// Description : Byte address of DMA transfer frame buffer.
+#define VEC_DMA_ADDR_L_AXI_ADDR_RESET  0x00000000
+#define VEC_DMA_ADDR_L_AXI_ADDR_BITS   0xffffffff
+#define VEC_DMA_ADDR_L_AXI_ADDR_MSB    31
+#define VEC_DMA_ADDR_L_AXI_ADDR_LSB    0
+#define VEC_DMA_ADDR_L_AXI_ADDR_ACCESS "RWF"
+// =============================================================================
+// Register    : VEC_DMA_STRIDE
+// JTAG access : synchronous
+// Description : This register sets the line byte stride.
+#define VEC_DMA_STRIDE_OFFSET 0x00000014
+#define VEC_DMA_STRIDE_BITS   0xffffffff
+#define VEC_DMA_STRIDE_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DMA_STRIDE_STRIDE
+// Description : Byte stride
+#define VEC_DMA_STRIDE_STRIDE_RESET  0x00000000
+#define VEC_DMA_STRIDE_STRIDE_BITS   0xffffffff
+#define VEC_DMA_STRIDE_STRIDE_MSB    31
+#define VEC_DMA_STRIDE_STRIDE_LSB    0
+#define VEC_DMA_STRIDE_STRIDE_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DMA_AREA
+// JTAG access : synchronous
+// Description : Interlaced pixel area. See example driver code.
+#define VEC_DMA_AREA_OFFSET 0x00000018
+#define VEC_DMA_AREA_BITS   0x03ff03ff
+#define VEC_DMA_AREA_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DMA_AREA_COLS_MINUS1
+// Description : Width
+#define VEC_DMA_AREA_COLS_MINUS1_RESET  0x000
+#define VEC_DMA_AREA_COLS_MINUS1_BITS   0x03ff0000
+#define VEC_DMA_AREA_COLS_MINUS1_MSB    25
+#define VEC_DMA_AREA_COLS_MINUS1_LSB    16
+#define VEC_DMA_AREA_COLS_MINUS1_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DMA_AREA_ROWS_PER_FIELD_MINUS1
+// Description : Lines per field = half of lines per interlaced frame
+#define VEC_DMA_AREA_ROWS_PER_FIELD_MINUS1_RESET  0x000
+#define VEC_DMA_AREA_ROWS_PER_FIELD_MINUS1_BITS   0x000003ff
+#define VEC_DMA_AREA_ROWS_PER_FIELD_MINUS1_MSB    9
+#define VEC_DMA_AREA_ROWS_PER_FIELD_MINUS1_LSB    0
+#define VEC_DMA_AREA_ROWS_PER_FIELD_MINUS1_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_YUV_SCALING
+// JTAG access : synchronous
+// Description : None
+#define VEC_YUV_SCALING_OFFSET 0x0000001c
+#define VEC_YUV_SCALING_BITS   0x3fffffff
+#define VEC_YUV_SCALING_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_YUV_SCALING_U10_SCALE_Y
+// Description : Y unsigned scaling factor - 8 binary places
+#define VEC_YUV_SCALING_U10_SCALE_Y_RESET  0x000
+#define VEC_YUV_SCALING_U10_SCALE_Y_BITS   0x3ff00000
+#define VEC_YUV_SCALING_U10_SCALE_Y_MSB    29
+#define VEC_YUV_SCALING_U10_SCALE_Y_LSB    20
+#define VEC_YUV_SCALING_U10_SCALE_Y_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_YUV_SCALING_S10_SCALE_U
+// Description : U signed scaling factor - 8 binary places
+#define VEC_YUV_SCALING_S10_SCALE_U_RESET  0x000
+#define VEC_YUV_SCALING_S10_SCALE_U_BITS   0x000ffc00
+#define VEC_YUV_SCALING_S10_SCALE_U_MSB    19
+#define VEC_YUV_SCALING_S10_SCALE_U_LSB    10
+#define VEC_YUV_SCALING_S10_SCALE_U_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_YUV_SCALING_S10_SCALE_V
+// Description : V signed scaling factor - 8 binary please
+#define VEC_YUV_SCALING_S10_SCALE_V_RESET  0x000
+#define VEC_YUV_SCALING_S10_SCALE_V_BITS   0x000003ff
+#define VEC_YUV_SCALING_S10_SCALE_V_MSB    9
+#define VEC_YUV_SCALING_S10_SCALE_V_LSB    0
+#define VEC_YUV_SCALING_S10_SCALE_V_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_BACK_PORCH
+// JTAG access : synchronous
+// Description : None
+#define VEC_BACK_PORCH_OFFSET 0x00000020
+#define VEC_BACK_PORCH_BITS   0x03ff03ff
+#define VEC_BACK_PORCH_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_BACK_PORCH_HBP_MINUS1
+// Description : Horizontal back porch
+#define VEC_BACK_PORCH_HBP_MINUS1_RESET  0x000
+#define VEC_BACK_PORCH_HBP_MINUS1_BITS   0x03ff0000
+#define VEC_BACK_PORCH_HBP_MINUS1_MSB    25
+#define VEC_BACK_PORCH_HBP_MINUS1_LSB    16
+#define VEC_BACK_PORCH_HBP_MINUS1_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_BACK_PORCH_VBP_MINUS1
+// Description : Vertical back porch
+#define VEC_BACK_PORCH_VBP_MINUS1_RESET  0x000
+#define VEC_BACK_PORCH_VBP_MINUS1_BITS   0x000003ff
+#define VEC_BACK_PORCH_VBP_MINUS1_MSB    9
+#define VEC_BACK_PORCH_VBP_MINUS1_LSB    0
+#define VEC_BACK_PORCH_VBP_MINUS1_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_FRONT_PORCH
+// JTAG access : synchronous
+// Description : None
+#define VEC_FRONT_PORCH_OFFSET 0x00000024
+#define VEC_FRONT_PORCH_BITS   0x03ff03ff
+#define VEC_FRONT_PORCH_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_FRONT_PORCH_HFP_MINUS1
+// Description : Horizontal front porch
+#define VEC_FRONT_PORCH_HFP_MINUS1_RESET  0x000
+#define VEC_FRONT_PORCH_HFP_MINUS1_BITS   0x03ff0000
+#define VEC_FRONT_PORCH_HFP_MINUS1_MSB    25
+#define VEC_FRONT_PORCH_HFP_MINUS1_LSB    16
+#define VEC_FRONT_PORCH_HFP_MINUS1_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_FRONT_PORCH_VFP_MINUS1
+// Description : Vertical front porch
+#define VEC_FRONT_PORCH_VFP_MINUS1_RESET  0x000
+#define VEC_FRONT_PORCH_VFP_MINUS1_BITS   0x000003ff
+#define VEC_FRONT_PORCH_VFP_MINUS1_MSB    9
+#define VEC_FRONT_PORCH_VFP_MINUS1_LSB    0
+#define VEC_FRONT_PORCH_VFP_MINUS1_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_SHIFT
+// JTAG access : synchronous
+// Description : Positions of R,G,B MS bits in the memory word. Note: due to an
+//               unintended red/blue swap, these fields have been renamed since
+//               a previous version. There is no functional change.
+#define VEC_SHIFT_OFFSET 0x00000028
+#define VEC_SHIFT_BITS   0x00007fff
+#define VEC_SHIFT_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_SHIFT_SHIFT_R
+// Description : Red MSB
+#define VEC_SHIFT_SHIFT_R_RESET  0x00
+#define VEC_SHIFT_SHIFT_R_BITS   0x00007c00
+#define VEC_SHIFT_SHIFT_R_MSB    14
+#define VEC_SHIFT_SHIFT_R_LSB    10
+#define VEC_SHIFT_SHIFT_R_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_SHIFT_SHIFT_G
+// Description : Green MSB
+#define VEC_SHIFT_SHIFT_G_RESET  0x00
+#define VEC_SHIFT_SHIFT_G_BITS   0x000003e0
+#define VEC_SHIFT_SHIFT_G_MSB    9
+#define VEC_SHIFT_SHIFT_G_LSB    5
+#define VEC_SHIFT_SHIFT_G_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_SHIFT_SHIFT_B
+// Description : Blue MSB
+#define VEC_SHIFT_SHIFT_B_RESET  0x00
+#define VEC_SHIFT_SHIFT_B_BITS   0x0000001f
+#define VEC_SHIFT_SHIFT_B_MSB    4
+#define VEC_SHIFT_SHIFT_B_LSB    0
+#define VEC_SHIFT_SHIFT_B_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_IMASK
+// JTAG access : synchronous
+// Description : Masks for R,G,B significant bits, left-justified within 10-bit
+//               fields.
+#define VEC_IMASK_OFFSET 0x0000002c
+#define VEC_IMASK_BITS   0x3fffffff
+#define VEC_IMASK_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_IMASK_MASK_R
+// Description : Red mask
+#define VEC_IMASK_MASK_R_RESET  0x000
+#define VEC_IMASK_MASK_R_BITS   0x3ff00000
+#define VEC_IMASK_MASK_R_MSB    29
+#define VEC_IMASK_MASK_R_LSB    20
+#define VEC_IMASK_MASK_R_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_IMASK_MASK_G
+// Description : Green mask
+#define VEC_IMASK_MASK_G_RESET  0x000
+#define VEC_IMASK_MASK_G_BITS   0x000ffc00
+#define VEC_IMASK_MASK_G_MSB    19
+#define VEC_IMASK_MASK_G_LSB    10
+#define VEC_IMASK_MASK_G_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_IMASK_MASK_B
+// Description : Blue mask
+#define VEC_IMASK_MASK_B_RESET  0x000
+#define VEC_IMASK_MASK_B_BITS   0x000003ff
+#define VEC_IMASK_MASK_B_MSB    9
+#define VEC_IMASK_MASK_B_LSB    0
+#define VEC_IMASK_MASK_B_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_MODE
+// JTAG access : synchronous
+// Description : None
+#define VEC_MODE_OFFSET 0x00000030
+#define VEC_MODE_BITS   0x01ff003f
+#define VEC_MODE_RESET  0x01c00000
+// -----------------------------------------------------------------------------
+// Field       : VEC_MODE_HIGH_WATER
+// Description : ALWAYS WRITE 8'hE0
+#define VEC_MODE_HIGH_WATER_RESET  0xe0
+#define VEC_MODE_HIGH_WATER_BITS   0x01fe0000
+#define VEC_MODE_HIGH_WATER_MSB    24
+#define VEC_MODE_HIGH_WATER_LSB    17
+#define VEC_MODE_HIGH_WATER_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_MODE_ALIGN16
+// Description : Data: 0=BYTE aligned; 1=BEAT aligned
+#define VEC_MODE_ALIGN16_RESET  0x0
+#define VEC_MODE_ALIGN16_BITS   0x00010000
+#define VEC_MODE_ALIGN16_MSB    16
+#define VEC_MODE_ALIGN16_LSB    16
+#define VEC_MODE_ALIGN16_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_MODE_VFP_EN
+// Description : Enable vertical front porch
+#define VEC_MODE_VFP_EN_RESET  0x0
+#define VEC_MODE_VFP_EN_BITS   0x00000020
+#define VEC_MODE_VFP_EN_MSB    5
+#define VEC_MODE_VFP_EN_LSB    5
+#define VEC_MODE_VFP_EN_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_MODE_VBP_EN
+// Description : Enable vertical back porch
+#define VEC_MODE_VBP_EN_RESET  0x0
+#define VEC_MODE_VBP_EN_BITS   0x00000010
+#define VEC_MODE_VBP_EN_MSB    4
+#define VEC_MODE_VBP_EN_LSB    4
+#define VEC_MODE_VBP_EN_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_MODE_HFP_EN
+// Description : Enable horizontal front porch
+#define VEC_MODE_HFP_EN_RESET  0x0
+#define VEC_MODE_HFP_EN_BITS   0x00000008
+#define VEC_MODE_HFP_EN_MSB    3
+#define VEC_MODE_HFP_EN_LSB    3
+#define VEC_MODE_HFP_EN_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_MODE_HBP_EN
+// Description : Enable horizontal back porch
+#define VEC_MODE_HBP_EN_RESET  0x0
+#define VEC_MODE_HBP_EN_BITS   0x00000004
+#define VEC_MODE_HBP_EN_MSB    2
+#define VEC_MODE_HBP_EN_LSB    2
+#define VEC_MODE_HBP_EN_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_MODE_FIELDS_PER_FRAME_MINUS1
+// Description : Interlaced / progressive
+#define VEC_MODE_FIELDS_PER_FRAME_MINUS1_RESET  0x0
+#define VEC_MODE_FIELDS_PER_FRAME_MINUS1_BITS   0x00000002
+#define VEC_MODE_FIELDS_PER_FRAME_MINUS1_MSB    1
+#define VEC_MODE_FIELDS_PER_FRAME_MINUS1_LSB    1
+#define VEC_MODE_FIELDS_PER_FRAME_MINUS1_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_MODE_FIRST_FIELD_ODD
+// Description : Interlacing order: odd/even or even/odd
+#define VEC_MODE_FIRST_FIELD_ODD_RESET  0x0
+#define VEC_MODE_FIRST_FIELD_ODD_BITS   0x00000001
+#define VEC_MODE_FIRST_FIELD_ODD_MSB    0
+#define VEC_MODE_FIRST_FIELD_ODD_LSB    0
+#define VEC_MODE_FIRST_FIELD_ODD_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_RGBSZ
+// JTAG access : synchronous
+// Description : None
+#define VEC_RGBSZ_OFFSET 0x00000034
+#define VEC_RGBSZ_BITS   0x00030fff
+#define VEC_RGBSZ_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1
+// Description : Pixel stride
+#define VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1_RESET  0x0
+#define VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1_BITS   0x00030000
+#define VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1_MSB    17
+#define VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1_LSB    16
+#define VEC_RGBSZ_BYTES_PER_PIXEL_MINUS1_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_RGBSZ_SCALE_R
+// Description : Red number of bits for shift-and-OR scaling
+#define VEC_RGBSZ_SCALE_R_RESET  0x0
+#define VEC_RGBSZ_SCALE_R_BITS   0x00000f00
+#define VEC_RGBSZ_SCALE_R_MSB    11
+#define VEC_RGBSZ_SCALE_R_LSB    8
+#define VEC_RGBSZ_SCALE_R_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_RGBSZ_SCALE_G
+// Description : Green number of bits for shift-and-OR scaling
+#define VEC_RGBSZ_SCALE_G_RESET  0x0
+#define VEC_RGBSZ_SCALE_G_BITS   0x000000f0
+#define VEC_RGBSZ_SCALE_G_MSB    7
+#define VEC_RGBSZ_SCALE_G_LSB    4
+#define VEC_RGBSZ_SCALE_G_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_RGBSZ_SCALE_B
+// Description : Blue number of bits for shift-and-OR scaling
+#define VEC_RGBSZ_SCALE_B_RESET  0x0
+#define VEC_RGBSZ_SCALE_B_BITS   0x0000000f
+#define VEC_RGBSZ_SCALE_B_MSB    3
+#define VEC_RGBSZ_SCALE_B_LSB    0
+#define VEC_RGBSZ_SCALE_B_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_PANICS
+// JTAG access : synchronous
+// Description : None
+#define VEC_PANICS_OFFSET 0x00000038
+#define VEC_PANICS_BITS   0xffffffff
+#define VEC_PANICS_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_PANICS_UCOUNT
+// Description : Upper panic count
+#define VEC_PANICS_UCOUNT_RESET  0x0000
+#define VEC_PANICS_UCOUNT_BITS   0xffff0000
+#define VEC_PANICS_UCOUNT_MSB    31
+#define VEC_PANICS_UCOUNT_LSB    16
+#define VEC_PANICS_UCOUNT_ACCESS "WC"
+// -----------------------------------------------------------------------------
+// Field       : VEC_PANICS_LCOUNT
+// Description : Lower panic count
+#define VEC_PANICS_LCOUNT_RESET  0x0000
+#define VEC_PANICS_LCOUNT_BITS   0x0000ffff
+#define VEC_PANICS_LCOUNT_MSB    15
+#define VEC_PANICS_LCOUNT_LSB    0
+#define VEC_PANICS_LCOUNT_ACCESS "WC"
+// =============================================================================
+// Register    : VEC_STATUS
+// JTAG access : synchronous
+// Description : None
+#define VEC_STATUS_OFFSET 0x0000003c
+#define VEC_STATUS_BITS   0xff000000
+#define VEC_STATUS_RESET  0x0d000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_STATUS_VERSION
+// Description : VEC module version code
+#define VEC_STATUS_VERSION_RESET  0x0d
+#define VEC_STATUS_VERSION_BITS   0xff000000
+#define VEC_STATUS_VERSION_MSB    31
+#define VEC_STATUS_VERSION_LSB    24
+#define VEC_STATUS_VERSION_ACCESS "RO"
+// =============================================================================
+// Register    : VEC_DMA_ADDR_H
+// JTAG access : synchronous
+// Description : Upper 32-bits
+#define VEC_DMA_ADDR_H_OFFSET 0x00000040
+#define VEC_DMA_ADDR_H_BITS   0xffffffff
+#define VEC_DMA_ADDR_H_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DMA_ADDR_H_AXI_ADDR
+// Description : Byte address of DMA transfer frame buffer.
+#define VEC_DMA_ADDR_H_AXI_ADDR_RESET  0x00000000
+#define VEC_DMA_ADDR_H_AXI_ADDR_BITS   0xffffffff
+#define VEC_DMA_ADDR_H_AXI_ADDR_MSB    31
+#define VEC_DMA_ADDR_H_AXI_ADDR_LSB    0
+#define VEC_DMA_ADDR_H_AXI_ADDR_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_BURST_ADDR_L
+// JTAG access : synchronous
+// Description : None
+#define VEC_BURST_ADDR_L_OFFSET 0x00000044
+#define VEC_BURST_ADDR_L_BITS   0xffffffff
+#define VEC_BURST_ADDR_L_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_BURST_ADDR_L_BURST_ADDR
+// Description : the lower 32-bits of the most recent read request sent to AXI
+//               memory.
+#define VEC_BURST_ADDR_L_BURST_ADDR_RESET  0x00000000
+#define VEC_BURST_ADDR_L_BURST_ADDR_BITS   0xffffffff
+#define VEC_BURST_ADDR_L_BURST_ADDR_MSB    31
+#define VEC_BURST_ADDR_L_BURST_ADDR_LSB    0
+#define VEC_BURST_ADDR_L_BURST_ADDR_ACCESS "RO"
+// =============================================================================
+// Register    : VEC_APB_TIMEOUT
+// JTAG access : synchronous
+// Description : None
+#define VEC_APB_TIMEOUT_OFFSET 0x00000048
+#define VEC_APB_TIMEOUT_BITS   0x000103ff
+#define VEC_APB_TIMEOUT_RESET  0x00000014
+// -----------------------------------------------------------------------------
+// Field       : VEC_APB_TIMEOUT_SLVERR_EN
+// Description : 1 = Assert PREADY and PSLVERR on timeout 0 = Assert PREADY only
+#define VEC_APB_TIMEOUT_SLVERR_EN_RESET  0x0
+#define VEC_APB_TIMEOUT_SLVERR_EN_BITS   0x00010000
+#define VEC_APB_TIMEOUT_SLVERR_EN_MSB    16
+#define VEC_APB_TIMEOUT_SLVERR_EN_LSB    16
+#define VEC_APB_TIMEOUT_SLVERR_EN_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_APB_TIMEOUT_TIMEOUT
+// Description : Maximum AXI clock cycles to wait for responses from DAC clock
+//               domain APB block
+#define VEC_APB_TIMEOUT_TIMEOUT_RESET  0x014
+#define VEC_APB_TIMEOUT_TIMEOUT_BITS   0x000003ff
+#define VEC_APB_TIMEOUT_TIMEOUT_MSB    9
+#define VEC_APB_TIMEOUT_TIMEOUT_LSB    0
+#define VEC_APB_TIMEOUT_TIMEOUT_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_80
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_80_OFFSET 0x00000080
+#define VEC_DAC_80_BITS   0x3fff3fff
+#define VEC_DAC_80_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_80_U14_DE_BGN
+// Description : Beginning of active data enable within each visible line
+#define VEC_DAC_80_U14_DE_BGN_RESET  0x0000
+#define VEC_DAC_80_U14_DE_BGN_BITS   0x3fff0000
+#define VEC_DAC_80_U14_DE_BGN_MSB    29
+#define VEC_DAC_80_U14_DE_BGN_LSB    16
+#define VEC_DAC_80_U14_DE_BGN_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_80_U14_DE_END
+// Description : End of active data enable within each visible line
+#define VEC_DAC_80_U14_DE_END_RESET  0x0000
+#define VEC_DAC_80_U14_DE_END_BITS   0x00003fff
+#define VEC_DAC_80_U14_DE_END_MSB    13
+#define VEC_DAC_80_U14_DE_END_LSB    0
+#define VEC_DAC_80_U14_DE_END_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_84
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_84_OFFSET 0x00000084
+#define VEC_DAC_84_BITS   0x1fff1fff
+#define VEC_DAC_84_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_84_U13_ACTIVE_RISE
+// Description : Horizontal blanking interval
+#define VEC_DAC_84_U13_ACTIVE_RISE_RESET  0x0000
+#define VEC_DAC_84_U13_ACTIVE_RISE_BITS   0x1fff0000
+#define VEC_DAC_84_U13_ACTIVE_RISE_MSB    28
+#define VEC_DAC_84_U13_ACTIVE_RISE_LSB    16
+#define VEC_DAC_84_U13_ACTIVE_RISE_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_84_U13_ACTIVE_FALL
+// Description : Horizontal blanking interval
+#define VEC_DAC_84_U13_ACTIVE_FALL_RESET  0x0000
+#define VEC_DAC_84_U13_ACTIVE_FALL_BITS   0x00001fff
+#define VEC_DAC_84_U13_ACTIVE_FALL_MSB    12
+#define VEC_DAC_84_U13_ACTIVE_FALL_LSB    0
+#define VEC_DAC_84_U13_ACTIVE_FALL_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_88
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_88_OFFSET 0x00000088
+#define VEC_DAC_88_BITS   0x1fff1fff
+#define VEC_DAC_88_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_88_U13_HALF_LINE_PERIOD
+// Description : Ratio of DAC clock to horizontal line rate, halved
+#define VEC_DAC_88_U13_HALF_LINE_PERIOD_RESET  0x0000
+#define VEC_DAC_88_U13_HALF_LINE_PERIOD_BITS   0x1fff0000
+#define VEC_DAC_88_U13_HALF_LINE_PERIOD_MSB    28
+#define VEC_DAC_88_U13_HALF_LINE_PERIOD_LSB    16
+#define VEC_DAC_88_U13_HALF_LINE_PERIOD_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_88_U13_HORZ_SYNC
+// Description : Width of horizontal sync pulses
+#define VEC_DAC_88_U13_HORZ_SYNC_RESET  0x0000
+#define VEC_DAC_88_U13_HORZ_SYNC_BITS   0x00001fff
+#define VEC_DAC_88_U13_HORZ_SYNC_MSB    12
+#define VEC_DAC_88_U13_HORZ_SYNC_LSB    0
+#define VEC_DAC_88_U13_HORZ_SYNC_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_8C
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_8C_OFFSET 0x0000008c
+#define VEC_DAC_8C_BITS   0x1fff1fff
+#define VEC_DAC_8C_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_8C_U13_BURST_RISE
+// Description : Start of raised-cosine colour burst envelope
+#define VEC_DAC_8C_U13_BURST_RISE_RESET  0x0000
+#define VEC_DAC_8C_U13_BURST_RISE_BITS   0x1fff0000
+#define VEC_DAC_8C_U13_BURST_RISE_MSB    28
+#define VEC_DAC_8C_U13_BURST_RISE_LSB    16
+#define VEC_DAC_8C_U13_BURST_RISE_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_8C_U13_BURST_FALL
+// Description : End of raised-cosine colour burst envelope
+#define VEC_DAC_8C_U13_BURST_FALL_RESET  0x0000
+#define VEC_DAC_8C_U13_BURST_FALL_BITS   0x00001fff
+#define VEC_DAC_8C_U13_BURST_FALL_MSB    12
+#define VEC_DAC_8C_U13_BURST_FALL_LSB    0
+#define VEC_DAC_8C_U13_BURST_FALL_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_90
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_90_OFFSET 0x00000090
+#define VEC_DAC_90_BITS   0x1fff3fff
+#define VEC_DAC_90_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_90_U13_VERT_EQ
+// Description : Width of vertical equalisation pulses (= half line minus
+//               serration)
+#define VEC_DAC_90_U13_VERT_EQ_RESET  0x0000
+#define VEC_DAC_90_U13_VERT_EQ_BITS   0x1fff0000
+#define VEC_DAC_90_U13_VERT_EQ_MSB    28
+#define VEC_DAC_90_U13_VERT_EQ_LSB    16
+#define VEC_DAC_90_U13_VERT_EQ_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_90_U14_VERT_SYNC
+// Description : Width of vertical sync pulses
+#define VEC_DAC_90_U14_VERT_SYNC_RESET  0x0000
+#define VEC_DAC_90_U14_VERT_SYNC_BITS   0x00003fff
+#define VEC_DAC_90_U14_VERT_SYNC_MSB    13
+#define VEC_DAC_90_U14_VERT_SYNC_LSB    0
+#define VEC_DAC_90_U14_VERT_SYNC_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_94
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_94_OFFSET 0x00000094
+#define VEC_DAC_94_BITS   0x03ff03ff
+#define VEC_DAC_94_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_94_U10_PRE_EQ_BGN
+// Description : Half-lines, inclusive, relative to field datum, where vertical
+//               pre-equalisation pulses start
+#define VEC_DAC_94_U10_PRE_EQ_BGN_RESET  0x000
+#define VEC_DAC_94_U10_PRE_EQ_BGN_BITS   0x03ff0000
+#define VEC_DAC_94_U10_PRE_EQ_BGN_MSB    25
+#define VEC_DAC_94_U10_PRE_EQ_BGN_LSB    16
+#define VEC_DAC_94_U10_PRE_EQ_BGN_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_94_U10_PRE_EQ_END
+// Description : Half-lines, inclusive, relative to field datum, where vertical
+//               pre-equalisation pulses end
+#define VEC_DAC_94_U10_PRE_EQ_END_RESET  0x000
+#define VEC_DAC_94_U10_PRE_EQ_END_BITS   0x000003ff
+#define VEC_DAC_94_U10_PRE_EQ_END_MSB    9
+#define VEC_DAC_94_U10_PRE_EQ_END_LSB    0
+#define VEC_DAC_94_U10_PRE_EQ_END_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_98
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_98_OFFSET 0x00000098
+#define VEC_DAC_98_BITS   0x03ff03ff
+#define VEC_DAC_98_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_98_U10_FIELD_SYNC_BGN
+// Description : Half-lines containing vertical sync pulses (inclusive)
+#define VEC_DAC_98_U10_FIELD_SYNC_BGN_RESET  0x000
+#define VEC_DAC_98_U10_FIELD_SYNC_BGN_BITS   0x03ff0000
+#define VEC_DAC_98_U10_FIELD_SYNC_BGN_MSB    25
+#define VEC_DAC_98_U10_FIELD_SYNC_BGN_LSB    16
+#define VEC_DAC_98_U10_FIELD_SYNC_BGN_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_98_U10_FIELD_SYNC_END
+// Description : Half-lines containing vertical sync pulses (inclusive)
+#define VEC_DAC_98_U10_FIELD_SYNC_END_RESET  0x000
+#define VEC_DAC_98_U10_FIELD_SYNC_END_BITS   0x000003ff
+#define VEC_DAC_98_U10_FIELD_SYNC_END_MSB    9
+#define VEC_DAC_98_U10_FIELD_SYNC_END_LSB    0
+#define VEC_DAC_98_U10_FIELD_SYNC_END_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_9C
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_9C_OFFSET 0x0000009c
+#define VEC_DAC_9C_BITS   0x03ff03ff
+#define VEC_DAC_9C_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_9C_U10_POST_EQ_BGN
+// Description : Half-lines containing vertical post-equalisation pulses
+#define VEC_DAC_9C_U10_POST_EQ_BGN_RESET  0x000
+#define VEC_DAC_9C_U10_POST_EQ_BGN_BITS   0x03ff0000
+#define VEC_DAC_9C_U10_POST_EQ_BGN_MSB    25
+#define VEC_DAC_9C_U10_POST_EQ_BGN_LSB    16
+#define VEC_DAC_9C_U10_POST_EQ_BGN_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_9C_U10_POST_EQ_END
+// Description : Half-lines containing vertical post-equalisation pulses
+#define VEC_DAC_9C_U10_POST_EQ_END_RESET  0x000
+#define VEC_DAC_9C_U10_POST_EQ_END_BITS   0x000003ff
+#define VEC_DAC_9C_U10_POST_EQ_END_MSB    9
+#define VEC_DAC_9C_U10_POST_EQ_END_LSB    0
+#define VEC_DAC_9C_U10_POST_EQ_END_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_A0
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_A0_OFFSET 0x000000a0
+#define VEC_DAC_A0_BITS   0x03ff03ff
+#define VEC_DAC_A0_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_A0_U10_FLD1_BURST_BGN
+// Description : First and last full frame lines (1-based numbering) within the
+//               PAL/NTSC four field sequence which require a colour burst
+#define VEC_DAC_A0_U10_FLD1_BURST_BGN_RESET  0x000
+#define VEC_DAC_A0_U10_FLD1_BURST_BGN_BITS   0x03ff0000
+#define VEC_DAC_A0_U10_FLD1_BURST_BGN_MSB    25
+#define VEC_DAC_A0_U10_FLD1_BURST_BGN_LSB    16
+#define VEC_DAC_A0_U10_FLD1_BURST_BGN_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_A0_U10_FLD1_BURST_END
+// Description : First and last full frame lines (1-based numbering) within the
+//               PAL/NTSC four field sequence which require a colour burst
+#define VEC_DAC_A0_U10_FLD1_BURST_END_RESET  0x000
+#define VEC_DAC_A0_U10_FLD1_BURST_END_BITS   0x000003ff
+#define VEC_DAC_A0_U10_FLD1_BURST_END_MSB    9
+#define VEC_DAC_A0_U10_FLD1_BURST_END_LSB    0
+#define VEC_DAC_A0_U10_FLD1_BURST_END_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_A4
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_A4_OFFSET 0x000000a4
+#define VEC_DAC_A4_BITS   0x03ff03ff
+#define VEC_DAC_A4_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_A4_U10_FLD2_BURST_BGN
+// Description : First and last full frame lines (1-based numbering) within the
+//               PAL/NTSC four field sequence which require a colour burst
+#define VEC_DAC_A4_U10_FLD2_BURST_BGN_RESET  0x000
+#define VEC_DAC_A4_U10_FLD2_BURST_BGN_BITS   0x03ff0000
+#define VEC_DAC_A4_U10_FLD2_BURST_BGN_MSB    25
+#define VEC_DAC_A4_U10_FLD2_BURST_BGN_LSB    16
+#define VEC_DAC_A4_U10_FLD2_BURST_BGN_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_A4_U10_FLD2_BURST_END
+// Description : First and last full frame lines (1-based numbering) within the
+//               PAL/NTSC four field sequence which require a colour burst
+#define VEC_DAC_A4_U10_FLD2_BURST_END_RESET  0x000
+#define VEC_DAC_A4_U10_FLD2_BURST_END_BITS   0x000003ff
+#define VEC_DAC_A4_U10_FLD2_BURST_END_MSB    9
+#define VEC_DAC_A4_U10_FLD2_BURST_END_LSB    0
+#define VEC_DAC_A4_U10_FLD2_BURST_END_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_A8
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_A8_OFFSET 0x000000a8
+#define VEC_DAC_A8_BITS   0x03ff03ff
+#define VEC_DAC_A8_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_A8_U10_FLD3_BURST_BGN
+// Description : First and last full frame lines (1-based numbering) within the
+//               PAL/NTSC four field sequence which require a colour burst
+#define VEC_DAC_A8_U10_FLD3_BURST_BGN_RESET  0x000
+#define VEC_DAC_A8_U10_FLD3_BURST_BGN_BITS   0x03ff0000
+#define VEC_DAC_A8_U10_FLD3_BURST_BGN_MSB    25
+#define VEC_DAC_A8_U10_FLD3_BURST_BGN_LSB    16
+#define VEC_DAC_A8_U10_FLD3_BURST_BGN_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_A8_U10_FLD3_BURST_END
+// Description : First and last full frame lines (1-based numbering) within the
+//               PAL/NTSC four field sequence which require a colour burst
+#define VEC_DAC_A8_U10_FLD3_BURST_END_RESET  0x000
+#define VEC_DAC_A8_U10_FLD3_BURST_END_BITS   0x000003ff
+#define VEC_DAC_A8_U10_FLD3_BURST_END_MSB    9
+#define VEC_DAC_A8_U10_FLD3_BURST_END_LSB    0
+#define VEC_DAC_A8_U10_FLD3_BURST_END_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_AC
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_AC_OFFSET 0x000000ac
+#define VEC_DAC_AC_BITS   0x03ff03ff
+#define VEC_DAC_AC_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_AC_U10_FLD4_BURST_BGN
+// Description : First and last full frame lines (1-based numbering) within the
+//               PAL/NTSC four field sequence which require a colour burst
+#define VEC_DAC_AC_U10_FLD4_BURST_BGN_RESET  0x000
+#define VEC_DAC_AC_U10_FLD4_BURST_BGN_BITS   0x03ff0000
+#define VEC_DAC_AC_U10_FLD4_BURST_BGN_MSB    25
+#define VEC_DAC_AC_U10_FLD4_BURST_BGN_LSB    16
+#define VEC_DAC_AC_U10_FLD4_BURST_BGN_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_AC_U10_FLD4_BURST_END
+// Description : First and last full frame lines (1-based numbering) within the
+//               PAL/NTSC four field sequence which require a colour burst
+#define VEC_DAC_AC_U10_FLD4_BURST_END_RESET  0x000
+#define VEC_DAC_AC_U10_FLD4_BURST_END_BITS   0x000003ff
+#define VEC_DAC_AC_U10_FLD4_BURST_END_MSB    9
+#define VEC_DAC_AC_U10_FLD4_BURST_END_LSB    0
+#define VEC_DAC_AC_U10_FLD4_BURST_END_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_B0
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_B0_OFFSET 0x000000b0
+#define VEC_DAC_B0_BITS   0x03ff03ff
+#define VEC_DAC_B0_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_B0_U10_FLD24_FULL_LINE_BGN
+// Description : First and last full visible lines (1-based numbering) in the
+//               PAL/NTSC four field sequence
+#define VEC_DAC_B0_U10_FLD24_FULL_LINE_BGN_RESET  0x000
+#define VEC_DAC_B0_U10_FLD24_FULL_LINE_BGN_BITS   0x03ff0000
+#define VEC_DAC_B0_U10_FLD24_FULL_LINE_BGN_MSB    25
+#define VEC_DAC_B0_U10_FLD24_FULL_LINE_BGN_LSB    16
+#define VEC_DAC_B0_U10_FLD24_FULL_LINE_BGN_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_B0_U10_FLD24_FULL_LINE_END
+// Description : First and last full visible lines (1-based numbering) in the
+//               PAL/NTSC four field sequence
+#define VEC_DAC_B0_U10_FLD24_FULL_LINE_END_RESET  0x000
+#define VEC_DAC_B0_U10_FLD24_FULL_LINE_END_BITS   0x000003ff
+#define VEC_DAC_B0_U10_FLD24_FULL_LINE_END_MSB    9
+#define VEC_DAC_B0_U10_FLD24_FULL_LINE_END_LSB    0
+#define VEC_DAC_B0_U10_FLD24_FULL_LINE_END_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_B4
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_B4_OFFSET 0x000000b4
+#define VEC_DAC_B4_BITS   0x03ff03ff
+#define VEC_DAC_B4_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_B4_U10_FLD13_FULL_LINE_BGN
+// Description : First and last full visible lines (1-based numbering) in the
+//               PAL/NTSC four field sequence
+#define VEC_DAC_B4_U10_FLD13_FULL_LINE_BGN_RESET  0x000
+#define VEC_DAC_B4_U10_FLD13_FULL_LINE_BGN_BITS   0x03ff0000
+#define VEC_DAC_B4_U10_FLD13_FULL_LINE_BGN_MSB    25
+#define VEC_DAC_B4_U10_FLD13_FULL_LINE_BGN_LSB    16
+#define VEC_DAC_B4_U10_FLD13_FULL_LINE_BGN_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_B4_U10_FLD13_FULL_LINE_END
+// Description : First and last full visible lines (1-based numbering) in the
+//               PAL/NTSC four field sequence
+#define VEC_DAC_B4_U10_FLD13_FULL_LINE_END_RESET  0x000
+#define VEC_DAC_B4_U10_FLD13_FULL_LINE_END_BITS   0x000003ff
+#define VEC_DAC_B4_U10_FLD13_FULL_LINE_END_MSB    9
+#define VEC_DAC_B4_U10_FLD13_FULL_LINE_END_LSB    0
+#define VEC_DAC_B4_U10_FLD13_FULL_LINE_END_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_B8
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_B8_OFFSET 0x000000b8
+#define VEC_DAC_B8_BITS   0x03ff03ff
+#define VEC_DAC_B8_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_B8_U10_BOT_HALF_LINE
+// Description : Top and bottom visible half-lines in 1-based standard full
+//               frame numbering, for interlaced modes. Set to zero to disable.
+#define VEC_DAC_B8_U10_BOT_HALF_LINE_RESET  0x000
+#define VEC_DAC_B8_U10_BOT_HALF_LINE_BITS   0x03ff0000
+#define VEC_DAC_B8_U10_BOT_HALF_LINE_MSB    25
+#define VEC_DAC_B8_U10_BOT_HALF_LINE_LSB    16
+#define VEC_DAC_B8_U10_BOT_HALF_LINE_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_B8_U10_TOP_HALF_LINE
+// Description : Top and bottom visible half-lines in 1-based standard full
+//               frame numbering, for interlaced modes. Set to zero to disable.
+#define VEC_DAC_B8_U10_TOP_HALF_LINE_RESET  0x000
+#define VEC_DAC_B8_U10_TOP_HALF_LINE_BITS   0x000003ff
+#define VEC_DAC_B8_U10_TOP_HALF_LINE_MSB    9
+#define VEC_DAC_B8_U10_TOP_HALF_LINE_LSB    0
+#define VEC_DAC_B8_U10_TOP_HALF_LINE_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_BC
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_BC_OFFSET 0x000000bc
+#define VEC_DAC_BC_BITS   0x07ff07ff
+#define VEC_DAC_BC_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_BC_S11_PEDESTAL
+// Description : NTSC pedestal. For 7.5 IRE, this field is 1024 * 7.5/100. For
+//               PAL, or Japanese NTSC, this field should be zero.
+#define VEC_DAC_BC_S11_PEDESTAL_RESET  0x000
+#define VEC_DAC_BC_S11_PEDESTAL_BITS   0x07ff0000
+#define VEC_DAC_BC_S11_PEDESTAL_MSB    26
+#define VEC_DAC_BC_S11_PEDESTAL_LSB    16
+#define VEC_DAC_BC_S11_PEDESTAL_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_BC_U11_HALF_LINES_PER_FIELD
+// Description : Mode = 625 PAL, Lines per field = 312.5,
+//               u11_half_lines_per_field = 1+2*312 Mode = 525 NTSC, Lines per
+//               field = 262.5, u11_half_lines_per_field = 1+2*262
+#define VEC_DAC_BC_U11_HALF_LINES_PER_FIELD_RESET  0x000
+#define VEC_DAC_BC_U11_HALF_LINES_PER_FIELD_BITS   0x000007ff
+#define VEC_DAC_BC_U11_HALF_LINES_PER_FIELD_MSB    10
+#define VEC_DAC_BC_U11_HALF_LINES_PER_FIELD_LSB    0
+#define VEC_DAC_BC_U11_HALF_LINES_PER_FIELD_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_C0
+// JTAG access : synchronous
+// Description : Synopsis DesignWare control
+#define VEC_DAC_C0_OFFSET 0x000000c0
+#define VEC_DAC_C0_BITS   0x000fffff
+#define VEC_DAC_C0_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_C0_DWC_CABLE_ENCTR3
+// Description : Synopsis test input
+#define VEC_DAC_C0_DWC_CABLE_ENCTR3_RESET  0x0
+#define VEC_DAC_C0_DWC_CABLE_ENCTR3_BITS   0x00080000
+#define VEC_DAC_C0_DWC_CABLE_ENCTR3_MSB    19
+#define VEC_DAC_C0_DWC_CABLE_ENCTR3_LSB    19
+#define VEC_DAC_C0_DWC_CABLE_ENCTR3_ACCESS "RO"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_C0_DWC_CABLE_CABLEOUT
+// Description : cable detect state
+#define VEC_DAC_C0_DWC_CABLE_CABLEOUT_RESET  0x0
+#define VEC_DAC_C0_DWC_CABLE_CABLEOUT_BITS   0x00070000
+#define VEC_DAC_C0_DWC_CABLE_CABLEOUT_MSB    18
+#define VEC_DAC_C0_DWC_CABLE_CABLEOUT_LSB    16
+#define VEC_DAC_C0_DWC_CABLE_CABLEOUT_ACCESS "RO"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_C0_DWC_MUX_2
+// Description : Select DAC channel 2 output
+#define VEC_DAC_C0_DWC_MUX_2_RESET  0x0
+#define VEC_DAC_C0_DWC_MUX_2_BITS   0x0000c000
+#define VEC_DAC_C0_DWC_MUX_2_MSB    15
+#define VEC_DAC_C0_DWC_MUX_2_LSB    14
+#define VEC_DAC_C0_DWC_MUX_2_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_C0_DWC_MUX_1
+// Description : Select DAC channel 1 output
+#define VEC_DAC_C0_DWC_MUX_1_RESET  0x0
+#define VEC_DAC_C0_DWC_MUX_1_BITS   0x00003000
+#define VEC_DAC_C0_DWC_MUX_1_MSB    13
+#define VEC_DAC_C0_DWC_MUX_1_LSB    12
+#define VEC_DAC_C0_DWC_MUX_1_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_C0_DWC_MUX_0
+// Description : Select DAC channel 0 output
+#define VEC_DAC_C0_DWC_MUX_0_RESET  0x0
+#define VEC_DAC_C0_DWC_MUX_0_BITS   0x00000c00
+#define VEC_DAC_C0_DWC_MUX_0_MSB    11
+#define VEC_DAC_C0_DWC_MUX_0_LSB    10
+#define VEC_DAC_C0_DWC_MUX_0_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_C0_DWC_TEST
+// Description : Fixed DAC command word
+#define VEC_DAC_C0_DWC_TEST_RESET  0x000
+#define VEC_DAC_C0_DWC_TEST_BITS   0x000003ff
+#define VEC_DAC_C0_DWC_TEST_MSB    9
+#define VEC_DAC_C0_DWC_TEST_LSB    0
+#define VEC_DAC_C0_DWC_TEST_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_C4
+// JTAG access : synchronous
+// Description : Synopsis DAC control
+#define VEC_DAC_C4_OFFSET 0x000000c4
+#define VEC_DAC_C4_BITS   0x1fffffff
+#define VEC_DAC_C4_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_C4_ENCTR
+// Description : Always write3'b000
+#define VEC_DAC_C4_ENCTR_RESET  0x0
+#define VEC_DAC_C4_ENCTR_BITS   0x1c000000
+#define VEC_DAC_C4_ENCTR_MSB    28
+#define VEC_DAC_C4_ENCTR_LSB    26
+#define VEC_DAC_C4_ENCTR_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_C4_ENSC
+// Description : Enable cable detect - write 3'b000
+#define VEC_DAC_C4_ENSC_RESET  0x0
+#define VEC_DAC_C4_ENSC_BITS   0x03800000
+#define VEC_DAC_C4_ENSC_MSB    25
+#define VEC_DAC_C4_ENSC_LSB    23
+#define VEC_DAC_C4_ENSC_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_C4_ENDAC
+// Description : Enable DAC channel
+#define VEC_DAC_C4_ENDAC_RESET  0x0
+#define VEC_DAC_C4_ENDAC_BITS   0x00700000
+#define VEC_DAC_C4_ENDAC_MSB    22
+#define VEC_DAC_C4_ENDAC_LSB    20
+#define VEC_DAC_C4_ENDAC_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_C4_ENVBG
+// Description : Enable internal bandgap reference - write '1'
+#define VEC_DAC_C4_ENVBG_RESET  0x0
+#define VEC_DAC_C4_ENVBG_BITS   0x00080000
+#define VEC_DAC_C4_ENVBG_MSB    19
+#define VEC_DAC_C4_ENVBG_LSB    19
+#define VEC_DAC_C4_ENVBG_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_C4_ENEXTREF
+// Description : Enable external reference - write '0'
+#define VEC_DAC_C4_ENEXTREF_RESET  0x0
+#define VEC_DAC_C4_ENEXTREF_BITS   0x00040000
+#define VEC_DAC_C4_ENEXTREF_MSB    18
+#define VEC_DAC_C4_ENEXTREF_LSB    18
+#define VEC_DAC_C4_ENEXTREF_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_C4_DAC2GC
+// Description : DAC channel 2 gain control - write 6'd63
+#define VEC_DAC_C4_DAC2GC_RESET  0x00
+#define VEC_DAC_C4_DAC2GC_BITS   0x0003f000
+#define VEC_DAC_C4_DAC2GC_MSB    17
+#define VEC_DAC_C4_DAC2GC_LSB    12
+#define VEC_DAC_C4_DAC2GC_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_C4_DAC1GC
+// Description : DAC channel 1 gain control - write 6'd63
+#define VEC_DAC_C4_DAC1GC_RESET  0x00
+#define VEC_DAC_C4_DAC1GC_BITS   0x00000fc0
+#define VEC_DAC_C4_DAC1GC_MSB    11
+#define VEC_DAC_C4_DAC1GC_LSB    6
+#define VEC_DAC_C4_DAC1GC_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_C4_DAC0GC
+// Description : DAC channel 0 gain control - write 6'd63
+#define VEC_DAC_C4_DAC0GC_RESET  0x00
+#define VEC_DAC_C4_DAC0GC_BITS   0x0000003f
+#define VEC_DAC_C4_DAC0GC_MSB    5
+#define VEC_DAC_C4_DAC0GC_LSB    0
+#define VEC_DAC_C4_DAC0GC_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_C8
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_C8_OFFSET 0x000000c8
+#define VEC_DAC_C8_BITS   0xffffffff
+#define VEC_DAC_C8_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_C8_U16_SCALE_SYNC
+// Description : Scaling applied prior to final summation to form the DAC
+//               command word(s)
+#define VEC_DAC_C8_U16_SCALE_SYNC_RESET  0x0000
+#define VEC_DAC_C8_U16_SCALE_SYNC_BITS   0xffff0000
+#define VEC_DAC_C8_U16_SCALE_SYNC_MSB    31
+#define VEC_DAC_C8_U16_SCALE_SYNC_LSB    16
+#define VEC_DAC_C8_U16_SCALE_SYNC_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_C8_U16_SCALE_LUMA
+// Description : Scaling applied prior to final summation to form the DAC
+//               command word(s)
+#define VEC_DAC_C8_U16_SCALE_LUMA_RESET  0x0000
+#define VEC_DAC_C8_U16_SCALE_LUMA_BITS   0x0000ffff
+#define VEC_DAC_C8_U16_SCALE_LUMA_MSB    15
+#define VEC_DAC_C8_U16_SCALE_LUMA_LSB    0
+#define VEC_DAC_C8_U16_SCALE_LUMA_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_CC
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_CC_OFFSET 0x000000cc
+#define VEC_DAC_CC_BITS   0xffffffff
+#define VEC_DAC_CC_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_CC_S16_SCALE_BURST
+// Description : Scaling applied prior to final summation to form the DAC
+//               command word(s)
+#define VEC_DAC_CC_S16_SCALE_BURST_RESET  0x0000
+#define VEC_DAC_CC_S16_SCALE_BURST_BITS   0xffff0000
+#define VEC_DAC_CC_S16_SCALE_BURST_MSB    31
+#define VEC_DAC_CC_S16_SCALE_BURST_LSB    16
+#define VEC_DAC_CC_S16_SCALE_BURST_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_CC_S16_SCALE_CHROMA
+// Description : Scaling applied prior to final summation to form the DAC
+//               command word(s)
+#define VEC_DAC_CC_S16_SCALE_CHROMA_RESET  0x0000
+#define VEC_DAC_CC_S16_SCALE_CHROMA_BITS   0x0000ffff
+#define VEC_DAC_CC_S16_SCALE_CHROMA_MSB    15
+#define VEC_DAC_CC_S16_SCALE_CHROMA_LSB    0
+#define VEC_DAC_CC_S16_SCALE_CHROMA_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_D0
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_D0_OFFSET 0x000000d0
+#define VEC_DAC_D0_BITS   0xffffffff
+#define VEC_DAC_D0_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_D0_S16_OFFSET_LUMA
+// Description : These offsets are applied to the chroma and luma channels
+//               before the final MUX
+#define VEC_DAC_D0_S16_OFFSET_LUMA_RESET  0x0000
+#define VEC_DAC_D0_S16_OFFSET_LUMA_BITS   0xffff0000
+#define VEC_DAC_D0_S16_OFFSET_LUMA_MSB    31
+#define VEC_DAC_D0_S16_OFFSET_LUMA_LSB    16
+#define VEC_DAC_D0_S16_OFFSET_LUMA_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_D0_S16_OFFSET_CHRO
+// Description : These offsets are applied to the chroma and luma channels
+//               before the final MUX
+#define VEC_DAC_D0_S16_OFFSET_CHRO_RESET  0x0000
+#define VEC_DAC_D0_S16_OFFSET_CHRO_BITS   0x0000ffff
+#define VEC_DAC_D0_S16_OFFSET_CHRO_MSB    15
+#define VEC_DAC_D0_S16_OFFSET_CHRO_LSB    0
+#define VEC_DAC_D0_S16_OFFSET_CHRO_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_D4
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_D4_OFFSET 0x000000d4
+#define VEC_DAC_D4_BITS   0xffffffff
+#define VEC_DAC_D4_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_D4_NCO_FREQ
+// Description : This 64-bit frequency command is applied to the phase
+//               accumulator of the NCO (numerically controlled oscillator)
+//               which generates the colour sub-carrier. This value is computed
+//               as ratio of sub-carrier frequency to DAC clock multiplied by
+//               2^64.
+#define VEC_DAC_D4_NCO_FREQ_RESET  0x00000000
+#define VEC_DAC_D4_NCO_FREQ_BITS   0xffffffff
+#define VEC_DAC_D4_NCO_FREQ_MSB    31
+#define VEC_DAC_D4_NCO_FREQ_LSB    0
+#define VEC_DAC_D4_NCO_FREQ_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_D8
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_D8_OFFSET 0x000000d8
+#define VEC_DAC_D8_BITS   0xffffffff
+#define VEC_DAC_D8_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_D8_NCO_FREQ
+// Description : This 64-bit frequency command is applied to the phase
+//               accumulator of the NCO (numerically controlled oscillator)
+//               which generates the colour sub-carrier. This value is computed
+//               as ratio of sub-carrier frequency to DAC clock multiplied by
+//               2^64.
+#define VEC_DAC_D8_NCO_FREQ_RESET  0x00000000
+#define VEC_DAC_D8_NCO_FREQ_BITS   0xffffffff
+#define VEC_DAC_D8_NCO_FREQ_MSB    31
+#define VEC_DAC_D8_NCO_FREQ_LSB    0
+#define VEC_DAC_D8_NCO_FREQ_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_DC
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_DC_OFFSET 0x000000dc
+#define VEC_DAC_DC_BITS   0xffffffff
+#define VEC_DAC_DC_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_DC_FIR_COEFF_CHROMA_0_6
+// Description : FIR filter coefficients
+#define VEC_DAC_DC_FIR_COEFF_CHROMA_0_6_RESET  0x0000
+#define VEC_DAC_DC_FIR_COEFF_CHROMA_0_6_BITS   0xffff0000
+#define VEC_DAC_DC_FIR_COEFF_CHROMA_0_6_MSB    31
+#define VEC_DAC_DC_FIR_COEFF_CHROMA_0_6_LSB    16
+#define VEC_DAC_DC_FIR_COEFF_CHROMA_0_6_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_DC_FIR_COEFF_LUMA_0_6
+// Description : FIR filter coefficients
+#define VEC_DAC_DC_FIR_COEFF_LUMA_0_6_RESET  0x0000
+#define VEC_DAC_DC_FIR_COEFF_LUMA_0_6_BITS   0x0000ffff
+#define VEC_DAC_DC_FIR_COEFF_LUMA_0_6_MSB    15
+#define VEC_DAC_DC_FIR_COEFF_LUMA_0_6_LSB    0
+#define VEC_DAC_DC_FIR_COEFF_LUMA_0_6_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_E0
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_E0_OFFSET 0x000000e0
+#define VEC_DAC_E0_BITS   0xffffffff
+#define VEC_DAC_E0_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_E0_FIR_COEFF_CHROMA_1_5
+// Description : FIR filter coefficients
+#define VEC_DAC_E0_FIR_COEFF_CHROMA_1_5_RESET  0x0000
+#define VEC_DAC_E0_FIR_COEFF_CHROMA_1_5_BITS   0xffff0000
+#define VEC_DAC_E0_FIR_COEFF_CHROMA_1_5_MSB    31
+#define VEC_DAC_E0_FIR_COEFF_CHROMA_1_5_LSB    16
+#define VEC_DAC_E0_FIR_COEFF_CHROMA_1_5_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_E0_FIR_COEFF_LUMA_1_5
+// Description : FIR filter coefficients
+#define VEC_DAC_E0_FIR_COEFF_LUMA_1_5_RESET  0x0000
+#define VEC_DAC_E0_FIR_COEFF_LUMA_1_5_BITS   0x0000ffff
+#define VEC_DAC_E0_FIR_COEFF_LUMA_1_5_MSB    15
+#define VEC_DAC_E0_FIR_COEFF_LUMA_1_5_LSB    0
+#define VEC_DAC_E0_FIR_COEFF_LUMA_1_5_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_E4
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_E4_OFFSET 0x000000e4
+#define VEC_DAC_E4_BITS   0xffffffff
+#define VEC_DAC_E4_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_E4_FIR_COEFF_CHROMA_2_4
+// Description : FIR filter coefficients
+#define VEC_DAC_E4_FIR_COEFF_CHROMA_2_4_RESET  0x0000
+#define VEC_DAC_E4_FIR_COEFF_CHROMA_2_4_BITS   0xffff0000
+#define VEC_DAC_E4_FIR_COEFF_CHROMA_2_4_MSB    31
+#define VEC_DAC_E4_FIR_COEFF_CHROMA_2_4_LSB    16
+#define VEC_DAC_E4_FIR_COEFF_CHROMA_2_4_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_E4_FIR_COEFF_LUMA_2_4
+// Description : FIR filter coefficients
+#define VEC_DAC_E4_FIR_COEFF_LUMA_2_4_RESET  0x0000
+#define VEC_DAC_E4_FIR_COEFF_LUMA_2_4_BITS   0x0000ffff
+#define VEC_DAC_E4_FIR_COEFF_LUMA_2_4_MSB    15
+#define VEC_DAC_E4_FIR_COEFF_LUMA_2_4_LSB    0
+#define VEC_DAC_E4_FIR_COEFF_LUMA_2_4_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_E8
+// JTAG access : synchronous
+// Description : None
+#define VEC_DAC_E8_OFFSET 0x000000e8
+#define VEC_DAC_E8_BITS   0xffffffff
+#define VEC_DAC_E8_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_E8_FIR_COEFF_CHROMA_3
+// Description : FIR filter coefficients
+#define VEC_DAC_E8_FIR_COEFF_CHROMA_3_RESET  0x0000
+#define VEC_DAC_E8_FIR_COEFF_CHROMA_3_BITS   0xffff0000
+#define VEC_DAC_E8_FIR_COEFF_CHROMA_3_MSB    31
+#define VEC_DAC_E8_FIR_COEFF_CHROMA_3_LSB    16
+#define VEC_DAC_E8_FIR_COEFF_CHROMA_3_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_E8_FIR_COEFF_LUMA_3
+// Description : FIR filter coefficients
+#define VEC_DAC_E8_FIR_COEFF_LUMA_3_RESET  0x0000
+#define VEC_DAC_E8_FIR_COEFF_LUMA_3_BITS   0x0000ffff
+#define VEC_DAC_E8_FIR_COEFF_LUMA_3_MSB    15
+#define VEC_DAC_E8_FIR_COEFF_LUMA_3_LSB    0
+#define VEC_DAC_E8_FIR_COEFF_LUMA_3_ACCESS "RW"
+// =============================================================================
+// Register    : VEC_DAC_EC
+// JTAG access : synchronous
+// Description : Misc. control
+#define VEC_DAC_EC_OFFSET 0x000000ec
+#define VEC_DAC_EC_BITS   0x001fffff
+#define VEC_DAC_EC_RESET  0x00000000
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_EC_SLOW_CLOCK
+// Description : Doubles the raised-cosine rate
+#define VEC_DAC_EC_SLOW_CLOCK_RESET  0x0
+#define VEC_DAC_EC_SLOW_CLOCK_BITS   0x00100000
+#define VEC_DAC_EC_SLOW_CLOCK_MSB    20
+#define VEC_DAC_EC_SLOW_CLOCK_LSB    20
+#define VEC_DAC_EC_SLOW_CLOCK_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_EC_FIR_RMINUS1
+// Description : Select 1, 3, 5 or 7 FIR taps
+#define VEC_DAC_EC_FIR_RMINUS1_RESET  0x0
+#define VEC_DAC_EC_FIR_RMINUS1_BITS   0x000c0000
+#define VEC_DAC_EC_FIR_RMINUS1_MSB    19
+#define VEC_DAC_EC_FIR_RMINUS1_LSB    18
+#define VEC_DAC_EC_FIR_RMINUS1_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_EC_VERT_FULL_NOT_HALF
+// Description : Disable half-line pulses during VBI
+#define VEC_DAC_EC_VERT_FULL_NOT_HALF_RESET  0x0
+#define VEC_DAC_EC_VERT_FULL_NOT_HALF_BITS   0x00020000
+#define VEC_DAC_EC_VERT_FULL_NOT_HALF_MSB    17
+#define VEC_DAC_EC_VERT_FULL_NOT_HALF_LSB    17
+#define VEC_DAC_EC_VERT_FULL_NOT_HALF_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_EC_SEQ_EN
+// Description : Enable NCO reset
+#define VEC_DAC_EC_SEQ_EN_RESET  0x0
+#define VEC_DAC_EC_SEQ_EN_BITS   0x00010000
+#define VEC_DAC_EC_SEQ_EN_MSB    16
+#define VEC_DAC_EC_SEQ_EN_LSB    16
+#define VEC_DAC_EC_SEQ_EN_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_EC_U2_FLD_MASK
+// Description : Field sequence
+#define VEC_DAC_EC_U2_FLD_MASK_RESET  0x0
+#define VEC_DAC_EC_U2_FLD_MASK_BITS   0x0000c000
+#define VEC_DAC_EC_U2_FLD_MASK_MSB    15
+#define VEC_DAC_EC_U2_FLD_MASK_LSB    14
+#define VEC_DAC_EC_U2_FLD_MASK_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_EC_U4_SEQ_MASK
+// Description : NCO reset sequence
+#define VEC_DAC_EC_U4_SEQ_MASK_RESET  0x0
+#define VEC_DAC_EC_U4_SEQ_MASK_BITS   0x00003c00
+#define VEC_DAC_EC_U4_SEQ_MASK_MSB    13
+#define VEC_DAC_EC_U4_SEQ_MASK_LSB    10
+#define VEC_DAC_EC_U4_SEQ_MASK_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_EC_INTERP_RATE_MINUS1
+// Description : Interpolation rate 2<=R<=16
+#define VEC_DAC_EC_INTERP_RATE_MINUS1_RESET  0x0
+#define VEC_DAC_EC_INTERP_RATE_MINUS1_BITS   0x000003c0
+#define VEC_DAC_EC_INTERP_RATE_MINUS1_MSB    9
+#define VEC_DAC_EC_INTERP_RATE_MINUS1_LSB    6
+#define VEC_DAC_EC_INTERP_RATE_MINUS1_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_EC_INTERP_SHIFT_MINUS1
+// Description : Power-of-2 scaling after interpolation
+#define VEC_DAC_EC_INTERP_SHIFT_MINUS1_RESET  0x0
+#define VEC_DAC_EC_INTERP_SHIFT_MINUS1_BITS   0x0000003c
+#define VEC_DAC_EC_INTERP_SHIFT_MINUS1_MSB    5
+#define VEC_DAC_EC_INTERP_SHIFT_MINUS1_LSB    2
+#define VEC_DAC_EC_INTERP_SHIFT_MINUS1_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_EC_FIELDS_PER_FRAME_MINUS1
+// Description : Interlaced / progressive
+#define VEC_DAC_EC_FIELDS_PER_FRAME_MINUS1_RESET  0x0
+#define VEC_DAC_EC_FIELDS_PER_FRAME_MINUS1_BITS   0x00000002
+#define VEC_DAC_EC_FIELDS_PER_FRAME_MINUS1_MSB    1
+#define VEC_DAC_EC_FIELDS_PER_FRAME_MINUS1_LSB    1
+#define VEC_DAC_EC_FIELDS_PER_FRAME_MINUS1_ACCESS "RW"
+// -----------------------------------------------------------------------------
+// Field       : VEC_DAC_EC_PAL_EN
+// Description : Enable phase alternate line (PAL) mode
+#define VEC_DAC_EC_PAL_EN_RESET  0x0
+#define VEC_DAC_EC_PAL_EN_BITS   0x00000001
+#define VEC_DAC_EC_PAL_EN_MSB    0
+#define VEC_DAC_EC_PAL_EN_LSB    0
+#define VEC_DAC_EC_PAL_EN_ACCESS "RW"
+// =============================================================================
+#endif // VEC_REGS_DEFINED
diff --git a/drivers/gpu/drm/tests/drm_cmdline_parser_test.c b/drivers/gpu/drm/tests/drm_cmdline_parser_test.c
index 1cfcb597b088..2d29eca448f6 100644
--- a/drivers/gpu/drm/tests/drm_cmdline_parser_test.c
+++ b/drivers/gpu/drm/tests/drm_cmdline_parser_test.c
@@ -724,6 +724,29 @@ static void drm_test_cmdline_multiple_options(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED);
 }
 
+static void drm_test_cmdline_multiple_options2(struct kunit *test)
+{
+	struct drm_cmdline_mode mode = { };
+	const char *cmdline = "720x480,reflect_x,rotate=270";
+
+	KUNIT_ASSERT_TRUE(test, drm_mode_parse_command_line_for_connector(cmdline,
+									  &no_connector, &mode));
+	KUNIT_EXPECT_TRUE(test, mode.specified);
+	KUNIT_EXPECT_EQ(test, mode.xres, 720);
+	KUNIT_EXPECT_EQ(test, mode.yres, 480);
+	KUNIT_EXPECT_EQ(test, mode.rotation_reflection, (DRM_MODE_ROTATE_270 | DRM_MODE_REFLECT_X));
+
+	KUNIT_EXPECT_FALSE(test, mode.refresh_specified);
+
+	KUNIT_EXPECT_FALSE(test, mode.bpp_specified);
+
+	KUNIT_EXPECT_FALSE(test, mode.rb);
+	KUNIT_EXPECT_FALSE(test, mode.cvt);
+	KUNIT_EXPECT_FALSE(test, mode.interlace);
+	KUNIT_EXPECT_FALSE(test, mode.margins);
+	KUNIT_EXPECT_EQ(test, mode.force, DRM_FORCE_UNSPECIFIED);
+}
+
 static void drm_test_cmdline_bpp_extra_and_option(struct kunit *test)
 {
 	struct drm_cmdline_mode mode = { };
@@ -1057,6 +1080,7 @@ static struct kunit_case drm_cmdline_parser_tests[] = {
 	KUNIT_CASE(drm_test_cmdline_vmirror),
 	KUNIT_CASE(drm_test_cmdline_margin_options),
 	KUNIT_CASE(drm_test_cmdline_multiple_options),
+	KUNIT_CASE(drm_test_cmdline_multiple_options2),
 	KUNIT_CASE(drm_test_cmdline_bpp_extra_and_option),
 	KUNIT_CASE(drm_test_cmdline_extra_and_option),
 	KUNIT_CASE(drm_test_cmdline_freestanding_options),
diff --git a/drivers/gpu/drm/tiny/ili9486.c b/drivers/gpu/drm/tiny/ili9486.c
index 70d366260041..249b3ad9db95 100644
--- a/drivers/gpu/drm/tiny/ili9486.c
+++ b/drivers/gpu/drm/tiny/ili9486.c
@@ -188,7 +188,6 @@ static const struct of_device_id ili9486_of_match[] = {
 MODULE_DEVICE_TABLE(of, ili9486_of_match);
 
 static const struct spi_device_id ili9486_id[] = {
-	{ "ili9486", 0 },
 	{ "rpi-lcd-35", 0 },
 	{ "piscreen", 0 },
 	{ }
diff --git a/drivers/gpu/drm/v3d/Makefile b/drivers/gpu/drm/v3d/Makefile
index b7d673f1153b..fcf710926057 100644
--- a/drivers/gpu/drm/v3d/Makefile
+++ b/drivers/gpu/drm/v3d/Makefile
@@ -13,7 +13,8 @@ v3d-y := \
 	v3d_trace_points.o \
 	v3d_sched.o \
 	v3d_sysfs.o \
-	v3d_submit.o
+	v3d_submit.o \
+	v3d_gemfs.o
 
 v3d-$(CONFIG_DEBUG_FS) += v3d_debugfs.o
 
diff --git a/drivers/gpu/drm/v3d/v3d_bo.c b/drivers/gpu/drm/v3d/v3d_bo.c
index ebe52bef4ffb..bb7815599435 100644
--- a/drivers/gpu/drm/v3d/v3d_bo.c
+++ b/drivers/gpu/drm/v3d/v3d_bo.c
@@ -13,10 +13,6 @@
  * Display engines requiring physically contiguous allocations should
  * look into Mesa's "renderonly" support (as used by the Mesa pl111
  * driver) for an example of how to integrate with V3D.
- *
- * Long term, we should support evicting pages from the MMU when under
- * memory pressure (thus the v3d_bo_get_pages() refcounting), but
- * that's not a high priority since our systems tend to not have swap.
  */
 
 #include <linux/dma-buf.h>
@@ -107,6 +103,7 @@ v3d_bo_create_finish(struct drm_gem_object *obj)
 	struct v3d_dev *v3d = to_v3d_dev(obj->dev);
 	struct v3d_bo *bo = to_v3d_bo(obj);
 	struct sg_table *sgt;
+	u64 align;
 	int ret;
 
 	/* So far we pin the BO in the MMU for its lifetime, so use
@@ -116,6 +113,15 @@ v3d_bo_create_finish(struct drm_gem_object *obj)
 	if (IS_ERR(sgt))
 		return PTR_ERR(sgt);
 
+	if (!v3d->gemfs)
+		align = SZ_4K;
+	else if (obj->size >= SZ_1M)
+		align = SZ_1M;
+	else if (obj->size >= SZ_64K)
+		align = SZ_64K;
+	else
+		align = SZ_4K;
+
 	spin_lock(&v3d->mm_lock);
 	/* Allocate the object's space in the GPU's page tables.
 	 * Inserting PTEs will happen later, but the offset is for the
@@ -123,7 +129,7 @@ v3d_bo_create_finish(struct drm_gem_object *obj)
 	 */
 	ret = drm_mm_insert_node_generic(&v3d->mm, &bo->node,
 					 obj->size >> V3D_MMU_PAGE_SHIFT,
-					 GMP_GRANULARITY >> V3D_MMU_PAGE_SHIFT, 0, 0);
+					 align >> V3D_MMU_PAGE_SHIFT, 0, 0);
 	spin_unlock(&v3d->mm_lock);
 	if (ret)
 		return ret;
@@ -143,10 +149,12 @@ struct v3d_bo *v3d_bo_create(struct drm_device *dev, struct drm_file *file_priv,
 			     size_t unaligned_size)
 {
 	struct drm_gem_shmem_object *shmem_obj;
+	struct v3d_dev *v3d = to_v3d_dev(dev);
 	struct v3d_bo *bo;
 	int ret;
 
-	shmem_obj = drm_gem_shmem_create(dev, unaligned_size);
+	shmem_obj = drm_gem_shmem_create_with_mnt(dev, unaligned_size,
+						  v3d->gemfs);
 	if (IS_ERR(shmem_obj))
 		return ERR_CAST(shmem_obj);
 	bo = to_v3d_bo(&shmem_obj->base);
diff --git a/drivers/gpu/drm/v3d/v3d_debugfs.c b/drivers/gpu/drm/v3d/v3d_debugfs.c
index 76816f2551c1..7e789e181af0 100644
--- a/drivers/gpu/drm/v3d/v3d_debugfs.c
+++ b/drivers/gpu/drm/v3d/v3d_debugfs.c
@@ -21,74 +21,74 @@ struct v3d_reg_def {
 };
 
 static const struct v3d_reg_def v3d_hub_reg_defs[] = {
-	REGDEF(33, 42, V3D_HUB_AXICFG),
-	REGDEF(33, 71, V3D_HUB_UIFCFG),
-	REGDEF(33, 71, V3D_HUB_IDENT0),
-	REGDEF(33, 71, V3D_HUB_IDENT1),
-	REGDEF(33, 71, V3D_HUB_IDENT2),
-	REGDEF(33, 71, V3D_HUB_IDENT3),
-	REGDEF(33, 71, V3D_HUB_INT_STS),
-	REGDEF(33, 71, V3D_HUB_INT_MSK_STS),
-
-	REGDEF(33, 71, V3D_MMU_CTL),
-	REGDEF(33, 71, V3D_MMU_VIO_ADDR),
-	REGDEF(33, 71, V3D_MMU_VIO_ID),
-	REGDEF(33, 71, V3D_MMU_DEBUG_INFO),
-
-	REGDEF(71, 71, V3D_GMP_STATUS(71)),
-	REGDEF(71, 71, V3D_GMP_CFG(71)),
-	REGDEF(71, 71, V3D_GMP_VIO_ADDR(71)),
+	REGDEF(V3D_GEN_33, V3D_GEN_42, V3D_HUB_AXICFG),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_HUB_UIFCFG),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_HUB_IDENT0),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_HUB_IDENT1),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_HUB_IDENT2),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_HUB_IDENT3),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_HUB_INT_STS),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_HUB_INT_MSK_STS),
+
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_MMU_CTL),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_MMU_VIO_ADDR),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_MMU_VIO_ID),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_MMU_DEBUG_INFO),
+
+	REGDEF(V3D_GEN_71, V3D_GEN_71, V3D_GMP_STATUS(71)),
+	REGDEF(V3D_GEN_71, V3D_GEN_71, V3D_GMP_CFG(71)),
+	REGDEF(V3D_GEN_71, V3D_GEN_71, V3D_GMP_VIO_ADDR(71)),
 };
 
 static const struct v3d_reg_def v3d_gca_reg_defs[] = {
-	REGDEF(33, 33, V3D_GCA_SAFE_SHUTDOWN),
-	REGDEF(33, 33, V3D_GCA_SAFE_SHUTDOWN_ACK),
+	REGDEF(V3D_GEN_33, V3D_GEN_33, V3D_GCA_SAFE_SHUTDOWN),
+	REGDEF(V3D_GEN_33, V3D_GEN_33, V3D_GCA_SAFE_SHUTDOWN_ACK),
 };
 
 static const struct v3d_reg_def v3d_core_reg_defs[] = {
-	REGDEF(33, 71, V3D_CTL_IDENT0),
-	REGDEF(33, 71, V3D_CTL_IDENT1),
-	REGDEF(33, 71, V3D_CTL_IDENT2),
-	REGDEF(33, 71, V3D_CTL_MISCCFG),
-	REGDEF(33, 71, V3D_CTL_INT_STS),
-	REGDEF(33, 71, V3D_CTL_INT_MSK_STS),
-	REGDEF(33, 71, V3D_CLE_CT0CS),
-	REGDEF(33, 71, V3D_CLE_CT0CA),
-	REGDEF(33, 71, V3D_CLE_CT0EA),
-	REGDEF(33, 71, V3D_CLE_CT1CS),
-	REGDEF(33, 71, V3D_CLE_CT1CA),
-	REGDEF(33, 71, V3D_CLE_CT1EA),
-
-	REGDEF(33, 71, V3D_PTB_BPCA),
-	REGDEF(33, 71, V3D_PTB_BPCS),
-
-	REGDEF(33, 42, V3D_GMP_STATUS(33)),
-	REGDEF(33, 42, V3D_GMP_CFG(33)),
-	REGDEF(33, 42, V3D_GMP_VIO_ADDR(33)),
-
-	REGDEF(33, 71, V3D_ERR_FDBGO),
-	REGDEF(33, 71, V3D_ERR_FDBGB),
-	REGDEF(33, 71, V3D_ERR_FDBGS),
-	REGDEF(33, 71, V3D_ERR_STAT),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_CTL_IDENT0),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_CTL_IDENT1),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_CTL_IDENT2),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_CTL_MISCCFG),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_CTL_INT_STS),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_CTL_INT_MSK_STS),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_CLE_CT0CS),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_CLE_CT0CA),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_CLE_CT0EA),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_CLE_CT1CS),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_CLE_CT1CA),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_CLE_CT1EA),
+
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_PTB_BPCA),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_PTB_BPCS),
+
+	REGDEF(V3D_GEN_33, V3D_GEN_42, V3D_GMP_STATUS(33)),
+	REGDEF(V3D_GEN_33, V3D_GEN_42, V3D_GMP_CFG(33)),
+	REGDEF(V3D_GEN_33, V3D_GEN_42, V3D_GMP_VIO_ADDR(33)),
+
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_ERR_FDBGO),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_ERR_FDBGB),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_ERR_FDBGS),
+	REGDEF(V3D_GEN_33, V3D_GEN_71, V3D_ERR_STAT),
 };
 
 static const struct v3d_reg_def v3d_csd_reg_defs[] = {
-	REGDEF(41, 71, V3D_CSD_STATUS),
-	REGDEF(41, 42, V3D_CSD_CURRENT_CFG0(41)),
-	REGDEF(41, 42, V3D_CSD_CURRENT_CFG1(41)),
-	REGDEF(41, 42, V3D_CSD_CURRENT_CFG2(41)),
-	REGDEF(41, 42, V3D_CSD_CURRENT_CFG3(41)),
-	REGDEF(41, 42, V3D_CSD_CURRENT_CFG4(41)),
-	REGDEF(41, 42, V3D_CSD_CURRENT_CFG5(41)),
-	REGDEF(41, 42, V3D_CSD_CURRENT_CFG6(41)),
-	REGDEF(71, 71, V3D_CSD_CURRENT_CFG0(71)),
-	REGDEF(71, 71, V3D_CSD_CURRENT_CFG1(71)),
-	REGDEF(71, 71, V3D_CSD_CURRENT_CFG2(71)),
-	REGDEF(71, 71, V3D_CSD_CURRENT_CFG3(71)),
-	REGDEF(71, 71, V3D_CSD_CURRENT_CFG4(71)),
-	REGDEF(71, 71, V3D_CSD_CURRENT_CFG5(71)),
-	REGDEF(71, 71, V3D_CSD_CURRENT_CFG6(71)),
-	REGDEF(71, 71, V3D_V7_CSD_CURRENT_CFG7),
+	REGDEF(V3D_GEN_41, V3D_GEN_71, V3D_CSD_STATUS),
+	REGDEF(V3D_GEN_41, V3D_GEN_42, V3D_CSD_CURRENT_CFG0(41)),
+	REGDEF(V3D_GEN_41, V3D_GEN_42, V3D_CSD_CURRENT_CFG1(41)),
+	REGDEF(V3D_GEN_41, V3D_GEN_42, V3D_CSD_CURRENT_CFG2(41)),
+	REGDEF(V3D_GEN_41, V3D_GEN_42, V3D_CSD_CURRENT_CFG3(41)),
+	REGDEF(V3D_GEN_41, V3D_GEN_42, V3D_CSD_CURRENT_CFG4(41)),
+	REGDEF(V3D_GEN_41, V3D_GEN_42, V3D_CSD_CURRENT_CFG5(41)),
+	REGDEF(V3D_GEN_41, V3D_GEN_42, V3D_CSD_CURRENT_CFG6(41)),
+	REGDEF(V3D_GEN_71, V3D_GEN_71, V3D_CSD_CURRENT_CFG0(71)),
+	REGDEF(V3D_GEN_71, V3D_GEN_71, V3D_CSD_CURRENT_CFG1(71)),
+	REGDEF(V3D_GEN_71, V3D_GEN_71, V3D_CSD_CURRENT_CFG2(71)),
+	REGDEF(V3D_GEN_71, V3D_GEN_71, V3D_CSD_CURRENT_CFG3(71)),
+	REGDEF(V3D_GEN_71, V3D_GEN_71, V3D_CSD_CURRENT_CFG4(71)),
+	REGDEF(V3D_GEN_71, V3D_GEN_71, V3D_CSD_CURRENT_CFG5(71)),
+	REGDEF(V3D_GEN_71, V3D_GEN_71, V3D_CSD_CURRENT_CFG6(71)),
+	REGDEF(V3D_GEN_71, V3D_GEN_71, V3D_V7_CSD_CURRENT_CFG7),
 };
 
 static int v3d_v3d_debugfs_regs(struct seq_file *m, void *unused)
@@ -164,7 +164,7 @@ static int v3d_v3d_debugfs_ident(struct seq_file *m, void *unused)
 		   str_yes_no(ident2 & V3D_HUB_IDENT2_WITH_MMU));
 	seq_printf(m, "TFU:        %s\n",
 		   str_yes_no(ident1 & V3D_HUB_IDENT1_WITH_TFU));
-	if (v3d->ver <= 42) {
+	if (v3d->ver <= V3D_GEN_42) {
 		seq_printf(m, "TSY:        %s\n",
 			   str_yes_no(ident1 & V3D_HUB_IDENT1_WITH_TSY));
 	}
@@ -196,11 +196,11 @@ static int v3d_v3d_debugfs_ident(struct seq_file *m, void *unused)
 		seq_printf(m, "  QPUs:         %d\n", nslc * qups);
 		seq_printf(m, "  Semaphores:   %d\n",
 			   V3D_GET_FIELD(ident1, V3D_IDENT1_NSEM));
-		if (v3d->ver <= 42) {
+		if (v3d->ver <= V3D_GEN_42) {
 			seq_printf(m, "  BCG int:      %d\n",
 				   (ident2 & V3D_IDENT2_BCG_INT) != 0);
 		}
-		if (v3d->ver < 40) {
+		if (v3d->ver < V3D_GEN_41) {
 			seq_printf(m, "  Override TMU: %d\n",
 				   (misccfg & V3D_MISCCFG_OVRTMUOUT) != 0);
 		}
@@ -234,7 +234,7 @@ static int v3d_measure_clock(struct seq_file *m, void *unused)
 	int core = 0;
 	int measure_ms = 1000;
 
-	if (v3d->ver >= 40) {
+	if (v3d->ver >= V3D_GEN_41) {
 		int cycle_count_reg = V3D_PCTR_CYCLE_COUNT(v3d->ver);
 		V3D_CORE_WRITE(core, V3D_V4_PCTR_0_SRC_0_3,
 			       V3D_SET_FIELD_VER(cycle_count_reg,
diff --git a/drivers/gpu/drm/v3d/v3d_drv.c b/drivers/gpu/drm/v3d/v3d_drv.c
index f45fdd7d542f..4277b65fcfd1 100644
--- a/drivers/gpu/drm/v3d/v3d_drv.c
+++ b/drivers/gpu/drm/v3d/v3d_drv.c
@@ -17,6 +17,7 @@
 #include <linux/dma-mapping.h>
 #include <linux/io.h>
 #include <linux/module.h>
+#include <linux/of.h>
 #include <linux/of_platform.h>
 #include <linux/platform_device.h>
 #include <linux/sched/clock.h>
@@ -24,6 +25,9 @@
 
 #include <drm/drm_drv.h>
 #include <drm/drm_managed.h>
+
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
 #include <uapi/drm/v3d_drm.h>
 
 #include "v3d_drv.h"
@@ -36,6 +40,17 @@
 #define DRIVER_MINOR 0
 #define DRIVER_PATCHLEVEL 0
 
+/* Only expose the `super_pages` modparam if THP is enabled. */
+#ifdef CONFIG_TRANSPARENT_HUGEPAGE
+bool super_pages = true;
+module_param_named(super_pages, super_pages, bool, 0400);
+MODULE_PARM_DESC(super_pages, "Enable/Disable Super Pages support.");
+#endif
+
+bool debug_mmu;
+module_param(debug_mmu, bool, 0644);
+MODULE_PARM_DESC(debug_mmu, "Enable/Disable MMU error logging");
+
 static int v3d_get_param_ioctl(struct drm_device *dev, void *data,
 			       struct drm_file *file_priv)
 {
@@ -86,7 +101,7 @@ static int v3d_get_param_ioctl(struct drm_device *dev, void *data,
 		args->value = 1;
 		return 0;
 	case DRM_V3D_PARAM_SUPPORTS_PERFMON:
-		args->value = (v3d->ver >= 40);
+		args->value = (v3d->ver >= V3D_GEN_41);
 		return 0;
 	case DRM_V3D_PARAM_SUPPORTS_MULTISYNC_EXT:
 		args->value = 1;
@@ -97,6 +112,9 @@ static int v3d_get_param_ioctl(struct drm_device *dev, void *data,
 	case DRM_V3D_PARAM_MAX_PERF_COUNTERS:
 		args->value = v3d->perfmon_info.max_counters;
 		return 0;
+	case DRM_V3D_PARAM_SUPPORTS_SUPER_PAGES:
+		args->value = !!v3d->gemfs;
+		return 0;
 	default:
 		DRM_DEBUG("Unknown parameter %d\n", args->param);
 		return -EINVAL;
@@ -136,12 +154,24 @@ v3d_open(struct drm_device *dev, struct drm_file *file)
 static void
 v3d_postclose(struct drm_device *dev, struct drm_file *file)
 {
+	struct v3d_dev *v3d = to_v3d_dev(dev);
 	struct v3d_file_priv *v3d_priv = file->driver_priv;
+	unsigned long irqflags;
 	enum v3d_queue q;
 
-	for (q = 0; q < V3D_MAX_QUEUES; q++)
+	for (q = 0; q < V3D_MAX_QUEUES; q++) {
+		struct v3d_queue_state *queue = &v3d->queue[q];
+		struct v3d_job *job = queue->active_job;
+
 		drm_sched_entity_destroy(&v3d_priv->sched_entity[q]);
 
+		if (job && job->base.entity == &v3d_priv->sched_entity[q]) {
+			spin_lock_irqsave(&queue->queue_lock, irqflags);
+			job->file_priv = NULL;
+			spin_unlock_irqrestore(&queue->queue_lock, irqflags);
+		}
+	}
+
 	v3d_perfmon_close_file(v3d_priv);
 	kfree(v3d_priv);
 }
@@ -214,6 +244,7 @@ static const struct drm_ioctl_desc v3d_drm_ioctls[] = {
 	DRM_IOCTL_DEF_DRV(V3D_PERFMON_GET_VALUES, v3d_perfmon_get_values_ioctl, DRM_RENDER_ALLOW),
 	DRM_IOCTL_DEF_DRV(V3D_SUBMIT_CPU, v3d_submit_cpu_ioctl, DRM_RENDER_ALLOW | DRM_AUTH),
 	DRM_IOCTL_DEF_DRV(V3D_PERFMON_GET_COUNTER, v3d_perfmon_get_counter_ioctl, DRM_RENDER_ALLOW),
+	DRM_IOCTL_DEF_DRV(V3D_PERFMON_SET_GLOBAL, v3d_perfmon_set_global_ioctl, DRM_RENDER_ALLOW),
 };
 
 static const struct drm_driver v3d_drm_driver = {
@@ -245,14 +276,44 @@ static const struct drm_driver v3d_drm_driver = {
 };
 
 static const struct of_device_id v3d_of_match[] = {
-	{ .compatible = "brcm,2711-v3d" },
-	{ .compatible = "brcm,2712-v3d" },
-	{ .compatible = "brcm,7268-v3d" },
-	{ .compatible = "brcm,7278-v3d" },
+	{ .compatible = "brcm,2711-v3d", .data = (void *)V3D_GEN_42 },
+	{ .compatible = "brcm,2712-v3d", .data = (void *)V3D_GEN_71 },
+	{ .compatible = "brcm,7268-v3d", .data = (void *)V3D_GEN_33 },
+	{ .compatible = "brcm,7278-v3d", .data = (void *)V3D_GEN_41 },
 	{},
 };
 MODULE_DEVICE_TABLE(of, v3d_of_match);
 
+static void
+v3d_idle_sms(struct v3d_dev *v3d)
+{
+	if (v3d->ver < V3D_GEN_71)
+		return;
+
+	V3D_SMS_WRITE(V3D_SMS_TEE_CS, V3D_SMS_CLEAR_POWER_OFF);
+
+	if (wait_for((V3D_GET_FIELD(V3D_SMS_READ(V3D_SMS_TEE_CS),
+				    V3D_SMS_STATE) == V3D_SMS_IDLE), 100)) {
+		DRM_ERROR("Failed to power up SMS\n");
+	}
+
+	v3d_reset_sms(v3d);
+}
+
+static void
+v3d_power_off_sms(struct v3d_dev *v3d)
+{
+	if (v3d->ver < V3D_GEN_71)
+		return;
+
+	V3D_SMS_WRITE(V3D_SMS_TEE_CS, V3D_SMS_POWER_OFF);
+
+	if (wait_for((V3D_GET_FIELD(V3D_SMS_READ(V3D_SMS_TEE_CS),
+				    V3D_SMS_STATE) == V3D_SMS_POWER_OFF_STATE), 100)) {
+		DRM_ERROR("Failed to power off SMS\n");
+	}
+}
+
 static int
 map_regs(struct v3d_dev *v3d, void __iomem **regs, const char *name)
 {
@@ -263,8 +324,11 @@ map_regs(struct v3d_dev *v3d, void __iomem **regs, const char *name)
 static int v3d_platform_drm_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
+	struct rpi_firmware *firmware;
+	struct device_node *node;
 	struct drm_device *drm;
 	struct v3d_dev *v3d;
+	enum v3d_gen gen;
 	int ret;
 	u32 mmu_debug;
 	u32 ident1, ident3;
@@ -278,6 +342,9 @@ static int v3d_platform_drm_probe(struct platform_device *pdev)
 
 	platform_set_drvdata(pdev, drm);
 
+	gen = (enum v3d_gen)of_device_get_match_data(dev);
+	v3d->ver = gen;
+
 	ret = map_regs(v3d, &v3d->hub_regs, "hub");
 	if (ret)
 		return ret;
@@ -286,6 +353,12 @@ static int v3d_platform_drm_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
+	if (v3d->ver >= V3D_GEN_71) {
+		ret = map_regs(v3d, &v3d->sms_regs, "sms");
+		if (ret)
+			return ret;
+	}
+
 	v3d->clk = devm_clk_get_optional(dev, NULL);
 	if (IS_ERR(v3d->clk))
 		return dev_err_probe(dev, PTR_ERR(v3d->clk), "Failed to get V3D clock\n");
@@ -296,6 +369,8 @@ static int v3d_platform_drm_probe(struct platform_device *pdev)
 		return ret;
 	}
 
+	v3d_idle_sms(v3d);
+
 	mmu_debug = V3D_READ(V3D_MMU_DEBUG_INFO);
 	mask = DMA_BIT_MASK(30 + V3D_GET_FIELD(mmu_debug, V3D_MMU_PA_WIDTH));
 	ret = dma_set_mask_and_coherent(dev, mask);
@@ -309,6 +384,11 @@ static int v3d_platform_drm_probe(struct platform_device *pdev)
 	ident1 = V3D_READ(V3D_HUB_IDENT1);
 	v3d->ver = (V3D_GET_FIELD(ident1, V3D_HUB_IDENT1_TVER) * 10 +
 		    V3D_GET_FIELD(ident1, V3D_HUB_IDENT1_REV));
+	/* Make sure that the V3D tech version retrieved from the HW is equal
+	 * to the one advertised by the device tree.
+	 */
+	WARN_ON(v3d->ver != gen);
+
 	v3d->cores = V3D_GET_FIELD(ident1, V3D_HUB_IDENT1_NCORES);
 	WARN_ON(v3d->cores > 1); /* multicore not yet implemented */
 
@@ -333,7 +413,32 @@ static int v3d_platform_drm_probe(struct platform_device *pdev)
 		}
 	}
 
-	if (v3d->ver < 41) {
+	node = rpi_firmware_find_node();
+	if (!node) {
+		ret = -EINVAL;
+		goto clk_disable;
+	}
+
+	firmware = rpi_firmware_get(node);
+	of_node_put(node);
+	if (!firmware) {
+		ret = -EPROBE_DEFER;
+		goto clk_disable;
+	}
+
+	v3d->clk_up_rate = rpi_firmware_clk_get_max_rate(firmware,
+							 RPI_FIRMWARE_V3D_CLK_ID);
+	rpi_firmware_put(firmware);
+
+	/* For downclocking, drop it to the minimum frequency we can get from
+	 * the CPRMAN clock generator dividing off our parent.  The divider is
+	 * 4 bits, but ask for just higher than that so that rounding doesn't
+	 * make cprman reject our rate.
+	 */
+	v3d->clk_down_rate =
+		(clk_get_rate(clk_get_parent(v3d->clk)) / (1 << 4)) + 10000;
+
+	if (v3d->ver < V3D_GEN_41) {
 		ret = map_regs(v3d, &v3d->gca_regs, "gca");
 		if (ret)
 			goto clk_disable;
@@ -362,6 +467,8 @@ static int v3d_platform_drm_probe(struct platform_device *pdev)
 	ret = v3d_sysfs_init(dev);
 	if (ret)
 		goto drm_unregister;
+	ret = clk_set_min_rate(v3d->clk, v3d->clk_down_rate);
+	WARN_ON_ONCE(ret != 0);
 
 	return 0;
 
@@ -393,6 +500,8 @@ static void v3d_platform_drm_remove(struct platform_device *pdev)
 	dma_free_wc(v3d->drm.dev, 4096, v3d->mmu_scratch,
 		    v3d->mmu_scratch_paddr);
 
+	v3d_power_off_sms(v3d);
+
 	clk_disable_unprepare(v3d->clk);
 }
 
diff --git a/drivers/gpu/drm/v3d/v3d_drv.h b/drivers/gpu/drm/v3d/v3d_drv.h
index d4b0549205c2..fb2eaeac4722 100644
--- a/drivers/gpu/drm/v3d/v3d_drv.h
+++ b/drivers/gpu/drm/v3d/v3d_drv.h
@@ -19,9 +19,8 @@ struct clk;
 struct platform_device;
 struct reset_control;
 
-#define GMP_GRANULARITY (128 * 1024)
-
 #define V3D_MMU_PAGE_SHIFT 12
+#define V3D_PAGE_FACTOR (PAGE_SIZE >> V3D_MMU_PAGE_SHIFT)
 
 #define V3D_MAX_QUEUES (V3D_CPU + 1)
 
@@ -59,6 +58,12 @@ struct v3d_queue_state {
 
 	/* Stores the GPU stats for this queue in the global context. */
 	struct v3d_stats stats;
+
+	/* Currently active job for this queue */
+	struct v3d_job *active_job;
+	spinlock_t queue_lock;
+	/* Protect dma fence for signalling job completion */
+	spinlock_t fence_lock;
 };
 
 /* Performance monitor object. The perform lifetime is controlled by userspace
@@ -101,11 +106,18 @@ enum v3d_irq {
 	V3D_MAX_IRQS,
 };
 
+enum v3d_gen {
+	V3D_GEN_33 = 33,
+	V3D_GEN_41 = 41,
+	V3D_GEN_42 = 42,
+	V3D_GEN_71 = 71,
+};
+
 struct v3d_dev {
 	struct drm_device drm;
 
 	/* Short representation (e.g. 33, 41) of the V3D tech version */
-	int ver;
+	enum v3d_gen ver;
 
 	/* Short representation (e.g. 5, 6) of the V3D tech revision */
 	int rev;
@@ -120,7 +132,14 @@ struct v3d_dev {
 	void __iomem *core_regs[3];
 	void __iomem *bridge_regs;
 	void __iomem *gca_regs;
+	void __iomem *sms_regs;
 	struct clk *clk;
+	struct delayed_work clk_down_work;
+	unsigned long clk_up_rate, clk_down_rate;
+	struct mutex clk_lock;
+	u32 clk_refcount;
+	bool clk_up;
+
 	struct reset_control *reset;
 
 	/* Virtual and DMA addresses of the single shared page table. */
@@ -145,21 +164,15 @@ struct v3d_dev {
 	struct drm_mm mm;
 	spinlock_t mm_lock;
 
-	struct work_struct overflow_mem_work;
+	/*
+	 * tmpfs instance used for shmem backed objects
+	 */
+	struct vfsmount *gemfs;
 
-	struct v3d_bin_job *bin_job;
-	struct v3d_render_job *render_job;
-	struct v3d_tfu_job *tfu_job;
-	struct v3d_csd_job *csd_job;
-	struct v3d_cpu_job *cpu_job;
+	struct work_struct overflow_mem_work;
 
 	struct v3d_queue_state queue[V3D_MAX_QUEUES];
 
-	/* Spinlock used to synchronize the overflow memory
-	 * management against bin job submission.
-	 */
-	spinlock_t job_lock;
-
 	/* Used to track the active perfmon if any. */
 	struct v3d_perfmon *active_perfmon;
 
@@ -187,6 +200,12 @@ struct v3d_dev {
 		u32 num_allocated;
 		u32 pages_allocated;
 	} bo_stats;
+
+	/* To support a performance analysis tool in user space, we require
+	 * a single, globally configured performance monitor (perfmon) for
+	 * all jobs.
+	 */
+	struct v3d_perfmon *global_perfmon;
 };
 
 static inline struct v3d_dev *
@@ -198,7 +217,7 @@ to_v3d_dev(struct drm_device *dev)
 static inline bool
 v3d_has_csd(struct v3d_dev *v3d)
 {
-	return v3d->ver >= 41;
+	return v3d->ver >= V3D_GEN_41;
 }
 
 #define v3d_to_pdev(v3d) to_platform_device((v3d)->drm.dev)
@@ -260,6 +279,15 @@ to_v3d_fence(struct dma_fence *fence)
 #define V3D_GCA_READ(offset) readl(v3d->gca_regs + offset)
 #define V3D_GCA_WRITE(offset, val) writel(val, v3d->gca_regs + offset)
 
+#define V3D_SMS_IDLE				0x0
+#define V3D_SMS_ISOLATING_FOR_RESET		0xa
+#define V3D_SMS_RESETTING			0xb
+#define V3D_SMS_ISOLATING_FOR_POWER_OFF	0xc
+#define V3D_SMS_POWER_OFF_STATE		0xd
+
+#define V3D_SMS_READ(offset) readl(v3d->sms_regs + (offset))
+#define V3D_SMS_WRITE(offset, val) writel(val, v3d->sms_regs + (offset))
+
 #define V3D_CORE_READ(core, offset) readl(v3d->core_regs[core] + offset)
 #define V3D_CORE_WRITE(core, offset, val) writel(val, v3d->core_regs[core] + offset)
 
@@ -290,9 +318,9 @@ struct v3d_job {
 	struct v3d_perfmon *perfmon;
 
 	/* File descriptor of the process that submitted the job that could be used
-	 * for collecting stats by process of GPU usage.
+	 * to collect per-process information about the GPU.
 	 */
-	struct drm_file *file;
+	struct v3d_file_priv *file_priv;
 
 	/* Callback for the freeing of the job on refcount going to 0. */
 	void (*free)(struct kref *ref);
@@ -533,15 +561,21 @@ void v3d_get_stats(const struct v3d_stats *stats, u64 timestamp,
 
 /* v3d_fence.c */
 extern const struct dma_fence_ops v3d_fence_ops;
-struct dma_fence *v3d_fence_create(struct v3d_dev *v3d, enum v3d_queue queue);
+struct dma_fence *v3d_fence_create(struct v3d_dev *v3d, enum v3d_queue q);
 
 /* v3d_gem.c */
 int v3d_gem_init(struct drm_device *dev);
 void v3d_gem_destroy(struct drm_device *dev);
+void v3d_reset_sms(struct v3d_dev *v3d);
 void v3d_reset(struct v3d_dev *v3d);
 void v3d_invalidate_caches(struct v3d_dev *v3d);
 void v3d_clean_caches(struct v3d_dev *v3d);
 
+/* v3d_gemfs.c */
+extern bool super_pages;
+void v3d_gemfs_init(struct v3d_dev *v3d);
+void v3d_gemfs_fini(struct v3d_dev *v3d);
+
 /* v3d_submit.c */
 void v3d_job_cleanup(struct v3d_job *job);
 void v3d_job_put(struct v3d_job *job);
@@ -555,6 +589,7 @@ int v3d_submit_cpu_ioctl(struct drm_device *dev, void *data,
 			 struct drm_file *file_priv);
 
 /* v3d_irq.c */
+extern bool debug_mmu;
 int v3d_irq_init(struct v3d_dev *v3d);
 void v3d_irq_enable(struct v3d_dev *v3d);
 void v3d_irq_disable(struct v3d_dev *v3d);
@@ -571,7 +606,7 @@ void v3d_timestamp_query_info_free(struct v3d_timestamp_query_info *query_info,
 				   unsigned int count);
 void v3d_performance_query_info_free(struct v3d_performance_query_info *query_info,
 				     unsigned int count);
-void v3d_job_update_stats(struct v3d_job *job, enum v3d_queue queue);
+void v3d_job_update_stats(struct v3d_job *job, enum v3d_queue q);
 int v3d_sched_init(struct v3d_dev *v3d);
 void v3d_sched_fini(struct v3d_dev *v3d);
 
@@ -593,7 +628,10 @@ int v3d_perfmon_get_values_ioctl(struct drm_device *dev, void *data,
 				 struct drm_file *file_priv);
 int v3d_perfmon_get_counter_ioctl(struct drm_device *dev, void *data,
 				  struct drm_file *file_priv);
+int v3d_perfmon_set_global_ioctl(struct drm_device *dev, void *data,
+				 struct drm_file *file_priv);
 
 /* v3d_sysfs.c */
 int v3d_sysfs_init(struct device *dev);
 void v3d_sysfs_destroy(struct device *dev);
+void v3d_submit_init(struct drm_device *dev);
diff --git a/drivers/gpu/drm/v3d/v3d_fence.c b/drivers/gpu/drm/v3d/v3d_fence.c
index 89840ed212c0..c82500a1df73 100644
--- a/drivers/gpu/drm/v3d/v3d_fence.c
+++ b/drivers/gpu/drm/v3d/v3d_fence.c
@@ -3,8 +3,9 @@
 
 #include "v3d_drv.h"
 
-struct dma_fence *v3d_fence_create(struct v3d_dev *v3d, enum v3d_queue queue)
+struct dma_fence *v3d_fence_create(struct v3d_dev *v3d, enum v3d_queue q)
 {
+	struct v3d_queue_state *queue = &v3d->queue[q];
 	struct v3d_fence *fence;
 
 	fence = kzalloc(sizeof(*fence), GFP_KERNEL);
@@ -12,10 +13,10 @@ struct dma_fence *v3d_fence_create(struct v3d_dev *v3d, enum v3d_queue queue)
 		return ERR_PTR(-ENOMEM);
 
 	fence->dev = &v3d->drm;
-	fence->queue = queue;
-	fence->seqno = ++v3d->queue[queue].emit_seqno;
-	dma_fence_init(&fence->base, &v3d_fence_ops, &v3d->job_lock,
-		       v3d->queue[queue].fence_context, fence->seqno);
+	fence->queue = q;
+	fence->seqno = ++queue->emit_seqno;
+	dma_fence_init(&fence->base, &v3d_fence_ops, &queue->fence_lock,
+		       queue->fence_context, fence->seqno);
 
 	return &fence->base;
 }
diff --git a/drivers/gpu/drm/v3d/v3d_gem.c b/drivers/gpu/drm/v3d/v3d_gem.c
index 6b6ba7a68fcb..62fcd8c9cbc4 100644
--- a/drivers/gpu/drm/v3d/v3d_gem.c
+++ b/drivers/gpu/drm/v3d/v3d_gem.c
@@ -4,6 +4,7 @@
 #include <linux/device.h>
 #include <linux/dma-mapping.h>
 #include <linux/io.h>
+#include <linux/clk.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
 #include <linux/reset.h>
@@ -25,7 +26,7 @@ v3d_init_core(struct v3d_dev *v3d, int core)
 	 * type.  If you want the default behavior, you can still put
 	 * "2" in the indirect texture state's output_type field.
 	 */
-	if (v3d->ver < 40)
+	if (v3d->ver < V3D_GEN_41)
 		V3D_CORE_WRITE(core, V3D_CTL_MISCCFG, V3D_MISCCFG_OVRTMUOUT);
 
 	/* Whenever we flush the L2T cache, we always want to flush
@@ -58,7 +59,7 @@ v3d_idle_axi(struct v3d_dev *v3d, int core)
 static void
 v3d_idle_gca(struct v3d_dev *v3d)
 {
-	if (v3d->ver >= 41)
+	if (v3d->ver >= V3D_GEN_41)
 		return;
 
 	V3D_GCA_WRITE(V3D_GCA_SAFE_SHUTDOWN, V3D_GCA_SAFE_SHUTDOWN_EN);
@@ -104,6 +105,22 @@ v3d_reset_v3d(struct v3d_dev *v3d)
 	v3d_init_hw_state(v3d);
 }
 
+void
+v3d_reset_sms(struct v3d_dev *v3d)
+{
+	if (v3d->ver < V3D_GEN_71)
+		return;
+
+	V3D_SMS_WRITE(V3D_SMS_REE_CS, V3D_SET_FIELD(0x4, V3D_SMS_STATE));
+
+	if (wait_for(!(V3D_GET_FIELD(V3D_SMS_READ(V3D_SMS_REE_CS),
+				     V3D_SMS_STATE) == V3D_SMS_ISOLATING_FOR_RESET) &&
+		     !(V3D_GET_FIELD(V3D_SMS_READ(V3D_SMS_REE_CS),
+				     V3D_SMS_STATE) == V3D_SMS_RESETTING), 100)) {
+		DRM_ERROR("Failed to wait for SMS reset\n");
+	}
+}
+
 void
 v3d_reset(struct v3d_dev *v3d)
 {
@@ -121,6 +138,7 @@ v3d_reset(struct v3d_dev *v3d)
 	v3d_irq_disable(v3d);
 
 	v3d_idle_gca(v3d);
+	v3d_reset_sms(v3d);
 	v3d_reset_v3d(v3d);
 
 	v3d_mmu_set_page_table(v3d);
@@ -134,13 +152,13 @@ v3d_reset(struct v3d_dev *v3d)
 static void
 v3d_flush_l3(struct v3d_dev *v3d)
 {
-	if (v3d->ver < 41) {
+	if (v3d->ver < V3D_GEN_41) {
 		u32 gca_ctrl = V3D_GCA_READ(V3D_GCA_CACHE_CTRL);
 
 		V3D_GCA_WRITE(V3D_GCA_CACHE_CTRL,
 			      gca_ctrl | V3D_GCA_CACHE_CTRL_FLUSH);
 
-		if (v3d->ver < 33) {
+		if (v3d->ver < V3D_GEN_33) {
 			V3D_GCA_WRITE(V3D_GCA_CACHE_CTRL,
 				      gca_ctrl & ~V3D_GCA_CACHE_CTRL_FLUSH);
 		}
@@ -153,7 +171,7 @@ v3d_flush_l3(struct v3d_dev *v3d)
 static void
 v3d_invalidate_l2c(struct v3d_dev *v3d, int core)
 {
-	if (v3d->ver > 32)
+	if (v3d->ver >= V3D_GEN_33)
 		return;
 
 	V3D_CORE_WRITE(core, V3D_CTL_L2CACTL,
@@ -254,10 +272,12 @@ v3d_gem_init(struct drm_device *dev)
 		queue->fence_context = dma_fence_context_alloc(1);
 		memset(&queue->stats, 0, sizeof(queue->stats));
 		seqcount_init(&queue->stats.lock);
+
+		spin_lock_init(&queue->queue_lock);
+		spin_lock_init(&queue->fence_lock);
 	}
 
 	spin_lock_init(&v3d->mm_lock);
-	spin_lock_init(&v3d->job_lock);
 	ret = drmm_mutex_init(dev, &v3d->bo_lock);
 	if (ret)
 		return ret;
@@ -271,6 +291,8 @@ v3d_gem_init(struct drm_device *dev)
 	if (ret)
 		return ret;
 
+	v3d_submit_init(dev);
+
 	/* Note: We don't allocate address 0.  Various bits of HW
 	 * treat 0 as special, such as the occlusion query counters
 	 * where 0 means "disabled".
@@ -290,11 +312,14 @@ v3d_gem_init(struct drm_device *dev)
 	v3d_init_hw_state(v3d);
 	v3d_mmu_set_page_table(v3d);
 
+	v3d_gemfs_init(v3d);
+
 	ret = v3d_sched_init(v3d);
 	if (ret) {
 		drm_mm_takedown(&v3d->mm);
-		dma_free_coherent(v3d->drm.dev, 4096 * 1024, (void *)v3d->pt,
+		dma_free_coherent(v3d->drm.dev, pt_size, (void *)v3d->pt,
 				  v3d->pt_paddr);
+		return ret;
 	}
 
 	return 0;
@@ -304,16 +329,16 @@ void
 v3d_gem_destroy(struct drm_device *dev)
 {
 	struct v3d_dev *v3d = to_v3d_dev(dev);
+	enum v3d_queue q;
 
 	v3d_sched_fini(v3d);
+	v3d_gemfs_fini(v3d);
 
 	/* Waiting for jobs to finish would need to be done before
 	 * unregistering V3D.
 	 */
-	WARN_ON(v3d->bin_job);
-	WARN_ON(v3d->render_job);
-	WARN_ON(v3d->tfu_job);
-	WARN_ON(v3d->csd_job);
+	for (q = 0; q < V3D_MAX_QUEUES; q++)
+		WARN_ON(v3d->queue[q].active_job);
 
 	drm_mm_takedown(&v3d->mm);
 
diff --git a/drivers/gpu/drm/v3d/v3d_gemfs.c b/drivers/gpu/drm/v3d/v3d_gemfs.c
new file mode 100644
index 000000000000..4c5e18590a5c
--- /dev/null
+++ b/drivers/gpu/drm/v3d/v3d_gemfs.c
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (C) 2024 Raspberry Pi */
+
+#include <linux/fs.h>
+#include <linux/mount.h>
+
+#include "v3d_drv.h"
+
+void v3d_gemfs_init(struct v3d_dev *v3d)
+{
+	char huge_opt[] = "huge=within_size";
+	struct file_system_type *type;
+	struct vfsmount *gemfs;
+
+	/*
+	 * By creating our own shmemfs mountpoint, we can pass in
+	 * mount flags that better match our usecase. However, we
+	 * only do so on platforms which benefit from it.
+	 */
+	if (!IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE))
+		goto err;
+
+	/* The user doesn't want to enable Super Pages */
+	if (!super_pages)
+		goto err;
+
+	type = get_fs_type("tmpfs");
+	if (!type)
+		goto err;
+
+	gemfs = vfs_kern_mount(type, SB_KERNMOUNT, type->name, huge_opt);
+	if (IS_ERR(gemfs))
+		goto err;
+
+	v3d->gemfs = gemfs;
+	drm_info(&v3d->drm, "Using Transparent Hugepages\n");
+
+	return;
+
+err:
+	v3d->gemfs = NULL;
+	drm_notice(&v3d->drm,
+		   "Transparent Hugepage support is recommended for optimal performance on this platform!\n");
+}
+
+void v3d_gemfs_fini(struct v3d_dev *v3d)
+{
+	if (v3d->gemfs)
+		kern_unmount(v3d->gemfs);
+}
diff --git a/drivers/gpu/drm/v3d/v3d_irq.c b/drivers/gpu/drm/v3d/v3d_irq.c
index b98e1a4b33c7..0c4ac2d656e5 100644
--- a/drivers/gpu/drm/v3d/v3d_irq.c
+++ b/drivers/gpu/drm/v3d/v3d_irq.c
@@ -42,6 +42,8 @@ v3d_overflow_mem_work(struct work_struct *work)
 		container_of(work, struct v3d_dev, overflow_mem_work);
 	struct drm_device *dev = &v3d->drm;
 	struct v3d_bo *bo = v3d_bo_create(dev, NULL /* XXX: GMP */, 256 * 1024);
+	struct v3d_queue_state *queue = &v3d->queue[V3D_BIN];
+	struct v3d_bin_job *bin_job;
 	struct drm_gem_object *obj;
 	unsigned long irqflags;
 
@@ -60,15 +62,17 @@ v3d_overflow_mem_work(struct work_struct *work)
 	 * bin job got scheduled, that's fine.  We'll just give them
 	 * some binner pool anyway.
 	 */
-	spin_lock_irqsave(&v3d->job_lock, irqflags);
-	if (!v3d->bin_job) {
-		spin_unlock_irqrestore(&v3d->job_lock, irqflags);
+	spin_lock_irqsave(&queue->queue_lock, irqflags);
+	bin_job = (struct v3d_bin_job *)queue->active_job;
+
+	if (!bin_job) {
+		spin_unlock_irqrestore(&queue->queue_lock, irqflags);
 		goto out;
 	}
 
 	drm_gem_object_get(obj);
-	list_add_tail(&bo->unref_head, &v3d->bin_job->render->unref_list);
-	spin_unlock_irqrestore(&v3d->job_lock, irqflags);
+	list_add_tail(&bo->unref_head, &bin_job->render->unref_list);
+	spin_unlock_irqrestore(&queue->queue_lock, irqflags);
 
 	v3d_mmu_flush_all(v3d);
 
@@ -79,6 +83,20 @@ v3d_overflow_mem_work(struct work_struct *work)
 	drm_gem_object_put(obj);
 }
 
+static void
+v3d_irq_signal_fence(struct v3d_dev *v3d, enum v3d_queue q,
+		     void (*trace_irq)(struct drm_device *, uint64_t))
+{
+	struct v3d_queue_state *queue = &v3d->queue[q];
+	struct v3d_fence *fence = to_v3d_fence(queue->active_job->irq_fence);
+
+	v3d_job_update_stats(queue->active_job, q);
+	trace_irq(&v3d->drm, fence->seqno);
+
+	queue->active_job = NULL;
+	dma_fence_signal(&fence->base);
+}
+
 static irqreturn_t
 v3d_irq(int irq, void *arg)
 {
@@ -102,48 +120,24 @@ v3d_irq(int irq, void *arg)
 	}
 
 	if (intsts & V3D_INT_FLDONE) {
-		struct v3d_fence *fence =
-			to_v3d_fence(v3d->bin_job->base.irq_fence);
-
-		v3d_job_update_stats(&v3d->bin_job->base, V3D_BIN);
-		trace_v3d_bcl_irq(&v3d->drm, fence->seqno);
-
-		v3d->bin_job = NULL;
-		dma_fence_signal(&fence->base);
-
+		v3d_irq_signal_fence(v3d, V3D_BIN, trace_v3d_bcl_irq);
 		status = IRQ_HANDLED;
 	}
 
 	if (intsts & V3D_INT_FRDONE) {
-		struct v3d_fence *fence =
-			to_v3d_fence(v3d->render_job->base.irq_fence);
-
-		v3d_job_update_stats(&v3d->render_job->base, V3D_RENDER);
-		trace_v3d_rcl_irq(&v3d->drm, fence->seqno);
-
-		v3d->render_job = NULL;
-		dma_fence_signal(&fence->base);
-
+		v3d_irq_signal_fence(v3d, V3D_RENDER, trace_v3d_rcl_irq);
 		status = IRQ_HANDLED;
 	}
 
 	if (intsts & V3D_INT_CSDDONE(v3d->ver)) {
-		struct v3d_fence *fence =
-			to_v3d_fence(v3d->csd_job->base.irq_fence);
-
-		v3d_job_update_stats(&v3d->csd_job->base, V3D_CSD);
-		trace_v3d_csd_irq(&v3d->drm, fence->seqno);
-
-		v3d->csd_job = NULL;
-		dma_fence_signal(&fence->base);
-
+		v3d_irq_signal_fence(v3d, V3D_CSD, trace_v3d_csd_irq);
 		status = IRQ_HANDLED;
 	}
 
 	/* We shouldn't be triggering these if we have GMP in
 	 * always-allowed mode.
 	 */
-	if (v3d->ver < 71 && (intsts & V3D_INT_GMPV))
+	if (v3d->ver < V3D_GEN_71 && (intsts & V3D_INT_GMPV))
 		dev_err(v3d->drm.dev, "GMP violation\n");
 
 	/* V3D 4.2 wires the hub and core IRQs together, so if we &
@@ -168,15 +162,7 @@ v3d_hub_irq(int irq, void *arg)
 	V3D_WRITE(V3D_HUB_INT_CLR, intsts);
 
 	if (intsts & V3D_HUB_INT_TFUC) {
-		struct v3d_fence *fence =
-			to_v3d_fence(v3d->tfu_job->base.irq_fence);
-
-		v3d_job_update_stats(&v3d->tfu_job->base, V3D_TFU);
-		trace_v3d_tfu_irq(&v3d->drm, fence->seqno);
-
-		v3d->tfu_job = NULL;
-		dma_fence_signal(&fence->base);
-
+		v3d_irq_signal_fence(v3d, V3D_TFU, trace_v3d_tfu_irq);
 		status = IRQ_HANDLED;
 	}
 
@@ -197,27 +183,32 @@ v3d_hub_irq(int irq, void *arg)
 			"GMP",
 		};
 		const char *client = "?";
+		static bool logged_error;
 
 		V3D_WRITE(V3D_MMU_CTL, V3D_READ(V3D_MMU_CTL));
 
-		if (v3d->ver >= 41) {
+		if (v3d->ver >= V3D_GEN_41) {
 			axi_id = axi_id >> 5;
 			if (axi_id < ARRAY_SIZE(v3d41_axi_ids))
 				client = v3d41_axi_ids[axi_id];
 		}
 
-		dev_err(v3d->drm.dev, "MMU error from client %s (%d) at 0x%llx%s%s%s\n",
-			client, axi_id, (long long)vio_addr,
-			((intsts & V3D_HUB_INT_MMU_WRV) ?
-			 ", write violation" : ""),
-			((intsts & V3D_HUB_INT_MMU_PTI) ?
-			 ", pte invalid" : ""),
-			((intsts & V3D_HUB_INT_MMU_CAP) ?
-			 ", cap exceeded" : ""));
+		if (!logged_error || debug_mmu) {
+			dev_err(v3d->drm.dev, "MMU error from client %s (%d) at 0x%llx%s%s%s\n",
+				client, axi_id, (long long)vio_addr,
+				((intsts & V3D_HUB_INT_MMU_WRV) ?
+				 ", write violation" : ""),
+				((intsts & V3D_HUB_INT_MMU_PTI) ?
+				 ", pte invalid" : ""),
+				((intsts & V3D_HUB_INT_MMU_CAP) ?
+				 ", cap exceeded" : ""));
+		}
+		logged_error = true;
+
 		status = IRQ_HANDLED;
 	}
 
-	if (v3d->ver >= 71 && (intsts & V3D_V7_HUB_INT_GMPV)) {
+	if (v3d->ver >= V3D_GEN_71 && (intsts & V3D_V7_HUB_INT_GMPV)) {
 		dev_err(v3d->drm.dev, "GMP Violation\n");
 		status = IRQ_HANDLED;
 	}
diff --git a/drivers/gpu/drm/v3d/v3d_mmu.c b/drivers/gpu/drm/v3d/v3d_mmu.c
index 5bb7821c0243..a25d25a8ae61 100644
--- a/drivers/gpu/drm/v3d/v3d_mmu.c
+++ b/drivers/gpu/drm/v3d/v3d_mmu.c
@@ -4,7 +4,7 @@
 /**
  * DOC: Broadcom V3D MMU
  *
- * The V3D 3.x hardware (compared to VC4) now includes an MMU.  It has
+ * The V3D 3.x hardware (compared to VC4) now includes an MMU. It has
  * a single level of page tables for the V3D's 4GB address space to
  * map to AXI bus addresses, thus it could need up to 4MB of
  * physically contiguous memory to store the PTEs.
@@ -15,19 +15,26 @@
  *
  * To protect clients from each other, we should use the GMP to
  * quickly mask out (at 128kb granularity) what pages are available to
- * each client.  This is not yet implemented.
+ * each client. This is not yet implemented.
  */
 
 #include "v3d_drv.h"
 #include "v3d_regs.h"
 
-/* Note: All PTEs for the 1MB superpage must be filled with the
- * superpage bit set.
+/* Note: All PTEs for the 64KB bigpage or 1MB superpage must be filled
+ * with the bigpage/superpage bit set.
  */
 #define V3D_PTE_SUPERPAGE BIT(31)
+#define V3D_PTE_BIGPAGE BIT(30)
 #define V3D_PTE_WRITEABLE BIT(29)
 #define V3D_PTE_VALID BIT(28)
 
+static bool v3d_mmu_is_aligned(u32 page, u32 page_address, size_t alignment)
+{
+	return IS_ALIGNED(page, alignment >> V3D_MMU_PAGE_SHIFT) &&
+		IS_ALIGNED(page_address, alignment >> V3D_MMU_PAGE_SHIFT);
+}
+
 int v3d_mmu_flush_all(struct v3d_dev *v3d)
 {
 	int ret;
@@ -78,19 +85,40 @@ void v3d_mmu_insert_ptes(struct v3d_bo *bo)
 	struct drm_gem_shmem_object *shmem_obj = &bo->base;
 	struct v3d_dev *v3d = to_v3d_dev(shmem_obj->base.dev);
 	u32 page = bo->node.start;
-	u32 page_prot = V3D_PTE_WRITEABLE | V3D_PTE_VALID;
-	struct sg_dma_page_iter dma_iter;
-
-	for_each_sgtable_dma_page(shmem_obj->sgt, &dma_iter, 0) {
-		dma_addr_t dma_addr = sg_page_iter_dma_address(&dma_iter);
-		u32 page_address = dma_addr >> V3D_MMU_PAGE_SHIFT;
-		u32 pte = page_prot | page_address;
-		u32 i;
-
-		BUG_ON(page_address + (PAGE_SIZE >> V3D_MMU_PAGE_SHIFT) >=
-		       BIT(24));
-		for (i = 0; i < PAGE_SIZE >> V3D_MMU_PAGE_SHIFT; i++)
-			v3d->pt[page++] = pte + i;
+	struct scatterlist *sgl;
+	unsigned int count;
+
+	for_each_sgtable_dma_sg(shmem_obj->sgt, sgl, count) {
+		dma_addr_t dma_addr = sg_dma_address(sgl);
+		u32 pfn = dma_addr >> V3D_MMU_PAGE_SHIFT;
+		unsigned int len = sg_dma_len(sgl);
+
+		while (len > 0) {
+			u32 page_prot = V3D_PTE_WRITEABLE | V3D_PTE_VALID;
+			u32 page_address = page_prot | pfn;
+			unsigned int i, page_size;
+
+			BUG_ON(pfn + V3D_PAGE_FACTOR >= BIT(24));
+
+			if (len >= SZ_1M &&
+			    v3d_mmu_is_aligned(page, page_address, SZ_1M)) {
+				page_size = SZ_1M;
+				page_address |= V3D_PTE_SUPERPAGE;
+			} else if (len >= SZ_64K &&
+				   v3d_mmu_is_aligned(page, page_address, SZ_64K)) {
+				page_size = SZ_64K;
+				page_address |= V3D_PTE_BIGPAGE;
+			} else {
+				page_size = SZ_4K;
+			}
+
+			for (i = 0; i < page_size >> V3D_MMU_PAGE_SHIFT; i++) {
+				v3d->pt[page++] = page_address + i;
+				pfn++;
+			}
+
+			len -= page_size;
+		}
 	}
 
 	WARN_ON_ONCE(page - bo->node.start !=
diff --git a/drivers/gpu/drm/v3d/v3d_perfmon.c b/drivers/gpu/drm/v3d/v3d_perfmon.c
index 1abfd738a601..9a3fe5255874 100644
--- a/drivers/gpu/drm/v3d/v3d_perfmon.c
+++ b/drivers/gpu/drm/v3d/v3d_perfmon.c
@@ -200,10 +200,10 @@ void v3d_perfmon_init(struct v3d_dev *v3d)
 	const struct v3d_perf_counter_desc *counters = NULL;
 	unsigned int max = 0;
 
-	if (v3d->ver >= 71) {
+	if (v3d->ver >= V3D_GEN_71) {
 		counters = v3d_v71_performance_counters;
 		max = ARRAY_SIZE(v3d_v71_performance_counters);
-	} else if (v3d->ver >= 42) {
+	} else if (v3d->ver >= V3D_GEN_42) {
 		counters = v3d_v42_performance_counters;
 		max = ARRAY_SIZE(v3d_v42_performance_counters);
 	}
@@ -313,6 +313,9 @@ static int v3d_perfmon_idr_del(int id, void *elem, void *data)
 	if (perfmon == v3d->active_perfmon)
 		v3d_perfmon_stop(v3d, perfmon, false);
 
+	/* If the global perfmon is being destroyed, set it to NULL */
+	cmpxchg(&v3d->global_perfmon, perfmon, NULL);
+
 	v3d_perfmon_put(perfmon);
 
 	return 0;
@@ -398,6 +401,9 @@ int v3d_perfmon_destroy_ioctl(struct drm_device *dev, void *data,
 	if (perfmon == v3d->active_perfmon)
 		v3d_perfmon_stop(v3d, perfmon, false);
 
+	/* If the global perfmon is being destroyed, set it to NULL */
+	cmpxchg(&v3d->global_perfmon, perfmon, NULL);
+
 	v3d_perfmon_put(perfmon);
 
 	return 0;
@@ -415,11 +421,7 @@ int v3d_perfmon_get_values_ioctl(struct drm_device *dev, void *data,
 	if (req->pad != 0)
 		return -EINVAL;
 
-	mutex_lock(&v3d_priv->perfmon.lock);
-	perfmon = idr_find(&v3d_priv->perfmon.idr, req->id);
-	v3d_perfmon_get(perfmon);
-	mutex_unlock(&v3d_priv->perfmon.lock);
-
+	perfmon = v3d_perfmon_find(v3d_priv, req->id);
 	if (!perfmon)
 		return -EINVAL;
 
@@ -461,3 +463,34 @@ int v3d_perfmon_get_counter_ioctl(struct drm_device *dev, void *data,
 
 	return 0;
 }
+
+int v3d_perfmon_set_global_ioctl(struct drm_device *dev, void *data,
+				 struct drm_file *file_priv)
+{
+	struct v3d_file_priv *v3d_priv = file_priv->driver_priv;
+	struct drm_v3d_perfmon_set_global *req = data;
+	struct v3d_dev *v3d = to_v3d_dev(dev);
+	struct v3d_perfmon *perfmon;
+
+	if (req->flags & ~DRM_V3D_PERFMON_CLEAR_GLOBAL)
+		return -EINVAL;
+
+	perfmon = v3d_perfmon_find(v3d_priv, req->id);
+	if (!perfmon)
+		return -EINVAL;
+
+	/* If the request is to clear the global performance monitor */
+	if (req->flags & DRM_V3D_PERFMON_CLEAR_GLOBAL) {
+		if (!v3d->global_perfmon)
+			return -EINVAL;
+
+		xchg(&v3d->global_perfmon, NULL);
+
+		return 0;
+	}
+
+	if (cmpxchg(&v3d->global_perfmon, NULL, perfmon))
+		return -EBUSY;
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/v3d/v3d_performance_counters.h b/drivers/gpu/drm/v3d/v3d_performance_counters.h
index d919a2fc9449..2bc4cce0744a 100644
--- a/drivers/gpu/drm/v3d/v3d_performance_counters.h
+++ b/drivers/gpu/drm/v3d/v3d_performance_counters.h
@@ -2,11 +2,12 @@
 /*
  * Copyright (C) 2024 Raspberry Pi
  */
+
 #ifndef V3D_PERFORMANCE_COUNTERS_H
 #define V3D_PERFORMANCE_COUNTERS_H
 
-/* Holds a description of a given performance counter. The index of performance
- * counter is given by the array on v3d_performance_counter.h
+/* Holds a description of a given performance counter. The index of
+ * performance counter is given by the array on `v3d_performance_counter.c`.
  */
 struct v3d_perf_counter_desc {
 	/* Category of the counter */
@@ -20,15 +21,12 @@ struct v3d_perf_counter_desc {
 };
 
 struct v3d_perfmon_info {
-	/*
-	 * Different revisions of V3D have different total number of
+	/* Different revisions of V3D have different total number of
 	 * performance counters.
 	 */
 	unsigned int max_counters;
 
-	/*
-	 * Array of counters valid for the platform.
-	 */
+	/* Array of counters valid for the platform. */
 	const struct v3d_perf_counter_desc *counters;
 };
 
diff --git a/drivers/gpu/drm/v3d/v3d_regs.h b/drivers/gpu/drm/v3d/v3d_regs.h
index 6da3c69082bd..c1870265eaee 100644
--- a/drivers/gpu/drm/v3d/v3d_regs.h
+++ b/drivers/gpu/drm/v3d/v3d_regs.h
@@ -515,4 +515,30 @@
 # define V3D_ERR_VPAERGS                               BIT(1)
 # define V3D_ERR_VPAEABB                               BIT(0)
 
+#define V3D_SMS_REE_CS                                 0x00000
+#define V3D_SMS_TEE_CS                                 0x00400
+# define V3D_SMS_INTERRUPT                             BIT(31)
+# define V3D_SMS_POWER_OFF                             BIT(30)
+# define V3D_SMS_CLEAR_POWER_OFF                       BIT(29)
+# define V3D_SMS_LOCK                                  BIT(28)
+# define V3D_SMS_CLEAR_LOCK                            BIT(27)
+# define V3D_SMS_SVP_MODE_EXIT                         BIT(26)
+# define V3D_SMS_CLEAR_SVP_MODE_EXIT                   BIT(25)
+# define V3D_SMS_SVP_MODE_ENTER                        BIT(24)
+# define V3D_SMS_CLEAR_SVP_MODE_ENTER                  BIT(23)
+# define V3D_SMS_THEIR_MODE_EXIT                       BIT(22)
+# define V3D_SMS_THEIR_MODE_ENTER                      BIT(21)
+# define V3D_SMS_OUR_MODE_EXIT                         BIT(20)
+# define V3D_SMS_CLEAR_OUR_MODE_EXIT                   BIT(19)
+# define V3D_SMS_SEQ_PC_MASK                           V3D_MASK(16, 10)
+# define V3D_SMS_SEQ_PC_SHIFT                          10
+# define V3D_SMS_HUBCORE_STATUS_MASK                   V3D_MASK(9, 8)
+# define V3D_SMS_HUBCORE_STATUS_SHIFT                  8
+# define V3D_SMS_NEW_MODE_MASK                         V3D_MASK(7, 6)
+# define V3D_SMS_NEW_MODE_SHIFT                        6
+# define V3D_SMS_OLD_MODE_MASK                         V3D_MASK(5, 4)
+# define V3D_SMS_OLD_MODE_SHIFT                        4
+# define V3D_SMS_STATE_MASK                            V3D_MASK(3, 0)
+# define V3D_SMS_STATE_SHIFT                           0
+
 #endif /* V3D_REGS_H */
diff --git a/drivers/gpu/drm/v3d/v3d_sched.c b/drivers/gpu/drm/v3d/v3d_sched.c
index c9c88d3ad669..c6921268d8f8 100644
--- a/drivers/gpu/drm/v3d/v3d_sched.c
+++ b/drivers/gpu/drm/v3d/v3d_sched.c
@@ -5,16 +5,16 @@
  * DOC: Broadcom V3D scheduling
  *
  * The shared DRM GPU scheduler is used to coordinate submitting jobs
- * to the hardware.  Each DRM fd (roughly a client process) gets its
- * own scheduler entity, which will process jobs in order.  The GPU
- * scheduler will round-robin between clients to submit the next job.
+ * to the hardware. Each DRM fd (roughly a client process) gets its
+ * own scheduler entity, which will process jobs in order. The GPU
+ * scheduler will schedule the clients with a FIFO scheduling algorithm.
  *
  * For simplicity, and in order to keep latency low for interactive
  * jobs when bulk background jobs are queued up, we submit a new job
  * to the HW only when it has completed the last one, instead of
- * filling up the CT[01]Q FIFOs with jobs.  Similarly, we use
- * drm_sched_job_add_dependency() to manage the dependency between bin and
- * render, instead of having the clients submit jobs using the HW's
+ * filling up the CT[01]Q FIFOs with jobs. Similarly, we use
+ * `drm_sched_job_add_dependency()` to manage the dependency between bin
+ * and render, instead of having the clients submit jobs using the HW's
  * semaphores to interlock between them.
  */
 
@@ -120,18 +120,26 @@ v3d_cpu_job_free(struct drm_sched_job *sched_job)
 static void
 v3d_switch_perfmon(struct v3d_dev *v3d, struct v3d_job *job)
 {
-	if (job->perfmon != v3d->active_perfmon)
+	struct v3d_perfmon *perfmon = v3d->global_perfmon;
+
+	if (!perfmon)
+		perfmon = job->perfmon;
+
+	if (perfmon == v3d->active_perfmon)
+		return;
+
+	if (perfmon != v3d->active_perfmon)
 		v3d_perfmon_stop(v3d, v3d->active_perfmon, true);
 
-	if (job->perfmon && v3d->active_perfmon != job->perfmon)
-		v3d_perfmon_start(v3d, job->perfmon);
+	if (perfmon && v3d->active_perfmon != perfmon)
+		v3d_perfmon_start(v3d, perfmon);
 }
 
 static void
 v3d_job_start_stats(struct v3d_job *job, enum v3d_queue queue)
 {
 	struct v3d_dev *v3d = job->v3d;
-	struct v3d_file_priv *file = job->file->driver_priv;
+	struct v3d_file_priv *file = job->file_priv;
 	struct v3d_stats *global_stats = &v3d->queue[queue].stats;
 	struct v3d_stats *local_stats = &file->stats[queue];
 	u64 now = local_clock();
@@ -186,11 +194,11 @@ v3d_stats_update(struct v3d_stats *stats, u64 now)
 }
 
 void
-v3d_job_update_stats(struct v3d_job *job, enum v3d_queue queue)
+v3d_job_update_stats(struct v3d_job *job, enum v3d_queue q)
 {
 	struct v3d_dev *v3d = job->v3d;
-	struct v3d_file_priv *file = job->file->driver_priv;
-	struct v3d_stats *global_stats = &v3d->queue[queue].stats;
+	struct v3d_queue_state *queue = &v3d->queue[q];
+	struct v3d_stats *global_stats = &queue->stats;
 	u64 now = local_clock();
 	unsigned long flags;
 
@@ -201,10 +209,10 @@ v3d_job_update_stats(struct v3d_job *job, enum v3d_queue queue)
 		preempt_disable();
 
 	/* Don't update the local stats if the file context has already closed */
-	if (file)
-		v3d_stats_update(&file->stats[queue], now);
-	else
-		drm_dbg(&v3d->drm, "The file descriptor was closed before job completion\n");
+	spin_lock(&queue->queue_lock);
+	if (job->file_priv)
+		v3d_stats_update(&job->file_priv->stats[q], now);
+	spin_unlock(&queue->queue_lock);
 
 	v3d_stats_update(global_stats, now);
 
@@ -218,23 +226,28 @@ static struct dma_fence *v3d_bin_job_run(struct drm_sched_job *sched_job)
 {
 	struct v3d_bin_job *job = to_bin_job(sched_job);
 	struct v3d_dev *v3d = job->base.v3d;
+	struct v3d_queue_state *queue = &v3d->queue[V3D_BIN];
 	struct drm_device *dev = &v3d->drm;
 	struct dma_fence *fence;
 	unsigned long irqflags;
 
-	if (unlikely(job->base.base.s_fence->finished.error))
+	if (unlikely(job->base.base.s_fence->finished.error)) {
+		spin_lock_irqsave(&queue->queue_lock, irqflags);
+		queue->active_job = NULL;
+		spin_unlock_irqrestore(&queue->queue_lock, irqflags);
 		return NULL;
+	}
 
 	/* Lock required around bin_job update vs
 	 * v3d_overflow_mem_work().
 	 */
-	spin_lock_irqsave(&v3d->job_lock, irqflags);
-	v3d->bin_job = job;
+	spin_lock_irqsave(&queue->queue_lock, irqflags);
+	queue->active_job = &job->base;
 	/* Clear out the overflow allocation, so we don't
 	 * reuse the overflow attached to a previous job.
 	 */
 	V3D_CORE_WRITE(0, V3D_PTB_BPOS, 0);
-	spin_unlock_irqrestore(&v3d->job_lock, irqflags);
+	spin_unlock_irqrestore(&queue->queue_lock, irqflags);
 
 	v3d_invalidate_caches(v3d);
 
@@ -277,10 +290,12 @@ static struct dma_fence *v3d_render_job_run(struct drm_sched_job *sched_job)
 	struct drm_device *dev = &v3d->drm;
 	struct dma_fence *fence;
 
-	if (unlikely(job->base.base.s_fence->finished.error))
+	if (unlikely(job->base.base.s_fence->finished.error)) {
+		v3d->queue[V3D_RENDER].active_job = NULL;
 		return NULL;
+	}
 
-	v3d->render_job = job;
+	v3d->queue[V3D_RENDER].active_job = &job->base;
 
 	/* Can we avoid this flush?  We need to be careful of
 	 * scheduling, though -- imagine job0 rendering to texture and
@@ -323,10 +338,12 @@ v3d_tfu_job_run(struct drm_sched_job *sched_job)
 	struct drm_device *dev = &v3d->drm;
 	struct dma_fence *fence;
 
-	if (unlikely(job->base.base.s_fence->finished.error))
+	if (unlikely(job->base.base.s_fence->finished.error)) {
+		v3d->queue[V3D_TFU].active_job = NULL;
 		return NULL;
+	}
 
-	v3d->tfu_job = job;
+	v3d->queue[V3D_TFU].active_job = &job->base;
 
 	fence = v3d_fence_create(v3d, V3D_TFU);
 	if (IS_ERR(fence))
@@ -345,11 +362,11 @@ v3d_tfu_job_run(struct drm_sched_job *sched_job)
 	V3D_WRITE(V3D_TFU_ICA(v3d->ver), job->args.ica);
 	V3D_WRITE(V3D_TFU_IUA(v3d->ver), job->args.iua);
 	V3D_WRITE(V3D_TFU_IOA(v3d->ver), job->args.ioa);
-	if (v3d->ver >= 71)
+	if (v3d->ver >= V3D_GEN_71)
 		V3D_WRITE(V3D_V7_TFU_IOC, job->args.v71.ioc);
 	V3D_WRITE(V3D_TFU_IOS(v3d->ver), job->args.ios);
 	V3D_WRITE(V3D_TFU_COEF0(v3d->ver), job->args.coef[0]);
-	if (v3d->ver >= 71 || (job->args.coef[0] & V3D_TFU_COEF0_USECOEF)) {
+	if (v3d->ver >= V3D_GEN_71 || (job->args.coef[0] & V3D_TFU_COEF0_USECOEF)) {
 		V3D_WRITE(V3D_TFU_COEF1(v3d->ver), job->args.coef[1]);
 		V3D_WRITE(V3D_TFU_COEF2(v3d->ver), job->args.coef[2]);
 		V3D_WRITE(V3D_TFU_COEF3(v3d->ver), job->args.coef[3]);
@@ -369,10 +386,12 @@ v3d_csd_job_run(struct drm_sched_job *sched_job)
 	struct dma_fence *fence;
 	int i, csd_cfg0_reg;
 
-	if (unlikely(job->base.base.s_fence->finished.error))
+	if (unlikely(job->base.base.s_fence->finished.error)) {
+		v3d->queue[V3D_CSD].active_job = NULL;
 		return NULL;
+	}
 
-	v3d->csd_job = job;
+	v3d->queue[V3D_CSD].active_job = &job->base;
 
 	v3d_invalidate_caches(v3d);
 
@@ -398,7 +417,7 @@ v3d_csd_job_run(struct drm_sched_job *sched_job)
 	 *
 	 * XXX: Set the CFG7 register
 	 */
-	if (v3d->ver >= 71)
+	if (v3d->ver >= V3D_GEN_71)
 		V3D_CORE_WRITE(0, V3D_V7_CSD_QUEUED_CFG7, 0);
 
 	/* CFG0 write kicks off the job. */
@@ -556,7 +575,7 @@ static void
 v3d_reset_performance_queries(struct v3d_cpu_job *job)
 {
 	struct v3d_performance_query_info *performance_query = &job->performance_query;
-	struct v3d_file_priv *v3d_priv = job->base.file->driver_priv;
+	struct v3d_file_priv *v3d_priv = job->base.file_priv;
 	struct v3d_dev *v3d = job->base.v3d;
 	struct v3d_perfmon *perfmon;
 
@@ -586,7 +605,7 @@ v3d_write_performance_query_result(struct v3d_cpu_job *job, void *data,
 {
 	struct v3d_performance_query_info *performance_query =
 						&job->performance_query;
-	struct v3d_file_priv *v3d_priv = job->base.file->driver_priv;
+	struct v3d_file_priv *v3d_priv = job->base.file_priv;
 	struct v3d_performance_query *perf_query =
 			&performance_query->queries[query];
 	struct v3d_dev *v3d = job->base.v3d;
@@ -669,8 +688,6 @@ v3d_cpu_job_run(struct drm_sched_job *sched_job)
 	struct v3d_cpu_job *job = to_cpu_job(sched_job);
 	struct v3d_dev *v3d = job->base.v3d;
 
-	v3d->cpu_job = job;
-
 	if (job->job_type >= ARRAY_SIZE(cpu_job_function)) {
 		DRM_DEBUG_DRIVER("Unknown CPU job: %d\n", job->job_type);
 		return NULL;
diff --git a/drivers/gpu/drm/v3d/v3d_submit.c b/drivers/gpu/drm/v3d/v3d_submit.c
index d607aa9c4ec2..cecc1444e5e8 100644
--- a/drivers/gpu/drm/v3d/v3d_submit.c
+++ b/drivers/gpu/drm/v3d/v3d_submit.c
@@ -5,16 +5,58 @@
  */
 
 #include <drm/drm_syncobj.h>
+#include <linux/clk.h>
 
 #include "v3d_drv.h"
 #include "v3d_regs.h"
 #include "v3d_trace.h"
 
+static void
+v3d_clock_down_work(struct work_struct *work)
+{
+	struct v3d_dev *v3d =
+		container_of(work, struct v3d_dev, clk_down_work.work);
+	int ret;
+
+	ret = clk_set_min_rate(v3d->clk, v3d->clk_down_rate);
+	v3d->clk_up = false;
+	WARN_ON_ONCE(ret != 0);
+}
+
+static void
+v3d_clock_up_get(struct v3d_dev *v3d)
+{
+	mutex_lock(&v3d->clk_lock);
+	if (v3d->clk_refcount++ == 0) {
+		cancel_delayed_work_sync(&v3d->clk_down_work);
+		if (!v3d->clk_up)  {
+			int ret;
+
+			ret = clk_set_min_rate(v3d->clk, v3d->clk_up_rate);
+			WARN_ON_ONCE(ret != 0);
+			v3d->clk_up = true;
+		}
+	}
+	mutex_unlock(&v3d->clk_lock);
+}
+
+static void
+v3d_clock_up_put(struct v3d_dev *v3d)
+{
+	mutex_lock(&v3d->clk_lock);
+	if (--v3d->clk_refcount == 0) {
+		schedule_delayed_work(&v3d->clk_down_work,
+			msecs_to_jiffies(100));
+	}
+	mutex_unlock(&v3d->clk_lock);
+}
+
 /* Takes the reservation lock on all the BOs being referenced, so that
- * at queue submit time we can update the reservations.
+ * we can attach fences and update the reservations after pushing the job
+ * to the queue.
  *
  * We don't lock the RCL the tile alloc/state BOs, or overflow memory
- * (all of which are on exec->unref_list).  They're entirely private
+ * (all of which are on render->unref_list). They're entirely private
  * to v3d, so we don't attach dma-buf fences to them.
  */
 static int
@@ -55,11 +97,11 @@ v3d_lock_bo_reservations(struct v3d_job *job,
  * @bo_count: Number of GEM handles passed in
  *
  * The command validator needs to reference BOs by their index within
- * the submitted job's BO list.  This does the validation of the job's
+ * the submitted job's BO list. This does the validation of the job's
  * BO list and reference counting for the lifetime of the job.
  *
  * Note that this function doesn't need to unreference the BOs on
- * failure, because that will happen at v3d_exec_cleanup() time.
+ * failure, because that will happen at `v3d_job_free()`.
  */
 static int
 v3d_lookup_bos(struct drm_device *dev,
@@ -84,9 +126,10 @@ v3d_lookup_bos(struct drm_device *dev,
 }
 
 static void
-v3d_job_free(struct kref *ref)
+v3d_job_free_common(struct v3d_job *job,
+		    bool is_gpu_job)
 {
-	struct v3d_job *job = container_of(ref, struct v3d_job, refcount);
+	struct v3d_dev *v3d = job->v3d;
 	int i;
 
 	if (job->bo) {
@@ -98,12 +141,31 @@ v3d_job_free(struct kref *ref)
 	dma_fence_put(job->irq_fence);
 	dma_fence_put(job->done_fence);
 
+	if (is_gpu_job)
+		v3d_clock_up_put(v3d);
+
 	if (job->perfmon)
 		v3d_perfmon_put(job->perfmon);
 
 	kfree(job);
 }
 
+static void
+v3d_job_free(struct kref *ref)
+{
+	struct v3d_job *job = container_of(ref, struct v3d_job, refcount);
+
+	v3d_job_free_common(job, true);
+}
+
+static void
+v3d_cpu_job_free(struct kref *ref)
+{
+	struct v3d_job *job = container_of(ref, struct v3d_job, refcount);
+
+	v3d_job_free_common(job, false);
+}
+
 static void
 v3d_render_job_free(struct kref *ref)
 {
@@ -165,7 +227,7 @@ v3d_job_init(struct v3d_dev *v3d, struct drm_file *file_priv,
 
 	job->v3d = v3d;
 	job->free = free;
-	job->file = file_priv;
+	job->file_priv = v3d_priv;
 
 	ret = drm_sched_job_init(&job->base, &v3d_priv->sched_entity[queue],
 				 1, v3d_priv);
@@ -198,6 +260,8 @@ v3d_job_init(struct v3d_dev *v3d, struct drm_file *file_priv,
 		if (ret && ret != -ENOENT)
 			goto fail_deps;
 	}
+	if (queue != V3D_CPU)
+		v3d_clock_up_get(v3d);
 
 	kref_init(&job->refcount);
 
@@ -981,6 +1045,11 @@ v3d_submit_cl_ioctl(struct drm_device *dev, void *data,
 		goto fail;
 
 	if (args->perfmon_id) {
+		if (v3d->global_perfmon) {
+			ret = -EAGAIN;
+			goto fail_perfmon;
+		}
+
 		render->base.perfmon = v3d_perfmon_find(v3d_priv,
 							args->perfmon_id);
 
@@ -1196,6 +1265,11 @@ v3d_submit_csd_ioctl(struct drm_device *dev, void *data,
 		goto fail;
 
 	if (args->perfmon_id) {
+		if (v3d->global_perfmon) {
+			ret = -EAGAIN;
+			goto fail_perfmon;
+		}
+
 		job->base.perfmon = v3d_perfmon_find(v3d_priv,
 						     args->perfmon_id);
 		if (!job->base.perfmon) {
@@ -1305,7 +1379,7 @@ v3d_submit_cpu_ioctl(struct drm_device *dev, void *data,
 	trace_v3d_submit_cpu_ioctl(&v3d->drm, cpu_job->job_type);
 
 	ret = v3d_job_init(v3d, file_priv, &cpu_job->base,
-			   v3d_job_free, 0, &se, V3D_CPU);
+			   v3d_cpu_job_free, 0, &se, V3D_CPU);
 	if (ret) {
 		v3d_job_deallocate((void *)&cpu_job);
 		goto fail;
@@ -1393,3 +1467,14 @@ v3d_submit_cpu_ioctl(struct drm_device *dev, void *data,
 
 	return ret;
 }
+
+void v3d_submit_init(struct drm_device *dev) {
+	struct v3d_dev *v3d = to_v3d_dev(dev);
+
+	mutex_init(&v3d->clk_lock);
+	INIT_DELAYED_WORK(&v3d->clk_down_work, v3d_clock_down_work);
+
+	/* kick the clock so firmware knows we are using firmware clock interface */
+	v3d_clock_up_get(v3d);
+	v3d_clock_up_put(v3d);
+}
\ No newline at end of file
diff --git a/drivers/gpu/drm/vc4/Makefile b/drivers/gpu/drm/vc4/Makefile
index c41f89a15a55..823ffbe0b78d 100644
--- a/drivers/gpu/drm/vc4/Makefile
+++ b/drivers/gpu/drm/vc4/Makefile
@@ -9,6 +9,7 @@ vc4-y := \
 	vc4_dpi.o \
 	vc4_dsi.o \
 	vc4_fence.o \
+	vc4_firmware_kms.o \
 	vc4_kms.o \
 	vc4_gem.o \
 	vc4_hdmi.o \
@@ -30,7 +31,8 @@ vc4-$(CONFIG_DRM_VC4_KUNIT_TEST) += \
 	tests/vc4_mock_crtc.o \
 	tests/vc4_mock_output.o \
 	tests/vc4_mock_plane.o \
-	tests/vc4_test_pv_muxing.o
+	tests/vc4_test_pv_muxing.o \
+	tests/vc4_test_lbm_size.o
 
 vc4-$(CONFIG_DEBUG_FS) += vc4_debugfs.o
 
diff --git a/drivers/gpu/drm/vc4/tests/vc4_mock.c b/drivers/gpu/drm/vc4/tests/vc4_mock.c
index 922849dd4b47..be427069f165 100644
--- a/drivers/gpu/drm/vc4/tests/vc4_mock.c
+++ b/drivers/gpu/drm/vc4/tests/vc4_mock.c
@@ -51,8 +51,8 @@ struct vc4_mock_desc {
 
 static const struct vc4_mock_desc vc4_mock =
 	VC4_MOCK_DESC(
-		VC4_MOCK_CRTC_DESC(&vc4_txp_crtc_data,
-				   VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_TXP,
+		VC4_MOCK_CRTC_DESC(&bcm2835_txp_data.base,
+				   VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_TXP0,
 							DRM_MODE_ENCODER_VIRTUAL,
 							DRM_MODE_CONNECTOR_WRITEBACK)),
 		VC4_MOCK_PIXELVALVE_DESC(&bcm2835_pv0_data,
@@ -77,8 +77,8 @@ static const struct vc4_mock_desc vc4_mock =
 
 static const struct vc4_mock_desc vc5_mock =
 	VC4_MOCK_DESC(
-		VC4_MOCK_CRTC_DESC(&vc4_txp_crtc_data,
-				   VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_TXP,
+		VC4_MOCK_CRTC_DESC(&bcm2835_txp_data.base,
+				   VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_TXP0,
 							DRM_MODE_ENCODER_VIRTUAL,
 							DRM_MODE_CONNECTOR_WRITEBACK)),
 		VC4_MOCK_PIXELVALVE_DESC(&bcm2711_pv0_data,
@@ -106,6 +106,26 @@ static const struct vc4_mock_desc vc5_mock =
 							      DRM_MODE_CONNECTOR_HDMIA)),
 );
 
+static const struct vc4_mock_desc vc6_mock =
+	VC4_MOCK_DESC(
+		VC4_MOCK_CRTC_DESC(&bcm2712_mop_data.base,
+				   VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_TXP0,
+							DRM_MODE_ENCODER_VIRTUAL,
+							DRM_MODE_CONNECTOR_WRITEBACK)),
+		VC4_MOCK_CRTC_DESC(&bcm2712_moplet_data.base,
+				   VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_TXP1,
+							DRM_MODE_ENCODER_VIRTUAL,
+							DRM_MODE_CONNECTOR_WRITEBACK)),
+		VC4_MOCK_PIXELVALVE_DESC(&bcm2712_pv0_data,
+					 VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_HDMI0,
+							      DRM_MODE_ENCODER_TMDS,
+							      DRM_MODE_CONNECTOR_HDMIA)),
+		VC4_MOCK_PIXELVALVE_DESC(&bcm2712_pv1_data,
+					 VC4_MOCK_OUTPUT_DESC(VC4_ENCODER_TYPE_HDMI1,
+							      DRM_MODE_ENCODER_TMDS,
+							      DRM_MODE_CONNECTOR_HDMIA)),
+);
+
 static int __build_one_pipe(struct kunit *test, struct drm_device *drm,
 			    const struct vc4_mock_pipe_desc *pipe)
 {
@@ -157,13 +177,31 @@ KUNIT_DEFINE_ACTION_WRAPPER(kunit_action_drm_dev_unregister,
 
 static struct vc4_dev *__mock_device(struct kunit *test, enum vc4_gen gen)
 {
+	const struct vc4_mock_desc *desc;
+	const struct drm_driver *drv;
 	struct drm_device *drm;
-	const struct drm_driver *drv = (gen == VC4_GEN_5) ? &vc5_drm_driver : &vc4_drm_driver;
-	const struct vc4_mock_desc *desc = (gen == VC4_GEN_5) ? &vc5_mock : &vc4_mock;
 	struct vc4_dev *vc4;
 	struct device *dev;
 	int ret;
 
+	switch (gen) {
+	case VC4_GEN_4:
+		drv = &vc4_drm_driver;
+		desc = &vc4_mock;
+		break;
+	case VC4_GEN_5:
+		drv = &vc5_drm_driver;
+		desc = &vc5_mock;
+		break;
+	case VC4_GEN_6_C:
+		drv = &vc5_drm_driver;
+		desc = &vc6_mock;
+		break;
+
+	default:
+		return NULL;
+	}
+
 	dev = drm_kunit_helper_alloc_device(test);
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev);
 
@@ -175,7 +213,7 @@ static struct vc4_dev *__mock_device(struct kunit *test, enum vc4_gen gen)
 	vc4->dev = dev;
 	vc4->gen = gen;
 
-	vc4->hvs = __vc4_hvs_alloc(vc4, NULL);
+	vc4->hvs = __vc4_hvs_alloc(vc4, NULL, NULL);
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, vc4->hvs);
 
 	drm = &vc4->base;
@@ -205,3 +243,8 @@ struct vc4_dev *vc5_mock_device(struct kunit *test)
 {
 	return __mock_device(test, VC4_GEN_5);
 }
+
+struct vc4_dev *vc6_mock_device(struct kunit *test)
+{
+	return __mock_device(test, VC4_GEN_6_C);
+}
diff --git a/drivers/gpu/drm/vc4/tests/vc4_mock.h b/drivers/gpu/drm/vc4/tests/vc4_mock.h
index 002b6218960c..37f6db2e4ff8 100644
--- a/drivers/gpu/drm/vc4/tests/vc4_mock.h
+++ b/drivers/gpu/drm/vc4/tests/vc4_mock.h
@@ -7,9 +7,9 @@
 
 static inline
 struct drm_crtc *vc4_find_crtc_for_encoder(struct kunit *test,
-					   struct drm_device *drm,
 					   struct drm_encoder *encoder)
 {
+	struct drm_device *drm = encoder->dev;
 	struct drm_crtc *crtc;
 
 	KUNIT_ASSERT_EQ(test, hweight32(encoder->possible_crtcs), 1);
@@ -21,9 +21,28 @@ struct drm_crtc *vc4_find_crtc_for_encoder(struct kunit *test,
 	return NULL;
 }
 
+static inline
+struct drm_plane *vc4_mock_find_plane_for_crtc(struct kunit *test,
+					       struct drm_crtc *crtc)
+{
+	struct drm_device *drm = crtc->dev;
+	struct drm_plane *plane;
+
+	drm_for_each_plane(plane, drm)
+		if (plane->possible_crtcs & drm_crtc_mask(crtc))
+			return plane;
+
+	return NULL;
+}
+
 struct drm_plane *vc4_dummy_plane(struct kunit *test, struct drm_device *drm,
 				  enum drm_plane_type type);
 
+struct drm_plane *
+vc4_mock_atomic_add_plane(struct kunit *test,
+			  struct drm_atomic_state *state,
+			  struct drm_crtc *crtc);
+
 struct vc4_dummy_crtc {
 	struct vc4_crtc crtc;
 };
@@ -50,10 +69,12 @@ struct vc4_dummy_output *vc4_dummy_output(struct kunit *test,
 
 struct vc4_dev *vc4_mock_device(struct kunit *test);
 struct vc4_dev *vc5_mock_device(struct kunit *test);
+struct vc4_dev *vc6_mock_device(struct kunit *test);
 
-int vc4_mock_atomic_add_output(struct kunit *test,
-			       struct drm_atomic_state *state,
-			       enum vc4_encoder_type type);
+struct vc4_dummy_output *
+vc4_mock_atomic_add_output(struct kunit *test,
+			   struct drm_atomic_state *state,
+			   enum vc4_encoder_type type);
 int vc4_mock_atomic_del_output(struct kunit *test,
 			       struct drm_atomic_state *state,
 			       enum vc4_encoder_type type);
diff --git a/drivers/gpu/drm/vc4/tests/vc4_mock_output.c b/drivers/gpu/drm/vc4/tests/vc4_mock_output.c
index f0ddc223c1f8..9715b808e2da 100644
--- a/drivers/gpu/drm/vc4/tests/vc4_mock_output.c
+++ b/drivers/gpu/drm/vc4/tests/vc4_mock_output.c
@@ -61,9 +61,10 @@ static const struct drm_display_mode default_mode = {
 	DRM_SIMPLE_MODE(640, 480, 64, 48)
 };
 
-int vc4_mock_atomic_add_output(struct kunit *test,
-			       struct drm_atomic_state *state,
-			       enum vc4_encoder_type type)
+struct vc4_dummy_output *
+vc4_mock_atomic_add_output(struct kunit *test,
+			   struct drm_atomic_state *state,
+			   enum vc4_encoder_type type)
 {
 	struct drm_device *drm = state->dev;
 	struct drm_connector_state *conn_state;
@@ -76,33 +77,33 @@ int vc4_mock_atomic_add_output(struct kunit *test,
 
 	encoder = vc4_find_encoder_by_type(drm, type);
 	if (!encoder)
-		return -ENODEV;
+		return ERR_PTR(-ENODEV);
 
-	crtc = vc4_find_crtc_for_encoder(test, drm, encoder);
+	crtc = vc4_find_crtc_for_encoder(test, encoder);
 	if (!crtc)
-		return -ENODEV;
+		return ERR_PTR(-ENODEV);
 
 	output = encoder_to_vc4_dummy_output(encoder);
 	conn = &output->connector;
 	conn_state = drm_atomic_get_connector_state(state, conn);
 	if (IS_ERR(conn_state))
-		return PTR_ERR(conn_state);
+		return ERR_CAST(conn_state);
 
 	ret = drm_atomic_set_crtc_for_connector(conn_state, crtc);
 	if (ret)
-		return ret;
+		return ERR_PTR(ret);
 
 	crtc_state = drm_atomic_get_crtc_state(state, crtc);
 	if (IS_ERR(crtc_state))
-		return PTR_ERR(crtc_state);
+		return ERR_CAST(crtc_state);
 
 	ret = drm_atomic_set_mode_for_crtc(crtc_state, &default_mode);
 	if (ret)
-		return ret;
+		return ERR_PTR(ret);
 
 	crtc_state->active = true;
 
-	return 0;
+	return output;
 }
 
 int vc4_mock_atomic_del_output(struct kunit *test,
@@ -122,7 +123,7 @@ int vc4_mock_atomic_del_output(struct kunit *test,
 	if (!encoder)
 		return -ENODEV;
 
-	crtc = vc4_find_crtc_for_encoder(test, drm, encoder);
+	crtc = vc4_find_crtc_for_encoder(test, encoder);
 	if (!crtc)
 		return -ENODEV;
 
diff --git a/drivers/gpu/drm/vc4/tests/vc4_mock_plane.c b/drivers/gpu/drm/vc4/tests/vc4_mock_plane.c
index 14357db82238..32e2a57de378 100644
--- a/drivers/gpu/drm/vc4/tests/vc4_mock_plane.c
+++ b/drivers/gpu/drm/vc4/tests/vc4_mock_plane.c
@@ -1,12 +1,31 @@
 // SPDX-License-Identifier: GPL-2.0
 
+#include <drm/drm_modeset_helper_vtables.h>
 #include <drm/drm_kunit_helpers.h>
+#include <drm/drm_atomic_uapi.h>
 #include <drm/drm_plane.h>
 
 #include <kunit/test.h>
 
 #include "vc4_mock.h"
 
+static const struct drm_plane_helper_funcs vc4_dummy_plane_helper_funcs = {
+	.atomic_check = vc4_plane_atomic_check,
+};
+
+static const struct drm_plane_funcs vc4_dummy_plane_funcs = {
+	.atomic_destroy_state	= vc4_plane_destroy_state,
+	.atomic_duplicate_state	= vc4_plane_duplicate_state,
+	.reset			= vc4_plane_reset,
+};
+
+static const uint32_t vc4_dummy_plane_formats[] = {
+	DRM_FORMAT_ARGB8888,
+	DRM_FORMAT_XRGB8888,
+	DRM_FORMAT_YUV420,
+	DRM_FORMAT_YUV422,
+};
+
 struct drm_plane *vc4_dummy_plane(struct kunit *test, struct drm_device *drm,
 				  enum drm_plane_type type)
 {
@@ -15,11 +34,33 @@ struct drm_plane *vc4_dummy_plane(struct kunit *test, struct drm_device *drm,
 	KUNIT_ASSERT_EQ(test, type, DRM_PLANE_TYPE_PRIMARY);
 
 	plane = drm_kunit_helper_create_primary_plane(test, drm,
-						      NULL,
-						      NULL,
-						      NULL, 0,
+						      &vc4_dummy_plane_funcs,
+						      &vc4_dummy_plane_helper_funcs,
+						      vc4_dummy_plane_formats,
+						      ARRAY_SIZE(vc4_dummy_plane_formats),
 						      NULL);
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane);
 
 	return plane;
 }
+
+struct drm_plane *
+vc4_mock_atomic_add_plane(struct kunit *test,
+			  struct drm_atomic_state *state,
+			  struct drm_crtc *crtc)
+{
+	struct drm_plane_state *plane_state;
+	struct drm_plane *plane;
+	int ret;
+
+	plane = vc4_mock_find_plane_for_crtc(test, crtc);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane);
+
+	plane_state = drm_atomic_get_plane_state(state, plane);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_state);
+
+	ret = drm_atomic_set_crtc_for_plane(plane_state, crtc);
+	KUNIT_EXPECT_EQ(test, ret, 0);
+
+	return plane;
+}
diff --git a/drivers/gpu/drm/vc4/tests/vc4_test_lbm_size.c b/drivers/gpu/drm/vc4/tests/vc4_test_lbm_size.c
new file mode 100644
index 000000000000..122c2664b093
--- /dev/null
+++ b/drivers/gpu/drm/vc4/tests/vc4_test_lbm_size.c
@@ -0,0 +1,314 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_atomic_uapi.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_kunit_helpers.h>
+
+#include "../../drm_crtc_internal.h"
+#include "../../drm_internal.h"
+
+#include <kunit/test.h>
+
+#include "../vc4_drv.h"
+
+#include "vc4_mock.h"
+
+u32 vc4_lbm_size(struct drm_plane_state *state);
+
+struct vc4_lbm_size_priv {
+	struct vc4_dev *vc4;
+	struct drm_file *file;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_atomic_state *state;
+};
+
+struct vc4_lbm_size_param {
+	unsigned int src_w, src_h;
+	unsigned int crtc_w, crtc_h;
+	bool forced_alpha;
+	u32 fourcc;
+	enum vc4_scaling_mode expected_x_scaling[2];
+	enum vc4_scaling_mode expected_y_scaling[2];
+	unsigned int expected_lbm_size;
+};
+
+static const struct vc4_lbm_size_param vc4_test_lbm_size_params[] = {
+	{
+		.src_w = 256,
+		.crtc_w = 256,
+		.src_h = 256,
+		.crtc_h = 512,
+		.fourcc = DRM_FORMAT_ARGB8888,
+		.expected_x_scaling = { VC4_SCALING_NONE, },
+		.expected_y_scaling = { VC4_SCALING_PPF, },
+		.expected_lbm_size = 32,
+	},
+	{
+		.src_w = 256,
+		.crtc_w = 179,
+		.src_h = 256,
+		.crtc_h = 512,
+		.fourcc = DRM_FORMAT_ARGB8888,
+		.expected_x_scaling = { VC4_SCALING_PPF, },
+		.expected_y_scaling = { VC4_SCALING_PPF, },
+		.expected_lbm_size = 23,
+	},
+	{
+		.src_w = 256,
+		.crtc_w = 256,
+		.src_h = 256,
+		.crtc_h = 512,
+		.fourcc = DRM_FORMAT_XRGB8888,
+		.expected_x_scaling = { VC4_SCALING_NONE, },
+		.expected_y_scaling = { VC4_SCALING_PPF, },
+		.expected_lbm_size = 24,
+	},
+	{
+		.src_w = 100,
+		.crtc_w = 73,
+		.src_h = 100,
+		.crtc_h = 73,
+		.fourcc = DRM_FORMAT_XRGB8888,
+		.expected_x_scaling = { VC4_SCALING_PPF, },
+		.expected_y_scaling = { VC4_SCALING_PPF, },
+		.expected_lbm_size = 8,
+	},
+	{
+		.src_w = 256,
+		.crtc_w = 256,
+		.src_h = 256,
+		.crtc_h = 512,
+		.forced_alpha = true,
+		.fourcc = DRM_FORMAT_ARGB8888,
+		.expected_x_scaling = { VC4_SCALING_NONE, },
+		.expected_y_scaling = { VC4_SCALING_PPF, },
+		.expected_lbm_size = 24,
+	},
+	{
+		.src_w = 100,
+		.crtc_w = 73,
+		.src_h = 100,
+		.crtc_h = 73,
+		.forced_alpha = true,
+		.fourcc = DRM_FORMAT_ARGB8888,
+		.expected_x_scaling = { VC4_SCALING_PPF, },
+		.expected_y_scaling = { VC4_SCALING_PPF, },
+		.expected_lbm_size = 8,
+	},
+	{
+		.src_w = 256,
+		.crtc_w = 94,
+		.src_h = 256,
+		.crtc_h = 94,
+		.fourcc = DRM_FORMAT_ARGB8888,
+		.expected_x_scaling = { VC4_SCALING_TPZ, },
+		.expected_y_scaling = { VC4_SCALING_TPZ, },
+		.expected_lbm_size = 6,
+	},
+
+/*
+ * TODO: Those tests reflect the LBM size calculation examples, but the
+ * driver ends up taking different scaler filters decisions, and thus
+ * doesn't end up with the same sizes. It would be valuable to have
+ * those tests, but the driver doesn't take a bad decision either, so
+ * it's not clear what we should do at this point.
+ */
+#if 0
+	{
+		.src_w = 320,
+		.crtc_w = 320,
+		.src_h = 320,
+		.crtc_h = 320,
+		.fourcc = DRM_FORMAT_YUV420,
+		.expected_x_scaling = { VC4_SCALING_NONE, VC4_SCALING_NONE, },
+		.expected_y_scaling = { VC4_SCALING_NONE, VC4_SCALING_PPF, },
+		.expected_lbm_size = 10,
+	},
+	{
+		.src_w = 512,
+		.crtc_w = 512,
+		.src_h = 512,
+		.crtc_h = 256,
+		.fourcc = DRM_FORMAT_YUV420,
+		.expected_x_scaling = { VC4_SCALING_NONE, VC4_SCALING_NONE, },
+		.expected_y_scaling = { VC4_SCALING_TPZ, VC4_SCALING_NONE, },
+		.expected_lbm_size = 5,
+	},
+	{
+		.src_w = 486,
+		.crtc_w = 157,
+		.src_h = 404,
+		.crtc_h = 929,
+		.fourcc = DRM_FORMAT_YUV422,
+		.expected_x_scaling = { VC4_SCALING_PPF, VC4_SCALING_PPF, },
+		.expected_y_scaling = { VC4_SCALING_PPF, VC4_SCALING_PPF, },
+		.expected_lbm_size = 20,
+	},
+	{
+		.src_w = 320,
+		.crtc_w = 128,
+		.src_h = 176,
+		.crtc_h = 70,
+		.fourcc = DRM_FORMAT_YUV420,
+		.expected_x_scaling = { VC4_SCALING_TPZ, VC4_SCALING_TPZ, },
+		.expected_y_scaling = { VC4_SCALING_TPZ, VC4_SCALING_TPZ, },
+		.expected_lbm_size = 8,
+	},
+#endif
+};
+
+static void vc4_test_lbm_size_desc(const struct vc4_lbm_size_param *t, char *desc)
+{
+	snprintf(desc, KUNIT_PARAM_DESC_SIZE,
+		 "%ux%u to %ux%u %s(%p4cc)",
+		 t->src_w, t->src_h,
+		 t->crtc_w, t->crtc_h,
+		 t->forced_alpha ? "with forced alpha " : "",
+		 &t->fourcc);
+}
+
+KUNIT_ARRAY_PARAM(vc4_test_lbm_size,
+		  vc4_test_lbm_size_params,
+		  vc4_test_lbm_size_desc);
+
+static void drm_vc4_test_vc4_lbm_size(struct kunit *test)
+{
+	const struct vc4_lbm_size_param *params = test->param_value;
+	const struct vc4_lbm_size_priv *priv = test->priv;
+	const struct drm_format_info *info;
+	struct drm_mode_fb_cmd2 fb_req = { };
+	struct drm_atomic_state *state = priv->state;
+	struct vc4_plane_state *vc4_plane_state;
+	struct drm_plane_state *plane_state;
+	struct vc4_dummy_output *output;
+	struct drm_framebuffer *fb;
+	struct drm_plane *plane;
+	struct drm_crtc *crtc;
+	struct vc4_dev *vc4;
+	unsigned int i;
+	int ret;
+
+	info = drm_format_info(params->fourcc);
+	KUNIT_ASSERT_NOT_NULL(test, info);
+
+	output = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI0);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, output);
+
+	crtc = vc4_find_crtc_for_encoder(test, &output->encoder.base);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc);
+
+	plane = vc4_mock_atomic_add_plane(test, state, crtc);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane);
+
+	plane_state = drm_atomic_get_plane_state(state, plane);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, plane_state);
+
+	vc4_plane_state = to_vc4_plane_state(plane_state);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, vc4_plane_state);
+
+	fb_req.pixel_format = params->fourcc;
+	fb_req.width = params->src_w;
+	fb_req.height = params->src_h;
+
+	for (i = 0; i < info->num_planes; i++) {
+		struct drm_mode_create_dumb dumb_args = { };
+
+		dumb_args.width = params->src_w;
+		dumb_args.height = params->src_h;
+		dumb_args.bpp = drm_format_info_bpp(info, i);
+
+		ret = drm_mode_create_dumb(state->dev, &dumb_args, priv->file);
+		KUNIT_ASSERT_EQ(test, ret, 0);
+
+		fb_req.handles[i] = dumb_args.handle;
+		fb_req.pitches[i] = dumb_args.pitch;
+	}
+
+	fb = drm_internal_framebuffer_create(state->dev, &fb_req, priv->file);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fb);
+
+	drm_atomic_set_fb_for_plane(plane_state, fb);
+
+	plane_state->src_x = 0;
+	plane_state->src_y = 0;
+	plane_state->src_h = params->src_h << 16;
+	plane_state->src_w = params->src_w << 16;
+
+	plane_state->crtc_x = 0;
+	plane_state->crtc_y = 0;
+	plane_state->crtc_h = params->crtc_h;
+	plane_state->crtc_w = params->crtc_w;
+
+	if (params->forced_alpha)
+		plane_state->alpha = 128;
+
+	ret = drm_atomic_check_only(state);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	vc4 = to_vc4_dev(state->dev);
+	KUNIT_ASSERT_NOT_NULL(test, vc4);
+	KUNIT_ASSERT_NOT_NULL(test, vc4->hvs);
+	KUNIT_EXPECT_EQ(test,
+			vc4->hvs->lbm_refcounts[vc4_plane_state->lbm_handle].size,
+			params->expected_lbm_size);
+
+	for (i = 0; i < 2; i++) {
+		KUNIT_EXPECT_EQ(test,
+				vc4_plane_state->x_scaling[i],
+				params->expected_x_scaling[i]);
+		KUNIT_EXPECT_EQ(test,
+				vc4_plane_state->y_scaling[i],
+				params->expected_y_scaling[i]);
+	}
+
+	drm_framebuffer_put(fb);
+
+	for (i = 0; i < info->num_planes; i++)
+		drm_mode_destroy_dumb(state->dev, fb_req.handles[i], priv->file);
+}
+
+static struct kunit_case vc4_lbm_size_tests[] = {
+	KUNIT_CASE_PARAM(drm_vc4_test_vc4_lbm_size,
+			 vc4_test_lbm_size_gen_params),
+	{}
+};
+
+static int vc4_lbm_size_test_init(struct kunit *test)
+{
+	struct drm_modeset_acquire_ctx *ctx;
+	struct vc4_lbm_size_priv *priv;
+	struct drm_device *drm;
+	struct vc4_dev *vc4;
+
+	priv = kunit_kzalloc(test, sizeof(*priv), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, priv);
+	test->priv = priv;
+
+	vc4 = vc6_mock_device(test);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, vc4);
+	priv->vc4 = vc4;
+
+	priv->file = drm_file_alloc(priv->vc4->base.primary);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->file);
+
+	ctx = drm_kunit_helper_acquire_ctx_alloc(test);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx);
+
+	drm = &vc4->base;
+	priv->state = drm_kunit_helper_atomic_state_alloc(test, drm, ctx);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, priv->state);
+
+	return 0;
+}
+
+static struct kunit_suite vc4_lbm_size_test_suite = {
+	.name = "vc4-lbm-size",
+	.init = vc4_lbm_size_test_init,
+	.test_cases = vc4_lbm_size_tests,
+};
+
+kunit_test_suite(vc4_lbm_size_test_suite);
diff --git a/drivers/gpu/drm/vc4/tests/vc4_test_pv_muxing.c b/drivers/gpu/drm/vc4/tests/vc4_test_pv_muxing.c
index 61622e951031..9408e2549daf 100644
--- a/drivers/gpu/drm/vc4/tests/vc4_test_pv_muxing.c
+++ b/drivers/gpu/drm/vc4/tests/vc4_test_pv_muxing.c
@@ -90,20 +90,27 @@ static const struct encoder_constraint vc4_encoder_constraints[] = {
 	ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_DSI0, 0),
 	ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_HDMI0, 1),
 	ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_VEC, 1),
-	ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_TXP, 2),
-	ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_DSI1, 2),
+	ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_TXP0, 2),
+	ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_DSI1, 0, 1, 2),
 };
 
 static const struct encoder_constraint vc5_encoder_constraints[] = {
 	ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_DPI, 0),
 	ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_DSI0, 0),
 	ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_VEC, 1),
-	ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_TXP, 0, 2),
+	ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_TXP0, 0, 2),
 	ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_DSI1, 0, 1, 2),
 	ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_HDMI0, 0, 1, 2),
 	ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_HDMI1, 0, 1, 2),
 };
 
+static const struct encoder_constraint vc6_encoder_constraints[] = {
+	ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_HDMI0, 0),
+	ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_HDMI1, 1),
+	ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_TXP1, 1),
+	ENCODER_CONSTRAINT(VC4_ENCODER_TYPE_TXP0, 2),
+};
+
 static bool check_vc4_encoder_constraints(enum vc4_encoder_type type, unsigned int channel)
 {
 	return __check_encoder_constraints(vc4_encoder_constraints,
@@ -118,6 +125,13 @@ static bool check_vc5_encoder_constraints(enum vc4_encoder_type type, unsigned i
 					   type, channel);
 }
 
+static bool check_vc6_encoder_constraints(enum vc4_encoder_type type, unsigned int channel)
+{
+	return __check_encoder_constraints(vc6_encoder_constraints,
+					   ARRAY_SIZE(vc6_encoder_constraints),
+					   type, channel);
+}
+
 static struct vc4_crtc_state *
 get_vc4_crtc_state_for_encoder(struct kunit *test,
 			       const struct drm_atomic_state *state,
@@ -131,7 +145,7 @@ get_vc4_crtc_state_for_encoder(struct kunit *test,
 	encoder = vc4_find_encoder_by_type(drm, type);
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, encoder);
 
-	crtc = vc4_find_crtc_for_encoder(test, drm, encoder);
+	crtc = vc4_find_crtc_for_encoder(test, encoder);
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, crtc);
 
 	new_crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
@@ -195,6 +209,9 @@ static void vc4_test_pv_muxing_desc(const struct pv_muxing_param *t, char *desc)
 #define VC5_PV_MUXING_TEST(_name, ...)		\
 	PV_MUXING_TEST(_name, vc5_mock_device, check_vc5_encoder_constraints, __VA_ARGS__)
 
+#define VC6_PV_MUXING_TEST(_name, ...)		\
+	PV_MUXING_TEST(_name, vc6_mock_device, check_vc6_encoder_constraints, __VA_ARGS__)
+
 static const struct pv_muxing_param vc4_test_pv_muxing_params[] = {
 	VC4_PV_MUXING_TEST("1 output: DSI0",
 			   VC4_ENCODER_TYPE_DSI0),
@@ -207,7 +224,7 @@ static const struct pv_muxing_param vc4_test_pv_muxing_params[] = {
 	VC4_PV_MUXING_TEST("1 output: DSI1",
 			   VC4_ENCODER_TYPE_DSI1),
 	VC4_PV_MUXING_TEST("1 output: TXP",
-			   VC4_ENCODER_TYPE_TXP),
+			   VC4_ENCODER_TYPE_TXP0),
 	VC4_PV_MUXING_TEST("2 outputs: DSI0, HDMI0",
 			   VC4_ENCODER_TYPE_DSI0,
 			   VC4_ENCODER_TYPE_HDMI0),
@@ -219,7 +236,7 @@ static const struct pv_muxing_param vc4_test_pv_muxing_params[] = {
 			   VC4_ENCODER_TYPE_DSI1),
 	VC4_PV_MUXING_TEST("2 outputs: DSI0, TXP",
 			   VC4_ENCODER_TYPE_DSI0,
-			   VC4_ENCODER_TYPE_TXP),
+			   VC4_ENCODER_TYPE_TXP0),
 	VC4_PV_MUXING_TEST("2 outputs: DPI, HDMI0",
 			   VC4_ENCODER_TYPE_DPI,
 			   VC4_ENCODER_TYPE_HDMI0),
@@ -231,19 +248,22 @@ static const struct pv_muxing_param vc4_test_pv_muxing_params[] = {
 			   VC4_ENCODER_TYPE_DSI1),
 	VC4_PV_MUXING_TEST("2 outputs: DPI, TXP",
 			   VC4_ENCODER_TYPE_DPI,
-			   VC4_ENCODER_TYPE_TXP),
+			   VC4_ENCODER_TYPE_TXP0),
 	VC4_PV_MUXING_TEST("2 outputs: HDMI0, DSI1",
 			   VC4_ENCODER_TYPE_HDMI0,
 			   VC4_ENCODER_TYPE_DSI1),
 	VC4_PV_MUXING_TEST("2 outputs: HDMI0, TXP",
 			   VC4_ENCODER_TYPE_HDMI0,
-			   VC4_ENCODER_TYPE_TXP),
+			   VC4_ENCODER_TYPE_TXP0),
 	VC4_PV_MUXING_TEST("2 outputs: VEC, DSI1",
 			   VC4_ENCODER_TYPE_VEC,
 			   VC4_ENCODER_TYPE_DSI1),
+	VC4_PV_MUXING_TEST("2 outputs: TXP, DSI1",
+			   VC4_ENCODER_TYPE_TXP0,
+			   VC4_ENCODER_TYPE_DSI1),
 	VC4_PV_MUXING_TEST("2 outputs: VEC, TXP",
 			   VC4_ENCODER_TYPE_VEC,
-			   VC4_ENCODER_TYPE_TXP),
+			   VC4_ENCODER_TYPE_TXP0),
 	VC4_PV_MUXING_TEST("3 outputs: DSI0, HDMI0, DSI1",
 			   VC4_ENCODER_TYPE_DSI0,
 			   VC4_ENCODER_TYPE_HDMI0,
@@ -251,7 +271,7 @@ static const struct pv_muxing_param vc4_test_pv_muxing_params[] = {
 	VC4_PV_MUXING_TEST("3 outputs: DSI0, HDMI0, TXP",
 			   VC4_ENCODER_TYPE_DSI0,
 			   VC4_ENCODER_TYPE_HDMI0,
-			   VC4_ENCODER_TYPE_TXP),
+			   VC4_ENCODER_TYPE_TXP0),
 	VC4_PV_MUXING_TEST("3 outputs: DSI0, VEC, DSI1",
 			   VC4_ENCODER_TYPE_DSI0,
 			   VC4_ENCODER_TYPE_VEC,
@@ -259,7 +279,7 @@ static const struct pv_muxing_param vc4_test_pv_muxing_params[] = {
 	VC4_PV_MUXING_TEST("3 outputs: DSI0, VEC, TXP",
 			   VC4_ENCODER_TYPE_DSI0,
 			   VC4_ENCODER_TYPE_VEC,
-			   VC4_ENCODER_TYPE_TXP),
+			   VC4_ENCODER_TYPE_TXP0),
 	VC4_PV_MUXING_TEST("3 outputs: DPI, HDMI0, DSI1",
 			   VC4_ENCODER_TYPE_DPI,
 			   VC4_ENCODER_TYPE_HDMI0,
@@ -267,7 +287,7 @@ static const struct pv_muxing_param vc4_test_pv_muxing_params[] = {
 	VC4_PV_MUXING_TEST("3 outputs: DPI, HDMI0, TXP",
 			   VC4_ENCODER_TYPE_DPI,
 			   VC4_ENCODER_TYPE_HDMI0,
-			   VC4_ENCODER_TYPE_TXP),
+			   VC4_ENCODER_TYPE_TXP0),
 	VC4_PV_MUXING_TEST("3 outputs: DPI, VEC, DSI1",
 			   VC4_ENCODER_TYPE_DPI,
 			   VC4_ENCODER_TYPE_VEC,
@@ -275,7 +295,23 @@ static const struct pv_muxing_param vc4_test_pv_muxing_params[] = {
 	VC4_PV_MUXING_TEST("3 outputs: DPI, VEC, TXP",
 			   VC4_ENCODER_TYPE_DPI,
 			   VC4_ENCODER_TYPE_VEC,
-			   VC4_ENCODER_TYPE_TXP),
+			   VC4_ENCODER_TYPE_TXP0),
+	VC4_PV_MUXING_TEST("3 outputs: DSI1, HDMI, TXP",
+			   VC4_ENCODER_TYPE_DSI1,
+			   VC4_ENCODER_TYPE_HDMI0,
+			   VC4_ENCODER_TYPE_TXP0),
+	VC4_PV_MUXING_TEST("3 outputs: DSI1, VEC, TXP",
+			   VC4_ENCODER_TYPE_DSI1,
+			   VC4_ENCODER_TYPE_VEC,
+			   VC4_ENCODER_TYPE_TXP0),
+	VC4_PV_MUXING_TEST("3 outputs: DSI1, DPI, TXP",
+			   VC4_ENCODER_TYPE_DSI1,
+			   VC4_ENCODER_TYPE_DPI,
+			   VC4_ENCODER_TYPE_TXP0),
+	VC4_PV_MUXING_TEST("3 outputs: DSI1, DSI0, TXP",
+			   VC4_ENCODER_TYPE_DSI1,
+			   VC4_ENCODER_TYPE_DSI0,
+			   VC4_ENCODER_TYPE_TXP0),
 };
 
 KUNIT_ARRAY_PARAM(vc4_test_pv_muxing,
@@ -286,9 +322,6 @@ static const struct pv_muxing_param vc4_test_pv_muxing_invalid_params[] = {
 	VC4_PV_MUXING_TEST("DPI/DSI0 Conflict",
 			   VC4_ENCODER_TYPE_DPI,
 			   VC4_ENCODER_TYPE_DSI0),
-	VC4_PV_MUXING_TEST("TXP/DSI1 Conflict",
-			   VC4_ENCODER_TYPE_TXP,
-			   VC4_ENCODER_TYPE_DSI1),
 	VC4_PV_MUXING_TEST("HDMI0/VEC Conflict",
 			   VC4_ENCODER_TYPE_HDMI0,
 			   VC4_ENCODER_TYPE_VEC),
@@ -296,22 +329,22 @@ static const struct pv_muxing_param vc4_test_pv_muxing_invalid_params[] = {
 			   VC4_ENCODER_TYPE_DSI0,
 			   VC4_ENCODER_TYPE_HDMI0,
 			   VC4_ENCODER_TYPE_DSI1,
-			   VC4_ENCODER_TYPE_TXP),
+			   VC4_ENCODER_TYPE_TXP0),
 	VC4_PV_MUXING_TEST("More than 3 outputs: DSI0, VEC, DSI1, TXP",
 			   VC4_ENCODER_TYPE_DSI0,
 			   VC4_ENCODER_TYPE_VEC,
 			   VC4_ENCODER_TYPE_DSI1,
-			   VC4_ENCODER_TYPE_TXP),
+			   VC4_ENCODER_TYPE_TXP0),
 	VC4_PV_MUXING_TEST("More than 3 outputs: DPI, HDMI0, DSI1, TXP",
 			   VC4_ENCODER_TYPE_DPI,
 			   VC4_ENCODER_TYPE_HDMI0,
 			   VC4_ENCODER_TYPE_DSI1,
-			   VC4_ENCODER_TYPE_TXP),
+			   VC4_ENCODER_TYPE_TXP0),
 	VC4_PV_MUXING_TEST("More than 3 outputs: DPI, VEC, DSI1, TXP",
 			   VC4_ENCODER_TYPE_DPI,
 			   VC4_ENCODER_TYPE_VEC,
 			   VC4_ENCODER_TYPE_DSI1,
-			   VC4_ENCODER_TYPE_TXP),
+			   VC4_ENCODER_TYPE_TXP0),
 };
 
 KUNIT_ARRAY_PARAM(vc4_test_pv_muxing_invalid,
@@ -342,7 +375,7 @@ static const struct pv_muxing_param vc5_test_pv_muxing_params[] = {
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("2 outputs: DPI, TXP",
 			   VC4_ENCODER_TYPE_DPI,
-			   VC4_ENCODER_TYPE_TXP),
+			   VC4_ENCODER_TYPE_TXP0),
 	VC5_PV_MUXING_TEST("2 outputs: DPI, VEC",
 			   VC4_ENCODER_TYPE_DPI,
 			   VC4_ENCODER_TYPE_VEC),
@@ -360,7 +393,7 @@ static const struct pv_muxing_param vc5_test_pv_muxing_params[] = {
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("2 outputs: DSI0, TXP",
 			   VC4_ENCODER_TYPE_DSI0,
-			   VC4_ENCODER_TYPE_TXP),
+			   VC4_ENCODER_TYPE_TXP0),
 	VC5_PV_MUXING_TEST("2 outputs: DSI0, VEC",
 			   VC4_ENCODER_TYPE_DSI0,
 			   VC4_ENCODER_TYPE_VEC),
@@ -372,7 +405,7 @@ static const struct pv_muxing_param vc5_test_pv_muxing_params[] = {
 			   VC4_ENCODER_TYPE_VEC),
 	VC5_PV_MUXING_TEST("2 outputs: DSI1, TXP",
 			   VC4_ENCODER_TYPE_DSI1,
-			   VC4_ENCODER_TYPE_TXP),
+			   VC4_ENCODER_TYPE_TXP0),
 	VC5_PV_MUXING_TEST("2 outputs: DSI1, HDMI0",
 			   VC4_ENCODER_TYPE_DSI1,
 			   VC4_ENCODER_TYPE_HDMI0),
@@ -384,7 +417,7 @@ static const struct pv_muxing_param vc5_test_pv_muxing_params[] = {
 			   VC4_ENCODER_TYPE_VEC),
 	VC5_PV_MUXING_TEST("2 outputs: HDMI0, TXP",
 			   VC4_ENCODER_TYPE_HDMI0,
-			   VC4_ENCODER_TYPE_TXP),
+			   VC4_ENCODER_TYPE_TXP0),
 	VC5_PV_MUXING_TEST("2 outputs: HDMI0, HDMI1",
 			   VC4_ENCODER_TYPE_HDMI0,
 			   VC4_ENCODER_TYPE_HDMI1),
@@ -393,14 +426,14 @@ static const struct pv_muxing_param vc5_test_pv_muxing_params[] = {
 			   VC4_ENCODER_TYPE_VEC),
 	VC5_PV_MUXING_TEST("2 outputs: HDMI1, TXP",
 			   VC4_ENCODER_TYPE_HDMI1,
-			   VC4_ENCODER_TYPE_TXP),
+			   VC4_ENCODER_TYPE_TXP0),
 	VC5_PV_MUXING_TEST("2 outputs: TXP, VEC",
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_VEC),
 	VC5_PV_MUXING_TEST("3 outputs: DPI, VEC, TXP",
 			   VC4_ENCODER_TYPE_DPI,
 			   VC4_ENCODER_TYPE_VEC,
-			   VC4_ENCODER_TYPE_TXP),
+			   VC4_ENCODER_TYPE_TXP0),
 	VC5_PV_MUXING_TEST("3 outputs: DPI, VEC, DSI1",
 			   VC4_ENCODER_TYPE_DPI,
 			   VC4_ENCODER_TYPE_VEC,
@@ -415,15 +448,15 @@ static const struct pv_muxing_param vc5_test_pv_muxing_params[] = {
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("3 outputs: DPI, TXP, DSI1",
 			   VC4_ENCODER_TYPE_DPI,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_DSI1),
 	VC5_PV_MUXING_TEST("3 outputs: DPI, TXP, HDMI0",
 			   VC4_ENCODER_TYPE_DPI,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_HDMI0),
 	VC5_PV_MUXING_TEST("3 outputs: DPI, TXP, HDMI1",
 			   VC4_ENCODER_TYPE_DPI,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("3 outputs: DPI, DSI1, HDMI0",
 			   VC4_ENCODER_TYPE_DPI,
@@ -440,7 +473,7 @@ static const struct pv_muxing_param vc5_test_pv_muxing_params[] = {
 	VC5_PV_MUXING_TEST("3 outputs: DSI0, VEC, TXP",
 			   VC4_ENCODER_TYPE_DSI0,
 			   VC4_ENCODER_TYPE_VEC,
-			   VC4_ENCODER_TYPE_TXP),
+			   VC4_ENCODER_TYPE_TXP0),
 	VC5_PV_MUXING_TEST("3 outputs: DSI0, VEC, DSI1",
 			   VC4_ENCODER_TYPE_DSI0,
 			   VC4_ENCODER_TYPE_VEC,
@@ -455,15 +488,15 @@ static const struct pv_muxing_param vc5_test_pv_muxing_params[] = {
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("3 outputs: DSI0, TXP, DSI1",
 			   VC4_ENCODER_TYPE_DSI0,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_DSI1),
 	VC5_PV_MUXING_TEST("3 outputs: DSI0, TXP, HDMI0",
 			   VC4_ENCODER_TYPE_DSI0,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_HDMI0),
 	VC5_PV_MUXING_TEST("3 outputs: DSI0, TXP, HDMI1",
 			   VC4_ENCODER_TYPE_DSI0,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("3 outputs: DSI0, DSI1, HDMI0",
 			   VC4_ENCODER_TYPE_DSI0,
@@ -490,17 +523,17 @@ static const struct pv_muxing_param vc5_test_pv_muxing_invalid_params[] = {
 	VC5_PV_MUXING_TEST("More than 3 outputs: DPI, VEC, TXP, DSI1",
 			   VC4_ENCODER_TYPE_DPI,
 			   VC4_ENCODER_TYPE_VEC,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_DSI1),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DPI, VEC, TXP, HDMI0",
 			   VC4_ENCODER_TYPE_DPI,
 			   VC4_ENCODER_TYPE_VEC,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_HDMI0),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DPI, VEC, TXP, HDMI1",
 			   VC4_ENCODER_TYPE_DPI,
 			   VC4_ENCODER_TYPE_VEC,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DPI, VEC, DSI1, HDMI0",
 			   VC4_ENCODER_TYPE_DPI,
@@ -519,17 +552,17 @@ static const struct pv_muxing_param vc5_test_pv_muxing_invalid_params[] = {
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DPI, TXP, DSI1, HDMI0",
 			   VC4_ENCODER_TYPE_DPI,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_DSI1,
 			   VC4_ENCODER_TYPE_HDMI0),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DPI, TXP, DSI1, HDMI1",
 			   VC4_ENCODER_TYPE_DPI,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_DSI1,
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DPI, TXP, HDMI0, HDMI1",
 			   VC4_ENCODER_TYPE_DPI,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_HDMI0,
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DPI, DSI1, HDMI0, HDMI1",
@@ -540,19 +573,19 @@ static const struct pv_muxing_param vc5_test_pv_muxing_invalid_params[] = {
 	VC5_PV_MUXING_TEST("More than 3 outputs: DPI, VEC, TXP, DSI1, HDMI0",
 			   VC4_ENCODER_TYPE_DPI,
 			   VC4_ENCODER_TYPE_VEC,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_DSI1,
 			   VC4_ENCODER_TYPE_HDMI0),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DPI, VEC, TXP, DSI1, HDMI1",
 			   VC4_ENCODER_TYPE_DPI,
 			   VC4_ENCODER_TYPE_VEC,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_DSI1,
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DPI, VEC, TXP, HDMI0, HDMI1",
 			   VC4_ENCODER_TYPE_DPI,
 			   VC4_ENCODER_TYPE_VEC,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_HDMI0,
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DPI, VEC, DSI1, HDMI0, HDMI1",
@@ -563,24 +596,24 @@ static const struct pv_muxing_param vc5_test_pv_muxing_invalid_params[] = {
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DPI, TXP, DSI1, HDMI0, HDMI1",
 			   VC4_ENCODER_TYPE_DPI,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_DSI1,
 			   VC4_ENCODER_TYPE_HDMI0,
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, VEC, TXP, DSI1",
 			   VC4_ENCODER_TYPE_DSI0,
 			   VC4_ENCODER_TYPE_VEC,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_DSI1),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, VEC, TXP, HDMI0",
 			   VC4_ENCODER_TYPE_DSI0,
 			   VC4_ENCODER_TYPE_VEC,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_HDMI0),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, VEC, TXP, HDMI1",
 			   VC4_ENCODER_TYPE_DSI0,
 			   VC4_ENCODER_TYPE_VEC,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, VEC, DSI1, HDMI0",
 			   VC4_ENCODER_TYPE_DSI0,
@@ -599,17 +632,17 @@ static const struct pv_muxing_param vc5_test_pv_muxing_invalid_params[] = {
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, TXP, DSI1, HDMI0",
 			   VC4_ENCODER_TYPE_DSI0,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_DSI1,
 			   VC4_ENCODER_TYPE_HDMI0),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, TXP, DSI1, HDMI1",
 			   VC4_ENCODER_TYPE_DSI0,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_DSI1,
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, TXP, HDMI0, HDMI1",
 			   VC4_ENCODER_TYPE_DSI0,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_HDMI0,
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, DSI1, HDMI0, HDMI1",
@@ -620,19 +653,19 @@ static const struct pv_muxing_param vc5_test_pv_muxing_invalid_params[] = {
 	VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, VEC, TXP, DSI1, HDMI0",
 			   VC4_ENCODER_TYPE_DSI0,
 			   VC4_ENCODER_TYPE_VEC,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_DSI1,
 			   VC4_ENCODER_TYPE_HDMI0),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, VEC, TXP, DSI1, HDMI1",
 			   VC4_ENCODER_TYPE_DSI0,
 			   VC4_ENCODER_TYPE_VEC,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_DSI1,
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, VEC, TXP, HDMI0, HDMI1",
 			   VC4_ENCODER_TYPE_DSI0,
 			   VC4_ENCODER_TYPE_VEC,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_HDMI0,
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, VEC, DSI1, HDMI0, HDMI1",
@@ -643,27 +676,27 @@ static const struct pv_muxing_param vc5_test_pv_muxing_invalid_params[] = {
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, TXP, DSI1, HDMI0, HDMI1",
 			   VC4_ENCODER_TYPE_DSI0,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_DSI1,
 			   VC4_ENCODER_TYPE_HDMI0,
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("More than 3 outputs: VEC, TXP, DSI1, HDMI0, HDMI1",
 			   VC4_ENCODER_TYPE_VEC,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_DSI1,
 			   VC4_ENCODER_TYPE_HDMI0,
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DPI, VEC, TXP, DSI1, HDMI0, HDMI1",
 			   VC4_ENCODER_TYPE_DPI,
 			   VC4_ENCODER_TYPE_VEC,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_DSI1,
 			   VC4_ENCODER_TYPE_HDMI0,
 			   VC4_ENCODER_TYPE_HDMI1),
 	VC5_PV_MUXING_TEST("More than 3 outputs: DSI0, VEC, TXP, DSI1, HDMI0, HDMI1",
 			   VC4_ENCODER_TYPE_DSI0,
 			   VC4_ENCODER_TYPE_VEC,
-			   VC4_ENCODER_TYPE_TXP,
+			   VC4_ENCODER_TYPE_TXP0,
 			   VC4_ENCODER_TYPE_DSI1,
 			   VC4_ENCODER_TYPE_HDMI0,
 			   VC4_ENCODER_TYPE_HDMI1),
@@ -673,6 +706,54 @@ KUNIT_ARRAY_PARAM(vc5_test_pv_muxing_invalid,
 		  vc5_test_pv_muxing_invalid_params,
 		  vc4_test_pv_muxing_desc);
 
+static const struct pv_muxing_param vc6_test_pv_muxing_params[] = {
+	VC6_PV_MUXING_TEST("1 output: HDMI0",
+			   VC4_ENCODER_TYPE_HDMI0),
+	VC6_PV_MUXING_TEST("1 output: HDMI1",
+			   VC4_ENCODER_TYPE_HDMI1),
+	VC6_PV_MUXING_TEST("1 output: MOPLET",
+			   VC4_ENCODER_TYPE_TXP1),
+	VC6_PV_MUXING_TEST("1 output: MOP",
+			   VC4_ENCODER_TYPE_TXP0),
+	VC6_PV_MUXING_TEST("2 outputs: HDMI0, HDMI1",
+			   VC4_ENCODER_TYPE_HDMI0,
+			   VC4_ENCODER_TYPE_HDMI1),
+	VC6_PV_MUXING_TEST("2 outputs: HDMI0, MOPLET",
+			   VC4_ENCODER_TYPE_HDMI0,
+			   VC4_ENCODER_TYPE_TXP1),
+	VC6_PV_MUXING_TEST("2 outputs: HDMI0, MOP",
+			   VC4_ENCODER_TYPE_HDMI0,
+			   VC4_ENCODER_TYPE_TXP0),
+	VC6_PV_MUXING_TEST("2 outputs: HDMI1, MOP",
+			   VC4_ENCODER_TYPE_HDMI1,
+			   VC4_ENCODER_TYPE_TXP0),
+	VC6_PV_MUXING_TEST("2 outputs: MOPLET, MOP",
+			   VC4_ENCODER_TYPE_TXP1,
+			   VC4_ENCODER_TYPE_TXP0),
+	VC6_PV_MUXING_TEST("3 outputs: HDMI0, HDMI1, MOP",
+			   VC4_ENCODER_TYPE_HDMI0,
+			   VC4_ENCODER_TYPE_HDMI1,
+			   VC4_ENCODER_TYPE_TXP0),
+	VC6_PV_MUXING_TEST("3 outputs: HDMI0, MOPLET, MOP",
+			   VC4_ENCODER_TYPE_HDMI0,
+			   VC4_ENCODER_TYPE_TXP1,
+			   VC4_ENCODER_TYPE_TXP0),
+};
+
+KUNIT_ARRAY_PARAM(vc6_test_pv_muxing,
+		  vc6_test_pv_muxing_params,
+		  vc4_test_pv_muxing_desc);
+
+static const struct pv_muxing_param vc6_test_pv_muxing_invalid_params[] = {
+	VC6_PV_MUXING_TEST("HDMI1/MOPLET Conflict",
+			   VC4_ENCODER_TYPE_HDMI1,
+			   VC4_ENCODER_TYPE_TXP1),
+};
+
+KUNIT_ARRAY_PARAM(vc6_test_pv_muxing_invalid,
+		  vc6_test_pv_muxing_invalid_params,
+		  vc4_test_pv_muxing_desc);
+
 static void drm_vc4_test_pv_muxing(struct kunit *test)
 {
 	const struct pv_muxing_param *params = test->param_value;
@@ -682,10 +763,11 @@ static void drm_vc4_test_pv_muxing(struct kunit *test)
 	int ret;
 
 	for (i = 0; i < params->nencoders; i++) {
+		struct vc4_dummy_output *output;
 		enum vc4_encoder_type enc_type = params->encoders[i];
 
-		ret = vc4_mock_atomic_add_output(test, state, enc_type);
-		KUNIT_ASSERT_EQ(test, ret, 0);
+		output = vc4_mock_atomic_add_output(test, state, enc_type);
+		KUNIT_ASSERT_NOT_ERR_OR_NULL(test, output);
 	}
 
 	ret = drm_atomic_check_only(state);
@@ -711,10 +793,11 @@ static void drm_vc4_test_pv_muxing_invalid(struct kunit *test)
 	int ret;
 
 	for (i = 0; i < params->nencoders; i++) {
+		struct vc4_dummy_output *output;
 		enum vc4_encoder_type enc_type = params->encoders[i];
 
-		ret = vc4_mock_atomic_add_output(test, state, enc_type);
-		KUNIT_ASSERT_EQ(test, ret, 0);
+		output = vc4_mock_atomic_add_output(test, state, enc_type);
+		KUNIT_ASSERT_NOT_ERR_OR_NULL(test, output);
 	}
 
 	ret = drm_atomic_check_only(state);
@@ -775,6 +858,20 @@ static struct kunit_suite vc5_pv_muxing_test_suite = {
 	.test_cases = vc5_pv_muxing_tests,
 };
 
+static struct kunit_case vc6_pv_muxing_tests[] = {
+	KUNIT_CASE_PARAM(drm_vc4_test_pv_muxing,
+			 vc6_test_pv_muxing_gen_params),
+	KUNIT_CASE_PARAM(drm_vc4_test_pv_muxing_invalid,
+			 vc6_test_pv_muxing_invalid_gen_params),
+	{}
+};
+
+static struct kunit_suite vc6_pv_muxing_test_suite = {
+	.name = "vc6-pv-muxing-combinations",
+	.init = vc4_pv_muxing_test_init,
+	.test_cases = vc6_pv_muxing_tests,
+};
+
 /* See
  * https://lore.kernel.org/all/3e113525-aa89-b1e2-56b7-ca55bd41d057@samsung.com/
  * and
@@ -784,6 +881,7 @@ static void drm_test_vc5_pv_muxing_bugs_subsequent_crtc_enable(struct kunit *tes
 {
 	struct drm_modeset_acquire_ctx *ctx;
 	struct drm_atomic_state *state;
+	struct vc4_dummy_output *output;
 	struct vc4_crtc_state *new_vc4_crtc_state;
 	struct vc4_hvs_state *new_hvs_state;
 	unsigned int hdmi0_channel;
@@ -802,8 +900,8 @@ static void drm_test_vc5_pv_muxing_bugs_subsequent_crtc_enable(struct kunit *tes
 	state = drm_kunit_helper_atomic_state_alloc(test, drm, ctx);
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
 
-	ret = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI0);
-	KUNIT_ASSERT_EQ(test, ret, 0);
+	output = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI0);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, output);
 
 	ret = drm_atomic_check_only(state);
 	KUNIT_ASSERT_EQ(test, ret, 0);
@@ -825,8 +923,8 @@ static void drm_test_vc5_pv_muxing_bugs_subsequent_crtc_enable(struct kunit *tes
 	state = drm_kunit_helper_atomic_state_alloc(test, drm, ctx);
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
 
-	ret = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI1);
-	KUNIT_ASSERT_EQ(test, ret, 0);
+	output = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI1);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, output);
 
 	ret = drm_atomic_check_only(state);
 	KUNIT_ASSERT_EQ(test, ret, 0);
@@ -856,6 +954,7 @@ static void drm_test_vc5_pv_muxing_bugs_stable_fifo(struct kunit *test)
 {
 	struct drm_modeset_acquire_ctx *ctx;
 	struct drm_atomic_state *state;
+	struct vc4_dummy_output *output;
 	struct vc4_crtc_state *new_vc4_crtc_state;
 	struct vc4_hvs_state *new_hvs_state;
 	unsigned int old_hdmi0_channel;
@@ -874,11 +973,11 @@ static void drm_test_vc5_pv_muxing_bugs_stable_fifo(struct kunit *test)
 	state = drm_kunit_helper_atomic_state_alloc(test, drm, ctx);
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
 
-	ret = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI0);
-	KUNIT_ASSERT_EQ(test, ret, 0);
+	output = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI0);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, output);
 
-	ret = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI1);
-	KUNIT_ASSERT_EQ(test, ret, 0);
+	output = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI1);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, output);
 
 	ret = drm_atomic_check_only(state);
 	KUNIT_ASSERT_EQ(test, ret, 0);
@@ -951,6 +1050,7 @@ drm_test_vc5_pv_muxing_bugs_subsequent_crtc_enable_too_many_crtc_state(struct ku
 {
 	struct drm_modeset_acquire_ctx *ctx;
 	struct drm_atomic_state *state;
+	struct vc4_dummy_output *output;
 	struct vc4_crtc_state *new_vc4_crtc_state;
 	struct drm_device *drm;
 	struct vc4_dev *vc4;
@@ -966,8 +1066,8 @@ drm_test_vc5_pv_muxing_bugs_subsequent_crtc_enable_too_many_crtc_state(struct ku
 	state = drm_kunit_helper_atomic_state_alloc(test, drm, ctx);
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
 
-	ret = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI0);
-	KUNIT_ASSERT_EQ(test, ret, 0);
+	output = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI0);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, output);
 
 	ret = drm_atomic_check_only(state);
 	KUNIT_ASSERT_EQ(test, ret, 0);
@@ -978,8 +1078,8 @@ drm_test_vc5_pv_muxing_bugs_subsequent_crtc_enable_too_many_crtc_state(struct ku
 	state = drm_kunit_helper_atomic_state_alloc(test, drm, ctx);
 	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, state);
 
-	ret = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI1);
-	KUNIT_ASSERT_EQ(test, ret, 0);
+	output = vc4_mock_atomic_add_output(test, state, VC4_ENCODER_TYPE_HDMI1);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, output);
 
 	ret = drm_atomic_check_only(state);
 	KUNIT_ASSERT_EQ(test, ret, 0);
@@ -1004,5 +1104,6 @@ static struct kunit_suite vc5_pv_muxing_bugs_test_suite = {
 kunit_test_suites(
 	&vc4_pv_muxing_test_suite,
 	&vc5_pv_muxing_test_suite,
+	&vc6_pv_muxing_test_suite,
 	&vc5_pv_muxing_bugs_test_suite
 );
diff --git a/drivers/gpu/drm/vc4/vc4_bo.c b/drivers/gpu/drm/vc4/vc4_bo.c
index 2a85d08b1985..fb450b6a4d44 100644
--- a/drivers/gpu/drm/vc4/vc4_bo.c
+++ b/drivers/gpu/drm/vc4/vc4_bo.c
@@ -251,7 +251,7 @@ void vc4_bo_add_to_purgeable_pool(struct vc4_bo *bo)
 {
 	struct vc4_dev *vc4 = to_vc4_dev(bo->base.base.dev);
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return;
 
 	mutex_lock(&vc4->purgeable.lock);
@@ -265,7 +265,7 @@ static void vc4_bo_remove_from_purgeable_pool_locked(struct vc4_bo *bo)
 {
 	struct vc4_dev *vc4 = to_vc4_dev(bo->base.base.dev);
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return;
 
 	/* list_del_init() is used here because the caller might release
@@ -396,7 +396,7 @@ struct drm_gem_object *vc4_create_object(struct drm_device *dev, size_t size)
 	struct vc4_dev *vc4 = to_vc4_dev(dev);
 	struct vc4_bo *bo;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return ERR_PTR(-ENODEV);
 
 	bo = kzalloc(sizeof(*bo), GFP_KERNEL);
@@ -427,7 +427,7 @@ struct vc4_bo *vc4_bo_create(struct drm_device *dev, size_t unaligned_size,
 	struct drm_gem_dma_object *dma_obj;
 	struct vc4_bo *bo;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return ERR_PTR(-ENODEV);
 
 	if (size == 0)
@@ -496,7 +496,7 @@ int vc4_bo_dumb_create(struct drm_file *file_priv,
 	struct vc4_bo *bo = NULL;
 	int ret;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	ret = vc4_dumb_fixup_args(args);
@@ -622,7 +622,7 @@ int vc4_bo_inc_usecnt(struct vc4_bo *bo)
 	struct vc4_dev *vc4 = to_vc4_dev(bo->base.base.dev);
 	int ret;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	/* Fast path: if the BO is already retained by someone, no need to
@@ -661,7 +661,7 @@ void vc4_bo_dec_usecnt(struct vc4_bo *bo)
 {
 	struct vc4_dev *vc4 = to_vc4_dev(bo->base.base.dev);
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return;
 
 	/* Fast path: if the BO is still retained by someone, no need to test
@@ -783,7 +783,7 @@ int vc4_create_bo_ioctl(struct drm_device *dev, void *data,
 	struct vc4_bo *bo = NULL;
 	int ret;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	ret = vc4_grab_bin_bo(vc4, vc4file);
@@ -813,7 +813,7 @@ int vc4_mmap_bo_ioctl(struct drm_device *dev, void *data,
 	struct drm_vc4_mmap_bo *args = data;
 	struct drm_gem_object *gem_obj;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	gem_obj = drm_gem_object_lookup(file_priv, args->handle);
@@ -839,7 +839,7 @@ vc4_create_shader_bo_ioctl(struct drm_device *dev, void *data,
 	struct vc4_bo *bo = NULL;
 	int ret;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	if (args->size == 0)
@@ -918,7 +918,7 @@ int vc4_set_tiling_ioctl(struct drm_device *dev, void *data,
 	struct vc4_bo *bo;
 	bool t_format;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	if (args->flags != 0)
@@ -964,7 +964,7 @@ int vc4_get_tiling_ioctl(struct drm_device *dev, void *data,
 	struct drm_gem_object *gem_obj;
 	struct vc4_bo *bo;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	if (args->flags != 0 || args->modifier != 0)
@@ -1007,7 +1007,7 @@ int vc4_bo_cache_init(struct drm_device *dev)
 	int ret;
 	int i;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	/* Create the initial set of BO labels that the kernel will
@@ -1071,7 +1071,7 @@ int vc4_label_bo_ioctl(struct drm_device *dev, void *data,
 	struct drm_gem_object *gem_obj;
 	int ret = 0, label;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	if (!args->len)
diff --git a/drivers/gpu/drm/vc4/vc4_crtc.c b/drivers/gpu/drm/vc4/vc4_crtc.c
index 26a7cf7f6465..307a53313669 100644
--- a/drivers/gpu/drm/vc4/vc4_crtc.c
+++ b/drivers/gpu/drm/vc4/vc4_crtc.c
@@ -83,13 +83,22 @@ static unsigned int
 vc4_crtc_get_cob_allocation(struct vc4_dev *vc4, unsigned int channel)
 {
 	struct vc4_hvs *hvs = vc4->hvs;
-	u32 dispbase = HVS_READ(SCALER_DISPBASEX(channel));
+	u32 dispbase, top, base;
+
 	/* Top/base are supposed to be 4-pixel aligned, but the
 	 * Raspberry Pi firmware fills the low bits (which are
 	 * presumably ignored).
 	 */
-	u32 top = VC4_GET_FIELD(dispbase, SCALER_DISPBASEX_TOP) & ~3;
-	u32 base = VC4_GET_FIELD(dispbase, SCALER_DISPBASEX_BASE) & ~3;
+
+	if (vc4->gen >= VC4_GEN_6_C) {
+		dispbase = HVS_READ(SCALER6_DISPX_COB(channel));
+		top = VC4_GET_FIELD(dispbase, SCALER6_DISPX_COB_TOP) & ~3;
+		base = VC4_GET_FIELD(dispbase, SCALER6_DISPX_COB_BASE) & ~3;
+	} else {
+		dispbase = HVS_READ(SCALER_DISPBASEX(channel));
+		top = VC4_GET_FIELD(dispbase, SCALER_DISPBASEX_TOP) & ~3;
+		base = VC4_GET_FIELD(dispbase, SCALER_DISPBASEX_BASE) & ~3;
+	}
 
 	return top - base + 4;
 }
@@ -105,6 +114,7 @@ static bool vc4_crtc_get_scanout_position(struct drm_crtc *crtc,
 	struct vc4_hvs *hvs = vc4->hvs;
 	struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
 	struct vc4_crtc_state *vc4_crtc_state = to_vc4_crtc_state(crtc->state);
+	unsigned int channel = vc4_crtc_state->assigned_channel;
 	unsigned int cob_size;
 	u32 val;
 	int fifo_lines;
@@ -121,7 +131,10 @@ static bool vc4_crtc_get_scanout_position(struct drm_crtc *crtc,
 	 * Read vertical scanline which is currently composed for our
 	 * pixelvalve by the HVS, and also the scaler status.
 	 */
-	val = HVS_READ(SCALER_DISPSTATX(vc4_crtc_state->assigned_channel));
+	if (vc4->gen >= VC4_GEN_6_C)
+		val = HVS_READ(SCALER6_DISPX_STATUS(channel));
+	else
+		val = HVS_READ(SCALER_DISPSTATX(channel));
 
 	/* Get optional system timestamp after query. */
 	if (etime)
@@ -130,18 +143,23 @@ static bool vc4_crtc_get_scanout_position(struct drm_crtc *crtc,
 	/* preempt_enable_rt() should go right here in PREEMPT_RT patchset. */
 
 	/* Vertical position of hvs composed scanline. */
-	*vpos = VC4_GET_FIELD(val, SCALER_DISPSTATX_LINE);
+
+	if (vc4->gen >= VC4_GEN_6_C)
+		*vpos = VC4_GET_FIELD(val, SCALER6_DISPX_STATUS_YLINE);
+	else
+		*vpos = VC4_GET_FIELD(val, SCALER_DISPSTATX_LINE);
+
 	*hpos = 0;
 
 	if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
 		*vpos /= 2;
 
 		/* Use hpos to correct for field offset in interlaced mode. */
-		if (vc4_hvs_get_fifo_frame_count(hvs, vc4_crtc_state->assigned_channel) % 2)
+		if (vc4_hvs_get_fifo_frame_count(hvs, channel) % 2)
 			*hpos += mode->crtc_htotal / 2;
 	}
 
-	cob_size = vc4_crtc_get_cob_allocation(vc4, vc4_crtc_state->assigned_channel);
+	cob_size = vc4_crtc_get_cob_allocation(vc4, channel);
 	/* This is the offset we need for translating hvs -> pv scanout pos. */
 	fifo_lines = cob_size / mode->crtc_hdisplay;
 
@@ -222,6 +240,11 @@ static u32 vc4_get_fifo_full_level(struct vc4_crtc *vc4_crtc, u32 format)
 	const struct vc4_crtc_data *crtc_data = vc4_crtc_to_vc4_crtc_data(vc4_crtc);
 	const struct vc4_pv_data *pv_data = vc4_crtc_to_vc4_pv_data(vc4_crtc);
 	struct vc4_dev *vc4 = to_vc4_dev(vc4_crtc->base.dev);
+
+	/*
+	 * NOTE: Could we use register 0x68 (PV_HW_CFG1) to get the FIFO
+	 * size?
+	 */
 	u32 fifo_len_bytes = pv_data->fifo_depth;
 
 	/*
@@ -338,7 +361,9 @@ static void vc4_crtc_config_pv(struct drm_crtc *crtc, struct drm_encoder *encode
 	bool is_dsi1 = vc4_encoder->type == VC4_ENCODER_TYPE_DSI1;
 	bool is_vec = vc4_encoder->type == VC4_ENCODER_TYPE_VEC;
 	u32 format = is_dsi1 ? PV_CONTROL_FORMAT_DSIV_24 : PV_CONTROL_FORMAT_24;
-	u8 ppc = pv_data->pixels_per_clock;
+	u8 ppc = (mode->flags & DRM_MODE_FLAG_INTERLACE) ?
+			pv_data->pixels_per_clock_int :
+			pv_data->pixels_per_clock;
 
 	u16 vert_bp = mode->crtc_vtotal - mode->crtc_vsync_end;
 	u16 vert_sync = mode->crtc_vsync_end - mode->crtc_vsync_start;
@@ -403,6 +428,8 @@ static void vc4_crtc_config_pv(struct drm_crtc *crtc, struct drm_encoder *encode
 		 */
 		CRTC_WRITE(PV_V_CONTROL,
 			   PV_VCONTROL_CONTINUOUS |
+			   (vc4->gen >= VC4_GEN_6_C && ppc == 1 ?
+					PV_VCONTROL_ODD_TIMING : 0) |
 			   (is_dsi ? PV_VCONTROL_DSI : 0) |
 			   PV_VCONTROL_INTERLACE |
 			   (odd_field_first
@@ -414,6 +441,8 @@ static void vc4_crtc_config_pv(struct drm_crtc *crtc, struct drm_encoder *encode
 	} else {
 		CRTC_WRITE(PV_V_CONTROL,
 			   PV_VCONTROL_CONTINUOUS |
+			   (vc4->gen >= VC4_GEN_6_C && ppc == 1 ?
+					PV_VCONTROL_ODD_TIMING : 0) |
 			   (is_dsi ? PV_VCONTROL_DSI : 0));
 		CRTC_WRITE(PV_VSYNCD_EVEN, 0);
 	}
@@ -428,11 +457,17 @@ static void vc4_crtc_config_pv(struct drm_crtc *crtc, struct drm_encoder *encode
 	if (is_dsi)
 		CRTC_WRITE(PV_HACT_ACT, mode->hdisplay * pixel_rep);
 
-	if (vc4->gen == VC4_GEN_5)
+	if (vc4->gen >= VC4_GEN_5)
 		CRTC_WRITE(PV_MUX_CFG,
 			   VC4_SET_FIELD(PV_MUX_CFG_RGB_PIXEL_MUX_MODE_NO_SWAP,
 					 PV_MUX_CFG_RGB_PIXEL_MUX_MODE));
 
+	if (vc4->gen >= VC4_GEN_6_C)
+		CRTC_WRITE(PV_PIPE_INIT_CTRL,
+			   VC4_SET_FIELD(1, PV_PIPE_INIT_CTRL_PV_INIT_WIDTH) |
+			   VC4_SET_FIELD(1, PV_PIPE_INIT_CTRL_PV_INIT_IDLE) |
+			   PV_PIPE_INIT_CTRL_PV_INIT_EN);
+
 	CRTC_WRITE(PV_CONTROL, PV_CONTROL_FIFO_CLR |
 		   vc4_crtc_get_fifo_full_level_bits(vc4_crtc, format) |
 		   VC4_SET_FIELD(format, PV_CONTROL_FORMAT) |
@@ -458,8 +493,10 @@ static void require_hvs_enabled(struct drm_device *dev)
 	struct vc4_dev *vc4 = to_vc4_dev(dev);
 	struct vc4_hvs *hvs = vc4->hvs;
 
-	WARN_ON_ONCE((HVS_READ(SCALER_DISPCTRL) & SCALER_DISPCTRL_ENABLE) !=
-		     SCALER_DISPCTRL_ENABLE);
+	if (vc4->gen >= VC4_GEN_6_C)
+		WARN_ON_ONCE(!(HVS_READ(SCALER6_CONTROL) & SCALER6_CONTROL_HVS_EN));
+	else
+		WARN_ON_ONCE(!(HVS_READ(SCALER_DISPCTRL) & SCALER_DISPCTRL_ENABLE));
 }
 
 static int vc4_crtc_disable(struct drm_crtc *crtc,
@@ -529,7 +566,11 @@ int vc4_crtc_disable_at_boot(struct drm_crtc *crtc)
 	if (!(of_device_is_compatible(vc4_crtc->pdev->dev.of_node,
 				      "brcm,bcm2711-pixelvalve2") ||
 	      of_device_is_compatible(vc4_crtc->pdev->dev.of_node,
-				      "brcm,bcm2711-pixelvalve4")))
+				      "brcm,bcm2711-pixelvalve4") ||
+	      of_device_is_compatible(vc4_crtc->pdev->dev.of_node,
+				      "brcm,bcm2712-pixelvalve0") ||
+	      of_device_is_compatible(vc4_crtc->pdev->dev.of_node,
+				      "brcm,bcm2712-pixelvalve1")))
 		return 0;
 
 	if (!(CRTC_READ(PV_CONTROL) & PV_CONTROL_EN))
@@ -603,11 +644,14 @@ static void vc4_crtc_atomic_disable(struct drm_crtc *crtc,
 
 	vc4_crtc_disable(crtc, encoder, state, old_vc4_state->assigned_channel);
 
+	vc4_hvs_atomic_disable(crtc, state);
+
 	/*
 	 * Make sure we issue a vblank event after disabling the CRTC if
 	 * someone was waiting it.
 	 */
 	vc4_crtc_send_vblank(crtc);
+	msleep(20);
 }
 
 static void vc4_crtc_atomic_enable(struct drm_crtc *crtc,
@@ -735,10 +779,17 @@ int vc4_crtc_atomic_check(struct drm_crtc *crtc,
 		if (conn_state->crtc != crtc)
 			continue;
 
-		vc4_state->margins.left = conn_state->tv.margins.left;
-		vc4_state->margins.right = conn_state->tv.margins.right;
-		vc4_state->margins.top = conn_state->tv.margins.top;
-		vc4_state->margins.bottom = conn_state->tv.margins.bottom;
+		if (memcmp(&vc4_state->margins, &conn_state->tv.margins,
+			   sizeof(vc4_state->margins))) {
+			memcpy(&vc4_state->margins, &conn_state->tv.margins,
+			       sizeof(vc4_state->margins));
+
+			/*
+			 * Need to force the dlist entries for all planes to be
+			 * updated so that the dest rectangles are changed.
+			 */
+			crtc_state->zpos_changed = true;
+		}
 		break;
 	}
 
@@ -765,12 +816,15 @@ static void vc4_disable_vblank(struct drm_crtc *crtc)
 {
 	struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
 	struct drm_device *dev = crtc->dev;
+	struct drm_encoder *encoder = vc4_get_crtc_encoder(crtc, crtc->state);
+	struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder);
 	int idx;
 
 	if (!drm_dev_enter(dev, &idx))
 		return;
 
-	CRTC_WRITE(PV_INTEN, 0);
+	if (!vc4_encoder || vc4_encoder->type != VC4_ENCODER_TYPE_DSI0)
+		CRTC_WRITE(PV_INTEN, 0);
 
 	drm_dev_exit(idx);
 }
@@ -781,14 +835,21 @@ static void vc4_crtc_handle_page_flip(struct vc4_crtc *vc4_crtc)
 	struct drm_device *dev = crtc->dev;
 	struct vc4_dev *vc4 = to_vc4_dev(dev);
 	struct vc4_hvs *hvs = vc4->hvs;
+	unsigned int current_dlist;
 	u32 chan = vc4_crtc->current_hvs_channel;
 	unsigned long flags;
 
 	spin_lock_irqsave(&dev->event_lock, flags);
 	spin_lock(&vc4_crtc->irq_lock);
+
+	if (vc4->gen >= VC4_GEN_6_C)
+		current_dlist = VC4_GET_FIELD(HVS_READ(SCALER6_DISPX_DL(chan)),
+					      SCALER6_DISPX_DL_LACT);
+	else
+		current_dlist = HVS_READ(SCALER_DISPLACTX(chan));
+
 	if (vc4_crtc->event &&
-	    (vc4_crtc->current_dlist == HVS_READ(SCALER_DISPLACTX(chan)) ||
-	     vc4_crtc->feeds_txp)) {
+	    (vc4_crtc->current_dlist == current_dlist || vc4_crtc->feeds_txp)) {
 		drm_crtc_send_vblank_event(crtc, vc4_crtc->event);
 		vc4_crtc->event = NULL;
 		drm_crtc_vblank_put(crtc);
@@ -799,7 +860,8 @@ static void vc4_crtc_handle_page_flip(struct vc4_crtc *vc4_crtc)
 		 * the CRTC and encoder already reconfigured, leading to
 		 * underruns. This can be seen when reconfiguring the CRTC.
 		 */
-		vc4_hvs_unmask_underrun(hvs, chan);
+		if (0 && vc4->gen < VC4_GEN_6_C)
+			vc4_hvs_unmask_underrun(hvs, chan);
 	}
 	spin_unlock(&vc4_crtc->irq_lock);
 	spin_unlock_irqrestore(&dev->event_lock, flags);
@@ -807,7 +869,14 @@ static void vc4_crtc_handle_page_flip(struct vc4_crtc *vc4_crtc)
 
 void vc4_crtc_handle_vblank(struct vc4_crtc *crtc)
 {
+	struct drm_encoder *encoder = vc4_get_crtc_encoder(&crtc->base, crtc->base.state);
+	struct vc4_encoder *vc4_encoder = to_vc4_encoder(encoder);
+
 	crtc->t_vblank = ktime_get();
+
+	if (vc4_encoder && vc4_encoder->vblank)
+		vc4_encoder->vblank(encoder);
+
 	drm_crtc_handle_vblank(&crtc->base);
 	vc4_crtc_handle_page_flip(crtc);
 }
@@ -1000,7 +1069,7 @@ static int vc4_async_page_flip(struct drm_crtc *crtc,
 	struct vc4_bo *bo = to_vc4_bo(&dma_bo->base);
 	int ret;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	/*
@@ -1043,7 +1112,7 @@ int vc4_page_flip(struct drm_crtc *crtc,
 		struct drm_device *dev = crtc->dev;
 		struct vc4_dev *vc4 = to_vc4_dev(dev);
 
-		if (vc4->gen == VC4_GEN_5)
+		if (vc4->gen > VC4_GEN_4)
 			return vc5_async_page_flip(crtc, fb, event, flags);
 		else
 			return vc4_async_page_flip(crtc, fb, event, flags);
@@ -1074,14 +1143,8 @@ void vc4_crtc_destroy_state(struct drm_crtc *crtc,
 	struct vc4_dev *vc4 = to_vc4_dev(crtc->dev);
 	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(state);
 
-	if (drm_mm_node_allocated(&vc4_state->mm)) {
-		unsigned long flags;
-
-		spin_lock_irqsave(&vc4->hvs->mm_lock, flags);
-		drm_mm_remove_node(&vc4_state->mm);
-		spin_unlock_irqrestore(&vc4->hvs->mm_lock, flags);
-
-	}
+	vc4_hvs_mark_dlist_entry_stale(vc4->hvs, vc4_state->mm);
+	vc4_state->mm = NULL;
 
 	drm_atomic_helper_crtc_destroy_state(crtc, state);
 }
@@ -1149,6 +1212,7 @@ const struct vc4_pv_data bcm2835_pv0_data = {
 	},
 	.fifo_depth = 64,
 	.pixels_per_clock = 1,
+	.pixels_per_clock_int = 1,
 	.encoder_types = {
 		[PV_CONTROL_CLK_SELECT_DSI] = VC4_ENCODER_TYPE_DSI0,
 		[PV_CONTROL_CLK_SELECT_DPI_SMI_HDMI] = VC4_ENCODER_TYPE_DPI,
@@ -1159,11 +1223,12 @@ const struct vc4_pv_data bcm2835_pv1_data = {
 	.base = {
 		.name = "pixelvalve-1",
 		.debugfs_name = "crtc1_regs",
-		.hvs_available_channels = BIT(2),
+		.hvs_available_channels = BIT(0) | BIT(1) | BIT(2),
 		.hvs_output = 2,
 	},
 	.fifo_depth = 64,
 	.pixels_per_clock = 1,
+	.pixels_per_clock_int = 1,
 	.encoder_types = {
 		[PV_CONTROL_CLK_SELECT_DSI] = VC4_ENCODER_TYPE_DSI1,
 		[PV_CONTROL_CLK_SELECT_DPI_SMI_HDMI] = VC4_ENCODER_TYPE_SMI,
@@ -1179,6 +1244,7 @@ const struct vc4_pv_data bcm2835_pv2_data = {
 	},
 	.fifo_depth = 64,
 	.pixels_per_clock = 1,
+	.pixels_per_clock_int = 1,
 	.encoder_types = {
 		[PV_CONTROL_CLK_SELECT_DPI_SMI_HDMI] = VC4_ENCODER_TYPE_HDMI0,
 		[PV_CONTROL_CLK_SELECT_VEC] = VC4_ENCODER_TYPE_VEC,
@@ -1194,6 +1260,7 @@ const struct vc4_pv_data bcm2711_pv0_data = {
 	},
 	.fifo_depth = 64,
 	.pixels_per_clock = 1,
+	.pixels_per_clock_int = 1,
 	.encoder_types = {
 		[0] = VC4_ENCODER_TYPE_DSI0,
 		[1] = VC4_ENCODER_TYPE_DPI,
@@ -1209,6 +1276,7 @@ const struct vc4_pv_data bcm2711_pv1_data = {
 	},
 	.fifo_depth = 64,
 	.pixels_per_clock = 1,
+	.pixels_per_clock_int = 1,
 	.encoder_types = {
 		[0] = VC4_ENCODER_TYPE_DSI1,
 		[1] = VC4_ENCODER_TYPE_SMI,
@@ -1224,6 +1292,7 @@ const struct vc4_pv_data bcm2711_pv2_data = {
 	},
 	.fifo_depth = 256,
 	.pixels_per_clock = 2,
+	.pixels_per_clock_int = 2,
 	.encoder_types = {
 		[0] = VC4_ENCODER_TYPE_HDMI0,
 	},
@@ -1238,6 +1307,7 @@ const struct vc4_pv_data bcm2711_pv3_data = {
 	},
 	.fifo_depth = 64,
 	.pixels_per_clock = 1,
+	.pixels_per_clock_int = 1,
 	.encoder_types = {
 		[PV_CONTROL_CLK_SELECT_VEC] = VC4_ENCODER_TYPE_VEC,
 	},
@@ -1252,6 +1322,35 @@ const struct vc4_pv_data bcm2711_pv4_data = {
 	},
 	.fifo_depth = 64,
 	.pixels_per_clock = 2,
+	.pixels_per_clock_int = 2,
+	.encoder_types = {
+		[0] = VC4_ENCODER_TYPE_HDMI1,
+	},
+};
+
+const struct vc4_pv_data bcm2712_pv0_data = {
+	.base = {
+		.debugfs_name = "crtc0_regs",
+		.hvs_available_channels = BIT(0),
+		.hvs_output = 0,
+	},
+	.fifo_depth = 64,
+	.pixels_per_clock = 1,
+	.pixels_per_clock_int = 2,
+	.encoder_types = {
+		[0] = VC4_ENCODER_TYPE_HDMI0,
+	},
+};
+
+const struct vc4_pv_data bcm2712_pv1_data = {
+	.base = {
+		.debugfs_name = "crtc1_regs",
+		.hvs_available_channels = BIT(1),
+		.hvs_output = 1,
+	},
+	.fifo_depth = 64,
+	.pixels_per_clock = 1,
+	.pixels_per_clock_int = 2,
 	.encoder_types = {
 		[0] = VC4_ENCODER_TYPE_HDMI1,
 	},
@@ -1266,6 +1365,8 @@ static const struct of_device_id vc4_crtc_dt_match[] = {
 	{ .compatible = "brcm,bcm2711-pixelvalve2", .data = &bcm2711_pv2_data },
 	{ .compatible = "brcm,bcm2711-pixelvalve3", .data = &bcm2711_pv3_data },
 	{ .compatible = "brcm,bcm2711-pixelvalve4", .data = &bcm2711_pv4_data },
+	{ .compatible = "brcm,bcm2712-pixelvalve0", .data = &bcm2712_pv0_data },
+	{ .compatible = "brcm,bcm2712-pixelvalve1", .data = &bcm2712_pv1_data },
 	{}
 };
 
diff --git a/drivers/gpu/drm/vc4/vc4_debugfs.c b/drivers/gpu/drm/vc4/vc4_debugfs.c
index fac624a663ea..8144dedf2248 100644
--- a/drivers/gpu/drm/vc4/vc4_debugfs.c
+++ b/drivers/gpu/drm/vc4/vc4_debugfs.c
@@ -24,7 +24,8 @@ vc4_debugfs_init(struct drm_minor *minor)
 	struct vc4_dev *vc4 = to_vc4_dev(minor->dev);
 	struct drm_device *drm = &vc4->base;
 
-	drm_WARN_ON(drm, vc4_hvs_debugfs_init(minor));
+	if (vc4->hvs)
+		drm_WARN_ON(drm, vc4_hvs_debugfs_init(minor));
 
 	if (vc4->v3d) {
 		drm_WARN_ON(drm, vc4_bo_debugfs_init(minor));
diff --git a/drivers/gpu/drm/vc4/vc4_dpi.c b/drivers/gpu/drm/vc4/vc4_dpi.c
index a382dc4654bd..c486b8ef092f 100644
--- a/drivers/gpu/drm/vc4/vc4_dpi.c
+++ b/drivers/gpu/drm/vc4/vc4_dpi.c
@@ -95,6 +95,8 @@ struct vc4_dpi {
 	struct clk *core_clock;
 
 	struct debugfs_regset32 regset;
+
+	int rgb_order_override;
 };
 
 #define to_vc4_dpi(_encoder)						\
@@ -205,6 +207,11 @@ static void vc4_dpi_encoder_enable(struct drm_encoder *encoder)
 			}
 		}
 
+		if (dpi->rgb_order_override >= 0) {
+			dpi_c &= ~DPI_ORDER_MASK;
+			dpi_c |= VC4_SET_FIELD(dpi->rgb_order_override, DPI_ORDER);
+		}
+
 		if (connector->display_info.bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE)
 			dpi_c |= DPI_PIXEL_CLK_INVERT;
 
@@ -313,6 +320,7 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data)
 {
 	struct platform_device *pdev = to_platform_device(dev);
 	struct drm_device *drm = dev_get_drvdata(master);
+	const char *rgb_order = NULL;
 	struct vc4_dpi *dpi;
 	int ret;
 
@@ -361,6 +369,20 @@ static int vc4_dpi_bind(struct device *dev, struct device *master, void *data)
 	if (ret)
 		return ret;
 
+	dpi->rgb_order_override = -1;
+	if (!of_property_read_string(dev->of_node, "rgb_order", &rgb_order)) {
+		if (!strcmp(rgb_order, "rgb"))
+			dpi->rgb_order_override = DPI_ORDER_RGB;
+		else if (!strcmp(rgb_order, "bgr"))
+			dpi->rgb_order_override = DPI_ORDER_BGR;
+		else if (!strcmp(rgb_order, "grb"))
+			dpi->rgb_order_override = DPI_ORDER_GRB;
+		else if (!strcmp(rgb_order, "brg"))
+			dpi->rgb_order_override = DPI_ORDER_BRG;
+		else
+			DRM_ERROR("Invalid dpi order %s - ignored\n", rgb_order);
+	}
+
 	ret = drmm_encoder_init(drm, &dpi->encoder.base,
 				&vc4_dpi_encoder_funcs,
 				DRM_MODE_ENCODER_DPI,
diff --git a/drivers/gpu/drm/vc4/vc4_drv.c b/drivers/gpu/drm/vc4/vc4_drv.c
index 550324819f37..55879b5f36ae 100644
--- a/drivers/gpu/drm/vc4/vc4_drv.c
+++ b/drivers/gpu/drm/vc4/vc4_drv.c
@@ -29,6 +29,7 @@
 #include <linux/of_device.h>
 #include <linux/platform_device.h>
 #include <linux/pm_runtime.h>
+#include <linux/dma-direct.h>
 
 #include <drm/drm_aperture.h>
 #include <drm/drm_atomic_helper.h>
@@ -98,7 +99,7 @@ static int vc4_get_param_ioctl(struct drm_device *dev, void *data,
 	if (args->pad != 0)
 		return -EINVAL;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	if (!vc4->v3d)
@@ -147,7 +148,7 @@ static int vc4_open(struct drm_device *dev, struct drm_file *file)
 	struct vc4_dev *vc4 = to_vc4_dev(dev);
 	struct vc4_file *vc4file;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	vc4file = kzalloc(sizeof(*vc4file), GFP_KERNEL);
@@ -165,7 +166,7 @@ static void vc4_close(struct drm_device *dev, struct drm_file *file)
 	struct vc4_dev *vc4 = to_vc4_dev(dev);
 	struct vc4_file *vc4file = file->driver_priv;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return;
 
 	if (vc4file->bin_bo_used)
@@ -175,6 +176,19 @@ static void vc4_close(struct drm_device *dev, struct drm_file *file)
 	kfree(vc4file);
 }
 
+static struct drm_gem_object *
+vc4_prime_import_sg_table(struct drm_device *dev,
+			  struct dma_buf_attachment *attach,
+			  struct sg_table *sgt)
+{
+	phys_addr_t phys = dma_to_phys(dev->dev, sg_dma_address(sgt->sgl));
+
+	if (swiotlb_find_pool(dev->dev, phys))
+		return ERR_PTR(-EINVAL);
+
+	return drm_gem_dma_prime_import_sg_table(dev, attach, sgt);
+}
+
 DEFINE_DRM_GEM_FOPS(vc4_drm_fops);
 
 static const struct drm_ioctl_desc vc4_drm_ioctls[] = {
@@ -211,7 +225,8 @@ const struct drm_driver vc4_drm_driver = {
 
 	.gem_create_object = vc4_create_object,
 
-	DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(vc4_bo_dumb_create),
+	.dumb_create		= vc4_bo_dumb_create,
+	.gem_prime_import_sg_table = vc4_prime_import_sg_table,
 
 	.ioctls = vc4_drm_ioctls,
 	.num_ioctls = ARRAY_SIZE(vc4_drm_ioctls),
@@ -234,7 +249,8 @@ const struct drm_driver vc5_drm_driver = {
 	.debugfs_init = vc4_debugfs_init,
 #endif
 
-	DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(vc5_dumb_create),
+	.dumb_create		= vc5_dumb_create,
+	.gem_prime_import_sg_table = vc4_prime_import_sg_table,
 
 	.fops = &vc4_drm_fops,
 
@@ -275,6 +291,7 @@ static void vc4_component_unbind_all(void *ptr)
 
 static const struct of_device_id vc4_dma_range_matches[] = {
 	{ .compatible = "brcm,bcm2711-hvs" },
+	{ .compatible = "brcm,bcm2712-hvs" },
 	{ .compatible = "brcm,bcm2835-hvs" },
 	{ .compatible = "brcm,bcm2835-v3d" },
 	{ .compatible = "brcm,cygnus-v3d" },
@@ -282,6 +299,18 @@ static const struct of_device_id vc4_dma_range_matches[] = {
 	{}
 };
 
+/*
+ * we need this helper function for determining presence of fkms
+ * before it's been bound
+ */
+static bool firmware_kms(void)
+{
+	return of_device_is_available(of_find_compatible_node(NULL, NULL,
+	       "raspberrypi,rpi-firmware-kms")) ||
+	       of_device_is_available(of_find_compatible_node(NULL, NULL,
+	       "raspberrypi,rpi-firmware-kms-2711"));
+}
+
 static int vc4_drm_bind(struct device *dev)
 {
 	struct platform_device *pdev = to_platform_device(dev);
@@ -296,16 +325,18 @@ static int vc4_drm_bind(struct device *dev)
 
 	dev->coherent_dma_mask = DMA_BIT_MASK(32);
 
-	if (of_device_is_compatible(dev->of_node, "brcm,bcm2711-vc5"))
-		gen = VC4_GEN_5;
-	else
-		gen = VC4_GEN_4;
+	gen = (enum vc4_gen)of_device_get_match_data(dev);
 
-	if (gen == VC4_GEN_5)
+	if (gen > VC4_GEN_4)
 		driver = &vc5_drm_driver;
 	else
 		driver = &vc4_drm_driver;
 
+	if (gen >= VC4_GEN_6_C)
+		dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36));
+	else
+		dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
+
 	node = of_find_matching_node_and_match(NULL, vc4_dma_range_matches,
 					       NULL);
 	if (node) {
@@ -360,7 +391,7 @@ static int vc4_drm_bind(struct device *dev)
 	if (ret)
 		goto err;
 
-	if (firmware) {
+	if (firmware && !firmware_kms()) {
 		ret = rpi_firmware_property(firmware,
 					    RPI_FIRMWARE_NOTIFY_DISPLAY_DONE,
 					    NULL, 0);
@@ -378,16 +409,20 @@ static int vc4_drm_bind(struct device *dev)
 	if (ret)
 		goto err;
 
-	ret = vc4_plane_create_additional_planes(drm);
-	if (ret)
-		goto err;
+	if (!vc4->firmware_kms) {
+		ret = vc4_plane_create_additional_planes(drm);
+		if (ret)
+			goto err;
+	}
 
 	ret = vc4_kms_load(drm);
 	if (ret < 0)
 		goto err;
 
-	drm_for_each_crtc(crtc, drm)
-		vc4_crtc_disable_at_boot(crtc);
+	if (!vc4->firmware_kms) {
+		drm_for_each_crtc(crtc, drm)
+			vc4_crtc_disable_at_boot(crtc);
+	}
 
 	ret = drm_dev_register(drm, 0);
 	if (ret < 0)
@@ -433,6 +468,7 @@ static struct platform_driver *const component_drivers[] = {
 	&vc4_dsi_driver,
 	&vc4_txp_driver,
 	&vc4_crtc_driver,
+	&vc4_firmware_kms_driver,
 	&vc4_v3d_driver,
 };
 
@@ -458,9 +494,11 @@ static void vc4_platform_drm_shutdown(struct platform_device *pdev)
 }
 
 static const struct of_device_id vc4_of_match[] = {
-	{ .compatible = "brcm,bcm2711-vc5", },
-	{ .compatible = "brcm,bcm2835-vc4", },
-	{ .compatible = "brcm,cygnus-vc4", },
+	{ .compatible = "brcm,bcm2711-vc5", .data = (void *)VC4_GEN_5 },
+	/* NB GEN_6_C will be corrected on D0 hw to GEN_6_D via vc4_hvs_bind */
+	{ .compatible = "brcm,bcm2712-vc6", .data = (void *)VC4_GEN_6_C },
+	{ .compatible = "brcm,bcm2835-vc4", .data = (void *)VC4_GEN_4 },
+	{ .compatible = "brcm,cygnus-vc4", .data = (void *)VC4_GEN_4 },
 	{},
 };
 MODULE_DEVICE_TABLE(of, vc4_of_match);
diff --git a/drivers/gpu/drm/vc4/vc4_drv.h b/drivers/gpu/drm/vc4/vc4_drv.h
index dd452e6a1143..f0005f1307b9 100644
--- a/drivers/gpu/drm/vc4/vc4_drv.h
+++ b/drivers/gpu/drm/vc4/vc4_drv.h
@@ -15,6 +15,7 @@
 #include <drm/drm_debugfs.h>
 #include <drm/drm_device.h>
 #include <drm/drm_encoder.h>
+#include <drm/drm_fourcc.h>
 #include <drm/drm_gem_dma_helper.h>
 #include <drm/drm_managed.h>
 #include <drm/drm_mm.h>
@@ -83,6 +84,8 @@ struct vc4_perfmon {
 enum vc4_gen {
 	VC4_GEN_4,
 	VC4_GEN_5,
+	VC4_GEN_6_C,
+	VC4_GEN_6_D,
 };
 
 struct vc4_dev {
@@ -93,8 +96,12 @@ struct vc4_dev {
 
 	unsigned int irq;
 
+	bool firmware_kms;
+	struct rpi_firmware *firmware;
+
 	struct vc4_hvs *hvs;
 	struct vc4_v3d *v3d;
+	struct vc4_fkms *fkms;
 
 	struct vc4_hang_state *hang_state;
 
@@ -315,6 +322,34 @@ struct vc4_v3d {
 	struct debugfs_regset32 regset;
 };
 
+#define VC4_NUM_LBM_HANDLES 64
+struct vc4_lbm_refcounts {
+	refcount_t refcount;
+
+	/* Allocation size */
+	size_t size;
+	/* Our allocation in LBM. */
+	struct drm_mm_node lbm;
+
+	/* Pointer back to the HVS structure */
+	struct vc4_hvs *hvs;
+};
+
+#define VC4_NUM_UPM_HANDLES 32
+struct vc4_upm_refcounts {
+	refcount_t refcount;
+
+	/* Allocation size */
+	size_t size;
+	/* Our allocation in UPM for prefetching. */
+	struct drm_mm_node upm;
+
+	/* Pointer back to the HVS structure */
+	struct vc4_hvs *hvs;
+};
+
+#define HVS_NUM_CHANNELS 3
+
 struct vc4_hvs {
 	struct vc4_dev *vc4;
 	struct platform_device *pdev;
@@ -323,6 +358,14 @@ struct vc4_hvs {
 	unsigned int dlist_mem_size;
 
 	struct clk *core_clk;
+	struct clk *disp_clk;
+
+	struct {
+		unsigned int desc;
+		unsigned int enabled: 1;
+	} eof_irq[HVS_NUM_CHANNELS];
+
+	bool bg_fill[HVS_NUM_CHANNELS];
 
 	unsigned long max_core_rate;
 
@@ -330,11 +373,24 @@ struct vc4_hvs {
 	 * list.  Units are dwords.
 	 */
 	struct drm_mm dlist_mm;
+
 	/* Memory manager for the LBM memory used by HVS scaling. */
 	struct drm_mm lbm_mm;
+	struct ida lbm_handles;
+	struct vc4_lbm_refcounts lbm_refcounts[VC4_NUM_LBM_HANDLES + 1];
+
+	/* Memory manager for the UPM memory used for prefetching. */
+	struct drm_mm upm_mm;
+	struct ida upm_handles;
+	struct vc4_upm_refcounts upm_refcounts[VC4_NUM_UPM_HANDLES + 1];
+
 	spinlock_t mm_lock;
 
+	struct list_head stale_dlist_entries;
+	struct work_struct free_dlist_work;
+
 	struct drm_mm_node mitchell_netravali_filter;
+	struct drm_mm_node nearest_neighbour_filter;
 
 	struct debugfs_regset32 regset;
 
@@ -353,7 +409,7 @@ struct vc4_hvs {
 	bool vc5_hdmi_enable_4096by2160;
 };
 
-#define HVS_NUM_CHANNELS 3
+#define HVS_UBM_WORD_SIZE 256
 
 struct vc4_hvs_state {
 	struct drm_private_state base;
@@ -400,7 +456,7 @@ struct vc4_plane_state {
 	 */
 	u32 pos0_offset;
 	u32 pos2_offset;
-	u32 ptr0_offset;
+	u32 ptr0_offset[DRM_FORMAT_MAX_PLANES];
 	u32 lbm_offset;
 
 	/* Offset where the plane's dlist was last stored in the
@@ -410,7 +466,7 @@ struct vc4_plane_state {
 
 	/* Clipped coordinates of the plane on the display. */
 	int crtc_x, crtc_y, crtc_w, crtc_h;
-	/* Clipped area being scanned from in the FB. */
+	/* Clipped area being scanned from in the FB in u16.16 format */
 	u32 src_x, src_y;
 
 	u32 src_w[2], src_h[2];
@@ -419,14 +475,19 @@ struct vc4_plane_state {
 	enum vc4_scaling_mode x_scaling[2], y_scaling[2];
 	bool is_unity;
 	bool is_yuv;
-
-	/* Offset to start scanning out from the start of the plane's
-	 * BO.
+	/* Allows use of nearest neighbour scaling filter when doing unity YUV444
+	 * workaround
 	 */
-	u32 offsets[3];
+	bool is_yuv444_unity;
 
 	/* Our allocation in LBM for temporary storage during scaling. */
-	struct drm_mm_node lbm;
+	unsigned int lbm_handle;
+
+	/* The Unified Pre-Fetcher Handle */
+	unsigned int upm_handle[DRM_FORMAT_MAX_PLANES];
+
+	/* Number of lines to pre-fetch */
+	unsigned int upm_buffer_lines;
 
 	/* Set when the plane has per-pixel alpha content or does not cover
 	 * the entire screen. This is a hint to the CRTC that it might need
@@ -462,7 +523,8 @@ enum vc4_encoder_type {
 	VC4_ENCODER_TYPE_DSI1,
 	VC4_ENCODER_TYPE_SMI,
 	VC4_ENCODER_TYPE_DPI,
-	VC4_ENCODER_TYPE_TXP,
+	VC4_ENCODER_TYPE_TXP0,
+	VC4_ENCODER_TYPE_TXP1,
 };
 
 struct vc4_encoder {
@@ -476,6 +538,7 @@ struct vc4_encoder {
 
 	void (*post_crtc_disable)(struct drm_encoder *encoder, struct drm_atomic_state *state);
 	void (*post_crtc_powerdown)(struct drm_encoder *encoder, struct drm_atomic_state *state);
+	void (*vblank)(struct drm_encoder *encoder);
 };
 
 #define to_vc4_encoder(_encoder)				\
@@ -509,7 +572,18 @@ struct vc4_crtc_data {
 	int hvs_output;
 };
 
-extern const struct vc4_crtc_data vc4_txp_crtc_data;
+struct vc4_txp_data {
+	struct vc4_crtc_data	base;
+	enum vc4_encoder_type encoder_type;
+	unsigned int high_addr_ptr_reg;
+	unsigned int has_byte_enable:1;
+	unsigned int size_minus_one:1;
+	unsigned int supports_40bit_addresses:1;
+};
+
+extern const struct vc4_txp_data bcm2712_mop_data;
+extern const struct vc4_txp_data bcm2712_moplet_data;
+extern const struct vc4_txp_data bcm2835_txp_data;
 
 struct vc4_pv_data {
 	struct vc4_crtc_data	base;
@@ -519,6 +593,8 @@ struct vc4_pv_data {
 
 	/* Number of pixels output per clock period */
 	u8 pixels_per_clock;
+	/* Number of pixels output per clock period when in an interlaced mode */
+	u8 pixels_per_clock_int;
 
 	enum vc4_encoder_type encoder_types[4];
 };
@@ -531,6 +607,8 @@ extern const struct vc4_pv_data bcm2711_pv1_data;
 extern const struct vc4_pv_data bcm2711_pv2_data;
 extern const struct vc4_pv_data bcm2711_pv3_data;
 extern const struct vc4_pv_data bcm2711_pv4_data;
+extern const struct vc4_pv_data bcm2712_pv0_data;
+extern const struct vc4_pv_data bcm2712_pv1_data;
 
 struct vc4_crtc {
 	struct drm_crtc base;
@@ -597,19 +675,21 @@ vc4_crtc_to_vc4_pv_data(const struct vc4_crtc *crtc)
 struct drm_encoder *vc4_get_crtc_encoder(struct drm_crtc *crtc,
 					 struct drm_crtc_state *state);
 
+struct vc4_hvs_dlist_allocation {
+	struct list_head node;
+	struct drm_mm_node mm_node;
+	unsigned int channel;
+	u8 target_frame_count;
+	bool dlist_programmed;
+};
+
 struct vc4_crtc_state {
 	struct drm_crtc_state base;
-	/* Dlist area for this CRTC configuration. */
-	struct drm_mm_node mm;
+	struct vc4_hvs_dlist_allocation *mm;
 	bool txp_armed;
 	unsigned int assigned_channel;
 
-	struct {
-		unsigned int left;
-		unsigned int right;
-		unsigned int top;
-		unsigned int bottom;
-	} margins;
+	struct drm_connector_tv_margins margins;
 
 	unsigned long hvs_load;
 
@@ -646,6 +726,12 @@ struct vc4_crtc_state {
 		writel(val, hvs->regs + (offset));					\
 	} while (0)
 
+#define HVS_READ6(offset) \
+	HVS_READ(hvs->vc4->gen == VC4_GEN_6_C ? SCALER6_ ## offset : SCALER6D_ ## offset)
+
+#define HVS_WRITE6(offset, val) \
+	HVS_WRITE(hvs->vc4->gen == VC4_GEN_6_C ? SCALER6_ ## offset : SCALER6D_ ## offset, val)
+
 #define VC4_REG32(reg) { .name = #reg, .offset = reg }
 
 struct vc4_exec_info {
@@ -970,6 +1056,9 @@ extern struct platform_driver vc4_dsi_driver;
 /* vc4_fence.c */
 extern const struct dma_fence_ops vc4_fence_ops;
 
+/* vc4_firmware_kms.c */
+extern struct platform_driver vc4_firmware_kms_driver;
+
 /* vc4_gem.c */
 int vc4_gem_init(struct drm_device *dev);
 int vc4_submit_cl_ioctl(struct drm_device *dev, void *data,
@@ -998,6 +1087,7 @@ extern struct platform_driver vc4_vec_driver;
 
 /* vc4_txp.c */
 extern struct platform_driver vc4_txp_driver;
+void vc4_txp_connector_reset(struct drm_connector *connector);
 
 /* vc4_irq.c */
 void vc4_irq_enable(struct drm_device *dev);
@@ -1008,10 +1098,14 @@ void vc4_irq_reset(struct drm_device *dev);
 
 /* vc4_hvs.c */
 extern struct platform_driver vc4_hvs_driver;
-struct vc4_hvs *__vc4_hvs_alloc(struct vc4_dev *vc4, struct platform_device *pdev);
+struct vc4_hvs *__vc4_hvs_alloc(struct vc4_dev *vc4,
+				void __iomem *regs,
+				struct platform_device *pdev);
 void vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int output);
 int vc4_hvs_get_fifo_from_output(struct vc4_hvs *hvs, unsigned int output);
 u8 vc4_hvs_get_fifo_frame_count(struct vc4_hvs *hvs, unsigned int fifo);
+void vc4_hvs_mark_dlist_entry_stale(struct vc4_hvs *hvs,
+				    struct vc4_hvs_dlist_allocation *alloc);
 int vc4_hvs_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state);
 void vc4_hvs_atomic_begin(struct drm_crtc *crtc, struct drm_atomic_state *state);
 void vc4_hvs_atomic_enable(struct drm_crtc *crtc, struct drm_atomic_state *state);
@@ -1029,6 +1123,12 @@ int vc4_kms_load(struct drm_device *dev);
 struct drm_plane *vc4_plane_init(struct drm_device *dev,
 				 enum drm_plane_type type,
 				 uint32_t possible_crtcs);
+void vc4_plane_reset(struct drm_plane *plane);
+void vc4_plane_destroy_state(struct drm_plane *plane,
+			     struct drm_plane_state *state);
+struct drm_plane_state *vc4_plane_duplicate_state(struct drm_plane *plane);
+int vc4_plane_atomic_check(struct drm_plane *plane,
+			   struct drm_atomic_state *state);
 int vc4_plane_create_additional_planes(struct drm_device *dev);
 u32 vc4_plane_write_dlist(struct drm_plane *plane, u32 __iomem *dlist);
 u32 vc4_plane_dlist_size(const struct drm_plane_state *state);
diff --git a/drivers/gpu/drm/vc4/vc4_dsi.c b/drivers/gpu/drm/vc4/vc4_dsi.c
index f5ccc1bf7a63..6c239ab36965 100644
--- a/drivers/gpu/drm/vc4/vc4_dsi.c
+++ b/drivers/gpu/drm/vc4/vc4_dsi.c
@@ -44,7 +44,6 @@
 
 #define DSI_CMD_FIFO_DEPTH  16
 #define DSI_PIX_FIFO_DEPTH 256
-#define DSI_PIX_FIFO_WIDTH   4
 
 #define DSI0_CTRL		0x00
 
@@ -170,11 +169,15 @@
 #define DSI1_DISP1_CTRL		0x2c
 /* Format of the data written to TXPKT_PIX_FIFO. */
 # define DSI_DISP1_PFORMAT_MASK		VC4_MASK(2, 1)
-# define DSI_DISP1_PFORMAT_SHIFT	1
-# define DSI_DISP1_PFORMAT_16BIT	0
-# define DSI_DISP1_PFORMAT_24BIT	1
-# define DSI_DISP1_PFORMAT_32BIT_LE	2
-# define DSI_DISP1_PFORMAT_32BIT_BE	3
+# define DSI1_DISP1_PFORMAT_SHIFT	1
+# define DSI0_DISP1_PFORMAT_16BIT	0
+# define DSI0_DISP1_PFORMAT_16BIT_ADJ	1
+# define DSI0_DISP1_PFORMAT_24BIT	2
+# define DSI0_DISP1_PFORMAT_32BIT_LE	3 /* NB Invalid, but required for macros to work */
+# define DSI1_DISP1_PFORMAT_16BIT	0
+# define DSI1_DISP1_PFORMAT_24BIT	1
+# define DSI1_DISP1_PFORMAT_32BIT_LE	2
+# define DSI1_DISP1_PFORMAT_32BIT_BE	3
 
 /* DISP1 is always command mode. */
 # define DSI_DISP1_ENABLE		BIT(0)
@@ -286,6 +289,8 @@
 					 DSI1_INT_PR_TO)
 
 #define DSI0_STAT		0x2c
+# define DSI0_STAT_ERR_CONT_LP1		BIT(6)
+# define DSI0_STAT_ERR_CONT_LP0		BIT(5)
 #define DSI0_HSTX_TO_CNT	0x30
 #define DSI0_LPRX_TO_CNT	0x34
 #define DSI0_TA_TO_CNT		0x38
@@ -358,6 +363,16 @@
 # define DSI_PHY_AFEC0_CTATADJ_MASK		VC4_MASK(3, 0)
 # define DSI_PHY_AFEC0_CTATADJ_SHIFT		0
 
+# define DSI0_AFEC0_PD_ALL_LANES	(DSI0_PHY_AFEC0_PD | \
+					 DSI0_PHY_AFEC0_PD_BG | \
+					 DSI0_PHY_AFEC0_PD_DLANE1)
+
+# define DSI1_AFEC0_PD_ALL_LANES	(DSI1_PHY_AFEC0_PD | \
+					 DSI1_PHY_AFEC0_PD_BG | \
+					 DSI1_PHY_AFEC0_PD_DLANE3 | \
+					 DSI1_PHY_AFEC0_PD_DLANE2 | \
+					 DSI1_PHY_AFEC0_PD_DLANE1)
+
 #define DSI0_PHY_AFEC1		0x68
 # define DSI0_PHY_AFEC1_IDR_DLANE1_MASK		VC4_MASK(10, 8)
 # define DSI0_PHY_AFEC1_IDR_DLANE1_SHIFT	8
@@ -398,7 +413,8 @@
 # define DSI1_CTRL_DISABLE_DISP_ECCC	BIT(1)
 # define DSI0_CTRL_CTRL0		BIT(0)
 # define DSI1_CTRL_EN			BIT(0)
-# define DSI0_CTRL_RESET_FIFOS		(DSI_CTRL_CLR_LDF | \
+# define DSI0_CTRL_RESET_FIFOS		(DSI0_CTRL_CTRL0 | \
+					 DSI_CTRL_CLR_LDF | \
 					 DSI0_CTRL_CLR_PBCF | \
 					 DSI0_CTRL_CLR_CPBCF |	\
 					 DSI0_CTRL_CLR_PDF | \
@@ -540,6 +556,7 @@ struct vc4_dsi_variant {
 	unsigned int port;
 
 	bool broken_axi_workaround;
+	unsigned int cmd_fifo_width;
 
 	const char *debugfs_name;
 	const struct debugfs_reg32 *regs;
@@ -816,6 +833,15 @@ static void vc4_dsi_bridge_post_disable(struct drm_bridge *bridge,
 	struct vc4_dsi *dsi = bridge_to_vc4_dsi(bridge);
 	struct device *dev = &dsi->pdev->dev;
 
+	/* Reset the DSI and all its fifos. */
+	DSI_PORT_WRITE(CTRL, DSI_CTRL_SOFT_RESET_CFG |
+		       DSI_PORT_BIT(CTRL_RESET_FIFOS));
+
+	/* Power down the analogue front end. */
+	DSI_PORT_WRITE(PHY_AFEC0, DSI_PORT_BIT(PHY_AFEC0_RESET) |
+		       DSI_PORT_BIT(PHY_AFEC0_PD) |
+		       DSI_PORT_BIT(AFEC0_PD_ALL_LANES));
+
 	clk_disable_unprepare(dsi->pll_phy_clock);
 	clk_disable_unprepare(dsi->escape_clock);
 	clk_disable_unprepare(dsi->pixel_clock);
@@ -846,6 +872,7 @@ static bool vc4_dsi_bridge_mode_fixup(struct drm_bridge *bridge,
 	unsigned long pixel_clock_hz = mode->clock * 1000;
 	unsigned long pll_clock = pixel_clock_hz * dsi->divider;
 	int divider;
+	u16 htotal;
 
 	/* Find what divider gets us a faster clock than the requested
 	 * pixel clock.
@@ -862,12 +889,27 @@ static bool vc4_dsi_bridge_mode_fixup(struct drm_bridge *bridge,
 	pixel_clock_hz = pll_clock / dsi->divider;
 
 	adjusted_mode->clock = pixel_clock_hz / 1000;
+	htotal = mode->htotal;
+
+	if (dsi->variant->port == 0 && mode->clock == 30000 &&
+	    mode->hdisplay == 800 && mode->htotal == (800 + 59 + 2 + 45) &&
+	    mode->vdisplay == 480 && mode->vtotal == (480 + 7 + 2 + 22)) {
+		/*
+		 * Raspberry Pi 7" panel via TC358762 seems to have an issue on
+		 * DSI0 that it doesn't actually follow the vertical timing that
+		 * is otherwise identical to that produced on DSI1.
+		 * Fixup the mode.
+		 */
+		htotal = 800 + 59 + 2 + 47;
+		adjusted_mode->vtotal = 480 + 7 + 2 + 45;
+		adjusted_mode->crtc_vtotal = 480 + 7 + 2 + 45;
+	}
 
 	/* Given the new pixel clock, adjust HFP to keep vrefresh the same. */
-	adjusted_mode->htotal = adjusted_mode->clock * mode->htotal /
+	adjusted_mode->htotal = adjusted_mode->clock * htotal /
 				mode->clock;
-	adjusted_mode->hsync_end += adjusted_mode->htotal - mode->htotal;
-	adjusted_mode->hsync_start += adjusted_mode->htotal - mode->htotal;
+	adjusted_mode->hsync_end += adjusted_mode->htotal - htotal;
+	adjusted_mode->hsync_start += adjusted_mode->htotal - htotal;
 
 	return true;
 }
@@ -927,12 +969,32 @@ static void vc4_dsi_bridge_pre_enable(struct drm_bridge *bridge,
 			"Failed to set phy clock to %ld: %d\n", phy_clock, ret);
 	}
 
-	/* Reset the DSI and all its fifos. */
+	ret = clk_prepare_enable(dsi->escape_clock);
+	if (ret) {
+		drm_err(bridge->dev, "Failed to turn on DSI escape clock: %d\n",
+			ret);
+		return;
+	}
+
+	ret = clk_prepare_enable(dsi->pll_phy_clock);
+	if (ret) {
+		drm_err(bridge->dev, "Failed to turn on DSI PLL: %d\n", ret);
+		return;
+	}
+
+	hs_clock = clk_get_rate(dsi->pll_phy_clock);
+
+	/*
+	 * Reset the DSI and all its fifos. The block must be enabled for the
+	 * FIFO resets to trigger.
+	 */
 	DSI_PORT_WRITE(CTRL,
 		       DSI_CTRL_SOFT_RESET_CFG |
 		       DSI_PORT_BIT(CTRL_RESET_FIFOS));
 
 	DSI_PORT_WRITE(CTRL,
+		       ((dsi->variant->port == 0) ?
+					DSI0_CTRL_CTRL0 : DSI1_CTRL_EN) |
 		       DSI_CTRL_HSDT_EOT_DISABLE |
 		       DSI_CTRL_RX_LPDT_EOT_DISABLE);
 
@@ -985,21 +1047,6 @@ static void vc4_dsi_bridge_pre_enable(struct drm_bridge *bridge,
 		mdelay(1);
 	}
 
-	ret = clk_prepare_enable(dsi->escape_clock);
-	if (ret) {
-		drm_err(bridge->dev, "Failed to turn on DSI escape clock: %d\n",
-			ret);
-		return;
-	}
-
-	ret = clk_prepare_enable(dsi->pll_phy_clock);
-	if (ret) {
-		drm_err(bridge->dev, "Failed to turn on DSI PLL: %d\n", ret);
-		return;
-	}
-
-	hs_clock = clk_get_rate(dsi->pll_phy_clock);
-
 	/* Yes, we set the DSI0P/DSI1P pixel clock to the byte rate,
 	 * not the pixel clock rate.  DSIxP take from the APHY's byte,
 	 * DDR2, or DDR4 clock (we use byte) and feed into the PV at
@@ -1110,16 +1157,16 @@ static void vc4_dsi_bridge_pre_enable(struct drm_bridge *bridge,
 	/* Set up DISP1 for transferring long command payloads through
 	 * the pixfifo.
 	 */
-	DSI_PORT_WRITE(DISP1_CTRL,
-		       VC4_SET_FIELD(DSI_DISP1_PFORMAT_32BIT_LE,
-				     DSI_DISP1_PFORMAT) |
-		       DSI_DISP1_ENABLE);
-
-	/* Ungate the block. */
-	if (dsi->variant->port == 0)
-		DSI_PORT_WRITE(CTRL, DSI_PORT_READ(CTRL) | DSI0_CTRL_CTRL0);
+	if (dsi->variant->cmd_fifo_width == 4)
+		DSI_PORT_WRITE(DISP1_CTRL,
+			       VC4_SET_FIELD(DSI_PORT_BIT(DISP1_PFORMAT_32BIT_LE),
+					     DSI_DISP1_PFORMAT) |
+			       DSI_DISP1_ENABLE);
 	else
-		DSI_PORT_WRITE(CTRL, DSI_PORT_READ(CTRL) | DSI1_CTRL_EN);
+		DSI_PORT_WRITE(DISP1_CTRL,
+			       VC4_SET_FIELD(DSI_PORT_BIT(DISP1_PFORMAT_24BIT),
+					     DSI_DISP1_PFORMAT) |
+			       DSI_DISP1_ENABLE);
 
 	/* Bring AFE out of reset. */
 	DSI_PORT_WRITE(PHY_AFEC0,
@@ -1170,10 +1217,9 @@ static int vc4_dsi_bridge_attach(struct drm_bridge *bridge,
 				 &dsi->bridge, flags);
 }
 
-static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host,
-				     const struct mipi_dsi_msg *msg)
+static ssize_t vc4_dsi_transfer(struct vc4_dsi *dsi,
+				const struct mipi_dsi_msg *msg, bool log_error)
 {
-	struct vc4_dsi *dsi = host_to_dsi(host);
 	struct drm_device *drm = dsi->bridge.dev;
 	struct mipi_dsi_packet packet;
 	u32 pkth = 0, pktc = 0;
@@ -1202,9 +1248,9 @@ static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host,
 			pix_fifo_len = 0;
 		} else {
 			cmd_fifo_len = (packet.payload_length %
-					DSI_PIX_FIFO_WIDTH);
+					dsi->variant->cmd_fifo_width);
 			pix_fifo_len = ((packet.payload_length - cmd_fifo_len) /
-					DSI_PIX_FIFO_WIDTH);
+					dsi->variant->cmd_fifo_width);
 		}
 
 		WARN_ON_ONCE(pix_fifo_len >= DSI_PIX_FIFO_DEPTH);
@@ -1222,14 +1268,25 @@ static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host,
 
 	for (i = 0; i < cmd_fifo_len; i++)
 		DSI_PORT_WRITE(TXPKT_CMD_FIFO, packet.payload[i]);
-	for (i = 0; i < pix_fifo_len; i++) {
-		const u8 *pix = packet.payload + cmd_fifo_len + i * 4;
-
-		DSI_PORT_WRITE(TXPKT_PIX_FIFO,
-			       pix[0] |
-			       pix[1] << 8 |
-			       pix[2] << 16 |
-			       pix[3] << 24);
+	if (dsi->variant->cmd_fifo_width == 4) {
+		for (i = 0; i < pix_fifo_len; i++) {
+			const u8 *pix = packet.payload + cmd_fifo_len + i * 4;
+
+			DSI_PORT_WRITE(TXPKT_PIX_FIFO,
+				       pix[0] |
+				       pix[1] << 8 |
+				       pix[2] << 16 |
+				       pix[3] << 24);
+		}
+	} else {
+		for (i = 0; i < pix_fifo_len; i++) {
+			const u8 *pix = packet.payload + cmd_fifo_len + i * 3;
+
+			DSI_PORT_WRITE(TXPKT_PIX_FIFO,
+				       pix[2] |
+				       pix[1] << 8 |
+				       pix[0] << 16);
+		}
 	}
 
 	if (msg->flags & MIPI_DSI_MSG_USE_LPM)
@@ -1283,10 +1340,12 @@ static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host,
 	DSI_PORT_WRITE(TXPKT1C, pktc);
 
 	if (!wait_for_completion_timeout(&dsi->xfer_completion,
-					 msecs_to_jiffies(1000))) {
-		dev_err(&dsi->pdev->dev, "transfer interrupt wait timeout");
-		dev_err(&dsi->pdev->dev, "instat: 0x%08x\n",
-			DSI_PORT_READ(INT_STAT));
+					 msecs_to_jiffies(500))) {
+		if (log_error) {
+			dev_err(&dsi->pdev->dev, "transfer interrupt wait timeout");
+			dev_err(&dsi->pdev->dev, "instat: 0x%08x, stat: 0x%08x\n",
+				DSI_PORT_READ(INT_STAT), DSI_PORT_READ(INT_STAT));
+		}
 		ret = -ETIMEDOUT;
 	} else {
 		ret = dsi->xfer_result;
@@ -1329,7 +1388,8 @@ static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host,
 	return ret;
 
 reset_fifo_and_return:
-	drm_err(drm, "DSI transfer failed, resetting: %d\n", ret);
+	if (log_error)
+		drm_err(drm, "DSI transfer failed, resetting: %d\n", ret);
 
 	DSI_PORT_WRITE(TXPKT1C, DSI_PORT_READ(TXPKT1C) & ~DSI_TXPKT1C_CMD_EN);
 	udelay(1);
@@ -1342,6 +1402,40 @@ static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host,
 	return ret;
 }
 
+static ssize_t vc4_dsi_host_transfer(struct mipi_dsi_host *host,
+				     const struct mipi_dsi_msg *msg)
+{
+	struct vc4_dsi *dsi = host_to_dsi(host);
+	u32 stat, disp0_ctrl;
+	int ret;
+
+	ret = vc4_dsi_transfer(dsi, msg, false);
+
+	if (ret == -ETIMEDOUT) {
+		stat = DSI_PORT_READ(STAT);
+		disp0_ctrl = DSI_PORT_READ(DISP0_CTRL);
+
+		DSI_PORT_WRITE(STAT, DSI_PORT_BIT(STAT_ERR_CONT_LP1));
+		if (!(disp0_ctrl & DSI_DISP0_ENABLE)) {
+			/* If video mode not enabled, then try recovering by
+			 * enabling it briefly to clear FIFOs and the state.
+			 */
+			disp0_ctrl |= DSI_DISP0_ENABLE;
+			DSI_PORT_WRITE(DISP0_CTRL, disp0_ctrl);
+			msleep(30);
+			disp0_ctrl &= ~DSI_DISP0_ENABLE;
+			DSI_PORT_WRITE(DISP0_CTRL, disp0_ctrl);
+			msleep(30);
+
+			ret = vc4_dsi_transfer(dsi, msg, true);
+		} else {
+			DRM_ERROR("DSI transfer failed whilst in HS mode stat: 0x%08x\n",
+				  stat);
+		}
+	}
+	return ret;
+}
+
 static const struct component_ops vc4_dsi_ops;
 static int vc4_dsi_host_attach(struct mipi_dsi_host *host,
 			       struct mipi_dsi_device *device)
@@ -1421,6 +1515,15 @@ static const struct drm_bridge_funcs vc4_dsi_bridge_funcs = {
 	.mode_fixup = vc4_dsi_bridge_mode_fixup,
 };
 
+static void vc4_dsi_reset_fifo(struct drm_encoder *encoder)
+{
+	struct vc4_dsi *dsi = to_vc4_dsi(encoder);
+	u32 val;
+
+	val = DSI_PORT_READ(CTRL);
+	DSI_PORT_WRITE(CTRL, val | DSI0_CTRL_CLR_PBCF);
+}
+
 static int vc4_dsi_late_register(struct drm_encoder *encoder)
 {
 	struct drm_device *drm = encoder->dev;
@@ -1437,6 +1540,7 @@ static const struct drm_encoder_funcs vc4_dsi_encoder_funcs = {
 
 static const struct vc4_dsi_variant bcm2711_dsi1_variant = {
 	.port			= 1,
+	.cmd_fifo_width		= 4,
 	.debugfs_name		= "dsi1_regs",
 	.regs			= dsi1_regs,
 	.nregs			= ARRAY_SIZE(dsi1_regs),
@@ -1444,6 +1548,7 @@ static const struct vc4_dsi_variant bcm2711_dsi1_variant = {
 
 static const struct vc4_dsi_variant bcm2835_dsi0_variant = {
 	.port			= 0,
+	.cmd_fifo_width		= 3,
 	.debugfs_name		= "dsi0_regs",
 	.regs			= dsi0_regs,
 	.nregs			= ARRAY_SIZE(dsi0_regs),
@@ -1451,6 +1556,7 @@ static const struct vc4_dsi_variant bcm2835_dsi0_variant = {
 
 static const struct vc4_dsi_variant bcm2835_dsi1_variant = {
 	.port			= 1,
+	.cmd_fifo_width		= 4,
 	.broken_axi_workaround	= true,
 	.debugfs_name		= "dsi1_regs",
 	.regs			= dsi1_regs,
@@ -1666,6 +1772,9 @@ static int vc4_dsi_bind(struct device *dev, struct device *master, void *data)
 	dsi->encoder.type = dsi->variant->port ?
 		VC4_ENCODER_TYPE_DSI1 : VC4_ENCODER_TYPE_DSI0;
 
+	if (dsi->encoder.type == VC4_ENCODER_TYPE_DSI0)
+		dsi->encoder.vblank = vc4_dsi_reset_fifo;
+
 	dsi->regs = vc4_ioremap_regs(pdev, 0);
 	if (IS_ERR(dsi->regs))
 		return PTR_ERR(dsi->regs);
diff --git a/drivers/gpu/drm/vc4/vc4_firmware_kms.c b/drivers/gpu/drm/vc4/vc4_firmware_kms.c
new file mode 100644
index 000000000000..fd1c528ec69c
--- /dev/null
+++ b/drivers/gpu/drm/vc4/vc4_firmware_kms.c
@@ -0,0 +1,2079 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2016 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+/**
+ * DOC: VC4 firmware KMS module.
+ *
+ * As a hack to get us from the current closed source driver world
+ * toward a totally open stack, implement KMS on top of the Raspberry
+ * Pi's firmware display stack.
+ */
+
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_blend.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_plane_helper.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+
+#include <linux/component.h>
+#include <linux/clk.h>
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
+#include "vc4_drv.h"
+#include "vc4_regs.h"
+#include "vc_image_types.h"
+
+int fkms_max_refresh_rate = 85;
+module_param(fkms_max_refresh_rate, int, 0644);
+MODULE_PARM_DESC(fkms_max_refresh_rate, "Max supported refresh rate");
+
+struct get_display_cfg {
+	u32  max_pixel_clock[2];  //Max pixel clock for each display
+};
+
+enum vc4_fkms_revision {
+	BCM2835_6_7,
+	BCM2711,
+	BCM2712,
+};
+
+struct vc4_fkms {
+	struct get_display_cfg cfg;
+	enum vc4_fkms_revision revision;
+};
+
+#define PLANES_PER_CRTC		8
+
+struct set_plane {
+	u8 display;
+	u8 plane_id;
+	u8 vc_image_type;
+	s8 layer;
+
+	u16 width;
+	u16 height;
+
+	u16 pitch;
+	u16 vpitch;
+
+	u32 src_x;	/* 16p16 */
+	u32 src_y;	/* 16p16 */
+
+	u32 src_w;	/* 16p16 */
+	u32 src_h;	/* 16p16 */
+
+	s16 dst_x;
+	s16 dst_y;
+
+	u16 dst_w;
+	u16 dst_h;
+
+	u8 alpha;
+	u8 num_planes;
+	u8 is_vu;
+	u8 color_encoding;
+
+	u32 planes[4];  /* DMA address of each plane */
+
+	u32 transform;
+};
+
+/* Values for the transform field */
+#define TRANSFORM_NO_ROTATE	0
+#define TRANSFORM_ROTATE_180	BIT(1)
+#define TRANSFORM_FLIP_HRIZ	BIT(16)
+#define TRANSFORM_FLIP_VERT	BIT(17)
+
+struct mailbox_set_plane {
+	struct rpi_firmware_property_tag_header tag;
+	struct set_plane plane;
+};
+
+struct mailbox_blank_display {
+	struct rpi_firmware_property_tag_header tag1;
+	u32 display;
+	struct rpi_firmware_property_tag_header tag2;
+	u32 blank;
+};
+
+struct mailbox_display_pwr {
+	struct rpi_firmware_property_tag_header tag1;
+	u32 display;
+	u32 state;
+};
+
+struct mailbox_get_edid {
+	struct rpi_firmware_property_tag_header tag1;
+	u32 block;
+	u32 display_number;
+	u8 edid[128];
+};
+
+struct set_timings {
+	u8 display;
+	u8 padding;
+	u16 video_id_code;
+
+	u32 clock;		/* in kHz */
+
+	u16 hdisplay;
+	u16 hsync_start;
+
+	u16 hsync_end;
+	u16 htotal;
+
+	u16 hskew;
+	u16 vdisplay;
+
+	u16 vsync_start;
+	u16 vsync_end;
+
+	u16 vtotal;
+	u16 vscan;
+
+	u16 vrefresh;
+	u16 padding2;
+
+	u32 flags;
+#define  TIMINGS_FLAGS_H_SYNC_POS	BIT(0)
+#define  TIMINGS_FLAGS_H_SYNC_NEG	0
+#define  TIMINGS_FLAGS_V_SYNC_POS	BIT(1)
+#define  TIMINGS_FLAGS_V_SYNC_NEG	0
+#define  TIMINGS_FLAGS_INTERLACE	BIT(2)
+
+#define TIMINGS_FLAGS_ASPECT_MASK	GENMASK(7, 4)
+#define TIMINGS_FLAGS_ASPECT_NONE	(0 << 4)
+#define TIMINGS_FLAGS_ASPECT_4_3	(1 << 4)
+#define TIMINGS_FLAGS_ASPECT_16_9	(2 << 4)
+#define TIMINGS_FLAGS_ASPECT_64_27	(3 << 4)
+#define TIMINGS_FLAGS_ASPECT_256_135	(4 << 4)
+
+/* Limited range RGB flag. Not set corresponds to full range. */
+#define TIMINGS_FLAGS_RGB_LIMITED	BIT(8)
+/* DVI monitor, therefore disable infoframes. Not set corresponds to HDMI. */
+#define TIMINGS_FLAGS_DVI		BIT(9)
+/* Double clock */
+#define TIMINGS_FLAGS_DBL_CLK		BIT(10)
+};
+
+struct mailbox_set_mode {
+	struct rpi_firmware_property_tag_header tag1;
+	struct set_timings timings;
+};
+
+static const struct vc_image_format {
+	u32 drm;	/* DRM_FORMAT_* */
+	u32 vc_image;	/* VC_IMAGE_* */
+	u32 is_vu;
+} vc_image_formats[] = {
+	{
+		.drm = DRM_FORMAT_XRGB8888,
+		.vc_image = VC_IMAGE_XRGB8888,
+	},
+	{
+		.drm = DRM_FORMAT_ARGB8888,
+		.vc_image = VC_IMAGE_ARGB8888,
+	},
+	{
+		.drm = DRM_FORMAT_XBGR8888,
+		.vc_image = VC_IMAGE_RGBX32,
+	},
+	{
+		.drm = DRM_FORMAT_ABGR8888,
+		.vc_image = VC_IMAGE_RGBA32,
+	},
+	{
+		.drm = DRM_FORMAT_RGBX8888,
+		.vc_image = VC_IMAGE_BGRX8888,
+	},
+	{
+		.drm = DRM_FORMAT_BGRX8888,
+		.vc_image = VC_IMAGE_RGBX8888,
+	},
+	{
+		.drm = DRM_FORMAT_RGB565,
+		.vc_image = VC_IMAGE_RGB565,
+	},
+	{
+		.drm = DRM_FORMAT_RGB888,
+		.vc_image = VC_IMAGE_BGR888,
+	},
+	{
+		.drm = DRM_FORMAT_BGR888,
+		.vc_image = VC_IMAGE_RGB888,
+	},
+	{
+		.drm = DRM_FORMAT_YUV422,
+		.vc_image = VC_IMAGE_YUV422PLANAR,
+	},
+	{
+		.drm = DRM_FORMAT_YUV420,
+		.vc_image = VC_IMAGE_YUV420,
+	},
+	{
+		.drm = DRM_FORMAT_YVU420,
+		.vc_image = VC_IMAGE_YUV420,
+		.is_vu = 1,
+	},
+	{
+		.drm = DRM_FORMAT_NV12,
+		.vc_image = VC_IMAGE_YUV420SP,
+	},
+	{
+		.drm = DRM_FORMAT_NV21,
+		.vc_image = VC_IMAGE_YUV420SP,
+		.is_vu = 1,
+	},
+	{
+		.drm = DRM_FORMAT_P030,
+		.vc_image = VC_IMAGE_YUV10COL,
+	},
+};
+
+static const struct vc_image_format *vc4_get_vc_image_fmt(u32 drm_format)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(vc_image_formats); i++) {
+		if (vc_image_formats[i].drm == drm_format)
+			return &vc_image_formats[i];
+	}
+
+	return NULL;
+}
+
+/* The firmware delivers a vblank interrupt to us through the SMI
+ * hardware, which has only this one register.
+ */
+#define SMICS 0x0
+#define SMIDSW0 0x14
+#define SMIDSW1 0x1C
+#define SMICS_INTERRUPTS (BIT(9) | BIT(10) | BIT(11))
+
+/* Flag to denote that the firmware is giving multiple display callbacks */
+#define SMI_NEW 0xabcd0000
+
+struct vc4_fkms_crtc {
+	struct drm_crtc base;
+	struct drm_encoder *encoder;
+	struct drm_connector *connector;
+	void __iomem *regs;
+
+	struct drm_pending_vblank_event *event;
+	bool vblank_enabled;
+	u32 display_number;
+	u32 display_type;
+};
+
+static inline struct vc4_fkms_crtc *to_vc4_fkms_crtc(struct drm_crtc *crtc)
+{
+	return container_of(crtc, struct vc4_fkms_crtc, base);
+}
+
+struct vc4_fkms_encoder {
+	struct drm_encoder base;
+	bool hdmi_monitor;
+	bool rgb_range_selectable;
+	int display_num;
+};
+
+static inline struct vc4_fkms_encoder *
+to_vc4_fkms_encoder(struct drm_encoder *encoder)
+{
+	return container_of(encoder, struct vc4_fkms_encoder, base);
+}
+
+/* "Broadcast RGB" property.
+ * Allows overriding of HDMI full or limited range RGB
+ */
+#define VC4_BROADCAST_RGB_AUTO 0
+#define VC4_BROADCAST_RGB_FULL 1
+#define VC4_BROADCAST_RGB_LIMITED 2
+
+/* VC4 FKMS connector KMS struct */
+struct vc4_fkms_connector {
+	struct drm_connector base;
+
+	/* Since the connector is attached to just the one encoder,
+	 * this is the reference to it so we can do the best_encoder()
+	 * hook.
+	 */
+	struct drm_encoder *encoder;
+	struct vc4_dev *vc4_dev;
+	u32 display_number;
+	u32 display_type;
+
+	struct drm_property *broadcast_rgb_property;
+};
+
+static inline struct vc4_fkms_connector *
+to_vc4_fkms_connector(struct drm_connector *connector)
+{
+	return container_of(connector, struct vc4_fkms_connector, base);
+}
+
+/* VC4 FKMS connector state */
+struct vc4_fkms_connector_state {
+	struct drm_connector_state base;
+
+	int broadcast_rgb;
+};
+
+#define to_vc4_fkms_connector_state(x) \
+			container_of(x, struct vc4_fkms_connector_state, base)
+
+static u32 vc4_get_display_type(u32 display_number)
+{
+	const u32 display_types[] = {
+		/* The firmware display (DispmanX) IDs map to specific types in
+		 * a fixed manner.
+		 */
+		DRM_MODE_ENCODER_DSI,	/* MAIN_LCD - DSI or DPI */
+		DRM_MODE_ENCODER_DSI,	/* AUX_LCD */
+		DRM_MODE_ENCODER_TMDS,	/* HDMI0 */
+		DRM_MODE_ENCODER_TVDAC,	/* VEC */
+		DRM_MODE_ENCODER_NONE,	/* FORCE_LCD */
+		DRM_MODE_ENCODER_NONE,	/* FORCE_TV */
+		DRM_MODE_ENCODER_NONE,	/* FORCE_OTHER */
+		DRM_MODE_ENCODER_TMDS,	/* HDMI1 */
+		DRM_MODE_ENCODER_NONE,	/* FORCE_TV2 */
+	};
+	return display_number > ARRAY_SIZE(display_types) - 1 ?
+			DRM_MODE_ENCODER_NONE : display_types[display_number];
+}
+
+/* Firmware's structure for making an FB mbox call. */
+struct fbinfo_s {
+	u32 xres, yres, xres_virtual, yres_virtual;
+	u32 pitch, bpp;
+	u32 xoffset, yoffset;
+	u32 base;
+	u32 screen_size;
+	u16 cmap[256];
+};
+
+struct vc4_fkms_plane {
+	struct drm_plane base;
+	struct fbinfo_s *fbinfo;
+	dma_addr_t fbinfo_bus_addr;
+	u32 pitch;
+	struct mailbox_set_plane mb;
+};
+
+static inline struct vc4_fkms_plane *to_vc4_fkms_plane(struct drm_plane *plane)
+{
+	return (struct vc4_fkms_plane *)plane;
+}
+
+static int vc4_plane_set_blank(struct drm_plane *plane, bool blank)
+{
+	struct vc4_dev *vc4 = to_vc4_dev(plane->dev);
+	struct vc4_fkms_plane *vc4_plane = to_vc4_fkms_plane(plane);
+	struct mailbox_set_plane blank_mb = {
+		.tag = { RPI_FIRMWARE_SET_PLANE, sizeof(struct set_plane), 0 },
+		.plane = {
+			.display = vc4_plane->mb.plane.display,
+			.plane_id = vc4_plane->mb.plane.plane_id,
+		}
+	};
+	static const char * const plane_types[] = {
+							"overlay",
+							"primary",
+							"cursor"
+						  };
+	int ret;
+
+	DRM_DEBUG_ATOMIC("[PLANE:%d:%s] %s plane %s",
+			 plane->base.id, plane->name, plane_types[plane->type],
+			 blank ? "blank" : "unblank");
+
+	if (blank)
+		ret = rpi_firmware_property_list(vc4->firmware, &blank_mb,
+						 sizeof(blank_mb));
+	else
+		ret = rpi_firmware_property_list(vc4->firmware, &vc4_plane->mb,
+						 sizeof(vc4_plane->mb));
+
+	WARN_ONCE(ret, "%s: firmware call failed. Please update your firmware",
+		  __func__);
+	return ret;
+}
+
+static void vc4_fkms_crtc_get_margins(struct drm_crtc_state *state,
+				      unsigned int *left, unsigned int *right,
+				      unsigned int *top, unsigned int *bottom)
+{
+	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(state);
+	struct drm_connector_state *conn_state;
+	struct drm_connector *conn;
+	int i;
+
+	*left = vc4_state->margins.left;
+	*right = vc4_state->margins.right;
+	*top = vc4_state->margins.top;
+	*bottom = vc4_state->margins.bottom;
+
+	/* We have to interate over all new connector states because
+	 * vc4_fkms_crtc_get_margins() might be called before
+	 * vc4_fkms_crtc_atomic_check() which means margins info in
+	 * vc4_crtc_state might be outdated.
+	 */
+	for_each_new_connector_in_state(state->state, conn, conn_state, i) {
+		if (conn_state->crtc != state->crtc)
+			continue;
+
+		*left = conn_state->tv.margins.left;
+		*right = conn_state->tv.margins.right;
+		*top = conn_state->tv.margins.top;
+		*bottom = conn_state->tv.margins.bottom;
+		break;
+	}
+}
+
+static int vc4_fkms_margins_adj(struct drm_plane_state *pstate,
+				struct set_plane *plane)
+{
+	unsigned int left, right, top, bottom;
+	int adjhdisplay, adjvdisplay;
+	struct drm_crtc_state *crtc_state;
+
+	crtc_state = drm_atomic_get_new_crtc_state(pstate->state,
+						   pstate->crtc);
+
+	vc4_fkms_crtc_get_margins(crtc_state, &left, &right, &top, &bottom);
+
+	if (!left && !right && !top && !bottom)
+		return 0;
+
+	if (left + right >= crtc_state->mode.hdisplay ||
+	    top + bottom >= crtc_state->mode.vdisplay)
+		return -EINVAL;
+
+	adjhdisplay = crtc_state->mode.hdisplay - (left + right);
+	plane->dst_x = DIV_ROUND_CLOSEST(plane->dst_x * adjhdisplay,
+					 (int)crtc_state->mode.hdisplay);
+	plane->dst_x += left;
+	if (plane->dst_x > (int)(crtc_state->mode.hdisplay - right))
+		plane->dst_x = crtc_state->mode.hdisplay - right;
+
+	adjvdisplay = crtc_state->mode.vdisplay - (top + bottom);
+	plane->dst_y = DIV_ROUND_CLOSEST(plane->dst_y * adjvdisplay,
+					 (int)crtc_state->mode.vdisplay);
+	plane->dst_y += top;
+	if (plane->dst_y > (int)(crtc_state->mode.vdisplay - bottom))
+		plane->dst_y = crtc_state->mode.vdisplay - bottom;
+
+	plane->dst_w = DIV_ROUND_CLOSEST(plane->dst_w * adjhdisplay,
+					 crtc_state->mode.hdisplay);
+	plane->dst_h = DIV_ROUND_CLOSEST(plane->dst_h * adjvdisplay,
+					 crtc_state->mode.vdisplay);
+
+	if (!plane->dst_w || !plane->dst_h)
+		return -EINVAL;
+
+	return 0;
+}
+
+static void vc4_plane_atomic_update(struct drm_plane *plane,
+				    struct drm_atomic_state *old_state)
+{
+	struct drm_plane_state *state = plane->state;
+
+	/*
+	 * Do NOT set now, as we haven't checked if the crtc is active or not.
+	 * Set from vc4_plane_set_blank instead.
+	 *
+	 * If the CRTC is on (or going to be on) and we're enabled,
+	 * then unblank.  Otherwise, stay blank until CRTC enable.
+	 */
+	if (state->crtc->state->active)
+		vc4_plane_set_blank(plane, false);
+}
+
+static void vc4_plane_atomic_disable(struct drm_plane *plane,
+				     struct drm_atomic_state *old_state)
+{
+	struct drm_plane_state *state = plane->state;
+	struct vc4_fkms_plane *vc4_plane = to_vc4_fkms_plane(plane);
+
+	DRM_DEBUG_ATOMIC("[PLANE:%d:%s] plane disable %dx%d@%d +%d,%d\n",
+			 plane->base.id, plane->name,
+			 state->crtc_w,
+			 state->crtc_h,
+			 vc4_plane->mb.plane.vc_image_type,
+			 state->crtc_x,
+			 state->crtc_y);
+	vc4_plane_set_blank(plane, true);
+}
+
+static bool plane_enabled(struct drm_plane_state *state)
+{
+	return state->fb && state->crtc;
+}
+
+static int vc4_plane_to_mb(struct drm_plane *plane,
+			   struct mailbox_set_plane *mb,
+			   struct drm_plane_state *state)
+{
+	struct drm_framebuffer *fb = state->fb;
+	struct drm_gem_dma_object *bo;
+	const struct drm_format_info *drm_fmt = fb->format;
+	const struct vc_image_format *vc_fmt =
+					vc4_get_vc_image_fmt(drm_fmt->format);
+	int num_planes = fb->format->num_planes;
+	unsigned int rotation;
+
+	mb->plane.vc_image_type = vc_fmt->vc_image;
+	mb->plane.width = fb->width;
+	mb->plane.height = fb->height;
+	mb->plane.pitch = fb->pitches[0];
+	mb->plane.src_w = state->src_w;
+	mb->plane.src_h = state->src_h;
+	mb->plane.src_x = state->src_x;
+	mb->plane.src_y = state->src_y;
+	mb->plane.dst_w = state->crtc_w;
+	mb->plane.dst_h = state->crtc_h;
+	mb->plane.dst_x = state->crtc_x;
+	mb->plane.dst_y = state->crtc_y;
+	mb->plane.alpha = state->alpha >> 8;
+	mb->plane.layer = state->normalized_zpos ?
+					state->normalized_zpos : -127;
+	mb->plane.num_planes = num_planes;
+	mb->plane.is_vu = vc_fmt->is_vu;
+	bo = drm_fb_dma_get_gem_obj(fb, 0);
+	mb->plane.planes[0] = bo->dma_addr + fb->offsets[0];
+
+	rotation = drm_rotation_simplify(state->rotation,
+					 DRM_MODE_ROTATE_0 |
+					 DRM_MODE_REFLECT_X |
+					 DRM_MODE_REFLECT_Y);
+
+	mb->plane.transform = TRANSFORM_NO_ROTATE;
+	if (rotation & DRM_MODE_REFLECT_X)
+		mb->plane.transform |= TRANSFORM_FLIP_HRIZ;
+	if (rotation & DRM_MODE_REFLECT_Y)
+		mb->plane.transform |= TRANSFORM_FLIP_VERT;
+
+	vc4_fkms_margins_adj(state, &mb->plane);
+
+	if (num_planes > 1) {
+		/* Assume this must be YUV */
+		/* Makes assumptions on the stride for the chroma planes as we
+		 * can't easily plumb in non-standard pitches.
+		 */
+		bo = drm_fb_dma_get_gem_obj(fb, 1);
+		mb->plane.planes[1] = bo->dma_addr + fb->offsets[1];
+		if (num_planes > 2) {
+			bo = drm_fb_dma_get_gem_obj(fb, 2);
+			mb->plane.planes[2] = bo->dma_addr + fb->offsets[2];
+		} else {
+			mb->plane.planes[2] = 0;
+		}
+
+		/* Special case the YUV420 with U and V as line interleaved
+		 * planes as we have special handling for that case.
+		 */
+		if (num_planes == 3 &&
+		    (fb->offsets[2] - fb->offsets[1]) == fb->pitches[1])
+			mb->plane.vc_image_type = VC_IMAGE_YUV420_S;
+
+		switch (state->color_encoding) {
+		default:
+		case DRM_COLOR_YCBCR_BT601:
+			if (state->color_range == DRM_COLOR_YCBCR_LIMITED_RANGE)
+				mb->plane.color_encoding =
+						VC_IMAGE_YUVINFO_CSC_ITUR_BT601;
+			else
+				mb->plane.color_encoding =
+						VC_IMAGE_YUVINFO_CSC_JPEG_JFIF;
+			break;
+		case DRM_COLOR_YCBCR_BT709:
+			/* Currently no support for a full range BT709 */
+			mb->plane.color_encoding =
+						VC_IMAGE_YUVINFO_CSC_ITUR_BT709;
+			break;
+		case DRM_COLOR_YCBCR_BT2020:
+			/* Currently no support for a full range BT2020 */
+			mb->plane.color_encoding =
+					VC_IMAGE_YUVINFO_CSC_REC_2020;
+			break;
+		}
+	} else {
+		mb->plane.planes[1] = 0;
+		mb->plane.planes[2] = 0;
+	}
+	mb->plane.planes[3] = 0;
+
+	switch (fourcc_mod_broadcom_mod(fb->modifier)) {
+	case DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED:
+		switch (mb->plane.vc_image_type) {
+		case VC_IMAGE_XRGB8888:
+			mb->plane.vc_image_type = VC_IMAGE_TF_RGBX32;
+			break;
+		case VC_IMAGE_ARGB8888:
+			mb->plane.vc_image_type = VC_IMAGE_TF_RGBA32;
+			break;
+		case VC_IMAGE_RGB565:
+			mb->plane.vc_image_type = VC_IMAGE_TF_RGB565;
+			break;
+		}
+		break;
+	case DRM_FORMAT_MOD_BROADCOM_SAND128:
+		switch (mb->plane.vc_image_type) {
+		case VC_IMAGE_YUV420SP:
+			mb->plane.vc_image_type = VC_IMAGE_YUV_UV;
+			break;
+		/* VC_IMAGE_YUV10COL could be included in here, but it is only
+		 * valid as a SAND128 format, so the table at the top will have
+		 * already set the correct format.
+		 */
+		}
+		/* Note that the column pitch is passed across in lines, not
+		 * bytes.
+		 */
+		mb->plane.pitch = fourcc_mod_broadcom_param(fb->modifier);
+		break;
+	}
+
+	DRM_DEBUG_ATOMIC("[PLANE:%d:%s] plane update %dx%d@%d +dst(%d,%d, %d,%d) +src(%d,%d, %d,%d) 0x%08x/%08x/%08x/%d, alpha %u zpos %u\n",
+			 plane->base.id, plane->name,
+			 mb->plane.width,
+			 mb->plane.height,
+			 mb->plane.vc_image_type,
+			 state->crtc_x,
+			 state->crtc_y,
+			 state->crtc_w,
+			 state->crtc_h,
+			 mb->plane.src_x,
+			 mb->plane.src_y,
+			 mb->plane.src_w,
+			 mb->plane.src_h,
+			 mb->plane.planes[0],
+			 mb->plane.planes[1],
+			 mb->plane.planes[2],
+			 fb->pitches[0],
+			 state->alpha,
+			 state->normalized_zpos);
+
+	return 0;
+}
+
+static int vc4_fkms_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 vc4_fkms_plane *vc4_plane = to_vc4_fkms_plane(plane);
+
+	if (!plane_enabled(new_plane_state))
+		return 0;
+
+	return vc4_plane_to_mb(plane, &vc4_plane->mb, new_plane_state);
+}
+
+static void vc4_plane_atomic_async_update(struct drm_plane *plane,
+					  struct drm_atomic_state *state)
+{
+	struct drm_plane_state *new_plane_state =
+		drm_atomic_get_new_plane_state(state, plane);
+
+	swap(plane->state->fb, new_plane_state->fb);
+	plane->state->crtc_x = new_plane_state->crtc_x;
+	plane->state->crtc_y = new_plane_state->crtc_y;
+	plane->state->crtc_w = new_plane_state->crtc_w;
+	plane->state->crtc_h = new_plane_state->crtc_h;
+	plane->state->src_x = new_plane_state->src_x;
+	plane->state->src_y = new_plane_state->src_y;
+	plane->state->src_w = new_plane_state->src_w;
+	plane->state->src_h = new_plane_state->src_h;
+	plane->state->alpha = new_plane_state->alpha;
+	plane->state->pixel_blend_mode = new_plane_state->pixel_blend_mode;
+	plane->state->rotation = new_plane_state->rotation;
+	plane->state->zpos = new_plane_state->zpos;
+	plane->state->normalized_zpos = new_plane_state->normalized_zpos;
+	plane->state->color_encoding = new_plane_state->color_encoding;
+	plane->state->color_range = new_plane_state->color_range;
+	plane->state->src = new_plane_state->src;
+	plane->state->dst = new_plane_state->dst;
+	plane->state->visible = new_plane_state->visible;
+
+	vc4_plane_set_blank(plane, false);
+}
+
+static int vc4_plane_atomic_async_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);
+	int ret = -EINVAL;
+
+	if (plane->type == 2 &&
+	    plane->state->fb &&
+	    new_plane_state->crtc->state->active)
+		ret = 0;
+
+	return ret;
+}
+
+/* Called during init to allocate the plane's atomic state. */
+static void vc4_fkms_plane_reset(struct drm_plane *plane)
+{
+	struct vc4_plane_state *vc4_state;
+
+	WARN_ON(plane->state);
+
+	vc4_state = kzalloc(sizeof(*vc4_state), GFP_KERNEL);
+	if (!vc4_state)
+		return;
+
+	__drm_atomic_helper_plane_reset(plane, &vc4_state->base);
+}
+
+static void vc4_plane_destroy(struct drm_plane *plane)
+{
+	drm_plane_cleanup(plane);
+}
+
+static bool vc4_fkms_format_mod_supported(struct drm_plane *plane,
+					  uint32_t format,
+					  uint64_t modifier)
+{
+	/* Support T_TILING for RGB formats only. */
+	switch (format) {
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_RGB565:
+		switch (modifier) {
+		case DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED:
+		case DRM_FORMAT_MOD_LINEAR:
+			return true;
+		default:
+			return false;
+		}
+	case DRM_FORMAT_NV12:
+		switch (fourcc_mod_broadcom_mod(modifier)) {
+		case DRM_FORMAT_MOD_LINEAR:
+		case DRM_FORMAT_MOD_BROADCOM_SAND128:
+			return true;
+		default:
+			return false;
+		}
+	case DRM_FORMAT_P030:
+		switch (fourcc_mod_broadcom_mod(modifier)) {
+		case DRM_FORMAT_MOD_BROADCOM_SAND128:
+			return true;
+		default:
+			return false;
+		}
+	case DRM_FORMAT_NV21:
+	case DRM_FORMAT_RGB888:
+	case DRM_FORMAT_BGR888:
+	case DRM_FORMAT_YUV422:
+	case DRM_FORMAT_YUV420:
+	case DRM_FORMAT_YVU420:
+	default:
+		return (modifier == DRM_FORMAT_MOD_LINEAR);
+	}
+}
+
+static struct drm_plane_state *vc4_fkms_plane_duplicate_state(struct drm_plane *plane)
+{
+	struct vc4_plane_state *vc4_state;
+
+	if (WARN_ON(!plane->state))
+		return NULL;
+
+	vc4_state = kzalloc(sizeof(*vc4_state), GFP_KERNEL);
+	if (!vc4_state)
+		return NULL;
+
+	__drm_atomic_helper_plane_duplicate_state(plane, &vc4_state->base);
+
+	return &vc4_state->base;
+}
+
+static const struct drm_plane_funcs vc4_plane_funcs = {
+	.update_plane = drm_atomic_helper_update_plane,
+	.disable_plane = drm_atomic_helper_disable_plane,
+	.destroy = vc4_plane_destroy,
+	.set_property = NULL,
+	.reset = vc4_fkms_plane_reset,
+	.atomic_duplicate_state = vc4_fkms_plane_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+	.format_mod_supported = vc4_fkms_format_mod_supported,
+};
+
+static const struct drm_plane_helper_funcs vc4_plane_helper_funcs = {
+	.prepare_fb = drm_gem_plane_helper_prepare_fb,
+	.cleanup_fb = NULL,
+	.atomic_check = vc4_fkms_plane_atomic_check,
+	.atomic_update = vc4_plane_atomic_update,
+	.atomic_disable = vc4_plane_atomic_disable,
+	.atomic_async_check = vc4_plane_atomic_async_check,
+	.atomic_async_update = vc4_plane_atomic_async_update,
+};
+
+static struct drm_plane *vc4_fkms_plane_init(struct drm_device *dev,
+					     enum drm_plane_type type,
+					     u8 display_num,
+					     u8 plane_id)
+{
+	struct drm_plane *plane = NULL;
+	struct vc4_fkms_plane *vc4_plane;
+	u32 formats[ARRAY_SIZE(vc_image_formats)];
+	unsigned int default_zpos = 0;
+	u32 num_formats = 0;
+	int ret = 0;
+	static const uint64_t modifiers[] = {
+		DRM_FORMAT_MOD_LINEAR,
+		/* VC4_T_TILED should come after linear, because we
+		 * would prefer to scan out linear (less bus traffic).
+		 */
+		DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED,
+		DRM_FORMAT_MOD_BROADCOM_SAND128,
+		DRM_FORMAT_MOD_INVALID,
+	};
+	int i;
+
+	vc4_plane = devm_kzalloc(dev->dev, sizeof(*vc4_plane),
+				 GFP_KERNEL);
+	if (!vc4_plane) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(vc_image_formats); i++)
+		formats[num_formats++] = vc_image_formats[i].drm;
+
+	plane = &vc4_plane->base;
+	ret = drm_universal_plane_init(dev, plane, 0,
+				       &vc4_plane_funcs,
+				       formats, num_formats, modifiers,
+				       type, NULL);
+
+	/* FIXME: Do we need to be checking return values from all these calls?
+	 */
+	drm_plane_helper_add(plane, &vc4_plane_helper_funcs);
+
+	drm_plane_create_alpha_property(plane);
+	drm_plane_create_rotation_property(plane, DRM_MODE_ROTATE_0,
+					   DRM_MODE_ROTATE_0 |
+					   DRM_MODE_ROTATE_180 |
+					   DRM_MODE_REFLECT_X |
+					   DRM_MODE_REFLECT_Y);
+	drm_plane_create_color_properties(plane,
+					  BIT(DRM_COLOR_YCBCR_BT601) |
+					  BIT(DRM_COLOR_YCBCR_BT709) |
+					  BIT(DRM_COLOR_YCBCR_BT2020),
+					  BIT(DRM_COLOR_YCBCR_LIMITED_RANGE) |
+					  BIT(DRM_COLOR_YCBCR_FULL_RANGE),
+					  DRM_COLOR_YCBCR_BT709,
+					  DRM_COLOR_YCBCR_LIMITED_RANGE);
+
+	/*
+	 * Default frame buffer setup is with FB on -127, and raspistill etc
+	 * tend to drop overlays on layer 2. Cursor plane was on layer +127.
+	 *
+	 * For F-KMS the mailbox call allows for a s8.
+	 * Remap zpos 0 to -127 for the background layer, but leave all the
+	 * other layers as requested by KMS.
+	 */
+	switch (type) {
+	default:
+	case DRM_PLANE_TYPE_PRIMARY:
+		default_zpos = 0;
+		break;
+	case DRM_PLANE_TYPE_OVERLAY:
+		default_zpos = 1;
+		break;
+	case DRM_PLANE_TYPE_CURSOR:
+		default_zpos = 2;
+		break;
+	}
+	drm_plane_create_zpos_property(plane, default_zpos, 0, 127);
+
+	/* Prepare the static elements of the mailbox structure */
+	vc4_plane->mb.tag.tag = RPI_FIRMWARE_SET_PLANE;
+	vc4_plane->mb.tag.buf_size = sizeof(struct set_plane);
+	vc4_plane->mb.tag.req_resp_size = 0;
+	vc4_plane->mb.plane.display = display_num;
+	vc4_plane->mb.plane.plane_id = plane_id;
+	vc4_plane->mb.plane.layer = default_zpos ? default_zpos : -127;
+
+	return plane;
+fail:
+	if (plane)
+		vc4_plane_destroy(plane);
+
+	return ERR_PTR(ret);
+}
+
+static void vc4_crtc_mode_set_nofb(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+	struct vc4_dev *vc4 = to_vc4_dev(dev);
+	struct vc4_fkms_crtc *vc4_fkms_crtc = to_vc4_fkms_crtc(crtc);
+	struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+	struct vc4_fkms_encoder *vc4_encoder =
+					to_vc4_fkms_encoder(vc4_fkms_crtc->encoder);
+	struct mailbox_set_mode mb = {
+		.tag1 = { RPI_FIRMWARE_SET_TIMING,
+			  sizeof(struct set_timings), 0},
+	};
+	union hdmi_infoframe frame;
+	int ret;
+
+	ret = drm_hdmi_avi_infoframe_from_display_mode(&frame.avi, vc4_fkms_crtc->connector, mode);
+	if (ret < 0) {
+		DRM_ERROR("couldn't fill AVI infoframe\n");
+		return;
+	}
+
+	DRM_DEBUG_KMS("Setting mode for display num %u mode name %s, clk %d, h(disp %d, start %d, end %d, total %d, skew %d) v(disp %d, start %d, end %d, total %d, scan %d), vrefresh %d, par %u, flags 0x%04x\n",
+		      vc4_fkms_crtc->display_number, mode->name, mode->clock,
+		      mode->hdisplay, mode->hsync_start, mode->hsync_end,
+		      mode->htotal, mode->hskew, mode->vdisplay,
+		      mode->vsync_start, mode->vsync_end, mode->vtotal,
+		      mode->vscan, drm_mode_vrefresh(mode),
+		      mode->picture_aspect_ratio, mode->flags);
+	mb.timings.display = vc4_fkms_crtc->display_number;
+
+	mb.timings.clock = mode->clock;
+	mb.timings.hdisplay = mode->hdisplay;
+	mb.timings.hsync_start = mode->hsync_start;
+	mb.timings.hsync_end = mode->hsync_end;
+	mb.timings.htotal = mode->htotal;
+	mb.timings.hskew = mode->hskew;
+	mb.timings.vdisplay = mode->vdisplay;
+	mb.timings.vsync_start = mode->vsync_start;
+	mb.timings.vsync_end = mode->vsync_end;
+	mb.timings.vtotal = mode->vtotal;
+	mb.timings.vscan = mode->vscan;
+	mb.timings.vrefresh = drm_mode_vrefresh(mode);
+	mb.timings.flags = 0;
+	if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+		mb.timings.flags |= TIMINGS_FLAGS_H_SYNC_POS;
+	if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+		mb.timings.flags |= TIMINGS_FLAGS_V_SYNC_POS;
+
+	switch (frame.avi.picture_aspect) {
+	default:
+	case HDMI_PICTURE_ASPECT_NONE:
+		mb.timings.flags |= TIMINGS_FLAGS_ASPECT_NONE;
+		break;
+	case HDMI_PICTURE_ASPECT_4_3:
+		mb.timings.flags |= TIMINGS_FLAGS_ASPECT_4_3;
+		break;
+	case HDMI_PICTURE_ASPECT_16_9:
+		mb.timings.flags |= TIMINGS_FLAGS_ASPECT_16_9;
+		break;
+	case HDMI_PICTURE_ASPECT_64_27:
+		mb.timings.flags |= TIMINGS_FLAGS_ASPECT_64_27;
+		break;
+	case HDMI_PICTURE_ASPECT_256_135:
+		mb.timings.flags |= TIMINGS_FLAGS_ASPECT_256_135;
+		break;
+	}
+
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+		mb.timings.flags |= TIMINGS_FLAGS_INTERLACE;
+	if (mode->flags & DRM_MODE_FLAG_DBLCLK)
+		mb.timings.flags |= TIMINGS_FLAGS_DBL_CLK;
+
+	mb.timings.video_id_code = frame.avi.video_code;
+
+	if (!vc4_encoder->hdmi_monitor) {
+		mb.timings.flags |= TIMINGS_FLAGS_DVI;
+	} else {
+		struct vc4_fkms_connector_state *conn_state =
+			to_vc4_fkms_connector_state(vc4_fkms_crtc->connector->state);
+
+		if (conn_state->broadcast_rgb == VC4_BROADCAST_RGB_AUTO) {
+			/* See CEA-861-E - 5.1 Default Encoding Parameters */
+			if (drm_default_rgb_quant_range(mode) ==
+					HDMI_QUANTIZATION_RANGE_LIMITED)
+				mb.timings.flags |= TIMINGS_FLAGS_RGB_LIMITED;
+		} else {
+			if (conn_state->broadcast_rgb ==
+						VC4_BROADCAST_RGB_LIMITED)
+				mb.timings.flags |= TIMINGS_FLAGS_RGB_LIMITED;
+
+			/* If not using the default range, then do not provide
+			 * a VIC as the HDMI spec requires that we do not
+			 * signal the opposite of the defined range in the AVI
+			 * infoframe.
+			 */
+			if (!!(mb.timings.flags & TIMINGS_FLAGS_RGB_LIMITED) !=
+			    (drm_default_rgb_quant_range(mode) ==
+					HDMI_QUANTIZATION_RANGE_LIMITED))
+				mb.timings.video_id_code = 0;
+		}
+	}
+
+	/*
+	 * FIXME: To implement
+	 * switch(mode->flag & DRM_MODE_FLAG_3D_MASK) {
+	 * case DRM_MODE_FLAG_3D_NONE:
+	 * case DRM_MODE_FLAG_3D_FRAME_PACKING:
+	 * case DRM_MODE_FLAG_3D_FIELD_ALTERNATIVE:
+	 * case DRM_MODE_FLAG_3D_LINE_ALTERNATIVE:
+	 * case DRM_MODE_FLAG_3D_SIDE_BY_SIDE_FULL:
+	 * case DRM_MODE_FLAG_3D_L_DEPTH:
+	 * case DRM_MODE_FLAG_3D_L_DEPTH_GFX_GFX_DEPTH:
+	 * case DRM_MODE_FLAG_3D_TOP_AND_BOTTOM:
+	 * case DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF:
+	 * }
+	 */
+
+	ret = rpi_firmware_property_list(vc4->firmware, &mb, sizeof(mb));
+}
+
+static void vc4_crtc_disable(struct drm_crtc *crtc,
+			     struct drm_atomic_state *state)
+{
+	struct drm_device *dev = crtc->dev;
+	struct drm_plane *plane;
+
+	DRM_DEBUG_KMS("[CRTC:%d] vblanks off.\n",
+		      crtc->base.id);
+	drm_crtc_vblank_off(crtc);
+
+	/* Always turn the planes off on CRTC disable. In DRM, planes
+	 * are enabled/disabled through the update/disable hooks
+	 * above, and the CRTC enable/disable independently controls
+	 * whether anything scans out at all, but the firmware doesn't
+	 * give us a CRTC-level control for that.
+	 */
+
+	drm_atomic_crtc_for_each_plane(plane, crtc)
+		vc4_plane_atomic_disable(plane, state);
+
+	/*
+	 * Make sure we issue a vblank event after disabling the CRTC if
+	 * someone was waiting it.
+	 */
+	if (crtc->state->event) {
+		unsigned long flags;
+
+		spin_lock_irqsave(&dev->event_lock, flags);
+		drm_crtc_send_vblank_event(crtc, crtc->state->event);
+		crtc->state->event = NULL;
+		spin_unlock_irqrestore(&dev->event_lock, flags);
+	}
+}
+
+static void vc4_crtc_consume_event(struct drm_crtc *crtc)
+{
+	struct vc4_fkms_crtc *vc4_fkms_crtc = to_vc4_fkms_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+	unsigned long flags;
+
+	if (!crtc->state->event)
+		return;
+
+	crtc->state->event->pipe = drm_crtc_index(crtc);
+
+	WARN_ON(drm_crtc_vblank_get(crtc) != 0);
+
+	spin_lock_irqsave(&dev->event_lock, flags);
+	vc4_fkms_crtc->event = crtc->state->event;
+	crtc->state->event = NULL;
+	spin_unlock_irqrestore(&dev->event_lock, flags);
+}
+
+static void vc4_crtc_enable(struct drm_crtc *crtc,
+			    struct drm_atomic_state *state)
+{
+	struct drm_plane *plane;
+
+	DRM_DEBUG_KMS("[CRTC:%d] vblanks on.\n",
+		      crtc->base.id);
+	drm_crtc_vblank_on(crtc);
+	vc4_crtc_consume_event(crtc);
+
+	/* Unblank the planes (if they're supposed to be displayed). */
+	drm_atomic_crtc_for_each_plane(plane, crtc)
+		if (plane->state->fb)
+			vc4_plane_set_blank(plane, plane->state->visible);
+}
+
+static enum drm_mode_status
+vc4_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
+{
+	struct vc4_fkms_crtc *vc4_fkms_crtc = to_vc4_fkms_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+	struct vc4_dev *vc4 = to_vc4_dev(dev);
+	struct vc4_fkms *fkms = vc4->fkms;
+
+	/* Do not allow doublescan modes from user space */
+	if (mode->flags & DRM_MODE_FLAG_DBLSCAN) {
+		DRM_DEBUG_KMS("[CRTC:%d] Doublescan mode rejected.\n",
+			      crtc->base.id);
+		return MODE_NO_DBLESCAN;
+	}
+
+	/* Disable refresh rates > defined threshold (default 85Hz) as limited
+	 * gain from them
+	 */
+	if (drm_mode_vrefresh(mode) > fkms_max_refresh_rate)
+		return MODE_BAD_VVALUE;
+
+	/* Limit the pixel clock based on the HDMI clock limits from the
+	 * firmware
+	 */
+	switch (vc4_fkms_crtc->display_number) {
+	case 2:	/* HDMI0 */
+		if (fkms->cfg.max_pixel_clock[0] &&
+		    mode->clock > fkms->cfg.max_pixel_clock[0])
+			return MODE_CLOCK_HIGH;
+		break;
+	case 7:	/* HDMI1 */
+		if (fkms->cfg.max_pixel_clock[1] &&
+		    mode->clock > fkms->cfg.max_pixel_clock[1])
+			return MODE_CLOCK_HIGH;
+		break;
+	}
+
+	/* Pi4 can't generate odd horizontal timings on HDMI, so reject modes
+	 * that would set them.
+	 */
+	if (fkms->revision >= BCM2711 &&
+	    (vc4_fkms_crtc->display_number == 2 || vc4_fkms_crtc->display_number == 7) &&
+	    !(mode->flags & DRM_MODE_FLAG_DBLCLK) &&
+	    ((mode->hdisplay |				/* active */
+	      (mode->hsync_start - mode->hdisplay) |	/* front porch */
+	      (mode->hsync_end - mode->hsync_start) |	/* sync pulse */
+	      (mode->htotal - mode->hsync_end)) & 1))	/* back porch */ {
+		DRM_DEBUG_KMS("[CRTC:%d] Odd timing rejected %u %u %u %u.\n",
+			      crtc->base.id, mode->hdisplay, mode->hsync_start,
+			      mode->hsync_end, mode->htotal);
+		return MODE_H_ILLEGAL;
+	}
+
+	return MODE_OK;
+}
+
+static int vc4_fkms_crtc_atomic_check(struct drm_crtc *crtc,
+				 struct drm_atomic_state *state)
+{
+	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
+									  crtc);
+	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc_state);
+	struct drm_connector *conn;
+	struct drm_connector_state *conn_state;
+	int i;
+
+	DRM_DEBUG_KMS("[CRTC:%d] crtc_atomic_check.\n", crtc->base.id);
+
+	for_each_new_connector_in_state(crtc_state->state, conn, conn_state, i) {
+		if (conn_state->crtc != crtc)
+			continue;
+
+		vc4_state->margins.left = conn_state->tv.margins.left;
+		vc4_state->margins.right = conn_state->tv.margins.right;
+		vc4_state->margins.top = conn_state->tv.margins.top;
+		vc4_state->margins.bottom = conn_state->tv.margins.bottom;
+		break;
+	}
+	return 0;
+}
+
+static void vc4_crtc_atomic_flush(struct drm_crtc *crtc,
+				  struct drm_atomic_state *state)
+{
+	struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state,
+									 crtc);
+
+	DRM_DEBUG_KMS("[CRTC:%d] crtc_atomic_flush.\n",
+		      crtc->base.id);
+	if (crtc->state->active && old_state->active && crtc->state->event)
+		vc4_crtc_consume_event(crtc);
+}
+
+static void vc4_crtc_handle_page_flip(struct vc4_fkms_crtc *vc4_fkms_crtc)
+{
+	struct drm_crtc *crtc = &vc4_fkms_crtc->base;
+	struct drm_device *dev = crtc->dev;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dev->event_lock, flags);
+	if (vc4_fkms_crtc->event) {
+		drm_crtc_send_vblank_event(crtc, vc4_fkms_crtc->event);
+		vc4_fkms_crtc->event = NULL;
+		drm_crtc_vblank_put(crtc);
+	}
+	spin_unlock_irqrestore(&dev->event_lock, flags);
+}
+
+static irqreturn_t vc4_crtc_irq_handler(int irq, void *data)
+{
+	struct vc4_fkms_crtc **crtc_list = data;
+	int i;
+	u32 stat = readl(crtc_list[0]->regs + SMICS);
+	irqreturn_t ret = IRQ_NONE;
+	u32 chan;
+
+	if (stat & SMICS_INTERRUPTS) {
+		writel(0, crtc_list[0]->regs + SMICS);
+
+		chan = readl(crtc_list[0]->regs + SMIDSW0);
+
+		if ((chan & 0xFFFF0000) != SMI_NEW) {
+			/* Older firmware. Treat the one interrupt as vblank/
+			 * complete for all crtcs.
+			 */
+			for (i = 0; crtc_list[i]; i++) {
+				if (crtc_list[i]->vblank_enabled)
+					drm_crtc_handle_vblank(&crtc_list[i]->base);
+				vc4_crtc_handle_page_flip(crtc_list[i]);
+			}
+		} else {
+			if (chan & 1) {
+				writel(SMI_NEW, crtc_list[0]->regs + SMIDSW0);
+				if (crtc_list[0]->vblank_enabled)
+					drm_crtc_handle_vblank(&crtc_list[0]->base);
+				vc4_crtc_handle_page_flip(crtc_list[0]);
+			}
+
+			if (crtc_list[1]) {
+				/* Check for the secondary display too */
+				chan = readl(crtc_list[0]->regs + SMIDSW1);
+
+				if (chan & 1) {
+					writel(SMI_NEW, crtc_list[0]->regs + SMIDSW1);
+
+					if (crtc_list[1]->vblank_enabled)
+						drm_crtc_handle_vblank(&crtc_list[1]->base);
+					vc4_crtc_handle_page_flip(crtc_list[1]);
+				}
+			}
+		}
+
+		ret = IRQ_HANDLED;
+	}
+
+	return ret;
+}
+
+static irqreturn_t vc4_crtc2712_irq_handler(int irq, void *data)
+{
+	struct vc4_fkms_crtc **crtc_list = data;
+	int i;
+
+	for (i = 0; crtc_list[i]; i++) {
+		if (crtc_list[i]->vblank_enabled)
+			drm_crtc_handle_vblank(&crtc_list[i]->base);
+		vc4_crtc_handle_page_flip(crtc_list[i]);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int vc4_fkms_page_flip(struct drm_crtc *crtc,
+			      struct drm_framebuffer *fb,
+			      struct drm_pending_vblank_event *event,
+			      uint32_t flags,
+			      struct drm_modeset_acquire_ctx *ctx)
+{
+	if (flags & DRM_MODE_PAGE_FLIP_ASYNC) {
+		DRM_ERROR("Async flips aren't allowed\n");
+		return -EINVAL;
+	}
+
+	return drm_atomic_helper_page_flip(crtc, fb, event, flags, ctx);
+}
+
+static struct drm_crtc_state *
+vc4_fkms_crtc_duplicate_state(struct drm_crtc *crtc)
+{
+	struct vc4_crtc_state *vc4_state, *old_vc4_state;
+
+	vc4_state = kzalloc(sizeof(*vc4_state), GFP_KERNEL);
+	if (!vc4_state)
+		return NULL;
+
+	old_vc4_state = to_vc4_crtc_state(crtc->state);
+	vc4_state->margins = old_vc4_state->margins;
+
+	__drm_atomic_helper_crtc_duplicate_state(crtc, &vc4_state->base);
+	return &vc4_state->base;
+}
+
+static void
+vc4_fkms_crtc_reset(struct drm_crtc *crtc)
+{
+	if (crtc->state)
+		__drm_atomic_helper_crtc_destroy_state(crtc->state);
+
+	crtc->state = kzalloc(sizeof(*crtc->state), GFP_KERNEL);
+	if (crtc->state)
+		crtc->state->crtc = crtc;
+}
+
+static int vc4_fkms_enable_vblank(struct drm_crtc *crtc)
+{
+	struct vc4_fkms_crtc *vc4_fkms_crtc = to_vc4_fkms_crtc(crtc);
+
+	DRM_DEBUG_KMS("[CRTC:%d] enable_vblank.\n",
+		      crtc->base.id);
+	vc4_fkms_crtc->vblank_enabled = true;
+
+	return 0;
+}
+
+static void vc4_fkms_disable_vblank(struct drm_crtc *crtc)
+{
+	struct vc4_fkms_crtc *vc4_fkms_crtc = to_vc4_fkms_crtc(crtc);
+
+	DRM_DEBUG_KMS("[CRTC:%d] disable_vblank.\n",
+		      crtc->base.id);
+	vc4_fkms_crtc->vblank_enabled = false;
+}
+
+static const struct drm_crtc_funcs vc4_crtc_funcs = {
+	.set_config = drm_atomic_helper_set_config,
+	.destroy = drm_crtc_cleanup,
+	.page_flip = vc4_fkms_page_flip,
+	.set_property = NULL,
+	.cursor_set = NULL, /* handled by drm_mode_cursor_universal */
+	.cursor_move = NULL, /* handled by drm_mode_cursor_universal */
+	.reset = vc4_fkms_crtc_reset,
+	.atomic_duplicate_state = vc4_fkms_crtc_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+	.enable_vblank = vc4_fkms_enable_vblank,
+	.disable_vblank = vc4_fkms_disable_vblank,
+};
+
+static const struct drm_crtc_helper_funcs vc4_crtc_helper_funcs = {
+	.mode_set_nofb = vc4_crtc_mode_set_nofb,
+	.mode_valid = vc4_crtc_mode_valid,
+	.atomic_check = vc4_fkms_crtc_atomic_check,
+	.atomic_flush = vc4_crtc_atomic_flush,
+	.atomic_enable = vc4_crtc_enable,
+	.atomic_disable = vc4_crtc_disable,
+};
+
+static const struct of_device_id vc4_firmware_kms_dt_match[] = {
+	{ .compatible = "raspberrypi,rpi-firmware-kms",
+	  .data = (void *)BCM2835_6_7 },
+	{ .compatible = "raspberrypi,rpi-firmware-kms-2711",
+	  .data = (void *)BCM2711 },
+	{ .compatible = "raspberrypi,rpi-firmware-kms-2712",
+	  .data = (void *)BCM2712 },
+	{}
+};
+
+static enum drm_connector_status
+vc4_fkms_connector_detect(struct drm_connector *connector, bool force)
+{
+	DRM_DEBUG_KMS("connector detect.\n");
+	return connector_status_connected;
+}
+
+/* Queries the firmware to populate a drm_mode structure for this display */
+static int vc4_fkms_get_fw_mode(struct vc4_fkms_connector *fkms_connector,
+				struct drm_display_mode *mode)
+{
+	struct vc4_dev *vc4 = fkms_connector->vc4_dev;
+	struct set_timings timings = { 0 };
+	int ret;
+
+	timings.display = fkms_connector->display_number;
+
+	ret = rpi_firmware_property(vc4->firmware,
+				    RPI_FIRMWARE_GET_DISPLAY_TIMING, &timings,
+				    sizeof(timings));
+	if (ret || !timings.clock)
+		/* No mode returned - abort */
+		return -1;
+
+	/* Equivalent to DRM_MODE macro. */
+	memset(mode, 0, sizeof(*mode));
+	strncpy(mode->name, "FIXED_MODE", sizeof(mode->name));
+	mode->status = 0;
+	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+	mode->clock = timings.clock;
+	mode->hdisplay = timings.hdisplay;
+	mode->hsync_start = timings.hsync_start;
+	mode->hsync_end = timings.hsync_end;
+	mode->htotal = timings.htotal;
+	mode->hskew = 0;
+	mode->vdisplay = timings.vdisplay;
+	mode->vsync_start = timings.vsync_start;
+	mode->vsync_end = timings.vsync_end;
+	mode->vtotal = timings.vtotal;
+	mode->vscan = timings.vscan;
+
+	if (timings.flags & TIMINGS_FLAGS_H_SYNC_POS)
+		mode->flags |= DRM_MODE_FLAG_PHSYNC;
+	else
+		mode->flags |= DRM_MODE_FLAG_NHSYNC;
+
+	if (timings.flags & TIMINGS_FLAGS_V_SYNC_POS)
+		mode->flags |= DRM_MODE_FLAG_PVSYNC;
+	else
+		mode->flags |= DRM_MODE_FLAG_NVSYNC;
+
+	if (timings.flags & TIMINGS_FLAGS_INTERLACE)
+		mode->flags |= DRM_MODE_FLAG_INTERLACE;
+
+	return 0;
+}
+
+static int vc4_fkms_get_edid_block(void *data, u8 *buf, unsigned int block,
+				   size_t len)
+{
+	struct vc4_fkms_connector *fkms_connector =
+					(struct vc4_fkms_connector *)data;
+	struct vc4_dev *vc4 = fkms_connector->vc4_dev;
+	struct mailbox_get_edid mb = {
+		.tag1 = { RPI_FIRMWARE_GET_EDID_BLOCK_DISPLAY,
+			  128 + 8, 0 },
+		.block = block,
+		.display_number = fkms_connector->display_number,
+	};
+	int ret = 0;
+
+	ret = rpi_firmware_property_list(vc4->firmware, &mb, sizeof(mb));
+
+	if (!ret)
+		memcpy(buf, mb.edid, len);
+
+	return ret;
+}
+
+static int vc4_fkms_connector_get_modes(struct drm_connector *connector)
+{
+	struct vc4_fkms_connector *fkms_connector =
+					to_vc4_fkms_connector(connector);
+	struct drm_encoder *encoder = fkms_connector->encoder;
+	struct vc4_fkms_encoder *vc4_encoder = to_vc4_fkms_encoder(encoder);
+	struct drm_display_mode fw_mode;
+	struct drm_display_mode *mode;
+	const struct drm_edid *drm_edid;
+	const struct edid *edid;
+	int num_modes;
+
+	if (!vc4_fkms_get_fw_mode(fkms_connector, &fw_mode)) {
+		drm_mode_debug_printmodeline(&fw_mode);
+		mode = drm_mode_duplicate(connector->dev,
+					  &fw_mode);
+		drm_mode_probed_add(connector, mode);
+		num_modes = 1;	/* 1 mode */
+	} else {
+		drm_edid = drm_edid_read_custom(connector, vc4_fkms_get_edid_block,
+				       fkms_connector);
+		edid = drm_edid_raw(drm_edid);
+
+		/* FIXME: Can we do CEC?
+		 * cec_s_phys_addr_from_edid(vc4->hdmi->cec_adap, edid);
+		 * if (!edid)
+		 *	return -ENODEV;
+		 */
+
+		vc4_encoder->hdmi_monitor = drm_detect_hdmi_monitor(edid);
+
+		drm_connector_update_edid_property(connector, edid);
+		num_modes = drm_add_edid_modes(connector, (struct edid *)edid);
+		kfree(drm_edid);
+	}
+
+	return num_modes;
+}
+
+/* This is the DSI panel resolution. Use this as a default should the firmware
+ * not respond to our request for the timings.
+ */
+static const struct drm_display_mode lcd_mode = {
+	DRM_MODE("800x480", DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
+		 25979400 / 1000,
+		 800, 800 + 1, 800 + 1 + 2, 800 + 1 + 2 + 46, 0,
+		 480, 480 + 7, 480 + 7 + 2, 480 + 7 + 2 + 21, 0,
+		 0)
+};
+
+static int vc4_fkms_lcd_connector_get_modes(struct drm_connector *connector)
+{
+	struct vc4_fkms_connector *fkms_connector =
+					to_vc4_fkms_connector(connector);
+	struct drm_display_mode *mode;
+	struct drm_display_mode fw_mode;
+
+	if (!vc4_fkms_get_fw_mode(fkms_connector, &fw_mode) && fw_mode.clock)
+		mode = drm_mode_duplicate(connector->dev,
+					  &fw_mode);
+	else
+		mode = drm_mode_duplicate(connector->dev,
+					  &lcd_mode);
+
+	if (!mode) {
+		DRM_ERROR("Failed to create a new display mode\n");
+		return -ENOMEM;
+	}
+
+	drm_mode_probed_add(connector, mode);
+
+	/* We have one mode */
+	return 1;
+}
+
+static struct drm_encoder *
+vc4_fkms_connector_best_encoder(struct drm_connector *connector)
+{
+	struct vc4_fkms_connector *fkms_connector =
+		to_vc4_fkms_connector(connector);
+	DRM_DEBUG_KMS("best_connector.\n");
+	return fkms_connector->encoder;
+}
+
+static void vc4_fkms_connector_destroy(struct drm_connector *connector)
+{
+	DRM_DEBUG_KMS("[CONNECTOR:%d] destroy.\n",
+		      connector->base.id);
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+}
+
+/**
+ * vc4_connector_duplicate_state - duplicate connector state
+ * @connector: digital connector
+ *
+ * Allocates and returns a copy of the connector state (both common and
+ * digital connector specific) for the specified connector.
+ *
+ * Returns: The newly allocated connector state, or NULL on failure.
+ */
+static struct drm_connector_state *
+vc4_connector_duplicate_state(struct drm_connector *connector)
+{
+	struct vc4_fkms_connector_state *state;
+
+	state = kmemdup(connector->state, sizeof(*state), GFP_KERNEL);
+	if (!state)
+		return NULL;
+
+	__drm_atomic_helper_connector_duplicate_state(connector, &state->base);
+	return &state->base;
+}
+
+/**
+ * vc4_connector_atomic_get_property - hook for connector->atomic_get_property.
+ * @connector: Connector to get the property for.
+ * @state: Connector state to retrieve the property from.
+ * @property: Property to retrieve.
+ * @val: Return value for the property.
+ *
+ * Returns the atomic property value for a digital connector.
+ */
+static int vc4_connector_atomic_get_property(struct drm_connector *connector,
+				      const struct drm_connector_state *state,
+				      struct drm_property *property,
+				      uint64_t *val)
+{
+	struct vc4_fkms_connector *fkms_connector =
+					to_vc4_fkms_connector(connector);
+	struct vc4_fkms_connector_state *vc4_conn_state =
+					to_vc4_fkms_connector_state(state);
+
+	if (property == fkms_connector->broadcast_rgb_property) {
+		*val = vc4_conn_state->broadcast_rgb;
+	} else {
+		DRM_DEBUG_ATOMIC("Unknown property [PROP:%d:%s]\n",
+				 property->base.id, property->name);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/**
+ * vc4_connector_atomic_set_property - hook for connector->atomic_set_property.
+ * @connector: Connector to set the property for.
+ * @state: Connector state to set the property on.
+ * @property: Property to set.
+ * @val: New value for the property.
+ *
+ * Sets the atomic property value for a digital connector.
+ */
+static int vc4_connector_atomic_set_property(struct drm_connector *connector,
+				      struct drm_connector_state *state,
+				      struct drm_property *property,
+				      uint64_t val)
+{
+	struct vc4_fkms_connector *fkms_connector =
+					to_vc4_fkms_connector(connector);
+	struct vc4_fkms_connector_state *vc4_conn_state =
+					to_vc4_fkms_connector_state(state);
+
+	if (property == fkms_connector->broadcast_rgb_property) {
+		vc4_conn_state->broadcast_rgb = val;
+		return 0;
+	}
+
+	DRM_DEBUG_ATOMIC("Unknown property [PROP:%d:%s]\n",
+			 property->base.id, property->name);
+	return -EINVAL;
+}
+
+static int vc4_connector_atomic_check(struct drm_connector *connector,
+			       struct drm_atomic_state *state)
+{
+	struct drm_connector_state *old_state =
+		drm_atomic_get_old_connector_state(state, connector);
+	struct vc4_fkms_connector_state *vc4_old_state =
+					to_vc4_fkms_connector_state(old_state);
+	struct drm_connector_state *new_state =
+		drm_atomic_get_new_connector_state(state, connector);
+	struct vc4_fkms_connector_state *vc4_new_state =
+					to_vc4_fkms_connector_state(new_state);
+	struct drm_crtc *crtc = new_state->crtc;
+
+	if (!crtc)
+		return 0;
+
+	if (vc4_old_state->broadcast_rgb != vc4_new_state->broadcast_rgb) {
+		struct drm_crtc_state *crtc_state;
+
+		crtc_state = drm_atomic_get_crtc_state(state, crtc);
+		if (IS_ERR(crtc_state))
+			return PTR_ERR(crtc_state);
+
+		crtc_state->mode_changed = true;
+	}
+	return 0;
+}
+
+static void vc4_hdmi_connector_reset(struct drm_connector *connector)
+{
+	drm_atomic_helper_connector_reset(connector);
+	drm_atomic_helper_connector_tv_margins_reset(connector);
+}
+
+static const struct drm_connector_funcs vc4_fkms_connector_funcs = {
+	.detect = vc4_fkms_connector_detect,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = vc4_fkms_connector_destroy,
+	.reset = vc4_hdmi_connector_reset,
+	.atomic_duplicate_state = vc4_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+	.atomic_get_property = vc4_connector_atomic_get_property,
+	.atomic_set_property = vc4_connector_atomic_set_property,
+};
+
+static const struct drm_connector_helper_funcs vc4_fkms_connector_helper_funcs = {
+	.get_modes = vc4_fkms_connector_get_modes,
+	.best_encoder = vc4_fkms_connector_best_encoder,
+	.atomic_check = vc4_connector_atomic_check,
+};
+
+static const struct drm_connector_helper_funcs vc4_fkms_lcd_conn_helper_funcs = {
+	.get_modes = vc4_fkms_lcd_connector_get_modes,
+	.best_encoder = vc4_fkms_connector_best_encoder,
+};
+
+static const struct drm_prop_enum_list broadcast_rgb_names[] = {
+	{ VC4_BROADCAST_RGB_AUTO, "Automatic" },
+	{ VC4_BROADCAST_RGB_FULL, "Full" },
+	{ VC4_BROADCAST_RGB_LIMITED, "Limited 16:235" },
+};
+
+static void
+vc4_attach_broadcast_rgb_property(struct vc4_fkms_connector *fkms_connector)
+{
+	struct drm_device *dev = fkms_connector->base.dev;
+	struct drm_property *prop;
+
+	prop = fkms_connector->broadcast_rgb_property;
+	if (!prop) {
+		prop = drm_property_create_enum(dev, DRM_MODE_PROP_ENUM,
+						"Broadcast RGB",
+						broadcast_rgb_names,
+						ARRAY_SIZE(broadcast_rgb_names));
+		if (!prop)
+			return;
+
+		fkms_connector->broadcast_rgb_property = prop;
+	}
+
+	drm_object_attach_property(&fkms_connector->base.base, prop, 0);
+}
+
+static struct drm_connector *
+vc4_fkms_connector_init(struct drm_device *dev, struct drm_encoder *encoder,
+			u32 display_num)
+{
+	struct drm_connector *connector = NULL;
+	struct vc4_fkms_connector *fkms_connector;
+	struct vc4_fkms_connector_state *conn_state = NULL;
+	struct vc4_dev *vc4_dev = to_vc4_dev(dev);
+	int ret = 0;
+
+	DRM_DEBUG_KMS("connector_init, display_num %u\n", display_num);
+
+	fkms_connector = devm_kzalloc(dev->dev, sizeof(*fkms_connector),
+				      GFP_KERNEL);
+	if (!fkms_connector)
+		return ERR_PTR(-ENOMEM);
+
+	/*
+	 * Allocate enough memory to hold vc4_fkms_connector_state,
+	 */
+	conn_state = kzalloc(sizeof(*conn_state), GFP_KERNEL);
+	if (!conn_state) {
+		kfree(fkms_connector);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	connector = &fkms_connector->base;
+
+	fkms_connector->encoder = encoder;
+	fkms_connector->display_number = display_num;
+	fkms_connector->display_type = vc4_get_display_type(display_num);
+	fkms_connector->vc4_dev = vc4_dev;
+
+	__drm_atomic_helper_connector_reset(connector,
+					    &conn_state->base);
+
+	if (fkms_connector->display_type == DRM_MODE_ENCODER_DSI) {
+		drm_connector_init(dev, connector, &vc4_fkms_connector_funcs,
+				   DRM_MODE_CONNECTOR_DSI);
+		drm_connector_helper_add(connector,
+					 &vc4_fkms_lcd_conn_helper_funcs);
+		connector->interlace_allowed = 0;
+	} else if (fkms_connector->display_type == DRM_MODE_ENCODER_TVDAC) {
+		drm_connector_init(dev, connector, &vc4_fkms_connector_funcs,
+				   DRM_MODE_CONNECTOR_Composite);
+		drm_connector_helper_add(connector,
+					 &vc4_fkms_lcd_conn_helper_funcs);
+		connector->interlace_allowed = 1;
+	} else {
+		drm_connector_init(dev, connector, &vc4_fkms_connector_funcs,
+				   DRM_MODE_CONNECTOR_HDMIA);
+		drm_connector_helper_add(connector,
+					 &vc4_fkms_connector_helper_funcs);
+		connector->interlace_allowed = 1;
+	}
+
+	ret = drm_mode_create_tv_margin_properties(dev);
+	if (ret)
+		goto fail;
+
+	drm_connector_attach_tv_margin_properties(connector);
+
+	connector->polled = (DRM_CONNECTOR_POLL_CONNECT |
+			     DRM_CONNECTOR_POLL_DISCONNECT);
+
+	connector->doublescan_allowed = 0;
+
+	vc4_attach_broadcast_rgb_property(fkms_connector);
+
+	drm_connector_attach_encoder(connector, encoder);
+
+	return connector;
+
+ fail:
+	if (connector)
+		vc4_fkms_connector_destroy(connector);
+
+	return ERR_PTR(ret);
+}
+
+static void vc4_fkms_encoder_destroy(struct drm_encoder *encoder)
+{
+	DRM_DEBUG_KMS("Encoder_destroy\n");
+	drm_encoder_cleanup(encoder);
+}
+
+static const struct drm_encoder_funcs vc4_fkms_encoder_funcs = {
+	.destroy = vc4_fkms_encoder_destroy,
+};
+
+static void vc4_fkms_display_power(struct drm_encoder *encoder, bool power)
+{
+	struct vc4_fkms_encoder *vc4_encoder = to_vc4_fkms_encoder(encoder);
+	struct vc4_dev *vc4 = to_vc4_dev(encoder->dev);
+
+	struct mailbox_display_pwr pwr = {
+		.tag1 = {RPI_FIRMWARE_SET_DISPLAY_POWER, 8, 0, },
+		.display = vc4_encoder->display_num,
+		.state = power ? 1 : 0,
+	};
+
+	rpi_firmware_property_list(vc4->firmware, &pwr, sizeof(pwr));
+}
+
+static void vc4_fkms_encoder_enable(struct drm_encoder *encoder)
+{
+	vc4_fkms_display_power(encoder, true);
+	DRM_DEBUG_KMS("Encoder_enable\n");
+}
+
+static void vc4_fkms_encoder_disable(struct drm_encoder *encoder)
+{
+	vc4_fkms_display_power(encoder, false);
+	DRM_DEBUG_KMS("Encoder_disable\n");
+}
+
+static const struct drm_encoder_helper_funcs vc4_fkms_encoder_helper_funcs = {
+	.enable = vc4_fkms_encoder_enable,
+	.disable = vc4_fkms_encoder_disable,
+};
+
+static int vc4_fkms_create_screen(struct device *dev, struct drm_device *drm,
+				  int display_idx, int display_ref,
+				  struct vc4_fkms_crtc **ret_crtc)
+{
+	struct vc4_dev *vc4 = to_vc4_dev(drm);
+	struct vc4_fkms_crtc *vc4_fkms_crtc;
+	struct vc4_fkms_encoder *vc4_encoder;
+	struct drm_crtc *crtc;
+	struct drm_plane *destroy_plane, *temp;
+	struct mailbox_blank_display blank = {
+		.tag1 = {RPI_FIRMWARE_FRAMEBUFFER_SET_DISPLAY_NUM, 4, 0, },
+		.display = display_idx,
+		.tag2 = { RPI_FIRMWARE_FRAMEBUFFER_BLANK, 4, 0, },
+		.blank = 1,
+	};
+	struct drm_plane *planes[PLANES_PER_CRTC];
+	int ret, i;
+
+	vc4_fkms_crtc = devm_kzalloc(dev, sizeof(*vc4_fkms_crtc), GFP_KERNEL);
+	if (!vc4_fkms_crtc)
+		return -ENOMEM;
+	crtc = &vc4_fkms_crtc->base;
+
+	vc4_fkms_crtc->display_number = display_ref;
+	vc4_fkms_crtc->display_type = vc4_get_display_type(display_ref);
+
+	/* Blank the firmware provided framebuffer */
+	rpi_firmware_property_list(vc4->firmware, &blank, sizeof(blank));
+
+	for (i = 0; i < PLANES_PER_CRTC; i++) {
+		planes[i] = vc4_fkms_plane_init(drm,
+						(i == 0) ?
+						  DRM_PLANE_TYPE_PRIMARY :
+						  (i == PLANES_PER_CRTC - 1) ?
+							DRM_PLANE_TYPE_CURSOR :
+							DRM_PLANE_TYPE_OVERLAY,
+						display_ref,
+						i + (display_idx * PLANES_PER_CRTC)
+					       );
+		if (IS_ERR(planes[i])) {
+			dev_err(dev, "failed to construct plane %u\n", i);
+			ret = PTR_ERR(planes[i]);
+			goto err;
+		}
+	}
+
+	drm_crtc_init_with_planes(drm, crtc, planes[0],
+				  planes[PLANES_PER_CRTC - 1], &vc4_crtc_funcs,
+				  NULL);
+	drm_crtc_helper_add(crtc, &vc4_crtc_helper_funcs);
+
+	/* Update the possible_crtcs mask for the overlay plane(s) */
+	for (i = 1; i < (PLANES_PER_CRTC - 1); i++)
+		planes[i]->possible_crtcs = drm_crtc_mask(crtc);
+
+	vc4_encoder = devm_kzalloc(dev, sizeof(*vc4_encoder), GFP_KERNEL);
+	if (!vc4_encoder)
+		return -ENOMEM;
+	vc4_fkms_crtc->encoder = &vc4_encoder->base;
+
+	vc4_encoder->display_num = display_ref;
+	vc4_encoder->base.possible_crtcs |= drm_crtc_mask(crtc);
+
+	drm_encoder_init(drm, &vc4_encoder->base, &vc4_fkms_encoder_funcs,
+			 vc4_fkms_crtc->display_type, NULL);
+	drm_encoder_helper_add(&vc4_encoder->base,
+			       &vc4_fkms_encoder_helper_funcs);
+
+	vc4_fkms_crtc->connector = vc4_fkms_connector_init(drm, &vc4_encoder->base,
+							   display_ref);
+	if (IS_ERR(vc4_fkms_crtc->connector)) {
+		ret = PTR_ERR(vc4_fkms_crtc->connector);
+		goto err_destroy_encoder;
+	}
+
+	*ret_crtc = vc4_fkms_crtc;
+
+	return 0;
+
+err_destroy_encoder:
+	vc4_fkms_encoder_destroy(vc4_fkms_crtc->encoder);
+	list_for_each_entry_safe(destroy_plane, temp,
+				 &drm->mode_config.plane_list, head) {
+		if (destroy_plane->possible_crtcs == 1 << drm_crtc_index(crtc))
+			destroy_plane->funcs->destroy(destroy_plane);
+	}
+err:
+	return ret;
+}
+
+static int vc4_fkms_bind(struct device *dev, struct device *master, void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct drm_device *drm = dev_get_drvdata(master);
+	struct vc4_dev *vc4 = to_vc4_dev(drm);
+	struct device_node *firmware_node;
+	const struct of_device_id *match;
+	struct vc4_fkms_crtc **crtc_list;
+	u32 num_displays, display_num;
+	struct vc4_fkms *fkms;
+	int ret;
+	u32 display_id;
+
+	vc4->firmware_kms = true;
+
+	fkms = devm_kzalloc(dev, sizeof(*fkms), GFP_KERNEL);
+	if (!fkms)
+		return -ENOMEM;
+
+	match = of_match_device(vc4_firmware_kms_dt_match, dev);
+	if (!match)
+		return -ENODEV;
+	fkms->revision = (enum vc4_fkms_revision)match->data;
+
+	firmware_node = of_parse_phandle(dev->of_node, "brcm,firmware", 0);
+	vc4->firmware = devm_rpi_firmware_get(&pdev->dev, firmware_node);
+	if (!vc4->firmware) {
+		DRM_DEBUG("Failed to get Raspberry Pi firmware reference.\n");
+		return -EPROBE_DEFER;
+	}
+	of_node_put(firmware_node);
+
+	ret = rpi_firmware_property(vc4->firmware,
+				    RPI_FIRMWARE_FRAMEBUFFER_GET_NUM_DISPLAYS,
+				    &num_displays, sizeof(u32));
+
+	/* If we fail to get the number of displays, then
+	 * assume old firmware that doesn't have the mailbox call, so just
+	 * set one display
+	 */
+	if (ret) {
+		num_displays = 1;
+		DRM_WARN("Unable to determine number of displays - assuming 1\n");
+		ret = 0;
+	}
+
+	ret = rpi_firmware_property(vc4->firmware,
+				    RPI_FIRMWARE_GET_DISPLAY_CFG,
+				    &fkms->cfg, sizeof(fkms->cfg));
+
+	if (ret)
+		return -EINVAL;
+	/* The firmware works in Hz. This will be compared against kHz, so div
+	 * 1000 now rather than multiple times later.
+	 */
+	fkms->cfg.max_pixel_clock[0] /= 1000;
+	fkms->cfg.max_pixel_clock[1] /= 1000;
+
+	/* Allocate a list, with space for a NULL on the end */
+	crtc_list = devm_kzalloc(dev, sizeof(crtc_list) * (num_displays + 1),
+				 GFP_KERNEL);
+	if (!crtc_list)
+		return -ENOMEM;
+
+	for (display_num = 0; display_num < num_displays; display_num++) {
+		display_id = display_num;
+		ret = rpi_firmware_property(vc4->firmware,
+					    RPI_FIRMWARE_FRAMEBUFFER_GET_DISPLAY_ID,
+					    &display_id, sizeof(display_id));
+		/* FIXME: Determine the correct error handling here.
+		 * Should we fail to create the one "screen" but keep the
+		 * others, or fail the whole thing?
+		 */
+		if (ret)
+			DRM_ERROR("Failed to get display id %u\n", display_num);
+
+		ret = vc4_fkms_create_screen(dev, drm, display_num, display_id,
+					     &crtc_list[display_num]);
+		if (ret)
+			DRM_ERROR("Oh dear, failed to create display %u\n",
+				  display_num);
+	}
+
+	if (num_displays > 0) {
+		if (fkms->revision >= BCM2712) {
+			ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
+					       vc4_crtc2712_irq_handler, 0,
+					       "vc4 firmware kms", crtc_list);
+		} else {
+			/* Map the SMI interrupt reg */
+			crtc_list[0]->regs = vc4_ioremap_regs(pdev, 0);
+			if (IS_ERR(crtc_list[0]->regs))
+				DRM_ERROR("Oh dear, failed to map registers\n");
+
+			writel(0, crtc_list[0]->regs + SMICS);
+			ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
+					       vc4_crtc_irq_handler, 0,
+					       "vc4 firmware kms", crtc_list);
+		}
+		if (ret)
+			DRM_ERROR("Oh dear, failed to register IRQ\n");
+	} else {
+		DRM_WARN("No displays found. Consider forcing hotplug if HDMI is attached\n");
+	}
+
+	vc4->fkms = fkms;
+
+	platform_set_drvdata(pdev, crtc_list);
+
+	return 0;
+}
+
+static void vc4_fkms_unbind(struct device *dev, struct device *master,
+			    void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct vc4_fkms_crtc **crtc_list = dev_get_drvdata(dev);
+	int i;
+
+	for (i = 0; crtc_list[i]; i++) {
+		vc4_fkms_connector_destroy(crtc_list[i]->connector);
+		vc4_fkms_encoder_destroy(crtc_list[i]->encoder);
+		drm_crtc_cleanup(&crtc_list[i]->base);
+	}
+
+	platform_set_drvdata(pdev, NULL);
+}
+
+static const struct component_ops vc4_fkms_ops = {
+	.bind   = vc4_fkms_bind,
+	.unbind = vc4_fkms_unbind,
+};
+
+static int vc4_fkms_probe(struct platform_device *pdev)
+{
+	return component_add(&pdev->dev, &vc4_fkms_ops);
+}
+
+static void vc4_fkms_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &vc4_fkms_ops);
+}
+
+struct platform_driver vc4_firmware_kms_driver = {
+	.probe = vc4_fkms_probe,
+	.remove = vc4_fkms_remove,
+	.driver = {
+		.name = "vc4_firmware_kms",
+		.of_match_table = vc4_firmware_kms_dt_match,
+	},
+};
diff --git a/drivers/gpu/drm/vc4/vc4_gem.c b/drivers/gpu/drm/vc4/vc4_gem.c
index be9c0b72ebe8..22bccd69eb62 100644
--- a/drivers/gpu/drm/vc4/vc4_gem.c
+++ b/drivers/gpu/drm/vc4/vc4_gem.c
@@ -76,7 +76,7 @@ vc4_get_hang_state_ioctl(struct drm_device *dev, void *data,
 	u32 i;
 	int ret = 0;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	if (!vc4->v3d) {
@@ -389,7 +389,7 @@ vc4_wait_for_seqno(struct drm_device *dev, uint64_t seqno, uint64_t timeout_ns,
 	unsigned long timeout_expire;
 	DEFINE_WAIT(wait);
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	if (vc4->finished_seqno >= seqno)
@@ -474,7 +474,7 @@ vc4_submit_next_bin_job(struct drm_device *dev)
 	struct vc4_dev *vc4 = to_vc4_dev(dev);
 	struct vc4_exec_info *exec;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return;
 
 again:
@@ -522,7 +522,7 @@ vc4_submit_next_render_job(struct drm_device *dev)
 	if (!exec)
 		return;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return;
 
 	/* A previous RCL may have written to one of our textures, and
@@ -543,7 +543,7 @@ vc4_move_job_to_render(struct drm_device *dev, struct vc4_exec_info *exec)
 	struct vc4_dev *vc4 = to_vc4_dev(dev);
 	bool was_empty = list_empty(&vc4->render_job_list);
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return;
 
 	list_move_tail(&exec->head, &vc4->render_job_list);
@@ -970,7 +970,7 @@ vc4_job_handle_completed(struct vc4_dev *vc4)
 	unsigned long irqflags;
 	struct vc4_seqno_cb *cb, *cb_temp;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return;
 
 	spin_lock_irqsave(&vc4->job_lock, irqflags);
@@ -1009,7 +1009,7 @@ int vc4_queue_seqno_cb(struct drm_device *dev,
 	struct vc4_dev *vc4 = to_vc4_dev(dev);
 	unsigned long irqflags;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	cb->func = func;
@@ -1065,7 +1065,7 @@ vc4_wait_seqno_ioctl(struct drm_device *dev, void *data,
 	struct vc4_dev *vc4 = to_vc4_dev(dev);
 	struct drm_vc4_wait_seqno *args = data;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	return vc4_wait_for_seqno_ioctl_helper(dev, args->seqno,
@@ -1082,7 +1082,7 @@ vc4_wait_bo_ioctl(struct drm_device *dev, void *data,
 	struct drm_gem_object *gem_obj;
 	struct vc4_bo *bo;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	if (args->pad != 0)
@@ -1131,7 +1131,7 @@ vc4_submit_cl_ioctl(struct drm_device *dev, void *data,
 				  args->shader_rec_size,
 				  args->bo_handle_count);
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	if (!vc4->v3d) {
@@ -1267,7 +1267,7 @@ int vc4_gem_init(struct drm_device *dev)
 	struct vc4_dev *vc4 = to_vc4_dev(dev);
 	int ret;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	vc4->dma_fence_context = dma_fence_context_alloc(1);
@@ -1326,7 +1326,7 @@ int vc4_gem_madvise_ioctl(struct drm_device *dev, void *data,
 	struct vc4_bo *bo;
 	int ret;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	switch (args->madv) {
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.c b/drivers/gpu/drm/vc4/vc4_hdmi.c
index 6b83d02b5d62..206ecf42e09c 100644
--- a/drivers/gpu/drm/vc4/vc4_hdmi.c
+++ b/drivers/gpu/drm/vc4/vc4_hdmi.c
@@ -43,6 +43,8 @@
 #include <linux/component.h>
 #include <linux/gpio/consumer.h>
 #include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
 #include <linux/pm_runtime.h>
@@ -50,6 +52,7 @@
 #include <linux/reset.h>
 #include <sound/dmaengine_pcm.h>
 #include <sound/hdmi-codec.h>
+#include <sound/jack.h>
 #include <sound/pcm_drm_eld.h>
 #include <sound/pcm_params.h>
 #include <sound/soc.h>
@@ -111,6 +114,10 @@
 
 #define HDMI_14_MAX_TMDS_CLK   (340 * 1000 * 1000)
 
+/* bit field to force hotplug detection. bit0 = HDMI0 */
+static int force_hotplug;
+module_param(force_hotplug, int, 0644);
+
 static bool vc4_hdmi_supports_scrambling(struct vc4_hdmi *vc4_hdmi)
 {
 	struct drm_display_info *display = &vc4_hdmi->connector.display_info;
@@ -383,7 +390,7 @@ static void vc4_hdmi_handle_hotplug(struct vc4_hdmi *vc4_hdmi,
 				    enum drm_connector_status status)
 {
 	struct drm_connector *connector = &vc4_hdmi->connector;
-	const struct drm_edid *drm_edid;
+	const struct drm_edid *drm_edid = NULL;
 	int ret;
 
 	/*
@@ -400,13 +407,25 @@ static void vc4_hdmi_handle_hotplug(struct vc4_hdmi *vc4_hdmi,
 	 * the lock for now.
 	 */
 
+	if (status != connector_status_disconnected)
+		drm_edid = drm_edid_read_ddc(connector, vc4_hdmi->ddc);
+
+	/*
+	 * Report plugged/unplugged events to ALSA jack detection.  Do this
+	 * *after* EDID probing, otherwise userspace might try to bring up
+	 * audio before it's ready.
+	 */
+	mutex_lock(&vc4_hdmi->update_plugged_status_lock);
+	if (vc4_hdmi->plugged_cb && vc4_hdmi->codec_dev)
+		vc4_hdmi->plugged_cb(vc4_hdmi->codec_dev,
+				     status != connector_status_disconnected);
+	mutex_unlock(&vc4_hdmi->update_plugged_status_lock);
+
 	if (status == connector_status_disconnected) {
 		cec_phys_addr_invalidate(vc4_hdmi->cec_adap);
 		return;
 	}
 
-	drm_edid = drm_edid_read_ddc(connector, vc4_hdmi->ddc);
-
 	drm_edid_connector_update(connector, drm_edid);
 	cec_s_phys_addr(vc4_hdmi->cec_adap,
 			connector->display_info.source_physical_address, false);
@@ -453,7 +472,9 @@ static int vc4_hdmi_connector_detect_ctx(struct drm_connector *connector,
 		return connector_status_unknown;
 	}
 
-	if (vc4_hdmi->hpd_gpio) {
+	if (force_hotplug & BIT(vc4_hdmi->encoder.type - VC4_ENCODER_TYPE_HDMI0))
+		status = connector_status_connected;
+	else if (vc4_hdmi->hpd_gpio) {
 		if (gpiod_get_value_cansleep(vc4_hdmi->hpd_gpio))
 			status = connector_status_connected;
 	} else {
@@ -751,6 +772,24 @@ static int vc4_hdmi_write_infoframe(struct drm_connector *connector,
 	return ret;
 }
 
+static int vc4_hdmi_clear_infoframe(struct drm_connector *connector,
+				    enum hdmi_infoframe_type type)
+{
+	struct vc4_hdmi *vc4_hdmi = connector_to_vc4_hdmi(connector);
+	struct drm_device *drm = connector->dev;
+	int ret;
+	int idx;
+
+	if (!drm_dev_enter(drm, &idx))
+		return 0;
+
+	ret = vc4_hdmi_stop_packet(vc4_hdmi, type, true);
+	if (ret)
+		drm_err(drm, "Failed to wait for infoframe to go idle: %d\n", ret);
+
+	drm_dev_exit(idx);
+	return ret;
+}
 #define SCRAMBLING_POLLING_DELAY_MS	1000
 
 static void vc4_hdmi_enable_scrambling(struct drm_encoder *encoder)
@@ -845,6 +884,7 @@ static void vc4_hdmi_encoder_post_crtc_disable(struct drm_encoder *encoder,
 {
 	struct vc4_hdmi *vc4_hdmi = encoder_to_vc4_hdmi(encoder);
 	struct drm_device *drm = vc4_hdmi->connector.dev;
+	struct vc4_dev *vc4 = to_vc4_dev(drm);
 	unsigned long flags;
 	int idx;
 
@@ -861,14 +901,25 @@ static void vc4_hdmi_encoder_post_crtc_disable(struct drm_encoder *encoder,
 
 	HDMI_WRITE(HDMI_VID_CTL, HDMI_READ(HDMI_VID_CTL) | VC4_HD_VID_CTL_CLRRGB);
 
+	if (vc4->gen >= VC4_GEN_6_C)
+		HDMI_WRITE(HDMI_VID_CTL, HDMI_READ(HDMI_VID_CTL) |
+			   VC4_HD_VID_CTL_BLANKPIX);
+
 	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 
 	mdelay(1);
 
-	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
-	HDMI_WRITE(HDMI_VID_CTL,
-		   HDMI_READ(HDMI_VID_CTL) & ~VC4_HD_VID_CTL_ENABLE);
-	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+	/*
+	 * TODO: This should work on BCM2712, but doesn't for some
+	 * reason and result in a system lockup.
+	 */
+	if (vc4->gen < VC4_GEN_6_C) {
+		spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+		HDMI_WRITE(HDMI_VID_CTL,
+			   HDMI_READ(HDMI_VID_CTL) &
+			   ~VC4_HD_VID_CTL_ENABLE);
+		spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+	}
 
 	vc4_hdmi_disable_scrambling(encoder);
 
@@ -900,6 +951,8 @@ static void vc4_hdmi_encoder_post_crtc_powerdown(struct drm_encoder *encoder,
 	if (vc4_hdmi->variant->phy_disable)
 		vc4_hdmi->variant->phy_disable(vc4_hdmi);
 
+	/* we no longer require a minimum clock rate */
+	clk_set_min_rate(vc4_hdmi->pixel_bvb_clock, 0);
 	clk_disable_unprepare(vc4_hdmi->pixel_bvb_clock);
 	clk_disable_unprepare(vc4_hdmi->pixel_clock);
 
@@ -1488,7 +1541,6 @@ static void vc4_hdmi_encoder_pre_crtc_configure(struct drm_encoder *encoder,
 		goto err_put_runtime_pm;
 	}
 
-
 	vc4_hdmi_cec_update_clk_div(vc4_hdmi);
 
 	if (tmds_char_rate > 297000000)
@@ -1594,10 +1646,13 @@ static void vc4_hdmi_encoder_post_crtc_enable(struct drm_encoder *encoder,
 	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
 
 	HDMI_WRITE(HDMI_VID_CTL,
+		   (HDMI_READ(HDMI_VID_CTL) &
+			~(VC4_HD_VID_CTL_VSYNC_LOW | VC4_HD_VID_CTL_HSYNC_LOW)) |
 		   VC4_HD_VID_CTL_ENABLE |
 		   VC4_HD_VID_CTL_CLRRGB |
 		   VC4_HD_VID_CTL_UNDERFLOW_ENABLE |
 		   VC4_HD_VID_CTL_FRAME_COUNTER_RESET |
+		   VC4_HD_VID_CTL_BLANK_INSERT_EN |
 		   (vsync_pos ? 0 : VC4_HD_VID_CTL_VSYNC_LOW) |
 		   (hsync_pos ? 0 : VC4_HD_VID_CTL_HSYNC_LOW));
 
@@ -1695,6 +1750,7 @@ vc4_hdmi_connector_clock_valid(const struct drm_connector *connector,
 static const struct drm_connector_hdmi_funcs vc4_hdmi_hdmi_connector_funcs = {
 	.tmds_char_rate_valid	= vc4_hdmi_connector_clock_valid,
 	.write_infoframe	= vc4_hdmi_write_infoframe,
+	.clear_infoframe	= vc4_hdmi_clear_infoframe,
 };
 
 #define WIFI_2_4GHz_CH1_MIN_FREQ	2400000000ULL
@@ -1709,7 +1765,9 @@ static int vc4_hdmi_encoder_atomic_check(struct drm_encoder *encoder,
 	unsigned long long tmds_char_rate = mode->clock * 1000;
 	unsigned long long tmds_bit_rate;
 
-	if (vc4_hdmi->variant->unsupported_odd_h_timings) {
+	if (vc4_hdmi->variant->unsupported_odd_h_timings ||
+	    (vc4_hdmi->variant->unsupported_int_odd_h_timings &&
+		(mode->flags & DRM_MODE_FLAG_INTERLACE))) {
 		if (mode->flags & DRM_MODE_FLAG_DBLCLK) {
 			/* Only try to fixup DBLCLK modes to get 480i and 576i
 			 * working.
@@ -2109,18 +2167,33 @@ static int vc4_hdmi_audio_prepare(struct device *dev, void *data,
 					     VC4_HDMI_AUDIO_PACKET_CEA_MASK);
 
 	/* Set the MAI threshold */
-	if (vc4->gen >= VC4_GEN_5)
+	switch (vc4->gen) {
+	case VC4_GEN_6_D:
+		HDMI_WRITE(HDMI_MAI_THR,
+			   VC4_SET_FIELD(0x10, VC6_D_HD_MAI_THR_PANICHIGH) |
+			   VC4_SET_FIELD(0x10, VC6_D_HD_MAI_THR_PANICLOW) |
+			   VC4_SET_FIELD(0x1c, VC6_D_HD_MAI_THR_DREQHIGH) |
+			   VC4_SET_FIELD(0x1c, VC6_D_HD_MAI_THR_DREQLOW));
+		break;
+	case VC4_GEN_6_C:
+	case VC4_GEN_5:
 		HDMI_WRITE(HDMI_MAI_THR,
 			   VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_PANICHIGH) |
 			   VC4_SET_FIELD(0x10, VC4_HD_MAI_THR_PANICLOW) |
 			   VC4_SET_FIELD(0x1c, VC4_HD_MAI_THR_DREQHIGH) |
 			   VC4_SET_FIELD(0x1c, VC4_HD_MAI_THR_DREQLOW));
-	else
+		break;
+	case VC4_GEN_4:
 		HDMI_WRITE(HDMI_MAI_THR,
 			   VC4_SET_FIELD(0x8, VC4_HD_MAI_THR_PANICHIGH) |
 			   VC4_SET_FIELD(0x8, VC4_HD_MAI_THR_PANICLOW) |
 			   VC4_SET_FIELD(0x6, VC4_HD_MAI_THR_DREQHIGH) |
 			   VC4_SET_FIELD(0x8, VC4_HD_MAI_THR_DREQLOW));
+		break;
+	default:
+		drm_err(drm, "Unknown VC4 generation: %d", vc4->gen);
+		break;
+	}
 
 	HDMI_WRITE(HDMI_MAI_CONFIG,
 		   VC4_HDMI_MAI_CONFIG_BIT_REVERSE |
@@ -2199,8 +2272,23 @@ static int vc4_hdmi_audio_get_eld(struct device *dev, void *data,
 	return 0;
 }
 
+static int vc4_hdmi_audio_hook_plugged_cb(struct device *dev, void *data,
+					   hdmi_codec_plugged_cb fn,
+					   struct device *codec_dev)
+{
+	struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
+
+	mutex_lock(&vc4_hdmi->update_plugged_status_lock);
+	vc4_hdmi->plugged_cb = fn;
+	vc4_hdmi->codec_dev = codec_dev;
+	mutex_unlock(&vc4_hdmi->update_plugged_status_lock);
+
+	return 0;
+}
+
 static const struct hdmi_codec_ops vc4_hdmi_codec_ops = {
 	.get_eld = vc4_hdmi_audio_get_eld,
+	.hook_plugged_cb = vc4_hdmi_audio_hook_plugged_cb,
 	.prepare = vc4_hdmi_audio_prepare,
 	.audio_shutdown = vc4_hdmi_audio_shutdown,
 	.audio_startup = vc4_hdmi_audio_startup,
@@ -2220,6 +2308,22 @@ static void vc4_hdmi_audio_codec_release(void *ptr)
 	vc4_hdmi->audio.codec_pdev = NULL;
 }
 
+static int vc4_hdmi_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct vc4_hdmi *vc4_hdmi = snd_soc_card_get_drvdata(rtd->card);
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	int ret;
+
+	ret = snd_soc_card_jack_new(rtd->card, "HDMI Jack", SND_JACK_LINEOUT,
+				    &vc4_hdmi->hdmi_jack);
+	if (ret) {
+		dev_err(rtd->dev, "HDMI Jack creation failed: %d\n", ret);
+		return ret;
+	}
+
+	return snd_soc_component_set_jack(component, &vc4_hdmi->hdmi_jack, NULL);
+}
+
 static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi)
 {
 	const struct vc4_hdmi_register *mai_data =
@@ -2228,7 +2332,7 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi)
 	struct snd_soc_card *card = &vc4_hdmi->audio.card;
 	struct device *dev = &vc4_hdmi->pdev->dev;
 	struct platform_device *codec_pdev;
-	const __be32 *addr;
+	struct resource *iomem;
 	int index, len;
 	int ret;
 
@@ -2264,22 +2368,18 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi)
 	}
 
 	/*
-	 * Get the physical address of VC4_HD_MAI_DATA. We need to retrieve
-	 * the bus address specified in the DT, because the physical address
-	 * (the one returned by platform_get_resource()) is not appropriate
-	 * for DMA transfers.
-	 * This VC/MMU should probably be exposed to avoid this kind of hacks.
+	 * Get the physical address of VC4_HD_MAI_DATA.
 	 */
 	index = of_property_match_string(dev->of_node, "reg-names", "hd");
 	/* Before BCM2711, we don't have a named register range */
 	if (index < 0)
 		index = 1;
 
-	addr = of_get_address(dev->of_node, index, NULL, NULL);
-	if (!addr)
+	iomem = platform_get_resource(vc4_hdmi->pdev, IORESOURCE_MEM, index);
+	if (!iomem)
 		return -EINVAL;
 
-	vc4_hdmi->audio.dma_data.addr = be32_to_cpup(addr) + mai_data->offset;
+	vc4_hdmi->audio.dma_data.addr = iomem->start + mai_data->offset;
 	vc4_hdmi->audio.dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
 	vc4_hdmi->audio.dma_data.maxburst = 2;
 
@@ -2349,6 +2449,8 @@ static int vc4_hdmi_audio_init(struct vc4_hdmi *vc4_hdmi)
 	dai_link->codecs->name = dev_name(&codec_pdev->dev);
 	dai_link->platforms->name = dev_name(dev);
 
+	dai_link->init = vc4_hdmi_codec_init;
+
 	card->dai_link = dai_link;
 	card->num_links = 1;
 	card->name = vc4_hdmi->variant->card_name;
@@ -2378,7 +2480,7 @@ static irqreturn_t vc4_hdmi_hpd_irq_thread(int irq, void *priv)
 	struct drm_connector *connector = &vc4_hdmi->connector;
 	struct drm_device *dev = connector->dev;
 
-	if (dev && dev->registered)
+	if (dev && dev->registered && !force_hotplug)
 		drm_connector_helper_hpd_irq_event(connector);
 
 	return IRQ_HANDLED;
@@ -3120,6 +3222,9 @@ static int vc4_hdmi_runtime_suspend(struct device *dev)
 {
 	struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
 
+	clk_disable_unprepare(vc4_hdmi->audio_clock);
+	/* we no longer require a minimum clock rate */
+	clk_set_min_rate(vc4_hdmi->hsm_clock, 0);
 	clk_disable_unprepare(vc4_hdmi->hsm_clock);
 
 	return 0;
@@ -3152,6 +3257,10 @@ static int vc4_hdmi_runtime_resume(struct device *dev)
 		goto err_disable_clk;
 	}
 
+	ret = clk_prepare_enable(vc4_hdmi->audio_clock);
+	if (ret)
+		goto err_disable_clk;
+
 	if (vc4_hdmi->variant->reset)
 		vc4_hdmi->variant->reset(vc4_hdmi);
 
@@ -3204,6 +3313,8 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
 	if (ret)
 		return ret;
 
+	mutex_init(&vc4_hdmi->update_plugged_status_lock);
+
 	spin_lock_init(&vc4_hdmi->hw_lock);
 	INIT_DELAYED_WORK(&vc4_hdmi->scrambling_work, vc4_hdmi_scrambling_wq);
 
@@ -3240,7 +3351,7 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
 	vc4_hdmi->ddc = of_find_i2c_adapter_by_node(ddc_node);
 	of_node_put(ddc_node);
 	if (!vc4_hdmi->ddc) {
-		drm_err(drm, "Failed to get ddc i2c adapter by node\n");
+		drm_dbg(drm, "Failed to get ddc i2c adapter by node\n");
 		return -EPROBE_DEFER;
 	}
 
@@ -3272,7 +3383,9 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
 		return ret;
 
 	if ((of_device_is_compatible(dev->of_node, "brcm,bcm2711-hdmi0") ||
-	     of_device_is_compatible(dev->of_node, "brcm,bcm2711-hdmi1")) &&
+	     of_device_is_compatible(dev->of_node, "brcm,bcm2711-hdmi1") ||
+	     of_device_is_compatible(dev->of_node, "brcm,bcm2712-hdmi0") ||
+	     of_device_is_compatible(dev->of_node, "brcm,bcm2712-hdmi1")) &&
 	    HDMI_READ(HDMI_VID_CTL) & VC4_HD_VID_CTL_ENABLE) {
 		clk_prepare_enable(vc4_hdmi->pixel_clock);
 		clk_prepare_enable(vc4_hdmi->hsm_clock);
@@ -3314,8 +3427,16 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
 	return ret;
 }
 
+static void vc4_hdmi_unbind(struct device *dev, struct device *master, void *data)
+{
+	struct vc4_hdmi *vc4_hdmi = dev_get_drvdata(dev);
+
+	mutex_destroy(&vc4_hdmi->update_plugged_status_lock);
+}
+
 static const struct component_ops vc4_hdmi_ops = {
 	.bind   = vc4_hdmi_bind,
+	.unbind = vc4_hdmi_unbind,
 };
 
 static int vc4_hdmi_dev_probe(struct platform_device *pdev)
@@ -3362,6 +3483,7 @@ static const struct vc4_hdmi_variant bcm2711_hdmi0_variant = {
 		PHY_LANE_CK,
 	},
 	.unsupported_odd_h_timings	= true,
+	.unsupported_int_odd_h_timings	= true,
 	.external_irq_controller	= true,
 
 	.init_resources		= vc5_hdmi_init_resources,
@@ -3391,6 +3513,7 @@ static const struct vc4_hdmi_variant bcm2711_hdmi1_variant = {
 		PHY_LANE_2,
 	},
 	.unsupported_odd_h_timings	= true,
+	.unsupported_int_odd_h_timings	= true,
 	.external_irq_controller	= true,
 
 	.init_resources		= vc5_hdmi_init_resources,
@@ -3406,10 +3529,68 @@ static const struct vc4_hdmi_variant bcm2711_hdmi1_variant = {
 	.hp_detect		= vc5_hdmi_hp_detect,
 };
 
+static const struct vc4_hdmi_variant bcm2712_hdmi0_variant = {
+	.encoder_type		= VC4_ENCODER_TYPE_HDMI0,
+	.debugfs_name		= "hdmi0_regs",
+	.card_name		= "vc4-hdmi-0",
+	.max_pixel_clock	= 600000000,
+	.registers		= vc6_hdmi_hdmi0_fields,
+	.num_registers		= ARRAY_SIZE(vc6_hdmi_hdmi0_fields),
+	.phy_lane_mapping	= {
+		PHY_LANE_0,
+		PHY_LANE_1,
+		PHY_LANE_2,
+		PHY_LANE_CK,
+	},
+	.unsupported_odd_h_timings	= false,
+	.unsupported_int_odd_h_timings	= true,
+	.external_irq_controller	= true,
+
+	.init_resources		= vc5_hdmi_init_resources,
+	.csc_setup		= vc5_hdmi_csc_setup,
+	.reset			= vc5_hdmi_reset,
+	.set_timings		= vc5_hdmi_set_timings,
+	.phy_init		= vc6_hdmi_phy_init,
+	.phy_disable		= vc6_hdmi_phy_disable,
+	.channel_map		= vc5_hdmi_channel_map,
+	.supports_hdr		= true,
+	.hp_detect		= vc5_hdmi_hp_detect,
+};
+
+static const struct vc4_hdmi_variant bcm2712_hdmi1_variant = {
+	.encoder_type		= VC4_ENCODER_TYPE_HDMI1,
+	.debugfs_name		= "hdmi1_regs",
+	.card_name		= "vc4-hdmi-1",
+	.max_pixel_clock	= 600000000,
+	.registers		= vc6_hdmi_hdmi1_fields,
+	.num_registers		= ARRAY_SIZE(vc6_hdmi_hdmi1_fields),
+	.phy_lane_mapping	= {
+		PHY_LANE_0,
+		PHY_LANE_1,
+		PHY_LANE_2,
+		PHY_LANE_CK,
+	},
+	.unsupported_odd_h_timings	= false,
+	.unsupported_int_odd_h_timings	= true,
+	.external_irq_controller	= true,
+
+	.init_resources		= vc5_hdmi_init_resources,
+	.csc_setup		= vc5_hdmi_csc_setup,
+	.reset			= vc5_hdmi_reset,
+	.set_timings		= vc5_hdmi_set_timings,
+	.phy_init		= vc6_hdmi_phy_init,
+	.phy_disable		= vc6_hdmi_phy_disable,
+	.channel_map		= vc5_hdmi_channel_map,
+	.supports_hdr		= true,
+	.hp_detect		= vc5_hdmi_hp_detect,
+};
+
 static const struct of_device_id vc4_hdmi_dt_match[] = {
 	{ .compatible = "brcm,bcm2835-hdmi", .data = &bcm2835_variant },
 	{ .compatible = "brcm,bcm2711-hdmi0", .data = &bcm2711_hdmi0_variant },
 	{ .compatible = "brcm,bcm2711-hdmi1", .data = &bcm2711_hdmi1_variant },
+	{ .compatible = "brcm,bcm2712-hdmi0", .data = &bcm2712_hdmi0_variant },
+	{ .compatible = "brcm,bcm2712-hdmi1", .data = &bcm2712_hdmi1_variant },
 	{}
 };
 
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi.h b/drivers/gpu/drm/vc4/vc4_hdmi.h
index b37f1d2c3fe5..fb13f355d123 100644
--- a/drivers/gpu/drm/vc4/vc4_hdmi.h
+++ b/drivers/gpu/drm/vc4/vc4_hdmi.h
@@ -2,8 +2,10 @@
 #define _VC4_HDMI_H_
 
 #include <drm/drm_connector.h>
+#include <linux/mutex.h>
 #include <media/cec.h>
 #include <sound/dmaengine_pcm.h>
+#include <sound/hdmi-codec.h>
 #include <sound/soc.h>
 
 #include "vc4_drv.h"
@@ -46,6 +48,10 @@ struct vc4_hdmi_variant {
 
 	/* The BCM2711 cannot deal with odd horizontal pixel timings */
 	bool unsupported_odd_h_timings;
+	/* The BCM2712 can handle odd horizontal pixel timings, but not in
+	 * interlaced modes
+	 */
+	bool unsupported_int_odd_h_timings;
 
 	/*
 	 * The BCM2711 CEC/hotplug IRQ controller is shared between the
@@ -213,6 +219,31 @@ struct vc4_hdmi {
 	 * KMS hooks. Protected by @mutex.
 	 */
 	enum hdmi_colorspace output_format;
+
+	/**
+	 * @plugged_cb: Callback provided by hdmi-codec to indicate that an
+	 * HDMI hotplug occurred and jack state should be updated. Protected by
+	 * @update_plugged_status_lock.
+	 */
+	hdmi_codec_plugged_cb plugged_cb;
+
+	/**
+	 * @plugged_cb: Context for plugged_cb. Protected by
+	 * @update_plugged_status_lock.
+	 */
+	struct device *codec_dev;
+
+	/**
+	 * @update_plugged_status_lock: Prevents a race condition where an HDMI
+	 * hotplug might occur between @plugged_cb and @codec_dev being set.
+	 */
+	struct mutex update_plugged_status_lock;
+
+	/**
+	 * @hdmi_jack: Represents the connection state of the HDMI plug, for
+	 * ALSA jack detection.
+	 */
+	struct snd_soc_jack hdmi_jack;
 };
 
 #define connector_to_vc4_hdmi(_connector)				\
@@ -237,4 +268,8 @@ void vc5_hdmi_phy_disable(struct vc4_hdmi *vc4_hdmi);
 void vc5_hdmi_phy_rng_enable(struct vc4_hdmi *vc4_hdmi);
 void vc5_hdmi_phy_rng_disable(struct vc4_hdmi *vc4_hdmi);
 
+void vc6_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi,
+		       struct drm_connector_state *conn_state);
+void vc6_hdmi_phy_disable(struct vc4_hdmi *vc4_hdmi);
+
 #endif /* _VC4_HDMI_H_ */
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi_phy.c b/drivers/gpu/drm/vc4/vc4_hdmi_phy.c
index 1f5507fc7a03..4a1ab59d8810 100644
--- a/drivers/gpu/drm/vc4/vc4_hdmi_phy.c
+++ b/drivers/gpu/drm/vc4/vc4_hdmi_phy.c
@@ -125,6 +125,49 @@
 #define VC4_HDMI_RM_FORMAT_SHIFT_SHIFT			24
 #define VC4_HDMI_RM_FORMAT_SHIFT_MASK			VC4_MASK(25, 24)
 
+#define VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_BG_PWRUP	BIT(8)
+#define VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_LDO_PWRUP	BIT(7)
+#define VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_BIAS_PWRUP	BIT(6)
+#define VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_RNDGEN_PWRUP	BIT(4)
+#define VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_TX_CK_PWRUP	BIT(3)
+#define VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_TX_2_PWRUP	BIT(2)
+#define VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_TX_1_PWRUP	BIT(1)
+#define VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_TX_0_PWRUP	BIT(0)
+
+#define VC6_HDMI_TX_PHY_PLL_REFCLK_REFCLK_SEL_CMOS	BIT(13)
+#define VC6_HDMI_TX_PHY_PLL_REFCLK_REFFRQ_MASK		VC4_MASK(9, 0)
+
+#define VC6_HDMI_TX_PHY_PLL_POST_KDIV_BYPASS_EN		BIT(4)
+#define VC6_HDMI_TX_PHY_PLL_POST_KDIV_CLK0_SEL_MASK	VC4_MASK(3, 2)
+#define VC6_HDMI_TX_PHY_PLL_POST_KDIV_KDIV_MASK		VC4_MASK(1, 0)
+
+#define VC6_HDMI_TX_PHY_PLL_VCOCLK_DIV_VCODIV_EN	BIT(10)
+#define VC6_HDMI_TX_PHY_PLL_VCOCLK_DIV_VCODIV_MASK	VC4_MASK(9, 0)
+
+#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_CTL_MASK	VC4_MASK(31, 28)
+#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_ENABLE_MASK		VC4_MASK(27, 27)
+#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_RATE_CTL_MASK	VC4_MASK(26, 26)
+#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_POST_TAP_EN_MASK	VC4_MASK(25, 25)
+#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_LDMOS_BIAS_CTL_MASK	VC4_MASK(24, 23)
+#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_COM_MODE_LDMOS_EN_MASK	VC4_MASK(22, 22)
+#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EDGE_SEL_MASK		VC4_MASK(21, 21)
+#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_SRC_HS_EN_MASK	VC4_MASK(20, 20)
+#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_TERM_CTL_MASK		VC4_MASK(19, 18)
+#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_SRC_EN_MASK	VC4_MASK(17, 17)
+#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_SRC_EN_MASK	VC4_MASK(16, 16)
+#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_CTL_MASK	VC4_MASK(15, 12)
+#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_SRC_HS_EN_MASK	VC4_MASK(11, 11)
+#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_MAIN_TAP_CURRENT_SELECT_MASK	VC4_MASK(10, 8)
+#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_POST_TAP_CURRENT_SELECT_MASK	VC4_MASK(7, 5)
+#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_CTL_SLOW_LOADING_MASK	VC4_MASK(4, 3)
+#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_CTL_SLOW_DRIVING_MASK	VC4_MASK(2, 1)
+#define VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_PRE_TAP_EN_MASK	VC4_MASK(0, 0)
+
+#define VC6_HDMI_TX_PHY_PLL_RESET_CTL_PLL_PLLPOST_RESETB	BIT(1)
+#define VC6_HDMI_TX_PHY_PLL_RESET_CTL_PLL_RESETB	BIT(0)
+
+#define VC6_HDMI_TX_PHY_PLL_POWERUP_CTL_PLL_PWRUP	BIT(0)
+
 #define OSCILLATOR_FREQUENCY	54000000
 
 void vc4_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi,
@@ -558,3 +601,607 @@ void vc5_hdmi_phy_rng_disable(struct vc4_hdmi *vc4_hdmi)
 		   VC4_HDMI_TX_PHY_POWERDOWN_CTL_RNDGEN_PWRDN);
 	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
 }
+
+#define VC6_VCO_MIN_FREQ	(8ULL * 1000 * 1000 * 1000)
+#define VC6_VCO_MAX_FREQ	(12ULL * 1000 * 1000 * 1000)
+
+static unsigned long long
+vc6_phy_get_vco_freq(unsigned long long tmds_rate, unsigned int *vco_div)
+{
+	unsigned int min_div;
+	unsigned int max_div;
+	unsigned int div;
+
+	div = 0;
+	while (tmds_rate * div * 10 < VC6_VCO_MIN_FREQ)
+		div++;
+	min_div = div;
+
+	while (tmds_rate * (div + 1) * 10 < VC6_VCO_MAX_FREQ)
+		div++;
+	max_div = div;
+
+	div = min_div + (max_div - min_div) / 2;
+
+	*vco_div = div;
+	return tmds_rate * div * 10;
+}
+
+struct vc6_phy_lane_settings {
+	unsigned int ext_current_ctl:4;
+	unsigned int ffe_enable:1;
+	unsigned int slew_rate_ctl:1;
+	unsigned int ffe_post_tap_en:1;
+	unsigned int ldmos_bias_ctl:2;
+	unsigned int com_mode_ldmos_en:1;
+	unsigned int edge_sel:1;
+	unsigned int ext_current_src_hs_en:1;
+	unsigned int term_ctl:2;
+	unsigned int ext_current_src_en:1;
+	unsigned int int_current_src_en:1;
+	unsigned int int_current_ctl:4;
+	unsigned int int_current_src_hs_en:1;
+	unsigned int main_tap_current_select:3;
+	unsigned int post_tap_current_select:3;
+	unsigned int slew_ctl_slow_loading:2;
+	unsigned int slew_ctl_slow_driving:2;
+	unsigned int ffe_pre_tap_en:1;
+};
+
+struct vc6_phy_settings {
+	unsigned long long min_rate;
+	unsigned long long max_rate;
+	struct vc6_phy_lane_settings channel[3];
+	struct vc6_phy_lane_settings clock;
+};
+
+static const struct vc6_phy_settings vc6_hdmi_phy_settings[] = {
+	{
+		0, 222000000,
+		{
+			{
+				/* 200mA */
+				.ext_current_ctl = 8,
+
+				/* 0.85V */
+				.ldmos_bias_ctl = 1,
+
+				/* Enable External Current Source */
+				.ext_current_src_en = 1,
+
+				/* 200mA */
+				.int_current_ctl = 8,
+
+				/* 17.6 mA */
+				.main_tap_current_select = 7,
+			},
+			{
+				/* 200mA */
+				.ext_current_ctl = 8,
+
+				/* 0.85V */
+				.ldmos_bias_ctl = 1,
+
+				/* Enable External Current Source */
+				.ext_current_src_en = 1,
+
+				/* 200mA */
+				.int_current_ctl = 8,
+
+				/* 17.6 mA */
+				.main_tap_current_select = 7,
+			},
+			{
+				/* 200mA */
+				.ext_current_ctl = 8,
+
+				/* 0.85V */
+				.ldmos_bias_ctl = 1,
+
+				/* Enable External Current Source */
+				.ext_current_src_en = 1,
+
+				/* 200mA */
+				.int_current_ctl = 8,
+
+				/* 17.6 mA */
+				.main_tap_current_select = 7,
+			},
+		},
+		{
+			/* 200mA */
+			.ext_current_ctl = 8,
+
+			/* 0.85V */
+			.ldmos_bias_ctl = 1,
+
+			/* Enable External Current Source */
+			.ext_current_src_en = 1,
+
+			/* 200mA */
+			.int_current_ctl = 8,
+
+			/* 17.6 mA */
+			.main_tap_current_select = 7,
+		},
+	},
+	{
+		222000001, 297000000,
+		{
+			{
+				/* 200mA and 180mA ?! */
+				.ext_current_ctl = 12,
+
+				/* 0.85V */
+				.ldmos_bias_ctl = 1,
+
+				/* 100 Ohm */
+				.term_ctl = 1,
+
+				/* Enable External Current Source */
+				.ext_current_src_en = 1,
+
+				/* Enable Internal Current Source */
+				.int_current_src_en = 1,
+			},
+			{
+				/* 200mA and 180mA ?! */
+				.ext_current_ctl = 12,
+
+				/* 0.85V */
+				.ldmos_bias_ctl = 1,
+
+				/* 100 Ohm */
+				.term_ctl = 1,
+
+				/* Enable External Current Source */
+				.ext_current_src_en = 1,
+
+				/* Enable Internal Current Source */
+				.int_current_src_en = 1,
+			},
+			{
+				/* 200mA and 180mA ?! */
+				.ext_current_ctl = 12,
+
+				/* 0.85V */
+				.ldmos_bias_ctl = 1,
+
+				/* 100 Ohm */
+				.term_ctl = 1,
+
+				/* Enable External Current Source */
+				.ext_current_src_en = 1,
+
+				/* Enable Internal Current Source */
+				.int_current_src_en = 1,
+			},
+		},
+		{
+			/* 200mA and 180mA ?! */
+			.ext_current_ctl = 12,
+
+			/* 0.85V */
+			.ldmos_bias_ctl = 1,
+
+			/* 100 Ohm */
+			.term_ctl = 1,
+
+			/* Enable External Current Source */
+			.ext_current_src_en = 1,
+
+			/* Enable Internal Current Source */
+			.int_current_src_en = 1,
+
+			/* Internal Current Source Half Swing Enable*/
+			.int_current_src_hs_en = 1,
+		},
+	},
+	{
+		297000001, 597000044,
+		{
+			{
+				/* 200mA */
+				.ext_current_ctl = 8,
+
+				/* Normal Slew Rate Control */
+				.slew_rate_ctl = 1,
+
+				/* 0.85V */
+				.ldmos_bias_ctl = 1,
+
+				/* 50 Ohms */
+				.term_ctl = 3,
+
+				/* Enable External Current Source */
+				.ext_current_src_en = 1,
+
+				/* Enable Internal Current Source */
+				.int_current_src_en = 1,
+
+				/* 200mA */
+				.int_current_ctl = 8,
+
+				/* 17.6 mA */
+				.main_tap_current_select = 7,
+			},
+			{
+				/* 200mA */
+				.ext_current_ctl = 8,
+
+				/* Normal Slew Rate Control */
+				.slew_rate_ctl = 1,
+
+				/* 0.85V */
+				.ldmos_bias_ctl = 1,
+
+				/* 50 Ohms */
+				.term_ctl = 3,
+
+				/* Enable External Current Source */
+				.ext_current_src_en = 1,
+
+				/* Enable Internal Current Source */
+				.int_current_src_en = 1,
+
+				/* 200mA */
+				.int_current_ctl = 8,
+
+				/* 17.6 mA */
+				.main_tap_current_select = 7,
+			},
+			{
+				/* 200mA */
+				.ext_current_ctl = 8,
+
+				/* Normal Slew Rate Control */
+				.slew_rate_ctl = 1,
+
+				/* 0.85V */
+				.ldmos_bias_ctl = 1,
+
+				/* 50 Ohms */
+				.term_ctl = 3,
+
+				/* Enable External Current Source */
+				.ext_current_src_en = 1,
+
+				/* Enable Internal Current Source */
+				.int_current_src_en = 1,
+
+				/* 200mA */
+				.int_current_ctl = 8,
+
+				/* 17.6 mA */
+				.main_tap_current_select = 7,
+			},
+		},
+		{
+			/* 200mA */
+			.ext_current_ctl = 8,
+
+			/* Normal Slew Rate Control */
+			.slew_rate_ctl = 1,
+
+			/* 0.85V */
+			.ldmos_bias_ctl = 1,
+
+			/* External Current Source Half Swing Enable*/
+			.ext_current_src_hs_en = 1,
+
+			/* 50 Ohms */
+			.term_ctl = 3,
+
+			/* Enable External Current Source */
+			.ext_current_src_en = 1,
+
+			/* Enable Internal Current Source */
+			.int_current_src_en = 1,
+
+			/* 200mA */
+			.int_current_ctl = 8,
+
+			/* Internal Current Source Half Swing Enable*/
+			.int_current_src_hs_en = 1,
+
+			/* 17.6 mA */
+			.main_tap_current_select = 7,
+		},
+	},
+};
+
+static const struct vc6_phy_settings *
+vc6_phy_get_settings(unsigned long long tmds_rate)
+{
+	unsigned int count = ARRAY_SIZE(vc6_hdmi_phy_settings);
+	unsigned int i;
+
+	for (i = 0; i < count; i++) {
+		const struct vc6_phy_settings *s = &vc6_hdmi_phy_settings[i];
+
+		if (tmds_rate >= s->min_rate && tmds_rate <= s->max_rate)
+			return s;
+	}
+
+	/*
+	 * If the pixel clock exceeds our max setting, try the max
+	 * setting anyway.
+	 */
+	return &vc6_hdmi_phy_settings[count - 1];
+}
+
+static const struct vc6_phy_lane_settings *
+vc6_phy_get_channel_settings(enum vc4_hdmi_phy_channel chan,
+			     unsigned long long tmds_rate)
+{
+	const struct vc6_phy_settings *settings = vc6_phy_get_settings(tmds_rate);
+
+	if (chan == PHY_LANE_CK)
+		return &settings->clock;
+
+	return &settings->channel[chan];
+}
+
+static void vc6_hdmi_reset_phy(struct vc4_hdmi *vc4_hdmi)
+{
+	lockdep_assert_held(&vc4_hdmi->hw_lock);
+
+	HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, 0);
+	HDMI_WRITE(HDMI_TX_PHY_POWERUP_CTL, 0);
+	HDMI_WRITE(HDMI_TX_PHY_PLL_POST_KDIV, VC6_HDMI_TX_PHY_PLL_POST_KDIV_BYPASS_EN);
+}
+
+void vc6_hdmi_phy_init(struct vc4_hdmi *vc4_hdmi,
+		       struct drm_connector_state *conn_state)
+{
+	const struct vc6_phy_lane_settings *chan0_settings;
+	const struct vc6_phy_lane_settings *chan1_settings;
+	const struct vc6_phy_lane_settings *chan2_settings;
+	const struct vc6_phy_lane_settings *clock_settings;
+	const struct vc4_hdmi_variant *variant = vc4_hdmi->variant;
+	unsigned long long pixel_freq = conn_state->hdmi.tmds_char_rate;
+	unsigned long long vco_freq;
+	unsigned char word_sel;
+	unsigned long flags;
+	unsigned int vco_div;
+
+	vco_freq = vc6_phy_get_vco_freq(pixel_freq, &vco_div);
+
+	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+
+	vc6_hdmi_reset_phy(vc4_hdmi);
+
+	HDMI_WRITE(HDMI_TX_PHY_PLL_MISC_0, 0x810c6000);
+	HDMI_WRITE(HDMI_TX_PHY_PLL_MISC_1, 0x00b8c451);
+	HDMI_WRITE(HDMI_TX_PHY_PLL_MISC_2, 0x46402e31);
+	HDMI_WRITE(HDMI_TX_PHY_PLL_MISC_3, 0x00b8c005);
+	HDMI_WRITE(HDMI_TX_PHY_PLL_MISC_4, 0x42410261);
+	HDMI_WRITE(HDMI_TX_PHY_PLL_MISC_5, 0xcc021001);
+	HDMI_WRITE(HDMI_TX_PHY_PLL_MISC_6, 0xc8301c80);
+	HDMI_WRITE(HDMI_TX_PHY_PLL_MISC_7, 0xb0804444);
+	HDMI_WRITE(HDMI_TX_PHY_PLL_MISC_8, 0xf80f8000);
+
+	HDMI_WRITE(HDMI_TX_PHY_PLL_REFCLK,
+		   VC6_HDMI_TX_PHY_PLL_REFCLK_REFCLK_SEL_CMOS |
+		   VC4_SET_FIELD(54, VC6_HDMI_TX_PHY_PLL_REFCLK_REFFRQ));
+
+	HDMI_WRITE(HDMI_TX_PHY_RESET_CTL, 0x7f);
+
+	HDMI_WRITE(HDMI_RM_OFFSET,
+		   VC4_HDMI_RM_OFFSET_ONLY |
+		   VC4_SET_FIELD(phy_get_rm_offset(vco_freq),
+				 VC4_HDMI_RM_OFFSET_OFFSET));
+
+	HDMI_WRITE(HDMI_TX_PHY_PLL_VCOCLK_DIV,
+		   VC6_HDMI_TX_PHY_PLL_VCOCLK_DIV_VCODIV_EN |
+		   VC4_SET_FIELD(vco_div,
+				 VC6_HDMI_TX_PHY_PLL_VCOCLK_DIV_VCODIV));
+
+	HDMI_WRITE(HDMI_TX_PHY_PLL_CFG,
+		   VC4_SET_FIELD(0, VC4_HDMI_TX_PHY_PLL_CFG_PDIV));
+
+	HDMI_WRITE(HDMI_TX_PHY_PLL_POST_KDIV,
+		   VC4_SET_FIELD(2, VC6_HDMI_TX_PHY_PLL_POST_KDIV_CLK0_SEL) |
+		   VC4_SET_FIELD(1, VC6_HDMI_TX_PHY_PLL_POST_KDIV_KDIV));
+
+	chan0_settings =
+		vc6_phy_get_channel_settings(variant->phy_lane_mapping[PHY_LANE_0],
+					     pixel_freq);
+	HDMI_WRITE(HDMI_TX_PHY_CTL_0,
+		   VC4_SET_FIELD(chan0_settings->ext_current_ctl,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_CTL) |
+		   VC4_SET_FIELD(chan0_settings->ffe_enable,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_ENABLE) |
+		   VC4_SET_FIELD(chan0_settings->slew_rate_ctl,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_RATE_CTL) |
+		   VC4_SET_FIELD(chan0_settings->ffe_post_tap_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_POST_TAP_EN) |
+		   VC4_SET_FIELD(chan0_settings->ldmos_bias_ctl,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_LDMOS_BIAS_CTL) |
+		   VC4_SET_FIELD(chan0_settings->com_mode_ldmos_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_COM_MODE_LDMOS_EN) |
+		   VC4_SET_FIELD(chan0_settings->edge_sel,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EDGE_SEL) |
+		   VC4_SET_FIELD(chan0_settings->ext_current_src_hs_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_SRC_HS_EN) |
+		   VC4_SET_FIELD(chan0_settings->term_ctl,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_TERM_CTL) |
+		   VC4_SET_FIELD(chan0_settings->ext_current_src_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_SRC_EN) |
+		   VC4_SET_FIELD(chan0_settings->int_current_src_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_SRC_EN) |
+		   VC4_SET_FIELD(chan0_settings->int_current_ctl,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_CTL) |
+		   VC4_SET_FIELD(chan0_settings->int_current_src_hs_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_SRC_HS_EN) |
+		   VC4_SET_FIELD(chan0_settings->main_tap_current_select,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_MAIN_TAP_CURRENT_SELECT) |
+		   VC4_SET_FIELD(chan0_settings->post_tap_current_select,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_POST_TAP_CURRENT_SELECT) |
+		   VC4_SET_FIELD(chan0_settings->slew_ctl_slow_loading,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_CTL_SLOW_LOADING) |
+		   VC4_SET_FIELD(chan0_settings->slew_ctl_slow_driving,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_CTL_SLOW_DRIVING) |
+		   VC4_SET_FIELD(chan0_settings->ffe_pre_tap_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_PRE_TAP_EN));
+
+	chan1_settings =
+		vc6_phy_get_channel_settings(variant->phy_lane_mapping[PHY_LANE_1],
+					     pixel_freq);
+	HDMI_WRITE(HDMI_TX_PHY_CTL_1,
+		   VC4_SET_FIELD(chan1_settings->ext_current_ctl,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_CTL) |
+		   VC4_SET_FIELD(chan1_settings->ffe_enable,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_ENABLE) |
+		   VC4_SET_FIELD(chan1_settings->slew_rate_ctl,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_RATE_CTL) |
+		   VC4_SET_FIELD(chan1_settings->ffe_post_tap_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_POST_TAP_EN) |
+		   VC4_SET_FIELD(chan1_settings->ldmos_bias_ctl,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_LDMOS_BIAS_CTL) |
+		   VC4_SET_FIELD(chan1_settings->com_mode_ldmos_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_COM_MODE_LDMOS_EN) |
+		   VC4_SET_FIELD(chan1_settings->edge_sel,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EDGE_SEL) |
+		   VC4_SET_FIELD(chan1_settings->ext_current_src_hs_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_SRC_HS_EN) |
+		   VC4_SET_FIELD(chan1_settings->term_ctl,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_TERM_CTL) |
+		   VC4_SET_FIELD(chan1_settings->ext_current_src_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_SRC_EN) |
+		   VC4_SET_FIELD(chan1_settings->int_current_src_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_SRC_EN) |
+		   VC4_SET_FIELD(chan1_settings->int_current_ctl,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_CTL) |
+		   VC4_SET_FIELD(chan1_settings->int_current_src_hs_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_SRC_HS_EN) |
+		   VC4_SET_FIELD(chan1_settings->main_tap_current_select,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_MAIN_TAP_CURRENT_SELECT) |
+		   VC4_SET_FIELD(chan1_settings->post_tap_current_select,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_POST_TAP_CURRENT_SELECT) |
+		   VC4_SET_FIELD(chan1_settings->slew_ctl_slow_loading,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_CTL_SLOW_LOADING) |
+		   VC4_SET_FIELD(chan1_settings->slew_ctl_slow_driving,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_CTL_SLOW_DRIVING) |
+		   VC4_SET_FIELD(chan1_settings->ffe_pre_tap_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_PRE_TAP_EN));
+
+	chan2_settings =
+		vc6_phy_get_channel_settings(variant->phy_lane_mapping[PHY_LANE_2],
+					     pixel_freq);
+	HDMI_WRITE(HDMI_TX_PHY_CTL_2,
+		   VC4_SET_FIELD(chan2_settings->ext_current_ctl,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_CTL) |
+		   VC4_SET_FIELD(chan2_settings->ffe_enable,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_ENABLE) |
+		   VC4_SET_FIELD(chan2_settings->slew_rate_ctl,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_RATE_CTL) |
+		   VC4_SET_FIELD(chan2_settings->ffe_post_tap_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_POST_TAP_EN) |
+		   VC4_SET_FIELD(chan2_settings->ldmos_bias_ctl,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_LDMOS_BIAS_CTL) |
+		   VC4_SET_FIELD(chan2_settings->com_mode_ldmos_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_COM_MODE_LDMOS_EN) |
+		   VC4_SET_FIELD(chan2_settings->edge_sel,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EDGE_SEL) |
+		   VC4_SET_FIELD(chan2_settings->ext_current_src_hs_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_SRC_HS_EN) |
+		   VC4_SET_FIELD(chan2_settings->term_ctl,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_TERM_CTL) |
+		   VC4_SET_FIELD(chan2_settings->ext_current_src_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_SRC_EN) |
+		   VC4_SET_FIELD(chan2_settings->int_current_src_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_SRC_EN) |
+		   VC4_SET_FIELD(chan2_settings->int_current_ctl,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_CTL) |
+		   VC4_SET_FIELD(chan2_settings->int_current_src_hs_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_SRC_HS_EN) |
+		   VC4_SET_FIELD(chan2_settings->main_tap_current_select,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_MAIN_TAP_CURRENT_SELECT) |
+		   VC4_SET_FIELD(chan2_settings->post_tap_current_select,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_POST_TAP_CURRENT_SELECT) |
+		   VC4_SET_FIELD(chan2_settings->slew_ctl_slow_loading,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_CTL_SLOW_LOADING) |
+		   VC4_SET_FIELD(chan2_settings->slew_ctl_slow_driving,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_CTL_SLOW_DRIVING) |
+		   VC4_SET_FIELD(chan2_settings->ffe_pre_tap_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_PRE_TAP_EN));
+
+	clock_settings =
+		vc6_phy_get_channel_settings(variant->phy_lane_mapping[PHY_LANE_CK],
+					     pixel_freq);
+	HDMI_WRITE(HDMI_TX_PHY_CTL_CK,
+		   VC4_SET_FIELD(clock_settings->ext_current_ctl,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_CTL) |
+		   VC4_SET_FIELD(clock_settings->ffe_enable,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_ENABLE) |
+		   VC4_SET_FIELD(clock_settings->slew_rate_ctl,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_RATE_CTL) |
+		   VC4_SET_FIELD(clock_settings->ffe_post_tap_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_POST_TAP_EN) |
+		   VC4_SET_FIELD(clock_settings->ldmos_bias_ctl,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_LDMOS_BIAS_CTL) |
+		   VC4_SET_FIELD(clock_settings->com_mode_ldmos_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_COM_MODE_LDMOS_EN) |
+		   VC4_SET_FIELD(clock_settings->edge_sel,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EDGE_SEL) |
+		   VC4_SET_FIELD(clock_settings->ext_current_src_hs_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_SRC_HS_EN) |
+		   VC4_SET_FIELD(clock_settings->term_ctl,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_TERM_CTL) |
+		   VC4_SET_FIELD(clock_settings->ext_current_src_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_EXT_CURRENT_SRC_EN) |
+		   VC4_SET_FIELD(clock_settings->int_current_src_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_SRC_EN) |
+		   VC4_SET_FIELD(clock_settings->int_current_ctl,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_CTL) |
+		   VC4_SET_FIELD(clock_settings->int_current_src_hs_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_INT_CURRENT_SRC_HS_EN) |
+		   VC4_SET_FIELD(clock_settings->main_tap_current_select,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_MAIN_TAP_CURRENT_SELECT) |
+		   VC4_SET_FIELD(clock_settings->post_tap_current_select,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_POST_TAP_CURRENT_SELECT) |
+		   VC4_SET_FIELD(clock_settings->slew_ctl_slow_loading,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_CTL_SLOW_LOADING) |
+		   VC4_SET_FIELD(clock_settings->slew_ctl_slow_driving,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_SLEW_CTL_SLOW_DRIVING) |
+		   VC4_SET_FIELD(clock_settings->ffe_pre_tap_en,
+				 VC6_HDMI_TX_PHY_HDMI_CTRL_CHX_FFE_PRE_TAP_EN));
+
+	if (pixel_freq >= 340000000)
+		word_sel = 3;
+	else
+		word_sel = 0;
+	HDMI_WRITE(HDMI_TX_PHY_TMDS_CLK_WORD_SEL, word_sel);
+
+	HDMI_WRITE(HDMI_TX_PHY_POWERUP_CTL,
+		   VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_BG_PWRUP |
+		   VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_LDO_PWRUP |
+		   VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_BIAS_PWRUP |
+		   VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_TX_CK_PWRUP |
+		   VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_TX_2_PWRUP |
+		   VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_TX_1_PWRUP |
+		   VC6_HDMI_TX_PHY_HDMI_POWERUP_CTL_TX_0_PWRUP);
+
+	HDMI_WRITE(HDMI_TX_PHY_PLL_POWERUP_CTL,
+		   VC6_HDMI_TX_PHY_PLL_POWERUP_CTL_PLL_PWRUP);
+
+	HDMI_WRITE(HDMI_TX_PHY_PLL_RESET_CTL,
+		   HDMI_READ(HDMI_TX_PHY_PLL_RESET_CTL) &
+		   ~VC6_HDMI_TX_PHY_PLL_RESET_CTL_PLL_RESETB);
+
+	HDMI_WRITE(HDMI_TX_PHY_PLL_RESET_CTL,
+		   HDMI_READ(HDMI_TX_PHY_PLL_RESET_CTL) |
+		   VC6_HDMI_TX_PHY_PLL_RESET_CTL_PLL_RESETB);
+
+	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+}
+
+void vc6_hdmi_phy_disable(struct vc4_hdmi *vc4_hdmi)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&vc4_hdmi->hw_lock, flags);
+	vc6_hdmi_reset_phy(vc4_hdmi);
+	spin_unlock_irqrestore(&vc4_hdmi->hw_lock, flags);
+}
diff --git a/drivers/gpu/drm/vc4/vc4_hdmi_regs.h b/drivers/gpu/drm/vc4/vc4_hdmi_regs.h
index b04b2fc8d831..59bfd69f54d9 100644
--- a/drivers/gpu/drm/vc4/vc4_hdmi_regs.h
+++ b/drivers/gpu/drm/vc4/vc4_hdmi_regs.h
@@ -111,13 +111,30 @@ enum vc4_hdmi_field {
 	HDMI_TX_PHY_CTL_1,
 	HDMI_TX_PHY_CTL_2,
 	HDMI_TX_PHY_CTL_3,
+	HDMI_TX_PHY_CTL_CK,
 	HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_1,
 	HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_2,
 	HDMI_TX_PHY_PLL_CALIBRATION_CONFIG_4,
 	HDMI_TX_PHY_PLL_CFG,
+	HDMI_TX_PHY_PLL_CFG_PDIV,
 	HDMI_TX_PHY_PLL_CTL_0,
 	HDMI_TX_PHY_PLL_CTL_1,
+	HDMI_TX_PHY_PLL_MISC_0,
+	HDMI_TX_PHY_PLL_MISC_1,
+	HDMI_TX_PHY_PLL_MISC_2,
+	HDMI_TX_PHY_PLL_MISC_3,
+	HDMI_TX_PHY_PLL_MISC_4,
+	HDMI_TX_PHY_PLL_MISC_5,
+	HDMI_TX_PHY_PLL_MISC_6,
+	HDMI_TX_PHY_PLL_MISC_7,
+	HDMI_TX_PHY_PLL_MISC_8,
+	HDMI_TX_PHY_PLL_POST_KDIV,
+	HDMI_TX_PHY_PLL_POWERUP_CTL,
+	HDMI_TX_PHY_PLL_REFCLK,
+	HDMI_TX_PHY_PLL_RESET_CTL,
+	HDMI_TX_PHY_PLL_VCOCLK_DIV,
 	HDMI_TX_PHY_POWERDOWN_CTL,
+	HDMI_TX_PHY_POWERUP_CTL,
 	HDMI_TX_PHY_RESET_CTL,
 	HDMI_TX_PHY_TMDS_CLK_WORD_SEL,
 	HDMI_VEC_INTERFACE_CFG,
@@ -411,6 +428,206 @@ static const struct vc4_hdmi_register __maybe_unused vc5_hdmi_hdmi1_fields[] = {
 	VC5_CSC_REG(HDMI_CSC_CHANNEL_CTL, 0x02c),
 };
 
+static const struct vc4_hdmi_register __maybe_unused vc6_hdmi_hdmi0_fields[] = {
+	VC4_HD_REG(HDMI_DVP_CTL, 0x0000),
+	VC4_HD_REG(HDMI_MAI_CTL, 0x0010),
+	VC4_HD_REG(HDMI_MAI_THR, 0x0014),
+	VC4_HD_REG(HDMI_MAI_FMT, 0x0018),
+	VC4_HD_REG(HDMI_MAI_DATA, 0x001c),
+	VC4_HD_REG(HDMI_MAI_SMP, 0x0020),
+	VC4_HD_REG(HDMI_VID_CTL, 0x0044),
+	VC4_HD_REG(HDMI_FRAME_COUNT, 0x0060),
+
+	VC4_HDMI_REG(HDMI_FIFO_CTL, 0x07c),
+	VC4_HDMI_REG(HDMI_AUDIO_PACKET_CONFIG, 0x0c0),
+	VC4_HDMI_REG(HDMI_RAM_PACKET_CONFIG, 0x0c4),
+	VC4_HDMI_REG(HDMI_RAM_PACKET_STATUS, 0x0cc),
+	VC4_HDMI_REG(HDMI_CRP_CFG, 0x0d0),
+	VC4_HDMI_REG(HDMI_CTS_0, 0x0d4),
+	VC4_HDMI_REG(HDMI_CTS_1, 0x0d8),
+	VC4_HDMI_REG(HDMI_SCHEDULER_CONTROL, 0x0e8),
+	VC4_HDMI_REG(HDMI_HORZA, 0x0ec),
+	VC4_HDMI_REG(HDMI_HORZB, 0x0f0),
+	VC4_HDMI_REG(HDMI_VERTA0, 0x0f4),
+	VC4_HDMI_REG(HDMI_VERTB0, 0x0f8),
+	VC4_HDMI_REG(HDMI_VERTA1, 0x100),
+	VC4_HDMI_REG(HDMI_VERTB1, 0x104),
+	VC4_HDMI_REG(HDMI_MISC_CONTROL, 0x114),
+	VC4_HDMI_REG(HDMI_MAI_CHANNEL_MAP, 0x0a4),
+	VC4_HDMI_REG(HDMI_MAI_CONFIG, 0x0a8),
+	VC4_HDMI_REG(HDMI_FORMAT_DET_1, 0x148),
+	VC4_HDMI_REG(HDMI_FORMAT_DET_2, 0x14c),
+	VC4_HDMI_REG(HDMI_FORMAT_DET_3, 0x150),
+	VC4_HDMI_REG(HDMI_FORMAT_DET_4, 0x158),
+	VC4_HDMI_REG(HDMI_FORMAT_DET_5, 0x15c),
+	VC4_HDMI_REG(HDMI_FORMAT_DET_6, 0x160),
+	VC4_HDMI_REG(HDMI_FORMAT_DET_7, 0x164),
+	VC4_HDMI_REG(HDMI_FORMAT_DET_8, 0x168),
+	VC4_HDMI_REG(HDMI_FORMAT_DET_9, 0x16c),
+	VC4_HDMI_REG(HDMI_FORMAT_DET_10, 0x170),
+	VC4_HDMI_REG(HDMI_DEEP_COLOR_CONFIG_1, 0x18c),
+	VC4_HDMI_REG(HDMI_GCP_CONFIG, 0x194),
+	VC4_HDMI_REG(HDMI_GCP_WORD_1, 0x198),
+	VC4_HDMI_REG(HDMI_HOTPLUG, 0x1c8),
+	VC4_HDMI_REG(HDMI_SCRAMBLER_CTL, 0x1e4),
+
+	VC5_DVP_REG(HDMI_CLOCK_STOP, 0x0bc),
+	VC5_DVP_REG(HDMI_VEC_INTERFACE_CFG, 0x0f0),
+	VC5_DVP_REG(HDMI_VEC_INTERFACE_XBAR, 0x0f4),
+
+	VC5_PHY_REG(HDMI_TX_PHY_RESET_CTL, 0x000),
+	VC5_PHY_REG(HDMI_TX_PHY_POWERUP_CTL, 0x004),
+	VC5_PHY_REG(HDMI_TX_PHY_CTL_0, 0x008),
+	VC5_PHY_REG(HDMI_TX_PHY_CTL_1, 0x00c),
+	VC5_PHY_REG(HDMI_TX_PHY_CTL_2, 0x010),
+	VC5_PHY_REG(HDMI_TX_PHY_CTL_CK, 0x014),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_REFCLK, 0x01c),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_POST_KDIV, 0x028),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_VCOCLK_DIV, 0x02c),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_CFG, 0x044),
+	VC5_PHY_REG(HDMI_TX_PHY_TMDS_CLK_WORD_SEL, 0x054),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_0, 0x060),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_1, 0x064),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_2, 0x068),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_3, 0x06c),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_4, 0x070),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_5, 0x074),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_6, 0x078),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_7, 0x07c),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_8, 0x080),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_RESET_CTL, 0x190),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_POWERUP_CTL, 0x194),
+
+	VC5_RM_REG(HDMI_RM_CONTROL, 0x000),
+	VC5_RM_REG(HDMI_RM_OFFSET, 0x018),
+	VC5_RM_REG(HDMI_RM_FORMAT, 0x01c),
+
+	VC5_RAM_REG(HDMI_RAM_PACKET_START, 0x000),
+
+	VC5_CEC_REG(HDMI_CEC_CNTRL_1, 0x010),
+	VC5_CEC_REG(HDMI_CEC_CNTRL_2, 0x014),
+	VC5_CEC_REG(HDMI_CEC_CNTRL_3, 0x018),
+	VC5_CEC_REG(HDMI_CEC_CNTRL_4, 0x01c),
+	VC5_CEC_REG(HDMI_CEC_CNTRL_5, 0x020),
+	VC5_CEC_REG(HDMI_CEC_TX_DATA_1, 0x028),
+	VC5_CEC_REG(HDMI_CEC_TX_DATA_2, 0x02c),
+	VC5_CEC_REG(HDMI_CEC_TX_DATA_3, 0x030),
+	VC5_CEC_REG(HDMI_CEC_TX_DATA_4, 0x034),
+	VC5_CEC_REG(HDMI_CEC_RX_DATA_1, 0x038),
+	VC5_CEC_REG(HDMI_CEC_RX_DATA_2, 0x03c),
+	VC5_CEC_REG(HDMI_CEC_RX_DATA_3, 0x040),
+	VC5_CEC_REG(HDMI_CEC_RX_DATA_4, 0x044),
+
+	VC5_CSC_REG(HDMI_CSC_CTL, 0x000),
+	VC5_CSC_REG(HDMI_CSC_12_11, 0x004),
+	VC5_CSC_REG(HDMI_CSC_14_13, 0x008),
+	VC5_CSC_REG(HDMI_CSC_22_21, 0x00c),
+	VC5_CSC_REG(HDMI_CSC_24_23, 0x010),
+	VC5_CSC_REG(HDMI_CSC_32_31, 0x014),
+	VC5_CSC_REG(HDMI_CSC_34_33, 0x018),
+	VC5_CSC_REG(HDMI_CSC_CHANNEL_CTL, 0x02c),
+};
+
+static const struct vc4_hdmi_register __maybe_unused vc6_hdmi_hdmi1_fields[] = {
+	VC4_HD_REG(HDMI_DVP_CTL, 0x0000),
+	VC4_HD_REG(HDMI_MAI_CTL, 0x0030),
+	VC4_HD_REG(HDMI_MAI_THR, 0x0034),
+	VC4_HD_REG(HDMI_MAI_FMT, 0x0038),
+	VC4_HD_REG(HDMI_MAI_DATA, 0x003c),
+	VC4_HD_REG(HDMI_MAI_SMP, 0x0040),
+	VC4_HD_REG(HDMI_VID_CTL, 0x0048),
+	VC4_HD_REG(HDMI_FRAME_COUNT, 0x0064),
+
+	VC4_HDMI_REG(HDMI_FIFO_CTL, 0x07c),
+	VC4_HDMI_REG(HDMI_AUDIO_PACKET_CONFIG, 0x0c0),
+	VC4_HDMI_REG(HDMI_RAM_PACKET_CONFIG, 0x0c4),
+	VC4_HDMI_REG(HDMI_RAM_PACKET_STATUS, 0x0cc),
+	VC4_HDMI_REG(HDMI_CRP_CFG, 0x0d0),
+	VC4_HDMI_REG(HDMI_CTS_0, 0x0d4),
+	VC4_HDMI_REG(HDMI_CTS_1, 0x0d8),
+	VC4_HDMI_REG(HDMI_SCHEDULER_CONTROL, 0x0e8),
+	VC4_HDMI_REG(HDMI_HORZA, 0x0ec),
+	VC4_HDMI_REG(HDMI_HORZB, 0x0f0),
+	VC4_HDMI_REG(HDMI_VERTA0, 0x0f4),
+	VC4_HDMI_REG(HDMI_VERTB0, 0x0f8),
+	VC4_HDMI_REG(HDMI_VERTA1, 0x100),
+	VC4_HDMI_REG(HDMI_VERTB1, 0x104),
+	VC4_HDMI_REG(HDMI_MISC_CONTROL, 0x114),
+	VC4_HDMI_REG(HDMI_MAI_CHANNEL_MAP, 0x0a4),
+	VC4_HDMI_REG(HDMI_MAI_CONFIG, 0x0a8),
+	VC4_HDMI_REG(HDMI_FORMAT_DET_1, 0x148),
+	VC4_HDMI_REG(HDMI_FORMAT_DET_2, 0x14c),
+	VC4_HDMI_REG(HDMI_FORMAT_DET_3, 0x150),
+	VC4_HDMI_REG(HDMI_FORMAT_DET_4, 0x158),
+	VC4_HDMI_REG(HDMI_FORMAT_DET_5, 0x15c),
+	VC4_HDMI_REG(HDMI_FORMAT_DET_6, 0x160),
+	VC4_HDMI_REG(HDMI_FORMAT_DET_7, 0x164),
+	VC4_HDMI_REG(HDMI_FORMAT_DET_8, 0x168),
+	VC4_HDMI_REG(HDMI_FORMAT_DET_9, 0x16c),
+	VC4_HDMI_REG(HDMI_FORMAT_DET_10, 0x170),
+	VC4_HDMI_REG(HDMI_DEEP_COLOR_CONFIG_1, 0x18c),
+	VC4_HDMI_REG(HDMI_GCP_CONFIG, 0x194),
+	VC4_HDMI_REG(HDMI_GCP_WORD_1, 0x198),
+	VC4_HDMI_REG(HDMI_HOTPLUG, 0x1c8),
+	VC4_HDMI_REG(HDMI_SCRAMBLER_CTL, 0x1e4),
+
+	VC5_DVP_REG(HDMI_CLOCK_STOP, 0x0bc),
+	VC5_DVP_REG(HDMI_VEC_INTERFACE_CFG, 0x0f0),
+	VC5_DVP_REG(HDMI_VEC_INTERFACE_XBAR, 0x0f4),
+
+	VC5_PHY_REG(HDMI_TX_PHY_RESET_CTL, 0x000),
+	VC5_PHY_REG(HDMI_TX_PHY_POWERUP_CTL, 0x004),
+	VC5_PHY_REG(HDMI_TX_PHY_CTL_0, 0x008),
+	VC5_PHY_REG(HDMI_TX_PHY_CTL_1, 0x00c),
+	VC5_PHY_REG(HDMI_TX_PHY_CTL_2, 0x010),
+	VC5_PHY_REG(HDMI_TX_PHY_CTL_CK, 0x014),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_REFCLK, 0x01c),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_POST_KDIV, 0x028),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_VCOCLK_DIV, 0x02c),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_CFG, 0x044),
+	VC5_PHY_REG(HDMI_TX_PHY_TMDS_CLK_WORD_SEL, 0x054),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_0, 0x060),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_1, 0x064),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_2, 0x068),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_3, 0x06c),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_4, 0x070),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_5, 0x074),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_6, 0x078),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_7, 0x07c),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_MISC_8, 0x080),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_RESET_CTL, 0x190),
+	VC5_PHY_REG(HDMI_TX_PHY_PLL_POWERUP_CTL, 0x194),
+
+	VC5_RM_REG(HDMI_RM_CONTROL, 0x000),
+	VC5_RM_REG(HDMI_RM_OFFSET, 0x018),
+	VC5_RM_REG(HDMI_RM_FORMAT, 0x01c),
+
+	VC5_RAM_REG(HDMI_RAM_PACKET_START, 0x000),
+
+	VC5_CEC_REG(HDMI_CEC_CNTRL_1, 0x010),
+	VC5_CEC_REG(HDMI_CEC_CNTRL_2, 0x014),
+	VC5_CEC_REG(HDMI_CEC_CNTRL_3, 0x018),
+	VC5_CEC_REG(HDMI_CEC_CNTRL_4, 0x01c),
+	VC5_CEC_REG(HDMI_CEC_CNTRL_5, 0x020),
+	VC5_CEC_REG(HDMI_CEC_TX_DATA_1, 0x028),
+	VC5_CEC_REG(HDMI_CEC_TX_DATA_2, 0x02c),
+	VC5_CEC_REG(HDMI_CEC_TX_DATA_3, 0x030),
+	VC5_CEC_REG(HDMI_CEC_TX_DATA_4, 0x034),
+	VC5_CEC_REG(HDMI_CEC_RX_DATA_1, 0x038),
+	VC5_CEC_REG(HDMI_CEC_RX_DATA_2, 0x03c),
+	VC5_CEC_REG(HDMI_CEC_RX_DATA_3, 0x040),
+	VC5_CEC_REG(HDMI_CEC_RX_DATA_4, 0x044),
+
+	VC5_CSC_REG(HDMI_CSC_CTL, 0x000),
+	VC5_CSC_REG(HDMI_CSC_12_11, 0x004),
+	VC5_CSC_REG(HDMI_CSC_14_13, 0x008),
+	VC5_CSC_REG(HDMI_CSC_22_21, 0x00c),
+	VC5_CSC_REG(HDMI_CSC_24_23, 0x010),
+	VC5_CSC_REG(HDMI_CSC_32_31, 0x014),
+	VC5_CSC_REG(HDMI_CSC_34_33, 0x018),
+	VC5_CSC_REG(HDMI_CSC_CHANNEL_CTL, 0x02c),
+};
+
 static inline
 void __iomem *__vc4_hdmi_get_field_base(struct vc4_hdmi *hdmi,
 					enum vc4_hdmi_regs reg)
@@ -498,8 +715,11 @@ static inline void vc4_hdmi_write(struct vc4_hdmi *hdmi,
 
 	field = &variant->registers[reg];
 	base = __vc4_hdmi_get_field_base(hdmi, field->reg);
-	if (!base)
+	if (!base) {
+		dev_warn(&hdmi->pdev->dev,
+			 "Unknown register ID %u\n", reg);
 		return;
+	}
 
 	writel(value, base + field->offset);
 }
diff --git a/drivers/gpu/drm/vc4/vc4_hvs.c b/drivers/gpu/drm/vc4/vc4_hvs.c
index c389e82463bf..cffa2474b6c7 100644
--- a/drivers/gpu/drm/vc4/vc4_hvs.c
+++ b/drivers/gpu/drm/vc4/vc4_hvs.c
@@ -33,7 +33,7 @@
 #include "vc4_drv.h"
 #include "vc4_regs.h"
 
-static const struct debugfs_reg32 hvs_regs[] = {
+static const struct debugfs_reg32 vc4_hvs_regs[] = {
 	VC4_REG32(SCALER_DISPCTRL),
 	VC4_REG32(SCALER_DISPSTAT),
 	VC4_REG32(SCALER_DISPID),
@@ -67,6 +67,140 @@ static const struct debugfs_reg32 hvs_regs[] = {
 	VC4_REG32(SCALER_OLEDCOEF2),
 };
 
+static const struct debugfs_reg32 vc6_hvs_regs[] = {
+	VC4_REG32(SCALER6_VERSION),
+	VC4_REG32(SCALER6_CXM_SIZE),
+	VC4_REG32(SCALER6_LBM_SIZE),
+	VC4_REG32(SCALER6_UBM_SIZE),
+	VC4_REG32(SCALER6_COBA_SIZE),
+	VC4_REG32(SCALER6_COB_SIZE),
+	VC4_REG32(SCALER6_CONTROL),
+	VC4_REG32(SCALER6_FETCHER_STATUS),
+	VC4_REG32(SCALER6_FETCH_STATUS),
+	VC4_REG32(SCALER6_HANDLE_ERROR),
+	VC4_REG32(SCALER6_DISP0_CTRL0),
+	VC4_REG32(SCALER6_DISP0_CTRL1),
+	VC4_REG32(SCALER6_DISP0_BGND),
+	VC4_REG32(SCALER6_DISP0_LPTRS),
+	VC4_REG32(SCALER6_DISP0_COB),
+	VC4_REG32(SCALER6_DISP0_STATUS),
+	VC4_REG32(SCALER6_DISP0_DL),
+	VC4_REG32(SCALER6_DISP0_RUN),
+	VC4_REG32(SCALER6_DISP1_CTRL0),
+	VC4_REG32(SCALER6_DISP1_CTRL1),
+	VC4_REG32(SCALER6_DISP1_BGND),
+	VC4_REG32(SCALER6_DISP1_LPTRS),
+	VC4_REG32(SCALER6_DISP1_COB),
+	VC4_REG32(SCALER6_DISP1_STATUS),
+	VC4_REG32(SCALER6_DISP1_DL),
+	VC4_REG32(SCALER6_DISP1_RUN),
+	VC4_REG32(SCALER6_DISP2_CTRL0),
+	VC4_REG32(SCALER6_DISP2_CTRL1),
+	VC4_REG32(SCALER6_DISP2_BGND),
+	VC4_REG32(SCALER6_DISP2_LPTRS),
+	VC4_REG32(SCALER6_DISP2_COB),
+	VC4_REG32(SCALER6_DISP2_STATUS),
+	VC4_REG32(SCALER6_DISP2_DL),
+	VC4_REG32(SCALER6_DISP2_RUN),
+	VC4_REG32(SCALER6_EOLN),
+	VC4_REG32(SCALER6_DL_STATUS),
+	VC4_REG32(SCALER6_BFG_MISC),
+	VC4_REG32(SCALER6_QOS0),
+	VC4_REG32(SCALER6_PROF0),
+	VC4_REG32(SCALER6_QOS1),
+	VC4_REG32(SCALER6_PROF1),
+	VC4_REG32(SCALER6_QOS2),
+	VC4_REG32(SCALER6_PROF2),
+	VC4_REG32(SCALER6_PRI_MAP0),
+	VC4_REG32(SCALER6_PRI_MAP1),
+	VC4_REG32(SCALER6_HISTCTRL),
+	VC4_REG32(SCALER6_HISTBIN0),
+	VC4_REG32(SCALER6_HISTBIN1),
+	VC4_REG32(SCALER6_HISTBIN2),
+	VC4_REG32(SCALER6_HISTBIN3),
+	VC4_REG32(SCALER6_HISTBIN4),
+	VC4_REG32(SCALER6_HISTBIN5),
+	VC4_REG32(SCALER6_HISTBIN6),
+	VC4_REG32(SCALER6_HISTBIN7),
+	VC4_REG32(SCALER6_HDR_CFG_REMAP),
+	VC4_REG32(SCALER6_COL_SPACE),
+	VC4_REG32(SCALER6_HVS_ID),
+	VC4_REG32(SCALER6_CFC1),
+	VC4_REG32(SCALER6_DISP_UPM_ISO0),
+	VC4_REG32(SCALER6_DISP_UPM_ISO1),
+	VC4_REG32(SCALER6_DISP_UPM_ISO2),
+	VC4_REG32(SCALER6_DISP_LBM_ISO0),
+	VC4_REG32(SCALER6_DISP_LBM_ISO1),
+	VC4_REG32(SCALER6_DISP_LBM_ISO2),
+	VC4_REG32(SCALER6_DISP_COB_ISO0),
+	VC4_REG32(SCALER6_DISP_COB_ISO1),
+	VC4_REG32(SCALER6_DISP_COB_ISO2),
+	VC4_REG32(SCALER6_BAD_COB),
+	VC4_REG32(SCALER6_BAD_LBM),
+	VC4_REG32(SCALER6_BAD_UPM),
+	VC4_REG32(SCALER6_BAD_AXI),
+};
+
+static const struct debugfs_reg32 vc6_d_hvs_regs[] = {
+	VC4_REG32(SCALER6D_VERSION),
+	VC4_REG32(SCALER6D_CXM_SIZE),
+	VC4_REG32(SCALER6D_LBM_SIZE),
+	VC4_REG32(SCALER6D_UBM_SIZE),
+	VC4_REG32(SCALER6D_COBA_SIZE),
+	VC4_REG32(SCALER6D_COB_SIZE),
+	VC4_REG32(SCALER6D_CONTROL),
+	VC4_REG32(SCALER6D_FETCHER_STATUS),
+	VC4_REG32(SCALER6D_FETCH_STATUS),
+	VC4_REG32(SCALER6D_HANDLE_ERROR),
+	VC4_REG32(SCALER6D_DISP0_CTRL0),
+	VC4_REG32(SCALER6D_DISP0_CTRL1),
+	VC4_REG32(SCALER6D_DISP0_BGND0),
+	VC4_REG32(SCALER6D_DISP0_BGND1),
+	VC4_REG32(SCALER6D_DISP0_LPTRS),
+	VC4_REG32(SCALER6D_DISP0_COB),
+	VC4_REG32(SCALER6D_DISP0_STATUS),
+	VC4_REG32(SCALER6D_DISP0_DL),
+	VC4_REG32(SCALER6D_DISP0_RUN),
+	VC4_REG32(SCALER6D_DISP1_CTRL0),
+	VC4_REG32(SCALER6D_DISP1_CTRL1),
+	VC4_REG32(SCALER6D_DISP1_BGND0),
+	VC4_REG32(SCALER6D_DISP1_BGND1),
+	VC4_REG32(SCALER6D_DISP1_LPTRS),
+	VC4_REG32(SCALER6D_DISP1_COB),
+	VC4_REG32(SCALER6D_DISP1_STATUS),
+	VC4_REG32(SCALER6D_DISP1_DL),
+	VC4_REG32(SCALER6D_DISP1_RUN),
+	VC4_REG32(SCALER6D_DISP2_CTRL0),
+	VC4_REG32(SCALER6D_DISP2_CTRL1),
+	VC4_REG32(SCALER6D_DISP2_BGND0),
+	VC4_REG32(SCALER6D_DISP2_BGND1),
+	VC4_REG32(SCALER6D_DISP2_LPTRS),
+	VC4_REG32(SCALER6D_DISP2_COB),
+	VC4_REG32(SCALER6D_DISP2_STATUS),
+	VC4_REG32(SCALER6D_DISP2_DL),
+	VC4_REG32(SCALER6D_DISP2_RUN),
+	VC4_REG32(SCALER6D_EOLN),
+	VC4_REG32(SCALER6D_DL_STATUS),
+	VC4_REG32(SCALER6D_QOS0),
+	VC4_REG32(SCALER6D_PROF0),
+	VC4_REG32(SCALER6D_QOS1),
+	VC4_REG32(SCALER6D_PROF1),
+	VC4_REG32(SCALER6D_QOS2),
+	VC4_REG32(SCALER6D_PROF2),
+	VC4_REG32(SCALER6D_PRI_MAP0),
+	VC4_REG32(SCALER6D_PRI_MAP1),
+	VC4_REG32(SCALER6D_HISTCTRL),
+	VC4_REG32(SCALER6D_HISTBIN0),
+	VC4_REG32(SCALER6D_HISTBIN1),
+	VC4_REG32(SCALER6D_HISTBIN2),
+	VC4_REG32(SCALER6D_HISTBIN3),
+	VC4_REG32(SCALER6D_HISTBIN4),
+	VC4_REG32(SCALER6D_HISTBIN5),
+	VC4_REG32(SCALER6D_HISTBIN6),
+	VC4_REG32(SCALER6D_HISTBIN7),
+	VC4_REG32(SCALER6D_HVS_ID),
+};
+
 void vc4_hvs_dump_state(struct vc4_hvs *hvs)
 {
 	struct drm_device *drm = &hvs->vc4->base;
@@ -145,6 +279,127 @@ static int vc4_hvs_debugfs_dlist(struct seq_file *m, void *data)
 	return 0;
 }
 
+static int vc6_hvs_debugfs_dlist(struct seq_file *m, void *data)
+{
+	struct drm_debugfs_entry *entry = m->private;
+	struct drm_device *dev = entry->dev;
+	struct vc4_dev *vc4 = to_vc4_dev(dev);
+	struct vc4_hvs *hvs = vc4->hvs;
+	struct drm_printer p = drm_seq_file_printer(m);
+	unsigned int dlist_mem_size = hvs->dlist_mem_size;
+	unsigned int next_entry_start;
+	unsigned int i;
+
+	for (i = 0; i < SCALER_CHANNELS_COUNT; i++) {
+		unsigned int active_dlist, dispstat;
+		unsigned int j;
+
+		dispstat = VC4_GET_FIELD(HVS_READ(SCALER6_DISPX_STATUS(i)),
+					 SCALER6_DISPX_STATUS_MODE);
+		if (dispstat == SCALER6_DISPX_STATUS_MODE_DISABLED ||
+		    dispstat == SCALER6_DISPX_STATUS_MODE_EOF) {
+			drm_printf(&p, "HVS chan %u disabled\n", i);
+			continue;
+		}
+
+		drm_printf(&p, "HVS chan %u:\n", i);
+
+		active_dlist = VC4_GET_FIELD(HVS_READ(SCALER6_DISPX_DL(i)),
+					     SCALER6_DISPX_DL_LACT);
+		next_entry_start = 0;
+
+		for (j = active_dlist; j < dlist_mem_size; j++) {
+			u32 dlist_word;
+
+			dlist_word = readl((u32 __iomem *)vc4->hvs->dlist + j);
+			drm_printf(&p, "dlist: %02d: 0x%08x\n", j,
+				   dlist_word);
+			if (!next_entry_start ||
+			    next_entry_start == j) {
+				if (dlist_word & SCALER_CTL0_END)
+					break;
+				next_entry_start = j +
+					VC4_GET_FIELD(dlist_word,
+						      SCALER_CTL0_SIZE);
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int vc6_hvs_debugfs_upm_allocs(struct seq_file *m, void *data)
+{
+	struct drm_debugfs_entry *entry = m->private;
+	struct drm_device *dev = entry->dev;
+	struct vc4_dev *vc4 = to_vc4_dev(dev);
+	struct vc4_hvs *hvs = vc4->hvs;
+	struct drm_printer p = drm_seq_file_printer(m);
+	struct vc4_upm_refcounts *refcount;
+	unsigned int i;
+
+	drm_printf(&p, "UPM Handles:\n");
+	for (i = 1; i <= VC4_NUM_UPM_HANDLES; i++) {
+		refcount = &hvs->upm_refcounts[i];
+		drm_printf(&p, "handle %u: refcount %u, size %zu [%08llx + %08llx]\n",
+			   i, refcount_read(&refcount->refcount), refcount->size,
+			   refcount->upm.start, refcount->upm.size);
+	}
+
+	return 0;
+}
+
+static int vc4_hvs_debugfs_dlist_allocs(struct seq_file *m, void *data)
+{
+	struct drm_debugfs_entry *entry = m->private;
+	struct drm_device *dev = entry->dev;
+	struct vc4_dev *vc4 = to_vc4_dev(dev);
+	struct vc4_hvs *hvs = vc4->hvs;
+	struct drm_printer p = drm_seq_file_printer(m);
+	struct vc4_hvs_dlist_allocation *cur, *next;
+	struct drm_mm_node *mm_node;
+	unsigned long flags;
+
+	spin_lock_irqsave(&hvs->mm_lock, flags);
+
+	drm_printf(&p, "Allocated nodes:\n");
+	list_for_each_entry(mm_node, drm_mm_nodes(&hvs->dlist_mm), node_list) {
+		drm_printf(&p, "node [%08llx + %08llx]\n", mm_node->start, mm_node->size);
+	}
+
+	drm_printf(&p, "Stale nodes:\n");
+	list_for_each_entry_safe(cur, next, &hvs->stale_dlist_entries, node) {
+		drm_printf(&p, "node [%08llx + %08llx] channel %u frcnt %u\n",
+			   cur->mm_node.start, cur->mm_node.size, cur->channel,
+			   cur->target_frame_count);
+	}
+
+	spin_unlock_irqrestore(&hvs->mm_lock, flags);
+
+	return 0;
+}
+
+static int vc4_hvs_debugfs_lbm_allocs(struct seq_file *m, void *data)
+{
+	struct drm_debugfs_entry *entry = m->private;
+	struct drm_device *dev = entry->dev;
+	struct vc4_dev *vc4 = to_vc4_dev(dev);
+	struct vc4_hvs *hvs = vc4->hvs;
+	struct drm_printer p = drm_seq_file_printer(m);
+	struct vc4_lbm_refcounts *refcount;
+	unsigned int i;
+
+	drm_printf(&p, "LBM Handles:\n");
+	for (i = 0; i < VC4_NUM_LBM_HANDLES; i++) {
+		refcount = &hvs->lbm_refcounts[i];
+		drm_printf(&p, "handle %u: refcount %u, size %zu [%08llx + %08llx]\n",
+			   i, refcount_read(&refcount->refcount), refcount->size,
+			   refcount->lbm.start, refcount->lbm.size);
+	}
+
+	return 0;
+}
+
 /* The filter kernel is composed of dwords each containing 3 9-bit
  * signed integers packed next to each other.
  */
@@ -178,6 +433,9 @@ static int vc4_hvs_debugfs_dlist(struct seq_file *m, void *data)
 static const u32 mitchell_netravali_1_3_1_3_kernel[] =
 	VC4_LINEAR_PHASE_KERNEL(0, -2, -6, -8, -10, -8, -3, 2, 18,
 				50, 82, 119, 155, 187, 213, 227);
+static const u32 nearest_neighbour_kernel[] =
+	VC4_LINEAR_PHASE_KERNEL(0, 0, 0, 0, 0, 0, 0, 0,
+				1, 1, 1, 1, 255, 255, 255, 255);
 
 static int vc4_hvs_upload_linear_kernel(struct vc4_hvs *hvs,
 					struct drm_mm_node *space,
@@ -215,12 +473,15 @@ static int vc4_hvs_upload_linear_kernel(struct vc4_hvs *hvs,
 static void vc4_hvs_lut_load(struct vc4_hvs *hvs,
 			     struct vc4_crtc *vc4_crtc)
 {
-	struct drm_device *drm = &hvs->vc4->base;
+	struct vc4_dev *vc4 = hvs->vc4;
+	struct drm_device *drm = &vc4->base;
 	struct drm_crtc *crtc = &vc4_crtc->base;
 	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc->state);
 	int idx;
 	u32 i;
 
+	WARN_ON_ONCE(vc4->gen > VC4_GEN_5);
+
 	if (!drm_dev_enter(drm, &idx))
 		return;
 
@@ -263,28 +524,326 @@ static void vc4_hvs_update_gamma_lut(struct vc4_hvs *hvs,
 	vc4_hvs_lut_load(hvs, vc4_crtc);
 }
 
+static void vc4_hvs_irq_enable_eof(struct vc4_hvs *hvs,
+				   unsigned int channel)
+{
+	struct vc4_dev *vc4 = hvs->vc4;
+
+	if (hvs->eof_irq[channel].enabled)
+		return;
+
+	switch (vc4->gen) {
+	case VC4_GEN_4:
+		HVS_WRITE(SCALER_DISPCTRL,
+			  HVS_READ(SCALER_DISPCTRL) |
+			  SCALER_DISPCTRL_DSPEIEOF(channel));
+		break;
+
+	case VC4_GEN_5:
+		HVS_WRITE(SCALER_DISPCTRL,
+			  HVS_READ(SCALER_DISPCTRL) |
+			  SCALER5_DISPCTRL_DSPEIEOF(channel));
+		break;
+
+	case VC4_GEN_6_C:
+	case VC4_GEN_6_D:
+		enable_irq(hvs->eof_irq[channel].desc);
+		break;
+
+	default:
+		break;
+	}
+
+	hvs->eof_irq[channel].enabled = true;
+}
+
+static void vc4_hvs_irq_clear_eof(struct vc4_hvs *hvs,
+				  unsigned int channel)
+{
+	struct vc4_dev *vc4 = hvs->vc4;
+
+	if (!hvs->eof_irq[channel].enabled)
+		return;
+
+	switch (vc4->gen) {
+	case VC4_GEN_4:
+		HVS_WRITE(SCALER_DISPCTRL,
+			  HVS_READ(SCALER_DISPCTRL) &
+			  ~SCALER_DISPCTRL_DSPEIEOF(channel));
+		break;
+
+	case VC4_GEN_5:
+		HVS_WRITE(SCALER_DISPCTRL,
+			  HVS_READ(SCALER_DISPCTRL) &
+			  ~SCALER5_DISPCTRL_DSPEIEOF(channel));
+		break;
+
+	case VC4_GEN_6_C:
+	case VC4_GEN_6_D:
+		disable_irq_nosync(hvs->eof_irq[channel].desc);
+		break;
+
+	default:
+		break;
+	}
+
+	hvs->eof_irq[channel].enabled = false;
+}
+
+static void vc4_hvs_free_dlist_entry_locked(struct vc4_hvs *hvs,
+					    struct vc4_hvs_dlist_allocation *alloc);
+
+static struct vc4_hvs_dlist_allocation *
+vc4_hvs_alloc_dlist_entry(struct vc4_hvs *hvs,
+			  unsigned int channel,
+			  size_t dlist_count)
+{
+	struct vc4_dev *vc4 = hvs->vc4;
+	struct drm_device *dev = &vc4->base;
+	struct vc4_hvs_dlist_allocation *alloc;
+	struct vc4_hvs_dlist_allocation *cur, *next;
+	unsigned long flags;
+	int ret;
+
+	if (channel == VC4_HVS_CHANNEL_DISABLED)
+		return NULL;
+
+	alloc = kzalloc(sizeof(*alloc), GFP_KERNEL);
+	if (!alloc)
+		return ERR_PTR(-ENOMEM);
+
+	INIT_LIST_HEAD(&alloc->node);
+
+	spin_lock_irqsave(&hvs->mm_lock, flags);
+	ret = drm_mm_insert_node(&hvs->dlist_mm, &alloc->mm_node,
+				 dlist_count);
+	spin_unlock_irqrestore(&hvs->mm_lock, flags);
+
+	if (ret) {
+		drm_err(dev, "Failed to allocate DLIST entry. Requested size=%zu. ret=%d. DISPCTRL is %08x\n",
+			dlist_count, ret, HVS_READ(SCALER_DISPCTRL));
+
+		/* This should never happen as stale entries should get released
+		 * as the frame counter interrupt triggers.
+		 * However we've seen this fail for reasons currently unknown.
+		 * Free all stale entries now so we should be able to complete
+		 * this allocation.
+		 */
+		spin_lock_irqsave(&hvs->mm_lock, flags);
+		list_for_each_entry_safe(cur, next, &hvs->stale_dlist_entries, node) {
+			vc4_hvs_free_dlist_entry_locked(hvs, cur);
+		}
+
+		ret = drm_mm_insert_node(&hvs->dlist_mm, &alloc->mm_node,
+					 dlist_count);
+		spin_unlock_irqrestore(&hvs->mm_lock, flags);
+
+		if (ret)
+			return ERR_PTR(ret);
+	}
+
+	alloc->channel = channel;
+
+	return alloc;
+}
+
+static void vc4_hvs_free_dlist_entry_locked(struct vc4_hvs *hvs,
+					    struct vc4_hvs_dlist_allocation *alloc)
+{
+	lockdep_assert_held(&hvs->mm_lock);
+
+	if (!list_empty(&alloc->node))
+		list_del(&alloc->node);
+
+	drm_mm_remove_node(&alloc->mm_node);
+	kfree(alloc);
+}
+
+void vc4_hvs_mark_dlist_entry_stale(struct vc4_hvs *hvs,
+				    struct vc4_hvs_dlist_allocation *alloc)
+{
+	unsigned long flags;
+	u8 frcnt;
+
+	if (!alloc)
+		return;
+
+	if (!drm_mm_node_allocated(&alloc->mm_node))
+		return;
+
+	/*
+	 * Kunit tests run with a mock device and we consider any hardware
+	 * access a test failure. Let's free the dlist allocation right away if
+	 * we're running under kunit, we won't risk a dlist corruption anyway.
+	 *
+	 * Likewise if the allocation was only checked and never programmed, we
+	 * can destroy the allocation immediately.
+	 */
+	if (kunit_get_current_test() || !alloc->dlist_programmed) {
+		spin_lock_irqsave(&hvs->mm_lock, flags);
+		vc4_hvs_free_dlist_entry_locked(hvs, alloc);
+		spin_unlock_irqrestore(&hvs->mm_lock, flags);
+		return;
+	}
+
+	frcnt = vc4_hvs_get_fifo_frame_count(hvs, alloc->channel);
+	alloc->target_frame_count = (frcnt + 1) & ((1 << 6) - 1);
+
+	spin_lock_irqsave(&hvs->mm_lock, flags);
+
+	list_add_tail(&alloc->node, &hvs->stale_dlist_entries);
+
+	HVS_WRITE(SCALER_DISPSTAT, SCALER_DISPSTAT_EOF(alloc->channel));
+	vc4_hvs_irq_enable_eof(hvs, alloc->channel);
+
+	spin_unlock_irqrestore(&hvs->mm_lock, flags);
+}
+
+static void vc4_hvs_schedule_dlist_sweep(struct vc4_hvs *hvs,
+					 unsigned int channel)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&hvs->mm_lock, flags);
+
+	if (!list_empty(&hvs->stale_dlist_entries))
+		queue_work(system_unbound_wq, &hvs->free_dlist_work);
+
+	if (list_empty(&hvs->stale_dlist_entries))
+		vc4_hvs_irq_clear_eof(hvs, channel);
+
+	spin_unlock_irqrestore(&hvs->mm_lock, flags);
+}
+
+/*
+ * Frame counts are essentially sequence numbers over 6 bits, and we
+ * thus can use sequence number arithmetic and follow the RFC1982 to
+ * implement proper comparison between them.
+ */
+static bool vc4_hvs_frcnt_lte(u8 cnt1, u8 cnt2)
+{
+	return (s8)((cnt1 << 2) - (cnt2 << 2)) <= 0;
+}
+
+static bool vc4_hvs_check_channel_active(struct vc4_hvs *hvs, unsigned int fifo)
+{
+	struct vc4_dev *vc4 = hvs->vc4;
+	struct drm_device *drm = &vc4->base;
+	bool enabled = false;
+	int idx;
+
+	if (!drm_dev_enter(drm, &idx))
+		return 0;
+
+	if (vc4->gen >= VC4_GEN_6_C)
+		enabled = HVS_READ(SCALER6_DISPX_CTRL0(fifo)) & SCALER6_DISPX_CTRL0_ENB;
+	else
+		enabled = HVS_READ(SCALER_DISPCTRLX(fifo)) & SCALER_DISPCTRLX_ENABLE;
+
+	drm_dev_exit(idx);
+	return enabled;
+}
+
+/*
+ * Some atomic commits (legacy cursor updates, mostly) will not wait for
+ * the next vblank and will just return once the commit has been pushed
+ * to the hardware.
+ *
+ * On the hardware side, our HVS stores the planes parameters in its
+ * context RAM, and will use part of the RAM to store data during the
+ * frame rendering.
+ *
+ * This interacts badly if we get multiple commits before the next
+ * vblank since we could end up overwriting the DLIST entries used by
+ * previous commits if our dlist allocation reuses that entry. In such a
+ * case, we would overwrite the data currently being used by the
+ * hardware, resulting in a corrupted frame.
+ *
+ * In order to work around this, we'll queue the dlist entries in a list
+ * once the associated CRTC state is destroyed. The HVS only allows us
+ * to know which entry is being active, but not which one are no longer
+ * being used, so in order to avoid freeing entries that are still used
+ * by the hardware we add a guesstimate of the frame count where our
+ * entry will no longer be used, and thus will only free those entries
+ * when we will have reached that frame count.
+ */
+static void vc4_hvs_dlist_free_work(struct work_struct *work)
+{
+	struct vc4_hvs *hvs = container_of(work, struct vc4_hvs, free_dlist_work);
+	struct vc4_hvs_dlist_allocation *cur, *next;
+	unsigned long flags;
+	bool active[3];
+	u8 frcnt[3];
+	int i;
+
+	spin_lock_irqsave(&hvs->mm_lock, flags);
+	for (i = 0; i < 3; i++) {
+		frcnt[i] = vc4_hvs_get_fifo_frame_count(hvs, i);
+		active[i] = vc4_hvs_check_channel_active(hvs, i);
+	}
+	list_for_each_entry_safe(cur, next, &hvs->stale_dlist_entries, node) {
+		if (active[cur->channel] &&
+		    !vc4_hvs_frcnt_lte(cur->target_frame_count, frcnt[cur->channel]))
+			continue;
+
+		vc4_hvs_free_dlist_entry_locked(hvs, cur);
+	}
+	spin_unlock_irqrestore(&hvs->mm_lock, flags);
+}
+
 u8 vc4_hvs_get_fifo_frame_count(struct vc4_hvs *hvs, unsigned int fifo)
 {
-	struct drm_device *drm = &hvs->vc4->base;
+	struct vc4_dev *vc4 = hvs->vc4;
+	struct drm_device *drm = &vc4->base;
 	u8 field = 0;
 	int idx;
 
+	WARN_ON_ONCE(vc4->gen > VC4_GEN_6_D);
+
 	if (!drm_dev_enter(drm, &idx))
 		return 0;
 
-	switch (fifo) {
-	case 0:
-		field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT1),
-				      SCALER_DISPSTAT1_FRCNT0);
+	switch (vc4->gen) {
+	case VC4_GEN_6_C:
+	case VC4_GEN_6_D:
+		field = VC4_GET_FIELD(HVS_READ(SCALER6_DISPX_STATUS(fifo)),
+				      SCALER6_DISPX_STATUS_FRCNT);
 		break;
-	case 1:
-		field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT1),
-				      SCALER_DISPSTAT1_FRCNT1);
+	case VC4_GEN_5:
+		switch (fifo) {
+		case 0:
+			field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT1),
+					      SCALER5_DISPSTAT1_FRCNT0);
+			break;
+		case 1:
+			field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT1),
+					      SCALER5_DISPSTAT1_FRCNT1);
+			break;
+		case 2:
+			field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT2),
+					      SCALER5_DISPSTAT2_FRCNT2);
+			break;
+		}
 		break;
-	case 2:
-		field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT2),
-				      SCALER_DISPSTAT2_FRCNT2);
+	case VC4_GEN_4:
+		switch (fifo) {
+		case 0:
+			field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT1),
+					      SCALER_DISPSTAT1_FRCNT0);
+			break;
+		case 1:
+			field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT1),
+					      SCALER_DISPSTAT1_FRCNT1);
+			break;
+		case 2:
+			field = VC4_GET_FIELD(HVS_READ(SCALER_DISPSTAT2),
+					      SCALER_DISPSTAT2_FRCNT2);
+			break;
+		}
 		break;
+	default:
+		drm_err(drm, "Unknown VC4 generation: %d", vc4->gen);
+		return 0;
 	}
 
 	drm_dev_exit(idx);
@@ -297,53 +856,80 @@ int vc4_hvs_get_fifo_from_output(struct vc4_hvs *hvs, unsigned int output)
 	u32 reg;
 	int ret;
 
-	if (vc4->gen == VC4_GEN_4)
+	WARN_ON_ONCE(vc4->gen > VC4_GEN_6_D);
+
+	switch (vc4->gen) {
+	case VC4_GEN_4:
 		return output;
 
-	/*
-	 * NOTE: We should probably use drm_dev_enter()/drm_dev_exit()
-	 * here, but this function is only used during the DRM device
-	 * initialization, so we should be fine.
-	 */
+	case VC4_GEN_5:
+		/*
+		 * NOTE: We should probably use
+		 * drm_dev_enter()/drm_dev_exit() here, but this
+		 * function is only used during the DRM device
+		 * initialization, so we should be fine.
+		 */
 
-	switch (output) {
-	case 0:
-		return 0;
+		switch (output) {
+		case 0:
+			return 0;
 
-	case 1:
-		return 1;
+		case 1:
+			return 1;
 
-	case 2:
-		reg = HVS_READ(SCALER_DISPECTRL);
-		ret = FIELD_GET(SCALER_DISPECTRL_DSP2_MUX_MASK, reg);
-		if (ret == 0)
-			return 2;
+		case 2:
+			reg = HVS_READ(SCALER_DISPECTRL);
+			ret = FIELD_GET(SCALER_DISPECTRL_DSP2_MUX_MASK, reg);
+			if (ret == 0)
+				return 2;
 
-		return 0;
+			return 0;
 
-	case 3:
-		reg = HVS_READ(SCALER_DISPCTRL);
-		ret = FIELD_GET(SCALER_DISPCTRL_DSP3_MUX_MASK, reg);
-		if (ret == 3)
-			return -EPIPE;
+		case 3:
+			reg = HVS_READ(SCALER_DISPCTRL);
+			ret = FIELD_GET(SCALER_DISPCTRL_DSP3_MUX_MASK, reg);
+			if (ret == 3)
+				return -EPIPE;
 
-		return ret;
+			return ret;
 
-	case 4:
-		reg = HVS_READ(SCALER_DISPEOLN);
-		ret = FIELD_GET(SCALER_DISPEOLN_DSP4_MUX_MASK, reg);
-		if (ret == 3)
-			return -EPIPE;
+		case 4:
+			reg = HVS_READ(SCALER_DISPEOLN);
+			ret = FIELD_GET(SCALER_DISPEOLN_DSP4_MUX_MASK, reg);
+			if (ret == 3)
+				return -EPIPE;
 
-		return ret;
+			return ret;
+
+		case 5:
+			reg = HVS_READ(SCALER_DISPDITHER);
+			ret = FIELD_GET(SCALER_DISPDITHER_DSP5_MUX_MASK, reg);
+			if (ret == 3)
+				return -EPIPE;
+
+			return ret;
 
-	case 5:
-		reg = HVS_READ(SCALER_DISPDITHER);
-		ret = FIELD_GET(SCALER_DISPDITHER_DSP5_MUX_MASK, reg);
-		if (ret == 3)
+		default:
 			return -EPIPE;
+		}
 
-		return ret;
+	case VC4_GEN_6_C:
+	case VC4_GEN_6_D:
+		switch (output) {
+		case 0:
+			return 0;
+
+		case 2:
+			return 2;
+
+		case 1:
+		case 3:
+		case 4:
+			return 1;
+
+		default:
+			return -EPIPE;
+		}
 
 	default:
 		return -EPIPE;
@@ -363,6 +949,8 @@ static int vc4_hvs_init_channel(struct vc4_hvs *hvs, struct drm_crtc *crtc,
 	u32 dispctrl;
 	int idx;
 
+	WARN_ON_ONCE(vc4->gen > VC4_GEN_5);
+
 	if (!drm_dev_enter(drm, &idx))
 		return -ENODEV;
 
@@ -413,25 +1001,64 @@ static int vc4_hvs_init_channel(struct vc4_hvs *hvs, struct drm_crtc *crtc,
 	return 0;
 }
 
-void vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan)
+static int vc6_hvs_init_channel(struct vc4_hvs *hvs, struct drm_crtc *crtc,
+				struct drm_display_mode *mode, bool oneshot)
 {
-	struct drm_device *drm = &hvs->vc4->base;
+	struct vc4_dev *vc4 = hvs->vc4;
+	struct drm_device *drm = &vc4->base;
+	struct vc4_crtc_state *vc4_crtc_state = to_vc4_crtc_state(crtc->state);
+	unsigned int chan = vc4_crtc_state->assigned_channel;
+	bool interlace = mode->flags & DRM_MODE_FLAG_INTERLACE;
+	u32 disp_ctrl1;
 	int idx;
 
+	WARN_ON_ONCE(vc4->gen < VC4_GEN_6_C);
+
 	if (!drm_dev_enter(drm, &idx))
-		return;
+		return -ENODEV;
 
-	if (!(HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_ENABLE))
-		goto out;
+	HVS_WRITE(SCALER6_DISPX_CTRL0(chan), SCALER6_DISPX_CTRL0_RESET);
 
-	HVS_WRITE(SCALER_DISPCTRLX(chan), SCALER_DISPCTRLX_RESET);
-	HVS_WRITE(SCALER_DISPCTRLX(chan), 0);
+	disp_ctrl1 = HVS_READ(SCALER6_DISPX_CTRL1(chan));
+	disp_ctrl1 &= ~SCALER6_DISPX_CTRL1_INTLACE;
+	HVS_WRITE(SCALER6_DISPX_CTRL1(chan),
+		  disp_ctrl1 | (interlace ? SCALER6_DISPX_CTRL1_INTLACE : 0));
 
-	/* Once we leave, the scaler should be disabled and its fifo empty. */
-	WARN_ON_ONCE(HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_RESET);
+	HVS_WRITE(SCALER6_DISPX_CTRL0(chan),
+		  SCALER6_DISPX_CTRL0_ENB |
+		  VC4_SET_FIELD(mode->hdisplay - 1,
+				SCALER6_DISPX_CTRL0_FWIDTH) |
+		  (oneshot ? SCALER6_DISPX_CTRL0_ONESHOT : 0) |
+		  VC4_SET_FIELD(mode->vdisplay - 1,
+				SCALER6_DISPX_CTRL0_LINES));
 
-	WARN_ON_ONCE(VC4_GET_FIELD(HVS_READ(SCALER_DISPSTATX(chan)),
-				   SCALER_DISPSTATX_MODE) !=
+	drm_dev_exit(idx);
+
+	return 0;
+}
+
+static void __vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan)
+{
+	struct vc4_dev *vc4 = hvs->vc4;
+	struct drm_device *drm = &vc4->base;
+	int idx;
+
+	WARN_ON_ONCE(vc4->gen > VC4_GEN_5);
+
+	if (!drm_dev_enter(drm, &idx))
+		return;
+
+	if (!(HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_ENABLE))
+		goto out;
+
+	HVS_WRITE(SCALER_DISPCTRLX(chan), SCALER_DISPCTRLX_RESET);
+	HVS_WRITE(SCALER_DISPCTRLX(chan), 0);
+
+	/* Once we leave, the scaler should be disabled and its fifo empty. */
+	WARN_ON_ONCE(HVS_READ(SCALER_DISPCTRLX(chan)) & SCALER_DISPCTRLX_RESET);
+
+	WARN_ON_ONCE(VC4_GET_FIELD(HVS_READ(SCALER_DISPSTATX(chan)),
+				   SCALER_DISPSTATX_MODE) !=
 		     SCALER_DISPSTATX_MODE_DISABLED);
 
 	WARN_ON_ONCE((HVS_READ(SCALER_DISPSTATX(chan)) &
@@ -442,17 +1069,54 @@ void vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan)
 	drm_dev_exit(idx);
 }
 
+static void __vc6_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan)
+{
+	struct vc4_dev *vc4 = hvs->vc4;
+	struct drm_device *drm = &vc4->base;
+	int idx;
+
+	WARN_ON_ONCE(vc4->gen < VC4_GEN_6_C);
+
+	if (!drm_dev_enter(drm, &idx))
+		return;
+
+	if (!(HVS_READ(SCALER6_DISPX_CTRL0(chan)) & SCALER6_DISPX_CTRL0_ENB))
+		goto out;
+
+	HVS_WRITE(SCALER6_DISPX_CTRL0(chan),
+		  HVS_READ(SCALER6_DISPX_CTRL0(chan)) | SCALER6_DISPX_CTRL0_RESET);
+
+	HVS_WRITE(SCALER6_DISPX_CTRL0(chan),
+		  HVS_READ(SCALER6_DISPX_CTRL0(chan)) & ~SCALER6_DISPX_CTRL0_ENB);
+
+	WARN_ON_ONCE(VC4_GET_FIELD(HVS_READ(SCALER6_DISPX_STATUS(chan)),
+				   SCALER6_DISPX_STATUS_MODE) !=
+		     SCALER6_DISPX_STATUS_MODE_DISABLED);
+
+out:
+	drm_dev_exit(idx);
+}
+
+void vc4_hvs_stop_channel(struct vc4_hvs *hvs, unsigned int chan)
+{
+	struct vc4_dev *vc4 = hvs->vc4;
+
+	if (vc4->gen >= VC4_GEN_6_C)
+		__vc6_hvs_stop_channel(hvs, chan);
+	else
+		__vc4_hvs_stop_channel(hvs, chan);
+}
+
 int vc4_hvs_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state)
 {
 	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
 	struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc_state);
+	struct vc4_hvs_dlist_allocation *alloc;
 	struct drm_device *dev = crtc->dev;
 	struct vc4_dev *vc4 = to_vc4_dev(dev);
 	struct drm_plane *plane;
-	unsigned long flags;
 	const struct drm_plane_state *plane_state;
 	u32 dlist_count = 0;
-	int ret;
 
 	/* The pixelvalve can only feed one encoder (and encoders are
 	 * 1:1 with connectors.)
@@ -460,17 +1124,26 @@ int vc4_hvs_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state)
 	if (hweight32(crtc_state->connector_mask) > 1)
 		return -EINVAL;
 
-	drm_atomic_crtc_state_for_each_plane_state(plane, plane_state, crtc_state)
-		dlist_count += vc4_plane_dlist_size(plane_state);
+	drm_atomic_crtc_state_for_each_plane_state(plane, plane_state, crtc_state) {
+		u32 plane_dlist_count = vc4_plane_dlist_size(plane_state);
+
+		drm_dbg_driver(dev, "[CRTC:%d:%s] Found [PLANE:%d:%s] with DLIST size: %u\n",
+			       crtc->base.id, crtc->name,
+			       plane->base.id, plane->name,
+			       plane_dlist_count);
+
+		dlist_count += plane_dlist_count;
+	}
 
 	dlist_count++; /* Account for SCALER_CTL0_END. */
 
-	spin_lock_irqsave(&vc4->hvs->mm_lock, flags);
-	ret = drm_mm_insert_node(&vc4->hvs->dlist_mm, &vc4_state->mm,
-				 dlist_count);
-	spin_unlock_irqrestore(&vc4->hvs->mm_lock, flags);
-	if (ret)
-		return ret;
+	drm_dbg_driver(dev, "[CRTC:%d:%s] Allocating DLIST block with size: %u\n",
+		       crtc->base.id, crtc->name, dlist_count);
+	alloc = vc4_hvs_alloc_dlist_entry(vc4->hvs, vc4_state->assigned_channel, dlist_count);
+	if (IS_ERR(alloc))
+		return PTR_ERR(alloc);
+
+	vc4_state->mm = alloc;
 
 	return 0;
 }
@@ -486,8 +1159,17 @@ static void vc4_hvs_install_dlist(struct drm_crtc *crtc)
 	if (!drm_dev_enter(dev, &idx))
 		return;
 
-	HVS_WRITE(SCALER_DISPLISTX(vc4_state->assigned_channel),
-		  vc4_state->mm.start);
+	WARN_ON(!vc4_state->mm);
+
+	vc4_state->mm->dlist_programmed = true;
+
+	if (vc4->gen >= VC4_GEN_6_C)
+		HVS_WRITE(SCALER6_DISPX_LPTRS(vc4_state->assigned_channel),
+			  VC4_SET_FIELD(vc4_state->mm->mm_node.start,
+					SCALER6_DISPX_LPTRS_HEADE));
+	else
+		HVS_WRITE(SCALER_DISPLISTX(vc4_state->assigned_channel),
+			  vc4_state->mm->mm_node.start);
 
 	drm_dev_exit(idx);
 }
@@ -514,8 +1196,10 @@ static void vc4_hvs_update_dlist(struct drm_crtc *crtc)
 		spin_unlock_irqrestore(&dev->event_lock, flags);
 	}
 
+	WARN_ON(!vc4_state->mm);
+
 	spin_lock_irqsave(&vc4_crtc->irq_lock, flags);
-	vc4_crtc->current_dlist = vc4_state->mm.start;
+	vc4_crtc->current_dlist = vc4_state->mm->mm_node.start;
 	spin_unlock_irqrestore(&vc4_crtc->irq_lock, flags);
 }
 
@@ -542,7 +1226,11 @@ void vc4_hvs_atomic_enable(struct drm_crtc *crtc,
 
 	vc4_hvs_install_dlist(crtc);
 	vc4_hvs_update_dlist(crtc);
-	vc4_hvs_init_channel(vc4->hvs, crtc, mode, oneshot);
+
+	if (vc4->gen >= VC4_GEN_6_C)
+		vc6_hvs_init_channel(vc4->hvs, crtc, mode, oneshot);
+	else
+		vc4_hvs_init_channel(vc4->hvs, crtc, mode, oneshot);
 }
 
 void vc4_hvs_atomic_disable(struct drm_crtc *crtc,
@@ -571,13 +1259,14 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc,
 	struct drm_plane *plane;
 	struct vc4_plane_state *vc4_plane_state;
 	bool debug_dump_regs = false;
-	bool enable_bg_fill = false;
-	u32 __iomem *dlist_start = vc4->hvs->dlist + vc4_state->mm.start;
-	u32 __iomem *dlist_next = dlist_start;
+	bool enable_bg_fill = true;
+	u32 __iomem *dlist_start, *dlist_next;
 	unsigned int zpos = 0;
 	bool found = false;
 	int idx;
 
+	WARN_ON_ONCE(vc4->gen > VC4_GEN_6_D);
+
 	if (!drm_dev_enter(dev, &idx)) {
 		vc4_crtc_send_vblank(crtc);
 		return;
@@ -591,6 +1280,9 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc,
 		vc4_hvs_dump_state(hvs);
 	}
 
+	dlist_start = vc4->hvs->dlist + vc4_state->mm->mm_node.start;
+	dlist_next = dlist_start;
+
 	/* Copy all the active planes' dlist contents to the hardware dlist. */
 	do {
 		found = false;
@@ -624,15 +1316,22 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc,
 	writel(SCALER_CTL0_END, dlist_next);
 	dlist_next++;
 
-	WARN_ON_ONCE(dlist_next - dlist_start != vc4_state->mm.size);
+	WARN_ON(!vc4_state->mm);
+	WARN_ON_ONCE(dlist_next - dlist_start != vc4_state->mm->mm_node.size);
 
-	if (enable_bg_fill)
+	if (vc4->gen >= VC4_GEN_6_C) {
 		/* This sets a black background color fill, as is the case
 		 * with other DRM drivers.
 		 */
+		hvs->bg_fill[channel] = enable_bg_fill;
+	} else {
+		/* we can actually run with a lower core clock when background
+		 * fill is enabled on VC4_GEN_5 so leave it enabled always.
+		 */
 		HVS_WRITE(SCALER_DISPBKGNDX(channel),
 			  HVS_READ(SCALER_DISPBKGNDX(channel)) |
 			  SCALER_DISPBKGND_FILL);
+	}
 
 	/* Only update DISPLIST if the CRTC was already running and is not
 	 * being disabled.
@@ -649,6 +1348,8 @@ void vc4_hvs_atomic_flush(struct drm_crtc *crtc,
 	if (crtc->state->color_mgmt_changed) {
 		u32 dispbkgndx = HVS_READ(SCALER_DISPBKGNDX(channel));
 
+		WARN_ON_ONCE(vc4->gen > VC4_GEN_5);
+
 		if (crtc->state->gamma_lut) {
 			vc4_hvs_update_gamma_lut(hvs, vc4_crtc);
 			dispbkgndx |= SCALER_DISPBKGND_GAMMA;
@@ -678,6 +1379,8 @@ void vc4_hvs_mask_underrun(struct vc4_hvs *hvs, int channel)
 	u32 dispctrl;
 	int idx;
 
+	WARN_ON(vc4->gen > VC4_GEN_5);
+
 	if (!drm_dev_enter(drm, &idx))
 		return;
 
@@ -698,6 +1401,8 @@ void vc4_hvs_unmask_underrun(struct vc4_hvs *hvs, int channel)
 	u32 dispctrl;
 	int idx;
 
+	WARN_ON(vc4->gen > VC4_GEN_5);
+
 	if (!drm_dev_enter(drm, &idx))
 		return;
 
@@ -732,6 +1437,8 @@ static irqreturn_t vc4_hvs_irq_handler(int irq, void *data)
 	u32 status;
 	u32 dspeislur;
 
+	WARN_ON(vc4->gen > VC4_GEN_5);
+
 	/*
 	 * NOTE: We don't need to protect the register access using
 	 * drm_dev_enter() there because the interrupt handler lifetime
@@ -759,6 +1466,11 @@ static irqreturn_t vc4_hvs_irq_handler(int irq, void *data)
 
 			irqret = IRQ_HANDLED;
 		}
+
+		if (status & SCALER_DISPSTAT_EOF(channel)) {
+			vc4_hvs_schedule_dlist_sweep(hvs, channel);
+			irqret = IRQ_HANDLED;
+		}
 	}
 
 	/* Clear every per-channel interrupt flag. */
@@ -769,6 +1481,36 @@ static irqreturn_t vc4_hvs_irq_handler(int irq, void *data)
 	return irqret;
 }
 
+static irqreturn_t vc6_hvs_eof_irq_handler(int irq, void *data)
+{
+	struct drm_device *dev = data;
+	struct vc4_dev *vc4 = to_vc4_dev(dev);
+	struct vc4_hvs *hvs = vc4->hvs;
+	unsigned int i;
+
+	for (i = 0; i < HVS_NUM_CHANNELS; i++) {
+		if (!hvs->eof_irq[i].enabled)
+			continue;
+
+		if (hvs->eof_irq[i].desc != irq)
+			continue;
+
+		if (hvs->bg_fill[i])
+			HVS_WRITE(SCALER6_DISPX_CTRL1(i),
+				  HVS_READ(SCALER6_DISPX_CTRL1(i)) |
+				  SCALER6_DISPX_CTRL1_BGENB);
+		else
+			HVS_WRITE(SCALER6_DISPX_CTRL1(i),
+				  HVS_READ(SCALER6_DISPX_CTRL1(i)) &
+				  ~SCALER6_DISPX_CTRL1_BGENB);
+
+		vc4_hvs_schedule_dlist_sweep(hvs, i);
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
 int vc4_hvs_debugfs_init(struct drm_minor *minor)
 {
 	struct drm_device *drm = minor->dev;
@@ -783,129 +1525,160 @@ int vc4_hvs_debugfs_init(struct drm_minor *minor)
 				    minor->debugfs_root,
 				    &vc4->load_tracker_enabled);
 
-	drm_debugfs_add_file(drm, "hvs_dlists", vc4_hvs_debugfs_dlist, NULL);
+	if (vc4->gen >= VC4_GEN_6_C) {
+		drm_debugfs_add_file(drm, "hvs_dlists", vc6_hvs_debugfs_dlist, NULL);
+		drm_debugfs_add_file(drm, "hvs_upm", vc6_hvs_debugfs_upm_allocs, NULL);
+	} else {
+		drm_debugfs_add_file(drm, "hvs_dlists", vc4_hvs_debugfs_dlist, NULL);
+	}
+
+	drm_debugfs_add_file(drm, "hvs_lbm", vc4_hvs_debugfs_lbm_allocs, NULL);
 
 	drm_debugfs_add_file(drm, "hvs_underrun", vc4_hvs_debugfs_underrun, NULL);
 
+	drm_debugfs_add_file(drm, "hvs_dlist_allocs", vc4_hvs_debugfs_dlist_allocs, NULL);
+
 	vc4_debugfs_add_regset32(drm, "hvs_regs", &hvs->regset);
 
 	return 0;
 }
 
-struct vc4_hvs *__vc4_hvs_alloc(struct vc4_dev *vc4, struct platform_device *pdev)
+struct vc4_hvs *__vc4_hvs_alloc(struct vc4_dev *vc4,
+				void __iomem *regs,
+				struct platform_device *pdev)
 {
 	struct drm_device *drm = &vc4->base;
 	struct vc4_hvs *hvs;
+	unsigned int dlist_start;
+	size_t dlist_size;
+	size_t lbm_size;
+	unsigned int i;
 
 	hvs = drmm_kzalloc(drm, sizeof(*hvs), GFP_KERNEL);
 	if (!hvs)
 		return ERR_PTR(-ENOMEM);
 
 	hvs->vc4 = vc4;
+	hvs->regs = regs;
 	hvs->pdev = pdev;
 
 	spin_lock_init(&hvs->mm_lock);
 
+	switch (vc4->gen) {
+	case VC4_GEN_4:
+	case VC4_GEN_5:
+		/* Set up the HVS display list memory manager. We never
+		 * overwrite the setup from the bootloader (just 128b
+		 * out of our 16K), since we don't want to scramble the
+		 * screen when transitioning from the firmware's boot
+		 * setup to runtime.
+		 */
+		dlist_start = HVS_BOOTLOADER_DLIST_END;
+		dlist_size = (SCALER_DLIST_SIZE >> 2) - HVS_BOOTLOADER_DLIST_END;
+		break;
+
+	case VC4_GEN_6_C:
+	case VC4_GEN_6_D:
+		dlist_start = HVS_BOOTLOADER_DLIST_END;
+
+		/*
+		 * If we are running a test, it means that we can't
+		 * access a register. Use a plausible size then.
+		 */
+		if (!kunit_get_current_test())
+			dlist_size = HVS_READ(SCALER6_CXM_SIZE);
+		else
+			dlist_size = 4096;
+
+		for (i = 0; i < VC4_NUM_UPM_HANDLES; i++) {
+			refcount_set(&hvs->upm_refcounts[i].refcount, 0);
+			hvs->upm_refcounts[i].hvs = hvs;
+		}
+
+		break;
+
+	default:
+		drm_err(drm, "Unknown VC4 generation: %d", vc4->gen);
+		return ERR_PTR(-ENODEV);
+	}
+
+	drm_mm_init(&hvs->dlist_mm, dlist_start, dlist_size);
+
+	hvs->dlist_mem_size = dlist_size;
+
+	INIT_LIST_HEAD(&hvs->stale_dlist_entries);
+	INIT_WORK(&hvs->free_dlist_work, vc4_hvs_dlist_free_work);
+
 	/* Set up the HVS display list memory manager.  We never
 	 * overwrite the setup from the bootloader (just 128b out of
 	 * our 16K), since we don't want to scramble the screen when
 	 * transitioning from the firmware's boot setup to runtime.
 	 */
-	hvs->dlist_mem_size = (SCALER_DLIST_SIZE >> 2) - HVS_BOOTLOADER_DLIST_END;
 	drm_mm_init(&hvs->dlist_mm,
 		    HVS_BOOTLOADER_DLIST_END,
-		    hvs->dlist_mem_size);
+		    (SCALER_DLIST_SIZE >> 2) - HVS_BOOTLOADER_DLIST_END);
 
 	/* Set up the HVS LBM memory manager.  We could have some more
 	 * complicated data structure that allowed reuse of LBM areas
 	 * between planes when they don't overlap on the screen, but
 	 * for now we just allocate globally.
 	 */
-	if (vc4->gen == VC4_GEN_4)
-		/* 48k words of 2x12-bit pixels */
-		drm_mm_init(&hvs->lbm_mm, 0, 48 * 1024);
-	else
-		/* 60k words of 4x12-bit pixels */
-		drm_mm_init(&hvs->lbm_mm, 0, 60 * 1024);
-
-	vc4->hvs = hvs;
-
-	return hvs;
-}
-
-static int vc4_hvs_bind(struct device *dev, struct device *master, void *data)
-{
-	struct platform_device *pdev = to_platform_device(dev);
-	struct drm_device *drm = dev_get_drvdata(master);
-	struct vc4_dev *vc4 = to_vc4_dev(drm);
-	struct vc4_hvs *hvs = NULL;
-	int ret;
-	u32 dispctrl;
-	u32 reg, top;
-
-	hvs = __vc4_hvs_alloc(vc4, NULL);
-	if (IS_ERR(hvs))
-		return PTR_ERR(hvs);
 
-	hvs->regs = vc4_ioremap_regs(pdev, 0);
-	if (IS_ERR(hvs->regs))
-		return PTR_ERR(hvs->regs);
+	switch (vc4->gen) {
+	case VC4_GEN_4:
+		/* 48k words of 2x12-bit pixels */
+		lbm_size = 48 * SZ_1K;
+		break;
 
-	hvs->regset.base = hvs->regs;
-	hvs->regset.regs = hvs_regs;
-	hvs->regset.nregs = ARRAY_SIZE(hvs_regs);
+	case VC4_GEN_5:
+		/* 60k words of 4x12-bit pixels */
+		lbm_size = 60 * SZ_1K;
+		break;
 
-	if (vc4->gen == VC4_GEN_5) {
-		struct rpi_firmware *firmware;
-		struct device_node *node;
-		unsigned int max_rate;
+	case VC4_GEN_6_C:
+	case VC4_GEN_6_D:
+		/*
+		 * If we are running a test, it means that we can't
+		 * access a register. Use a plausible size then.
+		 */
+		lbm_size = 1024;
+		break;
 
-		node = rpi_firmware_find_node();
-		if (!node)
-			return -EINVAL;
+	default:
+		drm_err(drm, "Unknown VC4 generation: %d", vc4->gen);
+		return ERR_PTR(-ENODEV);
+	}
 
-		firmware = rpi_firmware_get(node);
-		of_node_put(node);
-		if (!firmware)
-			return -EPROBE_DEFER;
+	drm_mm_init(&hvs->lbm_mm, 0, lbm_size);
+	ida_init(&hvs->lbm_handles);
 
-		hvs->core_clk = devm_clk_get(&pdev->dev, NULL);
-		if (IS_ERR(hvs->core_clk)) {
-			dev_err(&pdev->dev, "Couldn't get core clock\n");
-			return PTR_ERR(hvs->core_clk);
-		}
+	if (vc4->gen >= VC4_GEN_6_C) {
+		ida_init(&hvs->upm_handles);
 
-		max_rate = rpi_firmware_clk_get_max_rate(firmware,
-							 RPI_FIRMWARE_CORE_CLK_ID);
-		rpi_firmware_put(firmware);
-		if (max_rate >= 550000000)
-			hvs->vc5_hdmi_enable_hdmi_20 = true;
+		/*
+		 * NOTE: On BCM2712, the size can also be read through
+		 * the SCALER_UBM_SIZE register. We would need to do a
+		 * register access though, which we can't do with kunit
+		 * that also uses this function to create its mock
+		 * device.
+		 */
+		drm_mm_init(&hvs->upm_mm, 0, 1024 * HVS_UBM_WORD_SIZE);
+	}
 
-		if (max_rate >= 600000000)
-			hvs->vc5_hdmi_enable_4096by2160 = true;
 
-		hvs->max_core_rate = max_rate;
+	vc4->hvs = hvs;
 
-		ret = clk_prepare_enable(hvs->core_clk);
-		if (ret) {
-			dev_err(&pdev->dev, "Couldn't enable the core clock\n");
-			return ret;
-		}
-	}
+	return hvs;
+}
 
-	if (vc4->gen == VC4_GEN_4)
-		hvs->dlist = hvs->regs + SCALER_DLIST_START;
-	else
-		hvs->dlist = hvs->regs + SCALER5_DLIST_START;
+static int vc4_hvs_hw_init(struct vc4_hvs *hvs)
+{
+	struct vc4_dev *vc4 = hvs->vc4;
+	u32 dispctrl, reg;
 
-	/* Upload filter kernels.  We only have the one for now, so we
-	 * keep it around for the lifetime of the driver.
-	 */
-	ret = vc4_hvs_upload_linear_kernel(hvs,
-					   &hvs->mitchell_netravali_filter,
-					   mitchell_netravali_1_3_1_3_kernel);
-	if (ret)
-		return ret;
+	dispctrl = HVS_READ(SCALER_DISPCTRL);
+	dispctrl |= SCALER_DISPCTRL_ENABLE;
+	HVS_WRITE(SCALER_DISPCTRL, dispctrl);
 
 	reg = HVS_READ(SCALER_DISPECTRL);
 	reg &= ~SCALER_DISPECTRL_DSP2_MUX_MASK;
@@ -928,8 +1701,6 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data)
 		  reg | VC4_SET_FIELD(3, SCALER_DISPDITHER_DSP5_MUX));
 
 	dispctrl = HVS_READ(SCALER_DISPCTRL);
-
-	dispctrl |= SCALER_DISPCTRL_ENABLE;
 	dispctrl |= SCALER_DISPCTRL_DISPEIRQ(0) |
 		    SCALER_DISPCTRL_DISPEIRQ(1) |
 		    SCALER_DISPCTRL_DISPEIRQ(2);
@@ -987,9 +1758,223 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data)
 
 	HVS_WRITE(SCALER_DISPCTRL, dispctrl);
 
-	/* Recompute Composite Output Buffer (COB) allocations for the displays
+	return 0;
+}
+
+#define CFC1_N_NL_CSC_CTRL(x)		(0xa000 + ((x) * 0x3000))
+#define CFC1_N_MA_CSC_COEFF_C00(x)	(0xa008 + ((x) * 0x3000))
+#define CFC1_N_MA_CSC_COEFF_C01(x)	(0xa00c + ((x) * 0x3000))
+#define CFC1_N_MA_CSC_COEFF_C02(x)	(0xa010 + ((x) * 0x3000))
+#define CFC1_N_MA_CSC_COEFF_C03(x)	(0xa014 + ((x) * 0x3000))
+#define CFC1_N_MA_CSC_COEFF_C04(x)	(0xa018 + ((x) * 0x3000))
+#define CFC1_N_MA_CSC_COEFF_C10(x)	(0xa01c + ((x) * 0x3000))
+#define CFC1_N_MA_CSC_COEFF_C11(x)	(0xa020 + ((x) * 0x3000))
+#define CFC1_N_MA_CSC_COEFF_C12(x)	(0xa024 + ((x) * 0x3000))
+#define CFC1_N_MA_CSC_COEFF_C13(x)	(0xa028 + ((x) * 0x3000))
+#define CFC1_N_MA_CSC_COEFF_C14(x)	(0xa02c + ((x) * 0x3000))
+#define CFC1_N_MA_CSC_COEFF_C20(x)	(0xa030 + ((x) * 0x3000))
+#define CFC1_N_MA_CSC_COEFF_C21(x)	(0xa034 + ((x) * 0x3000))
+#define CFC1_N_MA_CSC_COEFF_C22(x)	(0xa038 + ((x) * 0x3000))
+#define CFC1_N_MA_CSC_COEFF_C23(x)	(0xa03c + ((x) * 0x3000))
+#define CFC1_N_MA_CSC_COEFF_C24(x)	(0xa040 + ((x) * 0x3000))
+
+#define SCALER_PI_CMP_CSC_RED0(x)		(0x200 + ((x) * 0x40))
+#define SCALER_PI_CMP_CSC_RED1(x)		(0x204 + ((x) * 0x40))
+#define SCALER_PI_CMP_CSC_RED_CLAMP(x)		(0x208 + ((x) * 0x40))
+#define SCALER_PI_CMP_CSC_CFG(x)		(0x20c + ((x) * 0x40))
+#define SCALER_PI_CMP_CSC_GREEN0(x)		(0x210 + ((x) * 0x40))
+#define SCALER_PI_CMP_CSC_GREEN1(x)		(0x214 + ((x) * 0x40))
+#define SCALER_PI_CMP_CSC_GREEN_CLAMP(x)	(0x218 + ((x) * 0x40))
+#define SCALER_PI_CMP_CSC_BLUE0(x)		(0x220 + ((x) * 0x40))
+#define SCALER_PI_CMP_CSC_BLUE1(x)		(0x224 + ((x) * 0x40))
+#define SCALER_PI_CMP_CSC_BLUE_CLAMP(x)		(0x228 + ((x) * 0x40))
+
+/* 4 S2.22 multiplication factors, and 1 S9.15 addititive element for each of 3
+ * output components
+ */
+struct vc6_csc_coeff_entry {
+	u32 csc[3][5];
+};
+
+static const struct vc6_csc_coeff_entry csc_coeffs[2][3] = {
+	[DRM_COLOR_YCBCR_LIMITED_RANGE] = {
+		[DRM_COLOR_YCBCR_BT601] = {
+			.csc = {
+				{ 0x004A8542, 0x0, 0x0066254A, 0x0, 0xFF908A0D },
+				{ 0x004A8542, 0xFFE6ED5D, 0xFFCBF856, 0x0, 0x0043C9A3 },
+				{ 0x004A8542, 0x00811A54, 0x0, 0x0, 0xFF759502 }
+			}
+		},
+		[DRM_COLOR_YCBCR_BT709] = {
+			.csc = {
+				{ 0x004A8542, 0x0, 0x0072BC44, 0x0, 0xFF83F312 },
+				{ 0x004A8542, 0xFFF25A22, 0xFFDDE4D0, 0x0, 0x00267064 },
+				{ 0x004A8542, 0x00873197, 0x0, 0x0, 0xFF6F7DC0 }
+			}
+		},
+		[DRM_COLOR_YCBCR_BT2020] = {
+			.csc = {
+				{ 0x004A8542, 0x0, 0x006B4A17, 0x0, 0xFF8B653F },
+				{ 0x004A8542, 0xFFF402D9, 0xFFDDE4D0, 0x0, 0x0024C7AE },
+				{ 0x004A8542, 0x008912CC, 0x0, 0x0, 0xFF6D9C8B }
+			}
+		}
+	},
+	[DRM_COLOR_YCBCR_FULL_RANGE] = {
+		[DRM_COLOR_YCBCR_BT601] = {
+			.csc = {
+				{ 0x00400000, 0x0, 0x0059BA5E, 0x0, 0xFFA645A1 },
+				{ 0x00400000, 0xFFE9F9AC, 0xFFD24B97, 0x0, 0x0043BABB },
+				{ 0x00400000, 0x00716872, 0x0, 0x0, 0xFF8E978D }
+			}
+		},
+		[DRM_COLOR_YCBCR_BT709] = {
+			.csc = {
+				{ 0x00400000, 0x0, 0x0064C985, 0x0, 0xFF9B367A },
+				{ 0x00400000, 0xFFF402E1, 0xFFE20A40, 0x0, 0x0029F2DE },
+				{ 0x00400000, 0x0076C226, 0x0, 0x0, 0xFF893DD9 }
+			}
+		},
+		[DRM_COLOR_YCBCR_BT2020] = {
+			.csc = {
+				{ 0x00400000, 0x0, 0x005E3F14, 0x0, 0xFFA1C0EB },
+				{ 0x00400000, 0xFFF577F6, 0xFFDB580F, 0x0, 0x002F2FFA },
+				{ 0x00400000, 0x007868DB, 0x0, 0x0, 0xFF879724 }
+			}
+		}
+	}
+};
+
+/*
+ * GEN_6D has a 3x3 matrix of signed 3p12 fixed point values, signed 13bit
+ * offset, and high and low clamp values. The offset can be applied before or
+ * after the multiply - for use here for YUV to RGB conversion it is easier to
+ * put it before.
+ * Clamp values are always set to max 0xfff and min 0, and the conversion always
+ * has the offset before the multiply.
+ */
+struct vc6_d0_csc_coeff_entry {
+	u32 csc[3][2];
+};
+
+static const struct vc6_d0_csc_coeff_entry vc6_d0_csc_coeffs[2][3] = {
+	[DRM_COLOR_YCBCR_LIMITED_RANGE] = {
+		[DRM_COLOR_YCBCR_BT601] = {
+			.csc = {
+				{ 0x1f002543, 0x00003313 },
+				{ 0x18002543, 0xf377e5fc },
+				{ 0x18002543, 0x408d0000 }
+			}
+		},
+		[DRM_COLOR_YCBCR_BT709] = {
+			.csc = {
+				{ 0x1f002543, 0x0000395e },
+				{ 0x18002543, 0xf92deef2 },
+				{ 0x18002543, 0x43990000 }
+			}
+		},
+		[DRM_COLOR_YCBCR_BT2020] = {
+			.csc = {
+				{ 0x1f002543, 0x000035a0 },
+				{ 0x18002543, 0xfa00eb20 },
+				{ 0x18002543, 0x44800000 }
+			}
+		}
+	},
+	[DRM_COLOR_YCBCR_FULL_RANGE] = {
+		[DRM_COLOR_YCBCR_BT601] = {
+			.csc = {
+				{ 0x00002000, 0x00002cdd },
+				{ 0x18002000, 0xf4fde926 },
+				{ 0x18002000, 0x38b40000 }
+			}
+		},
+		[DRM_COLOR_YCBCR_BT709] = {
+			.csc = {
+				{ 0x00002000, 0x00003265 },
+				{ 0x18002000, 0xfa01f105 },
+				{ 0x18002000, 0x3b610000 }
+			}
+		},
+		[DRM_COLOR_YCBCR_BT2020] = {
+			.csc = {
+				{ 0x00002000, 0x00002f30 },
+				{ 0x18002000, 0xfabcedb7 },
+				{ 0x18002000, 0x3c340000 }
+			}
+		}
+	}
+};
+
+static int vc6_hvs_hw_init(struct vc4_hvs *hvs)
+{
+	const struct vc6_csc_coeff_entry *coeffs;
+	const struct vc6_d0_csc_coeff_entry *d0_coeffs;
+	unsigned int i;
+
+	HVS_WRITE(SCALER6_CONTROL,
+		  SCALER6_CONTROL_HVS_EN |
+		  VC4_SET_FIELD(8, SCALER6_CONTROL_PF_LINES) |
+		  VC4_SET_FIELD(15, SCALER6_CONTROL_MAX_REQS));
+
+	/* Set HVS arbiter priority to max */
+	HVS_WRITE(SCALER6(PRI_MAP0), 0xffffffff);
+	HVS_WRITE(SCALER6(PRI_MAP1), 0xffffffff);
+
+	if (hvs->vc4->gen == VC4_GEN_6_C) {
+		for (i = 0; i < 6; i++) {
+			coeffs = &csc_coeffs[i / 3][i % 3];
+
+			HVS_WRITE(CFC1_N_MA_CSC_COEFF_C00(i), coeffs->csc[0][0]);
+			HVS_WRITE(CFC1_N_MA_CSC_COEFF_C01(i), coeffs->csc[0][1]);
+			HVS_WRITE(CFC1_N_MA_CSC_COEFF_C02(i), coeffs->csc[0][2]);
+			HVS_WRITE(CFC1_N_MA_CSC_COEFF_C03(i), coeffs->csc[0][3]);
+			HVS_WRITE(CFC1_N_MA_CSC_COEFF_C04(i), coeffs->csc[0][4]);
+
+			HVS_WRITE(CFC1_N_MA_CSC_COEFF_C10(i), coeffs->csc[1][0]);
+			HVS_WRITE(CFC1_N_MA_CSC_COEFF_C11(i), coeffs->csc[1][1]);
+			HVS_WRITE(CFC1_N_MA_CSC_COEFF_C12(i), coeffs->csc[1][2]);
+			HVS_WRITE(CFC1_N_MA_CSC_COEFF_C13(i), coeffs->csc[1][3]);
+			HVS_WRITE(CFC1_N_MA_CSC_COEFF_C14(i), coeffs->csc[1][4]);
+
+			HVS_WRITE(CFC1_N_MA_CSC_COEFF_C20(i), coeffs->csc[2][0]);
+			HVS_WRITE(CFC1_N_MA_CSC_COEFF_C21(i), coeffs->csc[2][1]);
+			HVS_WRITE(CFC1_N_MA_CSC_COEFF_C22(i), coeffs->csc[2][2]);
+			HVS_WRITE(CFC1_N_MA_CSC_COEFF_C23(i), coeffs->csc[2][3]);
+			HVS_WRITE(CFC1_N_MA_CSC_COEFF_C24(i), coeffs->csc[2][4]);
+
+			HVS_WRITE(CFC1_N_NL_CSC_CTRL(i), BIT(15));
+		}
+	} else {
+		for (i = 0; i < 6; i++) {
+			d0_coeffs = &vc6_d0_csc_coeffs[i / 3][i % 3];
+			HVS_WRITE(SCALER_PI_CMP_CSC_RED0(i), d0_coeffs->csc[0][0]);
+			HVS_WRITE(SCALER_PI_CMP_CSC_RED1(i), d0_coeffs->csc[0][1]);
+			HVS_WRITE(SCALER_PI_CMP_CSC_RED_CLAMP(i), 0xfff00000);
+			HVS_WRITE(SCALER_PI_CMP_CSC_CFG(i), 1);
+			HVS_WRITE(SCALER_PI_CMP_CSC_GREEN0(i), d0_coeffs->csc[1][0]);
+			HVS_WRITE(SCALER_PI_CMP_CSC_GREEN1(i), d0_coeffs->csc[1][1]);
+			HVS_WRITE(SCALER_PI_CMP_CSC_GREEN_CLAMP(i), 0xfff00000);
+			HVS_WRITE(SCALER_PI_CMP_CSC_BLUE0(i), d0_coeffs->csc[2][0]);
+			HVS_WRITE(SCALER_PI_CMP_CSC_BLUE1(i), d0_coeffs->csc[2][1]);
+			HVS_WRITE(SCALER_PI_CMP_CSC_BLUE_CLAMP(i), 0xfff00000);
+		}
+	}
+
+	return 0;
+}
+
+static int vc4_hvs_cob_init(struct vc4_hvs *hvs)
+{
+	struct vc4_dev *vc4 = hvs->vc4;
+	u32 reg, top, base;
+
+	/*
+	 * Recompute Composite Output Buffer (COB) allocations for the
+	 * displays
 	 */
-	if (vc4->gen == VC4_GEN_4) {
+	switch (vc4->gen) {
+	case VC4_GEN_4:
 		/* The COB is 20736 pixels, or just over 10 lines at 2048 wide.
 		 * The bottom 2048 pixels are full 32bpp RGBA (intended for the
 		 * TXP composing RGBA to memory), whilst the remainder are only
@@ -1013,7 +1998,9 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data)
 		top = VC4_COB_SIZE;
 		reg |= (top - 1) << 16;
 		HVS_WRITE(SCALER_DISPBASE0, reg);
-	} else {
+		break;
+
+	case VC4_GEN_5:
 		/* The COB is 44416 pixels, or 10.8 lines at 4096 wide.
 		 * The bottom 4096 pixels are full RGBA (intended for the TXP
 		 * composing RGBA to memory), whilst the remainder are only
@@ -1039,13 +2026,191 @@ static int vc4_hvs_bind(struct device *dev, struct device *master, void *data)
 		top = VC5_COB_SIZE;
 		reg |= top << 16;
 		HVS_WRITE(SCALER_DISPBASE0, reg);
+		break;
+
+	case VC4_GEN_6_C:
+	case VC4_GEN_6_D:
+		#define VC6_COB_LINE_WIDTH	3840
+		#define VC6_COB_NUM_LINES	4
+		base = 0;
+		top = 3840;
+
+		HVS_WRITE(SCALER6_DISPX_COB(2),
+			  VC4_SET_FIELD(top, SCALER6_DISPX_COB_TOP) |
+			  VC4_SET_FIELD(base, SCALER6_DISPX_COB_BASE));
+
+		base = top + 16;
+		top += VC6_COB_LINE_WIDTH * VC6_COB_NUM_LINES;
+
+		HVS_WRITE(SCALER6_DISPX_COB(1),
+			  VC4_SET_FIELD(top, SCALER6_DISPX_COB_TOP) |
+			  VC4_SET_FIELD(base, SCALER6_DISPX_COB_BASE));
+
+		base = top + 16;
+		top += VC6_COB_LINE_WIDTH * VC6_COB_NUM_LINES;
+
+		HVS_WRITE(SCALER6_DISPX_COB(0),
+			  VC4_SET_FIELD(top, SCALER6_DISPX_COB_TOP) |
+			  VC4_SET_FIELD(base, SCALER6_DISPX_COB_BASE));
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int vc4_hvs_bind(struct device *dev, struct device *master, void *data)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct drm_device *drm = dev_get_drvdata(master);
+	struct vc4_dev *vc4 = to_vc4_dev(drm);
+	struct vc4_hvs *hvs = NULL;
+	void __iomem *regs;
+	int ret;
+
+	regs = vc4_ioremap_regs(pdev, 0);
+	if (IS_ERR(regs))
+		return PTR_ERR(regs);
+
+	hvs = __vc4_hvs_alloc(vc4, regs, pdev);
+	if (IS_ERR(hvs))
+		return PTR_ERR(hvs);
+
+	hvs->regset.base = hvs->regs;
+
+	if (vc4->gen == VC4_GEN_6_C) {
+		hvs->regset.regs = vc6_hvs_regs;
+		hvs->regset.nregs = ARRAY_SIZE(vc6_hvs_regs);
+
+		if (VC4_GET_FIELD(HVS_READ(SCALER6_VERSION), SCALER6_VERSION) ==
+						SCALER6_VERSION_D0) {
+			vc4->gen = VC4_GEN_6_D;
+			hvs->regset.regs = vc6_d_hvs_regs;
+			hvs->regset.nregs = ARRAY_SIZE(vc6_d_hvs_regs);
+		}
+	} else {
+		hvs->regset.regs = vc4_hvs_regs;
+		hvs->regset.nregs = ARRAY_SIZE(vc4_hvs_regs);
+	}
+
+	if (vc4->gen >= VC4_GEN_5) {
+		struct rpi_firmware *firmware;
+		struct device_node *node;
+		unsigned int max_rate;
+
+		node = rpi_firmware_find_node();
+		if (!node)
+			return -EINVAL;
+
+		firmware = rpi_firmware_get(node);
+		of_node_put(node);
+		if (!firmware)
+			return -EPROBE_DEFER;
+
+		hvs->core_clk = devm_clk_get(&pdev->dev,
+					     (vc4->gen >= VC4_GEN_6_C) ? "core" : NULL);
+		if (IS_ERR(hvs->core_clk)) {
+			dev_err(&pdev->dev, "Couldn't get core clock\n");
+			return PTR_ERR(hvs->core_clk);
+		}
+
+		hvs->disp_clk = devm_clk_get(&pdev->dev,
+					     (vc4->gen >= VC4_GEN_6_C) ? "disp" : NULL);
+		if (IS_ERR(hvs->disp_clk)) {
+			dev_err(&pdev->dev, "Couldn't get disp clock\n");
+			return PTR_ERR(hvs->disp_clk);
+		}
+
+		max_rate = rpi_firmware_clk_get_max_rate(firmware,
+							 RPI_FIRMWARE_CORE_CLK_ID);
+		rpi_firmware_put(firmware);
+		if (max_rate >= 550000000)
+			hvs->vc5_hdmi_enable_hdmi_20 = true;
+
+		if (max_rate >= 600000000)
+			hvs->vc5_hdmi_enable_4096by2160 = true;
+
+		hvs->max_core_rate = max_rate;
+
+		ret = clk_prepare_enable(hvs->core_clk);
+		if (ret) {
+			dev_err(&pdev->dev, "Couldn't enable the core clock\n");
+			return ret;
+		}
+
+		ret = clk_prepare_enable(hvs->disp_clk);
+		if (ret) {
+			dev_err(&pdev->dev, "Couldn't enable the disp clock\n");
+			return ret;
+		}
 	}
 
-	ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
-			       vc4_hvs_irq_handler, 0, "vc4 hvs", drm);
+	if (vc4->gen >= VC4_GEN_5)
+		hvs->dlist = hvs->regs + SCALER5_DLIST_START;
+	else
+		hvs->dlist = hvs->regs + SCALER_DLIST_START;
+
+	if (vc4->gen >= VC4_GEN_6_C)
+		ret = vc6_hvs_hw_init(hvs);
+	else
+		ret = vc4_hvs_hw_init(hvs);
 	if (ret)
 		return ret;
 
+	/* Upload filter kernels.  We only have the two for now, so we
+	 * keep them around for the lifetime of the driver.
+	 */
+	ret = vc4_hvs_upload_linear_kernel(hvs,
+					   &hvs->mitchell_netravali_filter,
+					   mitchell_netravali_1_3_1_3_kernel);
+	if (ret)
+		return ret;
+	ret = vc4_hvs_upload_linear_kernel(hvs,
+					   &hvs->nearest_neighbour_filter,
+					   nearest_neighbour_kernel);
+	if (ret)
+		return ret;
+
+	ret = vc4_hvs_cob_init(hvs);
+	if (ret)
+		return ret;
+
+	if (vc4->gen < VC4_GEN_6_C) {
+		ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
+				       vc4_hvs_irq_handler, 0, "vc4 hvs", drm);
+		if (ret)
+			return ret;
+	} else {
+		unsigned int i;
+
+		for (i = 0; i < HVS_NUM_CHANNELS; i++) {
+			char irq_name[16];
+			int irq;
+
+			snprintf(irq_name, sizeof(irq_name), "ch%u-eof", i);
+
+			irq = platform_get_irq_byname(pdev, irq_name);
+			if (irq < 0) {
+				dev_err(&pdev->dev,
+					"Couldn't get %s interrupt: %d\n",
+					irq_name, irq);
+
+				return irq;
+			}
+
+			ret = devm_request_irq(&pdev->dev,
+					       irq,
+					       vc6_hvs_eof_irq_handler,
+					       IRQF_NO_AUTOEN,
+					       dev_name(&pdev->dev),
+					       drm);
+
+			hvs->eof_irq[i].desc = irq;
+		}
+	}
+
 	return 0;
 }
 
@@ -1059,6 +2224,8 @@ static void vc4_hvs_unbind(struct device *dev, struct device *master,
 
 	if (drm_mm_node_allocated(&vc4->hvs->mitchell_netravali_filter))
 		drm_mm_remove_node(&vc4->hvs->mitchell_netravali_filter);
+	if (drm_mm_node_allocated(&vc4->hvs->nearest_neighbour_filter))
+		drm_mm_remove_node(&vc4->hvs->nearest_neighbour_filter);
 
 	drm_mm_for_each_node_safe(node, next, &vc4->hvs->dlist_mm)
 		drm_mm_remove_node(node);
@@ -1069,6 +2236,10 @@ static void vc4_hvs_unbind(struct device *dev, struct device *master,
 		drm_mm_remove_node(node);
 	drm_mm_takedown(&vc4->hvs->lbm_mm);
 
+	/* we no longer require a minimum clock rate */
+	clk_set_min_rate(hvs->disp_clk, 0);
+	clk_disable_unprepare(hvs->disp_clk);
+	clk_set_min_rate(hvs->core_clk, 0);
 	clk_disable_unprepare(hvs->core_clk);
 
 	vc4->hvs = NULL;
@@ -1091,6 +2262,7 @@ static void vc4_hvs_dev_remove(struct platform_device *pdev)
 
 static const struct of_device_id vc4_hvs_dt_match[] = {
 	{ .compatible = "brcm,bcm2711-hvs" },
+	{ .compatible = "brcm,bcm2712-hvs" },
 	{ .compatible = "brcm,bcm2835-hvs" },
 	{}
 };
diff --git a/drivers/gpu/drm/vc4/vc4_irq.c b/drivers/gpu/drm/vc4/vc4_irq.c
index 968356d1b91d..69b399f3b802 100644
--- a/drivers/gpu/drm/vc4/vc4_irq.c
+++ b/drivers/gpu/drm/vc4/vc4_irq.c
@@ -263,7 +263,7 @@ vc4_irq_enable(struct drm_device *dev)
 {
 	struct vc4_dev *vc4 = to_vc4_dev(dev);
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return;
 
 	if (!vc4->v3d)
@@ -280,7 +280,7 @@ vc4_irq_disable(struct drm_device *dev)
 {
 	struct vc4_dev *vc4 = to_vc4_dev(dev);
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return;
 
 	if (!vc4->v3d)
@@ -303,7 +303,7 @@ int vc4_irq_install(struct drm_device *dev, int irq)
 	struct vc4_dev *vc4 = to_vc4_dev(dev);
 	int ret;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	if (irq == IRQ_NOTCONNECTED)
@@ -324,7 +324,7 @@ void vc4_irq_uninstall(struct drm_device *dev)
 {
 	struct vc4_dev *vc4 = to_vc4_dev(dev);
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return;
 
 	vc4_irq_disable(dev);
@@ -337,7 +337,7 @@ void vc4_irq_reset(struct drm_device *dev)
 	struct vc4_dev *vc4 = to_vc4_dev(dev);
 	unsigned long irqflags;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return;
 
 	/* Acknowledge any stale IRQs. */
diff --git a/drivers/gpu/drm/vc4/vc4_kms.c b/drivers/gpu/drm/vc4/vc4_kms.c
index bddfcad10950..2e6ae0d04518 100644
--- a/drivers/gpu/drm/vc4/vc4_kms.c
+++ b/drivers/gpu/drm/vc4/vc4_kms.c
@@ -138,6 +138,11 @@ vc4_ctm_commit(struct vc4_dev *vc4, struct drm_atomic_state *state)
 	struct vc4_ctm_state *ctm_state = to_vc4_ctm_state(vc4->ctm_manager.state);
 	struct drm_color_ctm *ctm = ctm_state->ctm;
 
+	if (vc4->firmware_kms)
+		return;
+
+	WARN_ON_ONCE(vc4->gen > VC4_GEN_5);
+
 	if (ctm_state->fifo) {
 		HVS_WRITE(SCALER_OLEDCOEF2,
 			  VC4_SET_FIELD(vc4_ctm_s31_32_to_s0_9(ctm->matrix[0]),
@@ -213,16 +218,17 @@ static void vc4_hvs_pv_muxing_commit(struct vc4_dev *vc4,
 	struct drm_crtc *crtc;
 	unsigned int i;
 
+	WARN_ON_ONCE(vc4->gen != VC4_GEN_4);
+
 	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
 		struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
 		struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc_state);
 		u32 dispctrl;
-		u32 dsp3_mux;
 
 		if (!crtc_state->active)
 			continue;
 
-		if (vc4_state->assigned_channel != 2)
+		if (vc4_crtc->data->hvs_output != 2)
 			continue;
 
 		/*
@@ -230,19 +236,28 @@ static void vc4_hvs_pv_muxing_commit(struct vc4_dev *vc4,
 		 * FIFO X'.
 		 * SCALER_DISPCTRL_DSP3 = 3 means 'disable DSP 3'.
 		 *
-		 * DSP3 is connected to FIFO2 unless the transposer is
-		 * enabled. In this case, FIFO 2 is directly accessed by the
-		 * TXP IP, and we need to disable the FIFO2 -> pixelvalve1
-		 * route.
+		 * It is more likely that we want the TXP than 3 displays, so
+		 * handle the mapping of DSP3 to any available FIFO.
+		 *
+		 * TXP can also run with a lower panic level than a live display,
+		 * as it doesn't have the same real-time constraint.
 		 */
-		if (vc4_crtc->feeds_txp)
-			dsp3_mux = VC4_SET_FIELD(3, SCALER_DISPCTRL_DSP3_MUX);
-		else
-			dsp3_mux = VC4_SET_FIELD(2, SCALER_DISPCTRL_DSP3_MUX);
-
 		dispctrl = HVS_READ(SCALER_DISPCTRL) &
-			   ~SCALER_DISPCTRL_DSP3_MUX_MASK;
-		HVS_WRITE(SCALER_DISPCTRL, dispctrl | dsp3_mux);
+			     ~SCALER_DISPCTRL_PANIC2_MASK;
+
+		if (vc4_crtc->feeds_txp) {
+			dispctrl |= VC4_SET_FIELD(0, SCALER_DISPCTRL_PANIC2);
+			drm_WARN_ON(&vc4->base,
+				    VC4_GET_FIELD(HVS_READ(SCALER_DISPCTRL),
+						  SCALER_DISPCTRL_DSP3_MUX) == 2);
+		} else {
+			dispctrl &= ~SCALER_DISPCTRL_DSP3_MUX_MASK;
+			dispctrl |= VC4_SET_FIELD(vc4_state->assigned_channel,
+						     SCALER_DISPCTRL_DSP3_MUX);
+			dispctrl |= VC4_SET_FIELD(2, SCALER_DISPCTRL_PANIC2);
+		}
+
+		HVS_WRITE(SCALER_DISPCTRL, dispctrl);
 	}
 }
 
@@ -256,6 +271,8 @@ static void vc5_hvs_pv_muxing_commit(struct vc4_dev *vc4,
 	unsigned int i;
 	u32 reg;
 
+	WARN_ON_ONCE(vc4->gen != VC4_GEN_5);
+
 	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
 		struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc_state);
 		struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
@@ -320,17 +337,62 @@ static void vc5_hvs_pv_muxing_commit(struct vc4_dev *vc4,
 	}
 }
 
+static void vc6_hvs_pv_muxing_commit(struct vc4_dev *vc4,
+				     struct drm_atomic_state *state)
+{
+	struct vc4_hvs *hvs = vc4->hvs;
+	struct drm_crtc_state *crtc_state;
+	struct drm_crtc *crtc;
+	unsigned int i;
+
+	WARN_ON_ONCE(vc4->gen != VC4_GEN_6_C && vc4->gen != VC4_GEN_6_D);
+
+	for_each_new_crtc_in_state(state, crtc, crtc_state, i) {
+		struct vc4_crtc_state *vc4_state = to_vc4_crtc_state(crtc_state);
+		struct vc4_encoder *vc4_encoder;
+		struct drm_encoder *encoder;
+		unsigned char mux;
+		u32 reg;
+
+		if (!vc4_state->update_muxing)
+			continue;
+
+		if (vc4_state->assigned_channel != 1)
+			continue;
+
+		encoder = vc4_get_crtc_encoder(crtc, crtc_state);
+		vc4_encoder = to_vc4_encoder(encoder);
+		switch (vc4_encoder->type) {
+		case VC4_ENCODER_TYPE_HDMI1:
+			mux = 0;
+			break;
+
+		case VC4_ENCODER_TYPE_TXP1:
+			mux = 2;
+			break;
+
+		default:
+			drm_err(&vc4->base, "Unhandled encoder type for PV muxing %d",
+				vc4_encoder->type);
+			mux = 0;
+			break;
+		}
+
+		reg = HVS_READ(SCALER6_CONTROL);
+		HVS_WRITE(SCALER6_CONTROL,
+			  (reg & ~SCALER6_CONTROL_DSP1_TARGET_MASK) |
+			  VC4_SET_FIELD(mux, SCALER6_CONTROL_DSP1_TARGET));
+	}
+}
+
 static void vc4_atomic_commit_tail(struct drm_atomic_state *state)
 {
 	struct drm_device *dev = state->dev;
 	struct vc4_dev *vc4 = to_vc4_dev(dev);
 	struct vc4_hvs *hvs = vc4->hvs;
-	struct drm_crtc_state *new_crtc_state;
 	struct vc4_hvs_state *new_hvs_state;
-	struct drm_crtc *crtc;
 	struct vc4_hvs_state *old_hvs_state;
 	unsigned int channel;
-	int i;
 
 	old_hvs_state = vc4_hvs_get_old_global_state(state);
 	if (WARN_ON(IS_ERR(old_hvs_state)))
@@ -340,14 +402,21 @@ static void vc4_atomic_commit_tail(struct drm_atomic_state *state)
 	if (WARN_ON(IS_ERR(new_hvs_state)))
 		return;
 
-	for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
-		struct vc4_crtc_state *vc4_crtc_state;
+	if (0 && vc4->gen < VC4_GEN_6_C) {
+		struct drm_crtc_state *new_crtc_state;
+		struct drm_crtc *crtc;
+		int i;
 
-		if (!new_crtc_state->commit)
-			continue;
+		for_each_new_crtc_in_state(state, crtc, new_crtc_state, i) {
+			struct vc4_crtc_state *vc4_crtc_state;
 
-		vc4_crtc_state = to_vc4_crtc_state(new_crtc_state);
-		vc4_hvs_mask_underrun(hvs, vc4_crtc_state->assigned_channel);
+			if (!new_crtc_state->commit || vc4->firmware_kms)
+				continue;
+
+
+			vc4_crtc_state = to_vc4_crtc_state(new_crtc_state);
+			vc4_hvs_mask_underrun(hvs, vc4_crtc_state->assigned_channel);
+		}
 	}
 
 	for (channel = 0; channel < HVS_NUM_CHANNELS; channel++) {
@@ -369,7 +438,7 @@ static void vc4_atomic_commit_tail(struct drm_atomic_state *state)
 		old_hvs_state->fifo_state[channel].pending_commit = NULL;
 	}
 
-	if (vc4->gen == VC4_GEN_5) {
+	if (vc4->gen == VC4_GEN_5 && !vc4->firmware_kms) {
 		unsigned long state_rate = max(old_hvs_state->core_clock_rate,
 					       new_hvs_state->core_clock_rate);
 		unsigned long core_rate = clamp_t(unsigned long, state_rate,
@@ -382,16 +451,34 @@ static void vc4_atomic_commit_tail(struct drm_atomic_state *state)
 		 * modeset.
 		 */
 		WARN_ON(clk_set_min_rate(hvs->core_clk, core_rate));
+		WARN_ON(clk_set_min_rate(hvs->disp_clk, core_rate));
 	}
 
 	drm_atomic_helper_commit_modeset_disables(dev, state);
 
-	vc4_ctm_commit(vc4, state);
+	if (vc4->gen <= VC4_GEN_5)
+		vc4_ctm_commit(vc4, state);
+
+	if (!vc4->firmware_kms) {
+		switch (vc4->gen) {
+		case VC4_GEN_4:
+			vc4_hvs_pv_muxing_commit(vc4, state);
+			break;
+
+		case VC4_GEN_5:
+			vc5_hvs_pv_muxing_commit(vc4, state);
+			break;
+
+		case VC4_GEN_6_C:
+		case VC4_GEN_6_D:
+			vc6_hvs_pv_muxing_commit(vc4, state);
+			break;
 
-	if (vc4->gen == VC4_GEN_5)
-		vc5_hvs_pv_muxing_commit(vc4, state);
-	else
-		vc4_hvs_pv_muxing_commit(vc4, state);
+		default:
+			drm_err(dev, "Unknown VC4 generation: %d", vc4->gen);
+			break;
+		}
+	}
 
 	drm_atomic_helper_commit_planes(dev, state,
 					DRM_PLANE_COMMIT_ACTIVE_ONLY);
@@ -406,7 +493,7 @@ static void vc4_atomic_commit_tail(struct drm_atomic_state *state)
 
 	drm_atomic_helper_cleanup_planes(dev, state);
 
-	if (vc4->gen == VC4_GEN_5) {
+	if (vc4->gen == VC4_GEN_5 && !vc4->firmware_kms) {
 		unsigned long core_rate = min_t(unsigned long,
 						hvs->max_core_rate,
 						new_hvs_state->core_clock_rate);
@@ -418,6 +505,7 @@ static void vc4_atomic_commit_tail(struct drm_atomic_state *state)
 		 * requirements.
 		 */
 		WARN_ON(clk_set_min_rate(hvs->core_clk, core_rate));
+		WARN_ON(clk_set_min_rate(hvs->disp_clk, core_rate));
 
 		drm_dbg(dev, "Core clock actual rate: %lu Hz\n",
 			clk_get_rate(hvs->core_clk));
@@ -426,11 +514,21 @@ static void vc4_atomic_commit_tail(struct drm_atomic_state *state)
 
 static int vc4_atomic_commit_setup(struct drm_atomic_state *state)
 {
+	struct drm_device *dev = state->dev;
+	struct vc4_dev *vc4 = to_vc4_dev(dev);
 	struct drm_crtc_state *crtc_state;
 	struct vc4_hvs_state *hvs_state;
 	struct drm_crtc *crtc;
 	unsigned int i;
 
+	/* We know for sure we don't want an async update here. Set
+	 * state->legacy_cursor_update to false to prevent
+	 * drm_atomic_helper_setup_commit() from auto-completing
+	 * commit->flip_done.
+	 */
+	if (!vc4->firmware_kms)
+		state->legacy_cursor_update = false;
+
 	hvs_state = vc4_hvs_get_new_global_state(state);
 	if (WARN_ON(IS_ERR(hvs_state)))
 		return PTR_ERR(hvs_state);
@@ -461,7 +559,7 @@ static struct drm_framebuffer *vc4_fb_create(struct drm_device *dev,
 	struct vc4_dev *vc4 = to_vc4_dev(dev);
 	struct drm_mode_fb_cmd2 mode_cmd_local;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return ERR_PTR(-ENODEV);
 
 	/* If the user didn't specify a modifier, use the
@@ -586,17 +684,26 @@ static int vc4_load_tracker_atomic_check(struct drm_atomic_state *state)
 	for_each_oldnew_plane_in_state(state, plane, old_plane_state,
 				       new_plane_state, i) {
 		struct vc4_plane_state *vc4_plane_state;
+		struct vc4_crtc *vc4_crtc;
 
 		if (old_plane_state->fb && old_plane_state->crtc) {
 			vc4_plane_state = to_vc4_plane_state(old_plane_state);
-			load_state->membus_load -= vc4_plane_state->membus_load;
-			load_state->hvs_load -= vc4_plane_state->hvs_load;
+			vc4_crtc = to_vc4_crtc(old_plane_state->crtc);
+
+			if (!vc4_crtc->feeds_txp) {
+				load_state->membus_load -= vc4_plane_state->membus_load;
+				load_state->hvs_load -= vc4_plane_state->hvs_load;
+			}
 		}
 
 		if (new_plane_state->fb && new_plane_state->crtc) {
 			vc4_plane_state = to_vc4_plane_state(new_plane_state);
-			load_state->membus_load += vc4_plane_state->membus_load;
-			load_state->hvs_load += vc4_plane_state->hvs_load;
+			vc4_crtc = to_vc4_crtc(new_plane_state->crtc);
+
+			if (!vc4_crtc->feeds_txp) {
+				load_state->membus_load += vc4_plane_state->membus_load;
+				load_state->hvs_load += vc4_plane_state->hvs_load;
+			}
 		}
 	}
 
@@ -799,6 +906,7 @@ static int cmp_vc4_crtc_hvs_output(const void *a, const void *b)
 static int vc4_pv_muxing_atomic_check(struct drm_device *dev,
 				      struct drm_atomic_state *state)
 {
+	struct vc4_dev *vc4 = to_vc4_dev(state->dev);
 	struct vc4_hvs_state *hvs_new_state;
 	struct drm_crtc **sorted_crtcs;
 	struct drm_crtc *crtc;
@@ -806,6 +914,9 @@ static int vc4_pv_muxing_atomic_check(struct drm_device *dev,
 	unsigned int i;
 	int ret;
 
+	if (vc4->firmware_kms)
+		return 0;
+
 	hvs_new_state = vc4_hvs_get_global_state(state);
 	if (IS_ERR(hvs_new_state))
 		return PTR_ERR(hvs_new_state);
@@ -1056,7 +1167,10 @@ int vc4_kms_load(struct drm_device *dev)
 		return ret;
 	}
 
-	if (vc4->gen == VC4_GEN_5) {
+	if (vc4->gen >= VC4_GEN_6_C) {
+		dev->mode_config.max_width = 8192;
+		dev->mode_config.max_height = 8192;
+	} else if (vc4->gen >= VC4_GEN_5) {
 		dev->mode_config.max_width = 7680;
 		dev->mode_config.max_height = 7680;
 	} else {
diff --git a/drivers/gpu/drm/vc4/vc4_perfmon.c b/drivers/gpu/drm/vc4/vc4_perfmon.c
index e4fda72c19f9..f1342f917cf7 100644
--- a/drivers/gpu/drm/vc4/vc4_perfmon.c
+++ b/drivers/gpu/drm/vc4/vc4_perfmon.c
@@ -23,7 +23,7 @@ void vc4_perfmon_get(struct vc4_perfmon *perfmon)
 		return;
 
 	vc4 = perfmon->dev;
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return;
 
 	refcount_inc(&perfmon->refcnt);
@@ -37,7 +37,7 @@ void vc4_perfmon_put(struct vc4_perfmon *perfmon)
 		return;
 
 	vc4 = perfmon->dev;
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return;
 
 	if (refcount_dec_and_test(&perfmon->refcnt))
@@ -49,7 +49,7 @@ void vc4_perfmon_start(struct vc4_dev *vc4, struct vc4_perfmon *perfmon)
 	unsigned int i;
 	u32 mask;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return;
 
 	if (WARN_ON_ONCE(!perfmon || vc4->active_perfmon))
@@ -69,7 +69,7 @@ void vc4_perfmon_stop(struct vc4_dev *vc4, struct vc4_perfmon *perfmon,
 {
 	unsigned int i;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return;
 
 	if (WARN_ON_ONCE(!vc4->active_perfmon ||
@@ -90,7 +90,7 @@ struct vc4_perfmon *vc4_perfmon_find(struct vc4_file *vc4file, int id)
 	struct vc4_dev *vc4 = vc4file->dev;
 	struct vc4_perfmon *perfmon;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return NULL;
 
 	mutex_lock(&vc4file->perfmon.lock);
@@ -105,7 +105,7 @@ void vc4_perfmon_open_file(struct vc4_file *vc4file)
 {
 	struct vc4_dev *vc4 = vc4file->dev;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return;
 
 	mutex_init(&vc4file->perfmon.lock);
@@ -131,7 +131,7 @@ void vc4_perfmon_close_file(struct vc4_file *vc4file)
 {
 	struct vc4_dev *vc4 = vc4file->dev;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return;
 
 	mutex_lock(&vc4file->perfmon.lock);
@@ -151,7 +151,7 @@ int vc4_perfmon_create_ioctl(struct drm_device *dev, void *data,
 	unsigned int i;
 	int ret;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	if (!vc4->v3d) {
@@ -205,7 +205,7 @@ int vc4_perfmon_destroy_ioctl(struct drm_device *dev, void *data,
 	struct drm_vc4_perfmon_destroy *req = data;
 	struct vc4_perfmon *perfmon;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	if (!vc4->v3d) {
@@ -233,7 +233,7 @@ int vc4_perfmon_get_values_ioctl(struct drm_device *dev, void *data,
 	struct vc4_perfmon *perfmon;
 	int ret;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	if (!vc4->v3d) {
@@ -241,11 +241,7 @@ int vc4_perfmon_get_values_ioctl(struct drm_device *dev, void *data,
 		return -ENODEV;
 	}
 
-	mutex_lock(&vc4file->perfmon.lock);
-	perfmon = idr_find(&vc4file->perfmon.idr, req->id);
-	vc4_perfmon_get(perfmon);
-	mutex_unlock(&vc4file->perfmon.lock);
-
+	perfmon = vc4_perfmon_find(vc4file, req->id);
 	if (!perfmon)
 		return -EINVAL;
 
diff --git a/drivers/gpu/drm/vc4/vc4_plane.c b/drivers/gpu/drm/vc4/vc4_plane.c
index 866bc46ee6d5..d8cc57947a65 100644
--- a/drivers/gpu/drm/vc4/vc4_plane.c
+++ b/drivers/gpu/drm/vc4/vc4_plane.c
@@ -36,6 +36,8 @@ static const struct hvs_format {
 	u32 pixel_order;
 	u32 pixel_order_hvs5;
 	bool hvs5_only;
+	bool hvs6_only;
+	bool hvs6_swap_chroma_pointers;
 } hvs_formats[] = {
 	{
 		.drm = DRM_FORMAT_XRGB8888,
@@ -61,6 +63,30 @@ static const struct hvs_format {
 		.pixel_order = HVS_PIXEL_ORDER_ARGB,
 		.pixel_order_hvs5 = HVS_PIXEL_ORDER_ABGR,
 	},
+	{
+		.drm = DRM_FORMAT_RGBX8888,
+		.hvs = HVS_PIXEL_FORMAT_RGBA8888,
+		.pixel_order = HVS_PIXEL_ORDER_RGBA,
+		.pixel_order_hvs5 = HVS_PIXEL_ORDER_RGBA,
+	},
+	{
+		.drm = DRM_FORMAT_RGBA8888,
+		.hvs = HVS_PIXEL_FORMAT_RGBA8888,
+		.pixel_order = HVS_PIXEL_ORDER_RGBA,
+		.pixel_order_hvs5 = HVS_PIXEL_ORDER_RGBA,
+	},
+	{
+		.drm = DRM_FORMAT_BGRX8888,
+		.hvs = HVS_PIXEL_FORMAT_RGBA8888,
+		.pixel_order = HVS_PIXEL_ORDER_BGRA,
+		.pixel_order_hvs5 = HVS_PIXEL_ORDER_BGRA,
+	},
+	{
+		.drm = DRM_FORMAT_BGRA8888,
+		.hvs = HVS_PIXEL_FORMAT_RGBA8888,
+		.pixel_order = HVS_PIXEL_ORDER_BGRA,
+		.pixel_order_hvs5 = HVS_PIXEL_ORDER_BGRA,
+	},
 	{
 		.drm = DRM_FORMAT_RGB565,
 		.hvs = HVS_PIXEL_FORMAT_RGB565,
@@ -108,6 +134,20 @@ static const struct hvs_format {
 		.hvs = HVS_PIXEL_FORMAT_YCBCR_YUV422_3PLANE,
 		.pixel_order = HVS_PIXEL_ORDER_XYCRCB,
 		.pixel_order_hvs5 = HVS_PIXEL_ORDER_XYCRCB,
+		.hvs6_swap_chroma_pointers = true,
+	},
+	{
+		.drm = DRM_FORMAT_YUV444,
+		.hvs = HVS_PIXEL_FORMAT_YCBCR_YUV422_3PLANE,
+		.pixel_order = HVS_PIXEL_ORDER_XYCBCR,
+		.pixel_order_hvs5 = HVS_PIXEL_ORDER_XYCBCR,
+	},
+	{
+		.drm = DRM_FORMAT_YVU444,
+		.hvs = HVS_PIXEL_FORMAT_YCBCR_YUV422_3PLANE,
+		.pixel_order = HVS_PIXEL_ORDER_XYCRCB,
+		.pixel_order_hvs5 = HVS_PIXEL_ORDER_XYCRCB,
+		.hvs6_swap_chroma_pointers = true,
 	},
 	{
 		.drm = DRM_FORMAT_YUV420,
@@ -120,6 +160,7 @@ static const struct hvs_format {
 		.hvs = HVS_PIXEL_FORMAT_YCBCR_YUV420_3PLANE,
 		.pixel_order = HVS_PIXEL_ORDER_XYCRCB,
 		.pixel_order_hvs5 = HVS_PIXEL_ORDER_XYCRCB,
+		.hvs6_swap_chroma_pointers = true,
 	},
 	{
 		.drm = DRM_FORMAT_NV12,
@@ -235,6 +276,42 @@ static const struct hvs_format {
 		.pixel_order = HVS_PIXEL_ORDER_BGRA,
 		.pixel_order_hvs5 = HVS_PIXEL_ORDER_RGBA,
 	},
+	{
+		.drm = DRM_FORMAT_P010,
+		.hvs = HVS_PIXEL_FORMAT_YCBCR_YUV420_15_6_2PLANE,
+		.pixel_order_hvs5 = HVS_PIXEL_ORDER_XYCBCR,
+		.hvs6_only = true,
+	},
+	{
+		.drm = DRM_FORMAT_P012,
+		.hvs = HVS_PIXEL_FORMAT_YCBCR_YUV420_15_6_2PLANE,
+		.pixel_order_hvs5 = HVS_PIXEL_ORDER_XYCBCR,
+		.hvs6_only = true,
+	},
+	{
+		.drm = DRM_FORMAT_P016,
+		.hvs = HVS_PIXEL_FORMAT_YCBCR_YUV420_15_6_2PLANE,
+		.pixel_order_hvs5 = HVS_PIXEL_ORDER_XYCBCR,
+		.hvs6_only = true,
+	},
+	{
+		.drm = DRM_FORMAT_S010,
+		.hvs = HVS_PIXEL_FORMAT_YCBCR_YUV420_9_0_3PLANE,
+		.pixel_order_hvs5 = HVS_PIXEL_ORDER_XYCBCR,
+		.hvs6_only = true,
+	},
+	{
+		.drm = DRM_FORMAT_S012,
+		.hvs = HVS_PIXEL_FORMAT_YCBCR_YUV420_11_2_3PLANE,
+		.pixel_order_hvs5 = HVS_PIXEL_ORDER_XYCBCR,
+		.hvs6_only = true,
+	},
+	{
+		.drm = DRM_FORMAT_S016,
+		.hvs = HVS_PIXEL_FORMAT_YCBCR_YUV420_15_6_3PLANE,
+		.pixel_order_hvs5 = HVS_PIXEL_ORDER_XYCBCR,
+		.hvs6_only = true,
+	},
 };
 
 static const struct hvs_format *vc4_get_hvs_format(u32 drm_format)
@@ -251,9 +328,13 @@ static const struct hvs_format *vc4_get_hvs_format(u32 drm_format)
 
 static enum vc4_scaling_mode vc4_get_scaling_mode(u32 src, u32 dst)
 {
-	if (dst == src)
+	if (dst == src >> 16)
 		return VC4_SCALING_NONE;
-	if (3 * dst >= 2 * src)
+
+	if (src <= (1 << 16))
+		/* Source rectangle <= 1 pixel can use TPZ for resize/upscale */
+		return VC4_SCALING_TPZ;
+	else if (3 * dst >= 2 * (src >> 16))
 		return VC4_SCALING_PPF;
 	else
 		return VC4_SCALING_TPZ;
@@ -264,9 +345,12 @@ static bool plane_enabled(struct drm_plane_state *state)
 	return state->fb && !WARN_ON(!state->crtc);
 }
 
-static struct drm_plane_state *vc4_plane_duplicate_state(struct drm_plane *plane)
+struct drm_plane_state *vc4_plane_duplicate_state(struct drm_plane *plane)
 {
+	struct vc4_dev *vc4 = to_vc4_dev(plane->dev);
+	struct vc4_hvs *hvs = vc4->hvs;
 	struct vc4_plane_state *vc4_state;
+	unsigned int i;
 
 	if (WARN_ON(!plane->state))
 		return NULL;
@@ -275,8 +359,15 @@ static struct drm_plane_state *vc4_plane_duplicate_state(struct drm_plane *plane
 	if (!vc4_state)
 		return NULL;
 
-	memset(&vc4_state->lbm, 0, sizeof(vc4_state->lbm));
+	for (i = 0; i < DRM_FORMAT_MAX_PLANES; i++) {
+		if (vc4_state->upm_handle[i])
+			refcount_inc(&hvs->upm_refcounts[vc4_state->upm_handle[i]].refcount);
+	}
+	if (vc4_state->lbm_handle)
+		refcount_inc(&hvs->lbm_refcounts[vc4_state->lbm_handle].refcount);
+
 	vc4_state->dlist_initialized = 0;
+	vc4_state->is_yuv444_unity = false;
 
 	__drm_atomic_helper_plane_duplicate_state(plane, &vc4_state->base);
 
@@ -294,18 +385,63 @@ static struct drm_plane_state *vc4_plane_duplicate_state(struct drm_plane *plane
 	return &vc4_state->base;
 }
 
-static void vc4_plane_destroy_state(struct drm_plane *plane,
-				    struct drm_plane_state *state)
+static void vc4_plane_release_lbm_ida(struct vc4_hvs *hvs, unsigned int lbm_handle)
+{
+	struct vc4_lbm_refcounts *refcount = &hvs->lbm_refcounts[lbm_handle];
+	unsigned long irqflags;
+
+	spin_lock_irqsave(&hvs->mm_lock, irqflags);
+	drm_mm_remove_node(&refcount->lbm);
+	spin_unlock_irqrestore(&hvs->mm_lock, irqflags);
+	refcount->lbm.start = 0;
+	refcount->lbm.size = 0;
+	refcount->size = 0;
+
+	ida_free(&hvs->lbm_handles, lbm_handle);
+}
+
+static void vc4_plane_release_upm_ida(struct vc4_hvs *hvs, unsigned int upm_handle)
+{
+	struct vc4_upm_refcounts *refcount = &hvs->upm_refcounts[upm_handle];
+	unsigned long irqflags;
+
+	spin_lock_irqsave(&hvs->mm_lock, irqflags);
+	drm_mm_remove_node(&refcount->upm);
+	spin_unlock_irqrestore(&hvs->mm_lock, irqflags);
+	refcount->upm.start = 0;
+	refcount->upm.size = 0;
+	refcount->size = 0;
+
+	ida_free(&hvs->upm_handles, upm_handle);
+}
+
+void vc4_plane_destroy_state(struct drm_plane *plane,
+			     struct drm_plane_state *state)
 {
 	struct vc4_dev *vc4 = to_vc4_dev(plane->dev);
+	struct vc4_hvs *hvs = vc4->hvs;
 	struct vc4_plane_state *vc4_state = to_vc4_plane_state(state);
+	unsigned int i;
 
-	if (drm_mm_node_allocated(&vc4_state->lbm)) {
-		unsigned long irqflags;
+	if (vc4_state->lbm_handle) {
+		struct vc4_lbm_refcounts *refcount;
+
+		refcount = &hvs->lbm_refcounts[vc4_state->lbm_handle];
+
+		if (refcount_dec_and_test(&refcount->refcount))
+			vc4_plane_release_lbm_ida(hvs, vc4_state->lbm_handle);
+	}
+
+	for (i = 0; i < DRM_FORMAT_MAX_PLANES; i++) {
+		struct vc4_upm_refcounts *refcount;
 
-		spin_lock_irqsave(&vc4->hvs->mm_lock, irqflags);
-		drm_mm_remove_node(&vc4_state->lbm);
-		spin_unlock_irqrestore(&vc4->hvs->mm_lock, irqflags);
+		if (!vc4_state->upm_handle[i])
+			continue;
+
+		refcount = &hvs->upm_refcounts[vc4_state->upm_handle[i]];
+
+		if (refcount_dec_and_test(&refcount->refcount))
+			vc4_plane_release_upm_ida(hvs, vc4_state->upm_handle[i]);
 	}
 
 	kfree(vc4_state->dlist);
@@ -314,7 +450,7 @@ static void vc4_plane_destroy_state(struct drm_plane *plane,
 }
 
 /* Called during init to allocate the plane's atomic state. */
-static void vc4_plane_reset(struct drm_plane *plane)
+void vc4_plane_reset(struct drm_plane *plane)
 {
 	struct vc4_plane_state *vc4_state;
 
@@ -438,12 +574,11 @@ static int vc4_plane_setup_clipping_and_scaling(struct drm_plane_state *state)
 {
 	struct vc4_plane_state *vc4_state = to_vc4_plane_state(state);
 	struct drm_framebuffer *fb = state->fb;
-	struct drm_gem_dma_object *bo;
 	int num_planes = fb->format->num_planes;
 	struct drm_crtc_state *crtc_state;
 	u32 h_subsample = fb->format->hsub;
 	u32 v_subsample = fb->format->vsub;
-	int i, ret;
+	int ret;
 
 	crtc_state = drm_atomic_get_existing_crtc_state(state->state,
 							state->crtc);
@@ -457,20 +592,10 @@ static int vc4_plane_setup_clipping_and_scaling(struct drm_plane_state *state)
 	if (ret)
 		return ret;
 
-	for (i = 0; i < num_planes; i++) {
-		bo = drm_fb_dma_get_gem_obj(fb, i);
-		vc4_state->offsets[i] = bo->dma_addr + fb->offsets[i];
-	}
-
-	/*
-	 * We don't support subpixel source positioning for scaling,
-	 * but fractional coordinates can be generated by clipping
-	 * so just round for now
-	 */
-	vc4_state->src_x = DIV_ROUND_CLOSEST(state->src.x1, 1 << 16);
-	vc4_state->src_y = DIV_ROUND_CLOSEST(state->src.y1, 1 << 16);
-	vc4_state->src_w[0] = DIV_ROUND_CLOSEST(state->src.x2, 1 << 16) - vc4_state->src_x;
-	vc4_state->src_h[0] = DIV_ROUND_CLOSEST(state->src.y2, 1 << 16) - vc4_state->src_y;
+	vc4_state->src_x = state->src.x1;
+	vc4_state->src_y = state->src.y1;
+	vc4_state->src_w[0] = state->src.x2 - vc4_state->src_x;
+	vc4_state->src_h[0] = state->src.y2 - vc4_state->src_y;
 
 	vc4_state->crtc_x = state->dst.x1;
 	vc4_state->crtc_y = state->dst.y1;
@@ -510,6 +635,12 @@ static int vc4_plane_setup_clipping_and_scaling(struct drm_plane_state *state)
 		 */
 		if (vc4_state->x_scaling[1] == VC4_SCALING_NONE)
 			vc4_state->x_scaling[1] = VC4_SCALING_PPF;
+
+		/* Similarly UV needs vertical scaling to be enabled.
+		 * Without this a 1:1 scaled YUV422 plane isn't rendered.
+		 */
+		if (vc4_state->y_scaling[1] == VC4_SCALING_NONE)
+			vc4_state->y_scaling[1] = VC4_SCALING_PPF;
 	} else {
 		vc4_state->is_yuv = false;
 		vc4_state->x_scaling[1] = VC4_SCALING_NONE;
@@ -521,33 +652,104 @@ static int vc4_plane_setup_clipping_and_scaling(struct drm_plane_state *state)
 
 static void vc4_write_tpz(struct vc4_plane_state *vc4_state, u32 src, u32 dst)
 {
+	struct vc4_dev *vc4 = to_vc4_dev(vc4_state->base.plane->dev);
 	u32 scale, recip;
 
-	scale = (1 << 16) * src / dst;
+	WARN_ON_ONCE(vc4->gen > VC4_GEN_6_D);
 
-	/* The specs note that while the reciprocal would be defined
-	 * as (1<<32)/scale, ~0 is close enough.
-	 */
-	recip = ~0 / scale;
+	if ((dst << 16) < src) {
+		scale = src / dst;
+
+		/* The specs note that while the reciprocal would be defined
+		 * as (1<<32)/scale, ~0 is close enough.
+		 */
+		recip = ~0 / scale;
+	} else {
+		scale = (1 << 16) + 1;
+		recip = (1 << 16) - 1;
+	}
 
 	vc4_dlist_write(vc4_state,
+			/*
+			 * The BCM2712 is lacking BIT(31) compared to
+			 * the previous generations, but we don't use
+			 * it.
+			 */
 			VC4_SET_FIELD(scale, SCALER_TPZ0_SCALE) |
 			VC4_SET_FIELD(0, SCALER_TPZ0_IPHASE));
 	vc4_dlist_write(vc4_state,
 			VC4_SET_FIELD(recip, SCALER_TPZ1_RECIP));
 }
 
-static void vc4_write_ppf(struct vc4_plane_state *vc4_state, u32 src, u32 dst)
+/* phase magnitude bits */
+#define PHASE_BITS 6
+
+static void vc4_write_ppf(struct vc4_plane_state *vc4_state, u32 src, u32 dst,
+			  u32 xy, int channel, int chroma_offset,
+			  bool no_interpolate)
 {
-	u32 scale = (1 << 16) * src / dst;
+	struct vc4_dev *vc4 = to_vc4_dev(vc4_state->base.plane->dev);
+	u32 scale = src / dst;
+	s32 offset, offset2;
+	s32 phase;
+
+	WARN_ON_ONCE(vc4->gen > VC4_GEN_6_D);
+
+	/*
+	 * Start the phase at 1/2 pixel from the 1st pixel at src_x.
+	 * 1/4 pixel for YUV, plus the offset for chroma siting.
+	 */
+	if (channel) {
+		/*
+		 * The phase is relative to scale_src->x, so shift it for
+		 * display list's x value
+		 */
+		offset = (xy & 0x1ffff) >> (16 - PHASE_BITS) >> 1;
+		offset -= chroma_offset >> (17 - PHASE_BITS);
+		offset += -(1 << PHASE_BITS >> 2);
+	} else {
+		/*
+		 * The phase is relative to scale_src->x, so shift it for
+		 * display list's x value
+		 */
+		offset = (xy & 0xffff) >> (16 - PHASE_BITS);
+		offset += -(1 << PHASE_BITS >> 1);
+
+		/*
+		 * This is a kludge to make sure the scaling factors are
+		 * consistent with YUV's luma scaling. We lose 1-bit precision
+		 * because of this.
+		 */
+		scale &= ~1;
+	}
+
+	/*
+	 * There may be a also small error introduced by precision of scale.
+	 * Add half of that as a compromise
+	 */
+	offset2 = src - dst * scale;
+	offset2 >>= 16 - PHASE_BITS;
+	phase = offset + (offset2 >> 1);
+
+	/* Ensure +ve values don't touch the sign bit, then truncate negative values */
+	if (phase >= 1 << PHASE_BITS)
+		phase = (1 << PHASE_BITS) - 1;
+
+	phase &= SCALER_PPF_IPHASE_MASK;
 
 	vc4_dlist_write(vc4_state,
+			(no_interpolate ? SCALER_PPF_NOINTERP : 0) |
 			SCALER_PPF_AGC |
 			VC4_SET_FIELD(scale, SCALER_PPF_SCALE) |
-			VC4_SET_FIELD(0, SCALER_PPF_IPHASE));
+			/*
+			 * The register layout documentation is slightly
+			 * different to setup the phase in the BCM2712,
+			 * but they seem equivalent.
+			 */
+			VC4_SET_FIELD(phase, SCALER_PPF_IPHASE));
 }
 
-static u32 vc4_lbm_size(struct drm_plane_state *state)
+static u32 __vc4_lbm_size(struct drm_plane_state *state)
 {
 	struct vc4_plane_state *vc4_state = to_vc4_plane_state(state);
 	struct vc4_dev *vc4 = to_vc4_dev(state->plane->dev);
@@ -569,7 +771,7 @@ static u32 vc4_lbm_size(struct drm_plane_state *state)
 	if (vc4_state->x_scaling[0] == VC4_SCALING_TPZ)
 		pix_per_line = vc4_state->crtc_w;
 	else
-		pix_per_line = vc4_state->src_w[0];
+		pix_per_line = vc4_state->src_w[0] >> 16;
 
 	if (!vc4_state->is_yuv) {
 		if (vc4_state->y_scaling[0] == VC4_SCALING_TPZ)
@@ -595,34 +797,170 @@ static u32 vc4_lbm_size(struct drm_plane_state *state)
 	return lbm;
 }
 
+static unsigned int vc4_lbm_words_per_component(const struct drm_plane_state *state,
+						unsigned int channel)
+{
+	const struct vc4_plane_state *vc4_state = to_vc4_plane_state(state);
+
+	switch (vc4_state->y_scaling[channel]) {
+	case VC4_SCALING_PPF:
+		return 4;
+
+	case VC4_SCALING_TPZ:
+		return 2;
+
+	default:
+		return 0;
+	}
+}
+
+static unsigned int vc4_lbm_components(const struct drm_plane_state *state,
+				       unsigned int channel)
+{
+	const struct drm_format_info *info = state->fb->format;
+	const struct vc4_plane_state *vc4_state = to_vc4_plane_state(state);
+
+	if (vc4_state->y_scaling[channel] == VC4_SCALING_NONE)
+		return 0;
+
+	if (info->is_yuv)
+		return channel ? 2 : 1;
+
+	if (vc4_state->y_scaling[channel] == VC4_SCALING_TPZ)
+		return 4;
+
+	if (info->has_alpha && state->alpha == DRM_BLEND_ALPHA_OPAQUE)
+		return 4;
+
+	return 3;
+}
+
+static unsigned int vc4_lbm_channel_size(const struct drm_plane_state *state,
+					 unsigned int channel)
+{
+	const struct drm_format_info *info = state->fb->format;
+	unsigned int components, words, wpc;
+	unsigned int width, lines;
+
+	/* LBM is meant to use the smaller of source or dest width, but there
+	 * is a issue with UV scaling that the size required for the second
+	 * channel is based on the source width only.
+	 */
+	if (info->hsub > 1 && channel == 1)
+		width = state->src_w >> 16;
+	else
+		width = min(state->src_w >> 16, state->crtc_w);
+	width = round_up(width / info->hsub, 4);
+
+	wpc = vc4_lbm_words_per_component(state, channel);
+	if (!wpc)
+		return 0;
+
+	components = vc4_lbm_components(state, channel);
+	if (!components)
+		return 0;
+
+	words = width * wpc * components;
+
+	lines = DIV_ROUND_UP(words, 128 / info->hsub);
+
+	return lines;
+}
+
+static unsigned int __vc6_lbm_size(const struct drm_plane_state *state)
+{
+	const struct drm_format_info *info = state->fb->format;
+
+	if (info->hsub > 1)
+		return max(vc4_lbm_channel_size(state, 0),
+			   vc4_lbm_channel_size(state, 1));
+	else
+		return vc4_lbm_channel_size(state, 0);
+}
+
+static u32 vc4_lbm_size(struct drm_plane_state *state)
+{
+	struct vc4_plane_state *vc4_state = to_vc4_plane_state(state);
+	struct vc4_dev *vc4 = to_vc4_dev(state->plane->dev);
+
+	/* LBM is not needed when there's no vertical scaling. */
+	if (vc4_state->y_scaling[0] == VC4_SCALING_NONE &&
+	    vc4_state->y_scaling[1] == VC4_SCALING_NONE)
+		return 0;
+
+	if (vc4->gen >= VC4_GEN_6_C)
+		return __vc6_lbm_size(state);
+	else
+		return __vc4_lbm_size(state);
+}
+
+static size_t vc6_upm_size(const struct drm_plane_state *state,
+			   unsigned int plane)
+{
+	const struct vc4_plane_state *vc4_state = to_vc4_plane_state(state);
+	unsigned int stride = state->fb->pitches[plane];
+	u64 base_format_mod = fourcc_mod_broadcom_mod(state->fb->modifier);
+
+	if (base_format_mod == DRM_FORMAT_MOD_BROADCOM_SAND128) {
+		if (state->fb->format->format == DRM_FORMAT_P030)
+			stride = (ALIGN(state->fb->width, 96) * 4) / 3;
+		else
+			stride = ALIGN(state->fb->width, 128);
+	}
+	if (!plane && (state->fb->format->format == DRM_FORMAT_YUV444 ||
+		       state->fb->format->format == DRM_FORMAT_YVU444))
+		stride <<= 1;
+	/*
+	 * TODO: This only works for raster formats, and is sub-optimal
+	 * for buffers with a stride aligned on 32 bytes.
+	 */
+	unsigned int words_per_line = (stride + 62) / 32;
+	unsigned int fetch_region_size = words_per_line * 32;
+	unsigned int buffer_lines = 2 << vc4_state->upm_buffer_lines;
+	unsigned int buffer_size = fetch_region_size * buffer_lines;
+
+	return ALIGN(buffer_size, HVS_UBM_WORD_SIZE);
+}
+
 static void vc4_write_scaling_parameters(struct drm_plane_state *state,
 					 int channel)
 {
+	struct vc4_dev *vc4 = to_vc4_dev(state->plane->dev);
 	struct vc4_plane_state *vc4_state = to_vc4_plane_state(state);
+	bool no_interpolate = state->scaling_filter == DRM_SCALING_FILTER_NEAREST_NEIGHBOR;
+
+	if (vc4_state->is_yuv444_unity)
+		no_interpolate = 1;
+
+	WARN_ON_ONCE(vc4->gen > VC4_GEN_6_D);
 
 	/* Ch0 H-PPF Word 0: Scaling Parameters */
 	if (vc4_state->x_scaling[channel] == VC4_SCALING_PPF) {
-		vc4_write_ppf(vc4_state,
-			      vc4_state->src_w[channel], vc4_state->crtc_w);
+		vc4_write_ppf(vc4_state, vc4_state->src_w[channel],
+			      vc4_state->crtc_w, vc4_state->src_x, channel,
+			      state->chroma_siting_h,
+			      no_interpolate);
 	}
 
 	/* Ch0 V-PPF Words 0-1: Scaling Parameters, Context */
 	if (vc4_state->y_scaling[channel] == VC4_SCALING_PPF) {
-		vc4_write_ppf(vc4_state,
-			      vc4_state->src_h[channel], vc4_state->crtc_h);
+		vc4_write_ppf(vc4_state, vc4_state->src_h[channel],
+			      vc4_state->crtc_h, vc4_state->src_y, channel,
+			      state->chroma_siting_v,
+			      no_interpolate);
 		vc4_dlist_write(vc4_state, 0xc0c0c0c0);
 	}
 
 	/* Ch0 H-TPZ Words 0-1: Scaling Parameters, Recip */
 	if (vc4_state->x_scaling[channel] == VC4_SCALING_TPZ) {
-		vc4_write_tpz(vc4_state,
-			      vc4_state->src_w[channel], vc4_state->crtc_w);
+		vc4_write_tpz(vc4_state, vc4_state->src_w[channel],
+			      vc4_state->crtc_w);
 	}
 
 	/* Ch0 V-TPZ Words 0-2: Scaling Parameters, Recip, Context */
 	if (vc4_state->y_scaling[channel] == VC4_SCALING_TPZ) {
-		vc4_write_tpz(vc4_state,
-			      vc4_state->src_h[channel], vc4_state->crtc_h);
+		vc4_write_tpz(vc4_state, vc4_state->src_h[channel],
+			      vc4_state->crtc_h);
 		vc4_dlist_write(vc4_state, 0xc0c0c0c0);
 	}
 }
@@ -660,7 +998,8 @@ static void vc4_plane_calc_load(struct drm_plane_state *state)
 	for (i = 0; i < fb->format->num_planes; i++) {
 		/* Even if the bandwidth/plane required for a single frame is
 		 *
-		 * vc4_state->src_w[i] * vc4_state->src_h[i] * cpp * vrefresh
+		 * (vc4_state->src_w[i] >> 16) * (vc4_state->src_h[i] >> 16) *
+		 *  cpp * vrefresh
 		 *
 		 * when downscaling, we have to read more pixels per line in
 		 * the time frame reserved for a single line, so the bandwidth
@@ -669,11 +1008,11 @@ static void vc4_plane_calc_load(struct drm_plane_state *state)
 		 * load by this number. We're likely over-estimating the read
 		 * demand, but that's better than under-estimating it.
 		 */
-		vscale_factor = DIV_ROUND_UP(vc4_state->src_h[i],
+		vscale_factor = DIV_ROUND_UP(vc4_state->src_h[i] >> 16,
 					     vc4_state->crtc_h);
-		vc4_state->membus_load += vc4_state->src_w[i] *
-					  vc4_state->src_h[i] * vscale_factor *
-					  fb->format->cpp[i];
+		vc4_state->membus_load += (vc4_state->src_w[i] >> 16) *
+					  (vc4_state->src_h[i] >> 16) *
+					  vscale_factor * fb->format->cpp[i];
 		vc4_state->hvs_load += vc4_state->crtc_h * vc4_state->crtc_w;
 	}
 
@@ -684,43 +1023,206 @@ static void vc4_plane_calc_load(struct drm_plane_state *state)
 
 static int vc4_plane_allocate_lbm(struct drm_plane_state *state)
 {
-	struct vc4_dev *vc4 = to_vc4_dev(state->plane->dev);
+	struct drm_device *drm = state->plane->dev;
+	struct vc4_dev *vc4 = to_vc4_dev(drm);
+	struct vc4_hvs *hvs = vc4->hvs;
+	struct drm_plane *plane = state->plane;
 	struct vc4_plane_state *vc4_state = to_vc4_plane_state(state);
+	struct vc4_lbm_refcounts *refcount;
 	unsigned long irqflags;
+	int lbm_handle;
 	u32 lbm_size;
+	int ret;
 
 	lbm_size = vc4_lbm_size(state);
 	if (!lbm_size)
 		return 0;
 
+	/*
+	 * NOTE: BCM2712 doesn't need to be aligned, since the size
+	 * returned by vc4_lbm_size() is in words already.
+	 */
+	if (vc4->gen == VC4_GEN_5)
+		lbm_size = ALIGN(lbm_size, 64);
+	else if (vc4->gen == VC4_GEN_4)
+		lbm_size = ALIGN(lbm_size, 32);
+
+	drm_dbg_driver(drm, "[PLANE:%d:%s] LBM Allocation Size: %u\n",
+		       plane->base.id, plane->name, lbm_size);
+
 	if (WARN_ON(!vc4_state->lbm_offset))
 		return -EINVAL;
 
 	/* Allocate the LBM memory that the HVS will use for temporary
 	 * storage due to our scaling/format conversion.
 	 */
-	if (!drm_mm_node_allocated(&vc4_state->lbm)) {
-		int ret;
+	lbm_handle = vc4_state->lbm_handle;
+	if (lbm_handle &&
+	    hvs->lbm_refcounts[lbm_handle].size == lbm_size) {
+		/* Allocation is the same size as the previous user of
+		 * the plane. Keep the allocation.
+		 */
+		vc4_state->lbm_handle = lbm_handle;
+	} else {
+		if (lbm_handle &&
+		    refcount_dec_and_test(&hvs->lbm_refcounts[lbm_handle].refcount)) {
+			vc4_plane_release_lbm_ida(hvs, lbm_handle);
+			vc4_state->lbm_handle = 0;
+		}
+
+		lbm_handle = ida_alloc_range(&hvs->lbm_handles, 1,
+					     VC4_NUM_LBM_HANDLES,
+					     GFP_KERNEL);
+		if (lbm_handle < 0) {
+			drm_err(drm, "Out of lbm_handles\n");
+			return lbm_handle;
+		}
+		vc4_state->lbm_handle = lbm_handle;
+
+		refcount = &hvs->lbm_refcounts[lbm_handle];
+		refcount_set(&refcount->refcount, 1);
+		refcount->size = lbm_size;
 
-		spin_lock_irqsave(&vc4->hvs->mm_lock, irqflags);
+		spin_lock_irqsave(&hvs->mm_lock, irqflags);
 		ret = drm_mm_insert_node_generic(&vc4->hvs->lbm_mm,
-						 &vc4_state->lbm,
-						 lbm_size,
-						 vc4->gen == VC4_GEN_5 ? 64 : 32,
+						 &refcount->lbm,
+						 lbm_size, 1,
 						 0, 0);
-		spin_unlock_irqrestore(&vc4->hvs->mm_lock, irqflags);
+		spin_unlock_irqrestore(&hvs->mm_lock, irqflags);
 
-		if (ret)
+		if (ret) {
+			drm_err(drm, "Failed to allocate LBM entry: %d\n", ret);
+			refcount_set(&refcount->refcount, 0);
+			ida_free(&hvs->lbm_handles, lbm_handle);
+			vc4_state->lbm_handle = 0;
 			return ret;
-	} else {
-		WARN_ON_ONCE(lbm_size != vc4_state->lbm.size);
+		}
 	}
 
-	vc4_state->dlist[vc4_state->lbm_offset] = vc4_state->lbm.start;
+	vc4_state->dlist[vc4_state->lbm_offset] = hvs->lbm_refcounts[lbm_handle].lbm.start;
+
+	return 0;
+}
+
+static void vc4_plane_free_lbm(struct drm_plane_state *state)
+{
+	struct vc4_plane_state *vc4_state = to_vc4_plane_state(state);
+	struct drm_device *drm = state->plane->dev;
+	struct vc4_dev *vc4 = to_vc4_dev(drm);
+	struct vc4_hvs *hvs = vc4->hvs;
+	unsigned int lbm_handle;
+
+	lbm_handle = vc4_state->lbm_handle;
+	if (!lbm_handle)
+		return;
+
+	if (refcount_dec_and_test(&hvs->lbm_refcounts[lbm_handle].refcount))
+		vc4_plane_release_lbm_ida(hvs, lbm_handle);
+	vc4_state->lbm_handle = 0;
+}
+
+static int vc6_plane_allocate_upm(struct drm_plane_state *state)
+{
+	const struct drm_format_info *info = state->fb->format;
+	struct drm_device *drm = state->plane->dev;
+	struct vc4_dev *vc4 = to_vc4_dev(drm);
+	struct vc4_hvs *hvs = vc4->hvs;
+	struct vc4_plane_state *vc4_state = to_vc4_plane_state(state);
+	unsigned int i;
+	int ret;
+
+	WARN_ON_ONCE(vc4->gen < VC4_GEN_6_C);
+
+	vc4_state->upm_buffer_lines = SCALER6_PTR0_UPM_BUFF_SIZE_2_LINES;
+
+	for (i = 0; i < info->num_planes; i++) {
+		struct vc4_upm_refcounts *refcount;
+		int upm_handle;
+		unsigned long irqflags;
+		size_t upm_size;
+
+		upm_size = vc6_upm_size(state, i);
+		if (!upm_size)
+			return -EINVAL;
+		upm_handle = vc4_state->upm_handle[i];
+
+		if (upm_handle &&
+		    hvs->upm_refcounts[upm_handle].size == upm_size) {
+			/* Allocation is the same size as the previous user of
+			 * the plane. Keep the allocation.
+			 */
+			vc4_state->upm_handle[i] = upm_handle;
+		} else {
+			if (upm_handle &&
+			    refcount_dec_and_test(&hvs->upm_refcounts[upm_handle].refcount)) {
+				vc4_plane_release_upm_ida(hvs, upm_handle);
+				vc4_state->upm_handle[i] = 0;
+			}
+
+			upm_handle = ida_alloc_range(&hvs->upm_handles, 1,
+						     VC4_NUM_UPM_HANDLES,
+						     GFP_KERNEL);
+			if (upm_handle < 0) {
+				drm_dbg(drm, "Out of upm_handles\n");
+				return upm_handle;
+			}
+			vc4_state->upm_handle[i] = upm_handle;
+
+			refcount = &hvs->upm_refcounts[upm_handle];
+			refcount_set(&refcount->refcount, 1);
+			refcount->size = upm_size;
+
+			spin_lock_irqsave(&hvs->mm_lock, irqflags);
+			ret = drm_mm_insert_node_generic(&hvs->upm_mm,
+							 &refcount->upm,
+							 upm_size, HVS_UBM_WORD_SIZE,
+							 0, 0);
+			spin_unlock_irqrestore(&hvs->mm_lock, irqflags);
+			if (ret) {
+				drm_err(drm, "Failed to allocate UPM entry: %d\n", ret);
+				refcount_set(&refcount->refcount, 0);
+				ida_free(&hvs->upm_handles, upm_handle);
+				vc4_state->upm_handle[i] = 0;
+				return ret;
+			}
+		}
+
+		refcount = &hvs->upm_refcounts[upm_handle];
+		vc4_state->dlist[vc4_state->ptr0_offset[i]] |=
+			VC4_SET_FIELD(refcount->upm.start / HVS_UBM_WORD_SIZE,
+				      SCALER6_PTR0_UPM_BASE) |
+			VC4_SET_FIELD(vc4_state->upm_handle[i] - 1,
+				      SCALER6_PTR0_UPM_HANDLE) |
+			VC4_SET_FIELD(vc4_state->upm_buffer_lines,
+				      SCALER6_PTR0_UPM_BUFF_SIZE);
+	}
 
 	return 0;
 }
 
+static void vc6_plane_free_upm(struct drm_plane_state *state)
+{
+	struct vc4_plane_state *vc4_state = to_vc4_plane_state(state);
+	struct drm_device *drm = state->plane->dev;
+	struct vc4_dev *vc4 = to_vc4_dev(drm);
+	struct vc4_hvs *hvs = vc4->hvs;
+	unsigned int i;
+
+	WARN_ON_ONCE(vc4->gen < VC4_GEN_6_C);
+
+	for (i = 0; i < DRM_FORMAT_MAX_PLANES; i++) {
+		unsigned int upm_handle;
+
+		upm_handle = vc4_state->upm_handle[i];
+		if (!upm_handle)
+			continue;
+
+		if (refcount_dec_and_test(&hvs->upm_refcounts[upm_handle].refcount))
+			vc4_plane_release_upm_ida(hvs, upm_handle);
+		vc4_state->upm_handle[i] = 0;
+	}
+}
+
 /*
  * The colorspace conversion matrices are held in 3 entries in the dlist.
  * Create an array of them, with entries for each full and limited mode, and
@@ -768,6 +1270,11 @@ static const u32 colorspace_coeffs[2][DRM_COLOR_ENCODING_MAX][3] = {
 
 static u32 vc4_hvs4_get_alpha_blend_mode(struct drm_plane_state *state)
 {
+	struct drm_device *dev = state->state->dev;
+	struct vc4_dev *vc4 = to_vc4_dev(dev);
+
+	WARN_ON_ONCE(vc4->gen != VC4_GEN_4);
+
 	if (!state->fb->format->has_alpha)
 		return VC4_SET_FIELD(SCALER_POS2_ALPHA_MODE_FIXED,
 				     SCALER_POS2_ALPHA_MODE);
@@ -789,25 +1296,56 @@ static u32 vc4_hvs4_get_alpha_blend_mode(struct drm_plane_state *state)
 
 static u32 vc4_hvs5_get_alpha_blend_mode(struct drm_plane_state *state)
 {
-	if (!state->fb->format->has_alpha)
-		return VC4_SET_FIELD(SCALER5_CTL2_ALPHA_MODE_FIXED,
-				     SCALER5_CTL2_ALPHA_MODE);
+	struct drm_device *dev = state->state->dev;
+	struct vc4_dev *vc4 = to_vc4_dev(dev);
 
-	switch (state->pixel_blend_mode) {
-	case DRM_MODE_BLEND_PIXEL_NONE:
-		return VC4_SET_FIELD(SCALER5_CTL2_ALPHA_MODE_FIXED,
-				     SCALER5_CTL2_ALPHA_MODE);
+	WARN_ON_ONCE(vc4->gen != VC4_GEN_5 && vc4->gen != VC4_GEN_6_C &&
+		     vc4->gen != VC4_GEN_6_D);
+
+	switch (vc4->gen) {
 	default:
-	case DRM_MODE_BLEND_PREMULTI:
-		return VC4_SET_FIELD(SCALER5_CTL2_ALPHA_MODE_PIPELINE,
-				     SCALER5_CTL2_ALPHA_MODE) |
-			SCALER5_CTL2_ALPHA_PREMULT;
-	case DRM_MODE_BLEND_COVERAGE:
-		return VC4_SET_FIELD(SCALER5_CTL2_ALPHA_MODE_PIPELINE,
-				     SCALER5_CTL2_ALPHA_MODE);
+	case VC4_GEN_5:
+	case VC4_GEN_6_C:
+		if (!state->fb->format->has_alpha)
+			return VC4_SET_FIELD(SCALER5_CTL2_ALPHA_MODE_FIXED,
+					     SCALER5_CTL2_ALPHA_MODE);
+
+		switch (state->pixel_blend_mode) {
+		case DRM_MODE_BLEND_PIXEL_NONE:
+			return VC4_SET_FIELD(SCALER5_CTL2_ALPHA_MODE_FIXED,
+					     SCALER5_CTL2_ALPHA_MODE);
+		default:
+		case DRM_MODE_BLEND_PREMULTI:
+			return VC4_SET_FIELD(SCALER5_CTL2_ALPHA_MODE_PIPELINE,
+					     SCALER5_CTL2_ALPHA_MODE) |
+				SCALER5_CTL2_ALPHA_PREMULT;
+		case DRM_MODE_BLEND_COVERAGE:
+			return VC4_SET_FIELD(SCALER5_CTL2_ALPHA_MODE_PIPELINE,
+					     SCALER5_CTL2_ALPHA_MODE);
+		}
+	case VC4_GEN_6_D:
+		/* 2712-D configures fixed alpha mode in CTL0 */
+		return state->pixel_blend_mode == DRM_MODE_BLEND_PREMULTI ?
+			SCALER5_CTL2_ALPHA_PREMULT : 0;
 	}
 }
 
+static u32 vc4_hvs6_get_alpha_mask_mode(struct drm_plane_state *state)
+{
+	struct drm_device *dev = state->state->dev;
+	struct vc4_dev *vc4 = to_vc4_dev(dev);
+
+	WARN_ON_ONCE(vc4->gen != VC4_GEN_6_C && vc4->gen != VC4_GEN_6_D);
+
+	if (vc4->gen == VC4_GEN_6_D &&
+	    (!state->fb->format->has_alpha ||
+	     state->pixel_blend_mode == DRM_MODE_BLEND_PIXEL_NONE))
+		return VC4_SET_FIELD(SCALER6D_CTL0_ALPHA_MASK_FIXED,
+				     SCALER6_CTL0_ALPHA_MASK);
+
+	return VC4_SET_FIELD(SCALER6_CTL0_ALPHA_MASK_NONE, SCALER6_CTL0_ALPHA_MASK);
+}
+
 /* Writes out a full display list for an active plane to the plane's
  * private dlist state.
  */
@@ -825,10 +1363,12 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
 	u32 v_subsample = fb->format->vsub;
 	bool mix_plane_alpha;
 	bool covers_screen;
-	u32 scl0, scl1, pitch0;
-	u32 tiling, src_y;
+	u32 scl0, scl1, pitch[2];
+	u32 tiling, src_x, src_y;
+	u32 width, height;
 	u32 hvs_format = format->hvs;
 	unsigned int rotation;
+	u32 offsets[3] = { 0 };
 	int ret, i;
 
 	if (vc4_state->dlist_initialized)
@@ -838,6 +1378,16 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
 	if (ret)
 		return ret;
 
+	if (!vc4_state->src_w[0] || !vc4_state->src_h[0] ||
+	    !vc4_state->crtc_w || !vc4_state->crtc_h) {
+		/* 0 source size probably means the plane is offscreen */
+		vc4_state->dlist_initialized = 1;
+		return 0;
+	}
+
+	width = vc4_state->src_w[0] >> 16;
+	height = vc4_state->src_h[0] >> 16;
+
 	/* SCL1 is used for Cb/Cr scaling of planar formats.  For RGB
 	 * and 4:4:4, scl1 should be set to scl0 so both channels of
 	 * the scaler do the same thing.  For YUV, the Y plane needs
@@ -858,26 +1408,23 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
 					 DRM_MODE_REFLECT_Y);
 
 	/* We must point to the last line when Y reflection is enabled. */
-	src_y = vc4_state->src_y;
+	src_y = vc4_state->src_y >> 16;
 	if (rotation & DRM_MODE_REFLECT_Y)
-		src_y += vc4_state->src_h[0] - 1;
+		src_y += height - 1;
+
+	src_x = vc4_state->src_x >> 16;
 
 	switch (base_format_mod) {
 	case DRM_FORMAT_MOD_LINEAR:
 		tiling = SCALER_CTL0_TILING_LINEAR;
-		pitch0 = VC4_SET_FIELD(fb->pitches[0], SCALER_SRC_PITCH);
+		pitch[0] = VC4_SET_FIELD(fb->pitches[0], SCALER_SRC_PITCH);
 
 		/* Adjust the base pointer to the first pixel to be scanned
 		 * out.
 		 */
 		for (i = 0; i < num_planes; i++) {
-			vc4_state->offsets[i] += src_y /
-						 (i ? v_subsample : 1) *
-						 fb->pitches[i];
-
-			vc4_state->offsets[i] += vc4_state->src_x /
-						 (i ? h_subsample : 1) *
-						 fb->format->cpp[i];
+			offsets[i] += src_y / (i ? v_subsample : 1) * fb->pitches[i];
+			offsets[i] += src_x / (i ? h_subsample : 1) * fb->format->cpp[i];
 		}
 
 		break;
@@ -898,7 +1445,7 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
 		 *	pitch * tile_h == tile_size * tiles_per_row
 		 */
 		u32 tiles_w = fb->pitches[0] >> (tile_size_shift - tile_h_shift);
-		u32 tiles_l = vc4_state->src_x >> tile_w_shift;
+		u32 tiles_l = src_x >> tile_w_shift;
 		u32 tiles_r = tiles_w - tiles_l;
 		u32 tiles_t = src_y >> tile_h_shift;
 		/* Intra-tile offsets, which modify the base address (the
@@ -908,7 +1455,7 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
 		u32 tile_y = (src_y >> 4) & 1;
 		u32 subtile_y = (src_y >> 2) & 3;
 		u32 utile_y = src_y & 3;
-		u32 x_off = vc4_state->src_x & tile_w_mask;
+		u32 x_off = src_x & tile_w_mask;
 		u32 y_off = src_y & tile_h_mask;
 
 		/* When Y reflection is requested we must set the
@@ -922,29 +1469,28 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
 		 */
 		if (rotation & DRM_MODE_REFLECT_Y) {
 			y_off = tile_h_mask - y_off;
-			pitch0 = SCALER_PITCH0_TILE_LINE_DIR;
+			pitch[0] = SCALER_PITCH0_TILE_LINE_DIR;
 		} else {
-			pitch0 = 0;
+			pitch[0] = 0;
 		}
 
 		tiling = SCALER_CTL0_TILING_256B_OR_T;
-		pitch0 |= (VC4_SET_FIELD(x_off, SCALER_PITCH0_SINK_PIX) |
-			   VC4_SET_FIELD(y_off, SCALER_PITCH0_TILE_Y_OFFSET) |
-			   VC4_SET_FIELD(tiles_l, SCALER_PITCH0_TILE_WIDTH_L) |
-			   VC4_SET_FIELD(tiles_r, SCALER_PITCH0_TILE_WIDTH_R));
-		vc4_state->offsets[0] += tiles_t * (tiles_w << tile_size_shift);
-		vc4_state->offsets[0] += subtile_y << 8;
-		vc4_state->offsets[0] += utile_y << 4;
+		pitch[0] |= (VC4_SET_FIELD(x_off, SCALER_PITCH0_SINK_PIX) |
+			     VC4_SET_FIELD(y_off, SCALER_PITCH0_TILE_Y_OFFSET) |
+			     VC4_SET_FIELD(tiles_l, SCALER_PITCH0_TILE_WIDTH_L) |
+			     VC4_SET_FIELD(tiles_r, SCALER_PITCH0_TILE_WIDTH_R));
+		offsets[0] += tiles_t * (tiles_w << tile_size_shift);
+		offsets[0] += subtile_y << 8;
+		offsets[0] += utile_y << 4;
 
 		/* Rows of tiles alternate left-to-right and right-to-left. */
 		if (tiles_t & 1) {
-			pitch0 |= SCALER_PITCH0_TILE_INITIAL_LINE_DIR;
-			vc4_state->offsets[0] += (tiles_w - tiles_l) <<
-						 tile_size_shift;
-			vc4_state->offsets[0] -= (1 + !tile_y) << 10;
+			pitch[0] |= SCALER_PITCH0_TILE_INITIAL_LINE_DIR;
+			offsets[0] += (tiles_w - tiles_l) << tile_size_shift;
+			offsets[0] -= (1 + !tile_y) << 10;
 		} else {
-			vc4_state->offsets[0] += tiles_l << tile_size_shift;
-			vc4_state->offsets[0] += tile_y << 10;
+			offsets[0] += tiles_l << tile_size_shift;
+			offsets[0] += tile_y << 10;
 		}
 
 		break;
@@ -954,6 +1500,7 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
 	case DRM_FORMAT_MOD_BROADCOM_SAND128:
 	case DRM_FORMAT_MOD_BROADCOM_SAND256: {
 		uint32_t param = fourcc_mod_broadcom_param(fb->modifier);
+		unsigned int tile_width = 0;
 
 		if (param > SCALER_TILE_HEIGHT_MASK) {
 			DRM_DEBUG_KMS("SAND height too large (%d)\n",
@@ -964,18 +1511,22 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
 		if (fb->format->format == DRM_FORMAT_P030) {
 			hvs_format = HVS_PIXEL_FORMAT_YCBCR_10BIT;
 			tiling = SCALER_CTL0_TILING_128B;
+			tile_width = 128;
 		} else {
 			hvs_format = HVS_PIXEL_FORMAT_H264;
 
 			switch (base_format_mod) {
 			case DRM_FORMAT_MOD_BROADCOM_SAND64:
 				tiling = SCALER_CTL0_TILING_64B;
+				tile_width = 64;
 				break;
 			case DRM_FORMAT_MOD_BROADCOM_SAND128:
 				tiling = SCALER_CTL0_TILING_128B;
+				tile_width = 128;
 				break;
 			case DRM_FORMAT_MOD_BROADCOM_SAND256:
 				tiling = SCALER_CTL0_TILING_256B_OR_T;
+				tile_width = 256;
 				break;
 			default:
 				return -EINVAL;
@@ -993,54 +1544,45 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
 		 * should be 6.
 		 */
 		for (i = 0; i < num_planes; i++) {
-			u32 tile_w, tile, x_off, pix_per_tile;
+			u32 tile, x_off, pix_per_tile;
 
-			if (fb->format->format == DRM_FORMAT_P030) {
-				/*
-				 * Spec says: bits [31:4] of the given address
-				 * should point to the 128-bit word containing
+			switch (param) {
+			case 0:
+				pitch[i] = fb->pitches[i];
+				break;
+			default:
+				pitch[i] = VC4_SET_FIELD(param, SCALER_TILE_HEIGHT);
+				break;
+			}
+
+			if (fb->format->format == DRM_FORMAT_P030) {
+				/*
+				 * Spec says: bits [31:4] of the given address
+				 * should point to the 128-bit word containing
 				 * the desired starting pixel, and bits[3:0]
 				 * should be between 0 and 11, indicating which
 				 * of the 12-pixels in that 128-bit word is the
 				 * first pixel to be used
 				 */
-				u32 remaining_pixels = vc4_state->src_x % 96;
+				u32 remaining_pixels = src_x % 96;
 				u32 aligned = remaining_pixels / 12;
 				u32 last_bits = remaining_pixels % 12;
 
 				x_off = aligned * 16 + last_bits;
-				tile_w = 128;
 				pix_per_tile = 96;
 			} else {
-				switch (base_format_mod) {
-				case DRM_FORMAT_MOD_BROADCOM_SAND64:
-					tile_w = 64;
-					break;
-				case DRM_FORMAT_MOD_BROADCOM_SAND128:
-					tile_w = 128;
-					break;
-				case DRM_FORMAT_MOD_BROADCOM_SAND256:
-					tile_w = 256;
-					break;
-				default:
-					return -EINVAL;
-				}
-				pix_per_tile = tile_w / fb->format->cpp[0];
-				x_off = (vc4_state->src_x % pix_per_tile) /
+				pix_per_tile = tile_width / fb->format->cpp[0];
+				x_off = (src_x % pix_per_tile) /
 					(i ? h_subsample : 1) *
 					fb->format->cpp[i];
 			}
 
-			tile = vc4_state->src_x / pix_per_tile;
+			tile = src_x / pix_per_tile;
 
-			vc4_state->offsets[i] += param * tile_w * tile;
-			vc4_state->offsets[i] += src_y /
-						 (i ? v_subsample : 1) *
-						 tile_w;
-			vc4_state->offsets[i] += x_off & ~(i ? 1 : 0);
+			offsets[i] += pitch[i] * tile * tile_width;
+			offsets[i] += src_y / (i ? v_subsample : 1) * tile_width;
+			offsets[i] += x_off & ~(i ? 1 : 0);
 		}
-
-		pitch0 = VC4_SET_FIELD(param, SCALER_TILE_HEIGHT);
 		break;
 	}
 
@@ -1050,6 +1592,30 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
 		return -EINVAL;
 	}
 
+	/* fetch an extra pixel if we don't actually line up with the left edge. */
+	if ((vc4_state->src_x & 0xffff) && vc4_state->src_x < (state->fb->width << 16))
+		width++;
+
+	/* same for the right side */
+	if (((vc4_state->src_x + vc4_state->src_w[0]) & 0xffff) &&
+	    vc4_state->src_x + vc4_state->src_w[0] < (state->fb->width << 16))
+		width++;
+
+	/* now for the top */
+	if ((vc4_state->src_y & 0xffff) && vc4_state->src_y < (state->fb->height << 16))
+		height++;
+
+	/* and the bottom */
+	if (((vc4_state->src_y + vc4_state->src_h[0]) & 0xffff) &&
+	    vc4_state->src_y + vc4_state->src_h[0] < (state->fb->height << 16))
+		height++;
+
+	/* For YUV444 the hardware wants double the width, otherwise it doesn't
+	 * fetch full width of chroma
+	 */
+	if (format->drm == DRM_FORMAT_YUV444 || format->drm == DRM_FORMAT_YVU444)
+		width <<= 1;
+
 	/* Don't waste cycles mixing with plane alpha if the set alpha
 	 * is opaque or there is no per-pixel alpha information.
 	 * In any case we use the alpha property value as the fixed alpha.
@@ -1092,10 +1658,8 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
 		vc4_dlist_write(vc4_state,
 				(mix_plane_alpha ? SCALER_POS2_ALPHA_MIX : 0) |
 				vc4_hvs4_get_alpha_blend_mode(state) |
-				VC4_SET_FIELD(vc4_state->src_w[0],
-					      SCALER_POS2_WIDTH) |
-				VC4_SET_FIELD(vc4_state->src_h[0],
-					      SCALER_POS2_HEIGHT));
+				VC4_SET_FIELD(width, SCALER_POS2_WIDTH) |
+				VC4_SET_FIELD(height, SCALER_POS2_HEIGHT));
 
 		/* Position Word 3: Context.  Written by the HVS. */
 		vc4_dlist_write(vc4_state, 0xc0c0c0c0);
@@ -1148,10 +1712,8 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
 		/* Position Word 2: Source Image Size */
 		vc4_state->pos2_offset = vc4_state->dlist_count;
 		vc4_dlist_write(vc4_state,
-				VC4_SET_FIELD(vc4_state->src_w[0],
-					      SCALER5_POS2_WIDTH) |
-				VC4_SET_FIELD(vc4_state->src_h[0],
-					      SCALER5_POS2_HEIGHT));
+				VC4_SET_FIELD(width, SCALER5_POS2_WIDTH) |
+				VC4_SET_FIELD(height, SCALER5_POS2_HEIGHT));
 
 		/* Position Word 3: Context.  Written by the HVS. */
 		vc4_dlist_write(vc4_state, 0xc0c0c0c0);
@@ -1162,16 +1724,20 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
 	 *
 	 * The pointers may be any byte address.
 	 */
-	vc4_state->ptr0_offset = vc4_state->dlist_count;
-	for (i = 0; i < num_planes; i++)
-		vc4_dlist_write(vc4_state, vc4_state->offsets[i]);
+	vc4_state->ptr0_offset[0] = vc4_state->dlist_count;
+
+	for (i = 0; i < num_planes; i++) {
+		struct drm_gem_dma_object *bo = drm_fb_dma_get_gem_obj(fb, i);
+
+		vc4_dlist_write(vc4_state, bo->dma_addr + fb->offsets[i] + offsets[i]);
+	}
 
 	/* Pointer Context Word 0/1/2: Written by the HVS */
 	for (i = 0; i < num_planes; i++)
 		vc4_dlist_write(vc4_state, 0xc0c0c0c0);
 
 	/* Pitch word 0 */
-	vc4_dlist_write(vc4_state, pitch0);
+	vc4_dlist_write(vc4_state, pitch[0]);
 
 	/* Pitch word 1/2 */
 	for (i = 1; i < num_planes; i++) {
@@ -1181,7 +1747,7 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
 					VC4_SET_FIELD(fb->pitches[i],
 						      SCALER_SRC_PITCH));
 		} else {
-			vc4_dlist_write(vc4_state, pitch0);
+			vc4_dlist_write(vc4_state, pitch[1]);
 		}
 	}
 
@@ -1234,7 +1800,18 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
 		    vc4_state->y_scaling[0] == VC4_SCALING_PPF ||
 		    vc4_state->x_scaling[1] == VC4_SCALING_PPF ||
 		    vc4_state->y_scaling[1] == VC4_SCALING_PPF) {
-			u32 kernel = VC4_SET_FIELD(vc4->hvs->mitchell_netravali_filter.start,
+			struct drm_mm_node *filter;
+
+			switch (state->scaling_filter) {
+			case DRM_SCALING_FILTER_DEFAULT:
+			default:
+				filter = &vc4->hvs->mitchell_netravali_filter;
+				break;
+			case DRM_SCALING_FILTER_NEAREST_NEIGHBOR:
+				filter = &vc4->hvs->nearest_neighbour_filter;
+				break;
+			}
+			u32 kernel = VC4_SET_FIELD(filter->start,
 						   SCALER_PPF_KERNEL_OFFSET);
 
 			/* HPPF plane 0 */
@@ -1274,6 +1851,482 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
 	return 0;
 }
 
+static u32 vc6_plane_get_csc_mode(struct vc4_plane_state *vc4_state)
+{
+	struct drm_plane_state *state = &vc4_state->base;
+	struct vc4_dev *vc4 = to_vc4_dev(state->plane->dev);
+	u32 ret = 0;
+
+	if (vc4_state->is_yuv) {
+		enum drm_color_encoding color_encoding = state->color_encoding;
+		enum drm_color_range color_range = state->color_range;
+
+		/* CSC pre-loaded with:
+		 * 0 = BT601 limited range
+		 * 1 = BT709 limited range
+		 * 2 = BT2020 limited range
+		 * 3 = BT601 full range
+		 * 4 = BT709 full range
+		 * 5 = BT2020 full range
+		 */
+		if (color_encoding > DRM_COLOR_YCBCR_BT2020)
+			color_encoding = DRM_COLOR_YCBCR_BT601;
+		if (color_range > DRM_COLOR_YCBCR_FULL_RANGE)
+			color_range = DRM_COLOR_YCBCR_LIMITED_RANGE;
+
+		if (vc4->gen == VC4_GEN_6_C) {
+			ret |= SCALER6C_CTL2_CSC_ENABLE;
+			ret |= VC4_SET_FIELD(color_encoding + (color_range * 3),
+					     SCALER6C_CTL2_BRCM_CFC_CONTROL);
+		} else {
+			ret |= SCALER6D_CTL2_CSC_ENABLE;
+			ret |= VC4_SET_FIELD(color_encoding + (color_range * 3),
+					     SCALER6D_CTL2_BRCM_CFC_CONTROL);
+		}
+	}
+
+	return ret;
+}
+
+static int vc6_get_plane_idx(const struct hvs_format *format, int plane)
+{
+	if (!plane || !format->hvs6_swap_chroma_pointers)
+		return plane;
+
+	return (plane == 1) ? 2 : 1;
+}
+
+static int vc6_plane_mode_set(struct drm_plane *plane,
+			      struct drm_plane_state *state)
+{
+	struct drm_device *drm = plane->dev;
+	struct vc4_dev *vc4 = to_vc4_dev(drm);
+	struct vc4_plane_state *vc4_state = to_vc4_plane_state(state);
+	struct drm_framebuffer *fb = state->fb;
+	const struct hvs_format *format = vc4_get_hvs_format(fb->format->format);
+	u64 base_format_mod = fourcc_mod_broadcom_mod(fb->modifier);
+	int num_planes = fb->format->num_planes;
+	u32 h_subsample = fb->format->hsub;
+	u32 v_subsample = fb->format->vsub;
+	bool mix_plane_alpha;
+	bool covers_screen;
+	u32 scl0, scl1, pitch[2];
+	u32 tiling, src_x, src_y;
+	u32 width, height;
+	u32 hvs_format = format->hvs;
+	u32 offsets[3] = { 0 };
+	unsigned int rotation;
+	int ret, i;
+
+	if (vc4_state->dlist_initialized)
+		return 0;
+
+	ret = vc4_plane_setup_clipping_and_scaling(state);
+	if (ret)
+		return ret;
+
+	if (state->fb->format->format == DRM_FORMAT_YUV444 ||
+	    state->fb->format->format == DRM_FORMAT_YVU444) {
+		/* Similar to YUV422 requiring the chroma scaler to always be
+		 * enabled that is handled in vc4_plane_setup_clipping_and_scaling,
+		 * GEN6 requires the scaler for the luma channel to be enabled
+		 * for YUV444.
+		 */
+		if (vc4_state->x_scaling[0] == VC4_SCALING_NONE) {
+			vc4_state->x_scaling[0] = VC4_SCALING_PPF;
+			vc4_state->is_unity = false;
+			vc4_state->is_yuv444_unity = true;
+		}
+
+		if (vc4_state->y_scaling[0] == VC4_SCALING_NONE) {
+			vc4_state->y_scaling[0] = VC4_SCALING_PPF;
+			vc4_state->is_unity = false;
+		} else {
+			/* Ensure that resizing vertically but not horizontally
+			 * doesn't switch the scaling filter.
+			 */
+			vc4_state->is_yuv444_unity = false;
+		}
+
+	}
+
+	if (!vc4_state->src_w[0] || !vc4_state->src_h[0] ||
+	    !vc4_state->crtc_w || !vc4_state->crtc_h) {
+		/* 0 source size probably means the plane is offscreen.
+		 * 0 destination size is a redundant plane.
+		 */
+		vc4_state->dlist_initialized = 1;
+		return 0;
+	}
+
+	width = vc4_state->src_w[0] >> 16;
+	height = vc4_state->src_h[0] >> 16;
+
+	/* SCL1 is used for Cb/Cr scaling of planar formats.  For RGB
+	 * and 4:4:4, scl1 should be set to scl0 so both channels of
+	 * the scaler do the same thing.  For YUV, the Y plane needs
+	 * to be put in channel 1 and Cb/Cr in channel 0, so we swap
+	 * the scl fields here.
+	 */
+	if (num_planes == 1) {
+		scl0 = vc4_get_scl_field(state, 0);
+		scl1 = scl0;
+	} else {
+		scl0 = vc4_get_scl_field(state, 1);
+		scl1 = vc4_get_scl_field(state, 0);
+	}
+
+	rotation = drm_rotation_simplify(state->rotation,
+					 DRM_MODE_ROTATE_0 |
+					 DRM_MODE_REFLECT_X |
+					 DRM_MODE_REFLECT_Y);
+
+	/* We must point to the last line when Y reflection is enabled. */
+	src_y = vc4_state->src_y >> 16;
+	if (rotation & DRM_MODE_REFLECT_Y)
+		src_y += height - 1;
+
+	src_x = vc4_state->src_x >> 16;
+
+	/* fetch an extra pixel if we don't actually line up with the left edge. */
+	if ((vc4_state->src_x & 0xffff) && vc4_state->src_x < (state->fb->width << 16))
+		width++;
+
+	/* same for the right side */
+	if (((vc4_state->src_x + vc4_state->src_w[0]) & 0xffff) &&
+	    vc4_state->src_x + vc4_state->src_w[0] < (state->fb->width << 16))
+		width++;
+
+	/* now for the top */
+	if ((vc4_state->src_y & 0xffff) && vc4_state->src_y < (state->fb->height << 16))
+		height++;
+
+	/* and the bottom */
+	if (((vc4_state->src_y + vc4_state->src_h[0]) & 0xffff) &&
+	    vc4_state->src_y + vc4_state->src_h[0] < (state->fb->height << 16))
+		height++;
+
+	switch (base_format_mod) {
+	case DRM_FORMAT_MOD_LINEAR:
+		tiling = SCALER6_CTL0_ADDR_MODE_LINEAR;
+
+		/* Adjust the base pointer to the first pixel to be scanned
+		 * out.
+		 */
+		for (i = 0; i < num_planes; i++) {
+			offsets[i] += src_y / (i ? v_subsample : 1) * fb->pitches[i];
+			offsets[i] += src_x / (i ? h_subsample : 1) * fb->format->cpp[i];
+		}
+
+		break;
+
+	case DRM_FORMAT_MOD_BROADCOM_SAND128:
+	case DRM_FORMAT_MOD_BROADCOM_SAND256: {
+		uint32_t param = fourcc_mod_broadcom_param(fb->modifier);
+		unsigned int tile_width = 0;
+		u32 components_per_word;
+		u32 starting_offset;
+		u32 fetch_count;
+
+		if (param > SCALER_TILE_HEIGHT_MASK) {
+			DRM_DEBUG_KMS("SAND height too large (%d)\n",
+				      param);
+			return -EINVAL;
+		}
+
+		if (fb->format->format == DRM_FORMAT_P030) {
+			hvs_format = HVS_PIXEL_FORMAT_YCBCR_10BIT;
+			tiling = SCALER6_CTL0_ADDR_MODE_128B;
+			tile_width = 128;
+		} else {
+			hvs_format = HVS_PIXEL_FORMAT_YCBCR_YUV420_2PLANE;
+
+			switch (base_format_mod) {
+			case DRM_FORMAT_MOD_BROADCOM_SAND128:
+				tiling = SCALER6_CTL0_ADDR_MODE_128B;
+				tile_width = 128;
+				break;
+			case DRM_FORMAT_MOD_BROADCOM_SAND256:
+				tiling = SCALER6_CTL0_ADDR_MODE_256B;
+				tile_width = 256;
+				break;
+			default:
+				return -EINVAL;
+			}
+		}
+
+		components_per_word = fb->format->format == DRM_FORMAT_P030 ? 24 : 32;
+		starting_offset = src_x % components_per_word;
+		fetch_count = (width + starting_offset + components_per_word - 1) /
+			components_per_word;
+
+		/* Adjust the base pointer to the first pixel to be scanned
+		 * out.
+		 *
+		 * For P030, y_ptr [31:4] is the 128bit word for the start pixel
+		 * y_ptr [3:0] is the pixel (0-11) contained within that 128bit
+		 * word that should be taken as the first pixel.
+		 * Ditto uv_ptr [31:4] vs [3:0], however [3:0] contains the
+		 * element within the 128bit word, eg for pixel 3 the value
+		 * should be 6.
+		 */
+		for (i = 0; i < num_planes; i++) {
+			u32 tile, x_off, pix_per_tile;
+
+			switch (param) {
+			case 0:
+				pitch[i] = fb->pitches[i];
+				break;
+			default:
+				pitch[i] = VC4_SET_FIELD(param, SCALER_TILE_HEIGHT);
+				break;
+			}
+
+			if (fb->format->format == DRM_FORMAT_P030) {
+				/*
+				 * Spec says: bits [31:5] of the given address
+				 * should point to the 256-bit word containing
+				 * the desired starting pixel, and bits[4:0]
+				 * should be between 0 and 23, indicating which
+				 * of the 24-pixels in that 256-bit word is the
+				 * first pixel to be used
+				 */
+				u32 remaining_pixels = src_x % 96;
+				u32 aligned = remaining_pixels / 24;
+				u32 last_bits = remaining_pixels % 24;
+
+				x_off = aligned * 32 + last_bits;
+				pix_per_tile = 96;
+			} else {
+				pix_per_tile = tile_width / fb->format->cpp[0];
+				x_off = (src_x % pix_per_tile) /
+					(i ? h_subsample : 1) *
+					fb->format->cpp[i];
+			}
+
+			tile = src_x / pix_per_tile;
+
+			offsets[i] += pitch[i] * tile * tile_width;
+			offsets[i] += src_y / (i ? v_subsample : 1) * tile_width;
+			offsets[i] += x_off & ~(i ? 1 : 0);
+
+			/*
+			 * Finished using the pitch as a pitch, so pack it as the
+			 * register value.
+			 */
+			pitch[i] = VC4_SET_FIELD(pitch[i], SCALER6_PTR2_PITCH) |
+				   VC4_SET_FIELD(fetch_count - 1, SCALER6_PTR2_FETCH_COUNT);
+		}
+
+		break;
+	}
+
+	default:
+		DRM_DEBUG_KMS("Unsupported FB tiling flag 0x%16llx",
+			      (long long)fb->modifier);
+		return -EINVAL;
+	}
+
+	/* for YUV444 hardware wants double the width, otherwise it doesn't
+	 * fetch full width of chroma
+	 */
+	if (format->drm == DRM_FORMAT_YUV444 || format->drm == DRM_FORMAT_YVU444)
+		width <<= 1;
+
+	/* Don't waste cycles mixing with plane alpha if the set alpha
+	 * is opaque or there is no per-pixel alpha information.
+	 * In any case we use the alpha property value as the fixed alpha.
+	 */
+	mix_plane_alpha = state->alpha != DRM_BLEND_ALPHA_OPAQUE &&
+			  fb->format->has_alpha;
+
+	/* Control Word 0: Scaling Configuration & Element Validity*/
+	vc4_dlist_write(vc4_state,
+			SCALER6_CTL0_VALID |
+			VC4_SET_FIELD(tiling, SCALER6_CTL0_ADDR_MODE) |
+			vc4_hvs6_get_alpha_mask_mode(state) |
+			(vc4_state->is_unity ? SCALER6_CTL0_UNITY : 0) |
+			VC4_SET_FIELD(format->pixel_order_hvs5, SCALER6_CTL0_ORDERRGBA) |
+			VC4_SET_FIELD(scl1, SCALER6_CTL0_SCL1_MODE) |
+			VC4_SET_FIELD(scl0, SCALER6_CTL0_SCL0_MODE) |
+			VC4_SET_FIELD(hvs_format, SCALER6_CTL0_PIXEL_FORMAT));
+
+	/* Position Word 0: Image Position */
+	vc4_state->pos0_offset = vc4_state->dlist_count;
+	vc4_dlist_write(vc4_state,
+			VC4_SET_FIELD(vc4_state->crtc_y, SCALER6_POS0_START_Y) |
+			(rotation & DRM_MODE_REFLECT_X ? SCALER6_POS0_HFLIP : 0) |
+			VC4_SET_FIELD(vc4_state->crtc_x, SCALER6_POS0_START_X));
+
+	/* Control Word 2: Alpha Value & CSC */
+	vc4_dlist_write(vc4_state,
+			vc6_plane_get_csc_mode(vc4_state) |
+			vc4_hvs5_get_alpha_blend_mode(state) |
+			(mix_plane_alpha ? SCALER6_CTL2_ALPHA_MIX : 0) |
+			VC4_SET_FIELD(state->alpha >> 4, SCALER5_CTL2_ALPHA));
+
+	/* Position Word 1: Scaled Image Dimensions */
+	if (!vc4_state->is_unity)
+		vc4_dlist_write(vc4_state,
+				VC4_SET_FIELD(vc4_state->crtc_h - 1,
+					      SCALER6_POS1_SCL_LINES) |
+				VC4_SET_FIELD(vc4_state->crtc_w - 1,
+					      SCALER6_POS1_SCL_WIDTH));
+
+	/* Position Word 2: Source Image Size */
+	vc4_state->pos2_offset = vc4_state->dlist_count;
+	vc4_dlist_write(vc4_state,
+			VC4_SET_FIELD(height - 1,
+				      SCALER6_POS2_SRC_LINES) |
+			VC4_SET_FIELD(width - 1,
+				      SCALER6_POS2_SRC_WIDTH));
+
+	/* Position Word 3: Context */
+	vc4_dlist_write(vc4_state, 0xc0c0c0c0);
+
+	/*
+	 * TODO: This only covers Raster Scan Order planes
+	 */
+	for (i = 0; i < num_planes; i++) {
+		int idx = vc6_get_plane_idx(format, i);
+		struct drm_gem_dma_object *bo =
+			drm_fb_dma_get_gem_obj(fb, idx);
+		dma_addr_t paddr = bo->dma_addr + fb->offsets[idx] + offsets[idx];
+
+		/* Pointer Word 0 */
+		vc4_state->ptr0_offset[i] = vc4_state->dlist_count;
+		vc4_dlist_write(vc4_state,
+				(rotation & DRM_MODE_REFLECT_Y ? SCALER6_PTR0_VFLIP : 0) |
+				/*
+				 * The UPM buffer will be allocated in
+				 * vc6_plane_allocate_upm().
+				 */
+				VC4_SET_FIELD(upper_32_bits(paddr) & 0xff,
+					      SCALER6_PTR0_UPPER_ADDR));
+
+		/* Pointer Word 1 */
+		vc4_dlist_write(vc4_state, lower_32_bits(paddr));
+
+		/* Pointer Word 2 */
+		if (base_format_mod != DRM_FORMAT_MOD_BROADCOM_SAND128 &&
+		    base_format_mod != DRM_FORMAT_MOD_BROADCOM_SAND256) {
+			vc4_dlist_write(vc4_state,
+					VC4_SET_FIELD(fb->pitches[i],
+						      SCALER6_PTR2_PITCH));
+		} else {
+			vc4_dlist_write(vc4_state, pitch[i]);
+		}
+	}
+
+	/*
+	 * Palette Word 0
+	 * TODO: We're not using the palette mode
+	 */
+
+	/*
+	 * Trans Word 0
+	 * TODO: It's only relevant if we set the trans_rgb bit in the
+	 * control word 0, and we don't at the moment.
+	 */
+
+	vc4_state->lbm_offset = 0;
+
+	if (!vc4_state->is_unity || fb->format->is_yuv) {
+		/*
+		 * Reserve a slot for the LBM Base Address. The real value will
+		 * be set when calling vc4_plane_allocate_lbm().
+		 */
+		if (vc4_state->y_scaling[0] != VC4_SCALING_NONE ||
+		    vc4_state->y_scaling[1] != VC4_SCALING_NONE) {
+			vc4_state->lbm_offset = vc4_state->dlist_count;
+			vc4_dlist_counter_increment(vc4_state);
+		}
+
+		if (vc4_state->x_scaling[0] != VC4_SCALING_NONE ||
+		    vc4_state->x_scaling[1] != VC4_SCALING_NONE ||
+		    vc4_state->y_scaling[0] != VC4_SCALING_NONE ||
+		    vc4_state->y_scaling[1] != VC4_SCALING_NONE) {
+			if (num_planes > 1)
+				/*
+				 * Emit Cb/Cr as channel 0 and Y as channel
+				 * 1. This matches how we set up scl0/scl1
+				 * above.
+				 */
+				vc4_write_scaling_parameters(state, 1);
+
+			vc4_write_scaling_parameters(state, 0);
+		}
+
+		/*
+		 * If any PPF setup was done, then all the kernel
+		 * pointers get uploaded.
+		 */
+		if (vc4_state->x_scaling[0] == VC4_SCALING_PPF ||
+		    vc4_state->y_scaling[0] == VC4_SCALING_PPF ||
+		    vc4_state->x_scaling[1] == VC4_SCALING_PPF ||
+		    vc4_state->y_scaling[1] == VC4_SCALING_PPF) {
+			struct drm_mm_node *filter;
+
+			switch (state->scaling_filter) {
+			case DRM_SCALING_FILTER_DEFAULT:
+			default:
+				filter = &vc4->hvs->mitchell_netravali_filter;
+				break;
+			case DRM_SCALING_FILTER_NEAREST_NEIGHBOR:
+				filter = &vc4->hvs->nearest_neighbour_filter;
+				break;
+			}
+			if (vc4_state->is_yuv444_unity)
+				filter = &vc4->hvs->nearest_neighbour_filter;
+
+			u32 kernel = VC4_SET_FIELD(filter->start,
+						   SCALER_PPF_KERNEL_OFFSET);
+
+			/* HPPF plane 0 */
+			vc4_dlist_write(vc4_state, kernel);
+			/* VPPF plane 0 */
+			vc4_dlist_write(vc4_state, kernel);
+			/* HPPF plane 1 */
+			vc4_dlist_write(vc4_state, kernel);
+			/* VPPF plane 1 */
+				vc4_dlist_write(vc4_state, kernel);
+		}
+	}
+
+	vc4_dlist_write(vc4_state, SCALER6_CTL0_END);
+
+	vc4_state->dlist[0] |=
+		VC4_SET_FIELD(vc4_state->dlist_count, SCALER6_CTL0_NEXT);
+
+	/* crtc_* are already clipped coordinates. */
+	covers_screen = vc4_state->crtc_x == 0 && vc4_state->crtc_y == 0 &&
+			vc4_state->crtc_w == state->crtc->mode.hdisplay &&
+			vc4_state->crtc_h == state->crtc->mode.vdisplay;
+
+	/*
+	 * Background fill might be necessary when the plane has per-pixel
+	 * alpha content or a non-opaque plane alpha and could blend from the
+	 * background or does not cover the entire screen.
+	 */
+	vc4_state->needs_bg_fill = fb->format->has_alpha || !covers_screen ||
+				   state->alpha != DRM_BLEND_ALPHA_OPAQUE;
+
+	/*
+	 * Flag the dlist as initialized to avoid checking it twice in case
+	 * the async update check already called vc4_plane_mode_set() and
+	 * decided to fallback to sync update because async update was not
+	 * possible.
+	 */
+	vc4_state->dlist_initialized = 1;
+
+	vc4_plane_calc_load(state);
+
+	drm_dbg_driver(drm, "[PLANE:%d:%s] Computed DLIST size: %u\n",
+		       plane->base.id, plane->name, vc4_state->dlist_count);
+
+	return 0;
+}
+
 /* If a modeset involves changing the setup of a plane, the atomic
  * infrastructure will call this to validate a proposed plane setup.
  * However, if a plane isn't getting updated, this (and the
@@ -1281,9 +2334,10 @@ static int vc4_plane_mode_set(struct drm_plane *plane,
  * compute the dlist here and have all active plane dlists get updated
  * in the CRTC's flush.
  */
-static int vc4_plane_atomic_check(struct drm_plane *plane,
-				  struct drm_atomic_state *state)
+int vc4_plane_atomic_check(struct drm_plane *plane,
+			   struct drm_atomic_state *state)
 {
+	struct vc4_dev *vc4 = to_vc4_dev(plane->dev);
 	struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
 										 plane);
 	struct vc4_plane_state *vc4_state = to_vc4_plane_state(new_plane_state);
@@ -1291,14 +2345,40 @@ static int vc4_plane_atomic_check(struct drm_plane *plane,
 
 	vc4_state->dlist_count = 0;
 
-	if (!plane_enabled(new_plane_state))
+	if (!plane_enabled(new_plane_state)) {
+		struct drm_plane_state *old_plane_state =
+				drm_atomic_get_old_plane_state(state, plane);
+
+		if (old_plane_state && plane_enabled(old_plane_state)) {
+			if (vc4->gen >= VC4_GEN_6_C)
+				vc6_plane_free_upm(new_plane_state);
+			vc4_plane_free_lbm(new_plane_state);
+		}
+		return 0;
+	}
+
+	if (vc4->gen >= VC4_GEN_6_C)
+		ret = vc6_plane_mode_set(plane, new_plane_state);
+	else
+		ret = vc4_plane_mode_set(plane, new_plane_state);
+	if (ret)
+		return ret;
+
+	if (!vc4_state->src_w[0] || !vc4_state->src_h[0] ||
+	    !vc4_state->crtc_w || !vc4_state->crtc_h)
 		return 0;
 
-	ret = vc4_plane_mode_set(plane, new_plane_state);
+	ret = vc4_plane_allocate_lbm(new_plane_state);
 	if (ret)
 		return ret;
 
-	return vc4_plane_allocate_lbm(new_plane_state);
+	if (vc4->gen >= VC4_GEN_6_C) {
+		ret = vc6_plane_allocate_upm(new_plane_state);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
 }
 
 static void vc4_plane_atomic_update(struct drm_plane *plane,
@@ -1344,31 +2424,62 @@ u32 vc4_plane_dlist_size(const struct drm_plane_state *state)
  */
 void vc4_plane_async_set_fb(struct drm_plane *plane, struct drm_framebuffer *fb)
 {
-	struct vc4_plane_state *vc4_state = to_vc4_plane_state(plane->state);
+	struct drm_plane_state *state = plane->state;
+	struct vc4_plane_state *vc4_state = to_vc4_plane_state(state);
 	struct drm_gem_dma_object *bo = drm_fb_dma_get_gem_obj(fb, 0);
-	uint32_t addr;
+	struct vc4_dev *vc4 = to_vc4_dev(plane->dev);
+	dma_addr_t dma_addr = bo->dma_addr + fb->offsets[0];
+	unsigned int rotation;
 	int idx;
 
 	if (!drm_dev_enter(plane->dev, &idx))
 		return;
 
+	rotation = drm_rotation_simplify(state->rotation,
+					 DRM_MODE_ROTATE_0 |
+					 DRM_MODE_REFLECT_X |
+					 DRM_MODE_REFLECT_Y);
+
+	/* We must point to the last line when Y reflection is enabled. */
+	if (rotation & DRM_MODE_REFLECT_Y)
+		dma_addr += fb->pitches[0] * ((vc4_state->src_h[0] >> 16) - 1);
+
 	/* We're skipping the address adjustment for negative origin,
 	 * because this is only called on the primary plane.
 	 */
 	WARN_ON_ONCE(plane->state->crtc_x < 0 || plane->state->crtc_y < 0);
-	addr = bo->dma_addr + fb->offsets[0];
 
-	/* Write the new address into the hardware immediately.  The
-	 * scanout will start from this address as soon as the FIFO
-	 * needs to refill with pixels.
-	 */
-	writel(addr, &vc4_state->hw_dlist[vc4_state->ptr0_offset]);
+	if (vc4->gen >= VC4_GEN_6_C) {
+		u32 value;
 
-	/* Also update the CPU-side dlist copy, so that any later
-	 * atomic updates that don't do a new modeset on our plane
-	 * also use our updated address.
-	 */
-	vc4_state->dlist[vc4_state->ptr0_offset] = addr;
+		value = vc4_state->dlist[vc4_state->ptr0_offset[0]] &
+					~SCALER6_PTR0_UPPER_ADDR_MASK;
+		value |= VC4_SET_FIELD(upper_32_bits(dma_addr) & 0xff,
+				       SCALER6_PTR0_UPPER_ADDR);
+
+		writel(value, &vc4_state->hw_dlist[vc4_state->ptr0_offset[0]]);
+		vc4_state->dlist[vc4_state->ptr0_offset[0]] = value;
+
+		value = lower_32_bits(dma_addr);
+		writel(value, &vc4_state->hw_dlist[vc4_state->ptr0_offset[0] + 1]);
+		vc4_state->dlist[vc4_state->ptr0_offset[0] + 1] = value;
+	} else {
+		u32 addr;
+
+		addr = (u32)dma_addr;
+
+		/* Write the new address into the hardware immediately.  The
+		 * scanout will start from this address as soon as the FIFO
+		 * needs to refill with pixels.
+		 */
+		writel(addr, &vc4_state->hw_dlist[vc4_state->ptr0_offset[0]]);
+
+		/* Also update the CPU-side dlist copy, so that any later
+		 * atomic updates that don't do a new modeset on our plane
+		 * also use our updated address.
+		 */
+		vc4_state->dlist[vc4_state->ptr0_offset[0]] = addr;
+	}
 
 	drm_dev_exit(idx);
 }
@@ -1423,8 +2534,6 @@ static void vc4_plane_atomic_async_update(struct drm_plane *plane,
 	       sizeof(vc4_state->y_scaling));
 	vc4_state->is_unity = new_vc4_state->is_unity;
 	vc4_state->is_yuv = new_vc4_state->is_yuv;
-	memcpy(vc4_state->offsets, new_vc4_state->offsets,
-	       sizeof(vc4_state->offsets));
 	vc4_state->needs_bg_fill = new_vc4_state->needs_bg_fill;
 
 	/* Update the current vc4_state pos0, pos2 and ptr0 dlist entries. */
@@ -1432,8 +2541,8 @@ static void vc4_plane_atomic_async_update(struct drm_plane *plane,
 		new_vc4_state->dlist[vc4_state->pos0_offset];
 	vc4_state->dlist[vc4_state->pos2_offset] =
 		new_vc4_state->dlist[vc4_state->pos2_offset];
-	vc4_state->dlist[vc4_state->ptr0_offset] =
-		new_vc4_state->dlist[vc4_state->ptr0_offset];
+	vc4_state->dlist[vc4_state->ptr0_offset[0]] =
+		new_vc4_state->dlist[vc4_state->ptr0_offset[0]];
 
 	/* Note that we can't just call vc4_plane_write_dlist()
 	 * because that would smash the context data that the HVS is
@@ -1443,8 +2552,8 @@ static void vc4_plane_atomic_async_update(struct drm_plane *plane,
 	       &vc4_state->hw_dlist[vc4_state->pos0_offset]);
 	writel(vc4_state->dlist[vc4_state->pos2_offset],
 	       &vc4_state->hw_dlist[vc4_state->pos2_offset]);
-	writel(vc4_state->dlist[vc4_state->ptr0_offset],
-	       &vc4_state->hw_dlist[vc4_state->ptr0_offset]);
+	writel(vc4_state->dlist[vc4_state->ptr0_offset[0]],
+	       &vc4_state->hw_dlist[vc4_state->ptr0_offset[0]]);
 
 	drm_dev_exit(idx);
 }
@@ -1452,13 +2561,17 @@ static void vc4_plane_atomic_async_update(struct drm_plane *plane,
 static int vc4_plane_atomic_async_check(struct drm_plane *plane,
 					struct drm_atomic_state *state)
 {
+	struct vc4_dev *vc4 = to_vc4_dev(plane->dev);
 	struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
 										 plane);
 	struct vc4_plane_state *old_vc4_state, *new_vc4_state;
 	int ret;
 	u32 i;
 
-	ret = vc4_plane_mode_set(plane, new_plane_state);
+	if (vc4->gen <= VC4_GEN_5)
+		ret = vc4_plane_mode_set(plane, new_plane_state);
+	else
+		ret = vc6_plane_mode_set(plane, new_plane_state);
 	if (ret)
 		return ret;
 
@@ -1471,7 +2584,7 @@ static int vc4_plane_atomic_async_check(struct drm_plane *plane,
 	if (old_vc4_state->dlist_count != new_vc4_state->dlist_count ||
 	    old_vc4_state->pos0_offset != new_vc4_state->pos0_offset ||
 	    old_vc4_state->pos2_offset != new_vc4_state->pos2_offset ||
-	    old_vc4_state->ptr0_offset != new_vc4_state->ptr0_offset ||
+	    old_vc4_state->ptr0_offset[0] != new_vc4_state->ptr0_offset[0] ||
 	    vc4_lbm_size(plane->state) != vc4_lbm_size(new_plane_state))
 		return -EINVAL;
 
@@ -1481,7 +2594,7 @@ static int vc4_plane_atomic_async_check(struct drm_plane *plane,
 	for (i = 0; i < new_vc4_state->dlist_count; i++) {
 		if (i == new_vc4_state->pos0_offset ||
 		    i == new_vc4_state->pos2_offset ||
-		    i == new_vc4_state->ptr0_offset ||
+		    i == new_vc4_state->ptr0_offset[0] ||
 		    (new_vc4_state->lbm_offset &&
 		     i == new_vc4_state->lbm_offset))
 			continue;
@@ -1598,6 +2711,12 @@ static bool vc4_format_mod_supported(struct drm_plane *plane,
 	case DRM_FORMAT_YVU420:
 	case DRM_FORMAT_NV16:
 	case DRM_FORMAT_NV61:
+	case DRM_FORMAT_P010:
+	case DRM_FORMAT_P012:
+	case DRM_FORMAT_P016:
+	case DRM_FORMAT_S010:
+	case DRM_FORMAT_S012:
+	case DRM_FORMAT_S016:
 	default:
 		return (modifier == DRM_FORMAT_MOD_LINEAR);
 	}
@@ -1624,18 +2743,21 @@ struct drm_plane *vc4_plane_init(struct drm_device *dev,
 	unsigned i;
 	static const uint64_t modifiers[] = {
 		DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED,
-		DRM_FORMAT_MOD_BROADCOM_SAND128,
-		DRM_FORMAT_MOD_BROADCOM_SAND64,
-		DRM_FORMAT_MOD_BROADCOM_SAND256,
+		DRM_FORMAT_MOD_BROADCOM_SAND128_COL_HEIGHT(0),
+		DRM_FORMAT_MOD_BROADCOM_SAND64_COL_HEIGHT(0),
+		DRM_FORMAT_MOD_BROADCOM_SAND256_COL_HEIGHT(0),
 		DRM_FORMAT_MOD_LINEAR,
 		DRM_FORMAT_MOD_INVALID
 	};
 
 	for (i = 0; i < ARRAY_SIZE(hvs_formats); i++) {
-		if (!hvs_formats[i].hvs5_only || vc4->gen == VC4_GEN_5) {
-			formats[num_formats] = hvs_formats[i].drm;
-			num_formats++;
-		}
+		if (hvs_formats[i].hvs5_only && vc4->gen < VC4_GEN_5)
+			continue;
+		if (hvs_formats[i].hvs6_only && vc4->gen < VC4_GEN_6_C)
+			continue;
+
+		formats[num_formats] = hvs_formats[i].drm;
+		num_formats++;
 	}
 
 	vc4_plane = drmm_universal_plane_alloc(dev, struct vc4_plane, base,
@@ -1647,7 +2769,7 @@ struct drm_plane *vc4_plane_init(struct drm_device *dev,
 		return ERR_CAST(vc4_plane);
 	plane = &vc4_plane->base;
 
-	if (vc4->gen == VC4_GEN_5)
+	if (vc4->gen >= VC4_GEN_5)
 		drm_plane_helper_add(plane, &vc5_plane_helper_funcs);
 	else
 		drm_plane_helper_add(plane, &vc4_plane_helper_funcs);
@@ -1672,6 +2794,12 @@ struct drm_plane *vc4_plane_init(struct drm_device *dev,
 					  DRM_COLOR_YCBCR_BT709,
 					  DRM_COLOR_YCBCR_LIMITED_RANGE);
 
+	drm_plane_create_scaling_filter_property(plane,
+						 BIT(DRM_SCALING_FILTER_DEFAULT) |
+						 BIT(DRM_SCALING_FILTER_NEAREST_NEIGHBOR));
+
+	drm_plane_create_chroma_siting_properties(plane, 0, 0);
+
 	if (type == DRM_PLANE_TYPE_PRIMARY)
 		drm_plane_create_zpos_immutable_property(plane, 0);
 
@@ -1679,12 +2807,27 @@ struct drm_plane *vc4_plane_init(struct drm_device *dev,
 }
 
 #define VC4_NUM_OVERLAY_PLANES	16
+#define VC4_NUM_TXP_OVERLAY_PLANES 32
 
 int vc4_plane_create_additional_planes(struct drm_device *drm)
 {
 	struct drm_plane *cursor_plane;
 	struct drm_crtc *crtc;
 	unsigned int i;
+	struct drm_crtc *txp_crtc;
+	uint32_t non_txp_crtc_mask;
+
+	drm_for_each_crtc(crtc, drm) {
+		struct vc4_crtc *vc4_crtc = to_vc4_crtc(crtc);
+
+		if (vc4_crtc->feeds_txp) {
+			txp_crtc = crtc;
+			break;
+		}
+	}
+
+	non_txp_crtc_mask = GENMASK(drm->mode_config.num_crtc - 1, 0) -
+					drm_crtc_mask(txp_crtc);
 
 	/* Set up some arbitrary number of planes.  We're not limited
 	 * by a set number of physical registers, just the space in
@@ -1698,7 +2841,22 @@ int vc4_plane_create_additional_planes(struct drm_device *drm)
 	for (i = 0; i < VC4_NUM_OVERLAY_PLANES; i++) {
 		struct drm_plane *plane =
 			vc4_plane_init(drm, DRM_PLANE_TYPE_OVERLAY,
-				       GENMASK(drm->mode_config.num_crtc - 1, 0));
+				       non_txp_crtc_mask);
+
+		if (IS_ERR(plane))
+			continue;
+
+		/* Create zpos property. Max of all the overlays + 1 primary +
+		 * 1 cursor plane on a crtc.
+		 */
+		drm_plane_create_zpos_property(plane, i + 1, 1,
+					       VC4_NUM_OVERLAY_PLANES + 1);
+	}
+
+	for (i = 0; i < VC4_NUM_TXP_OVERLAY_PLANES; i++) {
+		struct drm_plane *plane =
+			vc4_plane_init(drm, DRM_PLANE_TYPE_OVERLAY,
+				       drm_crtc_mask(txp_crtc));
 
 		if (IS_ERR(plane))
 			continue;
diff --git a/drivers/gpu/drm/vc4/vc4_regs.h b/drivers/gpu/drm/vc4/vc4_regs.h
index 8ac9515554f8..68d83d27c32a 100644
--- a/drivers/gpu/drm/vc4/vc4_regs.h
+++ b/drivers/gpu/drm/vc4/vc4_regs.h
@@ -19,6 +19,20 @@
 
 #define VC4_GET_FIELD(word, field) FIELD_GET(field##_MASK, word)
 
+#define VC6_SET_FIELD(value, field)					\
+	({								\
+		WARN_ON(!FIELD_FIT(hvs->vc4->gen == VC4_GEN_6_C ?	\
+				    SCALER6_ ## field ## _MASK :	\
+				    SCALER6D_ ## field ## _MASK, value));\
+		FIELD_PREP(hvs->vc4->gen == VC4_GEN_6_C ?		\
+				    SCALER6_ ## field ## _MASK :	\
+				    SCALER6D_ ## field ## _MASK, value);	\
+	 })
+
+#define VC6_GET_FIELD(word, field) FIELD_GET(hvs->vc4->gen == VC4_GEN_6_C ?	\
+					     SCALER6_ ## field ## _MASK :	\
+					     SCALER6D_ ## field ## _MASK, word)
+
 #define V3D_IDENT0   0x00000
 # define V3D_EXPECTED_IDENT0 \
 	((2 << 24) | \
@@ -155,6 +169,7 @@
 # define PV_CONTROL_EN				BIT(0)
 
 #define PV_V_CONTROL				0x04
+# define PV_VCONTROL_ODD_TIMING			BIT(29)
 # define PV_VCONTROL_ODD_DELAY_MASK		VC4_MASK(22, 6)
 # define PV_VCONTROL_ODD_DELAY_SHIFT		6
 # define PV_VCONTROL_ODD_FIRST			BIT(5)
@@ -215,6 +230,11 @@
 # define PV_MUX_CFG_RGB_PIXEL_MUX_MODE_SHIFT	2
 # define PV_MUX_CFG_RGB_PIXEL_MUX_MODE_NO_SWAP	8
 
+#define PV_PIPE_INIT_CTRL			0x94
+# define PV_PIPE_INIT_CTRL_PV_INIT_WIDTH_MASK	VC4_MASK(11, 8)
+# define PV_PIPE_INIT_CTRL_PV_INIT_IDLE_MASK	VC4_MASK(7, 4)
+# define PV_PIPE_INIT_CTRL_PV_INIT_EN		BIT(0)
+
 #define SCALER_CHANNELS_COUNT			3
 
 #define SCALER_DISPCTRL                         0x00000000
@@ -418,6 +438,10 @@
 # define SCALER_DISPSTAT1_FRCNT0_SHIFT		18
 # define SCALER_DISPSTAT1_FRCNT1_MASK		VC4_MASK(17, 12)
 # define SCALER_DISPSTAT1_FRCNT1_SHIFT		12
+# define SCALER5_DISPSTAT1_FRCNT0_MASK		VC4_MASK(25, 20)
+# define SCALER5_DISPSTAT1_FRCNT0_SHIFT		20
+# define SCALER5_DISPSTAT1_FRCNT1_MASK		VC4_MASK(19, 14)
+# define SCALER5_DISPSTAT1_FRCNT1_SHIFT		14
 
 #define SCALER_DISPSTATX(x)			(SCALER_DISPSTAT0 +        \
 						 (x) * (SCALER_DISPSTAT1 - \
@@ -436,6 +460,8 @@
 #define SCALER_DISPSTAT2                        0x00000068
 # define SCALER_DISPSTAT2_FRCNT2_MASK		VC4_MASK(17, 12)
 # define SCALER_DISPSTAT2_FRCNT2_SHIFT		12
+# define SCALER5_DISPSTAT2_FRCNT2_MASK		VC4_MASK(19, 14)
+# define SCALER5_DISPSTAT2_FRCNT2_SHIFT		14
 
 #define SCALER_DISPBASE2                        0x0000006c
 #define SCALER_DISPALPHA2                       0x00000070
@@ -514,6 +540,206 @@
 
 #define SCALER5_DLIST_START			0x00004000
 
+#define SCALER6_VERSION				0x00000000
+# define SCALER6_VERSION_MASK			VC4_MASK(7, 0)
+# define SCALER6_VERSION_C0			0x00000053
+# define SCALER6_VERSION_D0			0x00000054
+#define SCALER6_CXM_SIZE			0x00000004
+#define SCALER6_LBM_SIZE			0x00000008
+#define SCALER6_UBM_SIZE			0x0000000c
+#define SCALER6_COBA_SIZE			0x00000010
+#define SCALER6_COB_SIZE			0x00000014
+
+#define SCALER6_CONTROL				0x00000020
+# define SCALER6_CONTROL_HVS_EN			BIT(31)
+# define SCALER6_CONTROL_PF_LINES_MASK		VC4_MASK(22, 18)
+# define SCALER6_CONTROL_ABORT_ON_EMPTY		BIT(16)
+# define SCALER6_CONTROL_DSP1_TARGET_MASK	VC4_MASK(13, 12)
+# define SCALER6_CONTROL_MAX_REQS_MASK		VC4_MASK(7, 4)
+
+#define SCALER6_FETCHER_STATUS			0x00000024
+#define SCALER6_FETCH_STATUS			0x00000028
+#define SCALER6_HANDLE_ERROR			0x0000002c
+
+#define SCALER6_DISP0_CTRL0			0x00000030
+#define SCALER6_DISPX_CTRL0(x)		((hvs->vc4->gen == VC4_GEN_6_C) ? \
+	(SCALER6_DISP0_CTRL0 + ((x) * (SCALER6_DISP1_CTRL0 - SCALER6_DISP0_CTRL0))) : \
+	(SCALER6D_DISP0_CTRL0 + ((x) * (SCALER6D_DISP1_CTRL0 - SCALER6D_DISP0_CTRL0))))
+# define SCALER6_DISPX_CTRL0_ENB		BIT(31)
+# define SCALER6_DISPX_CTRL0_RESET		BIT(30)
+# define SCALER6_DISPX_CTRL0_FWIDTH_MASK	VC4_MASK(28, 16)
+# define SCALER6_DISPX_CTRL0_ONESHOT		BIT(15)
+# define SCALER6_DISPX_CTRL0_ONECTX_MASK	VC4_MASK(14, 13)
+# define SCALER6_DISPX_CTRL0_LINES_MASK		VC4_MASK(12, 0)
+
+#define SCALER6_DISP0_CTRL1			0x00000034
+#define SCALER6_DISPX_CTRL1(x)		((hvs->vc4->gen == VC4_GEN_6_C) ? \
+	(SCALER6_DISP0_CTRL1 + ((x) * (SCALER6_DISP1_CTRL1 - SCALER6_DISP0_CTRL1))) : \
+	(SCALER6D_DISP0_CTRL1 + ((x) * (SCALER6D_DISP1_CTRL1 - SCALER6D_DISP0_CTRL1))))
+# define SCALER6_DISPX_CTRL1_BGENB		BIT(8)
+# define SCALER6_DISPX_CTRL1_INTLACE		BIT(0)
+
+#define SCALER6_DISP0_BGND			0x00000038
+#define SCALER6_DISPX_BGND(x)		((hvs->vc4->gen == VC4_GEN_6_C) ? \
+	(SCALER6_DISP0_BGND + ((x) * (SCALER6_DISP1_BGND - SCALER6_DISP0_BGND))) : \
+	(SCALER6D_DISP0_BGND + ((x) * (SCALER6D_DISP1_BGND - SCALER6D_DISP0_BGND))))
+
+#define SCALER6_DISP0_LPTRS			0x0000003c
+#define SCALER6_DISPX_LPTRS(x)		((hvs->vc4->gen == VC4_GEN_6_C) ? \
+	(SCALER6_DISP0_LPTRS + ((x) * (SCALER6_DISP1_LPTRS - SCALER6_DISP0_LPTRS))) : \
+	(SCALER6D_DISP0_LPTRS + ((x) * (SCALER6D_DISP1_LPTRS - SCALER6D_DISP0_LPTRS))))
+# define SCALER6_DISPX_LPTRS_HEADE_MASK		VC4_MASK(11, 0)
+
+#define SCALER6_DISP0_COB			0x00000040
+#define SCALER6_DISPX_COB(x)		((hvs->vc4->gen == VC4_GEN_6_C) ? \
+	(SCALER6_DISP0_COB + ((x) * (SCALER6_DISP1_COB - SCALER6_DISP0_COB))) : \
+	(SCALER6D_DISP0_COB + ((x) * (SCALER6D_DISP1_COB - SCALER6D_DISP0_COB))))
+# define SCALER6_DISPX_COB_TOP_MASK		VC4_MASK(31, 16)
+# define SCALER6_DISPX_COB_BASE_MASK		VC4_MASK(15, 0)
+
+#define SCALER6_DISP0_STATUS			0x00000044
+#define SCALER6_DISPX_STATUS(x)		((hvs->vc4->gen == VC4_GEN_6_C) ? \
+	(SCALER6_DISP0_STATUS + ((x) * (SCALER6_DISP1_STATUS - SCALER6_DISP0_STATUS))) : \
+	(SCALER6D_DISP0_STATUS + ((x) * (SCALER6D_DISP1_STATUS - SCALER6D_DISP0_STATUS))))
+# define SCALER6_DISPX_STATUS_EMPTY		BIT(22)
+# define SCALER6_DISPX_STATUS_FRCNT_MASK	VC4_MASK(21, 16)
+# define SCALER6_DISPX_STATUS_OFIELD		BIT(15)
+# define SCALER6_DISPX_STATUS_MODE_MASK		VC4_MASK(14, 13)
+# define SCALER6_DISPX_STATUS_MODE_DISABLED	0
+# define SCALER6_DISPX_STATUS_MODE_INIT		1
+# define SCALER6_DISPX_STATUS_MODE_RUN		2
+# define SCALER6_DISPX_STATUS_MODE_EOF		3
+# define SCALER6_DISPX_STATUS_YLINE_MASK	VC4_MASK(12, 0)
+
+#define SCALER6_DISP0_DL			0x00000048
+
+#define SCALER6_DISPX_DL(x)		((hvs->vc4->gen == VC4_GEN_6_C) ? \
+	(SCALER6_DISP0_DL + ((x) * (SCALER6_DISP1_DL - SCALER6_DISP0_DL))) : \
+	(SCALER6D_DISP0_DL + ((x) * (SCALER6D_DISP1_DL - SCALER6D_DISP0_DL))))
+# define SCALER6_DISPX_DL_LACT_MASK		VC4_MASK(11, 0)
+
+#define SCALER6_DISP0_RUN			0x0000004c
+#define SCALER6_DISP1_CTRL0			0x00000050
+#define SCALER6_DISP1_CTRL1			0x00000054
+#define SCALER6_DISP1_BGND			0x00000058
+#define SCALER6_DISP1_LPTRS			0x0000005c
+#define SCALER6_DISP1_COB			0x00000060
+#define SCALER6_DISP1_STATUS			0x00000064
+#define SCALER6_DISP1_DL			0x00000068
+#define SCALER6_DISP1_RUN			0x0000006c
+#define SCALER6_DISP2_CTRL0			0x00000070
+#define SCALER6_DISP2_CTRL1			0x00000074
+#define SCALER6_DISP2_BGND			0x00000078
+#define SCALER6_DISP2_LPTRS			0x0000007c
+#define SCALER6_DISP2_COB			0x00000080
+#define SCALER6_DISP2_STATUS			0x00000084
+#define SCALER6_DISP2_DL			0x00000088
+#define SCALER6_DISP2_RUN			0x0000008c
+#define SCALER6_EOLN				0x00000090
+#define SCALER6_DL_STATUS			0x00000094
+#define SCALER6_BFG_MISC			0x0000009c
+#define SCALER6_QOS0				0x000000a0
+#define SCALER6_PROF0				0x000000a4
+#define SCALER6_QOS1				0x000000a8
+#define SCALER6_PROF1				0x000000ac
+#define SCALER6_QOS2				0x000000b0
+#define SCALER6_PROF2				0x000000b4
+#define SCALER6_PRI_MAP0			0x000000b8
+#define SCALER6_PRI_MAP1			0x000000bc
+#define SCALER6_HISTCTRL			0x000000c0
+#define SCALER6_HISTBIN0			0x000000c4
+#define SCALER6_HISTBIN1			0x000000c8
+#define SCALER6_HISTBIN2			0x000000cc
+#define SCALER6_HISTBIN3			0x000000d0
+#define SCALER6_HISTBIN4			0x000000d4
+#define SCALER6_HISTBIN5			0x000000d8
+#define SCALER6_HISTBIN6			0x000000dc
+#define SCALER6_HISTBIN7			0x000000e0
+#define SCALER6_HDR_CFG_REMAP			0x000000f4
+#define SCALER6_COL_SPACE			0x000000f8
+#define SCALER6_HVS_ID				0x000000fc
+#define SCALER6_CFC1				0x00000100
+#define SCALER6_DISP_UPM_ISO0			0x00000200
+#define SCALER6_DISP_UPM_ISO1			0x00000204
+#define SCALER6_DISP_UPM_ISO2			0x00000208
+#define SCALER6_DISP_LBM_ISO0			0x0000020c
+#define SCALER6_DISP_LBM_ISO1			0x00000210
+#define SCALER6_DISP_LBM_ISO2			0x00000214
+#define SCALER6_DISP_COB_ISO0			0x00000218
+#define SCALER6_DISP_COB_ISO1			0x0000021c
+#define SCALER6_DISP_COB_ISO2			0x00000220
+#define SCALER6_BAD_COB				0x00000224
+#define SCALER6_BAD_LBM				0x00000228
+#define SCALER6_BAD_UPM				0x0000022c
+#define SCALER6_BAD_AXI				0x00000230
+
+#define SCALER6D_VERSION			0x00000000
+#define SCALER6D_CXM_SIZE			0x00000004
+#define SCALER6D_LBM_SIZE			0x00000008
+#define SCALER6D_UBM_SIZE			0x0000000c
+#define SCALER6D_COBA_SIZE			0x00000010
+#define SCALER6D_COB_SIZE			0x00000014
+#define SCALER6D_CONTROL			0x00000020
+#define SCALER6D_FETCHER_STATUS			0x00000024
+#define SCALER6D_FETCH_STATUS			0x00000028
+#define SCALER6D_HANDLE_ERROR			0x0000002c
+#define SCALER6D_EOLN				0x00000030
+#define SCALER6D_DL_STATUS			0x00000034
+#define SCALER6D_PRI_MAP0			0x00000038
+#define SCALER6D_PRI_MAP1			0x0000003c
+#define SCALER6D_HISTCTRL			0x000000d0
+#define SCALER6D_HISTBIN0			0x000000d4
+#define SCALER6D_HISTBIN1			0x000000d8
+#define SCALER6D_HISTBIN2			0x000000dc
+#define SCALER6D_HISTBIN3			0x000000e0
+#define SCALER6D_HISTBIN4			0x000000e4
+#define SCALER6D_HISTBIN5			0x000000e8
+#define SCALER6D_HISTBIN6			0x000000ec
+#define SCALER6D_HISTBIN7			0x000000f0
+#define SCALER6D_HVS_ID				0x000000fc
+
+#define SCALER6D_DISP0_CTRL0			0x00000100
+#define SCALER6D_DISP0_CTRL1			0x00000104
+#define SCALER6D_DISP0_BGND			0x00000108
+#define SCALER6D_DISP0_LPTRS			0x00000110
+#define SCALER6D_DISP0_COB			0x00000114
+#define SCALER6D_DISP0_STATUS			0x00000118
+#define SCALER6D_DISP0_CTRL0			0x00000100
+#define SCALER6D_DISP0_CTRL1			0x00000104
+#define SCALER6D_DISP0_BGND0			0x00000108
+#define SCALER6D_DISP0_BGND1			0x0000010c
+#define SCALER6D_DISP0_LPTRS			0x00000110
+#define SCALER6D_DISP0_COB			0x00000114
+#define SCALER6D_DISP0_STATUS			0x00000118
+#define SCALER6D_DISP0_DL			0x0000011c
+#define SCALER6D_DISP0_RUN			0x00000120
+#define SCALER6D_QOS0				0x00000124
+#define SCALER6D_PROF0				0x00000128
+#define SCALER6D_DISP1_CTRL0			0x00000140
+#define SCALER6D_DISP1_CTRL1			0x00000144
+#define SCALER6D_DISP1_BGND0			0x00000148
+#define SCALER6D_DISP1_BGND1			0x0000014c
+#define SCALER6D_DISP1_LPTRS			0x00000150
+#define SCALER6D_DISP1_COB			0x00000154
+#define SCALER6D_DISP1_STATUS			0x00000158
+#define SCALER6D_DISP1_DL			0x0000015c
+#define SCALER6D_DISP1_RUN			0x00000160
+#define SCALER6D_QOS1				0x00000164
+#define SCALER6D_PROF1				0x00000168
+#define SCALER6D_DISP2_CTRL0			0x00000180
+#define SCALER6D_DISP2_CTRL1			0x00000184
+#define SCALER6D_DISP2_BGND0			0x00000188
+#define SCALER6D_DISP2_BGND1			0x0000018c
+#define SCALER6D_DISP2_LPTRS			0x00000190
+#define SCALER6D_DISP2_COB			0x00000194
+#define SCALER6D_DISP2_STATUS			0x00000198
+#define SCALER6D_DISP2_DL			0x0000019c
+#define SCALER6D_DISP2_RUN			0x000001a0
+#define SCALER6D_QOS2				0x000001a4
+#define SCALER6D_PROF2				0x000001a8
+
+#define SCALER6(x) ((hvs->vc4->gen == VC4_GEN_6_C) ? SCALER6_ ## x : SCALER6D_ ## x)
+
 # define VC4_HDMI_SW_RESET_FORMAT_DETECT	BIT(1)
 # define VC4_HDMI_SW_RESET_HDMI			BIT(0)
 
@@ -761,6 +987,15 @@ enum {
 # define VC4_HD_MAI_THR_DREQLOW_MASK		VC4_MASK(5, 0)
 # define VC4_HD_MAI_THR_DREQLOW_SHIFT		0
 
+# define VC6_D_HD_MAI_THR_PANICHIGH_MASK	VC4_MASK(29, 23)
+# define VC6_D_HD_MAI_THR_PANICHIGH_SHIFT	23
+# define VC6_D_HD_MAI_THR_PANICLOW_MASK		VC4_MASK(21, 15)
+# define VC6_D_HD_MAI_THR_PANICLOW_SHIFT	15
+# define VC6_D_HD_MAI_THR_DREQHIGH_MASK		VC4_MASK(13, 7)
+# define VC6_D_HD_MAI_THR_DREQHIGH_SHIFT	7
+# define VC6_D_HD_MAI_THR_DREQLOW_MASK		VC4_MASK(6, 0)
+# define VC6_D_HD_MAI_THR_DREQLOW_SHIFT		0
+
 /* Divider from HDMI HSM clock to MAI serial clock.  Sampling period
  * converges to N / (M + 1) cycles.
  */
@@ -777,6 +1012,7 @@ enum {
 # define VC4_HD_VID_CTL_CLRSYNC			BIT(24)
 # define VC4_HD_VID_CTL_CLRRGB			BIT(23)
 # define VC4_HD_VID_CTL_BLANKPIX		BIT(18)
+# define VC4_HD_VID_CTL_BLANK_INSERT_EN		BIT(16)
 
 # define VC4_HD_CSC_CTL_ORDER_MASK		VC4_MASK(7, 5)
 # define VC4_HD_CSC_CTL_ORDER_SHIFT		5
@@ -843,6 +1079,15 @@ enum hvs_pixel_format {
 	HVS_PIXEL_FORMAT_AYUV444_RGB = 15,
 	HVS_PIXEL_FORMAT_RGBA1010102 = 16,
 	HVS_PIXEL_FORMAT_YCBCR_10BIT = 17,
+	/* 10 bit YUV420 formats with data with various different alignments */
+	HVS_PIXEL_FORMAT_YCBCR_YUV420_15_6_2PLANE = 24,
+	HVS_PIXEL_FORMAT_YCBCR_YUV420_15_6_3PLANE = 25,
+	HVS_PIXEL_FORMAT_YCBCR_YUV420_13_4_2PLANE = 26,
+	HVS_PIXEL_FORMAT_YCBCR_YUV420_13_4_3PLANE = 27,
+	HVS_PIXEL_FORMAT_YCBCR_YUV420_11_2_2PLANE = 28,
+	HVS_PIXEL_FORMAT_YCBCR_YUV420_11_2_3PLANE = 29,
+	HVS_PIXEL_FORMAT_YCBCR_YUV420_9_0_2PLANE = 30,
+	HVS_PIXEL_FORMAT_YCBCR_YUV420_9_0_3PLANE = 31,
 };
 
 /* Note: the LSB is the rightmost character shown.  Only valid for
@@ -967,6 +1212,9 @@ enum hvs_pixel_format {
 #define SCALER5_CTL2_ALPHA_MASK			VC4_MASK(15, 4)
 #define SCALER5_CTL2_ALPHA_SHIFT		4
 
+#define SCALER6D_CTL2_CSC_ENABLE		BIT(19)
+#define SCALER6D_CTL2_BRCM_CFC_CONTROL_MASK	VC4_MASK(22, 20)
+
 #define SCALER_POS1_SCL_HEIGHT_MASK		VC4_MASK(27, 16)
 #define SCALER_POS1_SCL_HEIGHT_SHIFT		16
 
@@ -1108,4 +1356,63 @@ enum hvs_pixel_format {
 #define SCALER_PITCH0_TILE_WIDTH_R_MASK		VC4_MASK(6, 0)
 #define SCALER_PITCH0_TILE_WIDTH_R_SHIFT	0
 
+#define SCALER6_CTL0_END			BIT(31)
+#define SCALER6_CTL0_VALID			BIT(30)
+#define SCALER6_CTL0_NEXT_MASK			VC4_MASK(29, 24)
+#define SCALER6_CTL0_RGB_TRANS			BIT(23)
+#define SCALER6_CTL0_ADDR_MODE_MASK		VC4_MASK(22, 20)
+#define SCALER6_CTL0_ADDR_MODE_LINEAR		0
+#define SCALER6_CTL0_ADDR_MODE_128B		1
+#define SCALER6_CTL0_ADDR_MODE_256B		2
+#define SCALER6_CTL0_ADDR_MODE_MAP8		3
+#define SCALER6_CTL0_ADDR_MODE_UIF		4
+
+#define SCALER6_CTL0_ALPHA_MASK_MASK		VC4_MASK(19, 18)
+#define SCALER6_CTL0_ALPHA_MASK_NONE		0
+#define SCALER6D_CTL0_ALPHA_MASK_FIXED		3
+#define SCALER6_CTL0_UNITY			BIT(15)
+#define SCALER6_CTL0_ORDERRGBA_MASK		VC4_MASK(14, 13)
+#define SCALER6_CTL0_SCL1_MODE_MASK		VC4_MASK(10, 8)
+#define SCALER6_CTL0_SCL0_MODE_MASK		VC4_MASK(7, 5)
+#define SCALER6_CTL0_PIXEL_FORMAT_MASK		VC4_MASK(4, 0)
+
+#define SCALER6_POS0_START_Y_MASK		VC4_MASK(28, 16)
+#define SCALER6_POS0_HFLIP			BIT(15)
+#define SCALER6_POS0_START_X_MASK		VC4_MASK(12, 0)
+
+#define SCALER6_CTL2_ALPHA_MODE_MASK		VC4_MASK(31, 30)
+#define SCALER6_CTL2_ALPHA_PREMULT		BIT(29)
+#define SCALER6_CTL2_ALPHA_MIX			BIT(28)
+#define SCALER6_CTL2_BFG			BIT(26)
+#define SCALER6C_CTL2_CSC_ENABLE		BIT(25)
+#define SCALER6C_CTL2_BRCM_CFC_CONTROL_MASK	VC4_MASK(18, 16)
+#define SCALER6_CTL2_ALPHA_MASK			VC4_MASK(15, 4)
+
+#define SCALER6_POS1_SCL_LINES_MASK		VC4_MASK(28, 16)
+#define SCALER6_POS1_SCL_WIDTH_MASK		VC4_MASK(12, 0)
+
+#define SCALER6_POS2_SRC_LINES_MASK		VC4_MASK(28, 16)
+#define SCALER6_POS2_SRC_WIDTH_MASK		VC4_MASK(12, 0)
+
+#define SCALER6_PTR0_VFLIP			BIT(31)
+#define SCALER6_PTR0_UPM_BASE_MASK		VC4_MASK(28, 16)
+#define SCALER6_PTR0_UPM_HANDLE_MASK		VC4_MASK(14, 10)
+#define SCALER6_PTR0_UPM_BUFF_SIZE_MASK		VC4_MASK(9, 8)
+#define SCALER6_PTR0_UPM_BUFF_SIZE_16_LINES	3
+#define SCALER6_PTR0_UPM_BUFF_SIZE_8_LINES	2
+#define SCALER6_PTR0_UPM_BUFF_SIZE_4_LINES	1
+#define SCALER6_PTR0_UPM_BUFF_SIZE_2_LINES	0
+#define SCALER6_PTR0_UPPER_ADDR_MASK		VC4_MASK(7, 0)
+
+#define SCALER6_PTR2_ALPHA_BPP_MASK		VC4_MASK(31, 31)
+#define SCALER6_PTR2_ALPHA_BPP_1BPP		1
+#define SCALER6_PTR2_ALPHA_BPP_8BPP		0
+#define SCALER6_PTR2_ALPHA_ORDER_MASK		VC4_MASK(30, 30)
+#define SCALER6_PTR2_ALPHA_ORDER_MSB_TO_LSB	1
+#define SCALER6_PTR2_ALPHA_ORDER_LSB_TO_MSB	0
+#define SCALER6_PTR2_ALPHA_OFFS_MASK		VC4_MASK(29, 27)
+#define SCALER6_PTR2_LSKIP_MASK			VC4_MASK(26, 24)
+#define SCALER6_PTR2_PITCH_MASK			VC4_MASK(16, 0)
+#define SCALER6_PTR2_FETCH_COUNT_MASK		VC4_MASK(26, 16)
+
 #endif /* VC4_REGS_H */
diff --git a/drivers/gpu/drm/vc4/vc4_render_cl.c b/drivers/gpu/drm/vc4/vc4_render_cl.c
index ae4ad956f04f..14079853338e 100644
--- a/drivers/gpu/drm/vc4/vc4_render_cl.c
+++ b/drivers/gpu/drm/vc4/vc4_render_cl.c
@@ -599,7 +599,7 @@ int vc4_get_rcl(struct drm_device *dev, struct vc4_exec_info *exec)
 	bool has_bin = args->bin_cl_size != 0;
 	int ret;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	if (args->min_x_tile > args->max_x_tile ||
diff --git a/drivers/gpu/drm/vc4/vc4_txp.c b/drivers/gpu/drm/vc4/vc4_txp.c
index ffe1f7d1b911..92ad71059769 100644
--- a/drivers/gpu/drm/vc4/vc4_txp.c
+++ b/drivers/gpu/drm/vc4/vc4_txp.c
@@ -15,6 +15,7 @@
 
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
+#include <drm/drm_blend.h>
 #include <drm/drm_drv.h>
 #include <drm/drm_edid.h>
 #include <drm/drm_fb_dma_helper.h>
@@ -145,6 +146,9 @@
 /* Number of lines received and committed to memory. */
 #define TXP_PROGRESS		0x10
 
+#define TXP_DST_PTR_HIGH_MOPLET	0x1c
+#define TXP_DST_PTR_HIGH_MOP	0x24
+
 #define TXP_READ(offset)								\
 	({										\
 		kunit_fail_current_test("Accessing a register in a unit test!\n");	\
@@ -159,6 +163,7 @@
 
 struct vc4_txp {
 	struct vc4_crtc	base;
+	const struct vc4_txp_data *data;
 
 	struct platform_device *pdev;
 
@@ -255,10 +260,22 @@ static int vc4_txp_connector_atomic_check(struct drm_connector *conn,
 	crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
 
 	fb = conn_state->writeback_job->fb;
-	if (fb->width != crtc_state->mode.hdisplay ||
-	    fb->height != crtc_state->mode.vdisplay) {
-		DRM_DEBUG_KMS("Invalid framebuffer size %ux%u\n",
-			      fb->width, fb->height);
+	if ((conn_state->rotation == DRM_MODE_ROTATE_0 &&
+	    (fb->width != crtc_state->mode.hdisplay ||
+	     fb->height != crtc_state->mode.vdisplay)) ||
+	    (conn_state->rotation == (DRM_MODE_ROTATE_0 | DRM_MODE_TRANSPOSE) &&
+	    (fb->width != crtc_state->mode.vdisplay ||
+	     fb->height != crtc_state->mode.hdisplay))) {
+		DRM_DEBUG_KMS("Invalid framebuffer size %ux%u vs mode %ux%u\n",
+			      fb->width, fb->height,
+			      crtc_state->mode.hdisplay, crtc_state->mode.vdisplay);
+		return -EINVAL;
+	}
+
+	if (conn_state->rotation & DRM_MODE_TRANSPOSE &&
+	    (fb->format->format == DRM_FORMAT_RGB888 ||
+	     fb->format->format == DRM_FORMAT_BGR888)) {
+		DRM_DEBUG_KMS("24bpp formats not supported when transposing\n");
 		return -EINVAL;
 	}
 
@@ -286,9 +303,13 @@ static void vc4_txp_connector_atomic_commit(struct drm_connector *conn,
 	struct drm_connector_state *conn_state = drm_atomic_get_new_connector_state(state,
 										    conn);
 	struct vc4_txp *txp = connector_to_vc4_txp(conn);
+	const struct vc4_txp_data *txp_data = txp->data;
 	struct drm_gem_dma_object *gem;
 	struct drm_display_mode *mode;
 	struct drm_framebuffer *fb;
+	unsigned int hdisplay;
+	unsigned int vdisplay;
+	dma_addr_t addr;
 	u32 ctrl;
 	int idx;
 	int i;
@@ -308,9 +329,11 @@ static void vc4_txp_connector_atomic_commit(struct drm_connector *conn,
 		return;
 
 	ctrl = TXP_GO | TXP_EI |
-	       VC4_SET_FIELD(0xf, TXP_BYTE_ENABLE) |
 	       VC4_SET_FIELD(txp_fmts[i], TXP_FORMAT);
 
+	if (txp_data->has_byte_enable)
+		ctrl |= VC4_SET_FIELD(0xf, TXP_BYTE_ENABLE);
+
 	if (fb->format->has_alpha)
 		ctrl |= TXP_ALPHA_ENABLE;
 	else
@@ -320,15 +343,32 @@ static void vc4_txp_connector_atomic_commit(struct drm_connector *conn,
 		 */
 		ctrl |= TXP_ALPHA_INVERT;
 
+	if (conn_state->rotation & DRM_MODE_TRANSPOSE)
+		ctrl |= TXP_TRANSPOSE;
+
 	if (!drm_dev_enter(drm, &idx))
 		return;
 
 	gem = drm_fb_dma_get_gem_obj(fb, 0);
-	TXP_WRITE(TXP_DST_PTR, gem->dma_addr + fb->offsets[0]);
+	addr = gem->dma_addr + fb->offsets[0];
+
+	TXP_WRITE(TXP_DST_PTR, lower_32_bits(addr));
+
+	if (txp_data->supports_40bit_addresses)
+		TXP_WRITE(txp_data->high_addr_ptr_reg, upper_32_bits(addr) & 0xff);
+
 	TXP_WRITE(TXP_DST_PITCH, fb->pitches[0]);
+
+	hdisplay = mode->hdisplay ?: 1;
+	vdisplay = mode->vdisplay ?: 1;
+	if (txp_data->size_minus_one) {
+		hdisplay -= 1;
+		vdisplay -= 1;
+	}
+
 	TXP_WRITE(TXP_DIM,
-		  VC4_SET_FIELD(mode->hdisplay, TXP_WIDTH) |
-		  VC4_SET_FIELD(mode->vdisplay, TXP_HEIGHT));
+		  VC4_SET_FIELD(hdisplay, TXP_WIDTH) |
+		  VC4_SET_FIELD(vdisplay, TXP_HEIGHT));
 
 	TXP_WRITE(TXP_DST_CTRL, ctrl);
 
@@ -350,11 +390,27 @@ vc4_txp_connector_detect(struct drm_connector *connector, bool force)
 	return connector_status_connected;
 }
 
+void vc4_txp_connector_reset(struct drm_connector *connector)
+{
+	uint64_t rotation = DRM_MODE_ROTATE_0;
+
+	drm_atomic_helper_connector_reset(connector);
+
+	if (connector->state) {
+		if (connector->rotation_property)
+			drm_object_property_get_default_value(&connector->base,
+							      connector->rotation_property,
+							      &rotation);
+
+		connector->state->rotation = rotation;
+	}
+}
+
 static const struct drm_connector_funcs vc4_txp_connector_funcs = {
 	.detect = vc4_txp_connector_detect,
 	.fill_modes = drm_helper_probe_single_connector_modes,
 	.destroy = drm_connector_cleanup,
-	.reset = drm_atomic_helper_connector_reset,
+	.reset = vc4_txp_connector_reset,
 	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
 	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
 };
@@ -362,6 +418,7 @@ static const struct drm_connector_funcs vc4_txp_connector_funcs = {
 static void vc4_txp_encoder_disable(struct drm_encoder *encoder)
 {
 	struct drm_device *drm = encoder->dev;
+	struct vc4_dev *vc4 = to_vc4_dev(drm);
 	struct vc4_txp *txp = encoder_to_vc4_txp(encoder);
 	int idx;
 
@@ -380,7 +437,8 @@ static void vc4_txp_encoder_disable(struct drm_encoder *encoder)
 		WARN_ON(TXP_READ(TXP_DST_CTRL) & TXP_BUSY);
 	}
 
-	TXP_WRITE(TXP_DST_CTRL, TXP_POWERDOWN);
+	if (vc4->gen < VC4_GEN_6_C)
+		TXP_WRITE(TXP_DST_CTRL, TXP_POWERDOWN);
 
 	drm_dev_exit(idx);
 }
@@ -484,17 +542,49 @@ static irqreturn_t vc4_txp_interrupt(int irq, void *data)
 	return IRQ_HANDLED;
 }
 
-const struct vc4_crtc_data vc4_txp_crtc_data = {
-	.name = "txp",
-	.debugfs_name = "txp_regs",
-	.hvs_available_channels = BIT(2),
-	.hvs_output = 2,
+const struct vc4_txp_data bcm2712_mop_data = {
+	.base = {
+		.name = "mop",
+		.debugfs_name = "mop_regs",
+		.hvs_available_channels = BIT(2),
+		.hvs_output = 2,
+	},
+	.encoder_type = VC4_ENCODER_TYPE_TXP0,
+	.high_addr_ptr_reg = TXP_DST_PTR_HIGH_MOP,
+	.has_byte_enable = true,
+	.size_minus_one = true,
+	.supports_40bit_addresses = true,
+};
+
+const struct vc4_txp_data bcm2712_moplet_data = {
+	.base = {
+		.name = "moplet",
+		.debugfs_name = "moplet_regs",
+		.hvs_available_channels = BIT(1),
+		.hvs_output = 4,
+	},
+	.encoder_type = VC4_ENCODER_TYPE_TXP1,
+	.high_addr_ptr_reg = TXP_DST_PTR_HIGH_MOPLET,
+	.size_minus_one = true,
+	.supports_40bit_addresses = true,
+};
+
+const struct vc4_txp_data bcm2835_txp_data = {
+	.base = {
+		.name = "txp",
+		.debugfs_name = "txp_regs",
+		.hvs_available_channels = BIT(2),
+		.hvs_output = 2,
+	},
+	.encoder_type = VC4_ENCODER_TYPE_TXP0,
+	.has_byte_enable = true,
 };
 
 static int vc4_txp_bind(struct device *dev, struct device *master, void *data)
 {
 	struct platform_device *pdev = to_platform_device(dev);
 	struct drm_device *drm = dev_get_drvdata(master);
+	const struct vc4_txp_data *txp_data;
 	struct vc4_encoder *vc4_encoder;
 	struct drm_encoder *encoder;
 	struct vc4_crtc *vc4_crtc;
@@ -509,6 +599,11 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data)
 	if (!txp)
 		return -ENOMEM;
 
+	txp_data = of_device_get_match_data(dev);
+	if (!txp_data)
+		return -ENODEV;
+
+	txp->data = txp_data;
 	txp->pdev = pdev;
 	txp->regs = vc4_ioremap_regs(pdev, 0);
 	if (IS_ERR(txp->regs))
@@ -519,13 +614,13 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data)
 	vc4_crtc->regset.regs = txp_regs;
 	vc4_crtc->regset.nregs = ARRAY_SIZE(txp_regs);
 
-	ret = vc4_crtc_init(drm, pdev, vc4_crtc, &vc4_txp_crtc_data,
+	ret = vc4_crtc_init(drm, pdev, vc4_crtc, &txp_data->base,
 			    &vc4_txp_crtc_funcs, &vc4_txp_crtc_helper_funcs, true);
 	if (ret)
 		return ret;
 
 	vc4_encoder = &txp->encoder;
-	txp->encoder.type = VC4_ENCODER_TYPE_TXP;
+	txp->encoder.type = txp_data->encoder_type;
 
 	encoder = &vc4_encoder->base;
 	encoder->possible_crtcs = drm_crtc_mask(&vc4_crtc->base);
@@ -545,6 +640,10 @@ static int vc4_txp_bind(struct device *dev, struct device *master, void *data)
 	if (ret)
 		return ret;
 
+	drm_connector_create_rotation_property(&txp->connector.base, DRM_MODE_ROTATE_0,
+					       DRM_MODE_ROTATE_0 |
+					       DRM_MODE_TRANSPOSE);
+
 	ret = devm_request_irq(dev, irq, vc4_txp_interrupt, 0,
 			       dev_name(dev), txp);
 	if (ret)
@@ -579,7 +678,9 @@ static void vc4_txp_remove(struct platform_device *pdev)
 }
 
 static const struct of_device_id vc4_txp_dt_match[] = {
-	{ .compatible = "brcm,bcm2835-txp" },
+	{ .compatible = "brcm,bcm2712-mop", .data = &bcm2712_mop_data },
+	{ .compatible = "brcm,bcm2712-moplet", .data = &bcm2712_moplet_data },
+	{ .compatible = "brcm,bcm2835-txp", .data = &bcm2835_txp_data },
 	{ /* sentinel */ },
 };
 
diff --git a/drivers/gpu/drm/vc4/vc4_v3d.c b/drivers/gpu/drm/vc4/vc4_v3d.c
index 43f69d74e876..76502406f0c3 100644
--- a/drivers/gpu/drm/vc4/vc4_v3d.c
+++ b/drivers/gpu/drm/vc4/vc4_v3d.c
@@ -127,7 +127,7 @@ static int vc4_v3d_debugfs_ident(struct seq_file *m, void *unused)
 int
 vc4_v3d_pm_get(struct vc4_dev *vc4)
 {
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	mutex_lock(&vc4->power_lock);
@@ -148,7 +148,7 @@ vc4_v3d_pm_get(struct vc4_dev *vc4)
 void
 vc4_v3d_pm_put(struct vc4_dev *vc4)
 {
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return;
 
 	mutex_lock(&vc4->power_lock);
@@ -178,7 +178,7 @@ int vc4_v3d_get_bin_slot(struct vc4_dev *vc4)
 	uint64_t seqno = 0;
 	struct vc4_exec_info *exec;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 try_again:
@@ -325,7 +325,7 @@ int vc4_v3d_bin_bo_get(struct vc4_dev *vc4, bool *used)
 {
 	int ret = 0;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	mutex_lock(&vc4->bin_bo_lock);
@@ -360,7 +360,7 @@ static void bin_bo_release(struct kref *ref)
 
 void vc4_v3d_bin_bo_put(struct vc4_dev *vc4)
 {
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return;
 
 	mutex_lock(&vc4->bin_bo_lock);
@@ -376,6 +376,8 @@ static int vc4_v3d_runtime_suspend(struct device *dev)
 
 	vc4_irq_disable(&vc4->base);
 
+	/* we no longer require a minimum clock rate */
+	clk_set_min_rate(v3d->clk, 0);
 	clk_disable_unprepare(v3d->clk);
 
 	return 0;
diff --git a/drivers/gpu/drm/vc4/vc4_validate.c b/drivers/gpu/drm/vc4/vc4_validate.c
index f3d7fdbe9083..5bf134968ade 100644
--- a/drivers/gpu/drm/vc4/vc4_validate.c
+++ b/drivers/gpu/drm/vc4/vc4_validate.c
@@ -109,7 +109,7 @@ vc4_use_bo(struct vc4_exec_info *exec, uint32_t hindex)
 	struct drm_gem_dma_object *obj;
 	struct vc4_bo *bo;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return NULL;
 
 	if (hindex >= exec->bo_count) {
@@ -169,7 +169,7 @@ vc4_check_tex_size(struct vc4_exec_info *exec, struct drm_gem_dma_object *fbo,
 	uint32_t utile_w = utile_width(cpp);
 	uint32_t utile_h = utile_height(cpp);
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return false;
 
 	/* The shaded vertex format stores signed 12.4 fixed point
@@ -495,7 +495,7 @@ vc4_validate_bin_cl(struct drm_device *dev,
 	uint32_t dst_offset = 0;
 	uint32_t src_offset = 0;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	while (src_offset < len) {
@@ -942,7 +942,7 @@ vc4_validate_shader_recs(struct drm_device *dev,
 	uint32_t i;
 	int ret = 0;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return -ENODEV;
 
 	for (i = 0; i < exec->shader_state_count; i++) {
diff --git a/drivers/gpu/drm/vc4/vc4_validate_shaders.c b/drivers/gpu/drm/vc4/vc4_validate_shaders.c
index afb1a4d82684..2d74e786914c 100644
--- a/drivers/gpu/drm/vc4/vc4_validate_shaders.c
+++ b/drivers/gpu/drm/vc4/vc4_validate_shaders.c
@@ -786,7 +786,7 @@ vc4_validate_shader(struct drm_gem_dma_object *shader_obj)
 	struct vc4_validated_shader_info *validated_shader = NULL;
 	struct vc4_shader_validation_state validation_state;
 
-	if (WARN_ON_ONCE(vc4->gen == VC4_GEN_5))
+	if (WARN_ON_ONCE(vc4->gen > VC4_GEN_4))
 		return NULL;
 
 	memset(&validation_state, 0, sizeof(validation_state));
diff --git a/drivers/gpu/drm/vc4/vc4_vec.c b/drivers/gpu/drm/vc4/vc4_vec.c
index eb64e881051e..d191747a5e10 100644
--- a/drivers/gpu/drm/vc4/vc4_vec.c
+++ b/drivers/gpu/drm/vc4/vc4_vec.c
@@ -67,7 +67,7 @@
 #define VEC_CONFIG0_YCDELAY		BIT(4)
 #define VEC_CONFIG0_RAMPEN		BIT(2)
 #define VEC_CONFIG0_YCDIS		BIT(2)
-#define VEC_CONFIG0_STD_MASK		GENMASK(1, 0)
+#define VEC_CONFIG0_STD_MASK		(VEC_CONFIG0_SECAM_STD | GENMASK(1, 0))
 #define VEC_CONFIG0_NTSC_STD		0
 #define VEC_CONFIG0_PAL_BDGHI_STD	1
 #define VEC_CONFIG0_PAL_M_STD		2
@@ -186,6 +186,8 @@
 #define VEC_DAC_MISC_DAC_RST_N		BIT(0)
 
 
+static char *vc4_vec_tv_norm;
+
 struct vc4_vec_variant {
 	u32 dac_config;
 };
@@ -272,6 +274,18 @@ static const struct debugfs_reg32 vec_regs[] = {
 	VC4_REG32(VEC_DAC_MISC),
 };
 
+static const struct drm_display_mode drm_mode_240p = {
+	DRM_MODE("720x240", DRM_MODE_TYPE_DRIVER, 13500,
+		 720, 720 + 14, 720 + 14 + 64, 720 + 14 + 64 + 60, 0,
+		 240, 240 + 3, 240 + 3 + 3, 262, 0, 0)
+};
+
+static const struct drm_display_mode drm_mode_288p = {
+	DRM_MODE("720x288", DRM_MODE_TYPE_DRIVER, 13500,
+		 720, 720 + 20, 720 + 20 + 64, 720 + 20 + 64 + 60, 0,
+		 288, 288 + 2, 288 + 2 + 3, 312, 0, 0)
+};
+
 static const struct vc4_vec_tv_mode vc4_vec_tv_modes[] = {
 	{
 		.mode = DRM_MODE_TV_MODE_NTSC,
@@ -371,6 +385,35 @@ static const struct drm_prop_enum_list legacy_tv_mode_names[] = {
 	{ VC4_VEC_TV_MODE_MONOCHROME, "Mono", },
 };
 
+static enum drm_connector_tv_mode
+vc4_vec_get_default_mode(struct drm_connector *connector)
+{
+	if (connector->cmdline_mode.tv_mode_specified) {
+		return connector->cmdline_mode.tv_mode;
+	} else if (vc4_vec_tv_norm) {
+		int ret;
+
+		ret = drm_get_tv_mode_from_name(vc4_vec_tv_norm, strlen(vc4_vec_tv_norm));
+		if (ret >= 0)
+			return ret;
+	} else if (connector->cmdline_mode.specified &&
+		   ((connector->cmdline_mode.refresh_specified &&
+		     (connector->cmdline_mode.refresh == 25 ||
+		      connector->cmdline_mode.refresh == 50)) ||
+		    (!connector->cmdline_mode.refresh_specified &&
+		     (connector->cmdline_mode.yres == 288 ||
+		      connector->cmdline_mode.yres == 576)))) {
+		/*
+		 * no explicitly specified TV norm; use PAL if a mode that
+		 * looks like PAL has been specified on the command line
+		 */
+		return DRM_MODE_TV_MODE_PAL;
+	}
+
+	/* in all other cases, default to NTSC */
+	return DRM_MODE_TV_MODE_NTSC;
+}
+
 static enum drm_connector_status
 vc4_vec_connector_detect(struct drm_connector *connector, bool force)
 {
@@ -436,52 +479,55 @@ vc4_vec_connector_set_property(struct drm_connector *connector,
 }
 
 static int
-vc4_vec_connector_get_property(struct drm_connector *connector,
-			       const struct drm_connector_state *state,
-			       struct drm_property *property,
-			       uint64_t *val)
+vc4_vec_generic_tv_mode_to_legacy(enum drm_connector_tv_mode tv_mode)
 {
-	struct vc4_vec *vec = connector_to_vc4_vec(connector);
-
-	if (property != vec->legacy_tv_mode_property)
-		return -EINVAL;
-
-	switch (state->tv.mode) {
+	switch (tv_mode) {
 	case DRM_MODE_TV_MODE_NTSC:
-		*val = VC4_VEC_TV_MODE_NTSC;
-		break;
+		return VC4_VEC_TV_MODE_NTSC;
 
 	case DRM_MODE_TV_MODE_NTSC_443:
-		*val = VC4_VEC_TV_MODE_NTSC_443;
-		break;
+		return VC4_VEC_TV_MODE_NTSC_443;
 
 	case DRM_MODE_TV_MODE_NTSC_J:
-		*val = VC4_VEC_TV_MODE_NTSC_J;
-		break;
+		return VC4_VEC_TV_MODE_NTSC_J;
 
 	case DRM_MODE_TV_MODE_PAL:
-		*val = VC4_VEC_TV_MODE_PAL;
-		break;
+		return VC4_VEC_TV_MODE_PAL;
 
 	case DRM_MODE_TV_MODE_PAL_M:
-		*val = VC4_VEC_TV_MODE_PAL_M;
-		break;
+		return VC4_VEC_TV_MODE_PAL_M;
 
 	case DRM_MODE_TV_MODE_PAL_N:
-		*val = VC4_VEC_TV_MODE_PAL_N;
-		break;
+		return VC4_VEC_TV_MODE_PAL_N;
 
 	case DRM_MODE_TV_MODE_SECAM:
-		*val = VC4_VEC_TV_MODE_SECAM;
-		break;
+		return VC4_VEC_TV_MODE_SECAM;
 
 	case DRM_MODE_TV_MODE_MONOCHROME:
-		*val = VC4_VEC_TV_MODE_MONOCHROME;
-		break;
+		return VC4_VEC_TV_MODE_MONOCHROME;
 
 	default:
 		return -EINVAL;
 	}
+}
+
+static int
+vc4_vec_connector_get_property(struct drm_connector *connector,
+			       const struct drm_connector_state *state,
+			       struct drm_property *property,
+			       uint64_t *val)
+{
+	struct vc4_vec *vec = connector_to_vc4_vec(connector);
+	enum vc4_vec_tv_mode_id legacy_mode;
+
+	if (property != vec->legacy_tv_mode_property)
+		return -EINVAL;
+
+	legacy_mode = vc4_vec_generic_tv_mode_to_legacy(state->tv.mode);
+	if (legacy_mode < 0)
+		return legacy_mode;
+
+	*val = legacy_mode;
 
 	return 0;
 }
@@ -496,14 +542,38 @@ static const struct drm_connector_funcs vc4_vec_connector_funcs = {
 	.atomic_set_property = vc4_vec_connector_set_property,
 };
 
+static int vc4_vec_connector_get_modes(struct drm_connector *connector)
+{
+	struct drm_display_mode *mode;
+	int count = drm_connector_helper_tv_get_modes(connector);
+
+	mode = drm_mode_duplicate(connector->dev, &drm_mode_240p);
+	if (!mode)
+		return -ENOMEM;
+
+	drm_mode_probed_add(connector, mode);
+	count++;
+
+	mode = drm_mode_duplicate(connector->dev, &drm_mode_288p);
+	if (!mode)
+		return -ENOMEM;
+
+	drm_mode_probed_add(connector, mode);
+	count++;
+
+	return count;
+}
+
 static const struct drm_connector_helper_funcs vc4_vec_connector_helper_funcs = {
 	.atomic_check = drm_atomic_helper_connector_tv_check,
-	.get_modes = drm_connector_helper_tv_get_modes,
+	.get_modes = vc4_vec_connector_get_modes,
 };
 
 static int vc4_vec_connector_init(struct drm_device *dev, struct vc4_vec *vec)
 {
 	struct drm_connector *connector = &vec->connector;
+	enum vc4_vec_tv_mode_id legacy_default_mode;
+	enum drm_connector_tv_mode default_mode;
 	struct drm_property *prop;
 	int ret;
 
@@ -516,9 +586,17 @@ static int vc4_vec_connector_init(struct drm_device *dev, struct vc4_vec *vec)
 
 	drm_connector_helper_add(connector, &vc4_vec_connector_helper_funcs);
 
+	default_mode = vc4_vec_get_default_mode(connector);
+	if (default_mode < 0)
+		return default_mode;
+
 	drm_object_attach_property(&connector->base,
 				   dev->mode_config.tv_mode_property,
-				   DRM_MODE_TV_MODE_NTSC);
+				   default_mode);
+
+	legacy_default_mode = vc4_vec_generic_tv_mode_to_legacy(default_mode);
+	if (legacy_default_mode < 0)
+		return legacy_default_mode;
 
 	prop = drm_property_create_enum(dev, 0, "mode",
 					legacy_tv_mode_names,
@@ -527,7 +605,7 @@ static int vc4_vec_connector_init(struct drm_device *dev, struct vc4_vec *vec)
 		return -ENOMEM;
 	vec->legacy_tv_mode_property = prop;
 
-	drm_object_attach_property(&connector->base, prop, VC4_VEC_TV_MODE_NTSC);
+	drm_object_attach_property(&connector->base, prop, legacy_default_mode);
 
 	drm_connector_attach_tv_margin_properties(connector);
 
@@ -854,3 +932,10 @@ struct platform_driver vc4_vec_driver = {
 		.of_match_table = vc4_vec_dt_match,
 	},
 };
+
+module_param_named(tv_norm, vc4_vec_tv_norm, charp, 0600);
+MODULE_PARM_DESC(tv_norm, "Default TV norm.\n"
+		 "\t\tSupported: NTSC, NTSC-J, NTSC-443, PAL, PAL-M, PAL-N,\n"
+		 "\t\t\tPAL60, SECAM.\n"
+		 "\t\tDefault: PAL if a 50 Hz mode has been set via video=,\n"
+		 "\t\t\tNTSC otherwise");
diff --git a/drivers/gpu/drm/vc4/vc_image_types.h b/drivers/gpu/drm/vc4/vc_image_types.h
new file mode 100644
index 000000000000..e8d2b4b162f7
--- /dev/null
+++ b/drivers/gpu/drm/vc4/vc_image_types.h
@@ -0,0 +1,175 @@
+
+/*
+ * Copyright (c) 2012, Broadcom Europe Ltd
+ *
+ * Values taken from vc_image_types.h released by Broadcom at
+ * https://github.com/raspberrypi/userland/blob/master/interface/vctypes/vc_image_types.h
+ * and vc_image_structs.h at
+ * https://github.com/raspberrypi/userland/blob/master/interface/vctypes/vc_image_structs.h
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+enum {
+	VC_IMAGE_MIN = 0, //bounds for error checking
+
+	VC_IMAGE_RGB565 = 1,
+	VC_IMAGE_1BPP,
+	VC_IMAGE_YUV420,
+	VC_IMAGE_48BPP,
+	VC_IMAGE_RGB888,
+	VC_IMAGE_8BPP,
+	/* 4bpp palettised image */
+	VC_IMAGE_4BPP,
+	/* A separated format of 16 colour/light shorts followed by 16 z
+	 * values
+	 */
+	VC_IMAGE_3D32,
+	/* 16 colours followed by 16 z values */
+	VC_IMAGE_3D32B,
+	/* A separated format of 16 material/colour/light shorts followed by
+	 * 16 z values
+	 */
+	VC_IMAGE_3D32MAT,
+	/* 32 bit format containing 18 bits of 6.6.6 RGB, 9 bits per short */
+	VC_IMAGE_RGB2X9,
+	/* 32-bit format holding 18 bits of 6.6.6 RGB */
+	VC_IMAGE_RGB666,
+	/* 4bpp palettised image with embedded palette */
+	VC_IMAGE_PAL4_OBSOLETE,
+	/* 8bpp palettised image with embedded palette */
+	VC_IMAGE_PAL8_OBSOLETE,
+	/* RGB888 with an alpha byte after each pixel */
+	VC_IMAGE_RGBA32,
+	/* a line of Y (32-byte padded), a line of U (16-byte padded), and a
+	 * line of V (16-byte padded)
+	 */
+	VC_IMAGE_YUV422,
+	/* RGB565 with a transparent patch */
+	VC_IMAGE_RGBA565,
+	/* Compressed (4444) version of RGBA32 */
+	VC_IMAGE_RGBA16,
+	/* VCIII codec format */
+	VC_IMAGE_YUV_UV,
+	/* VCIII T-format RGBA8888 */
+	VC_IMAGE_TF_RGBA32,
+	/* VCIII T-format RGBx8888 */
+	VC_IMAGE_TF_RGBX32,
+	/* VCIII T-format float */
+	VC_IMAGE_TF_FLOAT,
+	/* VCIII T-format RGBA4444 */
+	VC_IMAGE_TF_RGBA16,
+	/* VCIII T-format RGB5551 */
+	VC_IMAGE_TF_RGBA5551,
+	/* VCIII T-format RGB565 */
+	VC_IMAGE_TF_RGB565,
+	/* VCIII T-format 8-bit luma and 8-bit alpha */
+	VC_IMAGE_TF_YA88,
+	/* VCIII T-format 8 bit generic sample */
+	VC_IMAGE_TF_BYTE,
+	/* VCIII T-format 8-bit palette */
+	VC_IMAGE_TF_PAL8,
+	/* VCIII T-format 4-bit palette */
+	VC_IMAGE_TF_PAL4,
+	/* VCIII T-format Ericsson Texture Compressed */
+	VC_IMAGE_TF_ETC1,
+	/* RGB888 with R & B swapped */
+	VC_IMAGE_BGR888,
+	/* RGB888 with R & B swapped, but with no pitch, i.e. no padding after
+	 * each row of pixels
+	 */
+	VC_IMAGE_BGR888_NP,
+	/* Bayer image, extra defines which variant is being used */
+	VC_IMAGE_BAYER,
+	/* General wrapper for codec images e.g. JPEG from camera */
+	VC_IMAGE_CODEC,
+	/* VCIII codec format */
+	VC_IMAGE_YUV_UV32,
+	/* VCIII T-format 8-bit luma */
+	VC_IMAGE_TF_Y8,
+	/* VCIII T-format 8-bit alpha */
+	VC_IMAGE_TF_A8,
+	/* VCIII T-format 16-bit generic sample */
+	VC_IMAGE_TF_SHORT,
+	/* VCIII T-format 1bpp black/white */
+	VC_IMAGE_TF_1BPP,
+	VC_IMAGE_OPENGL,
+	/* VCIII-B0 HVS YUV 4:4:4 interleaved samples */
+	VC_IMAGE_YUV444I,
+	/* Y, U, & V planes separately (VC_IMAGE_YUV422 has them interleaved on
+	 * a per line basis)
+	 */
+	VC_IMAGE_YUV422PLANAR,
+	/* 32bpp with 8bit alpha at MS byte, with R, G, B (LS byte) */
+	VC_IMAGE_ARGB8888,
+	/* 32bpp with 8bit unused at MS byte, with R, G, B (LS byte) */
+	VC_IMAGE_XRGB8888,
+
+	/* interleaved 8 bit samples of Y, U, Y, V (4 flavours) */
+	VC_IMAGE_YUV422YUYV,
+	VC_IMAGE_YUV422YVYU,
+	VC_IMAGE_YUV422UYVY,
+	VC_IMAGE_YUV422VYUY,
+
+	/* 32bpp like RGBA32 but with unused alpha */
+	VC_IMAGE_RGBX32,
+	/* 32bpp, corresponding to RGBA with unused alpha */
+	VC_IMAGE_RGBX8888,
+	/* 32bpp, corresponding to BGRA with unused alpha */
+	VC_IMAGE_BGRX8888,
+
+	/* Y as a plane, then UV byte interleaved in plane with same pitch,
+	 * half height
+	 */
+	VC_IMAGE_YUV420SP,
+
+	/* Y, U, & V planes separately 4:4:4 */
+	VC_IMAGE_YUV444PLANAR,
+
+	/* T-format 8-bit U - same as TF_Y8 buf from U plane */
+	VC_IMAGE_TF_U8,
+	/* T-format 8-bit U - same as TF_Y8 buf from V plane */
+	VC_IMAGE_TF_V8,
+
+	/* YUV4:2:0 planar, 16bit values */
+	VC_IMAGE_YUV420_16,
+	/* YUV4:2:0 codec format, 16bit values */
+	VC_IMAGE_YUV_UV_16,
+	/* YUV4:2:0 with U,V in side-by-side format */
+	VC_IMAGE_YUV420_S,
+	/* 10-bit YUV 420 column image format */
+	VC_IMAGE_YUV10COL,
+	/* 32-bpp, 10-bit R/G/B, 2-bit Alpha */
+	VC_IMAGE_RGBA1010102,
+
+	VC_IMAGE_MAX,     /* bounds for error checking */
+	VC_IMAGE_FORCE_ENUM_16BIT = 0xffff,
+};
+
+enum {
+	/* Unknown or unset - defaults to BT601 interstitial */
+	VC_IMAGE_YUVINFO_UNSPECIFIED    = 0,
+
+	/* colour-space conversions data [4 bits] */
+
+	/* ITU-R BT.601-5 [SDTV] (compatible with VideoCore-II) */
+	VC_IMAGE_YUVINFO_CSC_ITUR_BT601      = 1,
+	/* ITU-R BT.709-3 [HDTV] */
+	VC_IMAGE_YUVINFO_CSC_ITUR_BT709      = 2,
+	/* JPEG JFIF */
+	VC_IMAGE_YUVINFO_CSC_JPEG_JFIF       = 3,
+	/* Title 47 Code of Federal Regulations (2003) 73.682 (a) (20) */
+	VC_IMAGE_YUVINFO_CSC_FCC             = 4,
+	/* Society of Motion Picture and Television Engineers 240M (1999) */
+	VC_IMAGE_YUVINFO_CSC_SMPTE_240M      = 5,
+	/* ITU-R BT.470-2 System M */
+	VC_IMAGE_YUVINFO_CSC_ITUR_BT470_2_M  = 6,
+	/* ITU-R BT.470-2 System B,G */
+	VC_IMAGE_YUVINFO_CSC_ITUR_BT470_2_BG = 7,
+	/* JPEG JFIF, but with 16..255 luma */
+	VC_IMAGE_YUVINFO_CSC_JPEG_JFIF_Y16_255 = 8,
+	/* Rec 2020 */
+	VC_IMAGE_YUVINFO_CSC_REC_2020        = 9,
+};
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 25eb5cc7de70..95fa88874b76 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -255,6 +255,9 @@
 #define USB_VENDOR_ID_BAANTO		0x2453
 #define USB_DEVICE_ID_BAANTO_MT_190W2	0x0100
 
+#define USB_VENDOR_ID_BEKEN		0x25a7
+#define USB_DEVICE_ID_AIRMOUSE_T3	0x2402
+
 #define USB_VENDOR_ID_BELKIN		0x050d
 #define USB_DEVICE_ID_FLIP_KVM		0x3201
 
@@ -1481,6 +1484,9 @@
 #define USB_VENDOR_ID_XIAOMI		0x2717
 #define USB_DEVICE_ID_MI_SILENT_MOUSE	0x5014
 
+#define USB_VENDOR_ID_XENTA			0x1d57
+#define USB_DEVICE_ID_AIRMOUSE_MX3		0xad03
+
 #define USB_VENDOR_ID_XIN_MO			0x16c0
 #define USB_DEVICE_ID_XIN_MO_DUAL_ARCADE	0x05e1
 #define USB_DEVICE_ID_THT_2P_ARCADE		0x75e1
diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c
index 7a3e0675d9ba..7a7d83bae8b9 100644
--- a/drivers/hid/hid-quirks.c
+++ b/drivers/hid/hid-quirks.c
@@ -44,6 +44,7 @@ static const struct hid_device_id hid_quirks[] = {
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS682), HID_QUIRK_NOGET },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_CS692), HID_QUIRK_NOGET },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_ATEN, USB_DEVICE_ID_ATEN_UC100KM), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_BEKEN, USB_DEVICE_ID_AIRMOUSE_T3), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_MULTI_TOUCH), HID_QUIRK_MULTI_INPUT },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE), HID_QUIRK_ALWAYS_POLL },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_CHICONY, USB_DEVICE_ID_CHICONY_PIXART_USB_OPTICAL_MOUSE2), HID_QUIRK_ALWAYS_POLL },
@@ -216,6 +217,7 @@ static const struct hid_device_id hid_quirks[] = {
 	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_QUAD_USB_JOYPAD), HID_QUIRK_NOGET | HID_QUIRK_MULTI_INPUT },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_XIN_MO, USB_DEVICE_ID_XIN_MO_DUAL_ARCADE), HID_QUIRK_MULTI_INPUT },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_GROUP_AUDIO), HID_QUIRK_NOGET },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_XENTA, USB_DEVICE_ID_AIRMOUSE_MX3), HID_QUIRK_INCREMENT_USAGE_ON_DUPLICATE },
 
 	{ 0 }
 };
diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c
index a2c5a31931f6..ef6a1b65bd26 100644
--- a/drivers/hid/usbhid/hid-core.c
+++ b/drivers/hid/usbhid/hid-core.c
@@ -47,7 +47,7 @@
  * Module parameters.
  */
 
-static unsigned int hid_mousepoll_interval;
+static unsigned int hid_mousepoll_interval = ~0;
 module_param_named(mousepoll, hid_mousepoll_interval, uint, 0644);
 MODULE_PARM_DESC(mousepoll, "Polling interval of mice");
 
@@ -1132,7 +1132,9 @@ static int usbhid_start(struct hid_device *hid)
 		 */
 		switch (hid->collection->usage) {
 		case HID_GD_MOUSE:
-			if (hid_mousepoll_interval > 0)
+			if (hid_mousepoll_interval == ~0 && interval < 16)
+				interval = 16;
+			else if (hid_mousepoll_interval != ~0 && hid_mousepoll_interval != 0)
 				interval = hid_mousepoll_interval;
 			break;
 		case HID_GD_JOYSTICK:
@@ -1144,6 +1146,7 @@ static int usbhid_start(struct hid_device *hid)
 				interval = hid_kbpoll_interval;
 			break;
 		}
+		usb_fixup_endpoint(dev, endpoint->bEndpointAddress, interval);
 
 		ret = -ENOMEM;
 		if (usb_endpoint_dir_in(endpoint)) {
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 19622dd6ec93..d387801c4a85 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -2167,11 +2167,12 @@ config SENSORS_INA2XX
 	select REGMAP_I2C
 	help
 	  If you say yes here you get support for INA219, INA220, INA226,
-	  INA230, and INA231 power monitor chips.
+	  INA230, INA231, and INA260 power monitor chips.
 
 	  The INA2xx driver is configured for the default configuration of
 	  the part as described in the datasheet.
-	  Default value for Rshunt is 10 mOhms.
+	  Default value for Rshunt is 10 mOhms except for INA260 which has an
+	  internal 2 mOhm shunt resistor.
 	  This driver can also be built as a module. If so, the module
 	  will be called ina2xx.
 
@@ -2551,6 +2552,13 @@ config SENSORS_INTEL_M10_BMC_HWMON
 	  sensors monitor various telemetry data of different components on the
 	  card, e.g. board temperature, FPGA core temperature/voltage/current.
 
+config SENSORS_RP1_ADC
+	tristate "RP1 ADC and temperature sensor driver"
+	depends on MFD_RP1
+	help
+	  Say yes here to enable support for the voltage and temperature
+	  sensors of the Raspberry Pi RP1 peripheral chip.
+
 if ACPI
 
 comment "ACPI drivers"
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 9554d2fdcf7b..d640c694f661 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -188,6 +188,7 @@ obj-$(CONFIG_SENSORS_POWR1220)  += powr1220.o
 obj-$(CONFIG_SENSORS_PT5161L)	+= pt5161l.o
 obj-$(CONFIG_SENSORS_PWM_FAN)	+= pwm-fan.o
 obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON)	+= raspberrypi-hwmon.o
+obj-$(CONFIG_SENSORS_RP1_ADC)	+= rp1-adc.o
 obj-$(CONFIG_SENSORS_SBTSI)	+= sbtsi_temp.o
 obj-$(CONFIG_SENSORS_SBRMI)	+= sbrmi.o
 obj-$(CONFIG_SENSORS_SCH56XX_COMMON)+= sch56xx-common.o
diff --git a/drivers/hwmon/adt7410.c b/drivers/hwmon/adt7410.c
index 3bf0e0a0882c..1b52d90815ac 100644
--- a/drivers/hwmon/adt7410.c
+++ b/drivers/hwmon/adt7410.c
@@ -94,9 +94,17 @@ static const struct i2c_device_id adt7410_ids[] = {
 };
 MODULE_DEVICE_TABLE(i2c, adt7410_ids);
 
+static const struct of_device_id adt7410_of_ids[] = {
+	{ .compatible = "adi,adt7410" },
+	{ .compatible = "adi,adt7420" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, adt7410_of_ids);
+
 static struct i2c_driver adt7410_driver = {
 	.driver = {
 		.name	= "adt7410",
+		.of_match_table = adt7410_of_ids,
 		.pm	= pm_sleep_ptr(&adt7x10_dev_pm_ops),
 	},
 	.probe		= adt7410_i2c_probe,
diff --git a/drivers/hwmon/aht10.c b/drivers/hwmon/aht10.c
index 4099b5ba0982..30319ce9d9b2 100644
--- a/drivers/hwmon/aht10.c
+++ b/drivers/hwmon/aht10.c
@@ -62,6 +62,12 @@ static const struct i2c_device_id aht10_id[] = {
 };
 MODULE_DEVICE_TABLE(i2c, aht10_id);
 
+static const struct of_device_id aht10_of_id[] = {
+	{ .compatible = "aosong,aht10", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, aht10_of_id);
+
 /**
  *   struct aht10_data - All the data required to operate an AHT10/AHT20 chip
  *   @client: the i2c client associated with the AHT10/AHT20
@@ -395,6 +401,7 @@ static int aht10_probe(struct i2c_client *client)
 static struct i2c_driver aht10_driver = {
 	.driver = {
 		.name = "aht10",
+		.of_match_table = aht10_of_id,
 	},
 	.probe      = aht10_probe,
 	.id_table   = aht10_id,
diff --git a/drivers/hwmon/ds1621.c b/drivers/hwmon/ds1621.c
index 42ec34cb8a5f..30d2a771054f 100644
--- a/drivers/hwmon/ds1621.c
+++ b/drivers/hwmon/ds1621.c
@@ -376,6 +376,16 @@ static const struct i2c_device_id ds1621_id[] = {
 };
 MODULE_DEVICE_TABLE(i2c, ds1621_id);
 
+static const struct of_device_id ds1621_of_ids[] = {
+	{ .compatible = "dallas,ds1621", },
+	{ .compatible = "dallas,ds1625", },
+	{ .compatible = "dallas,ds1631", },
+	{ .compatible = "dallas,ds1721", },
+	{ .compatible = "dallas,ds1731", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ds1621_of_ids);
+
 /* This is the driver that will be inserted */
 static struct i2c_driver ds1621_driver = {
 	.driver = {
diff --git a/drivers/hwmon/emc2305.c b/drivers/hwmon/emc2305.c
index 5b5fccac9635..1a6712eb5962 100644
--- a/drivers/hwmon/emc2305.c
+++ b/drivers/hwmon/emc2305.c
@@ -12,12 +12,13 @@
 #include <linux/platform_data/emc2305.h>
 #include <linux/thermal.h>
 
+#define EMC2305_REG_FAN_STATUS		0x24
+#define EMC2305_REG_FAN_STALL_STATUS	0x25
 #define EMC2305_REG_DRIVE_FAIL_STATUS	0x27
 #define EMC2305_REG_VENDOR		0xfe
 #define EMC2305_FAN_MAX			0xff
 #define EMC2305_FAN_MIN			0x00
 #define EMC2305_FAN_MAX_STATE		10
-#define EMC2305_DEVICE			0x34
 #define EMC2305_VENDOR			0x5d
 #define EMC2305_REG_PRODUCT_ID		0xfd
 #define EMC2305_TACH_REGS_UNUSE_BITS	3
@@ -36,6 +37,7 @@
 #define EMC2305_RPM_FACTOR		3932160
 
 #define EMC2305_REG_FAN_DRIVE(n)	(0x30 + 0x10 * (n))
+#define EMC2305_REG_FAN_CFG(n)		(0x32 + 0x10 * (n))
 #define EMC2305_REG_FAN_MIN_DRIVE(n)	(0x38 + 0x10 * (n))
 #define EMC2305_REG_FAN_TACH(n)		(0x3e + 0x10 * (n))
 
@@ -55,6 +57,15 @@ static const struct i2c_device_id emc2305_ids[] = {
 };
 MODULE_DEVICE_TABLE(i2c, emc2305_ids);
 
+static const struct of_device_id emc2305_dt_ids[] = {
+	{ .compatible = "microchip,emc2305" },
+	{ .compatible = "microchip,emc2303" },
+	{ .compatible = "microchip,emc2302" },
+	{ .compatible = "microchip,emc2301" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, emc2305_dt_ids);
+
 /**
  * struct emc2305_cdev_data - device-specific cooling device state
  * @cdev: cooling device
@@ -100,6 +111,7 @@ struct emc2305_data {
 	u8 pwm_num;
 	bool pwm_separate;
 	u8 pwm_min[EMC2305_PWM_MAX];
+	u8 pwm_max;
 	struct emc2305_cdev_data cdev_data[EMC2305_PWM_MAX];
 };
 
@@ -272,7 +284,7 @@ static int emc2305_set_pwm(struct device *dev, long val, int channel)
 	struct i2c_client *client = data->client;
 	int ret;
 
-	if (val < data->pwm_min[channel] || val > EMC2305_FAN_MAX)
+	if (val < data->pwm_min[channel] || val > data->pwm_max)
 		return -EINVAL;
 
 	ret = i2c_smbus_write_byte_data(client, EMC2305_REG_FAN_DRIVE(channel), val);
@@ -283,6 +295,49 @@ static int emc2305_set_pwm(struct device *dev, long val, int channel)
 	return 0;
 }
 
+static int emc2305_get_tz_of(struct device *dev)
+{
+	struct device_node *np = dev->of_node;
+	struct emc2305_data *data = dev_get_drvdata(dev);
+	int ret = 0;
+	u8 val;
+	int i;
+
+	/* OF parameters are optional - overwrite default setting
+	 * if some of them are provided.
+	 */
+
+	ret = of_property_read_u8(np, "emc2305,cooling-levels", &val);
+	if (!ret)
+		data->max_state = val;
+	else if (ret != -EINVAL)
+		return ret;
+
+	ret = of_property_read_u8(np, "emc2305,pwm-max", &val);
+	if (!ret)
+		data->pwm_max = val;
+	else if (ret != -EINVAL)
+		return ret;
+
+	ret = of_property_read_u8(np, "emc2305,pwm-min", &val);
+	if (!ret)
+		for (i = 0; i < EMC2305_PWM_MAX; i++)
+			data->pwm_min[i] = val;
+	else if (ret != -EINVAL)
+		return ret;
+
+	/* Not defined or 0 means one thermal zone over all cooling devices.
+	 * Otherwise - separated thermal zones for each PWM channel.
+	 */
+	ret = of_property_read_u8(np, "emc2305,pwm-channel", &val);
+	if (!ret)
+		data->pwm_separate = (val != 0);
+	else if (ret != -EINVAL)
+		return ret;
+
+	return 0;
+}
+
 static int emc2305_set_single_tz(struct device *dev, int idx)
 {
 	struct emc2305_data *data = dev_get_drvdata(dev);
@@ -292,9 +347,17 @@ static int emc2305_set_single_tz(struct device *dev, int idx)
 	cdev_idx = (idx) ? idx - 1 : 0;
 	pwm = data->pwm_min[cdev_idx];
 
-	data->cdev_data[cdev_idx].cdev =
-		thermal_cooling_device_register(emc2305_fan_name[idx], data,
-						&emc2305_cooling_ops);
+	if (dev->of_node)
+		data->cdev_data[cdev_idx].cdev =
+			devm_thermal_of_cooling_device_register(dev, dev->of_node,
+								emc2305_fan_name[idx],
+								data,
+								&emc2305_cooling_ops);
+	else
+		data->cdev_data[cdev_idx].cdev =
+			thermal_cooling_device_register(emc2305_fan_name[idx],
+							data,
+							&emc2305_cooling_ops);
 
 	if (IS_ERR(data->cdev_data[cdev_idx].cdev)) {
 		dev_err(dev, "Failed to register cooling device %s\n", emc2305_fan_name[idx]);
@@ -353,9 +416,11 @@ static void emc2305_unset_tz(struct device *dev)
 	int i;
 
 	/* Unregister cooling device. */
-	for (i = 0; i < EMC2305_PWM_MAX; i++)
-		if (data->cdev_data[i].cdev)
-			thermal_cooling_device_unregister(data->cdev_data[i].cdev);
+	if (!dev->of_node) {
+		for (i = 0; i < EMC2305_PWM_MAX; i++)
+			if (data->cdev_data[i].cdev)
+				thermal_cooling_device_unregister(data->cdev_data[i].cdev);
+	}
 }
 
 static umode_t
@@ -577,11 +642,18 @@ static int emc2305_probe(struct i2c_client *client)
 		data->pwm_separate = pdata->pwm_separate;
 		for (i = 0; i < EMC2305_PWM_MAX; i++)
 			data->pwm_min[i] = pdata->pwm_min[i];
+		data->pwm_max = EMC2305_FAN_MAX;
 	} else {
 		data->max_state = EMC2305_FAN_MAX_STATE;
 		data->pwm_separate = false;
 		for (i = 0; i < EMC2305_PWM_MAX; i++)
 			data->pwm_min[i] = EMC2305_FAN_MIN;
+		data->pwm_max = EMC2305_FAN_MAX;
+		if (dev->of_node) {
+			ret = emc2305_get_tz_of(dev);
+			if (ret < 0)
+				return ret;
+		}
 	}
 
 	data->hwmon_dev = devm_hwmon_device_register_with_info(dev, "emc2305", data,
@@ -602,6 +674,12 @@ static int emc2305_probe(struct i2c_client *client)
 			return ret;
 	}
 
+	/* Acknowledge any existing faults. Stops the device responding on the
+	 * SMBus alert address.
+	 */
+	i2c_smbus_read_byte_data(client, EMC2305_REG_FAN_STALL_STATUS);
+	i2c_smbus_read_byte_data(client, EMC2305_REG_FAN_STATUS);
+
 	return 0;
 }
 
@@ -616,6 +694,7 @@ static void emc2305_remove(struct i2c_client *client)
 static struct i2c_driver emc2305_driver = {
 	.driver = {
 		.name = "emc2305",
+		.of_match_table = emc2305_dt_ids,
 	},
 	.probe = emc2305_probe,
 	.remove	  = emc2305_remove,
diff --git a/drivers/hwmon/ina2xx.c b/drivers/hwmon/ina2xx.c
index f0fa6d073627..514e848cb950 100644
--- a/drivers/hwmon/ina2xx.c
+++ b/drivers/hwmon/ina2xx.c
@@ -56,12 +56,14 @@
 /* settings - depend on use case */
 #define INA219_CONFIG_DEFAULT		0x399F	/* PGA=8 */
 #define INA226_CONFIG_DEFAULT		0x4527	/* averages=16 */
+#define INA260_CONFIG_DEFAULT		0x6527	/* averages=16 */
 
 /* worst case is 68.10 ms (~14.6Hz, ina219) */
 #define INA2XX_CONVERSION_RATE		15
 #define INA2XX_MAX_DELAY		69 /* worst case delay in ms */
 
 #define INA2XX_RSHUNT_DEFAULT		10000
+#define INA260_RSHUNT			2000
 
 /* bit mask for reading the averaging setting in the configuration register */
 #define INA226_AVG_RD_MASK		GENMASK(11, 9)
@@ -125,10 +127,12 @@ static const struct regmap_config ina2xx_regmap_config = {
 	.writeable_reg = ina2xx_writeable_reg,
 };
 
-enum ina2xx_ids { ina219, ina226 };
+enum ina2xx_ids { ina219, ina226, ina260 };
 
 struct ina2xx_config {
 	u16 config_default;
+	bool has_alerts;	/* chip supports alerts and limits */
+	bool has_ishunt;	/* chip has internal shunt resistor */
 	int calibration_value;
 	int shunt_div;
 	int bus_voltage_shift;
@@ -155,6 +159,8 @@ static const struct ina2xx_config ina2xx_config[] = {
 		.bus_voltage_shift = 3,
 		.bus_voltage_lsb = 4000,
 		.power_lsb_factor = 20,
+		.has_alerts = false,
+		.has_ishunt = false,
 	},
 	[ina226] = {
 		.config_default = INA226_CONFIG_DEFAULT,
@@ -163,6 +169,17 @@ static const struct ina2xx_config ina2xx_config[] = {
 		.bus_voltage_shift = 0,
 		.bus_voltage_lsb = 1250,
 		.power_lsb_factor = 25,
+		.has_alerts = true,
+		.has_ishunt = false,
+	},
+	[ina260] = {
+		.config_default = INA260_CONFIG_DEFAULT,
+		.shunt_div = 400,
+		.bus_voltage_shift = 0,
+		.bus_voltage_lsb = 1250,
+		.power_lsb_factor = 8,
+		.has_alerts = true,
+		.has_ishunt = true,
 	},
 };
 
@@ -254,6 +271,15 @@ static int ina2xx_read_init(struct device *dev, int reg, long *val)
 	unsigned int regval;
 	int ret, retry;
 
+	if (data->config->has_ishunt) {
+		/* No calibration needed */
+		ret = regmap_read(regmap, reg, &regval);
+		if (ret < 0)
+			return ret;
+		*val = ina2xx_get_value(data, reg, regval);
+		return 0;
+	}
+
 	for (retry = 5; retry; retry--) {
 		ret = regmap_read(regmap, reg, &regval);
 		if (ret < 0)
@@ -682,7 +708,7 @@ static umode_t ina2xx_is_visible(const void *_data, enum hwmon_sensor_types type
 	case hwmon_chip:
 		switch (attr) {
 		case hwmon_chip_update_interval:
-			if (chip == ina226)
+			if (chip == ina226 || chip == ina260)
 				return 0644;
 			break;
 		default:
@@ -791,7 +817,9 @@ static int ina2xx_init(struct device *dev, struct ina2xx_data *data)
 	u32 shunt;
 	int ret;
 
-	if (device_property_read_u32(dev, "shunt-resistor", &shunt) < 0)
+	if (data->config->has_ishunt)
+		shunt = INA260_RSHUNT;
+	else if (device_property_read_u32(dev, "shunt-resistor", &shunt) < 0)
 		shunt = INA2XX_RSHUNT_DEFAULT;
 
 	ret = ina2xx_set_shunt(data, shunt);
@@ -811,6 +839,9 @@ static int ina2xx_init(struct device *dev, struct ina2xx_data *data)
 						FIELD_PREP(INA226_ALERT_POLARITY, active_high));
 	}
 
+	if (data->config->has_ishunt)
+		return 0;
+
 	/*
 	 * Calibration register is set to the best value, which eliminates
 	 * truncation errors on calculating current register in hardware.
@@ -856,7 +887,8 @@ static int ina2xx_probe(struct i2c_client *client)
 
 	hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name,
 							 data, &ina2xx_chip_info,
-							 ina2xx_groups);
+							 data->config->has_ishunt ?
+								NULL : ina2xx_groups);
 	if (IS_ERR(hwmon_dev))
 		return PTR_ERR(hwmon_dev);
 
@@ -872,6 +904,7 @@ static const struct i2c_device_id ina2xx_id[] = {
 	{ "ina226", ina226 },
 	{ "ina230", ina226 },
 	{ "ina231", ina226 },
+	{ "ina260", ina260 },
 	{ }
 };
 MODULE_DEVICE_TABLE(i2c, ina2xx_id);
@@ -897,6 +930,10 @@ static const struct of_device_id __maybe_unused ina2xx_of_match[] = {
 		.compatible = "ti,ina231",
 		.data = (void *)ina226
 	},
+	{
+		.compatible = "ti,ina260",
+		.data = (void *)ina260
+	},
 	{ },
 };
 MODULE_DEVICE_TABLE(of, ina2xx_of_match);
diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c
index c434db4656e7..2f5e145a3692 100644
--- a/drivers/hwmon/pwm-fan.c
+++ b/drivers/hwmon/pwm-fan.c
@@ -13,6 +13,7 @@
 #include <linux/module.h>
 #include <linux/mutex.h>
 #include <linux/platform_device.h>
+#include <linux/of_address.h>
 #include <linux/property.h>
 #include <linux/pwm.h>
 #include <linux/regulator/consumer.h>
@@ -52,6 +53,9 @@ struct pwm_fan_ctx {
 	ktime_t sample_start;
 	struct timer_list rpm_timer;
 
+	void __iomem *rpm_regbase;
+	unsigned int rpm_offset;
+
 	unsigned int pwm_value;
 	unsigned int pwm_fan_state;
 	unsigned int pwm_fan_max_state;
@@ -62,6 +66,10 @@ struct pwm_fan_ctx {
 	struct hwmon_channel_info fan_channel;
 };
 
+static const u32 rpm_reg_channel_config[] = {
+	HWMON_F_INPUT, 0
+};
+
 /* This handler assumes self resetting edge triggered interrupt. */
 static irqreturn_t pulse_handler(int irq, void *dev_id)
 {
@@ -337,7 +345,10 @@ static int pwm_fan_read(struct device *dev, enum hwmon_sensor_types type,
 		}
 		return -EOPNOTSUPP;
 	case hwmon_fan:
-		*val = ctx->tachs[channel].rpm;
+		if (ctx->rpm_regbase)
+			*val = (long)readl(ctx->rpm_regbase + ctx->rpm_offset);
+		else
+			*val = ctx->tachs[channel].rpm;
 		return 0;
 
 	default:
@@ -470,6 +481,7 @@ static void pwm_fan_cleanup(void *__ctx)
 	/* Switch off everything */
 	ctx->enable_mode = pwm_disable_reg_disable;
 	pwm_fan_power_off(ctx, true);
+	iounmap(ctx->rpm_regbase);
 }
 
 static int pwm_fan_probe(struct platform_device *pdev)
@@ -542,10 +554,23 @@ static int pwm_fan_probe(struct platform_device *pdev)
 		return ret;
 
 	ctx->tach_count = platform_irq_count(pdev);
+	if (ctx->tach_count == 0) {
+		struct device_node *rpm_node;
+
+		rpm_node = of_parse_phandle(dev->of_node, "rpm-regmap", 0);
+		if (rpm_node)
+			ctx->rpm_regbase = of_iomap(rpm_node, 0);
+	}
+
 	if (ctx->tach_count < 0)
 		return dev_err_probe(dev, ctx->tach_count,
 				     "Could not get number of fan tachometer inputs\n");
-	dev_dbg(dev, "%d fan tachometer inputs\n", ctx->tach_count);
+	if (IS_ERR(ctx->rpm_regbase))
+		return dev_err_probe(dev, PTR_ERR(ctx->rpm_regbase),
+				     "Could not get rpm reg\n");
+
+	dev_dbg(dev, "%d fan tachometer inputs, %d rpm regmap\n", ctx->tach_count,
+		!!ctx->rpm_regbase);
 
 	if (ctx->tach_count) {
 		channel_count++;	/* We also have a FAN channel. */
@@ -576,12 +601,24 @@ static int pwm_fan_probe(struct platform_device *pdev)
 
 		device_property_read_u32_array(dev, "pulses-per-revolution",
 					       ctx->pulses_per_revolution, ctx->tach_count);
+	} else if (ctx->rpm_regbase) {
+		channel_count++;	/* We also have a FAN channel. */
+		ctx->fan_channel.type = hwmon_fan;
+		ctx->fan_channel.config = rpm_reg_channel_config;
+
+		if (device_property_read_u32(dev, "rpm-offset", &ctx->rpm_offset)) {
+			dev_err(&pdev->dev, "unable to read 'rpm-offset'");
+			ret = -EINVAL;
+			goto error;
+		}
 	}
 
 	channels = devm_kcalloc(dev, channel_count + 1,
 				sizeof(struct hwmon_channel_info *), GFP_KERNEL);
-	if (!channels)
-		return -ENOMEM;
+	if (!channels) {
+		ret = -ENOMEM;
+		goto error;
+	}
 
 	channels[0] = HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE);
 
@@ -617,6 +654,8 @@ static int pwm_fan_probe(struct platform_device *pdev)
 		ctx->sample_start = ktime_get();
 		mod_timer(&ctx->rpm_timer, jiffies + HZ);
 
+		channels[1] = &ctx->fan_channel;
+	} else if (ctx->rpm_regbase) {
 		channels[1] = &ctx->fan_channel;
 	}
 
@@ -627,12 +666,13 @@ static int pwm_fan_probe(struct platform_device *pdev)
 						     ctx, &ctx->info, NULL);
 	if (IS_ERR(hwmon)) {
 		dev_err(dev, "Failed to register hwmon device\n");
-		return PTR_ERR(hwmon);
+		ret = PTR_ERR(hwmon);
+		goto error;
 	}
 
 	ret = pwm_fan_get_cooling_data(dev, ctx);
 	if (ret)
-		return ret;
+		goto error;
 
 	ctx->pwm_fan_state = ctx->pwm_fan_max_state;
 	if (IS_ENABLED(CONFIG_THERMAL)) {
@@ -643,12 +683,17 @@ static int pwm_fan_probe(struct platform_device *pdev)
 			dev_err(dev,
 				"Failed to register pwm-fan as cooling device: %d\n",
 				ret);
-			return ret;
+			goto error;
 		}
 		ctx->cdev = cdev;
 	}
 
 	return 0;
+
+error:
+	if (ctx->rpm_regbase)
+		iounmap(ctx->rpm_regbase);
+	return ret;
 }
 
 static void pwm_fan_shutdown(struct platform_device *pdev)
diff --git a/drivers/hwmon/rp1-adc.c b/drivers/hwmon/rp1-adc.c
new file mode 100644
index 000000000000..3201a3cfa7a9
--- /dev/null
+++ b/drivers/hwmon/rp1-adc.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for the RP1 ADC and temperature sensor
+ * Copyright (C) 2023 Raspberry Pi Ltd.
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+
+#define MODULE_NAME	"rp1-adc"
+
+#define RP1_ADC_CS		0x00
+#define RP1_ADC_RESULT		0x04
+#define RP1_ADC_FCS		0x08
+#define RP1_ADC_FIFO		0x0c
+#define RP1_ADC_DIV		0x10
+
+#define RP1_ADC_INTR		0x14
+#define RP1_ADC_INTE		0x18
+#define RP1_ADC_INTF		0x1c
+#define RP1_ADC_INTS		0x20
+
+#define RP1_ADC_RWTYPE_SET	0x2000
+#define RP1_ADC_RWTYPE_CLR	0x3000
+
+#define RP1_ADC_CS_RROBIN_MASK	0x1f
+#define RP1_ADC_CS_RROBIN_SHIFT	16
+#define RP1_ADC_CS_AINSEL_MASK	0x7
+#define RP1_ADC_CS_AINSEL_SHIFT	12
+#define RP1_ADC_CS_ERR_STICKY	0x400
+#define RP1_ADC_CS_ERR		0x200
+#define RP1_ADC_CS_READY	0x100
+#define RP1_ADC_CS_START_MANY	0x8
+#define RP1_ADC_CS_START_ONCE	0x4
+#define RP1_ADC_CS_TS_EN	0x2
+#define RP1_ADC_CS_EN		0x1
+
+#define RP1_ADC_FCS_THRESH_MASK	0xf
+#define RP1_ADC_FCS_THRESH_SHIFT	24
+#define RP1_ADC_FCS_LEVEL_MASK	0xf
+#define RP1_ADC_FCS_LEVEL_SHIFT	16
+#define RP1_ADC_FCS_OVER	0x800
+#define RP1_ADC_FCS_UNDER	0x400
+#define RP1_ADC_FCS_FULL	0x200
+#define RP1_ADC_FCS_EMPTY	0x100
+#define RP1_ADC_FCS_DREQ_EN	0x8
+#define RP1_ADC_FCS_ERR		0x4
+#define RP1_ADC_FCS_SHIFR	0x2
+#define RP1_ADC_FCS_EN		0x1
+
+#define RP1_ADC_FIFO_ERR	0x8000
+#define RP1_ADC_FIFO_VAL_MASK	0xfff
+
+#define RP1_ADC_DIV_INT_MASK	0xffff
+#define RP1_ADC_DIV_INT_SHIFT	8
+#define RP1_ADC_DIV_FRAC_MASK	0xff
+#define RP1_ADC_DIV_FRAC_SHIFT	0
+
+struct rp1_adc_data {
+	void __iomem *base;
+	spinlock_t lock;
+	struct device *hwmon_dev;
+	int vref_mv;
+};
+
+static int rp1_adc_ready_wait(struct rp1_adc_data *data)
+{
+	int retries = 10;
+
+	while (retries && !(readl(data->base + RP1_ADC_CS) & RP1_ADC_CS_READY))
+		retries--;
+
+	return retries ? 0 : -EIO;
+}
+
+static int rp1_adc_read(struct rp1_adc_data *data,
+			struct device_attribute *devattr, unsigned int *val)
+{
+	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
+	int channel = attr->index;
+	int ret;
+
+	spin_lock(&data->lock);
+
+	writel(RP1_ADC_CS_AINSEL_MASK << RP1_ADC_CS_AINSEL_SHIFT,
+	       data->base + RP1_ADC_RWTYPE_CLR + RP1_ADC_CS);
+	writel(channel << RP1_ADC_CS_AINSEL_SHIFT,
+	       data->base + RP1_ADC_RWTYPE_SET + RP1_ADC_CS);
+	writel(RP1_ADC_CS_START_ONCE,
+	       data->base + RP1_ADC_RWTYPE_SET + RP1_ADC_CS);
+
+	ret = rp1_adc_ready_wait(data);
+	if (ret)
+		return ret;
+
+	/* Asserted if the completed conversion had a convergence error */
+	if (readl(data->base + RP1_ADC_CS) & RP1_ADC_CS_ERR)
+		return -EIO;
+
+	*val = readl(data->base + RP1_ADC_RESULT);
+
+	spin_unlock(&data->lock);
+
+	return ret;
+}
+
+static int rp1_adc_to_mv(struct rp1_adc_data *data, unsigned int val)
+{
+	return ((u64)data->vref_mv * val) / 0xfff;
+}
+
+static ssize_t rp1_adc_show(struct device *dev,
+			    struct device_attribute *devattr,
+			    char *buf)
+{
+	struct rp1_adc_data *data = dev_get_drvdata(dev);
+	unsigned int val;
+	int ret;
+
+	ret = rp1_adc_read(data, devattr, &val);
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%d\n", rp1_adc_to_mv(data, val));
+}
+
+static ssize_t rp1_adc_temp_show(struct device *dev,
+				 struct device_attribute *devattr,
+				 char *buf)
+{
+	struct rp1_adc_data *data = dev_get_drvdata(dev);
+	unsigned int val;
+	int ret, mv, mc;
+
+	writel(RP1_ADC_CS_TS_EN,
+	       data->base + RP1_ADC_RWTYPE_SET + RP1_ADC_CS);
+	ret = rp1_adc_read(data, devattr, &val);
+	if (ret)
+		return ret;
+
+	mv = rp1_adc_to_mv(data, val);
+
+	/* T = 27 - (ADC_voltage - 0.706)/0.001721 */
+
+	mc = 27000 - DIV_ROUND_CLOSEST((mv - 706) * (s64)1000000, 1721);
+
+	return sprintf(buf, "%d\n", mc);
+}
+
+static ssize_t rp1_adc_raw_show(struct device *dev,
+				struct device_attribute *devattr,
+				char *buf)
+{
+	struct rp1_adc_data *data = dev_get_drvdata(dev);
+	unsigned int val;
+	int ret = rp1_adc_read(data, devattr, &val);
+
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%u\n", val);
+}
+
+static ssize_t rp1_adc_temp_raw_show(struct device *dev,
+				     struct device_attribute *devattr,
+				     char *buf)
+{
+	struct rp1_adc_data *data = dev_get_drvdata(dev);
+	unsigned int val;
+	int ret = rp1_adc_read(data, devattr, &val);
+
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%u\n", val);
+}
+
+static SENSOR_DEVICE_ATTR_RO(in1_input, rp1_adc, 0);
+static SENSOR_DEVICE_ATTR_RO(in2_input, rp1_adc, 1);
+static SENSOR_DEVICE_ATTR_RO(in3_input, rp1_adc, 2);
+static SENSOR_DEVICE_ATTR_RO(in4_input, rp1_adc, 3);
+static SENSOR_DEVICE_ATTR_RO(temp1_input, rp1_adc_temp, 4);
+static SENSOR_DEVICE_ATTR_RO(in1_raw, rp1_adc_raw, 0);
+static SENSOR_DEVICE_ATTR_RO(in2_raw, rp1_adc_raw, 1);
+static SENSOR_DEVICE_ATTR_RO(in3_raw, rp1_adc_raw, 2);
+static SENSOR_DEVICE_ATTR_RO(in4_raw, rp1_adc_raw, 3);
+static SENSOR_DEVICE_ATTR_RO(temp1_raw, rp1_adc_temp_raw, 4);
+
+static struct attribute *rp1_adc_attrs[] = {
+	&sensor_dev_attr_in1_input.dev_attr.attr,
+	&sensor_dev_attr_in2_input.dev_attr.attr,
+	&sensor_dev_attr_in3_input.dev_attr.attr,
+	&sensor_dev_attr_in4_input.dev_attr.attr,
+	&sensor_dev_attr_temp1_input.dev_attr.attr,
+	&sensor_dev_attr_in1_raw.dev_attr.attr,
+	&sensor_dev_attr_in2_raw.dev_attr.attr,
+	&sensor_dev_attr_in3_raw.dev_attr.attr,
+	&sensor_dev_attr_in4_raw.dev_attr.attr,
+	&sensor_dev_attr_temp1_raw.dev_attr.attr,
+	NULL
+};
+
+static umode_t rp1_adc_is_visible(struct kobject *kobj,
+				  struct attribute *attr, int index)
+{
+	return 0444;
+}
+
+static const struct attribute_group rp1_adc_group = {
+	.attrs = rp1_adc_attrs,
+	.is_visible = rp1_adc_is_visible,
+};
+__ATTRIBUTE_GROUPS(rp1_adc);
+
+static int __init rp1_adc_probe(struct platform_device *pdev)
+{
+	struct rp1_adc_data *data;
+	struct regulator *reg;
+	struct clk *clk;
+	int vref_uv, ret;
+
+	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	spin_lock_init(&data->lock);
+
+	data->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(data->base))
+		return PTR_ERR(data->base);
+
+	platform_set_drvdata(pdev, data);
+
+	clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(clk))
+		return -ENODEV;
+
+	clk_set_rate(clk, 50000000);
+	clk_prepare_enable(clk);
+
+	reg = devm_regulator_get(&pdev->dev, "vref");
+	if (IS_ERR(reg))
+		return PTR_ERR(reg);
+
+	vref_uv = regulator_get_voltage(reg);
+	data->vref_mv = DIV_ROUND_CLOSEST(vref_uv, 1000);
+
+	data->hwmon_dev =
+	    devm_hwmon_device_register_with_groups(&pdev->dev,
+						   "rp1_adc",
+						   data,
+						   rp1_adc_groups);
+	if (IS_ERR(data->hwmon_dev)) {
+		ret = PTR_ERR(data->hwmon_dev);
+		dev_err(&pdev->dev, "hwmon_device_register failed with %d.\n", ret);
+		goto err_register;
+	}
+
+	/* Disable interrupts */
+	writel(0, data->base + RP1_ADC_INTE);
+
+	/* Enable the block, clearing any sticky error */
+	writel(RP1_ADC_CS_EN | RP1_ADC_CS_ERR_STICKY, data->base + RP1_ADC_CS);
+
+	return 0;
+
+err_register:
+	sysfs_remove_group(&pdev->dev.kobj, &rp1_adc_group);
+
+	return ret;
+}
+
+static void rp1_adc_remove(struct platform_device *pdev)
+{
+	struct rp1_adc_data *data = platform_get_drvdata(pdev);
+
+	hwmon_device_unregister(data->hwmon_dev);
+}
+
+static const struct of_device_id rp1_adc_dt_ids[] = {
+	{ .compatible = "raspberrypi,rp1-adc", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, rp1_adc_dt_ids);
+
+static struct platform_driver rp1_adc_driver = {
+	.remove		= rp1_adc_remove,
+	.driver		= {
+		.name	= MODULE_NAME,
+		.of_match_table = rp1_adc_dt_ids,
+	},
+};
+
+module_platform_driver_probe(rp1_adc_driver, rp1_adc_probe);
+
+MODULE_DESCRIPTION("RP1 ADC driver");
+MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/sht3x.c b/drivers/hwmon/sht3x.c
index 94466e28dc56..3c45832779c7 100644
--- a/drivers/hwmon/sht3x.c
+++ b/drivers/hwmon/sht3x.c
@@ -961,19 +961,19 @@ static int sht3x_probe(struct i2c_client *client)
 	return PTR_ERR_OR_ZERO(hwmon_dev);
 }
 
-/* device ID table */
-static const struct i2c_device_id sht3x_ids[] = {
-	{"sht3x", sht3x},
-	{"sts3x", sts3x},
+static const struct of_device_id sht3x_of_ids[] = {
+	{ .compatible = "sensirion,sht3x" },
+	{ .compatible = "sensirion,sts3x" },
 	{}
 };
-
-MODULE_DEVICE_TABLE(i2c, sht3x_ids);
+MODULE_DEVICE_TABLE(of, sht3x_of_ids);
 
 static struct i2c_driver sht3x_i2c_driver = {
-	.driver.name = "sht3x",
+	.driver = {
+		.name = "sht3x",
+		.of_match_table = sht3x_of_ids,
+	},
 	.probe       = sht3x_probe,
-	.id_table    = sht3x_ids,
 };
 
 static int __init sht3x_init(void)
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 0e679cc50148..4f6f3b93bd9e 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -16,6 +16,25 @@ config I2C_CCGX_UCSI
 	  for Cypress CCGx Type-C controller. Individual bus drivers
 	  need to select this one on demand.
 
+config I2C_BCM2708
+	tristate "BCM2708 BSC"
+	depends on ARCH_BCM2835
+	help
+	  Enabling this option will add BSC (Broadcom Serial Controller)
+	  support for the BCM2708. BSC is a Broadcom proprietary bus compatible
+	  with I2C/TWI/SMBus.
+
+config I2C_BCM2708_BAUDRATE
+	prompt "BCM2708 I2C baudrate"
+	depends on I2C_BCM2708
+	int
+	default 100000
+	help
+	  Set the I2C baudrate. This will alter the default value. A
+	  different baudrate can be set by using a module parameter as well. If
+	  no parameter is provided when loading, this is the value that will be
+	  used.
+
 config I2C_ALI1535
 	tristate "ALI 1535"
 	depends on PCI && HAS_IOPORT
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index ecc07c50f2a0..a0ba5cd01edc 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -3,6 +3,8 @@
 # Makefile for the i2c bus drivers.
 #
 
+obj-$(CONFIG_I2C_BCM2708)	+= i2c-bcm2708.o
+
 # ACPI drivers
 obj-$(CONFIG_I2C_SCMI)		+= i2c-scmi.o
 
diff --git a/drivers/i2c/busses/i2c-bcm2708.c b/drivers/i2c/busses/i2c-bcm2708.c
new file mode 100644
index 000000000000..09907b5c2540
--- /dev/null
+++ b/drivers/i2c/busses/i2c-bcm2708.c
@@ -0,0 +1,510 @@
+/*
+ * Driver for Broadcom BCM2708 BSC Controllers
+ *
+ * Copyright (C) 2012 Chris Boot & Frank Buss
+ *
+ * This driver is inspired by:
+ * i2c-ocores.c, by Peter Korsgaard <jacmet@sunsite.dk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+
+/* BSC register offsets */
+#define BSC_C			0x00
+#define BSC_S			0x04
+#define BSC_DLEN		0x08
+#define BSC_A			0x0c
+#define BSC_FIFO		0x10
+#define BSC_DIV			0x14
+#define BSC_DEL			0x18
+#define BSC_CLKT		0x1c
+
+/* Bitfields in BSC_C */
+#define BSC_C_I2CEN		0x00008000
+#define BSC_C_INTR		0x00000400
+#define BSC_C_INTT		0x00000200
+#define BSC_C_INTD		0x00000100
+#define BSC_C_ST		0x00000080
+#define BSC_C_CLEAR_1		0x00000020
+#define BSC_C_CLEAR_2		0x00000010
+#define BSC_C_READ		0x00000001
+
+/* Bitfields in BSC_S */
+#define BSC_S_CLKT		0x00000200
+#define BSC_S_ERR		0x00000100
+#define BSC_S_RXF		0x00000080
+#define BSC_S_TXE		0x00000040
+#define BSC_S_RXD		0x00000020
+#define BSC_S_TXD		0x00000010
+#define BSC_S_RXR		0x00000008
+#define BSC_S_TXW		0x00000004
+#define BSC_S_DONE		0x00000002
+#define BSC_S_TA		0x00000001
+
+#define I2C_WAIT_LOOP_COUNT	200
+
+#define DRV_NAME		"bcm2708_i2c"
+
+static unsigned int baudrate;
+module_param(baudrate, uint, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+MODULE_PARM_DESC(baudrate, "The I2C baudrate");
+
+static bool combined = false;
+module_param(combined, bool, 0644);
+MODULE_PARM_DESC(combined, "Use combined transactions");
+
+struct bcm2708_i2c {
+	struct i2c_adapter adapter;
+
+	spinlock_t lock;
+	void __iomem *base;
+	int irq;
+	struct clk *clk;
+	u32 cdiv;
+	u32 clk_tout;
+
+	struct completion done;
+
+	struct i2c_msg *msg;
+	int pos;
+	int nmsgs;
+	bool error;
+};
+
+static inline u32 bcm2708_rd(struct bcm2708_i2c *bi, unsigned reg)
+{
+	return readl(bi->base + reg);
+}
+
+static inline void bcm2708_wr(struct bcm2708_i2c *bi, unsigned reg, u32 val)
+{
+	writel(val, bi->base + reg);
+}
+
+static inline void bcm2708_bsc_reset(struct bcm2708_i2c *bi)
+{
+	bcm2708_wr(bi, BSC_C, 0);
+	bcm2708_wr(bi, BSC_S, BSC_S_CLKT | BSC_S_ERR | BSC_S_DONE);
+}
+
+static inline void bcm2708_bsc_fifo_drain(struct bcm2708_i2c *bi)
+{
+	while ((bi->pos < bi->msg->len) && (bcm2708_rd(bi, BSC_S) & BSC_S_RXD))
+		bi->msg->buf[bi->pos++] = bcm2708_rd(bi, BSC_FIFO);
+}
+
+static inline void bcm2708_bsc_fifo_fill(struct bcm2708_i2c *bi)
+{
+	while ((bi->pos < bi->msg->len) && (bcm2708_rd(bi, BSC_S) & BSC_S_TXD))
+		bcm2708_wr(bi, BSC_FIFO, bi->msg->buf[bi->pos++]);
+}
+
+static inline int bcm2708_bsc_setup(struct bcm2708_i2c *bi)
+{
+	u32 cdiv, s, clk_tout;
+	u32 c = BSC_C_I2CEN | BSC_C_INTD | BSC_C_ST | BSC_C_CLEAR_1;
+	int wait_loops = I2C_WAIT_LOOP_COUNT;
+
+	/* Can't call clk_get_rate as it locks a mutex and here we are spinlocked.
+	 * Use the value that we cached in the probe.
+	 */
+	cdiv = bi->cdiv;
+	clk_tout = bi->clk_tout;
+
+	if (bi->msg->flags & I2C_M_RD)
+		c |= BSC_C_INTR | BSC_C_READ;
+	else
+		c |= BSC_C_INTT;
+
+	bcm2708_wr(bi, BSC_CLKT, clk_tout);
+	bcm2708_wr(bi, BSC_DIV, cdiv);
+	bcm2708_wr(bi, BSC_A, bi->msg->addr);
+	bcm2708_wr(bi, BSC_DLEN, bi->msg->len);
+	if (combined)
+	{
+		/* Do the next two messages meet combined transaction criteria?
+		   - Current message is a write, next message is a read
+		   - Both messages to same slave address
+		   - Write message can fit inside FIFO (16 bytes or less) */
+		if ( (bi->nmsgs > 1) &&
+			!(bi->msg[0].flags & I2C_M_RD) && (bi->msg[1].flags & I2C_M_RD) &&
+			 (bi->msg[0].addr == bi->msg[1].addr) && (bi->msg[0].len <= 16)) {
+
+			/* Clear FIFO */
+			bcm2708_wr(bi, BSC_C, BSC_C_CLEAR_1);
+
+			/* Fill FIFO with entire write message (16 byte FIFO) */
+			while (bi->pos < bi->msg->len) {
+				bcm2708_wr(bi, BSC_FIFO, bi->msg->buf[bi->pos++]);
+			}
+			/* Start write transfer (no interrupts, don't clear FIFO) */
+			bcm2708_wr(bi, BSC_C, BSC_C_I2CEN | BSC_C_ST);
+
+			/* poll for transfer start bit (should only take 1-20 polls) */
+			do {
+				s = bcm2708_rd(bi, BSC_S);
+			} while (!(s & (BSC_S_TA | BSC_S_ERR | BSC_S_CLKT | BSC_S_DONE)) && --wait_loops >= 0);
+
+			/* did we time out or some error occured? */
+			if (wait_loops < 0 || (s & (BSC_S_ERR | BSC_S_CLKT))) {
+				return -1;
+			}
+
+			/* Send next read message before the write transfer finishes. */
+			bi->nmsgs--;
+			bi->msg++;
+			bi->pos = 0;
+			bcm2708_wr(bi, BSC_DLEN, bi->msg->len);
+			c = BSC_C_I2CEN | BSC_C_INTD | BSC_C_INTR | BSC_C_ST | BSC_C_READ;
+		}
+	}
+	bcm2708_wr(bi, BSC_C, c);
+
+	return 0;
+}
+
+static irqreturn_t bcm2708_i2c_interrupt(int irq, void *dev_id)
+{
+	struct bcm2708_i2c *bi = dev_id;
+	bool handled = true;
+	u32 s;
+	int ret;
+
+	spin_lock(&bi->lock);
+
+	/* we may see camera interrupts on the "other" I2C channel
+		   Just return if we've not sent anything */
+	if (!bi->nmsgs || !bi->msg) {
+		goto early_exit;
+	}
+
+	s = bcm2708_rd(bi, BSC_S);
+
+	if (s & (BSC_S_CLKT | BSC_S_ERR)) {
+		bcm2708_bsc_reset(bi);
+		bi->error = true;
+
+		bi->msg = 0; /* to inform the that all work is done */
+		bi->nmsgs = 0;
+		/* wake up our bh */
+		complete(&bi->done);
+	} else if (s & BSC_S_DONE) {
+		bi->nmsgs--;
+
+		if (bi->msg->flags & I2C_M_RD) {
+			bcm2708_bsc_fifo_drain(bi);
+		}
+
+		bcm2708_bsc_reset(bi);
+
+		if (bi->nmsgs) {
+			/* advance to next message */
+			bi->msg++;
+			bi->pos = 0;
+			ret = bcm2708_bsc_setup(bi);
+			if (ret < 0) {
+				bcm2708_bsc_reset(bi);
+				bi->error = true;
+				bi->msg = 0; /* to inform the that all work is done */
+				bi->nmsgs = 0;
+				/* wake up our bh */
+				complete(&bi->done);
+				goto early_exit;
+			}
+		} else {
+			bi->msg = 0; /* to inform the that all work is done */
+			bi->nmsgs = 0;
+			/* wake up our bh */
+			complete(&bi->done);
+		}
+	} else if (s & BSC_S_TXW) {
+		bcm2708_bsc_fifo_fill(bi);
+	} else if (s & BSC_S_RXR) {
+		bcm2708_bsc_fifo_drain(bi);
+	} else {
+		handled = false;
+	}
+
+early_exit:
+	spin_unlock(&bi->lock);
+
+	return handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static int bcm2708_i2c_master_xfer(struct i2c_adapter *adap,
+	struct i2c_msg *msgs, int num)
+{
+	struct bcm2708_i2c *bi = adap->algo_data;
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&bi->lock, flags);
+
+	reinit_completion(&bi->done);
+	bi->msg = msgs;
+	bi->pos = 0;
+	bi->nmsgs = num;
+	bi->error = false;
+
+	ret = bcm2708_bsc_setup(bi);
+
+	spin_unlock_irqrestore(&bi->lock, flags);
+
+	/* check the result of the setup */
+	if (ret < 0)
+	{
+		dev_err(&adap->dev, "transfer setup timed out\n");
+		goto error_timeout;
+	}
+
+	ret = wait_for_completion_timeout(&bi->done, adap->timeout);
+	if (ret == 0) {
+		dev_err(&adap->dev, "transfer timed out\n");
+		goto error_timeout;
+	}
+
+	ret = bi->error ? -EIO : num;
+	return ret;
+
+error_timeout:
+	spin_lock_irqsave(&bi->lock, flags);
+	bcm2708_bsc_reset(bi);
+	bi->msg = 0; /* to inform the interrupt handler that there's nothing else to be done */
+	bi->nmsgs = 0;
+	spin_unlock_irqrestore(&bi->lock, flags);
+	return -ETIMEDOUT;
+}
+
+static u32 bcm2708_i2c_functionality(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C | /*I2C_FUNC_10BIT_ADDR |*/ I2C_FUNC_SMBUS_EMUL;
+}
+
+static struct i2c_algorithm bcm2708_i2c_algorithm = {
+	.master_xfer = bcm2708_i2c_master_xfer,
+	.functionality = bcm2708_i2c_functionality,
+};
+
+static int bcm2708_i2c_probe(struct platform_device *pdev)
+{
+	struct resource *regs;
+	int irq, err = -ENOMEM;
+	struct clk *clk;
+	struct bcm2708_i2c *bi;
+	struct i2c_adapter *adap;
+	unsigned long bus_hz;
+	u32 cdiv, clk_tout;
+	u32 baud;
+
+	baud = CONFIG_I2C_BCM2708_BAUDRATE;
+
+	if (pdev->dev.of_node) {
+		u32 bus_clk_rate;
+		pdev->id = of_alias_get_id(pdev->dev.of_node, "i2c");
+		if (pdev->id < 0) {
+			dev_err(&pdev->dev, "alias is missing\n");
+			return -EINVAL;
+		}
+		if (!of_property_read_u32(pdev->dev.of_node,
+					"clock-frequency", &bus_clk_rate))
+			baud = bus_clk_rate;
+		else
+			dev_warn(&pdev->dev,
+				"Could not read clock-frequency property\n");
+	}
+
+	if (baudrate)
+		baud = baudrate;
+
+	regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!regs) {
+		dev_err(&pdev->dev, "could not get IO memory\n");
+		return -ENXIO;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "could not get IRQ\n");
+		return irq;
+	}
+
+	clk = clk_get(&pdev->dev, NULL);
+	if (IS_ERR(clk)) {
+		dev_err(&pdev->dev, "could not find clk: %ld\n", PTR_ERR(clk));
+		return PTR_ERR(clk);
+	}
+
+	err = clk_prepare_enable(clk);
+	if (err) {
+		dev_err(&pdev->dev, "could not enable clk: %d\n", err);
+		goto out_clk_put;
+	}
+
+	bi = kzalloc(sizeof(*bi), GFP_KERNEL);
+	if (!bi)
+		goto out_clk_disable;
+
+	platform_set_drvdata(pdev, bi);
+
+	adap = &bi->adapter;
+	adap->class = I2C_CLASS_HWMON;
+	adap->algo = &bcm2708_i2c_algorithm;
+	adap->algo_data = bi;
+	adap->dev.parent = &pdev->dev;
+	adap->nr = pdev->id;
+	strscpy(adap->name, dev_name(&pdev->dev), sizeof(adap->name));
+	adap->dev.of_node = pdev->dev.of_node;
+
+	switch (pdev->id) {
+	case 0:
+		adap->class = I2C_CLASS_HWMON;
+		break;
+	case 1:
+		adap->class = I2C_CLASS_HWMON;
+		break;
+	case 2:
+		adap->class = I2C_CLASS_HWMON;
+		break;
+	default:
+		dev_err(&pdev->dev, "can only bind to BSC 0, 1 or 2\n");
+		err = -ENXIO;
+		goto out_free_bi;
+	}
+
+	spin_lock_init(&bi->lock);
+	init_completion(&bi->done);
+
+	bi->base = ioremap(regs->start, resource_size(regs));
+	if (!bi->base) {
+		dev_err(&pdev->dev, "could not remap memory\n");
+		goto out_free_bi;
+	}
+
+	bi->irq = irq;
+	bi->clk = clk;
+
+	err = request_irq(irq, bcm2708_i2c_interrupt, IRQF_SHARED,
+			dev_name(&pdev->dev), bi);
+	if (err) {
+		dev_err(&pdev->dev, "could not request IRQ: %d\n", err);
+		goto out_iounmap;
+	}
+
+	bcm2708_bsc_reset(bi);
+
+	err = i2c_add_numbered_adapter(adap);
+	if (err < 0) {
+		dev_err(&pdev->dev, "could not add I2C adapter: %d\n", err);
+		goto out_free_irq;
+	}
+
+	bus_hz = clk_get_rate(bi->clk);
+	cdiv = bus_hz / baud;
+	if (cdiv > 0xffff) {
+		cdiv = 0xffff;
+		baud = bus_hz / cdiv;
+	}
+
+	clk_tout = 35/1000*baud; //35ms timeout as per SMBus specs.
+	if (clk_tout > 0xffff)
+		clk_tout = 0xffff;
+	
+	bi->cdiv = cdiv;
+	bi->clk_tout = clk_tout;
+
+	dev_info(&pdev->dev, "BSC%d Controller at 0x%08lx (irq %d) (baudrate %d)\n",
+		pdev->id, (unsigned long)regs->start, irq, baud);
+
+	return 0;
+
+out_free_irq:
+	free_irq(bi->irq, bi);
+out_iounmap:
+	iounmap(bi->base);
+out_free_bi:
+	kfree(bi);
+out_clk_disable:
+	clk_disable_unprepare(clk);
+out_clk_put:
+	clk_put(clk);
+	return err;
+}
+
+static void bcm2708_i2c_remove(struct platform_device *pdev)
+{
+	struct bcm2708_i2c *bi = platform_get_drvdata(pdev);
+
+	platform_set_drvdata(pdev, NULL);
+
+	i2c_del_adapter(&bi->adapter);
+	free_irq(bi->irq, bi);
+	iounmap(bi->base);
+	clk_disable_unprepare(bi->clk);
+	clk_put(bi->clk);
+	kfree(bi);
+}
+
+static const struct of_device_id bcm2708_i2c_of_match[] = {
+        { .compatible = "brcm,bcm2708-i2c" },
+        {},
+};
+MODULE_DEVICE_TABLE(of, bcm2708_i2c_of_match);
+
+static struct platform_driver bcm2708_i2c_driver = {
+	.driver		= {
+		.name	= DRV_NAME,
+		.owner	= THIS_MODULE,
+		.of_match_table = bcm2708_i2c_of_match,
+	},
+	.probe		= bcm2708_i2c_probe,
+	.remove		= bcm2708_i2c_remove,
+};
+
+// module_platform_driver(bcm2708_i2c_driver);
+
+
+static int __init bcm2708_i2c_init(void)
+{
+	return platform_driver_register(&bcm2708_i2c_driver);
+}
+
+static void __exit bcm2708_i2c_exit(void)
+{
+	platform_driver_unregister(&bcm2708_i2c_driver);
+}
+
+module_init(bcm2708_i2c_init);
+module_exit(bcm2708_i2c_exit);
+
+
+
+MODULE_DESCRIPTION("BSC controller driver for Broadcom BCM2708");
+MODULE_AUTHOR("Chris Boot <bootc@bootc.net>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/drivers/i2c/busses/i2c-bcm2835.c b/drivers/i2c/busses/i2c-bcm2835.c
index ae42e37052a8..1c3289e6991b 100644
--- a/drivers/i2c/busses/i2c-bcm2835.c
+++ b/drivers/i2c/busses/i2c-bcm2835.c
@@ -56,6 +56,22 @@
 #define BCM2835_I2C_CDIV_MIN	0x0002
 #define BCM2835_I2C_CDIV_MAX	0xFFFE
 
+static unsigned int debug;
+module_param(debug, uint, 0644);
+MODULE_PARM_DESC(debug, "1=err, 2=isr, 3=xfer");
+
+static unsigned int clk_tout_ms = 35; /* SMBUs-recommended 35ms */
+module_param(clk_tout_ms, uint, 0644);
+MODULE_PARM_DESC(clk_tout_ms, "clock-stretch timeout (mS)");
+
+#define BCM2835_DEBUG_MAX	512
+struct bcm2835_debug {
+	struct i2c_msg *msg;
+	int msg_idx;
+	size_t remain;
+	u32 status;
+};
+
 struct bcm2835_i2c_dev {
 	struct device *dev;
 	void __iomem *regs;
@@ -68,8 +84,78 @@ struct bcm2835_i2c_dev {
 	u32 msg_err;
 	u8 *msg_buf;
 	size_t msg_buf_remaining;
+	struct bcm2835_debug debug[BCM2835_DEBUG_MAX];
+	unsigned int debug_num;
+	unsigned int debug_num_msgs;
 };
 
+static inline void bcm2835_debug_add(struct bcm2835_i2c_dev *i2c_dev, u32 s)
+{
+	if (!i2c_dev->debug_num_msgs || i2c_dev->debug_num >= BCM2835_DEBUG_MAX)
+		return;
+
+	i2c_dev->debug[i2c_dev->debug_num].msg = i2c_dev->curr_msg;
+	i2c_dev->debug[i2c_dev->debug_num].msg_idx =
+				i2c_dev->debug_num_msgs - i2c_dev->num_msgs;
+	i2c_dev->debug[i2c_dev->debug_num].remain = i2c_dev->msg_buf_remaining;
+	i2c_dev->debug[i2c_dev->debug_num].status = s;
+	i2c_dev->debug_num++;
+}
+
+static void bcm2835_debug_print_status(struct bcm2835_i2c_dev *i2c_dev,
+				       struct bcm2835_debug *d)
+{
+	u32 s = d->status;
+
+	pr_info("isr: remain=%zu, status=0x%x : %s%s%s%s%s%s%s%s%s%s [i2c%d]\n",
+		d->remain, s,
+		s & BCM2835_I2C_S_TA ? "TA " : "",
+		s & BCM2835_I2C_S_DONE ? "DONE " : "",
+		s & BCM2835_I2C_S_TXW ? "TXW " : "",
+		s & BCM2835_I2C_S_RXR ? "RXR " : "",
+		s & BCM2835_I2C_S_TXD ? "TXD " : "",
+		s & BCM2835_I2C_S_RXD ? "RXD " : "",
+		s & BCM2835_I2C_S_TXE ? "TXE " : "",
+		s & BCM2835_I2C_S_RXF ? "RXF " : "",
+		s & BCM2835_I2C_S_ERR ? "ERR " : "",
+		s & BCM2835_I2C_S_CLKT ? "CLKT " : "",
+		i2c_dev->adapter.nr);
+}
+
+static void bcm2835_debug_print_msg(struct bcm2835_i2c_dev *i2c_dev,
+				    struct i2c_msg *msg, int i, int total,
+				    const char *fname)
+{
+	pr_info("%s: msg(%d/%d) %s addr=0x%02x, len=%u flags=%s%s%s%s%s%s%s [i2c%d]\n",
+		fname, i, total,
+		msg->flags & I2C_M_RD ? "read" : "write", msg->addr, msg->len,
+		msg->flags & I2C_M_TEN ? "TEN" : "",
+		msg->flags & I2C_M_RECV_LEN ? "RECV_LEN" : "",
+		msg->flags & I2C_M_NO_RD_ACK ? "NO_RD_ACK" : "",
+		msg->flags & I2C_M_IGNORE_NAK ? "IGNORE_NAK" : "",
+		msg->flags & I2C_M_REV_DIR_ADDR ? "REV_DIR_ADDR" : "",
+		msg->flags & I2C_M_NOSTART ? "NOSTART" : "",
+		msg->flags & I2C_M_STOP ? "STOP" : "",
+		i2c_dev->adapter.nr);
+}
+
+static void bcm2835_debug_print(struct bcm2835_i2c_dev *i2c_dev)
+{
+	struct bcm2835_debug *d;
+	unsigned int i;
+
+	for (i = 0; i < i2c_dev->debug_num; i++) {
+		d = &i2c_dev->debug[i];
+		if (d->status == ~0)
+			bcm2835_debug_print_msg(i2c_dev, d->msg, d->msg_idx,
+				i2c_dev->debug_num_msgs, "start_transfer");
+		else
+			bcm2835_debug_print_status(i2c_dev, d);
+	}
+	if (i2c_dev->debug_num >= BCM2835_DEBUG_MAX)
+		pr_info("BCM2835_DEBUG_MAX reached\n");
+}
+
 static inline void bcm2835_i2c_writel(struct bcm2835_i2c_dev *i2c_dev,
 				      u32 reg, u32 val)
 {
@@ -111,6 +197,7 @@ static int clk_bcm2835_i2c_set_rate(struct clk_hw *hw, unsigned long rate,
 {
 	struct clk_bcm2835_i2c *div = to_clk_bcm2835_i2c(hw);
 	u32 redl, fedl;
+	u32 clk_tout;
 	u32 divider = clk_bcm2835_i2c_calc_divider(rate, parent_rate);
 
 	if (divider == -EINVAL)
@@ -134,6 +221,17 @@ static int clk_bcm2835_i2c_set_rate(struct clk_hw *hw, unsigned long rate,
 	bcm2835_i2c_writel(div->i2c_dev, BCM2835_I2C_DEL,
 			   (fedl << BCM2835_I2C_FEDL_SHIFT) |
 			   (redl << BCM2835_I2C_REDL_SHIFT));
+
+	/*
+	 * Set the clock stretch timeout.
+	 */
+	if (rate > 0xffff*1000/clk_tout_ms)
+	    clk_tout = 0xffff;
+	else
+	    clk_tout = clk_tout_ms*rate/1000;
+
+	bcm2835_i2c_writel(div->i2c_dev, BCM2835_I2C_CLKT, clk_tout);
+
 	return 0;
 }
 
@@ -257,6 +355,7 @@ static void bcm2835_i2c_start_transfer(struct bcm2835_i2c_dev *i2c_dev)
 	bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_A, msg->addr);
 	bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_DLEN, msg->len);
 	bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_C, c);
+	bcm2835_debug_add(i2c_dev, ~0);
 }
 
 static void bcm2835_i2c_finish_transfer(struct bcm2835_i2c_dev *i2c_dev)
@@ -283,12 +382,11 @@ static irqreturn_t bcm2835_i2c_isr(int this_irq, void *data)
 	u32 val, err;
 
 	val = bcm2835_i2c_readl(i2c_dev, BCM2835_I2C_S);
+	bcm2835_debug_add(i2c_dev, val);
 
 	err = val & (BCM2835_I2C_S_CLKT | BCM2835_I2C_S_ERR);
-	if (err) {
+	if (err && !(val & BCM2835_I2C_S_TA))
 		i2c_dev->msg_err = err;
-		goto complete;
-	}
 
 	if (val & BCM2835_I2C_S_DONE) {
 		if (!i2c_dev->curr_msg) {
@@ -300,8 +398,6 @@ static irqreturn_t bcm2835_i2c_isr(int this_irq, void *data)
 
 		if ((val & BCM2835_I2C_S_RXD) || i2c_dev->msg_buf_remaining)
 			i2c_dev->msg_err = BCM2835_I2C_S_LEN;
-		else
-			i2c_dev->msg_err = 0;
 		goto complete;
 	}
 
@@ -347,17 +443,29 @@ static int bcm2835_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[],
 {
 	struct bcm2835_i2c_dev *i2c_dev = i2c_get_adapdata(adap);
 	unsigned long time_left;
+	bool ignore_nak = false;
 	int i;
 
-	for (i = 0; i < (num - 1); i++)
+	if (debug)
+		i2c_dev->debug_num_msgs = num;
+
+	if (debug > 2)
+		for (i = 0; i < num; i++)
+			bcm2835_debug_print_msg(i2c_dev, &msgs[i], i + 1, num, __func__);
+
+	for (i = 0; i < (num - 1); i++) {
 		if (msgs[i].flags & I2C_M_RD) {
 			dev_warn_once(i2c_dev->dev,
 				      "only one read message supported, has to be last\n");
 			return -EOPNOTSUPP;
 		}
+		if (msgs[i].flags & I2C_M_IGNORE_NAK)
+			ignore_nak = true;
+	}
 
 	i2c_dev->curr_msg = msgs;
 	i2c_dev->num_msgs = num;
+	i2c_dev->msg_err = 0;
 	reinit_completion(&i2c_dev->completion);
 
 	bcm2835_i2c_start_transfer(i2c_dev);
@@ -367,6 +475,13 @@ static int bcm2835_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[],
 
 	bcm2835_i2c_finish_transfer(i2c_dev);
 
+	if (ignore_nak)
+		i2c_dev->msg_err &= ~BCM2835_I2C_S_ERR;
+
+	if (debug > 1 || (debug && (!time_left || i2c_dev->msg_err)))
+		bcm2835_debug_print(i2c_dev);
+	i2c_dev->debug_num_msgs = 0;
+	i2c_dev->debug_num = 0;
 	if (!time_left) {
 		bcm2835_i2c_writel(i2c_dev, BCM2835_I2C_C,
 				   BCM2835_I2C_C_CLEAR);
@@ -376,7 +491,9 @@ static int bcm2835_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[],
 	if (!i2c_dev->msg_err)
 		return num;
 
-	dev_dbg(i2c_dev->dev, "i2c transfer failed: %x\n", i2c_dev->msg_err);
+	if (debug)
+		dev_err(i2c_dev->dev, "i2c transfer failed: %x\n",
+			i2c_dev->msg_err);
 
 	if (i2c_dev->msg_err & BCM2835_I2C_S_ERR)
 		return -EREMOTEIO;
@@ -386,7 +503,7 @@ static int bcm2835_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[],
 
 static u32 bcm2835_i2c_func(struct i2c_adapter *adap)
 {
-	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING;
 }
 
 static const struct i2c_algorithm bcm2835_i2c_algo = {
diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c
index b3282785523d..9101e33885fd 100644
--- a/drivers/i2c/busses/i2c-designware-common.c
+++ b/drivers/i2c/busses/i2c-designware-common.c
@@ -63,6 +63,8 @@ static char *abort_sources[] = {
 		"slave lost the bus while transmitting data to a remote master",
 	[ABRT_SLAVE_RD_INTX] =
 		"incorrect slave-transmitter mode configuration",
+	[ABRT_SLAVE_SDA_STUCK_AT_LOW] =
+		"SDA stuck at low",
 };
 
 static int dw_reg_read(void *context, unsigned int reg, unsigned int *val)
@@ -360,6 +362,9 @@ static void i2c_dw_adjust_bus_speed(struct dw_i2c_dev *dev)
 {
 	u32 acpi_speed = i2c_dw_acpi_round_bus_speed(dev->dev);
 	struct i2c_timings *t = &dev->timings;
+	u32 wanted_speed;
+	u32 legal_speed = 0;
+	int i;
 
 	/*
 	 * Find bus speed from the "clock-frequency" device property, ACPI
@@ -371,6 +376,30 @@ static void i2c_dw_adjust_bus_speed(struct dw_i2c_dev *dev)
 		t->bus_freq_hz = max(t->bus_freq_hz, acpi_speed);
 	else
 		t->bus_freq_hz = I2C_MAX_FAST_MODE_FREQ;
+
+	wanted_speed = t->bus_freq_hz;
+
+	/* For unsupported speeds, scale down the lowest speed which is faster. */
+	for (i = 0; i < ARRAY_SIZE(supported_speeds); i++) {
+		/* supported speeds is in decreasing order */
+		if (wanted_speed == supported_speeds[i]) {
+			legal_speed = 0;
+			break;
+		}
+		if (wanted_speed > supported_speeds[i])
+			break;
+
+		legal_speed = supported_speeds[i];
+	}
+
+	if (legal_speed) {
+		/*
+		 * Pretend this was the requested speed, but preserve the preferred
+		 * speed so the clock counts can be scaled.
+		 */
+		t->bus_freq_hz = legal_speed;
+		dev->wanted_bus_speed = wanted_speed;
+	}
 }
 
 int i2c_dw_fw_parse_and_configure(struct dw_i2c_dev *dev)
@@ -665,8 +694,16 @@ int i2c_dw_wait_bus_not_busy(struct dw_i2c_dev *dev)
 int i2c_dw_handle_tx_abort(struct dw_i2c_dev *dev)
 {
 	unsigned long abort_source = dev->abort_source;
+	unsigned int reg;
 	int i;
 
+	if (abort_source & DW_IC_TX_ABRT_SLAVE_SDA_STUCK_AT_LOW) {
+		regmap_write(dev->map, DW_IC_ENABLE,
+			     DW_IC_ENABLE_ENABLE | DW_IC_ENABLE_BUS_RECOVERY);
+		regmap_read_poll_timeout(dev->map, DW_IC_ENABLE, reg,
+					 !(reg & DW_IC_ENABLE_BUS_RECOVERY),
+					 1100, 200000);
+	}
 	if (abort_source & DW_IC_TX_ABRT_NOACK) {
 		for_each_set_bit(i, &abort_source, ARRAY_SIZE(abort_sources))
 			dev_dbg(dev->dev,
@@ -681,6 +718,8 @@ int i2c_dw_handle_tx_abort(struct dw_i2c_dev *dev)
 		return -EAGAIN;
 	else if (abort_source & DW_IC_TX_ABRT_GCALL_READ)
 		return -EINVAL; /* wrong msgs[] data */
+	else if (abort_source & DW_IC_TX_ABRT_SLAVE_SDA_STUCK_AT_LOW)
+		return -EREMOTEIO;
 	else
 		return -EIO;
 }
diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h
index e3d76e423842..d1eb7cf44e34 100644
--- a/drivers/i2c/busses/i2c-designware-core.h
+++ b/drivers/i2c/busses/i2c-designware-core.h
@@ -79,9 +79,12 @@
 #define DW_IC_ENABLE_STATUS			0x9c
 #define DW_IC_CLR_RESTART_DET			0xa8
 #define DW_IC_SMBUS_INTR_MASK			0xcc
+#define DW_IC_SCL_STUCK_AT_LOW_TIMEOUT		0xac
+#define DW_IC_SDA_STUCK_AT_LOW_TIMEOUT		0xb0
 #define DW_IC_COMP_PARAM_1			0xf4
 #define DW_IC_COMP_VERSION			0xf8
 #define DW_IC_SDA_HOLD_MIN_VERS			0x3131312A /* "111*" == v1.11* */
+#define DW_IC_BUS_CLEAR_MIN_VERS		0x3230302A /* "200*" == v2.00* */
 #define DW_IC_COMP_TYPE				0xfc
 #define DW_IC_COMP_TYPE_VALUE			0x44570140 /* "DW" + 0x0140 */
 
@@ -111,6 +114,7 @@
 
 #define DW_IC_ENABLE_ENABLE			BIT(0)
 #define DW_IC_ENABLE_ABORT			BIT(1)
+#define DW_IC_ENABLE_BUS_RECOVERY		BIT(3)
 
 #define DW_IC_STATUS_ACTIVITY			BIT(0)
 #define DW_IC_STATUS_TFE			BIT(2)
@@ -118,13 +122,16 @@
 #define DW_IC_STATUS_MASTER_ACTIVITY		BIT(5)
 #define DW_IC_STATUS_SLAVE_ACTIVITY		BIT(6)
 #define DW_IC_STATUS_MASTER_HOLD_TX_FIFO_EMPTY	BIT(7)
+#define DW_IC_STATUS_SDA_STUCK_NOT_RECOVERED	BIT(11)
 
 #define DW_IC_SDA_HOLD_RX_SHIFT			16
 #define DW_IC_SDA_HOLD_RX_MASK			GENMASK(23, 16)
 
 #define DW_IC_ERR_TX_ABRT			0x1
 
+#define DW_IC_TAR_SPECIAL			BIT(11)
 #define DW_IC_TAR_10BITADDR_MASTER		BIT(12)
+#define DW_IC_TAR_SMBUS_QUICK_CMD		BIT(16)
 
 #define DW_IC_COMP_PARAM_1_SPEED_MODE_HIGH	(BIT(2) | BIT(3))
 #define DW_IC_COMP_PARAM_1_SPEED_MODE_MASK	GENMASK(3, 2)
@@ -163,6 +170,7 @@
 #define ABRT_SLAVE_FLUSH_TXFIFO			13
 #define ABRT_SLAVE_ARBLOST			14
 #define ABRT_SLAVE_RD_INTX			15
+#define ABRT_SLAVE_SDA_STUCK_AT_LOW		17
 
 #define DW_IC_TX_ABRT_7B_ADDR_NOACK		BIT(ABRT_7B_ADDR_NOACK)
 #define DW_IC_TX_ABRT_10ADDR1_NOACK		BIT(ABRT_10ADDR1_NOACK)
@@ -178,6 +186,7 @@
 #define DW_IC_RX_ABRT_SLAVE_RD_INTX		BIT(ABRT_SLAVE_RD_INTX)
 #define DW_IC_RX_ABRT_SLAVE_ARBLOST		BIT(ABRT_SLAVE_ARBLOST)
 #define DW_IC_RX_ABRT_SLAVE_FLUSH_TXFIFO	BIT(ABRT_SLAVE_FLUSH_TXFIFO)
+#define DW_IC_TX_ABRT_SLAVE_SDA_STUCK_AT_LOW	BIT(ABRT_SLAVE_SDA_STUCK_AT_LOW)
 
 #define DW_IC_TX_ABRT_NOACK			(DW_IC_TX_ABRT_7B_ADDR_NOACK | \
 						 DW_IC_TX_ABRT_10ADDR1_NOACK | \
@@ -292,6 +301,7 @@ struct dw_i2c_dev {
 	u16			fp_lcnt;
 	u16			hs_hcnt;
 	u16			hs_lcnt;
+	u32			wanted_bus_speed;
 	int			(*acquire_lock)(void);
 	void			(*release_lock)(void);
 	int			semaphore_idx;
diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c
index 196bb073c6bc..cb33468b2e33 100644
--- a/drivers/i2c/busses/i2c-designware-master.c
+++ b/drivers/i2c/busses/i2c-designware-master.c
@@ -41,6 +41,34 @@ static void i2c_dw_configure_fifo_master(struct dw_i2c_dev *dev)
 	regmap_write(dev->map, DW_IC_CON, dev->master_cfg);
 }
 
+static u32 linear_interpolate(u32 x, u32 x1, u32 x2, u32 y1, u32 y2)
+{
+	return ((x - x1) * y2 + (x2 - x) * y1) / (x2 - x1);
+}
+
+static u16 u16_clamp(u32 v)
+{
+	return (u16)min(v, 0xffff);
+}
+
+static void clock_calc(struct dw_i2c_dev *dev, u32 *hcnt, u32 *lcnt)
+{
+	struct i2c_timings *t = &dev->timings;
+	u32 wanted_khz = (dev->wanted_bus_speed ?: t->bus_freq_hz)/1000;
+	u32 clk_khz = i2c_dw_clk_rate(dev);
+	u32 min_high_ns = (wanted_khz <= 100) ? 4000 :
+			  (wanted_khz <= 400) ?
+				linear_interpolate(wanted_khz, 100, 400, 4000, 600) :
+				linear_interpolate(wanted_khz, 400, 1000, 600, 260);
+	u32 high_cycles = (u32)(((u64)clk_khz * min_high_ns + 999999) / 1000000) + 1;
+	u32 extra_high_cycles = (u32)((u64)clk_khz * t->scl_fall_ns / 1000000);
+	u32 extra_low_cycles = (u32)((u64)clk_khz * t->scl_rise_ns / 1000000);
+	u32 period = ((u64)clk_khz + wanted_khz - 1) / wanted_khz;
+
+	*hcnt = u16_clamp(high_cycles - extra_high_cycles);
+	*lcnt = u16_clamp(period - high_cycles - extra_low_cycles);
+}
+
 static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev)
 {
 	unsigned int comp_param1;
@@ -48,6 +76,7 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev)
 	struct i2c_timings *t = &dev->timings;
 	const char *fp_str = "";
 	u32 ic_clk;
+	u32 hcnt, lcnt;
 	int ret;
 
 	ret = i2c_dw_acquire_lock(dev);
@@ -63,6 +92,8 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev)
 	sda_falling_time = t->sda_fall_ns ?: 300; /* ns */
 	scl_falling_time = t->scl_fall_ns ?: 300; /* ns */
 
+	clock_calc(dev, &hcnt, &lcnt);
+
 	/* Calculate SCL timing parameters for standard mode if not set */
 	if (!dev->ss_hcnt || !dev->ss_lcnt) {
 		ic_clk = i2c_dw_clk_rate(dev);
@@ -82,6 +113,8 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev)
 					scl_falling_time,
 					0);	/* No offset */
 	}
+	dev->ss_hcnt = hcnt;
+	dev->ss_lcnt = lcnt;
 	dev_dbg(dev->dev, "Standard Mode HCNT:LCNT = %d:%d\n",
 		dev->ss_hcnt, dev->ss_lcnt);
 
@@ -140,6 +173,8 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev)
 					scl_falling_time,
 					0);	/* No offset */
 	}
+	dev->fs_hcnt = hcnt;
+	dev->fs_lcnt = lcnt;
 	dev_dbg(dev->dev, "Fast Mode%s HCNT:LCNT = %d:%d\n",
 		fp_str, dev->fs_hcnt, dev->fs_lcnt);
 
@@ -172,10 +207,15 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev)
 						scl_falling_time,
 						0);	/* No offset */
 		}
+		dev->hs_hcnt = hcnt;
+		dev->hs_lcnt = lcnt;
 		dev_dbg(dev->dev, "High Speed Mode HCNT:LCNT = %d:%d\n",
 			dev->hs_hcnt, dev->hs_lcnt);
 	}
 
+	if (!dev->sda_hold_time)
+		dev->sda_hold_time = lcnt / 2;
+
 	ret = i2c_dw_set_sda_hold(dev);
 	if (ret)
 		return ret;
@@ -194,6 +234,7 @@ static int i2c_dw_set_timings_master(struct dw_i2c_dev *dev)
  */
 static int i2c_dw_init_master(struct dw_i2c_dev *dev)
 {
+	unsigned int timeout = 0;
 	int ret;
 
 	ret = i2c_dw_acquire_lock(dev);
@@ -224,6 +265,17 @@ static int i2c_dw_init_master(struct dw_i2c_dev *dev)
 		regmap_write(dev->map, DW_IC_HS_SCL_LCNT, dev->hs_lcnt);
 	}
 
+	if (dev->master_cfg & DW_IC_CON_BUS_CLEAR_CTRL) {
+		/* Set a sensible timeout if not already configured */
+		regmap_read(dev->map, DW_IC_SDA_STUCK_AT_LOW_TIMEOUT, &timeout);
+		if (timeout == ~0) {
+			/* Use 10ms as a timeout, which is 1000 cycles at 100kHz */
+			timeout = i2c_dw_clk_rate(dev) * 10; /* clock rate is in kHz */
+			regmap_write(dev->map, DW_IC_SDA_STUCK_AT_LOW_TIMEOUT, timeout);
+			regmap_write(dev->map, DW_IC_SCL_STUCK_AT_LOW_TIMEOUT, timeout);
+		}
+	}
+
 	/* Write SDA hold time if supported */
 	if (dev->sda_hold_time)
 		regmap_write(dev->map, DW_IC_SDA_HOLD, dev->sda_hold_time);
@@ -255,6 +307,10 @@ static void i2c_dw_xfer_init(struct dw_i2c_dev *dev)
 		ic_tar = DW_IC_TAR_10BITADDR_MASTER;
 	}
 
+	/* Convert a zero-length read into an SMBUS quick command */
+	if (!msgs[dev->msg_write_idx].len)
+		ic_tar = DW_IC_TAR_SPECIAL | DW_IC_TAR_SMBUS_QUICK_CMD;
+
 	regmap_update_bits(dev->map, DW_IC_CON, DW_IC_CON_10BITADDR_MASTER,
 			   ic_con);
 
@@ -464,6 +520,14 @@ i2c_dw_xfer_msg(struct dw_i2c_dev *dev)
 		regmap_read(dev->map, DW_IC_RXFLR, &flr);
 		rx_limit = dev->rx_fifo_depth - flr;
 
+		/* Handle SMBUS quick commands */
+		if (!buf_len) {
+			if (msgs[dev->msg_write_idx].flags & I2C_M_RD)
+				regmap_write(dev->map, DW_IC_DATA_CMD, 0x300);
+			else
+				regmap_write(dev->map, DW_IC_DATA_CMD, 0x200);
+		}
+
 		while (buf_len > 0 && tx_limit > 0 && rx_limit > 0) {
 			u32 cmd = 0;
 
@@ -902,14 +966,15 @@ static const struct i2c_algorithm i2c_dw_algo = {
 };
 
 static const struct i2c_adapter_quirks i2c_dw_quirks = {
-	.flags = I2C_AQ_NO_ZERO_LEN,
+	.flags = 0,
 };
 
 void i2c_dw_configure_master(struct dw_i2c_dev *dev)
 {
 	struct i2c_timings *t = &dev->timings;
 
-	dev->functionality = I2C_FUNC_10BIT_ADDR | DW_IC_DEFAULT_FUNCTIONALITY;
+	dev->functionality = I2C_FUNC_10BIT_ADDR | I2C_FUNC_SMBUS_QUICK |
+			     DW_IC_DEFAULT_FUNCTIONALITY;
 
 	dev->master_cfg = DW_IC_CON_MASTER | DW_IC_CON_SLAVE_DISABLE |
 			  DW_IC_CON_RESTART_EN;
@@ -991,6 +1056,7 @@ int i2c_dw_probe_master(struct dw_i2c_dev *dev)
 	struct i2c_adapter *adap = &dev->adapter;
 	unsigned long irq_flags;
 	unsigned int ic_con;
+	unsigned int id_ver;
 	int ret;
 
 	init_completion(&dev->cmd_complete);
@@ -1025,7 +1091,11 @@ int i2c_dw_probe_master(struct dw_i2c_dev *dev)
 	if (ret)
 		return ret;
 
-	if (ic_con & DW_IC_CON_BUS_CLEAR_CTRL)
+	ret = regmap_read(dev->map, DW_IC_COMP_VERSION, &id_ver);
+	if (ret)
+		return ret;
+
+	if (ic_con & DW_IC_CON_BUS_CLEAR_CTRL || id_ver >= DW_IC_BUS_CLEAR_MIN_VERS)
 		dev->master_cfg |= DW_IC_CON_BUS_CLEAR_CTRL;
 
 	ret = dev->init(dev);
diff --git a/drivers/i2c/busses/i2c-gpio.c b/drivers/i2c/busses/i2c-gpio.c
index e0bd218e2f14..aebc2d96c23f 100644
--- a/drivers/i2c/busses/i2c-gpio.c
+++ b/drivers/i2c/busses/i2c-gpio.c
@@ -428,7 +428,9 @@ static int i2c_gpio_probe(struct platform_device *pdev)
 	adap->dev.parent = dev;
 	device_set_node(&adap->dev, fwnode);
 
-	adap->nr = pdev->id;
+	if (pdev->id != PLATFORM_DEVID_NONE || !pdev->dev.of_node ||
+	    of_property_read_u32(pdev->dev.of_node, "reg", &adap->nr))
+		adap->nr = pdev->id;
 	ret = i2c_bit_add_numbered_bus(adap);
 	if (ret)
 		return ret;
diff --git a/drivers/i2c/i2c-mux.c b/drivers/i2c/i2c-mux.c
index fda72e8be885..6018f196d0fd 100644
--- a/drivers/i2c/i2c-mux.c
+++ b/drivers/i2c/i2c-mux.c
@@ -333,8 +333,13 @@ int i2c_mux_add_adapter(struct i2c_mux_core *muxc,
 	if (muxc->dev->of_node) {
 		struct device_node *dev_node = muxc->dev->of_node;
 		struct device_node *mux_node, *child = NULL;
+		u32 base_nr = 0;
 		u32 reg;
 
+		of_property_read_u32(dev_node, "base-nr", &base_nr);
+		if (!force_nr && base_nr)
+			force_nr = base_nr + chan_id;
+
 		if (muxc->arbitrator)
 			mux_node = of_get_child_by_name(dev_node, "i2c-arb");
 		else if (muxc->gate)
diff --git a/drivers/iio/adc/mcp3422.c b/drivers/iio/adc/mcp3422.c
index 50834fdcf738..e27813611a64 100644
--- a/drivers/iio/adc/mcp3422.c
+++ b/drivers/iio/adc/mcp3422.c
@@ -407,7 +407,14 @@ static const struct i2c_device_id mcp3422_id[] = {
 MODULE_DEVICE_TABLE(i2c, mcp3422_id);
 
 static const struct of_device_id mcp3422_of_match[] = {
-	{ .compatible = "mcp3422" },
+	{ .compatible = "microchip,mcp3421" },
+	{ .compatible = "microchip,mcp3422" },
+	{ .compatible = "microchip,mcp3423" },
+	{ .compatible = "microchip,mcp3424" },
+	{ .compatible = "microchip,mcp3425" },
+	{ .compatible = "microchip,mcp3426" },
+	{ .compatible = "microchip,mcp3427" },
+	{ .compatible = "microchip,mcp3428" },
 	{ }
 };
 MODULE_DEVICE_TABLE(of, mcp3422_of_match);
diff --git a/drivers/iio/humidity/dht11.c b/drivers/iio/humidity/dht11.c
index c97e25448772..81c0bb9dd851 100644
--- a/drivers/iio/humidity/dht11.c
+++ b/drivers/iio/humidity/dht11.c
@@ -152,9 +152,9 @@ static int dht11_decode(struct dht11 *dht11, int offset)
 		dht11->temperature = (((temp_int & 0x7f) << 8) + temp_dec) *
 					((temp_int & 0x80) ? -100 : 100);
 		dht11->humidity = ((hum_int << 8) + hum_dec) * 100;
-	} else if (temp_dec == 0 && hum_dec == 0) {  /* DHT11 */
-		dht11->temperature = temp_int * 1000;
-		dht11->humidity = hum_int * 1000;
+	} else if (temp_dec < 10 && hum_dec < 10) {  /* DHT11 */
+		dht11->temperature = temp_int * 1000 + temp_dec * 100;
+		dht11->humidity = hum_int * 1000 + hum_dec * 100;
 	} else {
 		dev_err(dht11->dev,
 			"Don't know how to decode data: %d %d %d %d\n",
diff --git a/drivers/iio/light/tsl4531.c b/drivers/iio/light/tsl4531.c
index a5788c09ad02..c918ebc930c3 100644
--- a/drivers/iio/light/tsl4531.c
+++ b/drivers/iio/light/tsl4531.c
@@ -232,9 +232,16 @@ static const struct i2c_device_id tsl4531_id[] = {
 };
 MODULE_DEVICE_TABLE(i2c, tsl4531_id);
 
+static const struct of_device_id tsl4531_of_id[] = {
+	{ .compatible = "amstaos,tsl4531" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, tsl4531_of_id);
+
 static struct i2c_driver tsl4531_driver = {
 	.driver = {
 		.name   = TSL4531_DRV_NAME,
+		.of_match_table = tsl4531_of_id,
 		.pm	= pm_sleep_ptr(&tsl4531_pm_ops),
 	},
 	.probe = tsl4531_probe,
diff --git a/drivers/iio/light/veml6070.c b/drivers/iio/light/veml6070.c
index f8321d346d77..15222705589c 100644
--- a/drivers/iio/light/veml6070.c
+++ b/drivers/iio/light/veml6070.c
@@ -194,9 +194,16 @@ static const struct i2c_device_id veml6070_id[] = {
 };
 MODULE_DEVICE_TABLE(i2c, veml6070_id);
 
+static const struct of_device_id veml6070_of_id[] = {
+	{ .compatible = "vishay,veml6070" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, veml6070_of_id);
+
 static struct i2c_driver veml6070_driver = {
 	.driver = {
 		.name   = VEML6070_DRV_NAME,
+		.of_match_table = veml6070_of_id,
 	},
 	.probe = veml6070_probe,
 	.remove  = veml6070_remove,
diff --git a/drivers/input/joystick/sensehat-joystick.c b/drivers/input/joystick/sensehat-joystick.c
index a84df39d3b2f..f24beb98e444 100644
--- a/drivers/input/joystick/sensehat-joystick.c
+++ b/drivers/input/joystick/sensehat-joystick.c
@@ -28,7 +28,7 @@ struct sensehat_joystick {
 };
 
 static const unsigned int keymap[] = {
-	BTN_DPAD_DOWN, BTN_DPAD_RIGHT, BTN_DPAD_UP, BTN_SELECT, BTN_DPAD_LEFT,
+	KEY_DOWN, KEY_RIGHT, KEY_UP, KEY_ENTER, KEY_LEFT
 };
 
 static irqreturn_t sensehat_joystick_report(int irq, void *cookie)
diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 1ac26fc2e3eb..2faf13fa23f5 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -479,6 +479,19 @@ config TOUCHSCREEN_ILITEK
 	  To compile this driver as a module, choose M here: the
 	  module will be called ilitek_ts_i2c.
 
+config TOUCHSCREEN_ILITEK_V3
+	tristate "Ilitek I2C ILI79600 Series Touch ICs"
+	depends on I2C
+	help
+	  Say Y here if you have touchscreen with ILITEK "V3" touch IC,
+	  it supports 79600 and similar over I2C (the IC also supports
+	  SPI)
+
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ilitek_v3_ts_i2c.
+
 config TOUCHSCREEN_IPROC
 	tristate "IPROC touch panel driver support"
 	depends on ARCH_BCM_IPROC || COMPILE_TEST
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 82bc837ca01e..0183ae020750 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -51,6 +51,7 @@ obj-$(CONFIG_TOUCHSCREEN_HIDEEP)	+= hideep.o
 obj-$(CONFIG_TOUCHSCREEN_HYNITRON_CSTXXX)	+= hynitron_cstxxx.o
 obj-$(CONFIG_TOUCHSCREEN_ILI210X)	+= ili210x.o
 obj-$(CONFIG_TOUCHSCREEN_ILITEK)	+= ilitek_ts_i2c.o
+obj-$(CONFIG_TOUCHSCREEN_ILITEK_V3)	+= ilitek_v3_ts_i2c.o
 obj-$(CONFIG_TOUCHSCREEN_IMAGIS)	+= imagis.o
 obj-$(CONFIG_TOUCHSCREEN_IMX6UL_TSC)	+= imx6ul_tsc.o
 obj-$(CONFIG_TOUCHSCREEN_INEXIO)	+= inexio.o
diff --git a/drivers/input/touchscreen/edt-ft5x06.c b/drivers/input/touchscreen/edt-ft5x06.c
index 85c6d8ce003f..3bd31c0c8ab3 100644
--- a/drivers/input/touchscreen/edt-ft5x06.c
+++ b/drivers/input/touchscreen/edt-ft5x06.c
@@ -69,6 +69,7 @@
 #define TOUCH_EVENT_RESERVED		0x03
 
 #define EDT_NAME_LEN			23
+#define EDT_NAME_PREFIX_LEN		8
 #define EDT_SWITCH_MODE_RETRIES		10
 #define EDT_SWITCH_MODE_DELAY		5 /* msec */
 #define EDT_RAW_DATA_RETRIES		100
@@ -80,6 +81,10 @@
 #define M06_REG_CMD(factory) ((factory) ? 0xf3 : 0xfc)
 #define M06_REG_ADDR(factory, addr) ((factory) ? (addr) & 0x7f : (addr) & 0x3f)
 
+#define RESET_DELAY_MS			300	/* reset deassert to I2C */
+#define FIRST_POLL_DELAY_MS		300	/* in addition to the above */
+#define POLL_INTERVAL_MS		17	/* 17ms = 60fps */
+
 enum edt_pmode {
 	EDT_PMODE_NOT_SUPPORTED,
 	EDT_PMODE_HIBERNATE,
@@ -139,14 +144,19 @@ struct edt_ft5x06_ts_data {
 	u8 tdata_cmd;
 	int tdata_len;
 	int tdata_offset;
+	unsigned int known_ids;
 
-	char name[EDT_NAME_LEN];
+	char name[EDT_NAME_PREFIX_LEN + EDT_NAME_LEN];
 	char fw_version[EDT_NAME_LEN];
+	int init_td_status;
 
 	struct edt_reg_addr reg_addr;
 	enum edt_ver version;
 	unsigned int crc_errors;
 	unsigned int header_errors;
+
+	struct timer_list timer;
+	struct work_struct work_i2c_poll;
 };
 
 struct edt_i2c_chip_data {
@@ -303,17 +313,49 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id)
 	u8 rdbuf[63];
 	int i, type, x, y, id;
 	int error;
+	int num_points;
+	unsigned int active_ids = 0, known_ids = tsdata->known_ids;
+	long released_ids;
+	int b = 0;
 
 	memset(rdbuf, 0, sizeof(rdbuf));
 	error = regmap_bulk_read(tsdata->regmap, tsdata->tdata_cmd, rdbuf,
 				 tsdata->tdata_len);
+	if (tsdata->version == EDT_M06) {
+		num_points = tsdata->max_support_points;
+	} else {
+		/* Register 2 is TD_STATUS, containing the number of touch
+		 * points.
+		 */
+		num_points = min(rdbuf[2] & 0xf, tsdata->max_support_points);
+
+		/* When polling FT5x06 without IRQ: initial register contents
+		 * could be stale or undefined; discard all readings until
+		 * TD_STATUS changes for the first time (or num_points is 0).
+		 */
+		if (tsdata->init_td_status) {
+			if (tsdata->init_td_status < 0)
+				tsdata->init_td_status = rdbuf[2];
+
+			if (num_points && rdbuf[2] == tsdata->init_td_status)
+				goto out;
+
+			tsdata->init_td_status = 0;
+		}
+
+		if (!error && num_points)
+			error = regmap_bulk_read(tsdata->regmap,
+						 tsdata->tdata_offset,
+						 &rdbuf[tsdata->tdata_offset],
+						 tsdata->point_len * num_points);
+	}
 	if (error) {
 		dev_err_ratelimited(dev, "Unable to fetch data, error: %d\n",
 				    error);
 		goto out;
 	}
 
-	for (i = 0; i < tsdata->max_support_points; i++) {
+	for (i = 0; i < num_points; i++) {
 		u8 *buf = &rdbuf[i * tsdata->point_len + tsdata->tdata_offset];
 
 		type = buf[0] >> 6;
@@ -335,11 +377,26 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id)
 
 		input_mt_slot(tsdata->input, id);
 		if (input_mt_report_slot_state(tsdata->input, MT_TOOL_FINGER,
-					       type != TOUCH_EVENT_UP))
+					       type != TOUCH_EVENT_UP)) {
 			touchscreen_report_pos(tsdata->input, &tsdata->prop,
 					       x, y, true);
+			active_ids |= BIT(id);
+		} else {
+			known_ids &= ~BIT(id);
+		}
 	}
 
+	/* One issue with the device is the TOUCH_UP message is not always
+	 * returned. Instead track which ids we know about and report when they
+	 * are no longer updated
+	 */
+	released_ids = known_ids & ~active_ids;
+	for_each_set_bit_from(b, &released_ids, tsdata->max_support_points) {
+		input_mt_slot(tsdata->input, b);
+		input_mt_report_slot_inactive(tsdata->input);
+	}
+	tsdata->known_ids = active_ids;
+
 	input_mt_report_pointer_emulation(tsdata->input, true);
 	input_sync(tsdata->input);
 
@@ -347,6 +404,22 @@ static irqreturn_t edt_ft5x06_ts_isr(int irq, void *dev_id)
 	return IRQ_HANDLED;
 }
 
+static void edt_ft5x06_ts_irq_poll_timer(struct timer_list *t)
+{
+	struct edt_ft5x06_ts_data *tsdata = from_timer(tsdata, t, timer);
+
+	schedule_work(&tsdata->work_i2c_poll);
+	mod_timer(&tsdata->timer, jiffies + msecs_to_jiffies(POLL_INTERVAL_MS));
+}
+
+static void edt_ft5x06_ts_work_i2c_poll(struct work_struct *work)
+{
+	struct edt_ft5x06_ts_data *tsdata = container_of(work,
+			struct edt_ft5x06_ts_data, work_i2c_poll);
+
+	edt_ft5x06_ts_isr(0, tsdata);
+}
+
 struct edt_ft5x06_attribute {
 	struct device_attribute dattr;
 	size_t field_offset;
@@ -862,6 +935,9 @@ static int edt_ft5x06_ts_identify(struct i2c_client *client,
 	char *model_name = tsdata->name;
 	char *fw_version = tsdata->fw_version;
 
+	snprintf(model_name, EDT_NAME_PREFIX_LEN + 1, "%s ", dev_name(&client->dev));
+	model_name += strlen(model_name);
+
 	/* see what we find if we assume it is a M06 *
 	 * if we get less than EDT_NAME_LEN, we don't want
 	 * to have garbage in there
@@ -1050,20 +1126,23 @@ static void edt_ft5x06_ts_get_parameters(struct edt_ft5x06_ts_data *tsdata)
 static void edt_ft5x06_ts_set_tdata_parameters(struct edt_ft5x06_ts_data *tsdata)
 {
 	int crclen;
+	int points;
 
 	if (tsdata->version == EDT_M06) {
 		tsdata->tdata_cmd = 0xf9;
 		tsdata->tdata_offset = 5;
 		tsdata->point_len = 4;
 		crclen = 1;
+		points = tsdata->max_support_points;
 	} else {
 		tsdata->tdata_cmd = 0x0;
 		tsdata->tdata_offset = 3;
 		tsdata->point_len = 6;
 		crclen = 0;
+		points = 0;
 	}
 
-	tsdata->tdata_len = tsdata->point_len * tsdata->max_support_points +
+	tsdata->tdata_len = tsdata->point_len * points +
 		tsdata->tdata_offset + crclen;
 }
 
@@ -1258,7 +1337,7 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client)
 	if (tsdata->reset_gpio) {
 		usleep_range(5000, 6000);
 		gpiod_set_value_cansleep(tsdata->reset_gpio, 0);
-		msleep(300);
+		msleep(RESET_DELAY_MS);
 	}
 
 	input = devm_input_allocate_device(&client->dev);
@@ -1332,17 +1411,28 @@ static int edt_ft5x06_ts_probe(struct i2c_client *client)
 		return error;
 	}
 
-	irq_flags = irq_get_trigger_type(client->irq);
-	if (irq_flags == IRQF_TRIGGER_NONE)
-		irq_flags = IRQF_TRIGGER_FALLING;
-	irq_flags |= IRQF_ONESHOT;
+	if (client->irq) {
+		irq_flags = irq_get_trigger_type(client->irq);
+		if (irq_flags == IRQF_TRIGGER_NONE)
+			irq_flags = IRQF_TRIGGER_FALLING;
+		irq_flags |= IRQF_ONESHOT;
 
-	error = devm_request_threaded_irq(&client->dev, client->irq,
-					  NULL, edt_ft5x06_ts_isr, irq_flags,
-					  client->name, tsdata);
-	if (error) {
-		dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
-		return error;
+		error = devm_request_threaded_irq(&client->dev, client->irq,
+						  NULL, edt_ft5x06_ts_isr,
+						  irq_flags, client->name,
+						  tsdata);
+		if (error) {
+			dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
+			return error;
+		}
+	} else {
+		tsdata->init_td_status = -1; /* filter bogus initial data */
+		INIT_WORK(&tsdata->work_i2c_poll,
+			  edt_ft5x06_ts_work_i2c_poll);
+		timer_setup(&tsdata->timer, edt_ft5x06_ts_irq_poll_timer, 0);
+		tsdata->timer.expires =
+			jiffies + msecs_to_jiffies(FIRST_POLL_DELAY_MS);
+		add_timer(&tsdata->timer);
 	}
 
 	error = input_register_device(input);
@@ -1364,6 +1454,10 @@ static void edt_ft5x06_ts_remove(struct i2c_client *client)
 {
 	struct edt_ft5x06_ts_data *tsdata = i2c_get_clientdata(client);
 
+	if (!client->irq) {
+		del_timer(&tsdata->timer);
+		cancel_work_sync(&tsdata->work_i2c_poll);
+	}
 	edt_ft5x06_ts_teardown_debugfs(tsdata);
 }
 
diff --git a/drivers/input/touchscreen/goodix.c b/drivers/input/touchscreen/goodix.c
index 4b497540ed2d..74dea38b9fad 100644
--- a/drivers/input/touchscreen/goodix.c
+++ b/drivers/input/touchscreen/goodix.c
@@ -44,9 +44,11 @@
 #define GOODIX_HAVE_KEY			BIT(4)
 #define GOODIX_BUFFER_STATUS_TIMEOUT	20
 
-#define RESOLUTION_LOC		1
-#define MAX_CONTACTS_LOC	5
-#define TRIGGER_LOC		6
+#define RESOLUTION_LOC			1
+#define MAX_CONTACTS_LOC		5
+#define TRIGGER_LOC			6
+
+#define GOODIX_POLL_INTERVAL_MS		17	/* 17ms = 60fps */
 
 /* Our special handling for GPIO accesses through ACPI is x86 specific */
 #if defined CONFIG_X86 && defined CONFIG_ACPI
@@ -297,6 +299,14 @@ static int goodix_ts_read_input_report(struct goodix_ts_data *ts, u8 *data)
 				return 0;
 		}
 
+		if (!ts->client->irq)
+			/*
+			 * No point in retrying if polling, particularly as some
+			 * versions continuously report "not ready" if there are
+			 * no touch points.
+			 */
+			break;
+
 		usleep_range(1000, 2000); /* Poll every 1 - 2 ms */
 	} while (time_before(jiffies, max_timeout));
 
@@ -497,6 +507,14 @@ static void goodix_process_events(struct goodix_ts_data *ts)
 	input_sync(ts->input_dev);
 }
 
+static void goodix_ts_work_i2c_poll(struct input_dev *input)
+{
+	struct goodix_ts_data *ts = input_get_drvdata(input);
+
+	goodix_process_events(ts);
+	goodix_i2c_write_u8(ts->client, GOODIX_READ_COOR_ADDR, 0);
+}
+
 /**
  * goodix_ts_irq_handler - The IRQ handler
  *
@@ -513,13 +531,29 @@ static irqreturn_t goodix_ts_irq_handler(int irq, void *dev_id)
 	return IRQ_HANDLED;
 }
 
+static void goodix_enable_irq(struct goodix_ts_data *ts)
+{
+	if (ts->client->irq)
+		enable_irq(ts->client->irq);
+}
+
+static void goodix_disable_irq(struct goodix_ts_data *ts)
+{
+	if (ts->client->irq)
+		disable_irq(ts->client->irq);
+}
+
 static void goodix_free_irq(struct goodix_ts_data *ts)
 {
-	devm_free_irq(&ts->client->dev, ts->client->irq, ts);
+	if (ts->client->irq)
+		devm_free_irq(&ts->client->dev, ts->client->irq, ts);
 }
 
 static int goodix_request_irq(struct goodix_ts_data *ts)
 {
+	if (!ts->client->irq)
+		return 0;
+
 	return devm_request_threaded_irq(&ts->client->dev, ts->client->irq,
 					 NULL, goodix_ts_irq_handler,
 					 ts->irq_flags, ts->client->name, ts);
@@ -1219,6 +1253,18 @@ static int goodix_configure_dev(struct goodix_ts_data *ts)
 		return error;
 	}
 
+	input_set_drvdata(ts->input_dev, ts);
+
+	if (!ts->client->irq) {
+		error = input_setup_polling(ts->input_dev, goodix_ts_work_i2c_poll);
+		if (error) {
+			dev_err(&ts->client->dev,
+				 "could not set up polling mode, %d\n", error);
+			return error;
+		}
+		input_set_poll_interval(ts->input_dev, GOODIX_POLL_INTERVAL_MS);
+	}
+
 	error = input_register_device(ts->input_dev);
 	if (error) {
 		dev_err(&ts->client->dev,
@@ -1422,7 +1468,7 @@ static int goodix_suspend(struct device *dev)
 
 	/* We need gpio pins to suspend/resume */
 	if (ts->irq_pin_access_method == IRQ_PIN_ACCESS_NONE) {
-		disable_irq(client->irq);
+		goodix_disable_irq(ts);
 		return 0;
 	}
 
@@ -1466,7 +1512,7 @@ static int goodix_resume(struct device *dev)
 	int error;
 
 	if (ts->irq_pin_access_method == IRQ_PIN_ACCESS_NONE) {
-		enable_irq(client->irq);
+		goodix_enable_irq(ts);
 		return 0;
 	}
 
diff --git a/drivers/input/touchscreen/ili210x.c b/drivers/input/touchscreen/ili210x.c
index 260c83dc23a2..b08d911c893c 100644
--- a/drivers/input/touchscreen/ili210x.c
+++ b/drivers/input/touchscreen/ili210x.c
@@ -67,6 +67,8 @@ struct ili210x {
 	u8 version_proto[2];
 	u8 ic_mode[2];
 	bool stop;
+	struct timer_list poll_timer;
+	struct work_struct poll_work;
 };
 
 static int ili210x_read_reg(struct i2c_client *client,
@@ -360,6 +362,34 @@ static irqreturn_t ili210x_irq(int irq, void *irq_data)
 	return IRQ_HANDLED;
 }
 
+static void ili210x_poll_work(struct work_struct *work)
+{
+	struct ili210x *priv = container_of(work, struct ili210x, poll_work);
+	struct i2c_client *client = priv->client;
+	const struct ili2xxx_chip *chip = priv->chip;
+	u8 touchdata[ILI210X_DATA_SIZE] = { 0 };
+	bool touch;
+	int error;
+
+	error = chip->get_touch_data(client, touchdata);
+	if (error) {
+		dev_err(&client->dev, "Unable to get touch data: %d\n", error);
+		return;
+	}
+
+	touch = ili210x_report_events(priv, touchdata);
+}
+
+static void ili210x_poll_timer_callback(struct timer_list *t)
+{
+	struct ili210x *priv = from_timer(priv, t, poll_timer);
+
+	schedule_work(&priv->poll_work);
+
+	if (!priv->stop)
+		mod_timer(&priv->poll_timer, jiffies + msecs_to_jiffies(ILI2XXX_POLL_PERIOD));
+}
+
 static int ili251x_firmware_update_resolution(struct device *dev)
 {
 	struct i2c_client *client = to_i2c_client(dev);
@@ -947,11 +977,6 @@ static int ili210x_i2c_probe(struct i2c_client *client)
 		return -ENODEV;
 	}
 
-	if (client->irq <= 0) {
-		dev_err(dev, "No IRQ!\n");
-		return -EINVAL;
-	}
-
 	reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
 	if (IS_ERR(reset_gpio))
 		return PTR_ERR(reset_gpio);
@@ -1003,12 +1028,17 @@ static int ili210x_i2c_probe(struct i2c_client *client)
 		return error;
 	}
 
-	error = devm_request_threaded_irq(dev, client->irq, NULL, ili210x_irq,
-					  IRQF_ONESHOT, client->name, priv);
-	if (error) {
-		dev_err(dev, "Unable to request touchscreen IRQ, err: %d\n",
-			error);
-		return error;
+	if (client->irq) {
+		error = devm_request_threaded_irq(dev, client->irq, NULL, ili210x_irq,
+					IRQF_ONESHOT, client->name, priv);
+		if (error) {
+			dev_err(dev, "Unable to request touchscreen IRQ, err: %d\n", error);
+			return error;
+		}
+	} else {
+		timer_setup(&priv->poll_timer, ili210x_poll_timer_callback, 0);
+		mod_timer(&priv->poll_timer, jiffies + msecs_to_jiffies(ILI2XXX_POLL_PERIOD));
+		INIT_WORK(&priv->poll_work, ili210x_poll_work);
 	}
 
 	error = devm_add_action_or_reset(dev, ili210x_stop, priv);
@@ -1024,6 +1054,16 @@ static int ili210x_i2c_probe(struct i2c_client *client)
 	return 0;
 }
 
+static void ili210x_i2c_remove(struct i2c_client *client)
+{
+	struct ili210x *tsdata = i2c_get_clientdata(client);
+
+	if (!client->irq) {
+		del_timer(&tsdata->poll_timer);
+		cancel_work_sync(&tsdata->poll_work);
+	}
+}
+
 static const struct i2c_device_id ili210x_i2c_id[] = {
 	{ "ili210x", (long)&ili210x_chip },
 	{ "ili2117", (long)&ili211x_chip },
@@ -1050,6 +1090,7 @@ static struct i2c_driver ili210x_ts_driver = {
 	},
 	.id_table = ili210x_i2c_id,
 	.probe = ili210x_i2c_probe,
+	.remove   = ili210x_i2c_remove,
 };
 
 module_i2c_driver(ili210x_ts_driver);
diff --git a/drivers/input/touchscreen/ilitek_v3_ts_i2c.c b/drivers/input/touchscreen/ilitek_v3_ts_i2c.c
new file mode 100644
index 000000000000..a02cd79b30d6
--- /dev/null
+++ b/drivers/input/touchscreen/ilitek_v3_ts_i2c.c
@@ -0,0 +1,761 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Raspberry Pi
+ *
+ * Based on edt-ft5x06 driver.
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/input/touchscreen.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+
+#include <drm/drm_panel.h>
+
+#define ILI_V3_NAME_LEN			23
+#define ILI_V3_NAME_PREFIX_LEN		8
+
+#define ILI_V3_POLL_INTERVAL		17	/* 60fps */
+#define ILI_V3_POLL_SLEEP_INTERVAL	300	/* 3.3fps */
+#define RESET_DELAY_MS			(65 + 85)
+
+#define ILI_V3_TOUCH_PACKET_LEN		72
+#define ILI_V3_GESTURE_PACKET_LEN	221
+#define P5_X_DEMO_MODE_PACKET_INFO_LEN	3
+#define TDDI_PID_ADDR					0x4009C
+#define TDDI_SECOND_PID_ADDR				0x40098
+#define TDDI_OTP_ID_ADDR				0x400A0
+#define TDDI_ANA_ID_ADDR				0x400A4
+
+#define P5_X_DEMO_AXIS_PACKET_ID			0x5B
+#define P5_X_GESTURE_PACKET_ID				0xAA
+
+#define P5_X_NEW_CONTROL_FORMAT				0xF2
+#define P5_X_READ_DATA_CTRL				0xF6
+ #define P5_X_GET_TP_INFORMATION				0x20
+ #define P5_X_GET_KEY_INFORMATION				0x27
+ #define P5_X_GET_TOOL_VERSION					0x28
+ #define P5_X_GET_PANEL_INFORMATION				0x29
+ #define P5_X_GET_FW_VERSION					0x21
+ #define P5_X_GET_PROTOCOL_VERSION				0x22
+ #define P5_X_GET_CORE_VERSION					0x23
+ #define P5_X_GET_CORE_VERSION_NEW				0x24
+ #define P5_X_GET_REPORT_INFORMATION				0x2B
+ #define P5_X_GET_ALL_INFORMATION				0x2F
+ #define P5_X_GET_REPORT_FORMAT					0x37
+ #define P5_X_GET_BLOCK_INFOMATION				0x38
+ #define P5_X_MODE_CONTROL					0xF0
+
+#define MIN_FW_VERSION 0x8c000600U	/* Minimum of 140.0.6.0 */
+
+#define MAX_TOUCH_NUM			10
+
+struct ili_v3_ts_data {
+	struct i2c_client *client;
+	struct i2c_client *slave_addr;
+	struct input_dev *input;
+	struct touchscreen_properties prop;
+	unsigned int panel_width;
+	unsigned int panel_height;
+	struct regulator *vcc;
+	struct regulator *iovcc;
+
+	struct gpio_desc *reset_gpio;
+	struct gpio_desc *wake_gpio;
+
+	struct drm_panel_follower panel_follower;
+
+	/* Mutex to prevent suspend/resume interacting with polling */
+	struct mutex mutex;
+	int max_support_points;
+
+	char name[ILI_V3_NAME_PREFIX_LEN + ILI_V3_NAME_LEN];
+
+	int finger;
+	int curt_touch[MAX_TOUCH_NUM];
+	int prev_touch[MAX_TOUCH_NUM];
+	u8 touch_num;
+	int last_touch;
+	bool asleep;
+};
+
+struct ili_v3_i2c_chip_data {
+	int  max_support_points;
+};
+
+struct ilitek_touch_info {
+	u16 id;
+	u16 x;
+	u16 y;
+	u16 pressure;
+};
+
+struct ilitek_fw_info {
+	u8 cmd;
+	u8 dummy1;
+	u8 min_x;
+	u8 min_y;
+	u16 max_x;
+	u16 max_y;
+	u8 xch_num;
+	u8 ych_num;
+	u8 dummy2;
+	u8 dummy3;
+	u8 stx;
+	u8 srx;
+	u8 dummy4;
+	u8 dummy5;
+	u8 fw_ver[4];	/* Offset 16-19 */
+	u8 fw_mp_ver[4];	/* Offset 20-23 */
+	u8 dummy6;
+	u8 core_ver[4];	/* Offset 25-28 */
+	u8 dummy6a;
+	u8 dummy6b;
+	u8 dummy6c;
+	u32 dummy7;	/* Offset 32-35 */
+	u32 dummy8;	/* Offset 36-39 */
+	u32 dummy9;	/* Offset 40-43 */
+	u8 dummy10;
+	u8 dummy10a;
+	u8 panel_info_cmd;	/* Offset 46 */
+	u8 panel_width_high;
+	u8 panel_width_low;
+	u8 panel_height_high;
+	u8 panel_height_low;
+	u8 panel_trans_xy;	/* Offset 51 */
+};
+
+static int ili_v3_i2c_transfer(struct i2c_client *client, u8 *write_buf,
+			       int write_len, u8 *read_buf, int read_len)
+{
+	struct i2c_msg msgs[2];
+	int ret = 0;
+
+	if (write_len && write_buf) {
+		msgs[0].flags = 0;
+		msgs[0].addr  = client->addr;
+		msgs[0].len   = write_len;
+		msgs[0].buf   = write_buf;
+		ret = i2c_transfer(client->adapter, msgs, 1);
+		if (ret >= 0)
+			ret = (ret == 1 ? 0 : -EIO);
+		fsleep(1000);
+	}
+
+	if (!ret && read_len && read_buf) {
+		msgs[0].flags = I2C_M_RD;
+		msgs[0].addr  = client->addr;
+		msgs[0].len   = read_len;
+		msgs[0].buf   = read_buf;
+		ret = i2c_transfer(client->adapter, msgs, 1);
+		if (ret >= 0)
+			ret = (ret == 1 ? 0 : -EIO);
+		fsleep(1000);
+	}
+
+	if (ret)
+		dev_dbg(&client->dev, "Error transferring %d / %d : %d\n",
+			write_len, read_len, ret);
+	return ret;
+}
+
+static int ili_v3_i2c_read_u32_reg(struct i2c_client *client, u32 reg, u32 *val)
+{
+	u8 wr_buf[4];
+	int ret;
+
+	wr_buf[0] = 0x25;
+	wr_buf[1] = (char)((reg & 0x000000FF) >> 0);
+	wr_buf[2] = (char)((reg & 0x0000FF00) >> 8);
+	wr_buf[3] = (char)((reg & 0x00FF0000) >> 16);
+
+	ret = ili_v3_i2c_transfer(client, wr_buf, sizeof(wr_buf), (u8 *)val,
+				  sizeof(*val));
+
+	return ret;
+}
+
+static int ili_v3_i2c_write_u32_reg(struct i2c_client *client, u32 reg, u32 val)
+{
+	u8 wr_buf[8];
+	int ret, i;
+
+	wr_buf[0] = 0x25;
+	wr_buf[1] = (u8)((reg & 0x000000FF) >> 0);
+	wr_buf[2] = (u8)((reg & 0x0000FF00) >> 8);
+	wr_buf[3] = (u8)((reg & 0x00FF0000) >> 16);
+
+	for (i = 0; i < 4; i++)
+		wr_buf[i + 4] = (u8)(val >> (8 * i));
+
+	ret = ili_v3_i2c_transfer(client, wr_buf, sizeof(wr_buf), NULL, 0);
+
+	return ret;
+}
+
+static u8 ili_v3_calc_checksum(const u8 *packet, int len)
+{
+	int i;
+	s32 sum = 0;
+
+	for (i = 0; i < len; i++)
+		sum += packet[i];
+
+	return (u8)((-sum) & 0xFF);
+}
+
+static void ili_v3_report_ap_mode(struct ili_v3_ts_data *tsdata, u8 *buf,
+				  int len)
+{
+	struct ilitek_touch_info touch_info[MAX_TOUCH_NUM] = {0};
+	int i = 0;
+	u32 xop = 0, yop = 0;
+
+	tsdata->finger = 0;
+
+	for (i = 0; i < MAX_TOUCH_NUM; i++) {
+		if (buf[5 * i + 1 + P5_X_DEMO_MODE_PACKET_INFO_LEN] == 0xFF &&
+		    buf[5 * i + 2 + P5_X_DEMO_MODE_PACKET_INFO_LEN] == 0xFF &&
+		    buf[5 * i + 3 + P5_X_DEMO_MODE_PACKET_INFO_LEN] == 0xFF &&
+		    buf[5 * i + 4 + P5_X_DEMO_MODE_PACKET_INFO_LEN] == 0xFF) {
+			tsdata->curt_touch[i] = 0;
+			continue;
+		}
+		xop = ((buf[5 * i + 1 + P5_X_DEMO_MODE_PACKET_INFO_LEN] << 8) |
+		       (buf[5 * i + 2 + P5_X_DEMO_MODE_PACKET_INFO_LEN]));
+		yop = ((buf[5 * i + 3 + P5_X_DEMO_MODE_PACKET_INFO_LEN] << 8) |
+		       (buf[5 * i + 4 + P5_X_DEMO_MODE_PACKET_INFO_LEN]));
+
+		touch_info[tsdata->finger].x = xop;
+		touch_info[tsdata->finger].y = yop;
+
+		touch_info[tsdata->finger].id = i;
+
+		touch_info[tsdata->finger].pressure = 1;
+
+		tsdata->finger++;
+		tsdata->curt_touch[i] = 1;
+	}
+
+	if (tsdata->finger) {
+		for (i = 0; i < tsdata->finger; i++) {
+			input_report_key(tsdata->input, BTN_TOUCH, 1);
+			input_mt_slot(tsdata->input, touch_info[i].id);
+			input_mt_report_slot_state(tsdata->input,
+						   MT_TOOL_FINGER, true);
+			touchscreen_report_pos(tsdata->input, &tsdata->prop,
+					       touch_info[i].x, touch_info[i].y,
+					       true);
+			input_report_key(tsdata->input, BTN_TOOL_FINGER, 1);
+		}
+		for (i = 0; i < MAX_TOUCH_NUM; i++) {
+			if (tsdata->curt_touch[i] == 0 &&
+			    tsdata->prev_touch[i] == 1) {
+				input_mt_slot(tsdata->input, i);
+				input_mt_report_slot_state(tsdata->input,
+							   MT_TOOL_FINGER,
+							   false);
+			}
+			tsdata->prev_touch[i] = tsdata->curt_touch[i];
+		}
+		input_sync(tsdata->input);
+		tsdata->last_touch = tsdata->finger;
+	} else {
+		if (tsdata->last_touch) {
+			for (i = 0; i < MAX_TOUCH_NUM; i++) {
+				if (tsdata->curt_touch[i] == 0 &&
+				    tsdata->prev_touch[i] == 1) {
+					input_mt_slot(tsdata->input, i);
+					input_mt_report_slot_state(tsdata->input,
+								   MT_TOOL_FINGER,
+								   false);
+				}
+				tsdata->prev_touch[i] = tsdata->curt_touch[i];
+			}
+			input_report_key(tsdata->input, BTN_TOUCH, 0);
+			input_report_key(tsdata->input, BTN_TOOL_FINGER, 0);
+			input_sync(tsdata->input);
+			tsdata->last_touch = 0;
+		}
+	}
+}
+
+static void ili_v3_handle_gesture(struct ili_v3_ts_data *tsdata)
+{
+	/*
+	 * Gestures are currently only being used in low power mode to wake up,
+	 * as this touch controller doesn't generate touch data whilst the
+	 * display is disabled.
+	 * Generate the most minimal event possible that should wake up the
+	 * display.
+	 */
+	input_report_key(tsdata->input, BTN_TOUCH, 1);
+	input_mt_slot(tsdata->input, 0);
+	input_mt_report_slot_state(tsdata->input,
+				   MT_TOOL_FINGER, true);
+	input_report_key(tsdata->input, BTN_TOOL_FINGER, 1);
+	input_sync(tsdata->input);
+
+	input_mt_slot(tsdata->input, 0);
+	input_mt_report_slot_state(tsdata->input,
+				   MT_TOOL_FINGER,
+				   false);
+	input_report_key(tsdata->input, BTN_TOUCH, 0);
+	input_report_key(tsdata->input, BTN_TOOL_FINGER, 0);
+	input_sync(tsdata->input);
+}
+
+static irqreturn_t ili_v3_ts_isr(int irq, void *dev_id)
+{
+	struct ili_v3_ts_data *tsdata = dev_id;
+	u8 checksum, len, expected_len;
+	u8 rdbuf[ILI_V3_GESTURE_PACKET_LEN];
+	u8 len_offset = 2;
+
+	mutex_lock(&tsdata->mutex);
+
+	len = tsdata->asleep ? ILI_V3_GESTURE_PACKET_LEN : ILI_V3_TOUCH_PACKET_LEN;
+	ili_v3_i2c_transfer(tsdata->client, NULL, 0, rdbuf, len);
+
+	switch (rdbuf[0]) {
+	case P5_X_DEMO_AXIS_PACKET_ID:
+		expected_len = ILI_V3_TOUCH_PACKET_LEN;
+		break;
+	case P5_X_GESTURE_PACKET_ID:
+		expected_len = ILI_V3_GESTURE_PACKET_LEN;
+		len_offset = 3;
+		break;
+
+	default:
+		goto out;
+	}
+
+	if (expected_len != rdbuf[len_offset])
+		dev_err(&tsdata->client->dev, "buffer len %u when %u was expected, cmd %02x\n",
+			rdbuf[len_offset], expected_len, rdbuf[0]);
+
+	checksum = ili_v3_calc_checksum(rdbuf, rdbuf[len_offset]);
+
+	if (checksum) {
+		dev_err(&tsdata->client->dev, "checksum failed cmd %02x len %u\n",
+			rdbuf[0], rdbuf[len_offset]);
+		goto out;
+	}
+
+	switch (rdbuf[0]) {
+	case P5_X_DEMO_AXIS_PACKET_ID:
+		ili_v3_report_ap_mode(tsdata, rdbuf, ILI_V3_TOUCH_PACKET_LEN);
+		break;
+	case P5_X_GESTURE_PACKET_ID:
+		if (!tsdata->asleep)
+			break;
+
+		ili_v3_handle_gesture(tsdata);
+		break;
+	}
+
+out:
+	mutex_unlock(&tsdata->mutex);
+
+	return IRQ_HANDLED;
+}
+
+static void ili_v3_ts_work_i2c_poll(struct input_dev *input)
+{
+	struct ili_v3_ts_data *tsdata = input_get_drvdata(input);
+
+	ili_v3_ts_isr(0, tsdata);
+}
+
+static int ili_v3_ts_identify(struct ili_v3_ts_data *tsdata)
+{
+	struct i2c_client *client = tsdata->client;
+	char *model_name = tsdata->name;
+
+	snprintf(model_name, ILI_V3_NAME_PREFIX_LEN + 1, "%s ",
+		 dev_name(&client->dev));
+	model_name += strlen(model_name);
+
+	snprintf(model_name, ILI_V3_NAME_LEN, "ili_v3");
+
+	return 0;
+}
+
+static int ili_v3_init(struct ili_v3_ts_data *tsdata)
+{
+	static u8 stop_msg[2] = { 0x10, 0x10 };
+	static u8 cmd_open_msg[4] = { 0x25, 0x62, 0x10, 0x18 };
+	static u8 slave_mode[5] = { 0x25, 0x00, 0x10, 0x04, 0x01};
+	static u8 slave_mode2[5] = { 0x25, 0x03, 0x10, 0x04, 0x00};
+	static u8 read_protocol_ver[2] = {P5_X_READ_DATA_CTRL,
+					  P5_X_GET_PROTOCOL_VERSION};
+	static u8 read_all_info[1] = {P5_X_GET_ALL_INFORMATION};
+	static u8 set_tp_data_len[3] = {P5_X_NEW_CONTROL_FORMAT, 0x00, 0x03};
+	struct i2c_client *master_client = tsdata->client;
+	struct i2c_client *slave_client = tsdata->slave_addr;
+	u8 rd_buf[97];
+	struct ilitek_fw_info *fw_info = (struct ilitek_fw_info *)rd_buf;
+	u32 value;
+
+	if (tsdata->reset_gpio) {
+		usleep_range(5000, 6000);
+		/* Come out of reset */
+		gpiod_set_value_cansleep(tsdata->reset_gpio, 0);
+		msleep(RESET_DELAY_MS);
+	}
+
+	ili_v3_i2c_transfer(master_client, stop_msg, sizeof(stop_msg), NULL, 0);
+
+	ili_v3_i2c_transfer(master_client, cmd_open_msg, sizeof(cmd_open_msg), NULL, 0);
+
+	ili_v3_i2c_transfer(slave_client, slave_mode, sizeof(slave_mode), NULL, 0);
+	ili_v3_i2c_transfer(slave_client, slave_mode2, sizeof(slave_mode2), NULL, 0);
+
+	ili_v3_i2c_transfer(master_client, cmd_open_msg, sizeof(cmd_open_msg), NULL, 0);
+
+	ili_v3_i2c_transfer(master_client, slave_mode, sizeof(slave_mode), NULL, 0);
+	ili_v3_i2c_transfer(master_client, slave_mode2, sizeof(slave_mode2), NULL, 0);
+
+	ili_v3_i2c_write_u32_reg(master_client, 0x051024, 0xa5a5a5a5);
+	ili_v3_i2c_read_u32_reg(master_client, 0x051024, &value);
+	if (value != 0xa5a5a5a5)
+		dev_err(&master_client->dev, "dummy read didn't match - %08x\n",
+			value);
+
+	ili_v3_i2c_read_u32_reg(slave_client, TDDI_PID_ADDR, &value);
+	dev_info(&master_client->dev, "Slave PIDADDR %08x\n", value);
+	ili_v3_i2c_read_u32_reg(slave_client, TDDI_SECOND_PID_ADDR, &value);
+	dev_info(&master_client->dev, "Slave TDDI_SECOND_PID_ADDR %08x\n", value);
+
+	ili_v3_i2c_read_u32_reg(master_client, TDDI_PID_ADDR, &value);
+	dev_info(&master_client->dev, "Master PIDADDR %08x\n", value);
+	ili_v3_i2c_read_u32_reg(master_client, TDDI_SECOND_PID_ADDR, &value);
+	dev_info(&master_client->dev, "Master TDDI_SECOND_PID_ADDR %08x\n", value);
+	ili_v3_i2c_read_u32_reg(master_client, TDDI_OTP_ID_ADDR, &value);
+	dev_info(&master_client->dev, "Master TDDI_OTP_ID_ADDR %08x\n", value);
+	ili_v3_i2c_read_u32_reg(master_client, TDDI_ANA_ID_ADDR, &value);
+	dev_info(&master_client->dev, "Master TDDI_ANA_ID_ADDR %08x\n", value);
+
+	if (tsdata->reset_gpio) {
+		mdelay(1);
+		gpiod_set_value_cansleep(tsdata->reset_gpio, 1);
+		mdelay(5);
+		gpiod_set_value_cansleep(tsdata->reset_gpio, 0);
+		msleep(RESET_DELAY_MS);
+	}
+
+	ili_v3_i2c_transfer(master_client, read_protocol_ver, 2, NULL, 0);
+	ili_v3_i2c_transfer(master_client, &read_protocol_ver[1], 1, rd_buf, 4);
+	dev_info(&master_client->dev, "protocol version is %08x\n",
+		 *((u32 *)rd_buf));
+
+	ili_v3_i2c_transfer(master_client, read_all_info, 1, rd_buf, 97);
+
+	if (be32_to_cpu(*(unsigned int *)fw_info->fw_ver) < MIN_FW_VERSION)
+		dev_err(&master_client->dev, "Firmware version %u.%u.%u.%u is older than expected. Minimum %u.%u.%u.%u\n",
+			fw_info->fw_ver[0], fw_info->fw_ver[1],
+			fw_info->fw_ver[2], fw_info->fw_ver[3],
+			(MIN_FW_VERSION >> 24) & 0xff, (MIN_FW_VERSION >> 16) & 0xff,
+			(MIN_FW_VERSION >> 8) & 0xff, MIN_FW_VERSION & 0xff);
+
+	dev_info(&master_client->dev, "FW AP Version %u.%u.%u.%u\n",
+		 fw_info->fw_ver[0], fw_info->fw_ver[1], fw_info->fw_ver[2],
+		 fw_info->fw_ver[3]);
+	dev_info(&master_client->dev, "FW MP Version %u.%u.%u.%u\n",
+		 fw_info->fw_mp_ver[0], fw_info->fw_mp_ver[1],
+		 fw_info->fw_mp_ver[2], fw_info->fw_mp_ver[3]);
+	dev_info(&master_client->dev, "Core Version %u.%u.%u.%u\n",
+		 fw_info->core_ver[0], fw_info->core_ver[1],
+		 fw_info->core_ver[2], fw_info->core_ver[3]);
+
+	if (fw_info->panel_info_cmd == P5_X_GET_PANEL_INFORMATION) {
+		tsdata->panel_width = (fw_info->panel_width_high << 8) |
+				      fw_info->panel_width_low;
+		tsdata->panel_height = (fw_info->panel_height_high << 8) |
+				       fw_info->panel_height_low;
+
+		dev_info(&master_client->dev, "Configured size %u x %u%s\n",
+			 tsdata->panel_width, tsdata->panel_height,
+			 fw_info->panel_trans_xy ? " swapxy" : "");
+	}
+
+	ili_v3_i2c_transfer(master_client, set_tp_data_len, sizeof(set_tp_data_len),
+			    NULL, 0);
+
+	ili_v3_ts_identify(tsdata);
+
+	return 0;
+}
+
+static void ili_v3_disable_regulators(void *arg)
+{
+	struct ili_v3_ts_data *data = arg;
+
+	regulator_disable(data->vcc);
+	regulator_disable(data->iovcc);
+}
+
+static int ili_v3_ts_suspend(struct drm_panel_follower *follower)
+{
+	struct ili_v3_ts_data *tsdata = container_of(follower,
+						     struct ili_v3_ts_data,
+						     panel_follower);
+	static u8 pll_wakeup[] = { 0xA3 };
+	static u8 gesture_symbol_msg[] = { 0x01, 0x0a, 0x08, 0xff, 0xff, 0xff };
+	static u8 gesture_msg[] = { 0x01, 0x0a, 0x02 };
+
+	mutex_lock(&tsdata->mutex);
+
+	input_set_poll_interval(tsdata->input, ILI_V3_POLL_SLEEP_INTERVAL);
+
+	ili_v3_i2c_transfer(tsdata->client, pll_wakeup, sizeof(pll_wakeup),
+			    NULL, 0);
+
+	ili_v3_i2c_transfer(tsdata->client, gesture_symbol_msg,
+			    sizeof(gesture_symbol_msg), NULL, 0);
+	ili_v3_i2c_transfer(tsdata->client, gesture_msg, sizeof(gesture_msg),
+			    NULL, 0);
+
+	tsdata->asleep = true;
+	mutex_unlock(&tsdata->mutex);
+
+	return 0;
+}
+
+static int ili_v3_ts_resume(struct drm_panel_follower *follower)
+{
+	struct ili_v3_ts_data *tsdata = container_of(follower,
+						     struct ili_v3_ts_data,
+						     panel_follower);
+	mutex_lock(&tsdata->mutex);
+
+	tsdata->asleep = false;
+
+	gpiod_set_value_cansleep(tsdata->reset_gpio, 0);
+	mdelay(1);
+	gpiod_set_value_cansleep(tsdata->reset_gpio, 1);
+	mdelay(5);
+	gpiod_set_value_cansleep(tsdata->reset_gpio, 0);
+	msleep(RESET_DELAY_MS);
+
+	input_set_poll_interval(tsdata->input, ILI_V3_POLL_INTERVAL);
+
+	mutex_unlock(&tsdata->mutex);
+
+	return 0;
+}
+
+static const struct drm_panel_follower_funcs ili_v3_follower_funcs = {
+	.panel_prepared = ili_v3_ts_resume,
+	.panel_unpreparing = ili_v3_ts_suspend,
+};
+
+static int ili_v3_ts_probe(struct i2c_client *client)
+{
+	const struct i2c_device_id *id = i2c_client_get_device_id(client);
+	const struct ili_v3_i2c_chip_data *chip_data;
+	struct ili_v3_ts_data *tsdata;
+	struct input_dev *input;
+	unsigned long irq_flags;
+	int error;
+
+	tsdata = devm_kzalloc(&client->dev, sizeof(*tsdata), GFP_KERNEL);
+	if (!tsdata)
+		return -ENOMEM;
+
+	tsdata->client = client;
+	i2c_set_clientdata(client, tsdata);
+
+	chip_data = device_get_match_data(&client->dev);
+	if (!chip_data)
+		chip_data = (const struct ili_v3_i2c_chip_data *)id->driver_data;
+	if (!chip_data || !chip_data->max_support_points) {
+		dev_err(&client->dev, "invalid or missing chip data\n");
+		return -EINVAL;
+	}
+
+	tsdata->max_support_points = chip_data->max_support_points;
+
+	tsdata->vcc = devm_regulator_get(&client->dev, "vcc");
+	if (IS_ERR(tsdata->vcc))
+		return dev_err_probe(&client->dev, PTR_ERR(tsdata->vcc),
+				     "failed to request regulator\n");
+
+	tsdata->iovcc = devm_regulator_get(&client->dev, "iovcc");
+	if (IS_ERR(tsdata->iovcc)) {
+		error = PTR_ERR(tsdata->iovcc);
+		if (error != -EPROBE_DEFER)
+			dev_err(&client->dev,
+				"failed to request iovcc regulator: %d\n", error);
+		return error;
+	}
+
+	tsdata->slave_addr = i2c_new_dummy_device(client->adapter, 0x4f);
+	if (IS_ERR(tsdata->slave_addr))
+		return PTR_ERR(tsdata->slave_addr);
+
+	error = regulator_enable(tsdata->iovcc);
+	if (error < 0) {
+		dev_err(&client->dev, "failed to enable iovcc: %d\n", error);
+		goto fail_release_i2c;
+	}
+
+	/* Delay enabling VCC for > 10us (T_ivd) after IOVCC */
+	usleep_range(10, 100);
+
+	error = regulator_enable(tsdata->vcc);
+	if (error < 0) {
+		dev_err(&client->dev, "failed to enable vcc: %d\n", error);
+		regulator_disable(tsdata->iovcc);
+		goto fail_release_i2c;
+	}
+	msleep(100);
+	error = devm_add_action_or_reset(&client->dev,
+					 ili_v3_disable_regulators,
+					 tsdata);
+	if (error) {
+		regulator_disable(tsdata->iovcc);
+		regulator_disable(tsdata->vcc);
+		goto fail_release_i2c;
+	}
+
+	tsdata->reset_gpio = devm_gpiod_get_optional(&client->dev,
+						     "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(tsdata->reset_gpio)) {
+		error = PTR_ERR(tsdata->reset_gpio);
+		dev_err(&client->dev,
+			"Failed to request GPIO reset pin, error %d\n", error);
+		goto fail_release_i2c;
+	}
+
+	mutex_init(&tsdata->mutex);
+	tsdata->client = client;
+	i2c_set_clientdata(client, tsdata);
+
+	error = ili_v3_init(tsdata);
+	if (error) {
+		dev_err(&client->dev, "touchscreen probe failed\n");
+		goto fail_release_i2c;
+	}
+
+	tsdata->panel_follower.funcs = &ili_v3_follower_funcs;
+
+	input = devm_input_allocate_device(&client->dev);
+	if (!input) {
+		dev_err(&client->dev, "failed to allocate input device.\n");
+		error = -ENOMEM;
+		goto fail_reset_gpio;
+	}
+	tsdata->input = input;
+
+	input->name = tsdata->name;
+	input->id.bustype = BUS_I2C;
+	input->dev.parent = &client->dev;
+	input_set_drvdata(input, tsdata);
+
+	input_set_abs_params(input, ABS_MT_POSITION_X,
+			     0, tsdata->panel_width - 1, 0, 0);
+	input_set_abs_params(input, ABS_MT_POSITION_Y,
+			     0, tsdata->panel_height - 1, 0, 0);
+
+	touchscreen_parse_properties(input, true, &tsdata->prop);
+
+	error = input_mt_init_slots(input, tsdata->max_support_points,
+				    INPUT_MT_DIRECT);
+	if (error) {
+		dev_err(&client->dev, "Unable to init MT slots.\n");
+		goto fail_reset_gpio;
+	}
+
+	if (client->irq) {
+		irq_flags = irq_get_trigger_type(client->irq);
+		if (irq_flags == IRQF_TRIGGER_NONE)
+			irq_flags = IRQF_TRIGGER_FALLING;
+		irq_flags |= IRQF_ONESHOT;
+
+		error = devm_request_threaded_irq(&client->dev, client->irq,
+						  NULL, ili_v3_ts_isr,
+						  irq_flags, client->name,
+						  tsdata);
+		if (error) {
+			dev_err(&client->dev, "Unable to request touchscreen IRQ.\n");
+			goto fail_reset_gpio;
+		}
+	} else {
+		error = input_setup_polling(input, ili_v3_ts_work_i2c_poll);
+		if (error) {
+			dev_err(&client->dev, "could not set up polling mode, %d\n",
+				error);
+			goto fail_reset_gpio;
+		}
+
+		input_set_poll_interval(input, ILI_V3_POLL_INTERVAL);
+	}
+
+	error = input_register_device(input);
+	if (error)
+		goto fail_reset_gpio;
+
+	error = drm_panel_add_follower(&client->dev, &tsdata->panel_follower);
+	if (error) {
+		dev_err(&client->dev, "Failed to add panel follower - %d\n", error);
+		goto fail_reset_gpio;
+	}
+
+	return 0;
+
+fail_reset_gpio:
+	gpiod_set_value_cansleep(tsdata->reset_gpio, 1);
+fail_release_i2c:
+	i2c_unregister_device(tsdata->slave_addr);
+
+	return error;
+}
+
+static void ili_v3_remove(struct i2c_client *client)
+{
+	struct ili_v3_ts_data *tsdata = i2c_get_clientdata(client);
+
+	gpiod_set_value_cansleep(tsdata->reset_gpio, 1);
+	drm_panel_remove_follower(&tsdata->panel_follower);
+	i2c_unregister_device(tsdata->slave_addr);
+}
+
+static const struct ili_v3_i2c_chip_data ili_v3_data = {
+	.max_support_points = 10,
+};
+
+static const struct i2c_device_id ili_v3_ts_id[] = {
+	{ .name = "ili79600a", .driver_data = (long)&ili_v3_data },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, ili_v3_ts_id);
+
+static const struct of_device_id ili_v3_of_match[] = {
+	{ .compatible = "ilitek,ili79600a", .data = &ili_v3_data },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, ili_v3_of_match);
+
+static struct i2c_driver ili_v3_ts_driver = {
+	.driver = {
+		.name = "ilitek_v3",
+		.of_match_table = ili_v3_of_match,
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+	},
+	.id_table = ili_v3_ts_id,
+	.probe    = ili_v3_ts_probe,
+	.remove   = ili_v3_remove,
+};
+
+module_i2c_driver(ili_v3_ts_driver);
+
+MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>");
+MODULE_DESCRIPTION("Ilitek V3 I2C Touchscreen Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index 1469ad0794f2..5a298a82265c 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -518,4 +518,11 @@ config SPRD_IOMMU
 
 	  Say Y here if you want to use the multimedia devices listed above.
 
+config BCM2712_IOMMU
+       bool "BCM2712 IOMMU driver"
+       depends on ARM64 && ARCH_BCM
+       select IOMMU_API
+       help
+	 IOMMU driver for BCM2712
+
 endif # IOMMU_SUPPORT
diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile
index 542760d963ec..52970e291d60 100644
--- a/drivers/iommu/Makefile
+++ b/drivers/iommu/Makefile
@@ -30,3 +30,4 @@ obj-$(CONFIG_IOMMU_SVA) += iommu-sva.o
 obj-$(CONFIG_IOMMU_IOPF) += io-pgfault.o
 obj-$(CONFIG_SPRD_IOMMU) += sprd-iommu.o
 obj-$(CONFIG_APPLE_DART) += apple-dart.o
+obj-$(CONFIG_BCM2712_IOMMU) += bcm2712-iommu.o bcm2712-iommu-cache.o
diff --git a/drivers/iommu/bcm2712-iommu-cache.c b/drivers/iommu/bcm2712-iommu-cache.c
new file mode 100644
index 000000000000..fdea69f5370b
--- /dev/null
+++ b/drivers/iommu/bcm2712-iommu-cache.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IOMMU driver for BCM2712
+ *
+ * Copyright (c) 2023 Raspberry Pi Ltd.
+ */
+
+#include "bcm2712-iommu.h"
+
+#include <linux/err.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define MMUC_CONTROL_ENABLE   1
+#define MMUC_CONTROL_FLUSH    2
+#define MMUC_CONTROL_FLUSHING 4
+
+void bcm2712_iommu_cache_flush(struct bcm2712_iommu_cache *cache)
+{
+	unsigned long flags;
+	int i;
+
+	spin_lock_irqsave(&cache->hw_lock, flags);
+	if (cache->reg_base) {
+		/* Enable and flush the TLB cache */
+		writel(MMUC_CONTROL_ENABLE | MMUC_CONTROL_FLUSH,
+		       cache->reg_base);
+
+		/* Wait for flush to complete: it should be very quick */
+		for (i = 0; i < 1024; i++) {
+			if (!(MMUC_CONTROL_FLUSHING & readl(cache->reg_base)))
+				break;
+			cpu_relax();
+		}
+	}
+	spin_unlock_irqrestore(&cache->hw_lock, flags);
+}
+
+static int bcm2712_iommu_cache_probe(struct platform_device *pdev)
+{
+	struct bcm2712_iommu_cache *cache;
+
+	dev_info(&pdev->dev, __func__);
+	cache = devm_kzalloc(&pdev->dev, sizeof(*cache), GFP_KERNEL);
+	if (!cache)
+		return -ENOMEM;
+
+	cache->dev = &pdev->dev;
+	platform_set_drvdata(pdev, cache);
+	spin_lock_init(&cache->hw_lock);
+
+	/* Get IOMMUC registers; we only use the first register (IOMMUC_CTRL) */
+	cache->reg_base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(cache->reg_base)) {
+		dev_err(&pdev->dev, "Failed to get IOMMU Cache registers address\n");
+		cache->reg_base = NULL;
+	}
+	return 0;
+}
+
+static const struct of_device_id bcm2712_iommu_cache_of_match[] = {
+	{
+		. compatible = "brcm,bcm2712-iommuc"
+	},
+	{ /* sentinel */ },
+};
+
+static struct platform_driver bcm2712_iommu_cache_driver = {
+	.probe = bcm2712_iommu_cache_probe,
+	.driver = {
+		.name = "bcm2712-iommu-cache",
+		.of_match_table = bcm2712_iommu_cache_of_match
+	},
+};
+
+builtin_platform_driver(bcm2712_iommu_cache_driver);
diff --git a/drivers/iommu/bcm2712-iommu.c b/drivers/iommu/bcm2712-iommu.c
new file mode 100644
index 000000000000..5ca171863d44
--- /dev/null
+++ b/drivers/iommu/bcm2712-iommu.c
@@ -0,0 +1,761 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * IOMMU driver for BCM2712
+ *
+ * Copyright (c) 2023-2025 Raspberry Pi Ltd.
+ */
+
+#include "bcm2712-iommu.h"
+#include "iommu-pages.h"
+
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/iommu.h>
+#include <linux/minmax.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define MMU_WR(off, val)   writel(val, mmu->reg_base + (off))
+#define MMU_RD(off)        readl(mmu->reg_base + (off))
+
+#define domain_to_mmu(d) (container_of(d, struct bcm2712_iommu_domain, base)->mmu)
+
+#define MMMU_CTRL_OFFSET                       0x00
+#define MMMU_CTRL_CAP_EXCEEDED                 BIT(27)
+#define MMMU_CTRL_CAP_EXCEEDED_ABORT_EN        BIT(26)
+#define MMMU_CTRL_CAP_EXCEEDED_INT_EN          BIT(25)
+#define MMMU_CTRL_CAP_EXCEEDED_EXCEPTION_EN    BIT(24)
+#define MMMU_CTRL_PT_INVALID                   BIT(20)
+#define MMMU_CTRL_PT_INVALID_ABORT_EN          BIT(19)
+#define MMMU_CTRL_PT_INVALID_INT_EN            BIT(18)
+#define MMMU_CTRL_PT_INVALID_EXCEPTION_EN      BIT(17)
+#define MMMU_CTRL_PT_INVALID_EN                BIT(16)
+#define MMMU_CTRL_WRITE_VIOLATION              BIT(12)
+#define MMMU_CTRL_WRITE_VIOLATION_ABORT_EN     BIT(11)
+#define MMMU_CTRL_WRITE_VIOLATION_INT_EN       BIT(10)
+#define MMMU_CTRL_WRITE_VIOLATION_EXCEPTION_EN BIT(9)
+#define MMMU_CTRL_BYPASS                       BIT(8)
+#define MMMU_CTRL_TLB_CLEARING                 BIT(7)
+#define MMMU_CTRL_STATS_CLEAR                  BIT(3)
+#define MMMU_CTRL_TLB_CLEAR                    BIT(2)
+#define MMMU_CTRL_STATS_ENABLE                 BIT(1)
+#define MMMU_CTRL_ENABLE                       BIT(0)
+
+#define MMMU_PT_PA_BASE_OFFSET                 0x04
+
+#define MMMU_HIT_OFFSET                        0x08
+#define MMMU_MISS_OFFSET                       0x0C
+#define MMMU_STALL_OFFSET                      0x10
+
+#define MMMU_ADDR_CAP_OFFSET                   0x14
+#define MMMU_ADDR_CAP_ENABLE                   BIT(31)
+#define ADDR_CAP_SHIFT 28 /* ADDR_CAP is defined to be in 256 MByte units */
+
+#define MMMU_SHOOT_DOWN_OFFSET                 0x18
+#define MMMU_SHOOT_DOWN_SHOOTING               BIT(31)
+#define MMMU_SHOOT_DOWN_SHOOT                  BIT(30)
+
+#define MMMU_BYPASS_START_OFFSET               0x1C
+#define MMMU_BYPASS_START_ENABLE               BIT(31)
+#define MMMU_BYPASS_START_INVERT               BIT(30)
+
+#define MMMU_BYPASS_END_OFFSET                 0x20
+#define MMMU_BYPASS_END_ENABLE                 BIT(31)
+
+#define MMMU_MISC_OFFSET                       0x24
+#define MMMU_MISC_SINGLE_TABLE                 BIT(31)
+
+#define MMMU_ILLEGAL_ADR_OFFSET                0x30
+#define MMMU_ILLEGAL_ADR_ENABLE                BIT(31)
+
+#define MMMU_VIO_ADDR_OFFSET                   0x34
+
+#define MMMU_DEBUG_INFO_OFFSET                 0x38
+#define MMMU_DEBUG_INFO_VERSION_MASK           0x0000000Fu
+#define MMMU_DEBUG_INFO_VA_WIDTH_MASK          0x000000F0u
+#define MMMU_DEBUG_INFO_PA_WIDTH_MASK          0x00000F00u
+#define MMMU_DEBUG_INFO_BIGPAGE_WIDTH_MASK     0x000FF000u
+#define MMMU_DEBUG_INFO_SUPERPAGE_WIDTH_MASK   0x0FF00000u
+#define MMMU_DEBUG_INFO_BYPASS_4M              BIT(28)
+#define MMMU_DEBUG_INFO_BYPASS                 BIT(29)
+
+#define MMMU_PTE_PAGESIZE_MASK                 0xC0000000u
+#define MMMU_PTE_WRITEABLE                     BIT(29)
+#define MMMU_PTE_VALID                         BIT(28)
+
+/*
+ * BCM2712 IOMMU is organized around 4Kbyte pages (IOMMU_PAGE_SIZE).
+ * Linux PAGE_SIZE must not be smaller but may be larger (e.g. 4K, 16K).
+ *
+ * Unlike many larger MMUs, this one uses a 4-byte word size, allowing
+ * 1024 entries within each 4K table page, and two-level translation.
+ * To avoid needing CMA, we require the top-level table to fit within
+ * a single Linux page. This restricts IOVA space to (PAGE_SIZE << 20).
+ * A "default" page is allocated to catch illegal reads and writes.
+ *
+ * To deal with multiple blocks connected to an IOMMU when only some
+ * of them are IOMMU clients, by default we start the IOVA region at
+ * 40GB; requests to lower addresses pass straight through to SDRAM.
+ * Both the base and size of the IOVA region are configurable.
+ *
+ * Currently we assume a 1:1:1 correspondence of IOMMU, group and domain.
+ */
+
+#define IOMMU_PAGE_SHIFT    12
+#define IOMMU_PAGE_SIZE     BIT(IOMMU_PAGE_SHIFT)
+
+#define LX_PAGEWORDS_SHIFT    (PAGE_SHIFT - 2)
+#define LX_PAGEWORDS_MASK     ((1u << LX_PAGEWORDS_SHIFT) - 1u)
+#define IOMMU_PAGEWORDS_SHIFT (IOMMU_PAGE_SHIFT - 2)
+#define IOMMU_HUGEPAGE_SHIFT  (IOMMU_PAGE_SHIFT + IOMMU_PAGEWORDS_SHIFT)
+#define L1_AP_BASE_SHIFT      (IOMMU_PAGE_SHIFT + 2 * IOMMU_PAGEWORDS_SHIFT)
+#define IOVA_TO_LXPAGE_SHIFT  (IOMMU_PAGE_SHIFT + LX_PAGEWORDS_SHIFT)
+
+#define DEFAULT_APERTURE_BASE (40ul << 30)
+#define DEFAULT_APERTURE_SIZE (2ul << 30)
+
+/*
+ * Allocate a Linux page for tables; return the index of its first IOMMU page.
+ * To avoid having to store DMA addresses, we assume that a DMA address used
+ * for IOMMU tables is always a physical address -- this is true for BCM2712.
+ * Note that iommu_alloc_page() will zero the page contents.
+ */
+static u32 bcm2712_iommu_get_page(struct bcm2712_iommu *mmu, u32 **ptr)
+{
+	*ptr = iommu_alloc_page(GFP_KERNEL);
+	if (*ptr) {
+		dma_addr_t dma = dma_map_single(mmu->dev, *ptr,
+						PAGE_SIZE, DMA_TO_DEVICE);
+		WARN_ON(dma_mapping_error(mmu->dev, dma) ||
+			dma != virt_to_phys(*ptr));
+		return (u32)(dma >> IOMMU_PAGE_SHIFT);
+	}
+	dev_err(mmu->dev, "Failed to allocate a page\n");
+	return 0;
+}
+
+static void bcm2712_iommu_free_page(struct bcm2712_iommu *mmu, void *ptr)
+{
+	if (ptr) {
+		dma_unmap_single(mmu->dev, virt_to_phys(ptr),
+				 PAGE_SIZE, DMA_TO_DEVICE);
+		iommu_free_page(ptr);
+	}
+}
+
+static int bcm2712_iommu_init(struct bcm2712_iommu *mmu)
+{
+	u32 u = MMU_RD(MMMU_DEBUG_INFO_OFFSET);
+
+	/*
+	 * Check IOMMU version and hardware configuration.
+	 * This driver is for VC IOMMU version >= 4 (with 2-level tables)
+	 * and assumes at least 36 bits of virtual and physical address space.
+	 * Bigpage and superpage sizes are typically 64K and 1M, but may vary
+	 * (hugepage size is fixed at 4M, the range covered by an L2 page).
+	 */
+	dev_info(mmu->dev, "%s: DEBUG_INFO = 0x%08x\n", __func__, u);
+	WARN_ON(FIELD_GET(MMMU_DEBUG_INFO_VERSION_MASK, u) < 4 ||
+		FIELD_GET(MMMU_DEBUG_INFO_VA_WIDTH_MASK, u) < 6 ||
+		FIELD_GET(MMMU_DEBUG_INFO_PA_WIDTH_MASK, u) < 6 ||
+		!(u & MMMU_DEBUG_INFO_BYPASS));
+
+	dma_set_mask_and_coherent(mmu->dev,
+				  DMA_BIT_MASK(FIELD_GET(MMMU_DEBUG_INFO_PA_WIDTH_MASK, u) + 30u));
+	mmu->bigpage_mask =
+		((1u << FIELD_GET(MMMU_DEBUG_INFO_BIGPAGE_WIDTH_MASK, u)) - 1u) <<
+		IOMMU_PAGE_SHIFT;
+	mmu->superpage_mask =
+		((1u << FIELD_GET(MMMU_DEBUG_INFO_SUPERPAGE_WIDTH_MASK, u)) - 1u) <<
+		IOMMU_PAGE_SHIFT;
+
+	/* Disable MMU and clear sticky flags; meanwhile flush the TLB */
+	MMU_WR(MMMU_CTRL_OFFSET,
+	       MMMU_CTRL_CAP_EXCEEDED    |
+	       MMMU_CTRL_PT_INVALID      |
+	       MMMU_CTRL_WRITE_VIOLATION |
+	       MMMU_CTRL_STATS_CLEAR     |
+	       MMMU_CTRL_TLB_CLEAR);
+
+	/*
+	 * Put MMU into 2-level mode; set address cap and "bypass" range
+	 * (note that some of these registers have unintuitive off-by-ones).
+	 * Addresses below the "aperture" are passed unchanged: this is
+	 * useful for blocks which share an IOMMU with other blocks
+	 * whose drivers are not IOMMU-aware.
+	 */
+	MMU_WR(MMMU_MISC_OFFSET,
+	       MMU_RD(MMMU_MISC_OFFSET) & ~MMMU_MISC_SINGLE_TABLE);
+	MMU_WR(MMMU_ADDR_CAP_OFFSET,
+	       MMMU_ADDR_CAP_ENABLE +
+	       ((mmu->aperture_end - mmu->dma_iova_offset) >> ADDR_CAP_SHIFT) - 1);
+	MMU_WR(MMMU_BYPASS_START_OFFSET, 0);
+	if (mmu->aperture_base > mmu->dma_iova_offset) {
+		unsigned int byp_shift = (u & MMMU_DEBUG_INFO_BYPASS_4M) ?
+			IOMMU_HUGEPAGE_SHIFT : ADDR_CAP_SHIFT;
+
+		MMU_WR(MMMU_BYPASS_END_OFFSET,
+		       MMMU_BYPASS_END_ENABLE +
+		       ((mmu->aperture_base - mmu->dma_iova_offset) >> byp_shift));
+	} else {
+		MMU_WR(MMMU_BYPASS_END_OFFSET, 0);
+	}
+
+	/*
+	 * Configure the addresses of the top-level table (offset because
+	 * the aperture does not start from zero), and of the default page.
+	 * For simplicity, both these regions are whole Linux pages.
+	 */
+	u = bcm2712_iommu_get_page(mmu, &mmu->top_table);
+	if (!u)
+		return -ENOMEM;
+	MMU_WR(MMMU_PT_PA_BASE_OFFSET,
+	       u - ((mmu->aperture_base - mmu->dma_iova_offset) >> L1_AP_BASE_SHIFT));
+	u = bcm2712_iommu_get_page(mmu, &mmu->default_page);
+	if (!u) {
+		bcm2712_iommu_free_page(mmu, mmu->top_table);
+		return -ENOMEM;
+	}
+	MMU_WR(MMMU_ILLEGAL_ADR_OFFSET, MMMU_ILLEGAL_ADR_ENABLE + u);
+	mmu->nmapped_pages = 0;
+
+	/* Flush (and enable) the shared TLB cache; enable this MMU. */
+	if (mmu->cache)
+		bcm2712_iommu_cache_flush(mmu->cache);
+	MMU_WR(MMMU_CTRL_OFFSET,
+	       MMMU_CTRL_CAP_EXCEEDED_ABORT_EN    |
+	       MMMU_CTRL_PT_INVALID_ABORT_EN      |
+	       MMMU_CTRL_PT_INVALID_EN            |
+	       MMMU_CTRL_WRITE_VIOLATION_ABORT_EN |
+	       MMMU_CTRL_STATS_ENABLE             |
+	       MMMU_CTRL_ENABLE);
+
+	return 0;
+}
+
+static int bcm2712_iommu_attach_dev(struct iommu_domain *domain, struct device *dev)
+{
+	struct bcm2712_iommu *mmu = dev ? dev_iommu_priv_get(dev) : 0;
+	struct bcm2712_iommu_domain *mydomain =
+		container_of(domain, struct bcm2712_iommu_domain, base);
+
+	dev_info(dev, "%s: MMU %s\n",
+		 __func__, mmu ? dev_name(mmu->dev) : "");
+
+	if (mmu) {
+		mydomain->mmu = mmu;
+		mmu->domain = mydomain;
+		domain->geometry.aperture_start = mmu->aperture_base;
+		domain->geometry.aperture_end = mmu->aperture_end - 1ul;
+		domain->geometry.force_aperture = true;
+
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int bcm2712_iommu_map(struct iommu_domain *domain, unsigned long iova,
+			     phys_addr_t pa, size_t bytes, size_t count,
+			     int prot, gfp_t gfp, size_t *mapped)
+{
+	struct bcm2712_iommu *mmu = domain_to_mmu(domain);
+	u32 entry = MMMU_PTE_VALID | (pa >> IOMMU_PAGE_SHIFT);
+	u32 align = (u32)(iova | pa | bytes);
+	unsigned long flags;
+	unsigned int p, p_last, lxp;
+	bool dirty_top = false;
+
+	/* Reject if not entirely within our aperture (should never happen) */
+	bytes *= count;
+	if (iova < mmu->aperture_base || iova + bytes > mmu->aperture_end) {
+		*mapped = 0;
+		return -EINVAL;
+	}
+
+	/* DMA addresses -> page numbers */
+	iova -= mmu->aperture_base;
+	p = iova >> IOMMU_PAGE_SHIFT;
+	p_last = (iova + bytes - 1) >> IOMMU_PAGE_SHIFT;
+
+	/*
+	 * Check we have allocated the required Level-2 tables (in units of
+	 * whole Linux pages, each covering one or more IOMMU-table pages).
+	 * Any new L2 pages need to be linked to the top-level table.
+	 * A failure here will cause the entire map_pages() call to fail.
+	 */
+	spin_lock_irqsave(&mmu->hw_lock, flags);
+	for (lxp = (p >> LX_PAGEWORDS_SHIFT);
+	     lxp <= (p_last >> LX_PAGEWORDS_SHIFT); lxp++) {
+		if (!mmu->tables[lxp]) {
+			unsigned int i, u;
+
+			u = bcm2712_iommu_get_page(mmu, &mmu->tables[lxp]);
+			if (!u)
+				break;
+			u |= MMMU_PTE_VALID;
+			if (!dirty_top) {
+				dma_sync_single_for_cpu(mmu->dev, virt_to_phys(mmu->top_table),
+							PAGE_SIZE, DMA_TO_DEVICE);
+				dirty_top = true;
+			}
+			for (i = lxp << (PAGE_SHIFT - IOMMU_PAGE_SHIFT);
+			     i < (lxp + 1) << (PAGE_SHIFT - IOMMU_PAGE_SHIFT); i++) {
+				mmu->top_table[i] = u++;
+			}
+		}
+	}
+	if (dirty_top)
+		dma_sync_single_for_device(mmu->dev, virt_to_phys(mmu->top_table),
+					   PAGE_SIZE, DMA_TO_DEVICE);
+	if (lxp <= (p_last >> LX_PAGEWORDS_SHIFT)) {
+		spin_unlock_irqrestore(&mmu->hw_lock, flags);
+		*mapped = 0;
+		return -ENOMEM;
+	}
+
+	/* large page and write enable flags */
+	if (!(align & ((1 << IOMMU_HUGEPAGE_SHIFT) - 1)))
+		entry |= FIELD_PREP(MMMU_PTE_PAGESIZE_MASK, 3);
+	else if (!(align & mmu->superpage_mask) && mmu->superpage_mask)
+		entry |= FIELD_PREP(MMMU_PTE_PAGESIZE_MASK, 2);
+	else if (!(align &  mmu->bigpage_mask) && mmu->bigpage_mask)
+		entry |= FIELD_PREP(MMMU_PTE_PAGESIZE_MASK, 1);
+	if (prot & IOMMU_WRITE)
+		entry |= MMMU_PTE_WRITEABLE;
+
+	/*
+	 * Again iterate over table-pages and bring them into CPU ownwership.
+	 * Fill in the required PT entries, then give them back to the device.
+	 */
+	while (p <= p_last) {
+		u32 *tbl = mmu->tables[p >> LX_PAGEWORDS_SHIFT];
+
+		dma_sync_single_for_cpu(mmu->dev, virt_to_phys(tbl),
+					PAGE_SIZE, DMA_TO_DEVICE);
+		while (p <= p_last) {
+			mmu->nmapped_pages += !tbl[p & LX_PAGEWORDS_MASK];
+			tbl[p & LX_PAGEWORDS_MASK] = entry++;
+			if (IS_ALIGNED(++p, (1u << LX_PAGEWORDS_SHIFT)))
+				break;
+		}
+		dma_sync_single_for_device(mmu->dev, virt_to_phys(tbl),
+					   PAGE_SIZE, DMA_TO_DEVICE);
+	}
+
+	spin_unlock_irqrestore(&mmu->hw_lock, flags);
+	*mapped = bytes;
+	return 0;
+}
+
+static size_t bcm2712_iommu_unmap(struct iommu_domain *domain, unsigned long iova,
+				  size_t bytes, size_t count,
+				  struct iommu_iotlb_gather *gather)
+{
+	struct bcm2712_iommu *mmu = domain_to_mmu(domain);
+	unsigned long flags;
+	unsigned int p, p_last;
+
+	/* Reject if not entirely within our aperture (should never happen) */
+	bytes *= count;
+	if (iova < mmu->aperture_base || iova + bytes > mmu->aperture_end)
+		return 0;
+
+	/* Record just the lower and upper bounds in "gather" */
+	spin_lock_irqsave(&mmu->hw_lock, flags);
+	iommu_iotlb_gather_add_range(gather, iova, bytes);
+
+	/* DMA addresses -> page numbers */
+	iova -= mmu->aperture_base;
+	p = iova >> IOMMU_PAGE_SHIFT;
+	p_last = (iova + bytes - 1) >> IOMMU_PAGE_SHIFT;
+
+	/*
+	 * Iterate over tables in Linux-pages and bring them into CPU ownwership.
+	 * Clear the required PT entries, then give them back to the device.
+	 */
+	while (p <= p_last) {
+		u32 *tbl = mmu->tables[p >> LX_PAGEWORDS_SHIFT];
+
+		if (tbl) {
+			dma_sync_single_for_cpu(mmu->dev, virt_to_phys(tbl),
+						PAGE_SIZE, DMA_TO_DEVICE);
+			while (p <= p_last) {
+				mmu->nmapped_pages -= !!tbl[p & LX_PAGEWORDS_MASK];
+				tbl[p & LX_PAGEWORDS_MASK] = 0;
+				if (IS_ALIGNED(++p, (1u << LX_PAGEWORDS_SHIFT)))
+					break;
+			}
+			dma_sync_single_for_device(mmu->dev, virt_to_phys(tbl),
+						   PAGE_SIZE, DMA_TO_DEVICE);
+		}
+	}
+
+	spin_unlock_irqrestore(&mmu->hw_lock, flags);
+	return bytes;
+}
+
+static int bcm2712_iommu_sync_range(struct iommu_domain *domain,
+				    unsigned long iova, size_t size)
+{
+	struct bcm2712_iommu *mmu = domain_to_mmu(domain);
+	unsigned long flags, iova_end;
+	unsigned int i, p4;
+
+	if (!mmu)
+		return 0;
+
+	iova_end = min(mmu->aperture_end, iova + size);
+	iova = max(mmu->aperture_base, iova);
+	if (iova_end <= iova)
+		return 0;
+
+	/* Flush the shared TLB cache */
+	spin_lock_irqsave(&mmu->hw_lock, flags);
+	if (mmu->cache)
+		bcm2712_iommu_cache_flush(mmu->cache);
+
+	/*
+	 * When flushing a large range or when nothing needs to be kept,
+	 * it's quicker to use the "TLB_CLEAR" flag. Otherwise, invalidate
+	 * TLB entries in lines of 4 words each. Each flush/clear operation
+	 * should complete almost instantaneously.
+	 */
+	if (mmu->nmapped_pages == 0 || iova_end >= iova + (1ul << 24)) {
+		MMU_WR(MMMU_CTRL_OFFSET,
+		       MMMU_CTRL_CAP_EXCEEDED_ABORT_EN    |
+		       MMMU_CTRL_PT_INVALID_ABORT_EN      |
+		       MMMU_CTRL_PT_INVALID_EN            |
+		       MMMU_CTRL_WRITE_VIOLATION_ABORT_EN |
+		       MMMU_CTRL_TLB_CLEAR                |
+		       MMMU_CTRL_STATS_ENABLE             |
+		       MMMU_CTRL_ENABLE);
+		for (i = 0; i < 1024; i++) {
+			if (!(MMMU_CTRL_TLB_CLEARING & MMU_RD(MMMU_CTRL_OFFSET)))
+				break;
+			cpu_relax();
+		}
+	} else {
+		iova -= mmu->dma_iova_offset; /* DMA address -> IOMMU virtual address */
+		iova_end -= (mmu->dma_iova_offset + 1); /* last byte in range */
+		for (p4 = iova >> (IOMMU_PAGE_SHIFT + 2);
+		     p4 <= iova_end >> (IOMMU_PAGE_SHIFT + 2); p4++) {
+			MMU_WR(MMMU_SHOOT_DOWN_OFFSET,
+			       MMMU_SHOOT_DOWN_SHOOT + (p4 << 2));
+			for (i = 0; i < 1024; i++) {
+				if (!(MMMU_SHOOT_DOWN_SHOOTING & MMU_RD(MMMU_SHOOT_DOWN_OFFSET)))
+					break;
+				cpu_relax();
+			}
+		}
+	}
+
+	spin_unlock_irqrestore(&mmu->hw_lock, flags);
+	return 0;
+}
+
+static void bcm2712_iommu_sync(struct iommu_domain *domain,
+			       struct iommu_iotlb_gather *gather)
+{
+	if (gather->end)
+		bcm2712_iommu_sync_range(domain, gather->start,
+					 gather->end - gather->start + 1);
+}
+
+static void bcm2712_iommu_sync_all(struct iommu_domain *domain)
+{
+	struct bcm2712_iommu *mmu = domain_to_mmu(domain);
+
+	if (mmu)
+		bcm2712_iommu_sync_range(domain,
+					 mmu->aperture_base,
+					 mmu->aperture_end - mmu->aperture_base);
+}
+
+static phys_addr_t bcm2712_iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)
+{
+	phys_addr_t addr = 0;
+	struct bcm2712_iommu *mmu = domain_to_mmu(domain);
+
+	if (iova  >= mmu->aperture_base && iova < mmu->aperture_end) {
+		unsigned long flags;
+		u32 *ptr;
+		u32 p;
+
+		spin_lock_irqsave(&mmu->hw_lock, flags);
+		p = (iova - mmu->aperture_base) >> IOMMU_PAGE_SHIFT;
+		ptr = mmu->tables[p >> LX_PAGEWORDS_SHIFT];
+		if (ptr) {
+			p = ptr[p & LX_PAGEWORDS_MASK] & 0x0FFFFFFFu;
+			addr = (((phys_addr_t)p) << IOMMU_PAGE_SHIFT) +
+				(iova & (IOMMU_PAGE_SIZE - 1u));
+		}
+		spin_unlock_irqrestore(&mmu->hw_lock, flags);
+	}
+
+	return addr;
+}
+
+static void bcm2712_iommu_domain_free(struct iommu_domain *domain)
+{
+	struct bcm2712_iommu_domain *mydomain =
+		container_of(domain, struct bcm2712_iommu_domain, base);
+
+	kfree(mydomain);
+}
+
+static const struct iommu_domain_ops bcm2712_iommu_domain_ops = {
+	.attach_dev	 = bcm2712_iommu_attach_dev,
+	.map_pages	 = bcm2712_iommu_map,
+	.unmap_pages	 = bcm2712_iommu_unmap,
+	.iotlb_sync      = bcm2712_iommu_sync,
+	.iotlb_sync_map  = bcm2712_iommu_sync_range,
+	.flush_iotlb_all = bcm2712_iommu_sync_all,
+	.iova_to_phys	 = bcm2712_iommu_iova_to_phys,
+	.free		 = bcm2712_iommu_domain_free,
+};
+
+static struct iommu_domain *bcm2712_iommu_domain_alloc(unsigned int type)
+{
+	struct bcm2712_iommu_domain *domain;
+
+	if (type != IOMMU_DOMAIN_UNMANAGED && type != IOMMU_DOMAIN_DMA)
+		return NULL;
+
+	domain = kzalloc(sizeof(*domain), GFP_KERNEL);
+	if (!domain)
+		return NULL;
+
+	domain->base.type = type;
+	domain->base.ops  = &bcm2712_iommu_domain_ops;
+	domain->base.geometry.force_aperture = true;
+	return &domain->base;
+}
+
+static struct iommu_device *bcm2712_iommu_probe_device(struct device *dev)
+{
+	struct bcm2712_iommu *mmu;
+
+	/*
+	 * For reasons I don't fully understand, we need to try both
+	 * cases (dev_iommu_priv_get() and platform_get_drvdata())
+	 * in order to get both GPU and ISP-BE to probe successfully.
+	 */
+	mmu = dev_iommu_priv_get(dev);
+	if (!mmu) {
+		struct device_node *np;
+		struct platform_device *pdev;
+
+		/* Ignore devices that don't have an "iommus" property with exactly one phandle */
+		if (!dev->of_node ||
+		    of_property_count_elems_of_size(dev->of_node, "iommus", sizeof(phandle)) != 1)
+			return ERR_PTR(-ENODEV);
+
+		np = of_parse_phandle(dev->of_node, "iommus", 0);
+		if (!np)
+			return ERR_PTR(-EINVAL);
+
+		pdev = of_find_device_by_node(np);
+		of_node_put(np);
+		if (pdev)
+			mmu = platform_get_drvdata(pdev);
+
+		if (!mmu)
+			return ERR_PTR(-ENODEV);
+	}
+
+	dev_info(dev, "%s: MMU %s\n", __func__, dev_name(mmu->dev));
+	dev_iommu_priv_set(dev, mmu);
+	return &mmu->iommu;
+}
+
+static void bcm2712_iommu_release_device(struct device *dev)
+{
+	dev_iommu_priv_set(dev, NULL);
+}
+
+static struct iommu_group *bcm2712_iommu_device_group(struct device *dev)
+{
+	struct bcm2712_iommu *mmu = dev_iommu_priv_get(dev);
+
+	if (!mmu || !mmu->group)
+		return ERR_PTR(-EINVAL);
+
+	dev_info(dev, "%s: MMU %s\n", __func__, dev_name(mmu->dev));
+	return iommu_group_ref_get(mmu->group);
+}
+
+static int bcm2712_iommu_of_xlate(struct device *dev,
+				  const struct of_phandle_args *args)
+{
+	struct platform_device *iommu_dev;
+	struct bcm2712_iommu *mmu;
+
+	iommu_dev = of_find_device_by_node(args->np);
+	mmu = platform_get_drvdata(iommu_dev);
+	dev_iommu_priv_set(dev, mmu);
+	dev_info(dev, "%s: MMU %s\n", __func__, dev_name(mmu->dev));
+
+	return 0;
+}
+
+static bool bcm2712_iommu_capable(struct device *dev, enum iommu_cap cap)
+{
+	return false;
+}
+
+static const struct iommu_ops bcm2712_iommu_ops = {
+	.capable        = bcm2712_iommu_capable,
+	.domain_alloc	= bcm2712_iommu_domain_alloc,
+	.probe_device	= bcm2712_iommu_probe_device,
+	.release_device	= bcm2712_iommu_release_device,
+	.device_group	= bcm2712_iommu_device_group,
+	/* Advertise native page sizes as well as 2M, 16K which Linux may prefer */
+	.pgsize_bitmap	= (SZ_4M | SZ_2M | SZ_1M | SZ_64K | SZ_16K | SZ_4K),
+	.default_domain_ops = &bcm2712_iommu_domain_ops,
+	.of_xlate = bcm2712_iommu_of_xlate,
+};
+
+static int bcm2712_iommu_probe(struct platform_device *pdev)
+{
+	struct bcm2712_iommu *mmu;
+	struct bcm2712_iommu_cache *cache = NULL;
+	unsigned int num_pages;
+	int ret;
+
+	/* First of all, check for an IOMMU shared cache */
+	if (pdev->dev.of_node) {
+		struct device_node *cache_np;
+		struct platform_device *cache_pdev;
+
+		cache_np = of_parse_phandle(pdev->dev.of_node, "cache", 0);
+		if (cache_np) {
+			cache_pdev = of_find_device_by_node(cache_np);
+			of_node_put(cache_np);
+			if (cache_pdev && !IS_ERR(cache_pdev))
+				cache = platform_get_drvdata(cache_pdev);
+			if (!cache)
+				return -EPROBE_DEFER;
+		}
+	}
+
+	/* Allocate private data */
+	mmu = devm_kzalloc(&pdev->dev, sizeof(*mmu), GFP_KERNEL);
+	if (!mmu)
+		return -ENOMEM;
+
+	mmu->name = dev_name(&pdev->dev);
+	mmu->dev = &pdev->dev;
+	mmu->cache = cache;
+	platform_set_drvdata(pdev, mmu);
+	spin_lock_init(&mmu->hw_lock);
+
+	/*
+	 * Configurable properties. The IOVA base address may be any
+	 * multiple of 4GB; when it is zero, pass-through will be disabled.
+	 * Its size may be any multiple of 256MB up to (PAGE_SIZE << 20).
+	 *
+	 * When the IOMMU is downstream of a PCIe RC or some other chip/bus
+	 * and serves some of the masters thereon (others using pass-through),
+	 * we seem to fumble and lose the "dma-ranges" address offset for DMA
+	 * masters on the far bus. The "dma-iova-offset" property optionally
+	 * restores it. Where present, we add the offset to all DMA addresses
+	 * (but we don't expect to see any offset on the IOMMU's AXI bus).
+	 */
+	mmu->aperture_base = DEFAULT_APERTURE_BASE;
+	mmu->aperture_end  = DEFAULT_APERTURE_SIZE;
+	mmu->dma_iova_offset = 0;
+	if (pdev->dev.of_node) {
+		if (!of_property_read_u64(pdev->dev.of_node,
+					  "aperture-base", &mmu->aperture_base))
+			mmu->aperture_base &= ~((1ul << L1_AP_BASE_SHIFT) - 1);
+		if (!of_property_read_u64(pdev->dev.of_node,
+					  "aperture-size", &mmu->aperture_end))
+			mmu->aperture_end = clamp(mmu->aperture_end &
+						  ~((1ul << ADDR_CAP_SHIFT) - 1),
+						  (1ul << ADDR_CAP_SHIFT),
+						  (PAGE_SIZE << 20));
+		if (!of_property_read_u64(pdev->dev.of_node,
+					  "dma-iova-offset", &mmu->dma_iova_offset))
+			mmu->aperture_base += mmu->dma_iova_offset;
+	}
+	num_pages = mmu->aperture_end >> IOVA_TO_LXPAGE_SHIFT;
+	mmu->aperture_end += mmu->aperture_base;
+	dev_info(&pdev->dev, "IOVA aperture 0x%llx..0x%llx including DMA offset 0x%llx\n",
+		 (unsigned long long)mmu->aperture_base,
+		 (unsigned long long)mmu->aperture_end,
+		 (unsigned long long)mmu->dma_iova_offset);
+
+	/* Allocate pointers for each page */
+	mmu->tables = devm_kzalloc(&pdev->dev,
+				   num_pages * sizeof(u32 *), GFP_KERNEL);
+	if (!mmu->tables)
+		return -ENOMEM;
+
+	/* Get IOMMU registers */
+	mmu->reg_base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(mmu->reg_base)) {
+		dev_err(&pdev->dev, "Failed to get IOMMU registers address\n");
+		ret = PTR_ERR(mmu->reg_base);
+		goto done_err;
+	}
+
+	/* Stuff */
+	mmu->group = iommu_group_alloc();
+	if (IS_ERR(mmu->group)) {
+		ret = PTR_ERR(mmu->group);
+		mmu->group = NULL;
+		goto done_err;
+	}
+	ret = iommu_device_sysfs_add(&mmu->iommu, mmu->dev, NULL, mmu->name);
+	if (ret)
+		goto done_err;
+
+	/* Initialize hardware -- this will try to allocate 2 pages */
+	ret = bcm2712_iommu_init(mmu);
+	if (ret)
+		goto done_err;
+
+	ret = iommu_device_register(&mmu->iommu, &bcm2712_iommu_ops, &pdev->dev);
+	if (!ret)
+		return 0;
+
+done_err:
+	dev_info(&pdev->dev, "%s: Failure %d\n", __func__, ret);
+	if (mmu->group)
+		iommu_group_put(mmu->group);
+
+	return ret;
+}
+
+static void bcm2712_iommu_remove(struct platform_device *pdev)
+{
+	struct bcm2712_iommu *mmu = platform_get_drvdata(pdev);
+	unsigned int i;
+
+	for (i = 0; i < (mmu->aperture_end - mmu->aperture_base) >> IOVA_TO_LXPAGE_SHIFT; i++)
+		bcm2712_iommu_free_page(mmu, mmu->tables[i]);
+	bcm2712_iommu_free_page(mmu, mmu->top_table);
+	bcm2712_iommu_free_page(mmu, mmu->default_page);
+
+	if (mmu->reg_base)
+		MMU_WR(MMMU_CTRL_OFFSET, 0); /* disable the MMU */
+}
+
+static const struct of_device_id bcm2712_iommu_of_match[] = {
+	{
+		. compatible = "brcm,bcm2712-iommu"
+	},
+	{ /* sentinel */ },
+};
+
+static struct platform_driver bcm2712_iommu_driver = {
+	.probe = bcm2712_iommu_probe,
+	.remove = bcm2712_iommu_remove,
+	.driver = {
+		.name = "bcm2712-iommu",
+		.of_match_table = bcm2712_iommu_of_match
+	},
+};
+
+builtin_platform_driver(bcm2712_iommu_driver);
diff --git a/drivers/iommu/bcm2712-iommu.h b/drivers/iommu/bcm2712-iommu.h
new file mode 100644
index 000000000000..0597b4f0da87
--- /dev/null
+++ b/drivers/iommu/bcm2712-iommu.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * IOMMU driver for BCM2712
+ *
+ * Copyright (c) 2023-2025 Raspberry Pi Ltd.
+ */
+
+#ifndef _BCM2712_IOMMU_H
+#define _BCM2712_IOMMU_H
+
+#include <linux/iommu.h>
+#include <linux/scatterlist.h>
+
+struct bcm2712_iommu_cache {
+	struct device *dev;
+	spinlock_t hw_lock; /* to protect HW registers */
+	void __iomem *reg_base;
+};
+
+void bcm2712_iommu_cache_flush(struct bcm2712_iommu_cache *cache);
+
+struct bcm2712_iommu {
+	struct device *dev;
+	struct iommu_device iommu;
+	struct iommu_group *group;
+	struct bcm2712_iommu_domain *domain;
+	char const *name;
+	u64 dma_iova_offset;  /* Hack for IOMMU attached to PCIe RC, included below */
+	u64 aperture_base;    /* Base of IOVA region as passed to DMA peripherals */
+	u64 aperture_end;     /* End of IOVA region as passed to DMA peripherals */
+	u32 **tables;         /* For each Linux page of L2 tables, kernel virtual address */
+	u32 *top_table;       /* Top-level table holding IOMMU page-numbers of L2 tables */
+	u32 *default_page;    /* Page used to trap illegal reads and writes */
+	struct bcm2712_iommu_cache *cache;
+	spinlock_t hw_lock;   /* to protect HW registers */
+	void __iomem *reg_base;
+	u32 bigpage_mask;
+	u32 superpage_mask;
+	unsigned int nmapped_pages;
+};
+
+struct bcm2712_iommu_domain {
+	struct iommu_domain base;
+	struct bcm2712_iommu *mmu;
+};
+
+#endif
diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c
index 0f0caf59023c..2ee1d0e6b389 100644
--- a/drivers/iommu/dma-iommu.c
+++ b/drivers/iommu/dma-iommu.c
@@ -21,6 +21,7 @@
 #include <linux/iova.h>
 #include <linux/irq.h>
 #include <linux/list_sort.h>
+#include <linux/mempolicy.h>
 #include <linux/memremap.h>
 #include <linux/mm.h>
 #include <linux/mutex.h>
@@ -883,11 +884,65 @@ static void __iommu_dma_free_pages(struct page **pages, int count)
 	kvfree(pages);
 }
 
+#if IS_ENABLED(CONFIG_NUMA)
+static struct mempolicy iommu_dma_mpol = {
+	.refcnt = ATOMIC_INIT(1), /* never free it */
+	.mode = MPOL_LOCAL,
+};
+
+static struct mempolicy *dma_iommu_numa_policy(void)
+{
+	return &iommu_dma_mpol;
+}
+
+static unsigned short dma_iommu_numa_mode(void)
+{
+	return iommu_dma_mpol.mode;
+}
+
+static int __init setup_numapolicy(char *str)
+{
+	struct mempolicy pol = { }, *ppol = &pol;
+	char buf[128];
+	int ret;
+
+	if (str)
+		ret = mpol_parse_str(str, &ppol);
+	else
+		ret = -EINVAL;
+
+	if (!ret) {
+		iommu_dma_mpol = pol;
+		mpol_to_str(buf, sizeof(buf), &pol);
+		pr_info("DMA IOMMU NUMA default policy overridden to '%s'\n", buf);
+	} else {
+		pr_warn("Unable to parse dma_iommu_numa_policy=\n");
+	}
+
+	return ret == 0;
+}
+__setup("iommu_dma_numa_policy=", setup_numapolicy);
+#else
+static struct mempolicy *dma_iommu_numa_policy(void)
+{
+	return NULL;
+}
+
+static unsigned short dma_iommu_numa_mode(void)
+{
+	return MPOL_LOCAL;
+}
+#endif
 static struct page **__iommu_dma_alloc_pages(struct device *dev,
 		unsigned int count, unsigned long order_mask, gfp_t gfp)
 {
 	struct page **pages;
 	unsigned int i = 0, nid = dev_to_node(dev);
+	const bool use_numa = nid == NUMA_NO_NODE &&
+			      dma_iommu_numa_mode() != MPOL_LOCAL;
+
+	if (use_numa)
+		order_mask = 1;
 
 	order_mask &= GENMASK(MAX_PAGE_ORDER, 0);
 	if (!order_mask)
@@ -903,6 +958,7 @@ static struct page **__iommu_dma_alloc_pages(struct device *dev,
 	while (count) {
 		struct page *page = NULL;
 		unsigned int order_size;
+		nodemask_t *nodemask;
 
 		/*
 		 * Higher-order allocations are a convenience rather
@@ -917,6 +973,10 @@ static struct page **__iommu_dma_alloc_pages(struct device *dev,
 			order_size = 1U << order;
 			if (order_mask > order_size)
 				alloc_flags |= __GFP_NORETRY;
+			if (use_numa)
+				nodemask = numa_policy_nodemask(gfp,
+								dma_iommu_numa_policy(),
+								i, &nid);
 			page = alloc_pages_node(nid, alloc_flags, order);
 			if (!page)
 				continue;
diff --git a/drivers/irqchip/Kconfig b/drivers/irqchip/Kconfig
index 5d5b3cf381b9..4288ecfd0a7b 100644
--- a/drivers/irqchip/Kconfig
+++ b/drivers/irqchip/Kconfig
@@ -110,6 +110,22 @@ config I8259
 	bool
 	select IRQ_DOMAIN
 
+config BCM2712_MIP
+	tristate "Broadcom BCM2712 MSI-X Interrupt Peripheral support"
+	depends on ARCH_BRCMSTB || COMPILE_TEST
+	default m if ARCH_BRCMSTB
+	depends on ARM_GIC
+	select GENERIC_IRQ_CHIP
+	select IRQ_DOMAIN_HIERARCHY
+	select GENERIC_MSI_IRQ
+	select IRQ_MSI_LIB
+	help
+	  Enable support for the Broadcom BCM2712 MSI-X target peripheral
+	  (MIP) needed by brcmstb PCIe to handle MSI-X interrupts on
+	  Raspberry Pi 5.
+
+	  If unsure say n.
+
 config BCM6345_L1_IRQ
 	bool
 	select GENERIC_IRQ_CHIP
diff --git a/drivers/irqchip/Makefile b/drivers/irqchip/Makefile
index e3679ec2b9f7..a11307b1b610 100644
--- a/drivers/irqchip/Makefile
+++ b/drivers/irqchip/Makefile
@@ -62,6 +62,7 @@ obj-$(CONFIG_XTENSA_MX)			+= irq-xtensa-mx.o
 obj-$(CONFIG_XILINX_INTC)		+= irq-xilinx-intc.o
 obj-$(CONFIG_IRQ_CROSSBAR)		+= irq-crossbar.o
 obj-$(CONFIG_SOC_VF610)			+= irq-vf610-mscm-ir.o
+obj-$(CONFIG_BCM2712_MIP)               += irq-bcm2712-mip.o
 obj-$(CONFIG_BCM6345_L1_IRQ)		+= irq-bcm6345-l1.o
 obj-$(CONFIG_BCM7038_L1_IRQ)		+= irq-bcm7038-l1.o
 obj-$(CONFIG_BCM7120_L2_IRQ)		+= irq-bcm7120-l2.o
diff --git a/drivers/irqchip/irq-bcm2712-mip.c b/drivers/irqchip/irq-bcm2712-mip.c
new file mode 100644
index 000000000000..49a19db2d1e1
--- /dev/null
+++ b/drivers/irqchip/irq-bcm2712-mip.c
@@ -0,0 +1,292 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024 Raspberry Pi Ltd., All Rights Reserved.
+ * Copyright (c) 2024 SUSE
+ */
+
+#include <linux/bitmap.h>
+#include <linux/irqchip.h>
+#include <linux/irqdomain.h>
+#include <linux/msi.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+
+#include "irq-msi-lib.h"
+
+#define MIP_INT_RAISE		0x00
+#define MIP_INT_CLEAR		0x10
+#define MIP_INT_CFGL_HOST	0x20
+#define MIP_INT_CFGH_HOST	0x30
+#define MIP_INT_MASKL_HOST	0x40
+#define MIP_INT_MASKH_HOST	0x50
+#define MIP_INT_MASKL_VPU	0x60
+#define MIP_INT_MASKH_VPU	0x70
+#define MIP_INT_STATUSL_HOST	0x80
+#define MIP_INT_STATUSH_HOST	0x90
+#define MIP_INT_STATUSL_VPU	0xa0
+#define MIP_INT_STATUSH_VPU	0xb0
+
+/**
+ * struct mip_priv - MSI-X interrupt controller data
+ * @lock:	Used to protect bitmap alloc/free
+ * @base:	Base address of MMIO area
+ * @msg_addr:	PCIe MSI-X address
+ * @msi_base:	MSI base
+ * @num_msis:	Count of MSIs
+ * @msi_offset:	MSI offset
+ * @bitmap:	A bitmap for hwirqs
+ * @parent:	Parent domain (GIC)
+ * @dev:	A device pointer
+ */
+struct mip_priv {
+	spinlock_t		lock;
+	void __iomem		*base;
+	u64			msg_addr;
+	u32			msi_base;
+	u32			num_msis;
+	u32			msi_offset;
+	unsigned long		*bitmap;
+	struct irq_domain	*parent;
+	struct device		*dev;
+};
+
+static void mip_compose_msi_msg(struct irq_data *d, struct msi_msg *msg)
+{
+	struct mip_priv *mip = irq_data_get_irq_chip_data(d);
+
+	msg->address_hi = upper_32_bits(mip->msg_addr);
+	msg->address_lo = lower_32_bits(mip->msg_addr);
+	msg->data = d->hwirq;
+}
+
+static struct irq_chip mip_middle_irq_chip = {
+	.name			= "MIP",
+	.irq_mask		= irq_chip_mask_parent,
+	.irq_unmask		= irq_chip_unmask_parent,
+	.irq_eoi		= irq_chip_eoi_parent,
+	.irq_set_affinity	= irq_chip_set_affinity_parent,
+	.irq_set_type		= irq_chip_set_type_parent,
+	.irq_compose_msi_msg	= mip_compose_msi_msg,
+};
+
+static int mip_alloc_hwirq(struct mip_priv *mip, unsigned int nr_irqs)
+{
+	guard(spinlock)(&mip->lock);
+	return bitmap_find_free_region(mip->bitmap, mip->num_msis, ilog2(nr_irqs));
+}
+
+static void mip_free_hwirq(struct mip_priv *mip, unsigned int hwirq,
+			   unsigned int nr_irqs)
+{
+	guard(spinlock)(&mip->lock);
+	bitmap_release_region(mip->bitmap, hwirq, ilog2(nr_irqs));
+}
+
+static int mip_middle_domain_alloc(struct irq_domain *domain, unsigned int virq,
+				   unsigned int nr_irqs, void *arg)
+{
+	struct mip_priv *mip = domain->host_data;
+	struct irq_fwspec fwspec = {0};
+	unsigned int hwirq, i;
+	struct irq_data *irqd;
+	int irq, ret;
+
+	irq = mip_alloc_hwirq(mip, nr_irqs);
+	if (irq < 0)
+		return irq;
+
+	hwirq = irq + mip->msi_offset;
+
+	fwspec.fwnode = domain->parent->fwnode;
+	fwspec.param_count = 3;
+	fwspec.param[0] = 0;
+	fwspec.param[1] = hwirq + mip->msi_base;
+	fwspec.param[2] = IRQ_TYPE_EDGE_RISING;
+
+	ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, &fwspec);
+	if (ret)
+		goto err_free_hwirq;
+
+	for (i = 0; i < nr_irqs; i++) {
+		irqd = irq_domain_get_irq_data(domain->parent, virq + i);
+		irqd->chip->irq_set_type(irqd, IRQ_TYPE_EDGE_RISING);
+
+		ret = irq_domain_set_hwirq_and_chip(domain, virq + i, hwirq + i,
+						    &mip_middle_irq_chip, mip);
+		if (ret)
+			goto err_free;
+
+		irqd = irq_get_irq_data(virq + i);
+		irqd_set_single_target(irqd);
+		irqd_set_affinity_on_activate(irqd);
+	}
+
+	return 0;
+
+err_free:
+	irq_domain_free_irqs_parent(domain, virq, nr_irqs);
+err_free_hwirq:
+	mip_free_hwirq(mip, irq, nr_irqs);
+	return ret;
+}
+
+static void mip_middle_domain_free(struct irq_domain *domain, unsigned int virq,
+				   unsigned int nr_irqs)
+{
+	struct irq_data *irqd = irq_domain_get_irq_data(domain, virq);
+	struct mip_priv *mip;
+	unsigned int hwirq;
+
+	if (!irqd)
+		return;
+
+	mip = irq_data_get_irq_chip_data(irqd);
+	hwirq = irqd_to_hwirq(irqd);
+	irq_domain_free_irqs_parent(domain, virq, nr_irqs);
+	mip_free_hwirq(mip, hwirq - mip->msi_offset, nr_irqs);
+}
+
+static const struct irq_domain_ops mip_middle_domain_ops = {
+	.select		= msi_lib_irq_domain_select,
+	.alloc		= mip_middle_domain_alloc,
+	.free		= mip_middle_domain_free,
+};
+
+#define MIP_MSI_FLAGS_REQUIRED	(MSI_FLAG_USE_DEF_DOM_OPS |	\
+				 MSI_FLAG_USE_DEF_CHIP_OPS |	\
+				 MSI_FLAG_PCI_MSI_MASK_PARENT)
+
+#define MIP_MSI_FLAGS_SUPPORTED	(MSI_GENERIC_FLAGS_MASK |	\
+				 MSI_FLAG_MULTI_PCI_MSI |	\
+				 MSI_FLAG_PCI_MSIX)
+
+static const struct msi_parent_ops mip_msi_parent_ops = {
+	.supported_flags	= MIP_MSI_FLAGS_SUPPORTED,
+	.required_flags		= MIP_MSI_FLAGS_REQUIRED,
+	.bus_select_token       = DOMAIN_BUS_GENERIC_MSI,
+	.bus_select_mask	= MATCH_PCI_MSI,
+	.prefix			= "MIP-MSI-",
+	.init_dev_msi_info	= msi_lib_init_dev_msi_info,
+};
+
+static int mip_init_domains(struct mip_priv *mip, struct device_node *np)
+{
+	struct irq_domain *middle;
+
+	middle = irq_domain_add_hierarchy(mip->parent, 0, mip->num_msis, np,
+					  &mip_middle_domain_ops, mip);
+	if (!middle)
+		return -ENOMEM;
+
+	irq_domain_update_bus_token(middle, DOMAIN_BUS_GENERIC_MSI);
+	middle->dev = mip->dev;
+	middle->flags |= IRQ_DOMAIN_FLAG_MSI_PARENT;
+	middle->msi_parent_ops = &mip_msi_parent_ops;
+
+	/*
+	 * All MSI-X unmasked for the host, masked for the VPU, and edge-triggered.
+	 */
+	writel(0, mip->base + MIP_INT_MASKL_HOST);
+	writel(0, mip->base + MIP_INT_MASKH_HOST);
+	writel(~0, mip->base + MIP_INT_MASKL_VPU);
+	writel(~0, mip->base + MIP_INT_MASKH_VPU);
+	writel(~0, mip->base + MIP_INT_CFGL_HOST);
+	writel(~0, mip->base + MIP_INT_CFGH_HOST);
+
+	return 0;
+}
+
+static int mip_parse_dt(struct mip_priv *mip, struct device_node *np)
+{
+	struct of_phandle_args args;
+	u64 size;
+	int ret;
+
+	ret = of_property_read_u32(np, "brcm,msi-offset", &mip->msi_offset);
+	if (ret)
+		mip->msi_offset = 0;
+
+	ret = of_parse_phandle_with_args(np, "msi-ranges", "#interrupt-cells",
+					 0, &args);
+	if (ret)
+		return ret;
+
+	ret = of_property_read_u32_index(np, "msi-ranges", args.args_count + 1,
+					 &mip->num_msis);
+	if (ret)
+		goto err_put;
+
+	ret = of_property_read_reg(np, 1, &mip->msg_addr, &size);
+	if (ret)
+		goto err_put;
+
+	mip->msi_base = args.args[1];
+
+	mip->parent = irq_find_host(args.np);
+	if (!mip->parent)
+		ret = -EINVAL;
+
+err_put:
+	of_node_put(args.np);
+	return ret;
+}
+
+static int __init mip_of_msi_init(struct device_node *node, struct device_node *parent)
+{
+	struct platform_device *pdev;
+	struct mip_priv *mip;
+	int ret;
+
+	pdev = of_find_device_by_node(node);
+	of_node_put(node);
+	if (!pdev)
+		return -EPROBE_DEFER;
+
+	mip = kzalloc(sizeof(*mip), GFP_KERNEL);
+	if (!mip)
+		return -ENOMEM;
+
+	spin_lock_init(&mip->lock);
+	mip->dev = &pdev->dev;
+
+	ret = mip_parse_dt(mip, node);
+	if (ret)
+		goto err_priv;
+
+	mip->base = of_iomap(node, 0);
+	if (!mip->base) {
+		ret = -ENXIO;
+		goto err_priv;
+	}
+
+	mip->bitmap = bitmap_zalloc(mip->num_msis, GFP_KERNEL);
+	if (!mip->bitmap) {
+		ret = -ENOMEM;
+		goto err_base;
+	}
+
+	ret = mip_init_domains(mip, node);
+	if (ret)
+		goto err_map;
+
+	dev_dbg(&pdev->dev, "MIP: MSI-X count: %u, base: %u, offset: %u, msg_addr: %llx\n",
+		mip->num_msis, mip->msi_base, mip->msi_offset, mip->msg_addr);
+
+	return 0;
+
+err_map:
+	bitmap_free(mip->bitmap);
+err_base:
+	iounmap(mip->base);
+err_priv:
+	kfree(mip);
+	return ret;
+}
+
+IRQCHIP_PLATFORM_DRIVER_BEGIN(mip_msi)
+IRQCHIP_MATCH("brcm,bcm2712-mip", mip_of_msi_init)
+IRQCHIP_PLATFORM_DRIVER_END(mip_msi)
+MODULE_DESCRIPTION("Broadcom BCM2712 MSI-X interrupt controller");
+MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>");
+MODULE_AUTHOR("Stanimir Varbanov <svarbanov@suse.de>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/irqchip/irq-bcm2835.c b/drivers/irqchip/irq-bcm2835.c
index 6c20604c2242..146bfab24213 100644
--- a/drivers/irqchip/irq-bcm2835.c
+++ b/drivers/irqchip/irq-bcm2835.c
@@ -40,12 +40,16 @@
 #include <linux/of_address.h>
 #include <linux/of_irq.h>
 #include <linux/irqchip.h>
+#include <linux/irqchip/irq-bcm2836.h>
 #include <linux/irqdomain.h>
 
 #include <asm/exception.h>
+#ifndef CONFIG_ARM64
+#include <asm/mach/irq.h>
+#endif
 
 /* Put the bank and irq (32 bits) into the hwirq */
-#define MAKE_HWIRQ(b, n)	((b << 5) | (n))
+#define MAKE_HWIRQ(b, n)	(((b) << 5) | (n))
 #define HWIRQ_BANK(i)		(i >> 5)
 #define HWIRQ_BIT(i)		BIT(i & 0x1f)
 
@@ -60,11 +64,17 @@
 #define BANK0_VALID_MASK	(BANK0_HWIRQ_MASK | BANK1_HWIRQ | BANK2_HWIRQ \
 					| SHORTCUT1_MASK | SHORTCUT2_MASK)
 
+#undef ARM_LOCAL_GPU_INT_ROUTING
+#define ARM_LOCAL_GPU_INT_ROUTING 0x0c
+
 #define REG_FIQ_CONTROL		0x0c
 #define FIQ_CONTROL_ENABLE	BIT(7)
+#define REG_FIQ_ENABLE		FIQ_CONTROL_ENABLE
+#define REG_FIQ_DISABLE	0
 
 #define NR_BANKS		3
 #define IRQS_PER_BANK		32
+#define NUMBER_IRQS		MAKE_HWIRQ(NR_BANKS, 0)
 
 static const int reg_pending[] __initconst = { 0x00, 0x04, 0x08 };
 static const int reg_enable[] __initconst = { 0x18, 0x10, 0x14 };
@@ -82,6 +92,7 @@ struct armctrl_ic {
 	void __iomem *enable[NR_BANKS];
 	void __iomem *disable[NR_BANKS];
 	struct irq_domain *domain;
+	void __iomem *local_base;
 };
 
 static struct armctrl_ic intc __read_mostly;
@@ -89,22 +100,78 @@ static void __exception_irq_entry bcm2835_handle_irq(
 	struct pt_regs *regs);
 static void bcm2836_chained_handle_irq(struct irq_desc *desc);
 
+static inline unsigned int hwirq_to_fiq(unsigned long hwirq)
+{
+	hwirq -= NUMBER_IRQS;
+	/*
+	 * The hwirq numbering used in this driver is:
+	 *   BASE (0-7) GPU1 (32-63) GPU2 (64-95).
+	 * This differ from the one used in the FIQ register:
+	 *   GPU1 (0-31) GPU2 (32-63) BASE (64-71)
+	 */
+	if (hwirq >= 32)
+		return hwirq - 32;
+
+	return hwirq + 64;
+}
+
 static void armctrl_mask_irq(struct irq_data *d)
 {
-	writel_relaxed(HWIRQ_BIT(d->hwirq), intc.disable[HWIRQ_BANK(d->hwirq)]);
+	if (d->hwirq >= NUMBER_IRQS)
+		writel_relaxed(REG_FIQ_DISABLE, intc.base + REG_FIQ_CONTROL);
+	else
+		writel_relaxed(HWIRQ_BIT(d->hwirq),
+			       intc.disable[HWIRQ_BANK(d->hwirq)]);
 }
 
 static void armctrl_unmask_irq(struct irq_data *d)
 {
-	writel_relaxed(HWIRQ_BIT(d->hwirq), intc.enable[HWIRQ_BANK(d->hwirq)]);
+	if (d->hwirq >= NUMBER_IRQS) {
+		if (num_online_cpus() > 1) {
+			unsigned int data;
+
+			if (!intc.local_base) {
+				pr_err("FIQ is disabled due to missing arm_local_intc\n");
+				return;
+			}
+
+			data = readl_relaxed(intc.local_base +
+					     ARM_LOCAL_GPU_INT_ROUTING);
+
+			data &= ~0xc;
+			data |= (1 << 2);
+			writel_relaxed(data,
+				       intc.local_base +
+				       ARM_LOCAL_GPU_INT_ROUTING);
+		}
+
+		writel_relaxed(REG_FIQ_ENABLE | hwirq_to_fiq(d->hwirq),
+			       intc.base + REG_FIQ_CONTROL);
+	} else {
+		writel_relaxed(HWIRQ_BIT(d->hwirq),
+			       intc.enable[HWIRQ_BANK(d->hwirq)]);
+	}
+}
+
+#if defined(CONFIG_SMP)
+void bcm2836_arm_irqchip_spin_gpu_irq(void);
+
+static void armctrl_ack_irq(struct irq_data *d)
+{
+	bcm2836_arm_irqchip_spin_gpu_irq();
 }
 
+#endif
+
 static struct irq_chip armctrl_chip = {
 	.name = "ARMCTRL-level",
 	.irq_mask = armctrl_mask_irq,
 	.irq_unmask = armctrl_unmask_irq,
 	.flags = IRQCHIP_MASK_ON_SUSPEND |
 		 IRQCHIP_SKIP_SET_WAKE,
+#if defined(CONFIG_SMP)
+	.irq_ack    = armctrl_ack_irq
+#endif
 };
 
 static int armctrl_xlate(struct irq_domain *d, struct device_node *ctrlr,
@@ -137,15 +204,16 @@ static int __init armctrl_of_init(struct device_node *node,
 				  bool is_2836)
 {
 	void __iomem *base;
-	int irq, b, i;
+	int irq = 0, last_irq, b, i;
 	u32 reg;
 
 	base = of_iomap(node, 0);
 	if (!base)
 		panic("%pOF: unable to map IC registers\n", node);
 
-	intc.domain = irq_domain_add_linear(node, MAKE_HWIRQ(NR_BANKS, 0),
-			&armctrl_ops, NULL);
+	intc.base = base;
+	intc.domain = irq_domain_add_linear(node, NUMBER_IRQS * 2,
+					    &armctrl_ops, NULL);
 	if (!intc.domain)
 		panic("%pOF: unable to create IRQ domain\n", node);
 
@@ -176,6 +244,8 @@ static int __init armctrl_of_init(struct device_node *node,
 		pr_err(FW_BUG "Bootloader left fiq enabled\n");
 	}
 
+	last_irq = irq;
+
 	if (is_2836) {
 		int parent_irq = irq_of_parse_and_map(node, 0);
 
@@ -188,6 +258,27 @@ static int __init armctrl_of_init(struct device_node *node,
 		set_handle_irq(bcm2835_handle_irq);
 	}
 
+	if (is_2836) {
+		extern void __iomem * __attribute__((weak)) arm_local_intc;
+		intc.local_base = arm_local_intc;
+		if (!intc.local_base)
+			pr_err("Failed to get local intc base. FIQ is disabled for cpus > 1\n");
+	}
+
+	/* Make a duplicate irq range which is used to enable FIQ */
+	for (b = 0; b < NR_BANKS; b++) {
+		for (i = 0; i < bank_irqs[b]; i++) {
+			irq = irq_create_mapping(intc.domain,
+					MAKE_HWIRQ(b, i) + NUMBER_IRQS);
+			BUG_ON(irq <= 0);
+			irq_set_chip(irq, &armctrl_chip);
+			irq_set_probe(irq);
+		}
+	}
+#ifndef CONFIG_ARM64
+	init_FIQ(irq - last_irq);
+#endif
+
 	return 0;
 }
 
@@ -255,7 +346,8 @@ static void bcm2836_chained_handle_irq(struct irq_desc *desc)
 {
 	u32 hwirq;
 
-	while ((hwirq = get_next_armctrl_hwirq()) != ~0)
+	hwirq = get_next_armctrl_hwirq();
+	if (hwirq != ~0)
 		generic_handle_domain_irq(intc.domain, hwirq);
 }
 
diff --git a/drivers/irqchip/irq-bcm2836.c b/drivers/irqchip/irq-bcm2836.c
index e5f1059b989f..00aea693d4b6 100644
--- a/drivers/irqchip/irq-bcm2836.c
+++ b/drivers/irqchip/irq-bcm2836.c
@@ -22,6 +22,9 @@ struct bcm2836_arm_irqchip_intc {
 
 static struct bcm2836_arm_irqchip_intc intc  __read_mostly;
 
+void __iomem *arm_local_intc;
+EXPORT_SYMBOL_GPL(arm_local_intc);
+
 static void bcm2836_arm_irqchip_mask_per_cpu_irq(unsigned int reg_offset,
 						 unsigned int bit,
 						 int cpu)
@@ -84,6 +87,32 @@ static void bcm2836_arm_irqchip_unmask_gpu_irq(struct irq_data *d)
 {
 }
 
+#if defined(CONFIG_SMP)
+
+void bcm2836_arm_irqchip_spin_gpu_irq(void)
+{
+	u32 i;
+	void __iomem *gpurouting = (intc.base + LOCAL_GPU_ROUTING);
+	u32 routing_val = readl(gpurouting);
+	u32 irq_route;
+
+	/* Preserve FIQ routing bits */
+	irq_route = routing_val & 0x3;
+	routing_val &= ~0x3;
+
+	for (i = 1; i <= 3; i++) {
+		irq_route = (irq_route + i) & 0x3;
+
+		if (cpu_active(irq_route)) {
+			writel(irq_route | routing_val, gpurouting);
+			return;
+		}
+	}
+}
+EXPORT_SYMBOL(bcm2836_arm_irqchip_spin_gpu_irq);
+
+#endif
+
 static struct irq_chip bcm2836_arm_irqchip_gpu = {
 	.name		= "bcm2836-gpu",
 	.irq_mask	= bcm2836_arm_irqchip_mask_gpu_irq,
@@ -128,7 +157,7 @@ static int bcm2836_map(struct irq_domain *d, unsigned int irq,
 	irq_set_percpu_devid(irq);
 	irq_domain_set_info(d, irq, hw, chip, d->host_data,
 			    handle_percpu_devid_irq, NULL, NULL);
-	irq_set_status_flags(irq, IRQ_NOAUTOEN);
+	irq_set_status_flags(irq, IRQ_NOAUTOEN | IRQ_TYPE_LEVEL_LOW);
 
 	return 0;
 }
@@ -320,6 +349,8 @@ static int __init bcm2836_arm_irqchip_l1_intc_of_init(struct device_node *node,
 		panic("%pOF: unable to map local interrupt registers\n", node);
 	}
 
+	arm_local_intc = intc.base;
+
 	bcm2835_init_local_timer_frequency();
 
 	intc.domain = irq_domain_add_linear(node, LAST_IRQ + 1,
diff --git a/drivers/irqchip/irq-brcmstb-l2.c b/drivers/irqchip/irq-brcmstb-l2.c
index 60863b2548f5..c9a9d0bf9596 100644
--- a/drivers/irqchip/irq-brcmstb-l2.c
+++ b/drivers/irqchip/irq-brcmstb-l2.c
@@ -51,6 +51,16 @@ static const struct brcmstb_intc_init_params l2_lvl_intc_init = {
 	.cpu_mask_clear		= 0x0C
 };
 
+/* Register offsets in the 2711 L2 level interrupt controller */
+static const struct brcmstb_intc_init_params l2_2711_lvl_intc_init = {
+	.handler		= handle_level_irq,
+	.cpu_status		= 0x00,
+	.cpu_clear		= 0x08,
+	.cpu_mask_status	= 0x0c,
+	.cpu_mask_set		= 0x10,
+	.cpu_mask_clear		= 0x14
+};
+
 /* L2 intc private data structure */
 struct brcmstb_l2_intc_data {
 	struct irq_domain *domain;
@@ -295,11 +305,18 @@ static int brcmstb_l2_lvl_intc_of_init(struct device_node *np, struct device_nod
 	return brcmstb_l2_intc_of_init(np, parent, &l2_lvl_intc_init);
 }
 
+static int __init brcmstb_l2_2711_lvl_intc_of_init(struct device_node *np,
+	struct device_node *parent)
+{
+	return brcmstb_l2_intc_of_init(np, parent, &l2_2711_lvl_intc_init);
+}
+
 IRQCHIP_PLATFORM_DRIVER_BEGIN(brcmstb_l2)
 IRQCHIP_MATCH("brcm,l2-intc", brcmstb_l2_edge_intc_of_init)
 IRQCHIP_MATCH("brcm,hif-spi-l2-intc", brcmstb_l2_edge_intc_of_init)
 IRQCHIP_MATCH("brcm,upg-aux-aon-l2-intc", brcmstb_l2_edge_intc_of_init)
 IRQCHIP_MATCH("brcm,bcm7271-l2-intc", brcmstb_l2_lvl_intc_of_init)
+IRQCHIP_MATCH("brcm,bcm2711-l2-intc", brcmstb_l2_2711_lvl_intc_of_init)
 IRQCHIP_PLATFORM_DRIVER_END(brcmstb_l2)
 MODULE_DESCRIPTION("Broadcom STB generic L2 interrupt controller");
 MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c
index 4d1612d557c8..9e503b6cec57 100644
--- a/drivers/leds/leds-gpio.c
+++ b/drivers/leds/leds-gpio.c
@@ -52,8 +52,15 @@ static void gpio_led_set(struct led_classdev *led_cdev,
 		led_dat->platform_gpio_blink_set(led_dat->gpiod, level,
 						 NULL, NULL);
 		led_dat->blinking = 0;
+	} else if (led_dat->cdev.flags & SET_GPIO_INPUT) {
+		gpiod_direction_input(led_dat->gpiod);
+		led_dat->cdev.flags &= ~SET_GPIO_INPUT;
+	} else if (led_dat->cdev.flags & SET_GPIO_OUTPUT) {
+		gpiod_direction_output(led_dat->gpiod, level);
+		led_dat->cdev.flags &= ~SET_GPIO_OUTPUT;
 	} else {
-		if (led_dat->can_sleep)
+		if (led_dat->can_sleep ||
+			(led_dat->cdev.flags & (SET_GPIO_INPUT | SET_GPIO_OUTPUT) ))
 			gpiod_set_value_cansleep(led_dat->gpiod, level);
 		else
 			gpiod_set_value(led_dat->gpiod, level);
@@ -67,6 +74,13 @@ static int gpio_led_set_blocking(struct led_classdev *led_cdev,
 	return 0;
 }
 
+static enum led_brightness gpio_led_get(struct led_classdev *led_cdev)
+{
+	struct gpio_led_data *led_dat =
+		container_of(led_cdev, struct gpio_led_data, cdev);
+	return gpiod_get_value_cansleep(led_dat->gpiod) ? LED_FULL : LED_OFF;
+}
+
 static int gpio_blink_set(struct led_classdev *led_cdev,
 	unsigned long *delay_on, unsigned long *delay_off)
 {
@@ -96,6 +110,7 @@ static int create_gpio_led(const struct gpio_led *template,
 		led_dat->platform_gpio_blink_set = blink_set;
 		led_dat->cdev.blink_set = gpio_blink_set;
 	}
+	led_dat->cdev.brightness_get = gpio_led_get;
 	if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) {
 		state = gpiod_get_value_cansleep(led_dat->gpiod);
 		if (state < 0)
diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig
index c11282a74b5a..c48c1c3e9e11 100644
--- a/drivers/leds/trigger/Kconfig
+++ b/drivers/leds/trigger/Kconfig
@@ -113,6 +113,13 @@ config LEDS_TRIGGER_CAMERA
 	  This enables direct flash/torch on/off by the driver, kernel space.
 	  If unsure, say Y.
 
+config LEDS_TRIGGER_INPUT
+	tristate "LED Input Trigger"
+	depends on LEDS_TRIGGERS
+	help
+	  This allows the GPIOs assigned to be LEDs to be initialised to inputs.
+	  If unsure, say Y.
+
 config LEDS_TRIGGER_PANIC
 	bool "LED Panic Trigger"
 	help
@@ -161,4 +168,15 @@ config LEDS_TRIGGER_INPUT_EVENTS
 
 	  When build as a module this driver will be called ledtrig-input-events.
 
+config LEDS_TRIGGER_ACTPWR
+	tristate "ACT/PWR Input Trigger"
+	depends on LEDS_TRIGGERS
+	help
+	  This trigger is intended for platforms that have one software-
+	  controllable LED and no dedicated activity or power LEDs, hence the
+	  need to make the one LED perform both functions. It cycles between
+	  default-on and an inverted mmc0 every 500ms, guaranteeing that it is
+	  on for at least half of the time.
+	  If unsure, say N.
+
 endif # LEDS_TRIGGERS
diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile
index 3b3628889f68..c17cbd1939d5 100644
--- a/drivers/leds/trigger/Makefile
+++ b/drivers/leds/trigger/Makefile
@@ -11,8 +11,10 @@ obj-$(CONFIG_LEDS_TRIGGER_ACTIVITY)	+= ledtrig-activity.o
 obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON)	+= ledtrig-default-on.o
 obj-$(CONFIG_LEDS_TRIGGER_TRANSIENT)	+= ledtrig-transient.o
 obj-$(CONFIG_LEDS_TRIGGER_CAMERA)	+= ledtrig-camera.o
+obj-$(CONFIG_LEDS_TRIGGER_INPUT)	+= ledtrig-input.o
 obj-$(CONFIG_LEDS_TRIGGER_PANIC)	+= ledtrig-panic.o
 obj-$(CONFIG_LEDS_TRIGGER_NETDEV)	+= ledtrig-netdev.o
 obj-$(CONFIG_LEDS_TRIGGER_PATTERN)	+= ledtrig-pattern.o
 obj-$(CONFIG_LEDS_TRIGGER_TTY)		+= ledtrig-tty.o
 obj-$(CONFIG_LEDS_TRIGGER_INPUT_EVENTS)	+= ledtrig-input-events.o
+obj-$(CONFIG_LEDS_TRIGGER_ACTPWR)	+= ledtrig-actpwr.o
diff --git a/drivers/leds/trigger/ledtrig-actpwr.c b/drivers/leds/trigger/ledtrig-actpwr.c
new file mode 100644
index 000000000000..1a52107ceb03
--- /dev/null
+++ b/drivers/leds/trigger/ledtrig-actpwr.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Activity/power trigger
+ *
+ * Copyright (C) 2020 Raspberry Pi (Trading) Ltd.
+ *
+ * Based on Atsushi Nemoto's ledtrig-heartbeat.c, although there may be
+ * nothing left of the original now.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/timer.h>
+#include <linux/leds.h>
+#include "../leds.h"
+
+enum {
+	TRIG_ACT,
+	TRIG_PWR,
+
+	TRIG_COUNT
+};
+
+struct actpwr_trig_src {
+	const char *name;
+	int interval;
+	bool invert;
+};
+
+struct actpwr_vled {
+	struct led_classdev cdev;
+	struct actpwr_trig_data *parent;
+	enum led_brightness value;
+	unsigned int interval;
+	bool invert;
+};
+
+struct actpwr_trig_data {
+	struct led_trigger trig;
+	struct actpwr_vled virt_leds[TRIG_COUNT];
+	struct actpwr_vled *active;
+	struct timer_list timer;
+	int next_active;
+};
+
+static int actpwr_trig_activate(struct led_classdev *led_cdev);
+static void actpwr_trig_deactivate(struct led_classdev *led_cdev);
+
+static const struct actpwr_trig_src actpwr_trig_sources[TRIG_COUNT] = {
+	[TRIG_ACT] = { "mmc0", 500, true },
+	[TRIG_PWR] = { "default-on", 500, false },
+};
+
+static struct actpwr_trig_data actpwr_data = {
+	{
+		.name     = "actpwr",
+		.activate = actpwr_trig_activate,
+		.deactivate = actpwr_trig_deactivate,
+	}
+};
+
+static void actpwr_brightness_set(struct led_classdev *led_cdev,
+				  enum led_brightness value)
+{
+	struct actpwr_vled *vled = container_of(led_cdev, struct actpwr_vled,
+					       cdev);
+	struct actpwr_trig_data *trig = vled->parent;
+
+	if (vled->invert)
+		value = !value;
+	vled->value = value;
+
+	if (vled == trig->active)
+		led_trigger_event(&trig->trig, value);
+}
+
+static int actpwr_brightness_set_blocking(struct led_classdev *led_cdev,
+					  enum led_brightness value)
+{
+	actpwr_brightness_set(led_cdev, value);
+	return 0;
+}
+
+static enum led_brightness actpwr_brightness_get(struct led_classdev *led_cdev)
+{
+	struct actpwr_vled *vled = container_of(led_cdev, struct actpwr_vled,
+					      cdev);
+
+	return vled->value;
+}
+
+static void actpwr_trig_cycle(struct timer_list *t)
+{
+	struct actpwr_trig_data *trig  = &actpwr_data;
+	struct actpwr_vled *active;
+
+	active = &trig->virt_leds[trig->next_active];
+	trig->active = active;
+	trig->next_active = (trig->next_active + 1) % TRIG_COUNT;
+
+	led_trigger_event(&trig->trig, active->value);
+
+	mod_timer(&trig->timer, jiffies + msecs_to_jiffies(active->interval));
+}
+
+static int actpwr_trig_activate(struct led_classdev *led_cdev)
+{
+	struct actpwr_trig_data *trig  = &actpwr_data;
+
+	/* Start the timer if this is the first LED */
+	if (!trig->active)
+		actpwr_trig_cycle(&trig->timer);
+	else
+		led_set_brightness_nosleep(led_cdev, trig->active->value);
+
+	return 0;
+}
+
+static void actpwr_trig_deactivate(struct led_classdev *led_cdev)
+{
+	struct actpwr_trig_data *trig  = &actpwr_data;
+
+	if (list_empty(&trig->trig.led_cdevs)) {
+		del_timer_sync(&trig->timer);
+		trig->active = NULL;
+	}
+}
+
+static int __init actpwr_trig_init(void)
+{
+	struct actpwr_trig_data *trig  = &actpwr_data;
+	int ret = 0;
+	int i;
+
+	timer_setup(&trig->timer, actpwr_trig_cycle, 0);
+
+	/* Register one "LED" for each source trigger */
+	for (i = 0; i < TRIG_COUNT; i++)
+	{
+		struct actpwr_vled *vled = &trig->virt_leds[i];
+		struct led_classdev *cdev = &vled->cdev;
+		const struct actpwr_trig_src *src = &actpwr_trig_sources[i];
+
+		vled->parent = trig;
+		vled->interval = src->interval;
+		vled->invert = src->invert;
+		cdev->name = src->name;
+		cdev->brightness_set = actpwr_brightness_set;
+		cdev->brightness_set_blocking = actpwr_brightness_set_blocking;
+		cdev->brightness_get = actpwr_brightness_get;
+		cdev->default_trigger = src->name;
+		ret = led_classdev_register(NULL, cdev);
+		if (ret)
+			goto error_classdev;
+	}
+
+	ret = led_trigger_register(&trig->trig);
+	if (ret)
+		goto error_classdev;
+
+	return 0;
+
+error_classdev:
+	while (i > 0)
+	{
+		i--;
+		led_classdev_unregister(&trig->virt_leds[i].cdev);
+	}
+
+	return ret;
+}
+
+static void __exit actpwr_trig_exit(void)
+{
+	int i;
+
+	led_trigger_unregister(&actpwr_data.trig);
+	for (i = 0; i < TRIG_COUNT; i++)
+	{
+		led_classdev_unregister(&actpwr_data.virt_leds[i].cdev);
+	}
+}
+
+module_init(actpwr_trig_init);
+module_exit(actpwr_trig_exit);
+
+MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>");
+MODULE_DESCRIPTION("ACT/PWR LED trigger");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/trigger/ledtrig-input.c b/drivers/leds/trigger/ledtrig-input.c
new file mode 100644
index 000000000000..8a974a355656
--- /dev/null
+++ b/drivers/leds/trigger/ledtrig-input.c
@@ -0,0 +1,55 @@
+/*
+ * Set LED GPIO to Input "Trigger"
+ *
+ * Copyright 2015 Phil Elwell <phil@raspberrypi.org>
+ *
+ * Based on Nick Forbes's ledtrig-default-on.c.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/gpio.h>
+#include "../leds.h"
+
+static int input_trig_activate(struct led_classdev *led_cdev)
+{
+	led_cdev->flags |= SET_GPIO_INPUT;
+	led_set_brightness(led_cdev, 0);
+	return 0;
+}
+
+static void input_trig_deactivate(struct led_classdev *led_cdev)
+{
+	led_cdev->flags |= SET_GPIO_OUTPUT;
+	led_set_brightness(led_cdev, 0);
+}
+
+static struct led_trigger input_led_trigger = {
+	.name     = "input",
+	.activate = input_trig_activate,
+	.deactivate = input_trig_deactivate,
+};
+
+static int __init input_trig_init(void)
+{
+	return led_trigger_register(&input_led_trigger);
+}
+
+static void __exit input_trig_exit(void)
+{
+	led_trigger_unregister(&input_led_trigger);
+}
+
+module_init(input_trig_init);
+module_exit(input_trig_exit);
+
+MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.org>");
+MODULE_DESCRIPTION("Set LED GPIO to Input \"trigger\"");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
index 6fb995778636..916177e6b4f5 100644
--- a/drivers/mailbox/Kconfig
+++ b/drivers/mailbox/Kconfig
@@ -295,4 +295,13 @@ config QCOM_IPCC
 	  acts as an interrupt controller for receiving interrupts from clients.
 	  Say Y here if you want to build this driver.
 
+config MBOX_RP1
+	tristate "RP1 Mailbox"
+	depends on MFD_RP1
+	help
+	  An implementation of a mailbox interface to the Raspberry Pi RP1 I/O
+	  interface. Although written as a mailbox driver, the hardware only
+	  provides an array of 32 doorbells.
+	  Say Y here if you want to use the RP1 Mailbox.
+
 endif
diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
index 3c3c27d54c13..e76d8e1c3710 100644
--- a/drivers/mailbox/Makefile
+++ b/drivers/mailbox/Makefile
@@ -64,3 +64,5 @@ obj-$(CONFIG_SPRD_MBOX)		+= sprd-mailbox.o
 obj-$(CONFIG_QCOM_CPUCP_MBOX)	+= qcom-cpucp-mbox.o
 
 obj-$(CONFIG_QCOM_IPCC)		+= qcom-ipcc.o
+
+obj-$(CONFIG_MBOX_RP1)		+= rp1-mailbox.o
diff --git a/drivers/mailbox/bcm2835-mailbox.c b/drivers/mailbox/bcm2835-mailbox.c
index ea12fb8d2401..8c54980b4b7e 100644
--- a/drivers/mailbox/bcm2835-mailbox.c
+++ b/drivers/mailbox/bcm2835-mailbox.c
@@ -45,12 +45,15 @@
 #define MAIL1_WRT	(ARM_0_MAIL1 + 0x00)
 #define MAIL1_STA	(ARM_0_MAIL1 + 0x18)
 
+/* On ARCH_BCM270x these come through <linux/interrupt.h> (arm_control.h ) */
+#ifndef ARM_MS_FULL
 /* Status register: FIFO state. */
 #define ARM_MS_FULL		BIT(31)
 #define ARM_MS_EMPTY		BIT(30)
 
 /* Configuration register: Enable interrupts. */
 #define ARM_MC_IHAVEDATAIRQEN	BIT(0)
+#endif
 
 struct bcm2835_mbox {
 	void __iomem *regs;
@@ -144,7 +147,7 @@ static int bcm2835_mbox_probe(struct platform_device *pdev)
 		return -ENOMEM;
 	spin_lock_init(&mbox->lock);
 
-	ret = devm_request_irq(dev, irq_of_parse_and_map(dev->of_node, 0),
+	ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
 			       bcm2835_mbox_irq, IRQF_NO_SUSPEND, dev_name(dev),
 			       mbox);
 	if (ret) {
@@ -193,7 +196,18 @@ static struct platform_driver bcm2835_mbox_driver = {
 	},
 	.probe		= bcm2835_mbox_probe,
 };
-module_platform_driver(bcm2835_mbox_driver);
+
+static int __init bcm2835_mbox_init(void)
+{
+	return platform_driver_register(&bcm2835_mbox_driver);
+}
+arch_initcall(bcm2835_mbox_init);
+
+static void __init bcm2835_mbox_exit(void)
+{
+	platform_driver_unregister(&bcm2835_mbox_driver);
+}
+module_exit(bcm2835_mbox_exit);
 
 MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>");
 MODULE_DESCRIPTION("BCM2835 mailbox IPC driver");
diff --git a/drivers/mailbox/rp1-mailbox.c b/drivers/mailbox/rp1-mailbox.c
new file mode 100644
index 000000000000..0e8af098b62b
--- /dev/null
+++ b/drivers/mailbox/rp1-mailbox.c
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ *  Copyright (C) 2023 Raspberry Pi Ltd.
+ *
+ * Parts of this driver are based on:
+ *  - bcm2835-mailbox.c
+ *    Copyright (C) 2010,2015 Broadcom
+ *    Copyright (C) 2013-2014 Lubomir Rintel
+ *    Copyright (C) 2013 Craig McGeachie
+ */
+
+#include <linux/compat.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/mailbox_controller.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+
+/*
+ * RP1's PROC_EVENTS register can generate interrupts on the M3 cores (when
+ * enabled). The 32-bit register is treated as 32 events, all of which share a
+ * common interrupt. HOST_EVENTS is the same in the reverse direction.
+ */
+#define SYSCFG_PROC_EVENTS		0x00000008
+#define SYSCFG_HOST_EVENTS		0x0000000c
+#define SYSCFG_HOST_EVENT_IRQ_EN	0x00000010
+#define SYSCFG_HOST_EVENT_IRQ		0x00000014
+
+#define HW_SET_BITS			0x00002000
+#define HW_CLR_BITS			0x00003000
+
+#define MAX_CHANS	4 /* 32 is the hardware limit */
+
+struct rp1_mbox {
+	void __iomem *regs;
+	unsigned int irq;
+	struct mbox_controller controller;
+};
+
+static struct rp1_mbox *rp1_chan_mbox(struct mbox_chan *chan)
+{
+	return container_of(chan->mbox, struct rp1_mbox, controller);
+}
+
+static unsigned int rp1_chan_event(struct mbox_chan *chan)
+{
+	return (unsigned int)(uintptr_t)chan->con_priv;
+}
+
+static irqreturn_t rp1_mbox_irq(int irq, void *dev_id)
+{
+	struct rp1_mbox *mbox = dev_id;
+	struct mbox_chan *chan;
+	unsigned int doorbell;
+	unsigned int evs;
+
+	evs = readl(mbox->regs + SYSCFG_HOST_EVENT_IRQ);
+	writel(evs, mbox->regs + SYSCFG_HOST_EVENTS + HW_CLR_BITS);
+
+	while (evs) {
+		doorbell = __ffs(evs);
+		chan = &mbox->controller.chans[doorbell];
+		mbox_chan_received_data(chan, NULL);
+		evs &= ~(1 << doorbell);
+	}
+	return IRQ_HANDLED;
+}
+
+static int rp1_send_data(struct mbox_chan *chan, void *data)
+{
+	struct rp1_mbox *mbox = rp1_chan_mbox(chan);
+	unsigned int event = rp1_chan_event(chan);
+
+	writel(event, mbox->regs + SYSCFG_PROC_EVENTS + HW_SET_BITS);
+
+	return 0;
+}
+
+static int rp1_startup(struct mbox_chan *chan)
+{
+	struct rp1_mbox *mbox = rp1_chan_mbox(chan);
+	unsigned int event = rp1_chan_event(chan);
+
+	writel(event, mbox->regs + SYSCFG_HOST_EVENT_IRQ_EN + HW_SET_BITS);
+
+	return 0;
+}
+
+static void rp1_shutdown(struct mbox_chan *chan)
+{
+	struct rp1_mbox *mbox = rp1_chan_mbox(chan);
+	unsigned int event = rp1_chan_event(chan);
+
+	writel(event, mbox->regs + SYSCFG_HOST_EVENT_IRQ_EN + HW_CLR_BITS);
+}
+
+static bool rp1_last_tx_done(struct mbox_chan *chan)
+{
+	struct rp1_mbox *mbox = rp1_chan_mbox(chan);
+	unsigned int event = rp1_chan_event(chan);
+	unsigned int evs;
+
+	evs = readl(mbox->regs + SYSCFG_HOST_EVENT_IRQ);
+
+	return !(evs & event);
+}
+
+static const struct mbox_chan_ops rp1_mbox_chan_ops = {
+	.send_data	= rp1_send_data,
+	.startup	= rp1_startup,
+	.shutdown	= rp1_shutdown,
+	.last_tx_done	= rp1_last_tx_done
+};
+
+static struct mbox_chan *rp1_mbox_xlate(struct mbox_controller *mbox,
+					const struct of_phandle_args *spec)
+{
+	struct mbox_chan *chan;
+	unsigned int doorbell;
+
+	if (spec->args_count != 1)
+		return ERR_PTR(-EINVAL);
+
+	doorbell = spec->args[0];
+	if (doorbell >= MAX_CHANS)
+		return ERR_PTR(-EINVAL);
+
+	chan = &mbox->chans[doorbell];
+
+	chan->con_priv = (void *)(uintptr_t)(1 << doorbell);
+
+	return chan;
+}
+
+static int rp1_mbox_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mbox_chan *chans;
+	struct rp1_mbox *mbox;
+	int ret = 0;
+
+	mbox = devm_kzalloc(dev, sizeof(*mbox), GFP_KERNEL);
+	if (mbox == NULL)
+		return -ENOMEM;
+
+	ret = devm_request_irq(dev, platform_get_irq(pdev, 0),
+			       rp1_mbox_irq, 0, dev_name(dev), mbox);
+	if (ret) {
+		dev_err(dev, "Failed to register a mailbox IRQ handler: %d\n",
+			ret);
+		return -ENODEV;
+	}
+
+	mbox->regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(mbox->regs)) {
+		ret = PTR_ERR(mbox->regs);
+		return ret;
+	}
+
+	chans = devm_kcalloc(dev, MAX_CHANS, sizeof(*chans), GFP_KERNEL);
+	if (!chans)
+		return -ENOMEM;
+
+	mbox->controller.txdone_poll = true;
+	mbox->controller.txpoll_period = 5;
+	mbox->controller.ops = &rp1_mbox_chan_ops;
+	mbox->controller.of_xlate = &rp1_mbox_xlate;
+	mbox->controller.dev = dev;
+	mbox->controller.num_chans = MAX_CHANS;
+	mbox->controller.chans = chans;
+
+	ret = devm_mbox_controller_register(dev, &mbox->controller);
+	if (ret)
+		return ret;
+
+	platform_set_drvdata(pdev, mbox);
+
+	return 0;
+}
+
+static const struct of_device_id rp1_mbox_of_match[] = {
+	{ .compatible = "raspberrypi,rp1-mbox", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, rp1_mbox_of_match);
+
+static struct platform_driver rp1_mbox_driver = {
+	.driver = {
+		.name = "rp1-mbox",
+		.of_match_table = rp1_mbox_of_match,
+	},
+	.probe = rp1_mbox_probe,
+};
+
+module_platform_driver(rp1_mbox_driver);
+
+MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>");
+MODULE_DESCRIPTION("RP1 mailbox IPC driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/common/videobuf2/videobuf2-core.c b/drivers/media/common/videobuf2/videobuf2-core.c
index b0523fc23506..4ebc327e2963 100644
--- a/drivers/media/common/videobuf2/videobuf2-core.c
+++ b/drivers/media/common/videobuf2/videobuf2-core.c
@@ -2424,11 +2424,11 @@ static int __find_plane_by_offset(struct vb2_queue *q, unsigned long offset,
 	return 0;
 }
 
-int vb2_core_expbuf(struct vb2_queue *q, int *fd, unsigned int type,
-		    struct vb2_buffer *vb, unsigned int plane, unsigned int flags)
+int vb2_core_expbuf_dmabuf(struct vb2_queue *q, unsigned int type,
+			   struct vb2_buffer *vb, unsigned int plane,
+			   unsigned int flags, struct dma_buf **dmabuf)
 {
 	struct vb2_plane *vb_plane;
-	int ret;
 	struct dma_buf *dbuf;
 
 	if (q->memory != VB2_MEMORY_MMAP) {
@@ -2473,6 +2473,21 @@ int vb2_core_expbuf(struct vb2_queue *q, int *fd, unsigned int type,
 		return -EINVAL;
 	}
 
+	*dmabuf = dbuf;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(vb2_core_expbuf_dmabuf);
+
+int vb2_core_expbuf(struct vb2_queue *q, int *fd, unsigned int type,
+		    struct vb2_buffer *vb, unsigned int plane, unsigned int flags)
+{
+	struct dma_buf *dbuf;
+	int ret;
+
+	ret = vb2_core_expbuf_dmabuf(q, type, vb, plane, flags, &dbuf);
+	if (ret)
+		return ret;
+
 	ret = dma_buf_fd(dbuf, flags & ~O_ACCMODE);
 	if (ret < 0) {
 		dprintk(q, 3, "buffer %d, plane %d failed to export (%d)\n",
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 470307135254..5bcc54682326 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -100,6 +100,28 @@ config VIDEO_GC2145
 	  To compile this driver as a module, choose M here: the
 	  module will be called gc2145.
 
+config VIDEO_ARDUCAM_64MP
+	tristate "Arducam 64MP sensor support"
+	depends on I2C && VIDEO_DEV
+	select VIDEO_V4L2_SUBDEV_API
+	help
+	  This is a Video4Linux2 sensor driver for the Arducam
+	  64MP camera.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called arducam_64mp.
+
+config VIDEO_ARDUCAM_PIVARIETY
+	tristate "Arducam Pivariety sensor support"
+	depends on I2C && VIDEO_DEV
+	select VIDEO_V4L2_SUBDEV_API
+	help
+	  This is a Video4Linux2 sensor driver for the Arducam
+	  Pivariety camera series.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called arducam-pivariety.
+
 config VIDEO_HI556
 	tristate "Hynix Hi-556 sensor support"
 	help
@@ -265,9 +287,73 @@ config VIDEO_IMX415
 	  To compile this driver as a module, choose M here: the
 	  module will be called imx415.
 
+config VIDEO_IMX477
+	tristate "Sony IMX477 sensor support"
+	depends on I2C && VIDEO_DEV
+	select V4L2_CCI_I2C
+	select VIDEO_V4L2_SUBDEV_API
+	select MEDIA_CONTROLLER
+	select V4L2_FWNODE
+	help
+	  This is a Video4Linux2 sensor driver for the Sony
+	  IMX477 camera. Also supports the Sony IMX378.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called imx477.
+
+config VIDEO_IMX500
+	tristate "Sony IMX500 sensor support"
+	depends on I2C && VIDEO_DEV
+	select VIDEO_V4L2_SUBDEV_API
+	select V4L2_CCI_I2C
+	help
+	  This is a Video4Linux2 sensor driver for the Sony
+	  IMX500 camera.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called IMX500.
+
+config VIDEO_IMX519
+	tristate "Arducam IMX519 sensor support"
+	depends on I2C && VIDEO_DEV
+	select VIDEO_V4L2_SUBDEV_API
+	help
+	  This is a Video4Linux2 sensor driver for the Arducam
+	  IMX519 camera.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called IMX519.
+
+config VIDEO_IMX708
+	tristate "Sony IMX708 sensor support"
+	depends on I2C && VIDEO_DEV
+	select MEDIA_CONTROLLER
+	select VIDEO_V4L2_SUBDEV_API
+	select V4L2_FWNODE
+	help
+	  This is a Video4Linux2 sensor driver for the Sony
+	  IMX708 camera.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called imx708.
+
 config VIDEO_MAX9271_LIB
 	tristate
 
+config VIDEO_MIRA220
+	tristate "ams MIRA220 sensor support"
+	depends on I2C && VIDEO_DEV
+	select MEDIA_CONTROLLER
+	select VIDEO_V4L2_SUBDEV_API
+	select V4L2_CCI_I2C
+	select V4L2_FWNODE
+	help
+	  This is a Video4Linux2 sensor driver for the ams
+	  MIRA220 camera.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called mira220.
+
 config VIDEO_MT9M001
 	tristate "mt9m001 support"
 	help
@@ -386,6 +472,16 @@ config VIDEO_OV13B10
 	  This is a Video4Linux2 sensor driver for the OmniVision
 	  OV13B10 camera.
 
+config VIDEO_OV2311
+	tristate "OmniVision OV2311 sensor support"
+	depends on I2C && VIDEO_DEV
+	help
+	  This is a Video4Linux2 sensor-level driver for the OmniVision
+	  OV2311 camera.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ov2311.
+
 config VIDEO_OV2640
 	tristate "OmniVision OV2640 sensor support"
 	help
@@ -725,6 +821,13 @@ endmenu
 menu "Lens drivers"
 	visible if MEDIA_CAMERA_SUPPORT
 
+config VIDEO_AD5398
+	tristate "AD5398 lens voice coil support"
+	depends on GPIOLIB && I2C && VIDEO_DEV
+	select MEDIA_CONTROLLER
+	help
+	  This is a driver for the AD5398 camera lens voice coil.
+
 config VIDEO_AD5820
 	tristate "AD5820 lens voice coil support"
 	depends on GPIOLIB && I2C && VIDEO_DEV
@@ -746,6 +849,19 @@ config VIDEO_AK7375
 	  capability. This is designed for linear control of
 	  voice coil motors, controlled via I2C serial interface.
 
+config VIDEO_BU64754
+	tristate "BU64754 Motor Driver for Camera Autofocus"
+	depends on I2C && VIDEO_DEV
+	select MEDIA_CONTROLLER
+	select VIDEO_V4L2_SUBDEV_API
+	select V4L2_ASYNC
+	select V4L2_CCI_I2C
+	help
+	  This is a driver for the BU64754 Motor Driver for Camera
+	  Autofocus. The BU64754GWZ is an actuator driver IC which
+	  can be controlled the actuator position precisely using
+	  with internal Hall Sensor.
+
 config VIDEO_DW9714
 	tristate "DW9714 lens voice coil support"
 	depends on GPIOLIB && I2C && VIDEO_DEV
@@ -1325,6 +1441,18 @@ config VIDEO_TW9910
 	  To compile this driver as a module, choose M here: the
 	  module will be called tw9910.
 
+config VIDEO_IRS1125
+	tristate "Infineon IRS1125 sensor support"
+	depends on I2C && VIDEO_DEV
+	select VIDEO_V4L2_SUBDEV_API
+	select V4L2_FWNODE
+	help
+	  This is a Video4Linux2 sensor-level driver for the Infineon
+	  IRS1125 camera.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called irs1125.
+
 config VIDEO_VPX3220
 	tristate "vpx3220a, vpx3216b & vpx3214c video decoders"
 	depends on VIDEO_DEV && I2C
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index fbb988bd067a..efc521b1bb22 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -3,6 +3,7 @@
 msp3400-objs	:=	msp3400-driver.o msp3400-kthreads.o
 
 obj-$(CONFIG_SDR_MAX2175) += max2175.o
+obj-$(CONFIG_VIDEO_AD5398) += ad5398_vcm.o
 obj-$(CONFIG_VIDEO_AD5820) += ad5820.o
 obj-$(CONFIG_VIDEO_ADP1653) += adp1653.o
 obj-$(CONFIG_VIDEO_ADV7170) += adv7170.o
@@ -20,9 +21,12 @@ obj-$(CONFIG_VIDEO_AK881X) += ak881x.o
 obj-$(CONFIG_VIDEO_ALVIUM_CSI2) += alvium-csi2.o
 obj-$(CONFIG_VIDEO_APTINA_PLL) += aptina-pll.o
 obj-$(CONFIG_VIDEO_AR0521) += ar0521.o
+obj-$(CONFIG_VIDEO_ARDUCAM_64MP) += arducam_64mp.o
+obj-$(CONFIG_VIDEO_ARDUCAM_PIVARIETY) += arducam-pivariety.o
 obj-$(CONFIG_VIDEO_BT819) += bt819.o
 obj-$(CONFIG_VIDEO_BT856) += bt856.o
 obj-$(CONFIG_VIDEO_BT866) += bt866.o
+obj-$(CONFIG_VIDEO_BU64754) += bu64754.o
 obj-$(CONFIG_VIDEO_CCS) += ccs/
 obj-$(CONFIG_VIDEO_CCS_PLL) += ccs-pll.o
 obj-$(CONFIG_VIDEO_CS3308) += cs3308.o
@@ -59,7 +63,12 @@ obj-$(CONFIG_VIDEO_IMX335) += imx335.o
 obj-$(CONFIG_VIDEO_IMX355) += imx355.o
 obj-$(CONFIG_VIDEO_IMX412) += imx412.o
 obj-$(CONFIG_VIDEO_IMX415) += imx415.o
+obj-$(CONFIG_VIDEO_IMX477) += imx477.o
+obj-$(CONFIG_VIDEO_IMX500) += imx500.o
+obj-$(CONFIG_VIDEO_IMX519) += imx519.o
+obj-$(CONFIG_VIDEO_IMX708) += imx708.o
 obj-$(CONFIG_VIDEO_IR_I2C) += ir-kbd-i2c.o
+obj-$(CONFIG_VIDEO_IRS1125) += irs1125.o
 obj-$(CONFIG_VIDEO_ISL7998X) += isl7998x.o
 obj-$(CONFIG_VIDEO_KS0127) += ks0127.o
 obj-$(CONFIG_VIDEO_LM3560) += lm3560.o
@@ -69,6 +78,7 @@ obj-$(CONFIG_VIDEO_MAX9271_LIB) += max9271.o
 obj-$(CONFIG_VIDEO_MAX9286) += max9286.o
 obj-$(CONFIG_VIDEO_MAX96714) += max96714.o
 obj-$(CONFIG_VIDEO_MAX96717) += max96717.o
+obj-$(CONFIG_VIDEO_MIRA220)	+= mira220.o
 obj-$(CONFIG_VIDEO_ML86V7667) += ml86v7667.o
 obj-$(CONFIG_VIDEO_MSP3400) += msp3400.o
 obj-$(CONFIG_VIDEO_MT9M001) += mt9m001.o
@@ -86,6 +96,7 @@ obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o
 obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o
 obj-$(CONFIG_VIDEO_OV13858) += ov13858.o
 obj-$(CONFIG_VIDEO_OV13B10) += ov13b10.o
+obj-$(CONFIG_VIDEO_OV2311) += ov2311.o
 obj-$(CONFIG_VIDEO_OV2640) += ov2640.o
 obj-$(CONFIG_VIDEO_OV2659) += ov2659.o
 obj-$(CONFIG_VIDEO_OV2680) += ov2680.o
diff --git a/drivers/media/i2c/ad5398_vcm.c b/drivers/media/i2c/ad5398_vcm.c
new file mode 100644
index 000000000000..649ff0b9e9c8
--- /dev/null
+++ b/drivers/media/i2c/ad5398_vcm.c
@@ -0,0 +1,340 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * AD5398 DAC driver for camera voice coil focus.
+ * Copyright (C) 2021 Raspberry Pi (Trading) Ltd.
+ *
+ * Based on AD5820 DAC driver by Nokia and TI.
+ *
+ * This driver uses the regulator framework notification hooks on the
+ * assumption that the VCM and sensor share a regulator. This means the VCM
+ * position will be restored when either the sensor or VCM subdevices are opened
+ * or powered up. The client can therefore choose to ignore the VCM subdevice,
+ * and the lens position will be as previously requested. Without that, there
+ * is a hard requirement to have the VCM subdevice open in order for the VCM
+ * to be powered and at the requested position.
+ */
+
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/gpio/consumer.h>
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+
+/* Register definitions */
+#define AD5398_POWER_DOWN		BIT(15)
+#define AD5398_DAC_SHIFT		4
+
+#define to_ad5398_device(sd)	container_of(sd, struct ad5398_device, subdev)
+
+struct ad5398_device {
+	struct v4l2_subdev subdev;
+	struct ad5398_platform_data *platform_data;
+	struct regulator *vana;
+	struct notifier_block nb;
+
+	struct v4l2_ctrl_handler ctrls;
+	u32 focus_absolute;
+
+	bool standby;
+};
+
+static int ad5398_write(struct ad5398_device *coil, u16 data)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&coil->subdev);
+	struct i2c_msg msg;
+	__be16 be_data;
+	int r;
+
+	if (!client->adapter)
+		return -ENODEV;
+
+	be_data = cpu_to_be16(data);
+	msg.addr  = client->addr;
+	msg.flags = 0;
+	msg.len   = 2;
+	msg.buf   = (u8 *)&be_data;
+
+	r = i2c_transfer(client->adapter, &msg, 1);
+	if (r < 0) {
+		dev_err(&client->dev, "write failed, error %d\n", r);
+		return r;
+	}
+
+	return 0;
+}
+
+/*
+ * Calculate status word and write it to the device based on current
+ * values of V4L2 controls. It is assumed that the stored V4L2 control
+ * values are properly limited and rounded.
+ */
+static int ad5398_update_hw(struct ad5398_device *coil)
+{
+	u16 status;
+
+	status = coil->focus_absolute << AD5398_DAC_SHIFT;
+
+	if (coil->standby)
+		status |= AD5398_POWER_DOWN;
+
+	return ad5398_write(coil, status);
+}
+
+/*
+ * Power handling
+ */
+static int ad5398_power_off(struct ad5398_device *coil)
+{
+	int ret = 0;
+
+	coil->standby = true;
+	ret = ad5398_update_hw(coil);
+
+	return ret;
+}
+
+static int ad5398_power_on(struct ad5398_device *coil)
+{
+	int ret;
+
+	/* Restore the hardware settings. */
+	coil->standby = false;
+	ret = ad5398_update_hw(coil);
+	if (ret)
+		goto fail;
+
+	return 0;
+
+fail:
+	coil->standby = true;
+
+	return ret;
+}
+
+/*
+ * V4L2 controls
+ */
+static int ad5398_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct ad5398_device *coil =
+		container_of(ctrl->handler, struct ad5398_device, ctrls);
+
+	switch (ctrl->id) {
+	case V4L2_CID_FOCUS_ABSOLUTE:
+		coil->focus_absolute = ctrl->val;
+		return ad5398_update_hw(coil);
+	}
+
+	return 0;
+}
+
+static const struct v4l2_ctrl_ops ad5398_ctrl_ops = {
+	.s_ctrl = ad5398_set_ctrl,
+};
+
+static int ad5398_init_controls(struct ad5398_device *coil)
+{
+	v4l2_ctrl_handler_init(&coil->ctrls, 1);
+
+	/*
+	 * V4L2_CID_FOCUS_ABSOLUTE
+	 *
+	 * Minimum current is 0 mA, maximum is 120 mA. Thus, 1 code is
+	 * equivalent to 120/1023 = 0.1173 mA. Nevertheless, we do not use [mA]
+	 * for focus position, because it is meaningless for user. Meaningful
+	 * would be to use focus distance or even its inverse, but since the
+	 * driver doesn't have sufficient knowledge to do the conversion, we
+	 * will just use abstract codes here. In any case, smaller value = focus
+	 * position farther from camera. The default zero value means focus at
+	 * infinity, and also least current consumption.
+	 */
+	v4l2_ctrl_new_std(&coil->ctrls, &ad5398_ctrl_ops,
+			  V4L2_CID_FOCUS_ABSOLUTE, 0, 1023, 1, 0);
+
+	if (coil->ctrls.error)
+		return coil->ctrls.error;
+
+	coil->focus_absolute = 0;
+
+	coil->subdev.ctrl_handler = &coil->ctrls;
+
+	return 0;
+}
+
+/*
+ * V4L2 subdev operations
+ */
+static int ad5398_registered(struct v4l2_subdev *subdev)
+{
+	struct ad5398_device *coil = to_ad5398_device(subdev);
+
+	return ad5398_init_controls(coil);
+}
+
+static int
+ad5398_set_power(struct v4l2_subdev *subdev, int on)
+{
+	struct ad5398_device *coil = to_ad5398_device(subdev);
+	int ret;
+
+	if (on)
+		ret = regulator_enable(coil->vana);
+	else
+		ret = regulator_disable(coil->vana);
+
+	return ret;
+}
+
+static int ad5398_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+	struct ad5398_device *coil = to_ad5398_device(sd);
+
+	return regulator_enable(coil->vana);
+}
+
+static int ad5398_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+	struct ad5398_device *coil = to_ad5398_device(sd);
+
+	return regulator_disable(coil->vana);
+}
+
+static const struct v4l2_subdev_core_ops ad5398_core_ops = {
+	.s_power = ad5398_set_power,
+};
+
+static const struct v4l2_subdev_ops ad5398_ops = {
+	.core = &ad5398_core_ops,
+};
+
+static const struct v4l2_subdev_internal_ops ad5398_internal_ops = {
+	.registered = ad5398_registered,
+	.open = ad5398_open,
+	.close = ad5398_close,
+};
+
+/*
+ * I2C driver
+ */
+static int __maybe_unused ad5398_suspend(struct device *dev)
+{
+	struct i2c_client *client = container_of(dev, struct i2c_client, dev);
+	struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+	struct ad5398_device *coil = to_ad5398_device(subdev);
+
+	return regulator_enable(coil->vana);
+}
+
+static int __maybe_unused ad5398_resume(struct device *dev)
+{
+	struct i2c_client *client = container_of(dev, struct i2c_client, dev);
+	struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+	struct ad5398_device *coil = to_ad5398_device(subdev);
+
+	return regulator_disable(coil->vana);
+}
+
+static int ad5398_regulator_notifier(struct notifier_block *nb,
+				     unsigned long event,
+				     void *ignored)
+{
+	struct ad5398_device *coil = container_of(nb, struct ad5398_device, nb);
+
+	if (event == REGULATOR_EVENT_ENABLE)
+		ad5398_power_on(coil);
+	else if (event == REGULATOR_EVENT_PRE_DISABLE)
+		ad5398_power_off(coil);
+
+	return NOTIFY_OK;
+}
+
+static int ad5398_probe(struct i2c_client *client)
+{
+	struct ad5398_device *coil;
+	int ret;
+
+	coil = devm_kzalloc(&client->dev, sizeof(*coil), GFP_KERNEL);
+	if (!coil)
+		return -ENOMEM;
+
+	coil->vana = devm_regulator_get(&client->dev, "VANA");
+	if (IS_ERR(coil->vana)) {
+		ret = PTR_ERR(coil->vana);
+		if (ret != -EPROBE_DEFER)
+			dev_err(&client->dev, "could not get regulator for vana\n");
+		return ret;
+	}
+
+	v4l2_i2c_subdev_init(&coil->subdev, client, &ad5398_ops);
+	coil->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+	coil->subdev.internal_ops = &ad5398_internal_ops;
+	coil->subdev.entity.function = MEDIA_ENT_F_LENS;
+	strscpy(coil->subdev.name, "ad5398 focus", sizeof(coil->subdev.name));
+
+	coil->nb.notifier_call = &ad5398_regulator_notifier;
+	ret = regulator_register_notifier(coil->vana, &coil->nb);
+	if (ret < 0)
+		return ret;
+
+	ret = media_entity_pads_init(&coil->subdev.entity, 0, NULL);
+	if (ret < 0)
+		goto cleanup2;
+
+	ret = v4l2_async_register_subdev(&coil->subdev);
+	if (ret < 0)
+		goto cleanup;
+
+	return ret;
+
+cleanup:
+	media_entity_cleanup(&coil->subdev.entity);
+cleanup2:
+	regulator_unregister_notifier(coil->vana, &coil->nb);
+	return ret;
+}
+
+static void ad5398_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *subdev = i2c_get_clientdata(client);
+	struct ad5398_device *coil = to_ad5398_device(subdev);
+
+	v4l2_async_unregister_subdev(&coil->subdev);
+	v4l2_ctrl_handler_free(&coil->ctrls);
+	media_entity_cleanup(&coil->subdev.entity);
+}
+
+static const struct i2c_device_id ad5398_id_table[] = {
+	{ "ad5398", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ad5398_id_table);
+
+static const struct of_device_id ad5398_of_table[] = {
+	{ .compatible = "adi,ad5398" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ad5398_of_table);
+
+static SIMPLE_DEV_PM_OPS(ad5398_pm, ad5398_suspend, ad5398_resume);
+
+static struct i2c_driver ad5398_i2c_driver = {
+	.driver		= {
+		.name	= "ad5398",
+		.pm	= &ad5398_pm,
+		.of_match_table = ad5398_of_table,
+	},
+	.probe		= ad5398_probe,
+	.remove		= ad5398_remove,
+	.id_table	= ad5398_id_table,
+};
+
+module_i2c_driver(ad5398_i2c_driver);
+
+MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>");
+MODULE_DESCRIPTION("AD5398 camera lens driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/i2c/adv7180.c b/drivers/media/i2c/adv7180.c
index 36e0f7a8c6d5..1666cd97e53e 100644
--- a/drivers/media/i2c/adv7180.c
+++ b/drivers/media/i2c/adv7180.c
@@ -189,6 +189,20 @@
 /* Initial number of frames to skip to avoid possible garbage */
 #define ADV7180_NUM_OF_SKIP_FRAMES       2
 
+enum adv7180_link_freq_idx {
+	INTERLACED_IDX,
+	I2P_IDX,
+};
+
+static const s64 adv7180_link_freqs[] = {
+	[INTERLACED_IDX] = 108000000,
+	[I2P_IDX] = 216000000,
+};
+
+static int dbg_input;
+module_param(dbg_input, int, 0644);
+MODULE_PARM_DESC(dbg_input, "Input number (0-31)");
+
 struct adv7180_state;
 
 #define ADV7180_FLAG_RESET_POWERED	BIT(0)
@@ -225,6 +239,7 @@ struct adv7180_state {
 	const struct adv7180_chip_info *chip_info;
 	enum v4l2_field		field;
 	bool			force_bt656_4;
+	struct v4l2_ctrl	*link_freq;
 };
 #define to_adv7180_sd(_ctrl) (&container_of(_ctrl->handler,		\
 					    struct adv7180_state,	\
@@ -403,10 +418,24 @@ static int adv7180_s_routing(struct v4l2_subdev *sd, u32 input,
 	return ret;
 }
 
+static void adv7180_check_input(struct v4l2_subdev *sd)
+{
+	struct adv7180_state *state = to_state(sd);
+
+	if (state->input != dbg_input)
+		if (adv7180_s_routing(sd, dbg_input, 0, 0))
+			/* Failed - reset dbg_input */
+			dbg_input = state->input;
+}
+
 static int adv7180_g_input_status(struct v4l2_subdev *sd, u32 *status)
 {
 	struct adv7180_state *state = to_state(sd);
-	int ret = mutex_lock_interruptible(&state->mutex);
+	int ret;
+
+	adv7180_check_input(sd);
+
+	ret = mutex_lock_interruptible(&state->mutex);
 	if (ret)
 		return ret;
 
@@ -432,7 +461,11 @@ static int adv7180_program_std(struct adv7180_state *state)
 static int adv7180_s_std(struct v4l2_subdev *sd, v4l2_std_id std)
 {
 	struct adv7180_state *state = to_state(sd);
-	int ret = mutex_lock_interruptible(&state->mutex);
+	int ret;
+
+	adv7180_check_input(sd);
+
+	ret = mutex_lock_interruptible(&state->mutex);
 
 	if (ret)
 		return ret;
@@ -454,6 +487,8 @@ static int adv7180_g_std(struct v4l2_subdev *sd, v4l2_std_id *norm)
 {
 	struct adv7180_state *state = to_state(sd);
 
+	adv7180_check_input(sd);
+
 	*norm = state->curr_norm;
 
 	return 0;
@@ -608,6 +643,9 @@ static int adv7180_s_ctrl(struct v4l2_ctrl *ctrl)
 
 	if (ret)
 		return ret;
+	if (ctrl->flags & V4L2_CTRL_FLAG_READ_ONLY)
+		goto unlock;
+
 	val = ctrl->val;
 	switch (ctrl->id) {
 	case V4L2_CID_BRIGHTNESS:
@@ -649,6 +687,7 @@ static int adv7180_s_ctrl(struct v4l2_ctrl *ctrl)
 		ret = -EINVAL;
 	}
 
+unlock:
 	mutex_unlock(&state->mutex);
 	return ret;
 }
@@ -669,7 +708,7 @@ static const struct v4l2_ctrl_config adv7180_ctrl_fast_switch = {
 
 static int adv7180_init_controls(struct adv7180_state *state)
 {
-	v4l2_ctrl_handler_init(&state->ctrl_hdl, 4);
+	v4l2_ctrl_handler_init(&state->ctrl_hdl, 5);
 
 	v4l2_ctrl_new_std(&state->ctrl_hdl, &adv7180_ctrl_ops,
 			  V4L2_CID_BRIGHTNESS, ADV7180_BRI_MIN,
@@ -695,6 +734,17 @@ static int adv7180_init_controls(struct adv7180_state *state)
 					     test_pattern_menu);
 	}
 
+	if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2) {
+		state->link_freq =
+			v4l2_ctrl_new_int_menu(&state->ctrl_hdl,
+					       &adv7180_ctrl_ops,
+					       V4L2_CID_LINK_FREQ,
+					       ARRAY_SIZE(adv7180_link_freqs) - 1,
+					       0, adv7180_link_freqs);
+		if (state->link_freq)
+			state->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+	}
+
 	state->sd.ctrl_handler = &state->ctrl_hdl;
 	if (state->ctrl_hdl.error) {
 		int err = state->ctrl_hdl.error;
@@ -715,10 +765,15 @@ static int adv7180_enum_mbus_code(struct v4l2_subdev *sd,
 				  struct v4l2_subdev_state *sd_state,
 				  struct v4l2_subdev_mbus_code_enum *code)
 {
+	struct adv7180_state *state = to_state(sd);
+
 	if (code->index != 0)
 		return -EINVAL;
 
-	code->code = MEDIA_BUS_FMT_UYVY8_2X8;
+	if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2)
+		code->code = MEDIA_BUS_FMT_UYVY8_1X16;
+	else
+		code->code = MEDIA_BUS_FMT_UYVY8_2X8;
 
 	return 0;
 }
@@ -728,7 +783,10 @@ static int adv7180_mbus_fmt(struct v4l2_subdev *sd,
 {
 	struct adv7180_state *state = to_state(sd);
 
-	fmt->code = MEDIA_BUS_FMT_UYVY8_2X8;
+	if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2)
+		fmt->code = MEDIA_BUS_FMT_UYVY8_1X16;
+	else
+		fmt->code = MEDIA_BUS_FMT_UYVY8_2X8;
 	fmt->colorspace = V4L2_COLORSPACE_SMPTE170M;
 	fmt->width = 720;
 	fmt->height = state->curr_norm & V4L2_STD_525_60 ? 480 : 576;
@@ -814,7 +872,16 @@ static int adv7180_set_pad_format(struct v4l2_subdev *sd,
 	ret = adv7180_mbus_fmt(sd,  &format->format);
 
 	if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
-		state->field = format->format.field;
+		if (state->field != format->format.field) {
+			state->field = format->format.field;
+			adv7180_set_power(state, false);
+			adv7180_set_field_mode(state);
+			adv7180_set_power(state, true);
+			if (state->chip_info->flags & ADV7180_FLAG_MIPI_CSI2)
+				__v4l2_ctrl_s_ctrl(state->link_freq,
+						   (state->field == V4L2_FIELD_NONE) ?
+						   I2P_IDX : INTERLACED_IDX);
+		}
 	} else {
 		framefmt = v4l2_subdev_state_get_format(sd_state, 0);
 		*framefmt = format->format;
@@ -897,6 +964,8 @@ static int adv7180_s_stream(struct v4l2_subdev *sd, int enable)
 		return 0;
 	}
 
+	adv7180_check_input(sd);
+
 	/* Must wait until querystd released the lock */
 	ret = mutex_lock_interruptible(&state->mutex);
 	if (ret)
@@ -1348,6 +1417,7 @@ static const struct adv7180_chip_info adv7282_m_info = {
 		BIT(ADV7182_INPUT_SVIDEO_AIN1_AIN2) |
 		BIT(ADV7182_INPUT_SVIDEO_AIN3_AIN4) |
 		BIT(ADV7182_INPUT_SVIDEO_AIN7_AIN8) |
+		BIT(ADV7182_INPUT_YPRPB_AIN1_AIN2_AIN3) |
 		BIT(ADV7182_INPUT_DIFF_CVBS_AIN1_AIN2) |
 		BIT(ADV7182_INPUT_DIFF_CVBS_AIN3_AIN4) |
 		BIT(ADV7182_INPUT_DIFF_CVBS_AIN7_AIN8),
@@ -1359,6 +1429,7 @@ static const struct adv7180_chip_info adv7282_m_info = {
 static int init_device(struct adv7180_state *state)
 {
 	int ret;
+	int i;
 
 	mutex_lock(&state->mutex);
 
@@ -1406,6 +1477,18 @@ static int init_device(struct adv7180_state *state)
 			goto out_unlock;
 	}
 
+	/* Select first valid input */
+	for (i = 0; i < 32; i++) {
+		if (BIT(i) & state->chip_info->valid_input_mask) {
+			ret = state->chip_info->select_input(state, i);
+
+			if (ret == 0) {
+				state->input = i;
+				break;
+			}
+		}
+	}
+
 out_unlock:
 	mutex_unlock(&state->mutex);
 
diff --git a/drivers/media/i2c/arducam-pivariety.c b/drivers/media/i2c/arducam-pivariety.c
new file mode 100644
index 000000000000..945c462662db
--- /dev/null
+++ b/drivers/media/i2c/arducam-pivariety.c
@@ -0,0 +1,1475 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A V4L2 driver for Arducam Pivariety Cameras
+ * Copyright (C) 2022 Arducam Technology co., Ltd.
+ *
+ * Based on Sony IMX219 camera driver
+ * Copyright (C) 2019, Raspberry Pi (Trading) Ltd
+ *
+ * I2C read and write method is taken from the OV9281 driver
+ * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fwnode.h>
+#include "arducam-pivariety.h"
+
+static int debug;
+module_param(debug, int, 0644);
+
+/* regulator supplies */
+static const char * const pivariety_supply_name[] = {
+	/* Supplies can be enabled in any order */
+	"VANA",  /* Analog (2.8V) supply */
+	"VDIG",  /* Digital Core (1.8V) supply */
+	"VDDL",  /* IF (1.2V) supply */
+};
+
+/* The supported raw formats. */
+static const u32 codes[] = {
+	MEDIA_BUS_FMT_SBGGR8_1X8,
+	MEDIA_BUS_FMT_SGBRG8_1X8,
+	MEDIA_BUS_FMT_SGRBG8_1X8,
+	MEDIA_BUS_FMT_SRGGB8_1X8,
+	MEDIA_BUS_FMT_Y8_1X8,
+
+	MEDIA_BUS_FMT_SBGGR10_1X10,
+	MEDIA_BUS_FMT_SGBRG10_1X10,
+	MEDIA_BUS_FMT_SGRBG10_1X10,
+	MEDIA_BUS_FMT_SRGGB10_1X10,
+	MEDIA_BUS_FMT_Y10_1X10,
+
+	MEDIA_BUS_FMT_SBGGR12_1X12,
+	MEDIA_BUS_FMT_SGBRG12_1X12,
+	MEDIA_BUS_FMT_SGRBG12_1X12,
+	MEDIA_BUS_FMT_SRGGB12_1X12,
+	MEDIA_BUS_FMT_Y12_1X12,
+};
+
+#define ARDUCAM_NUM_SUPPLIES ARRAY_SIZE(pivariety_supply_name)
+
+#define ARDUCAM_XCLR_MIN_DELAY_US	10000
+#define ARDUCAM_XCLR_DELAY_RANGE_US	1000
+
+#define MAX_CTRLS 32
+
+struct pivariety {
+	struct v4l2_subdev sd;
+	struct media_pad pad;
+
+	struct v4l2_mbus_config_mipi_csi2 bus;
+	struct clk *xclk;
+	u32 xclk_freq;
+
+	struct gpio_desc *reset_gpio;
+	struct regulator_bulk_data supplies[ARDUCAM_NUM_SUPPLIES];
+
+	struct arducam_format *supported_formats;
+	int num_supported_formats;
+	int current_format_idx;
+	int current_resolution_idx;
+	int lanes;
+	int bayer_order_volatile;
+	bool wait_until_free;
+
+	struct v4l2_ctrl_handler ctrl_handler;
+	struct v4l2_ctrl *ctrls[MAX_CTRLS];
+	/* V4L2 Controls */
+	struct v4l2_ctrl *vflip;
+	struct v4l2_ctrl *hflip;
+
+	struct v4l2_rect crop;
+	/*
+	 * Mutex for serialized access:
+	 * Protect sensor module set pad format and start/stop streaming safely.
+	 */
+	struct mutex mutex;
+
+	/* Streaming on/off */
+	bool streaming;
+};
+
+static inline struct pivariety *to_pivariety(struct v4l2_subdev *_sd)
+{
+	return container_of(_sd, struct pivariety, sd);
+}
+
+/* Write registers up to 4 at a time */
+static int pivariety_write_reg(struct i2c_client *client, u16 reg, u32 val)
+{
+	unsigned int len = sizeof(u32);
+	u32 buf_i, val_i = 0;
+	u8 buf[6];
+	u8 *val_p;
+	__be32 val_be;
+
+	buf[0] = reg >> 8;
+	buf[1] = reg & 0xff;
+
+	val_be = cpu_to_be32(val);
+	val_p = (u8 *)&val_be;
+	buf_i = 2;
+
+	while (val_i < 4)
+		buf[buf_i++] = val_p[val_i++];
+
+	if (i2c_master_send(client, buf, len + 2) != len + 2)
+		return -EIO;
+
+	return 0;
+}
+
+/* Read registers up to 4 at a time */
+static int pivariety_read_reg(struct i2c_client *client, u16 reg, u32 *val)
+{
+	struct i2c_msg msgs[2];
+	unsigned int len = sizeof(u32);
+	u8 *data_be_p;
+	__be32 data_be = 0;
+	__be16 reg_addr_be = cpu_to_be16(reg);
+	int ret;
+
+	data_be_p = (u8 *)&data_be;
+	/* Write register address */
+	msgs[0].addr = client->addr;
+	msgs[0].flags = 0;
+	msgs[0].len = 2;
+	msgs[0].buf = (u8 *)&reg_addr_be;
+
+	/* Read data from register */
+	msgs[1].addr = client->addr;
+	msgs[1].flags = I2C_M_RD;
+	msgs[1].len = len;
+	msgs[1].buf = data_be_p;
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret != ARRAY_SIZE(msgs))
+		return -EIO;
+
+	*val = be32_to_cpu(data_be);
+
+	return 0;
+}
+
+static int
+pivariety_read(struct pivariety *pivariety, u16 addr, u32 *value)
+{
+	struct v4l2_subdev *sd = &pivariety->sd;
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	int ret, count = 0;
+
+	while (count++ < I2C_READ_RETRY_COUNT) {
+		ret = pivariety_read_reg(client, addr, value);
+		if (!ret) {
+			v4l2_dbg(2, debug, sd, "%s: 0x%02x 0x%04x\n",
+				 __func__, addr, *value);
+			return ret;
+		}
+	}
+
+	v4l2_err(sd, "%s: Reading register 0x%02x failed\n",
+		 __func__, addr);
+
+	return ret;
+}
+
+static int pivariety_write(struct pivariety *pivariety, u16 addr, u32 value)
+{
+	struct v4l2_subdev *sd = &pivariety->sd;
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	int ret, count = 0;
+
+	while (count++ < I2C_WRITE_RETRY_COUNT) {
+		ret = pivariety_write_reg(client, addr, value);
+		if (!ret)
+			return ret;
+	}
+
+	v4l2_err(sd, "%s: Write 0x%04x to register 0x%02x failed\n",
+		 __func__, value, addr);
+
+	return ret;
+}
+
+static int wait_for_free(struct pivariety *pivariety, int interval)
+{
+	u32 value;
+	u32 count = 0;
+
+	while (count++ < (1000 / interval)) {
+		int ret = pivariety_read(pivariety, SYSTEM_IDLE_REG, &value);
+
+		if (!ret && !value)
+			break;
+		msleep(interval);
+	}
+
+	v4l2_dbg(2, debug, &pivariety->sd, "%s: End wait, Count: %d.\n",
+		 __func__, count);
+
+	return 0;
+}
+
+static int is_raw(int pixformat)
+{
+	return pixformat >= 0x28 && pixformat <= 0x2D;
+}
+
+static u32 bayer_to_mbus_code(int data_type, int bayer_order)
+{
+	const u32 depth8[] = {
+		MEDIA_BUS_FMT_SBGGR8_1X8,
+		MEDIA_BUS_FMT_SGBRG8_1X8,
+		MEDIA_BUS_FMT_SGRBG8_1X8,
+		MEDIA_BUS_FMT_SRGGB8_1X8,
+		MEDIA_BUS_FMT_Y8_1X8,
+	};
+
+	const u32 depth10[] = {
+		MEDIA_BUS_FMT_SBGGR10_1X10,
+		MEDIA_BUS_FMT_SGBRG10_1X10,
+		MEDIA_BUS_FMT_SGRBG10_1X10,
+		MEDIA_BUS_FMT_SRGGB10_1X10,
+		MEDIA_BUS_FMT_Y10_1X10,
+	};
+
+	const u32 depth12[] = {
+		MEDIA_BUS_FMT_SBGGR12_1X12,
+		MEDIA_BUS_FMT_SGBRG12_1X12,
+		MEDIA_BUS_FMT_SGRBG12_1X12,
+		MEDIA_BUS_FMT_SRGGB12_1X12,
+		MEDIA_BUS_FMT_Y12_1X12,
+	};
+
+	if (bayer_order < 0 || bayer_order > 4)
+		return 0;
+
+	switch (data_type) {
+	case IMAGE_DT_RAW8:
+		return depth8[bayer_order];
+	case IMAGE_DT_RAW10:
+		return depth10[bayer_order];
+	case IMAGE_DT_RAW12:
+		return depth12[bayer_order];
+	}
+
+	return 0;
+}
+
+static u32 yuv422_to_mbus_code(int data_type, int order)
+{
+	const u32 depth8[] = {
+		MEDIA_BUS_FMT_YUYV8_1X16,
+		MEDIA_BUS_FMT_YVYU8_1X16,
+		MEDIA_BUS_FMT_UYVY8_1X16,
+		MEDIA_BUS_FMT_VYUY8_1X16,
+	};
+
+	const u32 depth10[] = {
+		MEDIA_BUS_FMT_YUYV10_1X20,
+		MEDIA_BUS_FMT_YVYU10_1X20,
+		MEDIA_BUS_FMT_UYVY10_1X20,
+		MEDIA_BUS_FMT_VYUY10_1X20,
+	};
+
+	if (order < 0 || order > 3)
+		return 0;
+
+	switch (data_type) {
+	case IMAGE_DT_YUV422_8:
+		return depth8[order];
+	case IMAGE_DT_YUV422_10:
+		return depth10[order];
+	}
+
+	return 0;
+}
+
+static u32 data_type_to_mbus_code(int data_type, int bayer_order)
+{
+	if (is_raw(data_type))
+		return bayer_to_mbus_code(data_type, bayer_order);
+
+	switch (data_type) {
+	case IMAGE_DT_YUV422_8:
+	case IMAGE_DT_YUV422_10:
+		return yuv422_to_mbus_code(data_type, bayer_order);
+	case IMAGE_DT_RGB565:
+		return MEDIA_BUS_FMT_RGB565_2X8_LE;
+	case IMAGE_DT_RGB888:
+		return MEDIA_BUS_FMT_RGB888_1X24;
+	}
+
+	return 0;
+}
+
+/* Get bayer order based on flip setting. */
+static u32 pivariety_get_format_code(struct pivariety *pivariety,
+				     struct arducam_format *format)
+{
+	unsigned int order, origin_order;
+
+	lockdep_assert_held(&pivariety->mutex);
+
+	/*
+	 * Only the bayer format needs to transform the format.
+	 */
+	if (!is_raw(format->data_type) ||
+	    !pivariety->bayer_order_volatile ||
+	    format->bayer_order == BAYER_ORDER_GRAY)
+		return data_type_to_mbus_code(format->data_type,
+					      format->bayer_order);
+
+	order = format->bayer_order;
+
+	origin_order = order;
+
+	order = (pivariety->hflip && pivariety->hflip->val ? order ^ 1 : order);
+	order = (pivariety->vflip && pivariety->vflip->val ? order ^ 2 : order);
+
+	v4l2_dbg(1, debug, &pivariety->sd, "%s: before: %d, after: %d.\n",
+		 __func__, origin_order, order);
+
+	return data_type_to_mbus_code(format->data_type, order);
+}
+
+/* Power/clock management functions */
+static int pivariety_power_on(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct pivariety *pivariety = to_pivariety(sd);
+	int ret;
+
+	ret = regulator_bulk_enable(ARDUCAM_NUM_SUPPLIES,
+				    pivariety->supplies);
+	if (ret) {
+		dev_err(dev, "%s: failed to enable regulators\n",
+			__func__);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(pivariety->xclk);
+	if (ret) {
+		dev_err(dev, "%s: failed to enable clock\n",
+			__func__);
+		goto reg_off;
+	}
+
+	gpiod_set_value_cansleep(pivariety->reset_gpio, 1);
+	usleep_range(ARDUCAM_XCLR_MIN_DELAY_US,
+		     ARDUCAM_XCLR_MIN_DELAY_US + ARDUCAM_XCLR_DELAY_RANGE_US);
+
+	return 0;
+
+reg_off:
+	regulator_bulk_disable(ARDUCAM_NUM_SUPPLIES, pivariety->supplies);
+
+	return ret;
+}
+
+static int pivariety_power_off(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct pivariety *pivariety = to_pivariety(sd);
+
+	gpiod_set_value_cansleep(pivariety->reset_gpio, 0);
+	regulator_bulk_disable(ARDUCAM_NUM_SUPPLIES, pivariety->supplies);
+	clk_disable_unprepare(pivariety->xclk);
+
+	return 0;
+}
+
+static int pivariety_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+	struct pivariety *pivariety = to_pivariety(sd);
+	struct v4l2_mbus_framefmt *try_fmt =
+		v4l2_subdev_state_get_format(fh->state, 0);
+	struct arducam_format *def_fmt = &pivariety->supported_formats[0];
+
+	/* Initialize try_fmt */
+	try_fmt->width = def_fmt->resolution_set->width;
+	try_fmt->height = def_fmt->resolution_set->height;
+	try_fmt->code = def_fmt->mbus_code;
+	try_fmt->field = V4L2_FIELD_NONE;
+
+	return 0;
+}
+
+static int pivariety_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	int ret, i;
+	struct pivariety *pivariety =
+		container_of(ctrl->handler, struct pivariety,
+			     ctrl_handler);
+	struct arducam_format *supported_fmts = pivariety->supported_formats;
+	int num_supported_formats = pivariety->num_supported_formats;
+
+	v4l2_dbg(3, debug, &pivariety->sd, "%s: cid = (0x%X), value = (%d).\n",
+		 __func__, ctrl->id, ctrl->val);
+
+	ret = pivariety_write(pivariety, CTRL_ID_REG, ctrl->id);
+	ret += pivariety_write(pivariety, CTRL_VALUE_REG, ctrl->val);
+	if (ret < 0)
+		return -EINVAL;
+
+	/* When flip is set, modify all bayer formats */
+	if (ctrl->id == V4L2_CID_VFLIP || ctrl->id == V4L2_CID_HFLIP) {
+		for (i = 0; i < num_supported_formats; i++) {
+			supported_fmts[i].mbus_code =
+				pivariety_get_format_code(pivariety,
+							  &supported_fmts[i]);
+		}
+	}
+
+	/*
+	 * When starting streaming, controls are set in batches,
+	 * and the short interval will cause some controls to be unsuccessfully
+	 * set.
+	 */
+	if (pivariety->wait_until_free)
+		wait_for_free(pivariety, 1);
+	else
+		usleep_range(200, 210);
+
+	return 0;
+}
+
+static const struct v4l2_ctrl_ops pivariety_ctrl_ops = {
+	.s_ctrl = pivariety_s_ctrl,
+};
+
+static int pivariety_enum_mbus_code(struct v4l2_subdev *sd,
+				    struct v4l2_subdev_state *sd_state,
+				    struct v4l2_subdev_mbus_code_enum *code)
+{
+	struct pivariety *pivariety = to_pivariety(sd);
+	struct arducam_format *supported_formats = pivariety->supported_formats;
+	int num_supported_formats = pivariety->num_supported_formats;
+
+	v4l2_dbg(1, debug, sd, "%s: index = (%d)\n", __func__, code->index);
+
+	if (code->index >= num_supported_formats)
+		return -EINVAL;
+
+	code->code = supported_formats[code->index].mbus_code;
+
+	return 0;
+}
+
+static int pivariety_enum_framesizes(struct v4l2_subdev *sd,
+				     struct v4l2_subdev_state *sd_state,
+				     struct v4l2_subdev_frame_size_enum *fse)
+{
+	int i;
+	struct pivariety *pivariety = to_pivariety(sd);
+	struct arducam_format *supported_formats = pivariety->supported_formats;
+	int num_supported_formats = pivariety->num_supported_formats;
+	struct arducam_format *format;
+	struct arducam_resolution *resolution;
+
+	v4l2_dbg(1, debug, sd, "%s: code = (0x%X), index = (%d)\n",
+		 __func__, fse->code, fse->index);
+
+	for (i = 0; i < num_supported_formats; i++) {
+		format = &supported_formats[i];
+		if (fse->code == format->mbus_code) {
+			if (fse->index >= format->num_resolution_set)
+				return -EINVAL;
+
+			resolution = &format->resolution_set[fse->index];
+			fse->min_width = resolution->width;
+			fse->max_width = resolution->width;
+			fse->min_height = resolution->height;
+			fse->max_height = resolution->height;
+
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int pivariety_get_fmt(struct v4l2_subdev *sd,
+			     struct v4l2_subdev_state *sd_state,
+			     struct v4l2_subdev_format *format)
+{
+	struct pivariety *pivariety = to_pivariety(sd);
+	struct arducam_format *current_format;
+	struct v4l2_mbus_framefmt *fmt = &format->format;
+	int cur_res_idx;
+
+	if (format->pad != 0)
+		return -EINVAL;
+
+	mutex_lock(&pivariety->mutex);
+
+	current_format =
+		&pivariety->supported_formats[pivariety->current_format_idx];
+	cur_res_idx = pivariety->current_resolution_idx;
+	format->format.width =
+		current_format->resolution_set[cur_res_idx].width;
+	format->format.height =
+		current_format->resolution_set[cur_res_idx].height;
+	format->format.code = current_format->mbus_code;
+	format->format.field = V4L2_FIELD_NONE;
+	fmt->colorspace = V4L2_COLORSPACE_RAW;
+	fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace);
+	fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true,
+							  fmt->colorspace,
+							  fmt->ycbcr_enc);
+	fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace);
+
+	v4l2_dbg(1, debug, sd, "%s: width: (%d) height: (%d) code: (0x%X)\n",
+		 __func__, format->format.width, format->format.height,
+		 format->format.code);
+
+	mutex_unlock(&pivariety->mutex);
+	return 0;
+}
+
+static int pivariety_get_fmt_idx_by_code(struct pivariety *pivariety,
+					 u32 mbus_code)
+{
+	int i;
+	u32 data_type;
+	struct arducam_format *formats = pivariety->supported_formats;
+
+	for (i = 0; i < pivariety->num_supported_formats; i++) {
+		if (formats[i].mbus_code == mbus_code)
+			return i;
+	}
+
+	/*
+	 * If the specified format is not found in the list of supported
+	 * formats, try to find a format of the same data type.
+	 */
+	for (i = 0; i < ARRAY_SIZE(codes); i++)
+		if (codes[i] == mbus_code)
+			break;
+
+	if (i >= ARRAY_SIZE(codes))
+		return -EINVAL;
+
+	data_type = i / 5 + IMAGE_DT_RAW8;
+
+	for (i = 0; i < pivariety->num_supported_formats; i++) {
+		if (formats[i].data_type == data_type)
+			return i;
+	}
+
+	return -EINVAL;
+}
+
+static struct v4l2_ctrl *get_control(struct pivariety *pivariety,
+				     u32 id)
+{
+	int index = 0;
+
+	while (index < MAX_CTRLS && pivariety->ctrls[index]) {
+		if (pivariety->ctrls[index]->id == id)
+			return pivariety->ctrls[index];
+		index++;
+	}
+
+	return NULL;
+}
+
+static int update_control(struct pivariety *pivariety, u32 id)
+{
+	struct v4l2_subdev *sd = &pivariety->sd;
+	struct v4l2_ctrl *ctrl;
+	u32 min, max, step, def, id2;
+	int ret = 0;
+
+	pivariety_write(pivariety, CTRL_ID_REG, id);
+	pivariety_read(pivariety, CTRL_ID_REG, &id2);
+
+	v4l2_dbg(1, debug, sd, "%s: Write ID: 0x%08X Read ID: 0x%08X\n",
+		 __func__, id, id2);
+
+	pivariety_write(pivariety, CTRL_VALUE_REG, 0);
+	wait_for_free(pivariety, 1);
+
+	ret += pivariety_read(pivariety, CTRL_MAX_REG, &max);
+	ret += pivariety_read(pivariety, CTRL_MIN_REG, &min);
+	ret += pivariety_read(pivariety, CTRL_DEF_REG, &def);
+	ret += pivariety_read(pivariety, CTRL_STEP_REG, &step);
+
+	if (ret < 0)
+		goto err;
+
+	if (id == NO_DATA_AVAILABLE || max == NO_DATA_AVAILABLE ||
+	    min == NO_DATA_AVAILABLE || def == NO_DATA_AVAILABLE ||
+	    step == NO_DATA_AVAILABLE)
+		goto err;
+
+	v4l2_dbg(1, debug, sd, "%s: min: %d, max: %d, step: %d, def: %d\n",
+		 __func__, min, max, step, def);
+
+	ctrl = get_control(pivariety, id);
+	return __v4l2_ctrl_modify_range(ctrl, min, max, step, def);
+
+err:
+	return -EINVAL;
+}
+
+static int update_controls(struct pivariety *pivariety)
+{
+	int ret = 0;
+	int index = 0;
+
+	wait_for_free(pivariety, 5);
+
+	while (index < MAX_CTRLS && pivariety->ctrls[index]) {
+		ret += update_control(pivariety, pivariety->ctrls[index]->id);
+		index++;
+	}
+
+	return ret;
+}
+
+static int pivariety_set_fmt(struct v4l2_subdev *sd,
+			     struct v4l2_subdev_state *sd_state,
+			     struct v4l2_subdev_format *format)
+{
+	int i, j;
+	struct pivariety *pivariety = to_pivariety(sd);
+	struct arducam_format *supported_formats = pivariety->supported_formats;
+
+	if (format->pad != 0)
+		return -EINVAL;
+
+	mutex_lock(&pivariety->mutex);
+
+	format->format.colorspace = V4L2_COLORSPACE_RAW;
+	format->format.field = V4L2_FIELD_NONE;
+
+	v4l2_dbg(1, debug, sd, "%s: code: 0x%X, width: %d, height: %d\n",
+		 __func__, format->format.code, format->format.width,
+		 format->format.height);
+
+	i = pivariety_get_fmt_idx_by_code(pivariety, format->format.code);
+	if (i < 0)
+		i = 0;
+
+	format->format.code = supported_formats[i].mbus_code;
+
+	for (j = 0; j < supported_formats[i].num_resolution_set; j++) {
+		if (supported_formats[i].resolution_set[j].width ==
+						format->format.width &&
+			supported_formats[i].resolution_set[j].height ==
+						format->format.height) {
+			v4l2_dbg(1, debug, sd,
+				 "%s: format match.\n", __func__);
+			v4l2_dbg(1, debug, sd,
+				 "%s: set format to device: %d %d.\n",
+				 __func__, supported_formats[i].index, j);
+
+			pivariety_write(pivariety, PIXFORMAT_INDEX_REG,
+					supported_formats[i].index);
+			pivariety_write(pivariety, RESOLUTION_INDEX_REG, j);
+
+			pivariety->current_format_idx = i;
+			pivariety->current_resolution_idx = j;
+
+			update_controls(pivariety);
+
+			goto unlock;
+		}
+	}
+
+	format->format.width = supported_formats[i].resolution_set[0].width;
+	format->format.height = supported_formats[i].resolution_set[0].height;
+
+	pivariety_write(pivariety, PIXFORMAT_INDEX_REG,
+			supported_formats[i].index);
+	pivariety_write(pivariety, RESOLUTION_INDEX_REG, 0);
+
+	pivariety->current_format_idx = i;
+	pivariety->current_resolution_idx = 0;
+	update_controls(pivariety);
+
+unlock:
+
+	mutex_unlock(&pivariety->mutex);
+
+	return 0;
+}
+
+/* Start streaming */
+static int pivariety_start_streaming(struct pivariety *pivariety)
+{
+	int ret;
+
+	/* set stream on register */
+	ret = pivariety_write(pivariety, MODE_SELECT_REG,
+			      ARDUCAM_MODE_STREAMING);
+
+	if (ret)
+		return ret;
+
+	wait_for_free(pivariety, 2);
+
+	/*
+	 * When starting streaming, controls are set in batches,
+	 * and the short interval will cause some controls to be unsuccessfully
+	 * set.
+	 */
+	pivariety->wait_until_free = true;
+	/* Apply customized values from user */
+	ret =  __v4l2_ctrl_handler_setup(pivariety->sd.ctrl_handler);
+
+	pivariety->wait_until_free = false;
+	if (ret)
+		return ret;
+
+	wait_for_free(pivariety, 2);
+
+	return ret;
+}
+
+static int pivariety_read_sel(struct pivariety *pivariety,
+			      struct v4l2_rect *rect)
+{
+	int ret = 0;
+
+	ret += pivariety_read(pivariety, IPC_SEL_TOP_REG, &rect->top);
+	ret += pivariety_read(pivariety, IPC_SEL_LEFT_REG, &rect->left);
+	ret += pivariety_read(pivariety, IPC_SEL_WIDTH_REG, &rect->width);
+	ret += pivariety_read(pivariety, IPC_SEL_HEIGHT_REG, &rect->height);
+
+	if (ret || rect->top == NO_DATA_AVAILABLE ||
+	    rect->left == NO_DATA_AVAILABLE ||
+	    rect->width == NO_DATA_AVAILABLE ||
+	    rect->height == NO_DATA_AVAILABLE) {
+		v4l2_err(&pivariety->sd, "%s: Failed to read selection.\n",
+			 __func__);
+		return -EINVAL;
+		}
+
+	return 0;
+}
+
+static const struct v4l2_rect *
+__pivariety_get_pad_crop(struct pivariety *pivariety,
+			 struct v4l2_subdev_state *sd_state,
+			 unsigned int pad,
+			 enum v4l2_subdev_format_whence which)
+{
+	int ret;
+
+	switch (which) {
+	case V4L2_SUBDEV_FORMAT_TRY:
+		return v4l2_subdev_state_get_crop(sd_state, pad);
+	case V4L2_SUBDEV_FORMAT_ACTIVE:
+		ret = pivariety_read_sel(pivariety, &pivariety->crop);
+		if (ret)
+			return NULL;
+		return &pivariety->crop;
+	}
+
+	return NULL;
+}
+
+static int pivariety_get_selection(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *sd_state,
+				   struct v4l2_subdev_selection *sel)
+{
+	int ret = 0;
+	struct v4l2_rect rect;
+	struct pivariety *pivariety = to_pivariety(sd);
+
+	ret = pivariety_write(pivariety, IPC_SEL_TARGET_REG, sel->target);
+	if (ret) {
+		v4l2_err(sd, "%s: Write register 0x%02x failed\n",
+			 __func__, IPC_SEL_TARGET_REG);
+		return -EINVAL;
+	}
+
+	wait_for_free(pivariety, 2);
+
+	switch (sel->target) {
+	case V4L2_SEL_TGT_CROP: {
+		mutex_lock(&pivariety->mutex);
+		sel->r = *__pivariety_get_pad_crop(pivariety, sd_state,
+						   sel->pad,
+						   sel->which);
+		mutex_unlock(&pivariety->mutex);
+
+		return 0;
+	}
+
+	case V4L2_SEL_TGT_NATIVE_SIZE:
+	case V4L2_SEL_TGT_CROP_DEFAULT:
+	case V4L2_SEL_TGT_CROP_BOUNDS:
+		ret = pivariety_read_sel(pivariety, &rect);
+		if (ret)
+			return -EINVAL;
+
+		sel->r = rect;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+/* Stop streaming */
+static int pivariety_stop_streaming(struct pivariety *pivariety)
+{
+	int ret;
+
+	/* set stream off register */
+	ret = pivariety_write(pivariety, MODE_SELECT_REG, ARDUCAM_MODE_STANDBY);
+	if (ret)
+		v4l2_err(&pivariety->sd, "%s failed to set stream\n", __func__);
+
+	/*
+	 * Return success even if it was an error, as there is nothing the
+	 * caller can do about it.
+	 */
+	return 0;
+}
+
+static int pivariety_set_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct pivariety *pivariety = to_pivariety(sd);
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	int ret = 0;
+
+	mutex_lock(&pivariety->mutex);
+	if (pivariety->streaming == enable) {
+		mutex_unlock(&pivariety->mutex);
+		return 0;
+	}
+
+	if (enable) {
+		ret = pm_runtime_get_sync(&client->dev);
+		if (ret < 0) {
+			pm_runtime_put_noidle(&client->dev);
+			goto err_unlock;
+		}
+
+		/*
+		 * Apply default & customized values
+		 * and then start streaming.
+		 */
+		ret = pivariety_start_streaming(pivariety);
+		if (ret)
+			goto err_rpm_put;
+	} else {
+		pivariety_stop_streaming(pivariety);
+		pm_runtime_put(&client->dev);
+	}
+
+	pivariety->streaming = enable;
+
+	/*
+	 * vflip and hflip cannot change during streaming
+	 * Pivariety may not implement flip control.
+	 */
+	if (pivariety->vflip)
+		__v4l2_ctrl_grab(pivariety->vflip, enable);
+
+	if (pivariety->hflip)
+		__v4l2_ctrl_grab(pivariety->hflip, enable);
+
+	mutex_unlock(&pivariety->mutex);
+
+	return ret;
+
+err_rpm_put:
+	pm_runtime_put(&client->dev);
+err_unlock:
+	mutex_unlock(&pivariety->mutex);
+
+	return ret;
+}
+
+static int __maybe_unused pivariety_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct pivariety *pivariety = to_pivariety(sd);
+
+	if (pivariety->streaming)
+		pivariety_stop_streaming(pivariety);
+
+	return 0;
+}
+
+static int __maybe_unused pivariety_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct pivariety *pivariety = to_pivariety(sd);
+	int ret;
+
+	if (pivariety->streaming) {
+		ret = pivariety_start_streaming(pivariety);
+		if (ret)
+			goto error;
+	}
+
+	return 0;
+
+error:
+	pivariety_stop_streaming(pivariety);
+	pivariety->streaming = 0;
+	return ret;
+}
+
+static int pivariety_get_regulators(struct pivariety *pivariety)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&pivariety->sd);
+	int i;
+
+	for (i = 0; i < ARDUCAM_NUM_SUPPLIES; i++)
+		pivariety->supplies[i].supply = pivariety_supply_name[i];
+
+	return devm_regulator_bulk_get(&client->dev,
+				       ARDUCAM_NUM_SUPPLIES,
+				       pivariety->supplies);
+}
+
+static int pivariety_get_mbus_config(struct v4l2_subdev *sd, unsigned int pad,
+				     struct v4l2_mbus_config *cfg)
+{
+	struct pivariety *pivariety = to_pivariety(sd);
+
+	if (pivariety->lanes > pivariety->bus.num_data_lanes)
+		return -EINVAL;
+
+	cfg->type = V4L2_MBUS_CSI2_DPHY;
+	cfg->bus.mipi_csi2.flags = pivariety->bus.flags;
+	cfg->bus.mipi_csi2.num_data_lanes = pivariety->lanes;
+
+	return 0;
+}
+
+static const struct v4l2_subdev_core_ops pivariety_core_ops = {
+	.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_video_ops pivariety_video_ops = {
+	.s_stream = pivariety_set_stream,
+};
+
+static const struct v4l2_subdev_pad_ops pivariety_pad_ops = {
+	.enum_mbus_code = pivariety_enum_mbus_code,
+	.get_fmt = pivariety_get_fmt,
+	.set_fmt = pivariety_set_fmt,
+	.enum_frame_size = pivariety_enum_framesizes,
+	.get_selection = pivariety_get_selection,
+	.get_mbus_config = pivariety_get_mbus_config,
+};
+
+static const struct v4l2_subdev_ops pivariety_subdev_ops = {
+	.core = &pivariety_core_ops,
+	.video = &pivariety_video_ops,
+	.pad = &pivariety_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops pivariety_internal_ops = {
+	.open = pivariety_open,
+};
+
+static void pivariety_free_controls(struct pivariety *pivariety)
+{
+	v4l2_ctrl_handler_free(pivariety->sd.ctrl_handler);
+	mutex_destroy(&pivariety->mutex);
+}
+
+static int pivariety_get_length_of_set(struct pivariety *pivariety,
+				       u16 idx_reg, u16 val_reg)
+{
+	int ret;
+	int index = 0;
+	u32 val;
+
+	while (1) {
+		ret = pivariety_write(pivariety, idx_reg, index);
+		ret += pivariety_read(pivariety, val_reg, &val);
+
+		if (ret < 0)
+			return -1;
+
+		if (val == NO_DATA_AVAILABLE)
+			break;
+		index++;
+	}
+	pivariety_write(pivariety, idx_reg, 0);
+	return index;
+}
+
+static int pivariety_enum_resolution(struct pivariety *pivariety,
+				     struct arducam_format *format)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&pivariety->sd);
+	int index = 0;
+	u32 width, height;
+	int num_resolution = 0;
+	int ret;
+
+	num_resolution = pivariety_get_length_of_set(pivariety,
+						     RESOLUTION_INDEX_REG,
+						     FORMAT_WIDTH_REG);
+	if (num_resolution < 0)
+		goto err;
+
+	format->resolution_set = devm_kzalloc(&client->dev,
+					      sizeof(*format->resolution_set) *
+								num_resolution,
+					      GFP_KERNEL);
+	while (1) {
+		ret = pivariety_write(pivariety, RESOLUTION_INDEX_REG, index);
+		ret += pivariety_read(pivariety, FORMAT_WIDTH_REG, &width);
+		ret += pivariety_read(pivariety, FORMAT_HEIGHT_REG, &height);
+
+		if (ret < 0)
+			goto err;
+
+		if (width == NO_DATA_AVAILABLE || height == NO_DATA_AVAILABLE)
+			break;
+
+		format->resolution_set[index].width = width;
+		format->resolution_set[index].height = height;
+
+		index++;
+	}
+
+	format->num_resolution_set = index;
+	pivariety_write(pivariety, RESOLUTION_INDEX_REG, 0);
+	return 0;
+err:
+	return -ENODEV;
+}
+
+static int pivariety_enum_pixformat(struct pivariety *pivariety)
+{
+	int ret = 0;
+	u32 mbus_code = 0;
+	int pixfmt_type;
+	int bayer_order;
+	int bayer_order_not_volatile;
+	int lanes;
+	int index = 0;
+	int num_pixformat = 0;
+	struct arducam_format *arducam_fmt;
+	struct i2c_client *client = v4l2_get_subdevdata(&pivariety->sd);
+
+	num_pixformat = pivariety_get_length_of_set(pivariety,
+						    PIXFORMAT_INDEX_REG,
+						    PIXFORMAT_TYPE_REG);
+
+	if (num_pixformat < 0)
+		goto err;
+
+	ret = pivariety_read(pivariety, FLIPS_DONT_CHANGE_ORDER_REG,
+			     &bayer_order_not_volatile);
+	if (bayer_order_not_volatile == NO_DATA_AVAILABLE)
+		pivariety->bayer_order_volatile = 1;
+	else
+		pivariety->bayer_order_volatile = !bayer_order_not_volatile;
+
+	if (ret < 0)
+		goto err;
+
+	pivariety->supported_formats =
+		devm_kzalloc(&client->dev,
+			     sizeof(*pivariety->supported_formats) *
+								num_pixformat,
+			     GFP_KERNEL);
+
+	while (1) {
+		ret = pivariety_write(pivariety, PIXFORMAT_INDEX_REG, index);
+		ret += pivariety_read(pivariety, PIXFORMAT_TYPE_REG,
+				      &pixfmt_type);
+
+		if (pixfmt_type == NO_DATA_AVAILABLE)
+			break;
+
+		ret += pivariety_read(pivariety, MIPI_LANES_REG, &lanes);
+		if (lanes == NO_DATA_AVAILABLE)
+			break;
+
+		ret += pivariety_read(pivariety, PIXFORMAT_ORDER_REG,
+				      &bayer_order);
+		if (ret < 0)
+			goto err;
+
+		mbus_code = data_type_to_mbus_code(pixfmt_type, bayer_order);
+		arducam_fmt = &pivariety->supported_formats[index];
+		arducam_fmt->index = index;
+		arducam_fmt->mbus_code = mbus_code;
+		arducam_fmt->bayer_order = bayer_order;
+		arducam_fmt->data_type = pixfmt_type;
+		if (pivariety_enum_resolution(pivariety, arducam_fmt))
+			goto err;
+
+		index++;
+	}
+
+	pivariety_write(pivariety, PIXFORMAT_INDEX_REG, 0);
+	pivariety->num_supported_formats = index;
+	pivariety->current_format_idx = 0;
+	pivariety->current_resolution_idx = 0;
+	pivariety->lanes = lanes;
+
+	return 0;
+
+err:
+	return -ENODEV;
+}
+
+static const char *pivariety_ctrl_get_name(u32 id)
+{
+	switch (id) {
+	case V4L2_CID_ARDUCAM_EXT_TRI:
+		return "trigger_mode";
+	case V4L2_CID_ARDUCAM_IRCUT:
+		return "ircut";
+	case V4L2_CID_ARDUCAM_STROBE_SHIFT:
+		return "strobe_shift";
+	case V4L2_CID_ARDUCAM_STROBE_WIDTH:
+		return "strobe_width";
+	case V4L2_CID_ARDUCAM_MODE:
+		return "mode";
+	default:
+		return NULL;
+	}
+}
+
+static enum v4l2_ctrl_type pivariety_get_v4l2_ctrl_type(u32 id)
+{
+	switch (id) {
+	case V4L2_CID_ARDUCAM_EXT_TRI:
+		return V4L2_CTRL_TYPE_BOOLEAN;
+	case V4L2_CID_ARDUCAM_IRCUT:
+		return V4L2_CTRL_TYPE_BOOLEAN;
+	default:
+		return V4L2_CTRL_TYPE_INTEGER;
+	}
+}
+
+static struct v4l2_ctrl *v4l2_ctrl_new_arducam(struct v4l2_ctrl_handler *hdl,
+					       const struct v4l2_ctrl_ops *ops,
+					       u32 id, s64 min, s64 max,
+					       u64 step, s64 def)
+{
+	struct v4l2_ctrl_config ctrl_cfg = {
+		.ops = ops,
+		.id = id,
+		.name = NULL,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.flags = 0,
+		.min = min,
+		.max = max,
+		.def = def,
+		.step = step,
+	};
+
+	ctrl_cfg.name = pivariety_ctrl_get_name(id);
+	ctrl_cfg.type = pivariety_get_v4l2_ctrl_type(id);
+
+	return v4l2_ctrl_new_custom(hdl, &ctrl_cfg, NULL);
+}
+
+static int pivariety_enum_controls(struct pivariety *pivariety)
+{
+	struct v4l2_subdev *sd = &pivariety->sd;
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct v4l2_ctrl_handler *ctrl_hdlr = &pivariety->ctrl_handler;
+	struct v4l2_fwnode_device_properties props;
+	struct v4l2_ctrl **ctrl = pivariety->ctrls;
+	int ret, index, num_ctrls;
+	u32 id, min, max, def, step;
+
+	num_ctrls = pivariety_get_length_of_set(pivariety, CTRL_INDEX_REG,
+						CTRL_ID_REG);
+	if (num_ctrls < 0)
+		goto err;
+
+	v4l2_dbg(1, debug, sd, "%s: num_ctrls = %d\n",
+		 __func__, num_ctrls);
+
+	ret = v4l2_ctrl_handler_init(ctrl_hdlr, num_ctrls);
+	if (ret)
+		return ret;
+
+	mutex_init(&pivariety->mutex);
+
+	index = 0;
+	while (1) {
+		ret = pivariety_write(pivariety, CTRL_INDEX_REG, index);
+		pivariety_write(pivariety, CTRL_VALUE_REG, 0);
+		wait_for_free(pivariety, 1);
+
+		ret += pivariety_read(pivariety, CTRL_ID_REG, &id);
+		ret += pivariety_read(pivariety, CTRL_MAX_REG, &max);
+		ret += pivariety_read(pivariety, CTRL_MIN_REG, &min);
+		ret += pivariety_read(pivariety, CTRL_DEF_REG, &def);
+		ret += pivariety_read(pivariety, CTRL_STEP_REG, &step);
+		if (ret < 0)
+			goto err;
+
+		if (id == NO_DATA_AVAILABLE || max == NO_DATA_AVAILABLE ||
+		    min == NO_DATA_AVAILABLE || def == NO_DATA_AVAILABLE ||
+		    step == NO_DATA_AVAILABLE)
+			break;
+
+		v4l2_dbg(1, debug, sd,
+			 "%s: index = %d, id = 0x%x, max = %d, min = %d, def = %d, step = %d\n",
+			 __func__, index, id, max, min, def, step);
+
+		if (v4l2_ctrl_get_name(id)) {
+			*ctrl = v4l2_ctrl_new_std(ctrl_hdlr,
+						  &pivariety_ctrl_ops,
+						  id, min,
+						  max, step,
+						  def);
+			v4l2_dbg(1, debug, sd, "%s: ctrl: 0x%p\n",
+				 __func__, *ctrl);
+		} else if (pivariety_ctrl_get_name(id)) {
+			*ctrl = v4l2_ctrl_new_arducam(ctrl_hdlr,
+						      &pivariety_ctrl_ops,
+						      id, min, max, step, def);
+
+			v4l2_dbg(1, debug, sd,
+				 "%s: new custom ctrl, ctrl: 0x%p.\n",
+				 __func__, *ctrl);
+		} else {
+			index++;
+			continue;
+		}
+
+		if (!*ctrl)
+			goto err;
+
+		switch (id) {
+		case V4L2_CID_HFLIP:
+			pivariety->hflip = *ctrl;
+			if (pivariety->bayer_order_volatile)
+				pivariety->hflip->flags |=
+						V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+			break;
+
+		case V4L2_CID_VFLIP:
+			pivariety->vflip = *ctrl;
+			if (pivariety->bayer_order_volatile)
+				pivariety->vflip->flags |=
+						V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+			break;
+
+		case V4L2_CID_HBLANK:
+			(*ctrl)->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+			break;
+		}
+
+		ctrl++;
+		index++;
+	}
+
+	pivariety_write(pivariety, CTRL_INDEX_REG, 0);
+
+	ret = v4l2_fwnode_device_parse(&client->dev, &props);
+	if (ret)
+		goto err;
+
+	ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr,
+					      &pivariety_ctrl_ops,
+					      &props);
+	if (ret)
+		goto err;
+
+	pivariety->sd.ctrl_handler = ctrl_hdlr;
+	v4l2_ctrl_handler_setup(ctrl_hdlr);
+	return 0;
+err:
+	mutex_destroy(&pivariety->mutex);
+	return -ENODEV;
+}
+
+static int pivariety_parse_dt(struct pivariety *pivariety, struct device *dev)
+{
+	struct fwnode_handle *endpoint;
+	struct v4l2_fwnode_endpoint ep_cfg = {
+		.bus_type = V4L2_MBUS_CSI2_DPHY
+	};
+	int ret = -EINVAL;
+
+	/* Get CSI2 bus config */
+	endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
+	if (!endpoint) {
+		dev_err(dev, "endpoint node not found\n");
+		return -EINVAL;
+	}
+
+	if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) {
+		dev_err(dev, "could not parse endpoint\n");
+		goto error_out;
+	}
+
+	pivariety->bus = ep_cfg.bus.mipi_csi2;
+
+	ret = 0;
+
+error_out:
+	v4l2_fwnode_endpoint_free(&ep_cfg);
+	fwnode_handle_put(endpoint);
+
+	return ret;
+}
+
+static int pivariety_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct pivariety *pivariety;
+	u32 device_id, firmware_version;
+	int ret;
+
+	pivariety = devm_kzalloc(&client->dev, sizeof(*pivariety), GFP_KERNEL);
+	if (!pivariety)
+		return -ENOMEM;
+
+	/* Initialize subdev */
+	v4l2_i2c_subdev_init(&pivariety->sd, client,
+			     &pivariety_subdev_ops);
+
+	if (pivariety_parse_dt(pivariety, dev))
+		return -EINVAL;
+
+	/* Get system clock (xclk) */
+	pivariety->xclk = devm_clk_get(dev, "xclk");
+	if (IS_ERR(pivariety->xclk)) {
+		dev_err(dev, "failed to get xclk\n");
+		return PTR_ERR(pivariety->xclk);
+	}
+
+	pivariety->xclk_freq = clk_get_rate(pivariety->xclk);
+	if (pivariety->xclk_freq != 24000000) {
+		dev_err(dev, "xclk frequency not supported: %d Hz\n",
+			pivariety->xclk_freq);
+		return -EINVAL;
+	}
+
+	ret = pivariety_get_regulators(pivariety);
+	if (ret)
+		return ret;
+
+	/* Request optional enable pin */
+	pivariety->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+							GPIOD_OUT_HIGH);
+
+	ret = pivariety_power_on(dev);
+	if (ret)
+		return ret;
+
+	ret = pivariety_read(pivariety, DEVICE_ID_REG, &device_id);
+	if (ret || device_id != DEVICE_ID) {
+		dev_err(dev, "probe failed\n");
+		ret = -ENODEV;
+		goto error_power_off;
+	}
+
+	ret = pivariety_read(pivariety, DEVICE_VERSION_REG, &firmware_version);
+	if (ret)
+		dev_err(dev, "read firmware version failed\n");
+
+	dev_info(dev, "firmware version: 0x%04X\n", firmware_version);
+
+	if (pivariety_enum_pixformat(pivariety)) {
+		dev_err(dev, "enum pixformat failed.\n");
+		ret = -ENODEV;
+		goto error_power_off;
+	}
+
+	if (pivariety_enum_controls(pivariety)) {
+		dev_err(dev, "enum controls failed.\n");
+		ret = -ENODEV;
+		goto error_power_off;
+	}
+
+	/* Initialize subdev */
+	pivariety->sd.internal_ops = &pivariety_internal_ops;
+	pivariety->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+	pivariety->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+	/* Initialize source pad */
+	pivariety->pad.flags = MEDIA_PAD_FL_SOURCE;
+
+	ret = media_entity_pads_init(&pivariety->sd.entity, 1, &pivariety->pad);
+	if (ret)
+		goto error_handler_free;
+
+	ret = v4l2_async_register_subdev_sensor(&pivariety->sd);
+	if (ret < 0)
+		goto error_media_entity;
+
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+	pm_runtime_idle(dev);
+
+	return 0;
+
+error_media_entity:
+	media_entity_cleanup(&pivariety->sd.entity);
+
+error_handler_free:
+	pivariety_free_controls(pivariety);
+
+error_power_off:
+	pivariety_power_off(dev);
+
+	return ret;
+}
+
+static void pivariety_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct pivariety *pivariety = to_pivariety(sd);
+
+	v4l2_async_unregister_subdev(sd);
+	media_entity_cleanup(&sd->entity);
+	pivariety_free_controls(pivariety);
+
+	pm_runtime_disable(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+}
+
+static const struct dev_pm_ops pivariety_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(pivariety_suspend, pivariety_resume)
+	SET_RUNTIME_PM_OPS(pivariety_power_off, pivariety_power_on, NULL)
+};
+
+static const struct of_device_id arducam_pivariety_dt_ids[] = {
+	{ .compatible = "arducam,arducam-pivariety" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, arducam_pivariety_dt_ids);
+
+static struct i2c_driver arducam_pivariety_i2c_driver = {
+	.driver = {
+		.name = "arducam-pivariety",
+		.of_match_table	= arducam_pivariety_dt_ids,
+		.pm = &pivariety_pm_ops,
+	},
+	.probe = pivariety_probe,
+	.remove = pivariety_remove,
+};
+
+module_i2c_driver(arducam_pivariety_i2c_driver);
+
+MODULE_AUTHOR("Lee Jackson <info@arducam.com>");
+MODULE_DESCRIPTION("Arducam Pivariety v4l2 driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/i2c/arducam-pivariety.h b/drivers/media/i2c/arducam-pivariety.h
new file mode 100644
index 000000000000..99d5ada309e8
--- /dev/null
+++ b/drivers/media/i2c/arducam-pivariety.h
@@ -0,0 +1,110 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _ARDUCAM_PIVARIETY_H_
+#define _ARDUCAM_PIVARIETY_H_
+
+#define DEVICE_REG_BASE		0x0100
+#define PIXFORMAT_REG_BASE	0x0200
+#define FORMAT_REG_BASE		0x0300
+#define CTRL_REG_BASE		0x0400
+#define IPC_REG_BASE		0x0600
+
+#define ARDUCAM_MODE_STANDBY		0x00
+#define ARDUCAM_MODE_STREAMING		0x01
+
+#define MODE_SELECT_REG		(DEVICE_REG_BASE | 0x0000)
+#define DEVICE_VERSION_REG	(DEVICE_REG_BASE | 0x0001)
+#define SENSOR_ID_REG		(DEVICE_REG_BASE | 0x0002)
+#define DEVICE_ID_REG		(DEVICE_REG_BASE | 0x0003)
+#define SYSTEM_IDLE_REG		(DEVICE_REG_BASE | 0x0007)
+
+#define PIXFORMAT_INDEX_REG		(PIXFORMAT_REG_BASE | 0x0000)
+#define PIXFORMAT_TYPE_REG		(PIXFORMAT_REG_BASE | 0x0001)
+#define PIXFORMAT_ORDER_REG		(PIXFORMAT_REG_BASE | 0x0002)
+#define MIPI_LANES_REG			(PIXFORMAT_REG_BASE | 0x0003)
+#define FLIPS_DONT_CHANGE_ORDER_REG	(PIXFORMAT_REG_BASE | 0x0004)
+
+#define RESOLUTION_INDEX_REG	(FORMAT_REG_BASE | 0x0000)
+#define FORMAT_WIDTH_REG	(FORMAT_REG_BASE | 0x0001)
+#define FORMAT_HEIGHT_REG	(FORMAT_REG_BASE | 0x0002)
+
+#define CTRL_INDEX_REG	(CTRL_REG_BASE | 0x0000)
+#define CTRL_ID_REG	(CTRL_REG_BASE | 0x0001)
+#define CTRL_MIN_REG	(CTRL_REG_BASE | 0x0002)
+#define CTRL_MAX_REG	(CTRL_REG_BASE | 0x0003)
+#define CTRL_STEP_REG	(CTRL_REG_BASE | 0x0004)
+#define CTRL_DEF_REG	(CTRL_REG_BASE | 0x0005)
+#define CTRL_VALUE_REG	(CTRL_REG_BASE | 0x0006)
+
+#define IPC_SEL_TARGET_REG	(IPC_REG_BASE | 0x0000)
+#define IPC_SEL_TOP_REG		(IPC_REG_BASE | 0x0001)
+#define IPC_SEL_LEFT_REG	(IPC_REG_BASE | 0x0002)
+#define IPC_SEL_WIDTH_REG	(IPC_REG_BASE | 0x0003)
+#define IPC_SEL_HEIGHT_REG	(IPC_REG_BASE | 0x0004)
+#define IPC_DELAY_REG		(IPC_REG_BASE | 0x0005)
+
+#define NO_DATA_AVAILABLE	0xFFFFFFFE
+
+#define DEVICE_ID	0x0030
+
+#define I2C_READ_RETRY_COUNT	3
+#define I2C_WRITE_RETRY_COUNT	2
+
+#define V4L2_CID_ARDUCAM_BASE		(V4L2_CID_USER_BASE + 0x1000)
+#define V4L2_CID_ARDUCAM_EXT_TRI	(V4L2_CID_ARDUCAM_BASE + 1)
+#define V4L2_CID_ARDUCAM_IRCUT		(V4L2_CID_ARDUCAM_BASE + 8)
+#define V4L2_CID_ARDUCAM_STROBE_SHIFT	(V4L2_CID_ARDUCAM_BASE + 14)
+#define V4L2_CID_ARDUCAM_STROBE_WIDTH	(V4L2_CID_ARDUCAM_BASE + 15)
+#define V4L2_CID_ARDUCAM_MODE		(V4L2_CID_ARDUCAM_BASE + 16)
+
+enum image_dt {
+	IMAGE_DT_YUV420_8 = 0x18,
+	IMAGE_DT_YUV420_10,
+
+	IMAGE_DT_YUV420CSPS_8 = 0x1C,
+	IMAGE_DT_YUV420CSPS_10,
+	IMAGE_DT_YUV422_8,
+	IMAGE_DT_YUV422_10,
+	IMAGE_DT_RGB444,
+	IMAGE_DT_RGB555,
+	IMAGE_DT_RGB565,
+	IMAGE_DT_RGB666,
+	IMAGE_DT_RGB888,
+
+	IMAGE_DT_RAW6 = 0x28,
+	IMAGE_DT_RAW7,
+	IMAGE_DT_RAW8,
+	IMAGE_DT_RAW10,
+	IMAGE_DT_RAW12,
+	IMAGE_DT_RAW14,
+};
+
+enum bayer_order {
+	BAYER_ORDER_BGGR = 0,
+	BAYER_ORDER_GBRG = 1,
+	BAYER_ORDER_GRBG = 2,
+	BAYER_ORDER_RGGB = 3,
+	BAYER_ORDER_GRAY = 4,
+};
+
+enum yuv_order {
+	YUV_ORDER_YUYV = 0,
+	YUV_ORDER_YVYU = 1,
+	YUV_ORDER_UYVY = 2,
+	YUV_ORDER_VYUY = 3,
+};
+
+struct arducam_resolution {
+	u32 width;
+	u32 height;
+};
+
+struct arducam_format {
+	u32 index;
+	u32 mbus_code;
+	u32 bayer_order;
+	u32 data_type;
+	u32 num_resolution_set;
+	struct arducam_resolution *resolution_set;
+};
+
+#endif
diff --git a/drivers/media/i2c/arducam_64mp.c b/drivers/media/i2c/arducam_64mp.c
new file mode 100644
index 000000000000..0562a8b1eb0f
--- /dev/null
+++ b/drivers/media/i2c/arducam_64mp.c
@@ -0,0 +1,2618 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A V4L2 driver for Arducam 64MP cameras.
+ * Copyright (C) 2021 Arducam Technology co., Ltd.
+ *
+ * Based on Sony IMX477 camera driver
+ * Copyright (C) 2020 Raspberry Pi (Trading) Ltd
+ */
+#include <linux/unaligned.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mediabus.h>
+
+#define ARDUCAM_64MP_REG_VALUE_08BIT	1
+#define ARDUCAM_64MP_REG_VALUE_16BIT	2
+
+/* Chip ID */
+#define ARDUCAM_64MP_REG_CHIP_ID	0x005E
+#define ARDUCAM_64MP_CHIP_ID		0x4136
+
+#define ARDUCAM_64MP_REG_MODE_SELECT	0x0100
+#define ARDUCAM_64MP_MODE_STANDBY	0x00
+#define ARDUCAM_64MP_MODE_STREAMING	0x01
+
+#define ARDUCAM_64MP_REG_ORIENTATION	0x101
+
+#define ARDUCAM_64MP_XCLK_FREQ		24000000
+
+#define ARDUCAM_64MP_DEFAULT_LINK_FREQ	456000000
+
+/* Pixel rate is fixed at 900MHz for all the modes */
+#define ARDUCAM_64MP_PIXEL_RATE		900000000
+
+/* V_TIMING internal */
+#define ARDUCAM_64MP_REG_FRAME_LENGTH	0x0340
+#define ARDUCAM_64MP_FRAME_LENGTH_MAX	0xffff
+
+/* Long exposure multiplier */
+#define ARDUCAM_64MP_LONG_EXP_SHIFT_MAX	7
+#define ARDUCAM_64MP_LONG_EXP_SHIFT_REG	0x3100
+
+/* Exposure control */
+#define ARDUCAM_64MP_REG_EXPOSURE	0x0202
+#define ARDUCAM_64MP_EXPOSURE_OFFSET	48
+#define ARDUCAM_64MP_EXPOSURE_MIN	9
+#define ARDUCAM_64MP_EXPOSURE_STEP	1
+#define ARDUCAM_64MP_EXPOSURE_DEFAULT	0x3e8
+#define ARDUCAM_64MP_EXPOSURE_MAX	(ARDUCAM_64MP_FRAME_LENGTH_MAX - \
+					 ARDUCAM_64MP_EXPOSURE_OFFSET)
+
+/* Analog gain control */
+#define ARDUCAM_64MP_REG_ANALOG_GAIN		0x0204
+#define ARDUCAM_64MP_ANA_GAIN_MIN		0
+#define ARDUCAM_64MP_ANA_GAIN_MAX		1008
+#define ARDUCAM_64MP_ANA_GAIN_STEP		1
+#define ARDUCAM_64MP_ANA_GAIN_DEFAULT		0x0
+
+/* Digital gain control */
+#define ARDUCAM_64MP_REG_DIGITAL_GAIN		0x020e
+#define ARDUCAM_64MP_DGTL_GAIN_MIN		0x0100
+#define ARDUCAM_64MP_DGTL_GAIN_MAX		0x0fff
+#define ARDUCAM_64MP_DGTL_GAIN_DEFAULT		0x0100
+#define ARDUCAM_64MP_DGTL_GAIN_STEP		1
+
+/* Test Pattern Control */
+#define ARDUCAM_64MP_REG_TEST_PATTERN		0x0600
+#define ARDUCAM_64MP_TEST_PATTERN_DISABLE	0
+#define ARDUCAM_64MP_TEST_PATTERN_SOLID_COLOR	1
+#define ARDUCAM_64MP_TEST_PATTERN_COLOR_BARS	2
+#define ARDUCAM_64MP_TEST_PATTERN_GREY_COLOR	3
+#define ARDUCAM_64MP_TEST_PATTERN_PN9		4
+
+/* Test pattern colour components */
+#define ARDUCAM_64MP_REG_TEST_PATTERN_R		0x0602
+#define ARDUCAM_64MP_REG_TEST_PATTERN_GR	0x0604
+#define ARDUCAM_64MP_REG_TEST_PATTERN_B		0x0606
+#define ARDUCAM_64MP_REG_TEST_PATTERN_GB	0x0608
+#define ARDUCAM_64MP_TEST_PATTERN_COLOUR_MIN	0
+#define ARDUCAM_64MP_TEST_PATTERN_COLOUR_MAX	0x0fff
+#define ARDUCAM_64MP_TEST_PATTERN_COLOUR_STEP	1
+#define ARDUCAM_64MP_TEST_PATTERN_R_DEFAULT	\
+	ARDUCAM_64MP_TEST_PATTERN_COLOUR_MAX
+#define ARDUCAM_64MP_TEST_PATTERN_GR_DEFAULT	0
+#define ARDUCAM_64MP_TEST_PATTERN_B_DEFAULT	0
+#define ARDUCAM_64MP_TEST_PATTERN_GB_DEFAULT	0
+
+/* Embedded metadata stream structure */
+#define ARDUCAM_64MP_EMBEDDED_LINE_WIDTH (((11560 * 3) + 15) & ~15)
+#define ARDUCAM_64MP_NUM_EMBEDDED_LINES 1
+
+enum pad_types {
+	IMAGE_PAD,
+	METADATA_PAD,
+	NUM_PADS
+};
+
+/* ARDUCAM_64MP native and active pixel array size. */
+#define ARDUCAM_64MP_NATIVE_WIDTH		9344U
+#define ARDUCAM_64MP_NATIVE_HEIGHT		7032U
+#define ARDUCAM_64MP_PIXEL_ARRAY_LEFT		48U
+#define ARDUCAM_64MP_PIXEL_ARRAY_TOP		40U
+#define ARDUCAM_64MP_PIXEL_ARRAY_WIDTH		9248U
+#define ARDUCAM_64MP_PIXEL_ARRAY_HEIGHT		6944U
+
+struct arducam_64mp_reg {
+	u16 address;
+	u8 val;
+};
+
+struct arducam_64mp_reg_list {
+	unsigned int num_of_regs;
+	const struct arducam_64mp_reg *regs;
+};
+
+/* Mode : resolution and related config&values */
+struct arducam_64mp_mode {
+	/* Frame width */
+	unsigned int width;
+
+	/* Frame height */
+	unsigned int height;
+
+	/* H-timing in pixels */
+	unsigned int line_length_pix;
+
+	/* Analog crop rectangle. */
+	struct v4l2_rect crop;
+
+	/* Default framerate. */
+	struct v4l2_fract timeperframe_default;
+
+	/* Default register values */
+	struct arducam_64mp_reg_list reg_list;
+};
+
+static const s64 arducam_64mp_link_freq_menu[] = {
+	ARDUCAM_64MP_DEFAULT_LINK_FREQ,
+};
+
+static const struct arducam_64mp_reg mode_common_regs[] = {
+	{0x0100, 0x00},
+	{0x0136, 0x18},
+	{0x0137, 0x00},
+	{0x33F0, 0x01},
+	{0x33F1, 0x03},
+	{0x0111, 0x02},
+	{0x3062, 0x00},
+	{0x3063, 0x12},
+	{0x3076, 0x00},
+	{0x3077, 0x30},
+	{0x1f06, 0x06},
+	{0x1f07, 0x82},
+	{0x1f04, 0x71},
+	{0x1f05, 0x01},
+	{0x1f08, 0x01},
+	{0x5bfe, 0x14},
+	{0x5c0d, 0x2d},
+	{0x5c1c, 0x30},
+	{0x5c2b, 0x32},
+	{0x5c37, 0x2e},
+	{0x5c40, 0x30},
+	{0x5c50, 0x14},
+	{0x5c5f, 0x28},
+	{0x5c6e, 0x28},
+	{0x5c7d, 0x32},
+	{0x5c89, 0x37},
+	{0x5c92, 0x56},
+	{0x5bfc, 0x12},
+	{0x5c0b, 0x2a},
+	{0x5c1a, 0x2c},
+	{0x5c29, 0x2f},
+	{0x5c36, 0x2e},
+	{0x5c3f, 0x2e},
+	{0x5c4e, 0x06},
+	{0x5c5d, 0x1e},
+	{0x5c6c, 0x20},
+	{0x5c7b, 0x1e},
+	{0x5c88, 0x32},
+	{0x5c91, 0x32},
+	{0x5c02, 0x14},
+	{0x5c11, 0x2f},
+	{0x5c20, 0x32},
+	{0x5c2f, 0x34},
+	{0x5c39, 0x31},
+	{0x5c42, 0x31},
+	{0x5c8b, 0x28},
+	{0x5c94, 0x28},
+	{0x5c00, 0x10},
+	{0x5c0f, 0x2c},
+	{0x5c1e, 0x2e},
+	{0x5c2d, 0x32},
+	{0x5c38, 0x2e},
+	{0x5c41, 0x2b},
+	{0x5c61, 0x0a},
+	{0x5c70, 0x0a},
+	{0x5c7f, 0x0a},
+	{0x5c8a, 0x1e},
+	{0x5c93, 0x2a},
+	{0x5bfa, 0x2b},
+	{0x5c09, 0x2d},
+	{0x5c18, 0x2e},
+	{0x5c27, 0x30},
+	{0x5c5b, 0x28},
+	{0x5c6a, 0x22},
+	{0x5c79, 0x42},
+	{0x5bfb, 0x2c},
+	{0x5c0a, 0x2f},
+	{0x5c19, 0x2e},
+	{0x5c28, 0x2e},
+	{0x5c4d, 0x20},
+	{0x5c5c, 0x1e},
+	{0x5c6b, 0x32},
+	{0x5c7a, 0x32},
+	{0x5bfd, 0x30},
+	{0x5c0c, 0x32},
+	{0x5c1b, 0x2e},
+	{0x5c2a, 0x30},
+	{0x5c4f, 0x28},
+	{0x5c5e, 0x32},
+	{0x5c6d, 0x37},
+	{0x5c7c, 0x56},
+	{0x5bff, 0x2e},
+	{0x5c0e, 0x32},
+	{0x5c1d, 0x2e},
+	{0x5c2c, 0x2b},
+	{0x5c51, 0x0a},
+	{0x5c60, 0x0a},
+	{0x5c6f, 0x1e},
+	{0x5c7e, 0x2a},
+	{0x5c01, 0x32},
+	{0x5c10, 0x34},
+	{0x5c1f, 0x31},
+	{0x5c2e, 0x31},
+	{0x5c71, 0x28},
+	{0x5c80, 0x28},
+	{0x5c4c, 0x2a},
+	{0x33f2, 0x01},
+	{0x1f04, 0x73},
+	{0x1f05, 0x01},
+	{0x5bfa, 0x35},
+	{0x5c09, 0x38},
+	{0x5c18, 0x3a},
+	{0x5c27, 0x38},
+	{0x5c5b, 0x25},
+	{0x5c6a, 0x24},
+	{0x5c79, 0x47},
+	{0x5bfc, 0x15},
+	{0x5c0b, 0x2e},
+	{0x5c1a, 0x36},
+	{0x5c29, 0x38},
+	{0x5c36, 0x36},
+	{0x5c3f, 0x36},
+	{0x5c4e, 0x0b},
+	{0x5c5d, 0x20},
+	{0x5c6c, 0x2a},
+	{0x5c7b, 0x25},
+	{0x5c88, 0x25},
+	{0x5c91, 0x22},
+	{0x5bfe, 0x15},
+	{0x5c0d, 0x32},
+	{0x5c1c, 0x36},
+	{0x5c2b, 0x36},
+	{0x5c37, 0x3a},
+	{0x5c40, 0x39},
+	{0x5c50, 0x06},
+	{0x5c5f, 0x22},
+	{0x5c6e, 0x23},
+	{0x5c7d, 0x2e},
+	{0x5c89, 0x44},
+	{0x5c92, 0x51},
+	{0x5d7f, 0x0a},
+	{0x5c00, 0x17},
+	{0x5c0f, 0x36},
+	{0x5c1e, 0x38},
+	{0x5c2d, 0x3c},
+	{0x5c38, 0x38},
+	{0x5c41, 0x36},
+	{0x5c52, 0x0a},
+	{0x5c61, 0x21},
+	{0x5c70, 0x23},
+	{0x5c7f, 0x1b},
+	{0x5c8a, 0x22},
+	{0x5c93, 0x20},
+	{0x5c02, 0x1a},
+	{0x5c11, 0x3e},
+	{0x5c20, 0x3f},
+	{0x5c2f, 0x3d},
+	{0x5c39, 0x3e},
+	{0x5c42, 0x3c},
+	{0x5c54, 0x02},
+	{0x5c63, 0x12},
+	{0x5c72, 0x14},
+	{0x5c81, 0x24},
+	{0x5c8b, 0x1c},
+	{0x5c94, 0x4e},
+	{0x5d8a, 0x09},
+	{0x5bfb, 0x36},
+	{0x5c0a, 0x38},
+	{0x5c19, 0x36},
+	{0x5c28, 0x36},
+	{0x5c4d, 0x2a},
+	{0x5c5c, 0x25},
+	{0x5c6b, 0x25},
+	{0x5c7a, 0x22},
+	{0x5bfd, 0x36},
+	{0x5c0c, 0x36},
+	{0x5c1b, 0x3a},
+	{0x5c2a, 0x39},
+	{0x5c4f, 0x23},
+	{0x5c5e, 0x2e},
+	{0x5c6d, 0x44},
+	{0x5c7c, 0x51},
+	{0x5d63, 0x0a},
+	{0x5bff, 0x38},
+	{0x5c0e, 0x3c},
+	{0x5c1d, 0x38},
+	{0x5c2c, 0x36},
+	{0x5c51, 0x23},
+	{0x5c60, 0x1b},
+	{0x5c6f, 0x22},
+	{0x5c7e, 0x20},
+	{0x5c01, 0x3f},
+	{0x5c10, 0x3d},
+	{0x5c1f, 0x3e},
+	{0x5c2e, 0x3c},
+	{0x5c53, 0x14},
+	{0x5c62, 0x24},
+	{0x5c71, 0x1c},
+	{0x5c80, 0x4e},
+	{0x5d76, 0x09},
+	{0x5c4c, 0x2a},
+	{0x33f2, 0x02},
+	{0x1f04, 0x78},
+	{0x1f05, 0x01},
+	{0x5bfa, 0x37},
+	{0x5c09, 0x36},
+	{0x5c18, 0x39},
+	{0x5c27, 0x38},
+	{0x5c5b, 0x27},
+	{0x5c6a, 0x2b},
+	{0x5c79, 0x48},
+	{0x5bfc, 0x16},
+	{0x5c0b, 0x32},
+	{0x5c1a, 0x33},
+	{0x5c29, 0x37},
+	{0x5c36, 0x36},
+	{0x5c3f, 0x35},
+	{0x5c4e, 0x0d},
+	{0x5c5d, 0x2d},
+	{0x5c6c, 0x23},
+	{0x5c7b, 0x25},
+	{0x5c88, 0x31},
+	{0x5c91, 0x2e},
+	{0x5bfe, 0x15},
+	{0x5c0d, 0x31},
+	{0x5c1c, 0x35},
+	{0x5c2b, 0x36},
+	{0x5c37, 0x35},
+	{0x5c40, 0x37},
+	{0x5c50, 0x0f},
+	{0x5c5f, 0x31},
+	{0x5c6e, 0x30},
+	{0x5c7d, 0x33},
+	{0x5c89, 0x36},
+	{0x5c92, 0x5b},
+	{0x5c00, 0x13},
+	{0x5c0f, 0x2f},
+	{0x5c1e, 0x2e},
+	{0x5c2d, 0x34},
+	{0x5c38, 0x33},
+	{0x5c41, 0x32},
+	{0x5c52, 0x0d},
+	{0x5c61, 0x27},
+	{0x5c70, 0x28},
+	{0x5c7f, 0x1f},
+	{0x5c8a, 0x25},
+	{0x5c93, 0x2c},
+	{0x5c02, 0x15},
+	{0x5c11, 0x36},
+	{0x5c20, 0x39},
+	{0x5c2f, 0x3a},
+	{0x5c39, 0x37},
+	{0x5c42, 0x37},
+	{0x5c54, 0x04},
+	{0x5c63, 0x1c},
+	{0x5c72, 0x1c},
+	{0x5c81, 0x1c},
+	{0x5c8b, 0x28},
+	{0x5c94, 0x24},
+	{0x5bfb, 0x33},
+	{0x5c0a, 0x37},
+	{0x5c19, 0x36},
+	{0x5c28, 0x35},
+	{0x5c4d, 0x23},
+	{0x5c5c, 0x25},
+	{0x5c6b, 0x31},
+	{0x5c7a, 0x2e},
+	{0x5bfd, 0x35},
+	{0x5c0c, 0x36},
+	{0x5c1b, 0x35},
+	{0x5c2a, 0x37},
+	{0x5c4f, 0x30},
+	{0x5c5e, 0x33},
+	{0x5c6d, 0x36},
+	{0x5c7c, 0x5b},
+	{0x5bff, 0x2e},
+	{0x5c0e, 0x34},
+	{0x5c1d, 0x33},
+	{0x5c2c, 0x32},
+	{0x5c51, 0x28},
+	{0x5c60, 0x1f},
+	{0x5c6f, 0x25},
+	{0x5c7e, 0x2c},
+	{0x5c01, 0x39},
+	{0x5c10, 0x3a},
+	{0x5c1f, 0x37},
+	{0x5c2e, 0x37},
+	{0x5c53, 0x1c},
+	{0x5c62, 0x1c},
+	{0x5c71, 0x28},
+	{0x5c80, 0x24},
+	{0x5c4c, 0x2c},
+	{0x33f2, 0x03},
+	{0x1f08, 0x00},
+	{0x32c8, 0x00},
+	{0x4017, 0x40},
+	{0x40a2, 0x01},
+	{0x40ac, 0x01},
+	{0x4328, 0x00},
+	{0x4329, 0xb3},
+	{0x4e15, 0x10},
+	{0x4e19, 0x2f},
+	{0x4e21, 0x0f},
+	{0x4e2f, 0x10},
+	{0x4e3d, 0x10},
+	{0x4e41, 0x2f},
+	{0x4e57, 0x29},
+	{0x4ffb, 0x2f},
+	{0x5011, 0x24},
+	{0x501d, 0x03},
+	{0x505f, 0x41},
+	{0x5060, 0xdf},
+	{0x5065, 0xdf},
+	{0x5066, 0x37},
+	{0x506e, 0x57},
+	{0x5070, 0xc5},
+	{0x5072, 0x57},
+	{0x5075, 0x53},
+	{0x5076, 0x55},
+	{0x5077, 0xc1},
+	{0x5078, 0xc3},
+	{0x5079, 0x53},
+	{0x507a, 0x55},
+	{0x507d, 0x57},
+	{0x507e, 0xdf},
+	{0x507f, 0xc5},
+	{0x5081, 0x57},
+	{0x53c8, 0x01},
+	{0x53c9, 0xe2},
+	{0x53ca, 0x03},
+	{0x5422, 0x7a},
+	{0x548e, 0x40},
+	{0x5497, 0x5e},
+	{0x54a1, 0x40},
+	{0x54a9, 0x40},
+	{0x54b2, 0x5e},
+	{0x54bc, 0x40},
+	{0x57c6, 0x00},
+	{0x583d, 0x0e},
+	{0x583e, 0x0e},
+	{0x583f, 0x0e},
+	{0x5840, 0x0e},
+	{0x5841, 0x0e},
+	{0x5842, 0x0e},
+	{0x5900, 0x12},
+	{0x5901, 0x12},
+	{0x5902, 0x14},
+	{0x5903, 0x12},
+	{0x5904, 0x14},
+	{0x5905, 0x12},
+	{0x5906, 0x14},
+	{0x5907, 0x12},
+	{0x590f, 0x12},
+	{0x5911, 0x12},
+	{0x5913, 0x12},
+	{0x591c, 0x12},
+	{0x591e, 0x12},
+	{0x5920, 0x12},
+	{0x5948, 0x08},
+	{0x5949, 0x08},
+	{0x594a, 0x08},
+	{0x594b, 0x08},
+	{0x594c, 0x08},
+	{0x594d, 0x08},
+	{0x594e, 0x08},
+	{0x594f, 0x08},
+	{0x595c, 0x08},
+	{0x595e, 0x08},
+	{0x5960, 0x08},
+	{0x596e, 0x08},
+	{0x5970, 0x08},
+	{0x5972, 0x08},
+	{0x597e, 0x0f},
+	{0x597f, 0x0f},
+	{0x599a, 0x0f},
+	{0x59de, 0x08},
+	{0x59df, 0x08},
+	{0x59fa, 0x08},
+	{0x5a59, 0x22},
+	{0x5a5b, 0x22},
+	{0x5a5d, 0x1a},
+	{0x5a5f, 0x22},
+	{0x5a61, 0x1a},
+	{0x5a63, 0x22},
+	{0x5a65, 0x1a},
+	{0x5a67, 0x22},
+	{0x5a77, 0x22},
+	{0x5a7b, 0x22},
+	{0x5a7f, 0x22},
+	{0x5a91, 0x22},
+	{0x5a95, 0x22},
+	{0x5a99, 0x22},
+	{0x5ae9, 0x66},
+	{0x5aeb, 0x66},
+	{0x5aed, 0x5e},
+	{0x5aef, 0x66},
+	{0x5af1, 0x5e},
+	{0x5af3, 0x66},
+	{0x5af5, 0x5e},
+	{0x5af7, 0x66},
+	{0x5b07, 0x66},
+	{0x5b0b, 0x66},
+	{0x5b0f, 0x66},
+	{0x5b21, 0x66},
+	{0x5b25, 0x66},
+	{0x5b29, 0x66},
+	{0x5b79, 0x46},
+	{0x5b7b, 0x3e},
+	{0x5b7d, 0x3e},
+	{0x5b89, 0x46},
+	{0x5b8b, 0x46},
+	{0x5b97, 0x46},
+	{0x5b99, 0x46},
+	{0x5c9e, 0x0a},
+	{0x5c9f, 0x08},
+	{0x5ca0, 0x0a},
+	{0x5ca1, 0x0a},
+	{0x5ca2, 0x0b},
+	{0x5ca3, 0x06},
+	{0x5ca4, 0x04},
+	{0x5ca5, 0x06},
+	{0x5ca6, 0x04},
+	{0x5cad, 0x0b},
+	{0x5cae, 0x0a},
+	{0x5caf, 0x0c},
+	{0x5cb0, 0x0a},
+	{0x5cb1, 0x0b},
+	{0x5cb2, 0x08},
+	{0x5cb3, 0x06},
+	{0x5cb4, 0x08},
+	{0x5cb5, 0x04},
+	{0x5cbc, 0x0b},
+	{0x5cbd, 0x09},
+	{0x5cbe, 0x08},
+	{0x5cbf, 0x09},
+	{0x5cc0, 0x0a},
+	{0x5cc1, 0x08},
+	{0x5cc2, 0x06},
+	{0x5cc3, 0x08},
+	{0x5cc4, 0x06},
+	{0x5ccb, 0x0a},
+	{0x5ccc, 0x09},
+	{0x5ccd, 0x0a},
+	{0x5cce, 0x08},
+	{0x5ccf, 0x0a},
+	{0x5cd0, 0x08},
+	{0x5cd1, 0x08},
+	{0x5cd2, 0x08},
+	{0x5cd3, 0x08},
+	{0x5cda, 0x09},
+	{0x5cdb, 0x09},
+	{0x5cdc, 0x08},
+	{0x5cdd, 0x08},
+	{0x5ce3, 0x09},
+	{0x5ce4, 0x08},
+	{0x5ce5, 0x08},
+	{0x5ce6, 0x08},
+	{0x5cf4, 0x04},
+	{0x5d04, 0x04},
+	{0x5d13, 0x06},
+	{0x5d22, 0x06},
+	{0x5d23, 0x04},
+	{0x5d2e, 0x06},
+	{0x5d37, 0x06},
+	{0x5d6f, 0x09},
+	{0x5d72, 0x0f},
+	{0x5d88, 0x0f},
+	{0x5de6, 0x01},
+	{0x5de7, 0x01},
+	{0x5de8, 0x01},
+	{0x5de9, 0x01},
+	{0x5dea, 0x01},
+	{0x5deb, 0x01},
+	{0x5dec, 0x01},
+	{0x5df2, 0x01},
+	{0x5df3, 0x01},
+	{0x5df4, 0x01},
+	{0x5df5, 0x01},
+	{0x5df6, 0x01},
+	{0x5df7, 0x01},
+	{0x5df8, 0x01},
+	{0x5dfe, 0x01},
+	{0x5dff, 0x01},
+	{0x5e00, 0x01},
+	{0x5e01, 0x01},
+	{0x5e02, 0x01},
+	{0x5e03, 0x01},
+	{0x5e04, 0x01},
+	{0x5e0a, 0x01},
+	{0x5e0b, 0x01},
+	{0x5e0c, 0x01},
+	{0x5e0d, 0x01},
+	{0x5e0e, 0x01},
+	{0x5e0f, 0x01},
+	{0x5e10, 0x01},
+	{0x5e16, 0x01},
+	{0x5e17, 0x01},
+	{0x5e18, 0x01},
+	{0x5e1e, 0x01},
+	{0x5e1f, 0x01},
+	{0x5e20, 0x01},
+	{0x5e6e, 0x5a},
+	{0x5e6f, 0x46},
+	{0x5e70, 0x46},
+	{0x5e71, 0x3c},
+	{0x5e72, 0x3c},
+	{0x5e73, 0x28},
+	{0x5e74, 0x28},
+	{0x5e75, 0x6e},
+	{0x5e76, 0x6e},
+	{0x5e81, 0x46},
+	{0x5e83, 0x3c},
+	{0x5e85, 0x28},
+	{0x5e87, 0x6e},
+	{0x5e92, 0x46},
+	{0x5e94, 0x3c},
+	{0x5e96, 0x28},
+	{0x5e98, 0x6e},
+	{0x5ecb, 0x26},
+	{0x5ecc, 0x26},
+	{0x5ecd, 0x26},
+	{0x5ece, 0x26},
+	{0x5ed2, 0x26},
+	{0x5ed3, 0x26},
+	{0x5ed4, 0x26},
+	{0x5ed5, 0x26},
+	{0x5ed9, 0x26},
+	{0x5eda, 0x26},
+	{0x5ee5, 0x08},
+	{0x5ee6, 0x08},
+	{0x5ee7, 0x08},
+	{0x6006, 0x14},
+	{0x6007, 0x14},
+	{0x6008, 0x14},
+	{0x6009, 0x14},
+	{0x600a, 0x14},
+	{0x600b, 0x14},
+	{0x600c, 0x14},
+	{0x600d, 0x22},
+	{0x600e, 0x22},
+	{0x600f, 0x14},
+	{0x601a, 0x14},
+	{0x601b, 0x14},
+	{0x601c, 0x14},
+	{0x601d, 0x14},
+	{0x601e, 0x14},
+	{0x601f, 0x14},
+	{0x6020, 0x14},
+	{0x6021, 0x22},
+	{0x6022, 0x22},
+	{0x6023, 0x14},
+	{0x602e, 0x14},
+	{0x602f, 0x14},
+	{0x6030, 0x14},
+	{0x6031, 0x22},
+	{0x6039, 0x14},
+	{0x603a, 0x14},
+	{0x603b, 0x14},
+	{0x603c, 0x22},
+	{0x6132, 0x0f},
+	{0x6133, 0x0f},
+	{0x6134, 0x0f},
+	{0x6135, 0x0f},
+	{0x6136, 0x0f},
+	{0x6137, 0x0f},
+	{0x6138, 0x0f},
+	{0x613e, 0x0f},
+	{0x613f, 0x0f},
+	{0x6140, 0x0f},
+	{0x6141, 0x0f},
+	{0x6142, 0x0f},
+	{0x6143, 0x0f},
+	{0x6144, 0x0f},
+	{0x614a, 0x0f},
+	{0x614b, 0x0f},
+	{0x614c, 0x0f},
+	{0x614d, 0x0f},
+	{0x614e, 0x0f},
+	{0x614f, 0x0f},
+	{0x6150, 0x0f},
+	{0x6156, 0x0f},
+	{0x6157, 0x0f},
+	{0x6158, 0x0f},
+	{0x6159, 0x0f},
+	{0x615a, 0x0f},
+	{0x615b, 0x0f},
+	{0x615c, 0x0f},
+	{0x6162, 0x0f},
+	{0x6163, 0x0f},
+	{0x6164, 0x0f},
+	{0x616a, 0x0f},
+	{0x616b, 0x0f},
+	{0x616c, 0x0f},
+	{0x6226, 0x00},
+	{0x84f8, 0x01},
+	{0x8501, 0x00},
+	{0x8502, 0x01},
+	{0x8505, 0x00},
+	{0x8744, 0x00},
+	{0x883c, 0x01},
+	{0x8845, 0x00},
+	{0x8846, 0x01},
+	{0x8849, 0x00},
+	{0x9004, 0x1f},
+	{0x9064, 0x4d},
+	{0x9065, 0x3d},
+	{0x922e, 0x91},
+	{0x922f, 0x2a},
+	{0x9230, 0xe2},
+	{0x9231, 0xc0},
+	{0x9232, 0xe2},
+	{0x9233, 0xc1},
+	{0x9234, 0xe2},
+	{0x9235, 0xc2},
+	{0x9236, 0xe2},
+	{0x9237, 0xc3},
+	{0x9238, 0xe2},
+	{0x9239, 0xd4},
+	{0x923a, 0xe2},
+	{0x923b, 0xd5},
+	{0x923c, 0x90},
+	{0x923d, 0x64},
+	{0xb0b9, 0x10},
+	{0xbc76, 0x00},
+	{0xbc77, 0x00},
+	{0xbc78, 0x00},
+	{0xbc79, 0x00},
+	{0xbc7b, 0x28},
+	{0xbc7c, 0x00},
+	{0xbc7d, 0x00},
+	{0xbc7f, 0xc0},
+	{0xc6b9, 0x01},
+	{0xecb5, 0x04},
+	{0xecbf, 0x04},
+	{0x0112, 0x0a},
+	{0x0113, 0x0a},
+	{0x0114, 0x01},
+	{0x0301, 0x08},
+	{0x0303, 0x02},
+	{0x0305, 0x04},
+	{0x0306, 0x01},
+	{0x0307, 0x2c},
+	{0x030b, 0x02},
+	{0x030d, 0x04},
+	{0x030e, 0x01},
+	{0x030f, 0x30},
+	{0x0310, 0x01},
+	{0x4018, 0x00},
+	{0x4019, 0x00},
+	{0x401a, 0x00},
+	{0x401b, 0x00},
+	{0x3400, 0x01},
+	{0x3092, 0x01},
+	{0x3093, 0x00},
+	{0x0350, 0x00},
+	{0x3419, 0x00},
+};
+
+/* 64 mpix 2.7fps */
+static const struct arducam_64mp_reg mode_9152x6944_regs[] = {
+	{0x0342, 0xb6},
+	{0x0343, 0xb2},
+	{0x0340, 0x1b},
+	{0x0341, 0x76},
+	{0x0344, 0x00},
+	{0x0345, 0x00},
+	{0x0346, 0x00},
+	{0x0347, 0x00},
+	{0x0348, 0x24},
+	{0x0349, 0x1f},
+	{0x034a, 0x1b},
+	{0x034b, 0x1f},
+	{0x0900, 0x00},
+	{0x0901, 0x11},
+	{0x0902, 0x0a},
+	{0x30d8, 0x00},
+	{0x3200, 0x01},
+	{0x3201, 0x01},
+	{0x0408, 0x00},
+	{0x0409, 0x00},
+	{0x040a, 0x00},
+	{0x040b, 0x00},
+	{0x040c, 0x23},
+	{0x040d, 0xc0},
+	{0x040e, 0x1b},
+	{0x040f, 0x20},
+	{0x034c, 0x23},
+	{0x034d, 0xc0},
+	{0x034e, 0x1b},
+	{0x034f, 0x20},
+	{0x30d9, 0x01},
+	{0x32d5, 0x01},
+	{0x32d6, 0x00},
+	{0x401e, 0x00},
+	{0x40b8, 0x04},
+	{0x40b9, 0x20},
+	{0x40bc, 0x02},
+	{0x40bd, 0x58},
+	{0x40be, 0x02},
+	{0x40bf, 0x58},
+	{0x41a4, 0x00},
+	{0x5a09, 0x01},
+	{0x5a17, 0x01},
+	{0x5a25, 0x01},
+	{0x5a33, 0x01},
+	{0x98d7, 0x14},
+	{0x98d8, 0x14},
+	{0x98d9, 0x00},
+	{0x99c4, 0x00},
+	{0x0202, 0x03},
+	{0x0203, 0xe8},
+	{0x0204, 0x00},
+	{0x0205, 0x00},
+	{0x020e, 0x01},
+	{0x020f, 0x00},
+	{0x341a, 0x00},
+	{0x341b, 0x00},
+	{0x341c, 0x00},
+	{0x341d, 0x00},
+	{0x341e, 0x02},
+	{0x341f, 0x3c},
+	{0x3420, 0x02},
+	{0x3421, 0x42},
+};
+
+/* 48 mpix 3.0fps */
+static const struct arducam_64mp_reg mode_8000x6000_regs[] = {
+	{0x0342, 0xb6},
+	{0x0343, 0xb2},
+	{0x0340, 0x19},
+	{0x0341, 0x0e},
+	{0x0344, 0x02},
+	{0x0345, 0x70},
+	{0x0346, 0x01},
+	{0x0347, 0xd8},
+	{0x0348, 0x21},
+	{0x0349, 0xaf},
+	{0x034a, 0x19},
+	{0x034b, 0x47},
+	{0x0900, 0x00},
+	{0x0901, 0x11},
+	{0x0902, 0x0a},
+	{0x30d8, 0x00},
+	{0x3200, 0x01},
+	{0x3201, 0x01},
+	{0x0408, 0x00},
+	{0x0409, 0x00},
+	{0x040a, 0x00},
+	{0x040b, 0x00},
+	{0x040c, 0x1f},
+	{0x040d, 0x40},
+	{0x040e, 0x17},
+	{0x040f, 0x70},
+	{0x034c, 0x1f},
+	{0x034d, 0x40},
+	{0x034e, 0x17},
+	{0x034f, 0x70},
+	{0x30d9, 0x01},
+	{0x32d5, 0x01},
+	{0x32d6, 0x00},
+	{0x401e, 0x00},
+	{0x40b8, 0x04},
+	{0x40b9, 0x20},
+	{0x40bc, 0x02},
+	{0x40bd, 0x58},
+	{0x40be, 0x02},
+	{0x40bf, 0x58},
+	{0x41a4, 0x00},
+	{0x5a09, 0x01},
+	{0x5a17, 0x01},
+	{0x5a25, 0x01},
+	{0x5a33, 0x01},
+	{0x98d7, 0x14},
+	{0x98d8, 0x14},
+	{0x98d9, 0x00},
+	{0x99c4, 0x00},
+	{0x0202, 0x03},
+	{0x0203, 0xe8},
+	{0x0204, 0x00},
+	{0x0205, 0x00},
+	{0x020e, 0x01},
+	{0x020f, 0x00},
+	{0x341a, 0x00},
+	{0x341b, 0x00},
+	{0x341c, 0x00},
+	{0x341d, 0x00},
+	{0x341e, 0x01},
+	{0x341f, 0xf4},
+	{0x3420, 0x01},
+	{0x3421, 0xf4},
+};
+
+/* 16 mpix 10fps */
+static const struct arducam_64mp_reg mode_4624x3472_regs[] = {
+	{0x0342, 0x63},
+	{0x0343, 0x97},
+	{0x0340, 0x0d},
+	{0x0341, 0xca},
+	{0x0344, 0x00},
+	{0x0345, 0x00},
+	{0x0346, 0x00},
+	{0x0347, 0x00},
+	{0x0348, 0x24},
+	{0x0349, 0x1f},
+	{0x034a, 0x1b},
+	{0x034b, 0x1f},
+	{0x0900, 0x01},
+	{0x0901, 0x22},
+	{0x0902, 0x08},
+	{0x30d8, 0x04},
+	{0x3200, 0x41},
+	{0x3201, 0x41},
+	{0x0408, 0x00},
+	{0x0409, 0x00},
+	{0x040a, 0x00},
+	{0x040b, 0x00},
+	{0x040c, 0x12},
+	{0x040d, 0x10},
+	{0x040e, 0x0d},
+	{0x040f, 0x90},
+	{0x034c, 0x12},
+	{0x034d, 0x10},
+	{0x034e, 0x0d},
+	{0x034f, 0x90},
+	{0x30d9, 0x00},
+	{0x32d5, 0x00},
+	{0x32d6, 0x00},
+	{0x401e, 0x00},
+	{0x40b8, 0x01},
+	{0x40b9, 0x2c},
+	{0x40bc, 0x01},
+	{0x40bd, 0x18},
+	{0x40be, 0x00},
+	{0x40bf, 0x00},
+	{0x41a4, 0x00},
+	{0x5a09, 0x01},
+	{0x5a17, 0x01},
+	{0x5a25, 0x01},
+	{0x5a33, 0x01},
+	{0x98d7, 0xb4},
+	{0x98d8, 0x8c},
+	{0x98d9, 0x0a},
+	{0x99c4, 0x16},
+	{0x341a, 0x00},
+	{0x341b, 0x00},
+	{0x341c, 0x00},
+	{0x341d, 0x00},
+	{0x341e, 0x01},
+	{0x341f, 0x21},
+	{0x3420, 0x01},
+	{0x3421, 0x21},
+};
+
+/* 4k 20fps mode */
+static const struct arducam_64mp_reg mode_3840x2160_regs[] = {
+	{0x0342, 0x4e},
+	{0x0343, 0xb7},
+	{0x0340, 0x08},
+	{0x0341, 0xb9},
+	{0x0344, 0x03},
+	{0x0345, 0x10},
+	{0x0346, 0x05},
+	{0x0347, 0x20},
+	{0x0348, 0x21},
+	{0x0349, 0x0f},
+	{0x034a, 0x15},
+	{0x034b, 0xff},
+	{0x0900, 0x01},
+	{0x0901, 0x22},
+	{0x0902, 0x08},
+	{0x30d8, 0x04},
+	{0x3200, 0x41},
+	{0x3201, 0x41},
+	{0x0408, 0x00},
+	{0x0409, 0x00},
+	{0x040a, 0x00},
+	{0x040b, 0x00},
+	{0x040c, 0x0f},
+	{0x040d, 0x00},
+	{0x040e, 0x08},
+	{0x040f, 0x70},
+	{0x034c, 0x0f},
+	{0x034d, 0x00},
+	{0x034e, 0x08},
+	{0x034f, 0x70},
+	{0x30d9, 0x00},
+	{0x32d5, 0x00},
+	{0x32d6, 0x00},
+	{0x401e, 0x00},
+	{0x40b8, 0x01},
+	{0x40b9, 0x2c},
+	{0x40bc, 0x01},
+	{0x40bd, 0x18},
+	{0x40be, 0x00},
+	{0x40bf, 0x00},
+	{0x41a4, 0x00},
+	{0x5a09, 0x01},
+	{0x5a17, 0x01},
+	{0x5a25, 0x01},
+	{0x5a33, 0x01},
+	{0x98d7, 0xb4},
+	{0x98d8, 0x8c},
+	{0x98d9, 0x0a},
+	{0x99c4, 0x16},
+	{0x341a, 0x00},
+	{0x341b, 0x00},
+	{0x341c, 0x00},
+	{0x341d, 0x00},
+	{0x341e, 0x00},
+	{0x341f, 0xf0},
+	{0x3420, 0x00},
+	{0x3421, 0xb4},
+};
+
+/* 4x4 binned 30fps mode */
+static const struct arducam_64mp_reg mode_2312x1736_regs[] = {
+	{0x0342, 0x33},
+	{0x0343, 0x60},
+	{0x0340, 0x08},
+	{0x0341, 0xe9},
+	{0x0344, 0x00},
+	{0x0345, 0x00},
+	{0x0346, 0x00},
+	{0x0347, 0x00},
+	{0x0348, 0x24},
+	{0x0349, 0x1f},
+	{0x034a, 0x1b},
+	{0x034b, 0x1f},
+	{0x0900, 0x01},
+	{0x0901, 0x44},
+	{0x0902, 0x08},
+	{0x30d8, 0x04},
+	{0x3200, 0x43},
+	{0x3201, 0x43},
+	{0x0408, 0x00},
+	{0x0409, 0x00},
+	{0x040a, 0x00},
+	{0x040b, 0x00},
+	{0x040c, 0x09},
+	{0x040d, 0x08},
+	{0x040e, 0x06},
+	{0x040f, 0xc8},
+	{0x034c, 0x09},
+	{0x034d, 0x08},
+	{0x034e, 0x06},
+	{0x034f, 0xc8},
+	{0x30d9, 0x00},
+	{0x32d5, 0x00},
+	{0x32d6, 0x00},
+	{0x401e, 0x00},
+	{0x40b8, 0x01},
+	{0x40b9, 0x2c},
+	{0x40bc, 0x01},
+	{0x40bd, 0x18},
+	{0x40be, 0x00},
+	{0x40bf, 0x00},
+	{0x41a4, 0x00},
+	{0x5a09, 0x01},
+	{0x5a17, 0x01},
+	{0x5a25, 0x01},
+	{0x5a33, 0x01},
+	{0x98d7, 0xb4},
+	{0x98d8, 0x8c},
+	{0x98d9, 0x0a},
+	{0x99c4, 0x16},
+	{0x341a, 0x00},
+	{0x341b, 0x00},
+	{0x341c, 0x00},
+	{0x341d, 0x00},
+	{0x341e, 0x00},
+	{0x341f, 0x90},
+	{0x3420, 0x00},
+	{0x3421, 0x90},
+};
+
+/* 1080p 60fps mode */
+static const struct arducam_64mp_reg mode_1920x1080_regs[] = {
+	{0x0342, 0x29},
+	{0x0343, 0xe3},
+	{0x0340, 0x05},
+	{0x0341, 0x76},
+	{0x0344, 0x03},
+	{0x0345, 0x10},
+	{0x0346, 0x05},
+	{0x0347, 0x20},
+	{0x0348, 0x21},
+	{0x0349, 0x0f},
+	{0x034a, 0x16},
+	{0x034b, 0x0f},
+	{0x0900, 0x01},
+	{0x0901, 0x44},
+	{0x0902, 0x08},
+	{0x30d8, 0x04},
+	{0x3200, 0x43},
+	{0x3201, 0x43},
+	{0x0408, 0x00},
+	{0x0409, 0x00},
+	{0x040a, 0x00},
+	{0x040b, 0x00},
+	{0x040c, 0x07},
+	{0x040d, 0x80},
+	{0x040e, 0x04},
+	{0x040f, 0x38},
+	{0x034c, 0x07},
+	{0x034d, 0x80},
+	{0x034e, 0x04},
+	{0x034f, 0x38},
+	{0x30d9, 0x00},
+	{0x32d5, 0x00},
+	{0x32d6, 0x00},
+	{0x401e, 0x00},
+	{0x40b8, 0x01},
+	{0x40b9, 0x2c},
+	{0x40bc, 0x01},
+	{0x40bd, 0x18},
+	{0x40be, 0x00},
+	{0x40bf, 0x00},
+	{0x41a4, 0x00},
+	{0x5a09, 0x01},
+	{0x5a17, 0x01},
+	{0x5a25, 0x01},
+	{0x5a33, 0x01},
+	{0x98d7, 0xb4},
+	{0x98d8, 0x8c},
+	{0x98d9, 0x0a},
+	{0x99c4, 0x16},
+	{0x341a, 0x00},
+	{0x341b, 0x00},
+	{0x341c, 0x00},
+	{0x341d, 0x00},
+	{0x341e, 0x00},
+	{0x341f, 0x78},
+	{0x3420, 0x00},
+	{0x3421, 0x5a},
+};
+
+/* 720p 120fps mode */
+static const struct arducam_64mp_reg mode_1280x720_regs[] = {
+	{0x0342, 0x1b},
+	{0x0343, 0x08},
+	{0x0340, 0x04},
+	{0x0341, 0x3b},
+	{0x0344, 0x08},
+	{0x0345, 0x10},
+	{0x0346, 0x07},
+	{0x0347, 0xf0},
+	{0x0348, 0x1c},
+	{0x0349, 0x0f},
+	{0x034a, 0x13},
+	{0x034b, 0x3f},
+	{0x0900, 0x01},
+	{0x0901, 0x44},
+	{0x0902, 0x08},
+	{0x30d8, 0x04},
+	{0x3200, 0x43},
+	{0x3201, 0x43},
+	{0x0408, 0x00},
+	{0x0409, 0x00},
+	{0x040a, 0x00},
+	{0x040b, 0x00},
+	{0x040c, 0x05},
+	{0x040d, 0x00},
+	{0x040e, 0x02},
+	{0x040f, 0xd0},
+	{0x034c, 0x05},
+	{0x034d, 0x00},
+	{0x034e, 0x02},
+	{0x034f, 0xd0},
+	{0x30d9, 0x00},
+	{0x32d5, 0x00},
+	{0x32d6, 0x00},
+	{0x401e, 0x00},
+	{0x40b8, 0x01},
+	{0x40b9, 0x2c},
+	{0x40bc, 0x01},
+	{0x40bd, 0x18},
+	{0x40be, 0x00},
+	{0x40bf, 0x00},
+	{0x41a4, 0x00},
+	{0x5a09, 0x01},
+	{0x5a17, 0x01},
+	{0x5a25, 0x01},
+	{0x5a33, 0x01},
+	{0x98d7, 0xb4},
+	{0x98d8, 0x8c},
+	{0x98d9, 0x0a},
+	{0x99c4, 0x16},
+	{0x341a, 0x00},
+	{0x341b, 0x00},
+	{0x341c, 0x00},
+	{0x341d, 0x00},
+	{0x341e, 0x00},
+	{0x341f, 0x50},
+	{0x3420, 0x00},
+	{0x3421, 0x3c},
+};
+
+/* Mode configs */
+static const struct arducam_64mp_mode supported_modes[] = {
+	{
+		.width = 9152,
+		.height = 6944,
+		.line_length_pix = 0xb6b2,
+		.crop = {
+			.left = ARDUCAM_64MP_PIXEL_ARRAY_LEFT,
+			.top = ARDUCAM_64MP_PIXEL_ARRAY_TOP,
+			.width = 9248,
+			.height = 6944,
+		},
+		.timeperframe_default = {
+			.numerator = 100,
+			.denominator = 270
+		},
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_9152x6944_regs),
+			.regs = mode_9152x6944_regs,
+		}
+	}, {
+		.width = 8000,
+		.height = 6000,
+		.line_length_pix = 0xb6b2,
+		.crop = {
+			.left = ARDUCAM_64MP_PIXEL_ARRAY_LEFT + 624,
+			.top = ARDUCAM_64MP_PIXEL_ARRAY_TOP + 472,
+			.width = 9248,
+			.height = 6944,
+		},
+		.timeperframe_default = {
+			.numerator = 100,
+			.denominator = 300
+		},
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_8000x6000_regs),
+			.regs = mode_8000x6000_regs,
+		}
+	}, {
+		.width = 4624,
+		.height = 3472,
+		.line_length_pix = 0x6397,
+		.crop = {
+			.left = ARDUCAM_64MP_PIXEL_ARRAY_LEFT,
+			.top = ARDUCAM_64MP_PIXEL_ARRAY_TOP,
+			.width = 9248,
+			.height = 6944,
+		},
+		.timeperframe_default = {
+			.numerator = 100,
+			.denominator = 1000
+		},
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_4624x3472_regs),
+			.regs = mode_4624x3472_regs,
+		}
+	}, {
+		.width = 3840,
+		.height = 2160,
+		.line_length_pix = 0x4eb7,
+		.crop = {
+			.left = ARDUCAM_64MP_PIXEL_ARRAY_LEFT + 784,
+			.top = ARDUCAM_64MP_PIXEL_ARRAY_TOP + 1312,
+			.width = 7680,
+			.height = 4320,
+		},
+		.timeperframe_default = {
+			.numerator = 100,
+			.denominator = 2000
+		},
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_3840x2160_regs),
+			.regs = mode_3840x2160_regs,
+		}
+	}, {
+		.width = 2312,
+		.height = 1736,
+		.line_length_pix = 0x3360,
+		.crop = {
+			.left = ARDUCAM_64MP_PIXEL_ARRAY_LEFT,
+			.top = ARDUCAM_64MP_PIXEL_ARRAY_TOP,
+			.width = 9248,
+			.height = 6944,
+		},
+		.timeperframe_default = {
+			.numerator = 100,
+			.denominator = 3000
+		},
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_2312x1736_regs),
+			.regs = mode_2312x1736_regs,
+		}
+	}, {
+		.width = 1920,
+		.height = 1080,
+		.line_length_pix = 0x29e3,
+		.crop = {
+			.left = ARDUCAM_64MP_PIXEL_ARRAY_LEFT + 784,
+			.top = ARDUCAM_64MP_PIXEL_ARRAY_TOP + 1312,
+			.width = 7680,
+			.height = 4320,
+		},
+		.timeperframe_default = {
+			.numerator = 100,
+			.denominator = 6000
+		},
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_1920x1080_regs),
+			.regs = mode_1920x1080_regs,
+		}
+	}, {
+		.width = 1280,
+		.height = 720,
+		.line_length_pix = 0x1b08,
+		.crop = {
+			.left = ARDUCAM_64MP_PIXEL_ARRAY_LEFT + 2064,
+			.top = ARDUCAM_64MP_PIXEL_ARRAY_TOP + 2032,
+			.width = 5120,
+			.height = 2880,
+		},
+		.timeperframe_default = {
+			.numerator = 100,
+			.denominator = 12000
+		},
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_1280x720_regs),
+			.regs = mode_1280x720_regs,
+		}
+	},
+};
+
+/*
+ * The supported formats.
+ * This table MUST contain 4 entries per format, to cover the various flip
+ * combinations in the order
+ * - no flip
+ * - h flip
+ * - v flip
+ * - h&v flips
+ */
+static const u32 codes[] = {
+	/* 10-bit modes. */
+	MEDIA_BUS_FMT_SRGGB10_1X10,
+	MEDIA_BUS_FMT_SGRBG10_1X10,
+	MEDIA_BUS_FMT_SGBRG10_1X10,
+	MEDIA_BUS_FMT_SBGGR10_1X10,
+};
+
+static const char * const arducam_64mp_test_pattern_menu[] = {
+	"Disabled",
+	"Color Bars",
+	"Solid Color",
+	"Grey Color Bars",
+	"PN9"
+};
+
+static const int arducam_64mp_test_pattern_val[] = {
+	ARDUCAM_64MP_TEST_PATTERN_DISABLE,
+	ARDUCAM_64MP_TEST_PATTERN_COLOR_BARS,
+	ARDUCAM_64MP_TEST_PATTERN_SOLID_COLOR,
+	ARDUCAM_64MP_TEST_PATTERN_GREY_COLOR,
+	ARDUCAM_64MP_TEST_PATTERN_PN9,
+};
+
+/* regulator supplies */
+static const char * const arducam_64mp_supply_name[] = {
+	/* Supplies can be enabled in any order */
+	"VANA",  /* Analog (2.8V) supply */
+	"VDIG",  /* Digital Core (1.05V) supply */
+	"VDDL",  /* IF (1.8V) supply */
+};
+
+#define ARDUCAM_64MP_NUM_SUPPLIES ARRAY_SIZE(arducam_64mp_supply_name)
+
+/*
+ * Initialisation delay between XCLR low->high and the moment when the sensor
+ * can start capture (i.e. can leave software standby), given by T7 in the
+ * datasheet is 7.7ms.  This does include I2C setup time as well.
+ *
+ * Note, that delay between XCLR low->high and reading the CCI ID register (T6
+ * in the datasheet) is much smaller - 1ms.
+ */
+#define ARDUCAM_64MP_XCLR_MIN_DELAY_US		8000
+#define ARDUCAM_64MP_XCLR_DELAY_RANGE_US	1000
+
+struct arducam_64mp {
+	struct v4l2_subdev sd;
+	struct media_pad pad[NUM_PADS];
+
+	unsigned int fmt_code;
+
+	struct clk *xclk;
+
+	struct gpio_desc *reset_gpio;
+	struct regulator_bulk_data supplies[ARDUCAM_64MP_NUM_SUPPLIES];
+
+	struct v4l2_ctrl_handler ctrl_handler;
+	/* V4L2 Controls */
+	struct v4l2_ctrl *pixel_rate;
+	struct v4l2_ctrl *exposure;
+	struct v4l2_ctrl *vflip;
+	struct v4l2_ctrl *hflip;
+	struct v4l2_ctrl *vblank;
+	struct v4l2_ctrl *hblank;
+
+	/* Current mode */
+	const struct arducam_64mp_mode *mode;
+
+	/*
+	 * Mutex for serialized access:
+	 * Protect sensor module set pad format and start/stop streaming safely.
+	 */
+	struct mutex mutex;
+
+	/* Streaming on/off */
+	bool streaming;
+
+	/* Rewrite common registers on stream on? */
+	bool common_regs_written;
+
+	/* Current long exposure factor in use. Set through V4L2_CID_VBLANK */
+	unsigned int long_exp_shift;
+};
+
+static inline struct arducam_64mp *to_arducam_64mp(struct v4l2_subdev *_sd)
+{
+	return container_of(_sd, struct arducam_64mp, sd);
+}
+
+/* Read registers up to 2 at a time */
+static int arducam_64mp_read_reg(struct i2c_client *client,
+				 u16 reg, u32 len, u32 *val)
+{
+	struct i2c_msg msgs[2];
+	u8 addr_buf[2] = { reg >> 8, reg & 0xff };
+	u8 data_buf[4] = { 0, };
+	int ret;
+
+	if (len > 4)
+		return -EINVAL;
+
+	/* Write register address */
+	msgs[0].addr = client->addr;
+	msgs[0].flags = 0;
+	msgs[0].len = ARRAY_SIZE(addr_buf);
+	msgs[0].buf = addr_buf;
+
+	/* Read data from register */
+	msgs[1].addr = client->addr;
+	msgs[1].flags = I2C_M_RD;
+	msgs[1].len = len;
+	msgs[1].buf = &data_buf[4 - len];
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret != ARRAY_SIZE(msgs))
+		return -EIO;
+
+	*val = get_unaligned_be32(data_buf);
+
+	return 0;
+}
+
+/* Write registers up to 2 at a time */
+static int arducam_64mp_write_reg(struct arducam_64mp *arducam_64mp,
+				  u16 reg, u32 len, u32 val)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&arducam_64mp->sd);
+	u8 buf[6];
+
+	if (len > 4)
+		return -EINVAL;
+
+	put_unaligned_be16(reg, buf);
+	put_unaligned_be32(val << (8 * (4 - len)), buf + 2);
+	if (i2c_master_send(client, buf, len + 2) != len + 2)
+		return -EIO;
+
+	return 0;
+}
+
+/* Write a list of registers */
+static int arducam_64mp_write_regs(struct arducam_64mp *arducam_64mp,
+				   const struct arducam_64mp_reg *regs, u32 len)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&arducam_64mp->sd);
+	unsigned int i;
+	int ret;
+
+	for (i = 0; i < len; i++) {
+		ret = arducam_64mp_write_reg(arducam_64mp, regs[i].address, 1,
+					     regs[i].val);
+		if (ret) {
+			dev_err_ratelimited(&client->dev,
+					    "Failed to write reg 0x%4.4x. error = %d\n",
+					    regs[i].address, ret);
+
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+/* Get bayer order based on flip setting. */
+static u32 arducam_64mp_get_format_code(struct arducam_64mp *arducam_64mp)
+{
+	unsigned int i;
+
+	lockdep_assert_held(&arducam_64mp->mutex);
+
+	i = (arducam_64mp->vflip->val ? 2 : 0) |
+	    (arducam_64mp->hflip->val ? 1 : 0);
+
+	return codes[i];
+}
+
+static int arducam_64mp_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+	struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd);
+	struct v4l2_mbus_framefmt *try_fmt_img =
+		v4l2_subdev_state_get_format(fh->state, IMAGE_PAD);
+	struct v4l2_mbus_framefmt *try_fmt_meta =
+		v4l2_subdev_state_get_format(fh->state, METADATA_PAD);
+	struct v4l2_rect *try_crop;
+
+	mutex_lock(&arducam_64mp->mutex);
+
+	/* Initialize try_fmt for the image pad */
+	try_fmt_img->width = supported_modes[0].width;
+	try_fmt_img->height = supported_modes[0].height;
+	try_fmt_img->code = arducam_64mp_get_format_code(arducam_64mp);
+	try_fmt_img->field = V4L2_FIELD_NONE;
+
+	/* Initialize try_fmt for the embedded metadata pad */
+	try_fmt_meta->width = ARDUCAM_64MP_EMBEDDED_LINE_WIDTH;
+	try_fmt_meta->height = ARDUCAM_64MP_NUM_EMBEDDED_LINES;
+	try_fmt_meta->code = MEDIA_BUS_FMT_SENSOR_DATA;
+	try_fmt_meta->field = V4L2_FIELD_NONE;
+
+	/* Initialize try_crop */
+	try_crop = v4l2_subdev_state_get_crop(fh->state, IMAGE_PAD);
+	try_crop->left = ARDUCAM_64MP_PIXEL_ARRAY_LEFT;
+	try_crop->top = ARDUCAM_64MP_PIXEL_ARRAY_TOP;
+	try_crop->width = ARDUCAM_64MP_PIXEL_ARRAY_WIDTH;
+	try_crop->height = ARDUCAM_64MP_PIXEL_ARRAY_HEIGHT;
+
+	mutex_unlock(&arducam_64mp->mutex);
+
+	return 0;
+}
+
+static void
+arducam_64mp_adjust_exposure_range(struct arducam_64mp *arducam_64mp)
+{
+	int exposure_max, exposure_def;
+
+	/* Honour the VBLANK limits when setting exposure. */
+	exposure_max = arducam_64mp->mode->height + arducam_64mp->vblank->val -
+		       ARDUCAM_64MP_EXPOSURE_OFFSET;
+	exposure_def = min(exposure_max, arducam_64mp->exposure->val);
+	__v4l2_ctrl_modify_range(arducam_64mp->exposure,
+				 arducam_64mp->exposure->minimum,
+				 exposure_max, arducam_64mp->exposure->step,
+				 exposure_def);
+}
+
+static int arducam_64mp_set_frame_length(struct arducam_64mp *arducam_64mp,
+					 unsigned int vblank)
+{
+	unsigned int val = vblank + arducam_64mp->mode->height;
+	int ret = 0;
+
+	arducam_64mp->long_exp_shift = 0;
+
+	while (val > ARDUCAM_64MP_FRAME_LENGTH_MAX) {
+		arducam_64mp->long_exp_shift++;
+		val >>= 1;
+	}
+
+	ret = arducam_64mp_write_reg(arducam_64mp,
+				     ARDUCAM_64MP_REG_FRAME_LENGTH,
+				     ARDUCAM_64MP_REG_VALUE_16BIT, val);
+	if (ret)
+		return ret;
+
+	return arducam_64mp_write_reg(arducam_64mp,
+				      ARDUCAM_64MP_LONG_EXP_SHIFT_REG,
+				      ARDUCAM_64MP_REG_VALUE_08BIT,
+				      arducam_64mp->long_exp_shift);
+}
+
+static int arducam_64mp_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct arducam_64mp *arducam_64mp =
+		container_of(ctrl->handler, struct arducam_64mp, ctrl_handler);
+	struct i2c_client *client = v4l2_get_subdevdata(&arducam_64mp->sd);
+	int ret;
+	u32 val;
+	/*
+	 * The VBLANK control may change the limits of usable exposure, so check
+	 * and adjust if necessary.
+	 */
+	if (ctrl->id == V4L2_CID_VBLANK)
+		arducam_64mp_adjust_exposure_range(arducam_64mp);
+
+	/*
+	 * Applying V4L2 control value only happens
+	 * when power is up for streaming
+	 */
+	if (pm_runtime_get_if_in_use(&client->dev) == 0)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_ANALOGUE_GAIN:
+		ret = arducam_64mp_write_reg(arducam_64mp,
+					     ARDUCAM_64MP_REG_ANALOG_GAIN,
+					     ARDUCAM_64MP_REG_VALUE_16BIT,
+					     ctrl->val);
+		break;
+	case V4L2_CID_EXPOSURE:
+		val = ctrl->val >> arducam_64mp->long_exp_shift;
+		ret = arducam_64mp_write_reg(arducam_64mp,
+					     ARDUCAM_64MP_REG_EXPOSURE,
+					     ARDUCAM_64MP_REG_VALUE_16BIT,
+					     val);
+		break;
+	case V4L2_CID_DIGITAL_GAIN:
+		ret = arducam_64mp_write_reg(arducam_64mp,
+					     ARDUCAM_64MP_REG_DIGITAL_GAIN,
+					     ARDUCAM_64MP_REG_VALUE_16BIT,
+					     ctrl->val);
+		break;
+	case V4L2_CID_TEST_PATTERN:
+		val = arducam_64mp_test_pattern_val[ctrl->val];
+		ret = arducam_64mp_write_reg(arducam_64mp,
+					     ARDUCAM_64MP_REG_TEST_PATTERN,
+					     ARDUCAM_64MP_REG_VALUE_16BIT,
+					     val);
+		break;
+	case V4L2_CID_TEST_PATTERN_RED:
+		ret = arducam_64mp_write_reg(arducam_64mp,
+					     ARDUCAM_64MP_REG_TEST_PATTERN_R,
+					     ARDUCAM_64MP_REG_VALUE_16BIT,
+					     ctrl->val);
+		break;
+	case V4L2_CID_TEST_PATTERN_GREENR:
+		ret = arducam_64mp_write_reg(arducam_64mp,
+					     ARDUCAM_64MP_REG_TEST_PATTERN_GR,
+					     ARDUCAM_64MP_REG_VALUE_16BIT,
+					     ctrl->val);
+		break;
+	case V4L2_CID_TEST_PATTERN_BLUE:
+		ret = arducam_64mp_write_reg(arducam_64mp,
+					     ARDUCAM_64MP_REG_TEST_PATTERN_B,
+					     ARDUCAM_64MP_REG_VALUE_16BIT,
+					     ctrl->val);
+		break;
+	case V4L2_CID_TEST_PATTERN_GREENB:
+		ret = arducam_64mp_write_reg(arducam_64mp,
+					     ARDUCAM_64MP_REG_TEST_PATTERN_GB,
+					     ARDUCAM_64MP_REG_VALUE_16BIT,
+					     ctrl->val);
+		break;
+	case V4L2_CID_HFLIP:
+	case V4L2_CID_VFLIP:
+		ret = arducam_64mp_write_reg(arducam_64mp,
+					     ARDUCAM_64MP_REG_ORIENTATION, 1,
+					     arducam_64mp->hflip->val |
+						arducam_64mp->vflip->val << 1);
+		break;
+	case V4L2_CID_VBLANK:
+		ret = arducam_64mp_set_frame_length(arducam_64mp, ctrl->val);
+		break;
+	default:
+		dev_info(&client->dev,
+			 "ctrl(id:0x%x,val:0x%x) is not handled\n",
+			 ctrl->id, ctrl->val);
+		ret = -EINVAL;
+		break;
+	}
+
+	pm_runtime_put(&client->dev);
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops arducam_64mp_ctrl_ops = {
+	.s_ctrl = arducam_64mp_set_ctrl,
+};
+
+static int arducam_64mp_enum_mbus_code(struct v4l2_subdev *sd,
+				       struct v4l2_subdev_state *sd_state,
+				       struct v4l2_subdev_mbus_code_enum *code)
+{
+	struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd);
+
+	if (code->pad >= NUM_PADS)
+		return -EINVAL;
+
+	if (code->pad == IMAGE_PAD) {
+		if (code->index > 0)
+			return -EINVAL;
+
+		code->code = arducam_64mp_get_format_code(arducam_64mp);
+	} else {
+		if (code->index > 0)
+			return -EINVAL;
+
+		code->code = MEDIA_BUS_FMT_SENSOR_DATA;
+	}
+
+	return 0;
+}
+
+static int arducam_64mp_enum_frame_size(struct v4l2_subdev *sd,
+					struct v4l2_subdev_state *sd_state,
+					struct v4l2_subdev_frame_size_enum *fse)
+{
+	struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd);
+
+	if (fse->pad >= NUM_PADS)
+		return -EINVAL;
+
+	if (fse->pad == IMAGE_PAD) {
+		if (fse->index >= ARRAY_SIZE(supported_modes))
+			return -EINVAL;
+
+		if (fse->code != arducam_64mp_get_format_code(arducam_64mp))
+			return -EINVAL;
+
+		fse->min_width = supported_modes[fse->index].width;
+		fse->max_width = fse->min_width;
+		fse->min_height = supported_modes[fse->index].height;
+		fse->max_height = fse->min_height;
+	} else {
+		if (fse->code != MEDIA_BUS_FMT_SENSOR_DATA || fse->index > 0)
+			return -EINVAL;
+
+		fse->min_width = ARDUCAM_64MP_EMBEDDED_LINE_WIDTH;
+		fse->max_width = fse->min_width;
+		fse->min_height = ARDUCAM_64MP_NUM_EMBEDDED_LINES;
+		fse->max_height = fse->min_height;
+	}
+
+	return 0;
+}
+
+static void arducam_64mp_reset_colorspace(struct v4l2_mbus_framefmt *fmt)
+{
+	fmt->colorspace = V4L2_COLORSPACE_RAW;
+	fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace);
+	fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true,
+							  fmt->colorspace,
+							  fmt->ycbcr_enc);
+	fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace);
+}
+
+static void
+arducam_64mp_update_image_pad_format(struct arducam_64mp *arducam_64mp,
+				     const struct arducam_64mp_mode *mode,
+				     struct v4l2_subdev_format *fmt)
+{
+	fmt->format.width = mode->width;
+	fmt->format.height = mode->height;
+	fmt->format.field = V4L2_FIELD_NONE;
+	arducam_64mp_reset_colorspace(&fmt->format);
+}
+
+static void
+arducam_64mp_update_metadata_pad_format(struct v4l2_subdev_format *fmt)
+{
+	fmt->format.width = ARDUCAM_64MP_EMBEDDED_LINE_WIDTH;
+	fmt->format.height = ARDUCAM_64MP_NUM_EMBEDDED_LINES;
+	fmt->format.code = MEDIA_BUS_FMT_SENSOR_DATA;
+	fmt->format.field = V4L2_FIELD_NONE;
+}
+
+static int arducam_64mp_get_pad_format(struct v4l2_subdev *sd,
+				       struct v4l2_subdev_state *sd_state,
+				       struct v4l2_subdev_format *fmt)
+{
+	struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd);
+
+	if (fmt->pad >= NUM_PADS)
+		return -EINVAL;
+
+	mutex_lock(&arducam_64mp->mutex);
+
+	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+		struct v4l2_mbus_framefmt *try_fmt =
+			v4l2_subdev_state_get_format(sd_state,
+						   fmt->pad);
+		/* update the code which could change due to vflip or hflip: */
+		try_fmt->code = fmt->pad == IMAGE_PAD ?
+				arducam_64mp_get_format_code(arducam_64mp) :
+				MEDIA_BUS_FMT_SENSOR_DATA;
+		fmt->format = *try_fmt;
+	} else {
+		if (fmt->pad == IMAGE_PAD) {
+			arducam_64mp_update_image_pad_format(arducam_64mp,
+							     arducam_64mp->mode,
+							     fmt);
+			fmt->format.code =
+			       arducam_64mp_get_format_code(arducam_64mp);
+		} else {
+			arducam_64mp_update_metadata_pad_format(fmt);
+		}
+	}
+
+	mutex_unlock(&arducam_64mp->mutex);
+	return 0;
+}
+
+static unsigned int
+arducam_64mp_get_frame_length(const struct arducam_64mp_mode *mode,
+			      const struct v4l2_fract *timeperframe)
+{
+	u64 frame_length;
+
+	frame_length = (u64)timeperframe->numerator * ARDUCAM_64MP_PIXEL_RATE;
+	do_div(frame_length,
+	       (u64)timeperframe->denominator * mode->line_length_pix);
+
+	if (WARN_ON(frame_length > ARDUCAM_64MP_FRAME_LENGTH_MAX))
+		frame_length = ARDUCAM_64MP_FRAME_LENGTH_MAX;
+
+	return max_t(unsigned int, frame_length, mode->height);
+}
+
+static void arducam_64mp_set_framing_limits(struct arducam_64mp *arducam_64mp)
+{
+	unsigned int frm_length_min, frm_length_default, hblank;
+	const struct arducam_64mp_mode *mode = arducam_64mp->mode;
+
+	/* The default framerate is highest possible framerate. */
+	frm_length_min =
+		arducam_64mp_get_frame_length(mode,
+					      &mode->timeperframe_default);
+	frm_length_default =
+		arducam_64mp_get_frame_length(mode,
+					      &mode->timeperframe_default);
+
+	/* Default to no long exposure multiplier. */
+	arducam_64mp->long_exp_shift = 0;
+
+	/* Update limits and set FPS to default */
+	__v4l2_ctrl_modify_range(arducam_64mp->vblank,
+				 frm_length_min - mode->height,
+				 ((1 << ARDUCAM_64MP_LONG_EXP_SHIFT_MAX) *
+				  ARDUCAM_64MP_FRAME_LENGTH_MAX) - mode->height,
+				 1, frm_length_default - mode->height);
+
+	/* Setting this will adjust the exposure limits as well. */
+	__v4l2_ctrl_s_ctrl(arducam_64mp->vblank,
+			   frm_length_default - mode->height);
+
+	/*
+	 * Currently PPL is fixed to the mode specified value, so hblank
+	 * depends on mode->width only, and is not changeable in any
+	 * way other than changing the mode.
+	 */
+	hblank = mode->line_length_pix - mode->width;
+	__v4l2_ctrl_modify_range(arducam_64mp->hblank, hblank, hblank, 1,
+				 hblank);
+}
+
+static int arducam_64mp_set_pad_format(struct v4l2_subdev *sd,
+				       struct v4l2_subdev_state *sd_state,
+				       struct v4l2_subdev_format *fmt)
+{
+	struct v4l2_mbus_framefmt *framefmt;
+	const struct arducam_64mp_mode *mode;
+	struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd);
+
+	if (fmt->pad >= NUM_PADS)
+		return -EINVAL;
+
+	mutex_lock(&arducam_64mp->mutex);
+
+	if (fmt->pad == IMAGE_PAD) {
+		/* Bayer order varies with flips */
+		fmt->format.code = arducam_64mp_get_format_code(arducam_64mp);
+
+		mode = v4l2_find_nearest_size(supported_modes,
+					      ARRAY_SIZE(supported_modes),
+					      width, height,
+					      fmt->format.width,
+					      fmt->format.height);
+		arducam_64mp_update_image_pad_format(arducam_64mp, mode, fmt);
+		if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+			framefmt = v4l2_subdev_state_get_format(sd_state,
+							      fmt->pad);
+			*framefmt = fmt->format;
+		} else {
+			arducam_64mp->mode = mode;
+			arducam_64mp->fmt_code = fmt->format.code;
+			arducam_64mp_set_framing_limits(arducam_64mp);
+		}
+	} else {
+		if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+			framefmt = v4l2_subdev_state_get_format(sd_state,
+							      fmt->pad);
+			*framefmt = fmt->format;
+		} else {
+			/* Only one embedded data mode is supported */
+			arducam_64mp_update_metadata_pad_format(fmt);
+		}
+	}
+
+	mutex_unlock(&arducam_64mp->mutex);
+
+	return 0;
+}
+
+static const struct v4l2_rect *
+__arducam_64mp_get_pad_crop(struct arducam_64mp *arducam_64mp,
+			    struct v4l2_subdev_state *sd_state,
+			    unsigned int pad,
+			    enum v4l2_subdev_format_whence which)
+{
+	switch (which) {
+	case V4L2_SUBDEV_FORMAT_TRY:
+		return v4l2_subdev_state_get_crop(sd_state,
+						pad);
+	case V4L2_SUBDEV_FORMAT_ACTIVE:
+		return &arducam_64mp->mode->crop;
+	}
+
+	return NULL;
+}
+
+static int arducam_64mp_get_selection(struct v4l2_subdev *sd,
+				      struct v4l2_subdev_state *sd_state,
+				      struct v4l2_subdev_selection *sel)
+{
+	switch (sel->target) {
+	case V4L2_SEL_TGT_CROP: {
+		struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd);
+
+		mutex_lock(&arducam_64mp->mutex);
+		sel->r = *__arducam_64mp_get_pad_crop(arducam_64mp, sd_state,
+						      sel->pad, sel->which);
+		mutex_unlock(&arducam_64mp->mutex);
+
+		return 0;
+	}
+
+	case V4L2_SEL_TGT_NATIVE_SIZE:
+		sel->r.left = 0;
+		sel->r.top = 0;
+		sel->r.width = ARDUCAM_64MP_NATIVE_WIDTH;
+		sel->r.height = ARDUCAM_64MP_NATIVE_HEIGHT;
+
+		return 0;
+
+	case V4L2_SEL_TGT_CROP_DEFAULT:
+	case V4L2_SEL_TGT_CROP_BOUNDS:
+		sel->r.left = ARDUCAM_64MP_PIXEL_ARRAY_LEFT;
+		sel->r.top = ARDUCAM_64MP_PIXEL_ARRAY_TOP;
+		sel->r.width = ARDUCAM_64MP_PIXEL_ARRAY_WIDTH;
+		sel->r.height = ARDUCAM_64MP_PIXEL_ARRAY_HEIGHT;
+
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+/* Start streaming */
+static int arducam_64mp_start_streaming(struct arducam_64mp *arducam_64mp)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&arducam_64mp->sd);
+	const struct arducam_64mp_reg_list *reg_list;
+	int ret;
+
+	if (!arducam_64mp->common_regs_written) {
+		ret = arducam_64mp_write_regs(arducam_64mp, mode_common_regs,
+					      ARRAY_SIZE(mode_common_regs));
+
+		if (ret) {
+			dev_err(&client->dev, "%s failed to set common settings\n",
+				__func__);
+			return ret;
+		}
+		arducam_64mp->common_regs_written = true;
+	}
+
+	/* Apply default values of current mode */
+	reg_list = &arducam_64mp->mode->reg_list;
+	ret = arducam_64mp_write_regs(arducam_64mp, reg_list->regs,
+				      reg_list->num_of_regs);
+	if (ret) {
+		dev_err(&client->dev, "%s failed to set mode\n", __func__);
+		return ret;
+	}
+
+	/* Apply customized values from user */
+	ret =  __v4l2_ctrl_handler_setup(arducam_64mp->sd.ctrl_handler);
+	if (ret)
+		return ret;
+
+	/* set stream on register */
+	return arducam_64mp_write_reg(arducam_64mp,
+				      ARDUCAM_64MP_REG_MODE_SELECT,
+				      ARDUCAM_64MP_REG_VALUE_08BIT,
+				      ARDUCAM_64MP_MODE_STREAMING);
+}
+
+/* Stop streaming */
+static void arducam_64mp_stop_streaming(struct arducam_64mp *arducam_64mp)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&arducam_64mp->sd);
+	int ret;
+
+	/* set stream off register */
+	ret = arducam_64mp_write_reg(arducam_64mp, ARDUCAM_64MP_REG_MODE_SELECT,
+				     ARDUCAM_64MP_REG_VALUE_08BIT,
+				     ARDUCAM_64MP_MODE_STANDBY);
+	if (ret)
+		dev_err(&client->dev, "%s failed to set stream\n", __func__);
+}
+
+static int arducam_64mp_set_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd);
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	int ret = 0;
+
+	mutex_lock(&arducam_64mp->mutex);
+	if (arducam_64mp->streaming == enable) {
+		mutex_unlock(&arducam_64mp->mutex);
+		return 0;
+	}
+
+	if (enable) {
+		ret = pm_runtime_get_sync(&client->dev);
+		if (ret < 0) {
+			pm_runtime_put_noidle(&client->dev);
+			goto err_unlock;
+		}
+
+		/*
+		 * Apply default & customized values
+		 * and then start streaming.
+		 */
+		ret = arducam_64mp_start_streaming(arducam_64mp);
+		if (ret)
+			goto err_rpm_put;
+	} else {
+		arducam_64mp_stop_streaming(arducam_64mp);
+		pm_runtime_put(&client->dev);
+	}
+
+	arducam_64mp->streaming = enable;
+
+	/* vflip and hflip cannot change during streaming */
+	__v4l2_ctrl_grab(arducam_64mp->vflip, enable);
+	__v4l2_ctrl_grab(arducam_64mp->hflip, enable);
+
+	mutex_unlock(&arducam_64mp->mutex);
+
+	return ret;
+
+err_rpm_put:
+	pm_runtime_put(&client->dev);
+err_unlock:
+	mutex_unlock(&arducam_64mp->mutex);
+
+	return ret;
+}
+
+/* Power/clock management functions */
+static int arducam_64mp_power_on(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd);
+	int ret;
+
+	ret = regulator_bulk_enable(ARDUCAM_64MP_NUM_SUPPLIES,
+				    arducam_64mp->supplies);
+	if (ret) {
+		dev_err(&client->dev, "%s: failed to enable regulators\n",
+			__func__);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(arducam_64mp->xclk);
+	if (ret) {
+		dev_err(&client->dev, "%s: failed to enable clock\n",
+			__func__);
+		goto reg_off;
+	}
+
+	gpiod_set_value_cansleep(arducam_64mp->reset_gpio, 1);
+	usleep_range(ARDUCAM_64MP_XCLR_MIN_DELAY_US,
+		     ARDUCAM_64MP_XCLR_MIN_DELAY_US +
+					ARDUCAM_64MP_XCLR_DELAY_RANGE_US);
+
+	return 0;
+
+reg_off:
+	regulator_bulk_disable(ARDUCAM_64MP_NUM_SUPPLIES,
+			       arducam_64mp->supplies);
+	return ret;
+}
+
+static int arducam_64mp_power_off(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd);
+
+	gpiod_set_value_cansleep(arducam_64mp->reset_gpio, 0);
+	regulator_bulk_disable(ARDUCAM_64MP_NUM_SUPPLIES,
+			       arducam_64mp->supplies);
+	clk_disable_unprepare(arducam_64mp->xclk);
+
+	/* Force reprogramming of the common registers when powered up again. */
+	arducam_64mp->common_regs_written = false;
+
+	return 0;
+}
+
+static int __maybe_unused arducam_64mp_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd);
+
+	if (arducam_64mp->streaming)
+		arducam_64mp_stop_streaming(arducam_64mp);
+
+	return 0;
+}
+
+static int __maybe_unused arducam_64mp_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd);
+	int ret;
+
+	if (arducam_64mp->streaming) {
+		ret = arducam_64mp_start_streaming(arducam_64mp);
+		if (ret)
+			goto error;
+	}
+
+	return 0;
+
+error:
+	arducam_64mp_stop_streaming(arducam_64mp);
+	arducam_64mp->streaming = 0;
+	return ret;
+}
+
+static int arducam_64mp_get_regulators(struct arducam_64mp *arducam_64mp)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&arducam_64mp->sd);
+	unsigned int i;
+
+	for (i = 0; i < ARDUCAM_64MP_NUM_SUPPLIES; i++)
+		arducam_64mp->supplies[i].supply = arducam_64mp_supply_name[i];
+
+	return devm_regulator_bulk_get(&client->dev,
+				       ARDUCAM_64MP_NUM_SUPPLIES,
+				       arducam_64mp->supplies);
+}
+
+/* Verify chip ID */
+static int arducam_64mp_identify_module(struct arducam_64mp *arducam_64mp)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&arducam_64mp->sd);
+	struct i2c_client *arducam_identifier;
+	int ret;
+	u32 val;
+
+	arducam_identifier = i2c_new_dummy_device(client->adapter, 0x50);
+	if (IS_ERR(arducam_identifier)) {
+		dev_err(&client->dev, "failed to create arducam_identifier\n");
+		return PTR_ERR(arducam_identifier);
+	}
+
+	ret = arducam_64mp_read_reg(arducam_identifier,
+				    ARDUCAM_64MP_REG_CHIP_ID,
+				    ARDUCAM_64MP_REG_VALUE_16BIT, &val);
+	if (ret) {
+		dev_err(&client->dev, "failed to read chip id %x, with error %d\n",
+			ARDUCAM_64MP_CHIP_ID, ret);
+		goto error;
+	}
+
+	if (val != ARDUCAM_64MP_CHIP_ID) {
+		dev_err(&client->dev, "chip id mismatch: %x!=%x\n",
+			ARDUCAM_64MP_CHIP_ID, val);
+		ret = -EIO;
+		goto error;
+	}
+
+	dev_info(&client->dev, "Device found Arducam 64MP.\n");
+
+error:
+	i2c_unregister_device(arducam_identifier);
+
+	return ret;
+}
+
+static const struct v4l2_subdev_core_ops arducam_64mp_core_ops = {
+	.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_video_ops arducam_64mp_video_ops = {
+	.s_stream = arducam_64mp_set_stream,
+};
+
+static const struct v4l2_subdev_pad_ops arducam_64mp_pad_ops = {
+	.enum_mbus_code = arducam_64mp_enum_mbus_code,
+	.get_fmt = arducam_64mp_get_pad_format,
+	.set_fmt = arducam_64mp_set_pad_format,
+	.get_selection = arducam_64mp_get_selection,
+	.enum_frame_size = arducam_64mp_enum_frame_size,
+};
+
+static const struct v4l2_subdev_ops arducam_64mp_subdev_ops = {
+	.core = &arducam_64mp_core_ops,
+	.video = &arducam_64mp_video_ops,
+	.pad = &arducam_64mp_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops arducam_64mp_internal_ops = {
+	.open = arducam_64mp_open,
+};
+
+/* Initialize control handlers */
+static int arducam_64mp_init_controls(struct arducam_64mp *arducam_64mp)
+{
+	struct v4l2_ctrl_handler *ctrl_hdlr;
+	struct i2c_client *client = v4l2_get_subdevdata(&arducam_64mp->sd);
+	struct v4l2_fwnode_device_properties props;
+	struct v4l2_ctrl *link_freq;
+	unsigned int i;
+	int ret;
+	u8 test_pattern_max;
+	u8 link_freq_max;
+
+	ctrl_hdlr = &arducam_64mp->ctrl_handler;
+	ret = v4l2_ctrl_handler_init(ctrl_hdlr, 16);
+	if (ret)
+		return ret;
+
+	mutex_init(&arducam_64mp->mutex);
+	ctrl_hdlr->lock = &arducam_64mp->mutex;
+
+	/* By default, PIXEL_RATE is read only */
+	arducam_64mp->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr,
+						     &arducam_64mp_ctrl_ops,
+						     V4L2_CID_PIXEL_RATE,
+						     ARDUCAM_64MP_PIXEL_RATE,
+						     ARDUCAM_64MP_PIXEL_RATE, 1,
+						     ARDUCAM_64MP_PIXEL_RATE);
+
+	/* LINK_FREQ is also read only */
+	link_freq_max = ARRAY_SIZE(arducam_64mp_link_freq_menu) - 1;
+	link_freq =
+		v4l2_ctrl_new_int_menu(ctrl_hdlr, &arducam_64mp_ctrl_ops,
+				       V4L2_CID_LINK_FREQ,
+				       link_freq_max, 0,
+				       arducam_64mp_link_freq_menu);
+	if (link_freq)
+		link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	/*
+	 * Create the controls here, but mode specific limits are setup
+	 * in the arducam_64mp_set_framing_limits() call below.
+	 */
+	arducam_64mp->vblank = v4l2_ctrl_new_std(ctrl_hdlr,
+						 &arducam_64mp_ctrl_ops,
+						 V4L2_CID_VBLANK,
+						 0, 0xffff, 1, 0);
+	arducam_64mp->hblank = v4l2_ctrl_new_std(ctrl_hdlr,
+						 &arducam_64mp_ctrl_ops,
+						 V4L2_CID_HBLANK,
+						 0, 0xffff, 1, 0);
+
+	/* HBLANK is read-only, but does change with mode. */
+	if (arducam_64mp->hblank)
+		arducam_64mp->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	arducam_64mp->exposure =
+		v4l2_ctrl_new_std(ctrl_hdlr,
+				  &arducam_64mp_ctrl_ops,
+				  V4L2_CID_EXPOSURE,
+				  ARDUCAM_64MP_EXPOSURE_MIN,
+				  ARDUCAM_64MP_EXPOSURE_MAX,
+				  ARDUCAM_64MP_EXPOSURE_STEP,
+				  ARDUCAM_64MP_EXPOSURE_DEFAULT);
+
+	v4l2_ctrl_new_std(ctrl_hdlr, &arducam_64mp_ctrl_ops,
+			  V4L2_CID_ANALOGUE_GAIN, ARDUCAM_64MP_ANA_GAIN_MIN,
+			  ARDUCAM_64MP_ANA_GAIN_MAX, ARDUCAM_64MP_ANA_GAIN_STEP,
+			  ARDUCAM_64MP_ANA_GAIN_DEFAULT);
+
+	v4l2_ctrl_new_std(ctrl_hdlr, &arducam_64mp_ctrl_ops,
+			  V4L2_CID_DIGITAL_GAIN, ARDUCAM_64MP_DGTL_GAIN_MIN,
+			  ARDUCAM_64MP_DGTL_GAIN_MAX,
+			  ARDUCAM_64MP_DGTL_GAIN_STEP,
+			  ARDUCAM_64MP_DGTL_GAIN_DEFAULT);
+
+	arducam_64mp->hflip = v4l2_ctrl_new_std(ctrl_hdlr,
+						&arducam_64mp_ctrl_ops,
+						V4L2_CID_HFLIP, 0, 1, 1, 0);
+	if (arducam_64mp->hflip)
+		arducam_64mp->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+	arducam_64mp->vflip = v4l2_ctrl_new_std(ctrl_hdlr,
+						&arducam_64mp_ctrl_ops,
+						V4L2_CID_VFLIP, 0, 1, 1, 0);
+	if (arducam_64mp->vflip)
+		arducam_64mp->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+	test_pattern_max = ARRAY_SIZE(arducam_64mp_test_pattern_menu) - 1;
+	v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &arducam_64mp_ctrl_ops,
+				     V4L2_CID_TEST_PATTERN,
+				     test_pattern_max,
+				     0, 0, arducam_64mp_test_pattern_menu);
+	for (i = 0; i < 4; i++) {
+		/*
+		 * The assumption is that
+		 * V4L2_CID_TEST_PATTERN_GREENR == V4L2_CID_TEST_PATTERN_RED + 1
+		 * V4L2_CID_TEST_PATTERN_BLUE   == V4L2_CID_TEST_PATTERN_RED + 2
+		 * V4L2_CID_TEST_PATTERN_GREENB == V4L2_CID_TEST_PATTERN_RED + 3
+		 */
+		v4l2_ctrl_new_std(ctrl_hdlr, &arducam_64mp_ctrl_ops,
+				  V4L2_CID_TEST_PATTERN_RED + i,
+				  ARDUCAM_64MP_TEST_PATTERN_COLOUR_MIN,
+				  ARDUCAM_64MP_TEST_PATTERN_COLOUR_MAX,
+				  ARDUCAM_64MP_TEST_PATTERN_COLOUR_STEP,
+				  ARDUCAM_64MP_TEST_PATTERN_COLOUR_MAX);
+		/* The "Solid color" pattern is white by default */
+	}
+
+	if (ctrl_hdlr->error) {
+		ret = ctrl_hdlr->error;
+		dev_err(&client->dev, "%s control init failed (%d)\n",
+			__func__, ret);
+		goto error;
+	}
+
+	ret = v4l2_fwnode_device_parse(&client->dev, &props);
+	if (ret)
+		goto error;
+
+	ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &arducam_64mp_ctrl_ops,
+					      &props);
+	if (ret)
+		goto error;
+
+	arducam_64mp->sd.ctrl_handler = ctrl_hdlr;
+
+	/* Setup exposure and frame/line length limits. */
+	arducam_64mp_set_framing_limits(arducam_64mp);
+
+	return 0;
+
+error:
+	v4l2_ctrl_handler_free(ctrl_hdlr);
+	mutex_destroy(&arducam_64mp->mutex);
+
+	return ret;
+}
+
+static void arducam_64mp_free_controls(struct arducam_64mp *arducam_64mp)
+{
+	v4l2_ctrl_handler_free(arducam_64mp->sd.ctrl_handler);
+	mutex_destroy(&arducam_64mp->mutex);
+}
+
+static int arducam_64mp_check_hwcfg(struct device *dev)
+{
+	struct fwnode_handle *endpoint;
+	struct v4l2_fwnode_endpoint ep_cfg = {
+		.bus_type = V4L2_MBUS_CSI2_DPHY
+	};
+	int ret = -EINVAL;
+
+	endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
+	if (!endpoint) {
+		dev_err(dev, "endpoint node not found\n");
+		return -EINVAL;
+	}
+
+	if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) {
+		dev_err(dev, "could not parse endpoint\n");
+		goto error_out;
+	}
+
+	/* Check the number of MIPI CSI2 data lanes */
+	if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2) {
+		dev_err(dev, "only 2 data lanes are currently supported\n");
+		goto error_out;
+	}
+
+	/* Check the link frequency set in device tree */
+	if (!ep_cfg.nr_of_link_frequencies) {
+		dev_err(dev, "link-frequency property not found in DT\n");
+		goto error_out;
+	}
+
+	if (ep_cfg.nr_of_link_frequencies != 1 ||
+	    ep_cfg.link_frequencies[0] != ARDUCAM_64MP_DEFAULT_LINK_FREQ) {
+		dev_err(dev, "Link frequency not supported: %lld\n",
+			ep_cfg.link_frequencies[0]);
+		goto error_out;
+	}
+
+	ret = 0;
+
+error_out:
+	v4l2_fwnode_endpoint_free(&ep_cfg);
+	fwnode_handle_put(endpoint);
+
+	return ret;
+}
+
+static const struct of_device_id arducam_64mp_dt_ids[] = {
+	{ .compatible = "arducam,64mp"},
+	{ /* sentinel */ }
+};
+
+static int arducam_64mp_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct arducam_64mp *arducam_64mp;
+	const struct of_device_id *match;
+	u32 xclk_freq;
+	int ret;
+
+	arducam_64mp = devm_kzalloc(&client->dev, sizeof(*arducam_64mp),
+				    GFP_KERNEL);
+	if (!arducam_64mp)
+		return -ENOMEM;
+
+	v4l2_i2c_subdev_init(&arducam_64mp->sd, client,
+			     &arducam_64mp_subdev_ops);
+
+	match = of_match_device(arducam_64mp_dt_ids, dev);
+	if (!match)
+		return -ENODEV;
+
+	/* Check the hardware configuration in device tree */
+	if (arducam_64mp_check_hwcfg(dev))
+		return -EINVAL;
+
+	/* Get system clock (xclk) */
+	arducam_64mp->xclk = devm_clk_get(dev, NULL);
+	if (IS_ERR(arducam_64mp->xclk)) {
+		dev_err(dev, "failed to get xclk\n");
+		return PTR_ERR(arducam_64mp->xclk);
+	}
+
+	xclk_freq = clk_get_rate(arducam_64mp->xclk);
+	if (xclk_freq != ARDUCAM_64MP_XCLK_FREQ) {
+		dev_err(dev, "xclk frequency not supported: %d Hz\n",
+			xclk_freq);
+		return -EINVAL;
+	}
+
+	ret = arducam_64mp_get_regulators(arducam_64mp);
+	if (ret) {
+		dev_err(dev, "failed to get regulators\n");
+		return ret;
+	}
+
+	/* Request optional enable pin */
+	arducam_64mp->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+							   GPIOD_OUT_HIGH);
+
+	/*
+	 * The sensor must be powered for arducam_64mp_identify_module()
+	 * to be able to read the CHIP_ID from arducam_identifier.
+	 */
+	ret = arducam_64mp_power_on(dev);
+	if (ret)
+		return ret;
+
+	ret = arducam_64mp_identify_module(arducam_64mp);
+	if (ret)
+		goto error_power_off;
+
+	/* Set default mode to max resolution */
+	arducam_64mp->mode = &supported_modes[0];
+	arducam_64mp->fmt_code = MEDIA_BUS_FMT_SRGGB10_1X10;
+
+	/* Enable runtime PM and turn off the device */
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+	pm_runtime_idle(dev);
+
+	/* This needs the pm runtime to be registered. */
+	ret = arducam_64mp_init_controls(arducam_64mp);
+	if (ret)
+		goto error_power_off;
+
+	/* Initialize subdev */
+	arducam_64mp->sd.internal_ops = &arducam_64mp_internal_ops;
+	arducam_64mp->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
+			    V4L2_SUBDEV_FL_HAS_EVENTS;
+	arducam_64mp->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+
+	/* Initialize source pads */
+	arducam_64mp->pad[IMAGE_PAD].flags = MEDIA_PAD_FL_SOURCE;
+	arducam_64mp->pad[METADATA_PAD].flags = MEDIA_PAD_FL_SOURCE;
+
+	ret = media_entity_pads_init(&arducam_64mp->sd.entity, NUM_PADS,
+				     arducam_64mp->pad);
+	if (ret) {
+		dev_err(dev, "failed to init entity pads: %d\n", ret);
+		goto error_handler_free;
+	}
+
+	ret = v4l2_async_register_subdev_sensor(&arducam_64mp->sd);
+	if (ret < 0) {
+		dev_err(dev, "failed to register sensor sub-device: %d\n", ret);
+		goto error_media_entity;
+	}
+
+	return 0;
+
+error_media_entity:
+	media_entity_cleanup(&arducam_64mp->sd.entity);
+
+error_handler_free:
+	arducam_64mp_free_controls(arducam_64mp);
+
+error_power_off:
+	pm_runtime_disable(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+	arducam_64mp_power_off(&client->dev);
+
+	return ret;
+}
+
+static void arducam_64mp_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct arducam_64mp *arducam_64mp = to_arducam_64mp(sd);
+
+	v4l2_async_unregister_subdev(sd);
+	media_entity_cleanup(&sd->entity);
+	arducam_64mp_free_controls(arducam_64mp);
+
+	pm_runtime_disable(&client->dev);
+	if (!pm_runtime_status_suspended(&client->dev))
+		arducam_64mp_power_off(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+}
+
+MODULE_DEVICE_TABLE(of, arducam_64mp_dt_ids);
+
+static const struct dev_pm_ops arducam_64mp_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(arducam_64mp_suspend, arducam_64mp_resume)
+	SET_RUNTIME_PM_OPS(arducam_64mp_power_off, arducam_64mp_power_on, NULL)
+};
+
+static struct i2c_driver arducam_64mp_i2c_driver = {
+	.driver = {
+		.name = "arducam_64mp",
+		.of_match_table	= arducam_64mp_dt_ids,
+		.pm = &arducam_64mp_pm_ops,
+	},
+	.probe = arducam_64mp_probe,
+	.remove = arducam_64mp_remove,
+};
+
+module_i2c_driver(arducam_64mp_i2c_driver);
+
+MODULE_AUTHOR("Lee Jackson <info@arducam.com>");
+MODULE_DESCRIPTION("Arducam 64MP sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/i2c/bu64754.c b/drivers/media/i2c/bu64754.c
new file mode 100644
index 000000000000..530b31e9876a
--- /dev/null
+++ b/drivers/media/i2c/bu64754.c
@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * The BU64754GWZ is an actuator driver IC which can control the
+ * actuator position precisely using an internal Hall Sensor.
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+
+#include <media/v4l2-cci.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+
+#define BU64754_REG_ACTIVE	CCI_REG16(0x07)
+#define BU64754_ACTIVE_MODE	0x8080
+
+#define BU64754_REG_SERVE	CCI_REG16(0xd9)
+#define BU64754_SERVE_ON	0x0404
+
+#define BU64754_REG_POSITION	CCI_REG16(0x45)
+#define BU64753_POSITION_MAX	1023 /* 0x3ff */
+#define BU64753_POSITION_STEPS	1
+
+#define BU64754_POWER_ON_DELAY	800 /* uS : t1, t3 */
+
+struct bu64754 {
+	struct device *dev;
+
+	struct v4l2_ctrl_handler ctrls_vcm;
+	struct v4l2_subdev sd;
+	struct regmap *cci;
+
+	u16 current_val;
+	struct regulator *vdd;
+	struct notifier_block notifier;
+};
+
+static inline struct bu64754 *sd_to_bu64754(struct v4l2_subdev *subdev)
+{
+	return container_of(subdev, struct bu64754, sd);
+}
+
+static int bu64754_set(struct bu64754 *bu64754, u16 position)
+{
+	int ret;
+
+	position &= 0x3ff; /* BU64753_POSITION_MAX */
+	ret = cci_write(bu64754->cci, BU64754_REG_POSITION, position, NULL);
+	if (ret) {
+		dev_err(bu64754->dev, "Set position failed ret=%d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int bu64754_active(struct bu64754 *bu64754)
+{
+	int ret;
+
+	/* Power on */
+	ret = cci_write(bu64754->cci, BU64754_REG_ACTIVE, BU64754_ACTIVE_MODE, NULL);
+	if (ret < 0) {
+		dev_err(bu64754->dev, "Failed to set active mode ret = %d\n",
+			ret);
+		return ret;
+	}
+
+	/* Serve on */
+	ret = cci_write(bu64754->cci, BU64754_REG_SERVE, BU64754_SERVE_ON, NULL);
+	if (ret < 0) {
+		dev_err(bu64754->dev, "Failed to enable serve ret = %d\n",
+			ret);
+		return ret;
+	}
+
+	return bu64754_set(bu64754, bu64754->current_val);
+}
+
+static int bu64754_standby(struct bu64754 *bu64754)
+{
+	int ret;
+
+	ret = cci_write(bu64754->cci, BU64754_REG_ACTIVE, 0, NULL);
+	if (ret < 0)
+		dev_err(bu64754->dev, "Failed to enter standby mode ret = %d\n",
+			ret);
+
+	return ret;
+}
+
+static int bu64754_regulator_event(struct notifier_block *nb,
+				   unsigned long action, void *data)
+{
+	struct bu64754 *bu64754 = container_of(nb, struct bu64754, notifier);
+
+	if (action & REGULATOR_EVENT_ENABLE) {
+		/*
+		 * Initialisation delay between VDD low->high and availability
+		 * i2c operation.
+		 */
+		usleep_range(BU64754_POWER_ON_DELAY,
+			     BU64754_POWER_ON_DELAY + 100);
+
+		bu64754_active(bu64754);
+	} else if (action & REGULATOR_EVENT_PRE_DISABLE) {
+		bu64754_standby(bu64754);
+	}
+
+	return 0;
+}
+
+static int bu64754_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct bu64754 *bu64754 = container_of(ctrl->handler,
+		struct bu64754, ctrls_vcm);
+
+	if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) {
+		bu64754->current_val = ctrl->val;
+		return bu64754_set(bu64754, ctrl->val);
+	}
+
+	return -EINVAL;
+}
+
+static const struct v4l2_ctrl_ops bu64754_vcm_ctrl_ops = {
+	.s_ctrl = bu64754_set_ctrl,
+};
+
+static int bu64754_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+	return pm_runtime_resume_and_get(sd->dev);
+}
+
+static int bu64754_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+	pm_runtime_put(sd->dev);
+	return 0;
+}
+
+static const struct v4l2_subdev_internal_ops bu64754_int_ops = {
+	.open = bu64754_open,
+	.close = bu64754_close,
+};
+
+static const struct v4l2_subdev_ops bu64754_ops = { };
+
+static void bu64754_subdev_cleanup(struct bu64754 *bu64754)
+{
+	v4l2_async_unregister_subdev(&bu64754->sd);
+	v4l2_ctrl_handler_free(&bu64754->ctrls_vcm);
+	media_entity_cleanup(&bu64754->sd.entity);
+}
+
+static int bu64754_init_controls(struct bu64754 *bu64754)
+{
+	struct v4l2_ctrl_handler *hdl = &bu64754->ctrls_vcm;
+	const struct v4l2_ctrl_ops *ops = &bu64754_vcm_ctrl_ops;
+
+	v4l2_ctrl_handler_init(hdl, 1);
+
+	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE,
+			  0, BU64753_POSITION_MAX, BU64753_POSITION_STEPS,
+			  0);
+
+	bu64754->current_val = 0;
+
+	bu64754->sd.ctrl_handler = hdl;
+	if (hdl->error) {
+		dev_err(bu64754->dev, "%s fail error: 0x%x\n",
+			__func__, hdl->error);
+		return hdl->error;
+	}
+
+	return 0;
+}
+
+static int bu64754_probe(struct i2c_client *client)
+{
+	struct bu64754 *bu64754;
+	int ret;
+
+	bu64754 = devm_kzalloc(&client->dev, sizeof(*bu64754), GFP_KERNEL);
+	if (!bu64754)
+		return -ENOMEM;
+
+	bu64754->dev = &client->dev;
+
+	bu64754->cci = devm_cci_regmap_init_i2c(client, 8);
+	if (IS_ERR(bu64754->cci)) {
+		dev_err(bu64754->dev, "Failed to initialize CCI\n");
+		return PTR_ERR(bu64754->cci);
+	}
+
+	bu64754->vdd = devm_regulator_get_optional(&client->dev, "vdd");
+	if (IS_ERR(bu64754->vdd)) {
+		if (PTR_ERR(bu64754->vdd) != -ENODEV)
+			return PTR_ERR(bu64754->vdd);
+
+		bu64754->vdd = NULL;
+	} else {
+		bu64754->notifier.notifier_call = bu64754_regulator_event;
+
+		ret = regulator_register_notifier(bu64754->vdd,
+						  &bu64754->notifier);
+		if (ret) {
+			dev_err(bu64754->dev,
+				"could not register regulator notifier\n");
+			return ret;
+		}
+	}
+
+	v4l2_i2c_subdev_init(&bu64754->sd, client, &bu64754_ops);
+	bu64754->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+	bu64754->sd.internal_ops = &bu64754_int_ops;
+	bu64754->sd.entity.function = MEDIA_ENT_F_LENS;
+
+	ret = bu64754_init_controls(bu64754);
+	if (ret)
+		goto err_cleanup;
+
+	ret = media_entity_pads_init(&bu64754->sd.entity, 0, NULL);
+	if (ret < 0)
+		goto err_cleanup;
+
+	bu64754->sd.entity.function = MEDIA_ENT_F_LENS;
+
+	ret = v4l2_async_register_subdev(&bu64754->sd);
+	if (ret < 0)
+		goto err_cleanup;
+
+	if (!bu64754->vdd)
+		pm_runtime_set_active(&client->dev);
+
+	pm_runtime_enable(&client->dev);
+	pm_runtime_idle(&client->dev);
+
+	return 0;
+
+err_cleanup:
+	v4l2_ctrl_handler_free(&bu64754->ctrls_vcm);
+	media_entity_cleanup(&bu64754->sd.entity);
+
+	return ret;
+}
+
+static void bu64754_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct bu64754 *bu64754 = sd_to_bu64754(sd);
+
+	if (bu64754->vdd)
+		regulator_unregister_notifier(bu64754->vdd,
+					      &bu64754->notifier);
+
+	pm_runtime_disable(&client->dev);
+
+	bu64754_subdev_cleanup(bu64754);
+}
+
+static int __maybe_unused bu64754_vcm_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct bu64754 *bu64754 = sd_to_bu64754(sd);
+
+	if (bu64754->vdd)
+		return regulator_disable(bu64754->vdd);
+
+	return bu64754_standby(bu64754);
+}
+
+static int  __maybe_unused bu64754_vcm_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct bu64754 *bu64754 = sd_to_bu64754(sd);
+
+	if (bu64754->vdd)
+		return regulator_enable(bu64754->vdd);
+
+	return bu64754_active(bu64754);
+}
+
+static const struct of_device_id bu64754_of_table[] = {
+	{ .compatible = "rohm,bu64754", },
+	{ /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(of, bu64754_of_table);
+
+static const struct dev_pm_ops bu64754_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(bu64754_vcm_suspend, bu64754_vcm_resume)
+	SET_RUNTIME_PM_OPS(bu64754_vcm_suspend, bu64754_vcm_resume, NULL)
+};
+
+static struct i2c_driver bu64754_i2c_driver = {
+	.driver = {
+		.name = "bu64754",
+		.pm = &bu64754_pm_ops,
+		.of_match_table = bu64754_of_table,
+	},
+	.probe = bu64754_probe,
+	.remove = bu64754_remove,
+};
+
+module_i2c_driver(bu64754_i2c_driver);
+
+MODULE_AUTHOR("Kieran Bingham");
+MODULE_DESCRIPTION("BU64754 VCM driver");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/media/i2c/dw9807-vcm.c b/drivers/media/i2c/dw9807-vcm.c
index 4148009e0e01..2362c4813f5e 100644
--- a/drivers/media/i2c/dw9807-vcm.c
+++ b/drivers/media/i2c/dw9807-vcm.c
@@ -1,12 +1,21 @@
 // SPDX-License-Identifier: GPL-2.0
 // Copyright (C) 2018 Intel Corporation
 
+/*
+ * DW9807 is a 10-bit DAC driver, capable of sinking up to 100mA.
+ *
+ * DW9817 is a bidirectional 10-bit driver, driving up to +/- 100mA.
+ * Operationally it is identical to DW9807, except that the idle position is
+ * the mid-point, not 0.
+ */
+
 #include <linux/acpi.h>
 #include <linux/delay.h>
 #include <linux/i2c.h>
 #include <linux/iopoll.h>
 #include <linux/module.h>
 #include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
 #include <media/v4l2-ctrls.h>
 #include <media/v4l2-device.h>
 
@@ -38,10 +47,22 @@
 
 #define MAX_RETRY		10
 
+#define DW9807_PW_MIN_DELAY_US		100
+#define DW9807_PW_DELAY_RANGE_US	10
+
+struct dw9807_cfg {
+	unsigned int idle_pos;
+	unsigned int default_pos;
+};
+
 struct dw9807_device {
 	struct v4l2_ctrl_handler ctrls_vcm;
 	struct v4l2_subdev sd;
 	u16 current_val;
+	u16 idle_pos;
+	struct regulator *vdd;
+	struct notifier_block notifier;
+	bool first;
 };
 
 static inline struct dw9807_device *sd_to_dw9807_vcm(
@@ -109,6 +130,102 @@ static int dw9807_set_dac(struct i2c_client *client, u16 data)
 	return 0;
 }
 
+/*
+ * The lens position is gradually moved in units of DW9807_CTRL_STEPS,
+ * to make the movements smoothly. In all cases, even when "start" and
+ * "end" are the same, the lens will be set to the "end" position.
+ *
+ * (We don't use hardware slew rate control, because it differs widely
+ * between otherwise-compatible ICs, and may need lens-specific tuning.)
+ */
+static int dw9807_ramp(struct i2c_client *client, int start, int end)
+{
+	int step, val, ret;
+
+	if (start < end)
+		step = DW9807_CTRL_STEPS;
+	else
+		step = -DW9807_CTRL_STEPS;
+
+	val = start;
+	while (true) {
+		val += step;
+		if (step * (val - end) >= 0)
+			val = end;
+		ret = dw9807_set_dac(client, val);
+		if (ret)
+			dev_err_ratelimited(&client->dev, "%s I2C failure: %d",
+					    __func__, ret);
+		if (val == end)
+			break;
+		usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10);
+	}
+
+	return ret;
+}
+
+static int dw9807_active(struct dw9807_device *dw9807_dev)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&dw9807_dev->sd);
+	const char tx_data[2] = { DW9807_CTL_ADDR, 0x00 };
+	int ret;
+
+	/* Power on */
+	ret = i2c_master_send(client, tx_data, sizeof(tx_data));
+	if (ret < 0) {
+		dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret);
+		return ret;
+	}
+
+	dw9807_dev->first = true;
+
+	return dw9807_ramp(client, dw9807_dev->idle_pos, dw9807_dev->current_val);
+}
+
+static int dw9807_standby(struct dw9807_device *dw9807_dev)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&dw9807_dev->sd);
+	const char tx_data[2] = { DW9807_CTL_ADDR, 0x01 };
+	int ret;
+
+	if (abs(dw9807_dev->current_val - dw9807_dev->idle_pos) > DW9807_CTRL_STEPS)
+		dw9807_ramp(client, dw9807_dev->current_val, dw9807_dev->idle_pos);
+
+	/* Power down */
+	ret = i2c_master_send(client, tx_data, sizeof(tx_data));
+	if (ret < 0) {
+		dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int dw9807_regulator_event(struct notifier_block *nb,
+				  unsigned long action, void *data)
+{
+	struct dw9807_device *dw9807_dev =
+		container_of(nb, struct dw9807_device, notifier);
+
+	if (action & REGULATOR_EVENT_ENABLE) {
+		/*
+		 * Initialisation delay between VDD low->high and the moment
+		 * when the i2c command is available.
+		 * From the datasheet, it should be 10ms + 2ms (max power
+		 * up sequence duration)
+		 */
+		usleep_range(DW9807_PW_MIN_DELAY_US,
+			     DW9807_PW_MIN_DELAY_US +
+			     DW9807_PW_DELAY_RANGE_US);
+
+		dw9807_active(dw9807_dev);
+	} else if (action & REGULATOR_EVENT_PRE_DISABLE) {
+		dw9807_standby(dw9807_dev);
+	}
+
+	return 0;
+}
+
 static int dw9807_set_ctrl(struct v4l2_ctrl *ctrl)
 {
 	struct dw9807_device *dev_vcm = container_of(ctrl->handler,
@@ -116,9 +233,11 @@ static int dw9807_set_ctrl(struct v4l2_ctrl *ctrl)
 
 	if (ctrl->id == V4L2_CID_FOCUS_ABSOLUTE) {
 		struct i2c_client *client = v4l2_get_subdevdata(&dev_vcm->sd);
+		int start = (dev_vcm->first) ? dev_vcm->current_val : ctrl->val;
 
+		dev_vcm->first = false;
 		dev_vcm->current_val = ctrl->val;
-		return dw9807_set_dac(client, ctrl->val);
+		return dw9807_ramp(client, start, ctrl->val);
 	}
 
 	return -EINVAL;
@@ -163,7 +282,8 @@ static int dw9807_init_controls(struct dw9807_device *dev_vcm)
 	v4l2_ctrl_handler_init(hdl, 1);
 
 	v4l2_ctrl_new_std(hdl, ops, V4L2_CID_FOCUS_ABSOLUTE,
-			  0, DW9807_MAX_FOCUS_POS, DW9807_FOCUS_STEPS, 0);
+			  0, DW9807_MAX_FOCUS_POS, DW9807_FOCUS_STEPS,
+			  dev_vcm->current_val);
 
 	dev_vcm->sd.ctrl_handler = hdl;
 	if (hdl->error) {
@@ -175,9 +295,32 @@ static int dw9807_init_controls(struct dw9807_device *dev_vcm)
 	return 0;
 }
 
+/* Compatible devices; in fact there are many similar chips.
+ * "data" holds the powered-off (zero current) lens position and a
+ * default/initial control value (which need not be the same as the powered-off
+ * value).
+ */
+static const struct dw9807_cfg dw9807_cfg = {
+	.idle_pos = 0,
+	.default_pos = 0
+};
+
+static const struct dw9807_cfg dw9817_cfg = {
+	.idle_pos = 512,
+	.default_pos = 480,
+};
+
+static const struct of_device_id dw9807_of_table[] = {
+	{ .compatible = "dongwoon,dw9807-vcm", .data = &dw9807_cfg },
+	{ .compatible = "dongwoon,dw9817-vcm", .data = &dw9817_cfg },
+	{ /* sentinel */ }
+};
+
 static int dw9807_probe(struct i2c_client *client)
 {
 	struct dw9807_device *dw9807_dev;
+	const struct of_device_id *match;
+	const struct dw9807_cfg *cfg;
 	int rval;
 
 	dw9807_dev = devm_kzalloc(&client->dev, sizeof(*dw9807_dev),
@@ -185,6 +328,31 @@ static int dw9807_probe(struct i2c_client *client)
 	if (dw9807_dev == NULL)
 		return -ENOMEM;
 
+	dw9807_dev->vdd = devm_regulator_get_optional(&client->dev, "VDD");
+	if (IS_ERR(dw9807_dev->vdd)) {
+		if (PTR_ERR(dw9807_dev->vdd) != -ENODEV)
+			return PTR_ERR(dw9807_dev->vdd);
+
+		dw9807_dev->vdd = NULL;
+	} else {
+		dw9807_dev->notifier.notifier_call = dw9807_regulator_event;
+
+		rval = regulator_register_notifier(dw9807_dev->vdd,
+						   &dw9807_dev->notifier);
+		if (rval) {
+			dev_err(&client->dev,
+				"could not register regulator notifier\n");
+			return rval;
+		}
+	}
+
+	match = i2c_of_match_device(dw9807_of_table, client);
+	if (match) {
+		cfg = (const struct dw9807_cfg *)match->data;
+		dw9807_dev->idle_pos = cfg->idle_pos;
+		dw9807_dev->current_val = cfg->default_pos;
+	}
+
 	v4l2_i2c_subdev_init(&dw9807_dev->sd, client, &dw9807_ops);
 	dw9807_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
 	dw9807_dev->sd.internal_ops = &dw9807_int_ops;
@@ -203,7 +371,8 @@ static int dw9807_probe(struct i2c_client *client)
 	if (rval < 0)
 		goto err_cleanup;
 
-	pm_runtime_set_active(&client->dev);
+	if (!dw9807_dev->vdd)
+		pm_runtime_set_active(&client->dev);
 	pm_runtime_enable(&client->dev);
 	pm_runtime_idle(&client->dev);
 
@@ -221,6 +390,10 @@ static void dw9807_remove(struct i2c_client *client)
 	struct v4l2_subdev *sd = i2c_get_clientdata(client);
 	struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd);
 
+	if (dw9807_dev->vdd)
+		regulator_unregister_notifier(dw9807_dev->vdd,
+					      &dw9807_dev->notifier);
+
 	pm_runtime_disable(&client->dev);
 
 	dw9807_subdev_cleanup(dw9807_dev);
@@ -236,25 +409,11 @@ static int __maybe_unused dw9807_vcm_suspend(struct device *dev)
 	struct i2c_client *client = to_i2c_client(dev);
 	struct v4l2_subdev *sd = i2c_get_clientdata(client);
 	struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd);
-	const char tx_data[2] = { DW9807_CTL_ADDR, 0x01 };
-	int ret, val;
-
-	for (val = dw9807_dev->current_val & ~(DW9807_CTRL_STEPS - 1);
-	     val >= 0; val -= DW9807_CTRL_STEPS) {
-		ret = dw9807_set_dac(client, val);
-		if (ret)
-			dev_err_once(dev, "%s I2C failure: %d", __func__, ret);
-		usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10);
-	}
 
-	/* Power down */
-	ret = i2c_master_send(client, tx_data, sizeof(tx_data));
-	if (ret < 0) {
-		dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret);
-		return ret;
-	}
+	if (dw9807_dev->vdd)
+		return regulator_disable(dw9807_dev->vdd);
 
-	return 0;
+	return dw9807_standby(dw9807_dev);
 }
 
 /*
@@ -268,35 +427,13 @@ static int  __maybe_unused dw9807_vcm_resume(struct device *dev)
 	struct i2c_client *client = to_i2c_client(dev);
 	struct v4l2_subdev *sd = i2c_get_clientdata(client);
 	struct dw9807_device *dw9807_dev = sd_to_dw9807_vcm(sd);
-	const char tx_data[2] = { DW9807_CTL_ADDR, 0x00 };
-	int ret, val;
-
-	/* Power on */
-	ret = i2c_master_send(client, tx_data, sizeof(tx_data));
-	if (ret < 0) {
-		dev_err(&client->dev, "I2C write CTL fail ret = %d\n", ret);
-		return ret;
-	}
 
-	for (val = dw9807_dev->current_val % DW9807_CTRL_STEPS;
-	     val < dw9807_dev->current_val + DW9807_CTRL_STEPS - 1;
-	     val += DW9807_CTRL_STEPS) {
-		ret = dw9807_set_dac(client, val);
-		if (ret)
-			dev_err_ratelimited(dev, "%s I2C failure: %d",
-						__func__, ret);
-		usleep_range(DW9807_CTRL_DELAY_US, DW9807_CTRL_DELAY_US + 10);
-	}
+	if (dw9807_dev->vdd)
+		return regulator_enable(dw9807_dev->vdd);
 
-	return 0;
+	return dw9807_active(dw9807_dev);
 }
 
-static const struct of_device_id dw9807_of_table[] = {
-	{ .compatible = "dongwoon,dw9807-vcm" },
-	/* Compatibility for older firmware, NEVER USE THIS IN FIRMWARE! */
-	{ .compatible = "dongwoon,dw9807" },
-	{ /* sentinel */ }
-};
 MODULE_DEVICE_TABLE(of, dw9807_of_table);
 
 static const struct dev_pm_ops dw9807_pm_ops = {
diff --git a/drivers/media/i2c/imx219.c b/drivers/media/i2c/imx219.c
index e0714abe8540..dc56485f4ff7 100644
--- a/drivers/media/i2c/imx219.c
+++ b/drivers/media/i2c/imx219.c
@@ -148,6 +148,12 @@
 #define IMX219_PIXEL_ARRAY_WIDTH	3280U
 #define IMX219_PIXEL_ARRAY_HEIGHT	2464U
 
+enum binning_bit_depths {
+	BINNING_IDX_8_BIT,
+	BINNING_IDX_10_BIT,
+	BINNING_IDX_MAX
+};
+
 /* Mode : resolution and related config&values */
 struct imx219_mode {
 	/* Frame width */
@@ -157,6 +163,9 @@ struct imx219_mode {
 
 	/* V-timing */
 	unsigned int vts_def;
+
+	/* binning mode based on format code */
+	unsigned int binning[BINNING_IDX_MAX];
 };
 
 static const struct cci_reg_sequence imx219_common_regs[] = {
@@ -316,24 +325,40 @@ static const struct imx219_mode supported_modes[] = {
 		.width = 3280,
 		.height = 2464,
 		.vts_def = 3526,
+		.binning = {
+			[BINNING_IDX_8_BIT] = IMX219_BINNING_NONE,
+			[BINNING_IDX_10_BIT] = IMX219_BINNING_NONE,
+		},
 	},
 	{
 		/* 1080P 30fps cropped */
 		.width = 1920,
 		.height = 1080,
 		.vts_def = 1763,
+		.binning = {
+			[BINNING_IDX_8_BIT] = IMX219_BINNING_NONE,
+			[BINNING_IDX_10_BIT] = IMX219_BINNING_NONE,
+		},
 	},
 	{
-		/* 2x2 binned 30fps mode */
+		/* 2x2 binned 60fps mode */
 		.width = 1640,
 		.height = 1232,
 		.vts_def = 1763,
+		.binning = {
+			[BINNING_IDX_8_BIT] = IMX219_BINNING_X2_ANALOG,
+			[BINNING_IDX_10_BIT] = IMX219_BINNING_X2,
+		},
 	},
 	{
-		/* 640x480 30fps mode */
+		/* 640x480 60fps mode */
 		.width = 640,
 		.height = 480,
 		.vts_def = 1763,
+		.binning = {
+			[BINNING_IDX_8_BIT] = IMX219_BINNING_X2_ANALOG,
+			[BINNING_IDX_10_BIT] = IMX219_BINNING_X2_ANALOG,
+		},
 	},
 };
 
@@ -385,6 +410,80 @@ static u32 imx219_get_format_code(struct imx219 *imx219, u32 code)
 	return imx219_mbus_formats[i];
 }
 
+static u32 imx219_get_format_bpp(const struct v4l2_mbus_framefmt *format)
+{
+	switch (format->code) {
+	case MEDIA_BUS_FMT_SRGGB8_1X8:
+	case MEDIA_BUS_FMT_SGRBG8_1X8:
+	case MEDIA_BUS_FMT_SGBRG8_1X8:
+	case MEDIA_BUS_FMT_SBGGR8_1X8:
+		return 8;
+
+	case MEDIA_BUS_FMT_SRGGB10_1X10:
+	case MEDIA_BUS_FMT_SGRBG10_1X10:
+	case MEDIA_BUS_FMT_SGBRG10_1X10:
+	case MEDIA_BUS_FMT_SBGGR10_1X10:
+	default:
+		return 10;
+	}
+}
+
+static unsigned int imx219_get_binning(struct imx219 *imx219, u8 *bin_h,
+				       u8 *bin_v)
+{
+	struct v4l2_subdev_state *state =
+		v4l2_subdev_get_locked_active_state(&imx219->sd);
+	const struct v4l2_mbus_framefmt *format =
+		v4l2_subdev_state_get_format(state, 0);
+	const struct v4l2_rect *crop = v4l2_subdev_state_get_crop(state, 0);
+	unsigned int bin_mode = IMX219_BINNING_NONE;
+	const struct imx219_mode *mode =
+		v4l2_find_nearest_size(supported_modes,
+				       ARRAY_SIZE(supported_modes),
+				       width, height,
+				       format->width, format->height);
+	switch (format->code) {
+	case MEDIA_BUS_FMT_SRGGB8_1X8:
+	case MEDIA_BUS_FMT_SGRBG8_1X8:
+	case MEDIA_BUS_FMT_SGBRG8_1X8:
+	case MEDIA_BUS_FMT_SBGGR8_1X8:
+		bin_mode = mode->binning[BINNING_IDX_8_BIT];
+		break;
+
+	case MEDIA_BUS_FMT_SRGGB10_1X10:
+	case MEDIA_BUS_FMT_SGRBG10_1X10:
+	case MEDIA_BUS_FMT_SGBRG10_1X10:
+	case MEDIA_BUS_FMT_SBGGR10_1X10:
+		bin_mode = mode->binning[BINNING_IDX_10_BIT];
+		break;
+	}
+
+	*bin_h = crop->width / format->width;
+	*bin_v = crop->height / format->height;
+
+	if (*bin_h == 2 && *bin_v == 2)
+		return bin_mode;
+	else if (*bin_h == 2 || *bin_v == 2)
+		/*
+		 * Don't use analog binning if only one dimension
+		 * is binned, as it crops the other dimension
+		 */
+		return IMX219_BINNING_X2;
+	else
+		return IMX219_BINNING_NONE;
+}
+
+static inline u32 imx219_get_rate_factor(struct imx219 *imx219)
+{
+	u8 bin_h, bin_v;
+	unsigned int binning = imx219_get_binning(imx219, &bin_h, &bin_v);
+
+	if (binning == IMX219_BINNING_X2_ANALOG)
+		return 2;
+
+	return 1;
+}
+
 /* -----------------------------------------------------------------------------
  * Controls
  */
@@ -396,10 +495,12 @@ static int imx219_set_ctrl(struct v4l2_ctrl *ctrl)
 	struct i2c_client *client = v4l2_get_subdevdata(&imx219->sd);
 	const struct v4l2_mbus_framefmt *format;
 	struct v4l2_subdev_state *state;
+	u32 rate_factor;
 	int ret = 0;
 
 	state = v4l2_subdev_get_locked_active_state(&imx219->sd);
 	format = v4l2_subdev_state_get_format(state, 0);
+	rate_factor = imx219_get_rate_factor(imx219);
 
 	if (ctrl->id == V4L2_CID_VBLANK) {
 		int exposure_max, exposure_def;
@@ -428,7 +529,7 @@ static int imx219_set_ctrl(struct v4l2_ctrl *ctrl)
 		break;
 	case V4L2_CID_EXPOSURE:
 		cci_write(imx219->regmap, IMX219_REG_EXPOSURE,
-			  ctrl->val, &ret);
+			  ctrl->val / rate_factor, &ret);
 		break;
 	case V4L2_CID_DIGITAL_GAIN:
 		cci_write(imx219->regmap, IMX219_REG_DIGITAL_GAIN,
@@ -445,7 +546,7 @@ static int imx219_set_ctrl(struct v4l2_ctrl *ctrl)
 		break;
 	case V4L2_CID_VBLANK:
 		cci_write(imx219->regmap, IMX219_REG_VTS,
-			  format->height + ctrl->val, &ret);
+			  (format->height + ctrl->val) / rate_factor, &ret);
 		break;
 	case V4L2_CID_TEST_PATTERN_RED:
 		cci_write(imx219->regmap, IMX219_REG_TESTP_RED,
@@ -613,29 +714,14 @@ static int imx219_set_framefmt(struct imx219 *imx219,
 {
 	const struct v4l2_mbus_framefmt *format;
 	const struct v4l2_rect *crop;
-	unsigned int bpp;
-	u64 bin_h, bin_v;
+	unsigned int binning;
+	u8 bin_h, bin_v;
+	u32 bpp;
 	int ret = 0;
 
 	format = v4l2_subdev_state_get_format(state, 0);
 	crop = v4l2_subdev_state_get_crop(state, 0);
-
-	switch (format->code) {
-	case MEDIA_BUS_FMT_SRGGB8_1X8:
-	case MEDIA_BUS_FMT_SGRBG8_1X8:
-	case MEDIA_BUS_FMT_SGBRG8_1X8:
-	case MEDIA_BUS_FMT_SBGGR8_1X8:
-		bpp = 8;
-		break;
-
-	case MEDIA_BUS_FMT_SRGGB10_1X10:
-	case MEDIA_BUS_FMT_SGRBG10_1X10:
-	case MEDIA_BUS_FMT_SGBRG10_1X10:
-	case MEDIA_BUS_FMT_SBGGR10_1X10:
-	default:
-		bpp = 10;
-		break;
-	}
+	bpp = imx219_get_format_bpp(format);
 
 	cci_write(imx219->regmap, IMX219_REG_X_ADD_STA_A,
 		  crop->left - IMX219_PIXEL_ARRAY_LEFT, &ret);
@@ -646,28 +732,11 @@ static int imx219_set_framefmt(struct imx219 *imx219,
 	cci_write(imx219->regmap, IMX219_REG_Y_ADD_END_A,
 		  crop->top - IMX219_PIXEL_ARRAY_TOP + crop->height - 1, &ret);
 
-	switch (crop->width / format->width) {
-	case 1:
-	default:
-		bin_h = IMX219_BINNING_NONE;
-		break;
-	case 2:
-		bin_h = bpp == 8 ? IMX219_BINNING_X2_ANALOG : IMX219_BINNING_X2;
-		break;
-	}
-
-	switch (crop->height / format->height) {
-	case 1:
-	default:
-		bin_v = IMX219_BINNING_NONE;
-		break;
-	case 2:
-		bin_v = bpp == 8 ? IMX219_BINNING_X2_ANALOG : IMX219_BINNING_X2;
-		break;
-	}
-
-	cci_write(imx219->regmap, IMX219_REG_BINNING_MODE_H, bin_h, &ret);
-	cci_write(imx219->regmap, IMX219_REG_BINNING_MODE_V, bin_v, &ret);
+	binning = imx219_get_binning(imx219, &bin_h, &bin_v);
+	cci_write(imx219->regmap, IMX219_REG_BINNING_MODE_H,
+		  (bin_h == 2) ? binning : IMX219_BINNING_NONE, &ret);
+	cci_write(imx219->regmap, IMX219_REG_BINNING_MODE_V,
+		  (bin_v == 2) ? binning : IMX219_BINNING_NONE, &ret);
 
 	cci_write(imx219->regmap, IMX219_REG_X_OUTPUT_SIZE,
 		  format->width, &ret);
@@ -861,6 +930,7 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd,
 	 */
 	bin_h = min(IMX219_PIXEL_ARRAY_WIDTH / format->width, 2U);
 	bin_v = min(IMX219_PIXEL_ARRAY_HEIGHT / format->height, 2U);
+	binning = min(bin_h, bin_v);
 
 	/* Ensure bin_h and bin_v are same to avoid 1:2 or 2:1 stretching */
 	binning = min(bin_h, bin_v);
@@ -875,6 +945,7 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd,
 		int exposure_max;
 		int exposure_def;
 		int hblank;
+		int pixel_rate;
 
 		/* Update limits and set FPS to default */
 		__v4l2_ctrl_modify_range(imx219->vblank, IMX219_VBLANK_MIN,
@@ -898,6 +969,12 @@ static int imx219_set_pad_format(struct v4l2_subdev *sd,
 		hblank = IMX219_PPL_DEFAULT - mode->width;
 		__v4l2_ctrl_modify_range(imx219->hblank, hblank, hblank, 1,
 					 hblank);
+
+		/* Scale the pixel rate based on the mode specific factor */
+		pixel_rate = imx219_get_pixel_rate(imx219) *
+			     imx219_get_rate_factor(imx219);
+		__v4l2_ctrl_modify_range(imx219->pixel_rate, pixel_rate,
+					 pixel_rate, 1, pixel_rate);
 	}
 
 	return 0;
diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c
index a87a265cd839..91a196680e91 100644
--- a/drivers/media/i2c/imx290.c
+++ b/drivers/media/i2c/imx290.c
@@ -13,6 +13,7 @@
 #include <linux/gpio/consumer.h>
 #include <linux/i2c.h>
 #include <linux/module.h>
+#include <linux/moduleparam.h>
 #include <linux/of.h>
 #include <linux/pm_runtime.h>
 #include <linux/regmap.h>
@@ -41,6 +42,9 @@
 #define IMX290_WINMODE_720P				(1 << 4)
 #define IMX290_WINMODE_CROP				(4 << 4)
 #define IMX290_FR_FDG_SEL				CCI_REG8(0x3009)
+#define IMX290_FDG_HCG					BIT(4)
+#define IMX290_FRSEL_60FPS				BIT(0)
+#define IMX290_FDG_LCG					0
 #define IMX290_BLKLEVEL					CCI_REG16_LE(0x300a)
 #define IMX290_GAIN					CCI_REG8(0x3014)
 #define IMX290_VMAX					CCI_REG24_LE(0x3018)
@@ -162,6 +166,10 @@
 
 #define IMX290_NUM_SUPPLIES				3
 
+static bool hcg_mode;
+module_param(hcg_mode, bool, 0664);
+MODULE_PARM_DESC(hcg_mode, "Enable HCG mode");
+
 enum imx290_colour_variant {
 	IMX290_VARIANT_COLOUR,
 	IMX290_VARIANT_MONO,
@@ -172,12 +180,15 @@ enum imx290_model {
 	IMX290_MODEL_IMX290LQR,
 	IMX290_MODEL_IMX290LLR,
 	IMX290_MODEL_IMX327LQR,
+	IMX290_MODEL_IMX462LQR,
+	IMX290_MODEL_IMX462LLR,
 };
 
 struct imx290_model_info {
 	enum imx290_colour_variant colour_variant;
 	const struct cci_reg_sequence *init_regs;
 	size_t init_regs_num;
+	unsigned int max_analog_gain;
 	const char *name;
 };
 
@@ -317,6 +328,50 @@ static const struct cci_reg_sequence imx290_global_init_settings_290[] = {
 	{ CCI_REG8(0x33b3), 0x04 },
 };
 
+static const struct cci_reg_sequence imx290_global_init_settings_462[] = {
+	{ CCI_REG8(0x300f), 0x00 },
+	{ CCI_REG8(0x3010), 0x21 },
+	{ CCI_REG8(0x3011), 0x02 },
+	{ CCI_REG8(0x3016), 0x09 },
+	{ CCI_REG8(0x3070), 0x02 },
+	{ CCI_REG8(0x3071), 0x11 },
+	{ CCI_REG8(0x309b), 0x10 },
+	{ CCI_REG8(0x309c), 0x22 },
+	{ CCI_REG8(0x30a2), 0x02 },
+	{ CCI_REG8(0x30a6), 0x20 },
+	{ CCI_REG8(0x30a8), 0x20 },
+	{ CCI_REG8(0x30aa), 0x20 },
+	{ CCI_REG8(0x30ac), 0x20 },
+	{ CCI_REG8(0x30b0), 0x43 },
+	{ CCI_REG8(0x3119), 0x9e },
+	{ CCI_REG8(0x311c), 0x1e },
+	{ CCI_REG8(0x311e), 0x08 },
+	{ CCI_REG8(0x3128), 0x05 },
+	{ CCI_REG8(0x313d), 0x83 },
+	{ CCI_REG8(0x3150), 0x03 },
+	{ CCI_REG8(0x317e), 0x00 },
+	{ CCI_REG8(0x32b8), 0x50 },
+	{ CCI_REG8(0x32b9), 0x10 },
+	{ CCI_REG8(0x32ba), 0x00 },
+	{ CCI_REG8(0x32bb), 0x04 },
+	{ CCI_REG8(0x32c8), 0x50 },
+	{ CCI_REG8(0x32c9), 0x10 },
+	{ CCI_REG8(0x32ca), 0x00 },
+	{ CCI_REG8(0x32cb), 0x04 },
+	{ CCI_REG8(0x332c), 0xd3 },
+	{ CCI_REG8(0x332d), 0x10 },
+	{ CCI_REG8(0x332e), 0x0d },
+	{ CCI_REG8(0x3358), 0x06 },
+	{ CCI_REG8(0x3359), 0xe1 },
+	{ CCI_REG8(0x335a), 0x11 },
+	{ CCI_REG8(0x3360), 0x1e },
+	{ CCI_REG8(0x3361), 0x61 },
+	{ CCI_REG8(0x3362), 0x10 },
+	{ CCI_REG8(0x33b0), 0x50 },
+	{ CCI_REG8(0x33b2), 0x1a },
+	{ CCI_REG8(0x33b3), 0x04 },
+};
+
 #define IMX290_NUM_CLK_REGS	2
 static const struct cci_reg_sequence xclk_regs[][IMX290_NUM_CLK_REGS] = {
 	[IMX290_CLK_37_125] = {
@@ -650,7 +705,8 @@ static int imx290_set_data_lanes(struct imx290 *imx290)
 		  &ret);
 	cci_write(imx290->regmap, IMX290_CSI_LANE_MODE, imx290->nlanes - 1,
 		  &ret);
-	cci_write(imx290->regmap, IMX290_FR_FDG_SEL, 0x01, &ret);
+	cci_write(imx290->regmap, IMX290_FR_FDG_SEL, IMX290_FRSEL_60FPS |
+		  (hcg_mode ? IMX290_FDG_HCG : IMX290_FDG_LCG), &ret);
 
 	return ret;
 }
@@ -879,14 +935,10 @@ static int imx290_ctrl_init(struct imx290 *imx290)
 	 * up to 72.0dB (240) add further digital gain. Limit the range to
 	 * analog gain only, support for digital gain can be added separately
 	 * if needed.
-	 *
-	 * The IMX327 and IMX462 are largely compatible with the IMX290, but
-	 * have an analog gain range of 0.0dB to 29.4dB and 42dB of digital
-	 * gain. When support for those sensors gets added to the driver, the
-	 * gain control should be adjusted accordingly.
 	 */
 	v4l2_ctrl_new_std(&imx290->ctrls, &imx290_ctrl_ops,
-			  V4L2_CID_ANALOGUE_GAIN, 0, 100, 1, 0);
+			  V4L2_CID_ANALOGUE_GAIN, 0,
+			  imx290->model->max_analog_gain, 1, 0);
 
 	/*
 	 * Correct range will be determined through imx290_ctrl_update setting
@@ -1441,20 +1493,37 @@ static const struct imx290_model_info imx290_models[] = {
 		.colour_variant = IMX290_VARIANT_COLOUR,
 		.init_regs = imx290_global_init_settings_290,
 		.init_regs_num = ARRAY_SIZE(imx290_global_init_settings_290),
+		.max_analog_gain = 100,
 		.name = "imx290",
 	},
 	[IMX290_MODEL_IMX290LLR] = {
 		.colour_variant = IMX290_VARIANT_MONO,
 		.init_regs = imx290_global_init_settings_290,
 		.init_regs_num = ARRAY_SIZE(imx290_global_init_settings_290),
+		.max_analog_gain = 100,
 		.name = "imx290",
 	},
 	[IMX290_MODEL_IMX327LQR] = {
 		.colour_variant = IMX290_VARIANT_COLOUR,
 		.init_regs = imx290_global_init_settings_327,
 		.init_regs_num = ARRAY_SIZE(imx290_global_init_settings_327),
+		.max_analog_gain = 98,
 		.name = "imx327",
 	},
+	[IMX290_MODEL_IMX462LQR] = {
+		.colour_variant = IMX290_VARIANT_COLOUR,
+		.init_regs = imx290_global_init_settings_462,
+		.init_regs_num = ARRAY_SIZE(imx290_global_init_settings_462),
+		.max_analog_gain = 98,
+		.name = "imx462",
+	},
+	[IMX290_MODEL_IMX462LLR] = {
+		.colour_variant = IMX290_VARIANT_MONO,
+		.init_regs = imx290_global_init_settings_462,
+		.init_regs_num = ARRAY_SIZE(imx290_global_init_settings_462),
+		.max_analog_gain = 98,
+		.name = "imx462",
+	},
 };
 
 static int imx290_parse_dt(struct imx290 *imx290)
@@ -1650,6 +1719,12 @@ static const struct of_device_id imx290_of_match[] = {
 	}, {
 		.compatible = "sony,imx327lqr",
 		.data = &imx290_models[IMX290_MODEL_IMX327LQR],
+	}, {
+		.compatible = "sony,imx462lqr",
+		.data = &imx290_models[IMX290_MODEL_IMX462LQR],
+	}, {
+		.compatible = "sony,imx462llr",
+		.data = &imx290_models[IMX290_MODEL_IMX462LLR],
 	},
 	{ /* sentinel */ },
 };
diff --git a/drivers/media/i2c/imx296.c b/drivers/media/i2c/imx296.c
index f3bec16b527c..fd4d53e076f4 100644
--- a/drivers/media/i2c/imx296.c
+++ b/drivers/media/i2c/imx296.c
@@ -20,6 +20,10 @@
 #include <media/v4l2-fwnode.h>
 #include <media/v4l2-subdev.h>
 
+static int trigger_mode;
+module_param(trigger_mode, int, 0644);
+MODULE_PARM_DESC(trigger_mode, "Set trigger mode: 0=default, 1=XTRIG");
+
 #define IMX296_PIXEL_ARRAY_WIDTH			1456
 #define IMX296_PIXEL_ARRAY_HEIGHT			1088
 
@@ -150,9 +154,9 @@
 #define IMX296_FID0_ROIPH1				IMX296_REG_16BIT(0x3310)
 #define IMX296_FID0_ROIPV1				IMX296_REG_16BIT(0x3312)
 #define IMX296_FID0_ROIWH1				IMX296_REG_16BIT(0x3314)
-#define IMX296_FID0_ROIWH1_MIN				80
+#define IMX296_FID0_ROIWH1_MIN				96
 #define IMX296_FID0_ROIWV1				IMX296_REG_16BIT(0x3316)
-#define IMX296_FID0_ROIWV1_MIN				4
+#define IMX296_FID0_ROIWV1_MIN				88
 
 #define IMX296_CM_HSST_STARTTMG				IMX296_REG_16BIT(0x4018)
 #define IMX296_CM_HSST_ENDTMG				IMX296_REG_16BIT(0x401a)
@@ -171,6 +175,7 @@
 #define IMX296_CKREQSEL					IMX296_REG_8BIT(0x4101)
 #define IMX296_CKREQSEL_HS				BIT(2)
 #define IMX296_GTTABLENUM				IMX296_REG_8BIT(0x4114)
+#define IMX296_MIPIC_AREA3W				IMX296_REG_16BIT(0x4182)
 #define IMX296_CTRL418C					IMX296_REG_8BIT(0x418c)
 
 struct imx296_clk_params {
@@ -200,6 +205,7 @@ struct imx296 {
 
 	const struct imx296_clk_params *clk_params;
 	bool mono;
+	int trigger_mode_of;
 
 	struct v4l2_subdev subdev;
 	struct media_pad pad;
@@ -207,6 +213,8 @@ struct imx296 {
 	struct v4l2_ctrl_handler ctrls;
 	struct v4l2_ctrl *hblank;
 	struct v4l2_ctrl *vblank;
+	struct v4l2_ctrl *vflip;
+	struct v4l2_ctrl *hflip;
 };
 
 static inline struct imx296 *to_imx296(struct v4l2_subdev *sd)
@@ -248,6 +256,36 @@ static int imx296_write(struct imx296 *sensor, u32 addr, u32 value, int *err)
 	return ret;
 }
 
+/*
+ * The supported formats.
+ * This table MUST contain 4 entries per format, to cover the various flip
+ * combinations in the order
+ * - no flip
+ * - h flip
+ * - v flip
+ * - h&v flips
+ */
+static const u32 mbus_codes[] = {
+	/* 10-bit modes. */
+	MEDIA_BUS_FMT_SRGGB10_1X10,
+	MEDIA_BUS_FMT_SGRBG10_1X10,
+	MEDIA_BUS_FMT_SGBRG10_1X10,
+	MEDIA_BUS_FMT_SBGGR10_1X10,
+};
+
+static u32 imx296_mbus_code(const struct imx296 *sensor)
+{
+	unsigned int i = 0;
+
+	if (sensor->mono)
+		return MEDIA_BUS_FMT_Y10_1X10;
+
+	if (sensor->vflip && sensor->hflip)
+		i = (sensor->vflip->val ? 2 : 0) | (sensor->hflip->val ? 1 : 0);
+
+	return mbus_codes[i];
+}
+
 static int imx296_power_on(struct imx296 *sensor)
 {
 	int ret;
@@ -342,6 +380,13 @@ static int imx296_s_ctrl(struct v4l2_ctrl *ctrl)
 			     &ret);
 		break;
 
+	case V4L2_CID_HFLIP:
+	case V4L2_CID_VFLIP:
+		imx296_write(sensor, IMX296_CTRL0E,
+			     sensor->vflip->val | (sensor->hflip->val << 1),
+			     &ret);
+		break;
+
 	case V4L2_CID_TEST_PATTERN:
 		if (ctrl->val) {
 			imx296_write(sensor, IMX296_PGHPOS, 8, &ret);
@@ -383,10 +428,36 @@ static const struct v4l2_ctrl_ops imx296_ctrl_ops = {
 	.s_ctrl = imx296_s_ctrl,
 };
 
+static void imx296_setup_hblank(struct imx296 *sensor, unsigned int width)
+{
+	/*
+	 * Horizontal blanking is controlled through the HMAX register, which
+	 * contains a line length in contains a line length in units of an
+	 * internal 74.25 MHz clock derived from the INCLK. The HMAX value is
+	 * currently fixed to 1100, convert it to a number of pixels based on
+	 * the nominal pixel rate.
+	 *
+	 * Horizontal blanking is fixed, regardless of the crop width, so
+	 * ensure the hblank limits are adjusted to account for this.
+	 */
+	unsigned int hblank = 1100 * 1188000000ULL / 10 / 74250000 - width;
+
+	if (!sensor->hblank) {
+		sensor->hblank = v4l2_ctrl_new_std(&sensor->ctrls,
+						   &imx296_ctrl_ops,
+						   V4L2_CID_HBLANK, hblank,
+						   hblank, 1, hblank);
+		if (sensor->hblank)
+			sensor->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+	} else {
+		__v4l2_ctrl_modify_range(sensor->hblank, hblank, hblank, 1,
+					 hblank);
+	}
+}
+
 static int imx296_ctrls_init(struct imx296 *sensor)
 {
 	struct v4l2_fwnode_device_properties props;
-	unsigned int hblank;
 	int ret;
 
 	ret = v4l2_fwnode_device_parse(sensor->dev, &props);
@@ -401,19 +472,17 @@ static int imx296_ctrls_init(struct imx296 *sensor)
 			  V4L2_CID_ANALOGUE_GAIN, IMX296_GAIN_MIN,
 			  IMX296_GAIN_MAX, 1, IMX296_GAIN_MIN);
 
-	/*
-	 * Horizontal blanking is controlled through the HMAX register, which
-	 * contains a line length in INCK clock units. The INCK frequency is
-	 * fixed to 74.25 MHz. The HMAX value is currently fixed to 1100,
-	 * convert it to a number of pixels based on the nominal pixel rate.
-	 */
-	hblank = 1100 * 1188000000ULL / 10 / 74250000
-	       - IMX296_PIXEL_ARRAY_WIDTH;
-	sensor->hblank = v4l2_ctrl_new_std(&sensor->ctrls, &imx296_ctrl_ops,
-					   V4L2_CID_HBLANK, hblank, hblank, 1,
-					   hblank);
-	if (sensor->hblank)
-		sensor->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+	sensor->hflip = v4l2_ctrl_new_std(&sensor->ctrls, &imx296_ctrl_ops,
+					  V4L2_CID_HFLIP, 0, 1, 1, 0);
+	if (sensor->hflip && !sensor->mono)
+		sensor->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+	sensor->vflip = v4l2_ctrl_new_std(&sensor->ctrls, &imx296_ctrl_ops,
+					  V4L2_CID_VFLIP, 0, 1, 1, 0);
+	if (sensor->vflip && !sensor->mono)
+		sensor->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+	imx296_setup_hblank(sensor, IMX296_PIXEL_ARRAY_WIDTH);
 
 	sensor->vblank = v4l2_ctrl_new_std(&sensor->ctrls, &imx296_ctrl_ops,
 					   V4L2_CID_VBLANK, 30,
@@ -468,7 +537,7 @@ static const struct {
 	{ IMX296_REG_8BIT(0x30a4), 0x5f },
 	{ IMX296_REG_8BIT(0x30a8), 0x91 },
 	{ IMX296_REG_8BIT(0x30ac), 0x28 },
-	{ IMX296_REG_8BIT(0x30af), 0x09 },
+	{ IMX296_REG_8BIT(0x30af), 0x0b },
 	{ IMX296_REG_8BIT(0x30df), 0x00 },
 	{ IMX296_REG_8BIT(0x3165), 0x00 },
 	{ IMX296_REG_8BIT(0x3169), 0x10 },
@@ -526,8 +595,11 @@ static int imx296_setup(struct imx296 *sensor, struct v4l2_subdev_state *state)
 		imx296_write(sensor, IMX296_FID0_ROIPV1, crop->top, &ret);
 		imx296_write(sensor, IMX296_FID0_ROIWH1, crop->width, &ret);
 		imx296_write(sensor, IMX296_FID0_ROIWV1, crop->height, &ret);
+		imx296_write(sensor, IMX296_MIPIC_AREA3W, crop->height, &ret);
 	} else {
 		imx296_write(sensor, IMX296_FID0_ROI, 0, &ret);
+		imx296_write(sensor, IMX296_MIPIC_AREA3W,
+			     IMX296_PIXEL_ARRAY_HEIGHT, &ret);
 	}
 
 	imx296_write(sensor, IMX296_CTRL0D,
@@ -566,7 +638,7 @@ static int imx296_setup(struct imx296 *sensor, struct v4l2_subdev_state *state)
 	imx296_write(sensor, IMX296_CTRL418C, sensor->clk_params->ctrl418c,
 		     &ret);
 
-	imx296_write(sensor, IMX296_GAINDLY, IMX296_GAINDLY_NONE, &ret);
+	imx296_write(sensor, IMX296_GAINDLY, IMX296_GAINDLY_1FRAME, &ret);
 	imx296_write(sensor, IMX296_BLKLEVEL, 0x03c, &ret);
 
 	return ret;
@@ -574,12 +646,24 @@ static int imx296_setup(struct imx296 *sensor, struct v4l2_subdev_state *state)
 
 static int imx296_stream_on(struct imx296 *sensor)
 {
-	int ret = 0;
+	int ret = 0, tm;
 
 	imx296_write(sensor, IMX296_CTRL00, 0, &ret);
 	usleep_range(2000, 5000);
+
+	/* external trigger mode: 0=normal, 1=triggered */
+	tm = (sensor->trigger_mode_of >= 0) ? sensor->trigger_mode_of : trigger_mode;
+	imx296_write(sensor, IMX296_CTRL0B,
+		     (tm == 1) ? IMX296_CTRL0B_TRIGEN : 0, &ret);
+	imx296_write(sensor, IMX296_LOWLAGTRG,
+		     (tm == 1) ? IMX296_LOWLAGTRG_FAST : 0, &ret);
+
 	imx296_write(sensor, IMX296_CTRL0A, 0, &ret);
 
+	/* vflip and hflip cannot change during streaming */
+	__v4l2_ctrl_grab(sensor->vflip, 1);
+	__v4l2_ctrl_grab(sensor->hflip, 1);
+
 	return ret;
 }
 
@@ -590,6 +674,9 @@ static int imx296_stream_off(struct imx296 *sensor)
 	imx296_write(sensor, IMX296_CTRL0A, IMX296_CTRL0A_XMSTA, &ret);
 	imx296_write(sensor, IMX296_CTRL00, IMX296_CTRL00_STANDBY, &ret);
 
+	__v4l2_ctrl_grab(sensor->vflip, 0);
+	__v4l2_ctrl_grab(sensor->hflip, 0);
+
 	return ret;
 }
 
@@ -650,8 +737,7 @@ static int imx296_enum_mbus_code(struct v4l2_subdev *sd,
 	if (code->index != 0)
 		return -EINVAL;
 
-	code->code = sensor->mono ? MEDIA_BUS_FMT_Y10_1X10
-		   : MEDIA_BUS_FMT_SBGGR10_1X10;
+	code->code = imx296_mbus_code(sensor);
 
 	return 0;
 }
@@ -661,10 +747,15 @@ static int imx296_enum_frame_size(struct v4l2_subdev *sd,
 				  struct v4l2_subdev_frame_size_enum *fse)
 {
 	const struct v4l2_mbus_framefmt *format;
+	struct imx296 *sensor = to_imx296(sd);
 
 	format = v4l2_subdev_state_get_format(state, fse->pad);
 
-	if (fse->index >= 2 || fse->code != format->code)
+	/*
+	 * Binning does not seem to work on either mono or colour sensor
+	 * variants. Disable enumerating the binned frame size for now.
+	 */
+	if (fse->index >= 1 || fse->code != imx296_mbus_code(sensor))
 		return -EINVAL;
 
 	fse->min_width = IMX296_PIXEL_ARRAY_WIDTH / (fse->index + 1);
@@ -686,35 +777,12 @@ static int imx296_set_format(struct v4l2_subdev *sd,
 	crop = v4l2_subdev_state_get_crop(state, fmt->pad);
 	format = v4l2_subdev_state_get_format(state, fmt->pad);
 
-	/*
-	 * Binning is only allowed when cropping is disabled according to the
-	 * documentation. This should be double-checked.
-	 */
-	if (crop->width == IMX296_PIXEL_ARRAY_WIDTH &&
-	    crop->height == IMX296_PIXEL_ARRAY_HEIGHT) {
-		unsigned int width;
-		unsigned int height;
-		unsigned int hratio;
-		unsigned int vratio;
-
-		/* Clamp the width and height to avoid dividing by zero. */
-		width = clamp_t(unsigned int, fmt->format.width,
-				crop->width / 2, crop->width);
-		height = clamp_t(unsigned int, fmt->format.height,
-				 crop->height / 2, crop->height);
-
-		hratio = DIV_ROUND_CLOSEST(crop->width, width);
-		vratio = DIV_ROUND_CLOSEST(crop->height, height);
-
-		format->width = crop->width / hratio;
-		format->height = crop->height / vratio;
-	} else {
-		format->width = crop->width;
-		format->height = crop->height;
-	}
+	format->width = crop->width;
+	format->height = crop->height;
 
-	format->code = sensor->mono ? MEDIA_BUS_FMT_Y10_1X10
-		     : MEDIA_BUS_FMT_SBGGR10_1X10;
+	imx296_setup_hblank(sensor, format->width);
+
+	format->code = imx296_mbus_code(sensor);
 	format->field = V4L2_FIELD_NONE;
 	format->colorspace = V4L2_COLORSPACE_RAW;
 	format->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
@@ -1013,6 +1081,7 @@ static int imx296_probe(struct i2c_client *client)
 	unsigned long clk_rate;
 	struct imx296 *sensor;
 	unsigned int i;
+	u32 tm_of;
 	int ret;
 
 	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
@@ -1079,6 +1148,10 @@ static int imx296_probe(struct i2c_client *client)
 	if (ret < 0)
 		goto err_power;
 
+	/* Default the trigger mode from OF to -1, which means invalid */
+	ret = of_property_read_u32(client->dev.of_node, "trigger-mode", &tm_of);
+	sensor->trigger_mode_of = (ret == 0) ? tm_of : -1;
+
 	/* Initialize the V4L2 subdev. */
 	ret = imx296_subdev_init(sensor);
 	if (ret < 0)
diff --git a/drivers/media/i2c/imx335.c b/drivers/media/i2c/imx335.c
index 9b4db4cd4929..7609f4e42463 100644
--- a/drivers/media/i2c/imx335.c
+++ b/drivers/media/i2c/imx335.c
@@ -35,6 +35,7 @@
 
 /* Lines per frame */
 #define IMX335_REG_VMAX			CCI_REG24_LE(0x3030)
+#define IMX335_REG_HMAX			CCI_REG16_LE(0x3034)
 
 #define IMX335_REG_OPB_SIZE_V		CCI_REG8(0x304c)
 #define IMX335_REG_ADBIT		CCI_REG8(0x3050)
@@ -42,10 +43,13 @@
 
 #define IMX335_REG_SHUTTER		CCI_REG24_LE(0x3058)
 #define IMX335_EXPOSURE_MIN		1
-#define IMX335_EXPOSURE_OFFSET		9
+#define IMX335_SHUTTER_MIN		9
+#define IMX335_SHUTTER_MIN_BINNED	17
 #define IMX335_EXPOSURE_STEP		1
 #define IMX335_EXPOSURE_DEFAULT		0x0648
 
+#define IMX335_REG_AREA2_WIDTH_1	CCI_REG16_LE(0x3072)
+
 #define IMX335_REG_AREA3_ST_ADR_1	CCI_REG16_LE(0x3074)
 #define IMX335_REG_AREA3_WIDTH_1	CCI_REG16_LE(0x3076)
 
@@ -56,6 +60,9 @@
 #define IMX335_AGAIN_STEP		1
 #define IMX335_AGAIN_DEFAULT		0
 
+/* Vertical flip */
+#define IMX335_REG_VREVERSE		CCI_REG8(0x304f)
+
 #define IMX335_REG_TPG_TESTCLKEN	CCI_REG8(0x3148)
 
 #define IMX335_REG_INCLKSEL1		CCI_REG16_LE(0x314c)
@@ -121,12 +128,19 @@
 #define IMX335_NUM_DATA_LANES	4
 
 /* IMX335 native and active pixel array size. */
-#define IMX335_NATIVE_WIDTH		2616U
-#define IMX335_NATIVE_HEIGHT		1964U
-#define IMX335_PIXEL_ARRAY_LEFT		12U
-#define IMX335_PIXEL_ARRAY_TOP		12U
-#define IMX335_PIXEL_ARRAY_WIDTH	2592U
-#define IMX335_PIXEL_ARRAY_HEIGHT	1944U
+static const struct v4l2_rect imx335_native_area = {
+	.top = 0,
+	.left = 0,
+	.width = 2696,
+	.height = 2044,
+};
+
+static const struct v4l2_rect imx335_active_area = {
+	.top = 50,
+	.left = 36,
+	.width = 2624,
+	.height = 1944,
+};
 
 /**
  * struct imx335_reg_list - imx335 sensor register list
@@ -144,8 +158,14 @@ static const char * const imx335_supply_name[] = {
 	"dvdd", /* Digital Core (1.2V) supply */
 };
 
+enum imx335_scan_mode {
+	IMX335_ALL_PIXEL,
+	IMX335_2_2_BINNING,
+};
+
 /**
  * struct imx335_mode - imx335 sensor mode structure
+ * @scan_mode: Configuration scan mode (All pixel / 2x2Binning)
  * @width: Frame width
  * @height: Frame height
  * @code: Format code
@@ -155,8 +175,11 @@ static const char * const imx335_supply_name[] = {
  * @vblank_max: Maximum vertical blanking in lines
  * @pclk: Sensor pixel clock
  * @reg_list: Register list for sensor mode
+ * @vflip_normal: Register list vflip (normal readout)
+ * @vflip_inverted: Register list vflip (inverted readout)
  */
 struct imx335_mode {
+	enum imx335_scan_mode scan_mode;
 	u32 width;
 	u32 height;
 	u32 code;
@@ -166,6 +189,8 @@ struct imx335_mode {
 	u32 vblank_max;
 	u64 pclk;
 	struct imx335_reg_list reg_list;
+	struct imx335_reg_list vflip_normal;
+	struct imx335_reg_list vflip_inverted;
 };
 
 /**
@@ -183,12 +208,12 @@ struct imx335_mode {
  * @pclk_ctrl: Pointer to pixel clock control
  * @hblank_ctrl: Pointer to horizontal blanking control
  * @vblank_ctrl: Pointer to vertical blanking control
+ * @vflip: Pointer to vertical flip control
  * @exp_ctrl: Pointer to exposure control
  * @again_ctrl: Pointer to analog gain control
  * @vblank: Vertical blanking in lines
  * @lane_mode: Mode for number of connected data lanes
  * @cur_mode: Pointer to current selected sensor mode
- * @mutex: Mutex for serializing sensor controls
  * @link_freq_bitmap: Menu bitmap for link_freq_ctrl
  * @cur_mbus_code: Currently selected media bus format code
  */
@@ -207,6 +232,7 @@ struct imx335 {
 	struct v4l2_ctrl *pclk_ctrl;
 	struct v4l2_ctrl *hblank_ctrl;
 	struct v4l2_ctrl *vblank_ctrl;
+	struct v4l2_ctrl *vflip;
 	struct {
 		struct v4l2_ctrl *exp_ctrl;
 		struct v4l2_ctrl *again_ctrl;
@@ -214,7 +240,6 @@ struct imx335 {
 	u32 vblank;
 	u32 lane_mode;
 	const struct imx335_mode *cur_mode;
-	struct mutex mutex;
 	unsigned long link_freq_bitmap;
 	u32 cur_mbus_code;
 };
@@ -252,17 +277,37 @@ static const int imx335_tpg_val[] = {
 };
 
 /* Sensor mode registers */
-static const struct cci_reg_sequence mode_2592x1940_regs[] = {
+static const struct cci_reg_sequence mode_2592x1944_regs[] = {
 	{ IMX335_REG_MODE_SELECT, IMX335_MODE_STANDBY },
 	{ IMX335_REG_MASTER_MODE, 0x00 },
 	{ IMX335_REG_WINMODE, 0x04 },
+	{ IMX335_REG_HMAX, 550 },
 	{ IMX335_REG_HTRIMMING_START, 48 },
 	{ IMX335_REG_HNUM, 2592 },
 	{ IMX335_REG_Y_OUT_SIZE, 1944 },
-	{ IMX335_REG_AREA3_ST_ADR_1, 176 },
+	{ IMX335_REG_AREA2_WIDTH_1, 40 },
 	{ IMX335_REG_AREA3_WIDTH_1, 3928 },
 	{ IMX335_REG_OPB_SIZE_V, 0 },
 	{ IMX335_REG_XVS_XHS_DRV, 0x00 },
+};
+
+static const struct cci_reg_sequence mode_1312x972_regs[] = {
+	{ IMX335_REG_MODE_SELECT, IMX335_MODE_STANDBY },
+	{ IMX335_REG_MASTER_MODE, 0x00 },
+	{ IMX335_REG_WINMODE, 0x01 },
+	{ IMX335_REG_HMAX, 275 },
+	{ IMX335_REG_HTRIMMING_START, 48 },
+	{ IMX335_REG_HNUM, 2600 },
+	{ IMX335_REG_Y_OUT_SIZE, 972 },
+	{ IMX335_REG_AREA2_WIDTH_1, 48 },
+	{ IMX335_REG_AREA3_WIDTH_1, 3936 },
+	{ IMX335_REG_OPB_SIZE_V, 0 },
+	{ IMX335_REG_XVS_XHS_DRV, 0x00 },
+	{ CCI_REG8(0x3300), 1 }, /* TCYCLE */
+	{ CCI_REG8(0x3199), 0x30 }, /* HADD/VADD */
+};
+
+static const struct cci_reg_sequence imx335_common_regs[] = {
 	{ CCI_REG8(0x3288), 0x21 },
 	{ CCI_REG8(0x328a), 0x02 },
 	{ CCI_REG8(0x3414), 0x05 },
@@ -333,16 +378,92 @@ static const struct cci_reg_sequence mode_2592x1940_regs[] = {
 	{ CCI_REG8(0x3a00), 0x00 },
 };
 
-static const struct cci_reg_sequence raw10_framefmt_regs[] = {
-	{ IMX335_REG_ADBIT, 0x00 },
-	{ IMX335_REG_MDBIT, 0x00 },
-	{ IMX335_REG_ADBIT1, 0x1ff },
+static const struct cci_reg_sequence mode_2592x1944_vflip_normal[] = {
+	{ IMX335_REG_AREA3_ST_ADR_1, 176 },
+
+	/* Undocumented V-Flip related registers on Page 55 of datasheet. */
+	{ CCI_REG8(0x3081), 0x02, },
+	{ CCI_REG8(0x3083), 0x02, },
+	{ CCI_REG16_LE(0x30b6), 0x00 },
+	{ CCI_REG16_LE(0x3116), 0x08 },
+};
+
+static const struct cci_reg_sequence mode_2592x1944_vflip_inverted[] = {
+	{ IMX335_REG_AREA3_ST_ADR_1, 4112 },
+
+	/* Undocumented V-Flip related registers on Page 55 of datasheet. */
+	{ CCI_REG8(0x3081), 0xfe, },
+	{ CCI_REG8(0x3083), 0xfe, },
+	{ CCI_REG16_LE(0x30b6), 0x1fa },
+	{ CCI_REG16_LE(0x3116), 0x002 },
+};
+
+static const struct cci_reg_sequence mode_1312x972_vflip_normal[] = {
+	{ IMX335_REG_AREA3_ST_ADR_1, 176 },
+
+	/* Undocumented */
+	{ CCI_REG8(0x3078), 0x04 },
+	{ CCI_REG8(0x3079), 0xfd },
+	{ CCI_REG8(0x307a), 0x04 },
+	{ CCI_REG8(0x307b), 0xfe },
+	{ CCI_REG8(0x307c), 0x04 },
+	{ CCI_REG8(0x307d), 0xfb },
+	{ CCI_REG8(0x307e), 0x04 },
+	{ CCI_REG8(0x307f), 0x02 },
+	{ CCI_REG8(0x3080), 0x04 },
+	{ CCI_REG8(0x3081), 0xfd },
+	{ CCI_REG8(0x3082), 0x04 },
+	{ CCI_REG8(0x3083), 0xfe },
+	{ CCI_REG8(0x3084), 0x04 },
+	{ CCI_REG8(0x3085), 0xfb },
+	{ CCI_REG8(0x3086), 0x04 },
+	{ CCI_REG8(0x3087), 0x02 },
+	{ CCI_REG8(0x30a4), 0x77 },
+	{ CCI_REG8(0x30a8), 0x20 },
+	{ CCI_REG8(0x30a9), 0x00 },
+	{ CCI_REG8(0x30ac), 0x08 },
+	{ CCI_REG8(0x30ad), 0x08 },
+	{ CCI_REG8(0x30b0), 0x20 },
+	{ CCI_REG8(0x30b1), 0x00 },
+	{ CCI_REG8(0x30b4), 0x10 },
+	{ CCI_REG8(0x30b5), 0x10 },
+	{ CCI_REG16_LE(0x30b6), 0x00 },
+	{ CCI_REG16_LE(0x3112), 0x10 },
+	{ CCI_REG16_LE(0x3116), 0x10 },
 };
 
-static const struct cci_reg_sequence raw12_framefmt_regs[] = {
-	{ IMX335_REG_ADBIT, 0x01 },
-	{ IMX335_REG_MDBIT, 0x01 },
-	{ IMX335_REG_ADBIT1, 0x47 },
+static const struct cci_reg_sequence mode_1312x972_vflip_inverted[] = {
+	{ IMX335_REG_AREA3_ST_ADR_1, 4112 },
+
+	/* Undocumented */
+	{ CCI_REG8(0x3078), 0x04 },
+	{ CCI_REG8(0x3079), 0xfd },
+	{ CCI_REG8(0x307a), 0x04 },
+	{ CCI_REG8(0x307b), 0xfe },
+	{ CCI_REG8(0x307c), 0x04 },
+	{ CCI_REG8(0x307d), 0xfb },
+	{ CCI_REG8(0x307e), 0x04 },
+	{ CCI_REG8(0x307f), 0x02 },
+	{ CCI_REG8(0x3080), 0xfc },
+	{ CCI_REG8(0x3081), 0x05 },
+	{ CCI_REG8(0x3082), 0xfc },
+	{ CCI_REG8(0x3083), 0x02 },
+	{ CCI_REG8(0x3084), 0xfc },
+	{ CCI_REG8(0x3085), 0x03 },
+	{ CCI_REG8(0x3086), 0xfc },
+	{ CCI_REG8(0x3087), 0xfe },
+	{ CCI_REG8(0x30a4), 0x77 },
+	{ CCI_REG8(0x30a8), 0x20 },
+	{ CCI_REG8(0x30a9), 0x00 },
+	{ CCI_REG8(0x30ac), 0x08 },
+	{ CCI_REG8(0x30ad), 0x78 },
+	{ CCI_REG8(0x30b0), 0x20 },
+	{ CCI_REG8(0x30b1), 0x00 },
+	{ CCI_REG8(0x30b4), 0x10 },
+	{ CCI_REG8(0x30b5), 0x70 },
+	{ CCI_REG16_LE(0x30b6), 0x01f2 },
+	{ CCI_REG16_LE(0x3112), 0x10 },
+	{ CCI_REG16_LE(0x3116), 0x02 },
 };
 
 static const struct cci_reg_sequence mipi_data_rate_1188Mbps[] = {
@@ -407,17 +528,49 @@ static const u32 imx335_mbus_codes[] = {
 };
 
 /* Supported sensor mode configurations */
-static const struct imx335_mode supported_mode = {
-	.width = 2592,
-	.height = 1944,
-	.hblank = 342,
-	.vblank = 2556,
-	.vblank_min = 2556,
-	.vblank_max = 133060,
-	.pclk = 396000000,
-	.reg_list = {
-		.num_of_regs = ARRAY_SIZE(mode_2592x1940_regs),
-		.regs = mode_2592x1940_regs,
+static const struct imx335_mode supported_modes[] = {
+	{
+		.scan_mode = IMX335_ALL_PIXEL,
+		.width = 2592,
+		.height = 1944,
+		.hblank = 342,
+		.vblank = 2556,
+		.vblank_min = 2556,
+		.vblank_max = 133060,
+		.pclk = 396000000,
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_2592x1944_regs),
+			.regs = mode_2592x1944_regs,
+		},
+		.vflip_normal = {
+			.num_of_regs = ARRAY_SIZE(mode_2592x1944_vflip_normal),
+			.regs = mode_2592x1944_vflip_normal,
+		},
+		.vflip_inverted = {
+			.num_of_regs = ARRAY_SIZE(mode_2592x1944_vflip_inverted),
+			.regs = mode_2592x1944_vflip_inverted,
+		}
+	}, {
+		.scan_mode = IMX335_2_2_BINNING,
+		.width = 1312,
+		.height = 972,
+		.hblank = 155,
+		.vblank = 3528,
+		.vblank_min = 3528,
+		.vblank_max = 133060,
+		.pclk = 396000000,
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_1312x972_regs),
+			.regs = mode_1312x972_regs,
+		},
+		.vflip_normal = {
+			.num_of_regs = ARRAY_SIZE(mode_1312x972_vflip_normal),
+			.regs = mode_1312x972_vflip_normal,
+		},
+		.vflip_inverted = {
+			.num_of_regs = ARRAY_SIZE(mode_1312x972_vflip_inverted),
+			.regs = mode_1312x972_vflip_inverted,
+		},
 	},
 };
 
@@ -449,7 +602,8 @@ static int imx335_update_controls(struct imx335 *imx335,
 	if (ret)
 		return ret;
 
-	ret = __v4l2_ctrl_s_ctrl(imx335->hblank_ctrl, mode->hblank);
+	ret = __v4l2_ctrl_modify_range(imx335->hblank_ctrl, mode->hblank,
+				       mode->hblank, 1, mode->hblank);
 	if (ret)
 		return ret;
 
@@ -492,6 +646,26 @@ static int imx335_update_exp_gain(struct imx335 *imx335, u32 exposure, u32 gain)
 	return ret;
 }
 
+static int imx335_update_vertical_flip(struct imx335 *imx335, u32 vflip)
+{
+	int ret = 0;
+
+	if (vflip)
+		cci_multi_reg_write(imx335->cci,
+				    imx335->cur_mode->vflip_inverted.regs,
+				    imx335->cur_mode->vflip_inverted.num_of_regs,
+				    &ret);
+	else
+		cci_multi_reg_write(imx335->cci,
+				    imx335->cur_mode->vflip_normal.regs,
+				    imx335->cur_mode->vflip_normal.num_of_regs,
+				    &ret);
+	if (ret)
+		return ret;
+
+	return cci_write(imx335->cci, IMX335_REG_VREVERSE, vflip, NULL);
+}
+
 static int imx335_update_test_pattern(struct imx335 *imx335, u32 pattern_index)
 {
 	int ret = 0;
@@ -553,18 +727,22 @@ static int imx335_set_ctrl(struct v4l2_ctrl *ctrl)
 
 	/* Propagate change of current control to all related controls */
 	if (ctrl->id == V4L2_CID_VBLANK) {
+		u32 shutter_min = IMX335_SHUTTER_MIN;
+		u32 lpfr;
+
 		imx335->vblank = imx335->vblank_ctrl->val;
+		lpfr = imx335->vblank + imx335->cur_mode->height;
 
 		dev_dbg(imx335->dev, "Received vblank %u, new lpfr %u\n",
-			imx335->vblank,
-			imx335->vblank + imx335->cur_mode->height);
+			imx335->vblank, lpfr);
+
+		if (imx335->cur_mode->scan_mode == IMX335_2_2_BINNING)
+			shutter_min = IMX335_SHUTTER_MIN_BINNED;
 
 		ret = __v4l2_ctrl_modify_range(imx335->exp_ctrl,
 					       IMX335_EXPOSURE_MIN,
-					       imx335->vblank +
-					       imx335->cur_mode->height -
-					       IMX335_EXPOSURE_OFFSET,
-					       1, IMX335_EXPOSURE_DEFAULT);
+					       lpfr - shutter_min, 1,
+					       IMX335_EXPOSURE_DEFAULT);
 		if (ret)
 			return ret;
 	}
@@ -593,6 +771,10 @@ static int imx335_set_ctrl(struct v4l2_ctrl *ctrl)
 
 		ret = imx335_update_exp_gain(imx335, exposure, analog_gain);
 
+		break;
+	case V4L2_CID_VFLIP:
+		ret = imx335_update_vertical_flip(imx335, ctrl->val);
+
 		break;
 	case V4L2_CID_TEST_PATTERN:
 		ret = imx335_update_test_pattern(imx335, ctrl->val);
@@ -660,17 +842,16 @@ static int imx335_enum_frame_size(struct v4l2_subdev *sd,
 	struct imx335 *imx335 = to_imx335(sd);
 	u32 code;
 
-	/* Only a single supported_mode available. */
-	if (fsize->index > 0)
+	if (fsize->index >= ARRAY_SIZE(supported_modes))
 		return -EINVAL;
 
 	code = imx335_get_format_code(imx335, fsize->code);
 	if (fsize->code != code)
 		return -EINVAL;
 
-	fsize->min_width = supported_mode.width;
+	fsize->min_width = supported_modes[fsize->index].width;
 	fsize->max_width = fsize->min_width;
-	fsize->min_height = supported_mode.height;
+	fsize->min_height = supported_modes[fsize->index].height;
 	fsize->max_height = fsize->min_height;
 
 	return 0;
@@ -697,36 +878,6 @@ static void imx335_fill_pad_format(struct imx335 *imx335,
 	fmt->format.xfer_func = V4L2_XFER_FUNC_NONE;
 }
 
-/**
- * imx335_get_pad_format() - Get subdevice pad format
- * @sd: pointer to imx335 V4L2 sub-device structure
- * @sd_state: V4L2 sub-device configuration
- * @fmt: V4L2 sub-device format need to be set
- *
- * Return: 0 if successful, error code otherwise.
- */
-static int imx335_get_pad_format(struct v4l2_subdev *sd,
-				 struct v4l2_subdev_state *sd_state,
-				 struct v4l2_subdev_format *fmt)
-{
-	struct imx335 *imx335 = to_imx335(sd);
-
-	mutex_lock(&imx335->mutex);
-
-	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
-		struct v4l2_mbus_framefmt *framefmt;
-
-		framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad);
-		fmt->format = *framefmt;
-	} else {
-		imx335_fill_pad_format(imx335, imx335->cur_mode, fmt);
-	}
-
-	mutex_unlock(&imx335->mutex);
-
-	return 0;
-}
-
 /**
  * imx335_set_pad_format() - Set subdevice pad format
  * @sd: pointer to imx335 V4L2 sub-device structure
@@ -740,12 +891,16 @@ static int imx335_set_pad_format(struct v4l2_subdev *sd,
 				 struct v4l2_subdev_format *fmt)
 {
 	struct imx335 *imx335 = to_imx335(sd);
+	struct v4l2_mbus_framefmt *format;
 	const struct imx335_mode *mode;
+	struct v4l2_rect *crop;
 	int i, ret = 0;
 
-	mutex_lock(&imx335->mutex);
+	mode = v4l2_find_nearest_size(supported_modes,
+				      ARRAY_SIZE(supported_modes),
+				      width, height,
+				      fmt->format.width, fmt->format.height);
 
-	mode = &supported_mode;
 	for (i = 0; i < ARRAY_SIZE(imx335_mbus_codes); i++) {
 		if (imx335_mbus_codes[i] == fmt->format.code)
 			imx335->cur_mbus_code = imx335_mbus_codes[i];
@@ -753,19 +908,25 @@ static int imx335_set_pad_format(struct v4l2_subdev *sd,
 
 	imx335_fill_pad_format(imx335, mode, fmt);
 
-	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
-		struct v4l2_mbus_framefmt *framefmt;
+	format = v4l2_subdev_state_get_format(sd_state, fmt->pad);
+	*format = fmt->format;
 
-		framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad);
-		*framefmt = fmt->format;
-	} else {
+	crop = v4l2_subdev_state_get_crop(sd_state, fmt->pad);
+	crop->width = fmt->format.width;
+	crop->height = fmt->format.height;
+	if (mode->scan_mode == IMX335_2_2_BINNING) {
+		crop->width *= 2;
+		crop->height *= 2;
+	}
+	crop->left = (imx335_native_area.width - crop->width) / 2;
+	crop->top = (imx335_native_area.height - crop->height) / 2;
+
+	if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
 		ret = imx335_update_controls(imx335, mode);
 		if (!ret)
 			imx335->cur_mode = mode;
 	}
 
-	mutex_unlock(&imx335->mutex);
-
 	return ret;
 }
 
@@ -783,14 +944,12 @@ static int imx335_init_state(struct v4l2_subdev *sd,
 	struct v4l2_subdev_format fmt = { 0 };
 
 	fmt.which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE;
-	imx335_fill_pad_format(imx335, &supported_mode, &fmt);
+	imx335_fill_pad_format(imx335, &supported_modes[0], &fmt);
 
-	mutex_lock(&imx335->mutex);
 	__v4l2_ctrl_modify_range(imx335->link_freq_ctrl, 0,
 				 __fls(imx335->link_freq_bitmap),
 				 ~(imx335->link_freq_bitmap),
 				 __ffs(imx335->link_freq_bitmap));
-	mutex_unlock(&imx335->mutex);
 
 	return imx335_set_pad_format(sd, sd_state, &fmt);
 }
@@ -808,22 +967,18 @@ static int imx335_get_selection(struct v4l2_subdev *sd,
 				struct v4l2_subdev_selection *sel)
 {
 	switch (sel->target) {
-	case V4L2_SEL_TGT_NATIVE_SIZE:
-		sel->r.top = 0;
-		sel->r.left = 0;
-		sel->r.width = IMX335_NATIVE_WIDTH;
-		sel->r.height = IMX335_NATIVE_HEIGHT;
+	case V4L2_SEL_TGT_CROP:
+		sel->r = *v4l2_subdev_state_get_crop(sd_state, 0);
 
 		return 0;
 
-	case V4L2_SEL_TGT_CROP:
+	case V4L2_SEL_TGT_NATIVE_SIZE:
+		sel->r = imx335_native_area;
+		return 0;
+
 	case V4L2_SEL_TGT_CROP_DEFAULT:
 	case V4L2_SEL_TGT_CROP_BOUNDS:
-		sel->r.top = IMX335_PIXEL_ARRAY_TOP;
-		sel->r.left = IMX335_PIXEL_ARRAY_LEFT;
-		sel->r.width = IMX335_PIXEL_ARRAY_WIDTH;
-		sel->r.height = IMX335_PIXEL_ARRAY_HEIGHT;
-
+		sel->r = imx335_active_area;
 		return 0;
 	}
 
@@ -832,39 +987,65 @@ static int imx335_get_selection(struct v4l2_subdev *sd,
 
 static int imx335_set_framefmt(struct imx335 *imx335)
 {
-	switch (imx335->cur_mbus_code) {
-	case MEDIA_BUS_FMT_SRGGB10_1X10:
-		return cci_multi_reg_write(imx335->cci, raw10_framefmt_regs,
-					   ARRAY_SIZE(raw10_framefmt_regs),
-					   NULL);
-
-	case MEDIA_BUS_FMT_SRGGB12_1X12:
-		return cci_multi_reg_write(imx335->cci, raw12_framefmt_regs,
-					   ARRAY_SIZE(raw12_framefmt_regs),
-					   NULL);
+	/*
+	 * In the all-pixel scan mode the AD conversion shall match the output
+	 * bit width requested.
+	 *
+	 * However, when 2/2 binning is enabled, the AD conversion is always
+	 * 10-bit, so we ensure ADBIT is clear and ADBIT1 is assigned 0x1ff.
+	 * That's as much as the documentation gives us...
+	 */
+	int ret = 0;
+	u8 bpp = imx335->cur_mbus_code == MEDIA_BUS_FMT_SRGGB10_1X10 ? 10 : 12;
+	u8 ad_conv = bpp;
+
+	/* Start with the output mode */
+	cci_write(imx335->cci, IMX335_REG_MDBIT, bpp == 12, &ret);
+
+	/* Enforce 10 bit AD on binning modes */
+	if (imx335->cur_mode->scan_mode == IMX335_2_2_BINNING)
+		ad_conv = 10;
+
+	/* AD Conversion configuration */
+	if (ad_conv == 10) {
+		cci_write(imx335->cci, IMX335_REG_ADBIT, 0x00, &ret);
+		cci_write(imx335->cci, IMX335_REG_ADBIT1, 0x1ff, &ret);
+	} else { /* 12 bit AD Conversion */
+		cci_write(imx335->cci, IMX335_REG_ADBIT, 0x01, &ret);
+		cci_write(imx335->cci, IMX335_REG_ADBIT1, 0x47, &ret);
 	}
 
-	return -EINVAL;
+	return ret;
 }
 
 /**
- * imx335_start_streaming() - Start sensor stream
- * @imx335: pointer to imx335 device
+ * imx335_enable_streams() - Enable sensor streams
+ * @sd: V4L2 subdevice
+ * @state: V4L2 subdevice state
+ * @pad: The pad to enable
+ * @streams_mask: Bitmask of streams to enable
  *
  * Return: 0 if successful, error code otherwise.
  */
-static int imx335_start_streaming(struct imx335 *imx335)
+static int imx335_enable_streams(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *state, u32 pad,
+				 u64 streams_mask)
 {
+	struct imx335 *imx335 = to_imx335(sd);
 	const struct imx335_reg_list *reg_list;
 	int ret;
 
+	ret = pm_runtime_resume_and_get(imx335->dev);
+	if (ret < 0)
+		return ret;
+
 	/* Setup PLL */
 	reg_list = &link_freq_reglist[__ffs(imx335->link_freq_bitmap)];
 	ret = cci_multi_reg_write(imx335->cci, reg_list->regs,
 				  reg_list->num_of_regs, NULL);
 	if (ret) {
 		dev_err(imx335->dev, "%s failed to set plls\n", __func__);
-		return ret;
+		goto err_rpm_put;
 	}
 
 	/* Write sensor mode registers */
@@ -873,27 +1054,35 @@ static int imx335_start_streaming(struct imx335 *imx335)
 				  reg_list->num_of_regs, NULL);
 	if (ret) {
 		dev_err(imx335->dev, "fail to write initial registers\n");
-		return ret;
+		goto err_rpm_put;
+	}
+
+	/* Write sensor common registers */
+	ret = cci_multi_reg_write(imx335->cci, imx335_common_regs,
+				  ARRAY_SIZE(imx335_common_regs), NULL);
+	if (ret) {
+		dev_err(imx335->dev, "fail to write initial registers\n");
+		goto err_rpm_put;
 	}
 
 	ret = imx335_set_framefmt(imx335);
 	if (ret) {
 		dev_err(imx335->dev, "%s failed to set frame format: %d\n",
 			__func__, ret);
-		return ret;
+		goto err_rpm_put;
 	}
 
 	/* Configure lanes */
 	ret = cci_write(imx335->cci, IMX335_REG_LANEMODE,
 			imx335->lane_mode, NULL);
 	if (ret)
-		return ret;
+		goto err_rpm_put;
 
 	/* Setup handler will write actual exposure and gain */
 	ret =  __v4l2_ctrl_handler_setup(imx335->sd.ctrl_handler);
 	if (ret) {
 		dev_err(imx335->dev, "fail to setup handler\n");
-		return ret;
+		goto err_rpm_put;
 	}
 
 	/* Start streaming */
@@ -901,62 +1090,39 @@ static int imx335_start_streaming(struct imx335 *imx335)
 			IMX335_MODE_STREAMING, NULL);
 	if (ret) {
 		dev_err(imx335->dev, "fail to start streaming\n");
-		return ret;
+		goto err_rpm_put;
 	}
 
 	/* Initial regulator stabilization period */
 	usleep_range(18000, 20000);
 
 	return 0;
-}
 
-/**
- * imx335_stop_streaming() - Stop sensor stream
- * @imx335: pointer to imx335 device
- *
- * Return: 0 if successful, error code otherwise.
- */
-static int imx335_stop_streaming(struct imx335 *imx335)
-{
-	return cci_write(imx335->cci, IMX335_REG_MODE_SELECT,
-			 IMX335_MODE_STANDBY, NULL);
+err_rpm_put:
+	pm_runtime_put(imx335->dev);
+
+	return ret;
 }
 
 /**
- * imx335_set_stream() - Enable sensor streaming
- * @sd: pointer to imx335 subdevice
- * @enable: set to enable sensor streaming
+ * imx335_disable_streams() - Disable sensor streams
+ * @sd: V4L2 subdevice
+ * @state: V4L2 subdevice state
+ * @pad: The pad to disable
+ * @streams_mask: Bitmask of streams to disable
  *
  * Return: 0 if successful, error code otherwise.
  */
-static int imx335_set_stream(struct v4l2_subdev *sd, int enable)
+static int imx335_disable_streams(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *state, u32 pad,
+				  u64 streams_mask)
 {
 	struct imx335 *imx335 = to_imx335(sd);
 	int ret;
 
-	mutex_lock(&imx335->mutex);
-
-	if (enable) {
-		ret = pm_runtime_resume_and_get(imx335->dev);
-		if (ret)
-			goto error_unlock;
-
-		ret = imx335_start_streaming(imx335);
-		if (ret)
-			goto error_power_off;
-	} else {
-		imx335_stop_streaming(imx335);
-		pm_runtime_put(imx335->dev);
-	}
-
-	mutex_unlock(&imx335->mutex);
-
-	return 0;
-
-error_power_off:
+	ret = cci_write(imx335->cci, IMX335_REG_MODE_SELECT,
+			IMX335_MODE_STANDBY, NULL);
 	pm_runtime_put(imx335->dev);
-error_unlock:
-	mutex_unlock(&imx335->mutex);
 
 	return ret;
 }
@@ -1077,7 +1243,7 @@ static int imx335_parse_hw_config(struct imx335 *imx335)
 
 /* V4l2 subdevice ops */
 static const struct v4l2_subdev_video_ops imx335_video_ops = {
-	.s_stream = imx335_set_stream,
+	.s_stream = v4l2_subdev_s_stream_helper,
 };
 
 static const struct v4l2_subdev_pad_ops imx335_pad_ops = {
@@ -1085,8 +1251,10 @@ static const struct v4l2_subdev_pad_ops imx335_pad_ops = {
 	.enum_frame_size = imx335_enum_frame_size,
 	.get_selection = imx335_get_selection,
 	.set_selection = imx335_get_selection,
-	.get_fmt = imx335_get_pad_format,
+	.get_fmt = v4l2_subdev_get_fmt,
 	.set_fmt = imx335_set_pad_format,
+	.enable_streams = imx335_enable_streams,
+	.disable_streams = imx335_disable_streams,
 };
 
 static const struct v4l2_subdev_ops imx335_subdev_ops = {
@@ -1168,7 +1336,7 @@ static int imx335_init_controls(struct imx335 *imx335)
 	struct v4l2_ctrl_handler *ctrl_hdlr = &imx335->ctrl_handler;
 	const struct imx335_mode *mode = imx335->cur_mode;
 	struct v4l2_fwnode_device_properties props;
-	u32 lpfr;
+	u32 lpfr, shutter_min;
 	int ret;
 
 	ret = v4l2_fwnode_device_parse(imx335->dev, &props);
@@ -1176,20 +1344,20 @@ static int imx335_init_controls(struct imx335 *imx335)
 		return ret;
 
 	/* v4l2_fwnode_device_properties can add two more controls */
-	ret = v4l2_ctrl_handler_init(ctrl_hdlr, 9);
+	ret = v4l2_ctrl_handler_init(ctrl_hdlr, 10);
 	if (ret)
 		return ret;
 
-	/* Serialize controls with sensor device */
-	ctrl_hdlr->lock = &imx335->mutex;
-
 	/* Initialize exposure and gain */
 	lpfr = mode->vblank + mode->height;
+	shutter_min = IMX335_SHUTTER_MIN;
+	if (mode->scan_mode == IMX335_2_2_BINNING)
+		shutter_min = IMX335_SHUTTER_MIN_BINNED;
 	imx335->exp_ctrl = v4l2_ctrl_new_std(ctrl_hdlr,
 					     &imx335_ctrl_ops,
 					     V4L2_CID_EXPOSURE,
 					     IMX335_EXPOSURE_MIN,
-					     lpfr - IMX335_EXPOSURE_OFFSET,
+					     lpfr - shutter_min,
 					     IMX335_EXPOSURE_STEP,
 					     IMX335_EXPOSURE_DEFAULT);
 
@@ -1211,6 +1379,13 @@ static int imx335_init_controls(struct imx335 *imx335)
 
 	v4l2_ctrl_cluster(2, &imx335->exp_ctrl);
 
+	imx335->vflip = v4l2_ctrl_new_std(ctrl_hdlr,
+					  &imx335_ctrl_ops,
+					  V4L2_CID_VFLIP,
+					  0, 1, 1, 0);
+	if (imx335->vflip)
+		imx335->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
 	imx335->vblank_ctrl = v4l2_ctrl_new_std(ctrl_hdlr,
 						&imx335_ctrl_ops,
 						V4L2_CID_VBLANK,
@@ -1295,12 +1470,10 @@ static int imx335_probe(struct i2c_client *client)
 		return ret;
 	}
 
-	mutex_init(&imx335->mutex);
-
 	ret = imx335_power_on(imx335->dev);
 	if (ret) {
 		dev_err(imx335->dev, "failed to power-on the sensor\n");
-		goto error_mutex_destroy;
+		return ret;
 	}
 
 	/* Check module identity */
@@ -1311,7 +1484,7 @@ static int imx335_probe(struct i2c_client *client)
 	}
 
 	/* Set default mode to max resolution */
-	imx335->cur_mode = &supported_mode;
+	imx335->cur_mode = &supported_modes[0];
 	imx335->cur_mbus_code = imx335_mbus_codes[0];
 	imx335->vblank = imx335->cur_mode->vblank;
 
@@ -1333,11 +1506,18 @@ static int imx335_probe(struct i2c_client *client)
 		goto error_handler_free;
 	}
 
+	imx335->sd.state_lock = imx335->ctrl_handler.lock;
+	ret = v4l2_subdev_init_finalize(&imx335->sd);
+	if (ret < 0) {
+		dev_err(imx335->dev, "subdev init error\n");
+		goto error_media_entity;
+	}
+
 	ret = v4l2_async_register_subdev_sensor(&imx335->sd);
 	if (ret < 0) {
 		dev_err(imx335->dev,
 			"failed to register async subdev: %d\n", ret);
-		goto error_media_entity;
+		goto error_subdev_cleanup;
 	}
 
 	pm_runtime_set_active(imx335->dev);
@@ -1346,14 +1526,14 @@ static int imx335_probe(struct i2c_client *client)
 
 	return 0;
 
+error_subdev_cleanup:
+	v4l2_subdev_cleanup(&imx335->sd);
 error_media_entity:
 	media_entity_cleanup(&imx335->sd.entity);
 error_handler_free:
 	v4l2_ctrl_handler_free(imx335->sd.ctrl_handler);
 error_power_off:
 	imx335_power_off(imx335->dev);
-error_mutex_destroy:
-	mutex_destroy(&imx335->mutex);
 
 	return ret;
 }
@@ -1367,9 +1547,9 @@ static int imx335_probe(struct i2c_client *client)
 static void imx335_remove(struct i2c_client *client)
 {
 	struct v4l2_subdev *sd = i2c_get_clientdata(client);
-	struct imx335 *imx335 = to_imx335(sd);
 
 	v4l2_async_unregister_subdev(sd);
+	v4l2_subdev_cleanup(sd);
 	media_entity_cleanup(&sd->entity);
 	v4l2_ctrl_handler_free(sd->ctrl_handler);
 
@@ -1377,8 +1557,6 @@ static void imx335_remove(struct i2c_client *client)
 	if (!pm_runtime_status_suspended(&client->dev))
 		imx335_power_off(&client->dev);
 	pm_runtime_set_suspended(&client->dev);
-
-	mutex_destroy(&imx335->mutex);
 }
 
 static const struct dev_pm_ops imx335_pm_ops = {
diff --git a/drivers/media/i2c/imx415.c b/drivers/media/i2c/imx415.c
index a20b0db330d3..806daac84476 100644
--- a/drivers/media/i2c/imx415.c
+++ b/drivers/media/i2c/imx415.c
@@ -26,6 +26,10 @@
 #define IMX415_PIXEL_ARRAY_WIDTH  3864
 #define IMX415_PIXEL_ARRAY_HEIGHT 2192
 #define IMX415_PIXEL_ARRAY_VBLANK 58
+#define IMX415_EXPOSURE_OFFSET	  8
+
+#define IMX415_PIXEL_RATE_74_25MHZ	891000000
+#define IMX415_PIXEL_RATE_72MHZ		864000000
 
 #define IMX415_NUM_CLK_PARAM_REGS 11
 
@@ -51,7 +55,10 @@
 #define IMX415_OUTSEL		  CCI_REG8(0x30c0)
 #define IMX415_DRV		  CCI_REG8(0x30c1)
 #define IMX415_VMAX		  CCI_REG24_LE(0x3024)
+#define IMX415_VMAX_MAX		  0xfffff
 #define IMX415_HMAX		  CCI_REG16_LE(0x3028)
+#define IMX415_HMAX_MAX		  0xffff
+#define IMX415_HMAX_MULTIPLIER	  12
 #define IMX415_SHR0		  CCI_REG24_LE(0x3050)
 #define IMX415_GAIN_PCG_0	  CCI_REG16_LE(0x3090)
 #define IMX415_AGAIN_MIN	  0
@@ -445,11 +452,8 @@ static const struct imx415_clk_params imx415_clk_params[] = {
 	},
 };
 
-/* all-pixel 2-lane 720 Mbps 15.74 Hz mode */
-static const struct cci_reg_sequence imx415_mode_2_720[] = {
-	{ IMX415_VMAX, 0x08CA },
-	{ IMX415_HMAX, 0x07F0 },
-	{ IMX415_LANEMODE, IMX415_LANEMODE_2 },
+/* 720 Mbps CSI configuration */
+static const struct cci_reg_sequence imx415_linkrate_720mbps[] = {
 	{ IMX415_TCLKPOST, 0x006F },
 	{ IMX415_TCLKPREPARE, 0x002F },
 	{ IMX415_TCLKTRAIL, 0x002F },
@@ -461,11 +465,8 @@ static const struct cci_reg_sequence imx415_mode_2_720[] = {
 	{ IMX415_TLPX, 0x0027 },
 };
 
-/* all-pixel 2-lane 1440 Mbps 30.01 Hz mode */
-static const struct cci_reg_sequence imx415_mode_2_1440[] = {
-	{ IMX415_VMAX, 0x08CA },
-	{ IMX415_HMAX, 0x042A },
-	{ IMX415_LANEMODE, IMX415_LANEMODE_2 },
+/* 1440 Mbps CSI configuration */
+static const struct cci_reg_sequence imx415_linkrate_1440mbps[] = {
 	{ IMX415_TCLKPOST, 0x009F },
 	{ IMX415_TCLKPREPARE, 0x0057 },
 	{ IMX415_TCLKTRAIL, 0x0057 },
@@ -477,11 +478,8 @@ static const struct cci_reg_sequence imx415_mode_2_1440[] = {
 	{ IMX415_TLPX, 0x004F },
 };
 
-/* all-pixel 4-lane 891 Mbps 30 Hz mode */
-static const struct cci_reg_sequence imx415_mode_4_891[] = {
-	{ IMX415_VMAX, 0x08CA },
-	{ IMX415_HMAX, 0x044C },
-	{ IMX415_LANEMODE, IMX415_LANEMODE_4 },
+/* 891 Mbps CSI configuration */
+static const struct cci_reg_sequence imx415_linkrate_891mbps[] = {
 	{ IMX415_TCLKPOST, 0x007F },
 	{ IMX415_TCLKPREPARE, 0x0037 },
 	{ IMX415_TCLKTRAIL, 0x0037 },
@@ -498,39 +496,9 @@ struct imx415_mode_reg_list {
 	const struct cci_reg_sequence *regs;
 };
 
-/*
- * Mode : number of lanes, lane rate and frame rate dependent settings
- *
- * pixel_rate and hmax_pix are needed to calculate hblank for the v4l2 ctrl
- * interface. These values can not be found in the data sheet and should be
- * treated as virtual values. Use following table when adding new modes.
- *
- * lane_rate  lanes    fps     hmax_pix   pixel_rate
- *
- *     594      2     10.000     4400       99000000
- *     891      2     15.000     4400      148500000
- *     720      2     15.748     4064      144000000
- *    1782      2     30.000     4400      297000000
- *    2079      2     30.000     4400      297000000
- *    1440      2     30.019     4510      304615385
- *
- *     594      4     20.000     5500      247500000
- *     594      4     25.000     4400      247500000
- *     720      4     25.000     4400      247500000
- *     720      4     30.019     4510      304615385
- *     891      4     30.000     4400      297000000
- *    1440      4     30.019     4510      304615385
- *    1440      4     60.038     4510      609230769
- *    1485      4     60.000     4400      594000000
- *    1782      4     60.000     4400      594000000
- *    2079      4     60.000     4400      594000000
- *    2376      4     90.164     4392      891000000
- */
 struct imx415_mode {
 	u64 lane_rate;
-	u32 lanes;
-	u32 hmax_pix;
-	u64 pixel_rate;
+	u32 hmax_min[2];
 	struct imx415_mode_reg_list reg_list;
 };
 
@@ -538,32 +506,26 @@ struct imx415_mode {
 static const struct imx415_mode supported_modes[] = {
 	{
 		.lane_rate = 720000000,
-		.lanes = 2,
-		.hmax_pix = 4064,
-		.pixel_rate = 144000000,
+		.hmax_min = { 2032, 1066 },
 		.reg_list = {
-			.num_of_regs = ARRAY_SIZE(imx415_mode_2_720),
-			.regs = imx415_mode_2_720,
+			.num_of_regs = ARRAY_SIZE(imx415_linkrate_720mbps),
+			.regs = imx415_linkrate_720mbps,
 		},
 	},
 	{
 		.lane_rate = 1440000000,
-		.lanes = 2,
-		.hmax_pix = 4510,
-		.pixel_rate = 304615385,
+		.hmax_min = { 1066, 533 },
 		.reg_list = {
-			.num_of_regs = ARRAY_SIZE(imx415_mode_2_1440),
-			.regs = imx415_mode_2_1440,
+			.num_of_regs = ARRAY_SIZE(imx415_linkrate_1440mbps),
+			.regs = imx415_linkrate_1440mbps,
 		},
 	},
 	{
 		.lane_rate = 891000000,
-		.lanes = 4,
-		.hmax_pix = 4400,
-		.pixel_rate = 297000000,
+		.hmax_min = { 2200, 1100 },
 		.reg_list = {
-			.num_of_regs = ARRAY_SIZE(imx415_mode_4_891),
-			.regs = imx415_mode_4_891,
+			.num_of_regs = ARRAY_SIZE(imx415_linkrate_891mbps),
+			.regs = imx415_linkrate_891mbps,
 		},
 	},
 };
@@ -587,6 +549,7 @@ static const char *const imx415_test_pattern_menu[] = {
 struct imx415 {
 	struct device *dev;
 	struct clk *clk;
+	unsigned long pixel_rate;
 	struct regulator_bulk_data supplies[ARRAY_SIZE(imx415_supply_names)];
 	struct gpio_desc *reset;
 	struct regmap *regmap;
@@ -598,8 +561,10 @@ struct imx415 {
 
 	struct v4l2_ctrl_handler ctrls;
 	struct v4l2_ctrl *vblank;
+	struct v4l2_ctrl *hblank;
 	struct v4l2_ctrl *hflip;
 	struct v4l2_ctrl *vflip;
+	struct v4l2_ctrl *exposure;
 
 	unsigned int cur_mode;
 	unsigned int num_data_lanes;
@@ -730,17 +695,38 @@ static int imx415_s_ctrl(struct v4l2_ctrl *ctrl)
 					     ctrls);
 	const struct v4l2_mbus_framefmt *format;
 	struct v4l2_subdev_state *state;
+	u32 exposure_max;
 	unsigned int vmax;
 	unsigned int flip;
 	int ret;
 
-	if (!pm_runtime_get_if_in_use(sensor->dev))
-		return 0;
-
 	state = v4l2_subdev_get_locked_active_state(&sensor->subdev);
 	format = v4l2_subdev_state_get_format(state, 0);
 
+	if (ctrl->id == V4L2_CID_VBLANK) {
+		exposure_max = format->height + ctrl->val -
+			       IMX415_EXPOSURE_OFFSET;
+		__v4l2_ctrl_modify_range(sensor->exposure,
+					 sensor->exposure->minimum,
+					 exposure_max, sensor->exposure->step,
+					 sensor->exposure->default_value);
+	}
+
+	if (!pm_runtime_get_if_in_use(sensor->dev))
+		return 0;
+
 	switch (ctrl->id) {
+	case V4L2_CID_VBLANK:
+		ret = cci_write(sensor->regmap, IMX415_VMAX,
+				format->height + ctrl->val, NULL);
+		if (ret)
+			return ret;
+		/*
+		 * Deliberately fall through as exposure is set based on VMAX
+		 * which has just changed.
+		 */
+		ctrl = sensor->exposure;
+		fallthrough;
 	case V4L2_CID_EXPOSURE:
 		/* clamp the exposure value to VMAX. */
 		vmax = format->height + sensor->vblank->cur.val;
@@ -766,6 +752,13 @@ static int imx415_s_ctrl(struct v4l2_ctrl *ctrl)
 		ret = imx415_set_testpattern(sensor, ctrl->val);
 		break;
 
+	case V4L2_CID_HBLANK:
+		ret = cci_write(sensor->regmap, IMX415_HMAX,
+				(format->width + ctrl->val) /
+						IMX415_HMAX_MULTIPLIER,
+				 NULL);
+		break;
+
 	default:
 		ret = -EINVAL;
 		break;
@@ -784,11 +777,11 @@ static int imx415_ctrls_init(struct imx415 *sensor)
 {
 	struct v4l2_fwnode_device_properties props;
 	struct v4l2_ctrl *ctrl;
-	u64 pixel_rate = supported_modes[sensor->cur_mode].pixel_rate;
 	u64 lane_rate = supported_modes[sensor->cur_mode].lane_rate;
 	u32 exposure_max = IMX415_PIXEL_ARRAY_HEIGHT +
-			   IMX415_PIXEL_ARRAY_VBLANK - 8;
-	u32 hblank;
+			   IMX415_PIXEL_ARRAY_VBLANK -
+			   IMX415_EXPOSURE_OFFSET;
+	u32 hblank_min, hblank_max;
 	unsigned int i;
 	int ret;
 
@@ -816,36 +809,33 @@ static int imx415_ctrls_init(struct imx415 *sensor)
 	if (ctrl)
 		ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
 
-	v4l2_ctrl_new_std(&sensor->ctrls, &imx415_ctrl_ops, V4L2_CID_EXPOSURE,
-			  4, exposure_max, 1, exposure_max);
+	sensor->exposure = v4l2_ctrl_new_std(&sensor->ctrls, &imx415_ctrl_ops,
+					     V4L2_CID_EXPOSURE, 4,
+					     exposure_max, 1, exposure_max);
 
 	v4l2_ctrl_new_std(&sensor->ctrls, &imx415_ctrl_ops,
 			  V4L2_CID_ANALOGUE_GAIN, IMX415_AGAIN_MIN,
 			  IMX415_AGAIN_MAX, IMX415_AGAIN_STEP,
 			  IMX415_AGAIN_MIN);
 
-	hblank = supported_modes[sensor->cur_mode].hmax_pix -
-		 IMX415_PIXEL_ARRAY_WIDTH;
+	hblank_min = (supported_modes[sensor->cur_mode].hmax_min[sensor->num_data_lanes == 2 ? 0 : 1] *
+		      IMX415_HMAX_MULTIPLIER) - IMX415_PIXEL_ARRAY_WIDTH;
+	hblank_max = (IMX415_HMAX_MAX * IMX415_HMAX_MULTIPLIER) -
+		     IMX415_PIXEL_ARRAY_WIDTH;
 	ctrl = v4l2_ctrl_new_std(&sensor->ctrls, &imx415_ctrl_ops,
-				 V4L2_CID_HBLANK, hblank, hblank, 1, hblank);
-	if (ctrl)
-		ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+				 V4L2_CID_HBLANK, hblank_min,
+				 hblank_max, IMX415_HMAX_MULTIPLIER,
+				 hblank_min);
 
 	sensor->vblank = v4l2_ctrl_new_std(&sensor->ctrls, &imx415_ctrl_ops,
 					   V4L2_CID_VBLANK,
 					   IMX415_PIXEL_ARRAY_VBLANK,
-					   IMX415_PIXEL_ARRAY_VBLANK, 1,
-					   IMX415_PIXEL_ARRAY_VBLANK);
-	if (sensor->vblank)
-		sensor->vblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+					   IMX415_VMAX_MAX - IMX415_PIXEL_ARRAY_HEIGHT,
+					   1, IMX415_PIXEL_ARRAY_VBLANK);
 
-	/*
-	 * The pixel rate used here is a virtual value and can be used for
-	 * calculating the frame rate together with hblank. It may not
-	 * necessarily be the physically correct pixel clock.
-	 */
-	v4l2_ctrl_new_std(&sensor->ctrls, NULL, V4L2_CID_PIXEL_RATE, pixel_rate,
-			  pixel_rate, 1, pixel_rate);
+	v4l2_ctrl_new_std(&sensor->ctrls, NULL, V4L2_CID_PIXEL_RATE,
+			  sensor->pixel_rate, sensor->pixel_rate, 1,
+			  sensor->pixel_rate);
 
 	sensor->hflip = v4l2_ctrl_new_std(&sensor->ctrls, &imx415_ctrl_ops,
 					  V4L2_CID_HFLIP, 0, 1, 1, 0);
@@ -890,7 +880,12 @@ static int imx415_set_mode(struct imx415 *sensor, int mode)
 			    IMX415_NUM_CLK_PARAM_REGS,
 			    &ret);
 
-	return 0;
+	ret = cci_write(sensor->regmap, IMX415_LANEMODE,
+			sensor->num_data_lanes == 2 ? IMX415_LANEMODE_2 :
+						      IMX415_LANEMODE_4,
+			NULL);
+
+	return ret;
 }
 
 static int imx415_setup(struct imx415 *sensor, struct v4l2_subdev_state *state)
@@ -1302,8 +1297,6 @@ static int imx415_parse_hw_config(struct imx415 *sensor)
 		}
 
 		for (j = 0; j < ARRAY_SIZE(supported_modes); ++j) {
-			if (sensor->num_data_lanes != supported_modes[j].lanes)
-				continue;
 			if (bus_cfg.link_frequencies[i] * 2 !=
 			    supported_modes[j].lane_rate)
 				continue;
@@ -1318,6 +1311,17 @@ static int imx415_parse_hw_config(struct imx415 *sensor)
 				    "no valid sensor mode defined\n");
 		goto done_endpoint_free;
 	}
+	switch (inck) {
+	case 27000000:
+	case 37125000:
+	case 74250000:
+		sensor->pixel_rate = IMX415_PIXEL_RATE_74_25MHZ;
+		break;
+	case 24000000:
+	case 72000000:
+		sensor->pixel_rate = IMX415_PIXEL_RATE_72MHZ;
+		break;
+	}
 
 	lane_rate = supported_modes[sensor->cur_mode].lane_rate;
 	for (i = 0; i < ARRAY_SIZE(imx415_clk_params); ++i) {
diff --git a/drivers/media/i2c/imx477.c b/drivers/media/i2c/imx477.c
new file mode 100644
index 000000000000..3737c98dc3e6
--- /dev/null
+++ b/drivers/media/i2c/imx477.c
@@ -0,0 +1,2279 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A V4L2 driver for Sony IMX477 cameras.
+ * Copyright (C) 2020, Raspberry Pi (Trading) Ltd
+ *
+ * Based on Sony imx219 camera driver
+ * Copyright (C) 2019-2020 Raspberry Pi (Trading) Ltd
+ */
+#include <linux/unaligned.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <media/v4l2-cci.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mediabus.h>
+
+static int dpc_enable = 1;
+module_param(dpc_enable, int, 0644);
+MODULE_PARM_DESC(dpc_enable, "Enable on-sensor DPC");
+
+static int trigger_mode;
+module_param(trigger_mode, int, 0644);
+MODULE_PARM_DESC(trigger_mode, "Set vsync trigger mode: 1=source, 2=sink");
+
+static int fstrobe_enable;
+module_param(fstrobe_enable, int, 0644);
+MODULE_PARM_DESC(fstrobe_enable, "Enable fstrobe signal");
+
+static int fstrobe_cont_trig;
+module_param(fstrobe_cont_trig, int, 0644);
+MODULE_PARM_DESC(fstrobe_cont_trig, "Configure fstrobe to be one-shot (0) or continuous (1)");
+
+static int fstrobe_width = 1;
+module_param(fstrobe_width, int, 0644);
+MODULE_PARM_DESC(fstrobe_width, "Set fstrobe pulse width in units of INCK");
+
+static int fstrobe_delay;
+module_param(fstrobe_delay, int, 0644);
+MODULE_PARM_DESC(fstrobe_delay, "Set fstrobe delay from end all lines starting to expose and the start of the strobe pulse");
+
+#define IMX477_REG_VALUE_08BIT		1
+#define IMX477_REG_VALUE_16BIT		2
+
+/* Chip ID */
+#define IMX477_REG_CHIP_ID		CCI_REG16(0x0016)
+#define IMX477_CHIP_ID			0x0477
+#define IMX378_CHIP_ID			0x0378
+
+#define IMX477_REG_MODE_SELECT		CCI_REG8(0x0100)
+#define IMX477_MODE_STANDBY		0x00
+#define IMX477_MODE_STREAMING		0x01
+
+#define IMX477_REG_ORIENTATION		CCI_REG8(0x101)
+
+#define IMX477_REG_CSI_DT_FMT_H		CCI_REG8(0x0112)
+#define IMX477_REG_CSI_DT_FMT_L		CCI_REG8(0x0113)
+
+#define IMX477_XCLK_FREQ		24000000
+
+#define IMX477_DEFAULT_LINK_FREQ	450000000
+
+/* Pixel rate is fixed at 840MHz for all the modes */
+#define IMX477_PIXEL_RATE		840000000
+
+/* V_TIMING internal */
+#define IMX477_REG_FRAME_LENGTH		CCI_REG16(0x0340)
+#define IMX477_VBLANK_MIN		48
+#define IMX477_FRAME_LENGTH_MAX		0xffdc
+
+/* H_TIMING internal */
+#define IMX477_REG_LINE_LENGTH		CCI_REG16(0x0342)
+#define IMX477_LINE_LENGTH_MAX		0xfff0
+
+/* Long exposure multiplier */
+#define IMX477_LONG_EXP_SHIFT_MAX	7
+#define IMX477_LONG_EXP_SHIFT_REG	CCI_REG8(0x3100)
+
+/* Exposure control */
+#define IMX477_REG_EXPOSURE		CCI_REG16(0x0202)
+#define IMX477_EXPOSURE_OFFSET		22
+#define IMX477_EXPOSURE_MIN		4
+#define IMX477_EXPOSURE_STEP		1
+#define IMX477_EXPOSURE_DEFAULT		0x640
+#define IMX477_EXPOSURE_MAX		(IMX477_FRAME_LENGTH_MAX - \
+					 IMX477_EXPOSURE_OFFSET)
+
+/* Analog gain control */
+#define IMX477_REG_ANALOG_GAIN		CCI_REG16(0x0204)
+#define IMX477_ANA_GAIN_MIN		0
+#define IMX477_ANA_GAIN_MAX		978
+#define IMX477_ANA_GAIN_STEP		1
+#define IMX477_ANA_GAIN_DEFAULT		0x0
+
+/* Digital gain control */
+#define IMX477_REG_DIGITAL_GAIN		CCI_REG16(0x020e)
+#define IMX477_DGTL_GAIN_MIN		0x0100
+#define IMX477_DGTL_GAIN_MAX		0xffff
+#define IMX477_DGTL_GAIN_DEFAULT	0x0100
+#define IMX477_DGTL_GAIN_STEP		1
+
+#define IMX477_REG_IOP_PXCK_DIV		CCI_REG8(0x0309)
+#define IMX477_REG_IOP_SYSCK_DIV	CCI_REG8(0x030b)
+  #define IMX477_IOP_SYSCK_DIV		0x02
+#define IMX477_REG_IOP_PREDIV		CCI_REG8(0x030d)
+  #define IMX477_IOP_PREDIV		0x02
+#define IMX477_REG_IOP_MPY		CCI_REG16(0x030e)
+
+/* Test Pattern Control */
+#define IMX477_REG_TEST_PATTERN		CCI_REG16(0x0600)
+#define IMX477_TEST_PATTERN_DISABLE	0
+#define IMX477_TEST_PATTERN_SOLID_COLOR	1
+#define IMX477_TEST_PATTERN_COLOR_BARS	2
+#define IMX477_TEST_PATTERN_GREY_COLOR	3
+#define IMX477_TEST_PATTERN_PN9		4
+
+/* Test pattern colour components */
+#define IMX477_REG_TEST_PATTERN_R	CCI_REG16(0x0602)
+#define IMX477_REG_TEST_PATTERN_GR	CCI_REG16(0x0604)
+#define IMX477_REG_TEST_PATTERN_B	CCI_REG16(0x0606)
+#define IMX477_REG_TEST_PATTERN_GB	CCI_REG16(0x0608)
+#define IMX477_TEST_PATTERN_COLOUR_MIN	0
+#define IMX477_TEST_PATTERN_COLOUR_MAX	0x0fff
+#define IMX477_TEST_PATTERN_COLOUR_STEP	1
+#define IMX477_TEST_PATTERN_R_DEFAULT	IMX477_TEST_PATTERN_COLOUR_MAX
+#define IMX477_TEST_PATTERN_GR_DEFAULT	0
+#define IMX477_TEST_PATTERN_B_DEFAULT	0
+#define IMX477_TEST_PATTERN_GB_DEFAULT	0
+
+#define IMX477_REG_DPHY_CTRL		CCI_REG8(0x0808)
+  #define IMX477_DPHY_CTRL_AUTO		0
+  #define IMX477_DPHY_CTRL_UI		1
+  #define IMX477_DPHY_CTRL_REGISTER	2
+#define IMX477_REG_REQ_LINK_BIT_RATE	CCI_REG32(0x0820)
+
+/* Trigger mode */
+#define IMX477_REG_MC_MODE		CCI_REG8(0x3f0b)
+#define IMX477_REG_MS_SEL		CCI_REG8(0x3041)
+#define IMX477_REG_XVS_IO_CTRL		CCI_REG8(0x3040)
+#define IMX477_REG_EXTOUT_EN		CCI_REG8(0x4b81)
+
+#define IMX477_REG_ADBIT_MODE		CCI_REG8(0x3f0d)
+/* Temperature sensor */
+#define IMX477_REG_TEMP_SEN_CTL		CCI_REG8(0x0138)
+
+/* Embedded metadata stream structure */
+#define IMX477_EMBEDDED_LINE_WIDTH 16384
+#define IMX477_NUM_EMBEDDED_LINES 1
+
+enum pad_types {
+	IMAGE_PAD,
+	METADATA_PAD,
+	NUM_PADS
+};
+
+/* IMX477 native and active pixel array size. */
+#define IMX477_NATIVE_WIDTH		4072U
+#define IMX477_NATIVE_HEIGHT		3176U
+#define IMX477_PIXEL_ARRAY_LEFT		8U
+#define IMX477_PIXEL_ARRAY_TOP		16U
+#define IMX477_PIXEL_ARRAY_WIDTH	4056U
+#define IMX477_PIXEL_ARRAY_HEIGHT	3040U
+
+struct imx477_reg_list {
+	unsigned int num_of_regs;
+	const struct cci_reg_sequence *regs;
+};
+
+/* Mode : resolution and related config&values */
+struct imx477_mode {
+	/* Frame width */
+	unsigned int width;
+
+	/* Frame height */
+	unsigned int height;
+
+	/* Analog crop rectangle. */
+	struct v4l2_rect crop;
+
+	/* Default frame_length value to use in this mode */
+	unsigned int frm_length_default;
+
+	/* Default register values */
+	struct imx477_reg_list reg_list;
+};
+
+static const struct cci_reg_sequence mode_common_regs[] = {
+	{CCI_REG8(0x0136), 0x18},
+	{CCI_REG8(0x0137), 0x00},
+	{CCI_REG8(0x0138), 0x01},
+	{CCI_REG8(0xe000), 0x00},
+	{CCI_REG8(0xe07a), 0x01},
+	{IMX477_REG_DPHY_CTRL, IMX477_DPHY_CTRL_REGISTER},
+	{CCI_REG8(0x4ae9), 0x18},
+	{CCI_REG8(0x4aea), 0x08},
+	{CCI_REG8(0xf61c), 0x04},
+	{CCI_REG8(0xf61e), 0x04},
+	{CCI_REG8(0x4ae9), 0x21},
+	{CCI_REG8(0x4aea), 0x80},
+	{CCI_REG8(0x38a8), 0x1f},
+	{CCI_REG8(0x38a9), 0xff},
+	{CCI_REG8(0x38aa), 0x1f},
+	{CCI_REG8(0x38ab), 0xff},
+	{CCI_REG8(0x55d4), 0x00},
+	{CCI_REG8(0x55d5), 0x00},
+	{CCI_REG8(0x55d6), 0x07},
+	{CCI_REG8(0x55d7), 0xff},
+	{CCI_REG8(0x55e8), 0x07},
+	{CCI_REG8(0x55e9), 0xff},
+	{CCI_REG8(0x55ea), 0x00},
+	{CCI_REG8(0x55eb), 0x00},
+	{CCI_REG8(0x574c), 0x07},
+	{CCI_REG8(0x574d), 0xff},
+	{CCI_REG8(0x574e), 0x00},
+	{CCI_REG8(0x574f), 0x00},
+	{CCI_REG8(0x5754), 0x00},
+	{CCI_REG8(0x5755), 0x00},
+	{CCI_REG8(0x5756), 0x07},
+	{CCI_REG8(0x5757), 0xff},
+	{CCI_REG8(0x5973), 0x04},
+	{CCI_REG8(0x5974), 0x01},
+	{CCI_REG8(0x5d13), 0xc3},
+	{CCI_REG8(0x5d14), 0x58},
+	{CCI_REG8(0x5d15), 0xa3},
+	{CCI_REG8(0x5d16), 0x1d},
+	{CCI_REG8(0x5d17), 0x65},
+	{CCI_REG8(0x5d18), 0x8c},
+	{CCI_REG8(0x5d1a), 0x06},
+	{CCI_REG8(0x5d1b), 0xa9},
+	{CCI_REG8(0x5d1c), 0x45},
+	{CCI_REG8(0x5d1d), 0x3a},
+	{CCI_REG8(0x5d1e), 0xab},
+	{CCI_REG8(0x5d1f), 0x15},
+	{CCI_REG8(0x5d21), 0x0e},
+	{CCI_REG8(0x5d22), 0x52},
+	{CCI_REG8(0x5d23), 0xaa},
+	{CCI_REG8(0x5d24), 0x7d},
+	{CCI_REG8(0x5d25), 0x57},
+	{CCI_REG8(0x5d26), 0xa8},
+	{CCI_REG8(0x5d37), 0x5a},
+	{CCI_REG8(0x5d38), 0x5a},
+	{CCI_REG8(0x5d77), 0x7f},
+	{CCI_REG8(0x7b75), 0x0e},
+	{CCI_REG8(0x7b76), 0x0b},
+	{CCI_REG8(0x7b77), 0x08},
+	{CCI_REG8(0x7b78), 0x0a},
+	{CCI_REG8(0x7b79), 0x47},
+	{CCI_REG8(0x7b7c), 0x00},
+	{CCI_REG8(0x7b7d), 0x00},
+	{CCI_REG8(0x8d1f), 0x00},
+	{CCI_REG8(0x8d27), 0x00},
+	{CCI_REG8(0x9004), 0x03},
+	{CCI_REG8(0x9200), 0x50},
+	{CCI_REG8(0x9201), 0x6c},
+	{CCI_REG8(0x9202), 0x71},
+	{CCI_REG8(0x9203), 0x00},
+	{CCI_REG8(0x9204), 0x71},
+	{CCI_REG8(0x9205), 0x01},
+	{CCI_REG8(0x9371), 0x6a},
+	{CCI_REG8(0x9373), 0x6a},
+	{CCI_REG8(0x9375), 0x64},
+	{CCI_REG8(0x991a), 0x00},
+	{CCI_REG8(0x996b), 0x8c},
+	{CCI_REG8(0x996c), 0x64},
+	{CCI_REG8(0x996d), 0x50},
+	{CCI_REG8(0x9a4c), 0x0d},
+	{CCI_REG8(0x9a4d), 0x0d},
+	{CCI_REG8(0xa001), 0x0a},
+	{CCI_REG8(0xa003), 0x0a},
+	{CCI_REG8(0xa005), 0x0a},
+	{CCI_REG8(0xa006), 0x01},
+	{CCI_REG8(0xa007), 0xc0},
+	{CCI_REG8(0xa009), 0xc0},
+	{CCI_REG8(0x3d8a), 0x01},
+	{CCI_REG8(0x4421), 0x04},
+	{CCI_REG8(0x7b3b), 0x01},
+	{CCI_REG8(0x7b4c), 0x00},
+	{CCI_REG8(0x9905), 0x00},
+	{CCI_REG8(0x9907), 0x00},
+	{CCI_REG8(0x9909), 0x00},
+	{CCI_REG8(0x990b), 0x00},
+	{CCI_REG8(0x9944), 0x3c},
+	{CCI_REG8(0x9947), 0x3c},
+	{CCI_REG8(0x994a), 0x8c},
+	{CCI_REG8(0x994b), 0x50},
+	{CCI_REG8(0x994c), 0x1b},
+	{CCI_REG8(0x994d), 0x8c},
+	{CCI_REG8(0x994e), 0x50},
+	{CCI_REG8(0x994f), 0x1b},
+	{CCI_REG8(0x9950), 0x8c},
+	{CCI_REG8(0x9951), 0x1b},
+	{CCI_REG8(0x9952), 0x0a},
+	{CCI_REG8(0x9953), 0x8c},
+	{CCI_REG8(0x9954), 0x1b},
+	{CCI_REG8(0x9955), 0x0a},
+	{CCI_REG8(0x9a13), 0x04},
+	{CCI_REG8(0x9a14), 0x04},
+	{CCI_REG8(0x9a19), 0x00},
+	{CCI_REG8(0x9a1c), 0x04},
+	{CCI_REG8(0x9a1d), 0x04},
+	{CCI_REG8(0x9a26), 0x05},
+	{CCI_REG8(0x9a27), 0x05},
+	{CCI_REG8(0x9a2c), 0x01},
+	{CCI_REG8(0x9a2d), 0x03},
+	{CCI_REG8(0x9a2f), 0x05},
+	{CCI_REG8(0x9a30), 0x05},
+	{CCI_REG8(0x9a41), 0x00},
+	{CCI_REG8(0x9a46), 0x00},
+	{CCI_REG8(0x9a47), 0x00},
+	{CCI_REG8(0x9c17), 0x35},
+	{CCI_REG8(0x9c1d), 0x31},
+	{CCI_REG8(0x9c29), 0x50},
+	{CCI_REG8(0x9c3b), 0x2f},
+	{CCI_REG8(0x9c41), 0x6b},
+	{CCI_REG8(0x9c47), 0x2d},
+	{CCI_REG8(0x9c4d), 0x40},
+	{CCI_REG8(0x9c6b), 0x00},
+	{CCI_REG8(0x9c71), 0xc8},
+	{CCI_REG8(0x9c73), 0x32},
+	{CCI_REG8(0x9c75), 0x04},
+	{CCI_REG8(0x9c7d), 0x2d},
+	{CCI_REG8(0x9c83), 0x40},
+	{CCI_REG8(0x9c94), 0x3f},
+	{CCI_REG8(0x9c95), 0x3f},
+	{CCI_REG8(0x9c96), 0x3f},
+	{CCI_REG8(0x9c97), 0x00},
+	{CCI_REG8(0x9c98), 0x00},
+	{CCI_REG8(0x9c99), 0x00},
+	{CCI_REG8(0x9c9a), 0x3f},
+	{CCI_REG8(0x9c9b), 0x3f},
+	{CCI_REG8(0x9c9c), 0x3f},
+	{CCI_REG8(0x9ca0), 0x0f},
+	{CCI_REG8(0x9ca1), 0x0f},
+	{CCI_REG8(0x9ca2), 0x0f},
+	{CCI_REG8(0x9ca3), 0x00},
+	{CCI_REG8(0x9ca4), 0x00},
+	{CCI_REG8(0x9ca5), 0x00},
+	{CCI_REG8(0x9ca6), 0x1e},
+	{CCI_REG8(0x9ca7), 0x1e},
+	{CCI_REG8(0x9ca8), 0x1e},
+	{CCI_REG8(0x9ca9), 0x00},
+	{CCI_REG8(0x9caa), 0x00},
+	{CCI_REG8(0x9cab), 0x00},
+	{CCI_REG8(0x9cac), 0x09},
+	{CCI_REG8(0x9cad), 0x09},
+	{CCI_REG8(0x9cae), 0x09},
+	{CCI_REG8(0x9cbd), 0x50},
+	{CCI_REG8(0x9cbf), 0x50},
+	{CCI_REG8(0x9cc1), 0x50},
+	{CCI_REG8(0x9cc3), 0x40},
+	{CCI_REG8(0x9cc5), 0x40},
+	{CCI_REG8(0x9cc7), 0x40},
+	{CCI_REG8(0x9cc9), 0x0a},
+	{CCI_REG8(0x9ccb), 0x0a},
+	{CCI_REG8(0x9ccd), 0x0a},
+	{CCI_REG8(0x9d17), 0x35},
+	{CCI_REG8(0x9d1d), 0x31},
+	{CCI_REG8(0x9d29), 0x50},
+	{CCI_REG8(0x9d3b), 0x2f},
+	{CCI_REG8(0x9d41), 0x6b},
+	{CCI_REG8(0x9d47), 0x42},
+	{CCI_REG8(0x9d4d), 0x5a},
+	{CCI_REG8(0x9d6b), 0x00},
+	{CCI_REG8(0x9d71), 0xc8},
+	{CCI_REG8(0x9d73), 0x32},
+	{CCI_REG8(0x9d75), 0x04},
+	{CCI_REG8(0x9d7d), 0x42},
+	{CCI_REG8(0x9d83), 0x5a},
+	{CCI_REG8(0x9d94), 0x3f},
+	{CCI_REG8(0x9d95), 0x3f},
+	{CCI_REG8(0x9d96), 0x3f},
+	{CCI_REG8(0x9d97), 0x00},
+	{CCI_REG8(0x9d98), 0x00},
+	{CCI_REG8(0x9d99), 0x00},
+	{CCI_REG8(0x9d9a), 0x3f},
+	{CCI_REG8(0x9d9b), 0x3f},
+	{CCI_REG8(0x9d9c), 0x3f},
+	{CCI_REG8(0x9d9d), 0x1f},
+	{CCI_REG8(0x9d9e), 0x1f},
+	{CCI_REG8(0x9d9f), 0x1f},
+	{CCI_REG8(0x9da0), 0x0f},
+	{CCI_REG8(0x9da1), 0x0f},
+	{CCI_REG8(0x9da2), 0x0f},
+	{CCI_REG8(0x9da3), 0x00},
+	{CCI_REG8(0x9da4), 0x00},
+	{CCI_REG8(0x9da5), 0x00},
+	{CCI_REG8(0x9da6), 0x1e},
+	{CCI_REG8(0x9da7), 0x1e},
+	{CCI_REG8(0x9da8), 0x1e},
+	{CCI_REG8(0x9da9), 0x00},
+	{CCI_REG8(0x9daa), 0x00},
+	{CCI_REG8(0x9dab), 0x00},
+	{CCI_REG8(0x9dac), 0x09},
+	{CCI_REG8(0x9dad), 0x09},
+	{CCI_REG8(0x9dae), 0x09},
+	{CCI_REG8(0x9dc9), 0x0a},
+	{CCI_REG8(0x9dcb), 0x0a},
+	{CCI_REG8(0x9dcd), 0x0a},
+	{CCI_REG8(0x9e17), 0x35},
+	{CCI_REG8(0x9e1d), 0x31},
+	{CCI_REG8(0x9e29), 0x50},
+	{CCI_REG8(0x9e3b), 0x2f},
+	{CCI_REG8(0x9e41), 0x6b},
+	{CCI_REG8(0x9e47), 0x2d},
+	{CCI_REG8(0x9e4d), 0x40},
+	{CCI_REG8(0x9e6b), 0x00},
+	{CCI_REG8(0x9e71), 0xc8},
+	{CCI_REG8(0x9e73), 0x32},
+	{CCI_REG8(0x9e75), 0x04},
+	{CCI_REG8(0x9e94), 0x0f},
+	{CCI_REG8(0x9e95), 0x0f},
+	{CCI_REG8(0x9e96), 0x0f},
+	{CCI_REG8(0x9e97), 0x00},
+	{CCI_REG8(0x9e98), 0x00},
+	{CCI_REG8(0x9e99), 0x00},
+	{CCI_REG8(0x9ea0), 0x0f},
+	{CCI_REG8(0x9ea1), 0x0f},
+	{CCI_REG8(0x9ea2), 0x0f},
+	{CCI_REG8(0x9ea3), 0x00},
+	{CCI_REG8(0x9ea4), 0x00},
+	{CCI_REG8(0x9ea5), 0x00},
+	{CCI_REG8(0x9ea6), 0x3f},
+	{CCI_REG8(0x9ea7), 0x3f},
+	{CCI_REG8(0x9ea8), 0x3f},
+	{CCI_REG8(0x9ea9), 0x00},
+	{CCI_REG8(0x9eaa), 0x00},
+	{CCI_REG8(0x9eab), 0x00},
+	{CCI_REG8(0x9eac), 0x09},
+	{CCI_REG8(0x9ead), 0x09},
+	{CCI_REG8(0x9eae), 0x09},
+	{CCI_REG8(0x9ec9), 0x0a},
+	{CCI_REG8(0x9ecb), 0x0a},
+	{CCI_REG8(0x9ecd), 0x0a},
+	{CCI_REG8(0x9f17), 0x35},
+	{CCI_REG8(0x9f1d), 0x31},
+	{CCI_REG8(0x9f29), 0x50},
+	{CCI_REG8(0x9f3b), 0x2f},
+	{CCI_REG8(0x9f41), 0x6b},
+	{CCI_REG8(0x9f47), 0x42},
+	{CCI_REG8(0x9f4d), 0x5a},
+	{CCI_REG8(0x9f6b), 0x00},
+	{CCI_REG8(0x9f71), 0xc8},
+	{CCI_REG8(0x9f73), 0x32},
+	{CCI_REG8(0x9f75), 0x04},
+	{CCI_REG8(0x9f94), 0x0f},
+	{CCI_REG8(0x9f95), 0x0f},
+	{CCI_REG8(0x9f96), 0x0f},
+	{CCI_REG8(0x9f97), 0x00},
+	{CCI_REG8(0x9f98), 0x00},
+	{CCI_REG8(0x9f99), 0x00},
+	{CCI_REG8(0x9f9a), 0x2f},
+	{CCI_REG8(0x9f9b), 0x2f},
+	{CCI_REG8(0x9f9c), 0x2f},
+	{CCI_REG8(0x9f9d), 0x00},
+	{CCI_REG8(0x9f9e), 0x00},
+	{CCI_REG8(0x9f9f), 0x00},
+	{CCI_REG8(0x9fa0), 0x0f},
+	{CCI_REG8(0x9fa1), 0x0f},
+	{CCI_REG8(0x9fa2), 0x0f},
+	{CCI_REG8(0x9fa3), 0x00},
+	{CCI_REG8(0x9fa4), 0x00},
+	{CCI_REG8(0x9fa5), 0x00},
+	{CCI_REG8(0x9fa6), 0x1e},
+	{CCI_REG8(0x9fa7), 0x1e},
+	{CCI_REG8(0x9fa8), 0x1e},
+	{CCI_REG8(0x9fa9), 0x00},
+	{CCI_REG8(0x9faa), 0x00},
+	{CCI_REG8(0x9fab), 0x00},
+	{CCI_REG8(0x9fac), 0x09},
+	{CCI_REG8(0x9fad), 0x09},
+	{CCI_REG8(0x9fae), 0x09},
+	{CCI_REG8(0x9fc9), 0x0a},
+	{CCI_REG8(0x9fcb), 0x0a},
+	{CCI_REG8(0x9fcd), 0x0a},
+	{CCI_REG8(0xa14b), 0xff},
+	{CCI_REG8(0xa151), 0x0c},
+	{CCI_REG8(0xa153), 0x50},
+	{CCI_REG8(0xa155), 0x02},
+	{CCI_REG8(0xa157), 0x00},
+	{CCI_REG8(0xa1ad), 0xff},
+	{CCI_REG8(0xa1b3), 0x0c},
+	{CCI_REG8(0xa1b5), 0x50},
+	{CCI_REG8(0xa1b9), 0x00},
+	{CCI_REG8(0xa24b), 0xff},
+	{CCI_REG8(0xa257), 0x00},
+	{CCI_REG8(0xa2ad), 0xff},
+	{CCI_REG8(0xa2b9), 0x00},
+	{CCI_REG8(0xb21f), 0x04},
+	{CCI_REG8(0xb35c), 0x00},
+	{CCI_REG8(0xb35e), 0x08},
+	{CCI_REG8(0x0114), 0x01},
+	{CCI_REG8(0x0350), 0x00},
+	{CCI_REG8(0xbcf1), 0x02},
+	{CCI_REG8(0x3ff9), 0x01},
+	{CCI_REG8(0x0220), 0x00},
+	{CCI_REG8(0x0221), 0x11},
+	{CCI_REG8(0x0381), 0x01},
+	{CCI_REG8(0x0383), 0x01},
+	{CCI_REG8(0x0385), 0x01},
+	{CCI_REG8(0x0387), 0x01},
+	{CCI_REG8(0x0902), 0x02},
+	{CCI_REG8(0x3140), 0x02},
+	{CCI_REG8(0x3c00), 0x00},
+	{CCI_REG8(0x9e9a), 0x2f},
+	{CCI_REG8(0x9e9b), 0x2f},
+	{CCI_REG8(0x9e9c), 0x2f},
+	{CCI_REG8(0x9e9d), 0x00},
+	{CCI_REG8(0x9e9e), 0x00},
+	{CCI_REG8(0x9e9f), 0x00},
+	{CCI_REG8(0x0301), 0x05},
+	{CCI_REG8(0x0303), 0x02},
+	{IMX477_REG_IOP_SYSCK_DIV, IMX477_IOP_SYSCK_DIV},
+	{IMX477_REG_IOP_PREDIV, IMX477_IOP_PREDIV},
+	{CCI_REG8(0x0310), 0x01},
+	{CCI_REG8(0x080a), 0x00},
+	{CCI_REG8(0x080b), 0x7f},
+	{CCI_REG8(0x080c), 0x00},
+	{CCI_REG8(0x080d), 0x4f},
+	{CCI_REG8(0x080e), 0x00},
+	{CCI_REG8(0x080f), 0x77},
+	{CCI_REG8(0x0810), 0x00},
+	{CCI_REG8(0x0811), 0x5f},
+	{CCI_REG8(0x0812), 0x00},
+	{CCI_REG8(0x0813), 0x57},
+	{CCI_REG8(0x0814), 0x00},
+	{CCI_REG8(0x0815), 0x4f},
+	{CCI_REG8(0x0816), 0x01},
+	{CCI_REG8(0x0817), 0x27},
+	{CCI_REG8(0x0818), 0x00},
+	{CCI_REG8(0x0819), 0x3f},
+	{CCI_REG8(0x3e20), 0x01},
+	{CCI_REG8(0x3e37), 0x00},
+	{CCI_REG8(0x3f50), 0x00},
+};
+
+/* 12 mpix 10fps */
+static const struct cci_reg_sequence mode_4056x3040_regs[] = {
+	{CCI_REG8(0x0344), 0x00},
+	{CCI_REG8(0x0345), 0x00},
+	{CCI_REG8(0x0346), 0x00},
+	{CCI_REG8(0x0347), 0x00},
+	{CCI_REG8(0x0348), 0x0f},
+	{CCI_REG8(0x0349), 0xd7},
+	{CCI_REG8(0x034a), 0x0b},
+	{CCI_REG8(0x034b), 0xdf},
+	{CCI_REG8(0x00e3), 0x00},
+	{CCI_REG8(0x00e4), 0x00},
+	{CCI_REG8(0x00fc), 0x0a},
+	{CCI_REG8(0x00fd), 0x0a},
+	{CCI_REG8(0x00fe), 0x0a},
+	{CCI_REG8(0x00ff), 0x0a},
+	{CCI_REG8(0x0900), 0x00},
+	{CCI_REG8(0x0901), 0x11},
+	{CCI_REG8(0x3c01), 0x03},
+	{CCI_REG8(0x3c02), 0xa2},
+	{CCI_REG8(0x5748), 0x07},
+	{CCI_REG8(0x5749), 0xff},
+	{CCI_REG8(0x574a), 0x00},
+	{CCI_REG8(0x574b), 0x00},
+	{CCI_REG8(0x7b75), 0x0a},
+	{CCI_REG8(0x7b76), 0x0c},
+	{CCI_REG8(0x7b77), 0x07},
+	{CCI_REG8(0x7b78), 0x06},
+	{CCI_REG8(0x7b79), 0x3c},
+	{CCI_REG8(0x7b53), 0x01},
+	{CCI_REG8(0x9369), 0x5a},
+	{CCI_REG8(0x936b), 0x55},
+	{CCI_REG8(0x936d), 0x28},
+	{CCI_REG8(0x9304), 0x00},
+	{CCI_REG8(0x9305), 0x00},
+	{CCI_REG8(0xa2a9), 0x60},
+	{CCI_REG8(0xa2b7), 0x00},
+	{CCI_REG8(0x0401), 0x00},
+	{CCI_REG8(0x0404), 0x00},
+	{CCI_REG8(0x0405), 0x10},
+	{CCI_REG8(0x0408), 0x00},
+	{CCI_REG8(0x0409), 0x00},
+	{CCI_REG8(0x040a), 0x00},
+	{CCI_REG8(0x040b), 0x00},
+	{CCI_REG8(0x040c), 0x0f},
+	{CCI_REG8(0x040d), 0xd8},
+	{CCI_REG8(0x040e), 0x0b},
+	{CCI_REG8(0x040f), 0xe0},
+	{CCI_REG8(0x034c), 0x0f},
+	{CCI_REG8(0x034d), 0xd8},
+	{CCI_REG8(0x034e), 0x0b},
+	{CCI_REG8(0x034f), 0xe0},
+	{CCI_REG8(0x0305), 0x04},
+	{CCI_REG8(0x0306), 0x01},
+	{CCI_REG8(0x0307), 0x5e},
+	{CCI_REG8(0xe04c), 0x00},
+	{CCI_REG8(0xe04d), 0x7f},
+	{CCI_REG8(0xe04e), 0x00},
+	{CCI_REG8(0xe04f), 0x1f},
+	{CCI_REG8(0x3f56), 0x02},
+	{CCI_REG8(0x3f57), 0xae},
+};
+
+/* 12 mpix cropped to 16:9 10fps */
+static const struct cci_reg_sequence mode_4056x2160_regs[] = {
+	{CCI_REG8(0x0344), 0x00},
+	{CCI_REG8(0x0345), 0x00},
+	{CCI_REG8(0x0346), 0x01},
+	{CCI_REG8(0x0347), 0xb8},
+	{CCI_REG8(0x0348), 0x0f},
+	{CCI_REG8(0x0349), 0xd7},
+	{CCI_REG8(0x034a), 0x0a},
+	{CCI_REG8(0x034b), 0x27},
+	{CCI_REG8(0x00e3), 0x00},
+	{CCI_REG8(0x00e4), 0x00},
+	{CCI_REG8(0x00fc), 0x0a},
+	{CCI_REG8(0x00fd), 0x0a},
+	{CCI_REG8(0x00fe), 0x0a},
+	{CCI_REG8(0x00ff), 0x0a},
+	{CCI_REG8(0x0900), 0x00},
+	{CCI_REG8(0x0901), 0x11},
+	{CCI_REG8(0x3c01), 0x03},
+	{CCI_REG8(0x3c02), 0xa2},
+	{CCI_REG8(0x5748), 0x07},
+	{CCI_REG8(0x5749), 0xff},
+	{CCI_REG8(0x574a), 0x00},
+	{CCI_REG8(0x574b), 0x00},
+	{CCI_REG8(0x7b75), 0x0a},
+	{CCI_REG8(0x7b76), 0x0c},
+	{CCI_REG8(0x7b77), 0x07},
+	{CCI_REG8(0x7b78), 0x06},
+	{CCI_REG8(0x7b79), 0x3c},
+	{CCI_REG8(0x7b53), 0x01},
+	{CCI_REG8(0x9369), 0x5a},
+	{CCI_REG8(0x936b), 0x55},
+	{CCI_REG8(0x936d), 0x28},
+	{CCI_REG8(0x9304), 0x00},
+	{CCI_REG8(0x9305), 0x00},
+	{CCI_REG8(0xa2a9), 0x60},
+	{CCI_REG8(0xa2b7), 0x00},
+	{CCI_REG8(0x0401), 0x00},
+	{CCI_REG8(0x0404), 0x00},
+	{CCI_REG8(0x0405), 0x10},
+	{CCI_REG8(0x0408), 0x00},
+	{CCI_REG8(0x0409), 0x00},
+	{CCI_REG8(0x040a), 0x00},
+	{CCI_REG8(0x040b), 0x00},
+	{CCI_REG8(0x040c), 0x0f},
+	{CCI_REG8(0x040d), 0xd8},
+	{CCI_REG8(0x040e), 0x08},
+	{CCI_REG8(0x040f), 0x70},
+	{CCI_REG8(0x034c), 0x0f},
+	{CCI_REG8(0x034d), 0xd8},
+	{CCI_REG8(0x034e), 0x08},
+	{CCI_REG8(0x034f), 0x70},
+	{CCI_REG8(0x0305), 0x04},
+	{CCI_REG8(0x0306), 0x01},
+	{CCI_REG8(0x0307), 0x5e},
+	{CCI_REG8(0xe04c), 0x00},
+	{CCI_REG8(0xe04d), 0x7f},
+	{CCI_REG8(0xe04e), 0x00},
+	{CCI_REG8(0xe04f), 0x1f},
+	{CCI_REG8(0x3f56), 0x02},
+	{CCI_REG8(0x3f57), 0xae},
+};
+
+/* 2x2 binned. 40fps */
+static const struct cci_reg_sequence mode_2028x1520_regs[] = {
+	{CCI_REG8(0x0344), 0x00},
+	{CCI_REG8(0x0345), 0x00},
+	{CCI_REG8(0x0346), 0x00},
+	{CCI_REG8(0x0347), 0x00},
+	{CCI_REG8(0x0348), 0x0f},
+	{CCI_REG8(0x0349), 0xd7},
+	{CCI_REG8(0x034a), 0x0b},
+	{CCI_REG8(0x034b), 0xdf},
+	{CCI_REG8(0x0900), 0x01},
+	{CCI_REG8(0x0901), 0x22},
+	{CCI_REG8(0x3c01), 0x03},
+	{CCI_REG8(0x3c02), 0xa2},
+	{CCI_REG8(0x5748), 0x07},
+	{CCI_REG8(0x5749), 0xff},
+	{CCI_REG8(0x574a), 0x00},
+	{CCI_REG8(0x574b), 0x00},
+	{CCI_REG8(0x7b53), 0x01},
+	{CCI_REG8(0x9369), 0x73},
+	{CCI_REG8(0x936b), 0x64},
+	{CCI_REG8(0x936d), 0x5f},
+	{CCI_REG8(0x9304), 0x00},
+	{CCI_REG8(0x9305), 0x00},
+	{CCI_REG8(0xa2a9), 0x60},
+	{CCI_REG8(0xa2b7), 0x00},
+	{CCI_REG8(0x0401), 0x00},
+	{CCI_REG8(0x0404), 0x00},
+	{CCI_REG8(0x0405), 0x20},
+	{CCI_REG8(0x0408), 0x00},
+	{CCI_REG8(0x0409), 0x00},
+	{CCI_REG8(0x040a), 0x00},
+	{CCI_REG8(0x040b), 0x00},
+	{CCI_REG8(0x040c), 0x0f},
+	{CCI_REG8(0x040d), 0xd8},
+	{CCI_REG8(0x040e), 0x0b},
+	{CCI_REG8(0x040f), 0xe0},
+	{CCI_REG8(0x034c), 0x07},
+	{CCI_REG8(0x034d), 0xec},
+	{CCI_REG8(0x034e), 0x05},
+	{CCI_REG8(0x034f), 0xf0},
+	{CCI_REG8(0x0305), 0x04},
+	{CCI_REG8(0x0306), 0x01},
+	{CCI_REG8(0x0307), 0x5e},
+	{CCI_REG8(0xe04c), 0x00},
+	{CCI_REG8(0xe04d), 0x7f},
+	{CCI_REG8(0xe04e), 0x00},
+	{CCI_REG8(0xe04f), 0x1f},
+	{CCI_REG8(0x3f56), 0x01},
+	{CCI_REG8(0x3f57), 0x6c},
+};
+
+/* 1080p cropped mode */
+static const struct cci_reg_sequence mode_2028x1080_regs[] = {
+	{CCI_REG8(0x0344), 0x00},
+	{CCI_REG8(0x0345), 0x00},
+	{CCI_REG8(0x0346), 0x01},
+	{CCI_REG8(0x0347), 0xb8},
+	{CCI_REG8(0x0348), 0x0f},
+	{CCI_REG8(0x0349), 0xd7},
+	{CCI_REG8(0x034a), 0x0a},
+	{CCI_REG8(0x034b), 0x27},
+	{CCI_REG8(0x0900), 0x01},
+	{CCI_REG8(0x0901), 0x22},
+	{CCI_REG8(0x3c01), 0x03},
+	{CCI_REG8(0x3c02), 0xa2},
+	{CCI_REG8(0x5748), 0x07},
+	{CCI_REG8(0x5749), 0xff},
+	{CCI_REG8(0x574a), 0x00},
+	{CCI_REG8(0x574b), 0x00},
+	{CCI_REG8(0x7b53), 0x01},
+	{CCI_REG8(0x9369), 0x73},
+	{CCI_REG8(0x936b), 0x64},
+	{CCI_REG8(0x936d), 0x5f},
+	{CCI_REG8(0x9304), 0x00},
+	{CCI_REG8(0x9305), 0x00},
+	{CCI_REG8(0xa2a9), 0x60},
+	{CCI_REG8(0xa2b7), 0x00},
+	{CCI_REG8(0x0401), 0x00},
+	{CCI_REG8(0x0404), 0x00},
+	{CCI_REG8(0x0405), 0x20},
+	{CCI_REG8(0x0408), 0x00},
+	{CCI_REG8(0x0409), 0x00},
+	{CCI_REG8(0x040a), 0x00},
+	{CCI_REG8(0x040b), 0x00},
+	{CCI_REG8(0x040c), 0x0f},
+	{CCI_REG8(0x040d), 0xd8},
+	{CCI_REG8(0x040e), 0x04},
+	{CCI_REG8(0x040f), 0x38},
+	{CCI_REG8(0x034c), 0x07},
+	{CCI_REG8(0x034d), 0xec},
+	{CCI_REG8(0x034e), 0x04},
+	{CCI_REG8(0x034f), 0x38},
+	{CCI_REG8(0x0305), 0x04},
+	{CCI_REG8(0x0306), 0x01},
+	{CCI_REG8(0x0307), 0x5e},
+	{CCI_REG8(0xe04c), 0x00},
+	{CCI_REG8(0xe04d), 0x7f},
+	{CCI_REG8(0xe04e), 0x00},
+	{CCI_REG8(0xe04f), 0x1f},
+	{CCI_REG8(0x3f56), 0x01},
+	{CCI_REG8(0x3f57), 0x6c},
+};
+
+/* 4x4 binned. 120fps */
+static const struct cci_reg_sequence mode_1332x990_regs[] = {
+	{CCI_REG8(0x420b), 0x01},
+	{CCI_REG8(0x990c), 0x00},
+	{CCI_REG8(0x990d), 0x08},
+	{CCI_REG8(0x9956), 0x8c},
+	{CCI_REG8(0x9957), 0x64},
+	{CCI_REG8(0x9958), 0x50},
+	{CCI_REG8(0x9a48), 0x06},
+	{CCI_REG8(0x9a49), 0x06},
+	{CCI_REG8(0x9a4a), 0x06},
+	{CCI_REG8(0x9a4b), 0x06},
+	{CCI_REG8(0x9a4c), 0x06},
+	{CCI_REG8(0x9a4d), 0x06},
+	{CCI_REG8(0x0114), 0x01},
+	{CCI_REG8(0x0340), 0x04},
+	{CCI_REG8(0x0341), 0x1a},
+	{CCI_REG8(0x0344), 0x00},
+	{CCI_REG8(0x0345), 0x00},
+	{CCI_REG8(0x0346), 0x02},
+	{CCI_REG8(0x0347), 0x10},
+	{CCI_REG8(0x0348), 0x0f},
+	{CCI_REG8(0x0349), 0xd7},
+	{CCI_REG8(0x034a), 0x09},
+	{CCI_REG8(0x034b), 0xcf},
+	{CCI_REG8(0x00e3), 0x00},
+	{CCI_REG8(0x00e4), 0x00},
+	{CCI_REG8(0x00fc), 0x0a},
+	{CCI_REG8(0x00fd), 0x0a},
+	{CCI_REG8(0x00fe), 0x0a},
+	{CCI_REG8(0x00ff), 0x0a},
+	{CCI_REG8(0xe013), 0x00},
+	{CCI_REG8(0x0220), 0x00},
+	{CCI_REG8(0x0221), 0x11},
+	{CCI_REG8(0x0381), 0x01},
+	{CCI_REG8(0x0383), 0x01},
+	{CCI_REG8(0x0385), 0x01},
+	{CCI_REG8(0x0387), 0x01},
+	{CCI_REG8(0x0900), 0x01},
+	{CCI_REG8(0x0901), 0x22},
+	{CCI_REG8(0x0902), 0x02},
+	{CCI_REG8(0x3140), 0x02},
+	{CCI_REG8(0x3c00), 0x00},
+	{CCI_REG8(0x3c01), 0x01},
+	{CCI_REG8(0x3c02), 0x9c},
+	{CCI_REG8(0x5748), 0x00},
+	{CCI_REG8(0x5749), 0x00},
+	{CCI_REG8(0x574a), 0x00},
+	{CCI_REG8(0x574b), 0xa4},
+	{CCI_REG8(0x7b75), 0x0e},
+	{CCI_REG8(0x7b76), 0x09},
+	{CCI_REG8(0x7b77), 0x08},
+	{CCI_REG8(0x7b78), 0x06},
+	{CCI_REG8(0x7b79), 0x34},
+	{CCI_REG8(0x7b53), 0x00},
+	{CCI_REG8(0x9369), 0x73},
+	{CCI_REG8(0x936b), 0x64},
+	{CCI_REG8(0x936d), 0x5f},
+	{CCI_REG8(0x9304), 0x03},
+	{CCI_REG8(0x9305), 0x80},
+	{CCI_REG8(0xa2a9), 0x27},
+	{CCI_REG8(0xa2b7), 0x03},
+	{CCI_REG8(0x0401), 0x00},
+	{CCI_REG8(0x0404), 0x00},
+	{CCI_REG8(0x0405), 0x10},
+	{CCI_REG8(0x0408), 0x01},
+	{CCI_REG8(0x0409), 0x5c},
+	{CCI_REG8(0x040a), 0x00},
+	{CCI_REG8(0x040b), 0x00},
+	{CCI_REG8(0x040c), 0x05},
+	{CCI_REG8(0x040d), 0x34},
+	{CCI_REG8(0x040e), 0x03},
+	{CCI_REG8(0x040f), 0xde},
+	{CCI_REG8(0x034c), 0x05},
+	{CCI_REG8(0x034d), 0x34},
+	{CCI_REG8(0x034e), 0x03},
+	{CCI_REG8(0x034f), 0xde},
+	{CCI_REG8(0x0305), 0x02},
+	{CCI_REG8(0x0306), 0x00},
+	{CCI_REG8(0x0307), 0xaf},
+	{CCI_REG8(0xe04c), 0x00},
+	{CCI_REG8(0xe04d), 0x5f},
+	{CCI_REG8(0xe04e), 0x00},
+	{CCI_REG8(0xe04f), 0x1f},
+	{CCI_REG8(0x3f56), 0x00},
+	{CCI_REG8(0x3f57), 0xbf},
+};
+
+/* Mode configs */
+static const struct imx477_mode supported_modes[] = {
+	{
+		/* 12MPix 10fps mode */
+		.width = 4056,
+		.height = 3040,
+		.crop = {
+			.left = IMX477_PIXEL_ARRAY_LEFT,
+			.top = IMX477_PIXEL_ARRAY_TOP,
+			.width = 4056,
+			.height = 3040,
+		},
+		.frm_length_default = 3500,
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_4056x3040_regs),
+			.regs = mode_4056x3040_regs,
+		},
+	},
+	{
+		/* 12MPix cropped 16:9  mode */
+		.width = 4056,
+		.height = 2160,
+		.crop = {
+			.left = IMX477_PIXEL_ARRAY_LEFT,
+			.top = IMX477_PIXEL_ARRAY_TOP + 440,
+			.width = 4056,
+			.height = 3040,
+		},
+		.frm_length_default = 10,
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_4056x2160_regs),
+			.regs = mode_4056x2160_regs,
+		},
+	},
+	{
+		/* 2x2 binned 40fps mode */
+		.width = 2028,
+		.height = 1520,
+		.crop = {
+			.left = IMX477_PIXEL_ARRAY_LEFT,
+			.top = IMX477_PIXEL_ARRAY_TOP,
+			.width = 4056,
+			.height = 3040,
+		},
+		.frm_length_default = 2197,
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_2028x1520_regs),
+			.regs = mode_2028x1520_regs,
+		},
+	},
+	{
+		/* 1080p 50fps cropped mode */
+		.width = 2028,
+		.height = 1080,
+		.crop = {
+			.left = IMX477_PIXEL_ARRAY_LEFT,
+			.top = IMX477_PIXEL_ARRAY_TOP + 440,
+			.width = 4056,
+			.height = 2160,
+		},
+		.frm_length_default = 2197,
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_2028x1080_regs),
+			.regs = mode_2028x1080_regs,
+		},
+	},
+	{
+		/* 120fps. 2x2 binned and cropped */
+		.width = 1332,
+		.height = 990,
+		.crop = {
+			/*
+			 * FIXME: the analog crop rectangle is actually
+			 * programmed with a horizontal displacement of 0
+			 * pixels, not 4. It gets shrunk after going through
+			 * the scaler. Move this information to the compose
+			 * rectangle once the driver is expanded to represent
+			 * its processing blocks with multiple subdevs.
+			 */
+			.left = IMX477_PIXEL_ARRAY_LEFT + 696,
+			.top = IMX477_PIXEL_ARRAY_TOP + 528,
+			.width = 2664,
+			.height = 1980,
+		},
+		.frm_length_default = 1050,
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_1332x990_regs),
+			.regs = mode_1332x990_regs,
+		}
+	}
+};
+
+/*
+ * The supported formats.
+ * This table MUST contain 4 entries per format, to cover the various flip
+ * combinations in the order
+ * - no flip
+ * - h flip
+ * - v flip
+ * - h&v flips
+ */
+static const u32 codes[] = {
+	/* 12-bit modes. */
+	MEDIA_BUS_FMT_SRGGB12_1X12,
+	MEDIA_BUS_FMT_SGRBG12_1X12,
+	MEDIA_BUS_FMT_SGBRG12_1X12,
+	MEDIA_BUS_FMT_SBGGR12_1X12,
+	/* 10-bit modes. */
+	MEDIA_BUS_FMT_SRGGB10_1X10,
+	MEDIA_BUS_FMT_SGRBG10_1X10,
+	MEDIA_BUS_FMT_SGBRG10_1X10,
+	MEDIA_BUS_FMT_SBGGR10_1X10,
+	/* 8-bit modes. */
+	MEDIA_BUS_FMT_SRGGB8_1X8,
+	MEDIA_BUS_FMT_SGRBG8_1X8,
+	MEDIA_BUS_FMT_SGBRG8_1X8,
+	MEDIA_BUS_FMT_SBGGR8_1X8,
+};
+
+static const char * const imx477_test_pattern_menu[] = {
+	"Disabled",
+	"Color Bars",
+	"Solid Color",
+	"Grey Color Bars",
+	"PN9"
+};
+
+static const int imx477_test_pattern_val[] = {
+	IMX477_TEST_PATTERN_DISABLE,
+	IMX477_TEST_PATTERN_COLOR_BARS,
+	IMX477_TEST_PATTERN_SOLID_COLOR,
+	IMX477_TEST_PATTERN_GREY_COLOR,
+	IMX477_TEST_PATTERN_PN9,
+};
+
+/* regulator supplies */
+static const char * const imx477_supply_name[] = {
+	/* Supplies can be enabled in any order */
+	"VANA",  /* Analog (2.8V) supply */
+	"VDIG",  /* Digital Core (1.05V) supply */
+	"VDDL",  /* IF (1.8V) supply */
+};
+
+#define IMX477_NUM_SUPPLIES ARRAY_SIZE(imx477_supply_name)
+
+/*
+ * Initialisation delay between XCLR low->high and the moment when the sensor
+ * can start capture (i.e. can leave software standby), given by T7 in the
+ * datasheet is 8ms.  This does include I2C setup time as well.
+ *
+ * Note, that delay between XCLR low->high and reading the CCI ID register (T6
+ * in the datasheet) is much smaller - 600us.
+ */
+#define IMX477_XCLR_MIN_DELAY_US	8000
+#define IMX477_XCLR_DELAY_RANGE_US	1000
+
+struct imx477_compatible_data {
+	unsigned int chip_id;
+	struct imx477_reg_list extra_regs;
+};
+
+struct imx477 {
+	struct v4l2_subdev sd;
+	struct media_pad pad[NUM_PADS];
+	struct regmap *regmap;
+
+	unsigned int fmt_code;
+
+	struct clk *xclk;
+	u32 xclk_freq;
+
+	struct gpio_desc *reset_gpio;
+	struct regulator_bulk_data supplies[IMX477_NUM_SUPPLIES];
+
+	struct v4l2_ctrl_handler ctrl_handler;
+	/* V4L2 Controls */
+	struct v4l2_ctrl *pixel_rate;
+	struct v4l2_ctrl *link_freq;
+	struct v4l2_ctrl *exposure;
+	struct v4l2_ctrl *vflip;
+	struct v4l2_ctrl *hflip;
+	struct v4l2_ctrl *vblank;
+	struct v4l2_ctrl *hblank;
+
+	u64 link_freq_value;
+	u16 iop_pll_mpy;
+
+	/* Current mode */
+	const struct imx477_mode *mode;
+
+	/* Trigger mode */
+	int trigger_mode_of;
+
+	/*
+	 * Mutex for serialized access:
+	 * Protect sensor module set pad format and start/stop streaming safely.
+	 */
+	struct mutex mutex;
+
+	/* Streaming on/off */
+	bool streaming;
+
+	/* Rewrite common registers on stream on? */
+	bool common_regs_written;
+
+	/* Current long exposure factor in use. Set through V4L2_CID_VBLANK */
+	unsigned int long_exp_shift;
+
+	/* Any extra information related to different compatible sensors */
+	const struct imx477_compatible_data *compatible_data;
+};
+
+static inline struct imx477 *to_imx477(struct v4l2_subdev *_sd)
+{
+	return container_of(_sd, struct imx477, sd);
+}
+
+/* Get bayer order based on flip setting. */
+static u32 imx477_get_format_code(struct imx477 *imx477, u32 code)
+{
+	unsigned int i;
+
+	lockdep_assert_held(&imx477->mutex);
+
+	for (i = 0; i < ARRAY_SIZE(codes); i++)
+		if (codes[i] == code)
+			break;
+
+	if (i >= ARRAY_SIZE(codes))
+		i = 0;
+
+	i = (i & ~3) | (imx477->vflip->val ? 2 : 0) |
+	    (imx477->hflip->val ? 1 : 0);
+
+	return codes[i];
+}
+
+static void imx477_set_default_format(struct imx477 *imx477)
+{
+	/* Set default mode to max resolution */
+	imx477->mode = &supported_modes[0];
+	imx477->fmt_code = MEDIA_BUS_FMT_SRGGB12_1X12;
+}
+
+static int imx477_get_bpp(unsigned int code)
+{
+	switch (code) {
+	default:
+	case MEDIA_BUS_FMT_SRGGB12_1X12:
+	case MEDIA_BUS_FMT_SGRBG12_1X12:
+	case MEDIA_BUS_FMT_SGBRG12_1X12:
+	case MEDIA_BUS_FMT_SBGGR12_1X12:
+		return 12;
+
+	case MEDIA_BUS_FMT_SRGGB10_1X10:
+	case MEDIA_BUS_FMT_SGRBG10_1X10:
+	case MEDIA_BUS_FMT_SGBRG10_1X10:
+	case MEDIA_BUS_FMT_SBGGR10_1X10:
+		return 10;
+
+	case MEDIA_BUS_FMT_SRGGB8_1X8:
+	case MEDIA_BUS_FMT_SGRBG8_1X8:
+	case MEDIA_BUS_FMT_SGBRG8_1X8:
+	case MEDIA_BUS_FMT_SBGGR8_1X8:
+		return 8;
+	}
+}
+
+static int imx477_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+	struct imx477 *imx477 = to_imx477(sd);
+	struct v4l2_mbus_framefmt *try_fmt_img =
+		v4l2_subdev_state_get_format(fh->state, IMAGE_PAD);
+	struct v4l2_mbus_framefmt *try_fmt_meta =
+		v4l2_subdev_state_get_format(fh->state, METADATA_PAD);
+	struct v4l2_rect *try_crop;
+
+	mutex_lock(&imx477->mutex);
+
+	/* Initialize try_fmt for the image pad */
+	try_fmt_img->width = supported_modes[0].width;
+	try_fmt_img->height = supported_modes[0].height;
+	try_fmt_img->code = imx477_get_format_code(imx477,
+						   MEDIA_BUS_FMT_SRGGB12_1X12);
+	try_fmt_img->field = V4L2_FIELD_NONE;
+
+	/* Initialize try_fmt for the embedded metadata pad */
+	try_fmt_meta->width = IMX477_EMBEDDED_LINE_WIDTH;
+	try_fmt_meta->height = IMX477_NUM_EMBEDDED_LINES;
+	try_fmt_meta->code = MEDIA_BUS_FMT_SENSOR_DATA;
+	try_fmt_meta->field = V4L2_FIELD_NONE;
+
+	/* Initialize try_crop */
+	try_crop = v4l2_subdev_state_get_crop(fh->state, IMAGE_PAD);
+	try_crop->left = IMX477_PIXEL_ARRAY_LEFT;
+	try_crop->top = IMX477_PIXEL_ARRAY_TOP;
+	try_crop->width = IMX477_PIXEL_ARRAY_WIDTH;
+	try_crop->height = IMX477_PIXEL_ARRAY_HEIGHT;
+
+	mutex_unlock(&imx477->mutex);
+
+	return 0;
+}
+
+static void imx477_adjust_exposure_range(struct imx477 *imx477)
+{
+	int exposure_max, exposure_def;
+
+	/* Honour the VBLANK limits when setting exposure. */
+	exposure_max = imx477->mode->height + imx477->vblank->val -
+		       IMX477_EXPOSURE_OFFSET;
+	exposure_def = min(exposure_max, imx477->exposure->val);
+	__v4l2_ctrl_modify_range(imx477->exposure, imx477->exposure->minimum,
+				 exposure_max, imx477->exposure->step,
+				 exposure_def);
+}
+
+static int imx477_set_frame_length(struct imx477 *imx477, unsigned int val)
+{
+	int ret = 0;
+
+	imx477->long_exp_shift = 0;
+
+	while (val > IMX477_FRAME_LENGTH_MAX) {
+		imx477->long_exp_shift++;
+		val >>= 1;
+	}
+
+	ret = cci_write(imx477->regmap, IMX477_REG_FRAME_LENGTH, val, NULL);
+
+	return cci_write(imx477->regmap, IMX477_LONG_EXP_SHIFT_REG,
+			 imx477->long_exp_shift, &ret);
+}
+
+static int imx477_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct imx477 *imx477 =
+		container_of(ctrl->handler, struct imx477, ctrl_handler);
+	struct i2c_client *client = v4l2_get_subdevdata(&imx477->sd);
+	int ret = 0;
+
+	/*
+	 * The VBLANK control may change the limits of usable exposure, so check
+	 * and adjust if necessary.
+	 */
+	if (ctrl->id == V4L2_CID_VBLANK)
+		imx477_adjust_exposure_range(imx477);
+
+	/*
+	 * Applying V4L2 control value only happens
+	 * when power is up for streaming
+	 */
+	if (pm_runtime_get_if_in_use(&client->dev) == 0)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_ANALOGUE_GAIN:
+		ret = cci_write(imx477->regmap, IMX477_REG_ANALOG_GAIN,
+				ctrl->val, NULL);
+		break;
+	case V4L2_CID_EXPOSURE:
+		ret = cci_write(imx477->regmap, IMX477_REG_EXPOSURE,
+				ctrl->val >> imx477->long_exp_shift, NULL);
+		break;
+	case V4L2_CID_DIGITAL_GAIN:
+		ret = cci_write(imx477->regmap, IMX477_REG_DIGITAL_GAIN,
+				ctrl->val, NULL);
+		break;
+	case V4L2_CID_TEST_PATTERN:
+		ret = cci_write(imx477->regmap, IMX477_REG_TEST_PATTERN,
+				imx477_test_pattern_val[ctrl->val], NULL);
+		break;
+	case V4L2_CID_TEST_PATTERN_RED:
+		ret = cci_write(imx477->regmap, IMX477_REG_TEST_PATTERN_R,
+				ctrl->val, NULL);
+		break;
+	case V4L2_CID_TEST_PATTERN_GREENR:
+		ret = cci_write(imx477->regmap, IMX477_REG_TEST_PATTERN_GR,
+				ctrl->val, NULL);
+		break;
+	case V4L2_CID_TEST_PATTERN_BLUE:
+		ret = cci_write(imx477->regmap, IMX477_REG_TEST_PATTERN_B,
+				ctrl->val, NULL);
+		break;
+	case V4L2_CID_TEST_PATTERN_GREENB:
+		ret = cci_write(imx477->regmap, IMX477_REG_TEST_PATTERN_GB,
+				ctrl->val, NULL);
+		break;
+	case V4L2_CID_HFLIP:
+	case V4L2_CID_VFLIP:
+		ret = cci_write(imx477->regmap, IMX477_REG_ORIENTATION,
+				imx477->hflip->val | imx477->vflip->val << 1,
+				NULL);
+		break;
+	case V4L2_CID_VBLANK:
+		ret = imx477_set_frame_length(imx477,
+					      imx477->mode->height + ctrl->val);
+		break;
+	case V4L2_CID_HBLANK:
+		ret = cci_write(imx477->regmap, IMX477_REG_LINE_LENGTH,
+				imx477->mode->width + ctrl->val, NULL);
+		break;
+	default:
+		dev_info(&client->dev,
+			 "ctrl(id:0x%x,val:0x%x) is not handled\n",
+			 ctrl->id, ctrl->val);
+		ret = -EINVAL;
+		break;
+	}
+
+	pm_runtime_mark_last_busy(&client->dev);
+	pm_runtime_put_autosuspend(&client->dev);
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops imx477_ctrl_ops = {
+	.s_ctrl = imx477_set_ctrl,
+};
+
+static int imx477_enum_mbus_code(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *sd_state,
+				 struct v4l2_subdev_mbus_code_enum *code)
+{
+	struct imx477 *imx477 = to_imx477(sd);
+
+	if (code->pad >= NUM_PADS)
+		return -EINVAL;
+
+	if (code->pad == IMAGE_PAD) {
+		if (code->index >= (ARRAY_SIZE(codes) / 4))
+			return -EINVAL;
+
+		mutex_lock(&imx477->mutex);
+		code->code = imx477_get_format_code(imx477,
+						    codes[code->index * 4]);
+		mutex_unlock(&imx477->mutex);
+	} else {
+		if (code->index > 0)
+			return -EINVAL;
+
+		code->code = MEDIA_BUS_FMT_SENSOR_DATA;
+	}
+
+	return 0;
+}
+
+static int imx477_enum_frame_size(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *sd_state,
+				  struct v4l2_subdev_frame_size_enum *fse)
+{
+	struct imx477 *imx477 = to_imx477(sd);
+	u32 code;
+
+	if (fse->pad >= NUM_PADS)
+		return -EINVAL;
+
+	if (fse->pad == IMAGE_PAD) {
+		if (fse->index >= ARRAY_SIZE(supported_modes))
+			return -EINVAL;
+
+		mutex_lock(&imx477->mutex);
+		code = imx477_get_format_code(imx477, fse->code);
+		mutex_unlock(&imx477->mutex);
+
+		if (fse->code != code)
+			return -EINVAL;
+
+		fse->min_width = supported_modes[fse->index].width;
+		fse->max_width = fse->min_width;
+		fse->min_height = supported_modes[fse->index].height;
+		fse->max_height = fse->min_height;
+	} else {
+		if (fse->code != MEDIA_BUS_FMT_SENSOR_DATA || fse->index > 0)
+			return -EINVAL;
+
+		fse->min_width = IMX477_EMBEDDED_LINE_WIDTH;
+		fse->max_width = fse->min_width;
+		fse->min_height = IMX477_NUM_EMBEDDED_LINES;
+		fse->max_height = fse->min_height;
+	}
+
+	return 0;
+}
+
+static void imx477_reset_colorspace(struct v4l2_mbus_framefmt *fmt)
+{
+	fmt->colorspace = V4L2_COLORSPACE_RAW;
+	fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace);
+	fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true,
+							  fmt->colorspace,
+							  fmt->ycbcr_enc);
+	fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace);
+}
+
+static void imx477_update_image_pad_format(struct imx477 *imx477,
+					   const struct imx477_mode *mode,
+					   struct v4l2_subdev_format *fmt)
+{
+	fmt->format.width = mode->width;
+	fmt->format.height = mode->height;
+	fmt->format.field = V4L2_FIELD_NONE;
+	imx477_reset_colorspace(&fmt->format);
+}
+
+static void imx477_update_metadata_pad_format(struct v4l2_subdev_format *fmt)
+{
+	fmt->format.width = IMX477_EMBEDDED_LINE_WIDTH;
+	fmt->format.height = IMX477_NUM_EMBEDDED_LINES;
+	fmt->format.code = MEDIA_BUS_FMT_SENSOR_DATA;
+	fmt->format.field = V4L2_FIELD_NONE;
+}
+
+static int imx477_get_pad_format(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *sd_state,
+				 struct v4l2_subdev_format *fmt)
+{
+	struct imx477 *imx477 = to_imx477(sd);
+
+	if (fmt->pad >= NUM_PADS)
+		return -EINVAL;
+
+	mutex_lock(&imx477->mutex);
+
+	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+		struct v4l2_mbus_framefmt *try_fmt =
+			v4l2_subdev_state_get_format(sd_state,
+						   fmt->pad);
+		/* update the code which could change due to vflip or hflip: */
+		try_fmt->code = fmt->pad == IMAGE_PAD ?
+				imx477_get_format_code(imx477, try_fmt->code) :
+				MEDIA_BUS_FMT_SENSOR_DATA;
+		fmt->format = *try_fmt;
+	} else {
+		if (fmt->pad == IMAGE_PAD) {
+			imx477_update_image_pad_format(imx477, imx477->mode,
+						       fmt);
+			fmt->format.code =
+			       imx477_get_format_code(imx477, imx477->fmt_code);
+		} else {
+			imx477_update_metadata_pad_format(fmt);
+		}
+	}
+
+	mutex_unlock(&imx477->mutex);
+	return 0;
+}
+
+static void imx477_set_framing_limits(struct imx477 *imx477)
+{
+	unsigned int hblank_min;
+	u64 line_length_min;
+	int bpp;
+	const struct imx477_mode *mode = imx477->mode;
+
+	/* Default to no long exposure multiplier. */
+	imx477->long_exp_shift = 0;
+
+	/* Update limits and set FPS to default */
+	__v4l2_ctrl_modify_range(imx477->vblank,
+				 IMX477_VBLANK_MIN,
+				 ((1 << IMX477_LONG_EXP_SHIFT_MAX) *
+					IMX477_FRAME_LENGTH_MAX) - mode->height,
+				 1, mode->frm_length_default - mode->height);
+
+	/* Setting this will adjust the exposure limits as well. */
+	__v4l2_ctrl_s_ctrl(imx477->vblank,
+			   mode->frm_length_default - mode->height);
+
+	bpp = imx477_get_bpp(imx477->fmt_code);
+
+	line_length_min = mode->width * bpp * (u64)IMX477_PIXEL_RATE;
+	do_div(line_length_min, imx477->link_freq_value * 2 * 2 /*LANES*/);
+	/* Allow 500pixel clocks for HS<>LP transitions (approx 0.6usecs) */
+	line_length_min += 500;
+
+	/*
+	 * 1332x990 12bit mode appears to need additional horizontal blanking
+	 * compared to all other modes. Empirically determined setting with
+	 * link freq of 750MHz where it still gives valid images. The limit
+	 * leaves all other modes unchanged as their line times are inherently
+	 * greater.
+	 */
+	if (bpp == 12)
+		line_length_min = max(line_length_min, 1332 + 4600);
+
+	hblank_min = line_length_min - mode->width;
+	__v4l2_ctrl_modify_range(imx477->hblank, hblank_min,
+				 IMX477_LINE_LENGTH_MAX, 1, hblank_min);
+	__v4l2_ctrl_s_ctrl(imx477->hblank, hblank_min);
+}
+
+static int imx477_set_pad_format(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *sd_state,
+				 struct v4l2_subdev_format *fmt)
+{
+	struct v4l2_mbus_framefmt *framefmt;
+	const struct imx477_mode *mode;
+	struct imx477 *imx477 = to_imx477(sd);
+
+	if (fmt->pad >= NUM_PADS)
+		return -EINVAL;
+
+	mutex_lock(&imx477->mutex);
+
+	if (fmt->pad == IMAGE_PAD) {
+		/* Bayer order varies with flips */
+		fmt->format.code = imx477_get_format_code(imx477,
+							  fmt->format.code);
+
+		mode = v4l2_find_nearest_size(supported_modes,
+					      ARRAY_SIZE(supported_modes),
+					      width, height,
+					      fmt->format.width,
+					      fmt->format.height);
+		imx477_update_image_pad_format(imx477, mode, fmt);
+		if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+			framefmt = v4l2_subdev_state_get_format(sd_state,
+							      fmt->pad);
+			*framefmt = fmt->format;
+		} else if (imx477->mode != mode ||
+			   imx477->fmt_code != fmt->format.code) {
+			imx477->mode = mode;
+			imx477->fmt_code = fmt->format.code;
+			imx477_set_framing_limits(imx477);
+		}
+	} else {
+		if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+			framefmt = v4l2_subdev_state_get_format(sd_state,
+							      fmt->pad);
+			*framefmt = fmt->format;
+		} else {
+			/* Only one embedded data mode is supported */
+			imx477_update_metadata_pad_format(fmt);
+		}
+	}
+
+	mutex_unlock(&imx477->mutex);
+
+	return 0;
+}
+
+static const struct v4l2_rect *
+__imx477_get_pad_crop(struct imx477 *imx477,
+		      struct v4l2_subdev_state *sd_state,
+		      unsigned int pad, enum v4l2_subdev_format_whence which)
+{
+	switch (which) {
+	case V4L2_SUBDEV_FORMAT_TRY:
+		return v4l2_subdev_state_get_crop(sd_state, pad);
+	case V4L2_SUBDEV_FORMAT_ACTIVE:
+		return &imx477->mode->crop;
+	}
+
+	return NULL;
+}
+
+static int imx477_get_selection(struct v4l2_subdev *sd,
+				struct v4l2_subdev_state *sd_state,
+				struct v4l2_subdev_selection *sel)
+{
+	switch (sel->target) {
+	case V4L2_SEL_TGT_CROP: {
+		struct imx477 *imx477 = to_imx477(sd);
+
+		mutex_lock(&imx477->mutex);
+		sel->r = *__imx477_get_pad_crop(imx477, sd_state, sel->pad,
+						sel->which);
+		mutex_unlock(&imx477->mutex);
+
+		return 0;
+	}
+
+	case V4L2_SEL_TGT_NATIVE_SIZE:
+		sel->r.left = 0;
+		sel->r.top = 0;
+		sel->r.width = IMX477_NATIVE_WIDTH;
+		sel->r.height = IMX477_NATIVE_HEIGHT;
+
+		return 0;
+
+	case V4L2_SEL_TGT_CROP_DEFAULT:
+	case V4L2_SEL_TGT_CROP_BOUNDS:
+		sel->r.left = IMX477_PIXEL_ARRAY_LEFT;
+		sel->r.top = IMX477_PIXEL_ARRAY_TOP;
+		sel->r.width = IMX477_PIXEL_ARRAY_WIDTH;
+		sel->r.height = IMX477_PIXEL_ARRAY_HEIGHT;
+
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+/* Start streaming */
+static int imx477_start_streaming(struct imx477 *imx477)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx477->sd);
+	const struct imx477_reg_list *reg_list;
+	const struct imx477_reg_list *extra_regs;
+	unsigned int fst_width;
+	unsigned int fst_mult;
+	int ret, tm, bpp;
+
+	if (!imx477->common_regs_written) {
+		ret = cci_multi_reg_write(imx477->regmap, mode_common_regs,
+					  ARRAY_SIZE(mode_common_regs), NULL);
+
+		extra_regs = &imx477->compatible_data->extra_regs;
+		cci_multi_reg_write(imx477->regmap, extra_regs->regs,
+				    extra_regs->num_of_regs, &ret);
+
+		/* Update the link frequency PLL multiplier register */
+		cci_write(imx477->regmap, IMX477_REG_IOP_MPY,
+			  imx477->iop_pll_mpy, &ret);
+
+		/*
+		 * Bit rate = link freq * 2 for DDR * 2 for num lanes.
+		 * 16p16 fixed point in the register. Ignore fractional part.
+		 */
+		cci_write(imx477->regmap, IMX477_REG_REQ_LINK_BIT_RATE,
+			  (((unsigned long)imx477->link_freq_value / 1000000) * 2 * 2) << 16,
+			  &ret);
+		/*
+		 * DPHY timings are those specified for 450MHz. Switch to auto
+		 * if using some other link frequency
+		 */
+		if (imx477->link_freq_value != 450000000UL)
+			cci_write(imx477->regmap, IMX477_REG_DPHY_CTRL,
+				  IMX477_DPHY_CTRL_AUTO, &ret);
+
+		if (ret) {
+			dev_err(&client->dev, "%s failed to set common settings\n",
+				__func__);
+			return ret;
+		}
+		imx477->common_regs_written = true;
+	}
+
+	/* Apply default values of current mode */
+	reg_list = &imx477->mode->reg_list;
+	ret = cci_multi_reg_write(imx477->regmap, reg_list->regs,
+				  reg_list->num_of_regs, NULL);
+	if (ret) {
+		dev_err(&client->dev, "%s failed to set mode\n", __func__);
+		return ret;
+	}
+
+	fst_width = max((unsigned int)fstrobe_width, 1U);
+	fst_mult = 1;
+
+	while (fst_width / fst_mult > 0xffff && fst_mult < 255)
+		fst_mult++;
+
+	fst_width /= fst_mult;
+
+	// FLASH_MD_RS
+	cci_write(imx477->regmap, CCI_REG8(0x0c1A),
+		  ((!!fstrobe_cont_trig) | (1 << 1)), &ret);
+	// FLASH_STRB_WIDTH
+	cci_write(imx477->regmap, CCI_REG16(0x0c18), fst_width, &ret);
+	// FLASH_STRB_WIDTH adjust
+	cci_write(imx477->regmap, CCI_REG8(0x0c12), fst_mult, &ret);
+	// FLASH_STRB_START_POINT
+	cci_write(imx477->regmap, CCI_REG16(0x0c14), fstrobe_delay, &ret);
+	// FLASH_STRB_DLY_RS
+	cci_write(imx477->regmap, CCI_REG8(0x0c16), 0, &ret);
+	// FLASH_TRIG_RS
+	cci_write(imx477->regmap, CCI_REG8(0x0c1B), !!fstrobe_enable, &ret);
+
+	bpp = imx477_get_bpp(imx477->fmt_code);
+	cci_write(imx477->regmap, IMX477_REG_CSI_DT_FMT_H, bpp, &ret);
+	cci_write(imx477->regmap, IMX477_REG_CSI_DT_FMT_L, bpp, &ret);
+	cci_write(imx477->regmap, IMX477_REG_IOP_PXCK_DIV, bpp, &ret);
+	cci_write(imx477->regmap, IMX477_REG_ADBIT_MODE, bpp == 12 ? 1 : 0,
+		  &ret);
+
+	/* Set on-sensor DPC. */
+	cci_write(imx477->regmap, CCI_REG8(0x0b05), !!dpc_enable, &ret);
+	cci_write(imx477->regmap, CCI_REG8(0x0b06), !!dpc_enable, &ret);
+
+	/* Apply customized values from user */
+	ret =  __v4l2_ctrl_handler_setup(imx477->sd.ctrl_handler);
+	if (ret)
+		return ret;
+
+	/* Set vsync trigger mode: 0=standalone, 1=source, 2=sink */
+	tm = (imx477->trigger_mode_of >= 0) ? imx477->trigger_mode_of : trigger_mode;
+	if (tm == 1)
+		cci_write(imx477->regmap, IMX477_REG_TEMP_SEN_CTL, 0, &ret);
+	cci_write(imx477->regmap, IMX477_REG_EXTOUT_EN, (tm == 1) ? 1 : 0,
+		  &ret);
+	cci_write(imx477->regmap, IMX477_REG_MS_SEL, (tm <= 1) ? 1 : 0, &ret);
+	cci_write(imx477->regmap, IMX477_REG_XVS_IO_CTRL, (tm == 1) ? 1 : 0,
+		  &ret);
+	cci_write(imx477->regmap, IMX477_REG_MC_MODE, (tm > 0) ? 1 : 0, &ret);
+
+	/* set stream on register */
+	cci_write(imx477->regmap, IMX477_REG_MODE_SELECT,
+		  IMX477_MODE_STREAMING, &ret);
+
+	/* now it's safe to re-enable temp sensor without glitching XVS */
+	if (tm == 1)
+		cci_write(imx477->regmap, IMX477_REG_TEMP_SEN_CTL, 1, &ret);
+
+	return ret;
+}
+
+/* Stop streaming */
+static void imx477_stop_streaming(struct imx477 *imx477)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx477->sd);
+	int ret;
+
+	/* set stream off register */
+	ret = cci_write(imx477->regmap, IMX477_REG_MODE_SELECT,
+			IMX477_MODE_STANDBY, NULL);
+	if (ret)
+		dev_err(&client->dev, "%s failed to set stream\n", __func__);
+
+	/* Stop driving XVS out (there is still a weak pull-up) */
+	cci_write(imx477->regmap, IMX477_REG_EXTOUT_EN, 0, NULL);
+}
+
+static int imx477_set_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct imx477 *imx477 = to_imx477(sd);
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	int ret = 0;
+
+	mutex_lock(&imx477->mutex);
+	if (imx477->streaming == enable) {
+		mutex_unlock(&imx477->mutex);
+		return 0;
+	}
+
+	if (enable) {
+		ret = pm_runtime_resume_and_get(&client->dev);
+		if (ret < 0)
+			goto err_unlock;
+
+		/*
+		 * Apply default & customized values
+		 * and then start streaming.
+		 */
+		ret = imx477_start_streaming(imx477);
+		if (ret) {
+			pm_runtime_put_sync(&client->dev);
+			goto err_unlock;
+		}
+	} else {
+		imx477_stop_streaming(imx477);
+		pm_runtime_mark_last_busy(&client->dev);
+		pm_runtime_put_autosuspend(&client->dev);
+	}
+
+	imx477->streaming = enable;
+
+	/* vflip and hflip cannot change during streaming */
+	__v4l2_ctrl_grab(imx477->vflip, enable);
+	__v4l2_ctrl_grab(imx477->hflip, enable);
+
+	mutex_unlock(&imx477->mutex);
+
+	return ret;
+
+err_unlock:
+	mutex_unlock(&imx477->mutex);
+
+	return ret;
+}
+
+/* Power/clock management functions */
+static int imx477_power_on(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct imx477 *imx477 = to_imx477(sd);
+	int ret;
+
+	ret = regulator_bulk_enable(IMX477_NUM_SUPPLIES,
+				    imx477->supplies);
+	if (ret) {
+		dev_err(&client->dev, "%s: failed to enable regulators\n",
+			__func__);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(imx477->xclk);
+	if (ret) {
+		dev_err(&client->dev, "%s: failed to enable clock\n",
+			__func__);
+		goto reg_off;
+	}
+
+	gpiod_set_value_cansleep(imx477->reset_gpio, 1);
+	usleep_range(IMX477_XCLR_MIN_DELAY_US,
+		     IMX477_XCLR_MIN_DELAY_US + IMX477_XCLR_DELAY_RANGE_US);
+
+	return 0;
+
+reg_off:
+	regulator_bulk_disable(IMX477_NUM_SUPPLIES, imx477->supplies);
+	return ret;
+}
+
+static int imx477_power_off(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct imx477 *imx477 = to_imx477(sd);
+
+	gpiod_set_value_cansleep(imx477->reset_gpio, 0);
+	regulator_bulk_disable(IMX477_NUM_SUPPLIES, imx477->supplies);
+	clk_disable_unprepare(imx477->xclk);
+
+	/* Force reprogramming of the common registers when powered up again. */
+	imx477->common_regs_written = false;
+
+	return 0;
+}
+
+static int __maybe_unused imx477_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct imx477 *imx477 = to_imx477(sd);
+
+	if (imx477->streaming)
+		imx477_stop_streaming(imx477);
+
+	return 0;
+}
+
+static int __maybe_unused imx477_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct imx477 *imx477 = to_imx477(sd);
+	int ret;
+
+	if (imx477->streaming) {
+		ret = imx477_start_streaming(imx477);
+		if (ret)
+			goto error;
+	}
+
+	return 0;
+
+error:
+	imx477_stop_streaming(imx477);
+	imx477->streaming = 0;
+	return ret;
+}
+
+static int imx477_get_regulators(struct imx477 *imx477)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx477->sd);
+	unsigned int i;
+
+	for (i = 0; i < IMX477_NUM_SUPPLIES; i++)
+		imx477->supplies[i].supply = imx477_supply_name[i];
+
+	return devm_regulator_bulk_get(&client->dev,
+				       IMX477_NUM_SUPPLIES,
+				       imx477->supplies);
+}
+
+/* Verify chip ID */
+static int imx477_identify_module(struct imx477 *imx477, u32 expected_id)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx477->sd);
+	int ret;
+	u64 val;
+
+	ret = cci_read(imx477->regmap, IMX477_REG_CHIP_ID, &val, NULL);
+	if (ret) {
+		dev_err(&client->dev, "failed to read chip id %x, with error %d\n",
+			expected_id, ret);
+		return ret;
+	}
+
+	if (val != expected_id) {
+		dev_err(&client->dev, "chip id mismatch: %x!=%llx\n",
+			expected_id, val);
+		return -EIO;
+	}
+
+	dev_info(&client->dev, "Device found is imx%llx\n", val);
+
+	return 0;
+}
+
+static const struct v4l2_subdev_core_ops imx477_core_ops = {
+	.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_video_ops imx477_video_ops = {
+	.s_stream = imx477_set_stream,
+};
+
+static const struct v4l2_subdev_pad_ops imx477_pad_ops = {
+	.enum_mbus_code = imx477_enum_mbus_code,
+	.get_fmt = imx477_get_pad_format,
+	.set_fmt = imx477_set_pad_format,
+	.get_selection = imx477_get_selection,
+	.enum_frame_size = imx477_enum_frame_size,
+};
+
+static const struct v4l2_subdev_ops imx477_subdev_ops = {
+	.core = &imx477_core_ops,
+	.video = &imx477_video_ops,
+	.pad = &imx477_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops imx477_internal_ops = {
+	.open = imx477_open,
+};
+
+/* Initialize control handlers */
+static int imx477_init_controls(struct imx477 *imx477)
+{
+	struct v4l2_ctrl_handler *ctrl_hdlr;
+	struct i2c_client *client = v4l2_get_subdevdata(&imx477->sd);
+	struct v4l2_fwnode_device_properties props;
+	unsigned int i;
+	int ret;
+
+	ctrl_hdlr = &imx477->ctrl_handler;
+	ret = v4l2_ctrl_handler_init(ctrl_hdlr, 16);
+	if (ret)
+		return ret;
+
+	mutex_init(&imx477->mutex);
+	ctrl_hdlr->lock = &imx477->mutex;
+
+	/* By default, PIXEL_RATE is read only */
+	imx477->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx477_ctrl_ops,
+					       V4L2_CID_PIXEL_RATE,
+					       IMX477_PIXEL_RATE,
+					       IMX477_PIXEL_RATE, 1,
+					       IMX477_PIXEL_RATE);
+	if (imx477->pixel_rate)
+		imx477->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	/* LINK_FREQ is also read only */
+	imx477->link_freq =
+		v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx477_ctrl_ops,
+				       V4L2_CID_LINK_FREQ, 0, 0,
+				       &imx477->link_freq_value);
+	if (imx477->link_freq)
+		imx477->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	/*
+	 * Create the controls here, but mode specific limits are setup
+	 * in the imx477_set_framing_limits() call below.
+	 */
+	imx477->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx477_ctrl_ops,
+					   V4L2_CID_VBLANK, IMX477_VBLANK_MIN,
+					   0xffff, 1, IMX477_VBLANK_MIN);
+	imx477->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx477_ctrl_ops,
+					   V4L2_CID_HBLANK, 0, 0xffff, 1, 0);
+
+	imx477->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx477_ctrl_ops,
+					     V4L2_CID_EXPOSURE,
+					     IMX477_EXPOSURE_MIN,
+					     IMX477_EXPOSURE_MAX,
+					     IMX477_EXPOSURE_STEP,
+					     IMX477_EXPOSURE_DEFAULT);
+
+	v4l2_ctrl_new_std(ctrl_hdlr, &imx477_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
+			  IMX477_ANA_GAIN_MIN, IMX477_ANA_GAIN_MAX,
+			  IMX477_ANA_GAIN_STEP, IMX477_ANA_GAIN_DEFAULT);
+
+	v4l2_ctrl_new_std(ctrl_hdlr, &imx477_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
+			  IMX477_DGTL_GAIN_MIN, IMX477_DGTL_GAIN_MAX,
+			  IMX477_DGTL_GAIN_STEP, IMX477_DGTL_GAIN_DEFAULT);
+
+	imx477->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx477_ctrl_ops,
+					  V4L2_CID_HFLIP, 0, 1, 1, 0);
+	if (imx477->hflip)
+		imx477->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+	imx477->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx477_ctrl_ops,
+					  V4L2_CID_VFLIP, 0, 1, 1, 0);
+	if (imx477->vflip)
+		imx477->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+	v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx477_ctrl_ops,
+				     V4L2_CID_TEST_PATTERN,
+				     ARRAY_SIZE(imx477_test_pattern_menu) - 1,
+				     0, 0, imx477_test_pattern_menu);
+	for (i = 0; i < 4; i++) {
+		/*
+		 * The assumption is that
+		 * V4L2_CID_TEST_PATTERN_GREENR == V4L2_CID_TEST_PATTERN_RED + 1
+		 * V4L2_CID_TEST_PATTERN_BLUE   == V4L2_CID_TEST_PATTERN_RED + 2
+		 * V4L2_CID_TEST_PATTERN_GREENB == V4L2_CID_TEST_PATTERN_RED + 3
+		 */
+		v4l2_ctrl_new_std(ctrl_hdlr, &imx477_ctrl_ops,
+				  V4L2_CID_TEST_PATTERN_RED + i,
+				  IMX477_TEST_PATTERN_COLOUR_MIN,
+				  IMX477_TEST_PATTERN_COLOUR_MAX,
+				  IMX477_TEST_PATTERN_COLOUR_STEP,
+				  IMX477_TEST_PATTERN_COLOUR_MAX);
+		/* The "Solid color" pattern is white by default */
+	}
+
+	if (ctrl_hdlr->error) {
+		ret = ctrl_hdlr->error;
+		dev_err(&client->dev, "%s control init failed (%d)\n",
+			__func__, ret);
+		goto error;
+	}
+
+	ret = v4l2_fwnode_device_parse(&client->dev, &props);
+	if (ret)
+		goto error;
+
+	ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx477_ctrl_ops,
+					      &props);
+	if (ret)
+		goto error;
+
+	imx477->sd.ctrl_handler = ctrl_hdlr;
+
+	mutex_lock(&imx477->mutex);
+
+	/* Setup exposure and frame/line length limits. */
+	imx477_set_framing_limits(imx477);
+
+	mutex_unlock(&imx477->mutex);
+
+	return 0;
+
+error:
+	v4l2_ctrl_handler_free(ctrl_hdlr);
+	mutex_destroy(&imx477->mutex);
+
+	return ret;
+}
+
+static void imx477_free_controls(struct imx477 *imx477)
+{
+	v4l2_ctrl_handler_free(imx477->sd.ctrl_handler);
+	mutex_destroy(&imx477->mutex);
+}
+
+static int imx477_check_link_freq(u64 link_frequency, u16 *mpy_out)
+{
+	u64 mpy = link_frequency * 2 * IMX477_IOP_SYSCK_DIV * IMX477_IOP_PREDIV;
+	u64 tmp;
+
+	do_div(mpy, IMX477_XCLK_FREQ);
+
+	tmp = mpy * (IMX477_XCLK_FREQ / IMX477_IOP_PREDIV);
+	do_div(tmp, IMX477_IOP_SYSCK_DIV * 2);
+
+	if (tmp != link_frequency)
+		return -EINVAL;
+
+	if (mpy_out)
+		*mpy_out = mpy;
+
+	return 0;
+}
+
+static int imx477_check_hwcfg(struct device *dev, struct imx477 *imx477)
+{
+	struct fwnode_handle *endpoint;
+	struct v4l2_fwnode_endpoint ep_cfg = {
+		.bus_type = V4L2_MBUS_CSI2_DPHY
+	};
+	int ret = -EINVAL;
+	int i;
+
+	endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
+	if (!endpoint) {
+		dev_err(dev, "endpoint node not found\n");
+		return -EINVAL;
+	}
+
+	if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) {
+		dev_err(dev, "could not parse endpoint\n");
+		goto error_out;
+	}
+
+	/* Check the number of MIPI CSI2 data lanes */
+	if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2) {
+		dev_err(dev, "only 2 data lanes are currently supported\n");
+		goto error_out;
+	}
+
+	/* Check the link frequency set in device tree */
+	if (!ep_cfg.nr_of_link_frequencies) {
+		dev_err(dev, "link-frequency property not found in DT\n");
+		goto error_out;
+	}
+
+	for (i = 0; i < ep_cfg.nr_of_link_frequencies; i++) {
+		if (!imx477_check_link_freq(ep_cfg.link_frequencies[i],
+					    &imx477->iop_pll_mpy)) {
+			imx477->link_freq_value = ep_cfg.link_frequencies[i];
+			break;
+		}
+	}
+
+	if (i == ep_cfg.nr_of_link_frequencies) {
+		dev_err(dev, "No supported link frequency configured\n");
+			ret = -EINVAL;
+			goto error_out;
+	}
+
+	ret = 0;
+
+error_out:
+	v4l2_fwnode_endpoint_free(&ep_cfg);
+	fwnode_handle_put(endpoint);
+
+	return ret;
+}
+
+static const struct imx477_compatible_data imx477_compatible = {
+	.chip_id = IMX477_CHIP_ID,
+	.extra_regs = {
+		.num_of_regs = 0,
+		.regs = NULL
+	}
+};
+
+static const struct cci_reg_sequence imx378_regs[] = {
+	{CCI_REG8(0x3e35), 0x01},
+	{CCI_REG8(0x4421), 0x08},
+	{CCI_REG8(0x3ff9), 0x00},
+};
+
+static const struct imx477_compatible_data imx378_compatible = {
+	.chip_id = IMX378_CHIP_ID,
+	.extra_regs = {
+		.num_of_regs = ARRAY_SIZE(imx378_regs),
+		.regs = imx378_regs
+	}
+};
+
+static const struct of_device_id imx477_dt_ids[] = {
+	{ .compatible = "sony,imx477", .data = &imx477_compatible },
+	{ .compatible = "sony,imx378", .data = &imx378_compatible },
+	{ /* sentinel */ }
+};
+
+static int imx477_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct imx477 *imx477;
+	const struct of_device_id *match;
+	int ret;
+	u32 tm_of;
+
+	imx477 = devm_kzalloc(&client->dev, sizeof(*imx477), GFP_KERNEL);
+	if (!imx477)
+		return -ENOMEM;
+
+	v4l2_i2c_subdev_init(&imx477->sd, client, &imx477_subdev_ops);
+
+	imx477->regmap = devm_cci_regmap_init_i2c(client, 16);
+	if (IS_ERR(imx477->regmap)) {
+		ret = PTR_ERR(imx477->regmap);
+		dev_err(&client->dev, "failed to initialize CCI: %d\n", ret);
+		return ret;
+	}
+
+	match = of_match_device(imx477_dt_ids, dev);
+	if (!match)
+		return -ENODEV;
+	imx477->compatible_data =
+		(const struct imx477_compatible_data *)match->data;
+
+	/* Check the hardware configuration in device tree */
+	if (imx477_check_hwcfg(dev, imx477))
+		return -EINVAL;
+
+	/* Default the trigger mode from OF to -1, which means invalid */
+	ret = of_property_read_u32(dev->of_node, "trigger-mode", &tm_of);
+	imx477->trigger_mode_of = (ret == 0) ? tm_of : -1;
+
+	/* Get system clock (xclk) */
+	imx477->xclk = devm_clk_get(dev, NULL);
+	if (IS_ERR(imx477->xclk)) {
+		dev_err(dev, "failed to get xclk\n");
+		return PTR_ERR(imx477->xclk);
+	}
+
+	imx477->xclk_freq = clk_get_rate(imx477->xclk);
+	if (imx477->xclk_freq != IMX477_XCLK_FREQ) {
+		dev_err(dev, "xclk frequency not supported: %d Hz\n",
+			imx477->xclk_freq);
+		return -EINVAL;
+	}
+
+	ret = imx477_get_regulators(imx477);
+	if (ret) {
+		dev_err(dev, "failed to get regulators\n");
+		return ret;
+	}
+
+	/* Request optional enable pin */
+	imx477->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+						     GPIOD_OUT_HIGH);
+
+	/*
+	 * The sensor must be powered for imx477_identify_module()
+	 * to be able to read the CHIP_ID register
+	 */
+	ret = imx477_power_on(dev);
+	if (ret)
+		return ret;
+
+	ret = imx477_identify_module(imx477, imx477->compatible_data->chip_id);
+	if (ret)
+		goto error_power_off;
+
+	/* Initialize default format */
+	imx477_set_default_format(imx477);
+
+	/*
+	 * Enable runtime PM with 5s autosuspend. As the device has been powered
+	 * manually, mark it as active, and increase the usage count without
+	 * resuming the device.
+	 */
+	pm_runtime_set_active(dev);
+	pm_runtime_get_noresume(dev);
+	pm_runtime_enable(dev);
+	pm_runtime_set_autosuspend_delay(dev, 5000);
+	pm_runtime_use_autosuspend(dev);
+
+	/* This needs the pm runtime to be registered. */
+	ret = imx477_init_controls(imx477);
+	if (ret)
+		goto error_power_off;
+
+	/* Initialize subdev */
+	imx477->sd.internal_ops = &imx477_internal_ops;
+	imx477->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
+			    V4L2_SUBDEV_FL_HAS_EVENTS;
+	imx477->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+
+	/* Initialize source pads */
+	imx477->pad[IMAGE_PAD].flags = MEDIA_PAD_FL_SOURCE;
+	imx477->pad[METADATA_PAD].flags = MEDIA_PAD_FL_SOURCE;
+
+	ret = media_entity_pads_init(&imx477->sd.entity, NUM_PADS, imx477->pad);
+	if (ret) {
+		dev_err(dev, "failed to init entity pads: %d\n", ret);
+		goto error_handler_free;
+	}
+
+	ret = v4l2_async_register_subdev_sensor(&imx477->sd);
+	if (ret < 0) {
+		dev_err(dev, "failed to register sensor sub-device: %d\n", ret);
+		goto error_media_entity;
+	}
+
+	pm_runtime_mark_last_busy(&client->dev);
+	pm_runtime_put_autosuspend(&client->dev);
+
+	return 0;
+
+error_media_entity:
+	media_entity_cleanup(&imx477->sd.entity);
+
+error_handler_free:
+	imx477_free_controls(imx477);
+
+error_power_off:
+	pm_runtime_disable(&client->dev);
+	pm_runtime_put_noidle(&client->dev);
+	imx477_power_off(&client->dev);
+
+	return ret;
+}
+
+static void imx477_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct imx477 *imx477 = to_imx477(sd);
+
+	v4l2_async_unregister_subdev(sd);
+	media_entity_cleanup(&sd->entity);
+	imx477_free_controls(imx477);
+
+	pm_runtime_disable(&client->dev);
+	if (!pm_runtime_status_suspended(&client->dev))
+		imx477_power_off(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+}
+
+MODULE_DEVICE_TABLE(of, imx477_dt_ids);
+
+static const struct dev_pm_ops imx477_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(imx477_suspend, imx477_resume)
+	SET_RUNTIME_PM_OPS(imx477_power_off, imx477_power_on, NULL)
+};
+
+static struct i2c_driver imx477_i2c_driver = {
+	.driver = {
+		.name = "imx477",
+		.of_match_table	= imx477_dt_ids,
+		.pm = &imx477_pm_ops,
+	},
+	.probe = imx477_probe,
+	.remove = imx477_remove,
+};
+
+module_i2c_driver(imx477_i2c_driver);
+
+MODULE_AUTHOR("Naushir Patuck <naush@raspberrypi.com>");
+MODULE_DESCRIPTION("Sony IMX477 sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/i2c/imx500.c b/drivers/media/i2c/imx500.c
new file mode 100644
index 000000000000..c5a0e1d1491b
--- /dev/null
+++ b/drivers/media/i2c/imx500.c
@@ -0,0 +1,3772 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A V4L2 driver for Sony IMX500 cameras.
+ * Copyright (C) 2024, Raspberry Pi Ltd
+ */
+#include <linux/unaligned.h>
+#include <linux/clk.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/earlycpio.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/kernel_read_file.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/vmalloc.h>
+#include <media/v4l2-cci.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mediabus.h>
+
+/* Chip ID */
+#define IMX500_REG_CHIP_ID CCI_REG16(0x0016)
+#define IMX500_CHIP_ID 0x0500
+
+#define IMX500_REG_MODE_SELECT CCI_REG8(0x0100)
+#define IMX500_MODE_STANDBY 0x00
+#define IMX500_MODE_STREAMING 0x01
+
+#define IMX500_REG_IMAGE_ONLY_MODE CCI_REG8(0xa700)
+#define IMX500_IMAGE_ONLY_FALSE 0x00
+#define IMX500_IMAGE_ONLY_TRUE 0x01
+
+#define IMX500_REG_SENSOR_TEMP_CTRL CCI_REG8(0x0138)
+
+#define IMX500_REG_ORIENTATION CCI_REG8(0x101)
+
+#define IMX500_XCLK_FREQ 24000000
+
+#define IMX500_DEFAULT_LINK_FREQ 444000000
+
+#define IMX500_PIXEL_RATE 744000000
+
+/* V_TIMING internal */
+#define IMX500_REG_FRAME_LENGTH CCI_REG16(0x0340)
+#define IMX500_FRAME_LENGTH_MAX 0xffdc
+#define IMX500_VBLANK_MIN 1117
+
+/* H_TIMING internal */
+#define IMX500_REG_LINE_LENGTH CCI_REG16(0x0342)
+#define IMX500_LINE_LENGTH_MAX 0xfff0
+
+/* Long exposure multiplier */
+#define IMX500_LONG_EXP_SHIFT_MAX 7
+#define IMX500_LONG_EXP_SHIFT_REG CCI_REG8(0x3210)
+#define IMX500_LONG_EXP_CIT_SHIFT_REG CCI_REG8(0x3100)
+
+/* Exposure control */
+#define IMX500_REG_EXPOSURE CCI_REG16(0x0202)
+#define IMX500_EXPOSURE_OFFSET 22
+#define IMX500_EXPOSURE_MIN 8
+#define IMX500_EXPOSURE_STEP 1
+#define IMX500_EXPOSURE_DEFAULT 0x640
+#define IMX500_EXPOSURE_MAX (IMX500_FRAME_LENGTH_MAX - IMX500_EXPOSURE_OFFSET)
+
+/* Analog gain control */
+#define IMX500_REG_ANALOG_GAIN CCI_REG16(0x0204)
+#define IMX500_ANA_GAIN_MIN 0
+#define IMX500_ANA_GAIN_MAX 978
+#define IMX500_ANA_GAIN_STEP 1
+#define IMX500_ANA_GAIN_DEFAULT 0x0
+
+/* Inference windows */
+#define IMX500_REG_DWP_AP_VC_VOFF CCI_REG16(0xD500)
+#define IMX500_REG_DWP_AP_VC_HOFF CCI_REG16(0xD502)
+#define IMX500_REG_DWP_AP_VC_VSIZE CCI_REG16(0xD504)
+#define IMX500_REG_DWP_AP_VC_HSIZE CCI_REG16(0xD506)
+
+#define IMX500_REG_DD_CH06_X_OUT_SIZE \
+	CCI_REG16(0x3054) /* Output pixel count for KPI */
+#define IMX500_REG_DD_CH07_X_OUT_SIZE \
+	CCI_REG16(0x3056) /* Output pixel count for Input Tensor */
+#define IMX500_REG_DD_CH08_X_OUT_SIZE \
+	CCI_REG16(0x3058) /* Output pixel count for Output Tensor */
+#define IMX500_REG_DD_CH09_X_OUT_SIZE \
+	CCI_REG16(0x305A) /* Output pixel count for PQ Settings */
+
+#define IMX500_REG_DD_CH06_Y_OUT_SIZE \
+	CCI_REG16(0x305C) /* Output line count for KPI */
+#define IMX500_REG_DD_CH07_Y_OUT_SIZE \
+	CCI_REG16(0x305E) /* Output line count for Input Tensor */
+#define IMX500_REG_DD_CH08_Y_OUT_SIZE \
+	CCI_REG16(0x3060) /* Output line count for Output Tensor */
+#define IMX500_REG_DD_CH09_Y_OUT_SIZE \
+	CCI_REG16(0x3062) /* Output line count for PQ Settings */
+
+#define IMX500_REG_DD_CH06_VCID \
+	CCI_REG8(0x3064) /* Virtual channel ID for KPI */
+#define IMX500_REG_DD_CH07_VCID \
+	CCI_REG8(0x3065) /* Virtual channel ID for Input Tensor */
+#define IMX500_REG_DD_CH08_VCID \
+	CCI_REG8(0x3066) /* Virtual channel ID for Output Tensor */
+#define IMX500_REG_DD_CH09_VCID \
+	CCI_REG8(0x3067) /* Virtual channel ID for PQ Settings */
+
+#define IMX500_REG_DD_CH06_DT CCI_REG8(0x3068) /* Data Type for KPI */
+#define IMX500_REG_DD_CH07_DT CCI_REG8(0x3069) /* Data Type for Input Tensor */
+#define IMX500_REG_DD_CH08_DT CCI_REG8(0x306A) /* Data Type for Output Tensor */
+#define IMX500_REG_DD_CH09_DT CCI_REG8(0x306B) /* Data Type for PQ Settings */
+
+#define IMX500_REG_DD_CH06_PACKING \
+	CCI_REG8(0x306C) /* Pixel/byte packing for KPI */
+#define IMX500_REG_DD_CH07_PACKING \
+	CCI_REG8(0x306D) /* Pixel/byte packing for Input Tensor */
+#define IMX500_REG_DD_CH08_PACKING \
+	CCI_REG8(0x306E) /* Pixel/byte packing for Output Tensor */
+#define IMX500_REG_DD_CH09_PACKING \
+	CCI_REG8(0x306F) /* Pixel/byte packing for PQ Settings */
+#define IMX500_DD_PACKING_8BPP 2 /* 8 bits/pixel */
+#define IMX500_DD_PACKING_10BPP 3 /* 10 bits/pixel */
+
+/* Interrupt command (start processing command inside IMX500 CPU) */
+#define IMX500_REG_DD_CMD_INT CCI_REG8(0x3080)
+#define IMX500_DD_CMD_INT_ST_TRANS 0
+#define IMX500_DD_CMD_INT_UPDATE 1
+#define IMX500_DD_CMD_INT_FLASH_ERASE 2
+
+/* State transition command type */
+#define IMX500_REG_DD_ST_TRANS_CMD CCI_REG8(0xD000)
+#define IMX500_DD_ST_TRANS_CMD_LOADER_FW 0
+#define IMX500_DD_ST_TRANS_CMD_MAIN_FW 1
+#define IMX500_DD_ST_TRANS_CMD_NW_WEIGHTS 2
+#define IMX500_DD_ST_TRANS_CMD_CLEAR_WEIGHTS 3
+
+/* Network weights update command */
+#define IMX500_REG_DD_UPDATE_CMD CCI_REG8(0xD001)
+#define IMX500_DD_UPDATE_CMD_SRAM 0
+#define IMX500_DD_UPDATE_CMD_FLASH 1
+
+/* Transfer source when loading into RAM */
+#define IMX500_REG_DD_LOAD_MODE CCI_REG8(0xD002)
+#define IMX500_DD_LOAD_MODE_AP 0
+#define IMX500_DD_LOAD_MODE_FLASH 1
+
+/* Image type to transfer */
+#define IMX500_REG_DD_IMAGE_TYPE CCI_REG8(0xD003)
+#define IMX500_DD_IMAGE_TYPE_LOADER_FW 0
+#define IMX500_DD_IMAGE_TYPE_MAIN_FW 1
+#define IMX500_DD_IMAGE_TYPE_NETWORK_WEIGHTS 2
+
+/* Number of divisions of download image file */
+#define IMX500_REG_DD_DOWNLOAD_DIV_NUM CCI_REG8(0xD004)
+
+#define IMX500_REG_DD_FLASH_TYPE CCI_REG8(0xD005)
+
+/* total size of download file (4-byte) */
+#define IMX500_REG_DD_DOWNLOAD_FILE_SIZE CCI_REG32(0xD008)
+
+/* Status notification (4-byte) */
+#define IMX500_REG_DD_REF_STS CCI_REG32(0xD010)
+#define IMX500_DD_REF_STS_FATAL 0xFF
+#define IMX500_DD_REF_STS_DETECT_CNT 0xFF00
+#define IMX500_DD_REF_STS_ERR_CNT 0xFF0000
+#define IMX500_DD_REF_CMD_REPLY_CNT 0xFF000000
+
+/* Command reply status */
+#define IMX500_REG_DD_CMD_REPLY_STS CCI_REG8(0xD014)
+#define IMX500_DD_CMD_REPLY_STS_TRANS_READY 0x00
+#define IMX500_DD_CMD_REPLY_STS_TRANS_DONE 0x01
+#define IMX500_DD_CMD_REPLY_STS_UPDATE_READY 0x10
+#define IMX500_DD_CMD_REPLY_STS_UPDATE_DONE 0x11
+#define IMX500_DD_CMD_REPLY_STS_UPDATE_CANCEL_DONE 0x12
+#define IMX500_DD_CMD_REPLY_STS_STATUS_ERROR 0xFF
+#define IMX500_DD_CMD_REPLY_STS_MAC_AUTH_ERROR 0xFE
+#define IMX500_DD_CMD_REPLY_STS_TIMEOUT_ERROR 0xFD
+#define IMX500_DD_CMD_REPLY_STS_PARAMETER_ERROR 0xFC
+#define IMX500_DD_CMD_REPLY_STS_INTERNAL_ERROR 0xFB
+#define IMX500_DD_CMD_REPLY_STS_PACKET_FMT_ERROR 0xFA
+
+/* Download status */
+#define IMX500_REG_DD_DOWNLOAD_STS CCI_REG8(0xD015)
+#define IMX500_DD_DOWNLOAD_STS_READY 0
+#define IMX500_DD_DOWNLOAD_STS_DOWNLOADING 1
+
+/* Update cancel */
+#define IMX500_REG_DD_UPDATE_CANCEL CCI_REG8(0xD016)
+#define IMX500_DD_UPDATE_CANCEL_NOT_CANCEL 0
+#define IMX500_DD_UPDATE_CANCEL_DO_CANCEL 1
+
+/* Notify error status */
+#define IMX500_REG_DD_ERR_STS CCI_REG8(0xD020)
+#define IMX500_DD_ERR_STS_STATUS_ERROR_BIT 0x1
+#define IMX500_DD_ERR_STS_INTERNAL_ERROR_BIT 0x2
+#define IMX500_DD_ERR_STS_PARAMETER_ERROR_BIT 0x4
+
+/* System state */
+#define IMX500_REG_DD_SYS_STATE CCI_REG8(0xD02A)
+#define IMX500_DD_SYS_STATE_STANDBY_NO_NETWORK 0
+#define IMX500_DD_SYS_STATE_STEAMING_NO_NETWORK 1
+#define IMX500_DD_SYS_STATE_STANDBY_WITH_NETWORK 2
+#define IMX500_DD_SYS_STATE_STREAMING_WITH_NETWORK 3
+
+#define IMX500_REG_MAIN_FW_VERSION CCI_REG32(0xD07C)
+
+/* Input tensor injection */
+#define IMX500_REG_DD_DAT_INJECTION_CHKSUM CCI_REG32(0xD0D4)
+#define IMX500_REG_DD_DAT_INJECTION_SPI_CMP_FRM CCI_REG8(0xD0DA)
+#define IMX500_REG_DD_DAT_INJECTION_HNDSK CCI_REG8(0xD0DB)
+#define IMX500_DD_DAT_INJECTION_HNDSK_TRANS_INIT_WAIT 1
+#define IMX500_DD_DAT_INJECTION_HNDSK_TRANS_INIT_COMP 2
+#define IMX500_DD_DAT_INJECTION_HNDSK_TRANS_READY_WAIT 3
+#define IMX500_DD_DAT_INJECTION_HNDSK_TRANS_READY_COMP 4
+#define IMX500_DD_DAT_INJECTION_HNDSK_TRANSFERRING 5
+#define IMX500_DD_DAT_INJECTION_HNDSK_TRANS_COMP_WAIT 6
+#define IMX500_DD_DAT_INJECTION_HNDSK_TRANS_COMP 7
+#define IMX500_DD_DAT_INJECTION_HNDSK_TRANS_ERROR 8
+#define IMX500_DD_DAT_INJECTION_HNDSK_TRANS_ERROR_WAIT_NOTIFY 9
+#define IMX500_DD_DAT_INJECTION_HNDSK_TRANS_COMP_WAIT_CHKSUM 10
+
+#define IMX500_REG_STRM_MODE_SEL CCI_REG8(0xD100)
+
+#define IMX500_REG_LEV_PL_MDET_OUT CCI_REG8(0xD641)
+
+/* Colour balance controls */
+#define IMX500_REG_COLOUR_BALANCE_R CCI_REG16(0xd804)
+#define IMX500_REG_COLOUR_BALANCE_GR CCI_REG16(0xd806)
+#define IMX500_REG_COLOUR_BALANCE_GB CCI_REG16(0xd808)
+#define IMX500_REG_COLOUR_BALANCE_B CCI_REG16(0xd80a)
+#define IMX500_COLOUR_BALANCE_MIN 0x0001
+#define IMX500_COLOUR_BALANCE_MAX 0x0fff
+#define IMX500_COLOUR_BALANCE_STEP 0x0001
+#define IMX500_COLOUR_BALANCE_DEFAULT 0x0100
+
+/* Embedded sizes */
+#define IMX500_MAX_EMBEDDED_SIZE \
+	(2 * ((((IMX500_PIXEL_ARRAY_WIDTH * 10) >> 3) + 15) & ~15))
+
+/* Inference sizes */
+#define IMX500_INFERENCE_LINE_WIDTH 2560
+#define IMX500_NUM_KPI_LINES 1
+#define IMX500_NUM_PQ_LINES 1
+
+/* IMX500 native and active pixel array size. */
+#define IMX500_NATIVE_WIDTH 4072U
+#define IMX500_NATIVE_HEIGHT 3176U
+#define IMX500_PIXEL_ARRAY_LEFT 8U
+#define IMX500_PIXEL_ARRAY_TOP 16U
+#define IMX500_PIXEL_ARRAY_WIDTH 4056U
+#define IMX500_PIXEL_ARRAY_HEIGHT 3040U
+
+enum pad_types { IMAGE_PAD, METADATA_PAD, NUM_PADS };
+
+#define V4L2_CID_USER_IMX500_INFERENCE_WINDOW (V4L2_CID_USER_IMX500_BASE + 0)
+#define V4L2_CID_USER_IMX500_NETWORK_FW_FD (V4L2_CID_USER_IMX500_BASE + 1)
+#define V4L2_CID_USER_GET_IMX500_DEVICE_ID (V4L2_CID_USER_IMX500_BASE + 2)
+#define V4L2_CID_USER_IMX500_ENABLE_INJECTION (V4L2_CID_USER_IMX500_BASE + 3)
+#define V4L2_CID_USER_IMX500_INPUT_TENSOR_FD (V4L2_CID_USER_IMX500_BASE + 4)
+#define V4L2_CID_USER_IMX500_INJECTION_CMP_FRM (V4L2_CID_USER_IMX500_BASE + 5)
+
+#define ONE_MIB (1024 * 1024)
+
+/* regulator supplies */
+static const char *const imx500_supply_name[] = {
+	/* Supplies can be enabled in any order */
+	"vana", /* Analog (2.7V) supply */
+	"vdig", /* Digital Core (0.84V) supply */
+	"vif", /* Interface (1.8V) supply */
+};
+
+#define IMX500_NUM_SUPPLIES ARRAY_SIZE(imx500_supply_name)
+
+enum imx500_image_type {
+	TYPE_LOADER = 0,
+	TYPE_MAIN = 1,
+	TYPE_NW_WEIGHTS = 2,
+	TYPE_MAX
+};
+
+struct imx500_reg_list {
+	unsigned int num_of_regs;
+	const struct cci_reg_sequence *regs;
+};
+
+/* Mode : resolution and related config&values */
+struct imx500_mode {
+	/* Frame width */
+	unsigned int width;
+
+	/* Frame height */
+	unsigned int height;
+
+	/* H-timing in pixels */
+	unsigned int line_length_pix;
+
+	/* Analog crop rectangle. */
+	struct v4l2_rect crop;
+
+	/* Default register values */
+	struct imx500_reg_list reg_list;
+};
+
+static const struct cci_reg_sequence mode_common_regs[] = {
+	{ CCI_REG8(0x0305), 0x02 },
+	{ CCI_REG8(0x0306), 0x00 },
+	{ CCI_REG8(0x030d), 0x02 },
+	{ CCI_REG8(0x030e), 0x00 },
+	{ CCI_REG8(0x0106), 0x01 }, /* FAST_STANDBY_CTL */
+	{ CCI_REG8(0x0136), 0x1b }, /* EXCLK_FREQ */
+	{ CCI_REG8(0x0137), 0x00 },
+	{ CCI_REG8(0x0112), 0x0a },
+	{ CCI_REG8(0x0113), 0x0a },
+	{ CCI_REG8(0x0114), 0x01 }, /* CSI_LANE_MODE */
+	{ CCI_REG16(0x3054), IMX500_INFERENCE_LINE_WIDTH },
+	{ CCI_REG16(0x3056), IMX500_INFERENCE_LINE_WIDTH },
+	{ CCI_REG16(0x3058), IMX500_INFERENCE_LINE_WIDTH },
+	{ CCI_REG16(0x305A), IMX500_INFERENCE_LINE_WIDTH }, /* X_OUT */
+	{ CCI_REG16(0x305C), IMX500_NUM_KPI_LINES }, /* KPI Y_OUT */
+	{ CCI_REG16(0x3062), IMX500_NUM_PQ_LINES }, /* PQ Y_OUT */
+	{ CCI_REG8(0x3068), 0x30 },
+	{ CCI_REG8(0x3069), 0x31 },
+	{ CCI_REG8(0x306A), 0x32 },
+	{ CCI_REG8(0x306B), 0x33 }, /* Data Types */
+};
+
+/* 12 mpix 15fps */
+static const struct cci_reg_sequence mode_4056x3040_regs[] = {
+	{ CCI_REG8(0x0340), 0x12 },
+	{ CCI_REG8(0x0341), 0x42 },
+	{ CCI_REG8(0x0342), 0x45 },
+	{ CCI_REG8(0x0343), 0xec },
+	{ CCI_REG8(0x3210), 0x00 },
+	{ CCI_REG8(0x0344), 0x00 },
+	{ CCI_REG8(0x0345), 0x00 },
+	{ CCI_REG8(0x0346), 0x00 },
+	{ CCI_REG8(0x0347), 0x00 },
+	{ CCI_REG8(0x0348), 0x0f },
+	{ CCI_REG8(0x0349), 0xd7 },
+	{ CCI_REG8(0x0350), 0x00 },
+	{ CCI_REG8(0x034a), 0x0b },
+	{ CCI_REG8(0x034b), 0xdf },
+	{ CCI_REG8(0x3f58), 0x01 },
+	{ CCI_REG8(0x0381), 0x01 },
+	{ CCI_REG8(0x0383), 0x01 },
+	{ CCI_REG8(0x0385), 0x01 },
+	{ CCI_REG8(0x0387), 0x01 },
+	{ CCI_REG8(0x0900), 0x00 },
+	{ CCI_REG8(0x0901), 0x11 },
+	{ CCI_REG8(0x0902), 0x00 },
+	{ CCI_REG8(0x3241), 0x11 },
+	{ CCI_REG8(0x3242), 0x01 },
+	{ CCI_REG8(0x3250), 0x00 },
+	{ CCI_REG8(0x3f0f), 0x00 },
+	{ CCI_REG8(0x3f40), 0x00 },
+	{ CCI_REG8(0x3f41), 0x00 },
+	{ CCI_REG8(0x3f42), 0x00 },
+	{ CCI_REG8(0x3f43), 0x00 },
+	{ CCI_REG8(0xb34e), 0x00 },
+	{ CCI_REG8(0xb351), 0x20 },
+	{ CCI_REG8(0xb35c), 0x00 },
+	{ CCI_REG8(0xb35e), 0x08 },
+	{ CCI_REG8(0x0401), 0x00 },
+	{ CCI_REG8(0x0404), 0x00 },
+	{ CCI_REG8(0x0405), 0x10 },
+	{ CCI_REG8(0x0408), 0x00 },
+	{ CCI_REG8(0x0409), 0x00 },
+	{ CCI_REG8(0x040a), 0x00 },
+	{ CCI_REG8(0x040b), 0x00 },
+	{ CCI_REG8(0x040c), 0x0f },
+	{ CCI_REG8(0x040d), 0xd8 },
+	{ CCI_REG8(0x040e), 0x0b },
+	{ CCI_REG8(0x040f), 0xe0 },
+	{ CCI_REG8(0x034c), 0x0f },
+	{ CCI_REG8(0x034d), 0xd8 },
+	{ CCI_REG8(0x034e), 0x0b },
+	{ CCI_REG8(0x034f), 0xe0 },
+	{ CCI_REG8(0x0301), 0x05 },
+	{ CCI_REG8(0x0303), 0x02 },
+	{ CCI_REG8(0x0307), 0x9b },
+	{ CCI_REG8(0x0309), 0x0a },
+	{ CCI_REG8(0x030b), 0x01 },
+	{ CCI_REG8(0x030f), 0x4a },
+	{ CCI_REG8(0x0310), 0x01 },
+	{ CCI_REG8(0x0820), 0x07 },
+	{ CCI_REG8(0x0821), 0xce },
+	{ CCI_REG8(0x0822), 0x00 },
+	{ CCI_REG8(0x0823), 0x00 },
+	{ CCI_REG8(0x3e20), 0x01 },
+	{ CCI_REG8(0x3e35), 0x01 },
+	{ CCI_REG8(0x3e36), 0x01 },
+	{ CCI_REG8(0x3e37), 0x00 },
+	{ CCI_REG8(0x3e3a), 0x01 },
+	{ CCI_REG8(0x3e3b), 0x00 },
+	{ CCI_REG8(0x00e3), 0x00 },
+	{ CCI_REG8(0x00e4), 0x00 },
+	{ CCI_REG8(0x00e6), 0x00 },
+	{ CCI_REG8(0x00e7), 0x00 },
+	{ CCI_REG8(0x00e8), 0x00 },
+	{ CCI_REG8(0x00e9), 0x00 },
+	{ CCI_REG8(0x3f50), 0x00 },
+	{ CCI_REG8(0x3f56), 0x02 },
+	{ CCI_REG8(0x3f57), 0x42 },
+	{ CCI_REG8(0x3606), 0x01 },
+	{ CCI_REG8(0x3607), 0x01 },
+	{ CCI_REG8(0x3f26), 0x00 },
+	{ CCI_REG8(0x3f4a), 0x00 },
+	{ CCI_REG8(0x3f4b), 0x00 },
+	{ CCI_REG8(0x4bc0), 0x16 },
+	{ CCI_REG8(0x7ba8), 0x00 },
+	{ CCI_REG8(0x7ba9), 0x00 },
+	{ CCI_REG8(0x886b), 0x00 },
+	{ CCI_REG8(0x579a), 0x00 },
+	{ CCI_REG8(0x579b), 0x0a },
+	{ CCI_REG8(0x579c), 0x01 },
+	{ CCI_REG8(0x579d), 0x2a },
+	{ CCI_REG8(0x57ac), 0x00 },
+	{ CCI_REG8(0x57ad), 0x00 },
+	{ CCI_REG8(0x57ae), 0x00 },
+	{ CCI_REG8(0x57af), 0x81 },
+	{ CCI_REG8(0x57be), 0x00 },
+	{ CCI_REG8(0x57bf), 0x00 },
+	{ CCI_REG8(0x57c0), 0x00 },
+	{ CCI_REG8(0x57c1), 0x81 },
+	{ CCI_REG8(0x57d0), 0x00 },
+	{ CCI_REG8(0x57d1), 0x00 },
+	{ CCI_REG8(0x57d2), 0x00 },
+	{ CCI_REG8(0x57d3), 0x81 },
+	{ CCI_REG8(0x5324), 0x00 },
+	{ CCI_REG8(0x5325), 0x26 },
+	{ CCI_REG8(0x5326), 0x00 },
+	{ CCI_REG8(0x5327), 0x6b },
+	{ CCI_REG8(0xbca7), 0x00 },
+	{ CCI_REG8(0x5fcc), 0x28 },
+	{ CCI_REG8(0x5fd7), 0x2d },
+	{ CCI_REG8(0x5fe2), 0x2d },
+	{ CCI_REG8(0x5fed), 0x2d },
+	{ CCI_REG8(0x5ff8), 0x2d },
+	{ CCI_REG8(0x6003), 0x2d },
+	{ CCI_REG8(0x5d0b), 0x01 },
+	{ CCI_REG8(0x6f6d), 0x00 },
+	{ CCI_REG8(0x61c9), 0x00 },
+	{ CCI_REG8(0x5352), 0x00 },
+	{ CCI_REG8(0x5353), 0x49 },
+	{ CCI_REG8(0x5356), 0x00 },
+	{ CCI_REG8(0x5357), 0x30 },
+	{ CCI_REG8(0x5358), 0x00 },
+	{ CCI_REG8(0x5359), 0x3b },
+	{ CCI_REG8(0x535c), 0x00 },
+	{ CCI_REG8(0x535d), 0xb0 },
+	{ CCI_REG8(0x6187), 0x18 },
+	{ CCI_REG8(0x6189), 0x18 },
+	{ CCI_REG8(0x618b), 0x18 },
+	{ CCI_REG8(0x618d), 0x1d },
+	{ CCI_REG8(0x618f), 0x1d },
+	{ CCI_REG8(0x5414), 0x01 },
+	{ CCI_REG8(0x5415), 0x0c },
+	{ CCI_REG8(0xbca8), 0x0a },
+	{ CCI_REG8(0x5fcf), 0x1e },
+	{ CCI_REG8(0x5fda), 0x1e },
+	{ CCI_REG8(0x5fe5), 0x1e },
+	{ CCI_REG8(0x5ff0), 0x1e },
+	{ CCI_REG8(0x5ffb), 0x1e },
+	{ CCI_REG8(0x6006), 0x1e },
+	{ CCI_REG8(0x616e), 0x04 },
+	{ CCI_REG8(0x616f), 0x04 },
+	{ CCI_REG8(0x6170), 0x04 },
+	{ CCI_REG8(0x6171), 0x06 },
+	{ CCI_REG8(0x6172), 0x06 },
+	{ CCI_REG8(0x6173), 0x0c },
+	{ CCI_REG8(0x6174), 0x0c },
+	{ CCI_REG8(0x6175), 0x0c },
+	{ CCI_REG8(0x6176), 0x00 },
+	{ CCI_REG8(0x6177), 0x10 },
+	{ CCI_REG8(0x6178), 0x00 },
+	{ CCI_REG8(0x6179), 0x1a },
+	{ CCI_REG8(0x617a), 0x00 },
+	{ CCI_REG8(0x617b), 0x1a },
+	{ CCI_REG8(0x617c), 0x00 },
+	{ CCI_REG8(0x617d), 0x27 },
+	{ CCI_REG8(0x617e), 0x00 },
+	{ CCI_REG8(0x617f), 0x27 },
+	{ CCI_REG8(0x6180), 0x00 },
+	{ CCI_REG8(0x6181), 0x44 },
+	{ CCI_REG8(0x6182), 0x00 },
+	{ CCI_REG8(0x6183), 0x44 },
+	{ CCI_REG8(0x6184), 0x00 },
+	{ CCI_REG8(0x6185), 0x44 },
+	{ CCI_REG8(0x5dfc), 0x0a },
+	{ CCI_REG8(0x5e00), 0x0a },
+	{ CCI_REG8(0x5e04), 0x0a },
+	{ CCI_REG8(0x5e08), 0x0a },
+	{ CCI_REG8(0x5dfd), 0x0a },
+	{ CCI_REG8(0x5e01), 0x0a },
+	{ CCI_REG8(0x5e05), 0x0a },
+	{ CCI_REG8(0x5e09), 0x0a },
+	{ CCI_REG8(0x5dfe), 0x0a },
+	{ CCI_REG8(0x5e02), 0x0a },
+	{ CCI_REG8(0x5e06), 0x0a },
+	{ CCI_REG8(0x5e0a), 0x0a },
+	{ CCI_REG8(0x5dff), 0x0a },
+	{ CCI_REG8(0x5e03), 0x0a },
+	{ CCI_REG8(0x5e07), 0x0a },
+	{ CCI_REG8(0x5e0b), 0x0a },
+	{ CCI_REG8(0x5dec), 0x12 },
+	{ CCI_REG8(0x5df0), 0x12 },
+	{ CCI_REG8(0x5df4), 0x21 },
+	{ CCI_REG8(0x5df8), 0x31 },
+	{ CCI_REG8(0x5ded), 0x12 },
+	{ CCI_REG8(0x5df1), 0x12 },
+	{ CCI_REG8(0x5df5), 0x21 },
+	{ CCI_REG8(0x5df9), 0x31 },
+	{ CCI_REG8(0x5dee), 0x12 },
+	{ CCI_REG8(0x5df2), 0x12 },
+	{ CCI_REG8(0x5df6), 0x21 },
+	{ CCI_REG8(0x5dfa), 0x31 },
+	{ CCI_REG8(0x5def), 0x12 },
+	{ CCI_REG8(0x5df3), 0x12 },
+	{ CCI_REG8(0x5df7), 0x21 },
+	{ CCI_REG8(0x5dfb), 0x31 },
+	{ CCI_REG8(0x5ddc), 0x0d },
+	{ CCI_REG8(0x5de0), 0x0d },
+	{ CCI_REG8(0x5de4), 0x0d },
+	{ CCI_REG8(0x5de8), 0x0d },
+	{ CCI_REG8(0x5ddd), 0x0d },
+	{ CCI_REG8(0x5de1), 0x0d },
+	{ CCI_REG8(0x5de5), 0x0d },
+	{ CCI_REG8(0x5de9), 0x0d },
+	{ CCI_REG8(0x5dde), 0x0d },
+	{ CCI_REG8(0x5de2), 0x0d },
+	{ CCI_REG8(0x5de6), 0x0d },
+	{ CCI_REG8(0x5dea), 0x0d },
+	{ CCI_REG8(0x5ddf), 0x0d },
+	{ CCI_REG8(0x5de3), 0x0d },
+	{ CCI_REG8(0x5de7), 0x0d },
+	{ CCI_REG8(0x5deb), 0x0d },
+	{ CCI_REG8(0x5dcc), 0x55 },
+	{ CCI_REG8(0x5dd0), 0x50 },
+	{ CCI_REG8(0x5dd4), 0x4b },
+	{ CCI_REG8(0x5dd8), 0x4b },
+	{ CCI_REG8(0x5dcd), 0x55 },
+	{ CCI_REG8(0x5dd1), 0x50 },
+	{ CCI_REG8(0x5dd5), 0x4b },
+	{ CCI_REG8(0x5dd9), 0x4b },
+	{ CCI_REG8(0x5dce), 0x55 },
+	{ CCI_REG8(0x5dd2), 0x50 },
+	{ CCI_REG8(0x5dd6), 0x4b },
+	{ CCI_REG8(0x5dda), 0x4b },
+	{ CCI_REG8(0x5dcf), 0x55 },
+	{ CCI_REG8(0x5dd3), 0x50 },
+	{ CCI_REG8(0x5dd7), 0x4b },
+	{ CCI_REG8(0x5ddb), 0x4b },
+	{ CCI_REG8(0x0202), 0x12 },
+	{ CCI_REG8(0x0203), 0x2c },
+	{ CCI_REG8(0x0204), 0x00 },
+	{ CCI_REG8(0x0205), 0x00 },
+	{ CCI_REG8(0x020e), 0x01 },
+	{ CCI_REG8(0x020f), 0x00 },
+	{ CCI_REG8(0x0210), 0x01 },
+	{ CCI_REG8(0x0211), 0x00 },
+	{ CCI_REG8(0x0212), 0x01 },
+	{ CCI_REG8(0x0213), 0x00 },
+	{ CCI_REG8(0x0214), 0x01 },
+	{ CCI_REG8(0x0215), 0x00 },
+};
+
+/* 2x2 binned. 56fps */
+static const struct cci_reg_sequence mode_2028x1520_regs[] = {
+	{ CCI_REG8(0x0112), 0x0a },
+	{ CCI_REG8(0x0113), 0x0a },
+	{ CCI_REG8(0x0114), 0x01 },
+	{ CCI_REG8(0x0342), 0x24 },
+	{ CCI_REG8(0x0343), 0xb6 },
+	{ CCI_REG8(0x0340), 0x0b },
+	{ CCI_REG8(0x0341), 0x9c },
+	{ CCI_REG8(0x3210), 0x00 },
+	{ CCI_REG8(0x0344), 0x00 },
+	{ CCI_REG8(0x0345), 0x00 },
+	{ CCI_REG8(0x0346), 0x00 },
+	{ CCI_REG8(0x0347), 0x00 },
+	{ CCI_REG8(0x0348), 0x0f },
+	{ CCI_REG8(0x0349), 0xd7 },
+	{ CCI_REG8(0x0350), 0x00 },
+	{ CCI_REG8(0x034a), 0x0b },
+	{ CCI_REG8(0x034b), 0xdf },
+	{ CCI_REG8(0x3f58), 0x01 },
+	{ CCI_REG8(0x0381), 0x01 },
+	{ CCI_REG8(0x0383), 0x01 },
+	{ CCI_REG8(0x0385), 0x01 },
+	{ CCI_REG8(0x0387), 0x01 },
+	{ CCI_REG8(0x0900), 0x01 },
+	{ CCI_REG8(0x0901), 0x22 },
+	{ CCI_REG8(0x0902), 0x02 },
+	{ CCI_REG8(0x3241), 0x11 },
+	{ CCI_REG8(0x3242), 0x01 },
+	{ CCI_REG8(0x3250), 0x03 },
+	{ CCI_REG8(0x3f0f), 0x00 },
+	{ CCI_REG8(0x3f40), 0x00 },
+	{ CCI_REG8(0x3f41), 0x00 },
+	{ CCI_REG8(0x3f42), 0x00 },
+	{ CCI_REG8(0x3f43), 0x00 },
+	{ CCI_REG8(0xb34e), 0x00 },
+	{ CCI_REG8(0xb351), 0x20 },
+	{ CCI_REG8(0xb35c), 0x00 },
+	{ CCI_REG8(0xb35e), 0x08 },
+	{ CCI_REG8(0x0401), 0x00 },
+	{ CCI_REG8(0x0404), 0x00 },
+	{ CCI_REG8(0x0405), 0x10 },
+	{ CCI_REG8(0x0408), 0x00 },
+	{ CCI_REG8(0x0409), 0x00 },
+	{ CCI_REG8(0x040a), 0x00 },
+	{ CCI_REG8(0x040b), 0x00 },
+	{ CCI_REG8(0x040c), 0x07 },
+	{ CCI_REG8(0x040d), 0xec },
+	{ CCI_REG8(0x040e), 0x05 },
+	{ CCI_REG8(0x040f), 0xf0 },
+	{ CCI_REG8(0x034c), 0x07 },
+	{ CCI_REG8(0x034d), 0xec },
+	{ CCI_REG8(0x034e), 0x05 },
+	{ CCI_REG8(0x034f), 0xf0 },
+	{ CCI_REG8(0x0301), 0x05 },
+	{ CCI_REG8(0x0303), 0x02 },
+	{ CCI_REG8(0x0307), 0x9b },
+	{ CCI_REG8(0x0309), 0x0a },
+	{ CCI_REG8(0x030b), 0x01 },
+	{ CCI_REG8(0x030f), 0x4a },
+	{ CCI_REG8(0x0310), 0x01 },
+	{ CCI_REG8(0x0820), 0x07 },
+	{ CCI_REG8(0x0821), 0xce },
+	{ CCI_REG8(0x0822), 0x00 },
+	{ CCI_REG8(0x0823), 0x00 },
+	{ CCI_REG8(0x3e20), 0x01 },
+	{ CCI_REG8(0x3e35), 0x01 },
+	{ CCI_REG8(0x3e36), 0x01 },
+	{ CCI_REG8(0x3e37), 0x00 },
+	{ CCI_REG8(0x3e3a), 0x01 },
+	{ CCI_REG8(0x3e3b), 0x00 },
+	{ CCI_REG8(0x00e3), 0x00 },
+	{ CCI_REG8(0x00e4), 0x00 },
+	{ CCI_REG8(0x00e6), 0x00 },
+	{ CCI_REG8(0x00e7), 0x00 },
+	{ CCI_REG8(0x00e8), 0x00 },
+	{ CCI_REG8(0x00e9), 0x00 },
+	{ CCI_REG8(0x3f50), 0x00 },
+	{ CCI_REG8(0x3f56), 0x01 },
+	{ CCI_REG8(0x3f57), 0x30 },
+	{ CCI_REG8(0x3606), 0x01 },
+	{ CCI_REG8(0x3607), 0x01 },
+	{ CCI_REG8(0x3f26), 0x00 },
+	{ CCI_REG8(0x3f4a), 0x00 },
+	{ CCI_REG8(0x3f4b), 0x00 },
+	{ CCI_REG8(0x4bc0), 0x16 },
+	{ CCI_REG8(0x7ba8), 0x00 },
+	{ CCI_REG8(0x7ba9), 0x00 },
+	{ CCI_REG8(0x886b), 0x00 },
+	{ CCI_REG8(0x579a), 0x00 },
+	{ CCI_REG8(0x579b), 0x0a },
+	{ CCI_REG8(0x579c), 0x01 },
+	{ CCI_REG8(0x579d), 0x2a },
+	{ CCI_REG8(0x57ac), 0x00 },
+	{ CCI_REG8(0x57ad), 0x00 },
+	{ CCI_REG8(0x57ae), 0x00 },
+	{ CCI_REG8(0x57af), 0x81 },
+	{ CCI_REG8(0x57be), 0x00 },
+	{ CCI_REG8(0x57bf), 0x00 },
+	{ CCI_REG8(0x57c0), 0x00 },
+	{ CCI_REG8(0x57c1), 0x81 },
+	{ CCI_REG8(0x57d0), 0x00 },
+	{ CCI_REG8(0x57d1), 0x00 },
+	{ CCI_REG8(0x57d2), 0x00 },
+	{ CCI_REG8(0x57d3), 0x81 },
+	{ CCI_REG8(0x5324), 0x00 },
+	{ CCI_REG8(0x5325), 0x31 },
+	{ CCI_REG8(0x5326), 0x00 },
+	{ CCI_REG8(0x5327), 0x60 },
+	{ CCI_REG8(0xbca7), 0x08 },
+	{ CCI_REG8(0x5fcc), 0x1e },
+	{ CCI_REG8(0x5fd7), 0x1e },
+	{ CCI_REG8(0x5fe2), 0x1e },
+	{ CCI_REG8(0x5fed), 0x1e },
+	{ CCI_REG8(0x5ff8), 0x1e },
+	{ CCI_REG8(0x6003), 0x1e },
+	{ CCI_REG8(0x5d0b), 0x02 },
+	{ CCI_REG8(0x6f6d), 0x01 },
+	{ CCI_REG8(0x61c9), 0x68 },
+	{ CCI_REG8(0x5352), 0x00 },
+	{ CCI_REG8(0x5353), 0x3f },
+	{ CCI_REG8(0x5356), 0x00 },
+	{ CCI_REG8(0x5357), 0x1c },
+	{ CCI_REG8(0x5358), 0x00 },
+	{ CCI_REG8(0x5359), 0x3d },
+	{ CCI_REG8(0x535c), 0x00 },
+	{ CCI_REG8(0x535d), 0xa6 },
+	{ CCI_REG8(0x6187), 0x1d },
+	{ CCI_REG8(0x6189), 0x1d },
+	{ CCI_REG8(0x618b), 0x1d },
+	{ CCI_REG8(0x618d), 0x23 },
+	{ CCI_REG8(0x618f), 0x23 },
+	{ CCI_REG8(0x5414), 0x01 },
+	{ CCI_REG8(0x5415), 0x12 },
+	{ CCI_REG8(0xbca8), 0x00 },
+	{ CCI_REG8(0x5fcf), 0x28 },
+	{ CCI_REG8(0x5fda), 0x2d },
+	{ CCI_REG8(0x5fe5), 0x2d },
+	{ CCI_REG8(0x5ff0), 0x2d },
+	{ CCI_REG8(0x5ffb), 0x2d },
+	{ CCI_REG8(0x6006), 0x2d },
+	{ CCI_REG8(0x616e), 0x04 },
+	{ CCI_REG8(0x616f), 0x04 },
+	{ CCI_REG8(0x6170), 0x04 },
+	{ CCI_REG8(0x6171), 0x06 },
+	{ CCI_REG8(0x6172), 0x06 },
+	{ CCI_REG8(0x6173), 0x0c },
+	{ CCI_REG8(0x6174), 0x0c },
+	{ CCI_REG8(0x6175), 0x0c },
+	{ CCI_REG8(0x6176), 0x00 },
+	{ CCI_REG8(0x6177), 0x10 },
+	{ CCI_REG8(0x6178), 0x00 },
+	{ CCI_REG8(0x6179), 0x1a },
+	{ CCI_REG8(0x617a), 0x00 },
+	{ CCI_REG8(0x617b), 0x1a },
+	{ CCI_REG8(0x617c), 0x00 },
+	{ CCI_REG8(0x617d), 0x27 },
+	{ CCI_REG8(0x617e), 0x00 },
+	{ CCI_REG8(0x617f), 0x27 },
+	{ CCI_REG8(0x6180), 0x00 },
+	{ CCI_REG8(0x6181), 0x44 },
+	{ CCI_REG8(0x6182), 0x00 },
+	{ CCI_REG8(0x6183), 0x44 },
+	{ CCI_REG8(0x6184), 0x00 },
+	{ CCI_REG8(0x6185), 0x44 },
+	{ CCI_REG8(0x5dfc), 0x0a },
+	{ CCI_REG8(0x5e00), 0x0a },
+	{ CCI_REG8(0x5e04), 0x0a },
+	{ CCI_REG8(0x5e08), 0x0a },
+	{ CCI_REG8(0x5dfd), 0x0a },
+	{ CCI_REG8(0x5e01), 0x0a },
+	{ CCI_REG8(0x5e05), 0x0a },
+	{ CCI_REG8(0x5e09), 0x0a },
+	{ CCI_REG8(0x5dfe), 0x0a },
+	{ CCI_REG8(0x5e02), 0x0a },
+	{ CCI_REG8(0x5e06), 0x0a },
+	{ CCI_REG8(0x5e0a), 0x0a },
+	{ CCI_REG8(0x5dff), 0x0a },
+	{ CCI_REG8(0x5e03), 0x0a },
+	{ CCI_REG8(0x5e07), 0x0a },
+	{ CCI_REG8(0x5e0b), 0x0a },
+	{ CCI_REG8(0x5dec), 0x12 },
+	{ CCI_REG8(0x5df0), 0x12 },
+	{ CCI_REG8(0x5df4), 0x21 },
+	{ CCI_REG8(0x5df8), 0x31 },
+	{ CCI_REG8(0x5ded), 0x12 },
+	{ CCI_REG8(0x5df1), 0x12 },
+	{ CCI_REG8(0x5df5), 0x21 },
+	{ CCI_REG8(0x5df9), 0x31 },
+	{ CCI_REG8(0x5dee), 0x12 },
+	{ CCI_REG8(0x5df2), 0x12 },
+	{ CCI_REG8(0x5df6), 0x21 },
+	{ CCI_REG8(0x5dfa), 0x31 },
+	{ CCI_REG8(0x5def), 0x12 },
+	{ CCI_REG8(0x5df3), 0x12 },
+	{ CCI_REG8(0x5df7), 0x21 },
+	{ CCI_REG8(0x5dfb), 0x31 },
+	{ CCI_REG8(0x5ddc), 0x0d },
+	{ CCI_REG8(0x5de0), 0x0d },
+	{ CCI_REG8(0x5de4), 0x0d },
+	{ CCI_REG8(0x5de8), 0x0d },
+	{ CCI_REG8(0x5ddd), 0x0d },
+	{ CCI_REG8(0x5de1), 0x0d },
+	{ CCI_REG8(0x5de5), 0x0d },
+	{ CCI_REG8(0x5de9), 0x0d },
+	{ CCI_REG8(0x5dde), 0x0d },
+	{ CCI_REG8(0x5de2), 0x0d },
+	{ CCI_REG8(0x5de6), 0x0d },
+	{ CCI_REG8(0x5dea), 0x0d },
+	{ CCI_REG8(0x5ddf), 0x0d },
+	{ CCI_REG8(0x5de3), 0x0d },
+	{ CCI_REG8(0x5de7), 0x0d },
+	{ CCI_REG8(0x5deb), 0x0d },
+	{ CCI_REG8(0x5dcc), 0x55 },
+	{ CCI_REG8(0x5dd0), 0x50 },
+	{ CCI_REG8(0x5dd4), 0x4b },
+	{ CCI_REG8(0x5dd8), 0x4b },
+	{ CCI_REG8(0x5dcd), 0x55 },
+	{ CCI_REG8(0x5dd1), 0x50 },
+	{ CCI_REG8(0x5dd5), 0x4b },
+	{ CCI_REG8(0x5dd9), 0x4b },
+	{ CCI_REG8(0x5dce), 0x55 },
+	{ CCI_REG8(0x5dd2), 0x50 },
+	{ CCI_REG8(0x5dd6), 0x4b },
+	{ CCI_REG8(0x5dda), 0x4b },
+	{ CCI_REG8(0x5dcf), 0x55 },
+	{ CCI_REG8(0x5dd3), 0x50 },
+	{ CCI_REG8(0x5dd7), 0x4b },
+	{ CCI_REG8(0x5ddb), 0x4b },
+	{ CCI_REG8(0x0202), 0x0b },
+	{ CCI_REG8(0x0203), 0x86 },
+	{ CCI_REG8(0x0204), 0x00 },
+	{ CCI_REG8(0x0205), 0x00 },
+	{ CCI_REG8(0x020e), 0x01 },
+	{ CCI_REG8(0x020f), 0x00 },
+	{ CCI_REG8(0x0210), 0x01 },
+	{ CCI_REG8(0x0211), 0x00 },
+	{ CCI_REG8(0x0212), 0x01 },
+	{ CCI_REG8(0x0213), 0x00 },
+	{ CCI_REG8(0x0214), 0x01 },
+	{ CCI_REG8(0x0215), 0x00 },
+};
+
+static const struct cci_reg_sequence metadata_output[] = {
+	{ CCI_REG8(0x3050), 1 }, /* MIPI Output enabled */
+	{ CCI_REG8(0x3051), 1 }, /* MIPI output frame includes pixels data */
+	{ CCI_REG8(0x3052), 1 }, /* MIPI output frame includes meta data */
+	{ IMX500_REG_DD_CH06_VCID, 0 },
+	{ IMX500_REG_DD_CH07_VCID, 0 },
+	{ IMX500_REG_DD_CH08_VCID, 0 },
+	{ IMX500_REG_DD_CH09_VCID, 0 },
+	{ IMX500_REG_DD_CH06_DT,
+	  0x12 }, /* KPI - User Defined 8-bit Data Type 1 */
+	{ IMX500_REG_DD_CH07_DT, 0x12 }, /* Input Tensor - U.D. 8-bit type 2 */
+	{ IMX500_REG_DD_CH08_DT, 0x12 }, /* Output Tensor - U.D. 8-bit type 3 */
+	{ IMX500_REG_DD_CH09_DT, 0x12 }, /* PQ - U.D. 8-bit type 4 */
+	{ IMX500_REG_DD_CH06_PACKING, IMX500_DD_PACKING_8BPP },
+	{ IMX500_REG_DD_CH07_PACKING, IMX500_DD_PACKING_8BPP },
+	{ IMX500_REG_DD_CH08_PACKING, IMX500_DD_PACKING_8BPP },
+	{ IMX500_REG_DD_CH09_PACKING, IMX500_DD_PACKING_8BPP },
+};
+
+static const struct cci_reg_sequence dnn_regs[] = {
+	{ CCI_REG8(0xd960), 0x52 },
+	{ CCI_REG8(0xd961), 0x52 },
+	{ CCI_REG8(0xd962), 0x52 },
+	{ CCI_REG8(0xd963), 0x52 },
+	{ CCI_REG8(0xd96c), 0x44 },
+	{ CCI_REG8(0xd96d), 0x44 },
+	{ CCI_REG8(0xd96e), 0x44 },
+	{ CCI_REG8(0xd96f), 0x44 },
+	{ CCI_REG8(0xd600), 0x20 },
+	/* Black level */
+	{ CCI_REG16(0xd80c), 0x100 },
+	{ CCI_REG16(0xd80e), 0x100 },
+	{ CCI_REG16(0xd810), 0x100 },
+	{ CCI_REG16(0xd812), 0x100 },
+	/* Gamma */
+	{ CCI_REG8(0xd814), 1 },
+	{ CCI_REG32(0xd850), 0x10000 },
+	{ CCI_REG32(0xd854), 0x40002 },
+	{ CCI_REG32(0xd858), 0x60005 },
+	{ CCI_REG32(0xd85c), 0x90008 },
+	{ CCI_REG32(0xd860), 0xc000a },
+	{ CCI_REG32(0xd864), 0x12000f },
+	{ CCI_REG32(0xd868), 0x1c0014 },
+	{ CCI_REG32(0xd86c), 0x2a0024 },
+	{ CCI_REG32(0xd870), 0x360030 },
+	{ CCI_REG32(0xd874), 0x46003c },
+	{ CCI_REG32(0xd878), 0x5a0051 },
+	{ CCI_REG32(0xd87c), 0x750064 },
+	{ CCI_REG32(0xd880), 0x920084 },
+	{ CCI_REG32(0xd884), 0xa9009e },
+	{ CCI_REG32(0xd888), 0xba00b2 },
+	{ CCI_REG32(0xd88c), 0xc700c1 },
+	{ CCI_REG32(0xd890), 0xd100cd },
+	{ CCI_REG32(0xd894), 0xde00d6 },
+	{ CCI_REG32(0xd898), 0xe900e4 },
+	{ CCI_REG32(0xd89c), 0xf300ee },
+	{ CCI_REG32(0xd8a0), 0xfb00f7 },
+	{ CCI_REG16(0xd8a4), 0xff },
+	{ CCI_REG32(0xd8a8), 0x10000 },
+	{ CCI_REG32(0xd8ac), 0x40002 },
+	{ CCI_REG32(0xd8b0), 0x60005 },
+	{ CCI_REG32(0xd8b4), 0x90008 },
+	{ CCI_REG32(0xd8b8), 0xc000a },
+	{ CCI_REG32(0xd8bc), 0x12000f },
+	{ CCI_REG32(0xd8c0), 0x1c0014 },
+	{ CCI_REG32(0xd8c4), 0x2a0024 },
+	{ CCI_REG32(0xd8c8), 0x360030 },
+	{ CCI_REG32(0xd8cc), 0x46003c },
+	{ CCI_REG32(0xd8d0), 0x5a0051 },
+	{ CCI_REG32(0xd8d4), 0x750064 },
+	{ CCI_REG32(0xd8d8), 0x920084 },
+	{ CCI_REG32(0xd8dc), 0xa9009e },
+	{ CCI_REG32(0xd8e0), 0xba00b2 },
+	{ CCI_REG32(0xd8e4), 0xc700c1 },
+	{ CCI_REG32(0xd8e8), 0xd100cd },
+	{ CCI_REG32(0xd8ec), 0xde00d6 },
+	{ CCI_REG32(0xd8f0), 0xe900e4 },
+	{ CCI_REG32(0xd8f4), 0xf300ee },
+	{ CCI_REG32(0xd8f8), 0xfb00f7 },
+	{ CCI_REG16(0xd8fc), 0xff },
+	{ CCI_REG32(0xd900), 0x10000 },
+	{ CCI_REG32(0xd904), 0x40002 },
+	{ CCI_REG32(0xd908), 0x60005 },
+	{ CCI_REG32(0xd90c), 0x90008 },
+	{ CCI_REG32(0xd910), 0xc000a },
+	{ CCI_REG32(0xd914), 0x12000f },
+	{ CCI_REG32(0xd918), 0x1c0014 },
+	{ CCI_REG32(0xd91c), 0x2a0024 },
+	{ CCI_REG32(0xd920), 0x360030 },
+	{ CCI_REG32(0xd924), 0x46003c },
+	{ CCI_REG32(0xd928), 0x5a0051 },
+	{ CCI_REG32(0xd92c), 0x750064 },
+	{ CCI_REG32(0xd930), 0x920084 },
+	{ CCI_REG32(0xd934), 0xa9009e },
+	{ CCI_REG32(0xd938), 0xba00b2 },
+	{ CCI_REG32(0xd93c), 0xc700c1 },
+	{ CCI_REG32(0xd940), 0xd100cd },
+	{ CCI_REG32(0xd944), 0xde00d6 },
+	{ CCI_REG32(0xd948), 0xe900e4 },
+	{ CCI_REG32(0xd94c), 0xf300ee },
+	{ CCI_REG32(0xd950), 0xfb00f7 },
+	{ CCI_REG16(0xd954), 0xff },
+	{ CCI_REG8(0xd826), 1 },
+	/* LSC */
+	{ CCI_REG32(0xe000), 0x2e502a0 },
+	{ CCI_REG32(0xe004), 0x2c80283 },
+	{ CCI_REG32(0xe008), 0x2700233 },
+	{ CCI_REG32(0xe00c), 0x22d01f6 },
+	{ CCI_REG32(0xe010), 0x1f401c3 },
+	{ CCI_REG32(0xe014), 0x1c5019c },
+	{ CCI_REG32(0xe018), 0x1bb0192 },
+	{ CCI_REG32(0xe01c), 0x1ba0192 },
+	{ CCI_REG32(0xe020), 0x1b90192 },
+	{ CCI_REG32(0xe024), 0x1ba0192 },
+	{ CCI_REG32(0xe028), 0x1ca019f },
+	{ CCI_REG32(0xe02c), 0x1fb01c8 },
+	{ CCI_REG32(0xe030), 0x23601fb },
+	{ CCI_REG32(0xe034), 0x27a0239 },
+	{ CCI_REG32(0xe038), 0x2d5028a },
+	{ CCI_REG32(0xe03c), 0x2f302a8 },
+	{ CCI_REG32(0xe040), 0x2c60283 },
+	{ CCI_REG32(0xe044), 0x27c0240 },
+	{ CCI_REG32(0xe048), 0x22d01f6 },
+	{ CCI_REG32(0xe04c), 0x1fd01cd },
+	{ CCI_REG32(0xe050), 0x1c4019c },
+	{ CCI_REG32(0xe054), 0x19c017b },
+	{ CCI_REG32(0xe058), 0x1810165 },
+	{ CCI_REG32(0xe05c), 0x175015c },
+	{ CCI_REG32(0xe060), 0x175015c },
+	{ CCI_REG32(0xe064), 0x1840167 },
+	{ CCI_REG32(0xe068), 0x1a0017e },
+	{ CCI_REG32(0xe06c), 0x1cc01a1 },
+	{ CCI_REG32(0xe070), 0x20501d1 },
+	{ CCI_REG32(0xe074), 0x23601fc },
+	{ CCI_REG32(0xe078), 0x2890246 },
+	{ CCI_REG32(0xe07c), 0x2d3028a },
+	{ CCI_REG32(0xe080), 0x2800243 },
+	{ CCI_REG32(0xe084), 0x245020e },
+	{ CCI_REG32(0xe088), 0x1ff01ce },
+	{ CCI_REG32(0xe08c), 0x1c4019c },
+	{ CCI_REG32(0xe090), 0x19a017b },
+	{ CCI_REG32(0xe094), 0x1650150 },
+	{ CCI_REG32(0xe098), 0x14a013a },
+	{ CCI_REG32(0xe09c), 0x13f0131 },
+	{ CCI_REG32(0xe0a0), 0x1400131 },
+	{ CCI_REG32(0xe0a4), 0x14d013c },
+	{ CCI_REG32(0xe0a8), 0x16a0154 },
+	{ CCI_REG32(0xe0ac), 0x1a1017e },
+	{ CCI_REG32(0xe0b0), 0x1cc01a1 },
+	{ CCI_REG32(0xe0b4), 0x20801d3 },
+	{ CCI_REG32(0xe0b8), 0x2510214 },
+	{ CCI_REG32(0xe0bc), 0x28b0249 },
+	{ CCI_REG32(0xe0c0), 0x2640229 },
+	{ CCI_REG32(0xe0c4), 0x22101ed },
+	{ CCI_REG32(0xe0c8), 0x1dc01b0 },
+	{ CCI_REG32(0xe0cc), 0x19c017c },
+	{ CCI_REG32(0xe0d0), 0x1650150 },
+	{ CCI_REG32(0xe0d4), 0x148013a },
+	{ CCI_REG32(0xe0d8), 0x123011c },
+	{ CCI_REG32(0xe0dc), 0x1190113 },
+	{ CCI_REG32(0xe0e0), 0x1190113 },
+	{ CCI_REG32(0xe0e4), 0x1280120 },
+	{ CCI_REG32(0xe0e8), 0x14c013c },
+	{ CCI_REG32(0xe0ec), 0x16b0154 },
+	{ CCI_REG32(0xe0f0), 0x1a30181 },
+	{ CCI_REG32(0xe0f4), 0x1e601b6 },
+	{ CCI_REG32(0xe0f8), 0x22c01f3 },
+	{ CCI_REG32(0xe0fc), 0x2700230 },
+	{ CCI_REG32(0xe100), 0x257021d },
+	{ CCI_REG32(0xe104), 0x20901d8 },
+	{ CCI_REG32(0xe108), 0x1c4019d },
+	{ CCI_REG32(0xe10c), 0x1820167 },
+	{ CCI_REG32(0xe110), 0x14b013b },
+	{ CCI_REG32(0xe114), 0x124011c },
+	{ CCI_REG32(0xe118), 0x1170113 },
+	{ CCI_REG32(0xe11c), 0x1010101 },
+	{ CCI_REG32(0xe120), 0x1030102 },
+	{ CCI_REG32(0xe124), 0x1190113 },
+	{ CCI_REG32(0xe128), 0x1280120 },
+	{ CCI_REG32(0xe12c), 0x14f013f },
+	{ CCI_REG32(0xe130), 0x189016c },
+	{ CCI_REG32(0xe134), 0x1ce01a3 },
+	{ CCI_REG32(0xe138), 0x21601df },
+	{ CCI_REG32(0xe13c), 0x2630224 },
+	{ CCI_REG32(0xe140), 0x257021d },
+	{ CCI_REG32(0xe144), 0x20101d0 },
+	{ CCI_REG32(0xe148), 0x1ba0194 },
+	{ CCI_REG32(0xe14c), 0x176015d },
+	{ CCI_REG32(0xe150), 0x13e0132 },
+	{ CCI_REG32(0xe154), 0x1190114 },
+	{ CCI_REG32(0xe158), 0x1010101 },
+	{ CCI_REG32(0xe15c), 0x1000100 },
+	{ CCI_REG32(0xe160), 0x1010100 },
+	{ CCI_REG32(0xe164), 0x1040103 },
+	{ CCI_REG32(0xe168), 0x11d0118 },
+	{ CCI_REG32(0xe16c), 0x1450136 },
+	{ CCI_REG32(0xe170), 0x17d0163 },
+	{ CCI_REG32(0xe174), 0x1c4019a },
+	{ CCI_REG32(0xe178), 0x20d01d6 },
+	{ CCI_REG32(0xe17c), 0x2630224 },
+	{ CCI_REG32(0xe180), 0x257021d },
+	{ CCI_REG32(0xe184), 0x20001d0 },
+	{ CCI_REG32(0xe188), 0x1b90194 },
+	{ CCI_REG32(0xe18c), 0x175015d },
+	{ CCI_REG32(0xe190), 0x13e0132 },
+	{ CCI_REG32(0xe194), 0x1180114 },
+	{ CCI_REG32(0xe198), 0x1040103 },
+	{ CCI_REG32(0xe19c), 0x1000100 },
+	{ CCI_REG32(0xe1a0), 0x1030102 },
+	{ CCI_REG32(0xe1a4), 0x1050103 },
+	{ CCI_REG32(0xe1a8), 0x11d0118 },
+	{ CCI_REG32(0xe1ac), 0x1450136 },
+	{ CCI_REG32(0xe1b0), 0x17d0163 },
+	{ CCI_REG32(0xe1b4), 0x1c4019a },
+	{ CCI_REG32(0xe1b8), 0x20d01d6 },
+	{ CCI_REG32(0xe1bc), 0x2640224 },
+	{ CCI_REG32(0xe1c0), 0x258021f },
+	{ CCI_REG32(0xe1c4), 0x20e01db },
+	{ CCI_REG32(0xe1c8), 0x1c7019f },
+	{ CCI_REG32(0xe1cc), 0x1840169 },
+	{ CCI_REG32(0xe1d0), 0x14d013e },
+	{ CCI_REG32(0xe1d4), 0x1290120 },
+	{ CCI_REG32(0xe1d8), 0x1180114 },
+	{ CCI_REG32(0xe1dc), 0x1050103 },
+	{ CCI_REG32(0xe1e0), 0x1050103 },
+	{ CCI_REG32(0xe1e4), 0x11e0117 },
+	{ CCI_REG32(0xe1e8), 0x12c0123 },
+	{ CCI_REG32(0xe1ec), 0x1530142 },
+	{ CCI_REG32(0xe1f0), 0x18d016f },
+	{ CCI_REG32(0xe1f4), 0x1d201a6 },
+	{ CCI_REG32(0xe1f8), 0x21a01e2 },
+	{ CCI_REG32(0xe1fc), 0x2640225 },
+	{ CCI_REG32(0xe200), 0x269022d },
+	{ CCI_REG32(0xe204), 0x22601f1 },
+	{ CCI_REG32(0xe208), 0x1e101b4 },
+	{ CCI_REG32(0xe20c), 0x1a10181 },
+	{ CCI_REG32(0xe210), 0x16c0156 },
+	{ CCI_REG32(0xe214), 0x14d013e },
+	{ CCI_REG32(0xe218), 0x1290120 },
+	{ CCI_REG32(0xe21c), 0x11f0118 },
+	{ CCI_REG32(0xe220), 0x11f0118 },
+	{ CCI_REG32(0xe224), 0x12b0123 },
+	{ CCI_REG32(0xe228), 0x1530142 },
+	{ CCI_REG32(0xe22c), 0x172015a },
+	{ CCI_REG32(0xe230), 0x1aa0187 },
+	{ CCI_REG32(0xe234), 0x1ec01bb },
+	{ CCI_REG32(0xe238), 0x23301f8 },
+	{ CCI_REG32(0xe23c), 0x2750233 },
+	{ CCI_REG32(0xe240), 0x28b024c },
+	{ CCI_REG32(0xe244), 0x24f0216 },
+	{ CCI_REG32(0xe248), 0x20701d4 },
+	{ CCI_REG32(0xe24c), 0x1ce01a4 },
+	{ CCI_REG32(0xe250), 0x1a10181 },
+	{ CCI_REG32(0xe254), 0x16c0156 },
+	{ CCI_REG32(0xe258), 0x1520141 },
+	{ CCI_REG32(0xe25c), 0x1480138 },
+	{ CCI_REG32(0xe260), 0x1480138 },
+	{ CCI_REG32(0xe264), 0x1550143 },
+	{ CCI_REG32(0xe268), 0x172015a },
+	{ CCI_REG32(0xe26c), 0x1aa0187 },
+	{ CCI_REG32(0xe270), 0x1d701a9 },
+	{ CCI_REG32(0xe274), 0x21201db },
+	{ CCI_REG32(0xe278), 0x25d021d },
+	{ CCI_REG32(0xe27c), 0x2990254 },
+	{ CCI_REG32(0xe280), 0x2d70291 },
+	{ CCI_REG32(0xe284), 0x28c024c },
+	{ CCI_REG32(0xe288), 0x2390201 },
+	{ CCI_REG32(0xe28c), 0x20701d4 },
+	{ CCI_REG32(0xe290), 0x1ce01a4 },
+	{ CCI_REG32(0xe294), 0x1a70184 },
+	{ CCI_REG32(0xe298), 0x18c016e },
+	{ CCI_REG32(0xe29c), 0x1810164 },
+	{ CCI_REG32(0xe2a0), 0x1810164 },
+	{ CCI_REG32(0xe2a4), 0x1900170 },
+	{ CCI_REG32(0xe2a8), 0x1ad0188 },
+	{ CCI_REG32(0xe2ac), 0x1d601a9 },
+	{ CCI_REG32(0xe2b0), 0x21201da },
+	{ CCI_REG32(0xe2b4), 0x2450207 },
+	{ CCI_REG32(0xe2b8), 0x29a0254 },
+	{ CCI_REG32(0xe2bc), 0x2ea029d },
+	{ CCI_REG32(0xe2c0), 0x2f602ae },
+	{ CCI_REG32(0xe2c4), 0x2d80291 },
+	{ CCI_REG32(0xe2c8), 0x280023f },
+	{ CCI_REG32(0xe2cc), 0x2390200 },
+	{ CCI_REG32(0xe2d0), 0x1fe01cc },
+	{ CCI_REG32(0xe2d4), 0x1d201a4 },
+	{ CCI_REG32(0xe2d8), 0x1c6019b },
+	{ CCI_REG32(0xe2dc), 0x1c6019b },
+	{ CCI_REG32(0xe2e0), 0x1c6019b },
+	{ CCI_REG32(0xe2e4), 0x1c8019b },
+	{ CCI_REG32(0xe2e8), 0x1d701a9 },
+	{ CCI_REG32(0xe2ec), 0x20801d1 },
+	{ CCI_REG32(0xe2f0), 0x2450206 },
+	{ CCI_REG32(0xe2f4), 0x28e0248 },
+	{ CCI_REG32(0xe2f8), 0x2ec029d },
+	{ CCI_REG32(0xe2fc), 0x30902b9 },
+	{ CCI_REG32(0xe300), 0x2a002a4 },
+	{ CCI_REG32(0xe304), 0x2830286 },
+	{ CCI_REG32(0xe308), 0x2330234 },
+	{ CCI_REG32(0xe30c), 0x1f601f7 },
+	{ CCI_REG32(0xe310), 0x1c301c4 },
+	{ CCI_REG32(0xe314), 0x19c019c },
+	{ CCI_REG32(0xe318), 0x1920193 },
+	{ CCI_REG32(0xe31c), 0x1920193 },
+	{ CCI_REG32(0xe320), 0x1920192 },
+	{ CCI_REG32(0xe324), 0x1920193 },
+	{ CCI_REG32(0xe328), 0x19f01a1 },
+	{ CCI_REG32(0xe32c), 0x1c801ca },
+	{ CCI_REG32(0xe330), 0x1fb01fe },
+	{ CCI_REG32(0xe334), 0x239023e },
+	{ CCI_REG32(0xe338), 0x28a0292 },
+	{ CCI_REG32(0xe33c), 0x2a802b0 },
+	{ CCI_REG32(0xe340), 0x2830287 },
+	{ CCI_REG32(0xe344), 0x2400242 },
+	{ CCI_REG32(0xe348), 0x1f601f8 },
+	{ CCI_REG32(0xe34c), 0x1cd01ce },
+	{ CCI_REG32(0xe350), 0x19c019d },
+	{ CCI_REG32(0xe354), 0x17b017d },
+	{ CCI_REG32(0xe358), 0x1650166 },
+	{ CCI_REG32(0xe35c), 0x15c015d },
+	{ CCI_REG32(0xe360), 0x15c015d },
+	{ CCI_REG32(0xe364), 0x1670168 },
+	{ CCI_REG32(0xe368), 0x17e0180 },
+	{ CCI_REG32(0xe36c), 0x1a101a3 },
+	{ CCI_REG32(0xe370), 0x1d101d3 },
+	{ CCI_REG32(0xe374), 0x1fc0200 },
+	{ CCI_REG32(0xe378), 0x246024c },
+	{ CCI_REG32(0xe37c), 0x28a0291 },
+	{ CCI_REG32(0xe380), 0x2430245 },
+	{ CCI_REG32(0xe384), 0x20e0211 },
+	{ CCI_REG32(0xe388), 0x1ce01d0 },
+	{ CCI_REG32(0xe38c), 0x19c019e },
+	{ CCI_REG32(0xe390), 0x17b017c },
+	{ CCI_REG32(0xe394), 0x1500152 },
+	{ CCI_REG32(0xe398), 0x13a013c },
+	{ CCI_REG32(0xe39c), 0x1310134 },
+	{ CCI_REG32(0xe3a0), 0x1310134 },
+	{ CCI_REG32(0xe3a4), 0x13c013f },
+	{ CCI_REG32(0xe3a8), 0x1540156 },
+	{ CCI_REG32(0xe3ac), 0x17e0180 },
+	{ CCI_REG32(0xe3b0), 0x1a101a4 },
+	{ CCI_REG32(0xe3b4), 0x1d301d8 },
+	{ CCI_REG32(0xe3b8), 0x2140219 },
+	{ CCI_REG32(0xe3bc), 0x249024e },
+	{ CCI_REG32(0xe3c0), 0x229022b },
+	{ CCI_REG32(0xe3c4), 0x1ed01ef },
+	{ CCI_REG32(0xe3c8), 0x1b001b2 },
+	{ CCI_REG32(0xe3cc), 0x17c017e },
+	{ CCI_REG32(0xe3d0), 0x1500151 },
+	{ CCI_REG32(0xe3d4), 0x13a013c },
+	{ CCI_REG32(0xe3d8), 0x11c011f },
+	{ CCI_REG32(0xe3dc), 0x1130117 },
+	{ CCI_REG32(0xe3e0), 0x1130117 },
+	{ CCI_REG32(0xe3e4), 0x1200123 },
+	{ CCI_REG32(0xe3e8), 0x13c013f },
+	{ CCI_REG32(0xe3ec), 0x1540156 },
+	{ CCI_REG32(0xe3f0), 0x1810183 },
+	{ CCI_REG32(0xe3f4), 0x1b601ba },
+	{ CCI_REG32(0xe3f8), 0x1f301f6 },
+	{ CCI_REG32(0xe3fc), 0x2300234 },
+	{ CCI_REG32(0xe400), 0x21d0221 },
+	{ CCI_REG32(0xe404), 0x1d801db },
+	{ CCI_REG32(0xe408), 0x19d019f },
+	{ CCI_REG32(0xe40c), 0x1670169 },
+	{ CCI_REG32(0xe410), 0x13b013d },
+	{ CCI_REG32(0xe414), 0x11c011f },
+	{ CCI_REG32(0xe418), 0x1130117 },
+	{ CCI_REG32(0xe41c), 0x1010106 },
+	{ CCI_REG32(0xe420), 0x1020108 },
+	{ CCI_REG32(0xe424), 0x1130117 },
+	{ CCI_REG32(0xe428), 0x1200123 },
+	{ CCI_REG32(0xe42c), 0x13f0142 },
+	{ CCI_REG32(0xe430), 0x16c016f },
+	{ CCI_REG32(0xe434), 0x1a301a6 },
+	{ CCI_REG32(0xe438), 0x1df01e2 },
+	{ CCI_REG32(0xe43c), 0x2240228 },
+	{ CCI_REG32(0xe440), 0x21d0220 },
+	{ CCI_REG32(0xe444), 0x1d001d3 },
+	{ CCI_REG32(0xe448), 0x1940196 },
+	{ CCI_REG32(0xe44c), 0x15d0160 },
+	{ CCI_REG32(0xe450), 0x1320135 },
+	{ CCI_REG32(0xe454), 0x1140118 },
+	{ CCI_REG32(0xe458), 0x1010106 },
+	{ CCI_REG32(0xe45c), 0x1000106 },
+	{ CCI_REG32(0xe460), 0x1000106 },
+	{ CCI_REG32(0xe464), 0x1030109 },
+	{ CCI_REG32(0xe468), 0x118011b },
+	{ CCI_REG32(0xe46c), 0x136013a },
+	{ CCI_REG32(0xe470), 0x1630165 },
+	{ CCI_REG32(0xe474), 0x19a019c },
+	{ CCI_REG32(0xe478), 0x1d601d9 },
+	{ CCI_REG32(0xe47c), 0x2240227 },
+	{ CCI_REG32(0xe480), 0x21d0220 },
+	{ CCI_REG32(0xe484), 0x1d001d3 },
+	{ CCI_REG32(0xe488), 0x1940196 },
+	{ CCI_REG32(0xe48c), 0x15d0160 },
+	{ CCI_REG32(0xe490), 0x1320135 },
+	{ CCI_REG32(0xe494), 0x1140118 },
+	{ CCI_REG32(0xe498), 0x1030109 },
+	{ CCI_REG32(0xe49c), 0x1000106 },
+	{ CCI_REG32(0xe4a0), 0x1020108 },
+	{ CCI_REG32(0xe4a4), 0x1030109 },
+	{ CCI_REG32(0xe4a8), 0x118011b },
+	{ CCI_REG32(0xe4ac), 0x1360139 },
+	{ CCI_REG32(0xe4b0), 0x1630165 },
+	{ CCI_REG32(0xe4b4), 0x19a019c },
+	{ CCI_REG32(0xe4b8), 0x1d601d9 },
+	{ CCI_REG32(0xe4bc), 0x2240227 },
+	{ CCI_REG32(0xe4c0), 0x21f0221 },
+	{ CCI_REG32(0xe4c4), 0x1db01de },
+	{ CCI_REG32(0xe4c8), 0x19f01a2 },
+	{ CCI_REG32(0xe4cc), 0x169016c },
+	{ CCI_REG32(0xe4d0), 0x13e0141 },
+	{ CCI_REG32(0xe4d4), 0x1200124 },
+	{ CCI_REG32(0xe4d8), 0x1140119 },
+	{ CCI_REG32(0xe4dc), 0x1030109 },
+	{ CCI_REG32(0xe4e0), 0x1030109 },
+	{ CCI_REG32(0xe4e4), 0x117011c },
+	{ CCI_REG32(0xe4e8), 0x1230126 },
+	{ CCI_REG32(0xe4ec), 0x1420145 },
+	{ CCI_REG32(0xe4f0), 0x16f0171 },
+	{ CCI_REG32(0xe4f4), 0x1a601a8 },
+	{ CCI_REG32(0xe4f8), 0x1e201e4 },
+	{ CCI_REG32(0xe4fc), 0x2250227 },
+	{ CCI_REG32(0xe500), 0x22d0231 },
+	{ CCI_REG32(0xe504), 0x1f101f4 },
+	{ CCI_REG32(0xe508), 0x1b401b7 },
+	{ CCI_REG32(0xe50c), 0x1810183 },
+	{ CCI_REG32(0xe510), 0x1560159 },
+	{ CCI_REG32(0xe514), 0x13e0141 },
+	{ CCI_REG32(0xe518), 0x1200124 },
+	{ CCI_REG32(0xe51c), 0x118011c },
+	{ CCI_REG32(0xe520), 0x118011c },
+	{ CCI_REG32(0xe524), 0x1230126 },
+	{ CCI_REG32(0xe528), 0x1420145 },
+	{ CCI_REG32(0xe52c), 0x15a015c },
+	{ CCI_REG32(0xe530), 0x1870188 },
+	{ CCI_REG32(0xe534), 0x1bb01bd },
+	{ CCI_REG32(0xe538), 0x1f801fb },
+	{ CCI_REG32(0xe53c), 0x2330236 },
+	{ CCI_REG32(0xe540), 0x24c0250 },
+	{ CCI_REG32(0xe544), 0x2160219 },
+	{ CCI_REG32(0xe548), 0x1d401d7 },
+	{ CCI_REG32(0xe54c), 0x1a401a6 },
+	{ CCI_REG32(0xe550), 0x1810183 },
+	{ CCI_REG32(0xe554), 0x1560158 },
+	{ CCI_REG32(0xe558), 0x1410144 },
+	{ CCI_REG32(0xe55c), 0x138013b },
+	{ CCI_REG32(0xe560), 0x138013b },
+	{ CCI_REG32(0xe564), 0x1430146 },
+	{ CCI_REG32(0xe568), 0x15a015c },
+	{ CCI_REG32(0xe56c), 0x1870188 },
+	{ CCI_REG32(0xe570), 0x1a901ab },
+	{ CCI_REG32(0xe574), 0x1db01dd },
+	{ CCI_REG32(0xe578), 0x21d0221 },
+	{ CCI_REG32(0xe57c), 0x2540259 },
+	{ CCI_REG32(0xe580), 0x2910296 },
+	{ CCI_REG32(0xe584), 0x24c0251 },
+	{ CCI_REG32(0xe588), 0x2010204 },
+	{ CCI_REG32(0xe58c), 0x1d401d6 },
+	{ CCI_REG32(0xe590), 0x1a401a5 },
+	{ CCI_REG32(0xe594), 0x1840186 },
+	{ CCI_REG32(0xe598), 0x16e0170 },
+	{ CCI_REG32(0xe59c), 0x1640167 },
+	{ CCI_REG32(0xe5a0), 0x1640167 },
+	{ CCI_REG32(0xe5a4), 0x1700173 },
+	{ CCI_REG32(0xe5a8), 0x188018a },
+	{ CCI_REG32(0xe5ac), 0x1a901ab },
+	{ CCI_REG32(0xe5b0), 0x1da01dd },
+	{ CCI_REG32(0xe5b4), 0x207020a },
+	{ CCI_REG32(0xe5b8), 0x2540259 },
+	{ CCI_REG32(0xe5bc), 0x29d02a3 },
+	{ CCI_REG32(0xe5c0), 0x2ae02b4 },
+	{ CCI_REG32(0xe5c4), 0x2910297 },
+	{ CCI_REG32(0xe5c8), 0x23f0243 },
+	{ CCI_REG32(0xe5cc), 0x2000201 },
+	{ CCI_REG32(0xe5d0), 0x1cc01cd },
+	{ CCI_REG32(0xe5d4), 0x1a401a6 },
+	{ CCI_REG32(0xe5d8), 0x19b019d },
+	{ CCI_REG32(0xe5dc), 0x19b019d },
+	{ CCI_REG32(0xe5e0), 0x19b019d },
+	{ CCI_REG32(0xe5e4), 0x19b019e },
+	{ CCI_REG32(0xe5e8), 0x1a901ab },
+	{ CCI_REG32(0xe5ec), 0x1d101d3 },
+	{ CCI_REG32(0xe5f0), 0x2060209 },
+	{ CCI_REG32(0xe5f4), 0x248024b },
+	{ CCI_REG32(0xe5f8), 0x29d02a3 },
+	{ CCI_REG32(0xe5fc), 0x2b902c0 },
+	{ CCI_REG8(0xd822), 0x01 },
+	{ CCI_REG8(0xd823), 0x0f },
+};
+
+/* Mode configs */
+static const struct imx500_mode imx500_supported_modes[] = {
+	{
+		/* 12MPix 10fps mode */
+		.width = 4056,
+		.height = 3040,
+		.line_length_pix = 17900,
+		.crop = {
+			.left = IMX500_PIXEL_ARRAY_LEFT,
+			.top = IMX500_PIXEL_ARRAY_TOP,
+			.width = 4056,
+			.height = 3040,
+		},
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_4056x3040_regs),
+			.regs = mode_4056x3040_regs,
+		},
+	},
+	{
+		/* 2x2 binned 40fps mode */
+		.width = 2028,
+		.height = 1520,
+		.line_length_pix = 9398,
+		.crop = {
+			.left = IMX500_PIXEL_ARRAY_LEFT,
+			.top = IMX500_PIXEL_ARRAY_TOP,
+			.width = 4056,
+			.height = 3040,
+		},
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_2028x1520_regs),
+			.regs = mode_2028x1520_regs,
+		},
+	},
+};
+
+/*
+ * The supported formats.
+ * This table MUST contain 4 entries per format, to cover the various flip
+ * combinations in the order
+ * - no flip
+ * - h flip
+ * - v flip
+ * - h&v flips
+ */
+static const u32 codes[] = {
+	/* 10-bit modes. */
+	MEDIA_BUS_FMT_SRGGB10_1X10,
+	MEDIA_BUS_FMT_SGRBG10_1X10,
+	MEDIA_BUS_FMT_SGBRG10_1X10,
+	MEDIA_BUS_FMT_SBGGR10_1X10,
+};
+
+enum imx500_state {
+	IMX500_STATE_RESET = 0,
+	IMX500_STATE_PROGRAM_EMPTY,
+	IMX500_STATE_WITHOUT_NETWORK,
+	IMX500_STATE_WITH_NETWORK,
+};
+
+struct imx500 {
+	struct dentry *debugfs;
+	struct v4l2_subdev sd;
+	struct media_pad pad[NUM_PADS];
+	struct regmap *regmap;
+
+	unsigned int fmt_code;
+
+	struct clk *xclk;
+	u32 xclk_freq;
+
+	struct gpio_desc *led_gpio;
+	struct gpio_desc *reset_gpio;
+	struct regulator_bulk_data supplies[IMX500_NUM_SUPPLIES];
+
+	struct v4l2_ctrl_handler ctrl_handler;
+	/* V4L2 Controls */
+	struct v4l2_ctrl *pixel_rate;
+	struct v4l2_ctrl *link_freq;
+	struct v4l2_ctrl *exposure;
+	struct v4l2_ctrl *vflip;
+	struct v4l2_ctrl *hflip;
+	struct v4l2_ctrl *vblank;
+	struct v4l2_ctrl *hblank;
+	struct v4l2_ctrl *network_fw_ctrl;
+	struct v4l2_ctrl *device_id;
+	struct v4l2_ctrl *enable_injection;
+	struct v4l2_ctrl *input_tensor_fd;
+	struct v4l2_ctrl *injection_cmp_frm;
+
+	struct v4l2_rect inference_window;
+
+	/* Current mode */
+	const struct imx500_mode *mode;
+
+	/*
+	 * Mutex for serialized access:
+	 * Protect sensor module set pad format and start/stop streaming safely.
+	 */
+	struct mutex mutex;
+
+	/* Streaming on/off */
+	bool streaming;
+
+	/* Rewrite common registers on stream on? */
+	bool common_regs_written;
+
+	bool loader_and_main_written;
+	bool network_written;
+
+	/* Tensor injection enabled */
+	bool tensor_injection;
+
+	/* Current injection comparison frame value */
+	u8 current_injection_cmp_frm;
+
+	/* Current long exposure factor in use. Set through V4L2_CID_VBLANK */
+	unsigned int long_exp_shift;
+
+	struct spi_device *spi_device;
+
+	const struct firmware *fw_loader;
+	const struct firmware *fw_main;
+	const u8 *fw_network;
+	size_t fw_network_size;
+	size_t fw_progress;
+	unsigned int fw_stage;
+
+	enum imx500_state fsm_state;
+
+	u32 num_inference_lines;
+};
+
+static inline struct imx500 *to_imx500(struct v4l2_subdev *_sd)
+{
+	return container_of(_sd, struct imx500, sd);
+}
+
+static bool validate_normalization_yuv(u16 reg, uint8_t size,
+				       uint32_t value)
+{
+	/* Some regs are 9-bit, some 8-bit, some 1-bit */
+	switch (reg) {
+	case 0xD62A:
+	case 0xD632:
+	case 0xD63A:
+	case 0xD644:
+	case 0xD648:
+	case 0xD64C:
+	case 0xD650:
+	case 0xD654:
+	case 0xD658:
+		return size == 2 && !(value & ~0x1FF);
+	case 0xD600:
+	case 0xD601:
+	case 0xD602:
+		return size == 1 && !(value & ~0xFF);
+	case 0xD629:
+	case 0xD630:
+	case 0xD638:
+	case 0xD643:
+	case 0xD647:
+	case 0xD64B:
+	case 0xD64F:
+	case 0xD653:
+	case 0xD657:
+		return size == 1 && !(value & ~0x01);
+	default:
+		return false;
+	}
+}
+
+/* Common function as bayer rgb + normalization use the same repeating register
+ * layout
+ */
+static bool validate_bit_pattern(u8 offset, uint8_t size, uint32_t value)
+{
+	/* There are no odd register addresses */
+	if (offset & 1)
+		return false;
+
+	/* Valid register sizes/patterns repeat every 4 */
+	offset = (offset >> 1) & 3;
+
+	if (offset == 1)
+		return size == 1 && !(value & ~1);
+	else
+		return size == 2 && !(value & ~0x1FF);
+}
+
+static bool validate_bayer_rgb_normalization(u16 reg, uint8_t size,
+					     uint32_t value)
+{
+	if (reg < 0xD684 || reg >= 0xD6E4)
+		return false;
+	return validate_bit_pattern(reg - 0xD684, size, value);
+}
+
+static bool validate_normalization_registers(u16 reg, uint8_t size,
+					     uint32_t value)
+{
+	if (reg < 0xD708 || reg >= 0xD750)
+		return false;
+	return validate_bit_pattern(reg - 0xD708, size, value);
+}
+
+static bool validate_image_format_selection(u16 reg, uint8_t size,
+					    uint32_t value)
+{
+	if (size != 1 || value > 5)
+		return false;
+	if (reg < 0xD750 || reg > 0xd752)
+		return false;
+	return true;
+}
+
+static bool validate_yc_conversion_factor(u16 reg, uint8_t size,
+					  uint32_t value)
+{
+	static const u32 allowed[9] = {
+		0x0FFF0FFF, 0x0FFF1FFF, 0x0FFF0FFF, 0x0FFF1FFF, 0x0FFF0FFF,
+		0x0FFF1FFF, 0x01FF01FF, 0x01FF01FF, 0x01FF01FF,
+	};
+
+	if (size > 4 || size & 1 || reg & 1 || reg < 0x76C || reg > 0xD7FA)
+		return false;
+
+	if (size == 2) {
+		if (reg & 2)
+			reg -= 2;
+		else
+			value <<= 16;
+	}
+
+	/* High registers (clip values) are all 2x 9-bit */
+	if (reg >= 0xD7D8)
+		return !(value & ~0x01FF01FF);
+
+	/* Early registers follow a repeating pattern */
+	reg -= 0xD76C;
+	reg >>= 2;
+	return !(value & ~allowed[reg % sizeof(allowed)]);
+}
+
+static bool validate_dnn_output_setting(u16 reg, uint8_t size,
+					uint32_t value)
+{
+	/* Only Y_OUT_SIZE for Input Tensor / Output Tensor is configurable from
+	 * userspace
+	 */
+	return (size == 2) && (value < 2046) &&
+	       ((reg == CCI_REG_ADDR(IMX500_REG_DD_CH07_Y_OUT_SIZE)) ||
+		(reg == CCI_REG_ADDR(IMX500_REG_DD_CH08_Y_OUT_SIZE)));
+}
+
+static bool __must_check
+imx500_validate_inference_register(const struct cci_reg_sequence *reg)
+{
+	unsigned int i;
+
+	static bool (*const checks[])(uint16_t, uint8_t, uint32_t) = {
+		validate_normalization_yuv,
+		validate_bayer_rgb_normalization,
+		validate_normalization_registers,
+		validate_image_format_selection,
+		validate_yc_conversion_factor,
+		validate_dnn_output_setting,
+	};
+
+	if (!reg)
+		return false;
+
+	for (i = 0; i < ARRAY_SIZE(checks); i++) {
+		if (checks[i](CCI_REG_ADDR(reg->reg),
+			      CCI_REG_WIDTH_BYTES(reg->reg), reg->val))
+			return true;
+	}
+
+	return false;
+}
+
+static int imx500_set_inference_window(struct imx500 *imx500)
+{
+	u16 left, top, width, height;
+
+	if (!imx500->inference_window.width ||
+	    !imx500->inference_window.height) {
+		width = 4056;
+		height = 3040;
+		left = 0;
+		top = 0;
+	} else {
+		width = min_t(u16, imx500->inference_window.width, 4056);
+		height = min_t(u16, imx500->inference_window.height, 3040);
+		left = min_t(u16, imx500->inference_window.left, 4056);
+		top = min_t(u16, imx500->inference_window.top, 3040);
+	}
+
+	const struct cci_reg_sequence window_regs[] = {
+		{ IMX500_REG_DWP_AP_VC_HOFF, left },
+		{ IMX500_REG_DWP_AP_VC_VOFF, top },
+		{ IMX500_REG_DWP_AP_VC_HSIZE, width },
+		{ IMX500_REG_DWP_AP_VC_VSIZE, height },
+	};
+
+	return cci_multi_reg_write(imx500->regmap, window_regs,
+				   ARRAY_SIZE(window_regs), NULL);
+}
+
+static int imx500_get_device_id(struct imx500 *imx500, u32 *device_id)
+{
+	const u32 addr = 0xd040;
+	unsigned int i;
+	int ret = 0;
+	u64 tmp, data;
+
+	for (i = 0; i < 4; i++) {
+		ret = cci_read(imx500->regmap, CCI_REG32(addr + i * 4), &tmp,
+			       NULL);
+		if (ret)
+			return -EREMOTEIO;
+		data = tmp & 0xffffffff;
+		device_id[i] = data;
+	}
+
+	return ret;
+}
+
+static int imx500_reg_val_write_cbk(void *arg,
+				    const struct cci_reg_sequence *reg)
+{
+	struct imx500 *imx500 = arg;
+
+	if (!imx500_validate_inference_register(reg))
+		return -EINVAL;
+
+	return cci_write(imx500->regmap, reg->reg, reg->val, NULL);
+}
+
+/* Get bayer order based on flip setting. */
+static u32 imx500_get_format_code(struct imx500 *imx500)
+{
+	unsigned int i;
+
+	lockdep_assert_held(&imx500->mutex);
+
+	i = (imx500->vflip->val ? 2 : 0) | (imx500->hflip->val ? 1 : 0);
+
+	return codes[i];
+}
+
+static void imx500_set_default_format(struct imx500 *imx500)
+{
+	/* Set default mode to max resolution */
+	imx500->mode = &imx500_supported_modes[0];
+	imx500->fmt_code = MEDIA_BUS_FMT_SRGGB10_1X10;
+}
+
+/* -1 on fail, block size on success */
+static int imx500_validate_fw_block(const char *data, size_t maxlen)
+{
+	const size_t header_size = 32;
+	static const char header_id[] = { '9', '4', '6', '4' };
+
+	const size_t footer_size = 64;
+	static const char footer_id[] = { '3', '6', '9', '5' };
+
+	u32 data_size;
+	u32 extra_bytes_size = 0;
+
+	const char *end = data + maxlen;
+
+	if (!data)
+		return -1;
+
+	if (maxlen < header_size)
+		return -1;
+
+	if (memcmp(data, &header_id, sizeof(header_id)))
+		return -1;
+
+	/* data_size is size of header + body */
+	memcpy(&data_size, data + sizeof(header_id), sizeof(data_size));
+	data_size = ___constant_swab32(data_size);
+
+	/* check the device_lock flag */
+	extra_bytes_size = *((u8 *)(data + 0x0e)) & 0x01 ? 32 : 0;
+
+	if (end - data_size - footer_size - extra_bytes_size < data)
+		return -1;
+	if (memcmp(data + data_size + footer_size - sizeof(footer_id),
+		   &footer_id, sizeof(footer_id)))
+		return -1;
+
+	return data_size + footer_size + extra_bytes_size;
+}
+
+/* Parse fw block by block, returning total valid fw size */
+static size_t imx500_valid_fw_bytes(const u8 *fw,
+				    const size_t fw_size)
+{
+	int i;
+	size_t bytes = 0;
+
+	const u8 *data = fw;
+	size_t size = fw_size;
+
+	while ((i = imx500_validate_fw_block(data, size)) > 0) {
+		bytes += i;
+		data += i;
+		size -= i;
+	}
+
+	return bytes;
+}
+
+static int imx500_iterate_nw_regs(
+	const u8 *fw, size_t fw_size, void *arg,
+	int (*cbk)(void *arg, const struct cci_reg_sequence *reg))
+{
+	struct cpio_data cd = { NULL, 0, "" };
+	const u8 *read_pos;
+	size_t entries;
+	size_t size;
+
+	if (!fw || !cbk)
+		return -EINVAL;
+
+	size = imx500_valid_fw_bytes(fw, fw_size);
+	cd = find_cpio_data("imx500_regs", (void *)(fw + size),
+			    fw_size - size, NULL);
+	if (!cd.data || cd.size % 7)
+		return -EINVAL;
+
+	read_pos = cd.data;
+	entries = cd.size / 7;
+
+	while (entries--) {
+		struct cci_reg_sequence reg = { 0, 0 };
+		u16 addr;
+		u8 len;
+		u32 val;
+		int ret;
+
+		memcpy(&addr, read_pos, sizeof(addr));
+		read_pos += sizeof(addr);
+		memcpy(&len, read_pos, sizeof(len));
+		read_pos += sizeof(len);
+		memcpy(&val, read_pos, sizeof(val));
+		read_pos += sizeof(val);
+
+		reg.reg = ((len << CCI_REG_WIDTH_SHIFT) | addr);
+		reg.val = val;
+
+		ret = cbk(arg, &reg);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int imx500_reg_tensor_lines_cbk(void *arg,
+				       const struct cci_reg_sequence *reg)
+{
+	u16 *tensor_lines = arg;
+
+	if (reg->val < 2046) {
+		switch (reg->reg) {
+		case IMX500_REG_DD_CH07_Y_OUT_SIZE:
+			tensor_lines[0] = reg->val;
+			break;
+		case IMX500_REG_DD_CH08_Y_OUT_SIZE:
+			tensor_lines[1] = reg->val;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static void imx500_calc_inference_lines(struct imx500 *imx500)
+{
+	u16 tensor_lines[2] = { 0, 0 };
+
+	if (!imx500->fw_network) {
+		imx500->num_inference_lines = 0;
+		return;
+	}
+
+	imx500_iterate_nw_regs(imx500->fw_network, imx500->fw_network_size,
+			       tensor_lines, imx500_reg_tensor_lines_cbk);
+
+	/* Full-res mode, embedded lines are actually slightly shorter than inference
+	 * lines 2544 vs 2560 (over-allocate with inf. width)
+	 */
+	imx500->num_inference_lines = IMX500_NUM_KPI_LINES +
+				      IMX500_NUM_PQ_LINES + tensor_lines[0] +
+				      tensor_lines[1];
+}
+
+static void imx500_adjust_exposure_range(struct imx500 *imx500)
+{
+	int exposure_max, exposure_def;
+
+	/* Honour the VBLANK limits when setting exposure. */
+	exposure_max = imx500->mode->height + imx500->vblank->val -
+		       IMX500_EXPOSURE_OFFSET;
+	exposure_def = min(exposure_max, imx500->exposure->val);
+	__v4l2_ctrl_modify_range(imx500->exposure, imx500->exposure->minimum,
+				 exposure_max, imx500->exposure->step,
+				 exposure_def);
+}
+
+static int imx500_set_frame_length(struct imx500 *imx500, unsigned int val)
+{
+	int ret = 0;
+
+	imx500->long_exp_shift = 0;
+
+	while (val > IMX500_FRAME_LENGTH_MAX) {
+		imx500->long_exp_shift++;
+		val >>= 1;
+	}
+
+	ret = cci_write(imx500->regmap, IMX500_REG_FRAME_LENGTH, val, NULL);
+	if (ret)
+		return ret;
+
+	ret = cci_write(imx500->regmap, IMX500_LONG_EXP_CIT_SHIFT_REG,
+			imx500->long_exp_shift, NULL);
+	if (ret)
+		return ret;
+
+	return cci_write(imx500->regmap, IMX500_LONG_EXP_SHIFT_REG,
+			 imx500->long_exp_shift, NULL);
+}
+
+/* reg is both input and output:
+ * reg->val is the value we're polling until we're NEQ to
+ * It is then populated with the updated value.
+ */
+static int __must_check imx500_poll_status_reg(struct imx500 *state,
+					       struct cci_reg_sequence *reg,
+					       u8 timeout)
+{
+	u64 read_value;
+	int ret;
+
+	while (timeout) {
+		ret = cci_read(state->regmap, reg->reg, &read_value, NULL);
+		if (ret)
+			return ret;
+
+		if (read_value != reg->val) {
+			reg->val = read_value;
+			return 0;
+		}
+
+		timeout--;
+		mdelay(50);
+	}
+	return -EAGAIN;
+}
+
+static int imx500_prepare_poll_cmd_reply_sts(struct imx500 *imx500,
+					     struct cci_reg_sequence *cmd_reply)
+{
+	/* Perform single-byte read of 4-byte IMX500_REG_DD_REF_STS register to
+	 * target CMD_REPLY_STS_CNT sub-register
+	 */
+	cmd_reply->reg = CCI_REG8(CCI_REG_ADDR(IMX500_REG_DD_REF_STS));
+
+	return cci_read(imx500->regmap, cmd_reply->reg, &cmd_reply->val, NULL);
+}
+
+static int imx500_clear_weights(struct imx500 *imx500)
+{
+	struct cci_reg_sequence cmd_reply_sts_cnt_reg;
+	u64 imx500_fsm_state;
+	u64 cmd_reply;
+	int ret;
+
+	static const struct cci_reg_sequence request_clear[] = {
+		{ IMX500_REG_DD_ST_TRANS_CMD,
+		  IMX500_DD_ST_TRANS_CMD_CLEAR_WEIGHTS },
+		{ IMX500_REG_DD_CMD_INT, IMX500_DD_CMD_INT_ST_TRANS },
+	};
+
+	if (imx500->fsm_state != IMX500_STATE_WITH_NETWORK)
+		return -EINVAL;
+
+	ret = cci_read(imx500->regmap, IMX500_REG_DD_SYS_STATE,
+		       &imx500_fsm_state, NULL);
+	if (ret || imx500_fsm_state != IMX500_DD_SYS_STATE_STANDBY_WITH_NETWORK)
+		return ret ? ret : -EREMOTEIO;
+
+	ret = imx500_prepare_poll_cmd_reply_sts(imx500, &cmd_reply_sts_cnt_reg);
+	if (ret)
+		return ret;
+
+	ret = cci_multi_reg_write(imx500->regmap, request_clear,
+				  ARRAY_SIZE(request_clear), NULL);
+	if (ret)
+		return ret;
+
+	ret = imx500_poll_status_reg(imx500, &cmd_reply_sts_cnt_reg, 5);
+	if (ret)
+		return ret;
+
+	ret = cci_read(imx500->regmap, IMX500_REG_DD_CMD_REPLY_STS, &cmd_reply,
+		       NULL);
+	if (ret || cmd_reply != IMX500_DD_CMD_REPLY_STS_TRANS_DONE)
+		return ret ? ret : -EREMOTEIO;
+
+	imx500->fsm_state = IMX500_STATE_WITHOUT_NETWORK;
+	imx500->network_written = false;
+	return 0;
+}
+
+static void imx500_clear_fw_network(struct imx500 *imx500)
+{
+	/* Remove any previous firmware blob. */
+	if (imx500->fw_network)
+		vfree(imx500->fw_network);
+
+	imx500->fw_network = NULL;
+	imx500->network_written = false;
+	imx500->fw_progress = 0;
+	v4l2_ctrl_activate(imx500->device_id, false);
+}
+
+static int __must_check imx500_spi_write(struct imx500 *state, const u8 *data,
+					 size_t size)
+{
+	if (size % 4)
+		return -EINVAL;
+
+	if (!state->spi_device)
+		return -ENODEV;
+
+	return spi_write(state->spi_device, data, size);
+}
+
+static int imx500_injection_wait_transfer_complete(struct imx500 *imx500)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
+	struct cci_reg_sequence hndsk_reg = {
+		IMX500_REG_DD_DAT_INJECTION_HNDSK,
+		IMX500_DD_DAT_INJECTION_HNDSK_TRANSFERRING
+	};
+	int ret;
+
+	/* Continue polling until we reach the final TRANS_COMP state */
+	while (hndsk_reg.val != IMX500_DD_DAT_INJECTION_HNDSK_TRANS_COMP) {
+		ret = imx500_poll_status_reg(imx500, &hndsk_reg, 10);
+		if (ret) {
+			dev_err(&client->dev,
+				"Handshake register did not update from state %llu\n",
+				hndsk_reg.val);
+			return ret;
+		}
+
+		/* Check if we've reached the final completion state */
+		if (hndsk_reg.val == IMX500_DD_DAT_INJECTION_HNDSK_TRANS_COMP) {
+			break;
+		} else if (hndsk_reg.val == IMX500_DD_DAT_INJECTION_HNDSK_TRANS_COMP_WAIT ||
+			   hndsk_reg.val == IMX500_DD_DAT_INJECTION_HNDSK_TRANS_COMP_WAIT_CHKSUM) {
+			continue;
+		} else if (hndsk_reg.val ==
+			   IMX500_DD_DAT_INJECTION_HNDSK_TRANS_ERROR) {
+			dev_err(&client->dev,
+				"Handshake register indicates transfer error\n");
+			return -EREMOTEIO;
+		} else if (hndsk_reg.val ==
+			   IMX500_DD_DAT_INJECTION_HNDSK_TRANS_ERROR_WAIT_NOTIFY) {
+			dev_err(&client->dev,
+				"Handshake register indicates transfer error (waiting for notification)\n");
+			return -EREMOTEIO;
+		}
+
+		/* Unexpected state */
+		dev_err(&client->dev,
+			"Handshake register in unexpected state %llu\n",
+			hndsk_reg.val);
+		return -EREMOTEIO;
+	}
+
+	return 0;
+}
+
+static u32 imx500_calc_injection_checksum(const u32 *data, size_t size)
+{
+	u32 checksum = 0;
+
+	while (size--) {
+		u32 val = *data++;
+
+		checksum += val;
+	}
+
+	return checksum;
+}
+
+static int imx500_inject_input_tensor(struct imx500 *imx500, const u32 *data,
+				      size_t size)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
+
+	int ret;
+	u64 cmp_frm;
+	u8 exp_frm;
+
+	u32 checksum = imx500_calc_injection_checksum(data, size);
+
+	ret = cci_write(imx500->regmap, IMX500_REG_DD_DAT_INJECTION_CHKSUM,
+			checksum, NULL);
+	if (ret) {
+		dev_err(&client->dev, "Failed to write checksum\n");
+		return ret;
+	}
+
+	ret = cci_write(imx500->regmap, IMX500_REG_STRM_MODE_SEL, 0, NULL);
+	if (ret) {
+		dev_err(&client->dev, "Failed to stop DNN processing\n");
+		return ret;
+	}
+
+	/* The handshake register (IMX500_REG_DD_DAT_INJECTION_HNDSK) is used bidirectionally:
+	 * - We write a state to request a transition (e.g. READY_WAIT)
+	 * - The sensor responds by updating the register with the new state (e.g. READY_COMP)
+	 * This allows us to coordinate state transitions between the host and sensor.
+	 */
+	struct cci_reg_sequence hndsk_reg = {
+		IMX500_REG_DD_DAT_INJECTION_HNDSK,
+		IMX500_DD_DAT_INJECTION_HNDSK_TRANS_READY_WAIT
+	};
+
+	ret = cci_multi_reg_write(imx500->regmap, &hndsk_reg, 1, NULL);
+	if (ret) {
+		dev_err(&client->dev, "Failed to inject state transition\n");
+		return ret;
+	}
+
+	ret = imx500_poll_status_reg(imx500, &hndsk_reg, 5);
+	if (ret) {
+		dev_err(&client->dev,
+			"Handshake register did not update after READY_WAIT request\n");
+		return ret;
+	}
+
+	if (hndsk_reg.val != IMX500_DD_DAT_INJECTION_HNDSK_TRANS_READY_COMP) {
+		dev_err(&client->dev,
+			"Failed to transition to READY_COMP state: %llu\n",
+			hndsk_reg.val);
+		return -EREMOTEIO;
+	}
+
+	ret = cci_write(imx500->regmap, IMX500_REG_DD_DAT_INJECTION_HNDSK,
+			IMX500_DD_DAT_INJECTION_HNDSK_TRANSFERRING, NULL);
+	if (ret) {
+		dev_err(&client->dev,
+			"Failed to request transition to TRANSFERRING state\n");
+		return ret;
+	}
+
+	for (size_t i = 0; i < size; i++)
+		*(u32 *)(data + i) = ___constant_swab32(data[i]);
+
+	if (imx500->led_gpio)
+		gpiod_set_value_cansleep(imx500->led_gpio, 1);
+
+	ret = imx500_spi_write(imx500, (const u8 *)data, size * 4);
+	if (ret) {
+		dev_err(&client->dev, "SPI transfer of input tensor failed\n");
+		return ret;
+	}
+
+	if (imx500->led_gpio)
+		gpiod_set_value_cansleep(imx500->led_gpio, 0);
+
+	ret = imx500_injection_wait_transfer_complete(imx500);
+	if (ret) {
+		dev_err(&client->dev, "Input tensor transfer failed to complete\n");
+		return ret;
+	}
+
+	ret = cci_read(imx500->regmap, IMX500_REG_DD_DAT_INJECTION_SPI_CMP_FRM,
+		       &cmp_frm, NULL);
+	if (ret) {
+		dev_err(&client->dev,
+			"Failed to read injection frame comparison register\n");
+		return ret;
+	}
+	exp_frm = cmp_frm;
+	exp_frm += 2 << (exp_frm > 254);
+	imx500->current_injection_cmp_frm = exp_frm;
+	dev_info(&client->dev, "injection_cmp_frm set to %u\n", exp_frm);
+
+	ret = cci_write(imx500->regmap, IMX500_REG_STRM_MODE_SEL, 4, NULL);
+	if (ret)
+		dev_err(&client->dev, "Failed to enable DNN processing\n");
+
+	return ret;
+}
+
+static int imx500_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct imx500 *imx500 =
+		container_of(ctrl->handler, struct imx500, ctrl_handler);
+	struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
+	int ret = 0;
+
+	const u8 *tensor_data;
+	size_t tensor_data_size;
+
+	if (ctrl->id == V4L2_CID_USER_IMX500_NETWORK_FW_FD) {
+		/* Reset state of the control. */
+		if (ctrl->val < 0) {
+			return 0;
+		} else if (ctrl->val == S32_MAX) {
+			ctrl->val = -1;
+			if (pm_runtime_get_if_in_use(&client->dev) == 0)
+				return 0;
+
+			if (imx500->network_written)
+				ret = imx500_clear_weights(imx500);
+			imx500_clear_fw_network(imx500);
+
+			pm_runtime_mark_last_busy(&client->dev);
+			pm_runtime_put_autosuspend(&client->dev);
+
+			return ret;
+		}
+
+		imx500_clear_fw_network(imx500);
+		ret = kernel_read_file_from_fd(ctrl->val, 0,
+					       (void **)&imx500->fw_network, INT_MAX,
+					       &imx500->fw_network_size,
+					       1);
+		/*
+		 * Back to reset state, the FD cannot be considered valid after
+		 * this IOCTL completes.
+		 */
+		ctrl->val = -1;
+
+		if (ret < 0) {
+			dev_err(&client->dev, "%s failed to read fw image: %d\n",
+				__func__, ret);
+			imx500_clear_fw_network(imx500);
+			return ret;
+		}
+		if (ret != imx500->fw_network_size) {
+			dev_err(&client->dev, "%s read fw image size mismatich: got %u, expected %zu\n",
+				__func__, ret, imx500->fw_network_size);
+			imx500_clear_fw_network(imx500);
+			return -EIO;
+		}
+
+		imx500_calc_inference_lines(imx500);
+		return 0;
+	}
+
+	if (ctrl->id == V4L2_CID_USER_IMX500_INPUT_TENSOR_FD) {
+		if (!imx500->streaming || !imx500->fw_network ||
+		    !imx500->tensor_injection) {
+			if (ctrl->val == -1)
+				return 0;
+
+			dev_err(&client->dev,
+				"Input tensor injection requires streaming, network and injection to be enabled\n");
+			return -EINVAL;
+		}
+
+		ret = kernel_read_file_from_fd(ctrl->val, 0,
+					       (void **)&tensor_data, INT_MAX,
+					       &tensor_data_size, 1);
+
+		if (ret < 0) {
+			dev_err(&client->dev,
+				"%s failed to read tensor data: %d\n", __func__,
+				ret);
+			return ret;
+		}
+
+		if (tensor_data_size % 4 != 0) {
+			dev_err(&client->dev,
+				"%s tensor data size must be multiple of 4, got %zu\n",
+				__func__, tensor_data_size);
+			vfree(tensor_data);
+			return -EINVAL;
+		}
+
+		ret = imx500_inject_input_tensor(imx500, (u32 *)tensor_data,
+						 tensor_data_size / 4);
+
+		vfree(tensor_data);
+
+		ctrl->val = -1;
+		return ret;
+	}
+
+	if (ctrl->id == V4L2_CID_USER_IMX500_ENABLE_INJECTION) {
+		/*
+		 * tensor_injection is latched-enabled as no deinitialisation is possible
+		 * without reset
+		 */
+		if (ctrl->val && !imx500->tensor_injection)
+			imx500->tensor_injection = ctrl->val;
+
+		return 0;
+	}
+
+	/*
+	 * The VBLANK control may change the limits of usable exposure, so check
+	 * and adjust if necessary.
+	 */
+	if (ctrl->id == V4L2_CID_VBLANK)
+		imx500_adjust_exposure_range(imx500);
+
+	/*
+	 * Applying V4L2 control value only happens
+	 * when power is up for streaming
+	 */
+	if (pm_runtime_get_if_in_use(&client->dev) == 0)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_ANALOGUE_GAIN:
+		ret = cci_write(imx500->regmap, IMX500_REG_ANALOG_GAIN,
+				ctrl->val, NULL);
+		break;
+	case V4L2_CID_EXPOSURE:
+		ret = cci_write(imx500->regmap, IMX500_REG_EXPOSURE,
+				ctrl->val >> imx500->long_exp_shift, NULL);
+		break;
+	case V4L2_CID_HFLIP:
+	case V4L2_CID_VFLIP:
+		ret = cci_write(imx500->regmap, IMX500_REG_ORIENTATION,
+				imx500->hflip->val | imx500->vflip->val << 1,
+				NULL);
+		break;
+	case V4L2_CID_VBLANK:
+		ret = imx500_set_frame_length(imx500,
+					      imx500->mode->height + ctrl->val);
+		break;
+	case V4L2_CID_HBLANK:
+		ret = cci_write(imx500->regmap, IMX500_REG_LINE_LENGTH,
+				imx500->mode->width + ctrl->val, NULL);
+		break;
+	case V4L2_CID_NOTIFY_GAINS:
+		ret = cci_write(imx500->regmap, IMX500_REG_COLOUR_BALANCE_B,
+				ctrl->p_new.p_u32[0], NULL);
+		cci_write(imx500->regmap, IMX500_REG_COLOUR_BALANCE_GB,
+			  ctrl->p_new.p_u32[1], &ret);
+		cci_write(imx500->regmap, IMX500_REG_COLOUR_BALANCE_GR,
+			  ctrl->p_new.p_u32[2], &ret);
+		cci_write(imx500->regmap, IMX500_REG_COLOUR_BALANCE_R,
+			  ctrl->p_new.p_u32[3], &ret);
+		break;
+	case V4L2_CID_USER_IMX500_INFERENCE_WINDOW:
+		memcpy(&imx500->inference_window, ctrl->p_new.p_u32,
+		       sizeof(struct v4l2_rect));
+		ret = imx500_set_inference_window(imx500);
+		break;
+	default:
+		dev_info(&client->dev,
+			 "ctrl(id:0x%x,val:0x%x) is not handled\n", ctrl->id,
+			 ctrl->val);
+		ret = -EINVAL;
+		break;
+	}
+
+	pm_runtime_mark_last_busy(&client->dev);
+	pm_runtime_put_autosuspend(&client->dev);
+
+	return ret;
+}
+
+static int imx500_get_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct imx500 *imx500 = container_of(ctrl->handler, struct imx500,
+					     ctrl_handler);
+	struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
+	u32 device_id[4] = {0};
+	int ret;
+
+	switch (ctrl->id) {
+	case V4L2_CID_USER_GET_IMX500_DEVICE_ID:
+		ret = imx500_get_device_id(imx500, device_id);
+		memcpy(ctrl->p_new.p_u32, device_id, sizeof(device_id));
+		break;
+	case V4L2_CID_USER_IMX500_INJECTION_CMP_FRM:
+		ctrl->val = imx500->current_injection_cmp_frm;
+		dev_dbg(&client->dev, "Injection comparison frame: %u\n",
+			ctrl->val);
+		ret = 0;
+		break;
+	default:
+		dev_info(&client->dev, "ctrl(id:0x%x,val:0x%x) is not handled\n",
+			 ctrl->id, ctrl->val);
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops imx500_ctrl_ops = {
+	.g_volatile_ctrl = imx500_get_ctrl,
+	.s_ctrl = imx500_set_ctrl,
+};
+
+static const struct v4l2_ctrl_config imx500_notify_gains_ctrl = {
+	.ops = &imx500_ctrl_ops,
+	.id = V4L2_CID_NOTIFY_GAINS,
+	.type = V4L2_CTRL_TYPE_U32,
+	.min = IMX500_COLOUR_BALANCE_MIN,
+	.max = IMX500_COLOUR_BALANCE_MAX,
+	.step = IMX500_COLOUR_BALANCE_STEP,
+	.def = IMX500_COLOUR_BALANCE_DEFAULT,
+	.dims = { 4 },
+	.elem_size = sizeof(u32),
+};
+
+static int imx500_enum_mbus_code(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *sd_state,
+				 struct v4l2_subdev_mbus_code_enum *code)
+{
+	struct imx500 *imx500 = to_imx500(sd);
+
+	if (code->pad >= NUM_PADS)
+		return -EINVAL;
+
+	if (code->pad == IMAGE_PAD) {
+		if (code->index != 0)
+			return -EINVAL;
+
+		code->code = imx500_get_format_code(imx500);
+	} else {
+		if (code->index > 0)
+			return -EINVAL;
+
+		code->code = MEDIA_BUS_FMT_SENSOR_DATA;
+	}
+
+	return 0;
+}
+
+static int imx500_enum_frame_size(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *sd_state,
+				  struct v4l2_subdev_frame_size_enum *fse)
+{
+	struct imx500 *imx500 = to_imx500(sd);
+
+	if (fse->pad >= NUM_PADS)
+		return -EINVAL;
+
+	if (fse->pad == IMAGE_PAD) {
+		const struct imx500_mode *mode_list = imx500_supported_modes;
+		unsigned int num_modes = ARRAY_SIZE(imx500_supported_modes);
+
+		if (fse->index >= num_modes)
+			return -EINVAL;
+
+		if (fse->code != imx500_get_format_code(imx500))
+			return -EINVAL;
+
+		fse->min_width = mode_list[fse->index].width;
+		fse->max_width = fse->min_width;
+		fse->min_height = mode_list[fse->index].height;
+		fse->max_height = fse->min_height;
+	} else {
+		if (fse->code != MEDIA_BUS_FMT_SENSOR_DATA || fse->index > 0)
+			return -EINVAL;
+
+		fse->min_width = IMX500_MAX_EMBEDDED_SIZE +
+				 imx500->num_inference_lines *
+					 IMX500_INFERENCE_LINE_WIDTH;
+		fse->max_width = fse->min_width;
+		fse->min_height = 1;
+		fse->max_height = fse->min_height;
+	}
+
+	return 0;
+}
+
+static void imx500_update_image_pad_format(struct imx500 *imx500,
+					   const struct imx500_mode *mode,
+					   struct v4l2_subdev_format *fmt)
+{
+	fmt->format.width = mode->width;
+	fmt->format.height = mode->height;
+	fmt->format.field = V4L2_FIELD_NONE;
+	fmt->format.colorspace = V4L2_COLORSPACE_RAW;
+	fmt->format.ycbcr_enc =
+		V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->format.colorspace);
+	fmt->format.quantization = V4L2_MAP_QUANTIZATION_DEFAULT(
+		true, fmt->format.colorspace, fmt->format.ycbcr_enc);
+	fmt->format.xfer_func =
+		V4L2_MAP_XFER_FUNC_DEFAULT(fmt->format.colorspace);
+}
+
+static void imx500_update_metadata_pad_format(const struct imx500 *imx500,
+					      struct v4l2_subdev_format *fmt)
+{
+	fmt->format.width =
+		IMX500_MAX_EMBEDDED_SIZE +
+		imx500->num_inference_lines * IMX500_INFERENCE_LINE_WIDTH;
+	fmt->format.height = 1;
+	fmt->format.code = MEDIA_BUS_FMT_SENSOR_DATA;
+	fmt->format.field = V4L2_FIELD_NONE;
+}
+
+static int imx500_get_pad_format(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *sd_state,
+				 struct v4l2_subdev_format *fmt)
+{
+	struct imx500 *imx500 = to_imx500(sd);
+
+	if (fmt->pad >= NUM_PADS)
+		return -EINVAL;
+
+	mutex_lock(&imx500->mutex);
+
+	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+		struct v4l2_mbus_framefmt *try_fmt = v4l2_subdev_state_get_format(
+			sd_state, fmt->pad);
+		/* update the code which could change due to vflip or hflip */
+		try_fmt->code = fmt->pad == IMAGE_PAD ?
+					imx500_get_format_code(imx500) :
+					MEDIA_BUS_FMT_SENSOR_DATA;
+		fmt->format = *try_fmt;
+	} else {
+		if (fmt->pad == IMAGE_PAD) {
+			imx500_update_image_pad_format(imx500, imx500->mode,
+						       fmt);
+			fmt->format.code = imx500_get_format_code(imx500);
+		} else {
+			imx500_update_metadata_pad_format(imx500, fmt);
+		}
+	}
+
+	mutex_unlock(&imx500->mutex);
+	return 0;
+}
+
+static void imx500_set_framing_limits(struct imx500 *imx500)
+{
+	unsigned int hblank_min;
+	const struct imx500_mode *mode = imx500->mode;
+
+	/* Default to no long exposure multiplier. */
+	imx500->long_exp_shift = 0;
+
+	/* Update limits and set FPS to default */
+	__v4l2_ctrl_modify_range(
+		imx500->vblank, IMX500_VBLANK_MIN,
+		((1 << IMX500_LONG_EXP_SHIFT_MAX) * IMX500_FRAME_LENGTH_MAX) -
+			mode->height, 1, IMX500_VBLANK_MIN);
+
+	/* Setting this will adjust the exposure limits as well. */
+	__v4l2_ctrl_s_ctrl(imx500->vblank, IMX500_VBLANK_MIN);
+
+	hblank_min = mode->line_length_pix - mode->width;
+	__v4l2_ctrl_modify_range(imx500->hblank, hblank_min, hblank_min, 1,
+				 hblank_min);
+	__v4l2_ctrl_s_ctrl(imx500->hblank, hblank_min);
+}
+
+static int imx500_set_pad_format(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *sd_state,
+				 struct v4l2_subdev_format *fmt)
+{
+	struct v4l2_mbus_framefmt *framefmt;
+	const struct imx500_mode *mode;
+	struct imx500 *imx500 = to_imx500(sd);
+
+	if (fmt->pad >= NUM_PADS)
+		return -EINVAL;
+
+	mutex_lock(&imx500->mutex);
+
+	if (fmt->pad == IMAGE_PAD) {
+		const struct imx500_mode *mode_list = imx500_supported_modes;
+		unsigned int num_modes = ARRAY_SIZE(imx500_supported_modes);
+
+		/* Bayer order varies with flips */
+		fmt->format.code = imx500_get_format_code(imx500);
+
+		mode = v4l2_find_nearest_size(mode_list, num_modes, width,
+					      height, fmt->format.width,
+					      fmt->format.height);
+		imx500_update_image_pad_format(imx500, mode, fmt);
+		if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+			framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad);
+			*framefmt = fmt->format;
+		} else if (imx500->mode != mode) {
+			imx500->mode = mode;
+			imx500->fmt_code = fmt->format.code;
+			imx500_set_framing_limits(imx500);
+		}
+	} else {
+		if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+			framefmt = v4l2_subdev_state_get_format(sd_state, fmt->pad);
+			*framefmt = fmt->format;
+		} else {
+			/* Only one embedded data mode is supported */
+			imx500_update_metadata_pad_format(imx500, fmt);
+		}
+	}
+
+	mutex_unlock(&imx500->mutex);
+
+	return 0;
+}
+
+static const struct v4l2_rect *
+__imx500_get_pad_crop(struct imx500 *imx500, struct v4l2_subdev_state *sd_state,
+		      unsigned int pad, enum v4l2_subdev_format_whence which)
+{
+	switch (which) {
+	case V4L2_SUBDEV_FORMAT_TRY:
+		return v4l2_subdev_state_get_crop(sd_state, pad);
+	case V4L2_SUBDEV_FORMAT_ACTIVE:
+		return &imx500->mode->crop;
+	}
+
+	return NULL;
+}
+
+static int imx500_get_selection(struct v4l2_subdev *sd,
+				struct v4l2_subdev_state *sd_state,
+				struct v4l2_subdev_selection *sel)
+{
+	switch (sel->target) {
+	case V4L2_SEL_TGT_CROP: {
+		struct imx500 *imx500 = to_imx500(sd);
+
+		mutex_lock(&imx500->mutex);
+		sel->r = *__imx500_get_pad_crop(imx500, sd_state, sel->pad,
+						sel->which);
+		mutex_unlock(&imx500->mutex);
+
+		return 0;
+	}
+
+	case V4L2_SEL_TGT_NATIVE_SIZE:
+		sel->r.left = 0;
+		sel->r.top = 0;
+		sel->r.width = IMX500_NATIVE_WIDTH;
+		sel->r.height = IMX500_NATIVE_HEIGHT;
+
+		return 0;
+
+	case V4L2_SEL_TGT_CROP_DEFAULT:
+	case V4L2_SEL_TGT_CROP_BOUNDS:
+		sel->r.left = IMX500_PIXEL_ARRAY_LEFT;
+		sel->r.top = IMX500_PIXEL_ARRAY_TOP;
+		sel->r.width = IMX500_PIXEL_ARRAY_WIDTH;
+		sel->r.height = IMX500_PIXEL_ARRAY_HEIGHT;
+
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int imx500_data_injection_init(struct imx500 *imx500)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
+
+	int ret;
+
+	ret = cci_write(imx500->regmap, IMX500_REG_LEV_PL_MDET_OUT, 1, NULL);
+	if (ret) {
+		dev_err(&client->dev,
+			"Failed to write LEV_PL_MDET_OUT register\n");
+		return ret;
+	}
+
+	ret = cci_write(imx500->regmap, IMX500_REG_DD_DAT_INJECTION_HNDSK,
+			IMX500_DD_DAT_INJECTION_HNDSK_TRANS_INIT_WAIT, NULL);
+	if (ret) {
+		dev_err(&client->dev, "Failed to write HNDSK register\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int imx500_data_injection_init_wait(struct imx500 *imx500)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
+
+	int ret;
+
+	struct cci_reg_sequence hndsk_reg = {
+		IMX500_REG_DD_DAT_INJECTION_HNDSK,
+		IMX500_DD_DAT_INJECTION_HNDSK_TRANS_INIT_WAIT
+	};
+
+	ret = imx500_poll_status_reg(imx500, &hndsk_reg, 40);
+	if (ret) {
+		dev_err(&client->dev,
+			"Handshake register did not update after INIT_WAIT request\n");
+		return ret;
+	}
+
+	if (hndsk_reg.val != IMX500_DD_DAT_INJECTION_HNDSK_TRANS_INIT_COMP) {
+		dev_err(&client->dev,
+			"Failed to transition to INIT_COMP state: %llu\n",
+			hndsk_reg.val);
+		return -EREMOTEIO;
+	}
+
+	return 0;
+}
+
+/* Moves the IMX500 internal state machine between states or updates.
+ *
+ * Prerequisites: Sensor is powered on and not currently streaming
+ */
+static int imx500_state_transition(struct imx500 *imx500, const u8 *fw,
+				   size_t fw_size, enum imx500_image_type type,
+				   bool update)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
+	struct cci_reg_sequence cmd_reply_sts_cnt_reg;
+	size_t valid_size;
+	int ret;
+	u64 tmp;
+
+	if (!imx500 || !fw || type >= TYPE_MAX)
+		return -EINVAL;
+
+	if (!update && (int)type != (int)imx500->fsm_state)
+		return -EINVAL;
+
+	/* Validate firmware */
+	valid_size = imx500_valid_fw_bytes(fw, fw_size);
+	if (!valid_size)
+		return -EINVAL;
+
+	ret = imx500_prepare_poll_cmd_reply_sts(imx500, &cmd_reply_sts_cnt_reg);
+	if (ret)
+		return ret;
+
+	struct cci_reg_sequence common_regs[] = {
+		{ IMX500_REG_DD_FLASH_TYPE, 0x02 },
+		{ IMX500_REG_DD_LOAD_MODE, IMX500_DD_LOAD_MODE_AP },
+		{ IMX500_REG_DD_IMAGE_TYPE, type },
+		{ IMX500_REG_DD_DOWNLOAD_DIV_NUM, (valid_size - 1) / ONE_MIB },
+		{ IMX500_REG_DD_DOWNLOAD_FILE_SIZE, valid_size },
+	};
+
+	struct cci_reg_sequence state_transition_regs[] = {
+		{ IMX500_REG_DD_ST_TRANS_CMD, type },
+		{ IMX500_REG_DD_CMD_INT, IMX500_DD_CMD_INT_ST_TRANS },
+	};
+
+	struct cci_reg_sequence update_regs[] = {
+		{ IMX500_REG_DD_UPDATE_CMD, IMX500_DD_UPDATE_CMD_SRAM },
+		{ IMX500_REG_DD_CMD_INT, IMX500_DD_CMD_INT_UPDATE },
+	};
+
+	ret = cci_multi_reg_write(imx500->regmap, common_regs,
+				  ARRAY_SIZE(common_regs), NULL);
+
+	cci_multi_reg_write(imx500->regmap,
+			    update ? update_regs : state_transition_regs, 2,
+			    &ret);
+	if (ret)
+		return ret;
+
+	/* Poll CMD_REPLY_STS_CNT until a response is available */
+	ret = imx500_poll_status_reg(imx500, &cmd_reply_sts_cnt_reg, 5);
+	if (ret) {
+		dev_err(&client->dev, "DD_REF_STS register did not update\n");
+		return ret;
+	}
+
+	/* Read response to state transition / update request */
+	ret = cci_read(imx500->regmap, IMX500_REG_DD_CMD_REPLY_STS, &tmp, NULL);
+	if (ret || tmp != (update ? IMX500_DD_CMD_REPLY_STS_UPDATE_READY :
+				    IMX500_DD_CMD_REPLY_STS_TRANS_READY))
+		return ret ? ret : -EBUSY;
+
+	imx500->fw_stage = type;
+	imx500->fw_progress = 0;
+
+	for (size_t i = 0; i <= valid_size / ONE_MIB; i++) {
+		const u8 *data = fw + (i * ONE_MIB);
+		size_t size = valid_size - (i * ONE_MIB);
+		struct cci_reg_sequence download_sts_reg = {
+			IMX500_REG_DD_DOWNLOAD_STS,
+			IMX500_DD_DOWNLOAD_STS_DOWNLOADING,
+		};
+
+		/* Calculate SPI xfer size avoiding 0-sized TXNs */
+		size = min_t(size_t, size, ONE_MIB);
+		if (!size)
+			break;
+
+		/* Poll until device is ready for download */
+		ret = imx500_poll_status_reg(imx500, &download_sts_reg, 100);
+		if (ret) {
+			dev_err(&client->dev,
+				"DD_DOWNLOAD_STS was never ready\n");
+			return ret;
+		}
+
+		/* Do SPI transfer */
+		if (imx500->led_gpio)
+			gpiod_set_value_cansleep(imx500->led_gpio, 1);
+		ret = imx500_spi_write(imx500, data, size);
+		if (imx500->led_gpio)
+			gpiod_set_value_cansleep(imx500->led_gpio, 0);
+
+		imx500->fw_progress += size;
+
+		if (ret < 0)
+			return ret;
+	}
+
+	/* Poll until another response is available */
+	ret = imx500_poll_status_reg(imx500, &cmd_reply_sts_cnt_reg, 5);
+	if (ret) {
+		dev_err(&client->dev,
+			"DD_REF_STS register did not update after SPI write(s)\n");
+		return ret;
+	}
+
+	/* Verify that state transition / update completed successfully */
+	ret = cci_read(imx500->regmap, IMX500_REG_DD_CMD_REPLY_STS, &tmp, NULL);
+	if (ret || tmp != (update ? IMX500_DD_CMD_REPLY_STS_UPDATE_DONE :
+				    IMX500_DD_CMD_REPLY_STS_TRANS_DONE))
+		return ret ? ret : -EREMOTEIO;
+
+	if (!update && imx500->fsm_state < IMX500_STATE_WITH_NETWORK)
+		imx500->fsm_state++;
+
+	imx500->fw_progress = fw_size;
+
+	return 0;
+}
+
+static int imx500_transition_to_standby_wo_network(struct imx500 *imx500)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
+	const struct firmware *firmware;
+	u64 fw_ver;
+	int ret;
+
+	firmware = imx500->fw_loader;
+	ret = imx500_state_transition(imx500, firmware->data, firmware->size,
+				      TYPE_LOADER, false);
+	if (ret) {
+		dev_err(&client->dev, "%s: failed to load loader firmware\n",
+			__func__);
+		return ret;
+	}
+
+	firmware = imx500->fw_main;
+	ret = imx500_state_transition(imx500, firmware->data, firmware->size,
+				      TYPE_MAIN, false);
+	if (ret) {
+		dev_err(&client->dev, "%s: failed to load main firmware\n",
+			__func__);
+		return ret;
+	}
+
+	ret = cci_read(imx500->regmap, IMX500_REG_MAIN_FW_VERSION, &fw_ver,
+		       NULL);
+	if (ret) {
+		dev_err(&client->dev,
+			"%s: could not read main firmware version\n", __func__);
+		return ret;
+	}
+
+	dev_info(&client->dev,
+		 "main firmware version: %llu%llu.%llu%llu.%llu%llu\n",
+		 (fw_ver >> 20) & 0xF, (fw_ver >> 16) & 0xF,
+		 (fw_ver >> 12) & 0xF, (fw_ver >> 8) & 0xF, (fw_ver >> 4) & 0xF,
+		 fw_ver & 0xF);
+
+	ret = cci_multi_reg_write(imx500->regmap, metadata_output,
+				  ARRAY_SIZE(metadata_output), NULL);
+	if (ret) {
+		dev_err(&client->dev,
+			"%s: failed to configure MIPI output for DNN\n",
+			__func__);
+		return ret;
+	}
+
+	ret = cci_multi_reg_write(imx500->regmap, dnn_regs,
+				  ARRAY_SIZE(dnn_regs), NULL);
+	if (ret) {
+		dev_err(&client->dev, "%s: unable to write DNN regs\n",
+			__func__);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int imx500_transition_to_network(struct imx500 *imx500)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
+	u64 imx500_fsm_state;
+	int ret;
+
+	ret = imx500_iterate_nw_regs(imx500->fw_network,
+				     imx500->fw_network_size, imx500,
+				     imx500_reg_val_write_cbk);
+	if (ret) {
+		dev_err(&client->dev,
+			"%s: unable to apply register writes from firmware\n",
+			__func__);
+		return ret;
+	}
+
+	/* Read IMX500 state to determine whether transition or update is required */
+	ret = cci_read(imx500->regmap, IMX500_REG_DD_SYS_STATE,
+		       &imx500_fsm_state, NULL);
+	if (ret || imx500_fsm_state & 1)
+		return ret ? ret : -EREMOTEIO;
+
+	ret = imx500_state_transition(
+		imx500, imx500->fw_network, imx500->fw_network_size,
+		TYPE_NW_WEIGHTS,
+		imx500_fsm_state == IMX500_DD_SYS_STATE_STANDBY_WITH_NETWORK);
+	if (ret) {
+		dev_err(&client->dev, "%s: failed to load network weights\n",
+			__func__);
+		return ret;
+	}
+
+	/* Select network 0 */
+	ret = cci_write(imx500->regmap, CCI_REG8(0xD701), 0, NULL);
+	if (ret) {
+		dev_err(&client->dev, "%s: failed to select network 0\n",
+			__func__);
+		return ret;
+	}
+
+	return ret;
+}
+
+/* Start streaming */
+static int imx500_start_streaming(struct imx500 *imx500)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
+	const struct imx500_reg_list *reg_list;
+	int ret;
+
+	ret = pm_runtime_resume_and_get(&client->dev);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * Disable the temperature sensor here - must be done else loading any
+	 * firmware fails...
+	 *
+	 * Re-enable before stream-on below.
+	 */
+	cci_write(imx500->regmap, IMX500_REG_SENSOR_TEMP_CTRL, 0, &ret);
+
+	ret = cci_write(imx500->regmap, IMX500_REG_IMAGE_ONLY_MODE,
+			imx500->fw_network ? IMX500_IMAGE_ONLY_FALSE :
+					     IMX500_IMAGE_ONLY_TRUE,
+			NULL);
+	if (ret) {
+		dev_err(&client->dev, "%s failed to set image mode\n",
+			__func__);
+		goto err_runtime_put;
+	}
+
+	/* Acquire loader and main firmware if needed */
+	if (imx500->fw_network) {
+		if (!imx500->fw_loader) {
+			ret = request_firmware(&imx500->fw_loader,
+					       "imx500_loader.fpk",
+					       &client->dev);
+			if (ret) {
+				dev_err(&client->dev,
+					"Unable to acquire firmware loader\n");
+				goto err_runtime_put;
+			}
+		}
+		if (!imx500->fw_main) {
+			ret = request_firmware(&imx500->fw_main,
+					       "imx500_firmware.fpk",
+					       &client->dev);
+			if (ret) {
+				dev_err(&client->dev,
+					"Unable to acquire main firmware\n");
+				goto err_runtime_put;
+			}
+		}
+	}
+
+	if (!imx500->common_regs_written) {
+		ret = cci_multi_reg_write(imx500->regmap, mode_common_regs,
+					  ARRAY_SIZE(mode_common_regs), NULL);
+
+		if (ret) {
+			dev_err(&client->dev,
+				"%s failed to set common settings\n", __func__);
+			goto err_runtime_put;
+		}
+
+		imx500->common_regs_written = true;
+	}
+
+	if (imx500->fw_network && !imx500->loader_and_main_written) {
+		ret = imx500_transition_to_standby_wo_network(imx500);
+		if (ret) {
+			dev_err(&client->dev,
+				"%s failed to transition from program empty state\n",
+				__func__);
+			goto err_runtime_put;
+		}
+		imx500->loader_and_main_written = true;
+	}
+
+	if (imx500->fw_network && !imx500->network_written) {
+		if (imx500->tensor_injection) {
+			ret = imx500_data_injection_init(imx500);
+			if (ret) {
+				dev_err(&client->dev,
+					"%s failed to initialize data injection\n",
+					__func__);
+				return ret;
+			}
+		}
+		ret = imx500_transition_to_network(imx500);
+		if (ret) {
+			dev_err(&client->dev,
+				"%s failed to transition to network loaded\n",
+				__func__);
+			goto err_runtime_put;
+		}
+		imx500->network_written = true;
+	}
+
+	/* Enable DNN */
+	if (imx500->fw_network) {
+		ret = cci_write(imx500->regmap, IMX500_REG_STRM_MODE_SEL, 4,
+				NULL);
+		if (ret) {
+			dev_err(&client->dev, "%s failed to enable DNN\n",
+				__func__);
+			goto err_runtime_put;
+		}
+
+		v4l2_ctrl_activate(imx500->device_id, true);
+	}
+
+	/* Apply default values of current mode */
+	reg_list = &imx500->mode->reg_list;
+	ret = cci_multi_reg_write(imx500->regmap, reg_list->regs,
+				  reg_list->num_of_regs, NULL);
+	if (ret) {
+		dev_err(&client->dev, "%s failed to set mode\n", __func__);
+		goto err_runtime_put;
+	}
+
+	/* Apply customized values from user */
+	ret = __v4l2_ctrl_handler_setup(imx500->sd.ctrl_handler);
+
+	/* Disable any sensor startup frame drops. This must be written here! */
+	cci_write(imx500->regmap, CCI_REG8(0xD405), 0, &ret);
+
+	/* Re-enable the temperature sensor. */
+	cci_write(imx500->regmap, IMX500_REG_SENSOR_TEMP_CTRL, 1, &ret);
+
+	/* set stream on register */
+	cci_write(imx500->regmap, IMX500_REG_MODE_SELECT, IMX500_MODE_STREAMING,
+		  &ret);
+
+	if (ret)
+		goto err_runtime_put;
+
+	if (imx500->tensor_injection) {
+		ret = imx500_data_injection_init_wait(imx500);
+		if (ret) {
+			dev_err(&client->dev,
+				"%s failed to wait for data injection init\n",
+				__func__);
+			goto err_runtime_put;
+		}
+	}
+
+	return 0;
+
+err_runtime_put:
+	__v4l2_ctrl_grab(imx500->enable_injection, false);
+	pm_runtime_mark_last_busy(&client->dev);
+	pm_runtime_put_autosuspend(&client->dev);
+
+	return ret;
+}
+
+static int imx500_power_on(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct imx500 *imx500 = to_imx500(sd);
+	int ret;
+
+	/* Acquire GPIOs first to ensure reset is asserted before power is applied */
+	imx500->led_gpio = devm_gpiod_get_optional(dev, "led", GPIOD_OUT_LOW);
+	if (IS_ERR(imx500->led_gpio)) {
+		ret = PTR_ERR(imx500->led_gpio);
+		dev_err(&client->dev, "%s: failed to get led gpio\n", __func__);
+		return ret;
+	}
+
+	imx500->reset_gpio =
+		devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(imx500->reset_gpio)) {
+		ret = PTR_ERR(imx500->reset_gpio);
+		dev_err(&client->dev, "%s: failed to get reset gpio\n",
+			__func__);
+		goto gpio_led_put;
+	}
+
+	ret = regulator_bulk_enable(IMX500_NUM_SUPPLIES, imx500->supplies);
+	if (ret) {
+		dev_err(&client->dev, "%s: failed to enable regulators\n",
+			__func__);
+		goto gpio_reset_put;
+	}
+
+	/* T4 - 1us
+	 * Ambiguous: Regulators rising to INCK start is specified by the datasheet
+	 * but also "Presence of INCK during Power off is acceptable"
+	 */
+	udelay(2);
+
+	ret = clk_prepare_enable(imx500->xclk);
+	if (ret) {
+		dev_err(&client->dev, "%s: failed to enable clock\n", __func__);
+		goto reg_off;
+	}
+
+	/* T5 - 0ms
+	 * Ambiguous: Regulators rising to XCLR rising is specified by the datasheet
+	 * as 0ms but also "XCLR pin should be set to 'High' after INCK supplied.".
+	 * T4 and T5 are shown as overlapping.
+	 */
+	if (imx500->reset_gpio)
+		gpiod_set_value_cansleep(imx500->reset_gpio, 1);
+
+	/* T7 - 9ms
+	 * "INCK start and CXLR rising till Send Streaming Command wait time"
+	 */
+	usleep_range(9000, 12000);
+
+	return 0;
+
+reg_off:
+	regulator_bulk_disable(IMX500_NUM_SUPPLIES, imx500->supplies);
+gpio_reset_put:
+	if (imx500->reset_gpio) {
+		devm_gpiod_put(dev, imx500->reset_gpio);
+		imx500->reset_gpio = NULL;
+	}
+gpio_led_put:
+	if (imx500->led_gpio) {
+		devm_gpiod_put(dev, imx500->led_gpio);
+		imx500->led_gpio = NULL;
+	}
+	return ret;
+}
+
+static int imx500_power_off(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct imx500 *imx500 = to_imx500(sd);
+
+	/* Datasheet specifies power down sequence as INCK disable, XCLR low,
+	 * regulator disable.  T1 (XCLR neg-edge to regulator disable) is specified
+	 * as 0us.
+	 *
+	 * Note, this is not the reverse order of power up.
+	 */
+	clk_disable_unprepare(imx500->xclk);
+	if (imx500->reset_gpio)
+		gpiod_set_value_cansleep(imx500->reset_gpio, 0);
+
+	/* Release GPIOs before disabling regulators */
+	if (imx500->reset_gpio) {
+		devm_gpiod_put(&client->dev, imx500->reset_gpio);
+		imx500->reset_gpio = NULL;
+	}
+	if (imx500->led_gpio) {
+		devm_gpiod_put(&client->dev, imx500->led_gpio);
+		imx500->led_gpio = NULL;
+	}
+
+	regulator_bulk_disable(IMX500_NUM_SUPPLIES, imx500->supplies);
+
+	/* Force reprogramming of the common registers when powered up again. */
+	imx500->fsm_state = IMX500_STATE_RESET;
+	imx500->tensor_injection = false;
+	imx500->common_regs_written = false;
+	imx500->loader_and_main_written = false;
+	imx500_clear_fw_network(imx500);
+
+	return 0;
+}
+
+/* Stop streaming */
+static void imx500_stop_streaming(struct imx500 *imx500)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
+	int ret;
+
+	/* set stream off register */
+	ret = cci_write(imx500->regmap, IMX500_REG_MODE_SELECT,
+			IMX500_MODE_STANDBY, NULL);
+	if (ret)
+		dev_err(&client->dev, "%s failed to set stream\n", __func__);
+
+	/* Disable DNN */
+	ret = cci_write(imx500->regmap, IMX500_REG_STRM_MODE_SEL, 0, NULL);
+	if (ret)
+		dev_err(&client->dev, "%s failed to disable DNN\n", __func__);
+
+	pm_runtime_mark_last_busy(&client->dev);
+
+	if (imx500->tensor_injection) {
+		/*
+		 * The tensor_injection state is not persistent.
+		 * It must be reset on streamoff to avoid breaking
+		 * non-injection-aware applications that may use the
+		 * device next. We reset both the internal flag and the
+		 * control's value.
+		 */
+		imx500->tensor_injection = false;
+		__v4l2_ctrl_s_ctrl(imx500->enable_injection, 0);
+
+		/*
+		 * A full power cycle is required to reset the sensor's SPI
+		 * block. Disabling runtime PM allows us to do this safely.
+		 */
+		pm_runtime_disable(&client->dev);
+		imx500_power_off(&client->dev);
+		pm_runtime_set_suspended(&client->dev);
+
+		/*
+		 * If the usage count is > 1, another user (e.g. sysfs) holds
+		 * a reference, so the device must be powered on again.
+		 * Otherwise, the upcoming 'put' will suspend, so we can
+		 * leave it off.
+		 */
+		if (atomic_read(&client->dev.power.usage_count) > 1) {
+			usleep_range(5000, 10000);
+			imx500_power_on(&client->dev);
+			pm_runtime_set_active(&client->dev);
+		}
+
+		pm_runtime_enable(&client->dev);
+	}
+
+	pm_runtime_put_autosuspend(&client->dev);
+}
+
+static int imx500_set_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct imx500 *imx500 = to_imx500(sd);
+	int ret = 0;
+
+	mutex_lock(&imx500->mutex);
+	if (imx500->streaming == enable) {
+		mutex_unlock(&imx500->mutex);
+		return 0;
+	}
+
+	if (enable) {
+		/*
+		 * Apply default & customized values
+		 * and then start streaming.
+		 */
+		ret = imx500_start_streaming(imx500);
+		if (ret)
+			goto err_start_streaming;
+	} else {
+		imx500_stop_streaming(imx500);
+	}
+
+	imx500->streaming = enable;
+
+	/* vflip and hflip cannot change during streaming */
+	__v4l2_ctrl_grab(imx500->vflip, enable);
+	__v4l2_ctrl_grab(imx500->hflip, enable);
+	__v4l2_ctrl_grab(imx500->network_fw_ctrl, enable);
+
+	mutex_unlock(&imx500->mutex);
+
+	return ret;
+
+err_start_streaming:
+	mutex_unlock(&imx500->mutex);
+
+	return ret;
+}
+
+static int imx500_get_regulators(struct imx500 *imx500)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
+	unsigned int i;
+
+	for (i = 0; i < IMX500_NUM_SUPPLIES; i++)
+		imx500->supplies[i].supply = imx500_supply_name[i];
+
+	return devm_regulator_bulk_get(&client->dev, IMX500_NUM_SUPPLIES,
+				       imx500->supplies);
+}
+
+/* Verify chip ID */
+static int imx500_identify_module(struct imx500 *imx500)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
+	int ret;
+	u64 val;
+
+	ret = cci_read(imx500->regmap, IMX500_REG_CHIP_ID, &val, NULL);
+	if (ret) {
+		dev_err(&client->dev,
+			"failed to read chip id %x, with error %d\n",
+			IMX500_CHIP_ID, ret);
+		return ret;
+	}
+
+	if (val != IMX500_CHIP_ID) {
+		dev_err(&client->dev, "chip id mismatch: %x!=%llx\n",
+			IMX500_CHIP_ID, val);
+		return -EIO;
+	}
+
+	dev_info(&client->dev, "Device found is imx%llx\n", val);
+
+	return 0;
+}
+
+static const struct v4l2_subdev_core_ops imx500_core_ops = {
+	.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_video_ops imx500_video_ops = {
+	.s_stream = imx500_set_stream,
+};
+
+static const struct v4l2_subdev_pad_ops imx500_pad_ops = {
+	.enum_mbus_code = imx500_enum_mbus_code,
+	.get_fmt = imx500_get_pad_format,
+	.set_fmt = imx500_set_pad_format,
+	.get_selection = imx500_get_selection,
+	.enum_frame_size = imx500_enum_frame_size,
+};
+
+static const struct v4l2_subdev_ops imx500_subdev_ops = {
+	.core = &imx500_core_ops,
+	.video = &imx500_video_ops,
+	.pad = &imx500_pad_ops,
+};
+
+static const s64 imx500_link_freq_menu[] = {
+	IMX500_DEFAULT_LINK_FREQ,
+};
+
+/* Custom control for inference window */
+static const struct v4l2_ctrl_config inf_window_ctrl = {
+	.name		= "IMX500 Inference Windows",
+	.id		= V4L2_CID_USER_IMX500_INFERENCE_WINDOW,
+	.dims[0]	= 4,
+	.ops		= &imx500_ctrl_ops,
+	.type		= V4L2_CTRL_TYPE_U32,
+	.elem_size	= sizeof(u32),
+	.flags		= V4L2_CTRL_FLAG_EXECUTE_ON_WRITE |
+			  V4L2_CTRL_FLAG_HAS_PAYLOAD,
+	.def		= 0,
+	.min		= 0x00,
+	.max		= 4032,
+	.step		= 1,
+};
+
+/* Custom control for network firmware file FD */
+static const struct v4l2_ctrl_config network_fw_fd = {
+	.name		= "IMX500 Network Firmware File FD",
+	.id		= V4L2_CID_USER_IMX500_NETWORK_FW_FD,
+	.ops		= &imx500_ctrl_ops,
+	.type		= V4L2_CTRL_TYPE_INTEGER,
+	.flags		= V4L2_CTRL_FLAG_EXECUTE_ON_WRITE |
+			  V4L2_CTRL_FLAG_WRITE_ONLY,
+	.min		= -1,
+	.max		= S32_MAX,
+	.step		= 1,
+	.def		= -1,
+};
+
+/* Custom control to get camera device id */
+static const struct v4l2_ctrl_config cam_get_device_id = {
+	.name		= "Get IMX500 Device ID",
+	.id		= V4L2_CID_USER_GET_IMX500_DEVICE_ID,
+	.dims[0]	= 4,
+	.ops		= &imx500_ctrl_ops,
+	.type		= V4L2_CTRL_TYPE_U32,
+	.flags		= V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE |
+			  V4L2_CTRL_FLAG_INACTIVE,
+	.elem_size	= sizeof(u32),
+	.min		= 0x00,
+	.max		= U32_MAX,
+	.step		= 1,
+	.def		= 0,
+};
+
+/* Custom control for enable injection */
+static const struct v4l2_ctrl_config enable_injection_ctrl = {
+	.name = "IMX500 Enable Input Tensor Injection",
+	.id = V4L2_CID_USER_IMX500_ENABLE_INJECTION,
+	.ops = &imx500_ctrl_ops,
+	.type = V4L2_CTRL_TYPE_BOOLEAN,
+	.min = 0,
+	.max = 1,
+	.step = 1,
+	.def = 0,
+};
+
+/* Custom control for input tensor file FD */
+static const struct v4l2_ctrl_config input_tensor_fd = {
+	.name = "IMX500 Input Tensor File FD",
+	.id = V4L2_CID_USER_IMX500_INPUT_TENSOR_FD,
+	.ops = &imx500_ctrl_ops,
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.flags = V4L2_CTRL_FLAG_EXECUTE_ON_WRITE | V4L2_CTRL_FLAG_WRITE_ONLY,
+	.min = -1,
+	.max = S32_MAX,
+	.step = 1,
+	.def = -1,
+};
+
+/* Custom control from tensor injection comparison frame */
+static const struct v4l2_ctrl_config injection_cmp_frm = {
+	.name = "IMX500 Injection Comparison Frame",
+	.id = V4L2_CID_USER_IMX500_INJECTION_CMP_FRM,
+	.ops = &imx500_ctrl_ops,
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE,
+	.min = 1,
+	.max = 254,
+	.step = 1,
+	.def = 42,
+};
+
+/* Initialize control handlers */
+static int imx500_init_controls(struct imx500 *imx500)
+{
+	struct v4l2_ctrl_handler *ctrl_hdlr;
+	struct i2c_client *client = v4l2_get_subdevdata(&imx500->sd);
+	struct v4l2_fwnode_device_properties props;
+	int ret;
+
+	ctrl_hdlr = &imx500->ctrl_handler;
+	ret = v4l2_ctrl_handler_init(ctrl_hdlr, 16);
+	if (ret)
+		return ret;
+
+	mutex_init(&imx500->mutex);
+	ctrl_hdlr->lock = &imx500->mutex;
+
+	/* By default, PIXEL_RATE is read only */
+	imx500->pixel_rate = v4l2_ctrl_new_std(
+		ctrl_hdlr, &imx500_ctrl_ops, V4L2_CID_PIXEL_RATE,
+		IMX500_PIXEL_RATE, IMX500_PIXEL_RATE, 1, IMX500_PIXEL_RATE);
+
+	/* LINK_FREQ is also read only */
+	imx500->link_freq =
+		v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx500_ctrl_ops,
+				       V4L2_CID_LINK_FREQ,
+				       ARRAY_SIZE(imx500_link_freq_menu) - 1, 0,
+				       imx500_link_freq_menu);
+	if (imx500->link_freq)
+		imx500->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	/*
+	 * Create the controls here, but mode specific limits are setup
+	 * in the imx500_set_framing_limits() call below.
+	 */
+	imx500->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx500_ctrl_ops,
+					   V4L2_CID_VBLANK, IMX500_VBLANK_MIN,
+					   0xffff, 1, IMX500_VBLANK_MIN);
+	imx500->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx500_ctrl_ops,
+					   V4L2_CID_HBLANK, 0, 0xffff, 1, 0);
+
+	imx500->exposure = v4l2_ctrl_new_std(
+		ctrl_hdlr, &imx500_ctrl_ops, V4L2_CID_EXPOSURE,
+		IMX500_EXPOSURE_MIN, IMX500_EXPOSURE_MAX, IMX500_EXPOSURE_STEP,
+		IMX500_EXPOSURE_DEFAULT);
+
+	v4l2_ctrl_new_std(ctrl_hdlr, &imx500_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
+			  IMX500_ANA_GAIN_MIN, IMX500_ANA_GAIN_MAX,
+			  IMX500_ANA_GAIN_STEP, IMX500_ANA_GAIN_DEFAULT);
+
+	imx500->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx500_ctrl_ops,
+					  V4L2_CID_HFLIP, 0, 1, 1, 0);
+	if (imx500->hflip)
+		imx500->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+	imx500->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx500_ctrl_ops,
+					  V4L2_CID_VFLIP, 0, 1, 1, 0);
+	if (imx500->vflip)
+		imx500->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+	v4l2_ctrl_new_custom(ctrl_hdlr, &imx500_notify_gains_ctrl, NULL);
+	v4l2_ctrl_new_custom(ctrl_hdlr, &inf_window_ctrl, NULL);
+	imx500->network_fw_ctrl =
+		v4l2_ctrl_new_custom(ctrl_hdlr, &network_fw_fd, NULL);
+	imx500->device_id =
+		v4l2_ctrl_new_custom(ctrl_hdlr, &cam_get_device_id, NULL);
+	imx500->enable_injection =
+		v4l2_ctrl_new_custom(ctrl_hdlr, &enable_injection_ctrl, NULL);
+	imx500->input_tensor_fd =
+		v4l2_ctrl_new_custom(ctrl_hdlr, &input_tensor_fd, NULL);
+	imx500->injection_cmp_frm =
+		v4l2_ctrl_new_custom(ctrl_hdlr, &injection_cmp_frm, NULL);
+
+	if (ctrl_hdlr->error) {
+		ret = ctrl_hdlr->error;
+		dev_err(&client->dev, "%s control init failed (%d)\n", __func__,
+			ret);
+		goto error;
+	}
+
+	ret = v4l2_fwnode_device_parse(&client->dev, &props);
+	if (ret)
+		goto error;
+
+	ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx500_ctrl_ops,
+					      &props);
+	if (ret)
+		goto error;
+
+	imx500->sd.ctrl_handler = ctrl_hdlr;
+
+	/* Setup exposure and frame/line length limits. */
+	imx500_set_framing_limits(imx500);
+
+	return 0;
+
+error:
+	v4l2_ctrl_handler_free(ctrl_hdlr);
+	mutex_destroy(&imx500->mutex);
+
+	return ret;
+}
+
+static void imx500_free_controls(struct imx500 *imx500)
+{
+	v4l2_ctrl_handler_free(imx500->sd.ctrl_handler);
+	mutex_destroy(&imx500->mutex);
+}
+
+static int imx500_check_hwcfg(struct device *dev)
+{
+	struct fwnode_handle *endpoint;
+	struct v4l2_fwnode_endpoint ep_cfg = { .bus_type =
+						       V4L2_MBUS_CSI2_DPHY };
+	int ret = -EINVAL;
+
+	endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
+	if (!endpoint) {
+		dev_err(dev, "endpoint node not found\n");
+		return -EINVAL;
+	}
+
+	if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) {
+		dev_err(dev, "could not parse endpoint\n");
+		goto error_out;
+	}
+
+	/* Check the number of MIPI CSI2 data lanes */
+	if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2) {
+		dev_err(dev, "only 2 data lanes are currently supported\n");
+		goto error_out;
+	}
+
+	/* Check the link frequency set in device tree */
+	if (!ep_cfg.nr_of_link_frequencies) {
+		dev_err(dev, "link-frequency property not found in DT\n");
+		goto error_out;
+	}
+
+	if (ep_cfg.nr_of_link_frequencies != 1 ||
+	    ep_cfg.link_frequencies[0] != IMX500_DEFAULT_LINK_FREQ) {
+		dev_err(dev, "Link frequency not supported: %lld\n",
+			ep_cfg.link_frequencies[0]);
+		goto error_out;
+	}
+
+	ret = 0;
+
+error_out:
+	v4l2_fwnode_endpoint_free(&ep_cfg);
+	fwnode_handle_put(endpoint);
+
+	return ret;
+}
+
+static int fw_progress_show(struct seq_file *s, void *data)
+{
+	struct imx500 *imx500 = s->private;
+
+	seq_printf(s, "%d %zu %zu\n", imx500->fw_stage, imx500->fw_progress,
+		   imx500->fw_network_size);
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(fw_progress);
+
+static int imx500_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct spi_device *spi = NULL;
+	char debugfs_name[128];
+	struct imx500 *imx500;
+	int ret;
+
+	struct device_node *spi_node = of_parse_phandle(dev->of_node, "spi", 0);
+
+	if (spi_node) {
+		struct device *tmp =
+			bus_find_device_by_of_node(&spi_bus_type, spi_node);
+		of_node_put(spi_node);
+		spi = tmp ? to_spi_device(tmp) : NULL;
+		if (!spi)
+			return -EPROBE_DEFER;
+	}
+
+	imx500 = devm_kzalloc(&client->dev, sizeof(*imx500), GFP_KERNEL);
+	if (!imx500)
+		return -ENOMEM;
+
+	imx500->regmap = devm_cci_regmap_init_i2c(client, 16);
+	if (IS_ERR(imx500->regmap))
+		return dev_err_probe(dev, PTR_ERR(imx500->regmap),
+				     "failed to initialise CCI\n");
+
+	imx500->spi_device = spi;
+
+	v4l2_i2c_subdev_init(&imx500->sd, client, &imx500_subdev_ops);
+
+	/* Check the hardware configuration in device tree */
+	if (imx500_check_hwcfg(dev))
+		return -EINVAL;
+
+	/* Get system clock (xclk) */
+	imx500->xclk = devm_clk_get(dev, NULL);
+	if (IS_ERR(imx500->xclk))
+		return dev_err_probe(dev, PTR_ERR(imx500->xclk),
+				     "failed to get xclk\n");
+
+	imx500->xclk_freq = clk_get_rate(imx500->xclk);
+	if (imx500->xclk_freq != IMX500_XCLK_FREQ) {
+		dev_err(dev, "xclk frequency not supported: %d Hz\n",
+			imx500->xclk_freq);
+		return -EINVAL;
+	}
+
+	ret = imx500_get_regulators(imx500);
+	if (ret) {
+		dev_err(dev, "failed to get regulators\n");
+		return ret;
+	}
+
+	/* GPIOs are acquired in imx500_power_on() to avoid preventing
+	 * regulator power down when shared with other drivers.
+	 */
+
+	/*
+	 * The sensor must be powered for imx500_identify_module()
+	 * to be able to read the CHIP_ID register. This also ensures
+	 * GPIOs are available.
+	 */
+	ret = imx500_power_on(dev);
+	if (ret)
+		return ret;
+
+	pm_runtime_set_active(dev);
+	pm_runtime_get_noresume(dev);
+	pm_runtime_enable(dev);
+	pm_runtime_set_autosuspend_delay(dev, 5000);
+	pm_runtime_use_autosuspend(dev);
+
+	ret = imx500_identify_module(imx500);
+	if (ret)
+		goto error_power_off;
+
+	/* Initialize default format */
+	imx500_set_default_format(imx500);
+
+	/* Initialize injection comparison frame to default value */
+	imx500->current_injection_cmp_frm = 0;
+
+	/* This needs the pm runtime to be registered. */
+	ret = imx500_init_controls(imx500);
+	if (ret)
+		goto error_power_off;
+
+	/* Initialize subdev */
+	imx500->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
+			    V4L2_SUBDEV_FL_HAS_EVENTS;
+	imx500->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+
+	/* Initialize source pads */
+	imx500->pad[IMAGE_PAD].flags = MEDIA_PAD_FL_SOURCE;
+	imx500->pad[METADATA_PAD].flags = MEDIA_PAD_FL_SOURCE;
+
+	ret = media_entity_pads_init(&imx500->sd.entity, NUM_PADS, imx500->pad);
+	if (ret) {
+		dev_err(dev, "failed to init entity pads: %d\n", ret);
+		goto error_handler_free;
+	}
+
+	ret = v4l2_async_register_subdev_sensor(&imx500->sd);
+	if (ret < 0) {
+		dev_err(dev, "failed to register sensor sub-device: %d\n", ret);
+		goto error_media_entity;
+	}
+
+	snprintf(debugfs_name, sizeof(debugfs_name), "imx500-fw:%s",
+		 dev_name(dev));
+	imx500->debugfs = debugfs_create_dir(debugfs_name, NULL);
+	debugfs_create_file("fw_progress", 0444, imx500->debugfs, imx500,
+			    &fw_progress_fops);
+
+	pm_runtime_mark_last_busy(&client->dev);
+	pm_runtime_put_autosuspend(&client->dev);
+
+	return 0;
+
+error_media_entity:
+	media_entity_cleanup(&imx500->sd.entity);
+
+error_handler_free:
+	imx500_free_controls(imx500);
+
+error_power_off:
+	pm_runtime_disable(&client->dev);
+	pm_runtime_put_noidle(&client->dev);
+	imx500_power_off(&client->dev);
+
+	return ret;
+}
+
+static void imx500_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct imx500 *imx500 = to_imx500(sd);
+
+	if (imx500->spi_device)
+		put_device(&imx500->spi_device->dev);
+
+	v4l2_async_unregister_subdev(sd);
+	media_entity_cleanup(&sd->entity);
+	imx500_free_controls(imx500);
+
+	if (imx500->fw_loader)
+		release_firmware(imx500->fw_loader);
+
+	if (imx500->fw_main)
+		release_firmware(imx500->fw_main);
+
+	imx500->fw_loader = NULL;
+	imx500->fw_main = NULL;
+	imx500_clear_fw_network(imx500);
+
+	pm_runtime_disable(&client->dev);
+	if (!pm_runtime_status_suspended(&client->dev))
+		imx500_power_off(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+}
+
+static const struct of_device_id imx500_dt_ids[] = {
+	{ .compatible = "sony,imx500" },
+	{ /* sentinel */ }
+};
+
+MODULE_DEVICE_TABLE(of, imx500_dt_ids);
+
+static const struct dev_pm_ops imx500_pm_ops = { SET_RUNTIME_PM_OPS(
+	imx500_power_off, imx500_power_on, NULL) };
+
+static struct i2c_driver imx500_i2c_driver = {
+	.driver = {
+		.name = "imx500",
+		.of_match_table	= imx500_dt_ids,
+		.pm = &imx500_pm_ops,
+	},
+	.probe = imx500_probe,
+	.remove = imx500_remove,
+};
+
+static int imx500_spi_probe(struct spi_device *spi)
+{
+	int result;
+
+	spi->bits_per_word = 8;
+	spi->max_speed_hz = 35000000;
+	spi->mode = SPI_MODE_3;
+
+	result = spi_setup(spi);
+	if (result < 0)
+		return dev_err_probe(&spi->dev, result, "spi_setup() failed");
+
+	return 0;
+}
+
+static void imx500_spi_remove(struct spi_device *spi)
+{
+}
+
+static const struct spi_device_id imx500_spi_id[] = {
+	{ "imx500", 0 },
+	{},
+};
+MODULE_DEVICE_TABLE(spi, imx500_spi_id);
+
+static struct spi_driver imx500_spi_driver = {
+	.driver = {
+		.name = "imx500",
+		.of_match_table = imx500_dt_ids,
+	},
+	.probe = imx500_spi_probe,
+	.remove = imx500_spi_remove,
+	.id_table = imx500_spi_id,
+};
+
+static int __init imx500_driver_init(void)
+{
+	int ret;
+
+	ret = spi_register_driver(&imx500_spi_driver);
+	if (ret)
+		return ret;
+
+	ret = i2c_add_driver(&imx500_i2c_driver);
+	if (ret)
+		spi_unregister_driver(&imx500_spi_driver);
+
+	return ret;
+}
+module_init(imx500_driver_init);
+
+static void __exit imx500_driver_exit(void)
+{
+	i2c_del_driver(&imx500_i2c_driver);
+	spi_unregister_driver(&imx500_spi_driver);
+}
+module_exit(imx500_driver_exit);
+
+MODULE_AUTHOR("Naushir Patuck <naush@raspberrypi.com>");
+MODULE_DESCRIPTION("Sony IMX500 sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/i2c/imx519.c b/drivers/media/i2c/imx519.c
new file mode 100644
index 000000000000..5fe002ec3b26
--- /dev/null
+++ b/drivers/media/i2c/imx519.c
@@ -0,0 +1,2146 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A V4L2 driver for Sony IMX519 cameras.
+ * Copyright (C) 2021 Arducam Technology co., Ltd.
+ *
+ * Based on Sony IMX477 camera driver
+ * Copyright (C) 2020 Raspberry Pi (Trading) Ltd
+ */
+#include <linux/unaligned.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mediabus.h>
+
+#define IMX519_REG_VALUE_08BIT		1
+#define IMX519_REG_VALUE_16BIT		2
+
+/* Chip ID */
+#define IMX519_REG_CHIP_ID		0x0016
+#define IMX519_CHIP_ID			0x0519
+
+#define IMX519_REG_MODE_SELECT		0x0100
+#define IMX519_MODE_STANDBY		0x00
+#define IMX519_MODE_STREAMING		0x01
+
+#define IMX519_REG_ORIENTATION		0x101
+
+#define IMX519_XCLK_FREQ		24000000
+
+#define IMX519_DEFAULT_LINK_FREQ	408000000
+
+/* Pixel rate is fixed at 426MHz for all the modes */
+#define IMX519_PIXEL_RATE		426666667
+
+/* V_TIMING internal */
+#define IMX519_REG_FRAME_LENGTH		0x0340
+#define IMX519_FRAME_LENGTH_MAX		0xffdc
+
+/* Long exposure multiplier */
+#define IMX519_LONG_EXP_SHIFT_MAX	7
+#define IMX519_LONG_EXP_SHIFT_REG	0x3100
+
+/* Exposure control */
+#define IMX519_REG_EXPOSURE		0x0202
+#define IMX519_EXPOSURE_OFFSET		32
+#define IMX519_EXPOSURE_MIN		20
+#define IMX519_EXPOSURE_STEP		1
+#define IMX519_EXPOSURE_DEFAULT		0x3e8
+#define IMX519_EXPOSURE_MAX		(IMX519_FRAME_LENGTH_MAX - \
+					 IMX519_EXPOSURE_OFFSET)
+
+/* Analog gain control */
+#define IMX519_REG_ANALOG_GAIN		0x0204
+#define IMX519_ANA_GAIN_MIN		0
+#define IMX519_ANA_GAIN_MAX		960
+#define IMX519_ANA_GAIN_STEP		1
+#define IMX519_ANA_GAIN_DEFAULT		0x0
+
+/* Digital gain control */
+#define IMX519_REG_DIGITAL_GAIN		0x020e
+#define IMX519_DGTL_GAIN_MIN		0x0100
+#define IMX519_DGTL_GAIN_MAX		0xffff
+#define IMX519_DGTL_GAIN_DEFAULT	0x0100
+#define IMX519_DGTL_GAIN_STEP		1
+
+/* Test Pattern Control */
+#define IMX519_REG_TEST_PATTERN		0x0600
+#define IMX519_TEST_PATTERN_DISABLE	0
+#define IMX519_TEST_PATTERN_SOLID_COLOR	1
+#define IMX519_TEST_PATTERN_COLOR_BARS	2
+#define IMX519_TEST_PATTERN_GREY_COLOR	3
+#define IMX519_TEST_PATTERN_PN9		4
+
+/* Test pattern colour components */
+#define IMX519_REG_TEST_PATTERN_R	0x0602
+#define IMX519_REG_TEST_PATTERN_GR	0x0604
+#define IMX519_REG_TEST_PATTERN_B	0x0606
+#define IMX519_REG_TEST_PATTERN_GB	0x0608
+#define IMX519_TEST_PATTERN_COLOUR_MIN	0
+#define IMX519_TEST_PATTERN_COLOUR_MAX	0x0fff
+#define IMX519_TEST_PATTERN_COLOUR_STEP	1
+#define IMX519_TEST_PATTERN_R_DEFAULT	IMX519_TEST_PATTERN_COLOUR_MAX
+#define IMX519_TEST_PATTERN_GR_DEFAULT	0
+#define IMX519_TEST_PATTERN_B_DEFAULT	0
+#define IMX519_TEST_PATTERN_GB_DEFAULT	0
+
+/* Embedded metadata stream structure */
+#define IMX519_EMBEDDED_LINE_WIDTH (5820 * 3)
+#define IMX519_NUM_EMBEDDED_LINES 1
+
+enum pad_types {
+	IMAGE_PAD,
+	METADATA_PAD,
+	NUM_PADS
+};
+
+/* IMX519 native and active pixel array size. */
+#define IMX519_NATIVE_WIDTH		4672U
+#define IMX519_NATIVE_HEIGHT		3648U
+#define IMX519_PIXEL_ARRAY_LEFT		8U
+#define IMX519_PIXEL_ARRAY_TOP		48U
+#define IMX519_PIXEL_ARRAY_WIDTH	4656U
+#define IMX519_PIXEL_ARRAY_HEIGHT	3496U
+
+struct imx519_reg {
+	u16 address;
+	u8 val;
+};
+
+struct imx519_reg_list {
+	unsigned int num_of_regs;
+	const struct imx519_reg *regs;
+};
+
+/* Mode : resolution and related config&values */
+struct imx519_mode {
+	/* Frame width */
+	unsigned int width;
+
+	/* Frame height */
+	unsigned int height;
+
+	/* H-timing in pixels */
+	unsigned int line_length_pix;
+
+	/* Analog crop rectangle. */
+	struct v4l2_rect crop;
+
+	/* Highest possible framerate. */
+	struct v4l2_fract timeperframe_min;
+
+	/* Default framerate. */
+	struct v4l2_fract timeperframe_default;
+
+	/* Default register values */
+	struct imx519_reg_list reg_list;
+};
+
+static const s64 imx519_link_freq_menu[] = {
+	IMX519_DEFAULT_LINK_FREQ,
+};
+
+static const struct imx519_reg mode_common_regs[] = {
+	{0x0100, 0x00},
+	{0x0136, 0x18},
+	{0x0137, 0x00},
+	{0x3c7e, 0x01},
+	{0x3c7f, 0x07},
+	{0x3020, 0x00},
+	{0x3e35, 0x01},
+	{0x3f7f, 0x01},
+	{0x5609, 0x57},
+	{0x5613, 0x51},
+	{0x561f, 0x5e},
+	{0x5623, 0xd2},
+	{0x5637, 0x11},
+	{0x5657, 0x11},
+	{0x5659, 0x12},
+	{0x5733, 0x60},
+	{0x5905, 0x57},
+	{0x590f, 0x51},
+	{0x591b, 0x5e},
+	{0x591f, 0xd2},
+	{0x5933, 0x11},
+	{0x5953, 0x11},
+	{0x5955, 0x12},
+	{0x5a2f, 0x60},
+	{0x5a85, 0x57},
+	{0x5a8f, 0x51},
+	{0x5a9b, 0x5e},
+	{0x5a9f, 0xd2},
+	{0x5ab3, 0x11},
+	{0x5ad3, 0x11},
+	{0x5ad5, 0x12},
+	{0x5baf, 0x60},
+	{0x5c15, 0x2a},
+	{0x5c17, 0x80},
+	{0x5c19, 0x31},
+	{0x5c1b, 0x87},
+	{0x5c25, 0x25},
+	{0x5c27, 0x7b},
+	{0x5c29, 0x2a},
+	{0x5c2b, 0x80},
+	{0x5c2d, 0x31},
+	{0x5c2f, 0x87},
+	{0x5c35, 0x2b},
+	{0x5c37, 0x81},
+	{0x5c39, 0x31},
+	{0x5c3b, 0x87},
+	{0x5c45, 0x25},
+	{0x5c47, 0x7b},
+	{0x5c49, 0x2a},
+	{0x5c4b, 0x80},
+	{0x5c4d, 0x31},
+	{0x5c4f, 0x87},
+	{0x5c55, 0x2d},
+	{0x5c57, 0x83},
+	{0x5c59, 0x32},
+	{0x5c5b, 0x88},
+	{0x5c65, 0x29},
+	{0x5c67, 0x7f},
+	{0x5c69, 0x2e},
+	{0x5c6b, 0x84},
+	{0x5c6d, 0x32},
+	{0x5c6f, 0x88},
+	{0x5e69, 0x04},
+	{0x5e9d, 0x00},
+	{0x5f18, 0x10},
+	{0x5f1a, 0x0e},
+	{0x5f20, 0x12},
+	{0x5f22, 0x10},
+	{0x5f24, 0x0e},
+	{0x5f28, 0x10},
+	{0x5f2a, 0x0e},
+	{0x5f30, 0x12},
+	{0x5f32, 0x10},
+	{0x5f34, 0x0e},
+	{0x5f38, 0x0f},
+	{0x5f39, 0x0d},
+	{0x5f3c, 0x11},
+	{0x5f3d, 0x0f},
+	{0x5f3e, 0x0d},
+	{0x5f61, 0x07},
+	{0x5f64, 0x05},
+	{0x5f67, 0x03},
+	{0x5f6a, 0x03},
+	{0x5f6d, 0x07},
+	{0x5f70, 0x07},
+	{0x5f73, 0x05},
+	{0x5f76, 0x02},
+	{0x5f79, 0x07},
+	{0x5f7c, 0x07},
+	{0x5f7f, 0x07},
+	{0x5f82, 0x07},
+	{0x5f85, 0x03},
+	{0x5f88, 0x02},
+	{0x5f8b, 0x01},
+	{0x5f8e, 0x01},
+	{0x5f91, 0x04},
+	{0x5f94, 0x05},
+	{0x5f97, 0x02},
+	{0x5f9d, 0x07},
+	{0x5fa0, 0x07},
+	{0x5fa3, 0x07},
+	{0x5fa6, 0x07},
+	{0x5fa9, 0x03},
+	{0x5fac, 0x01},
+	{0x5faf, 0x01},
+	{0x5fb5, 0x03},
+	{0x5fb8, 0x02},
+	{0x5fbb, 0x01},
+	{0x5fc1, 0x07},
+	{0x5fc4, 0x07},
+	{0x5fc7, 0x07},
+	{0x5fd1, 0x00},
+	{0x6302, 0x79},
+	{0x6305, 0x78},
+	{0x6306, 0xa5},
+	{0x6308, 0x03},
+	{0x6309, 0x20},
+	{0x630b, 0x0a},
+	{0x630d, 0x48},
+	{0x630f, 0x06},
+	{0x6311, 0xa4},
+	{0x6313, 0x03},
+	{0x6314, 0x20},
+	{0x6316, 0x0a},
+	{0x6317, 0x31},
+	{0x6318, 0x4a},
+	{0x631a, 0x06},
+	{0x631b, 0x40},
+	{0x631c, 0xa4},
+	{0x631e, 0x03},
+	{0x631f, 0x20},
+	{0x6321, 0x0a},
+	{0x6323, 0x4a},
+	{0x6328, 0x80},
+	{0x6329, 0x01},
+	{0x632a, 0x30},
+	{0x632b, 0x02},
+	{0x632c, 0x20},
+	{0x632d, 0x02},
+	{0x632e, 0x30},
+	{0x6330, 0x60},
+	{0x6332, 0x90},
+	{0x6333, 0x01},
+	{0x6334, 0x30},
+	{0x6335, 0x02},
+	{0x6336, 0x20},
+	{0x6338, 0x80},
+	{0x633a, 0xa0},
+	{0x633b, 0x01},
+	{0x633c, 0x60},
+	{0x633d, 0x02},
+	{0x633e, 0x60},
+	{0x633f, 0x01},
+	{0x6340, 0x30},
+	{0x6341, 0x02},
+	{0x6342, 0x20},
+	{0x6343, 0x03},
+	{0x6344, 0x80},
+	{0x6345, 0x03},
+	{0x6346, 0x90},
+	{0x6348, 0xf0},
+	{0x6349, 0x01},
+	{0x634a, 0x20},
+	{0x634b, 0x02},
+	{0x634c, 0x10},
+	{0x634d, 0x03},
+	{0x634e, 0x60},
+	{0x6350, 0xa0},
+	{0x6351, 0x01},
+	{0x6352, 0x60},
+	{0x6353, 0x02},
+	{0x6354, 0x50},
+	{0x6355, 0x02},
+	{0x6356, 0x60},
+	{0x6357, 0x01},
+	{0x6358, 0x30},
+	{0x6359, 0x02},
+	{0x635a, 0x30},
+	{0x635b, 0x03},
+	{0x635c, 0x90},
+	{0x635f, 0x01},
+	{0x6360, 0x10},
+	{0x6361, 0x01},
+	{0x6362, 0x40},
+	{0x6363, 0x02},
+	{0x6364, 0x50},
+	{0x6368, 0x70},
+	{0x636a, 0xa0},
+	{0x636b, 0x01},
+	{0x636c, 0x50},
+	{0x637d, 0xe4},
+	{0x637e, 0xb4},
+	{0x638c, 0x8e},
+	{0x638d, 0x38},
+	{0x638e, 0xe3},
+	{0x638f, 0x4c},
+	{0x6390, 0x30},
+	{0x6391, 0xc3},
+	{0x6392, 0xae},
+	{0x6393, 0xba},
+	{0x6394, 0xeb},
+	{0x6395, 0x6e},
+	{0x6396, 0x34},
+	{0x6397, 0xe3},
+	{0x6398, 0xcf},
+	{0x6399, 0x3c},
+	{0x639a, 0xf3},
+	{0x639b, 0x0c},
+	{0x639c, 0x30},
+	{0x639d, 0xc1},
+	{0x63b9, 0xa3},
+	{0x63ba, 0xfe},
+	{0x7600, 0x01},
+	{0x79a0, 0x01},
+	{0x79a1, 0x01},
+	{0x79a2, 0x01},
+	{0x79a3, 0x01},
+	{0x79a4, 0x01},
+	{0x79a5, 0x20},
+	{0x79a9, 0x00},
+	{0x79aa, 0x01},
+	{0x79ad, 0x00},
+	{0x79af, 0x00},
+	{0x8173, 0x01},
+	{0x835c, 0x01},
+	{0x8a74, 0x01},
+	{0x8c1f, 0x00},
+	{0x8c27, 0x00},
+	{0x8c3b, 0x03},
+	{0x9004, 0x0b},
+	{0x920c, 0x6a},
+	{0x920d, 0x22},
+	{0x920e, 0x6a},
+	{0x920f, 0x23},
+	{0x9214, 0x6a},
+	{0x9215, 0x20},
+	{0x9216, 0x6a},
+	{0x9217, 0x21},
+	{0x9385, 0x3e},
+	{0x9387, 0x1b},
+	{0x938d, 0x4d},
+	{0x938f, 0x43},
+	{0x9391, 0x1b},
+	{0x9395, 0x4d},
+	{0x9397, 0x43},
+	{0x9399, 0x1b},
+	{0x939d, 0x3e},
+	{0x939f, 0x2f},
+	{0x93a5, 0x43},
+	{0x93a7, 0x2f},
+	{0x93a9, 0x2f},
+	{0x93ad, 0x34},
+	{0x93af, 0x2f},
+	{0x93b5, 0x3e},
+	{0x93b7, 0x2f},
+	{0x93bd, 0x4d},
+	{0x93bf, 0x43},
+	{0x93c1, 0x2f},
+	{0x93c5, 0x4d},
+	{0x93c7, 0x43},
+	{0x93c9, 0x2f},
+	{0x974b, 0x02},
+	{0x995c, 0x8c},
+	{0x995d, 0x00},
+	{0x995e, 0x00},
+	{0x9963, 0x64},
+	{0x9964, 0x50},
+	{0xaa0a, 0x26},
+	{0xae03, 0x04},
+	{0xae04, 0x03},
+	{0xae05, 0x03},
+	{0xbc1c, 0x08},
+	{0xbcf1, 0x02},
+	{0x38a3, 0x00},
+};
+
+/* 16 mpix 10fps */
+static const struct imx519_reg mode_4656x3496_regs[] = {
+	{0x0111, 0x02},
+	{0x0112, 0x0a},
+	{0x0113, 0x0a},
+	{0x0114, 0x01},
+	{0x0342, 0x31},
+	{0x0343, 0x6a},
+	{0x0340, 0x0d},
+	{0x0341, 0xf4},
+	{0x0344, 0x00},
+	{0x0345, 0x00},
+	{0x0346, 0x00},
+	{0x0347, 0x00},
+	{0x0348, 0x12},
+	{0x0349, 0x2f},
+	{0x034a, 0x0d},
+	{0x034b, 0xa7},
+	{0x0220, 0x00},
+	{0x0221, 0x11},
+	{0x0222, 0x01},
+	{0x0900, 0x00},
+	{0x0901, 0x11},
+	{0x0902, 0x0a},
+	{0x3f4c, 0x01},
+	{0x3f4d, 0x01},
+	{0x4254, 0x7f},
+	{0x0401, 0x00},
+	{0x0404, 0x00},
+	{0x0405, 0x10},
+	{0x0408, 0x00},
+	{0x0409, 0x00},
+	{0x040a, 0x00},
+	{0x040b, 0x00},
+	{0x040c, 0x12},
+	{0x040d, 0x30},
+	{0x040e, 0x0d},
+	{0x040f, 0xa8},
+	{0x034c, 0x12},
+	{0x034d, 0x30},
+	{0x034e, 0x0d},
+	{0x034f, 0xa8},
+	{0x0301, 0x06},
+	{0x0303, 0x04},
+	{0x0305, 0x06},
+	{0x0306, 0x01},
+	{0x0307, 0x40},
+	{0x0309, 0x0a},
+	{0x030b, 0x02},
+	{0x030d, 0x04},
+	{0x030e, 0x01},
+	{0x030f, 0x10},
+	{0x0310, 0x01},
+	{0x0820, 0x0a},
+	{0x0821, 0x20},
+	{0x0822, 0x00},
+	{0x0823, 0x00},
+	{0x3e20, 0x01},
+	{0x3e37, 0x01},
+	{0x3e3b, 0x00},
+	{0x38a4, 0x00},
+	{0x38a5, 0x00},
+	{0x38a6, 0x00},
+	{0x38a7, 0x00},
+	{0x38a8, 0x01},
+	{0x38a9, 0x23},
+	{0x38aa, 0x01},
+	{0x38ab, 0x23},
+	{0x0106, 0x00},
+	{0x0b00, 0x00},
+	{0x3230, 0x00},
+	{0x3f14, 0x01},
+	{0x3f3c, 0x01},
+	{0x3f0d, 0x0a},
+	{0x3fbc, 0x00},
+	{0x3c06, 0x00},
+	{0x3c07, 0x48},
+	{0x3c0a, 0x00},
+	{0x3c0b, 0x00},
+	{0x3f78, 0x00},
+	{0x3f79, 0x40},
+	{0x3f7c, 0x00},
+	{0x3f7d, 0x00},
+};
+
+/* 4k 21fps mode */
+static const struct imx519_reg mode_3840x2160_regs[] = {
+	{0x0111, 0x02},
+	{0x0112, 0x0a},
+	{0x0113, 0x0a},
+	{0x0114, 0x01},
+	{0x0342, 0x28},
+	{0x0343, 0xf6},
+	{0x0340, 0x08},
+	{0x0341, 0xd4},
+	{0x0344, 0x01},
+	{0x0345, 0x98},
+	{0x0346, 0x02},
+	{0x0347, 0xa0},
+	{0x0348, 0x10},
+	{0x0349, 0x97},
+	{0x034a, 0x0b},
+	{0x034b, 0x17},
+	{0x0220, 0x00},
+	{0x0221, 0x11},
+	{0x0222, 0x01},
+	{0x0900, 0x00},
+	{0x0901, 0x11},
+	{0x0902, 0x0a},
+	{0x3f4c, 0x01},
+	{0x3f4d, 0x01},
+	{0x4254, 0x7f},
+	{0x0401, 0x00},
+	{0x0404, 0x00},
+	{0x0405, 0x10},
+	{0x0408, 0x00},
+	{0x0409, 0x00},
+	{0x040a, 0x00},
+	{0x040b, 0x00},
+	{0x040c, 0x0f},
+	{0x040d, 0x00},
+	{0x040e, 0x08},
+	{0x040f, 0x70},
+	{0x034c, 0x0f},
+	{0x034d, 0x00},
+	{0x034e, 0x08},
+	{0x034f, 0x70},
+	{0x0301, 0x06},
+	{0x0303, 0x04},
+	{0x0305, 0x06},
+	{0x0306, 0x01},
+	{0x0307, 0x40},
+	{0x0309, 0x0a},
+	{0x030b, 0x02},
+	{0x030d, 0x04},
+	{0x030e, 0x01},
+	{0x030f, 0x10},
+	{0x0310, 0x01},
+	{0x0820, 0x0a},
+	{0x0821, 0x20},
+	{0x0822, 0x00},
+	{0x0823, 0x00},
+	{0x3e20, 0x01},
+	{0x3e37, 0x01},
+	{0x3e3b, 0x00},
+	{0x38a4, 0x00},
+	{0x38a5, 0x00},
+	{0x38a6, 0x00},
+	{0x38a7, 0x00},
+	{0x38a8, 0x00},
+	{0x38a9, 0xf0},
+	{0x38aa, 0x00},
+	{0x38ab, 0xb4},
+	{0x0106, 0x00},
+	{0x0b00, 0x00},
+	{0x3230, 0x00},
+	{0x3f14, 0x01},
+	{0x3f3c, 0x01},
+	{0x3f0d, 0x0a},
+	{0x3fbc, 0x00},
+	{0x3c06, 0x00},
+	{0x3c07, 0x48},
+	{0x3c0a, 0x00},
+	{0x3c0b, 0x00},
+	{0x3f78, 0x00},
+	{0x3f79, 0x40},
+	{0x3f7c, 0x00},
+	{0x3f7d, 0x00},
+};
+
+/* 2x2 binned 30fps mode */
+static const struct imx519_reg mode_2328x1748_regs[] = {
+	{0x0111, 0x02},
+	{0x0112, 0x0a},
+	{0x0113, 0x0a},
+	{0x0114, 0x01},
+	{0x0342, 0x19},
+	{0x0343, 0x70},
+	{0x0340, 0x08},
+	{0x0341, 0x88},
+	{0x0344, 0x00},
+	{0x0345, 0x00},
+	{0x0346, 0x00},
+	{0x0347, 0x00},
+	{0x0348, 0x12},
+	{0x0349, 0x2f},
+	{0x034a, 0x0d},
+	{0x034b, 0xa7},
+	{0x0220, 0x00},
+	{0x0221, 0x11},
+	{0x0222, 0x01},
+	{0x0900, 0x01},
+	{0x0901, 0x22},
+	{0x0902, 0x0a},
+	{0x3f4c, 0x05},
+	{0x3f4d, 0x03},
+	{0x4254, 0x7f},
+	{0x0401, 0x00},
+	{0x0404, 0x00},
+	{0x0405, 0x10},
+	{0x0408, 0x00},
+	{0x0409, 0x00},
+	{0x040a, 0x00},
+	{0x040b, 0x00},
+	{0x040c, 0x09},
+	{0x040d, 0x18},
+	{0x040e, 0x06},
+	{0x040f, 0xd4},
+	{0x034c, 0x09},
+	{0x034d, 0x18},
+	{0x034e, 0x06},
+	{0x034f, 0xd4},
+	{0x0301, 0x06},
+	{0x0303, 0x04},
+	{0x0305, 0x06},
+	{0x0306, 0x01},
+	{0x0307, 0x40},
+	{0x0309, 0x0a},
+	{0x030b, 0x02},
+	{0x030d, 0x04},
+	{0x030e, 0x01},
+	{0x030f, 0x10},
+	{0x0310, 0x01},
+	{0x0820, 0x0a},
+	{0x0821, 0x20},
+	{0x0822, 0x00},
+	{0x0823, 0x00},
+	{0x3e20, 0x01},
+	{0x3e37, 0x01},
+	{0x3e3b, 0x00},
+	{0x38a4, 0x00},
+	{0x38a5, 0x00},
+	{0x38a6, 0x00},
+	{0x38a7, 0x00},
+	{0x38a8, 0x00},
+	{0x38a9, 0x91},
+	{0x38aa, 0x00},
+	{0x38ab, 0x91},
+	{0x0106, 0x00},
+	{0x0b00, 0x00},
+	{0x3230, 0x00},
+	{0x3f14, 0x01},
+	{0x3f3c, 0x01},
+	{0x3f0d, 0x0a},
+	{0x3fbc, 0x00},
+	{0x3c06, 0x00},
+	{0x3c07, 0x48},
+	{0x3c0a, 0x00},
+	{0x3c0b, 0x00},
+	{0x3f78, 0x00},
+	{0x3f79, 0x40},
+	{0x3f7c, 0x00},
+	{0x3f7d, 0x00},
+};
+
+/* 1080p 60fps mode */
+static const struct imx519_reg mode_1920x1080_regs[] = {
+	{0x0111, 0x02},
+	{0x0112, 0x0a},
+	{0x0113, 0x0a},
+	{0x0114, 0x01},
+	{0x0342, 0x17},
+	{0x0343, 0x8b},
+	{0x0340, 0x04},
+	{0x0341, 0x9c},
+	{0x0344, 0x01},
+	{0x0345, 0x98},
+	{0x0346, 0x02},
+	{0x0347, 0xa2},
+	{0x0348, 0x10},
+	{0x0349, 0x97},
+	{0x034a, 0x0b},
+	{0x034b, 0x15},
+	{0x0220, 0x00},
+	{0x0221, 0x11},
+	{0x0222, 0x01},
+	{0x0900, 0x01},
+	{0x0901, 0x22},
+	{0x0902, 0x0a},
+	{0x3f4c, 0x05},
+	{0x3f4d, 0x03},
+	{0x4254, 0x7f},
+	{0x0401, 0x00},
+	{0x0404, 0x00},
+	{0x0405, 0x10},
+	{0x0408, 0x00},
+	{0x0409, 0x00},
+	{0x040a, 0x00},
+	{0x040b, 0x00},
+	{0x040c, 0x07},
+	{0x040d, 0x80},
+	{0x040e, 0x04},
+	{0x040f, 0x38},
+	{0x034c, 0x07},
+	{0x034d, 0x80},
+	{0x034e, 0x04},
+	{0x034f, 0x38},
+	{0x0301, 0x06},
+	{0x0303, 0x04},
+	{0x0305, 0x06},
+	{0x0306, 0x01},
+	{0x0307, 0x40},
+	{0x0309, 0x0a},
+	{0x030b, 0x02},
+	{0x030d, 0x04},
+	{0x030e, 0x01},
+	{0x030f, 0x10},
+	{0x0310, 0x01},
+	{0x0820, 0x0a},
+	{0x0821, 0x20},
+	{0x0822, 0x00},
+	{0x0823, 0x00},
+	{0x3e20, 0x01},
+	{0x3e37, 0x01},
+	{0x3e3b, 0x00},
+	{0x38a4, 0x00},
+	{0x38a5, 0x00},
+	{0x38a6, 0x00},
+	{0x38a7, 0x00},
+	{0x38a8, 0x00},
+	{0x38a9, 0x78},
+	{0x38aa, 0x00},
+	{0x38ab, 0x5a},
+	{0x0106, 0x00},
+	{0x0b00, 0x00},
+	{0x3230, 0x00},
+	{0x3f14, 0x01},
+	{0x3f3c, 0x01},
+	{0x3f0d, 0x0a},
+	{0x3fbc, 0x00},
+	{0x3c06, 0x00},
+	{0x3c07, 0x48},
+	{0x3c0a, 0x00},
+	{0x3c0b, 0x00},
+	{0x3f78, 0x00},
+	{0x3f79, 0x40},
+	{0x3f7c, 0x00},
+	{0x3f7d, 0x00},
+};
+
+/* 720p 120fps mode */
+static const struct imx519_reg mode_1280x720_regs[] = {
+	{0x0111, 0x02},
+	{0x0112, 0x0a},
+	{0x0113, 0x0a},
+	{0x0114, 0x01},
+	{0x0342, 0x18},
+	{0x0343, 0x00},
+	{0x0340, 0x03},
+	{0x0341, 0x34},
+	{0x0344, 0x04},
+	{0x0345, 0x18},
+	{0x0346, 0x04},
+	{0x0347, 0x12},
+	{0x0348, 0x0e},
+	{0x0349, 0x17},
+	{0x034a, 0x09},
+	{0x034b, 0xb6},
+	{0x0220, 0x00},
+	{0x0221, 0x11},
+	{0x0222, 0x01},
+	{0x0900, 0x01},
+	{0x0901, 0x22},
+	{0x0902, 0x0a},
+	{0x3f4c, 0x05},
+	{0x3f4d, 0x03},
+	{0x4254, 0x7f},
+	{0x0401, 0x00},
+	{0x0404, 0x00},
+	{0x0405, 0x10},
+	{0x0408, 0x00},
+	{0x0409, 0x00},
+	{0x040a, 0x00},
+	{0x040b, 0x00},
+	{0x040c, 0x05},
+	{0x040d, 0x00},
+	{0x040e, 0x02},
+	{0x040f, 0xd0},
+	{0x034c, 0x05},
+	{0x034d, 0x00},
+	{0x034e, 0x02},
+	{0x034f, 0xd0},
+	{0x0301, 0x06},
+	{0x0303, 0x04},
+	{0x0305, 0x06},
+	{0x0306, 0x01},
+	{0x0307, 0x40},
+	{0x0309, 0x0a},
+	{0x030b, 0x02},
+	{0x030d, 0x04},
+	{0x030e, 0x01},
+	{0x030f, 0x10},
+	{0x0310, 0x01},
+	{0x0820, 0x0a},
+	{0x0821, 0x20},
+	{0x0822, 0x00},
+	{0x0823, 0x00},
+	{0x3e20, 0x01},
+	{0x3e37, 0x01},
+	{0x3e3b, 0x00},
+	{0x38a4, 0x00},
+	{0x38a5, 0x00},
+	{0x38a6, 0x00},
+	{0x38a7, 0x00},
+	{0x38a8, 0x00},
+	{0x38a9, 0x50},
+	{0x38aa, 0x00},
+	{0x38ab, 0x3c},
+	{0x0106, 0x00},
+	{0x0b00, 0x00},
+	{0x3230, 0x00},
+	{0x3f14, 0x01},
+	{0x3f3c, 0x01},
+	{0x3f0d, 0x0a},
+	{0x3fbc, 0x00},
+	{0x3c06, 0x00},
+	{0x3c07, 0x48},
+	{0x3c0a, 0x00},
+	{0x3c0b, 0x00},
+	{0x3f78, 0x00},
+	{0x3f79, 0x40},
+	{0x3f7c, 0x00},
+	{0x3f7d, 0x00},
+};
+
+/* Mode configs */
+static const struct imx519_mode supported_modes_10bit[] = {
+	{
+		.width = 4656,
+		.height = 3496,
+		.line_length_pix = 0x316a,
+		.crop = {
+			.left = IMX519_PIXEL_ARRAY_LEFT,
+			.top = IMX519_PIXEL_ARRAY_TOP,
+			.width = 4656,
+			.height = 3496,
+		},
+		.timeperframe_min = {
+			.numerator = 100,
+			.denominator = 900
+		},
+		.timeperframe_default = {
+			.numerator = 100,
+			.denominator = 900
+		},
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_4656x3496_regs),
+			.regs = mode_4656x3496_regs,
+		}
+	},
+	{
+		.width = 3840,
+		.height = 2160,
+		.line_length_pix = 0x28f6,
+		.crop = {
+			.left = IMX519_PIXEL_ARRAY_LEFT + 408,
+			.top = IMX519_PIXEL_ARRAY_TOP + 672,
+			.width = 3840,
+			.height = 2160,
+		},
+		.timeperframe_min = {
+			.numerator = 100,
+			.denominator = 1800
+		},
+		.timeperframe_default = {
+			.numerator = 100,
+			.denominator = 1800
+		},
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_3840x2160_regs),
+			.regs = mode_3840x2160_regs,
+		}
+	},
+	{
+		.width = 2328,
+		.height = 1748,
+		.line_length_pix = 0x1970,
+		.crop = {
+			.left = IMX519_PIXEL_ARRAY_LEFT,
+			.top = IMX519_PIXEL_ARRAY_TOP,
+			.width = 4656,
+			.height = 3496,
+		},
+		.timeperframe_min = {
+			.numerator = 100,
+			.denominator = 3000
+		},
+		.timeperframe_default = {
+			.numerator = 100,
+			.denominator = 3000
+		},
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_2328x1748_regs),
+			.regs = mode_2328x1748_regs,
+		}
+	},
+	{
+		.width = 1920,
+		.height = 1080,
+		.line_length_pix = 0x178b,
+		.crop = {
+			.left = IMX519_PIXEL_ARRAY_LEFT + 408,
+			.top = IMX519_PIXEL_ARRAY_TOP + 674,
+			.width = 3840,
+			.height = 2160,
+		},
+		.timeperframe_min = {
+			.numerator = 100,
+			.denominator = 6000
+		},
+		.timeperframe_default = {
+			.numerator = 100,
+			.denominator = 6000
+		},
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_1920x1080_regs),
+			.regs = mode_1920x1080_regs,
+		}
+	},
+	{
+		.width = 1280,
+		.height = 720,
+		.line_length_pix = 0x1800,
+		.crop = {
+			.left = IMX519_PIXEL_ARRAY_LEFT + 1048,
+			.top = IMX519_PIXEL_ARRAY_TOP + 1042,
+			.width = 2560,
+			.height = 1440,
+		},
+		.timeperframe_min = {
+			.numerator = 100,
+			.denominator = 8000
+		},
+		.timeperframe_default = {
+			.numerator = 100,
+			.denominator = 8000
+		},
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_1280x720_regs),
+			.regs = mode_1280x720_regs,
+		}
+	}
+};
+
+/*
+ * The supported formats.
+ * This table MUST contain 4 entries per format, to cover the various flip
+ * combinations in the order
+ * - no flip
+ * - h flip
+ * - v flip
+ * - h&v flips
+ */
+static const u32 codes[] = {
+	/* 10-bit modes. */
+	MEDIA_BUS_FMT_SRGGB10_1X10,
+	MEDIA_BUS_FMT_SGRBG10_1X10,
+	MEDIA_BUS_FMT_SGBRG10_1X10,
+	MEDIA_BUS_FMT_SBGGR10_1X10,
+};
+
+static const char * const imx519_test_pattern_menu[] = {
+	"Disabled",
+	"Color Bars",
+	"Solid Color",
+	"Grey Color Bars",
+	"PN9"
+};
+
+static const int imx519_test_pattern_val[] = {
+	IMX519_TEST_PATTERN_DISABLE,
+	IMX519_TEST_PATTERN_COLOR_BARS,
+	IMX519_TEST_PATTERN_SOLID_COLOR,
+	IMX519_TEST_PATTERN_GREY_COLOR,
+	IMX519_TEST_PATTERN_PN9,
+};
+
+/* regulator supplies */
+static const char * const imx519_supply_name[] = {
+	/* Supplies can be enabled in any order */
+	"VANA",  /* Analog (2.8V) supply */
+	"VDIG",  /* Digital Core (1.05V) supply */
+	"VDDL",  /* IF (1.8V) supply */
+};
+
+#define IMX519_NUM_SUPPLIES ARRAY_SIZE(imx519_supply_name)
+
+/*
+ * Initialisation delay between XCLR low->high and the moment when the sensor
+ * can start capture (i.e. can leave software standby), given by T7 in the
+ * datasheet is 8ms.  This does include I2C setup time as well.
+ *
+ * Note, that delay between XCLR low->high and reading the CCI ID register (T6
+ * in the datasheet) is much smaller - 600us.
+ */
+#define IMX519_XCLR_MIN_DELAY_US	8000
+#define IMX519_XCLR_DELAY_RANGE_US	1000
+
+struct imx519 {
+	struct v4l2_subdev sd;
+	struct media_pad pad[NUM_PADS];
+
+	unsigned int fmt_code;
+
+	struct clk *xclk;
+
+	struct gpio_desc *reset_gpio;
+	struct regulator_bulk_data supplies[IMX519_NUM_SUPPLIES];
+
+	struct v4l2_ctrl_handler ctrl_handler;
+	/* V4L2 Controls */
+	struct v4l2_ctrl *pixel_rate;
+	struct v4l2_ctrl *exposure;
+	struct v4l2_ctrl *vflip;
+	struct v4l2_ctrl *hflip;
+	struct v4l2_ctrl *vblank;
+	struct v4l2_ctrl *hblank;
+
+	/* Current mode */
+	const struct imx519_mode *mode;
+
+	/*
+	 * Mutex for serialized access:
+	 * Protect sensor module set pad format and start/stop streaming safely.
+	 */
+	struct mutex mutex;
+
+	/* Streaming on/off */
+	bool streaming;
+
+	/* Rewrite common registers on stream on? */
+	bool common_regs_written;
+
+	/* Current long exposure factor in use. Set through V4L2_CID_VBLANK */
+	unsigned int long_exp_shift;
+};
+
+static inline struct imx519 *to_imx519(struct v4l2_subdev *_sd)
+{
+	return container_of(_sd, struct imx519, sd);
+}
+
+/* Read registers up to 2 at a time */
+static int imx519_read_reg(struct imx519 *imx519, u16 reg, u32 len, u32 *val)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx519->sd);
+	struct i2c_msg msgs[2];
+	u8 addr_buf[2] = { reg >> 8, reg & 0xff };
+	u8 data_buf[4] = { 0, };
+	int ret;
+
+	if (len > 4)
+		return -EINVAL;
+
+	/* Write register address */
+	msgs[0].addr = client->addr;
+	msgs[0].flags = 0;
+	msgs[0].len = ARRAY_SIZE(addr_buf);
+	msgs[0].buf = addr_buf;
+
+	/* Read data from register */
+	msgs[1].addr = client->addr;
+	msgs[1].flags = I2C_M_RD;
+	msgs[1].len = len;
+	msgs[1].buf = &data_buf[4 - len];
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret != ARRAY_SIZE(msgs))
+		return -EIO;
+
+	*val = get_unaligned_be32(data_buf);
+
+	return 0;
+}
+
+/* Write registers up to 2 at a time */
+static int imx519_write_reg(struct imx519 *imx519, u16 reg, u32 len, u32 val)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx519->sd);
+	u8 buf[6];
+
+	if (len > 4)
+		return -EINVAL;
+
+	put_unaligned_be16(reg, buf);
+	put_unaligned_be32(val << (8 * (4 - len)), buf + 2);
+	if (i2c_master_send(client, buf, len + 2) != len + 2)
+		return -EIO;
+
+	return 0;
+}
+
+/* Write a list of registers */
+static int imx519_write_regs(struct imx519 *imx519,
+			     const struct imx519_reg *regs, u32 len)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx519->sd);
+	unsigned int i;
+	int ret;
+
+	for (i = 0; i < len; i++) {
+		ret = imx519_write_reg(imx519, regs[i].address, 1, regs[i].val);
+		if (ret) {
+			dev_err_ratelimited(&client->dev,
+					    "Failed to write reg 0x%4.4x. error = %d\n",
+					    regs[i].address, ret);
+
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+/* Get bayer order based on flip setting. */
+static u32 imx519_get_format_code(struct imx519 *imx519)
+{
+	unsigned int i;
+
+	lockdep_assert_held(&imx519->mutex);
+
+	i = (imx519->vflip->val ? 2 : 0) |
+	    (imx519->hflip->val ? 1 : 0);
+
+	return codes[i];
+}
+
+static int imx519_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+	struct imx519 *imx519 = to_imx519(sd);
+	struct v4l2_mbus_framefmt *try_fmt_img =
+		v4l2_subdev_state_get_format(fh->state, IMAGE_PAD);
+	struct v4l2_mbus_framefmt *try_fmt_meta =
+		v4l2_subdev_state_get_format(fh->state, METADATA_PAD);
+	struct v4l2_rect *try_crop;
+
+	mutex_lock(&imx519->mutex);
+
+	/* Initialize try_fmt for the image pad */
+	try_fmt_img->width = supported_modes_10bit[0].width;
+	try_fmt_img->height = supported_modes_10bit[0].height;
+	try_fmt_img->code = imx519_get_format_code(imx519);
+	try_fmt_img->field = V4L2_FIELD_NONE;
+
+	/* Initialize try_fmt for the embedded metadata pad */
+	try_fmt_meta->width = IMX519_EMBEDDED_LINE_WIDTH;
+	try_fmt_meta->height = IMX519_NUM_EMBEDDED_LINES;
+	try_fmt_meta->code = MEDIA_BUS_FMT_SENSOR_DATA;
+	try_fmt_meta->field = V4L2_FIELD_NONE;
+
+	/* Initialize try_crop */
+	try_crop = v4l2_subdev_state_get_crop(fh->state, IMAGE_PAD);
+	try_crop->left = IMX519_PIXEL_ARRAY_LEFT;
+	try_crop->top = IMX519_PIXEL_ARRAY_TOP;
+	try_crop->width = IMX519_PIXEL_ARRAY_WIDTH;
+	try_crop->height = IMX519_PIXEL_ARRAY_HEIGHT;
+
+	mutex_unlock(&imx519->mutex);
+
+	return 0;
+}
+
+static void imx519_adjust_exposure_range(struct imx519 *imx519)
+{
+	int exposure_max, exposure_def;
+
+	/* Honour the VBLANK limits when setting exposure. */
+	exposure_max = imx519->mode->height + imx519->vblank->val -
+		       IMX519_EXPOSURE_OFFSET;
+	exposure_def = min(exposure_max, imx519->exposure->val);
+	__v4l2_ctrl_modify_range(imx519->exposure, imx519->exposure->minimum,
+				 exposure_max, imx519->exposure->step,
+				 exposure_def);
+}
+
+static int imx519_set_frame_length(struct imx519 *imx519, unsigned int val)
+{
+	int ret = 0;
+
+	imx519->long_exp_shift = 0;
+
+	while (val > IMX519_FRAME_LENGTH_MAX) {
+		imx519->long_exp_shift++;
+		val >>= 1;
+	}
+
+	ret = imx519_write_reg(imx519, IMX519_REG_FRAME_LENGTH,
+			       IMX519_REG_VALUE_16BIT, val);
+	if (ret)
+		return ret;
+
+	return imx519_write_reg(imx519, IMX519_LONG_EXP_SHIFT_REG,
+				IMX519_REG_VALUE_08BIT, imx519->long_exp_shift);
+}
+
+static int imx519_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct imx519 *imx519 =
+		container_of(ctrl->handler, struct imx519, ctrl_handler);
+	struct i2c_client *client = v4l2_get_subdevdata(&imx519->sd);
+	int ret = 0;
+
+	/*
+	 * The VBLANK control may change the limits of usable exposure, so check
+	 * and adjust if necessary.
+	 */
+	if (ctrl->id == V4L2_CID_VBLANK)
+		imx519_adjust_exposure_range(imx519);
+
+	/*
+	 * Applying V4L2 control value only happens
+	 * when power is up for streaming
+	 */
+	if (pm_runtime_get_if_in_use(&client->dev) == 0)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_ANALOGUE_GAIN:
+		ret = imx519_write_reg(imx519, IMX519_REG_ANALOG_GAIN,
+				       IMX519_REG_VALUE_16BIT, ctrl->val);
+		break;
+	case V4L2_CID_EXPOSURE:
+		ret = imx519_write_reg(imx519, IMX519_REG_EXPOSURE,
+				       IMX519_REG_VALUE_16BIT, ctrl->val >>
+							imx519->long_exp_shift);
+		break;
+	case V4L2_CID_DIGITAL_GAIN:
+		ret = imx519_write_reg(imx519, IMX519_REG_DIGITAL_GAIN,
+				       IMX519_REG_VALUE_16BIT, ctrl->val);
+		break;
+	case V4L2_CID_TEST_PATTERN:
+		ret = imx519_write_reg(imx519, IMX519_REG_TEST_PATTERN,
+				       IMX519_REG_VALUE_16BIT,
+				       imx519_test_pattern_val[ctrl->val]);
+		break;
+	case V4L2_CID_TEST_PATTERN_RED:
+		ret = imx519_write_reg(imx519, IMX519_REG_TEST_PATTERN_R,
+				       IMX519_REG_VALUE_16BIT, ctrl->val);
+		break;
+	case V4L2_CID_TEST_PATTERN_GREENR:
+		ret = imx519_write_reg(imx519, IMX519_REG_TEST_PATTERN_GR,
+				       IMX519_REG_VALUE_16BIT, ctrl->val);
+		break;
+	case V4L2_CID_TEST_PATTERN_BLUE:
+		ret = imx519_write_reg(imx519, IMX519_REG_TEST_PATTERN_B,
+				       IMX519_REG_VALUE_16BIT, ctrl->val);
+		break;
+	case V4L2_CID_TEST_PATTERN_GREENB:
+		ret = imx519_write_reg(imx519, IMX519_REG_TEST_PATTERN_GB,
+				       IMX519_REG_VALUE_16BIT, ctrl->val);
+		break;
+	case V4L2_CID_HFLIP:
+	case V4L2_CID_VFLIP:
+		ret = imx519_write_reg(imx519, IMX519_REG_ORIENTATION, 1,
+				       imx519->hflip->val |
+				       imx519->vflip->val << 1);
+		break;
+	case V4L2_CID_VBLANK:
+		ret = imx519_set_frame_length(imx519,
+					      imx519->mode->height + ctrl->val);
+		break;
+	default:
+		dev_info(&client->dev,
+			 "ctrl(id:0x%x,val:0x%x) is not handled\n",
+			 ctrl->id, ctrl->val);
+		ret = -EINVAL;
+		break;
+	}
+
+	pm_runtime_put(&client->dev);
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops imx519_ctrl_ops = {
+	.s_ctrl = imx519_set_ctrl,
+};
+
+static int imx519_enum_mbus_code(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *sd_state,
+				 struct v4l2_subdev_mbus_code_enum *code)
+{
+	struct imx519 *imx519 = to_imx519(sd);
+
+	if (code->pad >= NUM_PADS)
+		return -EINVAL;
+
+	if (code->pad == IMAGE_PAD) {
+		if (code->index > 0)
+			return -EINVAL;
+
+		code->code = imx519_get_format_code(imx519);
+	} else {
+		if (code->index > 0)
+			return -EINVAL;
+
+		code->code = MEDIA_BUS_FMT_SENSOR_DATA;
+	}
+
+	return 0;
+}
+
+static int imx519_enum_frame_size(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *sd_state,
+				  struct v4l2_subdev_frame_size_enum *fse)
+{
+	struct imx519 *imx519 = to_imx519(sd);
+
+	if (fse->pad >= NUM_PADS)
+		return -EINVAL;
+
+	if (fse->pad == IMAGE_PAD) {
+		if (fse->index >= ARRAY_SIZE(supported_modes_10bit))
+			return -EINVAL;
+
+		if (fse->code != imx519_get_format_code(imx519))
+			return -EINVAL;
+
+		fse->min_width = supported_modes_10bit[fse->index].width;
+		fse->max_width = fse->min_width;
+		fse->min_height = supported_modes_10bit[fse->index].height;
+		fse->max_height = fse->min_height;
+	} else {
+		if (fse->code != MEDIA_BUS_FMT_SENSOR_DATA || fse->index > 0)
+			return -EINVAL;
+
+		fse->min_width = IMX519_EMBEDDED_LINE_WIDTH;
+		fse->max_width = fse->min_width;
+		fse->min_height = IMX519_NUM_EMBEDDED_LINES;
+		fse->max_height = fse->min_height;
+	}
+
+	return 0;
+}
+
+static void imx519_reset_colorspace(struct v4l2_mbus_framefmt *fmt)
+{
+	fmt->colorspace = V4L2_COLORSPACE_RAW;
+	fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace);
+	fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true,
+							  fmt->colorspace,
+							  fmt->ycbcr_enc);
+	fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace);
+}
+
+static void imx519_update_image_pad_format(struct imx519 *imx519,
+					   const struct imx519_mode *mode,
+					   struct v4l2_subdev_format *fmt)
+{
+	fmt->format.width = mode->width;
+	fmt->format.height = mode->height;
+	fmt->format.field = V4L2_FIELD_NONE;
+	imx519_reset_colorspace(&fmt->format);
+}
+
+static void imx519_update_metadata_pad_format(struct v4l2_subdev_format *fmt)
+{
+	fmt->format.width = IMX519_EMBEDDED_LINE_WIDTH;
+	fmt->format.height = IMX519_NUM_EMBEDDED_LINES;
+	fmt->format.code = MEDIA_BUS_FMT_SENSOR_DATA;
+	fmt->format.field = V4L2_FIELD_NONE;
+}
+
+static int imx519_get_pad_format(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *sd_state,
+				 struct v4l2_subdev_format *fmt)
+{
+	struct imx519 *imx519 = to_imx519(sd);
+
+	if (fmt->pad >= NUM_PADS)
+		return -EINVAL;
+
+	mutex_lock(&imx519->mutex);
+
+	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+		struct v4l2_mbus_framefmt *try_fmt =
+			v4l2_subdev_state_get_format(sd_state,
+						   fmt->pad);
+		/* update the code which could change due to vflip or hflip: */
+		try_fmt->code = fmt->pad == IMAGE_PAD ?
+				imx519_get_format_code(imx519) :
+				MEDIA_BUS_FMT_SENSOR_DATA;
+		fmt->format = *try_fmt;
+	} else {
+		if (fmt->pad == IMAGE_PAD) {
+			imx519_update_image_pad_format(imx519, imx519->mode,
+						       fmt);
+			fmt->format.code =
+			       imx519_get_format_code(imx519);
+		} else {
+			imx519_update_metadata_pad_format(fmt);
+		}
+	}
+
+	mutex_unlock(&imx519->mutex);
+	return 0;
+}
+
+static
+unsigned int imx519_get_frame_length(const struct imx519_mode *mode,
+				     const struct v4l2_fract *timeperframe)
+{
+	u64 frame_length;
+
+	frame_length = (u64)timeperframe->numerator * IMX519_PIXEL_RATE;
+	do_div(frame_length,
+	       (u64)timeperframe->denominator * mode->line_length_pix);
+
+	if (WARN_ON(frame_length > IMX519_FRAME_LENGTH_MAX))
+		frame_length = IMX519_FRAME_LENGTH_MAX;
+
+	return max_t(unsigned int, frame_length, mode->height);
+}
+
+static void imx519_set_framing_limits(struct imx519 *imx519)
+{
+	unsigned int frm_length_min, frm_length_default, hblank;
+	const struct imx519_mode *mode = imx519->mode;
+
+	frm_length_min = imx519_get_frame_length(mode, &mode->timeperframe_min);
+	frm_length_default =
+		     imx519_get_frame_length(mode, &mode->timeperframe_default);
+
+	/* Default to no long exposure multiplier. */
+	imx519->long_exp_shift = 0;
+
+	/* Update limits and set FPS to default */
+	__v4l2_ctrl_modify_range(imx519->vblank, frm_length_min - mode->height,
+				 ((1 << IMX519_LONG_EXP_SHIFT_MAX) *
+					IMX519_FRAME_LENGTH_MAX) - mode->height,
+				 1, frm_length_default - mode->height);
+
+	/* Setting this will adjust the exposure limits as well. */
+	__v4l2_ctrl_s_ctrl(imx519->vblank, frm_length_default - mode->height);
+
+	/*
+	 * Currently PPL is fixed to the mode specified value, so hblank
+	 * depends on mode->width only, and is not changeable in any
+	 * way other than changing the mode.
+	 */
+	hblank = mode->line_length_pix - mode->width;
+	__v4l2_ctrl_modify_range(imx519->hblank, hblank, hblank, 1, hblank);
+}
+
+static int imx519_set_pad_format(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *sd_state,
+				 struct v4l2_subdev_format *fmt)
+{
+	struct v4l2_mbus_framefmt *framefmt;
+	const struct imx519_mode *mode;
+	struct imx519 *imx519 = to_imx519(sd);
+
+	if (fmt->pad >= NUM_PADS)
+		return -EINVAL;
+
+	mutex_lock(&imx519->mutex);
+
+	if (fmt->pad == IMAGE_PAD) {
+		/* Bayer order varies with flips */
+		fmt->format.code = imx519_get_format_code(imx519);
+
+		mode = v4l2_find_nearest_size(supported_modes_10bit,
+					      ARRAY_SIZE(supported_modes_10bit),
+					      width, height,
+					      fmt->format.width,
+					      fmt->format.height);
+		imx519_update_image_pad_format(imx519, mode, fmt);
+		if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+			framefmt = v4l2_subdev_state_get_format(sd_state,
+							      fmt->pad);
+			*framefmt = fmt->format;
+		} else {
+			imx519->mode = mode;
+			imx519->fmt_code = fmt->format.code;
+			imx519_set_framing_limits(imx519);
+		}
+	} else {
+		if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+			framefmt = v4l2_subdev_state_get_format(sd_state,
+							      fmt->pad);
+			*framefmt = fmt->format;
+		} else {
+			/* Only one embedded data mode is supported */
+			imx519_update_metadata_pad_format(fmt);
+		}
+	}
+
+	mutex_unlock(&imx519->mutex);
+
+	return 0;
+}
+
+static const struct v4l2_rect *
+__imx519_get_pad_crop(struct imx519 *imx519, struct v4l2_subdev_state *sd_state,
+		      unsigned int pad, enum v4l2_subdev_format_whence which)
+{
+	switch (which) {
+	case V4L2_SUBDEV_FORMAT_TRY:
+		return v4l2_subdev_state_get_crop(sd_state, pad);
+	case V4L2_SUBDEV_FORMAT_ACTIVE:
+		return &imx519->mode->crop;
+	}
+
+	return NULL;
+}
+
+static int imx519_get_selection(struct v4l2_subdev *sd,
+				struct v4l2_subdev_state *sd_state,
+				struct v4l2_subdev_selection *sel)
+{
+	switch (sel->target) {
+	case V4L2_SEL_TGT_CROP: {
+		struct imx519 *imx519 = to_imx519(sd);
+
+		mutex_lock(&imx519->mutex);
+		sel->r = *__imx519_get_pad_crop(imx519, sd_state, sel->pad,
+						sel->which);
+		mutex_unlock(&imx519->mutex);
+
+		return 0;
+	}
+
+	case V4L2_SEL_TGT_NATIVE_SIZE:
+		sel->r.left = 0;
+		sel->r.top = 0;
+		sel->r.width = IMX519_NATIVE_WIDTH;
+		sel->r.height = IMX519_NATIVE_HEIGHT;
+
+		return 0;
+
+	case V4L2_SEL_TGT_CROP_DEFAULT:
+	case V4L2_SEL_TGT_CROP_BOUNDS:
+		sel->r.left = IMX519_PIXEL_ARRAY_LEFT;
+		sel->r.top = IMX519_PIXEL_ARRAY_TOP;
+		sel->r.width = IMX519_PIXEL_ARRAY_WIDTH;
+		sel->r.height = IMX519_PIXEL_ARRAY_HEIGHT;
+
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+/* Start streaming */
+static int imx519_start_streaming(struct imx519 *imx519)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx519->sd);
+	const struct imx519_reg_list *reg_list;
+	int ret;
+
+	if (!imx519->common_regs_written) {
+		ret = imx519_write_regs(imx519, mode_common_regs,
+					ARRAY_SIZE(mode_common_regs));
+
+		if (ret) {
+			dev_err(&client->dev, "%s failed to set common settings\n",
+				__func__);
+			return ret;
+		}
+		imx519->common_regs_written = true;
+	}
+
+	/* Apply default values of current mode */
+	reg_list = &imx519->mode->reg_list;
+	ret = imx519_write_regs(imx519, reg_list->regs, reg_list->num_of_regs);
+	if (ret) {
+		dev_err(&client->dev, "%s failed to set mode\n", __func__);
+		return ret;
+	}
+
+	/* Apply customized values from user */
+	ret =  __v4l2_ctrl_handler_setup(imx519->sd.ctrl_handler);
+	if (ret)
+		return ret;
+
+	/* set stream on register */
+	return imx519_write_reg(imx519, IMX519_REG_MODE_SELECT,
+				IMX519_REG_VALUE_08BIT, IMX519_MODE_STREAMING);
+}
+
+/* Stop streaming */
+static void imx519_stop_streaming(struct imx519 *imx519)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx519->sd);
+	int ret;
+
+	/* set stream off register */
+	ret = imx519_write_reg(imx519, IMX519_REG_MODE_SELECT,
+			       IMX519_REG_VALUE_08BIT, IMX519_MODE_STANDBY);
+	if (ret)
+		dev_err(&client->dev, "%s failed to set stream\n", __func__);
+}
+
+static int imx519_set_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct imx519 *imx519 = to_imx519(sd);
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	int ret = 0;
+
+	mutex_lock(&imx519->mutex);
+	if (imx519->streaming == enable) {
+		mutex_unlock(&imx519->mutex);
+		return 0;
+	}
+
+	if (enable) {
+		ret = pm_runtime_get_sync(&client->dev);
+		if (ret < 0) {
+			pm_runtime_put_noidle(&client->dev);
+			goto err_unlock;
+		}
+
+		/*
+		 * Apply default & customized values
+		 * and then start streaming.
+		 */
+		ret = imx519_start_streaming(imx519);
+		if (ret)
+			goto err_rpm_put;
+	} else {
+		imx519_stop_streaming(imx519);
+		pm_runtime_put(&client->dev);
+	}
+
+	imx519->streaming = enable;
+
+	/* vflip and hflip cannot change during streaming */
+	__v4l2_ctrl_grab(imx519->vflip, enable);
+	__v4l2_ctrl_grab(imx519->hflip, enable);
+
+	mutex_unlock(&imx519->mutex);
+
+	return ret;
+
+err_rpm_put:
+	pm_runtime_put(&client->dev);
+err_unlock:
+	mutex_unlock(&imx519->mutex);
+
+	return ret;
+}
+
+/* Power/clock management functions */
+static int imx519_power_on(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct imx519 *imx519 = to_imx519(sd);
+	int ret;
+
+	ret = regulator_bulk_enable(IMX519_NUM_SUPPLIES,
+				    imx519->supplies);
+	if (ret) {
+		dev_err(&client->dev, "%s: failed to enable regulators\n",
+			__func__);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(imx519->xclk);
+	if (ret) {
+		dev_err(&client->dev, "%s: failed to enable clock\n",
+			__func__);
+		goto reg_off;
+	}
+
+	gpiod_set_value_cansleep(imx519->reset_gpio, 1);
+	usleep_range(IMX519_XCLR_MIN_DELAY_US,
+		     IMX519_XCLR_MIN_DELAY_US + IMX519_XCLR_DELAY_RANGE_US);
+
+	return 0;
+
+reg_off:
+	regulator_bulk_disable(IMX519_NUM_SUPPLIES, imx519->supplies);
+	return ret;
+}
+
+static int imx519_power_off(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct imx519 *imx519 = to_imx519(sd);
+
+	gpiod_set_value_cansleep(imx519->reset_gpio, 0);
+	regulator_bulk_disable(IMX519_NUM_SUPPLIES, imx519->supplies);
+	clk_disable_unprepare(imx519->xclk);
+
+	/* Force reprogramming of the common registers when powered up again. */
+	imx519->common_regs_written = false;
+
+	return 0;
+}
+
+static int __maybe_unused imx519_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct imx519 *imx519 = to_imx519(sd);
+
+	if (imx519->streaming)
+		imx519_stop_streaming(imx519);
+
+	return 0;
+}
+
+static int __maybe_unused imx519_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct imx519 *imx519 = to_imx519(sd);
+	int ret;
+
+	if (imx519->streaming) {
+		ret = imx519_start_streaming(imx519);
+		if (ret)
+			goto error;
+	}
+
+	return 0;
+
+error:
+	imx519_stop_streaming(imx519);
+	imx519->streaming = 0;
+	return ret;
+}
+
+static int imx519_get_regulators(struct imx519 *imx519)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx519->sd);
+	unsigned int i;
+
+	for (i = 0; i < IMX519_NUM_SUPPLIES; i++)
+		imx519->supplies[i].supply = imx519_supply_name[i];
+
+	return devm_regulator_bulk_get(&client->dev,
+				       IMX519_NUM_SUPPLIES,
+				       imx519->supplies);
+}
+
+/* Verify chip ID */
+static int imx519_identify_module(struct imx519 *imx519, u32 expected_id)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx519->sd);
+	int ret;
+	u32 val;
+
+	ret = imx519_read_reg(imx519, IMX519_REG_CHIP_ID,
+			      IMX519_REG_VALUE_16BIT, &val);
+	if (ret) {
+		dev_err(&client->dev, "failed to read chip id %x, with error %d\n",
+			expected_id, ret);
+		return ret;
+	}
+
+	if (val != expected_id) {
+		dev_err(&client->dev, "chip id mismatch: %x!=%x\n",
+			expected_id, val);
+		return -EIO;
+	}
+
+	dev_info(&client->dev, "Device found is imx%x\n", val);
+
+	return 0;
+}
+
+static const struct v4l2_subdev_core_ops imx519_core_ops = {
+	.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_video_ops imx519_video_ops = {
+	.s_stream = imx519_set_stream,
+};
+
+static const struct v4l2_subdev_pad_ops imx519_pad_ops = {
+	.enum_mbus_code = imx519_enum_mbus_code,
+	.get_fmt = imx519_get_pad_format,
+	.set_fmt = imx519_set_pad_format,
+	.get_selection = imx519_get_selection,
+	.enum_frame_size = imx519_enum_frame_size,
+};
+
+static const struct v4l2_subdev_ops imx519_subdev_ops = {
+	.core = &imx519_core_ops,
+	.video = &imx519_video_ops,
+	.pad = &imx519_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops imx519_internal_ops = {
+	.open = imx519_open,
+};
+
+/* Initialize control handlers */
+static int imx519_init_controls(struct imx519 *imx519)
+{
+	struct v4l2_ctrl_handler *ctrl_hdlr;
+	struct i2c_client *client = v4l2_get_subdevdata(&imx519->sd);
+	struct v4l2_fwnode_device_properties props;
+	struct v4l2_ctrl *link_freq;
+	unsigned int i;
+	int ret;
+
+	ctrl_hdlr = &imx519->ctrl_handler;
+	ret = v4l2_ctrl_handler_init(ctrl_hdlr, 16);
+	if (ret)
+		return ret;
+
+	mutex_init(&imx519->mutex);
+	ctrl_hdlr->lock = &imx519->mutex;
+
+	/* By default, PIXEL_RATE is read only */
+	imx519->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx519_ctrl_ops,
+					       V4L2_CID_PIXEL_RATE,
+					       IMX519_PIXEL_RATE,
+					       IMX519_PIXEL_RATE, 1,
+					       IMX519_PIXEL_RATE);
+
+	/* LINK_FREQ is also read only */
+	link_freq =
+		v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx519_ctrl_ops,
+				       V4L2_CID_LINK_FREQ,
+				       ARRAY_SIZE(imx519_link_freq_menu) - 1, 0,
+				       imx519_link_freq_menu);
+	if (link_freq)
+		link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	/*
+	 * Create the controls here, but mode specific limits are setup
+	 * in the imx519_set_framing_limits() call below.
+	 */
+	imx519->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx519_ctrl_ops,
+					   V4L2_CID_VBLANK, 0, 0xffff, 1, 0);
+	imx519->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx519_ctrl_ops,
+					   V4L2_CID_HBLANK, 0, 0xffff, 1, 0);
+
+	/* HBLANK is read-only for now, but does change with mode. */
+	if (imx519->hblank)
+		imx519->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	imx519->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx519_ctrl_ops,
+					     V4L2_CID_EXPOSURE,
+					     IMX519_EXPOSURE_MIN,
+					     IMX519_EXPOSURE_MAX,
+					     IMX519_EXPOSURE_STEP,
+					     IMX519_EXPOSURE_DEFAULT);
+
+	v4l2_ctrl_new_std(ctrl_hdlr, &imx519_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
+			  IMX519_ANA_GAIN_MIN, IMX519_ANA_GAIN_MAX,
+			  IMX519_ANA_GAIN_STEP, IMX519_ANA_GAIN_DEFAULT);
+
+	v4l2_ctrl_new_std(ctrl_hdlr, &imx519_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
+			  IMX519_DGTL_GAIN_MIN, IMX519_DGTL_GAIN_MAX,
+			  IMX519_DGTL_GAIN_STEP, IMX519_DGTL_GAIN_DEFAULT);
+
+	imx519->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx519_ctrl_ops,
+					  V4L2_CID_HFLIP, 0, 1, 1, 0);
+	if (imx519->hflip)
+		imx519->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+	imx519->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx519_ctrl_ops,
+					  V4L2_CID_VFLIP, 0, 1, 1, 0);
+	if (imx519->vflip)
+		imx519->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+	v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx519_ctrl_ops,
+				     V4L2_CID_TEST_PATTERN,
+				     ARRAY_SIZE(imx519_test_pattern_menu) - 1,
+				     0, 0, imx519_test_pattern_menu);
+	for (i = 0; i < 4; i++) {
+		/*
+		 * The assumption is that
+		 * V4L2_CID_TEST_PATTERN_GREENR == V4L2_CID_TEST_PATTERN_RED + 1
+		 * V4L2_CID_TEST_PATTERN_BLUE   == V4L2_CID_TEST_PATTERN_RED + 2
+		 * V4L2_CID_TEST_PATTERN_GREENB == V4L2_CID_TEST_PATTERN_RED + 3
+		 */
+		v4l2_ctrl_new_std(ctrl_hdlr, &imx519_ctrl_ops,
+				  V4L2_CID_TEST_PATTERN_RED + i,
+				  IMX519_TEST_PATTERN_COLOUR_MIN,
+				  IMX519_TEST_PATTERN_COLOUR_MAX,
+				  IMX519_TEST_PATTERN_COLOUR_STEP,
+				  IMX519_TEST_PATTERN_COLOUR_MAX);
+		/* The "Solid color" pattern is white by default */
+	}
+
+	if (ctrl_hdlr->error) {
+		ret = ctrl_hdlr->error;
+		dev_err(&client->dev, "%s control init failed (%d)\n",
+			__func__, ret);
+		goto error;
+	}
+
+	ret = v4l2_fwnode_device_parse(&client->dev, &props);
+	if (ret)
+		goto error;
+
+	ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx519_ctrl_ops,
+					      &props);
+	if (ret)
+		goto error;
+
+	imx519->sd.ctrl_handler = ctrl_hdlr;
+
+	/* Setup exposure and frame/line length limits. */
+	imx519_set_framing_limits(imx519);
+
+	return 0;
+
+error:
+	v4l2_ctrl_handler_free(ctrl_hdlr);
+	mutex_destroy(&imx519->mutex);
+
+	return ret;
+}
+
+static void imx519_free_controls(struct imx519 *imx519)
+{
+	v4l2_ctrl_handler_free(imx519->sd.ctrl_handler);
+	mutex_destroy(&imx519->mutex);
+}
+
+static int imx519_check_hwcfg(struct device *dev)
+{
+	struct fwnode_handle *endpoint;
+	struct v4l2_fwnode_endpoint ep_cfg = {
+		.bus_type = V4L2_MBUS_CSI2_DPHY
+	};
+	int ret = -EINVAL;
+
+	endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
+	if (!endpoint) {
+		dev_err(dev, "endpoint node not found\n");
+		return -EINVAL;
+	}
+
+	if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) {
+		dev_err(dev, "could not parse endpoint\n");
+		goto error_out;
+	}
+
+	/* Check the number of MIPI CSI2 data lanes */
+	if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2) {
+		dev_err(dev, "only 2 data lanes are currently supported\n");
+		goto error_out;
+	}
+
+	/* Check the link frequency set in device tree */
+	if (!ep_cfg.nr_of_link_frequencies) {
+		dev_err(dev, "link-frequency property not found in DT\n");
+		goto error_out;
+	}
+
+	if (ep_cfg.nr_of_link_frequencies != 1 ||
+	    ep_cfg.link_frequencies[0] != IMX519_DEFAULT_LINK_FREQ) {
+		dev_err(dev, "Link frequency not supported: %lld\n",
+			ep_cfg.link_frequencies[0]);
+		goto error_out;
+	}
+
+	ret = 0;
+
+error_out:
+	v4l2_fwnode_endpoint_free(&ep_cfg);
+	fwnode_handle_put(endpoint);
+
+	return ret;
+}
+
+static const struct of_device_id imx519_dt_ids[] = {
+	{ .compatible = "sony,imx519"},
+	{ /* sentinel */ }
+};
+
+static int imx519_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct imx519 *imx519;
+	const struct of_device_id *match;
+	u32 xclk_freq;
+	int ret;
+
+	imx519 = devm_kzalloc(&client->dev, sizeof(*imx519), GFP_KERNEL);
+	if (!imx519)
+		return -ENOMEM;
+
+	v4l2_i2c_subdev_init(&imx519->sd, client, &imx519_subdev_ops);
+
+	match = of_match_device(imx519_dt_ids, dev);
+	if (!match)
+		return -ENODEV;
+
+	/* Check the hardware configuration in device tree */
+	if (imx519_check_hwcfg(dev))
+		return -EINVAL;
+
+	/* Get system clock (xclk) */
+	imx519->xclk = devm_clk_get(dev, NULL);
+	if (IS_ERR(imx519->xclk)) {
+		dev_err(dev, "failed to get xclk\n");
+		return PTR_ERR(imx519->xclk);
+	}
+
+	xclk_freq = clk_get_rate(imx519->xclk);
+	if (xclk_freq != IMX519_XCLK_FREQ) {
+		dev_err(dev, "xclk frequency not supported: %d Hz\n",
+			xclk_freq);
+		return -EINVAL;
+	}
+
+	ret = imx519_get_regulators(imx519);
+	if (ret) {
+		dev_err(dev, "failed to get regulators\n");
+		return ret;
+	}
+
+	/* Request optional enable pin */
+	imx519->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+						     GPIOD_OUT_HIGH);
+
+	/*
+	 * The sensor must be powered for imx519_identify_module()
+	 * to be able to read the CHIP_ID register
+	 */
+	ret = imx519_power_on(dev);
+	if (ret)
+		return ret;
+
+	ret = imx519_identify_module(imx519, IMX519_CHIP_ID);
+	if (ret)
+		goto error_power_off;
+
+	/* Set default mode to max resolution */
+	imx519->mode = &supported_modes_10bit[0];
+	imx519->fmt_code = MEDIA_BUS_FMT_SRGGB10_1X10;
+
+	/* Enable runtime PM and turn off the device */
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+	pm_runtime_idle(dev);
+
+	/* This needs the pm runtime to be registered. */
+	ret = imx519_init_controls(imx519);
+	if (ret)
+		goto error_power_off;
+
+	/* Initialize subdev */
+	imx519->sd.internal_ops = &imx519_internal_ops;
+	imx519->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
+			    V4L2_SUBDEV_FL_HAS_EVENTS;
+	imx519->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+
+	/* Initialize source pads */
+	imx519->pad[IMAGE_PAD].flags = MEDIA_PAD_FL_SOURCE;
+	imx519->pad[METADATA_PAD].flags = MEDIA_PAD_FL_SOURCE;
+
+	ret = media_entity_pads_init(&imx519->sd.entity, NUM_PADS, imx519->pad);
+	if (ret) {
+		dev_err(dev, "failed to init entity pads: %d\n", ret);
+		goto error_handler_free;
+	}
+
+	ret = v4l2_async_register_subdev_sensor(&imx519->sd);
+	if (ret < 0) {
+		dev_err(dev, "failed to register sensor sub-device: %d\n", ret);
+		goto error_media_entity;
+	}
+
+	return 0;
+
+error_media_entity:
+	media_entity_cleanup(&imx519->sd.entity);
+
+error_handler_free:
+	imx519_free_controls(imx519);
+
+error_power_off:
+	pm_runtime_disable(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+	imx519_power_off(&client->dev);
+
+	return ret;
+}
+
+static void imx519_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct imx519 *imx519 = to_imx519(sd);
+
+	v4l2_async_unregister_subdev(sd);
+	media_entity_cleanup(&sd->entity);
+	imx519_free_controls(imx519);
+
+	pm_runtime_disable(&client->dev);
+	if (!pm_runtime_status_suspended(&client->dev))
+		imx519_power_off(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+}
+
+MODULE_DEVICE_TABLE(of, imx519_dt_ids);
+
+static const struct dev_pm_ops imx519_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(imx519_suspend, imx519_resume)
+	SET_RUNTIME_PM_OPS(imx519_power_off, imx519_power_on, NULL)
+};
+
+static struct i2c_driver imx519_i2c_driver = {
+	.driver = {
+		.name = "imx519",
+		.of_match_table	= imx519_dt_ids,
+		.pm = &imx519_pm_ops,
+	},
+	.probe = imx519_probe,
+	.remove = imx519_remove,
+};
+
+module_i2c_driver(imx519_i2c_driver);
+
+MODULE_AUTHOR("Lee Jackson <info@arducam.com>");
+MODULE_DESCRIPTION("Sony IMX519 sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/i2c/imx708.c b/drivers/media/i2c/imx708.c
new file mode 100644
index 000000000000..6ab38e6a30e5
--- /dev/null
+++ b/drivers/media/i2c/imx708.c
@@ -0,0 +1,2134 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A V4L2 driver for Sony IMX708 cameras.
+ * Copyright (C) 2022, Raspberry Pi Ltd
+ *
+ * Based on Sony imx477 camera driver
+ * Copyright (C) 2020 Raspberry Pi Ltd
+ */
+#include <linux/unaligned.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mediabus.h>
+
+/*
+ * Parameter to adjust Quad Bayer re-mosaic broken line correction
+ * strength, used in full-resolution mode only. Set zero to disable.
+ */
+static int qbc_adjust = 2;
+module_param(qbc_adjust, int, 0644);
+MODULE_PARM_DESC(qbc_adjust, "Quad Bayer broken line correction strength [0,2-5]");
+
+#define IMX708_REG_VALUE_08BIT		1
+#define IMX708_REG_VALUE_16BIT		2
+
+/* Chip ID */
+#define IMX708_REG_CHIP_ID		0x0016
+#define IMX708_CHIP_ID			0x0708
+
+#define IMX708_REG_MODE_SELECT		0x0100
+#define IMX708_MODE_STANDBY		0x00
+#define IMX708_MODE_STREAMING		0x01
+
+#define IMX708_REG_ORIENTATION		0x101
+
+#define IMX708_INCLK_FREQ		24000000
+
+/* Default initial pixel rate, will get updated for each mode. */
+#define IMX708_INITIAL_PIXEL_RATE	590000000
+
+/* V_TIMING internal */
+#define IMX708_REG_FRAME_LENGTH		0x0340
+#define IMX708_FRAME_LENGTH_MAX		0xffff
+
+/* Long exposure multiplier */
+#define IMX708_LONG_EXP_SHIFT_MAX	7
+#define IMX708_LONG_EXP_SHIFT_REG	0x3100
+
+/* Exposure control */
+#define IMX708_REG_EXPOSURE		0x0202
+#define IMX708_EXPOSURE_OFFSET		48
+#define IMX708_EXPOSURE_DEFAULT		0x640
+#define IMX708_EXPOSURE_STEP		1
+#define IMX708_EXPOSURE_MIN		1
+#define IMX708_EXPOSURE_MAX		(IMX708_FRAME_LENGTH_MAX - \
+					 IMX708_EXPOSURE_OFFSET)
+
+/* Analog gain control */
+#define IMX708_REG_ANALOG_GAIN		0x0204
+#define IMX708_ANA_GAIN_MIN		112
+#define IMX708_ANA_GAIN_MAX		960
+#define IMX708_ANA_GAIN_STEP		1
+#define IMX708_ANA_GAIN_DEFAULT	   IMX708_ANA_GAIN_MIN
+
+/* Digital gain control */
+#define IMX708_REG_DIGITAL_GAIN		0x020e
+#define IMX708_DGTL_GAIN_MIN		0x0100
+#define IMX708_DGTL_GAIN_MAX		0xffff
+#define IMX708_DGTL_GAIN_DEFAULT	0x0100
+#define IMX708_DGTL_GAIN_STEP		1
+
+/* Colour balance controls */
+#define IMX708_REG_COLOUR_BALANCE_RED   0x0b90
+#define IMX708_REG_COLOUR_BALANCE_BLUE	0x0b92
+#define IMX708_COLOUR_BALANCE_MIN	0x01
+#define IMX708_COLOUR_BALANCE_MAX	0xffff
+#define IMX708_COLOUR_BALANCE_STEP	0x01
+#define IMX708_COLOUR_BALANCE_DEFAULT	0x100
+
+/* Test Pattern Control */
+#define IMX708_REG_TEST_PATTERN		0x0600
+#define IMX708_TEST_PATTERN_DISABLE	0
+#define IMX708_TEST_PATTERN_SOLID_COLOR	1
+#define IMX708_TEST_PATTERN_COLOR_BARS	2
+#define IMX708_TEST_PATTERN_GREY_COLOR	3
+#define IMX708_TEST_PATTERN_PN9		4
+
+/* Test pattern colour components */
+#define IMX708_REG_TEST_PATTERN_R	0x0602
+#define IMX708_REG_TEST_PATTERN_GR	0x0604
+#define IMX708_REG_TEST_PATTERN_B	0x0606
+#define IMX708_REG_TEST_PATTERN_GB	0x0608
+#define IMX708_TEST_PATTERN_COLOUR_MIN	0
+#define IMX708_TEST_PATTERN_COLOUR_MAX	0x0fff
+#define IMX708_TEST_PATTERN_COLOUR_STEP	1
+
+#define IMX708_REG_BASE_SPC_GAINS_L	0x7b10
+#define IMX708_REG_BASE_SPC_GAINS_R	0x7c00
+
+/* HDR exposure ratio (long:med == med:short) */
+#define IMX708_HDR_EXPOSURE_RATIO       4
+#define IMX708_REG_MID_EXPOSURE		0x3116
+#define IMX708_REG_SHT_EXPOSURE		0x0224
+#define IMX708_REG_MID_ANALOG_GAIN	0x3118
+#define IMX708_REG_SHT_ANALOG_GAIN	0x0216
+
+/* QBC Re-mosaic broken line correction registers */
+#define IMX708_LPF_INTENSITY_EN		0xC428
+#define IMX708_LPF_INTENSITY_ENABLED	0x00
+#define IMX708_LPF_INTENSITY_DISABLED	0x01
+#define IMX708_LPF_INTENSITY		0xC429
+
+/*
+ * Metadata buffer holds a variety of data, all sent with the same VC/DT (0x12).
+ * It comprises two scanlines (of up to 5760 bytes each, for 4608 pixels)
+ * of embedded data, one line of PDAF data, and two lines of AE-HIST data
+ * (AE histograms are valid for HDR mode and empty in non-HDR modes).
+ */
+#define IMX708_EMBEDDED_LINE_WIDTH (5 * 5760)
+#define IMX708_NUM_EMBEDDED_LINES 1
+
+enum pad_types {
+	IMAGE_PAD,
+	METADATA_PAD,
+	NUM_PADS
+};
+
+/* IMX708 native and active pixel array size. */
+#define IMX708_NATIVE_WIDTH		4640U
+#define IMX708_NATIVE_HEIGHT		2658U
+#define IMX708_PIXEL_ARRAY_LEFT		16U
+#define IMX708_PIXEL_ARRAY_TOP		24U
+#define IMX708_PIXEL_ARRAY_WIDTH	4608U
+#define IMX708_PIXEL_ARRAY_HEIGHT	2592U
+
+struct imx708_reg {
+	u16 address;
+	u8 val;
+};
+
+struct imx708_reg_list {
+	unsigned int num_of_regs;
+	const struct imx708_reg *regs;
+};
+
+/* Mode : resolution and related config&values */
+struct imx708_mode {
+	/* Frame width */
+	unsigned int width;
+
+	/* Frame height */
+	unsigned int height;
+
+	/* H-timing in pixels */
+	unsigned int line_length_pix;
+
+	/* Analog crop rectangle. */
+	struct v4l2_rect crop;
+
+	/* Highest possible framerate. */
+	unsigned int vblank_min;
+
+	/* Default framerate. */
+	unsigned int vblank_default;
+
+	/* Default register values */
+	struct imx708_reg_list reg_list;
+
+	/* Not all modes have the same pixel rate. */
+	u64 pixel_rate;
+
+	/* Not all modes have the same minimum exposure. */
+	u32 exposure_lines_min;
+
+	/* Not all modes have the same exposure lines step. */
+	u32 exposure_lines_step;
+
+	/* HDR flag, used for checking if the current mode is HDR */
+	bool hdr;
+
+	/* Quad Bayer Re-mosaic flag */
+	bool remosaic;
+};
+
+/* Default PDAF pixel correction gains */
+static const u8 pdaf_gains[2][9] = {
+	{ 0x4c, 0x4c, 0x4c, 0x46, 0x3e, 0x39, 0x36, 0x36, 0x36 },
+	{ 0x36, 0x36, 0x36, 0x39, 0x3e, 0x46, 0x4c, 0x4c, 0x4c }
+};
+
+/* Link frequency setup */
+enum {
+	IMX708_LINK_FREQ_450MHZ,
+	IMX708_LINK_FREQ_447MHZ,
+	IMX708_LINK_FREQ_453MHZ,
+};
+
+static const s64 link_freqs[] = {
+	[IMX708_LINK_FREQ_450MHZ] = 450000000,
+	[IMX708_LINK_FREQ_447MHZ] = 447000000,
+	[IMX708_LINK_FREQ_453MHZ] = 453000000,
+};
+
+/* 450MHz is the nominal "default" link frequency */
+static const struct imx708_reg link_450Mhz_regs[] = {
+	{0x030E, 0x01},
+	{0x030F, 0x2c},
+};
+
+static const struct imx708_reg link_447Mhz_regs[] = {
+	{0x030E, 0x01},
+	{0x030F, 0x2a},
+};
+
+static const struct imx708_reg link_453Mhz_regs[] = {
+	{0x030E, 0x01},
+	{0x030F, 0x2e},
+};
+
+static const struct imx708_reg_list link_freq_regs[] = {
+	[IMX708_LINK_FREQ_450MHZ] = {
+		.regs = link_450Mhz_regs,
+		.num_of_regs = ARRAY_SIZE(link_450Mhz_regs)
+	},
+	[IMX708_LINK_FREQ_447MHZ] = {
+		.regs = link_447Mhz_regs,
+		.num_of_regs = ARRAY_SIZE(link_447Mhz_regs)
+	},
+	[IMX708_LINK_FREQ_453MHZ] = {
+		.regs = link_453Mhz_regs,
+		.num_of_regs = ARRAY_SIZE(link_453Mhz_regs)
+	},
+};
+
+static const struct imx708_reg mode_common_regs[] = {
+	{0x0100, 0x00},
+	{0x0136, 0x18},
+	{0x0137, 0x00},
+	{0x33F0, 0x02},
+	{0x33F1, 0x05},
+	{0x3062, 0x00},
+	{0x3063, 0x12},
+	{0x3068, 0x00},
+	{0x3069, 0x12},
+	{0x306A, 0x00},
+	{0x306B, 0x30},
+	{0x3076, 0x00},
+	{0x3077, 0x30},
+	{0x3078, 0x00},
+	{0x3079, 0x30},
+	{0x5E54, 0x0C},
+	{0x6E44, 0x00},
+	{0xB0B6, 0x01},
+	{0xE829, 0x00},
+	{0xF001, 0x08},
+	{0xF003, 0x08},
+	{0xF00D, 0x10},
+	{0xF00F, 0x10},
+	{0xF031, 0x08},
+	{0xF033, 0x08},
+	{0xF03D, 0x10},
+	{0xF03F, 0x10},
+	{0x0112, 0x0A},
+	{0x0113, 0x0A},
+	{0x0114, 0x01},
+	{0x0B8E, 0x01},
+	{0x0B8F, 0x00},
+	{0x0B94, 0x01},
+	{0x0B95, 0x00},
+	{0x3400, 0x01},
+	{0x3478, 0x01},
+	{0x3479, 0x1c},
+	{0x3091, 0x01},
+	{0x3092, 0x00},
+	{0x3419, 0x00},
+	{0xBCF1, 0x02},
+	{0x3094, 0x01},
+	{0x3095, 0x01},
+	{0x3362, 0x00},
+	{0x3363, 0x00},
+	{0x3364, 0x00},
+	{0x3365, 0x00},
+	{0x0138, 0x01},
+};
+
+/* 10-bit. */
+static const struct imx708_reg mode_4608x2592_regs[] = {
+	{0x0342, 0x3D},
+	{0x0343, 0x20},
+	{0x0340, 0x0A},
+	{0x0341, 0x59},
+	{0x0344, 0x00},
+	{0x0345, 0x00},
+	{0x0346, 0x00},
+	{0x0347, 0x00},
+	{0x0348, 0x11},
+	{0x0349, 0xFF},
+	{0x034A, 0X0A},
+	{0x034B, 0x1F},
+	{0x0220, 0x62},
+	{0x0222, 0x01},
+	{0x0900, 0x00},
+	{0x0901, 0x11},
+	{0x0902, 0x0A},
+	{0x3200, 0x01},
+	{0x3201, 0x01},
+	{0x32D5, 0x01},
+	{0x32D6, 0x00},
+	{0x32DB, 0x01},
+	{0x32DF, 0x00},
+	{0x350C, 0x00},
+	{0x350D, 0x00},
+	{0x0408, 0x00},
+	{0x0409, 0x00},
+	{0x040A, 0x00},
+	{0x040B, 0x00},
+	{0x040C, 0x12},
+	{0x040D, 0x00},
+	{0x040E, 0x0A},
+	{0x040F, 0x20},
+	{0x034C, 0x12},
+	{0x034D, 0x00},
+	{0x034E, 0x0A},
+	{0x034F, 0x20},
+	{0x0301, 0x05},
+	{0x0303, 0x02},
+	{0x0305, 0x02},
+	{0x0306, 0x00},
+	{0x0307, 0x7C},
+	{0x030B, 0x02},
+	{0x030D, 0x04},
+	{0x0310, 0x01},
+	{0x3CA0, 0x00},
+	{0x3CA1, 0x64},
+	{0x3CA4, 0x00},
+	{0x3CA5, 0x00},
+	{0x3CA6, 0x00},
+	{0x3CA7, 0x00},
+	{0x3CAA, 0x00},
+	{0x3CAB, 0x00},
+	{0x3CB8, 0x00},
+	{0x3CB9, 0x08},
+	{0x3CBA, 0x00},
+	{0x3CBB, 0x00},
+	{0x3CBC, 0x00},
+	{0x3CBD, 0x3C},
+	{0x3CBE, 0x00},
+	{0x3CBF, 0x00},
+	{0x0202, 0x0A},
+	{0x0203, 0x29},
+	{0x0224, 0x01},
+	{0x0225, 0xF4},
+	{0x3116, 0x01},
+	{0x3117, 0xF4},
+	{0x0204, 0x00},
+	{0x0205, 0x00},
+	{0x0216, 0x00},
+	{0x0217, 0x00},
+	{0x0218, 0x01},
+	{0x0219, 0x00},
+	{0x020E, 0x01},
+	{0x020F, 0x00},
+	{0x3118, 0x00},
+	{0x3119, 0x00},
+	{0x311A, 0x01},
+	{0x311B, 0x00},
+	{0x341a, 0x00},
+	{0x341b, 0x00},
+	{0x341c, 0x00},
+	{0x341d, 0x00},
+	{0x341e, 0x01},
+	{0x341f, 0x20},
+	{0x3420, 0x00},
+	{0x3421, 0xd8},
+	{0x3366, 0x00},
+	{0x3367, 0x00},
+	{0x3368, 0x00},
+	{0x3369, 0x00},
+};
+
+static const struct imx708_reg mode_2x2binned_regs[] = {
+	{0x0342, 0x1E},
+	{0x0343, 0x90},
+	{0x0340, 0x05},
+	{0x0341, 0x38},
+	{0x0344, 0x00},
+	{0x0345, 0x00},
+	{0x0346, 0x00},
+	{0x0347, 0x00},
+	{0x0348, 0x11},
+	{0x0349, 0xFF},
+	{0x034A, 0X0A},
+	{0x034B, 0x1F},
+	{0x0220, 0x62},
+	{0x0222, 0x01},
+	{0x0900, 0x01},
+	{0x0901, 0x22},
+	{0x0902, 0x08},
+	{0x3200, 0x41},
+	{0x3201, 0x41},
+	{0x32D5, 0x00},
+	{0x32D6, 0x00},
+	{0x32DB, 0x01},
+	{0x32DF, 0x00},
+	{0x350C, 0x00},
+	{0x350D, 0x00},
+	{0x0408, 0x00},
+	{0x0409, 0x00},
+	{0x040A, 0x00},
+	{0x040B, 0x00},
+	{0x040C, 0x09},
+	{0x040D, 0x00},
+	{0x040E, 0x05},
+	{0x040F, 0x10},
+	{0x034C, 0x09},
+	{0x034D, 0x00},
+	{0x034E, 0x05},
+	{0x034F, 0x10},
+	{0x0301, 0x05},
+	{0x0303, 0x02},
+	{0x0305, 0x02},
+	{0x0306, 0x00},
+	{0x0307, 0x7A},
+	{0x030B, 0x02},
+	{0x030D, 0x04},
+	{0x0310, 0x01},
+	{0x3CA0, 0x00},
+	{0x3CA1, 0x3C},
+	{0x3CA4, 0x00},
+	{0x3CA5, 0x3C},
+	{0x3CA6, 0x00},
+	{0x3CA7, 0x00},
+	{0x3CAA, 0x00},
+	{0x3CAB, 0x00},
+	{0x3CB8, 0x00},
+	{0x3CB9, 0x1C},
+	{0x3CBA, 0x00},
+	{0x3CBB, 0x08},
+	{0x3CBC, 0x00},
+	{0x3CBD, 0x1E},
+	{0x3CBE, 0x00},
+	{0x3CBF, 0x0A},
+	{0x0202, 0x05},
+	{0x0203, 0x08},
+	{0x0224, 0x01},
+	{0x0225, 0xF4},
+	{0x3116, 0x01},
+	{0x3117, 0xF4},
+	{0x0204, 0x00},
+	{0x0205, 0x70},
+	{0x0216, 0x00},
+	{0x0217, 0x70},
+	{0x0218, 0x01},
+	{0x0219, 0x00},
+	{0x020E, 0x01},
+	{0x020F, 0x00},
+	{0x3118, 0x00},
+	{0x3119, 0x70},
+	{0x311A, 0x01},
+	{0x311B, 0x00},
+	{0x341a, 0x00},
+	{0x341b, 0x00},
+	{0x341c, 0x00},
+	{0x341d, 0x00},
+	{0x341e, 0x00},
+	{0x341f, 0x90},
+	{0x3420, 0x00},
+	{0x3421, 0x6c},
+	{0x3366, 0x00},
+	{0x3367, 0x00},
+	{0x3368, 0x00},
+	{0x3369, 0x00},
+};
+
+static const struct imx708_reg mode_2x2binned_720p_regs[] = {
+	{0x0342, 0x14},
+	{0x0343, 0x60},
+	{0x0340, 0x04},
+	{0x0341, 0xB6},
+	{0x0344, 0x03},
+	{0x0345, 0x00},
+	{0x0346, 0x01},
+	{0x0347, 0xB0},
+	{0x0348, 0x0E},
+	{0x0349, 0xFF},
+	{0x034A, 0x08},
+	{0x034B, 0x6F},
+	{0x0220, 0x62},
+	{0x0222, 0x01},
+	{0x0900, 0x01},
+	{0x0901, 0x22},
+	{0x0902, 0x08},
+	{0x3200, 0x41},
+	{0x3201, 0x41},
+	{0x32D5, 0x00},
+	{0x32D6, 0x00},
+	{0x32DB, 0x01},
+	{0x32DF, 0x01},
+	{0x350C, 0x00},
+	{0x350D, 0x00},
+	{0x0408, 0x00},
+	{0x0409, 0x00},
+	{0x040A, 0x00},
+	{0x040B, 0x00},
+	{0x040C, 0x06},
+	{0x040D, 0x00},
+	{0x040E, 0x03},
+	{0x040F, 0x60},
+	{0x034C, 0x06},
+	{0x034D, 0x00},
+	{0x034E, 0x03},
+	{0x034F, 0x60},
+	{0x0301, 0x05},
+	{0x0303, 0x02},
+	{0x0305, 0x02},
+	{0x0306, 0x00},
+	{0x0307, 0x76},
+	{0x030B, 0x02},
+	{0x030D, 0x04},
+	{0x0310, 0x01},
+	{0x3CA0, 0x00},
+	{0x3CA1, 0x3C},
+	{0x3CA4, 0x01},
+	{0x3CA5, 0x5E},
+	{0x3CA6, 0x00},
+	{0x3CA7, 0x00},
+	{0x3CAA, 0x00},
+	{0x3CAB, 0x00},
+	{0x3CB8, 0x00},
+	{0x3CB9, 0x0C},
+	{0x3CBA, 0x00},
+	{0x3CBB, 0x04},
+	{0x3CBC, 0x00},
+	{0x3CBD, 0x1E},
+	{0x3CBE, 0x00},
+	{0x3CBF, 0x05},
+	{0x0202, 0x04},
+	{0x0203, 0x86},
+	{0x0224, 0x01},
+	{0x0225, 0xF4},
+	{0x3116, 0x01},
+	{0x3117, 0xF4},
+	{0x0204, 0x00},
+	{0x0205, 0x70},
+	{0x0216, 0x00},
+	{0x0217, 0x70},
+	{0x0218, 0x01},
+	{0x0219, 0x00},
+	{0x020E, 0x01},
+	{0x020F, 0x00},
+	{0x3118, 0x00},
+	{0x3119, 0x70},
+	{0x311A, 0x01},
+	{0x311B, 0x00},
+	{0x341a, 0x00},
+	{0x341b, 0x00},
+	{0x341c, 0x00},
+	{0x341d, 0x00},
+	{0x341e, 0x00},
+	{0x341f, 0x60},
+	{0x3420, 0x00},
+	{0x3421, 0x48},
+	{0x3366, 0x00},
+	{0x3367, 0x00},
+	{0x3368, 0x00},
+	{0x3369, 0x00},
+};
+
+static const struct imx708_reg mode_hdr_regs[] = {
+	{0x0342, 0x14},
+	{0x0343, 0x60},
+	{0x0340, 0x0A},
+	{0x0341, 0x5B},
+	{0x0344, 0x00},
+	{0x0345, 0x00},
+	{0x0346, 0x00},
+	{0x0347, 0x00},
+	{0x0348, 0x11},
+	{0x0349, 0xFF},
+	{0x034A, 0X0A},
+	{0x034B, 0x1F},
+	{0x0220, 0x01},
+	{0x0222, IMX708_HDR_EXPOSURE_RATIO},
+	{0x0900, 0x00},
+	{0x0901, 0x11},
+	{0x0902, 0x0A},
+	{0x3200, 0x01},
+	{0x3201, 0x01},
+	{0x32D5, 0x00},
+	{0x32D6, 0x00},
+	{0x32DB, 0x01},
+	{0x32DF, 0x00},
+	{0x350C, 0x00},
+	{0x350D, 0x00},
+	{0x0408, 0x00},
+	{0x0409, 0x00},
+	{0x040A, 0x00},
+	{0x040B, 0x00},
+	{0x040C, 0x09},
+	{0x040D, 0x00},
+	{0x040E, 0x05},
+	{0x040F, 0x10},
+	{0x034C, 0x09},
+	{0x034D, 0x00},
+	{0x034E, 0x05},
+	{0x034F, 0x10},
+	{0x0301, 0x05},
+	{0x0303, 0x02},
+	{0x0305, 0x02},
+	{0x0306, 0x00},
+	{0x0307, 0xA2},
+	{0x030B, 0x02},
+	{0x030D, 0x04},
+	{0x0310, 0x01},
+	{0x3CA0, 0x00},
+	{0x3CA1, 0x00},
+	{0x3CA4, 0x00},
+	{0x3CA5, 0x00},
+	{0x3CA6, 0x00},
+	{0x3CA7, 0x28},
+	{0x3CAA, 0x00},
+	{0x3CAB, 0x00},
+	{0x3CB8, 0x00},
+	{0x3CB9, 0x30},
+	{0x3CBA, 0x00},
+	{0x3CBB, 0x00},
+	{0x3CBC, 0x00},
+	{0x3CBD, 0x32},
+	{0x3CBE, 0x00},
+	{0x3CBF, 0x00},
+	{0x0202, 0x0A},
+	{0x0203, 0x2B},
+	{0x0224, 0x0A},
+	{0x0225, 0x2B},
+	{0x3116, 0x0A},
+	{0x3117, 0x2B},
+	{0x0204, 0x00},
+	{0x0205, 0x00},
+	{0x0216, 0x00},
+	{0x0217, 0x00},
+	{0x0218, 0x01},
+	{0x0219, 0x00},
+	{0x020E, 0x01},
+	{0x020F, 0x00},
+	{0x3118, 0x00},
+	{0x3119, 0x00},
+	{0x311A, 0x01},
+	{0x311B, 0x00},
+	{0x341a, 0x00},
+	{0x341b, 0x00},
+	{0x341c, 0x00},
+	{0x341d, 0x00},
+	{0x341e, 0x00},
+	{0x341f, 0x90},
+	{0x3420, 0x00},
+	{0x3421, 0x6c},
+	{0x3360, 0x01},
+	{0x3361, 0x01},
+	{0x3366, 0x09},
+	{0x3367, 0x00},
+	{0x3368, 0x05},
+	{0x3369, 0x10},
+};
+
+/* Mode configs. Keep separate lists for when HDR is enabled or not. */
+static const struct imx708_mode supported_modes_10bit_no_hdr[] = {
+	{
+		/* Full resolution. */
+		.width = 4608,
+		.height = 2592,
+		.line_length_pix = 0x3d20,
+		.crop = {
+			.left = IMX708_PIXEL_ARRAY_LEFT,
+			.top = IMX708_PIXEL_ARRAY_TOP,
+			.width = 4608,
+			.height = 2592,
+		},
+		.vblank_min = 58,
+		.vblank_default = 58,
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_4608x2592_regs),
+			.regs = mode_4608x2592_regs,
+		},
+		.pixel_rate = 595200000,
+		.exposure_lines_min = 8,
+		.exposure_lines_step = 1,
+		.hdr = false,
+		.remosaic = true
+	},
+	{
+		/* regular 2x2 binned. */
+		.width = 2304,
+		.height = 1296,
+		.line_length_pix = 0x1e90,
+		.crop = {
+			.left = IMX708_PIXEL_ARRAY_LEFT,
+			.top = IMX708_PIXEL_ARRAY_TOP,
+			.width = 4608,
+			.height = 2592,
+		},
+		.vblank_min = 40,
+		.vblank_default = 1198,
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_2x2binned_regs),
+			.regs = mode_2x2binned_regs,
+		},
+		.pixel_rate = 585600000,
+		.exposure_lines_min = 4,
+		.exposure_lines_step = 2,
+		.hdr = false,
+		.remosaic = false
+	},
+	{
+		/* 2x2 binned and cropped for 720p. */
+		.width = 1536,
+		.height = 864,
+		.line_length_pix = 0x1460,
+		.crop = {
+			.left = IMX708_PIXEL_ARRAY_LEFT + 768,
+			.top = IMX708_PIXEL_ARRAY_TOP + 432,
+			.width = 3072,
+			.height = 1728,
+		},
+		.vblank_min = 40,
+		.vblank_default = 2755,
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_2x2binned_720p_regs),
+			.regs = mode_2x2binned_720p_regs,
+		},
+		.pixel_rate = 566400000,
+		.exposure_lines_min = 4,
+		.exposure_lines_step = 2,
+		.hdr = false,
+		.remosaic = false
+	},
+};
+
+static const struct imx708_mode supported_modes_10bit_hdr[] = {
+	{
+		/* There's only one HDR mode, which is 2x2 downscaled */
+		.width = 2304,
+		.height = 1296,
+		.line_length_pix = 0x1460,
+		.crop = {
+			.left = IMX708_PIXEL_ARRAY_LEFT,
+			.top = IMX708_PIXEL_ARRAY_TOP,
+			.width = 4608,
+			.height = 2592,
+		},
+		.vblank_min = 3673,
+		.vblank_default = 3673,
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(mode_hdr_regs),
+			.regs = mode_hdr_regs,
+		},
+		.pixel_rate = 777600000,
+		.exposure_lines_min = 8 * IMX708_HDR_EXPOSURE_RATIO * IMX708_HDR_EXPOSURE_RATIO,
+		.exposure_lines_step = 2 * IMX708_HDR_EXPOSURE_RATIO * IMX708_HDR_EXPOSURE_RATIO,
+		.hdr = true,
+		.remosaic = false
+	}
+};
+
+/*
+ * The supported formats.
+ * This table MUST contain 4 entries per format, to cover the various flip
+ * combinations in the order
+ * - no flip
+ * - h flip
+ * - v flip
+ * - h&v flips
+ */
+static const u32 codes[] = {
+	/* 10-bit modes. */
+	MEDIA_BUS_FMT_SRGGB10_1X10,
+	MEDIA_BUS_FMT_SGRBG10_1X10,
+	MEDIA_BUS_FMT_SGBRG10_1X10,
+	MEDIA_BUS_FMT_SBGGR10_1X10,
+};
+
+static const char * const imx708_test_pattern_menu[] = {
+	"Disabled",
+	"Color Bars",
+	"Solid Color",
+	"Grey Color Bars",
+	"PN9"
+};
+
+static const int imx708_test_pattern_val[] = {
+	IMX708_TEST_PATTERN_DISABLE,
+	IMX708_TEST_PATTERN_COLOR_BARS,
+	IMX708_TEST_PATTERN_SOLID_COLOR,
+	IMX708_TEST_PATTERN_GREY_COLOR,
+	IMX708_TEST_PATTERN_PN9,
+};
+
+/* regulator supplies */
+static const char * const imx708_supply_name[] = {
+	/* Supplies can be enabled in any order */
+	"vana1",  /* Analog1 (2.8V) supply */
+	"vana2",  /* Analog2 (1.8V) supply */
+	"vdig",  /* Digital Core (1.1V) supply */
+	"vddl",  /* IF (1.8V) supply */
+};
+
+/*
+ * Initialisation delay between XCLR low->high and the moment when the sensor
+ * can start capture (i.e. can leave software standby), given by T7 in the
+ * datasheet is 8ms.  This does include I2C setup time as well.
+ *
+ * Note, that delay between XCLR low->high and reading the CCI ID register (T6
+ * in the datasheet) is much smaller - 600us.
+ */
+#define IMX708_XCLR_MIN_DELAY_US	8000
+#define IMX708_XCLR_DELAY_RANGE_US	1000
+
+struct imx708 {
+	struct v4l2_subdev sd;
+	struct media_pad pad[NUM_PADS];
+
+	struct v4l2_mbus_framefmt fmt;
+
+	struct clk *inclk;
+	u32 inclk_freq;
+
+	struct gpio_desc *reset_gpio;
+	struct regulator_bulk_data supplies[ARRAY_SIZE(imx708_supply_name)];
+
+	struct v4l2_ctrl_handler ctrl_handler;
+	/* V4L2 Controls */
+	struct v4l2_ctrl *pixel_rate;
+	struct v4l2_ctrl *exposure;
+	struct v4l2_ctrl *vblank;
+	struct v4l2_ctrl *hblank;
+	struct v4l2_ctrl *hdr_mode;
+	struct v4l2_ctrl *link_freq;
+	struct {
+		struct v4l2_ctrl *hflip;
+		struct v4l2_ctrl *vflip;
+	};
+
+	/* Current mode */
+	const struct imx708_mode *mode;
+
+	/*
+	 * Mutex for serialized access:
+	 * Protect sensor module set pad format and start/stop streaming safely.
+	 */
+	struct mutex mutex;
+
+	/* Streaming on/off */
+	bool streaming;
+
+	/* Rewrite common registers on stream on? */
+	bool common_regs_written;
+
+	/* Current long exposure factor in use. Set through V4L2_CID_VBLANK */
+	unsigned int long_exp_shift;
+
+	unsigned int link_freq_idx;
+};
+
+static inline struct imx708 *to_imx708(struct v4l2_subdev *_sd)
+{
+	return container_of(_sd, struct imx708, sd);
+}
+
+static inline void get_mode_table(unsigned int code,
+				  const struct imx708_mode **mode_list,
+				  unsigned int *num_modes,
+				  bool hdr_enable)
+{
+	switch (code) {
+	/* 10-bit */
+	case MEDIA_BUS_FMT_SRGGB10_1X10:
+	case MEDIA_BUS_FMT_SGRBG10_1X10:
+	case MEDIA_BUS_FMT_SGBRG10_1X10:
+	case MEDIA_BUS_FMT_SBGGR10_1X10:
+		if (hdr_enable) {
+			*mode_list = supported_modes_10bit_hdr;
+			*num_modes = ARRAY_SIZE(supported_modes_10bit_hdr);
+		} else {
+			*mode_list = supported_modes_10bit_no_hdr;
+			*num_modes = ARRAY_SIZE(supported_modes_10bit_no_hdr);
+		}
+		break;
+	default:
+		*mode_list = NULL;
+		*num_modes = 0;
+	}
+}
+
+/* Read registers up to 2 at a time */
+static int imx708_read_reg(struct imx708 *imx708, u16 reg, u32 len, u32 *val)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd);
+	struct i2c_msg msgs[2];
+	u8 addr_buf[2] = { reg >> 8, reg & 0xff };
+	u8 data_buf[4] = { 0, };
+	int ret;
+
+	if (len > 4)
+		return -EINVAL;
+
+	/* Write register address */
+	msgs[0].addr = client->addr;
+	msgs[0].flags = 0;
+	msgs[0].len = ARRAY_SIZE(addr_buf);
+	msgs[0].buf = addr_buf;
+
+	/* Read data from register */
+	msgs[1].addr = client->addr;
+	msgs[1].flags = I2C_M_RD;
+	msgs[1].len = len;
+	msgs[1].buf = &data_buf[4 - len];
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret != ARRAY_SIZE(msgs))
+		return -EIO;
+
+	*val = get_unaligned_be32(data_buf);
+
+	return 0;
+}
+
+/* Write registers up to 2 at a time */
+static int imx708_write_reg(struct imx708 *imx708, u16 reg, u32 len, u32 val)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd);
+	u8 buf[6];
+
+	if (len > 4)
+		return -EINVAL;
+
+	put_unaligned_be16(reg, buf);
+	put_unaligned_be32(val << (8 * (4 - len)), buf + 2);
+	if (i2c_master_send(client, buf, len + 2) != len + 2)
+		return -EIO;
+
+	return 0;
+}
+
+/* Write a list of registers */
+static int imx708_write_regs(struct imx708 *imx708,
+			     const struct imx708_reg *regs, u32 len)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd);
+	unsigned int i;
+
+	for (i = 0; i < len; i++) {
+		int ret;
+
+		ret = imx708_write_reg(imx708, regs[i].address, 1, regs[i].val);
+		if (ret) {
+			dev_err_ratelimited(&client->dev,
+					    "Failed to write reg 0x%4.4x. error = %d\n",
+					    regs[i].address, ret);
+
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+/* Get bayer order based on flip setting. */
+static u32 imx708_get_format_code(struct imx708 *imx708)
+{
+	unsigned int i;
+
+	lockdep_assert_held(&imx708->mutex);
+
+	i = (imx708->vflip->val ? 2 : 0) |
+	    (imx708->hflip->val ? 1 : 0);
+
+	return codes[i];
+}
+
+static void imx708_set_default_format(struct imx708 *imx708)
+{
+	struct v4l2_mbus_framefmt *fmt = &imx708->fmt;
+
+	/* Set default mode to max resolution */
+	imx708->mode = &supported_modes_10bit_no_hdr[0];
+
+	/* fmt->code not set as it will always be computed based on flips */
+	fmt->colorspace = V4L2_COLORSPACE_RAW;
+	fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace);
+	fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true,
+							  fmt->colorspace,
+							  fmt->ycbcr_enc);
+	fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace);
+	fmt->width = imx708->mode->width;
+	fmt->height = imx708->mode->height;
+	fmt->field = V4L2_FIELD_NONE;
+}
+
+static int imx708_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+	struct imx708 *imx708 = to_imx708(sd);
+	struct v4l2_mbus_framefmt *try_fmt_img =
+		v4l2_subdev_state_get_format(fh->state, IMAGE_PAD);
+	struct v4l2_mbus_framefmt *try_fmt_meta =
+		v4l2_subdev_state_get_format(fh->state, METADATA_PAD);
+	struct v4l2_rect *try_crop;
+
+	mutex_lock(&imx708->mutex);
+
+	/* Initialize try_fmt for the image pad */
+	if (imx708->hdr_mode->val) {
+		try_fmt_img->width = supported_modes_10bit_hdr[0].width;
+		try_fmt_img->height = supported_modes_10bit_hdr[0].height;
+	} else {
+		try_fmt_img->width = supported_modes_10bit_no_hdr[0].width;
+		try_fmt_img->height = supported_modes_10bit_no_hdr[0].height;
+	}
+	try_fmt_img->code = imx708_get_format_code(imx708);
+	try_fmt_img->field = V4L2_FIELD_NONE;
+
+	/* Initialize try_fmt for the embedded metadata pad */
+	try_fmt_meta->width = IMX708_EMBEDDED_LINE_WIDTH;
+	try_fmt_meta->height = IMX708_NUM_EMBEDDED_LINES;
+	try_fmt_meta->code = MEDIA_BUS_FMT_SENSOR_DATA;
+	try_fmt_meta->field = V4L2_FIELD_NONE;
+
+	/* Initialize try_crop */
+	try_crop = v4l2_subdev_state_get_crop(fh->state, IMAGE_PAD);
+	try_crop->left = IMX708_PIXEL_ARRAY_LEFT;
+	try_crop->top = IMX708_PIXEL_ARRAY_TOP;
+	try_crop->width = IMX708_PIXEL_ARRAY_WIDTH;
+	try_crop->height = IMX708_PIXEL_ARRAY_HEIGHT;
+
+	mutex_unlock(&imx708->mutex);
+
+	return 0;
+}
+
+static int imx708_set_exposure(struct imx708 *imx708, unsigned int val)
+{
+	val = max(val, imx708->mode->exposure_lines_min);
+	val -= val % imx708->mode->exposure_lines_step;
+
+	/*
+	 * In HDR mode this will set the longest exposure. The sensor
+	 * will automatically divide the medium and short ones by 4,16.
+	 */
+	return imx708_write_reg(imx708, IMX708_REG_EXPOSURE,
+				IMX708_REG_VALUE_16BIT,
+				val >> imx708->long_exp_shift);
+}
+
+static void imx708_adjust_exposure_range(struct imx708 *imx708,
+					 struct v4l2_ctrl *ctrl)
+{
+	int exposure_max, exposure_def;
+
+	/* Honour the VBLANK limits when setting exposure. */
+	exposure_max = imx708->mode->height + imx708->vblank->val -
+		IMX708_EXPOSURE_OFFSET;
+	exposure_def = min(exposure_max, imx708->exposure->val);
+	__v4l2_ctrl_modify_range(imx708->exposure, imx708->exposure->minimum,
+				 exposure_max, imx708->exposure->step,
+				 exposure_def);
+}
+
+static int imx708_set_analogue_gain(struct imx708 *imx708, unsigned int val)
+{
+	int ret;
+
+	/*
+	 * In HDR mode this will set the gain for the longest exposure,
+	 * and by default the sensor uses the same gain for all of them.
+	 */
+	ret = imx708_write_reg(imx708, IMX708_REG_ANALOG_GAIN,
+			       IMX708_REG_VALUE_16BIT, val);
+
+	return ret;
+}
+
+static int imx708_set_frame_length(struct imx708 *imx708, unsigned int val)
+{
+	int ret;
+
+	imx708->long_exp_shift = 0;
+
+	while (val > IMX708_FRAME_LENGTH_MAX) {
+		imx708->long_exp_shift++;
+		val >>= 1;
+	}
+
+	ret = imx708_write_reg(imx708, IMX708_REG_FRAME_LENGTH,
+			       IMX708_REG_VALUE_16BIT, val);
+	if (ret)
+		return ret;
+
+	return imx708_write_reg(imx708, IMX708_LONG_EXP_SHIFT_REG,
+				IMX708_REG_VALUE_08BIT, imx708->long_exp_shift);
+}
+
+static void imx708_set_framing_limits(struct imx708 *imx708)
+{
+	const struct imx708_mode *mode = imx708->mode;
+	unsigned int hblank;
+
+	__v4l2_ctrl_modify_range(imx708->pixel_rate,
+				 mode->pixel_rate, mode->pixel_rate,
+				 1, mode->pixel_rate);
+
+	/* Update limits and set FPS to default */
+	__v4l2_ctrl_modify_range(imx708->vblank, mode->vblank_min,
+				 ((1 << IMX708_LONG_EXP_SHIFT_MAX) *
+					IMX708_FRAME_LENGTH_MAX) - mode->height,
+				 1, mode->vblank_default);
+
+	/*
+	 * Currently PPL is fixed to the mode specified value, so hblank
+	 * depends on mode->width only, and is not changeable in any
+	 * way other than changing the mode.
+	 */
+	hblank = mode->line_length_pix - mode->width;
+	__v4l2_ctrl_modify_range(imx708->hblank, hblank, hblank, 1, hblank);
+}
+
+static int imx708_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct imx708 *imx708 =
+		container_of(ctrl->handler, struct imx708, ctrl_handler);
+	struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd);
+	const struct imx708_mode *mode_list;
+	unsigned int code, num_modes;
+	int ret = 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_VBLANK:
+		/*
+		 * The VBLANK control may change the limits of usable exposure,
+		 * so check and adjust if necessary.
+		 */
+		imx708_adjust_exposure_range(imx708, ctrl);
+		break;
+
+	case V4L2_CID_WIDE_DYNAMIC_RANGE:
+		/*
+		 * The WIDE_DYNAMIC_RANGE control can also be applied immediately
+		 * as it doesn't set any registers. Don't do anything if the mode
+		 * already matches.
+		 */
+		if (imx708->mode && imx708->mode->hdr != ctrl->val) {
+			code = imx708_get_format_code(imx708);
+			get_mode_table(code, &mode_list, &num_modes, ctrl->val);
+			imx708->mode = v4l2_find_nearest_size(mode_list,
+							      num_modes,
+							      width, height,
+							      imx708->mode->width,
+							      imx708->mode->height);
+			imx708_set_framing_limits(imx708);
+		}
+		break;
+	}
+
+	/*
+	 * Applying V4L2 control value only happens
+	 * when power is up for streaming
+	 */
+	if (pm_runtime_get_if_in_use(&client->dev) == 0)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_ANALOGUE_GAIN:
+		imx708_set_analogue_gain(imx708, ctrl->val);
+		break;
+	case V4L2_CID_EXPOSURE:
+		ret = imx708_set_exposure(imx708, ctrl->val);
+		break;
+	case V4L2_CID_DIGITAL_GAIN:
+		ret = imx708_write_reg(imx708, IMX708_REG_DIGITAL_GAIN,
+				       IMX708_REG_VALUE_16BIT, ctrl->val);
+		break;
+	case V4L2_CID_TEST_PATTERN:
+		ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN,
+				       IMX708_REG_VALUE_16BIT,
+				       imx708_test_pattern_val[ctrl->val]);
+		break;
+	case V4L2_CID_TEST_PATTERN_RED:
+		ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_R,
+				       IMX708_REG_VALUE_16BIT, ctrl->val);
+		break;
+	case V4L2_CID_TEST_PATTERN_GREENR:
+		ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_GR,
+				       IMX708_REG_VALUE_16BIT, ctrl->val);
+		break;
+	case V4L2_CID_TEST_PATTERN_BLUE:
+		ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_B,
+				       IMX708_REG_VALUE_16BIT, ctrl->val);
+		break;
+	case V4L2_CID_TEST_PATTERN_GREENB:
+		ret = imx708_write_reg(imx708, IMX708_REG_TEST_PATTERN_GB,
+				       IMX708_REG_VALUE_16BIT, ctrl->val);
+		break;
+	case V4L2_CID_HFLIP:
+	case V4L2_CID_VFLIP:
+		ret = imx708_write_reg(imx708, IMX708_REG_ORIENTATION, 1,
+				       imx708->hflip->val |
+				       imx708->vflip->val << 1);
+		break;
+	case V4L2_CID_VBLANK:
+		ret = imx708_set_frame_length(imx708,
+					      imx708->mode->height + ctrl->val);
+		break;
+	case V4L2_CID_NOTIFY_GAINS:
+		ret = imx708_write_reg(imx708, IMX708_REG_COLOUR_BALANCE_BLUE,
+				       IMX708_REG_VALUE_16BIT,
+				       ctrl->p_new.p_u32[0]);
+		if (ret)
+			break;
+		ret = imx708_write_reg(imx708, IMX708_REG_COLOUR_BALANCE_RED,
+				       IMX708_REG_VALUE_16BIT,
+				       ctrl->p_new.p_u32[3]);
+		break;
+	case V4L2_CID_WIDE_DYNAMIC_RANGE:
+		/* Already handled above. */
+		break;
+	default:
+		dev_info(&client->dev,
+			 "ctrl(id:0x%x,val:0x%x) is not handled\n",
+			 ctrl->id, ctrl->val);
+		ret = -EINVAL;
+		break;
+	}
+
+	pm_runtime_mark_last_busy(&client->dev);
+	pm_runtime_put_autosuspend(&client->dev);
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops imx708_ctrl_ops = {
+	.s_ctrl = imx708_set_ctrl,
+};
+
+static int imx708_enum_mbus_code(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *sd_state,
+				 struct v4l2_subdev_mbus_code_enum *code)
+{
+	struct imx708 *imx708 = to_imx708(sd);
+
+	if (code->pad >= NUM_PADS)
+		return -EINVAL;
+
+	if (code->pad == IMAGE_PAD) {
+		if (code->index >= (ARRAY_SIZE(codes) / 4))
+			return -EINVAL;
+
+		mutex_lock(&imx708->mutex);
+		code->code = imx708_get_format_code(imx708);
+		mutex_unlock(&imx708->mutex);
+	} else {
+		if (code->index > 0)
+			return -EINVAL;
+
+		code->code = MEDIA_BUS_FMT_SENSOR_DATA;
+	}
+
+	return 0;
+}
+
+static int imx708_enum_frame_size(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *sd_state,
+				  struct v4l2_subdev_frame_size_enum *fse)
+{
+	struct imx708 *imx708 = to_imx708(sd);
+	u32 code;
+
+	if (fse->pad >= NUM_PADS)
+		return -EINVAL;
+
+	if (fse->pad == IMAGE_PAD) {
+		const struct imx708_mode *mode_list;
+		unsigned int num_modes;
+
+		get_mode_table(fse->code, &mode_list, &num_modes,
+			       imx708->hdr_mode->val);
+
+		if (fse->index >= num_modes)
+			return -EINVAL;
+
+		mutex_lock(&imx708->mutex);
+		code = imx708_get_format_code(imx708);
+		mutex_unlock(&imx708->mutex);
+
+		if (fse->code != code)
+			return -EINVAL;
+
+		fse->min_width = mode_list[fse->index].width;
+		fse->max_width = fse->min_width;
+		fse->min_height = mode_list[fse->index].height;
+		fse->max_height = fse->min_height;
+	} else {
+		if (fse->code != MEDIA_BUS_FMT_SENSOR_DATA || fse->index > 0)
+			return -EINVAL;
+
+		fse->min_width = IMX708_EMBEDDED_LINE_WIDTH;
+		fse->max_width = fse->min_width;
+		fse->min_height = IMX708_NUM_EMBEDDED_LINES;
+		fse->max_height = fse->min_height;
+	}
+
+	return 0;
+}
+
+static void imx708_reset_colorspace(struct v4l2_mbus_framefmt *fmt)
+{
+	fmt->colorspace = V4L2_COLORSPACE_RAW;
+	fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace);
+	fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true,
+							  fmt->colorspace,
+							  fmt->ycbcr_enc);
+	fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace);
+}
+
+static void imx708_update_image_pad_format(struct imx708 *imx708,
+					   const struct imx708_mode *mode,
+					   struct v4l2_subdev_format *fmt)
+{
+	fmt->format.width = mode->width;
+	fmt->format.height = mode->height;
+	fmt->format.field = V4L2_FIELD_NONE;
+	imx708_reset_colorspace(&fmt->format);
+}
+
+static void imx708_update_metadata_pad_format(struct v4l2_subdev_format *fmt)
+{
+	fmt->format.width = IMX708_EMBEDDED_LINE_WIDTH;
+	fmt->format.height = IMX708_NUM_EMBEDDED_LINES;
+	fmt->format.code = MEDIA_BUS_FMT_SENSOR_DATA;
+	fmt->format.field = V4L2_FIELD_NONE;
+}
+
+static int imx708_get_pad_format(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *sd_state,
+				 struct v4l2_subdev_format *fmt)
+{
+	struct imx708 *imx708 = to_imx708(sd);
+
+	if (fmt->pad >= NUM_PADS)
+		return -EINVAL;
+
+	mutex_lock(&imx708->mutex);
+
+	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+		struct v4l2_mbus_framefmt *try_fmt =
+			v4l2_subdev_state_get_format(sd_state,
+						   fmt->pad);
+		/* update the code which could change due to vflip or hflip */
+		try_fmt->code = fmt->pad == IMAGE_PAD ?
+				imx708_get_format_code(imx708) :
+				MEDIA_BUS_FMT_SENSOR_DATA;
+		fmt->format = *try_fmt;
+	} else {
+		if (fmt->pad == IMAGE_PAD) {
+			imx708_update_image_pad_format(imx708, imx708->mode,
+						       fmt);
+			fmt->format.code = imx708_get_format_code(imx708);
+		} else {
+			imx708_update_metadata_pad_format(fmt);
+		}
+	}
+
+	mutex_unlock(&imx708->mutex);
+	return 0;
+}
+
+static int imx708_set_pad_format(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *sd_state,
+				 struct v4l2_subdev_format *fmt)
+{
+	struct v4l2_mbus_framefmt *framefmt;
+	const struct imx708_mode *mode;
+	struct imx708 *imx708 = to_imx708(sd);
+
+	if (fmt->pad >= NUM_PADS)
+		return -EINVAL;
+
+	mutex_lock(&imx708->mutex);
+
+	if (fmt->pad == IMAGE_PAD) {
+		const struct imx708_mode *mode_list;
+		unsigned int num_modes;
+
+		/* Bayer order varies with flips */
+		fmt->format.code = imx708_get_format_code(imx708);
+
+		get_mode_table(fmt->format.code, &mode_list, &num_modes,
+			       imx708->hdr_mode->val);
+
+		mode = v4l2_find_nearest_size(mode_list,
+					      num_modes,
+					      width, height,
+					      fmt->format.width,
+					      fmt->format.height);
+		imx708_update_image_pad_format(imx708, mode, fmt);
+		if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+			framefmt = v4l2_subdev_state_get_format(sd_state,
+							      fmt->pad);
+			*framefmt = fmt->format;
+		} else {
+			imx708->mode = mode;
+			imx708_set_framing_limits(imx708);
+		}
+	} else {
+		if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+			framefmt = v4l2_subdev_state_get_format(sd_state,
+							      fmt->pad);
+			*framefmt = fmt->format;
+		} else {
+			/* Only one embedded data mode is supported */
+			imx708_update_metadata_pad_format(fmt);
+		}
+	}
+
+	mutex_unlock(&imx708->mutex);
+
+	return 0;
+}
+
+static const struct v4l2_rect *
+__imx708_get_pad_crop(struct imx708 *imx708, struct v4l2_subdev_state *sd_state,
+		      unsigned int pad, enum v4l2_subdev_format_whence which)
+{
+	switch (which) {
+	case V4L2_SUBDEV_FORMAT_TRY:
+		return v4l2_subdev_state_get_crop(sd_state, pad);
+	case V4L2_SUBDEV_FORMAT_ACTIVE:
+		return &imx708->mode->crop;
+	}
+
+	return NULL;
+}
+
+static int imx708_get_selection(struct v4l2_subdev *sd,
+				struct v4l2_subdev_state *sd_state,
+				struct v4l2_subdev_selection *sel)
+{
+	switch (sel->target) {
+	case V4L2_SEL_TGT_CROP: {
+		struct imx708 *imx708 = to_imx708(sd);
+
+		mutex_lock(&imx708->mutex);
+		sel->r = *__imx708_get_pad_crop(imx708, sd_state, sel->pad,
+						sel->which);
+		mutex_unlock(&imx708->mutex);
+
+		return 0;
+	}
+
+	case V4L2_SEL_TGT_NATIVE_SIZE:
+		sel->r.left = 0;
+		sel->r.top = 0;
+		sel->r.width = IMX708_NATIVE_WIDTH;
+		sel->r.height = IMX708_NATIVE_HEIGHT;
+
+		return 0;
+
+	case V4L2_SEL_TGT_CROP_DEFAULT:
+	case V4L2_SEL_TGT_CROP_BOUNDS:
+		sel->r.left = IMX708_PIXEL_ARRAY_LEFT;
+		sel->r.top = IMX708_PIXEL_ARRAY_TOP;
+		sel->r.width = IMX708_PIXEL_ARRAY_WIDTH;
+		sel->r.height = IMX708_PIXEL_ARRAY_HEIGHT;
+
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+/* Start streaming */
+static int imx708_start_streaming(struct imx708 *imx708)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd);
+	const struct imx708_reg_list *reg_list, *freq_regs;
+	int i, ret;
+	u32 val;
+
+	if (!imx708->common_regs_written) {
+		ret = imx708_write_regs(imx708, mode_common_regs,
+					ARRAY_SIZE(mode_common_regs));
+		if (ret) {
+			dev_err(&client->dev, "%s failed to set common settings\n",
+				__func__);
+			return ret;
+		}
+
+		ret = imx708_read_reg(imx708, IMX708_REG_BASE_SPC_GAINS_L,
+				      IMX708_REG_VALUE_08BIT, &val);
+		if (ret == 0 && val == 0x40) {
+			for (i = 0; i < 54 && ret == 0; i++) {
+				ret = imx708_write_reg(imx708,
+						       IMX708_REG_BASE_SPC_GAINS_L + i,
+						       IMX708_REG_VALUE_08BIT,
+						       pdaf_gains[0][i % 9]);
+			}
+			for (i = 0; i < 54 && ret == 0; i++) {
+				ret = imx708_write_reg(imx708,
+						       IMX708_REG_BASE_SPC_GAINS_R + i,
+						       IMX708_REG_VALUE_08BIT,
+						       pdaf_gains[1][i % 9]);
+			}
+		}
+		if (ret) {
+			dev_err(&client->dev, "%s failed to set PDAF gains\n",
+				__func__);
+			return ret;
+		}
+
+		imx708->common_regs_written = true;
+	}
+
+	/* Apply default values of current mode */
+	reg_list = &imx708->mode->reg_list;
+	ret = imx708_write_regs(imx708, reg_list->regs, reg_list->num_of_regs);
+	if (ret) {
+		dev_err(&client->dev, "%s failed to set mode\n", __func__);
+		return ret;
+	}
+
+	/* Update the link frequency registers */
+	freq_regs = &link_freq_regs[imx708->link_freq_idx];
+	ret = imx708_write_regs(imx708, freq_regs->regs,
+				freq_regs->num_of_regs);
+	if (ret) {
+		dev_err(&client->dev, "%s failed to set link frequency registers\n",
+			__func__);
+		return ret;
+	}
+
+	/* Quad Bayer re-mosaic adjustments (for full-resolution mode only) */
+	if (imx708->mode->remosaic && qbc_adjust > 0) {
+		imx708_write_reg(imx708, IMX708_LPF_INTENSITY,
+				 IMX708_REG_VALUE_08BIT, qbc_adjust);
+		imx708_write_reg(imx708,
+				 IMX708_LPF_INTENSITY_EN,
+				 IMX708_REG_VALUE_08BIT,
+				 IMX708_LPF_INTENSITY_ENABLED);
+	} else {
+		imx708_write_reg(imx708,
+				 IMX708_LPF_INTENSITY_EN,
+				 IMX708_REG_VALUE_08BIT,
+				 IMX708_LPF_INTENSITY_DISABLED);
+	}
+
+	/* Apply customized values from user */
+	ret =  __v4l2_ctrl_handler_setup(imx708->sd.ctrl_handler);
+	if (ret)
+		return ret;
+
+	/* set stream on register */
+	return imx708_write_reg(imx708, IMX708_REG_MODE_SELECT,
+				IMX708_REG_VALUE_08BIT, IMX708_MODE_STREAMING);
+}
+
+/* Stop streaming */
+static void imx708_stop_streaming(struct imx708 *imx708)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd);
+	int ret;
+
+	/* set stream off register */
+	ret = imx708_write_reg(imx708, IMX708_REG_MODE_SELECT,
+			       IMX708_REG_VALUE_08BIT, IMX708_MODE_STANDBY);
+	if (ret)
+		dev_err(&client->dev, "%s failed to set stream\n", __func__);
+}
+
+static int imx708_set_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct imx708 *imx708 = to_imx708(sd);
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	int ret = 0;
+
+	mutex_lock(&imx708->mutex);
+	if (imx708->streaming == enable) {
+		mutex_unlock(&imx708->mutex);
+		return 0;
+	}
+
+	if (enable) {
+		ret = pm_runtime_resume_and_get(&client->dev);
+		if (ret < 0)
+			goto err_unlock;
+
+		/*
+		 * Apply default & customized values
+		 * and then start streaming.
+		 */
+		ret = imx708_start_streaming(imx708);
+		if (ret) {
+			pm_runtime_put_sync(&client->dev);
+			goto err_unlock;
+		}
+	} else {
+		imx708_stop_streaming(imx708);
+		pm_runtime_mark_last_busy(&client->dev);
+		pm_runtime_put_autosuspend(&client->dev);
+	}
+
+	imx708->streaming = enable;
+
+	/* vflip/hflip and hdr mode cannot change during streaming */
+	__v4l2_ctrl_grab(imx708->vflip, enable);
+	__v4l2_ctrl_grab(imx708->hflip, enable);
+	__v4l2_ctrl_grab(imx708->hdr_mode, enable);
+
+	mutex_unlock(&imx708->mutex);
+
+	return ret;
+
+err_unlock:
+	mutex_unlock(&imx708->mutex);
+
+	return ret;
+}
+
+/* Power/clock management functions */
+static int imx708_power_on(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct imx708 *imx708 = to_imx708(sd);
+	int ret;
+
+	ret = regulator_bulk_enable(ARRAY_SIZE(imx708_supply_name),
+				    imx708->supplies);
+	if (ret) {
+		dev_err(&client->dev, "%s: failed to enable regulators\n",
+			__func__);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(imx708->inclk);
+	if (ret) {
+		dev_err(&client->dev, "%s: failed to enable clock\n",
+			__func__);
+		goto reg_off;
+	}
+
+	gpiod_set_value_cansleep(imx708->reset_gpio, 1);
+	usleep_range(IMX708_XCLR_MIN_DELAY_US,
+		     IMX708_XCLR_MIN_DELAY_US + IMX708_XCLR_DELAY_RANGE_US);
+
+	return 0;
+
+reg_off:
+	regulator_bulk_disable(ARRAY_SIZE(imx708_supply_name),
+			       imx708->supplies);
+	return ret;
+}
+
+static int imx708_power_off(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct imx708 *imx708 = to_imx708(sd);
+
+	gpiod_set_value_cansleep(imx708->reset_gpio, 0);
+	regulator_bulk_disable(ARRAY_SIZE(imx708_supply_name),
+			       imx708->supplies);
+	clk_disable_unprepare(imx708->inclk);
+
+	/* Force reprogramming of the common registers when powered up again. */
+	imx708->common_regs_written = false;
+
+	return 0;
+}
+
+static int __maybe_unused imx708_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct imx708 *imx708 = to_imx708(sd);
+
+	if (imx708->streaming)
+		imx708_stop_streaming(imx708);
+
+	return 0;
+}
+
+static int __maybe_unused imx708_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct imx708 *imx708 = to_imx708(sd);
+	int ret;
+
+	if (imx708->streaming) {
+		ret = imx708_start_streaming(imx708);
+		if (ret)
+			goto error;
+	}
+
+	return 0;
+
+error:
+	imx708_stop_streaming(imx708);
+	imx708->streaming = 0;
+	return ret;
+}
+
+static int imx708_get_regulators(struct imx708 *imx708)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd);
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(imx708_supply_name); i++)
+		imx708->supplies[i].supply = imx708_supply_name[i];
+
+	return devm_regulator_bulk_get(&client->dev,
+				       ARRAY_SIZE(imx708_supply_name),
+				       imx708->supplies);
+}
+
+/* Verify chip ID */
+static int imx708_identify_module(struct imx708 *imx708)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd);
+	int ret;
+	u32 val;
+
+	ret = imx708_read_reg(imx708, IMX708_REG_CHIP_ID,
+			      IMX708_REG_VALUE_16BIT, &val);
+	if (ret) {
+		dev_err(&client->dev, "failed to read chip id %x, with error %d\n",
+			IMX708_CHIP_ID, ret);
+		return ret;
+	}
+
+	if (val != IMX708_CHIP_ID) {
+		dev_err(&client->dev, "chip id mismatch: %x!=%x\n",
+			IMX708_CHIP_ID, val);
+		return -EIO;
+	}
+
+	ret = imx708_read_reg(imx708, 0x0000, IMX708_REG_VALUE_16BIT, &val);
+	if (!ret) {
+		dev_info(&client->dev, "camera module ID 0x%04x\n", val);
+		snprintf(imx708->sd.name, sizeof(imx708->sd.name), "imx708%s%s",
+			 val & 0x02 ? "_wide" : "",
+			 val & 0x80 ? "_noir" : "");
+	}
+
+	return 0;
+}
+
+static const struct v4l2_subdev_core_ops imx708_core_ops = {
+	.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_video_ops imx708_video_ops = {
+	.s_stream = imx708_set_stream,
+};
+
+static const struct v4l2_subdev_pad_ops imx708_pad_ops = {
+	.enum_mbus_code = imx708_enum_mbus_code,
+	.get_fmt = imx708_get_pad_format,
+	.set_fmt = imx708_set_pad_format,
+	.get_selection = imx708_get_selection,
+	.enum_frame_size = imx708_enum_frame_size,
+};
+
+static const struct v4l2_subdev_ops imx708_subdev_ops = {
+	.core = &imx708_core_ops,
+	.video = &imx708_video_ops,
+	.pad = &imx708_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops imx708_internal_ops = {
+	.open = imx708_open,
+};
+
+static const struct v4l2_ctrl_config imx708_notify_gains_ctrl = {
+	.ops = &imx708_ctrl_ops,
+	.id = V4L2_CID_NOTIFY_GAINS,
+	.type = V4L2_CTRL_TYPE_U32,
+	.min = IMX708_COLOUR_BALANCE_MIN,
+	.max = IMX708_COLOUR_BALANCE_MAX,
+	.step = IMX708_COLOUR_BALANCE_STEP,
+	.def = IMX708_COLOUR_BALANCE_DEFAULT,
+	.dims = { 4 },
+	.elem_size = sizeof(u32),
+};
+
+/* Initialize control handlers */
+static int imx708_init_controls(struct imx708 *imx708)
+{
+	struct v4l2_ctrl_handler *ctrl_hdlr;
+	struct i2c_client *client = v4l2_get_subdevdata(&imx708->sd);
+	struct v4l2_fwnode_device_properties props;
+	struct v4l2_ctrl *ctrl;
+	unsigned int i;
+	int ret;
+
+	ctrl_hdlr = &imx708->ctrl_handler;
+	ret = v4l2_ctrl_handler_init(ctrl_hdlr, 16);
+	if (ret)
+		return ret;
+
+	mutex_init(&imx708->mutex);
+	ctrl_hdlr->lock = &imx708->mutex;
+
+	/* By default, PIXEL_RATE is read only */
+	imx708->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops,
+					       V4L2_CID_PIXEL_RATE,
+					       IMX708_INITIAL_PIXEL_RATE,
+					       IMX708_INITIAL_PIXEL_RATE, 1,
+					       IMX708_INITIAL_PIXEL_RATE);
+
+	ctrl = v4l2_ctrl_new_int_menu(ctrl_hdlr, &imx708_ctrl_ops,
+				      V4L2_CID_LINK_FREQ, 0, 0,
+				      &link_freqs[imx708->link_freq_idx]);
+	if (ctrl)
+		ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	/*
+	 * Create the controls here, but mode specific limits are setup
+	 * in the imx708_set_framing_limits() call below.
+	 */
+	imx708->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops,
+					   V4L2_CID_VBLANK, 0, 0xffff, 1, 0);
+	imx708->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops,
+					   V4L2_CID_HBLANK, 0, 0xffff, 1, 0);
+
+	imx708->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops,
+					     V4L2_CID_EXPOSURE,
+					     IMX708_EXPOSURE_MIN,
+					     IMX708_EXPOSURE_MAX,
+					     IMX708_EXPOSURE_STEP,
+					     IMX708_EXPOSURE_DEFAULT);
+
+	v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
+			  IMX708_ANA_GAIN_MIN, IMX708_ANA_GAIN_MAX,
+			  IMX708_ANA_GAIN_STEP, IMX708_ANA_GAIN_DEFAULT);
+
+	v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
+			  IMX708_DGTL_GAIN_MIN, IMX708_DGTL_GAIN_MAX,
+			  IMX708_DGTL_GAIN_STEP, IMX708_DGTL_GAIN_DEFAULT);
+
+	imx708->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops,
+					  V4L2_CID_HFLIP, 0, 1, 1, 0);
+
+	imx708->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops,
+					  V4L2_CID_VFLIP, 0, 1, 1, 0);
+	v4l2_ctrl_cluster(2, &imx708->hflip);
+
+	v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &imx708_ctrl_ops,
+				     V4L2_CID_TEST_PATTERN,
+				     ARRAY_SIZE(imx708_test_pattern_menu) - 1,
+				     0, 0, imx708_test_pattern_menu);
+	for (i = 0; i < 4; i++) {
+		/*
+		 * The assumption is that
+		 * V4L2_CID_TEST_PATTERN_GREENR == V4L2_CID_TEST_PATTERN_RED + 1
+		 * V4L2_CID_TEST_PATTERN_BLUE   == V4L2_CID_TEST_PATTERN_RED + 2
+		 * V4L2_CID_TEST_PATTERN_GREENB == V4L2_CID_TEST_PATTERN_RED + 3
+		 */
+		v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops,
+				  V4L2_CID_TEST_PATTERN_RED + i,
+				  IMX708_TEST_PATTERN_COLOUR_MIN,
+				  IMX708_TEST_PATTERN_COLOUR_MAX,
+				  IMX708_TEST_PATTERN_COLOUR_STEP,
+				  IMX708_TEST_PATTERN_COLOUR_MAX);
+		/* The "Solid color" pattern is white by default */
+	}
+
+	v4l2_ctrl_new_custom(ctrl_hdlr, &imx708_notify_gains_ctrl, NULL);
+
+	imx708->hdr_mode = v4l2_ctrl_new_std(ctrl_hdlr, &imx708_ctrl_ops,
+					     V4L2_CID_WIDE_DYNAMIC_RANGE,
+					     0, 1, 1, 0);
+
+	ret = v4l2_fwnode_device_parse(&client->dev, &props);
+	if (ret)
+		goto error;
+
+	v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &imx708_ctrl_ops, &props);
+
+	if (ctrl_hdlr->error) {
+		ret = ctrl_hdlr->error;
+		dev_err(&client->dev, "%s control init failed (%d)\n",
+			__func__, ret);
+		goto error;
+	}
+
+	imx708->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+	imx708->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+	imx708->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+	imx708->hdr_mode->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+	imx708->sd.ctrl_handler = ctrl_hdlr;
+
+	/* Setup exposure and frame/line length limits. */
+	mutex_lock(&imx708->mutex);
+	imx708_set_framing_limits(imx708);
+	mutex_unlock(&imx708->mutex);
+
+	return 0;
+
+error:
+	v4l2_ctrl_handler_free(ctrl_hdlr);
+	mutex_destroy(&imx708->mutex);
+
+	return ret;
+}
+
+static void imx708_free_controls(struct imx708 *imx708)
+{
+	v4l2_ctrl_handler_free(imx708->sd.ctrl_handler);
+	mutex_destroy(&imx708->mutex);
+}
+
+static int imx708_check_hwcfg(struct device *dev, struct imx708 *imx708)
+{
+	struct fwnode_handle *endpoint;
+	struct v4l2_fwnode_endpoint ep_cfg = {
+		.bus_type = V4L2_MBUS_CSI2_DPHY
+	};
+	int ret = -EINVAL;
+	int i;
+
+	endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL);
+	if (!endpoint) {
+		dev_err(dev, "endpoint node not found\n");
+		return -EINVAL;
+	}
+
+	if (v4l2_fwnode_endpoint_alloc_parse(endpoint, &ep_cfg)) {
+		dev_err(dev, "could not parse endpoint\n");
+		goto error_out;
+	}
+
+	/* Check the number of MIPI CSI2 data lanes */
+	if (ep_cfg.bus.mipi_csi2.num_data_lanes != 2) {
+		dev_err(dev, "only 2 data lanes are currently supported\n");
+		goto error_out;
+	}
+
+	/* Check the link frequency set in device tree */
+	if (!ep_cfg.nr_of_link_frequencies) {
+		dev_err(dev, "link-frequency property not found in DT\n");
+		goto error_out;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(link_freqs); i++) {
+		if (link_freqs[i] == ep_cfg.link_frequencies[0]) {
+			imx708->link_freq_idx = i;
+			break;
+		}
+	}
+
+	if (i == ARRAY_SIZE(link_freqs)) {
+		dev_err(dev, "Link frequency not supported: %lld\n",
+			ep_cfg.link_frequencies[0]);
+			ret = -EINVAL;
+			goto error_out;
+	}
+
+	ret = 0;
+
+error_out:
+	v4l2_fwnode_endpoint_free(&ep_cfg);
+	fwnode_handle_put(endpoint);
+
+	return ret;
+}
+
+static int imx708_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct imx708 *imx708;
+	int ret;
+
+	imx708 = devm_kzalloc(&client->dev, sizeof(*imx708), GFP_KERNEL);
+	if (!imx708)
+		return -ENOMEM;
+
+	v4l2_i2c_subdev_init(&imx708->sd, client, &imx708_subdev_ops);
+
+	/* Check the hardware configuration in device tree */
+	if (imx708_check_hwcfg(dev, imx708))
+		return -EINVAL;
+
+	/* Get system clock (inclk) */
+	imx708->inclk = devm_clk_get(dev, "inclk");
+	if (IS_ERR(imx708->inclk))
+		return dev_err_probe(dev, PTR_ERR(imx708->inclk),
+				     "failed to get inclk\n");
+
+	imx708->inclk_freq = clk_get_rate(imx708->inclk);
+	if (imx708->inclk_freq != IMX708_INCLK_FREQ)
+		return dev_err_probe(dev, -EINVAL,
+				     "inclk frequency not supported: %d Hz\n",
+				     imx708->inclk_freq);
+
+	ret = imx708_get_regulators(imx708);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to get regulators\n");
+
+	/* Request optional enable pin */
+	imx708->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+						     GPIOD_OUT_HIGH);
+
+	/*
+	 * The sensor must be powered for imx708_identify_module()
+	 * to be able to read the CHIP_ID register
+	 */
+	ret = imx708_power_on(dev);
+	if (ret)
+		return ret;
+
+	ret = imx708_identify_module(imx708);
+	if (ret)
+		goto error_power_off;
+
+	/* Initialize default format */
+	imx708_set_default_format(imx708);
+
+	/*
+	 * Enable runtime PM with autosuspend. As the device has been powered
+	 * manually, mark it as active, and increase the usage count without
+	 * resuming the device.
+	 */
+	pm_runtime_set_active(dev);
+	pm_runtime_get_noresume(dev);
+	pm_runtime_enable(dev);
+	pm_runtime_set_autosuspend_delay(dev, 5000);
+	pm_runtime_use_autosuspend(dev);
+
+	/* This needs the pm runtime to be registered. */
+	ret = imx708_init_controls(imx708);
+	if (ret)
+		goto error_pm_runtime;
+
+	/* Initialize subdev */
+	imx708->sd.internal_ops = &imx708_internal_ops;
+	imx708->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
+			    V4L2_SUBDEV_FL_HAS_EVENTS;
+	imx708->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+
+	/* Initialize source pads */
+	imx708->pad[IMAGE_PAD].flags = MEDIA_PAD_FL_SOURCE;
+	imx708->pad[METADATA_PAD].flags = MEDIA_PAD_FL_SOURCE;
+
+	ret = media_entity_pads_init(&imx708->sd.entity, NUM_PADS, imx708->pad);
+	if (ret) {
+		dev_err(dev, "failed to init entity pads: %d\n", ret);
+		goto error_handler_free;
+	}
+
+	ret = v4l2_async_register_subdev_sensor(&imx708->sd);
+	if (ret < 0) {
+		dev_err(dev, "failed to register sensor sub-device: %d\n", ret);
+		goto error_media_entity;
+	}
+
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+
+	return 0;
+
+error_media_entity:
+	media_entity_cleanup(&imx708->sd.entity);
+
+error_handler_free:
+	imx708_free_controls(imx708);
+
+error_pm_runtime:
+	pm_runtime_disable(dev);
+	pm_runtime_put_noidle(dev);
+
+error_power_off:
+	imx708_power_off(&client->dev);
+
+	return ret;
+}
+
+static void imx708_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct imx708 *imx708 = to_imx708(sd);
+
+	v4l2_async_unregister_subdev(sd);
+	media_entity_cleanup(&sd->entity);
+	imx708_free_controls(imx708);
+
+	pm_runtime_disable(&client->dev);
+	if (!pm_runtime_status_suspended(&client->dev))
+		imx708_power_off(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+}
+
+static const struct of_device_id imx708_dt_ids[] = {
+	{ .compatible = "sony,imx708" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx708_dt_ids);
+
+static const struct dev_pm_ops imx708_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(imx708_suspend, imx708_resume)
+	SET_RUNTIME_PM_OPS(imx708_power_off, imx708_power_on, NULL)
+};
+
+static struct i2c_driver imx708_i2c_driver = {
+	.driver = {
+		.name = "imx708",
+		.of_match_table	= imx708_dt_ids,
+		.pm = &imx708_pm_ops,
+	},
+	.probe = imx708_probe,
+	.remove = imx708_remove,
+};
+
+module_i2c_driver(imx708_i2c_driver);
+
+MODULE_AUTHOR("David Plowman <david.plowman@raspberrypi.com>");
+MODULE_DESCRIPTION("Sony IMX708 sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/i2c/irs1125.c b/drivers/media/i2c/irs1125.c
new file mode 100644
index 000000000000..f69b4c5a9827
--- /dev/null
+++ b/drivers/media/i2c/irs1125.c
@@ -0,0 +1,1197 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A V4L2 driver for Infineon IRS1125 TOF cameras.
+ * Copyright (C) 2018, pieye GmbH
+ *
+ * Based on V4L2 OmniVision OV5647 Image Sensor driver
+ * Copyright (C) 2016 Ramiro Oliveira <roliveir@synopsys.com>
+ *
+ * DT / fwnode changes, and GPIO control taken from ov5640.c
+ * Copyright (C) 2011-2013 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright (C) 2014-2017 Mentor Graphics Inc.
+ *
+ */
+
+#include "irs1125.h"
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_graph.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-image-sizes.h>
+#include <media/v4l2-mediabus.h>
+
+#define CHECK_BIT(val, pos) ((val) & BIT(pos))
+
+#define SENSOR_NAME "irs1125"
+
+#define RESET_ACTIVE_DELAY_MS	 20
+
+#define IRS1125_ALTERNATE_FW "irs1125_af.bin"
+
+#define IRS1125_REG_SAFE_RECONFIG	0xA850
+#define IRS1125_REG_CSICFG		0xA882
+#define IRS1125_REG_DESIGN_STEP		0xB0AD
+#define IRS1125_REG_EFUSEVAL2		0xB09F
+#define IRS1125_REG_EFUSEVAL3		0xB0A0
+#define IRS1125_REG_EFUSEVAL4		0xB0A1
+#define IRS1125_REG_DMEM_SHADOW		0xC320
+
+#define IRS1125_DESIGN_STEP_EXPECTED	0x0a12
+
+#define IRS1125_ROW_START_DEF		0
+#define IRS1125_COLUMN_START_DEF	0
+#define IRS1125_WINDOW_HEIGHT_DEF	288
+#define IRS1125_WINDOW_WIDTH_DEF	352
+
+struct regval_list {
+	u16 addr;
+	u16 data;
+};
+
+struct irs1125 {
+	struct v4l2_subdev sd;
+	struct media_pad pad;
+	/* the parsed DT endpoint info */
+	struct v4l2_fwnode_endpoint ep;
+
+	struct clk *xclk;
+	struct v4l2_ctrl_handler ctrl_handler;
+
+	/* To serialize asynchronus callbacks */
+	struct mutex lock;
+
+	/* image data layout */
+	unsigned int num_seq;
+
+	/* reset pin */
+	struct gpio_desc *reset;
+
+	/* V4l2 Controls to grab */
+	struct v4l2_ctrl *ctrl_modplls;
+	struct v4l2_ctrl *ctrl_numseq;
+
+	int power_count;
+	bool mod_pll_init;
+};
+
+static inline struct irs1125 *to_state(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct irs1125, sd);
+}
+
+static const char *expo_ctrl_names[IRS1125_NUM_SEQ_ENTRIES] = {
+	"safe reconfiguration of exposure of sequence 0",
+	"safe reconfiguration of exposure of sequence 1",
+	"safe reconfiguration of exposure of sequence 2",
+	"safe reconfiguration of exposure of sequence 3",
+	"safe reconfiguration of exposure of sequence 4",
+	"safe reconfiguration of exposure of sequence 5",
+	"safe reconfiguration of exposure of sequence 6",
+	"safe reconfiguration of exposure of sequence 7",
+	"safe reconfiguration of exposure of sequence 8",
+	"safe reconfiguration of exposure of sequence 9",
+	"safe reconfiguration of exposure of sequence 10",
+	"safe reconfiguration of exposure of sequence 11",
+	"safe reconfiguration of exposure of sequence 12",
+	"safe reconfiguration of exposure of sequence 13",
+	"safe reconfiguration of exposure of sequence 14",
+	"safe reconfiguration of exposure of sequence 15",
+	"safe reconfiguration of exposure of sequence 16",
+	"safe reconfiguration of exposure of sequence 17",
+	"safe reconfiguration of exposure of sequence 18",
+	"safe reconfiguration of exposure of sequence 19",
+};
+
+static const char *frame_ctrl_names[IRS1125_NUM_SEQ_ENTRIES] = {
+	"safe reconfiguration of framerate of sequence 0",
+	"safe reconfiguration of framerate of sequence 1",
+	"safe reconfiguration of framerate of sequence 2",
+	"safe reconfiguration of framerate of sequence 3",
+	"safe reconfiguration of framerate of sequence 4",
+	"safe reconfiguration of framerate of sequence 5",
+	"safe reconfiguration of framerate of sequence 6",
+	"safe reconfiguration of framerate of sequence 7",
+	"safe reconfiguration of framerate of sequence 8",
+	"safe reconfiguration of framerate of sequence 9",
+	"safe reconfiguration of framerate of sequence 10",
+	"safe reconfiguration of framerate of sequence 11",
+	"safe reconfiguration of framerate of sequence 12",
+	"safe reconfiguration of framerate of sequence 13",
+	"safe reconfiguration of framerate of sequence 14",
+	"safe reconfiguration of framerate of sequence 15",
+	"safe reconfiguration of framerate of sequence 16",
+	"safe reconfiguration of framerate of sequence 17",
+	"safe reconfiguration of framerate of sequence 18",
+	"safe reconfiguration of framerate of sequence 19",
+};
+
+static struct regval_list irs1125_26mhz[] = {
+	{0xB017, 0x0413},
+	{0xB086, 0x3535},
+	{0xB0AE, 0xEF02},
+	{0xA000, 0x0004},
+	{0xFFFF, 100},
+
+	{0xB062, 0x6383},
+	{0xB063, 0x55A8},
+	{0xB068, 0x7628},
+	{0xB069, 0x03E2},
+
+	{0xFFFF, 100},
+	{0xB05A, 0x01C5},
+	{0xB05C, 0x0206},
+	{0xB05D, 0x01C5},
+	{0xB05F, 0x0206},
+	{0xB016, 0x1335},
+	{0xFFFF, 100},
+	{0xA893, 0x8261},
+	{0xA894, 0x89d8},
+	{0xA895, 0x131d},
+	{0xA896, 0x4251},
+	{0xA897, 0x9D8A},
+	{0xA898, 0x0BD8},
+	{0xA899, 0x2245},
+	{0xA89A, 0xAB9B},
+	{0xA89B, 0x03B9},
+	{0xA89C, 0x8041},
+	{0xA89D, 0xE07E},
+	{0xA89E, 0x0307},
+	{0xFFFF, 100},
+	{0xA88D, 0x0004},
+	{0xA800, 0x0E68},
+	{0xA801, 0x0000},
+	{0xA802, 0x000C},
+	{0xA803, 0x0000},
+	{0xA804, 0x0E68},
+	{0xA805, 0x0000},
+	{0xA806, 0x0440},
+	{0xA807, 0x0000},
+	{0xA808, 0x0E68},
+	{0xA809, 0x0000},
+	{0xA80A, 0x0884},
+	{0xA80B, 0x0000},
+	{0xA80C, 0x0E68},
+	{0xA80D, 0x0000},
+	{0xA80E, 0x0CC8},
+	{0xA80F, 0x0000},
+	{0xA810, 0x0E68},
+	{0xA811, 0x0000},
+	{0xA812, 0x2000},
+	{0xA813, 0x0000},
+	{0xA882, 0x0081},
+	{0xA88C, 0x403A},
+	{0xA88F, 0x031E},
+	{0xA892, 0x0351},
+	{0x9813, 0x13FF},
+	{0x981B, 0x7608},
+
+	{0xB008, 0x0000},
+	{0xB015, 0x1513},
+
+	{0xFFFF, 100}
+};
+
+static struct regval_list irs1125_seq_cfg_init[] = {
+	{0xC3A0, 0x823D},
+	{0xC3A1, 0xB13B},
+	{0xC3A2, 0x0313},
+	{0xC3A3, 0x4659},
+	{0xC3A4, 0xC4EC},
+	{0xC3A5, 0x03CE},
+	{0xC3A6, 0x4259},
+	{0xC3A7, 0xC4EC},
+	{0xC3A8, 0x03CE},
+	{0xC3A9, 0x8839},
+	{0xC3AA, 0x89D8},
+	{0xC3AB, 0x031D},
+
+	{0xC24C, 0x5529},
+	{0xC24D, 0x0000},
+	{0xC24E, 0x1200},
+	{0xC24F, 0x6CB2},
+	{0xC250, 0x0000},
+	{0xC251, 0x5529},
+	{0xC252, 0x42F4},
+	{0xC253, 0xD1AF},
+	{0xC254, 0x8A18},
+	{0xC255, 0x0002},
+	{0xC256, 0x5529},
+	{0xC257, 0x6276},
+	{0xC258, 0x11A7},
+	{0xC259, 0xD907},
+	{0xC25A, 0x0000},
+	{0xC25B, 0x5529},
+	{0xC25C, 0x07E0},
+	{0xC25D, 0x7BFE},
+	{0xC25E, 0x6402},
+	{0xC25F, 0x0019},
+
+	{0xC3AC, 0x0007},
+	{0xC3AD, 0xED88},
+	{0xC320, 0x003E},
+	{0xC321, 0x0000},
+	{0xC322, 0x2000},
+	{0xC323, 0x0000},
+	{0xC324, 0x0271},
+	{0xC325, 0x0000},
+	{0xC326, 0x000C},
+	{0xC327, 0x0000},
+	{0xC328, 0x0271},
+	{0xC329, 0x0000},
+	{0xC32A, 0x0440},
+	{0xC32B, 0x0000},
+	{0xC32C, 0x0271},
+	{0xC32D, 0x0000},
+	{0xC32E, 0x0884},
+	{0xC32F, 0x0000},
+	{0xC330, 0x0271},
+	{0xC331, 0x0000},
+	{0xC332, 0x0CC8},
+	{0xC333, 0x0000},
+	{0xA88D, 0x0004},
+
+	{0xA890, 0x0000},
+	{0xC219, 0x0002},
+	{0xC21A, 0x0000},
+	{0xC21B, 0x0000},
+	{0xC21C, 0x00CD},
+	{0xC21D, 0x0009},
+	{0xC21E, 0x00CD},
+	{0xC21F, 0x0009},
+
+	{0xA87C, 0x0000},
+	{0xC032, 0x0001},
+	{0xC034, 0x0000},
+	{0xC035, 0x0001},
+	{0xC039, 0x0000},
+	{0xC401, 0x0002},
+
+	{0xFFFF, 1}
+};
+
+static int irs1125_write(struct v4l2_subdev *sd, u16 reg, u16 val)
+{
+	int ret;
+	unsigned char data[4] = { reg >> 8, reg & 0xff, val >> 8, val & 0xff};
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	ret = i2c_master_send(client, data, 4);
+	if (ret < 0)
+		dev_err(&client->dev, "%s: i2c write error, reg: %x\n",
+			__func__, reg);
+
+	dev_dbg(&client->dev, "write addr 0x%04x, val 0x%04x\n", reg, val);
+	return ret;
+}
+
+static int irs1125_read(struct v4l2_subdev *sd, u16 reg, u16 *val)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct i2c_msg msgs[2];
+	u8 addr_buf[2] = { reg >> 8, reg & 0xff };
+	u8 data_buf[2] = { 0, };
+	int ret;
+
+	/* Write register address */
+	msgs[0].addr = client->addr;
+	msgs[0].flags = 0;
+	msgs[0].len = ARRAY_SIZE(addr_buf);
+	msgs[0].buf = addr_buf;
+
+	/* Read data from register */
+	msgs[1].addr = client->addr;
+	msgs[1].flags = I2C_M_RD;
+	msgs[1].len = 2;
+	msgs[1].buf = data_buf;
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret != ARRAY_SIZE(msgs)) {
+		if (ret >= 0)
+			ret = -EIO;
+		return ret;
+	}
+
+	*val = data_buf[1] | (data_buf[0] << 8);
+
+	return 0;
+}
+
+static int irs1125_write_array(struct v4l2_subdev *sd,
+			       struct regval_list *regs, int array_size)
+{
+	int i, ret;
+
+	for (i = 0; i < array_size; i++) {
+		if (regs[i].addr == 0xFFFF) {
+			msleep(regs[i].data);
+		} else {
+			ret = irs1125_write(sd, regs[i].addr, regs[i].data);
+			if (ret < 0)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int irs1125_stream_on(struct v4l2_subdev *sd)
+{
+	int ret;
+	struct irs1125 *irs1125 = to_state(sd);
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	v4l2_ctrl_grab(irs1125->ctrl_numseq, 1);
+	v4l2_ctrl_grab(irs1125->ctrl_modplls, 1);
+
+	ret = irs1125_write(sd, 0xC400, 0x0001);
+	if (ret < 0) {
+		dev_err(&client->dev, "error enabling firmware: %d", ret);
+		return ret;
+	}
+
+	msleep(100);
+
+	return irs1125_write(sd, 0xA87C, 0x0001);
+}
+
+static int irs1125_stream_off(struct v4l2_subdev *sd)
+{
+	int ret;
+	struct irs1125 *irs1125 = to_state(sd);
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	v4l2_ctrl_grab(irs1125->ctrl_numseq, 0);
+	v4l2_ctrl_grab(irs1125->ctrl_modplls, 0);
+
+	ret = irs1125_write(sd, 0xA87C, 0x0000);
+	if (ret < 0) {
+		dev_err(&client->dev, "error disabling trigger: %d", ret);
+		return ret;
+	}
+
+	msleep(100);
+
+	return irs1125_write(sd, 0xC400, 0x0002);
+}
+
+static int __sensor_init(struct v4l2_subdev *sd)
+{
+	unsigned int cnt, idx;
+	int ret;
+	u16 val;
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	struct irs1125 *irs1125 = to_state(sd);
+	const struct firmware *fw;
+	struct regval_list *reg_data;
+
+	cnt = 0;
+	while (1) {
+		ret = irs1125_read(sd, 0xC40F, &val);
+		if (ret < 0) {
+			dev_err(&client->dev, "read register 0xC40F failed\n");
+			return ret;
+		}
+		if (CHECK_BIT(val, 14) == 0)
+			break;
+
+		if (cnt >= 5) {
+			dev_err(&client->dev, "timeout waiting for 0xC40F\n");
+			return -EAGAIN;
+		}
+
+		cnt++;
+	}
+
+	ret = irs1125_write_array(sd, irs1125_26mhz,
+				  ARRAY_SIZE(irs1125_26mhz));
+	if (ret < 0) {
+		dev_err(&client->dev, "write sensor default regs error\n");
+		return ret;
+	}
+
+	/* set CSI-2 number of data lanes */
+	if (irs1125->ep.bus.mipi_csi2.num_data_lanes == 1) {
+		val = 0x0001;
+	} else if (irs1125->ep.bus.mipi_csi2.num_data_lanes == 2) {
+		val = 0x0081;
+	} else {
+		dev_err(&client->dev, "invalid number of data lanes %d\n",
+			irs1125->ep.bus.mipi_csi2.num_data_lanes);
+		return -EINVAL;
+	}
+
+	ret = irs1125_write(sd, IRS1125_REG_CSICFG, val);
+	if (ret < 0) {
+		dev_err(&client->dev, "write sensor csi2 config error\n");
+		return ret;
+	}
+
+	/* request the firmware, this will block and timeout */
+	ret = request_firmware(&fw, IRS1125_ALTERNATE_FW, &client->dev);
+	if (ret) {
+		dev_err(&client->dev,
+			"did not find the firmware file '%s' (status %d)\n",
+			IRS1125_ALTERNATE_FW, ret);
+		return ret;
+	}
+
+	if (fw->size % 4) {
+		dev_err(&client->dev, "firmware file '%s' invalid\n",
+			IRS1125_ALTERNATE_FW);
+		release_firmware(fw);
+		return -EINVAL;
+	}
+
+	for (idx = 0; idx < fw->size; idx += 4)	{
+		reg_data = (struct regval_list *)&fw->data[idx];
+		ret = irs1125_write(sd, reg_data->addr, reg_data->data);
+		if (ret < 0) {
+			dev_err(&client->dev, "firmware write error\n");
+			release_firmware(fw);
+			return ret;
+		}
+	}
+	release_firmware(fw);
+
+	ret = irs1125_write_array(sd, irs1125_seq_cfg_init,
+				  ARRAY_SIZE(irs1125_seq_cfg_init));
+	if (ret < 0) {
+		dev_err(&client->dev, "write default sequence failed\n");
+		return ret;
+	}
+
+	irs1125->mod_pll_init = true;
+	v4l2_ctrl_handler_setup(&irs1125->ctrl_handler);
+	irs1125->mod_pll_init = false;
+
+	return irs1125_write(sd, 0xA87C, 0x0001);
+}
+
+static int irs1125_sensor_power(struct v4l2_subdev *sd, int on)
+{
+	int ret = 0;
+	struct irs1125 *irs1125 = to_state(sd);
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	mutex_lock(&irs1125->lock);
+
+	if (on && !irs1125->power_count) {
+		gpiod_set_value_cansleep(irs1125->reset, 1);
+		msleep(RESET_ACTIVE_DELAY_MS);
+
+		ret = clk_prepare_enable(irs1125->xclk);
+		if (ret < 0) {
+			dev_err(&client->dev, "clk prepare enable failed\n");
+			goto out;
+		}
+
+		ret = __sensor_init(sd);
+		if (ret < 0) {
+			clk_disable_unprepare(irs1125->xclk);
+			dev_err(&client->dev,
+				"Camera not available, check Power\n");
+			goto out;
+		}
+	} else if (!on && irs1125->power_count == 1) {
+		gpiod_set_value_cansleep(irs1125->reset, 0);
+	}
+
+	/* Update the power count. */
+	irs1125->power_count += on ? 1 : -1;
+	WARN_ON(irs1125->power_count < 0);
+
+out:
+	mutex_unlock(&irs1125->lock);
+
+	return ret;
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int irs1125_sensor_get_register(struct v4l2_subdev *sd,
+				       struct v4l2_dbg_register *reg)
+{
+	u16 val;
+	int ret;
+
+	ret = irs1125_read(sd, reg->reg & 0xffff, &val);
+	if (ret < 0)
+		return ret;
+
+	reg->val = val;
+	reg->size = 1;
+
+	return 0;
+}
+
+static int irs1125_sensor_set_register(struct v4l2_subdev *sd,
+				       const struct v4l2_dbg_register *reg)
+{
+	return irs1125_write(sd, reg->reg & 0xffff, reg->val & 0xffff);
+}
+#endif
+
+static const struct v4l2_subdev_core_ops irs1125_subdev_core_ops = {
+	.s_power = irs1125_sensor_power,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.g_register = irs1125_sensor_get_register,
+	.s_register = irs1125_sensor_set_register,
+#endif
+};
+
+static int irs1125_s_stream(struct v4l2_subdev *sd, int enable)
+{
+	if (enable)
+		return irs1125_stream_on(sd);
+	else
+		return irs1125_stream_off(sd);
+}
+
+static const struct v4l2_subdev_video_ops irs1125_subdev_video_ops = {
+	.s_stream = irs1125_s_stream,
+};
+
+static int irs1125_enum_mbus_code(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *sd_state,
+				  struct v4l2_subdev_mbus_code_enum *code)
+{
+	if (code->index > 0)
+		return -EINVAL;
+
+	code->code = MEDIA_BUS_FMT_Y12_1X12;
+
+	return 0;
+}
+
+static int irs1125_set_get_fmt(struct v4l2_subdev *sd,
+			       struct v4l2_subdev_state *sd_state,
+			       struct v4l2_subdev_format *format)
+{
+	struct v4l2_mbus_framefmt *fmt = &format->format;
+	struct irs1125 *irs1125 = to_state(sd);
+
+	if (format->pad != 0)
+		return -EINVAL;
+
+	/* Only one format is supported, so return that */
+	memset(fmt, 0, sizeof(*fmt));
+	fmt->code = MEDIA_BUS_FMT_Y12_1X12;
+	fmt->colorspace = V4L2_COLORSPACE_RAW;
+	fmt->field = V4L2_FIELD_NONE;
+	fmt->width = IRS1125_WINDOW_WIDTH_DEF;
+	fmt->height = IRS1125_WINDOW_HEIGHT_DEF * irs1125->num_seq;
+
+	return 0;
+}
+
+static const struct v4l2_subdev_pad_ops irs1125_subdev_pad_ops = {
+	.enum_mbus_code = irs1125_enum_mbus_code,
+	.set_fmt = irs1125_set_get_fmt,
+	.get_fmt = irs1125_set_get_fmt,
+};
+
+static const struct v4l2_subdev_ops irs1125_subdev_ops = {
+	.core = &irs1125_subdev_core_ops,
+	.video = &irs1125_subdev_video_ops,
+	.pad = &irs1125_subdev_pad_ops,
+};
+
+static int irs1125_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct irs1125 *dev = container_of(ctrl->handler,
+					struct irs1125, ctrl_handler);
+	struct i2c_client *client = v4l2_get_subdevdata(&dev->sd);
+	int err = 0, i;
+
+	switch (ctrl->id) {
+	case IRS1125_CID_SAFE_RECONFIG_S0_EXPO:
+	case IRS1125_CID_SAFE_RECONFIG_S0_FRAME:
+	case IRS1125_CID_SAFE_RECONFIG_S1_EXPO:
+	case IRS1125_CID_SAFE_RECONFIG_S1_FRAME:
+	case IRS1125_CID_SAFE_RECONFIG_S2_EXPO:
+	case IRS1125_CID_SAFE_RECONFIG_S2_FRAME:
+	case IRS1125_CID_SAFE_RECONFIG_S3_EXPO:
+	case IRS1125_CID_SAFE_RECONFIG_S3_FRAME:
+	case IRS1125_CID_SAFE_RECONFIG_S4_EXPO:
+	case IRS1125_CID_SAFE_RECONFIG_S4_FRAME:
+	case IRS1125_CID_SAFE_RECONFIG_S5_EXPO:
+	case IRS1125_CID_SAFE_RECONFIG_S5_FRAME:
+	case IRS1125_CID_SAFE_RECONFIG_S6_EXPO:
+	case IRS1125_CID_SAFE_RECONFIG_S6_FRAME:
+	case IRS1125_CID_SAFE_RECONFIG_S7_EXPO:
+	case IRS1125_CID_SAFE_RECONFIG_S7_FRAME:
+	case IRS1125_CID_SAFE_RECONFIG_S8_EXPO:
+	case IRS1125_CID_SAFE_RECONFIG_S8_FRAME:
+	case IRS1125_CID_SAFE_RECONFIG_S9_EXPO:
+	case IRS1125_CID_SAFE_RECONFIG_S9_FRAME:
+	case IRS1125_CID_SAFE_RECONFIG_S10_EXPO:
+	case IRS1125_CID_SAFE_RECONFIG_S10_FRAME:
+	case IRS1125_CID_SAFE_RECONFIG_S11_EXPO:
+	case IRS1125_CID_SAFE_RECONFIG_S11_FRAME:
+	case IRS1125_CID_SAFE_RECONFIG_S12_EXPO:
+	case IRS1125_CID_SAFE_RECONFIG_S12_FRAME:
+	case IRS1125_CID_SAFE_RECONFIG_S13_EXPO:
+	case IRS1125_CID_SAFE_RECONFIG_S13_FRAME:
+	case IRS1125_CID_SAFE_RECONFIG_S14_EXPO:
+	case IRS1125_CID_SAFE_RECONFIG_S14_FRAME:
+	case IRS1125_CID_SAFE_RECONFIG_S15_EXPO:
+	case IRS1125_CID_SAFE_RECONFIG_S15_FRAME:
+	case IRS1125_CID_SAFE_RECONFIG_S16_EXPO:
+	case IRS1125_CID_SAFE_RECONFIG_S16_FRAME:
+	case IRS1125_CID_SAFE_RECONFIG_S17_EXPO:
+	case IRS1125_CID_SAFE_RECONFIG_S17_FRAME:
+	case IRS1125_CID_SAFE_RECONFIG_S18_EXPO:
+	case IRS1125_CID_SAFE_RECONFIG_S18_FRAME:
+	case IRS1125_CID_SAFE_RECONFIG_S19_EXPO:
+	case IRS1125_CID_SAFE_RECONFIG_S19_FRAME: {
+		unsigned int offset = ctrl->id -
+			IRS1125_CID_SAFE_RECONFIG_S0_EXPO;
+
+		err = irs1125_write(&dev->sd,
+				    IRS1125_REG_SAFE_RECONFIG + offset,
+				    ctrl->val);
+		break;
+	}
+	case IRS1125_CID_MOD_PLL: {
+		struct irs1125_mod_pll *mod_new;
+
+		if (dev->mod_pll_init)
+			break;
+
+		mod_new = (struct irs1125_mod_pll *)ctrl->p_new.p;
+		for (i = 0; i < IRS1125_NUM_MOD_PLLS; i++) {
+			unsigned int pll_offset, ssc_offset;
+
+			pll_offset = i * 3;
+			ssc_offset = i * 5;
+
+			err = irs1125_write(&dev->sd, 0xC3A0 + pll_offset,
+					    mod_new[i].pllcfg1);
+			if (err < 0)
+				break;
+
+			err = irs1125_write(&dev->sd, 0xC3A1 + pll_offset,
+					    mod_new[i].pllcfg2);
+			if (err < 0)
+				break;
+
+			err = irs1125_write(&dev->sd, 0xC3A2 + pll_offset,
+					    mod_new[i].pllcfg3);
+			if (err < 0)
+				break;
+
+			err = irs1125_write(&dev->sd, 0xC24C + ssc_offset,
+					    mod_new[i].pllcfg4);
+			if (err < 0)
+				break;
+
+			err = irs1125_write(&dev->sd, 0xC24D + ssc_offset,
+					    mod_new[i].pllcfg5);
+			if (err < 0)
+				break;
+
+			err = irs1125_write(&dev->sd, 0xC24E + ssc_offset,
+					    mod_new[i].pllcfg6);
+			if (err < 0)
+				break;
+
+			err = irs1125_write(&dev->sd, 0xC24F + ssc_offset,
+					    mod_new[i].pllcfg7);
+			if (err < 0)
+				break;
+
+			err = irs1125_write(&dev->sd, 0xC250 + ssc_offset,
+					    mod_new[i].pllcfg8);
+			if (err < 0)
+				break;
+		}
+		break;
+	}
+	case IRS1125_CID_SEQ_CONFIG: {
+		struct irs1125_seq_cfg *cfg_new;
+
+		cfg_new = (struct irs1125_seq_cfg *)ctrl->p_new.p;
+		for (i = 0; i < IRS1125_NUM_SEQ_ENTRIES; i++) {
+			unsigned int seq_offset = i * 4;
+			u16 addr, val;
+
+			addr = IRS1125_REG_DMEM_SHADOW + seq_offset;
+			val = cfg_new[i].exposure;
+			err = irs1125_write(&dev->sd, addr, val);
+			if (err < 0)
+				break;
+
+			addr = IRS1125_REG_DMEM_SHADOW + 1 + seq_offset;
+			val = cfg_new[i].framerate;
+			err = irs1125_write(&dev->sd, addr, val);
+			if (err < 0)
+				break;
+
+			addr = IRS1125_REG_DMEM_SHADOW + 2 + seq_offset;
+			val = cfg_new[i].ps;
+			err = irs1125_write(&dev->sd, addr, val);
+			if (err < 0)
+				break;
+
+			addr = IRS1125_REG_DMEM_SHADOW + 3 + seq_offset;
+			val = cfg_new[i].pll;
+			err = irs1125_write(&dev->sd, addr, val);
+			if (err < 0)
+				break;
+		}
+		break;
+	}
+	case IRS1125_CID_NUM_SEQS:
+		err = irs1125_write(&dev->sd, 0xA88D, ctrl->val - 1);
+		if (err >= 0)
+			dev->num_seq = ctrl->val;
+		break;
+	case IRS1125_CID_CONTINUOUS_TRIG:
+		if (ctrl->val == 0)
+			err = irs1125_write(&dev->sd, 0xA87C, 0);
+		else
+			err = irs1125_write(&dev->sd, 0xA87C, 1);
+		break;
+	case IRS1125_CID_TRIGGER:
+		if (ctrl->val != 0) {
+			err = irs1125_write(&dev->sd, 0xA87C, 1);
+			if (err >= 0)
+				err = irs1125_write(&dev->sd, 0xA87C, 0);
+		}
+		break;
+	case IRS1125_CID_RECONFIG:
+		if (ctrl->val != 0)
+			err = irs1125_write(&dev->sd, 0xA87A, 1);
+		break;
+	case IRS1125_CID_ILLU_ON:
+		if (ctrl->val == 0)
+			err = irs1125_write(&dev->sd, 0xA892, 0x377);
+		else
+			err = irs1125_write(&dev->sd, 0xA892, 0x355);
+		break;
+	default:
+		break;
+	}
+
+	if (err < 0)
+		dev_err(&client->dev, "Error executing control ID: %d, val %d, err %d",
+			ctrl->id, ctrl->val, err);
+	else
+		err = 0;
+
+	return err;
+}
+
+static const struct v4l2_ctrl_ops irs1125_ctrl_ops = {
+	.s_ctrl = irs1125_s_ctrl,
+};
+
+static const struct v4l2_ctrl_config irs1125_custom_ctrls[] = {
+	{
+		.ops = &irs1125_ctrl_ops,
+		.id = IRS1125_CID_NUM_SEQS,
+		.name = "Change number of sequences",
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.flags = V4L2_CTRL_FLAG_MODIFY_LAYOUT,
+		.min = 1,
+		.max = 20,
+		.step = 1,
+		.def = 5,
+	}, {
+		.ops = &irs1125_ctrl_ops,
+		.id = IRS1125_CID_MOD_PLL,
+		.name = "Reconfigure modulation PLLs",
+		.type = V4L2_CTRL_TYPE_U16,
+		.flags = V4L2_CTRL_FLAG_HAS_PAYLOAD,
+		.min = 0,
+		.max = U16_MAX,
+		.step = 1,
+		.def = 0,
+		.elem_size = sizeof(u16),
+		.dims = {sizeof(struct irs1125_mod_pll) / sizeof(u16),
+			IRS1125_NUM_MOD_PLLS}
+	}, {
+		.ops = &irs1125_ctrl_ops,
+		.id = IRS1125_CID_SEQ_CONFIG,
+		.name = "Change sequence settings",
+		.type = V4L2_CTRL_TYPE_U16,
+		.flags = V4L2_CTRL_FLAG_HAS_PAYLOAD,
+		.min = 0,
+		.max = U16_MAX,
+		.step = 1,
+		.def = 0,
+		.elem_size = sizeof(u16),
+		.dims = {sizeof(struct irs1125_seq_cfg) / sizeof(u16),
+			IRS1125_NUM_SEQ_ENTRIES}
+	}, {
+		.ops = &irs1125_ctrl_ops,
+		.id = IRS1125_CID_CONTINUOUS_TRIG,
+		.name = "Enable/disable continuous trigger",
+		.type = V4L2_CTRL_TYPE_BOOLEAN,
+		.flags = V4L2_CTRL_FLAG_EXECUTE_ON_WRITE,
+		.min = 0,
+		.max = 1,
+		.step = 1,
+		.def = 0
+	}, {
+		.ops = &irs1125_ctrl_ops,
+		.id = IRS1125_CID_TRIGGER,
+		.name = "Capture a single sequence",
+		.type = V4L2_CTRL_TYPE_BOOLEAN,
+		.flags = V4L2_CTRL_FLAG_EXECUTE_ON_WRITE,
+		.min = 0,
+		.max = 1,
+		.step = 1,
+		.def = 0
+	}, {
+		.ops = &irs1125_ctrl_ops,
+		.id = IRS1125_CID_RECONFIG,
+		.name = "Trigger imager reconfiguration",
+		.type = V4L2_CTRL_TYPE_BOOLEAN,
+		.flags = V4L2_CTRL_FLAG_EXECUTE_ON_WRITE,
+		.min = 0,
+		.max = 1,
+		.step = 1,
+		.def = 0
+	}, {
+		.ops = &irs1125_ctrl_ops,
+		.id = IRS1125_CID_ILLU_ON,
+		.name = "Turn illu on or off",
+		.type = V4L2_CTRL_TYPE_BOOLEAN,
+		.flags = V4L2_CTRL_FLAG_EXECUTE_ON_WRITE,
+		.min = 0,
+		.max = 1,
+		.step = 1,
+		.def = 1
+	}, {
+		.ops = &irs1125_ctrl_ops,
+		.id = IRS1125_CID_IDENT0,
+		.name = "Get ident 0 information",
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.flags = V4L2_CTRL_FLAG_READ_ONLY,
+		.min = S32_MIN,
+		.max = S32_MAX,
+		.step = 1,
+		.def = 0
+	}, {
+		.ops = &irs1125_ctrl_ops,
+		.id = IRS1125_CID_IDENT1,
+		.name = "Get ident 1 information",
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.flags = V4L2_CTRL_FLAG_READ_ONLY,
+		.min = S32_MIN,
+		.max = S32_MAX,
+		.step = 1,
+		.def = 0
+	}, {
+		.ops = &irs1125_ctrl_ops,
+		.id = IRS1125_CID_IDENT2,
+		.name = "Get ident 2 information",
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.flags = V4L2_CTRL_FLAG_READ_ONLY,
+		.min = S32_MIN,
+		.max = S32_MAX,
+		.step = 1,
+		.def = 0
+	}
+};
+
+static int irs1125_detect(struct v4l2_subdev *sd)
+{
+	u16 read;
+	int ret;
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	ret = irs1125_read(sd, IRS1125_REG_DESIGN_STEP, &read);
+	if (ret < 0) {
+		dev_err(&client->dev, "error reading from i2c\n");
+		return ret;
+	}
+
+	if (read != IRS1125_DESIGN_STEP_EXPECTED) {
+		dev_err(&client->dev, "Design step expected 0x%x got 0x%x",
+			IRS1125_DESIGN_STEP_EXPECTED, read);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int irs1125_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+	struct v4l2_mbus_framefmt *format =
+	v4l2_subdev_state_get_format(fh->state, 0);
+
+	format->code = MEDIA_BUS_FMT_Y12_1X12;
+	format->width = IRS1125_WINDOW_WIDTH_DEF;
+	format->height = IRS1125_WINDOW_HEIGHT_DEF;
+	format->field = V4L2_FIELD_NONE;
+	format->colorspace = V4L2_COLORSPACE_RAW;
+
+	return 0;
+}
+
+static const struct v4l2_subdev_internal_ops irs1125_subdev_internal_ops = {
+	.open = irs1125_open,
+};
+
+static int irs1125_ctrls_init(struct irs1125 *sensor, struct device *dev)
+{
+	struct v4l2_ctrl *ctrl;
+	int err, i;
+	struct v4l2_ctrl_handler *hdl = &sensor->ctrl_handler;
+	struct v4l2_ctrl_config ctrl_cfg = {
+		.ops = &irs1125_ctrl_ops,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.min = 0,
+		.max = U16_MAX,
+		.step = 1,
+		.def = 0x1000
+	};
+
+	v4l2_ctrl_handler_init(hdl, ARRAY_SIZE(irs1125_custom_ctrls));
+
+	for (i = 0; i < ARRAY_SIZE(irs1125_custom_ctrls); i++)	{
+		ctrl = v4l2_ctrl_new_custom(hdl, &irs1125_custom_ctrls[i],
+					    NULL);
+		if (!ctrl)
+			dev_err(dev, "Failed to init custom control %s\n",
+				irs1125_custom_ctrls[i].name);
+		else if (irs1125_custom_ctrls[i].id == IRS1125_CID_NUM_SEQS)
+			sensor->ctrl_numseq = ctrl;
+		else if (irs1125_custom_ctrls[i].id == IRS1125_CID_MOD_PLL)
+			sensor->ctrl_modplls = ctrl;
+	}
+
+	if (hdl->error)	{
+		dev_err(dev, "Error %d adding controls\n", hdl->error);
+		err = hdl->error;
+		goto error_ctrls;
+	}
+
+	for (i = 0; i < IRS1125_NUM_SEQ_ENTRIES; i++) {
+		ctrl_cfg.name = expo_ctrl_names[i];
+		ctrl_cfg.id = IRS1125_CID_SAFE_RECONFIG_S0_EXPO + i * 2;
+		ctrl = v4l2_ctrl_new_custom(hdl, &ctrl_cfg,
+					    NULL);
+		if (!ctrl)
+			dev_err(dev, "Failed to init exposure control %s\n",
+				ctrl_cfg.name);
+	}
+
+	ctrl_cfg.def = 0;
+	for (i = 0; i < IRS1125_NUM_SEQ_ENTRIES; i++) {
+		ctrl_cfg.name = frame_ctrl_names[i];
+		ctrl_cfg.id = IRS1125_CID_SAFE_RECONFIG_S0_FRAME + i * 2;
+		ctrl = v4l2_ctrl_new_custom(hdl, &ctrl_cfg,
+					    NULL);
+		if (!ctrl)
+			dev_err(dev, "Failed to init framerate control %s\n",
+				ctrl_cfg.name);
+	}
+
+	sensor->sd.ctrl_handler = hdl;
+	return 0;
+
+error_ctrls:
+	v4l2_ctrl_handler_free(&sensor->ctrl_handler);
+	return -err;
+}
+
+static int irs1125_ident_setup(struct irs1125 *sensor, struct device *dev)
+{
+	int ret;
+	struct v4l2_ctrl *ctrl;
+	struct v4l2_subdev *sd;
+	u16 read;
+
+	sd = &sensor->sd;
+
+	ctrl = v4l2_ctrl_find(&sensor->ctrl_handler, IRS1125_CID_IDENT0);
+	if (!ctrl) {
+		dev_err(dev, "could not find device ctrl.\n");
+		return -EINVAL;
+	}
+
+	ret = irs1125_read(sd, IRS1125_REG_EFUSEVAL2, &read);
+	if (ret < 0) {
+		dev_err(dev, "error reading from i2c\n");
+		return -EIO;
+	}
+
+	v4l2_ctrl_s_ctrl(ctrl, read);
+
+	ctrl = v4l2_ctrl_find(&sensor->ctrl_handler, IRS1125_CID_IDENT1);
+	if (!ctrl) {
+		dev_err(dev, "could not find device ctrl.\n");
+		return -EINVAL;
+	}
+
+	ret = irs1125_read(sd, IRS1125_REG_EFUSEVAL3, &read);
+	if (ret < 0) {
+		dev_err(dev, "error reading from i2c\n");
+		return -EIO;
+	}
+
+	v4l2_ctrl_s_ctrl(ctrl, read);
+
+	ctrl = v4l2_ctrl_find(&sensor->ctrl_handler, IRS1125_CID_IDENT2);
+	if (!ctrl) {
+		dev_err(dev, "could not find device ctrl.\n");
+		return -EINVAL;
+	}
+
+	ret = irs1125_read(sd, IRS1125_REG_EFUSEVAL4, &read);
+	if (ret < 0) {
+		dev_err(dev, "error reading from i2c\n");
+		return -EIO;
+	}
+	v4l2_ctrl_s_ctrl(ctrl, read & 0xFFFC);
+
+	return 0;
+}
+
+static int irs1125_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct irs1125 *sensor;
+	int ret;
+	struct fwnode_handle *endpoint;
+	u32 xclk_freq;
+	int gpio_num;
+
+	sensor = devm_kzalloc(dev, sizeof(*sensor), GFP_KERNEL);
+	if (!sensor)
+		return -ENOMEM;
+
+	v4l2_i2c_subdev_init(&sensor->sd, client, &irs1125_subdev_ops);
+
+	/* Get CSI2 bus config */
+	endpoint = fwnode_graph_get_next_endpoint(dev_fwnode(dev),
+						  NULL);
+	if (!endpoint) {
+		dev_err(dev, "endpoint node not found\n");
+		return -EINVAL;
+	}
+
+	ret = v4l2_fwnode_endpoint_parse(endpoint, &sensor->ep);
+	fwnode_handle_put(endpoint);
+	if (ret) {
+		dev_err(dev, "Could not parse endpoint\n");
+		return ret;
+	}
+
+	/* get system clock (xclk) */
+	sensor->xclk = devm_clk_get(dev, NULL);
+	if (IS_ERR(sensor->xclk)) {
+		dev_err(dev, "could not get xclk");
+		return PTR_ERR(sensor->xclk);
+	}
+
+	xclk_freq = clk_get_rate(sensor->xclk);
+	if (xclk_freq != 26000000) {
+		dev_err(dev, "Unsupported clock frequency: %u\n", xclk_freq);
+		return -EINVAL;
+	}
+
+	sensor->num_seq = 5;
+
+	/* Request the power down GPIO */
+	sensor->reset = devm_gpiod_get(&client->dev, "pwdn",
+				       GPIOD_OUT_LOW);
+
+	if (IS_ERR(sensor->reset)) {
+		dev_err(dev, "could not get reset");
+		return PTR_ERR(sensor->reset);
+	}
+
+	gpio_num = desc_to_gpio(sensor->reset);
+	dev_dbg(&client->dev, "reset on GPIO num %d\n", gpio_num);
+
+	mutex_init(&sensor->lock);
+
+	ret = irs1125_ctrls_init(sensor, dev);
+	if (ret < 0)
+		goto mutex_remove;
+
+	sensor->sd.internal_ops = &irs1125_subdev_internal_ops;
+	sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+	sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+	sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
+	ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad);
+	if (ret < 0)
+		goto mutex_remove;
+
+	gpiod_set_value_cansleep(sensor->reset, 1);
+	msleep(RESET_ACTIVE_DELAY_MS);
+
+	ret = irs1125_detect(&sensor->sd);
+	if (ret < 0)
+		goto error;
+
+	ret = irs1125_ident_setup(sensor, dev);
+	if (ret < 0)
+		goto error;
+
+	gpiod_set_value_cansleep(sensor->reset, 0);
+
+	ret = v4l2_async_register_subdev(&sensor->sd);
+	if (ret < 0)
+		goto error;
+
+	dev_dbg(dev, "Infineon IRS1125 camera driver probed\n");
+
+	return 0;
+
+error:
+	media_entity_cleanup(&sensor->sd.entity);
+mutex_remove:
+	mutex_destroy(&sensor->lock);
+	return ret;
+}
+
+static void irs1125_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct irs1125 *irs1125 = to_state(sd);
+
+	v4l2_async_unregister_subdev(&irs1125->sd);
+	media_entity_cleanup(&irs1125->sd.entity);
+	v4l2_device_unregister_subdev(sd);
+	mutex_destroy(&irs1125->lock);
+	v4l2_ctrl_handler_free(&irs1125->ctrl_handler);
+}
+
+#if IS_ENABLED(CONFIG_OF)
+static const struct of_device_id irs1125_of_match[] = {
+	{ .compatible = "infineon,irs1125" },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, irs1125_of_match);
+#endif
+
+static struct i2c_driver irs1125_driver = {
+	.driver = {
+		.of_match_table = of_match_ptr(irs1125_of_match),
+		.name	 = SENSOR_NAME,
+	},
+	.probe		= irs1125_probe,
+	.remove		= irs1125_remove,
+};
+
+module_i2c_driver(irs1125_driver);
+
+MODULE_AUTHOR("Markus Proeller <markus.proeller@pieye.org>");
+MODULE_DESCRIPTION("Infineon irs1125 sensor driver");
+MODULE_LICENSE("GPL v2");
+
diff --git a/drivers/media/i2c/irs1125.h b/drivers/media/i2c/irs1125.h
new file mode 100644
index 000000000000..96d676123d5e
--- /dev/null
+++ b/drivers/media/i2c/irs1125.h
@@ -0,0 +1,95 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * A V4L2 driver for Infineon IRS1125 TOF cameras.
+ * Copyright (C) 2018, pieye GmbH
+ *
+ * Based on V4L2 OmniVision OV5647 Image Sensor driver
+ * Copyright (C) 2016 Ramiro Oliveira <roliveir@synopsys.com>
+ *
+ * DT / fwnode changes, and GPIO control taken from ov5640.c
+ * Copyright (C) 2011-2013 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright (C) 2014-2017 Mentor Graphics Inc.
+ *
+ */
+
+#ifndef IRS1125_H
+#define IRS1125_H
+
+#include <linux/v4l2-controls.h>
+#include <linux/types.h>
+
+#define IRS1125_NUM_SEQ_ENTRIES 20
+#define IRS1125_NUM_MOD_PLLS 4
+
+#define IRS1125_CID_CUSTOM_BASE			(V4L2_CID_USER_BASE | 0xf000)
+#define IRS1125_CID_CONTINUOUS_TRIG		(IRS1125_CID_CUSTOM_BASE + 1)
+#define IRS1125_CID_TRIGGER			(IRS1125_CID_CUSTOM_BASE + 2)
+#define IRS1125_CID_RECONFIG			(IRS1125_CID_CUSTOM_BASE + 3)
+#define IRS1125_CID_ILLU_ON			(IRS1125_CID_CUSTOM_BASE + 4)
+#define IRS1125_CID_NUM_SEQS			(IRS1125_CID_CUSTOM_BASE + 5)
+#define IRS1125_CID_MOD_PLL			(IRS1125_CID_CUSTOM_BASE + 6)
+#define IRS1125_CID_SEQ_CONFIG			(IRS1125_CID_CUSTOM_BASE + 7)
+#define IRS1125_CID_IDENT0			(IRS1125_CID_CUSTOM_BASE + 8)
+#define IRS1125_CID_IDENT1			(IRS1125_CID_CUSTOM_BASE + 9)
+#define IRS1125_CID_IDENT2			(IRS1125_CID_CUSTOM_BASE + 10)
+#define IRS1125_CID_SAFE_RECONFIG_S0_EXPO	(IRS1125_CID_CUSTOM_BASE + 11)
+#define IRS1125_CID_SAFE_RECONFIG_S0_FRAME	(IRS1125_CID_CUSTOM_BASE + 12)
+#define IRS1125_CID_SAFE_RECONFIG_S1_EXPO	(IRS1125_CID_CUSTOM_BASE + 13)
+#define IRS1125_CID_SAFE_RECONFIG_S1_FRAME	(IRS1125_CID_CUSTOM_BASE + 14)
+#define IRS1125_CID_SAFE_RECONFIG_S2_EXPO	(IRS1125_CID_CUSTOM_BASE + 15)
+#define IRS1125_CID_SAFE_RECONFIG_S2_FRAME	(IRS1125_CID_CUSTOM_BASE + 16)
+#define IRS1125_CID_SAFE_RECONFIG_S3_EXPO	(IRS1125_CID_CUSTOM_BASE + 17)
+#define IRS1125_CID_SAFE_RECONFIG_S3_FRAME	(IRS1125_CID_CUSTOM_BASE + 18)
+#define IRS1125_CID_SAFE_RECONFIG_S4_EXPO	(IRS1125_CID_CUSTOM_BASE + 19)
+#define IRS1125_CID_SAFE_RECONFIG_S4_FRAME	(IRS1125_CID_CUSTOM_BASE + 20)
+#define IRS1125_CID_SAFE_RECONFIG_S5_EXPO	(IRS1125_CID_CUSTOM_BASE + 21)
+#define IRS1125_CID_SAFE_RECONFIG_S5_FRAME	(IRS1125_CID_CUSTOM_BASE + 22)
+#define IRS1125_CID_SAFE_RECONFIG_S6_EXPO	(IRS1125_CID_CUSTOM_BASE + 23)
+#define IRS1125_CID_SAFE_RECONFIG_S6_FRAME	(IRS1125_CID_CUSTOM_BASE + 24)
+#define IRS1125_CID_SAFE_RECONFIG_S7_EXPO	(IRS1125_CID_CUSTOM_BASE + 25)
+#define IRS1125_CID_SAFE_RECONFIG_S7_FRAME	(IRS1125_CID_CUSTOM_BASE + 26)
+#define IRS1125_CID_SAFE_RECONFIG_S8_EXPO	(IRS1125_CID_CUSTOM_BASE + 27)
+#define IRS1125_CID_SAFE_RECONFIG_S8_FRAME	(IRS1125_CID_CUSTOM_BASE + 28)
+#define IRS1125_CID_SAFE_RECONFIG_S9_EXPO	(IRS1125_CID_CUSTOM_BASE + 29)
+#define IRS1125_CID_SAFE_RECONFIG_S9_FRAME	(IRS1125_CID_CUSTOM_BASE + 30)
+#define IRS1125_CID_SAFE_RECONFIG_S10_EXPO	(IRS1125_CID_CUSTOM_BASE + 31)
+#define IRS1125_CID_SAFE_RECONFIG_S10_FRAME	(IRS1125_CID_CUSTOM_BASE + 32)
+#define IRS1125_CID_SAFE_RECONFIG_S11_EXPO	(IRS1125_CID_CUSTOM_BASE + 33)
+#define IRS1125_CID_SAFE_RECONFIG_S11_FRAME	(IRS1125_CID_CUSTOM_BASE + 34)
+#define IRS1125_CID_SAFE_RECONFIG_S12_EXPO	(IRS1125_CID_CUSTOM_BASE + 35)
+#define IRS1125_CID_SAFE_RECONFIG_S12_FRAME	(IRS1125_CID_CUSTOM_BASE + 36)
+#define IRS1125_CID_SAFE_RECONFIG_S13_EXPO	(IRS1125_CID_CUSTOM_BASE + 37)
+#define IRS1125_CID_SAFE_RECONFIG_S13_FRAME	(IRS1125_CID_CUSTOM_BASE + 38)
+#define IRS1125_CID_SAFE_RECONFIG_S14_EXPO	(IRS1125_CID_CUSTOM_BASE + 39)
+#define IRS1125_CID_SAFE_RECONFIG_S14_FRAME	(IRS1125_CID_CUSTOM_BASE + 40)
+#define IRS1125_CID_SAFE_RECONFIG_S15_EXPO	(IRS1125_CID_CUSTOM_BASE + 41)
+#define IRS1125_CID_SAFE_RECONFIG_S15_FRAME	(IRS1125_CID_CUSTOM_BASE + 42)
+#define IRS1125_CID_SAFE_RECONFIG_S16_EXPO	(IRS1125_CID_CUSTOM_BASE + 43)
+#define IRS1125_CID_SAFE_RECONFIG_S16_FRAME	(IRS1125_CID_CUSTOM_BASE + 44)
+#define IRS1125_CID_SAFE_RECONFIG_S17_EXPO	(IRS1125_CID_CUSTOM_BASE + 45)
+#define IRS1125_CID_SAFE_RECONFIG_S17_FRAME	(IRS1125_CID_CUSTOM_BASE + 46)
+#define IRS1125_CID_SAFE_RECONFIG_S18_EXPO	(IRS1125_CID_CUSTOM_BASE + 47)
+#define IRS1125_CID_SAFE_RECONFIG_S18_FRAME	(IRS1125_CID_CUSTOM_BASE + 48)
+#define IRS1125_CID_SAFE_RECONFIG_S19_EXPO	(IRS1125_CID_CUSTOM_BASE + 49)
+#define IRS1125_CID_SAFE_RECONFIG_S19_FRAME	(IRS1125_CID_CUSTOM_BASE + 50)
+
+struct irs1125_seq_cfg {
+	__u16 exposure;
+	__u16 framerate;
+	__u16 ps;
+	__u16 pll;
+};
+
+struct irs1125_mod_pll {
+	__u16 pllcfg1;
+	__u16 pllcfg2;
+	__u16 pllcfg3;
+	__u16 pllcfg4;
+	__u16 pllcfg5;
+	__u16 pllcfg6;
+	__u16 pllcfg7;
+	__u16 pllcfg8;
+};
+
+#endif /* IRS1125 */
+
diff --git a/drivers/media/i2c/mira220.c b/drivers/media/i2c/mira220.c
new file mode 100644
index 000000000000..69dcc969e5d3
--- /dev/null
+++ b/drivers/media/i2c/mira220.c
@@ -0,0 +1,1998 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * A V4L2 driver for ams MIRA220 cameras.
+ * Copyright (C) 2022, ams-OSRAM
+ *
+ * Based on Sony IMX219 camera driver
+ * Copyright (C) 2019, Raspberry Pi (Trading) Ltd
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-mediabus.h>
+#include <media/v4l2-cci.h>
+#include <linux/unaligned.h>
+
+/* Active pixel array is 1600 (H) x 1400 (V) pixels.
+ * Physical resolution including buffer pixels: 1642 (H) x 1464 (V) pixels.
+ */
+#define MIRA220_NATIVE_WIDTH 1642U
+#define MIRA220_NATIVE_HEIGHT 1464U
+#define MIRA220_PIXEL_ARRAY_LEFT 21U
+#define MIRA220_PIXEL_ARRAY_TOP 32U
+#define MIRA220_PIXEL_ARRAY_WIDTH 1600U
+#define MIRA220_PIXEL_ARRAY_HEIGHT 1400U
+
+/* Mira220 does not support analog gain. */
+#define MIRA220_ANALOG_GAIN_MIN 1
+#define MIRA220_ANALOG_GAIN_MAX 1
+#define MIRA220_ANALOG_GAIN_STEP 1
+#define MIRA220_ANALOG_GAIN_DEFAULT MIRA220_ANALOG_GAIN_MIN
+
+/* Bit depth */
+#define MIRA220_BIT_DEPTH_REG CCI_REG8(0x209E)
+#define MIRA220_BIT_DEPTH_12_BIT 0x02
+#define MIRA220_BIT_DEPTH_10_BIT 0x04
+#define MIRA220_BIT_DEPTH_8_BIT 0x06
+#define MIRA220_CSI_DATA_TYPE_REG CCI_REG8(0x208D)
+#define MIRA220_CSI_DATA_TYPE_12_BIT 0x04
+#define MIRA220_CSI_DATA_TYPE_10_BIT 0x02
+#define MIRA220_CSI_DATA_TYPE_8_BIT 0x01
+
+/* Imager state master/slave registers */
+#define MIRA220_IMAGER_STATE_REG CCI_REG8(0x1003)
+#define MIRA220_IMAGER_STATE_STOP_AT_ROW 0x02
+#define MIRA220_IMAGER_STATE_STOP_AT_FRAME 0x04
+#define MIRA220_IMAGER_STATE_MASTER_CONTROL 0x10
+#define MIRA220_IMAGER_STATE_SLAVE_CONTROL 0x08
+
+/* Start image acquisition */
+#define MIRA220_IMAGER_RUN_REG CCI_REG8(0x10F0)
+#define MIRA220_IMAGER_RUN_START 0x01
+#define MIRA220_IMAGER_RUN_STOP 0x00
+
+/* Continuous running, not limited to nr of frames. */
+#define MIRA220_IMAGER_RUN_CONT_REG CCI_REG8(0x1002)
+#define MIRA220_IMAGER_RUN_CONT_ENABLE 0x04
+#define MIRA220_IMAGER_RUN_CONT_DISABLE 0x00
+
+/* Exposure time is indicated in number of rows */
+#define MIRA220_EXP_TIME_REG CCI_REG16_LE(0x100C)
+
+/* Vertical Blank */
+#define MIRA220_VBLANK_REG CCI_REG16_LE(0x1012)
+
+/* Horizontal flip */
+#define MIRA220_HFLIP_REG CCI_REG8(0x209C)
+#define MIRA220_HFLIP_ENABLE_MIRROR 1
+#define MIRA220_HFLIP_DISABLE_MIRROR 0
+
+/* Vertical flip */
+#define MIRA220_VFLIP_REG CCI_REG8(0x1095)
+#define MIRA220_VFLIP_ENABLE_FLIP 1
+#define MIRA220_VFLIP_DISABLE_FLIP 0
+
+/* OTP control */
+#define MIRA220_OTP_CMD_REG CCI_REG8(0x0080)
+#define MIRA220_OTP_CMD_UP 0x4
+#define MIRA220_OTP_CMD_DOWN 0x8
+
+/* Global sampling time */
+#define MIRA220_GLOB_NUM_CLK_CYCLES 1928
+
+/* External clock frequency is 38.4 M */
+#define MIRA220_SUPPORTED_XCLK_FREQ 38400000
+
+// Default exposure is adjusted to mode with smallest height
+#define MIRA220_DEFAULT_EXPOSURE 1000
+#define MIRA220_EXPOSURE_MIN 1
+// Power on function timing
+#define MIRA220_XCLR_MIN_DELAY_US 100000
+#define MIRA220_XCLR_DELAY_RANGE_US 30
+
+/* Pixel rate is an artificial value
+ * This value is used for timing calculations
+ * in combination with vblank/hblank
+ */
+#define MIRA220_PIXEL_RATE 384000000 //384M (x10)
+
+#define MIRA220_HBLANK_1600x1400_304 1440
+
+/* Test Pattern */
+#define MIRA220_REG_TEST_PATTERN CCI_REG8(0x2091)
+#define MIRA220_TEST_PATTERN_DISABLE 0x00
+#define MIRA220_TEST_PATTERN_VERTICAL_GRADIENT 0x01
+
+struct mira220_reg {
+	u16 address;
+	u8 val;
+};
+
+struct mira220_reg_list {
+	unsigned int num_of_regs;
+	const struct cci_reg_sequence *regs;
+};
+
+
+/* Mode : resolution and related config&values */
+struct mira220_mode {
+	unsigned int width;
+	unsigned int height;
+	struct v4l2_rect crop;
+	struct mira220_reg_list reg_list;
+	u32 row_length;
+	u32 pixel_rate;
+	u32 min_vblank;
+	u32 max_vblank;
+	u32 hblank;
+	u32 code;
+};
+
+static const struct cci_reg_sequence full_1600_1400_1500_12b_2lanes_reg_new[] = {
+	/* Base configuration*/
+	{ CCI_REG8(0x1003), 0x2 },
+	{ CCI_REG8(0x6006), 0x0 },
+	{ CCI_REG8(0x6012), 0x1 },
+	{ CCI_REG8(0x6013), 0x0 },
+	{ CCI_REG8(0x6006), 0x1 },
+	{ CCI_REG8(0x205D), 0x0 },
+	{ CCI_REG8(0x2063), 0x0 },
+	{ CCI_REG8(0x24DC), 0x13 },
+	{ CCI_REG8(0x24DD), 0x3 },
+	{ CCI_REG8(0x24DE), 0x3 },
+	{ CCI_REG8(0x24DF), 0x0 },
+	{ CCI_REG8(0x4006), 0x8 },
+	{ CCI_REG8(0x401C), 0x6F },
+	{ CCI_REG8(0x204B), 0x3 },
+	{ CCI_REG8(0x205B), 0x64 },
+	{ CCI_REG8(0x205C), 0x0 },
+	{ CCI_REG8(0x4018), 0x3F },
+	{ CCI_REG8(0x403B), 0xB },
+	{ CCI_REG8(0x403E), 0xE },
+	{ CCI_REG8(0x402B), 0x6 },
+	{ CCI_REG8(0x401E), 0x2 },
+	{ CCI_REG8(0x4038), 0x3B },
+	{ CCI_REG8(0x1077), 0x0 },
+	{ CCI_REG8(0x1078), 0x0 },
+	{ CCI_REG8(0x1009), 0x8 },
+	{ CCI_REG8(0x100A), 0x0 },
+	{ CCI_REG8(0x110F), 0x8 },
+	{ CCI_REG8(0x1110), 0x0 },
+	{ CCI_REG8(0x1006), 0x2 },
+	{ CCI_REG8(0x402C), 0x64 },
+	{ CCI_REG8(0x3064), 0x0 },
+	{ CCI_REG8(0x3065), 0xF0 },
+	{ CCI_REG8(0x4013), 0x13 },
+	{ CCI_REG8(0x401F), 0x9 },
+	{ CCI_REG8(0x4020), 0x13 },
+	{ CCI_REG8(0x4044), 0x75 },
+	{ CCI_REG8(0x4027), 0x0 },
+	{ CCI_REG8(0x3215), 0x69 },
+	{ CCI_REG8(0x3216), 0xF },
+	{ CCI_REG8(0x322B), 0x69 },
+	{ CCI_REG8(0x322C), 0xF },
+	{ CCI_REG8(0x4051), 0x80 },
+	{ CCI_REG8(0x4052), 0x10 },
+	{ CCI_REG8(0x4057), 0x80 },
+	{ CCI_REG8(0x4058), 0x10 },
+	{ CCI_REG8(0x3212), 0x59 },
+	{ CCI_REG8(0x4047), 0x8F },
+	{ CCI_REG8(0x4026), 0x10 },
+	{ CCI_REG8(0x4032), 0x53 },
+	{ CCI_REG8(0x4036), 0x17 },
+	{ CCI_REG8(0x50B8), 0xF4 },
+	{ CCI_REG8(0x3016), 0x0 },
+	{ CCI_REG8(0x3017), 0x2C },
+	{ CCI_REG8(0x3018), 0x8C },
+	{ CCI_REG8(0x3019), 0x45 },
+	{ CCI_REG8(0x301A), 0x5 },
+	{ CCI_REG8(0x3013), 0xA },
+	{ CCI_REG8(0x301B), 0x0 },
+	{ CCI_REG8(0x301C), 0x4 },
+	{ CCI_REG8(0x301D), 0x88 },
+	{ CCI_REG8(0x301E), 0x45 },
+	{ CCI_REG8(0x301F), 0x5 },
+	{ CCI_REG8(0x3020), 0x0 },
+	{ CCI_REG8(0x3021), 0x4 },
+	{ CCI_REG8(0x3022), 0x88 },
+	{ CCI_REG8(0x3023), 0x45 },
+	{ CCI_REG8(0x3024), 0x5 },
+	{ CCI_REG8(0x3025), 0x0 },
+	{ CCI_REG8(0x3026), 0x4 },
+	{ CCI_REG8(0x3027), 0x88 },
+	{ CCI_REG8(0x3028), 0x45 },
+	{ CCI_REG8(0x3029), 0x5 },
+	{ CCI_REG8(0x302F), 0x0 },
+	{ CCI_REG8(0x3056), 0x0 },
+	{ CCI_REG8(0x3057), 0x0 },
+	{ CCI_REG8(0x3300), 0x1 },
+	{ CCI_REG8(0x3301), 0x0 },
+	{ CCI_REG8(0x3302), 0xB0 },
+	{ CCI_REG8(0x3303), 0xB0 },
+	{ CCI_REG8(0x3304), 0x16 },
+	{ CCI_REG8(0x3305), 0x15 },
+	{ CCI_REG8(0x3306), 0x1 },
+	{ CCI_REG8(0x3307), 0x0 },
+	{ CCI_REG8(0x3308), 0x30 },
+	{ CCI_REG8(0x3309), 0xA0 },
+	{ CCI_REG8(0x330A), 0x16 },
+	{ CCI_REG8(0x330B), 0x15 },
+	{ CCI_REG8(0x330C), 0x1 },
+	{ CCI_REG8(0x330D), 0x0 },
+	{ CCI_REG8(0x330E), 0x30 },
+	{ CCI_REG8(0x330F), 0xA0 },
+	{ CCI_REG8(0x3310), 0x16 },
+	{ CCI_REG8(0x3311), 0x15 },
+	{ CCI_REG8(0x3312), 0x1 },
+	{ CCI_REG8(0x3313), 0x0 },
+	{ CCI_REG8(0x3314), 0x30 },
+	{ CCI_REG8(0x3315), 0xA0 },
+	{ CCI_REG8(0x3316), 0x16 },
+	{ CCI_REG8(0x3317), 0x15 },
+	{ CCI_REG8(0x3318), 0x1 },
+	{ CCI_REG8(0x3319), 0x0 },
+	{ CCI_REG8(0x331A), 0x30 },
+	{ CCI_REG8(0x331B), 0xA0 },
+	{ CCI_REG8(0x331C), 0x16 },
+	{ CCI_REG8(0x331D), 0x15 },
+	{ CCI_REG8(0x331E), 0x1 },
+	{ CCI_REG8(0x331F), 0x0 },
+	{ CCI_REG8(0x3320), 0x30 },
+	{ CCI_REG8(0x3321), 0xA0 },
+	{ CCI_REG8(0x3322), 0x16 },
+	{ CCI_REG8(0x3323), 0x15 },
+	{ CCI_REG8(0x3324), 0x1 },
+	{ CCI_REG8(0x3325), 0x0 },
+	{ CCI_REG8(0x3326), 0x30 },
+	{ CCI_REG8(0x3327), 0xA0 },
+	{ CCI_REG8(0x3328), 0x16 },
+	{ CCI_REG8(0x3329), 0x15 },
+	{ CCI_REG8(0x332A), 0x2B },
+	{ CCI_REG8(0x332B), 0x0 },
+	{ CCI_REG8(0x332C), 0x30 },
+	{ CCI_REG8(0x332D), 0xA0 },
+	{ CCI_REG8(0x332E), 0x16 },
+	{ CCI_REG8(0x332F), 0x15 },
+	{ CCI_REG8(0x3330), 0x1 },
+	{ CCI_REG8(0x3331), 0x0 },
+	{ CCI_REG8(0x3332), 0x10 },
+	{ CCI_REG8(0x3333), 0xA0 },
+	{ CCI_REG8(0x3334), 0x16 },
+	{ CCI_REG8(0x3335), 0x15 },
+	{ CCI_REG8(0x3058), 0x8 },
+	{ CCI_REG8(0x3059), 0x0 },
+	{ CCI_REG8(0x305A), 0x9 },
+	{ CCI_REG8(0x305B), 0x0 },
+	{ CCI_REG8(0x3336), 0x1 },
+	{ CCI_REG8(0x3337), 0x0 },
+	{ CCI_REG8(0x3338), 0x90 },
+	{ CCI_REG8(0x3339), 0xB0 },
+	{ CCI_REG8(0x333A), 0x16 },
+	{ CCI_REG8(0x333B), 0x15 },
+	{ CCI_REG8(0x333C), 0x1F },
+	{ CCI_REG8(0x333D), 0x0 },
+	{ CCI_REG8(0x333E), 0x10 },
+	{ CCI_REG8(0x333F), 0xA0 },
+	{ CCI_REG8(0x3340), 0x16 },
+	{ CCI_REG8(0x3341), 0x15 },
+	{ CCI_REG8(0x3342), 0x52 },
+	{ CCI_REG8(0x3343), 0x0 },
+	{ CCI_REG8(0x3344), 0x10 },
+	{ CCI_REG8(0x3345), 0x80 },
+	{ CCI_REG8(0x3346), 0x16 },
+	{ CCI_REG8(0x3347), 0x15 },
+	{ CCI_REG8(0x3348), 0x1 },
+	{ CCI_REG8(0x3349), 0x0 },
+	{ CCI_REG8(0x334A), 0x10 },
+	{ CCI_REG8(0x334B), 0x80 },
+	{ CCI_REG8(0x334C), 0x16 },
+	{ CCI_REG8(0x334D), 0x1D },
+	{ CCI_REG8(0x334E), 0x1 },
+	{ CCI_REG8(0x334F), 0x0 },
+	{ CCI_REG8(0x3350), 0x50 },
+	{ CCI_REG8(0x3351), 0x84 },
+	{ CCI_REG8(0x3352), 0x16 },
+	{ CCI_REG8(0x3353), 0x1D },
+	{ CCI_REG8(0x3354), 0x18 },
+	{ CCI_REG8(0x3355), 0x0 },
+	{ CCI_REG8(0x3356), 0x10 },
+	{ CCI_REG8(0x3357), 0x84 },
+	{ CCI_REG8(0x3358), 0x16 },
+	{ CCI_REG8(0x3359), 0x1D },
+	{ CCI_REG8(0x335A), 0x80 },
+	{ CCI_REG8(0x335B), 0x2 },
+	{ CCI_REG8(0x335C), 0x10 },
+	{ CCI_REG8(0x335D), 0xC4 },
+	{ CCI_REG8(0x335E), 0x14 },
+	{ CCI_REG8(0x335F), 0x1D },
+	{ CCI_REG8(0x3360), 0xA5 },
+	{ CCI_REG8(0x3361), 0x0 },
+	{ CCI_REG8(0x3362), 0x10 },
+	{ CCI_REG8(0x3363), 0x84 },
+	{ CCI_REG8(0x3364), 0x16 },
+	{ CCI_REG8(0x3365), 0x1D },
+	{ CCI_REG8(0x3366), 0x1 },
+	{ CCI_REG8(0x3367), 0x0 },
+	{ CCI_REG8(0x3368), 0x90 },
+	{ CCI_REG8(0x3369), 0x84 },
+	{ CCI_REG8(0x336A), 0x16 },
+	{ CCI_REG8(0x336B), 0x1D },
+	{ CCI_REG8(0x336C), 0x12 },
+	{ CCI_REG8(0x336D), 0x0 },
+	{ CCI_REG8(0x336E), 0x10 },
+	{ CCI_REG8(0x336F), 0x84 },
+	{ CCI_REG8(0x3370), 0x16 },
+	{ CCI_REG8(0x3371), 0x15 },
+	{ CCI_REG8(0x3372), 0x32 },
+	{ CCI_REG8(0x3373), 0x0 },
+	{ CCI_REG8(0x3374), 0x30 },
+	{ CCI_REG8(0x3375), 0x84 },
+	{ CCI_REG8(0x3376), 0x16 },
+	{ CCI_REG8(0x3377), 0x15 },
+	{ CCI_REG8(0x3378), 0x26 },
+	{ CCI_REG8(0x3379), 0x0 },
+	{ CCI_REG8(0x337A), 0x10 },
+	{ CCI_REG8(0x337B), 0x84 },
+	{ CCI_REG8(0x337C), 0x16 },
+	{ CCI_REG8(0x337D), 0x15 },
+	{ CCI_REG8(0x337E), 0x80 },
+	{ CCI_REG8(0x337F), 0x2 },
+	{ CCI_REG8(0x3380), 0x10 },
+	{ CCI_REG8(0x3381), 0xC4 },
+	{ CCI_REG8(0x3382), 0x14 },
+	{ CCI_REG8(0x3383), 0x15 },
+	{ CCI_REG8(0x3384), 0xA9 },
+	{ CCI_REG8(0x3385), 0x0 },
+	{ CCI_REG8(0x3386), 0x10 },
+	{ CCI_REG8(0x3387), 0x84 },
+	{ CCI_REG8(0x3388), 0x16 },
+	{ CCI_REG8(0x3389), 0x15 },
+	{ CCI_REG8(0x338A), 0x41 },
+	{ CCI_REG8(0x338B), 0x0 },
+	{ CCI_REG8(0x338C), 0x10 },
+	{ CCI_REG8(0x338D), 0x80 },
+	{ CCI_REG8(0x338E), 0x16 },
+	{ CCI_REG8(0x338F), 0x15 },
+	{ CCI_REG8(0x3390), 0x2 },
+	{ CCI_REG8(0x3391), 0x0 },
+	{ CCI_REG8(0x3392), 0x10 },
+	{ CCI_REG8(0x3393), 0xA0 },
+	{ CCI_REG8(0x3394), 0x16 },
+	{ CCI_REG8(0x3395), 0x15 },
+	{ CCI_REG8(0x305C), 0x18 },
+	{ CCI_REG8(0x305D), 0x0 },
+	{ CCI_REG8(0x305E), 0x19 },
+	{ CCI_REG8(0x305F), 0x0 },
+	{ CCI_REG8(0x3396), 0x1 },
+	{ CCI_REG8(0x3397), 0x0 },
+	{ CCI_REG8(0x3398), 0x90 },
+	{ CCI_REG8(0x3399), 0x30 },
+	{ CCI_REG8(0x339A), 0x56 },
+	{ CCI_REG8(0x339B), 0x57 },
+	{ CCI_REG8(0x339C), 0x1 },
+	{ CCI_REG8(0x339D), 0x0 },
+	{ CCI_REG8(0x339E), 0x10 },
+	{ CCI_REG8(0x339F), 0x20 },
+	{ CCI_REG8(0x33A0), 0xD6 },
+	{ CCI_REG8(0x33A1), 0x17 },
+	{ CCI_REG8(0x33A2), 0x1 },
+	{ CCI_REG8(0x33A3), 0x0 },
+	{ CCI_REG8(0x33A4), 0x10 },
+	{ CCI_REG8(0x33A5), 0x28 },
+	{ CCI_REG8(0x33A6), 0xD6 },
+	{ CCI_REG8(0x33A7), 0x17 },
+	{ CCI_REG8(0x33A8), 0x3 },
+	{ CCI_REG8(0x33A9), 0x0 },
+	{ CCI_REG8(0x33AA), 0x10 },
+	{ CCI_REG8(0x33AB), 0x20 },
+	{ CCI_REG8(0x33AC), 0xD6 },
+	{ CCI_REG8(0x33AD), 0x17 },
+	{ CCI_REG8(0x33AE), 0x61 },
+	{ CCI_REG8(0x33AF), 0x0 },
+	{ CCI_REG8(0x33B0), 0x10 },
+	{ CCI_REG8(0x33B1), 0x20 },
+	{ CCI_REG8(0x33B2), 0xD6 },
+	{ CCI_REG8(0x33B3), 0x15 },
+	{ CCI_REG8(0x33B4), 0x1 },
+	{ CCI_REG8(0x33B5), 0x0 },
+	{ CCI_REG8(0x33B6), 0x10 },
+	{ CCI_REG8(0x33B7), 0x20 },
+	{ CCI_REG8(0x33B8), 0xD6 },
+	{ CCI_REG8(0x33B9), 0x1D },
+	{ CCI_REG8(0x33BA), 0x1 },
+	{ CCI_REG8(0x33BB), 0x0 },
+	{ CCI_REG8(0x33BC), 0x50 },
+	{ CCI_REG8(0x33BD), 0x20 },
+	{ CCI_REG8(0x33BE), 0xD6 },
+	{ CCI_REG8(0x33BF), 0x1D },
+	{ CCI_REG8(0x33C0), 0x2C },
+	{ CCI_REG8(0x33C1), 0x0 },
+	{ CCI_REG8(0x33C2), 0x10 },
+	{ CCI_REG8(0x33C3), 0x20 },
+	{ CCI_REG8(0x33C4), 0xD6 },
+	{ CCI_REG8(0x33C5), 0x1D },
+	{ CCI_REG8(0x33C6), 0x1 },
+	{ CCI_REG8(0x33C7), 0x0 },
+	{ CCI_REG8(0x33C8), 0x90 },
+	{ CCI_REG8(0x33C9), 0x20 },
+	{ CCI_REG8(0x33CA), 0xD6 },
+	{ CCI_REG8(0x33CB), 0x1D },
+	{ CCI_REG8(0x33CC), 0x83 },
+	{ CCI_REG8(0x33CD), 0x0 },
+	{ CCI_REG8(0x33CE), 0x10 },
+	{ CCI_REG8(0x33CF), 0x20 },
+	{ CCI_REG8(0x33D0), 0xD6 },
+	{ CCI_REG8(0x33D1), 0x15 },
+	{ CCI_REG8(0x33D2), 0x1 },
+	{ CCI_REG8(0x33D3), 0x0 },
+	{ CCI_REG8(0x33D4), 0x10 },
+	{ CCI_REG8(0x33D5), 0x30 },
+	{ CCI_REG8(0x33D6), 0xD6 },
+	{ CCI_REG8(0x33D7), 0x15 },
+	{ CCI_REG8(0x33D8), 0x1 },
+	{ CCI_REG8(0x33D9), 0x0 },
+	{ CCI_REG8(0x33DA), 0x10 },
+	{ CCI_REG8(0x33DB), 0x20 },
+	{ CCI_REG8(0x33DC), 0xD6 },
+	{ CCI_REG8(0x33DD), 0x15 },
+	{ CCI_REG8(0x33DE), 0x1 },
+	{ CCI_REG8(0x33DF), 0x0 },
+	{ CCI_REG8(0x33E0), 0x10 },
+	{ CCI_REG8(0x33E1), 0x20 },
+	{ CCI_REG8(0x33E2), 0x56 },
+	{ CCI_REG8(0x33E3), 0x15 },
+	{ CCI_REG8(0x33E4), 0x7 },
+	{ CCI_REG8(0x33E5), 0x0 },
+	{ CCI_REG8(0x33E6), 0x10 },
+	{ CCI_REG8(0x33E7), 0x20 },
+	{ CCI_REG8(0x33E8), 0x16 },
+	{ CCI_REG8(0x33E9), 0x15 },
+	{ CCI_REG8(0x3060), 0x26 },
+	{ CCI_REG8(0x3061), 0x0 },
+	{ CCI_REG8(0x302A), 0xFF },
+	{ CCI_REG8(0x302B), 0xFF },
+	{ CCI_REG8(0x302C), 0xFF },
+	{ CCI_REG8(0x302D), 0xFF },
+	{ CCI_REG8(0x302E), 0x3F },
+	{ CCI_REG8(0x3013), 0xB },
+	{ CCI_REG8(0x102B), 0x2C },
+	{ CCI_REG8(0x102C), 0x1 },
+	{ CCI_REG8(0x1035), 0x54 },
+	{ CCI_REG8(0x1036), 0x0 },
+	{ CCI_REG8(0x3090), 0x2A },
+	{ CCI_REG8(0x3091), 0x1 },
+	{ CCI_REG8(0x30C6), 0x5 },
+	{ CCI_REG8(0x30C7), 0x0 },
+	{ CCI_REG8(0x30C8), 0x0 },
+	{ CCI_REG8(0x30C9), 0x0 },
+	{ CCI_REG8(0x30CA), 0x0 },
+	{ CCI_REG8(0x30CB), 0x0 },
+	{ CCI_REG8(0x30CC), 0x0 },
+	{ CCI_REG8(0x30CD), 0x0 },
+	{ CCI_REG8(0x30CE), 0x0 },
+	{ CCI_REG8(0x30CF), 0x5 },
+	{ CCI_REG8(0x30D0), 0x0 },
+	{ CCI_REG8(0x30D1), 0x0 },
+	{ CCI_REG8(0x30D2), 0x0 },
+	{ CCI_REG8(0x30D3), 0x0 },
+	{ CCI_REG8(0x30D4), 0x0 },
+	{ CCI_REG8(0x30D5), 0x0 },
+	{ CCI_REG8(0x30D6), 0x0 },
+	{ CCI_REG8(0x30D7), 0x0 },
+	{ CCI_REG8(0x30F3), 0x5 },
+	{ CCI_REG8(0x30F4), 0x0 },
+	{ CCI_REG8(0x30F5), 0x0 },
+	{ CCI_REG8(0x30F6), 0x0 },
+	{ CCI_REG8(0x30F7), 0x0 },
+	{ CCI_REG8(0x30F8), 0x0 },
+	{ CCI_REG8(0x30F9), 0x0 },
+	{ CCI_REG8(0x30FA), 0x0 },
+	{ CCI_REG8(0x30FB), 0x0 },
+	{ CCI_REG8(0x30D8), 0x5 },
+	{ CCI_REG8(0x30D9), 0x0 },
+	{ CCI_REG8(0x30DA), 0x0 },
+	{ CCI_REG8(0x30DB), 0x0 },
+	{ CCI_REG8(0x30DC), 0x0 },
+	{ CCI_REG8(0x30DD), 0x0 },
+	{ CCI_REG8(0x30DE), 0x0 },
+	{ CCI_REG8(0x30DF), 0x0 },
+	{ CCI_REG8(0x30E0), 0x0 },
+	{ CCI_REG8(0x30E1), 0x5 },
+	{ CCI_REG8(0x30E2), 0x0 },
+	{ CCI_REG8(0x30E3), 0x0 },
+	{ CCI_REG8(0x30E4), 0x0 },
+	{ CCI_REG8(0x30E5), 0x0 },
+	{ CCI_REG8(0x30E6), 0x0 },
+	{ CCI_REG8(0x30E7), 0x0 },
+	{ CCI_REG8(0x30E8), 0x0 },
+	{ CCI_REG8(0x30E9), 0x0 },
+	{ CCI_REG8(0x30F3), 0x5 },
+	{ CCI_REG8(0x30F4), 0x2 },
+	{ CCI_REG8(0x30F5), 0x0 },
+	{ CCI_REG8(0x30F6), 0x17 },
+	{ CCI_REG8(0x30F7), 0x1 },
+	{ CCI_REG8(0x30F8), 0x0 },
+	{ CCI_REG8(0x30F9), 0x0 },
+	{ CCI_REG8(0x30FA), 0x0 },
+	{ CCI_REG8(0x30FB), 0x0 },
+	{ CCI_REG8(0x30D8), 0x3 },
+	{ CCI_REG8(0x30D9), 0x1 },
+	{ CCI_REG8(0x30DA), 0x0 },
+	{ CCI_REG8(0x30DB), 0x19 },
+	{ CCI_REG8(0x30DC), 0x1 },
+	{ CCI_REG8(0x30DD), 0x0 },
+	{ CCI_REG8(0x30DE), 0x0 },
+	{ CCI_REG8(0x30DF), 0x0 },
+	{ CCI_REG8(0x30E0), 0x0 },
+	{ CCI_REG8(0x30A2), 0x5 },
+	{ CCI_REG8(0x30A3), 0x2 },
+	{ CCI_REG8(0x30A4), 0x0 },
+	{ CCI_REG8(0x30A5), 0x22 },
+	{ CCI_REG8(0x30A6), 0x0 },
+	{ CCI_REG8(0x30A7), 0x0 },
+	{ CCI_REG8(0x30A8), 0x0 },
+	{ CCI_REG8(0x30A9), 0x0 },
+	{ CCI_REG8(0x30AA), 0x0 },
+	{ CCI_REG8(0x30AB), 0x5 },
+	{ CCI_REG8(0x30AC), 0x2 },
+	{ CCI_REG8(0x30AD), 0x0 },
+	{ CCI_REG8(0x30AE), 0x22 },
+	{ CCI_REG8(0x30AF), 0x0 },
+	{ CCI_REG8(0x30B0), 0x0 },
+	{ CCI_REG8(0x30B1), 0x0 },
+	{ CCI_REG8(0x30B2), 0x0 },
+	{ CCI_REG8(0x30B3), 0x0 },
+	{ CCI_REG8(0x30BD), 0x5 },
+	{ CCI_REG8(0x30BE), 0x9F },
+	{ CCI_REG8(0x30BF), 0x0 },
+	{ CCI_REG8(0x30C0), 0x7D },
+	{ CCI_REG8(0x30C1), 0x0 },
+	{ CCI_REG8(0x30C2), 0x0 },
+	{ CCI_REG8(0x30C3), 0x0 },
+	{ CCI_REG8(0x30C4), 0x0 },
+	{ CCI_REG8(0x30C5), 0x0 },
+	{ CCI_REG8(0x30B4), 0x4 },
+	{ CCI_REG8(0x30B5), 0x9C },
+	{ CCI_REG8(0x30B6), 0x0 },
+	{ CCI_REG8(0x30B7), 0x7D },
+	{ CCI_REG8(0x30B8), 0x0 },
+	{ CCI_REG8(0x30B9), 0x0 },
+	{ CCI_REG8(0x30BA), 0x0 },
+	{ CCI_REG8(0x30BB), 0x0 },
+	{ CCI_REG8(0x30BC), 0x0 },
+	{ CCI_REG8(0x30FC), 0x5 },
+	{ CCI_REG8(0x30FD), 0x0 },
+	{ CCI_REG8(0x30FE), 0x0 },
+	{ CCI_REG8(0x30FF), 0x0 },
+	{ CCI_REG8(0x3100), 0x0 },
+	{ CCI_REG8(0x3101), 0x0 },
+	{ CCI_REG8(0x3102), 0x0 },
+	{ CCI_REG8(0x3103), 0x0 },
+	{ CCI_REG8(0x3104), 0x0 },
+	{ CCI_REG8(0x3105), 0x5 },
+	{ CCI_REG8(0x3106), 0x0 },
+	{ CCI_REG8(0x3107), 0x0 },
+	{ CCI_REG8(0x3108), 0x0 },
+	{ CCI_REG8(0x3109), 0x0 },
+	{ CCI_REG8(0x310A), 0x0 },
+	{ CCI_REG8(0x310B), 0x0 },
+	{ CCI_REG8(0x310C), 0x0 },
+	{ CCI_REG8(0x310D), 0x0 },
+	{ CCI_REG8(0x3099), 0x5 },
+	{ CCI_REG8(0x309A), 0x96 },
+	{ CCI_REG8(0x309B), 0x0 },
+	{ CCI_REG8(0x309C), 0x6 },
+	{ CCI_REG8(0x309D), 0x0 },
+	{ CCI_REG8(0x309E), 0x0 },
+	{ CCI_REG8(0x309F), 0x0 },
+	{ CCI_REG8(0x30A0), 0x0 },
+	{ CCI_REG8(0x30A1), 0x0 },
+	{ CCI_REG8(0x310E), 0x5 },
+	{ CCI_REG8(0x310F), 0x2 },
+	{ CCI_REG8(0x3110), 0x0 },
+	{ CCI_REG8(0x3111), 0x2B },
+	{ CCI_REG8(0x3112), 0x0 },
+	{ CCI_REG8(0x3113), 0x0 },
+	{ CCI_REG8(0x3114), 0x0 },
+	{ CCI_REG8(0x3115), 0x0 },
+	{ CCI_REG8(0x3116), 0x0 },
+	{ CCI_REG8(0x3117), 0x5 },
+	{ CCI_REG8(0x3118), 0x2 },
+	{ CCI_REG8(0x3119), 0x0 },
+	{ CCI_REG8(0x311A), 0x2C },
+	{ CCI_REG8(0x311B), 0x0 },
+	{ CCI_REG8(0x311C), 0x0 },
+	{ CCI_REG8(0x311D), 0x0 },
+	{ CCI_REG8(0x311E), 0x0 },
+	{ CCI_REG8(0x311F), 0x0 },
+	{ CCI_REG8(0x30EA), 0x0 },
+	{ CCI_REG8(0x30EB), 0x0 },
+	{ CCI_REG8(0x30EC), 0x0 },
+	{ CCI_REG8(0x30ED), 0x0 },
+	{ CCI_REG8(0x30EE), 0x0 },
+	{ CCI_REG8(0x30EF), 0x0 },
+	{ CCI_REG8(0x30F0), 0x0 },
+	{ CCI_REG8(0x30F1), 0x0 },
+	{ CCI_REG8(0x30F2), 0x0 },
+	{ CCI_REG8(0x313B), 0x3 },
+	{ CCI_REG8(0x313C), 0x31 },
+	{ CCI_REG8(0x313D), 0x0 },
+	{ CCI_REG8(0x313E), 0x7 },
+	{ CCI_REG8(0x313F), 0x0 },
+	{ CCI_REG8(0x3140), 0x68 },
+	{ CCI_REG8(0x3141), 0x0 },
+	{ CCI_REG8(0x3142), 0x34 },
+	{ CCI_REG8(0x3143), 0x0 },
+	{ CCI_REG8(0x31A0), 0x3 },
+	{ CCI_REG8(0x31A1), 0x16 },
+	{ CCI_REG8(0x31A2), 0x0 },
+	{ CCI_REG8(0x31A3), 0x8 },
+	{ CCI_REG8(0x31A4), 0x0 },
+	{ CCI_REG8(0x31A5), 0x7E },
+	{ CCI_REG8(0x31A6), 0x0 },
+	{ CCI_REG8(0x31A7), 0x8 },
+	{ CCI_REG8(0x31A8), 0x0 },
+	{ CCI_REG8(0x31A9), 0x3 },
+	{ CCI_REG8(0x31AA), 0x16 },
+	{ CCI_REG8(0x31AB), 0x0 },
+	{ CCI_REG8(0x31AC), 0x8 },
+	{ CCI_REG8(0x31AD), 0x0 },
+	{ CCI_REG8(0x31AE), 0x7E },
+	{ CCI_REG8(0x31AF), 0x0 },
+	{ CCI_REG8(0x31B0), 0x8 },
+	{ CCI_REG8(0x31B1), 0x0 },
+	{ CCI_REG8(0x31B2), 0x3 },
+	{ CCI_REG8(0x31B3), 0x16 },
+	{ CCI_REG8(0x31B4), 0x0 },
+	{ CCI_REG8(0x31B5), 0x8 },
+	{ CCI_REG8(0x31B6), 0x0 },
+	{ CCI_REG8(0x31B7), 0x7E },
+	{ CCI_REG8(0x31B8), 0x0 },
+	{ CCI_REG8(0x31B9), 0x8 },
+	{ CCI_REG8(0x31BA), 0x0 },
+	{ CCI_REG8(0x3120), 0x5 },
+	{ CCI_REG8(0x3121), 0x45 },
+	{ CCI_REG8(0x3122), 0x0 },
+	{ CCI_REG8(0x3123), 0x1D },
+	{ CCI_REG8(0x3124), 0x0 },
+	{ CCI_REG8(0x3125), 0xA9 },
+	{ CCI_REG8(0x3126), 0x0 },
+	{ CCI_REG8(0x3127), 0x6D },
+	{ CCI_REG8(0x3128), 0x0 },
+	{ CCI_REG8(0x3129), 0x5 },
+	{ CCI_REG8(0x312A), 0x15 },
+	{ CCI_REG8(0x312B), 0x0 },
+	{ CCI_REG8(0x312C), 0xA },
+	{ CCI_REG8(0x312D), 0x0 },
+	{ CCI_REG8(0x312E), 0x45 },
+	{ CCI_REG8(0x312F), 0x0 },
+	{ CCI_REG8(0x3130), 0x1D },
+	{ CCI_REG8(0x3131), 0x0 },
+	{ CCI_REG8(0x3132), 0x5 },
+	{ CCI_REG8(0x3133), 0x7D },
+	{ CCI_REG8(0x3134), 0x0 },
+	{ CCI_REG8(0x3135), 0xA },
+	{ CCI_REG8(0x3136), 0x0 },
+	{ CCI_REG8(0x3137), 0xA9 },
+	{ CCI_REG8(0x3138), 0x0 },
+	{ CCI_REG8(0x3139), 0x6D },
+	{ CCI_REG8(0x313A), 0x0 },
+	{ CCI_REG8(0x3144), 0x5 },
+	{ CCI_REG8(0x3145), 0x0 },
+	{ CCI_REG8(0x3146), 0x0 },
+	{ CCI_REG8(0x3147), 0x30 },
+	{ CCI_REG8(0x3148), 0x0 },
+	{ CCI_REG8(0x3149), 0x0 },
+	{ CCI_REG8(0x314A), 0x0 },
+	{ CCI_REG8(0x314B), 0x0 },
+	{ CCI_REG8(0x314C), 0x0 },
+	{ CCI_REG8(0x314D), 0x3 },
+	{ CCI_REG8(0x314E), 0x0 },
+	{ CCI_REG8(0x314F), 0x0 },
+	{ CCI_REG8(0x3150), 0x31 },
+	{ CCI_REG8(0x3151), 0x0 },
+	{ CCI_REG8(0x3152), 0x0 },
+	{ CCI_REG8(0x3153), 0x0 },
+	{ CCI_REG8(0x3154), 0x0 },
+	{ CCI_REG8(0x3155), 0x0 },
+	{ CCI_REG8(0x31D8), 0x5 },
+	{ CCI_REG8(0x31D9), 0x3A },
+	{ CCI_REG8(0x31DA), 0x0 },
+	{ CCI_REG8(0x31DB), 0x2E },
+	{ CCI_REG8(0x31DC), 0x0 },
+	{ CCI_REG8(0x31DD), 0x9E },
+	{ CCI_REG8(0x31DE), 0x0 },
+	{ CCI_REG8(0x31DF), 0x7E },
+	{ CCI_REG8(0x31E0), 0x0 },
+	{ CCI_REG8(0x31E1), 0x5 },
+	{ CCI_REG8(0x31E2), 0x4 },
+	{ CCI_REG8(0x31E3), 0x0 },
+	{ CCI_REG8(0x31E4), 0x4 },
+	{ CCI_REG8(0x31E5), 0x0 },
+	{ CCI_REG8(0x31E6), 0x73 },
+	{ CCI_REG8(0x31E7), 0x0 },
+	{ CCI_REG8(0x31E8), 0x4 },
+	{ CCI_REG8(0x31E9), 0x0 },
+	{ CCI_REG8(0x31EA), 0x5 },
+	{ CCI_REG8(0x31EB), 0x0 },
+	{ CCI_REG8(0x31EC), 0x0 },
+	{ CCI_REG8(0x31ED), 0x0 },
+	{ CCI_REG8(0x31EE), 0x0 },
+	{ CCI_REG8(0x31EF), 0x0 },
+	{ CCI_REG8(0x31F0), 0x0 },
+	{ CCI_REG8(0x31F1), 0x0 },
+	{ CCI_REG8(0x31F2), 0x0 },
+	{ CCI_REG8(0x31F3), 0x0 },
+	{ CCI_REG8(0x31F4), 0x0 },
+	{ CCI_REG8(0x31F5), 0x0 },
+	{ CCI_REG8(0x31F6), 0x0 },
+	{ CCI_REG8(0x31F7), 0x0 },
+	{ CCI_REG8(0x31F8), 0x0 },
+	{ CCI_REG8(0x31F9), 0x0 },
+	{ CCI_REG8(0x31FA), 0x0 },
+	{ CCI_REG8(0x31FB), 0x5 },
+	{ CCI_REG8(0x31FC), 0x0 },
+	{ CCI_REG8(0x31FD), 0x0 },
+	{ CCI_REG8(0x31FE), 0x0 },
+	{ CCI_REG8(0x31FF), 0x0 },
+	{ CCI_REG8(0x3200), 0x0 },
+	{ CCI_REG8(0x3201), 0x0 },
+	{ CCI_REG8(0x3202), 0x0 },
+	{ CCI_REG8(0x3203), 0x0 },
+	{ CCI_REG8(0x3204), 0x0 },
+	{ CCI_REG8(0x3205), 0x0 },
+	{ CCI_REG8(0x3206), 0x0 },
+	{ CCI_REG8(0x3207), 0x0 },
+	{ CCI_REG8(0x3208), 0x0 },
+	{ CCI_REG8(0x3209), 0x0 },
+	{ CCI_REG8(0x320A), 0x0 },
+	{ CCI_REG8(0x320B), 0x0 },
+	{ CCI_REG8(0x3164), 0x5 },
+	{ CCI_REG8(0x3165), 0x14 },
+	{ CCI_REG8(0x3166), 0x0 },
+	{ CCI_REG8(0x3167), 0xC },
+	{ CCI_REG8(0x3168), 0x0 },
+	{ CCI_REG8(0x3169), 0x44 },
+	{ CCI_REG8(0x316A), 0x0 },
+	{ CCI_REG8(0x316B), 0x1F },
+	{ CCI_REG8(0x316C), 0x0 },
+	{ CCI_REG8(0x316D), 0x5 },
+	{ CCI_REG8(0x316E), 0x7C },
+	{ CCI_REG8(0x316F), 0x0 },
+	{ CCI_REG8(0x3170), 0xC },
+	{ CCI_REG8(0x3171), 0x0 },
+	{ CCI_REG8(0x3172), 0xA8 },
+	{ CCI_REG8(0x3173), 0x0 },
+	{ CCI_REG8(0x3174), 0x6F },
+	{ CCI_REG8(0x3175), 0x0 },
+	{ CCI_REG8(0x31C4), 0x5 },
+	{ CCI_REG8(0x31C5), 0x24 },
+	{ CCI_REG8(0x31C6), 0x1 },
+	{ CCI_REG8(0x31C7), 0x4 },
+	{ CCI_REG8(0x31C8), 0x0 },
+	{ CCI_REG8(0x31C9), 0x5 },
+	{ CCI_REG8(0x31CA), 0x24 },
+	{ CCI_REG8(0x31CB), 0x1 },
+	{ CCI_REG8(0x31CC), 0x4 },
+	{ CCI_REG8(0x31CD), 0x0 },
+	{ CCI_REG8(0x31CE), 0x5 },
+	{ CCI_REG8(0x31CF), 0x24 },
+	{ CCI_REG8(0x31D0), 0x1 },
+	{ CCI_REG8(0x31D1), 0x4 },
+	{ CCI_REG8(0x31D2), 0x0 },
+	{ CCI_REG8(0x31D3), 0x5 },
+	{ CCI_REG8(0x31D4), 0x73 },
+	{ CCI_REG8(0x31D5), 0x0 },
+	{ CCI_REG8(0x31D6), 0xB1 },
+	{ CCI_REG8(0x31D7), 0x0 },
+	{ CCI_REG8(0x3176), 0x5 },
+	{ CCI_REG8(0x3177), 0x10 },
+	{ CCI_REG8(0x3178), 0x0 },
+	{ CCI_REG8(0x3179), 0x56 },
+	{ CCI_REG8(0x317A), 0x0 },
+	{ CCI_REG8(0x317B), 0x0 },
+	{ CCI_REG8(0x317C), 0x0 },
+	{ CCI_REG8(0x317D), 0x0 },
+	{ CCI_REG8(0x317E), 0x0 },
+	{ CCI_REG8(0x317F), 0x5 },
+	{ CCI_REG8(0x3180), 0x6A },
+	{ CCI_REG8(0x3181), 0x0 },
+	{ CCI_REG8(0x3182), 0xAD },
+	{ CCI_REG8(0x3183), 0x0 },
+	{ CCI_REG8(0x3184), 0x0 },
+	{ CCI_REG8(0x3185), 0x0 },
+	{ CCI_REG8(0x3186), 0x0 },
+	{ CCI_REG8(0x3187), 0x0 },
+	{ CCI_REG8(0x100C), 0x7E },
+	{ CCI_REG8(0x100D), 0x0 },
+	{ CCI_REG8(0x1012), 0xDF },
+	{ CCI_REG8(0x1013), 0x2B },
+	{ CCI_REG8(0x1002), 0x4 },
+	/* Sensor control mode */
+	{ CCI_REG8(0x0043), 0x0 }, //  Sensor Control Mode.SLEEP_POWER_MODE(0)
+	{ CCI_REG8(0x0043), 0x0 }, //  Sensor Control Mode.IDLE_POWER_MODE(0)
+	{ CCI_REG8(0x0043), 0x4 }, //  Sensor Control Mode.SYSTEM_CLOCK_ENABLE(0)
+	{ CCI_REG8(0x0043), 0xC }, //  Sensor Control Mode.SRAM_CLOCK_ENABLE(0)
+	{ CCI_REG8(0x1002), 0x4 }, //  Sensor Control Mode.IMAGER_RUN_CONT(0)
+	{ CCI_REG8(0x1001), 0x41 }, //  Sensor Control Mode.EXT_EVENT_SEL(0)
+	{ CCI_REG8(0x10F2), 0x1 }, //  Sensor Control Mode.NB_OF_FRAMES_A(0)
+	{ CCI_REG8(0x10F3), 0x0 }, //  Sensor Control Mode.NB_OF_FRAMES_A(1)
+	{ CCI_REG8(0x1111), 0x1 }, //  Sensor Control Mode.NB_OF_FRAMES_B(0)
+	{ CCI_REG8(0x1112), 0x0 }, //  Sensor Control Mode.NB_OF_FRAMES_B(1)
+	{ CCI_REG8(0x0012), 0x0 }, //  IO Drive Strength.DIG_DRIVE_STRENGTH(0)
+	{ CCI_REG8(0x0012), 0x0 }, //  IO Drive Strength.CCI_DRIVE_STRENGTH(0)
+	{ CCI_REG8(0x1001), 0x41 }, //  Readout && Exposure.EXT_EXP_PW_SEL(0)
+	{ CCI_REG8(0x10D0), 0x0 }, //  Readout && Exposure.EXT_EXP_PW_DELAY(0)
+	{ CCI_REG8(0x10D1), 0x0 }, //  Readout && Exposure.EXT_EXP_PW_DELAY(1)
+	{ CCI_REG8(0x1012), 0x91 }, //  Readout && Exposure.VBLANK_A(0)
+	{ CCI_REG8(0x1013), 0xD }, //  Readout && Exposure.VBLANK_A(1)
+	{ CCI_REG8(0x1103), 0x91 }, //  Readout && Exposure.VBLANK_B(0)
+	{ CCI_REG8(0x1104), 0xD }, //  Readout && Exposure.VBLANK_B(1)
+	{ CCI_REG8(0x100C), 0x80 }, //  Readout && Exposure.EXP_TIME_A(0)
+	{ CCI_REG8(0x100D), 0x0 }, //  Readout && Exposure.EXP_TIME_A(1)
+	{ CCI_REG8(0x1115), 0x80 }, //  Readout && Exposure.EXP_TIME_B(0)
+	{ CCI_REG8(0x1116), 0x0 }, //  Readout && Exposure.EXP_TIME_B(1)
+	{ CCI_REG8(0x102B), 0x30 }, //  Readout && Exposure.ROW_LENGTH_A(0)
+	{ CCI_REG8(0x102C), 0x1 }, //  Readout && Exposure.ROW_LENGTH_A(1)
+	{ CCI_REG8(0x1113), 0x30 }, //  Readout && Exposure.ROW_LENGTH_B(0)
+	{ CCI_REG8(0x1114), 0x1 }, //  Readout && Exposure.ROW_LENGTH_B(1)
+	/* ROI */
+	{ CCI_REG8(0x2008), 0x20 }, //  Horizontal ROI.HSIZE_A(0)
+	{ CCI_REG8(0x2009), 0x3 }, //  Horizontal ROI.HSIZE_A(1)
+	{ CCI_REG8(0x2098), 0x20 }, //  Horizontal ROI.HSIZE_B(0)
+	{ CCI_REG8(0x2099), 0x3 }, //  Horizontal ROI.HSIZE_B(1)
+	{ CCI_REG8(0x200A), 0x0 }, //  Horizontal ROI.HSTART_A(0)
+	{ CCI_REG8(0x200B), 0x0 }, //  Horizontal ROI.HSTART_A(1)
+	{ CCI_REG8(0x209A), 0x0 }, //  Horizontal ROI.HSTART_B(0)
+	{ CCI_REG8(0x209B), 0x0 }, //  Horizontal ROI.HSTART_B(1)
+	{ CCI_REG8(0x207D), 0x40 }, //  Horizontal ROI.MIPI_HSIZE(0)
+	{ CCI_REG8(0x207E), 0x6 }, //  Horizontal ROI.MIPI_HSIZE(1)
+	{ CCI_REG8(0x107D), 0x0 }, //  Vertical ROI.VSTART0_A(0)
+	{ CCI_REG8(0x107E), 0x0 }, //  Vertical ROI.VSTART0_A(1)
+	{ CCI_REG8(0x1087), 0x78 }, //  Vertical ROI.VSIZE0_A(0)
+	{ CCI_REG8(0x1088), 0x5 }, //  Vertical ROI.VSIZE0_A(1)
+	{ CCI_REG8(0x1105), 0x0 }, //  Vertical ROI.VSTART0_B(0)
+	{ CCI_REG8(0x1106), 0x0 }, //  Vertical ROI.VSTART0_B(1)
+	{ CCI_REG8(0x110A), 0x78 }, //  Vertical ROI.VSIZE0_B(0)
+	{ CCI_REG8(0x110B), 0x5 }, //  Vertical ROI.VSIZE0_B(1)
+	{ CCI_REG8(0x107D), 0x0 }, //  Vertical ROI.VSTART1_A(0)
+	{ CCI_REG8(0x107E), 0x0 }, //  Vertical ROI.VSTART1_A(1)
+	{ CCI_REG8(0x107F), 0x0 }, //  Vertical ROI.VSTART1_A(2)
+	{ CCI_REG8(0x1087), 0x78 }, //  Vertical ROI.VSIZE1_A(0)
+	{ CCI_REG8(0x1088), 0x5 }, //  Vertical ROI.VSIZE1_A(1)
+	{ CCI_REG8(0x1089), 0x0 }, //  Vertical ROI.VSIZE1_A(2)
+	{ CCI_REG8(0x1105), 0x0 }, //  Vertical ROI.VSTART1_B(0)
+	{ CCI_REG8(0x1106), 0x0 }, //  Vertical ROI.VSTART1_B(1)
+	{ CCI_REG8(0x1107), 0x0 }, //  Vertical ROI.VSTART1_B(2)
+	{ CCI_REG8(0x110A), 0x78 }, //  Vertical ROI.VSIZE1_B(0)
+	{ CCI_REG8(0x110B), 0x5 }, //  Vertical ROI.VSIZE1_B(1)
+	{ CCI_REG8(0x110C), 0x0 }, //  Vertical ROI.VSIZE1_B(2)
+	{ CCI_REG8(0x107D), 0x0 }, //  Vertical ROI.VSTART2_A(0)
+	{ CCI_REG8(0x107E), 0x0 }, //  Vertical ROI.VSTART2_A(1)
+	{ CCI_REG8(0x107F), 0x0 }, //  Vertical ROI.VSTART2_A(2)
+	{ CCI_REG8(0x1080), 0x0 }, //  Vertical ROI.VSTART2_A(3)
+	{ CCI_REG8(0x1081), 0x0 }, //  Vertical ROI.VSTART2_A(4)
+	{ CCI_REG8(0x1087), 0x78 }, //  Vertical ROI.VSIZE2_A(0)
+	{ CCI_REG8(0x1088), 0x5 }, //  Vertical ROI.VSIZE2_A(1)
+	{ CCI_REG8(0x1089), 0x0 }, //  Vertical ROI.VSIZE2_A(2)
+	{ CCI_REG8(0x108A), 0x0 }, //  Vertical ROI.VSIZE2_A(3)
+	{ CCI_REG8(0x108B), 0x0 }, //  Vertical ROI.VSIZE2_A(4)
+	{ CCI_REG8(0x1105), 0x0 }, //  Vertical ROI.VSTART2_B(0)
+	{ CCI_REG8(0x1106), 0x0 }, //  Vertical ROI.VSTART2_B(1)
+	{ CCI_REG8(0x1107), 0x0 }, //  Vertical ROI.VSTART2_B(2)
+	{ CCI_REG8(0x1108), 0x0 }, //  Vertical ROI.VSTART2_B(3)
+	{ CCI_REG8(0x1109), 0x0 }, //  Vertical ROI.VSTART2_B(4)
+	{ CCI_REG8(0x110A), 0x78 }, //  Vertical ROI.VSIZE2_B(0)
+	{ CCI_REG8(0x110B), 0x5 }, //  Vertical ROI.VSIZE2_B(1)
+	{ CCI_REG8(0x110C), 0x0 }, //  Vertical ROI.VSIZE2_B(2)
+	{ CCI_REG8(0x110D), 0x0 }, //  Vertical ROI.VSIZE2_B(3)
+	{ CCI_REG8(0x110E), 0x0 }, //  Vertical ROI.VSIZE2_B(4)
+	/* Mirror and Flip */
+	{ CCI_REG8(0x209C), 0x0 }, //  Mirroring && Flipping.HFLIP_A(0)
+	{ CCI_REG8(0x209D), 0x0 }, //  Mirroring && Flipping.HFLIP_B(0)
+	{ CCI_REG8(0x1095), 0x0 }, //  Mirroring && Flipping.VFLIP(0)
+	{ CCI_REG8(0x2063), 0x0 }, //  Mirroring && Flipping.BIT_ORDER(0)
+	/* MIPI */
+	{ CCI_REG8(0x6006), 0x0 }, //  MIPI.TX_CTRL_EN(0)
+	{ CCI_REG8(0x5004), 0x1 }, //  MIPI.datarate
+	{ CCI_REG8(0x5086), 0x2 }, //  MIPI.datarate
+	{ CCI_REG8(0x5087), 0x4E }, //  MIPI.datarate
+	{ CCI_REG8(0x5088), 0x0 }, //  MIPI.datarate
+	{ CCI_REG8(0x5090), 0x0 }, //  MIPI.datarate
+	{ CCI_REG8(0x5091), 0x8 }, //  MIPI.datarate
+	{ CCI_REG8(0x5092), 0x14 }, //  MIPI.datarate
+	{ CCI_REG8(0x5093), 0xF }, //  MIPI.datarate
+	{ CCI_REG8(0x5094), 0x6 }, //  MIPI.datarate
+	{ CCI_REG8(0x5095), 0x32 }, //  MIPI.datarate
+	{ CCI_REG8(0x5096), 0xE }, //  MIPI.datarate
+	{ CCI_REG8(0x5097), 0x0 }, //  MIPI.datarate
+	{ CCI_REG8(0x5098), 0x11 }, //  MIPI.datarate
+	{ CCI_REG8(0x5004), 0x0 }, //  MIPI.datarate
+	{ CCI_REG8(0x2066), 0x6C }, //  MIPI.datarate
+	{ CCI_REG8(0x2067), 0x7 }, //  MIPI.datarate
+	{ CCI_REG8(0x206E), 0x7E }, //  MIPI.datarate
+	{ CCI_REG8(0x206F), 0x6 }, //  MIPI.datarate
+	{ CCI_REG8(0x20AC), 0x7E }, //  MIPI.datarate
+	{ CCI_REG8(0x20AD), 0x6 }, //  MIPI.datarate
+	{ CCI_REG8(0x2076), 0xC8 }, //  MIPI.datarate
+	{ CCI_REG8(0x2077), 0x0 }, //  MIPI.datarate
+	{ CCI_REG8(0x20B4), 0xC8 }, //  MIPI.datarate
+	{ CCI_REG8(0x20B5), 0x0 }, //  MIPI.datarate
+	{ CCI_REG8(0x2078), 0x1E }, //  MIPI.datarate
+	{ CCI_REG8(0x2079), 0x4 }, //  MIPI.datarate
+	{ CCI_REG8(0x20B6), 0x1E }, //  MIPI.datarate
+	{ CCI_REG8(0x20B7), 0x4 }, //  MIPI.datarate
+	{ CCI_REG8(0x207A), 0xD4 }, //  MIPI.datarate
+	{ CCI_REG8(0x207B), 0x4 }, //  MIPI.datarate
+	{ CCI_REG8(0x20B8), 0xD4 }, //  MIPI.datarate
+	{ CCI_REG8(0x20B9), 0x4 }, //  MIPI.datarate
+	{ CCI_REG8(0x208D), 0x4 }, //  MIPI.CSI2_DTYPE(0)
+	{ CCI_REG8(0x208E), 0x0 }, //  MIPI.CSI2_DTYPE(1)
+	{ CCI_REG8(0x207C), 0x0 }, //  MIPI.VC_ID(0)
+	{ CCI_REG8(0x6001), 0x7 }, //  MIPI.TINIT(0)
+	{ CCI_REG8(0x6002), 0xD8 }, //  MIPI.TINIT(1)
+	{ CCI_REG8(0x6010), 0x0 }, //  MIPI.FRAME_MODE(0)
+	{ CCI_REG8(0x6010), 0x0 }, //  MIPI.EMBEDDED_FRAME_MODE(0)
+	{ CCI_REG8(0x6011), 0x0 }, //  MIPI.DATA_ENABLE_POLARITY(0)
+	{ CCI_REG8(0x6011), 0x0 }, //  MIPI.HSYNC_POLARITY(0)
+	{ CCI_REG8(0x6011), 0x0 }, //  MIPI.VSYNC_POLARITY(0)
+	{ CCI_REG8(0x6012), 0x1 }, //  MIPI.LANE(0)
+	{ CCI_REG8(0x6013), 0x0 }, //  MIPI.CLK_MODE(0)
+	{ CCI_REG8(0x6016), 0x0 }, //  MIPI.FRAME_COUNTER(0)
+	{ CCI_REG8(0x6017), 0x0 }, //  MIPI.FRAME_COUNTER(1)
+	{ CCI_REG8(0x6037), 0x1 }, //  MIPI.LINE_COUNT_RAW8(0)
+	{ CCI_REG8(0x6037), 0x3 }, //  MIPI.LINE_COUNT_RAW10(0)
+	{ CCI_REG8(0x6037), 0x7 }, //  MIPI.LINE_COUNT_RAW12(0)
+	{ CCI_REG8(0x6039), 0x1 }, //  MIPI.LINE_COUNT_EMB(0)
+	{ CCI_REG8(0x6018), 0x0 }, //  MIPI.CCI_READ_INTERRUPT_EN(0)
+	{ CCI_REG8(0x6018), 0x0 }, //  MIPI.CCI_WRITE_INTERRUPT_EN(0)
+	{ CCI_REG8(0x6065), 0x0 }, //  MIPI.TWAKE_TIMER(0)
+	{ CCI_REG8(0x6066), 0x0 }, //  MIPI.TWAKE_TIMER(1)
+	{ CCI_REG8(0x601C), 0x0 }, //  MIPI.SKEW_CAL_EN(0)
+	{ CCI_REG8(0x601D), 0x0 }, //  MIPI.SKEW_COUNT(0)
+	{ CCI_REG8(0x601E), 0x22 }, //  MIPI.SKEW_COUNT(1)
+	{ CCI_REG8(0x601F), 0x0 }, //  MIPI.SCRAMBLING_EN(0)
+	{ CCI_REG8(0x6003), 0x1 }, //  MIPI.INIT_SKEW_EN(0)
+	{ CCI_REG8(0x6004), 0x7A }, //  MIPI.INIT_SKEW(0)
+	{ CCI_REG8(0x6005), 0x12 }, //  MIPI.INIT_SKEW(1)
+	{ CCI_REG8(0x6006), 0x1 }, //  MIPI.TX_CTRL_EN(0)
+	/* Processing */
+	{ CCI_REG8(0x4006), 0x8 }, //  Processing.BSP(0)
+	{ CCI_REG8(0x209E), 0x2 }, //  Processing.BIT_DEPTH(0)
+	{ CCI_REG8(0x2045), 0x1 }, //  Processing.CDS_RNC(0)
+	{ CCI_REG8(0x2048), 0x1 }, //  Processing.CDS_IMG(0)
+	{ CCI_REG8(0x204B), 0x3 }, //  Processing.RNC_EN(0)
+	{ CCI_REG8(0x205B), 0x64 }, //  Processing.RNC_DARK_TARGET(0)
+	{ CCI_REG8(0x205C), 0x0 }, //  Processing.RNC_DARK_TARGET(1)
+	{ CCI_REG8(0x24DC), 0x12 }, //  Defect Pixel Correction.DC_ENABLE(0)
+	{ CCI_REG8(0x24DC), 0x10 }, //  Defect Pixel Correction.DC_MODE(0)
+	{ CCI_REG8(0x24DC),	0x0 }, //  Defect Pixel Correction.DC_REPLACEMENT_VALUE(0)
+	{ CCI_REG8(0x24DD), 0x0 }, //  Defect Pixel Correction.DC_LIMIT_LOW(0)
+	{ CCI_REG8(0x24DE), 0x0 }, //  Defect Pixel Correction.DC_LIMIT_HIGH(0)
+	{ CCI_REG8(0x24DF),	0x0 }, //  Defect Pixel Correction.DC_LIMIT_HIGH_MODE(0)
+	/* Illumination */
+	{ CCI_REG8(0x10D7), 0x1 }, //  Illumination Trigger.ILLUM_EN(0)
+	{ CCI_REG8(0x10D8), 0x2 }, //  Illumination Trigger.ILLUM_POL(0)
+	/* Histogram */
+	{ CCI_REG8(0x205D), 0x0 }, //  Histogram.HIST_EN(0)
+	{ CCI_REG8(0x205E), 0x0 }, //  Histogram.HIST_USAGE_RATIO(0)
+	{ CCI_REG8(0x2063), 0x0 }, //  Histogram.PIXEL_DATA_SUPP(0)
+	{ CCI_REG8(0x2063), 0x0 }, //  Histogram.PIXEL_TRANSMISSION(0)
+	/* TP */
+	{ CCI_REG8(0x2091), 0x0 }, //  Test Pattern Generator.TPG_EN(0)
+	{ CCI_REG8(0x2091), 0x0 }, //  Test Pattern Generator.TPG_CONFIG(0)
+};
+
+static const char *const mira220_test_pattern_menu[] = {
+	"Disabled",
+	"Vertial Gradient",
+};
+
+static const int mira220_test_pattern_val[] = {
+	MIRA220_TEST_PATTERN_DISABLE,
+	MIRA220_TEST_PATTERN_VERTICAL_GRADIENT,
+};
+
+/* regulator supplies */
+static const char *const mira220_supply_name[] = {
+	/* Supplies can be enabled in any order */
+	"vana", /* Analog (2.8V) supply */
+	"vdig", /* Digital Core (1.8V) supply */
+	"vddl", /* IF (1.2V) supply */
+};
+
+#define MIRA220_NUM_SUPPLIES ARRAY_SIZE(mira220_supply_name)
+
+
+// Mira220 comes in monochrome and RGB variants. This driver implements the RGB variant.
+/*
+ * The supported formats.
+ * This table MUST contain 4 entries per format, to cover the various flip
+ * combinations in the order
+ * - no flip
+ * - h flip
+ * - v flip
+ * - h&v flips
+ */
+static const u32 mira220_mbus_formats[] = {
+	MEDIA_BUS_FMT_SRGGB12_1X12,
+	MEDIA_BUS_FMT_SGRBG12_1X12,
+	MEDIA_BUS_FMT_SGBRG12_1X12,
+	MEDIA_BUS_FMT_SBGGR12_1X12,
+
+	MEDIA_BUS_FMT_SRGGB10_1X10,
+	MEDIA_BUS_FMT_SGRBG10_1X10,
+	MEDIA_BUS_FMT_SGBRG10_1X10,
+	MEDIA_BUS_FMT_SBGGR10_1X10,
+
+	MEDIA_BUS_FMT_SRGGB8_1X8,
+	MEDIA_BUS_FMT_SGRBG8_1X8,
+	MEDIA_BUS_FMT_SGBRG8_1X8,
+	MEDIA_BUS_FMT_SBGGR8_1X8,
+
+};
+
+/* Mode configs */
+static const struct mira220_mode supported_modes[] = {
+	/* 2 MPx 30fps 12bpp mode */
+	{
+		.width = 1600,
+		.height = 1400,
+		.crop = {
+			.left = MIRA220_PIXEL_ARRAY_LEFT,
+			.top = MIRA220_PIXEL_ARRAY_TOP,
+			.width = 1600,
+			.height = 1400
+		},
+		.reg_list = {
+			.num_of_regs = ARRAY_SIZE(full_1600_1400_1500_12b_2lanes_reg_new),
+			.regs = full_1600_1400_1500_12b_2lanes_reg_new,
+		},
+		// vblank is ceil(MIRA220_GLOB_NUM_CLK_CYCLES / ROW_LENGTH)  + 11
+		// ROW_LENGTH is configured by register 0x102B, 0x102C.
+		.row_length = 304,
+		.pixel_rate = MIRA220_PIXEL_RATE,
+		.min_vblank = 20,
+		.max_vblank = 50000,
+		.hblank = MIRA220_HBLANK_1600x1400_304,
+	},
+};
+
+struct mira220 {
+	struct v4l2_subdev sd;
+	struct media_pad pad;
+
+	struct v4l2_mbus_framefmt fmt;
+
+	struct clk *xclk; /* system clock to MIRA220 */
+	u32 xclk_freq;
+
+	struct regulator_bulk_data supplies[MIRA220_NUM_SUPPLIES];
+
+	struct v4l2_ctrl_handler ctrl_handler;
+	struct v4l2_ctrl *pixel_rate;
+	struct v4l2_ctrl *vflip;
+	struct v4l2_ctrl *hflip;
+	struct v4l2_ctrl *vblank;
+	struct v4l2_ctrl *hblank;
+	struct v4l2_ctrl *exposure;
+	struct v4l2_ctrl *gain;
+
+	/* Current mode */
+	const struct mira220_mode *mode;
+
+	struct mutex mutex;
+
+	struct regmap *regmap;
+};
+
+static inline struct mira220 *to_mira220(struct v4l2_subdev *_sd)
+{
+	return container_of(_sd, struct mira220, sd);
+}
+
+/* Power/clock management functions */
+static int mira220_power_on(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct mira220 *mira220 = to_mira220(sd);
+	int ret = -EINVAL;
+
+	ret = regulator_bulk_enable(MIRA220_NUM_SUPPLIES, mira220->supplies);
+	if (ret) {
+		dev_err(&client->dev, "%s: failed to enable regulators\n",
+			__func__);
+		goto reg_off;
+	}
+	ret = clk_prepare_enable(mira220->xclk);
+	if (ret) {
+		dev_err(&client->dev, "%s: failed to enable clock\n", __func__);
+		goto clk_off;
+	}
+	fsleep(MIRA220_XCLR_MIN_DELAY_US);
+
+	return 0;
+
+clk_off:
+	clk_disable_unprepare(mira220->xclk);
+reg_off:
+	ret = regulator_bulk_disable(MIRA220_NUM_SUPPLIES, mira220->supplies);
+	return ret;
+}
+
+static int mira220_power_off(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct mira220 *mira220 = to_mira220(sd);
+	(void)mira220;
+
+	clk_disable_unprepare(mira220->xclk);
+	regulator_bulk_disable(MIRA220_NUM_SUPPLIES, mira220->supplies);
+
+	return 0;
+}
+
+static int mira220_write_start_streaming_regs(struct mira220 *mira220)
+{
+	struct i2c_client *const client = v4l2_get_subdevdata(&mira220->sd);
+	int ret = 0;
+
+	// Setting master control
+	ret = cci_write(mira220->regmap, MIRA220_IMAGER_STATE_REG,
+			MIRA220_IMAGER_STATE_MASTER_CONTROL, NULL);
+	if (ret) {
+		dev_err(&client->dev, "Error setting master control");
+		return ret;
+	}
+
+	// Enable continuous streaming
+	ret = cci_write(mira220->regmap, MIRA220_IMAGER_RUN_CONT_REG,
+			MIRA220_IMAGER_RUN_CONT_ENABLE, NULL);
+	if (ret) {
+		dev_err(&client->dev, "Error enabling continuous streaming");
+		return ret;
+	}
+
+	ret = cci_write(mira220->regmap, MIRA220_IMAGER_RUN_REG,
+			MIRA220_IMAGER_RUN_START, NULL);
+	if (ret) {
+		dev_err(&client->dev, "Error setting internal trigger");
+		return ret;
+	}
+
+	return ret;
+}
+
+static int mira220_write_stop_streaming_regs(struct mira220 *mira220)
+{
+	struct i2c_client *const client = v4l2_get_subdevdata(&mira220->sd);
+	int ret = 0;
+
+	ret = cci_write(mira220->regmap, MIRA220_IMAGER_STATE_REG,
+			MIRA220_IMAGER_STATE_STOP_AT_ROW, NULL);
+
+	if (ret) {
+		dev_err(&client->dev,
+			"Error setting stop-at-row imager state after multiple attempts. Exiting.");
+		return ret;
+	}
+
+	ret = cci_write(mira220->regmap, MIRA220_IMAGER_RUN_REG,
+			MIRA220_IMAGER_RUN_STOP, NULL);
+	if (ret) {
+		dev_err(&client->dev, "Error setting run reg to stop");
+		return ret;
+	}
+
+	fsleep(40000);
+
+	return ret;
+}
+
+// Returns the maximum exposure time in row_length (reg value).
+// Calculation is baded on Mira220 datasheet Section 9.2.
+static u32 mira220_calculate_max_exposure_time(u32 height, u32 vblank,
+					       u32 row_length)
+{
+	return (height + vblank) -
+	       (int)(MIRA220_GLOB_NUM_CLK_CYCLES / row_length);
+}
+
+static int mira220_write_exposure_reg(struct mira220 *mira220, u32 exposure)
+{
+	struct i2c_client *const client = v4l2_get_subdevdata(&mira220->sd);
+	const u32 max_exposure = mira220_calculate_max_exposure_time(
+		mira220->mode->height, mira220->vblank->val,
+		mira220->mode->row_length);
+	u32 ret = 0;
+
+	u32 capped_exposure = exposure;
+
+	if (exposure > max_exposure)
+		capped_exposure = max_exposure;
+
+
+	ret = cci_write(mira220->regmap, MIRA220_EXP_TIME_REG, capped_exposure,
+			NULL);
+
+
+	if (ret) {
+		dev_err_ratelimited(&client->dev,
+				    "Error setting exposure time to %d",
+				    capped_exposure);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* Get bayer order based on flip setting. */
+static u32 mira220_get_format_code(struct mira220 *mira220, u32 code)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(mira220_mbus_formats); i++)
+		if (mira220_mbus_formats[i] == code)
+			break;
+
+	if (i >= ARRAY_SIZE(mira220_mbus_formats))
+		i = 0;
+
+	i = (i & ~3) | (mira220->vflip->val ? 2 : 0) | (mira220->hflip->val ? 0 : 1);
+
+
+	return mira220_mbus_formats[i];
+}
+
+
+static int mira220_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct mira220 *mira220 =
+		container_of(ctrl->handler, struct mira220, ctrl_handler);
+	struct i2c_client *client = v4l2_get_subdevdata(&mira220->sd);
+	int ret = 0;
+
+	if (ctrl->id == V4L2_CID_VBLANK) {
+		int exposure_max, exposure_def;
+
+		/* Update max exposure while meeting expected vblanking */
+		exposure_max = mira220_calculate_max_exposure_time(
+			mira220->mode->height, ctrl->val,
+			mira220->mode->row_length);
+		exposure_def = (exposure_max < MIRA220_DEFAULT_EXPOSURE) ?
+				       exposure_max :
+				       MIRA220_DEFAULT_EXPOSURE;
+		__v4l2_ctrl_modify_range(mira220->exposure,
+					 mira220->exposure->minimum,
+					 exposure_max, mira220->exposure->step,
+					 exposure_def);
+	}
+
+	/*
+	 * Applying V4L2 control value only happens
+	 * when power is up for streaming
+	 */
+
+	if (pm_runtime_get_if_in_use(&client->dev) == 0) {
+		dev_info(
+			&client->dev,
+			"device in use, ctrl(id:0x%x,val:0x%x) is not handled\n",
+			ctrl->id, ctrl->val);
+		return 0;
+	}
+
+	switch (ctrl->id) {
+	case V4L2_CID_ANALOGUE_GAIN:
+		break;
+	case V4L2_CID_EXPOSURE:
+		ret = mira220_write_exposure_reg(mira220, ctrl->val);
+		break;
+	case V4L2_CID_TEST_PATTERN:
+		ret = cci_write(mira220->regmap, MIRA220_REG_TEST_PATTERN,
+				mira220_test_pattern_val[ctrl->val], NULL);
+		break;
+	case V4L2_CID_HFLIP:
+		ret = cci_write(mira220->regmap, MIRA220_HFLIP_REG, mira220->hflip->val,
+		NULL);
+		break;
+
+	case V4L2_CID_VFLIP:
+		ret = cci_write(mira220->regmap, MIRA220_VFLIP_REG, mira220->vflip->val,
+				NULL);
+		break;
+	case V4L2_CID_VBLANK:
+		ret = cci_write(mira220->regmap, MIRA220_VBLANK_REG, ctrl->val,
+				NULL);
+
+		break;
+	case V4L2_CID_HBLANK:
+		break;
+	default:
+		dev_info(&client->dev,
+			 "ctrl(id:0x%x,val:0x%x) is not handled\n", ctrl->id,
+			 ctrl->val);
+		ret = -EINVAL;
+		break;
+	}
+
+	pm_runtime_put(&client->dev);
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops mira220_ctrl_ops = {
+	.s_ctrl = mira220_set_ctrl,
+};
+
+
+static void mira220_update_pad_format(struct mira220 *mira220,
+	const struct mira220_mode *mode,
+	struct v4l2_mbus_framefmt *fmt, u32 code)
+{
+	/* Bayer order varies with flips */
+	fmt->code = mira220_get_format_code(mira220, code);
+	fmt->width = mode->width;
+	fmt->height = mode->height;
+	fmt->field = V4L2_FIELD_NONE;
+	fmt->colorspace = V4L2_COLORSPACE_RAW;
+	fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
+	fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
+	fmt->xfer_func = V4L2_XFER_FUNC_NONE;
+}
+
+static int mira220_set_pad_format(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *state,
+				  struct v4l2_subdev_format *fmt)
+{
+	struct mira220 *mira220 = to_mira220(sd);
+	const struct mira220_mode *mode;
+	struct v4l2_mbus_framefmt *format;
+	struct v4l2_rect *crop;
+
+	u32 max_exposure = 0, default_exp = 0;
+
+	// /* Validate format or use default */
+
+
+	mode = v4l2_find_nearest_size(supported_modes,
+				      ARRAY_SIZE(supported_modes), width,
+				      height, fmt->format.width,
+				      fmt->format.height);
+
+	mira220_update_pad_format(mira220, mode, &fmt->format, fmt->format.code);
+
+	format = v4l2_subdev_state_get_format(state, 0);
+	*format = fmt->format;
+
+	crop = v4l2_subdev_state_get_crop(state, 0);
+	crop->width = format->width * 1;
+	crop->height = format->height * 1;
+	crop->left = MIRA220_PIXEL_ARRAY_LEFT;
+	crop->top = MIRA220_PIXEL_ARRAY_TOP;
+
+	if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) {
+		// mira220->fmt = fmt->format;
+		// mira220->mode = mode;
+
+		// Update controls based on new mode (range and current value).
+		max_exposure = mira220_calculate_max_exposure_time(
+			mira220->mode->height, mira220->mode->min_vblank,
+			mira220->mode->row_length);
+		default_exp = (max_exposure < MIRA220_DEFAULT_EXPOSURE) ?
+				      max_exposure :
+				      MIRA220_DEFAULT_EXPOSURE;
+		__v4l2_ctrl_modify_range(mira220->exposure,
+					 MIRA220_EXPOSURE_MIN, max_exposure, 1,
+					 default_exp);
+
+		// Update pixel rate based on new mode.
+		__v4l2_ctrl_modify_range(mira220->pixel_rate,
+					 mira220->mode->pixel_rate,
+					 mira220->mode->pixel_rate, 1,
+					 mira220->mode->pixel_rate);
+
+		// Update hblank based on new mode.
+		__v4l2_ctrl_modify_range(mira220->hblank, mira220->mode->hblank,
+					 mira220->mode->hblank, 1,
+					 mira220->mode->hblank);
+
+		__v4l2_ctrl_modify_range(mira220->vblank,
+					 mira220->mode->min_vblank,
+					 mira220->mode->max_vblank, 1,
+					 mira220->mode->min_vblank);
+
+		__v4l2_ctrl_s_ctrl(mira220->vblank, mira220->mode->min_vblank);
+	}
+
+	return 0;
+}
+
+// This function should enumerate all the media bus formats for the requested pads. If the requested
+// format index is beyond the number of avaialble formats it shall return -EINVAL;
+static int mira220_enum_mbus_code(struct v4l2_subdev *sd,
+				  struct v4l2_subdev_state *state,
+				  struct v4l2_subdev_mbus_code_enum *code)
+{
+	struct mira220 *mira220 = to_mira220(sd);
+
+	if (code->index >= (ARRAY_SIZE(mira220_mbus_formats) / 4))
+		return -EINVAL;
+
+	code->code = mira220_get_format_code(
+		mira220, mira220_mbus_formats[code->index * 4]);
+
+	return 0;
+}
+
+static int mira220_enum_frame_size(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *state,
+				   struct v4l2_subdev_frame_size_enum *fse)
+{
+	struct mira220 *mira220 = to_mira220(sd);
+	u32 code;
+
+	if (fse->index >= ARRAY_SIZE(supported_modes))
+		return -EINVAL;
+
+	code = mira220_get_format_code(mira220, fse->code);
+	if (fse->code != code)
+		return -EINVAL;
+
+	fse->min_width = supported_modes[fse->index].width;
+	fse->max_width = fse->min_width;
+	fse->min_height = supported_modes[fse->index].height;
+	fse->max_height = fse->min_height;
+
+
+	return 0;
+}
+
+static int mira220_init_state(struct v4l2_subdev *sd,
+			      struct v4l2_subdev_state *state)
+{
+	struct v4l2_subdev_format fmt = {
+		.which = V4L2_SUBDEV_FORMAT_TRY,
+		.pad = 0,
+		.format = {
+			.code = MEDIA_BUS_FMT_SGRBG12_1X12,
+			.width = supported_modes[0].width,
+			.height = supported_modes[0].height,
+		},
+	};
+
+	mira220_set_pad_format(sd, state, &fmt);
+
+	return 0;
+}
+
+
+static int mira220_set_framefmt(struct mira220 *mira220,
+	struct v4l2_subdev_state *state)
+{
+	const struct v4l2_mbus_framefmt *format;
+	const struct v4l2_rect *crop;
+	int ret = 0;
+
+	format = v4l2_subdev_state_get_format(state, 0);
+	crop = v4l2_subdev_state_get_crop(state, 0);
+	switch (format->code) {
+	case MEDIA_BUS_FMT_Y8_1X8:
+	case MEDIA_BUS_FMT_SRGGB8_1X8:
+	case MEDIA_BUS_FMT_SGRBG8_1X8:
+	case MEDIA_BUS_FMT_SGBRG8_1X8:
+	case MEDIA_BUS_FMT_SBGGR8_1X8:
+		cci_write(mira220->regmap, MIRA220_BIT_DEPTH_REG,
+			  MIRA220_BIT_DEPTH_8_BIT, NULL);
+		cci_write(mira220->regmap, MIRA220_CSI_DATA_TYPE_REG,
+			  MIRA220_CSI_DATA_TYPE_8_BIT, NULL);
+		break;
+	case MEDIA_BUS_FMT_Y10_1X10:
+	case MEDIA_BUS_FMT_SRGGB10_1X10:
+	case MEDIA_BUS_FMT_SGRBG10_1X10:
+	case MEDIA_BUS_FMT_SGBRG10_1X10:
+	case MEDIA_BUS_FMT_SBGGR10_1X10:
+		cci_write(mira220->regmap, MIRA220_BIT_DEPTH_REG,
+			  MIRA220_BIT_DEPTH_10_BIT, NULL);
+		cci_write(mira220->regmap, MIRA220_CSI_DATA_TYPE_REG,
+			  MIRA220_CSI_DATA_TYPE_10_BIT, NULL);
+
+		break;
+	case MEDIA_BUS_FMT_Y12_1X12:
+	case MEDIA_BUS_FMT_SGRBG12_1X12:
+	case MEDIA_BUS_FMT_SGBRG12_1X12:
+	case MEDIA_BUS_FMT_SBGGR12_1X12:
+	case MEDIA_BUS_FMT_SRGGB12_1X12:
+		cci_write(mira220->regmap, MIRA220_BIT_DEPTH_REG,
+			  MIRA220_BIT_DEPTH_12_BIT, NULL);
+		cci_write(mira220->regmap, MIRA220_CSI_DATA_TYPE_REG,
+			  MIRA220_CSI_DATA_TYPE_12_BIT, NULL);
+
+		break;
+	default:
+			ret = -EINVAL;
+			break;
+	}
+
+	return ret;
+}
+
+
+static int mira220_get_selection(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *state,
+				 struct v4l2_subdev_selection *sel)
+{
+	switch (sel->target) {
+	case V4L2_SEL_TGT_CROP: {
+		sel->r = *v4l2_subdev_state_get_crop(state, 0);
+		return 0;
+	}
+
+	case V4L2_SEL_TGT_NATIVE_SIZE:
+		sel->r.top = 0;
+		sel->r.left = 0;
+		sel->r.width = MIRA220_NATIVE_WIDTH;
+		sel->r.height = MIRA220_NATIVE_HEIGHT;
+		return 0;
+
+	case V4L2_SEL_TGT_CROP_DEFAULT:
+	case V4L2_SEL_TGT_CROP_BOUNDS:
+		sel->r.top = MIRA220_PIXEL_ARRAY_TOP;
+		sel->r.left = MIRA220_PIXEL_ARRAY_LEFT;
+		sel->r.width = MIRA220_PIXEL_ARRAY_WIDTH;
+		sel->r.height = MIRA220_PIXEL_ARRAY_HEIGHT;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int mira220_start_streaming(struct mira220 *mira220,
+				struct v4l2_subdev_state *state)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&mira220->sd);
+	const struct mira220_reg_list *reg_list;
+	int ret;
+	/* Follow examples of other camera driver, here use pm_runtime_resume_and_get */
+
+	ret = pm_runtime_resume_and_get(&client->dev);
+
+	if (ret < 0) {
+		pm_runtime_put_noidle(&client->dev);
+		return ret;
+	}
+
+	/* Apply default values of current mode */
+	/* Stop treaming before uploading register sequence */
+	ret = mira220_write_stop_streaming_regs(mira220);
+	if (ret) {
+		dev_err(&client->dev, "Could not write stream-on sequence");
+		goto err_rpm_put;
+	}
+
+	reg_list = &mira220->mode->reg_list;
+	ret = cci_multi_reg_write(mira220->regmap, reg_list->regs,
+				  reg_list->num_of_regs, NULL);
+	if (ret) {
+		dev_err(&client->dev, "%s failed to set mode\n", __func__);
+		goto err_rpm_put;
+	}
+
+	ret = mira220_set_framefmt(mira220, state);
+	if (ret) {
+		dev_err(&client->dev, "%s failed to set frame format: %d\n",
+			__func__, ret);
+		goto err_rpm_put;
+	}
+
+	/* Apply customized values from user */
+	ret = __v4l2_ctrl_handler_setup(mira220->sd.ctrl_handler);
+	if (ret)
+		goto err_rpm_put;
+
+	ret = mira220_write_start_streaming_regs(mira220);
+	if (ret) {
+		dev_err(&client->dev, "Could not write stream-on sequence");
+		goto err_rpm_put;
+	}
+	/* vflip and hflip cannot change during streaming */
+	__v4l2_ctrl_grab(mira220->hflip, true);
+	__v4l2_ctrl_grab(mira220->vflip, true);
+
+	return 0;
+
+err_rpm_put:
+	pm_runtime_put(&client->dev);
+	return ret;
+}
+
+static void mira220_stop_streaming(struct mira220 *mira220)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&mira220->sd);
+	int ret = 0;
+
+	ret = mira220_write_stop_streaming_regs(mira220);
+	if (ret) {
+		dev_err(&client->dev,
+			"Could not write the stream-off sequence");
+	}
+	__v4l2_ctrl_grab(mira220->hflip, false);
+	__v4l2_ctrl_grab(mira220->vflip, false);
+	pm_runtime_put(&client->dev);
+}
+
+static int mira220_set_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct mira220 *mira220 = to_mira220(sd);
+	struct v4l2_subdev_state *state;
+	int ret = 0;
+
+	state = v4l2_subdev_lock_and_get_active_state(sd);
+
+	if (enable)
+		ret = mira220_start_streaming(mira220, state);
+	else
+		mira220_stop_streaming(mira220);
+
+	v4l2_subdev_unlock_state(state);
+	return ret;
+}
+
+static int mira220_get_regulators(struct mira220 *mira220)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&mira220->sd);
+	unsigned int i;
+
+	for (i = 0; i < MIRA220_NUM_SUPPLIES; i++)
+		mira220->supplies[i].supply = mira220_supply_name[i];
+
+	return devm_regulator_bulk_get(&client->dev, MIRA220_NUM_SUPPLIES,
+				       mira220->supplies);
+}
+
+/* OTP power on */
+static void mira220_otp_power_on(struct mira220 *mira220)
+{
+	int ret;
+
+	ret = cci_write(mira220->regmap, MIRA220_OTP_CMD_REG,
+			MIRA220_OTP_CMD_UP, NULL);
+
+}
+
+/* OTP power off */
+static void mira220_otp_power_off(struct mira220 *mira220)
+{
+	int ret;
+
+	ret = cci_write(mira220->regmap, MIRA220_OTP_CMD_REG,
+			MIRA220_OTP_CMD_DOWN, NULL);
+
+}
+
+/* OTP power on */
+static int mira220_otp_read(struct mira220 *mira220, u8 addr, u8 offset,
+			    u8 *val)
+{
+	int ret;
+	u64 readback;
+
+	ret = cci_write(mira220->regmap, CCI_REG8(0x0086), addr, NULL);
+	ret = cci_write(mira220->regmap, CCI_REG8(0x0080), 0x02, NULL);
+	ret = cci_read(mira220->regmap, CCI_REG8(0x0082 + offset), &readback,
+		       NULL);
+	*val = readback & 0xFF;
+
+	return ret;
+}
+
+/* Verify chip ID */
+static int mira220_identify_module(struct mira220 *mira220)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&mira220->sd);
+	int ret;
+	u8 val;
+
+	mira220_otp_power_on(mira220);
+
+	fsleep(100);
+
+	ret = mira220_otp_read(mira220, 0x0d, 0, &val);
+	dev_err(&client->dev, "Read OTP add 0x0d with val %x\n", val);
+	ret = mira220_otp_read(mira220, 0x19, 0, &val);
+	dev_err(&client->dev, "Read OTP add 0x19 with val %x\n", val);
+	ret = mira220_otp_read(mira220, 0x19, 1, &val);
+	dev_err(&client->dev, "Read OTP add 0x19+1 with val %x\n", val);
+
+	mira220_otp_power_off(mira220);
+
+	return ret;
+}
+
+static const struct v4l2_subdev_core_ops mira220_core_ops = {
+	.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_video_ops mira220_video_ops = {
+	.s_stream = mira220_set_stream,
+};
+
+static const struct v4l2_subdev_pad_ops mira220_pad_ops = {
+	.enum_mbus_code = mira220_enum_mbus_code,
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = mira220_set_pad_format,
+	.get_selection = mira220_get_selection,
+	.enum_frame_size = mira220_enum_frame_size,
+};
+
+static const struct v4l2_subdev_ops mira220_subdev_ops = {
+	.core = &mira220_core_ops,
+	.video = &mira220_video_ops,
+	.pad = &mira220_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops mira220_internal_ops = {
+	.init_state = mira220_init_state,
+};
+
+/* Initialize control handlers */
+static int mira220_init_controls(struct mira220 *mira220)
+{
+	struct i2c_client *client = v4l2_get_subdevdata(&mira220->sd);
+	struct v4l2_ctrl_handler *ctrl_hdlr;
+	struct v4l2_fwnode_device_properties props;
+	int ret;
+
+	u32 max_exposure = 0;
+
+	ctrl_hdlr = &mira220->ctrl_handler;
+	/* v4l2_ctrl_handler_init gives a hint/guess of the number of v4l2_ctrl_new */
+	ret = v4l2_ctrl_handler_init(ctrl_hdlr, 9);
+	if (ret)
+		return ret;
+
+	mutex_init(&mira220->mutex);
+	ctrl_hdlr->lock = &mira220->mutex;
+	/* By default, PIXEL_RATE is read only */
+	mira220->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &mira220_ctrl_ops,
+						V4L2_CID_PIXEL_RATE,
+						mira220->mode->pixel_rate,
+						mira220->mode->pixel_rate, 1,
+						mira220->mode->pixel_rate);
+
+	mira220->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &mira220_ctrl_ops,
+					    V4L2_CID_VBLANK,
+					    mira220->mode->min_vblank,
+					    mira220->mode->max_vblank, 1,
+					    mira220->mode->min_vblank);
+
+	mira220->hblank =
+		v4l2_ctrl_new_std(ctrl_hdlr, &mira220_ctrl_ops, V4L2_CID_HBLANK,
+				  mira220->mode->hblank, mira220->mode->hblank,
+				  1, mira220->mode->hblank);
+
+	// Exposure is indicated in number of lines here
+	// Max is determined by vblank + vsize and Tglob.
+	max_exposure = mira220_calculate_max_exposure_time(
+		mira220->mode->height, mira220->vblank->val,
+		mira220->mode->row_length);
+
+	mira220->exposure =
+		v4l2_ctrl_new_std(ctrl_hdlr, &mira220_ctrl_ops,
+				  V4L2_CID_EXPOSURE, MIRA220_EXPOSURE_MIN,
+				  max_exposure, 1, MIRA220_DEFAULT_EXPOSURE);
+
+	mira220->gain = v4l2_ctrl_new_std(
+		ctrl_hdlr, &mira220_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
+		MIRA220_ANALOG_GAIN_MIN, MIRA220_ANALOG_GAIN_MAX,
+		MIRA220_ANALOG_GAIN_STEP, MIRA220_ANALOG_GAIN_DEFAULT);
+
+	mira220->hflip = v4l2_ctrl_new_std(ctrl_hdlr, &mira220_ctrl_ops,
+					   V4L2_CID_HFLIP, 0, 1, 1, 0);
+	if (mira220->hflip)
+		mira220->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+	mira220->vflip = v4l2_ctrl_new_std(ctrl_hdlr, &mira220_ctrl_ops,
+					   V4L2_CID_VFLIP, 0, 1, 1, 0);
+	if (mira220->vflip)
+		mira220->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+	v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &mira220_ctrl_ops,
+				     V4L2_CID_TEST_PATTERN,
+				     ARRAY_SIZE(mira220_test_pattern_menu) - 1,
+				     0, 0, mira220_test_pattern_menu);
+
+	if (ctrl_hdlr->error) {
+		ret = ctrl_hdlr->error;
+		dev_err(&client->dev, "%s control init failed (%d)\n", __func__,
+			ret);
+		goto error;
+	}
+
+	ret = v4l2_fwnode_device_parse(&client->dev, &props);
+	if (ret)
+		goto error;
+
+	ret = v4l2_ctrl_new_fwnode_properties(ctrl_hdlr, &mira220_ctrl_ops,
+					      &props);
+	if (ret)
+		goto error;
+
+	mira220->sd.ctrl_handler = ctrl_hdlr;
+
+	return 0;
+
+error:
+	v4l2_ctrl_handler_free(ctrl_hdlr);
+	mutex_destroy(&mira220->mutex);
+
+	return ret;
+}
+
+static void mira220_free_controls(struct mira220 *mira220)
+{
+	v4l2_ctrl_handler_free(mira220->sd.ctrl_handler);
+	mutex_destroy(&mira220->mutex);
+}
+
+static int mira220_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct mira220 *mira220;
+	int ret;
+
+	mira220 = devm_kzalloc(&client->dev, sizeof(*mira220), GFP_KERNEL);
+	if (!mira220)
+		return -ENOMEM;
+
+	v4l2_i2c_subdev_init(&mira220->sd, client, &mira220_subdev_ops);
+	mira220->sd.internal_ops = &mira220_internal_ops;
+
+	mira220->regmap = devm_cci_regmap_init_i2c(client, 16);
+	if (IS_ERR(mira220->regmap))
+		return dev_err_probe(dev, PTR_ERR(mira220->regmap),
+				     "failed to initialize CCI\n");
+	/* Get system clock (xclk) */
+	mira220->xclk = devm_clk_get(dev, NULL);
+	if (IS_ERR(mira220->xclk)) {
+		dev_err(dev, "failed to get xclk\n");
+		return PTR_ERR(mira220->xclk);
+	}
+	mira220->xclk_freq = clk_get_rate(mira220->xclk);
+	if (mira220->xclk_freq != MIRA220_SUPPORTED_XCLK_FREQ) {
+		dev_err(dev, "xclk frequency not supported: %d Hz\n",
+			mira220->xclk_freq);
+		return -EINVAL;
+	}
+
+	ret = mira220_get_regulators(mira220);
+	if (ret) {
+		dev_err(dev, "failed to get regulators\n");
+		return ret;
+	}
+
+	fsleep(10000);
+
+	// The sensor must be powered for mira220_identify_module()
+	// to be able to read the CHIP_ID register
+
+	 ret = mira220_power_on(dev);
+	if (ret)
+		return ret;
+
+	fsleep(100000);
+
+	ret = mira220_identify_module(mira220);
+	if (ret)
+		goto error_power_off;
+
+	/* Set default mode to max resolution */
+	mira220->mode = &supported_modes[0];
+
+	ret = mira220_init_controls(mira220);
+	if (ret)
+		goto error_power_off;
+
+	/* Initialize subdev */
+	mira220->sd.internal_ops = &mira220_internal_ops;
+	mira220->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
+			     V4L2_SUBDEV_FL_HAS_EVENTS;
+	mira220->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
+
+	/* Initialize source pads */
+	mira220->pad.flags = MEDIA_PAD_FL_SOURCE;
+
+
+
+	ret = media_entity_pads_init(&mira220->sd.entity, 1, &mira220->pad);
+	if (ret) {
+		dev_err_probe(dev, ret, "failed to init entity pads\n");
+		goto error_handler_free;
+	}
+
+	mira220->sd.state_lock = mira220->ctrl_handler.lock;
+	ret = v4l2_subdev_init_finalize(&mira220->sd);
+	if (ret < 0) {
+		dev_err_probe(dev, ret, "subdev init error\n");
+		goto error_media_entity;
+	}
+
+	ret = v4l2_async_register_subdev_sensor(&mira220->sd);
+	if (ret < 0) {
+		dev_err_probe(dev, ret,
+			      "failed to register sensor sub-device\n");
+		goto error_subdev_cleanup;
+	}
+
+	/* Enable runtime PM and turn off the device */
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+	pm_runtime_idle(dev);
+
+	return 0;
+
+error_subdev_cleanup:
+	v4l2_subdev_cleanup(&mira220->sd);
+
+error_media_entity:
+	media_entity_cleanup(&mira220->sd.entity);
+
+error_handler_free:
+	mira220_free_controls(mira220);
+
+error_power_off:
+	mira220_power_off(dev);
+
+	return ret;
+}
+
+static void mira220_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct mira220 *mira220 = to_mira220(sd);
+
+	v4l2_async_unregister_subdev(sd);
+	media_entity_cleanup(&sd->entity);
+	mira220_free_controls(mira220);
+
+	pm_runtime_disable(&client->dev);
+	if (!pm_runtime_status_suspended(&client->dev))
+		mira220_power_off(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+}
+
+static const struct dev_pm_ops mira220_pm_ops = {
+		SET_RUNTIME_PM_OPS(mira220_power_off, mira220_power_on, NULL)
+};
+
+static const struct of_device_id mira220_dt_ids[] = {
+	{ .compatible = "ams,mira220" },
+	{ /* sentinel */ } };
+MODULE_DEVICE_TABLE(of, mira220_dt_ids);
+
+static struct i2c_driver mira220_i2c_driver = {
+	.driver = {
+		.name = "mira220",
+		.of_match_table	= mira220_dt_ids,
+		.pm = &mira220_pm_ops,
+	},
+	.probe = mira220_probe,
+	.remove = mira220_remove,
+};
+
+module_i2c_driver(mira220_i2c_driver);
+
+MODULE_AUTHOR("Philippe Baetens <philippe.baetens@ams-osram.com>");
+MODULE_DESCRIPTION("ams MIRA220 sensor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/i2c/ov2311.c b/drivers/media/i2c/ov2311.c
new file mode 100644
index 000000000000..3680561ad779
--- /dev/null
+++ b/drivers/media/i2c/ov2311.c
@@ -0,0 +1,1178 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Omnivision OV2311 1600x1300 global shutter image sensor driver
+ * Copyright (C) 2022, Raspberry Pi (Trading) Ltd
+ *
+ * This driver is based on the OV9281 driver.
+ * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd.
+ * Register configuration from
+ * https://github.com/ArduCAM/ArduCAM_USB_Camera_Shield/tree/master/Config/USB3.0_UC-425_Rev.C%2BUC-628_Rev.B/OV2311
+ * with additional exposure and gain register information from
+ * https://github.com/renesas-rcar/linux-bsp/tree/0cf6e36f5bf49e1c2aab87139ec5b588623c56f8/drivers/media/i2c/imagers
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <media/media-entity.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+
+#define OV2311_LINK_FREQ		400000000
+#define OV2311_LANES			2
+
+/* pixel rate = link frequency * 2 * lanes / BITS_PER_SAMPLE */
+#define OV2311_PIXEL_RATE_10BIT		(OV2311_LINK_FREQ * 2 * \
+					 OV2311_LANES / 10)
+#define OV2311_PIXEL_RATE_8BIT		(OV2311_LINK_FREQ * 2 * \
+					 OV2311_LANES / 8)
+#define OV2311_XVCLK_FREQ		24000000
+
+#define CHIP_ID				0x2311
+#define OV2311_REG_CHIP_ID		0x300a
+
+#define OV2311_REG_CTRL_MODE		0x0100
+#define OV2311_MODE_SW_STANDBY		0x0
+#define OV2311_MODE_STREAMING		BIT(0)
+
+#define OV2311_REG_V_FLIP		0x3820
+#define OV2311_REG_H_FLIP		0x3821
+#define OV2311_FLIP_BIT			BIT(2)
+
+#define OV2311_REG_EXPOSURE		0x3501
+#define	OV2311_EXPOSURE_MIN		4
+#define	OV2311_EXPOSURE_STEP		1
+#define OV2311_VTS_MAX			0xffff
+
+#define OV2311_REG_GAIN_H		0x3508
+#define OV2311_REG_GAIN_L		0x3509
+#define OV2311_GAIN_H_MASK		0x07
+#define OV2311_GAIN_H_SHIFT		8
+#define OV2311_GAIN_L_MASK		0xff
+#define OV2311_GAIN_MIN			0x100
+#define OV2311_GAIN_MAX			0x780
+#define OV2311_GAIN_STEP		1
+#define OV2311_GAIN_DEFAULT		OV2311_GAIN_MIN
+
+#define OV2311_REG_TEST_PATTERN		0x5e00
+#define OV2311_TEST_PATTERN_ENABLE	0x80
+#define OV2311_TEST_PATTERN_DISABLE	0x0
+
+#define OV2311_REG_VTS			0x380e
+
+/*
+ * OV2311 native and active pixel array size.
+ * Datasheet not available to confirm these values. renesas-rcar linux-bsp tree
+ * has these values.
+ */
+#define OV2311_NATIVE_WIDTH		1616U
+#define OV2311_NATIVE_HEIGHT		1316U
+#define OV2311_PIXEL_ARRAY_LEFT		8U
+#define OV2311_PIXEL_ARRAY_TOP		8U
+#define OV2311_PIXEL_ARRAY_WIDTH	1600U
+#define OV2311_PIXEL_ARRAY_HEIGHT	1300U
+
+#define REG_NULL			0xFFFF
+
+#define OV2311_REG_VALUE_08BIT		1
+#define OV2311_REG_VALUE_16BIT		2
+#define OV2311_REG_VALUE_24BIT		3
+
+#define OV2311_NAME			"ov2311"
+
+static const char * const ov2311_supply_names[] = {
+	"avdd",		/* Analog power */
+	"dovdd",	/* Digital I/O power */
+	"dvdd",		/* Digital core power */
+};
+
+#define OV2311_NUM_SUPPLIES ARRAY_SIZE(ov2311_supply_names)
+
+struct regval {
+	u16 addr;
+	u8 val;
+};
+
+struct ov2311_mode {
+	u32 width;
+	u32 height;
+	u32 hts_def;
+	u32 vts_def;
+	u32 exp_def;
+	struct v4l2_rect crop;
+	const struct regval *reg_list;
+};
+
+struct ov2311 {
+	struct i2c_client	*client;
+	struct clk		*xvclk;
+	struct gpio_desc	*reset_gpio;
+	struct gpio_desc	*pwdn_gpio;
+	struct regulator_bulk_data supplies[OV2311_NUM_SUPPLIES];
+
+	struct v4l2_subdev	subdev;
+	struct media_pad	pad;
+	struct v4l2_ctrl_handler ctrl_handler;
+	struct v4l2_ctrl	*exposure;
+	struct v4l2_ctrl	*hblank;
+	struct v4l2_ctrl	*vblank;
+	struct v4l2_ctrl	*pixel_rate;
+	/*
+	 * Mutex for serialized access:
+	 * Protect sensor module set pad format and start/stop streaming safely.
+	 */
+	struct mutex		mutex;
+
+	/* Streaming on/off */
+	bool streaming;
+
+	const struct ov2311_mode *cur_mode;
+	u32			code;
+};
+
+#define to_ov2311(sd) container_of(sd, struct ov2311, subdev)
+
+/*
+ * Xclk 24Mhz
+ * max_framerate 60fps for 10 bit, 74.6fps for 8 bit.
+ */
+static const struct regval ov2311_common_regs[] = {
+	{ 0x0103, 0x01 },
+	{ 0x0100, 0x00 },
+	{ 0x0300, 0x01 },
+	{ 0x0302, 0x32 },
+	{ 0x0303, 0x00 },
+	{ 0x0304, 0x03 },
+	{ 0x0305, 0x02 },
+	{ 0x0306, 0x01 },
+	{ 0x030e, 0x04 },
+	{ 0x3001, 0x02 },
+	{ 0x3004, 0x00 },
+	{ 0x3005, 0x00 },
+	{ 0x3006, 0x00 },
+	{ 0x3011, 0x0d },
+	{ 0x3014, 0x04 },
+	{ 0x301c, 0xf0 },
+	{ 0x3020, 0x00 },
+	{ 0x302c, 0x00 },
+	{ 0x302d, 0x12 },
+	{ 0x302e, 0x4c },
+	{ 0x302f, 0x8c },
+	{ 0x3030, 0x10 },
+	{ 0x303f, 0x03 },
+	{ 0x3103, 0x00 },
+	{ 0x3106, 0x08 },
+	{ 0x31ff, 0x01 },
+	{ 0x3501, 0x05 },
+	{ 0x3502, 0xba },
+	{ 0x3506, 0x00 },
+	{ 0x3507, 0x00 },
+	{ 0x3620, 0x67 },
+	{ 0x3633, 0x78 },
+	{ 0x3666, 0x00 },
+	{ 0x3670, 0x68 },
+	{ 0x3674, 0x10 },
+	{ 0x3675, 0x00 },
+	{ 0x3680, 0x84 },
+	{ 0x36a2, 0x04 },
+	{ 0x36a3, 0x80 },
+	{ 0x36b0, 0x00 },
+	{ 0x3700, 0x35 },
+	{ 0x3704, 0x59 },
+	{ 0x3712, 0x00 },
+	{ 0x3713, 0x02 },
+	{ 0x379b, 0x01 },
+	{ 0x379c, 0x10 },
+	{ 0x3800, 0x00 },
+	{ 0x3801, 0x00 },
+	{ 0x3804, 0x06 },
+	{ 0x3805, 0x4f },
+	{ 0x3810, 0x00 },
+	{ 0x3811, 0x08 },
+	{ 0x3812, 0x00 },
+	{ 0x3813, 0x08 },
+	{ 0x3814, 0x11 },
+	{ 0x3815, 0x11 },
+	{ 0x3816, 0x00 },
+	{ 0x3817, 0x00 },
+	{ 0x3818, 0x04 },
+	{ 0x3819, 0x00 },
+	{ 0x382b, 0x5a },
+	{ 0x382c, 0x09 },
+	{ 0x382d, 0x9a },
+	{ 0x3882, 0x02 },
+	{ 0x3883, 0x6c },
+	{ 0x3885, 0x07 },
+	{ 0x389d, 0x03 },
+	{ 0x38a6, 0x00 },
+	{ 0x38a7, 0x01 },
+	{ 0x38b3, 0x07 },
+	{ 0x38b1, 0x00 },
+	{ 0x38e5, 0x02 },
+	{ 0x38e7, 0x00 },
+	{ 0x38e8, 0x00 },
+	{ 0x3910, 0xff },
+	{ 0x3911, 0xff },
+	{ 0x3912, 0x08 },
+	{ 0x3913, 0x00 },
+	{ 0x3914, 0x00 },
+	{ 0x3915, 0x00 },
+	{ 0x391c, 0x00 },
+	{ 0x3920, 0xa5 },
+	{ 0x3921, 0x00 },
+	{ 0x3922, 0x00 },
+	{ 0x3923, 0x00 },
+	{ 0x3924, 0x05 },
+	{ 0x3925, 0x00 },
+	{ 0x3926, 0x00 },
+	{ 0x3927, 0x00 },
+	{ 0x3928, 0x1a },
+	{ 0x392d, 0x05 },
+	{ 0x392e, 0xf2 },
+	{ 0x392f, 0x40 },
+	{ 0x4001, 0x00 },
+	{ 0x4003, 0x40 },
+	{ 0x4008, 0x12 },
+	{ 0x4009, 0x1b },
+	{ 0x400c, 0x0c },
+	{ 0x400d, 0x13 },
+	{ 0x4010, 0xf0 },
+	{ 0x4011, 0x00 },
+	{ 0x4016, 0x00 },
+	{ 0x4017, 0x04 },
+	{ 0x4042, 0x11 },
+	{ 0x4043, 0x70 },
+	{ 0x4045, 0x00 },
+	{ 0x4409, 0x5f },
+	{ 0x450b, 0x00 },
+	{ 0x4600, 0x00 },
+	{ 0x4601, 0xa0 },
+	{ 0x4708, 0x09 },
+	{ 0x470c, 0x81 },
+	{ 0x4710, 0x06 },
+	{ 0x4711, 0x00 },
+	{ 0x4800, 0x00 },
+	{ 0x481f, 0x30 },
+	{ 0x4837, 0x14 },
+	{ 0x4f00, 0x00 },
+	{ 0x4f07, 0x00 },
+	{ 0x4f08, 0x03 },
+	{ 0x4f09, 0x08 },
+	{ 0x4f0c, 0x06 },
+	{ 0x4f0d, 0x02 },
+	{ 0x4f10, 0x00 },
+	{ 0x4f11, 0x00 },
+	{ 0x4f12, 0x07 },
+	{ 0x4f13, 0xe2 },
+	{ 0x5000, 0x9f },
+	{ 0x5001, 0x20 },
+	{ 0x5026, 0x00 },
+	{ 0x5c00, 0x00 },
+	{ 0x5c01, 0x2c },
+	{ 0x5c02, 0x00 },
+	{ 0x5c03, 0x7f },
+	{ 0x5e00, 0x00 },
+	{ 0x5e01, 0x41 },
+	{REG_NULL, 0x00},
+};
+
+static const struct regval ov2311_1600x1300_regs[] = {
+	{ 0x3802, 0x00 },
+	{ 0x3803, 0x00 },
+	{ 0x3806, 0x05 },
+	{ 0x3807, 0x23 },
+	{ 0x3808, 0x06 },
+	{ 0x3809, 0x40 },
+	{ 0x380a, 0x05 },
+	{ 0x380b, 0x14 },
+	{ 0x380c, 0x03 },
+	{ 0x380d, 0x88 },
+	{REG_NULL, 0x00},
+};
+
+static const struct regval ov2311_1600x1080_regs[] = {
+	{ 0x3802, 0x00 },
+	{ 0x3803, 0x6e },
+	{ 0x3806, 0x04 },
+	{ 0x3807, 0xae },
+	{ 0x3808, 0x06 },
+	{ 0x3809, 0x40 },
+	{ 0x380a, 0x04 },
+	{ 0x380b, 0x38 },
+	{ 0x380c, 0x03 },
+	{ 0x380d, 0x88 },
+
+	{ 0x5d01, 0x00 },
+	{ 0x5d02, 0x04 },
+	{ 0x5d03, 0x00 },
+	{ 0x5d04, 0x04 },
+	{ 0x5d05, 0x00 },
+	{REG_NULL, 0x00},
+};
+
+static const struct regval op_10bit[] = {
+	{ 0x030d, 0x5a },
+	{ 0x3662, 0x65 },
+	{REG_NULL, 0x00},
+};
+
+static const struct regval op_8bit[] = {
+	{ 0x030d, 0x70 },
+	{ 0x3662, 0x67 },
+	{REG_NULL, 0x00},
+};
+
+static const struct ov2311_mode supported_modes[] = {
+	{
+		.width = 1600,
+		.height = 1300,
+		.exp_def = 0x0320,
+		.hts_def = (0x0388 * 2),/* Registers 0x380c / 0x380d  * 2 */
+		.vts_def = 0x5c2,	/* Registers 0x380e / 0x380f
+					 * 60fps for 10bpp
+					 */
+		.crop = {
+			.left = OV2311_PIXEL_ARRAY_LEFT,
+			.top = OV2311_PIXEL_ARRAY_TOP,
+			.width = 1600,
+			.height = 1300
+		},
+		.reg_list = ov2311_1600x1300_regs,
+	},
+	{
+		.width = 1600,
+		.height = 1080,
+		.exp_def = 0x0320,
+		.hts_def = (0x0388 * 2),/* Registers 0x380c / 0x380d  * 2 */
+		.vts_def = 0x5c2,	/* Registers 0x380e / 0x380f
+					 * 60fps for 10bpp
+					 */
+		.crop = {
+			.left = OV2311_PIXEL_ARRAY_LEFT,
+			.top = 110 + OV2311_PIXEL_ARRAY_TOP,
+			.width = 1600,
+			.height = 1080
+		},
+		.reg_list = ov2311_1600x1080_regs,
+	},
+};
+
+static const s64 link_freq_menu_items[] = {
+	OV2311_LINK_FREQ
+};
+
+static const char * const ov2311_test_pattern_menu[] = {
+	"Disabled",
+	"Vertical Color Bar Type 1",
+	"Vertical Color Bar Type 2",
+	"Vertical Color Bar Type 3",
+	"Vertical Color Bar Type 4"
+};
+
+/* Write registers up to 4 at a time */
+static int ov2311_write_reg(struct i2c_client *client, u16 reg,
+			    u32 len, u32 val)
+{
+	u32 buf_i, val_i;
+	u8 buf[6];
+	u8 *val_p;
+	__be32 val_be;
+
+	if (len > 4)
+		return -EINVAL;
+
+	buf[0] = reg >> 8;
+	buf[1] = reg & 0xff;
+
+	val_be = cpu_to_be32(val);
+	val_p = (u8 *)&val_be;
+	buf_i = 2;
+	val_i = 4 - len;
+
+	while (val_i < 4)
+		buf[buf_i++] = val_p[val_i++];
+
+	if (i2c_master_send(client, buf, len + 2) != len + 2)
+		return -EIO;
+
+	return 0;
+}
+
+static int ov2311_write_array(struct i2c_client *client,
+			      const struct regval *regs)
+{
+	u32 i;
+	int ret = 0;
+
+	for (i = 0; ret == 0 && regs[i].addr != REG_NULL; i++)
+		ret = ov2311_write_reg(client, regs[i].addr,
+				       OV2311_REG_VALUE_08BIT, regs[i].val);
+
+	return ret;
+}
+
+/* Read registers up to 4 at a time */
+static int ov2311_read_reg(struct i2c_client *client, u16 reg, unsigned int len,
+			   u32 *val)
+{
+	struct i2c_msg msgs[2];
+	u8 *data_be_p;
+	__be32 data_be = 0;
+	__be16 reg_addr_be = cpu_to_be16(reg);
+	int ret;
+
+	if (len > 4 || !len)
+		return -EINVAL;
+
+	data_be_p = (u8 *)&data_be;
+	/* Write register address */
+	msgs[0].addr = client->addr;
+	msgs[0].flags = 0;
+	msgs[0].len = 2;
+	msgs[0].buf = (u8 *)&reg_addr_be;
+
+	/* Read data from register */
+	msgs[1].addr = client->addr;
+	msgs[1].flags = I2C_M_RD;
+	msgs[1].len = len;
+	msgs[1].buf = &data_be_p[4 - len];
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret != ARRAY_SIZE(msgs))
+		return -EIO;
+
+	*val = be32_to_cpu(data_be);
+
+	return 0;
+}
+
+static int ov2311_set_fmt(struct v4l2_subdev *sd,
+			  struct v4l2_subdev_state *sd_state,
+			  struct v4l2_subdev_format *fmt)
+{
+	struct ov2311 *ov2311 = to_ov2311(sd);
+	const struct ov2311_mode *mode;
+	s64 h_blank, vblank_def, pixel_rate;
+
+	mutex_lock(&ov2311->mutex);
+
+	mode = v4l2_find_nearest_size(supported_modes,
+				      ARRAY_SIZE(supported_modes),
+				      width, height,
+				      fmt->format.width,
+				      fmt->format.height);
+	if (fmt->format.code != MEDIA_BUS_FMT_Y8_1X8)
+		fmt->format.code = MEDIA_BUS_FMT_Y10_1X10;
+	fmt->format.width = mode->width;
+	fmt->format.height = mode->height;
+	fmt->format.field = V4L2_FIELD_NONE;
+	fmt->format.colorspace = V4L2_COLORSPACE_RAW;
+	fmt->format.ycbcr_enc =
+			V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->format.colorspace);
+	fmt->format.quantization =
+		V4L2_MAP_QUANTIZATION_DEFAULT(true, fmt->format.colorspace,
+					      fmt->format.ycbcr_enc);
+	fmt->format.xfer_func =
+		V4L2_MAP_XFER_FUNC_DEFAULT(fmt->format.colorspace);
+
+	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+		*v4l2_subdev_state_get_format(sd_state, fmt->pad) =
+								fmt->format;
+	} else {
+		ov2311->cur_mode = mode;
+		ov2311->code = fmt->format.code;
+		h_blank = mode->hts_def - mode->width;
+		__v4l2_ctrl_modify_range(ov2311->hblank, h_blank,
+					 h_blank, 1, h_blank);
+		__v4l2_ctrl_s_ctrl(ov2311->hblank, h_blank);
+		vblank_def = mode->vts_def - mode->height;
+		__v4l2_ctrl_modify_range(ov2311->vblank, vblank_def,
+					 OV2311_VTS_MAX - mode->height,
+					 1, vblank_def);
+		__v4l2_ctrl_s_ctrl(ov2311->vblank, vblank_def);
+
+		pixel_rate = (fmt->format.code == MEDIA_BUS_FMT_Y10_1X10) ?
+			OV2311_PIXEL_RATE_10BIT : OV2311_PIXEL_RATE_8BIT;
+		__v4l2_ctrl_modify_range(ov2311->pixel_rate, pixel_rate,
+					 pixel_rate, 1, pixel_rate);
+	}
+
+	mutex_unlock(&ov2311->mutex);
+
+	return 0;
+}
+
+static int ov2311_get_fmt(struct v4l2_subdev *sd,
+			  struct v4l2_subdev_state *sd_state,
+			  struct v4l2_subdev_format *fmt)
+{
+	struct ov2311 *ov2311 = to_ov2311(sd);
+	const struct ov2311_mode *mode = ov2311->cur_mode;
+
+	mutex_lock(&ov2311->mutex);
+	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+		fmt->format = *v4l2_subdev_state_get_format(sd_state, fmt->pad);
+	} else {
+		fmt->format.width = mode->width;
+		fmt->format.height = mode->height;
+		fmt->format.code = ov2311->code;
+		fmt->format.field = V4L2_FIELD_NONE;
+		fmt->format.colorspace = V4L2_COLORSPACE_SRGB;
+		fmt->format.ycbcr_enc =
+			V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->format.colorspace);
+		fmt->format.quantization =
+			V4L2_MAP_QUANTIZATION_DEFAULT(true,
+						      fmt->format.colorspace,
+						      fmt->format.ycbcr_enc);
+		fmt->format.xfer_func =
+			V4L2_MAP_XFER_FUNC_DEFAULT(fmt->format.colorspace);
+	}
+	mutex_unlock(&ov2311->mutex);
+
+	return 0;
+}
+
+static int ov2311_enum_mbus_code(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *sd_state,
+				 struct v4l2_subdev_mbus_code_enum *code)
+{
+	switch (code->index) {
+	default:
+		return -EINVAL;
+	case 0:
+		code->code = MEDIA_BUS_FMT_Y10_1X10;
+		break;
+	case 1:
+		code->code = MEDIA_BUS_FMT_Y8_1X8;
+		break;
+	}
+
+	return 0;
+}
+
+static int ov2311_enum_frame_sizes(struct v4l2_subdev *sd,
+				   struct v4l2_subdev_state *sd_state,
+				   struct v4l2_subdev_frame_size_enum *fse)
+{
+	if (fse->index >= ARRAY_SIZE(supported_modes))
+		return -EINVAL;
+
+	if (fse->code != MEDIA_BUS_FMT_Y10_1X10 &&
+	    fse->code != MEDIA_BUS_FMT_Y8_1X8)
+		return -EINVAL;
+
+	fse->min_width  = supported_modes[fse->index].width;
+	fse->max_width  = supported_modes[fse->index].width;
+	fse->max_height = supported_modes[fse->index].height;
+	fse->min_height = supported_modes[fse->index].height;
+
+	return 0;
+}
+
+static int ov2311_enable_test_pattern(struct ov2311 *ov2311, u32 pattern)
+{
+	u32 val;
+
+	if (pattern)
+		val = (pattern - 1) | OV2311_TEST_PATTERN_ENABLE;
+	else
+		val = OV2311_TEST_PATTERN_DISABLE;
+
+	return ov2311_write_reg(ov2311->client, OV2311_REG_TEST_PATTERN,
+				OV2311_REG_VALUE_08BIT, val);
+}
+
+static const struct v4l2_rect *
+__ov2311_get_pad_crop(struct ov2311 *ov2311, struct v4l2_subdev_state *sd_state,
+		      unsigned int pad, enum v4l2_subdev_format_whence which)
+{
+	switch (which) {
+	case V4L2_SUBDEV_FORMAT_TRY:
+		return v4l2_subdev_state_get_crop(sd_state, pad);
+	case V4L2_SUBDEV_FORMAT_ACTIVE:
+		return &ov2311->cur_mode->crop;
+	}
+
+	return NULL;
+}
+
+static int ov2311_get_selection(struct v4l2_subdev *sd,
+				struct v4l2_subdev_state *sd_state,
+				struct v4l2_subdev_selection *sel)
+{
+	switch (sel->target) {
+	case V4L2_SEL_TGT_CROP: {
+		struct ov2311 *ov2311 = to_ov2311(sd);
+
+		mutex_lock(&ov2311->mutex);
+		sel->r = *__ov2311_get_pad_crop(ov2311, sd_state, sel->pad,
+						sel->which);
+		mutex_unlock(&ov2311->mutex);
+
+		return 0;
+	}
+
+	case V4L2_SEL_TGT_NATIVE_SIZE:
+		sel->r.top = 0;
+		sel->r.left = 0;
+		sel->r.width = OV2311_NATIVE_WIDTH;
+		sel->r.height = OV2311_NATIVE_HEIGHT;
+
+		return 0;
+
+	case V4L2_SEL_TGT_CROP_DEFAULT:
+	case V4L2_SEL_TGT_CROP_BOUNDS:
+		sel->r.top = OV2311_PIXEL_ARRAY_TOP;
+		sel->r.left = OV2311_PIXEL_ARRAY_LEFT;
+		sel->r.width = OV2311_PIXEL_ARRAY_WIDTH;
+		sel->r.height = OV2311_PIXEL_ARRAY_HEIGHT;
+
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int ov2311_start_stream(struct ov2311 *ov2311)
+{
+	int ret;
+
+	ret = ov2311_write_array(ov2311->client, ov2311_common_regs);
+	if (ret)
+		return ret;
+
+	ret = ov2311_write_array(ov2311->client, ov2311->cur_mode->reg_list);
+	if (ret)
+		return ret;
+
+	if (ov2311->code == MEDIA_BUS_FMT_Y10_1X10)
+		ret = ov2311_write_array(ov2311->client, op_10bit);
+	else
+		ret = ov2311_write_array(ov2311->client, op_8bit);
+	if (ret)
+		return ret;
+
+	/* In case these controls are set before streaming */
+	mutex_unlock(&ov2311->mutex);
+	ret = v4l2_ctrl_handler_setup(&ov2311->ctrl_handler);
+	mutex_lock(&ov2311->mutex);
+	if (ret)
+		return ret;
+
+	return ov2311_write_reg(ov2311->client, OV2311_REG_CTRL_MODE,
+				OV2311_REG_VALUE_08BIT, OV2311_MODE_STREAMING);
+}
+
+static int ov2311_stop_stream(struct ov2311 *ov2311)
+{
+	return ov2311_write_reg(ov2311->client, OV2311_REG_CTRL_MODE,
+				OV2311_REG_VALUE_08BIT, OV2311_MODE_SW_STANDBY);
+}
+
+static int ov2311_s_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct ov2311 *ov2311 = to_ov2311(sd);
+	struct i2c_client *client = ov2311->client;
+	int ret = 0;
+
+	mutex_lock(&ov2311->mutex);
+	if (ov2311->streaming == enable) {
+		mutex_unlock(&ov2311->mutex);
+		return 0;
+	}
+
+	if (enable) {
+		ret = pm_runtime_resume_and_get(&client->dev);
+		if (ret < 0)
+			goto unlock_and_return;
+
+		ret = ov2311_start_stream(ov2311);
+		if (ret) {
+			v4l2_err(sd, "start stream failed while write regs\n");
+			pm_runtime_put(&client->dev);
+			goto unlock_and_return;
+		}
+	} else {
+		ov2311_stop_stream(ov2311);
+		pm_runtime_put(&client->dev);
+	}
+
+	ov2311->streaming = enable;
+
+unlock_and_return:
+	mutex_unlock(&ov2311->mutex);
+
+	return ret;
+}
+
+static int ov2311_power_on(struct device *dev)
+{
+	struct v4l2_subdev *sd = dev_get_drvdata(dev);
+	struct ov2311 *ov2311 = to_ov2311(sd);
+	int ret;
+
+	ret = clk_set_rate(ov2311->xvclk, OV2311_XVCLK_FREQ);
+	if (ret < 0)
+		dev_warn(dev, "Failed to set xvclk rate (24MHz)\n");
+	if (clk_get_rate(ov2311->xvclk) != OV2311_XVCLK_FREQ)
+		dev_warn(dev, "xvclk mismatched, modes are based on 24MHz - rate is %lu\n",
+			 clk_get_rate(ov2311->xvclk));
+
+	ret = clk_prepare_enable(ov2311->xvclk);
+	if (ret < 0) {
+		dev_err(dev, "Failed to enable xvclk\n");
+		return ret;
+	}
+
+	gpiod_set_value_cansleep(ov2311->reset_gpio, 0);
+
+	ret = regulator_bulk_enable(OV2311_NUM_SUPPLIES, ov2311->supplies);
+	if (ret < 0) {
+		dev_err(dev, "Failed to enable regulators\n");
+		goto disable_clk;
+	}
+
+	gpiod_set_value_cansleep(ov2311->reset_gpio, 1);
+
+	usleep_range(500, 1000);
+	gpiod_set_value_cansleep(ov2311->pwdn_gpio, 1);
+
+	usleep_range(1000, 2000);
+
+	return 0;
+
+disable_clk:
+	clk_disable_unprepare(ov2311->xvclk);
+
+	return ret;
+}
+
+static int ov2311_power_off(struct device *dev)
+{
+	struct v4l2_subdev *sd = dev_get_drvdata(dev);
+	struct ov2311 *ov2311 = to_ov2311(sd);
+
+	gpiod_set_value_cansleep(ov2311->pwdn_gpio, 0);
+	clk_disable_unprepare(ov2311->xvclk);
+	gpiod_set_value_cansleep(ov2311->reset_gpio, 0);
+	regulator_bulk_disable(OV2311_NUM_SUPPLIES, ov2311->supplies);
+
+	return 0;
+}
+
+static int ov2311_runtime_resume(struct device *dev)
+{
+	struct v4l2_subdev *sd = dev_get_drvdata(dev);
+	struct ov2311 *ov2311 = to_ov2311(sd);
+	int ret;
+
+	if (ov2311->streaming) {
+		ret = ov2311_start_stream(ov2311);
+		if (ret)
+			goto error;
+	}
+	return 0;
+
+error:
+	ov2311_stop_stream(ov2311);
+	ov2311->streaming = 0;
+	return ret;
+}
+
+static int ov2311_runtime_suspend(struct device *dev)
+{
+	struct v4l2_subdev *sd = dev_get_drvdata(dev);
+	struct ov2311 *ov2311 = to_ov2311(sd);
+
+	if (ov2311->streaming)
+		ov2311_stop_stream(ov2311);
+
+	return 0;
+}
+
+static int ov2311_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+	struct ov2311 *ov2311 = to_ov2311(sd);
+	struct v4l2_mbus_framefmt *try_fmt =
+				v4l2_subdev_state_get_format(fh->state, 0);
+	const struct ov2311_mode *def_mode = &supported_modes[0];
+
+	mutex_lock(&ov2311->mutex);
+	/* Initialize try_fmt */
+	try_fmt->width = def_mode->width;
+	try_fmt->height = def_mode->height;
+	try_fmt->code = MEDIA_BUS_FMT_Y10_1X10;
+	try_fmt->field = V4L2_FIELD_NONE;
+	try_fmt->colorspace = V4L2_COLORSPACE_RAW;
+	try_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(try_fmt->colorspace);
+	try_fmt->quantization =
+		V4L2_MAP_QUANTIZATION_DEFAULT(true, try_fmt->colorspace,
+					      try_fmt->ycbcr_enc);
+	try_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(try_fmt->colorspace);
+
+	mutex_unlock(&ov2311->mutex);
+	/* No crop or compose */
+
+	return 0;
+}
+
+static const struct dev_pm_ops ov2311_pm_ops = {
+	SET_RUNTIME_PM_OPS(ov2311_runtime_suspend, ov2311_runtime_resume, NULL)
+	SET_RUNTIME_PM_OPS(ov2311_power_off, ov2311_power_on, NULL)
+};
+
+static const struct v4l2_subdev_internal_ops ov2311_internal_ops = {
+	.open = ov2311_open,
+};
+
+static const struct v4l2_subdev_core_ops ov2311_core_ops = {
+	.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_video_ops ov2311_video_ops = {
+	.s_stream = ov2311_s_stream,
+};
+
+static const struct v4l2_subdev_pad_ops ov2311_pad_ops = {
+	.enum_mbus_code = ov2311_enum_mbus_code,
+	.enum_frame_size = ov2311_enum_frame_sizes,
+	.get_fmt = ov2311_get_fmt,
+	.set_fmt = ov2311_set_fmt,
+	.get_selection = ov2311_get_selection,
+};
+
+static const struct v4l2_subdev_ops ov2311_subdev_ops = {
+	.core	= &ov2311_core_ops,
+	.video	= &ov2311_video_ops,
+	.pad	= &ov2311_pad_ops,
+};
+
+static int ov2311_set_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct ov2311 *ov2311 = container_of(ctrl->handler,
+					     struct ov2311, ctrl_handler);
+	struct i2c_client *client = ov2311->client;
+	s64 max;
+	int ret = 0;
+
+	/* Propagate change of current control to all related controls */
+	switch (ctrl->id) {
+	case V4L2_CID_VBLANK:
+		/* Update max exposure while meeting expected vblanking */
+		max = ov2311->cur_mode->height + ctrl->val - 4;
+		__v4l2_ctrl_modify_range(ov2311->exposure,
+					 ov2311->exposure->minimum, max,
+					 ov2311->exposure->step,
+					 ov2311->exposure->default_value);
+		break;
+	}
+
+	if (pm_runtime_get(&client->dev) <= 0)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_EXPOSURE:
+		ret = ov2311_write_reg(ov2311->client, OV2311_REG_EXPOSURE,
+				       OV2311_REG_VALUE_16BIT, ctrl->val);
+		break;
+	case V4L2_CID_ANALOGUE_GAIN:
+		ret = ov2311_write_reg(ov2311->client, OV2311_REG_GAIN_H,
+				       OV2311_REG_VALUE_08BIT,
+				       (ctrl->val >> OV2311_GAIN_H_SHIFT) &
+							OV2311_GAIN_H_MASK);
+		ret |= ov2311_write_reg(ov2311->client, OV2311_REG_GAIN_L,
+				       OV2311_REG_VALUE_08BIT,
+				       ctrl->val & OV2311_GAIN_L_MASK);
+		break;
+	case V4L2_CID_VBLANK:
+		ret = ov2311_write_reg(ov2311->client, OV2311_REG_VTS,
+				       OV2311_REG_VALUE_16BIT,
+				       ctrl->val + ov2311->cur_mode->height);
+		break;
+	case V4L2_CID_TEST_PATTERN:
+		ret = ov2311_enable_test_pattern(ov2311, ctrl->val);
+		break;
+	case V4L2_CID_HFLIP:
+		ret = ov2311_write_reg(ov2311->client, OV2311_REG_H_FLIP,
+				       OV2311_REG_VALUE_08BIT,
+				       ctrl->val ? OV2311_FLIP_BIT : 0);
+		break;
+	case V4L2_CID_VFLIP:
+		ret = ov2311_write_reg(ov2311->client, OV2311_REG_V_FLIP,
+				       OV2311_REG_VALUE_08BIT,
+				       ctrl->val ? OV2311_FLIP_BIT : 0);
+		break;
+	default:
+		dev_warn(&client->dev, "%s Unhandled id:0x%x, val:0x%x\n",
+			 __func__, ctrl->id, ctrl->val);
+		break;
+	}
+
+	pm_runtime_put(&client->dev);
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops ov2311_ctrl_ops = {
+	.s_ctrl = ov2311_set_ctrl,
+};
+
+static int ov2311_initialize_controls(struct ov2311 *ov2311)
+{
+	struct v4l2_fwnode_device_properties props;
+	const struct ov2311_mode *mode;
+	struct v4l2_ctrl_handler *handler;
+	struct v4l2_ctrl *ctrl;
+	s64 exposure_max, vblank_def;
+	u32 h_blank;
+	int ret;
+
+	handler = &ov2311->ctrl_handler;
+	mode = ov2311->cur_mode;
+	ret = v4l2_ctrl_handler_init(handler, 11);
+	if (ret)
+		return ret;
+	handler->lock = &ov2311->mutex;
+
+	ctrl = v4l2_ctrl_new_int_menu(handler, NULL, V4L2_CID_LINK_FREQ,
+				      0, 0, link_freq_menu_items);
+	if (ctrl)
+		ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	ov2311->pixel_rate = v4l2_ctrl_new_std(handler, NULL,
+					       V4L2_CID_PIXEL_RATE,
+					       OV2311_PIXEL_RATE_10BIT,
+					       OV2311_PIXEL_RATE_10BIT, 1,
+					       OV2311_PIXEL_RATE_10BIT);
+
+	h_blank = mode->hts_def - mode->width;
+	ov2311->hblank = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK,
+					   h_blank, h_blank, 1, h_blank);
+	if (ov2311->hblank)
+		ov2311->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	vblank_def = mode->vts_def - mode->height;
+	ov2311->vblank = v4l2_ctrl_new_std(handler, &ov2311_ctrl_ops,
+					   V4L2_CID_VBLANK, vblank_def,
+					   OV2311_VTS_MAX - mode->height, 1,
+					   vblank_def);
+
+	exposure_max = mode->vts_def - 4;
+	ov2311->exposure = v4l2_ctrl_new_std(handler, &ov2311_ctrl_ops,
+					     V4L2_CID_EXPOSURE,
+					     OV2311_EXPOSURE_MIN, exposure_max,
+					     OV2311_EXPOSURE_STEP,
+					     mode->exp_def);
+
+	v4l2_ctrl_new_std(handler, &ov2311_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
+			  OV2311_GAIN_MIN, OV2311_GAIN_MAX, OV2311_GAIN_STEP,
+			  OV2311_GAIN_DEFAULT);
+
+	v4l2_ctrl_new_std_menu_items(handler, &ov2311_ctrl_ops,
+				     V4L2_CID_TEST_PATTERN,
+				     ARRAY_SIZE(ov2311_test_pattern_menu) - 1,
+				     0, 0, ov2311_test_pattern_menu);
+
+	v4l2_ctrl_new_std(handler, &ov2311_ctrl_ops,
+			  V4L2_CID_HFLIP, 0, 1, 1, 0);
+
+	v4l2_ctrl_new_std(handler, &ov2311_ctrl_ops,
+			  V4L2_CID_VFLIP, 0, 1, 1, 0);
+
+	ret = v4l2_fwnode_device_parse(&ov2311->client->dev, &props);
+	if (ret)
+		goto err_free_handler;
+
+	ret = v4l2_ctrl_new_fwnode_properties(handler, &ov2311_ctrl_ops,
+					      &props);
+	if (ret)
+		goto err_free_handler;
+
+	if (handler->error) {
+		ret = handler->error;
+		dev_err(&ov2311->client->dev,
+			"Failed to init controls(%d)\n", ret);
+		goto err_free_handler;
+	}
+
+	ov2311->subdev.ctrl_handler = handler;
+
+	return 0;
+
+err_free_handler:
+	v4l2_ctrl_handler_free(handler);
+
+	return ret;
+}
+
+static int ov2311_check_sensor_id(struct ov2311 *ov2311,
+				  struct i2c_client *client)
+{
+	struct device *dev = &ov2311->client->dev;
+	u32 id = 0, id_msb = 0;
+	int ret;
+
+	ret = ov2311_read_reg(client, OV2311_REG_CHIP_ID + 1,
+			      OV2311_REG_VALUE_08BIT, &id);
+	if (!ret)
+		ret = ov2311_read_reg(client, OV2311_REG_CHIP_ID,
+				      OV2311_REG_VALUE_08BIT, &id_msb);
+	id |= (id_msb << 8);
+	if (ret || id != CHIP_ID) {
+		dev_err(dev, "Unexpected sensor id(%04x), ret(%d)\n", id, ret);
+		return -ENODEV;
+	}
+
+	dev_info(dev, "Detected OV%06x sensor\n", CHIP_ID);
+
+	return 0;
+}
+
+static int ov2311_configure_regulators(struct ov2311 *ov2311)
+{
+	unsigned int i;
+
+	for (i = 0; i < OV2311_NUM_SUPPLIES; i++)
+		ov2311->supplies[i].supply = ov2311_supply_names[i];
+
+	return devm_regulator_bulk_get(&ov2311->client->dev,
+				       OV2311_NUM_SUPPLIES,
+				       ov2311->supplies);
+}
+
+static int ov2311_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct ov2311 *ov2311;
+	struct v4l2_subdev *sd;
+	int ret;
+
+	ov2311 = devm_kzalloc(dev, sizeof(*ov2311), GFP_KERNEL);
+	if (!ov2311)
+		return -ENOMEM;
+
+	ov2311->client = client;
+	ov2311->cur_mode = &supported_modes[0];
+
+	ov2311->xvclk = devm_clk_get(dev, "xvclk");
+	if (IS_ERR(ov2311->xvclk)) {
+		dev_err(dev, "Failed to get xvclk\n");
+		return -EINVAL;
+	}
+
+	ov2311->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+						     GPIOD_OUT_LOW);
+	if (IS_ERR(ov2311->reset_gpio))
+		dev_warn(dev, "Failed to get reset-gpios\n");
+
+	ov2311->pwdn_gpio = devm_gpiod_get_optional(dev, "pwdn", GPIOD_OUT_LOW);
+	if (IS_ERR(ov2311->pwdn_gpio))
+		dev_warn(dev, "Failed to get pwdn-gpios\n");
+
+	ret = ov2311_configure_regulators(ov2311);
+	if (ret) {
+		dev_err(dev, "Failed to get power regulators\n");
+		return ret;
+	}
+
+	mutex_init(&ov2311->mutex);
+
+	sd = &ov2311->subdev;
+	v4l2_i2c_subdev_init(sd, client, &ov2311_subdev_ops);
+	ret = ov2311_initialize_controls(ov2311);
+	if (ret)
+		goto err_destroy_mutex;
+
+	ret = ov2311_power_on(&client->dev);
+	if (ret)
+		goto err_free_handler;
+
+	ret = ov2311_check_sensor_id(ov2311, client);
+	if (ret)
+		goto err_power_off;
+
+	sd->internal_ops = &ov2311_internal_ops;
+	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+	ov2311->pad.flags = MEDIA_PAD_FL_SOURCE;
+	sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
+	ret = media_entity_pads_init(&sd->entity, 1, &ov2311->pad);
+	if (ret < 0)
+		goto err_power_off;
+
+	ret = v4l2_async_register_subdev(sd);
+	if (ret) {
+		dev_err(dev, "v4l2 async register subdev failed\n");
+		goto err_clean_entity;
+	}
+
+	pm_runtime_set_active(dev);
+	pm_runtime_enable(dev);
+	pm_runtime_idle(dev);
+
+	return 0;
+
+err_clean_entity:
+	media_entity_cleanup(&sd->entity);
+err_power_off:
+	ov2311_power_off(&client->dev);
+err_free_handler:
+	v4l2_ctrl_handler_free(&ov2311->ctrl_handler);
+err_destroy_mutex:
+	mutex_destroy(&ov2311->mutex);
+
+	return ret;
+}
+
+static void ov2311_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct ov2311 *ov2311 = to_ov2311(sd);
+
+	v4l2_async_unregister_subdev(sd);
+	media_entity_cleanup(&sd->entity);
+	v4l2_ctrl_handler_free(&ov2311->ctrl_handler);
+	mutex_destroy(&ov2311->mutex);
+
+	pm_runtime_disable(&client->dev);
+	if (!pm_runtime_status_suspended(&client->dev))
+		ov2311_power_off(&client->dev);
+	pm_runtime_set_suspended(&client->dev);
+}
+
+static const struct of_device_id ov2311_of_match[] = {
+	{ .compatible = "ovti,ov2311" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, ov2311_of_match);
+
+static const struct i2c_device_id ov2311_match_id[] = {
+	{ "ovti,ov2311", 0 },
+	{ },
+};
+
+static struct i2c_driver ov2311_i2c_driver = {
+	.driver = {
+		.name = OV2311_NAME,
+		.pm = &ov2311_pm_ops,
+		.of_match_table = of_match_ptr(ov2311_of_match),
+	},
+	.probe		= &ov2311_probe,
+	.remove		= &ov2311_remove,
+	.id_table	= ov2311_match_id,
+};
+
+module_i2c_driver(ov2311_i2c_driver);
+
+MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com");
+MODULE_DESCRIPTION("OmniVision OV2311 sensor driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/i2c/ov5647.c b/drivers/media/i2c/ov5647.c
index 305677913a0c..c1fbe9051ce5 100644
--- a/drivers/media/i2c/ov5647.c
+++ b/drivers/media/i2c/ov5647.c
@@ -20,6 +20,7 @@
 #include <linux/module.h>
 #include <linux/of_graph.h>
 #include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
 #include <linux/slab.h>
 #include <linux/videodev2.h>
 #include <media/v4l2-ctrls.h>
@@ -52,8 +53,12 @@
 #define OV5647_REG_AEC_AGC		0x3503
 #define OV5647_REG_GAIN_HI		0x350a
 #define OV5647_REG_GAIN_LO		0x350b
+#define OV5647_REG_HTS_HI		0x380c
+#define OV5647_REG_HTS_LO		0x380d
 #define OV5647_REG_VTS_HI		0x380e
 #define OV5647_REG_VTS_LO		0x380f
+#define OV5647_REG_VFLIP		0x3820
+#define OV5647_REG_HFLIP		0x3821
 #define OV5647_REG_FRAME_OFF_NUMBER	0x4202
 #define OV5647_REG_MIPI_CTRL00		0x4800
 #define OV5647_REG_MIPI_CTRL14		0x4814
@@ -76,11 +81,29 @@
 #define OV5647_VBLANK_MIN		24
 #define OV5647_VTS_MAX			32767
 
+#define OV5647_HTS_MAX			0x1fff
+
 #define OV5647_EXPOSURE_MIN		4
 #define OV5647_EXPOSURE_STEP		1
 #define OV5647_EXPOSURE_DEFAULT		1000
 #define OV5647_EXPOSURE_MAX		65535
 
+/* regulator supplies */
+static const char * const ov5647_supply_names[] = {
+	"avdd",		/* Analog power */
+	"dovdd",	/* Digital I/O power */
+	"dvdd",		/* Digital core power */
+};
+
+#define OV5647_NUM_SUPPLIES ARRAY_SIZE(ov5647_supply_names)
+
+#define FREQ_INDEX_FULL		0
+#define FREQ_INDEX_VGA		1
+static const s64 ov5647_link_freqs[] = {
+	[FREQ_INDEX_FULL]	= 218500000,
+	[FREQ_INDEX_VGA]	= 208333000,
+};
+
 struct regval_list {
 	u16 addr;
 	u8 data;
@@ -90,6 +113,7 @@ struct ov5647_mode {
 	struct v4l2_mbus_framefmt	format;
 	struct v4l2_rect		crop;
 	u64				pixel_rate;
+	unsigned int			link_freq_index;
 	int				hts;
 	int				vts;
 	const struct regval_list	*reg_list;
@@ -102,6 +126,7 @@ struct ov5647 {
 	struct mutex			lock;
 	struct clk			*xclk;
 	struct gpio_desc		*pwdn;
+	struct regulator_bulk_data supplies[OV5647_NUM_SUPPLIES];
 	bool				clock_ncont;
 	struct v4l2_ctrl_handler	ctrls;
 	const struct ov5647_mode	*mode;
@@ -109,6 +134,9 @@ struct ov5647 {
 	struct v4l2_ctrl		*hblank;
 	struct v4l2_ctrl		*vblank;
 	struct v4l2_ctrl		*exposure;
+	struct v4l2_ctrl		*hflip;
+	struct v4l2_ctrl		*vflip;
+	struct v4l2_ctrl		*link_freq;
 };
 
 static inline struct ov5647 *to_sensor(struct v4l2_subdev *sd)
@@ -142,22 +170,16 @@ static const struct regval_list sensor_oe_enable_regs[] = {
 	{0x3002, 0xe4},
 };
 
-static struct regval_list ov5647_2592x1944_10bpp[] = {
+static struct regval_list ov5647_common_regs[] = {
 	{0x0100, 0x00},
 	{0x0103, 0x01},
 	{0x3034, 0x1a},
 	{0x3035, 0x21},
-	{0x3036, 0x69},
 	{0x303c, 0x11},
 	{0x3106, 0xf5},
-	{0x3821, 0x06},
-	{0x3820, 0x00},
 	{0x3827, 0xec},
 	{0x370c, 0x03},
-	{0x3612, 0x5b},
-	{0x3618, 0x04},
 	{0x5000, 0x06},
-	{0x5002, 0x41},
 	{0x5003, 0x08},
 	{0x5a00, 0x08},
 	{0x3000, 0x00},
@@ -172,26 +194,6 @@ static struct regval_list ov5647_2592x1944_10bpp[] = {
 	{0x3a19, 0xf8},
 	{0x3c01, 0x80},
 	{0x3b07, 0x0c},
-	{0x380c, 0x0b},
-	{0x380d, 0x1c},
-	{0x3814, 0x11},
-	{0x3815, 0x11},
-	{0x3708, 0x64},
-	{0x3709, 0x12},
-	{0x3808, 0x0a},
-	{0x3809, 0x20},
-	{0x380a, 0x07},
-	{0x380b, 0x98},
-	{0x3800, 0x00},
-	{0x3801, 0x00},
-	{0x3802, 0x00},
-	{0x3803, 0x00},
-	{0x3804, 0x0a},
-	{0x3805, 0x3f},
-	{0x3806, 0x07},
-	{0x3807, 0xa3},
-	{0x3811, 0x10},
-	{0x3813, 0x06},
 	{0x3630, 0x2e},
 	{0x3632, 0xe2},
 	{0x3633, 0x23},
@@ -211,11 +213,6 @@ static struct regval_list ov5647_2592x1944_10bpp[] = {
 	{0x3f06, 0x10},
 	{0x3f01, 0x0a},
 	{0x3a08, 0x01},
-	{0x3a09, 0x28},
-	{0x3a0a, 0x00},
-	{0x3a0b, 0xf6},
-	{0x3a0d, 0x08},
-	{0x3a0e, 0x06},
 	{0x3a0f, 0x58},
 	{0x3a10, 0x50},
 	{0x3a1b, 0x58},
@@ -223,54 +220,57 @@ static struct regval_list ov5647_2592x1944_10bpp[] = {
 	{0x3a11, 0x60},
 	{0x3a1f, 0x28},
 	{0x4001, 0x02},
-	{0x4004, 0x04},
 	{0x4000, 0x09},
+	{0x3503, 0x03},
+};
+
+static struct regval_list ov5647_2592x1944_10bpp[] = {
+	{0x3036, 0x69},
+	{0x3821, 0x00},
+	{0x3820, 0x00},
+	{0x3612, 0x5b},
+	{0x3618, 0x04},
+	{0x5002, 0x41},
+	{0x3814, 0x11},
+	{0x3815, 0x11},
+	{0x3708, 0x64},
+	{0x3709, 0x12},
+	{0x3800, 0x00},
+	{0x3801, 0x00},
+	{0x3802, 0x00},
+	{0x3803, 0x00},
+	{0x3804, 0x0a},
+	{0x3805, 0x3f},
+	{0x3806, 0x07},
+	{0x3807, 0xa3},
+	{0x3808, 0x0a},
+	{0x3809, 0x20},
+	{0x380a, 0x07},
+	{0x380b, 0x98},
+	{0x3811, 0x10},
+	{0x3813, 0x06},
+	{0x3a09, 0x28},
+	{0x3a0a, 0x00},
+	{0x3a0b, 0xf6},
+	{0x3a0d, 0x08},
+	{0x3a0e, 0x06},
+	{0x4004, 0x04},
 	{0x4837, 0x19},
 	{0x4800, 0x24},
-	{0x3503, 0x03},
 	{0x0100, 0x01},
 };
 
 static struct regval_list ov5647_1080p30_10bpp[] = {
-	{0x0100, 0x00},
-	{0x0103, 0x01},
-	{0x3034, 0x1a},
-	{0x3035, 0x21},
-	{0x3036, 0x62},
-	{0x303c, 0x11},
-	{0x3106, 0xf5},
-	{0x3821, 0x06},
+	{0x3036, 0x69},
+	{0x3821, 0x00},
 	{0x3820, 0x00},
-	{0x3827, 0xec},
-	{0x370c, 0x03},
 	{0x3612, 0x5b},
 	{0x3618, 0x04},
-	{0x5000, 0x06},
 	{0x5002, 0x41},
-	{0x5003, 0x08},
-	{0x5a00, 0x08},
-	{0x3000, 0x00},
-	{0x3001, 0x00},
-	{0x3002, 0x00},
-	{0x3016, 0x08},
-	{0x3017, 0xe0},
-	{0x3018, 0x44},
-	{0x301c, 0xf8},
-	{0x301d, 0xf0},
-	{0x3a18, 0x00},
-	{0x3a19, 0xf8},
-	{0x3c01, 0x80},
-	{0x3b07, 0x0c},
-	{0x380c, 0x09},
-	{0x380d, 0x70},
 	{0x3814, 0x11},
 	{0x3815, 0x11},
 	{0x3708, 0x64},
 	{0x3709, 0x12},
-	{0x3808, 0x07},
-	{0x3809, 0x80},
-	{0x380a, 0x04},
-	{0x380b, 0x38},
 	{0x3800, 0x01},
 	{0x3801, 0x5c},
 	{0x3802, 0x01},
@@ -279,75 +279,30 @@ static struct regval_list ov5647_1080p30_10bpp[] = {
 	{0x3805, 0xe3},
 	{0x3806, 0x05},
 	{0x3807, 0xf1},
+	{0x3808, 0x07},
+	{0x3809, 0x80},
+	{0x380a, 0x04},
+	{0x380b, 0x38},
 	{0x3811, 0x04},
 	{0x3813, 0x02},
-	{0x3630, 0x2e},
-	{0x3632, 0xe2},
-	{0x3633, 0x23},
-	{0x3634, 0x44},
-	{0x3636, 0x06},
-	{0x3620, 0x64},
-	{0x3621, 0xe0},
-	{0x3600, 0x37},
-	{0x3704, 0xa0},
-	{0x3703, 0x5a},
-	{0x3715, 0x78},
-	{0x3717, 0x01},
-	{0x3731, 0x02},
-	{0x370b, 0x60},
-	{0x3705, 0x1a},
-	{0x3f05, 0x02},
-	{0x3f06, 0x10},
-	{0x3f01, 0x0a},
-	{0x3a08, 0x01},
 	{0x3a09, 0x4b},
 	{0x3a0a, 0x01},
 	{0x3a0b, 0x13},
 	{0x3a0d, 0x04},
 	{0x3a0e, 0x03},
-	{0x3a0f, 0x58},
-	{0x3a10, 0x50},
-	{0x3a1b, 0x58},
-	{0x3a1e, 0x50},
-	{0x3a11, 0x60},
-	{0x3a1f, 0x28},
-	{0x4001, 0x02},
 	{0x4004, 0x04},
-	{0x4000, 0x09},
 	{0x4837, 0x19},
 	{0x4800, 0x34},
-	{0x3503, 0x03},
 	{0x0100, 0x01},
 };
 
 static struct regval_list ov5647_2x2binned_10bpp[] = {
-	{0x0100, 0x00},
-	{0x0103, 0x01},
-	{0x3034, 0x1a},
-	{0x3035, 0x21},
-	{0x3036, 0x62},
-	{0x303c, 0x11},
-	{0x3106, 0xf5},
-	{0x3827, 0xec},
-	{0x370c, 0x03},
+	{0x3036, 0x69},
+	{0x3821, 0x01},
+	{0x3820, 0x41},
 	{0x3612, 0x59},
 	{0x3618, 0x00},
-	{0x5000, 0x06},
 	{0x5002, 0x41},
-	{0x5003, 0x08},
-	{0x5a00, 0x08},
-	{0x3000, 0x00},
-	{0x3001, 0x00},
-	{0x3002, 0x00},
-	{0x3016, 0x08},
-	{0x3017, 0xe0},
-	{0x3018, 0x44},
-	{0x301c, 0xf8},
-	{0x301d, 0xf0},
-	{0x3a18, 0x00},
-	{0x3a19, 0xf8},
-	{0x3c01, 0x80},
-	{0x3b07, 0x0c},
 	{0x3800, 0x00},
 	{0x3801, 0x00},
 	{0x3802, 0x00},
@@ -360,50 +315,18 @@ static struct regval_list ov5647_2x2binned_10bpp[] = {
 	{0x3809, 0x10},
 	{0x380a, 0x03},
 	{0x380b, 0xcc},
-	{0x380c, 0x07},
-	{0x380d, 0x68},
 	{0x3811, 0x0c},
 	{0x3813, 0x06},
 	{0x3814, 0x31},
 	{0x3815, 0x31},
-	{0x3630, 0x2e},
-	{0x3632, 0xe2},
-	{0x3633, 0x23},
-	{0x3634, 0x44},
-	{0x3636, 0x06},
-	{0x3620, 0x64},
-	{0x3621, 0xe0},
-	{0x3600, 0x37},
-	{0x3704, 0xa0},
-	{0x3703, 0x5a},
-	{0x3715, 0x78},
-	{0x3717, 0x01},
-	{0x3731, 0x02},
-	{0x370b, 0x60},
-	{0x3705, 0x1a},
-	{0x3f05, 0x02},
-	{0x3f06, 0x10},
-	{0x3f01, 0x0a},
-	{0x3a08, 0x01},
 	{0x3a09, 0x28},
 	{0x3a0a, 0x00},
 	{0x3a0b, 0xf6},
 	{0x3a0d, 0x08},
 	{0x3a0e, 0x06},
-	{0x3a0f, 0x58},
-	{0x3a10, 0x50},
-	{0x3a1b, 0x58},
-	{0x3a1e, 0x50},
-	{0x3a11, 0x60},
-	{0x3a1f, 0x28},
-	{0x4001, 0x02},
 	{0x4004, 0x04},
-	{0x4000, 0x09},
 	{0x4837, 0x16},
 	{0x4800, 0x24},
-	{0x3503, 0x03},
-	{0x3820, 0x41},
-	{0x3821, 0x07},
 	{0x350a, 0x00},
 	{0x350b, 0x10},
 	{0x3500, 0x00},
@@ -414,37 +337,15 @@ static struct regval_list ov5647_2x2binned_10bpp[] = {
 };
 
 static struct regval_list ov5647_640x480_10bpp[] = {
-	{0x0100, 0x00},
-	{0x0103, 0x01},
-	{0x3035, 0x11},
 	{0x3036, 0x46},
-	{0x303c, 0x11},
-	{0x3821, 0x07},
+	{0x3821, 0x01},
 	{0x3820, 0x41},
-	{0x370c, 0x03},
 	{0x3612, 0x59},
 	{0x3618, 0x00},
-	{0x5000, 0x06},
-	{0x5003, 0x08},
-	{0x5a00, 0x08},
-	{0x3000, 0xff},
-	{0x3001, 0xff},
-	{0x3002, 0xff},
-	{0x301d, 0xf0},
-	{0x3a18, 0x00},
-	{0x3a19, 0xf8},
-	{0x3c01, 0x80},
-	{0x3b07, 0x0c},
-	{0x380c, 0x07},
-	{0x380d, 0x3c},
 	{0x3814, 0x35},
 	{0x3815, 0x35},
 	{0x3708, 0x64},
 	{0x3709, 0x52},
-	{0x3808, 0x02},
-	{0x3809, 0x80},
-	{0x380a, 0x01},
-	{0x380b, 0xe0},
 	{0x3800, 0x00},
 	{0x3801, 0x10},
 	{0x3802, 0x00},
@@ -453,53 +354,17 @@ static struct regval_list ov5647_640x480_10bpp[] = {
 	{0x3805, 0x2f},
 	{0x3806, 0x07},
 	{0x3807, 0x9f},
-	{0x3630, 0x2e},
-	{0x3632, 0xe2},
-	{0x3633, 0x23},
-	{0x3634, 0x44},
-	{0x3620, 0x64},
-	{0x3621, 0xe0},
-	{0x3600, 0x37},
-	{0x3704, 0xa0},
-	{0x3703, 0x5a},
-	{0x3715, 0x78},
-	{0x3717, 0x01},
-	{0x3731, 0x02},
-	{0x370b, 0x60},
-	{0x3705, 0x1a},
-	{0x3f05, 0x02},
-	{0x3f06, 0x10},
-	{0x3f01, 0x0a},
-	{0x3a08, 0x01},
+	{0x3808, 0x02},
+	{0x3809, 0x80},
+	{0x380a, 0x01},
+	{0x380b, 0xe0},
 	{0x3a09, 0x2e},
 	{0x3a0a, 0x00},
 	{0x3a0b, 0xfb},
 	{0x3a0d, 0x02},
 	{0x3a0e, 0x01},
-	{0x3a0f, 0x58},
-	{0x3a10, 0x50},
-	{0x3a1b, 0x58},
-	{0x3a1e, 0x50},
-	{0x3a11, 0x60},
-	{0x3a1f, 0x28},
-	{0x4001, 0x02},
 	{0x4004, 0x02},
-	{0x4000, 0x09},
-	{0x3000, 0x00},
-	{0x3001, 0x00},
-	{0x3002, 0x00},
-	{0x3017, 0xe0},
-	{0x301c, 0xfc},
-	{0x3636, 0x06},
-	{0x3016, 0x08},
-	{0x3827, 0xec},
-	{0x3018, 0x44},
-	{0x3035, 0x21},
-	{0x3106, 0xf5},
-	{0x3034, 0x1a},
-	{0x301c, 0xf8},
 	{0x4800, 0x34},
-	{0x3503, 0x03},
 	{0x0100, 0x01},
 };
 
@@ -520,6 +385,7 @@ static const struct ov5647_mode ov5647_modes[] = {
 			.height		= 1944
 		},
 		.pixel_rate	= 87500000,
+		.link_freq_index = FREQ_INDEX_FULL,
 		.hts		= 2844,
 		.vts		= 0x7b0,
 		.reg_list	= ov5647_2592x1944_10bpp,
@@ -540,7 +406,8 @@ static const struct ov5647_mode ov5647_modes[] = {
 			.width		= 1928,
 			.height		= 1080,
 		},
-		.pixel_rate	= 81666700,
+		.pixel_rate	= 87500000,
+		.link_freq_index = FREQ_INDEX_FULL,
 		.hts		= 2416,
 		.vts		= 0x450,
 		.reg_list	= ov5647_1080p30_10bpp,
@@ -561,7 +428,8 @@ static const struct ov5647_mode ov5647_modes[] = {
 			.width		= 2592,
 			.height		= 1944,
 		},
-		.pixel_rate	= 81666700,
+		.pixel_rate	= 87500000,
+		.link_freq_index = FREQ_INDEX_FULL,
 		.hts		= 1896,
 		.vts		= 0x59b,
 		.reg_list	= ov5647_2x2binned_10bpp,
@@ -582,7 +450,8 @@ static const struct ov5647_mode ov5647_modes[] = {
 			.width		= 2560,
 			.height		= 1920,
 		},
-		.pixel_rate	= 58333000,
+		.pixel_rate	= 55000000,
+		.link_freq_index = FREQ_INDEX_VGA,
 		.hts		= 1852,
 		.vts		= 0x1f8,
 		.reg_list	= ov5647_640x480_10bpp,
@@ -601,7 +470,13 @@ static int ov5647_write16(struct v4l2_subdev *sd, u16 reg, u16 val)
 	int ret;
 
 	ret = i2c_master_send(client, data, 4);
-	if (ret < 0) {
+	/*
+	 * Writing the wrong number of bytes also needs to be flagged as an
+	 * error. Success needs to produce a 0 return code.
+	 */
+	if (ret == 4) {
+		ret = 0;
+	} else {
 		dev_dbg(&client->dev, "%s: i2c write error, reg: %x\n",
 			__func__, reg);
 		return ret;
@@ -617,10 +492,17 @@ static int ov5647_write(struct v4l2_subdev *sd, u16 reg, u8 val)
 	int ret;
 
 	ret = i2c_master_send(client, data, 3);
-	if (ret < 0) {
+	/*
+	 * Writing the wrong number of bytes also needs to be flagged as an
+	 * error. Success needs to produce a 0 return code.
+	 */
+	if (ret == 3) {
+		ret = 0;
+	} else {
 		dev_dbg(&client->dev, "%s: i2c write error, reg: %x\n",
 				__func__, reg);
-		return ret;
+		if (ret >= 0)
+			ret = -EINVAL;
 	}
 
 	return 0;
@@ -695,6 +577,13 @@ static int ov5647_set_mode(struct v4l2_subdev *sd)
 	if (ret < 0)
 		return ret;
 
+	ret = ov5647_write_array(sd, ov5647_common_regs,
+				 ARRAY_SIZE(ov5647_common_regs));
+	if (ret < 0) {
+		dev_err(&client->dev, "write sensor common regs error\n");
+		return ret;
+	}
+
 	ret = ov5647_write_array(sd, sensor->mode->reg_list,
 				 sensor->mode->num_regs);
 	if (ret < 0) {
@@ -777,6 +666,12 @@ static int ov5647_power_on(struct device *dev)
 
 	dev_dbg(dev, "OV5647 power on\n");
 
+	ret = regulator_bulk_enable(OV5647_NUM_SUPPLIES, sensor->supplies);
+	if (ret < 0) {
+		dev_err(dev, "Failed to enable regulators\n");
+		return ret;
+	}
+
 	if (sensor->pwdn) {
 		gpiod_set_value_cansleep(sensor->pwdn, 0);
 		msleep(PWDN_ACTIVE_DELAY_MS);
@@ -808,6 +703,7 @@ static int ov5647_power_on(struct device *dev)
 	clk_disable_unprepare(sensor->xclk);
 error_pwdn:
 	gpiod_set_value_cansleep(sensor->pwdn, 1);
+	regulator_bulk_disable(OV5647_NUM_SUPPLIES, sensor->supplies);
 
 	return ret;
 }
@@ -837,6 +733,7 @@ static int ov5647_power_off(struct device *dev)
 
 	clk_disable_unprepare(sensor->xclk);
 	gpiod_set_value_cansleep(sensor->pwdn, 1);
+	regulator_bulk_disable(OV5647_NUM_SUPPLIES, sensor->supplies);
 
 	return 0;
 }
@@ -873,6 +770,8 @@ static const struct v4l2_subdev_core_ops ov5647_subdev_core_ops = {
 	.g_register		= ov5647_sensor_get_register,
 	.s_register		= ov5647_sensor_set_register,
 #endif
+	.subscribe_event = v4l2_ctrl_subdev_subscribe_event,
+	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
 };
 
 static const struct v4l2_rect *
@@ -933,6 +832,25 @@ static const struct v4l2_subdev_video_ops ov5647_subdev_video_ops = {
 	.s_stream =		ov5647_s_stream,
 };
 
+/* This function returns the mbus code for the current settings of the
+   HFLIP and VFLIP controls. */
+
+static u32 ov5647_get_mbus_code(struct v4l2_subdev *sd)
+{
+	struct ov5647 *sensor = to_sensor(sd);
+	/* The control values are only 0 or 1. */
+	int index =  sensor->hflip->val | (sensor->vflip->val << 1);
+
+	static const u32 codes[4] = {
+		MEDIA_BUS_FMT_SGBRG10_1X10,
+		MEDIA_BUS_FMT_SBGGR10_1X10,
+		MEDIA_BUS_FMT_SRGGB10_1X10,
+		MEDIA_BUS_FMT_SGRBG10_1X10
+	};
+
+	return codes[index];
+}
+
 static int ov5647_enum_mbus_code(struct v4l2_subdev *sd,
 				 struct v4l2_subdev_state *sd_state,
 				 struct v4l2_subdev_mbus_code_enum *code)
@@ -940,7 +858,7 @@ static int ov5647_enum_mbus_code(struct v4l2_subdev *sd,
 	if (code->index > 0)
 		return -EINVAL;
 
-	code->code = MEDIA_BUS_FMT_SBGGR10_1X10;
+	code->code = ov5647_get_mbus_code(sd);
 
 	return 0;
 }
@@ -951,7 +869,7 @@ static int ov5647_enum_frame_size(struct v4l2_subdev *sd,
 {
 	const struct v4l2_mbus_framefmt *fmt;
 
-	if (fse->code != MEDIA_BUS_FMT_SBGGR10_1X10 ||
+	if (fse->code != ov5647_get_mbus_code(sd) ||
 	    fse->index >= ARRAY_SIZE(ov5647_modes))
 		return -EINVAL;
 
@@ -984,6 +902,8 @@ static int ov5647_get_pad_fmt(struct v4l2_subdev *sd,
 	}
 
 	*fmt = *sensor_format;
+	/* The code we pass back must reflect the current h/vflips. */
+	fmt->code = ov5647_get_mbus_code(sd);
 	mutex_unlock(&sensor->lock);
 
 	return 0;
@@ -1014,7 +934,8 @@ static int ov5647_set_pad_fmt(struct v4l2_subdev *sd,
 					 mode->pixel_rate, 1, mode->pixel_rate);
 
 		hblank = mode->hts - mode->format.width;
-		__v4l2_ctrl_modify_range(sensor->hblank, hblank, hblank, 1,
+		__v4l2_ctrl_modify_range(sensor->hblank, hblank,
+					 OV5647_HTS_MAX - mode->format.width, 1,
 					 hblank);
 
 		vblank = mode->vts - mode->format.height;
@@ -1029,8 +950,12 @@ static int ov5647_set_pad_fmt(struct v4l2_subdev *sd,
 					 sensor->exposure->minimum,
 					 exposure_max, sensor->exposure->step,
 					 exposure_def);
+
+		__v4l2_ctrl_s_ctrl(sensor->link_freq, mode->link_freq_index);
 	}
 	*fmt = mode->format;
+	/* The code we pass back must reflect the current h/vflips. */
+	fmt->code = ov5647_get_mbus_code(sd);
 	mutex_unlock(&sensor->lock);
 
 	return 0;
@@ -1206,6 +1131,25 @@ static int ov5647_s_exposure(struct v4l2_subdev *sd, u32 val)
 	return ov5647_write(sd, OV5647_REG_EXP_LO, (val & 0xf) << 4);
 }
 
+static int ov5647_s_flip( struct v4l2_subdev *sd, u16 reg, u32 ctrl_val)
+{
+	int ret;
+	u8 reg_val;
+
+	/* Set or clear bit 1 and leave everything else alone. */
+	ret = ov5647_read(sd, reg, &reg_val);
+	if (ret == 0) {
+		if (ctrl_val)
+			reg_val |= 2;
+		else
+			reg_val &= ~2;
+
+		ret = ov5647_write(sd, reg, reg_val);
+	}
+
+	return ret;
+}
+
 static int ov5647_s_ctrl(struct v4l2_ctrl *ctrl)
 {
 	struct ov5647 *sensor = container_of(ctrl->handler,
@@ -1257,6 +1201,10 @@ static int ov5647_s_ctrl(struct v4l2_ctrl *ctrl)
 		ret = ov5647_write16(sd, OV5647_REG_VTS_HI,
 				     sensor->mode->format.height + ctrl->val);
 		break;
+	case V4L2_CID_HBLANK:
+		ret = ov5647_write16(sd, OV5647_REG_HTS_HI,
+				     sensor->mode->format.width + ctrl->val);
+		break;
 	case V4L2_CID_TEST_PATTERN:
 		ret = ov5647_write(sd, OV5647_REG_ISPCTRL3D,
 				   ov5647_test_pattern_val[ctrl->val]);
@@ -1264,10 +1212,17 @@ static int ov5647_s_ctrl(struct v4l2_ctrl *ctrl)
 
 	/* Read-only, but we adjust it based on mode. */
 	case V4L2_CID_PIXEL_RATE:
-	case V4L2_CID_HBLANK:
 		/* Read-only, but we adjust it based on mode. */
 		break;
 
+	case V4L2_CID_HFLIP:
+		/* There's an in-built hflip in the sensor, so account for that here. */
+		ov5647_s_flip(sd, OV5647_REG_HFLIP, !ctrl->val);
+		break;
+	case V4L2_CID_VFLIP:
+		ov5647_s_flip(sd, OV5647_REG_VFLIP, ctrl->val);
+		break;
+
 	default:
 		dev_info(&client->dev,
 			 "Control (id:0x%x, val:0x%x) not supported\n",
@@ -1284,14 +1239,25 @@ static const struct v4l2_ctrl_ops ov5647_ctrl_ops = {
 	.s_ctrl = ov5647_s_ctrl,
 };
 
-static int ov5647_init_controls(struct ov5647 *sensor)
+static int ov5647_configure_regulators(struct device *dev,
+				       struct ov5647 *sensor)
+{
+	unsigned int i;
+
+	for (i = 0; i < OV5647_NUM_SUPPLIES; i++)
+		sensor->supplies[i].supply = ov5647_supply_names[i];
+
+	return devm_regulator_bulk_get(dev, OV5647_NUM_SUPPLIES,
+				       sensor->supplies);
+}
+
+static int ov5647_init_controls(struct ov5647 *sensor, struct device *dev)
 {
 	struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd);
 	int hblank, exposure_max, exposure_def;
+	struct v4l2_fwnode_device_properties props;
 
-	v4l2_ctrl_handler_init(&sensor->ctrls, 9);
-
-	sensor->ctrls.lock = &sensor->lock;
+	v4l2_ctrl_handler_init(&sensor->ctrls, 10);
 
 	v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops,
 			  V4L2_CID_AUTOGAIN, 0, 1, 1, 0);
@@ -1322,10 +1288,11 @@ static int ov5647_init_controls(struct ov5647 *sensor)
 					       sensor->mode->pixel_rate, 1,
 					       sensor->mode->pixel_rate);
 
-	/* By default, HBLANK is read only, but it does change per mode. */
 	hblank = sensor->mode->hts - sensor->mode->format.width;
 	sensor->hblank = v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops,
-					   V4L2_CID_HBLANK, hblank, hblank, 1,
+					   V4L2_CID_HBLANK, hblank,
+					   OV5647_HTS_MAX -
+					   sensor->mode->format.width, 1,
 					   hblank);
 
 	sensor->vblank = v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops,
@@ -1340,11 +1307,33 @@ static int ov5647_init_controls(struct ov5647 *sensor)
 				     ARRAY_SIZE(ov5647_test_pattern_menu) - 1,
 				     0, 0, ov5647_test_pattern_menu);
 
+	sensor->hflip = v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops,
+					  V4L2_CID_HFLIP, 0, 1, 1, 0);
+	if (sensor->hflip)
+		sensor->hflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+	sensor->vflip = v4l2_ctrl_new_std(&sensor->ctrls, &ov5647_ctrl_ops,
+					  V4L2_CID_VFLIP, 0, 1, 1, 0);
+	if (sensor->vflip)
+		sensor->vflip->flags |= V4L2_CTRL_FLAG_MODIFY_LAYOUT;
+
+	sensor->link_freq =
+		v4l2_ctrl_new_int_menu(&sensor->ctrls, &ov5647_ctrl_ops,
+				       V4L2_CID_LINK_FREQ,
+				       ARRAY_SIZE(ov5647_link_freqs) - 1, 0,
+				       ov5647_link_freqs);
+	if (sensor->link_freq)
+		sensor->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	v4l2_fwnode_device_parse(dev, &props);
+
+	v4l2_ctrl_new_fwnode_properties(&sensor->ctrls, &ov5647_ctrl_ops,
+					&props);
+
 	if (sensor->ctrls.error)
 		goto handler_free;
 
 	sensor->pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY;
-	sensor->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
 	sensor->sd.ctrl_handler = &sensor->ctrls;
 
 	return 0;
@@ -1419,19 +1408,25 @@ static int ov5647_probe(struct i2c_client *client)
 		return -EINVAL;
 	}
 
+	ret = ov5647_configure_regulators(dev, sensor);
+	if (ret) {
+		dev_err(dev, "Failed to get power regulators\n");
+		return ret;
+	}
+
 	mutex_init(&sensor->lock);
 
 	sensor->mode = OV5647_DEFAULT_MODE;
 
+	ret = ov5647_init_controls(sensor, dev);
+	if (ret)
+		goto mutex_destroy;
+
 	sd = &sensor->sd;
 	v4l2_i2c_subdev_init(sd, client, &ov5647_subdev_ops);
 	sd->internal_ops = &ov5647_subdev_internal_ops;
 	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
 
-	ret = ov5647_init_controls(sensor);
-	if (ret)
-		goto mutex_destroy;
-
 	sensor->pad.flags = MEDIA_PAD_FL_SOURCE;
 	sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
 	ret = media_entity_pads_init(&sd->entity, 1, &sensor->pad);
@@ -1446,7 +1441,7 @@ static int ov5647_probe(struct i2c_client *client)
 	if (ret < 0)
 		goto power_off;
 
-	ret = v4l2_async_register_subdev(sd);
+	ret = v4l2_async_register_subdev_sensor(sd);
 	if (ret < 0)
 		goto power_off;
 
diff --git a/drivers/media/i2c/ov7251.c b/drivers/media/i2c/ov7251.c
index 3226888d77e9..14f0128c340e 100644
--- a/drivers/media/i2c/ov7251.c
+++ b/drivers/media/i2c/ov7251.c
@@ -23,6 +23,10 @@
 #include <media/v4l2-fwnode.h>
 #include <media/v4l2-subdev.h>
 
+static int trigger_mode;
+module_param(trigger_mode, int, 0644);
+MODULE_PARM_DESC(trigger_mode, "Set vsync trigger mode: 0=standalone, (1=source - not implemented), 2=sink");
+
 #define OV7251_SC_MODE_SELECT		0x0100
 #define OV7251_SC_MODE_SELECT_SW_STANDBY	0x0
 #define OV7251_SC_MODE_SELECT_STREAMING		0x1
@@ -525,7 +529,6 @@ static const struct reg_value ov7251_setting_vga_90fps[] = {
 	{ 0x3662, 0x01 },
 	{ 0x3663, 0x70 },
 	{ 0x3664, 0x50 },
-	{ 0x3666, 0x0a },
 	{ 0x3669, 0x1a },
 	{ 0x366a, 0x00 },
 	{ 0x366b, 0x50 },
@@ -592,9 +595,8 @@ static const struct reg_value ov7251_setting_vga_90fps[] = {
 	{ 0x3c00, 0x89 },
 	{ 0x3c01, 0x63 },
 	{ 0x3c02, 0x01 },
-	{ 0x3c03, 0x00 },
 	{ 0x3c04, 0x00 },
-	{ 0x3c05, 0x03 },
+	{ 0x3c05, 0x01 },
 	{ 0x3c06, 0x00 },
 	{ 0x3c07, 0x06 },
 	{ 0x3c0c, 0x01 },
@@ -624,6 +626,16 @@ static const struct reg_value ov7251_setting_vga_90fps[] = {
 	{ 0x5001, 0x80 },
 };
 
+static const struct reg_value ov7251_ext_trig_on[] = {
+	{ 0x3666, 0x00 },
+	{ 0x3c03, 0x17 },
+};
+
+static const struct reg_value ov7251_ext_trig_off[] = {
+	{ 0x3666, 0x0a },
+	{ 0x3c03, 0x00 },
+};
+
 static const unsigned long supported_xclk_rates[] = {
 	[OV7251_19_2_MHZ] = 19200000,
 	[OV7251_24_MHZ] = 24000000,
@@ -1053,7 +1065,7 @@ static int ov7251_s_ctrl(struct v4l2_ctrl *ctrl)
 	case V4L2_CID_EXPOSURE:
 		ret = ov7251_set_exposure(ov7251, ctrl->val);
 		break;
-	case V4L2_CID_GAIN:
+	case V4L2_CID_ANALOGUE_GAIN:
 		ret = ov7251_set_gain(ov7251, ctrl->val);
 		break;
 	case V4L2_CID_TEST_PATTERN:
@@ -1348,6 +1360,14 @@ static int ov7251_s_stream(struct v4l2_subdev *subdev, int enable)
 			return ret;
 		}
 
+		ret = ov7251_set_register_array(ov7251,
+					ov7251_global_init_setting,
+					ARRAY_SIZE(ov7251_global_init_setting));
+		if (ret < 0) {
+			dev_err(ov7251->dev, "could not set global_init_setting\n");
+			goto err_power_down;
+		}
+
 		ret = ov7251_pll_configure(ov7251);
 		if (ret) {
 			dev_err(ov7251->dev, "error configuring PLLs\n");
@@ -1368,6 +1388,23 @@ static int ov7251_s_stream(struct v4l2_subdev *subdev, int enable)
 			dev_err(ov7251->dev, "could not sync v4l2 controls\n");
 			goto err_power_down;
 		}
+
+		/* Set vsync trigger mode */
+		switch (trigger_mode) {
+		case 2:
+			ov7251_set_register_array(ov7251,
+						  ov7251_ext_trig_on,
+						  ARRAY_SIZE(ov7251_ext_trig_on));
+			break;
+		case 0:
+		default:
+			/* case 1 for ext trig source currently not implemented */
+			ov7251_set_register_array(ov7251,
+						  ov7251_ext_trig_off,
+						  ARRAY_SIZE(ov7251_ext_trig_off));
+			break;
+		}
+
 		ret = ov7251_write_reg(ov7251, OV7251_SC_MODE_SELECT,
 				       OV7251_SC_MODE_SELECT_STREAMING);
 		if (ret)
@@ -1564,7 +1601,7 @@ static int ov7251_init_ctrls(struct ov7251 *ov7251)
 	s64 pixel_rate;
 	int hblank;
 
-	v4l2_ctrl_handler_init(&ov7251->ctrls, 7);
+	v4l2_ctrl_handler_init(&ov7251->ctrls, 9);
 	ov7251->ctrls.lock = &ov7251->lock;
 
 	v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops,
@@ -1574,7 +1611,7 @@ static int ov7251_init_ctrls(struct ov7251 *ov7251)
 	ov7251->exposure = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops,
 					     V4L2_CID_EXPOSURE, 1, 32, 1, 32);
 	ov7251->gain = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops,
-					 V4L2_CID_GAIN, 16, 1023, 1, 16);
+					 V4L2_CID_ANALOGUE_GAIN, 16, 1023, 1, 16);
 	v4l2_ctrl_new_std_menu_items(&ov7251->ctrls, &ov7251_ctrl_ops,
 				     V4L2_CID_TEST_PATTERN,
 				     ARRAY_SIZE(ov7251_test_pattern_menu) - 1,
@@ -1623,6 +1660,7 @@ static int ov7251_init_ctrls(struct ov7251 *ov7251)
 
 static int ov7251_probe(struct i2c_client *client)
 {
+	struct v4l2_fwnode_device_properties props;
 	struct device *dev = &client->dev;
 	struct ov7251 *ov7251;
 	unsigned int rate = 0, clk_rate = 0;
@@ -1698,7 +1736,8 @@ static int ov7251_probe(struct i2c_client *client)
 		return PTR_ERR(ov7251->analog_regulator);
 	}
 
-	ov7251->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
+	ov7251->enable_gpio = devm_gpiod_get_optional(dev, "enable",
+						      GPIOD_OUT_HIGH);
 	if (IS_ERR(ov7251->enable_gpio)) {
 		dev_err(dev, "cannot get enable gpio\n");
 		return PTR_ERR(ov7251->enable_gpio);
@@ -1713,6 +1752,15 @@ static int ov7251_probe(struct i2c_client *client)
 		goto destroy_mutex;
 	}
 
+	ret = v4l2_fwnode_device_parse(&client->dev, &props);
+	if (ret)
+		goto free_ctrl;
+
+	ret = v4l2_ctrl_new_fwnode_properties(&ov7251->ctrls, &ov7251_ctrl_ops,
+					      &props);
+	if (ret)
+		goto free_ctrl;
+
 	v4l2_i2c_subdev_init(&ov7251->sd, client, &ov7251_subdev_ops);
 	ov7251->sd.internal_ops = &ov7251_internal_ops;
 	ov7251->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
diff --git a/drivers/media/i2c/ov9282.c b/drivers/media/i2c/ov9282.c
index 87e5d7ce5a47..e9d4253357f0 100644
--- a/drivers/media/i2c/ov9282.c
+++ b/drivers/media/i2c/ov9282.c
@@ -65,6 +65,14 @@
 #define OV9282_REG_MIPI_CTRL00	0x4800
 #define OV9282_GATED_CLOCK	BIT(5)
 
+/* Trigger mode registers */
+#define OV9282_REG_POWER_CTRL 0x4F00
+#define OV9282_REG_LOW_POWER_MODE_CTRL 0x3030
+#define OV9282_REG_NUM_FRAME_ON_TRIG 0x303F
+#define OV9282_REG_SLEEP_PERIOD_CTRL0 0x302C
+#define OV9282_REG_SLEEP_PERIOD_CTRL3 0x302F
+#define OV9282_REG_TIMING_23 0x3823
+
 /* Input clock rate */
 #define OV9282_INCLK_RATE	24000000
 
@@ -187,6 +195,7 @@ struct ov9282 {
 	const struct ov9282_mode *cur_mode;
 	u32 code;
 	struct mutex mutex;
+	int trigger_mode;
 };
 
 static const s64 link_freq[] = {
@@ -947,6 +956,52 @@ static int ov9282_get_selection(struct v4l2_subdev *sd,
 	return -EINVAL;
 }
 
+/**
+ * ov9282_apply_trigger_config() - Configure sensor for FSIN external trigger mode
+ * @ov9282: pointer to ov9282 device
+ *
+ * Return: 0 on success, error code otherwise.
+ */
+static int ov9282_apply_trigger_config(struct ov9282 *ov9282)
+{
+	int ret;
+
+	ret = ov9282_write_reg(ov9282, OV9282_REG_MODE_SELECT,
+			1, OV9282_MODE_STANDBY);
+	if (ret)
+		return ret;
+
+	/* Low power mode */
+	ret = ov9282_write_reg(ov9282, OV9282_REG_POWER_CTRL, 1, 0x01);
+	if (ret)
+		return ret;
+
+	/* External trigger snapshot */
+	ret = ov9282_write_reg(ov9282, OV9282_REG_LOW_POWER_MODE_CTRL, 1, 0x04);
+	if (ret)
+		return ret;
+
+	/* 1 frame per trigger */
+	ret = ov9282_write_reg(ov9282, OV9282_REG_NUM_FRAME_ON_TRIG, 1, 0x01);
+	if (ret)
+		return ret;
+
+	ret = ov9282_write_reg(ov9282, OV9282_REG_SLEEP_PERIOD_CTRL0, 1, 0x00);
+	if (ret)
+		return ret;
+
+	ret = ov9282_write_reg(ov9282, OV9282_REG_SLEEP_PERIOD_CTRL3, 1, 0x7F);
+	if (ret)
+		return ret;
+
+	/* No auto wake */
+	ret = ov9282_write_reg(ov9282, OV9282_REG_TIMING_23, 1, 0x00);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
 /**
  * ov9282_start_streaming() - Start sensor stream
  * @ov9282: pointer to ov9282 device
@@ -998,15 +1053,26 @@ static int ov9282_start_streaming(struct ov9282 *ov9282)
 		return ret;
 	}
 
-	/* Start streaming */
-	ret = ov9282_write_reg(ov9282, OV9282_REG_MODE_SELECT,
-			       1, OV9282_MODE_STREAMING);
-	if (ret) {
-		dev_err(ov9282->dev, "fail to start streaming");
-		return ret;
+	/* Configure FSIN external trigger mode */
+	if (ov9282->trigger_mode > 0) {
+		ret = ov9282_apply_trigger_config(ov9282);
+		if (ret) {
+			dev_err(ov9282->dev, "failed to config external trigger mode");
+			return ret;
+		}
+		/* stay in standby mode and wait for trigger signal */
+		ret = ov9282_write_reg(ov9282, OV9282_REG_MODE_SELECT,
+				       1, OV9282_MODE_STANDBY);
+	} else {
+		/* Start streaming */
+		ret = ov9282_write_reg(ov9282, OV9282_REG_MODE_SELECT,
+				       1, OV9282_MODE_STREAMING);
 	}
 
-	return 0;
+	if (ret)
+		dev_err(ov9282->dev, "fail to start streaming");
+
+	return ret;
 }
 
 /**
@@ -1069,12 +1135,16 @@ static int ov9282_set_stream(struct v4l2_subdev *sd, int enable)
 static int ov9282_detect(struct ov9282 *ov9282)
 {
 	int ret;
-	u32 val;
+	u32 val, msb;
 
-	ret = ov9282_read_reg(ov9282, OV9282_REG_ID, 2, &val);
+	ret = ov9282_read_reg(ov9282, OV9282_REG_ID + 1, 1, &val);
+	if (ret)
+		return ret;
+	ret = ov9282_read_reg(ov9282, OV9282_REG_ID, 1, &msb);
 	if (ret)
 		return ret;
 
+	val |= (msb << 8);
 	if (val != OV9282_ID) {
 		dev_err(ov9282->dev, "chip id mismatch: %x!=%x",
 			OV9282_ID, val);
@@ -1388,6 +1458,7 @@ static int ov9282_probe(struct i2c_client *client)
 {
 	struct ov9282 *ov9282;
 	int ret;
+	u32 trig_mod;
 
 	ov9282 = devm_kzalloc(&client->dev, sizeof(*ov9282), GFP_KERNEL);
 	if (!ov9282)
@@ -1427,6 +1498,10 @@ static int ov9282_probe(struct i2c_client *client)
 	ov9282->code = MEDIA_BUS_FMT_Y10_1X10;
 	ov9282->vblank = ov9282->cur_mode->vblank;
 
+	ret = of_property_read_u32(client->dev.of_node,
+			"trigger-mode", &trig_mod);
+	ov9282->trigger_mode = (ret == 0) ? trig_mod : -1;
+
 	ret = ov9282_init_controls(ov9282);
 	if (ret) {
 		dev_err(ov9282->dev, "failed to init controls: %d", ret);
diff --git a/drivers/media/i2c/tc358743.c b/drivers/media/i2c/tc358743.c
index 6a023fe22635..fb799a45398c 100644
--- a/drivers/media/i2c/tc358743.c
+++ b/drivers/media/i2c/tc358743.c
@@ -649,6 +649,7 @@ static void tc358743_set_ref_clk(struct v4l2_subdev *sd)
 static void tc358743_set_csi_color_space(struct v4l2_subdev *sd)
 {
 	struct tc358743_state *state = to_state(sd);
+	struct device *dev = &state->i2c_client->dev;
 
 	switch (state->mbus_fmt_code) {
 	case MEDIA_BUS_FMT_UYVY8_1X16:
@@ -664,7 +665,17 @@ static void tc358743_set_csi_color_space(struct v4l2_subdev *sd)
 		mutex_unlock(&state->confctl_mutex);
 		break;
 	case MEDIA_BUS_FMT_RGB888_1X24:
-		v4l2_dbg(2, debug, sd, "%s: RGB 888 24-bit\n", __func__);
+		/*
+		 * The driver was initially introduced with RGB888
+		 * support, but CSI really means BGR.
+		 *
+		 * Since we might have applications that would have
+		 * hard-coded the RGB888, let's support both.
+		 */
+		dev_warn_once(dev, "RGB format isn't actually supported by the hardware. The application should be fixed to use BGR.");
+		fallthrough;
+	case MEDIA_BUS_FMT_BGR888_1X24:
+		v4l2_dbg(2, debug, sd, "%s: BGR 888 24-bit\n", __func__);
 		i2c_wr8_and_or(sd, VOUT_SET2,
 				~(MASK_SEL422 | MASK_VOUT_422FIL_100) & 0xff,
 				0x00);
@@ -1325,11 +1336,26 @@ static int tc358743_log_status(struct v4l2_subdev *sd)
 	v4l2_info(sd, "Stopped: %s\n",
 			(i2c_rd16(sd, CSI_STATUS) & MASK_S_HLT) ?
 			"yes" : "no");
-	v4l2_info(sd, "Color space: %s\n",
-			state->mbus_fmt_code == MEDIA_BUS_FMT_UYVY8_1X16 ?
-			"YCbCr 422 16-bit" :
-			state->mbus_fmt_code == MEDIA_BUS_FMT_RGB888_1X24 ?
-			"RGB 888 24-bit" : "Unsupported");
+
+	switch (state->mbus_fmt_code) {
+	case MEDIA_BUS_FMT_BGR888_1X24:
+		/*
+		 * The driver was initially introduced with RGB888
+		 * support, but CSI really means BGR.
+		 *
+		 * Since we might have applications that would have
+		 * hard-coded the RGB888, let's support both.
+		 */
+	case MEDIA_BUS_FMT_RGB888_1X24:
+		v4l2_info(sd, "Color space: BGR 888 24-bit\n");
+		break;
+	case MEDIA_BUS_FMT_UYVY8_1X16:
+		v4l2_info(sd, "Color space: YCbCr 422 16-bit\n");
+		break;
+	default:
+		v4l2_info(sd, "Color space: Unsupported\n");
+		break;
+	}
 
 	v4l2_info(sd, "-----%s status-----\n", is_hdmi(sd) ? "HDMI" : "DVI-D");
 	v4l2_info(sd, "HDCP encrypted content: %s\n",
@@ -1666,11 +1692,18 @@ static int tc358743_enum_mbus_code(struct v4l2_subdev *sd,
 {
 	switch (code->index) {
 	case 0:
-		code->code = MEDIA_BUS_FMT_RGB888_1X24;
+		code->code = MEDIA_BUS_FMT_BGR888_1X24;
 		break;
 	case 1:
 		code->code = MEDIA_BUS_FMT_UYVY8_1X16;
 		break;
+	case 2:
+		/*
+		 * We need to keep RGB888 for backward compatibility,
+		 * but we should list it last for userspace to pick BGR.
+		 */
+		code->code = MEDIA_BUS_FMT_RGB888_1X24;
+		break;
 	default:
 		return -EINVAL;
 	}
@@ -1680,6 +1713,7 @@ static int tc358743_enum_mbus_code(struct v4l2_subdev *sd,
 static u32 tc358743_g_colorspace(u32 code)
 {
 	switch (code) {
+	case MEDIA_BUS_FMT_BGR888_1X24:
 	case MEDIA_BUS_FMT_RGB888_1X24:
 		return V4L2_COLORSPACE_SRGB;
 	case MEDIA_BUS_FMT_UYVY8_1X16:
@@ -1717,7 +1751,8 @@ static int tc358743_set_fmt(struct v4l2_subdev *sd,
 	u32 code = format->format.code; /* is overwritten by get_fmt */
 	int ret = tc358743_get_fmt(sd, sd_state, format);
 
-	if (code == MEDIA_BUS_FMT_RGB888_1X24 ||
+	if (code == MEDIA_BUS_FMT_BGR888_1X24 ||
+	    code == MEDIA_BUS_FMT_RGB888_1X24 ||
 	    code == MEDIA_BUS_FMT_UYVY8_1X16)
 		format->format.code = code;
 	format->format.colorspace = tc358743_g_colorspace(format->format.code);
@@ -1980,6 +2015,7 @@ static int tc358743_probe_of(struct tc358743_state *state)
 	/*
 	 * The CSI bps per lane must be between 62.5 Mbps and 1 Gbps.
 	 * The default is 594 Mbps for 4-lane 1080p60 or 2-lane 720p60.
+	 * 972 Mbps allows 1080P50 UYVY over 2-lane.
 	 */
 	bps_pr_lane = 2 * endpoint.link_frequencies[0];
 	if (bps_pr_lane < 62500000U || bps_pr_lane > 1000000000U) {
@@ -1993,23 +2029,42 @@ static int tc358743_probe_of(struct tc358743_state *state)
 			       state->pdata.refclk_hz * state->pdata.pll_prd;
 
 	/*
-	 * FIXME: These timings are from REF_02 for 594 Mbps per lane (297 MHz
-	 * link frequency). In principle it should be possible to calculate
+	 * FIXME: These timings are from REF_02 for 594 or 972 Mbps per lane
+	 * (297 MHz or 486 MHz link frequency).
+	 * In principle it should be possible to calculate
 	 * them based on link frequency and resolution.
 	 */
-	if (bps_pr_lane != 594000000U)
+	switch (bps_pr_lane) {
+	default:
 		dev_warn(dev, "untested bps per lane: %u bps\n", bps_pr_lane);
-	state->pdata.lineinitcnt = 0xe80;
-	state->pdata.lptxtimecnt = 0x003;
-	/* tclk-preparecnt: 3, tclk-zerocnt: 20 */
-	state->pdata.tclk_headercnt = 0x1403;
-	state->pdata.tclk_trailcnt = 0x00;
-	/* ths-preparecnt: 3, ths-zerocnt: 1 */
-	state->pdata.ths_headercnt = 0x0103;
-	state->pdata.twakeup = 0x4882;
-	state->pdata.tclk_postcnt = 0x008;
-	state->pdata.ths_trailcnt = 0x2;
-	state->pdata.hstxvregcnt = 0;
+		fallthrough;
+	case 594000000U:
+		state->pdata.lineinitcnt = 0xe80;
+		state->pdata.lptxtimecnt = 0x003;
+		/* tclk-preparecnt: 3, tclk-zerocnt: 20 */
+		state->pdata.tclk_headercnt = 0x1403;
+		state->pdata.tclk_trailcnt = 0x00;
+		/* ths-preparecnt: 3, ths-zerocnt: 1 */
+		state->pdata.ths_headercnt = 0x0103;
+		state->pdata.twakeup = 0x4882;
+		state->pdata.tclk_postcnt = 0x008;
+		state->pdata.ths_trailcnt = 0x2;
+		state->pdata.hstxvregcnt = 0;
+		break;
+	case 972000000U:
+		state->pdata.lineinitcnt = 0x1b58;
+		state->pdata.lptxtimecnt = 0x007;
+		/* tclk-preparecnt: 6, tclk-zerocnt: 40 */
+		state->pdata.tclk_headercnt = 0x2806;
+		state->pdata.tclk_trailcnt = 0x00;
+		/* ths-preparecnt: 6, ths-zerocnt: 8 */
+		state->pdata.ths_headercnt = 0x0806;
+		state->pdata.twakeup = 0x4268;
+		state->pdata.tclk_postcnt = 0x008;
+		state->pdata.ths_trailcnt = 0x5;
+		state->pdata.hstxvregcnt = 0;
+		break;
+	}
 
 	state->reset_gpio = devm_gpiod_get_optional(dev, "reset",
 						    GPIOD_OUT_LOW);
@@ -2117,7 +2172,7 @@ static int tc358743_probe(struct i2c_client *client)
 	if (err < 0)
 		goto err_hdl;
 
-	state->mbus_fmt_code = MEDIA_BUS_FMT_RGB888_1X24;
+	state->mbus_fmt_code = MEDIA_BUS_FMT_BGR888_1X24;
 
 	sd->dev = &client->dev;
 
diff --git a/drivers/media/mc/mc-device.c b/drivers/media/mc/mc-device.c
index c0dd4ae57227..5a458160200a 100644
--- a/drivers/media/mc/mc-device.c
+++ b/drivers/media/mc/mc-device.c
@@ -679,6 +679,23 @@ void media_device_unregister_entity(struct media_entity *entity)
 }
 EXPORT_SYMBOL_GPL(media_device_unregister_entity);
 
+#ifdef CONFIG_DEBUG_FS
+/*
+ * Log the state of media requests.
+ * Very useful for debugging.
+ */
+static int media_device_requests(struct seq_file *file, void *priv)
+{
+	struct media_device *dev = dev_get_drvdata(file->private);
+
+	seq_printf(file, "number of requests: %d\n",
+		   atomic_read(&dev->num_requests));
+	seq_printf(file, "number of request objects: %d\n",
+		   atomic_read(&dev->num_request_objects));
+	return 0;
+}
+#endif
+
 void media_device_init(struct media_device *mdev)
 {
 	INIT_LIST_HEAD(&mdev->entities);
@@ -697,6 +714,9 @@ void media_device_init(struct media_device *mdev)
 		media_set_bus_info(mdev->bus_info, sizeof(mdev->bus_info),
 				   mdev->dev);
 
+	atomic_set(&mdev->num_requests, 0);
+	atomic_set(&mdev->num_request_objects, 0);
+
 	dev_dbg(mdev->dev, "Media device initialized\n");
 }
 EXPORT_SYMBOL_GPL(media_device_init);
@@ -748,6 +768,15 @@ int __must_check __media_device_register(struct media_device *mdev,
 
 	dev_dbg(mdev->dev, "Media device registered\n");
 
+#ifdef CONFIG_DEBUG_FS
+	if (!media_debugfs_root)
+		media_debugfs_root = debugfs_create_dir("media", NULL);
+	mdev->media_dir = debugfs_create_dir(dev_name(&devnode->dev),
+					     media_debugfs_root);
+	debugfs_create_devm_seqfile(&devnode->dev, "requests",
+				    mdev->media_dir, media_device_requests);
+#endif
+
 	return 0;
 }
 EXPORT_SYMBOL_GPL(__media_device_register);
@@ -824,6 +853,7 @@ void media_device_unregister(struct media_device *mdev)
 
 	dev_dbg(mdev->dev, "Media device unregistered\n");
 
+	debugfs_remove_recursive(mdev->media_dir);
 	device_remove_file(&mdev->devnode->dev, &dev_attr_model);
 	media_devnode_unregister(mdev->devnode);
 	/* devnode free is handled in media_devnode_*() */
diff --git a/drivers/media/mc/mc-devnode.c b/drivers/media/mc/mc-devnode.c
index 6daa7aa99442..51260a7d6921 100644
--- a/drivers/media/mc/mc-devnode.c
+++ b/drivers/media/mc/mc-devnode.c
@@ -45,6 +45,9 @@ static dev_t media_dev_t;
 static DEFINE_MUTEX(media_devnode_lock);
 static DECLARE_BITMAP(media_devnode_nums, MEDIA_NUM_DEVICES);
 
+/* debugfs */
+struct dentry *media_debugfs_root;
+
 /* Called when the last user of the media device exits. */
 static void media_devnode_release(struct device *cd)
 {
@@ -231,6 +234,7 @@ int __must_check media_devnode_register(struct media_device *mdev,
 	if (devnode->parent)
 		devnode->dev.parent = devnode->parent;
 	dev_set_name(&devnode->dev, "media%d", devnode->minor);
+	dev_set_drvdata(&devnode->dev, mdev);
 	device_initialize(&devnode->dev);
 
 	/* Part 2: Initialize the character device */
@@ -309,6 +313,7 @@ static int __init media_devnode_init(void)
 
 static void __exit media_devnode_exit(void)
 {
+	debugfs_remove_recursive(media_debugfs_root);
 	bus_unregister(&media_bus_type);
 	unregister_chrdev_region(media_dev_t, MEDIA_NUM_DEVICES);
 }
diff --git a/drivers/media/mc/mc-request.c b/drivers/media/mc/mc-request.c
index 0c98a6b45be7..1bef09f6051b 100644
--- a/drivers/media/mc/mc-request.c
+++ b/drivers/media/mc/mc-request.c
@@ -54,6 +54,7 @@ static void media_request_clean(struct media_request *req)
 	req->access_count = 0;
 	WARN_ON(req->num_incomplete_objects);
 	req->num_incomplete_objects = 0;
+	req->manual_completion = false;
 	wake_up_interruptible_all(&req->poll_wait);
 }
 
@@ -74,6 +75,7 @@ static void media_request_release(struct kref *kref)
 		mdev->ops->req_free(req);
 	else
 		kfree(req);
+	atomic_dec(&mdev->num_requests);
 }
 
 void media_request_put(struct media_request *req)
@@ -324,6 +326,7 @@ int media_request_alloc(struct media_device *mdev, int *alloc_fd)
 	req->mdev = mdev;
 	req->state = MEDIA_REQUEST_STATE_IDLE;
 	req->num_incomplete_objects = 0;
+	req->manual_completion = false;
 	kref_init(&req->kref);
 	INIT_LIST_HEAD(&req->objects);
 	spin_lock_init(&req->lock);
@@ -335,6 +338,7 @@ int media_request_alloc(struct media_device *mdev, int *alloc_fd)
 
 	snprintf(req->debug_str, sizeof(req->debug_str), "%u:%d",
 		 atomic_inc_return(&mdev->request_id), fd);
+	atomic_inc(&mdev->num_requests);
 	dev_dbg(mdev->dev, "request: allocated %s\n", req->debug_str);
 
 	fd_install(fd, filp);
@@ -358,10 +362,12 @@ static void media_request_object_release(struct kref *kref)
 	struct media_request_object *obj =
 		container_of(kref, struct media_request_object, kref);
 	struct media_request *req = obj->req;
+	struct media_device *mdev = obj->mdev;
 
 	if (WARN_ON(req))
 		media_request_object_unbind(obj);
 	obj->ops->release(obj);
+	atomic_dec(&mdev->num_request_objects);
 }
 
 struct media_request_object *
@@ -426,6 +432,7 @@ int media_request_object_bind(struct media_request *req,
 	obj->req = req;
 	obj->ops = ops;
 	obj->priv = priv;
+	obj->mdev = req->mdev;
 
 	if (is_buffer)
 		list_add_tail(&obj->list, &req->objects);
@@ -433,6 +440,7 @@ int media_request_object_bind(struct media_request *req,
 		list_add(&obj->list, &req->objects);
 	req->num_incomplete_objects++;
 	ret = 0;
+	atomic_inc(&obj->mdev->num_request_objects);
 
 unlock:
 	spin_unlock_irqrestore(&req->lock, flags);
@@ -470,7 +478,7 @@ void media_request_object_unbind(struct media_request_object *obj)
 
 	req->num_incomplete_objects--;
 	if (req->state == MEDIA_REQUEST_STATE_QUEUED &&
-	    !req->num_incomplete_objects) {
+	    !req->num_incomplete_objects && !req->manual_completion) {
 		req->state = MEDIA_REQUEST_STATE_COMPLETE;
 		completed = true;
 		wake_up_interruptible_all(&req->poll_wait);
@@ -499,7 +507,7 @@ void media_request_object_complete(struct media_request_object *obj)
 	    WARN_ON(req->state != MEDIA_REQUEST_STATE_QUEUED))
 		goto unlock;
 
-	if (!--req->num_incomplete_objects) {
+	if (!--req->num_incomplete_objects && !req->manual_completion) {
 		req->state = MEDIA_REQUEST_STATE_COMPLETE;
 		wake_up_interruptible_all(&req->poll_wait);
 		completed = true;
@@ -510,3 +518,35 @@ void media_request_object_complete(struct media_request_object *obj)
 		media_request_put(req);
 }
 EXPORT_SYMBOL_GPL(media_request_object_complete);
+
+void media_request_manual_complete(struct media_request *req)
+{
+	unsigned long flags;
+	bool completed = false;
+
+	if (WARN_ON(!req))
+		return;
+	if (WARN_ON(!req->manual_completion))
+		return;
+
+	spin_lock_irqsave(&req->lock, flags);
+	if (WARN_ON(req->state != MEDIA_REQUEST_STATE_QUEUED))
+		goto unlock;
+
+	req->manual_completion = false;
+	/*
+	 * It is expected that all other objects in this request are
+	 * completed when this function is called. WARN if that is
+	 * not the case.
+	 */
+	if (!WARN_ON(req->num_incomplete_objects)) {
+		req->state = MEDIA_REQUEST_STATE_COMPLETE;
+		wake_up_interruptible_all(&req->poll_wait);
+		completed = true;
+	}
+unlock:
+	spin_unlock_irqrestore(&req->lock, flags);
+	if (completed)
+		media_request_put(req);
+}
+EXPORT_SYMBOL_GPL(media_request_manual_complete);
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index 85d2627776b6..9356334956fc 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -67,6 +67,7 @@ source "drivers/media/platform/amlogic/Kconfig"
 source "drivers/media/platform/amphion/Kconfig"
 source "drivers/media/platform/aspeed/Kconfig"
 source "drivers/media/platform/atmel/Kconfig"
+source "drivers/media/platform/bcm2835/Kconfig"
 source "drivers/media/platform/broadcom/Kconfig"
 source "drivers/media/platform/cadence/Kconfig"
 source "drivers/media/platform/chips-media/Kconfig"
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index ace4e34483dd..0f94e5830db2 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -10,6 +10,7 @@ obj-y += amlogic/
 obj-y += amphion/
 obj-y += aspeed/
 obj-y += atmel/
+obj-y += bcm2835/
 obj-y += broadcom/
 obj-y += cadence/
 obj-y += chips-media/
diff --git a/drivers/media/platform/bcm2835/Kconfig b/drivers/media/platform/bcm2835/Kconfig
new file mode 100644
index 000000000000..09d20dd52452
--- /dev/null
+++ b/drivers/media/platform/bcm2835/Kconfig
@@ -0,0 +1,24 @@
+# Broadcom VideoCore4 V4L2 camera support
+
+config VIDEO_BCM2835_UNICAM_LEGACY
+	tristate "Broadcom BCM283x/BCM271x Unicam video capture driver - no MC"
+	depends on VIDEO_DEV
+	depends on ARCH_BCM2835 || COMPILE_TEST
+	select VIDEO_V4L2_SUBDEV_API
+	select MEDIA_CONTROLLER
+	select VIDEOBUF2_DMA_CONTIG
+	select V4L2_FWNODE
+	help
+	  Say Y here to enable support for the BCM283x/BCM271x CSI-2 receiver.
+	  This is a V4L2 driver that controls the CSI-2 receiver directly,
+	  independently from the VC4 firmware.
+	  This is the downstream version of this driver that still supports
+	  being driven from the video node for simple devices. The mainline
+	  driver only supports using Media Controller.
+	  This driver is mutually exclusive with the use of bcm2835-camera. The
+	  firmware will disable all access to the peripheral from within the
+	  firmware if it finds a DT node using it, and bcm2835-camera will
+	  therefore fail to probe.
+
+	  To compile this driver as a module, choose M here. The module will be
+	  called bcm2835-unicam.
diff --git a/drivers/media/platform/bcm2835/Makefile b/drivers/media/platform/bcm2835/Makefile
new file mode 100644
index 000000000000..55a48691f513
--- /dev/null
+++ b/drivers/media/platform/bcm2835/Makefile
@@ -0,0 +1,4 @@
+# Makefile for BCM2835 Unicam driver
+
+bcm2835-unicam-legacy-y := bcm2835-unicam.o
+obj-$(CONFIG_VIDEO_BCM2835_UNICAM_LEGACY) += bcm2835-unicam-legacy.o
diff --git a/drivers/media/platform/bcm2835/bcm2835-unicam.c b/drivers/media/platform/bcm2835/bcm2835-unicam.c
new file mode 100644
index 000000000000..1c4ae29d69a0
--- /dev/null
+++ b/drivers/media/platform/bcm2835/bcm2835-unicam.c
@@ -0,0 +1,3528 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * BCM283x / BCM271x Unicam Capture Driver
+ *
+ * Copyright (C) 2017-2020 - Raspberry Pi (Trading) Ltd.
+ *
+ * Dave Stevenson <dave.stevenson@raspberrypi.com>
+ *
+ * Based on TI am437x driver by
+ *   Benoit Parrot <bparrot@ti.com>
+ *   Lad, Prabhakar <prabhakar.csengg@gmail.com>
+ *
+ * and TI CAL camera interface driver by
+ *    Benoit Parrot <bparrot@ti.com>
+ *
+ *
+ * There are two camera drivers in the kernel for BCM283x - this one
+ * and bcm2835-camera (currently in staging).
+ *
+ * This driver directly controls the Unicam peripheral - there is no
+ * involvement with the VideoCore firmware. Unicam receives CSI-2 or
+ * CCP2 data and writes it into SDRAM.
+ * The only potential processing options are to repack Bayer data into an
+ * alternate format, and applying windowing.
+ * The repacking does not shift the data, so can repack V4L2_PIX_FMT_Sxxxx10P
+ * to V4L2_PIX_FMT_Sxxxx10, or V4L2_PIX_FMT_Sxxxx12P to V4L2_PIX_FMT_Sxxxx12,
+ * but not generically up to V4L2_PIX_FMT_Sxxxx16. The driver will add both
+ * formats where the relevant formats are defined, and will automatically
+ * configure the repacking as required.
+ * Support for windowing may be added later.
+ *
+ * It should be possible to connect this driver to any sensor with a
+ * suitable output interface and V4L2 subdevice driver.
+ *
+ * bcm2835-camera uses the VideoCore firmware to control the sensor,
+ * Unicam, ISP, and all tuner control loops. Fully processed frames are
+ * delivered to the driver by the firmware. It only has sensor drivers
+ * for Omnivision OV5647, and Sony IMX219 sensors.
+ *
+ * The two drivers are mutually exclusive for the same Unicam instance.
+ * The VideoCore firmware checks the device tree configuration during boot.
+ * If it finds device tree nodes called csi0 or csi1 it will block the
+ * firmware from accessing the peripheral, and bcm2835-camera will
+ * not be able to stream data.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/videodev2.h>
+
+#include <media/mipi-csi2.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-dv-timings.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-fwnode.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include <media/v4l2-async.h>
+
+#include "vc4-regs-unicam.h"
+
+#define UNICAM_MODULE_NAME	"unicam"
+#define UNICAM_VERSION		"0.1.0"
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "Debug level 0-3");
+
+static int media_controller;
+module_param(media_controller, int, 0644);
+MODULE_PARM_DESC(media_controller, "Use media controller API");
+
+#define unicam_dbg(level, dev, fmt, arg...)	\
+		v4l2_dbg(level, debug, &(dev)->v4l2_dev, fmt, ##arg)
+#define unicam_info(dev, fmt, arg...)	\
+		v4l2_info(&(dev)->v4l2_dev, fmt, ##arg)
+#define unicam_err(dev, fmt, arg...)	\
+		v4l2_err(&(dev)->v4l2_dev, fmt, ##arg)
+
+/*
+ * Unicam must request a minimum of 250Mhz from the VPU clock.
+ * Otherwise the input FIFOs overrun and cause image corruption.
+ */
+#define MIN_VPU_CLOCK_RATE (250 * 1000 * 1000)
+/*
+ * To protect against a dodgy sensor driver never returning an error from
+ * enum_mbus_code, set a maximum index value to be used.
+ */
+#define MAX_ENUM_MBUS_CODE	128
+
+/*
+ * Stride is a 16 bit register, but also has to be a multiple of 32.
+ */
+#define BPL_ALIGNMENT		32
+#define MAX_BYTESPERLINE	((1 << 16) - BPL_ALIGNMENT)
+/*
+ * Max width is therefore determined by the max stride divided by
+ * the number of bits per pixel. Take 32bpp as a
+ * worst case.
+ * No imposed limit on the height, so adopt a square image for want
+ * of anything better.
+ */
+#define MAX_WIDTH		(MAX_BYTESPERLINE / 4)
+#define MAX_HEIGHT		MAX_WIDTH
+/* Define a nominal minimum image size */
+#define MIN_WIDTH		16
+#define MIN_HEIGHT		16
+/* Default size of the embedded buffer */
+#define UNICAM_EMBEDDED_SIZE	16384
+
+/*
+ * Size of the dummy buffer allocation.
+ *
+ * Due to a HW bug causing buffer overruns in circular buffer mode under certain
+ * (not yet fully known) conditions, the dummy buffer allocation is set to a
+ * a single page size, but the hardware gets programmed with a buffer size of 0.
+ */
+#define DUMMY_BUF_SIZE		(PAGE_SIZE)
+
+enum pad_types {
+	IMAGE_PAD,
+	METADATA_PAD,
+	MAX_NODES
+};
+
+#define MASK_CS_DEFAULT		BIT(V4L2_COLORSPACE_DEFAULT)
+#define MASK_CS_SMPTE170M	BIT(V4L2_COLORSPACE_SMPTE170M)
+#define MASK_CS_SMPTE240M	BIT(V4L2_COLORSPACE_SMPTE240M)
+#define MASK_CS_REC709		BIT(V4L2_COLORSPACE_REC709)
+#define MASK_CS_BT878		BIT(V4L2_COLORSPACE_BT878)
+#define MASK_CS_470_M		BIT(V4L2_COLORSPACE_470_SYSTEM_M)
+#define MASK_CS_470_BG		BIT(V4L2_COLORSPACE_470_SYSTEM_BG)
+#define MASK_CS_JPEG		BIT(V4L2_COLORSPACE_JPEG)
+#define MASK_CS_SRGB		BIT(V4L2_COLORSPACE_SRGB)
+#define MASK_CS_OPRGB		BIT(V4L2_COLORSPACE_OPRGB)
+#define MASK_CS_BT2020		BIT(V4L2_COLORSPACE_BT2020)
+#define MASK_CS_RAW		BIT(V4L2_COLORSPACE_RAW)
+#define MASK_CS_DCI_P3		BIT(V4L2_COLORSPACE_DCI_P3)
+
+#define MAX_COLORSPACE		32
+
+/*
+ * struct unicam_fmt - Unicam media bus format information
+ * @pixelformat: V4L2 pixel format FCC identifier. 0 if n/a.
+ * @repacked_fourcc: V4L2 pixel format FCC identifier if the data is expanded
+ * out to 16bpp. 0 if n/a.
+ * @code: V4L2 media bus format code.
+ * @depth: Bits per pixel as delivered from the source.
+ * @csi_dt: CSI data type.
+ * @valid_colorspaces: Bitmask of valid colorspaces so that the Media Controller
+ *		centric try_fmt can validate the colorspace and pass
+ *		v4l2-compliance.
+ * @check_variants: Flag to denote that there are multiple mediabus formats
+ *		still in the list that could match this V4L2 format.
+ * @mc_skip: Media Controller shouldn't list this format via ENUM_FMT as it is
+ *		a duplicate of an earlier format.
+ * @metadata_fmt: This format only applies to the metadata pad.
+ */
+struct unicam_fmt {
+	u32	fourcc;
+	u32	repacked_fourcc;
+	u32	code;
+	u8	depth;
+	u8	csi_dt;
+	u32	valid_colorspaces;
+	u8	check_variants:1;
+	u8	mc_skip:1;
+	u8	metadata_fmt:1;
+};
+
+static const struct unicam_fmt formats[] = {
+	/* YUV Formats */
+	{
+		.fourcc		= V4L2_PIX_FMT_YUYV,
+		.code		= MEDIA_BUS_FMT_YUYV8_2X8,
+		.depth		= 16,
+		.csi_dt		= MIPI_CSI2_DT_YUV422_8B,
+		.check_variants = 1,
+		.valid_colorspaces = MASK_CS_SMPTE170M | MASK_CS_REC709 |
+				     MASK_CS_JPEG,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_UYVY,
+		.code		= MEDIA_BUS_FMT_UYVY8_2X8,
+		.depth		= 16,
+		.csi_dt		= MIPI_CSI2_DT_YUV422_8B,
+		.check_variants = 1,
+		.valid_colorspaces = MASK_CS_SMPTE170M | MASK_CS_REC709 |
+				     MASK_CS_JPEG,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_YVYU,
+		.code		= MEDIA_BUS_FMT_YVYU8_2X8,
+		.depth		= 16,
+		.csi_dt		= MIPI_CSI2_DT_YUV422_8B,
+		.check_variants = 1,
+		.valid_colorspaces = MASK_CS_SMPTE170M | MASK_CS_REC709 |
+				     MASK_CS_JPEG,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_VYUY,
+		.code		= MEDIA_BUS_FMT_VYUY8_2X8,
+		.depth		= 16,
+		.csi_dt		= MIPI_CSI2_DT_YUV422_8B,
+		.check_variants = 1,
+		.valid_colorspaces = MASK_CS_SMPTE170M | MASK_CS_REC709 |
+				     MASK_CS_JPEG,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_YUYV,
+		.code		= MEDIA_BUS_FMT_YUYV8_1X16,
+		.depth		= 16,
+		.csi_dt		= MIPI_CSI2_DT_YUV422_8B,
+		.mc_skip	= 1,
+		.valid_colorspaces = MASK_CS_SMPTE170M | MASK_CS_REC709 |
+				     MASK_CS_JPEG,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_UYVY,
+		.code		= MEDIA_BUS_FMT_UYVY8_1X16,
+		.depth		= 16,
+		.csi_dt		= MIPI_CSI2_DT_YUV422_8B,
+		.mc_skip	= 1,
+		.valid_colorspaces = MASK_CS_SMPTE170M | MASK_CS_REC709 |
+				     MASK_CS_JPEG,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_YVYU,
+		.code		= MEDIA_BUS_FMT_YVYU8_1X16,
+		.depth		= 16,
+		.csi_dt		= MIPI_CSI2_DT_YUV422_8B,
+		.mc_skip	= 1,
+		.valid_colorspaces = MASK_CS_SMPTE170M | MASK_CS_REC709 |
+				     MASK_CS_JPEG,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_VYUY,
+		.code		= MEDIA_BUS_FMT_VYUY8_1X16,
+		.depth		= 16,
+		.csi_dt		= MIPI_CSI2_DT_YUV422_8B,
+		.mc_skip	= 1,
+		.valid_colorspaces = MASK_CS_SMPTE170M | MASK_CS_REC709 |
+				     MASK_CS_JPEG,
+	}, {
+	/* RGB Formats */
+		.fourcc		= V4L2_PIX_FMT_RGB565, /* gggbbbbb rrrrrggg */
+		.code		= MEDIA_BUS_FMT_RGB565_2X8_LE,
+		.depth		= 16,
+		.csi_dt		= MIPI_CSI2_DT_RGB565,
+		.valid_colorspaces = MASK_CS_SRGB,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_RGB565X, /* rrrrrggg gggbbbbb */
+		.code		= MEDIA_BUS_FMT_RGB565_2X8_BE,
+		.depth		= 16,
+		.csi_dt		= MIPI_CSI2_DT_RGB565,
+		.valid_colorspaces = MASK_CS_SRGB,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_RGB555, /* gggbbbbb arrrrrgg */
+		.code		= MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE,
+		.depth		= 16,
+		.csi_dt		= MIPI_CSI2_DT_RGB555,
+		.valid_colorspaces = MASK_CS_SRGB,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_RGB555X, /* arrrrrgg gggbbbbb */
+		.code		= MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE,
+		.depth		= 16,
+		.csi_dt		= MIPI_CSI2_DT_RGB555,
+		.valid_colorspaces = MASK_CS_SRGB,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_RGB24, /* rgb */
+		.code		= MEDIA_BUS_FMT_RGB888_1X24,
+		.depth		= 24,
+		.csi_dt		= MIPI_CSI2_DT_RGB888,
+		.valid_colorspaces = MASK_CS_SRGB,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_BGR24, /* bgr */
+		.code		= MEDIA_BUS_FMT_BGR888_1X24,
+		.depth		= 24,
+		.csi_dt		= MIPI_CSI2_DT_RGB888,
+		.valid_colorspaces = MASK_CS_SRGB,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_RGB32, /* argb */
+		.code		= MEDIA_BUS_FMT_ARGB8888_1X32,
+		.depth		= 32,
+		.csi_dt		= 0x0,
+		.valid_colorspaces = MASK_CS_SRGB,
+	}, {
+	/* Bayer Formats */
+		.fourcc		= V4L2_PIX_FMT_SBGGR8,
+		.code		= MEDIA_BUS_FMT_SBGGR8_1X8,
+		.depth		= 8,
+		.csi_dt		= MIPI_CSI2_DT_RAW8,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_SGBRG8,
+		.code		= MEDIA_BUS_FMT_SGBRG8_1X8,
+		.depth		= 8,
+		.csi_dt		= MIPI_CSI2_DT_RAW8,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_SGRBG8,
+		.code		= MEDIA_BUS_FMT_SGRBG8_1X8,
+		.depth		= 8,
+		.csi_dt		= MIPI_CSI2_DT_RAW8,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_SRGGB8,
+		.code		= MEDIA_BUS_FMT_SRGGB8_1X8,
+		.depth		= 8,
+		.csi_dt		= MIPI_CSI2_DT_RAW8,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_SBGGR10P,
+		.repacked_fourcc = V4L2_PIX_FMT_SBGGR10,
+		.code		= MEDIA_BUS_FMT_SBGGR10_1X10,
+		.depth		= 10,
+		.csi_dt		= MIPI_CSI2_DT_RAW10,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_SGBRG10P,
+		.repacked_fourcc = V4L2_PIX_FMT_SGBRG10,
+		.code		= MEDIA_BUS_FMT_SGBRG10_1X10,
+		.depth		= 10,
+		.csi_dt		= MIPI_CSI2_DT_RAW10,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_SGRBG10P,
+		.repacked_fourcc = V4L2_PIX_FMT_SGRBG10,
+		.code		= MEDIA_BUS_FMT_SGRBG10_1X10,
+		.depth		= 10,
+		.csi_dt		= MIPI_CSI2_DT_RAW10,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_SRGGB10P,
+		.repacked_fourcc = V4L2_PIX_FMT_SRGGB10,
+		.code		= MEDIA_BUS_FMT_SRGGB10_1X10,
+		.depth		= 10,
+		.csi_dt		= MIPI_CSI2_DT_RAW10,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_SBGGR12P,
+		.repacked_fourcc = V4L2_PIX_FMT_SBGGR12,
+		.code		= MEDIA_BUS_FMT_SBGGR12_1X12,
+		.depth		= 12,
+		.csi_dt		= MIPI_CSI2_DT_RAW12,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_SGBRG12P,
+		.repacked_fourcc = V4L2_PIX_FMT_SGBRG12,
+		.code		= MEDIA_BUS_FMT_SGBRG12_1X12,
+		.depth		= 12,
+		.csi_dt		= MIPI_CSI2_DT_RAW12,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_SGRBG12P,
+		.repacked_fourcc = V4L2_PIX_FMT_SGRBG12,
+		.code		= MEDIA_BUS_FMT_SGRBG12_1X12,
+		.depth		= 12,
+		.csi_dt		= MIPI_CSI2_DT_RAW12,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_SRGGB12P,
+		.repacked_fourcc = V4L2_PIX_FMT_SRGGB12,
+		.code		= MEDIA_BUS_FMT_SRGGB12_1X12,
+		.depth		= 12,
+		.csi_dt		= MIPI_CSI2_DT_RAW12,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_SBGGR14P,
+		.repacked_fourcc = V4L2_PIX_FMT_SBGGR14,
+		.code		= MEDIA_BUS_FMT_SBGGR14_1X14,
+		.depth		= 14,
+		.csi_dt		= MIPI_CSI2_DT_RAW14,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_SGBRG14P,
+		.repacked_fourcc = V4L2_PIX_FMT_SGBRG14,
+		.code		= MEDIA_BUS_FMT_SGBRG14_1X14,
+		.depth		= 14,
+		.csi_dt		= MIPI_CSI2_DT_RAW14,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_SGRBG14P,
+		.repacked_fourcc = V4L2_PIX_FMT_SGRBG14,
+		.code		= MEDIA_BUS_FMT_SGRBG14_1X14,
+		.depth		= 14,
+		.csi_dt		= MIPI_CSI2_DT_RAW14,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_SRGGB14P,
+		.repacked_fourcc = V4L2_PIX_FMT_SRGGB14,
+		.code		= MEDIA_BUS_FMT_SRGGB14_1X14,
+		.depth		= 14,
+		.csi_dt		= MIPI_CSI2_DT_RAW14,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_SBGGR16,
+		.code		= MEDIA_BUS_FMT_SBGGR16_1X16,
+		.depth		= 16,
+		.csi_dt		= MIPI_CSI2_DT_RAW16,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_SGBRG16,
+		.code		= MEDIA_BUS_FMT_SGBRG16_1X16,
+		.depth		= 16,
+		.csi_dt		= MIPI_CSI2_DT_RAW16,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_SGRBG16,
+		.code		= MEDIA_BUS_FMT_SGRBG16_1X16,
+		.depth		= 16,
+		.csi_dt		= MIPI_CSI2_DT_RAW16,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_SRGGB16,
+		.code		= MEDIA_BUS_FMT_SRGGB16_1X16,
+		.depth		= 16,
+		.csi_dt		= MIPI_CSI2_DT_RAW16,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+
+	/* Greyscale formats */
+		.fourcc		= V4L2_PIX_FMT_GREY,
+		.code		= MEDIA_BUS_FMT_Y8_1X8,
+		.depth		= 8,
+		.csi_dt		= MIPI_CSI2_DT_RAW8,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_Y10P,
+		.repacked_fourcc = V4L2_PIX_FMT_Y10,
+		.code		= MEDIA_BUS_FMT_Y10_1X10,
+		.depth		= 10,
+		.csi_dt		= MIPI_CSI2_DT_RAW10,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_Y12P,
+		.repacked_fourcc = V4L2_PIX_FMT_Y12,
+		.code		= MEDIA_BUS_FMT_Y12_1X12,
+		.depth		= 12,
+		.csi_dt		= MIPI_CSI2_DT_RAW12,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_Y14P,
+		.repacked_fourcc = V4L2_PIX_FMT_Y14,
+		.code		= MEDIA_BUS_FMT_Y14_1X14,
+		.depth		= 14,
+		.csi_dt		= MIPI_CSI2_DT_RAW14,
+		.valid_colorspaces = MASK_CS_RAW,
+	}, {
+		.fourcc		= V4L2_PIX_FMT_Y16,
+		.code		= MEDIA_BUS_FMT_Y16_1X16,
+		.depth		= 16,
+		.csi_dt		= MIPI_CSI2_DT_RAW16,
+		.valid_colorspaces = MASK_CS_RAW,
+	},
+	/* Embedded data format */
+	{
+		.fourcc		= V4L2_META_FMT_SENSOR_DATA,
+		.code		= MEDIA_BUS_FMT_SENSOR_DATA,
+		.depth		= 8,
+		.metadata_fmt	= 1,
+	}
+};
+
+struct unicam_buffer {
+	struct vb2_v4l2_buffer vb;
+	struct list_head list;
+};
+
+static inline struct unicam_buffer *to_unicam_buffer(struct vb2_buffer *vb)
+{
+	return container_of(vb, struct unicam_buffer, vb.vb2_buf);
+}
+
+struct unicam_node {
+	bool registered;
+	int open;
+	bool streaming;
+	unsigned int pad_id;
+	/* Source pad id on the sensor for this node */
+	unsigned int src_pad_id;
+	/* Pointer pointing to current v4l2_buffer */
+	struct unicam_buffer *cur_frm;
+	/* Pointer pointing to next v4l2_buffer */
+	struct unicam_buffer *next_frm;
+	/* video capture */
+	const struct unicam_fmt *fmt;
+	/* Used to store current pixel format */
+	struct v4l2_format v_fmt;
+	/* Used to store current mbus frame format */
+	struct v4l2_mbus_framefmt m_fmt;
+	/* Buffer queue used in video-buf */
+	struct vb2_queue buffer_queue;
+	/* Queue of filled frames */
+	struct list_head dma_queue;
+	/* IRQ lock for DMA queue */
+	spinlock_t dma_queue_lock;
+	/* lock used to access this structure */
+	struct mutex lock;
+	/* Identifies video device for this channel */
+	struct video_device video_dev;
+	/* Pointer to the parent handle */
+	struct unicam_device *dev;
+	struct media_pad pad;
+	unsigned int embedded_lines;
+	struct media_pipeline pipe;
+	/*
+	 * Dummy buffer intended to be used by unicam
+	 * if we have no other queued buffers to swap to.
+	 */
+	void *dummy_buf_cpu_addr;
+	dma_addr_t dummy_buf_dma_addr;
+};
+
+struct unicam_device {
+	struct kref kref;
+
+	/* V4l2 specific parameters */
+	struct v4l2_async_connection *asd;
+
+	/* peripheral base address */
+	void __iomem *base;
+	/* clock gating base address */
+	void __iomem *clk_gate_base;
+	/* lp clock handle */
+	struct clk *clock;
+	/* vpu clock handle */
+	struct clk *vpu_clock;
+	/* clock status for error handling */
+	bool clocks_enabled;
+	/* V4l2 device */
+	struct v4l2_device v4l2_dev;
+	struct media_device mdev;
+
+	struct gpio_desc *sync_gpio;
+
+	/* parent device */
+	struct platform_device *pdev;
+	/* subdevice async Notifier */
+	struct v4l2_async_notifier notifier;
+	unsigned int sequence;
+	bool frame_started;
+
+	/* ptr to  sub device */
+	struct v4l2_subdev *sensor;
+	/* Pad config for the sensor */
+	struct v4l2_subdev_state *sensor_state;
+
+	enum v4l2_mbus_type bus_type;
+	/*
+	 * Stores bus.mipi_csi2.flags for CSI2 sensors, or
+	 * bus.mipi_csi1.strobe for CCP2.
+	 */
+	unsigned int bus_flags;
+	unsigned int max_data_lanes;
+	unsigned int active_data_lanes;
+	bool sensor_embedded_data;
+
+	struct unicam_node node[MAX_NODES];
+	struct v4l2_ctrl_handler ctrl_handler;
+
+	bool mc_api;
+};
+
+static inline struct unicam_device *
+to_unicam_device(struct v4l2_device *v4l2_dev)
+{
+	return container_of(v4l2_dev, struct unicam_device, v4l2_dev);
+}
+
+/* Hardware access */
+static inline void clk_write(struct unicam_device *dev, u32 val)
+{
+	writel(val | 0x5a000000, dev->clk_gate_base);
+}
+
+static inline u32 reg_read(struct unicam_device *dev, u32 offset)
+{
+	return readl(dev->base + offset);
+}
+
+static inline void reg_write(struct unicam_device *dev, u32 offset, u32 val)
+{
+	writel(val, dev->base + offset);
+}
+
+static inline int get_field(u32 value, u32 mask)
+{
+	return (value & mask) >> __ffs(mask);
+}
+
+static inline void set_field(u32 *valp, u32 field, u32 mask)
+{
+	u32 val = *valp;
+
+	val &= ~mask;
+	val |= (field << __ffs(mask)) & mask;
+	*valp = val;
+}
+
+static inline u32 reg_read_field(struct unicam_device *dev, u32 offset,
+				 u32 mask)
+{
+	return get_field(reg_read(dev, offset), mask);
+}
+
+static inline void reg_write_field(struct unicam_device *dev, u32 offset,
+				   u32 field, u32 mask)
+{
+	u32 val = reg_read(dev, offset);
+
+	set_field(&val, field, mask);
+	reg_write(dev, offset, val);
+}
+
+/* Power management functions */
+static inline int unicam_runtime_get(struct unicam_device *dev)
+{
+	return pm_runtime_get_sync(&dev->pdev->dev);
+}
+
+static inline void unicam_runtime_put(struct unicam_device *dev)
+{
+	pm_runtime_put_sync(&dev->pdev->dev);
+}
+
+/* Format setup functions */
+static const struct unicam_fmt *find_format_by_code(u32 code)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(formats); i++) {
+		if (formats[i].code == code)
+			return &formats[i];
+	}
+
+	return NULL;
+}
+
+static int check_mbus_format(struct unicam_device *dev,
+			     const struct unicam_fmt *format)
+{
+	unsigned int i;
+	int ret = 0;
+
+	for (i = 0; !ret && i < MAX_ENUM_MBUS_CODE; i++) {
+		struct v4l2_subdev_mbus_code_enum mbus_code = {
+			.index = i,
+			.pad = IMAGE_PAD,
+			.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+		};
+
+		ret = v4l2_subdev_call(dev->sensor, pad, enum_mbus_code,
+				       NULL, &mbus_code);
+
+		if (!ret && mbus_code.code == format->code)
+			return 1;
+	}
+
+	return 0;
+}
+
+static const struct unicam_fmt *find_format_by_pix(struct unicam_device *dev,
+						   u32 pixelformat)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(formats); i++) {
+		if (formats[i].fourcc == pixelformat ||
+		    formats[i].repacked_fourcc == pixelformat) {
+			if (formats[i].check_variants &&
+			    !check_mbus_format(dev, &formats[i]))
+				continue;
+			return &formats[i];
+		}
+	}
+
+	return NULL;
+}
+
+static unsigned int bytes_per_line(u32 width, const struct unicam_fmt *fmt,
+				   u32 v4l2_fourcc)
+{
+	if (v4l2_fourcc == fmt->repacked_fourcc)
+		/* Repacking always goes to 16bpp */
+		return ALIGN(width << 1, BPL_ALIGNMENT);
+	else
+		return ALIGN((width * fmt->depth) >> 3, BPL_ALIGNMENT);
+}
+
+static int __subdev_get_format(struct unicam_device *dev,
+			       struct v4l2_mbus_framefmt *fmt, int pad_id)
+{
+	struct v4l2_subdev_format sd_fmt = {
+		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+		.pad = dev->node[pad_id].src_pad_id,
+	};
+	int ret;
+
+	ret = v4l2_subdev_call(dev->sensor, pad, get_fmt, dev->sensor_state,
+			       &sd_fmt);
+	if (ret < 0)
+		return ret;
+
+	*fmt = sd_fmt.format;
+
+	unicam_dbg(1, dev, "%s %dx%d code:%04x\n", __func__,
+		   fmt->width, fmt->height, fmt->code);
+
+	return 0;
+}
+
+static int __subdev_set_format(struct unicam_device *dev,
+			       struct v4l2_mbus_framefmt *fmt, int pad_id)
+{
+	struct v4l2_subdev_format sd_fmt = {
+		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+		.pad = dev->node[pad_id].src_pad_id,
+	};
+	int ret;
+
+	sd_fmt.format = *fmt;
+
+	ret = v4l2_subdev_call(dev->sensor, pad, set_fmt, dev->sensor_state,
+			       &sd_fmt);
+	if (ret < 0)
+		return ret;
+
+	*fmt = sd_fmt.format;
+
+	if (pad_id == IMAGE_PAD)
+		unicam_dbg(1, dev, "%s %dx%d code:%04x\n", __func__, fmt->width,
+			   fmt->height, fmt->code);
+	else
+		unicam_dbg(1, dev, "%s Embedded data code:%04x\n", __func__,
+			   sd_fmt.format.code);
+
+	return 0;
+}
+
+static int unicam_calc_format_size_bpl(struct unicam_device *dev,
+				       const struct unicam_fmt *fmt,
+				       struct v4l2_format *f)
+{
+	unsigned int min_bytesperline;
+
+	v4l_bound_align_image(&f->fmt.pix.width, MIN_WIDTH, MAX_WIDTH, 2,
+			      &f->fmt.pix.height, MIN_HEIGHT, MAX_HEIGHT, 0,
+			      0);
+
+	min_bytesperline = bytes_per_line(f->fmt.pix.width, fmt,
+					  f->fmt.pix.pixelformat);
+
+	if (f->fmt.pix.bytesperline > min_bytesperline &&
+	    f->fmt.pix.bytesperline <= MAX_BYTESPERLINE)
+		f->fmt.pix.bytesperline = ALIGN(f->fmt.pix.bytesperline,
+						BPL_ALIGNMENT);
+	else
+		f->fmt.pix.bytesperline = min_bytesperline;
+
+	f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
+
+	unicam_dbg(3, dev, "%s: fourcc: %08X size: %dx%d bpl:%d img_size:%d\n",
+		   __func__,
+		   f->fmt.pix.pixelformat,
+		   f->fmt.pix.width, f->fmt.pix.height,
+		   f->fmt.pix.bytesperline, f->fmt.pix.sizeimage);
+
+	return 0;
+}
+
+static int unicam_reset_format(struct unicam_node *node)
+{
+	struct unicam_device *dev = node->dev;
+	struct v4l2_mbus_framefmt mbus_fmt;
+	int ret;
+
+	if (dev->sensor_embedded_data || node->pad_id != METADATA_PAD) {
+		ret = __subdev_get_format(dev, &mbus_fmt, node->pad_id);
+		if (ret) {
+			unicam_err(dev, "Failed to get_format - ret %d\n", ret);
+			return ret;
+		}
+
+		if (mbus_fmt.code != node->fmt->code) {
+			unicam_err(dev, "code mismatch - fmt->code %08x, mbus_fmt.code %08x\n",
+				   node->fmt->code, mbus_fmt.code);
+			return ret;
+		}
+	}
+
+	if (node->pad_id == IMAGE_PAD) {
+		v4l2_fill_pix_format(&node->v_fmt.fmt.pix, &mbus_fmt);
+		node->v_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+		unicam_calc_format_size_bpl(dev, node->fmt, &node->v_fmt);
+	} else {
+		node->v_fmt.type = V4L2_BUF_TYPE_META_CAPTURE;
+		node->v_fmt.fmt.meta.dataformat = V4L2_META_FMT_SENSOR_DATA;
+		if (dev->sensor_embedded_data) {
+			node->v_fmt.fmt.meta.buffersize =
+					mbus_fmt.width * mbus_fmt.height;
+			node->embedded_lines = mbus_fmt.height;
+		} else {
+			node->v_fmt.fmt.meta.buffersize = UNICAM_EMBEDDED_SIZE;
+			node->embedded_lines = 1;
+		}
+	}
+
+	node->m_fmt = mbus_fmt;
+	return 0;
+}
+
+static void unicam_wr_dma_addr(struct unicam_device *dev, dma_addr_t dmaaddr,
+			       unsigned int buffer_size, int pad_id)
+{
+	dma_addr_t endaddr = dmaaddr + buffer_size;
+
+	if (pad_id == IMAGE_PAD) {
+		reg_write(dev, UNICAM_IBSA0, dmaaddr);
+		reg_write(dev, UNICAM_IBEA0, endaddr);
+	} else {
+		reg_write(dev, UNICAM_DBSA0, dmaaddr);
+		reg_write(dev, UNICAM_DBEA0, endaddr);
+	}
+}
+
+static unsigned int unicam_get_lines_done(struct unicam_device *dev)
+{
+	dma_addr_t start_addr, cur_addr;
+	unsigned int stride = dev->node[IMAGE_PAD].v_fmt.fmt.pix.bytesperline;
+	struct unicam_buffer *frm = dev->node[IMAGE_PAD].cur_frm;
+
+	if (!frm)
+		return 0;
+
+	start_addr = vb2_dma_contig_plane_dma_addr(&frm->vb.vb2_buf, 0);
+	cur_addr = reg_read(dev, UNICAM_IBWP);
+	return (unsigned int)(cur_addr - start_addr) / stride;
+}
+
+static void unicam_schedule_next_buffer(struct unicam_node *node)
+{
+	struct unicam_device *dev = node->dev;
+	struct unicam_buffer *buf;
+	unsigned int size;
+	dma_addr_t addr;
+
+	buf = list_first_entry(&node->dma_queue, struct unicam_buffer, list);
+	node->next_frm = buf;
+	list_del(&buf->list);
+
+	addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
+	size = (node->pad_id == IMAGE_PAD) ?
+			node->v_fmt.fmt.pix.sizeimage :
+			node->v_fmt.fmt.meta.buffersize;
+
+	unicam_wr_dma_addr(dev, addr, size, node->pad_id);
+}
+
+static void unicam_schedule_dummy_buffer(struct unicam_node *node)
+{
+	struct unicam_device *dev = node->dev;
+
+	unicam_dbg(3, dev, "Scheduling dummy buffer for node %d\n",
+		   node->pad_id);
+
+	unicam_wr_dma_addr(dev, node->dummy_buf_dma_addr, 0, node->pad_id);
+	node->next_frm = NULL;
+}
+
+static void unicam_process_buffer_complete(struct unicam_node *node,
+					   unsigned int sequence)
+{
+	node->cur_frm->vb.field = node->m_fmt.field;
+	node->cur_frm->vb.sequence = sequence;
+
+	vb2_buffer_done(&node->cur_frm->vb.vb2_buf, VB2_BUF_STATE_DONE);
+}
+
+static void unicam_queue_event_sof(struct unicam_device *unicam)
+{
+	struct v4l2_event event = {
+		.type = V4L2_EVENT_FRAME_SYNC,
+		.u.frame_sync.frame_sequence = unicam->sequence,
+	};
+
+	v4l2_event_queue(&unicam->node[IMAGE_PAD].video_dev, &event);
+}
+
+/*
+ * unicam_isr : ISR handler for unicam capture
+ * @irq: irq number
+ * @dev_id: dev_id ptr
+ *
+ * It changes status of the captured buffer, takes next buffer from the queue
+ * and sets its address in unicam registers
+ */
+static irqreturn_t unicam_isr(int irq, void *dev)
+{
+	struct unicam_device *unicam = dev;
+	unsigned int lines_done = unicam_get_lines_done(dev);
+	unsigned int sequence = unicam->sequence;
+	unsigned int i;
+	u32 ista, sta;
+	bool fe;
+	u64 ts;
+
+	sta = reg_read(unicam, UNICAM_STA);
+	/* Write value back to clear the interrupts */
+	reg_write(unicam, UNICAM_STA, sta);
+
+	ista = reg_read(unicam, UNICAM_ISTA);
+	/* Write value back to clear the interrupts */
+	reg_write(unicam, UNICAM_ISTA, ista);
+
+	unicam_dbg(3, unicam, "ISR: ISTA: 0x%X, STA: 0x%X, sequence %d, lines done %d",
+		   ista, sta, sequence, lines_done);
+
+	if (!(sta & (UNICAM_IS | UNICAM_PI0)))
+		return IRQ_HANDLED;
+
+	/*
+	 * Look for either the Frame End interrupt or the Packet Capture status
+	 * to signal a frame end.
+	 */
+	fe = (ista & UNICAM_FEI || sta & UNICAM_PI0);
+
+	/*
+	 * We must run the frame end handler first. If we have a valid next_frm
+	 * and we get a simultaneout FE + FS interrupt, running the FS handler
+	 * first would null out the next_frm ptr and we would have lost the
+	 * buffer forever.
+	 */
+	if (fe) {
+		bool inc_seq = unicam->frame_started;
+
+		if (unicam->sync_gpio)
+			gpiod_set_value(unicam->sync_gpio, 0);
+		/*
+		 * Ensure we have swapped buffers already as we can't
+		 * stop the peripheral. If no buffer is available, use a
+		 * dummy buffer to dump out frames until we get a new buffer
+		 * to use.
+		 */
+		for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
+			struct unicam_node *node = &unicam->node[i];
+
+			if (!node->streaming)
+				continue;
+
+			/*
+			 * If cur_frm == next_frm, it means we have not had
+			 * a chance to swap buffers, likely due to having
+			 * multiple interrupts occurring simultaneously (like FE
+			 * + FS + LS). In this case, we cannot signal the buffer
+			 * as complete, as the HW will reuse that buffer.
+			 */
+			if (node->cur_frm && node->cur_frm != node->next_frm) {
+				/*
+				 * This condition checks if FE + FS for the same
+				 * frame has occurred. In such cases, we cannot
+				 * return out the frame, as no buffer handling
+				 * or timestamping has yet been done as part of
+				 * the FS handler.
+				 */
+				if (!node->cur_frm->vb.vb2_buf.timestamp) {
+					unicam_dbg(2, unicam, "ISR: FE without FS, dropping frame\n");
+					continue;
+				}
+
+				unicam_process_buffer_complete(node, sequence);
+				node->cur_frm = node->next_frm;
+				node->next_frm = NULL;
+				inc_seq = true;
+			} else {
+				node->cur_frm = node->next_frm;
+			}
+		}
+
+		/*
+		 * Increment the sequence number conditionally on either a FS
+		 * having already occurred, or in the FE + FS condition as
+		 * caught in the FE handler above. This ensures the sequence
+		 * number corresponds to the frames generated by the sensor, not
+		 * the frames dequeued to userland.
+		 */
+		if (inc_seq) {
+			unicam->sequence++;
+			unicam->frame_started = false;
+		}
+	}
+
+	if (ista & UNICAM_FSI) {
+		/*
+		 * Timestamp is to be when the first data byte was captured,
+		 * aka frame start.
+		 */
+		ts = ktime_get_ns();
+
+		if (unicam->sync_gpio)
+			gpiod_set_value(unicam->sync_gpio, 1);
+
+		for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
+			if (!unicam->node[i].streaming)
+				continue;
+
+			if (unicam->node[i].cur_frm)
+				unicam->node[i].cur_frm->vb.vb2_buf.timestamp =
+								ts;
+			else
+				unicam_dbg(2, unicam, "ISR: [%d] Dropping frame, buffer not available at FS\n",
+					   i);
+			/*
+			 * Set the next frame output to go to a dummy frame
+			 * if no buffer currently queued.
+			 */
+			if (!unicam->node[i].next_frm ||
+			    unicam->node[i].next_frm == unicam->node[i].cur_frm) {
+				unicam_schedule_dummy_buffer(&unicam->node[i]);
+			} else if (unicam->node[i].cur_frm) {
+				/*
+				 * Repeated FS without FE. Hardware will have
+				 * swapped buffers, but the cur_frm doesn't
+				 * contain valid data. Return cur_frm to the
+				 * queue.
+				 */
+				spin_lock(&unicam->node[i].dma_queue_lock);
+				list_add_tail(&unicam->node[i].cur_frm->list,
+					      &unicam->node[i].dma_queue);
+				spin_unlock(&unicam->node[i].dma_queue_lock);
+				unicam->node[i].cur_frm = unicam->node[i].next_frm;
+				unicam->node[i].next_frm = NULL;
+			}
+		}
+
+		unicam_queue_event_sof(unicam);
+		unicam->frame_started = true;
+	}
+
+	/*
+	 * Cannot swap buffer at frame end, there may be a race condition
+	 * where the HW does not actually swap it if the new frame has
+	 * already started.
+	 */
+	if (ista & (UNICAM_FSI | UNICAM_LCI) && !fe) {
+		for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
+			if (!unicam->node[i].streaming)
+				continue;
+
+			spin_lock(&unicam->node[i].dma_queue_lock);
+			if (!list_empty(&unicam->node[i].dma_queue) &&
+			    !unicam->node[i].next_frm)
+				unicam_schedule_next_buffer(&unicam->node[i]);
+			spin_unlock(&unicam->node[i].dma_queue_lock);
+		}
+	}
+
+	return IRQ_HANDLED;
+}
+
+/* V4L2 Common IOCTLs */
+static int unicam_querycap(struct file *file, void *priv,
+			   struct v4l2_capability *cap)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+
+	strscpy(cap->driver, UNICAM_MODULE_NAME, sizeof(cap->driver));
+	strscpy(cap->card, UNICAM_MODULE_NAME, sizeof(cap->card));
+
+	snprintf(cap->bus_info, sizeof(cap->bus_info),
+		 "platform:%s", dev_name(&dev->pdev->dev));
+
+	cap->capabilities |= V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_META_CAPTURE;
+
+	return 0;
+}
+
+static int unicam_log_status(struct file *file, void *fh)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+	u32 reg;
+
+	/* status for sub devices */
+	v4l2_device_call_all(&dev->v4l2_dev, 0, core, log_status);
+
+	unicam_info(dev, "-----Receiver status-----\n");
+	unicam_info(dev, "V4L2 width/height:   %ux%u\n",
+		    node->v_fmt.fmt.pix.width, node->v_fmt.fmt.pix.height);
+	unicam_info(dev, "Mediabus format:     %08x\n", node->fmt->code);
+	unicam_info(dev, "V4L2 format:         %08x\n",
+		    node->v_fmt.fmt.pix.pixelformat);
+	reg = reg_read(dev, UNICAM_IPIPE);
+	unicam_info(dev, "Unpacking/packing:   %u / %u\n",
+		    get_field(reg, UNICAM_PUM_MASK),
+		    get_field(reg, UNICAM_PPM_MASK));
+	unicam_info(dev, "----Live data----\n");
+	unicam_info(dev, "Programmed stride:   %4u\n",
+		    reg_read(dev, UNICAM_IBLS));
+	unicam_info(dev, "Detected resolution: %ux%u\n",
+		    reg_read(dev, UNICAM_IHSTA),
+		    reg_read(dev, UNICAM_IVSTA));
+	unicam_info(dev, "Write pointer:       %08x\n",
+		    reg_read(dev, UNICAM_IBWP));
+
+	return 0;
+}
+
+/* V4L2 Video Centric IOCTLs */
+static int unicam_enum_fmt_vid_cap(struct file *file, void  *priv,
+				   struct v4l2_fmtdesc *f)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+	unsigned int index = 0;
+	unsigned int i;
+	int ret = 0;
+
+	if (node->pad_id != IMAGE_PAD)
+		return -EINVAL;
+
+	for (i = 0; !ret && i < MAX_ENUM_MBUS_CODE; i++) {
+		struct v4l2_subdev_mbus_code_enum mbus_code = {
+			.index = i,
+			.pad = IMAGE_PAD,
+			.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+		};
+		const struct unicam_fmt *fmt;
+
+		ret = v4l2_subdev_call(dev->sensor, pad, enum_mbus_code,
+				       NULL, &mbus_code);
+		if (ret < 0) {
+			unicam_dbg(2, dev,
+				   "subdev->enum_mbus_code idx %d returned %d - index invalid\n",
+				   i, ret);
+			return -EINVAL;
+		}
+
+		fmt = find_format_by_code(mbus_code.code);
+		if (fmt) {
+			if (fmt->fourcc) {
+				if (index == f->index) {
+					f->pixelformat = fmt->fourcc;
+					break;
+				}
+				index++;
+			}
+			if (fmt->repacked_fourcc) {
+				if (index == f->index) {
+					f->pixelformat = fmt->repacked_fourcc;
+					break;
+				}
+				index++;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int unicam_g_fmt_vid_cap(struct file *file, void *priv,
+				struct v4l2_format *f)
+{
+	struct v4l2_mbus_framefmt mbus_fmt = {0};
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+	const struct unicam_fmt *fmt = NULL;
+	int ret;
+
+	if (node->pad_id != IMAGE_PAD)
+		return -EINVAL;
+
+	/*
+	 * If a flip has occurred in the sensor, the fmt code might have
+	 * changed. So we will need to re-fetch the format from the subdevice.
+	 */
+	ret = __subdev_get_format(dev, &mbus_fmt, node->pad_id);
+	if (ret)
+		return -EINVAL;
+
+	/* Find the V4L2 format from mbus code. We must match a known format. */
+	fmt = find_format_by_code(mbus_fmt.code);
+	if (!fmt)
+		return -EINVAL;
+
+	if (node->fmt != fmt) {
+		/*
+		 * The sensor format has changed so the pixelformat needs to
+		 * be updated. Try and retain the packed/unpacked choice if
+		 * at all possible.
+		 */
+		if (node->fmt->repacked_fourcc ==
+						node->v_fmt.fmt.pix.pixelformat)
+			/* Using the repacked format */
+			node->v_fmt.fmt.pix.pixelformat = fmt->repacked_fourcc;
+		else
+			/* Using the native format */
+			node->v_fmt.fmt.pix.pixelformat = fmt->fourcc;
+
+		node->fmt = fmt;
+	}
+
+	*f = node->v_fmt;
+
+	return 0;
+}
+
+static const struct unicam_fmt *
+get_first_supported_format(struct unicam_device *dev)
+{
+	struct v4l2_subdev_mbus_code_enum mbus_code;
+	const struct unicam_fmt *fmt = NULL;
+	unsigned int i;
+	int ret = 0;
+
+	for (i = 0; ret != -EINVAL && ret != -ENOIOCTLCMD; ++i) {
+		memset(&mbus_code, 0, sizeof(mbus_code));
+		mbus_code.index = i;
+		mbus_code.pad = IMAGE_PAD;
+		mbus_code.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+
+		ret = v4l2_subdev_call(dev->sensor, pad, enum_mbus_code, NULL,
+				       &mbus_code);
+		if (ret < 0) {
+			unicam_dbg(2, dev,
+				   "subdev->enum_mbus_code idx %u returned %d - continue\n",
+				   i, ret);
+			continue;
+		}
+
+		unicam_dbg(2, dev, "subdev %s: code: 0x%08x idx: %u\n",
+			   dev->sensor->name, mbus_code.code, i);
+
+		fmt = find_format_by_code(mbus_code.code);
+		unicam_dbg(2, dev, "fmt 0x%08x returned as %p, V4L2 FOURCC 0x%08x, csi_dt 0x%02x\n",
+			   mbus_code.code, fmt, fmt ? fmt->fourcc : 0,
+			   fmt ? fmt->csi_dt : 0);
+		if (fmt)
+			return fmt;
+	}
+
+	return NULL;
+}
+
+static int unicam_try_fmt_vid_cap(struct file *file, void *priv,
+				  struct v4l2_format *f)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+	struct v4l2_subdev_format sd_fmt = {
+		.which = V4L2_SUBDEV_FORMAT_TRY,
+		.pad = IMAGE_PAD
+	};
+	struct v4l2_mbus_framefmt *mbus_fmt = &sd_fmt.format;
+	const struct unicam_fmt *fmt;
+	int ret;
+
+	if (node->pad_id != IMAGE_PAD)
+		return -EINVAL;
+
+	fmt = find_format_by_pix(dev, f->fmt.pix.pixelformat);
+	if (!fmt) {
+		/*
+		 * Pixel format not supported by unicam. Choose the first
+		 * supported format, and let the sensor choose something else.
+		 */
+		unicam_dbg(3, dev, "Fourcc format (0x%08x) not found. Use first format.\n",
+			   f->fmt.pix.pixelformat);
+
+		fmt = &formats[0];
+		f->fmt.pix.pixelformat = fmt->fourcc;
+	}
+
+	v4l2_fill_mbus_format(mbus_fmt, &f->fmt.pix, fmt->code);
+	/*
+	 * No support for receiving interlaced video, so never
+	 * request it from the sensor subdev.
+	 */
+	mbus_fmt->field = V4L2_FIELD_NONE;
+
+	ret = v4l2_subdev_call(dev->sensor, pad, set_fmt, dev->sensor_state,
+			       &sd_fmt);
+	if (ret && ret != -ENOIOCTLCMD && ret != -ENODEV)
+		return ret;
+
+	if (mbus_fmt->field != V4L2_FIELD_NONE)
+		unicam_info(dev, "Sensor trying to send interlaced video - results may be unpredictable\n");
+
+	v4l2_fill_pix_format(&f->fmt.pix, &sd_fmt.format);
+	if (mbus_fmt->code != fmt->code) {
+		/* Sensor has returned an alternate format */
+		fmt = find_format_by_code(mbus_fmt->code);
+		if (!fmt) {
+			/*
+			 * The alternate format is one unicam can't support.
+			 * Find the first format that is supported by both, and
+			 * then set that.
+			 */
+			fmt = get_first_supported_format(dev);
+			mbus_fmt->code = fmt->code;
+
+			ret = v4l2_subdev_call(dev->sensor, pad, set_fmt,
+					       dev->sensor_state, &sd_fmt);
+			if (ret && ret != -ENOIOCTLCMD && ret != -ENODEV)
+				return ret;
+
+			if (mbus_fmt->field != V4L2_FIELD_NONE)
+				unicam_info(dev, "Sensor trying to send interlaced video - results may be unpredictable\n");
+
+			v4l2_fill_pix_format(&f->fmt.pix, &sd_fmt.format);
+
+			if (mbus_fmt->code != fmt->code) {
+				/*
+				 * We've set a format that the sensor reports
+				 * as being supported, but it refuses to set it.
+				 * Not much else we can do.
+				 * Assume that the sensor driver may accept the
+				 * format when it is set (rather than tried).
+				 */
+				unicam_err(dev, "Sensor won't accept default format, and Unicam can't support sensor default\n");
+			}
+		}
+
+		if (fmt->fourcc)
+			f->fmt.pix.pixelformat = fmt->fourcc;
+		else
+			f->fmt.pix.pixelformat = fmt->repacked_fourcc;
+	}
+
+	return unicam_calc_format_size_bpl(dev, fmt, f);
+}
+
+static int unicam_s_fmt_vid_cap(struct file *file, void *priv,
+				struct v4l2_format *f)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+	struct vb2_queue *q = &node->buffer_queue;
+	struct v4l2_mbus_framefmt mbus_fmt = {0};
+	const struct unicam_fmt *fmt;
+	int ret;
+
+	if (vb2_is_busy(q))
+		return -EBUSY;
+
+	ret = unicam_try_fmt_vid_cap(file, priv, f);
+	if (ret < 0)
+		return ret;
+
+	fmt = find_format_by_pix(dev, f->fmt.pix.pixelformat);
+	if (!fmt) {
+		/*
+		 * Unknown pixel format - adopt a default.
+		 * This shouldn't happen as try_fmt should have resolved any
+		 * issues first.
+		 */
+		fmt = get_first_supported_format(dev);
+		if (!fmt)
+			/*
+			 * It shouldn't be possible to get here with no
+			 * supported formats
+			 */
+			return -EINVAL;
+		f->fmt.pix.pixelformat = fmt->fourcc;
+		return -EINVAL;
+	}
+
+	v4l2_fill_mbus_format(&mbus_fmt, &f->fmt.pix, fmt->code);
+
+	ret = __subdev_set_format(dev, &mbus_fmt, node->pad_id);
+	if (ret) {
+		unicam_dbg(3, dev, "%s __subdev_set_format failed %d\n",
+			   __func__, ret);
+		return ret;
+	}
+
+	/* Just double check nothing has gone wrong */
+	if (mbus_fmt.code != fmt->code) {
+		unicam_dbg(3, dev,
+			   "%s subdev changed format on us, this should not happen\n",
+			   __func__);
+		return -EINVAL;
+	}
+
+	node->fmt = fmt;
+	node->v_fmt.fmt.pix.pixelformat = f->fmt.pix.pixelformat;
+	node->v_fmt.fmt.pix.bytesperline = f->fmt.pix.bytesperline;
+	unicam_reset_format(node);
+
+	unicam_dbg(3, dev,
+		   "%s %dx%d, mbus_fmt 0x%08X, V4L2 pix 0x%08X.\n",
+		   __func__, node->v_fmt.fmt.pix.width,
+		   node->v_fmt.fmt.pix.height, mbus_fmt.code,
+		   node->v_fmt.fmt.pix.pixelformat);
+
+	*f = node->v_fmt;
+
+	return 0;
+}
+
+static int unicam_enum_fmt_meta_cap(struct file *file, void *priv,
+				    struct v4l2_fmtdesc *f)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+	const struct unicam_fmt *fmt;
+	u32 code;
+	int ret = 0;
+
+	if (node->pad_id != METADATA_PAD || f->index != 0)
+		return -EINVAL;
+
+	if (dev->sensor_embedded_data) {
+		struct v4l2_subdev_mbus_code_enum mbus_code = {
+			.index = f->index,
+			.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+			.pad = METADATA_PAD,
+		};
+
+		ret = v4l2_subdev_call(dev->sensor, pad, enum_mbus_code, NULL,
+				       &mbus_code);
+		if (ret < 0) {
+			unicam_dbg(2, dev,
+				   "subdev->enum_mbus_code idx 0 returned %d - index invalid\n",
+				   ret);
+			return -EINVAL;
+		}
+
+		code = mbus_code.code;
+	} else {
+		code = MEDIA_BUS_FMT_SENSOR_DATA;
+	}
+
+	fmt = find_format_by_code(code);
+	if (fmt)
+		f->pixelformat = fmt->fourcc;
+
+	return 0;
+}
+
+static int unicam_g_fmt_meta_cap(struct file *file, void *priv,
+				 struct v4l2_format *f)
+{
+	struct unicam_node *node = video_drvdata(file);
+
+	if (node->pad_id != METADATA_PAD)
+		return -EINVAL;
+
+	*f = node->v_fmt;
+
+	return 0;
+}
+
+static int unicam_enum_input(struct file *file, void *priv,
+			     struct v4l2_input *inp)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+	int ret;
+
+	if (inp->index != 0)
+		return -EINVAL;
+
+	inp->type = V4L2_INPUT_TYPE_CAMERA;
+	if (v4l2_subdev_has_op(dev->sensor, pad, s_dv_timings)) {
+		inp->capabilities = V4L2_IN_CAP_DV_TIMINGS;
+		inp->std = 0;
+	} else if (v4l2_subdev_has_op(dev->sensor, video, s_std)) {
+		inp->capabilities = V4L2_IN_CAP_STD;
+		if (v4l2_subdev_call(dev->sensor, video, g_tvnorms, &inp->std) < 0)
+			inp->std = V4L2_STD_ALL;
+	} else {
+		inp->capabilities = 0;
+		inp->std = 0;
+	}
+
+	if (v4l2_subdev_has_op(dev->sensor, video, g_input_status)) {
+		ret = v4l2_subdev_call(dev->sensor, video, g_input_status,
+				       &inp->status);
+		if (ret < 0)
+			return ret;
+	}
+
+	snprintf(inp->name, sizeof(inp->name), "Camera 0");
+	return 0;
+}
+
+static int unicam_g_input(struct file *file, void *priv, unsigned int *i)
+{
+	*i = 0;
+
+	return 0;
+}
+
+static int unicam_s_input(struct file *file, void *priv, unsigned int i)
+{
+	/*
+	 * FIXME: Ideally we would like to be able to query the source
+	 * subdevice for information over the input connectors it supports,
+	 * and map that through in to a call to video_ops->s_routing.
+	 * There is no infrastructure support for defining that within
+	 * devicetree at present. Until that is implemented we can't
+	 * map a user physical connector number to s_routing input number.
+	 */
+	if (i > 0)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int unicam_querystd(struct file *file, void *priv,
+			   v4l2_std_id *std)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+
+	return v4l2_subdev_call(dev->sensor, video, querystd, std);
+}
+
+static int unicam_g_std(struct file *file, void *priv, v4l2_std_id *std)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+
+	return v4l2_subdev_call(dev->sensor, video, g_std, std);
+}
+
+static int unicam_s_std(struct file *file, void *priv, v4l2_std_id std)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+	int ret;
+	v4l2_std_id current_std;
+
+	ret = v4l2_subdev_call(dev->sensor, video, g_std, &current_std);
+	if (ret)
+		return ret;
+
+	if (std == current_std)
+		return 0;
+
+	if (vb2_is_busy(&node->buffer_queue))
+		return -EBUSY;
+
+	ret = v4l2_subdev_call(dev->sensor, video, s_std, std);
+
+	/* Force recomputation of bytesperline */
+	node->v_fmt.fmt.pix.bytesperline = 0;
+
+	unicam_reset_format(node);
+
+	return ret;
+}
+
+static int unicam_s_edid(struct file *file, void *priv, struct v4l2_edid *edid)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+
+	return v4l2_subdev_call(dev->sensor, pad, set_edid, edid);
+}
+
+static int unicam_g_edid(struct file *file, void *priv, struct v4l2_edid *edid)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+
+	return v4l2_subdev_call(dev->sensor, pad, get_edid, edid);
+}
+
+static int unicam_s_selection(struct file *file, void *priv,
+			      struct v4l2_selection *sel)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+	struct v4l2_subdev_selection sdsel = {
+		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+		.target = sel->target,
+		.flags = sel->flags,
+		.r = sel->r,
+	};
+
+	if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	return v4l2_subdev_call(dev->sensor, pad, set_selection, NULL, &sdsel);
+}
+
+static int unicam_g_selection(struct file *file, void *priv,
+			      struct v4l2_selection *sel)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+	struct v4l2_subdev_selection sdsel = {
+		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+		.target = sel->target,
+	};
+	int ret;
+
+	if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	ret = v4l2_subdev_call(dev->sensor, pad, get_selection, NULL, &sdsel);
+	if (!ret)
+		sel->r = sdsel.r;
+
+	return ret;
+}
+
+static int unicam_enum_framesizes(struct file *file, void *priv,
+				  struct v4l2_frmsizeenum *fsize)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+	const struct unicam_fmt *fmt;
+	struct v4l2_subdev_frame_size_enum fse;
+	int ret;
+
+	/* check for valid format */
+	fmt = find_format_by_pix(dev, fsize->pixel_format);
+	if (!fmt) {
+		unicam_dbg(3, dev, "Invalid pixel code: %x\n",
+			   fsize->pixel_format);
+		return -EINVAL;
+	}
+	fse.code = fmt->code;
+
+	fse.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+	fse.index = fsize->index;
+	fse.pad = node->src_pad_id;
+
+	ret = v4l2_subdev_call(dev->sensor, pad, enum_frame_size, NULL, &fse);
+	if (ret)
+		return ret;
+
+	unicam_dbg(1, dev, "%s: index: %d code: %x W:[%d,%d] H:[%d,%d]\n",
+		   __func__, fse.index, fse.code, fse.min_width, fse.max_width,
+		   fse.min_height, fse.max_height);
+
+	fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+	fsize->discrete.width = fse.max_width;
+	fsize->discrete.height = fse.max_height;
+
+	return 0;
+}
+
+static int unicam_enum_frameintervals(struct file *file, void *priv,
+				      struct v4l2_frmivalenum *fival)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+	const struct unicam_fmt *fmt;
+	struct v4l2_subdev_frame_interval_enum fie = {
+		.index = fival->index,
+		.pad = node->src_pad_id,
+		.width = fival->width,
+		.height = fival->height,
+		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+	};
+	int ret;
+
+	fmt = find_format_by_pix(dev, fival->pixel_format);
+	if (!fmt)
+		return -EINVAL;
+
+	fie.code = fmt->code;
+	ret = v4l2_subdev_call(dev->sensor, pad, enum_frame_interval,
+			       NULL, &fie);
+	if (ret)
+		return ret;
+
+	fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+	fival->discrete = fie.interval;
+
+	return 0;
+}
+
+static int unicam_g_parm(struct file *file, void *fh, struct v4l2_streamparm *a)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+
+	return v4l2_g_parm_cap(video_devdata(file), dev->sensor, a);
+}
+
+static int unicam_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+
+	return v4l2_s_parm_cap(video_devdata(file), dev->sensor, a);
+}
+
+static int unicam_g_dv_timings(struct file *file, void *priv,
+			       struct v4l2_dv_timings *timings)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+
+	return v4l2_subdev_call(dev->sensor, pad, g_dv_timings, 0, timings);
+}
+
+static int unicam_s_dv_timings(struct file *file, void *priv,
+			       struct v4l2_dv_timings *timings)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+	struct v4l2_dv_timings current_timings;
+	int ret;
+
+	ret = v4l2_subdev_call(dev->sensor, pad, g_dv_timings, 0,
+			       &current_timings);
+
+	if (ret < 0)
+		return ret;
+
+	if (v4l2_match_dv_timings(timings, &current_timings, 0, false))
+		return 0;
+
+	if (vb2_is_busy(&node->buffer_queue))
+		return -EBUSY;
+
+	ret = v4l2_subdev_call(dev->sensor, pad, s_dv_timings, 0, timings);
+
+	/* Force recomputation of bytesperline */
+	node->v_fmt.fmt.pix.bytesperline = 0;
+
+	unicam_reset_format(node);
+
+	return ret;
+}
+
+static int unicam_query_dv_timings(struct file *file, void *priv,
+				   struct v4l2_dv_timings *timings)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+
+	return v4l2_subdev_call(dev->sensor, pad, query_dv_timings, 0, timings);
+}
+
+static int unicam_enum_dv_timings(struct file *file, void *priv,
+				  struct v4l2_enum_dv_timings *timings)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+	int ret;
+
+	timings->pad = node->src_pad_id;
+	ret = v4l2_subdev_call(dev->sensor, pad, enum_dv_timings, timings);
+	timings->pad = node->pad_id;
+
+	return ret;
+}
+
+static int unicam_dv_timings_cap(struct file *file, void *priv,
+				 struct v4l2_dv_timings_cap *cap)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+	int ret;
+
+	cap->pad = node->src_pad_id;
+	ret = v4l2_subdev_call(dev->sensor, pad, dv_timings_cap, cap);
+	cap->pad = node->pad_id;
+
+	return ret;
+}
+
+static int unicam_subscribe_event(struct v4l2_fh *fh,
+				  const struct v4l2_event_subscription *sub)
+{
+	switch (sub->type) {
+	case V4L2_EVENT_FRAME_SYNC:
+		return v4l2_event_subscribe(fh, sub, 2, NULL);
+	case V4L2_EVENT_SOURCE_CHANGE:
+		return v4l2_event_subscribe(fh, sub, 4, NULL);
+	}
+
+	return v4l2_ctrl_subscribe_event(fh, sub);
+}
+
+static void unicam_notify(struct v4l2_subdev *sd,
+			  unsigned int notification, void *arg)
+{
+	struct unicam_device *dev = to_unicam_device(sd->v4l2_dev);
+
+	switch (notification) {
+	case V4L2_DEVICE_NOTIFY_EVENT:
+		v4l2_event_queue(&dev->node[IMAGE_PAD].video_dev, arg);
+		break;
+	default:
+		break;
+	}
+}
+
+/* unicam capture ioctl operations */
+static const struct v4l2_ioctl_ops unicam_ioctl_ops = {
+	.vidioc_querycap		= unicam_querycap,
+	.vidioc_enum_fmt_vid_cap	= unicam_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap		= unicam_g_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap		= unicam_s_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap		= unicam_try_fmt_vid_cap,
+
+	.vidioc_enum_fmt_meta_cap	= unicam_enum_fmt_meta_cap,
+	.vidioc_g_fmt_meta_cap		= unicam_g_fmt_meta_cap,
+	.vidioc_s_fmt_meta_cap		= unicam_g_fmt_meta_cap,
+	.vidioc_try_fmt_meta_cap	= unicam_g_fmt_meta_cap,
+
+	.vidioc_enum_input		= unicam_enum_input,
+	.vidioc_g_input			= unicam_g_input,
+	.vidioc_s_input			= unicam_s_input,
+
+	.vidioc_querystd		= unicam_querystd,
+	.vidioc_s_std			= unicam_s_std,
+	.vidioc_g_std			= unicam_g_std,
+
+	.vidioc_g_edid			= unicam_g_edid,
+	.vidioc_s_edid			= unicam_s_edid,
+
+	.vidioc_enum_framesizes		= unicam_enum_framesizes,
+	.vidioc_enum_frameintervals	= unicam_enum_frameintervals,
+
+	.vidioc_g_selection		= unicam_g_selection,
+	.vidioc_s_selection		= unicam_s_selection,
+
+	.vidioc_g_parm			= unicam_g_parm,
+	.vidioc_s_parm			= unicam_s_parm,
+
+	.vidioc_s_dv_timings		= unicam_s_dv_timings,
+	.vidioc_g_dv_timings		= unicam_g_dv_timings,
+	.vidioc_query_dv_timings	= unicam_query_dv_timings,
+	.vidioc_enum_dv_timings		= unicam_enum_dv_timings,
+	.vidioc_dv_timings_cap		= unicam_dv_timings_cap,
+
+	.vidioc_reqbufs			= vb2_ioctl_reqbufs,
+	.vidioc_create_bufs		= vb2_ioctl_create_bufs,
+	.vidioc_prepare_buf		= vb2_ioctl_prepare_buf,
+	.vidioc_querybuf		= vb2_ioctl_querybuf,
+	.vidioc_qbuf			= vb2_ioctl_qbuf,
+	.vidioc_dqbuf			= vb2_ioctl_dqbuf,
+	.vidioc_expbuf			= vb2_ioctl_expbuf,
+	.vidioc_streamon		= vb2_ioctl_streamon,
+	.vidioc_streamoff		= vb2_ioctl_streamoff,
+
+	.vidioc_log_status		= unicam_log_status,
+	.vidioc_subscribe_event		= unicam_subscribe_event,
+	.vidioc_unsubscribe_event	= v4l2_event_unsubscribe,
+};
+
+/* V4L2 Media Controller Centric IOCTLs */
+
+static int unicam_mc_enum_fmt_vid_cap(struct file *file, void  *priv,
+				      struct v4l2_fmtdesc *f)
+{
+	int i, j;
+
+	for (i = 0, j = 0; i < ARRAY_SIZE(formats); i++) {
+		if (f->mbus_code && formats[i].code != f->mbus_code)
+			continue;
+		if (formats[i].mc_skip || formats[i].metadata_fmt)
+			continue;
+
+		if (formats[i].fourcc) {
+			if (j == f->index) {
+				f->pixelformat = formats[i].fourcc;
+				f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+				return 0;
+			}
+			j++;
+		}
+		if (formats[i].repacked_fourcc) {
+			if (j == f->index) {
+				f->pixelformat = formats[i].repacked_fourcc;
+				f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+				return 0;
+			}
+			j++;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int unicam_mc_g_fmt_vid_cap(struct file *file, void *priv,
+				   struct v4l2_format *f)
+{
+	struct unicam_node *node = video_drvdata(file);
+
+	if (node->pad_id != IMAGE_PAD)
+		return -EINVAL;
+
+	*f = node->v_fmt;
+
+	return 0;
+}
+
+static void unicam_mc_try_fmt(struct unicam_node *node, struct v4l2_format *f,
+			      const struct unicam_fmt **ret_fmt)
+{
+	struct v4l2_pix_format *v4l2_format = &f->fmt.pix;
+	struct unicam_device *dev = node->dev;
+	const struct unicam_fmt *fmt;
+	int is_rgb;
+
+	/*
+	 * Default to the first format if the requested pixel format code isn't
+	 * supported.
+	 */
+	fmt = find_format_by_pix(dev, v4l2_format->pixelformat);
+	if (!fmt) {
+		fmt = &formats[0];
+		v4l2_format->pixelformat = fmt->fourcc;
+	}
+
+	unicam_calc_format_size_bpl(dev, fmt, f);
+
+	if (v4l2_format->field == V4L2_FIELD_ANY)
+		v4l2_format->field = V4L2_FIELD_NONE;
+
+	if (ret_fmt)
+		*ret_fmt = fmt;
+
+	if (v4l2_format->colorspace >= MAX_COLORSPACE ||
+	    !(fmt->valid_colorspaces & (1 << v4l2_format->colorspace))) {
+		v4l2_format->colorspace = __ffs(fmt->valid_colorspaces);
+
+		v4l2_format->xfer_func =
+			V4L2_MAP_XFER_FUNC_DEFAULT(v4l2_format->colorspace);
+		v4l2_format->ycbcr_enc =
+			V4L2_MAP_YCBCR_ENC_DEFAULT(v4l2_format->colorspace);
+		is_rgb = v4l2_format->colorspace == V4L2_COLORSPACE_SRGB;
+		v4l2_format->quantization =
+			V4L2_MAP_QUANTIZATION_DEFAULT(is_rgb,
+						      v4l2_format->colorspace,
+						      v4l2_format->ycbcr_enc);
+	}
+
+	unicam_dbg(3, dev, "%s: %08x %ux%u (bytesperline %u sizeimage %u)\n",
+		   __func__, v4l2_format->pixelformat,
+		   v4l2_format->width, v4l2_format->height,
+		   v4l2_format->bytesperline, v4l2_format->sizeimage);
+}
+
+static int unicam_mc_try_fmt_vid_cap(struct file *file, void *priv,
+				     struct v4l2_format *f)
+{
+	struct unicam_node *node = video_drvdata(file);
+
+	unicam_mc_try_fmt(node, f, NULL);
+	return 0;
+}
+
+static int unicam_mc_s_fmt_vid_cap(struct file *file, void *priv,
+				   struct v4l2_format *f)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+	const struct unicam_fmt *fmt;
+
+	if (vb2_is_busy(&node->buffer_queue)) {
+		unicam_dbg(3, dev, "%s device busy\n", __func__);
+		return -EBUSY;
+	}
+
+	unicam_mc_try_fmt(node, f, &fmt);
+
+	node->v_fmt = *f;
+	node->fmt = fmt;
+
+	return 0;
+}
+
+static int unicam_mc_enum_framesizes(struct file *file, void *fh,
+				     struct v4l2_frmsizeenum *fsize)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+
+	if (fsize->index > 0)
+		return -EINVAL;
+
+	if (!find_format_by_pix(dev, fsize->pixel_format)) {
+		unicam_dbg(3, dev, "Invalid pixel format 0x%08x\n",
+			   fsize->pixel_format);
+		return -EINVAL;
+	}
+
+	fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
+	fsize->stepwise.min_width = MIN_WIDTH;
+	fsize->stepwise.max_width = MAX_WIDTH;
+	fsize->stepwise.step_width = 1;
+	fsize->stepwise.min_height = MIN_HEIGHT;
+	fsize->stepwise.max_height = MAX_HEIGHT;
+	fsize->stepwise.step_height = 1;
+
+	return 0;
+}
+
+static int unicam_mc_enum_fmt_meta_cap(struct file *file, void  *priv,
+				       struct v4l2_fmtdesc *f)
+{
+	int i, j;
+
+	for (i = 0, j = 0; i < ARRAY_SIZE(formats); i++) {
+		if (f->mbus_code && formats[i].code != f->mbus_code)
+			continue;
+		if (!formats[i].metadata_fmt)
+			continue;
+
+		if (formats[i].fourcc) {
+			if (j == f->index) {
+				f->pixelformat = formats[i].fourcc;
+				f->type = V4L2_BUF_TYPE_META_CAPTURE;
+				return 0;
+			}
+			j++;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int unicam_mc_g_fmt_meta_cap(struct file *file, void *priv,
+				    struct v4l2_format *f)
+{
+	struct unicam_node *node = video_drvdata(file);
+
+	if (node->pad_id != METADATA_PAD)
+		return -EINVAL;
+
+	*f = node->v_fmt;
+
+	return 0;
+}
+
+static int unicam_mc_try_fmt_meta_cap(struct file *file, void *priv,
+				      struct v4l2_format *f)
+{
+	struct unicam_node *node = video_drvdata(file);
+
+	if (node->pad_id != METADATA_PAD)
+		return -EINVAL;
+
+	f->fmt.meta.dataformat = V4L2_META_FMT_SENSOR_DATA;
+
+	return 0;
+}
+
+static int unicam_mc_s_fmt_meta_cap(struct file *file, void *priv,
+				    struct v4l2_format *f)
+{
+	struct unicam_node *node = video_drvdata(file);
+
+	if (node->pad_id != METADATA_PAD)
+		return -EINVAL;
+
+	unicam_mc_try_fmt_meta_cap(file, priv, f);
+
+	node->v_fmt = *f;
+
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops unicam_mc_ioctl_ops = {
+	.vidioc_querycap      = unicam_querycap,
+	.vidioc_enum_fmt_vid_cap  = unicam_mc_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap     = unicam_mc_g_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap   = unicam_mc_try_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap     = unicam_mc_s_fmt_vid_cap,
+
+	.vidioc_enum_fmt_meta_cap	= unicam_mc_enum_fmt_meta_cap,
+	.vidioc_g_fmt_meta_cap		= unicam_mc_g_fmt_meta_cap,
+	.vidioc_try_fmt_meta_cap	= unicam_mc_try_fmt_meta_cap,
+	.vidioc_s_fmt_meta_cap		= unicam_mc_s_fmt_meta_cap,
+
+	.vidioc_enum_framesizes   = unicam_mc_enum_framesizes,
+	.vidioc_reqbufs       = vb2_ioctl_reqbufs,
+	.vidioc_create_bufs   = vb2_ioctl_create_bufs,
+	.vidioc_prepare_buf   = vb2_ioctl_prepare_buf,
+	.vidioc_querybuf      = vb2_ioctl_querybuf,
+	.vidioc_qbuf          = vb2_ioctl_qbuf,
+	.vidioc_dqbuf         = vb2_ioctl_dqbuf,
+	.vidioc_expbuf        = vb2_ioctl_expbuf,
+	.vidioc_streamon      = vb2_ioctl_streamon,
+	.vidioc_streamoff     = vb2_ioctl_streamoff,
+
+	.vidioc_log_status		= unicam_log_status,
+	.vidioc_subscribe_event		= unicam_subscribe_event,
+	.vidioc_unsubscribe_event	= v4l2_event_unsubscribe,
+};
+
+static int
+unicam_mc_subdev_link_validate_get_format(struct media_pad *pad,
+					  struct v4l2_subdev_format *fmt)
+{
+	if (is_media_entity_v4l2_subdev(pad->entity)) {
+		struct v4l2_subdev *sd =
+			media_entity_to_v4l2_subdev(pad->entity);
+
+		fmt->which = V4L2_SUBDEV_FORMAT_ACTIVE;
+		fmt->pad = pad->index;
+		return v4l2_subdev_call(sd, pad, get_fmt, NULL, fmt);
+	}
+
+	return -EINVAL;
+}
+
+static int unicam_mc_video_link_validate(struct media_link *link)
+{
+	struct video_device *vd = container_of(link->sink->entity,
+						struct video_device, entity);
+	struct unicam_node *node = container_of(vd, struct unicam_node,
+						video_dev);
+	struct unicam_device *unicam = node->dev;
+	struct v4l2_subdev_format source_fmt;
+	int ret;
+
+	if (!media_entity_remote_source_pad_unique(link->sink->entity)) {
+		unicam_dbg(1, unicam,
+			   "video node %s pad not connected\n", vd->name);
+		return -ENOTCONN;
+	}
+
+	ret = unicam_mc_subdev_link_validate_get_format(link->source,
+							&source_fmt);
+	if (ret < 0)
+		return 0;
+
+	if (node->pad_id == IMAGE_PAD) {
+		struct v4l2_pix_format *pix_fmt = &node->v_fmt.fmt.pix;
+		const struct unicam_fmt *fmt;
+
+		if (source_fmt.format.width != pix_fmt->width ||
+		    source_fmt.format.height != pix_fmt->height) {
+			unicam_err(unicam,
+				   "Wrong width or height %ux%u (remote pad set to %ux%u)\n",
+				   pix_fmt->width, pix_fmt->height,
+				   source_fmt.format.width,
+				   source_fmt.format.height);
+			return -EINVAL;
+		}
+
+		fmt = find_format_by_code(source_fmt.format.code);
+
+		if (!fmt || (fmt->fourcc != pix_fmt->pixelformat &&
+			     fmt->repacked_fourcc != pix_fmt->pixelformat))
+			return -EINVAL;
+	} else {
+		struct v4l2_meta_format *meta_fmt = &node->v_fmt.fmt.meta;
+
+		if (source_fmt.format.width != meta_fmt->buffersize ||
+		    source_fmt.format.height != 1 ||
+		    source_fmt.format.code != MEDIA_BUS_FMT_SENSOR_DATA) {
+			unicam_err(unicam,
+				   "Wrong metadata width/height/code %ux%u %08x (remote pad set to %ux%u %08x)\n",
+				   meta_fmt->buffersize, 1,
+				   MEDIA_BUS_FMT_SENSOR_DATA,
+				   source_fmt.format.width,
+				   source_fmt.format.height,
+				   source_fmt.format.code);
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+static const struct media_entity_operations unicam_mc_entity_ops = {
+	.link_validate = unicam_mc_video_link_validate,
+};
+
+/* videobuf2 Operations */
+
+static int unicam_queue_setup(struct vb2_queue *vq,
+			      unsigned int *nbuffers,
+			      unsigned int *nplanes,
+			      unsigned int sizes[],
+			      struct device *alloc_devs[])
+{
+	struct unicam_node *node = vb2_get_drv_priv(vq);
+	struct unicam_device *dev = node->dev;
+	unsigned int size = node->pad_id == IMAGE_PAD ?
+				    node->v_fmt.fmt.pix.sizeimage :
+				    node->v_fmt.fmt.meta.buffersize;
+
+	if (*nplanes) {
+		if (sizes[0] < size) {
+			unicam_err(dev, "sizes[0] %i < size %u\n", sizes[0],
+				   size);
+			return -EINVAL;
+		}
+		size = sizes[0];
+	}
+
+	*nplanes = 1;
+	sizes[0] = size;
+
+	return 0;
+}
+
+static int unicam_buffer_prepare(struct vb2_buffer *vb)
+{
+	struct unicam_node *node = vb2_get_drv_priv(vb->vb2_queue);
+	struct unicam_device *dev = node->dev;
+	struct unicam_buffer *buf = to_unicam_buffer(vb);
+	unsigned long size;
+
+	if (WARN_ON(!node->fmt))
+		return -EINVAL;
+
+	size = node->pad_id == IMAGE_PAD ? node->v_fmt.fmt.pix.sizeimage :
+					   node->v_fmt.fmt.meta.buffersize;
+	if (vb2_plane_size(vb, 0) < size) {
+		unicam_err(dev, "data will not fit into plane (%lu < %lu)\n",
+			   vb2_plane_size(vb, 0), size);
+		return -EINVAL;
+	}
+
+	vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size);
+	return 0;
+}
+
+static void unicam_buffer_queue(struct vb2_buffer *vb)
+{
+	struct unicam_node *node = vb2_get_drv_priv(vb->vb2_queue);
+	struct unicam_buffer *buf = to_unicam_buffer(vb);
+	unsigned long flags;
+
+	spin_lock_irqsave(&node->dma_queue_lock, flags);
+	list_add_tail(&buf->list, &node->dma_queue);
+	spin_unlock_irqrestore(&node->dma_queue_lock, flags);
+}
+
+static void unicam_set_packing_config(struct unicam_device *dev)
+{
+	u32 pack, unpack;
+	u32 val;
+
+	if (dev->node[IMAGE_PAD].v_fmt.fmt.pix.pixelformat ==
+	    dev->node[IMAGE_PAD].fmt->fourcc) {
+		unpack = UNICAM_PUM_NONE;
+		pack = UNICAM_PPM_NONE;
+	} else {
+		switch (dev->node[IMAGE_PAD].fmt->depth) {
+		case 8:
+			unpack = UNICAM_PUM_UNPACK8;
+			break;
+		case 10:
+			unpack = UNICAM_PUM_UNPACK10;
+			break;
+		case 12:
+			unpack = UNICAM_PUM_UNPACK12;
+			break;
+		case 14:
+			unpack = UNICAM_PUM_UNPACK14;
+			break;
+		case 16:
+			unpack = UNICAM_PUM_UNPACK16;
+			break;
+		default:
+			unpack = UNICAM_PUM_NONE;
+			break;
+		}
+
+		/* Repacking is always to 16bpp */
+		pack = UNICAM_PPM_PACK16;
+	}
+
+	val = 0;
+	set_field(&val, unpack, UNICAM_PUM_MASK);
+	set_field(&val, pack, UNICAM_PPM_MASK);
+	reg_write(dev, UNICAM_IPIPE, val);
+}
+
+static void unicam_cfg_image_id(struct unicam_device *dev)
+{
+	if (dev->bus_type == V4L2_MBUS_CSI2_DPHY) {
+		/* CSI2 mode, hardcode VC 0 for now. */
+		reg_write(dev, UNICAM_IDI0,
+			  (0 << 6) | dev->node[IMAGE_PAD].fmt->csi_dt);
+	} else {
+		/* CCP2 mode */
+		reg_write(dev, UNICAM_IDI0,
+			  0x80 | dev->node[IMAGE_PAD].fmt->csi_dt);
+	}
+}
+
+static void unicam_enable_ed(struct unicam_device *dev)
+{
+	u32 val = reg_read(dev, UNICAM_DCS);
+
+	set_field(&val, 2, UNICAM_EDL_MASK);
+	/* Do not wrap at the end of the embedded data buffer */
+	set_field(&val, 0, UNICAM_DBOB);
+
+	reg_write(dev, UNICAM_DCS, val);
+}
+
+static void unicam_start_rx(struct unicam_device *dev, dma_addr_t *addr)
+{
+	int line_int_freq = dev->node[IMAGE_PAD].v_fmt.fmt.pix.height >> 2;
+	unsigned int size, i;
+	u32 val;
+
+	if (line_int_freq < 128)
+		line_int_freq = 128;
+
+	/* Enable lane clocks */
+	val = 1;
+	for (i = 0; i < dev->active_data_lanes; i++)
+		val = val << 2 | 1;
+	clk_write(dev, val);
+
+	/* Basic init */
+	reg_write(dev, UNICAM_CTRL, UNICAM_MEM);
+
+	/* Enable analogue control, and leave in reset. */
+	val = UNICAM_AR;
+	set_field(&val, 7, UNICAM_CTATADJ_MASK);
+	set_field(&val, 7, UNICAM_PTATADJ_MASK);
+	reg_write(dev, UNICAM_ANA, val);
+	usleep_range(1000, 2000);
+
+	/* Come out of reset */
+	reg_write_field(dev, UNICAM_ANA, 0, UNICAM_AR);
+
+	/* Peripheral reset */
+	reg_write_field(dev, UNICAM_CTRL, 1, UNICAM_CPR);
+	reg_write_field(dev, UNICAM_CTRL, 0, UNICAM_CPR);
+
+	reg_write_field(dev, UNICAM_CTRL, 0, UNICAM_CPE);
+
+	/* Enable Rx control. */
+	val = reg_read(dev, UNICAM_CTRL);
+	if (dev->bus_type == V4L2_MBUS_CSI2_DPHY) {
+		set_field(&val, UNICAM_CPM_CSI2, UNICAM_CPM_MASK);
+		set_field(&val, UNICAM_DCM_STROBE, UNICAM_DCM_MASK);
+	} else {
+		set_field(&val, UNICAM_CPM_CCP2, UNICAM_CPM_MASK);
+		set_field(&val, dev->bus_flags, UNICAM_DCM_MASK);
+	}
+	/* Packet framer timeout */
+	set_field(&val, 0xf, UNICAM_PFT_MASK);
+	set_field(&val, 128, UNICAM_OET_MASK);
+	reg_write(dev, UNICAM_CTRL, val);
+
+	reg_write(dev, UNICAM_IHWIN, 0);
+	reg_write(dev, UNICAM_IVWIN, 0);
+
+	/* AXI bus access QoS setup */
+	val = reg_read(dev, UNICAM_PRI);
+	set_field(&val, 0, UNICAM_BL_MASK);
+	set_field(&val, 0, UNICAM_BS_MASK);
+	set_field(&val, 0xe, UNICAM_PP_MASK);
+	set_field(&val, 8, UNICAM_NP_MASK);
+	set_field(&val, 2, UNICAM_PT_MASK);
+	set_field(&val, 1, UNICAM_PE);
+	reg_write(dev, UNICAM_PRI, val);
+
+	reg_write_field(dev, UNICAM_ANA, 0, UNICAM_DDL);
+
+	val = UNICAM_FSIE | UNICAM_FEIE | UNICAM_IBOB;
+	set_field(&val, line_int_freq, UNICAM_LCIE_MASK);
+	reg_write(dev, UNICAM_ICTL, val);
+	reg_write(dev, UNICAM_STA, UNICAM_STA_MASK_ALL);
+	reg_write(dev, UNICAM_ISTA, UNICAM_ISTA_MASK_ALL);
+
+	/* tclk_term_en */
+	reg_write_field(dev, UNICAM_CLT, 2, UNICAM_CLT1_MASK);
+	/* tclk_settle */
+	reg_write_field(dev, UNICAM_CLT, 6, UNICAM_CLT2_MASK);
+	/* td_term_en */
+	reg_write_field(dev, UNICAM_DLT, 2, UNICAM_DLT1_MASK);
+	/* ths_settle */
+	reg_write_field(dev, UNICAM_DLT, 6, UNICAM_DLT2_MASK);
+	/* trx_enable */
+	reg_write_field(dev, UNICAM_DLT, 0, UNICAM_DLT3_MASK);
+
+	reg_write_field(dev, UNICAM_CTRL, 0, UNICAM_SOE);
+
+	/* Packet compare setup - required to avoid missing frame ends */
+	val = 0;
+	set_field(&val, 1, UNICAM_PCE);
+	set_field(&val, 1, UNICAM_GI);
+	set_field(&val, 1, UNICAM_CPH);
+	set_field(&val, 0, UNICAM_PCVC_MASK);
+	set_field(&val, 1, UNICAM_PCDT_MASK);
+	reg_write(dev, UNICAM_CMP0, val);
+
+	/* Enable clock lane and set up terminations */
+	val = 0;
+	if (dev->bus_type == V4L2_MBUS_CSI2_DPHY) {
+		/* CSI2 */
+		set_field(&val, 1, UNICAM_CLE);
+		set_field(&val, 1, UNICAM_CLLPE);
+		if (!(dev->bus_flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK)) {
+			set_field(&val, 1, UNICAM_CLTRE);
+			set_field(&val, 1, UNICAM_CLHSE);
+		}
+	} else {
+		/* CCP2 */
+		set_field(&val, 1, UNICAM_CLE);
+		set_field(&val, 1, UNICAM_CLHSE);
+		set_field(&val, 1, UNICAM_CLTRE);
+	}
+	reg_write(dev, UNICAM_CLK, val);
+
+	/*
+	 * Enable required data lanes with appropriate terminations.
+	 * The same value needs to be written to UNICAM_DATn registers for
+	 * the active lanes, and 0 for inactive ones.
+	 */
+	val = 0;
+	if (dev->bus_type == V4L2_MBUS_CSI2_DPHY) {
+		/* CSI2 */
+		set_field(&val, 1, UNICAM_DLE);
+		set_field(&val, 1, UNICAM_DLLPE);
+		if (!(dev->bus_flags & V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK)) {
+			set_field(&val, 1, UNICAM_DLTRE);
+			set_field(&val, 1, UNICAM_DLHSE);
+		}
+	} else {
+		/* CCP2 */
+		set_field(&val, 1, UNICAM_DLE);
+		set_field(&val, 1, UNICAM_DLHSE);
+		set_field(&val, 1, UNICAM_DLTRE);
+	}
+	reg_write(dev, UNICAM_DAT0, val);
+
+	if (dev->active_data_lanes == 1)
+		val = 0;
+	reg_write(dev, UNICAM_DAT1, val);
+
+	if (dev->max_data_lanes > 2) {
+		/*
+		 * Registers UNICAM_DAT2 and UNICAM_DAT3 only valid if the
+		 * instance supports more than 2 data lanes.
+		 */
+		if (dev->active_data_lanes == 2)
+			val = 0;
+		reg_write(dev, UNICAM_DAT2, val);
+
+		if (dev->active_data_lanes == 3)
+			val = 0;
+		reg_write(dev, UNICAM_DAT3, val);
+	}
+
+	reg_write(dev, UNICAM_IBLS,
+		  dev->node[IMAGE_PAD].v_fmt.fmt.pix.bytesperline);
+	size = dev->node[IMAGE_PAD].v_fmt.fmt.pix.sizeimage;
+	unicam_wr_dma_addr(dev, addr[IMAGE_PAD], size, IMAGE_PAD);
+	unicam_set_packing_config(dev);
+	unicam_cfg_image_id(dev);
+
+	val = reg_read(dev, UNICAM_MISC);
+	set_field(&val, 1, UNICAM_FL0);
+	set_field(&val, 1, UNICAM_FL1);
+	reg_write(dev, UNICAM_MISC, val);
+
+	if (dev->node[METADATA_PAD].streaming && dev->sensor_embedded_data) {
+		size = dev->node[METADATA_PAD].v_fmt.fmt.meta.buffersize;
+		unicam_enable_ed(dev);
+		unicam_wr_dma_addr(dev, addr[METADATA_PAD], size, METADATA_PAD);
+	}
+
+	/* Enable peripheral */
+	reg_write_field(dev, UNICAM_CTRL, 1, UNICAM_CPE);
+
+	/* Load image pointers */
+	reg_write_field(dev, UNICAM_ICTL, 1, UNICAM_LIP_MASK);
+
+	/* Load embedded data buffer pointers if needed */
+	if (dev->node[METADATA_PAD].streaming && dev->sensor_embedded_data)
+		reg_write_field(dev, UNICAM_DCS, 1, UNICAM_LDP);
+}
+
+static void unicam_disable(struct unicam_device *dev)
+{
+	/* Analogue lane control disable */
+	reg_write_field(dev, UNICAM_ANA, 1, UNICAM_DDL);
+
+	/* Stop the output engine */
+	reg_write_field(dev, UNICAM_CTRL, 1, UNICAM_SOE);
+
+	/* Disable the data lanes. */
+	reg_write(dev, UNICAM_DAT0, 0);
+	reg_write(dev, UNICAM_DAT1, 0);
+
+	if (dev->max_data_lanes > 2) {
+		reg_write(dev, UNICAM_DAT2, 0);
+		reg_write(dev, UNICAM_DAT3, 0);
+	}
+
+	/* Peripheral reset */
+	reg_write_field(dev, UNICAM_CTRL, 1, UNICAM_CPR);
+	usleep_range(50, 100);
+	reg_write_field(dev, UNICAM_CTRL, 0, UNICAM_CPR);
+
+	/* Disable peripheral */
+	reg_write_field(dev, UNICAM_CTRL, 0, UNICAM_CPE);
+
+	/* Clear ED setup */
+	reg_write(dev, UNICAM_DCS, 0);
+
+	/* Disable all lane clocks */
+	clk_write(dev, 0);
+}
+
+static void unicam_return_buffers(struct unicam_node *node,
+				  enum vb2_buffer_state state)
+{
+	struct unicam_buffer *buf, *tmp;
+	unsigned long flags;
+
+	spin_lock_irqsave(&node->dma_queue_lock, flags);
+	list_for_each_entry_safe(buf, tmp, &node->dma_queue, list) {
+		list_del(&buf->list);
+		vb2_buffer_done(&buf->vb.vb2_buf, state);
+	}
+
+	if (node->cur_frm)
+		vb2_buffer_done(&node->cur_frm->vb.vb2_buf,
+				state);
+	if (node->next_frm && node->cur_frm != node->next_frm)
+		vb2_buffer_done(&node->next_frm->vb.vb2_buf,
+				state);
+
+	node->cur_frm = NULL;
+	node->next_frm = NULL;
+	spin_unlock_irqrestore(&node->dma_queue_lock, flags);
+}
+
+static int unicam_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct unicam_node *node = vb2_get_drv_priv(vq);
+	struct unicam_device *dev = node->dev;
+	dma_addr_t buffer_addr[MAX_NODES] = { 0 };
+	unsigned long flags;
+	unsigned int i;
+	int ret;
+
+	node->streaming = true;
+	if (!(dev->node[IMAGE_PAD].open && dev->node[IMAGE_PAD].streaming &&
+	      (!dev->node[METADATA_PAD].open ||
+	       dev->node[METADATA_PAD].streaming))) {
+		/*
+		 * Metadata pad must be enabled before image pad if it is
+		 * wanted.
+		 */
+		unicam_dbg(3, dev, "Not all nodes are streaming yet.");
+		return 0;
+	}
+
+	dev->sequence = 0;
+	ret = unicam_runtime_get(dev);
+	if (ret < 0) {
+		unicam_dbg(3, dev, "unicam_runtime_get failed\n");
+		goto err_streaming;
+	}
+
+	ret = media_pipeline_start(dev->node[IMAGE_PAD].video_dev.entity.pads,
+				   &dev->node[IMAGE_PAD].pipe);
+	if (ret < 0) {
+		unicam_err(dev, "Failed to start media pipeline: %d\n", ret);
+		goto err_pm_put;
+	}
+
+	dev->active_data_lanes = dev->max_data_lanes;
+
+	if (dev->bus_type == V4L2_MBUS_CSI2_DPHY) {
+		struct v4l2_mbus_config mbus_config = { 0 };
+
+		ret = v4l2_subdev_call(dev->sensor, pad, get_mbus_config,
+				       0, &mbus_config);
+		if (ret < 0 && ret != -ENOIOCTLCMD) {
+			unicam_dbg(3, dev, "g_mbus_config failed\n");
+			goto error_pipeline;
+		}
+
+		dev->active_data_lanes = mbus_config.bus.mipi_csi2.num_data_lanes;
+		if (!dev->active_data_lanes)
+			dev->active_data_lanes = dev->max_data_lanes;
+		if (dev->active_data_lanes > dev->max_data_lanes) {
+			unicam_err(dev, "Device has requested %u data lanes, which is >%u configured in DT\n",
+				   dev->active_data_lanes,
+				   dev->max_data_lanes);
+			ret = -EINVAL;
+			goto error_pipeline;
+		}
+	}
+
+	unicam_dbg(1, dev, "Running with %u data lanes\n",
+		   dev->active_data_lanes);
+
+	ret = clk_set_min_rate(dev->vpu_clock, MIN_VPU_CLOCK_RATE);
+	if (ret) {
+		unicam_err(dev, "failed to set up VPU clock\n");
+		goto error_pipeline;
+	}
+
+	ret = clk_prepare_enable(dev->vpu_clock);
+	if (ret) {
+		unicam_err(dev, "Failed to enable VPU clock: %d\n", ret);
+		goto error_pipeline;
+	}
+
+	ret = clk_set_rate(dev->clock, 100 * 1000 * 1000);
+	if (ret) {
+		unicam_err(dev, "failed to set up CSI clock\n");
+		goto err_vpu_clock;
+	}
+
+	ret = clk_prepare_enable(dev->clock);
+	if (ret) {
+		unicam_err(dev, "Failed to enable CSI clock: %d\n", ret);
+		goto err_vpu_clock;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(dev->node); i++) {
+		struct unicam_buffer *buf;
+
+		if (!dev->node[i].streaming)
+			continue;
+
+		spin_lock_irqsave(&dev->node[i].dma_queue_lock, flags);
+		buf = list_first_entry(&dev->node[i].dma_queue,
+				       struct unicam_buffer, list);
+		dev->node[i].cur_frm = buf;
+		dev->node[i].next_frm = buf;
+		list_del(&buf->list);
+		spin_unlock_irqrestore(&dev->node[i].dma_queue_lock, flags);
+
+		buffer_addr[i] =
+			vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
+	}
+
+	dev->frame_started = false;
+	unicam_start_rx(dev, buffer_addr);
+
+	ret = v4l2_subdev_call(dev->sensor, video, s_stream, 1);
+	if (ret < 0) {
+		unicam_err(dev, "stream on failed in subdev\n");
+		goto err_disable_unicam;
+	}
+
+	dev->clocks_enabled = true;
+	return 0;
+
+err_disable_unicam:
+	unicam_disable(dev);
+	clk_disable_unprepare(dev->clock);
+err_vpu_clock:
+	if (clk_set_min_rate(dev->vpu_clock, 0))
+		unicam_err(dev, "failed to reset the VPU clock\n");
+	clk_disable_unprepare(dev->vpu_clock);
+error_pipeline:
+	if (node->pad_id == IMAGE_PAD)
+		media_pipeline_stop(dev->node[IMAGE_PAD].video_dev.entity.pads);
+err_pm_put:
+	unicam_runtime_put(dev);
+err_streaming:
+	unicam_return_buffers(node, VB2_BUF_STATE_QUEUED);
+	node->streaming = false;
+
+	return ret;
+}
+
+static void unicam_stop_streaming(struct vb2_queue *vq)
+{
+	struct unicam_node *node = vb2_get_drv_priv(vq);
+	struct unicam_device *dev = node->dev;
+
+	node->streaming = false;
+
+	if (node->pad_id == IMAGE_PAD) {
+		/*
+		 * Stop streaming the sensor and disable the peripheral.
+		 * We cannot continue streaming embedded data with the
+		 * image pad disabled.
+		 */
+		if (v4l2_subdev_call(dev->sensor, video, s_stream, 0) < 0)
+			unicam_err(dev, "stream off failed in subdev\n");
+
+		unicam_disable(dev);
+
+		media_pipeline_stop(node->video_dev.entity.pads);
+
+		if (dev->clocks_enabled) {
+			if (clk_set_min_rate(dev->vpu_clock, 0))
+				unicam_err(dev, "failed to reset the min VPU clock\n");
+
+			clk_disable_unprepare(dev->vpu_clock);
+			clk_disable_unprepare(dev->clock);
+			dev->clocks_enabled = false;
+		}
+		unicam_runtime_put(dev);
+
+	} else if (node->pad_id == METADATA_PAD) {
+		/*
+		 * Allow the hardware to spin in the dummy buffer.
+		 * This is only really needed if the embedded data pad is
+		 * disabled before the image pad.
+		 */
+		unicam_wr_dma_addr(dev, node->dummy_buf_dma_addr, 0,
+				   METADATA_PAD);
+	}
+
+	/* Clear all queued buffers for the node */
+	unicam_return_buffers(node, VB2_BUF_STATE_ERROR);
+}
+
+
+static const struct vb2_ops unicam_video_qops = {
+	.wait_prepare		= vb2_ops_wait_prepare,
+	.wait_finish		= vb2_ops_wait_finish,
+	.queue_setup		= unicam_queue_setup,
+	.buf_prepare		= unicam_buffer_prepare,
+	.buf_queue		= unicam_buffer_queue,
+	.start_streaming	= unicam_start_streaming,
+	.stop_streaming		= unicam_stop_streaming,
+};
+
+/*
+ * unicam_v4l2_open : This function is based on the v4l2_fh_open helper
+ * function. It has been augmented to handle sensor subdevice power management,
+ */
+static int unicam_v4l2_open(struct file *file)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+	int ret;
+
+	mutex_lock(&node->lock);
+
+	ret = v4l2_fh_open(file);
+	if (ret) {
+		unicam_err(dev, "v4l2_fh_open failed\n");
+		goto unlock;
+	}
+
+	node->open++;
+
+	if (!v4l2_fh_is_singular_file(file))
+		goto unlock;
+
+	ret = v4l2_subdev_call(dev->sensor, core, s_power, 1);
+	if (ret < 0 && ret != -ENOIOCTLCMD) {
+		v4l2_fh_release(file);
+		node->open--;
+		goto unlock;
+	}
+
+	ret = 0;
+
+unlock:
+	mutex_unlock(&node->lock);
+	return ret;
+}
+
+static int unicam_v4l2_release(struct file *file)
+{
+	struct unicam_node *node = video_drvdata(file);
+	struct unicam_device *dev = node->dev;
+	struct v4l2_subdev *sd = dev->sensor;
+	bool fh_singular;
+	int ret;
+
+	mutex_lock(&node->lock);
+
+	fh_singular = v4l2_fh_is_singular_file(file);
+
+	ret = _vb2_fop_release(file, NULL);
+
+	if (fh_singular)
+		v4l2_subdev_call(sd, core, s_power, 0);
+
+	node->open--;
+	mutex_unlock(&node->lock);
+
+	return ret;
+}
+
+/* unicam capture driver file operations */
+static const struct v4l2_file_operations unicam_fops = {
+	.owner		= THIS_MODULE,
+	.open		= unicam_v4l2_open,
+	.release	= unicam_v4l2_release,
+	.read		= vb2_fop_read,
+	.poll		= vb2_fop_poll,
+	.unlocked_ioctl	= video_ioctl2,
+	.mmap		= vb2_fop_mmap,
+};
+
+static int
+unicam_async_bound(struct v4l2_async_notifier *notifier,
+		   struct v4l2_subdev *subdev,
+		   struct v4l2_async_connection *asd)
+{
+	struct unicam_device *unicam = to_unicam_device(notifier->v4l2_dev);
+
+	if (unicam->sensor) {
+		unicam_info(unicam, "Rejecting subdev %s (Already set!!)",
+			    subdev->name);
+		return 0;
+	}
+
+	unicam->sensor = subdev;
+	unicam_dbg(1, unicam, "Using sensor %s for capture\n", subdev->name);
+
+	return 0;
+}
+
+static void unicam_release(struct kref *kref)
+{
+	struct unicam_device *unicam =
+		container_of(kref, struct unicam_device, kref);
+
+	v4l2_ctrl_handler_free(&unicam->ctrl_handler);
+	media_device_cleanup(&unicam->mdev);
+
+	if (unicam->sensor_state)
+		__v4l2_subdev_state_free(unicam->sensor_state);
+
+	kfree(unicam);
+}
+
+static void unicam_put(struct unicam_device *unicam)
+{
+	kref_put(&unicam->kref, unicam_release);
+}
+
+static void unicam_get(struct unicam_device *unicam)
+{
+	kref_get(&unicam->kref);
+}
+
+static void unicam_node_release(struct video_device *vdev)
+{
+	struct unicam_node *node = video_get_drvdata(vdev);
+
+	unicam_put(node->dev);
+}
+
+static int unicam_set_default_format(struct unicam_device *unicam,
+				     struct unicam_node *node,
+				     int pad_id,
+				     const struct unicam_fmt **ret_fmt)
+{
+	struct v4l2_mbus_framefmt mbus_fmt = {0};
+	const struct unicam_fmt *fmt;
+	int ret;
+
+	if (pad_id == IMAGE_PAD) {
+		ret = __subdev_get_format(unicam, &mbus_fmt, pad_id);
+		if (ret) {
+			unicam_err(unicam, "Failed to get_format - ret %d\n",
+				   ret);
+			return ret;
+		}
+
+		fmt = find_format_by_code(mbus_fmt.code);
+		if (!fmt) {
+			/*
+			 * Find the first format that the sensor and unicam both
+			 * support
+			 */
+			fmt = get_first_supported_format(unicam);
+
+			if (fmt) {
+				mbus_fmt.code = fmt->code;
+				ret = __subdev_set_format(unicam, &mbus_fmt, pad_id);
+				if (ret)
+					return -EINVAL;
+			}
+		}
+		if (mbus_fmt.field != V4L2_FIELD_NONE) {
+			/* Interlaced not supported - disable it now. */
+			mbus_fmt.field = V4L2_FIELD_NONE;
+			ret = __subdev_set_format(unicam, &mbus_fmt, pad_id);
+			if (ret)
+				return -EINVAL;
+		}
+
+		if (fmt)
+			node->v_fmt.fmt.pix.pixelformat = fmt->fourcc ? fmt->fourcc
+						: fmt->repacked_fourcc;
+	} else {
+		/* Fix this node format as embedded data. */
+		fmt = find_format_by_code(MEDIA_BUS_FMT_SENSOR_DATA);
+		node->v_fmt.fmt.meta.dataformat = fmt->fourcc;
+	}
+
+	*ret_fmt = fmt;
+
+	return 0;
+}
+
+static void unicam_mc_set_default_format(struct unicam_node *node, int pad_id)
+{
+	if (pad_id == IMAGE_PAD) {
+		struct v4l2_pix_format *pix_fmt = &node->v_fmt.fmt.pix;
+
+		pix_fmt->width = 640;
+		pix_fmt->height = 480;
+		pix_fmt->field = V4L2_FIELD_NONE;
+		pix_fmt->colorspace = V4L2_COLORSPACE_SRGB;
+		pix_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601;
+		pix_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE;
+		pix_fmt->xfer_func = V4L2_XFER_FUNC_SRGB;
+		pix_fmt->pixelformat = formats[0].fourcc;
+		unicam_calc_format_size_bpl(node->dev, &formats[0],
+					    &node->v_fmt);
+		node->v_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+		node->fmt = &formats[0];
+	} else {
+		const struct unicam_fmt *fmt;
+
+		/* Fix this node format as embedded data. */
+		fmt = find_format_by_code(MEDIA_BUS_FMT_SENSOR_DATA);
+		node->v_fmt.fmt.meta.dataformat = fmt->fourcc;
+		node->fmt = fmt;
+
+		node->v_fmt.fmt.meta.buffersize = UNICAM_EMBEDDED_SIZE;
+		node->embedded_lines = 1;
+		node->v_fmt.type = V4L2_BUF_TYPE_META_CAPTURE;
+	}
+}
+
+static int register_node(struct unicam_device *unicam, struct unicam_node *node,
+			 enum v4l2_buf_type type, int pad_id)
+{
+	struct video_device *vdev;
+	struct vb2_queue *q;
+	int ret;
+
+	node->dev = unicam;
+	node->pad_id = pad_id;
+
+	if (!unicam->mc_api) {
+		const struct unicam_fmt *fmt;
+
+		ret = unicam_set_default_format(unicam, node, pad_id, &fmt);
+		if (ret)
+			return ret;
+		node->fmt = fmt;
+		/* Read current subdev format */
+		if (fmt)
+			unicam_reset_format(node);
+	} else {
+		unicam_mc_set_default_format(node, pad_id);
+	}
+
+	if (!unicam->mc_api &&
+	    v4l2_subdev_has_op(unicam->sensor, video, s_std)) {
+		v4l2_std_id tvnorms;
+
+		if (WARN_ON(!v4l2_subdev_has_op(unicam->sensor, video,
+						g_tvnorms)))
+			/*
+			 * Subdevice should not advertise s_std but not
+			 * g_tvnorms
+			 */
+			return -EINVAL;
+
+		ret = v4l2_subdev_call(unicam->sensor, video,
+				       g_tvnorms, &tvnorms);
+		if (WARN_ON(ret))
+			return -EINVAL;
+		node->video_dev.tvnorms |= tvnorms;
+	}
+
+	spin_lock_init(&node->dma_queue_lock);
+	mutex_init(&node->lock);
+
+	vdev = &node->video_dev;
+	if (pad_id == IMAGE_PAD) {
+		if (!unicam->mc_api) {
+			/* Add controls from the subdevice */
+			ret = v4l2_ctrl_add_handler(&unicam->ctrl_handler,
+						    unicam->sensor->ctrl_handler,
+						    NULL,
+						    true);
+			if (ret < 0)
+				return ret;
+		}
+
+		/*
+		 * If the sensor subdevice has any controls, associate the node
+		 *  with the ctrl handler to allow access from userland.
+		 */
+		if (!list_empty(&unicam->ctrl_handler.ctrls))
+			vdev->ctrl_handler = &unicam->ctrl_handler;
+	}
+
+	q = &node->buffer_queue;
+	q->type = type;
+	q->io_modes = VB2_MMAP | VB2_DMABUF | VB2_READ;
+	q->drv_priv = node;
+	q->ops = &unicam_video_qops;
+	q->mem_ops = &vb2_dma_contig_memops;
+	q->buf_struct_size = sizeof(struct unicam_buffer);
+	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	q->lock = &node->lock;
+	q->min_queued_buffers = 1;
+	q->dev = &unicam->pdev->dev;
+
+	ret = vb2_queue_init(q);
+	if (ret) {
+		unicam_err(unicam, "vb2_queue_init() failed\n");
+		return ret;
+	}
+
+	INIT_LIST_HEAD(&node->dma_queue);
+
+	vdev->release = unicam_node_release;
+	vdev->fops = &unicam_fops;
+	vdev->ioctl_ops = unicam->mc_api ? &unicam_mc_ioctl_ops :
+					   &unicam_ioctl_ops;
+	vdev->v4l2_dev = &unicam->v4l2_dev;
+	vdev->vfl_dir = VFL_DIR_RX;
+	vdev->queue = q;
+	vdev->lock = &node->lock;
+	vdev->device_caps = (pad_id == IMAGE_PAD) ?
+				V4L2_CAP_VIDEO_CAPTURE : V4L2_CAP_META_CAPTURE;
+	vdev->device_caps |= V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
+	if (unicam->mc_api) {
+		vdev->device_caps |= V4L2_CAP_IO_MC;
+		vdev->entity.ops = &unicam_mc_entity_ops;
+	}
+
+	/* Define the device names */
+	snprintf(vdev->name, sizeof(vdev->name), "%s-%s", UNICAM_MODULE_NAME,
+		 pad_id == IMAGE_PAD ? "image" : "embedded");
+
+	video_set_drvdata(vdev, node);
+	if (pad_id == IMAGE_PAD)
+		vdev->entity.flags |= MEDIA_ENT_FL_DEFAULT;
+	node->pad.flags = MEDIA_PAD_FL_SINK;
+	media_entity_pads_init(&vdev->entity, 1, &node->pad);
+
+	node->dummy_buf_cpu_addr = dma_alloc_coherent(&unicam->pdev->dev,
+						      DUMMY_BUF_SIZE,
+						      &node->dummy_buf_dma_addr,
+						      GFP_KERNEL);
+	if (!node->dummy_buf_cpu_addr) {
+		unicam_err(unicam, "Unable to allocate dummy buffer.\n");
+		return -ENOMEM;
+	}
+	if (!unicam->mc_api) {
+		if (pad_id == METADATA_PAD ||
+		    !v4l2_subdev_has_op(unicam->sensor, video, s_std)) {
+			v4l2_disable_ioctl(&node->video_dev, VIDIOC_S_STD);
+			v4l2_disable_ioctl(&node->video_dev, VIDIOC_G_STD);
+			v4l2_disable_ioctl(&node->video_dev, VIDIOC_ENUMSTD);
+		}
+		if (pad_id == METADATA_PAD ||
+		    !v4l2_subdev_has_op(unicam->sensor, video, querystd))
+			v4l2_disable_ioctl(&node->video_dev, VIDIOC_QUERYSTD);
+		if (pad_id == METADATA_PAD ||
+		    !v4l2_subdev_has_op(unicam->sensor, pad, s_dv_timings)) {
+			v4l2_disable_ioctl(&node->video_dev, VIDIOC_S_EDID);
+			v4l2_disable_ioctl(&node->video_dev, VIDIOC_G_EDID);
+			v4l2_disable_ioctl(&node->video_dev,
+					   VIDIOC_DV_TIMINGS_CAP);
+			v4l2_disable_ioctl(&node->video_dev,
+					   VIDIOC_G_DV_TIMINGS);
+			v4l2_disable_ioctl(&node->video_dev,
+					   VIDIOC_S_DV_TIMINGS);
+			v4l2_disable_ioctl(&node->video_dev,
+					   VIDIOC_ENUM_DV_TIMINGS);
+			v4l2_disable_ioctl(&node->video_dev,
+					   VIDIOC_QUERY_DV_TIMINGS);
+		}
+		if (pad_id == METADATA_PAD ||
+		    !v4l2_subdev_has_op(unicam->sensor, pad,
+					enum_frame_interval))
+			v4l2_disable_ioctl(&node->video_dev,
+					   VIDIOC_ENUM_FRAMEINTERVALS);
+		if (pad_id == METADATA_PAD ||
+		    !v4l2_subdev_has_op(unicam->sensor, pad,
+					get_frame_interval))
+			v4l2_disable_ioctl(&node->video_dev, VIDIOC_G_PARM);
+		if (pad_id == METADATA_PAD ||
+		    !v4l2_subdev_has_op(unicam->sensor, pad,
+					set_frame_interval))
+			v4l2_disable_ioctl(&node->video_dev, VIDIOC_S_PARM);
+
+		if (pad_id == METADATA_PAD ||
+		    !v4l2_subdev_has_op(unicam->sensor, pad,
+					enum_frame_size))
+			v4l2_disable_ioctl(&node->video_dev,
+					   VIDIOC_ENUM_FRAMESIZES);
+
+		if (node->pad_id == METADATA_PAD ||
+		    !v4l2_subdev_has_op(unicam->sensor, pad, set_selection))
+			v4l2_disable_ioctl(&node->video_dev,
+					   VIDIOC_S_SELECTION);
+
+		if (node->pad_id == METADATA_PAD ||
+		    !v4l2_subdev_has_op(unicam->sensor, pad, get_selection))
+			v4l2_disable_ioctl(&node->video_dev,
+					   VIDIOC_G_SELECTION);
+	}
+
+	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+	if (ret) {
+		unicam_err(unicam, "Unable to register video device %s\n",
+			   vdev->name);
+		return ret;
+	}
+
+	/*
+	 * Acquire a reference to unicam, which will be released when the video
+	 * device will be unregistered and userspace will have closed all open
+	 * file handles.
+	 */
+	unicam_get(unicam);
+	node->registered = true;
+
+	if (pad_id != METADATA_PAD || unicam->sensor_embedded_data) {
+		ret = media_create_pad_link(&unicam->sensor->entity,
+					    node->src_pad_id,
+					    &node->video_dev.entity, 0,
+					    MEDIA_LNK_FL_ENABLED |
+					    MEDIA_LNK_FL_IMMUTABLE);
+		if (ret)
+			unicam_err(unicam, "Unable to create pad link for %s\n",
+				   vdev->name);
+	}
+
+	return ret;
+}
+
+static void unregister_nodes(struct unicam_device *unicam)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(unicam->node); i++) {
+		struct unicam_node *node = &unicam->node[i];
+
+		if (node->dummy_buf_cpu_addr) {
+			dma_free_coherent(&unicam->pdev->dev, DUMMY_BUF_SIZE,
+					  node->dummy_buf_cpu_addr,
+					  node->dummy_buf_dma_addr);
+		}
+
+		if (node->registered) {
+			node->registered = false;
+			video_unregister_device(&node->video_dev);
+		}
+	}
+}
+
+static int unicam_async_complete(struct v4l2_async_notifier *notifier)
+{
+	static struct lock_class_key key;
+	struct unicam_device *unicam = to_unicam_device(notifier->v4l2_dev);
+	unsigned int i, source_pads = 0;
+	int ret;
+
+	unicam->v4l2_dev.notify = unicam_notify;
+
+	unicam->sensor_state = __v4l2_subdev_state_alloc(unicam->sensor,
+							 "unicam:async->lock", &key);
+	if (!unicam->sensor_state)
+		return -ENOMEM;
+
+	for (i = 0; i < unicam->sensor->entity.num_pads; i++) {
+		if (unicam->sensor->entity.pads[i].flags & MEDIA_PAD_FL_SOURCE) {
+			if (source_pads < MAX_NODES) {
+				unicam->node[source_pads].src_pad_id = i;
+				unicam_dbg(3, unicam, "source pad %u is index %u\n",
+					   source_pads, i);
+			}
+			source_pads++;
+		}
+	}
+	if (!source_pads) {
+		unicam_err(unicam, "No source pads on sensor.\n");
+		ret = -ENODEV;
+		goto unregister;
+	}
+
+	ret = register_node(unicam, &unicam->node[IMAGE_PAD],
+			    V4L2_BUF_TYPE_VIDEO_CAPTURE, IMAGE_PAD);
+	if (ret) {
+		unicam_err(unicam, "Unable to register image video device.\n");
+		goto unregister;
+	}
+
+	if (source_pads >= 2) {
+		unicam->sensor_embedded_data = true;
+
+		ret = register_node(unicam, &unicam->node[METADATA_PAD],
+				    V4L2_BUF_TYPE_META_CAPTURE, METADATA_PAD);
+		if (ret) {
+			unicam_err(unicam, "Unable to register metadata video device.\n");
+			goto unregister;
+		}
+	}
+
+	if (unicam->mc_api)
+		ret = v4l2_device_register_subdev_nodes(&unicam->v4l2_dev);
+	else
+		ret = v4l2_device_register_ro_subdev_nodes(&unicam->v4l2_dev);
+	if (ret) {
+		unicam_err(unicam, "Unable to register subdev nodes.\n");
+		goto unregister;
+	}
+
+	/*
+	 * Release the initial reference, all references are now owned by the
+	 * video devices.
+	 */
+	unicam_put(unicam);
+	return 0;
+
+unregister:
+	unregister_nodes(unicam);
+	unicam_put(unicam);
+
+	return ret;
+}
+
+static const struct v4l2_async_notifier_operations unicam_async_ops = {
+	.bound = unicam_async_bound,
+	.complete = unicam_async_complete,
+};
+
+static int of_unicam_connect_subdevs(struct unicam_device *dev)
+{
+	struct platform_device *pdev = dev->pdev;
+	struct v4l2_fwnode_endpoint ep = { };
+	struct device_node *ep_node;
+	struct device_node *sensor_node;
+	unsigned int lane;
+	int ret = -EINVAL;
+
+	if (of_property_read_u32(pdev->dev.of_node, "brcm,num-data-lanes",
+				 &dev->max_data_lanes) < 0) {
+		unicam_err(dev, "number of data lanes not set\n");
+		return -EINVAL;
+	}
+
+	/* Get the local endpoint and remote device. */
+	ep_node = of_graph_get_next_endpoint(pdev->dev.of_node, NULL);
+	if (!ep_node) {
+		unicam_dbg(3, dev, "can't get next endpoint\n");
+		return -EINVAL;
+	}
+
+	unicam_dbg(3, dev, "ep_node is %pOF\n", ep_node);
+
+	sensor_node = of_graph_get_remote_port_parent(ep_node);
+	if (!sensor_node) {
+		unicam_dbg(3, dev, "can't get remote parent\n");
+		goto cleanup_exit;
+	}
+
+	unicam_dbg(1, dev, "found subdevice %pOF\n", sensor_node);
+
+	/* Parse the local endpoint and validate its configuration. */
+	v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep_node), &ep);
+
+	unicam_dbg(3, dev, "parsed local endpoint, bus_type %u\n",
+		   ep.bus_type);
+
+	dev->bus_type = ep.bus_type;
+
+	switch (ep.bus_type) {
+	case V4L2_MBUS_CSI2_DPHY:
+		switch (ep.bus.mipi_csi2.num_data_lanes) {
+		case 1:
+		case 2:
+		case 4:
+			break;
+
+		default:
+			unicam_err(dev, "subdevice %pOF: %u data lanes not supported\n",
+				   sensor_node,
+				   ep.bus.mipi_csi2.num_data_lanes);
+			goto cleanup_exit;
+		}
+
+		for (lane = 0; lane < ep.bus.mipi_csi2.num_data_lanes; lane++) {
+			if (ep.bus.mipi_csi2.data_lanes[lane] != lane + 1) {
+				unicam_err(dev, "subdevice %pOF: data lanes reordering not supported\n",
+					   sensor_node);
+				goto cleanup_exit;
+			}
+		}
+
+		if (ep.bus.mipi_csi2.num_data_lanes > dev->max_data_lanes) {
+			unicam_err(dev, "subdevice requires %u data lanes when %u are supported\n",
+				   ep.bus.mipi_csi2.num_data_lanes,
+				   dev->max_data_lanes);
+		}
+
+		dev->max_data_lanes = ep.bus.mipi_csi2.num_data_lanes;
+		dev->bus_flags = ep.bus.mipi_csi2.flags;
+
+		break;
+
+	case V4L2_MBUS_CCP2:
+		if (ep.bus.mipi_csi1.clock_lane != 0 ||
+		    ep.bus.mipi_csi1.data_lane != 1) {
+			unicam_err(dev, "subdevice %pOF: unsupported lanes configuration\n",
+				   sensor_node);
+			goto cleanup_exit;
+		}
+
+		dev->max_data_lanes = 1;
+		dev->bus_flags = ep.bus.mipi_csi1.strobe;
+		break;
+
+	default:
+		/* Unsupported bus type */
+		unicam_err(dev, "subdevice %pOF: unsupported bus type %u\n",
+			   sensor_node, ep.bus_type);
+		goto cleanup_exit;
+	}
+
+	unicam_dbg(3, dev, "subdevice %pOF: %s bus, %u data lanes, flags=0x%08x\n",
+		   sensor_node,
+		   dev->bus_type == V4L2_MBUS_CSI2_DPHY ? "CSI-2" : "CCP2",
+		   dev->max_data_lanes, dev->bus_flags);
+
+	/* Initialize and register the async notifier. */
+	v4l2_async_nf_init(&dev->notifier, &dev->v4l2_dev);
+	dev->notifier.ops = &unicam_async_ops;
+
+	dev->asd = v4l2_async_nf_add_fwnode(&dev->notifier,
+					    of_fwnode_handle(sensor_node),
+					    struct v4l2_async_connection);
+	if (IS_ERR(dev->asd)) {
+		unicam_err(dev, "Error adding subdevice: %d\n", ret);
+		goto cleanup_exit;
+	}
+
+	ret = v4l2_async_nf_register(&dev->notifier);
+	if (ret) {
+		unicam_err(dev, "Error registering async notifier: %d\n", ret);
+		ret = -EINVAL;
+	}
+
+cleanup_exit:
+	of_node_put(sensor_node);
+	of_node_put(ep_node);
+
+	return ret;
+}
+
+static int unicam_probe(struct platform_device *pdev)
+{
+	struct unicam_device *unicam;
+	int ret;
+
+	unicam = kzalloc(sizeof(*unicam), GFP_KERNEL);
+	if (!unicam)
+		return -ENOMEM;
+
+	kref_init(&unicam->kref);
+	unicam->pdev = pdev;
+
+	/*
+	 * Adopt the current setting of the module parameter, and check if
+	 * device tree requests it.
+	 */
+	unicam->mc_api = media_controller;
+
+	/* Compatible is for the non-legacy version of the driver - use compatible */
+	if (of_device_get_match_data(&unicam->pdev->dev))
+		unicam->mc_api = true;
+
+	if (of_property_read_bool(pdev->dev.of_node, "brcm,media-controller"))
+		unicam->mc_api = true;
+
+	unicam->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(unicam->base)) {
+		unicam_err(unicam, "Failed to get main io block\n");
+		ret = PTR_ERR(unicam->base);
+		goto err_unicam_put;
+	}
+
+	unicam->clk_gate_base = devm_platform_ioremap_resource(pdev, 1);
+	if (IS_ERR(unicam->clk_gate_base)) {
+		unicam_err(unicam, "Failed to get 2nd io block\n");
+		ret = PTR_ERR(unicam->clk_gate_base);
+		goto err_unicam_put;
+	}
+
+	unicam->clock = devm_clk_get(&pdev->dev, "lp");
+	if (IS_ERR(unicam->clock)) {
+		unicam_err(unicam, "Failed to get lp clock\n");
+		ret = PTR_ERR(unicam->clock);
+		goto err_unicam_put;
+	}
+
+	unicam->vpu_clock = devm_clk_get(&pdev->dev, "vpu");
+	if (IS_ERR(unicam->vpu_clock)) {
+		unicam_err(unicam, "Failed to get vpu clock\n");
+		ret = PTR_ERR(unicam->vpu_clock);
+		goto err_unicam_put;
+	}
+
+	unicam->sync_gpio = devm_gpiod_get_optional(&pdev->dev, "sync",
+						    GPIOD_OUT_LOW);
+
+	ret = platform_get_irq(pdev, 0);
+	if (ret <= 0) {
+		dev_err(&pdev->dev, "No IRQ resource\n");
+		ret = -EINVAL;
+		goto err_unicam_put;
+	}
+
+	ret = devm_request_irq(&pdev->dev, ret, unicam_isr, 0,
+			       "unicam_capture0", unicam);
+	if (ret) {
+		dev_err(&pdev->dev, "Unable to request interrupt\n");
+		ret = -EINVAL;
+		goto err_unicam_put;
+	}
+
+	unicam->mdev.dev = &pdev->dev;
+	strscpy(unicam->mdev.model, UNICAM_MODULE_NAME,
+		sizeof(unicam->mdev.model));
+	strscpy(unicam->mdev.serial, "", sizeof(unicam->mdev.serial));
+	snprintf(unicam->mdev.bus_info, sizeof(unicam->mdev.bus_info),
+		 "platform:%s", dev_name(&pdev->dev));
+	unicam->mdev.hw_revision = 0;
+
+	media_device_init(&unicam->mdev);
+
+	unicam->v4l2_dev.mdev = &unicam->mdev;
+
+	ret = v4l2_device_register(&pdev->dev, &unicam->v4l2_dev);
+	if (ret) {
+		unicam_err(unicam,
+			   "Unable to register v4l2 device.\n");
+		goto err_unicam_put;
+	}
+
+	ret = media_device_register(&unicam->mdev);
+	if (ret < 0) {
+		unicam_err(unicam,
+			   "Unable to register media-controller device.\n");
+		goto err_v4l2_unregister;
+	}
+
+	/* Reserve space for the controls */
+	ret = v4l2_ctrl_handler_init(&unicam->ctrl_handler, 16);
+	if (ret < 0)
+		goto err_media_unregister;
+
+	/* set the driver data in platform device */
+	platform_set_drvdata(pdev, unicam);
+
+	ret = of_unicam_connect_subdevs(unicam);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to connect subdevs\n");
+		goto err_media_unregister;
+	}
+
+	/* Enable the block power domain */
+	pm_runtime_enable(&pdev->dev);
+
+	return 0;
+
+err_media_unregister:
+	media_device_unregister(&unicam->mdev);
+err_v4l2_unregister:
+	v4l2_device_unregister(&unicam->v4l2_dev);
+err_unicam_put:
+	unicam_put(unicam);
+
+	return ret;
+}
+
+static void unicam_remove(struct platform_device *pdev)
+{
+	struct unicam_device *unicam = platform_get_drvdata(pdev);
+
+	unicam_dbg(2, unicam, "%s\n", __func__);
+
+	v4l2_async_nf_unregister(&unicam->notifier);
+	v4l2_device_unregister(&unicam->v4l2_dev);
+	media_device_unregister(&unicam->mdev);
+	unregister_nodes(unicam);
+
+	pm_runtime_disable(&pdev->dev);
+}
+
+static const struct of_device_id unicam_of_match[] = {
+	{ .compatible = "brcm,bcm2835-unicam", .data = (void *)1 },
+	{ .compatible = "brcm,bcm2835-unicam-legacy", .data = 0 },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, unicam_of_match);
+
+static struct platform_driver unicam_driver = {
+	.probe		= unicam_probe,
+	.remove		= unicam_remove,
+	.driver = {
+		.name	= UNICAM_MODULE_NAME,
+		.of_match_table = of_match_ptr(unicam_of_match),
+	},
+};
+
+module_platform_driver(unicam_driver);
+
+MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>");
+MODULE_DESCRIPTION("BCM2835 Unicam driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(UNICAM_VERSION);
diff --git a/drivers/media/platform/bcm2835/vc4-regs-unicam.h b/drivers/media/platform/bcm2835/vc4-regs-unicam.h
new file mode 100644
index 000000000000..ae059a171d0f
--- /dev/null
+++ b/drivers/media/platform/bcm2835/vc4-regs-unicam.h
@@ -0,0 +1,253 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+/*
+ * Copyright (C) 2017-2020 Raspberry Pi Trading.
+ * Dave Stevenson <dave.stevenson@raspberrypi.com>
+ */
+
+#ifndef VC4_REGS_UNICAM_H
+#define VC4_REGS_UNICAM_H
+
+/*
+ * The following values are taken from files found within the code drop
+ * made by Broadcom for the BCM21553 Graphics Driver, predominantly in
+ * brcm_usrlib/dag/vmcsx/vcinclude/hardware_vc4.h.
+ * They have been modified to be only the register offset.
+ */
+#define UNICAM_CTRL	0x000
+#define UNICAM_STA	0x004
+#define UNICAM_ANA	0x008
+#define UNICAM_PRI	0x00c
+#define UNICAM_CLK	0x010
+#define UNICAM_CLT	0x014
+#define UNICAM_DAT0	0x018
+#define UNICAM_DAT1	0x01c
+#define UNICAM_DAT2	0x020
+#define UNICAM_DAT3	0x024
+#define UNICAM_DLT	0x028
+#define UNICAM_CMP0	0x02c
+#define UNICAM_CMP1	0x030
+#define UNICAM_CAP0	0x034
+#define UNICAM_CAP1	0x038
+#define UNICAM_ICTL	0x100
+#define UNICAM_ISTA	0x104
+#define UNICAM_IDI0	0x108
+#define UNICAM_IPIPE	0x10c
+#define UNICAM_IBSA0	0x110
+#define UNICAM_IBEA0	0x114
+#define UNICAM_IBLS	0x118
+#define UNICAM_IBWP	0x11c
+#define UNICAM_IHWIN	0x120
+#define UNICAM_IHSTA	0x124
+#define UNICAM_IVWIN	0x128
+#define UNICAM_IVSTA	0x12c
+#define UNICAM_ICC	0x130
+#define UNICAM_ICS	0x134
+#define UNICAM_IDC	0x138
+#define UNICAM_IDPO	0x13c
+#define UNICAM_IDCA	0x140
+#define UNICAM_IDCD	0x144
+#define UNICAM_IDS	0x148
+#define UNICAM_DCS	0x200
+#define UNICAM_DBSA0	0x204
+#define UNICAM_DBEA0	0x208
+#define UNICAM_DBWP	0x20c
+#define UNICAM_DBCTL	0x300
+#define UNICAM_IBSA1	0x304
+#define UNICAM_IBEA1	0x308
+#define UNICAM_IDI1	0x30c
+#define UNICAM_DBSA1	0x310
+#define UNICAM_DBEA1	0x314
+#define UNICAM_MISC	0x400
+
+/*
+ * The following bitmasks are from the kernel released by Broadcom
+ * for Android - https://android.googlesource.com/kernel/bcm/
+ * The Rhea, Hawaii, and Java chips all contain the same VideoCore4
+ * Unicam block as BCM2835, as defined in eg
+ * arch/arm/mach-rhea/include/mach/rdb_A0/brcm_rdb_cam.h and similar.
+ * Values reworked to use the kernel BIT and GENMASK macros.
+ *
+ * Some of the bit mnenomics have been amended to match the datasheet.
+ */
+/* UNICAM_CTRL Register */
+#define UNICAM_CPE		BIT(0)
+#define UNICAM_MEM		BIT(1)
+#define UNICAM_CPR		BIT(2)
+#define UNICAM_CPM_MASK		GENMASK(3, 3)
+#define UNICAM_CPM_CSI2		0
+#define UNICAM_CPM_CCP2		1
+#define UNICAM_SOE		BIT(4)
+#define UNICAM_DCM_MASK		GENMASK(5, 5)
+#define UNICAM_DCM_STROBE	0
+#define UNICAM_DCM_DATA		1
+#define UNICAM_SLS		BIT(6)
+#define UNICAM_PFT_MASK		GENMASK(11, 8)
+#define UNICAM_OET_MASK		GENMASK(20, 12)
+
+/* UNICAM_STA Register */
+#define UNICAM_SYN		BIT(0)
+#define UNICAM_CS		BIT(1)
+#define UNICAM_SBE		BIT(2)
+#define UNICAM_PBE		BIT(3)
+#define UNICAM_HOE		BIT(4)
+#define UNICAM_PLE		BIT(5)
+#define UNICAM_SSC		BIT(6)
+#define UNICAM_CRCE		BIT(7)
+#define UNICAM_OES		BIT(8)
+#define UNICAM_IFO		BIT(9)
+#define UNICAM_OFO		BIT(10)
+#define UNICAM_BFO		BIT(11)
+#define UNICAM_DL		BIT(12)
+#define UNICAM_PS		BIT(13)
+#define UNICAM_IS		BIT(14)
+#define UNICAM_PI0		BIT(15)
+#define UNICAM_PI1		BIT(16)
+#define UNICAM_FSI_S		BIT(17)
+#define UNICAM_FEI_S		BIT(18)
+#define UNICAM_LCI_S		BIT(19)
+#define UNICAM_BUF0_RDY		BIT(20)
+#define UNICAM_BUF0_NO		BIT(21)
+#define UNICAM_BUF1_RDY		BIT(22)
+#define UNICAM_BUF1_NO		BIT(23)
+#define UNICAM_DI		BIT(24)
+
+#define UNICAM_STA_MASK_ALL \
+		(UNICAM_DL + \
+		UNICAM_SBE + \
+		UNICAM_PBE + \
+		UNICAM_HOE + \
+		UNICAM_PLE + \
+		UNICAM_SSC + \
+		UNICAM_CRCE + \
+		UNICAM_IFO + \
+		UNICAM_OFO + \
+		UNICAM_PS + \
+		UNICAM_PI0 + \
+		UNICAM_PI1)
+
+/* UNICAM_ANA Register */
+#define UNICAM_APD		BIT(0)
+#define UNICAM_BPD		BIT(1)
+#define UNICAM_AR		BIT(2)
+#define UNICAM_DDL		BIT(3)
+#define UNICAM_CTATADJ_MASK	GENMASK(7, 4)
+#define UNICAM_PTATADJ_MASK	GENMASK(11, 8)
+
+/* UNICAM_PRI Register */
+#define UNICAM_PE		BIT(0)
+#define UNICAM_PT_MASK		GENMASK(2, 1)
+#define UNICAM_NP_MASK		GENMASK(7, 4)
+#define UNICAM_PP_MASK		GENMASK(11, 8)
+#define UNICAM_BS_MASK		GENMASK(15, 12)
+#define UNICAM_BL_MASK		GENMASK(17, 16)
+
+/* UNICAM_CLK Register */
+#define UNICAM_CLE		BIT(0)
+#define UNICAM_CLPD		BIT(1)
+#define UNICAM_CLLPE		BIT(2)
+#define UNICAM_CLHSE		BIT(3)
+#define UNICAM_CLTRE		BIT(4)
+#define UNICAM_CLAC_MASK	GENMASK(8, 5)
+#define UNICAM_CLSTE		BIT(29)
+
+/* UNICAM_CLT Register */
+#define UNICAM_CLT1_MASK	GENMASK(7, 0)
+#define UNICAM_CLT2_MASK	GENMASK(15, 8)
+
+/* UNICAM_DATn Registers */
+#define UNICAM_DLE		BIT(0)
+#define UNICAM_DLPD		BIT(1)
+#define UNICAM_DLLPE		BIT(2)
+#define UNICAM_DLHSE		BIT(3)
+#define UNICAM_DLTRE		BIT(4)
+#define UNICAM_DLSM		BIT(5)
+#define UNICAM_DLFO		BIT(28)
+#define UNICAM_DLSTE		BIT(29)
+
+#define UNICAM_DAT_MASK_ALL (UNICAM_DLSTE + UNICAM_DLFO)
+
+/* UNICAM_DLT Register */
+#define UNICAM_DLT1_MASK	GENMASK(7, 0)
+#define UNICAM_DLT2_MASK	GENMASK(15, 8)
+#define UNICAM_DLT3_MASK	GENMASK(23, 16)
+
+/* UNICAM_ICTL Register */
+#define UNICAM_FSIE		BIT(0)
+#define UNICAM_FEIE		BIT(1)
+#define UNICAM_IBOB		BIT(2)
+#define UNICAM_FCM		BIT(3)
+#define UNICAM_TFC		BIT(4)
+#define UNICAM_LIP_MASK		GENMASK(6, 5)
+#define UNICAM_LCIE_MASK	GENMASK(28, 16)
+
+/* UNICAM_IDI0/1 Register */
+#define UNICAM_ID0_MASK		GENMASK(7, 0)
+#define UNICAM_ID1_MASK		GENMASK(15, 8)
+#define UNICAM_ID2_MASK		GENMASK(23, 16)
+#define UNICAM_ID3_MASK		GENMASK(31, 24)
+
+/* UNICAM_ISTA Register */
+#define UNICAM_FSI		BIT(0)
+#define UNICAM_FEI		BIT(1)
+#define UNICAM_LCI		BIT(2)
+
+#define UNICAM_ISTA_MASK_ALL (UNICAM_FSI + UNICAM_FEI + UNICAM_LCI)
+
+/* UNICAM_IPIPE Register */
+#define UNICAM_PUM_MASK		GENMASK(2, 0)
+		/* Unpacking modes */
+		#define UNICAM_PUM_NONE		0
+		#define UNICAM_PUM_UNPACK6	1
+		#define UNICAM_PUM_UNPACK7	2
+		#define UNICAM_PUM_UNPACK8	3
+		#define UNICAM_PUM_UNPACK10	4
+		#define UNICAM_PUM_UNPACK12	5
+		#define UNICAM_PUM_UNPACK14	6
+		#define UNICAM_PUM_UNPACK16	7
+#define UNICAM_DDM_MASK		GENMASK(6, 3)
+#define UNICAM_PPM_MASK		GENMASK(9, 7)
+		/* Packing modes */
+		#define UNICAM_PPM_NONE		0
+		#define UNICAM_PPM_PACK8	1
+		#define UNICAM_PPM_PACK10	2
+		#define UNICAM_PPM_PACK12	3
+		#define UNICAM_PPM_PACK14	4
+		#define UNICAM_PPM_PACK16	5
+#define UNICAM_DEM_MASK		GENMASK(11, 10)
+#define UNICAM_DEBL_MASK	GENMASK(14, 12)
+#define UNICAM_ICM_MASK		GENMASK(16, 15)
+#define UNICAM_IDM_MASK		GENMASK(17, 17)
+
+/* UNICAM_ICC Register */
+#define UNICAM_ICFL_MASK	GENMASK(4, 0)
+#define UNICAM_ICFH_MASK	GENMASK(9, 5)
+#define UNICAM_ICST_MASK	GENMASK(12, 10)
+#define UNICAM_ICLT_MASK	GENMASK(15, 13)
+#define UNICAM_ICLL_MASK	GENMASK(31, 16)
+
+/* UNICAM_DCS Register */
+#define UNICAM_DIE		BIT(0)
+#define UNICAM_DIM		BIT(1)
+#define UNICAM_DBOB		BIT(3)
+#define UNICAM_FDE		BIT(4)
+#define UNICAM_LDP		BIT(5)
+#define UNICAM_EDL_MASK		GENMASK(15, 8)
+
+/* UNICAM_DBCTL Register */
+#define UNICAM_DBEN		BIT(0)
+#define UNICAM_BUF0_IE		BIT(1)
+#define UNICAM_BUF1_IE		BIT(2)
+
+/* UNICAM_CMP[0,1] register */
+#define UNICAM_PCE		BIT(31)
+#define UNICAM_GI		BIT(9)
+#define UNICAM_CPH		BIT(8)
+#define UNICAM_PCVC_MASK	GENMASK(7, 6)
+#define UNICAM_PCDT_MASK	GENMASK(5, 0)
+
+/* UNICAM_MISC register */
+#define UNICAM_FL0		BIT(6)
+#define UNICAM_FL1		BIT(9)
+
+#endif
diff --git a/drivers/media/platform/broadcom/bcm2835-unicam.c b/drivers/media/platform/broadcom/bcm2835-unicam.c
index 9f81e1582a30..b273bf624850 100644
--- a/drivers/media/platform/broadcom/bcm2835-unicam.c
+++ b/drivers/media/platform/broadcom/bcm2835-unicam.c
@@ -2711,7 +2711,7 @@ static void unicam_remove(struct platform_device *pdev)
 }
 
 static const struct of_device_id unicam_of_match[] = {
-	{ .compatible = "brcm,bcm2835-unicam", },
+	{ .compatible = "brcm,bcm2835-unicam-upstream", },
 	{ /* sentinel */ },
 };
 MODULE_DEVICE_TABLE(of, unicam_of_match);
diff --git a/drivers/media/platform/raspberrypi/Kconfig b/drivers/media/platform/raspberrypi/Kconfig
index e928f979019e..c90fd591d723 100644
--- a/drivers/media/platform/raspberrypi/Kconfig
+++ b/drivers/media/platform/raspberrypi/Kconfig
@@ -2,4 +2,6 @@
 
 comment "Raspberry Pi media platform drivers"
 
+source "drivers/media/platform/raspberrypi/hevc_dec/Kconfig"
 source "drivers/media/platform/raspberrypi/pisp_be/Kconfig"
+source "drivers/media/platform/raspberrypi/rp1_cfe/Kconfig"
diff --git a/drivers/media/platform/raspberrypi/Makefile b/drivers/media/platform/raspberrypi/Makefile
index c0d1a2dab486..b7fe9aa25c10 100644
--- a/drivers/media/platform/raspberrypi/Makefile
+++ b/drivers/media/platform/raspberrypi/Makefile
@@ -1,3 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
 
+obj-y += hevc_dec/
 obj-y += pisp_be/
+obj-y += rp1_cfe/
diff --git a/drivers/media/platform/raspberrypi/hevc_dec/Kconfig b/drivers/media/platform/raspberrypi/hevc_dec/Kconfig
new file mode 100644
index 000000000000..ae1fd079e5c9
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/hevc_dec/Kconfig
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config VIDEO_RPI_HEVC_DEC
+	tristate "Rasperry Pi HEVC decoder"
+	depends on VIDEO_DEV && VIDEO_DEV
+	depends on OF
+	select MEDIA_CONTROLLER
+	select MEDIA_CONTROLLER_REQUEST_API
+	select VIDEOBUF2_DMA_CONTIG
+	select V4L2_MEM2MEM_DEV
+	help
+	  Support for the Raspberry Pi HEVC / H265 H/W decoder as a stateless
+	  V4L2 decoder device.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called rpi-hevc-dec.
+
diff --git a/drivers/media/platform/raspberrypi/hevc_dec/Makefile b/drivers/media/platform/raspberrypi/hevc_dec/Makefile
new file mode 100644
index 000000000000..b6506bf2e00d
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/hevc_dec/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_VIDEO_RPI_HEVC_DEC) += rpi-hevc-dec.o
+
+rpi-hevc-dec-y = hevc_d.o hevc_d_video.o hevc_d_hw.o\
+		 hevc_d_h265.o
diff --git a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.c b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.c
new file mode 100644
index 000000000000..dee978882f82
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.c
@@ -0,0 +1,459 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Raspberry Pi HEVC driver
+ *
+ * Copyright (C) 2024 Raspberry Pi Ltd
+ *
+ * Based on the Cedrus VPU driver, that is:
+ *
+ * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com>
+ * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ * Copyright (C) 2018 Bootlin
+ */
+
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/of.h>
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-mem2mem.h>
+
+#include "hevc_d.h"
+#include "hevc_d_h265.h"
+#include "hevc_d_video.h"
+#include "hevc_d_hw.h"
+
+/*
+ * Default /dev/videoN node number.
+ * Deliberately avoid the very low numbers as these are often taken by webcams
+ * etc, and simple apps tend to only go for /dev/video0.
+ */
+static int video_nr = 19;
+module_param(video_nr, int, 0644);
+MODULE_PARM_DESC(video_nr, "decoder video device number");
+
+static const struct hevc_d_control hevc_d_ctrls[] = {
+	{
+		.cfg = {
+			.id	= V4L2_CID_STATELESS_HEVC_SPS,
+			.ops	= &hevc_d_hevc_sps_ctrl_ops,
+		},
+		.required	= false,
+	}, {
+		.cfg = {
+			.id	= V4L2_CID_STATELESS_HEVC_PPS,
+			.ops	= &hevc_d_hevc_pps_ctrl_ops,
+		},
+		.required	= false,
+	}, {
+		.cfg = {
+			.id = V4L2_CID_STATELESS_HEVC_SCALING_MATRIX,
+		},
+		.required	= false,
+	}, {
+		.cfg = {
+			.id	= V4L2_CID_STATELESS_HEVC_DECODE_PARAMS,
+		},
+		.required	= true,
+	}, {
+		.cfg = {
+			.name	= "Slice param array",
+			.id	= V4L2_CID_STATELESS_HEVC_SLICE_PARAMS,
+			.type	= V4L2_CTRL_TYPE_HEVC_SLICE_PARAMS,
+			.flags	= V4L2_CTRL_FLAG_DYNAMIC_ARRAY,
+			.dims	= { 0x1000 },
+		},
+		.required	= true,
+	}, {
+		.cfg = {
+			.id	= V4L2_CID_STATELESS_HEVC_DECODE_MODE,
+			.min	= V4L2_STATELESS_HEVC_DECODE_MODE_FRAME_BASED,
+			.max	= V4L2_STATELESS_HEVC_DECODE_MODE_FRAME_BASED,
+			.def	= V4L2_STATELESS_HEVC_DECODE_MODE_FRAME_BASED,
+		},
+		.required	= false,
+	}, {
+		.cfg = {
+			.id	= V4L2_CID_STATELESS_HEVC_START_CODE,
+			.min	= V4L2_STATELESS_HEVC_START_CODE_NONE,
+			.max	= V4L2_STATELESS_HEVC_START_CODE_ANNEX_B,
+			.def	= V4L2_STATELESS_HEVC_START_CODE_NONE,
+		},
+		.required	= false,
+	},
+};
+
+#define HEVC_D_CTRLS_COUNT	ARRAY_SIZE(hevc_d_ctrls)
+
+struct v4l2_ctrl *hevc_d_find_ctrl(struct hevc_d_ctx *ctx, u32 id)
+{
+	unsigned int i;
+
+	for (i = 0; i < HEVC_D_CTRLS_COUNT; i++)
+		if (ctx->ctrls[i]->id == id)
+			return ctx->ctrls[i];
+
+	return NULL;
+}
+
+void *hevc_d_find_control_data(struct hevc_d_ctx *ctx, u32 id)
+{
+	struct v4l2_ctrl *const ctrl = hevc_d_find_ctrl(ctx, id);
+
+	return !ctrl ? NULL : ctrl->p_cur.p;
+}
+
+static int hevc_d_init_ctrls(struct hevc_d_dev *dev, struct hevc_d_ctx *ctx)
+{
+	struct v4l2_ctrl_handler *hdl = &ctx->hdl;
+	struct v4l2_ctrl *ctrl;
+	unsigned int i;
+
+	v4l2_ctrl_handler_init(hdl, HEVC_D_CTRLS_COUNT);
+	if (hdl->error) {
+		v4l2_err(&dev->v4l2_dev,
+			 "Failed to initialize control handler\n");
+		return hdl->error;
+	}
+
+	ctx->ctrls = kzalloc(HEVC_D_CTRLS_COUNT * sizeof(ctrl), GFP_KERNEL);
+	if (!ctx->ctrls)
+		return -ENOMEM;
+
+	for (i = 0; i < HEVC_D_CTRLS_COUNT; i++) {
+		ctrl = v4l2_ctrl_new_custom(hdl, &hevc_d_ctrls[i].cfg,
+					    ctx);
+		if (hdl->error) {
+			v4l2_err(&dev->v4l2_dev,
+				 "Failed to create new custom control id=%#x\n",
+				 hevc_d_ctrls[i].cfg.id);
+
+			v4l2_ctrl_handler_free(hdl);
+			kfree(ctx->ctrls);
+			return hdl->error;
+		}
+
+		ctx->ctrls[i] = ctrl;
+	}
+
+	ctx->fh.ctrl_handler = hdl;
+	v4l2_ctrl_handler_setup(hdl);
+
+	return 0;
+}
+
+static int hevc_d_request_validate(struct media_request *req)
+{
+	struct media_request_object *obj;
+	struct v4l2_ctrl_handler *parent_hdl, *hdl;
+	struct hevc_d_ctx *ctx = NULL;
+	struct v4l2_ctrl *ctrl_test;
+	unsigned int count;
+	unsigned int i;
+
+	list_for_each_entry(obj, &req->objects, list) {
+		struct vb2_buffer *vb;
+
+		if (vb2_request_object_is_buffer(obj)) {
+			vb = container_of(obj, struct vb2_buffer, req_obj);
+			ctx = vb2_get_drv_priv(vb->vb2_queue);
+
+			break;
+		}
+	}
+
+	if (!ctx)
+		return -ENOENT;
+
+	count = vb2_request_buffer_cnt(req);
+	if (!count) {
+		v4l2_info(&ctx->dev->v4l2_dev,
+			  "No buffer was provided with the request\n");
+		return -ENOENT;
+	} else if (count > 1) {
+		v4l2_info(&ctx->dev->v4l2_dev,
+			  "More than one buffer was provided with the request\n");
+		return -EINVAL;
+	}
+
+	parent_hdl = &ctx->hdl;
+
+	hdl = v4l2_ctrl_request_hdl_find(req, parent_hdl);
+	if (!hdl) {
+		v4l2_info(&ctx->dev->v4l2_dev, "Missing codec control(s)\n");
+		return -ENOENT;
+	}
+
+	for (i = 0; i < HEVC_D_CTRLS_COUNT; i++) {
+		if (!hevc_d_ctrls[i].required)
+			continue;
+
+		ctrl_test =
+			v4l2_ctrl_request_hdl_ctrl_find(hdl,
+							hevc_d_ctrls[i].cfg.id);
+		if (!ctrl_test) {
+			v4l2_info(&ctx->dev->v4l2_dev,
+				  "Missing required codec control %d: id=%#x\n",
+				  i, hevc_d_ctrls[i].cfg.id);
+			v4l2_ctrl_request_hdl_put(hdl);
+			return -ENOENT;
+		}
+	}
+
+	v4l2_ctrl_request_hdl_put(hdl);
+
+	return vb2_request_validate(req);
+}
+
+static int hevc_d_open(struct file *file)
+{
+	struct hevc_d_dev *dev = video_drvdata(file);
+	struct hevc_d_ctx *ctx = NULL;
+	int ret;
+
+	if (mutex_lock_interruptible(&dev->dev_mutex))
+		return -ERESTARTSYS;
+
+	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+	if (!ctx) {
+		mutex_unlock(&dev->dev_mutex);
+		ret = -ENOMEM;
+		goto err_unlock;
+	}
+
+	mutex_init(&ctx->ctx_mutex);
+
+	v4l2_fh_init(&ctx->fh, video_devdata(file));
+	file->private_data = &ctx->fh;
+	ctx->dev = dev;
+
+	ret = hevc_d_init_ctrls(dev, ctx);
+	if (ret)
+		goto err_free;
+
+	ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx,
+					    &hevc_d_queue_init);
+	if (IS_ERR(ctx->fh.m2m_ctx)) {
+		ret = PTR_ERR(ctx->fh.m2m_ctx);
+		goto err_ctrls;
+	}
+
+	/* The only bit of format info that we can guess now is H265 src
+	 * Everything else we need more info for
+	 */
+	hevc_d_prepare_src_format(&ctx->src_fmt);
+
+	v4l2_fh_add(&ctx->fh);
+
+	mutex_unlock(&dev->dev_mutex);
+
+	return 0;
+
+err_ctrls:
+	v4l2_ctrl_handler_free(&ctx->hdl);
+	kfree(ctx->ctrls);
+err_free:
+	mutex_destroy(&ctx->ctx_mutex);
+	kfree(ctx);
+err_unlock:
+	mutex_unlock(&dev->dev_mutex);
+
+	return ret;
+}
+
+static int hevc_d_release(struct file *file)
+{
+	struct hevc_d_dev *dev = video_drvdata(file);
+	struct hevc_d_ctx *ctx = container_of(file->private_data,
+					      struct hevc_d_ctx, fh);
+
+	mutex_lock(&dev->dev_mutex);
+
+	v4l2_fh_del(&ctx->fh);
+	v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
+
+	v4l2_ctrl_handler_free(&ctx->hdl);
+	kfree(ctx->ctrls);
+
+	v4l2_fh_exit(&ctx->fh);
+	mutex_destroy(&ctx->ctx_mutex);
+
+	kfree(ctx);
+
+	mutex_unlock(&dev->dev_mutex);
+
+	return 0;
+}
+
+static void hevc_d_media_req_queue(struct media_request *req)
+{
+	media_request_mark_manual_completion(req);
+	v4l2_m2m_request_queue(req);
+}
+
+static const struct v4l2_file_operations hevc_d_fops = {
+	.owner		= THIS_MODULE,
+	.open		= hevc_d_open,
+	.release	= hevc_d_release,
+	.poll		= v4l2_m2m_fop_poll,
+	.unlocked_ioctl	= video_ioctl2,
+	.mmap		= v4l2_m2m_fop_mmap,
+};
+
+static const struct video_device hevc_d_video_device = {
+	.name		= HEVC_D_NAME,
+	.vfl_dir	= VFL_DIR_M2M,
+	.fops		= &hevc_d_fops,
+	.ioctl_ops	= &hevc_d_ioctl_ops,
+	.minor		= -1,
+	.release	= video_device_release_empty,
+	.device_caps	= V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING,
+};
+
+static const struct v4l2_m2m_ops hevc_d_m2m_ops = {
+	.device_run	= hevc_d_device_run,
+};
+
+static const struct media_device_ops hevc_d_m2m_media_ops = {
+	.req_validate	= hevc_d_request_validate,
+	.req_queue	= hevc_d_media_req_queue,
+};
+
+static int hevc_d_probe(struct platform_device *pdev)
+{
+	struct hevc_d_dev *dev;
+	struct video_device *vfd;
+	int ret;
+
+	dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	dev->vfd = hevc_d_video_device;
+	dev->dev = &pdev->dev;
+	dev->pdev = pdev;
+
+	ret = 0;
+	ret = hevc_d_hw_probe(dev);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to probe hardware - %d\n", ret);
+		return ret;
+	}
+
+	mutex_init(&dev->dev_mutex);
+
+	ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to register V4L2 device\n");
+		return ret;
+	}
+
+	vfd = &dev->vfd;
+	vfd->lock = &dev->dev_mutex;
+	vfd->v4l2_dev = &dev->v4l2_dev;
+
+	snprintf(vfd->name, sizeof(vfd->name), "%s", hevc_d_video_device.name);
+	video_set_drvdata(vfd, dev);
+
+	ret = dma_set_mask_and_coherent(dev->dev, DMA_BIT_MASK(36));
+	if (ret) {
+		v4l2_err(&dev->v4l2_dev,
+			 "Failed dma_set_mask_and_coherent\n");
+		goto err_v4l2;
+	}
+
+	dev->m2m_dev = v4l2_m2m_init(&hevc_d_m2m_ops);
+	if (IS_ERR(dev->m2m_dev)) {
+		v4l2_err(&dev->v4l2_dev,
+			 "Failed to initialize V4L2 M2M device\n");
+		ret = PTR_ERR(dev->m2m_dev);
+
+		goto err_v4l2;
+	}
+
+	dev->mdev.dev = &pdev->dev;
+	strscpy(dev->mdev.model, HEVC_D_NAME, sizeof(dev->mdev.model));
+	strscpy(dev->mdev.bus_info, "platform:" HEVC_D_NAME,
+		sizeof(dev->mdev.bus_info));
+
+	media_device_init(&dev->mdev);
+	dev->mdev.ops = &hevc_d_m2m_media_ops;
+	dev->v4l2_dev.mdev = &dev->mdev;
+
+	ret = video_register_device(vfd, VFL_TYPE_VIDEO, video_nr);
+	if (ret) {
+		v4l2_err(&dev->v4l2_dev, "Failed to register video device\n");
+		goto err_m2m;
+	}
+
+	v4l2_info(&dev->v4l2_dev,
+		  "Device registered as /dev/video%d\n", vfd->num);
+
+	ret = v4l2_m2m_register_media_controller(dev->m2m_dev, vfd,
+						 MEDIA_ENT_F_PROC_VIDEO_DECODER);
+	if (ret) {
+		v4l2_err(&dev->v4l2_dev,
+			 "Failed to initialize V4L2 M2M media controller\n");
+		goto err_video;
+	}
+
+	ret = media_device_register(&dev->mdev);
+	if (ret) {
+		v4l2_err(&dev->v4l2_dev, "Failed to register media device\n");
+		goto err_m2m_mc;
+	}
+
+	platform_set_drvdata(pdev, dev);
+
+	return 0;
+
+err_m2m_mc:
+	v4l2_m2m_unregister_media_controller(dev->m2m_dev);
+err_video:
+	video_unregister_device(&dev->vfd);
+err_m2m:
+	v4l2_m2m_release(dev->m2m_dev);
+err_v4l2:
+	v4l2_device_unregister(&dev->v4l2_dev);
+
+	return ret;
+}
+
+static void hevc_d_remove(struct platform_device *pdev)
+{
+	struct hevc_d_dev *dev = platform_get_drvdata(pdev);
+
+	if (media_devnode_is_registered(dev->mdev.devnode)) {
+		media_device_unregister(&dev->mdev);
+		v4l2_m2m_unregister_media_controller(dev->m2m_dev);
+		media_device_cleanup(&dev->mdev);
+	}
+
+	v4l2_m2m_release(dev->m2m_dev);
+	video_unregister_device(&dev->vfd);
+	v4l2_device_unregister(&dev->v4l2_dev);
+
+	hevc_d_hw_remove(dev);
+}
+
+static const struct of_device_id hevc_d_dt_match[] = {
+	{ .compatible = "raspberrypi,hevc-dec", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, hevc_d_dt_match);
+
+static struct platform_driver hevc_d_driver = {
+	.probe		= hevc_d_probe,
+	.remove		= hevc_d_remove,
+	.driver		= {
+		.name = HEVC_D_NAME,
+		.of_match_table	= of_match_ptr(hevc_d_dt_match),
+	},
+};
+module_platform_driver(hevc_d_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("John Cox <john.cox@raspberrypi.com>");
+MODULE_DESCRIPTION("Raspberry Pi HEVC V4L2 driver");
diff --git a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.h b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.h
new file mode 100644
index 000000000000..37c2d1612d2a
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d.h
@@ -0,0 +1,189 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Raspberry Pi HEVC driver
+ *
+ * Copyright (C) 2024 Raspberry Pi Ltd
+ *
+ * Based on the Cedrus VPU driver, that is:
+ *
+ * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com>
+ * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ * Copyright (C) 2018 Bootlin
+ */
+
+#ifndef _HEVC_D_H_
+#define _HEVC_D_H_
+
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-dma-contig.h>
+
+#define HEVC_D_DEC_ENV_COUNT 6
+#define HEVC_D_P1BUF_COUNT 3
+#define HEVC_D_P2BUF_COUNT 3
+
+#define HEVC_D_NAME			"rpi-hevc-dec"
+
+#define HEVC_D_CAPABILITY_UNTILED	BIT(0)
+#define HEVC_D_CAPABILITY_H265_DEC	BIT(1)
+
+#define HEVC_D_QUIRK_NO_DMA_OFFSET	BIT(0)
+
+enum hevc_d_irq_status {
+	HEVC_D_IRQ_NONE,
+	HEVC_D_IRQ_ERROR,
+	HEVC_D_IRQ_OK,
+};
+
+struct hevc_d_control {
+	struct v4l2_ctrl_config cfg;
+	unsigned char		required:1;
+};
+
+struct hevc_d_h265_run {
+	u32 slice_ents;
+	const struct v4l2_ctrl_hevc_sps			*sps;
+	const struct v4l2_ctrl_hevc_pps			*pps;
+	const struct v4l2_ctrl_hevc_decode_params	*dec;
+	const struct v4l2_ctrl_hevc_slice_params	*slice_params;
+	const struct v4l2_ctrl_hevc_scaling_matrix	*scaling_matrix;
+};
+
+struct hevc_d_run {
+	struct vb2_v4l2_buffer	*src;
+	struct vb2_v4l2_buffer	*dst;
+
+	struct hevc_d_h265_run	h265;
+};
+
+struct hevc_d_buffer {
+	struct v4l2_m2m_buffer          m2m_buf;
+};
+
+struct hevc_d_dec_state;
+struct hevc_d_dec_env;
+
+struct hevc_d_gptr {
+	size_t size;
+	__u8 *ptr;
+	dma_addr_t addr;
+	unsigned long attrs;
+};
+
+struct hevc_d_dev;
+typedef void (*hevc_d_irq_callback)(struct hevc_d_dev *dev, void *ctx);
+
+struct hevc_d_q_aux;
+#define HEVC_D_AUX_ENT_COUNT VB2_MAX_FRAME
+
+struct hevc_d_ctx {
+	struct v4l2_fh			fh;
+	struct hevc_d_dev		*dev;
+
+	struct v4l2_pix_format_mplane	src_fmt;
+	struct v4l2_pix_format_mplane	dst_fmt;
+	int dst_fmt_set;
+
+	int				src_stream_on;
+	int				dst_stream_on;
+
+	/*
+	 * fatal_err is set if an error has occurred s.t. decode cannot
+	 * continue (such as running out of CMA)
+	 */
+	int fatal_err;
+
+	/* Lock for queue operations */
+	struct mutex			ctx_mutex;
+
+	struct v4l2_ctrl_handler	hdl;
+	struct v4l2_ctrl		**ctrls;
+
+	/*
+	 * state contains stuff that is only needed in phase0
+	 * it could be held in dec_env but that would be wasteful
+	 */
+	struct hevc_d_dec_state *state;
+	struct hevc_d_dec_env *dec0;
+
+	/* Spinlock protecting dec_free */
+	spinlock_t dec_lock;
+	struct hevc_d_dec_env *dec_free;
+
+	struct hevc_d_dec_env *dec_pool;
+
+	unsigned int p1idx;
+	atomic_t p1out;
+
+	unsigned int p2idx;
+	struct hevc_d_gptr pu_bufs[HEVC_D_P2BUF_COUNT];
+	struct hevc_d_gptr coeff_bufs[HEVC_D_P2BUF_COUNT];
+
+	/* Spinlock protecting aux_free */
+	spinlock_t aux_lock;
+	struct hevc_d_q_aux *aux_free;
+
+	struct hevc_d_q_aux *aux_ents[HEVC_D_AUX_ENT_COUNT];
+
+	unsigned int colmv_stride;
+	unsigned int colmv_picsize;
+};
+
+struct hevc_d_variant {
+	unsigned int	capabilities;
+	unsigned int	quirks;
+	unsigned int	mod_rate;
+};
+
+struct hevc_d_hw_irq_ent;
+
+#define HEVC_D_ICTL_ENABLE_UNLIMITED (-1)
+
+struct hevc_d_hw_irq_ctrl {
+	/* Spinlock protecting claim and tail */
+	spinlock_t lock;
+	struct hevc_d_hw_irq_ent *claim;
+	struct hevc_d_hw_irq_ent *tail;
+
+	/* Ent for pending irq - also prevents sched */
+	struct hevc_d_hw_irq_ent *irq;
+	/* Non-zero => do not start a new job - outer layer sched pending */
+	int no_sched;
+	/* Enable count. -1 always OK, 0 do not sched, +ve shed & count down */
+	int enable;
+	/* Thread CB requested */
+	bool thread_reqed;
+};
+
+struct hevc_d_dev {
+	struct v4l2_device	v4l2_dev;
+	struct video_device	vfd;
+	struct media_device	mdev;
+	struct media_pad	pad[2];
+	struct platform_device	*pdev;
+	struct device		*dev;
+	struct v4l2_m2m_dev	*m2m_dev;
+
+	/* Device file mutex */
+	struct mutex		dev_mutex;
+
+	void __iomem		*base_irq;
+	void __iomem		*base_h265;
+
+	struct clk		*clock;
+	unsigned long		max_clock_rate;
+
+	int			cache_align;
+
+	struct hevc_d_hw_irq_ctrl ic_active1;
+	struct hevc_d_hw_irq_ctrl ic_active2;
+};
+
+struct v4l2_ctrl *hevc_d_find_ctrl(struct hevc_d_ctx *ctx, u32 id);
+void *hevc_d_find_control_data(struct hevc_d_ctx *ctx, u32 id);
+
+#endif
diff --git a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.c b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.c
new file mode 100644
index 000000000000..6c9a9d6db15a
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.c
@@ -0,0 +1,2585 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Raspberry Pi HEVC driver
+ *
+ * Copyright (C) 2020 Raspberry Pi Ltd
+ *
+ * Based on the Cedrus VPU driver, that is:
+ *
+ * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com>
+ * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ * Copyright (C) 2018 Bootlin
+ */
+
+#include <linux/delay.h>
+#include <linux/types.h>
+
+#include <media/videobuf2-dma-contig.h>
+
+#include "hevc_d.h"
+#include "hevc_d_h265.h"
+#include "hevc_d_hw.h"
+#include "hevc_d_video.h"
+
+enum hevc_slice_type {
+	HEVC_SLICE_B = 0,
+	HEVC_SLICE_P = 1,
+	HEVC_SLICE_I = 2,
+};
+
+enum hevc_layer { L0 = 0, L1 = 1 };
+
+static int gptr_alloc(struct hevc_d_dev *const dev, struct hevc_d_gptr *gptr,
+		      size_t size, unsigned long attrs)
+{
+	gptr->size = size;
+	gptr->attrs = attrs;
+	gptr->addr = 0;
+	gptr->ptr = dma_alloc_attrs(dev->dev, gptr->size, &gptr->addr,
+				    GFP_KERNEL, gptr->attrs);
+	return !gptr->ptr ? -ENOMEM : 0;
+}
+
+static void gptr_free(struct hevc_d_dev *const dev,
+		      struct hevc_d_gptr *const gptr)
+{
+	if (gptr->ptr)
+		dma_free_attrs(dev->dev, gptr->size, gptr->ptr, gptr->addr,
+			       gptr->attrs);
+	gptr->size = 0;
+	gptr->ptr = NULL;
+	gptr->addr = 0;
+	gptr->attrs = 0;
+}
+
+/* Realloc but do not copy
+ *
+ * Frees then allocs.
+ * If the alloc fails then it attempts to re-allocote the old size
+ * On error then check gptr->ptr to determine if anything is currently
+ * allocated.
+ */
+static int gptr_realloc_new(struct hevc_d_dev * const dev,
+			    struct hevc_d_gptr * const gptr, size_t size)
+{
+	const size_t old_size = gptr->size;
+
+	if (size == gptr->size)
+		return 0;
+
+	if (gptr->ptr)
+		dma_free_attrs(dev->dev, gptr->size, gptr->ptr,
+			       gptr->addr, gptr->attrs);
+
+	gptr->addr = 0;
+	gptr->size = size;
+	gptr->ptr = dma_alloc_attrs(dev->dev, gptr->size,
+				    &gptr->addr, GFP_KERNEL, gptr->attrs);
+
+	if (!gptr->ptr) {
+		gptr->addr = 0;
+		gptr->size = old_size;
+		gptr->ptr = dma_alloc_attrs(dev->dev, gptr->size,
+					    &gptr->addr, GFP_KERNEL, gptr->attrs);
+		if (!gptr->ptr) {
+			gptr->size = 0;
+			gptr->addr = 0;
+			gptr->attrs = 0;
+		}
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+static size_t next_size(const size_t x)
+{
+	return hevc_d_round_up_size(x + 1);
+}
+
+#define NUM_SCALING_FACTORS 4064 /* Not a typo = 0xbe0 + 0x400 */
+
+#define AXI_BASE64 0
+
+#define PROB_BACKUP ((20 << 12) + (20 << 6) + (0 << 0))
+#define PROB_RELOAD ((20 << 12) + (20 << 0) + (0 << 6))
+
+#define HEVC_MAX_REFS V4L2_HEVC_DPB_ENTRIES_NUM_MAX
+
+struct rpi_cmd {
+	u32 addr;
+	u32 data;
+} __packed;
+
+struct hevc_d_q_aux {
+	unsigned int refcount;
+	unsigned int q_index;
+	struct hevc_d_q_aux *next;
+	struct hevc_d_gptr col;
+};
+
+enum hevc_d_decode_state {
+	HEVC_D_DECODE_SLICE_START,
+	HEVC_D_DECODE_ERROR_DONE,
+	HEVC_D_DECODE_PHASE1,
+	HEVC_D_DECODE_END,
+};
+
+struct hevc_d_dec_env {
+	struct hevc_d_ctx *ctx;
+	struct hevc_d_dec_env *next;
+
+	enum hevc_d_decode_state state;
+	unsigned int decode_order;
+	int p1_status;		/* P1 status - what to realloc */
+
+	struct rpi_cmd *cmd_fifo;
+	unsigned int cmd_len, cmd_max;
+	unsigned int num_slice_msgs;
+	unsigned int pic_width_in_ctbs_y;
+	unsigned int pic_height_in_ctbs_y;
+	unsigned int dpbno_col;
+	u32 reg_slicestart;
+	int collocated_from_l0_flag;
+	/*
+	 * Last CTB/Tile X,Y processed by (wpp_)entry_point
+	 * Could be in _state as P0 only but needs updating where _state
+	 * is const
+	 */
+	unsigned int entry_ctb_x;
+	unsigned int entry_ctb_y;
+	unsigned int entry_tile_x;
+	unsigned int entry_tile_y;
+	unsigned int entry_qp;
+	u32 entry_slice;
+
+	u32 rpi_config2;
+	u32 rpi_framesize;
+	u32 rpi_currpoc;
+
+	struct vb2_v4l2_buffer *frame_buf;
+	struct vb2_v4l2_buffer *src_buf;
+	dma_addr_t frame_luma_addr;
+	unsigned int luma_stride;
+	dma_addr_t frame_chroma_addr;
+	unsigned int chroma_stride;
+	dma_addr_t ref_addrs[16][2];
+	struct hevc_d_q_aux *frame_aux;
+	struct hevc_d_q_aux *col_aux;
+
+	dma_addr_t cmd_addr;
+	size_t cmd_size;
+
+	dma_addr_t pu_base_vc;
+	dma_addr_t coeff_base_vc;
+	u32 pu_stride;
+	u32 coeff_stride;
+
+#define SLICE_MSGS_MAX (2 * HEVC_MAX_REFS * 8 + 3)
+	u16 slice_msgs[SLICE_MSGS_MAX];
+	u8 scaling_factors[NUM_SCALING_FACTORS];
+
+	struct media_request *req_pin;
+	struct hevc_d_hw_irq_ent irq_ent;
+};
+
+struct hevc_d_dec_state {
+	struct v4l2_ctrl_hevc_sps sps;
+	struct v4l2_ctrl_hevc_pps pps;
+
+	/* Helper vars & tables derived from sps/pps */
+	unsigned int log2_ctb_size;     /* log2 width of a CTB */
+	unsigned int ctb_width;         /* Width in CTBs */
+	unsigned int ctb_height;        /* Height in CTBs */
+	unsigned int ctb_size;          /* Pic area in CTBs */
+	unsigned int tile_width;        /* Width in tiles */
+	unsigned int tile_height;       /* Height in tiles */
+
+	int *col_bd;
+	int *row_bd;
+	int *ctb_addr_rs_to_ts;
+	int *ctb_addr_ts_to_rs;
+
+	/* Aux starage for DPB */
+	struct hevc_d_q_aux *ref_aux[HEVC_MAX_REFS];
+	struct hevc_d_q_aux *frame_aux;
+
+	/* Slice vars */
+	unsigned int slice_idx;
+	bool slice_temporal_mvp;  /* Slice flag but constant for frame */
+	bool use_aux;
+	bool mk_aux;
+
+	/* Temp vars per run - don't actually need to persist */
+	dma_addr_t src_addr;
+	const struct v4l2_ctrl_hevc_slice_params *sh;
+	const struct v4l2_ctrl_hevc_decode_params *dec;
+	unsigned int nb_refs[2];
+	unsigned int slice_qp;
+	unsigned int max_num_merge_cand; // 0 if I-slice
+	bool dependent_slice_segment_flag;
+
+	unsigned int start_ts;          /* slice_segment_addr -> ts */
+	unsigned int start_ctb_x;       /* CTB X,Y of start_ts */
+	unsigned int start_ctb_y;
+	unsigned int prev_ctb_x;        /* CTB X,Y of start_ts - 1 */
+	unsigned int prev_ctb_y;
+};
+
+static inline int clip_int(const int x, const int lo, const int hi)
+{
+	return x < lo ? lo : x > hi ? hi : x;
+}
+
+/* Phase 1 command and bit FIFOs */
+static int cmds_check_space(struct hevc_d_dec_env *const de, unsigned int n)
+{
+	struct rpi_cmd *a;
+	unsigned int newmax;
+
+	if (n > 0x100000) {
+		v4l2_err(&de->ctx->dev->v4l2_dev,
+			 "%s: n %u implausible\n", __func__, n);
+		return -ENOMEM;
+	}
+
+	if (de->cmd_len + n <= de->cmd_max)
+		return 0;
+
+	newmax = roundup_pow_of_two(de->cmd_len + n);
+
+	a = krealloc(de->cmd_fifo, newmax * sizeof(struct rpi_cmd),
+		     GFP_KERNEL);
+	if (!a) {
+		v4l2_err(&de->ctx->dev->v4l2_dev,
+			 "Failed cmd buffer realloc from %u to %u\n",
+			 de->cmd_max, newmax);
+		return -ENOMEM;
+	}
+	v4l2_info(&de->ctx->dev->v4l2_dev,
+		  "cmd buffer realloc from %u to %u\n", de->cmd_max, newmax);
+
+	de->cmd_fifo = a;
+	de->cmd_max = newmax;
+	return 0;
+}
+
+// ???? u16 addr - put in u32
+static void p1_apb_write(struct hevc_d_dec_env *const de, const u16 addr,
+			 const u32 data)
+{
+	if (de->cmd_len >= de->cmd_max) {
+		v4l2_err(&de->ctx->dev->v4l2_dev,
+			 "%s: Overflow @ %d\n", __func__, de->cmd_len);
+		return;
+	}
+
+	de->cmd_fifo[de->cmd_len].addr = addr;
+	de->cmd_fifo[de->cmd_len].data = data;
+
+	de->cmd_len++;
+}
+
+static int ctb_to_tile(unsigned int ctb, unsigned int *bd, int num)
+{
+	int i;
+
+	for (i = 1; ctb >= bd[i]; i++)
+		; /* bd[] has num+1 elements; bd[0]=0; */
+
+	return i - 1;
+}
+
+static unsigned int ctb_to_tile_x(const struct hevc_d_dec_state *const s,
+				  const unsigned int ctb_x)
+{
+	return ctb_to_tile(ctb_x, s->col_bd, s->tile_width);
+}
+
+static unsigned int ctb_to_tile_y(const struct hevc_d_dec_state *const s,
+				  const unsigned int ctb_y)
+{
+	return ctb_to_tile(ctb_y, s->row_bd, s->tile_height);
+}
+
+static void aux_q_free(struct hevc_d_ctx *const ctx,
+		       struct hevc_d_q_aux *const aq)
+{
+	struct hevc_d_dev *const dev = ctx->dev;
+
+	gptr_free(dev, &aq->col);
+	kfree(aq);
+}
+
+static struct hevc_d_q_aux *aux_q_alloc(struct hevc_d_ctx *const ctx,
+					const unsigned int q_index)
+{
+	struct hevc_d_dev *const dev = ctx->dev;
+	struct hevc_d_q_aux *const aq = kzalloc(sizeof(*aq), GFP_KERNEL);
+
+	if (!aq)
+		return NULL;
+
+	if (gptr_alloc(dev, &aq->col, ctx->colmv_picsize,
+		       DMA_ATTR_FORCE_CONTIGUOUS | DMA_ATTR_NO_KERNEL_MAPPING))
+		goto fail;
+
+	/*
+	 * Spinlock not required as called in P0 only and
+	 * aux checks done by _new
+	 */
+	aq->refcount = 1;
+	aq->q_index = q_index;
+	ctx->aux_ents[q_index] = aq;
+	return aq;
+
+fail:
+	kfree(aq);
+	return NULL;
+}
+
+static struct hevc_d_q_aux *aux_q_new(struct hevc_d_ctx *const ctx,
+				      const unsigned int q_index)
+{
+	struct hevc_d_q_aux *aq;
+	unsigned long lockflags;
+
+	spin_lock_irqsave(&ctx->aux_lock, lockflags);
+	/*
+	 * If we already have this allocated to a slot then use that
+	 * and assume that it will all work itself out in the pipeline
+	 */
+	aq = ctx->aux_ents[q_index];
+	if (aq) {
+		++aq->refcount;
+	} else {
+		aq = ctx->aux_free;
+		if (aq) {
+			ctx->aux_free = aq->next;
+			aq->next = NULL;
+			aq->refcount = 1;
+			aq->q_index = q_index;
+			ctx->aux_ents[q_index] = aq;
+		}
+	}
+	spin_unlock_irqrestore(&ctx->aux_lock, lockflags);
+
+	if (!aq)
+		aq = aux_q_alloc(ctx, q_index);
+
+	return aq;
+}
+
+static struct hevc_d_q_aux *aux_q_ref_idx(struct hevc_d_ctx *const ctx,
+					  const int q_index)
+{
+	unsigned long lockflags;
+	struct hevc_d_q_aux *aq;
+
+	spin_lock_irqsave(&ctx->aux_lock, lockflags);
+	aq = ctx->aux_ents[q_index];
+	if (aq)
+		++aq->refcount;
+	spin_unlock_irqrestore(&ctx->aux_lock, lockflags);
+
+	return aq;
+}
+
+static struct hevc_d_q_aux *aux_q_ref(struct hevc_d_ctx *const ctx,
+				      struct hevc_d_q_aux *const aq)
+{
+	unsigned long lockflags;
+
+	if (aq) {
+		spin_lock_irqsave(&ctx->aux_lock, lockflags);
+		++aq->refcount;
+		spin_unlock_irqrestore(&ctx->aux_lock, lockflags);
+	}
+	return aq;
+}
+
+static void aux_q_release(struct hevc_d_ctx *const ctx,
+			  struct hevc_d_q_aux **const paq)
+{
+	struct hevc_d_q_aux *const aq = *paq;
+	unsigned long lockflags;
+
+	if (!aq)
+		return;
+
+	*paq = NULL;
+
+	spin_lock_irqsave(&ctx->aux_lock, lockflags);
+	if (--aq->refcount == 0) {
+		aq->next = ctx->aux_free;
+		ctx->aux_free = aq;
+		ctx->aux_ents[aq->q_index] = NULL;
+		aq->q_index = ~0U;
+	}
+	spin_unlock_irqrestore(&ctx->aux_lock, lockflags);
+}
+
+static void aux_q_init(struct hevc_d_ctx *const ctx)
+{
+	spin_lock_init(&ctx->aux_lock);
+	ctx->aux_free = NULL;
+}
+
+static void aux_q_uninit(struct hevc_d_ctx *const ctx)
+{
+	struct hevc_d_q_aux *aq;
+
+	ctx->colmv_picsize = 0;
+	ctx->colmv_stride = 0;
+	while ((aq = ctx->aux_free) != NULL) {
+		ctx->aux_free = aq->next;
+		aux_q_free(ctx, aq);
+	}
+}
+
+/*
+ * Initialisation process for context variables (CABAC init)
+ * see H.265 9.3.2.2
+ *
+ * N.B. If comparing with FFmpeg note that this h/w uses slightly different
+ * offsets to FFmpegs array
+ */
+
+/* Actual number of values */
+#define RPI_PROB_VALS 154U
+/* Rounded up as we copy words */
+#define RPI_PROB_ARRAY_SIZE ((154 + 3) & ~3)
+
+/* Initialiser values - see tables H.265 9-4 through 9-42 */
+static const u8 prob_init[3][156] = {
+	{
+		153, 200, 139, 141, 157, 154, 154, 154, 154, 154, 184, 154, 154,
+		154, 184, 63,  154, 154, 154, 154, 154, 154, 154, 154, 154, 154,
+		154, 154, 154, 153, 138, 138, 111, 141, 94,  138, 182, 154, 154,
+		154, 140, 92,  137, 138, 140, 152, 138, 139, 153, 74,  149, 92,
+		139, 107, 122, 152, 140, 179, 166, 182, 140, 227, 122, 197, 110,
+		110, 124, 125, 140, 153, 125, 127, 140, 109, 111, 143, 127, 111,
+		79,  108, 123, 63,  110, 110, 124, 125, 140, 153, 125, 127, 140,
+		109, 111, 143, 127, 111, 79,  108, 123, 63,  91,  171, 134, 141,
+		138, 153, 136, 167, 152, 152, 139, 139, 111, 111, 125, 110, 110,
+		94,  124, 108, 124, 107, 125, 141, 179, 153, 125, 107, 125, 141,
+		179, 153, 125, 107, 125, 141, 179, 153, 125, 140, 139, 182, 182,
+		152, 136, 152, 136, 153, 136, 139, 111, 136, 139, 111, 0,   0,
+	},
+	{
+		153, 185, 107, 139, 126, 197, 185, 201, 154, 149, 154, 139, 154,
+		154, 154, 152, 110, 122, 95,  79,  63,  31,  31,  153, 153, 168,
+		140, 198, 79,  124, 138, 94,  153, 111, 149, 107, 167, 154, 154,
+		154, 154, 196, 196, 167, 154, 152, 167, 182, 182, 134, 149, 136,
+		153, 121, 136, 137, 169, 194, 166, 167, 154, 167, 137, 182, 125,
+		110, 94,  110, 95,  79,  125, 111, 110, 78,  110, 111, 111, 95,
+		94,  108, 123, 108, 125, 110, 94,  110, 95,  79,  125, 111, 110,
+		78,  110, 111, 111, 95,  94,  108, 123, 108, 121, 140, 61,  154,
+		107, 167, 91,  122, 107, 167, 139, 139, 155, 154, 139, 153, 139,
+		123, 123, 63,  153, 166, 183, 140, 136, 153, 154, 166, 183, 140,
+		136, 153, 154, 166, 183, 140, 136, 153, 154, 170, 153, 123, 123,
+		107, 121, 107, 121, 167, 151, 183, 140, 151, 183, 140, 0,   0,
+	},
+	{
+		153, 160, 107, 139, 126, 197, 185, 201, 154, 134, 154, 139, 154,
+		154, 183, 152, 154, 137, 95,  79,  63,  31,  31,  153, 153, 168,
+		169, 198, 79,  224, 167, 122, 153, 111, 149, 92,  167, 154, 154,
+		154, 154, 196, 167, 167, 154, 152, 167, 182, 182, 134, 149, 136,
+		153, 121, 136, 122, 169, 208, 166, 167, 154, 152, 167, 182, 125,
+		110, 124, 110, 95,  94,  125, 111, 111, 79,  125, 126, 111, 111,
+		79,  108, 123, 93,  125, 110, 124, 110, 95,  94,  125, 111, 111,
+		79,  125, 126, 111, 111, 79,  108, 123, 93,  121, 140, 61,  154,
+		107, 167, 91,  107, 107, 167, 139, 139, 170, 154, 139, 153, 139,
+		123, 123, 63,  124, 166, 183, 140, 136, 153, 154, 166, 183, 140,
+		136, 153, 154, 166, 183, 140, 136, 153, 154, 170, 153, 138, 138,
+		122, 121, 122, 121, 167, 151, 183, 140, 151, 183, 140, 0,   0,
+	},
+};
+
+#define CMDS_WRITE_PROB ((RPI_PROB_ARRAY_SIZE / 4) + 1)
+
+static void write_prob(struct hevc_d_dec_env *const de,
+		       const struct hevc_d_dec_state *const s)
+{
+	const unsigned int init_type =
+		((s->sh->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_CABAC_INIT) != 0 &&
+		 s->sh->slice_type != HEVC_SLICE_I) ?
+			s->sh->slice_type + 1 :
+			2 - s->sh->slice_type;
+	const int q = clip_int(s->slice_qp, 0, 51);
+	const u8 *p = prob_init[init_type];
+	u8 dst[RPI_PROB_ARRAY_SIZE];
+	unsigned int i;
+
+	for (i = 0; i < RPI_PROB_VALS; i++) {
+		int init_value = p[i];
+		int m = (init_value >> 4) * 5 - 45;
+		int n = ((init_value & 15) << 3) - 16;
+		int pre = 2 * (((m * q) >> 4) + n) - 127;
+
+		pre ^= pre >> 31;
+		if (pre > 124)
+			pre = 124 + (pre & 1);
+		dst[i] = pre;
+	}
+	for (i = RPI_PROB_VALS; i != RPI_PROB_ARRAY_SIZE; ++i)
+		dst[i] = 0;
+
+	for (i = 0; i < RPI_PROB_ARRAY_SIZE; i += 4)
+		p1_apb_write(de, 0x1000 + i,
+			     dst[i] + (dst[i + 1] << 8) + (dst[i + 2] << 16) +
+				     (dst[i + 3] << 24));
+
+	/*
+	 * Having written the prob array back it up
+	 * This is not always needed but is a small overhead that simplifies
+	 * (and speeds up) some multi-tile & WPP scenarios
+	 * There are no scenarios where having written a prob we ever want
+	 * a previous (non-initial) state back
+	 */
+	p1_apb_write(de, RPI_TRANSFER, PROB_BACKUP);
+}
+
+#define CMDS_WRITE_SCALING_FACTORS NUM_SCALING_FACTORS
+static void write_scaling_factors(struct hevc_d_dec_env *const de)
+{
+	const u8 *p = (u8 *)de->scaling_factors;
+	int i;
+
+	for (i = 0; i < NUM_SCALING_FACTORS; i += 4, p += 4)
+		p1_apb_write(de, 0x2000 + i,
+			     p[0] + (p[1] << 8) + (p[2] << 16) + (p[3] << 24));
+}
+
+static inline __u32 dma_to_axi_addr(dma_addr_t a)
+{
+	return (__u32)(a >> 6);
+}
+
+#define CMDS_WRITE_BITSTREAM 4
+static int write_bitstream(struct hevc_d_dec_env *const de,
+			   const struct hevc_d_dec_state *const s)
+{
+	// FIXME!!!!
+	// Note that FFmpeg V4L2 does not remove emulation prevention bytes,
+	// so this is matched in the configuration here.
+	// Whether that is the correct behaviour or not is not clear in the
+	// spec.
+	const int rpi_use_emu = 1;
+	unsigned int offset = s->sh->data_byte_offset;
+	const unsigned int len = (s->sh->bit_size + 7) / 8 - offset;
+	dma_addr_t addr = s->src_addr + offset;
+
+	offset = addr & 63;
+
+	p1_apb_write(de, RPI_BFBASE, dma_to_axi_addr(addr));
+	p1_apb_write(de, RPI_BFNUM, len);
+	p1_apb_write(de, RPI_BFCONTROL, offset + (1 << 7)); // Stop
+	p1_apb_write(de, RPI_BFCONTROL, offset + (rpi_use_emu << 6));
+	return 0;
+}
+
+/*
+ * The slice constant part of the slice register - width and height need to
+ * be ORed in later as they are per-tile / WPP-row
+ */
+static u32 slice_reg_const(const struct hevc_d_dec_state *const s)
+{
+	u32 x = (s->max_num_merge_cand << 0) |
+		(s->nb_refs[L0] << 4) |
+		(s->nb_refs[L1] << 8) |
+		(s->sh->slice_type << 12);
+
+	if (s->sh->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_SAO_LUMA)
+		x |= BIT(14);
+	if (s->sh->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_SAO_CHROMA)
+		x |= BIT(15);
+	if (s->sh->slice_type == HEVC_SLICE_B &&
+	    (s->sh->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_MVD_L1_ZERO))
+		x |= BIT(16);
+
+	return x;
+}
+
+#define CMDS_NEW_SLICE_SEGMENT (4 + CMDS_WRITE_SCALING_FACTORS)
+
+static void new_slice_segment(struct hevc_d_dec_env *const de,
+			      const struct hevc_d_dec_state *const s)
+{
+	const struct v4l2_ctrl_hevc_sps *const sps = &s->sps;
+	const struct v4l2_ctrl_hevc_pps *const pps = &s->pps;
+
+	p1_apb_write(de,
+		     RPI_SPS0,
+		     ((sps->log2_min_luma_coding_block_size_minus3 + 3) << 0) |
+		     (s->log2_ctb_size << 4) |
+		     ((sps->log2_min_luma_transform_block_size_minus2 + 2)
+							<< 8) |
+		     ((sps->log2_min_luma_transform_block_size_minus2 + 2 +
+		       sps->log2_diff_max_min_luma_transform_block_size)
+						<< 12) |
+		     ((sps->bit_depth_luma_minus8 + 8) << 16) |
+		     ((sps->bit_depth_chroma_minus8 + 8) << 20) |
+		     (sps->max_transform_hierarchy_depth_intra << 24) |
+		     (sps->max_transform_hierarchy_depth_inter << 28));
+
+	p1_apb_write(de,
+		     RPI_SPS1,
+		     ((sps->pcm_sample_bit_depth_luma_minus1 + 1) << 0) |
+		     ((sps->pcm_sample_bit_depth_chroma_minus1 + 1) << 4) |
+		     ((sps->log2_min_pcm_luma_coding_block_size_minus3 + 3)
+						<< 8) |
+		     ((sps->log2_min_pcm_luma_coding_block_size_minus3 + 3 +
+		       sps->log2_diff_max_min_pcm_luma_coding_block_size)
+						<< 12) |
+		     (((sps->flags & V4L2_HEVC_SPS_FLAG_SEPARATE_COLOUR_PLANE) ?
+				0 : sps->chroma_format_idc) << 16) |
+		     ((!!(sps->flags & V4L2_HEVC_SPS_FLAG_AMP_ENABLED)) << 18) |
+		     ((!!(sps->flags & V4L2_HEVC_SPS_FLAG_PCM_ENABLED)) << 19) |
+		     ((!!(sps->flags & V4L2_HEVC_SPS_FLAG_SCALING_LIST_ENABLED))
+						<< 20) |
+		     ((!!(sps->flags &
+			   V4L2_HEVC_SPS_FLAG_STRONG_INTRA_SMOOTHING_ENABLED))
+						<< 21));
+
+	p1_apb_write(de,
+		     RPI_PPS,
+		     ((s->log2_ctb_size - pps->diff_cu_qp_delta_depth) << 0) |
+		     ((!!(pps->flags & V4L2_HEVC_PPS_FLAG_CU_QP_DELTA_ENABLED))
+						 << 4) |
+		     ((!!(pps->flags &
+				V4L2_HEVC_PPS_FLAG_TRANSQUANT_BYPASS_ENABLED))
+						 << 5) |
+		     ((!!(pps->flags & V4L2_HEVC_PPS_FLAG_TRANSFORM_SKIP_ENABLED))
+						 << 6) |
+		     ((!!(pps->flags &
+				V4L2_HEVC_PPS_FLAG_SIGN_DATA_HIDING_ENABLED))
+						<< 7) |
+		     (((pps->pps_cb_qp_offset + s->sh->slice_cb_qp_offset) & 255)
+						<< 8) |
+		     (((pps->pps_cr_qp_offset + s->sh->slice_cr_qp_offset) & 255)
+						<< 16) |
+		     ((!!(pps->flags &
+				V4L2_HEVC_PPS_FLAG_CONSTRAINED_INTRA_PRED))
+						<< 24));
+
+	if (!s->start_ts &&
+	    (sps->flags & V4L2_HEVC_SPS_FLAG_SCALING_LIST_ENABLED) != 0)
+		write_scaling_factors(de);
+
+	if (!s->dependent_slice_segment_flag) {
+		int ctb_col = s->sh->slice_segment_addr %
+							de->pic_width_in_ctbs_y;
+		int ctb_row = s->sh->slice_segment_addr /
+							de->pic_width_in_ctbs_y;
+
+		de->reg_slicestart = (ctb_col << 0) + (ctb_row << 16);
+	}
+
+	p1_apb_write(de, RPI_SLICESTART, de->reg_slicestart);
+}
+
+/* Slice messages */
+
+static void msg_slice(struct hevc_d_dec_env *const de, const u16 msg)
+{
+	de->slice_msgs[de->num_slice_msgs++] = msg;
+}
+
+#define CMDS_PROGRAM_SLICECMDS (1 + SLICE_MSGS_MAX)
+static void program_slicecmds(struct hevc_d_dec_env *const de,
+			      const int sliceid)
+{
+	int i;
+
+	p1_apb_write(de, RPI_SLICECMDS, de->num_slice_msgs + (sliceid << 8));
+
+	for (i = 0; i < de->num_slice_msgs; i++)
+		p1_apb_write(de, 0x4000 + 4 * i, de->slice_msgs[i] & 0xffff);
+}
+
+/* NoBackwardPredictionFlag 8.3.5 - Simply checks POCs */
+static int has_backward(const struct v4l2_hevc_dpb_entry *const dpb,
+			const __u8 *const idx, const unsigned int n,
+			const s32 cur_poc)
+{
+	unsigned int i;
+
+	for (i = 0; i < n; ++i) {
+		if (cur_poc < dpb[idx[i]].pic_order_cnt_val)
+			return 0;
+	}
+	return 1;
+}
+
+static void pre_slice_decode(struct hevc_d_dec_env *const de,
+			     const struct hevc_d_dec_state *const s)
+{
+	const struct v4l2_ctrl_hevc_slice_params *const sh = s->sh;
+	const struct v4l2_ctrl_hevc_decode_params *const dec = s->dec;
+	int weighted_pred_flag, idx;
+	u16 cmd_slice;
+	unsigned int collocated_from_l0_flag;
+
+	de->num_slice_msgs = 0;
+
+	cmd_slice = 0;
+	if (sh->slice_type == HEVC_SLICE_I)
+		cmd_slice = 1;
+	if (sh->slice_type == HEVC_SLICE_P)
+		cmd_slice = 2;
+	if (sh->slice_type == HEVC_SLICE_B)
+		cmd_slice = 3;
+
+	cmd_slice |= (s->nb_refs[L0] << 2) | (s->nb_refs[L1] << 6) |
+		     (s->max_num_merge_cand << 11);
+
+	collocated_from_l0_flag =
+		!s->slice_temporal_mvp ||
+		sh->slice_type != HEVC_SLICE_B ||
+		(sh->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_COLLOCATED_FROM_L0);
+	cmd_slice |= collocated_from_l0_flag << 14;
+
+	if (sh->slice_type == HEVC_SLICE_P || sh->slice_type == HEVC_SLICE_B) {
+		/* Flag to say all reference pictures are from the past */
+		const int no_backward_pred_flag =
+			has_backward(dec->dpb, sh->ref_idx_l0, s->nb_refs[L0],
+				     sh->slice_pic_order_cnt) &&
+			has_backward(dec->dpb, sh->ref_idx_l1, s->nb_refs[L1],
+				     sh->slice_pic_order_cnt);
+		cmd_slice |= no_backward_pred_flag << 10;
+		msg_slice(de, cmd_slice);
+
+		if (s->slice_temporal_mvp) {
+			const __u8 *const rpl = collocated_from_l0_flag ?
+						sh->ref_idx_l0 : sh->ref_idx_l1;
+			de->dpbno_col = rpl[sh->collocated_ref_idx];
+		}
+
+		/* Write reference picture descriptions */
+		weighted_pred_flag =
+			sh->slice_type == HEVC_SLICE_P ?
+				!!(s->pps.flags & V4L2_HEVC_PPS_FLAG_WEIGHTED_PRED) :
+				!!(s->pps.flags & V4L2_HEVC_PPS_FLAG_WEIGHTED_BIPRED);
+
+		for (idx = 0; idx < s->nb_refs[L0]; ++idx) {
+			unsigned int dpb_no = sh->ref_idx_l0[idx];
+
+			msg_slice(de,
+				  dpb_no |
+				  ((dec->dpb[dpb_no].flags &
+					V4L2_HEVC_DPB_ENTRY_LONG_TERM_REFERENCE) ?
+						 (1 << 4) : 0) |
+				  (weighted_pred_flag ? (3 << 5) : 0));
+			msg_slice(de, dec->dpb[dpb_no].pic_order_cnt_val & 0xffff);
+
+			if (weighted_pred_flag) {
+				const struct v4l2_hevc_pred_weight_table
+					*const w = &sh->pred_weight_table;
+				const int luma_weight_denom =
+					(1 << w->luma_log2_weight_denom);
+				const unsigned int chroma_log2_weight_denom =
+					(w->luma_log2_weight_denom +
+					 w->delta_chroma_log2_weight_denom);
+				const int chroma_weight_denom =
+					(1 << chroma_log2_weight_denom);
+
+				msg_slice(de,
+					  w->luma_log2_weight_denom |
+					  (((w->delta_luma_weight_l0[idx] +
+					     luma_weight_denom) & 0x1ff)
+						 << 3));
+				msg_slice(de, w->luma_offset_l0[idx] & 0xff);
+				msg_slice(de,
+					  chroma_log2_weight_denom |
+					  (((w->delta_chroma_weight_l0[idx][0] +
+					     chroma_weight_denom) & 0x1ff)
+						   << 3));
+				msg_slice(de,
+					  w->chroma_offset_l0[idx][0] & 0xff);
+				msg_slice(de,
+					  chroma_log2_weight_denom |
+					  (((w->delta_chroma_weight_l0[idx][1] +
+					     chroma_weight_denom) & 0x1ff)
+						   << 3));
+				msg_slice(de,
+					  w->chroma_offset_l0[idx][1] & 0xff);
+			}
+		}
+
+		for (idx = 0; idx < s->nb_refs[L1]; ++idx) {
+			unsigned int dpb_no = sh->ref_idx_l1[idx];
+
+			msg_slice(de,
+				  dpb_no |
+				  ((dec->dpb[dpb_no].flags &
+					 V4L2_HEVC_DPB_ENTRY_LONG_TERM_REFERENCE) ?
+						 (1 << 4) : 0) |
+					(weighted_pred_flag ? (3 << 5) : 0));
+			msg_slice(de, dec->dpb[dpb_no].pic_order_cnt_val & 0xffff);
+			if (weighted_pred_flag) {
+				const struct v4l2_hevc_pred_weight_table
+					*const w = &sh->pred_weight_table;
+				const int luma_weight_denom =
+					(1 << w->luma_log2_weight_denom);
+				const unsigned int chroma_log2_weight_denom =
+					(w->luma_log2_weight_denom +
+					 w->delta_chroma_log2_weight_denom);
+				const int chroma_weight_denom =
+					(1 << chroma_log2_weight_denom);
+
+				msg_slice(de,
+					  w->luma_log2_weight_denom |
+					  (((w->delta_luma_weight_l1[idx] +
+					     luma_weight_denom) & 0x1ff) << 3));
+				msg_slice(de, w->luma_offset_l1[idx] & 0xff);
+				msg_slice(de,
+					  chroma_log2_weight_denom |
+					  (((w->delta_chroma_weight_l1[idx][0] +
+					     chroma_weight_denom) & 0x1ff)
+							<< 3));
+				msg_slice(de,
+					  w->chroma_offset_l1[idx][0] & 0xff);
+				msg_slice(de,
+					  chroma_log2_weight_denom |
+					  (((w->delta_chroma_weight_l1[idx][1] +
+					     chroma_weight_denom) & 0x1ff)
+						   << 3));
+				msg_slice(de,
+					  w->chroma_offset_l1[idx][1] & 0xff);
+			}
+		}
+	} else {
+		msg_slice(de, cmd_slice);
+	}
+
+	msg_slice(de,
+		  (sh->slice_beta_offset_div2 & 15) |
+		  ((sh->slice_tc_offset_div2 & 15) << 4) |
+		  ((sh->flags &
+		    V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_DEBLOCKING_FILTER_DISABLED) ?
+						1 << 8 : 0) |
+		  ((sh->flags &
+			  V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_LOOP_FILTER_ACROSS_SLICES_ENABLED) ?
+						1 << 9 : 0) |
+		  ((s->pps.flags &
+			  V4L2_HEVC_PPS_FLAG_LOOP_FILTER_ACROSS_TILES_ENABLED) ?
+						1 << 10 : 0));
+
+	msg_slice(de, ((sh->slice_cr_qp_offset & 31) << 5) +
+		       (sh->slice_cb_qp_offset & 31)); /* CMD_QPOFF */
+}
+
+#define CMDS_WRITE_SLICE 1
+static void write_slice(struct hevc_d_dec_env *const de,
+			const struct hevc_d_dec_state *const s,
+			const u32 slice_const,
+			const unsigned int ctb_col,
+			const unsigned int ctb_row)
+{
+	const unsigned int cs = (1 << s->log2_ctb_size);
+	const unsigned int w_last = s->sps.pic_width_in_luma_samples & (cs - 1);
+	const unsigned int h_last = s->sps.pic_height_in_luma_samples & (cs - 1);
+
+	p1_apb_write(de, RPI_SLICE,
+		     slice_const |
+		     ((ctb_col + 1 < s->ctb_width || !w_last ?
+				cs : w_last) << 17) |
+		     ((ctb_row + 1 < s->ctb_height || !h_last ?
+				cs : h_last) << 24));
+}
+
+#define PAUSE_MODE_WPP  1
+#define PAUSE_MODE_TILE 0xffff
+
+/*
+ * N.B. This can be called to fill in data from the previous slice so must not
+ * use any state data that may change from slice to slice (e.g. qp)
+ */
+#define CMDS_NEW_ENTRY_POINT (6 + CMDS_WRITE_SLICE)
+
+static void new_entry_point(struct hevc_d_dec_env *const de,
+			    const struct hevc_d_dec_state *const s,
+			    const bool do_bte,
+			    const bool reset_qp_y,
+			    const u32 pause_mode,
+			    const unsigned int tile_x,
+			    const unsigned int tile_y,
+			    const unsigned int ctb_col,
+			    const unsigned int ctb_row,
+			    const unsigned int slice_qp,
+			    const u32 slice_const)
+{
+	const unsigned int endx = s->col_bd[tile_x + 1] - 1;
+	const unsigned int endy = (pause_mode == PAUSE_MODE_WPP) ?
+		ctb_row : s->row_bd[tile_y + 1] - 1;
+
+	p1_apb_write(de, RPI_TILESTART,
+		     s->col_bd[tile_x] | (s->row_bd[tile_y] << 16));
+	p1_apb_write(de, RPI_TILEEND, endx | (endy << 16));
+
+	if (do_bte)
+		p1_apb_write(de, RPI_BEGINTILEEND, endx | (endy << 16));
+
+	write_slice(de, s, slice_const, endx, endy);
+
+	if (reset_qp_y) {
+		unsigned int sps_qp_bd_offset =
+			6 * s->sps.bit_depth_luma_minus8;
+
+		p1_apb_write(de, RPI_QP, sps_qp_bd_offset + slice_qp);
+	}
+
+	p1_apb_write(de, RPI_MODE,
+		     pause_mode |
+			((endx == s->ctb_width - 1) << 17) |
+			((endy == s->ctb_height - 1) << 18));
+
+	p1_apb_write(de, RPI_CONTROL, (ctb_col << 0) | (ctb_row << 16));
+
+	de->entry_tile_x = tile_x;
+	de->entry_tile_y = tile_y;
+	de->entry_ctb_x = ctb_col;
+	de->entry_ctb_y = ctb_row;
+	de->entry_qp = slice_qp;
+	de->entry_slice = slice_const;
+}
+
+/* Wavefront mode */
+
+#define CMDS_WPP_PAUSE 4
+static void wpp_pause(struct hevc_d_dec_env *const de, int ctb_row)
+{
+	p1_apb_write(de, RPI_STATUS, (ctb_row << 18) | 0x25);
+	p1_apb_write(de, RPI_TRANSFER, PROB_BACKUP);
+	p1_apb_write(de, RPI_MODE,
+		     ctb_row == de->pic_height_in_ctbs_y - 1 ?
+							0x70000 : 0x30000);
+	p1_apb_write(de, RPI_CONTROL, (ctb_row << 16) + 2);
+}
+
+#define CMDS_WPP_ENTRY_FILL_1 (CMDS_WPP_PAUSE + 2 + CMDS_NEW_ENTRY_POINT)
+static int wpp_entry_fill(struct hevc_d_dec_env *const de,
+			  const struct hevc_d_dec_state *const s,
+			  const unsigned int last_y)
+{
+	int rv;
+	const unsigned int last_x = s->ctb_width - 1;
+
+	rv = cmds_check_space(de, CMDS_WPP_ENTRY_FILL_1 *
+				  (last_y - de->entry_ctb_y));
+	if (rv)
+		return rv;
+
+	while (de->entry_ctb_y < last_y) {
+		/* wpp_entry_x/y set by wpp_entry_point */
+		if (s->ctb_width > 2)
+			wpp_pause(de, de->entry_ctb_y);
+		p1_apb_write(de, RPI_STATUS,
+			     (de->entry_ctb_y << 18) | (last_x << 5) | 2);
+
+		/* if width == 1 then the saved state is the init one */
+		if (s->ctb_width == 2)
+			p1_apb_write(de, RPI_TRANSFER, PROB_BACKUP);
+		else
+			p1_apb_write(de, RPI_TRANSFER, PROB_RELOAD);
+
+		new_entry_point(de, s, false, true, PAUSE_MODE_WPP,
+				0, 0, 0, de->entry_ctb_y + 1,
+				de->entry_qp, de->entry_slice);
+	}
+	return 0;
+}
+
+static int wpp_end_previous_slice(struct hevc_d_dec_env *const de,
+				  const struct hevc_d_dec_state *const s)
+{
+	int rv;
+
+	rv = wpp_entry_fill(de, s, s->prev_ctb_y);
+	if (rv)
+		return rv;
+
+	rv = cmds_check_space(de, CMDS_WPP_PAUSE + 2);
+	if (rv)
+		return rv;
+
+	if (de->entry_ctb_x < 2 &&
+	    (de->entry_ctb_y < s->start_ctb_y || s->start_ctb_x > 2) &&
+	    s->ctb_width > 2)
+		wpp_pause(de, s->prev_ctb_y);
+	p1_apb_write(de, RPI_STATUS,
+		     1 | (s->prev_ctb_x << 5) | (s->prev_ctb_y << 18));
+	if (s->start_ctb_x == 2 ||
+	    (s->ctb_width == 2 && de->entry_ctb_y < s->start_ctb_y))
+		p1_apb_write(de, RPI_TRANSFER, PROB_BACKUP);
+	return 0;
+}
+
+/*
+ * Only main profile supported so WPP => !Tiles which makes some of the
+ * next chunk code simpler
+ */
+static int wpp_decode_slice(struct hevc_d_dec_env *const de,
+			    const struct hevc_d_dec_state *const s,
+			    bool last_slice)
+{
+	bool reset_qp_y = true;
+	const bool indep = !s->dependent_slice_segment_flag;
+	int rv;
+
+	if (s->start_ts) {
+		rv = wpp_end_previous_slice(de, s);
+		if (rv)
+			return rv;
+	}
+	pre_slice_decode(de, s);
+
+	rv = cmds_check_space(de,
+			      CMDS_WRITE_BITSTREAM +
+				CMDS_WRITE_PROB +
+				CMDS_PROGRAM_SLICECMDS +
+				CMDS_NEW_SLICE_SEGMENT +
+				CMDS_NEW_ENTRY_POINT);
+	if (rv)
+		return rv;
+
+	rv = write_bitstream(de, s);
+	if (rv)
+		return rv;
+
+	if (!s->start_ts || indep || s->ctb_width == 1)
+		write_prob(de, s);
+	else if (!s->start_ctb_x)
+		p1_apb_write(de, RPI_TRANSFER, PROB_RELOAD);
+	else
+		reset_qp_y = false;
+
+	program_slicecmds(de, s->slice_idx);
+	new_slice_segment(de, s);
+	new_entry_point(de, s, indep, reset_qp_y, PAUSE_MODE_WPP,
+			0, 0, s->start_ctb_x, s->start_ctb_y,
+			s->slice_qp, slice_reg_const(s));
+
+	if (last_slice) {
+		rv = wpp_entry_fill(de, s, s->ctb_height - 1);
+		if (rv)
+			return rv;
+
+		rv = cmds_check_space(de, CMDS_WPP_PAUSE + 1);
+		if (rv)
+			return rv;
+
+		if (de->entry_ctb_x < 2 && s->ctb_width > 2)
+			wpp_pause(de, s->ctb_height - 1);
+
+		p1_apb_write(de, RPI_STATUS,
+			     1 | ((s->ctb_width - 1) << 5) |
+				((s->ctb_height - 1) << 18));
+	}
+	return 0;
+}
+
+/* Tiles mode */
+
+/* Guarantees 1 cmd entry free on exit */
+static int tile_entry_fill(struct hevc_d_dec_env *const de,
+			   const struct hevc_d_dec_state *const s,
+			   const unsigned int last_tile_x,
+			   const unsigned int last_tile_y)
+{
+	while (de->entry_tile_y < last_tile_y ||
+	       (de->entry_tile_y == last_tile_y &&
+		de->entry_tile_x < last_tile_x)) {
+		int rv;
+		unsigned int t_x = de->entry_tile_x;
+		unsigned int t_y = de->entry_tile_y;
+		const unsigned int last_x = s->col_bd[t_x + 1] - 1;
+		const unsigned int last_y = s->row_bd[t_y + 1] - 1;
+
+		/* One more than needed here */
+		rv = cmds_check_space(de, CMDS_NEW_ENTRY_POINT + 3);
+		if (rv)
+			return rv;
+
+		p1_apb_write(de, RPI_STATUS,
+			     2 | (last_x << 5) | (last_y << 18));
+		p1_apb_write(de, RPI_TRANSFER, PROB_RELOAD);
+
+		// Inc tile
+		if (++t_x >= s->tile_width) {
+			t_x = 0;
+			++t_y;
+		}
+
+		new_entry_point(de, s, false, true, PAUSE_MODE_TILE,
+				t_x, t_y, s->col_bd[t_x], s->row_bd[t_y],
+				de->entry_qp, de->entry_slice);
+	}
+	return 0;
+}
+
+/* Write STATUS register with expected end CTU address of previous slice */
+static int end_previous_slice(struct hevc_d_dec_env *const de,
+			      const struct hevc_d_dec_state *const s)
+{
+	int rv;
+
+	rv = tile_entry_fill(de, s,
+			     ctb_to_tile_x(s, s->prev_ctb_x),
+			     ctb_to_tile_y(s, s->prev_ctb_y));
+	if (rv)
+		return rv;
+
+	p1_apb_write(de, RPI_STATUS,
+		     1 | (s->prev_ctb_x << 5) | (s->prev_ctb_y << 18));
+	return 0;
+}
+
+static int decode_slice(struct hevc_d_dec_env *const de,
+			const struct hevc_d_dec_state *const s,
+			bool last_slice)
+{
+	bool reset_qp_y;
+	unsigned int tile_x = ctb_to_tile_x(s, s->start_ctb_x);
+	unsigned int tile_y = ctb_to_tile_y(s, s->start_ctb_y);
+	int rv;
+
+	if (s->start_ts) {
+		rv = end_previous_slice(de, s);
+		if (rv)
+			return rv;
+	}
+
+	rv = cmds_check_space(de,
+			      CMDS_WRITE_BITSTREAM +
+				CMDS_WRITE_PROB +
+				CMDS_PROGRAM_SLICECMDS +
+				CMDS_NEW_SLICE_SEGMENT +
+				CMDS_NEW_ENTRY_POINT);
+	if (rv)
+		return rv;
+
+	pre_slice_decode(de, s);
+	rv = write_bitstream(de, s);
+	if (rv)
+		return rv;
+
+	reset_qp_y = !s->start_ts ||
+		!s->dependent_slice_segment_flag ||
+		tile_x != ctb_to_tile_x(s, s->prev_ctb_x) ||
+		tile_y != ctb_to_tile_y(s, s->prev_ctb_y);
+	if (reset_qp_y)
+		write_prob(de, s);
+
+	program_slicecmds(de, s->slice_idx);
+	new_slice_segment(de, s);
+	new_entry_point(de, s, !s->dependent_slice_segment_flag, reset_qp_y,
+			PAUSE_MODE_TILE,
+			tile_x, tile_y, s->start_ctb_x, s->start_ctb_y,
+			s->slice_qp, slice_reg_const(s));
+
+	/*
+	 * If this is the last slice then fill in the other tile entries
+	 * now, otherwise this will be done at the start of the next slice
+	 * when it will be known where this slice finishes
+	 */
+	if (last_slice) {
+		rv = tile_entry_fill(de, s,
+				     s->tile_width - 1,
+				     s->tile_height - 1);
+		if (rv)
+			return rv;
+		p1_apb_write(de, RPI_STATUS,
+			     1 | ((s->ctb_width - 1) << 5) |
+				((s->ctb_height - 1) << 18));
+	}
+	return 0;
+}
+
+/* Scaling factors */
+
+static void expand_scaling_list(const unsigned int size_id,
+				u8 *const dst0,
+				const u8 *const src0, uint8_t dc)
+{
+	u8 *d;
+	unsigned int x, y;
+
+	switch (size_id) {
+	case 0:
+		memcpy(dst0, src0, 16);
+		break;
+	case 1:
+		memcpy(dst0, src0, 64);
+		break;
+	case 2:
+		d = dst0;
+
+		for (y = 0; y != 16; y++) {
+			const u8 *s = src0 + (y >> 1) * 8;
+
+			for (x = 0; x != 8; ++x) {
+				*d++ = *s;
+				*d++ = *s++;
+			}
+		}
+		dst0[0] = dc;
+		break;
+	default:
+		d = dst0;
+
+		for (y = 0; y != 32; y++) {
+			const u8 *s = src0 + (y >> 2) * 8;
+
+			for (x = 0; x != 8; ++x) {
+				*d++ = *s;
+				*d++ = *s;
+				*d++ = *s;
+				*d++ = *s++;
+			}
+		}
+		dst0[0] = dc;
+		break;
+	}
+}
+
+static void populate_scaling_factors(const struct hevc_d_run *const run,
+				     struct hevc_d_dec_env *const de,
+				     const struct hevc_d_dec_state *const s)
+{
+	const struct v4l2_ctrl_hevc_scaling_matrix *const sl =
+		run->h265.scaling_matrix;
+	/* Array of constants for scaling factors */
+	static const u32 scaling_factor_offsets[4][6] = {
+		/*
+		 * MID0    MID1    MID2    MID3    MID4    MID5
+		 */
+		/* SID0 (4x4) */
+		{ 0x0000, 0x0010, 0x0020, 0x0030, 0x0040, 0x0050 },
+		/* SID1 (8x8) */
+		{ 0x0060, 0x00A0, 0x00E0, 0x0120, 0x0160, 0x01A0 },
+		/* SID2 (16x16) */
+		{ 0x01E0, 0x02E0, 0x03E0, 0x04E0, 0x05E0, 0x06E0 },
+		/* SID3 (32x32) */
+		{ 0x07E0, 0x0BE0, 0x0000, 0x0000, 0x0000, 0x0000 }
+	};
+	unsigned int mid;
+
+	for (mid = 0; mid < 6; mid++)
+		expand_scaling_list(0, de->scaling_factors +
+					    scaling_factor_offsets[0][mid],
+				    sl->scaling_list_4x4[mid], 0);
+	for (mid = 0; mid < 6; mid++)
+		expand_scaling_list(1, de->scaling_factors +
+					    scaling_factor_offsets[1][mid],
+				    sl->scaling_list_8x8[mid], 0);
+	for (mid = 0; mid < 6; mid++)
+		expand_scaling_list(2, de->scaling_factors +
+					    scaling_factor_offsets[2][mid],
+				    sl->scaling_list_16x16[mid],
+				    sl->scaling_list_dc_coef_16x16[mid]);
+	for (mid = 0; mid < 2; mid++)
+		expand_scaling_list(3, de->scaling_factors +
+					    scaling_factor_offsets[3][mid],
+				    sl->scaling_list_32x32[mid],
+				    sl->scaling_list_dc_coef_32x32[mid]);
+}
+
+static void free_ps_info(struct hevc_d_dec_state *const s)
+{
+	kfree(s->ctb_addr_rs_to_ts);
+	s->ctb_addr_rs_to_ts = NULL;
+	kfree(s->ctb_addr_ts_to_rs);
+	s->ctb_addr_ts_to_rs = NULL;
+
+	kfree(s->col_bd);
+	s->col_bd = NULL;
+	kfree(s->row_bd);
+	s->row_bd = NULL;
+}
+
+static unsigned int tile_width(const struct hevc_d_dec_state *const s,
+			       const unsigned int t_x)
+{
+	return s->col_bd[t_x + 1] - s->col_bd[t_x];
+}
+
+static unsigned int tile_height(const struct hevc_d_dec_state *const s,
+				const unsigned int t_y)
+{
+	return s->row_bd[t_y + 1] - s->row_bd[t_y];
+}
+
+static void fill_rs_to_ts(struct hevc_d_dec_state *const s)
+{
+	unsigned int ts = 0;
+	unsigned int t_y;
+	unsigned int tr_rs = 0;
+
+	for (t_y = 0; t_y != s->tile_height; ++t_y) {
+		const unsigned int t_h = tile_height(s, t_y);
+		unsigned int t_x;
+		unsigned int tc_rs = tr_rs;
+
+		for (t_x = 0; t_x != s->tile_width; ++t_x) {
+			const unsigned int t_w = tile_width(s, t_x);
+			unsigned int y;
+			unsigned int rs = tc_rs;
+
+			for (y = 0; y != t_h; ++y) {
+				unsigned int x;
+
+				for (x = 0; x != t_w; ++x) {
+					s->ctb_addr_rs_to_ts[rs + x] = ts;
+					s->ctb_addr_ts_to_rs[ts] = rs + x;
+					++ts;
+				}
+				rs += s->ctb_width;
+			}
+			tc_rs += t_w;
+		}
+		tr_rs += t_h * s->ctb_width;
+	}
+}
+
+static int updated_ps(struct hevc_d_dec_state *const s)
+{
+	unsigned int i;
+
+	free_ps_info(s);
+
+	/* Inferred parameters */
+	s->log2_ctb_size = s->sps.log2_min_luma_coding_block_size_minus3 + 3 +
+			   s->sps.log2_diff_max_min_luma_coding_block_size;
+
+	s->ctb_width = (s->sps.pic_width_in_luma_samples +
+			(1 << s->log2_ctb_size) - 1) >>
+		       s->log2_ctb_size;
+	s->ctb_height = (s->sps.pic_height_in_luma_samples +
+			 (1 << s->log2_ctb_size) - 1) >>
+			s->log2_ctb_size;
+	s->ctb_size = s->ctb_width * s->ctb_height;
+
+	s->ctb_addr_rs_to_ts = kmalloc_array(s->ctb_size,
+					     sizeof(*s->ctb_addr_rs_to_ts),
+					     GFP_KERNEL);
+	if (!s->ctb_addr_rs_to_ts)
+		goto fail;
+	s->ctb_addr_ts_to_rs = kmalloc_array(s->ctb_size,
+					     sizeof(*s->ctb_addr_ts_to_rs),
+					     GFP_KERNEL);
+	if (!s->ctb_addr_ts_to_rs)
+		goto fail;
+
+	if (!(s->pps.flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED)) {
+		s->tile_width = 1;
+		s->tile_height = 1;
+	} else {
+		s->tile_width = s->pps.num_tile_columns_minus1 + 1;
+		s->tile_height = s->pps.num_tile_rows_minus1 + 1;
+	}
+
+	s->col_bd = kmalloc_array((s->tile_width + 1), sizeof(*s->col_bd),
+				  GFP_KERNEL);
+	if (!s->col_bd)
+		goto fail;
+	s->row_bd = kmalloc_array((s->tile_height + 1), sizeof(*s->row_bd),
+				  GFP_KERNEL);
+	if (!s->row_bd)
+		goto fail;
+
+	s->col_bd[0] = 0;
+	for (i = 1; i < s->tile_width; i++)
+		s->col_bd[i] = s->col_bd[i - 1] +
+			s->pps.column_width_minus1[i - 1] + 1;
+	s->col_bd[s->tile_width] = s->ctb_width;
+
+	s->row_bd[0] = 0;
+	for (i = 1; i < s->tile_height; i++)
+		s->row_bd[i] = s->row_bd[i - 1] +
+			s->pps.row_height_minus1[i - 1] + 1;
+	s->row_bd[s->tile_height] = s->ctb_height;
+
+	fill_rs_to_ts(s);
+	return 0;
+
+fail:
+	free_ps_info(s);
+	/* Set invalid to force reload */
+	s->sps.pic_width_in_luma_samples = 0;
+	return -ENOMEM;
+}
+
+static int write_cmd_buffer(struct hevc_d_dev *const dev,
+			    struct hevc_d_dec_env *const de,
+			    const struct hevc_d_dec_state *const s)
+{
+	const size_t cmd_size = ALIGN(de->cmd_len * sizeof(de->cmd_fifo[0]),
+				      dev->cache_align);
+
+	de->cmd_addr = dma_map_single(dev->dev, de->cmd_fifo,
+				      cmd_size, DMA_TO_DEVICE);
+	if (dma_mapping_error(dev->dev, de->cmd_addr)) {
+		v4l2_err(&dev->v4l2_dev,
+			 "Map cmd buffer (%zu): FAILED\n", cmd_size);
+		return -ENOMEM;
+	}
+	de->cmd_size = cmd_size;
+	return 0;
+}
+
+static void setup_colmv(struct hevc_d_ctx *const ctx, struct hevc_d_run *run,
+			struct hevc_d_dec_state *const s)
+{
+	ctx->colmv_stride = ALIGN(s->sps.pic_width_in_luma_samples, 64);
+	ctx->colmv_picsize = ctx->colmv_stride *
+		(ALIGN(s->sps.pic_height_in_luma_samples, 64) >> 4);
+}
+
+static struct hevc_d_dec_env *dec_env_new(struct hevc_d_ctx *const ctx)
+{
+	struct hevc_d_dec_env *de;
+	unsigned long lock_flags;
+
+	spin_lock_irqsave(&ctx->dec_lock, lock_flags);
+
+	de = ctx->dec_free;
+	if (de) {
+		ctx->dec_free = de->next;
+		de->next = NULL;
+		de->state = HEVC_D_DECODE_SLICE_START;
+	}
+
+	spin_unlock_irqrestore(&ctx->dec_lock, lock_flags);
+	return de;
+}
+
+/* Can be called from irq context */
+static void dec_env_delete(struct hevc_d_dec_env *const de)
+{
+	struct hevc_d_ctx * const ctx = de->ctx;
+	unsigned long lock_flags;
+
+	if (de->cmd_size) {
+		dma_unmap_single(ctx->dev->dev, de->cmd_addr, de->cmd_size,
+				 DMA_TO_DEVICE);
+		de->cmd_size = 0;
+	}
+
+	aux_q_release(ctx, &de->frame_aux);
+	aux_q_release(ctx, &de->col_aux);
+
+	spin_lock_irqsave(&ctx->dec_lock, lock_flags);
+
+	de->state = HEVC_D_DECODE_END;
+	de->next = ctx->dec_free;
+	ctx->dec_free = de;
+
+	spin_unlock_irqrestore(&ctx->dec_lock, lock_flags);
+}
+
+static void dec_env_uninit(struct hevc_d_ctx *const ctx)
+{
+	unsigned int i;
+
+	if (ctx->dec_pool) {
+		for (i = 0; i != HEVC_D_DEC_ENV_COUNT; ++i) {
+			struct hevc_d_dec_env *const de = ctx->dec_pool + i;
+
+			kfree(de->cmd_fifo);
+		}
+
+		kfree(ctx->dec_pool);
+	}
+
+	ctx->dec_pool = NULL;
+	ctx->dec_free = NULL;
+}
+
+static int dec_env_init(struct hevc_d_ctx *const ctx)
+{
+	unsigned int i;
+
+	ctx->dec_pool = kzalloc(sizeof(*ctx->dec_pool) * HEVC_D_DEC_ENV_COUNT,
+				GFP_KERNEL);
+	if (!ctx->dec_pool)
+		return -1;
+
+	spin_lock_init(&ctx->dec_lock);
+
+	ctx->dec_free = ctx->dec_pool;
+	for (i = 0; i != HEVC_D_DEC_ENV_COUNT - 1; ++i)
+		ctx->dec_pool[i].next = ctx->dec_pool + i + 1;
+
+	for (i = 0; i != HEVC_D_DEC_ENV_COUNT; ++i) {
+		struct hevc_d_dec_env *const de = ctx->dec_pool + i;
+
+		de->ctx = ctx;
+		de->decode_order = i;
+		de->cmd_max = 8096;
+		de->cmd_fifo = kmalloc_array(de->cmd_max,
+					     sizeof(struct rpi_cmd),
+					     GFP_KERNEL);
+		if (!de->cmd_fifo)
+			goto fail;
+	}
+
+	return 0;
+
+fail:
+	dec_env_uninit(ctx);
+	return -1;
+}
+
+/*
+ * Assume that we get exactly the same DPB for every slice it makes no real
+ * sense otherwise.
+ */
+#if V4L2_HEVC_DPB_ENTRIES_NUM_MAX > 16
+#error HEVC_DPB_ENTRIES > h/w slots
+#endif
+
+static u32 mk_config2(const struct hevc_d_dec_state *const s)
+{
+	const struct v4l2_ctrl_hevc_sps *const sps = &s->sps;
+	const struct v4l2_ctrl_hevc_pps *const pps = &s->pps;
+	u32 c;
+
+	c = (sps->bit_depth_luma_minus8 + 8) << 0;	/* BitDepthY */
+	c |= (sps->bit_depth_chroma_minus8 + 8) << 4;	/* BitDepthC */
+	if (sps->bit_depth_luma_minus8)			/* BitDepthY */
+		c |= BIT(8);
+	if (sps->bit_depth_chroma_minus8)		/* BitDepthC */
+		c |= BIT(9);
+	c |= s->log2_ctb_size << 10;
+	if (pps->flags & V4L2_HEVC_PPS_FLAG_CONSTRAINED_INTRA_PRED)
+		c |= BIT(13);
+	if (sps->flags & V4L2_HEVC_SPS_FLAG_STRONG_INTRA_SMOOTHING_ENABLED)
+		c |= BIT(14);
+	if (s->mk_aux)
+		c |= BIT(15); /* Write motion vectors to external memory */
+	c |= (pps->log2_parallel_merge_level_minus2 + 2) << 16;
+	if (s->slice_temporal_mvp)
+		c |= BIT(19);
+	if (sps->flags & V4L2_HEVC_SPS_FLAG_PCM_LOOP_FILTER_DISABLED)
+		c |= BIT(20);
+	c |= (pps->pps_cb_qp_offset & 31) << 21;
+	c |= (pps->pps_cr_qp_offset & 31) << 26;
+	return c;
+}
+
+static inline bool is_ref_unit_type(const unsigned int nal_unit_type)
+{
+	/* From Table 7-1
+	 * True for 1, 3, 5, 7, 9, 11, 13, 15
+	 */
+	return (nal_unit_type & ~0xe) != 0;
+}
+
+void hevc_d_h265_setup(struct hevc_d_ctx *ctx, struct hevc_d_run *run)
+{
+	struct hevc_d_dev *const dev = ctx->dev;
+	const struct v4l2_ctrl_hevc_decode_params *const dec =
+						run->h265.dec;
+	/* sh0 used where slice header contents should be constant over all
+	 * slices, or first slice of frame
+	 */
+	const struct v4l2_ctrl_hevc_slice_params *const sh0 =
+					run->h265.slice_params;
+	struct hevc_d_q_aux *dpb_q_aux[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+	struct hevc_d_dec_state *const s = ctx->state;
+	struct vb2_queue *vq;
+	struct hevc_d_dec_env *de = ctx->dec0;
+	unsigned int prev_rs;
+	unsigned int i;
+	int rv;
+	bool slice_temporal_mvp;
+	unsigned int ctb_size_y;
+	bool sps_changed = false;
+
+	s->sh = NULL;  /* Avoid use until in the slice loop */
+
+	slice_temporal_mvp = (sh0->flags &
+		   V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_TEMPORAL_MVP_ENABLED);
+
+	if (de) {
+		v4l2_warn(&dev->v4l2_dev, "Decode env set unexpectedly");
+		goto fail;
+	}
+
+	/* Frame start */
+
+	if (!is_sps_set(run->h265.sps)) {
+		v4l2_warn(&dev->v4l2_dev, "SPS never set\n");
+		goto fail;
+	}
+	/* Can't check for PPS easily as all 0's looks valid */
+
+	if (memcmp(&s->sps, run->h265.sps, sizeof(s->sps)) != 0) {
+		/* SPS changed */
+		memcpy(&s->sps, run->h265.sps, sizeof(s->sps));
+		sps_changed = true;
+	}
+	if (sps_changed ||
+	    memcmp(&s->pps, run->h265.pps, sizeof(s->pps)) != 0) {
+		/* SPS changed */
+		memcpy(&s->pps, run->h265.pps, sizeof(s->pps));
+
+		/* Recalc stuff as required */
+		rv = updated_ps(s);
+		if (rv)
+			goto fail;
+	}
+
+	de = dec_env_new(ctx);
+	if (!de) {
+		v4l2_err(&dev->v4l2_dev, "Failed to find free decode env\n");
+		goto fail;
+	}
+	ctx->dec0 = de;
+
+	ctb_size_y =
+		1U << (s->sps.log2_min_luma_coding_block_size_minus3 +
+		       3 + s->sps.log2_diff_max_min_luma_coding_block_size);
+
+	de->pic_width_in_ctbs_y =
+		(s->sps.pic_width_in_luma_samples + ctb_size_y - 1) /
+			ctb_size_y; /* 7-15 */
+	de->pic_height_in_ctbs_y =
+		(s->sps.pic_height_in_luma_samples + ctb_size_y - 1) /
+			ctb_size_y; /* 7-17 */
+	de->cmd_len = 0;
+	de->dpbno_col = ~0U;
+
+	switch (ctx->dst_fmt.pixelformat) {
+	case V4L2_PIX_FMT_NV12MT_COL128:
+	case V4L2_PIX_FMT_NV12MT_10_COL128:
+		de->luma_stride = ctx->dst_fmt.height * 128;
+		de->frame_luma_addr =
+			vb2_dma_contig_plane_dma_addr(&run->dst->vb2_buf, 0);
+		de->chroma_stride = de->luma_stride / 2;
+		de->frame_chroma_addr =
+			vb2_dma_contig_plane_dma_addr(&run->dst->vb2_buf, 1);
+		break;
+	case V4L2_PIX_FMT_NV12_COL128:
+	case V4L2_PIX_FMT_NV12_10_COL128:
+		de->luma_stride = ctx->dst_fmt.plane_fmt[0].bytesperline * 128;
+		de->frame_luma_addr =
+			vb2_dma_contig_plane_dma_addr(&run->dst->vb2_buf, 0);
+		de->chroma_stride = de->luma_stride;
+		de->frame_chroma_addr = de->frame_luma_addr +
+					(ctx->dst_fmt.height * 128);
+		break;
+	}
+
+	de->frame_aux = NULL;
+
+	if (s->sps.bit_depth_luma_minus8 !=
+	    s->sps.bit_depth_chroma_minus8) {
+		v4l2_warn(&dev->v4l2_dev,
+			  "Chroma depth (%d) != Luma depth (%d)\n",
+			  s->sps.bit_depth_chroma_minus8 + 8,
+			  s->sps.bit_depth_luma_minus8 + 8);
+		goto fail;
+	}
+	if (s->sps.bit_depth_luma_minus8 == 0) {
+		if (ctx->dst_fmt.pixelformat != V4L2_PIX_FMT_NV12MT_COL128 &&
+		    ctx->dst_fmt.pixelformat != V4L2_PIX_FMT_NV12_COL128) {
+			v4l2_err(&dev->v4l2_dev,
+				 "Pixel format %#x != NV12MT_COL128 for 8-bit output",
+				 ctx->dst_fmt.pixelformat);
+			goto fail;
+		}
+	} else if (s->sps.bit_depth_luma_minus8 == 2) {
+		if (ctx->dst_fmt.pixelformat != V4L2_PIX_FMT_NV12MT_10_COL128 &&
+		    ctx->dst_fmt.pixelformat != V4L2_PIX_FMT_NV12_10_COL128) {
+			v4l2_err(&dev->v4l2_dev,
+				 "Pixel format %#x != NV12MT_10_COL128 for 10-bit output",
+				 ctx->dst_fmt.pixelformat);
+			goto fail;
+		}
+	} else {
+		v4l2_warn(&dev->v4l2_dev, "Luma depth (%d) unsupported\n",
+			  s->sps.bit_depth_luma_minus8 + 8);
+		goto fail;
+	}
+	switch (ctx->dst_fmt.pixelformat) {
+	case V4L2_PIX_FMT_NV12MT_COL128:
+	case V4L2_PIX_FMT_NV12MT_10_COL128:
+		if (run->dst->vb2_buf.num_planes != 2) {
+			v4l2_warn(&dev->v4l2_dev, "Capture planes (%d) != 2\n",
+				  run->dst->vb2_buf.num_planes);
+			goto fail;
+		}
+		if (run->dst->planes[0].length < ctx->dst_fmt.plane_fmt[0].sizeimage ||
+		    run->dst->planes[1].length < ctx->dst_fmt.plane_fmt[1].sizeimage) {
+			v4l2_warn(&dev->v4l2_dev,
+				  "Capture planes length (%d/%d) < sizeimage (%d/%d)\n",
+				  run->dst->planes[0].length,
+				  run->dst->planes[1].length,
+				  ctx->dst_fmt.plane_fmt[0].sizeimage,
+				  ctx->dst_fmt.plane_fmt[1].sizeimage);
+			goto fail;
+		}
+		break;
+	case V4L2_PIX_FMT_NV12_COL128:
+	case V4L2_PIX_FMT_NV12_10_COL128:
+		if (run->dst->vb2_buf.num_planes != 1) {
+			v4l2_warn(&dev->v4l2_dev, "Capture planes (%d) != 1\n",
+				  run->dst->vb2_buf.num_planes);
+			goto fail;
+		}
+		if (run->dst->planes[0].length < ctx->dst_fmt.plane_fmt[0].sizeimage) {
+			v4l2_warn(&dev->v4l2_dev,
+				  "Capture planes length (%d) < sizeimage (%d)\n",
+				  run->dst->planes[0].length,
+				  ctx->dst_fmt.plane_fmt[0].sizeimage);
+			goto fail;
+		}
+		break;
+	}
+
+	/*
+	 * Fill in ref planes with our address s.t. if we mess up refs
+	 * somehow then we still have a valid address entry
+	 */
+	for (i = 0; i != 16; ++i) {
+		de->ref_addrs[i][0] = de->frame_luma_addr;
+		de->ref_addrs[i][1] = de->frame_chroma_addr;
+	}
+
+	/*
+	 * Stash initial temporal_mvp flag
+	 * This must be the same for all pic slices (7.4.7.1)
+	 */
+	s->slice_temporal_mvp = slice_temporal_mvp;
+
+	/*
+	 * Need Aux ents for all (ref) DPB ents if temporal MV could
+	 * be enabled for any pic
+	 */
+	s->use_aux = ((s->sps.flags &
+		       V4L2_HEVC_SPS_FLAG_SPS_TEMPORAL_MVP_ENABLED) != 0);
+	s->mk_aux = s->use_aux &&
+		    (s->sps.sps_max_sub_layers_minus1 >= sh0->nuh_temporal_id_plus1 ||
+		     is_ref_unit_type(sh0->nal_unit_type));
+
+	/* Phase 2 reg pre-calc */
+	de->rpi_config2 = mk_config2(s);
+	de->rpi_framesize = (s->sps.pic_height_in_luma_samples << 16) |
+			    s->sps.pic_width_in_luma_samples;
+	de->rpi_currpoc = sh0->slice_pic_order_cnt;
+
+	if (s->sps.flags &
+	    V4L2_HEVC_SPS_FLAG_SPS_TEMPORAL_MVP_ENABLED) {
+		setup_colmv(ctx, run, s);
+	}
+
+	s->slice_idx = 0;
+
+	if (sh0->slice_segment_addr != 0) {
+		v4l2_warn(&dev->v4l2_dev,
+			  "New frame but segment_addr=%d\n",
+			  sh0->slice_segment_addr);
+		goto fail;
+	}
+
+	/* Either map src buffer or use directly */
+	s->src_addr = 0;
+
+	s->src_addr = vb2_dma_contig_plane_dma_addr(&run->src->vb2_buf, 0);
+	if (!s->src_addr) {
+		v4l2_err(&dev->v4l2_dev, "Failed to map src buffer\n");
+		goto fail;
+	}
+
+	/* Pre calc parameters */
+	s->dec = dec;
+	for (i = 0; i != run->h265.slice_ents; ++i) {
+		const struct v4l2_ctrl_hevc_slice_params *const sh = sh0 + i;
+		const bool last_slice = i + 1 == run->h265.slice_ents;
+
+		s->sh = sh;
+
+		if (run->src->planes[0].bytesused < (sh->bit_size + 7) / 8) {
+			v4l2_warn(&dev->v4l2_dev,
+				  "Bit size %d > bytesused %d\n",
+				  sh->bit_size, run->src->planes[0].bytesused);
+			goto fail;
+		}
+		if (sh->data_byte_offset >= sh->bit_size / 8) {
+			v4l2_warn(&dev->v4l2_dev,
+				  "Bit size %u < Byte offset %u * 8\n",
+				  sh->bit_size, sh->data_byte_offset);
+			goto fail;
+		}
+
+		s->slice_qp = 26 + s->pps.init_qp_minus26 + sh->slice_qp_delta;
+		s->max_num_merge_cand = sh->slice_type == HEVC_SLICE_I ?
+						0 :
+						(5 - sh->five_minus_max_num_merge_cand);
+		s->dependent_slice_segment_flag =
+			((sh->flags &
+			  V4L2_HEVC_SLICE_PARAMS_FLAG_DEPENDENT_SLICE_SEGMENT) != 0);
+
+		s->nb_refs[0] = (sh->slice_type == HEVC_SLICE_I) ?
+					0 :
+					sh->num_ref_idx_l0_active_minus1 + 1;
+		s->nb_refs[1] = (sh->slice_type != HEVC_SLICE_B) ?
+					0 :
+					sh->num_ref_idx_l1_active_minus1 + 1;
+
+		if (s->sps.flags & V4L2_HEVC_SPS_FLAG_SCALING_LIST_ENABLED)
+			populate_scaling_factors(run, de, s);
+
+		/* Calc all the random coord info to avoid repeated conversion in/out */
+		s->start_ts = s->ctb_addr_rs_to_ts[sh->slice_segment_addr];
+		s->start_ctb_x = sh->slice_segment_addr % de->pic_width_in_ctbs_y;
+		s->start_ctb_y = sh->slice_segment_addr / de->pic_width_in_ctbs_y;
+		/* Last CTB of previous slice */
+		prev_rs = !s->start_ts ? 0 : s->ctb_addr_ts_to_rs[s->start_ts - 1];
+		s->prev_ctb_x = prev_rs % de->pic_width_in_ctbs_y;
+		s->prev_ctb_y = prev_rs / de->pic_width_in_ctbs_y;
+
+		if ((s->pps.flags & V4L2_HEVC_PPS_FLAG_ENTROPY_CODING_SYNC_ENABLED))
+			rv = wpp_decode_slice(de, s, last_slice);
+		else
+			rv = decode_slice(de, s, last_slice);
+		if (rv)
+			goto fail;
+
+		++s->slice_idx;
+	}
+
+	/* Frame end */
+	memset(dpb_q_aux, 0,
+	       sizeof(*dpb_q_aux) * V4L2_HEVC_DPB_ENTRIES_NUM_MAX);
+
+	/*
+	 * Locate ref frames
+	 * At least in the current implementation this is constant across all
+	 * slices. If this changes we will need idx mapping code.
+	 * Uses sh so here rather than trigger
+	 */
+
+	vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx,
+			     V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+
+	if (!vq) {
+		v4l2_err(&dev->v4l2_dev, "VQ gone!\n");
+		goto fail;
+	}
+
+	if (write_cmd_buffer(dev, de, s))
+		goto fail;
+
+	for (i = 0; i < dec->num_active_dpb_entries; ++i) {
+		struct vb2_buffer *buf = vb2_find_buffer(vq, dec->dpb[i].timestamp);
+
+		if (!buf) {
+			v4l2_warn(&dev->v4l2_dev,
+				  "Missing DPB ent %d, timestamp=%lld\n",
+				  i, (long long)dec->dpb[i].timestamp);
+			continue;
+		}
+
+		if (s->use_aux) {
+			int buffer_index = buf->index;
+
+			dpb_q_aux[i] = aux_q_ref_idx(ctx, buffer_index);
+			if (!dpb_q_aux[i])
+				v4l2_warn(&dev->v4l2_dev,
+					  "Missing DPB AUX ent %d, timestamp=%lld, index=%d\n",
+					  i, (long long)dec->dpb[i].timestamp,
+					  buffer_index);
+		}
+
+		de->ref_addrs[i][0] =
+			vb2_dma_contig_plane_dma_addr(buf, 0);
+		if (ctx->dst_fmt.pixelformat == V4L2_PIX_FMT_NV12MT_COL128 ||
+		    ctx->dst_fmt.pixelformat == V4L2_PIX_FMT_NV12MT_10_COL128)
+			de->ref_addrs[i][1] =
+				vb2_dma_contig_plane_dma_addr(buf, 1);
+		else
+			de->ref_addrs[i][1] = de->ref_addrs[i][0] +
+				(ctx->dst_fmt.height * 128);
+	}
+
+	/* Move DPB from temp */
+	for (i = 0; i != V4L2_HEVC_DPB_ENTRIES_NUM_MAX; ++i) {
+		aux_q_release(ctx, &s->ref_aux[i]);
+		s->ref_aux[i] = dpb_q_aux[i];
+	}
+
+	/* Unref the old frame aux too - it is either in the DPB or not now */
+	aux_q_release(ctx, &s->frame_aux);
+
+	if (s->mk_aux) {
+		s->frame_aux = aux_q_new(ctx, run->dst->vb2_buf.index);
+
+		if (!s->frame_aux) {
+			v4l2_err(&dev->v4l2_dev,
+				 "Failed to obtain aux storage for frame\n");
+			goto fail;
+		}
+
+		de->frame_aux = aux_q_ref(ctx, s->frame_aux);
+	}
+
+	if (de->dpbno_col != ~0U) {
+		if (de->dpbno_col >= dec->num_active_dpb_entries) {
+			v4l2_err(&dev->v4l2_dev,
+				 "Col ref index %d >= %d\n",
+				 de->dpbno_col,
+				 dec->num_active_dpb_entries);
+		} else {
+			/* Standard requires that the col pic is constant for
+			 * the duration of the pic (text of collocated_ref_idx
+			 * in H265-2 2018 7.4.7.1)
+			 */
+
+			/* Spot the collocated ref in passing */
+			de->col_aux = aux_q_ref(ctx,
+						dpb_q_aux[de->dpbno_col]);
+
+			if (!de->col_aux) {
+				v4l2_warn(&dev->v4l2_dev,
+					  "Missing DPB ent for col\n");
+				/* Need to abort if this fails as P2 may
+				 * explode on bad data
+				 */
+				goto fail;
+			}
+		}
+	}
+
+	de->state = HEVC_D_DECODE_PHASE1;
+	return;
+
+fail:
+	if (de)
+		// Actual error reporting happens in Trigger
+		de->state = HEVC_D_DECODE_ERROR_DONE;
+}
+
+/* Handle PU and COEFF stream overflow
+ *
+ * Returns:
+ * -1  Phase 1 decode error
+ *  0  OK
+ * >0  Out of space (bitmask)
+ */
+
+#define STATUS_COEFF_EXHAUSTED	8
+#define STATUS_PU_EXHAUSTED	16
+
+static int check_status(const struct hevc_d_dev *const dev)
+{
+	const u32 cfstatus = apb_read(dev, RPI_CFSTATUS);
+	const u32 cfnum = apb_read(dev, RPI_CFNUM);
+	u32 status = apb_read(dev, RPI_STATUS);
+
+	/*
+	 * Handle PU and COEFF stream overflow
+	 * This is the definition of successful completion of phase 1.
+	 * It assures that status register is zero and all blocks in each tile
+	 * have completed
+	 */
+	if (cfstatus == cfnum)
+		return 0;
+
+	status &= (STATUS_PU_EXHAUSTED | STATUS_COEFF_EXHAUSTED);
+	if (status)
+		return status;
+
+	return -1;
+}
+
+static void phase2_cb(struct hevc_d_dev *const dev, void *v)
+{
+	struct hevc_d_dec_env *const de = v;
+
+	/* Done with buffers - allow new P1 */
+	hevc_d_hw_irq_active1_enable_claim(dev, 1);
+
+	v4l2_m2m_buf_done(de->frame_buf, VB2_BUF_STATE_DONE);
+	de->frame_buf = NULL;
+
+	media_request_manual_complete(de->req_pin);
+	de->req_pin = NULL;
+
+	dec_env_delete(de);
+}
+
+static void phase2_claimed(struct hevc_d_dev *const dev, void *v)
+{
+	struct hevc_d_dec_env *const de = v;
+	unsigned int i;
+
+	apb_write_vc_addr(dev, RPI_PURBASE, de->pu_base_vc);
+	apb_write_vc_len(dev, RPI_PURSTRIDE, de->pu_stride);
+	apb_write_vc_addr(dev, RPI_COEFFRBASE, de->coeff_base_vc);
+	apb_write_vc_len(dev, RPI_COEFFRSTRIDE, de->coeff_stride);
+
+	apb_write_vc_addr(dev, RPI_OUTYBASE, de->frame_luma_addr);
+	apb_write_vc_addr(dev, RPI_OUTCBASE, de->frame_chroma_addr);
+	apb_write_vc_len(dev, RPI_OUTYSTRIDE, de->luma_stride);
+	apb_write_vc_len(dev, RPI_OUTCSTRIDE, de->chroma_stride);
+
+	for (i = 0; i < 16; i++) {
+		// Strides are in fact unused but fill in anyway
+		apb_write_vc_addr(dev, 0x9000 + 16 * i, de->ref_addrs[i][0]);
+		apb_write_vc_len(dev, 0x9004 + 16 * i, de->luma_stride);
+		apb_write_vc_addr(dev, 0x9008 + 16 * i, de->ref_addrs[i][1]);
+		apb_write_vc_len(dev, 0x900C + 16 * i, de->chroma_stride);
+	}
+
+	apb_write(dev, RPI_CONFIG2, de->rpi_config2);
+	apb_write(dev, RPI_FRAMESIZE, de->rpi_framesize);
+	apb_write(dev, RPI_CURRPOC, de->rpi_currpoc);
+
+	/* collocated reads/writes */
+	apb_write_vc_len(dev, RPI_COLSTRIDE,
+			 de->ctx->colmv_stride);
+	apb_write_vc_len(dev, RPI_MVSTRIDE,
+			 de->ctx->colmv_stride);
+	apb_write_vc_addr(dev, RPI_MVBASE,
+			  !de->frame_aux ? 0 : de->frame_aux->col.addr);
+	apb_write_vc_addr(dev, RPI_COLBASE,
+			  !de->col_aux ? 0 : de->col_aux->col.addr);
+
+	hevc_d_hw_irq_active2_irq(dev, &de->irq_ent, phase2_cb, de);
+
+	apb_write_final(dev, RPI_NUMROWS, de->pic_height_in_ctbs_y);
+}
+
+static void phase1_claimed(struct hevc_d_dev *const dev, void *v);
+
+/* release any and all objects associated with de and reenable phase 1 if
+ * required
+ *///  1 if required
+static void phase1_err_fin(struct hevc_d_dev *const dev,
+			   struct hevc_d_ctx *const ctx,
+			   struct hevc_d_dec_env *const de)
+{
+	/* Return all detached buffers */
+	if (de->src_buf)
+		v4l2_m2m_buf_done(de->src_buf, VB2_BUF_STATE_ERROR);
+	de->src_buf = NULL;
+	if (de->frame_buf)
+		v4l2_m2m_buf_done(de->frame_buf, VB2_BUF_STATE_ERROR);
+	de->frame_buf = NULL;
+
+	if (de->req_pin)
+		media_request_manual_complete(de->req_pin);
+	de->req_pin = NULL;
+
+	dec_env_delete(de);
+
+	/* Reenable phase 0 if we were blocking */
+	if (atomic_add_return(-1, &ctx->p1out) >= HEVC_D_P1BUF_COUNT - 1)
+		v4l2_m2m_job_finish(dev->m2m_dev, ctx->fh.m2m_ctx);
+
+	/* Done with P1-P2 buffers - allow new P1 */
+	hevc_d_hw_irq_active1_enable_claim(dev, 1);
+}
+
+static void phase1_thread(struct hevc_d_dev *const dev, void *v)
+{
+	struct hevc_d_dec_env *const de = v;
+	struct hevc_d_ctx *const ctx = de->ctx;
+
+	struct hevc_d_gptr *const pu_gptr = ctx->pu_bufs + ctx->p2idx;
+	struct hevc_d_gptr *const coeff_gptr = ctx->coeff_bufs + ctx->p2idx;
+
+	if (de->p1_status & STATUS_PU_EXHAUSTED) {
+		if (gptr_realloc_new(dev, pu_gptr, next_size(pu_gptr->size))) {
+			v4l2_err(&dev->v4l2_dev,
+				 "%s: PU realloc (%zx) failed\n",
+				 __func__, pu_gptr->size);
+			goto fail;
+		}
+		v4l2_info(&dev->v4l2_dev, "%s: PU realloc (%zx) OK\n",
+			  __func__, pu_gptr->size);
+	}
+
+	if (de->p1_status & STATUS_COEFF_EXHAUSTED) {
+		if (gptr_realloc_new(dev, coeff_gptr,
+				     next_size(coeff_gptr->size))) {
+			v4l2_err(&dev->v4l2_dev,
+				 "%s: Coeff realloc (%zx) failed\n",
+				 __func__, coeff_gptr->size);
+			goto fail;
+		}
+		v4l2_info(&dev->v4l2_dev, "%s: Coeff realloc (%zx) OK\n",
+			  __func__, coeff_gptr->size);
+	}
+
+	phase1_claimed(dev, de);
+	return;
+
+fail:
+	if (!pu_gptr->addr || !coeff_gptr->addr) {
+		v4l2_err(&dev->v4l2_dev,
+			 "%s: Fatal: failed to reclaim old alloc\n",
+			 __func__);
+		ctx->fatal_err = 1;
+	}
+	phase1_err_fin(dev, ctx, de);
+}
+
+/* Always called in irq context (this is good) */
+static void phase1_cb(struct hevc_d_dev *const dev, void *v)
+{
+	struct hevc_d_dec_env *const de = v;
+	struct hevc_d_ctx *const ctx = de->ctx;
+
+	de->p1_status = check_status(dev);
+
+	if (de->p1_status != 0) {
+		v4l2_info(&dev->v4l2_dev, "%s: Post wait: %#x\n",
+			  __func__, de->p1_status);
+
+		if (de->p1_status < 0)
+			goto fail;
+
+		/* Need to realloc - push onto a thread rather than IRQ */
+		hevc_d_hw_irq_active1_thread(dev, &de->irq_ent,
+					     phase1_thread, de);
+		return;
+	}
+
+	v4l2_m2m_buf_done(de->src_buf, VB2_BUF_STATE_DONE);
+	de->src_buf = NULL;
+
+	/* All phase1 error paths done - it is safe to inc p2idx */
+	ctx->p2idx =
+		(ctx->p2idx + 1 >= HEVC_D_P2BUF_COUNT) ? 0 : ctx->p2idx + 1;
+
+	/* Renable the next setup if we were blocking */
+	if (atomic_add_return(-1, &ctx->p1out) >= HEVC_D_P1BUF_COUNT - 1)
+		v4l2_m2m_job_finish(dev->m2m_dev, ctx->fh.m2m_ctx);
+
+	hevc_d_hw_irq_active2_claim(dev, &de->irq_ent, phase2_claimed, de);
+
+	return;
+
+fail:
+	phase1_err_fin(dev, ctx, de);
+}
+
+static void phase1_claimed(struct hevc_d_dev *const dev, void *v)
+{
+	struct hevc_d_dec_env *const de = v;
+	struct hevc_d_ctx *const ctx = de->ctx;
+
+	const struct hevc_d_gptr * const pu_gptr = ctx->pu_bufs + ctx->p2idx;
+	const struct hevc_d_gptr * const coeff_gptr = ctx->coeff_bufs +
+						      ctx->p2idx;
+
+	if (ctx->fatal_err)
+		goto fail;
+
+	de->pu_base_vc = pu_gptr->addr;
+	de->pu_stride =
+		ALIGN_DOWN(pu_gptr->size / de->pic_height_in_ctbs_y, 64);
+
+	de->coeff_base_vc = coeff_gptr->addr;
+	de->coeff_stride =
+		ALIGN_DOWN(coeff_gptr->size / de->pic_height_in_ctbs_y, 64);
+
+	/* phase1_claimed blocked until cb_phase1 completed so p2idx inc
+	 * in cb_phase1 after error detection
+	 */
+
+	apb_write_vc_addr(dev, RPI_PUWBASE, de->pu_base_vc);
+	apb_write_vc_len(dev, RPI_PUWSTRIDE, de->pu_stride);
+	apb_write_vc_addr(dev, RPI_COEFFWBASE, de->coeff_base_vc);
+	apb_write_vc_len(dev, RPI_COEFFWSTRIDE, de->coeff_stride);
+
+	/* Trigger command FIFO */
+	apb_write(dev, RPI_CFNUM, de->cmd_len);
+
+	/* Claim irq */
+	hevc_d_hw_irq_active1_irq(dev, &de->irq_ent, phase1_cb, de);
+
+	/* Start the h/w */
+	apb_write_vc_addr_final(dev, RPI_CFBASE, de->cmd_addr);
+
+	return;
+
+fail:
+	phase1_err_fin(dev, ctx, de);
+}
+
+static void dec_state_delete(struct hevc_d_ctx *const ctx)
+{
+	unsigned int i;
+	struct hevc_d_dec_state *const s = ctx->state;
+
+	if (!s)
+		return;
+	ctx->state = NULL;
+
+	free_ps_info(s);
+
+	for (i = 0; i != HEVC_MAX_REFS; ++i)
+		aux_q_release(ctx, &s->ref_aux[i]);
+	aux_q_release(ctx, &s->frame_aux);
+
+	kfree(s);
+}
+
+struct irq_sync {
+	atomic_t done;
+	wait_queue_head_t wq;
+	struct hevc_d_hw_irq_ent irq_ent;
+};
+
+static void phase2_sync_claimed(struct hevc_d_dev *const dev, void *v)
+{
+	struct irq_sync *const sync = v;
+
+	atomic_set(&sync->done, 1);
+	wake_up(&sync->wq);
+}
+
+static void phase1_sync_claimed(struct hevc_d_dev *const dev, void *v)
+{
+	struct irq_sync *const sync = v;
+
+	hevc_d_hw_irq_active1_enable_claim(dev, 1);
+	hevc_d_hw_irq_active2_claim(dev, &sync->irq_ent, phase2_sync_claimed, sync);
+}
+
+/* Sync with IRQ operations
+ *
+ * Claims phase1 and phase2 in turn and waits for the phase2 claim so any
+ * pending IRQ ops will have completed by the time this returns
+ *
+ * phase1 has counted enables so must reenable once claimed
+ * phase2 has unlimited enables
+ */
+static void irq_sync(struct hevc_d_dev *const dev)
+{
+	struct irq_sync sync;
+
+	atomic_set(&sync.done, 0);
+	init_waitqueue_head(&sync.wq);
+
+	hevc_d_hw_irq_active1_claim(dev, &sync.irq_ent, phase1_sync_claimed, &sync);
+	wait_event(sync.wq, atomic_read(&sync.done));
+}
+
+static void h265_ctx_uninit(struct hevc_d_dev *const dev, struct hevc_d_ctx *ctx)
+{
+	unsigned int i;
+
+	dec_env_uninit(ctx);
+	dec_state_delete(ctx);
+
+	/*
+	 * dec_env & state must be killed before this to release the buffer to
+	 * the free pool
+	 */
+	aux_q_uninit(ctx);
+
+	for (i = 0; i != ARRAY_SIZE(ctx->pu_bufs); ++i)
+		gptr_free(dev, ctx->pu_bufs + i);
+	for (i = 0; i != ARRAY_SIZE(ctx->coeff_bufs); ++i)
+		gptr_free(dev, ctx->coeff_bufs + i);
+}
+
+void hevc_d_h265_stop(struct hevc_d_ctx *ctx)
+{
+	struct hevc_d_dev *const dev = ctx->dev;
+
+	irq_sync(dev);
+	h265_ctx_uninit(dev, ctx);
+}
+
+int hevc_d_h265_start(struct hevc_d_ctx *ctx)
+{
+	struct hevc_d_dev *const dev = ctx->dev;
+	unsigned int i;
+
+	unsigned int w = ctx->dst_fmt.width;
+	unsigned int h = ctx->dst_fmt.height;
+	unsigned int wxh;
+	size_t pu_alloc;
+	size_t coeff_alloc;
+
+	/* Generate a sanitised WxH for memory alloc.  Assume HD if unset */
+	if (w == 0)
+		w = 1920;
+	if (w > 4096)
+		w = 4096;
+	if (h == 0)
+		h = 1088;
+	if (h > 4096)
+		h = 4096;
+	wxh = w * h;
+
+	ctx->fatal_err = 0;
+	ctx->dec0 = NULL;
+	ctx->state = kzalloc(sizeof(*ctx->state), GFP_KERNEL);
+	if (!ctx->state) {
+		v4l2_err(&dev->v4l2_dev, "Failed to allocate decode state\n");
+		goto fail;
+	}
+
+	if (dec_env_init(ctx) != 0) {
+		v4l2_err(&dev->v4l2_dev, "Failed to allocate decode envs\n");
+		goto fail;
+	}
+
+	coeff_alloc = hevc_d_round_up_size(wxh);
+	pu_alloc = hevc_d_round_up_size(wxh / 4);
+	for (i = 0; i != ARRAY_SIZE(ctx->pu_bufs); ++i) {
+		/* Don't actually need a kernel mapping here */
+		if (gptr_alloc(dev, ctx->pu_bufs + i, pu_alloc,
+			       DMA_ATTR_NO_KERNEL_MAPPING)) {
+			v4l2_err(&dev->v4l2_dev,
+				 "Failed to alloc %#zx PU%d buffer\n",
+				 pu_alloc, i);
+			goto fail;
+		}
+		if (gptr_alloc(dev, ctx->coeff_bufs + i, coeff_alloc,
+			       DMA_ATTR_NO_KERNEL_MAPPING)) {
+			v4l2_err(&dev->v4l2_dev,
+				 "Failed to alloc %#zx Coeff%d buffer\n",
+				 pu_alloc, i);
+			goto fail;
+		}
+	}
+	aux_q_init(ctx);
+
+	return 0;
+
+fail:
+	h265_ctx_uninit(dev, ctx);
+	return -ENOMEM;
+}
+
+void hevc_d_h265_trigger(struct hevc_d_ctx *ctx)
+{
+	struct hevc_d_dev *const dev = ctx->dev;
+	struct hevc_d_dec_env *const de = ctx->dec0;
+	struct vb2_v4l2_buffer *src_buf;
+	struct media_request *req;
+
+	src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+	req = src_buf->vb2_buf.req_obj.req;
+
+	switch (!de ? HEVC_D_DECODE_ERROR_DONE : de->state) {
+	default:
+		v4l2_err(&dev->v4l2_dev, "%s: Unexpected state: %d\n", __func__,
+			 de->state);
+		fallthrough;
+	case HEVC_D_DECODE_ERROR_DONE:
+		ctx->dec0 = NULL;
+		dec_env_delete(de);
+		v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev, ctx->fh.m2m_ctx,
+						 VB2_BUF_STATE_ERROR);
+		media_request_manual_complete(req);
+		break;
+
+	case HEVC_D_DECODE_PHASE1:
+		ctx->dec0 = NULL;
+
+		ctx->p1idx = (ctx->p1idx + 1 >= HEVC_D_P1BUF_COUNT) ?
+							0 : ctx->p1idx + 1;
+
+		/* We know we have src & dst so no need to test */
+		de->src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+		de->frame_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
+		de->req_pin = req;
+
+		/* We could get rid of the src buffer here if we've already
+		 * copied it, but we don't copy the last buffer unless it
+		 * didn't return a contig dma addr, and that shouldn't happen
+		 */
+
+		/* Enable the next setup if our Q isn't too big */
+		if (atomic_add_return(1, &ctx->p1out) < HEVC_D_P1BUF_COUNT)
+			v4l2_m2m_job_finish(dev->m2m_dev, ctx->fh.m2m_ctx);
+
+		hevc_d_hw_irq_active1_claim(dev, &de->irq_ent, phase1_claimed,
+					    de);
+		break;
+	}
+}
+
+static int try_ctrl_sps(struct v4l2_ctrl *ctrl)
+{
+	const struct v4l2_ctrl_hevc_sps *const sps = ctrl->p_new.p_hevc_sps;
+	struct hevc_d_ctx *const ctx = ctrl->priv;
+	struct hevc_d_dev *const dev = ctx->dev;
+
+	if (sps->chroma_format_idc != 1) {
+		v4l2_warn(&dev->v4l2_dev,
+			  "Chroma format (%d) unsupported\n",
+			  sps->chroma_format_idc);
+		return -EINVAL;
+	}
+
+	if (sps->bit_depth_luma_minus8 != 0 &&
+	    sps->bit_depth_luma_minus8 != 2) {
+		v4l2_warn(&dev->v4l2_dev,
+			  "Luma depth (%d) unsupported\n",
+			  sps->bit_depth_luma_minus8 + 8);
+		return -EINVAL;
+	}
+
+	if (sps->bit_depth_luma_minus8 != sps->bit_depth_chroma_minus8) {
+		v4l2_warn(&dev->v4l2_dev,
+			  "Chroma depth (%d) != Luma depth (%d)\n",
+			  sps->bit_depth_chroma_minus8 + 8,
+			  sps->bit_depth_luma_minus8 + 8);
+		return -EINVAL;
+	}
+
+	if (!sps->pic_width_in_luma_samples ||
+	    !sps->pic_height_in_luma_samples ||
+	    sps->pic_width_in_luma_samples > 4096 ||
+	    sps->pic_height_in_luma_samples > 4096) {
+		v4l2_warn(&dev->v4l2_dev,
+			  "Bad sps width (%u) x height (%u)\n",
+			  sps->pic_width_in_luma_samples,
+			  sps->pic_height_in_luma_samples);
+		return -EINVAL;
+	}
+
+	if (!ctx->dst_fmt_set)
+		return 0;
+
+	if ((sps->bit_depth_luma_minus8 == 0 &&
+	     ctx->dst_fmt.pixelformat != V4L2_PIX_FMT_NV12MT_COL128 &&
+	     ctx->dst_fmt.pixelformat != V4L2_PIX_FMT_NV12_COL128) ||
+	    (sps->bit_depth_luma_minus8 == 2 &&
+	     ctx->dst_fmt.pixelformat != V4L2_PIX_FMT_NV12MT_10_COL128 &&
+	     ctx->dst_fmt.pixelformat != V4L2_PIX_FMT_NV12_10_COL128)) {
+		v4l2_warn(&dev->v4l2_dev,
+			  "SPS luma depth %d does not match capture format\n",
+			  sps->bit_depth_luma_minus8 + 8);
+		return -EINVAL;
+	}
+
+	if (sps->pic_width_in_luma_samples > ctx->dst_fmt.width ||
+	    sps->pic_height_in_luma_samples > ctx->dst_fmt.height) {
+		v4l2_warn(&dev->v4l2_dev,
+			  "SPS size (%dx%d) > capture size (%d,%d)\n",
+			  sps->pic_width_in_luma_samples,
+			  sps->pic_height_in_luma_samples,
+			  ctx->dst_fmt.width,
+			  ctx->dst_fmt.height);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+const struct v4l2_ctrl_ops hevc_d_hevc_sps_ctrl_ops = {
+	.try_ctrl = try_ctrl_sps,
+};
+
+static int try_ctrl_pps(struct v4l2_ctrl *ctrl)
+{
+	const struct v4l2_ctrl_hevc_pps *const pps = ctrl->p_new.p_hevc_pps;
+	struct hevc_d_ctx *const ctx = ctrl->priv;
+	struct hevc_d_dev *const dev = ctx->dev;
+
+	if ((pps->flags &
+	     V4L2_HEVC_PPS_FLAG_ENTROPY_CODING_SYNC_ENABLED) &&
+	    (pps->flags &
+	     V4L2_HEVC_PPS_FLAG_TILES_ENABLED) &&
+	    (pps->num_tile_columns_minus1 || pps->num_tile_rows_minus1)) {
+		v4l2_warn(&dev->v4l2_dev,
+			  "WPP + Tiles not supported\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+const struct v4l2_ctrl_ops hevc_d_hevc_pps_ctrl_ops = {
+	.try_ctrl = try_ctrl_pps,
+};
+
+void hevc_d_device_run(void *priv)
+{
+	struct hevc_d_ctx *const ctx = priv;
+	struct hevc_d_dev *const dev = ctx->dev;
+	struct hevc_d_run run = {};
+	struct media_request *src_req;
+
+	run.src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
+	run.dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
+
+	if (!run.src || !run.dst) {
+		v4l2_err(&dev->v4l2_dev, "%s: Missing buffer: src=%p, dst=%p\n",
+			 __func__, run.src, run.dst);
+		goto fail;
+	}
+
+	/* Apply request(s) controls */
+	src_req = run.src->vb2_buf.req_obj.req;
+	if (!src_req) {
+		v4l2_err(&dev->v4l2_dev, "%s: Missing request\n", __func__);
+		goto fail;
+	}
+
+	v4l2_ctrl_request_setup(src_req, &ctx->hdl);
+
+	switch (ctx->src_fmt.pixelformat) {
+	case V4L2_PIX_FMT_HEVC_SLICE:
+	{
+		const struct v4l2_ctrl *ctrl;
+
+		run.h265.sps =
+			hevc_d_find_control_data(ctx,
+						 V4L2_CID_STATELESS_HEVC_SPS);
+		run.h265.pps =
+			hevc_d_find_control_data(ctx,
+						 V4L2_CID_STATELESS_HEVC_PPS);
+		run.h265.dec =
+			hevc_d_find_control_data(ctx,
+						 V4L2_CID_STATELESS_HEVC_DECODE_PARAMS);
+
+		ctrl = hevc_d_find_ctrl(ctx,
+					V4L2_CID_STATELESS_HEVC_SLICE_PARAMS);
+		if (!ctrl || !ctrl->elems) {
+			v4l2_err(&dev->v4l2_dev, "%s: Missing slice params\n",
+				 __func__);
+			goto fail;
+		}
+		run.h265.slice_ents = ctrl->elems;
+		run.h265.slice_params = ctrl->p_cur.p;
+
+		run.h265.scaling_matrix =
+			hevc_d_find_control_data(ctx,
+						 V4L2_CID_STATELESS_HEVC_SCALING_MATRIX);
+		break;
+	}
+
+	default:
+		break;
+	}
+
+	v4l2_m2m_buf_copy_metadata(run.src, run.dst, true);
+
+	hevc_d_h265_setup(ctx, &run);
+
+	/* Complete request(s) controls */
+	v4l2_ctrl_request_complete(src_req, &ctx->hdl);
+
+	hevc_d_h265_trigger(ctx);
+	return;
+
+fail:
+	/* We really shouldn't get here but tidy up what we can */
+	v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev, ctx->fh.m2m_ctx,
+					 VB2_BUF_STATE_ERROR);
+	media_request_manual_complete(src_req);
+}
diff --git a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.h b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.h
new file mode 100644
index 000000000000..775fe7de5dd6
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_h265.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Raspberry Pi HEVC driver
+ *
+ * Copyright (C) 2024 Raspberry Pi Ltd
+ *
+ */
+
+#ifndef _HEVC_D_H265_H_
+#define _HEVC_D_H265_H_
+#include "hevc_d.h"
+
+extern const struct v4l2_ctrl_ops hevc_d_hevc_sps_ctrl_ops;
+extern const struct v4l2_ctrl_ops hevc_d_hevc_pps_ctrl_ops;
+
+void hevc_d_h265_setup(struct hevc_d_ctx *ctx, struct hevc_d_run *run);
+int hevc_d_h265_start(struct hevc_d_ctx *ctx);
+void hevc_d_h265_stop(struct hevc_d_ctx *ctx);
+void hevc_d_h265_trigger(struct hevc_d_ctx *ctx);
+
+void hevc_d_device_run(void *priv);
+
+#endif
diff --git a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.c b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.c
new file mode 100644
index 000000000000..5030a6909301
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.c
@@ -0,0 +1,376 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Raspberry Pi HEVC driver
+ *
+ * Copyright (C) 2024 Raspberry Pi Ltd
+ *
+ * Based on the Cedrus VPU driver, that is:
+ *
+ * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com>
+ * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ * Copyright (C) 2018 Bootlin
+ */
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#include <media/videobuf2-core.h>
+#include <media/v4l2-mem2mem.h>
+
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
+#include "hevc_d.h"
+#include "hevc_d_hw.h"
+
+static void pre_irq(struct hevc_d_dev *dev, struct hevc_d_hw_irq_ent *ient,
+		    hevc_d_irq_callback cb, void *v,
+		    struct hevc_d_hw_irq_ctrl *ictl)
+{
+	unsigned long flags;
+
+	if (ictl->irq) {
+		v4l2_err(&dev->v4l2_dev, "Attempt to claim IRQ when already claimed\n");
+		return;
+	}
+
+	ient->cb = cb;
+	ient->v = v;
+
+	spin_lock_irqsave(&ictl->lock, flags);
+	ictl->irq = ient;
+	ictl->no_sched++;
+	spin_unlock_irqrestore(&ictl->lock, flags);
+}
+
+/* Should be called from inside ictl->lock */
+static inline bool sched_enabled(const struct hevc_d_hw_irq_ctrl * const ictl)
+{
+	return ictl->no_sched <= 0 && ictl->enable;
+}
+
+/* Should be called from inside ictl->lock & after checking sched_enabled() */
+static inline void set_claimed(struct hevc_d_hw_irq_ctrl * const ictl)
+{
+	if (ictl->enable > 0)
+		--ictl->enable;
+	ictl->no_sched = 1;
+}
+
+/* Should be called from inside ictl->lock */
+static struct hevc_d_hw_irq_ent *get_sched(struct hevc_d_hw_irq_ctrl * const ictl)
+{
+	struct hevc_d_hw_irq_ent *ient;
+
+	if (!sched_enabled(ictl))
+		return NULL;
+
+	ient = ictl->claim;
+	if (!ient)
+		return NULL;
+	ictl->claim = ient->next;
+
+	set_claimed(ictl);
+	return ient;
+}
+
+/* Run a callback & check to see if there is anything else to run */
+static void sched_cb(struct hevc_d_dev * const dev,
+		     struct hevc_d_hw_irq_ctrl * const ictl,
+		     struct hevc_d_hw_irq_ent *ient)
+{
+	while (ient) {
+		unsigned long flags;
+
+		ient->cb(dev, ient->v);
+
+		spin_lock_irqsave(&ictl->lock, flags);
+
+		/*
+		 * Always dec no_sched after cb exec - must have been set
+		 * on entry to cb
+		 */
+		--ictl->no_sched;
+		ient = get_sched(ictl);
+
+		spin_unlock_irqrestore(&ictl->lock, flags);
+	}
+}
+
+/* Should only ever be called from its own IRQ cb so no lock required */
+static void pre_thread(struct hevc_d_dev *dev,
+		       struct hevc_d_hw_irq_ent *ient,
+		       hevc_d_irq_callback cb, void *v,
+		       struct hevc_d_hw_irq_ctrl *ictl)
+{
+	ient->cb = cb;
+	ient->v = v;
+	ictl->irq = ient;
+	ictl->thread_reqed = true;
+	ictl->no_sched++;	/* This is unwound in do_thread */
+}
+
+/* Called in irq context */
+static void do_irq(struct hevc_d_dev * const dev,
+		   struct hevc_d_hw_irq_ctrl * const ictl)
+{
+	struct hevc_d_hw_irq_ent *ient;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ictl->lock, flags);
+	ient = ictl->irq;
+	ictl->irq = NULL;
+	spin_unlock_irqrestore(&ictl->lock, flags);
+
+	sched_cb(dev, ictl, ient);
+}
+
+static void do_claim(struct hevc_d_dev * const dev,
+		     struct hevc_d_hw_irq_ent *ient,
+		     const hevc_d_irq_callback cb, void * const v,
+		     struct hevc_d_hw_irq_ctrl * const ictl)
+{
+	unsigned long flags;
+
+	ient->next = NULL;
+	ient->cb = cb;
+	ient->v = v;
+
+	spin_lock_irqsave(&ictl->lock, flags);
+
+	if (ictl->claim) {
+		/* If we have a Q then add to end */
+		ictl->tail->next = ient;
+		ictl->tail = ient;
+		ient = NULL;
+	} else if (!sched_enabled(ictl)) {
+		/* Empty Q but other activity in progress so Q */
+		ictl->claim = ient;
+		ictl->tail = ient;
+		ient = NULL;
+	} else {
+		/*
+		 * Nothing else going on - schedule immediately and
+		 * prevent anything else scheduling claims
+		 */
+		set_claimed(ictl);
+	}
+
+	spin_unlock_irqrestore(&ictl->lock, flags);
+
+	sched_cb(dev, ictl, ient);
+}
+
+/* Enable n claims.
+ * n < 0   set to unlimited (default on init)
+ * n = 0   if previously unlimited then disable otherwise nop
+ * n > 0   if previously unlimited then set to n enables
+ *         otherwise add n enables
+ * The enable count is automatically decremented every time a claim is run
+ */
+static void do_enable_claim(struct hevc_d_dev * const dev,
+			    int n,
+			    struct hevc_d_hw_irq_ctrl * const ictl)
+{
+	unsigned long flags;
+	struct hevc_d_hw_irq_ent *ient;
+
+	spin_lock_irqsave(&ictl->lock, flags);
+	ictl->enable = n < 0 ? -1 : ictl->enable <= 0 ? n : ictl->enable + n;
+	ient = get_sched(ictl);
+	spin_unlock_irqrestore(&ictl->lock, flags);
+
+	sched_cb(dev, ictl, ient);
+}
+
+static void ictl_init(struct hevc_d_hw_irq_ctrl * const ictl, int enables)
+{
+	spin_lock_init(&ictl->lock);
+	ictl->claim = NULL;
+	ictl->tail = NULL;
+	ictl->irq = NULL;
+	ictl->no_sched = 0;
+	ictl->enable = enables;
+	ictl->thread_reqed = false;
+}
+
+static void ictl_uninit(struct hevc_d_hw_irq_ctrl * const ictl)
+{
+	/* Nothing to do */
+}
+
+static irqreturn_t hevc_d_irq_irq(int irq, void *data)
+{
+	struct hevc_d_dev * const dev = data;
+	__u32 ictrl;
+
+	ictrl = irq_read(dev, ARG_IC_ICTRL);
+	if (!(ictrl & ARG_IC_ICTRL_ALL_IRQ_MASK)) {
+		v4l2_warn(&dev->v4l2_dev, "IRQ but no IRQ bits set\n");
+		return IRQ_NONE;
+	}
+
+	/* Cancel any/all irqs */
+	irq_write(dev, ARG_IC_ICTRL, ictrl & ~ARG_IC_ICTRL_SET_ZERO_MASK);
+
+	/*
+	 * Service Active2 before Active1 so Phase 1 can transition to Phase 2
+	 * without delay
+	 */
+	if (ictrl & ARG_IC_ICTRL_ACTIVE2_INT_SET)
+		do_irq(dev, &dev->ic_active2);
+	if (ictrl & ARG_IC_ICTRL_ACTIVE1_INT_SET)
+		do_irq(dev, &dev->ic_active1);
+
+	return dev->ic_active1.thread_reqed || dev->ic_active2.thread_reqed ?
+		IRQ_WAKE_THREAD : IRQ_HANDLED;
+}
+
+static void do_thread(struct hevc_d_dev * const dev,
+		      struct hevc_d_hw_irq_ctrl *const ictl)
+{
+	unsigned long flags;
+	struct hevc_d_hw_irq_ent *ient = NULL;
+
+	spin_lock_irqsave(&ictl->lock, flags);
+
+	if (ictl->thread_reqed) {
+		ient = ictl->irq;
+		ictl->thread_reqed = false;
+		ictl->irq = NULL;
+	}
+
+	spin_unlock_irqrestore(&ictl->lock, flags);
+
+	sched_cb(dev, ictl, ient);
+}
+
+static irqreturn_t hevc_d_irq_thread(int irq, void *data)
+{
+	struct hevc_d_dev * const dev = data;
+
+	do_thread(dev, &dev->ic_active1);
+	do_thread(dev, &dev->ic_active2);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * May only be called from Active1 CB
+ * IRQs should not be expected until execution continues in the cb
+ */
+void hevc_d_hw_irq_active1_thread(struct hevc_d_dev *dev,
+				  struct hevc_d_hw_irq_ent *ient,
+				  hevc_d_irq_callback thread_cb, void *ctx)
+{
+	pre_thread(dev, ient, thread_cb, ctx, &dev->ic_active1);
+}
+
+void hevc_d_hw_irq_active1_enable_claim(struct hevc_d_dev *dev,
+					int n)
+{
+	do_enable_claim(dev, n, &dev->ic_active1);
+}
+
+void hevc_d_hw_irq_active1_claim(struct hevc_d_dev *dev,
+				 struct hevc_d_hw_irq_ent *ient,
+				 hevc_d_irq_callback ready_cb, void *ctx)
+{
+	do_claim(dev, ient, ready_cb, ctx, &dev->ic_active1);
+}
+
+void hevc_d_hw_irq_active1_irq(struct hevc_d_dev *dev,
+			       struct hevc_d_hw_irq_ent *ient,
+			       hevc_d_irq_callback irq_cb, void *ctx)
+{
+	pre_irq(dev, ient, irq_cb, ctx, &dev->ic_active1);
+}
+
+void hevc_d_hw_irq_active2_claim(struct hevc_d_dev *dev,
+				 struct hevc_d_hw_irq_ent *ient,
+				 hevc_d_irq_callback ready_cb, void *ctx)
+{
+	do_claim(dev, ient, ready_cb, ctx, &dev->ic_active2);
+}
+
+void hevc_d_hw_irq_active2_irq(struct hevc_d_dev *dev,
+			       struct hevc_d_hw_irq_ent *ient,
+			       hevc_d_irq_callback irq_cb, void *ctx)
+{
+	pre_irq(dev, ient, irq_cb, ctx, &dev->ic_active2);
+}
+
+int hevc_d_hw_probe(struct hevc_d_dev *dev)
+{
+	struct rpi_firmware *firmware;
+	struct device_node *node;
+	__u32 irq_stat;
+	int irq_dec;
+	int ret = 0;
+
+	ictl_init(&dev->ic_active1, HEVC_D_P2BUF_COUNT);
+	ictl_init(&dev->ic_active2, HEVC_D_ICTL_ENABLE_UNLIMITED);
+
+	dev->base_irq = devm_platform_ioremap_resource_byname(dev->pdev, "intc");
+	if (IS_ERR(dev->base_irq))
+		return PTR_ERR(dev->base_irq);
+
+	dev->base_h265 = devm_platform_ioremap_resource_byname(dev->pdev, "hevc");
+	if (IS_ERR(dev->base_h265))
+		return PTR_ERR(dev->base_h265);
+
+	dev->clock = devm_clk_get(&dev->pdev->dev, NULL);
+	if (IS_ERR(dev->clock))
+		return PTR_ERR(dev->clock);
+
+	node = rpi_firmware_find_node();
+	if (!node)
+		return -EINVAL;
+
+	firmware = rpi_firmware_get(node);
+	of_node_put(node);
+	if (!firmware)
+		return -EPROBE_DEFER;
+
+	dev->max_clock_rate = rpi_firmware_clk_get_max_rate(firmware,
+							    RPI_FIRMWARE_HEVC_CLK_ID);
+	rpi_firmware_put(firmware);
+
+	dev->cache_align = dma_get_cache_alignment();
+
+	/* Disable IRQs & reset anything pending */
+	irq_write(dev, 0,
+		  ARG_IC_ICTRL_ACTIVE1_EN_SET | ARG_IC_ICTRL_ACTIVE2_EN_SET);
+	irq_stat = irq_read(dev, 0);
+	irq_write(dev, 0, irq_stat);
+
+	irq_dec = platform_get_irq(dev->pdev, 0);
+	if (irq_dec <= 0)
+		return irq_dec;
+	ret = devm_request_threaded_irq(dev->dev, irq_dec,
+					hevc_d_irq_irq,
+					hevc_d_irq_thread,
+					0, dev_name(dev->dev), dev);
+	if (ret)
+		dev_err(dev->dev, "Failed to request IRQ - %d\n", ret);
+
+	return ret;
+}
+
+void hevc_d_hw_remove(struct hevc_d_dev *dev)
+{
+	/*
+	 * IRQ auto freed on unload so no need to do it here
+	 * ioremap auto freed on unload
+	 */
+	ictl_uninit(&dev->ic_active1);
+	ictl_uninit(&dev->ic_active2);
+}
+
diff --git a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.h b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.h
new file mode 100644
index 000000000000..8d91931aadf2
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_hw.h
@@ -0,0 +1,303 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Raspberry Pi HEVC driver
+ *
+ * Copyright (C) 2024 Raspberry Pi Ltd
+ *
+ * Based on the Cedrus VPU driver, that is:
+ *
+ * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com>
+ * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ * Copyright (C) 2018 Bootlin
+ */
+
+#ifndef _HEVC_D_HW_H_
+#define _HEVC_D_HW_H_
+
+struct hevc_d_hw_irq_ent {
+	struct hevc_d_hw_irq_ent *next;
+	hevc_d_irq_callback cb;
+	void *v;
+};
+
+/* Phase 1 Register offsets */
+
+#define RPI_SPS0 0
+#define RPI_SPS1 4
+#define RPI_PPS 8
+#define RPI_SLICE 12
+#define RPI_TILESTART 16
+#define RPI_TILEEND 20
+#define RPI_SLICESTART 24
+#define RPI_MODE 28
+#define RPI_LEFT0 32
+#define RPI_LEFT1 36
+#define RPI_LEFT2 40
+#define RPI_LEFT3 44
+#define RPI_QP 48
+#define RPI_CONTROL 52
+#define RPI_STATUS 56
+#define RPI_VERSION 60
+#define RPI_BFBASE 64
+#define RPI_BFNUM 68
+#define RPI_BFCONTROL 72
+#define RPI_BFSTATUS 76
+#define RPI_PUWBASE 80
+#define RPI_PUWSTRIDE 84
+#define RPI_COEFFWBASE 88
+#define RPI_COEFFWSTRIDE 92
+#define RPI_SLICECMDS 96
+#define RPI_BEGINTILEEND 100
+#define RPI_TRANSFER 104
+#define RPI_CFBASE 108
+#define RPI_CFNUM 112
+#define RPI_CFSTATUS 116
+
+/* Phase 2 Register offsets */
+
+#define RPI_PURBASE 0x8000
+#define RPI_PURSTRIDE 0x8004
+#define RPI_COEFFRBASE 0x8008
+#define RPI_COEFFRSTRIDE 0x800C
+#define RPI_NUMROWS 0x8010
+#define RPI_CONFIG2 0x8014
+#define RPI_OUTYBASE 0x8018
+#define RPI_OUTYSTRIDE 0x801C
+#define RPI_OUTCBASE 0x8020
+#define RPI_OUTCSTRIDE 0x8024
+#define RPI_STATUS2 0x8028
+#define RPI_FRAMESIZE 0x802C
+#define RPI_MVBASE 0x8030
+#define RPI_MVSTRIDE 0x8034
+#define RPI_COLBASE 0x8038
+#define RPI_COLSTRIDE 0x803C
+#define RPI_CURRPOC 0x8040
+
+/*
+ * Write a general register value
+ * Order is unimportant
+ */
+static inline void apb_write(const struct hevc_d_dev * const dev,
+			     const unsigned int offset, const u32 val)
+{
+	writel_relaxed(val, dev->base_h265 + offset);
+}
+
+/* Write the final register value that actually starts the phase */
+static inline void apb_write_final(const struct hevc_d_dev * const dev,
+				   const unsigned int offset, const u32 val)
+{
+	writel(val, dev->base_h265 + offset);
+}
+
+static inline u32 apb_read(const struct hevc_d_dev * const dev,
+			   const unsigned int offset)
+{
+	return readl(dev->base_h265 + offset);
+}
+
+static inline void irq_write(const struct hevc_d_dev * const dev,
+			     const unsigned int offset, const u32 val)
+{
+	writel(val, dev->base_irq + offset);
+}
+
+static inline u32 irq_read(const struct hevc_d_dev * const dev,
+			   const unsigned int offset)
+{
+	return readl(dev->base_irq + offset);
+}
+
+static inline void apb_write_vc_addr(const struct hevc_d_dev * const dev,
+				     const unsigned int offset,
+				     const dma_addr_t a)
+{
+	apb_write(dev, offset, (u32)(a >> 6));
+}
+
+static inline void apb_write_vc_addr_final(const struct hevc_d_dev * const dev,
+					   const unsigned int offset,
+					   const dma_addr_t a)
+{
+	apb_write_final(dev, offset, (u32)(a >> 6));
+}
+
+static inline void apb_write_vc_len(const struct hevc_d_dev * const dev,
+				    const unsigned int offset,
+				    const unsigned int x)
+{
+	apb_write(dev, offset, (x + 63) >> 6);
+}
+
+/* *ARG_IC_ICTRL - Interrupt control for ARGON Core*
+ * Offset (byte space) = 40'h2b10000
+ * Physical Address (byte space) = 40'h7eb10000
+ * Verilog Macro Address = `ARG_IC_REG_START + `ARGON_INTCTRL_ICTRL
+ * Reset Value = 32'b100x100x_100xxxxx_xxxxxxx0_x100x100
+ * Access = RW (32-bit only)
+ * Interrupt control logic for ARGON Core.
+ */
+#define ARG_IC_ICTRL 0
+
+/* acc=LWC ACTIVE1_INT FIELD ACCESS: LWC
+ *
+ * Interrupt 1
+ * This is set and held when an hevc_active1 interrupt edge is detected
+ * The polarity of the edge is set by the ACTIVE1_EDGE field
+ * Write a 1 to this bit to clear down the latched interrupt
+ * The latched interrupt is only enabled out onto the interrupt line if
+ * ACTIVE1_EN is set
+ * Reset value is *0* decimal.
+ */
+#define ARG_IC_ICTRL_ACTIVE1_INT_SET		BIT(0)
+
+/* ACTIVE1_EDGE Sets the polarity of the interrupt edge detection logic
+ * This logic detects edges of the hevc_active1 line from the argon core
+ * 0 = negedge, 1 = posedge
+ * Reset value is *0* decimal.
+ */
+#define ARG_IC_ICTRL_ACTIVE1_EDGE_SET		BIT(1)
+
+/* ACTIVE1_EN Enables ACTIVE1_INT out onto the argon interrupt line.
+ * If this isn't set, the interrupt logic will work but no interrupt will be
+ * set to the interrupt controller
+ * Reset value is *1* decimal.
+ *
+ * [JC] The above appears to be a lie - if unset then b0 is never set
+ */
+#define ARG_IC_ICTRL_ACTIVE1_EN_SET		BIT(2)
+
+/* acc=RO ACTIVE1_STATUS FIELD ACCESS: RO
+ *
+ * The current status of the hevc_active1 signal
+ */
+#define ARG_IC_ICTRL_ACTIVE1_STATUS_SET		BIT(3)
+
+/* acc=LWC ACTIVE2_INT FIELD ACCESS: LWC
+ *
+ * Interrupt 2
+ * This is set and held when an hevc_active2 interrupt edge is detected
+ * The polarity of the edge is set by the ACTIVE2_EDGE field
+ * Write a 1 to this bit to clear down the latched interrupt
+ * The latched interrupt is only enabled out onto the interrupt line if
+ * ACTIVE2_EN is set
+ * Reset value is *0* decimal.
+ */
+#define ARG_IC_ICTRL_ACTIVE2_INT_SET		BIT(4)
+
+/* ACTIVE2_EDGE Sets the polarity of the interrupt edge detection logic
+ * This logic detects edges of the hevc_active2 line from the argon core
+ * 0 = negedge, 1 = posedge
+ * Reset value is *0* decimal.
+ */
+#define ARG_IC_ICTRL_ACTIVE2_EDGE_SET		BIT(5)
+
+/* ACTIVE2_EN Enables ACTIVE2_INT out onto the argon interrupt line.
+ * If this isn't set, the interrupt logic will work but no interrupt will be
+ * set to the interrupt controller
+ * Reset value is *1* decimal.
+ */
+#define ARG_IC_ICTRL_ACTIVE2_EN_SET		BIT(6)
+
+/* acc=RO ACTIVE2_STATUS FIELD ACCESS: RO
+ *
+ * The current status of the hevc_active2 signal
+ */
+#define ARG_IC_ICTRL_ACTIVE2_STATUS_SET		BIT(7)
+
+/* TEST_INT Forces the argon int high for test purposes.
+ * Reset value is *0* decimal.
+ */
+#define ARG_IC_ICTRL_TEST_INT			BIT(8)
+#define ARG_IC_ICTRL_SPARE			BIT(9)
+
+/* acc=RO VP9_INTERRUPT_STATUS FIELD ACCESS: RO
+ *
+ * The current status of the vp9_interrupt signal
+ */
+#define ARG_IC_ICTRL_VP9_INTERRUPT_STATUS	BIT(10)
+
+/* AIO_INT_ENABLE 1 = Or the AIO int in with the Argon int so the VPU can see
+ * it
+ * 0 = the AIO int is masked. (It should still be connected to the GIC though).
+ */
+#define ARG_IC_ICTRL_AIO_INT_ENABLE		BIT(20)
+#define ARG_IC_ICTRL_H264_ACTIVE_INT		BIT(21)
+#define ARG_IC_ICTRL_H264_ACTIVE_EDGE		BIT(22)
+#define ARG_IC_ICTRL_H264_ACTIVE_EN		BIT(23)
+#define ARG_IC_ICTRL_H264_ACTIVE_STATUS		BIT(24)
+#define ARG_IC_ICTRL_H264_INTERRUPT_INT		BIT(25)
+#define ARG_IC_ICTRL_H264_INTERRUPT_EDGE	BIT(26)
+#define ARG_IC_ICTRL_H264_INTERRUPT_EN		BIT(27)
+
+/* acc=RO H264_INTERRUPT_STATUS FIELD ACCESS: RO
+ *
+ * The current status of the h264_interrupt signal
+ */
+#define ARG_IC_ICTRL_H264_INTERRUPT_STATUS	BIT(28)
+
+/* acc=LWC VP9_INTERRUPT_INT FIELD ACCESS: LWC
+ *
+ * Interrupt 1
+ * This is set and held when an vp9_interrupt interrupt edge is detected
+ * The polarity of the edge is set by the VP9_INTERRUPT_EDGE field
+ * Write a 1 to this bit to clear down the latched interrupt
+ * The latched interrupt is only enabled out onto the interrupt line if
+ * VP9_INTERRUPT_EN is set
+ * Reset value is *0* decimal.
+ */
+#define ARG_IC_ICTRL_VP9_INTERRUPT_INT		BIT(29)
+
+/* VP9_INTERRUPT_EDGE Sets the polarity of the interrupt edge detection logic
+ * This logic detects edges of the vp9_interrupt line from the argon h264 core
+ * 0 = negedge, 1 = posedge
+ * Reset value is *0* decimal.
+ */
+#define ARG_IC_ICTRL_VP9_INTERRUPT_EDGE		BIT(30)
+
+/* VP9_INTERRUPT_EN Enables VP9_INTERRUPT_INT out onto the argon interrupt line.
+ * If this isn't set, the interrupt logic will work but no interrupt will be
+ * set to the interrupt controller
+ * Reset value is *1* decimal.
+ */
+#define ARG_IC_ICTRL_VP9_INTERRUPT_EN		BIT(31)
+
+/* Bits 19:12, 11 reserved - read ?, write 0 */
+#define ARG_IC_ICTRL_SET_ZERO_MASK		((0xff << 12) | BIT(11))
+
+/* All IRQ bits */
+#define ARG_IC_ICTRL_ALL_IRQ_MASK   (\
+		ARG_IC_ICTRL_VP9_INTERRUPT_INT  |\
+		ARG_IC_ICTRL_H264_INTERRUPT_INT |\
+		ARG_IC_ICTRL_ACTIVE1_INT_SET    |\
+		ARG_IC_ICTRL_ACTIVE2_INT_SET)
+
+/* Regulate claim Q */
+void hevc_d_hw_irq_active1_enable_claim(struct hevc_d_dev *dev,
+					int n);
+/* Auto release once all CBs called */
+void hevc_d_hw_irq_active1_claim(struct hevc_d_dev *dev,
+				 struct hevc_d_hw_irq_ent *ient,
+				 hevc_d_irq_callback ready_cb, void *ctx);
+/* May only be called in claim cb */
+void hevc_d_hw_irq_active1_irq(struct hevc_d_dev *dev,
+			       struct hevc_d_hw_irq_ent *ient,
+			       hevc_d_irq_callback irq_cb, void *ctx);
+/* May only be called in irq cb */
+void hevc_d_hw_irq_active1_thread(struct hevc_d_dev *dev,
+				  struct hevc_d_hw_irq_ent *ient,
+				  hevc_d_irq_callback thread_cb, void *ctx);
+
+/* Auto release once all CBs called */
+void hevc_d_hw_irq_active2_claim(struct hevc_d_dev *dev,
+				 struct hevc_d_hw_irq_ent *ient,
+				 hevc_d_irq_callback ready_cb, void *ctx);
+/* May only be called in claim cb */
+void hevc_d_hw_irq_active2_irq(struct hevc_d_dev *dev,
+			       struct hevc_d_hw_irq_ent *ient,
+			       hevc_d_irq_callback irq_cb, void *ctx);
+
+int hevc_d_hw_probe(struct hevc_d_dev *dev);
+void hevc_d_hw_remove(struct hevc_d_dev *dev);
+
+#endif
diff --git a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c
new file mode 100644
index 000000000000..274af191d9e6
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.c
@@ -0,0 +1,750 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Raspberry Pi HEVC driver
+ *
+ * Copyright (C) 2024 Raspberry Pi Ltd
+ *
+ * Based on the Cedrus VPU driver, that is:
+ *
+ * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com>
+ * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ * Copyright (C) 2018 Bootlin
+ */
+
+#include <media/videobuf2-dma-contig.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-mem2mem.h>
+
+#include "hevc_d.h"
+#include "hevc_d_h265.h"
+#include "hevc_d_hw.h"
+#include "hevc_d_video.h"
+
+#define HEVC_D_DECODE_SRC	BIT(0)
+#define HEVC_D_DECODE_DST	BIT(1)
+
+#define HEVC_D_MIN_WIDTH	16U
+#define HEVC_D_MIN_HEIGHT	16U
+#define HEVC_D_DEFAULT_WIDTH	1920U
+#define HEVC_D_DEFAULT_HEIGHT	1088U
+#define HEVC_D_MAX_WIDTH	4096U
+#define HEVC_D_MAX_HEIGHT	4096U
+
+static inline struct hevc_d_ctx *hevc_d_file2ctx(struct file *file)
+{
+	return container_of(file->private_data, struct hevc_d_ctx, fh);
+}
+
+/* constrain x to y,y*2 */
+static inline unsigned int constrain2x(unsigned int x, unsigned int y)
+{
+	return (x < y) ?
+			y :
+			(x > y * 2) ? y : x;
+}
+
+size_t hevc_d_round_up_size(const size_t x)
+{
+	/* Admit no size < 256 */
+	const unsigned int n = x < 256 ? 8 : ilog2(x);
+
+	return x >= (3 << n) ? 4 << n : (3 << n);
+}
+
+size_t hevc_d_bit_buf_size(unsigned int w, unsigned int h, unsigned int bits_minus8)
+{
+	const size_t wxh = w * h;
+	size_t bits_alloc;
+
+	/* Annex A gives a min compression of 2 @ lvl 3.1
+	 * (wxh <= 983040) and min 4 thereafter but avoid
+	 * the odity of 983041 having a lower limit than
+	 * 983040.
+	 * Multiply by 3/2 for 4:2:0
+	 */
+	bits_alloc = wxh < 983040 ? wxh * 3 / 4 :
+		wxh < 983040 * 2 ? 983040 * 3 / 4 :
+		wxh * 3 / 8;
+	/* Allow for bit depth */
+	bits_alloc += (bits_alloc * bits_minus8) / 8;
+	return hevc_d_round_up_size(bits_alloc);
+}
+
+void hevc_d_prepare_src_format(struct v4l2_pix_format_mplane *pix_fmt)
+{
+	size_t size;
+	u32 w;
+	u32 h;
+
+	w = pix_fmt->width;
+	h = pix_fmt->height;
+	if (!w || !h) {
+		w = HEVC_D_DEFAULT_WIDTH;
+		h = HEVC_D_DEFAULT_HEIGHT;
+	}
+	if (w > HEVC_D_MAX_WIDTH)
+		w = HEVC_D_MAX_WIDTH;
+	if (h > HEVC_D_MAX_HEIGHT)
+		h = HEVC_D_MAX_HEIGHT;
+
+	if (!pix_fmt->plane_fmt[0].sizeimage ||
+	    pix_fmt->plane_fmt[0].sizeimage > SZ_32M) {
+		/* Unspecified or way too big - pick max for size */
+		size = hevc_d_bit_buf_size(w, h, 2);
+	}
+	/* Set a minimum */
+	size = max_t(u32, SZ_4K, pix_fmt->plane_fmt[0].sizeimage);
+
+	pix_fmt->pixelformat = V4L2_PIX_FMT_HEVC_SLICE;
+	pix_fmt->width = w;
+	pix_fmt->height = h;
+	pix_fmt->num_planes = 1;
+	pix_fmt->field = V4L2_FIELD_NONE;
+	/* Zero bytes per line for encoded source. */
+	pix_fmt->plane_fmt[0].bytesperline = 0;
+	pix_fmt->plane_fmt[0].sizeimage = size;
+}
+
+/* Take any pix_format and make it valid */
+static void hevc_d_prepare_dst_format(struct v4l2_pix_format_mplane *pix_fmt)
+{
+	unsigned int width = pix_fmt->width;
+	unsigned int height = pix_fmt->height;
+	unsigned int sizeimage = pix_fmt->plane_fmt[0].sizeimage;
+	unsigned int bytesperline = pix_fmt->plane_fmt[0].bytesperline;
+
+	if (!width)
+		width = HEVC_D_DEFAULT_WIDTH;
+	if (width > HEVC_D_MAX_WIDTH)
+		width = HEVC_D_MAX_WIDTH;
+	if (!height)
+		height = HEVC_D_DEFAULT_HEIGHT;
+	if (height > HEVC_D_MAX_HEIGHT)
+		height = HEVC_D_MAX_HEIGHT;
+
+	/* For column formats set bytesperline to column height (stride2) */
+	switch (pix_fmt->pixelformat) {
+	default:
+		pix_fmt->pixelformat = V4L2_PIX_FMT_NV12MT_COL128;
+		fallthrough;
+	case V4L2_PIX_FMT_NV12MT_COL128:
+		/* Width rounds up to columns */
+		width = ALIGN(width, 128);
+		height = ALIGN(height, 8);
+
+		/* column height is sizeimage / bytesperline */
+		bytesperline = width;
+		sizeimage = bytesperline * height;
+		break;
+
+	case V4L2_PIX_FMT_NV12MT_10_COL128:
+		/* width in pixels (3 pels = 4 bytes) rounded to 128 byte
+		 * columns
+		 */
+		width = ALIGN(((width + 2) / 3), 32) * 3;
+		height = ALIGN(height, 8);
+
+		/* column height is sizeimage / bytesperline */
+		bytesperline = width * 4 / 3;
+		sizeimage = bytesperline * height;
+		break;
+
+	case V4L2_PIX_FMT_NV12_COL128:
+		/* Width rounds up to columns */
+		width = ALIGN(width, 128);
+		height = ALIGN(height, 8);
+
+		/* column height
+		 * Accept suggested shape if at least min & < 2 * min
+		 */
+		bytesperline = constrain2x(bytesperline, height * 3 / 2);
+		sizeimage = bytesperline * width;
+		break;
+
+	case V4L2_PIX_FMT_NV12_10_COL128:
+		/* width in pixels (3 pels = 4 bytes) rounded to 128 byte
+		 * columns
+		 */
+		width = ALIGN(((width + 2) / 3), 32) * 3;
+		height = ALIGN(height, 8);
+
+		/* column height
+		 * Accept suggested shape if at least min & < 2 * min
+		 */
+		bytesperline = constrain2x(bytesperline, height * 3 / 2);
+		sizeimage = bytesperline * width * 4 / 3;
+		break;
+	}
+
+	pix_fmt->width = width;
+	pix_fmt->height = height;
+
+	pix_fmt->field = V4L2_FIELD_NONE;
+	switch (pix_fmt->pixelformat) {
+	default:
+	case V4L2_PIX_FMT_NV12MT_COL128:
+	case V4L2_PIX_FMT_NV12MT_10_COL128:
+		pix_fmt->plane_fmt[0].bytesperline = bytesperline;
+		pix_fmt->plane_fmt[0].sizeimage = sizeimage;
+		pix_fmt->plane_fmt[1].bytesperline = bytesperline;
+		pix_fmt->plane_fmt[1].sizeimage = sizeimage / 2;
+		pix_fmt->num_planes = 2;
+		break;
+	case V4L2_PIX_FMT_NV12_COL128:
+	case V4L2_PIX_FMT_NV12_10_COL128:
+		pix_fmt->plane_fmt[0].bytesperline = bytesperline;
+		pix_fmt->plane_fmt[0].sizeimage = sizeimage;
+		pix_fmt->num_planes = 1;
+		break;
+	}
+}
+
+static int hevc_d_querycap(struct file *file, void *priv,
+			   struct v4l2_capability *cap)
+{
+	strscpy(cap->driver, HEVC_D_NAME, sizeof(cap->driver));
+	strscpy(cap->card, HEVC_D_NAME, sizeof(cap->card));
+	snprintf(cap->bus_info, sizeof(cap->bus_info),
+		 "platform:%s", HEVC_D_NAME);
+
+	return 0;
+}
+
+static int hevc_d_enum_fmt_vid_out(struct file *file, void *priv,
+				   struct v4l2_fmtdesc *f)
+{
+	/*
+	 * Input formats
+	 * H.265 Slice only
+	 */
+	if (f->index == 0) {
+		f->pixelformat = V4L2_PIX_FMT_HEVC_SLICE;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int hevc_d_hevc_validate_sps(const struct v4l2_ctrl_hevc_sps * const sps)
+{
+	const unsigned int ctb_log2_size_y =
+			sps->log2_min_luma_coding_block_size_minus3 + 3 +
+			sps->log2_diff_max_min_luma_coding_block_size;
+	const unsigned int min_tb_log2_size_y =
+			sps->log2_min_luma_transform_block_size_minus2 + 2;
+	const unsigned int max_tb_log2_size_y = min_tb_log2_size_y +
+			sps->log2_diff_max_min_luma_transform_block_size;
+
+	/* Local limitations */
+	if (sps->pic_width_in_luma_samples < 32 ||
+	    sps->pic_width_in_luma_samples > 4096)
+		return 0;
+	if (sps->pic_height_in_luma_samples < 32 ||
+	    sps->pic_height_in_luma_samples > 4096)
+		return 0;
+	if (!(sps->bit_depth_luma_minus8 == 0 ||
+	      sps->bit_depth_luma_minus8 == 2))
+		return 0;
+	if (sps->bit_depth_luma_minus8 != sps->bit_depth_chroma_minus8)
+		return 0;
+	if (sps->chroma_format_idc != 1)
+		return 0;
+
+	/*  Limits from H.265 7.4.3.2.1 */
+	if (sps->log2_max_pic_order_cnt_lsb_minus4 > 12)
+		return 0;
+	if (sps->sps_max_dec_pic_buffering_minus1 > 15)
+		return 0;
+	if (sps->sps_max_num_reorder_pics >
+				sps->sps_max_dec_pic_buffering_minus1)
+		return 0;
+	if (ctb_log2_size_y > 6)
+		return 0;
+	if (max_tb_log2_size_y > 5)
+		return 0;
+	if (max_tb_log2_size_y > ctb_log2_size_y)
+		return 0;
+	if (sps->max_transform_hierarchy_depth_inter >
+				(ctb_log2_size_y - min_tb_log2_size_y))
+		return 0;
+	if (sps->max_transform_hierarchy_depth_intra >
+				(ctb_log2_size_y - min_tb_log2_size_y))
+		return 0;
+	/* Check pcm stuff */
+	if (sps->num_short_term_ref_pic_sets > 64)
+		return 0;
+	if (sps->num_long_term_ref_pics_sps > 32)
+		return 0;
+	return 1;
+}
+
+static u32 pixelformat_from_sps(const struct v4l2_ctrl_hevc_sps * const sps,
+				const int index)
+{
+	static const u32 all_formats[] = {
+		//V4L2_PIX_FMT_NV12MT_COL128,
+		//V4L2_PIX_FMT_NV12MT_10_COL128,
+		V4L2_PIX_FMT_NV12_COL128,
+		V4L2_PIX_FMT_NV12_10_COL128,
+	};
+	u32 pf = 0;
+
+	if (!is_sps_set(sps) || !hevc_d_hevc_validate_sps(sps)) {
+		/* Treat this as an error? For now return both */
+
+		if (index < ARRAY_SIZE(all_formats))
+			pf = all_formats[index];
+	} else {
+		if (index == 0) {
+/*			if (sps->bit_depth_luma_minus8 == 0)
+				pf = V4L2_PIX_FMT_NV12MT_COL128;
+			else if (sps->bit_depth_luma_minus8 == 2)
+				pf = V4L2_PIX_FMT_NV12MT_10_COL128;
+		} else if (index == 1) {
+			*/
+			if (sps->bit_depth_luma_minus8 == 0)
+				pf = V4L2_PIX_FMT_NV12_COL128;
+			else if (sps->bit_depth_luma_minus8 == 2)
+				pf = V4L2_PIX_FMT_NV12_10_COL128;
+		}
+	}
+
+	return pf;
+}
+
+static void copy_color(struct v4l2_pix_format_mplane *d,
+		       const struct v4l2_pix_format_mplane *s)
+{
+	d->colorspace   = s->colorspace;
+	d->xfer_func    = s->xfer_func;
+	d->ycbcr_enc    = s->ycbcr_enc;
+	d->quantization = s->quantization;
+}
+
+static struct v4l2_pix_format_mplane
+hevc_d_hevc_default_dst_fmt(struct hevc_d_ctx * const ctx)
+{
+	const struct v4l2_ctrl_hevc_sps * const sps =
+		hevc_d_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_SPS);
+	struct v4l2_pix_format_mplane pix_fmt;
+
+	memset(&pix_fmt, 0, sizeof(pix_fmt));
+	if (is_sps_set(sps)) {
+		pix_fmt.width = sps->pic_width_in_luma_samples;
+		pix_fmt.height = sps->pic_height_in_luma_samples;
+		pix_fmt.pixelformat = pixelformat_from_sps(sps, 0);
+	}
+
+	hevc_d_prepare_dst_format(&pix_fmt);
+	copy_color(&pix_fmt, &ctx->src_fmt);
+
+	return pix_fmt;
+}
+
+static u32 hevc_d_hevc_get_dst_pixelformat(struct hevc_d_ctx * const ctx,
+					   const int index)
+{
+	const struct v4l2_ctrl_hevc_sps * const sps =
+		hevc_d_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_SPS);
+
+	return pixelformat_from_sps(sps, index);
+}
+
+static int hevc_d_enum_fmt_vid_cap(struct file *file, void *priv,
+				   struct v4l2_fmtdesc *f)
+{
+	struct hevc_d_ctx * const ctx = hevc_d_file2ctx(file);
+
+	const u32 pf = hevc_d_hevc_get_dst_pixelformat(ctx, f->index);
+
+	if (pf == 0)
+		return -EINVAL;
+
+	f->pixelformat = pf;
+	return 0;
+}
+
+/*
+ * get dst format - sets it to default if otherwise unset
+ * returns a pointer to the struct as a convienience
+ */
+static struct v4l2_pix_format_mplane *get_dst_fmt(struct hevc_d_ctx *const ctx)
+{
+	if (!ctx->dst_fmt_set)
+		ctx->dst_fmt = hevc_d_hevc_default_dst_fmt(ctx);
+	return &ctx->dst_fmt;
+}
+
+static int hevc_d_g_fmt_vid_cap(struct file *file, void *priv,
+				struct v4l2_format *f)
+{
+	struct hevc_d_ctx *ctx = hevc_d_file2ctx(file);
+
+	f->fmt.pix_mp = *get_dst_fmt(ctx);
+	return 0;
+}
+
+static int hevc_d_g_fmt_vid_out(struct file *file, void *priv,
+				struct v4l2_format *f)
+{
+	struct hevc_d_ctx *ctx = hevc_d_file2ctx(file);
+
+	f->fmt.pix_mp = ctx->src_fmt;
+	return 0;
+}
+
+static int hevc_d_try_fmt_vid_cap(struct file *file, void *priv,
+				  struct v4l2_format *f)
+{
+	struct hevc_d_ctx *ctx = hevc_d_file2ctx(file);
+	const struct v4l2_ctrl_hevc_sps * const sps =
+		hevc_d_find_control_data(ctx, V4L2_CID_STATELESS_HEVC_SPS);
+	u32 pixelformat;
+	int i;
+
+	for (i = 0; (pixelformat = pixelformat_from_sps(sps, i)) != 0; i++) {
+		if (f->fmt.pix_mp.pixelformat == pixelformat)
+			break;
+	}
+
+	/*
+	 * We don't have any way of finding out colourspace so believe
+	 * anything we are told - take anything set in src as a default
+	 */
+	if (f->fmt.pix_mp.colorspace == V4L2_COLORSPACE_DEFAULT)
+		copy_color(&f->fmt.pix_mp, &ctx->src_fmt);
+
+	f->fmt.pix_mp.pixelformat = pixelformat;
+	hevc_d_prepare_dst_format(&f->fmt.pix_mp);
+	return 0;
+}
+
+static int hevc_d_try_fmt_vid_out(struct file *file, void *priv,
+				  struct v4l2_format *f)
+{
+	hevc_d_prepare_src_format(&f->fmt.pix_mp);
+	return 0;
+}
+
+static int hevc_d_s_fmt_vid_cap(struct file *file, void *priv,
+				struct v4l2_format *f)
+{
+	struct hevc_d_ctx *ctx = hevc_d_file2ctx(file);
+	struct vb2_queue *vq;
+	int ret;
+
+	vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
+	if (vb2_is_busy(vq))
+		return -EBUSY;
+
+	ret = hevc_d_try_fmt_vid_cap(file, priv, f);
+	if (ret)
+		return ret;
+
+	ctx->dst_fmt = f->fmt.pix_mp;
+	ctx->dst_fmt_set = 1;
+
+	return 0;
+}
+
+static int hevc_d_s_fmt_vid_out(struct file *file, void *priv,
+				struct v4l2_format *f)
+{
+	struct hevc_d_ctx *ctx = hevc_d_file2ctx(file);
+	struct vb2_queue *vq;
+	int ret;
+
+	vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
+	if (vb2_is_busy(vq))
+		return -EBUSY;
+
+	ret = hevc_d_try_fmt_vid_out(file, priv, f);
+	if (ret)
+		return ret;
+
+	ctx->src_fmt = f->fmt.pix_mp;
+	ctx->dst_fmt_set = 0;  /* Setting src invalidates dst */
+
+	/* Propagate colorspace information to capture. */
+	copy_color(&ctx->dst_fmt, &f->fmt.pix_mp);
+	return 0;
+}
+
+const struct v4l2_ioctl_ops hevc_d_ioctl_ops = {
+	.vidioc_querycap		= hevc_d_querycap,
+
+	.vidioc_enum_fmt_vid_cap	= hevc_d_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap_mplane	= hevc_d_g_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap_mplane	= hevc_d_try_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap_mplane	= hevc_d_s_fmt_vid_cap,
+
+	.vidioc_enum_fmt_vid_out	= hevc_d_enum_fmt_vid_out,
+	.vidioc_g_fmt_vid_out_mplane	= hevc_d_g_fmt_vid_out,
+	.vidioc_try_fmt_vid_out_mplane	= hevc_d_try_fmt_vid_out,
+	.vidioc_s_fmt_vid_out_mplane	= hevc_d_s_fmt_vid_out,
+
+	.vidioc_reqbufs			= v4l2_m2m_ioctl_reqbufs,
+	.vidioc_querybuf		= v4l2_m2m_ioctl_querybuf,
+	.vidioc_qbuf			= v4l2_m2m_ioctl_qbuf,
+	.vidioc_dqbuf			= v4l2_m2m_ioctl_dqbuf,
+	.vidioc_prepare_buf		= v4l2_m2m_ioctl_prepare_buf,
+	.vidioc_create_bufs		= v4l2_m2m_ioctl_create_bufs,
+	.vidioc_expbuf			= v4l2_m2m_ioctl_expbuf,
+
+	.vidioc_streamon		= v4l2_m2m_ioctl_streamon,
+	.vidioc_streamoff		= v4l2_m2m_ioctl_streamoff,
+
+	.vidioc_try_decoder_cmd		= v4l2_m2m_ioctl_stateless_try_decoder_cmd,
+	.vidioc_decoder_cmd		= v4l2_m2m_ioctl_stateless_decoder_cmd,
+
+	.vidioc_subscribe_event		= v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event	= v4l2_event_unsubscribe,
+};
+
+static int hevc_d_queue_setup(struct vb2_queue *vq, unsigned int *nbufs,
+			      unsigned int *nplanes, unsigned int sizes[],
+			      struct device *alloc_devs[])
+{
+	struct hevc_d_ctx *ctx = vb2_get_drv_priv(vq);
+	struct v4l2_pix_format_mplane *pix_fmt;
+	int expected_nplanes;
+
+	if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
+		pix_fmt = &ctx->src_fmt;
+		expected_nplanes = 1;
+	} else {
+		pix_fmt = get_dst_fmt(ctx);
+		expected_nplanes = 2;
+	}
+
+	if (*nplanes) {
+		if (pix_fmt->pixelformat == V4L2_PIX_FMT_NV12MT_COL128 ||
+		    pix_fmt->pixelformat == V4L2_PIX_FMT_NV12MT_10_COL128) {
+			if (*nplanes != expected_nplanes ||
+			    sizes[0] < pix_fmt->plane_fmt[0].sizeimage ||
+			    sizes[1] < pix_fmt->plane_fmt[1].sizeimage)
+				return -EINVAL;
+		} else {
+			if (sizes[0] < pix_fmt->plane_fmt[0].sizeimage)
+				return -EINVAL;
+		}
+	} else {
+		sizes[0] = pix_fmt->plane_fmt[0].sizeimage;
+		if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
+			*nplanes = 1;
+		} else {
+			if (pix_fmt->pixelformat == V4L2_PIX_FMT_NV12MT_COL128 ||
+			    pix_fmt->pixelformat == V4L2_PIX_FMT_NV12MT_10_COL128) {
+				sizes[1] = pix_fmt->plane_fmt[1].sizeimage;
+				*nplanes = 2;
+			} else {
+				*nplanes = 1;
+			}
+		}
+	}
+
+	return 0;
+}
+
+static void hevc_d_queue_cleanup(struct vb2_queue *vq, u32 state)
+{
+	struct hevc_d_ctx *ctx = vb2_get_drv_priv(vq);
+	struct vb2_v4l2_buffer *vbuf;
+
+	for (;;) {
+		if (V4L2_TYPE_IS_OUTPUT(vq->type))
+			vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+		else
+			vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
+
+		if (!vbuf)
+			return;
+
+		v4l2_ctrl_request_complete(vbuf->vb2_buf.req_obj.req,
+					   &ctx->hdl);
+		v4l2_m2m_buf_done(vbuf, state);
+	}
+}
+
+static int hevc_d_buf_out_validate(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+
+	vbuf->field = V4L2_FIELD_NONE;
+	return 0;
+}
+
+static int hevc_d_buf_prepare(struct vb2_buffer *vb)
+{
+	struct vb2_queue *vq = vb->vb2_queue;
+	struct hevc_d_ctx *ctx = vb2_get_drv_priv(vq);
+	struct v4l2_pix_format_mplane *pix_fmt;
+
+	if (V4L2_TYPE_IS_OUTPUT(vq->type))
+		pix_fmt = &ctx->src_fmt;
+	else
+		pix_fmt = &ctx->dst_fmt;
+
+	if (vb2_plane_size(vb, 0) < pix_fmt->plane_fmt[0].sizeimage ||
+	    vb2_plane_size(vb, 1) < pix_fmt->plane_fmt[1].sizeimage)
+		return -EINVAL;
+
+	vb2_set_plane_payload(vb, 0, pix_fmt->plane_fmt[0].sizeimage);
+	vb2_set_plane_payload(vb, 1, pix_fmt->plane_fmt[1].sizeimage);
+
+	return 0;
+}
+
+/* Only stops the clock if streaom off on both output & capture */
+static void stop_clock(struct hevc_d_dev *dev, struct hevc_d_ctx *ctx)
+{
+	if (ctx->src_stream_on ||
+	    ctx->dst_stream_on)
+		return;
+
+	clk_set_min_rate(dev->clock, 0);
+	clk_disable_unprepare(dev->clock);
+}
+
+/* Always starts the clock if it isn't already on this ctx */
+static int start_clock(struct hevc_d_dev *dev, struct hevc_d_ctx *ctx)
+{
+	int rv;
+
+	rv = clk_set_min_rate(dev->clock, dev->max_clock_rate);
+	if (rv) {
+		dev_err(dev->dev, "Failed to set clock rate\n");
+		return rv;
+	}
+
+	rv = clk_prepare_enable(dev->clock);
+	if (rv) {
+		dev_err(dev->dev, "Failed to enable clock\n");
+		return rv;
+	}
+
+	return 0;
+}
+
+static int hevc_d_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct hevc_d_ctx *ctx = vb2_get_drv_priv(vq);
+	struct hevc_d_dev *dev = ctx->dev;
+	int ret = 0;
+
+	if (!V4L2_TYPE_IS_OUTPUT(vq->type)) {
+		ctx->dst_stream_on = 1;
+		goto ok;
+	}
+
+	if (ctx->src_fmt.pixelformat != V4L2_PIX_FMT_HEVC_SLICE) {
+		ret = -EINVAL;
+		goto fail_cleanup;
+	}
+
+	if (ctx->src_stream_on)
+		goto ok;
+
+	ret = start_clock(dev, ctx);
+	if (ret)
+		goto fail_cleanup;
+
+	ret = hevc_d_h265_start(ctx);
+	if (ret)
+		goto fail_stop_clock;
+
+	ctx->src_stream_on = 1;
+ok:
+	return 0;
+
+fail_stop_clock:
+	stop_clock(dev, ctx);
+fail_cleanup:
+	v4l2_err(&dev->v4l2_dev, "%s: qtype=%d: FAIL\n", __func__, vq->type);
+	hevc_d_queue_cleanup(vq, VB2_BUF_STATE_QUEUED);
+	return ret;
+}
+
+static void hevc_d_stop_streaming(struct vb2_queue *vq)
+{
+	struct hevc_d_ctx *ctx = vb2_get_drv_priv(vq);
+	struct hevc_d_dev *dev = ctx->dev;
+
+	if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
+		ctx->src_stream_on = 0;
+		hevc_d_h265_stop(ctx);
+	} else {
+		ctx->dst_stream_on = 0;
+	}
+
+	hevc_d_queue_cleanup(vq, VB2_BUF_STATE_ERROR);
+
+	vb2_wait_for_all_buffers(vq);
+
+	stop_clock(dev, ctx);
+}
+
+static void hevc_d_buf_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct hevc_d_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+
+	v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
+}
+
+static void hevc_d_buf_request_complete(struct vb2_buffer *vb)
+{
+	struct hevc_d_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+
+	v4l2_ctrl_request_complete(vb->req_obj.req, &ctx->hdl);
+}
+
+static const struct vb2_ops hevc_d_qops = {
+	.queue_setup		= hevc_d_queue_setup,
+	.buf_prepare		= hevc_d_buf_prepare,
+	.buf_queue		= hevc_d_buf_queue,
+	.buf_out_validate	= hevc_d_buf_out_validate,
+	.buf_request_complete	= hevc_d_buf_request_complete,
+	.start_streaming	= hevc_d_start_streaming,
+	.stop_streaming		= hevc_d_stop_streaming,
+	.wait_prepare		= vb2_ops_wait_prepare,
+	.wait_finish		= vb2_ops_wait_finish,
+};
+
+int hevc_d_queue_init(void *priv, struct vb2_queue *src_vq,
+		      struct vb2_queue *dst_vq)
+{
+	struct hevc_d_ctx *ctx = priv;
+	int ret;
+
+	src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+	src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+	src_vq->drv_priv = ctx;
+	src_vq->buf_struct_size = sizeof(struct hevc_d_buffer);
+	src_vq->ops = &hevc_d_qops;
+	src_vq->mem_ops = &vb2_dma_contig_memops;
+	src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	src_vq->lock = &ctx->ctx_mutex;
+	src_vq->dev = ctx->dev->dev;
+	src_vq->supports_requests = true;
+	src_vq->requires_requests = true;
+
+	ret = vb2_queue_init(src_vq);
+	if (ret)
+		return ret;
+
+	dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+	dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+	dst_vq->drv_priv = ctx;
+	dst_vq->buf_struct_size = sizeof(struct hevc_d_buffer);
+	dst_vq->min_queued_buffers = 1;
+	dst_vq->ops = &hevc_d_qops;
+	dst_vq->mem_ops = &vb2_dma_contig_memops;
+	dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	dst_vq->lock = &ctx->ctx_mutex;
+	dst_vq->dev = ctx->dev->dev;
+
+	return vb2_queue_init(dst_vq);
+}
diff --git a/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.h b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.h
new file mode 100644
index 000000000000..3ea193423194
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/hevc_dec/hevc_d_video.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Raspberry Pi HEVC driver
+ *
+ * Copyright (C) 2024 Raspberry Pi Ltd
+ *
+ * Based on the Cedrus VPU driver, that is:
+ *
+ * Copyright (C) 2016 Florent Revest <florent.revest@free-electrons.com>
+ * Copyright (C) 2018 Paul Kocialkowski <paul.kocialkowski@bootlin.com>
+ * Copyright (C) 2018 Bootlin
+ */
+
+#ifndef _HEVC_D_VIDEO_H_
+#define _HEVC_D_VIDEO_H_
+
+struct hevc_d_format {
+	u32		pixelformat;
+	u32		directions;
+	unsigned int	capabilities;
+};
+
+static inline int is_sps_set(const struct v4l2_ctrl_hevc_sps * const sps)
+{
+	return sps && sps->pic_width_in_luma_samples;
+}
+
+extern const struct v4l2_ioctl_ops hevc_d_ioctl_ops;
+
+int hevc_d_queue_init(void *priv, struct vb2_queue *src_vq,
+		      struct vb2_queue *dst_vq);
+
+size_t hevc_d_bit_buf_size(unsigned int w, unsigned int h, unsigned int bits_minus8);
+size_t hevc_d_round_up_size(const size_t x);
+
+void hevc_d_prepare_src_format(struct v4l2_pix_format_mplane *pix_fmt);
+
+#endif
diff --git a/drivers/media/platform/raspberrypi/pisp_be/Kconfig b/drivers/media/platform/raspberrypi/pisp_be/Kconfig
index a9e51fd94aad..91ad7c1477a0 100644
--- a/drivers/media/platform/raspberrypi/pisp_be/Kconfig
+++ b/drivers/media/platform/raspberrypi/pisp_be/Kconfig
@@ -2,6 +2,7 @@ config VIDEO_RASPBERRYPI_PISP_BE
 	tristate "Raspberry Pi PiSP Backend (BE) ISP driver"
 	depends on V4L_PLATFORM_DRIVERS
 	depends on VIDEO_DEV
+	depends on PM
 	depends on ARCH_BCM2835 || COMPILE_TEST
 	depends on PM
 	select VIDEO_V4L2_SUBDEV_API
diff --git a/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c b/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c
index 49594e539c4f..2db4211b2b35 100644
--- a/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c
+++ b/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c
@@ -9,9 +9,11 @@
 #include <linux/io.h>
 #include <linux/kernel.h>
 #include <linux/lockdep.h>
+#include <linux/minmax.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
 #include <linux/pm_runtime.h>
+#include <linux/slab.h>
 #include <media/v4l2-device.h>
 #include <media/v4l2-ioctl.h>
 #include <media/videobuf2-dma-contig.h>
@@ -21,9 +23,20 @@
 
 #include "pisp_be_formats.h"
 
+/* Offset to use when registering the /dev/videoX node */
+#define PISPBE_VIDEO_NODE_OFFSET 20
+
 /* Maximum number of config buffers possible */
 #define PISP_BE_NUM_CONFIG_BUFFERS VB2_MAX_FRAME
 
+/*
+ * We want to support 2 independent instances allowing 2 simultaneous users
+ * of the ISP-BE (of course they share hardware, platform resources and mutex).
+ * Each such instance comprises a group of device nodes representing input
+ * and output queues, and a media controller device node to describe them.
+ */
+#define PISPBE_NUM_NODE_GROUPS 2
+
 #define PISPBE_NAME "pispbe"
 
 /* Some ISP-BE registers */
@@ -156,13 +169,11 @@ struct pispbe_node {
 	struct media_pad pad;
 	struct media_intf_devnode *intf_devnode;
 	struct media_link *intf_link;
-	struct pispbe_dev *pispbe;
+	struct pispbe_node_group *node_group;
 	/* Video device lock */
 	struct mutex node_lock;
 	/* vb2_queue lock */
 	struct mutex queue_lock;
-	/* Protect pispbe_node->ready_queue and pispbe_buffer->ready_list */
-	spinlock_t ready_lock;
 	struct list_head ready_queue;
 	struct vb2_queue queue;
 	struct v4l2_format format;
@@ -173,9 +184,27 @@ struct pispbe_node {
 #define NODE_NAME(node) \
 		(node_desc[(node)->id].ent_name + sizeof(PISPBE_NAME))
 
+/*
+ * Node group structure, which comprises all the input and output nodes that a
+ * single PiSP client will need, along with its own v4l2 and media devices.
+ */
+struct pispbe_node_group {
+	unsigned int id;
+	struct v4l2_device v4l2_dev;
+	struct v4l2_subdev sd;
+	struct pispbe_dev *pispbe;
+	struct media_device mdev;
+	struct pispbe_node node[PISPBE_NUM_NODES];
+	u32 streaming_map; /* bitmap of which nodes are streaming */
+	struct media_pad pad[PISPBE_NUM_NODES]; /* output pads first */
+	struct pisp_be_tiles_config *config;
+	dma_addr_t config_dma_addr;
+	unsigned int sequence;
+};
+
 /* Records details of the jobs currently running or queued on the h/w. */
 struct pispbe_job {
-	bool valid;
+	struct pispbe_node_group *node_group;
 	/*
 	 * An array of buffer pointers - remember it's source buffers first,
 	 * then captures, then metadata last.
@@ -190,6 +219,9 @@ struct pispbe_hw_enables {
 
 /* Records a job configuration and memory addresses. */
 struct pispbe_job_descriptor {
+	struct list_head queue;
+	struct pispbe_buffer *buffers[PISPBE_NUM_NODES];
+	struct pispbe_node_group *node_group;
 	dma_addr_t hw_dma_addrs[N_HW_ADDRESSES];
 	struct pisp_be_tiles_config *config;
 	struct pispbe_hw_enables hw_enables;
@@ -198,25 +230,18 @@ struct pispbe_job_descriptor {
 
 /*
  * Structure representing the entire PiSP Back End device, comprising several
- * nodes which share platform resources and a mutex for the actual HW.
+ * nodes groups which share platform resources and a mutex for the actual HW.
  */
 struct pispbe_dev {
 	struct device *dev;
-	struct pispbe_dev *pispbe;
-	struct pisp_be_tiles_config *config;
 	void __iomem *be_reg_base;
 	struct clk *clk;
-	struct v4l2_device v4l2_dev;
-	struct v4l2_subdev sd;
-	struct media_device mdev;
-	struct media_pad pad[PISPBE_NUM_NODES]; /* output pads first */
-	struct pispbe_node node[PISPBE_NUM_NODES];
-	dma_addr_t config_dma_addr;
-	unsigned int sequence;
-	u32 streaming_map;
+	struct pispbe_node_group node_group[PISPBE_NUM_NODE_GROUPS];
 	struct pispbe_job queued_job, running_job;
-	spinlock_t hw_lock; /* protects "hw_busy" flag and streaming_map */
+	/* protects "hw_busy" flag, streaming_map and job_queue */
+	spinlock_t hw_lock;
 	bool hw_busy; /* non-zero if a job is queued or is being started */
+	struct list_head job_queue;
 	int irq;
 	u32 hw_version;
 	u8 done, started;
@@ -348,9 +373,9 @@ static dma_addr_t pispbe_get_addr(struct pispbe_buffer *buf)
 	return 0;
 }
 
-static void pispbe_xlate_addrs(struct pispbe_dev *pispbe,
-			       struct pispbe_job_descriptor *job,
-			       struct pispbe_buffer *buf[PISPBE_NUM_NODES])
+static void pispbe_xlate_addrs(struct pispbe_job_descriptor *job,
+			       struct pispbe_buffer *buf[PISPBE_NUM_NODES],
+			       struct pispbe_node_group *node_group)
 {
 	struct pispbe_hw_enables *hw_en = &job->hw_enables;
 	struct pisp_be_tiles_config *config = job->config;
@@ -366,13 +391,10 @@ static void pispbe_xlate_addrs(struct pispbe_dev *pispbe,
 	 * to 3 planes.
 	 */
 	ret = pispbe_get_planes_addr(addrs, buf[MAIN_INPUT_NODE],
-				     &pispbe->node[MAIN_INPUT_NODE]);
+				     &node_group->node[MAIN_INPUT_NODE]);
 	if (ret <= 0) {
-		/*
-		 * This shouldn't happen; pispbe_schedule_internal should insist
-		 * on an input.
-		 */
-		dev_warn(pispbe->dev, "ISP-BE missing input\n");
+		/* Shouldn't happen, we have validated an input is available. */
+		dev_warn(node_group->pispbe->dev, "ISP-BE missing input\n");
 		hw_en->bayer_enables = 0;
 		hw_en->rgb_enables = 0;
 		return;
@@ -427,7 +449,7 @@ static void pispbe_xlate_addrs(struct pispbe_dev *pispbe,
 	for (unsigned int i = 0; i < PISP_BACK_END_NUM_OUTPUTS; i++) {
 		ret = pispbe_get_planes_addr(addrs + 7 + 3 * i,
 					     buf[OUTPUT0_NODE + i],
-					     &pispbe->node[OUTPUT0_NODE + i]);
+					     &node_group->node[OUTPUT0_NODE + i]);
 		if (ret <= 0)
 			hw_en->rgb_enables &= ~(PISP_BE_RGB_ENABLE_OUTPUT0 << i);
 	}
@@ -443,45 +465,52 @@ static void pispbe_xlate_addrs(struct pispbe_dev *pispbe,
  * For Output0, Output1, Tdn and Stitch, a buffer only needs to be
  * available if the blocks are enabled in the config.
  *
- * Needs to be called with hw_lock held.
+ * If all the buffers required to form a job are available, append the
+ * job descriptor to the job queue to be later queued to the HW.
  *
  * Returns 0 if a job has been successfully prepared, < 0 otherwise.
  */
-static int pispbe_prepare_job(struct pispbe_dev *pispbe,
-			      struct pispbe_job_descriptor *job)
+static int pispbe_prepare_job(struct pispbe_node_group *node_group)
 {
+	struct pispbe_job_descriptor __free(kfree) *job = NULL;
 	struct pispbe_buffer *buf[PISPBE_NUM_NODES] = {};
+	struct pispbe_dev *pispbe = node_group->pispbe;
+	unsigned int streaming_map;
 	unsigned int config_index;
 	struct pispbe_node *node;
-	unsigned long flags;
 
-	lockdep_assert_held(&pispbe->hw_lock);
+	lockdep_assert_irqs_enabled();
 
-	memset(job, 0, sizeof(struct pispbe_job_descriptor));
+	scoped_guard(spinlock_irq, &pispbe->hw_lock) {
+		static const u32 mask = BIT(CONFIG_NODE) | BIT(MAIN_INPUT_NODE);
 
-	if (((BIT(CONFIG_NODE) | BIT(MAIN_INPUT_NODE)) &
-		pispbe->streaming_map) !=
-			(BIT(CONFIG_NODE) | BIT(MAIN_INPUT_NODE)))
-		return -ENODEV;
+		if ((node_group->streaming_map & mask) != mask)
+			return -ENODEV;
 
-	node = &pispbe->node[CONFIG_NODE];
-	spin_lock_irqsave(&node->ready_lock, flags);
+		/*
+		 * Take a copy of streaming_map: nodes activated after this
+		 * point are ignored when preparing this job.
+		 */
+		streaming_map = node_group->streaming_map;
+	}
+
+	job = kzalloc(sizeof(*job), GFP_KERNEL);
+	if (!job)
+		return -ENOMEM;
+
+	node = &node_group->node[CONFIG_NODE];
 	buf[CONFIG_NODE] = list_first_entry_or_null(&node->ready_queue,
 						    struct pispbe_buffer,
 						    ready_list);
-	if (buf[CONFIG_NODE]) {
-		list_del(&buf[CONFIG_NODE]->ready_list);
-		pispbe->queued_job.buf[CONFIG_NODE] = buf[CONFIG_NODE];
-	}
-	spin_unlock_irqrestore(&node->ready_lock, flags);
-
-	/* Exit early if no config buffer has been queued. */
 	if (!buf[CONFIG_NODE])
 		return -ENODEV;
 
+	list_del(&buf[CONFIG_NODE]->ready_list);
+	job->buffers[CONFIG_NODE] = buf[CONFIG_NODE];
+
 	config_index = buf[CONFIG_NODE]->vb.vb2_buf.index;
-	job->config = &pispbe->config[config_index];
-	job->tiles = pispbe->config_dma_addr +
+	job->config = &node_group->config[config_index];
+	job->tiles = node_group->config_dma_addr +
 		     config_index * sizeof(struct pisp_be_tiles_config) +
 		     offsetof(struct pisp_be_tiles_config, tiles);
 
@@ -498,7 +527,7 @@ static int pispbe_prepare_job(struct pispbe_dev *pispbe,
 			continue;
 
 		buf[i] = NULL;
-		if (!(pispbe->streaming_map & BIT(i)))
+		if (!(streaming_map & BIT(i)))
 			continue;
 
 		if ((!(rgb_en & PISP_BE_RGB_ENABLE_OUTPUT0) &&
@@ -522,65 +551,74 @@ static int pispbe_prepare_job(struct pispbe_dev *pispbe,
 			ignore_buffers = true;
 		}
 
-		node = &pispbe->node[i];
+		node = &node_group->node[i];
 
 		/* Pull a buffer from each V4L2 queue to form the queued job */
-		spin_lock_irqsave(&node->ready_lock, flags);
 		buf[i] = list_first_entry_or_null(&node->ready_queue,
 						  struct pispbe_buffer,
 						  ready_list);
 		if (buf[i]) {
 			list_del(&buf[i]->ready_list);
-			pispbe->queued_job.buf[i] = buf[i];
+			job->buffers[i] = buf[i];
 		}
-		spin_unlock_irqrestore(&node->ready_lock, flags);
 
 		if (!buf[i] && !ignore_buffers)
 			goto err_return_buffers;
 	}
 
-	pispbe->queued_job.valid = true;
+	job->node_group = node_group;
 
 	/* Convert buffers to DMA addresses for the hardware */
-	pispbe_xlate_addrs(pispbe, job, buf);
+	pispbe_xlate_addrs(job, buf, node_group);
+
+	scoped_guard(spinlock_irq, &pispbe->hw_lock) {
+		list_add_tail(&job->queue, &pispbe->job_queue);
+	}
+
+	/* Set job to NULL to avoid automatic release due to __free(). */
+	job = NULL;
 
 	return 0;
 
 err_return_buffers:
 	for (unsigned int i = 0; i < PISPBE_NUM_NODES; i++) {
-		struct pispbe_node *n =  &pispbe->node[i];
+		struct pispbe_node *n =  &node_group->node[i];
 
 		if (!buf[i])
 			continue;
 
 		/* Return the buffer to the ready_list queue */
-		spin_lock_irqsave(&n->ready_lock, flags);
 		list_add(&buf[i]->ready_list, &n->ready_queue);
-		spin_unlock_irqrestore(&n->ready_lock, flags);
 	}
 
-	memset(&pispbe->queued_job, 0, sizeof(pispbe->queued_job));
-
 	return -ENODEV;
 }
 
 static void pispbe_schedule(struct pispbe_dev *pispbe, bool clear_hw_busy)
 {
-	struct pispbe_job_descriptor job;
-	unsigned long flags;
-	int ret;
+	struct pispbe_job_descriptor *job;
+
+	scoped_guard(spinlock_irqsave, &pispbe->hw_lock) {
+		if (clear_hw_busy)
+			pispbe->hw_busy = false;
 
-	spin_lock_irqsave(&pispbe->hw_lock, flags);
+		if (pispbe->hw_busy)
+			return;
 
-	if (clear_hw_busy)
-		pispbe->hw_busy = false;
+		job = list_first_entry_or_null(&pispbe->job_queue,
+					       struct pispbe_job_descriptor,
+					       queue);
+		if (!job)
+			return;
 
-	if (pispbe->hw_busy)
-		goto unlock_and_return;
+		list_del(&job->queue);
 
-	ret = pispbe_prepare_job(pispbe, &job);
-	if (ret)
-		goto unlock_and_return;
+		for (unsigned int i = 0; i < PISPBE_NUM_NODES; i++)
+			pispbe->queued_job.buf[i] = job->buffers[i];
+		pispbe->queued_job.node_group = job->node_group;
+
+		pispbe->hw_busy = true;
+	}
 
 	/*
 	 * We can kick the job off without the hw_lock, as this can
@@ -588,34 +626,8 @@ static void pispbe_schedule(struct pispbe_dev *pispbe, bool clear_hw_busy)
 	 * only when the following job has been queued and an interrupt
 	 * is rised.
 	 */
-	pispbe->hw_busy = true;
-	spin_unlock_irqrestore(&pispbe->hw_lock, flags);
-
-	if (job.config->num_tiles <= 0 ||
-	    job.config->num_tiles > PISP_BACK_END_NUM_TILES ||
-	    !((job.hw_enables.bayer_enables | job.hw_enables.rgb_enables) &
-	      PISP_BE_BAYER_ENABLE_INPUT)) {
-		/*
-		 * Bad job. We can't let it proceed as it could lock up
-		 * the hardware, or worse!
-		 *
-		 * For now, just force num_tiles to 0, which causes the
-		 * H/W to do something bizarre but survivable. It
-		 * increments (started,done) counters by more than 1,
-		 * but we seem to survive...
-		 */
-		dev_dbg(pispbe->dev, "Bad job: invalid number of tiles: %u\n",
-			job.config->num_tiles);
-		job.config->num_tiles = 0;
-	}
-
-	pispbe_queue_job(pispbe, &job);
-
-	return;
-
-unlock_and_return:
-	/* No job has been queued, just release the lock and return. */
-	spin_unlock_irqrestore(&pispbe->hw_lock, flags);
+	pispbe_queue_job(pispbe, job);
+	kfree(job);
 }
 
 static void pispbe_isr_jobdone(struct pispbe_dev *pispbe,
@@ -627,13 +639,13 @@ static void pispbe_isr_jobdone(struct pispbe_dev *pispbe,
 	for (unsigned int i = 0; i < PISPBE_NUM_NODES; i++) {
 		if (buf[i]) {
 			buf[i]->vb.vb2_buf.timestamp = ts;
-			buf[i]->vb.sequence = pispbe->sequence;
+			buf[i]->vb.sequence = job->node_group->sequence;
 			vb2_buffer_done(&buf[i]->vb.vb2_buf,
 					VB2_BUF_STATE_DONE);
 		}
 	}
 
-	pispbe->sequence++;
+	job->node_group->sequence++;
 }
 
 static irqreturn_t pispbe_isr(int irq, void *dev)
@@ -657,7 +669,7 @@ static irqreturn_t pispbe_isr(int irq, void *dev)
 	 * we previously saw "start" now finishes, and we then queued a new job
 	 * which we see both start and finish "simultaneously".
 	 */
-	if (pispbe->running_job.valid && pispbe->done != done) {
+	if (pispbe->running_job.node_group && pispbe->done != done) {
 		pispbe_isr_jobdone(pispbe, &pispbe->running_job);
 		memset(&pispbe->running_job, 0, sizeof(pispbe->running_job));
 		pispbe->done++;
@@ -667,7 +679,7 @@ static irqreturn_t pispbe_isr(int irq, void *dev)
 		pispbe->started++;
 		can_queue_another = 1;
 
-		if (pispbe->done != done && pispbe->queued_job.valid) {
+		if (pispbe->done != done && pispbe->queued_job.node_group) {
 			pispbe_isr_jobdone(pispbe, &pispbe->queued_job);
 			pispbe->done++;
 		} else {
@@ -691,12 +703,12 @@ static irqreturn_t pispbe_isr(int irq, void *dev)
 	return IRQ_HANDLED;
 }
 
-static int pisp_be_validate_config(struct pispbe_dev *pispbe,
+static int pisp_be_validate_config(struct pispbe_node_group *node_group,
 				   struct pisp_be_tiles_config *config)
 {
 	u32 bayer_enables = config->config.global.bayer_enables;
 	u32 rgb_enables = config->config.global.rgb_enables;
-	struct device *dev = pispbe->dev;
+	struct device *dev = node_group->pispbe->dev;
 	struct v4l2_format *fmt;
 	unsigned int bpl, size;
 
@@ -706,8 +718,15 @@ static int pisp_be_validate_config(struct pispbe_dev *pispbe,
 		return -EIO;
 	}
 
+	if (config->num_tiles == 0 ||
+	    config->num_tiles > PISP_BACK_END_NUM_TILES) {
+		dev_dbg(dev, "%s: Invalid number of tiles: %d\n", __func__,
+			config->num_tiles);
+		return -EINVAL;
+	}
+
 	/* Ensure output config strides and buffer sizes match the V4L2 formats. */
-	fmt = &pispbe->node[TDN_OUTPUT_NODE].format;
+	fmt = &node_group->node[TDN_OUTPUT_NODE].format;
 	if (bayer_enables & PISP_BE_BAYER_ENABLE_TDN_OUTPUT) {
 		bpl = config->config.tdn_output_format.stride;
 		size = bpl * config->config.tdn_output_format.height;
@@ -725,7 +744,7 @@ static int pisp_be_validate_config(struct pispbe_dev *pispbe,
 		}
 	}
 
-	fmt = &pispbe->node[STITCH_OUTPUT_NODE].format;
+	fmt = &node_group->node[STITCH_OUTPUT_NODE].format;
 	if (bayer_enables & PISP_BE_BAYER_ENABLE_STITCH_OUTPUT) {
 		bpl = config->config.stitch_output_format.stride;
 		size = bpl * config->config.stitch_output_format.height;
@@ -751,7 +770,7 @@ static int pisp_be_validate_config(struct pispbe_dev *pispbe,
 		    PISP_IMAGE_FORMAT_WALLPAPER_ROLL)
 			continue; /* TODO: Size checks for wallpaper formats */
 
-		fmt = &pispbe->node[OUTPUT0_NODE + j].format;
+		fmt = &node_group->node[OUTPUT0_NODE + j].format;
 		for (unsigned int i = 0; i < fmt->fmt.pix_mp.num_planes; i++) {
 			bpl = !i ? config->config.output_format[j].image.stride
 			    : config->config.output_format[j].image.stride2;
@@ -783,7 +802,7 @@ static int pispbe_node_queue_setup(struct vb2_queue *q, unsigned int *nbuffers,
 				   struct device *alloc_devs[])
 {
 	struct pispbe_node *node = vb2_get_drv_priv(q);
-	struct pispbe_dev *pispbe = node->pispbe;
+	struct pispbe_dev *pispbe = node->node_group->pispbe;
 	unsigned int num_planes = NODE_IS_MPLANE(node) ?
 				  node->format.fmt.pix_mp.num_planes : 1;
 
@@ -821,7 +840,7 @@ static int pispbe_node_queue_setup(struct vb2_queue *q, unsigned int *nbuffers,
 static int pispbe_node_buffer_prepare(struct vb2_buffer *vb)
 {
 	struct pispbe_node *node = vb2_get_drv_priv(vb->vb2_queue);
-	struct pispbe_dev *pispbe = node->pispbe;
+	struct pispbe_dev *pispbe = node->node_group->pispbe;
 	unsigned int num_planes = NODE_IS_MPLANE(node) ?
 				  node->format.fmt.pix_mp.num_planes : 1;
 
@@ -841,12 +860,12 @@ static int pispbe_node_buffer_prepare(struct vb2_buffer *vb)
 	}
 
 	if (node->id == CONFIG_NODE) {
-		void *dst = &node->pispbe->config[vb->index];
+		void *dst = &node->node_group->config[vb->index];
 		void *src = vb2_plane_vaddr(vb, 0);
 
 		memcpy(dst, src, sizeof(struct pisp_be_tiles_config));
 
-		return pisp_be_validate_config(pispbe, dst);
+		return pisp_be_validate_config(node->node_group, dst);
 	}
 
 	return 0;
@@ -859,55 +878,53 @@ static void pispbe_node_buffer_queue(struct vb2_buffer *buf)
 	struct pispbe_buffer *buffer =
 		container_of(vbuf, struct pispbe_buffer, vb);
 	struct pispbe_node *node = vb2_get_drv_priv(buf->vb2_queue);
-	struct pispbe_dev *pispbe = node->pispbe;
-	unsigned long flags;
+	struct pispbe_node_group *node_group = node->node_group;
+	struct pispbe_dev *pispbe = node->node_group->pispbe;
 
 	dev_dbg(pispbe->dev, "%s: for node %s\n", __func__, NODE_NAME(node));
-	spin_lock_irqsave(&node->ready_lock, flags);
 	list_add_tail(&buffer->ready_list, &node->ready_queue);
-	spin_unlock_irqrestore(&node->ready_lock, flags);
 
 	/*
 	 * Every time we add a buffer, check if there's now some work for the hw
-	 * to do.
+	 * to do, but only for this client.
 	 */
-	pispbe_schedule(pispbe, false);
+	if (!pispbe_prepare_job(node_group))
+		pispbe_schedule(pispbe, false);
 }
 
 static int pispbe_node_start_streaming(struct vb2_queue *q, unsigned int count)
 {
 	struct pispbe_node *node = vb2_get_drv_priv(q);
-	struct pispbe_dev *pispbe = node->pispbe;
+	struct pispbe_node_group *node_group = node->node_group;
+	struct pispbe_dev *pispbe = node_group->pispbe;
 	struct pispbe_buffer *buf, *tmp;
-	unsigned long flags;
 	int ret;
 
 	ret = pm_runtime_resume_and_get(pispbe->dev);
 	if (ret < 0)
 		goto err_return_buffers;
 
-	spin_lock_irqsave(&pispbe->hw_lock, flags);
-	node->pispbe->streaming_map |=  BIT(node->id);
-	node->pispbe->sequence = 0;
-	spin_unlock_irqrestore(&pispbe->hw_lock, flags);
+	scoped_guard(spinlock_irq, &pispbe->hw_lock) {
+		node->node_group->streaming_map |=  BIT(node->id);
+		node->node_group->sequence = 0;
+	}
 
 	dev_dbg(pispbe->dev, "%s: for node %s (count %u)\n",
 		__func__, NODE_NAME(node), count);
-	dev_dbg(pispbe->dev, "Nodes streaming now 0x%x\n",
-		node->pispbe->streaming_map);
+	dev_dbg(pispbe->dev, "Nodes streaming for this group now 0x%x\n",
+		node->node_group->streaming_map);
 
 	/* Maybe we're ready to run. */
-	pispbe_schedule(pispbe, false);
+	if (!pispbe_prepare_job(node_group))
+		pispbe_schedule(pispbe, false);
 
 	return 0;
 
 err_return_buffers:
-	spin_lock_irqsave(&pispbe->hw_lock, flags);
 	list_for_each_entry_safe(buf, tmp, &node->ready_queue, ready_list) {
 		list_del(&buf->ready_list);
 		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED);
 	}
-	spin_unlock_irqrestore(&pispbe->hw_lock, flags);
 
 	return ret;
 }
@@ -915,9 +932,10 @@ static int pispbe_node_start_streaming(struct vb2_queue *q, unsigned int count)
 static void pispbe_node_stop_streaming(struct vb2_queue *q)
 {
 	struct pispbe_node *node = vb2_get_drv_priv(q);
-	struct pispbe_dev *pispbe = node->pispbe;
+	struct pispbe_node_group *node_group = node->node_group;
+	struct pispbe_dev *pispbe = node_group->pispbe;
+	struct pispbe_job_descriptor *job, *temp;
 	struct pispbe_buffer *buf;
-	unsigned long flags;
 
 	/*
 	 * Now this is a bit awkward. In a simple M2M device we could just wait
@@ -929,11 +947,7 @@ static void pispbe_node_stop_streaming(struct vb2_queue *q)
 	 * This may return buffers out of order.
 	 */
 	dev_dbg(pispbe->dev, "%s: for node %s\n", __func__, NODE_NAME(node));
-	spin_lock_irqsave(&pispbe->hw_lock, flags);
 	do {
-		unsigned long flags1;
-
-		spin_lock_irqsave(&node->ready_lock, flags1);
 		buf = list_first_entry_or_null(&node->ready_queue,
 					       struct pispbe_buffer,
 					       ready_list);
@@ -941,21 +955,30 @@ static void pispbe_node_stop_streaming(struct vb2_queue *q)
 			list_del(&buf->ready_list);
 			vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
 		}
-		spin_unlock_irqrestore(&node->ready_lock, flags1);
 	} while (buf);
-	spin_unlock_irqrestore(&pispbe->hw_lock, flags);
 
 	vb2_wait_for_all_buffers(&node->queue);
 
-	spin_lock_irqsave(&pispbe->hw_lock, flags);
-	pispbe->streaming_map &= ~BIT(node->id);
-	spin_unlock_irqrestore(&pispbe->hw_lock, flags);
+	spin_lock_irq(&pispbe->hw_lock);
+	node_group->streaming_map &= ~BIT(node->id);
+
+	/*
+	 * If a node has stopped streaming release all jobs belonging to the
+	 * node group immediately.
+	 */
+	list_for_each_entry_safe(job, temp, &pispbe->job_queue, queue) {
+		if (job->node_group == node->node_group) {
+			list_del(&job->queue);
+			kfree(job);
+		}
+	}
+	spin_unlock_irq(&pispbe->hw_lock);
 
 	pm_runtime_mark_last_busy(pispbe->dev);
 	pm_runtime_put_autosuspend(pispbe->dev);
 
-	dev_dbg(pispbe->dev, "Nodes streaming now 0x%x\n",
-		pispbe->streaming_map);
+	dev_dbg(pispbe->dev, "Nodes streaming for this group now 0x%x\n",
+		node_group->streaming_map);
 }
 
 static const struct vb2_ops pispbe_node_queue_ops = {
@@ -979,7 +1002,7 @@ static int pispbe_node_querycap(struct file *file, void *priv,
 				struct v4l2_capability *cap)
 {
 	struct pispbe_node *node = video_drvdata(file);
-	struct pispbe_dev *pispbe = node->pispbe;
+	struct pispbe_dev *pispbe = node->node_group->pispbe;
 
 	strscpy(cap->driver, PISPBE_NAME, sizeof(cap->driver));
 	strscpy(cap->card, PISPBE_NAME, sizeof(cap->card));
@@ -995,7 +1018,7 @@ static int pispbe_node_g_fmt_vid_cap(struct file *file, void *priv,
 				     struct v4l2_format *f)
 {
 	struct pispbe_node *node = video_drvdata(file);
-	struct pispbe_dev *pispbe = node->pispbe;
+	struct pispbe_dev *pispbe = node->node_group->pispbe;
 
 	if (!NODE_IS_CAPTURE(node) || NODE_IS_META(node)) {
 		dev_dbg(pispbe->dev,
@@ -1015,7 +1038,7 @@ static int pispbe_node_g_fmt_vid_out(struct file *file, void *priv,
 				     struct v4l2_format *f)
 {
 	struct pispbe_node *node = video_drvdata(file);
-	struct pispbe_dev *pispbe = node->pispbe;
+	struct pispbe_dev *pispbe = node->node_group->pispbe;
 
 	if (NODE_IS_CAPTURE(node) || NODE_IS_META(node)) {
 		dev_dbg(pispbe->dev,
@@ -1035,7 +1058,7 @@ static int pispbe_node_g_fmt_meta_out(struct file *file, void *priv,
 				      struct v4l2_format *f)
 {
 	struct pispbe_node *node = video_drvdata(file);
-	struct pispbe_dev *pispbe = node->pispbe;
+	struct pispbe_dev *pispbe = node->node_group->pispbe;
 
 	if (!NODE_IS_META(node) || NODE_IS_CAPTURE(node)) {
 		dev_dbg(pispbe->dev,
@@ -1073,9 +1096,16 @@ static void pispbe_set_plane_params(struct v4l2_format *f,
 	for (unsigned int i = 0; i < nplanes; i++) {
 		struct v4l2_plane_pix_format *p = &f->fmt.pix_mp.plane_fmt[i];
 		unsigned int bpl, plane_size;
+		/*
+		 * If a stride has been provided, ensure it meets the minimal
+		 * alignment constraints. If not provided, use an optimal stride
+		 * alignment for efficiency.
+		 */
+		const unsigned int align =
+			p->bytesperline ? fmt->min_align : fmt->opt_align;
 
 		bpl = (f->fmt.pix_mp.width * fmt->bit_depth) >> 3;
-		bpl = ALIGN(max(p->bytesperline, bpl), fmt->align);
+		bpl = ALIGN(max(p->bytesperline, bpl), align);
 
 		plane_size = bpl * f->fmt.pix_mp.height *
 		      (nplanes > 1 ? fmt->plane_factor[i] : total_plane_factor);
@@ -1092,7 +1122,7 @@ static void pispbe_set_plane_params(struct v4l2_format *f,
 
 static void pispbe_try_format(struct v4l2_format *f, struct pispbe_node *node)
 {
-	struct pispbe_dev *pispbe = node->pispbe;
+	struct pispbe_dev *pispbe = node->node_group->pispbe;
 	u32 pixfmt = f->fmt.pix_mp.pixelformat;
 	const struct pisp_be_format *fmt;
 	bool is_rgb;
@@ -1114,10 +1144,12 @@ static void pispbe_try_format(struct v4l2_format *f, struct pispbe_node *node)
 	f->fmt.pix_mp.pixelformat = fmt->fourcc;
 	f->fmt.pix_mp.num_planes = fmt->num_planes;
 	f->fmt.pix_mp.field = V4L2_FIELD_NONE;
-	f->fmt.pix_mp.width = max(min(f->fmt.pix_mp.width, 65536u),
-				  PISP_BACK_END_MIN_TILE_WIDTH);
-	f->fmt.pix_mp.height = max(min(f->fmt.pix_mp.height, 65536u),
-				   PISP_BACK_END_MIN_TILE_HEIGHT);
+	f->fmt.pix_mp.width = clamp(f->fmt.pix_mp.width,
+				    PISP_BACK_END_MIN_TILE_WIDTH,
+				    PISP_BACK_END_MAX_TILE_WIDTH);
+	f->fmt.pix_mp.height = clamp(f->fmt.pix_mp.height,
+				     PISP_BACK_END_MIN_TILE_HEIGHT,
+				     PISP_BACK_END_MAX_TILE_HEIGHT);
 
 	/*
 	 * Fill in the actual colour space when the requested one was
@@ -1156,7 +1188,7 @@ static int pispbe_node_try_fmt_vid_cap(struct file *file, void *priv,
 				       struct v4l2_format *f)
 {
 	struct pispbe_node *node = video_drvdata(file);
-	struct pispbe_dev *pispbe = node->pispbe;
+	struct pispbe_dev *pispbe = node->node_group->pispbe;
 
 	if (!NODE_IS_CAPTURE(node) || NODE_IS_META(node)) {
 		dev_dbg(pispbe->dev,
@@ -1174,7 +1206,7 @@ static int pispbe_node_try_fmt_vid_out(struct file *file, void *priv,
 				       struct v4l2_format *f)
 {
 	struct pispbe_node *node = video_drvdata(file);
-	struct pispbe_dev *pispbe = node->pispbe;
+	struct pispbe_dev *pispbe = node->node_group->pispbe;
 
 	if (!NODE_IS_OUTPUT(node) || NODE_IS_META(node)) {
 		dev_dbg(pispbe->dev,
@@ -1192,7 +1224,7 @@ static int pispbe_node_try_fmt_meta_out(struct file *file, void *priv,
 					struct v4l2_format *f)
 {
 	struct pispbe_node *node = video_drvdata(file);
-	struct pispbe_dev *pispbe = node->pispbe;
+	struct pispbe_dev *pispbe = node->node_group->pispbe;
 
 	if (!NODE_IS_META(node) || NODE_IS_CAPTURE(node)) {
 		dev_dbg(pispbe->dev,
@@ -1211,7 +1243,7 @@ static int pispbe_node_s_fmt_vid_cap(struct file *file, void *priv,
 				     struct v4l2_format *f)
 {
 	struct pispbe_node *node = video_drvdata(file);
-	struct pispbe_dev *pispbe = node->pispbe;
+	struct pispbe_dev *pispbe = node->node_group->pispbe;
 	int ret;
 
 	ret = pispbe_node_try_fmt_vid_cap(file, priv, f);
@@ -1234,7 +1266,7 @@ static int pispbe_node_s_fmt_vid_out(struct file *file, void *priv,
 				     struct v4l2_format *f)
 {
 	struct pispbe_node *node = video_drvdata(file);
-	struct pispbe_dev *pispbe = node->pispbe;
+	struct pispbe_dev *pispbe = node->node_group->pispbe;
 	int ret;
 
 	ret = pispbe_node_try_fmt_vid_out(file, priv, f);
@@ -1257,7 +1289,7 @@ static int pispbe_node_s_fmt_meta_out(struct file *file, void *priv,
 				      struct v4l2_format *f)
 {
 	struct pispbe_node *node = video_drvdata(file);
-	struct pispbe_dev *pispbe = node->pispbe;
+	struct pispbe_dev *pispbe = node->node_group->pispbe;
 	int ret;
 
 	ret = pispbe_node_try_fmt_meta_out(file, priv, f);
@@ -1306,7 +1338,7 @@ static int pispbe_enum_framesizes(struct file *file, void *priv,
 				  struct v4l2_frmsizeenum *fsize)
 {
 	struct pispbe_node *node = video_drvdata(file);
-	struct pispbe_dev *pispbe = node->pispbe;
+	struct pispbe_dev *pispbe = node->node_group->pispbe;
 
 	if (NODE_IS_META(node) || fsize->index)
 		return -EINVAL;
@@ -1391,23 +1423,24 @@ static void pispbe_node_def_fmt(struct pispbe_node *node)
  * Initialise a struct pispbe_node and register it as /dev/video<N>
  * to represent one of the PiSP Back End's input or output streams.
  */
-static int pispbe_init_node(struct pispbe_dev *pispbe, unsigned int id)
+static int pispbe_init_node(struct pispbe_node_group *node_group,
+			    unsigned int id)
 {
 	bool output = NODE_DESC_IS_OUTPUT(&node_desc[id]);
-	struct pispbe_node *node = &pispbe->node[id];
+	struct pispbe_node *node = &node_group->node[id];
 	struct media_entity *entity = &node->vfd.entity;
+	struct pispbe_dev *pispbe = node_group->pispbe;
 	struct video_device *vdev = &node->vfd;
 	struct vb2_queue *q = &node->queue;
 	int ret;
 
 	node->id = id;
-	node->pispbe = pispbe;
+	node->node_group = node_group;
 	node->buf_type = node_desc[id].buf_type;
 
 	mutex_init(&node->node_lock);
 	mutex_init(&node->queue_lock);
 	INIT_LIST_HEAD(&node->ready_queue);
-	spin_lock_init(&node->ready_lock);
 
 	node->format.type = node->buf_type;
 	pispbe_node_def_fmt(node);
@@ -1419,7 +1452,7 @@ static int pispbe_init_node(struct pispbe_dev *pispbe, unsigned int id)
 	q->ops = &pispbe_node_queue_ops;
 	q->buf_struct_size = sizeof(struct pispbe_buffer);
 	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
-	q->dev = pispbe->dev;
+	q->dev = node->node_group->pispbe->dev;
 	/* get V4L2 to handle node->queue locking */
 	q->lock = &node->queue_lock;
 
@@ -1431,7 +1464,7 @@ static int pispbe_init_node(struct pispbe_dev *pispbe, unsigned int id)
 
 	*vdev = pispbe_videodev; /* default initialization */
 	strscpy(vdev->name, node_desc[id].ent_name, sizeof(vdev->name));
-	vdev->v4l2_dev = &pispbe->v4l2_dev;
+	vdev->v4l2_dev = &node_group->v4l2_dev;
 	vdev->vfl_dir = output ? VFL_DIR_TX : VFL_DIR_RX;
 	/* get V4L2 to serialise our ioctls */
 	vdev->lock = &node->node_lock;
@@ -1447,7 +1480,8 @@ static int pispbe_init_node(struct pispbe_dev *pispbe, unsigned int id)
 		goto err_unregister_queue;
 	}
 
-	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+	ret = video_register_device(vdev, VFL_TYPE_VIDEO,
+				    PISPBE_VIDEO_NODE_OFFSET);
 	if (ret) {
 		dev_err(pispbe->dev,
 			"Failed to register video %s device node\n",
@@ -1457,11 +1491,11 @@ static int pispbe_init_node(struct pispbe_dev *pispbe, unsigned int id)
 	video_set_drvdata(vdev, node);
 
 	if (output)
-		ret = media_create_pad_link(entity, 0, &pispbe->sd.entity,
+		ret = media_create_pad_link(entity, 0, &node_group->sd.entity,
 					    id, MEDIA_LNK_FL_IMMUTABLE |
 					    MEDIA_LNK_FL_ENABLED);
 	else
-		ret = media_create_pad_link(&pispbe->sd.entity, id, entity,
+		ret = media_create_pad_link(&node_group->sd.entity, id, entity,
 					    0, MEDIA_LNK_FL_IMMUTABLE |
 					    MEDIA_LNK_FL_ENABLED);
 	if (ret)
@@ -1490,9 +1524,10 @@ static const struct v4l2_subdev_ops pispbe_sd_ops = {
 	.pad = &pispbe_pad_ops,
 };
 
-static int pispbe_init_subdev(struct pispbe_dev *pispbe)
+static int pispbe_init_subdev(struct pispbe_node_group *node_group)
 {
-	struct v4l2_subdev *sd = &pispbe->sd;
+	struct pispbe_dev *pispbe = node_group->pispbe;
+	struct v4l2_subdev *sd = &node_group->sd;
 	int ret;
 
 	v4l2_subdev_init(sd, &pispbe_sd_ops);
@@ -1502,16 +1537,16 @@ static int pispbe_init_subdev(struct pispbe_dev *pispbe)
 	strscpy(sd->name, PISPBE_NAME, sizeof(sd->name));
 
 	for (unsigned int i = 0; i < PISPBE_NUM_NODES; i++)
-		pispbe->pad[i].flags =
+		node_group->pad[i].flags =
 			NODE_DESC_IS_OUTPUT(&node_desc[i]) ?
 			MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE;
 
 	ret = media_entity_pads_init(&sd->entity, PISPBE_NUM_NODES,
-				     pispbe->pad);
+				     node_group->pad);
 	if (ret)
 		goto error;
 
-	ret = v4l2_device_register_subdev(&pispbe->v4l2_dev, sd);
+	ret = v4l2_device_register_subdev(&node_group->v4l2_dev, sd);
 	if (ret)
 		goto error;
 
@@ -1522,36 +1557,43 @@ static int pispbe_init_subdev(struct pispbe_dev *pispbe)
 	return ret;
 }
 
-static int pispbe_init_devices(struct pispbe_dev *pispbe)
+static int pispbe_init_group(struct pispbe_dev *pispbe, unsigned int id)
 {
+	struct pispbe_node_group *node_group = &pispbe->node_group[id];
 	struct v4l2_device *v4l2_dev;
 	struct media_device *mdev;
 	unsigned int num_regist;
 	int ret;
 
+	node_group->id = id;
+	node_group->pispbe = pispbe;
+	node_group->streaming_map = 0;
+
+	dev_dbg(pispbe->dev, "Register nodes for group %u\n", id);
+
 	/* Register v4l2_device and media_device */
-	mdev = &pispbe->mdev;
-	mdev->hw_revision = pispbe->hw_version;
-	mdev->dev = pispbe->dev;
+	mdev = &node_group->mdev;
+	mdev->hw_revision = node_group->pispbe->hw_version;
+	mdev->dev = node_group->pispbe->dev;
 	strscpy(mdev->model, PISPBE_NAME, sizeof(mdev->model));
 	media_device_init(mdev);
 
-	v4l2_dev = &pispbe->v4l2_dev;
-	v4l2_dev->mdev = &pispbe->mdev;
+	v4l2_dev = &node_group->v4l2_dev;
+	v4l2_dev->mdev = &node_group->mdev;
 	strscpy(v4l2_dev->name, PISPBE_NAME, sizeof(v4l2_dev->name));
 
-	ret = v4l2_device_register(pispbe->dev, v4l2_dev);
+	ret = v4l2_device_register(pispbe->dev, &node_group->v4l2_dev);
 	if (ret)
 		goto err_media_dev_cleanup;
 
 	/* Register the PISPBE subdevice. */
-	ret = pispbe_init_subdev(pispbe);
+	ret = pispbe_init_subdev(node_group);
 	if (ret)
 		goto err_unregister_v4l2;
 
 	/* Create device video nodes */
 	for (num_regist = 0; num_regist < PISPBE_NUM_NODES; num_regist++) {
-		ret = pispbe_init_node(pispbe, num_regist);
+		ret = pispbe_init_node(node_group, num_regist);
 		if (ret)
 			goto err_unregister_nodes;
 	}
@@ -1560,12 +1602,12 @@ static int pispbe_init_devices(struct pispbe_dev *pispbe)
 	if (ret)
 		goto err_unregister_nodes;
 
-	pispbe->config =
+	node_group->config =
 		dma_alloc_coherent(pispbe->dev,
 				   sizeof(struct pisp_be_tiles_config) *
 					PISP_BE_NUM_CONFIG_BUFFERS,
-				   &pispbe->config_dma_addr, GFP_KERNEL);
-	if (!pispbe->config) {
+				   &node_group->config_dma_addr, GFP_KERNEL);
+	if (!node_group->config) {
 		dev_err(pispbe->dev, "Unable to allocate cached config buffers.\n");
 		ret = -ENOMEM;
 		goto err_unregister_mdev;
@@ -1577,11 +1619,11 @@ static int pispbe_init_devices(struct pispbe_dev *pispbe)
 	media_device_unregister(mdev);
 err_unregister_nodes:
 	while (num_regist-- > 0) {
-		video_unregister_device(&pispbe->node[num_regist].vfd);
-		vb2_queue_release(&pispbe->node[num_regist].queue);
+		video_unregister_device(&node_group->node[num_regist].vfd);
+		vb2_queue_release(&node_group->node[num_regist].queue);
 	}
-	v4l2_device_unregister_subdev(&pispbe->sd);
-	media_entity_cleanup(&pispbe->sd.entity);
+	v4l2_device_unregister_subdev(&node_group->sd);
+	media_entity_cleanup(&node_group->sd.entity);
 err_unregister_v4l2:
 	v4l2_device_unregister(v4l2_dev);
 err_media_dev_cleanup:
@@ -1589,31 +1631,33 @@ static int pispbe_init_devices(struct pispbe_dev *pispbe)
 	return ret;
 }
 
-static void pispbe_destroy_devices(struct pispbe_dev *pispbe)
+static void pispbe_destroy_node_group(struct pispbe_node_group *node_group)
 {
-	if (pispbe->config) {
-		dma_free_coherent(pispbe->dev,
+	struct pispbe_dev *pispbe = node_group->pispbe;
+
+	if (node_group->config) {
+		dma_free_coherent(node_group->pispbe->dev,
 				  sizeof(struct pisp_be_tiles_config) *
 					PISP_BE_NUM_CONFIG_BUFFERS,
-				  pispbe->config,
-				  pispbe->config_dma_addr);
+				  node_group->config,
+				  node_group->config_dma_addr);
 	}
 
 	dev_dbg(pispbe->dev, "Unregister from media controller\n");
 
-	v4l2_device_unregister_subdev(&pispbe->sd);
-	media_entity_cleanup(&pispbe->sd.entity);
-	media_device_unregister(&pispbe->mdev);
+	v4l2_device_unregister_subdev(&node_group->sd);
+	media_entity_cleanup(&node_group->sd.entity);
+	media_device_unregister(&node_group->mdev);
 
 	for (int i = PISPBE_NUM_NODES - 1; i >= 0; i--) {
-		video_unregister_device(&pispbe->node[i].vfd);
-		vb2_queue_release(&pispbe->node[i].queue);
-		mutex_destroy(&pispbe->node[i].node_lock);
-		mutex_destroy(&pispbe->node[i].queue_lock);
+		video_unregister_device(&node_group->node[i].vfd);
+		vb2_queue_release(&node_group->node[i].queue);
+		mutex_destroy(&node_group->node[i].node_lock);
+		mutex_destroy(&node_group->node[i].queue_lock);
 	}
 
-	media_device_cleanup(&pispbe->mdev);
-	v4l2_device_unregister(&pispbe->v4l2_dev);
+	media_device_cleanup(&node_group->mdev);
+	v4l2_device_unregister(&node_group->v4l2_dev);
 }
 
 static int pispbe_runtime_suspend(struct device *dev)
@@ -1681,9 +1725,13 @@ static int pispbe_hw_init(struct pispbe_dev *pispbe)
 	return 0;
 }
 
-/* Probe the ISP-BE hardware block, as a single platform device. */
+/*
+ * Probe the ISP-BE hardware block, as a single platform device.
+ * This will instantiate multiple "node groups" each with many device nodes.
+ */
 static int pispbe_probe(struct platform_device *pdev)
 {
+	unsigned int num_groups = 0;
 	struct pispbe_dev *pispbe;
 	int ret;
 
@@ -1691,6 +1739,8 @@ static int pispbe_probe(struct platform_device *pdev)
 	if (!pispbe)
 		return -ENOMEM;
 
+	INIT_LIST_HEAD(&pispbe->job_queue);
+
 	dev_set_drvdata(&pdev->dev, pispbe);
 	pispbe->dev = &pdev->dev;
 	platform_set_drvdata(pdev, pispbe);
@@ -1736,17 +1786,26 @@ static int pispbe_probe(struct platform_device *pdev)
 	if (ret)
 		goto pm_runtime_suspend_err;
 
-	ret = pispbe_init_devices(pispbe);
-	if (ret)
-		goto disable_devs_err;
+	/*
+	 * Initialise and register devices for each node_group, including media
+	 * device
+	 */
+	for (num_groups = 0;
+	     num_groups < PISPBE_NUM_NODE_GROUPS;
+	     num_groups++) {
+		ret = pispbe_init_group(pispbe, num_groups);
+		if (ret)
+			goto disable_nodes_err;
+	}
 
 	pm_runtime_mark_last_busy(pispbe->dev);
 	pm_runtime_put_autosuspend(pispbe->dev);
 
 	return 0;
 
-disable_devs_err:
-	pispbe_destroy_devices(pispbe);
+disable_nodes_err:
+	while (num_groups-- > 0)
+		pispbe_destroy_node_group(&pispbe->node_group[num_groups]);
 pm_runtime_suspend_err:
 	pm_runtime_put(pispbe->dev);
 pm_runtime_disable_err:
@@ -1760,7 +1819,8 @@ static void pispbe_remove(struct platform_device *pdev)
 {
 	struct pispbe_dev *pispbe = platform_get_drvdata(pdev);
 
-	pispbe_destroy_devices(pispbe);
+	for (int i = PISPBE_NUM_NODE_GROUPS - 1; i >= 0; i--)
+		pispbe_destroy_node_group(&pispbe->node_group[i]);
 
 	pm_runtime_dont_use_autosuspend(pispbe->dev);
 	pm_runtime_disable(pispbe->dev);
diff --git a/drivers/media/platform/raspberrypi/pisp_be/pisp_be_formats.h b/drivers/media/platform/raspberrypi/pisp_be/pisp_be_formats.h
index b5cb7b8c7531..1b0386c49281 100644
--- a/drivers/media/platform/raspberrypi/pisp_be/pisp_be_formats.h
+++ b/drivers/media/platform/raspberrypi/pisp_be/pisp_be_formats.h
@@ -16,7 +16,8 @@
 
 struct pisp_be_format {
 	unsigned int fourcc;
-	unsigned int align;
+	unsigned int opt_align;
+	unsigned int min_align;
 	unsigned int bit_depth;
 	/* 0P3 factor for plane sizing */
 	unsigned int plane_factor[PISPBE_MAX_PLANES];
@@ -58,7 +59,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_YUV420,
 		/* 128 alignment to ensure U/V planes are 64 byte aligned. */
-		.align		    = 128,
+		.opt_align	    = 128,
+		.min_align	    = 32,
 		.bit_depth	    = 8,
 		.plane_factor	    = { P3(1), P3(0.25), P3(0.25) },
 		.num_planes	    = 1,
@@ -68,7 +70,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_YVU420,
 		/* 128 alignment to ensure U/V planes are 64 byte aligned. */
-		.align		    = 128,
+		.opt_align	    = 128,
+		.min_align	    = 32,
 		.bit_depth	    = 8,
 		.plane_factor	    = { P3(1), P3(0.25), P3(0.25) },
 		.num_planes	    = 1,
@@ -77,7 +80,8 @@ static const struct pisp_be_format supported_formats[] = {
 	},
 	{
 		.fourcc		    = V4L2_PIX_FMT_NV12,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.bit_depth	    = 8,
 		.plane_factor	    = { P3(1), P3(0.5) },
 		.num_planes	    = 1,
@@ -86,7 +90,8 @@ static const struct pisp_be_format supported_formats[] = {
 	},
 	{
 		.fourcc		    = V4L2_PIX_FMT_NV21,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.bit_depth	    = 8,
 		.plane_factor	    = { P3(1), P3(0.5) },
 		.num_planes	    = 1,
@@ -95,7 +100,8 @@ static const struct pisp_be_format supported_formats[] = {
 	},
 	{
 		.fourcc		    = V4L2_PIX_FMT_YUYV,
-		.align		    = 64,
+		.opt_align	    = 64,
+		.min_align	    = 16,
 		.bit_depth	    = 16,
 		.plane_factor	    = { P3(1) },
 		.num_planes	    = 1,
@@ -104,7 +110,8 @@ static const struct pisp_be_format supported_formats[] = {
 	},
 	{
 		.fourcc		    = V4L2_PIX_FMT_UYVY,
-		.align		    = 64,
+		.opt_align	    = 64,
+		.min_align	    = 16,
 		.bit_depth	    = 16,
 		.plane_factor	    = { P3(1) },
 		.num_planes	    = 1,
@@ -113,7 +120,8 @@ static const struct pisp_be_format supported_formats[] = {
 	},
 	{
 		.fourcc		    = V4L2_PIX_FMT_YVYU,
-		.align		    = 64,
+		.opt_align	    = 64,
+		.min_align	    = 16,
 		.bit_depth	    = 16,
 		.plane_factor	    = { P3(1) },
 		.num_planes	    = 1,
@@ -122,17 +130,30 @@ static const struct pisp_be_format supported_formats[] = {
 	},
 	{
 		.fourcc		    = V4L2_PIX_FMT_VYUY,
-		.align		    = 64,
+		.opt_align	    = 64,
+		.min_align	    = 16,
 		.bit_depth	    = 16,
 		.plane_factor	    = { P3(1) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_ALL_SRGB,
 		.colorspace_default = V4L2_COLORSPACE_SMPTE170M,
 	},
+	{
+		.fourcc		    = V4L2_PIX_FMT_YUV422P,
+		/* 128 alignment to ensure U/V planes are 64 byte aligned. */
+		.opt_align	    = 128,
+		.min_align	    = 32,
+		.bit_depth	    = 8,
+		.plane_factor	    = { P3(1), P3(0.5), P3(0.5) },
+		.num_planes	    = 1,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_ALL_SRGB,
+		.colorspace_default = V4L2_COLORSPACE_SMPTE170M,
+	},
 	/* Multiplane YUV formats */
 	{
 		.fourcc		    = V4L2_PIX_FMT_YUV420M,
-		.align		    = 64,
+		.opt_align	    = 64,
+		.min_align	    = 16,
 		.bit_depth	    = 8,
 		.plane_factor	    = { P3(1), P3(0.25), P3(0.25) },
 		.num_planes	    = 3,
@@ -141,7 +162,8 @@ static const struct pisp_be_format supported_formats[] = {
 	},
 	{
 		.fourcc		    = V4L2_PIX_FMT_NV12M,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.bit_depth	    = 8,
 		.plane_factor	    = { P3(1), P3(0.5) },
 		.num_planes	    = 2,
@@ -150,7 +172,8 @@ static const struct pisp_be_format supported_formats[] = {
 	},
 	{
 		.fourcc		    = V4L2_PIX_FMT_NV21M,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.bit_depth	    = 8,
 		.plane_factor	    = { P3(1), P3(0.5) },
 		.num_planes	    = 2,
@@ -159,7 +182,8 @@ static const struct pisp_be_format supported_formats[] = {
 	},
 	{
 		.fourcc		    = V4L2_PIX_FMT_YVU420M,
-		.align		    = 64,
+		.opt_align	    = 64,
+		.min_align	    = 16,
 		.bit_depth	    = 8,
 		.plane_factor	    = { P3(1), P3(0.25), P3(0.25) },
 		.num_planes	    = 3,
@@ -168,7 +192,8 @@ static const struct pisp_be_format supported_formats[] = {
 	},
 	{
 		.fourcc		    = V4L2_PIX_FMT_YUV422M,
-		.align		    = 64,
+		.opt_align	    = 64,
+		.min_align	    = 16,
 		.bit_depth	    = 8,
 		.plane_factor	    = { P3(1), P3(0.5), P3(0.5) },
 		.num_planes	    = 3,
@@ -177,7 +202,8 @@ static const struct pisp_be_format supported_formats[] = {
 	},
 	{
 		.fourcc		    = V4L2_PIX_FMT_YVU422M,
-		.align		    = 64,
+		.opt_align	    = 64,
+		.min_align	    = 16,
 		.bit_depth	    = 8,
 		.plane_factor	    = { P3(1), P3(0.5), P3(0.5) },
 		.num_planes	    = 3,
@@ -186,7 +212,8 @@ static const struct pisp_be_format supported_formats[] = {
 	},
 	{
 		.fourcc		    = V4L2_PIX_FMT_YUV444M,
-		.align		    = 64,
+		.opt_align	    = 64,
+		.min_align	    = 16,
 		.bit_depth	    = 8,
 		.plane_factor	    = { P3(1), P3(1), P3(1) },
 		.num_planes	    = 3,
@@ -195,7 +222,8 @@ static const struct pisp_be_format supported_formats[] = {
 	},
 	{
 		.fourcc		    = V4L2_PIX_FMT_YVU444M,
-		.align		    = 64,
+		.opt_align	    = 64,
+		.min_align	    = 16,
 		.bit_depth	    = 8,
 		.plane_factor	    = { P3(1), P3(1), P3(1) },
 		.num_planes	    = 3,
@@ -205,7 +233,8 @@ static const struct pisp_be_format supported_formats[] = {
 	/* RGB formats */
 	{
 		.fourcc		    = V4L2_PIX_FMT_RGB24,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.bit_depth	    = 24,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
@@ -214,7 +243,8 @@ static const struct pisp_be_format supported_formats[] = {
 	},
 	{
 		.fourcc		    = V4L2_PIX_FMT_BGR24,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.bit_depth	    = 24,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
@@ -223,7 +253,8 @@ static const struct pisp_be_format supported_formats[] = {
 	},
 	{
 		.fourcc		    = V4L2_PIX_FMT_XBGR32,
-		.align		    = 64,
+		.opt_align	    = 64,
+		.min_align	    = 16,
 		.bit_depth	    = 32,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
@@ -232,7 +263,8 @@ static const struct pisp_be_format supported_formats[] = {
 	},
 	{
 		.fourcc		    = V4L2_PIX_FMT_RGBX32,
-		.align		    = 64,
+		.opt_align	    = 64,
+		.min_align	    = 16,
 		.bit_depth	    = 32,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
@@ -241,7 +273,8 @@ static const struct pisp_be_format supported_formats[] = {
 	},
 	{
 		.fourcc		    = V4L2_PIX_FMT_RGB48,
-		.align		    = 64,
+		.opt_align	    = 64,
+		.min_align	    = 16,
 		.bit_depth	    = 48,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
@@ -250,7 +283,8 @@ static const struct pisp_be_format supported_formats[] = {
 	},
 	{
 		.fourcc		    = V4L2_PIX_FMT_BGR48,
-		.align		    = 64,
+		.opt_align	    = 64,
+		.min_align	    = 16,
 		.bit_depth	    = 48,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
@@ -261,7 +295,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_SRGGB8,
 		.bit_depth	    = 8,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -270,7 +305,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_SBGGR8,
 		.bit_depth	    = 8,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -279,7 +315,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_SGRBG8,
 		.bit_depth	    = 8,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -288,7 +325,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_SGBRG8,
 		.bit_depth	    = 8,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -298,7 +336,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_SRGGB16,
 		.bit_depth	    = 16,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -307,7 +346,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_SBGGR16,
 		.bit_depth	    = 16,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -316,7 +356,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_SGRBG16,
 		.bit_depth	    = 16,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -325,7 +366,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_SGBRG16,
 		.bit_depth	    = 16,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -336,7 +378,8 @@ static const struct pisp_be_format supported_formats[] = {
 		/* 10 bit */
 		.fourcc		    = V4L2_PIX_FMT_SRGGB10,
 		.bit_depth	    = 16,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -345,7 +388,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_SBGGR10,
 		.bit_depth	    = 16,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -354,7 +398,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_SGRBG10,
 		.bit_depth	    = 16,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -363,7 +408,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_SGBRG10,
 		.bit_depth	    = 16,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -373,7 +419,8 @@ static const struct pisp_be_format supported_formats[] = {
 		/* 12 bit */
 		.fourcc		    = V4L2_PIX_FMT_SRGGB12,
 		.bit_depth	    = 16,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -382,7 +429,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_SBGGR12,
 		.bit_depth	    = 16,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -391,7 +439,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_SGRBG12,
 		.bit_depth	    = 16,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -400,7 +449,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_SGBRG12,
 		.bit_depth	    = 16,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -410,7 +460,8 @@ static const struct pisp_be_format supported_formats[] = {
 		/* 14 bit */
 		.fourcc		    = V4L2_PIX_FMT_SRGGB14,
 		.bit_depth	    = 16,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -419,7 +470,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_SBGGR14,
 		.bit_depth	    = 16,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -428,7 +480,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_SGRBG14,
 		.bit_depth	    = 16,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -437,7 +490,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_SGBRG14,
 		.bit_depth	    = 16,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -447,7 +501,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_PISP_COMP1_BGGR,
 		.bit_depth	    = 8,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -456,7 +511,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_PISP_COMP1_RGGB,
 		.bit_depth	    = 8,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -465,7 +521,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_PISP_COMP1_GRBG,
 		.bit_depth	    = 8,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -474,7 +531,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		    = V4L2_PIX_FMT_PISP_COMP1_GBRG,
 		.bit_depth	    = 8,
-		.align		    = 32,
+		.opt_align	    = 32,
+		.min_align	    = 16,
 		.plane_factor	    = { P3(1.0) },
 		.num_planes	    = 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -484,7 +542,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		= V4L2_PIX_FMT_GREY,
 		.bit_depth	= 8,
-		.align		= 32,
+		.opt_align	= 32,
+		.min_align	= 16,
 		.num_planes	= 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
 		.colorspace_default = V4L2_COLORSPACE_RAW,
@@ -492,7 +551,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		= V4L2_PIX_FMT_Y16,
 		.bit_depth	= 16,
-		.align		= 32,
+		.opt_align	= 32,
+		.min_align	= 16,
 		.plane_factor	= { P3(1.0) },
 		.num_planes	= 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
@@ -501,7 +561,8 @@ static const struct pisp_be_format supported_formats[] = {
 	{
 		.fourcc		= V4L2_PIX_FMT_PISP_COMP1_MONO,
 		.bit_depth	= 8,
-		.align		= 32,
+		.opt_align	= 32,
+		.min_align	= 16,
 		.plane_factor	= { P3(1.0) },
 		.num_planes	= 1,
 		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/Kconfig b/drivers/media/platform/raspberrypi/rp1_cfe/Kconfig
new file mode 100644
index 000000000000..8cb1255319fb
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/rp1_cfe/Kconfig
@@ -0,0 +1,14 @@
+# RP1 V4L2 camera support
+
+config VIDEO_RP1_CFE
+	tristate "RP1 Camera Frond End (CFE) video capture driver"
+	depends on VIDEO_DEV
+	select VIDEO_V4L2_SUBDEV_API
+	select MEDIA_CONTROLLER
+	select VIDEOBUF2_DMA_CONTIG
+	select V4L2_FWNODE
+	help
+	  Say Y here to enable support for the RP1 Camera Front End.
+
+	  To compile this driver as a module, choose M here. The module will be
+	  called rp1-cfe.
diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/Makefile b/drivers/media/platform/raspberrypi/rp1_cfe/Makefile
new file mode 100644
index 000000000000..9709d6f603e9
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/rp1_cfe/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for RP1 Camera Front End driver
+#
+rp1-cfe-objs := cfe.o csi2.o pisp_fe.o dphy.o
+obj-$(CONFIG_VIDEO_RP1_CFE) += rp1-cfe.o
diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/cfe.c b/drivers/media/platform/raspberrypi/rp1_cfe/cfe.c
new file mode 100644
index 000000000000..82801ac918a2
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/rp1_cfe/cfe.c
@@ -0,0 +1,2471 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * RP1 Camera Front End Driver
+ *
+ * Copyright (C) 2021-2022 - Raspberry Pi Ltd.
+ *
+ */
+
+#include <linux/atomic.h>
+#include <linux/clk.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/phy/phy.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-async.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-dv-timings.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "cfe.h"
+#include "cfe_fmts.h"
+#include "csi2.h"
+#include "pisp_fe.h"
+#include "pisp_fe_config.h"
+#include "pisp_statistics.h"
+
+#define CFE_MODULE_NAME	"rp1-cfe"
+#define CFE_VERSION	"1.0"
+
+bool cfe_debug_verbose;
+module_param_named(verbose_debug, cfe_debug_verbose, bool, 0644);
+MODULE_PARM_DESC(verbose_debug, "verbose debugging messages");
+
+#define cfe_dbg_verbose(fmt, arg...)                          \
+	do {                                                  \
+		if (cfe_debug_verbose)                        \
+			dev_dbg(&cfe->pdev->dev, fmt, ##arg); \
+	} while (0)
+#define cfe_dbg(fmt, arg...) dev_dbg(&cfe->pdev->dev, fmt, ##arg)
+#define cfe_info(fmt, arg...) dev_info(&cfe->pdev->dev, fmt, ##arg)
+#define cfe_err(fmt, arg...) dev_err(&cfe->pdev->dev, fmt, ##arg)
+
+/* MIPICFG registers */
+#define MIPICFG_CFG		0x004
+#define MIPICFG_INTR		0x028
+#define MIPICFG_INTE		0x02c
+#define MIPICFG_INTF		0x030
+#define MIPICFG_INTS		0x034
+
+#define MIPICFG_CFG_SEL_CSI	BIT(0)
+
+#define MIPICFG_INT_CSI_DMA	BIT(0)
+#define MIPICFG_INT_CSI_HOST	BIT(2)
+#define MIPICFG_INT_PISP_FE	BIT(4)
+
+#define BPL_ALIGNMENT 16
+#define MAX_BYTESPERLINE 0xffffff00
+#define MAX_BUFFER_SIZE  0xffffff00
+/*
+ * Max width is therefore determined by the max stride divided by the number of
+ * bits per pixel.
+ *
+ * However, to avoid overflow issues let's use a 16k maximum. This lets us
+ * calculate 16k * 16k * 4 with 32bits. If we need higher maximums, a careful
+ * review and adjustment of the code is needed so that it will deal with
+ * overflows correctly.
+ */
+#define MAX_WIDTH 16384
+#define MAX_HEIGHT MAX_WIDTH
+/* Define a nominal minimum image size */
+#define MIN_WIDTH 16
+#define MIN_HEIGHT 16
+/* Default size of the embedded buffer */
+#define DEFAULT_EMBEDDED_SIZE 16384
+
+const struct v4l2_mbus_framefmt cfe_default_format = {
+	.width = 640,
+	.height = 480,
+	.code = MEDIA_BUS_FMT_SRGGB10_1X10,
+	.field = V4L2_FIELD_NONE,
+	.colorspace = V4L2_COLORSPACE_RAW,
+	.ycbcr_enc = V4L2_YCBCR_ENC_601,
+	.quantization = V4L2_QUANTIZATION_FULL_RANGE,
+	.xfer_func = V4L2_XFER_FUNC_NONE,
+};
+
+const struct v4l2_mbus_framefmt cfe_default_meta_format = {
+	.width = DEFAULT_EMBEDDED_SIZE,
+	.height = 1,
+	.code = MEDIA_BUS_FMT_SENSOR_DATA,
+	.field = V4L2_FIELD_NONE,
+};
+
+enum node_ids {
+	/* CSI2 HW output nodes first. */
+	CSI2_CH0,
+	CSI2_CH1,
+	CSI2_CH2,
+	CSI2_CH3,
+	/* FE only nodes from here on. */
+	FE_OUT0,
+	FE_OUT1,
+	FE_STATS,
+	FE_CONFIG,
+	NUM_NODES
+};
+
+struct node_description {
+	unsigned int id;
+	const char *name;
+	unsigned int caps;
+	unsigned int pad_flags;
+	unsigned int link_pad;
+};
+
+/* Must match the ordering of enum ids */
+static const struct node_description node_desc[NUM_NODES] = {
+	[CSI2_CH0] = {
+		.name = "csi2_ch0",
+		.caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_META_CAPTURE,
+		.pad_flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT,
+		.link_pad = CSI2_NUM_CHANNELS + 0
+	},
+	/*
+	 * TODO: This node should be named "csi2_ch1" and the caps should be set
+	 * to both video and meta capture. However, to keep compatibility with
+	 * the current libcamera, keep the name as "embedded" and support
+	 * only meta capture.
+	 */
+	[CSI2_CH1] = {
+		.name = "embedded",
+		.caps = V4L2_CAP_META_CAPTURE,
+		.pad_flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT,
+		.link_pad = CSI2_NUM_CHANNELS + 1
+	},
+	[CSI2_CH2] = {
+		.name = "csi2_ch2",
+		.caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_META_CAPTURE,
+		.pad_flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT,
+		.link_pad = CSI2_NUM_CHANNELS + 2
+	},
+	[CSI2_CH3] = {
+		.name = "csi2_ch3",
+		.caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_META_CAPTURE,
+		.pad_flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT,
+		.link_pad = CSI2_NUM_CHANNELS + 3
+	},
+	[FE_OUT0] = {
+		.name = "fe_image0",
+		.caps = V4L2_CAP_VIDEO_CAPTURE,
+		.pad_flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT,
+		.link_pad = FE_OUTPUT0_PAD
+	},
+	[FE_OUT1] = {
+		.name = "fe_image1",
+		.caps = V4L2_CAP_VIDEO_CAPTURE,
+		.pad_flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT,
+		.link_pad = FE_OUTPUT1_PAD
+	},
+	[FE_STATS] = {
+		.name = "fe_stats",
+		.caps = V4L2_CAP_META_CAPTURE,
+		.pad_flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT,
+		.link_pad = FE_STATS_PAD
+	},
+	[FE_CONFIG] = {
+		.name = "fe_config",
+		.caps = V4L2_CAP_META_OUTPUT,
+		.pad_flags = MEDIA_PAD_FL_SOURCE | MEDIA_PAD_FL_MUST_CONNECT,
+		.link_pad = FE_CONFIG_PAD
+	},
+};
+
+#define is_fe_node(node) (((node)->id) >= FE_OUT0)
+#define is_csi2_node(node) (!is_fe_node(node))
+
+#define node_supports_image_output(node) \
+	(!!(node_desc[(node)->id].caps & V4L2_CAP_VIDEO_CAPTURE))
+#define node_supports_meta_output(node) \
+	(!!(node_desc[(node)->id].caps & V4L2_CAP_META_CAPTURE))
+#define node_supports_image_input(node) \
+	(!!(node_desc[(node)->id].caps & V4L2_CAP_VIDEO_OUTPUT))
+#define node_supports_meta_input(node) \
+	(!!(node_desc[(node)->id].caps & V4L2_CAP_META_OUTPUT))
+#define node_supports_image(node) \
+	(node_supports_image_output(node) || node_supports_image_input(node))
+#define node_supports_meta(node) \
+	(node_supports_meta_output(node) || node_supports_meta_input(node))
+
+#define is_image_output_node(node) \
+	((node)->buffer_queue.type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+#define is_image_input_node(node) \
+	((node)->buffer_queue.type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
+#define is_image_node(node) \
+	(is_image_output_node(node) || is_image_input_node(node))
+#define is_meta_output_node(node) \
+	((node)->buffer_queue.type == V4L2_BUF_TYPE_META_CAPTURE)
+#define is_meta_input_node(node) \
+	((node)->buffer_queue.type == V4L2_BUF_TYPE_META_OUTPUT)
+#define is_meta_node(node) \
+	(is_meta_output_node(node) || is_meta_input_node(node))
+
+/* To track state across all nodes. */
+#define NUM_STATES		5
+#define NODE_REGISTERED		BIT(0)
+#define NODE_ENABLED		BIT(1)
+#define NODE_STREAMING		BIT(2)
+#define FS_INT			BIT(3)
+#define FE_INT			BIT(4)
+
+struct cfe_buffer {
+	struct vb2_v4l2_buffer vb;
+	struct list_head list;
+};
+
+struct cfe_config_buffer {
+	struct cfe_buffer buf;
+	struct pisp_fe_config config;
+};
+
+static inline struct cfe_buffer *to_cfe_buffer(struct vb2_buffer *vb)
+{
+	return container_of(vb, struct cfe_buffer, vb.vb2_buf);
+}
+
+static inline
+struct cfe_config_buffer *to_cfe_config_buffer(struct cfe_buffer *buf)
+{
+	return container_of(buf, struct cfe_config_buffer, buf);
+}
+
+struct cfe_node {
+	unsigned int id;
+	/* Pointer pointing to current v4l2_buffer */
+	struct cfe_buffer *cur_frm;
+	/* Pointer pointing to next v4l2_buffer */
+	struct cfe_buffer *next_frm;
+	/* Used to store current pixel format */
+	struct v4l2_format vid_fmt;
+	/* Used to store current meta format */
+	struct v4l2_format meta_fmt;
+	/* Buffer queue used in video-buf */
+	struct vb2_queue buffer_queue;
+	/* Queue of filled frames */
+	struct list_head dma_queue;
+	/* lock used to access this structure */
+	struct mutex lock;
+	/* Identifies video device for this channel */
+	struct video_device video_dev;
+	/* Pointer to the parent handle */
+	struct cfe_device *cfe;
+	struct media_pad pad;
+	unsigned int fs_count;
+	u64 ts;
+};
+
+struct cfe_device {
+	struct dentry *debugfs;
+	struct kref kref;
+
+	/* V4l2 specific parameters */
+	struct v4l2_async_connection *asd;
+
+	/* peripheral base address */
+	void __iomem *mipi_cfg_base;
+
+	struct clk *clk;
+
+	/* V4l2 device */
+	struct v4l2_device v4l2_dev;
+	struct media_device mdev;
+	struct media_pipeline pipe;
+
+	/* IRQ lock for node state and DMA queues */
+	spinlock_t state_lock;
+	bool job_ready;
+	bool job_queued;
+
+	/* parent device */
+	struct platform_device *pdev;
+	/* subdevice async Notifier */
+	struct v4l2_async_notifier notifier;
+
+	/* ptr to sub device */
+	struct v4l2_subdev *sensor;
+
+	struct cfe_node node[NUM_NODES];
+	DECLARE_BITMAP(node_flags, NUM_STATES * NUM_NODES);
+
+	struct csi2_device csi2;
+	struct pisp_fe_device fe;
+
+	int fe_csi2_channel;
+};
+
+static inline bool is_fe_enabled(struct cfe_device *cfe)
+{
+	return cfe->fe_csi2_channel != -1;
+}
+
+static inline struct cfe_device *to_cfe_device(struct v4l2_device *v4l2_dev)
+{
+	return container_of(v4l2_dev, struct cfe_device, v4l2_dev);
+}
+
+static inline u32 cfg_reg_read(struct cfe_device *cfe, u32 offset)
+{
+	return readl(cfe->mipi_cfg_base + offset);
+}
+
+static inline void cfg_reg_write(struct cfe_device *cfe, u32 offset, u32 val)
+{
+	writel(val, cfe->mipi_cfg_base + offset);
+}
+
+static bool check_state(struct cfe_device *cfe, unsigned long state,
+			unsigned int node_id)
+{
+	unsigned long bit;
+
+	for_each_set_bit(bit, &state, sizeof(state)) {
+		if (!test_bit(bit + (node_id * NUM_STATES), cfe->node_flags))
+			return false;
+	}
+	return true;
+}
+
+static void set_state(struct cfe_device *cfe, unsigned long state,
+		      unsigned int node_id)
+{
+	unsigned long bit;
+
+	for_each_set_bit(bit, &state, sizeof(state))
+		set_bit(bit + (node_id * NUM_STATES), cfe->node_flags);
+}
+
+static void clear_state(struct cfe_device *cfe, unsigned long state,
+			unsigned int node_id)
+{
+	unsigned long bit;
+
+	for_each_set_bit(bit, &state, sizeof(state))
+		clear_bit(bit + (node_id * NUM_STATES), cfe->node_flags);
+}
+
+static bool test_any_node(struct cfe_device *cfe, unsigned long cond)
+{
+	unsigned int i;
+
+	for (i = 0; i < NUM_NODES; i++) {
+		if (check_state(cfe, cond, i))
+			return true;
+	}
+
+	return false;
+}
+
+static bool test_all_nodes(struct cfe_device *cfe, unsigned long precond,
+			   unsigned long cond)
+{
+	unsigned int i;
+
+	for (i = 0; i < NUM_NODES; i++) {
+		if (check_state(cfe, precond, i)) {
+			if (!check_state(cfe, cond, i))
+				return false;
+		}
+	}
+
+	return true;
+}
+
+static int mipi_cfg_regs_show(struct seq_file *s, void *data)
+{
+	struct cfe_device *cfe = s->private;
+	int ret;
+
+	ret = pm_runtime_resume_and_get(&cfe->pdev->dev);
+	if (ret)
+		return ret;
+
+#define DUMP(reg) seq_printf(s, #reg " \t0x%08x\n", cfg_reg_read(cfe, reg))
+	DUMP(MIPICFG_CFG);
+	DUMP(MIPICFG_INTR);
+	DUMP(MIPICFG_INTE);
+	DUMP(MIPICFG_INTF);
+	DUMP(MIPICFG_INTS);
+#undef DUMP
+
+	pm_runtime_put(&cfe->pdev->dev);
+
+	return 0;
+}
+
+static int format_show(struct seq_file *s, void *data)
+{
+	struct cfe_device *cfe = s->private;
+	unsigned int i;
+
+	for (i = 0; i < NUM_NODES; i++) {
+		struct cfe_node *node = &cfe->node[i];
+		unsigned long sb, state = 0;
+
+		for (sb = 0; sb < NUM_STATES; sb++) {
+			if (check_state(cfe, BIT(sb), i))
+				state |= BIT(sb);
+		}
+
+		seq_printf(s, "\nNode %u (%s) state: 0x%lx\n", i,
+			   node_desc[i].name, state);
+
+		if (node_supports_image(node))
+			seq_printf(s, "format: " V4L2_FOURCC_CONV " 0x%x\n"
+				      "resolution: %ux%u\nbpl: %u\nsize: %u\n",
+				   V4L2_FOURCC_CONV_ARGS(node->vid_fmt.fmt.pix.pixelformat),
+				   node->vid_fmt.fmt.pix.pixelformat,
+				   node->vid_fmt.fmt.pix.width,
+				   node->vid_fmt.fmt.pix.height,
+				   node->vid_fmt.fmt.pix.bytesperline,
+				   node->vid_fmt.fmt.pix.sizeimage);
+
+		if (node_supports_meta(node))
+			seq_printf(s, "format: " V4L2_FOURCC_CONV " 0x%x\nsize: %u\n",
+				   V4L2_FOURCC_CONV_ARGS(node->meta_fmt.fmt.meta.dataformat),
+				   node->meta_fmt.fmt.meta.dataformat,
+				   node->meta_fmt.fmt.meta.buffersize);
+	}
+
+	return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(mipi_cfg_regs);
+DEFINE_SHOW_ATTRIBUTE(format);
+
+/* Format setup functions */
+const struct cfe_fmt *find_format_by_code(u32 code)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(formats); i++) {
+		if (formats[i].code == code)
+			return &formats[i];
+	}
+
+	return NULL;
+}
+
+const struct cfe_fmt *find_format_by_pix(u32 pixelformat)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(formats); i++) {
+		if (formats[i].fourcc == pixelformat)
+			return &formats[i];
+	}
+
+	return NULL;
+}
+
+/*
+ * Given the mbus code, find the 16 bit remapped code. Returns 0 if no remap
+ * possible.
+ */
+u32 cfe_find_16bit_code(u32 code)
+{
+	const struct cfe_fmt *cfe_fmt;
+
+	cfe_fmt = find_format_by_code(code);
+
+	if (!cfe_fmt || !cfe_fmt->remap[CFE_REMAP_16BIT])
+		return 0;
+
+	cfe_fmt = find_format_by_pix(cfe_fmt->remap[CFE_REMAP_16BIT]);
+	if (!cfe_fmt)
+		return 0;
+
+	return cfe_fmt->code;
+}
+
+/*
+ * Given the mbus code, find the 8 bit compressed code. Returns 0 if no remap
+ * possible.
+ */
+u32 cfe_find_compressed_code(u32 code)
+{
+	const struct cfe_fmt *cfe_fmt;
+
+	cfe_fmt = find_format_by_code(code);
+
+	if (!cfe_fmt || !cfe_fmt->remap[CFE_REMAP_COMPRESSED])
+		return 0;
+
+	cfe_fmt = find_format_by_pix(cfe_fmt->remap[CFE_REMAP_COMPRESSED]);
+	if (!cfe_fmt)
+		return 0;
+
+	return cfe_fmt->code;
+}
+
+static int cfe_calc_format_size_bpl(struct cfe_device *cfe,
+				    const struct cfe_fmt *fmt,
+				    struct v4l2_format *f)
+{
+	unsigned int min_bytesperline;
+
+	v4l_bound_align_image(&f->fmt.pix.width, MIN_WIDTH, MAX_WIDTH, 2,
+			      &f->fmt.pix.height, MIN_HEIGHT, MAX_HEIGHT, 0, 0);
+
+	min_bytesperline =
+		ALIGN((f->fmt.pix.width * fmt->depth) >> 3, BPL_ALIGNMENT);
+
+	if (f->fmt.pix.bytesperline > min_bytesperline &&
+	    f->fmt.pix.bytesperline <= MAX_BYTESPERLINE)
+		f->fmt.pix.bytesperline =
+			ALIGN(f->fmt.pix.bytesperline, BPL_ALIGNMENT);
+	else
+		f->fmt.pix.bytesperline = min_bytesperline;
+
+	f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
+
+	cfe_dbg("%s: " V4L2_FOURCC_CONV " size: %ux%u bpl:%u img_size:%u\n",
+		__func__, V4L2_FOURCC_CONV_ARGS(f->fmt.pix.pixelformat),
+		f->fmt.pix.width, f->fmt.pix.height,
+		f->fmt.pix.bytesperline, f->fmt.pix.sizeimage);
+
+	return 0;
+}
+
+static void cfe_schedule_next_csi2_job(struct cfe_device *cfe)
+{
+	struct cfe_buffer *buf;
+	unsigned int i;
+	dma_addr_t addr;
+
+	for (i = 0; i < CSI2_NUM_CHANNELS; i++) {
+		struct cfe_node *node = &cfe->node[i];
+		unsigned int stride, size;
+
+		if (!check_state(cfe, NODE_STREAMING, i))
+			continue;
+
+		buf = list_first_entry(&node->dma_queue, struct cfe_buffer,
+				       list);
+		node->next_frm = buf;
+		list_del(&buf->list);
+
+		cfe_dbg_verbose("%s: [%s] buffer:%p\n", __func__,
+				node_desc[node->id].name, &buf->vb.vb2_buf);
+
+		if (is_meta_node(node)) {
+			size = node->meta_fmt.fmt.meta.buffersize;
+			stride = 0;
+		} else {
+			size = node->vid_fmt.fmt.pix.sizeimage;
+			stride = node->vid_fmt.fmt.pix.bytesperline;
+		}
+
+		addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0);
+		csi2_set_buffer(&cfe->csi2, node->id, addr, stride, size);
+	}
+}
+
+static void cfe_schedule_next_pisp_job(struct cfe_device *cfe)
+{
+	struct vb2_buffer *vb2_bufs[FE_NUM_PADS] = { 0 };
+	struct cfe_config_buffer *config_buf;
+	struct cfe_buffer *buf;
+	unsigned int i;
+
+	for (i = CSI2_NUM_CHANNELS; i < NUM_NODES; i++) {
+		struct cfe_node *node = &cfe->node[i];
+
+		if (!check_state(cfe, NODE_STREAMING, i))
+			continue;
+
+		buf = list_first_entry(&node->dma_queue, struct cfe_buffer,
+				       list);
+
+		cfe_dbg_verbose("%s: [%s] buffer:%p\n", __func__,
+				node_desc[node->id].name, &buf->vb.vb2_buf);
+
+		node->next_frm = buf;
+		vb2_bufs[node_desc[i].link_pad] = &buf->vb.vb2_buf;
+		list_del(&buf->list);
+	}
+
+	config_buf = to_cfe_config_buffer(cfe->node[FE_CONFIG].next_frm);
+	pisp_fe_submit_job(&cfe->fe, vb2_bufs, &config_buf->config);
+}
+
+static bool cfe_check_job_ready(struct cfe_device *cfe)
+{
+	unsigned int i;
+
+	for (i = 0; i < NUM_NODES; i++) {
+		struct cfe_node *node = &cfe->node[i];
+
+		if (!check_state(cfe, NODE_ENABLED, i))
+			continue;
+
+		if (list_empty(&node->dma_queue)) {
+			cfe_dbg_verbose("%s: [%s] has no buffer, unable to schedule job\n",
+				__func__, node_desc[i].name);
+			return false;
+		}
+	}
+
+	return true;
+}
+
+static void cfe_prepare_next_job(struct cfe_device *cfe)
+{
+	cfe->job_queued = true;
+	cfe_schedule_next_csi2_job(cfe);
+	if (is_fe_enabled(cfe))
+		cfe_schedule_next_pisp_job(cfe);
+
+	/* Flag if another job is ready after this. */
+	cfe->job_ready = cfe_check_job_ready(cfe);
+
+	cfe_dbg_verbose("%s: end with scheduled job\n", __func__);
+}
+
+static void cfe_process_buffer_complete(struct cfe_node *node,
+					enum vb2_buffer_state state)
+{
+	struct cfe_device *cfe = node->cfe;
+
+	cfe_dbg_verbose("%s: [%s] buffer:%p\n", __func__,
+			node_desc[node->id].name, &node->cur_frm->vb.vb2_buf);
+
+	node->cur_frm->vb.sequence = node->fs_count - 1;
+	vb2_buffer_done(&node->cur_frm->vb.vb2_buf, state);
+}
+
+static void cfe_queue_event_sof(struct cfe_node *node)
+{
+	struct v4l2_event event = {
+		.type = V4L2_EVENT_FRAME_SYNC,
+		.u.frame_sync.frame_sequence = node->fs_count - 1,
+	};
+
+	v4l2_event_queue(&node->video_dev, &event);
+}
+
+static void cfe_sof_isr_handler(struct cfe_node *node)
+{
+	struct cfe_device *cfe = node->cfe;
+	bool matching_fs = true;
+	unsigned int i;
+
+	cfe_dbg_verbose("%s: [%s] seq %u\n", __func__, node_desc[node->id].name,
+			node->fs_count);
+
+	/*
+	 * If the sensor is producing unexpected frame event ordering over a
+	 * sustained period of time, guard against the possibility of coming
+	 * here and orphaning the cur_frm if it's not been dequeued already.
+	 * Unfortunately, there is not enough hardware state to tell if this
+	 * may have occurred.
+	 */
+	if (WARN(node->cur_frm, "%s: [%s] Orphanded frame at seq %u\n",
+		 __func__, node_desc[node->id].name, node->fs_count))
+		cfe_process_buffer_complete(node, VB2_BUF_STATE_ERROR);
+
+	node->cur_frm = node->next_frm;
+	node->next_frm = NULL;
+	node->fs_count++;
+
+	node->ts = ktime_get_ns();
+	for (i = 0; i < NUM_NODES; i++) {
+		if (!check_state(cfe, NODE_STREAMING, i) || i == node->id)
+			continue;
+		/*
+		 * This checks if any other node has seen a FS. If yes, use the
+		 * same timestamp, eventually across all node buffers.
+		 */
+		if (cfe->node[i].fs_count >= node->fs_count)
+			node->ts = cfe->node[i].ts;
+		/*
+		 * This checks if all other node have seen a matching FS. If
+		 * yes, we can flag another job to be queued.
+		 */
+		if (matching_fs && cfe->node[i].fs_count != node->fs_count)
+			matching_fs = false;
+	}
+
+	if (matching_fs)
+		cfe->job_queued = false;
+
+	if (node->cur_frm)
+		node->cur_frm->vb.vb2_buf.timestamp = node->ts;
+
+	set_state(cfe, FS_INT, node->id);
+	clear_state(cfe, FE_INT, node->id);
+
+	if (is_image_output_node(node))
+		cfe_queue_event_sof(node);
+}
+
+static void cfe_eof_isr_handler(struct cfe_node *node)
+{
+	struct cfe_device *cfe = node->cfe;
+
+	cfe_dbg_verbose("%s: [%s] seq %u\n", __func__, node_desc[node->id].name,
+			node->fs_count - 1);
+
+	if (node->cur_frm)
+		cfe_process_buffer_complete(node, VB2_BUF_STATE_DONE);
+
+	node->cur_frm = NULL;
+	set_state(cfe, FE_INT, node->id);
+	clear_state(cfe, FS_INT, node->id);
+}
+
+static irqreturn_t cfe_isr(int irq, void *dev)
+{
+	struct cfe_device *cfe = dev;
+	unsigned int i;
+	bool sof[NUM_NODES] = {0}, eof[NUM_NODES] = {0};
+	u32 sts;
+
+	sts = cfg_reg_read(cfe, MIPICFG_INTS);
+
+	if (sts & MIPICFG_INT_CSI_DMA)
+		csi2_isr(&cfe->csi2, sof, eof);
+
+	if (sts & MIPICFG_INT_PISP_FE)
+		pisp_fe_isr(&cfe->fe, sof + CSI2_NUM_CHANNELS,
+			    eof + CSI2_NUM_CHANNELS);
+
+	spin_lock(&cfe->state_lock);
+
+	for (i = 0; i < NUM_NODES; i++) {
+		struct cfe_node *node = &cfe->node[i];
+
+		/*
+		 * The check_state(NODE_STREAMING) is to ensure we do not loop
+		 * over the CSI2_CHx nodes when the FE is active since they
+		 * generate interrupts even though the node is not streaming.
+		 */
+		if (!check_state(cfe, NODE_STREAMING, i) ||
+		    !(sof[i] || eof[i]))
+			continue;
+
+		/*
+		 * There are 3 cases where we could get FS + FE_ACK at
+		 * the same time:
+		 * 1) FE of the current frame, and FS of the next frame.
+		 * 2) FS + FE of the same frame.
+		 * 3) FE of the current frame, and FS + FE of the next
+		 *    frame. To handle this, see the sof handler below.
+		 *
+		 * (1) is handled implicitly by the ordering of the FE and FS
+		 * handlers below.
+		 */
+		if (eof[i]) {
+			/*
+			 * The condition below tests for (2). Run the FS handler
+			 * first before the FE handler, both for the current
+			 * frame.
+			 */
+			if (sof[i] && !check_state(cfe, FS_INT, i)) {
+				cfe_sof_isr_handler(node);
+				sof[i] = false;
+			}
+
+			cfe_eof_isr_handler(node);
+		}
+
+		if (sof[i]) {
+			/*
+			 * The condition below tests for (3). In such cases, we
+			 * come in here with FS flag set in the node state from
+			 * the previous frame since it only gets cleared in
+			 * eof_isr_handler(). Handle the FE for the previous
+			 * frame first before the FS handler for the current
+			 * frame.
+			 */
+			if (check_state(cfe, FS_INT, node->id) &&
+			    !check_state(cfe, FE_INT, node->id)) {
+				cfe_dbg("%s: [%s] Handling missing previous FE interrupt\n",
+					__func__, node_desc[node->id].name);
+				cfe_eof_isr_handler(node);
+			}
+
+			cfe_sof_isr_handler(node);
+		}
+
+		if (!cfe->job_queued && cfe->job_ready)
+			cfe_prepare_next_job(cfe);
+	}
+
+	spin_unlock(&cfe->state_lock);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Stream helpers
+ */
+
+static void cfe_start_channel(struct cfe_node *node)
+{
+	struct cfe_device *cfe = node->cfe;
+	struct v4l2_subdev_state *state;
+	struct v4l2_mbus_framefmt *source_fmt;
+	const struct cfe_fmt *fmt;
+	unsigned long flags;
+	bool start_fe = is_fe_enabled(cfe) &&
+			test_all_nodes(cfe, NODE_ENABLED, NODE_STREAMING);
+
+	cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name);
+
+	state = v4l2_subdev_lock_and_get_active_state(&cfe->csi2.sd);
+
+	if (start_fe) {
+		unsigned int width, height;
+
+		WARN_ON(!is_fe_enabled(cfe));
+		cfe_dbg("%s: %s using csi2 channel %d\n",
+			__func__, node_desc[FE_OUT0].name,
+			cfe->fe_csi2_channel);
+
+		source_fmt = v4l2_subdev_state_get_format(state,
+							cfe->fe_csi2_channel);
+		fmt = find_format_by_code(source_fmt->code);
+
+		width = source_fmt->width;
+		height = source_fmt->height;
+
+		/*
+		 * Must have a valid CSI2 datatype, but it will be 0 for 16-bit
+		 * formats to work-around the HW mismatch.
+		 */
+		WARN_ON(fmt->depth != 16 && !fmt->csi_dt);
+
+		/*
+		 * Start the associated CSI2 Channel as well.
+		 *
+		 * Must write to the ADDR register to latch the ctrl values
+		 * even if we are connected to the front end. Once running,
+		 * this is handled by the CSI2 AUTO_ARM mode.
+		 */
+		csi2_start_channel(&cfe->csi2, cfe->fe_csi2_channel,
+				   CSI2_MODE_FE_STREAMING,
+				   true, false, width, height);
+		csi2_set_buffer(&cfe->csi2, cfe->fe_csi2_channel, 0, 0, -1);
+		pisp_fe_start(&cfe->fe);
+	}
+
+	if (is_csi2_node(node)) {
+		unsigned int width = 0, height = 0;
+
+		u32 mode = CSI2_MODE_NORMAL;
+
+		source_fmt = v4l2_subdev_state_get_format(state,
+			node_desc[node->id].link_pad - CSI2_NUM_CHANNELS);
+		fmt = find_format_by_code(source_fmt->code);
+
+		/* Must have a valid CSI2 datatype. */
+		WARN_ON(!fmt->csi_dt);
+
+		if (is_image_output_node(node)) {
+			width = source_fmt->width;
+			height = source_fmt->height;
+
+			/* We don't need to repack in the case of 16-bit output. */
+			if (fmt->depth != 16 &&
+			    node->vid_fmt.fmt.pix.pixelformat ==
+					fmt->remap[CFE_REMAP_16BIT])
+				mode = CSI2_MODE_REMAP;
+			else if (node->vid_fmt.fmt.pix.pixelformat ==
+					fmt->remap[CFE_REMAP_COMPRESSED]) {
+				mode = CSI2_MODE_COMPRESSED;
+				csi2_set_compression(&cfe->csi2, node->id,
+						     CSI2_COMPRESSION_DELTA, 0,
+						     0);
+			}
+		}
+		/* Unconditionally start this CSI2 channel. */
+		csi2_start_channel(&cfe->csi2, node->id,
+				   mode,
+				   /* Auto arm */
+				   false,
+				   /* Pack bytes */
+				   is_meta_node(node) ? true : false,
+				   width, height);
+	}
+
+	v4l2_subdev_unlock_state(state);
+
+	spin_lock_irqsave(&cfe->state_lock, flags);
+	if (cfe->job_ready && test_all_nodes(cfe, NODE_ENABLED, NODE_STREAMING))
+		cfe_prepare_next_job(cfe);
+	spin_unlock_irqrestore(&cfe->state_lock, flags);
+}
+
+static void cfe_stop_channel(struct cfe_node *node, bool fe_stop)
+{
+	struct cfe_device *cfe = node->cfe;
+
+	cfe_dbg("%s: [%s] fe_stop %u\n", __func__,
+		node_desc[node->id].name, fe_stop);
+
+	if (fe_stop) {
+		csi2_stop_channel(&cfe->csi2, cfe->fe_csi2_channel);
+		pisp_fe_stop(&cfe->fe);
+	}
+
+	if (is_csi2_node(node))
+		csi2_stop_channel(&cfe->csi2, node->id);
+}
+
+static void cfe_return_buffers(struct cfe_node *node,
+			       enum vb2_buffer_state state)
+{
+	struct cfe_device *cfe = node->cfe;
+	struct cfe_buffer *buf, *tmp;
+	unsigned long flags;
+
+	cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name);
+
+	spin_lock_irqsave(&cfe->state_lock, flags);
+	list_for_each_entry_safe(buf, tmp, &node->dma_queue, list) {
+		list_del(&buf->list);
+		vb2_buffer_done(&buf->vb.vb2_buf, state);
+	}
+
+	if (node->cur_frm)
+		vb2_buffer_done(&node->cur_frm->vb.vb2_buf, state);
+	if (node->next_frm && node->cur_frm != node->next_frm)
+		vb2_buffer_done(&node->next_frm->vb.vb2_buf, state);
+
+	node->cur_frm = NULL;
+	node->next_frm = NULL;
+	spin_unlock_irqrestore(&cfe->state_lock, flags);
+}
+
+/*
+ * vb2 ops
+ */
+
+static int cfe_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
+			   unsigned int *nplanes, unsigned int sizes[],
+			   struct device *alloc_devs[])
+{
+	struct cfe_node *node = vb2_get_drv_priv(vq);
+	struct cfe_device *cfe = node->cfe;
+	unsigned int size = is_image_node(node) ? node->vid_fmt.fmt.pix.sizeimage :
+						  node->meta_fmt.fmt.meta.buffersize;
+
+	cfe_dbg("%s: [%s] type:%u\n", __func__, node_desc[node->id].name,
+		node->buffer_queue.type);
+
+	if (vb2_get_num_buffers(vq) + *nbuffers < 3)
+		*nbuffers = 3 - vb2_get_num_buffers(vq);
+
+	if (*nplanes) {
+		if (sizes[0] < size) {
+			cfe_err("sizes[0] %i < size %u\n", sizes[0], size);
+			return -EINVAL;
+		}
+		size = sizes[0];
+	}
+
+	*nplanes = 1;
+	sizes[0] = size;
+
+	return 0;
+}
+
+static int cfe_buffer_prepare(struct vb2_buffer *vb)
+{
+	struct cfe_node *node = vb2_get_drv_priv(vb->vb2_queue);
+	struct cfe_device *cfe = node->cfe;
+	struct cfe_buffer *buf = to_cfe_buffer(vb);
+	unsigned long size;
+
+	cfe_dbg_verbose("%s: [%s] buffer:%p\n", __func__,
+			node_desc[node->id].name, vb);
+
+	size = is_image_node(node) ? node->vid_fmt.fmt.pix.sizeimage :
+				     node->meta_fmt.fmt.meta.buffersize;
+	if (vb2_plane_size(vb, 0) < size) {
+		cfe_err("data will not fit into plane (%lu < %lu)\n",
+			vb2_plane_size(vb, 0), size);
+		return -EINVAL;
+	}
+
+	vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size);
+
+	if (node->id == FE_CONFIG) {
+		struct cfe_config_buffer *b = to_cfe_config_buffer(buf);
+		void *addr = vb2_plane_vaddr(vb, 0);
+
+		memcpy(&b->config, addr, sizeof(struct pisp_fe_config));
+		return pisp_fe_validate_config(&cfe->fe, &b->config,
+					       &cfe->node[FE_OUT0].vid_fmt,
+					       &cfe->node[FE_OUT1].vid_fmt);
+	}
+
+	return 0;
+}
+
+static void cfe_buffer_queue(struct vb2_buffer *vb)
+{
+	struct cfe_node *node = vb2_get_drv_priv(vb->vb2_queue);
+	struct cfe_device *cfe = node->cfe;
+	struct cfe_buffer *buf = to_cfe_buffer(vb);
+	unsigned long flags;
+
+	cfe_dbg_verbose("%s: [%s] buffer:%p\n", __func__,
+			node_desc[node->id].name, vb);
+
+	spin_lock_irqsave(&cfe->state_lock, flags);
+
+	list_add_tail(&buf->list, &node->dma_queue);
+
+	if (!cfe->job_ready)
+		cfe->job_ready = cfe_check_job_ready(cfe);
+
+	if (!cfe->job_queued && cfe->job_ready &&
+	    test_all_nodes(cfe, NODE_ENABLED, NODE_STREAMING)) {
+		cfe_dbg("Preparing job immediately for channel %u\n",
+			node->id);
+		cfe_prepare_next_job(cfe);
+	}
+
+	spin_unlock_irqrestore(&cfe->state_lock, flags);
+}
+
+static u64 sensor_link_rate(struct cfe_device *cfe)
+{
+	struct v4l2_mbus_framefmt *source_fmt;
+	struct v4l2_subdev_state *state;
+	struct media_entity *entity;
+	struct v4l2_subdev *subdev;
+	const struct cfe_fmt *fmt;
+	struct media_pad *pad;
+	s64 link_freq;
+
+	state = v4l2_subdev_lock_and_get_active_state(&cfe->csi2.sd);
+	source_fmt = v4l2_subdev_state_get_format(state, 0);
+	fmt = find_format_by_code(source_fmt->code);
+	v4l2_subdev_unlock_state(state);
+
+	/*
+	 * Walk up the media graph to find either the sensor entity, or another
+	 * entity that advertises the V4L2_CID_LINK_FREQ or V4L2_CID_PIXEL_RATE
+	 * control through the subdev.
+	 */
+	entity = &cfe->csi2.sd.entity;
+	while (1) {
+		pad = &entity->pads[0];
+		if (!(pad->flags & MEDIA_PAD_FL_SINK))
+			goto err;
+
+		pad = media_pad_remote_pad_first(pad);
+		if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
+			goto err;
+
+		entity = pad->entity;
+		subdev = media_entity_to_v4l2_subdev(entity);
+		if (entity->function == MEDIA_ENT_F_CAM_SENSOR ||
+		    v4l2_ctrl_find(subdev->ctrl_handler, V4L2_CID_LINK_FREQ) ||
+		    v4l2_ctrl_find(subdev->ctrl_handler, V4L2_CID_PIXEL_RATE))
+			break;
+	}
+
+	link_freq = v4l2_get_link_freq(subdev->ctrl_handler, fmt->depth,
+				       cfe->csi2.dphy.active_lanes * 2);
+	if (link_freq < 0)
+		goto err;
+
+	/* x2 for DDR. */
+	link_freq *= 2;
+	cfe_info("Using a link rate of %lld Mbps\n", link_freq / (1000 * 1000));
+	return link_freq;
+
+err:
+	cfe_err("Unable to determine sensor link rate, using 999 Mbps\n");
+	return 999 * 1000000UL;
+}
+
+static int cfe_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct v4l2_mbus_config mbus_config = { 0 };
+	struct cfe_node *node = vb2_get_drv_priv(vq);
+	struct cfe_device *cfe = node->cfe;
+	int ret;
+
+	cfe_dbg("%s: [%s] begin.\n", __func__, node_desc[node->id].name);
+
+	if (!check_state(cfe, NODE_ENABLED, node->id)) {
+		cfe_err("%s node link is not enabled.\n",
+			node_desc[node->id].name);
+		ret = -EINVAL;
+		goto err_streaming;
+	}
+
+	ret = pm_runtime_resume_and_get(&cfe->pdev->dev);
+	if (ret < 0) {
+		cfe_err("pm_runtime_resume_and_get failed\n");
+		goto err_streaming;
+	}
+
+	/* When using the Frontend, we must enable the FE_CONFIG node. */
+	if (is_fe_enabled(cfe) &&
+	    !check_state(cfe, NODE_ENABLED, cfe->node[FE_CONFIG].id)) {
+		cfe_err("FE enabled, but FE_CONFIG node is not\n");
+		ret = -EINVAL;
+		goto err_pm_put;
+	}
+
+	ret = media_pipeline_start(&node->pad, &cfe->pipe);
+	if (ret < 0) {
+		cfe_err("Failed to start media pipeline: %d\n", ret);
+		goto err_pm_put;
+	}
+
+	clear_state(cfe, FS_INT | FE_INT, node->id);
+	set_state(cfe, NODE_STREAMING, node->id);
+	node->fs_count = 0;
+	cfe_start_channel(node);
+
+	if (!test_all_nodes(cfe, NODE_ENABLED, NODE_STREAMING)) {
+		cfe_dbg("Not all nodes are set to streaming yet!\n");
+		return 0;
+	}
+
+	cfg_reg_write(cfe, MIPICFG_CFG, MIPICFG_CFG_SEL_CSI);
+	cfg_reg_write(cfe, MIPICFG_INTE, MIPICFG_INT_CSI_DMA | MIPICFG_INT_PISP_FE);
+
+	ret = v4l2_subdev_call(cfe->sensor, pad, get_mbus_config, 0,
+			       &mbus_config);
+	if (ret < 0 && ret != -ENOIOCTLCMD) {
+		cfe_err("g_mbus_config failed\n");
+		goto err_pm_put;
+	}
+
+	cfe->csi2.dphy.active_lanes = mbus_config.bus.mipi_csi2.num_data_lanes;
+	if (!cfe->csi2.dphy.active_lanes)
+		cfe->csi2.dphy.active_lanes = cfe->csi2.dphy.max_lanes;
+	if (cfe->csi2.dphy.active_lanes > cfe->csi2.dphy.max_lanes) {
+		cfe_err("Device has requested %u data lanes, which is >%u configured in DT\n",
+			cfe->csi2.dphy.active_lanes, cfe->csi2.dphy.max_lanes);
+		ret = -EINVAL;
+		goto err_disable_cfe;
+	}
+
+	cfe_dbg("Configuring CSI-2 block - %u data lanes\n", cfe->csi2.dphy.active_lanes);
+	cfe->csi2.dphy.dphy_rate = sensor_link_rate(cfe) / 1000000UL;
+	csi2_open_rx(&cfe->csi2);
+
+	cfe_dbg("Starting sensor streaming\n");
+	ret = v4l2_subdev_call(cfe->sensor, video, s_stream, 1);
+	if (ret < 0) {
+		cfe_err("stream on failed in subdev\n");
+		goto err_disable_cfe;
+	}
+
+	cfe_dbg("%s: [%s] end.\n", __func__, node_desc[node->id].name);
+
+	return 0;
+
+err_disable_cfe:
+	csi2_close_rx(&cfe->csi2);
+	cfe_stop_channel(node, true);
+	media_pipeline_stop(&node->pad);
+err_pm_put:
+	pm_runtime_put(&cfe->pdev->dev);
+err_streaming:
+	cfe_return_buffers(node, VB2_BUF_STATE_QUEUED);
+	clear_state(cfe, NODE_STREAMING, node->id);
+
+	return ret;
+}
+
+static void cfe_stop_streaming(struct vb2_queue *vq)
+{
+	struct cfe_node *node = vb2_get_drv_priv(vq);
+	struct cfe_device *cfe = node->cfe;
+	unsigned long flags;
+	bool fe_stop;
+
+	cfe_dbg("%s: [%s] begin.\n", __func__, node_desc[node->id].name);
+
+	spin_lock_irqsave(&cfe->state_lock, flags);
+	fe_stop = is_fe_enabled(cfe) &&
+		  test_all_nodes(cfe, NODE_ENABLED, NODE_STREAMING);
+
+	cfe->job_ready = false;
+	clear_state(cfe, NODE_STREAMING, node->id);
+	spin_unlock_irqrestore(&cfe->state_lock, flags);
+
+	cfe_stop_channel(node, fe_stop);
+
+	if (!test_any_node(cfe, NODE_STREAMING)) {
+		/* Stop streaming the sensor and disable the peripheral. */
+		if (v4l2_subdev_call(cfe->sensor, video, s_stream, 0) < 0)
+			cfe_err("stream off failed in subdev\n");
+
+		csi2_close_rx(&cfe->csi2);
+
+		cfg_reg_write(cfe, MIPICFG_INTE, 0);
+	}
+
+	media_pipeline_stop(&node->pad);
+
+	/* Clear all queued buffers for the node */
+	cfe_return_buffers(node, VB2_BUF_STATE_ERROR);
+
+	pm_runtime_put(&cfe->pdev->dev);
+
+	cfe_dbg("%s: [%s] end.\n", __func__, node_desc[node->id].name);
+}
+
+static const struct vb2_ops cfe_video_qops = {
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish,
+	.queue_setup = cfe_queue_setup,
+	.buf_prepare = cfe_buffer_prepare,
+	.buf_queue = cfe_buffer_queue,
+	.start_streaming = cfe_start_streaming,
+	.stop_streaming = cfe_stop_streaming,
+};
+
+/*
+ * v4l2 ioctl ops
+ */
+
+static int cfe_querycap(struct file *file, void *priv,
+			struct v4l2_capability *cap)
+{
+	struct cfe_node *node = video_drvdata(file);
+	struct cfe_device *cfe = node->cfe;
+
+	strscpy(cap->driver, CFE_MODULE_NAME, sizeof(cap->driver));
+	strscpy(cap->card, CFE_MODULE_NAME, sizeof(cap->card));
+
+	snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
+		 dev_name(&cfe->pdev->dev));
+
+	cap->capabilities |= V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_META_CAPTURE |
+			     V4L2_CAP_META_OUTPUT;
+
+	return 0;
+}
+
+static int cfe_enum_fmt_vid_cap(struct file *file, void *priv,
+				struct v4l2_fmtdesc *f)
+{
+	struct cfe_node *node = video_drvdata(file);
+	struct cfe_device *cfe = node->cfe;
+	unsigned int i, j;
+
+	if (!node_supports_image_output(node))
+		return -EINVAL;
+
+	cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name);
+
+	for (i = 0, j = 0; i < ARRAY_SIZE(formats); i++) {
+		if (f->mbus_code && formats[i].code != f->mbus_code)
+			continue;
+
+		if (formats[i].flags & CFE_FORMAT_FLAG_META_OUT ||
+		    formats[i].flags & CFE_FORMAT_FLAG_META_CAP)
+			continue;
+
+		if (is_fe_node(node) &&
+		    !(formats[i].flags & CFE_FORMAT_FLAG_FE_OUT))
+			continue;
+
+		if (j == f->index) {
+			f->pixelformat = formats[i].fourcc;
+			f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+			return 0;
+		}
+		j++;
+	}
+
+	return -EINVAL;
+}
+
+static int cfe_g_fmt(struct file *file, void *priv,
+		     struct v4l2_format *f)
+{
+	struct cfe_node *node = video_drvdata(file);
+	struct cfe_device *cfe = node->cfe;
+
+	cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name);
+
+	if (!node_supports_image(node))
+		return -EINVAL;
+
+	*f = node->vid_fmt;
+
+	return 0;
+}
+
+static int try_fmt_vid_cap(struct cfe_node *node, struct v4l2_format *f)
+{
+	struct cfe_device *cfe = node->cfe;
+	const struct cfe_fmt *fmt;
+
+	cfe_dbg("%s: [%s] %ux%u, V4L2 pix " V4L2_FOURCC_CONV "\n",
+		__func__, node_desc[node->id].name,
+		f->fmt.pix.width, f->fmt.pix.height,
+		V4L2_FOURCC_CONV_ARGS(f->fmt.pix.pixelformat));
+
+	if (!node_supports_image_output(node))
+		return -EINVAL;
+
+	/*
+	 * Default to a format that works for both CSI2 and FE.
+	 */
+	fmt = find_format_by_pix(f->fmt.pix.pixelformat);
+	if (!fmt)
+		fmt = find_format_by_code(MEDIA_BUS_FMT_SBGGR10_1X10);
+
+	f->fmt.pix.pixelformat = fmt->fourcc;
+
+	if (is_fe_node(node) && fmt->remap[CFE_REMAP_16BIT]) {
+		f->fmt.pix.pixelformat = fmt->remap[CFE_REMAP_16BIT];
+		fmt = find_format_by_pix(f->fmt.pix.pixelformat);
+	}
+
+	f->fmt.pix.field = V4L2_FIELD_NONE;
+
+	cfe_calc_format_size_bpl(cfe, fmt, f);
+
+	return 0;
+}
+
+static int cfe_s_fmt_vid_cap(struct file *file, void *priv,
+			     struct v4l2_format *f)
+{
+	struct cfe_node *node = video_drvdata(file);
+	struct cfe_device *cfe = node->cfe;
+	struct vb2_queue *q = &node->buffer_queue;
+	int ret;
+
+	cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name);
+
+	if (vb2_is_busy(q))
+		return -EBUSY;
+
+	ret = try_fmt_vid_cap(node, f);
+	if (ret)
+		return ret;
+
+	node->vid_fmt = *f;
+
+	cfe_dbg("%s: Set %ux%u, V4L2 pix " V4L2_FOURCC_CONV "\n", __func__,
+		node->vid_fmt.fmt.pix.width, node->vid_fmt.fmt.pix.height,
+		V4L2_FOURCC_CONV_ARGS(node->vid_fmt.fmt.pix.pixelformat));
+
+	return 0;
+}
+
+static int cfe_try_fmt_vid_cap(struct file *file, void *priv,
+			       struct v4l2_format *f)
+{
+	struct cfe_node *node = video_drvdata(file);
+	struct cfe_device *cfe = node->cfe;
+
+	cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name);
+
+	return try_fmt_vid_cap(node, f);
+}
+
+static int cfe_enum_fmt_meta(struct file *file, void *priv,
+			     struct v4l2_fmtdesc *f)
+{
+	struct cfe_node *node = video_drvdata(file);
+	struct cfe_device *cfe = node->cfe;
+
+	cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name);
+
+	if (!node_supports_meta(node) || f->index != 0)
+		return -EINVAL;
+
+	switch (node->id) {
+	case CSI2_CH0...CSI2_CH3:
+		f->pixelformat = V4L2_META_FMT_SENSOR_DATA;
+		return 0;
+	case FE_STATS:
+		f->pixelformat = V4L2_META_FMT_RPI_FE_STATS;
+		return 0;
+	case FE_CONFIG:
+		f->pixelformat = V4L2_META_FMT_RPI_FE_CFG;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int try_fmt_meta(struct cfe_node *node, struct v4l2_format *f)
+{
+	if (!node_supports_meta(node))
+		return -EINVAL;
+
+	switch (node->id) {
+	case CSI2_CH0...CSI2_CH3:
+		f->fmt.meta.dataformat = V4L2_META_FMT_SENSOR_DATA;
+		if (!f->fmt.meta.buffersize)
+			f->fmt.meta.buffersize = DEFAULT_EMBEDDED_SIZE;
+		f->fmt.meta.buffersize =
+			min_t(u32, f->fmt.meta.buffersize, MAX_BUFFER_SIZE);
+		f->fmt.meta.buffersize =
+			ALIGN(f->fmt.meta.buffersize, BPL_ALIGNMENT);
+		return 0;
+	case FE_STATS:
+		f->fmt.meta.dataformat = V4L2_META_FMT_RPI_FE_STATS;
+		f->fmt.meta.buffersize = sizeof(struct pisp_statistics);
+		return 0;
+	case FE_CONFIG:
+		f->fmt.meta.dataformat = V4L2_META_FMT_RPI_FE_CFG;
+		f->fmt.meta.buffersize = sizeof(struct pisp_fe_config);
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int cfe_g_fmt_meta(struct file *file, void *priv, struct v4l2_format *f)
+{
+	struct cfe_node *node = video_drvdata(file);
+	struct cfe_device *cfe = node->cfe;
+
+	cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name);
+
+	if (!node_supports_meta(node))
+		return -EINVAL;
+
+	*f = node->meta_fmt;
+
+	return 0;
+}
+
+static int cfe_s_fmt_meta(struct file *file, void *priv, struct v4l2_format *f)
+{
+	struct cfe_node *node = video_drvdata(file);
+	struct cfe_device *cfe = node->cfe;
+	struct vb2_queue *q = &node->buffer_queue;
+	int ret;
+
+	cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name);
+
+	if (vb2_is_busy(q))
+		return -EBUSY;
+
+	if (!node_supports_meta(node))
+		return -EINVAL;
+
+	ret = try_fmt_meta(node, f);
+	if (ret)
+		return ret;
+
+	node->meta_fmt = *f;
+
+	cfe_dbg("%s: Set " V4L2_FOURCC_CONV "\n", __func__,
+		V4L2_FOURCC_CONV_ARGS(node->meta_fmt.fmt.meta.dataformat));
+
+	return 0;
+}
+
+static int cfe_try_fmt_meta(struct file *file, void *priv,
+			    struct v4l2_format *f)
+{
+	struct cfe_node *node = video_drvdata(file);
+	struct cfe_device *cfe = node->cfe;
+
+	cfe_dbg("%s: [%s]\n", __func__, node_desc[node->id].name);
+	return try_fmt_meta(node, f);
+}
+
+static int cfe_enum_framesizes(struct file *file, void *priv,
+			       struct v4l2_frmsizeenum *fsize)
+{
+	struct cfe_node *node = video_drvdata(file);
+	struct cfe_device *cfe = node->cfe;
+	const struct cfe_fmt *fmt;
+
+	cfe_dbg("%s [%s]\n", __func__, node_desc[node->id].name);
+
+	if (fsize->index > 0)
+		return -EINVAL;
+
+	/* check for valid format */
+	fmt = find_format_by_pix(fsize->pixel_format);
+	if (!fmt) {
+		cfe_dbg("Invalid pixel code: %x\n", fsize->pixel_format);
+		return -EINVAL;
+	}
+
+	/* TODO: Do we have limits on the step_width? */
+
+	fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
+	fsize->stepwise.min_width = MIN_WIDTH;
+	fsize->stepwise.max_width = MAX_WIDTH;
+	fsize->stepwise.step_width = 2;
+	fsize->stepwise.min_height = MIN_HEIGHT;
+	fsize->stepwise.max_height = MAX_HEIGHT;
+	fsize->stepwise.step_height = 1;
+
+	return 0;
+}
+
+static int cfe_vb2_ioctl_reqbufs(struct file *file, void *priv,
+				 struct v4l2_requestbuffers *p)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct cfe_node *node = video_get_drvdata(vdev);
+	struct cfe_device *cfe = node->cfe;
+	int ret;
+
+	cfe_dbg("%s: [%s] type:%u\n", __func__, node_desc[node->id].name,
+		p->type);
+
+	if (p->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
+	    p->type != V4L2_BUF_TYPE_META_CAPTURE &&
+	    p->type != V4L2_BUF_TYPE_META_OUTPUT)
+		return -EINVAL;
+
+	ret = vb2_queue_change_type(vdev->queue, p->type);
+	if (ret)
+		return ret;
+
+	return vb2_ioctl_reqbufs(file, priv, p);
+}
+
+static int cfe_vb2_ioctl_create_bufs(struct file *file, void *priv,
+				     struct v4l2_create_buffers *p)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct cfe_node *node = video_get_drvdata(vdev);
+	struct cfe_device *cfe = node->cfe;
+	int ret;
+
+	cfe_dbg("%s: [%s] type:%u\n", __func__, node_desc[node->id].name,
+		p->format.type);
+
+	if (p->format.type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
+	    p->format.type != V4L2_BUF_TYPE_META_CAPTURE &&
+	    p->format.type != V4L2_BUF_TYPE_META_OUTPUT)
+		return -EINVAL;
+
+	ret = vb2_queue_change_type(vdev->queue, p->format.type);
+	if (ret)
+		return ret;
+
+	return vb2_ioctl_create_bufs(file, priv, p);
+}
+
+static int cfe_subscribe_event(struct v4l2_fh *fh,
+			       const struct v4l2_event_subscription *sub)
+{
+	struct cfe_node *node = video_get_drvdata(fh->vdev);
+
+	switch (sub->type) {
+	case V4L2_EVENT_FRAME_SYNC:
+		if (!node_supports_image_output(node))
+			break;
+
+		return v4l2_event_subscribe(fh, sub, 2, NULL);
+	case V4L2_EVENT_SOURCE_CHANGE:
+		if (!node_supports_image_output(node) &&
+		    !node_supports_meta_output(node))
+			break;
+
+		return v4l2_event_subscribe(fh, sub, 4, NULL);
+	}
+
+	return v4l2_ctrl_subscribe_event(fh, sub);
+}
+
+static const struct v4l2_ioctl_ops cfe_ioctl_ops = {
+	.vidioc_querycap = cfe_querycap,
+	.vidioc_enum_fmt_vid_cap = cfe_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap = cfe_g_fmt,
+	.vidioc_s_fmt_vid_cap = cfe_s_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap = cfe_try_fmt_vid_cap,
+
+	.vidioc_enum_fmt_meta_cap = cfe_enum_fmt_meta,
+	.vidioc_g_fmt_meta_cap = cfe_g_fmt_meta,
+	.vidioc_s_fmt_meta_cap = cfe_s_fmt_meta,
+	.vidioc_try_fmt_meta_cap = cfe_try_fmt_meta,
+
+	.vidioc_enum_fmt_meta_out = cfe_enum_fmt_meta,
+	.vidioc_g_fmt_meta_out = cfe_g_fmt_meta,
+	.vidioc_s_fmt_meta_out = cfe_s_fmt_meta,
+	.vidioc_try_fmt_meta_out = cfe_try_fmt_meta,
+
+	.vidioc_enum_framesizes = cfe_enum_framesizes,
+
+	.vidioc_reqbufs = cfe_vb2_ioctl_reqbufs,
+	.vidioc_create_bufs = cfe_vb2_ioctl_create_bufs,
+	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+	.vidioc_querybuf = vb2_ioctl_querybuf,
+	.vidioc_qbuf = vb2_ioctl_qbuf,
+	.vidioc_dqbuf = vb2_ioctl_dqbuf,
+	.vidioc_expbuf = vb2_ioctl_expbuf,
+	.vidioc_streamon = vb2_ioctl_streamon,
+	.vidioc_streamoff = vb2_ioctl_streamoff,
+
+	.vidioc_subscribe_event = cfe_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static void cfe_notify(struct v4l2_subdev *sd, unsigned int notification,
+		       void *arg)
+{
+	struct cfe_device *cfe = to_cfe_device(sd->v4l2_dev);
+	unsigned int i;
+
+	switch (notification) {
+	case V4L2_DEVICE_NOTIFY_EVENT:
+		for (i = 0; i < NUM_NODES; i++) {
+			struct cfe_node *node = &cfe->node[i];
+
+			if (check_state(cfe, NODE_REGISTERED, i))
+				continue;
+
+			v4l2_event_queue(&node->video_dev, arg);
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+/* cfe capture driver file operations */
+static const struct v4l2_file_operations cfe_fops = {
+	.owner = THIS_MODULE,
+	.open = v4l2_fh_open,
+	.release = vb2_fop_release,
+	.poll = vb2_fop_poll,
+	.unlocked_ioctl = video_ioctl2,
+	.mmap = vb2_fop_mmap,
+};
+
+static int cfe_video_link_validate(struct cfe_node *node,
+				   struct v4l2_subdev *remote_sd,
+				   struct v4l2_mbus_framefmt *remote_fmt)
+{
+	struct cfe_device *cfe = node->cfe;
+
+	if (!remote_fmt) {
+		return -EINVAL;
+	}
+
+	if (is_image_output_node(node)) {
+		struct v4l2_pix_format *pix_fmt = &node->vid_fmt.fmt.pix;
+		const struct cfe_fmt *fmt = NULL;
+		unsigned int i;
+
+		if (remote_fmt->width != pix_fmt->width ||
+		    remote_fmt->height != pix_fmt->height) {
+			cfe_err("Wrong width or height %ux%u (remote pad set to %ux%u)\n",
+				pix_fmt->width, pix_fmt->height,
+				remote_fmt->width,
+				remote_fmt->height);
+			return -EINVAL;
+		}
+
+		for (i = 0; i < ARRAY_SIZE(formats); i++) {
+			if (formats[i].code == remote_fmt->code &&
+			    formats[i].fourcc == pix_fmt->pixelformat) {
+				fmt = &formats[i];
+				break;
+			}
+		}
+		if (!fmt) {
+			cfe_err("Format mismatch!\n");
+			return -EINVAL;
+		}
+	} else if (is_csi2_node(node) && is_meta_output_node(node)) {
+		struct v4l2_meta_format *meta_fmt = &node->meta_fmt.fmt.meta;
+		const struct cfe_fmt *fmt;
+		u32 remote_size;
+
+		fmt = find_format_by_code(remote_fmt->code);
+		if (!fmt || fmt->fourcc != meta_fmt->dataformat) {
+			cfe_err("Metadata format mismatch!\n");
+			return -EINVAL;
+		}
+
+		remote_size = DIV_ROUND_UP(remote_fmt->width * remote_fmt->height * fmt->depth, 8);
+
+		if (remote_fmt->code != MEDIA_BUS_FMT_SENSOR_DATA) {
+			cfe_err("Bad metadata mbus format\n");
+			return -EINVAL;
+		}
+
+		if (remote_size > meta_fmt->buffersize) {
+			cfe_err("Metadata buffer too small: %u < %u\n",
+				meta_fmt->buffersize, remote_size);
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+static int cfe_video_link_validate_output(struct media_link *link)
+{
+	struct video_device *vd = container_of(link->source->entity,
+					       struct video_device, entity);
+	struct cfe_node *node = container_of(vd, struct cfe_node, video_dev);
+	struct cfe_device *cfe = node->cfe;
+	struct v4l2_mbus_framefmt *sink_fmt;
+	struct v4l2_subdev_state *state;
+	struct v4l2_subdev *sink_sd;
+	int ret;
+
+	cfe_dbg("%s: [%s] link \"%s\":%u -> \"%s\":%u\n", __func__,
+		node_desc[node->id].name,
+		link->source->entity->name, link->source->index,
+		link->sink->entity->name, link->sink->index);
+
+	if (!media_entity_remote_source_pad_unique(link->source->entity)) {
+		cfe_err("video node %s pad not connected\n", vd->name);
+		return -ENOTCONN;
+	}
+
+	sink_sd = media_entity_to_v4l2_subdev(link->sink->entity);
+
+	state = v4l2_subdev_lock_and_get_active_state(sink_sd);
+
+	sink_fmt = v4l2_subdev_state_get_format(state, link->sink->index);
+
+	ret = cfe_video_link_validate(node, sink_sd, sink_fmt);
+
+	v4l2_subdev_unlock_state(state);
+
+	return ret;
+}
+
+static int cfe_video_link_validate_capture(struct media_link *link)
+{
+	struct video_device *vd = container_of(link->sink->entity,
+					       struct video_device, entity);
+	struct cfe_node *node = container_of(vd, struct cfe_node, video_dev);
+	struct cfe_device *cfe = node->cfe;
+	struct v4l2_mbus_framefmt *source_fmt;
+	struct v4l2_subdev_state *state;
+	struct v4l2_subdev *source_sd;
+	int ret;
+
+	cfe_dbg("%s: [%s] link \"%s\":%u -> \"%s\":%u\n", __func__,
+		node_desc[node->id].name,
+		link->source->entity->name, link->source->index,
+		link->sink->entity->name, link->sink->index);
+
+	if (!media_entity_remote_source_pad_unique(link->sink->entity)) {
+		cfe_err("video node %s pad not connected\n", vd->name);
+		return -ENOTCONN;
+	}
+
+	source_sd = media_entity_to_v4l2_subdev(link->source->entity);
+
+	state = v4l2_subdev_lock_and_get_active_state(source_sd);
+
+	source_fmt = v4l2_subdev_state_get_format(state, link->source->index);
+
+	ret = cfe_video_link_validate(node, source_sd, source_fmt);
+
+	v4l2_subdev_unlock_state(state);
+
+	return ret;
+}
+
+static const struct media_entity_operations cfe_media_entity_ops_output = {
+	.link_validate = cfe_video_link_validate_output,
+};
+
+static const struct media_entity_operations cfe_media_entity_ops_capture = {
+	.link_validate = cfe_video_link_validate_capture,
+};
+
+static int cfe_video_link_notify(struct media_link *link, u32 flags,
+				 unsigned int notification)
+{
+	struct media_device *mdev = link->graph_obj.mdev;
+	struct cfe_device *cfe = container_of(mdev, struct cfe_device, mdev);
+	struct media_entity *fe = &cfe->fe.sd.entity;
+	struct media_entity *csi2 = &cfe->csi2.sd.entity;
+	unsigned long lock_flags;
+	unsigned int i;
+
+	if (notification != MEDIA_DEV_NOTIFY_POST_LINK_CH)
+		return 0;
+
+	cfe_dbg("%s: %s[%u] -> %s[%u] 0x%x", __func__,
+		link->source->entity->name, link->source->index,
+		link->sink->entity->name, link->sink->index, flags);
+
+	spin_lock_irqsave(&cfe->state_lock, lock_flags);
+
+	for (i = 0; i < NUM_NODES; i++) {
+		if (link->sink->entity != &cfe->node[i].video_dev.entity &&
+		    link->source->entity != &cfe->node[i].video_dev.entity)
+			continue;
+
+		if (link->flags & MEDIA_LNK_FL_ENABLED)
+			set_state(cfe, NODE_ENABLED, i);
+		else
+			clear_state(cfe, NODE_ENABLED, i);
+
+		break;
+	}
+
+	spin_unlock_irqrestore(&cfe->state_lock, lock_flags);
+
+	if (link->source->entity != csi2)
+		return 0;
+	if (link->sink->entity != fe)
+		return 0;
+	if (link->sink->index != 0)
+		return 0;
+
+	cfe->fe_csi2_channel = -1;
+	if (link->flags & MEDIA_LNK_FL_ENABLED) {
+		if (link->source->index == node_desc[CSI2_CH0].link_pad)
+			cfe->fe_csi2_channel = CSI2_CH0;
+		else if (link->source->index == node_desc[CSI2_CH1].link_pad)
+			cfe->fe_csi2_channel = CSI2_CH1;
+		else if (link->source->index == node_desc[CSI2_CH2].link_pad)
+			cfe->fe_csi2_channel = CSI2_CH2;
+		else if (link->source->index == node_desc[CSI2_CH3].link_pad)
+			cfe->fe_csi2_channel = CSI2_CH3;
+	}
+
+	if (is_fe_enabled(cfe))
+		cfe_dbg("%s: Found CSI2:%d -> FE:0 link\n", __func__,
+			cfe->fe_csi2_channel);
+	else
+		cfe_dbg("%s: Unable to find CSI2:x -> FE:0 link\n", __func__);
+
+	return 0;
+}
+
+static const struct media_device_ops cfe_media_device_ops = {
+	.link_notify = cfe_video_link_notify,
+};
+
+static void cfe_release(struct kref *kref)
+{
+	struct cfe_device *cfe = container_of(kref, struct cfe_device, kref);
+
+	media_device_cleanup(&cfe->mdev);
+
+	kfree(cfe);
+}
+
+static void cfe_put(struct cfe_device *cfe)
+{
+	kref_put(&cfe->kref, cfe_release);
+}
+
+static void cfe_get(struct cfe_device *cfe)
+{
+	kref_get(&cfe->kref);
+}
+
+static void cfe_node_release(struct video_device *vdev)
+{
+	struct cfe_node *node = video_get_drvdata(vdev);
+
+	cfe_put(node->cfe);
+}
+
+static int cfe_register_node(struct cfe_device *cfe, int id)
+{
+	struct video_device *vdev;
+	const struct cfe_fmt *fmt;
+	struct vb2_queue *q;
+	struct cfe_node *node = &cfe->node[id];
+	int ret;
+
+	node->cfe = cfe;
+	node->id = id;
+
+	if (node_supports_image(node)) {
+		if (node_supports_image_output(node))
+			node->vid_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+		else
+			node->vid_fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+
+		fmt = find_format_by_code(cfe_default_format.code);
+		if (!fmt) {
+			cfe_err("Failed to find format code\n");
+			return -EINVAL;
+		}
+
+		node->vid_fmt.fmt.pix.pixelformat = fmt->fourcc;
+		v4l2_fill_pix_format(&node->vid_fmt.fmt.pix, &cfe_default_format);
+
+		ret = try_fmt_vid_cap(node, &node->vid_fmt);
+		if (ret)
+			return ret;
+	}
+
+	if (node_supports_meta(node)) {
+		if (node_supports_meta_output(node))
+			node->meta_fmt.type = V4L2_BUF_TYPE_META_CAPTURE;
+		else
+			node->meta_fmt.type = V4L2_BUF_TYPE_META_OUTPUT;
+
+		ret = try_fmt_meta(node, &node->meta_fmt);
+		if (ret)
+			return ret;
+	}
+
+	mutex_init(&node->lock);
+
+	q = &node->buffer_queue;
+	q->type = node_supports_image(node) ? node->vid_fmt.type :
+					      node->meta_fmt.type;
+	q->io_modes = VB2_MMAP | VB2_DMABUF;
+	q->drv_priv = node;
+	q->ops = &cfe_video_qops;
+	q->mem_ops = &vb2_dma_contig_memops;
+	q->buf_struct_size = id == FE_CONFIG ? sizeof(struct cfe_config_buffer)
+					     : sizeof(struct cfe_buffer);
+	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	q->lock = &node->lock;
+	q->min_queued_buffers = 1;
+	q->dev = &cfe->pdev->dev;
+
+	ret = vb2_queue_init(q);
+	if (ret) {
+		cfe_err("vb2_queue_init() failed\n");
+		return ret;
+	}
+
+	INIT_LIST_HEAD(&node->dma_queue);
+
+	vdev = &node->video_dev;
+	vdev->release = cfe_node_release;
+	vdev->fops = &cfe_fops;
+	vdev->ioctl_ops = &cfe_ioctl_ops;
+	vdev->v4l2_dev = &cfe->v4l2_dev;
+	if ((node_supports_image_output(node) ||
+	     node_supports_meta_output(node))) {
+		vdev->entity.ops = &cfe_media_entity_ops_capture;
+		vdev->vfl_dir = VFL_DIR_RX;
+	} else {
+		vdev->entity.ops = &cfe_media_entity_ops_output;
+		vdev->vfl_dir = VFL_DIR_TX;
+	}
+	vdev->queue = q;
+	vdev->lock = &node->lock;
+	vdev->device_caps = node_desc[id].caps;
+	vdev->device_caps |= V4L2_CAP_STREAMING | V4L2_CAP_IO_MC;
+
+	/* Define the device names */
+	snprintf(vdev->name, sizeof(vdev->name), "%s-%s", CFE_MODULE_NAME,
+		 node_desc[id].name);
+
+	video_set_drvdata(vdev, node);
+	if (node->id == FE_OUT0)
+		vdev->entity.flags |= MEDIA_ENT_FL_DEFAULT;
+	node->pad.flags = node_desc[id].pad_flags;
+	media_entity_pads_init(&vdev->entity, 1, &node->pad);
+
+	if (!node_supports_image(node)) {
+		v4l2_disable_ioctl(&node->video_dev,
+				   VIDIOC_ENUM_FRAMEINTERVALS);
+		v4l2_disable_ioctl(&node->video_dev,
+				   VIDIOC_ENUM_FRAMESIZES);
+	}
+
+	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+	if (ret) {
+		cfe_err("Unable to register video device %s\n", vdev->name);
+		return ret;
+	}
+
+	cfe_info("Registered [%s] node id %d successfully as /dev/video%u\n",
+		 vdev->name, id, vdev->num);
+
+	/*
+	 * Acquire a reference to cfe, which will be released when the video
+	 * device will be unregistered and userspace will have closed all open
+	 * file handles.
+	 */
+	cfe_get(cfe);
+	set_state(cfe, NODE_REGISTERED, id);
+
+	return 0;
+}
+
+static void cfe_unregister_nodes(struct cfe_device *cfe)
+{
+	unsigned int i;
+
+	for (i = 0; i < NUM_NODES; i++) {
+		struct cfe_node *node = &cfe->node[i];
+
+		if (check_state(cfe, NODE_REGISTERED, i)) {
+			clear_state(cfe, NODE_REGISTERED, i);
+			video_unregister_device(&node->video_dev);
+		}
+	}
+}
+
+static int cfe_link_node_pads(struct cfe_device *cfe)
+{
+	unsigned int i, source_pad = 0;
+	int ret;
+
+	for (i = 0; i < CSI2_NUM_CHANNELS; i++) {
+		struct cfe_node *node = &cfe->node[i];
+
+		if (!check_state(cfe, NODE_REGISTERED, i))
+			continue;
+
+		/* Find next source pad */
+		while (source_pad < cfe->sensor->entity.num_pads &&
+		       !(cfe->sensor->entity.pads[source_pad].flags &
+							MEDIA_PAD_FL_SOURCE))
+			source_pad++;
+
+		if (source_pad < cfe->sensor->entity.num_pads) {
+			/* Sensor -> CSI2 */
+			ret = media_create_pad_link(&cfe->sensor->entity, source_pad,
+						    &cfe->csi2.sd.entity, i,
+						    MEDIA_LNK_FL_IMMUTABLE |
+						    MEDIA_LNK_FL_ENABLED);
+			if (ret)
+				return ret;
+
+			/* Dealt with that source_pad, look at the next one next time */
+			source_pad++;
+		}
+
+		/* CSI2 channel # -> /dev/video# */
+		ret = media_create_pad_link(&cfe->csi2.sd.entity,
+					    node_desc[i].link_pad,
+					    &node->video_dev.entity, 0, 0);
+		if (ret)
+			return ret;
+
+		if (node_supports_image(node)) {
+			/* CSI2 channel # -> FE Input */
+			ret = media_create_pad_link(&cfe->csi2.sd.entity,
+						    node_desc[i].link_pad,
+						    &cfe->fe.sd.entity,
+						    FE_STREAM_PAD, 0);
+			if (ret)
+				return ret;
+		}
+	}
+
+	for (; i < NUM_NODES; i++) {
+		struct cfe_node *node = &cfe->node[i];
+		struct media_entity *src, *dst;
+		unsigned int src_pad, dst_pad;
+
+		if (node_desc[i].pad_flags & MEDIA_PAD_FL_SINK) {
+			/* FE -> /dev/video# */
+			src = &cfe->fe.sd.entity;
+			src_pad = node_desc[i].link_pad;
+			dst = &node->video_dev.entity;
+			dst_pad = 0;
+		} else {
+			/* /dev/video# -> FE */
+			dst = &cfe->fe.sd.entity;
+			dst_pad = node_desc[i].link_pad;
+			src = &node->video_dev.entity;
+			src_pad = 0;
+		}
+
+		ret = media_create_pad_link(src, src_pad, dst, dst_pad, 0);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int cfe_probe_complete(struct cfe_device *cfe)
+{
+	unsigned int i;
+	int ret;
+
+	cfe->v4l2_dev.notify = cfe_notify;
+
+	for (i = 0; i < NUM_NODES; i++) {
+		ret = cfe_register_node(cfe, i);
+		if (ret) {
+			cfe_err("Unable to register video node %u.\n", i);
+			goto unregister;
+		}
+	}
+
+	ret = cfe_link_node_pads(cfe);
+	if (ret) {
+		cfe_err("Unable to link node pads.\n");
+		goto unregister;
+	}
+
+	ret = v4l2_device_register_subdev_nodes(&cfe->v4l2_dev);
+	if (ret) {
+		cfe_err("Unable to register subdev nodes.\n");
+		goto unregister;
+	}
+
+	return 0;
+
+unregister:
+	cfe_unregister_nodes(cfe);
+	return ret;
+}
+
+static int cfe_async_bound(struct v4l2_async_notifier *notifier,
+			   struct v4l2_subdev *subdev,
+			   struct v4l2_async_connection *asd)
+{
+	struct cfe_device *cfe = to_cfe_device(notifier->v4l2_dev);
+
+	if (cfe->sensor) {
+		cfe_info("Rejecting subdev %s (Already set!!)", subdev->name);
+		return 0;
+	}
+
+	cfe->sensor = subdev;
+	cfe_info("Using sensor %s for capture\n", subdev->name);
+
+	return 0;
+}
+
+static int cfe_async_complete(struct v4l2_async_notifier *notifier)
+{
+	struct cfe_device *cfe = to_cfe_device(notifier->v4l2_dev);
+
+	return cfe_probe_complete(cfe);
+}
+
+static const struct v4l2_async_notifier_operations cfe_async_ops = {
+	.bound = cfe_async_bound,
+	.complete = cfe_async_complete,
+};
+
+static int of_cfe_connect_subdevs(struct cfe_device *cfe)
+{
+	struct platform_device *pdev = cfe->pdev;
+	struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
+	struct device_node *node = pdev->dev.of_node;
+	struct device_node *ep_node;
+	struct device_node *sensor_node;
+	unsigned int lane;
+	int ret = -EINVAL;
+
+	/* Get the local endpoint and remote device. */
+	ep_node = of_graph_get_next_endpoint(node, NULL);
+	if (!ep_node) {
+		cfe_err("can't get next endpoint\n");
+		return -EINVAL;
+	}
+
+	cfe_dbg("ep_node is %pOF\n", ep_node);
+
+	sensor_node = of_graph_get_remote_port_parent(ep_node);
+	if (!sensor_node) {
+		cfe_err("can't get remote parent\n");
+		goto cleanup_exit;
+	}
+
+	cfe_info("found subdevice %pOF\n", sensor_node);
+
+	/* Parse the local endpoint and validate its configuration. */
+	v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep_node), &ep);
+
+	cfe->csi2.multipacket_line =
+		fwnode_property_present(of_fwnode_handle(ep_node),
+					"multipacket-line");
+
+	if (ep.bus_type != V4L2_MBUS_CSI2_DPHY) {
+		cfe_err("endpoint node type != CSI2\n");
+		return -EINVAL;
+	}
+
+	for (lane = 0; lane < ep.bus.mipi_csi2.num_data_lanes; lane++) {
+		if (ep.bus.mipi_csi2.data_lanes[lane] != lane + 1) {
+			cfe_err("subdevice %pOF: data lanes reordering not supported\n",
+				sensor_node);
+			goto cleanup_exit;
+		}
+	}
+
+	cfe->csi2.dphy.max_lanes = ep.bus.mipi_csi2.num_data_lanes;
+	cfe->csi2.bus_flags = ep.bus.mipi_csi2.flags;
+
+	cfe_dbg("subdevice %pOF: %u data lanes, flags=0x%08x, multipacket_line=%u\n",
+		sensor_node, cfe->csi2.dphy.max_lanes, cfe->csi2.bus_flags,
+		cfe->csi2.multipacket_line);
+
+	/* Initialize and register the async notifier. */
+	v4l2_async_nf_init(&cfe->notifier, &cfe->v4l2_dev);
+	cfe->notifier.ops = &cfe_async_ops;
+
+	cfe->asd = v4l2_async_nf_add_fwnode(&cfe->notifier,
+					    of_fwnode_handle(sensor_node),
+					    struct v4l2_async_connection);
+	if (IS_ERR(cfe->asd)) {
+		cfe_err("Error adding subdevice: %d\n", ret);
+		goto cleanup_exit;
+	}
+
+	ret = v4l2_async_nf_register(&cfe->notifier);
+	if (ret) {
+		cfe_err("Error registering async notifier: %d\n", ret);
+		ret = -EINVAL;
+	}
+
+cleanup_exit:
+	of_node_put(sensor_node);
+	of_node_put(ep_node);
+
+	return ret;
+}
+
+static int cfe_probe(struct platform_device *pdev)
+{
+	struct cfe_device *cfe;
+	char debugfs_name[32];
+	int ret;
+
+	cfe = kzalloc(sizeof(*cfe), GFP_KERNEL);
+	if (!cfe)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, cfe);
+
+	kref_init(&cfe->kref);
+	cfe->pdev = pdev;
+	cfe->fe_csi2_channel = -1;
+	spin_lock_init(&cfe->state_lock);
+
+	cfe->csi2.base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(cfe->csi2.base)) {
+		dev_err(&pdev->dev, "Failed to get dma io block\n");
+		ret = PTR_ERR(cfe->csi2.base);
+		goto err_cfe_put;
+	}
+
+	cfe->csi2.dphy.base = devm_platform_ioremap_resource(pdev, 1);
+	if (IS_ERR(cfe->csi2.dphy.base)) {
+		dev_err(&pdev->dev, "Failed to get host io block\n");
+		ret = PTR_ERR(cfe->csi2.dphy.base);
+		goto err_cfe_put;
+	}
+
+	cfe->mipi_cfg_base = devm_platform_ioremap_resource(pdev, 2);
+	if (IS_ERR(cfe->mipi_cfg_base)) {
+		dev_err(&pdev->dev, "Failed to get mipi cfg io block\n");
+		ret = PTR_ERR(cfe->mipi_cfg_base);
+		goto err_cfe_put;
+	}
+
+	cfe->fe.base = devm_platform_ioremap_resource(pdev, 3);
+	if (IS_ERR(cfe->fe.base)) {
+		dev_err(&pdev->dev, "Failed to get pisp fe io block\n");
+		ret = PTR_ERR(cfe->fe.base);
+		goto err_cfe_put;
+	}
+
+	ret = platform_get_irq(pdev, 0);
+	if (ret <= 0) {
+		dev_err(&pdev->dev, "No IRQ resource\n");
+		ret = -EINVAL;
+		goto err_cfe_put;
+	}
+
+	ret = devm_request_irq(&pdev->dev, ret, cfe_isr, 0, "rp1-cfe", cfe);
+	if (ret) {
+		dev_err(&pdev->dev, "Unable to request interrupt\n");
+		ret = -EINVAL;
+		goto err_cfe_put;
+	}
+
+	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+	if (ret) {
+		dev_err(&pdev->dev, "DMA enable failed\n");
+		goto err_cfe_put;
+	}
+
+	/* TODO: Enable clock only when running. */
+	cfe->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(cfe->clk))
+		return dev_err_probe(&pdev->dev, PTR_ERR(cfe->clk),
+				     "clock not found\n");
+
+	cfe->mdev.dev = &pdev->dev;
+	cfe->mdev.ops = &cfe_media_device_ops;
+	strscpy(cfe->mdev.model, CFE_MODULE_NAME, sizeof(cfe->mdev.model));
+	strscpy(cfe->mdev.serial, "", sizeof(cfe->mdev.serial));
+	snprintf(cfe->mdev.bus_info, sizeof(cfe->mdev.bus_info), "platform:%s",
+		 dev_name(&pdev->dev));
+
+	media_device_init(&cfe->mdev);
+
+	cfe->v4l2_dev.mdev = &cfe->mdev;
+
+	ret = v4l2_device_register(&pdev->dev, &cfe->v4l2_dev);
+	if (ret) {
+		cfe_err("Unable to register v4l2 device.\n");
+		goto err_cfe_put;
+	}
+
+	snprintf(debugfs_name, sizeof(debugfs_name), "rp1-cfe:%s",
+		 dev_name(&pdev->dev));
+	cfe->debugfs = debugfs_create_dir(debugfs_name, NULL);
+	debugfs_create_file("format", 0444, cfe->debugfs, cfe, &format_fops);
+	debugfs_create_file("regs", 0444, cfe->debugfs, cfe,
+			    &mipi_cfg_regs_fops);
+
+	/* Enable the block power domain */
+	pm_runtime_enable(&pdev->dev);
+
+	ret = pm_runtime_resume_and_get(&cfe->pdev->dev);
+	if (ret)
+		goto err_runtime_disable;
+
+	cfe->csi2.v4l2_dev = &cfe->v4l2_dev;
+	ret = csi2_init(&cfe->csi2, cfe->debugfs);
+	if (ret) {
+		cfe_err("Failed to init csi2 (%d)\n", ret);
+		goto err_runtime_put;
+	}
+
+	cfe->fe.v4l2_dev = &cfe->v4l2_dev;
+	ret = pisp_fe_init(&cfe->fe, cfe->debugfs);
+	if (ret) {
+		cfe_err("Failed to init pisp fe (%d)\n", ret);
+		goto err_csi2_uninit;
+	}
+
+	cfe->mdev.hw_revision = cfe->fe.hw_revision;
+	ret = media_device_register(&cfe->mdev);
+	if (ret < 0) {
+		cfe_err("Unable to register media-controller device.\n");
+		goto err_pisp_fe_uninit;
+	}
+
+	ret = of_cfe_connect_subdevs(cfe);
+	if (ret) {
+		cfe_err("Failed to connect subdevs\n");
+		goto err_media_unregister;
+	}
+
+	pm_runtime_put(&cfe->pdev->dev);
+
+	return 0;
+
+err_media_unregister:
+	media_device_unregister(&cfe->mdev);
+err_pisp_fe_uninit:
+	pisp_fe_uninit(&cfe->fe);
+err_csi2_uninit:
+	csi2_uninit(&cfe->csi2);
+err_runtime_put:
+	pm_runtime_put(&cfe->pdev->dev);
+err_runtime_disable:
+	pm_runtime_disable(&pdev->dev);
+	debugfs_remove(cfe->debugfs);
+	v4l2_device_unregister(&cfe->v4l2_dev);
+err_cfe_put:
+	cfe_put(cfe);
+
+	return ret;
+}
+
+static void cfe_remove(struct platform_device *pdev)
+{
+	struct cfe_device *cfe = platform_get_drvdata(pdev);
+
+	debugfs_remove(cfe->debugfs);
+
+	v4l2_async_nf_unregister(&cfe->notifier);
+	media_device_unregister(&cfe->mdev);
+	cfe_unregister_nodes(cfe);
+
+	pisp_fe_uninit(&cfe->fe);
+	csi2_uninit(&cfe->csi2);
+
+	pm_runtime_disable(&pdev->dev);
+
+	v4l2_device_unregister(&cfe->v4l2_dev);
+
+	cfe_put(cfe);
+}
+
+static int cfe_runtime_suspend(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct cfe_device *cfe = platform_get_drvdata(pdev);
+
+	clk_disable_unprepare(cfe->clk);
+
+	return 0;
+}
+
+static int cfe_runtime_resume(struct device *dev)
+{
+	struct platform_device *pdev = to_platform_device(dev);
+	struct cfe_device *cfe = platform_get_drvdata(pdev);
+	int ret;
+
+	ret = clk_prepare_enable(cfe->clk);
+	if (ret) {
+		dev_err(dev, "Unable to enable clock\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct dev_pm_ops cfe_pm_ops = {
+	SET_RUNTIME_PM_OPS(cfe_runtime_suspend, cfe_runtime_resume, NULL)
+	SET_LATE_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume)
+};
+
+static const struct of_device_id cfe_of_match[] = {
+	{ .compatible = "raspberrypi,rp1-cfe" },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, cfe_of_match);
+
+static struct platform_driver cfe_driver = {
+	.probe		= cfe_probe,
+	.remove		= cfe_remove,
+	.driver = {
+		.name	= CFE_MODULE_NAME,
+		.of_match_table = cfe_of_match,
+		.pm = &cfe_pm_ops,
+	},
+};
+
+module_platform_driver(cfe_driver);
+
+MODULE_AUTHOR("Naushir Patuck <naush@raspberrypi.com>");
+MODULE_DESCRIPTION("RP1 Camera Front End driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(CFE_VERSION);
diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/cfe.h b/drivers/media/platform/raspberrypi/rp1_cfe/cfe.h
new file mode 100644
index 000000000000..637b63a838c4
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/rp1_cfe/cfe.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * RP1 CFE driver.
+ * Copyright (c) 2021 Raspberry Pi Ltd.
+ *
+ */
+#ifndef _RP1_CFE_
+#define _RP1_CFE_
+
+#include <linux/types.h>
+#include <linux/media-bus-format.h>
+#include <linux/videodev2.h>
+
+extern bool cfe_debug_verbose;
+
+enum cfe_remap_types {
+	CFE_REMAP_16BIT,
+	CFE_REMAP_COMPRESSED,
+	CFE_NUM_REMAP,
+};
+
+#define CFE_FORMAT_FLAG_META_OUT	BIT(0)
+#define CFE_FORMAT_FLAG_META_CAP	BIT(1)
+#define CFE_FORMAT_FLAG_FE_OUT		BIT(2)
+
+struct cfe_fmt {
+	u32 fourcc;
+	u32 code;
+	u8 depth;
+	u8 csi_dt;
+	u32 remap[CFE_NUM_REMAP];
+	u32 flags;
+};
+
+extern const struct v4l2_mbus_framefmt cfe_default_format;
+extern const struct v4l2_mbus_framefmt cfe_default_meta_format;
+
+const struct cfe_fmt *find_format_by_code(u32 code);
+const struct cfe_fmt *find_format_by_pix(u32 pixelformat);
+u32 cfe_find_16bit_code(u32 code);
+u32 cfe_find_compressed_code(u32 code);
+
+#endif
diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/cfe_fmts.h b/drivers/media/platform/raspberrypi/rp1_cfe/cfe_fmts.h
new file mode 100644
index 000000000000..e192f211abde
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/rp1_cfe/cfe_fmts.h
@@ -0,0 +1,318 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * RP1 Camera Front End formats definition
+ *
+ * Copyright (C) 2021 - Raspberry Pi Ltd.
+ *
+ */
+#ifndef _CFE_FMTS_H_
+#define _CFE_FMTS_H_
+
+#include "cfe.h"
+#include <media/mipi-csi2.h>
+
+static const struct cfe_fmt formats[] = {
+	/* YUV Formats */
+	{
+		.fourcc = V4L2_PIX_FMT_YUYV,
+		.code = MEDIA_BUS_FMT_YUYV8_1X16,
+		.depth = 16,
+		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_UYVY,
+		.code = MEDIA_BUS_FMT_UYVY8_1X16,
+		.depth = 16,
+		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_YVYU,
+		.code = MEDIA_BUS_FMT_YVYU8_1X16,
+		.depth = 16,
+		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_VYUY,
+		.code = MEDIA_BUS_FMT_VYUY8_1X16,
+		.depth = 16,
+		.csi_dt = MIPI_CSI2_DT_YUV422_8B,
+	},
+	{
+		/* RGB Formats */
+		.fourcc = V4L2_PIX_FMT_RGB565, /* gggbbbbb rrrrrggg */
+		.code = MEDIA_BUS_FMT_RGB565_2X8_LE,
+		.depth = 16,
+		.csi_dt = MIPI_CSI2_DT_RGB565,
+	},
+	{	.fourcc = V4L2_PIX_FMT_RGB565X, /* rrrrrggg gggbbbbb */
+		.code = MEDIA_BUS_FMT_RGB565_2X8_BE,
+		.depth = 16,
+		.csi_dt = MIPI_CSI2_DT_RGB565,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_RGB555, /* gggbbbbb arrrrrgg */
+		.code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE,
+		.depth = 16,
+		.csi_dt = MIPI_CSI2_DT_RGB555,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_RGB555X, /* arrrrrgg gggbbbbb */
+		.code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE,
+		.depth = 16,
+		.csi_dt = MIPI_CSI2_DT_RGB555,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_RGB24, /* rgb */
+		.code = MEDIA_BUS_FMT_RGB888_1X24,
+		.depth = 24,
+		.csi_dt = MIPI_CSI2_DT_RGB888,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_BGR24, /* bgr */
+		.code = MEDIA_BUS_FMT_BGR888_1X24,
+		.depth = 24,
+		.csi_dt = MIPI_CSI2_DT_RGB888,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_RGB32, /* argb */
+		.code = MEDIA_BUS_FMT_ARGB8888_1X32,
+		.depth = 32,
+		.csi_dt = 0x0,
+	},
+
+	/* Bayer Formats */
+	{
+		.fourcc = V4L2_PIX_FMT_SBGGR8,
+		.code = MEDIA_BUS_FMT_SBGGR8_1X8,
+		.depth = 8,
+		.csi_dt = MIPI_CSI2_DT_RAW8,
+		.remap = { V4L2_PIX_FMT_SBGGR16, V4L2_PIX_FMT_PISP_COMP1_BGGR },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGBRG8,
+		.code = MEDIA_BUS_FMT_SGBRG8_1X8,
+		.depth = 8,
+		.csi_dt = MIPI_CSI2_DT_RAW8,
+		.remap = { V4L2_PIX_FMT_SGBRG16, V4L2_PIX_FMT_PISP_COMP1_GBRG },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGRBG8,
+		.code = MEDIA_BUS_FMT_SGRBG8_1X8,
+		.depth = 8,
+		.csi_dt = MIPI_CSI2_DT_RAW8,
+		.remap = { V4L2_PIX_FMT_SGRBG16, V4L2_PIX_FMT_PISP_COMP1_GRBG },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SRGGB8,
+		.code = MEDIA_BUS_FMT_SRGGB8_1X8,
+		.depth = 8,
+		.csi_dt = MIPI_CSI2_DT_RAW8,
+		.remap = { V4L2_PIX_FMT_SRGGB16, V4L2_PIX_FMT_PISP_COMP1_RGGB },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SBGGR10P,
+		.code = MEDIA_BUS_FMT_SBGGR10_1X10,
+		.depth = 10,
+		.csi_dt = MIPI_CSI2_DT_RAW10,
+		.remap = { V4L2_PIX_FMT_SBGGR16, V4L2_PIX_FMT_PISP_COMP1_BGGR },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGBRG10P,
+		.code = MEDIA_BUS_FMT_SGBRG10_1X10,
+		.depth = 10,
+		.csi_dt = MIPI_CSI2_DT_RAW10,
+		.remap = { V4L2_PIX_FMT_SGBRG16, V4L2_PIX_FMT_PISP_COMP1_GBRG },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGRBG10P,
+		.code = MEDIA_BUS_FMT_SGRBG10_1X10,
+		.depth = 10,
+		.csi_dt = MIPI_CSI2_DT_RAW10,
+		.remap = { V4L2_PIX_FMT_SGRBG16, V4L2_PIX_FMT_PISP_COMP1_GRBG },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SRGGB10P,
+		.code = MEDIA_BUS_FMT_SRGGB10_1X10,
+		.depth = 10,
+		.csi_dt = MIPI_CSI2_DT_RAW10,
+		.remap = { V4L2_PIX_FMT_SRGGB16, V4L2_PIX_FMT_PISP_COMP1_RGGB },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SBGGR12P,
+		.code = MEDIA_BUS_FMT_SBGGR12_1X12,
+		.depth = 12,
+		.csi_dt = MIPI_CSI2_DT_RAW12,
+		.remap = { V4L2_PIX_FMT_SBGGR16, V4L2_PIX_FMT_PISP_COMP1_BGGR },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGBRG12P,
+		.code = MEDIA_BUS_FMT_SGBRG12_1X12,
+		.depth = 12,
+		.csi_dt = MIPI_CSI2_DT_RAW12,
+		.remap = { V4L2_PIX_FMT_SGBRG16, V4L2_PIX_FMT_PISP_COMP1_GBRG },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGRBG12P,
+		.code = MEDIA_BUS_FMT_SGRBG12_1X12,
+		.depth = 12,
+		.csi_dt = MIPI_CSI2_DT_RAW12,
+		.remap = { V4L2_PIX_FMT_SGRBG16, V4L2_PIX_FMT_PISP_COMP1_GRBG },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SRGGB12P,
+		.code = MEDIA_BUS_FMT_SRGGB12_1X12,
+		.depth = 12,
+		.csi_dt = MIPI_CSI2_DT_RAW12,
+		.remap = { V4L2_PIX_FMT_SRGGB16, V4L2_PIX_FMT_PISP_COMP1_RGGB },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SBGGR14P,
+		.code = MEDIA_BUS_FMT_SBGGR14_1X14,
+		.depth = 14,
+		.csi_dt = MIPI_CSI2_DT_RAW14,
+		.remap = { V4L2_PIX_FMT_SBGGR16, V4L2_PIX_FMT_PISP_COMP1_BGGR },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGBRG14P,
+		.code = MEDIA_BUS_FMT_SGBRG14_1X14,
+		.depth = 14,
+		.csi_dt = MIPI_CSI2_DT_RAW14,
+		.remap = { V4L2_PIX_FMT_SGBRG16, V4L2_PIX_FMT_PISP_COMP1_GBRG },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGRBG14P,
+		.code = MEDIA_BUS_FMT_SGRBG14_1X14,
+		.depth = 14,
+		.csi_dt = MIPI_CSI2_DT_RAW14,
+		.remap = { V4L2_PIX_FMT_SGRBG16, V4L2_PIX_FMT_PISP_COMP1_GRBG },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SRGGB14P,
+		.code = MEDIA_BUS_FMT_SRGGB14_1X14,
+		.depth = 14,
+		.csi_dt = MIPI_CSI2_DT_RAW14,
+		.remap = { V4L2_PIX_FMT_SRGGB16, V4L2_PIX_FMT_PISP_COMP1_RGGB },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SBGGR16,
+		.code = MEDIA_BUS_FMT_SBGGR16_1X16,
+		.depth = 16,
+		.csi_dt = 0, /* Avoid RP1 HW mismatch for 16-bit modes. */
+		.flags = CFE_FORMAT_FLAG_FE_OUT,
+		.remap = { V4L2_PIX_FMT_SBGGR16, V4L2_PIX_FMT_PISP_COMP1_BGGR },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGBRG16,
+		.code = MEDIA_BUS_FMT_SGBRG16_1X16,
+		.depth = 16,
+		.csi_dt = 0, /* Avoid RP1 HW mismatch for 16-bit modes. */
+		.flags = CFE_FORMAT_FLAG_FE_OUT,
+		.remap = { V4L2_PIX_FMT_SGBRG16, V4L2_PIX_FMT_PISP_COMP1_GBRG },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SGRBG16,
+		.code = MEDIA_BUS_FMT_SGRBG16_1X16,
+		.depth = 16,
+		.csi_dt = 0, /* Avoid RP1 HW mismatch for 16-bit modes. */
+		.flags = CFE_FORMAT_FLAG_FE_OUT,
+		.remap = { V4L2_PIX_FMT_SGRBG16, V4L2_PIX_FMT_PISP_COMP1_GRBG },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_SRGGB16,
+		.code = MEDIA_BUS_FMT_SRGGB16_1X16,
+		.depth = 16,
+		.csi_dt = 0, /* Avoid RP1 HW mismatch for 16-bit modes. */
+		.flags = CFE_FORMAT_FLAG_FE_OUT,
+		.remap = { V4L2_PIX_FMT_SRGGB16, V4L2_PIX_FMT_PISP_COMP1_RGGB },
+	},
+	/* PiSP Compressed Mode 1 */
+	{
+		.fourcc = V4L2_PIX_FMT_PISP_COMP1_RGGB,
+		.code = MEDIA_BUS_FMT_SRGGB16_1X16,
+		.depth = 8,
+		.flags = CFE_FORMAT_FLAG_FE_OUT,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_PISP_COMP1_BGGR,
+		.code = MEDIA_BUS_FMT_SBGGR16_1X16,
+		.depth = 8,
+		.flags = CFE_FORMAT_FLAG_FE_OUT,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_PISP_COMP1_GBRG,
+		.code = MEDIA_BUS_FMT_SGBRG16_1X16,
+		.depth = 8,
+		.flags = CFE_FORMAT_FLAG_FE_OUT,
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_PISP_COMP1_GRBG,
+		.code = MEDIA_BUS_FMT_SGRBG16_1X16,
+		.depth = 8,
+		.flags = CFE_FORMAT_FLAG_FE_OUT,
+	},
+	/* Greyscale format */
+	{
+		.fourcc = V4L2_PIX_FMT_GREY,
+		.code = MEDIA_BUS_FMT_Y8_1X8,
+		.depth = 8,
+		.csi_dt = MIPI_CSI2_DT_RAW8,
+		.remap = { V4L2_PIX_FMT_Y16, V4L2_PIX_FMT_PISP_COMP1_MONO },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_Y10P,
+		.code = MEDIA_BUS_FMT_Y10_1X10,
+		.depth = 10,
+		.csi_dt = MIPI_CSI2_DT_RAW10,
+		.remap = { V4L2_PIX_FMT_Y16, V4L2_PIX_FMT_PISP_COMP1_MONO },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_Y12P,
+		.code = MEDIA_BUS_FMT_Y12_1X12,
+		.depth = 12,
+		.csi_dt = MIPI_CSI2_DT_RAW12,
+		.remap = { V4L2_PIX_FMT_Y16, V4L2_PIX_FMT_PISP_COMP1_MONO },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_Y14P,
+		.code = MEDIA_BUS_FMT_Y14_1X14,
+		.depth = 14,
+		.csi_dt = MIPI_CSI2_DT_RAW14,
+		.remap = { V4L2_PIX_FMT_Y16, V4L2_PIX_FMT_PISP_COMP1_MONO },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_Y16,
+		.code = MEDIA_BUS_FMT_Y16_1X16,
+		.depth = 16,
+		.csi_dt = MIPI_CSI2_DT_RAW16,
+		.flags = CFE_FORMAT_FLAG_FE_OUT,
+		.remap = { V4L2_PIX_FMT_Y16, V4L2_PIX_FMT_PISP_COMP1_MONO },
+	},
+	{
+		.fourcc = V4L2_PIX_FMT_PISP_COMP1_MONO,
+		.code = MEDIA_BUS_FMT_Y16_1X16,
+		.depth = 8,
+		.flags = CFE_FORMAT_FLAG_FE_OUT,
+	},
+	/* Embedded data format */
+	{
+		.fourcc = V4L2_META_FMT_SENSOR_DATA,
+		.code = MEDIA_BUS_FMT_SENSOR_DATA,
+		.depth = 8,
+		.csi_dt = MIPI_CSI2_DT_EMBEDDED_8B,
+		.flags = CFE_FORMAT_FLAG_META_CAP,
+	},
+
+	/* Frontend formats */
+	{
+		.fourcc = V4L2_META_FMT_RPI_FE_CFG,
+		.code = MEDIA_BUS_FMT_FIXED,
+		.flags = CFE_FORMAT_FLAG_META_OUT,
+	},
+	{
+		.fourcc = V4L2_META_FMT_RPI_FE_STATS,
+		.code = MEDIA_BUS_FMT_FIXED,
+		.flags = CFE_FORMAT_FLAG_META_CAP,
+	},
+};
+
+#endif /* _CFE_FMTS_H_ */
diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/csi2.c b/drivers/media/platform/raspberrypi/rp1_cfe/csi2.c
new file mode 100644
index 000000000000..25dc4354c67d
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/rp1_cfe/csi2.c
@@ -0,0 +1,628 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * RP1 CSI-2 Driver
+ *
+ * Copyright (C) 2021 - Raspberry Pi Ltd.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/moduleparam.h>
+#include <linux/pm_runtime.h>
+#include <linux/seq_file.h>
+
+#include <media/videobuf2-dma-contig.h>
+
+#include "csi2.h"
+#include "cfe.h"
+
+static bool csi2_track_errors;
+module_param_named(track_csi2_errors, csi2_track_errors, bool, 0);
+MODULE_PARM_DESC(track_csi2_errors, "track csi-2 errors");
+
+#define csi2_dbg_verbose(fmt, arg...)                             \
+	do {                                                      \
+		if (cfe_debug_verbose)                            \
+			dev_dbg(csi2->v4l2_dev->dev, fmt, ##arg); \
+	} while (0)
+#define csi2_dbg(fmt, arg...) dev_dbg(csi2->v4l2_dev->dev, fmt, ##arg)
+#define csi2_info(fmt, arg...) dev_info(csi2->v4l2_dev->dev, fmt, ##arg)
+#define csi2_err(fmt, arg...) dev_err(csi2->v4l2_dev->dev, fmt, ##arg)
+
+/* CSI2-DMA registers */
+#define CSI2_STATUS		0x000
+#define CSI2_QOS		0x004
+#define CSI2_DISCARDS_OVERFLOW	0x008
+#define CSI2_DISCARDS_INACTIVE	0x00c
+#define CSI2_DISCARDS_UNMATCHED	0x010
+#define CSI2_DISCARDS_LEN_LIMIT	0x014
+
+#define CSI2_DISCARDS_AMOUNT_SHIFT	0
+#define CSI2_DISCARDS_AMOUNT_MASK	GENMASK(23, 0)
+#define CSI2_DISCARDS_DT_SHIFT		24
+#define CSI2_DISCARDS_DT_MASK		GENMASK(29, 24)
+#define CSI2_DISCARDS_VC_SHIFT		30
+#define CSI2_DISCARDS_VC_MASK		GENMASK(31, 30)
+
+#define CSI2_LLEV_PANICS	0x018
+#define CSI2_ULEV_PANICS	0x01c
+#define CSI2_IRQ_MASK		0x020
+#define CSI2_IRQ_MASK_IRQ_OVERFLOW		BIT(0)
+#define CSI2_IRQ_MASK_IRQ_DISCARD_OVERFLOW	BIT(1)
+#define CSI2_IRQ_MASK_IRQ_DISCARD_LENGTH_LIMIT	BIT(2)
+#define CSI2_IRQ_MASK_IRQ_DISCARD_UNMATCHED	BIT(3)
+#define CSI2_IRQ_MASK_IRQ_DISCARD_INACTIVE	BIT(4)
+#define CSI2_IRQ_MASK_IRQ_ALL                                              \
+	(CSI2_IRQ_MASK_IRQ_OVERFLOW | CSI2_IRQ_MASK_IRQ_DISCARD_OVERFLOW | \
+	 CSI2_IRQ_MASK_IRQ_DISCARD_LENGTH_LIMIT |                          \
+	 CSI2_IRQ_MASK_IRQ_DISCARD_UNMATCHED |                             \
+	 CSI2_IRQ_MASK_IRQ_DISCARD_INACTIVE)
+
+#define CSI2_CTRL		0x024
+#define CSI2_CH_CTRL(x)		((x) * 0x40 + 0x28)
+#define CSI2_CH_ADDR0(x)	((x) * 0x40 + 0x2c)
+#define CSI2_CH_ADDR1(x)	((x) * 0x40 + 0x3c)
+#define CSI2_CH_STRIDE(x)	((x) * 0x40 + 0x30)
+#define CSI2_CH_LENGTH(x)	((x) * 0x40 + 0x34)
+#define CSI2_CH_DEBUG(x)	((x) * 0x40 + 0x38)
+#define CSI2_CH_FRAME_SIZE(x)	((x) * 0x40 + 0x40)
+#define CSI2_CH_COMP_CTRL(x)	((x) * 0x40 + 0x44)
+#define CSI2_CH_FE_FRAME_ID(x)	((x) * 0x40 + 0x48)
+
+/* CSI2_STATUS */
+#define IRQ_FS(x)		(BIT(0) << (x))
+#define IRQ_FE(x)		(BIT(4) << (x))
+#define IRQ_FE_ACK(x)		(BIT(8) << (x))
+#define IRQ_LE(x)		(BIT(12) << (x))
+#define IRQ_LE_ACK(x)		(BIT(16) << (x))
+#define IRQ_CH_MASK(x)		(IRQ_FS(x) | IRQ_FE(x) | IRQ_FE_ACK(x) | IRQ_LE(x) | IRQ_LE_ACK(x))
+#define IRQ_OVERFLOW		BIT(20)
+#define IRQ_DISCARD_OVERFLOW	BIT(21)
+#define IRQ_DISCARD_LEN_LIMIT	BIT(22)
+#define IRQ_DISCARD_UNMATCHED	BIT(23)
+#define IRQ_DISCARD_INACTIVE	BIT(24)
+
+/* CSI2_CTRL */
+#define EOP_IS_EOL		BIT(0)
+
+/* CSI2_CH_CTRL */
+#define DMA_EN			BIT(0)
+#define FORCE			BIT(3)
+#define AUTO_ARM		BIT(4)
+#define IRQ_EN_FS		BIT(13)
+#define IRQ_EN_FE		BIT(14)
+#define IRQ_EN_FE_ACK		BIT(15)
+#define IRQ_EN_LE		BIT(16)
+#define IRQ_EN_LE_ACK		BIT(17)
+#define FLUSH_FE		BIT(28)
+#define PACK_LINE		BIT(29)
+#define PACK_BYTES		BIT(30)
+#define CH_MODE_MASK		GENMASK(2, 1)
+#define VC_MASK			GENMASK(6, 5)
+#define DT_MASK			GENMASK(12, 7)
+#define LC_MASK			GENMASK(27, 18)
+
+/* CHx_COMPRESSION_CONTROL */
+#define COMP_OFFSET_MASK	GENMASK(15, 0)
+#define COMP_SHIFT_MASK		GENMASK(19, 16)
+#define COMP_MODE_MASK		GENMASK(25, 24)
+
+static inline u32 csi2_reg_read(struct csi2_device *csi2, u32 offset)
+{
+	return readl(csi2->base + offset);
+}
+
+static inline void csi2_reg_write(struct csi2_device *csi2, u32 offset, u32 val)
+{
+	writel(val, csi2->base + offset);
+	csi2_dbg_verbose("csi2: write 0x%04x -> 0x%03x\n", val, offset);
+}
+
+static inline void set_field(u32 *valp, u32 field, u32 mask)
+{
+	u32 val = *valp;
+
+	val &= ~mask;
+	val |= (field << __ffs(mask)) & mask;
+	*valp = val;
+}
+
+static int csi2_regs_show(struct seq_file *s, void *data)
+{
+	struct csi2_device *csi2 = s->private;
+	unsigned int i;
+	int ret;
+
+	ret = pm_runtime_resume_and_get(csi2->v4l2_dev->dev);
+	if (ret)
+		return ret;
+
+#define DUMP(reg) seq_printf(s, #reg " \t0x%08x\n", csi2_reg_read(csi2, reg))
+#define DUMP_CH(idx, reg) seq_printf(s, #reg "(%u) \t0x%08x\n", idx, csi2_reg_read(csi2, reg(idx)))
+
+	DUMP(CSI2_STATUS);
+	DUMP(CSI2_DISCARDS_OVERFLOW);
+	DUMP(CSI2_DISCARDS_INACTIVE);
+	DUMP(CSI2_DISCARDS_UNMATCHED);
+	DUMP(CSI2_DISCARDS_LEN_LIMIT);
+	DUMP(CSI2_LLEV_PANICS);
+	DUMP(CSI2_ULEV_PANICS);
+	DUMP(CSI2_IRQ_MASK);
+	DUMP(CSI2_CTRL);
+
+	for (i = 0; i < CSI2_NUM_CHANNELS; ++i) {
+		DUMP_CH(i, CSI2_CH_CTRL);
+		DUMP_CH(i, CSI2_CH_ADDR0);
+		DUMP_CH(i, CSI2_CH_ADDR1);
+		DUMP_CH(i, CSI2_CH_STRIDE);
+		DUMP_CH(i, CSI2_CH_LENGTH);
+		DUMP_CH(i, CSI2_CH_DEBUG);
+		DUMP_CH(i, CSI2_CH_FRAME_SIZE);
+		DUMP_CH(i, CSI2_CH_COMP_CTRL);
+		DUMP_CH(i, CSI2_CH_FE_FRAME_ID);
+	}
+
+#undef DUMP
+#undef DUMP_CH
+
+	pm_runtime_put(csi2->v4l2_dev->dev);
+
+	return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(csi2_regs);
+
+static int csi2_errors_show(struct seq_file *s, void *data)
+{
+	struct csi2_device *csi2 = s->private;
+	unsigned long flags;
+	u32 discards_table[DISCARDS_TABLE_NUM_VCS][DISCARDS_TABLE_NUM_ENTRIES];
+	u32 discards_dt_table[DISCARDS_TABLE_NUM_ENTRIES];
+	u32 overflows;
+
+	spin_lock_irqsave(&csi2->errors_lock, flags);
+
+	memcpy(discards_table, csi2->discards_table, sizeof(discards_table));
+	memcpy(discards_dt_table, csi2->discards_dt_table,
+	       sizeof(discards_dt_table));
+	overflows = csi2->overflows;
+
+	csi2->overflows = 0;
+	memset(csi2->discards_table, 0, sizeof(discards_table));
+	memset(csi2->discards_dt_table, 0, sizeof(discards_dt_table));
+
+	spin_unlock_irqrestore(&csi2->errors_lock, flags);
+
+	seq_printf(s, "Overflows %u\n", overflows);
+	seq_puts(s, "Discards:\n");
+	seq_puts(s, "VC            OVLF        LEN  UNMATCHED   INACTIVE\n");
+
+	for (unsigned int vc = 0; vc < DISCARDS_TABLE_NUM_VCS; ++vc) {
+		seq_printf(s, "%u       %10u %10u %10u %10u\n", vc,
+			   discards_table[vc][DISCARDS_TABLE_OVERFLOW],
+			   discards_table[vc][DISCARDS_TABLE_LENGTH_LIMIT],
+			   discards_table[vc][DISCARDS_TABLE_UNMATCHED],
+			   discards_table[vc][DISCARDS_TABLE_INACTIVE]);
+	}
+
+	seq_printf(s, "Last DT %10u %10u %10u %10u\n",
+		   discards_dt_table[DISCARDS_TABLE_OVERFLOW],
+		   discards_dt_table[DISCARDS_TABLE_LENGTH_LIMIT],
+		   discards_dt_table[DISCARDS_TABLE_UNMATCHED],
+		   discards_dt_table[DISCARDS_TABLE_INACTIVE]);
+
+	return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(csi2_errors);
+
+static void csi2_isr_handle_errors(struct csi2_device *csi2, u32 status)
+{
+	spin_lock(&csi2->errors_lock);
+
+	if (status & IRQ_OVERFLOW)
+		csi2->overflows++;
+
+	for (unsigned int i = 0; i < DISCARDS_TABLE_NUM_ENTRIES; ++i) {
+		static const u32 discard_bits[] = {
+			IRQ_DISCARD_OVERFLOW,
+			IRQ_DISCARD_LEN_LIMIT,
+			IRQ_DISCARD_UNMATCHED,
+			IRQ_DISCARD_INACTIVE,
+		};
+		static const u8 discard_regs[] = {
+			CSI2_DISCARDS_OVERFLOW,
+			CSI2_DISCARDS_LEN_LIMIT,
+			CSI2_DISCARDS_UNMATCHED,
+			CSI2_DISCARDS_INACTIVE,
+		};
+		u32 amount;
+		u8 dt, vc;
+		u32 v;
+
+		if (!(status & discard_bits[i]))
+			continue;
+
+		v = csi2_reg_read(csi2, discard_regs[i]);
+		csi2_reg_write(csi2, discard_regs[i], 0);
+
+		amount = (v & CSI2_DISCARDS_AMOUNT_MASK) >>
+			 CSI2_DISCARDS_AMOUNT_SHIFT;
+		dt = (v & CSI2_DISCARDS_DT_MASK) >> CSI2_DISCARDS_DT_SHIFT;
+		vc = (v & CSI2_DISCARDS_VC_MASK) >> CSI2_DISCARDS_VC_SHIFT;
+
+		csi2->discards_table[vc][i] += amount;
+		csi2->discards_dt_table[i] = dt;
+	}
+
+	spin_unlock(&csi2->errors_lock);
+}
+
+void csi2_isr(struct csi2_device *csi2, bool *sof, bool *eof)
+{
+	unsigned int i;
+	u32 status;
+
+	status = csi2_reg_read(csi2, CSI2_STATUS);
+	csi2_dbg_verbose("ISR: STA: 0x%x\n", status);
+
+	/* Write value back to clear the interrupts */
+	csi2_reg_write(csi2, CSI2_STATUS, status);
+
+	for (i = 0; i < CSI2_NUM_CHANNELS; i++) {
+		u32 dbg;
+
+		if ((status & IRQ_CH_MASK(i)) == 0)
+			continue;
+
+		dbg = csi2_reg_read(csi2, CSI2_CH_DEBUG(i));
+
+		csi2_dbg_verbose("ISR: [%u], %s%s%s%s%s frame: %u line: %u\n",
+				 i, (status & IRQ_FS(i)) ? "FS " : "",
+				 (status & IRQ_FE(i)) ? "FE " : "",
+				 (status & IRQ_FE_ACK(i)) ? "FE_ACK " : "",
+				 (status & IRQ_LE(i)) ? "LE " : "",
+				 (status & IRQ_LE_ACK(i)) ? "LE_ACK " : "",
+				 dbg >> 16,
+				 csi2->num_lines[i] ?
+					 ((dbg & 0xffff) % csi2->num_lines[i]) :
+					 0);
+
+		sof[i] = !!(status & IRQ_FS(i));
+		eof[i] = !!(status & IRQ_FE_ACK(i));
+	}
+
+	if (csi2_track_errors)
+		csi2_isr_handle_errors(csi2, status);
+}
+
+void csi2_set_buffer(struct csi2_device *csi2, unsigned int channel,
+		     dma_addr_t dmaaddr, unsigned int stride, unsigned int size)
+{
+	u64 addr = dmaaddr;
+	/*
+	 * ADDRESS0 must be written last as it triggers the double buffering
+	 * mechanism for all buffer registers within the hardware.
+	 */
+	addr >>= 4;
+	csi2_reg_write(csi2, CSI2_CH_LENGTH(channel), size >> 4);
+	csi2_reg_write(csi2, CSI2_CH_STRIDE(channel), stride >> 4);
+	csi2_reg_write(csi2, CSI2_CH_ADDR1(channel), addr >> 32);
+	csi2_reg_write(csi2, CSI2_CH_ADDR0(channel), addr & 0xffffffff);
+}
+
+void csi2_set_compression(struct csi2_device *csi2, unsigned int channel,
+			  enum csi2_compression_mode mode, unsigned int shift,
+			  unsigned int offset)
+{
+	u32 compression = 0;
+
+	set_field(&compression, COMP_OFFSET_MASK, offset);
+	set_field(&compression, COMP_SHIFT_MASK, shift);
+	set_field(&compression, COMP_MODE_MASK, mode);
+	csi2_reg_write(csi2, CSI2_CH_COMP_CTRL(channel), compression);
+}
+
+static int csi2_get_vc_dt_fallback(struct csi2_device *csi2,
+				   unsigned int channel, u8 *vc, u8 *dt)
+{
+	struct v4l2_subdev *sd = &csi2->sd;
+	struct v4l2_subdev_state *state;
+	struct v4l2_mbus_framefmt *fmt;
+	const struct cfe_fmt *cfe_fmt;
+
+	state = v4l2_subdev_get_locked_active_state(sd);
+
+	/* Without Streams API, the channel number matches the sink pad */
+	fmt = v4l2_subdev_state_get_format(state, channel);
+	if (!fmt)
+		return -EINVAL;
+
+	cfe_fmt = find_format_by_code(fmt->code);
+	if (!cfe_fmt)
+		return -EINVAL;
+
+	*vc = 0;
+	*dt = cfe_fmt->csi_dt;
+
+	return 0;
+}
+
+static int csi2_get_vc_dt(struct csi2_device *csi2, unsigned int channel,
+			  u8 *vc, u8 *dt)
+{
+	struct v4l2_mbus_frame_desc remote_desc;
+	const struct media_pad *remote_pad;
+	struct v4l2_subdev *source_sd;
+	int ret;
+
+	/* Without Streams API, the channel number matches the sink pad */
+	remote_pad = media_pad_remote_pad_first(&csi2->pad[channel]);
+	if (!remote_pad)
+		return -EPIPE;
+
+	source_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
+
+	ret = v4l2_subdev_call(source_sd, pad, get_frame_desc,
+			       remote_pad->index, &remote_desc);
+	if (ret == -ENOIOCTLCMD) {
+		csi2_dbg("source does not support get_frame_desc, use fallback\n");
+		return csi2_get_vc_dt_fallback(csi2, channel, vc, dt);
+	} else if (ret) {
+		csi2_err("Failed to get frame descriptor\n");
+		return ret;
+	}
+
+	if (remote_desc.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) {
+		csi2_err("Frame descriptor does not describe CSI-2 link");
+		return -EINVAL;
+	}
+
+	if (remote_desc.num_entries != 1) {
+		csi2_err("Frame descriptor does not have a single entry");
+		return -EINVAL;
+	}
+
+	*vc = remote_desc.entry[0].bus.csi2.vc;
+	*dt = remote_desc.entry[0].bus.csi2.dt;
+
+	return 0;
+}
+
+void csi2_start_channel(struct csi2_device *csi2, unsigned int channel,
+			enum csi2_mode mode, bool auto_arm,
+			bool pack_bytes, unsigned int width,
+			unsigned int height)
+{
+	u32 ctrl;
+	int ret;
+	u8 vc, dt;
+
+	ret = csi2_get_vc_dt(csi2, channel, &vc, &dt);
+	if (ret)
+		return;
+
+	csi2_dbg("%s [%u]\n", __func__, channel);
+
+	csi2_reg_write(csi2, CSI2_CH_CTRL(channel), 0);
+	csi2_reg_write(csi2, CSI2_CH_DEBUG(channel), 0);
+	csi2_reg_write(csi2, CSI2_STATUS, IRQ_CH_MASK(channel));
+
+	/* Enable channel and FS/FE interrupts. */
+	ctrl = DMA_EN | IRQ_EN_FS | IRQ_EN_FE_ACK | PACK_LINE;
+	/* PACK_BYTES ensures no striding for embedded data. */
+	if (pack_bytes)
+		ctrl |= PACK_BYTES;
+
+	if (auto_arm)
+		ctrl |= AUTO_ARM;
+
+	if (width && height) {
+		set_field(&ctrl, mode, CH_MODE_MASK);
+		csi2_reg_write(csi2, CSI2_CH_FRAME_SIZE(channel),
+			       (height << 16) | width);
+	} else {
+		set_field(&ctrl, 0x0, CH_MODE_MASK);
+		csi2_reg_write(csi2, CSI2_CH_FRAME_SIZE(channel), 0);
+	}
+
+	set_field(&ctrl, vc, VC_MASK);
+	set_field(&ctrl, dt, DT_MASK);
+	csi2_reg_write(csi2, CSI2_CH_CTRL(channel), ctrl);
+	csi2->num_lines[channel] = height;
+}
+
+void csi2_stop_channel(struct csi2_device *csi2, unsigned int channel)
+{
+	csi2_dbg("%s [%u]\n", __func__, channel);
+
+	/* Channel disable.  Use FORCE to allow stopping mid-frame. */
+	csi2_reg_write(csi2, CSI2_CH_CTRL(channel), FORCE);
+	/* Latch the above change by writing to the ADDR0 register. */
+	csi2_reg_write(csi2, CSI2_CH_ADDR0(channel), 0);
+	/* Write this again, the HW needs it! */
+	csi2_reg_write(csi2, CSI2_CH_ADDR0(channel), 0);
+}
+
+void csi2_open_rx(struct csi2_device *csi2)
+{
+	csi2_reg_write(csi2, CSI2_IRQ_MASK,
+		       csi2_track_errors ? CSI2_IRQ_MASK_IRQ_ALL : 0);
+
+	dphy_start(&csi2->dphy);
+
+	csi2_reg_write(csi2, CSI2_CTRL,
+		       csi2->multipacket_line ? 0 : EOP_IS_EOL);
+}
+
+void csi2_close_rx(struct csi2_device *csi2)
+{
+	dphy_stop(&csi2->dphy);
+
+	csi2_reg_write(csi2, CSI2_IRQ_MASK, 0);
+}
+
+static int csi2_init_cfg(struct v4l2_subdev *sd,
+			 struct v4l2_subdev_state *state)
+{
+	struct v4l2_mbus_framefmt *fmt;
+
+	for (unsigned int i = 0; i < CSI2_NUM_CHANNELS; ++i) {
+		const struct v4l2_mbus_framefmt *def_fmt;
+
+		/* CSI2_CH1_EMBEDDED */
+		if (i == 1)
+			def_fmt = &cfe_default_meta_format;
+		else
+			def_fmt = &cfe_default_format;
+
+		fmt = v4l2_subdev_state_get_format(state, i);
+		*fmt = *def_fmt;
+
+		fmt = v4l2_subdev_state_get_format(state, i + CSI2_NUM_CHANNELS);
+		*fmt = *def_fmt;
+	}
+
+	return 0;
+}
+
+static int csi2_pad_set_fmt(struct v4l2_subdev *sd,
+			    struct v4l2_subdev_state *state,
+			    struct v4l2_subdev_format *format)
+{
+	if (format->pad < CSI2_NUM_CHANNELS) {
+		/*
+		 * Store the sink pad format and propagate it to the source pad.
+		 */
+
+		struct v4l2_mbus_framefmt *fmt;
+
+		fmt = v4l2_subdev_state_get_format(state, format->pad);
+		if (!fmt)
+			return -EINVAL;
+
+		*fmt = format->format;
+
+		fmt = v4l2_subdev_state_get_format(state,
+			format->pad + CSI2_NUM_CHANNELS);
+		if (!fmt)
+			return -EINVAL;
+
+		format->format.field = V4L2_FIELD_NONE;
+
+		*fmt = format->format;
+	} else {
+		/*
+		 * Only allow changing the source pad mbus code.
+		 */
+
+		struct v4l2_mbus_framefmt *sink_fmt, *source_fmt;
+		u32 sink_code;
+		u32 code;
+
+		sink_fmt = v4l2_subdev_state_get_format(state,
+			format->pad - CSI2_NUM_CHANNELS);
+		if (!sink_fmt)
+			return -EINVAL;
+
+		source_fmt = v4l2_subdev_state_get_format(state, format->pad);
+		if (!source_fmt)
+			return -EINVAL;
+
+		sink_code = sink_fmt->code;
+		code = format->format.code;
+
+		/*
+		 * If the source code from the user does not match the code in
+		 * the sink pad, check that the source code matches either the
+		 * 16-bit version or the compressed version of the sink code.
+		 */
+
+		if (code != sink_code &&
+		    (code == cfe_find_16bit_code(sink_code) ||
+		     code == cfe_find_compressed_code(sink_code)))
+			source_fmt->code = code;
+
+		format->format.code = source_fmt->code;
+	}
+
+	return 0;
+}
+
+static const struct v4l2_subdev_internal_ops csi2_internal_ops = {
+	.init_state = csi2_init_cfg,
+};
+
+static const struct v4l2_subdev_pad_ops csi2_subdev_pad_ops = {
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = csi2_pad_set_fmt,
+	.link_validate = v4l2_subdev_link_validate_default,
+};
+
+static const struct media_entity_operations csi2_entity_ops = {
+	.link_validate = v4l2_subdev_link_validate,
+};
+
+static const struct v4l2_subdev_ops csi2_subdev_ops = {
+	.pad = &csi2_subdev_pad_ops,
+};
+
+int csi2_init(struct csi2_device *csi2, struct dentry *debugfs)
+{
+	unsigned int i, ret;
+
+	spin_lock_init(&csi2->errors_lock);
+
+	csi2->dphy.dev = csi2->v4l2_dev->dev;
+	dphy_probe(&csi2->dphy);
+
+	debugfs_create_file("csi2_regs", 0444, debugfs, csi2, &csi2_regs_fops);
+
+	if (csi2_track_errors)
+		debugfs_create_file("csi2_errors", 0444, debugfs, csi2,
+				    &csi2_errors_fops);
+
+	for (i = 0; i < CSI2_NUM_CHANNELS * 2; i++)
+		csi2->pad[i].flags = i < CSI2_NUM_CHANNELS ?
+				     MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE;
+
+	ret = media_entity_pads_init(&csi2->sd.entity, ARRAY_SIZE(csi2->pad),
+				     csi2->pad);
+	if (ret)
+		return ret;
+
+	/* Initialize subdev */
+	v4l2_subdev_init(&csi2->sd, &csi2_subdev_ops);
+	csi2->sd.internal_ops = &csi2_internal_ops;
+	csi2->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+	csi2->sd.entity.ops = &csi2_entity_ops;
+	csi2->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
+	csi2->sd.owner = THIS_MODULE;
+	snprintf(csi2->sd.name, sizeof(csi2->sd.name), "csi2");
+
+	ret = v4l2_subdev_init_finalize(&csi2->sd);
+	if (ret)
+		goto err_entity_cleanup;
+
+	ret = v4l2_device_register_subdev(csi2->v4l2_dev, &csi2->sd);
+	if (ret) {
+		csi2_err("Failed register csi2 subdev (%d)\n", ret);
+		goto err_subdev_cleanup;
+	}
+
+	return 0;
+
+err_subdev_cleanup:
+	v4l2_subdev_cleanup(&csi2->sd);
+err_entity_cleanup:
+	media_entity_cleanup(&csi2->sd.entity);
+
+	return ret;
+}
+
+void csi2_uninit(struct csi2_device *csi2)
+{
+	v4l2_device_unregister_subdev(&csi2->sd);
+	v4l2_subdev_cleanup(&csi2->sd);
+	media_entity_cleanup(&csi2->sd.entity);
+}
diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/csi2.h b/drivers/media/platform/raspberrypi/rp1_cfe/csi2.h
new file mode 100644
index 000000000000..4fff16ee9407
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/rp1_cfe/csi2.h
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * RP1 CSI-2 driver.
+ * Copyright (c) 2021 Raspberry Pi Ltd.
+ *
+ */
+#ifndef _RP1_CSI2_
+#define _RP1_CSI2_
+
+#include <linux/debugfs.h>
+#include <linux/io.h>
+#include <linux/types.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+
+#include "dphy.h"
+
+#define CSI2_NUM_CHANNELS 4
+
+#define DISCARDS_TABLE_NUM_VCS 4
+
+enum csi2_mode {
+	CSI2_MODE_NORMAL = 0,
+	CSI2_MODE_REMAP = 1,
+	CSI2_MODE_COMPRESSED = 2,
+	CSI2_MODE_FE_STREAMING = 3,
+};
+
+enum csi2_compression_mode {
+	CSI2_COMPRESSION_DELTA = 1,
+	CSI2_COMPRESSION_SIMPLE = 2,
+	CSI2_COMPRESSION_COMBINED = 3,
+};
+
+struct csi2_cfg {
+	u16 width;
+	u16 height;
+	u32 stride;
+	u32 buffer_size;
+};
+
+enum discards_table_index {
+	DISCARDS_TABLE_OVERFLOW = 0,
+	DISCARDS_TABLE_LENGTH_LIMIT,
+	DISCARDS_TABLE_UNMATCHED,
+	DISCARDS_TABLE_INACTIVE,
+	DISCARDS_TABLE_NUM_ENTRIES,
+};
+
+struct csi2_device {
+	/* Parent V4l2 device */
+	struct v4l2_device *v4l2_dev;
+
+	void __iomem *base;
+
+	struct dphy_data dphy;
+
+	enum v4l2_mbus_type bus_type;
+	unsigned int bus_flags;
+	bool multipacket_line;
+	unsigned int num_lines[CSI2_NUM_CHANNELS];
+
+	struct media_pad pad[CSI2_NUM_CHANNELS * 2];
+	struct v4l2_subdev sd;
+
+	/* lock for csi2 errors counters */
+	spinlock_t errors_lock;
+	u32 overflows;
+	u32 discards_table[DISCARDS_TABLE_NUM_VCS][DISCARDS_TABLE_NUM_ENTRIES];
+	u32 discards_dt_table[DISCARDS_TABLE_NUM_ENTRIES];
+};
+
+void csi2_isr(struct csi2_device *csi2, bool *sof, bool *eof);
+void csi2_set_buffer(struct csi2_device *csi2, unsigned int channel,
+		     dma_addr_t dmaaddr, unsigned int stride,
+		     unsigned int size);
+void csi2_set_compression(struct csi2_device *csi2, unsigned int channel,
+			  enum csi2_compression_mode mode, unsigned int shift,
+			  unsigned int offset);
+void csi2_start_channel(struct csi2_device *csi2, unsigned int channel,
+			enum csi2_mode mode, bool auto_arm,
+			bool pack_bytes, unsigned int width,
+			unsigned int height);
+void csi2_stop_channel(struct csi2_device *csi2, unsigned int channel);
+void csi2_open_rx(struct csi2_device *csi2);
+void csi2_close_rx(struct csi2_device *csi2);
+int csi2_init(struct csi2_device *csi2, struct dentry *debugfs);
+void csi2_uninit(struct csi2_device *csi2);
+
+#endif
diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/dphy.c b/drivers/media/platform/raspberrypi/rp1_cfe/dphy.c
new file mode 100644
index 000000000000..7eec3e77109e
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/rp1_cfe/dphy.c
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * RP1 CSI-2 Driver
+ *
+ * Copyright (C) 2021 - Raspberry Pi Ltd.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/dev_printk.h>
+#include <linux/pm_runtime.h>
+
+#include "dphy.h"
+
+#define dphy_dbg(fmt, arg...) dev_dbg(dphy->dev, fmt, ##arg)
+#define dphy_info(fmt, arg...) dev_info(dphy->dev, fmt, ##arg)
+#define dphy_err(fmt, arg...) dev_err(dphy->dev, fmt, ##arg)
+
+/* DW dphy Host registers */
+#define VERSION		0x000
+#define N_LANES		0x004
+#define RESETN		0x008
+#define PHY_SHUTDOWNZ	0x040
+#define PHY_RSTZ	0x044
+#define PHY_RX		0x048
+#define	PHY_STOPSTATE	0x04c
+#define PHY_TST_CTRL0	0x050
+#define PHY_TST_CTRL1	0x054
+#define PHY2_TST_CTRL0	0x058
+#define PHY2_TST_CTRL1	0x05c
+
+/* DW dphy Host Transactions */
+#define DPHY_HS_RX_CTRL_LANE0_OFFSET	0x44
+#define DPHY_PLL_INPUT_DIV_OFFSET	0x17
+#define DPHY_PLL_LOOP_DIV_OFFSET	0x18
+#define DPHY_PLL_DIV_CTRL_OFFSET	0x19
+
+static u32 dw_csi2_host_read(struct dphy_data *dphy, u32 offset)
+{
+	return readl(dphy->base + offset);
+}
+
+static void dw_csi2_host_write(struct dphy_data *dphy, u32 offset, u32 data)
+{
+	writel(data, dphy->base + offset);
+}
+
+static void set_tstclr(struct dphy_data *dphy, u32 val)
+{
+	u32 ctrl0 = dw_csi2_host_read(dphy, PHY_TST_CTRL0);
+
+	dw_csi2_host_write(dphy, PHY_TST_CTRL0, (ctrl0 & ~1) | val);
+}
+
+static void set_tstclk(struct dphy_data *dphy, u32 val)
+{
+	u32 ctrl0 = dw_csi2_host_read(dphy, PHY_TST_CTRL0);
+
+	dw_csi2_host_write(dphy, PHY_TST_CTRL0, (ctrl0 & ~2) | (val << 1));
+}
+
+static uint8_t get_tstdout(struct dphy_data *dphy)
+{
+	u32 ctrl1 = dw_csi2_host_read(dphy, PHY_TST_CTRL1);
+
+	return ((ctrl1 >> 8) & 0xff);
+}
+
+static void set_testen(struct dphy_data *dphy, u32 val)
+{
+	u32 ctrl1 = dw_csi2_host_read(dphy, PHY_TST_CTRL1);
+
+	dw_csi2_host_write(dphy, PHY_TST_CTRL1,
+			   (ctrl1 & ~(1 << 16)) | (val << 16));
+}
+
+static void set_testdin(struct dphy_data *dphy, u32 val)
+{
+	u32 ctrl1 = dw_csi2_host_read(dphy, PHY_TST_CTRL1);
+
+	dw_csi2_host_write(dphy, PHY_TST_CTRL1, (ctrl1 & ~0xff) | val);
+}
+
+static uint8_t dphy_transaction(struct dphy_data *dphy, u8 test_code,
+				uint8_t test_data)
+{
+	/* See page 101 of the MIPI DPHY databook. */
+	set_tstclk(dphy, 1);
+	set_testen(dphy, 0);
+	set_testdin(dphy, test_code);
+	set_testen(dphy, 1);
+	set_tstclk(dphy, 0);
+	set_testen(dphy, 0);
+	set_testdin(dphy, test_data);
+	set_tstclk(dphy, 1);
+	return get_tstdout(dphy);
+}
+
+static void dphy_set_hsfreqrange(struct dphy_data *dphy, uint32_t mbps)
+{
+	/* See Table 5-1 on page 65 of dphy databook */
+	static const u16 hsfreqrange_table[][2] = {
+		{ 89, 0b000000 },   { 99, 0b010000 },	{ 109, 0b100000 },
+		{ 129, 0b000001 },  { 139, 0b010001 },	{ 149, 0b100001 },
+		{ 169, 0b000010 },  { 179, 0b010010 },	{ 199, 0b100010 },
+		{ 219, 0b000011 },  { 239, 0b010011 },	{ 249, 0b100011 },
+		{ 269, 0b000100 },  { 299, 0b010100 },	{ 329, 0b000101 },
+		{ 359, 0b010101 },  { 399, 0b100101 },	{ 449, 0b000110 },
+		{ 499, 0b010110 },  { 549, 0b000111 },	{ 599, 0b010111 },
+		{ 649, 0b001000 },  { 699, 0b011000 },	{ 749, 0b001001 },
+		{ 799, 0b011001 },  { 849, 0b101001 },	{ 899, 0b111001 },
+		{ 949, 0b001010 },  { 999, 0b011010 },	{ 1049, 0b101010 },
+		{ 1099, 0b111010 }, { 1149, 0b001011 }, { 1199, 0b011011 },
+		{ 1249, 0b101011 }, { 1299, 0b111011 }, { 1349, 0b001100 },
+		{ 1399, 0b011100 }, { 1449, 0b101100 }, { 1500, 0b111100 },
+	};
+	unsigned int i;
+
+	if (mbps < 80 || mbps > 1500)
+		dphy_err("DPHY: Datarate %u Mbps out of range\n", mbps);
+
+	for (i = 0; i < ARRAY_SIZE(hsfreqrange_table) - 1; i++) {
+		if (mbps <= hsfreqrange_table[i][0])
+			break;
+	}
+
+	dphy_transaction(dphy, DPHY_HS_RX_CTRL_LANE0_OFFSET,
+			 hsfreqrange_table[i][1] << 1);
+}
+
+static void dphy_init(struct dphy_data *dphy)
+{
+	dw_csi2_host_write(dphy, PHY_RSTZ, 0);
+	dw_csi2_host_write(dphy, PHY_SHUTDOWNZ, 0);
+	set_tstclk(dphy, 1);
+	set_testen(dphy, 0);
+	set_tstclr(dphy, 1);
+	usleep_range(15, 20);
+	set_tstclr(dphy, 0);
+	usleep_range(15, 20);
+
+	dphy_set_hsfreqrange(dphy, dphy->dphy_rate);
+
+	usleep_range(5, 10);
+	dw_csi2_host_write(dphy, PHY_SHUTDOWNZ, 1);
+	usleep_range(5, 10);
+	dw_csi2_host_write(dphy, PHY_RSTZ, 1);
+}
+
+void dphy_start(struct dphy_data *dphy)
+{
+	dw_csi2_host_write(dphy, RESETN, 0);
+	dw_csi2_host_write(dphy, N_LANES, (dphy->active_lanes - 1));
+	dphy_init(dphy);
+	dw_csi2_host_write(dphy, RESETN, 0xffffffff);
+	usleep_range(10, 50);
+}
+
+void dphy_stop(struct dphy_data *dphy)
+{
+	/*
+	 * We no longer go into reset here, because the camera might still be
+	 * streaming. If we kill the CSI-2 Host in mid-packet, it can leave the
+	 * IDI interface in a bad state, causing the next packet to be lost.
+	 *
+	 * XXX Is it safe to assume it will be idle before the next dphy_start?
+	 * XXX What happens if the camera itself generates an incomplete packet?
+	 *
+	 * TODO: Instead, should we consider resetting the *entire* MIPI block
+	 * (including CSI2AXI and ISP-FE)? That can't safely be done until
+	 * all AXI traffic has completed. It would cause APB access to hang.
+	 */
+}
+
+void dphy_probe(struct dphy_data *dphy)
+{
+	u32 host_ver;
+	u8 host_ver_major, host_ver_minor;
+
+	host_ver = dw_csi2_host_read(dphy, VERSION);
+	host_ver_major = (u8)((host_ver >> 24) - '0');
+	host_ver_minor = (u8)((host_ver >> 16) - '0');
+	host_ver_minor = host_ver_minor * 10;
+	host_ver_minor += (u8)((host_ver >> 8) - '0');
+
+	dphy_info("DW dphy Host HW v%u.%u\n", host_ver_major, host_ver_minor);
+}
diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/dphy.h b/drivers/media/platform/raspberrypi/rp1_cfe/dphy.h
new file mode 100644
index 000000000000..9d7a80b3f684
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/rp1_cfe/dphy.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2021 Raspberry Pi Ltd.
+ *
+ */
+
+#ifndef _RP1_DPHY_
+#define _RP1_DPHY_
+
+#include <linux/io.h>
+#include <linux/types.h>
+
+struct dphy_data {
+	struct device *dev;
+
+	void __iomem *base;
+
+	u32 dphy_rate;
+	u32 max_lanes;
+	u32 active_lanes;
+};
+
+void dphy_probe(struct dphy_data *dphy);
+void dphy_start(struct dphy_data *dphy);
+void dphy_stop(struct dphy_data *dphy);
+
+#endif
diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/pisp_common.h b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_common.h
new file mode 100644
index 000000000000..e91aa2ed3659
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_common.h
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
+/*
+ * RP1 PiSP common definitions.
+ *
+ * Copyright (C) 2021 - Raspberry Pi Ltd.
+ *
+ */
+#ifndef _PISP_COMMON_H_
+#define _PISP_COMMON_H_
+
+#include "pisp_types.h"
+
+struct pisp_bla_config {
+	u16 black_level_r;
+	u16 black_level_gr;
+	u16 black_level_gb;
+	u16 black_level_b;
+	u16 output_black_level;
+	u8 pad[2];
+};
+
+struct pisp_wbg_config {
+	u16 gain_r;
+	u16 gain_g;
+	u16 gain_b;
+	u8 pad[2];
+};
+
+struct pisp_compress_config {
+	/* value subtracted from incoming data */
+	u16 offset;
+	u8 pad;
+	/* 1 => Companding; 2 => Delta (recommended); 3 => Combined (for HDR) */
+	u8 mode;
+};
+
+struct pisp_decompress_config {
+	/* value added to reconstructed data */
+	u16 offset;
+	u8 pad;
+	/* 1 => Companding; 2 => Delta (recommended); 3 => Combined (for HDR) */
+	u8 mode;
+};
+
+enum pisp_axi_flags {
+	/*
+	 * round down bursts to end at a 32-byte boundary, to align following
+	 * bursts
+	 */
+	PISP_AXI_FLAG_ALIGN = 128,
+	/* for FE writer: force WSTRB high, to pad output to 16-byte boundary */
+	PISP_AXI_FLAG_PAD = 64,
+	/* for FE writer: Use Output FIFO level to trigger "panic" */
+	PISP_AXI_FLAG_PANIC = 32,
+};
+
+struct pisp_axi_config {
+	/*
+	 * burst length minus one, which must be in the range 0:15; OR'd with
+	 * flags
+	 */
+	u8 maxlen_flags;
+	/* { prot[2:0], cache[3:0] } fields, echoed on AXI bus */
+	u8 cache_prot;
+	/* QoS field(s) (4x4 bits for FE writer; 4 bits for other masters) */
+	u16 qos;
+};
+
+#endif /* _PISP_COMMON_H_ */
diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/pisp_fe.c b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_fe.c
new file mode 100644
index 000000000000..07bc550deea7
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_fe.c
@@ -0,0 +1,569 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PiSP Front End driver.
+ * Copyright (c) 2021 Raspberry Pi Ltd.
+ *
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/moduleparam.h>
+#include <linux/pm_runtime.h>
+#include <linux/seq_file.h>
+
+#include <media/videobuf2-dma-contig.h>
+
+#include "pisp_fe.h"
+#include "cfe.h"
+
+#define FE_VERSION		0x000
+#define FE_CONTROL		0x004
+#define FE_STATUS		0x008
+#define FE_FRAME_STATUS		0x00c
+#define FE_ERROR_STATUS		0x010
+#define FE_OUTPUT_STATUS	0x014
+#define FE_INT_EN		0x018
+#define FE_INT_STATUS		0x01c
+
+/* CONTROL */
+#define FE_CONTROL_QUEUE	BIT(0)
+#define FE_CONTROL_ABORT	BIT(1)
+#define FE_CONTROL_RESET	BIT(2)
+#define FE_CONTROL_LATCH_REGS	BIT(3)
+
+/* INT_EN / INT_STATUS */
+#define FE_INT_EOF		BIT(0)
+#define FE_INT_SOF		BIT(1)
+#define FE_INT_LINES0		BIT(8)
+#define FE_INT_LINES1		BIT(9)
+#define FE_INT_STATS		BIT(16)
+#define FE_INT_QREADY		BIT(24)
+
+/* STATUS */
+#define FE_STATUS_QUEUED	BIT(0)
+#define FE_STATUS_WAITING	BIT(1)
+#define FE_STATUS_ACTIVE	BIT(2)
+
+#define PISP_FE_CONFIG_BASE_OFFSET	0x0040
+
+#define PISP_FE_ENABLE_STATS_CLUSTER \
+	(PISP_FE_ENABLE_STATS_CROP | PISP_FE_ENABLE_DECIMATE    | \
+	 PISP_FE_ENABLE_BLC        | PISP_FE_ENABLE_CDAF_STATS  | \
+	 PISP_FE_ENABLE_AWB_STATS  | PISP_FE_ENABLE_RGBY        | \
+	 PISP_FE_ENABLE_LSC        | PISP_FE_ENABLE_AGC_STATS)
+
+#define PISP_FE_ENABLE_OUTPUT_CLUSTER(i)				\
+	((PISP_FE_ENABLE_CROP0     | PISP_FE_ENABLE_DOWNSCALE0 |	\
+	  PISP_FE_ENABLE_COMPRESS0 | PISP_FE_ENABLE_OUTPUT0) << (4 * (i)))
+
+struct pisp_fe_config_param {
+	u32 dirty_flags;
+	u32 dirty_flags_extra;
+	size_t offset;
+	size_t size;
+};
+
+static const struct pisp_fe_config_param pisp_fe_config_map[] = {
+	/* *_dirty_flag_extra types */
+	{ 0, PISP_FE_DIRTY_GLOBAL,     offsetof(struct pisp_fe_config, global),
+					sizeof(struct pisp_fe_global_config)         },
+	{ 0, PISP_FE_DIRTY_FLOATING,   offsetof(struct pisp_fe_config, floating_stats),
+					sizeof(struct pisp_fe_floating_stats_config) },
+	{ 0, PISP_FE_DIRTY_OUTPUT_AXI, offsetof(struct pisp_fe_config, output_axi),
+					sizeof(struct pisp_fe_output_axi_config)     },
+	/* *_dirty_flag types */
+	{ PISP_FE_ENABLE_INPUT,      0, offsetof(struct pisp_fe_config, input),
+					sizeof(struct pisp_fe_input_config)          },
+	{ PISP_FE_ENABLE_DECOMPRESS, 0, offsetof(struct pisp_fe_config, decompress),
+					sizeof(struct pisp_decompress_config)        },
+	{ PISP_FE_ENABLE_DECOMPAND,  0, offsetof(struct pisp_fe_config, decompand),
+					sizeof(struct pisp_fe_decompand_config)      },
+	{ PISP_FE_ENABLE_BLA,        0, offsetof(struct pisp_fe_config, bla),
+					sizeof(struct pisp_bla_config)               },
+	{ PISP_FE_ENABLE_DPC,        0, offsetof(struct pisp_fe_config, dpc),
+					sizeof(struct pisp_fe_dpc_config)            },
+	{ PISP_FE_ENABLE_STATS_CROP, 0, offsetof(struct pisp_fe_config, stats_crop),
+					sizeof(struct pisp_fe_crop_config)           },
+	{ PISP_FE_ENABLE_BLC,	     0, offsetof(struct pisp_fe_config, blc),
+					sizeof(struct pisp_bla_config)               },
+	{ PISP_FE_ENABLE_CDAF_STATS, 0, offsetof(struct pisp_fe_config, cdaf_stats),
+					sizeof(struct pisp_fe_cdaf_stats_config)     },
+	{ PISP_FE_ENABLE_AWB_STATS,  0, offsetof(struct pisp_fe_config, awb_stats),
+					sizeof(struct pisp_fe_awb_stats_config)      },
+	{ PISP_FE_ENABLE_RGBY,       0, offsetof(struct pisp_fe_config, rgby),
+					sizeof(struct pisp_fe_rgby_config)           },
+	{ PISP_FE_ENABLE_LSC,        0, offsetof(struct pisp_fe_config, lsc),
+					sizeof(struct pisp_fe_lsc_config)            },
+	{ PISP_FE_ENABLE_AGC_STATS,  0, offsetof(struct pisp_fe_config, agc_stats),
+					sizeof(struct pisp_agc_statistics)           },
+	{ PISP_FE_ENABLE_CROP0,      0, offsetof(struct pisp_fe_config, ch[0].crop),
+					sizeof(struct pisp_fe_crop_config)           },
+	{ PISP_FE_ENABLE_DOWNSCALE0, 0, offsetof(struct pisp_fe_config, ch[0].downscale),
+					sizeof(struct pisp_fe_downscale_config)      },
+	{ PISP_FE_ENABLE_COMPRESS0,  0, offsetof(struct pisp_fe_config, ch[0].compress),
+					sizeof(struct pisp_compress_config)          },
+	{ PISP_FE_ENABLE_OUTPUT0,    0, offsetof(struct pisp_fe_config, ch[0].output),
+					sizeof(struct pisp_fe_output_config)         },
+	{ PISP_FE_ENABLE_CROP1,      0, offsetof(struct pisp_fe_config, ch[1].crop),
+					sizeof(struct pisp_fe_crop_config)           },
+	{ PISP_FE_ENABLE_DOWNSCALE1, 0, offsetof(struct pisp_fe_config, ch[1].downscale),
+					sizeof(struct pisp_fe_downscale_config)      },
+	{ PISP_FE_ENABLE_COMPRESS1,  0, offsetof(struct pisp_fe_config, ch[1].compress),
+					sizeof(struct pisp_compress_config)          },
+	{ PISP_FE_ENABLE_OUTPUT1,    0, offsetof(struct pisp_fe_config, ch[1].output),
+					sizeof(struct pisp_fe_output_config)         },
+};
+
+#define pisp_fe_dbg_verbose(fmt, arg...)                        \
+	do {                                                    \
+		if (cfe_debug_verbose)                          \
+			dev_dbg(fe->v4l2_dev->dev, fmt, ##arg); \
+	} while (0)
+#define pisp_fe_dbg(fmt, arg...) dev_dbg(fe->v4l2_dev->dev, fmt, ##arg)
+#define pisp_fe_info(fmt, arg...) dev_info(fe->v4l2_dev->dev, fmt, ##arg)
+#define pisp_fe_err(fmt, arg...) dev_err(fe->v4l2_dev->dev, fmt, ##arg)
+
+static inline u32 pisp_fe_reg_read(struct pisp_fe_device *fe, u32 offset)
+{
+	return readl(fe->base + offset);
+}
+
+static inline void pisp_fe_reg_write(struct pisp_fe_device *fe, u32 offset,
+				     u32 val)
+{
+	writel(val, fe->base + offset);
+	pisp_fe_dbg_verbose("fe: write 0x%04x -> 0x%03x\n", val, offset);
+}
+
+static inline void pisp_fe_reg_write_relaxed(struct pisp_fe_device *fe, u32 offset,
+					     u32 val)
+{
+	writel_relaxed(val, fe->base + offset);
+	pisp_fe_dbg_verbose("fe: write 0x%04x -> 0x%03x\n", val, offset);
+}
+
+static int pisp_regs_show(struct seq_file *s, void *data)
+{
+	struct pisp_fe_device *fe = s->private;
+	int ret;
+
+	ret = pm_runtime_resume_and_get(fe->v4l2_dev->dev);
+	if (ret)
+		return ret;
+
+	pisp_fe_reg_write(fe, FE_CONTROL, FE_CONTROL_LATCH_REGS);
+
+#define DUMP(reg) seq_printf(s, #reg " \t0x%08x\n", pisp_fe_reg_read(fe, reg))
+	DUMP(FE_VERSION);
+	DUMP(FE_CONTROL);
+	DUMP(FE_STATUS);
+	DUMP(FE_FRAME_STATUS);
+	DUMP(FE_ERROR_STATUS);
+	DUMP(FE_OUTPUT_STATUS);
+	DUMP(FE_INT_EN);
+	DUMP(FE_INT_STATUS);
+#undef DUMP
+
+	pm_runtime_put(fe->v4l2_dev->dev);
+
+	return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(pisp_regs);
+
+static void pisp_config_write(struct pisp_fe_device *fe,
+			      struct pisp_fe_config *config,
+			      unsigned int start_offset,
+			      unsigned int size)
+{
+	const unsigned int max_offset =
+		offsetof(struct pisp_fe_config, ch[PISP_FE_NUM_OUTPUTS]);
+	unsigned int i, end_offset;
+	u32 *cfg = (u32 *)config;
+
+	start_offset = min(start_offset, max_offset);
+	end_offset = min(start_offset + size, max_offset);
+
+	cfg += start_offset >> 2;
+	for (i = start_offset; i < end_offset; i += 4, cfg++)
+		pisp_fe_reg_write_relaxed(fe, PISP_FE_CONFIG_BASE_OFFSET + i,
+					  *cfg);
+}
+
+void pisp_fe_isr(struct pisp_fe_device *fe, bool *sof, bool *eof)
+{
+	u32 status, int_status, out_status, frame_status, error_status;
+	unsigned int i;
+
+	pisp_fe_reg_write(fe, FE_CONTROL, FE_CONTROL_LATCH_REGS);
+	status = pisp_fe_reg_read(fe, FE_STATUS);
+	out_status = pisp_fe_reg_read(fe, FE_OUTPUT_STATUS);
+	frame_status = pisp_fe_reg_read(fe, FE_FRAME_STATUS);
+	error_status = pisp_fe_reg_read(fe, FE_ERROR_STATUS);
+
+	int_status = pisp_fe_reg_read(fe, FE_INT_STATUS);
+	pisp_fe_reg_write(fe, FE_INT_STATUS, int_status);
+
+	pisp_fe_dbg_verbose("%s: status 0x%x out 0x%x frame 0x%x error 0x%x int 0x%x\n",
+		__func__, status, out_status, frame_status, error_status,
+		int_status);
+
+	/* We do not report interrupts for the input/stream pad. */
+	for (i = 0; i < FE_NUM_PADS - 1; i++) {
+		sof[i] = !!(int_status & FE_INT_SOF);
+		eof[i] = !!(int_status & FE_INT_EOF);
+	}
+}
+
+static bool pisp_fe_validate_output(struct pisp_fe_config const *cfg,
+				    unsigned int c, struct v4l2_format const *f)
+{
+	unsigned int wbytes;
+
+	wbytes = cfg->ch[c].output.format.width;
+	if (cfg->ch[c].output.format.format & PISP_IMAGE_FORMAT_BPS_MASK)
+		wbytes *= 2;
+
+	/* Check output image dimensions are nonzero and not too big */
+	if (cfg->ch[c].output.format.width < 2 ||
+	    cfg->ch[c].output.format.height < 2 ||
+	    cfg->ch[c].output.format.height > f->fmt.pix.height ||
+	    cfg->ch[c].output.format.stride > f->fmt.pix.bytesperline ||
+	    wbytes > f->fmt.pix.bytesperline)
+		return false;
+
+	/* Check for zero-sized crops, which could cause lockup */
+	if ((cfg->global.enables & PISP_FE_ENABLE_CROP(c)) &&
+	    ((cfg->ch[c].crop.offset_x >= (cfg->input.format.width & ~1) ||
+	      cfg->ch[c].crop.offset_y >= cfg->input.format.height ||
+	      cfg->ch[c].crop.width < 2 ||
+	      cfg->ch[c].crop.height < 2)))
+		return false;
+
+	if ((cfg->global.enables & PISP_FE_ENABLE_DOWNSCALE(c)) &&
+	    (cfg->ch[c].downscale.output_width < 2 ||
+	     cfg->ch[c].downscale.output_height < 2))
+		return false;
+
+	return true;
+}
+
+static bool pisp_fe_validate_stats(struct pisp_fe_config const *cfg)
+{
+	/* Check for zero-sized crop, which could cause lockup */
+	return (!(cfg->global.enables & PISP_FE_ENABLE_STATS_CROP) ||
+		(cfg->stats_crop.offset_x < (cfg->input.format.width & ~1) &&
+		 cfg->stats_crop.offset_y < cfg->input.format.height &&
+		 cfg->stats_crop.width >= 2 &&
+		 cfg->stats_crop.height >= 2));
+}
+
+int pisp_fe_validate_config(struct pisp_fe_device *fe,
+			    struct pisp_fe_config *cfg,
+			    struct v4l2_format const *f0,
+			    struct v4l2_format const *f1)
+{
+	unsigned int i;
+
+	/*
+	 * Check the input is enabled, streaming and has nonzero size;
+	 * to avoid cases where the hardware might lock up or try to
+	 * read inputs from memory (which this driver doesn't support).
+	 */
+	if (!(cfg->global.enables & PISP_FE_ENABLE_INPUT) ||
+	    cfg->input.streaming != 1 || cfg->input.format.width < 2 ||
+	    cfg->input.format.height < 2) {
+		pisp_fe_err("%s: Input config not valid", __func__);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < PISP_FE_NUM_OUTPUTS; i++) {
+		if (!(cfg->global.enables & PISP_FE_ENABLE_OUTPUT(i))) {
+			if (cfg->global.enables &
+					PISP_FE_ENABLE_OUTPUT_CLUSTER(i)) {
+				pisp_fe_err("%s: Output %u not valid",
+					    __func__, i);
+				return -EINVAL;
+			}
+			continue;
+		}
+
+		if (!pisp_fe_validate_output(cfg, i, i ? f1 : f0))
+			return -EINVAL;
+	}
+
+	if ((cfg->global.enables & PISP_FE_ENABLE_STATS_CLUSTER) &&
+	    !pisp_fe_validate_stats(cfg)) {
+		pisp_fe_err("%s: Stats config not valid", __func__);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+void pisp_fe_submit_job(struct pisp_fe_device *fe, struct vb2_buffer **vb2_bufs,
+			struct pisp_fe_config *cfg)
+{
+	unsigned int i;
+	u64 addr;
+	u32 status;
+
+	/*
+	 * Check output buffers exist and outputs are correctly configured.
+	 * If valid, set the buffer's DMA address; otherwise disable.
+	 */
+	for (i = 0; i < PISP_FE_NUM_OUTPUTS; i++) {
+		struct vb2_buffer *buf = vb2_bufs[FE_OUTPUT0_PAD + i];
+
+		if (!(cfg->global.enables & PISP_FE_ENABLE_OUTPUT(i)))
+			continue;
+
+		addr = vb2_dma_contig_plane_dma_addr(buf, 0);
+		cfg->output_buffer[i].addr_lo = addr & 0xffffffff;
+		cfg->output_buffer[i].addr_hi = addr >> 32;
+	}
+
+	if (vb2_bufs[FE_STATS_PAD]) {
+		addr = vb2_dma_contig_plane_dma_addr(vb2_bufs[FE_STATS_PAD], 0);
+		cfg->stats_buffer.addr_lo = addr & 0xffffffff;
+		cfg->stats_buffer.addr_hi = addr >> 32;
+	}
+
+	/* Set up ILINES interrupts 3/4 of the way down each output */
+	cfg->ch[0].output.ilines =
+		max(0x80u, (3u * cfg->ch[0].output.format.height) >> 2);
+	cfg->ch[1].output.ilines =
+		max(0x80u, (3u * cfg->ch[1].output.format.height) >> 2);
+
+	/*
+	 * The hardware must have consumed the previous config by now.
+	 * This read of status also serves as a memory barrier before the
+	 * sequence of relaxed writes which follow.
+	 */
+	status = pisp_fe_reg_read(fe, FE_STATUS);
+	pisp_fe_dbg_verbose("%s: status = 0x%x\n", __func__, status);
+	if (WARN_ON(status & FE_STATUS_QUEUED))
+		return;
+
+	/*
+	 * Unconditionally write buffers, global and input parameters.
+	 * Write cropping and output parameters whenever they are enabled.
+	 * Selectively write other parameters that have been marked as
+	 * changed through the dirty flags.
+	 */
+	pisp_config_write(fe, cfg, 0,
+			  offsetof(struct pisp_fe_config, decompress));
+	cfg->dirty_flags_extra &= ~PISP_FE_DIRTY_GLOBAL;
+	cfg->dirty_flags &= ~PISP_FE_ENABLE_INPUT;
+	cfg->dirty_flags |= (cfg->global.enables &
+			     (PISP_FE_ENABLE_STATS_CROP        |
+			      PISP_FE_ENABLE_OUTPUT_CLUSTER(0) |
+			      PISP_FE_ENABLE_OUTPUT_CLUSTER(1)));
+	for (i = 0; i < ARRAY_SIZE(pisp_fe_config_map); i++) {
+		const struct pisp_fe_config_param *p = &pisp_fe_config_map[i];
+
+		if (cfg->dirty_flags & p->dirty_flags ||
+		    cfg->dirty_flags_extra & p->dirty_flags_extra)
+			pisp_config_write(fe, cfg, p->offset, p->size);
+	}
+
+	/* This final non-relaxed write serves as a memory barrier */
+	pisp_fe_reg_write(fe, FE_CONTROL, FE_CONTROL_QUEUE);
+}
+
+void pisp_fe_start(struct pisp_fe_device *fe)
+{
+	pisp_fe_reg_write(fe, FE_CONTROL, FE_CONTROL_RESET);
+	pisp_fe_reg_write(fe, FE_INT_STATUS, ~0);
+	pisp_fe_reg_write(fe, FE_INT_EN, FE_INT_EOF | FE_INT_SOF | FE_INT_LINES0 | FE_INT_LINES1);
+	fe->inframe_count = 0;
+}
+
+void pisp_fe_stop(struct pisp_fe_device *fe)
+{
+	pisp_fe_reg_write(fe, FE_INT_EN, 0);
+	pisp_fe_reg_write(fe, FE_CONTROL, FE_CONTROL_ABORT);
+	usleep_range(1000, 2000);
+	WARN_ON(pisp_fe_reg_read(fe, FE_STATUS));
+	pisp_fe_reg_write(fe, FE_INT_STATUS, ~0);
+}
+
+static int pisp_fe_init_cfg(struct v4l2_subdev *sd,
+			    struct v4l2_subdev_state *state)
+{
+	struct v4l2_mbus_framefmt *fmt;
+
+	fmt = v4l2_subdev_state_get_format(state, FE_STREAM_PAD);
+	*fmt = cfe_default_format;
+	fmt->code = MEDIA_BUS_FMT_SRGGB16_1X16;
+
+	fmt = v4l2_subdev_state_get_format(state, FE_CONFIG_PAD);
+	*fmt = cfe_default_meta_format;
+	fmt->code = MEDIA_BUS_FMT_FIXED;
+
+	fmt = v4l2_subdev_state_get_format(state, FE_OUTPUT0_PAD);
+	*fmt = cfe_default_format;
+	fmt->code = MEDIA_BUS_FMT_SRGGB16_1X16;
+
+	fmt = v4l2_subdev_state_get_format(state, FE_OUTPUT1_PAD);
+	*fmt = cfe_default_format;
+	fmt->code = MEDIA_BUS_FMT_SRGGB16_1X16;
+
+	fmt = v4l2_subdev_state_get_format(state, FE_STATS_PAD);
+	*fmt = cfe_default_meta_format;
+	fmt->code = MEDIA_BUS_FMT_FIXED;
+
+	return 0;
+}
+
+static int pisp_fe_pad_set_fmt(struct v4l2_subdev *sd,
+			       struct v4l2_subdev_state *state,
+			       struct v4l2_subdev_format *format)
+{
+	struct v4l2_mbus_framefmt *fmt;
+	const struct cfe_fmt *cfe_fmt;
+
+	/* TODO: format propagation to source pads */
+	/* TODO: format validation */
+
+	switch (format->pad) {
+	case FE_STREAM_PAD:
+		cfe_fmt = find_format_by_code(format->format.code);
+		if (!cfe_fmt || !(cfe_fmt->flags & CFE_FORMAT_FLAG_FE_OUT))
+			cfe_fmt = find_format_by_code(MEDIA_BUS_FMT_SRGGB16_1X16);
+
+		format->format.code = cfe_fmt->code;
+		format->format.field = V4L2_FIELD_NONE;
+
+		fmt = v4l2_subdev_state_get_format(state, FE_STREAM_PAD);
+		*fmt = format->format;
+
+		fmt = v4l2_subdev_state_get_format(state, FE_OUTPUT0_PAD);
+		*fmt = format->format;
+
+		fmt = v4l2_subdev_state_get_format(state, FE_OUTPUT1_PAD);
+		*fmt = format->format;
+
+		return 0;
+
+	case FE_OUTPUT0_PAD:
+	case FE_OUTPUT1_PAD: {
+		/*
+		 * TODO: we should allow scaling and cropping by allowing the
+		 * user to set the size here.
+		 */
+		struct v4l2_mbus_framefmt *sink_fmt, *source_fmt;
+		u32 sink_code;
+		u32 code;
+
+		sink_fmt = v4l2_subdev_state_get_format(state, FE_STREAM_PAD);
+		if (!sink_fmt)
+			return -EINVAL;
+
+		source_fmt = v4l2_subdev_state_get_format(state, format->pad);
+		if (!source_fmt)
+			return -EINVAL;
+
+		sink_code = sink_fmt->code;
+		code = format->format.code;
+
+		/*
+		 * If the source code from the user does not match the code in
+		 * the sink pad, check that the source code matches the
+		 * compressed version of the sink code.
+		 */
+
+		if (code != sink_code &&
+		    code == cfe_find_compressed_code(sink_code))
+			source_fmt->code = code;
+
+		return 0;
+	}
+
+	case FE_CONFIG_PAD:
+	case FE_STATS_PAD:
+	default:
+		return v4l2_subdev_get_fmt(sd, state, format);
+	}
+}
+
+static const struct v4l2_subdev_internal_ops pisp_fe_internal_ops = {
+	.init_state = pisp_fe_init_cfg,
+};
+
+static const struct v4l2_subdev_pad_ops pisp_fe_subdev_pad_ops = {
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = pisp_fe_pad_set_fmt,
+	.link_validate = v4l2_subdev_link_validate_default,
+};
+
+static const struct media_entity_operations pisp_fe_entity_ops = {
+	.link_validate = v4l2_subdev_link_validate,
+};
+
+static const struct v4l2_subdev_ops pisp_fe_subdev_ops = {
+	.pad = &pisp_fe_subdev_pad_ops,
+};
+
+int pisp_fe_init(struct pisp_fe_device *fe, struct dentry *debugfs)
+{
+	int ret;
+
+	debugfs_create_file("pisp_regs", 0444, debugfs, fe, &pisp_regs_fops);
+
+	fe->hw_revision = pisp_fe_reg_read(fe, FE_VERSION);
+	pisp_fe_info("PiSP FE HW v%u.%u\n",
+		     (fe->hw_revision >> 24) & 0xff,
+		     (fe->hw_revision >> 20) & 0x0f);
+
+	fe->pad[FE_STREAM_PAD].flags =
+		MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
+	fe->pad[FE_CONFIG_PAD].flags = MEDIA_PAD_FL_SINK;
+	fe->pad[FE_OUTPUT0_PAD].flags = MEDIA_PAD_FL_SOURCE;
+	fe->pad[FE_OUTPUT1_PAD].flags = MEDIA_PAD_FL_SOURCE;
+	fe->pad[FE_STATS_PAD].flags = MEDIA_PAD_FL_SOURCE;
+
+	ret = media_entity_pads_init(&fe->sd.entity, ARRAY_SIZE(fe->pad),
+				     fe->pad);
+	if (ret)
+		return ret;
+
+	/* Initialize subdev */
+	v4l2_subdev_init(&fe->sd, &pisp_fe_subdev_ops);
+	fe->sd.internal_ops = &pisp_fe_internal_ops;
+	fe->sd.entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
+	fe->sd.entity.ops = &pisp_fe_entity_ops;
+	fe->sd.entity.name = "pisp-fe";
+	fe->sd.flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
+	fe->sd.owner = THIS_MODULE;
+	snprintf(fe->sd.name, sizeof(fe->sd.name), "pisp-fe");
+
+	ret = v4l2_subdev_init_finalize(&fe->sd);
+	if (ret)
+		goto err_entity_cleanup;
+
+	ret = v4l2_device_register_subdev(fe->v4l2_dev, &fe->sd);
+	if (ret) {
+		pisp_fe_err("Failed register pisp fe subdev (%d)\n", ret);
+		goto err_subdev_cleanup;
+	}
+
+	/* Must be in IDLE state (STATUS == 0) here. */
+	WARN_ON(pisp_fe_reg_read(fe, FE_STATUS));
+
+	return 0;
+
+err_subdev_cleanup:
+	v4l2_subdev_cleanup(&fe->sd);
+err_entity_cleanup:
+	media_entity_cleanup(&fe->sd.entity);
+
+	return ret;
+}
+
+void pisp_fe_uninit(struct pisp_fe_device *fe)
+{
+	v4l2_device_unregister_subdev(&fe->sd);
+	v4l2_subdev_cleanup(&fe->sd);
+	media_entity_cleanup(&fe->sd.entity);
+}
diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/pisp_fe.h b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_fe.h
new file mode 100644
index 000000000000..12760cb55af4
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_fe.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * PiSP Front End driver.
+ * Copyright (c) 2021 Raspberry Pi Ltd.
+ *
+ */
+#ifndef _PISP_FE_H_
+#define _PISP_FE_H_
+
+#include <linux/debugfs.h>
+#include <linux/io.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+
+#include <media/media-device.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+
+#include "pisp_fe_config.h"
+
+enum pisp_fe_pads {
+	FE_STREAM_PAD,
+	FE_CONFIG_PAD,
+	FE_OUTPUT0_PAD,
+	FE_OUTPUT1_PAD,
+	FE_STATS_PAD,
+	FE_NUM_PADS
+};
+
+struct pisp_fe_device {
+	/* Parent V4l2 device */
+	struct v4l2_device *v4l2_dev;
+	void __iomem *base;
+	u32 hw_revision;
+
+	u16 inframe_count;
+	struct media_pad pad[FE_NUM_PADS];
+	struct v4l2_subdev sd;
+};
+
+void pisp_fe_isr(struct pisp_fe_device *fe, bool *sof, bool *eof);
+int pisp_fe_validate_config(struct pisp_fe_device *fe,
+			    struct pisp_fe_config *cfg,
+			    struct v4l2_format const *f0,
+			    struct v4l2_format const *f1);
+void pisp_fe_submit_job(struct pisp_fe_device *fe, struct vb2_buffer **vb2_bufs,
+			struct pisp_fe_config *cfg);
+void pisp_fe_start(struct pisp_fe_device *fe);
+void pisp_fe_stop(struct pisp_fe_device *fe);
+int pisp_fe_init(struct pisp_fe_device *fe, struct dentry *debugfs);
+void pisp_fe_uninit(struct pisp_fe_device *fe);
+
+#endif
diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/pisp_fe_config.h b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_fe_config.h
new file mode 100644
index 000000000000..35ffda72986f
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_fe_config.h
@@ -0,0 +1,272 @@
+/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
+/*
+ * RP1 PiSP Front End Driver Configuration structures
+ *
+ * Copyright (C) 2021 - Raspberry Pi Ltd.
+ *
+ */
+#ifndef _PISP_FE_CONFIG_
+#define _PISP_FE_CONFIG_
+
+#include "pisp_common.h"
+
+#include "pisp_statistics.h"
+
+#define PISP_FE_NUM_OUTPUTS 2
+
+enum pisp_fe_enable {
+	PISP_FE_ENABLE_INPUT = 0x000001,
+	PISP_FE_ENABLE_DECOMPRESS = 0x000002,
+	PISP_FE_ENABLE_DECOMPAND = 0x000004,
+	PISP_FE_ENABLE_BLA = 0x000008,
+	PISP_FE_ENABLE_DPC = 0x000010,
+	PISP_FE_ENABLE_STATS_CROP = 0x000020,
+	PISP_FE_ENABLE_DECIMATE = 0x000040,
+	PISP_FE_ENABLE_BLC = 0x000080,
+	PISP_FE_ENABLE_CDAF_STATS = 0x000100,
+	PISP_FE_ENABLE_AWB_STATS = 0x000200,
+	PISP_FE_ENABLE_RGBY = 0x000400,
+	PISP_FE_ENABLE_LSC = 0x000800,
+	PISP_FE_ENABLE_AGC_STATS = 0x001000,
+	PISP_FE_ENABLE_CROP0 = 0x010000,
+	PISP_FE_ENABLE_DOWNSCALE0 = 0x020000,
+	PISP_FE_ENABLE_COMPRESS0 = 0x040000,
+	PISP_FE_ENABLE_OUTPUT0 = 0x080000,
+	PISP_FE_ENABLE_CROP1 = 0x100000,
+	PISP_FE_ENABLE_DOWNSCALE1 = 0x200000,
+	PISP_FE_ENABLE_COMPRESS1 = 0x400000,
+	PISP_FE_ENABLE_OUTPUT1 = 0x800000
+};
+
+#define PISP_FE_ENABLE_CROP(i) (PISP_FE_ENABLE_CROP0 << (4 * (i)))
+#define PISP_FE_ENABLE_DOWNSCALE(i) (PISP_FE_ENABLE_DOWNSCALE0 << (4 * (i)))
+#define PISP_FE_ENABLE_COMPRESS(i) (PISP_FE_ENABLE_COMPRESS0 << (4 * (i)))
+#define PISP_FE_ENABLE_OUTPUT(i) (PISP_FE_ENABLE_OUTPUT0 << (4 * (i)))
+
+/*
+ * We use the enable flags to show when blocks are "dirty", but we need some
+ * extra ones too.
+ */
+enum pisp_fe_dirty {
+	PISP_FE_DIRTY_GLOBAL = 0x0001,
+	PISP_FE_DIRTY_FLOATING = 0x0002,
+	PISP_FE_DIRTY_OUTPUT_AXI = 0x0004
+};
+
+struct pisp_fe_global_config {
+	u32 enables;
+	u8 bayer_order;
+	u8 pad[3];
+};
+
+struct pisp_fe_input_axi_config {
+	/* burst length minus one, in the range 0..15; OR'd with flags */
+	u8 maxlen_flags;
+	/* { prot[2:0], cache[3:0] } fields */
+	u8 cache_prot;
+	/* QoS (only 4 LS bits are used) */
+	u16 qos;
+};
+
+struct pisp_fe_output_axi_config {
+	/* burst length minus one, in the range 0..15; OR'd with flags */
+	u8 maxlen_flags;
+	/* { prot[2:0], cache[3:0] } fields */
+	u8 cache_prot;
+	/* QoS (4 bitfields of 4 bits each for different panic levels) */
+	u16 qos;
+	/*  For Panic mode: Output FIFO panic threshold */
+	u16 thresh;
+	/*  For Panic mode: Output FIFO statistics throttle threshold */
+	u16 throttle;
+};
+
+struct pisp_fe_input_config {
+	u8 streaming;
+	u8 pad[3];
+	struct pisp_image_format_config format;
+	struct pisp_fe_input_axi_config axi;
+	/* Extra cycles delay before issuing each burst request */
+	u8 holdoff;
+	u8 pad2[3];
+};
+
+struct pisp_fe_output_config {
+	struct pisp_image_format_config format;
+	u16 ilines;
+	u8 pad[2];
+};
+
+struct pisp_fe_input_buffer_config {
+	u32 addr_lo;
+	u32 addr_hi;
+	u16 frame_id;
+	u16 pad;
+};
+
+#define PISP_FE_DECOMPAND_LUT_SIZE 65
+
+struct pisp_fe_decompand_config {
+	u16 lut[PISP_FE_DECOMPAND_LUT_SIZE];
+	u16 pad;
+};
+
+struct pisp_fe_dpc_config {
+	u8 coeff_level;
+	u8 coeff_range;
+	u8 coeff_range2;
+#define PISP_FE_DPC_FLAG_FOLDBACK 1
+#define PISP_FE_DPC_FLAG_VFLAG 2
+	u8 flags;
+};
+
+#define PISP_FE_LSC_LUT_SIZE 16
+
+struct pisp_fe_lsc_config {
+	u8 shift;
+	u8 pad0;
+	u16 scale;
+	u16 centre_x;
+	u16 centre_y;
+	u16 lut[PISP_FE_LSC_LUT_SIZE];
+};
+
+struct pisp_fe_rgby_config {
+	u16 gain_r;
+	u16 gain_g;
+	u16 gain_b;
+	u8 maxflag;
+	u8 pad;
+};
+
+struct pisp_fe_agc_stats_config {
+	u16 offset_x;
+	u16 offset_y;
+	u16 size_x;
+	u16 size_y;
+	/* each weight only 4 bits */
+	u8 weights[PISP_AGC_STATS_NUM_ZONES / 2];
+	u16 row_offset_x;
+	u16 row_offset_y;
+	u16 row_size_x;
+	u16 row_size_y;
+	u8 row_shift;
+	u8 float_shift;
+	u8 pad1[2];
+};
+
+struct pisp_fe_awb_stats_config {
+	u16 offset_x;
+	u16 offset_y;
+	u16 size_x;
+	u16 size_y;
+	u8 shift;
+	u8 pad[3];
+	u16 r_lo;
+	u16 r_hi;
+	u16 g_lo;
+	u16 g_hi;
+	u16 b_lo;
+	u16 b_hi;
+};
+
+struct pisp_fe_floating_stats_region {
+	u16 offset_x;
+	u16 offset_y;
+	u16 size_x;
+	u16 size_y;
+};
+
+struct pisp_fe_floating_stats_config {
+	struct pisp_fe_floating_stats_region
+					regions[PISP_FLOATING_STATS_NUM_ZONES];
+};
+
+#define PISP_FE_CDAF_NUM_WEIGHTS 8
+
+struct pisp_fe_cdaf_stats_config {
+	u16 noise_constant;
+	u16 noise_slope;
+	u16 offset_x;
+	u16 offset_y;
+	u16 size_x;
+	u16 size_y;
+	u16 skip_x;
+	u16 skip_y;
+	u32 mode;
+};
+
+struct pisp_fe_stats_buffer_config {
+	u32 addr_lo;
+	u32 addr_hi;
+};
+
+struct pisp_fe_crop_config {
+	u16 offset_x;
+	u16 offset_y;
+	u16 width;
+	u16 height;
+};
+
+enum pisp_fe_downscale_flags {
+	DOWNSCALE_BAYER =
+		1, /* downscale the four Bayer components independently... */
+	DOWNSCALE_BIN =
+		2 /* ...without trying to preserve their spatial relationship */
+};
+
+struct pisp_fe_downscale_config {
+	u8 xin;
+	u8 xout;
+	u8 yin;
+	u8 yout;
+	u8 flags; /* enum pisp_fe_downscale_flags */
+	u8 pad[3];
+	u16 output_width;
+	u16 output_height;
+};
+
+struct pisp_fe_output_buffer_config {
+	u32 addr_lo;
+	u32 addr_hi;
+};
+
+/* Each of the two output channels/branches: */
+struct pisp_fe_output_branch_config {
+	struct pisp_fe_crop_config crop;
+	struct pisp_fe_downscale_config downscale;
+	struct pisp_compress_config compress;
+	struct pisp_fe_output_config output;
+	u32 pad;
+};
+
+/* And finally one to rule them all: */
+struct pisp_fe_config {
+	/* I/O configuration: */
+	struct pisp_fe_stats_buffer_config stats_buffer;
+	struct pisp_fe_output_buffer_config output_buffer[PISP_FE_NUM_OUTPUTS];
+	struct pisp_fe_input_buffer_config input_buffer;
+	/* processing configuration: */
+	struct pisp_fe_global_config global;
+	struct pisp_fe_input_config input;
+	struct pisp_decompress_config decompress;
+	struct pisp_fe_decompand_config decompand;
+	struct pisp_bla_config bla;
+	struct pisp_fe_dpc_config dpc;
+	struct pisp_fe_crop_config stats_crop;
+	u32 spare1; /* placeholder for future decimate configuration */
+	struct pisp_bla_config blc;
+	struct pisp_fe_rgby_config rgby;
+	struct pisp_fe_lsc_config lsc;
+	struct pisp_fe_agc_stats_config agc_stats;
+	struct pisp_fe_awb_stats_config awb_stats;
+	struct pisp_fe_cdaf_stats_config cdaf_stats;
+	struct pisp_fe_floating_stats_config floating_stats;
+	struct pisp_fe_output_axi_config output_axi;
+	struct pisp_fe_output_branch_config ch[PISP_FE_NUM_OUTPUTS];
+	/* non-register fields: */
+	u32 dirty_flags; /* these use pisp_fe_enable */
+	u32 dirty_flags_extra; /* these use pisp_fe_dirty */
+};
+
+#endif /* _PISP_FE_CONFIG_ */
diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/pisp_statistics.h b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_statistics.h
new file mode 100644
index 000000000000..d8d7fcb57fec
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_statistics.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
+/*
+ * RP1 PiSP Front End statistics definitions
+ *
+ * Copyright (C) 2021 - Raspberry Pi Ltd.
+ *
+ */
+#ifndef _PISP_FE_STATISTICS_H_
+#define _PISP_FE_STATISTICS_H_
+
+#define PISP_FLOATING_STATS_NUM_ZONES 4
+#define PISP_AGC_STATS_NUM_BINS 1024
+#define PISP_AGC_STATS_SIZE 16
+#define PISP_AGC_STATS_NUM_ZONES (PISP_AGC_STATS_SIZE * PISP_AGC_STATS_SIZE)
+#define PISP_AGC_STATS_NUM_ROW_SUMS 512
+
+struct pisp_agc_statistics_zone {
+	u64 Y_sum;
+	u32 counted;
+	u32 pad;
+};
+
+struct pisp_agc_statistics {
+	u32 row_sums[PISP_AGC_STATS_NUM_ROW_SUMS];
+	/*
+	 * 32-bits per bin means an image (just less than) 16384x16384 pixels
+	 * in size can weight every pixel from 0 to 15.
+	 */
+	u32 histogram[PISP_AGC_STATS_NUM_BINS];
+	struct pisp_agc_statistics_zone floating[PISP_FLOATING_STATS_NUM_ZONES];
+};
+
+#define PISP_AWB_STATS_SIZE 32
+#define PISP_AWB_STATS_NUM_ZONES (PISP_AWB_STATS_SIZE * PISP_AWB_STATS_SIZE)
+
+struct pisp_awb_statistics_zone {
+	u32 R_sum;
+	u32 G_sum;
+	u32 B_sum;
+	u32 counted;
+};
+
+struct pisp_awb_statistics {
+	struct pisp_awb_statistics_zone zones[PISP_AWB_STATS_NUM_ZONES];
+	struct pisp_awb_statistics_zone floating[PISP_FLOATING_STATS_NUM_ZONES];
+};
+
+#define PISP_CDAF_STATS_SIZE 8
+#define PISP_CDAF_STATS_NUM_FOMS (PISP_CDAF_STATS_SIZE * PISP_CDAF_STATS_SIZE)
+
+struct pisp_cdaf_statistics {
+	u64 foms[PISP_CDAF_STATS_NUM_FOMS];
+	u64 floating[PISP_FLOATING_STATS_NUM_ZONES];
+};
+
+struct pisp_statistics {
+	struct pisp_awb_statistics awb;
+	struct pisp_agc_statistics agc;
+	struct pisp_cdaf_statistics cdaf;
+};
+
+#endif /* _PISP_FE_STATISTICS_H_ */
diff --git a/drivers/media/platform/raspberrypi/rp1_cfe/pisp_types.h b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_types.h
new file mode 100644
index 000000000000..93d2d3c27a56
--- /dev/null
+++ b/drivers/media/platform/raspberrypi/rp1_cfe/pisp_types.h
@@ -0,0 +1,144 @@
+/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
+/*
+ * RP1 PiSP Front End image definitions.
+ *
+ * Copyright (C) 2021 - Raspberry Pi Ltd.
+ *
+ */
+#ifndef _PISP_FE_TYPES_H_
+#define _PISP_FE_TYPES_H_
+
+/* This definition must match the format description in the hardware exactly! */
+struct pisp_image_format_config {
+	/* size in pixels */
+	u16 width, height;
+	/* must match struct pisp_image_format below */
+	u32 format;
+	s32 stride;
+	/* some planar image formats will need a second stride */
+	s32 stride2;
+};
+
+static_assert(sizeof(struct pisp_image_format_config) == 16);
+
+enum pisp_bayer_order {
+	/*
+	 * Note how bayer_order&1 tells you if G is on the even pixels of the
+	 * checkerboard or not, and bayer_order&2 tells you if R is on the even
+	 * rows or is swapped with B. Note that if the top (of the 8) bits is
+	 * set, this denotes a monochrome or greyscale image, and the lower bits
+	 * should all be ignored.
+	 */
+	PISP_BAYER_ORDER_RGGB = 0,
+	PISP_BAYER_ORDER_GBRG = 1,
+	PISP_BAYER_ORDER_BGGR = 2,
+	PISP_BAYER_ORDER_GRBG = 3,
+	PISP_BAYER_ORDER_GREYSCALE = 128
+};
+
+enum pisp_image_format {
+	/*
+	 * Precise values are mostly tbd. Generally these will be portmanteau
+	 * values comprising bit fields and flags. This format must be shared
+	 * throughout the PiSP.
+	 */
+	PISP_IMAGE_FORMAT_BPS_8 = 0x00000000,
+	PISP_IMAGE_FORMAT_BPS_10 = 0x00000001,
+	PISP_IMAGE_FORMAT_BPS_12 = 0x00000002,
+	PISP_IMAGE_FORMAT_BPS_16 = 0x00000003,
+	PISP_IMAGE_FORMAT_BPS_MASK = 0x00000003,
+
+	PISP_IMAGE_FORMAT_PLANARITY_INTERLEAVED = 0x00000000,
+	PISP_IMAGE_FORMAT_PLANARITY_SEMI_PLANAR = 0x00000010,
+	PISP_IMAGE_FORMAT_PLANARITY_PLANAR = 0x00000020,
+	PISP_IMAGE_FORMAT_PLANARITY_MASK = 0x00000030,
+
+	PISP_IMAGE_FORMAT_SAMPLING_444 = 0x00000000,
+	PISP_IMAGE_FORMAT_SAMPLING_422 = 0x00000100,
+	PISP_IMAGE_FORMAT_SAMPLING_420 = 0x00000200,
+	PISP_IMAGE_FORMAT_SAMPLING_MASK = 0x00000300,
+
+	PISP_IMAGE_FORMAT_ORDER_NORMAL = 0x00000000,
+	PISP_IMAGE_FORMAT_ORDER_SWAPPED = 0x00001000,
+
+	PISP_IMAGE_FORMAT_SHIFT_0 = 0x00000000,
+	PISP_IMAGE_FORMAT_SHIFT_1 = 0x00010000,
+	PISP_IMAGE_FORMAT_SHIFT_2 = 0x00020000,
+	PISP_IMAGE_FORMAT_SHIFT_3 = 0x00030000,
+	PISP_IMAGE_FORMAT_SHIFT_4 = 0x00040000,
+	PISP_IMAGE_FORMAT_SHIFT_5 = 0x00050000,
+	PISP_IMAGE_FORMAT_SHIFT_6 = 0x00060000,
+	PISP_IMAGE_FORMAT_SHIFT_7 = 0x00070000,
+	PISP_IMAGE_FORMAT_SHIFT_8 = 0x00080000,
+	PISP_IMAGE_FORMAT_SHIFT_MASK = 0x000f0000,
+
+	PISP_IMAGE_FORMAT_UNCOMPRESSED = 0x00000000,
+	PISP_IMAGE_FORMAT_COMPRESSION_MODE_1 = 0x01000000,
+	PISP_IMAGE_FORMAT_COMPRESSION_MODE_2 = 0x02000000,
+	PISP_IMAGE_FORMAT_COMPRESSION_MODE_3 = 0x03000000,
+	PISP_IMAGE_FORMAT_COMPRESSION_MASK = 0x03000000,
+
+	PISP_IMAGE_FORMAT_HOG_SIGNED = 0x04000000,
+	PISP_IMAGE_FORMAT_HOG_UNSIGNED = 0x08000000,
+	PISP_IMAGE_FORMAT_INTEGRAL_IMAGE = 0x10000000,
+	PISP_IMAGE_FORMAT_WALLPAPER_ROLL = 0x20000000,
+	PISP_IMAGE_FORMAT_THREE_CHANNEL = 0x40000000,
+
+	/* Lastly a few specific instantiations of the above. */
+	PISP_IMAGE_FORMAT_SINGLE_16 = PISP_IMAGE_FORMAT_BPS_16,
+	PISP_IMAGE_FORMAT_THREE_16 =
+		PISP_IMAGE_FORMAT_BPS_16 | PISP_IMAGE_FORMAT_THREE_CHANNEL
+};
+
+#define PISP_IMAGE_FORMAT_bps_8(fmt)                                           \
+	(((fmt) & PISP_IMAGE_FORMAT_BPS_MASK) == PISP_IMAGE_FORMAT_BPS_8)
+#define PISP_IMAGE_FORMAT_bps_10(fmt)                                          \
+	(((fmt) & PISP_IMAGE_FORMAT_BPS_MASK) == PISP_IMAGE_FORMAT_BPS_10)
+#define PISP_IMAGE_FORMAT_bps_12(fmt)                                          \
+	(((fmt) & PISP_IMAGE_FORMAT_BPS_MASK) == PISP_IMAGE_FORMAT_BPS_12)
+#define PISP_IMAGE_FORMAT_bps_16(fmt)                                          \
+	(((fmt) & PISP_IMAGE_FORMAT_BPS_MASK) == PISP_IMAGE_FORMAT_BPS_16)
+#define PISP_IMAGE_FORMAT_bps(fmt)                                             \
+	(((fmt) & PISP_IMAGE_FORMAT_BPS_MASK) ?                                \
+		       8 + (2 << (((fmt) & PISP_IMAGE_FORMAT_BPS_MASK) - 1)) : \
+		       8)
+#define PISP_IMAGE_FORMAT_shift(fmt)                                           \
+	(((fmt) & PISP_IMAGE_FORMAT_SHIFT_MASK) / PISP_IMAGE_FORMAT_SHIFT_1)
+#define PISP_IMAGE_FORMAT_three_channel(fmt)                                   \
+	((fmt) & PISP_IMAGE_FORMAT_THREE_CHANNEL)
+#define PISP_IMAGE_FORMAT_single_channel(fmt)                                  \
+	(!((fmt) & PISP_IMAGE_FORMAT_THREE_CHANNEL))
+#define PISP_IMAGE_FORMAT_compressed(fmt)                                      \
+	(((fmt) & PISP_IMAGE_FORMAT_COMPRESSION_MASK) !=                       \
+	 PISP_IMAGE_FORMAT_UNCOMPRESSED)
+#define PISP_IMAGE_FORMAT_sampling_444(fmt)                                    \
+	(((fmt) & PISP_IMAGE_FORMAT_SAMPLING_MASK) ==                          \
+	 PISP_IMAGE_FORMAT_SAMPLING_444)
+#define PISP_IMAGE_FORMAT_sampling_422(fmt)                                    \
+	(((fmt) & PISP_IMAGE_FORMAT_SAMPLING_MASK) ==                          \
+	 PISP_IMAGE_FORMAT_SAMPLING_422)
+#define PISP_IMAGE_FORMAT_sampling_420(fmt)                                    \
+	(((fmt) & PISP_IMAGE_FORMAT_SAMPLING_MASK) ==                          \
+	 PISP_IMAGE_FORMAT_SAMPLING_420)
+#define PISP_IMAGE_FORMAT_order_normal(fmt)                                    \
+	(!((fmt) & PISP_IMAGE_FORMAT_ORDER_SWAPPED))
+#define PISP_IMAGE_FORMAT_order_swapped(fmt)                                   \
+	((fmt) & PISP_IMAGE_FORMAT_ORDER_SWAPPED)
+#define PISP_IMAGE_FORMAT_interleaved(fmt)                                     \
+	(((fmt) & PISP_IMAGE_FORMAT_PLANARITY_MASK) ==                         \
+	 PISP_IMAGE_FORMAT_PLANARITY_INTERLEAVED)
+#define PISP_IMAGE_FORMAT_semiplanar(fmt)                                      \
+	(((fmt) & PISP_IMAGE_FORMAT_PLANARITY_MASK) ==                         \
+	 PISP_IMAGE_FORMAT_PLANARITY_SEMI_PLANAR)
+#define PISP_IMAGE_FORMAT_planar(fmt)                                          \
+	(((fmt) & PISP_IMAGE_FORMAT_PLANARITY_MASK) ==                         \
+	 PISP_IMAGE_FORMAT_PLANARITY_PLANAR)
+#define PISP_IMAGE_FORMAT_wallpaper(fmt)                                       \
+	((fmt) & PISP_IMAGE_FORMAT_WALLPAPER_ROLL)
+#define PISP_IMAGE_FORMAT_HOG(fmt)                                             \
+	((fmt) &                                                               \
+	 (PISP_IMAGE_FORMAT_HOG_SIGNED | PISP_IMAGE_FORMAT_HOG_UNSIGNED))
+
+#define PISP_WALLPAPER_WIDTH 128 // in bytes
+
+#endif /* _PISP_FE_TYPES_H_ */
diff --git a/drivers/media/platform/video-mux.c b/drivers/media/platform/video-mux.c
index 31e9e92e723e..ccce867d5709 100644
--- a/drivers/media/platform/video-mux.c
+++ b/drivers/media/platform/video-mux.c
@@ -20,10 +20,27 @@
 #include <media/v4l2-mc.h>
 #include <media/v4l2-subdev.h>
 
+struct video_mux_asd {
+	struct v4l2_async_connection base;
+	unsigned int port;
+};
+
+static inline struct video_mux_asd *to_video_mux_asd(struct v4l2_async_connection *asd)
+{
+	return container_of(asd, struct video_mux_asd, base);
+}
+
+struct video_mux_pad_cfg {
+	unsigned int num_lanes;
+	bool non_continuous;
+	struct v4l2_subdev *source;
+};
+
 struct video_mux {
 	struct v4l2_subdev subdev;
 	struct v4l2_async_notifier notifier;
 	struct media_pad *pads;
+	struct video_mux_pad_cfg *cfg;
 	struct mux_control *mux;
 	struct mutex lock;
 	int active;
@@ -52,6 +69,8 @@ static int video_mux_link_setup(struct media_entity *entity,
 				const struct media_pad *remote, u32 flags)
 {
 	struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+	struct v4l2_subdev *source_sd;
+	struct v4l2_subdev_state *sd_state;
 	struct video_mux *vmux = v4l2_subdev_to_video_mux(sd);
 	u16 source_pad = entity->num_pads - 1;
 	int ret = 0;
@@ -67,10 +86,10 @@ static int video_mux_link_setup(struct media_entity *entity,
 		remote->entity->name, remote->index, local->entity->name,
 		local->index, flags & MEDIA_LNK_FL_ENABLED);
 
+	sd_state = v4l2_subdev_lock_and_get_active_state(sd);
 	mutex_lock(&vmux->lock);
 
 	if (flags & MEDIA_LNK_FL_ENABLED) {
-		struct v4l2_subdev_state *sd_state;
 		struct v4l2_mbus_framefmt *source_mbusformat;
 
 		if (vmux->active == local->index)
@@ -88,12 +107,14 @@ static int video_mux_link_setup(struct media_entity *entity,
 		vmux->active = local->index;
 
 		/* Propagate the active format to the source */
-		sd_state = v4l2_subdev_lock_and_get_active_state(sd);
 		source_mbusformat = v4l2_subdev_state_get_format(sd_state,
 								 source_pad);
 		*source_mbusformat = *v4l2_subdev_state_get_format(sd_state,
 								   vmux->active);
-		v4l2_subdev_unlock_state(sd_state);
+
+		source_sd = media_entity_to_v4l2_subdev(remote->entity);
+		vmux->subdev.ctrl_handler = source_sd->ctrl_handler;
+
 	} else {
 		if (vmux->active != local->index)
 			goto out;
@@ -101,10 +122,13 @@ static int video_mux_link_setup(struct media_entity *entity,
 		dev_dbg(sd->dev, "going inactive\n");
 		mux_control_deselect(vmux->mux);
 		vmux->active = -1;
+
+		vmux->subdev.ctrl_handler = NULL;
 	}
 
 out:
 	mutex_unlock(&vmux->lock);
+	v4l2_subdev_unlock_state(sd_state);
 	return ret;
 }
 
@@ -301,9 +325,33 @@ static int video_mux_init_state(struct v4l2_subdev *sd,
 	return 0;
 }
 
+static int video_mux_get_mbus_config(struct v4l2_subdev *sd,
+				     unsigned int pad,
+				     struct v4l2_mbus_config *cfg)
+{
+	struct video_mux *vmux = v4l2_subdev_to_video_mux(sd);
+	int ret;
+
+	ret = v4l2_subdev_call(vmux->cfg[vmux->active].source, pad, get_mbus_config,
+			       0, cfg);
+
+	if (ret != -ENOIOCTLCMD)
+		return ret;
+
+	cfg->type = V4L2_MBUS_CSI2_DPHY;
+	cfg->bus.mipi_csi2.num_data_lanes = vmux->cfg[vmux->active].num_lanes;
+
+	/* Support for non-continuous CSI-2 clock is missing in pdate mode */
+	if (vmux->cfg[vmux->active].non_continuous)
+		cfg->bus.mipi_csi2.flags |= V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
+
+	return 0;
+};
+
 static const struct v4l2_subdev_pad_ops video_mux_pad_ops = {
 	.get_fmt = v4l2_subdev_get_fmt,
 	.set_fmt = video_mux_set_format,
+	.get_mbus_config = video_mux_get_mbus_config,
 };
 
 static const struct v4l2_subdev_ops video_mux_subdev_ops = {
@@ -320,6 +368,9 @@ static int video_mux_notify_bound(struct v4l2_async_notifier *notifier,
 				  struct v4l2_async_connection *asd)
 {
 	struct video_mux *vmux = notifier_to_video_mux(notifier);
+	unsigned int port = to_video_mux_asd(asd)->port;
+
+	vmux->cfg[port].source = sd;
 
 	return v4l2_create_fwnode_links(sd, &vmux->subdev);
 }
@@ -337,7 +388,7 @@ static int video_mux_async_register(struct video_mux *vmux,
 	v4l2_async_subdev_nf_init(&vmux->notifier, &vmux->subdev);
 
 	for (i = 0; i < num_input_pads; i++) {
-		struct v4l2_async_connection *asd;
+		struct video_mux_asd *asd;
 		struct fwnode_handle *ep, *remote_ep;
 
 		ep = fwnode_graph_get_endpoint_by_id(
@@ -355,8 +406,7 @@ static int video_mux_async_register(struct video_mux *vmux,
 		fwnode_handle_put(remote_ep);
 
 		asd = v4l2_async_nf_add_fwnode_remote(&vmux->notifier, ep,
-						      struct v4l2_async_connection);
-
+						      struct video_mux_asd);
 		fwnode_handle_put(ep);
 
 		if (IS_ERR(asd)) {
@@ -365,6 +415,8 @@ static int video_mux_async_register(struct video_mux *vmux,
 			if (ret != -EEXIST)
 				goto err_nf_cleanup;
 		}
+
+		asd->port = i;
 	}
 
 	vmux->notifier.ops = &video_mux_notify_ops;
@@ -390,6 +442,9 @@ static int video_mux_probe(struct platform_device *pdev)
 {
 	struct device_node *np = pdev->dev.of_node;
 	struct device *dev = &pdev->dev;
+	struct v4l2_fwnode_endpoint fwnode_ep = {
+		.bus_type = V4L2_MBUS_CSI2_DPHY
+	};
 	struct device_node *ep;
 	struct video_mux *vmux;
 	unsigned int num_pads = 0;
@@ -437,10 +492,27 @@ static int video_mux_probe(struct platform_device *pdev)
 	if (!vmux->pads)
 		return -ENOMEM;
 
-	for (i = 0; i < num_pads; i++)
+	vmux->cfg = devm_kcalloc(dev, num_pads, sizeof(*vmux->cfg), GFP_KERNEL);
+	if (!vmux->cfg)
+		return -ENOMEM;
+
+	for (i = 0; i < num_pads; i++) {
 		vmux->pads[i].flags = (i < num_pads - 1) ? MEDIA_PAD_FL_SINK
 							 : MEDIA_PAD_FL_SOURCE;
 
+		ep = of_graph_get_endpoint_by_regs(pdev->dev.of_node, i, 0);
+		if (ep) {
+			ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &fwnode_ep);
+			if (!ret) {
+				/* Get number of data lanes */
+				vmux->cfg[i].num_lanes = fwnode_ep.bus.mipi_csi2.num_data_lanes;
+				vmux->cfg[i].non_continuous = fwnode_ep.bus.mipi_csi2.flags &
+							V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK;
+			}
+			of_node_put(ep);
+		}
+	}
+
 	vmux->subdev.entity.function = MEDIA_ENT_F_VID_MUX;
 	ret = media_entity_pads_init(&vmux->subdev.entity, num_pads,
 				     vmux->pads);
diff --git a/drivers/media/spi/Kconfig b/drivers/media/spi/Kconfig
index 4656afae5bb4..328ea94d14f9 100644
--- a/drivers/media/spi/Kconfig
+++ b/drivers/media/spi/Kconfig
@@ -9,6 +9,7 @@ menu "Media SPI Adapters"
 config CXD2880_SPI_DRV
 	tristate "Sony CXD2880 SPI support"
 	depends on DVB_CORE && SPI
+	select DVB_CXD2880 if MEDIA_SUBDRV_AUTOSELECT
 	default m if !MEDIA_SUBDRV_AUTOSELECT
 	help
 	  Choose if you would like to have SPI interface support for Sony CXD2880.
diff --git a/drivers/media/test-drivers/vicodec/vicodec-core.c b/drivers/media/test-drivers/vicodec/vicodec-core.c
index 846e90c06291..2870fa3b529c 100644
--- a/drivers/media/test-drivers/vicodec/vicodec-core.c
+++ b/drivers/media/test-drivers/vicodec/vicodec-core.c
@@ -446,8 +446,10 @@ static void device_run(void *priv)
 	ctx->comp_magic_cnt = 0;
 	ctx->comp_has_frame = false;
 	spin_unlock(ctx->lock);
-	if (ctx->is_stateless && src_req)
+	if (ctx->is_stateless && src_req) {
 		v4l2_ctrl_request_complete(src_req, &ctx->hdl);
+		media_request_manual_complete(src_req);
+	}
 
 	if (ctx->is_enc)
 		v4l2_m2m_job_finish(dev->stateful_enc.m2m_dev, ctx->fh.m2m_ctx);
@@ -1523,8 +1525,12 @@ static void vicodec_return_bufs(struct vb2_queue *q, u32 state)
 			vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
 		if (vbuf == NULL)
 			return;
-		v4l2_ctrl_request_complete(vbuf->vb2_buf.req_obj.req,
-					   &ctx->hdl);
+		if (ctx->is_stateless && V4L2_TYPE_IS_OUTPUT(q->type)) {
+			struct media_request *req = vbuf->vb2_buf.req_obj.req;
+
+			v4l2_ctrl_request_complete(req, &ctx->hdl);
+			media_request_manual_complete(req);
+		}
 		spin_lock(ctx->lock);
 		v4l2_m2m_buf_done(vbuf, state);
 		spin_unlock(ctx->lock);
@@ -1677,6 +1683,7 @@ static void vicodec_buf_request_complete(struct vb2_buffer *vb)
 	struct vicodec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
 
 	v4l2_ctrl_request_complete(vb->req_obj.req, &ctx->hdl);
+	media_request_manual_complete(vb->req_obj.req);
 }
 
 
@@ -2001,6 +2008,12 @@ static int vicodec_request_validate(struct media_request *req)
 	return vb2_request_validate(req);
 }
 
+static void vicodec_request_queue(struct media_request *req)
+{
+	media_request_mark_manual_completion(req);
+	v4l2_m2m_request_queue(req);
+}
+
 static const struct v4l2_file_operations vicodec_fops = {
 	.owner		= THIS_MODULE,
 	.open		= vicodec_open,
@@ -2021,7 +2034,7 @@ static const struct video_device vicodec_videodev = {
 
 static const struct media_device_ops vicodec_m2m_media_ops = {
 	.req_validate	= vicodec_request_validate,
-	.req_queue	= v4l2_m2m_request_queue,
+	.req_queue	= vicodec_request_queue,
 };
 
 static const struct v4l2_m2m_ops m2m_ops = {
diff --git a/drivers/media/usb/dvb-usb-v2/rtl28xxu.c b/drivers/media/usb/dvb-usb-v2/rtl28xxu.c
index f7884bb56fcc..b0a2fd7e13aa 100644
--- a/drivers/media/usb/dvb-usb-v2/rtl28xxu.c
+++ b/drivers/media/usb/dvb-usb-v2/rtl28xxu.c
@@ -1964,6 +1964,10 @@ static const struct usb_device_id rtl28xxu_id_table[] = {
 		&rtl28xxu_props, "Compro VideoMate U650F", NULL) },
 	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, 0xd394,
 		&rtl28xxu_props, "MaxMedia HU394-T", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_GTEK, 0xb803 /*USB_PID_AUGUST_DVBT205*/,
+		&rtl28xxu_props, "August DVB-T 205", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_GTEK, 0xa803 /*USB_PID_AUGUST_DVBT205*/,
+		&rtl28xxu_props, "August DVB-T 205", NULL) },
 	{ DVB_USB_DEVICE(USB_VID_LEADTEK, 0x6a03,
 		&rtl28xxu_props, "Leadtek WinFast DTV Dongle mini", NULL) },
 	{ DVB_USB_DEVICE(USB_VID_GTEK, USB_PID_CPYTO_REDI_PC50A,
diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c
index 1ea52011247a..e7a1820c4969 100644
--- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c
+++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c
@@ -228,6 +228,7 @@ const char * const *v4l2_ctrl_get_menu(u32 id)
 		"Flash",
 		"Cloudy",
 		"Shade",
+		"Greyworld",
 		NULL,
 	};
 	static const char * const camera_iso_sensitivity_auto[] = {
diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c
index 0f5c3e286aa1..f7db4a052f17 100644
--- a/drivers/media/v4l2-core/v4l2-ioctl.c
+++ b/drivers/media/v4l2-core/v4l2-ioctl.c
@@ -1369,6 +1369,8 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt)
 	case V4L2_PIX_FMT_NV12_4L4:	descr = "Y/UV 4:2:0 (4x4 Linear)"; break;
 	case V4L2_PIX_FMT_NV12_16L16:	descr = "Y/UV 4:2:0 (16x16 Linear)"; break;
 	case V4L2_PIX_FMT_NV12_32L32:   descr = "Y/UV 4:2:0 (32x32 Linear)"; break;
+	case V4L2_PIX_FMT_NV12_COL128: descr = "Y/CbCr 4:2:0 (128b cols)"; break;
+	case V4L2_PIX_FMT_NV12_10_COL128: descr = "10-bit Y/CbCr 4:2:0 (128b cols)"; break;
 	case V4L2_PIX_FMT_NV15_4L4:	descr = "10-bit Y/UV 4:2:0 (4x4 Linear)"; break;
 	case V4L2_PIX_FMT_P010_4L4:	descr = "10-bit Y/UV 4:2:0 (4x4 Linear)"; break;
 	case V4L2_PIX_FMT_NV12M:	descr = "Y/UV 4:2:0 (N-C)"; break;
@@ -1376,7 +1378,9 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt)
 	case V4L2_PIX_FMT_NV16M:	descr = "Y/UV 4:2:2 (N-C)"; break;
 	case V4L2_PIX_FMT_NV61M:	descr = "Y/VU 4:2:2 (N-C)"; break;
 	case V4L2_PIX_FMT_NV12MT:	descr = "Y/UV 4:2:0 (64x32 MB, N-C)"; break;
+	case V4L2_PIX_FMT_NV12MT_COL128: descr = "Y/CbCr 4:2:0 (128b cols)"; break;
 	case V4L2_PIX_FMT_NV12MT_16X16:	descr = "Y/UV 4:2:0 (16x16 MB, N-C)"; break;
+	case V4L2_PIX_FMT_NV12MT_10_COL128: descr = "10-bit Y/CbCr 4:2:0 (128b cols)"; break;
 	case V4L2_PIX_FMT_P012M:	descr = "12-bit Y/UV 4:2:0 (N-C)"; break;
 	case V4L2_PIX_FMT_YUV420M:	descr = "Planar YUV 4:2:0 (N-C)"; break;
 	case V4L2_PIX_FMT_YVU420M:	descr = "Planar YVU 4:2:0 (N-C)"; break;
@@ -1474,6 +1478,10 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt)
 	case V4L2_META_FMT_GENERIC_CSI2_16:	descr = "8-bit Generic Meta, 16b CSI-2"; break;
 	case V4L2_META_FMT_GENERIC_CSI2_20:	descr = "8-bit Generic Meta, 20b CSI-2"; break;
 	case V4L2_META_FMT_GENERIC_CSI2_24:	descr = "8-bit Generic Meta, 24b CSI-2"; break;
+	case V4L2_META_FMT_SENSOR_DATA:	descr = "Sensor Ancillary Metadata"; break;
+	case V4L2_META_FMT_BCM2835_ISP_STATS: descr = "BCM2835 ISP Image Statistics"; break;
+	case V4L2_META_FMT_RPI_FE_CFG: descr = "PiSP FE Config format"; break;
+	case V4L2_META_FMT_RPI_FE_STATS: descr = "PiSP FE Statistics format"; break;
 
 	default:
 		/* Compressed formats */
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index f08b8009eeea..6783743237a2 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -1210,6 +1210,16 @@ config MFD_SY7636A
 	  To enable support for building sub-devices as modules,
 	  choose M here.
 
+config MFD_RASPBERRYPI_POE_HAT
+	tristate "Raspberry Pi PoE HAT MFD"
+	depends on I2C
+	select MFD_SIMPLE_MFD_I2C
+	help
+	  This module supports the PWM fan controller found on the Raspberry Pi
+	  POE and POE+ HAT boards, and the power supply driver on the POE+ HAT.
+	  (Functionally it relies on MFD_SIMPLE_MFD_I2C to provide the framework
+	  that loads the child drivers).
+
 config MFD_RDC321X
 	tristate "RDC R-321x southbridge"
 	select MFD_CORE
@@ -2398,6 +2408,18 @@ config MFD_INTEL_M10_BMC_PMCI
 	  additional drivers must be enabled in order to use the functionality
 	  of the device.
 
+config MFD_RP1
+	tristate "RP1 MFD driver"
+	depends on PCI
+	depends on PCI_MSI
+	select MFD_CORE
+	help
+	  Support for the RP1 peripheral chip.
+
+	  This driver provides support for the Raspberry Pi RP1 peripheral chip.
+	  It is responsible for enabling the Device Tree node once the PCIe endpoint
+	  has been configured, and handling interrupts.
+
 config MFD_RSMU_I2C
 	tristate "Renesas Synchronization Management Unit with I2C"
 	depends on I2C && OF
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 2a9f91e81af8..de7e9f11b306 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -289,3 +289,5 @@ obj-$(CONFIG_MFD_ATC260X_I2C)	+= atc260x-i2c.o
 
 obj-$(CONFIG_MFD_RSMU_I2C)	+= rsmu_i2c.o rsmu_core.o
 obj-$(CONFIG_MFD_RSMU_SPI)	+= rsmu_spi.o rsmu_core.o
+
+obj-$(CONFIG_MFD_RP1)		+= rp1.o
diff --git a/drivers/mfd/bcm2835-pm.c b/drivers/mfd/bcm2835-pm.c
index 3cb2b9423121..8b31775da7b6 100644
--- a/drivers/mfd/bcm2835-pm.c
+++ b/drivers/mfd/bcm2835-pm.c
@@ -69,12 +69,30 @@ static int bcm2835_pm_get_pdata(struct platform_device *pdev,
 	return 0;
 }
 
+static const struct of_device_id bcm2835_pm_of_match[] = {
+	{ .compatible = "brcm,bcm2835-pm-wdt", },
+	{ .compatible = "brcm,bcm2835-pm", },
+	{ .compatible = "brcm,bcm2711-pm", },
+	{ .compatible = "brcm,bcm2712-pm", .data = (const void *)1},
+	{},
+};
+MODULE_DEVICE_TABLE(of, bcm2835_pm_of_match);
+
 static int bcm2835_pm_probe(struct platform_device *pdev)
 {
+	const struct of_device_id *of_id;
 	struct device *dev = &pdev->dev;
 	struct bcm2835_pm *pm;
+	bool is_2712;
 	int ret;
 
+	of_id = of_match_node(bcm2835_pm_of_match, pdev->dev.of_node);
+	if (!of_id) {
+		dev_err(&pdev->dev, "Failed to match compatible string\n");
+		return -EINVAL;
+	}
+	is_2712 = !!of_id->data;
+
 	pm = devm_kzalloc(dev, sizeof(*pm), GFP_KERNEL);
 	if (!pm)
 		return -ENOMEM;
@@ -97,21 +115,13 @@ static int bcm2835_pm_probe(struct platform_device *pdev)
 	 * bcm2835-pm binding as the key for whether we can reference
 	 * the full PM register range and support power domains.
 	 */
-	if (pm->asb)
+	if (pm->asb || is_2712)
 		return devm_mfd_add_devices(dev, -1, bcm2835_power_devs,
 					    ARRAY_SIZE(bcm2835_power_devs),
 					    NULL, 0, NULL);
 	return 0;
 }
 
-static const struct of_device_id bcm2835_pm_of_match[] = {
-	{ .compatible = "brcm,bcm2835-pm-wdt", },
-	{ .compatible = "brcm,bcm2835-pm", },
-	{ .compatible = "brcm,bcm2711-pm", },
-	{},
-};
-MODULE_DEVICE_TABLE(of, bcm2835_pm_of_match);
-
 static struct platform_driver bcm2835_pm_driver = {
 	.probe		= bcm2835_pm_probe,
 	.driver = {
diff --git a/drivers/mfd/rp1.c b/drivers/mfd/rp1.c
new file mode 100644
index 000000000000..af0047ee3b96
--- /dev/null
+++ b/drivers/mfd/rp1.c
@@ -0,0 +1,375 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2018-22 Raspberry Pi Ltd.
+ * All rights reserved.
+ */
+
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/completion.h>
+#include <linux/etherdevice.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/mfd/core.h>
+#include <linux/mmc/host.h>
+#include <linux/module.h>
+#include <linux/msi.h>
+#include <linux/of_platform.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/rp1_platform.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/mfd/rp1.h>
+
+/* TO DO:
+ * 1. Occasional shutdown crash - RP1 being closed before its children?
+ * 2. DT mode interrupt handling.
+ */
+
+#define RP1_DRIVER_NAME "rp1"
+
+#define PCI_VENDOR_ID_RPI 0x1de4
+#define PCI_DEVICE_ID_RP1_C0 0x0001
+#define PCI_DEVICE_REV_RP1_C0 2
+
+#define RP1_ACTUAL_IRQS		RP1_INT_END
+#define RP1_IRQS		RP1_ACTUAL_IRQS
+
+#define RP1_SYSCLK_RATE		200000000
+#define RP1_SYSCLK_FPGA_RATE	60000000
+
+// Don't want to include the whole sysinfo reg header
+#define SYSINFO_CHIP_ID_OFFSET	0x00000000
+#define SYSINFO_PLATFORM_OFFSET	0x00000004
+
+#define REG_RW          0x000
+#define REG_SET         0x800
+#define REG_CLR         0xc00
+
+// MSIX CFG registers start at 0x8
+#define MSIX_CFG(x) (0x8 + (4 * (x)))
+
+#define MSIX_CFG_IACK_EN        BIT(3)
+#define MSIX_CFG_IACK           BIT(2)
+#define MSIX_CFG_TEST           BIT(1)
+#define MSIX_CFG_ENABLE         BIT(0)
+
+#define INTSTATL		0x108
+#define INTSTATH		0x10c
+
+struct rp1_dev {
+	struct pci_dev *pdev;
+	struct device *dev;
+	resource_size_t bar_start;
+	resource_size_t bar_end;
+	struct clk *sys_clk;
+	struct irq_domain *domain;
+	struct irq_data *pcie_irqds[64];
+	void __iomem *msix_cfg_regs;
+};
+
+static bool rp1_level_triggered_irq[RP1_ACTUAL_IRQS] = { 0 };
+
+static struct rp1_dev *g_rp1;
+static u32 g_chip_id, g_platform;
+
+static void dump_bar(struct pci_dev *pdev, unsigned int bar)
+{
+	dev_info(&pdev->dev,
+		 "bar%d len 0x%llx, start 0x%llx, end 0x%llx, flags, 0x%lx\n",
+		 bar,
+		 pci_resource_len(pdev, bar),
+		 pci_resource_start(pdev, bar),
+		 pci_resource_end(pdev, bar),
+		 pci_resource_flags(pdev, bar));
+}
+
+static void msix_cfg_set(struct rp1_dev *rp1, unsigned int hwirq, u32 value)
+{
+	writel(value, rp1->msix_cfg_regs + REG_SET + MSIX_CFG(hwirq));
+}
+
+static void msix_cfg_clr(struct rp1_dev *rp1, unsigned int hwirq, u32 value)
+{
+	writel(value, rp1->msix_cfg_regs + REG_CLR + MSIX_CFG(hwirq));
+}
+
+static void rp1_mask_irq(struct irq_data *irqd)
+{
+	struct rp1_dev *rp1 = irqd->domain->host_data;
+	struct irq_data *pcie_irqd = rp1->pcie_irqds[irqd->hwirq];
+
+	pci_msi_mask_irq(pcie_irqd);
+}
+
+static void rp1_unmask_irq(struct irq_data *irqd)
+{
+	struct rp1_dev *rp1 = irqd->domain->host_data;
+	struct irq_data *pcie_irqd = rp1->pcie_irqds[irqd->hwirq];
+
+	pci_msi_unmask_irq(pcie_irqd);
+}
+
+static int rp1_irq_set_type(struct irq_data *irqd, unsigned int type)
+{
+	struct rp1_dev *rp1 = irqd->domain->host_data;
+	unsigned int hwirq = (unsigned int)irqd->hwirq;
+	int ret = 0;
+
+	switch (type) {
+	case IRQ_TYPE_LEVEL_HIGH:
+		dev_dbg(rp1->dev, "MSIX IACK EN for irq %d\n", hwirq);
+		msix_cfg_set(rp1, hwirq, MSIX_CFG_IACK_EN);
+		rp1_level_triggered_irq[hwirq] = true;
+	break;
+	case IRQ_TYPE_EDGE_RISING:
+		msix_cfg_clr(rp1, hwirq, MSIX_CFG_IACK_EN);
+		rp1_level_triggered_irq[hwirq] = false;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int rp1_irq_set_affinity(struct irq_data *irqd, const struct cpumask *dest, bool force)
+{
+	struct rp1_dev *rp1 = irqd->domain->host_data;
+	struct irq_data *pcie_irqd = rp1->pcie_irqds[irqd->hwirq];
+
+	return msi_domain_set_affinity(pcie_irqd, dest, force);
+}
+
+static struct irq_chip rp1_irq_chip = {
+	.name            = "rp1_irq_chip",
+	.irq_mask        = rp1_mask_irq,
+	.irq_unmask      = rp1_unmask_irq,
+	.irq_set_type    = rp1_irq_set_type,
+	.irq_set_affinity = rp1_irq_set_affinity,
+};
+
+static void rp1_chained_handle_irq(struct irq_desc *desc)
+{
+	struct irq_chip *chip = irq_desc_get_chip(desc);
+	struct rp1_dev *rp1 = desc->irq_data.chip_data;
+	unsigned int hwirq = desc->irq_data.hwirq & 0x3f;
+	int new_irq;
+
+	rp1 = g_rp1;
+
+	chained_irq_enter(chip, desc);
+
+	new_irq = irq_linear_revmap(rp1->domain, hwirq);
+	generic_handle_irq(new_irq);
+	if (rp1_level_triggered_irq[hwirq])
+		msix_cfg_set(rp1, hwirq, MSIX_CFG_IACK);
+
+	chained_irq_exit(chip, desc);
+}
+
+static int rp1_irq_xlate(struct irq_domain *d, struct device_node *node,
+			 const u32 *intspec, unsigned int intsize,
+			 unsigned long *out_hwirq, unsigned int *out_type)
+{
+	struct rp1_dev *rp1 = d->host_data;
+	struct irq_data *pcie_irqd;
+	unsigned long hwirq;
+	int pcie_irq;
+	int ret;
+
+	ret = irq_domain_xlate_twocell(d, node, intspec, intsize,
+				       &hwirq, out_type);
+	if (!ret) {
+		pcie_irq = pci_irq_vector(rp1->pdev, hwirq);
+		pcie_irqd = irq_get_irq_data(pcie_irq);
+		rp1->pcie_irqds[hwirq] = pcie_irqd;
+		*out_hwirq = hwirq;
+	}
+	return ret;
+}
+
+static int rp1_irq_activate(struct irq_domain *d, struct irq_data *irqd,
+			    bool reserve)
+{
+	struct rp1_dev *rp1 = d->host_data;
+	struct irq_data *pcie_irqd;
+
+	pcie_irqd = rp1->pcie_irqds[irqd->hwirq];
+	msix_cfg_set(rp1, (unsigned int)irqd->hwirq, MSIX_CFG_ENABLE);
+	return irq_domain_activate_irq(pcie_irqd, reserve);
+}
+
+static void rp1_irq_deactivate(struct irq_domain *d, struct irq_data *irqd)
+{
+	struct rp1_dev *rp1 = d->host_data;
+	struct irq_data *pcie_irqd;
+
+	pcie_irqd = rp1->pcie_irqds[irqd->hwirq];
+	msix_cfg_clr(rp1, (unsigned int)irqd->hwirq, MSIX_CFG_ENABLE);
+	return irq_domain_deactivate_irq(pcie_irqd);
+}
+
+static const struct irq_domain_ops rp1_domain_ops = {
+	.xlate      = rp1_irq_xlate,
+	.activate   = rp1_irq_activate,
+	.deactivate = rp1_irq_deactivate,
+};
+
+static inline dma_addr_t rp1_io_to_phys(struct rp1_dev *rp1, unsigned int offset)
+{
+	return rp1->bar_start + offset;
+}
+
+static u32 rp1_reg_read(struct rp1_dev *rp1, unsigned int base_addr, u32 offset)
+{
+	dma_addr_t phys = rp1_io_to_phys(rp1, base_addr);
+	void __iomem *regblock = ioremap(phys, 0x1000);
+	u32 value = readl(regblock + offset);
+
+	iounmap(regblock);
+	return value;
+}
+
+void rp1_get_platform(u32 *chip_id, u32 *platform)
+{
+	if (chip_id)
+		*chip_id = g_chip_id;
+	if (platform)
+		*platform = g_platform;
+}
+EXPORT_SYMBOL_GPL(rp1_get_platform);
+
+static int rp1_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+	struct reset_control *reset;
+	struct platform_device *pcie_pdev;
+	struct device_node *rp1_node;
+	struct rp1_dev *rp1;
+	int err  = 0;
+	int i;
+
+	reset = devm_reset_control_get_optional_exclusive(&pdev->dev, NULL);
+	if (IS_ERR(reset))
+		return PTR_ERR(reset);
+	reset_control_reset(reset);
+
+	dump_bar(pdev, 0);
+	dump_bar(pdev, 1);
+
+	if (pci_resource_len(pdev, 1) <= 0x10000) {
+		dev_err(&pdev->dev,
+			"Not initialised - is the firmware running?\n");
+		return -EINVAL;
+	}
+
+	/* enable pci device */
+	err = pcim_enable_device(pdev);
+	if (err < 0) {
+		dev_err(&pdev->dev, "Enabling PCI device has failed: %d",
+			err);
+		return err;
+	}
+
+	pci_set_master(pdev);
+
+	err = pci_alloc_irq_vectors(pdev, RP1_IRQS, RP1_IRQS,
+				    PCI_IRQ_MSIX);
+	if (err != RP1_IRQS) {
+		dev_err(&pdev->dev, "pci_alloc_irq_vectors failed - %d\n", err);
+		return err;
+	}
+
+	rp1 = devm_kzalloc(&pdev->dev, sizeof(*rp1), GFP_KERNEL);
+	if (!rp1)
+		return -ENOMEM;
+
+	rp1->pdev = pdev;
+	rp1->dev = &pdev->dev;
+
+	pci_set_drvdata(pdev, rp1);
+
+	rp1->bar_start = pci_resource_start(pdev, 1);
+	rp1->bar_end = pci_resource_end(pdev, 1);
+
+	// Get chip id
+	g_chip_id = rp1_reg_read(rp1, RP1_SYSINFO_BASE, SYSINFO_CHIP_ID_OFFSET);
+	g_platform = rp1_reg_read(rp1, RP1_SYSINFO_BASE, SYSINFO_PLATFORM_OFFSET);
+	dev_info(&pdev->dev, "chip_id 0x%x%s\n", g_chip_id,
+		 (g_platform & RP1_PLATFORM_FPGA) ? " FPGA" : "");
+	if (g_chip_id != RP1_C0_CHIP_ID) {
+		dev_err(&pdev->dev, "wrong chip id (%x)\n", g_chip_id);
+		return -EINVAL;
+	}
+
+	rp1_node = of_find_node_by_name(NULL, "rp1");
+	if (!rp1_node) {
+		dev_err(&pdev->dev, "failed to find RP1 DT node\n");
+		return -EINVAL;
+	}
+
+	pcie_pdev = of_find_device_by_node(rp1_node->parent);
+	rp1->domain = irq_domain_add_linear(rp1_node, RP1_IRQS,
+					    &rp1_domain_ops, rp1);
+
+	g_rp1 = rp1;
+
+	/* TODO can this go in the rp1 device tree entry? */
+	rp1->msix_cfg_regs = ioremap(rp1_io_to_phys(rp1, RP1_PCIE_APBS_BASE), 0x1000);
+
+	for (i = 0; i < RP1_IRQS; i++) {
+		int irq = irq_create_mapping(rp1->domain, i);
+
+		if (irq < 0) {
+			dev_err(&pdev->dev, "failed to create irq mapping\n");
+			return irq;
+		}
+
+		irq_set_chip_data(irq, rp1);
+		irq_set_chip_and_handler(irq, &rp1_irq_chip, handle_level_irq);
+		irq_set_probe(irq);
+		irq_set_chained_handler(pci_irq_vector(pdev, i),
+					rp1_chained_handle_irq);
+	}
+
+	of_node_to_fwnode(rp1_node)->dev = rp1->dev;
+	of_platform_populate(rp1_node, NULL, NULL, &pcie_pdev->dev);
+	of_node_put(rp1_node);
+
+	return 0;
+}
+
+static void rp1_remove(struct pci_dev *pdev)
+{
+	struct rp1_dev *rp1 = pci_get_drvdata(pdev);
+
+	mfd_remove_devices(&pdev->dev);
+
+	clk_unregister(rp1->sys_clk);
+}
+
+static const struct pci_device_id dev_id_table[] = {
+	{ PCI_DEVICE(PCI_VENDOR_ID_RPI, PCI_DEVICE_ID_RP1_C0), },
+	{ 0, }
+};
+
+static struct pci_driver rp1_driver = {
+	.name		= RP1_DRIVER_NAME,
+	.id_table	= dev_id_table,
+	.probe		= rp1_probe,
+	.remove		= rp1_remove,
+};
+
+module_pci_driver(rp1_driver);
+
+MODULE_AUTHOR("Phil Elwell <phil@raspberrypi.com>");
+MODULE_DESCRIPTION("RP1 wrapper");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/simple-mfd-i2c.c b/drivers/mfd/simple-mfd-i2c.c
index 908eae338fee..1c108e0ae6a3 100644
--- a/drivers/mfd/simple-mfd-i2c.c
+++ b/drivers/mfd/simple-mfd-i2c.c
@@ -29,6 +29,15 @@ static const struct regmap_config regmap_config_8r_8v = {
 	.val_bits = 8,
 };
 
+static const struct regmap_config regmap_config_16r_8v = {
+	.reg_bits = 16,
+	.val_bits = 8,
+};
+
+static const struct simple_mfd_data rpi_poe_core = {
+	.regmap_config = &regmap_config_16r_8v,
+};
+
 static int simple_mfd_i2c_probe(struct i2c_client *i2c)
 {
 	const struct simple_mfd_data *simple_mfd_data;
@@ -117,6 +126,8 @@ static const struct of_device_id simple_mfd_i2c_of_match[] = {
 	{ .compatible = "maxim,max5970", .data = &maxim_max5970},
 	{ .compatible = "maxim,max5978", .data = &maxim_max5970},
 	{ .compatible = "maxim,max77705-battery", .data = &maxim_mon_max77705},
+	{ .compatible = "raspberrypi,poe-core", &rpi_poe_core },
+	{ .compatible = "raspberrypi,sensehat" },
 	{ .compatible = "silergy,sy7636a", .data = &silergy_sy7636a},
 	{ .compatible = "spacemit,p1", .data = &spacemit_p1, },
 	{}
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 3fe7e2a9bd29..3cd5e67fb0d1 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -9,6 +9,32 @@ config SENSORS_LIS3LV02D
 	tristate
 	depends on INPUT
 
+config BCM2835_SMI
+	tristate "Broadcom 283x Secondary Memory Interface driver"
+	depends on ARCH_BCM2835
+	default m
+	help
+		Driver for enabling and using Broadcom's Secondary/Slow Memory Interface.
+		Appears as /dev/bcm2835_smi. For ioctl interface see drivers/misc/bcm2835_smi.h
+
+config RP1_PIO
+	tristate "Raspberry Pi RP1 PIO driver"
+	depends on FIRMWARE_RP1 || COMPILE_TEST
+	default n
+	help
+		Driver providing control of the Raspberry Pi PIO block, as found in
+		RP1.
+
+config WS2812_PIO_RP1
+	tristate "Raspberry Pi PIO-base WS2812 driver"
+	depends on RP1_PIO || COMPILE_TEST
+	default n
+	help
+		Driver for the WS2812 (NeoPixel) LEDs using the RP1 PIO hardware.
+		The driver creates a character device to which rgbw pixels may be
+		written. Single-byte writes to offset 0 set the brightness at
+		runtime.
+
 config AD525X_DPOT
 	tristate "Analog Devices Digital Potentiometers"
 	depends on (I2C || SPI) && SYSFS
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index a9f94525e181..d3f355d263b4 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_AD525X_DPOT)	+= ad525x_dpot.o
 obj-$(CONFIG_AD525X_DPOT_I2C)	+= ad525x_dpot-i2c.o
 obj-$(CONFIG_AD525X_DPOT_SPI)	+= ad525x_dpot-spi.o
 obj-$(CONFIG_ATMEL_SSC)		+= atmel-ssc.o
+obj-$(CONFIG_BCM2835_SMI)	+= bcm2835_smi.o
 obj-$(CONFIG_DUMMY_IRQ)		+= dummy-irq.o
 obj-$(CONFIG_ICS932S401)	+= ics932s401.o
 obj-$(CONFIG_LKDTM)		+= lkdtm/
@@ -18,6 +19,8 @@ obj-$(CONFIG_PHANTOM)		+= phantom.o
 obj-$(CONFIG_RPMB)		+= rpmb-core.o
 obj-$(CONFIG_QCOM_COINCELL)	+= qcom-coincell.o
 obj-$(CONFIG_QCOM_FASTRPC)	+= fastrpc.o
+obj-$(CONFIG_RP1_PIO)		+= rp1-pio.o
+obj-$(CONFIG_WS2812_PIO_RP1)	+= ws2812-pio-rp1.o
 obj-$(CONFIG_SENSORS_BH1770)	+= bh1770glc.o
 obj-$(CONFIG_SENSORS_APDS990X)	+= apds990x.o
 obj-$(CONFIG_ENCLOSURE_SERVICES) += enclosure.o
diff --git a/drivers/misc/bcm2835_smi.c b/drivers/misc/bcm2835_smi.c
new file mode 100644
index 000000000000..09246d5ea7d0
--- /dev/null
+++ b/drivers/misc/bcm2835_smi.c
@@ -0,0 +1,952 @@
+/**
+ * Broadcom Secondary Memory Interface driver
+ *
+ * Written by Luke Wren <luke@raspberrypi.org>
+ * Copyright (c) 2015, Raspberry Pi (Trading) Ltd.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The names of the above-listed copyright holders may not be used
+ *    to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * ALTERNATIVELY, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2, as published by the Free
+ * Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/of_address.h>
+#include <linux/of_platform.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/pagemap.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/semaphore.h>
+#include <linux/spinlock.h>
+#include <linux/io.h>
+
+#define BCM2835_SMI_IMPLEMENTATION
+#include <linux/broadcom/bcm2835_smi.h>
+
+#define DRIVER_NAME "smi-bcm2835"
+
+#define N_PAGES_FROM_BYTES(n) ((n + PAGE_SIZE-1) / PAGE_SIZE)
+
+#define DMA_WRITE_TO_MEM true
+#define DMA_READ_FROM_MEM false
+
+struct bcm2835_smi_instance {
+	struct device *dev;
+	struct smi_settings settings;
+	__iomem void *smi_regs_ptr;
+	phys_addr_t smi_regs_busaddr;
+
+	struct dma_chan *dma_chan;
+	struct dma_slave_config dma_config;
+
+	struct bcm2835_smi_bounce_info bounce;
+
+	struct scatterlist buffer_sgl;
+
+	struct clk *clk;
+
+	/* Sometimes we are called into in an atomic context (e.g. by
+	   JFFS2 + MTD) so we can't use a mutex */
+	spinlock_t transaction_lock;
+};
+
+/****************************************************************************
+*
+*   SMI peripheral setup
+*
+***************************************************************************/
+
+static inline void write_smi_reg(struct bcm2835_smi_instance *inst,
+	u32 val, unsigned reg)
+{
+	writel(val, inst->smi_regs_ptr + reg);
+}
+
+static inline u32 read_smi_reg(struct bcm2835_smi_instance *inst, unsigned reg)
+{
+	return readl(inst->smi_regs_ptr + reg);
+}
+
+/* Token-paste macro for e.g SMIDSR_RSTROBE ->  value of SMIDSR_RSTROBE_MASK */
+#define _CONCAT(x, y) x##y
+#define CONCAT(x, y) _CONCAT(x, y)
+
+#define SET_BIT_FIELD(dest, field, bits) ((dest) = \
+	((dest) & ~CONCAT(field, _MASK)) | (((bits) << CONCAT(field, _OFFS))& \
+	 CONCAT(field, _MASK)))
+#define GET_BIT_FIELD(src, field) (((src) & \
+	CONCAT(field, _MASK)) >> CONCAT(field, _OFFS))
+
+static void smi_dump_context_labelled(struct bcm2835_smi_instance *inst,
+	const char *label)
+{
+	dev_err(inst->dev, "SMI context dump: %s", label);
+	dev_err(inst->dev, "SMICS:  0x%08x", read_smi_reg(inst, SMICS));
+	dev_err(inst->dev, "SMIL:   0x%08x", read_smi_reg(inst, SMIL));
+	dev_err(inst->dev, "SMIDSR: 0x%08x", read_smi_reg(inst, SMIDSR0));
+	dev_err(inst->dev, "SMIDSW: 0x%08x", read_smi_reg(inst, SMIDSW0));
+	dev_err(inst->dev, "SMIDC:  0x%08x", read_smi_reg(inst, SMIDC));
+	dev_err(inst->dev, "SMIFD:  0x%08x", read_smi_reg(inst, SMIFD));
+	dev_err(inst->dev, " ");
+}
+
+static inline void smi_dump_context(struct bcm2835_smi_instance *inst)
+{
+	smi_dump_context_labelled(inst, "");
+}
+
+static void smi_get_default_settings(struct bcm2835_smi_instance *inst)
+{
+	struct smi_settings *settings = &inst->settings;
+
+	settings->data_width = SMI_WIDTH_16BIT;
+	settings->pack_data = true;
+
+	settings->read_setup_time = 1;
+	settings->read_hold_time = 1;
+	settings->read_pace_time = 1;
+	settings->read_strobe_time = 3;
+
+	settings->write_setup_time = settings->read_setup_time;
+	settings->write_hold_time = settings->read_hold_time;
+	settings->write_pace_time = settings->read_pace_time;
+	settings->write_strobe_time = settings->read_strobe_time;
+
+	settings->dma_enable = true;
+	settings->dma_passthrough_enable = false;
+	settings->dma_read_thresh = 0x01;
+	settings->dma_write_thresh = 0x3f;
+	settings->dma_panic_read_thresh = 0x20;
+	settings->dma_panic_write_thresh = 0x20;
+}
+
+void bcm2835_smi_set_regs_from_settings(struct bcm2835_smi_instance *inst)
+{
+	struct smi_settings *settings = &inst->settings;
+	int smidsr_temp = 0, smidsw_temp = 0, smics_temp,
+	    smidcs_temp, smidc_temp = 0;
+
+	spin_lock(&inst->transaction_lock);
+
+	/* temporarily disable the peripheral: */
+	smics_temp = read_smi_reg(inst, SMICS);
+	write_smi_reg(inst, 0, SMICS);
+	smidcs_temp = read_smi_reg(inst, SMIDCS);
+	write_smi_reg(inst, 0, SMIDCS);
+
+	if (settings->pack_data)
+		smics_temp |= SMICS_PXLDAT;
+	else
+		smics_temp &= ~SMICS_PXLDAT;
+
+	SET_BIT_FIELD(smidsr_temp, SMIDSR_RWIDTH, settings->data_width);
+	SET_BIT_FIELD(smidsr_temp, SMIDSR_RSETUP, settings->read_setup_time);
+	SET_BIT_FIELD(smidsr_temp, SMIDSR_RHOLD, settings->read_hold_time);
+	SET_BIT_FIELD(smidsr_temp, SMIDSR_RPACE, settings->read_pace_time);
+	SET_BIT_FIELD(smidsr_temp, SMIDSR_RSTROBE, settings->read_strobe_time);
+	write_smi_reg(inst, smidsr_temp, SMIDSR0);
+
+	SET_BIT_FIELD(smidsw_temp, SMIDSW_WWIDTH, settings->data_width);
+	if (settings->data_width == SMI_WIDTH_8BIT)
+		smidsw_temp |= SMIDSW_WSWAP;
+	else
+		smidsw_temp &= ~SMIDSW_WSWAP;
+	SET_BIT_FIELD(smidsw_temp, SMIDSW_WSETUP, settings->write_setup_time);
+	SET_BIT_FIELD(smidsw_temp, SMIDSW_WHOLD, settings->write_hold_time);
+	SET_BIT_FIELD(smidsw_temp, SMIDSW_WPACE, settings->write_pace_time);
+	SET_BIT_FIELD(smidsw_temp, SMIDSW_WSTROBE,
+			settings->write_strobe_time);
+	write_smi_reg(inst, smidsw_temp, SMIDSW0);
+
+	SET_BIT_FIELD(smidc_temp, SMIDC_REQR, settings->dma_read_thresh);
+	SET_BIT_FIELD(smidc_temp, SMIDC_REQW, settings->dma_write_thresh);
+	SET_BIT_FIELD(smidc_temp, SMIDC_PANICR,
+		      settings->dma_panic_read_thresh);
+	SET_BIT_FIELD(smidc_temp, SMIDC_PANICW,
+		      settings->dma_panic_write_thresh);
+	if (settings->dma_passthrough_enable) {
+		smidc_temp |= SMIDC_DMAP;
+		smidsr_temp |= SMIDSR_RDREQ;
+		write_smi_reg(inst, smidsr_temp, SMIDSR0);
+		smidsw_temp |= SMIDSW_WDREQ;
+		write_smi_reg(inst, smidsw_temp, SMIDSW0);
+	} else
+		smidc_temp &= ~SMIDC_DMAP;
+	if (settings->dma_enable)
+		smidc_temp |= SMIDC_DMAEN;
+	else
+		smidc_temp &= ~SMIDC_DMAEN;
+
+	write_smi_reg(inst, smidc_temp, SMIDC);
+
+	/* re-enable (if was previously enabled) */
+	write_smi_reg(inst, smics_temp, SMICS);
+	write_smi_reg(inst, smidcs_temp, SMIDCS);
+
+	spin_unlock(&inst->transaction_lock);
+}
+EXPORT_SYMBOL(bcm2835_smi_set_regs_from_settings);
+
+struct smi_settings *bcm2835_smi_get_settings_from_regs
+	(struct bcm2835_smi_instance *inst)
+{
+	struct smi_settings *settings = &inst->settings;
+	int smidsr, smidsw, smidc;
+
+	spin_lock(&inst->transaction_lock);
+
+	smidsr = read_smi_reg(inst, SMIDSR0);
+	smidsw = read_smi_reg(inst, SMIDSW0);
+	smidc = read_smi_reg(inst, SMIDC);
+
+	settings->pack_data = (read_smi_reg(inst, SMICS) & SMICS_PXLDAT) ?
+	    true : false;
+
+	settings->data_width = GET_BIT_FIELD(smidsr, SMIDSR_RWIDTH);
+	settings->read_setup_time = GET_BIT_FIELD(smidsr, SMIDSR_RSETUP);
+	settings->read_hold_time = GET_BIT_FIELD(smidsr, SMIDSR_RHOLD);
+	settings->read_pace_time = GET_BIT_FIELD(smidsr, SMIDSR_RPACE);
+	settings->read_strobe_time = GET_BIT_FIELD(smidsr, SMIDSR_RSTROBE);
+
+	settings->write_setup_time = GET_BIT_FIELD(smidsw, SMIDSW_WSETUP);
+	settings->write_hold_time = GET_BIT_FIELD(smidsw, SMIDSW_WHOLD);
+	settings->write_pace_time = GET_BIT_FIELD(smidsw, SMIDSW_WPACE);
+	settings->write_strobe_time = GET_BIT_FIELD(smidsw, SMIDSW_WSTROBE);
+
+	settings->dma_read_thresh = GET_BIT_FIELD(smidc, SMIDC_REQR);
+	settings->dma_write_thresh = GET_BIT_FIELD(smidc, SMIDC_REQW);
+	settings->dma_panic_read_thresh = GET_BIT_FIELD(smidc, SMIDC_PANICR);
+	settings->dma_panic_write_thresh = GET_BIT_FIELD(smidc, SMIDC_PANICW);
+	settings->dma_passthrough_enable = (smidc & SMIDC_DMAP) ? true : false;
+	settings->dma_enable = (smidc & SMIDC_DMAEN) ? true : false;
+
+	spin_unlock(&inst->transaction_lock);
+
+	return settings;
+}
+EXPORT_SYMBOL(bcm2835_smi_get_settings_from_regs);
+
+static inline void smi_set_address(struct bcm2835_smi_instance *inst,
+	unsigned int address)
+{
+	int smia_temp = 0, smida_temp = 0;
+
+	SET_BIT_FIELD(smia_temp, SMIA_ADDR, address);
+	SET_BIT_FIELD(smida_temp, SMIDA_ADDR, address);
+
+	/* Write to both address registers - user doesn't care whether we're
+	   doing programmed or direct transfers. */
+	write_smi_reg(inst, smia_temp, SMIA);
+	write_smi_reg(inst, smida_temp, SMIDA);
+}
+
+static void smi_setup_regs(struct bcm2835_smi_instance *inst)
+{
+
+	dev_dbg(inst->dev, "Initialising SMI registers...");
+	/* Disable the peripheral if already enabled */
+	write_smi_reg(inst, 0, SMICS);
+	write_smi_reg(inst, 0, SMIDCS);
+
+	smi_get_default_settings(inst);
+	bcm2835_smi_set_regs_from_settings(inst);
+	smi_set_address(inst, 0);
+
+	write_smi_reg(inst, read_smi_reg(inst, SMICS) | SMICS_ENABLE, SMICS);
+	write_smi_reg(inst, read_smi_reg(inst, SMIDCS) | SMIDCS_ENABLE,
+		SMIDCS);
+}
+
+/****************************************************************************
+*
+*   Low-level SMI access functions
+*   Other modules should use the exported higher-level functions e.g.
+*   bcm2835_smi_write_buf() unless they have a good reason to use these
+*
+***************************************************************************/
+
+static inline uint32_t smi_read_single_word(struct bcm2835_smi_instance *inst)
+{
+	int timeout = 0;
+
+	write_smi_reg(inst, SMIDCS_ENABLE, SMIDCS);
+	write_smi_reg(inst, SMIDCS_ENABLE | SMIDCS_START, SMIDCS);
+	/* Make sure things happen in the right order...*/
+	mb();
+	while (!(read_smi_reg(inst, SMIDCS) & SMIDCS_DONE) &&
+		++timeout < 10000)
+		;
+	if (timeout < 10000)
+		return read_smi_reg(inst, SMIDD);
+
+	dev_err(inst->dev,
+		"SMI direct read timed out (is the clock set up correctly?)");
+	return 0;
+}
+
+static inline void smi_write_single_word(struct bcm2835_smi_instance *inst,
+	uint32_t data)
+{
+	int timeout = 0;
+
+	write_smi_reg(inst, SMIDCS_ENABLE | SMIDCS_WRITE, SMIDCS);
+	write_smi_reg(inst, data, SMIDD);
+	write_smi_reg(inst, SMIDCS_ENABLE | SMIDCS_WRITE | SMIDCS_START,
+		SMIDCS);
+
+	while (!(read_smi_reg(inst, SMIDCS) & SMIDCS_DONE) &&
+		++timeout < 10000)
+		;
+	if (timeout >= 10000)
+		dev_err(inst->dev,
+		"SMI direct write timed out (is the clock set up correctly?)");
+}
+
+/* Initiates a programmed read into the read FIFO. It is up to the caller to
+ * read data from the FIFO -  either via paced DMA transfer,
+ * or polling SMICS_RXD to check whether data is available.
+ * SMICS_ACTIVE will go low upon completion. */
+static void smi_init_programmed_read(struct bcm2835_smi_instance *inst,
+	int num_transfers)
+{
+	int smics_temp;
+
+	/* Disable the peripheral: */
+	smics_temp = read_smi_reg(inst, SMICS) & ~(SMICS_ENABLE | SMICS_WRITE);
+	write_smi_reg(inst, smics_temp, SMICS);
+	while (read_smi_reg(inst, SMICS) & SMICS_ENABLE)
+		;
+
+	/* Program the transfer count: */
+	write_smi_reg(inst, num_transfers, SMIL);
+
+	/* re-enable and start: */
+	smics_temp |= SMICS_ENABLE;
+	write_smi_reg(inst, smics_temp, SMICS);
+	smics_temp |= SMICS_CLEAR;
+	/* Just to be certain: */
+	mb();
+	while (read_smi_reg(inst, SMICS) & SMICS_ACTIVE)
+		;
+	write_smi_reg(inst, smics_temp, SMICS);
+	smics_temp |= SMICS_START;
+	write_smi_reg(inst, smics_temp, SMICS);
+}
+
+/* Initiates a programmed write sequence, using data from the write FIFO.
+ * It is up to the caller to initiate a DMA transfer before calling,
+ * or use another method to keep the write FIFO topped up.
+ * SMICS_ACTIVE will go low upon completion.
+ */
+static void smi_init_programmed_write(struct bcm2835_smi_instance *inst,
+	int num_transfers)
+{
+	int smics_temp;
+
+	/* Disable the peripheral: */
+	smics_temp = read_smi_reg(inst, SMICS) & ~SMICS_ENABLE;
+	write_smi_reg(inst, smics_temp, SMICS);
+	while (read_smi_reg(inst, SMICS) & SMICS_ENABLE)
+		;
+
+	/* Program the transfer count: */
+	write_smi_reg(inst, num_transfers, SMIL);
+
+	/* setup, re-enable and start: */
+	smics_temp |= SMICS_WRITE | SMICS_ENABLE;
+	write_smi_reg(inst, smics_temp, SMICS);
+	smics_temp |= SMICS_START;
+	write_smi_reg(inst, smics_temp, SMICS);
+}
+
+/* Initiate a read and then poll FIFO for data, reading out as it appears. */
+static void smi_read_fifo(struct bcm2835_smi_instance *inst,
+	uint32_t *dest, int n_bytes)
+{
+	if (read_smi_reg(inst, SMICS) & SMICS_RXD) {
+		smi_dump_context_labelled(inst,
+			"WARNING: read FIFO not empty at start of read call.");
+		while (read_smi_reg(inst, SMICS))
+			;
+	}
+
+	/* Dispatch the read: */
+	if (inst->settings.data_width == SMI_WIDTH_8BIT)
+		smi_init_programmed_read(inst, n_bytes);
+	else if (inst->settings.data_width == SMI_WIDTH_16BIT)
+		smi_init_programmed_read(inst, n_bytes / 2);
+	else {
+		dev_err(inst->dev, "Unsupported data width for read.");
+		return;
+	}
+
+	/* Poll FIFO to keep it empty */
+	while (!(read_smi_reg(inst, SMICS) & SMICS_DONE))
+		if (read_smi_reg(inst, SMICS) & SMICS_RXD)
+			*dest++ = read_smi_reg(inst, SMID);
+
+	/* Ensure that the FIFO is emptied */
+	if (read_smi_reg(inst, SMICS) & SMICS_RXD) {
+		int fifo_count;
+
+		fifo_count = GET_BIT_FIELD(read_smi_reg(inst, SMIFD),
+			SMIFD_FCNT);
+		while (fifo_count--)
+			*dest++ = read_smi_reg(inst, SMID);
+	}
+
+	if (!(read_smi_reg(inst, SMICS) & SMICS_DONE))
+		smi_dump_context_labelled(inst,
+			"WARNING: transaction finished but done bit not set.");
+
+	if (read_smi_reg(inst, SMICS) & SMICS_RXD)
+		smi_dump_context_labelled(inst,
+			"WARNING: read FIFO not empty at end of read call.");
+
+}
+
+/* Initiate a write, and then keep the FIFO topped up. */
+static void smi_write_fifo(struct bcm2835_smi_instance *inst,
+	uint32_t *src, int n_bytes)
+{
+	int i, timeout = 0;
+
+	/* Empty FIFOs if not already so */
+	if (!(read_smi_reg(inst, SMICS) & SMICS_TXE)) {
+		smi_dump_context_labelled(inst,
+		    "WARNING: write fifo not empty at start of write call.");
+		write_smi_reg(inst, read_smi_reg(inst, SMICS) | SMICS_CLEAR,
+			SMICS);
+	}
+
+	/* Initiate the transfer */
+	if (inst->settings.data_width == SMI_WIDTH_8BIT)
+		smi_init_programmed_write(inst, n_bytes);
+	else if (inst->settings.data_width == SMI_WIDTH_16BIT)
+		smi_init_programmed_write(inst, n_bytes / 2);
+	else {
+		dev_err(inst->dev, "Unsupported data width for write.");
+		return;
+	}
+	/* Fill the FIFO: */
+	for (i = 0; i < (n_bytes - 1) / 4 + 1; ++i) {
+		while (!(read_smi_reg(inst, SMICS) & SMICS_TXD))
+			;
+		write_smi_reg(inst, *src++, SMID);
+	}
+	/* Busy wait... */
+	while (!(read_smi_reg(inst, SMICS) & SMICS_DONE) && ++timeout <
+		1000000)
+		;
+	if (timeout >= 1000000)
+		smi_dump_context_labelled(inst,
+			"Timed out on write operation!");
+	if (!(read_smi_reg(inst, SMICS) & SMICS_TXE))
+		smi_dump_context_labelled(inst,
+			"WARNING: FIFO not empty at end of write operation.");
+}
+
+/****************************************************************************
+*
+*   SMI DMA operations
+*
+***************************************************************************/
+
+/* Disable SMI and put it into the correct direction before doing DMA setup.
+   Stops spurious DREQs during setup. Peripheral is re-enabled by init_*() */
+static void smi_disable(struct bcm2835_smi_instance *inst,
+	enum dma_transfer_direction direction)
+{
+	int smics_temp = read_smi_reg(inst, SMICS) & ~SMICS_ENABLE;
+
+	if (direction == DMA_DEV_TO_MEM)
+		smics_temp &= ~SMICS_WRITE;
+	else
+		smics_temp |= SMICS_WRITE;
+	write_smi_reg(inst, smics_temp, SMICS);
+	while (read_smi_reg(inst, SMICS) & SMICS_ACTIVE)
+		;
+}
+
+static struct scatterlist *smi_scatterlist_from_buffer(
+	struct bcm2835_smi_instance *inst,
+	dma_addr_t buf,
+	size_t len,
+	struct scatterlist *sg)
+{
+	sg_init_table(sg, 1);
+	sg_dma_address(sg) = buf;
+	sg_dma_len(sg) = len;
+	return sg;
+}
+
+static void smi_dma_callback_user_copy(void *param)
+{
+	/* Notify the bottom half that a chunk is ready for user copy */
+	struct bcm2835_smi_instance *inst =
+		(struct bcm2835_smi_instance *)param;
+
+	up(&inst->bounce.callback_sem);
+}
+
+/* Creates a descriptor, assigns the given callback, and submits the
+   descriptor to dmaengine. Does not block - can queue up multiple
+   descriptors and then wait for them all to complete.
+   sg_len is the number of control blocks, NOT the number of bytes.
+   dir can be DMA_MEM_TO_DEV or DMA_DEV_TO_MEM.
+   callback can be NULL - in this case it is not called. */
+static inline struct dma_async_tx_descriptor *smi_dma_submit_sgl(
+	struct bcm2835_smi_instance *inst,
+	struct scatterlist *sgl,
+	size_t sg_len,
+	enum dma_transfer_direction dir,
+	dma_async_tx_callback callback)
+{
+	struct dma_async_tx_descriptor *desc;
+
+	desc = dmaengine_prep_slave_sg(inst->dma_chan,
+				       sgl,
+				       sg_len,
+				       dir,
+				       DMA_PREP_INTERRUPT | DMA_CTRL_ACK |
+				       DMA_PREP_FENCE);
+	if (!desc) {
+		dev_err(inst->dev, "read_sgl: dma slave preparation failed!");
+		write_smi_reg(inst, read_smi_reg(inst, SMICS) & ~SMICS_ACTIVE,
+			SMICS);
+		while (read_smi_reg(inst, SMICS) & SMICS_ACTIVE)
+			cpu_relax();
+		write_smi_reg(inst, read_smi_reg(inst, SMICS) | SMICS_ACTIVE,
+			SMICS);
+		return NULL;
+	}
+	desc->callback = callback;
+	desc->callback_param = inst;
+	if (dmaengine_submit(desc) < 0)
+		return NULL;
+	return desc;
+}
+
+/* NB this function blocks until the transfer is complete */
+static void
+smi_dma_read_sgl(struct bcm2835_smi_instance *inst,
+	struct scatterlist *sgl, size_t sg_len, size_t n_bytes)
+{
+	struct dma_async_tx_descriptor *desc;
+
+	/* Disable SMI and set to read before dispatching DMA - if SMI is in
+	 * write mode and TX fifo is empty, it will generate a DREQ which may
+	 * cause the read DMA to complete before the SMI read command is even
+	 * dispatched! We want to dispatch DMA before SMI read so that reading
+	 * is gapless, for logic analyser.
+	 */
+
+	smi_disable(inst, DMA_DEV_TO_MEM);
+
+	desc = smi_dma_submit_sgl(inst, sgl, sg_len, DMA_DEV_TO_MEM, NULL);
+	dma_async_issue_pending(inst->dma_chan);
+
+	if (inst->settings.data_width == SMI_WIDTH_8BIT)
+		smi_init_programmed_read(inst, n_bytes);
+	else
+		smi_init_programmed_read(inst, n_bytes / 2);
+
+	if (dma_wait_for_async_tx(desc) == DMA_ERROR)
+		smi_dump_context_labelled(inst, "DMA timeout!");
+}
+
+static void
+smi_dma_write_sgl(struct bcm2835_smi_instance *inst,
+	struct scatterlist *sgl, size_t sg_len, size_t n_bytes)
+{
+	struct dma_async_tx_descriptor *desc;
+
+	if (inst->settings.data_width == SMI_WIDTH_8BIT)
+		smi_init_programmed_write(inst, n_bytes);
+	else
+		smi_init_programmed_write(inst, n_bytes / 2);
+
+	desc = smi_dma_submit_sgl(inst, sgl, sg_len, DMA_MEM_TO_DEV, NULL);
+	dma_async_issue_pending(inst->dma_chan);
+
+	if (dma_wait_for_async_tx(desc) == DMA_ERROR)
+		smi_dump_context_labelled(inst, "DMA timeout!");
+	else
+		/* Wait for SMI to finish our writes */
+		while (!(read_smi_reg(inst, SMICS) & SMICS_DONE))
+			cpu_relax();
+}
+
+ssize_t bcm2835_smi_user_dma(
+	struct bcm2835_smi_instance *inst,
+	enum dma_transfer_direction dma_dir,
+	char __user *user_ptr, size_t count,
+	struct bcm2835_smi_bounce_info **bounce)
+{
+	int chunk_no = 0, chunk_size, count_left = count;
+	struct scatterlist *sgl;
+	void (*init_trans_func)(struct bcm2835_smi_instance *, int);
+
+	spin_lock(&inst->transaction_lock);
+
+	if (dma_dir == DMA_DEV_TO_MEM)
+		init_trans_func = smi_init_programmed_read;
+	else
+		init_trans_func = smi_init_programmed_write;
+
+	smi_disable(inst, dma_dir);
+
+	sema_init(&inst->bounce.callback_sem, 0);
+	if (bounce)
+		*bounce = &inst->bounce;
+	while (count_left) {
+		chunk_size = count_left > DMA_BOUNCE_BUFFER_SIZE ?
+			DMA_BOUNCE_BUFFER_SIZE : count_left;
+		if (chunk_size == DMA_BOUNCE_BUFFER_SIZE) {
+			sgl =
+			&inst->bounce.sgl[chunk_no % DMA_BOUNCE_BUFFER_COUNT];
+		} else {
+			sgl = smi_scatterlist_from_buffer(
+				inst,
+				inst->bounce.phys[
+					chunk_no % DMA_BOUNCE_BUFFER_COUNT],
+				chunk_size,
+				&inst->buffer_sgl);
+		}
+
+		if (!smi_dma_submit_sgl(inst, sgl, 1, dma_dir,
+			smi_dma_callback_user_copy
+		)) {
+			dev_err(inst->dev, "sgl submit failed");
+			count = 0;
+			goto out;
+		}
+		count_left -= chunk_size;
+		chunk_no++;
+	}
+	dma_async_issue_pending(inst->dma_chan);
+
+	if (inst->settings.data_width == SMI_WIDTH_8BIT)
+		init_trans_func(inst, count);
+	else if (inst->settings.data_width == SMI_WIDTH_16BIT)
+		init_trans_func(inst, count / 2);
+out:
+	spin_unlock(&inst->transaction_lock);
+	return count;
+}
+EXPORT_SYMBOL(bcm2835_smi_user_dma);
+
+
+/****************************************************************************
+*
+*   High level buffer transfer functions - for use by other drivers
+*
+***************************************************************************/
+
+/* Buffer must be physically contiguous - i.e. kmalloc, not vmalloc! */
+void bcm2835_smi_write_buf(
+	struct bcm2835_smi_instance *inst,
+	const void *buf, size_t n_bytes)
+{
+	int odd_bytes = n_bytes & 0x3;
+
+	n_bytes -= odd_bytes;
+
+	spin_lock(&inst->transaction_lock);
+
+	if (n_bytes > DMA_THRESHOLD_BYTES) {
+		dma_addr_t phy_addr = dma_map_single(
+			inst->dev,
+			(void *)buf,
+			n_bytes,
+			DMA_TO_DEVICE);
+		struct scatterlist *sgl =
+			smi_scatterlist_from_buffer(inst, phy_addr, n_bytes,
+				&inst->buffer_sgl);
+
+		if (!sgl) {
+			smi_dump_context_labelled(inst,
+			"Error: could not create scatterlist for write!");
+			goto out;
+		}
+		smi_dma_write_sgl(inst, sgl, 1, n_bytes);
+
+		dma_unmap_single
+			(inst->dev, phy_addr, n_bytes, DMA_TO_DEVICE);
+	} else if (n_bytes) {
+		smi_write_fifo(inst, (uint32_t *) buf, n_bytes);
+	}
+	buf += n_bytes;
+
+	if (inst->settings.data_width == SMI_WIDTH_8BIT) {
+		while (odd_bytes--)
+			smi_write_single_word(inst, *(uint8_t *) (buf++));
+	} else {
+		while (odd_bytes >= 2) {
+			smi_write_single_word(inst, *(uint16_t *)buf);
+			buf += 2;
+			odd_bytes -= 2;
+		}
+		if (odd_bytes) {
+			/* Reading an odd number of bytes on a 16 bit bus is
+			   a user bug. It's kinder to fail early and tell them
+			   than to e.g. transparently give them the bottom byte
+			   of a 16 bit transfer. */
+			dev_err(inst->dev,
+		"WARNING: odd number of bytes specified for wide transfer.");
+			dev_err(inst->dev,
+		"At least one byte dropped as a result.");
+			dump_stack();
+		}
+	}
+out:
+	spin_unlock(&inst->transaction_lock);
+}
+EXPORT_SYMBOL(bcm2835_smi_write_buf);
+
+void bcm2835_smi_read_buf(struct bcm2835_smi_instance *inst,
+	void *buf, size_t n_bytes)
+{
+
+	/* SMI is inherently 32-bit, which causes surprising amounts of mess
+	   for bytes % 4 != 0. Easiest to avoid this mess altogether
+	   by handling remainder separately. */
+	int odd_bytes = n_bytes & 0x3;
+
+	spin_lock(&inst->transaction_lock);
+	n_bytes -= odd_bytes;
+	if (n_bytes > DMA_THRESHOLD_BYTES) {
+		dma_addr_t phy_addr = dma_map_single(inst->dev,
+						     buf, n_bytes,
+						     DMA_FROM_DEVICE);
+		struct scatterlist *sgl = smi_scatterlist_from_buffer(
+			inst, phy_addr, n_bytes,
+			&inst->buffer_sgl);
+		if (!sgl) {
+			smi_dump_context_labelled(inst,
+			"Error: could not create scatterlist for read!");
+			goto out;
+		}
+		smi_dma_read_sgl(inst, sgl, 1, n_bytes);
+		dma_unmap_single(inst->dev, phy_addr, n_bytes, DMA_FROM_DEVICE);
+	} else if (n_bytes) {
+		smi_read_fifo(inst, (uint32_t *)buf, n_bytes);
+	}
+	buf += n_bytes;
+
+	if (inst->settings.data_width == SMI_WIDTH_8BIT) {
+		while (odd_bytes--)
+			*((uint8_t *) (buf++)) = smi_read_single_word(inst);
+	} else {
+		while (odd_bytes >= 2) {
+			*(uint16_t *) buf = smi_read_single_word(inst);
+			buf += 2;
+			odd_bytes -= 2;
+		}
+		if (odd_bytes) {
+			dev_err(inst->dev,
+		"WARNING: odd number of bytes specified for wide transfer.");
+			dev_err(inst->dev,
+		"At least one byte dropped as a result.");
+			dump_stack();
+		}
+	}
+out:
+	spin_unlock(&inst->transaction_lock);
+}
+EXPORT_SYMBOL(bcm2835_smi_read_buf);
+
+void bcm2835_smi_set_address(struct bcm2835_smi_instance *inst,
+	unsigned int address)
+{
+	spin_lock(&inst->transaction_lock);
+	smi_set_address(inst, address);
+	spin_unlock(&inst->transaction_lock);
+}
+EXPORT_SYMBOL(bcm2835_smi_set_address);
+
+struct bcm2835_smi_instance *bcm2835_smi_get(struct device_node *node)
+{
+	struct platform_device *pdev;
+
+	if (!node)
+		return NULL;
+
+	pdev = of_find_device_by_node(node);
+	if (!pdev)
+		return NULL;
+
+	return platform_get_drvdata(pdev);
+}
+EXPORT_SYMBOL(bcm2835_smi_get);
+
+/****************************************************************************
+*
+*   bcm2835_smi_probe - called when the driver is loaded.
+*
+***************************************************************************/
+
+static int bcm2835_smi_dma_setup(struct bcm2835_smi_instance *inst)
+{
+	int i, rv = 0;
+
+	inst->dma_chan = dma_request_slave_channel(inst->dev, "rx-tx");
+
+	inst->dma_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+	inst->dma_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+	inst->dma_config.src_addr = inst->smi_regs_busaddr + SMID;
+	inst->dma_config.dst_addr = inst->dma_config.src_addr;
+	/* Direction unimportant - always overridden by prep_slave_sg */
+	inst->dma_config.direction = DMA_DEV_TO_MEM;
+	dmaengine_slave_config(inst->dma_chan, &inst->dma_config);
+	/* Alloc and map bounce buffers */
+	for (i = 0; i < DMA_BOUNCE_BUFFER_COUNT; ++i) {
+		inst->bounce.buffer[i] =
+		dmam_alloc_coherent(inst->dev, DMA_BOUNCE_BUFFER_SIZE,
+				&inst->bounce.phys[i],
+				GFP_KERNEL);
+		if (!inst->bounce.buffer[i]) {
+			dev_err(inst->dev, "Could not allocate buffer!");
+			rv = -ENOMEM;
+			break;
+		}
+		smi_scatterlist_from_buffer(
+			inst,
+			inst->bounce.phys[i],
+			DMA_BOUNCE_BUFFER_SIZE,
+			&inst->bounce.sgl[i]
+		);
+	}
+
+	return rv;
+}
+
+static int bcm2835_smi_probe(struct platform_device *pdev)
+{
+	int err;
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct resource *ioresource;
+	struct bcm2835_smi_instance *inst;
+
+	/* We require device tree support */
+	if (!node)
+		return -EINVAL;
+	/* Allocate buffers and instance data */
+	inst = devm_kzalloc(dev, sizeof(struct bcm2835_smi_instance),
+		GFP_KERNEL);
+	if (!inst)
+		return -ENOMEM;
+
+	inst->dev = dev;
+	spin_lock_init(&inst->transaction_lock);
+
+	inst->smi_regs_ptr = devm_platform_get_and_ioremap_resource(pdev, 0,
+								    &ioresource);
+	if (IS_ERR(inst->smi_regs_ptr)) {
+		err = PTR_ERR(inst->smi_regs_ptr);
+		goto err;
+	}
+	inst->smi_regs_busaddr = ioresource->start;
+
+	err = bcm2835_smi_dma_setup(inst);
+	if (err)
+		goto err;
+
+	/* request clock */
+	inst->clk = devm_clk_get(dev, NULL);
+	if (!inst->clk)
+		goto err;
+	clk_prepare_enable(inst->clk);
+
+	/* Finally, do peripheral setup */
+	smi_setup_regs(inst);
+
+	platform_set_drvdata(pdev, inst);
+
+	dev_info(inst->dev, "initialised");
+
+	return 0;
+err:
+	kfree(inst);
+	return err;
+}
+
+/****************************************************************************
+*
+*   bcm2835_smi_remove - called when the driver is unloaded.
+*
+***************************************************************************/
+
+static void bcm2835_smi_remove(struct platform_device *pdev)
+{
+	struct bcm2835_smi_instance *inst = platform_get_drvdata(pdev);
+	struct device *dev = inst->dev;
+
+	dmaengine_terminate_all(inst->dma_chan);
+	dma_release_channel(inst->dma_chan);
+
+	clk_disable_unprepare(inst->clk);
+
+	dev_info(dev, "SMI device removed - OK");
+}
+
+/****************************************************************************
+*
+*   Register the driver with device tree
+*
+***************************************************************************/
+
+static const struct of_device_id bcm2835_smi_of_match[] = {
+	{.compatible = "brcm,bcm2835-smi",},
+	{ /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, bcm2835_smi_of_match);
+
+static struct platform_driver bcm2835_smi_driver = {
+	.probe = bcm2835_smi_probe,
+	.remove = bcm2835_smi_remove,
+	.driver = {
+		   .name = DRIVER_NAME,
+		   .owner = THIS_MODULE,
+		   .of_match_table = bcm2835_smi_of_match,
+		   },
+};
+
+module_platform_driver(bcm2835_smi_driver);
+
+MODULE_ALIAS("platform:smi-bcm2835");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Device driver for BCM2835's secondary memory interface");
+MODULE_AUTHOR("Luke Wren <luke@raspberrypi.org>");
diff --git a/drivers/misc/rp1-fw-pio.h b/drivers/misc/rp1-fw-pio.h
new file mode 100644
index 000000000000..ba28cba38f84
--- /dev/null
+++ b/drivers/misc/rp1-fw-pio.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ *  Copyright (C) 2023 2023-2024 Raspberry Pi Ltd.
+ */
+
+#ifndef __SOC_RP1_FIRMWARE_OPS_H__
+#define __SOC_RP1_FIRMWARE_OPS_H__
+
+#include <linux/rp1-firmware.h>
+
+#define FOURCC_PIO RP1_FOURCC("PIO ")
+
+enum rp1_pio_ops {
+	PIO_CAN_ADD_PROGRAM,	// u16 num_instrs, u16 origin -> origin
+	PIO_ADD_PROGRAM,	// u16 num_instrs, u16 origin, u16 prog[] -> rc
+	PIO_REMOVE_PROGRAM,	// u16 num_instrs, u16 origin
+	PIO_CLEAR_INSTR_MEM,	// -
+
+	PIO_SM_CLAIM,		// u16 mask -> sm
+	PIO_SM_UNCLAIM,		// u16 mask
+	PIO_SM_IS_CLAIMED,	// u16 mask -> claimed
+
+	PIO_SM_INIT,		// u16 sm, u16 initial_pc, u32 sm_config[4]
+	PIO_SM_SET_CONFIG,	// u16 sm, u16 rsvd, u32 sm_config[4]
+	PIO_SM_EXEC,		// u16 sm, u16 instr, u8 blocking, u8 rsvd
+	PIO_SM_CLEAR_FIFOS,	// u16 sm
+	PIO_SM_SET_CLKDIV,	// u16 sm, u16 div_int, u8 div_frac, u8 rsvd
+	PIO_SM_SET_PINS,	// u16 sm, u16 rsvd, u32 values, u32 mask
+	PIO_SM_SET_PINDIRS,	// u16 sm, u16 rsvd, u32 dirs, u32 mask
+	PIO_SM_SET_ENABLED,	// u16 mask, u8 enable, u8 rsvd
+	PIO_SM_RESTART,		// u16 mask
+	PIO_SM_CLKDIV_RESTART,	// u16 mask
+	PIO_SM_ENABLE_SYNC,	// u16 mask
+	PIO_SM_PUT,		// u16 sm, u8 blocking, u8 rsvd, u32 data
+	PIO_SM_GET,		// u16 sm, u8 blocking, u8 rsvd -> u32 data
+	PIO_SM_SET_DMACTRL,	// u16 sm, u16 is_tx, u32 ctrl
+
+	GPIO_INIT,		// u16 gpio
+	GPIO_SET_FUNCTION,	// u16 gpio, u16 fn
+	GPIO_SET_PULLS,		// u16 gpio, u8 up, u8 down
+	GPIO_SET_OUTOVER,	// u16 gpio, u16 value
+	GPIO_SET_INOVER,	// u16 gpio, u16 value
+	GPIO_SET_OEOVER,	// u16 gpio, u16 value
+	GPIO_SET_INPUT_ENABLED,	// u16 gpio, u16 value
+	GPIO_SET_DRIVE_STRENGTH,	// u16 gpio, u16 value
+
+	READ_HW,		// src address, len -> data bytes
+	WRITE_HW,		// dst address, data
+
+	PIO_SM_FIFO_STATE,	// u16 sm, u8 tx -> u16 level, u8 empty, u8 full
+	PIO_SM_DRAIN_TX,	// u16 sm
+
+	PIO_COUNT
+};
+
+#endif
diff --git a/drivers/misc/rp1-pio.c b/drivers/misc/rp1-pio.c
new file mode 100644
index 000000000000..f848e5f9d843
--- /dev/null
+++ b/drivers/misc/rp1-pio.c
@@ -0,0 +1,1408 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PIO driver for RP1
+ *
+ * Copyright (C) 2023-2024 Raspberry Pi Ltd.
+ *
+ * Parts of this driver are based on:
+ *  - vcio.c, by Noralf Trønnes
+ *    Copyright (C) 2010 Broadcom
+ *    Copyright (C) 2015 Noralf Trønnes
+ *    Copyright (C) 2021 Raspberry Pi (Trading) Ltd.
+ *  - bcm2835_smi.c & bcm2835_smi_dev.c by Luke Wren
+ *    Copyright (c) 2015 Raspberry Pi (Trading) Ltd.
+ */
+
+#include <linux/cdev.h>
+#include <linux/compat.h>
+#include <linux/device.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/ioctl.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pio_rp1.h>
+#include <linux/platform_device.h>
+#include <linux/rp1-firmware.h>
+#include <linux/semaphore.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
+#include <uapi/misc/rp1_pio_if.h>
+
+#include "rp1-fw-pio.h"
+
+#define DRIVER_NAME		"rp1-pio"
+
+#define RP1_PIO_SMS_COUNT	4
+#define RP1_PIO_INSTR_COUNT	32
+
+#define MAX_ARG_SIZE		256
+
+#define RP1_PIO_FIFO_TX0	0x00
+#define RP1_PIO_FIFO_TX1	0x04
+#define RP1_PIO_FIFO_TX2	0x08
+#define RP1_PIO_FIFO_TX3	0x0c
+#define RP1_PIO_FIFO_RX0	0x10
+#define RP1_PIO_FIFO_RX1	0x14
+#define RP1_PIO_FIFO_RX2	0x18
+#define RP1_PIO_FIFO_RX3	0x1c
+
+#define RP1_PIO_DMACTRL_DEFAULT	0x80000104
+
+#define HANDLER(_n, _f) \
+	[_IOC_NR(PIO_IOC_ ## _n)] = { #_n, rp1_pio_ ## _f, _IOC_SIZE(PIO_IOC_ ## _n) }
+
+
+#define ROUND_UP(x, y) (((x) + (y) - 1) - (((x) + (y) - 1) % (y)))
+
+#define DMA_BOUNCE_BUFFER_SIZE 0x1000
+#define DMA_BOUNCE_BUFFER_COUNT 4
+
+struct dma_xfer_state {
+	struct dma_info *dma;
+	void (*callback)(void *param);
+	void *callback_param;
+};
+
+struct dma_buf_info {
+	void *buf;
+	dma_addr_t dma_addr;
+	struct scatterlist sgl;
+};
+
+struct dma_info {
+	struct semaphore buf_sem;
+	struct dma_chan *chan;
+	size_t buf_size;
+	size_t buf_count;
+	unsigned int head_idx;
+	unsigned int tail_idx;
+	struct dma_buf_info bufs[DMA_BOUNCE_BUFFER_COUNT];
+};
+
+struct rp1_pio_device {
+	struct platform_device *pdev;
+	struct rp1_firmware *fw;
+	uint16_t fw_pio_base;
+	uint16_t fw_pio_count;
+	dev_t dev_num;
+	struct class *dev_class;
+	struct cdev cdev;
+	phys_addr_t phys_addr;
+	uint32_t claimed_sms;
+	uint32_t claimed_dmas;
+	spinlock_t lock;
+	struct mutex instr_mutex;
+	struct dma_info dma_configs[RP1_PIO_SMS_COUNT][RP1_PIO_DIR_COUNT];
+	uint32_t used_instrs;
+	uint8_t instr_refcounts[RP1_PIO_INSTR_COUNT];
+	uint16_t instrs[RP1_PIO_INSTR_COUNT];
+	uint client_count;
+};
+
+struct rp1_pio_client {
+	struct rp1_pio_device *pio;
+	uint32_t claimed_sms;
+	uint32_t claimed_instrs;
+	uint32_t claimed_dmas;
+	int error;
+};
+
+static struct rp1_pio_device *g_pio;
+
+static int rp1_pio_message(struct rp1_pio_device *pio,
+			   uint16_t op, const void *data, unsigned int data_len)
+{
+	uint32_t rc;
+	int ret;
+
+	if (op >= pio->fw_pio_count)
+		return -EOPNOTSUPP;
+	ret = rp1_firmware_message(pio->fw, pio->fw_pio_base + op,
+				   data, data_len,
+				   &rc, sizeof(rc));
+	if (ret == 4)
+		ret = rc;
+	return ret;
+}
+
+static int rp1_pio_message_resp(struct rp1_pio_device *pio,
+				uint16_t op, const void *data, unsigned int data_len,
+				void *resp, void __user *userbuf, unsigned int resp_len)
+{
+	uint32_t resp_buf[1 + 32];
+	int ret;
+
+	if (op >= pio->fw_pio_count)
+		return -EOPNOTSUPP;
+	if (resp_len + 4 >= sizeof(resp_buf))
+		return -EINVAL;
+	if (!resp && !userbuf)
+		return -EINVAL;
+	ret = rp1_firmware_message(pio->fw, pio->fw_pio_base + op,
+				   data, data_len,
+				   resp_buf, resp_len + 4);
+	if (ret >= 4 && !resp_buf[0]) {
+		ret -= 4;
+		if (resp)
+			memcpy(resp, &resp_buf[1], ret);
+		else if (copy_to_user(userbuf, &resp_buf[1], ret))
+			ret = -EFAULT;
+	} else if (ret >= 0) {
+		ret = -EIO;
+	}
+	return ret;
+}
+
+static int rp1_pio_read_hw(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_device *pio = client->pio;
+	struct rp1_access_hw_args *args = param;
+
+	return rp1_pio_message_resp(pio, READ_HW,
+				    args, 8, NULL, args->data, args->len);
+}
+
+static int rp1_pio_write_hw(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_device *pio = client->pio;
+	struct rp1_access_hw_args *args = param;
+	uint32_t write_buf[32 + 1];
+	int len;
+
+	len = min(args->len, sizeof(write_buf) - 4);
+	write_buf[0] = args->addr;
+	if (copy_from_user(&write_buf[1], args->data, len))
+		return -EFAULT;
+	return rp1_firmware_message(pio->fw, pio->fw_pio_base + WRITE_HW,
+				    write_buf, 4 + len, NULL, 0);
+}
+
+static int rp1_pio_find_program(struct rp1_pio_device *pio,
+				struct rp1_pio_add_program_args *prog)
+{
+	uint start, end, prog_size;
+	uint32_t used_mask;
+	uint i;
+
+	start = (prog->origin != RP1_PIO_ORIGIN_ANY) ? prog->origin : 0;
+	end = (prog->origin != RP1_PIO_ORIGIN_ANY) ? prog->origin :
+			(RP1_PIO_INSTRUCTION_COUNT - prog->num_instrs);
+	prog_size = sizeof(prog->instrs[0]) * prog->num_instrs;
+	used_mask = (uint32_t)(~0) >> (32 - prog->num_instrs);
+
+	/* Find the best match */
+	for (i = start; i <= end; i++) {
+		uint32_t mask = used_mask << i;
+
+		if ((pio->used_instrs & mask) != mask)
+			continue;
+		if (!memcmp(pio->instrs + i, prog->instrs, prog_size))
+			return i;
+	}
+
+	return -1;
+}
+
+int rp1_pio_can_add_program(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_add_program_args *args = param;
+	struct rp1_pio_device *pio = client->pio;
+	int offset;
+
+	if (args->num_instrs > RP1_PIO_INSTR_COUNT ||
+		((args->origin != RP1_PIO_ORIGIN_ANY) &&
+		 (args->origin >= RP1_PIO_INSTR_COUNT ||
+		  ((args->origin + args->num_instrs) > RP1_PIO_INSTR_COUNT))))
+		return -EINVAL;
+
+	mutex_lock(&pio->instr_mutex);
+	offset = rp1_pio_find_program(pio, args);
+	mutex_unlock(&pio->instr_mutex);
+	if (offset >= 0)
+		return offset;
+
+	/* Don't send the instructions, just the header */
+	return rp1_pio_message(pio, PIO_CAN_ADD_PROGRAM, args,
+			       offsetof(struct rp1_pio_add_program_args, instrs));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_can_add_program);
+
+int rp1_pio_add_program(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_add_program_args *args = param;
+	struct rp1_pio_device *pio = client->pio;
+	int offset;
+	uint i;
+
+	if (args->num_instrs > RP1_PIO_INSTR_COUNT ||
+		((args->origin != RP1_PIO_ORIGIN_ANY) &&
+		 (args->origin >= RP1_PIO_INSTR_COUNT ||
+		  ((args->origin + args->num_instrs) > RP1_PIO_INSTR_COUNT))))
+		return -EINVAL;
+
+	mutex_lock(&pio->instr_mutex);
+	offset = rp1_pio_find_program(pio, args);
+	if (offset < 0)
+		offset = rp1_pio_message(client->pio, PIO_ADD_PROGRAM, args, sizeof(*args));
+
+	if (offset >= 0) {
+		uint32_t used_mask;
+		uint prog_size;
+
+		used_mask = ((uint32_t)(~0) >> (-args->num_instrs & 0x1f)) << offset;
+		prog_size = sizeof(args->instrs[0]) * args->num_instrs;
+
+		if ((pio->used_instrs & used_mask) != used_mask) {
+			pio->used_instrs |= used_mask;
+			memcpy(pio->instrs + offset, args->instrs, prog_size);
+		}
+		client->claimed_instrs |= used_mask;
+		for (i = 0; i < args->num_instrs; i++)
+			pio->instr_refcounts[offset + i]++;
+	}
+	mutex_unlock(&pio->instr_mutex);
+	return offset;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_add_program);
+
+static void rp1_pio_remove_instrs(struct rp1_pio_device *pio, uint32_t mask)
+{
+	struct rp1_pio_remove_program_args args;
+	uint i;
+
+	mutex_lock(&pio->instr_mutex);
+	args.num_instrs = 0;
+	for (i = 0; ; i++, mask >>= 1) {
+		if ((mask & 1) && pio->instr_refcounts[i] && !--pio->instr_refcounts[i]) {
+			pio->used_instrs &= ~(1 << i);
+			args.num_instrs++;
+		} else if (args.num_instrs) {
+			args.origin = i - args.num_instrs;
+			rp1_pio_message(pio, PIO_REMOVE_PROGRAM, &args, sizeof(args));
+			args.num_instrs = 0;
+		}
+		if (!mask)
+			break;
+	}
+	mutex_unlock(&pio->instr_mutex);
+}
+
+int rp1_pio_remove_program(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_remove_program_args *args = param;
+	uint32_t used_mask;
+	int ret = -ENOENT;
+
+	if (args->num_instrs > RP1_PIO_INSTR_COUNT ||
+		args->origin >= RP1_PIO_INSTR_COUNT ||
+		(args->origin + args->num_instrs) > RP1_PIO_INSTR_COUNT)
+		return -EINVAL;
+
+	used_mask = ((uint32_t)(~0) >> (32 - args->num_instrs)) << args->origin;
+	if ((client->claimed_instrs & used_mask) == used_mask) {
+		client->claimed_instrs &= ~used_mask;
+		rp1_pio_remove_instrs(client->pio, used_mask);
+		ret = 0;
+	}
+	return ret;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_remove_program);
+
+int rp1_pio_clear_instr_mem(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_device *pio = client->pio;
+
+	mutex_lock(&pio->instr_mutex);
+	(void)rp1_pio_message(client->pio, PIO_CLEAR_INSTR_MEM, NULL, 0);
+	memset(pio->instr_refcounts, 0, sizeof(pio->instr_refcounts));
+	pio->used_instrs = 0;
+	client->claimed_instrs = 0;
+	mutex_unlock(&pio->instr_mutex);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_clear_instr_mem);
+
+int rp1_pio_sm_claim(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_claim_args *args = param;
+	struct rp1_pio_device *pio = client->pio;
+	int ret;
+
+	mutex_lock(&pio->instr_mutex);
+	ret = rp1_pio_message(client->pio, PIO_SM_CLAIM, args, sizeof(*args));
+	if (ret >= 0) {
+		if (args->mask)
+			client->claimed_sms |= args->mask;
+		else
+			client->claimed_sms |= (1 << ret);
+		pio->claimed_sms |= client->claimed_sms;
+	}
+	mutex_unlock(&pio->instr_mutex);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_claim);
+
+int rp1_pio_sm_unclaim(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_claim_args *args = param;
+	struct rp1_pio_device *pio = client->pio;
+
+	mutex_lock(&pio->instr_mutex);
+	(void)rp1_pio_message(client->pio, PIO_SM_UNCLAIM, args, sizeof(*args));
+	client->claimed_sms &= ~args->mask;
+	pio->claimed_sms &= ~args->mask;
+	mutex_unlock(&pio->instr_mutex);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_unclaim);
+
+int rp1_pio_sm_is_claimed(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_claim_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_IS_CLAIMED, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_is_claimed);
+
+int rp1_pio_sm_init(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_init_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_INIT, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_init);
+
+int rp1_pio_sm_set_config(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_set_config_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_SET_CONFIG, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_set_config);
+
+int rp1_pio_sm_exec(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_exec_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_EXEC, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_exec);
+
+int rp1_pio_sm_clear_fifos(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_clear_fifos_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_CLEAR_FIFOS, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_clear_fifos);
+
+int rp1_pio_sm_set_clkdiv(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_set_clkdiv_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_SET_CLKDIV, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_set_clkdiv);
+
+int rp1_pio_sm_set_pins(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_set_pins_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_SET_PINS, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_set_pins);
+
+int rp1_pio_sm_set_pindirs(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_set_pindirs_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_SET_PINDIRS, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_set_pindirs);
+
+int rp1_pio_sm_set_enabled(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_set_enabled_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_SET_ENABLED, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_set_enabled);
+
+int rp1_pio_sm_restart(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_restart_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_RESTART, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_restart);
+
+int rp1_pio_sm_clkdiv_restart(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_restart_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_CLKDIV_RESTART, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_clkdiv_restart);
+
+int rp1_pio_sm_enable_sync(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_enable_sync_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_ENABLE_SYNC, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_enable_sync);
+
+int rp1_pio_sm_put(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_put_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_PUT, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_put);
+
+int rp1_pio_sm_get(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_get_args *args = param;
+	int ret;
+
+	ret = rp1_pio_message_resp(client->pio, PIO_SM_GET, args, sizeof(*args),
+				   &args->data, NULL, sizeof(args->data));
+	if (ret >= 0)
+		return offsetof(struct rp1_pio_sm_get_args, data) + ret;
+	return ret;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_get);
+
+int rp1_pio_sm_set_dmactrl(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_set_dmactrl_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_SET_DMACTRL, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_set_dmactrl);
+
+int rp1_pio_sm_fifo_state(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_fifo_state_args *args = param;
+	const int level_offset = offsetof(struct rp1_pio_sm_fifo_state_args, level);
+	int ret;
+
+	ret = rp1_pio_message_resp(client->pio, PIO_SM_FIFO_STATE, args, sizeof(*args),
+				   &args->level, NULL, sizeof(*args) - level_offset);
+	if (ret >= 0)
+		return level_offset + ret;
+	return ret;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_fifo_state);
+
+int rp1_pio_sm_drain_tx(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_clear_fifos_args *args = param;
+
+	return rp1_pio_message(client->pio, PIO_SM_DRAIN_TX, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_drain_tx);
+
+int rp1_pio_gpio_init(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_gpio_init_args *args = param;
+
+	return rp1_pio_message(client->pio, GPIO_INIT, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_gpio_init);
+
+int rp1_pio_gpio_set_function(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_gpio_set_function_args *args = param;
+
+	return rp1_pio_message(client->pio, GPIO_SET_FUNCTION, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_function);
+
+int rp1_pio_gpio_set_pulls(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_gpio_set_pulls_args *args = param;
+
+	return rp1_pio_message(client->pio, GPIO_SET_PULLS, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_pulls);
+
+int rp1_pio_gpio_set_outover(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_gpio_set_args *args = param;
+
+	return rp1_pio_message(client->pio, GPIO_SET_OUTOVER, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_outover);
+
+int rp1_pio_gpio_set_inover(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_gpio_set_args *args = param;
+
+	return rp1_pio_message(client->pio, GPIO_SET_INOVER, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_inover);
+
+int rp1_pio_gpio_set_oeover(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_gpio_set_args *args = param;
+
+	return rp1_pio_message(client->pio, GPIO_SET_OEOVER, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_oeover);
+
+int rp1_pio_gpio_set_input_enabled(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_gpio_set_args *args = param;
+
+	return rp1_pio_message(client->pio, GPIO_SET_INPUT_ENABLED, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_input_enabled);
+
+int rp1_pio_gpio_set_drive_strength(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_gpio_set_args *args = param;
+
+	return rp1_pio_message(client->pio, GPIO_SET_DRIVE_STRENGTH, args, sizeof(*args));
+}
+EXPORT_SYMBOL_GPL(rp1_pio_gpio_set_drive_strength);
+
+static void rp1_pio_sm_dma_callback(void *param)
+{
+	struct dma_info *dma = param;
+
+	up(&dma->buf_sem);
+}
+
+static void rp1_pio_sm_kernel_dma_callback(void *param)
+{
+	struct dma_xfer_state *dxs = param;
+
+	dxs->dma->tail_idx++;
+	up(&dxs->dma->buf_sem);
+
+	dxs->callback(dxs->callback_param);
+
+	kfree(dxs);
+}
+
+static void rp1_pio_sm_dma_free(struct device *dev, struct dma_info *dma)
+{
+	dmaengine_terminate_all(dma->chan);
+	while (dma->buf_count > 0) {
+		dma->buf_count--;
+		dma_free_coherent(dev, ROUND_UP(dma->buf_size, PAGE_SIZE),
+				  dma->bufs[dma->buf_count].buf,
+				  dma->bufs[dma->buf_count].dma_addr);
+	}
+
+	dma_release_channel(dma->chan);
+}
+
+static int rp1_pio_sm_config_xfer_internal(struct rp1_pio_client *client, uint sm, uint dir,
+					   uint buf_size, uint buf_count)
+{
+	struct rp1_pio_sm_set_dmactrl_args set_dmactrl_args;
+	struct rp1_pio_device *pio = client->pio;
+	struct platform_device *pdev = pio->pdev;
+	struct device *dev = &pdev->dev;
+	struct dma_slave_config config = {};
+	struct dma_slave_caps dma_caps;
+	phys_addr_t fifo_addr;
+	struct dma_info *dma;
+	uint32_t dma_mask;
+	char chan_name[4];
+	int ret = 0;
+
+	if (sm >= RP1_PIO_SMS_COUNT || dir >= RP1_PIO_DIR_COUNT)
+		return -EINVAL;
+	if ((buf_count || buf_size) &&
+	    (!buf_size || (buf_size & 3) ||
+	     !buf_count || buf_count > DMA_BOUNCE_BUFFER_COUNT))
+		return -EINVAL;
+
+	dma_mask = 1 << (sm * 2 + dir);
+
+	dma = &pio->dma_configs[sm][dir];
+
+	spin_lock(&pio->lock);
+	if (pio->claimed_dmas & dma_mask)
+		rp1_pio_sm_dma_free(dev, dma);
+	pio->claimed_dmas |= dma_mask;
+	client->claimed_dmas |= dma_mask;
+	spin_unlock(&pio->lock);
+
+	dma->buf_size = buf_size;
+	/* Round up the allocations */
+	buf_size = ROUND_UP(buf_size, PAGE_SIZE);
+	sema_init(&dma->buf_sem, 0);
+
+	/* Allocate and configure a DMA channel */
+	/* Careful - each SM FIFO has its own DREQ value */
+	chan_name[0] = (dir == RP1_PIO_DIR_TO_SM) ? 't' : 'r';
+	chan_name[1] = 'x';
+	chan_name[2] = '0' + sm;
+	chan_name[3] = '\0';
+
+	dma->chan = dma_request_chan(dev, chan_name);
+	if (IS_ERR(dma->chan)) {
+		ret = PTR_ERR(dma->chan);
+		goto err_unclaim;
+	}
+
+	/* Alloc and map bounce buffers */
+	for (dma->buf_count = 0; dma->buf_count < buf_count; dma->buf_count++) {
+		struct dma_buf_info *dbi = &dma->bufs[dma->buf_count];
+
+		dbi->buf = dma_alloc_coherent(dma->chan->device->dev, buf_size,
+					      &dbi->dma_addr, GFP_KERNEL);
+		if (!dbi->buf) {
+			ret = -ENOMEM;
+			goto err_dma_free;
+		}
+		sg_init_table(&dbi->sgl, 1);
+		sg_dma_address(&dbi->sgl) = dbi->dma_addr;
+	}
+
+	fifo_addr = pio->phys_addr;
+	fifo_addr += sm * (RP1_PIO_FIFO_TX1 - RP1_PIO_FIFO_TX0);
+	fifo_addr += (dir == RP1_PIO_DIR_TO_SM) ? RP1_PIO_FIFO_TX0 : RP1_PIO_FIFO_RX0;
+
+	config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+	config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+	config.src_addr = fifo_addr;
+	config.dst_addr = fifo_addr;
+	config.direction = (dir == RP1_PIO_DIR_TO_SM) ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
+	dma_caps.max_burst = 4;
+	dma_get_slave_caps(dma->chan, &dma_caps);
+	if (dir == RP1_PIO_DIR_TO_SM)
+		config.dst_maxburst = dma_caps.max_burst;
+	else
+		config.src_maxburst = dma_caps.max_burst;
+
+	ret = dmaengine_slave_config(dma->chan, &config);
+	if (ret)
+		goto err_dma_free;
+
+	set_dmactrl_args.sm = sm;
+	set_dmactrl_args.is_tx = (dir == RP1_PIO_DIR_TO_SM);
+	set_dmactrl_args.ctrl = RP1_PIO_DMACTRL_DEFAULT;
+	if (dir == RP1_PIO_DIR_FROM_SM)
+		set_dmactrl_args.ctrl = (RP1_PIO_DMACTRL_DEFAULT & ~0x1f) | 1;
+
+	ret = rp1_pio_sm_set_dmactrl(client, &set_dmactrl_args);
+	if (ret)
+		goto err_dma_free;
+
+	return 0;
+
+err_dma_free:
+	rp1_pio_sm_dma_free(dev, dma);
+
+err_unclaim:
+	spin_lock(&pio->lock);
+	client->claimed_dmas &= ~dma_mask;
+	pio->claimed_dmas &= ~dma_mask;
+	spin_unlock(&pio->lock);
+
+	return ret;
+}
+
+static int rp1_pio_sm_config_xfer_user(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_config_xfer_args *args = param;
+
+	return rp1_pio_sm_config_xfer_internal(client, args->sm, args->dir,
+					       args->buf_size, args->buf_count);
+}
+
+static int rp1_pio_sm_config_xfer32_user(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_config_xfer32_args *args = param;
+
+	return rp1_pio_sm_config_xfer_internal(client, args->sm, args->dir,
+					       args->buf_size, args->buf_count);
+}
+
+static int rp1_pio_sm_tx_user(struct rp1_pio_device *pio, struct dma_info *dma,
+				  const void __user *userbuf, size_t bytes)
+{
+	struct platform_device *pdev = pio->pdev;
+	struct dma_async_tx_descriptor *desc;
+	struct device *dev = &pdev->dev;
+	int ret = 0;
+
+	/* Clean the slate - we're running synchronously */
+	dma->head_idx = 0;
+	dma->tail_idx = 0;
+
+	while (bytes > 0) {
+		size_t copy_bytes = min(bytes, dma->buf_size);
+		struct dma_buf_info *dbi;
+
+		/* grab the next free buffer, waiting if they're all full */
+		if (dma->head_idx - dma->tail_idx == dma->buf_count) {
+			if (down_timeout(&dma->buf_sem,
+				msecs_to_jiffies(1000))) {
+				dev_err(dev, "DMA bounce timed out\n");
+				break;
+			}
+			dma->tail_idx++;
+		}
+
+		dbi = &dma->bufs[dma->head_idx % dma->buf_count];
+
+		sg_dma_len(&dbi->sgl) = copy_bytes;
+
+		ret = copy_from_user(dbi->buf, userbuf, copy_bytes);
+		if (ret < 0)
+			break;
+
+		userbuf += copy_bytes;
+
+		desc = dmaengine_prep_slave_sg(dma->chan, &dbi->sgl, 1,
+					       DMA_MEM_TO_DEV,
+					       DMA_PREP_INTERRUPT | DMA_CTRL_ACK |
+					       DMA_PREP_FENCE);
+		if (!desc) {
+			dev_err(dev, "DMA preparation failed\n");
+			ret = -EIO;
+			break;
+		}
+
+		desc->callback = rp1_pio_sm_dma_callback;
+		desc->callback_param = dma;
+
+		/* Submit the buffer - the callback will kick the semaphore */
+		ret = dmaengine_submit(desc);
+		if (ret < 0)
+			break;
+		ret = 0;
+
+		dma_async_issue_pending(dma->chan);
+
+		dma->head_idx++;
+		bytes -= copy_bytes;
+	}
+
+	/* Block for completion */
+	while (dma->tail_idx != dma->head_idx) {
+		if (down_timeout(&dma->buf_sem, msecs_to_jiffies(1000))) {
+			dev_err(dev, "DMA wait timed out\n");
+			ret = -ETIMEDOUT;
+			break;
+		}
+		dma->tail_idx++;
+	}
+
+	return ret;
+}
+
+static int rp1_pio_sm_rx_user(struct rp1_pio_device *pio, struct dma_info *dma,
+				  void __user *userbuf, size_t bytes)
+{
+	struct platform_device *pdev = pio->pdev;
+	struct dma_async_tx_descriptor *desc;
+	struct device *dev = &pdev->dev;
+	int ret = 0;
+
+	/* Clean the slate - we're running synchronously */
+	dma->head_idx = 0;
+	dma->tail_idx = 0;
+
+	while (bytes || dma->tail_idx != dma->head_idx) {
+		size_t copy_bytes = min(bytes, dma->buf_size);
+		struct dma_buf_info *dbi;
+
+		/*
+		 * wait for the next RX to complete if all the buffers are
+		 * outstanding or we're finishing up.
+		 */
+		if (!bytes || dma->head_idx - dma->tail_idx == dma->buf_count) {
+			if (down_timeout(&dma->buf_sem,
+				msecs_to_jiffies(1000))) {
+				dev_err(dev, "DMA wait timed out\n");
+				ret = -ETIMEDOUT;
+				break;
+			}
+
+			dbi = &dma->bufs[dma->tail_idx++ % dma->buf_count];
+			ret = copy_to_user(userbuf, dbi->buf, sg_dma_len(&dbi->sgl));
+			if (ret < 0)
+				break;
+			userbuf += sg_dma_len(&dbi->sgl);
+
+			if (!bytes)
+				continue;
+		}
+
+		dbi = &dma->bufs[dma->head_idx % dma->buf_count];
+		sg_dma_len(&dbi->sgl) = copy_bytes;
+		desc = dmaengine_prep_slave_sg(dma->chan, &dbi->sgl, 1,
+					       DMA_DEV_TO_MEM,
+					       DMA_PREP_INTERRUPT | DMA_CTRL_ACK |
+					       DMA_PREP_FENCE);
+		if (!desc) {
+			dev_err(dev, "DMA preparation failed\n");
+			ret = -EIO;
+			break;
+		}
+
+		desc->callback = rp1_pio_sm_dma_callback;
+		desc->callback_param = dma;
+
+		/* Submit the buffer - the callback will kick the semaphore */
+		ret = dmaengine_submit(desc);
+		if (ret < 0)
+			break;
+
+		dma->head_idx++;
+		dma_async_issue_pending(dma->chan);
+
+		bytes -= copy_bytes;
+	}
+
+	return ret;
+}
+
+static int rp1_pio_sm_xfer_data32_user(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_xfer_data32_args *args = param;
+	struct rp1_pio_device *pio = client->pio;
+	struct dma_info *dma;
+
+	if (args->sm >= RP1_PIO_SMS_COUNT || args->dir >= RP1_PIO_DIR_COUNT ||
+	    !args->data_bytes || !args->data)
+		return -EINVAL;
+
+	dma = &pio->dma_configs[args->sm][args->dir];
+
+	if (args->dir == RP1_PIO_DIR_TO_SM)
+		return rp1_pio_sm_tx_user(pio, dma, args->data, args->data_bytes);
+	else
+		return rp1_pio_sm_rx_user(pio, dma, args->data, args->data_bytes);
+}
+
+static int rp1_pio_sm_xfer_data_user(struct rp1_pio_client *client, void *param)
+{
+	struct rp1_pio_sm_xfer_data_args *args = param;
+	struct rp1_pio_sm_xfer_data32_args args32;
+
+	args32.sm = args->sm;
+	args32.dir = args->dir;
+	args32.data_bytes = args->data_bytes;
+	args32.data = args->data;
+
+	return rp1_pio_sm_xfer_data32_user(client, &args32);
+}
+
+int rp1_pio_sm_config_xfer(struct rp1_pio_client *client, uint sm, uint dir,
+			      uint buf_size, uint buf_count)
+{
+	return rp1_pio_sm_config_xfer_internal(client, sm, dir, buf_size, buf_count);
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_config_xfer);
+
+int rp1_pio_sm_xfer_data(struct rp1_pio_client *client, uint sm, uint dir,
+				    uint data_bytes, void *data, dma_addr_t dma_addr,
+				    void (*callback)(void *param), void *param)
+{
+	struct rp1_pio_device *pio = client->pio;
+	struct platform_device *pdev = pio->pdev;
+	struct dma_async_tx_descriptor *desc;
+	struct dma_xfer_state *dxs = NULL;
+	struct device *dev = &pdev->dev;
+	struct dma_buf_info *dbi = NULL;
+	struct scatterlist sg;
+	struct dma_info *dma;
+	int ret = 0;
+
+	if (sm >= RP1_PIO_SMS_COUNT || dir >= RP1_PIO_DIR_COUNT)
+		return -EINVAL;
+
+	dma = &pio->dma_configs[sm][dir];
+
+	if (!dma_addr) {
+		dxs = kmalloc(sizeof(*dxs), GFP_KERNEL);
+		dxs->dma = dma;
+		dxs->callback = callback;
+		dxs->callback_param = param;
+		callback = rp1_pio_sm_kernel_dma_callback;
+		param = dxs;
+
+		if (!dma->buf_count || data_bytes > dma->buf_size)
+			return -EINVAL;
+
+		/* Grab a dma buffer */
+		if (dma->head_idx - dma->tail_idx == dma->buf_count) {
+			if (down_timeout(&dma->buf_sem, msecs_to_jiffies(1000))) {
+				dev_err(dev, "DMA wait timed out\n");
+				return -ETIMEDOUT;
+			}
+		}
+
+		dbi = &dma->bufs[dma->head_idx % dma->buf_count];
+		dma_addr = dbi->dma_addr;
+
+		if (dir == PIO_DIR_TO_SM)
+			memcpy(dbi->buf, data, data_bytes);
+	}
+
+	sg_init_table(&sg, 1);
+	sg_dma_address(&sg) = dma_addr;
+	sg_dma_len(&sg) = data_bytes;
+
+	desc = dmaengine_prep_slave_sg(dma->chan, &sg, 1,
+					   (dir == PIO_DIR_TO_SM) ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM,
+					   DMA_PREP_INTERRUPT | DMA_CTRL_ACK |
+					   DMA_PREP_FENCE);
+	if (!desc) {
+		dev_err(dev, "DMA preparation failed\n");
+		return -EIO;
+	}
+
+	desc->callback = callback;
+	desc->callback_param = param;
+
+	ret = dmaengine_submit(desc);
+	if (ret < 0) {
+		dev_err(dev, "dmaengine_submit failed (%d)\n", ret);
+		return ret;
+	}
+
+	dma->head_idx++;
+	dma_async_issue_pending(dma->chan);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_sm_xfer_data);
+
+struct handler_info {
+	const char *name;
+	int (*func)(struct rp1_pio_client *client, void *param);
+	int argsize;
+} ioctl_handlers[] = {
+	HANDLER(SM_CONFIG_XFER, sm_config_xfer_user),
+	HANDLER(SM_XFER_DATA, sm_xfer_data_user),
+	HANDLER(SM_XFER_DATA32, sm_xfer_data32_user),
+	HANDLER(SM_CONFIG_XFER32, sm_config_xfer32_user),
+
+	HANDLER(CAN_ADD_PROGRAM, can_add_program),
+	HANDLER(ADD_PROGRAM, add_program),
+	HANDLER(REMOVE_PROGRAM, remove_program),
+	HANDLER(CLEAR_INSTR_MEM, clear_instr_mem),
+
+	HANDLER(SM_CLAIM, sm_claim),
+	HANDLER(SM_UNCLAIM, sm_unclaim),
+	HANDLER(SM_IS_CLAIMED, sm_is_claimed),
+
+	HANDLER(SM_INIT, sm_init),
+	HANDLER(SM_SET_CONFIG, sm_set_config),
+	HANDLER(SM_EXEC, sm_exec),
+	HANDLER(SM_CLEAR_FIFOS, sm_clear_fifos),
+	HANDLER(SM_SET_CLKDIV, sm_set_clkdiv),
+	HANDLER(SM_SET_PINS, sm_set_pins),
+	HANDLER(SM_SET_PINDIRS, sm_set_pindirs),
+	HANDLER(SM_SET_ENABLED, sm_set_enabled),
+	HANDLER(SM_RESTART, sm_restart),
+	HANDLER(SM_CLKDIV_RESTART, sm_clkdiv_restart),
+	HANDLER(SM_ENABLE_SYNC, sm_enable_sync),
+	HANDLER(SM_PUT, sm_put),
+	HANDLER(SM_GET, sm_get),
+	HANDLER(SM_SET_DMACTRL, sm_set_dmactrl),
+	HANDLER(SM_FIFO_STATE, sm_fifo_state),
+	HANDLER(SM_DRAIN_TX, sm_drain_tx),
+
+	HANDLER(GPIO_INIT, gpio_init),
+	HANDLER(GPIO_SET_FUNCTION, gpio_set_function),
+	HANDLER(GPIO_SET_PULLS, gpio_set_pulls),
+	HANDLER(GPIO_SET_OUTOVER, gpio_set_outover),
+	HANDLER(GPIO_SET_INOVER, gpio_set_inover),
+	HANDLER(GPIO_SET_OEOVER, gpio_set_oeover),
+	HANDLER(GPIO_SET_INPUT_ENABLED, gpio_set_input_enabled),
+	HANDLER(GPIO_SET_DRIVE_STRENGTH, gpio_set_drive_strength),
+
+	HANDLER(READ_HW, read_hw),
+	HANDLER(WRITE_HW, write_hw),
+};
+
+struct rp1_pio_client *rp1_pio_open(void)
+{
+	struct rp1_pio_client *client;
+
+	if (!g_pio)
+		return ERR_PTR(-EPROBE_DEFER);
+	if (IS_ERR(g_pio))
+		return ERR_CAST(g_pio);
+
+	client = kzalloc(sizeof(*client), GFP_KERNEL);
+	if (!client)
+		return ERR_PTR(-ENOMEM);
+
+	client->pio = g_pio;
+
+	return client;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_open);
+
+void rp1_pio_close(struct rp1_pio_client *client)
+{
+	struct rp1_pio_device *pio = client->pio;
+	uint claimed_dmas = client->claimed_dmas;
+	int i;
+
+	/* Free any allocated resources */
+
+	for (i = 0; claimed_dmas; i++) {
+		uint mask = (1 << i);
+
+		if (claimed_dmas & mask) {
+			struct dma_info *dma = &pio->dma_configs[i >> 1][i & 1];
+
+			claimed_dmas &= ~mask;
+			rp1_pio_sm_dma_free(&pio->pdev->dev, dma);
+		}
+	}
+
+	spin_lock(&pio->lock);
+	pio->claimed_dmas &= ~client->claimed_dmas;
+	spin_unlock(&pio->lock);
+
+	if (client->claimed_sms) {
+		struct rp1_pio_sm_set_enabled_args se_args = {
+			.mask = client->claimed_sms, .enable = 0
+		};
+		struct rp1_pio_sm_claim_args uc_args = {
+			.mask = client->claimed_sms
+		};
+
+		rp1_pio_sm_set_enabled(client, &se_args);
+		rp1_pio_sm_unclaim(client, &uc_args);
+	}
+
+	if (client->claimed_instrs)
+		rp1_pio_remove_instrs(pio, client->claimed_instrs);
+
+	/* Reinitialise the SM? */
+
+	kfree(client);
+}
+EXPORT_SYMBOL_GPL(rp1_pio_close);
+
+void rp1_pio_set_error(struct rp1_pio_client *client, int err)
+{
+	client->error = err;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_set_error);
+
+int rp1_pio_get_error(const struct rp1_pio_client *client)
+{
+	return client->error;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_get_error);
+
+void rp1_pio_clear_error(struct rp1_pio_client *client)
+{
+	client->error = 0;
+}
+EXPORT_SYMBOL_GPL(rp1_pio_clear_error);
+
+static int rp1_pio_file_open(struct inode *inode, struct file *filp)
+{
+	struct rp1_pio_client *client;
+
+	client = rp1_pio_open();
+	if (IS_ERR(client))
+		return PTR_ERR(client);
+
+	filp->private_data = client;
+
+	return 0;
+}
+
+static int rp1_pio_file_release(struct inode *inode, struct file *filp)
+{
+	struct rp1_pio_client *client = filp->private_data;
+
+	rp1_pio_close(client);
+
+	return 0;
+}
+
+static long rp1_pio_ioctl(struct file *filp, unsigned int ioctl_num,
+			  unsigned long ioctl_param)
+{
+	struct rp1_pio_client *client = filp->private_data;
+	struct device *dev = &client->pio->pdev->dev;
+	void __user *argp = (void __user *)ioctl_param;
+	int nr = _IOC_NR(ioctl_num);
+	int sz = _IOC_SIZE(ioctl_num);
+	struct handler_info *hdlr = &ioctl_handlers[nr];
+	uint32_t argbuf[MAX_ARG_SIZE/sizeof(uint32_t)];
+	int ret;
+
+	if (nr >= ARRAY_SIZE(ioctl_handlers) || !hdlr->func) {
+		dev_err(dev, "unknown ioctl: %x\n", ioctl_num);
+		return -EOPNOTSUPP;
+	}
+
+	if (sz != hdlr->argsize) {
+		dev_err(dev, "wrong %s argsize (expected %d, got %d)\n",
+			hdlr->name, hdlr->argsize, sz);
+		return -EINVAL;
+	}
+
+	if (copy_from_user(argbuf, argp, sz))
+		return -EFAULT;
+
+	ret = (hdlr->func)(client, argbuf);
+	dev_dbg(dev, "%s: %s -> %d\n", __func__, hdlr->name, ret);
+	if (ret > 0) {
+		if (copy_to_user(argp, argbuf, ret))
+			ret = -EFAULT;
+	}
+
+	return ret;
+}
+
+#ifdef CONFIG_COMPAT
+
+struct rp1_pio_sm_xfer_data_args_compat {
+	uint16_t sm;
+	uint16_t dir;
+	uint16_t data_bytes;
+	compat_uptr_t data;
+};
+
+struct rp1_pio_sm_xfer_data32_args_compat {
+	uint16_t sm;
+	uint16_t dir;
+	uint32_t data_bytes;
+	compat_uptr_t data;
+};
+
+struct rp1_access_hw_args_compat {
+	uint32_t addr;
+	uint32_t len;
+	compat_uptr_t data;
+};
+
+#define PIO_IOC_SM_XFER_DATA_COMPAT \
+	_IOW(PIO_IOC_MAGIC, 1, struct rp1_pio_sm_xfer_data_args_compat)
+#define PIO_IOC_SM_XFER_DATA32_COMPAT \
+	_IOW(PIO_IOC_MAGIC, 2, struct rp1_pio_sm_xfer_data32_args_compat)
+#define PIO_IOC_READ_HW_COMPAT _IOW(PIO_IOC_MAGIC, 8, struct rp1_access_hw_args_compat)
+#define PIO_IOC_WRITE_HW_COMPAT _IOW(PIO_IOC_MAGIC, 9, struct rp1_access_hw_args_compat)
+
+static long rp1_pio_compat_ioctl(struct file *filp, unsigned int ioctl_num,
+				 unsigned long ioctl_param)
+{
+	struct rp1_pio_client *client = filp->private_data;
+
+	switch (ioctl_num) {
+	case PIO_IOC_SM_XFER_DATA_COMPAT:
+	{
+		struct rp1_pio_sm_xfer_data_args_compat compat_param;
+		struct rp1_pio_sm_xfer_data_args param;
+
+		if (copy_from_user(&compat_param, compat_ptr(ioctl_param), sizeof(compat_param)))
+			return -EFAULT;
+		param.sm = compat_param.sm;
+		param.dir = compat_param.dir;
+		param.data_bytes = compat_param.data_bytes;
+		param.data = compat_ptr(compat_param.data);
+		return rp1_pio_sm_xfer_data_user(client, &param);
+	}
+	case PIO_IOC_SM_XFER_DATA32_COMPAT:
+	{
+		struct rp1_pio_sm_xfer_data32_args_compat compat_param;
+		struct rp1_pio_sm_xfer_data32_args param;
+
+		if (copy_from_user(&compat_param, compat_ptr(ioctl_param), sizeof(compat_param)))
+			return -EFAULT;
+		param.sm = compat_param.sm;
+		param.dir = compat_param.dir;
+		param.data_bytes = compat_param.data_bytes;
+		param.data = compat_ptr(compat_param.data);
+		return rp1_pio_sm_xfer_data32_user(client, &param);
+	}
+
+	case PIO_IOC_READ_HW_COMPAT:
+	case PIO_IOC_WRITE_HW_COMPAT:
+	{
+		struct rp1_access_hw_args_compat compat_param;
+		struct rp1_access_hw_args param;
+
+		if (copy_from_user(&compat_param, compat_ptr(ioctl_param), sizeof(compat_param)))
+			return -EFAULT;
+		param.addr = compat_param.addr;
+		param.len = compat_param.len;
+		param.data = compat_ptr(compat_param.data);
+		if (ioctl_num == PIO_IOC_READ_HW_COMPAT)
+			return rp1_pio_read_hw(client, &param);
+		else
+			return rp1_pio_write_hw(client, &param);
+	}
+	default:
+		return rp1_pio_ioctl(filp, ioctl_num, ioctl_param);
+	}
+}
+#else
+#define rp1_pio_compat_ioctl NULL
+#endif
+
+const struct file_operations rp1_pio_fops = {
+	.owner =	THIS_MODULE,
+	.open =		rp1_pio_file_open,
+	.release =	rp1_pio_file_release,
+	.unlocked_ioctl = rp1_pio_ioctl,
+	.compat_ioctl = rp1_pio_compat_ioctl,
+};
+
+static int rp1_pio_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct resource *ioresource;
+	struct rp1_pio_device *pio;
+	struct rp1_firmware *fw;
+	uint32_t op_count = 0;
+	uint32_t op_base = 0;
+	struct device *cdev;
+	char dev_name[16];
+	void *p;
+	int ret;
+	int i;
+
+	/* Run-time check for a build-time misconfiguration */
+	for (i = 0; i < ARRAY_SIZE(ioctl_handlers); i++) {
+		struct handler_info *hdlr = &ioctl_handlers[i];
+
+		if (WARN_ON(hdlr->argsize > MAX_ARG_SIZE))
+			return -EINVAL;
+	}
+
+	pdev->id = of_alias_get_id(pdev->dev.of_node, "pio");
+	if (pdev->id < 0)
+		return dev_err_probe(dev, pdev->id, "alias is missing\n");
+
+	fw = devm_rp1_firmware_get(dev, dev->of_node);
+	if (!fw)
+		return dev_err_probe(dev, -EPROBE_DEFER, "failed to find RP1 firmware driver\n");
+	if (IS_ERR(fw)) {
+		dev_warn(dev, "failed to contact RP1 firmware\n");
+		ret = PTR_ERR(fw);
+		goto out_err;
+	}
+	ret = rp1_firmware_get_feature(fw, FOURCC_PIO, &op_base, &op_count);
+	if (ret < 0)
+		goto out_err;
+
+	pio = devm_kzalloc(&pdev->dev, sizeof(*pio), GFP_KERNEL);
+	if (!pio) {
+		ret = -ENOMEM;
+		goto out_err;
+	}
+
+	platform_set_drvdata(pdev, pio);
+	pio->fw_pio_base = op_base;
+	pio->fw_pio_count = op_count;
+	pio->pdev = pdev;
+	pio->fw = fw;
+	spin_lock_init(&pio->lock);
+	mutex_init(&pio->instr_mutex);
+
+	p = devm_platform_get_and_ioremap_resource(pdev, 0, &ioresource);
+	if (IS_ERR(p)) {
+		ret = PTR_ERR(p);
+		goto out_err;
+	}
+
+	pio->phys_addr = ioresource->start;
+
+	ret = alloc_chrdev_region(&pio->dev_num, 0, 1, DRIVER_NAME);
+	if (ret < 0) {
+		dev_err(dev, "alloc_chrdev_region failed (rc=%d)\n", ret);
+		goto out_err;
+	}
+
+	pio->dev_class = class_create(DRIVER_NAME);
+	if (IS_ERR(pio->dev_class)) {
+		ret = PTR_ERR(pio->dev_class);
+		dev_err(dev, "class_create failed (err %d)\n", ret);
+		goto out_unregister;
+	}
+
+	cdev_init(&pio->cdev, &rp1_pio_fops);
+	ret = cdev_add(&pio->cdev, pio->dev_num, 1);
+	if (ret) {
+		dev_err(dev, "cdev_add failed (err %d)\n", ret);
+		goto out_class_destroy;
+	}
+
+	sprintf(dev_name, "pio%d", pdev->id);
+	cdev = device_create(pio->dev_class, NULL, pio->dev_num, NULL, dev_name);
+	if (IS_ERR(cdev)) {
+		ret = PTR_ERR(cdev);
+		dev_err(dev, "%s: device_create failed (err %d)\n", __func__, ret);
+		goto out_cdev_del;
+	}
+
+	g_pio = pio;
+
+	dev_info(dev, "Created instance as %s\n", dev_name);
+	return 0;
+
+out_cdev_del:
+	cdev_del(&pio->cdev);
+
+out_class_destroy:
+	class_destroy(pio->dev_class);
+
+out_unregister:
+	unregister_chrdev_region(pio->dev_num, 1);
+
+out_err:
+	g_pio = ERR_PTR(ret);
+	return ret;
+}
+
+static void rp1_pio_remove(struct platform_device *pdev)
+{
+	struct rp1_pio_device *pio = platform_get_drvdata(pdev);
+
+	/* There should be no clients */
+
+	if (g_pio == pio)
+		g_pio = NULL;
+
+	device_destroy(pio->dev_class, pio->dev_num);
+	cdev_del(&pio->cdev);
+	class_destroy(pio->dev_class);
+	unregister_chrdev_region(pio->dev_num, 1);
+}
+
+static const struct of_device_id rp1_pio_ids[] = {
+	{ .compatible = "raspberrypi,rp1-pio" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, rp1_pio_ids);
+
+static struct platform_driver rp1_pio_driver = {
+	.driver	= {
+		.name		= "rp1-pio",
+		.of_match_table	= of_match_ptr(rp1_pio_ids),
+	},
+	.probe		= rp1_pio_probe,
+	.remove_new	= rp1_pio_remove,
+	.shutdown	= rp1_pio_remove,
+};
+
+module_platform_driver(rp1_pio_driver);
+
+MODULE_DESCRIPTION("PIO controller driver for Raspberry Pi RP1");
+MODULE_AUTHOR("Phil Elwell");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/ws2812-pio-rp1.c b/drivers/misc/ws2812-pio-rp1.c
new file mode 100644
index 000000000000..311446a32621
--- /dev/null
+++ b/drivers/misc/ws2812-pio-rp1.c
@@ -0,0 +1,507 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Raspberry Pi PIO-based WS2812 driver
+ *
+ * Copyright (C) 2014-2024 Raspberry Pi Ltd.
+ *
+ * Author: Phil Elwell (phil@raspberrypi.com)
+ *
+ * Based on the ws2812 driver by Gordon Hollingworth <gordon@raspberrypi.com>
+ */
+
+#include <linux/cdev.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fcntl.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/pio_rp1.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/gpio/consumer.h>
+
+#define DRIVER_NAME	"ws2812-pio-rp1"
+#define MAX_INSTANCES	4
+
+#define RESET_US	50
+#define PIXEL_BYTES	4
+
+struct ws2812_pio_rp1_state {
+	struct device	*dev;
+	struct gpio_desc *gpiod;
+	struct gpio_desc *power_gpiod;
+	uint		gpio;
+	PIO		pio;
+	uint		sm;
+	uint		offset;
+
+	u8		*buffer;
+	u8		*pixbuf;
+	u32		pixbuf_size;
+	u32		write_end;
+
+	u8		brightness;
+	u32		invert;
+	u32		num_leds;
+	u32		xfer_end_us;
+	bool		is_rgbw;
+	struct delayed_work deferred_work;
+
+	struct completion dma_completion;
+	struct cdev	cdev;
+	dev_t		dev_num;
+	const char	*dev_name;
+};
+
+static			DEFINE_MUTEX(ws2812_pio_mutex);
+static			DEFINE_IDA(ws2812_pio_ida);
+static long		ws2812_pio_ref_count;
+static struct class	*ws2812_pio_class;
+static dev_t		ws2812_pio_dev_num;
+/*
+ * WS2812B gamma correction
+ *	GammaE=255*(res/255).^(1/.45)
+ *	From: http://rgb-123.com/ws2812-color-output/
+ */
+
+static const u8 ws2812_gamma[] = {
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2,
+	2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5,
+	6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11,
+	11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18,
+	19, 19, 20, 21, 21, 22, 22, 23, 23, 24, 25, 25, 26, 27, 27, 28,
+	29, 29, 30, 31, 31, 32, 33, 34, 34, 35, 36, 37, 37, 38, 39, 40,
+	40, 41, 42, 43, 44, 45, 46, 46, 47, 48, 49, 50, 51, 52, 53, 54,
+	55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70,
+	71, 72, 73, 74, 76, 77, 78, 79, 80, 81, 83, 84, 85, 86, 88, 89,
+	90, 91, 93, 94, 95, 96, 98, 99, 100, 102, 103, 104, 106, 107, 109, 110,
+	111, 113, 114, 116, 117, 119, 120, 121, 123, 124, 126, 128, 129, 131, 132, 134,
+	135, 137, 138, 140, 142, 143, 145, 146, 148, 150, 151, 153, 155, 157, 158, 160,
+	162, 163, 165, 167, 169, 170, 172, 174, 176, 178, 179, 181, 183, 185, 187, 189,
+	191, 193, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220,
+	222, 224, 227, 229, 231, 233, 235, 237, 239, 241, 244, 246, 248, 250, 252, 255
+};
+
+// ------ //
+// ws2812 //
+// ------ //
+
+#define ws2812_wrap_target 0
+#define ws2812_wrap 3
+
+#define ws2812_T1 3
+#define ws2812_T2 4
+#define ws2812_T3 3
+
+static const uint16_t ws2812_program_instructions[] = {
+		//     .wrap_target
+	0x6221,	//  0: out    x, 1            side 0 [2]
+	0x1223,	//  1: jmp    !x, 3           side 1 [2]
+	0x1300,	//  2: jmp    0               side 1 [3]
+	0xa342,	//  3: nop                    side 0 [3]
+		//     .wrap
+};
+
+static const struct pio_program ws2812_program = {
+	.instructions = ws2812_program_instructions,
+	.length = 4,
+	.origin = -1,
+};
+
+static inline pio_sm_config ws2812_program_get_default_config(uint offset)
+{
+	pio_sm_config c = pio_get_default_sm_config();
+
+	sm_config_set_wrap(&c, offset + ws2812_wrap_target, offset + ws2812_wrap);
+	sm_config_set_sideset(&c, 1, false, false);
+	return c;
+}
+
+static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, uint freq,
+				       bool rgbw)
+{
+	int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3;
+	struct fp24_8 div;
+	pio_sm_config c;
+
+	pio_gpio_init(pio, pin);
+	pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
+	c = ws2812_program_get_default_config(offset);
+	sm_config_set_sideset_pins(&c, pin);
+	sm_config_set_out_shift(&c, false, true, rgbw ? 32 : 24);
+	sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
+	div = make_fp24_8(clock_get_hz(clk_sys), freq * cycles_per_bit);
+	sm_config_set_clkdiv(&c, div);
+	pio_sm_init(pio, sm, offset, &c);
+	pio_sm_set_enabled(pio, sm, true);
+}
+
+static uint8_t ws2812_apply_gamma(uint8_t brightness, uint8_t val)
+{
+	int bright;
+
+	if (!val || !brightness)
+		return val;
+	bright = (val * brightness) / 255;
+	return ws2812_gamma[bright];
+}
+
+static inline uint8_t *rgbw_u32(const struct ws2812_pio_rp1_state *state,
+			       uint8_t r, uint8_t g, uint8_t b, uint8_t w, uint8_t *p)
+{
+	p[0] = ws2812_apply_gamma(state->brightness, w);
+	p[1] = ws2812_apply_gamma(state->brightness, b);
+	p[2] = ws2812_apply_gamma(state->brightness, r);
+	p[3] = ws2812_apply_gamma(state->brightness, g);
+	return p + 4;
+}
+
+static void ws2812_dma_complete(void *param)
+{
+	struct ws2812_pio_rp1_state *state = param;
+
+	complete(&state->dma_completion);
+}
+
+static void ws2812_update_leds(struct ws2812_pio_rp1_state *state, uint length)
+{
+	init_completion(&state->dma_completion);
+	if (!pio_sm_xfer_data(state->pio, state->sm, PIO_DIR_TO_SM, length, state->buffer, 0,
+			      (void (*)(void *))ws2812_dma_complete, state)) {
+		wait_for_completion(&state->dma_completion);
+		usleep_range(RESET_US, RESET_US + 100);
+	}
+}
+
+static void ws2812_clear_leds(struct ws2812_pio_rp1_state *state)
+{
+	uint8_t *p_buffer;
+	uint length;
+	int i;
+
+	p_buffer = state->buffer;
+	for (i = 0; i < state->num_leds; i++)
+		p_buffer = rgbw_u32(state, 0, 0, 0, 0, p_buffer);
+
+	length = (void *)p_buffer - (void *)state->buffer;
+
+	ws2812_update_leds(state, length);
+}
+
+/*
+ * Function to write the RGB buffer to the WS2812 leds, the input buffer
+ * contains a sequence of up to num_leds RGB32 integers, these are then
+ * gamma-corrected before being sent to the PIO state machine.
+ */
+
+static ssize_t ws2812_pio_rp1_write(struct file *filp, const char __user *buf, size_t count,
+				    loff_t *ppos)
+{
+	struct ws2812_pio_rp1_state *state;
+	uint32_t pixbuf_size;
+	unsigned long delay;
+	loff_t pos = *ppos;
+	int err = 0;
+
+	state = (struct ws2812_pio_rp1_state *)filp->private_data;
+	pixbuf_size = state->pixbuf_size;
+
+	if (pos > pixbuf_size)
+		return -EFBIG;
+
+	if (count > pixbuf_size) {
+		err = -EFBIG;
+		count = pixbuf_size;
+	}
+
+	if (pos + count > pixbuf_size) {
+		if (!err)
+			err = -ENOSPC;
+
+		count = pixbuf_size - pos;
+	}
+
+	if (!pos && count == 1) {
+		if (copy_from_user(&state->brightness, buf, 1))
+			return -EFAULT;
+	} else {
+		if (copy_from_user(state->pixbuf + pos, buf, count))
+			return -EFAULT;
+		pos += count;
+		state->write_end = (u32)pos;
+	}
+
+	*ppos = pos;
+
+	delay = (state->write_end == pixbuf_size) ? 0 : HZ / 20;
+	schedule_delayed_work(&state->deferred_work, delay);
+
+	return err ? err : count;
+}
+
+static void ws2812_pio_rp1_deferred_work(struct work_struct *work)
+{
+	struct ws2812_pio_rp1_state *state =
+		container_of(work, struct ws2812_pio_rp1_state, deferred_work.work);
+	uint8_t *p_buffer;
+	uint32_t *p_rgb;
+	int blank_bytes;
+	uint length;
+	int i;
+
+	blank_bytes = state->pixbuf_size - state->write_end;
+	if (blank_bytes > 0)
+		memset(state->pixbuf + state->write_end, 0, blank_bytes);
+
+	p_rgb = (uint32_t *)state->pixbuf;
+	p_buffer = state->buffer;
+
+	for (i = 0; i < state->num_leds; i++) {
+		uint32_t rgbw_pix = *(p_rgb++);
+
+		p_buffer = rgbw_u32(state,
+				    (uint8_t)(rgbw_pix >> 0),
+				    (uint8_t)(rgbw_pix >> 8),
+				    (uint8_t)(rgbw_pix >> 16),
+				    (uint8_t)(rgbw_pix >> 24),
+				    p_buffer);
+	}
+
+	length = (void *)p_buffer - (void *)state->buffer;
+
+	ws2812_update_leds(state, length);
+}
+
+static int ws2812_pio_rp1_open(struct inode *inode, struct file *file)
+{
+	struct ws2812_pio_rp1_state *state;
+
+	state  = container_of(inode->i_cdev, struct ws2812_pio_rp1_state, cdev);
+	file->private_data = state;
+
+	return 0;
+}
+
+const struct file_operations ws2812_pio_rp1_fops = {
+	.owner = THIS_MODULE,
+	.write = ws2812_pio_rp1_write,
+	.open = ws2812_pio_rp1_open,
+};
+
+/*
+ * Probe function
+ */
+static int ws2812_pio_rp1_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct of_phandle_args of_args = { 0 };
+	struct ws2812_pio_rp1_state *state;
+	struct device *dev = &pdev->dev;
+	struct device *char_dev;
+	const char *dev_name;
+	uint32_t brightness;
+	bool is_rp1;
+	int minor;
+	int ret;
+
+	state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
+	if (IS_ERR(state))
+		return PTR_ERR(state);
+
+	state->dev = dev;
+
+	platform_set_drvdata(pdev, state);
+
+	ret = of_property_read_u32(np, "rpi,num-leds", &state->num_leds);
+	if (ret)
+		return dev_err_probe(dev, ret, "Could not get num-leds\n");
+
+	brightness = 255;
+	of_property_read_u32(np, "rpi,brightness", &brightness);
+	state->brightness = min(brightness, 255);
+
+	state->pixbuf_size = state->num_leds * PIXEL_BYTES;
+
+	state->is_rgbw = of_property_read_bool(np, "rpi,rgbw");
+	state->gpiod = devm_gpiod_get(dev, "leds", GPIOD_ASIS);
+	if (IS_ERR(state->gpiod))
+		return dev_err_probe(dev, PTR_ERR(state->gpiod),
+				     "Could not get a gpio\n");
+
+	/* This must be an RP1 GPIO in the first bank, and retrieve the offset. */
+	/* Unfortunately I think this has to be done by parsing the gpios property */
+
+	/* This really shouldn't fail, given that we have a gpiod */
+	if (of_parse_phandle_with_args(np, "leds-gpios", "#gpio-cells", 0, &of_args))
+		return dev_err_probe(dev, -EINVAL,
+				     "Can't find gpio declaration\n");
+
+	is_rp1 = of_device_is_compatible(of_args.np, "raspberrypi,rp1-gpio");
+	of_node_put(of_args.np);
+	if (!is_rp1 || of_args.args_count != 2)
+		return dev_err_probe(dev, -EINVAL,
+				     "Not an RP1 gpio\n");
+
+	state->gpio = of_args.args[0];
+
+	state->pixbuf = devm_kmalloc(dev, state->pixbuf_size, GFP_KERNEL);
+	if (state->pixbuf == NULL)
+		return -ENOMEM;
+
+	state->buffer = devm_kmalloc(dev, state->num_leds * PIXEL_BYTES, GFP_KERNEL);
+	if (state->buffer == NULL)
+		return -ENOMEM;
+
+	ret = of_property_read_string(np, "dev-name", &dev_name);
+	if (ret) {
+		pr_err("Failed to read 'dev-name' property\n");
+		return ret;
+	}
+
+	state->pio = pio_open();
+	if (IS_ERR(state->pio))
+		return dev_err_probe(dev, PTR_ERR(state->pio),
+				     "Could not open PIO\n");
+
+	state->sm = pio_claim_unused_sm(state->pio, false);
+	if ((int)state->sm < 0) {
+		dev_err(dev, "No free PIO SM\n");
+		ret = -EBUSY;
+		goto fail_pio;
+	}
+
+	state->offset = pio_add_program(state->pio, &ws2812_program);
+	if (state->offset == PIO_ORIGIN_ANY) {
+		dev_err(dev, "Not enough PIO program space\n");
+		ret = -EBUSY;
+		goto fail_pio;
+	}
+
+	pio_sm_config_xfer(state->pio, state->sm, PIO_DIR_TO_SM, state->num_leds * sizeof(int), 1);
+
+	pio_sm_clear_fifos(state->pio, state->sm);
+	pio_sm_set_clkdiv(state->pio, state->sm, make_fp24_8(1, 1));
+	ws2812_program_init(state->pio, state->sm, state->offset, state->gpio, 800000,
+			    state->is_rgbw);
+
+	mutex_lock(&ws2812_pio_mutex);
+
+	if (!ws2812_pio_ref_count) {
+		ret = alloc_chrdev_region(&ws2812_pio_dev_num, 0, MAX_INSTANCES, DRIVER_NAME);
+		if (ret < 0) {
+			dev_err(dev, "alloc_chrdev_region failed (rc=%d)\n", ret);
+			goto fail_mutex;
+		}
+
+		ws2812_pio_class = class_create(DRIVER_NAME);
+		if (IS_ERR(ws2812_pio_class)) {
+			pr_err("Unable to create class " DRIVER_NAME "\n");
+			ret = PTR_ERR(ws2812_pio_class);
+			goto fail_chrdev;
+		}
+	}
+
+	ws2812_pio_ref_count++;
+
+	minor = ida_alloc_range(&ws2812_pio_ida, 0, MAX_INSTANCES - 1, GFP_KERNEL);
+	if (minor < 0) {
+		pr_err("No free instances\n");
+		ret = minor;
+		goto fail_class;
+
+	}
+
+	mutex_unlock(&ws2812_pio_mutex);
+
+	state->dev_num = MKDEV(MAJOR(ws2812_pio_dev_num), minor);
+	state->dev_name = devm_kasprintf(dev, GFP_KERNEL, dev_name, minor);
+
+	char_dev = device_create(ws2812_pio_class, NULL, state->dev_num, NULL, state->dev_name);
+
+	if (IS_ERR(char_dev)) {
+		pr_err("Unable to create device %s\n", state->dev_name);
+		ret = PTR_ERR(char_dev);
+		goto fail_ida;
+	}
+
+	state->cdev.owner = THIS_MODULE;
+	cdev_init(&state->cdev, &ws2812_pio_rp1_fops);
+
+	ret = cdev_add(&state->cdev, state->dev_num, 1);
+	if (ret) {
+		pr_err("cdev_add failed\n");
+		goto fail_device;
+	}
+
+	INIT_DELAYED_WORK(&state->deferred_work, ws2812_pio_rp1_deferred_work);
+
+	ws2812_clear_leds(state);
+
+	dev_info(&pdev->dev, "Instantiated %d LEDs on GPIO %d as /dev/%s\n",
+		 state->num_leds, state->gpio, state->dev_name);
+
+	return 0;
+
+fail_device:
+	device_destroy(ws2812_pio_class, state->dev_num);
+fail_ida:
+	mutex_lock(&ws2812_pio_mutex);
+	ida_free(&ws2812_pio_ida, minor);
+fail_class:
+	ws2812_pio_ref_count--;
+	if (ws2812_pio_ref_count)
+		goto fail_mutex;
+	class_destroy(ws2812_pio_class);
+fail_chrdev:
+	unregister_chrdev_region(ws2812_pio_dev_num, MAX_INSTANCES);
+fail_mutex:
+	mutex_unlock(&ws2812_pio_mutex);
+fail_pio:
+	pio_close(state->pio);
+
+	return ret;
+}
+
+static void ws2812_pio_rp1_remove(struct platform_device *pdev)
+{
+	struct ws2812_pio_rp1_state *state = platform_get_drvdata(pdev);
+
+	cancel_delayed_work(&state->deferred_work);
+	platform_set_drvdata(pdev, NULL);
+
+	cdev_del(&state->cdev);
+	device_destroy(ws2812_pio_class, state->dev_num);
+
+	mutex_lock(&ws2812_pio_mutex);
+	ida_free(&ws2812_pio_ida, MINOR(state->dev_num));
+	ws2812_pio_ref_count--;
+	if (!ws2812_pio_ref_count) {
+		class_destroy(ws2812_pio_class);
+		unregister_chrdev_region(ws2812_pio_dev_num, MAX_INSTANCES);
+	}
+	mutex_unlock(&ws2812_pio_mutex);
+
+	pio_close(state->pio);
+}
+
+static const struct of_device_id ws2812_pio_rp1_match[] = {
+	{ .compatible = "raspberrypi,ws2812-pio-rp1" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ws2812_pio_rp1_match);
+
+static struct platform_driver ws2812_pio_rp1_driver = {
+	.driver = {
+		.name = "ws2812-pio-rp1",
+		.of_match_table = ws2812_pio_rp1_match,
+	},
+	.probe = ws2812_pio_rp1_probe,
+	.remove_new  = ws2812_pio_rp1_remove,
+};
+module_platform_driver(ws2812_pio_rp1_driver);
+
+MODULE_DESCRIPTION("WS2812 PIO RP1 driver");
+MODULE_AUTHOR("Phil Elwell");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c
index 08b8276e1da9..8ace6dbfc0d2 100644
--- a/drivers/mmc/core/block.c
+++ b/drivers/mmc/core/block.c
@@ -218,6 +218,13 @@ static DEFINE_MUTEX(open_lock);
 module_param(perdev_minors, int, 0444);
 MODULE_PARM_DESC(perdev_minors, "Minors numbers to allocate per device");
 
+/*
+ * Allow quirks to be overridden for the current card
+ */
+static char *card_quirks;
+module_param(card_quirks, charp, 0644);
+MODULE_PARM_DESC(card_quirks, "Force the use of the indicated quirks (a bitfield)");
+
 static inline int mmc_blk_part_switch(struct mmc_card *card,
 				      unsigned int part_type);
 static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq,
@@ -927,7 +934,10 @@ static int mmc_blk_part_switch_pre(struct mmc_card *card,
 
 	if ((part_type & mask) == rpmb) {
 		if (card->ext_csd.cmdq_en) {
-			ret = mmc_cmdq_disable(card);
+			if (mmc_card_sd(card))
+				ret = mmc_sd_cmdq_disable(card);
+			else
+				ret = mmc_cmdq_disable(card);
 			if (ret)
 				return ret;
 		}
@@ -946,8 +956,12 @@ static int mmc_blk_part_switch_post(struct mmc_card *card,
 
 	if ((part_type & mask) == rpmb) {
 		mmc_retune_unpause(card->host);
-		if (card->reenable_cmdq && !card->ext_csd.cmdq_en)
-			ret = mmc_cmdq_enable(card);
+		if (card->reenable_cmdq && !card->ext_csd.cmdq_en) {
+			if (mmc_card_sd(card))
+				ret = mmc_sd_cmdq_enable(card);
+			else
+				ret = mmc_cmdq_enable(card);
+		}
 	}
 
 	return ret;
@@ -1158,7 +1172,10 @@ static void mmc_blk_issue_drv_op(struct mmc_queue *mq, struct request *req)
 	switch (mq_rq->drv_op) {
 	case MMC_DRV_OP_IOCTL:
 		if (card->ext_csd.cmdq_en) {
-			ret = mmc_cmdq_disable(card);
+			if (mmc_card_sd(card))
+				ret = mmc_sd_cmdq_disable(card);
+			else
+				ret = mmc_cmdq_disable(card);
 			if (ret)
 				break;
 		}
@@ -1176,8 +1193,12 @@ static void mmc_blk_issue_drv_op(struct mmc_queue *mq, struct request *req)
 		/* Always switch back to main area after RPMB access */
 		if (rpmb_ioctl)
 			mmc_blk_part_switch(card, 0);
-		else if (card->reenable_cmdq && !card->ext_csd.cmdq_en)
-			mmc_cmdq_enable(card);
+		else if (card->reenable_cmdq && !card->ext_csd.cmdq_en) {
+			if (mmc_card_sd(card))
+				mmc_sd_cmdq_enable(card);
+			else
+				mmc_cmdq_enable(card);
+		}
 		break;
 	case MMC_DRV_OP_BOOT_WP:
 		ret = mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, EXT_CSD_BOOT_WP,
@@ -1218,12 +1239,26 @@ static void mmc_blk_issue_erase_rq(struct mmc_queue *mq, struct request *req,
 	unsigned int from, nr;
 	int err = 0;
 	blk_status_t status = BLK_STS_OK;
+	bool restart_cmdq = false;
 
 	if (!mmc_can_erase(card)) {
 		status = BLK_STS_NOTSUPP;
 		goto fail;
 	}
 
+	/*
+	 * Only Discard ops are supported with SD cards in CQ mode
+	 * (SD Physical Spec v9.00 4.19.2)
+	 */
+	if (mmc_card_sd(card) && card->ext_csd.cmdq_en && erase_arg != SD_DISCARD_ARG) {
+		restart_cmdq = true;
+		err = mmc_sd_cmdq_disable(card);
+		if (err) {
+			status = BLK_STS_IOERR;
+			goto fail;
+		}
+	}
+
 	from = blk_rq_pos(req);
 	nr = blk_rq_sectors(req);
 
@@ -1244,6 +1279,11 @@ static void mmc_blk_issue_erase_rq(struct mmc_queue *mq, struct request *req,
 		status = BLK_STS_IOERR;
 	else
 		mmc_blk_reset_success(md, type);
+
+	if (restart_cmdq)
+		err = mmc_sd_cmdq_enable(card);
+	if (err)
+		status = BLK_STS_IOERR;
 fail:
 	blk_mq_end_request(req, status);
 }
@@ -1565,6 +1605,7 @@ static void mmc_blk_cqe_complete_rq(struct mmc_queue *mq, struct request *req)
 	struct request_queue *q = req->q;
 	struct mmc_host *host = mq->card->host;
 	enum mmc_issue_type issue_type = mmc_issue_type(mq, req);
+	bool write = req_op(req) == REQ_OP_WRITE;
 	unsigned long flags;
 	bool put_card;
 	int err;
@@ -1596,6 +1637,8 @@ static void mmc_blk_cqe_complete_rq(struct mmc_queue *mq, struct request *req)
 
 	spin_lock_irqsave(&mq->lock, flags);
 
+	if (write)
+		mq->pending_writes--;
 	mq->in_flight[issue_type] -= 1;
 
 	put_card = (mmc_tot_in_flight(mq) == 0);
@@ -2004,7 +2047,7 @@ static void mmc_blk_mq_rw_recovery(struct mmc_queue *mq, struct request *req)
 		return;
 	}
 
-	if (rq_data_dir(req) == READ && brq->data.blocks >
+	if (0 && rq_data_dir(req) == READ && brq->data.blocks >
 			queue_physical_block_size(mq->queue) >> 9) {
 		/* Read one (native) sector at a time */
 		mmc_blk_read_single(mq, req);
@@ -2112,6 +2155,8 @@ static void mmc_blk_mq_complete_rq(struct mmc_queue *mq, struct request *req)
 	struct mmc_queue_req *mqrq = req_to_mmc_queue_req(req);
 	unsigned int nr_bytes = mqrq->brq.data.bytes_xfered;
 
+	if (req_op(req) == REQ_OP_WRITE)
+		mq->pending_writes--;
 	if (nr_bytes) {
 		if (blk_update_request(req, BLK_STS_OK, nr_bytes))
 			blk_mq_requeue_request(req, true);
@@ -2206,13 +2251,17 @@ static void mmc_blk_mq_poll_completion(struct mmc_queue *mq,
 	mmc_blk_urgent_bkops(mq, mqrq);
 }
 
-static void mmc_blk_mq_dec_in_flight(struct mmc_queue *mq, enum mmc_issue_type issue_type)
+static void mmc_blk_mq_dec_in_flight(struct mmc_queue *mq, enum mmc_issue_type issue_type,
+				     bool write)
 {
 	unsigned long flags;
 	bool put_card;
 
 	spin_lock_irqsave(&mq->lock, flags);
 
+	if (write)
+		mq->pending_writes--;
+
 	mq->in_flight[issue_type] -= 1;
 
 	put_card = (mmc_tot_in_flight(mq) == 0);
@@ -2227,6 +2276,7 @@ static void mmc_blk_mq_post_req(struct mmc_queue *mq, struct request *req,
 				bool can_sleep)
 {
 	enum mmc_issue_type issue_type = mmc_issue_type(mq, req);
+	bool write = req_op(req) == REQ_OP_WRITE;
 	struct mmc_queue_req *mqrq = req_to_mmc_queue_req(req);
 	struct mmc_request *mrq = &mqrq->brq.mrq;
 	struct mmc_host *host = mq->card->host;
@@ -2246,7 +2296,7 @@ static void mmc_blk_mq_post_req(struct mmc_queue *mq, struct request *req,
 			blk_mq_complete_request(req);
 	}
 
-	mmc_blk_mq_dec_in_flight(mq, issue_type);
+	mmc_blk_mq_dec_in_flight(mq, issue_type, write);
 }
 
 void mmc_blk_mq_recovery(struct mmc_queue *mq)
@@ -3255,6 +3305,8 @@ static int mmc_blk_probe(struct mmc_card *card)
 {
 	struct mmc_blk_data *md;
 	int ret = 0;
+	char quirk_str[24];
+	char cap_str[10];
 
 	/*
 	 * Check that the card supports the command class(es) we need.
@@ -3262,7 +3314,16 @@ static int mmc_blk_probe(struct mmc_card *card)
 	if (!(card->csd.cmdclass & CCC_BLOCK_READ))
 		return -ENODEV;
 
-	mmc_fixup_device(card, mmc_blk_fixups);
+	if (card_quirks) {
+		unsigned long quirks;
+		if (kstrtoul(card_quirks, 0, &quirks) == 0)
+			card->quirks = (unsigned int)quirks;
+		else
+			pr_err("mmc_block: Invalid card_quirks parameter '%s'\n",
+			       card_quirks);
+	}
+	else
+		mmc_fixup_device(card, mmc_blk_fixups);
 
 	card->complete_wq = alloc_workqueue("mmc_complete",
 					WQ_MEM_RECLAIM | WQ_HIGHPRI, 0);
@@ -3277,6 +3338,17 @@ static int mmc_blk_probe(struct mmc_card *card)
 		goto out_free;
 	}
 
+	string_get_size((u64)get_capacity(md->disk), 512, STRING_UNITS_2,
+			cap_str, sizeof(cap_str));
+	if (card->quirks)
+		snprintf(quirk_str, sizeof(quirk_str),
+			 " (quirks 0x%08x)", card->quirks);
+	else
+		quirk_str[0] = '\0';
+	pr_info("%s: %s %s %s%s%s\n",
+		md->disk->disk_name, mmc_card_id(card), mmc_card_name(card),
+		cap_str, md->read_only ? " (ro)" : "", quirk_str);
+
 	ret = mmc_blk_alloc_parts(card, md);
 	if (ret)
 		goto out;
diff --git a/drivers/mmc/core/bus.c b/drivers/mmc/core/bus.c
index 4f3a26676ccb..27e2ff70165d 100644
--- a/drivers/mmc/core/bus.c
+++ b/drivers/mmc/core/bus.c
@@ -266,6 +266,8 @@ static void mmc_release_card(struct device *dev)
 
 	sdio_free_common_cis(card);
 
+	kfree(card->ext_reg_buf);
+
 	kfree(card->info);
 
 	kfree(card);
diff --git a/drivers/mmc/core/card.h b/drivers/mmc/core/card.h
index 9cbdd240c3a7..a36cda051d16 100644
--- a/drivers/mmc/core/card.h
+++ b/drivers/mmc/core/card.h
@@ -88,11 +88,13 @@ struct mmc_fixup {
 #define CID_MANFID_GIGASTONE    0x12
 #define CID_MANFID_MICRON       0x13
 #define CID_MANFID_SAMSUNG      0x15
+#define CID_MANFID_SAMSUNG_SD	0x1b
 #define CID_MANFID_APACER       0x27
 #define CID_MANFID_SWISSBIT     0x5D
 #define CID_MANFID_KINGSTON     0x70
 #define CID_MANFID_HYNIX	0x90
 #define CID_MANFID_KINGSTON_SD	0x9F
+#define CID_MANFID_LONGSYS_SD	0xAD
 #define CID_MANFID_NUMONYX	0xFE
 
 #define END_FIXUP { NULL }
@@ -300,4 +302,9 @@ static inline int mmc_card_no_uhs_ddr50_tuning(const struct mmc_card *c)
 	return c->quirks & MMC_QUIRK_NO_UHS_DDR50_TUNING;
 }
 
+static inline int mmc_card_working_sd_cq(const struct mmc_card *c)
+{
+	return c->quirks & MMC_QUIRK_WORKING_SD_CQ;
+}
+
 #endif
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 327029f5c59b..95d40fd26369 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -455,6 +455,7 @@ int mmc_cqe_start_req(struct mmc_host *host, struct mmc_request *mrq)
 		goto out_err;
 
 	trace_mmc_request_start(host, mrq);
+	led_trigger_event(host->led, LED_FULL);
 
 	return 0;
 
@@ -542,10 +543,18 @@ int mmc_cqe_recovery(struct mmc_host *host)
 	 * Recovery is expected seldom, if at all, but it reduces performance,
 	 * so make sure it is not completely silent.
 	 */
-	pr_warn("%s: running CQE recovery\n", mmc_hostname(host));
+	pr_warn_ratelimited("%s: running CQE recovery\n", mmc_hostname(host));
 
 	host->cqe_ops->cqe_recovery_start(host);
 
+	err = mmc_detect_card_removed(host);
+	if (err) {
+		host->cqe_ops->cqe_recovery_finish(host);
+		host->cqe_ops->cqe_off(host);
+		mmc_retune_release(host);
+		return err;
+	}
+
 	memset(&cmd, 0, sizeof(cmd));
 	cmd.opcode       = MMC_STOP_TRANSMISSION;
 	cmd.flags        = MMC_RSP_R1B | MMC_CMD_AC;
@@ -556,7 +565,11 @@ int mmc_cqe_recovery(struct mmc_host *host)
 	mmc_poll_for_busy(host->card, MMC_CQE_RECOVERY_TIMEOUT, true, MMC_BUSY_IO);
 
 	memset(&cmd, 0, sizeof(cmd));
-	cmd.opcode       = MMC_CMDQ_TASK_MGMT;
+	if (mmc_card_sd(host->card))
+		cmd.opcode = SD_CMDQ_TASK_MGMT;
+	else
+		cmd.opcode = MMC_CMDQ_TASK_MGMT;
+
 	cmd.arg          = 1; /* Discard entire queue */
 	cmd.flags        = MMC_RSP_R1B | MMC_CMD_AC;
 	cmd.flags       &= ~MMC_RSP_CRC; /* Ignore CRC */
@@ -1818,7 +1831,8 @@ EXPORT_SYMBOL(mmc_erase);
 
 int mmc_can_erase(struct mmc_card *card)
 {
-	if (card->csd.cmdclass & CCC_ERASE && card->erase_size)
+	if (card->csd.cmdclass & CCC_ERASE && card->erase_size &&
+	    !(card->quirks & MMC_QUIRK_ERASE_BROKEN))
 		return 1;
 	return 0;
 }
diff --git a/drivers/mmc/core/host.c b/drivers/mmc/core/host.c
index 48bda70145ee..018b77ade4c6 100644
--- a/drivers/mmc/core/host.c
+++ b/drivers/mmc/core/host.c
@@ -272,7 +272,7 @@ EXPORT_SYMBOL(mmc_of_parse_clk_phase);
 int mmc_of_parse(struct mmc_host *host)
 {
 	struct device *dev = host->parent;
-	u32 bus_width, drv_type, cd_debounce_delay_ms;
+	u32 bus_width, drv_type, cd_debounce_delay_ms, cq_allow;
 	int ret;
 
 	if (!dev || !dev_fwnode(dev))
@@ -407,6 +407,15 @@ int mmc_of_parse(struct mmc_host *host)
 		host->caps2 &= ~(MMC_CAP2_HS400_1_8V | MMC_CAP2_HS400_1_2V |
 				 MMC_CAP2_HS400_ES);
 
+	cq_allow = 0;
+	/*
+	 * Downstream property - if a u32 and 2 instead of a bool,
+	 * trust most A2 SD cards claiming CQ support.
+	 */
+	device_property_read_u32(dev, "supports-cqe", &cq_allow);
+	if (cq_allow == 2)
+		host->caps2 |= MMC_CAP2_SD_CQE_PERMISSIVE;
+
 	/* Must be after "non-removable" check */
 	if (device_property_read_u32(dev, "fixed-emmc-driver-type", &drv_type) == 0) {
 		if (host->caps & MMC_CAP_NONREMOVABLE)
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index 6a23be214543..851701b11266 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -1657,6 +1657,7 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
 		card->ocr = ocr;
 		card->type = MMC_TYPE_MMC;
 		card->rca = 1;
+		card->max_posted_writes = 1;
 		memcpy(card->raw_cid, cid, sizeof(card->raw_cid));
 	}
 
@@ -1915,13 +1916,14 @@ static int mmc_init_card(struct mmc_host *host, u32 ocr,
 			host->cqe_enabled = true;
 
 			if (card->ext_csd.cmdq_en) {
-				pr_info("%s: Command Queue Engine enabled\n",
-					mmc_hostname(host));
+				pr_info("%s: Command Queue Engine enabled, %u tags\n",
+					mmc_hostname(host), card->ext_csd.cmdq_depth);
 			} else {
 				host->hsq_enabled = true;
 				pr_info("%s: Host Software Queue enabled\n",
 					mmc_hostname(host));
 			}
+			card->max_posted_writes = card->ext_csd.cmdq_depth;
 		}
 	}
 
diff --git a/drivers/mmc/core/queue.c b/drivers/mmc/core/queue.c
index 4d6844261912..6d99da3a22fb 100644
--- a/drivers/mmc/core/queue.c
+++ b/drivers/mmc/core/queue.c
@@ -266,6 +266,11 @@ static blk_status_t mmc_mq_queue_rq(struct blk_mq_hw_ctx *hctx,
 			spin_unlock_irq(&mq->lock);
 			return BLK_STS_RESOURCE;
 		}
+		if (!host->hsq_enabled && host->cqe_enabled && req_op(req) == REQ_OP_WRITE &&
+		    mq->pending_writes >= card->max_posted_writes) {
+			spin_unlock_irq(&mq->lock);
+			return BLK_STS_RESOURCE;
+		}
 		break;
 	default:
 		/*
@@ -282,6 +287,8 @@ static blk_status_t mmc_mq_queue_rq(struct blk_mq_hw_ctx *hctx,
 	/* Parallel dispatch of requests is not supported at the moment */
 	mq->busy = true;
 
+	if (req_op(req) == REQ_OP_WRITE)
+		mq->pending_writes++;
 	mq->in_flight[issue_type] += 1;
 	get_card = (mmc_tot_in_flight(mq) == 1);
 	cqe_retune_ok = (mmc_cqe_qcnt(mq) == 1);
@@ -321,6 +328,8 @@ static blk_status_t mmc_mq_queue_rq(struct blk_mq_hw_ctx *hctx,
 		bool put_card = false;
 
 		spin_lock_irq(&mq->lock);
+		if (req_op(req) == REQ_OP_WRITE)
+			mq->pending_writes--;
 		mq->in_flight[issue_type] -= 1;
 		if (mmc_tot_in_flight(mq) == 0)
 			put_card = true;
diff --git a/drivers/mmc/core/queue.h b/drivers/mmc/core/queue.h
index 1498840a4ea0..39180d97911b 100644
--- a/drivers/mmc/core/queue.h
+++ b/drivers/mmc/core/queue.h
@@ -79,6 +79,7 @@ struct mmc_queue {
 	struct request_queue	*queue;
 	spinlock_t		lock;
 	int			in_flight[MMC_ISSUE_MAX];
+	int			pending_writes;
 	unsigned int		cqe_busy;
 #define MMC_CQE_DCMD_BUSY	BIT(0)
 	bool			busy;
diff --git a/drivers/mmc/core/quirks.h b/drivers/mmc/core/quirks.h
index c417ed34c057..f23319a5fb79 100644
--- a/drivers/mmc/core/quirks.h
+++ b/drivers/mmc/core/quirks.h
@@ -18,10 +18,22 @@
 static const struct mmc_fixup __maybe_unused mmc_sd_fixups[] = {
 	/*
 	 * Kingston Canvas Go! Plus microSD cards never finish SD cache flush.
-	 * This has so far only been observed on cards from 11/2019, while new
-	 * cards from 2023/05 do not exhibit this behavior.
+	 * This has been observed on cards from 2019/11 and 2021/11, while new
+	 * cards from 2023/05 and 2024/08 do not exhibit this behavior.
 	 */
-	_FIXUP_EXT("SD64G", CID_MANFID_KINGSTON_SD, 0x5449, 2019, 11,
+	_FIXUP_EXT(CID_NAME_ANY, CID_MANFID_KINGSTON_SD, 0x5449, 2019, CID_MONTH_ANY,
+		   0, -1ull, SDIO_ANY_ID, SDIO_ANY_ID, add_quirk_sd,
+		   MMC_QUIRK_BROKEN_SD_CACHE, EXT_CSD_REV_ANY),
+
+	_FIXUP_EXT(CID_NAME_ANY, CID_MANFID_KINGSTON_SD, 0x5449, 2020, CID_MONTH_ANY,
+		   0, -1ull, SDIO_ANY_ID, SDIO_ANY_ID, add_quirk_sd,
+		   MMC_QUIRK_BROKEN_SD_CACHE, EXT_CSD_REV_ANY),
+
+	_FIXUP_EXT(CID_NAME_ANY, CID_MANFID_KINGSTON_SD, 0x5449, 2021, CID_MONTH_ANY,
+		   0, -1ull, SDIO_ANY_ID, SDIO_ANY_ID, add_quirk_sd,
+		   MMC_QUIRK_BROKEN_SD_CACHE, EXT_CSD_REV_ANY),
+
+	_FIXUP_EXT(CID_NAME_ANY, CID_MANFID_KINGSTON_SD, 0x5449, 2022, CID_MONTH_ANY,
 		   0, -1ull, SDIO_ANY_ID, SDIO_ANY_ID, add_quirk_sd,
 		   MMC_QUIRK_BROKEN_SD_CACHE, EXT_CSD_REV_ANY),
 
@@ -45,10 +57,30 @@ static const struct mmc_fixup __maybe_unused mmc_sd_fixups[] = {
 		   MMC_QUIRK_NO_UHS_DDR50_TUNING, EXT_CSD_REV_ANY),
 
 	/*
-	 * Some SD cards reports discard support while they don't
+	 * Samsung Pro Plus/EVO Plus/Pro Ultimate SD cards (2023) claim to cache
+	 * flush OK, but become unresponsive afterwards.
 	 */
-	MMC_FIXUP(CID_NAME_ANY, CID_MANFID_SANDISK_SD, 0x5344, add_quirk_sd,
-		  MMC_QUIRK_BROKEN_SD_DISCARD),
+	_FIXUP_EXT(CID_NAME_ANY, CID_MANFID_SAMSUNG_SD, 0x534d, 2023, CID_MONTH_ANY,
+		   0, -1ull, SDIO_ANY_ID, SDIO_ANY_ID, add_quirk_sd,
+		   MMC_QUIRK_BROKEN_SD_CACHE, EXT_CSD_REV_ANY),
+
+	/*
+	 * Early Sandisk Extreme and Extreme Pro A2 cards never finish SD cache
+	 * flush in CQ mode. Latest card date this was seen on is 10/2020.
+	 */
+	_FIXUP_EXT(CID_NAME_ANY, CID_MANFID_SANDISK_SD, 0x5344, 2019, CID_MONTH_ANY,
+		   0, -1ull, SDIO_ANY_ID, SDIO_ANY_ID, add_quirk_sd,
+		   MMC_QUIRK_BROKEN_SD_CACHE, EXT_CSD_REV_ANY),
+
+	_FIXUP_EXT(CID_NAME_ANY, CID_MANFID_SANDISK_SD, 0x5344, 2020, CID_MONTH_ANY,
+		   0, -1ull, SDIO_ANY_ID, SDIO_ANY_ID, add_quirk_sd,
+		   MMC_QUIRK_BROKEN_SD_CACHE, EXT_CSD_REV_ANY),
+
+	/* SD A2 allow-list - only trust CQ on these cards */
+	/* Raspberry Pi A2 cards */
+	_FIXUP_EXT(CID_NAME_ANY, CID_MANFID_LONGSYS_SD, 0x4c53, CID_YEAR_ANY, CID_MONTH_ANY,
+		   cid_rev(1, 0, 0, 0), -1ull, SDIO_ANY_ID, SDIO_ANY_ID, add_quirk_sd,
+		   MMC_QUIRK_WORKING_SD_CQ, EXT_CSD_REV_ANY),
 
 	END_FIXUP
 };
@@ -153,6 +185,29 @@ static const struct mmc_fixup __maybe_unused mmc_blk_fixups[] = {
 	MMC_FIXUP("M62704", CID_MANFID_KINGSTON, 0x0100, add_quirk_mmc,
 		  MMC_QUIRK_TRIM_BROKEN),
 
+	/*
+	 * Some SD cards reports discard support while they don't
+	 */
+	MMC_FIXUP(CID_NAME_ANY, CID_MANFID_SANDISK_SD, 0x5344, add_quirk_sd,
+		  MMC_QUIRK_BROKEN_SD_DISCARD),
+
+	/*
+	 *  On some Kingston SD cards, multiple erases of less than 64
+	 *  sectors can cause corruption.
+	 */
+	MMC_FIXUP("SD16G", 0x41, 0x3432, add_quirk, MMC_QUIRK_ERASE_BROKEN),
+	MMC_FIXUP("SD32G", 0x41, 0x3432, add_quirk, MMC_QUIRK_ERASE_BROKEN),
+	MMC_FIXUP("SD64G", 0x41, 0x3432, add_quirk, MMC_QUIRK_ERASE_BROKEN),
+
+	/*
+	 * Larger Integral SD cards using rebranded Phison controllers trash
+	 * nearby flash blocks after erases.
+	 */
+	MMC_FIXUP("SD64G", 0x27, 0x5048, add_quirk, MMC_QUIRK_ERASE_BROKEN),
+	MMC_FIXUP("SD128", 0x27, 0x5048, add_quirk, MMC_QUIRK_ERASE_BROKEN),
+	MMC_FIXUP("SD256", 0x27, 0x5048, add_quirk, MMC_QUIRK_ERASE_BROKEN),
+	MMC_FIXUP("SD512", 0x27, 0x5048, add_quirk, MMC_QUIRK_ERASE_BROKEN),
+
 	END_FIXUP
 };
 
diff --git a/drivers/mmc/core/sd.c b/drivers/mmc/core/sd.c
index 916ae9996e9d..551553a44d97 100644
--- a/drivers/mmc/core/sd.c
+++ b/drivers/mmc/core/sd.c
@@ -726,7 +726,8 @@ MMC_DEV_ATTR(oemid, "0x%04x\n", card->cid.oemid);
 MMC_DEV_ATTR(serial, "0x%08x\n", card->cid.serial);
 MMC_DEV_ATTR(ocr, "0x%08x\n", card->ocr);
 MMC_DEV_ATTR(rca, "0x%04x\n", card->rca);
-
+MMC_DEV_ATTR(ext_perf, "%02x\n", card->ext_perf.feature_support);
+MMC_DEV_ATTR(ext_power, "%02x\n", card->ext_power.feature_support);
 
 static ssize_t mmc_dsr_show(struct device *dev, struct device_attribute *attr,
 			    char *buf)
@@ -788,6 +789,8 @@ static struct attribute *sd_std_attrs[] = {
 	&dev_attr_ocr.attr,
 	&dev_attr_rca.attr,
 	&dev_attr_dsr.attr,
+	&dev_attr_ext_perf.attr,
+	&dev_attr_ext_power.attr,
 	NULL,
 };
 
@@ -1027,98 +1030,16 @@ static bool mmc_sd_card_using_v18(struct mmc_card *card)
 	       (SD_MODE_UHS_SDR50 | SD_MODE_UHS_SDR104 | SD_MODE_UHS_DDR50);
 }
 
-static int sd_write_ext_reg(struct mmc_card *card, u8 fno, u8 page, u16 offset,
-			    u8 reg_data)
-{
-	struct mmc_host *host = card->host;
-	struct mmc_request mrq = {};
-	struct mmc_command cmd = {};
-	struct mmc_data data = {};
-	struct scatterlist sg;
-	u8 *reg_buf;
-
-	reg_buf = kzalloc(512, GFP_KERNEL);
-	if (!reg_buf)
-		return -ENOMEM;
-
-	mrq.cmd = &cmd;
-	mrq.data = &data;
-
-	/*
-	 * Arguments of CMD49:
-	 * [31:31] MIO (0 = memory).
-	 * [30:27] FNO (function number).
-	 * [26:26] MW - mask write mode (0 = disable).
-	 * [25:18] page number.
-	 * [17:9] offset address.
-	 * [8:0] length (0 = 1 byte).
-	 */
-	cmd.arg = fno << 27 | page << 18 | offset << 9;
-
-	/* The first byte in the buffer is the data to be written. */
-	reg_buf[0] = reg_data;
-
-	data.flags = MMC_DATA_WRITE;
-	data.blksz = 512;
-	data.blocks = 1;
-	data.sg = &sg;
-	data.sg_len = 1;
-	sg_init_one(&sg, reg_buf, 512);
-
-	cmd.opcode = SD_WRITE_EXTR_SINGLE;
-	cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
-
-	mmc_set_data_timeout(&data, card);
-	mmc_wait_for_req(host, &mrq);
-
-	kfree(reg_buf);
-
-	/*
-	 * Note that, the SD card is allowed to signal busy on DAT0 up to 1s
-	 * after the CMD49. Although, let's leave this to be managed by the
-	 * caller.
-	 */
-
-	if (cmd.error)
-		return cmd.error;
-	if (data.error)
-		return data.error;
-
-	return 0;
-}
-
-static int sd_read_ext_reg(struct mmc_card *card, u8 fno, u8 page,
-			   u16 offset, u16 len, u8 *reg_buf)
-{
-	u32 cmd_args;
-
-	/*
-	 * Command arguments of CMD48:
-	 * [31:31] MIO (0 = memory).
-	 * [30:27] FNO (function number).
-	 * [26:26] reserved (0).
-	 * [25:18] page number.
-	 * [17:9] offset address.
-	 * [8:0] length (0 = 1 byte, 1ff = 512 bytes).
-	 */
-	cmd_args = fno << 27 | page << 18 | offset << 9 | (len -1);
-
-	return mmc_send_adtc_data(card, card->host, SD_READ_EXTR_SINGLE,
-				  cmd_args, reg_buf, 512);
-}
-
 static int sd_parse_ext_reg_power(struct mmc_card *card, u8 fno, u8 page,
 				  u16 offset)
 {
 	int err;
 	u8 *reg_buf;
 
-	reg_buf = kzalloc(512, GFP_KERNEL);
-	if (!reg_buf)
-		return -ENOMEM;
+	reg_buf = card->ext_reg_buf;
 
 	/* Read the extension register for power management function. */
-	err = sd_read_ext_reg(card, fno, page, offset, 512, reg_buf);
+	err = mmc_sd_read_ext_reg(card, fno, page, offset, 512, reg_buf);
 	if (err) {
 		pr_warn("%s: error %d reading PM func of ext reg\n",
 			mmc_hostname(card->host), err);
@@ -1145,7 +1066,6 @@ static int sd_parse_ext_reg_power(struct mmc_card *card, u8 fno, u8 page,
 	card->ext_power.offset = offset;
 
 out:
-	kfree(reg_buf);
 	return err;
 }
 
@@ -1155,11 +1075,9 @@ static int sd_parse_ext_reg_perf(struct mmc_card *card, u8 fno, u8 page,
 	int err;
 	u8 *reg_buf;
 
-	reg_buf = kzalloc(512, GFP_KERNEL);
-	if (!reg_buf)
-		return -ENOMEM;
+	reg_buf = card->ext_reg_buf;
 
-	err = sd_read_ext_reg(card, fno, page, offset, 512, reg_buf);
+	err = mmc_sd_read_ext_reg(card, fno, page, offset, 512, reg_buf);
 	if (err) {
 		pr_warn("%s: error %d reading PERF func of ext reg\n",
 			mmc_hostname(card->host), err);
@@ -1185,16 +1103,34 @@ static int sd_parse_ext_reg_perf(struct mmc_card *card, u8 fno, u8 page,
 	if ((reg_buf[4] & BIT(0)) && !mmc_card_broken_sd_cache(card))
 		card->ext_perf.feature_support |= SD_EXT_PERF_CACHE;
 
-	/* Command queue support indicated via queue depth bits (0 to 4). */
-	if (reg_buf[6] & 0x1f)
+	/*
+	 * Command queue support indicated via queue depth bits (0 to 4).
+	 * Qualify this with the other mandatory required features.
+	 */
+	if (reg_buf[6] & 0x1f && card->ext_power.feature_support & SD_EXT_POWER_OFF_NOTIFY &&
+	    card->ext_perf.feature_support & SD_EXT_PERF_CACHE) {
 		card->ext_perf.feature_support |= SD_EXT_PERF_CMD_QUEUE;
+		card->ext_csd.cmdq_depth = reg_buf[6] & 0x1f;
+		card->ext_csd.cmdq_support = true;
+		pr_debug("%s: Command Queue supported depth %u\n",
+			 mmc_hostname(card->host),
+			 card->ext_csd.cmdq_depth);
+		/*
+		 * If CQ is enabled, there is a contract between host and card such that
+		 * VDD will be maintained and removed only if a power off notification
+		 * is provided. An SD card in an accessible slot means surprise removal
+		 * is a possibility. As a middle ground, keep the default maximum of 1
+		 * posted write unless the card is "hardwired".
+		 */
+		if (!mmc_card_is_removable(card->host))
+			card->max_posted_writes = card->ext_csd.cmdq_depth;
+	}
 
 	card->ext_perf.fno = fno;
 	card->ext_perf.page = page;
 	card->ext_perf.offset = offset;
 
 out:
-	kfree(reg_buf);
 	return err;
 }
 
@@ -1249,7 +1185,7 @@ static int sd_parse_ext_reg(struct mmc_card *card, u8 *gen_info_buf,
 	return 0;
 }
 
-static int sd_read_ext_regs(struct mmc_card *card)
+static int mmc_sd_read_ext_regs(struct mmc_card *card)
 {
 	int err, i;
 	u8 num_ext, *gen_info_buf;
@@ -1261,15 +1197,21 @@ static int sd_read_ext_regs(struct mmc_card *card)
 	if (!(card->scr.cmds & SD_SCR_CMD48_SUPPORT))
 		return 0;
 
-	gen_info_buf = kzalloc(512, GFP_KERNEL);
+	gen_info_buf = kzalloc(1024, GFP_KERNEL);
 	if (!gen_info_buf)
 		return -ENOMEM;
 
+	card->ext_reg_buf = kzalloc(512, GFP_KERNEL);
+	if (!card->ext_reg_buf) {
+		err = -ENOMEM;
+		goto out;
+	}
+
 	/*
 	 * Read 512 bytes of general info, which is found at function number 0,
 	 * at page 0 and with no offset.
 	 */
-	err = sd_read_ext_reg(card, 0, 0, 0, 512, gen_info_buf);
+	err = mmc_sd_read_ext_reg(card, 0, 0, 0, 512, gen_info_buf);
 	if (err) {
 		pr_err("%s: error %d reading general info of SD ext reg\n",
 			mmc_hostname(card->host), err);
@@ -1286,14 +1228,23 @@ static int sd_read_ext_regs(struct mmc_card *card)
 	num_ext = gen_info_buf[4];
 
 	/*
-	 * We only support revision 0 and limit it to 512 bytes for simplicity.
+	 * We only support revision 0 and up to the spec-defined maximum of 1K.
 	 * No matter what, let's return zero to allow us to continue using the
 	 * card, even if we can't support the features from the SD function
 	 * extensions registers.
 	 */
-	if (rev != 0 || len > 512) {
-		pr_warn("%s: non-supported SD ext reg layout\n",
-			mmc_hostname(card->host));
+	if (rev != 0 || len > 1024) {
+		pr_warn("%s: non-supported SD ext reg layout rev %u length %u\n",
+			mmc_hostname(card->host), rev, len);
+		goto out;
+	}
+
+	/* If the General Information block spills into the next page, read the rest */
+	if (len > 512)
+		err = mmc_sd_read_ext_reg(card, 0, 1, 0, 512, &gen_info_buf[512]);
+	if (err) {
+		pr_err("%s: error %d reading page 1 of general info of SD ext reg\n",
+			mmc_hostname(card->host), err);
 		goto out;
 	}
 
@@ -1331,9 +1282,7 @@ static int sd_flush_cache(struct mmc_host *host)
 	if (!sd_cache_enabled(host))
 		return 0;
 
-	reg_buf = kzalloc(512, GFP_KERNEL);
-	if (!reg_buf)
-		return -ENOMEM;
+	reg_buf = card->ext_reg_buf;
 
 	/*
 	 * Set Flush Cache at bit 0 in the performance enhancement register at
@@ -1343,7 +1292,7 @@ static int sd_flush_cache(struct mmc_host *host)
 	page = card->ext_perf.page;
 	offset = card->ext_perf.offset + 261;
 
-	err = sd_write_ext_reg(card, fno, page, offset, BIT(0));
+	err = mmc_sd_write_ext_reg(card, fno, page, offset, BIT(0));
 	if (err) {
 		pr_warn("%s: error %d writing Cache Flush bit\n",
 			mmc_hostname(host), err);
@@ -1359,7 +1308,7 @@ static int sd_flush_cache(struct mmc_host *host)
 	 * Read the Flush Cache bit. The card shall reset it, to confirm that
 	 * it's has completed the flushing of the cache.
 	 */
-	err = sd_read_ext_reg(card, fno, page, offset, 1, reg_buf);
+	err = mmc_sd_read_ext_reg(card, fno, page, offset, 1, reg_buf);
 	if (err) {
 		pr_warn("%s: error %d reading Cache Flush bit\n",
 			mmc_hostname(host), err);
@@ -1369,26 +1318,20 @@ static int sd_flush_cache(struct mmc_host *host)
 	if (reg_buf[0] & BIT(0))
 		err = -ETIMEDOUT;
 out:
-	kfree(reg_buf);
 	return err;
 }
 
 static int sd_enable_cache(struct mmc_card *card)
 {
-	u8 *reg_buf;
 	int err;
 
 	card->ext_perf.feature_enabled &= ~SD_EXT_PERF_CACHE;
 
-	reg_buf = kzalloc(512, GFP_KERNEL);
-	if (!reg_buf)
-		return -ENOMEM;
-
 	/*
 	 * Set Cache Enable at bit 0 in the performance enhancement register at
 	 * 260 bytes offset.
 	 */
-	err = sd_write_ext_reg(card, card->ext_perf.fno, card->ext_perf.page,
+	err = mmc_sd_write_ext_reg(card, card->ext_perf.fno, card->ext_perf.page,
 			       card->ext_perf.offset + 260, BIT(0));
 	if (err) {
 		pr_warn("%s: error %d writing Cache Enable bit\n",
@@ -1402,7 +1345,6 @@ static int sd_enable_cache(struct mmc_card *card)
 		card->ext_perf.feature_enabled |= SD_EXT_PERF_CACHE;
 
 out:
-	kfree(reg_buf);
 	return err;
 }
 
@@ -1445,6 +1387,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
 
 		card->ocr = ocr;
 		card->type = MMC_TYPE_SD;
+		card->max_posted_writes = 1;
 		memcpy(card->raw_cid, cid, sizeof(card->raw_cid));
 	}
 
@@ -1562,7 +1505,7 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
 cont:
 	if (!oldcard) {
 		/* Read/parse the extension registers. */
-		err = sd_read_ext_regs(card);
+		err = mmc_sd_read_ext_regs(card);
 		if (err)
 			goto free_card;
 	}
@@ -1574,13 +1517,45 @@ static int mmc_sd_init_card(struct mmc_host *host, u32 ocr,
 			goto free_card;
 	}
 
+	/* Disallow command queueing on unvetted cards unless overridden */
+	if (!(host->caps2 & MMC_CAP2_SD_CQE_PERMISSIVE) && !mmc_card_working_sd_cq(card))
+		card->ext_csd.cmdq_support = false;
+
+	/* Enable command queueing if supported */
+	if (card->ext_csd.cmdq_support && host->caps2 & MMC_CAP2_CQE) {
+		/*
+		 * Right now the MMC block layer uses DCMDs to issue
+		 * cache-flush commands specific to eMMC devices.
+		 * Turning off DCMD support avoids generating Illegal Command
+		 * errors on SD, and flushing is instead done synchronously
+		 * by mmc_blk_issue_flush().
+		 */
+		host->caps2 &= ~MMC_CAP2_CQE_DCMD;
+		err = mmc_sd_cmdq_enable(card);
+		if (err && err != -EBADMSG)
+			goto free_card;
+		if (err) {
+			pr_warn("%s: Enabling CMDQ failed\n",
+				mmc_hostname(card->host));
+			card->ext_csd.cmdq_support = false;
+			card->ext_csd.cmdq_depth = 0;
+		}
+	}
+	card->reenable_cmdq = card->ext_csd.cmdq_en;
+
 	if (host->cqe_ops && !host->cqe_enabled) {
 		err = host->cqe_ops->cqe_enable(host, card);
 		if (!err) {
 			host->cqe_enabled = true;
-			host->hsq_enabled = true;
-			pr_info("%s: Host Software Queue enabled\n",
-				mmc_hostname(host));
+
+			if (card->ext_csd.cmdq_en) {
+				pr_info("%s: Command Queue Engine enabled, %u tags\n",
+					mmc_hostname(host), card->ext_csd.cmdq_depth);
+			} else {
+				host->hsq_enabled = true;
+				pr_info("%s: Host Software Queue enabled\n",
+					mmc_hostname(host));
+			}
 		}
 	}
 
@@ -1661,7 +1636,7 @@ static int sd_busy_poweroff_notify_cb(void *cb_data, bool *busy)
 	 * one byte offset and is one byte long. The Power Off Notification
 	 * Ready is bit 0.
 	 */
-	err = sd_read_ext_reg(card, card->ext_power.fno, card->ext_power.page,
+	err = mmc_sd_read_ext_reg(card, card->ext_power.fno, card->ext_power.page,
 			      card->ext_power.offset + 1, 1, data->reg_buf);
 	if (err) {
 		pr_warn("%s: error %d reading status reg of PM func\n",
@@ -1687,7 +1662,7 @@ static int sd_poweroff_notify(struct mmc_card *card)
 	 * Set the Power Off Notification bit in the power management settings
 	 * register at 2 bytes offset.
 	 */
-	err = sd_write_ext_reg(card, card->ext_power.fno, card->ext_power.page,
+	err = mmc_sd_write_ext_reg(card, card->ext_power.fno, card->ext_power.page,
 			       card->ext_power.offset + 2, BIT(0));
 	if (err) {
 		pr_warn("%s: error %d writing Power Off Notify bit\n",
diff --git a/drivers/mmc/core/sd_ops.c b/drivers/mmc/core/sd_ops.c
index f93c392040ae..47a1903268b2 100644
--- a/drivers/mmc/core/sd_ops.c
+++ b/drivers/mmc/core/sd_ops.c
@@ -8,6 +8,7 @@
 #include <linux/slab.h>
 #include <linux/types.h>
 #include <linux/export.h>
+#include <linux/ktime.h>
 #include <linux/scatterlist.h>
 
 #include <linux/mmc/host.h>
@@ -393,3 +394,136 @@ int mmc_app_sd_status(struct mmc_card *card, void *ssr)
 
 	return 0;
 }
+
+
+int mmc_sd_write_ext_reg(struct mmc_card *card, u8 fno, u8 page, u16 offset,
+		     u8 reg_data)
+{
+	struct mmc_host *host = card->host;
+	struct mmc_request mrq = {};
+	struct mmc_command cmd = {};
+	struct mmc_data data = {};
+	struct scatterlist sg;
+	u8 *reg_buf;
+
+	reg_buf = card->ext_reg_buf;
+	memset(reg_buf, 0, 512);
+
+	mrq.cmd = &cmd;
+	mrq.data = &data;
+
+	/*
+	 * Arguments of CMD49:
+	 * [31:31] MIO (0 = memory).
+	 * [30:27] FNO (function number).
+	 * [26:26] MW - mask write mode (0 = disable).
+	 * [25:18] page number.
+	 * [17:9] offset address.
+	 * [8:0] length (0 = 1 byte).
+	 */
+	cmd.arg = fno << 27 | page << 18 | offset << 9;
+
+	/* The first byte in the buffer is the data to be written. */
+	reg_buf[0] = reg_data;
+
+	data.flags = MMC_DATA_WRITE;
+	data.blksz = 512;
+	data.blocks = 1;
+	data.sg = &sg;
+	data.sg_len = 1;
+	sg_init_one(&sg, reg_buf, 512);
+
+	cmd.opcode = SD_WRITE_EXTR_SINGLE;
+	cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
+
+	mmc_set_data_timeout(&data, card);
+	mmc_wait_for_req(host, &mrq);
+
+	/*
+	 * Note that, the SD card is allowed to signal busy on DAT0 up to 1s
+	 * after the CMD49. Although, let's leave this to be managed by the
+	 * caller.
+	 */
+
+	if (cmd.error)
+		return cmd.error;
+	if (data.error)
+		return data.error;
+
+	return 0;
+}
+
+int mmc_sd_read_ext_reg(struct mmc_card *card, u8 fno, u8 page,
+			u16 offset, u16 len, u8 *reg_buf)
+{
+	u32 cmd_args;
+
+	/*
+	 * Command arguments of CMD48:
+	 * [31:31] MIO (0 = memory).
+	 * [30:27] FNO (function number).
+	 * [26:26] reserved (0).
+	 * [25:18] page number.
+	 * [17:9] offset address.
+	 * [8:0] length (0 = 1 byte, 1ff = 512 bytes).
+	 */
+	cmd_args = fno << 27 | page << 18 | offset << 9 | (len - 1);
+
+	return mmc_send_adtc_data(card, card->host, SD_READ_EXTR_SINGLE,
+				  cmd_args, reg_buf, 512);
+}
+
+static int mmc_sd_cmdq_switch(struct mmc_card *card, bool enable)
+{
+	int err;
+	u8 reg = 0;
+	u8 *reg_buf = card->ext_reg_buf;
+	ktime_t timeout;
+	/*
+	 * SD offers two command queueing modes - sequential (in-order) and
+	 * voluntary (out-of-order). Apps Class A2 performance is only
+	 * guaranteed for voluntary CQ (bit 1 = 0), so use that in preference
+	 * to sequential.
+	 */
+	if (enable)
+		reg = BIT(0);
+
+	/* Performance enhancement register byte 262 controls command queueing */
+	err = mmc_sd_write_ext_reg(card, card->ext_perf.fno, card->ext_perf.page,
+				   card->ext_perf.offset + 262, reg);
+	if (err)
+		goto out;
+
+	/* Poll the register - cards may have a lazy init/deinit sequence. */
+	timeout = ktime_add_ms(ktime_get(), 10);
+	while (1) {
+		err = mmc_sd_read_ext_reg(card, card->ext_perf.fno, card->ext_perf.page,
+					  card->ext_perf.offset + 262, 1, reg_buf);
+		if (err)
+			break;
+		if ((reg_buf[0] & BIT(0)) == reg)
+			break;
+		if (ktime_after(ktime_get(), timeout)) {
+			err = -EBADMSG;
+			break;
+		}
+		usleep_range(100, 200);
+	}
+out:
+	if (!err)
+		card->ext_csd.cmdq_en = enable;
+
+	return err;
+}
+
+int mmc_sd_cmdq_enable(struct mmc_card *card)
+{
+	return mmc_sd_cmdq_switch(card, true);
+}
+EXPORT_SYMBOL_GPL(mmc_sd_cmdq_enable);
+
+int mmc_sd_cmdq_disable(struct mmc_card *card)
+{
+	return mmc_sd_cmdq_switch(card, false);
+}
+EXPORT_SYMBOL_GPL(mmc_sd_cmdq_disable);
diff --git a/drivers/mmc/core/sd_ops.h b/drivers/mmc/core/sd_ops.h
index 7667fc223b74..1b8138368a81 100644
--- a/drivers/mmc/core/sd_ops.h
+++ b/drivers/mmc/core/sd_ops.h
@@ -21,6 +21,12 @@ int mmc_send_relative_addr(struct mmc_host *host, unsigned int *rca);
 int mmc_app_send_scr(struct mmc_card *card);
 int mmc_app_sd_status(struct mmc_card *card, void *ssr);
 int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card);
+int mmc_sd_cmdq_enable(struct mmc_card *card);
+int mmc_sd_cmdq_disable(struct mmc_card *card);
+int mmc_sd_write_ext_reg(struct mmc_card *card, u8 fno, u8 page, u16 offset,
+			 u8 reg_data);
+int mmc_sd_read_ext_reg(struct mmc_card *card, u8 fno, u8 page,
+			u16 offset, u16 len, u8 *reg_buf);
 
 #endif
 
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index fb0a018d02cf..ccca5522c283 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -5,6 +5,46 @@
 
 comment "MMC/SD/SDIO Host Controller Drivers"
 
+config MMC_BCM2835_MMC
+	tristate "MMC support on BCM2835"
+	depends on MACH_BCM2708 || MACH_BCM2709 || ARCH_BCM2835
+	help
+	  This selects the MMC Interface on BCM2835.
+
+	  If you have a controller with this interface, say Y or M here.
+
+	  If unsure, say N.
+
+config MMC_BCM2835_DMA
+	bool "DMA support on BCM2835 Arasan controller"
+	depends on MMC_BCM2835_MMC
+	help
+	  Enable DMA support on the Arasan SDHCI controller in Broadcom 2708
+	  based chips.
+
+	  If unsure, say N.
+
+config MMC_BCM2835_PIO_DMA_BARRIER
+	int "Block count limit for PIO transfers"
+	depends on MMC_BCM2835_MMC && MMC_BCM2835_DMA
+	range 0 256
+	default 2
+	help
+	  The inclusive limit in bytes under which PIO will be used instead of DMA
+
+	  If unsure, say 2 here.
+
+config MMC_BCM2835_SDHOST
+	tristate "Support for the SDHost controller on BCM2708/9"
+	depends on ARCH_BCM2835
+	select MMC_HSQ
+	help
+	  This selects the SDHost controller on BCM2835/6.
+
+	  If you have a controller with this interface, say Y or M here.
+
+	  If unsure, say N.
+
 config MMC_DEBUG
 	bool "MMC host drivers debugging"
 	depends on MMC != n
@@ -1031,6 +1071,7 @@ config MMC_SDHCI_BRCMSTB
 	depends on ARCH_BRCMSTB || ARCH_BCM2835 || BMIPS_GENERIC || COMPILE_TEST
 	depends on MMC_SDHCI_PLTFM
 	select MMC_CQHCI
+	select OF_DYNAMIC
 	default ARCH_BRCMSTB || BMIPS_GENERIC
 	help
 	  This selects support for the SDIO/SD/MMC Host Controller on
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index 3ccffebbe59b..89443b08b16f 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -22,6 +22,8 @@ obj-$(CONFIG_MMC_SDHCI_F_SDH30)	+= sdhci_f_sdh30.o
 obj-$(CONFIG_MMC_SDHCI_MILBEAUT)	+= sdhci-milbeaut.o
 obj-$(CONFIG_MMC_SDHCI_SPEAR)	+= sdhci-spear.o
 obj-$(CONFIG_MMC_SDHCI_AM654)	+= sdhci_am654.o
+obj-$(CONFIG_MMC_BCM2835_MMC)	+= bcm2835-mmc.o
+obj-$(CONFIG_MMC_BCM2835_SDHOST)	+= bcm2835-sdhost.o
 obj-$(CONFIG_MMC_WBSD)		+= wbsd.o
 obj-$(CONFIG_MMC_AU1X)		+= au1xmmc.o
 obj-$(CONFIG_MMC_ALCOR)	+= alcor.o
diff --git a/drivers/mmc/host/bcm2835-mmc.c b/drivers/mmc/host/bcm2835-mmc.c
new file mode 100644
index 000000000000..dc889bbb7542
--- /dev/null
+++ b/drivers/mmc/host/bcm2835-mmc.c
@@ -0,0 +1,1560 @@
+/*
+ * BCM2835 MMC host driver.
+ *
+ * Author:      Gellert Weisz <gellert@raspberrypi.org>
+ *              Copyright 2014
+ *
+ * Based on
+ *  sdhci-bcm2708.c by Broadcom
+ *  sdhci-bcm2835.c by Stephen Warren and Oleksandr Tymoshenko
+ *  sdhci.c and sdhci-pci.c by Pierre Ossman
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sd.h>
+#include <linux/scatterlist.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/blkdev.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/of_dma.h>
+#include <linux/swiotlb.h>
+
+#include "sdhci.h"
+
+
+#define DRIVER_NAME "mmc-bcm2835"
+
+#define DBG(f, x...) \
+pr_debug(DRIVER_NAME " [%s()]: " f, __func__, ## x)
+
+#ifndef CONFIG_MMC_BCM2835_DMA
+ #define FORCE_PIO
+#endif
+
+
+/* the inclusive limit in bytes under which PIO will be used instead of DMA */
+#ifdef CONFIG_MMC_BCM2835_PIO_DMA_BARRIER
+#define PIO_DMA_BARRIER CONFIG_MMC_BCM2835_PIO_DMA_BARRIER
+#else
+#define PIO_DMA_BARRIER 00
+#endif
+
+#define MIN_FREQ 400000
+#define TIMEOUT_VAL 0xE
+#define BCM2835_SDHCI_WRITE_DELAY(f)	(((2 * 1000000) / f) + 1)
+
+
+unsigned mmc_debug;
+unsigned mmc_debug2;
+
+struct bcm2835_host {
+	spinlock_t				lock;
+
+	void __iomem			*ioaddr;
+	u32						bus_addr;
+
+	struct mmc_host			*mmc;
+
+	u32						timeout;
+
+	int						clock;	/* Current clock speed */
+	u8						pwr;	/* Current voltage */
+
+	unsigned int			max_clk;		/* Max possible freq */
+	unsigned int			timeout_clk;	/* Timeout freq (KHz) */
+	unsigned int			clk_mul;		/* Clock Muliplier value */
+
+	struct tasklet_struct	finish_tasklet;		/* Tasklet structures */
+
+	struct timer_list		timer;			/* Timer for timeouts */
+
+	struct sg_mapping_iter	sg_miter;		/* SG state for PIO */
+	unsigned int			blocks;			/* remaining PIO blocks */
+
+	int						irq;			/* Device IRQ */
+
+
+	u32						ier;			/* cached registers */
+
+	struct mmc_request		*mrq;			/* Current request */
+	struct mmc_command		*cmd;			/* Current command */
+	struct mmc_data			*data;			/* Current data request */
+	unsigned int			data_early:1;		/* Data finished before cmd */
+
+	wait_queue_head_t		buf_ready_int;		/* Waitqueue for Buffer Read Ready interrupt */
+
+	u32						shadow;
+
+	/*DMA part*/
+	struct dma_chan			*dma_chan_rxtx;		/* DMA channel for reads and writes */
+	struct dma_slave_config		dma_cfg_rx;
+	struct dma_slave_config		dma_cfg_tx;
+	struct dma_async_tx_descriptor	*tx_desc;	/* descriptor */
+
+	bool					have_dma;
+	bool					use_dma;
+	bool					wait_for_dma;
+	/*end of DMA part*/
+
+	int						max_delay;	/* maximum length of time spent waiting */
+
+	int						flags;				/* Host attributes */
+#define SDHCI_REQ_USE_DMA	(1<<2)	/* Use DMA for this req. */
+#define SDHCI_DEVICE_DEAD	(1<<3)	/* Device unresponsive */
+#define SDHCI_AUTO_CMD12	(1<<6)	/* Auto CMD12 support */
+#define SDHCI_AUTO_CMD23	(1<<7)	/* Auto CMD23 support */
+#define SDHCI_SDIO_IRQ_ENABLED	(1<<9)	/* SDIO irq enabled */
+
+	u32				overclock_50;	/* frequency to use when 50MHz is requested (in MHz) */
+	u32				max_overclock;	/* Highest reported */
+};
+
+
+static inline void bcm2835_mmc_writel(struct bcm2835_host *host, u32 val, int reg, int from)
+{
+	unsigned delay;
+	lockdep_assert_held_once(&host->lock);
+	writel(val, host->ioaddr + reg);
+	udelay(BCM2835_SDHCI_WRITE_DELAY(max(host->clock, MIN_FREQ)));
+
+	delay = ((mmc_debug >> 16) & 0xf) << ((mmc_debug >> 20) & 0xf);
+	if (delay && !((1<<from) & mmc_debug2))
+		udelay(delay);
+}
+
+static inline void mmc_raw_writel(struct bcm2835_host *host, u32 val, int reg)
+{
+	unsigned delay;
+	lockdep_assert_held_once(&host->lock);
+	writel(val, host->ioaddr + reg);
+
+	delay = ((mmc_debug >> 24) & 0xf) << ((mmc_debug >> 28) & 0xf);
+	if (delay)
+		udelay(delay);
+}
+
+static inline u32 bcm2835_mmc_readl(struct bcm2835_host *host, int reg)
+{
+	lockdep_assert_held_once(&host->lock);
+	return readl(host->ioaddr + reg);
+}
+
+static inline void bcm2835_mmc_writew(struct bcm2835_host *host, u16 val, int reg)
+{
+	u32 oldval = (reg == SDHCI_COMMAND) ? host->shadow :
+		bcm2835_mmc_readl(host, reg & ~3);
+	u32 word_num = (reg >> 1) & 1;
+	u32 word_shift = word_num * 16;
+	u32 mask = 0xffff << word_shift;
+	u32 newval = (oldval & ~mask) | (val << word_shift);
+
+	if (reg == SDHCI_TRANSFER_MODE)
+		host->shadow = newval;
+	else
+		bcm2835_mmc_writel(host, newval, reg & ~3, 0);
+
+}
+
+static inline void bcm2835_mmc_writeb(struct bcm2835_host *host, u8 val, int reg)
+{
+	u32 oldval = bcm2835_mmc_readl(host, reg & ~3);
+	u32 byte_num = reg & 3;
+	u32 byte_shift = byte_num * 8;
+	u32 mask = 0xff << byte_shift;
+	u32 newval = (oldval & ~mask) | (val << byte_shift);
+
+	bcm2835_mmc_writel(host, newval, reg & ~3, 1);
+}
+
+
+static inline u16 bcm2835_mmc_readw(struct bcm2835_host *host, int reg)
+{
+	u32 val = bcm2835_mmc_readl(host, (reg & ~3));
+	u32 word_num = (reg >> 1) & 1;
+	u32 word_shift = word_num * 16;
+	u32 word = (val >> word_shift) & 0xffff;
+
+	return word;
+}
+
+static inline u8 bcm2835_mmc_readb(struct bcm2835_host *host, int reg)
+{
+	u32 val = bcm2835_mmc_readl(host, (reg & ~3));
+	u32 byte_num = reg & 3;
+	u32 byte_shift = byte_num * 8;
+	u32 byte = (val >> byte_shift) & 0xff;
+
+	return byte;
+}
+
+static void bcm2835_mmc_unsignal_irqs(struct bcm2835_host *host, u32 clear)
+{
+	u32 ier;
+
+	ier = bcm2835_mmc_readl(host, SDHCI_SIGNAL_ENABLE);
+	ier &= ~clear;
+	/* change which requests generate IRQs - makes no difference to
+	   the content of SDHCI_INT_STATUS, or the need to acknowledge IRQs */
+	bcm2835_mmc_writel(host, ier, SDHCI_SIGNAL_ENABLE, 2);
+}
+
+
+static void bcm2835_mmc_dumpregs(struct bcm2835_host *host)
+{
+	pr_debug(DRIVER_NAME ": =========== REGISTER DUMP (%s)===========\n",
+		mmc_hostname(host->mmc));
+
+	pr_debug(DRIVER_NAME ": Sys addr: 0x%08x | Version:  0x%08x\n",
+		bcm2835_mmc_readl(host, SDHCI_DMA_ADDRESS),
+		bcm2835_mmc_readw(host, SDHCI_HOST_VERSION));
+	pr_debug(DRIVER_NAME ": Blk size: 0x%08x | Blk cnt:  0x%08x\n",
+		bcm2835_mmc_readw(host, SDHCI_BLOCK_SIZE),
+		bcm2835_mmc_readw(host, SDHCI_BLOCK_COUNT));
+	pr_debug(DRIVER_NAME ": Argument: 0x%08x | Trn mode: 0x%08x\n",
+		bcm2835_mmc_readl(host, SDHCI_ARGUMENT),
+		bcm2835_mmc_readw(host, SDHCI_TRANSFER_MODE));
+	pr_debug(DRIVER_NAME ": Present:  0x%08x | Host ctl: 0x%08x\n",
+		bcm2835_mmc_readl(host, SDHCI_PRESENT_STATE),
+		bcm2835_mmc_readb(host, SDHCI_HOST_CONTROL));
+	pr_debug(DRIVER_NAME ": Power:    0x%08x | Blk gap:  0x%08x\n",
+		bcm2835_mmc_readb(host, SDHCI_POWER_CONTROL),
+		bcm2835_mmc_readb(host, SDHCI_BLOCK_GAP_CONTROL));
+	pr_debug(DRIVER_NAME ": Wake-up:  0x%08x | Clock:    0x%08x\n",
+		bcm2835_mmc_readb(host, SDHCI_WAKE_UP_CONTROL),
+		bcm2835_mmc_readw(host, SDHCI_CLOCK_CONTROL));
+	pr_debug(DRIVER_NAME ": Timeout:  0x%08x | Int stat: 0x%08x\n",
+		bcm2835_mmc_readb(host, SDHCI_TIMEOUT_CONTROL),
+		bcm2835_mmc_readl(host, SDHCI_INT_STATUS));
+	pr_debug(DRIVER_NAME ": Int enab: 0x%08x | Sig enab: 0x%08x\n",
+		bcm2835_mmc_readl(host, SDHCI_INT_ENABLE),
+		bcm2835_mmc_readl(host, SDHCI_SIGNAL_ENABLE));
+	pr_debug(DRIVER_NAME ": AC12 err: 0x%08x | Slot int: 0x%08x\n",
+		bcm2835_mmc_readw(host, SDHCI_AUTO_CMD_STATUS),
+		bcm2835_mmc_readw(host, SDHCI_SLOT_INT_STATUS));
+	pr_debug(DRIVER_NAME ": Caps:     0x%08x | Caps_1:   0x%08x\n",
+		bcm2835_mmc_readl(host, SDHCI_CAPABILITIES),
+		bcm2835_mmc_readl(host, SDHCI_CAPABILITIES_1));
+	pr_debug(DRIVER_NAME ": Cmd:      0x%08x | Max curr: 0x%08x\n",
+		bcm2835_mmc_readw(host, SDHCI_COMMAND),
+		bcm2835_mmc_readl(host, SDHCI_MAX_CURRENT));
+	pr_debug(DRIVER_NAME ": Host ctl2: 0x%08x\n",
+		bcm2835_mmc_readw(host, SDHCI_HOST_CONTROL2));
+
+	pr_debug(DRIVER_NAME ": ===========================================\n");
+}
+
+
+static void bcm2835_mmc_reset(struct bcm2835_host *host, u8 mask)
+{
+	unsigned long timeout;
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->lock, flags);
+	bcm2835_mmc_writeb(host, mask, SDHCI_SOFTWARE_RESET);
+
+	if (mask & SDHCI_RESET_ALL)
+		host->clock = 0;
+
+	/* Wait max 100 ms */
+	timeout = 100;
+
+	/* hw clears the bit when it's done */
+	while (bcm2835_mmc_readb(host, SDHCI_SOFTWARE_RESET) & mask) {
+		if (timeout == 0) {
+			pr_err("%s: Reset 0x%x never completed.\n",
+				mmc_hostname(host->mmc), (int)mask);
+			bcm2835_mmc_dumpregs(host);
+			return;
+		}
+		timeout--;
+		spin_unlock_irqrestore(&host->lock, flags);
+		mdelay(1);
+		spin_lock_irqsave(&host->lock, flags);
+	}
+
+	if (100-timeout > 10 && 100-timeout > host->max_delay) {
+		host->max_delay = 100-timeout;
+		pr_warn("Warning: MMC controller hung for %d ms\n", host->max_delay);
+	}
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void bcm2835_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
+
+static void bcm2835_mmc_init(struct bcm2835_host *host, int soft)
+{
+	unsigned long flags;
+	if (soft)
+		bcm2835_mmc_reset(host, SDHCI_RESET_CMD|SDHCI_RESET_DATA);
+	else
+		bcm2835_mmc_reset(host, SDHCI_RESET_ALL);
+
+	host->ier = SDHCI_INT_BUS_POWER | SDHCI_INT_DATA_END_BIT |
+		    SDHCI_INT_DATA_CRC | SDHCI_INT_DATA_TIMEOUT |
+		    SDHCI_INT_INDEX | SDHCI_INT_END_BIT | SDHCI_INT_CRC |
+		    SDHCI_INT_TIMEOUT | SDHCI_INT_DATA_END |
+		    SDHCI_INT_RESPONSE;
+
+	spin_lock_irqsave(&host->lock, flags);
+	bcm2835_mmc_writel(host, host->ier, SDHCI_INT_ENABLE, 3);
+	bcm2835_mmc_writel(host, host->ier, SDHCI_SIGNAL_ENABLE, 3);
+	spin_unlock_irqrestore(&host->lock, flags);
+
+	if (soft) {
+		/* force clock reconfiguration */
+		host->clock = 0;
+		bcm2835_mmc_set_ios(host->mmc, &host->mmc->ios);
+	}
+}
+
+
+
+static void bcm2835_mmc_finish_data(struct bcm2835_host *host);
+
+static void bcm2835_mmc_dma_complete(void *param)
+{
+	struct bcm2835_host *host = param;
+	struct dma_chan *dma_chan;
+	unsigned long flags;
+	u32 dir_data;
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	host->use_dma = false;
+
+	if (host->data) {
+		dma_chan = host->dma_chan_rxtx;
+		if (host->data->flags & MMC_DATA_WRITE)
+			dir_data = DMA_TO_DEVICE;
+		else
+			dir_data = DMA_FROM_DEVICE;
+		dma_unmap_sg(dma_chan->device->dev,
+		     host->data->sg, host->data->sg_len,
+		     dir_data);
+		if (! (host->data->flags & MMC_DATA_WRITE))
+			bcm2835_mmc_finish_data(host);
+	} else if (host->wait_for_dma) {
+		host->wait_for_dma = false;
+		tasklet_schedule(&host->finish_tasklet);
+	}
+
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void bcm2835_bcm2835_mmc_read_block_pio(struct bcm2835_host *host)
+{
+	unsigned long flags;
+	size_t blksize, len, chunk;
+
+	u32 scratch = 0;
+	u8 *buf;
+
+	blksize = host->data->blksz;
+	chunk = 0;
+
+	local_irq_save(flags);
+
+	while (blksize) {
+		if (!sg_miter_next(&host->sg_miter))
+			BUG();
+
+		len = min(host->sg_miter.length, blksize);
+
+		blksize -= len;
+		host->sg_miter.consumed = len;
+
+		buf = host->sg_miter.addr;
+
+		while (len) {
+			if (chunk == 0) {
+				scratch = bcm2835_mmc_readl(host, SDHCI_BUFFER);
+				chunk = 4;
+			}
+
+			*buf = scratch & 0xFF;
+
+			buf++;
+			scratch >>= 8;
+			chunk--;
+			len--;
+		}
+	}
+
+	sg_miter_stop(&host->sg_miter);
+
+	local_irq_restore(flags);
+}
+
+static void bcm2835_bcm2835_mmc_write_block_pio(struct bcm2835_host *host)
+{
+	unsigned long flags;
+	size_t blksize, len, chunk;
+	u32 scratch;
+	u8 *buf;
+
+	blksize = host->data->blksz;
+	chunk = 0;
+	chunk = 0;
+	scratch = 0;
+
+	local_irq_save(flags);
+
+	while (blksize) {
+		if (!sg_miter_next(&host->sg_miter))
+			BUG();
+
+		len = min(host->sg_miter.length, blksize);
+
+		blksize -= len;
+		host->sg_miter.consumed = len;
+
+		buf = host->sg_miter.addr;
+
+		while (len) {
+			scratch |= (u32)*buf << (chunk * 8);
+
+			buf++;
+			chunk++;
+			len--;
+
+			if ((chunk == 4) || ((len == 0) && (blksize == 0))) {
+				mmc_raw_writel(host, scratch, SDHCI_BUFFER);
+				chunk = 0;
+				scratch = 0;
+			}
+		}
+	}
+
+	sg_miter_stop(&host->sg_miter);
+
+	local_irq_restore(flags);
+}
+
+
+static void bcm2835_mmc_transfer_pio(struct bcm2835_host *host)
+{
+	u32 mask;
+
+	BUG_ON(!host->data);
+
+	if (host->blocks == 0)
+		return;
+
+	if (host->data->flags & MMC_DATA_READ)
+		mask = SDHCI_DATA_AVAILABLE;
+	else
+		mask = SDHCI_SPACE_AVAILABLE;
+
+	while (bcm2835_mmc_readl(host, SDHCI_PRESENT_STATE) & mask) {
+
+		if (host->data->flags & MMC_DATA_READ)
+			bcm2835_bcm2835_mmc_read_block_pio(host);
+		else
+			bcm2835_bcm2835_mmc_write_block_pio(host);
+
+		host->blocks--;
+
+		/* QUIRK used in sdhci.c removes the 'if' */
+		/* but it seems this is unnecessary */
+		if (host->blocks == 0)
+			break;
+
+
+	}
+}
+
+
+static void bcm2835_mmc_transfer_dma(struct bcm2835_host *host)
+{
+	u32 len, dir_data, dir_slave;
+	struct dma_async_tx_descriptor *desc = NULL;
+	struct dma_chan *dma_chan;
+
+
+	WARN_ON(!host->data);
+
+	if (!host->data)
+		return;
+
+	if (host->blocks == 0)
+		return;
+
+	dma_chan = host->dma_chan_rxtx;
+	if (host->data->flags & MMC_DATA_READ) {
+		dir_data = DMA_FROM_DEVICE;
+		dir_slave = DMA_DEV_TO_MEM;
+	} else {
+		dir_data = DMA_TO_DEVICE;
+		dir_slave = DMA_MEM_TO_DEV;
+	}
+
+	/* The parameters have already been validated, so this will not fail */
+	(void)dmaengine_slave_config(dma_chan,
+				     (dir_data == DMA_FROM_DEVICE) ?
+				     &host->dma_cfg_rx :
+				     &host->dma_cfg_tx);
+
+	BUG_ON(!dma_chan->device);
+	BUG_ON(!dma_chan->device->dev);
+	BUG_ON(!host->data->sg);
+
+	len = dma_map_sg(dma_chan->device->dev, host->data->sg,
+			 host->data->sg_len, dir_data);
+	if (len > 0) {
+		desc = dmaengine_prep_slave_sg(dma_chan, host->data->sg,
+					       len, dir_slave,
+					       DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+	} else {
+		dev_err(mmc_dev(host->mmc), "dma_map_sg returned zero length\n");
+	}
+	if (desc) {
+		unsigned long flags;
+		spin_lock_irqsave(&host->lock, flags);
+		bcm2835_mmc_unsignal_irqs(host, SDHCI_INT_DATA_AVAIL |
+						    SDHCI_INT_SPACE_AVAIL);
+		host->tx_desc = desc;
+		desc->callback = bcm2835_mmc_dma_complete;
+		desc->callback_param = host;
+		spin_unlock_irqrestore(&host->lock, flags);
+		dmaengine_submit(desc);
+		dma_async_issue_pending(dma_chan);
+	} else {
+		dma_unmap_sg(dma_chan->device->dev, host->data->sg, len, dir_data);
+	}
+
+}
+
+
+
+static void bcm2835_mmc_set_transfer_irqs(struct bcm2835_host *host)
+{
+	u32 pio_irqs = SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL;
+	u32 dma_irqs = SDHCI_INT_DMA_END | SDHCI_INT_ADMA_ERROR;
+
+	if (host->use_dma)
+		host->ier = (host->ier & ~pio_irqs) | dma_irqs;
+	else
+		host->ier = (host->ier & ~dma_irqs) | pio_irqs;
+
+	bcm2835_mmc_writel(host, host->ier, SDHCI_INT_ENABLE, 4);
+	bcm2835_mmc_writel(host, host->ier, SDHCI_SIGNAL_ENABLE, 4);
+}
+
+
+static void bcm2835_mmc_prepare_data(struct bcm2835_host *host, struct mmc_command *cmd)
+{
+	u8 count;
+	struct mmc_data *data = cmd->data;
+
+	WARN_ON(host->data);
+
+	if (data || (cmd->flags & MMC_RSP_BUSY)) {
+		count = TIMEOUT_VAL;
+		bcm2835_mmc_writeb(host, count, SDHCI_TIMEOUT_CONTROL);
+	}
+
+	if (!data)
+		return;
+
+	/* Sanity checks */
+	BUG_ON(data->blksz * data->blocks > 524288);
+	BUG_ON(data->blksz > host->mmc->max_blk_size);
+	BUG_ON(data->blocks > 65535);
+
+	host->data = data;
+	host->data_early = 0;
+	host->data->bytes_xfered = 0;
+
+
+	if (!(host->flags & SDHCI_REQ_USE_DMA)) {
+		int flags;
+
+		flags = SG_MITER_ATOMIC;
+		if (host->data->flags & MMC_DATA_READ)
+			flags |= SG_MITER_TO_SG;
+		else
+			flags |= SG_MITER_FROM_SG;
+		sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags);
+		host->blocks = data->blocks;
+	}
+
+	host->use_dma = host->have_dma && data->blocks > PIO_DMA_BARRIER;
+
+	bcm2835_mmc_set_transfer_irqs(host);
+
+	/* Set the DMA boundary value and block size */
+	bcm2835_mmc_writew(host, SDHCI_MAKE_BLKSZ(SDHCI_DEFAULT_BOUNDARY_ARG,
+		data->blksz), SDHCI_BLOCK_SIZE);
+	bcm2835_mmc_writew(host, data->blocks, SDHCI_BLOCK_COUNT);
+
+	BUG_ON(!host->data);
+}
+
+static void bcm2835_mmc_set_transfer_mode(struct bcm2835_host *host,
+	struct mmc_command *cmd)
+{
+	u16 mode;
+	struct mmc_data *data = cmd->data;
+
+	if (data == NULL) {
+		/* clear Auto CMD settings for no data CMDs */
+		mode = bcm2835_mmc_readw(host, SDHCI_TRANSFER_MODE);
+		bcm2835_mmc_writew(host, mode & ~(SDHCI_TRNS_AUTO_CMD12 |
+				SDHCI_TRNS_AUTO_CMD23), SDHCI_TRANSFER_MODE);
+		return;
+	}
+
+	WARN_ON(!host->data);
+
+	mode = SDHCI_TRNS_BLK_CNT_EN;
+
+	if ((mmc_op_multi(cmd->opcode) || data->blocks > 1)) {
+		mode |= SDHCI_TRNS_MULTI;
+
+		/*
+		 * If we are sending CMD23, CMD12 never gets sent
+		 * on successful completion (so no Auto-CMD12).
+		 */
+		if (!host->mrq->sbc && (host->flags & SDHCI_AUTO_CMD12))
+			mode |= SDHCI_TRNS_AUTO_CMD12;
+		else if (host->mrq->sbc && (host->flags & SDHCI_AUTO_CMD23)) {
+			mode |= SDHCI_TRNS_AUTO_CMD23;
+			bcm2835_mmc_writel(host, host->mrq->sbc->arg, SDHCI_ARGUMENT2, 5);
+		}
+	}
+
+	if (data->flags & MMC_DATA_READ)
+		mode |= SDHCI_TRNS_READ;
+	if (host->flags & SDHCI_REQ_USE_DMA)
+		mode |= SDHCI_TRNS_DMA;
+
+	bcm2835_mmc_writew(host, mode, SDHCI_TRANSFER_MODE);
+}
+
+static void bcm2835_mmc_send_command(struct bcm2835_host *host, struct mmc_command *cmd)
+{
+	int flags;
+	u32 mask;
+	unsigned long timeout;
+
+	WARN_ON(host->cmd);
+
+	/* Wait max 10 ms */
+	timeout = 1000;
+
+	mask = SDHCI_CMD_INHIBIT;
+	if ((cmd->data != NULL) || (cmd->flags & MMC_RSP_BUSY))
+		mask |= SDHCI_DATA_INHIBIT;
+
+	/* We shouldn't wait for data inihibit for stop commands, even
+	   though they might use busy signaling */
+	if (host->mrq->data && (cmd == host->mrq->data->stop))
+		mask &= ~SDHCI_DATA_INHIBIT;
+
+	while (bcm2835_mmc_readl(host, SDHCI_PRESENT_STATE) & mask) {
+		if (timeout == 0) {
+			pr_err("%s: Controller never released inhibit bit(s).\n",
+				mmc_hostname(host->mmc));
+			bcm2835_mmc_dumpregs(host);
+			cmd->error = -EIO;
+			tasklet_schedule(&host->finish_tasklet);
+			return;
+		}
+		timeout--;
+		udelay(10);
+	}
+
+	if ((1000-timeout)/100 > 1 && (1000-timeout)/100 > host->max_delay) {
+		host->max_delay = (1000-timeout)/100;
+		pr_warn("Warning: MMC controller hung for %d ms\n", host->max_delay);
+	}
+
+	timeout = jiffies;
+	if (!cmd->data && cmd->busy_timeout > 9000)
+		timeout += DIV_ROUND_UP(cmd->busy_timeout, 1000) * HZ + HZ;
+	else
+		timeout += 10 * HZ;
+	mod_timer(&host->timer, timeout);
+
+	host->cmd = cmd;
+	host->use_dma = false;
+
+	bcm2835_mmc_prepare_data(host, cmd);
+
+	bcm2835_mmc_writel(host, cmd->arg, SDHCI_ARGUMENT, 6);
+
+	bcm2835_mmc_set_transfer_mode(host, cmd);
+
+	if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) {
+		pr_err("%s: Unsupported response type!\n",
+			mmc_hostname(host->mmc));
+		cmd->error = -EINVAL;
+		tasklet_schedule(&host->finish_tasklet);
+		return;
+	}
+
+	if (!(cmd->flags & MMC_RSP_PRESENT))
+		flags = SDHCI_CMD_RESP_NONE;
+	else if (cmd->flags & MMC_RSP_136)
+		flags = SDHCI_CMD_RESP_LONG;
+	else if (cmd->flags & MMC_RSP_BUSY)
+		flags = SDHCI_CMD_RESP_SHORT_BUSY;
+	else
+		flags = SDHCI_CMD_RESP_SHORT;
+
+	if (cmd->flags & MMC_RSP_CRC)
+		flags |= SDHCI_CMD_CRC;
+	if (cmd->flags & MMC_RSP_OPCODE)
+		flags |= SDHCI_CMD_INDEX;
+
+	if (cmd->data)
+		flags |= SDHCI_CMD_DATA;
+
+	bcm2835_mmc_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND);
+}
+
+
+static void bcm2835_mmc_finish_data(struct bcm2835_host *host)
+{
+	struct mmc_data *data;
+
+	BUG_ON(!host->data);
+
+	data = host->data;
+	host->data = NULL;
+
+	if (data->error)
+		data->bytes_xfered = 0;
+	else
+		data->bytes_xfered = data->blksz * data->blocks;
+
+	/*
+	 * Need to send CMD12 if -
+	 * a) open-ended multiblock transfer (no CMD23)
+	 * b) error in multiblock transfer
+	 */
+	if (data->stop &&
+	    (data->error ||
+	     !host->mrq->sbc)) {
+
+		/*
+		 * The controller needs a reset of internal state machines
+		 * upon error conditions.
+		 */
+		if (data->error) {
+			bcm2835_mmc_reset(host, SDHCI_RESET_CMD);
+			bcm2835_mmc_reset(host, SDHCI_RESET_DATA);
+		}
+
+		bcm2835_mmc_send_command(host, data->stop);
+	} else if (host->use_dma) {
+		host->wait_for_dma = true;
+	} else {
+		tasklet_schedule(&host->finish_tasklet);
+	}
+}
+
+static void bcm2835_mmc_finish_command(struct bcm2835_host *host)
+{
+	int i;
+
+	BUG_ON(host->cmd == NULL);
+
+	if (host->cmd->flags & MMC_RSP_PRESENT) {
+		if (host->cmd->flags & MMC_RSP_136) {
+			/* CRC is stripped so we need to do some shifting. */
+			for (i = 0; i < 4; i++) {
+				host->cmd->resp[i] = bcm2835_mmc_readl(host,
+					SDHCI_RESPONSE + (3-i)*4) << 8;
+				if (i != 3)
+					host->cmd->resp[i] |=
+						bcm2835_mmc_readb(host,
+						SDHCI_RESPONSE + (3-i)*4-1);
+			}
+		} else {
+			host->cmd->resp[0] = bcm2835_mmc_readl(host, SDHCI_RESPONSE);
+		}
+	}
+
+	host->cmd->error = 0;
+
+	/* Finished CMD23, now send actual command. */
+	if (host->cmd == host->mrq->sbc) {
+		host->cmd = NULL;
+		bcm2835_mmc_send_command(host, host->mrq->cmd);
+
+		if (host->mrq->cmd->data && host->use_dma) {
+			/* DMA transfer starts now, PIO starts after interrupt */
+			bcm2835_mmc_transfer_dma(host);
+		}
+	} else {
+
+		/* Processed actual command. */
+		if (host->data && host->data_early)
+			bcm2835_mmc_finish_data(host);
+
+		if (!host->cmd->data)
+			tasklet_schedule(&host->finish_tasklet);
+
+		host->cmd = NULL;
+	}
+}
+
+
+static void bcm2835_mmc_timeout_timer(struct timer_list *t)
+{
+	struct bcm2835_host *host = from_timer(host, t, timer);
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	if (host->mrq) {
+		pr_err("%s: Timeout waiting for hardware interrupt.\n",
+			mmc_hostname(host->mmc));
+		bcm2835_mmc_dumpregs(host);
+
+		if (host->data) {
+			host->data->error = -ETIMEDOUT;
+			bcm2835_mmc_finish_data(host);
+		} else {
+			if (host->cmd)
+				host->cmd->error = -ETIMEDOUT;
+			else
+				host->mrq->cmd->error = -ETIMEDOUT;
+
+			tasklet_schedule(&host->finish_tasklet);
+		}
+	}
+
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+
+static void bcm2835_mmc_enable_sdio_irq_nolock(struct bcm2835_host *host, int enable)
+{
+	if (!(host->flags & SDHCI_DEVICE_DEAD)) {
+		if (enable)
+			host->ier |= SDHCI_INT_CARD_INT;
+		else
+			host->ier &= ~SDHCI_INT_CARD_INT;
+
+		bcm2835_mmc_writel(host, host->ier, SDHCI_INT_ENABLE, 7);
+		bcm2835_mmc_writel(host, host->ier, SDHCI_SIGNAL_ENABLE, 7);
+	}
+}
+
+static void bcm2835_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+	struct bcm2835_host *host = mmc_priv(mmc);
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->lock, flags);
+	if (enable)
+		host->flags |= SDHCI_SDIO_IRQ_ENABLED;
+	else
+		host->flags &= ~SDHCI_SDIO_IRQ_ENABLED;
+
+	bcm2835_mmc_enable_sdio_irq_nolock(host, enable);
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void bcm2835_mmc_cmd_irq(struct bcm2835_host *host, u32 intmask)
+{
+
+	BUG_ON(intmask == 0);
+
+	if (!host->cmd) {
+		pr_err("%s: Got command interrupt 0x%08x even "
+			"though no command operation was in progress.\n",
+			mmc_hostname(host->mmc), (unsigned)intmask);
+		bcm2835_mmc_dumpregs(host);
+		return;
+	}
+
+	if (intmask & SDHCI_INT_TIMEOUT)
+		host->cmd->error = -ETIMEDOUT;
+	else if (intmask & (SDHCI_INT_CRC | SDHCI_INT_END_BIT |
+			SDHCI_INT_INDEX)) {
+			host->cmd->error = -EILSEQ;
+	}
+
+	if (host->cmd->error) {
+		tasklet_schedule(&host->finish_tasklet);
+		return;
+	}
+
+	if (intmask & SDHCI_INT_RESPONSE)
+		bcm2835_mmc_finish_command(host);
+
+}
+
+static void bcm2835_mmc_data_irq(struct bcm2835_host *host, u32 intmask)
+{
+	struct dma_chan *dma_chan;
+	u32 dir_data;
+
+	BUG_ON(intmask == 0);
+
+	if (!host->data) {
+		/*
+		 * The "data complete" interrupt is also used to
+		 * indicate that a busy state has ended. See comment
+		 * above in sdhci_cmd_irq().
+		 */
+		if (host->cmd && (host->cmd->flags & MMC_RSP_BUSY)) {
+			if (intmask & SDHCI_INT_DATA_END) {
+				bcm2835_mmc_finish_command(host);
+				return;
+			}
+		}
+
+		pr_debug("%s: Got data interrupt 0x%08x even "
+			"though no data operation was in progress.\n",
+			mmc_hostname(host->mmc), (unsigned)intmask);
+		bcm2835_mmc_dumpregs(host);
+
+		return;
+	}
+
+	if (intmask & SDHCI_INT_DATA_TIMEOUT)
+		host->data->error = -ETIMEDOUT;
+	else if (intmask & SDHCI_INT_DATA_END_BIT)
+		host->data->error = -EILSEQ;
+	else if ((intmask & SDHCI_INT_DATA_CRC) &&
+		SDHCI_GET_CMD(bcm2835_mmc_readw(host, SDHCI_COMMAND))
+			!= MMC_BUS_TEST_R)
+		host->data->error = -EILSEQ;
+
+	if (host->use_dma) {
+		if  (host->data->flags & MMC_DATA_WRITE) {
+			/* IRQ handled here */
+
+			dma_chan = host->dma_chan_rxtx;
+			dir_data = DMA_TO_DEVICE;
+			dma_unmap_sg(dma_chan->device->dev,
+				 host->data->sg, host->data->sg_len,
+				 dir_data);
+
+			bcm2835_mmc_finish_data(host);
+		}
+
+	} else {
+		if (host->data->error)
+			bcm2835_mmc_finish_data(host);
+		else {
+			if (intmask & (SDHCI_INT_DATA_AVAIL | SDHCI_INT_SPACE_AVAIL))
+				bcm2835_mmc_transfer_pio(host);
+
+			if (intmask & SDHCI_INT_DATA_END) {
+				if (host->cmd) {
+					/*
+					 * Data managed to finish before the
+					 * command completed. Make sure we do
+					 * things in the proper order.
+					 */
+					host->data_early = 1;
+				} else {
+					bcm2835_mmc_finish_data(host);
+				}
+			}
+		}
+	}
+}
+
+
+static irqreturn_t bcm2835_mmc_irq(int irq, void *dev_id)
+{
+	irqreturn_t result = IRQ_NONE;
+	struct bcm2835_host *host = dev_id;
+	u32 intmask, mask, unexpected = 0;
+	int max_loops = 16;
+
+	spin_lock(&host->lock);
+
+	intmask = bcm2835_mmc_readl(host, SDHCI_INT_STATUS);
+
+	if (!intmask || intmask == 0xffffffff) {
+		result = IRQ_NONE;
+		goto out;
+	}
+
+	do {
+		/* Clear selected interrupts. */
+		mask = intmask & (SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK |
+				  SDHCI_INT_BUS_POWER);
+		bcm2835_mmc_writel(host, mask, SDHCI_INT_STATUS, 8);
+
+
+		if (intmask & SDHCI_INT_CMD_MASK)
+			bcm2835_mmc_cmd_irq(host, intmask & SDHCI_INT_CMD_MASK);
+
+		if (intmask & SDHCI_INT_DATA_MASK)
+			bcm2835_mmc_data_irq(host, intmask & SDHCI_INT_DATA_MASK);
+
+		if (intmask & SDHCI_INT_BUS_POWER)
+			pr_err("%s: Card is consuming too much power!\n",
+				mmc_hostname(host->mmc));
+
+		if (intmask & SDHCI_INT_CARD_INT) {
+			bcm2835_mmc_enable_sdio_irq_nolock(host, false);
+			sdio_signal_irq(host->mmc);
+		}
+
+		intmask &= ~(SDHCI_INT_CARD_INSERT | SDHCI_INT_CARD_REMOVE |
+			     SDHCI_INT_CMD_MASK | SDHCI_INT_DATA_MASK |
+			     SDHCI_INT_ERROR | SDHCI_INT_BUS_POWER |
+			     SDHCI_INT_CARD_INT);
+
+		if (intmask) {
+			unexpected |= intmask;
+			bcm2835_mmc_writel(host, intmask, SDHCI_INT_STATUS, 9);
+		}
+
+		if (result == IRQ_NONE)
+			result = IRQ_HANDLED;
+
+		intmask = bcm2835_mmc_readl(host, SDHCI_INT_STATUS);
+	} while (intmask && --max_loops);
+out:
+	spin_unlock(&host->lock);
+
+	if (unexpected) {
+		pr_err("%s: Unexpected interrupt 0x%08x.\n",
+			   mmc_hostname(host->mmc), unexpected);
+		bcm2835_mmc_dumpregs(host);
+	}
+
+	return result;
+}
+
+
+static void bcm2835_mmc_ack_sdio_irq(struct mmc_host *mmc)
+{
+	struct bcm2835_host *host = mmc_priv(mmc);
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->lock, flags);
+	if (host->flags & SDHCI_SDIO_IRQ_ENABLED)
+		bcm2835_mmc_enable_sdio_irq_nolock(host, true);
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void bcm2835_mmc_set_clock(struct bcm2835_host *host, unsigned int clock)
+{
+	int div = 0; /* Initialized for compiler warning */
+	int real_div = div, clk_mul = 1;
+	u16 clk = 0;
+	unsigned long timeout;
+	unsigned int input_clock = clock;
+
+	if (host->overclock_50 && (clock >= 50000000))
+		clock = host->overclock_50 * 1000000 + 999999;
+
+	host->mmc->actual_clock = 0;
+
+	bcm2835_mmc_writew(host, 0, SDHCI_CLOCK_CONTROL);
+
+	if (clock == 0)
+		return;
+
+	/* Version 3.00 divisors must be a multiple of 2. */
+	if (host->max_clk <= clock)
+		div = 1;
+	else {
+		for (div = 2; div < SDHCI_MAX_DIV_SPEC_300;
+			 div += 2) {
+			if ((host->max_clk / div) <= clock)
+				break;
+		}
+	}
+
+	real_div = div;
+	div >>= 1;
+
+	if (real_div)
+		clock = (host->max_clk * clk_mul) / real_div;
+	host->mmc->actual_clock = clock;
+
+	if ((clock > input_clock) && (clock > host->max_overclock)) {
+		pr_warn("%s: Overclocking to %dHz\n",
+			mmc_hostname(host->mmc), clock);
+		host->max_overclock = clock;
+	}
+
+	clk |= (div & SDHCI_DIV_MASK) << SDHCI_DIVIDER_SHIFT;
+	clk |= ((div & SDHCI_DIV_HI_MASK) >> SDHCI_DIV_MASK_LEN)
+		<< SDHCI_DIVIDER_HI_SHIFT;
+	clk |= SDHCI_CLOCK_INT_EN;
+	bcm2835_mmc_writew(host, clk, SDHCI_CLOCK_CONTROL);
+
+	/* Wait max 20 ms */
+	timeout = 20;
+	while (!((clk = bcm2835_mmc_readw(host, SDHCI_CLOCK_CONTROL))
+		& SDHCI_CLOCK_INT_STABLE)) {
+		if (timeout == 0) {
+			pr_err("%s: Internal clock never "
+				"stabilised.\n", mmc_hostname(host->mmc));
+			bcm2835_mmc_dumpregs(host);
+			return;
+		}
+		timeout--;
+		mdelay(1);
+	}
+
+	if (20-timeout > 10 && 20-timeout > host->max_delay) {
+		host->max_delay = 20-timeout;
+		pr_warn("Warning: MMC controller hung for %d ms\n", host->max_delay);
+	}
+
+	clk |= SDHCI_CLOCK_CARD_EN;
+	bcm2835_mmc_writew(host, clk, SDHCI_CLOCK_CONTROL);
+}
+
+static void bcm2835_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct bcm2835_host *host;
+	unsigned long flags;
+
+	host = mmc_priv(mmc);
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	WARN_ON(host->mrq != NULL);
+
+	host->mrq = mrq;
+
+	if (mrq->sbc && !(host->flags & SDHCI_AUTO_CMD23))
+		bcm2835_mmc_send_command(host, mrq->sbc);
+	else
+		bcm2835_mmc_send_command(host, mrq->cmd);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+
+	if (!(mrq->sbc && !(host->flags & SDHCI_AUTO_CMD23)) && mrq->cmd->data && host->use_dma) {
+		/* DMA transfer starts now, PIO starts after interrupt */
+		bcm2835_mmc_transfer_dma(host);
+	}
+}
+
+
+static void bcm2835_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+
+	struct bcm2835_host *host = mmc_priv(mmc);
+	unsigned long flags;
+	u8 ctrl;
+	u16 clk, ctrl_2;
+
+	pr_debug("bcm2835_mmc_set_ios: clock %d, pwr %d, bus_width %d, timing %d, vdd %d, drv_type %d\n",
+		 ios->clock, ios->power_mode, ios->bus_width,
+		 ios->timing, ios->signal_voltage, ios->drv_type);
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	if (!ios->clock || ios->clock != host->clock) {
+		bcm2835_mmc_set_clock(host, ios->clock);
+		host->clock = ios->clock;
+	}
+
+	if (host->pwr != SDHCI_POWER_330) {
+		host->pwr = SDHCI_POWER_330;
+		bcm2835_mmc_writeb(host, SDHCI_POWER_330 | SDHCI_POWER_ON, SDHCI_POWER_CONTROL);
+	}
+
+	ctrl = bcm2835_mmc_readb(host, SDHCI_HOST_CONTROL);
+
+	/* set bus width */
+	ctrl &= ~SDHCI_CTRL_8BITBUS;
+	if (ios->bus_width == MMC_BUS_WIDTH_4)
+		ctrl |= SDHCI_CTRL_4BITBUS;
+	else
+		ctrl &= ~SDHCI_CTRL_4BITBUS;
+
+	ctrl &= ~SDHCI_CTRL_HISPD; /* NO_HISPD_BIT */
+
+
+	bcm2835_mmc_writeb(host, ctrl, SDHCI_HOST_CONTROL);
+	/*
+	 * We only need to set Driver Strength if the
+	 * preset value enable is not set.
+	 */
+	ctrl_2 = bcm2835_mmc_readw(host, SDHCI_HOST_CONTROL2);
+	ctrl_2 &= ~SDHCI_CTRL_DRV_TYPE_MASK;
+	if (ios->drv_type == MMC_SET_DRIVER_TYPE_A)
+		ctrl_2 |= SDHCI_CTRL_DRV_TYPE_A;
+	else if (ios->drv_type == MMC_SET_DRIVER_TYPE_C)
+		ctrl_2 |= SDHCI_CTRL_DRV_TYPE_C;
+
+	bcm2835_mmc_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
+
+	/* Reset SD Clock Enable */
+	clk = bcm2835_mmc_readw(host, SDHCI_CLOCK_CONTROL);
+	clk &= ~SDHCI_CLOCK_CARD_EN;
+	bcm2835_mmc_writew(host, clk, SDHCI_CLOCK_CONTROL);
+
+	/* Re-enable SD Clock */
+	bcm2835_mmc_set_clock(host, host->clock);
+	bcm2835_mmc_writeb(host, ctrl, SDHCI_HOST_CONTROL);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+
+static struct mmc_host_ops bcm2835_ops = {
+	.request = bcm2835_mmc_request,
+	.set_ios = bcm2835_mmc_set_ios,
+	.enable_sdio_irq = bcm2835_mmc_enable_sdio_irq,
+	.ack_sdio_irq = bcm2835_mmc_ack_sdio_irq,
+};
+
+
+static void bcm2835_mmc_tasklet_finish(unsigned long param)
+{
+	struct bcm2835_host *host;
+	unsigned long flags;
+	struct mmc_request *mrq;
+
+	host = (struct bcm2835_host *)param;
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	/*
+	 * If this tasklet gets rescheduled while running, it will
+	 * be run again afterwards but without any active request.
+	 */
+	if (!host->mrq) {
+		spin_unlock_irqrestore(&host->lock, flags);
+		return;
+	}
+
+	del_timer(&host->timer);
+
+	mrq = host->mrq;
+
+	/*
+	 * The controller needs a reset of internal state machines
+	 * upon error conditions.
+	 */
+	if (!(host->flags & SDHCI_DEVICE_DEAD) &&
+	    ((mrq->cmd && mrq->cmd->error) ||
+		 (mrq->data && (mrq->data->error ||
+		  (mrq->data->stop && mrq->data->stop->error))))) {
+
+		spin_unlock_irqrestore(&host->lock, flags);
+		bcm2835_mmc_reset(host, SDHCI_RESET_CMD);
+		bcm2835_mmc_reset(host, SDHCI_RESET_DATA);
+		spin_lock_irqsave(&host->lock, flags);
+	}
+
+	host->mrq = NULL;
+	host->cmd = NULL;
+	host->data = NULL;
+
+	spin_unlock_irqrestore(&host->lock, flags);
+	mmc_request_done(host->mmc, mrq);
+}
+
+
+
+static int bcm2835_mmc_add_host(struct bcm2835_host *host)
+{
+	struct mmc_host *mmc = host->mmc;
+	struct device *dev = mmc->parent;
+#ifndef FORCE_PIO
+	struct dma_slave_config cfg;
+#endif
+	int ret;
+
+	bcm2835_mmc_reset(host, SDHCI_RESET_ALL);
+
+	host->clk_mul = 0;
+
+	if (!mmc->f_max || mmc->f_max > host->max_clk)
+		mmc->f_max = host->max_clk;
+	mmc->f_min = host->max_clk / SDHCI_MAX_DIV_SPEC_300;
+
+	/* SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK */
+	host->timeout_clk = mmc->f_max / 1000;
+	mmc->max_busy_timeout = (1 << 27) / host->timeout_clk;
+
+	/* host controller capabilities */
+	mmc->caps |= MMC_CAP_CMD23 | MMC_CAP_NEEDS_POLL |
+		MMC_CAP_SDIO_IRQ | MMC_CAP_SD_HIGHSPEED |
+		MMC_CAP_MMC_HIGHSPEED;
+
+	mmc->caps2 |= MMC_CAP2_SDIO_IRQ_NOTHREAD;
+
+	host->flags = SDHCI_AUTO_CMD23;
+
+	dev_info(dev, "mmc_debug:%x mmc_debug2:%x\n", mmc_debug, mmc_debug2);
+#ifdef FORCE_PIO
+	dev_info(dev, "Forcing PIO mode\n");
+	host->have_dma = false;
+#else
+	if (IS_ERR_OR_NULL(host->dma_chan_rxtx)) {
+		dev_err(dev, "%s: Unable to initialise DMA channel. Falling back to PIO\n",
+			DRIVER_NAME);
+		host->have_dma = false;
+	} else {
+		dev_info(dev, "DMA channel allocated");
+
+		cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+		cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+		/* Validate the slave configurations */
+
+		cfg.direction = DMA_MEM_TO_DEV;
+		cfg.src_addr = 0;
+		cfg.dst_addr = host->bus_addr + SDHCI_BUFFER;
+
+		ret = dmaengine_slave_config(host->dma_chan_rxtx, &cfg);
+
+		if (ret == 0) {
+			host->dma_cfg_tx = cfg;
+
+			cfg.direction = DMA_DEV_TO_MEM;
+			cfg.src_addr = host->bus_addr + SDHCI_BUFFER;
+			cfg.dst_addr = 0;
+
+			ret = dmaengine_slave_config(host->dma_chan_rxtx, &cfg);
+		}
+
+		if (ret == 0) {
+			host->dma_cfg_rx = cfg;
+
+			host->have_dma = true;
+		} else {
+			pr_err("%s: unable to configure DMA channel. "
+			       "Falling back to PIO\n",
+			       mmc_hostname(mmc));
+			dma_release_channel(host->dma_chan_rxtx);
+			host->dma_chan_rxtx = NULL;
+			host->have_dma = false;
+		}
+	}
+#endif
+	mmc->max_segs = 128;
+	mmc->max_req_size = min_t(size_t, 524288, dma_max_mapping_size(dev));
+	mmc->max_seg_size = mmc->max_req_size;
+	mmc->max_blk_size = 512;
+	mmc->max_blk_count =  65535;
+
+	/* report supported voltage ranges */
+	mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+
+	tasklet_init(&host->finish_tasklet,
+		bcm2835_mmc_tasklet_finish, (unsigned long)host);
+
+	timer_setup(&host->timer, bcm2835_mmc_timeout_timer, 0);
+	init_waitqueue_head(&host->buf_ready_int);
+
+	bcm2835_mmc_init(host, 0);
+	ret = request_irq(host->irq, bcm2835_mmc_irq, IRQF_SHARED,
+				   mmc_hostname(mmc), host);
+	if (ret) {
+		dev_err(dev, "Failed to request IRQ %d: %d\n", host->irq, ret);
+		goto untasklet;
+	}
+
+	ret = mmc_add_host(mmc);
+	if (ret) {
+		dev_err(dev, "could not add MMC host\n");
+		goto free_irq;
+	}
+
+	return 0;
+
+free_irq:
+	free_irq(host->irq, host);
+untasklet:
+	tasklet_kill(&host->finish_tasklet);
+
+	return ret;
+}
+
+static int bcm2835_mmc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct clk *clk;
+	struct resource *iomem;
+	struct bcm2835_host *host;
+	struct mmc_host *mmc;
+	int ret;
+
+	mmc = mmc_alloc_host(sizeof(*host), dev);
+	if (!mmc)
+		return -ENOMEM;
+
+	mmc->ops = &bcm2835_ops;
+	host = mmc_priv(mmc);
+	host->mmc = mmc;
+	host->timeout = msecs_to_jiffies(1000);
+	spin_lock_init(&host->lock);
+
+	host->ioaddr = devm_platform_get_and_ioremap_resource(pdev, 0, &iomem);
+	if (IS_ERR(host->ioaddr)) {
+		ret = PTR_ERR(host->ioaddr);
+		goto err;
+	}
+
+	host->bus_addr = iomem->start;
+
+#ifndef FORCE_PIO
+	if (node) {
+		host->dma_chan_rxtx = dma_request_slave_channel(dev, "rx-tx");
+		if (!host->dma_chan_rxtx)
+			host->dma_chan_rxtx =
+				dma_request_slave_channel(dev, "tx");
+		if (!host->dma_chan_rxtx)
+			host->dma_chan_rxtx =
+				dma_request_slave_channel(dev, "rx");
+	} else {
+		dma_cap_mask_t mask;
+
+		dma_cap_zero(mask);
+		/* we don't care about the channel, any would work */
+		dma_cap_set(DMA_SLAVE, mask);
+		host->dma_chan_rxtx = dma_request_channel(mask, NULL, NULL);
+	}
+#endif
+	clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(clk)) {
+		ret = PTR_ERR(clk);
+		if (ret == -EPROBE_DEFER)
+			dev_info(dev, "could not get clk, deferring probe\n");
+		else
+			dev_err(dev, "could not get clk\n");
+		goto err;
+	}
+
+	host->max_clk = clk_get_rate(clk);
+
+	host->irq = platform_get_irq(pdev, 0);
+	if (host->irq <= 0) {
+		dev_err(dev, "get IRQ failed\n");
+		ret = -EINVAL;
+		goto err;
+	}
+
+	if (node) {
+		ret = mmc_of_parse(mmc);
+		if (ret)
+			goto err;
+
+		/* Read any custom properties */
+		of_property_read_u32(node,
+				     "brcm,overclock-50",
+				     &host->overclock_50);
+	} else {
+		mmc->caps |= MMC_CAP_4_BIT_DATA;
+	}
+
+	ret = bcm2835_mmc_add_host(host);
+	if (ret)
+		goto err;
+
+	platform_set_drvdata(pdev, host);
+
+	return 0;
+err:
+	if (host->dma_chan_rxtx)
+		dma_release_channel(host->dma_chan_rxtx);
+	mmc_free_host(mmc);
+
+	return ret;
+}
+
+static void bcm2835_mmc_remove(struct platform_device *pdev)
+{
+	struct bcm2835_host *host = platform_get_drvdata(pdev);
+	unsigned long flags;
+	int dead;
+	u32 scratch;
+
+	dead = 0;
+	scratch = bcm2835_mmc_readl(host, SDHCI_INT_STATUS);
+	if (scratch == (u32)-1)
+		dead = 1;
+
+
+	if (dead) {
+		spin_lock_irqsave(&host->lock, flags);
+
+		host->flags |= SDHCI_DEVICE_DEAD;
+
+		if (host->mrq) {
+			pr_err("%s: Controller removed during "
+				" transfer!\n", mmc_hostname(host->mmc));
+
+			host->mrq->cmd->error = -ENOMEDIUM;
+			tasklet_schedule(&host->finish_tasklet);
+		}
+
+		spin_unlock_irqrestore(&host->lock, flags);
+	}
+
+	mmc_remove_host(host->mmc);
+
+	if (!dead)
+		bcm2835_mmc_reset(host, SDHCI_RESET_ALL);
+
+	free_irq(host->irq, host);
+
+	del_timer_sync(&host->timer);
+
+	tasklet_kill(&host->finish_tasklet);
+
+	if (host->dma_chan_rxtx)
+		dma_release_channel(host->dma_chan_rxtx);
+
+	mmc_free_host(host->mmc);
+}
+
+
+static const struct of_device_id bcm2835_mmc_match[] = {
+	{ .compatible = "brcm,bcm2835-mmc" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, bcm2835_mmc_match);
+
+
+
+static struct platform_driver bcm2835_mmc_driver = {
+	.probe      = bcm2835_mmc_probe,
+	.remove     = bcm2835_mmc_remove,
+	.driver     = {
+		.name		= DRIVER_NAME,
+		.owner		= THIS_MODULE,
+		.of_match_table	= bcm2835_mmc_match,
+	},
+};
+module_platform_driver(bcm2835_mmc_driver);
+
+module_param(mmc_debug, uint, 0644);
+module_param(mmc_debug2, uint, 0644);
+MODULE_ALIAS("platform:mmc-bcm2835");
+MODULE_DESCRIPTION("BCM2835 SDHCI driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Gellert Weisz");
diff --git a/drivers/mmc/host/bcm2835-sdhost.c b/drivers/mmc/host/bcm2835-sdhost.c
new file mode 100644
index 000000000000..45e7ed7c0f85
--- /dev/null
+++ b/drivers/mmc/host/bcm2835-sdhost.c
@@ -0,0 +1,2219 @@
+/*
+ * BCM2835 SD host driver.
+ *
+ * Author:      Phil Elwell <phil@raspberrypi.org>
+ *              Copyright (C) 2015-2016 Raspberry Pi (Trading) Ltd.
+ *
+ * Based on
+ *  mmc-bcm2835.c by Gellert Weisz
+ * which is, in turn, based on
+ *  sdhci-bcm2708.c by Broadcom
+ *  sdhci-bcm2835.c by Stephen Warren and Oleksandr Tymoshenko
+ *  sdhci.c and sdhci-pci.c by Pierre Ossman
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define FIFO_READ_THRESHOLD     4
+#define FIFO_WRITE_THRESHOLD    4
+#define ALLOW_CMD23_READ        1
+#define ALLOW_CMD23_WRITE       0
+#define ENABLE_LOG              1
+#define SDDATA_FIFO_PIO_BURST   8
+#define CMD_DALLY_US            1
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/sd.h>
+#include <linux/mmc/sdio.h>
+#include <linux/scatterlist.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/clk.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/blkdev.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/of_dma.h>
+#include <linux/time.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/highmem.h>
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
+/* For mmc_card_blockaddr */
+#include "../core/card.h"
+#include "mmc_hsq.h"
+
+#define DRIVER_NAME "sdhost-bcm2835"
+
+#define SDCMD  0x00 /* Command to SD card              - 16 R/W */
+#define SDARG  0x04 /* Argument to SD card             - 32 R/W */
+#define SDTOUT 0x08 /* Start value for timeout counter - 32 R/W */
+#define SDCDIV 0x0c /* Start value for clock divider   - 11 R/W */
+#define SDRSP0 0x10 /* SD card response (31:0)         - 32 R   */
+#define SDRSP1 0x14 /* SD card response (63:32)        - 32 R   */
+#define SDRSP2 0x18 /* SD card response (95:64)        - 32 R   */
+#define SDRSP3 0x1c /* SD card response (127:96)       - 32 R   */
+#define SDHSTS 0x20 /* SD host status                  - 11 R   */
+#define SDVDD  0x30 /* SD card power control           -  1 R/W */
+#define SDEDM  0x34 /* Emergency Debug Mode            - 13 R/W */
+#define SDHCFG 0x38 /* Host configuration              -  2 R/W */
+#define SDHBCT 0x3c /* Host byte count (debug)         - 32 R/W */
+#define SDDATA 0x40 /* Data to/from SD card            - 32 R/W */
+#define SDHBLC 0x50 /* Host block count (SDIO/SDHC)    -  9 R/W */
+
+#define SDCMD_NEW_FLAG                  0x8000
+#define SDCMD_FAIL_FLAG                 0x4000
+#define SDCMD_BUSYWAIT                  0x800
+#define SDCMD_NO_RESPONSE               0x400
+#define SDCMD_LONG_RESPONSE             0x200
+#define SDCMD_WRITE_CMD                 0x80
+#define SDCMD_READ_CMD                  0x40
+#define SDCMD_CMD_MASK                  0x3f
+
+#define SDCDIV_MAX_CDIV                 0x7ff
+
+#define SDHSTS_BUSY_IRPT                0x400
+#define SDHSTS_BLOCK_IRPT               0x200
+#define SDHSTS_SDIO_IRPT                0x100
+#define SDHSTS_REW_TIME_OUT             0x80
+#define SDHSTS_CMD_TIME_OUT             0x40
+#define SDHSTS_CRC16_ERROR              0x20
+#define SDHSTS_CRC7_ERROR               0x10
+#define SDHSTS_FIFO_ERROR               0x08
+/* Reserved */
+/* Reserved */
+#define SDHSTS_DATA_FLAG                0x01
+
+#define SDHSTS_TRANSFER_ERROR_MASK      (SDHSTS_CRC7_ERROR|SDHSTS_CRC16_ERROR|SDHSTS_REW_TIME_OUT|SDHSTS_FIFO_ERROR)
+#define SDHSTS_ERROR_MASK               (SDHSTS_CMD_TIME_OUT|SDHSTS_TRANSFER_ERROR_MASK)
+
+#define SDHCFG_BUSY_IRPT_EN     (1<<10)
+#define SDHCFG_BLOCK_IRPT_EN    (1<<8)
+#define SDHCFG_SDIO_IRPT_EN     (1<<5)
+#define SDHCFG_DATA_IRPT_EN     (1<<4)
+#define SDHCFG_SLOW_CARD        (1<<3)
+#define SDHCFG_WIDE_EXT_BUS     (1<<2)
+#define SDHCFG_WIDE_INT_BUS     (1<<1)
+#define SDHCFG_REL_CMD_LINE     (1<<0)
+
+#define SDEDM_FORCE_DATA_MODE   (1<<19)
+#define SDEDM_CLOCK_PULSE       (1<<20)
+#define SDEDM_BYPASS            (1<<21)
+
+#define SDEDM_WRITE_THRESHOLD_SHIFT 9
+#define SDEDM_READ_THRESHOLD_SHIFT 14
+#define SDEDM_THRESHOLD_MASK     0x1f
+
+#define SDEDM_FSM_MASK           0xf
+#define SDEDM_FSM_IDENTMODE      0x0
+#define SDEDM_FSM_DATAMODE       0x1
+#define SDEDM_FSM_READDATA       0x2
+#define SDEDM_FSM_WRITEDATA      0x3
+#define SDEDM_FSM_READWAIT       0x4
+#define SDEDM_FSM_READCRC        0x5
+#define SDEDM_FSM_WRITECRC       0x6
+#define SDEDM_FSM_WRITEWAIT1     0x7
+#define SDEDM_FSM_POWERDOWN      0x8
+#define SDEDM_FSM_POWERUP        0x9
+#define SDEDM_FSM_WRITESTART1    0xa
+#define SDEDM_FSM_WRITESTART2    0xb
+#define SDEDM_FSM_GENPULSES      0xc
+#define SDEDM_FSM_WRITEWAIT2     0xd
+#define SDEDM_FSM_STARTPOWDOWN   0xf
+
+#define SDDATA_FIFO_WORDS        16
+
+#define USE_CMD23_FLAGS          ((ALLOW_CMD23_READ * MMC_DATA_READ) | \
+				  (ALLOW_CMD23_WRITE * MMC_DATA_WRITE))
+
+#define MHZ 1000000
+
+
+struct bcm2835_host {
+	spinlock_t		lock;
+
+	struct rpi_firmware	*fw;
+
+	void __iomem		*ioaddr;
+	phys_addr_t		bus_addr;
+
+	struct mmc_host		*mmc;
+
+	u32			pio_timeout;	/* In jiffies */
+
+	int			clock;		/* Current clock speed */
+
+	bool			slow_card;	/* Force 11-bit divisor */
+
+	unsigned int		max_clk;	/* Max possible freq */
+
+	struct tasklet_struct	finish_tasklet;	/* Tasklet structures */
+
+	struct work_struct	cmd_wait_wq;	/* Workqueue function */
+
+	struct timer_list	timer;		/* Timer for timeouts */
+
+	struct sg_mapping_iter	sg_miter;	/* SG state for PIO */
+	unsigned int		blocks;		/* remaining PIO blocks */
+
+	int			irq;		/* Device IRQ */
+
+	u32			cmd_quick_poll_retries;
+	u32			ns_per_fifo_word;
+
+	/* cached registers */
+	u32			hcfg;
+	u32			cdiv;
+
+	struct mmc_request		*mrq;			/* Current request */
+	struct mmc_command		*cmd;			/* Current command */
+	struct mmc_data			*data;			/* Current data request */
+	unsigned int			data_complete:1;	/* Data finished before cmd */
+
+	unsigned int			flush_fifo:1;		/* Drain the fifo when finishing */
+
+	unsigned int			use_busy:1;		/* Wait for busy interrupt */
+
+	unsigned int			use_sbc:1;		/* Send CMD23 */
+
+	unsigned int			debug:1;		/* Enable debug output */
+	unsigned int			firmware_sets_cdiv:1;	/* Let the firmware manage the clock */
+	unsigned int			reset_clock:1;		/* Reset the clock fore the next request */
+
+	/*DMA part*/
+	struct dma_chan			*dma_chan_rxtx;		/* DMA channel for reads and writes */
+	struct dma_chan			*dma_chan;		/* Channel in use */
+	struct dma_slave_config		dma_cfg_rx;
+	struct dma_slave_config		dma_cfg_tx;
+	struct dma_async_tx_descriptor	*dma_desc;
+	u32				dma_dir;
+	u32				drain_words;
+	struct page 			*drain_page;
+	u32				drain_offset;
+
+	bool				allow_dma;
+	bool				use_dma;
+	/*end of DMA part*/
+
+	int				max_delay;	/* maximum length of time spent waiting */
+	struct timespec64		stop_time;	/* when the last stop was issued */
+	u32				delay_after_stop; /* minimum time between stop and subsequent data transfer */
+	u32				delay_after_this_stop; /* minimum time between this stop and subsequent data transfer */
+	u32				user_overclock_50; /* User's preferred frequency to use when 50MHz is requested (in MHz) */
+	u32				overclock_50;	/* frequency to use when 50MHz is requested (in MHz) */
+	u32				overclock;	/* Current frequency if overclocked, else zero */
+	u32				pio_limit;	/* Maximum block count for PIO (0 = always DMA) */
+
+	u32				sectors;	/* Cached card size in sectors */
+};
+
+#if ENABLE_LOG
+
+struct log_entry_struct {
+	char event[4];
+	u32 timestamp;
+	u32 param1;
+	u32 param2;
+};
+
+typedef struct log_entry_struct LOG_ENTRY_T;
+
+LOG_ENTRY_T *sdhost_log_buf;
+dma_addr_t sdhost_log_addr;
+static u32 sdhost_log_idx;
+static spinlock_t log_lock;
+static void __iomem *timer_base;
+
+#define LOG_ENTRIES (256*1)
+#define LOG_SIZE (sizeof(LOG_ENTRY_T)*LOG_ENTRIES)
+
+static void log_init(struct device *dev)
+{
+	struct device_node *np;
+
+	spin_lock_init(&log_lock);
+
+	np = of_find_compatible_node(NULL, NULL,
+				     "brcm,bcm2835-system-timer");
+	timer_base = of_iomap(np, 0);
+
+	if (timer_base) {
+		sdhost_log_buf = dma_alloc_coherent(dev, LOG_SIZE, &sdhost_log_addr,
+							GFP_KERNEL);
+		if (sdhost_log_buf)
+			pr_info("sdhost: log_buf @ %p (%llx)\n",
+				sdhost_log_buf, (u64)sdhost_log_addr);
+		else
+			pr_err("sdhost: failed to allocate log buf\n");
+	} else {
+		pr_err("sdhost: failed to remap timer - wrong dtb?\n");
+	}
+}
+
+static void log_event_impl(const char *event, u32 param1, u32 param2)
+{
+	if (sdhost_log_buf) {
+		LOG_ENTRY_T *entry;
+		unsigned long flags;
+
+		spin_lock_irqsave(&log_lock, flags);
+
+		entry = sdhost_log_buf + sdhost_log_idx;
+		memcpy(entry->event, event, 4);
+		entry->timestamp = (readl(timer_base + 4) & 0x3fffffff) +
+			(smp_processor_id()<<30);
+		entry->param1 = param1;
+		entry->param2 = param2;
+		sdhost_log_idx = (sdhost_log_idx + 1) % LOG_ENTRIES;
+
+		spin_unlock_irqrestore(&log_lock, flags);
+	}
+}
+
+static void log_dump(void)
+{
+	if (sdhost_log_buf) {
+		LOG_ENTRY_T *entry;
+		unsigned long flags;
+		int idx;
+
+		spin_lock_irqsave(&log_lock, flags);
+
+		idx = sdhost_log_idx;
+		do {
+			entry = sdhost_log_buf + idx;
+			if (entry->event[0] != '\0')
+				pr_info("[%08x] %.4s %x %x\n",
+				       entry->timestamp,
+				       entry->event,
+				       entry->param1,
+				       entry->param2);
+			idx = (idx + 1) % LOG_ENTRIES;
+		} while (idx != sdhost_log_idx);
+
+		spin_unlock_irqrestore(&log_lock, flags);
+	}
+}
+
+#define log_event(event, param1, param2) log_event_impl(event, (u32)(uintptr_t)param1, (u32)(uintptr_t)param2)
+
+#else
+
+#define log_init(x) (void)0
+#define log_event(event, param1, param2) (void)0
+#define log_dump() (void)0
+
+#endif
+
+static inline void bcm2835_sdhost_write(struct bcm2835_host *host, u32 val, int reg)
+{
+	writel(val, host->ioaddr + reg);
+}
+
+static inline u32 bcm2835_sdhost_read(struct bcm2835_host *host, int reg)
+{
+	return readl(host->ioaddr + reg);
+}
+
+static inline u32 bcm2835_sdhost_read_relaxed(struct bcm2835_host *host, int reg)
+{
+	return readl_relaxed(host->ioaddr + reg);
+}
+
+static void bcm2835_sdhost_dumpcmd(struct bcm2835_host *host,
+				   struct mmc_command *cmd,
+				   const char *label)
+{
+	if (cmd)
+		pr_info("%s:%c%s op %d arg 0x%x flags 0x%x - resp %08x %08x %08x %08x, err %d\n",
+			mmc_hostname(host->mmc),
+			(cmd == host->cmd) ? '>' : ' ',
+			label, cmd->opcode, cmd->arg, cmd->flags,
+			cmd->resp[0], cmd->resp[1], cmd->resp[2], cmd->resp[3],
+			cmd->error);
+}
+
+static void bcm2835_sdhost_dumpregs(struct bcm2835_host *host)
+{
+	if (host->mrq)
+	{
+		bcm2835_sdhost_dumpcmd(host, host->mrq->sbc, "sbc");
+		bcm2835_sdhost_dumpcmd(host, host->mrq->cmd, "cmd");
+		if (host->mrq->data)
+			pr_info("%s: data blocks %x blksz %x - err %d\n",
+			       mmc_hostname(host->mmc),
+			       host->mrq->data->blocks,
+			       host->mrq->data->blksz,
+			       host->mrq->data->error);
+		bcm2835_sdhost_dumpcmd(host, host->mrq->stop, "stop");
+	}
+
+	pr_info("%s: =========== REGISTER DUMP ===========\n",
+		mmc_hostname(host->mmc));
+
+	pr_info("%s: SDCMD  0x%08x\n",
+		mmc_hostname(host->mmc),
+		bcm2835_sdhost_read(host, SDCMD));
+	pr_info("%s: SDARG  0x%08x\n",
+		mmc_hostname(host->mmc),
+		bcm2835_sdhost_read(host, SDARG));
+	pr_info("%s: SDTOUT 0x%08x\n",
+		mmc_hostname(host->mmc),
+		bcm2835_sdhost_read(host, SDTOUT));
+	pr_info("%s: SDCDIV 0x%08x\n",
+		mmc_hostname(host->mmc),
+		bcm2835_sdhost_read(host, SDCDIV));
+	pr_info("%s: SDRSP0 0x%08x\n",
+		mmc_hostname(host->mmc),
+		bcm2835_sdhost_read(host, SDRSP0));
+	pr_info("%s: SDRSP1 0x%08x\n",
+		mmc_hostname(host->mmc),
+		bcm2835_sdhost_read(host, SDRSP1));
+	pr_info("%s: SDRSP2 0x%08x\n",
+		mmc_hostname(host->mmc),
+		bcm2835_sdhost_read(host, SDRSP2));
+	pr_info("%s: SDRSP3 0x%08x\n",
+		mmc_hostname(host->mmc),
+		bcm2835_sdhost_read(host, SDRSP3));
+	pr_info("%s: SDHSTS 0x%08x\n",
+		mmc_hostname(host->mmc),
+		bcm2835_sdhost_read(host, SDHSTS));
+	pr_info("%s: SDVDD  0x%08x\n",
+		mmc_hostname(host->mmc),
+		bcm2835_sdhost_read(host, SDVDD));
+	pr_info("%s: SDEDM  0x%08x\n",
+		mmc_hostname(host->mmc),
+		bcm2835_sdhost_read(host, SDEDM));
+	pr_info("%s: SDHCFG 0x%08x\n",
+		mmc_hostname(host->mmc),
+		bcm2835_sdhost_read(host, SDHCFG));
+	pr_info("%s: SDHBCT 0x%08x\n",
+		mmc_hostname(host->mmc),
+		bcm2835_sdhost_read(host, SDHBCT));
+	pr_info("%s: SDHBLC 0x%08x\n",
+		mmc_hostname(host->mmc),
+		bcm2835_sdhost_read(host, SDHBLC));
+
+	pr_info("%s: ===========================================\n",
+		mmc_hostname(host->mmc));
+}
+
+static void bcm2835_sdhost_set_power(struct bcm2835_host *host, bool on)
+{
+	bcm2835_sdhost_write(host, on ? 1 : 0, SDVDD);
+}
+
+static void bcm2835_sdhost_reset_internal(struct bcm2835_host *host)
+{
+	u32 temp;
+
+	if (host->debug)
+		pr_info("%s: reset\n", mmc_hostname(host->mmc));
+
+	bcm2835_sdhost_set_power(host, false);
+
+	bcm2835_sdhost_write(host, 0, SDCMD);
+	bcm2835_sdhost_write(host, 0, SDARG);
+	bcm2835_sdhost_write(host, 0xf00000, SDTOUT);
+	bcm2835_sdhost_write(host, 0, SDCDIV);
+	bcm2835_sdhost_write(host, 0x7f8, SDHSTS); /* Write 1s to clear */
+	bcm2835_sdhost_write(host, 0, SDHCFG);
+	bcm2835_sdhost_write(host, 0, SDHBCT);
+	bcm2835_sdhost_write(host, 0, SDHBLC);
+
+	/* Limit fifo usage due to silicon bug */
+	temp = bcm2835_sdhost_read(host, SDEDM);
+	temp &= ~((SDEDM_THRESHOLD_MASK<<SDEDM_READ_THRESHOLD_SHIFT) |
+		  (SDEDM_THRESHOLD_MASK<<SDEDM_WRITE_THRESHOLD_SHIFT));
+	temp |= (FIFO_READ_THRESHOLD << SDEDM_READ_THRESHOLD_SHIFT) |
+		(FIFO_WRITE_THRESHOLD << SDEDM_WRITE_THRESHOLD_SHIFT);
+	bcm2835_sdhost_write(host, temp, SDEDM);
+	mdelay(10);
+	bcm2835_sdhost_set_power(host, true);
+	mdelay(10);
+	host->clock = 0;
+	host->sectors = 0;
+	bcm2835_sdhost_write(host, host->hcfg, SDHCFG);
+	bcm2835_sdhost_write(host, SDCDIV_MAX_CDIV, SDCDIV);
+}
+
+#if 0 // todo fix
+static void bcm2835_sdhost_reset(struct mmc_host *mmc)
+{
+	struct bcm2835_host *host = mmc_priv(mmc);
+	unsigned long flags;
+	spin_lock_irqsave(&host->lock, flags);
+	log_event("RST<", 0, 0);
+
+	bcm2835_sdhost_reset_internal(host);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+#endif
+
+static void bcm2835_sdhost_set_ios(struct mmc_host *mmc, struct mmc_ios *ios);
+
+static void bcm2835_sdhost_init(struct bcm2835_host *host, int soft)
+{
+	pr_debug("bcm2835_sdhost_init(%d)\n", soft);
+
+	/* Set interrupt enables */
+	host->hcfg = SDHCFG_BUSY_IRPT_EN;
+
+	bcm2835_sdhost_reset_internal(host);
+
+	if (soft) {
+		/* force clock reconfiguration */
+		host->clock = 0;
+		bcm2835_sdhost_set_ios(host->mmc, &host->mmc->ios);
+	}
+}
+
+static void bcm2835_sdhost_wait_transfer_complete(struct bcm2835_host *host)
+{
+	int timediff;
+	u32 alternate_idle;
+	u32 edm;
+
+	alternate_idle = (host->mrq->data->flags & MMC_DATA_READ) ?
+		SDEDM_FSM_READWAIT : SDEDM_FSM_WRITESTART1;
+
+	edm = bcm2835_sdhost_read(host, SDEDM);
+
+	log_event("WTC<", edm, 0);
+
+	timediff = 0;
+
+	while (1) {
+		u32 fsm = edm & SDEDM_FSM_MASK;
+		if ((fsm == SDEDM_FSM_IDENTMODE) ||
+		    (fsm == SDEDM_FSM_DATAMODE))
+			break;
+		if (fsm == alternate_idle) {
+			bcm2835_sdhost_write(host,
+					     edm | SDEDM_FORCE_DATA_MODE,
+					     SDEDM);
+			break;
+		}
+
+		timediff++;
+		if (timediff == 100000) {
+			pr_err("%s: wait_transfer_complete - still waiting after %d retries\n",
+			       mmc_hostname(host->mmc),
+			       timediff);
+			log_dump();
+			bcm2835_sdhost_dumpregs(host);
+			host->mrq->data->error = -ETIMEDOUT;
+			log_event("WTC!", edm, 0);
+			return;
+		}
+		cpu_relax();
+		edm = bcm2835_sdhost_read(host, SDEDM);
+	}
+	log_event("WTC>", edm, 0);
+}
+
+static void bcm2835_sdhost_finish_data(struct bcm2835_host *host);
+
+static void bcm2835_sdhost_dma_complete(void *param)
+{
+	struct bcm2835_host *host = param;
+	struct mmc_data *data = host->data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->lock, flags);
+	log_event("DMA<", host->data, bcm2835_sdhost_read(host, SDHSTS));
+	log_event("DMA ", bcm2835_sdhost_read(host, SDCMD),
+		  bcm2835_sdhost_read(host, SDEDM));
+
+	if (host->dma_chan) {
+		dma_unmap_sg(host->dma_chan->device->dev,
+			     data->sg, data->sg_len,
+			     host->dma_dir);
+
+		host->dma_chan = NULL;
+	}
+
+	if (host->drain_words) {
+		void *page;
+		u32 *buf;
+
+		if (host->drain_offset & PAGE_MASK) {
+			host->drain_page += host->drain_offset >> PAGE_SHIFT;
+			host->drain_offset &= ~PAGE_MASK;
+		}
+
+		page = kmap_atomic(host->drain_page);
+		buf = page + host->drain_offset;
+
+		while (host->drain_words) {
+			u32 edm = bcm2835_sdhost_read(host, SDEDM);
+			if ((edm >> 4) & 0x1f)
+				*(buf++) = bcm2835_sdhost_read(host,
+							       SDDATA);
+			host->drain_words--;
+		}
+
+		kunmap_atomic(page);
+	}
+
+	bcm2835_sdhost_finish_data(host);
+
+	log_event("DMA>", host->data, 0);
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void bcm2835_sdhost_read_block_pio(struct bcm2835_host *host)
+{
+	unsigned long flags;
+	size_t blksize, len;
+	u32 *buf;
+	unsigned long wait_max;
+
+	blksize = host->data->blksz;
+
+	wait_max = jiffies + msecs_to_jiffies(host->pio_timeout);
+
+	local_irq_save(flags);
+
+	while (blksize) {
+		int copy_words;
+		u32 hsts = 0;
+
+		if (!sg_miter_next(&host->sg_miter)) {
+			host->data->error = -EINVAL;
+			break;
+		}
+
+		len = min(host->sg_miter.length, blksize);
+		if (len % 4) {
+			host->data->error = -EINVAL;
+			break;
+		}
+
+		blksize -= len;
+		host->sg_miter.consumed = len;
+
+		buf = (u32 *)host->sg_miter.addr;
+
+		copy_words = len/4;
+
+		while (copy_words) {
+			int burst_words, words;
+			u32 edm;
+
+			burst_words = SDDATA_FIFO_PIO_BURST;
+			if (burst_words > copy_words)
+				burst_words = copy_words;
+			edm = bcm2835_sdhost_read(host, SDEDM);
+			words = ((edm >> 4) & 0x1f);
+
+			if (words < burst_words) {
+				int fsm_state = (edm & SDEDM_FSM_MASK);
+				if ((fsm_state != SDEDM_FSM_READDATA) &&
+				    (fsm_state != SDEDM_FSM_READWAIT) &&
+				    (fsm_state != SDEDM_FSM_READCRC)) {
+					hsts = bcm2835_sdhost_read(host,
+								   SDHSTS);
+					pr_info("%s: fsm %x, hsts %x\n",
+					       mmc_hostname(host->mmc),
+					       fsm_state, hsts);
+					if (hsts & SDHSTS_ERROR_MASK)
+						break;
+				}
+
+				if (time_after(jiffies, wait_max)) {
+					pr_err("%s: PIO read timeout - EDM %x\n",
+					       mmc_hostname(host->mmc),
+					       edm);
+					hsts = SDHSTS_REW_TIME_OUT;
+					break;
+				}
+				ndelay((burst_words - words) *
+				       host->ns_per_fifo_word);
+				continue;
+			} else if (words > copy_words) {
+				words = copy_words;
+			}
+
+			copy_words -= words;
+
+			while (words) {
+				*(buf++) = bcm2835_sdhost_read(host, SDDATA);
+				words--;
+			}
+		}
+
+		if (hsts & SDHSTS_ERROR_MASK)
+			break;
+	}
+
+	sg_miter_stop(&host->sg_miter);
+
+	local_irq_restore(flags);
+}
+
+static void bcm2835_sdhost_write_block_pio(struct bcm2835_host *host)
+{
+	unsigned long flags;
+	size_t blksize, len;
+	u32 *buf;
+	unsigned long wait_max;
+
+	blksize = host->data->blksz;
+
+	wait_max = jiffies + msecs_to_jiffies(host->pio_timeout);
+
+	local_irq_save(flags);
+
+	while (blksize) {
+		int copy_words;
+		u32 hsts = 0;
+
+		if (!sg_miter_next(&host->sg_miter)) {
+			host->data->error = -EINVAL;
+			break;
+		}
+
+		len = min(host->sg_miter.length, blksize);
+		if (len % 4) {
+			host->data->error = -EINVAL;
+			break;
+		}
+
+		blksize -= len;
+		host->sg_miter.consumed = len;
+
+		buf = (u32 *)host->sg_miter.addr;
+
+		copy_words = len/4;
+
+		while (copy_words) {
+			int burst_words, words;
+			u32 edm;
+
+			burst_words = SDDATA_FIFO_PIO_BURST;
+			if (burst_words > copy_words)
+				burst_words = copy_words;
+			edm = bcm2835_sdhost_read(host, SDEDM);
+			words = SDDATA_FIFO_WORDS - ((edm >> 4) & 0x1f);
+
+			if (words < burst_words) {
+				int fsm_state = (edm & SDEDM_FSM_MASK);
+				if ((fsm_state != SDEDM_FSM_WRITEDATA) &&
+				    (fsm_state != SDEDM_FSM_WRITESTART1) &&
+				    (fsm_state != SDEDM_FSM_WRITESTART2)) {
+					hsts = bcm2835_sdhost_read(host,
+								   SDHSTS);
+					pr_info("%s: fsm %x, hsts %x\n",
+					       mmc_hostname(host->mmc),
+					       fsm_state, hsts);
+					if (hsts & SDHSTS_ERROR_MASK)
+						break;
+				}
+
+				if (time_after(jiffies, wait_max)) {
+					pr_err("%s: PIO write timeout - EDM %x\n",
+					       mmc_hostname(host->mmc),
+					       edm);
+					hsts = SDHSTS_REW_TIME_OUT;
+					break;
+				}
+				ndelay((burst_words - words) *
+				       host->ns_per_fifo_word);
+				continue;
+			} else if (words > copy_words) {
+				words = copy_words;
+			}
+
+			copy_words -= words;
+
+			while (words) {
+				bcm2835_sdhost_write(host, *(buf++), SDDATA);
+				words--;
+			}
+		}
+
+		if (hsts & SDHSTS_ERROR_MASK)
+			break;
+	}
+
+	sg_miter_stop(&host->sg_miter);
+
+	local_irq_restore(flags);
+}
+
+static void bcm2835_sdhost_transfer_pio(struct bcm2835_host *host)
+{
+	u32 sdhsts;
+	bool is_read;
+	BUG_ON(!host->data);
+	log_event("XFP<", host->data, host->blocks);
+
+	is_read = (host->data->flags & MMC_DATA_READ) != 0;
+	if (is_read)
+		bcm2835_sdhost_read_block_pio(host);
+	else
+		bcm2835_sdhost_write_block_pio(host);
+
+	sdhsts = bcm2835_sdhost_read(host, SDHSTS);
+	if (sdhsts & (SDHSTS_CRC16_ERROR |
+		      SDHSTS_CRC7_ERROR |
+		      SDHSTS_FIFO_ERROR)) {
+		pr_err("%s: %s transfer error - HSTS %x\n",
+		       mmc_hostname(host->mmc),
+		       is_read ? "read" : "write",
+		       sdhsts);
+		host->data->error = -EILSEQ;
+	} else if ((sdhsts & (SDHSTS_CMD_TIME_OUT |
+			      SDHSTS_REW_TIME_OUT))) {
+		pr_err("%s: %s timeout error - HSTS %x\n",
+		       mmc_hostname(host->mmc),
+		       is_read ? "read" : "write",
+		       sdhsts);
+		host->data->error = -ETIMEDOUT;
+	}
+	log_event("XFP>", host->data, host->blocks);
+}
+
+static void bcm2835_sdhost_prepare_dma(struct bcm2835_host *host,
+	struct mmc_data *data)
+{
+	int len, dir_data, dir_slave;
+	struct dma_async_tx_descriptor *desc = NULL;
+	struct dma_chan *dma_chan;
+
+	log_event("PRD<", data, 0);
+	pr_debug("bcm2835_sdhost_prepare_dma()\n");
+
+	dma_chan = host->dma_chan_rxtx;
+	if (data->flags & MMC_DATA_READ) {
+		dir_data = DMA_FROM_DEVICE;
+		dir_slave = DMA_DEV_TO_MEM;
+	} else {
+		dir_data = DMA_TO_DEVICE;
+		dir_slave = DMA_MEM_TO_DEV;
+	}
+	log_event("PRD1", dma_chan, 0);
+
+	BUG_ON(!dma_chan->device);
+	BUG_ON(!dma_chan->device->dev);
+	BUG_ON(!data->sg);
+
+	/* The block doesn't manage the FIFO DREQs properly for multi-block
+	   transfers, so don't attempt to DMA the final few words.
+	   Unfortunately this requires the final sg entry to be trimmed.
+	   N.B. This code demands that the overspill is contained in
+	   a single sg entry.
+	*/
+
+	host->drain_words = 0;
+	if ((data->blocks > 1) && (dir_data == DMA_FROM_DEVICE)) {
+		struct scatterlist *sg;
+		u32 len;
+		int i;
+
+		len = min((u32)(FIFO_READ_THRESHOLD - 1) * 4,
+			  (u32)data->blocks * data->blksz);
+
+		for_each_sg(data->sg, sg, data->sg_len, i) {
+			if (sg_is_last(sg)) {
+				BUG_ON(sg->length < len);
+				sg->length -= len;
+				host->drain_page = sg_page(sg);
+				host->drain_offset = sg->offset + sg->length;
+			}
+		}
+		host->drain_words = len/4;
+	}
+
+	/* The parameters have already been validated, so this will not fail */
+	(void)dmaengine_slave_config(dma_chan,
+				     (dir_data == DMA_FROM_DEVICE) ?
+				     &host->dma_cfg_rx :
+				     &host->dma_cfg_tx);
+
+	len = dma_map_sg(dma_chan->device->dev, data->sg, data->sg_len,
+			 dir_data);
+
+	log_event("PRD2", len, 0);
+	if (len > 0)
+		desc = dmaengine_prep_slave_sg(dma_chan, data->sg,
+					       len, dir_slave,
+					       DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+	log_event("PRD3", desc, 0);
+
+	if (desc) {
+		desc->callback = bcm2835_sdhost_dma_complete;
+		desc->callback_param = host;
+		host->dma_desc = desc;
+		host->dma_chan = dma_chan;
+		host->dma_dir = dir_data;
+	}
+	log_event("PDM>", data, 0);
+}
+
+static void bcm2835_sdhost_start_dma(struct bcm2835_host *host)
+{
+	log_event("SDMA", host->data, host->dma_chan);
+	dmaengine_submit(host->dma_desc);
+	dma_async_issue_pending(host->dma_chan);
+}
+
+static void bcm2835_sdhost_set_transfer_irqs(struct bcm2835_host *host)
+{
+	u32 all_irqs = SDHCFG_DATA_IRPT_EN | SDHCFG_BLOCK_IRPT_EN |
+		SDHCFG_BUSY_IRPT_EN;
+	if (host->dma_desc)
+		host->hcfg = (host->hcfg & ~all_irqs) |
+			SDHCFG_BUSY_IRPT_EN;
+	else
+		host->hcfg = (host->hcfg & ~all_irqs) |
+			SDHCFG_DATA_IRPT_EN |
+			SDHCFG_BUSY_IRPT_EN;
+
+	bcm2835_sdhost_write(host, host->hcfg, SDHCFG);
+}
+
+static void bcm2835_sdhost_prepare_data(struct bcm2835_host *host, struct mmc_command *cmd)
+{
+	struct mmc_data *data = cmd->data;
+
+	WARN_ON(host->data);
+
+	host->data = data;
+	if (!data)
+		return;
+
+	/* Sanity checks */
+	BUG_ON(data->blksz * data->blocks > 524288);
+	BUG_ON(data->blksz > host->mmc->max_blk_size);
+	BUG_ON(data->blocks > 65535);
+
+	host->data_complete = 0;
+	host->flush_fifo = 0;
+	host->data->bytes_xfered = 0;
+
+	if (!host->sectors && host->mmc->card) {
+		struct mmc_card *card = host->mmc->card;
+		if (!mmc_card_sd(card) && mmc_card_blockaddr(card)) {
+			/*
+			 * The EXT_CSD sector count is in number of 512 byte
+			 * sectors.
+			 */
+			host->sectors = card->ext_csd.sectors;
+		} else {
+			/*
+			 * The CSD capacity field is in units of read_blkbits.
+			 * set_capacity takes units of 512 bytes.
+			 */
+			host->sectors = card->csd.capacity <<
+				(card->csd.read_blkbits - 9);
+		}
+	}
+
+	if (!host->dma_desc) {
+		/* Use PIO */
+		int flags = SG_MITER_ATOMIC;
+
+		if (data->flags & MMC_DATA_READ)
+			flags |= SG_MITER_TO_SG;
+		else
+			flags |= SG_MITER_FROM_SG;
+		sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags);
+		host->blocks = data->blocks;
+	}
+
+	bcm2835_sdhost_set_transfer_irqs(host);
+
+	bcm2835_sdhost_write(host, data->blksz, SDHBCT);
+	bcm2835_sdhost_write(host, data->blocks, SDHBLC);
+
+	BUG_ON(!host->data);
+}
+
+static bool bcm2835_sdhost_send_command(struct bcm2835_host *host,
+				 struct mmc_command *cmd)
+{
+	u32 sdcmd, sdhsts;
+	unsigned long timeout;
+	int delay;
+
+	WARN_ON(host->cmd);
+	log_event("CMD<", cmd->opcode, cmd->arg);
+
+	if (cmd->data)
+		pr_debug("%s: send_command %d 0x%x "
+			 "(flags 0x%x) - %s %d*%d\n",
+			 mmc_hostname(host->mmc),
+			 cmd->opcode, cmd->arg, cmd->flags,
+			 (cmd->data->flags & MMC_DATA_READ) ?
+			 "read" : "write", cmd->data->blocks,
+			 cmd->data->blksz);
+	else
+		pr_debug("%s: send_command %d 0x%x (flags 0x%x)\n",
+			 mmc_hostname(host->mmc),
+			 cmd->opcode, cmd->arg, cmd->flags);
+
+	/* Wait max 100 ms */
+	timeout = 10000;
+
+	while (bcm2835_sdhost_read(host, SDCMD) & SDCMD_NEW_FLAG) {
+		if (timeout == 0) {
+			pr_warn("%s: previous command never completed.\n",
+				mmc_hostname(host->mmc));
+			if (host->debug)
+				bcm2835_sdhost_dumpregs(host);
+			cmd->error = -EILSEQ;
+			tasklet_schedule(&host->finish_tasklet);
+			return false;
+		}
+		timeout--;
+		udelay(10);
+	}
+
+	delay = (10000 - timeout)/100;
+	if (delay > host->max_delay) {
+		host->max_delay = delay;
+		pr_warn("%s: controller hung for %d ms\n",
+			   mmc_hostname(host->mmc),
+			   host->max_delay);
+	}
+
+	timeout = jiffies;
+	if (!cmd->data && cmd->busy_timeout > 9000)
+		timeout += DIV_ROUND_UP(cmd->busy_timeout, 1000) * HZ + HZ;
+	else
+		timeout += 10 * HZ;
+	mod_timer(&host->timer, timeout);
+
+	host->cmd = cmd;
+
+	/* Clear any error flags */
+	sdhsts = bcm2835_sdhost_read(host, SDHSTS);
+	if (sdhsts & SDHSTS_ERROR_MASK)
+		bcm2835_sdhost_write(host, sdhsts, SDHSTS);
+
+	if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) {
+		pr_err("%s: unsupported response type!\n",
+			mmc_hostname(host->mmc));
+		cmd->error = -EINVAL;
+		tasklet_schedule(&host->finish_tasklet);
+		return false;
+	}
+
+	bcm2835_sdhost_prepare_data(host, cmd);
+
+	bcm2835_sdhost_write(host, cmd->arg, SDARG);
+
+	sdcmd = cmd->opcode & SDCMD_CMD_MASK;
+
+	host->use_busy = 0;
+	if (!(cmd->flags & MMC_RSP_PRESENT)) {
+		sdcmd |= SDCMD_NO_RESPONSE;
+	} else {
+		if (cmd->flags & MMC_RSP_136)
+			sdcmd |= SDCMD_LONG_RESPONSE;
+		if (cmd->flags & MMC_RSP_BUSY) {
+			sdcmd |= SDCMD_BUSYWAIT;
+			host->use_busy = 1;
+		}
+	}
+
+	if (cmd->data) {
+		log_event("CMDD", cmd->data->blocks, cmd->data->blksz);
+		if (host->delay_after_this_stop) {
+			struct timespec64 now;
+			int time_since_stop;
+
+			ktime_get_real_ts64(&now);
+			time_since_stop = now.tv_sec - host->stop_time.tv_sec;
+			if (time_since_stop < 2) {
+				/* Possibly less than one second */
+				time_since_stop = time_since_stop * 1000000 +
+					(now.tv_nsec - host->stop_time.tv_nsec)/1000;
+				if (time_since_stop <
+				    host->delay_after_this_stop)
+					udelay(host->delay_after_this_stop -
+					       time_since_stop);
+			}
+		}
+
+		host->delay_after_this_stop = host->delay_after_stop;
+		if ((cmd->data->flags & MMC_DATA_READ) && !host->use_sbc) {
+			/* See if read crosses one of the hazardous sectors */
+			u32 first_blk, last_blk;
+
+			/* Intentionally include the following sector because
+			   without CMD23/SBC the read may run on. */
+			first_blk = host->mrq->cmd->arg;
+			last_blk = first_blk + cmd->data->blocks;
+
+			if (((last_blk >= (host->sectors - 64)) &&
+			     (first_blk <= (host->sectors - 64))) ||
+			    ((last_blk >= (host->sectors - 32)) &&
+			     (first_blk <= (host->sectors - 32)))) {
+				host->delay_after_this_stop =
+					max(250u, host->delay_after_stop);
+			}
+		}
+
+		if (cmd->data->flags & MMC_DATA_WRITE)
+			sdcmd |= SDCMD_WRITE_CMD;
+		if (cmd->data->flags & MMC_DATA_READ)
+			sdcmd |= SDCMD_READ_CMD;
+	}
+
+	bcm2835_sdhost_write(host, sdcmd | SDCMD_NEW_FLAG, SDCMD);
+
+	return true;
+}
+
+static void bcm2835_sdhost_finish_command(struct bcm2835_host *host,
+					  unsigned long *irq_flags);
+static void bcm2835_sdhost_transfer_complete(struct bcm2835_host *host);
+
+static void bcm2835_sdhost_finish_data(struct bcm2835_host *host)
+{
+	struct mmc_data *data;
+
+	data = host->data;
+	BUG_ON(!data);
+
+	log_event("FDA<", host->mrq, host->cmd);
+	pr_debug("finish_data(error %d, stop %d, sbc %d)\n",
+	       data->error, data->stop ? 1 : 0,
+	       host->mrq->sbc ? 1 : 0);
+
+	host->hcfg &= ~(SDHCFG_DATA_IRPT_EN | SDHCFG_BLOCK_IRPT_EN);
+	bcm2835_sdhost_write(host, host->hcfg, SDHCFG);
+
+	data->bytes_xfered = data->error ? 0 : (data->blksz * data->blocks);
+
+	host->data_complete = 1;
+
+	if (host->cmd) {
+		/*
+		 * Data managed to finish before the
+		 * command completed. Make sure we do
+		 * things in the proper order.
+		 */
+		pr_debug("Finished early - HSTS %x\n",
+			 bcm2835_sdhost_read(host, SDHSTS));
+	}
+	else
+		bcm2835_sdhost_transfer_complete(host);
+	log_event("FDA>", host->mrq, host->cmd);
+}
+
+static void bcm2835_sdhost_transfer_complete(struct bcm2835_host *host)
+{
+	struct mmc_data *data;
+
+	BUG_ON(host->cmd);
+	BUG_ON(!host->data);
+	BUG_ON(!host->data_complete);
+
+	data = host->data;
+	host->data = NULL;
+
+	log_event("TCM<", data, data->error);
+	pr_debug("transfer_complete(error %d, stop %d)\n",
+	       data->error, data->stop ? 1 : 0);
+
+	/*
+	 * Need to send CMD12 if -
+	 * a) open-ended multiblock transfer (no CMD23)
+	 * b) error in multiblock transfer
+	 */
+	if (host->mrq->stop && (data->error || !host->use_sbc)) {
+		if (bcm2835_sdhost_send_command(host, host->mrq->stop)) {
+			/* No busy, so poll for completion */
+			if (!host->use_busy)
+				bcm2835_sdhost_finish_command(host, NULL);
+
+			if (host->delay_after_this_stop)
+				ktime_get_real_ts64(&host->stop_time);
+		}
+	} else {
+		bcm2835_sdhost_wait_transfer_complete(host);
+		tasklet_schedule(&host->finish_tasklet);
+	}
+	log_event("TCM>", data, 0);
+}
+
+/* If irq_flags is valid, the caller is in a thread context and is allowed
+   to sleep */
+static void bcm2835_sdhost_finish_command(struct bcm2835_host *host,
+					  unsigned long *irq_flags)
+{
+	u32 sdcmd;
+	u32 retries;
+#ifdef DEBUG
+	struct timespec64 before, after;
+	int timediff = 0;
+#endif
+
+	log_event("FCM<", host->mrq, host->cmd);
+	pr_debug("finish_command(%x)\n", bcm2835_sdhost_read(host, SDCMD));
+
+	BUG_ON(!host->cmd || !host->mrq);
+
+	/* Poll quickly at first */
+
+	retries = host->cmd_quick_poll_retries;
+	if (!retries) {
+		/* Work out how many polls take 1us by timing 10us */
+		struct timespec64 start, now;
+		int us_diff;
+
+		retries = 1;
+		do {
+			int i;
+
+			retries *= 2;
+
+			ktime_get_real_ts64(&start);
+
+			for (i = 0; i < retries; i++) {
+				cpu_relax();
+				sdcmd = bcm2835_sdhost_read(host, SDCMD);
+			}
+
+			ktime_get_real_ts64(&now);
+			us_diff = (now.tv_sec - start.tv_sec) * 1000000 +
+				(now.tv_nsec - start.tv_nsec)/1000;
+		} while (us_diff < 10);
+
+		host->cmd_quick_poll_retries = ((retries * us_diff + 9)*CMD_DALLY_US)/10 + 1;
+		retries = 1; // We've already waited long enough this time
+	}
+
+	for (sdcmd = bcm2835_sdhost_read(host, SDCMD);
+	     (sdcmd & SDCMD_NEW_FLAG) && retries;
+	     retries--) {
+		cpu_relax();
+		sdcmd = bcm2835_sdhost_read(host, SDCMD);
+	}
+
+	if (!retries) {
+		unsigned long wait_max;
+
+		if (!irq_flags) {
+			/* Schedule the work */
+			log_event("CWWQ", 0, 0);
+			schedule_work(&host->cmd_wait_wq);
+			return;
+		}
+
+		/* Wait max 100 ms */
+		wait_max = jiffies + msecs_to_jiffies(100);
+		while (time_before(jiffies, wait_max)) {
+			spin_unlock_irqrestore(&host->lock, *irq_flags);
+			usleep_range(1, 10);
+			spin_lock_irqsave(&host->lock, *irq_flags);
+			sdcmd = bcm2835_sdhost_read(host, SDCMD);
+			if (!(sdcmd & SDCMD_NEW_FLAG))
+				break;
+		}
+	}
+
+	/* Check for errors */
+	if (sdcmd & SDCMD_NEW_FLAG) {
+		if (host->debug) {
+			pr_err("%s: command %d never completed.\n",
+			       mmc_hostname(host->mmc), host->cmd->opcode);
+			bcm2835_sdhost_dumpregs(host);
+		}
+		host->cmd->error = -EILSEQ;
+		tasklet_schedule(&host->finish_tasklet);
+		return;
+	} else if (sdcmd & SDCMD_FAIL_FLAG) {
+		u32 sdhsts = bcm2835_sdhost_read(host, SDHSTS);
+
+		/* Clear the errors */
+		bcm2835_sdhost_write(host, SDHSTS_ERROR_MASK, SDHSTS);
+
+		if (host->debug)
+			pr_info("%s: error detected - CMD %x, HSTS %03x, EDM %x\n",
+				mmc_hostname(host->mmc), sdcmd, sdhsts,
+				bcm2835_sdhost_read(host, SDEDM));
+
+		if ((sdhsts & SDHSTS_CRC7_ERROR) &&
+		    (host->cmd->opcode == 1)) {
+			if (host->debug)
+				pr_info("%s: ignoring CRC7 error for CMD1\n",
+					mmc_hostname(host->mmc));
+		} else {
+			u32 edm, fsm;
+
+			if (sdhsts & SDHSTS_CMD_TIME_OUT) {
+				if (host->debug)
+					pr_warn("%s: command %d timeout\n",
+					       mmc_hostname(host->mmc),
+					       host->cmd->opcode);
+				host->cmd->error = -ETIMEDOUT;
+			} else {
+				pr_warn("%s: unexpected command %d error\n",
+				       mmc_hostname(host->mmc),
+				       host->cmd->opcode);
+				host->cmd->error = -EILSEQ;
+			}
+
+			edm = readl(host->ioaddr + SDEDM);
+			fsm = edm & SDEDM_FSM_MASK;
+			if (fsm == SDEDM_FSM_READWAIT ||
+			    fsm == SDEDM_FSM_WRITESTART1)
+				writel(edm | SDEDM_FORCE_DATA_MODE,
+				       host->ioaddr + SDEDM);
+			tasklet_schedule(&host->finish_tasklet);
+			return;
+		}
+	}
+
+	if (host->cmd->flags & MMC_RSP_PRESENT) {
+		if (host->cmd->flags & MMC_RSP_136) {
+			int i;
+			for (i = 0; i < 4; i++)
+				host->cmd->resp[3 - i] = bcm2835_sdhost_read(host, SDRSP0 + i*4);
+			pr_debug("%s: finish_command %08x %08x %08x %08x\n",
+				 mmc_hostname(host->mmc),
+				 host->cmd->resp[0], host->cmd->resp[1], host->cmd->resp[2], host->cmd->resp[3]);
+			log_event("RSP ", host->cmd->resp[0], host->cmd->resp[1]);
+		} else {
+			host->cmd->resp[0] = bcm2835_sdhost_read(host, SDRSP0);
+			pr_debug("%s: finish_command %08x\n",
+				 mmc_hostname(host->mmc),
+				 host->cmd->resp[0]);
+			log_event("RSP ", host->cmd->resp[0], 0);
+		}
+	}
+
+	if (host->cmd == host->mrq->sbc) {
+		/* Finished CMD23, now send actual command. */
+		host->cmd = NULL;
+		if (bcm2835_sdhost_send_command(host, host->mrq->cmd)) {
+			if (host->data && host->dma_desc)
+				/* DMA transfer starts now, PIO starts after irq */
+				bcm2835_sdhost_start_dma(host);
+
+			if (!host->use_busy)
+				bcm2835_sdhost_finish_command(host, NULL);
+		}
+	} else if (host->cmd == host->mrq->stop) {
+		/* Finished CMD12 */
+		tasklet_schedule(&host->finish_tasklet);
+	} else {
+		/* Processed actual command. */
+		host->cmd = NULL;
+		if (!host->data)
+			tasklet_schedule(&host->finish_tasklet);
+		else if (host->data_complete)
+			bcm2835_sdhost_transfer_complete(host);
+	}
+	log_event("FCM>", host->mrq, host->cmd);
+}
+
+static void bcm2835_sdhost_timeout(struct timer_list *t)
+{
+	struct bcm2835_host *host = from_timer(host, t, timer);
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->lock, flags);
+	log_event("TIM<", 0, 0);
+
+	if (host->mrq) {
+		pr_err("%s: timeout waiting for hardware interrupt.\n",
+			mmc_hostname(host->mmc));
+		log_dump();
+		bcm2835_sdhost_dumpregs(host);
+
+		if (host->data) {
+			host->data->error = -ETIMEDOUT;
+			bcm2835_sdhost_finish_data(host);
+		} else {
+			if (host->cmd)
+				host->cmd->error = -ETIMEDOUT;
+			else
+				host->mrq->cmd->error = -ETIMEDOUT;
+
+			pr_debug("timeout_timer tasklet_schedule\n");
+			tasklet_schedule(&host->finish_tasklet);
+		}
+	}
+
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void bcm2835_sdhost_busy_irq(struct bcm2835_host *host, u32 intmask)
+{
+	log_event("IRQB", host->cmd, intmask);
+	if (!host->cmd) {
+		pr_err("%s: got command busy interrupt 0x%08x even "
+			"though no command operation was in progress.\n",
+			mmc_hostname(host->mmc), (unsigned)intmask);
+		bcm2835_sdhost_dumpregs(host);
+		return;
+	}
+
+	if (!host->use_busy) {
+		pr_err("%s: got command busy interrupt 0x%08x even "
+			"though not expecting one.\n",
+			mmc_hostname(host->mmc), (unsigned)intmask);
+		bcm2835_sdhost_dumpregs(host);
+		return;
+	}
+	host->use_busy = 0;
+
+	if (intmask & SDHSTS_ERROR_MASK)
+	{
+		pr_err("sdhost_busy_irq: intmask %x, data %p\n", intmask, host->mrq->data);
+		if (intmask & SDHSTS_CRC7_ERROR)
+			host->cmd->error = -EILSEQ;
+		else if (intmask & (SDHSTS_CRC16_ERROR |
+				    SDHSTS_FIFO_ERROR)) {
+			if (host->mrq->data)
+				host->mrq->data->error = -EILSEQ;
+			else
+				host->cmd->error = -EILSEQ;
+		} else if (intmask & SDHSTS_REW_TIME_OUT) {
+			if (host->mrq->data)
+				host->mrq->data->error = -ETIMEDOUT;
+			else
+				host->cmd->error = -ETIMEDOUT;
+		} else if (intmask & SDHSTS_CMD_TIME_OUT)
+			host->cmd->error = -ETIMEDOUT;
+
+		if (host->debug) {
+			log_dump();
+			bcm2835_sdhost_dumpregs(host);
+		}
+	}
+	else
+		bcm2835_sdhost_finish_command(host, NULL);
+}
+
+static void bcm2835_sdhost_data_irq(struct bcm2835_host *host, u32 intmask)
+{
+	/* There are no dedicated data/space available interrupt
+	   status bits, so it is necessary to use the single shared
+	   data/space available FIFO status bits. It is therefore not
+	   an error to get here when there is no data transfer in
+	   progress. */
+	log_event("IRQD", host->data, intmask);
+	if (!host->data)
+		return;
+
+	if (intmask & (SDHSTS_CRC16_ERROR |
+		       SDHSTS_FIFO_ERROR |
+		       SDHSTS_REW_TIME_OUT)) {
+		if (intmask & (SDHSTS_CRC16_ERROR |
+			       SDHSTS_FIFO_ERROR))
+			host->data->error = -EILSEQ;
+		else
+			host->data->error = -ETIMEDOUT;
+
+		if (host->debug) {
+			log_dump();
+			bcm2835_sdhost_dumpregs(host);
+		}
+	}
+
+	if (host->data->error) {
+		bcm2835_sdhost_finish_data(host);
+	} else if (host->data->flags & MMC_DATA_WRITE) {
+		/* Use the block interrupt for writes after the first block */
+		host->hcfg &= ~(SDHCFG_DATA_IRPT_EN);
+		host->hcfg |= SDHCFG_BLOCK_IRPT_EN;
+		bcm2835_sdhost_write(host, host->hcfg, SDHCFG);
+		bcm2835_sdhost_transfer_pio(host);
+	} else {
+		bcm2835_sdhost_transfer_pio(host);
+		host->blocks--;
+		if ((host->blocks == 0) || host->data->error)
+			bcm2835_sdhost_finish_data(host);
+	}
+}
+
+static void bcm2835_sdhost_block_irq(struct bcm2835_host *host, u32 intmask)
+{
+	log_event("IRQK", host->data, intmask);
+	if (!host->data) {
+		pr_err("%s: got block interrupt 0x%08x even "
+			"though no data operation was in progress.\n",
+			mmc_hostname(host->mmc), (unsigned)intmask);
+		bcm2835_sdhost_dumpregs(host);
+		return;
+	}
+
+	if (intmask & (SDHSTS_CRC16_ERROR |
+		       SDHSTS_FIFO_ERROR |
+		       SDHSTS_REW_TIME_OUT)) {
+		if (intmask & (SDHSTS_CRC16_ERROR |
+			       SDHSTS_FIFO_ERROR))
+			host->data->error = -EILSEQ;
+		else
+			host->data->error = -ETIMEDOUT;
+
+		if (host->debug) {
+			log_dump();
+			bcm2835_sdhost_dumpregs(host);
+		}
+	}
+
+	if (!host->dma_desc) {
+		BUG_ON(!host->blocks);
+		if (host->data->error || (--host->blocks == 0)) {
+			bcm2835_sdhost_finish_data(host);
+		} else {
+			bcm2835_sdhost_transfer_pio(host);
+		}
+	} else if (host->data->flags & MMC_DATA_WRITE) {
+		bcm2835_sdhost_finish_data(host);
+	}
+}
+
+static irqreturn_t bcm2835_sdhost_irq(int irq, void *dev_id)
+{
+	irqreturn_t result = IRQ_NONE;
+	struct bcm2835_host *host = dev_id;
+	u32 intmask;
+
+	spin_lock(&host->lock);
+
+	intmask = bcm2835_sdhost_read(host, SDHSTS);
+	log_event("IRQ<", intmask, 0);
+
+	bcm2835_sdhost_write(host,
+			     SDHSTS_BUSY_IRPT |
+			     SDHSTS_BLOCK_IRPT |
+			     SDHSTS_SDIO_IRPT |
+			     SDHSTS_DATA_FLAG,
+			     SDHSTS);
+
+	if (intmask & SDHSTS_BLOCK_IRPT) {
+		bcm2835_sdhost_block_irq(host, intmask);
+		result = IRQ_HANDLED;
+	}
+
+	if (intmask & SDHSTS_BUSY_IRPT) {
+		bcm2835_sdhost_busy_irq(host, intmask);
+		result = IRQ_HANDLED;
+	}
+
+	/* There is no true data interrupt status bit, so it is
+	   necessary to qualify the data flag with the interrupt
+	   enable bit */
+	if ((intmask & SDHSTS_DATA_FLAG) &&
+	    (host->hcfg & SDHCFG_DATA_IRPT_EN)) {
+		bcm2835_sdhost_data_irq(host, intmask);
+		result = IRQ_HANDLED;
+	}
+
+	log_event("IRQ>", bcm2835_sdhost_read(host, SDHSTS), 0);
+	spin_unlock(&host->lock);
+
+	return result;
+}
+
+static void bcm2835_sdhost_set_clock(struct bcm2835_host *host, unsigned int clock)
+{
+	int div = 0; /* Initialized for compiler warning */
+	unsigned int input_clock = clock;
+	unsigned long flags;
+
+	if (host->debug)
+		pr_info("%s: set_clock(%d)\n", mmc_hostname(host->mmc), clock);
+
+	if (host->overclock_50 && (clock >= 50*MHZ))
+		clock = host->overclock_50 * MHZ + (MHZ - 1);
+
+	/* The SDCDIV register has 11 bits, and holds (div - 2).
+	   But in data mode the max is 50MHz wihout a minimum, and only the
+	   bottom 3 bits are used. Since the switch over is automatic (unless
+	   we have marked the card as slow...), chosen values have to make
+	   sense in both modes.
+	   Ident mode must be 100-400KHz, so can range check the requested
+	   clock. CMD15 must be used to return to data mode, so this can be
+	   monitored.
+
+	   clock 250MHz -> 0->125MHz, 1->83.3MHz, 2->62.5MHz, 3->50.0MHz
+                           4->41.7MHz, 5->35.7MHz, 6->31.3MHz, 7->27.8MHz
+
+			 623->400KHz/27.8MHz
+			 reset value (507)->491159/50MHz
+
+	   BUT, the 3-bit clock divisor in data mode is too small if the
+	   core clock is higher than 250MHz, so instead use the SLOW_CARD
+	   configuration bit to force the use of the ident clock divisor
+	   at all times.
+	*/
+
+	host->mmc->actual_clock = 0;
+
+	if (host->firmware_sets_cdiv) {
+		u32 msg[3] = { clock, 0, 0 };
+
+		rpi_firmware_property(host->fw,
+				      RPI_FIRMWARE_SET_SDHOST_CLOCK,
+				      &msg, sizeof(msg));
+
+		clock = max(msg[1], msg[2]);
+		spin_lock_irqsave(&host->lock, flags);
+	} else {
+		spin_lock_irqsave(&host->lock, flags);
+		if (clock < 100000) {
+			/* Can't stop the clock, but make it as slow as
+			 * possible to show willing
+			 */
+			host->cdiv = SDCDIV_MAX_CDIV;
+			bcm2835_sdhost_write(host, host->cdiv, SDCDIV);
+			spin_unlock_irqrestore(&host->lock, flags);
+			return;
+		}
+
+		div = host->max_clk / clock;
+		if (div < 2)
+			div = 2;
+		if ((host->max_clk / div) > clock)
+			div++;
+		div -= 2;
+
+		if (div > SDCDIV_MAX_CDIV)
+			div = SDCDIV_MAX_CDIV;
+
+		clock = host->max_clk / (div + 2);
+
+		host->cdiv = div;
+		bcm2835_sdhost_write(host, host->cdiv, SDCDIV);
+
+		if (host->debug)
+			pr_info("%s: clock=%d -> max_clk=%d, cdiv=%x "
+				"(actual clock %d)\n",
+				mmc_hostname(host->mmc), input_clock,
+				host->max_clk, host->cdiv,
+				clock);
+	}
+
+	/* Calibrate some delays */
+
+	host->ns_per_fifo_word = (1000000000/clock) *
+		((host->mmc->caps & MMC_CAP_4_BIT_DATA) ? 8 : 32);
+
+	if (input_clock == 50 * MHZ) {
+		if (clock > input_clock) {
+			/* Save the closest value, to make it easier
+			   to reduce in the event of error */
+			host->overclock_50 = (clock/MHZ);
+
+			if (clock != host->overclock) {
+				pr_info("%s: overclocking to %dHz\n",
+					mmc_hostname(host->mmc), clock);
+				host->overclock = clock;
+			}
+		} else if (host->overclock) {
+			host->overclock = 0;
+			if (clock == 50 * MHZ)
+				pr_warn("%s: cancelling overclock\n",
+					mmc_hostname(host->mmc));
+		}
+	} else if (input_clock == 0) {
+		/* Reset the preferred overclock when the clock is stopped.
+		 * This always happens during initialisation. */
+		host->overclock_50 = host->user_overclock_50;
+		host->overclock = 0;
+	}
+
+	/* Set the timeout to 500ms */
+	bcm2835_sdhost_write(host, clock/2, SDTOUT);
+
+	host->mmc->actual_clock = clock;
+	host->clock = input_clock;
+	host->reset_clock = 0;
+
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void bcm2835_sdhost_request(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct bcm2835_host *host;
+	unsigned long flags;
+	u32 edm, fsm;
+
+	host = mmc_priv(mmc);
+
+	if (host->debug) {
+		struct mmc_command *cmd = mrq->cmd;
+		BUG_ON(!cmd);
+		if (cmd->data)
+			pr_info("%s: cmd %d 0x%x (flags 0x%x) - %s %d*%d\n",
+				mmc_hostname(mmc),
+				cmd->opcode, cmd->arg, cmd->flags,
+				(cmd->data->flags & MMC_DATA_READ) ?
+				"read" : "write", cmd->data->blocks,
+				cmd->data->blksz);
+		else
+			pr_info("%s: cmd %d 0x%x (flags 0x%x)\n",
+				mmc_hostname(mmc),
+				cmd->opcode, cmd->arg, cmd->flags);
+	}
+
+	/* Reset the error statuses in case this is a retry */
+	if (mrq->sbc)
+		mrq->sbc->error = 0;
+	if (mrq->cmd)
+		mrq->cmd->error = 0;
+	if (mrq->data)
+		mrq->data->error = 0;
+	if (mrq->stop)
+		mrq->stop->error = 0;
+
+	if (mrq->data && !is_power_of_2(mrq->data->blksz)) {
+		pr_err("%s: unsupported block size (%d bytes)\n",
+		       mmc_hostname(mmc), mrq->data->blksz);
+		mrq->cmd->error = -EINVAL;
+		mmc_request_done(mmc, mrq);
+		return;
+	}
+
+	if (host->use_dma && mrq->data &&
+	    (mrq->data->blocks > host->pio_limit))
+		bcm2835_sdhost_prepare_dma(host, mrq->data);
+
+	if (host->reset_clock)
+	    bcm2835_sdhost_set_clock(host, host->clock);
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	WARN_ON(host->mrq != NULL);
+	host->mrq = mrq;
+
+	edm = bcm2835_sdhost_read(host, SDEDM);
+	fsm = edm & SDEDM_FSM_MASK;
+
+	log_event("REQ<", mrq, edm);
+	if ((fsm != SDEDM_FSM_IDENTMODE) &&
+	    (fsm != SDEDM_FSM_DATAMODE)) {
+		log_event("REQ!", mrq, edm);
+		if (host->debug) {
+			pr_warn("%s: previous command (%d) not complete (EDM %x)\n",
+			       mmc_hostname(host->mmc),
+			       bcm2835_sdhost_read(host, SDCMD) & SDCMD_CMD_MASK,
+			       edm);
+			log_dump();
+			bcm2835_sdhost_dumpregs(host);
+		}
+		mrq->cmd->error = -EILSEQ;
+		tasklet_schedule(&host->finish_tasklet);
+		spin_unlock_irqrestore(&host->lock, flags);
+		return;
+	}
+
+	host->use_sbc = !!mrq->sbc &&
+		(host->mrq->data->flags & USE_CMD23_FLAGS);
+	if (host->use_sbc) {
+		if (bcm2835_sdhost_send_command(host, mrq->sbc)) {
+			if (!host->use_busy)
+				bcm2835_sdhost_finish_command(host, &flags);
+		}
+	} else if (bcm2835_sdhost_send_command(host, mrq->cmd)) {
+		if (host->data && host->dma_desc)
+			/* DMA transfer starts now, PIO starts after irq */
+			bcm2835_sdhost_start_dma(host);
+
+		if (!host->use_busy)
+			bcm2835_sdhost_finish_command(host, &flags);
+	}
+
+	log_event("CMD ", mrq->cmd->opcode,
+		   mrq->data ? (u32)mrq->data->blksz : 0);
+
+	log_event("REQ>", mrq, 0);
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void bcm2835_sdhost_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+
+	struct bcm2835_host *host = mmc_priv(mmc);
+	unsigned long flags;
+
+	if (host->debug)
+		pr_info("%s: ios clock %d, pwr %d, bus_width %d, "
+			"timing %d, vdd %d, drv_type %d\n",
+			mmc_hostname(mmc),
+			ios->clock, ios->power_mode, ios->bus_width,
+			ios->timing, ios->signal_voltage, ios->drv_type);
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	log_event("IOS<", ios->clock, 0);
+
+	/* set bus width */
+	host->hcfg &= ~SDHCFG_WIDE_EXT_BUS;
+	if (ios->bus_width == MMC_BUS_WIDTH_4)
+		host->hcfg |= SDHCFG_WIDE_EXT_BUS;
+
+	host->hcfg |= SDHCFG_WIDE_INT_BUS;
+
+	/* Disable clever clock switching, to cope with fast core clocks */
+	host->hcfg |= SDHCFG_SLOW_CARD;
+
+	bcm2835_sdhost_write(host, host->hcfg, SDHCFG);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+
+	if (!ios->clock || ios->clock != host->clock)
+		bcm2835_sdhost_set_clock(host, ios->clock);
+}
+
+static struct mmc_host_ops bcm2835_sdhost_ops = {
+	.request = bcm2835_sdhost_request,
+	.set_ios = bcm2835_sdhost_set_ios,
+// todo:fix	.hw_reset = bcm2835_sdhost_reset,
+};
+
+static void bcm2835_sdhost_cmd_wait_work(struct work_struct *work)
+{
+	struct bcm2835_host *host;
+	unsigned long flags;
+
+	host = container_of(work, struct bcm2835_host, cmd_wait_wq);
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	log_event("CWK<", host->cmd, host->mrq);
+
+	/*
+	 * If this tasklet gets rescheduled while running, it will
+	 * be run again afterwards but without any active request.
+	 */
+	if (!host->mrq) {
+		spin_unlock_irqrestore(&host->lock, flags);
+		return;
+	}
+
+	bcm2835_sdhost_finish_command(host, &flags);
+
+	log_event("CWK>", host->cmd, 0);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void bcm2835_sdhost_tasklet_finish(unsigned long param)
+{
+	struct bcm2835_host *host;
+	unsigned long flags;
+	struct mmc_request *mrq;
+	struct dma_chan *terminate_chan = NULL;
+
+	host = (struct bcm2835_host *)param;
+
+	spin_lock_irqsave(&host->lock, flags);
+
+	log_event("TSK<", host->mrq, 0);
+	/*
+	 * If this tasklet gets rescheduled while running, it will
+	 * be run again afterwards but without any active request.
+	 */
+	if (!host->mrq) {
+		spin_unlock_irqrestore(&host->lock, flags);
+		return;
+	}
+
+	del_timer(&host->timer);
+
+	mrq = host->mrq;
+
+	/* Drop the overclock after any data corruption, or after any
+	 * error while overclocked. Ignore errors for status commands,
+	 * as they are likely when a card is ejected. */
+	if (host->overclock) {
+		if ((mrq->cmd && mrq->cmd->error &&
+		     (mrq->cmd->opcode != MMC_SEND_STATUS)) ||
+		    (mrq->data && mrq->data->error) ||
+		    (mrq->stop && mrq->stop->error) ||
+		    (mrq->sbc && mrq->sbc->error)) {
+			host->overclock_50--;
+			pr_warn("%s: reducing overclock due to errors\n",
+				mmc_hostname(host->mmc));
+			host->reset_clock = 1;
+			mrq->cmd->error = -ETIMEDOUT;
+			mrq->cmd->retries = 1;
+		}
+	}
+
+	host->mrq = NULL;
+	host->cmd = NULL;
+	host->data = NULL;
+
+	host->dma_desc = NULL;
+	terminate_chan = host->dma_chan;
+	host->dma_chan = NULL;
+
+	spin_unlock_irqrestore(&host->lock, flags);
+
+	if (terminate_chan)
+	{
+		int err = dmaengine_terminate_all(terminate_chan);
+		if (err)
+			pr_err("%s: failed to terminate DMA (%d)\n",
+			       mmc_hostname(host->mmc), err);
+	}
+
+	/* The SDHOST block doesn't report any errors for a disconnected
+	   interface. All cards and SDIO devices should report some supported
+	   voltage range, so a zero response to SEND_OP_COND, IO_SEND_OP_COND
+	   or APP_SEND_OP_COND can be treated as an error. */
+	if (((mrq->cmd->opcode == MMC_SEND_OP_COND) ||
+	     (mrq->cmd->opcode == SD_IO_SEND_OP_COND) ||
+	     (mrq->cmd->opcode == SD_APP_OP_COND)) &&
+	    (mrq->cmd->error == 0) &&
+	    (mrq->cmd->resp[0] == 0)) {
+		mrq->cmd->error = -ETIMEDOUT;
+		if (host->debug)
+			pr_info("%s: faking timeout due to zero OCR\n",
+				mmc_hostname(host->mmc));
+	}
+
+	if (!mmc_hsq_finalize_request(host->mmc, mrq))
+		mmc_request_done(host->mmc, mrq);
+	log_event("TSK>", mrq, 0);
+}
+
+static int bcm2835_sdhost_add_host(struct platform_device *pdev)
+{
+	struct bcm2835_host *host = platform_get_drvdata(pdev);
+	struct mmc_host *mmc;
+	struct mmc_hsq *hsq;
+	struct dma_slave_config cfg;
+	char pio_limit_string[20];
+	int ret;
+
+	mmc = host->mmc;
+
+	if (!mmc->f_max || mmc->f_max > host->max_clk)
+		mmc->f_max = host->max_clk;
+	mmc->f_min = host->max_clk / SDCDIV_MAX_CDIV;
+
+	mmc->max_busy_timeout =  (~(unsigned int)0)/(mmc->f_max/1000);
+
+	pr_debug("f_max %d, f_min %d, max_busy_timeout %d\n",
+		 mmc->f_max, mmc->f_min, mmc->max_busy_timeout);
+
+	/* host controller capabilities */
+	mmc->caps |=
+		MMC_CAP_SD_HIGHSPEED | MMC_CAP_MMC_HIGHSPEED |
+		MMC_CAP_NEEDS_POLL | MMC_CAP_HW_RESET |
+		((ALLOW_CMD23_READ|ALLOW_CMD23_WRITE) * MMC_CAP_CMD23);
+
+	spin_lock_init(&host->lock);
+
+	if (host->allow_dma) {
+		if (IS_ERR_OR_NULL(host->dma_chan_rxtx)) {
+			pr_err("%s: unable to initialise DMA channel. "
+			       "Falling back to PIO\n",
+			       mmc_hostname(mmc));
+			host->use_dma = false;
+		} else {
+			cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+			cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+			/* Validate the slave configurations */
+
+			cfg.direction = DMA_MEM_TO_DEV;
+			cfg.src_addr = 0;
+			cfg.dst_addr = host->bus_addr + SDDATA;
+
+			ret = dmaengine_slave_config(host->dma_chan_rxtx, &cfg);
+
+			if (ret == 0) {
+				host->dma_cfg_tx = cfg;
+
+				cfg.direction = DMA_DEV_TO_MEM;
+				cfg.src_addr = host->bus_addr + SDDATA;
+				cfg.dst_addr = 0;
+
+				ret = dmaengine_slave_config(host->dma_chan_rxtx, &cfg);
+			}
+
+			if (ret == 0) {
+				host->dma_cfg_rx = cfg;
+
+				host->use_dma = true;
+			} else {
+				pr_err("%s: unable to configure DMA channel. "
+				       "Falling back to PIO\n",
+				       mmc_hostname(mmc));
+				dma_release_channel(host->dma_chan_rxtx);
+				host->dma_chan_rxtx = NULL;
+				host->use_dma = false;
+			}
+		}
+	} else {
+		host->use_dma = false;
+	}
+
+	mmc->max_segs = 128;
+	mmc->max_req_size = min_t(size_t, 524288, dma_max_mapping_size(&pdev->dev));
+	mmc->max_seg_size = mmc->max_req_size;
+	mmc->max_blk_size = 512;
+	mmc->max_blk_count =  65535;
+
+	/* report supported voltage ranges */
+	mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+
+	tasklet_init(&host->finish_tasklet,
+		bcm2835_sdhost_tasklet_finish, (unsigned long)host);
+
+	INIT_WORK(&host->cmd_wait_wq, bcm2835_sdhost_cmd_wait_work);
+
+	timer_setup(&host->timer, bcm2835_sdhost_timeout, 0);
+
+	bcm2835_sdhost_init(host, 0);
+
+	ret = request_irq(host->irq, bcm2835_sdhost_irq, 0 /*IRQF_SHARED*/,
+				  mmc_hostname(mmc), host);
+	if (ret) {
+		pr_err("%s: failed to request IRQ %d: %d\n",
+		       mmc_hostname(mmc), host->irq, ret);
+		goto untasklet;
+	}
+
+	hsq = devm_kzalloc(&pdev->dev, sizeof(*hsq), GFP_KERNEL);
+	if (!hsq) {
+		ret = -ENOMEM;
+		goto free_irq;
+	}
+
+	ret = mmc_hsq_init(hsq, host->mmc);
+	if (ret)
+		goto free_irq;
+
+	mmc_add_host(mmc);
+
+	pio_limit_string[0] = '\0';
+	if (host->use_dma && (host->pio_limit > 0))
+		sprintf(pio_limit_string, " (>%d)", host->pio_limit);
+	pr_info("%s: %s loaded - DMA %s%s\n",
+		mmc_hostname(mmc), DRIVER_NAME,
+		host->use_dma ? "enabled" : "disabled",
+		pio_limit_string);
+
+	return 0;
+
+free_irq:
+	free_irq(host->irq, host);
+
+untasklet:
+	tasklet_kill(&host->finish_tasklet);
+
+	return ret;
+}
+
+static int bcm2835_sdhost_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct clk *clk;
+	struct resource *iomem;
+	struct bcm2835_host *host;
+	struct mmc_host *mmc;
+	u32 msg[3];
+	int ret;
+
+	pr_debug("bcm2835_sdhost_probe\n");
+	mmc = mmc_alloc_host(sizeof(*host), dev);
+	if (!mmc)
+		return -ENOMEM;
+
+	mmc->ops = &bcm2835_sdhost_ops;
+	host = mmc_priv(mmc);
+	host->mmc = mmc;
+	host->pio_timeout = msecs_to_jiffies(500);
+	host->pio_limit = 1;
+	host->max_delay = 1; /* Warn if over 1ms */
+	host->allow_dma = 1;
+	spin_lock_init(&host->lock);
+
+	host->ioaddr = devm_platform_get_and_ioremap_resource(pdev, 0, &iomem);
+	if (IS_ERR(host->ioaddr)) {
+		ret = PTR_ERR(host->ioaddr);
+		goto err;
+	}
+
+	host->bus_addr = iomem->start;
+
+	if (node) {
+		/* Read any custom properties */
+		of_property_read_u32(node,
+				     "brcm,delay-after-stop",
+				     &host->delay_after_stop);
+		of_property_read_u32(node,
+				     "brcm,overclock-50",
+				     &host->user_overclock_50);
+		of_property_read_u32(node,
+				     "brcm,pio-limit",
+				     &host->pio_limit);
+		host->allow_dma =
+			!of_property_read_bool(node, "brcm,force-pio");
+		host->debug = of_property_read_bool(node, "brcm,debug");
+	}
+
+	host->dma_chan = NULL;
+	host->dma_desc = NULL;
+
+	/* Formally recognise the other way of disabling DMA */
+	if (host->pio_limit == 0x7fffffff)
+		host->allow_dma = false;
+
+	if (host->allow_dma) {
+		if (node) {
+			host->dma_chan_rxtx =
+				dma_request_slave_channel(dev, "rx-tx");
+			if (!host->dma_chan_rxtx)
+				host->dma_chan_rxtx =
+					dma_request_slave_channel(dev, "tx");
+			if (!host->dma_chan_rxtx)
+				host->dma_chan_rxtx =
+					dma_request_slave_channel(dev, "rx");
+		} else {
+			dma_cap_mask_t mask;
+
+			dma_cap_zero(mask);
+			/* we don't care about the channel, any would work */
+			dma_cap_set(DMA_SLAVE, mask);
+			host->dma_chan_rxtx =
+				dma_request_channel(mask, NULL, NULL);
+		}
+	}
+
+	clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(clk)) {
+		ret = PTR_ERR(clk);
+		if (ret == -EPROBE_DEFER)
+			dev_info(dev, "could not get clk, deferring probe\n");
+		else
+			dev_err(dev, "could not get clk\n");
+		goto err;
+	}
+
+	host->fw = rpi_firmware_get(
+		of_parse_phandle(dev->of_node, "firmware", 0));
+	if (!host->fw) {
+		ret = -EPROBE_DEFER;
+		goto err;
+	}
+
+	host->max_clk = clk_get_rate(clk);
+
+	host->irq = platform_get_irq(pdev, 0);
+	if (host->irq <= 0) {
+		dev_err(dev, "get IRQ failed\n");
+		ret = -EINVAL;
+		goto err;
+	}
+
+	pr_debug(" - max_clk %lx, irq %d\n",
+		 (unsigned long)host->max_clk,
+		 (int)host->irq);
+
+	log_init(dev);
+
+	if (node)
+		mmc_of_parse(mmc);
+	else
+		mmc->caps |= MMC_CAP_4_BIT_DATA;
+
+	msg[0] = 0;
+	msg[1] = ~0;
+	msg[2] = ~0;
+
+	rpi_firmware_property(host->fw,
+			      RPI_FIRMWARE_SET_SDHOST_CLOCK,
+			      &msg, sizeof(msg));
+
+	host->firmware_sets_cdiv = (msg[1] != ~0);
+
+	platform_set_drvdata(pdev, host);
+
+	ret = bcm2835_sdhost_add_host(pdev);
+	if (ret)
+		goto err;
+
+	pr_debug("bcm2835_sdhost_probe -> OK\n");
+
+	return 0;
+
+err:
+	pr_debug("bcm2835_sdhost_probe -> err %d\n", ret);
+	if (host->fw)
+		rpi_firmware_put(host->fw);
+	if (host->dma_chan_rxtx)
+		dma_release_channel(host->dma_chan_rxtx);
+	mmc_free_host(mmc);
+
+	return ret;
+}
+
+static void bcm2835_sdhost_remove(struct platform_device *pdev)
+{
+	struct bcm2835_host *host = platform_get_drvdata(pdev);
+
+	pr_debug("bcm2835_sdhost_remove\n");
+
+	mmc_remove_host(host->mmc);
+
+	bcm2835_sdhost_set_power(host, false);
+
+	free_irq(host->irq, host);
+
+	del_timer_sync(&host->timer);
+
+	tasklet_kill(&host->finish_tasklet);
+	rpi_firmware_put(host->fw);
+	if (host->dma_chan_rxtx)
+		dma_release_channel(host->dma_chan_rxtx);
+	mmc_free_host(host->mmc);
+	platform_set_drvdata(pdev, NULL);
+
+	pr_debug("bcm2835_sdhost_remove - OK\n");
+}
+
+static const struct of_device_id bcm2835_sdhost_match[] = {
+	{ .compatible = "brcm,bcm2835-sdhost" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, bcm2835_sdhost_match);
+
+static struct platform_driver bcm2835_sdhost_driver = {
+	.probe      = bcm2835_sdhost_probe,
+	.remove     = bcm2835_sdhost_remove,
+	.driver     = {
+		.name		= DRIVER_NAME,
+		.owner		= THIS_MODULE,
+		.of_match_table	= bcm2835_sdhost_match,
+	},
+};
+module_platform_driver(bcm2835_sdhost_driver);
+
+MODULE_ALIAS("platform:sdhost-bcm2835");
+MODULE_DESCRIPTION("BCM2835 SDHost driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Phil Elwell");
diff --git a/drivers/mmc/host/bcm2835.c b/drivers/mmc/host/bcm2835.c
index f923447ed2ce..a6ce4fd61c16 100644
--- a/drivers/mmc/host/bcm2835.c
+++ b/drivers/mmc/host/bcm2835.c
@@ -38,7 +38,6 @@
 #include <linux/io.h>
 #include <linux/iopoll.h>
 #include <linux/module.h>
-#include <linux/of_address.h>
 #include <linux/of_irq.h>
 #include <linux/platform_device.h>
 #include <linux/scatterlist.h>
@@ -49,6 +48,8 @@
 #include <linux/mmc/mmc.h>
 #include <linux/mmc/sd.h>
 
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
 #define SDCMD  0x00 /* Command to SD card              - 16 R/W */
 #define SDARG  0x04 /* Argument to SD card             - 32 R/W */
 #define SDTOUT 0x08 /* Start value for timeout counter - 32 R/W */
@@ -187,6 +188,14 @@ struct bcm2835_host {
 	struct page		*drain_page;
 	u32			drain_offset;
 	bool			use_dma;
+
+	/* Downstream additions */
+	struct rpi_firmware	*fw;
+	u32			pio_limit;	/* Maximum block count for PIO (0 = always DMA) */
+	u32			user_overclock_50; /* User's preferred overclock frequency */
+	u32			overclock_50;	/* Freq to use when 50MHz is requested (MHz) */
+	u32			overclock;	/* Current frequency if overclocked, else zero */
+	bool			reset_clock:1;	/* Reset the clock for the next request */
 };
 
 static void bcm2835_dumpcmd(struct bcm2835_host *host, struct mmc_command *cmd,
@@ -241,8 +250,11 @@ static void bcm2835_dumpregs(struct bcm2835_host *host)
 
 static void bcm2835_reset_internal(struct bcm2835_host *host)
 {
+	u32 cdiv = host->cdiv;
 	u32 temp;
 
+	if (!cdiv)
+		cdiv = readl(host->ioaddr + SDCDIV);
 	writel(SDVDD_POWER_OFF, host->ioaddr + SDVDD);
 	writel(0, host->ioaddr + SDCMD);
 	writel(0, host->ioaddr + SDARG);
@@ -263,9 +275,8 @@ static void bcm2835_reset_internal(struct bcm2835_host *host)
 	msleep(20);
 	writel(SDVDD_POWER_ON, host->ioaddr + SDVDD);
 	msleep(20);
-	host->clock = 0;
 	writel(host->hcfg, host->ioaddr + SDHCFG);
-	writel(host->cdiv, host->ioaddr + SDCDIV);
+	writel(cdiv, host->ioaddr + SDCDIV);
 }
 
 static void bcm2835_reset(struct mmc_host *mmc)
@@ -597,6 +608,25 @@ static void bcm2835_finish_request(struct bcm2835_host *host)
 
 	mrq = host->mrq;
 
+	/* Drop the overclock after any data corruption, or after any
+	 * error while overclocked. Ignore errors for status commands,
+	 * as they are likely when a card is ejected.
+	 */
+	if (host->overclock) {
+		if ((mrq->cmd && mrq->cmd->error &&
+		     (mrq->cmd->opcode != MMC_SEND_STATUS)) ||
+		    (mrq->data && mrq->data->error) ||
+		    (mrq->stop && mrq->stop->error) ||
+		    (mrq->sbc && mrq->sbc->error)) {
+			host->overclock_50--;
+			dev_warn(&host->pdev->dev,
+				 "reducing overclock due to errors\n");
+			host->reset_clock = 1;
+			mrq->cmd->error = -ETIMEDOUT;
+			mrq->cmd->retries = 1;
+		}
+	}
+
 	host->mrq = NULL;
 	host->cmd = NULL;
 	host->data = NULL;
@@ -1093,8 +1123,13 @@ static void bcm2835_dma_complete_work(struct work_struct *work)
 static void bcm2835_set_clock(struct bcm2835_host *host, unsigned int clock)
 {
 	struct mmc_host *mmc = mmc_from_priv(host);
+	const unsigned int input_clock = clock;
+	const unsigned int MHZ = 1000000;
 	int div;
 
+	if (host->overclock_50 && (clock >= 50*MHZ))
+		clock = host->overclock_50 * MHZ + (MHZ - 1);
+
 	/* The SDCDIV register has 11 bits, and holds (div - 2).  But
 	 * in data mode the max is 50MHz wihout a minimum, and only
 	 * the bottom 3 bits are used. Since the switch over is
@@ -1116,38 +1151,78 @@ static void bcm2835_set_clock(struct bcm2835_host *host, unsigned int clock)
 	 * clock divisor at all times.
 	 */
 
-	if (clock < 100000) {
-		/* Can't stop the clock, but make it as slow as possible
-		 * to show willing
-		 */
-		host->cdiv = SDCDIV_MAX_CDIV;
-		writel(host->cdiv, host->ioaddr + SDCDIV);
-		return;
-	}
+	if (host->fw) {
+		u32 msg[3] = { clock, 0, 0 };
 
-	div = host->max_clk / clock;
-	if (div < 2)
-		div = 2;
-	if ((host->max_clk / div) > clock)
-		div++;
-	div -= 2;
+		rpi_firmware_property(host->fw,
+				      RPI_FIRMWARE_SET_SDHOST_CLOCK,
+				      &msg, sizeof(msg));
 
-	if (div > SDCDIV_MAX_CDIV)
-		div = SDCDIV_MAX_CDIV;
+		clock = max(msg[1], msg[2]);
+		host->cdiv = 0;
+	} else {
+		if (clock < 100000) {
+			/* Can't stop the clock, but make it as slow as possible
+			 * to show willing
+			 */
+			host->cdiv = SDCDIV_MAX_CDIV;
+			writel(host->cdiv, host->ioaddr + SDCDIV);
+			return;
+		}
 
-	clock = host->max_clk / (div + 2);
-	mmc->actual_clock = clock;
+		div = host->max_clk / clock;
+		if (div < 2)
+			div = 2;
+		if ((host->max_clk / div) > clock)
+			div++;
+		div -= 2;
+
+		if (div > SDCDIV_MAX_CDIV)
+			div = SDCDIV_MAX_CDIV;
+
+		clock = host->max_clk / (div + 2);
+
+		host->cdiv = div;
+		writel(host->cdiv, host->ioaddr + SDCDIV);
+	}
 
 	/* Calibrate some delays */
 
 	host->ns_per_fifo_word = (1000000000 / clock) *
 		((mmc->caps & MMC_CAP_4_BIT_DATA) ? 8 : 32);
 
-	host->cdiv = div;
-	writel(host->cdiv, host->ioaddr + SDCDIV);
+	if (input_clock == 50 * MHZ) {
+		if (clock > input_clock) {
+			/* Save the closest value, to make it easier
+			 * to reduce in the event of error
+			 */
+			host->overclock_50 = (clock/MHZ);
+
+			if (clock != host->overclock) {
+				pr_info("%s: overclocking to %dHz\n",
+					mmc_hostname(mmc), clock);
+				host->overclock = clock;
+			}
+		} else if (host->overclock) {
+			host->overclock = 0;
+			if (clock == 50 * MHZ)
+				pr_warn("%s: cancelling overclock\n",
+					mmc_hostname(mmc));
+		}
+	} else if (input_clock == 0) {
+		/* Reset the preferred overclock when the clock is stopped.
+		 * This always happens during initialisation.
+		 */
+		host->overclock_50 = host->user_overclock_50;
+		host->overclock = 0;
+	}
 
 	/* Set the timeout to 500ms */
-	writel(mmc->actual_clock / 2, host->ioaddr + SDTOUT);
+	writel(clock / 2, host->ioaddr + SDTOUT);
+
+	mmc->actual_clock = clock;
+	host->clock = input_clock;
+	host->reset_clock = 0;
 }
 
 static void bcm2835_request(struct mmc_host *mmc, struct mmc_request *mrq)
@@ -1177,6 +1252,9 @@ static void bcm2835_request(struct mmc_host *mmc, struct mmc_request *mrq)
 		return;
 	}
 
+	if (host->reset_clock)
+		bcm2835_set_clock(host, host->clock);
+
 	mutex_lock(&host->mutex);
 
 	WARN_ON(host->mrq);
@@ -1200,7 +1278,7 @@ static void bcm2835_request(struct mmc_host *mmc, struct mmc_request *mrq)
 		return;
 	}
 
-	if (host->use_dma && mrq->data && (mrq->data->blocks > PIO_THRESHOLD))
+	if (host->use_dma && mrq->data && (mrq->data->blocks > host->pio_limit))
 		bcm2835_prepare_dma(host, mrq->data);
 
 	host->use_sbc = !!mrq->sbc && host->mrq->data &&
@@ -1335,8 +1413,8 @@ static int bcm2835_add_host(struct bcm2835_host *host)
 	}
 
 	pio_limit_string[0] = '\0';
-	if (host->use_dma && (PIO_THRESHOLD > 0))
-		sprintf(pio_limit_string, " (>%d)", PIO_THRESHOLD);
+	if (host->use_dma && (host->pio_limit > 0))
+		sprintf(pio_limit_string, " (>%d)", host->pio_limit);
 	dev_info(dev, "loaded - DMA %s%s\n",
 		 host->use_dma ? "enabled" : "disabled", pio_limit_string);
 
@@ -1346,10 +1424,13 @@ static int bcm2835_add_host(struct bcm2835_host *host)
 static int bcm2835_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
-	struct clk *clk;
+	struct device_node *node = dev->of_node;
 	struct bcm2835_host *host;
+	struct rpi_firmware *fw;
+	struct resource *iomem;
+	bool allow_dma = true;
 	struct mmc_host *mmc;
-	const __be32 *regaddr_p;
+	struct clk *clk;
 	int ret;
 
 	dev_dbg(dev, "%s\n", __func__);
@@ -1362,24 +1443,31 @@ static int bcm2835_probe(struct platform_device *pdev)
 	host->pdev = pdev;
 	spin_lock_init(&host->lock);
 
-	host->ioaddr = devm_platform_ioremap_resource(pdev, 0);
+	host->ioaddr = devm_platform_get_and_ioremap_resource(pdev, 0, &iomem);
 	if (IS_ERR(host->ioaddr)) {
 		ret = PTR_ERR(host->ioaddr);
 		goto err;
 	}
 
-	/* Parse OF address directly to get the physical address for
-	 * DMA to our registers.
-	 */
-	regaddr_p = of_get_address(pdev->dev.of_node, 0, NULL, NULL);
-	if (!regaddr_p) {
-		dev_err(dev, "Can't get phys address\n");
-		ret = -EINVAL;
-		goto err;
+	host->phys_addr = iomem->start;
+	host->pio_limit = PIO_THRESHOLD;
+
+	if (node) {
+		/* Read any custom properties */
+		of_property_read_u32(node,
+				     "brcm,overclock-50",
+				     &host->user_overclock_50);
+		of_property_read_u32(node,
+				     "brcm,pio-limit",
+				     &host->pio_limit);
+		allow_dma =
+			!of_property_read_bool(node, "brcm,force-pio");
+
+		/* Formally recognise the other way of disabling DMA */
+		if (host->pio_limit == 0x7fffffff)
+			allow_dma = false;
 	}
 
-	host->phys_addr = be32_to_cpup(regaddr_p);
-
 	host->dma_chan = NULL;
 	host->dma_desc = NULL;
 
@@ -1401,6 +1489,24 @@ static int bcm2835_probe(struct platform_device *pdev)
 		goto err;
 	}
 
+	fw = rpi_firmware_get(
+		of_parse_phandle(dev->of_node, "firmware", 0));
+	if (fw) {
+		u32 msg[3];
+
+		msg[0] = 0;
+		msg[1] = ~0;
+		msg[2] = ~0;
+
+		rpi_firmware_property(fw, RPI_FIRMWARE_SET_SDHOST_CLOCK,
+				      &msg, sizeof(msg));
+
+		if (msg[1] != ~0)
+			host->fw = fw;
+		else
+			rpi_firmware_put(fw);
+	}
+
 	host->max_clk = clk_get_rate(clk);
 
 	host->irq = platform_get_irq(pdev, 0);
diff --git a/drivers/mmc/host/cqhci-core.c b/drivers/mmc/host/cqhci-core.c
index 178277d90c31..78e80bc574eb 100644
--- a/drivers/mmc/host/cqhci-core.c
+++ b/drivers/mmc/host/cqhci-core.c
@@ -388,9 +388,11 @@ static void cqhci_off(struct mmc_host *mmc)
 
 	err = readx_poll_timeout(cqhci_read_ctl, cq_host, reg,
 				 reg & CQHCI_HALT, 0, CQHCI_OFF_TIMEOUT);
-	if (err < 0)
+	if (err < 0) {
 		pr_err("%s: cqhci: CQE stuck on\n", mmc_hostname(mmc));
-	else
+		/* eMMC v5.1 B.2.8 recommends writing 0 to CQHCI_CTL if stuck */
+		cqhci_writel(cq_host, 0, CQHCI_CTL);
+	} else
 		pr_debug("%s: cqhci: CQE off\n", mmc_hostname(mmc));
 
 	if (cq_host->ops->post_disable)
@@ -980,8 +982,11 @@ static bool cqhci_halt(struct mmc_host *mmc, unsigned int timeout)
 
 	ret = cqhci_halted(cq_host);
 
-	if (!ret)
+	if (!ret) {
 		pr_warn("%s: cqhci: Failed to halt\n", mmc_hostname(mmc));
+		/* eMMC v5.1 B.2.8 recommends writing 0 to CQHCI_CTL if stuck */
+		cqhci_writel(cq_host, 0, CQHCI_CTL);
+	}
 
 	return ret;
 }
diff --git a/drivers/mmc/host/sdhci-brcmstb.c b/drivers/mmc/host/sdhci-brcmstb.c
index 9d9ddc2f6f70..fe064f53abc5 100644
--- a/drivers/mmc/host/sdhci-brcmstb.c
+++ b/drivers/mmc/host/sdhci-brcmstb.c
@@ -12,6 +12,8 @@
 #include <linux/of.h>
 #include <linux/bitops.h>
 #include <linux/delay.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/regulator/consumer.h>
 
 #include "sdhci-cqhci.h"
 #include "sdhci-pltfm.h"
@@ -28,29 +30,38 @@
 
 #define BRCMSTB_PRIV_FLAGS_HAS_CQE		BIT(0)
 #define BRCMSTB_PRIV_FLAGS_GATE_CLOCK		BIT(1)
+#define BRCMSTB_PRIV_FLAGS_HAS_SD_EXPRESS	BIT(2)
 
 #define SDHCI_ARASAN_CQE_BASE_ADDR		0x200
 
 #define SDIO_CFG_CQ_CAPABILITY			0x4c
-#define SDIO_CFG_CQ_CAPABILITY_FMUL		GENMASK(13, 12)
+#define  SDIO_CFG_CQ_CAPABILITY_FMUL_SHIFT	12
 
 #define SDIO_CFG_CTRL				0x0
 #define SDIO_CFG_CTRL_SDCD_N_TEST_EN		BIT(31)
 #define SDIO_CFG_CTRL_SDCD_N_TEST_LEV		BIT(30)
 
+#define SDIO_CFG_SD_PIN_SEL			0x44
+#define  SDIO_CFG_SD_PIN_SEL_MASK		0x3
+#define  SDIO_CFG_SD_PIN_SEL_SD			BIT(1)
+#define  SDIO_CFG_SD_PIN_SEL_MMC		BIT(0)
+
 #define SDIO_CFG_MAX_50MHZ_MODE			0x1ac
 #define SDIO_CFG_MAX_50MHZ_MODE_STRAP_OVERRIDE	BIT(31)
 #define SDIO_CFG_MAX_50MHZ_MODE_ENABLE		BIT(0)
 
-#define MMC_CAP_HSE_MASK	(MMC_CAP2_HSX00_1_2V | MMC_CAP2_HSX00_1_8V)
-/* Select all SD UHS type I SDR speed above 50MB/s */
-#define MMC_CAP_UHS_I_SDR_MASK	(MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104)
-
 struct sdhci_brcmstb_priv {
 	void __iomem *cfg_regs;
 	unsigned int flags;
 	struct clk *base_clk;
 	u32 base_freq_hz;
+	struct regulator *sde_1v8;
+	struct device_node *sde_pcie;
+	void *__iomem sde_ioaddr;
+	void *__iomem sde_ioaddr2;
+	struct pinctrl *pinctrl;
+	struct pinctrl_state *pins_default;
+	struct pinctrl_state *pins_sdex;
 };
 
 struct brcmstb_match_priv {
@@ -141,6 +152,42 @@ static void sdhci_brcmstb_hs400es(struct mmc_host *mmc, struct mmc_ios *ios)
 	writel(reg, host->ioaddr + SDHCI_VENDOR);
 }
 
+static void sdhci_bcm2712_set_clock(struct sdhci_host *host, unsigned int clock)
+{
+	u16 clk;
+	u32 reg;
+	bool is_emmc_rate = false;
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_brcmstb_priv *brcmstb_priv = sdhci_pltfm_priv(pltfm_host);
+
+	host->mmc->actual_clock = 0;
+
+	sdhci_writew(host, 0, SDHCI_CLOCK_CONTROL);
+
+	switch (host->mmc->ios.timing) {
+	case MMC_TIMING_MMC_HS400:
+	case MMC_TIMING_MMC_HS200:
+	case MMC_TIMING_MMC_DDR52:
+	case MMC_TIMING_MMC_HS:
+	is_emmc_rate = true;
+	break;
+	}
+
+	reg = readl(brcmstb_priv->cfg_regs + SDIO_CFG_SD_PIN_SEL);
+	reg &= ~SDIO_CFG_SD_PIN_SEL_MASK;
+	if (is_emmc_rate)
+		reg |= SDIO_CFG_SD_PIN_SEL_MMC;
+	else
+		reg |= SDIO_CFG_SD_PIN_SEL_SD;
+	writel(reg, brcmstb_priv->cfg_regs + SDIO_CFG_SD_PIN_SEL);
+
+	if (clock == 0)
+		return;
+
+	clk = sdhci_calc_clk(host, clock, &host->mmc->actual_clock);
+	sdhci_enable_clk(host, clk);
+}
+
 static void sdhci_brcmstb_set_clock(struct sdhci_host *host, unsigned int clock)
 {
 	u16 clk;
@@ -156,6 +203,17 @@ static void sdhci_brcmstb_set_clock(struct sdhci_host *host, unsigned int clock)
 	sdhci_enable_clk(host, clk);
 }
 
+static void sdhci_brcmstb_set_power(struct sdhci_host *host, unsigned char mode,
+				  unsigned short vdd)
+{
+	if (!IS_ERR(host->mmc->supply.vmmc)) {
+		struct mmc_host *mmc = host->mmc;
+
+		mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, vdd);
+	}
+	sdhci_set_power_noreg(host, mode, vdd);
+}
+
 static void sdhci_brcmstb_set_uhs_signaling(struct sdhci_host *host,
 					    unsigned int timing)
 {
@@ -185,21 +243,41 @@ static void sdhci_brcmstb_set_uhs_signaling(struct sdhci_host *host,
 	sdhci_writew(host, ctrl_2, SDHCI_HOST_CONTROL2);
 }
 
+static void sdhci_bcm2712_hs400_downgrade(struct mmc_host *mmc)
+{
+	struct sdhci_host *host = mmc_priv(mmc);
+	/*
+	 * The eMMC PHY and its internal controller parses and validates
+	 * the uhs_mode, divisor, pin_sel, and sampling clock select
+	 * output from the SD controller. It will refuse to update its
+	 * config if HS timings are selected while the clock is >52MHz.
+	 * so bump the clock down now before card/controller setup is
+	 * performed.
+	 */
+	sdhci_bcm2712_set_clock(host, 52000000);
+}
+
 static void sdhci_brcmstb_cfginit_2712(struct sdhci_host *host)
 {
 	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
 	struct sdhci_brcmstb_priv *brcmstb_priv = sdhci_pltfm_priv(pltfm_host);
 	u32 reg;
+	u32 uhs_mask = (MMC_CAP_UHS_SDR50 | MMC_CAP_UHS_SDR104);
+	u32 hsemmc_mask = (MMC_CAP2_HS200_1_8V_SDR | MMC_CAP2_HS200_1_2V_SDR |
+			   MMC_CAP2_HS400_1_8V | MMC_CAP2_HS400_1_2V);
+	u32 base_clk_mhz;
 
 	/*
 	 * If we support a speed that requires tuning,
 	 * then select the delay line PHY as the clock source.
 	 */
-	if ((host->mmc->caps & MMC_CAP_UHS_I_SDR_MASK) || (host->mmc->caps2 & MMC_CAP_HSE_MASK)) {
+	if ((host->mmc->caps & uhs_mask) || (host->mmc->caps2 & hsemmc_mask)) {
 		reg = readl(brcmstb_priv->cfg_regs + SDIO_CFG_MAX_50MHZ_MODE);
 		reg &= ~SDIO_CFG_MAX_50MHZ_MODE_ENABLE;
 		reg |= SDIO_CFG_MAX_50MHZ_MODE_STRAP_OVERRIDE;
 		writel(reg, brcmstb_priv->cfg_regs + SDIO_CFG_MAX_50MHZ_MODE);
+
+		host->mmc_host_ops.hs400_downgrade = sdhci_bcm2712_hs400_downgrade;
 	}
 
 	if ((host->mmc->caps & MMC_CAP_NONREMOVABLE) ||
@@ -210,6 +288,109 @@ static void sdhci_brcmstb_cfginit_2712(struct sdhci_host *host)
 		reg |= SDIO_CFG_CTRL_SDCD_N_TEST_EN;
 		writel(reg, brcmstb_priv->cfg_regs + SDIO_CFG_CTRL);
 	}
+
+	/* Guesstimate the timer frequency (controller base clock) */
+	base_clk_mhz = max_t(u32, clk_get_rate(pltfm_host->clk) / (1000 * 1000), 1);
+	reg = (3 << SDIO_CFG_CQ_CAPABILITY_FMUL_SHIFT) | base_clk_mhz;
+	writel(reg, brcmstb_priv->cfg_regs + SDIO_CFG_CQ_CAPABILITY);
+}
+
+static int bcm2712_init_sd_express(struct sdhci_host *host, struct mmc_ios *ios)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct sdhci_brcmstb_priv *brcmstb_priv = sdhci_pltfm_priv(pltfm_host);
+	struct device *dev = host->mmc->parent;
+	u32 ctrl_val;
+	u32 present_state;
+	int ret;
+
+	if (!brcmstb_priv->sde_ioaddr || !brcmstb_priv->sde_ioaddr2)
+		return -EINVAL;
+
+	if (!brcmstb_priv->pinctrl)
+		return -EINVAL;
+
+	/* Turn off the SD clock first */
+	sdhci_set_clock(host, 0);
+
+	/* Disable SD DAT0-3 pulls */
+	pinctrl_select_state(brcmstb_priv->pinctrl, brcmstb_priv->pins_sdex);
+
+	ctrl_val = readl(brcmstb_priv->sde_ioaddr);
+	dev_dbg(dev, "ctrl_val 1 %08x\n", ctrl_val);
+
+	/* Tri-state the SD pins */
+	ctrl_val |= 0x1ff8;
+	writel(ctrl_val, brcmstb_priv->sde_ioaddr);
+	dev_dbg(dev, "ctrl_val 1->%08x (%08x)\n", ctrl_val, readl(brcmstb_priv->sde_ioaddr));
+	/* Let voltages settle */
+	udelay(100);
+
+	/* Enable the PCIe sideband pins */
+	ctrl_val &= ~0x6000;
+	writel(ctrl_val, brcmstb_priv->sde_ioaddr);
+	dev_dbg(dev, "ctrl_val 1->%08x (%08x)\n", ctrl_val, readl(brcmstb_priv->sde_ioaddr));
+	/* Let voltages settle */
+	udelay(100);
+
+	/* Turn on the 1v8 VDD2 regulator */
+	ret = regulator_enable(brcmstb_priv->sde_1v8);
+	if (ret)
+		return ret;
+
+	/* Wait for Tpvcrl */
+	msleep(1);
+
+	/* Sample DAT2 (CLKREQ#) - if low, card is in PCIe mode */
+	present_state = sdhci_readl(host, SDHCI_PRESENT_STATE);
+	present_state = (present_state & SDHCI_DATA_LVL_MASK) >> SDHCI_DATA_LVL_SHIFT;
+	dev_dbg(dev, "state = 0x%08x\n", present_state);
+
+	if (present_state & BIT(2)) {
+		dev_err(dev, "DAT2 still high, abandoning SDex switch\n");
+		return -ENODEV;
+	}
+
+	/* Turn on the LCPLL PTEST mux */
+	ctrl_val = readl(brcmstb_priv->sde_ioaddr2 + 20); // misc5
+	ctrl_val &= ~(0x7 << 7);
+	ctrl_val |= 3 << 7;
+	writel(ctrl_val, brcmstb_priv->sde_ioaddr2 + 20);
+	dev_dbg(dev, "misc 5->%08x (%08x)\n", ctrl_val, readl(brcmstb_priv->sde_ioaddr2 + 20));
+
+	/* PTEST diff driver enable */
+	ctrl_val = readl(brcmstb_priv->sde_ioaddr2);
+	ctrl_val |= BIT(21);
+	writel(ctrl_val, brcmstb_priv->sde_ioaddr2);
+
+	dev_dbg(dev, "misc 0->%08x (%08x)\n", ctrl_val, readl(brcmstb_priv->sde_ioaddr2));
+
+	/* Wait for more than the minimum Tpvpgl time */
+	msleep(100);
+
+	if (brcmstb_priv->sde_pcie) {
+		struct of_changeset changeset;
+		static struct property okay_property = {
+			.name = "status",
+			.value = "okay",
+			.length = 5,
+		};
+
+		/* Enable the pcie controller */
+		of_changeset_init(&changeset);
+		ret = of_changeset_update_property(&changeset,
+						   brcmstb_priv->sde_pcie,
+						   &okay_property);
+		if (ret) {
+			dev_err(dev, "%s: failed to update property - %d\n", __func__,
+			       ret);
+			return -ENODEV;
+		}
+		ret = of_changeset_apply(&changeset);
+	}
+
+	dev_dbg(dev, "%s -> %d\n", __func__, ret);
+	return ret;
 }
 
 static void sdhci_brcmstb_dumpregs(struct mmc_host *mmc)
@@ -220,6 +401,7 @@ static void sdhci_brcmstb_dumpregs(struct mmc_host *mmc)
 static void sdhci_brcmstb_cqe_enable(struct mmc_host *mmc)
 {
 	struct sdhci_host *host = mmc_priv(mmc);
+	struct cqhci_host *cq_host = mmc->cqe_private;
 	u32 reg;
 
 	reg = sdhci_readl(host, SDHCI_PRESENT_STATE);
@@ -229,6 +411,22 @@ static void sdhci_brcmstb_cqe_enable(struct mmc_host *mmc)
 	}
 
 	sdhci_cqe_enable(mmc);
+
+	/*
+	 * The controller resets this register to a very short default interval
+	 * whenever CQHCI is disabled.
+	 *
+	 * For removable cards CBC needs to be clear or card removal can hang
+	 * the CQE. In polling mode, a CIT of 0x4000 "cycles" seems to produce the best
+	 * throughput.
+	 *
+	 * For nonremovable cards, the specification default of CBC=1 CIT=0x1000
+	 * suffices.
+	 */
+	if (mmc->caps & MMC_CAP_NONREMOVABLE)
+		cqhci_writel(cq_host, 0x00011000, CQHCI_SSC1);
+	else
+		cqhci_writel(cq_host, 0x00004000, CQHCI_SSC1);
 }
 
 static const struct cqhci_host_ops sdhci_brcmstb_cqhci_ops = {
@@ -245,11 +443,12 @@ static struct sdhci_ops sdhci_brcmstb_ops = {
 };
 
 static struct sdhci_ops sdhci_brcmstb_ops_2712 = {
-	.set_clock = sdhci_set_clock,
-	.set_power = sdhci_set_power_and_bus_voltage,
+	.set_clock = sdhci_bcm2712_set_clock,
+	.set_power = sdhci_brcmstb_set_power,
 	.set_bus_width = sdhci_set_bus_width,
-	.reset = sdhci_reset,
+	.reset = brcmstb_reset,
 	.set_uhs_signaling = sdhci_set_uhs_signaling,
+	.init_sd_express = bcm2712_init_sd_express,
 };
 
 static struct sdhci_ops sdhci_brcmstb_ops_7216 = {
@@ -267,6 +466,8 @@ static struct sdhci_ops sdhci_brcmstb_ops_74165b0 = {
 };
 
 static const struct brcmstb_match_priv match_priv_2712 = {
+	.flags = BRCMSTB_MATCH_FLAGS_USE_CARD_BUSY,
+	.hs400es = sdhci_brcmstb_hs400es,
 	.cfginit = sdhci_brcmstb_cfginit_2712,
 	.ops = &sdhci_brcmstb_ops_2712,
 };
@@ -370,8 +571,10 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev)
 	struct sdhci_pltfm_host *pltfm_host;
 	const struct of_device_id *match;
 	struct sdhci_brcmstb_priv *priv;
-	u32 actual_clock_mhz;
+	u32 actual_clock_mhz, cqe;
 	struct sdhci_host *host;
+	struct resource *iomem;
+	bool no_pinctrl = false;
 	struct clk *clk;
 	struct clk *base_clk = NULL;
 	int res;
@@ -394,12 +597,21 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev)
 		return PTR_ERR(host);
 
 	pltfm_host = sdhci_priv(host);
+	pltfm_host->clk = clk;
+
 	priv = sdhci_pltfm_priv(pltfm_host);
-	if (device_property_read_bool(&pdev->dev, "supports-cqe")) {
+	cqe = 0;
+	device_property_read_u32(&pdev->dev, "supports-cqe", &cqe);
+	if (cqe > 0) {
 		priv->flags |= BRCMSTB_PRIV_FLAGS_HAS_CQE;
 		match_priv->ops->irq = sdhci_brcmstb_cqhci_irq;
 	}
 
+	priv->sde_pcie = of_parse_phandle(pdev->dev.of_node,
+					  "sde-pcie", 0);
+	if (priv->sde_pcie)
+		priv->flags |= BRCMSTB_PRIV_FLAGS_HAS_SD_EXPRESS;
+
 	/* Map in the non-standard CFG registers */
 	priv->cfg_regs = devm_platform_get_and_ioremap_resource(pdev, 1, NULL);
 	if (IS_ERR(priv->cfg_regs)) {
@@ -412,6 +624,43 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev)
 	if (res)
 		goto err;
 
+	priv->sde_1v8 = devm_regulator_get_optional(&pdev->dev, "sde-1v8");
+	if (IS_ERR(priv->sde_1v8))
+		priv->flags &= ~BRCMSTB_PRIV_FLAGS_HAS_SD_EXPRESS;
+
+	iomem = platform_get_resource(pdev, IORESOURCE_MEM, 2);
+	if (iomem) {
+		priv->sde_ioaddr = devm_ioremap_resource(&pdev->dev, iomem);
+		if (IS_ERR(priv->sde_ioaddr))
+			priv->sde_ioaddr = NULL;
+	}
+
+	iomem = platform_get_resource(pdev, IORESOURCE_MEM, 3);
+	if (iomem) {
+		priv->sde_ioaddr2 = devm_ioremap_resource(&pdev->dev, iomem);
+		if (IS_ERR(priv->sde_ioaddr2))
+			priv->sde_ioaddr = NULL;
+	}
+
+	priv->pinctrl = devm_pinctrl_get(&pdev->dev);
+	if (IS_ERR(priv->pinctrl)) {
+			no_pinctrl = true;
+	}
+	priv->pins_default = pinctrl_lookup_state(priv->pinctrl, "default");
+	if (IS_ERR(priv->pins_default)) {
+			dev_dbg(&pdev->dev, "No pinctrl default state\n");
+			no_pinctrl = true;
+	}
+	priv->pins_sdex = pinctrl_lookup_state(priv->pinctrl, "sd-express");
+	if (IS_ERR(priv->pins_sdex)) {
+			dev_dbg(&pdev->dev, "No pinctrl sd-express state\n");
+			no_pinctrl = true;
+	}
+	if (no_pinctrl || !priv->sde_ioaddr || !priv->sde_ioaddr2) {
+		priv->pinctrl = NULL;
+		priv->flags &= ~BRCMSTB_PRIV_FLAGS_HAS_SD_EXPRESS;
+	}
+
 	/*
 	 * Automatic clock gating does not work for SD cards that may
 	 * voltage switch so only enable it for non-removable devices.
@@ -428,6 +677,10 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev)
 	    (host->mmc->caps2 & MMC_CAP2_HS400_ES))
 		host->mmc_host_ops.hs400_enhanced_strobe = match_priv->hs400es;
 
+	if (host->ops->init_sd_express &&
+	    (priv->flags & BRCMSTB_PRIV_FLAGS_HAS_SD_EXPRESS))
+		host->mmc->caps2 |= MMC_CAP2_SD_EXP;
+
 	if (match_priv->cfginit)
 		match_priv->cfginit(host);
 
@@ -481,7 +734,6 @@ static int sdhci_brcmstb_probe(struct platform_device *pdev)
 	if (res)
 		goto err;
 
-	pltfm_host->clk = clk;
 	return res;
 
 err:
diff --git a/drivers/mmc/host/sdhci-iproc.c b/drivers/mmc/host/sdhci-iproc.c
index 10235fdff246..f014437cb911 100644
--- a/drivers/mmc/host/sdhci-iproc.c
+++ b/drivers/mmc/host/sdhci-iproc.c
@@ -198,6 +198,7 @@ static const struct sdhci_ops sdhci_iproc_32only_ops = {
 	.write_b = sdhci_iproc_writeb,
 	.set_clock = sdhci_set_clock,
 	.get_max_clock = sdhci_iproc_get_max_clock,
+	.set_power = sdhci_set_power_and_bus_voltage,
 	.set_bus_width = sdhci_set_bus_width,
 	.reset = sdhci_reset,
 	.set_uhs_signaling = sdhci_set_uhs_signaling,
diff --git a/drivers/mmc/host/sdhci-of-dwcmshc.c b/drivers/mmc/host/sdhci-of-dwcmshc.c
index 9852f95e0c26..d34b36e546c1 100644
--- a/drivers/mmc/host/sdhci-of-dwcmshc.c
+++ b/drivers/mmc/host/sdhci-of-dwcmshc.c
@@ -221,6 +221,7 @@ struct rk35xx_priv {
 
 struct dwcmshc_priv {
 	struct clk	*bus_clk;
+	struct clk	*sdio_clk;
 	int vendor_specific_area1; /* P_VENDOR_SPECIFIC_AREA1 reg */
 	int vendor_specific_area2; /* P_VENDOR_SPECIFIC_AREA2 reg */
 
@@ -302,6 +303,17 @@ static void dwcmshc_reset(struct sdhci_host *host, u8 mask)
 		sdhci_writel(host, SDHCI_INT_RESPONSE, SDHCI_INT_STATUS);
 }
 
+static void dwcmshc_set_clock(struct sdhci_host *host, unsigned int clock)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+	struct dwcmshc_priv *priv = sdhci_pltfm_priv(pltfm_host);
+
+	if (priv->sdio_clk)
+		clk_set_rate(priv->sdio_clk, clock);
+
+	sdhci_set_clock(host, clock);
+}
+
 static unsigned int dwcmshc_get_max_clock(struct sdhci_host *host)
 {
 	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
@@ -1150,10 +1162,11 @@ static int sg2042_init(struct device *dev, struct sdhci_host *host,
 }
 
 static const struct sdhci_ops sdhci_dwcmshc_ops = {
-	.set_clock		= sdhci_set_clock,
+	.set_clock		= dwcmshc_set_clock,
 	.set_bus_width		= sdhci_set_bus_width,
 	.set_uhs_signaling	= dwcmshc_set_uhs_signaling,
 	.get_max_clock		= dwcmshc_get_max_clock,
+	.get_timeout_clock	= sdhci_pltfm_clk_get_timeout_clock,
 	.reset			= dwcmshc_reset,
 	.adma_write_desc	= dwcmshc_adma_write_desc,
 	.irq			= dwcmshc_cqe_irq_handler,
@@ -1226,8 +1239,10 @@ static const struct sdhci_ops sdhci_dwcmshc_sg2042_ops = {
 static const struct dwcmshc_pltfm_data sdhci_dwcmshc_pdata = {
 	.pdata = {
 		.ops = &sdhci_dwcmshc_ops,
-		.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN,
-		.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN,
+		.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
+			SDHCI_QUIRK_BROKEN_CARD_DETECTION,
+		.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
+			SDHCI_QUIRK2_BROKEN_HS200,
 	},
 };
 
@@ -1242,13 +1257,28 @@ static const struct dwcmshc_pltfm_data sdhci_dwcmshc_bf3_pdata = {
 };
 #endif
 
+static const struct dwcmshc_pltfm_data sdhci_dwcmshc_rp1_pdata = {
+	.pdata = {
+		.ops = &sdhci_dwcmshc_ops,
+		.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
+			  SDHCI_QUIRK_BROKEN_CARD_DETECTION,
+		.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
+			   SDHCI_QUIRK2_BROKEN_HS200 |
+			   SDHCI_QUIRK2_SPURIOUS_INT_RESP,
+	}
+};
+
 static const struct dwcmshc_pltfm_data sdhci_dwcmshc_rk35xx_pdata = {
 	.pdata = {
 		.ops = &sdhci_dwcmshc_rk35xx_ops,
 		.quirks = SDHCI_QUIRK_CAP_CLOCK_BASE_BROKEN |
 			  SDHCI_QUIRK_BROKEN_TIMEOUT_VAL,
 		.quirks2 = SDHCI_QUIRK2_PRESET_VALUE_BROKEN |
-			   SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN,
+			   SDHCI_QUIRK2_CLOCK_DIV_ZERO_BROKEN |
+			   SDHCI_QUIRK2_NO_SDR50 |
+			   SDHCI_QUIRK2_NO_SDR104 |
+			   SDHCI_QUIRK2_NO_SDR25,
+
 	},
 	.init = dwcmshc_rk35xx_init,
 	.postinit = dwcmshc_rk35xx_postinit,
@@ -1360,6 +1390,10 @@ static void dwcmshc_cqhci_init(struct sdhci_host *host, struct platform_device *
 }
 
 static const struct of_device_id sdhci_dwcmshc_dt_ids[] = {
+	{
+		.compatible = "raspberrypi,rp1-dwcmshc",
+		.data = &sdhci_dwcmshc_rp1_pdata,
+	},
 	{
 		.compatible = "rockchip,rk3588-dwcmshc",
 		.data = &sdhci_dwcmshc_rk35xx_pdata,
@@ -1453,13 +1487,32 @@ static int dwcmshc_probe(struct platform_device *pdev)
 		priv->bus_clk = devm_clk_get(dev, "bus");
 		if (!IS_ERR(priv->bus_clk))
 			clk_prepare_enable(priv->bus_clk);
+
+		pltfm_host->timeout_clk = devm_clk_get(dev, "timeout");
+		if (!IS_ERR(pltfm_host->timeout_clk))
+			err = clk_prepare_enable(pltfm_host->timeout_clk);
+		if (err)
+			goto free_pltfm;
+
+		priv->sdio_clk = devm_clk_get_optional(&pdev->dev, "sdio");
 	}
 
+	pltfm_host->timeout_clk = devm_clk_get(&pdev->dev, "timeout");
+	if (IS_ERR(pltfm_host->timeout_clk)) {
+		err = PTR_ERR(pltfm_host->timeout_clk);
+		dev_err(&pdev->dev, "failed to get timeout clk: %d\n", err);
+		goto free_pltfm;
+	}
+	err = clk_prepare_enable(pltfm_host->timeout_clk);
+	if (err)
+		goto free_pltfm;
+
 	err = mmc_of_parse(host->mmc);
 	if (err)
 		goto err_clk;
 
 	sdhci_get_of_property(pdev);
+	sdhci_enable_v4_mode(host);
 
 	priv->vendor_specific_area1 =
 		sdhci_readl(host, DWCMSHC_P_VENDOR_AREA1) & DWCMSHC_AREA1_MASK;
@@ -1519,6 +1572,7 @@ static int dwcmshc_probe(struct platform_device *pdev)
 	pm_runtime_put_noidle(dev);
 err_clk:
 	clk_disable_unprepare(pltfm_host->clk);
+	clk_disable_unprepare(pltfm_host->timeout_clk);
 	clk_disable_unprepare(priv->bus_clk);
 	clk_bulk_disable_unprepare(priv->num_other_clks, priv->other_clks);
 free_pltfm:
diff --git a/drivers/mmc/host/sdhci-pltfm.c b/drivers/mmc/host/sdhci-pltfm.c
index 62753d72198a..6035afd36c17 100644
--- a/drivers/mmc/host/sdhci-pltfm.c
+++ b/drivers/mmc/host/sdhci-pltfm.c
@@ -32,6 +32,14 @@ unsigned int sdhci_pltfm_clk_get_max_clock(struct sdhci_host *host)
 }
 EXPORT_SYMBOL_GPL(sdhci_pltfm_clk_get_max_clock);
 
+unsigned int sdhci_pltfm_clk_get_timeout_clock(struct sdhci_host *host)
+{
+	struct sdhci_pltfm_host *pltfm_host = sdhci_priv(host);
+
+	return clk_get_rate(pltfm_host->timeout_clk);
+}
+EXPORT_SYMBOL_GPL(sdhci_pltfm_clk_get_timeout_clock);
+
 static const struct sdhci_ops sdhci_pltfm_ops = {
 	.set_clock = sdhci_set_clock,
 	.set_bus_width = sdhci_set_bus_width,
diff --git a/drivers/mmc/host/sdhci-pltfm.h b/drivers/mmc/host/sdhci-pltfm.h
index b81d5b0fd616..4eb77191c421 100644
--- a/drivers/mmc/host/sdhci-pltfm.h
+++ b/drivers/mmc/host/sdhci-pltfm.h
@@ -20,6 +20,7 @@ struct sdhci_pltfm_data {
 
 struct sdhci_pltfm_host {
 	struct clk *clk;
+	struct clk *timeout_clk;
 
 	/* migrate from sdhci_of_host */
 	unsigned int clock;
@@ -106,6 +107,8 @@ extern void sdhci_pltfm_remove(struct platform_device *pdev);
 
 extern unsigned int sdhci_pltfm_clk_get_max_clock(struct sdhci_host *host);
 
+extern unsigned int sdhci_pltfm_clk_get_timeout_clock(struct sdhci_host *host);
+
 static inline void *sdhci_pltfm_priv(struct sdhci_pltfm_host *host)
 {
 	return host->private;
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index bd67cbb9a19e..c59f03a401db 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -40,7 +40,7 @@
 	pr_debug("%s: " DRIVER_NAME ": " f, mmc_hostname(host->mmc), ## x)
 
 #define SDHCI_DUMP(f, x...) \
-	pr_err("%s: " DRIVER_NAME ": " f, mmc_hostname(host->mmc), ## x)
+	pr_debug("%s: " DRIVER_NAME ": " f, mmc_hostname(host->mmc), ## x)
 
 #define MAX_TUNING_LOOP 40
 
@@ -1081,7 +1081,7 @@ static void sdhci_initialize_data(struct sdhci_host *host,
 	WARN_ON(host->data);
 
 	/* Sanity checks */
-	BUG_ON(data->blksz * data->blocks > 524288);
+	BUG_ON(data->blksz * data->blocks > host->mmc->max_req_size);
 	BUG_ON(data->blksz > host->mmc->max_blk_size);
 	BUG_ON(data->blocks > 65535);
 
@@ -1713,6 +1713,12 @@ static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
 	if (host->use_external_dma)
 		sdhci_external_dma_pre_transfer(host, cmd);
 
+	if (host->quirks2 & SDHCI_QUIRK2_SPURIOUS_INT_RESP) {
+		host->ier |= SDHCI_INT_RESPONSE;
+		sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
+		sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
+	}
+
 	sdhci_writew(host, SDHCI_MAKE_CMD(cmd->opcode, flags), SDHCI_COMMAND);
 
 	return true;
@@ -3032,6 +3038,15 @@ static void sdhci_card_event(struct mmc_host *mmc)
 	spin_unlock_irqrestore(&host->lock, flags);
 }
 
+static int sdhci_init_sd_express(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct sdhci_host *host = mmc_priv(mmc);
+
+	if (!host->ops->init_sd_express)
+		return -EOPNOTSUPP;
+	return host->ops->init_sd_express(host, ios);
+}
+
 static const struct mmc_host_ops sdhci_ops = {
 	.request	= sdhci_request,
 	.post_req	= sdhci_post_req,
@@ -3047,6 +3062,7 @@ static const struct mmc_host_ops sdhci_ops = {
 	.execute_tuning			= sdhci_execute_tuning,
 	.card_event			= sdhci_card_event,
 	.card_busy	= sdhci_card_busy,
+	.init_sd_express = sdhci_init_sd_express,
 };
 
 /*****************************************************************************\
@@ -3194,7 +3210,7 @@ static void sdhci_timeout_timer(struct timer_list *t)
 	spin_lock_irqsave(&host->lock, flags);
 
 	if (host->cmd && !sdhci_data_line_cmd(host->cmd)) {
-		pr_err("%s: Timeout waiting for hardware cmd interrupt.\n",
+		pr_debug("%s: Timeout waiting for hardware cmd interrupt.\n",
 		       mmc_hostname(host->mmc));
 		sdhci_err_stats_inc(host, REQ_TIMEOUT);
 		sdhci_dumpregs(host);
@@ -3217,7 +3233,7 @@ static void sdhci_timeout_data_timer(struct timer_list *t)
 
 	if (host->data || host->data_cmd ||
 	    (host->cmd && sdhci_data_line_cmd(host->cmd))) {
-		pr_err("%s: Timeout waiting for hardware interrupt.\n",
+		pr_debug("%s: Timeout waiting for hardware interrupt.\n",
 		       mmc_hostname(host->mmc));
 		sdhci_err_stats_inc(host, REQ_TIMEOUT);
 		sdhci_dumpregs(host);
@@ -3281,6 +3297,11 @@ static void sdhci_cmd_irq(struct sdhci_host *host, u32 intmask, u32 *intmask_p)
 		if (intmask & SDHCI_INT_TIMEOUT) {
 			host->cmd->error = -ETIMEDOUT;
 			sdhci_err_stats_inc(host, CMD_TIMEOUT);
+			if (host->quirks2 & SDHCI_QUIRK2_SPURIOUS_INT_RESP) {
+				host->ier &= ~SDHCI_INT_RESPONSE;
+				sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
+				sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
+			}
 		} else {
 			host->cmd->error = -EILSEQ;
 			if (!mmc_op_tuning(host->cmd->opcode))
@@ -4572,6 +4593,15 @@ int sdhci_setup_host(struct sdhci_host *host)
 	    !(host->quirks2 & SDHCI_QUIRK2_BROKEN_DDR50))
 		mmc->caps |= MMC_CAP_UHS_DDR50;
 
+	if (host->quirks2 & SDHCI_QUIRK2_NO_SDR25)
+		mmc->caps &= ~MMC_CAP_UHS_SDR25;
+
+	if (host->quirks2 & SDHCI_QUIRK2_NO_SDR50)
+		mmc->caps &= ~MMC_CAP_UHS_SDR50;
+
+	if (host->quirks2 & SDHCI_QUIRK2_NO_SDR104)
+		mmc->caps &= ~MMC_CAP_UHS_SDR104;
+
 	/* Does the host need tuning for SDR50? */
 	if (host->caps1 & SDHCI_USE_SDR50_TUNING)
 		host->flags |= SDHCI_SDR50_NEEDS_TUNING;
@@ -4686,11 +4716,16 @@ int sdhci_setup_host(struct sdhci_host *host)
 	spin_lock_init(&host->lock);
 
 	/*
-	 * Maximum number of sectors in one transfer. Limited by SDMA boundary
-	 * size (512KiB). Note some tuning modes impose a 4MiB limit, but this
-	 * is less anyway.
+	 * Maximum number of sectors in one transfer.
+	 * 4MiB is preferred for multi-descriptor DMA as a) card sequential
+	 * write speeds are only guaranteed with a 4MiB write length and
+	 * b) most tuning modes require a 4MiB limit.
+	 * SDMA has a 512KiB boundary size.
 	 */
-	mmc->max_req_size = 524288;
+	if (host->flags & SDHCI_USE_ADMA)
+		mmc->max_req_size = SZ_4M;
+	else
+		mmc->max_req_size = SZ_512K;
 
 	/*
 	 * Maximum number of segments. Depends on if the hardware
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index da1b0a46b1d9..76bef04239d4 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -485,6 +485,14 @@ struct sdhci_host {
 /* Issue CMD and DATA reset together */
 #define SDHCI_QUIRK2_ISSUE_CMD_DAT_RESET_TOGETHER	(1<<19)
 
+/* Quirks to ignore a speed if a that speed is unreliable */
+#define SDHCI_QUIRK2_NO_SDR25	(1<<19)
+#define SDHCI_QUIRK2_NO_SDR50  (1<<20)
+#define SDHCI_QUIRK2_NO_SDR104	(1<<21)
+
+/* Command timeouts may generate a trailing INT_RESPONSE later */
+#define SDHCI_QUIRK2_SPURIOUS_INT_RESP			(1<<31)
+
 	int irq;		/* Device IRQ */
 	void __iomem *ioaddr;	/* Mapped address */
 	phys_addr_t mapbase;	/* physical address base */
@@ -667,6 +675,7 @@ struct sdhci_ops {
 	void	(*request_done)(struct sdhci_host *host,
 				struct mmc_request *mrq);
 	void    (*dump_vendor_regs)(struct sdhci_host *host);
+	int	(*init_sd_express)(struct sdhci_host *host, struct mmc_ios *ios);
 };
 
 #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.c b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
index 49f6e83d6013..72bf43651e2f 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.c
@@ -67,6 +67,12 @@
 
 /* Forward declarations */
 static void bcmgenet_set_rx_mode(struct net_device *dev);
+static bool skip_umac_reset = false;
+module_param(skip_umac_reset, bool, 0444);
+MODULE_PARM_DESC(skip_umac_reset, "Skip UMAC reset step");
+static bool eee = true;
+module_param(eee, bool, 0444);
+MODULE_PARM_DESC(eee, "Enable EEE (default Y)");
 
 static inline void bcmgenet_writel(u32 value, void __iomem *offset)
 {
@@ -2486,6 +2492,11 @@ static void reset_umac(struct bcmgenet_priv *priv)
 	bcmgenet_rbuf_ctrl_set(priv, 0);
 	udelay(10);
 
+	if (skip_umac_reset) {
+		pr_warn("Skipping UMAC reset\n");
+		return;
+	}
+
 	/* issue soft reset and disable MAC while updating its registers */
 	spin_lock_bh(&priv->reg_lock);
 	bcmgenet_umac_writel(priv, CMD_SW_RESET, UMAC_CMD);
@@ -2657,7 +2668,7 @@ static void bcmgenet_init_tx_ring(struct bcmgenet_priv *priv,
 
 	bcmgenet_tdma_ring_writel(priv, index, 0, TDMA_PROD_INDEX);
 	bcmgenet_tdma_ring_writel(priv, index, 0, TDMA_CONS_INDEX);
-	bcmgenet_tdma_ring_writel(priv, index, 1, DMA_MBUF_DONE_THRESH);
+	bcmgenet_tdma_ring_writel(priv, index, 10, DMA_MBUF_DONE_THRESH);
 	/* Disable rate control for now */
 	bcmgenet_tdma_ring_writel(priv, index, flow_period_val,
 				  TDMA_FLOW_PERIOD);
@@ -3415,6 +3426,17 @@ static int bcmgenet_open(struct net_device *dev)
 
 	bcmgenet_phy_pause_set(dev, priv->rx_pause, priv->tx_pause);
 
+	if (!eee) {
+		struct ethtool_keee eee_data;
+
+		ret = bcmgenet_get_eee(dev, &eee_data);
+		if (ret == 0) {
+			eee_data.eee_enabled = 0;
+			bcmgenet_set_eee(dev, &eee_data);
+			netdev_warn(dev, "EEE disabled\n");
+		}
+	}
+
 	bcmgenet_netif_start(dev);
 
 	netif_tx_start_all_queues(dev);
@@ -4132,9 +4154,12 @@ static int bcmgenet_probe(struct platform_device *pdev)
 	netif_set_real_num_rx_queues(priv->dev, priv->hw_params->rx_queues + 1);
 
 	/* Set default coalescing parameters */
-	for (i = 0; i < priv->hw_params->rx_queues; i++)
+	for (i = 0; i < priv->hw_params->rx_queues; i++) {
 		priv->rx_rings[i].rx_max_coalesced_frames = 1;
+		priv->rx_rings[i].rx_coalesce_usecs = 50;
+	}
 	priv->rx_rings[DESC_INDEX].rx_max_coalesced_frames = 1;
+	priv->rx_rings[DESC_INDEX].rx_coalesce_usecs = 50;
 
 	/* libphy will determine the link state */
 	netif_carrier_off(dev);
diff --git a/drivers/net/ethernet/broadcom/genet/bcmgenet.h b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
index c0005a0fff56..f9735e680256 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmgenet.h
+++ b/drivers/net/ethernet/broadcom/genet/bcmgenet.h
@@ -31,7 +31,7 @@
 #define ENET_PAD		8
 #define ENET_MAX_MTU_SIZE	(ETH_DATA_LEN + ETH_HLEN + VLAN_HLEN + \
 				 ENET_BRCM_TAG_LEN + ETH_FCS_LEN + ENET_PAD)
-#define DMA_MAX_BURST_LENGTH    0x10
+#define DMA_MAX_BURST_LENGTH    0x08
 
 /* misc. configuration */
 #define MAX_NUM_OF_FS_RULES		16
diff --git a/drivers/net/ethernet/broadcom/genet/bcmmii.c b/drivers/net/ethernet/broadcom/genet/bcmmii.c
index 9beb65e6d0a9..74889ce67ec1 100644
--- a/drivers/net/ethernet/broadcom/genet/bcmmii.c
+++ b/drivers/net/ethernet/broadcom/genet/bcmmii.c
@@ -301,14 +301,14 @@ int bcmgenet_mii_probe(struct net_device *dev)
 	struct device_node *dn = kdev->of_node;
 	phy_interface_t phy_iface = priv->phy_interface;
 	struct phy_device *phydev;
-	u32 phy_flags = PHY_BRCM_AUTO_PWRDWN_ENABLE |
-			PHY_BRCM_DIS_TXCRXC_NOENRGY |
-			PHY_BRCM_IDDQ_SUSPEND;
+	u32 phy_flags = 0;
 	int ret;
 
 	/* Communicate the integrated PHY revision */
 	if (priv->internal_phy)
 		phy_flags = priv->gphy_rev;
+	else
+		phy_flags = PHY_BRCM_AUTO_PWRDWN_ENABLE;
 
 	/* This is an ugly quirk but we have not been correctly interpreting
 	 * the phy_interface values and we have done that across different
diff --git a/drivers/net/ethernet/cadence/macb.h b/drivers/net/ethernet/cadence/macb.h
index 2847278d9cd4..f797244f5764 100644
--- a/drivers/net/ethernet/cadence/macb.h
+++ b/drivers/net/ethernet/cadence/macb.h
@@ -86,6 +86,8 @@
 #define GEM_PBUFRXCUT		0x0044 /* RX Partial Store and Forward */
 #define GEM_JML			0x0048 /* Jumbo Max Length */
 #define GEM_HS_MAC_CONFIG	0x0050 /* GEM high speed config */
+#define GEM_AMP			0x0054 /* AXI Max Pipeline */
+#define GEM_INTMOD		0x005c /* Interrupt moderation */
 #define GEM_HRB			0x0080 /* Hash Bottom */
 #define GEM_HRT			0x0084 /* Hash Top */
 #define GEM_SA1B		0x0088 /* Specific1 Bottom */
@@ -348,6 +350,21 @@
 #define GEM_ADDR64_OFFSET	30 /* Address bus width - 64b or 32b */
 #define GEM_ADDR64_SIZE		1
 
+/* Bitfields in AMP */
+#define GEM_AR2R_MAX_PIPE_OFFSET	0  /* Maximum number of outstanding AXI read requests */
+#define GEM_AR2R_MAX_PIPE_SIZE		8
+#define GEM_AW2W_MAX_PIPE_OFFSET	8  /* Maximum number of outstanding AXI write requests */
+#define GEM_AW2W_MAX_PIPE_SIZE		8
+#define GEM_AW2B_FILL_OFFSET		16 /* Select wether the max AW2W transactions operates between: */
+#define GEM_AW2B_FILL_AW2W		0  /*   0: the AW to W AXI channel */
+#define GEM_AW2B_FILL_AW2B		1  /*   1: AW to B channel */
+#define GEM_AW2B_FILL_SIZE              1
+
+/* Bitfields in INTMOD */
+#define GEM_RX_MODERATION_OFFSET	0  /* RX interrupt moderation */
+#define GEM_RX_MODERATION_SIZE		8
+#define GEM_TX_MODERATION_OFFSET	16 /* TX interrupt moderation */
+#define GEM_TX_MODERATION_SIZE		8
 
 /* Bitfields in PBUFRXCUT */
 #define GEM_ENCUTTHRU_OFFSET	31 /* Enable RX partial store and forward */
@@ -813,6 +830,7 @@
 	})
 
 #define MACB_READ_NSR(bp)	macb_readl(bp, NSR)
+#define MACB_READ_TSR(bp)	macb_readl(bp, TSR)
 
 /* struct macb_dma_desc - Hardware DMA descriptor
  * @addr: DMA address of data buffer
@@ -1229,6 +1247,7 @@ struct macb_queue {
 	dma_addr_t		tx_ring_dma;
 	struct work_struct	tx_error_task;
 	bool			txubr_pending;
+	bool			tx_pending;
 	struct napi_struct	napi_tx;
 
 	dma_addr_t		rx_ring_dma;
@@ -1296,9 +1315,15 @@ struct macb {
 
 	u32			caps;
 	unsigned int		dma_burst_length;
+	u8			aw2w_max_pipe;
+	u8			ar2r_max_pipe;
+	bool			use_aw2b_fill;
 
 	phy_interface_t		phy_interface;
 
+	struct gpio_desc	*phy_reset_gpio;
+	int			phy_reset_ms;
+
 	/* AT91RM9200 transmit queue (1 on wire + 1 queued) */
 	struct macb_tx_skb	rm9200_txq[2];
 	unsigned int		max_tx_length;
diff --git a/drivers/net/ethernet/cadence/macb_main.c b/drivers/net/ethernet/cadence/macb_main.c
index fbf8d0ba9c47..728f55265665 100644
--- a/drivers/net/ethernet/cadence/macb_main.c
+++ b/drivers/net/ethernet/cadence/macb_main.c
@@ -42,6 +42,9 @@
 #include <linux/inetdevice.h>
 #include "macb.h"
 
+static unsigned int txdelay = 35;
+module_param(txdelay, uint, 0644);
+
 /* This structure is only used for MACB on SiFive FU540 devices */
 struct sifive_fu540_macb_mgmt {
 	void __iomem *reg;
@@ -335,7 +338,7 @@ static int macb_mdio_wait_for_idle(struct macb *bp)
 	u32 val;
 
 	return readx_poll_timeout(MACB_READ_NSR, bp, val, val & MACB_BIT(IDLE),
-				  1, MACB_MDIO_TIMEOUT);
+				  100, MACB_MDIO_TIMEOUT);
 }
 
 static int macb_mdio_read_c22(struct mii_bus *bus, int mii_id, int regnum)
@@ -494,6 +497,19 @@ static int macb_mdio_write_c45(struct mii_bus *bus, int mii_id,
 	return status;
 }
 
+static int macb_mdio_reset(struct mii_bus *bus)
+{
+	struct macb *bp = bus->priv;
+
+	if (bp->phy_reset_gpio) {
+		gpiod_set_value_cansleep(bp->phy_reset_gpio, 1);
+		msleep(bp->phy_reset_ms);
+		gpiod_set_value_cansleep(bp->phy_reset_gpio, 0);
+	}
+
+	return 0;
+}
+
 static void macb_init_buffers(struct macb *bp)
 {
 	struct macb_queue *queue;
@@ -1066,6 +1082,7 @@ static int macb_mii_init(struct macb *bp)
 	bp->mii_bus->write = &macb_mdio_write_c22;
 	bp->mii_bus->read_c45 = &macb_mdio_read_c45;
 	bp->mii_bus->write_c45 = &macb_mdio_write_c45;
+	bp->mii_bus->reset = &macb_mdio_reset;
 	snprintf(bp->mii_bus->id, MII_BUS_ID_SIZE, "%s-%x",
 		 bp->pdev->name, bp->pdev->id);
 	bp->mii_bus->priv = bp;
@@ -1731,6 +1748,11 @@ static int macb_rx(struct macb_queue *queue, struct napi_struct *napi,
 
 		macb_init_rx_ring(queue);
 		queue_writel(queue, RBQP, queue->rx_ring_dma);
+#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
+		if (bp->hw_dma_cap & HW_DMA_CAP_64B)
+			macb_writel(bp, RBQPH,
+				    upper_32_bits(queue->rx_ring_dma));
+#endif
 
 		macb_writel(bp, NCR, ctrl | MACB_BIT(RE));
 
@@ -2033,8 +2055,9 @@ static irqreturn_t macb_interrupt(int irq, void *dev_id)
 				queue_writel(queue, ISR, MACB_BIT(TCOMP) |
 							 MACB_BIT(TXUBR));
 
-			if (status & MACB_BIT(TXUBR)) {
+			if (status & MACB_BIT(TXUBR) || queue->tx_pending) {
 				queue->txubr_pending = true;
+				queue->tx_pending = 0;
 				wmb(); // ensure softirq can see update
 			}
 
@@ -2490,6 +2513,11 @@ static netdev_tx_t macb_start_xmit(struct sk_buff *skb, struct net_device *dev)
 	skb_tx_timestamp(skb);
 
 	spin_lock(&bp->lock);
+
+	/* TSTART write might get dropped, so make the IRQ retrigger a buffer read */
+	if (macb_readl(bp, TSR) & MACB_BIT(TGO))
+		queue->tx_pending = 1;
+
 	macb_writel(bp, NCR, macb_readl(bp, NCR) | MACB_BIT(TSTART));
 	spin_unlock(&bp->lock);
 
@@ -2901,6 +2929,37 @@ static void macb_configure_dma(struct macb *bp)
 	}
 }
 
+static void gem_init_axi(struct macb *bp)
+{
+	u32 amp;
+
+	/* AXI pipeline setup - don't touch values unless specified in device
+	 * tree. Some hardware could have reset values > 1.
+	 */
+	amp = gem_readl(bp, AMP);
+
+	if (bp->use_aw2b_fill)
+		amp = GEM_BFINS(AW2B_FILL, bp->use_aw2b_fill, amp);
+	if (bp->aw2w_max_pipe)
+		amp = GEM_BFINS(AW2W_MAX_PIPE, bp->aw2w_max_pipe, amp);
+	if (bp->ar2r_max_pipe)
+		amp = GEM_BFINS(AR2R_MAX_PIPE, bp->ar2r_max_pipe, amp);
+
+	gem_writel(bp, AMP, amp);
+}
+
+static void gem_init_intmod(struct macb *bp)
+{
+	unsigned int throttle;
+	u32 intmod = 0;
+
+	/* Use sensible interrupt moderation thresholds (50us rx and tx) */
+	throttle = (1000 * 50) / 800;
+	intmod = GEM_BFINS(TX_MODERATION, throttle, intmod);
+	intmod = GEM_BFINS(RX_MODERATION, throttle, intmod);
+	gem_writel(bp, INTMOD, intmod);
+}
+
 static void macb_init_hw(struct macb *bp)
 {
 	u32 config;
@@ -2929,6 +2988,11 @@ static void macb_init_hw(struct macb *bp)
 	if (bp->caps & MACB_CAPS_JUMBO)
 		bp->rx_frm_len_mask = MACB_RX_JFRMLEN_MASK;
 
+	if (macb_is_gem(bp)) {
+		gem_init_axi(bp);
+		gem_init_intmod(bp);
+	}
+
 	macb_configure_dma(bp);
 
 	/* Enable RX partial store and forward and set watermark */
@@ -3296,6 +3360,52 @@ static void gem_get_ethtool_strings(struct net_device *dev, u32 sset, u8 *p)
 	}
 }
 
+static int gem_set_coalesce(struct net_device *dev,
+			    struct ethtool_coalesce *ec,
+			    struct kernel_ethtool_coalesce *kernel_coal,
+			    struct netlink_ext_ack *extack)
+{
+	struct macb *bp = netdev_priv(dev);
+	unsigned int tx_throttle;
+	unsigned int rx_throttle;
+	u32 intmod = 0;
+
+	/* GEM has simple IRQ throttling support. RX and TX interrupts
+	 * are separately moderated on 800ns quantums, with no support
+	 * for frame coalescing.
+	 */
+
+	/* Max is 255 * 0.8us = 204us. Zero implies no moderation. */
+	if (ec->rx_coalesce_usecs > 204 || ec->tx_coalesce_usecs > 204)
+		return -EINVAL;
+
+	tx_throttle = (1000 * ec->tx_coalesce_usecs) / 800;
+	rx_throttle = (1000 * ec->rx_coalesce_usecs) / 800;
+
+	intmod = GEM_BFINS(TX_MODERATION, tx_throttle, intmod);
+	intmod = GEM_BFINS(RX_MODERATION, rx_throttle, intmod);
+
+	gem_writel(bp, INTMOD, intmod);
+
+	return 0;
+}
+
+static int gem_get_coalesce(struct net_device *dev,
+			    struct ethtool_coalesce *ec,
+			    struct kernel_ethtool_coalesce *kernel_coal,
+			    struct netlink_ext_ack *extack)
+{
+	struct macb *bp = netdev_priv(dev);
+	u32 intmod;
+
+	intmod = gem_readl(bp, INTMOD);
+
+	ec->tx_coalesce_usecs = (GEM_BFEXT(TX_MODERATION, intmod) * 800) / 1000;
+	ec->rx_coalesce_usecs = (GEM_BFEXT(RX_MODERATION, intmod) * 800) / 1000;
+
+	return 0;
+}
+
 static struct net_device_stats *macb_get_stats(struct net_device *dev)
 {
 	struct macb *bp = netdev_priv(dev);
@@ -3883,6 +3993,8 @@ static const struct ethtool_ops macb_ethtool_ops = {
 };
 
 static const struct ethtool_ops gem_ethtool_ops = {
+	.supported_coalesce_params = ETHTOOL_COALESCE_RX_USECS |
+				     ETHTOOL_COALESCE_TX_USECS,
 	.get_regs_len		= macb_get_regs_len,
 	.get_regs		= macb_get_regs,
 	.get_wol		= macb_get_wol,
@@ -3892,6 +4004,8 @@ static const struct ethtool_ops gem_ethtool_ops = {
 	.get_ethtool_stats	= gem_get_ethtool_stats,
 	.get_strings		= gem_get_ethtool_strings,
 	.get_sset_count		= gem_get_sset_count,
+	.get_coalesce		= gem_get_coalesce,
+	.set_coalesce		= gem_set_coalesce,
 	.get_link_ksettings     = macb_get_link_ksettings,
 	.set_link_ksettings     = macb_set_link_ksettings,
 	.get_ringparam		= macb_get_ringparam,
@@ -5062,6 +5176,17 @@ static const struct macb_config versal_config = {
 	.usrio = &macb_default_usrio,
 };
 
+static const struct macb_config raspberrypi_rp1_config = {
+	.caps = MACB_CAPS_GIGABIT_MODE_AVAILABLE | MACB_CAPS_CLK_HW_CHG |
+		MACB_CAPS_JUMBO |
+		MACB_CAPS_GEM_HAS_PTP,
+	.dma_burst_length = 16,
+	.clk_init = macb_clk_init,
+	.init = macb_init,
+	.usrio = &macb_default_usrio,
+	.jumbo_max_len = 10240,
+};
+
 static const struct of_device_id macb_dt_ids[] = {
 	{ .compatible = "cdns,at91sam9260-macb", .data = &at91sam9260_config },
 	{ .compatible = "cdns,macb" },
@@ -5082,6 +5207,7 @@ static const struct of_device_id macb_dt_ids[] = {
 	{ .compatible = "microchip,mpfs-macb", .data = &mpfs_config },
 	{ .compatible = "microchip,sama7g5-gem", .data = &sama7g5_gem_config },
 	{ .compatible = "microchip,sama7g5-emac", .data = &sama7g5_emac_config },
+	{ .compatible = "raspberrypi,rp1-gem", .data = &raspberrypi_rp1_config },
 	{ .compatible = "xlnx,zynqmp-gem", .data = &zynqmp_config},
 	{ .compatible = "xlnx,zynq-gem", .data = &zynq_config },
 	{ .compatible = "xlnx,versal-gem", .data = &versal_config},
@@ -5213,6 +5339,11 @@ static int macb_probe(struct platform_device *pdev)
 			}
 		}
 	}
+
+	device_property_read_u8(&pdev->dev, "cdns,aw2w-max-pipe", &bp->aw2w_max_pipe);
+	device_property_read_u8(&pdev->dev, "cdns,ar2r-max-pipe", &bp->ar2r_max_pipe);
+	bp->use_aw2b_fill = device_property_read_bool(&pdev->dev, "cdns,use-aw2b-fill");
+
 	spin_lock_init(&bp->lock);
 	spin_lock_init(&bp->stats_lock);
 
@@ -5273,6 +5404,21 @@ static int macb_probe(struct platform_device *pdev)
 	else
 		bp->phy_interface = interface;
 
+	/* optional PHY reset-related properties */
+	bp->phy_reset_gpio = devm_gpiod_get_optional(&pdev->dev, "phy-reset",
+						     GPIOD_OUT_LOW);
+	if (IS_ERR(bp->phy_reset_gpio)) {
+		dev_err(&pdev->dev, "Failed to obtain phy-reset gpio\n");
+		err = PTR_ERR(bp->phy_reset_gpio);
+		goto err_out_free_netdev;
+	}
+
+	bp->phy_reset_ms = 10;
+	of_property_read_u32(np, "phy-reset-duration", &bp->phy_reset_ms);
+	/* A sane reset duration should not be longer than 1s */
+	if (bp->phy_reset_ms > 1000)
+		bp->phy_reset_ms = 1000;
+
 	/* IP specific init */
 	err = init(pdev);
 	if (err)
@@ -5344,6 +5490,19 @@ static void macb_remove(struct platform_device *pdev)
 	}
 }
 
+static void macb_shutdown(struct platform_device *pdev)
+{
+	struct net_device *dev;
+
+	dev = platform_get_drvdata(pdev);
+
+	rtnl_lock();
+	netif_device_detach(dev);
+	if (netif_running(dev))
+		dev_close(dev);
+	rtnl_unlock();
+}
+
 static int __maybe_unused macb_suspend(struct device *dev)
 {
 	struct net_device *netdev = dev_get_drvdata(dev);
@@ -5616,6 +5775,7 @@ static const struct dev_pm_ops macb_pm_ops = {
 static struct platform_driver macb_driver = {
 	.probe		= macb_probe,
 	.remove_new	= macb_remove,
+	.shutdown	= macb_shutdown,
 	.driver		= {
 		.name		= "macb",
 		.of_match_table	= of_match_ptr(macb_dt_ids),
diff --git a/drivers/net/phy/bcm-phy-ptp.c b/drivers/net/phy/bcm-phy-ptp.c
index 208e8f561e06..b9abf814d7fd 100644
--- a/drivers/net/phy/bcm-phy-ptp.c
+++ b/drivers/net/phy/bcm-phy-ptp.c
@@ -913,6 +913,18 @@ struct bcm_ptp_private *bcm_ptp_probe(struct phy_device *phydev)
 	switch (BRCM_PHY_MODEL(phydev)) {
 	case PHY_ID_BCM54210E:
 		break;
+#ifdef PHY_ID_BCM54213PE
+	case PHY_ID_BCM54213PE:
+		switch (phydev->mdio.addr) {
+		case 0: // CM4 - this is a BCM54210PE which supports PTP
+			break;
+		case 1: //  4B - this is a BCM54213PE which doesn't
+			return NULL;
+		default: // Unknown - assume it's BCM54210PE
+			break;
+		}
+		break;
+#endif
 	default:
 		return NULL;
 	}
diff --git a/drivers/net/phy/broadcom.c b/drivers/net/phy/broadcom.c
index 9260c822e467..9e072d2fb11e 100644
--- a/drivers/net/phy/broadcom.c
+++ b/drivers/net/phy/broadcom.c
@@ -126,6 +126,11 @@ static int bcm54210e_config_init(struct phy_device *phydev)
 	return 0;
 }
 
+static int bcm54213pe_config_init(struct phy_device *phydev)
+{
+	return bcm54210e_config_init(phydev);
+}
+
 static int bcm54612e_config_init(struct phy_device *phydev)
 {
 	int reg;
@@ -297,7 +302,8 @@ static void bcm54xx_adjust_rxrefclk(struct phy_device *phydev)
 	    BRCM_PHY_MODEL(phydev) != PHY_ID_BCM50610M &&
 	    BRCM_PHY_MODEL(phydev) != PHY_ID_BCM54210E &&
 	    BRCM_PHY_MODEL(phydev) != PHY_ID_BCM54810 &&
-	    BRCM_PHY_MODEL(phydev) != PHY_ID_BCM54811)
+	    BRCM_PHY_MODEL(phydev) != PHY_ID_BCM54811 &&
+	    BRCM_PHY_MODEL(phydev) != PHY_ID_BCM54213PE)
 		return;
 
 	val = bcm_phy_read_shadow(phydev, BCM54XX_SHD_SCR3);
@@ -430,18 +436,17 @@ static int bcm54811_config_init(struct phy_device *phydev)
 static int bcm54xx_config_init(struct phy_device *phydev)
 {
 	int reg, err, val;
+	u32 led_modes[] = {BCM_LED_MULTICOLOR_LINK_ACT,
+			   BCM_LED_MULTICOLOR_LINK};
+	struct device_node *np = phydev->mdio.dev.of_node;
 
 	reg = phy_read(phydev, MII_BCM54XX_ECR);
 	if (reg < 0)
 		return reg;
 
-	/* Mask interrupts globally.  */
-	reg |= MII_BCM54XX_ECR_IM;
-	err = phy_write(phydev, MII_BCM54XX_ECR, reg);
-	if (err < 0)
-		return err;
-
-	/* Unmask events we are interested in.  */
+	/* Initially all interrupts are masked in IMR, so unmask events
+	 * we are interested in.
+	 */
 	reg = ~(MII_BCM54XX_INT_DUPLEX |
 		MII_BCM54XX_INT_SPEED |
 		MII_BCM54XX_INT_LINK);
@@ -454,6 +459,9 @@ static int bcm54xx_config_init(struct phy_device *phydev)
 	    (phydev->dev_flags & PHY_BRCM_CLEAR_RGMII_MODE))
 		bcm_phy_write_shadow(phydev, BCM54XX_SHD_RGMII_MODE, 0);
 
+	if (of_property_read_bool(np, "brcm,powerdown-enable"))
+		phydev->dev_flags |= PHY_BRCM_AUTO_PWRDWN_ENABLE;
+
 	bcm54xx_adjust_rxrefclk(phydev);
 
 	switch (BRCM_PHY_MODEL(phydev)) {
@@ -467,6 +475,9 @@ static int bcm54xx_config_init(struct phy_device *phydev)
 	case PHY_ID_BCM54612E:
 		err = bcm54612e_config_init(phydev);
 		break;
+	case PHY_ID_BCM54213PE:
+		err = bcm54213pe_config_init(phydev);
+		break;
 	case PHY_ID_BCM54616S:
 		err = bcm54616s_config_init(phydev);
 		break;
@@ -488,22 +499,32 @@ static int bcm54xx_config_init(struct phy_device *phydev)
 
 	bcm54xx_phydsp_config(phydev);
 
+	of_property_read_u32_array(np, "led-modes", led_modes, 2);
+
 	/* For non-SFP setups, encode link speed into LED1 and LED3 pair
 	 * (green/amber).
-	 * Also flash these two LEDs on activity. This means configuring
-	 * them for MULTICOLOR and encoding link/activity into them.
 	 * Don't do this for devices on an SFP module, since some of these
 	 * use the LED outputs to control the SFP LOS signal, and changing
 	 * these settings will cause LOS to malfunction.
 	 */
 	if (!phy_on_sfp(phydev)) {
+		int led_swap = of_property_read_bool(np, "led-swap") ? 1 : 0;
 		val = BCM54XX_SHD_LEDS1_LED1(BCM_LED_SRC_MULTICOLOR1) |
 			BCM54XX_SHD_LEDS1_LED3(BCM_LED_SRC_MULTICOLOR1);
 		bcm_phy_write_shadow(phydev, BCM54XX_SHD_LEDS1, val);
+		/* BCM54210PE controls two extra LEDs with the next register.
+		 * Make LED3 shadow LED1, but preserve LED4 as is - useful on
+		 * CM4/CM5 which use LED3 for ETH_LEDY instead of LED1. LED4
+		 * is either unused or configured as INT pin on CM5.
+		 */
+		reg = bcm_phy_read_shadow(phydev, BCM54XX_SHD_LEDS2);
+		reg &= ~(0xf << 0);
+		reg |= BCM54XX_SHD_LEDS1_LED1(BCM_LED_SRC_MULTICOLOR1);
+		bcm_phy_write_shadow(phydev, BCM54XX_SHD_LEDS2, reg);
 
 		val = BCM_LED_MULTICOLOR_IN_PHASE |
-			BCM54XX_SHD_LEDS1_LED1(BCM_LED_MULTICOLOR_LINK_ACT) |
-			BCM54XX_SHD_LEDS1_LED3(BCM_LED_MULTICOLOR_LINK_ACT);
+			BCM54XX_SHD_LEDS1_LED1(led_modes[0 ^ led_swap]) |
+			BCM54XX_SHD_LEDS1_LED3(led_modes[1 ^ led_swap]);
 		bcm_phy_write_exp(phydev, BCM_EXP_MULTICOLOR, val);
 	}
 
@@ -1453,7 +1474,7 @@ static struct phy_driver broadcom_drivers[] = {
 	.link_change_notify	= bcm54xx_link_change_notify,
 }, {
 	.phy_id		= PHY_ID_BCM54210E,
-	.phy_id_mask	= 0xfffffff0,
+	.phy_id_mask	= 0xffffffff,
 	.name		= "Broadcom BCM54210E",
 	/* PHY_GBIT_FEATURES */
 	.flags		= PHY_ALWAYS_CALL_SUSPEND,
@@ -1470,6 +1491,21 @@ static struct phy_driver broadcom_drivers[] = {
 	.get_wol	= bcm54xx_phy_get_wol,
 	.set_wol	= bcm54xx_phy_set_wol,
 	.led_brightness_set	= bcm_phy_led_brightness_set,
+}, {
+	.phy_id		= PHY_ID_BCM54213PE,
+	.phy_id_mask	= 0xffffffff,
+	.name		= "Broadcom BCM54213PE",
+	/* PHY_GBIT_FEATURES */
+	.get_sset_count	= bcm_phy_get_sset_count,
+	.get_strings	= bcm_phy_get_strings,
+	.get_stats	= bcm54xx_get_stats,
+	.probe		= bcm54xx_phy_probe,
+	.config_init	= bcm54xx_config_init,
+	.config_intr	= bcm_phy_config_intr,
+	.handle_interrupt = bcm_phy_handle_interrupt,
+	.link_change_notify	= bcm54xx_link_change_notify,
+	.suspend	= bcm54xx_suspend,
+	.resume		= bcm54xx_resume,
 }, {
 	.phy_id		= PHY_ID_BCM5461,
 	.phy_id_mask	= 0xfffffff0,
@@ -1737,7 +1773,8 @@ module_phy_driver(broadcom_drivers);
 static struct mdio_device_id __maybe_unused broadcom_tbl[] = {
 	{ PHY_ID_BCM5411, 0xfffffff0 },
 	{ PHY_ID_BCM5421, 0xfffffff0 },
-	{ PHY_ID_BCM54210E, 0xfffffff0 },
+	{ PHY_ID_BCM54210E, 0xffffffff },
+	{ PHY_ID_BCM54213PE, 0xffffffff },
 	{ PHY_ID_BCM5461, 0xfffffff0 },
 	{ PHY_ID_BCM54612E, 0xfffffff0 },
 	{ PHY_ID_BCM54616S, 0xfffffff0 },
diff --git a/drivers/net/phy/microchip.c b/drivers/net/phy/microchip.c
index ffca1cec4ec9..485508afc2f6 100644
--- a/drivers/net/phy/microchip.c
+++ b/drivers/net/phy/microchip.c
@@ -198,6 +198,7 @@ static int lan88xx_probe(struct phy_device *phydev)
 	struct device *dev = &phydev->mdio.dev;
 	struct lan88xx_priv *priv;
 	u32 led_modes[4];
+	u32 downshift_after = 0;
 	int len;
 
 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
@@ -227,6 +228,32 @@ static int lan88xx_probe(struct phy_device *phydev)
 		return -EINVAL;
 	}
 
+	if (!of_property_read_u32(dev->of_node,
+				  "microchip,downshift-after",
+				  &downshift_after)) {
+		u32 mask = LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_MASK;
+		u32 val= LAN78XX_PHY_CTRL3_AUTO_DOWNSHIFT;
+
+		switch (downshift_after) {
+		case 2:	val |= LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_2;
+			break;
+		case 3:	val |= LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_3;
+			break;
+		case 4:	val |= LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_4;
+			break;
+		case 5:	val |= LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_5;
+			break;
+		case 0: // Disable completely
+			mask = LAN78XX_PHY_CTRL3_AUTO_DOWNSHIFT;
+			val = 0;
+			break;
+		default:
+			return -EINVAL;
+		}
+		(void)phy_modify_paged(phydev, 1, LAN78XX_PHY_CTRL3,
+				       mask, val);
+	}
+
 	/* these values can be used to identify internal PHY */
 	priv->chip_id = phy_read_mmd(phydev, 3, LAN88XX_MMD3_CHIP_ID);
 	priv->chip_rev = phy_read_mmd(phydev, 3, LAN88XX_MMD3_CHIP_REV);
diff --git a/drivers/net/phy/phy-c45.c b/drivers/net/phy/phy-c45.c
index 5695935fdce9..ac987e5e82dc 100644
--- a/drivers/net/phy/phy-c45.c
+++ b/drivers/net/phy/phy-c45.c
@@ -942,7 +942,7 @@ EXPORT_SYMBOL_GPL(genphy_c45_read_eee_abilities);
  */
 int genphy_c45_an_config_eee_aneg(struct phy_device *phydev)
 {
-	if (!phydev->eee_enabled) {
+	if (!phydev->eee_cfg.eee_enabled) {
 		__ETHTOOL_DECLARE_LINK_MODE_MASK(adv) = {};
 
 		return genphy_c45_write_eee_adv(phydev, adv);
@@ -1575,8 +1575,6 @@ int genphy_c45_ethtool_set_eee(struct phy_device *phydev,
 		linkmode_copy(phydev->advertising_eee, adv);
 	}
 
-	phydev->eee_enabled = data->eee_enabled;
-
 	ret = genphy_c45_an_config_eee_aneg(phydev);
 	if (ret > 0) {
 		ret = phy_restart_aneg(phydev);
diff --git a/drivers/net/phy/phy.c b/drivers/net/phy/phy.c
index 707a6ff5242b..2979cfd1f363 100644
--- a/drivers/net/phy/phy.c
+++ b/drivers/net/phy/phy.c
@@ -1728,7 +1728,7 @@ EXPORT_SYMBOL(phy_ethtool_get_eee);
  * phy_ethtool_set_eee_noneg - Adjusts MAC LPI configuration without PHY
  *			       renegotiation
  * @phydev: pointer to the target PHY device structure
- * @data: pointer to the ethtool_keee structure containing the new EEE settings
+ * @old_cfg: pointer to the eee_config structure containing the old EEE settings
  *
  * This function updates the Energy Efficient Ethernet (EEE) configuration
  * for cases where only the MAC's Low Power Idle (LPI) configuration changes,
@@ -1739,11 +1739,10 @@ EXPORT_SYMBOL(phy_ethtool_get_eee);
  * configuration.
  */
 static void phy_ethtool_set_eee_noneg(struct phy_device *phydev,
-				      struct ethtool_keee *data)
+				      const struct eee_config *old_cfg)
 {
-	if (phydev->eee_cfg.tx_lpi_enabled != data->tx_lpi_enabled ||
-	    phydev->eee_cfg.tx_lpi_timer != data->tx_lpi_timer) {
-		eee_to_eeecfg(&phydev->eee_cfg, data);
+	if (phydev->eee_cfg.tx_lpi_enabled != old_cfg->tx_lpi_enabled ||
+	    phydev->eee_cfg.tx_lpi_timer != old_cfg->tx_lpi_timer) {
 		phydev->enable_tx_lpi = eeecfg_mac_can_tx_lpi(&phydev->eee_cfg);
 		if (phydev->link) {
 			phydev->link = false;
@@ -1763,18 +1762,23 @@ static void phy_ethtool_set_eee_noneg(struct phy_device *phydev,
  */
 int phy_ethtool_set_eee(struct phy_device *phydev, struct ethtool_keee *data)
 {
+	struct eee_config old_cfg;
 	int ret;
 
 	if (!phydev->drv)
 		return -EIO;
 
 	mutex_lock(&phydev->lock);
+
+	old_cfg = phydev->eee_cfg;
+	eee_to_eeecfg(&phydev->eee_cfg, data);
+
 	ret = genphy_c45_ethtool_set_eee(phydev, data);
-	if (ret >= 0) {
-		if (ret == 0)
-			phy_ethtool_set_eee_noneg(phydev, data);
-		eee_to_eeecfg(&phydev->eee_cfg, data);
-	}
+	if (ret == 0)
+		phy_ethtool_set_eee_noneg(phydev, &old_cfg);
+	else if (ret < 0)
+		phydev->eee_cfg = old_cfg;
+
 	mutex_unlock(&phydev->lock);
 
 	return ret < 0 ? ret : 0;
diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c
index 7f995d0e51f7..127f4d5ea2cd 100644
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
@@ -3619,12 +3619,12 @@ static int phy_probe(struct device *dev)
 	/* There is no "enabled" flag. If PHY is advertising, assume it is
 	 * kind of enabled.
 	 */
-	phydev->eee_enabled = !linkmode_empty(phydev->advertising_eee);
+	phydev->eee_cfg.eee_enabled = !linkmode_empty(phydev->advertising_eee);
 
 	/* Some PHYs may advertise, by default, not support EEE modes. So,
 	 * we need to clean them.
 	 */
-	if (phydev->eee_enabled)
+	if (phydev->eee_cfg.eee_enabled)
 		linkmode_and(phydev->advertising_eee, phydev->supported_eee,
 			     phydev->advertising_eee);
 
diff --git a/drivers/net/usb/lan78xx.c b/drivers/net/usb/lan78xx.c
index 8647980df005..63137e037665 100644
--- a/drivers/net/usb/lan78xx.c
+++ b/drivers/net/usb/lan78xx.c
@@ -604,6 +604,20 @@ static int lan78xx_alloc_tx_resources(struct lan78xx_net *dev)
 				      dev->n_tx_urbs, dev->tx_urb_size, dev);
 }
 
+/* TSO seems to be having some issue with Selective Acknowledge (SACK) that
+ * results in lost data never being retransmitted.
+ * Disable it by default now, but adds a module parameter to enable it for
+ * debug purposes (the full cause is not currently understood).
+ */
+static bool enable_tso;
+module_param(enable_tso, bool, 0644);
+MODULE_PARM_DESC(enable_tso, "Enables TCP segmentation offload");
+
+#define INT_URB_MICROFRAMES_PER_MS	8
+static int int_urb_interval_ms = 8;
+module_param(int_urb_interval_ms, int, 0);
+MODULE_PARM_DESC(int_urb_interval_ms, "Override usb interrupt urb interval");
+
 static int lan78xx_read_reg(struct lan78xx_net *dev, u32 index, u32 *data)
 {
 	u32 *buf;
@@ -1421,6 +1435,9 @@ static int lan78xx_link_reset(struct lan78xx_net *dev)
 	if (unlikely(ret < 0))
 		return ret;
 
+	/* Acknowledge any pending PHY interrupt, lest it be the last */
+	phy_read(phydev, LAN88XX_INT_STS);
+
 	mutex_lock(&phydev->lock);
 	phy_read_status(phydev);
 	link = phydev->link;
@@ -1685,14 +1702,11 @@ static int lan78xx_get_eee(struct net_device *net, struct ethtool_keee *edata)
 	if (ret < 0)
 		goto exit;
 
-	ret = lan78xx_read_reg(dev, MAC_CR, &buf);
-	if (buf & MAC_CR_EEE_EN_) {
-		/* EEE_TX_LPI_REQ_DLY & tx_lpi_timer are same uSec unit */
-		ret = lan78xx_read_reg(dev, EEE_TX_LPI_REQ_DLY, &buf);
+	ret = lan78xx_read_reg(dev, EEE_TX_LPI_REQ_DLY, &buf);
+	if (ret >= 0)
 		edata->tx_lpi_timer = buf;
-	} else {
+	else
 		edata->tx_lpi_timer = 0;
-	}
 
 	ret = 0;
 exit:
@@ -1918,6 +1932,7 @@ static const struct ethtool_ops lan78xx_ethtool_ops = {
 	.set_link_ksettings = lan78xx_set_link_ksettings,
 	.get_regs_len	= lan78xx_get_regs_len,
 	.get_regs	= lan78xx_get_regs,
+	.get_ts_info    = ethtool_op_get_ts_info,
 };
 
 static int lan78xx_init_mac_address(struct lan78xx_net *dev)
@@ -2420,7 +2435,26 @@ static int lan78xx_phy_init(struct lan78xx_net *dev)
 	mii_adv_to_linkmode_adv_t(fc, mii_adv);
 	linkmode_or(phydev->advertising, fc, phydev->advertising);
 
-	phy_support_eee(phydev);
+	if (of_property_read_bool(phydev->mdio.dev.of_node,
+				  "microchip,eee-enabled")) {
+		struct ethtool_keee edata;
+		memset(&edata, 0, sizeof(edata));
+
+		linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT,
+			edata.advertised);
+		linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT,
+			edata.advertised);
+
+		edata.eee_enabled = true;
+		edata.tx_lpi_enabled = true;
+		if (of_property_read_u32(phydev->mdio.dev.of_node,
+					 "microchip,tx-lpi-timer",
+					 &edata.tx_lpi_timer))
+			edata.tx_lpi_timer = 600; /* non-aggressive */
+		(void)lan78xx_set_eee(dev->net, &edata);
+
+		phy_support_eee(phydev);
+	}
 
 	if (phydev->mdio.dev.of_node) {
 		u32 reg;
@@ -2899,6 +2933,11 @@ static int lan78xx_reset(struct lan78xx_net *dev)
 	int ret;
 	u32 buf;
 	u8 sig;
+	bool has_eeprom;
+	bool has_otp;
+
+	has_eeprom = !lan78xx_read_eeprom(dev, 0, 0, NULL);
+	has_otp = !lan78xx_read_otp(dev, 0, 0, NULL);
 
 	ret = lan78xx_read_reg(dev, HW_CFG, &buf);
 	if (ret < 0)
@@ -2967,6 +3006,10 @@ static int lan78xx_reset(struct lan78xx_net *dev)
 	buf |= HW_CFG_CLK125_EN_;
 	buf |= HW_CFG_REFCLK25_EN_;
 
+	/* If no valid EEPROM and no valid OTP, enable the LEDs by default */
+	if (!has_eeprom && !has_otp)
+	    buf |= HW_CFG_LED0_EN_ | HW_CFG_LED1_EN_;
+
 	ret = lan78xx_write_reg(dev, HW_CFG, buf);
 	if (ret < 0)
 		return ret;
@@ -3069,6 +3112,9 @@ static int lan78xx_reset(struct lan78xx_net *dev)
 			buf |= MAC_CR_AUTO_DUPLEX_ | MAC_CR_AUTO_SPEED_;
 		}
 	}
+	/* If no valid EEPROM and no valid OTP, enable AUTO negotiation */
+	if (!has_eeprom && !has_otp)
+	    buf |= MAC_CR_AUTO_DUPLEX_ | MAC_CR_AUTO_SPEED_;
 	ret = lan78xx_write_reg(dev, MAC_CR, buf);
 	if (ret < 0)
 		return ret;
@@ -3466,8 +3512,14 @@ static int lan78xx_bind(struct lan78xx_net *dev, struct usb_interface *intf)
 	if (DEFAULT_RX_CSUM_ENABLE)
 		dev->net->features |= NETIF_F_RXCSUM;
 
-	if (DEFAULT_TSO_CSUM_ENABLE)
-		dev->net->features |= NETIF_F_TSO | NETIF_F_TSO6 | NETIF_F_SG;
+	if (DEFAULT_TSO_CSUM_ENABLE) {
+		dev->net->features |= NETIF_F_SG;
+		/* Use module parameter to control TCP segmentation offload as
+		 * it appears to cause issues.
+		 */
+		if (enable_tso)
+			dev->net->features |= NETIF_F_TSO | NETIF_F_TSO6;
+	}
 
 	if (DEFAULT_VLAN_RX_OFFLOAD)
 		dev->net->features |= NETIF_F_HW_VLAN_CTAG_RX;
@@ -4437,7 +4489,13 @@ static int lan78xx_probe(struct usb_interface *intf,
 	if (ret < 0)
 		goto out4;
 
-	period = ep_intr->desc.bInterval;
+	if (int_urb_interval_ms <= 0)
+		period = ep_intr->desc.bInterval;
+	else
+		period = int_urb_interval_ms * INT_URB_MICROFRAMES_PER_MS;
+
+	netif_notice(dev, probe, netdev, "int urb period %d\n", period);
+
 	maxp = usb_maxpacket(dev->udev, dev->pipe_intr);
 
 	dev->urb_intr = usb_alloc_urb(0, GFP_KERNEL);
diff --git a/drivers/net/usb/smsc95xx.c b/drivers/net/usb/smsc95xx.c
index 8e82184be5e7..737a1734e451 100644
--- a/drivers/net/usb/smsc95xx.c
+++ b/drivers/net/usb/smsc95xx.c
@@ -79,6 +79,14 @@ static bool turbo_mode = true;
 module_param(turbo_mode, bool, 0644);
 MODULE_PARM_DESC(turbo_mode, "Enable multiple frames per Rx transaction");
 
+static int packetsize = 2560;
+module_param(packetsize, int, 0644);
+MODULE_PARM_DESC(packetsize, "Override the RX URB packet size");
+
+static char *macaddr = ":";
+module_param(macaddr, charp, 0);
+MODULE_PARM_DESC(macaddr, "MAC address");
+
 static int __must_check smsc95xx_read_reg(struct usbnet *dev, u32 index,
 					  u32 *data)
 {
@@ -801,6 +809,21 @@ static int smsc95xx_ioctl(struct net_device *netdev, struct ifreq *rq, int cmd)
 	return phy_mii_ioctl(netdev->phydev, rq, cmd);
 }
 
+/* Check the macaddr module parameter for a MAC address */
+static int smsc95xx_macaddr_param(struct usbnet *dev, struct net_device *nd)
+{
+	u8 mtbl[ETH_ALEN];
+
+	if (mac_pton(macaddr, mtbl)) {
+		netif_dbg(dev, ifup, dev->net,
+			  "Overriding MAC address with: %pM\n", mtbl);
+		dev_addr_mod(nd, 0, mtbl, ETH_ALEN);
+		return 0;
+	} else {
+		return -EINVAL;
+	}
+}
+
 static void smsc95xx_init_mac_address(struct usbnet *dev)
 {
 	u8 addr[ETH_ALEN];
@@ -824,6 +847,14 @@ static void smsc95xx_init_mac_address(struct usbnet *dev)
 		}
 	}
 
+	/* Check module parameters */
+	if (smsc95xx_macaddr_param(dev, dev->net) == 0) {
+		if (is_valid_ether_addr(dev->net->dev_addr)) {
+			netif_dbg(dev, ifup, dev->net, "MAC address read from module parameter\n");
+			return;
+		}
+	}
+
 	/* no useful static MAC address found. generate a random one */
 	eth_hw_addr_random(dev->net);
 	netif_dbg(dev, ifup, dev->net, "MAC address set to eth_random_addr\n");
@@ -932,13 +963,13 @@ static int smsc95xx_reset(struct usbnet *dev)
 
 	if (!turbo_mode) {
 		burst_cap = 0;
-		dev->rx_urb_size = MAX_SINGLE_PACKET_SIZE;
+		dev->rx_urb_size = packetsize ? packetsize : MAX_SINGLE_PACKET_SIZE;
 	} else if (dev->udev->speed == USB_SPEED_HIGH) {
-		burst_cap = DEFAULT_HS_BURST_CAP_SIZE / HS_USB_PKT_SIZE;
-		dev->rx_urb_size = DEFAULT_HS_BURST_CAP_SIZE;
+		dev->rx_urb_size = packetsize ? packetsize : DEFAULT_HS_BURST_CAP_SIZE;
+		burst_cap = dev->rx_urb_size / HS_USB_PKT_SIZE;
 	} else {
-		burst_cap = DEFAULT_FS_BURST_CAP_SIZE / FS_USB_PKT_SIZE;
-		dev->rx_urb_size = DEFAULT_FS_BURST_CAP_SIZE;
+		dev->rx_urb_size = packetsize ? packetsize : DEFAULT_FS_BURST_CAP_SIZE;
+		burst_cap = dev->rx_urb_size / FS_USB_PKT_SIZE;
 	}
 
 	netif_dbg(dev, ifup, dev->net, "rx_urb_size=%ld\n",
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h
index fe31051a9e11..8c3613ff23f5 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h
@@ -298,7 +298,7 @@ void brcmf_rx_event(struct device *dev, struct sk_buff *rxp);
 
 int brcmf_alloc(struct device *dev, struct brcmf_mp_device *settings);
 /* Indication from bus module regarding presence/insertion of dongle. */
-int brcmf_attach(struct device *dev);
+int brcmf_attach(struct device *dev, bool start_bus);
 /* Indication from bus module regarding removal/absence of dongle */
 void brcmf_detach(struct device *dev);
 void brcmf_free(struct device *dev);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 2b8a85a7bf9e..d4ae8cc05166 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -9,6 +9,7 @@
 #include <linux/etherdevice.h>
 #include <linux/module.h>
 #include <linux/vmalloc.h>
+#include <linux/ctype.h>
 #include <net/cfg80211.h>
 #include <net/netlink.h>
 #include <uapi/linux/if_arp.h>
@@ -89,6 +90,9 @@
 
 #define BRCMF_PS_MAX_TIMEOUT_MS		2000
 
+#define MGMT_AUTH_FRAME_DWELL_TIME	4000
+#define MGMT_AUTH_FRAME_WAIT_TIME	(MGMT_AUTH_FRAME_DWELL_TIME + 100)
+
 /* Dump obss definitions */
 #define ACS_MSRMNT_DELAY		80
 #define CHAN_NOISE_DUMMY		(-80)
@@ -1944,14 +1948,18 @@ static s32 brcmf_set_wpa_version(struct net_device *ndev,
 	s32 val = 0;
 	s32 err = 0;
 
-	if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_1)
+	if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_1) {
 		val = WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED;
-	else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_2)
-		val = WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED;
-	else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_3)
+	} else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_2) {
+		if (sme->crypto.akm_suites[0] == WLAN_AKM_SUITE_SAE)
+			val = WPA3_AUTH_SAE_PSK;
+		else
+			val = WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED;
+	} else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_3) {
 		val = WPA3_AUTH_SAE_PSK;
-	else
+	} else {
 		val = WPA_AUTH_DISABLED;
+	}
 	brcmf_dbg(CONN, "setting wpa_auth to 0x%0x\n", val);
 	err = brcmf_fil_bsscfg_int_set(ifp, "wpa_auth", val);
 	if (err) {
@@ -2162,6 +2170,10 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme)
 			if (sme->crypto.sae_pwd) {
 				brcmf_dbg(INFO, "using SAE offload\n");
 				profile->use_fwsup = BRCMF_PROFILE_FWSUP_SAE;
+			} else if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FWSUP) &&
+				brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SAE_EXT)) {
+				brcmf_dbg(INFO, "using EXTSAE with PSK offload\n");
+				profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK;
 			}
 			break;
 		case WLAN_AKM_SUITE_FT_OVER_SAE:
@@ -2217,7 +2229,7 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme)
 	brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "mfp", mfp);
 
 skip_mfp_config:
-	brcmf_dbg(CONN, "setting wpa_auth to %d\n", val);
+	brcmf_dbg(CONN, "setting wpa_auth to 0x%0x\n", val);
 	err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "wpa_auth", val);
 	if (err) {
 		bphy_err(drvr, "could not set wpa_auth (%d)\n", err);
@@ -2462,43 +2474,52 @@ brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev,
 		goto done;
 	}
 
-	if (sme->crypto.psk &&
-	    profile->use_fwsup != BRCMF_PROFILE_FWSUP_SAE) {
-		if (WARN_ON(profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE)) {
-			err = -EINVAL;
-			goto done;
+	if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FWSUP)) {
+		if (sme->crypto.psk) {
+			if ((profile->use_fwsup != BRCMF_PROFILE_FWSUP_SAE) &&
+				(profile->use_fwsup != BRCMF_PROFILE_FWSUP_PSK)) {
+				if (WARN_ON(profile->use_fwsup !=
+					BRCMF_PROFILE_FWSUP_NONE)) {
+					err = -EINVAL;
+					goto done;
+				}
+				brcmf_dbg(INFO, "using PSK offload\n");
+				profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK;
+			}
+		} else if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_1X) {
+			profile->use_fwsup = BRCMF_PROFILE_FWSUP_NONE;
 		}
-		brcmf_dbg(INFO, "using PSK offload\n");
-		profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK;
-	}
 
-	if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE) {
-		/* enable firmware supplicant for this interface */
-		err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 1);
-		if (err < 0) {
-			bphy_err(drvr, "failed to enable fw supplicant\n");
-			goto done;
+		if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE) {
+			/* enable firmware supplicant for this interface */
+			err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 1);
+			if (err < 0) {
+				bphy_err(drvr, "failed to enable fw supplicant\n");
+				goto done;
+			}
+		} else {
+			err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 0);
 		}
-	}
 
-	if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_PSK)
-		err = brcmf_set_pmk(ifp, sme->crypto.psk,
-				    BRCMF_WSEC_MAX_PSK_LEN);
-	else if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_SAE) {
-		/* clean up user-space RSNE */
-		err = brcmf_fil_iovar_data_set(ifp, "wpaie", NULL, 0);
-		if (err) {
-			bphy_err(drvr, "failed to clean up user-space RSNE\n");
-			goto done;
-		}
-		err = brcmf_fwvid_set_sae_password(ifp, &sme->crypto);
-		if (!err && sme->crypto.psk)
+		if ((profile->use_fwsup == BRCMF_PROFILE_FWSUP_PSK) &&
+			sme->crypto.psk) {
 			err = brcmf_set_pmk(ifp, sme->crypto.psk,
 					    BRCMF_WSEC_MAX_PSK_LEN);
+		} else if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_SAE) {
+			/* clean up user-space RSNE */
+			err = brcmf_fil_iovar_data_set(ifp, "wpaie", NULL, 0);
+			if (err) {
+				bphy_err(drvr, "failed to clean up user-space RSNE\n");
+				goto done;
+			}
+			err = brcmf_fwvid_set_sae_password(ifp, &sme->crypto);
+			if (!err && sme->crypto.psk)
+				err = brcmf_set_pmk(ifp, sme->crypto.psk,
+						    BRCMF_WSEC_MAX_PSK_LEN);
+		}
+		if (err)
+			goto done;
 	}
-	if (err)
-		goto done;
-
 	/* Join with specific BSSID and cached SSID
 	 * If SSID is zero join based on BSSID only
 	 */
@@ -3299,7 +3320,7 @@ brcmf_cfg80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *ndev,
 		brcmf_dbg(INFO, "Do not enable power save for P2P clients\n");
 		pm = PM_OFF;
 	}
-	brcmf_dbg(INFO, "power save %s\n", (pm ? "enabled" : "disabled"));
+	brcmf_info("power save %s\n", (pm ? "enabled" : "disabled"));
 
 	err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_PM, pm);
 	if (err) {
@@ -3309,6 +3330,7 @@ brcmf_cfg80211_set_power_mgmt(struct wiphy *wiphy, struct net_device *ndev,
 			bphy_err(drvr, "error (%d)\n", err);
 	}
 
+	timeout = 2000; /* 2000ms - the maximum */
 	err = brcmf_fil_iovar_int_set(ifp, "pm2_sleep_ret",
 				min_t(u32, timeout, BRCMF_PS_MAX_TIMEOUT_MS));
 	if (err)
@@ -5517,9 +5539,12 @@ brcmf_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
 	s32 ie_len;
 	struct brcmf_fil_action_frame_le *action_frame;
 	struct brcmf_fil_af_params_le *af_params;
-	bool ack;
+	bool ack = false;
 	s32 chan_nr;
 	u32 freq;
+	struct brcmf_mf_params_le *mf_params;
+	u32 mf_params_len;
+	s32 timeout;
 
 	brcmf_dbg(TRACE, "Enter\n");
 
@@ -5599,6 +5624,71 @@ brcmf_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
 		cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, ack,
 					GFP_KERNEL);
 		kfree(af_params);
+	} else if (ieee80211_is_auth(mgmt->frame_control)) {
+		reinit_completion(&vif->mgmt_tx);
+		clear_bit(BRCMF_MGMT_TX_ACK, &vif->mgmt_tx_status);
+		clear_bit(BRCMF_MGMT_TX_NOACK, &vif->mgmt_tx_status);
+		clear_bit(BRCMF_MGMT_TX_OFF_CHAN_COMPLETED,
+			  &vif->mgmt_tx_status);
+
+		mf_params_len = offsetof(struct brcmf_mf_params_le, data) +
+				(len - DOT11_MGMT_HDR_LEN);
+		mf_params = kzalloc(mf_params_len, GFP_KERNEL);
+		if (!mf_params) {
+			err = -ENOMEM;
+			goto exit;
+		}
+
+		mf_params->dwell_time = cpu_to_le32(MGMT_AUTH_FRAME_DWELL_TIME);
+		mf_params->len = cpu_to_le16(len - DOT11_MGMT_HDR_LEN);
+		mf_params->frame_control = mgmt->frame_control;
+
+		if (chan)
+			freq = chan->center_freq;
+		else
+			brcmf_fil_cmd_int_get(vif->ifp, BRCMF_C_GET_CHANNEL,
+					      &freq);
+		chan_nr = ieee80211_frequency_to_channel(freq);
+		mf_params->channel = cpu_to_le32(chan_nr);
+		memcpy(&mf_params->da[0], &mgmt->da[0], ETH_ALEN);
+		memcpy(&mf_params->bssid[0], &mgmt->bssid[0], ETH_ALEN);
+		mf_params->packet_id = cpu_to_le32(*cookie);
+		memcpy(mf_params->data, &buf[DOT11_MGMT_HDR_LEN],
+		       le16_to_cpu(mf_params->len));
+
+		brcmf_dbg(TRACE, "Auth frame, cookie=%d, fc=%04x, len=%d, channel=%d\n",
+			  le32_to_cpu(mf_params->packet_id),
+			  le16_to_cpu(mf_params->frame_control),
+			  le16_to_cpu(mf_params->len),
+			  le32_to_cpu(mf_params->channel));
+
+		vif->mgmt_tx_id = le32_to_cpu(mf_params->packet_id);
+		set_bit(BRCMF_MGMT_TX_SEND_FRAME, &vif->mgmt_tx_status);
+
+		err = brcmf_fil_bsscfg_data_set(vif->ifp, "mgmt_frame",
+						mf_params, mf_params_len);
+		if (err) {
+			bphy_err(drvr, "Failed to send Auth frame: err=%d\n",
+				 err);
+			goto tx_status;
+		}
+
+		timeout =
+			wait_for_completion_timeout(&vif->mgmt_tx,
+						    MGMT_AUTH_FRAME_WAIT_TIME);
+		if (test_bit(BRCMF_MGMT_TX_ACK, &vif->mgmt_tx_status)) {
+			brcmf_dbg(TRACE, "TX Auth frame operation is success\n");
+			ack = true;
+		} else {
+			bphy_err(drvr, "TX Auth frame operation is failed: status=%ld)\n",
+				 vif->mgmt_tx_status);
+		}
+
+tx_status:
+		cfg80211_mgmt_tx_status(wdev, *cookie, buf, len, ack,
+					GFP_KERNEL);
+		kfree(mf_params);
+
 	} else {
 		brcmf_dbg(TRACE, "Unhandled, fc=%04x!!\n", mgmt->frame_control);
 		brcmf_dbg_hex_dump(true, buf, len, "payload, len=%zu\n", len);
@@ -5922,6 +6012,40 @@ static int brcmf_cfg80211_del_pmk(struct wiphy *wiphy, struct net_device *dev,
 	return brcmf_set_pmk(ifp, NULL, 0);
 }
 
+static int
+brcmf_cfg80211_external_auth(struct wiphy *wiphy, struct net_device *dev,
+			     struct cfg80211_external_auth_params *params)
+{
+	struct brcmf_if *ifp;
+	struct brcmf_pub *drvr;
+	struct brcmf_auth_req_status_le auth_status;
+	int ret = 0;
+
+	brcmf_dbg(TRACE, "Enter\n");
+
+	ifp = netdev_priv(dev);
+	drvr = ifp->drvr;
+	if (params->status == WLAN_STATUS_SUCCESS) {
+		auth_status.flags = cpu_to_le16(BRCMF_EXTAUTH_SUCCESS);
+	} else {
+		bphy_err(drvr, "External authentication failed: status=%d\n",
+			 params->status);
+		auth_status.flags = cpu_to_le16(BRCMF_EXTAUTH_FAIL);
+	}
+
+	memcpy(auth_status.peer_mac, params->bssid, ETH_ALEN);
+	auth_status.ssid_len = cpu_to_le32(min_t(u8, params->ssid.ssid_len,
+						 IEEE80211_MAX_SSID_LEN));
+	memcpy(auth_status.ssid, params->ssid.ssid, auth_status.ssid_len);
+
+	ret = brcmf_fil_iovar_data_set(ifp, "auth_status", &auth_status,
+				       sizeof(auth_status));
+	if (ret < 0)
+		bphy_err(drvr, "auth_status iovar failed: ret=%d\n", ret);
+
+	return ret;
+}
+
 static struct cfg80211_ops brcmf_cfg80211_ops = {
 	.add_virtual_intf = brcmf_cfg80211_add_iface,
 	.del_virtual_intf = brcmf_cfg80211_del_iface,
@@ -5969,6 +6093,7 @@ static struct cfg80211_ops brcmf_cfg80211_ops = {
 	.update_connect_params = brcmf_cfg80211_update_conn_params,
 	.set_pmk = brcmf_cfg80211_set_pmk,
 	.del_pmk = brcmf_cfg80211_del_pmk,
+	.external_auth = brcmf_cfg80211_external_auth,
 };
 
 struct cfg80211_ops *brcmf_cfg80211_get_ops(struct brcmf_mp_device *settings)
@@ -6015,6 +6140,7 @@ struct brcmf_cfg80211_vif *brcmf_alloc_vif(struct brcmf_cfg80211_info *cfg,
 		vif->mbss = mbss;
 	}
 
+	init_completion(&vif->mgmt_tx);
 	list_add_tail(&vif->list, &cfg->vif_list);
 	return vif;
 }
@@ -6706,6 +6832,122 @@ static s32 brcmf_notify_vif_event(struct brcmf_if *ifp,
 	return -EINVAL;
 }
 
+static s32
+brcmf_notify_ext_auth_request(struct brcmf_if *ifp,
+			      const struct brcmf_event_msg *e, void *data)
+{
+	struct brcmf_pub *drvr = ifp->drvr;
+	struct cfg80211_external_auth_params params;
+	struct brcmf_auth_req_status_le *auth_req =
+		(struct brcmf_auth_req_status_le *)data;
+	s32 err = 0;
+
+	brcmf_dbg(INFO, "Enter: event %s (%d) received\n",
+		  brcmf_fweh_event_name(e->event_code), e->event_code);
+
+	if (e->datalen < sizeof(*auth_req)) {
+		bphy_err(drvr, "Event %s (%d) data too small. Ignore\n",
+			 brcmf_fweh_event_name(e->event_code), e->event_code);
+		return -EINVAL;
+	}
+
+	memset(&params, 0, sizeof(params));
+	params.action = NL80211_EXTERNAL_AUTH_START;
+	params.key_mgmt_suite = ntohl(WLAN_AKM_SUITE_SAE);
+	params.status = WLAN_STATUS_SUCCESS;
+	params.ssid.ssid_len = min_t(u32, 32, le32_to_cpu(auth_req->ssid_len));
+	memcpy(params.ssid.ssid, auth_req->ssid, params.ssid.ssid_len);
+	memcpy(params.bssid, auth_req->peer_mac, ETH_ALEN);
+
+	err = cfg80211_external_auth_request(ifp->ndev, &params, GFP_ATOMIC);
+	if (err)
+		bphy_err(drvr, "Ext Auth request to supplicant failed (%d)\n",
+			 err);
+
+	return err;
+}
+
+static s32
+brcmf_notify_auth_frame_rx(struct brcmf_if *ifp,
+			   const struct brcmf_event_msg *e, void *data)
+{
+	struct brcmf_pub *drvr = ifp->drvr;
+	struct brcmf_cfg80211_info *cfg = drvr->config;
+	struct wireless_dev *wdev;
+	u32 mgmt_frame_len = e->datalen - sizeof(struct brcmf_rx_mgmt_data);
+	struct brcmf_rx_mgmt_data *rxframe = (struct brcmf_rx_mgmt_data *)data;
+	u8 *frame = (u8 *)(rxframe + 1);
+	struct brcmu_chan ch;
+	struct ieee80211_mgmt *mgmt_frame;
+	s32 freq;
+
+	brcmf_dbg(INFO, "Enter: event %s (%d) received\n",
+		  brcmf_fweh_event_name(e->event_code), e->event_code);
+
+	if (e->datalen < sizeof(*rxframe)) {
+		bphy_err(drvr, "Event %s (%d) data too small. Ignore\n",
+			 brcmf_fweh_event_name(e->event_code), e->event_code);
+		return -EINVAL;
+	}
+
+	wdev = &ifp->vif->wdev;
+	WARN_ON(!wdev);
+
+	ch.chspec = be16_to_cpu(rxframe->chanspec);
+	cfg->d11inf.decchspec(&ch);
+
+	mgmt_frame = kzalloc(mgmt_frame_len, GFP_KERNEL);
+	if (!mgmt_frame)
+		return -ENOMEM;
+
+	mgmt_frame->frame_control = cpu_to_le16(IEEE80211_STYPE_AUTH);
+	memcpy(mgmt_frame->da, ifp->mac_addr, ETH_ALEN);
+	memcpy(mgmt_frame->sa, e->addr, ETH_ALEN);
+	brcmf_fil_cmd_data_get(ifp, BRCMF_C_GET_BSSID, mgmt_frame->bssid,
+			       ETH_ALEN);
+	frame += offsetof(struct ieee80211_mgmt, u);
+	memcpy(&mgmt_frame->u, frame,
+	       mgmt_frame_len - offsetof(struct ieee80211_mgmt, u));
+
+	freq = ieee80211_channel_to_frequency(ch.control_ch_num,
+					      ch.band == BRCMU_CHAN_BAND_2G ?
+					      NL80211_BAND_2GHZ :
+					      NL80211_BAND_5GHZ);
+
+	cfg80211_rx_mgmt(wdev, freq, 0, (u8 *)mgmt_frame, mgmt_frame_len,
+			 NL80211_RXMGMT_FLAG_EXTERNAL_AUTH);
+	kfree(mgmt_frame);
+	return 0;
+}
+
+static s32
+brcmf_notify_mgmt_tx_status(struct brcmf_if *ifp,
+			    const struct brcmf_event_msg *e, void *data)
+{
+	struct brcmf_cfg80211_vif *vif = ifp->vif;
+	u32 *packet_id = (u32 *)data;
+
+	brcmf_dbg(INFO, "Enter: event %s (%d), status=%d\n",
+		  brcmf_fweh_event_name(e->event_code), e->event_code,
+		  e->status);
+
+	if (!test_bit(BRCMF_MGMT_TX_SEND_FRAME, &vif->mgmt_tx_status) ||
+	    (*packet_id != vif->mgmt_tx_id))
+		return 0;
+
+	if (e->event_code == BRCMF_E_MGMT_FRAME_TXSTATUS) {
+		if (e->status == BRCMF_E_STATUS_SUCCESS)
+			set_bit(BRCMF_MGMT_TX_ACK, &vif->mgmt_tx_status);
+		else
+			set_bit(BRCMF_MGMT_TX_NOACK, &vif->mgmt_tx_status);
+	} else {
+		set_bit(BRCMF_MGMT_TX_OFF_CHAN_COMPLETED, &vif->mgmt_tx_status);
+	}
+
+	complete(&vif->mgmt_tx);
+	return 0;
+}
+
 static void brcmf_init_conf(struct brcmf_cfg80211_conf *conf)
 {
 	conf->frag_threshold = (u32)-1;
@@ -6750,7 +6992,16 @@ static void brcmf_register_event_handlers(struct brcmf_cfg80211_info *cfg)
 			    brcmf_p2p_notify_action_tx_complete);
 	brcmf_fweh_register(cfg->pub, BRCMF_E_PSK_SUP,
 			    brcmf_notify_connect_status);
-	brcmf_fweh_register(cfg->pub, BRCMF_E_RSSI, brcmf_notify_rssi);
+	brcmf_fweh_register(cfg->pub, BRCMF_E_RSSI,
+				brcmf_notify_rssi);
+	brcmf_fweh_register(cfg->pub, BRCMF_E_EXT_AUTH_REQ,
+			    brcmf_notify_ext_auth_request);
+	brcmf_fweh_register(cfg->pub, BRCMF_E_EXT_AUTH_FRAME_RX,
+			    brcmf_notify_auth_frame_rx);
+	brcmf_fweh_register(cfg->pub, BRCMF_E_MGMT_FRAME_TXSTATUS,
+			    brcmf_notify_mgmt_tx_status);
+	brcmf_fweh_register(cfg->pub, BRCMF_E_MGMT_FRAME_OFF_CHAN_COMPLETE,
+			    brcmf_notify_mgmt_tx_status);
 }
 
 static void brcmf_deinit_priv_mem(struct brcmf_cfg80211_info *cfg)
@@ -7337,6 +7588,7 @@ brcmf_txrx_stypes[NUM_NL80211_IFTYPES] = {
 	[NL80211_IFTYPE_STATION] = {
 		.tx = 0xffff,
 		.rx = BIT(IEEE80211_STYPE_ACTION >> 4) |
+		      BIT(IEEE80211_STYPE_AUTH >> 4) |
 		      BIT(IEEE80211_STYPE_PROBE_REQ >> 4)
 	},
 	[NL80211_IFTYPE_P2P_CLIENT] = {
@@ -7642,6 +7894,8 @@ static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp)
 			wiphy_ext_feature_set(wiphy,
 					      NL80211_EXT_FEATURE_SAE_OFFLOAD_AP);
 	}
+	if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SAE_EXT))
+		wiphy->features |= NL80211_FEATURE_SAE;
 	wiphy->mgmt_stypes = brcmf_txrx_stypes;
 	wiphy->max_remain_on_channel_duration = 5000;
 	if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PNO)) {
@@ -8183,31 +8437,45 @@ static void brcmf_cfg80211_reg_notifier(struct wiphy *wiphy,
 	struct brcmf_if *ifp = brcmf_get_ifp(cfg->pub, 0);
 	struct brcmf_pub *drvr = cfg->pub;
 	struct brcmf_fil_country_le ccreq;
+	char *alpha2;
 	s32 err;
 	int i;
 
-	/* The country code gets set to "00" by default at boot, ignore */
-	if (req->alpha2[0] == '0' && req->alpha2[1] == '0')
+	err = brcmf_fil_iovar_data_get(ifp, "country", &ccreq, sizeof(ccreq));
+	if (err) {
+		bphy_err(drvr, "Country code iovar returned err = %d\n", err);
 		return;
+	}
+
+	/* The country code gets set to "00" by default at boot - substitute
+	 * any saved ccode from the nvram file unless there is a valid code
+	 * already set.
+	 */
+	alpha2 = req->alpha2;
+	if (alpha2[0] == '0' && alpha2[1] == '0') {
+		extern char saved_ccode[2];
+
+		if ((isupper(ccreq.country_abbrev[0]) &&
+		     isupper(ccreq.country_abbrev[1])) ||
+		    !saved_ccode[0])
+			return;
+		alpha2 = saved_ccode;
+		pr_debug("brcmfmac: substituting saved ccode %c%c\n",
+			 alpha2[0], alpha2[1]);
+	}
 
 	/* ignore non-ISO3166 country codes */
 	for (i = 0; i < 2; i++)
-		if (req->alpha2[i] < 'A' || req->alpha2[i] > 'Z') {
+		if (alpha2[i] < 'A' || alpha2[i] > 'Z') {
 			bphy_err(drvr, "not an ISO3166 code (0x%02x 0x%02x)\n",
-				 req->alpha2[0], req->alpha2[1]);
+				 alpha2[0], alpha2[1]);
 			return;
 		}
 
 	brcmf_dbg(TRACE, "Enter: initiator=%d, alpha=%c%c\n", req->initiator,
-		  req->alpha2[0], req->alpha2[1]);
-
-	err = brcmf_fil_iovar_data_get(ifp, "country", &ccreq, sizeof(ccreq));
-	if (err) {
-		bphy_err(drvr, "Country code iovar returned err = %d\n", err);
-		return;
-	}
+		  alpha2[0], alpha2[1]);
 
-	err = brcmf_translate_country_code(ifp->drvr, req->alpha2, &ccreq);
+	err = brcmf_translate_country_code(ifp->drvr, alpha2, &ccreq);
 	if (err)
 		return;
 
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
index dc3a6a537507..f6573da57dc0 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
@@ -178,6 +178,21 @@ enum brcmf_vif_status {
 	BRCMF_VIF_STATUS_ASSOC_SUCCESS,
 };
 
+/**
+ * enum brcmf_mgmt_tx_status - mgmt frame tx status
+ *
+ * @BRCMF_MGMT_TX_ACK: mgmt frame acked
+ * @BRCMF_MGMT_TX_NOACK: mgmt frame not acked
+ * @BRCMF_MGMT_TX_OFF_CHAN_COMPLETED: off-channel complete
+ * @BRCMF_MGMT_TX_SEND_FRAME: mgmt frame tx is in progres
+ */
+enum brcmf_mgmt_tx_status {
+	BRCMF_MGMT_TX_ACK,
+	BRCMF_MGMT_TX_NOACK,
+	BRCMF_MGMT_TX_OFF_CHAN_COMPLETED,
+	BRCMF_MGMT_TX_SEND_FRAME
+};
+
 /**
  * struct vif_saved_ie - holds saved IEs for a virtual interface.
  *
@@ -224,6 +239,9 @@ struct brcmf_cfg80211_vif {
 	unsigned long sme_state;
 	struct vif_saved_ie saved_ie;
 	struct list_head list;
+	struct completion mgmt_tx;
+	unsigned long mgmt_tx_status;
+	u32 mgmt_tx_id;
 	u16 mgmt_rx_reg;
 	bool mbss;
 	int is_11d;
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c
index b24faae35873..85c00b8dae42 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c
@@ -20,6 +20,8 @@
 #include "of.h"
 #include "firmware.h"
 #include "chip.h"
+#include "fweh.h"
+#include <brcm_hw_ids.h>
 
 MODULE_AUTHOR("Broadcom Corporation");
 MODULE_DESCRIPTION("Broadcom 802.11 wireless LAN fullmac driver.");
@@ -274,6 +276,8 @@ int brcmf_c_preinit_dcmds(struct brcmf_if *ifp)
 	char *clmver;
 	char *ptr;
 	s32 err;
+	struct eventmsgs_ext *eventmask_msg = NULL;
+	u8 msglen;
 
 	if (is_valid_ether_addr(ifp->mac_addr)) {
 		/* set mac address */
@@ -433,6 +437,41 @@ int brcmf_c_preinit_dcmds(struct brcmf_if *ifp)
 		goto done;
 	}
 
+	/* Enable event_msg_ext specific to 43012 chip */
+	if (bus->chip == CY_CC_43012_CHIP_ID) {
+		/* Program event_msg_ext to support event larger than 128 */
+		msglen = (roundup(fweh->num_event_codes, NBBY) / NBBY) +
+				  EVENTMSGS_EXT_STRUCT_SIZE;
+		/* Allocate buffer for eventmask_msg */
+		eventmask_msg = kzalloc(msglen, GFP_KERNEL);
+		if (!eventmask_msg) {
+			err = -ENOMEM;
+			goto done;
+		}
+
+		/* Read the current programmed event_msgs_ext */
+		eventmask_msg->ver = EVENTMSGS_VER;
+		eventmask_msg->len = roundup(fweh->num_event_codes, NBBY) / NBBY;
+		err = brcmf_fil_iovar_data_get(ifp, "event_msgs_ext",
+					       eventmask_msg,
+					       msglen);
+
+		/* Enable ULP event */
+		brcmf_dbg(EVENT, "enable event ULP\n");
+		setbit(eventmask_msg->mask, BRCMF_E_ULP);
+
+		/* Write updated Event mask */
+		eventmask_msg->ver = EVENTMSGS_VER;
+		eventmask_msg->command = EVENTMSGS_SET_MASK;
+		eventmask_msg->len = (roundup(fweh->num_event_codes, NBBY) / NBBY);
+
+		err = brcmf_fil_iovar_data_set(ifp, "event_msgs_ext",
+					       eventmask_msg, msglen);
+		if (err) {
+			brcmf_err("Set event_msgs_ext error (%d)\n", err);
+			goto done;
+		}
+	}
 	/* Setup default scan channel time */
 	err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_SCAN_CHANNEL_TIME,
 				    BRCMF_DEFAULT_SCAN_CHANNEL_TIME);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
index 20ab9b1eea28..f4011f7502e1 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
@@ -1322,7 +1322,7 @@ int brcmf_alloc(struct device *dev, struct brcmf_mp_device *settings)
 	return 0;
 }
 
-int brcmf_attach(struct device *dev)
+int brcmf_attach(struct device *dev, bool start_bus)
 {
 	struct brcmf_bus *bus_if = dev_get_drvdata(dev);
 	struct brcmf_pub *drvr = bus_if->drvr;
@@ -1363,10 +1363,13 @@ int brcmf_attach(struct device *dev)
 	brcmf_fweh_register(drvr, BRCMF_E_PSM_WATCHDOG,
 			    brcmf_psm_watchdog_notify);
 
-	ret = brcmf_bus_started(drvr, drvr->ops);
-	if (ret != 0) {
-		bphy_err(drvr, "dongle is not responding: err=%d\n", ret);
-		goto fail;
+	if (start_bus) {
+		ret = brcmf_bus_started(drvr, drvr->ops);
+		if (ret != 0) {
+			bphy_err(drvr, "dongle is not responding: err=%d\n",
+				 ret);
+			goto fail;
+		}
 	}
 
 	return 0;
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h
index 9bb5f709d41a..f3804d6f8768 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h
@@ -29,6 +29,7 @@
 #define BRCMF_MSGBUF_VAL	0x00040000
 #define BRCMF_PCIE_VAL		0x00080000
 #define BRCMF_FWCON_VAL		0x00100000
+#define BRCMF_ULP_VAL		0x00200000
 
 /* set default print format */
 #undef pr_fmt
@@ -67,7 +68,12 @@ void __brcmf_err(struct brcmf_bus *bus, const char *func, const char *fmt, ...);
 #if defined(DEBUG) || defined(CONFIG_BRCM_TRACING)
 
 /* For debug/tracing purposes treat info messages as errors */
-#define brcmf_info brcmf_err
+// #define brcmf_info brcmf_err
+
+#define brcmf_info(fmt, ...)						\
+	do {								\
+		pr_info("%s: " fmt, __func__, ##__VA_ARGS__);		\
+	} while (0)
 
 __printf(3, 4)
 void __brcmf_dbg(u32 level, const char *func, const char *fmt, ...);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
index 0d9ae197fa1e..e8ca74137228 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
@@ -44,6 +44,8 @@ static const struct brcmf_feat_fwcap brcmf_fwcap_map[] = {
 	{ BRCMF_FEAT_DOT11H, "802.11h" },
 	{ BRCMF_FEAT_SAE, "sae" },
 	{ BRCMF_FEAT_FWAUTH, "idauth" },
+	{ BRCMF_FEAT_SAE_EXT, "sae_ext" },
+	{ BRCMF_FEAT_SAE_EXT, "extsae" },
 };
 
 #ifdef DEBUG
@@ -240,7 +242,14 @@ static void brcmf_feat_firmware_capabilities(struct brcmf_if *ifp)
 	brcmf_dbg(INFO, "[ %s]\n", caps);
 
 	for (i = 0; i < ARRAY_SIZE(brcmf_fwcap_map); i++) {
-		if (strnstr(caps, brcmf_fwcap_map[i].fwcap_id, sizeof(caps))) {
+		const char *match = strnstr(caps, brcmf_fwcap_map[i].fwcap_id, sizeof(caps));
+		if (match) {
+			char endc;
+			if (match != caps && match[-1] != ' ')
+				continue;
+			endc = match[strlen(brcmf_fwcap_map[i].fwcap_id)];
+			if (endc != '\0' && endc != ' ')
+				continue;
 			id = brcmf_fwcap_map[i].feature;
 			brcmf_dbg(INFO, "enabling feature: %s\n",
 				  brcmf_feat_names[id]);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
index 7f4f0b3e4a7b..e2fc7c9dffdb 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
@@ -30,6 +30,7 @@
  * SAE: simultaneous authentication of equals
  * FWAUTH: Firmware authenticator
  * DUMP_OBSS: Firmware has capable to dump obss info to support ACS
+ * SAE_EXT: SAE be handled by userspace supplicant
  * SCAN_V2: Version 2 scan params
  */
 #define BRCMF_FEAT_LIST \
@@ -55,6 +56,7 @@
 	BRCMF_FEAT_DEF(SAE) \
 	BRCMF_FEAT_DEF(FWAUTH) \
 	BRCMF_FEAT_DEF(DUMP_OBSS) \
+	BRCMF_FEAT_DEF(SAE_EXT) \
 	BRCMF_FEAT_DEF(SCAN_V2) \
 	BRCMF_FEAT_DEF(PMKID_V2) \
 	BRCMF_FEAT_DEF(PMKID_V3)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c
index 83f8ed7d00f9..d6f267932e07 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/firmware.c
@@ -10,6 +10,7 @@
 #include <linux/firmware.h>
 #include <linux/module.h>
 #include <linux/bcm47xx_nvram.h>
+#include <linux/ctype.h>
 
 #include "debug.h"
 #include "firmware.h"
@@ -32,6 +33,8 @@ enum nvram_parser_state {
 	END
 };
 
+char saved_ccode[2] = {};
+
 /**
  * struct nvram_parser - internal info for parser.
  *
@@ -562,11 +565,27 @@ static int brcmf_fw_request_nvram_done(const struct firmware *fw, void *ctx)
 			goto fail;
 	}
 
-	if (data)
+	if (data) {
+		char *ccode = strnstr((char *)data, "ccode=", data_len);
+		/* Ensure this is a whole token */
+		if (ccode && ((void *)ccode == (void *)data || isspace(ccode[-1]))) {
+			/* Comment out the line */
+			ccode[0] = '#';
+			ccode += 6;
+			if (isupper(ccode[0]) && isupper(ccode[1]) &&
+			    isspace(ccode[2])) {
+				pr_debug("brcmfmac: intercepting ccode=%c%c\n",
+					 ccode[0], ccode[1]);
+				saved_ccode[0] = ccode[0];
+				saved_ccode[1] = ccode[1];
+			}
+		};
+
 		nvram = brcmf_fw_nvram_strip(data, data_len, &nvram_length,
 					     fwctx->req->domain_nr,
 					     fwctx->req->bus_nr,
 					     fwctx->dev);
+	}
 
 	if (free_bcm47xx_nvram)
 		bcm47xx_nvram_release_contents(data);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c
index f0b6a7607f16..ef491a290f6c 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c
@@ -432,6 +432,8 @@ int brcmf_fweh_activate_events(struct brcmf_if *ifp)
 {
 	struct brcmf_fweh_info *fweh = ifp->drvr->fweh;
 	enum brcmf_fweh_event_code code;
+	struct eventmsgs_ext *eventmask_msg;
+	u32 msglen;
 	int i, err;
 
 	memset(fweh->event_mask, 0, fweh->event_mask_len);
@@ -444,15 +446,32 @@ int brcmf_fweh_activate_events(struct brcmf_if *ifp)
 		}
 	}
 
+	msglen = EVENTMSGS_EXT_STRUCT_SIZE + fweh->event_mask_len;
+	eventmask_msg = kzalloc(msglen, GFP_KERNEL);
+	if (!eventmask_msg)
+		return -ENOMEM;
+
 	/* want to handle IF event as well */
 	brcmf_dbg(EVENT, "enable event IF\n");
 	setbit(fweh->event_mask, BRCMF_E_IF);
 
+	eventmask_msg->ver = EVENTMSGS_VER;
+	eventmask_msg->command = EVENTMSGS_SET_MASK;
+	eventmask_msg->len = fweh->event_mask_len;
+	memcpy(eventmask_msg->mask, fweh->event_mask, fweh->event_mask_len);
+
+	err = brcmf_fil_iovar_data_set(ifp, "event_msgs_ext", eventmask_msg,
+				       msglen);
+	if (!err)
+		goto end;
+
 	err = brcmf_fil_iovar_data_set(ifp, "event_msgs", fweh->event_mask,
 				       fweh->event_mask_len);
 	if (err)
 		bphy_err(fweh->drvr, "Set event_msgs error (%d)\n", err);
 
+end:
+	kfree(eventmask_msg);
 	return err;
 }
 
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h
index eed439b84010..d29442c47951 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h
@@ -94,7 +94,12 @@ struct brcmf_cfg80211_info;
 	BRCMF_ENUM_DEF(FIFO_CREDIT_MAP, 74) \
 	BRCMF_ENUM_DEF(ACTION_FRAME_RX, 75) \
 	BRCMF_ENUM_DEF(TDLS_PEER_EVENT, 92) \
-	BRCMF_ENUM_DEF(BCMC_CREDIT_SUPPORT, 127)
+	BRCMF_ENUM_DEF(BCMC_CREDIT_SUPPORT, 127) \
+	BRCMF_ENUM_DEF(ULP, 146) \
+	BRCMF_ENUM_DEF(EXT_AUTH_REQ, 187) \
+	BRCMF_ENUM_DEF(EXT_AUTH_FRAME_RX, 188) \
+	BRCMF_ENUM_DEF(MGMT_FRAME_TXSTATUS, 189) \
+	BRCMF_ENUM_DEF(MGMT_FRAME_OFF_CHAN_COMPLETE, 190)
 
 #define BRCMF_ENUM_DEF(id, val) \
 	BRCMF_E_##id = (val),
@@ -280,6 +285,28 @@ struct brcmf_if_event {
 	u8 role;
 };
 
+enum event_msgs_ext_command {
+	EVENTMSGS_NONE		=	0,
+	EVENTMSGS_SET_BIT	=	1,
+	EVENTMSGS_RESET_BIT	=	2,
+	EVENTMSGS_SET_MASK	=	3
+};
+
+#define EVENTMSGS_VER 1
+#define EVENTMSGS_EXT_STRUCT_SIZE	offsetof(struct eventmsgs_ext, mask[0])
+
+/* len-	for SET it would be mask size from the application to the firmware */
+/*		for GET it would be actual firmware mask size */
+/* maxgetsize -	is only used for GET. indicate max mask size that the */
+/*				application can read from the firmware */
+struct eventmsgs_ext {
+	u8	ver;
+	u8	command;
+	u8	len;
+	u8	maxgetsize;
+	u8	mask[1];
+};
+
 typedef int (*brcmf_fweh_handler_t)(struct brcmf_if *ifp,
 				    const struct brcmf_event_msg *evtmsg,
 				    void *data);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
index e74a23e11830..86539bef6b88 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
@@ -178,6 +178,11 @@
 #define BRCMF_PMKSA_VER_3		3
 #define BRCMF_PMKSA_NO_EXPIRY		0xffffffff
 
+#define BRCMF_EXTAUTH_START	1
+#define BRCMF_EXTAUTH_ABORT	2
+#define BRCMF_EXTAUTH_FAIL	3
+#define BRCMF_EXTAUTH_SUCCESS	4
+
 /* MAX_CHUNK_LEN is the maximum length for data passing to firmware in each
  * ioctl. It is relatively small because firmware has small maximum size input
  * playload restriction for ioctls.
@@ -598,6 +603,47 @@ struct brcmf_wsec_sae_pwd_le {
 	u8 key[BRCMF_WSEC_MAX_SAE_PASSWORD_LEN];
 };
 
+/**
+ * struct brcmf_auth_req_status_le - external auth request and status update
+ *
+ * @flags: flags for external auth status
+ * @peer_mac: peer MAC address
+ * @ssid_len: length of ssid
+ * @ssid: ssid characters
+ */
+struct brcmf_auth_req_status_le {
+	__le16 flags;
+	u8 peer_mac[ETH_ALEN];
+	__le32 ssid_len;
+	u8 ssid[IEEE80211_MAX_SSID_LEN];
+	u8 pmkid[WLAN_PMKID_LEN];
+};
+
+/**
+ * struct brcmf_mf_params_le - management frame parameters for mgmt_frame iovar
+ *
+ * @version: version of the iovar
+ * @dwell_time: dwell duration in ms
+ * @len: length of frame data
+ * @frame_control: frame control
+ * @channel: channel
+ * @da: peer MAC address
+ * @bssid: BSS network identifier
+ * @packet_id: packet identifier
+ * @data: frame data
+ */
+struct brcmf_mf_params_le {
+	__le32 version;
+	__le32 dwell_time;
+	__le16 len;
+	__le16 frame_control;
+	__le16 channel;
+	u8 da[ETH_ALEN];
+	u8 bssid[ETH_ALEN];
+	__le32 packet_id;
+	u8 data[1];
+};
+
 /* Used to get specific STA parameters */
 struct brcmf_scb_val_le {
 	__le32 val;
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c
index ef08b4163c93..9752a8764986 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c
@@ -1281,6 +1281,10 @@ static s32 brcmf_p2p_abort_action_frame(struct brcmf_cfg80211_info *cfg)
 	brcmf_dbg(TRACE, "Enter\n");
 
 	vif = p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif;
+
+	if (!vif)
+		vif = p2p->bss_idx[P2PAPI_BSSCFG_PRIMARY].vif;
+
 	err = brcmf_fil_bsscfg_data_set(vif->ifp, "actframe_abort", &int_val,
 					sizeof(s32));
 	if (err)
@@ -1817,6 +1821,7 @@ bool brcmf_p2p_send_action_frame(struct brcmf_if *ifp,
 	/* validate channel and p2p ies */
 	if (config_af_params.search_channel &&
 	    IS_P2P_SOCIAL_CHANNEL(le32_to_cpu(af_params->channel)) &&
+	    p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif &&
 	    p2p->bss_idx[P2PAPI_BSSCFG_DEVICE].vif->saved_ie.probe_req_ie_len) {
 		afx_hdl = &p2p->afx_hdl;
 		afx_hdl->peer_listen_chan = le32_to_cpu(af_params->channel);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index 5dee54819fbd..4c38f3eb9e7a 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -2207,7 +2207,7 @@ static void brcmf_pcie_setup(struct device *dev, int ret,
 
 	init_waitqueue_head(&devinfo->mbdata_resp_wait);
 
-	ret = brcmf_attach(&devinfo->pdev->dev);
+	ret = brcmf_attach(&devinfo->pdev->dev, true);
 	if (ret)
 		goto fail;
 
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c
index 7b936668c1b6..51e32540fe8c 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c
@@ -35,9 +35,12 @@
 #include "core.h"
 #include "common.h"
 #include "bcdc.h"
+#include "debug.h"
+#include "fwil.h"
 
 #define DCMD_RESP_TIMEOUT	msecs_to_jiffies(2500)
 #define CTL_DONE_TIMEOUT	msecs_to_jiffies(2500)
+#define ULP_HUDI_PROC_DONE_TIME	msecs_to_jiffies(2500)
 
 /* watermark expressed in number of words */
 #define DEFAULT_F2_WATERMARK    0x8
@@ -325,7 +328,16 @@ struct rte_console {
 
 #define KSO_WAIT_US 50
 #define MAX_KSO_ATTEMPTS (PMU_MAX_TRANSITION_DLY/KSO_WAIT_US)
-#define BRCMF_SDIO_MAX_ACCESS_ERRORS	5
+#define BRCMF_SDIO_MAX_ACCESS_ERRORS	20
+
+static void brcmf_sdio_firmware_callback(struct device *dev, int err,
+					 struct brcmf_fw_request *fwreq);
+static struct brcmf_fw_request *
+	brcmf_sdio_prepare_fw_request(struct brcmf_sdio *bus);
+static int brcmf_sdio_f2_ready(struct brcmf_sdio *bus);
+static int brcmf_ulp_event_notify(struct brcmf_if *ifp,
+				  const struct brcmf_event_msg *evtmsg,
+				  void *data);
 
 #ifdef DEBUG
 /* Device console log buffer state */
@@ -609,6 +621,7 @@ BRCMF_FW_DEF(4329, "brcmfmac4329-sdio");
 BRCMF_FW_DEF(4330, "brcmfmac4330-sdio");
 BRCMF_FW_DEF(4334, "brcmfmac4334-sdio");
 BRCMF_FW_DEF(43340, "brcmfmac43340-sdio");
+BRCMF_FW_DEF(43341, "brcmfmac43341-sdio");
 BRCMF_FW_DEF(4335, "brcmfmac4335-sdio");
 BRCMF_FW_DEF(43362, "brcmfmac43362-sdio");
 BRCMF_FW_DEF(4339, "brcmfmac4339-sdio");
@@ -618,7 +631,7 @@ BRCMF_FW_CLM_DEF(43430A1, "brcmfmac43430-sdio");
 BRCMF_FW_DEF(43430B0, "brcmfmac43430b0-sdio");
 BRCMF_FW_CLM_DEF(43439, "brcmfmac43439-sdio");
 BRCMF_FW_CLM_DEF(43455, "brcmfmac43455-sdio");
-BRCMF_FW_DEF(43456, "brcmfmac43456-sdio");
+BRCMF_FW_CLM_DEF(43456, "brcmfmac43456-sdio");
 BRCMF_FW_CLM_DEF(4354, "brcmfmac4354-sdio");
 BRCMF_FW_CLM_DEF(4356, "brcmfmac4356-sdio");
 BRCMF_FW_DEF(4359, "brcmfmac4359-sdio");
@@ -641,7 +654,7 @@ static const struct brcmf_firmware_mapping brcmf_sdio_fwnames[] = {
 	BRCMF_FW_ENTRY(BRCM_CC_4330_CHIP_ID, 0xFFFFFFFF, 4330),
 	BRCMF_FW_ENTRY(BRCM_CC_4334_CHIP_ID, 0xFFFFFFFF, 4334),
 	BRCMF_FW_ENTRY(BRCM_CC_43340_CHIP_ID, 0xFFFFFFFF, 43340),
-	BRCMF_FW_ENTRY(BRCM_CC_43341_CHIP_ID, 0xFFFFFFFF, 43340),
+	BRCMF_FW_ENTRY(BRCM_CC_43341_CHIP_ID, 0xFFFFFFFF, 43341),
 	BRCMF_FW_ENTRY(BRCM_CC_4335_CHIP_ID, 0xFFFFFFFF, 4335),
 	BRCMF_FW_ENTRY(BRCM_CC_43362_CHIP_ID, 0xFFFFFFFE, 43362),
 	BRCMF_FW_ENTRY(BRCM_CC_4339_CHIP_ID, 0xFFFFFFFF, 4339),
@@ -1104,7 +1117,7 @@ static void brcmf_sdio_get_console_addr(struct brcmf_sdio *bus)
 }
 #endif /* DEBUG */
 
-static u32 brcmf_sdio_hostmail(struct brcmf_sdio *bus)
+static u32 brcmf_sdio_hostmail(struct brcmf_sdio *bus, u32 *hmbd)
 {
 	struct brcmf_sdio_dev *sdiod = bus->sdiodev;
 	struct brcmf_core *core = bus->sdio_core;
@@ -1193,6 +1206,9 @@ static u32 brcmf_sdio_hostmail(struct brcmf_sdio *bus)
 			 HMB_DATA_FCDATA_MASK | HMB_DATA_VERSION_MASK))
 		brcmf_err("Unknown mailbox data content: 0x%02x\n",
 			  hmb_data);
+	/* Populate hmb_data if argument is passed for DS1 check later */
+	if (hmbd)
+		*hmbd = hmb_data;
 
 	return intstatus;
 }
@@ -2579,6 +2595,182 @@ static int brcmf_sdio_intr_rstatus(struct brcmf_sdio *bus)
 	return ret;
 }
 
+/* This Function is used to retrieve important
+ * details from dongle related to ULP mode Mostly
+ * values/SHM details that will be vary depending
+ * on the firmware branches
+ */
+static void
+brcmf_sdio_ulp_preinit(struct device *dev)
+{
+	struct brcmf_bus *bus_if = dev_get_drvdata(dev);
+	struct brcmf_sdio_dev *sdiodev = bus_if->bus_priv.sdio;
+	struct brcmf_if *ifp = bus_if->drvr->iflist[0];
+
+	brcmf_dbg(ULP, "Enter\n");
+
+	/* Query ulp_sdioctrl iovar to get the ULP related SHM offsets */
+	brcmf_fil_iovar_data_get(ifp, "ulp_sdioctrl",
+				 &sdiodev->fmac_ulp.ulp_shm_offset,
+				 sizeof(sdiodev->fmac_ulp.ulp_shm_offset));
+
+	sdiodev->ulp = false;
+
+	brcmf_dbg(ULP, "m_ulp_ctrl_sdio[%x] m_ulp_wakeevt_ind [%x]\n",
+		  M_DS1_CTRL_SDIO(sdiodev->fmac_ulp),
+		  M_WAKEEVENT_IND(sdiodev->fmac_ulp));
+	brcmf_dbg(ULP, "m_ulp_wakeind [%x]\n",
+		  M_ULP_WAKE_IND(sdiodev->fmac_ulp));
+}
+
+/* Reinitialize ARM because In DS1 mode ARM got off */
+static int
+brcmf_sdio_ulp_reinit_fw(struct brcmf_sdio *bus)
+{
+	struct brcmf_sdio_dev *sdiodev = bus->sdiodev;
+	struct brcmf_fw_request *fwreq;
+	int err = 0;
+
+	/* After firmware redownload tx/rx seq are reset accordingly
+	 * these values are reset on FMAC side tx_max is initially set to 4,
+	 * which later is updated by FW.
+	 */
+	bus->tx_seq = 0;
+	bus->rx_seq = 0;
+	bus->tx_max = 4;
+
+	fwreq = brcmf_sdio_prepare_fw_request(bus);
+	if (!fwreq)
+		return -ENOMEM;
+
+	err = brcmf_fw_get_firmwares(sdiodev->dev, fwreq,
+				     brcmf_sdio_firmware_callback);
+	if (err != 0) {
+		brcmf_err("async firmware request failed: %d\n", err);
+		kfree(fwreq);
+	}
+
+	return err;
+}
+
+/* Check if device is in DS1 mode and handshake with ULP UCODE */
+static bool
+brcmf_sdio_ulp_pre_redownload_check(struct brcmf_sdio *bus, u32 hmb_data)
+{
+	struct brcmf_sdio_dev *sdiod = bus->sdiodev;
+	int err = 0;
+	u32 value = 0;
+	u32 val32, ulp_wake_ind, wowl_wake_ind;
+	int reg_addr;
+	unsigned long timeout;
+	struct brcmf_ulp *fmac_ulp = &bus->sdiodev->fmac_ulp;
+	int i = 0;
+
+	/* If any host mail box data is present, ignore DS1 exit sequence */
+	if (hmb_data)
+		return false;
+	/* Skip if DS1 Exit is already in progress
+	 * This can happen if firmware download is taking more time
+	 */
+	if (fmac_ulp->ulp_state == FMAC_ULP_TRIGGERED)
+		return false;
+
+	value = brcmf_sdiod_func0_rb(sdiod, SDIO_CCCR_IOEx, &err);
+
+	if (value == SDIO_FUNC_ENABLE_1) {
+		brcmf_dbg(ULP, "GOT THE INTERRUPT FROM UCODE\n");
+		sdiod->ulp = true;
+		fmac_ulp->ulp_state = FMAC_ULP_TRIGGERED;
+		ulp_wake_ind = D11SHM_RDW(sdiod,
+					  M_ULP_WAKE_IND(sdiod->fmac_ulp),
+					  &err);
+		wowl_wake_ind = D11SHM_RDW(sdiod,
+					   M_WAKEEVENT_IND(sdiod->fmac_ulp),
+					   &err);
+
+		brcmf_dbg(ULP, "wowl_wake_ind: 0x%08x, ulp_wake_ind: 0x%08x state %s\n",
+			  wowl_wake_ind, ulp_wake_ind, (fmac_ulp->ulp_state) ?
+			  "DS1 Exit Triggered" : "IDLE State");
+
+		if (wowl_wake_ind || ulp_wake_ind) {
+			/* RX wake Don't do anything.
+			 * Just bail out and re-download firmware.
+			 */
+			 /* Print out PHY TX error block when bit 9 set */
+			if ((ulp_wake_ind & C_DS1_PHY_TXERR) &&
+			    M_DS1_PHYTX_ERR_BLK(sdiod->fmac_ulp)) {
+				brcmf_err("Dump PHY TX Error SHM Locations\n");
+				for (i = 0; i < PHYTX_ERR_BLK_SIZE; i++) {
+					pr_err("0x%x",
+					       D11SHM_RDW(sdiod,
+					       (M_DS1_PHYTX_ERR_BLK(sdiod->fmac_ulp) +
+						(i * 2)), &err));
+				}
+				brcmf_err("\n");
+			}
+		} else {
+			/* TX wake negotiate with MAC */
+			brcmf_dbg(ULP, "M_DS1_CTRL_SDIO: 0x%08x\n",
+				  (u32)D11SHM_RDW(sdiod,
+				  M_DS1_CTRL_SDIO(sdiod->fmac_ulp),
+				  &err));
+			val32 = D11SHM_RD(sdiod,
+					  M_DS1_CTRL_SDIO(sdiod->fmac_ulp),
+					  &err);
+			D11SHM_WR(sdiod, M_DS1_CTRL_SDIO(sdiod->fmac_ulp),
+				  val32, (C_DS1_CTRL_SDIO_DS1_EXIT |
+				  C_DS1_CTRL_REQ_VALID), &err);
+			val32 = D11REG_RD(sdiod, D11_MACCONTROL_REG, &err);
+			val32 = val32 | D11_MACCONTROL_REG_WAKE;
+			D11REG_WR(sdiod, D11_MACCONTROL_REG, val32, &err);
+
+			/* Poll for PROC_DONE to be set by ucode */
+			value = D11SHM_RDW(sdiod,
+					   M_DS1_CTRL_SDIO(sdiod->fmac_ulp),
+					   &err);
+			/* Wait here (polling) for C_DS1_CTRL_PROC_DONE */
+			timeout = jiffies + ULP_HUDI_PROC_DONE_TIME;
+			while (!(value & C_DS1_CTRL_PROC_DONE)) {
+				value = D11SHM_RDW(sdiod,
+						   M_DS1_CTRL_SDIO(sdiod->fmac_ulp),
+						   &err);
+				if (time_after(jiffies, timeout))
+					break;
+				usleep_range(1000, 2000);
+			}
+			brcmf_dbg(ULP, "M_DS1_CTRL_SDIO: 0x%08x\n",
+				  (u32)D11SHM_RDW(sdiod,
+				  M_DS1_CTRL_SDIO(sdiod->fmac_ulp), &err));
+			value = D11SHM_RDW(sdiod,
+					   M_DS1_CTRL_SDIO(sdiod->fmac_ulp),
+					   &err);
+			if (!(value & C_DS1_CTRL_PROC_DONE)) {
+				brcmf_err("Timeout Failed to enter DS1 Exit state!\n");
+				return false;
+			}
+		}
+
+		ulp_wake_ind = D11SHM_RDW(sdiod,
+					  M_ULP_WAKE_IND(sdiod->fmac_ulp),
+					  &err);
+		wowl_wake_ind = D11SHM_RDW(sdiod,
+					   M_WAKEEVENT_IND(sdiod->fmac_ulp),
+					   &err);
+		brcmf_dbg(ULP, "wowl_wake_ind: 0x%08x, ulp_wake_ind: 0x%08x\n",
+			  wowl_wake_ind, ulp_wake_ind);
+		reg_addr = CORE_CC_REG(
+			  brcmf_chip_get_pmu(bus->ci)->base, min_res_mask);
+		brcmf_sdiod_writel(sdiod, reg_addr,
+				   DEFAULT_43012_MIN_RES_MASK, &err);
+		if (err)
+			brcmf_err("min_res_mask failed\n");
+
+		return true;
+	}
+
+	return false;
+}
+
 static void brcmf_sdio_dpc(struct brcmf_sdio *bus)
 {
 	struct brcmf_sdio_dev *sdiod = bus->sdiodev;
@@ -2650,8 +2842,11 @@ static void brcmf_sdio_dpc(struct brcmf_sdio *bus)
 
 	/* Handle host mailbox indication */
 	if (intstatus & I_HMB_HOST_INT) {
+		u32 hmb_data = 0;
 		intstatus &= ~I_HMB_HOST_INT;
-		intstatus |= brcmf_sdio_hostmail(bus);
+		intstatus |= brcmf_sdio_hostmail(bus, &hmb_data);
+		if (brcmf_sdio_ulp_pre_redownload_check(bus, hmb_data))
+			brcmf_sdio_ulp_reinit_fw(bus);
 	}
 
 	sdio_release_host(bus->sdiodev->func1);
@@ -2696,7 +2891,7 @@ static void brcmf_sdio_dpc(struct brcmf_sdio *bus)
 	brcmf_sdio_clrintr(bus);
 
 	if (bus->ctrl_frame_stat && (bus->clkstate == CLK_AVAIL) &&
-	    txctl_ok(bus)) {
+	    txctl_ok(bus) && brcmf_sdio_f2_ready(bus)) {
 		sdio_claim_host(bus->sdiodev->func1);
 		if (bus->ctrl_frame_stat) {
 			err = brcmf_sdio_tx_ctrlframe(bus,  bus->ctrl_frame_buf,
@@ -3566,6 +3761,10 @@ static int brcmf_sdio_bus_preinit(struct device *dev)
 	if (err < 0)
 		goto done;
 
+	/* initialize SHM address from firmware for DS1 */
+	if (!bus->sdiodev->ulp)
+		brcmf_sdio_ulp_preinit(dev);
+
 	bus->tx_hdrlen = SDPCM_HWHDR_LEN + SDPCM_SWHDR_LEN;
 	if (sdiodev->sg_support) {
 		bus->txglom = false;
@@ -4214,7 +4413,7 @@ static void brcmf_sdio_firmware_callback(struct device *dev, int err,
 	u8 saveclk, bpreq;
 	u8 devctl;
 
-	brcmf_dbg(TRACE, "Enter: dev=%s, err=%d\n", dev_name(dev), err);
+	brcmf_dbg(ULP, "Enter: dev=%s, err=%d\n", dev_name(dev), err);
 
 	if (err)
 		goto fail;
@@ -4391,12 +4590,25 @@ static void brcmf_sdio_firmware_callback(struct device *dev, int err,
 	}
 
 	/* Attach to the common layer, reserve hdr space */
-	err = brcmf_attach(sdiod->dev);
+	err = brcmf_attach(sdiod->dev, !bus->sdiodev->ulp);
 	if (err != 0) {
 		brcmf_err("brcmf_attach failed\n");
 		goto free;
 	}
 
+	/* Register for ULP events */
+	if (sdiod->func1->device == SDIO_DEVICE_ID_BROADCOM_CYPRESS_43012)
+		brcmf_fweh_register(bus_if->drvr, BRCMF_E_ULP,
+				    brcmf_ulp_event_notify);
+
+	if (bus->sdiodev->ulp) {
+		/* For ULP, after firmware redownload complete
+		 * set ULP state to IDLE
+		 */
+		if (bus->sdiodev->fmac_ulp.ulp_state == FMAC_ULP_TRIGGERED)
+			bus->sdiodev->fmac_ulp.ulp_state = FMAC_ULP_IDLE;
+	}
+
 	/* ready */
 	return;
 
@@ -4639,3 +4851,40 @@ int brcmf_sdio_sleep(struct brcmf_sdio *bus, bool sleep)
 
 	return ret;
 }
+
+/* Check F2 Ready bit before sending data to Firmware */
+static int
+brcmf_sdio_f2_ready(struct brcmf_sdio *bus)
+{
+	int ret = -1;
+	int iordy_status = 0;
+
+	sdio_claim_host(bus->sdiodev->func1);
+	/* Read the status of IOR2 */
+	iordy_status = brcmf_sdiod_func0_rb(bus->sdiodev, SDIO_CCCR_IORx, NULL);
+
+	sdio_release_host(bus->sdiodev->func1);
+	ret = iordy_status & SDIO_FUNC_ENABLE_2;
+	return ret;
+}
+
+static int brcmf_ulp_event_notify(struct brcmf_if *ifp,
+				  const struct brcmf_event_msg *evtmsg,
+				  void *data)
+{
+	int err = 0;
+	struct brcmf_bus *bus_if = ifp->drvr->bus_if;
+	struct brcmf_sdio_dev *sdiodev;
+	struct brcmf_sdio *bus;
+	struct brcmf_ulp_event *ulp_event = (struct brcmf_ulp_event *)data;
+
+	sdiodev = bus_if->bus_priv.sdio;
+	bus = sdiodev->bus;
+
+	brcmf_dbg(ULP, "Chip went to DS1 state : action %d\n",
+		  ulp_event->ulp_dongle_action);
+	if (ulp_event->ulp_dongle_action == FMAC_ULP_ENTRY)
+		bus->sdiodev->fmac_ulp.ulp_state = FMAC_ULP_ENTRY_RECV;
+
+	return err;
+}
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h
index 0d18ed15b403..aadf251d5440 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.h
@@ -165,6 +165,35 @@ struct brcmf_sdreg {
 struct brcmf_sdio;
 struct brcmf_sdiod_freezer;
 
+/* ULP SHM Offsets info */
+struct ulp_shm_info {
+	u32 m_ulp_ctrl_sdio;
+	u32 m_ulp_wakeevt_ind;
+	u32 m_ulp_wakeind;
+	u32 m_ulp_phytxblk;
+};
+
+/* FMAC ULP state machine */
+#define FMAC_ULP_IDLE		(0)
+#define FMAC_ULP_ENTRY_RECV		(1)
+#define FMAC_ULP_TRIGGERED		(2)
+
+/* BRCMF_E_ULP event data */
+#define FMAC_ULP_EVENT_VERSION		1
+#define FMAC_ULP_DISABLE_CONSOLE		1 /* Disable console */
+#define FMAC_ULP_UCODE_DOWNLOAD		2 /* Download ULP ucode file */
+#define FMAC_ULP_ENTRY		3 /* Inform ulp entry to Host */
+
+struct brcmf_ulp {
+	uint ulp_state;
+	struct ulp_shm_info ulp_shm_offset;
+};
+
+struct brcmf_ulp_event {
+	u16 version;
+	u16 ulp_dongle_action;
+};
+
 struct brcmf_sdio_dev {
 	struct sdio_func *func1;
 	struct sdio_func *func2;
@@ -193,6 +222,8 @@ struct brcmf_sdio_dev {
 	enum brcmf_sdiod_state state;
 	struct brcmf_sdiod_freezer *freezer;
 	const struct firmware *clm_fw;
+	struct brcmf_ulp fmac_ulp;
+	bool ulp;
 };
 
 /* sdio core registers */
@@ -367,4 +398,83 @@ void brcmf_sdio_wowl_config(struct device *dev, bool enabled);
 int brcmf_sdio_sleep(struct brcmf_sdio *bus, bool sleep);
 void brcmf_sdio_trigger_dpc(struct brcmf_sdio *bus);
 
+/* SHM offsets */
+#define M_DS1_CTRL_SDIO(ptr)	((ptr).ulp_shm_offset.m_ulp_ctrl_sdio)
+#define M_WAKEEVENT_IND(ptr)	((ptr).ulp_shm_offset.m_ulp_wakeevt_ind)
+#define M_ULP_WAKE_IND(ptr)		((ptr).ulp_shm_offset.m_ulp_wakeind)
+#define M_DS1_PHYTX_ERR_BLK(ptr)	((ptr).ulp_shm_offset.m_ulp_phytxblk)
+
+#define D11_BASE_ADDR			0x18001000
+#define D11_AXI_BASE_ADDR		0xE8000000
+#define D11_SHM_BASE_ADDR		(D11_AXI_BASE_ADDR + 0x4000)
+
+#define D11REG_ADDR(offset)	(D11_BASE_ADDR + (offset))
+#define D11IHR_ADDR(offset)	(D11_AXI_BASE_ADDR + 0x400 + (2 * (offset)))
+#define D11SHM_ADDR(offset)	(D11_SHM_BASE_ADDR + (offset))
+
+/* MacControl register */
+#define D11_MACCONTROL_REG			D11REG_ADDR(0x120)
+#define D11_MACCONTROL_REG_WAKE		0x4000000
+
+/* HUDI Sequence SHM bits */
+#define	C_DS1_CTRL_SDIO_DS1_SLEEP		0x1
+#define	C_DS1_CTRL_SDIO_MAC_ON			0x2
+#define	C_DS1_CTRL_SDIO_RADIO_PHY_ON	0x4
+#define	C_DS1_CTRL_SDIO_DS1_EXIT		0x8
+#define	C_DS1_CTRL_PROC_DONE			0x100
+#define	C_DS1_CTRL_REQ_VALID			0x200
+
+/* M_ULP_WAKEIND bits */
+#define	C_WATCHDOG_EXPIRY	BIT(0)
+#define	C_FCBS_ERROR		BIT(1)
+#define	C_RETX_FAILURE		BIT(2)
+#define	C_HOST_WAKEUP		BIT(3)
+#define	C_INVALID_FCBS_BLOCK	BIT(4)
+#define	C_HUDI_DS1_EXIT		BIT(5)
+#define	C_LOB_SLEEP		BIT(6)
+#define	C_DS1_PHY_TXERR		BIT(9)
+#define	C_DS1_WAKE_TIMER	BIT(10)
+
+#define PHYTX_ERR_BLK_SIZE		18
+#define D11SHM_FIRST2BYTE_MASK		0xFFFF0000
+#define D11SHM_SECOND2BYTE_MASK		0x0000FFFF
+#define D11SHM_2BYTE_SHIFT		16
+
+#define D11SHM_RD(sdh, offset, ret) \
+	brcmf_sdiod_readl(sdh, D11SHM_ADDR(offset), ret)
+
+/* SHM Read is motified based on SHM 4 byte alignment as SHM size is 2 bytes and
+ * 2 byte is currently not working on FMAC
+ * If SHM address is not 4 byte aligned, then right shift by 16
+ * otherwise, mask the first two MSB bytes
+ * Suppose data in address 7260 is 0x440002 and it is 4 byte aligned
+ * Correct SHM value is 0x2 for this SHM offset and next SHM value is 0x44
+ */
+#define D11SHM_RDW(sdh, offset, ret) \
+	((offset % 4) ? \
+		(brcmf_sdiod_readl(sdh, D11SHM_ADDR(offset), ret) \
+		>> D11SHM_2BYTE_SHIFT) : \
+		(brcmf_sdiod_readl(sdh, D11SHM_ADDR(offset), ret) \
+		& D11SHM_SECOND2BYTE_MASK))
+
+/* SHM is of size 2 bytes, 4 bytes write will overwrite other SHM's
+ * First read 4 bytes and then clear the required two bytes based on
+ * 4 byte alignment, then update the required value and write the
+ * 4 byte value now
+ */
+#define D11SHM_WR(sdh, offset, val, mask, ret) \
+	do { \
+		if ((offset) % 4) \
+			val = (val & D11SHM_SECOND2BYTE_MASK) | \
+				((mask) << D11SHM_2BYTE_SHIFT); \
+		else \
+			val = (mask) | (val & D11SHM_FIRST2BYTE_MASK); \
+		brcmf_sdiod_writel(sdh, D11SHM_ADDR(offset), val, ret); \
+	} while (0)
+#define D11REG_WR(sdh, addr, val, ret) \
+	brcmf_sdiod_writel(sdh, addr, val, ret)
+
+#define D11REG_RD(sdh, addr, ret) \
+	brcmf_sdiod_readl(sdh, addr, ret)
+
 #endif /* BRCMFMAC_SDIO_H */
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c
index 0364b81f6e91..e06248ded787 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/usb.c
@@ -1202,7 +1202,7 @@ static void brcmf_usb_probe_phase2(struct device *dev, int ret,
 		goto error;
 
 	/* Attach to the common driver interface */
-	ret = brcmf_attach(devinfo->dev);
+	ret = brcmf_attach(devinfo->dev, true);
 	if (ret)
 		goto error;
 
@@ -1279,7 +1279,7 @@ static int brcmf_usb_probe_cb(struct brcmf_usbdev_info *devinfo,
 		ret = brcmf_alloc(devinfo->dev, devinfo->settings);
 		if (ret)
 			goto fail;
-		ret = brcmf_attach(devinfo->dev);
+		ret = brcmf_attach(devinfo->dev, true);
 		if (ret)
 			goto fail;
 		/* we are done */
diff --git a/drivers/net/wireless/broadcom/brcm80211/include/chipcommon.h b/drivers/net/wireless/broadcom/brcm80211/include/chipcommon.h
index 0340bba96868..090a75bcd728 100644
--- a/drivers/net/wireless/broadcom/brcm80211/include/chipcommon.h
+++ b/drivers/net/wireless/broadcom/brcm80211/include/chipcommon.h
@@ -308,4 +308,6 @@ struct chipcregs {
 */
 #define PMU_MAX_TRANSITION_DLY	15000
 
+#define DEFAULT_43012_MIN_RES_MASK 0x0f8bfe77
+
 #endif				/* _SBCHIPC_H */
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
index d2c384f58028..c033ce7b769d 100644
--- a/drivers/nvmem/Kconfig
+++ b/drivers/nvmem/Kconfig
@@ -40,6 +40,18 @@ config NVMEM_APPLE_EFUSES
 	  This driver can also be built as a module. If so, the module will
 	  be called nvmem-apple-efuses.
 
+config NVMEM_RASPBERRYPI_OTP
+	tristate "Raspberry Pi OTP support"
+	depends on (ARCH_BCM && RASPBERRYPI_FIRMWARE) || COMPILE_TEST
+	default ARCH_BCM && RASPBERRYPI_FIRMWARE
+	help
+	  Say y here to enable support for accessing OTP on Raspberry Pi boards.
+	  These are used to store non-volatile information such as serial number,
+	  board revision and customer stored data.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called nvmem-raspberrypi-otp.
+
 config NVMEM_BCM_OCOTP
 	tristate "Broadcom On-Chip OTP Controller support"
 	depends on ARCH_BCM_IPROC || COMPILE_TEST
diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile
index cdd01fbf1313..27a03c856fd9 100644
--- a/drivers/nvmem/Makefile
+++ b/drivers/nvmem/Makefile
@@ -12,6 +12,8 @@ obj-y				+= layouts/
 # Devices
 obj-$(CONFIG_NVMEM_APPLE_EFUSES)	+= nvmem-apple-efuses.o
 nvmem-apple-efuses-y 			:= apple-efuses.o
+obj-$(CONFIG_NVMEM_RASPBERRYPI_OTP)	+= nvmem-raspberrypi-otp.o
+nvmem-raspberrypi-otp-y			:= raspberrypi-otp.o
 obj-$(CONFIG_NVMEM_BCM_OCOTP)		+= nvmem-bcm-ocotp.o
 nvmem-bcm-ocotp-y			:= bcm-ocotp.o
 obj-$(CONFIG_NVMEM_BRCM_NVRAM)		+= nvmem_brcm_nvram.o
diff --git a/drivers/nvmem/raspberrypi-otp.c b/drivers/nvmem/raspberrypi-otp.c
new file mode 100644
index 000000000000..a7c735e9e964
--- /dev/null
+++ b/drivers/nvmem/raspberrypi-otp.c
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/**
+ * raspberrypi-otp.c
+ *
+ * nvmem driver using firmware mailbox to access otp
+ *
+ * Copyright (c) 2024, Raspberry Pi Ltd.
+ */
+
+#include <linux/nvmem-provider.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
+struct rpi_otp_priv {
+	struct rpi_firmware *fw;
+	u32 block;
+};
+
+#define MAX_ROWS 192
+
+#define RPI_FIRMWARE_GET_USER_OTP 0x00030024
+#define RPI_FIRMWARE_SET_USER_OTP 0x00038024
+
+static int rpi_otp_read(void *context, unsigned int offset, void *val,
+			     size_t bytes)
+{
+	struct rpi_otp_priv *priv = context;
+	int words = bytes / sizeof(u32);
+	int index = offset / sizeof(u32);
+	u32 data[3 + MAX_ROWS] = {priv->block, index, words};
+	int err = 0;
+
+	if (words > MAX_ROWS)
+		return -EINVAL;
+
+	err = rpi_firmware_property(priv->fw, RPI_FIRMWARE_GET_USER_OTP,
+				    &data, sizeof(data));
+	if (err == 0)
+		memcpy(val, data + 3, bytes);
+	else
+		memset(val, 0xee, bytes);
+	return err;
+}
+
+static int rpi_otp_write(void *context, unsigned int offset, void *val,
+			     size_t bytes)
+{
+	struct rpi_otp_priv *priv = context;
+	int words = bytes / sizeof(u32);
+	int index = offset / sizeof(u32);
+	u32 data[3 + MAX_ROWS] = {priv->block, index, words};
+
+	if (bytes > MAX_ROWS * sizeof(u32))
+		return -EINVAL;
+
+	memcpy(data + 3, val, bytes);
+	return rpi_firmware_property(priv->fw, RPI_FIRMWARE_SET_USER_OTP,
+				    &data, sizeof(data));
+}
+
+static int rpi_otp_probe(struct platform_device *pdev)
+{
+	struct rpi_otp_priv *priv;
+	struct nvmem_config config = {
+		.dev = &pdev->dev,
+		.reg_read = rpi_otp_read,
+		.reg_write = rpi_otp_write,
+		.stride = sizeof(u32),
+		.word_size = sizeof(u32),
+		.type = NVMEM_TYPE_OTP,
+		.root_only = true,
+	};
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	struct device_node *fw_node;
+	struct rpi_firmware *fw;
+	u32 reg[2];
+	const char *pname;
+
+	if (of_property_read_u32_array(np, "reg", reg, ARRAY_SIZE(reg))) {
+		dev_err(dev, "Failed to parse \"reg\" property\n");
+		return -EINVAL;
+	}
+
+	pname = of_get_property(np, "name", NULL);
+	if (!pname) {
+		dev_err(dev, "Failed to parse \"name\" property\n");
+		return -ENOENT;
+	}
+
+	config.name = pname;
+	config.size = reg[1] * sizeof(u32);
+	config.read_only = !of_property_read_bool(np, "rw");
+
+	fw_node = of_parse_phandle(np, "firmware", 0);
+	if (!fw_node) {
+		dev_err(dev, "Missing firmware node\n");
+		return -ENOENT;
+	}
+
+	fw = rpi_firmware_get(fw_node);
+	if (!fw)
+		return -EPROBE_DEFER;
+
+	priv = devm_kzalloc(config.dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->fw = fw;
+	priv->block = reg[0];
+	config.priv = priv;
+
+	return PTR_ERR_OR_ZERO(devm_nvmem_register(config.dev, &config));
+}
+
+static const struct of_device_id rpi_otp_of_match[] = {
+	{ .compatible = "raspberrypi,rpi-otp", },
+	{}
+};
+
+MODULE_DEVICE_TABLE(of, rpi_otp_of_match);
+
+static struct platform_driver rpi_otp_driver = {
+	.driver = {
+		.name = "rpi_otp",
+		.of_match_table = rpi_otp_of_match,
+	},
+	.probe = rpi_otp_probe,
+};
+
+module_platform_driver(rpi_otp_driver);
+
+MODULE_AUTHOR("Dom Cobley <popcornmix@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig
index 0e2d608c3e20..c634bd8a0843 100644
--- a/drivers/of/Kconfig
+++ b/drivers/of/Kconfig
@@ -120,4 +120,15 @@ config OF_OVERLAY_KUNIT_TEST
 config OF_NUMA
 	bool
 
+config OF_DMA_DEFAULT_COHERENT
+	# arches should select this if DMA is coherent by default for OF devices
+	bool
+
+config OF_CONFIGFS
+	bool "Device Tree Overlay ConfigFS interface"
+	select CONFIGFS_FS
+	select OF_OVERLAY
+	help
+	  Enable a simple user-space driven DT overlay interface.
+
 endif # OF
diff --git a/drivers/of/Makefile b/drivers/of/Makefile
index 379a0afcbdc0..f294b36207f8 100644
--- a/drivers/of/Makefile
+++ b/drivers/of/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-y = base.o cpu.o device.o module.o platform.o property.o
 obj-$(CONFIG_OF_KOBJ) += kobj.o
+obj-$(CONFIG_OF_CONFIGFS) += configfs.o
 obj-$(CONFIG_OF_DYNAMIC) += dynamic.o
 obj-$(CONFIG_OF_FLATTREE) += fdt.o empty_root.dtb.o
 obj-$(CONFIG_OF_EARLY_FLATTREE) += fdt_address.o
diff --git a/drivers/of/configfs.c b/drivers/of/configfs.c
new file mode 100644
index 000000000000..0e6bccb8ff2e
--- /dev/null
+++ b/drivers/of/configfs.c
@@ -0,0 +1,277 @@
+/*
+ * Configfs entries for device-tree
+ *
+ * Copyright (C) 2013 - Pantelis Antoniou <panto@antoniou-consulting.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+#include <linux/ctype.h>
+#include <linux/cpu.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_fdt.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/proc_fs.h>
+#include <linux/configfs.h>
+#include <linux/types.h>
+#include <linux/stat.h>
+#include <linux/limits.h>
+#include <linux/file.h>
+#include <linux/vmalloc.h>
+#include <linux/firmware.h>
+#include <linux/sizes.h>
+
+#include "of_private.h"
+
+struct cfs_overlay_item {
+	struct config_item	item;
+
+	char			path[PATH_MAX];
+
+	const struct firmware	*fw;
+	struct device_node	*overlay;
+	int			ov_id;
+
+	void			*dtbo;
+	int			dtbo_size;
+};
+
+static inline struct cfs_overlay_item *to_cfs_overlay_item(
+		struct config_item *item)
+{
+	return item ? container_of(item, struct cfs_overlay_item, item) : NULL;
+}
+
+static ssize_t cfs_overlay_item_path_show(struct config_item *item,
+		char *page)
+{
+	struct cfs_overlay_item *overlay = to_cfs_overlay_item(item);
+	return sprintf(page, "%s\n", overlay->path);
+}
+
+static ssize_t cfs_overlay_item_path_store(struct config_item *item,
+		const char *page, size_t count)
+{
+	struct cfs_overlay_item *overlay = to_cfs_overlay_item(item);
+	const char *p = page;
+	char *s;
+	int err;
+
+	/* if it's set do not allow changes */
+	if (overlay->path[0] != '\0' || overlay->dtbo_size > 0)
+		return -EPERM;
+
+	/* copy to path buffer (and make sure it's always zero terminated */
+	count = snprintf(overlay->path, sizeof(overlay->path) - 1, "%s", p);
+	overlay->path[sizeof(overlay->path) - 1] = '\0';
+
+	/* strip trailing newlines */
+	s = overlay->path + strlen(overlay->path);
+	while (s > overlay->path && *--s == '\n')
+		*s = '\0';
+
+	pr_debug("%s: path is '%s'\n", __func__, overlay->path);
+
+	err = request_firmware(&overlay->fw, overlay->path, NULL);
+	if (err != 0)
+		goto out_err;
+
+	err = of_overlay_fdt_apply((void *)overlay->fw->data,
+				   (u32)overlay->fw->size, &overlay->ov_id, NULL);
+	if (err != 0)
+		goto out_err;
+
+	return count;
+
+out_err:
+
+	release_firmware(overlay->fw);
+	overlay->fw = NULL;
+
+	overlay->path[0] = '\0';
+	return err;
+}
+
+static ssize_t cfs_overlay_item_status_show(struct config_item *item,
+		char *page)
+{
+	struct cfs_overlay_item *overlay = to_cfs_overlay_item(item);
+
+	return sprintf(page, "%s\n",
+			overlay->ov_id > 0 ? "applied" : "unapplied");
+}
+
+CONFIGFS_ATTR(cfs_overlay_item_, path);
+CONFIGFS_ATTR_RO(cfs_overlay_item_, status);
+
+static struct configfs_attribute *cfs_overlay_attrs[] = {
+	&cfs_overlay_item_attr_path,
+	&cfs_overlay_item_attr_status,
+	NULL,
+};
+
+static ssize_t cfs_overlay_item_dtbo_read(struct config_item *item,
+		void *buf, size_t max_count)
+{
+	struct cfs_overlay_item *overlay = to_cfs_overlay_item(item);
+
+	pr_debug("%s: buf=%p max_count=%zu\n", __func__,
+			buf, max_count);
+
+	if (overlay->dtbo == NULL)
+		return 0;
+
+	/* copy if buffer provided */
+	if (buf != NULL) {
+		/* the buffer must be large enough */
+		if (overlay->dtbo_size > max_count)
+			return -ENOSPC;
+
+		memcpy(buf, overlay->dtbo, overlay->dtbo_size);
+	}
+
+	return overlay->dtbo_size;
+}
+
+static ssize_t cfs_overlay_item_dtbo_write(struct config_item *item,
+		const void *buf, size_t count)
+{
+	struct cfs_overlay_item *overlay = to_cfs_overlay_item(item);
+	int err;
+
+	/* if it's set do not allow changes */
+	if (overlay->path[0] != '\0' || overlay->dtbo_size > 0)
+		return -EPERM;
+
+	/* copy the contents */
+	overlay->dtbo = kmemdup(buf, count, GFP_KERNEL);
+	if (overlay->dtbo == NULL)
+		return -ENOMEM;
+
+	overlay->dtbo_size = count;
+
+	err = of_overlay_fdt_apply(overlay->dtbo, overlay->dtbo_size,
+				   &overlay->ov_id, NULL);
+	if (err != 0)
+		goto out_err;
+
+	return count;
+
+out_err:
+	kfree(overlay->dtbo);
+	overlay->dtbo = NULL;
+	overlay->dtbo_size = 0;
+	overlay->ov_id = 0;
+
+	return err;
+}
+
+CONFIGFS_BIN_ATTR(cfs_overlay_item_, dtbo, NULL, SZ_1M);
+
+static struct configfs_bin_attribute *cfs_overlay_bin_attrs[] = {
+	&cfs_overlay_item_attr_dtbo,
+	NULL,
+};
+
+static void cfs_overlay_release(struct config_item *item)
+{
+	struct cfs_overlay_item *overlay = to_cfs_overlay_item(item);
+
+	if (overlay->ov_id > 0)
+		of_overlay_remove(&overlay->ov_id);
+	if (overlay->fw)
+		release_firmware(overlay->fw);
+	/* kfree with NULL is safe */
+	kfree(overlay->dtbo);
+	kfree(overlay);
+}
+
+static struct configfs_item_operations cfs_overlay_item_ops = {
+	.release	= cfs_overlay_release,
+};
+
+static struct config_item_type cfs_overlay_type = {
+	.ct_item_ops	= &cfs_overlay_item_ops,
+	.ct_attrs	= cfs_overlay_attrs,
+	.ct_bin_attrs	= cfs_overlay_bin_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static struct config_item *cfs_overlay_group_make_item(
+		struct config_group *group, const char *name)
+{
+	struct cfs_overlay_item *overlay;
+
+	overlay = kzalloc(sizeof(*overlay), GFP_KERNEL);
+	if (!overlay)
+		return ERR_PTR(-ENOMEM);
+
+	config_item_init_type_name(&overlay->item, name, &cfs_overlay_type);
+	return &overlay->item;
+}
+
+static void cfs_overlay_group_drop_item(struct config_group *group,
+		struct config_item *item)
+{
+	struct cfs_overlay_item *overlay = to_cfs_overlay_item(item);
+
+	config_item_put(&overlay->item);
+}
+
+static struct configfs_group_operations overlays_ops = {
+	.make_item	= cfs_overlay_group_make_item,
+	.drop_item	= cfs_overlay_group_drop_item,
+};
+
+static struct config_item_type overlays_type = {
+	.ct_group_ops   = &overlays_ops,
+	.ct_owner       = THIS_MODULE,
+};
+
+static struct configfs_group_operations of_cfs_ops = {
+	/* empty - we don't allow anything to be created */
+};
+
+static struct config_item_type of_cfs_type = {
+	.ct_group_ops   = &of_cfs_ops,
+	.ct_owner       = THIS_MODULE,
+};
+
+struct config_group of_cfs_overlay_group;
+
+static struct configfs_subsystem of_cfs_subsys = {
+	.su_group = {
+		.cg_item = {
+			.ci_namebuf = "device-tree",
+			.ci_type = &of_cfs_type,
+		},
+	},
+	.su_mutex = __MUTEX_INITIALIZER(of_cfs_subsys.su_mutex),
+};
+
+static int __init of_cfs_init(void)
+{
+	int ret;
+
+	pr_info("%s\n", __func__);
+
+	config_group_init(&of_cfs_subsys.su_group);
+	config_group_init_type_name(&of_cfs_overlay_group, "overlays",
+			&overlays_type);
+	configfs_add_default_group(&of_cfs_overlay_group,
+			&of_cfs_subsys.su_group);
+
+	ret = configfs_register_subsystem(&of_cfs_subsys);
+	if (ret != 0) {
+		pr_err("%s: failed to register subsys\n", __func__);
+		goto out;
+	}
+	pr_info("%s: OK\n", __func__);
+out:
+	return ret;
+}
+late_initcall(of_cfs_init);
diff --git a/drivers/of/overlay.c b/drivers/of/overlay.c
index cbdecccca097..1ebe2ea15f12 100644
--- a/drivers/of/overlay.c
+++ b/drivers/of/overlay.c
@@ -241,6 +241,8 @@ static struct property *dup_and_fixup_symbol_prop(
 	if (!target_path)
 		return NULL;
 	target_path_len = strlen(target_path);
+	if (!strcmp(target_path, "/"))
+		target_path_len = 0;
 
 	new_prop = kzalloc(sizeof(*new_prop), GFP_KERNEL);
 	if (!new_prop)
diff --git a/drivers/pci/controller/pcie-brcmstb.c b/drivers/pci/controller/pcie-brcmstb.c
index 3f9b1f7131bf..59743e0a0535 100644
--- a/drivers/pci/controller/pcie-brcmstb.c
+++ b/drivers/pci/controller/pcie-brcmstb.c
@@ -5,6 +5,7 @@
 #include <linux/bitops.h>
 #include <linux/clk.h>
 #include <linux/compiler.h>
+#include <linux/debugfs.h>
 #include <linux/delay.h>
 #include <linux/init.h>
 #include <linux/interrupt.h>
@@ -46,7 +47,10 @@
 #define  PCIE_RC_CFG_PRIV1_ID_VAL3_CLASS_CODE_MASK	0xffffff
 
 #define PCIE_RC_CFG_PRIV1_LINK_CAPABILITY			0x04dc
-#define  PCIE_RC_CFG_PRIV1_LINK_CAPABILITY_MAX_LINK_WIDTH_MASK	0x1f0
+#define  PCIE_RC_CFG_PRIV1_LINK_CAPABILITY_ASPM_SUPPORT_MASK	0xc00
+#define  PCIE_RC_CFG_PRIV1_LINK_CAPABILITY_ASPM_SUPPORT_L0S	0x1
+#define  PCIE_RC_CFG_PRIV1_LINK_CAPABILITY_ASPM_SUPPORT_L1	0x2
+#define  PCIE_RC_CFG_PRIV1_LINK_CAPABILITY_MAX_LINK_SPEED_MASK	0xf
 
 #define PCIE_RC_CFG_PRIV1_ROOT_CAP			0x4f8
 #define  PCIE_RC_CFG_PRIV1_ROOT_CAP_L1SS_MODE_MASK	0xf8
@@ -55,8 +59,37 @@
 #define PCIE_RC_DL_MDIO_WR_DATA				0x1104
 #define PCIE_RC_DL_MDIO_RD_DATA				0x1108
 
-#define PCIE_RC_PL_REG_PHY_CTL_1			0x1804
-#define  PCIE_RC_PL_REG_PHY_CTL_1_REG_P2_POWERDOWN_ENA_NOSYNC_MASK	0x8
+#define PCIE_RC_PL_PHY_CTL_15				0x184c
+#define  PCIE_RC_PL_PHY_CTL_15_DIS_PLL_PD_MASK		0x400000
+#define  PCIE_RC_PL_PHY_CTL_15_PM_CLK_PERIOD_MASK	0xff
+
+#define PCIE_RC_PL_STATS_CTRL				0x1940
+#define  PCIE_RC_PL_STATS_CTRL_EN_MASK			0x1
+#define  PCIE_RC_PL_STATS_CTRL_LEN_MASK			0xfffffff0
+
+#define PCIE_RC_PL_STATS_TXTLP_LO			0x1944
+#define PCIE_RC_PL_STATS_TXTLP_HI			0x1948
+#define PCIE_RC_PL_STATS_TXDLLP_LO			0x194c
+#define PCIE_RC_PL_STATS_TXDLLP_HI			0x1950
+#define PCIE_RC_PL_STATS_RXTLP_LO			0x195c
+#define PCIE_RC_PL_STATS_RXTLP_HI			0x1960
+#define PCIE_RC_PL_STATS_RXDLLP_LO			0x1964
+#define PCIE_RC_PL_STATS_RXDLLP_HI			0x1968
+#define PCIE_RC_PL_STATS_RXPL_ERR			0x1974
+#define PCIE_RC_PL_STATS_RXDL_ERR			0x1978
+#define PCIE_RC_PL_STATS_RXTL_ERR			0x197c
+
+#define PCIE_RC_PL_LTSSM_STATS_3			0x19b0
+#define  PCIE_RC_PL_LTSSM_STATS_3_TIME_L0S_MASK		0xffff0000
+#define  PCIE_RC_PL_LTSSM_STATS_3_TIME_RECOV_MASK	0x0000ffff
+
+#define PCIE_RC_PL_LTSSM_STATS_CNT			0x19b4
+#define  PCIE_RC_PL_LTSSM_STATS_CNT_L0S_FAIL_MASK	0xffff0000
+#define  PCIE_RC_PL_LTSSM_STATS_CNT_RECOV_MASK		0x0000ffff
+
+#define PCIE_RC_PL_LTSSM_HIST_0				0x1cec
+#define PCIE_RC_PL_LTSSM_HIST(n)	\
+		(PCIE_RC_PL_LTSSM_HIST_0 + ((n) * 4))
 
 #define PCIE_MISC_MISC_CTRL				0x4008
 #define  PCIE_MISC_MISC_CTRL_PCIE_RCB_64B_MODE_MASK	0x80
@@ -130,11 +163,16 @@
 		PCIE_MISC_CPU_2_PCIE_MEM_WIN0_LIMIT_HI + ((win) * 8)
 
 #define  PCIE_MISC_HARD_PCIE_HARD_DEBUG_CLKREQ_DEBUG_ENABLE_MASK	0x2
+#define  PCIE_MISC_HARD_PCIE_HARD_DEBUG_PERST_ASSERT_MASK		0x8
+#define  PCIE_MISC_HARD_PCIE_HARD_DEBUG_REFCLK_OVRD_ENABLE_MASK		0x10000
+#define  PCIE_MISC_HARD_PCIE_HARD_DEBUG_REFCLK_OVRD_OUT_MASK		0x100000
 #define  PCIE_MISC_HARD_PCIE_HARD_DEBUG_L1SS_ENABLE_MASK		0x200000
 #define  PCIE_MISC_HARD_PCIE_HARD_DEBUG_SERDES_IDDQ_MASK		0x08000000
 #define  PCIE_BMIPS_MISC_HARD_PCIE_HARD_DEBUG_SERDES_IDDQ_MASK		0x00800000
 #define  PCIE_CLKREQ_MASK \
 	  (PCIE_MISC_HARD_PCIE_HARD_DEBUG_CLKREQ_DEBUG_ENABLE_MASK | \
+	   PCIE_MISC_HARD_PCIE_HARD_DEBUG_REFCLK_OVRD_ENABLE_MASK | \
+	   PCIE_MISC_HARD_PCIE_HARD_DEBUG_REFCLK_OVRD_OUT_MASK | \
 	   PCIE_MISC_HARD_PCIE_HARD_DEBUG_L1SS_ENABLE_MASK)
 
 #define PCIE_MISC_UBUS_BAR1_CONFIG_REMAP			0x40ac
@@ -149,9 +187,6 @@
 #define  MSI_INT_MASK_SET		0x10
 #define  MSI_INT_MASK_CLR		0x14
 
-#define PCIE_EXT_CFG_DATA				0x8000
-#define PCIE_EXT_CFG_INDEX				0x9000
-
 #define  PCIE_RGR1_SW_INIT_1_PERST_MASK			0x1
 #define  PCIE_RGR1_SW_INIT_1_PERST_SHIFT		0x0
 
@@ -177,8 +212,9 @@
 #define MDIO_PORT0			0x0
 #define MDIO_DATA_MASK			0x7fffffff
 #define MDIO_PORT_MASK			0xf0000
+#define MDIO_PORT_EXT_MASK		0x200000
 #define MDIO_REGAD_MASK			0xffff
-#define MDIO_CMD_MASK			0xfff00000
+#define MDIO_CMD_MASK			0x00100000
 #define MDIO_CMD_READ			0x1
 #define MDIO_CMD_WRITE			0x0
 #define MDIO_DATA_DONE_MASK		0x80000000
@@ -210,6 +246,58 @@
 #define  PCIE_DVT_PMU_PCIE_PHY_CTRL_DAST_PWRDN_MASK		0x1
 #define  PCIE_DVT_PMU_PCIE_PHY_CTRL_DAST_PWRDN_SHIFT		0x0
 
+/* BCM7712/2712-specific registers */
+
+#define PCIE_RC_TL_VDM_CTL0				0x0a20
+#define  PCIE_RC_TL_VDM_CTL0_VDM_ENABLED_MASK		0x10000
+#define  PCIE_RC_TL_VDM_CTL0_VDM_IGNORETAG_MASK		0x20000
+#define  PCIE_RC_TL_VDM_CTL0_VDM_IGNOREVNDRID_MASK	0x40000
+
+#define PCIE_RC_TL_VDM_CTL1				0x0a0c
+#define  PCIE_RC_TL_VDM_CTL1_VDM_VNDRID0_MASK		0x0000ffff
+#define  PCIE_RC_TL_VDM_CTL1_VDM_VNDRID1_MASK		0xffff0000
+
+#define PCIE_MISC_CTRL_1					0x40A0
+#define  PCIE_MISC_CTRL_1_OUTBOUND_TC_MASK			0xf
+#define  PCIE_MISC_CTRL_1_OUTBOUND_NO_SNOOP_MASK		BIT(3)
+#define  PCIE_MISC_CTRL_1_OUTBOUND_RO_MASK			BIT(4)
+#define  PCIE_MISC_CTRL_1_EN_VDM_QOS_CONTROL_MASK		BIT(5)
+
+#define PCIE_MISC_UBUS_CTRL	0x40a4
+#define  PCIE_MISC_UBUS_CTRL_UBUS_PCIE_REPLY_ERR_DIS_MASK	BIT(13)
+#define  PCIE_MISC_UBUS_CTRL_UBUS_PCIE_REPLY_DECERR_DIS_MASK	BIT(19)
+
+#define PCIE_MISC_UBUS_TIMEOUT	0x40a8
+
+#define PCIE_MISC_RC_CONFIG_RETRY_TIMEOUT	0x405c
+
+/* AXI priority forwarding - automatic level-based */
+#define PCIE_MISC_TC_QUEUE_TO_QOS_MAP(x)		(0x4160 - (x) * 4)
+/* Defined in quarter-fullness */
+#define  QUEUE_THRESHOLD_34_TO_QOS_MAP_SHIFT		12
+#define  QUEUE_THRESHOLD_23_TO_QOS_MAP_SHIFT		8
+#define  QUEUE_THRESHOLD_12_TO_QOS_MAP_SHIFT		4
+#define  QUEUE_THRESHOLD_01_TO_QOS_MAP_SHIFT		0
+#define  QUEUE_THRESHOLD_MASK				0xf
+
+/* VDM messages indexing TCs to AXI priorities */
+/* Indexes 8-15 */
+#define PCIE_MISC_VDM_PRIORITY_TO_QOS_MAP_HI		0x4164
+/* Indexes 0-7 */
+#define PCIE_MISC_VDM_PRIORITY_TO_QOS_MAP_LO		0x4168
+#define  VDM_PRIORITY_TO_QOS_MAP_SHIFT(x)		(4 * (x))
+#define  VDM_PRIORITY_TO_QOS_MAP_MASK			0xf
+
+#define PCIE_MISC_AXI_INTF_CTRL 0x416C
+#define  AXI_EN_RCLK_QOS_ARRAY_FIX			BIT(13)
+#define  AXI_EN_QOS_UPDATE_TIMING_FIX			BIT(12)
+#define  AXI_DIS_QOS_GATING_IN_MASTER			BIT(11)
+#define  AXI_REQFIFO_EN_QOS_PROPAGATION			BIT(7)
+#define  AXI_BRIDGE_LOW_LATENCY_MODE			BIT(6)
+#define  AXI_MASTER_MAX_OUTSTANDING_REQUESTS_MASK	0x3f
+
+#define PCIE_MISC_AXI_READ_ERROR_DATA		0x4170
+
 /* Forward declarations */
 struct brcm_pcie;
 
@@ -237,13 +325,115 @@ struct inbound_win {
 	u64 cpu_addr;
 };
 
+#define TRACE_BUF_LEN 64
+struct trace_entry {
+	u8 st;
+	ktime_t ts;
+};
+
+static const char *brcm_pcie_decode_ltssm_state(u8 state)
+{
+	switch (state) {
+	case 0x01:
+		return "L0";
+	case 0x02:
+		return "L1";
+	case 0x03:
+		return "L2";
+	case 0x04:
+		return "RxL0s_TxL0s";
+	case 0x05:
+		return "RxL0_TxL0s";
+	case 0x09:
+		return "Polling.Active";
+	case 0x0A:
+		return "Polling.Configuration";
+	case 0x0C:
+		return "Polling.Compliance";
+	case 0x10:
+		return "Configuration.Link";
+	case 0x11:
+		return "Configuration.Linkwidth.Accept";
+	case 0x12:
+		return "Configuration.Lanenum.Wait";
+	case 0x13:
+		return "Confguration.Lanenum.Accept";
+	case 0x14:
+		return "Confguration.Complete";
+	case 0x15:
+		return "Configuration.Idle";
+	case 0x20:
+		return "Recovery.RcvrLock";
+	case 0x21:
+		return "Recovery.RcvrCfg";
+	case 0x22:
+		return "Recovery.Idle";
+	case 0x23:
+		return "Recovery.Speed";
+	case 0x24:
+		return "Recovery.EQ_Phase0";
+	case 0x25:
+		return "Recovery.EQ_Phase1";
+	case 0x26:
+		return "Recovery.EQ_Phase2";
+	case 0x27:
+		return "Recovery.EQ_Phase3";
+	case 0x40:
+		return "Disable";
+	case 0x43:
+		return "Reset";
+	case 0x47:
+		return "Loopback.Entry";
+	case 0x48:
+		return "Loopback.Active";
+	case 0x49:
+		return "Loopback.Exit";
+	case 0x4C:
+		return "Loopback.Master.Entry";
+	case 0x4D:
+		return "Loopback.Master.Active";
+	case 0x4E:
+		return "Loopback.Master.Exit";
+	case 0x70:
+		return "Recovery.Speed_Change";
+	case 0x80:
+		return "Detect.Quiet";
+	case 0x81:
+		return "Detect.Active";
+	case 0x82:
+		return "Detect.Wait";
+	case 0x83:
+		return "Detect.Active2";
+	default:
+	break;
+	}
+	return NULL;
+};
+
+/*
+ * The RESCAL block is tied to PCIe controller #1, regardless of the number of
+ * controllers, and turning off PCIe controller #1 prevents access to the RESCAL
+ * register blocks, therefore no other controller can access this register
+ * space, and depending upon the bus fabric we may get a timeout (UBUS/GISB),
+ * or a hang (AXI).
+ */
+#define CFG_QUIRK_AVOID_BRIDGE_SHUTDOWN		BIT(0)
+
+/*
+ * MDIO register map differences and required changes to the defaults mean that refclk
+ * spread-spectrum clocking isn't supportable.
+ */
+#define CFG_QUIRK_NO_SSC			BIT(1)
+
 struct pcie_cfg_data {
 	const int *offsets;
 	const enum pcie_soc_base soc_base;
 	const bool has_phy;
+	const u32 quirks;
 	u8 num_inbound_wins;
 	int (*perst_set)(struct brcm_pcie *pcie, u32 val);
 	int (*bridge_sw_init_set)(struct brcm_pcie *pcie, u32 val);
+	int (*post_setup)(struct brcm_pcie *pcie);
 };
 
 struct subdev_regulators {
@@ -289,6 +479,22 @@ struct brcm_pcie {
 	struct subdev_regulators *sr;
 	bool			ep_wakeup_capable;
 	const struct pcie_cfg_data	*cfg;
+	u32			tperst_clk_ms;
+	bool			trace_ltssm;
+	struct trace_entry	*ltssm_trace_buf;
+	/* Statistics exposed in debugfs */
+	struct dentry *debugfs_dir;
+	u64 tx_tlp;
+	u64 rx_tlp;
+	u64 tx_dllp;
+	u64 rx_dllp;
+	u32 pl_rx_err;
+	u32 dl_rx_err;
+	u32 tl_rx_err;
+	u16 l0s_exit_time;
+	u16 recov_time;
+	u16 l0s_fail_cnt;
+	u16 recov_cnt;
 };
 
 static inline bool is_bmips(const struct brcm_pcie *pcie)
@@ -318,6 +524,7 @@ static u32 brcm_pcie_mdio_form_pkt(int port, int regad, int cmd)
 {
 	u32 pkt = 0;
 
+	pkt |= FIELD_PREP(MDIO_PORT_EXT_MASK, port >> 4);
 	pkt |= FIELD_PREP(MDIO_PORT_MASK, port);
 	pkt |= FIELD_PREP(MDIO_REGAD_MASK, regad);
 	pkt |= FIELD_PREP(MDIO_CMD_MASK, cmd);
@@ -403,10 +610,10 @@ static void brcm_pcie_set_gen(struct brcm_pcie *pcie, int gen)
 	u16 lnkctl2 = readw(pcie->base + BRCM_PCIE_CAP_REGS + PCI_EXP_LNKCTL2);
 	u32 lnkcap = readl(pcie->base + PCIE_RC_CFG_PRIV1_LINK_CAPABILITY);
 
-	lnkcap = (lnkcap & ~PCI_EXP_LNKCAP_SLS) | gen;
+	u32p_replace_bits(&lnkcap, gen, PCI_EXP_LNKCAP_SLS);
 	writel(lnkcap, pcie->base + PCIE_RC_CFG_PRIV1_LINK_CAPABILITY);
 
-	lnkctl2 = (lnkctl2 & ~0xf) | gen;
+	u16p_replace_bits(&lnkctl2, gen, PCI_EXP_LNKCTL2_TLS);
 	writew(lnkctl2, pcie->base + BRCM_PCIE_CAP_REGS + PCI_EXP_LNKCTL2);
 }
 
@@ -463,7 +670,7 @@ static struct irq_chip brcm_msi_irq_chip = {
 
 static struct msi_domain_info brcm_msi_domain_info = {
 	.flags	= MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |
-		  MSI_FLAG_NO_AFFINITY | MSI_FLAG_MULTI_PCI_MSI,
+		  MSI_FLAG_NO_AFFINITY | MSI_FLAG_MULTI_PCI_MSI | MSI_FLAG_PCI_MSIX,
 	.chip	= &brcm_msi_irq_chip,
 };
 
@@ -548,7 +755,7 @@ static int brcm_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
 		return hwirq;
 
 	for (i = 0; i < nr_irqs; i++)
-		irq_domain_set_info(domain, virq + i, hwirq + i,
+		irq_domain_set_info(domain, virq + i, (irq_hw_number_t)hwirq + i,
 				    &brcm_msi_bottom_irq_chip, domain->host_data,
 				    handle_edge_irq, NULL, NULL);
 	return 0;
@@ -715,8 +922,8 @@ static void __iomem *brcm_pcie_map_bus(struct pci_bus *bus,
 
 	/* For devices, write to the config space index register */
 	idx = PCIE_ECAM_OFFSET(bus->number, devfn, 0);
-	writel(idx, pcie->base + PCIE_EXT_CFG_INDEX);
-	return base + PCIE_EXT_CFG_DATA + PCIE_ECAM_REG(where);
+	writel(idx, base + IDX_ADDR(pcie));
+	return base + DATA_ADDR(pcie) + PCIE_ECAM_REG(where);
 }
 
 static void __iomem *brcm7425_pcie_map_bus(struct pci_bus *bus,
@@ -819,6 +1026,141 @@ static int brcm_pcie_perst_set_generic(struct brcm_pcie *pcie, u32 val)
 	return 0;
 }
 
+static int brcm_pcie_post_setup_bcm2712(struct brcm_pcie *pcie)
+{
+	const u16 data[] = { 0x50b9, 0xbda1, 0x0094, 0x97b4, 0x5030, 0x5030, 0x0007 };
+	const u8 regs[] = { 0x16, 0x17, 0x18, 0x19, 0x1b, 0x1c, 0x1e };
+	int ret, i;
+	u32 tmp;
+	u8 qos_map[8];
+
+	/* Allow a 54MHz (xosc) refclk source */
+	ret = brcm_pcie_mdio_write(pcie->base, MDIO_PORT0, SET_ADDR_OFFSET, 0x1600);
+	if (ret < 0)
+		return ret;
+
+	for (i = 0; i < ARRAY_SIZE(regs); i++) {
+		ret = brcm_pcie_mdio_write(pcie->base, MDIO_PORT0, regs[i], data[i]);
+		if (ret < 0)
+			return ret;
+	}
+
+	usleep_range(100, 200);
+
+	/*
+	 * Set L1SS sub-state timers to avoid lengthy state transitions,
+	 * PM clock period is 18.52ns (1/54MHz, round down).
+	 */
+	tmp = readl(pcie->base + PCIE_RC_PL_PHY_CTL_15);
+	tmp &= ~PCIE_RC_PL_PHY_CTL_15_PM_CLK_PERIOD_MASK;
+	tmp |= 0x12;
+	writel(tmp, pcie->base + PCIE_RC_PL_PHY_CTL_15);
+
+	/*
+	 * BCM7712/2712 uses a UBUS-AXI bridge.
+	 * Suppress AXI error responses and return 1s for read failures.
+	 */
+	tmp = readl(pcie->base + PCIE_MISC_UBUS_CTRL);
+	u32p_replace_bits(&tmp, 1, PCIE_MISC_UBUS_CTRL_UBUS_PCIE_REPLY_ERR_DIS_MASK);
+	u32p_replace_bits(&tmp, 1, PCIE_MISC_UBUS_CTRL_UBUS_PCIE_REPLY_DECERR_DIS_MASK);
+	writel(tmp, pcie->base + PCIE_MISC_UBUS_CTRL);
+	writel(0xffffffff, pcie->base + PCIE_MISC_AXI_READ_ERROR_DATA);
+
+	/*
+	 * Adjust timeouts. The UBUS timeout also affects Configuration Request
+	 * Retry responses, as the request will get terminated if
+	 * either timeout expires, so both have to be a large value
+	 * (in clocks of 750MHz).
+	 * Set UBUS timeout to 250ms, then set RC config retry timeout
+	 * to be ~240ms.
+	 *
+	 * If CRSSVE=1 this will stop the core from blocking on a Retry
+	 * response, but does require the device to be well-behaved...
+	 */
+	writel(0xB2D0000, pcie->base + PCIE_MISC_UBUS_TIMEOUT);
+	writel(0xABA0000, pcie->base + PCIE_MISC_RC_CONFIG_RETRY_TIMEOUT);
+
+	/*
+	 * BCM2712 has a configurable QoS mechanism that assigns TLP Traffic Classes
+	 * to separate AXI IDs with a configurable priority scheme.
+	 * Dynamic priority elevation is supported through reception of Type 1
+	 * Vendor Defined Messages, but several bugs make this largely ineffective.
+	 */
+
+	/* Disable broken forwarding search. Set chicken bits for 2712D0 */
+	tmp = readl(pcie->base + PCIE_MISC_AXI_INTF_CTRL);
+	tmp &= ~AXI_REQFIFO_EN_QOS_PROPAGATION;
+	tmp |= AXI_EN_RCLK_QOS_ARRAY_FIX | AXI_EN_QOS_UPDATE_TIMING_FIX |
+		AXI_DIS_QOS_GATING_IN_MASTER;
+	writel(tmp, pcie->base + PCIE_MISC_AXI_INTF_CTRL);
+
+	/*
+	 * Work around spurious QoS=0 assignments to inbound traffic.
+	 * If the QOS_UPDATE_TIMING_FIX bit is Reserved-0, then this is a
+	 * 2712C1 chip, or a single-lane RC. Use the best-effort alternative
+	 * which is to partially throttle AXI requests in-flight to SDRAM.
+	 */
+	tmp = readl(pcie->base + PCIE_MISC_AXI_INTF_CTRL);
+	if (!(tmp & AXI_EN_QOS_UPDATE_TIMING_FIX)) {
+		tmp &= ~AXI_MASTER_MAX_OUTSTANDING_REQUESTS_MASK;
+		tmp |= 15;
+		writel(tmp, pcie->base + PCIE_MISC_AXI_INTF_CTRL);
+	}
+
+	/* Disable VDM reception by default */
+	tmp = readl(pcie->base + PCIE_MISC_CTRL_1);
+	tmp &= ~PCIE_MISC_CTRL_1_EN_VDM_QOS_CONTROL_MASK;
+	writel(tmp, pcie->base + PCIE_MISC_CTRL_1);
+
+	if (!of_property_read_u8_array(pcie->np, "brcm,fifo-qos-map", qos_map, 4)) {
+
+		/*
+		 * Backpressure mode - each element is QoS for each
+		 * quartile of FIFO level. Each TC gets the same map, because
+		 * this mode is intended for nonrealtime EPs.
+		 */
+		tmp = 0;
+		for (i = 0; i < 4; i++) {
+			/* Priorities range from 0-15 */
+			qos_map[i] &= 0x0f;
+			tmp |= qos_map[i] << (i * 4);
+		}
+		for (i = 0; i < 8; i++)
+			writel(tmp, pcie->base + PCIE_MISC_TC_QUEUE_TO_QOS_MAP(i));
+
+	} else if (!of_property_read_u8_array(pcie->np, "brcm,vdm-qos-map", qos_map, 8)) {
+		/* Enable VDM reception */
+		tmp = readl(pcie->base + PCIE_MISC_CTRL_1);
+		tmp |= PCIE_MISC_CTRL_1_EN_VDM_QOS_CONTROL_MASK;
+		writel(tmp, pcie->base + PCIE_MISC_CTRL_1);
+
+		tmp = 0;
+		for (i = 0; i < 8; i++) {
+			/* Priorities range from 0-15 */
+			qos_map[i] &= 0x0f;
+			tmp |= qos_map[i] << (i * 4);
+		}
+		/* Broken forwarding means no point separating panic priorities from normal */
+		writel(tmp, pcie->base + PCIE_MISC_VDM_PRIORITY_TO_QOS_MAP_LO);
+		writel(tmp, pcie->base + PCIE_MISC_VDM_PRIORITY_TO_QOS_MAP_HI);
+
+		/* Match Vendor ID of 0 */
+		writel(0, pcie->base + PCIE_RC_TL_VDM_CTL1);
+
+		/*
+		 * Forward VDMs to priority interface anyway -
+		 * useful for debugging starvation through the received VDM count fields.
+		 */
+		tmp = readl(pcie->base + PCIE_RC_TL_VDM_CTL0);
+		tmp |= PCIE_RC_TL_VDM_CTL0_VDM_ENABLED_MASK |
+			PCIE_RC_TL_VDM_CTL0_VDM_IGNORETAG_MASK |
+			PCIE_RC_TL_VDM_CTL0_VDM_IGNOREVNDRID_MASK;
+		writel(tmp, pcie->base + PCIE_RC_TL_VDM_CTL0);
+	}
+
+	return 0;
+}
+
 static void add_inbound_win(struct inbound_win *b, u8 *count, u64 size,
 			    u64 cpu_addr, u64 pci_offset)
 {
@@ -1028,7 +1370,7 @@ static int brcm_pcie_setup(struct brcm_pcie *pcie)
 	void __iomem *base = pcie->base;
 	struct pci_host_bridge *bridge;
 	struct resource_entry *entry;
-	u32 tmp, burst, num_lanes, num_lanes_cap;
+	u32 tmp, burst, aspm_support;
 	u8 num_out_wins = 0;
 	int num_inbound_wins = 0;
 	int memc, ret;
@@ -1127,33 +1469,16 @@ static int brcm_pcie_setup(struct brcm_pcie *pcie)
 		pcie->msi_target_addr = BRCM_MSI_TARGET_ADDR_GT_4GB;
 
 
-	/* Don't advertise L0s capability if 'aspm-no-l0s' */
+	/* Always advertise L1 capability */
+	aspm_support = PCIE_RC_CFG_PRIV1_LINK_CAPABILITY_ASPM_SUPPORT_L1;
+	/* Advertise L0s capability unless 'aspm-no-l0s' is set */
+	if (!of_property_read_bool(pcie->np, "aspm-no-l0s"))
+		aspm_support |= PCIE_RC_CFG_PRIV1_LINK_CAPABILITY_ASPM_SUPPORT_L0S;
 	tmp = readl(base + PCIE_RC_CFG_PRIV1_LINK_CAPABILITY);
-	if (of_property_read_bool(pcie->np, "aspm-no-l0s"))
-		tmp &= ~PCI_EXP_LNKCAP_ASPM_L0S;
+	u32p_replace_bits(&tmp, aspm_support,
+		PCIE_RC_CFG_PRIV1_LINK_CAPABILITY_ASPM_SUPPORT_MASK);
 	writel(tmp, base + PCIE_RC_CFG_PRIV1_LINK_CAPABILITY);
 
-	/* 'tmp' still holds the contents of PRIV1_LINK_CAPABILITY */
-	num_lanes_cap = u32_get_bits(tmp, PCIE_RC_CFG_PRIV1_LINK_CAPABILITY_MAX_LINK_WIDTH_MASK);
-	num_lanes = 0;
-
-	/*
-	 * Use hardware negotiated Max Link Width value by default.  If the
-	 * "num-lanes" DT property is present, assume that the chip's default
-	 * link width capability information is incorrect/undesired and use the
-	 * specified value instead.
-	 */
-	if (!of_property_read_u32(pcie->np, "num-lanes", &num_lanes) &&
-	    num_lanes && num_lanes <= 4 && num_lanes_cap != num_lanes) {
-		u32p_replace_bits(&tmp, num_lanes,
-			PCIE_RC_CFG_PRIV1_LINK_CAPABILITY_MAX_LINK_WIDTH_MASK);
-		writel(tmp, base + PCIE_RC_CFG_PRIV1_LINK_CAPABILITY);
-		tmp = readl(base + PCIE_RC_PL_REG_PHY_CTL_1);
-		u32p_replace_bits(&tmp, 1,
-			PCIE_RC_PL_REG_PHY_CTL_1_REG_P2_POWERDOWN_ENA_NOSYNC_MASK);
-		writel(tmp, base + PCIE_RC_PL_REG_PHY_CTL_1);
-	}
-
 	/*
 	 * For config space accesses on the RC, show the right class for
 	 * a PCIe-PCIe bridge (the default setting is to be EP mode).
@@ -1200,6 +1525,12 @@ static int brcm_pcie_setup(struct brcm_pcie *pcie)
 		PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1_ENDIAN_MODE_BAR2_MASK);
 	writel(tmp, base + PCIE_RC_CFG_VENDOR_VENDOR_SPECIFIC_REG1);
 
+	if (pcie->cfg->post_setup) {
+		ret = pcie->cfg->post_setup(pcie);
+		if (ret < 0)
+			return ret;
+	}
+
 	return 0;
 }
 
@@ -1235,10 +1566,6 @@ static void brcm_config_clkreq(struct brcm_pcie *pcie)
 		mode = "safe";
 	}
 
-	/* Start out assuming safe mode (both mode bits cleared) */
-	clkreq_cntl = readl(pcie->base + HARD_DEBUG(pcie));
-	clkreq_cntl &= ~PCIE_CLKREQ_MASK;
-
 	if (strcmp(mode, "no-l1ss") == 0) {
 		/*
 		 * "no-l1ss" -- Provides Clock Power Management, L0s, and
@@ -1272,35 +1599,199 @@ static void brcm_config_clkreq(struct brcm_pcie *pcie)
 
 	} else {
 		/*
-		 * "safe" -- No power savings; refclk is driven by RC
+		 * "safe" -- No power savings; refclk and CLKREQ# are driven by RC
 		 * unconditionally.
 		 */
 		if (strcmp(mode, "safe") != 0)
 			dev_err(pcie->dev, err_msg);
 		mode = "safe";
+		clkreq_cntl |= PCIE_MISC_HARD_PCIE_HARD_DEBUG_REFCLK_OVRD_OUT_MASK;
+		clkreq_cntl |= PCIE_MISC_HARD_PCIE_HARD_DEBUG_REFCLK_OVRD_ENABLE_MASK;
+		/*
+		 * Un-advertise L1ss as configuring an EP to enter L1.x with CLKREQ#
+		 * physically unconnected will result in a dead link.
+		 */
+		tmp = readl(pcie->base + PCIE_RC_CFG_PRIV1_ROOT_CAP);
+		u32p_replace_bits(&tmp, 2, PCIE_RC_CFG_PRIV1_ROOT_CAP_L1SS_MODE_MASK);
+		writel(tmp, pcie->base + PCIE_RC_CFG_PRIV1_ROOT_CAP);
 	}
 	writel(clkreq_cntl, pcie->base + HARD_DEBUG(pcie));
 
 	dev_info(pcie->dev, "clkreq-mode set to %s\n", mode);
 }
 
+static void brcm_pcie_stats_trigger(struct brcm_pcie *pcie, u32 micros)
+{
+	u32 tmp;
+
+	/*
+	 * A 0->1 transition on CTRL_EN is required to clear counters and start capture.
+	 * A microseconds count of 0 starts continuous gathering.
+	 */
+	tmp = readl(pcie->base + PCIE_RC_PL_STATS_CTRL);
+	u32p_replace_bits(&tmp, 0, PCIE_RC_PL_STATS_CTRL_EN_MASK);
+	writel(tmp, pcie->base + PCIE_RC_PL_STATS_CTRL);
+
+	if (micros >= (1 << 28))
+		micros = (1 << 28) - 1U;
+	u32p_replace_bits(&tmp, micros, PCIE_RC_PL_STATS_CTRL_LEN_MASK);
+	u32p_replace_bits(&tmp, 1, PCIE_RC_PL_STATS_CTRL_EN_MASK);
+
+	writel(tmp, pcie->base + PCIE_RC_PL_STATS_CTRL);
+}
+
+static void brcm_pcie_stats_capture(struct brcm_pcie *pcie)
+{
+	u32 tmp;
+
+	/* Snapshot the counters - capture engine may still be running */
+	pcie->tx_tlp = (u64)readl(pcie->base + PCIE_RC_PL_STATS_TXTLP_LO) +
+			((u64)readl(pcie->base + PCIE_RC_PL_STATS_TXTLP_HI) << 32ULL);
+	pcie->rx_tlp = (u64)readl(pcie->base + PCIE_RC_PL_STATS_RXTLP_LO) +
+			((u64)readl(pcie->base + PCIE_RC_PL_STATS_RXTLP_HI) << 32ULL);
+	pcie->tx_dllp = (u64)readl(pcie->base + PCIE_RC_PL_STATS_TXDLLP_LO) +
+			((u64)readl(pcie->base + PCIE_RC_PL_STATS_TXDLLP_HI) << 32ULL);
+	pcie->rx_dllp = (u64)readl(pcie->base + PCIE_RC_PL_STATS_RXDLLP_LO) +
+			((u64)readl(pcie->base + PCIE_RC_PL_STATS_RXDLLP_HI) << 32ULL);
+
+	pcie->pl_rx_err = readl(pcie->base + PCIE_RC_PL_STATS_RXPL_ERR);
+	pcie->dl_rx_err = readl(pcie->base + PCIE_RC_PL_STATS_RXDL_ERR);
+	pcie->tl_rx_err = readl(pcie->base + PCIE_RC_PL_STATS_RXTL_ERR);
+
+	tmp = readl(pcie->base + PCIE_RC_PL_LTSSM_STATS_3);
+	pcie->l0s_exit_time = FIELD_GET(PCIE_RC_PL_LTSSM_STATS_3_TIME_L0S_MASK, tmp);
+	pcie->recov_time = FIELD_GET(PCIE_RC_PL_LTSSM_STATS_3_TIME_RECOV_MASK, tmp);
+
+	tmp = readl(pcie->base + PCIE_RC_PL_LTSSM_STATS_CNT);
+	pcie->l0s_fail_cnt = FIELD_GET(PCIE_RC_PL_LTSSM_STATS_CNT_L0S_FAIL_MASK, tmp);
+	pcie->recov_cnt = FIELD_GET(PCIE_RC_PL_LTSSM_STATS_CNT_RECOV_MASK, tmp);
+}
+
+/*
+ * Dump the link state machine transitions for the first 100ms after fundamental reset release.
+ * Most link training completes in a far shorter time.
+ *
+ * The CPU-intensive nature of the capture means that this should only be used to
+ * diagnose fatal link startup failures.
+ */
+static void brcm_pcie_trace_link_start(struct brcm_pcie *pcie)
+{
+	struct device *dev = pcie->dev;
+	struct trace_entry *trace = pcie->ltssm_trace_buf;
+	int i = 0, j = 0;
+	u8 cur_state;
+	u32 ltssm_hist0, ltssm_hist1 = 0;
+	ktime_t start, timeout;
+
+	start = ktime_get();
+	timeout = ktime_add(start, ktime_set(0, NSEC_PER_MSEC * 100));
+	/*
+	 * The LTSSM history registers are implemented as an "open FIFO" where register data
+	 * shuffles along with each push - or moves under one's feet, if you prefer.
+	 * We can't atomically read more than one 32bit value (covering 4 entries), so poll
+	 * quickly while guessing the position of the first value we haven't seen yet.
+	 */
+	do {
+		/*
+		 * This delay appears to work around a HW bug where data can temporarily
+		 * appear nibble-shifted.
+		 */
+		ndelay(10);
+		/* Snapshot the FIFO state. Lowest different "byte" is latest data. */
+		ltssm_hist0 = readl(pcie->base + PCIE_RC_PL_LTSSM_HIST(3));
+		if (ltssm_hist0 == ltssm_hist1)
+			continue;
+		ltssm_hist1 = ltssm_hist0;
+
+		/*
+		 * If the "fifo" has changed, we don't know by how much.
+		 * Scan through byte-wise and look for states
+		 */
+		for (j = 24; j >= 0; j -= 8) {
+			cur_state = (ltssm_hist0 >> j) & 0xff;
+			/* Unassigned entry */
+			if (cur_state == 0xff)
+				continue;
+			if (i > 0 && trace[i-1].st == cur_state) {
+				/*
+				 * This is probably what we last saw.
+				 * Next byte should be a new entry.
+				 */
+				j -= 8;
+				break;
+			} else if (i == 0 && brcm_pcie_decode_ltssm_state(cur_state)) {
+				/* Probably a new valid entry */
+				break;
+			}
+		}
+
+		for (; j >= 0 && i < TRACE_BUF_LEN; j -= 8) {
+			cur_state = (ltssm_hist0 >> j) & 0xff;
+			trace[i].st = cur_state;
+			trace[i].ts = ktime_sub(ktime_get(), start);
+			i++;
+		}
+		if (i == TRACE_BUF_LEN)
+			break;
+	} while (!ktime_after(ktime_get(), timeout));
+
+	dev_info(dev, "LTSSM trace captured %d events (max %u):\n",
+		i, TRACE_BUF_LEN);
+	for (i = 0; i < TRACE_BUF_LEN; i++) {
+		if (!trace[i].st)
+			break;
+		dev_info(dev, "%llu : %02x - %s\n",
+			 ktime_to_us(trace[i].ts),
+			 trace[i].st,
+			 brcm_pcie_decode_ltssm_state(trace[i].st));
+	}
+}
+
 static int brcm_pcie_start_link(struct brcm_pcie *pcie)
 {
 	struct device *dev = pcie->dev;
 	void __iomem *base = pcie->base;
-	u16 nlw, cls, lnksta;
+	u16 nlw, cls, lnksta, tmp16;
 	bool ssc_good = false;
 	int ret, i;
+	u32 tmp;
 
 	/* Limit the generation if specified */
 	if (pcie->gen)
 		brcm_pcie_set_gen(pcie, pcie->gen);
 
+	brcm_pcie_stats_trigger(pcie, 0);
+
+	/* Disable CLKREQ# input prior to link-up */
+	tmp = readl(pcie->base + HARD_DEBUG(pcie));
+	tmp &= ~PCIE_CLKREQ_MASK;
+	writel(tmp, pcie->base + HARD_DEBUG(pcie));
+
 	/* Unassert the fundamental reset */
-	ret = pcie->cfg->perst_set(pcie, 0);
+	if (pcie->tperst_clk_ms) {
+		/*
+		 * Increase Tperst_clk time by forcing PERST# output low while
+		 * the internal reset is released, so the PLL generates stable
+		 * refclk output further in advance of PERST# deassertion.
+		 */
+		tmp = readl(pcie->base + HARD_DEBUG(pcie));
+		u32p_replace_bits(&tmp, 1, PCIE_MISC_HARD_PCIE_HARD_DEBUG_PERST_ASSERT_MASK);
+		writel(tmp, pcie->base + HARD_DEBUG(pcie));
+
+		ret = pcie->cfg->perst_set(pcie, 0);
+		fsleep(pcie->tperst_clk_ms * USEC_PER_MSEC);
+
+		tmp = readl(pcie->base + HARD_DEBUG(pcie));
+		u32p_replace_bits(&tmp, 0, PCIE_MISC_HARD_PCIE_HARD_DEBUG_PERST_ASSERT_MASK);
+		writel(tmp, pcie->base + HARD_DEBUG(pcie));
+	} else {
+		ret = pcie->cfg->perst_set(pcie, 0);
+	}
 	if (ret)
 		return ret;
 
+	if (pcie->trace_ltssm)
+		brcm_pcie_trace_link_start(pcie);
 	/*
 	 * Wait for 100ms after PERST# deassertion; see PCIe CEM specification
 	 * sections 2.2, PCIe r5.0, 6.6.1.
@@ -1337,6 +1828,20 @@ static int brcm_pcie_start_link(struct brcm_pcie *pcie)
 		 pci_speed_string(pcie_link_speed[cls]), nlw,
 		 ssc_good ? "(SSC)" : "(!SSC)");
 
+	/* Snapshot the boot-time stats */
+	brcm_pcie_stats_capture(pcie);
+
+	/*
+	 * RootCtl bits are reset by perst_n, which undoes pci_enable_crs()
+	 * called prior to pci_add_new_bus() during probe. Re-enable here.
+	 */
+	tmp16 = readw(base + BRCM_PCIE_CAP_REGS + PCI_EXP_RTCAP);
+	if (tmp16 & PCI_EXP_RTCAP_CRSVIS) {
+		tmp16 = readw(base + BRCM_PCIE_CAP_REGS + PCI_EXP_RTCTL);
+		u16p_replace_bits(&tmp16, 1, PCI_EXP_RTCTL_CRSSVE);
+		writew(tmp16, base + BRCM_PCIE_CAP_REGS + PCI_EXP_RTCTL);
+	}
+
 	return 0;
 }
 
@@ -1511,8 +2016,9 @@ static int brcm_pcie_turn_off(struct brcm_pcie *pcie)
 	u32p_replace_bits(&tmp, 1, PCIE_MISC_HARD_PCIE_HARD_DEBUG_SERDES_IDDQ_MASK);
 	writel(tmp, base + HARD_DEBUG(pcie));
 
-	/* Shutdown PCIe bridge */
-	ret = pcie->cfg->bridge_sw_init_set(pcie, 1);
+	if (!(pcie->cfg->quirks & CFG_QUIRK_AVOID_BRIDGE_SHUTDOWN))
+		/* Shutdown PCIe bridge */
+		ret = pcie->cfg->bridge_sw_init_set(pcie, 1);
 
 	return ret;
 }
@@ -1656,6 +2162,7 @@ static int brcm_pcie_resume_noirq(struct device *dev)
 
 static void __brcm_pcie_remove(struct brcm_pcie *pcie)
 {
+	debugfs_remove_recursive(pcie->debugfs_dir);
 	brcm_msi_remove(pcie);
 	brcm_pcie_turn_off(pcie);
 	if (brcm_phy_stop(pcie))
@@ -1678,7 +2185,7 @@ static void brcm_pcie_remove(struct platform_device *pdev)
 static const int pcie_offsets[] = {
 	[RGR1_SW_INIT_1]	= 0x9210,
 	[EXT_CFG_INDEX]		= 0x9000,
-	[EXT_CFG_DATA]		= 0x9004,
+	[EXT_CFG_DATA]		= 0x8000,
 	[PCIE_HARD_DEBUG]	= 0x4204,
 	[PCIE_INTR2_CPU_BASE]	= 0x4300,
 };
@@ -1686,7 +2193,7 @@ static const int pcie_offsets[] = {
 static const int pcie_offsets_bcm7278[] = {
 	[RGR1_SW_INIT_1]	= 0xc010,
 	[EXT_CFG_INDEX]		= 0x9000,
-	[EXT_CFG_DATA]		= 0x9004,
+	[EXT_CFG_DATA]		= 0x8000,
 	[PCIE_HARD_DEBUG]	= 0x4204,
 	[PCIE_INTR2_CPU_BASE]	= 0x4300,
 };
@@ -1700,8 +2207,9 @@ static const int pcie_offsets_bcm7425[] = {
 };
 
 static const int pcie_offsets_bcm7712[] = {
+	[RGR1_SW_INIT_1]	= 0x9210,
 	[EXT_CFG_INDEX]		= 0x9000,
-	[EXT_CFG_DATA]		= 0x9004,
+	[EXT_CFG_DATA]		= 0x8000,
 	[PCIE_HARD_DEBUG]	= 0x4304,
 	[PCIE_INTR2_CPU_BASE]	= 0x4400,
 };
@@ -1722,6 +2230,16 @@ static const struct pcie_cfg_data bcm2711_cfg = {
 	.num_inbound_wins = 3,
 };
 
+static const struct pcie_cfg_data bcm2712_cfg = {
+	.offsets	= pcie_offsets_bcm7712,
+	.soc_base	= BCM7712,
+	.perst_set	= brcm_pcie_perst_set_7278,
+	.bridge_sw_init_set = brcm_pcie_bridge_sw_init_set_generic,
+	.post_setup	= brcm_pcie_post_setup_bcm2712,
+	.quirks		= CFG_QUIRK_AVOID_BRIDGE_SHUTDOWN | CFG_QUIRK_NO_SSC,
+	.num_inbound_wins = 10,
+};
+
 static const struct pcie_cfg_data bcm4908_cfg = {
 	.offsets	= pcie_offsets,
 	.soc_base	= BCM4908,
@@ -1773,6 +2291,7 @@ static const struct pcie_cfg_data bcm7712_cfg = {
 
 static const struct of_device_id brcm_pcie_match[] = {
 	{ .compatible = "brcm,bcm2711-pcie", .data = &bcm2711_cfg },
+	{ .compatible = "brcm,bcm2712-pcie", .data = &bcm2712_cfg },
 	{ .compatible = "brcm,bcm4908-pcie", .data = &bcm4908_cfg },
 	{ .compatible = "brcm,bcm7211-pcie", .data = &generic_cfg },
 	{ .compatible = "brcm,bcm7216-pcie", .data = &bcm7216_cfg },
@@ -1800,6 +2319,98 @@ static struct pci_ops brcm7425_pcie_ops = {
 	.remove_bus = brcm_pcie_remove_bus,
 };
 
+static ssize_t debugfs_stats_trigger_write(struct file *filp,
+					   const char __user *buf,
+					   size_t count, loff_t *ppos)
+{
+	struct seq_file *m = filp->private_data;
+	struct brcm_pcie *pcie = m->private;
+	char kbuf[16] = {};
+	unsigned long micros;
+
+	if (count > sizeof(kbuf))
+		return -EOVERFLOW;
+
+	if (copy_from_user(kbuf, buf, count))
+		return -EINVAL;
+
+	if (kstrtol(kbuf, 0, &micros) < 0)
+		return -EINVAL;
+
+	if (micros >= (1 << 28))
+		return -ERANGE;
+
+	brcm_pcie_stats_trigger(pcie, micros);
+	return count;
+}
+
+static int debugfs_stats_trigger_show(struct seq_file *s, void *unused)
+{
+	struct brcm_pcie *pcie = s->private;
+	u32 tmp;
+
+	/* Return the state of the capture engine */
+	tmp = readl(pcie->base + PCIE_RC_PL_STATS_CTRL);
+	tmp = FIELD_GET(PCIE_RC_PL_STATS_CTRL_EN_MASK, tmp);
+	seq_printf(s, "%u\n", tmp);
+	return 0;
+}
+DEFINE_SHOW_STORE_ATTRIBUTE(debugfs_stats_trigger);
+
+static ssize_t debugfs_stats_snapshot_write(struct file *filp,
+					    const char __user *buf,
+					    size_t count, loff_t *ppos)
+{
+	struct seq_file *m = filp->private_data;
+	struct brcm_pcie *pcie = m->private;
+
+	/* Any write triggers a snapshot of the stats register set */
+	brcm_pcie_stats_capture(pcie);
+	return count;
+}
+
+static int debugfs_stats_snapshot_show(struct seq_file *s, void *unused)
+{
+	struct brcm_pcie *pcie = s->private;
+
+	seq_printf(s, "tx_tlp:\t\t%llu\n", pcie->tx_tlp);
+	seq_printf(s, "rx_tlp:\t\t%llu\n", pcie->rx_tlp);
+	seq_printf(s, "tx_dllp:\t%llu\n", pcie->tx_dllp);
+	seq_printf(s, "rx_dllp:\t%llu\n", pcie->rx_dllp);
+	seq_printf(s, "pl_rx_err:\t%u\n", pcie->pl_rx_err);
+	seq_printf(s, "dl_rx_err:\t%u\n", pcie->dl_rx_err);
+	seq_printf(s, "tl_rx_err:\t%u\n", pcie->tl_rx_err);
+	seq_printf(s, "l0s_exit_time:\t%u\n", pcie->l0s_exit_time);
+	seq_printf(s, "recov_time:\t%u\n", pcie->recov_time);
+	seq_printf(s, "l0s_fail_cnt\t%u\n", pcie->l0s_fail_cnt);
+	seq_printf(s, "recov_cnt:\t%u\n", pcie->recov_cnt);
+
+	return 0;
+}
+DEFINE_SHOW_STORE_ATTRIBUTE(debugfs_stats_snapshot);
+
+static void brcm_pcie_init_debugfs(struct brcm_pcie *pcie)
+{
+	char *name;
+
+	name = devm_kasprintf(pcie->dev, GFP_KERNEL, "%pOFP", pcie->dev->of_node);
+	if (!name)
+		return;
+
+	pcie->debugfs_dir = debugfs_create_dir(name, NULL);
+	if (!pcie->debugfs_dir)
+		return;
+
+	debugfs_create_file("stats_snapshot", 0644, pcie->debugfs_dir, pcie,
+			    &debugfs_stats_snapshot_fops);
+	debugfs_create_file("stats_trigger", 0644, pcie->debugfs_dir, pcie,
+			    &debugfs_stats_trigger_fops);
+}
+
+static bool trace_ltssm;
+module_param(trace_ltssm, bool, 0444);
+MODULE_PARM_DESC(trace_ltssm, "Capture and dump link states during link training");
+
 static int brcm_pcie_probe(struct platform_device *pdev)
 {
 	struct device_node *np = pdev->dev.of_node;
@@ -1822,6 +2433,19 @@ static int brcm_pcie_probe(struct platform_device *pdev)
 	pcie->dev = &pdev->dev;
 	pcie->np = np;
 	pcie->cfg = data;
+	pcie->trace_ltssm = trace_ltssm;
+
+	brcm_pcie_init_debugfs(pcie);
+
+	if (pcie->trace_ltssm) {
+		pcie->ltssm_trace_buf = devm_kzalloc(&pdev->dev,
+						     sizeof(struct trace_entry) * TRACE_BUF_LEN,
+						     GFP_KERNEL);
+		if (!pcie->ltssm_trace_buf) {
+			dev_err(&pdev->dev, "could not allocate trace buffer\n");
+			return -ENOMEM;
+		}
+	}
 
 	pcie->base = devm_platform_ioremap_resource(pdev, 0);
 	if (IS_ERR(pcie->base))
@@ -1834,7 +2458,10 @@ static int brcm_pcie_probe(struct platform_device *pdev)
 	ret = of_pci_get_max_link_speed(np);
 	pcie->gen = (ret < 0) ? 0 : ret;
 
-	pcie->ssc = of_property_read_bool(np, "brcm,enable-ssc");
+	pcie->ssc = !(pcie->cfg->quirks & CFG_QUIRK_NO_SSC) &&
+		    of_property_read_bool(np, "brcm,enable-ssc");
+
+	of_property_read_u32(np, "brcm,tperst-clk-ms", &pcie->tperst_clk_ms);
 
 	pcie->rescal = devm_reset_control_get_optional_shared(&pdev->dev, "rescal");
 	if (IS_ERR(pcie->rescal))
diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c
index 9e71eb4d1010..96e81ee240dd 100644
--- a/drivers/pci/probe.c
+++ b/drivers/pci/probe.c
@@ -994,9 +994,6 @@ static int pci_register_host_bridge(struct pci_host_bridge *bridge)
 	else
 		pr_info("PCI host bridge to bus %s\n", name);
 
-	if (nr_node_ids > 1 && pcibus_to_node(bus) == NUMA_NO_NODE)
-		dev_warn(&bus->dev, "Unknown NUMA node; performance will be reduced\n");
-
 	/* Check if the boot configuration by FW needs to be preserved */
 	bridge->preserve_config = pci_preserve_config(bridge);
 
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index 3d05ea35c536..f2c11b778e43 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -6276,6 +6276,22 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x56b0, aspm_l1_acceptable_latency
 DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x56b1, aspm_l1_acceptable_latency);
 DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x56c0, aspm_l1_acceptable_latency);
 DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x56c1, aspm_l1_acceptable_latency);
+
+/*
+ * VL805 firmware can report 0 in the L0s/L1 Acceptable Latency fields.
+ * This is shorter than its own exit latency so ASPM for the link partner
+ * never gets enabled (but firmware toggles EP L1/L0s enable internally).
+ * However, L0s is flaky so explicitly disable it.
+ */
+static void vl805_aspm_fixup(struct pci_dev *dev)
+{
+	dev->devcap &= ~PCI_EXP_DEVCAP_L1;
+	/* Set to own exit latency + 1 */
+	dev->devcap |= FIELD_PREP(PCI_EXP_DEVCAP_L1, 5);
+	pci_disable_link_state(dev, PCIE_LINK_STATE_L0S);
+	pci_info(dev, "ASPM: VL805 fixup applied\n");
+}
+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_VIA, 0x3483, vl805_aspm_fixup);
 #endif
 
 #ifdef CONFIG_PCIE_DPC
diff --git a/drivers/perf/Kconfig b/drivers/perf/Kconfig
index bab8ba64162f..09bb65a4dc75 100644
--- a/drivers/perf/Kconfig
+++ b/drivers/perf/Kconfig
@@ -251,6 +251,14 @@ config ALIBABA_UNCORE_DRW_PMU
 	  Support for Driveway PMU events monitoring on Yitian 710 DDR
 	  Sub-system.
 
+config RPI_AXIPERF
+        depends on ARCH_BCM2835
+        tristate "RaspberryPi AXI Performance monitors"
+        default n
+        help
+          Say y if you want to use Raspberry Pi AXI performance monitors, m if
+          you want to build it as a module.
+
 source "drivers/perf/hisilicon/Kconfig"
 
 config MARVELL_CN10K_DDR_PMU
diff --git a/drivers/perf/Makefile b/drivers/perf/Makefile
index 8268f38e42c5..6318355bbf13 100644
--- a/drivers/perf/Makefile
+++ b/drivers/perf/Makefile
@@ -32,3 +32,4 @@ obj-$(CONFIG_DWC_PCIE_PMU) += dwc_pcie_pmu.o
 obj-$(CONFIG_ARM_CORESIGHT_PMU_ARCH_SYSTEM_PMU) += arm_cspmu/
 obj-$(CONFIG_MESON_DDR_PMU) += amlogic/
 obj-$(CONFIG_CXL_PMU) += cxl_pmu.o
+obj-$(CONFIG_RPI_AXIPERF) += raspberrypi_axi_monitor.o
diff --git a/drivers/perf/raspberrypi_axi_monitor.c b/drivers/perf/raspberrypi_axi_monitor.c
new file mode 100644
index 000000000000..b5ad12a2a9ab
--- /dev/null
+++ b/drivers/perf/raspberrypi_axi_monitor.c
@@ -0,0 +1,826 @@
+/*
+ * raspberrypi_axi_monitor.c
+ *
+ * Author: james.hughes@raspberrypi.org
+ *
+ * Raspberry Pi AXI performance counters.
+ *
+ * Copyright (C) 2017 Raspberry Pi Trading Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/devcoredump.h>
+#include <linux/device.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
+#define NUM_MONITORS 2
+#define NUM_BUS_WATCHERS_PER_MONITOR 3
+
+#define SYSTEM_MONITOR 0
+#define VPU_MONITOR 1
+
+#define MAX_BUSES 16
+#define DEFAULT_SAMPLE_TIME 100
+
+#define NUM_BUS_WATCHER_RESULTS 11
+
+struct bus_watcher_data {
+	union	{
+		u32 results[NUM_BUS_WATCHER_RESULTS];
+		struct {
+			u32 atrans;
+			u32 atwait;
+			u32 amax;
+			u32 wtrans;
+			u32 wtwait;
+			u32 wmax;
+			u32 rtrans;
+			u32 rtwait;
+			u32 rmax;
+			u32 rpend;
+			u32 ratrans;
+		};
+	};
+};
+
+
+struct rpi_axiperf {
+	struct platform_device *dev;
+	struct dentry *root_folder;
+
+	struct task_struct *monitor_thread;
+	struct mutex lock;
+
+	struct rpi_firmware *firmware;
+
+	/* Sample time spent on for each bus */
+	int sample_time;
+
+	/* chip specific bus config */
+	const struct bwconfig_config *config;
+
+	/* Now storage for the per monitor settings and the resulting
+	 * performance figures
+	 */
+	struct {
+		/* Bit field of buses we want to monitor */
+		int bus_enabled;
+		/* Bit field of buses to filter by */
+		int bus_filter;
+		/* The current buses being monitored on this monitor */
+		int current_bus[NUM_BUS_WATCHERS_PER_MONITOR];
+		/* The last bus monitored on this monitor */
+		int last_monitored;
+
+		/* Set true if this mailbox must use the mailbox interface
+		 * rather than access registers directly.
+		 */
+		int use_mailbox_interface;
+
+		/* Current result values */
+		struct bus_watcher_data results[MAX_BUSES];
+
+		struct dentry *debugfs_entry;
+		void __iomem *base_address;
+
+	}  monitor[NUM_MONITORS];
+
+};
+
+static struct rpi_axiperf *state;
+
+/* Two monitors, System and VPU, each with the following register sets.
+ * Each monitor can only monitor one bus at a time, so we time share them,
+ * giving each bus 100ms (default, settable via debugfs) of time on its
+ * associated monitor
+ * Record results from the three Bus watchers per monitor and push to the sysfs
+ */
+
+/* general registers */
+const int GEN_CTRL;
+
+const int GEN_CTL_ENABLE_BIT	= BIT(0);
+const int GEN_CTL_RESET_BIT	= BIT(1);
+const int GEN_CTL_WATCH_BIT	= BIT(2);
+
+/* Bus watcher registers */
+const int BW_PITCH		= 0x40;
+
+const int BW0_CTRL		= 0x40;
+const int BW1_CTRL		= 0x80;
+const int BW2_CTRL		= 0xc0;
+
+const int BW_ATRANS_OFFSET	= 0x04;
+const int BW_ATWAIT_OFFSET	= 0x08;
+const int BW_AMAX_OFFSET	= 0x0c;
+const int BW_WTRANS_OFFSET	= 0x10;
+const int BW_WTWAIT_OFFSET	= 0x14;
+const int BW_WMAX_OFFSET	= 0x18;
+const int BW_RTRANS_OFFSET	= 0x1c;
+const int BW_RTWAIT_OFFSET	= 0x20;
+const int BW_RMAX_OFFSET	= 0x24;
+
+const int BW_CTRL_RESET_BIT	= BIT(31);
+const int BW_CTRL_ENABLE_BIT	= BIT(30);
+const int BW_CTRL_ENABLE_ID_FILTER_BIT	= BIT(29);
+const int BW_CTRL_LIMIT_HALT_BIT	= BIT(28);
+
+const int BW_CTRL_SOURCE_SHIFT	= 8;
+const int BW_CTRL_SOURCE_MASK	= GENMASK(12, 8); // 5 bits
+const int BW_CTRL_BUS_WATCH_SHIFT;
+const int BW_CTRL_BUS_WATCH_MASK = GENMASK(5, 0); // 6 bits
+const int BW_CTRL_BUS_FILTER_SHIFT = 8;
+
+static const char *bus_filter_strings[] = {
+	"",
+	"CORE0_V",
+	"ICACHE0",
+	"DCACHE0",
+	"CORE1_V",
+	"ICACHE1",
+	"DCACHE1",
+	"L2_MAIN",
+	"HOST_PORT",
+	"HOST_PORT2",
+	"HVS",
+	"ISP",
+	"VIDEO_DCT",
+	"VIDEO_SD2AXI",
+	"CAM0",
+	"CAM1",
+	"DMA0",
+	"DMA1",
+	"DMA2_VPU",
+	"JPEG",
+	"VIDEO_CME",
+	"TRANSPOSER",
+	"VIDEO_FME",
+	"CCP2TX",
+	"USB",
+	"V3D0",
+	"V3D1",
+	"V3D2",
+	"AVE",
+	"DEBUG",
+	"CPU",
+	"M30"
+};
+
+static const char * const bus_filter_strings_2711[] = {
+	"AIO",
+	"CORE0_V",
+	"ICACHE0",
+	"DCACHE0",
+	"CORE1_V",
+	"ICACHE1",
+	"DCACHE1",
+	"L2_MAIN",
+	"ARGON",
+	"PCIE",
+	"HVS",
+	"ISP",
+	"VIDEO_DCT",
+	"VIDEO_SD2AXI",
+	"CAM0",
+	"CAM1",
+	"DMA0",
+	"DMA1",
+	"DMA2",
+	"JPEG",
+	"VIDEO_CME",
+	"TRANSPOSER",
+	"VIDEO_FME",
+	"GIGE",
+	"USB",
+	"V3D0",
+	"V3D1",
+	"V3D2",
+	"GISB_AXI",
+	"DEBUG",
+	"ARM",
+	"EMMCSTB",
+};
+
+static const char * const bus_filter_strings_2712[] = {
+	"",
+	"VPU_UC0",
+	"VPU_IC0",
+	"VPU_DC0",
+	"VPU_UC1",
+	"VPU_IC1",
+	"VPU_DC1",
+	"VPU_L2",
+	"DMA2",
+	"VPU_DEBUG",
+	"ARM",
+	"DMA0",
+	"DMA1",
+	"RAAGA",
+	"BBSI",
+	"PCIE0",
+	"PCIE1",
+	"PCIE2",
+	"UMR",
+	"SAGE",
+	"HVDP",
+	"BSP",
+	"HVS",
+	"HVS_WMK",
+	"MOP0",
+	"MOP1",
+	"MBVN",
+	"DSI",
+	"XPT",
+	"EMMC0",
+	"GENET",
+	"USB",
+	"ARGON",
+	"UNICAM",
+	"PISP",
+	"PISPFE",
+	"JPEG",
+	"EMMC1",
+	"EMMC2",
+	"TRC",
+	"BSTM0",
+	"BSTM1",
+	"BSTM0_SEC",
+	"BSTM1_SEC",
+	"AIO",
+	"MAP",
+	"SYS_DMA",
+	"MMUCACHE0",
+	"MMUCACHE1",
+	"MPUCACHE0",
+	"MPUCACHE1",
+};
+
+static const char *system_bus_string[] = {
+	"DMA_L2",
+	"TRANS",
+	"JPEG",
+	"SYSTEM_UC",
+	"DMA_UC",
+	"SYSTEM_L2",
+	"CCP2TX",
+	"MPHI_RX",
+	"MPHI_TX",
+	"HVS",
+	"H264",
+	"ISP",
+	"V3D",
+	"PERIPHERAL",
+	"CPU_UC",
+	"CPU_L2"
+};
+
+static const char * const system_bus_string_2711[] = {
+	"DMA_L2",
+	"TRANS",
+	"JPEG",
+	"VPU_UC",
+	"DMA_UC",
+	"SYSTEM_L2",
+	"HVS",
+	"ARGON",
+	"H264",
+	"PERIPHERAL",
+	"ARM_UC",
+	"ARM_L2",
+};
+
+static const char * const system_bus_string_2712[] = {
+	"VPU_UC",
+	"DISPLAY_TOP",
+	"V3D",
+	"ARM",
+	"XPT",
+	"BSTM_TOP",
+	"PCIE_01",
+	"ARGON_TOP",
+	"ARB3",
+	"SRC",
+	"HVDP",
+	"PER",
+	"SYSTEM_L2",
+};
+
+static const char *vpu_bus_string[] = {
+	"VPU1_D_L2",
+	"VPU0_D_L2",
+	"VPU1_I_L2",
+	"VPU0_I_L2",
+	"SYSTEM_L2",
+	"L2_FLUSH",
+	"DMA_L2",
+	"VPU1_D_UC",
+	"VPU0_D_UC",
+	"VPU1_I_UC",
+	"VPU0_I_UC",
+	"SYSTEM_UC",
+	"L2_OUT",
+	"DMA_UC",
+	"SDRAM",
+	"L2_IN"
+};
+
+static const char * const vpu_bus_string_2711[] = {
+	"VPU1_D_L2",
+	"VPU0_D_L2",
+	"VPU1_I_L2",
+	"VPU0_I_L2",
+	"SYSTEM_L2",
+	"DMA_L2",
+	"VPU1_D_UC",
+	"VPU0_D_UC",
+	"VPU1_I_UC",
+	"VPU0_I_UC",
+	"VPU_UC",
+	"L2_OUT",
+	"DMA_UC",
+	"L2_IN"
+};
+
+static const char * const vpu_bus_string_2712[] = {
+	"VPU1_D_L2",
+	"VPU0_D_L2",
+	"VPU1_I_L2",
+	"VPU0_I_L2",
+	"SYSTEM_L2",
+	"DMA_L2",
+	"VPU1_D_UC",
+	"VPU0_D_UC",
+	"VPU1_I_UC",
+	"VPU0_I_UC",
+	"VPU_UC",
+	"L2_OUT",
+	"DMA_UC",
+	"L2_IN"
+};
+
+struct bwconfig_config {
+	const char * const *bus_filter_strings;
+	const int num_bus_filters;
+	const char * const *system_bus_string;
+	const int num_system_buses;
+	const char * const *vpu_bus_string;
+	const int num_vpu_buses;
+};
+
+static const struct bwconfig_config config_2835 = {
+	bus_filter_strings, ARRAY_SIZE(bus_filter_strings),
+	system_bus_string, ARRAY_SIZE(system_bus_string),
+	vpu_bus_string, ARRAY_SIZE(vpu_bus_string),
+};
+
+static const struct bwconfig_config config_2711 = {
+	bus_filter_strings_2711, ARRAY_SIZE(bus_filter_strings_2711),
+	system_bus_string_2711, ARRAY_SIZE(system_bus_string_2711),
+	vpu_bus_string_2711, ARRAY_SIZE(vpu_bus_string_2711),
+};
+
+static const struct bwconfig_config config_2712 = {
+	bus_filter_strings_2712, ARRAY_SIZE(bus_filter_strings_2712),
+	system_bus_string_2712, ARRAY_SIZE(system_bus_string_2712),
+	vpu_bus_string_2712, ARRAY_SIZE(vpu_bus_string_2712),
+};
+
+static const char *monitor_name[] = {
+	"System",
+	"VPU"
+};
+
+static inline void write_reg(int monitor, int reg, u32 value)
+{
+	writel(value, state->monitor[monitor].base_address + reg);
+}
+
+static inline u32 read_reg(int monitor, u32 reg)
+{
+	return readl(state->monitor[monitor].base_address + reg);
+}
+
+static void read_bus_watcher(int monitor, int watcher, u32 *results)
+{
+	if (state->monitor[monitor].use_mailbox_interface) {
+		/* We have NUM_BUS_WATCHER_RESULTS results, plus the overheads
+		 * of start address and length
+		 */
+		u32 tmp[NUM_BUS_WATCHER_RESULTS+2];
+		int err;
+
+		tmp[0] = (u32)(uintptr_t)(state->monitor[monitor].base_address + watcher
+				+ BW_ATRANS_OFFSET);
+		tmp[1] = NUM_BUS_WATCHER_RESULTS;
+
+		err = rpi_firmware_property(state->firmware,
+					    RPI_FIRMWARE_GET_PERIPH_REG,
+					    tmp, sizeof(tmp));
+
+		if (err < 0 || tmp[1] != NUM_BUS_WATCHER_RESULTS)
+			dev_err_once(&state->dev->dev,
+				     "Failed to read bus watcher");
+		else
+			memcpy(results, &tmp[2],
+			       NUM_BUS_WATCHER_RESULTS * sizeof(u32));
+	} else {
+		int i;
+		void __iomem *addr = state->monitor[monitor].base_address
+				+ watcher + BW_ATRANS_OFFSET;
+		for (i = 0; i < NUM_BUS_WATCHER_RESULTS; i++, addr += 4)
+			*results++ = readl(addr);
+	}
+}
+
+static void set_monitor_control(int monitor, u32 set)
+{
+	if (state->monitor[monitor].use_mailbox_interface) {
+		u32 tmp[3] = {(u32)(uintptr_t)(state->monitor[monitor].base_address +
+				GEN_CTRL), 1, set};
+		int err = rpi_firmware_property(state->firmware,
+						RPI_FIRMWARE_SET_PERIPH_REG,
+						tmp, sizeof(tmp));
+
+		if (err < 0 || tmp[1] != 1)
+			dev_err_once(&state->dev->dev,
+				"Failed to set monitor control");
+	} else
+		write_reg(monitor, GEN_CTRL, set);
+}
+
+static void set_bus_watcher_control(int monitor, int watcher, u32 set)
+{
+	if (state->monitor[monitor].use_mailbox_interface) {
+		u32 tmp[3] = {(u32)(uintptr_t)(state->monitor[monitor].base_address +
+				    watcher), 1, set};
+		int err = rpi_firmware_property(state->firmware,
+						RPI_FIRMWARE_SET_PERIPH_REG,
+						tmp, sizeof(tmp));
+		if (err < 0 || tmp[1] != 1)
+			dev_err_once(&state->dev->dev,
+				"Failed to set bus watcher control");
+	} else
+		write_reg(monitor, watcher, set);
+}
+
+static void monitor(struct rpi_axiperf *state)
+{
+	int monitor, num_buses[NUM_MONITORS];
+
+	mutex_lock(&state->lock);
+
+	for (monitor = 0; monitor < NUM_MONITORS; monitor++) {
+		typeof(state->monitor[0]) *mon = &(state->monitor[monitor]);
+
+		/* Anything enabled? */
+		if (mon->bus_enabled == 0) {
+			/* No, disable all monitoring for this monitor */
+			set_monitor_control(monitor, GEN_CTL_RESET_BIT);
+		} else {
+			int i;
+
+			/* Find out how many busses we want to monitor, and
+			 * spread our 3 actual monitors over them
+			 */
+			num_buses[monitor] = hweight32(mon->bus_enabled);
+			num_buses[monitor] = min(num_buses[monitor],
+						 NUM_BUS_WATCHERS_PER_MONITOR);
+
+			for (i = 0; i < num_buses[monitor]; i++) {
+				int bus_control;
+
+				do {
+					mon->last_monitored++;
+					mon->last_monitored &= 0xf;
+				} while ((mon->bus_enabled &
+					 (1 << mon->last_monitored)) == 0);
+
+				mon->current_bus[i] = mon->last_monitored;
+
+				/* Reset the counters */
+				set_bus_watcher_control(monitor,
+							BW0_CTRL +
+							i*BW_PITCH,
+							BW_CTRL_RESET_BIT);
+
+				bus_control = BW_CTRL_ENABLE_BIT |
+						mon->current_bus[i];
+
+				if (mon->bus_filter) {
+					bus_control |=
+						BW_CTRL_ENABLE_ID_FILTER_BIT;
+					bus_control |=
+						((mon->bus_filter & 0x1f)
+						<< BW_CTRL_BUS_FILTER_SHIFT);
+				}
+
+				// Start capture
+				set_bus_watcher_control(monitor,
+							BW0_CTRL + i*BW_PITCH,
+							bus_control);
+			}
+		}
+
+		/* start monitoring */
+		set_monitor_control(monitor, GEN_CTL_ENABLE_BIT | GEN_CTL_WATCH_BIT);
+	}
+
+	mutex_unlock(&state->lock);
+
+	msleep(state->sample_time);
+
+	/* Now read the results */
+
+	mutex_lock(&state->lock);
+	for (monitor = 0; monitor < NUM_MONITORS; monitor++) {
+		typeof(state->monitor[0]) *mon = &(state->monitor[monitor]);
+
+		/* Anything enabled? */
+		if (mon->bus_enabled == 0) {
+			/* No, disable all monitoring for this monitor */
+			set_monitor_control(monitor, 0);
+		} else {
+			int i;
+
+			for (i = 0; i < num_buses[monitor]; i++) {
+				int bus = mon->current_bus[i];
+
+				read_bus_watcher(monitor,
+					BW0_CTRL + i*BW_PITCH,
+					(u32 *)&mon->results[bus].results);
+			}
+		}
+	}
+	mutex_unlock(&state->lock);
+}
+
+static int monitor_thread(void *data)
+{
+	struct rpi_axiperf *state  = data;
+
+	while (1) {
+		monitor(state);
+
+		if (kthread_should_stop())
+			return 0;
+	}
+	return 0;
+}
+
+static ssize_t myreader(struct file *fp, char __user *user_buffer,
+			size_t count, loff_t *position)
+{
+#define INIT_BUFF_SIZE 2048
+
+	int i;
+	int idx = (int)(uintptr_t)(fp->private_data);
+	int num_buses, cnt;
+	char *string_buffer;
+	int buff_size = INIT_BUFF_SIZE;
+	char *p;
+	typeof(state->monitor[0]) *mon = &(state->monitor[idx]);
+	const struct bwconfig_config *config = state->config;
+
+	if (idx < 0 || idx > NUM_MONITORS)
+		idx = 0;
+
+	num_buses = idx == SYSTEM_MONITOR ? config->num_system_buses : config->num_vpu_buses;
+
+	string_buffer = kmalloc(buff_size, GFP_KERNEL);
+
+	if (!string_buffer) {
+		dev_err(&state->dev->dev,
+				"Failed temporary string allocation\n");
+		return 0;
+	}
+
+	p = string_buffer;
+
+	mutex_lock(&state->lock);
+
+	if (mon->bus_filter) {
+		int filt = min(mon->bus_filter & 0x1f, config->num_bus_filters);
+
+		cnt = snprintf(p, buff_size,
+			       "\nMonitoring transactions from %s only\n",
+			       config->bus_filter_strings[filt]);
+		p += cnt;
+		buff_size -= cnt;
+	}
+
+	cnt = snprintf(p, buff_size, "     Bus    |    Atrans    Atwait      AMax    Wtrans    Wtwait      WMax    Rtrans    Rtwait      RMax     RPend   RAtrans\n"
+				     "===========================================================================================================================\n");
+
+	if (cnt >= buff_size)
+		goto done;
+
+	p += cnt;
+	buff_size -= cnt;
+
+#define M(x) ((x) >= 1000000000 ? (x)/1000000 : (x) >= 1000 ? (x)/1000 : (x))
+#define N(x) ((x) >= 1000000000 ? 'M' : (x) >= 1000 ? 'K' : ' ')
+
+	for (i = 0; i < num_buses; i++) {
+		if (mon->bus_enabled & (1 << i)) {
+			typeof(mon->results[0]) *res = &(mon->results[i]);
+
+			cnt = snprintf(p, buff_size,
+					"%11s | %8u%c %8u%c %8u%c %8u%c %8u%c %8u%c %8u%c %8u%c %8u%c %8u%c %8u%c\n",
+					idx == SYSTEM_MONITOR ?
+						config->system_bus_string[i] :
+						config->vpu_bus_string[i],
+					M(res->atrans), N(res->atrans),
+					M(res->atwait), N(res->atwait),
+					M(res->amax), N(res->amax),
+					M(res->wtrans), N(res->wtrans),
+					M(res->wtwait), N(res->wtwait),
+					M(res->wmax), N(res->wmax),
+					M(res->rtrans), N(res->rtrans),
+					M(res->rtwait), N(res->rtwait),
+					M(res->rmax), N(res->rmax),
+					M(res->rpend), N(res->rpend),
+					M(res->ratrans), N(res->ratrans)
+					);
+			if (cnt >= buff_size)
+				goto done;
+
+			p += cnt;
+			buff_size -= cnt;
+		}
+	}
+
+	mutex_unlock(&state->lock);
+
+done:
+
+	/* did the last string entry exceeed our buffer size? ie out of string
+	 * buffer space. Null terminate, use what we have.
+	 */
+	if (cnt >= buff_size) {
+		buff_size = 0;
+		string_buffer[INIT_BUFF_SIZE] = 0;
+	}
+
+	cnt = simple_read_from_buffer(user_buffer, count, position,
+				      string_buffer,
+				      INIT_BUFF_SIZE - buff_size);
+
+	kfree(string_buffer);
+
+	return cnt;
+}
+
+static ssize_t mywriter(struct file *fp, const char __user *user_buffer,
+			size_t count, loff_t *position)
+{
+	int idx = (int)(uintptr_t)(fp->private_data);
+
+	if (idx < 0 || idx > NUM_MONITORS)
+		idx = 0;
+
+	/* At the moment, this does nothing, but in the future it could be
+	 * used to reset counters etc
+	 */
+	return count;
+}
+
+static const struct file_operations fops_debug = {
+	.read = myreader,
+	.write = mywriter,
+	.open = simple_open
+};
+
+static int rpi_axiperf_probe(struct platform_device *pdev)
+{
+	int ret = 0, i;
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	struct device_node *fw_node;
+
+	state = kzalloc(sizeof(struct rpi_axiperf), GFP_KERNEL);
+	if (!state)
+		return -ENOMEM;
+
+	state->config = of_device_get_match_data(dev);
+	if (!state->config)
+		return -EINVAL;
+
+	/* Get the firmware handle for future rpi-firmware-xxx calls */
+	fw_node = of_parse_phandle(np, "firmware", 0);
+	if (!fw_node) {
+		dev_err(dev, "Missing firmware node\n");
+		return -ENOENT;
+	}
+
+	state->firmware = rpi_firmware_get(fw_node);
+	if (!state->firmware)
+		return -EPROBE_DEFER;
+
+	/* Special case for the VPU monitor, we must use the mailbox interface
+	 * as it is not accessible from the ARM address space.
+	 */
+	state->monitor[VPU_MONITOR].use_mailbox_interface = 1;
+	state->monitor[SYSTEM_MONITOR].use_mailbox_interface = 0;
+
+	for (i = 0; i < NUM_MONITORS; i++) {
+		if (state->monitor[i].use_mailbox_interface) {
+			 of_property_read_u32_index(np, "reg", i*2,
+				(u32 *)(&state->monitor[i].base_address));
+		} else {
+			struct resource *resource =
+				platform_get_resource(pdev, IORESOURCE_MEM, i);
+
+			state->monitor[i].base_address =
+				devm_ioremap_resource(&pdev->dev, resource);
+		}
+
+		if (IS_ERR(state->monitor[i].base_address))
+			return PTR_ERR(state->monitor[i].base_address);
+
+		/* Enable all buses by default */
+		state->monitor[i].bus_enabled = 0xffff;
+	}
+
+	state->dev = pdev;
+	platform_set_drvdata(pdev, state);
+
+	state->sample_time = DEFAULT_SAMPLE_TIME;
+
+	/* Set up all the debugfs stuff */
+	state->root_folder = debugfs_create_dir(KBUILD_MODNAME, NULL);
+
+	for (i = 0; i < NUM_MONITORS; i++) {
+		state->monitor[i].debugfs_entry =
+			debugfs_create_dir(monitor_name[i], state->root_folder);
+		if (IS_ERR(state->monitor[i].debugfs_entry))
+			state->monitor[i].debugfs_entry = NULL;
+
+		debugfs_create_file("data", 0444,
+				    state->monitor[i].debugfs_entry,
+				    (void *)(uintptr_t)i, &fops_debug);
+		debugfs_create_u32("enable", 0644,
+				   state->monitor[i].debugfs_entry,
+				   &state->monitor[i].bus_enabled);
+		debugfs_create_u32("filter", 0644,
+				   state->monitor[i].debugfs_entry,
+				   &state->monitor[i].bus_filter);
+		debugfs_create_u32("sample_time", 0644,
+				   state->monitor[i].debugfs_entry,
+				   &state->sample_time);
+	}
+
+	mutex_init(&state->lock);
+
+	state->monitor_thread = kthread_run(monitor_thread, state,
+					    "rpi-axiperfmon");
+
+	return ret;
+
+}
+
+static void rpi_axiperf_remove(struct platform_device *dev)
+{
+	kthread_stop(state->monitor_thread);
+
+	debugfs_remove_recursive(state->root_folder);
+	state->root_folder = NULL;
+}
+
+static const struct of_device_id rpi_axiperf_match[] = {
+	{ .compatible = "brcm,bcm2835-axiperf",
+	  .data = &config_2835 },
+	{ .compatible = "brcm,bcm2711-axiperf",
+	  .data = &config_2711 },
+	{ .compatible = "brcm,bcm2712-axiperf",
+	  .data = &config_2712 },
+	{},
+};
+MODULE_DEVICE_TABLE(of, rpi_axiperf_match);
+
+static struct platform_driver rpi_axiperf_driver  = {
+	.probe =	rpi_axiperf_probe,
+	.remove =	rpi_axiperf_remove,
+	.driver = {
+		.name   = "rpi-bcm2835-axiperf",
+		.of_match_table = of_match_ptr(rpi_axiperf_match),
+	},
+};
+
+module_platform_driver(rpi_axiperf_driver);
+
+/* Module information */
+MODULE_AUTHOR("James Hughes <james.hughes@raspberrypi.org>");
+MODULE_DESCRIPTION("RPI AXI Performance monitor driver");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/phy/broadcom/Kconfig b/drivers/phy/broadcom/Kconfig
index 1d89a2fd9b79..a67ac49a0d59 100644
--- a/drivers/phy/broadcom/Kconfig
+++ b/drivers/phy/broadcom/Kconfig
@@ -93,7 +93,7 @@ config PHY_BRCM_SATA
 
 config PHY_BRCM_USB
 	tristate "Broadcom STB USB PHY driver"
-	depends on ARCH_BCMBCA || ARCH_BRCMSTB || COMPILE_TEST
+	depends on ARCH_BCMBCA || ARCH_BRCMSTB || ARCH_BCM2835 || COMPILE_TEST
 	depends on OF
 	select GENERIC_PHY
 	select SOC_BRCMSTB if ARCH_BRCMSTB
diff --git a/drivers/phy/broadcom/phy-brcm-usb-init-synopsys.c b/drivers/phy/broadcom/phy-brcm-usb-init-synopsys.c
index dc452610934a..b377eefc6ded 100644
--- a/drivers/phy/broadcom/phy-brcm-usb-init-synopsys.c
+++ b/drivers/phy/broadcom/phy-brcm-usb-init-synopsys.c
@@ -347,6 +347,36 @@ static void usb_init_common_7216(struct brcm_usb_init_params *params)
 	usb_init_common(params);
 }
 
+static void usb_init_common_2712(struct brcm_usb_init_params *params)
+{
+	void __iomem *ctrl = params->regs[BRCM_REGS_CTRL];
+	void __iomem *bdc_ec = params->regs[BRCM_REGS_BDC_EC];
+	u32 reg;
+
+	if (params->syscon_piarbctl)
+		syscon_piarbctl_init(params->syscon_piarbctl);
+
+	USB_CTRL_UNSET(ctrl, USB_PM, USB_PWRDN);
+
+	usb_wake_enable_7211b0(params, false);
+
+	usb_init_common(params);
+
+	/*
+	 * The BDC controller will get occasional failures with
+	 * the default "Read Transaction Size" of 6 (1024 bytes).
+	 * Set it to 4 (256 bytes).
+	 */
+	if ((params->supported_port_modes != USB_CTLR_MODE_HOST) && bdc_ec) {
+		reg = brcm_usb_readl(bdc_ec + BDC_EC_AXIRDA);
+		reg &= ~BDC_EC_AXIRDA_RTS_MASK;
+		reg |= (0x4 << BDC_EC_AXIRDA_RTS_SHIFT);
+		brcm_usb_writel(reg, bdc_ec + BDC_EC_AXIRDA);
+	}
+
+	usb2_eye_fix_7211b0(params);
+}
+
 static void usb_init_xhci(struct brcm_usb_init_params *params)
 {
 	pr_debug("%s\n", __func__);
@@ -392,6 +422,18 @@ static void usb_uninit_common_7211b0(struct brcm_usb_init_params *params)
 
 }
 
+static void usb_uninit_common_2712(struct brcm_usb_init_params *params)
+{
+	void __iomem *ctrl = params->regs[BRCM_REGS_CTRL];
+
+	if (params->wake_enabled) {
+		USB_CTRL_SET(ctrl, TEST_PORT_CTL, TPOUT_SEL_PME_GEN);
+		usb_wake_enable_7211b0(params, true);
+	} else {
+		USB_CTRL_SET(ctrl, USB_PM, USB_PWRDN);
+	}
+}
+
 static void usb_uninit_xhci(struct brcm_usb_init_params *params)
 {
 
@@ -446,6 +488,16 @@ static const struct brcm_usb_init_ops bcm7211b0_ops = {
 	.set_dual_select = usb_set_dual_select,
 };
 
+static const struct brcm_usb_init_ops bcm2712_ops = {
+	.init_ipp = usb_init_ipp,
+	.init_common = usb_init_common_2712,
+	.init_xhci = usb_init_xhci,
+	.uninit_common = usb_uninit_common_2712,
+	.uninit_xhci = usb_uninit_xhci,
+	.get_dual_select = usb_get_dual_select,
+	.set_dual_select = usb_set_dual_select,
+};
+
 void brcm_usb_dvr_init_7216(struct brcm_usb_init_params *params)
 {
 
@@ -463,3 +515,10 @@ void brcm_usb_dvr_init_7211b0(struct brcm_usb_init_params *params)
 	params->family_name = "7211";
 	params->ops = &bcm7211b0_ops;
 }
+
+void brcm_usb_dvr_init_2712(struct brcm_usb_init_params *params)
+{
+	params->family_name = "2712";
+	params->ops = &bcm2712_ops;
+	params->suspend_with_clocks = true;
+}
diff --git a/drivers/phy/broadcom/phy-brcm-usb-init.h b/drivers/phy/broadcom/phy-brcm-usb-init.h
index c1a88f5cd4cd..67083474126d 100644
--- a/drivers/phy/broadcom/phy-brcm-usb-init.h
+++ b/drivers/phy/broadcom/phy-brcm-usb-init.h
@@ -70,12 +70,14 @@ struct  brcm_usb_init_params {
 	const struct brcm_usb_init_ops *ops;
 	struct regmap *syscon_piarbctl;
 	bool wake_enabled;
+	bool suspend_with_clocks;
 };
 
 void brcm_usb_dvr_init_4908(struct brcm_usb_init_params *params);
 void brcm_usb_dvr_init_7445(struct brcm_usb_init_params *params);
 void brcm_usb_dvr_init_7216(struct brcm_usb_init_params *params);
 void brcm_usb_dvr_init_7211b0(struct brcm_usb_init_params *params);
+void brcm_usb_dvr_init_2712(struct brcm_usb_init_params *params);
 
 static inline u32 brcm_usb_readl(void __iomem *addr)
 {
diff --git a/drivers/phy/broadcom/phy-brcm-usb.c b/drivers/phy/broadcom/phy-brcm-usb.c
index ad2eec095601..342ed464a363 100644
--- a/drivers/phy/broadcom/phy-brcm-usb.c
+++ b/drivers/phy/broadcom/phy-brcm-usb.c
@@ -75,7 +75,7 @@ struct brcm_usb_phy_data {
 };
 
 static s8 *node_reg_names[BRCM_REGS_MAX] = {
-	"crtl", "xhci_ec", "xhci_gbl", "usb_phy", "usb_mdio", "bdc_ec"
+	"ctrl", "xhci_ec", "xhci_gbl", "usb_phy", "usb_mdio", "bdc_ec"
 };
 
 static int brcm_pm_notifier(struct notifier_block *notifier,
@@ -315,6 +315,18 @@ static const struct match_chip_info chip_info_7211b0 = {
 	.optional_reg = BRCM_REGS_BDC_EC,
 };
 
+static const struct match_chip_info chip_info_2712 = {
+	.init_func = &brcm_usb_dvr_init_2712,
+	.required_regs = {
+		BRCM_REGS_CTRL,
+		BRCM_REGS_XHCI_EC,
+		BRCM_REGS_XHCI_GBL,
+		BRCM_REGS_USB_MDIO,
+		-1,
+	},
+	.optional_reg = BRCM_REGS_BDC_EC,
+};
+
 static const struct match_chip_info chip_info_7445 = {
 	.init_func = &brcm_usb_dvr_init_7445,
 	.required_regs = {
@@ -337,6 +349,10 @@ static const struct of_device_id brcm_usb_dt_ids[] = {
 		.compatible = "brcm,bcm7211-usb-phy",
 		.data = &chip_info_7211b0,
 	},
+	{
+		.compatible = "brcm,bcm2712-usb-phy",
+		.data = &chip_info_2712,
+	},
 	{
 		.compatible = "brcm,brcmstb-usb-phy",
 		.data = &chip_info_7445,
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index e05174e5efbc..cfecf66cac71 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -588,6 +588,13 @@ config PINCTRL_MLXBF3
 	  each pin. This driver can also be built as a module called
 	  pinctrl-mlxbf3.
 
+config PINCTRL_RP1
+	bool "Pinctrl driver for RP1"
+	select PINMUX
+	select PINCONF
+	select GENERIC_PINCONF
+	select GPIOLIB_IRQCHIP
+
 source "drivers/pinctrl/actions/Kconfig"
 source "drivers/pinctrl/aspeed/Kconfig"
 source "drivers/pinctrl/bcm/Kconfig"
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index 97823f52b972..2df5ae86e458 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -47,6 +47,7 @@ obj-$(CONFIG_PINCTRL_PIC32)	+= pinctrl-pic32.o
 obj-$(CONFIG_PINCTRL_PISTACHIO)	+= pinctrl-pistachio.o
 obj-$(CONFIG_PINCTRL_RK805)	+= pinctrl-rk805.o
 obj-$(CONFIG_PINCTRL_ROCKCHIP)	+= pinctrl-rockchip.o
+obj-$(CONFIG_PINCTRL_RP1)	+= pinctrl-rp1.o
 obj-$(CONFIG_PINCTRL_SCMI)	+= pinctrl-scmi.o
 obj-$(CONFIG_PINCTRL_SINGLE)	+= pinctrl-single.o
 obj-$(CONFIG_PINCTRL_ST) 	+= pinctrl-st.o
diff --git a/drivers/pinctrl/bcm/Kconfig b/drivers/pinctrl/bcm/Kconfig
index 35b51ce4298e..f2ce009999e7 100644
--- a/drivers/pinctrl/bcm/Kconfig
+++ b/drivers/pinctrl/bcm/Kconfig
@@ -3,6 +3,15 @@
 # Broadcom pinctrl drivers
 #
 
+config PINCTRL_BCM2712
+	bool "Broadcom BCM2712 PINCONF driver"
+	depends on OF && (ARCH_BCM2835 || ARCH_BRCMSTB || COMPILE_TEST)
+	select PINMUX
+	select PINCONF
+	select GENERIC_PINCONF
+	help
+	   Say Y here to enable the Broadcom BCM2712 PINCONF driver.
+
 config PINCTRL_BCM281XX
 	bool "Broadcom BCM281xx pinctrl driver"
 	depends on OF && (ARCH_BCM_MOBILE || COMPILE_TEST)
diff --git a/drivers/pinctrl/bcm/Makefile b/drivers/pinctrl/bcm/Makefile
index 82b868ec1471..d298e4785829 100644
--- a/drivers/pinctrl/bcm/Makefile
+++ b/drivers/pinctrl/bcm/Makefile
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 # Broadcom pinctrl support
 
+obj-$(CONFIG_PINCTRL_BCM2712)		+= pinctrl-bcm2712.o
 obj-$(CONFIG_PINCTRL_BCM281XX)		+= pinctrl-bcm281xx.o
 obj-$(CONFIG_PINCTRL_BCM2835)		+= pinctrl-bcm2835.o
 obj-$(CONFIG_PINCTRL_BCM4908)		+= pinctrl-bcm4908.o
diff --git a/drivers/pinctrl/bcm/pinctrl-bcm2712.c b/drivers/pinctrl/bcm/pinctrl-bcm2712.c
new file mode 100644
index 000000000000..c349bcf05391
--- /dev/null
+++ b/drivers/pinctrl/bcm/pinctrl-bcm2712.c
@@ -0,0 +1,1247 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for Broadcom BCM2712 GPIO units (pinctrl only)
+ *
+ * Copyright (C) 2021-3 Raspberry Pi Ltd.
+ * Copyright (C) 2012 Chris Boot, Simon Arlott, Stephen Warren
+ *
+ * Based heavily on the BCM2835 GPIO & pinctrl driver, which was inspired by:
+ * pinctrl-nomadik.c, please see original file for copyright information
+ * pinctrl-tegra.c, please see original file for copyright information
+ */
+
+#include <linux/bitmap.h>
+#include <linux/bug.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/of_address.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/pinctrl/machine.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+
+#define MODULE_NAME "pinctrl-bcm2712"
+
+/* Register offsets */
+
+#define BCM2712_PULL_NONE	0
+#define BCM2712_PULL_DOWN	1
+#define BCM2712_PULL_UP		2
+#define BCM2712_PULL_MASK	0x3
+
+#define BCM2712_FSEL_COUNT 9
+#define BCM2712_FSEL_MASK  0xf
+
+#define FUNC(f) \
+	[func_##f] = #f
+#define PIN(i, f1, f2, f3, f4, f5, f6, f7, f8) \
+	[i] = { \
+		.funcs = { \
+			func_##f1, \
+			func_##f2, \
+			func_##f3, \
+			func_##f4, \
+			func_##f5, \
+			func_##f6, \
+			func_##f7, \
+			func_##f8, \
+		}, \
+	}
+
+#define MUX_BIT_VALID	0x8000
+#define REG_BIT_INVALID	0xffff
+
+#define BIT_TO_REG(b) (((b) >> 5) << 2)
+#define BIT_TO_SHIFT(b) ((b) & 0x1f)
+
+#define MUX_BIT(mr, mb) (MUX_BIT_VALID + ((mr)*4)*8 + (mb)*4)
+#define GPIO_REGS(n, mr, mb, pr, pb) \
+	[n] = { MUX_BIT(mr, mb), ((pr)*4)*8 + (pb)*2 }
+
+#define EMMC_REGS(n, pr, pb) \
+	[n] = { 0, ((pr)*4)*8 + (pb)*2 }
+
+#define AGPIO_REGS(n, mr, mb, pr, pb) \
+	[n] = { MUX_BIT(mr, mb), ((pr)*4)*8 + (pb)*2 }
+
+#define SGPIO_REGS(n, mr, mb) \
+	[n+32] = { MUX_BIT(mr, mb), REG_BIT_INVALID }
+
+#define GPIO_PIN(a) PINCTRL_PIN(a, "gpio" #a)
+#define AGPIO_PIN(a) PINCTRL_PIN(a, "aon_gpio" #a)
+#define SGPIO_PIN(a) PINCTRL_PIN(a+32, "aon_sgpio" #a)
+
+struct pin_regs {
+	u16 mux_bit;
+	u16 pad_bit;
+};
+
+struct bcm2712_pinctrl {
+	struct device *dev;
+	void __iomem *base;
+	struct pinctrl_dev *pctl_dev;
+	struct pinctrl_desc pctl_desc;
+	const struct pin_regs *pin_regs;
+	const struct bcm2712_pin_funcs *pin_funcs;
+	const char *const *gpio_groups;
+	struct pinctrl_gpio_range gpio_range;
+	spinlock_t lock;
+};
+
+struct bcm_plat_data {
+	const struct pinctrl_desc *pctl_desc;
+	const struct pinctrl_gpio_range *gpio_range;
+	const struct pin_regs *pin_regs;
+	const struct bcm2712_pin_funcs *pin_funcs;
+};
+
+struct bcm2712_pin_funcs {
+	u8 funcs[BCM2712_FSEL_COUNT - 1];
+};
+
+enum bcm2712_funcs {
+	func_gpio,
+	func_alt1,
+	func_alt2,
+	func_alt3,
+	func_alt4,
+	func_alt5,
+	func_alt6,
+	func_alt7,
+	func_alt8,
+	func_aon_cpu_standbyb,
+	func_aon_fp_4sec_resetb,
+	func_aon_gpclk,
+	func_aon_pwm,
+	func_arm_jtag,
+	func_aud_fs_clk0,
+	func_avs_pmu_bsc,
+	func_bsc_m0,
+	func_bsc_m1,
+	func_bsc_m2,
+	func_bsc_m3,
+	func_clk_observe,
+	func_ctl_hdmi_5v,
+	func_enet0,
+	func_enet0_mii,
+	func_enet0_rgmii,
+	func_ext_sc_clk,
+	func_fl0,
+	func_fl1,
+	func_gpclk0,
+	func_gpclk1,
+	func_gpclk2,
+	func_hdmi_tx0_auto_i2c,
+	func_hdmi_tx0_bsc,
+	func_hdmi_tx1_auto_i2c,
+	func_hdmi_tx1_bsc,
+	func_i2s_in,
+	func_i2s_out,
+	func_ir_in,
+	func_mtsif,
+	func_mtsif_alt,
+	func_mtsif_alt1,
+	func_pdm,
+	func_pkt,
+	func_pm_led_out,
+	func_sc0,
+	func_sd0,
+	func_sd2,
+	func_sd_card_a,
+	func_sd_card_b,
+	func_sd_card_c,
+	func_sd_card_d,
+	func_sd_card_e,
+	func_sd_card_f,
+	func_sd_card_g,
+	func_spdif_out,
+	func_spi_m,
+	func_spi_s,
+	func_sr_edm_sense,
+	func_te0,
+	func_te1,
+	func_tsio,
+	func_uart0,
+	func_uart1,
+	func_uart2,
+	func_usb_pwr,
+	func_usb_vbus,
+	func_uui,
+	func_vc_i2c0,
+	func_vc_i2c3,
+	func_vc_i2c4,
+	func_vc_i2c5,
+	func_vc_i2csl,
+	func_vc_pcm,
+	func_vc_pwm0,
+	func_vc_pwm1,
+	func_vc_spi0,
+	func_vc_spi3,
+	func_vc_spi4,
+	func_vc_spi5,
+	func_vc_uart0,
+	func_vc_uart2,
+	func_vc_uart3,
+	func_vc_uart4,
+	func__,
+	func_count = func__
+};
+
+static const struct pin_regs bcm2712_c0_gpio_pin_regs[] = {
+	GPIO_REGS(0, 0, 0, 7, 7),
+	GPIO_REGS(1, 0, 1, 7, 8),
+	GPIO_REGS(2, 0, 2, 7, 9),
+	GPIO_REGS(3, 0, 3, 7, 10),
+	GPIO_REGS(4, 0, 4, 7, 11),
+	GPIO_REGS(5, 0, 5, 7, 12),
+	GPIO_REGS(6, 0, 6, 7, 13),
+	GPIO_REGS(7, 0, 7, 7, 14),
+	GPIO_REGS(8, 1, 0, 8, 0),
+	GPIO_REGS(9, 1, 1, 8, 1),
+	GPIO_REGS(10, 1, 2, 8, 2),
+	GPIO_REGS(11, 1, 3, 8, 3),
+	GPIO_REGS(12, 1, 4, 8, 4),
+	GPIO_REGS(13, 1, 5, 8, 5),
+	GPIO_REGS(14, 1, 6, 8, 6),
+	GPIO_REGS(15, 1, 7, 8, 7),
+	GPIO_REGS(16, 2, 0, 8, 8),
+	GPIO_REGS(17, 2, 1, 8, 9),
+	GPIO_REGS(18, 2, 2, 8, 10),
+	GPIO_REGS(19, 2, 3, 8, 11),
+	GPIO_REGS(20, 2, 4, 8, 12),
+	GPIO_REGS(21, 2, 5, 8, 13),
+	GPIO_REGS(22, 2, 6, 8, 14),
+	GPIO_REGS(23, 2, 7, 9, 0),
+	GPIO_REGS(24, 3, 0, 9, 1),
+	GPIO_REGS(25, 3, 1, 9, 2),
+	GPIO_REGS(26, 3, 2, 9, 3),
+	GPIO_REGS(27, 3, 3, 9, 4),
+	GPIO_REGS(28, 3, 4, 9, 5),
+	GPIO_REGS(29, 3, 5, 9, 6),
+	GPIO_REGS(30, 3, 6, 9, 7),
+	GPIO_REGS(31, 3, 7, 9, 8),
+	GPIO_REGS(32, 4, 0, 9, 9),
+	GPIO_REGS(33, 4, 1, 9, 10),
+	GPIO_REGS(34, 4, 2, 9, 11),
+	GPIO_REGS(35, 4, 3, 9, 12),
+	GPIO_REGS(36, 4, 4, 9, 13),
+	GPIO_REGS(37, 4, 5, 9, 14),
+	GPIO_REGS(38, 4, 6, 10, 0),
+	GPIO_REGS(39, 4, 7, 10, 1),
+	GPIO_REGS(40, 5, 0, 10, 2),
+	GPIO_REGS(41, 5, 1, 10, 3),
+	GPIO_REGS(42, 5, 2, 10, 4),
+	GPIO_REGS(43, 5, 3, 10, 5),
+	GPIO_REGS(44, 5, 4, 10, 6),
+	GPIO_REGS(45, 5, 5, 10, 7),
+	GPIO_REGS(46, 5, 6, 10, 8),
+	GPIO_REGS(47, 5, 7, 10, 9),
+	GPIO_REGS(48, 6, 0, 10, 10),
+	GPIO_REGS(49, 6, 1, 10, 11),
+	GPIO_REGS(50, 6, 2, 10, 12),
+	GPIO_REGS(51, 6, 3, 10, 13),
+	GPIO_REGS(52, 6, 4, 10, 14),
+	GPIO_REGS(53, 6, 5, 11, 0),
+	EMMC_REGS(54, 11, 1), /* EMMC_CMD */
+	EMMC_REGS(55, 11, 2), /* EMMC_DS */
+	EMMC_REGS(56, 11, 3), /* EMMC_CLK */
+	EMMC_REGS(57, 11, 4), /* EMMC_DAT0 */
+	EMMC_REGS(58, 11, 5), /* EMMC_DAT1 */
+	EMMC_REGS(59, 11, 6), /* EMMC_DAT2 */
+	EMMC_REGS(60, 11, 7), /* EMMC_DAT3 */
+	EMMC_REGS(61, 11, 8), /* EMMC_DAT4 */
+	EMMC_REGS(62, 11, 9), /* EMMC_DAT5 */
+	EMMC_REGS(63, 11, 10), /* EMMC_DAT6 */
+	EMMC_REGS(64, 11, 11), /* EMMC_DAT7 */
+};
+
+static struct pin_regs bcm2712_c0_aon_gpio_pin_regs[] = {
+	AGPIO_REGS(0, 3, 0, 6, 10),
+	AGPIO_REGS(1, 3, 1, 6, 11),
+	AGPIO_REGS(2, 3, 2, 6, 12),
+	AGPIO_REGS(3, 3, 3, 6, 13),
+	AGPIO_REGS(4, 3, 4, 6, 14),
+	AGPIO_REGS(5, 3, 5, 7, 0),
+	AGPIO_REGS(6, 3, 6, 7, 1),
+	AGPIO_REGS(7, 3, 7, 7, 2),
+	AGPIO_REGS(8, 4, 0, 7, 3),
+	AGPIO_REGS(9, 4, 1, 7, 4),
+	AGPIO_REGS(10, 4, 2, 7, 5),
+	AGPIO_REGS(11, 4, 3, 7, 6),
+	AGPIO_REGS(12, 4, 4, 7, 7),
+	AGPIO_REGS(13, 4, 5, 7, 8),
+	AGPIO_REGS(14, 4, 6, 7, 9),
+	AGPIO_REGS(15, 4, 7, 7, 10),
+	AGPIO_REGS(16, 5, 0, 7, 11),
+	SGPIO_REGS(0, 0, 0),
+	SGPIO_REGS(1, 0, 1),
+	SGPIO_REGS(2, 0, 2),
+	SGPIO_REGS(3, 0, 3),
+	SGPIO_REGS(4, 1, 0),
+	SGPIO_REGS(5, 2, 0),
+};
+
+static const struct pinctrl_pin_desc bcm2712_c0_gpio_pins[] = {
+	GPIO_PIN(0),
+	GPIO_PIN(1),
+	GPIO_PIN(2),
+	GPIO_PIN(3),
+	GPIO_PIN(4),
+	GPIO_PIN(5),
+	GPIO_PIN(6),
+	GPIO_PIN(7),
+	GPIO_PIN(8),
+	GPIO_PIN(9),
+	GPIO_PIN(10),
+	GPIO_PIN(11),
+	GPIO_PIN(12),
+	GPIO_PIN(13),
+	GPIO_PIN(14),
+	GPIO_PIN(15),
+	GPIO_PIN(16),
+	GPIO_PIN(17),
+	GPIO_PIN(18),
+	GPIO_PIN(19),
+	GPIO_PIN(20),
+	GPIO_PIN(21),
+	GPIO_PIN(22),
+	GPIO_PIN(23),
+	GPIO_PIN(24),
+	GPIO_PIN(25),
+	GPIO_PIN(26),
+	GPIO_PIN(27),
+	GPIO_PIN(28),
+	GPIO_PIN(29),
+	GPIO_PIN(30),
+	GPIO_PIN(31),
+	GPIO_PIN(32),
+	GPIO_PIN(33),
+	GPIO_PIN(34),
+	GPIO_PIN(35),
+	GPIO_PIN(36),
+	GPIO_PIN(37),
+	GPIO_PIN(38),
+	GPIO_PIN(39),
+	GPIO_PIN(40),
+	GPIO_PIN(41),
+	GPIO_PIN(42),
+	GPIO_PIN(43),
+	GPIO_PIN(44),
+	GPIO_PIN(45),
+	GPIO_PIN(46),
+	GPIO_PIN(47),
+	GPIO_PIN(48),
+	GPIO_PIN(49),
+	GPIO_PIN(50),
+	GPIO_PIN(51),
+	GPIO_PIN(52),
+	GPIO_PIN(53),
+	PINCTRL_PIN(54, "emmc_cmd"),
+	PINCTRL_PIN(55, "emmc_ds"),
+	PINCTRL_PIN(56, "emmc_clk"),
+	PINCTRL_PIN(57, "emmc_dat0"),
+	PINCTRL_PIN(58, "emmc_dat1"),
+	PINCTRL_PIN(59, "emmc_dat2"),
+	PINCTRL_PIN(60, "emmc_dat3"),
+	PINCTRL_PIN(61, "emmc_dat4"),
+	PINCTRL_PIN(62, "emmc_dat5"),
+	PINCTRL_PIN(63, "emmc_dat6"),
+	PINCTRL_PIN(64, "emmc_dat7"),
+};
+
+static struct pinctrl_pin_desc bcm2712_c0_aon_gpio_pins[] = {
+	AGPIO_PIN(0),
+	AGPIO_PIN(1),
+	AGPIO_PIN(2),
+	AGPIO_PIN(3),
+	AGPIO_PIN(4),
+	AGPIO_PIN(5),
+	AGPIO_PIN(6),
+	AGPIO_PIN(7),
+	AGPIO_PIN(8),
+	AGPIO_PIN(9),
+	AGPIO_PIN(10),
+	AGPIO_PIN(11),
+	AGPIO_PIN(12),
+	AGPIO_PIN(13),
+	AGPIO_PIN(14),
+	AGPIO_PIN(15),
+	AGPIO_PIN(16),
+	SGPIO_PIN(0),
+	SGPIO_PIN(1),
+	SGPIO_PIN(2),
+	SGPIO_PIN(3),
+	SGPIO_PIN(4),
+	SGPIO_PIN(5),
+};
+
+static const struct pin_regs bcm2712_d0_gpio_pin_regs[] = {
+	GPIO_REGS(1, 0, 0, 4, 5),
+	GPIO_REGS(2, 0, 1, 4, 6),
+	GPIO_REGS(3, 0, 2, 4, 7),
+	GPIO_REGS(4, 0, 3, 4, 8),
+	GPIO_REGS(10, 0, 4, 4, 9),
+	GPIO_REGS(11, 0, 5, 4, 10),
+	GPIO_REGS(12, 0, 6, 4, 11),
+	GPIO_REGS(13, 0, 7, 4, 12),
+	GPIO_REGS(14, 1, 0, 4, 13),
+	GPIO_REGS(15, 1, 1, 4, 14),
+	GPIO_REGS(18, 1, 2, 5, 0),
+	GPIO_REGS(19, 1, 3, 5, 1),
+	GPIO_REGS(20, 1, 4, 5, 2),
+	GPIO_REGS(21, 1, 5, 5, 3),
+	GPIO_REGS(22, 1, 6, 5, 4),
+	GPIO_REGS(23, 1, 7, 5, 5),
+	GPIO_REGS(24, 2, 0, 5, 6),
+	GPIO_REGS(25, 2, 1, 5, 7),
+	GPIO_REGS(26, 2, 2, 5, 8),
+	GPIO_REGS(27, 2, 3, 5, 9),
+	GPIO_REGS(28, 2, 4, 5, 10),
+	GPIO_REGS(29, 2, 5, 5, 11),
+	GPIO_REGS(30, 2, 6, 5, 12),
+	GPIO_REGS(31, 2, 7, 5, 13),
+	GPIO_REGS(32, 3, 0, 5, 14),
+	GPIO_REGS(33, 3, 1, 6, 0),
+	GPIO_REGS(34, 3, 2, 6, 1),
+	GPIO_REGS(35, 3, 3, 6, 2),
+	EMMC_REGS(36, 6, 3), /* EMMC_CMD */
+	EMMC_REGS(37, 6, 4), /* EMMC_DS */
+	EMMC_REGS(38, 6, 5), /* EMMC_CLK */
+	EMMC_REGS(39, 6, 6), /* EMMC_DAT0 */
+	EMMC_REGS(40, 6, 7), /* EMMC_DAT1 */
+	EMMC_REGS(41, 6, 8), /* EMMC_DAT2 */
+	EMMC_REGS(42, 6, 9), /* EMMC_DAT3 */
+	EMMC_REGS(43, 6, 10), /* EMMC_DAT4 */
+	EMMC_REGS(44, 6, 11), /* EMMC_DAT5 */
+	EMMC_REGS(45, 6, 12), /* EMMC_DAT6 */
+	EMMC_REGS(46, 6, 13), /* EMMC_DAT7 */
+};
+
+static struct pin_regs bcm2712_d0_aon_gpio_pin_regs[] = {
+	AGPIO_REGS(0, 3, 0, 5, 9),
+	AGPIO_REGS(1, 3, 1, 5, 10),
+	AGPIO_REGS(2, 3, 2, 5, 11),
+	AGPIO_REGS(3, 3, 3, 5, 12),
+	AGPIO_REGS(4, 3, 4, 5, 13),
+	AGPIO_REGS(5, 3, 5, 5, 14),
+	AGPIO_REGS(6, 3, 6, 6, 0),
+	AGPIO_REGS(8, 3, 7, 6, 1),
+	AGPIO_REGS(9, 4, 0, 6, 2),
+	AGPIO_REGS(12, 4, 1, 6, 3),
+	AGPIO_REGS(13, 4, 2, 6, 4),
+	AGPIO_REGS(14, 4, 3, 6, 5),
+	SGPIO_REGS(0, 0, 0),
+	SGPIO_REGS(1, 0, 1),
+	SGPIO_REGS(2, 0, 2),
+	SGPIO_REGS(3, 0, 3),
+	SGPIO_REGS(4, 1, 0),
+	SGPIO_REGS(5, 2, 0),
+};
+
+static const struct pinctrl_pin_desc bcm2712_d0_gpio_pins[] = {
+	GPIO_PIN(1),
+	GPIO_PIN(2),
+	GPIO_PIN(3),
+	GPIO_PIN(4),
+	GPIO_PIN(10),
+	GPIO_PIN(11),
+	GPIO_PIN(12),
+	GPIO_PIN(13),
+	GPIO_PIN(14),
+	GPIO_PIN(15),
+	GPIO_PIN(18),
+	GPIO_PIN(19),
+	GPIO_PIN(20),
+	GPIO_PIN(21),
+	GPIO_PIN(22),
+	GPIO_PIN(23),
+	GPIO_PIN(24),
+	GPIO_PIN(25),
+	GPIO_PIN(26),
+	GPIO_PIN(27),
+	GPIO_PIN(28),
+	GPIO_PIN(29),
+	GPIO_PIN(30),
+	GPIO_PIN(31),
+	GPIO_PIN(32),
+	GPIO_PIN(33),
+	GPIO_PIN(34),
+	GPIO_PIN(35),
+	PINCTRL_PIN(36, "emmc_cmd"),
+	PINCTRL_PIN(37, "emmc_ds"),
+	PINCTRL_PIN(38, "emmc_clk"),
+	PINCTRL_PIN(39, "emmc_dat0"),
+	PINCTRL_PIN(40, "emmc_dat1"),
+	PINCTRL_PIN(41, "emmc_dat2"),
+	PINCTRL_PIN(42, "emmc_dat3"),
+	PINCTRL_PIN(43, "emmc_dat4"),
+	PINCTRL_PIN(44, "emmc_dat5"),
+	PINCTRL_PIN(45, "emmc_dat6"),
+	PINCTRL_PIN(46, "emmc_dat7"),
+};
+
+static struct pinctrl_pin_desc bcm2712_d0_aon_gpio_pins[] = {
+	AGPIO_PIN(0),
+	AGPIO_PIN(1),
+	AGPIO_PIN(2),
+	AGPIO_PIN(3),
+	AGPIO_PIN(4),
+	AGPIO_PIN(5),
+	AGPIO_PIN(6),
+	AGPIO_PIN(8),
+	AGPIO_PIN(9),
+	AGPIO_PIN(12),
+	AGPIO_PIN(13),
+	AGPIO_PIN(14),
+	SGPIO_PIN(0),
+	SGPIO_PIN(1),
+	SGPIO_PIN(2),
+	SGPIO_PIN(3),
+	SGPIO_PIN(4),
+	SGPIO_PIN(5),
+};
+
+static const char * const bcm2712_func_names[] = {
+	FUNC(gpio),
+	FUNC(alt1),
+	FUNC(alt2),
+	FUNC(alt3),
+	FUNC(alt4),
+	FUNC(alt5),
+	FUNC(alt6),
+	FUNC(alt7),
+	FUNC(alt8),
+	FUNC(aon_cpu_standbyb),
+	FUNC(aon_fp_4sec_resetb),
+	FUNC(aon_gpclk),
+	FUNC(aon_pwm),
+	FUNC(arm_jtag),
+	FUNC(aud_fs_clk0),
+	FUNC(avs_pmu_bsc),
+	FUNC(bsc_m0),
+	FUNC(bsc_m1),
+	FUNC(bsc_m2),
+	FUNC(bsc_m3),
+	FUNC(clk_observe),
+	FUNC(ctl_hdmi_5v),
+	FUNC(enet0),
+	FUNC(enet0_mii),
+	FUNC(enet0_rgmii),
+	FUNC(ext_sc_clk),
+	FUNC(fl0),
+	FUNC(fl1),
+	FUNC(gpclk0),
+	FUNC(gpclk1),
+	FUNC(gpclk2),
+	FUNC(hdmi_tx0_auto_i2c),
+	FUNC(hdmi_tx0_bsc),
+	FUNC(hdmi_tx1_auto_i2c),
+	FUNC(hdmi_tx1_bsc),
+	FUNC(i2s_in),
+	FUNC(i2s_out),
+	FUNC(ir_in),
+	FUNC(mtsif),
+	FUNC(mtsif_alt),
+	FUNC(mtsif_alt1),
+	FUNC(pdm),
+	FUNC(pkt),
+	FUNC(pm_led_out),
+	FUNC(sc0),
+	FUNC(sd0),
+	FUNC(sd2),
+	FUNC(sd_card_a),
+	FUNC(sd_card_b),
+	FUNC(sd_card_c),
+	FUNC(sd_card_d),
+	FUNC(sd_card_e),
+	FUNC(sd_card_f),
+	FUNC(sd_card_g),
+	FUNC(spdif_out),
+	FUNC(spi_m),
+	FUNC(spi_s),
+	FUNC(sr_edm_sense),
+	FUNC(te0),
+	FUNC(te1),
+	FUNC(tsio),
+	FUNC(uart0),
+	FUNC(uart1),
+	FUNC(uart2),
+	FUNC(usb_pwr),
+	FUNC(usb_vbus),
+	FUNC(uui),
+	FUNC(vc_i2c0),
+	FUNC(vc_i2c3),
+	FUNC(vc_i2c4),
+	FUNC(vc_i2c5),
+	FUNC(vc_i2csl),
+	FUNC(vc_pcm),
+	FUNC(vc_pwm0),
+	FUNC(vc_pwm1),
+	FUNC(vc_spi0),
+	FUNC(vc_spi3),
+	FUNC(vc_spi4),
+	FUNC(vc_spi5),
+	FUNC(vc_uart0),
+	FUNC(vc_uart2),
+	FUNC(vc_uart3),
+	FUNC(vc_uart4),
+};
+
+static const struct bcm2712_pin_funcs bcm2712_c0_aon_gpio_pin_funcs[] = {
+	PIN(0, ir_in, vc_spi0, vc_uart3, vc_i2c3, te0, vc_i2c0, _, _),
+	PIN(1, vc_pwm0, vc_spi0, vc_uart3, vc_i2c3, te1, aon_pwm, vc_i2c0, vc_pwm1),
+	PIN(2, vc_pwm0, vc_spi0, vc_uart3, ctl_hdmi_5v, fl0, aon_pwm, ir_in, vc_pwm1),
+	PIN(3, ir_in, vc_spi0, vc_uart3, aon_fp_4sec_resetb, fl1, sd_card_g, aon_gpclk, _),
+	PIN(4, gpclk0, vc_spi0, vc_i2csl, aon_gpclk, pm_led_out, aon_pwm, sd_card_g, vc_pwm0),
+	PIN(5, gpclk1, ir_in, vc_i2csl, clk_observe, aon_pwm, sd_card_g, vc_pwm0, _),
+	PIN(6, uart1, vc_uart4, gpclk2, ctl_hdmi_5v, vc_uart0, vc_spi3, _, _),
+	PIN(7, uart1, vc_uart4, gpclk0, aon_pwm, vc_uart0, vc_spi3, _, _),
+	PIN(8, uart1, vc_uart4, vc_i2csl, ctl_hdmi_5v, vc_uart0, vc_spi3, _, _),
+	PIN(9, uart1, vc_uart4, vc_i2csl, aon_pwm, vc_uart0, vc_spi3, _, _),
+	PIN(10, tsio, ctl_hdmi_5v, sc0, spdif_out, vc_spi5, usb_pwr, aon_gpclk, sd_card_f),
+	PIN(11, tsio, uart0, sc0, aud_fs_clk0, vc_spi5, usb_vbus, vc_uart2, sd_card_f),
+	PIN(12, tsio, uart0, vc_uart0, tsio, vc_spi5, usb_pwr, vc_uart2, sd_card_f),
+	PIN(13, bsc_m1, uart0, vc_uart0, uui, vc_spi5, arm_jtag, vc_uart2, vc_i2c3),
+	PIN(14, bsc_m1, uart0, vc_uart0, uui, vc_spi5, arm_jtag, vc_uart2, vc_i2c3),
+	PIN(15, ir_in, aon_fp_4sec_resetb, vc_uart0, pm_led_out, ctl_hdmi_5v, aon_pwm, aon_gpclk, _),
+	PIN(16, aon_cpu_standbyb, gpclk0, pm_led_out, ctl_hdmi_5v, vc_pwm0, usb_pwr, aud_fs_clk0, _),
+};
+
+static const struct bcm2712_pin_funcs bcm2712_c0_aon_sgpio_pin_funcs[] = {
+	PIN(0, hdmi_tx0_bsc, hdmi_tx0_auto_i2c, bsc_m0, vc_i2c0, _, _, _, _),
+	PIN(1, hdmi_tx0_bsc, hdmi_tx0_auto_i2c, bsc_m0, vc_i2c0, _, _, _, _),
+	PIN(2, hdmi_tx1_bsc, hdmi_tx1_auto_i2c, bsc_m1, vc_i2c4, ctl_hdmi_5v, _, _, _),
+	PIN(3, hdmi_tx1_bsc, hdmi_tx1_auto_i2c, bsc_m1, vc_i2c4, _, _, _, _),
+	PIN(4, avs_pmu_bsc, bsc_m2, vc_i2c5, ctl_hdmi_5v, _, _, _, _),
+	PIN(5, avs_pmu_bsc, bsc_m2, vc_i2c5, _, _, _, _, _),
+};
+
+static const struct bcm2712_pin_funcs bcm2712_c0_gpio_pin_funcs[] = {
+	PIN(0, bsc_m3, vc_i2c0, gpclk0, enet0, vc_pwm1, vc_spi0, ir_in, _),
+	PIN(1, bsc_m3, vc_i2c0, gpclk1, enet0, vc_pwm1, sr_edm_sense, vc_spi0, vc_uart3),
+	PIN(2, pdm, i2s_in, gpclk2, vc_spi4, pkt, vc_spi0, vc_uart3, _),
+	PIN(3, pdm, i2s_in, vc_spi4, pkt, vc_spi0, vc_uart3, _, _),
+	PIN(4, pdm, i2s_in, arm_jtag, vc_spi4, pkt, vc_spi0, vc_uart3, _),
+	PIN(5, pdm, vc_i2c3, arm_jtag, sd_card_e, vc_spi4, pkt, vc_pcm, vc_i2c5),
+	PIN(6, pdm, vc_i2c3, arm_jtag, sd_card_e, vc_spi4, pkt, vc_pcm, vc_i2c5),
+	PIN(7, i2s_out, spdif_out, arm_jtag, sd_card_e, vc_i2c3, enet0_rgmii, vc_pcm, vc_spi4),
+	PIN(8, i2s_out, aud_fs_clk0, arm_jtag, sd_card_e, vc_i2c3, enet0_mii, vc_pcm, vc_spi4),
+	PIN(9, i2s_out, aud_fs_clk0, arm_jtag, sd_card_e, enet0_mii, sd_card_c, vc_spi4, _),
+	PIN(10, bsc_m3, mtsif_alt1, i2s_in, i2s_out, vc_spi5, enet0_mii, sd_card_c, vc_spi4),
+	PIN(11, bsc_m3, mtsif_alt1, i2s_in, i2s_out, vc_spi5, enet0_mii, sd_card_c, vc_spi4),
+	PIN(12, spi_s, mtsif_alt1, i2s_in, i2s_out, vc_spi5, vc_i2csl, sd0, sd_card_d),
+	PIN(13, spi_s, mtsif_alt1, i2s_out, usb_vbus, vc_spi5, vc_i2csl, sd0, sd_card_d),
+	PIN(14, spi_s, vc_i2csl, enet0_rgmii, arm_jtag, vc_spi5, vc_pwm0, vc_i2c4, sd_card_d),
+	PIN(15, spi_s, vc_i2csl, vc_spi3, arm_jtag, vc_pwm0, vc_i2c4, gpclk0, _),
+	PIN(16, sd_card_b, i2s_out, vc_spi3, i2s_in, sd0, enet0_rgmii, gpclk1, _),
+	PIN(17, sd_card_b, i2s_out, vc_spi3, i2s_in, ext_sc_clk, sd0, enet0_rgmii, gpclk2),
+	PIN(18, sd_card_b, i2s_out, vc_spi3, i2s_in, sd0, enet0_rgmii, vc_pwm1, _),
+	PIN(19, sd_card_b, usb_pwr, vc_spi3, pkt, spdif_out, sd0, ir_in, vc_pwm1),
+	PIN(20, sd_card_b, uui, vc_uart0, arm_jtag, uart2, usb_pwr, vc_pcm, vc_uart4),
+	PIN(21, usb_pwr, uui, vc_uart0, arm_jtag, uart2, sd_card_b, vc_pcm, vc_uart4),
+	PIN(22, usb_pwr, enet0, vc_uart0, mtsif, uart2, usb_vbus, vc_pcm, vc_i2c5),
+	PIN(23, usb_vbus, enet0, vc_uart0, mtsif, uart2, i2s_out, vc_pcm, vc_i2c5),
+	PIN(24, mtsif, pkt, uart0, enet0_rgmii, enet0_rgmii, vc_i2c4, vc_uart3, _),
+	PIN(25, mtsif, pkt, sc0, uart0, enet0_rgmii, enet0_rgmii, vc_i2c4, vc_uart3),
+	PIN(26, mtsif, pkt, sc0, uart0, enet0_rgmii, vc_uart4, vc_spi5, _),
+	PIN(27, mtsif, pkt, sc0, uart0, enet0_rgmii, vc_uart4, vc_spi5, _),
+	PIN(28, mtsif, pkt, sc0, enet0_rgmii, vc_uart4, vc_spi5, _, _),
+	PIN(29, mtsif, pkt, sc0, enet0_rgmii, vc_uart4, vc_spi5, _, _),
+	PIN(30, mtsif, pkt, sc0, sd2, enet0_rgmii, gpclk0, vc_pwm0, _),
+	PIN(31, mtsif, pkt, sc0, sd2, enet0_rgmii, vc_spi3, vc_pwm0, _),
+	PIN(32, mtsif, pkt, sc0, sd2, enet0_rgmii, vc_spi3, vc_uart3, _),
+	PIN(33, mtsif, pkt, sd2, enet0_rgmii, vc_spi3, vc_uart3, _, _),
+	PIN(34, mtsif, pkt, ext_sc_clk, sd2, enet0_rgmii, vc_spi3, vc_i2c5, _),
+	PIN(35, mtsif, pkt, sd2, enet0_rgmii, vc_spi3, vc_i2c5, _, _),
+	PIN(36, sd0, mtsif, sc0, i2s_in, vc_uart3, vc_uart2, _, _),
+	PIN(37, sd0, mtsif, sc0, vc_spi0, i2s_in, vc_uart3, vc_uart2, _),
+	PIN(38, sd0, mtsif_alt, sc0, vc_spi0, i2s_in, vc_uart3, vc_uart2, _),
+	PIN(39, sd0, mtsif_alt, sc0, vc_spi0, vc_uart3, vc_uart2, _, _),
+	PIN(40, sd0, mtsif_alt, sc0, vc_spi0, bsc_m3, _, _, _),
+	PIN(41, sd0, mtsif_alt, sc0, vc_spi0, bsc_m3, _, _, _),
+	PIN(42, vc_spi0, mtsif_alt, vc_i2c0, sd_card_a, mtsif_alt1, arm_jtag, pdm, spi_m),
+	PIN(43, vc_spi0, mtsif_alt, vc_i2c0, sd_card_a, mtsif_alt1, arm_jtag, pdm, spi_m),
+	PIN(44, vc_spi0, mtsif_alt, enet0, sd_card_a, mtsif_alt1, arm_jtag, pdm, spi_m),
+	PIN(45, vc_spi0, mtsif_alt, enet0, sd_card_a, mtsif_alt1, arm_jtag, pdm, spi_m),
+	PIN(46, vc_spi0, mtsif_alt, sd_card_a, mtsif_alt1, arm_jtag, pdm, spi_m, _),
+	PIN(47, enet0, mtsif_alt, i2s_out, mtsif_alt1, arm_jtag, _, _, _),
+	PIN(48, sc0, usb_pwr, spdif_out, mtsif, _, _, _, _),
+	PIN(49, sc0, usb_pwr, aud_fs_clk0, mtsif, _, _, _, _),
+	PIN(50, sc0, usb_vbus, sc0, _, _, _, _, _),
+	PIN(51, sc0, enet0, sc0, sr_edm_sense, _, _, _, _),
+	PIN(52, sc0, enet0, vc_pwm1, _, _, _, _, _),
+	PIN(53, sc0, enet0_rgmii, ext_sc_clk, _, _, _, _, _),
+};
+
+static const struct bcm2712_pin_funcs bcm2712_d0_aon_gpio_pin_funcs[] = {
+	PIN(0, ir_in, vc_spi0, vc_uart0, vc_i2c3, uart0, vc_i2c0, _, _),
+	PIN(1, vc_pwm0, vc_spi0, vc_uart0, vc_i2c3, uart0, aon_pwm, vc_i2c0, vc_pwm1),
+	PIN(2, vc_pwm0, vc_spi0, vc_uart0, ctl_hdmi_5v, uart0, aon_pwm, ir_in, vc_pwm1),
+	PIN(3, ir_in, vc_spi0, vc_uart0, uart0, sd_card_g, aon_gpclk, _, _),
+	PIN(4, gpclk0, vc_spi0, pm_led_out, aon_pwm, sd_card_g, vc_pwm0, _, _),
+	PIN(5, gpclk1, ir_in, aon_pwm, sd_card_g, vc_pwm0, _, _, _),
+	PIN(6, uart1, vc_uart2, ctl_hdmi_5v, gpclk2, vc_spi3, _, _, _),
+	PIN(7, _, _, _, _, _, _, _, _),
+	PIN(8, uart1, vc_uart2, ctl_hdmi_5v, vc_spi0, vc_spi3, _, _, _),
+	PIN(9, uart1, vc_uart2, vc_uart0, aon_pwm, vc_spi0, vc_uart2, vc_spi3, _),
+	PIN(10, _, _, _, _, _, _, _, _),
+	PIN(11, _, _, _, _, _, _, _, _),
+	PIN(12, uart1, vc_uart2, vc_uart0, vc_spi0, usb_pwr, vc_uart2, vc_spi3, _),
+	PIN(13, bsc_m1, vc_uart0, uui, vc_spi0, arm_jtag, vc_uart2, vc_i2c3, _),
+	PIN(14, bsc_m1, aon_gpclk, vc_uart0, uui, vc_spi0, arm_jtag, vc_uart2, vc_i2c3),
+};
+
+static const struct bcm2712_pin_funcs bcm2712_d0_aon_sgpio_pin_funcs[] = {
+	PIN(0, hdmi_tx0_bsc, hdmi_tx0_auto_i2c, bsc_m0, vc_i2c0, _, _, _, _),
+	PIN(1, hdmi_tx0_bsc, hdmi_tx0_auto_i2c, bsc_m0, vc_i2c0, _, _, _, _),
+	PIN(2, hdmi_tx1_bsc, hdmi_tx1_auto_i2c, bsc_m1, vc_i2c0, ctl_hdmi_5v, _, _, _),
+	PIN(3, hdmi_tx1_bsc, hdmi_tx1_auto_i2c, bsc_m1, vc_i2c0, _, _, _, _),
+	PIN(4, avs_pmu_bsc, bsc_m2, vc_i2c3, ctl_hdmi_5v, _, _, _, _),
+	PIN(5, avs_pmu_bsc, bsc_m2, vc_i2c3, _, _, _, _, _),
+};
+
+static const struct bcm2712_pin_funcs bcm2712_d0_gpio_pin_funcs[] = {
+	PIN(1, vc_i2c0, usb_pwr, gpclk0, sd_card_e, vc_spi3, sr_edm_sense, vc_spi0, vc_uart0),
+	PIN(2, vc_i2c0, usb_pwr, gpclk1, sd_card_e, vc_spi3, clk_observe, vc_spi0, vc_uart0),
+	PIN(3, vc_i2c3, usb_vbus, gpclk2, sd_card_e, vc_spi3, vc_spi0, vc_uart0, _),
+	PIN(4, vc_i2c3, vc_pwm1, vc_spi3, sd_card_e, vc_spi3, vc_spi0, vc_uart0, _),
+	PIN(10, bsc_m3, vc_pwm1, vc_spi3, sd_card_e, vc_spi3, gpclk0, _, _),
+	PIN(11, bsc_m3, vc_spi3, clk_observe, sd_card_c, gpclk1, _, _, _),
+	PIN(12, spi_s, vc_spi3, sd_card_c, sd_card_d, _, _, _, _),
+	PIN(13, spi_s, vc_spi3, sd_card_c, sd_card_d, _, _, _, _),
+	PIN(14, spi_s, uui, arm_jtag, vc_pwm0, vc_i2c0, sd_card_d, _, _),
+	PIN(15, spi_s, uui, arm_jtag, vc_pwm0, vc_i2c0, gpclk0, _, _),
+	PIN(18, sd_card_f, vc_pwm1, _, _, _, _, _, _),
+	PIN(19, sd_card_f, usb_pwr, vc_pwm1, _, _, _, _, _),
+	PIN(20, vc_i2c3, uui, vc_uart0, arm_jtag, vc_uart2, _, _, _),
+	PIN(21, vc_i2c3, uui, vc_uart0, arm_jtag, vc_uart2, _, _, _),
+	PIN(22, sd_card_f, vc_uart0, vc_i2c3, _, _, _, _, _),
+	PIN(23, vc_uart0, vc_i2c3, _, _, _, _, _, _),
+	PIN(24, sd_card_b, vc_spi0, arm_jtag, uart0, usb_pwr, vc_uart2, vc_uart0, _),
+	PIN(25, sd_card_b, vc_spi0, arm_jtag, uart0, usb_pwr, vc_uart2, vc_uart0, _),
+	PIN(26, sd_card_b, vc_spi0, arm_jtag, uart0, usb_vbus, vc_uart2, vc_spi0, _),
+	PIN(27, sd_card_b, vc_spi0, arm_jtag, uart0, vc_uart2, vc_spi0, _, _),
+	PIN(28, sd_card_b, vc_spi0, arm_jtag, vc_i2c0, vc_spi0, _, _, _),
+	PIN(29, arm_jtag, vc_i2c0, vc_spi0, _, _, _, _, _),
+	PIN(30, sd2, gpclk0, vc_pwm0, _, _, _, _, _),
+	PIN(31, sd2, vc_spi3, vc_pwm0, _, _, _, _, _),
+	PIN(32, sd2, vc_spi3, vc_uart3, _, _, _, _, _),
+	PIN(33, sd2, vc_spi3, vc_uart3, _, _, _, _, _),
+	PIN(34, sd2, vc_spi3, vc_i2c5, _, _, _, _, _),
+	PIN(35, sd2, vc_spi3, vc_i2c5, _, _, _, _, _),
+};
+
+static inline u32 bcm2712_reg_rd(struct bcm2712_pinctrl *pc, unsigned reg)
+{
+	return readl(pc->base + reg);
+}
+
+static inline void bcm2712_reg_wr(struct bcm2712_pinctrl *pc, unsigned reg,
+		u32 val)
+{
+	writel(val, pc->base + reg);
+}
+
+static enum bcm2712_funcs bcm2712_pinctrl_fsel_get(
+	struct bcm2712_pinctrl *pc, unsigned pin)
+{
+	u32 bit = pc->pin_regs[pin].mux_bit;
+	enum bcm2712_funcs func;
+	int fsel;
+	u32 val;
+
+	if (!bit)
+		return func_gpio;
+	bit &= ~MUX_BIT_VALID;
+
+	val = bcm2712_reg_rd(pc, BIT_TO_REG(bit));
+	fsel = (val >> BIT_TO_SHIFT(bit)) & BCM2712_FSEL_MASK;
+	func = pc->pin_funcs[pin].funcs[fsel];
+	if (func >= func_count)
+		func = (enum bcm2712_funcs)fsel;
+
+	dev_dbg(pc->dev, "get %04x: %08x (%u => %s)\n",
+		BIT_TO_REG(bit), val, pin,
+		bcm2712_func_names[func]);
+
+	return func;
+}
+
+static void bcm2712_pinctrl_fsel_set(
+	struct bcm2712_pinctrl *pc, unsigned pin,
+	enum bcm2712_funcs func)
+{
+	u32 bit = pc->pin_regs[pin].mux_bit, val;
+	const u8 *pin_funcs;
+	unsigned long flags;
+	int fsel;
+	int cur;
+	int i;
+
+	if (!bit || func >= func_count)
+		return;
+	bit &= ~MUX_BIT_VALID;
+
+	fsel = BCM2712_FSEL_COUNT;
+
+	if (func >= BCM2712_FSEL_COUNT) {
+		/* Convert to an fsel number */
+		pin_funcs = pc->pin_funcs[pin].funcs;
+		for (i = 1; i < BCM2712_FSEL_COUNT; i++) {
+			if (pin_funcs[i - 1] == func) {
+				fsel = i;
+				break;
+			}
+		}
+	} else {
+		fsel = (enum bcm2712_funcs)func;
+	}
+	if (fsel >= BCM2712_FSEL_COUNT)
+		return;
+
+	spin_lock_irqsave(&pc->lock, flags);
+
+	val = bcm2712_reg_rd(pc, BIT_TO_REG(bit));
+	cur = (val >> BIT_TO_SHIFT(bit)) & BCM2712_FSEL_MASK;
+
+	dev_dbg(pc->dev, "read %04x: %08x (%u => %s)\n",
+		BIT_TO_REG(bit), val, pin,
+		bcm2712_func_names[cur]);
+
+	if (cur != fsel) {
+		val &= ~(BCM2712_FSEL_MASK << BIT_TO_SHIFT(bit));
+		val |= fsel << BIT_TO_SHIFT(bit);
+
+		dev_dbg(pc->dev, "write %04x: %08x (%u <= %s)\n",
+			BIT_TO_REG(bit), val, pin,
+			bcm2712_func_names[fsel]);
+		bcm2712_reg_wr(pc, BIT_TO_REG(bit), val);
+	}
+
+	spin_unlock_irqrestore(&pc->lock, flags);
+}
+
+static int bcm2712_pctl_get_groups_count(struct pinctrl_dev *pctldev)
+{
+	struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev);
+
+	return pc->pctl_desc.npins;
+}
+
+static const char *bcm2712_pctl_get_group_name(struct pinctrl_dev *pctldev,
+		unsigned selector)
+{
+	struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev);
+
+	return pc->gpio_groups[selector];
+}
+
+static int bcm2712_pctl_get_group_pins(struct pinctrl_dev *pctldev,
+		unsigned selector,
+		const unsigned **pins,
+		unsigned *num_pins)
+{
+	struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev);
+
+	*pins = &pc->pctl_desc.pins[selector].number;
+	*num_pins = 1;
+
+	return 0;
+}
+
+static void bcm2712_pctl_pin_dbg_show(struct pinctrl_dev *pctldev,
+		struct seq_file *s,
+		unsigned offset)
+{
+	struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev);
+	enum bcm2712_funcs fsel = bcm2712_pinctrl_fsel_get(pc, offset);
+	const char *fname = bcm2712_func_names[fsel];
+
+	seq_printf(s, "function %s", fname);
+}
+
+static void bcm2712_pctl_dt_free_map(struct pinctrl_dev *pctldev,
+		struct pinctrl_map *maps, unsigned num_maps)
+{
+	int i;
+
+	for (i = 0; i < num_maps; i++)
+		if (maps[i].type == PIN_MAP_TYPE_CONFIGS_PIN)
+			kfree(maps[i].data.configs.configs);
+
+	kfree(maps);
+}
+
+static const struct pinctrl_ops bcm2712_pctl_ops = {
+	.get_groups_count = bcm2712_pctl_get_groups_count,
+	.get_group_name = bcm2712_pctl_get_group_name,
+	.get_group_pins = bcm2712_pctl_get_group_pins,
+	.pin_dbg_show = bcm2712_pctl_pin_dbg_show,
+	.dt_node_to_map = pinconf_generic_dt_node_to_map_all,
+	.dt_free_map = bcm2712_pctl_dt_free_map,
+};
+
+static int bcm2712_pmx_free(struct pinctrl_dev *pctldev,
+		unsigned offset)
+{
+	struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev);
+
+	/* disable by setting to GPIO */
+	bcm2712_pinctrl_fsel_set(pc, offset, func_gpio);
+	return 0;
+}
+
+static int bcm2712_pmx_get_functions_count(struct pinctrl_dev *pctldev)
+{
+	return func_count;
+}
+
+static const char *bcm2712_pmx_get_function_name(struct pinctrl_dev *pctldev,
+		unsigned selector)
+{
+	return (selector < func_count) ? bcm2712_func_names[selector] : NULL;
+}
+
+static int bcm2712_pmx_get_function_groups(struct pinctrl_dev *pctldev,
+		unsigned selector,
+		const char * const **groups,
+		unsigned * const num_groups)
+{
+	struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev);
+	/* every pin can do every function */
+	*groups = pc->gpio_groups;
+	*num_groups = pc->pctl_desc.npins;
+
+	return 0;
+}
+
+static int bcm2712_pmx_set(struct pinctrl_dev *pctldev,
+		unsigned func_selector,
+		unsigned group_selector)
+{
+	struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev);
+	const struct pinctrl_desc *pctldesc = &pc->pctl_desc;
+	const struct pinctrl_pin_desc *pindesc;
+
+	if (group_selector >= pctldesc->npins)
+		return -EINVAL;
+	pindesc = &pctldesc->pins[group_selector];
+	bcm2712_pinctrl_fsel_set(pc, pindesc->number, func_selector);
+
+	return 0;
+}
+static int bcm2712_pmx_gpio_request_enable(struct pinctrl_dev *pctldev,
+					   struct pinctrl_gpio_range *range,
+					   unsigned pin)
+{
+	struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev);
+
+	bcm2712_pinctrl_fsel_set(pc, pin, func_gpio);
+
+	return 0;
+}
+
+static void bcm2712_pmx_gpio_disable_free(struct pinctrl_dev *pctldev,
+		struct pinctrl_gpio_range *range,
+		unsigned offset)
+{
+	struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev);
+
+	/* disable by setting to GPIO */
+	bcm2712_pinctrl_fsel_set(pc, offset, func_gpio);
+}
+
+static const struct pinmux_ops bcm2712_pmx_ops = {
+	.free = bcm2712_pmx_free,
+	.get_functions_count = bcm2712_pmx_get_functions_count,
+	.get_function_name = bcm2712_pmx_get_function_name,
+	.get_function_groups = bcm2712_pmx_get_function_groups,
+	.set_mux = bcm2712_pmx_set,
+	.gpio_request_enable = bcm2712_pmx_gpio_request_enable,
+	.gpio_disable_free = bcm2712_pmx_gpio_disable_free,
+};
+
+static unsigned int bcm2712_pull_config_get(struct bcm2712_pinctrl *pc,
+					    unsigned int pin)
+{
+	u32 bit = pc->pin_regs[pin].pad_bit, val;
+
+	if (unlikely(bit == REG_BIT_INVALID))
+	    return BCM2712_PULL_NONE;
+
+	val = bcm2712_reg_rd(pc, BIT_TO_REG(bit));
+	return (val >> BIT_TO_SHIFT(bit)) & BCM2712_PULL_MASK;
+}
+
+static void bcm2712_pull_config_set(struct bcm2712_pinctrl *pc,
+				    unsigned int pin, unsigned int arg)
+{
+	u32 bit = pc->pin_regs[pin].pad_bit, val;
+	unsigned long flags;
+
+	if (unlikely(bit == REG_BIT_INVALID)) {
+	    dev_warn(pc->dev, "can't set pulls for %s\n", pc->gpio_groups[pin]);
+	    return;
+	}
+
+	spin_lock_irqsave(&pc->lock, flags);
+
+	val = bcm2712_reg_rd(pc, BIT_TO_REG(bit));
+	val &= ~(BCM2712_PULL_MASK << BIT_TO_SHIFT(bit));
+	val |= (arg << BIT_TO_SHIFT(bit));
+	bcm2712_reg_wr(pc, BIT_TO_REG(bit), val);
+
+	spin_unlock_irqrestore(&pc->lock, flags);
+}
+
+static int bcm2712_pinconf_get(struct pinctrl_dev *pctldev,
+			unsigned pin, unsigned long *config)
+{
+	struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev);
+	enum pin_config_param param = pinconf_to_config_param(*config);
+	u32 arg;
+
+	switch (param) {
+	case PIN_CONFIG_BIAS_DISABLE:
+		arg = (bcm2712_pull_config_get(pc, pin) == BCM2712_PULL_NONE);
+		break;
+	case PIN_CONFIG_BIAS_PULL_DOWN:
+		arg = (bcm2712_pull_config_get(pc, pin) == BCM2712_PULL_DOWN);
+		break;
+	case PIN_CONFIG_BIAS_PULL_UP:
+		arg = (bcm2712_pull_config_get(pc, pin) == BCM2712_PULL_UP);
+		break;
+	default:
+		return -ENOTSUPP;
+	}
+
+	*config = pinconf_to_config_packed(param, arg);
+
+	return 0;
+}
+
+static int bcm2712_pinconf_set(struct pinctrl_dev *pctldev,
+			       unsigned int pin, unsigned long *configs,
+			       unsigned int num_configs)
+{
+	struct bcm2712_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev);
+	u32 param, arg;
+	int i;
+
+	for (i = 0; i < num_configs; i++) {
+		param = pinconf_to_config_param(configs[i]);
+		arg = pinconf_to_config_argument(configs[i]);
+
+		switch (param) {
+		case PIN_CONFIG_BIAS_DISABLE:
+			bcm2712_pull_config_set(pc, pin, BCM2712_PULL_NONE);
+			break;
+		case PIN_CONFIG_BIAS_PULL_DOWN:
+			bcm2712_pull_config_set(pc, pin, BCM2712_PULL_DOWN);
+			break;
+		case PIN_CONFIG_BIAS_PULL_UP:
+			bcm2712_pull_config_set(pc, pin, BCM2712_PULL_UP);
+			break;
+		default:
+			return -ENOTSUPP;
+		}
+	} /* for each config */
+
+	return 0;
+}
+
+static const struct pinconf_ops bcm2712_pinconf_ops = {
+	.is_generic = true,
+	.pin_config_get = bcm2712_pinconf_get,
+	.pin_config_set = bcm2712_pinconf_set,
+};
+
+static const struct pinctrl_desc bcm2712_c0_pinctrl_desc = {
+	.name = "pinctrl-bcm2712",
+	.pins = bcm2712_c0_gpio_pins,
+	.npins = ARRAY_SIZE(bcm2712_c0_gpio_pins),
+	.pctlops = &bcm2712_pctl_ops,
+	.pmxops = &bcm2712_pmx_ops,
+	.confops = &bcm2712_pinconf_ops,
+	.owner = THIS_MODULE,
+};
+
+static const struct pinctrl_desc bcm2712_c0_aon_pinctrl_desc = {
+	.name = "aon-pinctrl-bcm2712",
+	.pins = bcm2712_c0_aon_gpio_pins,
+	.npins = ARRAY_SIZE(bcm2712_c0_aon_gpio_pins),
+	.pctlops = &bcm2712_pctl_ops,
+	.pmxops = &bcm2712_pmx_ops,
+	.confops = &bcm2712_pinconf_ops,
+	.owner = THIS_MODULE,
+};
+
+static const struct pinctrl_desc bcm2712_d0_pinctrl_desc = {
+	.name = "pinctrl-bcm2712",
+	.pins = bcm2712_d0_gpio_pins,
+	.npins = ARRAY_SIZE(bcm2712_d0_gpio_pins),
+	.pctlops = &bcm2712_pctl_ops,
+	.pmxops = &bcm2712_pmx_ops,
+	.confops = &bcm2712_pinconf_ops,
+	.owner = THIS_MODULE,
+};
+
+static const struct pinctrl_desc bcm2712_d0_aon_pinctrl_desc = {
+	.name = "aon-pinctrl-bcm2712",
+	.pins = bcm2712_d0_aon_gpio_pins,
+	.npins = ARRAY_SIZE(bcm2712_d0_aon_gpio_pins),
+	.pctlops = &bcm2712_pctl_ops,
+	.pmxops = &bcm2712_pmx_ops,
+	.confops = &bcm2712_pinconf_ops,
+	.owner = THIS_MODULE,
+};
+
+static const struct pinctrl_gpio_range bcm2712_c0_pinctrl_gpio_range = {
+	.name = "pinctrl-bcm2712",
+	.npins = ARRAY_SIZE(bcm2712_c0_gpio_pins),
+};
+
+static const struct pinctrl_gpio_range bcm2712_c0_aon_pinctrl_gpio_range = {
+	.name = "aon-pinctrl-bcm2712",
+	.npins = ARRAY_SIZE(bcm2712_c0_aon_gpio_pins),
+};
+
+static const struct pinctrl_gpio_range bcm2712_d0_pinctrl_gpio_range = {
+	.name = "pinctrl-bcm2712",
+	.npins = ARRAY_SIZE(bcm2712_d0_gpio_pins),
+};
+
+static const struct pinctrl_gpio_range bcm2712_d0_aon_pinctrl_gpio_range = {
+	.name = "aon-pinctrl-bcm2712",
+	.npins = ARRAY_SIZE(bcm2712_d0_aon_gpio_pins),
+};
+
+static const struct bcm_plat_data bcm2712_c0_plat_data = {
+	.pctl_desc = &bcm2712_c0_pinctrl_desc,
+	.gpio_range = &bcm2712_c0_pinctrl_gpio_range,
+	.pin_regs = bcm2712_c0_gpio_pin_regs,
+	.pin_funcs = bcm2712_c0_gpio_pin_funcs,
+};
+
+static const struct bcm_plat_data bcm2712_c0_aon_plat_data = {
+	.pctl_desc = &bcm2712_c0_aon_pinctrl_desc,
+	.gpio_range = &bcm2712_c0_aon_pinctrl_gpio_range,
+	.pin_regs = bcm2712_c0_aon_gpio_pin_regs,
+	.pin_funcs = bcm2712_c0_aon_gpio_pin_funcs,
+};
+
+static const struct bcm_plat_data bcm2712_d0_plat_data = {
+	.pctl_desc = &bcm2712_d0_pinctrl_desc,
+	.gpio_range = &bcm2712_d0_pinctrl_gpio_range,
+	.pin_regs = bcm2712_d0_gpio_pin_regs,
+	.pin_funcs = bcm2712_d0_gpio_pin_funcs,
+};
+
+static const struct bcm_plat_data bcm2712_d0_aon_plat_data = {
+	.pctl_desc = &bcm2712_d0_aon_pinctrl_desc,
+	.gpio_range = &bcm2712_d0_aon_pinctrl_gpio_range,
+	.pin_regs = bcm2712_d0_aon_gpio_pin_regs,
+	.pin_funcs = bcm2712_d0_aon_gpio_pin_funcs,
+};
+
+static const struct of_device_id bcm2712_pinctrl_match[] = {
+	{
+		.compatible = "brcm,bcm2712-pinctrl",
+		.data = &bcm2712_c0_plat_data,
+	},
+	{
+		.compatible = "brcm,bcm2712-aon-pinctrl",
+		.data = &bcm2712_c0_aon_plat_data,
+	},
+
+	{
+		.compatible = "brcm,bcm2712c0-pinctrl",
+		.data = &bcm2712_c0_plat_data,
+	},
+	{
+		.compatible = "brcm,bcm2712c0-aon-pinctrl",
+		.data = &bcm2712_c0_aon_plat_data,
+	},
+
+	{
+		.compatible = "brcm,bcm2712d0-pinctrl",
+		.data = &bcm2712_d0_plat_data,
+	},
+	{
+		.compatible = "brcm,bcm2712d0-aon-pinctrl",
+		.data = &bcm2712_d0_aon_plat_data,
+	},
+	{}
+};
+
+static int bcm2712_pinctrl_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	const struct bcm_plat_data *pdata;
+	const struct of_device_id *match;
+	struct bcm2712_pinctrl *pc;
+	const char **names;
+	int num_pins, i;
+
+	match = of_match_node(bcm2712_pinctrl_match, np);
+	if (!match)
+		return -EINVAL;
+	pdata = match->data;
+
+	pc = devm_kzalloc(dev, sizeof(*pc), GFP_KERNEL);
+	if (!pc)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, pc);
+	pc->dev = dev;
+	spin_lock_init(&pc->lock);
+
+	pc->base = devm_of_iomap(dev, np, 0, NULL);
+	if (IS_ERR(pc->base)) {
+		dev_err(dev, "could not get IO memory\n");
+		return PTR_ERR(pc->base);
+	}
+
+	pc->pctl_desc = *pdata->pctl_desc;
+	num_pins = pc->pctl_desc.npins;
+	names = devm_kmalloc_array(dev, num_pins, sizeof(const char *),
+				   GFP_KERNEL);
+	if (!names)
+		return -ENOMEM;
+	for (i = 0; i < num_pins; i++)
+		names[i] = pc->pctl_desc.pins[i].name;
+	pc->gpio_groups = names;
+	pc->pin_regs = pdata->pin_regs;
+	pc->pin_funcs = pdata->pin_funcs;
+	pc->pctl_dev = devm_pinctrl_register(dev, &pc->pctl_desc, pc);
+	if (IS_ERR(pc->pctl_dev))
+		return PTR_ERR(pc->pctl_dev);
+
+	pc->gpio_range = *pdata->gpio_range;
+	pinctrl_add_gpio_range(pc->pctl_dev, &pc->gpio_range);
+
+	return 0;
+}
+
+static struct platform_driver bcm2712_pinctrl_driver = {
+	.probe = bcm2712_pinctrl_probe,
+	.driver = {
+		.name = MODULE_NAME,
+		.of_match_table = bcm2712_pinctrl_match,
+		.suppress_bind_attrs = true,
+	},
+};
+builtin_platform_driver(bcm2712_pinctrl_driver);
diff --git a/drivers/pinctrl/bcm/pinctrl-bcm2835.c b/drivers/pinctrl/bcm/pinctrl-bcm2835.c
index cc1fe0555e19..0414a1c96009 100644
--- a/drivers/pinctrl/bcm/pinctrl-bcm2835.c
+++ b/drivers/pinctrl/bcm/pinctrl-bcm2835.c
@@ -245,7 +245,7 @@ static const char * const irq_type_names[] = {
 	[IRQ_TYPE_LEVEL_LOW] = "level-low",
 };
 
-static bool persist_gpio_outputs;
+static bool persist_gpio_outputs = true;
 module_param(persist_gpio_outputs, bool, 0444);
 MODULE_PARM_DESC(persist_gpio_outputs, "Enable GPIO_OUT persistence when pin is freed");
 
@@ -425,15 +425,32 @@ static void bcm2835_gpio_irq_handle_bank(struct bcm2835_pinctrl *pc,
 	unsigned long events;
 	unsigned offset;
 	unsigned gpio;
+	u32 levs, levs2;
 
 	events = bcm2835_gpio_rd(pc, GPEDS0 + bank * 4);
+	levs = bcm2835_gpio_rd(pc, GPLEV0 + bank * 4);
 	events &= mask;
 	events &= pc->enabled_irq_map[bank];
+	bcm2835_gpio_wr(pc, GPEDS0 + bank * 4, events);
+
+retry:
 	for_each_set_bit(offset, &events, 32) {
 		gpio = (32 * bank) + offset;
 		generic_handle_domain_irq(pc->gpio_chip.irq.domain,
 					  gpio);
 	}
+	events = bcm2835_gpio_rd(pc, GPEDS0 + bank * 4);
+	levs2 = bcm2835_gpio_rd(pc, GPLEV0 + bank * 4);
+
+	events |= levs2 & ~levs & bcm2835_gpio_rd(pc, GPREN0 + bank * 4);
+	events |= ~levs2 & levs & bcm2835_gpio_rd(pc, GPFEN0 + bank * 4);
+	events &= mask;
+	events &= pc->enabled_irq_map[bank];
+	if (events) {
+		bcm2835_gpio_wr(pc, GPEDS0 + bank * 4, events);
+		levs = levs2;
+		goto retry;
+	}
 }
 
 static void bcm2835_gpio_irq_handler(struct irq_desc *desc)
@@ -673,11 +690,7 @@ static int bcm2835_gpio_irq_set_type(struct irq_data *data, unsigned int type)
 
 static void bcm2835_gpio_irq_ack(struct irq_data *data)
 {
-	struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
-	struct bcm2835_pinctrl *pc = gpiochip_get_data(chip);
-	unsigned gpio = irqd_to_hwirq(data);
-
-	bcm2835_gpio_set_bit(pc, GPEDS0, gpio);
+	/* Nothing to do - the main interrupt handler includes the ACK */
 }
 
 static int bcm2835_gpio_irq_set_wake(struct irq_data *data, unsigned int on)
@@ -1423,7 +1436,7 @@ static int bcm2835_pinctrl_probe(struct platform_device *pdev)
 	girq->default_type = IRQ_TYPE_NONE;
 	girq->handler = handle_level_irq;
 
-	err = gpiochip_add_data(&pc->gpio_chip, pc);
+	err = devm_gpiochip_add_data(dev, &pc->gpio_chip, pc);
 	if (err) {
 		dev_err(dev, "could not add GPIO chip\n");
 		goto out_remove;
diff --git a/drivers/pinctrl/pinctrl-rp1.c b/drivers/pinctrl/pinctrl-rp1.c
new file mode 100644
index 000000000000..c3e9b83865e5
--- /dev/null
+++ b/drivers/pinctrl/pinctrl-rp1.c
@@ -0,0 +1,1695 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for Raspberry Pi RP1 GPIO unit (pinctrl + GPIO)
+ *
+ * Copyright (C) 2023 Raspberry Pi Ltd.
+ *
+ * This driver is inspired by:
+ * pinctrl-bcm2835.c, please see original file for copyright information
+ */
+
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/bug.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/driver.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/irqdesc.h>
+#include <linux/init.h>
+#include <linux/of_address.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/pinctrl/machine.h>
+#include <linux/pinctrl/pinconf.h>
+#include <linux/pinctrl/pinctrl.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/pinctrl/pinconf-generic.h>
+#include <linux/platform_device.h>
+#include <linux/seq_file.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include "core.h"
+#include "pinconf.h"
+#include "pinctrl-utils.h"
+
+#define MODULE_NAME "pinctrl-rp1"
+#define RP1_NUM_GPIOS	54
+#define RP1_NUM_BANKS	3
+
+#define RP1_RW_OFFSET			0x0000
+#define RP1_XOR_OFFSET			0x1000
+#define RP1_SET_OFFSET			0x2000
+#define RP1_CLR_OFFSET			0x3000
+
+#define RP1_GPIO_STATUS			0x0000
+#define RP1_GPIO_CTRL			0x0004
+
+#define RP1_GPIO_PCIE_INTE		0x011c
+#define RP1_GPIO_PCIE_INTS		0x0124
+
+#define RP1_GPIO_EVENTS_SHIFT_RAW	20
+#define RP1_GPIO_STATUS_FALLING		BIT(20)
+#define RP1_GPIO_STATUS_RISING		BIT(21)
+#define RP1_GPIO_STATUS_LOW		BIT(22)
+#define RP1_GPIO_STATUS_HIGH		BIT(23)
+
+#define RP1_GPIO_EVENTS_SHIFT_FILTERED	24
+#define RP1_GPIO_STATUS_F_FALLING	BIT(24)
+#define RP1_GPIO_STATUS_F_RISING	BIT(25)
+#define RP1_GPIO_STATUS_F_LOW		BIT(26)
+#define RP1_GPIO_STATUS_F_HIGH		BIT(27)
+
+#define RP1_GPIO_CTRL_FUNCSEL_LSB	0
+#define RP1_GPIO_CTRL_FUNCSEL_MASK	0x0000001f
+#define RP1_GPIO_CTRL_OUTOVER_LSB	12
+#define RP1_GPIO_CTRL_OUTOVER_MASK	0x00003000
+#define RP1_GPIO_CTRL_OEOVER_LSB	14
+#define RP1_GPIO_CTRL_OEOVER_MASK	0x0000c000
+#define RP1_GPIO_CTRL_INOVER_LSB	16
+#define RP1_GPIO_CTRL_INOVER_MASK	0x00030000
+#define RP1_GPIO_CTRL_IRQEN_FALLING	BIT(20)
+#define RP1_GPIO_CTRL_IRQEN_RISING	BIT(21)
+#define RP1_GPIO_CTRL_IRQEN_LOW		BIT(22)
+#define RP1_GPIO_CTRL_IRQEN_HIGH	BIT(23)
+#define RP1_GPIO_CTRL_IRQEN_F_FALLING	BIT(24)
+#define RP1_GPIO_CTRL_IRQEN_F_RISING	BIT(25)
+#define RP1_GPIO_CTRL_IRQEN_F_LOW	BIT(26)
+#define RP1_GPIO_CTRL_IRQEN_F_HIGH	BIT(27)
+#define RP1_GPIO_CTRL_IRQRESET		BIT(28)
+#define RP1_GPIO_CTRL_IRQOVER_LSB	30
+#define RP1_GPIO_CTRL_IRQOVER_MASK	0xc0000000
+
+#define RP1_INT_EDGE_FALLING		BIT(0)
+#define RP1_INT_EDGE_RISING		BIT(1)
+#define RP1_INT_LEVEL_LOW		BIT(2)
+#define RP1_INT_LEVEL_HIGH		BIT(3)
+#define RP1_INT_MASK			0xf
+
+#define RP1_INT_EDGE_BOTH		(RP1_INT_EDGE_FALLING |	\
+					 RP1_INT_EDGE_RISING)
+#define RP1_PUD_OFF			0
+#define RP1_PUD_DOWN			1
+#define RP1_PUD_UP			2
+
+#define RP1_FSEL_COUNT			9
+
+#define RP1_FSEL_ALT0			0x00
+#define RP1_FSEL_GPIO			0x05
+#define RP1_FSEL_NONE			0x09
+#define RP1_FSEL_NONE_HW		0x1f
+
+#define RP1_DIR_OUTPUT			0
+#define RP1_DIR_INPUT			1
+
+#define RP1_OUTOVER_PERI		0
+#define RP1_OUTOVER_INVPERI		1
+#define RP1_OUTOVER_LOW			2
+#define RP1_OUTOVER_HIGH		3
+
+#define RP1_OEOVER_PERI			0
+#define RP1_OEOVER_INVPERI		1
+#define RP1_OEOVER_DISABLE		2
+#define RP1_OEOVER_ENABLE		3
+
+#define RP1_INOVER_PERI			0
+#define RP1_INOVER_INVPERI		1
+#define RP1_INOVER_LOW			2
+#define RP1_INOVER_HIGH			3
+
+#define RP1_RIO_OUT			0x00
+#define RP1_RIO_OE			0x04
+#define RP1_RIO_IN			0x08
+
+#define RP1_PAD_SLEWFAST_MASK		0x00000001
+#define RP1_PAD_SLEWFAST_LSB		0
+#define RP1_PAD_SCHMITT_MASK		0x00000002
+#define RP1_PAD_SCHMITT_LSB		1
+#define RP1_PAD_PULL_MASK		0x0000000c
+#define RP1_PAD_PULL_LSB		2
+#define RP1_PAD_DRIVE_MASK		0x00000030
+#define RP1_PAD_DRIVE_LSB		4
+#define RP1_PAD_IN_ENABLE_MASK		0x00000040
+#define RP1_PAD_IN_ENABLE_LSB		6
+#define RP1_PAD_OUT_DISABLE_MASK	0x00000080
+#define RP1_PAD_OUT_DISABLE_LSB		7
+
+#define RP1_PAD_DRIVE_2MA		0x00000000
+#define RP1_PAD_DRIVE_4MA		0x00000010
+#define RP1_PAD_DRIVE_8MA		0x00000020
+#define RP1_PAD_DRIVE_12MA		0x00000030
+
+#define FLD_GET(r, f) (((r) & (f ## _MASK)) >> (f ## _LSB))
+#define FLD_SET(r, f, v) r = (((r) & ~(f ## _MASK)) | ((v) << (f ## _LSB)))
+
+#define FUNC(f) \
+	[func_##f] = #f
+#define RP1_MAX_FSEL 8
+#define PIN(i, f0, f1, f2, f3, f4, f5, f6, f7, f8) \
+	[i] = { \
+		.funcs = { \
+			func_##f0, \
+			func_##f1, \
+			func_##f2, \
+			func_##f3, \
+			func_##f4, \
+			func_##f5, \
+			func_##f6, \
+			func_##f7, \
+			func_##f8, \
+		}, \
+	}
+
+#define LEGACY_MAP(n, f0, f1, f2, f3, f4, f5) \
+	[n] = { \
+		func_gpio, \
+		func_gpio, \
+		func_##f5, \
+		func_##f4, \
+		func_##f0, \
+		func_##f1, \
+		func_##f2, \
+		func_##f3, \
+	}
+
+struct rp1_iobank_desc {
+	int min_gpio;
+	int num_gpios;
+	int gpio_offset;
+	int inte_offset;
+	int ints_offset;
+	int rio_offset;
+	int pads_offset;
+};
+
+struct rp1_pin_info {
+	u8 num;
+	u8 bank;
+	u8 offset;
+	u8 fsel;
+	u8 irq_type;
+
+	void __iomem *gpio;
+	void __iomem *rio;
+	void __iomem *inte;
+	void __iomem *ints;
+	void __iomem *pad;
+	void __iomem *dummy;
+};
+
+enum funcs {
+	func_alt0,
+	func_alt1,
+	func_alt2,
+	func_alt3,
+	func_alt4,
+	func_gpio,
+	func_alt6,
+	func_alt7,
+	func_alt8,
+	func_none,
+	func_aaud,
+	func_dcd0,
+	func_dpi,
+	func_dsi0_te_ext,
+	func_dsi1_te_ext,
+	func_dsr0,
+	func_dtr0,
+	func_gpclk0,
+	func_gpclk1,
+	func_gpclk2,
+	func_gpclk3,
+	func_gpclk4,
+	func_gpclk5,
+	func_i2c0,
+	func_i2c1,
+	func_i2c2,
+	func_i2c3,
+	func_i2c4,
+	func_i2c5,
+	func_i2c6,
+	func_i2s0,
+	func_i2s1,
+	func_i2s2,
+	func_ir,
+	func_mic,
+	func_pcie_clkreq_n,
+	func_pio,
+	func_proc_rio,
+	func_pwm0,
+	func_pwm1,
+	func_ri0,
+	func_sd0,
+	func_sd1,
+	func_spi0,
+	func_spi1,
+	func_spi2,
+	func_spi3,
+	func_spi4,
+	func_spi5,
+	func_spi6,
+	func_spi7,
+	func_spi8,
+	func_uart0,
+	func_uart1,
+	func_uart2,
+	func_uart3,
+	func_uart4,
+	func_uart5,
+	func_vbus0,
+	func_vbus1,
+	func_vbus2,
+	func_vbus3,
+	func__,
+	func_count = func__,
+	func_invalid = func__,
+};
+
+struct rp1_pin_funcs {
+	u8 funcs[RP1_FSEL_COUNT];
+};
+
+struct rp1_pinctrl {
+	struct device *dev;
+	void __iomem *gpio_base;
+	void __iomem *rio_base;
+	void __iomem *pads_base;
+	void __iomem *dummy_base;
+	int irq[RP1_NUM_BANKS];
+	struct rp1_pin_info pins[RP1_NUM_GPIOS];
+
+	struct pinctrl_dev *pctl_dev;
+	struct gpio_chip gpio_chip;
+	struct pinctrl_gpio_range gpio_range;
+
+	raw_spinlock_t irq_lock[RP1_NUM_BANKS];
+};
+
+const struct rp1_iobank_desc rp1_iobanks[RP1_NUM_BANKS] = {
+	/*         gpio   inte    ints     rio    pads */
+	{  0, 28, 0x0000, 0x011c, 0x0124, 0x0000, 0x0004 },
+	{ 28,  6, 0x4000, 0x411c, 0x4124, 0x4000, 0x4004 },
+	{ 34, 20, 0x8000, 0x811c, 0x8124, 0x8000, 0x8004 },
+};
+
+/* pins are just named GPIO0..GPIO53 */
+#define RP1_GPIO_PIN(a) PINCTRL_PIN(a, "gpio" #a)
+static struct pinctrl_pin_desc rp1_gpio_pins[] = {
+	RP1_GPIO_PIN(0),
+	RP1_GPIO_PIN(1),
+	RP1_GPIO_PIN(2),
+	RP1_GPIO_PIN(3),
+	RP1_GPIO_PIN(4),
+	RP1_GPIO_PIN(5),
+	RP1_GPIO_PIN(6),
+	RP1_GPIO_PIN(7),
+	RP1_GPIO_PIN(8),
+	RP1_GPIO_PIN(9),
+	RP1_GPIO_PIN(10),
+	RP1_GPIO_PIN(11),
+	RP1_GPIO_PIN(12),
+	RP1_GPIO_PIN(13),
+	RP1_GPIO_PIN(14),
+	RP1_GPIO_PIN(15),
+	RP1_GPIO_PIN(16),
+	RP1_GPIO_PIN(17),
+	RP1_GPIO_PIN(18),
+	RP1_GPIO_PIN(19),
+	RP1_GPIO_PIN(20),
+	RP1_GPIO_PIN(21),
+	RP1_GPIO_PIN(22),
+	RP1_GPIO_PIN(23),
+	RP1_GPIO_PIN(24),
+	RP1_GPIO_PIN(25),
+	RP1_GPIO_PIN(26),
+	RP1_GPIO_PIN(27),
+	RP1_GPIO_PIN(28),
+	RP1_GPIO_PIN(29),
+	RP1_GPIO_PIN(30),
+	RP1_GPIO_PIN(31),
+	RP1_GPIO_PIN(32),
+	RP1_GPIO_PIN(33),
+	RP1_GPIO_PIN(34),
+	RP1_GPIO_PIN(35),
+	RP1_GPIO_PIN(36),
+	RP1_GPIO_PIN(37),
+	RP1_GPIO_PIN(38),
+	RP1_GPIO_PIN(39),
+	RP1_GPIO_PIN(40),
+	RP1_GPIO_PIN(41),
+	RP1_GPIO_PIN(42),
+	RP1_GPIO_PIN(43),
+	RP1_GPIO_PIN(44),
+	RP1_GPIO_PIN(45),
+	RP1_GPIO_PIN(46),
+	RP1_GPIO_PIN(47),
+	RP1_GPIO_PIN(48),
+	RP1_GPIO_PIN(49),
+	RP1_GPIO_PIN(50),
+	RP1_GPIO_PIN(51),
+	RP1_GPIO_PIN(52),
+	RP1_GPIO_PIN(53),
+};
+
+/* one pin per group */
+static const char * const rp1_gpio_groups[] = {
+	"gpio0",
+	"gpio1",
+	"gpio2",
+	"gpio3",
+	"gpio4",
+	"gpio5",
+	"gpio6",
+	"gpio7",
+	"gpio8",
+	"gpio9",
+	"gpio10",
+	"gpio11",
+	"gpio12",
+	"gpio13",
+	"gpio14",
+	"gpio15",
+	"gpio16",
+	"gpio17",
+	"gpio18",
+	"gpio19",
+	"gpio20",
+	"gpio21",
+	"gpio22",
+	"gpio23",
+	"gpio24",
+	"gpio25",
+	"gpio26",
+	"gpio27",
+	"gpio28",
+	"gpio29",
+	"gpio30",
+	"gpio31",
+	"gpio32",
+	"gpio33",
+	"gpio34",
+	"gpio35",
+	"gpio36",
+	"gpio37",
+	"gpio38",
+	"gpio39",
+	"gpio40",
+	"gpio41",
+	"gpio42",
+	"gpio43",
+	"gpio44",
+	"gpio45",
+	"gpio46",
+	"gpio47",
+	"gpio48",
+	"gpio49",
+	"gpio50",
+	"gpio51",
+	"gpio52",
+	"gpio53",
+};
+
+static const char * const rp1_func_names[] = {
+	FUNC(alt0),
+	FUNC(alt1),
+	FUNC(alt2),
+	FUNC(alt3),
+	FUNC(alt4),
+	FUNC(gpio),
+	FUNC(alt6),
+	FUNC(alt7),
+	FUNC(alt8),
+	FUNC(none),
+	FUNC(aaud),
+	FUNC(dcd0),
+	FUNC(dpi),
+	FUNC(dsi0_te_ext),
+	FUNC(dsi1_te_ext),
+	FUNC(dsr0),
+	FUNC(dtr0),
+	FUNC(gpclk0),
+	FUNC(gpclk1),
+	FUNC(gpclk2),
+	FUNC(gpclk3),
+	FUNC(gpclk4),
+	FUNC(gpclk5),
+	FUNC(i2c0),
+	FUNC(i2c1),
+	FUNC(i2c2),
+	FUNC(i2c3),
+	FUNC(i2c4),
+	FUNC(i2c5),
+	FUNC(i2c6),
+	FUNC(i2s0),
+	FUNC(i2s1),
+	FUNC(i2s2),
+	FUNC(ir),
+	FUNC(mic),
+	FUNC(pcie_clkreq_n),
+	FUNC(pio),
+	FUNC(proc_rio),
+	FUNC(pwm0),
+	FUNC(pwm1),
+	FUNC(ri0),
+	FUNC(sd0),
+	FUNC(sd1),
+	FUNC(spi0),
+	FUNC(spi1),
+	FUNC(spi2),
+	FUNC(spi3),
+	FUNC(spi4),
+	FUNC(spi5),
+	FUNC(spi6),
+	FUNC(spi7),
+	FUNC(spi8),
+	FUNC(uart0),
+	FUNC(uart1),
+	FUNC(uart2),
+	FUNC(uart3),
+	FUNC(uart4),
+	FUNC(uart5),
+	FUNC(vbus0),
+	FUNC(vbus1),
+	FUNC(vbus2),
+	FUNC(vbus3),
+	[func_invalid] = "?"
+};
+
+static const struct rp1_pin_funcs rp1_gpio_pin_funcs[] = {
+	PIN(0, spi0, dpi, uart1, i2c0, _, gpio, proc_rio, pio, spi2),
+	PIN(1, spi0, dpi, uart1, i2c0, _, gpio, proc_rio, pio, spi2),
+	PIN(2, spi0, dpi, uart1, i2c1, ir, gpio, proc_rio, pio, spi2),
+	PIN(3, spi0, dpi, uart1, i2c1, ir, gpio, proc_rio, pio, spi2),
+	PIN(4, gpclk0, dpi, uart2, i2c2, ri0, gpio, proc_rio, pio, spi3),
+	PIN(5, gpclk1, dpi, uart2, i2c2, dtr0, gpio, proc_rio, pio, spi3),
+	PIN(6, gpclk2, dpi, uart2, i2c3, dcd0, gpio, proc_rio, pio, spi3),
+	PIN(7, spi0, dpi, uart2, i2c3, dsr0, gpio, proc_rio, pio, spi3),
+	PIN(8, spi0, dpi, uart3, i2c0, _, gpio, proc_rio, pio, spi4),
+	PIN(9, spi0, dpi, uart3, i2c0, _, gpio, proc_rio, pio, spi4),
+	PIN(10, spi0, dpi, uart3, i2c1, _, gpio, proc_rio, pio, spi4),
+	PIN(11, spi0, dpi, uart3, i2c1, _, gpio, proc_rio, pio, spi4),
+	PIN(12, pwm0, dpi, uart4, i2c2, aaud, gpio, proc_rio, pio, spi5),
+	PIN(13, pwm0, dpi, uart4, i2c2, aaud, gpio, proc_rio, pio, spi5),
+	PIN(14, pwm0, dpi, uart4, i2c3, uart0, gpio, proc_rio, pio, spi5),
+	PIN(15, pwm0, dpi, uart4, i2c3, uart0, gpio, proc_rio, pio, spi5),
+	PIN(16, spi1, dpi, dsi0_te_ext, _, uart0, gpio, proc_rio, pio, _),
+	PIN(17, spi1, dpi, dsi1_te_ext, _, uart0, gpio, proc_rio, pio, _),
+	PIN(18, spi1, dpi, i2s0, pwm0, i2s1, gpio, proc_rio, pio, gpclk1),
+	PIN(19, spi1, dpi, i2s0, pwm0, i2s1, gpio, proc_rio, pio, _),
+	PIN(20, spi1, dpi, i2s0, gpclk0, i2s1, gpio, proc_rio, pio, _),
+	PIN(21, spi1, dpi, i2s0, gpclk1, i2s1, gpio, proc_rio, pio, _),
+	PIN(22, sd0, dpi, i2s0, i2c3, i2s1, gpio, proc_rio, pio, _),
+	PIN(23, sd0, dpi, i2s0, i2c3, i2s1, gpio, proc_rio, pio, _),
+	PIN(24, sd0, dpi, i2s0, _, i2s1, gpio, proc_rio, pio, spi2),
+	PIN(25, sd0, dpi, i2s0, mic, i2s1, gpio, proc_rio, pio, spi3),
+	PIN(26, sd0, dpi, i2s0, mic, i2s1, gpio, proc_rio, pio, spi5),
+	PIN(27, sd0, dpi, i2s0, mic, i2s1, gpio, proc_rio, pio, spi1),
+	PIN(28, sd1, i2c4, i2s2, spi6, vbus0, gpio, proc_rio, _, _),
+	PIN(29, sd1, i2c4, i2s2, spi6, vbus0, gpio, proc_rio, _, _),
+	PIN(30, sd1, i2c5, i2s2, spi6, uart5, gpio, proc_rio, _, _),
+	PIN(31, sd1, i2c5, i2s2, spi6, uart5, gpio, proc_rio, _, _),
+	PIN(32, sd1, gpclk3, i2s2, spi6, uart5, gpio, proc_rio, _, _),
+	PIN(33, sd1, gpclk4, i2s2, spi6, uart5, gpio, proc_rio, _, _),
+	PIN(34, pwm1, gpclk3, vbus0, i2c4, mic, gpio, proc_rio, _, _),
+	PIN(35, spi8, pwm1, vbus0, i2c4, mic, gpio, proc_rio, _, _),
+	PIN(36, spi8, uart5, pcie_clkreq_n, i2c5, mic, gpio, proc_rio, _, _),
+	PIN(37, spi8, uart5, mic, i2c5, pcie_clkreq_n, gpio, proc_rio, _, _),
+	PIN(38, spi8, uart5, mic, i2c6, aaud, gpio, proc_rio, dsi0_te_ext, _),
+	PIN(39, spi8, uart5, mic, i2c6, aaud, gpio, proc_rio, dsi1_te_ext, _),
+	PIN(40, pwm1, uart5, i2c4, spi6, aaud, gpio, proc_rio, _, _),
+	PIN(41, pwm1, uart5, i2c4, spi6, aaud, gpio, proc_rio, _, _),
+	PIN(42, gpclk5, uart5, vbus1, spi6, i2s2, gpio, proc_rio, _, _),
+	PIN(43, gpclk4, uart5, vbus1, spi6, i2s2, gpio, proc_rio, _, _),
+	PIN(44, gpclk5, i2c5, pwm1, spi6, i2s2, gpio, proc_rio, _, _),
+	PIN(45, pwm1, i2c5, spi7, spi6, i2s2, gpio, proc_rio, _, _),
+	PIN(46, gpclk3, i2c4, spi7, mic, i2s2, gpio, proc_rio, dsi0_te_ext, _),
+	PIN(47, gpclk5, i2c4, spi7, mic, i2s2, gpio, proc_rio, dsi1_te_ext, _),
+	PIN(48, pwm1, pcie_clkreq_n, spi7, mic, uart5, gpio, proc_rio, _, _),
+	PIN(49, spi8, spi7, i2c5, aaud, uart5, gpio, proc_rio, _, _),
+	PIN(50, spi8, spi7, i2c5, aaud, vbus2, gpio, proc_rio, _, _),
+	PIN(51, spi8, spi7, i2c6, aaud, vbus2, gpio, proc_rio, _, _),
+	PIN(52, spi8, _, i2c6, aaud, vbus3, gpio, proc_rio, _, _),
+	PIN(53, spi8, spi7, _, pcie_clkreq_n, vbus3, gpio, proc_rio, _, _),
+};
+
+static const u8 legacy_fsel_map[][8] = {
+	LEGACY_MAP(0, i2c0, _, dpi, spi2, uart1, _),
+	LEGACY_MAP(1, i2c0, _, dpi, spi2, uart1, _),
+	LEGACY_MAP(2, i2c1, _, dpi, spi2, uart1, _),
+	LEGACY_MAP(3, i2c1, _, dpi, spi2, uart1, _),
+	LEGACY_MAP(4, gpclk0, _, dpi, spi3, uart2, i2c2),
+	LEGACY_MAP(5, gpclk1, _, dpi, spi3, uart2, i2c2),
+	LEGACY_MAP(6, gpclk2, _, dpi, spi3, uart2, i2c3),
+	LEGACY_MAP(7, spi0, _, dpi, spi3, uart2, i2c3),
+	LEGACY_MAP(8, spi0, _, dpi, _, uart3, i2c0),
+	LEGACY_MAP(9, spi0, _, dpi, _, uart3, i2c0),
+	LEGACY_MAP(10, spi0, _, dpi, _, uart3, i2c1),
+	LEGACY_MAP(11, spi0, _, dpi, _, uart3, i2c1),
+	LEGACY_MAP(12, pwm0, _, dpi, spi5, uart4, i2c2),
+	LEGACY_MAP(13, pwm0, _, dpi, spi5, uart4, i2c2),
+	LEGACY_MAP(14, uart0, _, dpi, spi5, uart4, _),
+	LEGACY_MAP(15, uart0, _, dpi, spi5, uart4, _),
+	LEGACY_MAP(16, _, _, dpi, uart0, spi1, _),
+	LEGACY_MAP(17, _, _, dpi, uart0, spi1, _),
+	LEGACY_MAP(18, i2s0, _, dpi, _, spi1, pwm0),
+	LEGACY_MAP(19, i2s0, _, dpi, _, spi1, pwm0),
+	LEGACY_MAP(20, i2s0, _, dpi, _, spi1, gpclk0),
+	LEGACY_MAP(21, i2s0, _, dpi, _, spi1, gpclk1),
+	LEGACY_MAP(22, sd0, _, dpi, _, _, i2c3),
+	LEGACY_MAP(23, sd0, _, dpi, _, _, i2c3),
+	LEGACY_MAP(24, sd0, _, dpi, _, _, spi2),
+	LEGACY_MAP(25, sd0, _, dpi, _, _, spi3),
+	LEGACY_MAP(26, sd0, _, dpi, _, _, spi5),
+	LEGACY_MAP(27, sd0, _, dpi, _, _, _),
+};
+
+static const char * const irq_type_names[] = {
+	[IRQ_TYPE_NONE] = "none",
+	[IRQ_TYPE_EDGE_RISING] = "edge-rising",
+	[IRQ_TYPE_EDGE_FALLING] = "edge-falling",
+	[IRQ_TYPE_EDGE_BOTH] = "edge-both",
+	[IRQ_TYPE_LEVEL_HIGH] = "level-high",
+	[IRQ_TYPE_LEVEL_LOW] = "level-low",
+};
+
+static bool persist_gpio_outputs = true;
+module_param(persist_gpio_outputs, bool, 0644);
+MODULE_PARM_DESC(persist_gpio_outputs, "Enable GPIO_OUT persistence when pin is freed");
+
+static bool pace_pin_updates = true;
+module_param(pace_pin_updates, bool, 0644);
+MODULE_PARM_DESC(pace_pin_updates, "Update pin states with guaranteed monotonicity if PCIe ASPM is enabled");
+
+static inline void rp1_pin_writel(u32 val, void __iomem *dummy, void __iomem *reg)
+{
+	unsigned long flags;
+
+	local_irq_save(flags);
+	/*
+	 * Issuing 6 pipelined writes to the RC's Slot Control register will stall the
+	 * peripheral bus inside 2712 if the link is in L1. This acts as a lightweight
+	 * "fence" operation preventing back-to-back writes arriving at RP1 on a wake.
+	 */
+	if (dummy) {
+		writel_relaxed(0, dummy);
+		writel_relaxed(0, dummy);
+		writel_relaxed(0, dummy);
+		writel_relaxed(0, dummy);
+		writel_relaxed(0, dummy);
+		writel_relaxed(0, dummy);
+	}
+	writel_relaxed(val, reg);
+	local_irq_restore(flags);
+}
+
+static inline u32 rp1_pin_readl(const void __iomem *ioaddr)
+{
+	/*
+	 * Prior posted writes may not yet have been emitted by the CPU - do a store-flush
+	 * before reading GPIO state, as this will serialise writes versus the next issued read.
+	 */
+	__dma_wmb();
+	return readl(ioaddr);
+}
+
+static int rp1_pinconf_set(struct pinctrl_dev *pctldev,
+			   unsigned int offset, unsigned long *configs,
+			   unsigned int num_configs);
+
+static struct rp1_pin_info *rp1_get_pin(struct gpio_chip *chip,
+					unsigned int offset)
+{
+	struct rp1_pinctrl *pc = gpiochip_get_data(chip);
+
+	if (pc && offset < RP1_NUM_GPIOS)
+		return &pc->pins[offset];
+	return NULL;
+}
+
+static struct rp1_pin_info *rp1_get_pin_pctl(struct pinctrl_dev *pctldev,
+					     unsigned int offset)
+{
+	struct rp1_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev);
+
+	if (pc && offset < RP1_NUM_GPIOS)
+		return &pc->pins[offset];
+	return NULL;
+}
+
+static void rp1_pad_update(struct rp1_pin_info *pin, u32 clr, u32 set)
+{
+	u32 padctrl = rp1_pin_readl(pin->pad);
+
+	padctrl &= ~clr;
+	padctrl |= set;
+
+	rp1_pin_writel(padctrl, pin->dummy, pin->pad);
+}
+
+static void rp1_input_enable(struct rp1_pin_info *pin, int value)
+{
+	rp1_pad_update(pin, RP1_PAD_IN_ENABLE_MASK,
+		       value ? RP1_PAD_IN_ENABLE_MASK : 0);
+}
+
+static void rp1_output_enable(struct rp1_pin_info *pin, int value)
+{
+	rp1_pad_update(pin, RP1_PAD_OUT_DISABLE_MASK,
+		       value ? 0 : RP1_PAD_OUT_DISABLE_MASK);
+}
+
+static u32 rp1_get_fsel(struct rp1_pin_info *pin)
+{
+	u32 ctrl = rp1_pin_readl(pin->gpio + RP1_GPIO_CTRL);
+	u32 oeover = FLD_GET(ctrl, RP1_GPIO_CTRL_OEOVER);
+	u32 fsel = FLD_GET(ctrl, RP1_GPIO_CTRL_FUNCSEL);
+
+	if (oeover != RP1_OEOVER_PERI || fsel >= RP1_FSEL_COUNT)
+		fsel = RP1_FSEL_NONE;
+
+	return fsel;
+}
+
+static void rp1_set_fsel(struct rp1_pin_info *pin, u32 fsel)
+{
+	u32 ctrl = rp1_pin_readl(pin->gpio + RP1_GPIO_CTRL);
+
+	if (fsel >= RP1_FSEL_COUNT)
+		fsel = RP1_FSEL_NONE_HW;
+
+	rp1_input_enable(pin, 1);
+	rp1_output_enable(pin, 1);
+
+	if (fsel == RP1_FSEL_NONE) {
+		FLD_SET(ctrl, RP1_GPIO_CTRL_OEOVER, RP1_OEOVER_DISABLE);
+	} else {
+		FLD_SET(ctrl, RP1_GPIO_CTRL_OUTOVER, RP1_OUTOVER_PERI);
+		FLD_SET(ctrl, RP1_GPIO_CTRL_OEOVER, RP1_OEOVER_PERI);
+	}
+	FLD_SET(ctrl, RP1_GPIO_CTRL_FUNCSEL, fsel);
+	rp1_pin_writel(ctrl, pin->dummy, pin->gpio + RP1_GPIO_CTRL);
+}
+
+static int rp1_get_dir(struct rp1_pin_info *pin)
+{
+	return !(rp1_pin_readl(pin->rio + RP1_RIO_OE) & (1 << pin->offset)) ?
+		RP1_DIR_INPUT : RP1_DIR_OUTPUT;
+}
+
+static void rp1_set_dir(struct rp1_pin_info *pin, bool is_input)
+{
+	int offset = is_input ? RP1_CLR_OFFSET : RP1_SET_OFFSET;
+
+	rp1_pin_writel(1 << pin->offset, pin->dummy, pin->rio + RP1_RIO_OE + offset);
+}
+
+static int rp1_get_value(struct rp1_pin_info *pin)
+{
+	return !!(rp1_pin_readl(pin->rio + RP1_RIO_IN) & (1 << pin->offset));
+}
+
+static void rp1_set_value(struct rp1_pin_info *pin, int value)
+{
+	/* Assume the pin is already an output */
+	rp1_pin_writel(1 << pin->offset, pin->dummy,
+		       pin->rio + RP1_RIO_OUT + (value ? RP1_SET_OFFSET : RP1_CLR_OFFSET));
+}
+
+static int rp1_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+	struct rp1_pin_info *pin = rp1_get_pin(chip, offset);
+	int ret;
+
+	if (!pin)
+		return -EINVAL;
+	ret = rp1_get_value(pin);
+	return ret;
+}
+
+static void rp1_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+	struct rp1_pin_info *pin = rp1_get_pin(chip, offset);
+
+	if (pin)
+		rp1_set_value(pin, value);
+}
+
+static int rp1_gpio_get_direction(struct gpio_chip *chip, unsigned int offset)
+{
+	struct rp1_pin_info *pin = rp1_get_pin(chip, offset);
+	u32 fsel;
+
+	if (!pin)
+		return -EINVAL;
+	fsel = rp1_get_fsel(pin);
+	if (fsel != RP1_FSEL_GPIO)
+		return -EINVAL;
+	return (rp1_get_dir(pin) == RP1_DIR_OUTPUT) ?
+		GPIO_LINE_DIRECTION_OUT :
+		GPIO_LINE_DIRECTION_IN;
+}
+
+static int rp1_gpio_direction_input(struct gpio_chip *chip, unsigned offset)
+{
+	struct rp1_pin_info *pin = rp1_get_pin(chip, offset);
+
+	if (!pin)
+		return -EINVAL;
+	rp1_set_dir(pin, RP1_DIR_INPUT);
+	rp1_set_fsel(pin, RP1_FSEL_GPIO);
+	return 0;
+}
+
+static int rp1_gpio_direction_output(struct gpio_chip *chip, unsigned offset,
+				     int value)
+{
+	struct rp1_pin_info *pin = rp1_get_pin(chip, offset);
+
+	if (!pin)
+		return -EINVAL;
+	rp1_set_value(pin, value);
+	rp1_set_dir(pin, RP1_DIR_OUTPUT);
+	rp1_set_fsel(pin, RP1_FSEL_GPIO);
+	return 0;
+}
+
+static int rp1_gpio_set_config(struct gpio_chip *gc, unsigned offset,
+			       unsigned long config)
+{
+	struct rp1_pinctrl *pc = gpiochip_get_data(gc);
+	unsigned long configs[] = { config };
+
+	return rp1_pinconf_set(pc->pctl_dev, offset, configs,
+			      ARRAY_SIZE(configs));
+}
+
+static const struct gpio_chip rp1_gpio_chip = {
+	.label = MODULE_NAME,
+	.owner = THIS_MODULE,
+	.request = gpiochip_generic_request,
+	.free = gpiochip_generic_free,
+	.direction_input = rp1_gpio_direction_input,
+	.direction_output = rp1_gpio_direction_output,
+	.get_direction = rp1_gpio_get_direction,
+	.get = rp1_gpio_get,
+	.set = rp1_gpio_set,
+	.base = -1,
+	.set_config = rp1_gpio_set_config,
+	.ngpio = RP1_NUM_GPIOS,
+	.can_sleep = false,
+};
+
+static void rp1_gpio_irq_handler(struct irq_desc *desc)
+{
+	struct gpio_chip *chip = irq_desc_get_handler_data(desc);
+	struct rp1_pinctrl *pc = gpiochip_get_data(chip);
+	struct irq_chip *host_chip = irq_desc_get_chip(desc);
+	const struct rp1_iobank_desc *bank;
+	int irq = irq_desc_get_irq(desc);
+	unsigned long ints;
+	int b;
+
+	if (pc->irq[0] == irq)
+		bank = &rp1_iobanks[0];
+	else if (pc->irq[1] == irq)
+		bank = &rp1_iobanks[1];
+	else
+		bank = &rp1_iobanks[2];
+
+	chained_irq_enter(host_chip, desc);
+
+	ints = readl(pc->gpio_base + bank->ints_offset);
+	for_each_set_bit(b, &ints, 32) {
+		struct rp1_pin_info *pin = rp1_get_pin(chip, bank->min_gpio + b);
+
+		writel(RP1_GPIO_CTRL_IRQRESET,
+		       pin->gpio + RP1_SET_OFFSET + RP1_GPIO_CTRL);
+		generic_handle_irq(irq_linear_revmap(pc->gpio_chip.irq.domain,
+						     bank->min_gpio + b));
+	}
+
+	chained_irq_exit(host_chip, desc);
+}
+
+static void rp1_gpio_irq_config(struct rp1_pin_info *pin, bool enable)
+{
+	writel(1 << pin->offset,
+	       pin->inte + (enable ? RP1_SET_OFFSET : RP1_CLR_OFFSET));
+	if (!enable)
+		/* Clear any latched events */
+		writel(RP1_GPIO_CTRL_IRQRESET,
+		       pin->gpio + RP1_SET_OFFSET + RP1_GPIO_CTRL);
+}
+
+static void rp1_gpio_irq_enable(struct irq_data *data)
+{
+	struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+	unsigned gpio = irqd_to_hwirq(data);
+	struct rp1_pin_info *pin = rp1_get_pin(chip, gpio);
+
+	rp1_gpio_irq_config(pin, true);
+}
+
+static void rp1_gpio_irq_disable(struct irq_data *data)
+{
+	struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+	unsigned gpio = irqd_to_hwirq(data);
+	struct rp1_pin_info *pin = rp1_get_pin(chip, gpio);
+
+	rp1_gpio_irq_config(pin, false);
+}
+
+static int rp1_irq_set_type(struct rp1_pin_info *pin, unsigned int type)
+{
+	u32 irq_flags;
+
+	switch (type) {
+	case IRQ_TYPE_NONE:
+		irq_flags = 0;
+		break;
+	case IRQ_TYPE_EDGE_RISING:
+		irq_flags = RP1_INT_EDGE_RISING;
+		break;
+	case IRQ_TYPE_EDGE_FALLING:
+		irq_flags = RP1_INT_EDGE_FALLING;
+		break;
+	case IRQ_TYPE_EDGE_BOTH:
+		irq_flags = RP1_INT_EDGE_RISING | RP1_INT_EDGE_FALLING;
+		break;
+	case IRQ_TYPE_LEVEL_HIGH:
+		irq_flags = RP1_INT_LEVEL_HIGH;
+		break;
+	case IRQ_TYPE_LEVEL_LOW:
+		irq_flags = RP1_INT_LEVEL_LOW;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	/* Clear the event enables */
+	writel(RP1_INT_MASK << RP1_GPIO_EVENTS_SHIFT_RAW,
+	       pin->gpio + RP1_CLR_OFFSET + RP1_GPIO_CTRL);
+	/* Clear any latched events */
+	writel(RP1_GPIO_CTRL_IRQRESET,
+		pin->gpio + RP1_SET_OFFSET + RP1_GPIO_CTRL);
+	/* Enable the events that are needed */
+	writel(irq_flags << RP1_GPIO_EVENTS_SHIFT_RAW,
+	       pin->gpio + RP1_SET_OFFSET + RP1_GPIO_CTRL);
+	pin->irq_type = type;
+
+	return 0;
+}
+
+static int rp1_gpio_irq_set_type(struct irq_data *data, unsigned int type)
+{
+	struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+	struct rp1_pinctrl *pc = gpiochip_get_data(chip);
+	unsigned gpio = irqd_to_hwirq(data);
+	struct rp1_pin_info *pin = rp1_get_pin(chip, gpio);
+	int bank = pin->bank;
+	unsigned long flags;
+	int ret;
+
+	raw_spin_lock_irqsave(&pc->irq_lock[bank], flags);
+
+	ret = rp1_irq_set_type(pin, type);
+	if (!ret) {
+		if (type & IRQ_TYPE_EDGE_BOTH)
+			irq_set_handler_locked(data, handle_edge_irq);
+		else
+			irq_set_handler_locked(data, handle_level_irq);
+	}
+
+	raw_spin_unlock_irqrestore(&pc->irq_lock[bank], flags);
+
+	return ret;
+}
+
+static void rp1_gpio_irq_ack(struct irq_data *data)
+{
+	struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+	unsigned gpio = irqd_to_hwirq(data);
+	struct rp1_pin_info *pin = rp1_get_pin(chip, gpio);
+
+	/* Clear any latched events */
+	writel(RP1_GPIO_CTRL_IRQRESET, pin->gpio + RP1_SET_OFFSET + RP1_GPIO_CTRL);
+}
+
+static int rp1_gpio_irq_set_affinity(struct irq_data *data, const struct cpumask *dest, bool force)
+{
+	struct gpio_chip *chip = irq_data_get_irq_chip_data(data);
+	struct rp1_pinctrl *pc = gpiochip_get_data(chip);
+	const struct rp1_iobank_desc *bank;
+	struct irq_data *parent_data = NULL;
+	int i;
+
+	for (i = 0; i < 3; i++) {
+		bank = &rp1_iobanks[i];
+		if (data->hwirq >= bank->min_gpio &&
+		    data->hwirq < bank->min_gpio + bank->num_gpios) {
+			parent_data = irq_get_irq_data(pc->irq[i]);
+			break;
+		}
+	}
+
+	if (parent_data && parent_data->chip->irq_set_affinity)
+		return parent_data->chip->irq_set_affinity(parent_data, dest, force);
+
+	return -EINVAL;
+}
+
+static struct irq_chip rp1_gpio_irq_chip = {
+	.name = MODULE_NAME,
+	.irq_enable = rp1_gpio_irq_enable,
+	.irq_disable = rp1_gpio_irq_disable,
+	.irq_set_type = rp1_gpio_irq_set_type,
+	.irq_ack = rp1_gpio_irq_ack,
+	.irq_mask = rp1_gpio_irq_disable,
+	.irq_unmask = rp1_gpio_irq_enable,
+	.irq_set_affinity = rp1_gpio_irq_set_affinity,
+	.flags = IRQCHIP_IMMUTABLE,
+};
+
+static int rp1_pctl_get_groups_count(struct pinctrl_dev *pctldev)
+{
+	return ARRAY_SIZE(rp1_gpio_groups);
+}
+
+static const char *rp1_pctl_get_group_name(struct pinctrl_dev *pctldev,
+					   unsigned selector)
+{
+	return rp1_gpio_groups[selector];
+}
+
+static enum funcs rp1_get_fsel_func(unsigned pin, unsigned fsel)
+{
+	if (pin < RP1_NUM_GPIOS) {
+		if (fsel < RP1_FSEL_COUNT)
+			return rp1_gpio_pin_funcs[pin].funcs[fsel];
+		else if (fsel == RP1_FSEL_NONE)
+			return func_none;
+	}
+	return func_invalid;
+}
+
+static int rp1_pctl_get_group_pins(struct pinctrl_dev *pctldev,
+				   unsigned selector,
+				   const unsigned **pins,
+				   unsigned *num_pins)
+{
+	*pins = &rp1_gpio_pins[selector].number;
+	*num_pins = 1;
+
+	return 0;
+}
+
+static void rp1_pctl_pin_dbg_show(struct pinctrl_dev *pctldev,
+				  struct seq_file *s,
+				  unsigned offset)
+{
+	struct rp1_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev);
+	struct gpio_chip *chip = &pc->gpio_chip;
+	struct rp1_pin_info *pin = rp1_get_pin_pctl(pctldev, offset);
+	u32 fsel = rp1_get_fsel(pin);
+	enum funcs func = rp1_get_fsel_func(offset, fsel);
+	int value = rp1_get_value(pin);
+	int irq = irq_find_mapping(chip->irq.domain, offset);
+
+	seq_printf(s, "function %s (%s) in %s; irq %d (%s)",
+		   rp1_func_names[fsel], rp1_func_names[func],
+		   value ? "hi" : "lo",
+		   irq, irq_type_names[pin->irq_type]);
+}
+
+static void rp1_pctl_dt_free_map(struct pinctrl_dev *pctldev,
+				 struct pinctrl_map *maps, unsigned num_maps)
+{
+	int i;
+
+	for (i = 0; i < num_maps; i++)
+		if (maps[i].type == PIN_MAP_TYPE_CONFIGS_PIN)
+			kfree(maps[i].data.configs.configs);
+
+	kfree(maps);
+}
+
+static int rp1_pctl_legacy_map_func(struct rp1_pinctrl *pc,
+				    struct device_node *np, u32 pin, u32 fnum,
+				    struct pinctrl_map *maps,
+				    unsigned int *num_maps)
+{
+	struct pinctrl_map *map = &maps[*num_maps];
+	enum funcs func;
+
+	if (fnum >= ARRAY_SIZE(legacy_fsel_map[0])) {
+		dev_err(pc->dev, "%pOF: invalid brcm,function %d\n", np, fnum);
+		return -EINVAL;
+	}
+
+	if (pin < ARRAY_SIZE(legacy_fsel_map)) {
+		func = legacy_fsel_map[pin][fnum];
+	} else if (fnum < 2) {
+		func = func_gpio;
+	} else {
+		dev_err(pc->dev, "%pOF: invalid brcm,pins value %d\n",
+			np, pin);
+		return -EINVAL;
+	}
+
+	if (func == func_invalid) {
+		dev_err(pc->dev, "%pOF: brcm,function %d not supported on pin %d\n",
+			np, fnum, pin);
+	}
+
+	map->type = PIN_MAP_TYPE_MUX_GROUP;
+	map->data.mux.group = rp1_gpio_groups[pin];
+	map->data.mux.function = rp1_func_names[func];
+	(*num_maps)++;
+
+	return 0;
+}
+
+static int rp1_pctl_legacy_map_pull(struct rp1_pinctrl *pc,
+				    struct device_node *np, u32 pin, u32 pull,
+				    struct pinctrl_map *maps,
+				    unsigned int *num_maps)
+{
+	struct pinctrl_map *map = &maps[*num_maps];
+	enum pin_config_param param;
+	unsigned long *configs;
+
+	switch (pull) {
+	case RP1_PUD_OFF:
+		param = PIN_CONFIG_BIAS_DISABLE;
+		break;
+	case RP1_PUD_DOWN:
+		param = PIN_CONFIG_BIAS_PULL_DOWN;
+		break;
+	case RP1_PUD_UP:
+		param = PIN_CONFIG_BIAS_PULL_UP;
+		break;
+	default:
+		dev_err(pc->dev, "%pOF: invalid brcm,pull %d\n", np, pull);
+		return -EINVAL;
+	}
+
+	configs = kzalloc(sizeof(*configs), GFP_KERNEL);
+	if (!configs)
+		return -ENOMEM;
+
+	configs[0] = pinconf_to_config_packed(param, 0);
+	map->type = PIN_MAP_TYPE_CONFIGS_PIN;
+	map->data.configs.group_or_pin = rp1_gpio_pins[pin].name;
+	map->data.configs.configs = configs;
+	map->data.configs.num_configs = 1;
+	(*num_maps)++;
+
+	return 0;
+}
+
+static int rp1_pctl_dt_node_to_map(struct pinctrl_dev *pctldev,
+				   struct device_node *np,
+				   struct pinctrl_map **map,
+				   unsigned int *num_maps)
+{
+	struct rp1_pinctrl *pc = pinctrl_dev_get_drvdata(pctldev);
+	struct property *pins, *funcs, *pulls;
+	int num_pins, num_funcs, num_pulls, maps_per_pin;
+	struct pinctrl_map *maps;
+	unsigned long *configs = NULL;
+	const char *function = NULL;
+	unsigned int reserved_maps;
+	int num_configs = 0;
+	int i, err;
+	u32 pin, func, pull;
+
+	/* Check for legacy pin declaration */
+	pins = of_find_property(np, "brcm,pins", NULL);
+
+	if (!pins) /* Assume generic bindings in this node */
+		return pinconf_generic_dt_node_to_map_all(pctldev, np, map, num_maps);
+
+	funcs = of_find_property(np, "brcm,function", NULL);
+	if (!funcs)
+		of_property_read_string(np, "function", &function);
+
+	pulls = of_find_property(np, "brcm,pull", NULL);
+	if (!pulls)
+		pinconf_generic_parse_dt_config(np, pctldev, &configs, &num_configs);
+
+	if (!function && !funcs && !num_configs && !pulls) {
+		dev_err(pc->dev,
+			"%pOF: no function, brcm,function, brcm,pull, etc.\n",
+			np);
+		return -EINVAL;
+	}
+
+	num_pins = pins->length / 4;
+	num_funcs = funcs ? (funcs->length / 4) : 0;
+	num_pulls = pulls ? (pulls->length / 4) : 0;
+
+	if (num_funcs > 1 && num_funcs != num_pins) {
+		dev_err(pc->dev,
+			"%pOF: brcm,function must have 1 or %d entries\n",
+			np, num_pins);
+		return -EINVAL;
+	}
+
+	if (num_pulls > 1 && num_pulls != num_pins) {
+		dev_err(pc->dev,
+			"%pOF: brcm,pull must have 1 or %d entries\n",
+			np, num_pins);
+		return -EINVAL;
+	}
+
+	maps_per_pin = 0;
+	if (function || num_funcs)
+		maps_per_pin++;
+	if (num_configs || num_pulls)
+		maps_per_pin++;
+	reserved_maps = num_pins * maps_per_pin;
+	maps = kcalloc(reserved_maps, sizeof(*maps), GFP_KERNEL);
+	if (!maps)
+		return -ENOMEM;
+
+	*num_maps = 0;
+
+	for (i = 0; i < num_pins; i++) {
+		err = of_property_read_u32_index(np, "brcm,pins", i, &pin);
+		if (err)
+			goto out;
+		if (num_funcs) {
+			err = of_property_read_u32_index(np, "brcm,function",
+							 (num_funcs > 1) ? i : 0,
+							 &func);
+			if (err)
+				goto out;
+			err = rp1_pctl_legacy_map_func(pc, np, pin, func,
+						       maps, num_maps);
+		} else if (function) {
+			err = pinctrl_utils_add_map_mux(pctldev, &maps,
+							&reserved_maps, num_maps,
+							rp1_gpio_groups[pin],
+							function);
+		}
+
+		if (err)
+			goto out;
+
+		if (num_pulls) {
+			err = of_property_read_u32_index(np, "brcm,pull",
+							 (num_pulls > 1) ? i : 0,
+							 &pull);
+			if (err)
+				goto out;
+			err = rp1_pctl_legacy_map_pull(pc, np, pin, pull,
+						       maps, num_maps);
+		} else if (num_configs) {
+			err = pinctrl_utils_add_map_configs(pctldev, &maps,
+							    &reserved_maps, num_maps,
+							    rp1_gpio_groups[pin],
+							    configs, num_configs,
+							    PIN_MAP_TYPE_CONFIGS_PIN);
+		}
+
+		if (err)
+			goto out;
+	}
+
+	*map = maps;
+
+	return 0;
+
+out:
+	rp1_pctl_dt_free_map(pctldev, maps, reserved_maps);
+	return err;
+}
+
+static const struct pinctrl_ops rp1_pctl_ops = {
+	.get_groups_count = rp1_pctl_get_groups_count,
+	.get_group_name = rp1_pctl_get_group_name,
+	.get_group_pins = rp1_pctl_get_group_pins,
+	.pin_dbg_show = rp1_pctl_pin_dbg_show,
+	.dt_node_to_map = rp1_pctl_dt_node_to_map,
+	.dt_free_map = rp1_pctl_dt_free_map,
+};
+
+static int rp1_pmx_free(struct pinctrl_dev *pctldev, unsigned offset)
+{
+	struct rp1_pin_info *pin = rp1_get_pin_pctl(pctldev, offset);
+	u32 fsel = rp1_get_fsel(pin);
+
+	/* Return all pins to GPIO_IN, unless persist_gpio_outputs is set */
+	if (persist_gpio_outputs && fsel == RP1_FSEL_GPIO)
+		return 0;
+
+	rp1_set_dir(pin, RP1_DIR_INPUT);
+	rp1_set_fsel(pin, RP1_FSEL_GPIO);
+
+	return 0;
+}
+
+static int rp1_pmx_get_functions_count(struct pinctrl_dev *pctldev)
+{
+	return func_count;
+}
+
+static const char *rp1_pmx_get_function_name(struct pinctrl_dev *pctldev,
+					     unsigned selector)
+{
+	return (selector < func_count) ? rp1_func_names[selector] : NULL;
+}
+
+static int rp1_pmx_get_function_groups(struct pinctrl_dev *pctldev,
+				       unsigned selector,
+				       const char * const **groups,
+				       unsigned * const num_groups)
+{
+	/* every pin can do every function */
+	*groups = rp1_gpio_groups;
+	*num_groups = ARRAY_SIZE(rp1_gpio_groups);
+
+	return 0;
+}
+
+static int rp1_pmx_set(struct pinctrl_dev *pctldev, unsigned func_selector,
+		       unsigned group_selector)
+{
+	struct rp1_pin_info *pin = rp1_get_pin_pctl(pctldev, group_selector);
+	const u8 *pin_funcs;
+	int fsel;
+
+	/* func_selector is an enum funcs, so needs translation */
+
+	if (func_selector >= RP1_FSEL_COUNT) {
+		/* Convert to an fsel number */
+		pin_funcs = rp1_gpio_pin_funcs[pin->num].funcs;
+		for (fsel = 0; fsel < RP1_FSEL_COUNT; fsel++) {
+			if (pin_funcs[fsel] == func_selector)
+				break;
+		}
+	} else {
+		fsel = (int)func_selector;
+	}
+
+	if (fsel >= RP1_FSEL_COUNT && fsel != RP1_FSEL_NONE)
+		return -EINVAL;
+
+	rp1_set_fsel(pin, fsel);
+
+	return 0;
+}
+
+static void rp1_pmx_gpio_disable_free(struct pinctrl_dev *pctldev,
+				      struct pinctrl_gpio_range *range,
+				      unsigned offset)
+{
+	(void)rp1_pmx_free(pctldev, offset);
+}
+
+static int rp1_pmx_gpio_set_direction(struct pinctrl_dev *pctldev,
+				      struct pinctrl_gpio_range *range,
+				      unsigned offset,
+				      bool input)
+{
+	struct rp1_pin_info *pin = rp1_get_pin_pctl(pctldev, offset);
+
+	rp1_set_dir(pin, input);
+	rp1_set_fsel(pin, RP1_FSEL_GPIO);
+
+	return 0;
+}
+
+static const struct pinmux_ops rp1_pmx_ops = {
+	.free = rp1_pmx_free,
+	.get_functions_count = rp1_pmx_get_functions_count,
+	.get_function_name = rp1_pmx_get_function_name,
+	.get_function_groups = rp1_pmx_get_function_groups,
+	.set_mux = rp1_pmx_set,
+	.gpio_disable_free = rp1_pmx_gpio_disable_free,
+	.gpio_set_direction = rp1_pmx_gpio_set_direction,
+};
+
+static void rp1_pull_config_set(struct rp1_pin_info *pin, unsigned int arg)
+{
+	u32 padctrl = rp1_pin_readl(pin->pad);
+
+	FLD_SET(padctrl, RP1_PAD_PULL, arg & 0x3);
+
+	writel(padctrl, pin->pad);
+}
+
+/* Generic pinconf methods */
+
+static int rp1_pinconf_set(struct pinctrl_dev *pctldev, unsigned int offset,
+			   unsigned long *configs, unsigned int num_configs)
+{
+	struct rp1_pin_info *pin = rp1_get_pin_pctl(pctldev, offset);
+	u32 param, arg;
+	int i;
+
+	if (!pin)
+		return -EINVAL;
+
+	for (i = 0; i < num_configs; i++) {
+		param = pinconf_to_config_param(configs[i]);
+		arg = pinconf_to_config_argument(configs[i]);
+
+		switch (param) {
+		case PIN_CONFIG_BIAS_DISABLE:
+			rp1_pull_config_set(pin, RP1_PUD_OFF);
+			break;
+
+		case PIN_CONFIG_BIAS_PULL_DOWN:
+			rp1_pull_config_set(pin, RP1_PUD_DOWN);
+			break;
+
+		case PIN_CONFIG_BIAS_PULL_UP:
+			rp1_pull_config_set(pin, RP1_PUD_UP);
+			break;
+
+		case PIN_CONFIG_INPUT_ENABLE:
+			rp1_input_enable(pin, arg);
+			break;
+
+		case PIN_CONFIG_OUTPUT_ENABLE:
+			rp1_output_enable(pin, arg);
+			break;
+
+		case PIN_CONFIG_OUTPUT:
+			rp1_set_value(pin, arg);
+			rp1_set_dir(pin, RP1_DIR_OUTPUT);
+			rp1_set_fsel(pin, RP1_FSEL_GPIO);
+			break;
+
+		case PIN_CONFIG_INPUT_SCHMITT_ENABLE:
+			rp1_pad_update(pin, RP1_PAD_SCHMITT_MASK,
+				       arg ? RP1_PAD_SCHMITT_MASK : 0);
+			break;
+
+		case PIN_CONFIG_SLEW_RATE:
+			rp1_pad_update(pin, RP1_PAD_SLEWFAST_MASK,
+				       arg ? RP1_PAD_SLEWFAST_MASK : 0);
+			break;
+
+		case PIN_CONFIG_DRIVE_STRENGTH:
+			switch (arg) {
+			case 2:
+				arg = RP1_PAD_DRIVE_2MA;
+				break;
+			case 4:
+				arg = RP1_PAD_DRIVE_4MA;
+				break;
+			case 8:
+				arg = RP1_PAD_DRIVE_8MA;
+				break;
+			case 12:
+				arg = RP1_PAD_DRIVE_12MA;
+				break;
+			default:
+				return -ENOTSUPP;
+			}
+			rp1_pad_update(pin, RP1_PAD_DRIVE_MASK, arg);
+			break;
+
+		default:
+			return -ENOTSUPP;
+
+		} /* switch param type */
+	} /* for each config */
+
+	return 0;
+}
+
+static int rp1_pinconf_get(struct pinctrl_dev *pctldev, unsigned offset,
+			   unsigned long *config)
+{
+	struct rp1_pin_info *pin = rp1_get_pin_pctl(pctldev, offset);
+	enum pin_config_param param = pinconf_to_config_param(*config);
+	u32 padctrl;
+	u32 arg;
+
+	if (!pin)
+		return -EINVAL;
+
+	padctrl = rp1_pin_readl(pin->pad);
+
+	switch (param) {
+	case PIN_CONFIG_INPUT_ENABLE:
+		arg = !!(padctrl & RP1_PAD_IN_ENABLE_MASK);
+		break;
+	case PIN_CONFIG_OUTPUT_ENABLE:
+		arg = !(padctrl & RP1_PAD_OUT_DISABLE_MASK);
+		break;
+	case PIN_CONFIG_INPUT_SCHMITT_ENABLE:
+		arg = !!(padctrl & RP1_PAD_SCHMITT_MASK);
+		break;
+	case PIN_CONFIG_SLEW_RATE:
+		arg = !!(padctrl & RP1_PAD_SLEWFAST_MASK);
+		break;
+	case PIN_CONFIG_DRIVE_STRENGTH:
+		switch (padctrl & RP1_PAD_DRIVE_MASK) {
+		case RP1_PAD_DRIVE_2MA:
+			arg = 2;
+			break;
+		case RP1_PAD_DRIVE_4MA:
+			arg = 4;
+			break;
+		case RP1_PAD_DRIVE_8MA:
+			arg = 8;
+			break;
+		case RP1_PAD_DRIVE_12MA:
+			arg = 12;
+			break;
+		}
+		break;
+	case PIN_CONFIG_BIAS_DISABLE:
+		arg = ((padctrl & RP1_PAD_PULL_MASK) == (RP1_PUD_OFF << RP1_PAD_PULL_LSB));
+		break;
+	case PIN_CONFIG_BIAS_PULL_DOWN:
+		arg = ((padctrl & RP1_PAD_PULL_MASK) == (RP1_PUD_DOWN << RP1_PAD_PULL_LSB));
+		break;
+
+	case PIN_CONFIG_BIAS_PULL_UP:
+		arg = ((padctrl & RP1_PAD_PULL_MASK) == (RP1_PUD_UP << RP1_PAD_PULL_LSB));
+		break;
+	default:
+		return -ENOTSUPP;
+	}
+
+	*config = pinconf_to_config_packed(param, arg);
+
+	return 0;
+}
+
+static const struct pinconf_ops rp1_pinconf_ops = {
+	.is_generic = true,
+	.pin_config_get = rp1_pinconf_get,
+	.pin_config_set = rp1_pinconf_set,
+};
+
+static struct pinctrl_desc rp1_pinctrl_desc = {
+	.name = MODULE_NAME,
+	.pins = rp1_gpio_pins,
+	.npins = ARRAY_SIZE(rp1_gpio_pins),
+	.pctlops = &rp1_pctl_ops,
+	.pmxops = &rp1_pmx_ops,
+	.confops = &rp1_pinconf_ops,
+	.owner = THIS_MODULE,
+};
+
+static struct pinctrl_gpio_range rp1_pinctrl_gpio_range = {
+	.name = MODULE_NAME,
+	.npins = RP1_NUM_GPIOS,
+};
+
+static const struct of_device_id rp1_pinctrl_match[] = {
+	{
+		.compatible = "raspberrypi,rp1-gpio",
+		.data = &rp1_pinconf_ops,
+	},
+	{}
+};
+
+static inline void __iomem *devm_auto_iomap(struct platform_device *pdev,
+					    unsigned int index)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+
+	if (np)
+		return devm_of_iomap(dev, np, (int)index, NULL);
+	else
+		return devm_platform_ioremap_resource(pdev, index);
+}
+
+static int rp1_pinctrl_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	struct device_node *rp1_node = NULL;
+	struct rp1_pinctrl *pc;
+	struct gpio_irq_chip *girq;
+	int err, i;
+
+	BUILD_BUG_ON(ARRAY_SIZE(rp1_gpio_pins) != RP1_NUM_GPIOS);
+	BUILD_BUG_ON(ARRAY_SIZE(rp1_gpio_groups) != RP1_NUM_GPIOS);
+
+	pc = devm_kzalloc(dev, sizeof(*pc), GFP_KERNEL);
+	if (!pc)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, pc);
+	pc->dev = dev;
+
+	pc->gpio_base = devm_auto_iomap(pdev, 0);
+	if (IS_ERR(pc->gpio_base)) {
+		dev_err(dev, "could not get GPIO IO memory\n");
+		return PTR_ERR(pc->gpio_base);
+	}
+
+	pc->rio_base = devm_auto_iomap(pdev, 1);
+	if (IS_ERR(pc->rio_base)) {
+		dev_err(dev, "could not get RIO IO memory\n");
+		return PTR_ERR(pc->rio_base);
+	}
+
+	pc->pads_base = devm_auto_iomap(pdev, 2);
+	if (IS_ERR(pc->pads_base)) {
+		dev_err(dev, "could not get PADS IO memory\n");
+		return PTR_ERR(pc->pads_base);
+	}
+
+	pc->gpio_chip = rp1_gpio_chip;
+	pc->gpio_chip.parent = dev;
+
+	/*
+	 * Workaround for the vagaries of PCIe on BCM2712
+	 *
+	 * If the link to RP1 is in L1, then the BRCMSTB RC will buffer many
+	 * outbound writes - and generate write responses for them, despite the
+	 * fact that the link is not yet active. This has the effect of compressing
+	 * multiple writes to GPIOs together, destroying any pacing that an application
+	 * may require in the 1-10us range.
+	 *
+	 * The RC Slot Control configuration register is special. It emits a
+	 * MsgD for every write to it, will stall further writes until the message
+	 * goes out on the wire. This can be (ab)used to force CPU stalls when the
+	 * link is inactive, at the cost of a small amount of downstream bandwidth
+	 * and some 200ns of added latency for each write.
+	 *
+	 * Several back-to-back configuration writes are necessary to "fill the pipe",
+	 * otherwise the outbound MAC can consume a pending MMIO write and reorder
+	 * it with respect to the config writes - undoing the intent.
+	 *
+	 * of_iomap() is used directly here as the address overlaps with the RC driver's
+	 * usage.
+	 */
+	rp1_node = of_find_node_by_name(NULL, "rp1");
+	if (!rp1_node)
+		dev_err(&pdev->dev, "failed to find RP1 DT node\n");
+	else if (pace_pin_updates &&
+		 of_device_is_compatible(rp1_node->parent, "brcm,bcm2712-pcie")) {
+		pc->dummy_base = of_iomap(rp1_node->parent, 0);
+		if (IS_ERR(pc->dummy_base)) {
+			dev_warn(&pdev->dev, "could not map bcm2712 root complex registers\n");
+			pc->dummy_base = NULL;
+		}
+	}
+
+	for (i = 0; i < RP1_NUM_BANKS; i++) {
+		const struct rp1_iobank_desc *bank = &rp1_iobanks[i];
+		int j;
+
+		for (j = 0; j < bank->num_gpios; j++) {
+			struct rp1_pin_info *pin =
+				&pc->pins[bank->min_gpio + j];
+
+			pin->num = bank->min_gpio + j;
+			pin->bank = i;
+			pin->offset = j;
+
+			pin->gpio = pc->gpio_base + bank->gpio_offset +
+				    j * sizeof(u32) * 2;
+			pin->inte = pc->gpio_base + bank->inte_offset;
+			pin->ints = pc->gpio_base + bank->ints_offset;
+			pin->rio  = pc->rio_base + bank->rio_offset;
+			pin->pad  = pc->pads_base + bank->pads_offset +
+				    j * sizeof(u32);
+			pin->dummy = pc->dummy_base ? pc->dummy_base + 0xc0 : NULL;
+		}
+
+		raw_spin_lock_init(&pc->irq_lock[i]);
+	}
+
+	pc->pctl_dev = devm_pinctrl_register(dev, &rp1_pinctrl_desc, pc);
+	if (IS_ERR(pc->pctl_dev)) {
+		err = PTR_ERR(pc->pctl_dev);
+		goto out_iounmap;
+	}
+
+	girq = &pc->gpio_chip.irq;
+	girq->chip = &rp1_gpio_irq_chip;
+	girq->parent_handler = rp1_gpio_irq_handler;
+	girq->num_parents = RP1_NUM_BANKS;
+	girq->parents = pc->irq;
+
+	/*
+	 * Use the same handler for all groups: this is necessary
+	 * since we use one gpiochip to cover all lines - the
+	 * irq handler then needs to figure out which group and
+	 * bank that was firing the IRQ and look up the per-group
+	 * and bank data.
+	 */
+	for (i = 0; i < RP1_NUM_BANKS; i++) {
+		pc->irq[i] = irq_of_parse_and_map(np, i);
+		if (!pc->irq[i]) {
+			girq->num_parents = i;
+			break;
+		}
+	}
+
+	girq->default_type = IRQ_TYPE_NONE;
+	girq->handler = handle_level_irq;
+
+	err = devm_gpiochip_add_data(dev, &pc->gpio_chip, pc);
+	if (err) {
+		dev_err(dev, "could not add GPIO chip\n");
+		goto out_iounmap;
+	}
+
+	pc->gpio_range = rp1_pinctrl_gpio_range;
+	pc->gpio_range.base = pc->gpio_chip.base;
+	pc->gpio_range.gc = &pc->gpio_chip;
+	pinctrl_add_gpio_range(pc->pctl_dev, &pc->gpio_range);
+
+	return 0;
+
+out_iounmap:
+	if (pc->dummy_base)
+		iounmap(pc->dummy_base);
+	return err;
+}
+
+static void rp1_pinctrl_remove(struct platform_device *pdev)
+{
+	struct rp1_pinctrl *pc = platform_get_drvdata(pdev);
+
+	if (pc->dummy_base)
+		iounmap(pc->dummy_base);
+}
+
+static struct platform_driver rp1_pinctrl_driver = {
+	.probe = rp1_pinctrl_probe,
+	.remove_new = rp1_pinctrl_remove,
+	.driver = {
+		.name = MODULE_NAME,
+		.of_match_table = rp1_pinctrl_match,
+		.suppress_bind_attrs = true,
+	},
+};
+builtin_platform_driver(rp1_pinctrl_driver);
diff --git a/drivers/pmdomain/bcm/bcm2835-power.c b/drivers/pmdomain/bcm/bcm2835-power.c
index 20130549a7a5..70eebda45716 100644
--- a/drivers/pmdomain/bcm/bcm2835-power.c
+++ b/drivers/pmdomain/bcm/bcm2835-power.c
@@ -80,6 +80,7 @@
 #define PM_IMAGE			0x108
 #define PM_GRAFX			0x10c
 #define PM_PROC				0x110
+#define PM_GRAFX_2712			0x304
 #define PM_ENAB				BIT(12)
 #define PM_ISPRSTN			BIT(8)
 #define PM_H264RSTN			BIT(7)
@@ -377,6 +378,9 @@ static int bcm2835_power_pd_power_on(struct generic_pm_domain *domain)
 		return bcm2835_power_power_on(pd, PM_GRAFX);
 
 	case BCM2835_POWER_DOMAIN_GRAFX_V3D:
+		if (!power->asb)
+			return bcm2835_asb_power_on(pd, PM_GRAFX_2712,
+						    0, 0, PM_V3DRSTN);
 		return bcm2835_asb_power_on(pd, PM_GRAFX,
 					    ASB_V3D_M_CTRL, ASB_V3D_S_CTRL,
 					    PM_V3DRSTN);
@@ -443,6 +447,9 @@ static int bcm2835_power_pd_power_off(struct generic_pm_domain *domain)
 		return bcm2835_power_power_off(pd, PM_GRAFX);
 
 	case BCM2835_POWER_DOMAIN_GRAFX_V3D:
+		if (!power->asb)
+			return bcm2835_asb_power_off(pd, PM_GRAFX_2712,
+						    0, 0, PM_V3DRSTN);
 		return bcm2835_asb_power_off(pd, PM_GRAFX,
 					     ASB_V3D_M_CTRL, ASB_V3D_S_CTRL,
 					     PM_V3DRSTN);
@@ -638,19 +645,21 @@ static int bcm2835_power_probe(struct platform_device *pdev)
 	power->asb = pm->asb;
 	power->rpivid_asb = pm->rpivid_asb;
 
-	id = readl(power->asb + ASB_AXI_BRDG_ID);
-	if (id != BCM2835_BRDG_ID /* "BRDG" */) {
-		dev_err(dev, "ASB register ID returned 0x%08x\n", id);
-		return -ENODEV;
-	}
-
-	if (power->rpivid_asb) {
-		id = readl(power->rpivid_asb + ASB_AXI_BRDG_ID);
+	if (power->asb) {
+		id = readl(power->asb + ASB_AXI_BRDG_ID);
 		if (id != BCM2835_BRDG_ID /* "BRDG" */) {
-			dev_err(dev, "RPiVid ASB register ID returned 0x%08x\n",
-				     id);
+			dev_err(dev, "ASB register ID returned 0x%08x\n", id);
 			return -ENODEV;
 		}
+
+		if (power->rpivid_asb) {
+			id = readl(power->rpivid_asb + ASB_AXI_BRDG_ID);
+			if (id != BCM2835_BRDG_ID /* "BRDG" */) {
+				dev_err(dev, "RPiVid ASB register ID returned 0x%08x\n",
+					id);
+				return -ENODEV;
+			}
+		}
 	}
 
 	power->pd_xlate.domains = devm_kcalloc(dev,
diff --git a/drivers/power/reset/gpio-poweroff.c b/drivers/power/reset/gpio-poweroff.c
index 52cfeee2cb28..770adf9b2dc9 100644
--- a/drivers/power/reset/gpio-poweroff.c
+++ b/drivers/power/reset/gpio-poweroff.c
@@ -44,7 +44,7 @@ static int gpio_poweroff_do_poweroff(struct sys_off_data *data)
 	/* give it some time */
 	mdelay(gpio_poweroff->timeout_ms);
 
-	WARN_ON(1);
+	//WARN_ON(1);
 
 	return NOTIFY_DONE;
 }
@@ -55,6 +55,7 @@ static int gpio_poweroff_probe(struct platform_device *pdev)
 	bool input = false;
 	enum gpiod_flags flags;
 	int priority = SYS_OFF_PRIO_DEFAULT;
+	bool export = false;
 	int ret;
 
 	gpio_poweroff = devm_kzalloc(&pdev->dev, sizeof(*gpio_poweroff), GFP_KERNEL);
@@ -86,10 +87,18 @@ static int gpio_poweroff_probe(struct platform_device *pdev)
 	if (IS_ERR(gpio_poweroff->reset_gpio))
 		return PTR_ERR(gpio_poweroff->reset_gpio);
 
+	export = device_property_read_bool(&pdev->dev, "export");
+	if (export) {
+		gpiod_export(gpio_poweroff->reset_gpio, true);
+		gpiod_export_link(&pdev->dev, "poweroff-gpio", gpio_poweroff->reset_gpio);
+	}
+
 	ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF,
 					    priority, gpio_poweroff_do_poweroff, gpio_poweroff);
-	if (ret)
+	if (ret) {
+		gpiod_unexport(gpio_poweroff->reset_gpio);
 		return dev_err_probe(&pdev->dev, ret, "Cannot register poweroff handler\n");
+	}
 
 	return 0;
 }
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index bcfa63fb9f1e..fc2fef63a5f3 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -28,6 +28,13 @@ config POWER_SUPPLY_HWMON
 	  Say 'Y' here if you want power supplies to
 	  have hwmon sysfs interface too.
 
+config RPI_POE_POWER
+	tristate "Raspberry Pi PoE+ HAT power supply driver"
+	depends on RASPBERRYPI_FIRMWARE
+	help
+	  Say Y here to enable support for Raspberry Pi PoE+ (Power over Ethernet
+	  Plus) HAT current measurement.
+
 config APM_POWER
 	tristate "APM emulation for class batteries"
 	depends on APM_EMULATION
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 8dcb41545317..be3bc820b122 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_POWER_SUPPLY)	+= power_supply.o
 obj-$(CONFIG_POWER_SUPPLY_HWMON) += power_supply_hwmon.o
 obj-$(CONFIG_GENERIC_ADC_BATTERY)	+= generic-adc-battery.o
 
+obj-$(CONFIG_RPI_POE_POWER)	+= rpi_poe_power.o
 obj-$(CONFIG_APM_POWER)		+= apm_power.o
 obj-$(CONFIG_AXP20X_POWER)	+= axp20x_usb_power.o
 obj-$(CONFIG_IP5XXX_POWER)	+= ip5xxx_power.o
diff --git a/drivers/power/supply/rpi_poe_power.c b/drivers/power/supply/rpi_poe_power.c
new file mode 100644
index 000000000000..e96f98c39f0e
--- /dev/null
+++ b/drivers/power/supply/rpi_poe_power.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * rpi-poe-power.c - Raspberry Pi PoE+ HAT power supply driver.
+ *
+ * Copyright (C) 2019 Raspberry Pi (Trading) Ltd.
+ * Based on axp20x_ac_power.c by Quentin Schulz <quentin.schulz@free-electrons.com>
+ *
+ * Author: Serge Schneider <serge@raspberrypi.org>
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
+#define RPI_POE_FW_BASE_REG		0x2
+
+#define RPI_POE_ADC_REG			0x0
+#define RPI_POE_FLAG_REG		0x2
+
+#define RPI_POE_FLAG_AT			BIT(0)
+#define RPI_POE_FLAG_OC			BIT(1)
+
+#define RPI_POE_CURRENT_AF_MAX	(2500 * 1000)
+#define RPI_POE_CURRENT_AT_MAX	(5000 * 1000)
+
+#define DRVNAME "rpi-poe-power-supply"
+
+struct rpi_poe_power_supply_ctx {
+	struct rpi_firmware *fw;
+
+	struct regmap *regmap;
+	u32 offset;
+
+	struct power_supply *supply;
+};
+
+struct fw_tag_data_s {
+	u32 reg;
+	u32 val;
+	u32 ret;
+};
+
+static int write_reg(struct rpi_poe_power_supply_ctx *ctx, u32 reg, u32 *val)
+{
+	struct fw_tag_data_s fw_tag_data = {
+		.reg = reg + RPI_POE_FW_BASE_REG,
+		.val = *val
+	};
+	int ret;
+
+	if (ctx->fw) {
+		ret = rpi_firmware_property(ctx->fw, RPI_FIRMWARE_SET_POE_HAT_VAL,
+					    &fw_tag_data, sizeof(fw_tag_data));
+		if (!ret && fw_tag_data.ret)
+			ret = -EIO;
+	} else {
+		ret = regmap_write(ctx->regmap, ctx->offset + reg, *val);
+	}
+
+	return ret;
+}
+
+static int read_reg(struct rpi_poe_power_supply_ctx *ctx, u32 reg, u32 *val)
+{
+	struct fw_tag_data_s fw_tag_data = {
+		.reg = reg + RPI_POE_FW_BASE_REG,
+		.val = *val
+	};
+	u32 value;
+	int ret;
+
+	if (ctx->fw) {
+		ret = rpi_firmware_property(ctx->fw, RPI_FIRMWARE_GET_POE_HAT_VAL,
+					    &fw_tag_data, sizeof(fw_tag_data));
+		if (!ret && fw_tag_data.ret)
+			ret = -EIO;
+		*val = fw_tag_data.val;
+	} else {
+		ret = regmap_read(ctx->regmap, ctx->offset + reg, &value);
+		if (!ret) {
+			*val = value;
+			ret = regmap_read(ctx->regmap, ctx->offset + reg + 1, &value);
+			*val |= value << 8;
+		}
+	}
+
+	return ret;
+}
+
+static int rpi_poe_power_supply_get_property(struct power_supply *psy,
+					enum power_supply_property psp,
+					union power_supply_propval *r_val)
+{
+	struct rpi_poe_power_supply_ctx *ctx = power_supply_get_drvdata(psy);
+	int ret;
+	unsigned int val = 0;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_HEALTH:
+		ret = read_reg(ctx, RPI_POE_FLAG_REG, &val);
+		if (ret)
+			return ret;
+
+		if (val & RPI_POE_FLAG_OC) {
+			r_val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+			val = RPI_POE_FLAG_OC;
+			ret = write_reg(ctx, RPI_POE_FLAG_REG, &val);
+			if (ret)
+				return ret;
+			return 0;
+		}
+
+		r_val->intval = POWER_SUPPLY_HEALTH_GOOD;
+		return 0;
+
+	case POWER_SUPPLY_PROP_ONLINE:
+		ret = read_reg(ctx, RPI_POE_ADC_REG, &val);
+		if (ret)
+			return ret;
+
+		r_val->intval = (val > 5);
+		return 0;
+
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		ret = read_reg(ctx, RPI_POE_ADC_REG, &val);
+		if (ret)
+			return ret;
+		val = (val * 3300)/9821;
+		r_val->intval = val * 1000;
+		return 0;
+
+	case POWER_SUPPLY_PROP_CURRENT_MAX:
+		ret = read_reg(ctx, RPI_POE_FLAG_REG, &val);
+		if (ret)
+			return ret;
+
+		if (val & RPI_POE_FLAG_AT)
+			r_val->intval = RPI_POE_CURRENT_AT_MAX;
+		else
+			r_val->intval = RPI_POE_CURRENT_AF_MAX;
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+
+	return -EINVAL;
+}
+
+static enum power_supply_property rpi_poe_power_supply_properties[] = {
+	POWER_SUPPLY_PROP_HEALTH,
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_CURRENT_NOW,
+	POWER_SUPPLY_PROP_CURRENT_MAX,
+};
+
+static const struct power_supply_desc rpi_poe_power_supply_desc = {
+	.name = "rpi-poe",
+	.type = POWER_SUPPLY_TYPE_MAINS,
+	.properties = rpi_poe_power_supply_properties,
+	.num_properties = ARRAY_SIZE(rpi_poe_power_supply_properties),
+	.get_property = rpi_poe_power_supply_get_property,
+};
+
+static int rpi_poe_power_supply_probe(struct platform_device *pdev)
+{
+	struct power_supply_config psy_cfg = {};
+	struct rpi_poe_power_supply_ctx *ctx;
+	struct device_node *fw_node;
+	u32 revision;
+
+	if (!of_device_is_available(pdev->dev.of_node))
+		return -ENODEV;
+
+	ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	if (pdev->dev.parent)
+		ctx->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+
+	if (ctx->regmap) {
+		if (device_property_read_u32(&pdev->dev, "reg", &ctx->offset))
+			return -EINVAL;
+	} else {
+		fw_node = of_parse_phandle(pdev->dev.of_node, "firmware", 0);
+		if (!fw_node) {
+			dev_err(&pdev->dev, "Missing firmware node\n");
+			return -ENOENT;
+		}
+
+		ctx->fw = rpi_firmware_get(fw_node);
+		if (!ctx->fw)
+			return -EPROBE_DEFER;
+		if (rpi_firmware_property(ctx->fw,
+					  RPI_FIRMWARE_GET_FIRMWARE_REVISION,
+					  &revision, sizeof(revision))) {
+			dev_err(&pdev->dev, "Failed to get firmware revision\n");
+			return -ENOENT;
+		}
+		if (revision < 0x60af72e8) {
+			dev_err(&pdev->dev, "Unsupported firmware\n");
+			return -ENOENT;
+		}
+	}
+
+	platform_set_drvdata(pdev, ctx);
+
+	psy_cfg.of_node = pdev->dev.of_node;
+	psy_cfg.drv_data = ctx;
+
+	ctx->supply = devm_power_supply_register(&pdev->dev,
+						   &rpi_poe_power_supply_desc,
+						   &psy_cfg);
+	if (IS_ERR(ctx->supply))
+		return PTR_ERR(ctx->supply);
+
+	return 0;
+}
+
+static const struct of_device_id of_rpi_poe_power_supply_match[] = {
+	{ .compatible = "raspberrypi,rpi-poe-power-supply", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, of_rpi_poe_power_supply_match);
+
+static struct platform_driver rpi_poe_power_supply_driver = {
+	.probe = rpi_poe_power_supply_probe,
+	.driver = {
+		.name = DRVNAME,
+		.of_match_table = of_rpi_poe_power_supply_match
+	},
+};
+
+module_platform_driver(rpi_poe_power_supply_driver);
+
+MODULE_AUTHOR("Serge Schneider <serge@raspberrypi.org>");
+MODULE_ALIAS("platform:" DRVNAME);
+MODULE_DESCRIPTION("Raspberry Pi PoE+ HAT power supply driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pps/clients/pps-gpio.c b/drivers/pps/clients/pps-gpio.c
index 1412f8af15f2..16870ea7ebdd 100644
--- a/drivers/pps/clients/pps-gpio.c
+++ b/drivers/pps/clients/pps-gpio.c
@@ -113,6 +113,9 @@ static int pps_gpio_setup(struct device *dev)
 	data->assert_falling_edge =
 		device_property_read_bool(dev, "assert-falling-edge");
 
+	data->capture_clear =
+		device_property_read_bool(dev, "capture-clear");
+
 	data->echo_pin = devm_gpiod_get_optional(dev, "echo", GPIOD_OUT_LOW);
 	if (IS_ERR(data->echo_pin))
 		return dev_err_probe(dev, PTR_ERR(data->echo_pin),
diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c
index c6b8b6478276..442d7e8f6fb9 100644
--- a/drivers/pps/pps.c
+++ b/drivers/pps/pps.c
@@ -254,12 +254,13 @@ static long pps_cdev_ioctl(struct file *file,
 static long pps_cdev_compat_ioctl(struct file *file,
 		unsigned int cmd, unsigned long arg)
 {
-	struct pps_device *pps = file->private_data;
-	void __user *uarg = (void __user *) arg;
 
 	cmd = _IOC(_IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), sizeof(void *));
 
+#ifdef CONFIG_X86_64
 	if (cmd == PPS_FETCH) {
+		struct pps_device *pps = file->private_data;
+		void __user *uarg = (void __user *) arg;
 		struct pps_fdata_compat compat;
 		struct pps_fdata fdata;
 		int err;
@@ -296,6 +297,7 @@ static long pps_cdev_compat_ioctl(struct file *file,
 		return copy_to_user(uarg, &compat,
 				sizeof(struct pps_fdata_compat)) ? -EFAULT : 0;
 	}
+#endif
 
 	return pps_cdev_ioctl(file, cmd, arg);
 }
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 0915c1e7df16..c337409828a8 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -491,6 +491,17 @@ config PWM_PCA9685
 	  To compile this driver as a module, choose M here: the module
 	  will be called pwm-pca9685.
 
+config PWM_PIO_RP1
+	tristate "RP1 PIO PWM support"
+	depends on FIRMWARE_RP1 || COMPILE_TEST
+	help
+	  This is a PWM framework driver for Raspberry Pi 5, using the PIO
+	  hardware of RP1 to provide PWM functionality. Supports up to 4
+	  instances on GPIOs in bank 0.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called pwm-pio-rp1.
+
 config PWM_PXA
 	tristate "PXA PWM support"
 	depends on ARCH_PXA || ARCH_MMP || COMPILE_TEST
@@ -510,6 +521,15 @@ config PWM_RASPBERRYPI_POE
 	  Enable Raspberry Pi firmware controller PWM bus used to control the
 	  official RPI PoE hat
 
+config PWM_RP1
+	tristate "RP1 PWM support"
+	depends on ARCH_BCM2835 || COMPILE_TEST
+	help
+	  PWM framework driver for Raspberry Pi RP1 controller
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called pwm-rp1.
+
 config PWM_RCAR
 	tristate "Renesas R-Car PWM support"
 	depends on ARCH_RENESAS || COMPILE_TEST
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 9081e0c0e9e0..11f59e12149f 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -44,8 +44,10 @@ obj-$(CONFIG_PWM_MXS)		+= pwm-mxs.o
 obj-$(CONFIG_PWM_NTXEC)		+= pwm-ntxec.o
 obj-$(CONFIG_PWM_OMAP_DMTIMER)	+= pwm-omap-dmtimer.o
 obj-$(CONFIG_PWM_PCA9685)	+= pwm-pca9685.o
+obj-$(CONFIG_PWM_PIO_RP1)	+= pwm-pio-rp1.o
 obj-$(CONFIG_PWM_PXA)		+= pwm-pxa.o
 obj-$(CONFIG_PWM_RASPBERRYPI_POE)	+= pwm-raspberrypi-poe.o
+obj-$(CONFIG_PWM_RP1)		+= pwm-rp1.o
 obj-$(CONFIG_PWM_RCAR)		+= pwm-rcar.o
 obj-$(CONFIG_PWM_RENESAS_TPU)	+= pwm-renesas-tpu.o
 obj-$(CONFIG_PWM_ROCKCHIP)	+= pwm-rockchip.o
diff --git a/drivers/pwm/pwm-pio-rp1.c b/drivers/pwm/pwm-pio-rp1.c
new file mode 100644
index 000000000000..f2dec72cb7e6
--- /dev/null
+++ b/drivers/pwm/pwm-pio-rp1.c
@@ -0,0 +1,260 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Raspberry Pi PIO PWM.
+ *
+ * Copyright (C) 2024 Raspberry Pi Ltd.
+ *
+ * Author: Phil Elwell (phil@raspberrypi.com)
+ *
+ * Based on the pwm-rp1 driver by:
+ *   Naushir Patuck <naush@raspberrypi.com>
+ * and on the pwm-gpio driver by:
+ *   Vincent Whitchurch <vincent.whitchurch@axis.com>
+ */
+
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/pio_rp1.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+
+struct pwm_pio_rp1 {
+	struct device *dev;
+	struct gpio_desc *gpiod;
+	struct mutex mutex;
+	PIO pio;
+	uint sm;
+	uint offset;
+	uint gpio;
+	uint32_t period;		/* In SM cycles */
+	uint32_t duty_cycle;		/* In SM cycles */
+	enum pwm_polarity polarity;
+	bool enabled;
+};
+
+/* Generated from pwm.pio by pioasm */
+#define pwm_wrap_target 0
+#define pwm_wrap 6
+#define pwm_loop_ticks 3
+
+static const uint16_t pwm_program_instructions[] = {
+		//     .wrap_target
+	0x9080, //  0: pull   noblock         side 0
+	0xa027, //  1: mov    x, osr
+	0xa046, //  2: mov    y, isr
+	0x00a5, //  3: jmp    x != y, 5
+	0x1806, //  4: jmp    6               side 1
+	0xa042, //  5: nop
+	0x0083, //  6: jmp    y--, 3
+		//     .wrap
+};
+
+static const struct pio_program pwm_program = {
+	.instructions = pwm_program_instructions,
+	.length = 7,
+	.origin = -1,
+};
+
+static unsigned int pwm_pio_resolution __read_mostly;
+
+static inline pio_sm_config pwm_program_get_default_config(uint offset)
+{
+	pio_sm_config c = pio_get_default_sm_config();
+
+	sm_config_set_wrap(&c, offset + pwm_wrap_target, offset + pwm_wrap);
+	sm_config_set_sideset(&c, 2, true, false);
+	return c;
+}
+
+static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin)
+{
+	pio_gpio_init(pio, pin);
+
+	pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
+	pio_sm_config c = pwm_program_get_default_config(offset);
+
+	sm_config_set_sideset_pins(&c, pin);
+	pio_sm_init(pio, sm, offset, &c);
+}
+
+/* Write `period` to the input shift register - must be disabled */
+static void pio_pwm_set_period(PIO pio, uint sm, uint32_t period)
+{
+	pio_sm_put_blocking(pio, sm, period - 1);
+	pio_sm_exec(pio, sm, pio_encode_pull(false, false));
+	pio_sm_exec(pio, sm, pio_encode_out(pio_isr, 32));
+}
+
+/* Write `level` to TX FIFO. State machine will copy this into X. */
+static void pio_pwm_set_level(PIO pio, uint sm, uint32_t level)
+{
+	pio_sm_put_blocking(pio, sm, level);
+}
+
+static int pwm_pio_rp1_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+			  const struct pwm_state *state)
+{
+	struct pwm_pio_rp1 *ppwm = pwmchip_get_drvdata(chip);
+	uint32_t new_duty_cycle;
+	uint32_t new_period;
+
+	new_period = DIV_ROUND_CLOSEST(state->period, pwm_pio_resolution);
+	if (new_period < 2)
+		new_period = 2;
+	new_duty_cycle = DIV_ROUND_CLOSEST(state->duty_cycle, pwm_pio_resolution);
+	if (new_duty_cycle > new_period - 1)
+		new_duty_cycle = new_period - 1;
+
+	mutex_lock(&ppwm->mutex);
+
+	if ((ppwm->enabled && !state->enabled) || new_period != ppwm->period) {
+		pio_sm_set_enabled(ppwm->pio, ppwm->sm, false);
+		ppwm->enabled = false;
+	}
+
+	if (new_period != ppwm->period) {
+		pio_pwm_set_period(ppwm->pio, ppwm->sm, new_period);
+		ppwm->period = new_period;
+	}
+
+	if (state->enabled && new_duty_cycle != ppwm->duty_cycle) {
+		pio_pwm_set_level(ppwm->pio, ppwm->sm, new_duty_cycle);
+		ppwm->duty_cycle = new_duty_cycle;
+	}
+
+	if (state->polarity != ppwm->polarity) {
+		pio_gpio_set_outover(ppwm->pio, ppwm->gpio,
+			(state->polarity == PWM_POLARITY_INVERSED) ?
+			GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
+		ppwm->polarity = state->polarity;
+	}
+
+	if (!ppwm->enabled && state->enabled) {
+		pio_sm_set_enabled(ppwm->pio, ppwm->sm, true);
+		ppwm->enabled = true;
+	}
+
+	mutex_unlock(&ppwm->mutex);
+
+	return 0;
+}
+
+static int pwm_pio_rp1_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+				 struct pwm_state *state)
+{
+	struct pwm_pio_rp1 *ppwm = pwmchip_get_drvdata(chip);
+
+	state->enabled = ppwm->enabled;
+	state->polarity = ppwm->polarity;
+	state->period = ppwm->period * pwm_pio_resolution;
+	state->duty_cycle = ppwm->duty_cycle * pwm_pio_resolution;
+
+	return 0;
+}
+
+static const struct pwm_ops pwm_pio_rp1_ops = {
+	.apply = pwm_pio_rp1_apply,
+	.get_state = pwm_pio_rp1_get_state,
+};
+
+static int pwm_pio_rp1_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct of_phandle_args of_args = { 0 };
+	struct device *dev = &pdev->dev;
+	struct pwm_pio_rp1 *ppwm;
+	struct pwm_chip *chip;
+	bool is_rp1;
+
+	chip = devm_pwmchip_alloc(dev, 1, sizeof(*ppwm));
+	if (IS_ERR(chip))
+		return PTR_ERR(chip);
+
+	ppwm = pwmchip_get_drvdata(chip);
+
+	mutex_init(&ppwm->mutex);
+
+	ppwm->gpiod = devm_gpiod_get(dev, NULL, GPIOD_ASIS);
+	/* Need to check that this is an RP1 GPIO in the first bank, and retrieve the offset */
+	/* Unfortunately I think this has to be done by parsing the gpios property */
+	if (IS_ERR(ppwm->gpiod))
+		return dev_err_probe(dev, PTR_ERR(ppwm->gpiod),
+				     "could not get a gpio\n");
+
+	/* This really shouldn't fail, given that we have a gpiod */
+	if (of_parse_phandle_with_args(np, "gpios", "#gpio-cells", 0, &of_args))
+		return dev_err_probe(dev, -EINVAL,
+				     "can't find gpio declaration\n");
+
+	is_rp1 = of_device_is_compatible(of_args.np, "raspberrypi,rp1-gpio");
+	of_node_put(of_args.np);
+	if (!is_rp1 || of_args.args_count != 2)
+		return dev_err_probe(dev, -EINVAL,
+				     "not an RP1 gpio\n");
+
+	ppwm->gpio = of_args.args[0];
+
+	ppwm->pio = pio_open();
+	if (IS_ERR(ppwm->pio))
+		return dev_err_probe(dev, PTR_ERR(ppwm->pio),
+				     "%pfw: could not open PIO\n",
+				     dev_fwnode(dev));
+
+	ppwm->sm = pio_claim_unused_sm(ppwm->pio, false);
+	if ((int)ppwm->sm < 0) {
+		pio_close(ppwm->pio);
+		return dev_err_probe(dev, -EBUSY,
+				     "%pfw: no free PIO SM\n",
+				     dev_fwnode(dev));
+	}
+
+	ppwm->offset = pio_add_program(ppwm->pio, &pwm_program);
+	if (ppwm->offset == PIO_ORIGIN_ANY) {
+		pio_close(ppwm->pio);
+		return dev_err_probe(dev, -EBUSY,
+				     "%pfw: not enough PIO program space\n",
+				     dev_fwnode(dev));
+	}
+
+	pwm_program_init(ppwm->pio, ppwm->sm, ppwm->offset, ppwm->gpio);
+
+	pwm_pio_resolution = (1000u * 1000 * 1000 * pwm_loop_ticks) / clock_get_hz(clk_sys);
+
+	chip->ops = &pwm_pio_rp1_ops;
+	chip->atomic = true;
+	chip->npwm = 1;
+
+	platform_set_drvdata(pdev, ppwm);
+
+	return devm_pwmchip_add(dev, chip);
+}
+
+static void pwm_pio_rp1_remove(struct platform_device *pdev)
+{
+	struct pwm_pio_rp1 *ppwm = platform_get_drvdata(pdev);
+
+	pio_close(ppwm->pio);
+}
+
+static const struct of_device_id pwm_pio_rp1_dt_ids[] = {
+	{ .compatible = "raspberrypi,pwm-pio-rp1" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, pwm_pio_rp1_dt_ids);
+
+static struct platform_driver pwm_pio_rp1_driver = {
+	.driver = {
+		.name = "pwm-pio-rp1",
+		.of_match_table = pwm_pio_rp1_dt_ids,
+	},
+	.probe = pwm_pio_rp1_probe,
+	.remove_new = pwm_pio_rp1_remove,
+};
+module_platform_driver(pwm_pio_rp1_driver);
+
+MODULE_DESCRIPTION("PWM PIO RP1 driver");
+MODULE_AUTHOR("Phil Elwell");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pwm/pwm-raspberrypi-poe.c b/drivers/pwm/pwm-raspberrypi-poe.c
index 8921e7ea2cea..19ff0726f78e 100644
--- a/drivers/pwm/pwm-raspberrypi-poe.c
+++ b/drivers/pwm/pwm-raspberrypi-poe.c
@@ -16,6 +16,7 @@
 #include <linux/of.h>
 #include <linux/platform_device.h>
 #include <linux/pwm.h>
+#include <linux/regmap.h>
 
 #include <soc/bcm2835/raspberrypi-firmware.h>
 #include <dt-bindings/pwm/raspberrypi,firmware-poe-pwm.h>
@@ -27,6 +28,10 @@
 
 struct raspberrypi_pwm {
 	struct rpi_firmware *firmware;
+
+	struct regmap *regmap;
+	u32 offset;
+
 	unsigned int duty_cycle;
 };
 
@@ -42,7 +47,7 @@ struct raspberrypi_pwm *raspberrypi_pwm_from_chip(struct pwm_chip *chip)
 	return pwmchip_get_drvdata(chip);
 }
 
-static int raspberrypi_pwm_set_property(struct rpi_firmware *firmware,
+static int raspberrypi_pwm_set_property(struct raspberrypi_pwm *pwm,
 					u32 reg, u32 val)
 {
 	struct raspberrypi_pwm_prop msg = {
@@ -51,17 +56,19 @@ static int raspberrypi_pwm_set_property(struct rpi_firmware *firmware,
 	};
 	int ret;
 
-	ret = rpi_firmware_property(firmware, RPI_FIRMWARE_SET_POE_HAT_VAL,
-				    &msg, sizeof(msg));
-	if (ret)
-		return ret;
-	if (msg.ret)
-		return -EIO;
+	if (pwm->firmware) {
+		ret = rpi_firmware_property(pwm->firmware, RPI_FIRMWARE_SET_POE_HAT_VAL,
+					    &msg, sizeof(msg));
+		if (!ret && msg.ret)
+			ret = -EIO;
+	} else {
+		ret = regmap_write(pwm->regmap, pwm->offset + reg, val);
+	}
 
-	return 0;
+	return ret;
 }
 
-static int raspberrypi_pwm_get_property(struct rpi_firmware *firmware,
+static int raspberrypi_pwm_get_property(struct raspberrypi_pwm *pwm,
 					u32 reg, u32 *val)
 {
 	struct raspberrypi_pwm_prop msg = {
@@ -69,16 +76,17 @@ static int raspberrypi_pwm_get_property(struct rpi_firmware *firmware,
 	};
 	int ret;
 
-	ret = rpi_firmware_property(firmware, RPI_FIRMWARE_GET_POE_HAT_VAL,
-				    &msg, sizeof(msg));
-	if (ret)
-		return ret;
-	if (msg.ret)
-		return -EIO;
-
-	*val = le32_to_cpu(msg.val);
+	if (pwm->firmware) {
+		ret = rpi_firmware_property(pwm->firmware, RPI_FIRMWARE_GET_POE_HAT_VAL,
+					    &msg, sizeof(msg));
+		if (!ret && msg.ret)
+			ret = -EIO;
+		*val = le32_to_cpu(msg.val);
+	} else {
+		ret = regmap_read(pwm->regmap, pwm->offset + reg, val);
+	}
 
-	return 0;
+	return ret;
 }
 
 static int raspberrypi_pwm_get_state(struct pwm_chip *chip,
@@ -118,7 +126,7 @@ static int raspberrypi_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 	if (duty_cycle == rpipwm->duty_cycle)
 		return 0;
 
-	ret = raspberrypi_pwm_set_property(rpipwm->firmware, RPI_PWM_CUR_DUTY_REG,
+	ret = raspberrypi_pwm_set_property(rpipwm, RPI_PWM_CUR_DUTY_REG,
 					   duty_cycle);
 	if (ret) {
 		dev_err(pwmchip_parent(chip), "Failed to set duty cycle: %pe\n",
@@ -145,28 +153,41 @@ static int raspberrypi_pwm_probe(struct platform_device *pdev)
 	struct raspberrypi_pwm *rpipwm;
 	int ret;
 
-	firmware_node = of_get_parent(dev->of_node);
-	if (!firmware_node) {
-		dev_err(dev, "Missing firmware node\n");
-		return -ENOENT;
-	}
-
-	firmware = devm_rpi_firmware_get(&pdev->dev, firmware_node);
-	of_node_put(firmware_node);
-	if (!firmware)
-		return dev_err_probe(dev, -EPROBE_DEFER,
-				     "Failed to get firmware handle\n");
-
 	chip = devm_pwmchip_alloc(&pdev->dev, RASPBERRYPI_FIRMWARE_PWM_NUM,
 				  sizeof(*rpipwm));
 	if (IS_ERR(chip))
 		return PTR_ERR(chip);
 	rpipwm = raspberrypi_pwm_from_chip(chip);
 
-	rpipwm->firmware = firmware;
+	if (!rpipwm)
+		return -ENOMEM;
+
+	if (pdev->dev.parent)
+		rpipwm->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+
+	if (rpipwm->regmap) {
+		ret = device_property_read_u32(&pdev->dev, "reg", &rpipwm->offset);
+		if (ret)
+			return -EINVAL;
+	} else {
+		firmware_node = of_get_parent(dev->of_node);
+		if (!firmware_node) {
+			dev_err(dev, "Missing firmware node\n");
+			return -ENOENT;
+		}
+
+		firmware = devm_rpi_firmware_get(&pdev->dev, firmware_node);
+		of_node_put(firmware_node);
+		if (!firmware)
+			return dev_err_probe(dev, -EPROBE_DEFER,
+					     "Failed to get firmware handle\n");
+
+		rpipwm->firmware = firmware;
+	}
+
 	chip->ops = &raspberrypi_pwm_ops;
 
-	ret = raspberrypi_pwm_get_property(rpipwm->firmware, RPI_PWM_CUR_DUTY_REG,
+	ret = raspberrypi_pwm_get_property(rpipwm, RPI_PWM_CUR_DUTY_REG,
 					   &rpipwm->duty_cycle);
 	if (ret) {
 		dev_err(dev, "Failed to get duty cycle: %pe\n", ERR_PTR(ret));
@@ -178,6 +199,7 @@ static int raspberrypi_pwm_probe(struct platform_device *pdev)
 
 static const struct of_device_id raspberrypi_pwm_of_match[] = {
 	{ .compatible = "raspberrypi,firmware-poe-pwm", },
+	{ .compatible = "raspberrypi,poe-pwm", },
 	{ }
 };
 MODULE_DEVICE_TABLE(of, raspberrypi_pwm_of_match);
diff --git a/drivers/pwm/pwm-rp1.c b/drivers/pwm/pwm-rp1.c
new file mode 100644
index 000000000000..fcf20ded80cf
--- /dev/null
+++ b/drivers/pwm/pwm-rp1.c
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * pwm-rp1.c
+ *
+ * Raspberry Pi RP1 PWM.
+ *
+ * Copyright © 2023 Raspberry Pi Ltd.
+ *
+ * Author: Naushir Patuck (naush@raspberrypi.com)
+ *
+ * Based on the pwm-bcm2835 driver by:
+ * Bart Tanghe <bart.tanghe@thomasmore.be>
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+
+#define PWM_GLOBAL_CTRL		0x000
+#define PWM_CHANNEL_CTRL(x)	(0x014 + ((x) * 16))
+#define PWM_RANGE(x)		(0x018 + ((x) * 16))
+#define PWM_DUTY(x)		(0x020 + ((x) * 16))
+
+/* 8:FIFO_POP_MASK + 0:Trailing edge M/S modulation */
+#define PWM_CHANNEL_DEFAULT	(BIT(8) + BIT(0))
+#define PWM_CHANNEL_ENABLE(x)	BIT(x)
+#define PWM_POLARITY		BIT(3)
+#define SET_UPDATE		BIT(31)
+#define PWM_MODE_MASK		GENMASK(1, 0)
+
+struct rp1_pwm {
+	void __iomem *base;
+	struct clk *clk;
+};
+
+static inline struct rp1_pwm *to_rp1_pwm(struct pwm_chip *chip)
+{
+	return pwmchip_get_drvdata(chip);
+}
+
+static void rp1_pwm_apply_config(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct rp1_pwm *pc = to_rp1_pwm(chip);
+	u32 value;
+
+	value = readl(pc->base + PWM_GLOBAL_CTRL);
+	value |= SET_UPDATE;
+	writel(value, pc->base + PWM_GLOBAL_CTRL);
+}
+
+static int rp1_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct rp1_pwm *pc = to_rp1_pwm(chip);
+
+	writel(PWM_CHANNEL_DEFAULT, pc->base + PWM_CHANNEL_CTRL(pwm->hwpwm));
+	return 0;
+}
+
+static void rp1_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct rp1_pwm *pc = to_rp1_pwm(chip);
+	u32 value;
+
+	value = readl(pc->base + PWM_CHANNEL_CTRL(pwm->hwpwm));
+	value &= ~PWM_MODE_MASK;
+	writel(value, pc->base + PWM_CHANNEL_CTRL(pwm->hwpwm));
+	rp1_pwm_apply_config(chip, pwm);
+}
+
+static int rp1_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+			 const struct pwm_state *state)
+{
+	struct rp1_pwm *pc = to_rp1_pwm(chip);
+	unsigned long clk_rate = clk_get_rate(pc->clk);
+	unsigned long clk_period;
+	u32 value;
+
+	if (!clk_rate) {
+		dev_err(&chip->dev, "failed to get clock rate\n");
+		return -EINVAL;
+	}
+
+	/* set period */
+	clk_period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, clk_rate);
+
+	writel(DIV_ROUND_CLOSEST(state->duty_cycle, clk_period),
+	       pc->base + PWM_DUTY(pwm->hwpwm));
+
+	/* set duty cycle */
+	writel(DIV_ROUND_CLOSEST(state->period, clk_period),
+	       pc->base + PWM_RANGE(pwm->hwpwm));
+
+	/* set polarity */
+	value = readl(pc->base + PWM_CHANNEL_CTRL(pwm->hwpwm));
+	if (state->polarity == PWM_POLARITY_NORMAL)
+		value &= ~PWM_POLARITY;
+	else
+		value |= PWM_POLARITY;
+	writel(value, pc->base + PWM_CHANNEL_CTRL(pwm->hwpwm));
+
+	/* enable/disable */
+	value = readl(pc->base + PWM_GLOBAL_CTRL);
+	if (state->enabled)
+		value |= PWM_CHANNEL_ENABLE(pwm->hwpwm);
+	else
+		value &= ~PWM_CHANNEL_ENABLE(pwm->hwpwm);
+	writel(value, pc->base + PWM_GLOBAL_CTRL);
+
+	rp1_pwm_apply_config(chip, pwm);
+
+	return 0;
+}
+
+static const struct pwm_ops rp1_pwm_ops = {
+	.request = rp1_pwm_request,
+	.free = rp1_pwm_free,
+	.apply = rp1_pwm_apply,
+};
+
+static int rp1_pwm_probe(struct platform_device *pdev)
+{
+	struct pwm_chip *chip;
+	struct rp1_pwm *pc;
+	int ret;
+
+	chip = devm_pwmchip_alloc(&pdev->dev, 4, sizeof(*pc));
+
+	if (IS_ERR(chip))
+		return PTR_ERR(chip);
+
+	pc = to_rp1_pwm(chip);
+
+	pc->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(pc->base))
+		return PTR_ERR(pc->base);
+
+	pc->clk = devm_clk_get_enabled(&pdev->dev, NULL);
+	if (IS_ERR(pc->clk))
+		return dev_err_probe(&pdev->dev, PTR_ERR(pc->clk),
+				     "clock not found\n");
+
+	chip->ops = &rp1_pwm_ops;
+	chip->of_xlate = of_pwm_xlate_with_flags;
+
+	ret = devm_pwmchip_add(&pdev->dev, chip);
+	if (ret < 0)
+		goto add_fail;
+
+	return 0;
+
+add_fail:
+	clk_disable_unprepare(pc->clk);
+	return ret;
+}
+
+static void rp1_pwm_remove(struct platform_device *pdev)
+{
+	struct rp1_pwm *pc = platform_get_drvdata(pdev);
+
+	clk_disable_unprepare(pc->clk);
+}
+
+static const struct of_device_id rp1_pwm_of_match[] = {
+	{ .compatible = "raspberrypi,rp1-pwm" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rp1_pwm_of_match);
+
+static struct platform_driver rp1_pwm_driver = {
+	.driver = {
+		.name = "rpi-pwm",
+		.of_match_table = rp1_pwm_of_match,
+	},
+	.probe = rp1_pwm_probe,
+	.remove = rp1_pwm_remove,
+};
+module_platform_driver(rp1_pwm_driver);
+
+MODULE_AUTHOR("Naushir Patuck <naush@raspberrypi.com");
+MODULE_DESCRIPTION("RP1 PWM driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 39297f7d8177..57aeec4aad26 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -1142,6 +1142,26 @@ config REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY
 	  touchscreen unit. The regulator is used to enable power to the
 	  TC358762, display and to control backlight.
 
+config REGULATOR_RASPBERRYPI_TOUCHSCREEN_V2
+	tristate "Raspberry Pi 7-inch touchscreen panel V2 regulator"
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on I2C
+	select REGMAP_I2C
+	help
+	  This driver supports regulator on the V2 Raspberry Pi
+	  touchscreen unit. The regulator is used to enable power to the
+	  display and to control backlight.
+
+config REGULATOR_WAVESHARE_TOUCHSCREEN
+	tristate "Waveshare touchscreen panel regulator"
+	depends on BACKLIGHT_CLASS_DEVICE
+	depends on I2C
+	select REGMAP_I2C
+	help
+	  This driver supports regulator on the waveshare
+	  touchscreen unit. The regulator is used to enable power to the
+	  display and to control backlight.
+
 config REGULATOR_RC5T583
 	tristate "RICOH RC5T583 Power regulators"
 	depends on MFD_RC5T583
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 3d5a803dce8a..09bdfb19d868 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -135,6 +135,8 @@ obj-$(CONFIG_REGULATOR_PCAP) += pcap-regulator.o
 obj-$(CONFIG_REGULATOR_PCF50633) += pcf50633-regulator.o
 obj-$(CONFIG_REGULATOR_RAA215300) += raa215300.o
 obj-$(CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_ATTINY)  += rpi-panel-attiny-regulator.o
+obj-$(CONFIG_REGULATOR_RASPBERRYPI_TOUCHSCREEN_V2)  += rpi-panel-v2-regulator.o
+obj-$(CONFIG_REGULATOR_WAVESHARE_TOUCHSCREEN)  += waveshare-panel-regulator.o
 obj-$(CONFIG_REGULATOR_RC5T583)  += rc5t583-regulator.o
 obj-$(CONFIG_REGULATOR_RK808)   += rk808-regulator.o
 obj-$(CONFIG_REGULATOR_RN5T618) += rn5t618-regulator.o
diff --git a/drivers/regulator/rpi-panel-attiny-regulator.c b/drivers/regulator/rpi-panel-attiny-regulator.c
index 6c3b6bfac961..a1ac963dfe5d 100644
--- a/drivers/regulator/rpi-panel-attiny-regulator.c
+++ b/drivers/regulator/rpi-panel-attiny-regulator.c
@@ -143,24 +143,8 @@ static int attiny_lcd_power_disable(struct regulator_dev *rdev)
 static int attiny_lcd_power_is_enabled(struct regulator_dev *rdev)
 {
 	struct attiny_lcd *state = rdev_get_drvdata(rdev);
-	unsigned int data;
-	int ret, i;
-
-	mutex_lock(&state->lock);
 
-	for (i = 0; i < 10; i++) {
-		ret = regmap_read(rdev->regmap, REG_PORTC, &data);
-		if (!ret)
-			break;
-		usleep_range(10000, 12000);
-	}
-
-	mutex_unlock(&state->lock);
-
-	if (ret < 0)
-		return ret;
-
-	return data & PC_RST_BRIDGE_N;
+	return state->port_states[REG_PORTC - REG_PORTA] & PC_RST_BRIDGE_N;
 }
 
 static const struct regulator_init_data attiny_regulator_default = {
@@ -245,39 +229,6 @@ static void attiny_gpio_set(struct gpio_chip *gc, unsigned int off, int val)
 	mutex_unlock(&state->lock);
 }
 
-static int attiny_i2c_read(struct i2c_client *client, u8 reg, unsigned int *buf)
-{
-	struct i2c_msg msgs[1];
-	u8 addr_buf[1] = { reg };
-	u8 data_buf[1] = { 0, };
-	int ret;
-
-	/* Write register address */
-	msgs[0].addr = client->addr;
-	msgs[0].flags = 0;
-	msgs[0].len = ARRAY_SIZE(addr_buf);
-	msgs[0].buf = addr_buf;
-
-	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
-	if (ret != ARRAY_SIZE(msgs))
-		return -EIO;
-
-	usleep_range(5000, 10000);
-
-	/* Read data from register */
-	msgs[0].addr = client->addr;
-	msgs[0].flags = I2C_M_RD;
-	msgs[0].len = 1;
-	msgs[0].buf = data_buf;
-
-	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
-	if (ret != ARRAY_SIZE(msgs))
-		return -EIO;
-
-	*buf = data_buf[0];
-	return 0;
-}
-
 /*
  * I2C driver interface functions
  */
@@ -289,7 +240,6 @@ static int attiny_i2c_probe(struct i2c_client *i2c)
 	struct regulator_dev *rdev;
 	struct attiny_lcd *state;
 	struct regmap *regmap;
-	unsigned int data;
 	int ret;
 
 	state = devm_kzalloc(&i2c->dev, sizeof(*state), GFP_KERNEL);
@@ -307,22 +257,6 @@ static int attiny_i2c_probe(struct i2c_client *i2c)
 		goto error;
 	}
 
-	ret = attiny_i2c_read(i2c, REG_ID, &data);
-	if (ret < 0) {
-		dev_err(&i2c->dev, "Failed to read REG_ID reg: %d\n", ret);
-		goto error;
-	}
-
-	switch (data) {
-	case 0xde: /* ver 1 */
-	case 0xc3: /* ver 2 */
-		break;
-	default:
-		dev_err(&i2c->dev, "Unknown Atmel firmware revision: 0x%02x\n", data);
-		ret = -ENODEV;
-		goto error;
-	}
-
 	regmap_write(regmap, REG_POWERON, 0);
 	msleep(30);
 	regmap_write(regmap, REG_PWM, 0);
@@ -386,6 +320,14 @@ static void attiny_i2c_remove(struct i2c_client *client)
 	mutex_destroy(&state->lock);
 }
 
+static void attiny_i2c_shutdown(struct i2c_client *client)
+{
+	struct attiny_lcd *state = i2c_get_clientdata(client);
+
+	regmap_write(state->regmap, REG_PWM, 0);
+	regmap_write(state->regmap, REG_POWERON, 0);
+}
+
 static const struct of_device_id attiny_dt_ids[] = {
 	{ .compatible = "raspberrypi,7inch-touchscreen-panel-regulator" },
 	{},
@@ -400,6 +342,7 @@ static struct i2c_driver attiny_regulator_driver = {
 	},
 	.probe = attiny_i2c_probe,
 	.remove	= attiny_i2c_remove,
+	.shutdown = attiny_i2c_shutdown,
 };
 
 module_i2c_driver(attiny_regulator_driver);
diff --git a/drivers/regulator/rpi-panel-v2-regulator.c b/drivers/regulator/rpi-panel-v2-regulator.c
new file mode 100644
index 000000000000..43db195fe3f0
--- /dev/null
+++ b/drivers/regulator/rpi-panel-v2-regulator.c
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022 Raspberry Pi Ltd.
+ *
+ * Based on rpi-panel-attiny-regulator.c by Marek Vasut <marex@denx.de>
+ */
+
+#include <linux/backlight.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+
+/* I2C registers of the microcontroller. */
+#define REG_ID		0x01
+#define REG_POWERON	0x02
+#define REG_PWM		0x03
+
+// bits for poweron register
+#define LCD_RESET_BIT	BIT(0)
+#define CTP_RESET_BIT	BIT(1)
+
+//bits for the PWM register
+#define PWM_BL_ENABLE	BIT(7)
+#define PWM_VALUE	GENMASK(4, 0)
+
+#define NUM_GPIO	2	/* Treat LCD_RESET and CTP_RESET as GPIOs */
+
+struct rpi_panel_v2_lcd {
+	/* lock to serialise overall accesses to the Atmel */
+	struct mutex	lock;
+	struct regmap	*regmap;
+	u8 poweron_state;
+
+	struct gpio_chip gc;
+};
+
+static const struct regmap_config rpi_panel_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = REG_PWM,
+};
+
+static int rpi_panel_v2_gpio_get_direction(struct gpio_chip *gc, unsigned int off)
+{
+	return GPIO_LINE_DIRECTION_OUT;
+}
+
+static void rpi_panel_v2_gpio_set(struct gpio_chip *gc, unsigned int off, int val)
+{
+	struct rpi_panel_v2_lcd *state = gpiochip_get_data(gc);
+	u8 last_val;
+
+	if (off >= NUM_GPIO)
+		return;
+
+	mutex_lock(&state->lock);
+
+	last_val = state->poweron_state;
+	if (val)
+		last_val |= (1 << off);
+	else
+		last_val &= ~(1 << off);
+
+	state->poweron_state = last_val;
+
+	regmap_write(state->regmap, REG_POWERON, last_val);
+
+	mutex_unlock(&state->lock);
+}
+
+static int rpi_panel_v2_update_status(struct backlight_device *bl)
+{
+	struct regmap *regmap = bl_get_data(bl);
+	int brightness = bl->props.brightness | PWM_BL_ENABLE;
+
+	if (bl->props.power != FB_BLANK_UNBLANK ||
+	    bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
+		brightness = 0;
+
+	return regmap_write(regmap, REG_PWM, brightness);
+}
+
+static const struct backlight_ops rpi_panel_v2_bl = {
+	.update_status	= rpi_panel_v2_update_status,
+};
+
+static int rpi_panel_v2_i2c_read(struct i2c_client *client, u8 reg, unsigned int *buf)
+{
+	struct i2c_msg msgs[1];
+	u8 addr_buf[1] = { reg };
+	u8 data_buf[1] = { 0, };
+	int ret;
+
+	/* Write register address */
+	msgs[0].addr = client->addr;
+	msgs[0].flags = 0;
+	msgs[0].len = ARRAY_SIZE(addr_buf);
+	msgs[0].buf = addr_buf;
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret != ARRAY_SIZE(msgs))
+		return -EIO;
+
+	usleep_range(5000, 10000);
+
+	/* Read data from register */
+	msgs[0].addr = client->addr;
+	msgs[0].flags = I2C_M_RD;
+	msgs[0].len = 1;
+	msgs[0].buf = data_buf;
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret != ARRAY_SIZE(msgs))
+		return -EIO;
+
+	*buf = data_buf[0];
+	return 0;
+}
+
+/*
+ * I2C driver interface functions
+ */
+static int rpi_panel_v2_i2c_probe(struct i2c_client *i2c)
+{
+	struct backlight_properties props = { };
+	struct backlight_device *bl;
+	struct rpi_panel_v2_lcd *state;
+	struct regmap *regmap;
+	unsigned int data;
+	int ret;
+
+	state = devm_kzalloc(&i2c->dev, sizeof(*state), GFP_KERNEL);
+	if (!state)
+		return -ENOMEM;
+
+	mutex_init(&state->lock);
+	i2c_set_clientdata(i2c, state);
+
+	regmap = devm_regmap_init_i2c(i2c, &rpi_panel_regmap_config);
+	if (IS_ERR(regmap)) {
+		ret = PTR_ERR(regmap);
+		dev_err(&i2c->dev, "Failed to allocate register map: %d\n",
+			ret);
+		goto error;
+	}
+
+	ret = rpi_panel_v2_i2c_read(i2c, REG_ID, &data);
+	if (ret < 0) {
+		dev_err(&i2c->dev, "Failed to read REG_ID reg: %d\n", ret);
+		goto error;
+	}
+
+	switch (data & 0x0f) {
+	case 0x01: /* 7 inch */
+	case 0x04: /* 7 inch - old */
+	case 0x08: /* 5 inch - old */
+	case 0x09: /* 5 inch */
+	case 0x0a: /* 10.1 inch */
+		break;
+	default:
+		dev_err(&i2c->dev, "Unknown revision: 0x%02x\n",
+			data & 0x0f);
+		ret = -ENODEV;
+		goto error;
+	}
+
+	regmap_write(regmap, REG_POWERON, 0);
+
+	state->regmap = regmap;
+	state->gc.parent = &i2c->dev;
+	state->gc.label = i2c->name;
+	state->gc.owner = THIS_MODULE;
+	state->gc.base = -1;
+	state->gc.ngpio = NUM_GPIO;
+
+	state->gc.set = rpi_panel_v2_gpio_set;
+	state->gc.get_direction = rpi_panel_v2_gpio_get_direction;
+	state->gc.can_sleep = true;
+
+	ret = devm_gpiochip_add_data(&i2c->dev, &state->gc, state);
+	if (ret) {
+		dev_err(&i2c->dev, "Failed to create gpiochip: %d\n", ret);
+		goto error;
+	}
+
+	props.type = BACKLIGHT_RAW;
+	props.max_brightness = PWM_VALUE;
+	bl = devm_backlight_device_register(&i2c->dev, dev_name(&i2c->dev),
+					    &i2c->dev, regmap, &rpi_panel_v2_bl,
+					    &props);
+	if (IS_ERR(bl))
+		return PTR_ERR(bl);
+
+	bl->props.brightness = PWM_VALUE;
+
+	return 0;
+
+error:
+	mutex_destroy(&state->lock);
+	return ret;
+}
+
+static void rpi_panel_v2_i2c_remove(struct i2c_client *client)
+{
+	struct rpi_panel_v2_lcd *state = i2c_get_clientdata(client);
+
+	mutex_destroy(&state->lock);
+}
+
+static void rpi_panel_v2_i2c_shutdown(struct i2c_client *client)
+{
+	struct rpi_panel_v2_lcd *state = i2c_get_clientdata(client);
+
+	regmap_write(state->regmap, REG_PWM, 0);
+	regmap_write(state->regmap, REG_POWERON, 0);
+}
+
+static const struct of_device_id rpi_panel_v2_dt_ids[] = {
+	{ .compatible = "raspberrypi,v2-touchscreen-panel-regulator" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, rpi_panel_v2_dt_ids);
+
+static struct i2c_driver rpi_panel_v2_regulator_driver = {
+	.driver = {
+		.name = "rpi_touchscreen_v2",
+		.of_match_table = of_match_ptr(rpi_panel_v2_dt_ids),
+	},
+	.probe = rpi_panel_v2_i2c_probe,
+	.remove	= rpi_panel_v2_i2c_remove,
+	.shutdown = rpi_panel_v2_i2c_shutdown,
+};
+
+module_i2c_driver(rpi_panel_v2_regulator_driver);
+
+MODULE_AUTHOR("Dave Stevenson <dave.stevenson@raspberrypi.com>");
+MODULE_DESCRIPTION("Regulator device driver for Raspberry Pi 7-inch V2 touchscreen");
+MODULE_LICENSE("GPL");
diff --git a/drivers/regulator/waveshare-panel-regulator.c b/drivers/regulator/waveshare-panel-regulator.c
new file mode 100644
index 000000000000..8b00b699b610
--- /dev/null
+++ b/drivers/regulator/waveshare-panel-regulator.c
@@ -0,0 +1,326 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 Waveshare International Limited
+ *
+ * Based on rpi-panel-v2-regulator.c by Dave Stevenson <dave.stevenson@raspberrypi.com>
+ */
+
+#include <linux/backlight.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/gpio/driver.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/of.h>
+
+/* I2C registers of the microcontroller. */
+#define REG_TP 0x94
+#define REG_LCD 0x95
+#define REG_PWM 0x96
+#define REG_SIZE 0x97
+#define REG_ID 0x98
+#define REG_VERSION 0x99
+
+#define NUM_GPIO 16 /* Treat BL_ENABLE, LCD_RESET, TP_RESET as GPIOs */
+
+struct waveshare_panel_lcd {
+	struct mutex dir_lock;
+	struct mutex pwr_lock;
+	struct regmap *regmap;
+	u16 poweron_state;
+	u16 direction_state;
+
+	struct gpio_chip gc;
+	struct gpio_desc *enable;
+};
+
+static const struct regmap_config waveshare_panel_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = REG_PWM,
+};
+
+static int waveshare_panel_gpio_direction_in(struct gpio_chip *gc,
+					     unsigned int offset)
+{
+	struct waveshare_panel_lcd *state = gpiochip_get_data(gc);
+
+	mutex_lock(&state->dir_lock);
+	state->direction_state |= BIT(offset);
+	mutex_unlock(&state->dir_lock);
+
+	return 0;
+}
+
+static int waveshare_panel_gpio_direction_out(struct gpio_chip *gc,
+					      unsigned int offset, int val)
+{
+	struct waveshare_panel_lcd *state = gpiochip_get_data(gc);
+	u16 last_val;
+
+	mutex_lock(&state->dir_lock);
+	state->direction_state &= ~BIT(offset);
+	mutex_unlock(&state->dir_lock);
+
+	mutex_lock(&state->pwr_lock);
+	last_val = state->poweron_state;
+	if (val)
+		last_val |= BIT(offset);
+	else
+		last_val &= ~BIT(offset);
+
+	state->poweron_state = last_val;
+	mutex_unlock(&state->pwr_lock);
+
+	regmap_write(state->regmap, REG_TP, last_val >> 8);
+	regmap_write(state->regmap, REG_LCD, last_val & 0xff);
+
+	return 0;
+}
+
+static int waveshare_panel_gpio_get_direction(struct gpio_chip *gc,
+					      unsigned int offset)
+{
+	struct waveshare_panel_lcd *state = gpiochip_get_data(gc);
+	u16 last_val;
+
+	mutex_lock(&state->dir_lock);
+	last_val = state->direction_state;
+	mutex_unlock(&state->dir_lock);
+
+	if (last_val & BIT(offset))
+		return GPIO_LINE_DIRECTION_IN;
+	else
+		return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int waveshare_panel_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+	struct waveshare_panel_lcd *state = gpiochip_get_data(gc);
+	u16 pwr_state;
+
+	mutex_lock(&state->pwr_lock);
+	pwr_state = state->poweron_state & BIT(offset);
+	mutex_unlock(&state->pwr_lock);
+
+	if (pwr_state)
+		return 1;
+	else
+		return 0;
+}
+
+static void waveshare_panel_gpio_set(struct gpio_chip *gc, unsigned int offset,
+				     int value)
+{
+	struct waveshare_panel_lcd *state = gpiochip_get_data(gc);
+	u16 last_val;
+
+	if (offset >= NUM_GPIO)
+		return;
+
+	mutex_lock(&state->pwr_lock);
+
+	last_val = state->poweron_state;
+	if (value)
+		last_val |= BIT(offset);
+	else
+		last_val &= ~BIT(offset);
+
+	state->poweron_state = last_val;
+
+	regmap_write(state->regmap, REG_TP, last_val >> 8);
+	regmap_write(state->regmap, REG_LCD, last_val & 0xff);
+
+	mutex_unlock(&state->pwr_lock);
+}
+
+static int waveshare_panel_update_status(struct backlight_device *bl)
+{
+	struct waveshare_panel_lcd *state = bl_get_data(bl);
+	int brightness = bl->props.brightness;
+
+	if (bl->props.power != FB_BLANK_UNBLANK ||
+	    bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
+		brightness = 0;
+
+	if (state->enable) {
+		if (brightness)
+			gpiod_set_value_cansleep(state->enable,
+						 1); // Enable BL_EN
+		else
+			gpiod_set_value_cansleep(state->enable,
+						 0); // Disable BL_EN
+	}
+
+	return regmap_write(state->regmap, REG_PWM, brightness);
+}
+
+static const struct backlight_ops waveshare_panel_bl = {
+	.update_status = waveshare_panel_update_status,
+};
+
+static int waveshare_panel_i2c_read(struct i2c_client *client, u8 reg,
+				    unsigned int *buf)
+{
+	struct i2c_msg msgs[1];
+	u8 addr_buf[1] = { reg };
+	u8 data_buf[1] = {
+		0,
+	};
+	int ret;
+
+	/* Write register address */
+	msgs[0].addr = client->addr;
+	msgs[0].flags = 0;
+	msgs[0].len = ARRAY_SIZE(addr_buf);
+	msgs[0].buf = addr_buf;
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret != ARRAY_SIZE(msgs))
+		return -EIO;
+
+	usleep_range(5000, 10000);
+
+	/* Read data from register */
+	msgs[0].addr = client->addr;
+	msgs[0].flags = I2C_M_RD;
+	msgs[0].len = 1;
+	msgs[0].buf = data_buf;
+
+	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
+	if (ret != ARRAY_SIZE(msgs))
+		return -EIO;
+
+	*buf = data_buf[0];
+	return 0;
+}
+
+/*
+ * I2C driver interface functions
+ */
+static int waveshare_panel_i2c_probe(struct i2c_client *i2c)
+{
+	struct backlight_properties props = {};
+	struct backlight_device *bl;
+	struct waveshare_panel_lcd *state;
+	struct regmap *regmap;
+	unsigned int data;
+	int ret;
+
+	state = devm_kzalloc(&i2c->dev, sizeof(*state), GFP_KERNEL);
+	if (!state)
+		return -ENOMEM;
+
+	mutex_init(&state->dir_lock);
+	mutex_init(&state->pwr_lock);
+
+	i2c_set_clientdata(i2c, state);
+
+	regmap = devm_regmap_init_i2c(i2c, &waveshare_panel_regmap_config);
+	if (IS_ERR(regmap)) {
+		ret = PTR_ERR(regmap);
+		dev_err(&i2c->dev, "Failed to allocate register map: %d\n",
+			ret);
+		goto error;
+	}
+
+	ret = waveshare_panel_i2c_read(i2c, REG_ID, &data);
+	if (ret == 0)
+		dev_info(&i2c->dev, "waveshare panel hw id = 0x%x\n", data);
+
+	ret = waveshare_panel_i2c_read(i2c, REG_SIZE, &data);
+	if (ret == 0)
+		dev_info(&i2c->dev, "waveshare panel size = %d\n", data);
+
+	ret = waveshare_panel_i2c_read(i2c, REG_VERSION, &data);
+	if (ret == 0)
+		dev_info(&i2c->dev, "waveshare panel mcu version = 0x%x\n",
+			 data);
+
+	state->direction_state = 0;
+	state->poweron_state = BIT(9) | BIT(8); // Enable VCC
+	regmap_write(regmap, REG_TP, state->poweron_state >> 8);
+	regmap_write(regmap, REG_LCD, state->poweron_state & 0xff);
+	msleep(20);
+
+	state->regmap = regmap;
+	state->gc.parent = &i2c->dev;
+	state->gc.label = i2c->name;
+	state->gc.owner = THIS_MODULE;
+	state->gc.base = -1;
+	state->gc.ngpio = NUM_GPIO;
+
+	state->gc.get = waveshare_panel_gpio_get;
+	state->gc.set = waveshare_panel_gpio_set;
+	state->gc.direction_input = waveshare_panel_gpio_direction_in;
+	state->gc.direction_output = waveshare_panel_gpio_direction_out;
+	state->gc.get_direction = waveshare_panel_gpio_get_direction;
+	state->gc.can_sleep = true;
+
+	ret = devm_gpiochip_add_data(&i2c->dev, &state->gc, state);
+	if (ret) {
+		dev_err(&i2c->dev, "Failed to create gpiochip: %d\n", ret);
+		goto error;
+	}
+
+	state->enable =
+		devm_gpiod_get_optional(&i2c->dev, "enable", GPIOD_OUT_LOW);
+	if (IS_ERR(state->enable))
+		return dev_err_probe(&i2c->dev, PTR_ERR(state->enable),
+				     "Couldn't get our enable GPIO\n");
+
+	props.type = BACKLIGHT_RAW;
+	props.max_brightness = 255;
+	bl = devm_backlight_device_register(&i2c->dev, dev_name(&i2c->dev),
+					    &i2c->dev, state,
+					    &waveshare_panel_bl, &props);
+	if (IS_ERR(bl))
+		return PTR_ERR(bl);
+
+	bl->props.brightness = 255;
+
+	return 0;
+
+error:
+	mutex_destroy(&state->dir_lock);
+	mutex_destroy(&state->pwr_lock);
+	return ret;
+}
+
+static void waveshare_panel_i2c_remove(struct i2c_client *client)
+{
+	struct waveshare_panel_lcd *state = i2c_get_clientdata(client);
+
+	mutex_destroy(&state->dir_lock);
+	mutex_destroy(&state->pwr_lock);
+}
+
+static void waveshare_panel_i2c_shutdown(struct i2c_client *client)
+{
+	struct waveshare_panel_lcd *state = i2c_get_clientdata(client);
+
+	regmap_write(state->regmap, REG_PWM, 0);
+}
+
+static const struct of_device_id waveshare_panel_dt_ids[] = {
+	{ .compatible = "waveshare,touchscreen-panel-regulator" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, waveshare_panel_dt_ids);
+
+static struct i2c_driver waveshare_panel_regulator_driver = {
+	.driver = {
+		.name = "waveshare_touchscreen",
+		.of_match_table = of_match_ptr(waveshare_panel_dt_ids),
+	},
+	.probe = waveshare_panel_i2c_probe,
+	.remove	= waveshare_panel_i2c_remove,
+	.shutdown = waveshare_panel_i2c_shutdown,
+};
+
+module_i2c_driver(waveshare_panel_regulator_driver);
+
+MODULE_AUTHOR("Waveshare Team <support@waveshare.com>");
+MODULE_DESCRIPTION("Regulator device driver for Waveshare touchscreen");
+MODULE_LICENSE("GPL");
diff --git a/drivers/reset/reset-brcmstb-rescal.c b/drivers/reset/reset-brcmstb-rescal.c
index 823317772bac..89c1cae675a0 100644
--- a/drivers/reset/reset-brcmstb-rescal.c
+++ b/drivers/reset/reset-brcmstb-rescal.c
@@ -20,6 +20,7 @@ struct brcm_rescal_reset {
 	struct reset_controller_dev rcdev;
 };
 
+/* Also doubles a deassert */
 static int brcm_rescal_reset_set(struct reset_controller_dev *rcdev,
 				 unsigned long id)
 {
@@ -52,6 +53,13 @@ static int brcm_rescal_reset_set(struct reset_controller_dev *rcdev,
 	return 0;
 }
 
+/* A dummy function - deassert/reset does all the work */
+static int brcm_rescal_reset_assert(struct reset_controller_dev *rcdev,
+				    unsigned long id)
+{
+	return 0;
+}
+
 static int brcm_rescal_reset_xlate(struct reset_controller_dev *rcdev,
 				   const struct of_phandle_args *reset_spec)
 {
@@ -61,6 +69,8 @@ static int brcm_rescal_reset_xlate(struct reset_controller_dev *rcdev,
 
 static const struct reset_control_ops brcm_rescal_reset_ops = {
 	.reset = brcm_rescal_reset_set,
+	.deassert = brcm_rescal_reset_set,
+	.assert = brcm_rescal_reset_assert,
 };
 
 static int brcm_rescal_reset_probe(struct platform_device *pdev)
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 66eb1122248b..36a56d647dfd 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -223,6 +223,17 @@ config RTC_DRV_AC100
 	  This driver can also be built as a module. If so, the module
 	  will be called rtc-ac100.
 
+config RTC_DRV_RPI
+	tristate "Raspberry Pi RTC"
+	depends on ARCH_BRCMSTB || COMPILE_TEST
+	default ARCH_BRCMSTB
+	help
+	  If you say yes here you get support for the RTC found on
+	  Raspberry Pi devices.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called rtc-rpi.
+
 config RTC_DRV_BRCMSTB
 	tristate "Broadcom STB wake-timer"
 	depends on ARCH_BRCMSTB || BMIPS_GENERIC || COMPILE_TEST
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index f62340ecc534..47adbf011f1c 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -143,6 +143,7 @@ obj-$(CONFIG_RTC_DRV_RC5T583)	+= rtc-rc5t583.o
 obj-$(CONFIG_RTC_DRV_RC5T619)	+= rtc-rc5t619.o
 obj-$(CONFIG_RTC_DRV_RK808)	+= rtc-rk808.o
 obj-$(CONFIG_RTC_DRV_RP5C01)	+= rtc-rp5c01.o
+obj-$(CONFIG_RTC_DRV_RPI)	+= rtc-rpi.o
 obj-$(CONFIG_RTC_DRV_RS5C313)	+= rtc-rs5c313.o
 obj-$(CONFIG_RTC_DRV_RS5C348)	+= rtc-rs5c348.o
 obj-$(CONFIG_RTC_DRV_RS5C372)	+= rtc-rs5c372.o
diff --git a/drivers/rtc/rtc-ds3232.c b/drivers/rtc/rtc-ds3232.c
index dd37b055693c..1ce653fcc0cf 100644
--- a/drivers/rtc/rtc-ds3232.c
+++ b/drivers/rtc/rtc-ds3232.c
@@ -701,9 +701,16 @@ static int ds3234_probe(struct spi_device *spi)
 	return ds3232_probe(&spi->dev, regmap, spi->irq, "ds3234");
 }
 
+static const  __maybe_unused struct of_device_id ds3234_of_match[] = {
+	{ .compatible = "dallas,ds3234" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ds3234_of_match);
+
 static struct spi_driver ds3234_driver = {
 	.driver = {
 		.name	 = "ds3234",
+		.of_match_table = of_match_ptr(ds3234_of_match),
 	},
 	.probe	 = ds3234_probe,
 };
diff --git a/drivers/rtc/rtc-pcf2123.c b/drivers/rtc/rtc-pcf2123.c
index e714661e61a9..89cda4dea7f8 100644
--- a/drivers/rtc/rtc-pcf2123.c
+++ b/drivers/rtc/rtc-pcf2123.c
@@ -479,3 +479,4 @@ module_spi_driver(pcf2123_driver);
 MODULE_AUTHOR("Chris Verges <chrisv@cyberswitching.com>");
 MODULE_DESCRIPTION("NXP PCF2123 RTC driver");
 MODULE_LICENSE("GPL");
+MODULE_ALIAS("spi:rtc-pcf2123");
diff --git a/drivers/rtc/rtc-pcf8523.c b/drivers/rtc/rtc-pcf8523.c
index 2c63c0ffd05a..0125000fa722 100644
--- a/drivers/rtc/rtc-pcf8523.c
+++ b/drivers/rtc/rtc-pcf8523.c
@@ -100,6 +100,7 @@ static int pcf8523_rtc_read_time(struct device *dev, struct rtc_time *tm)
 {
 	struct pcf8523 *pcf8523 = dev_get_drvdata(dev);
 	u8 regs[10];
+	u32 value;
 	int err;
 
 	err = regmap_bulk_read(pcf8523->regmap, PCF8523_REG_CONTROL1, regs,
@@ -107,9 +108,36 @@ static int pcf8523_rtc_read_time(struct device *dev, struct rtc_time *tm)
 	if (err < 0)
 		return err;
 
-	if ((regs[0] & PCF8523_CONTROL1_STOP) || (regs[3] & PCF8523_SECONDS_OS))
+	if (regs[PCF8523_REG_CONTROL1] & PCF8523_CONTROL1_STOP)
 		return -EINVAL;
 
+	if (regs[PCF8523_REG_SECONDS] & PCF8523_SECONDS_OS) {
+		/*
+		 * If the oscillator was stopped, try to clear the flag. Upon
+		 * power-up the flag is always set, but if we cannot clear it
+		 * the oscillator isn't running properly for some reason. The
+		 * sensible thing therefore is to return an error, signalling
+		 * that the clock cannot be assumed to be correct.
+		 */
+
+		regs[PCF8523_REG_SECONDS] &= ~PCF8523_SECONDS_OS;
+
+		err = regmap_write(pcf8523->regmap, PCF8523_REG_SECONDS,
+				   regs[PCF8523_REG_SECONDS]);
+		if (err < 0)
+			return err;
+
+		err = regmap_read(pcf8523->regmap, PCF8523_REG_SECONDS,
+				  &value);
+		if (err < 0)
+			return err;
+
+		if (value & PCF8523_SECONDS_OS)
+			return -EAGAIN;
+
+		regs[PCF8523_REG_SECONDS] = value;
+	}
+
 	tm->tm_sec = bcd2bin(regs[3] & 0x7f);
 	tm->tm_min = bcd2bin(regs[4] & 0x7f);
 	tm->tm_hour = bcd2bin(regs[5] & 0x3f);
diff --git a/drivers/rtc/rtc-rpi.c b/drivers/rtc/rtc-rpi.c
new file mode 100644
index 000000000000..006012333e78
--- /dev/null
+++ b/drivers/rtc/rtc-rpi.c
@@ -0,0 +1,277 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+/**
+ * rtc-rpi.c
+ *
+ * RTC driver using firmware mailbox
+ * Supports battery backed RTC and wake alarms
+ *
+ * Based on rtc-meson-vrtc by Neil Armstrong
+ *
+ * Copyright (c) 2023, Raspberry Pi Ltd.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/rtc.h>
+#include <linux/of.h>
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
+struct rpi_rtc_data {
+	struct rtc_device *rtc;
+	struct rpi_firmware *fw;
+	u32 bbat_vchg_microvolts;
+};
+
+#define RPI_FIRMWARE_GET_RTC_REG 0x00030087
+#define RPI_FIRMWARE_SET_RTC_REG 0x00038087
+
+enum {
+	RTC_TIME,
+	RTC_ALARM,
+	RTC_ALARM_PENDING,
+	RTC_ALARM_ENABLE,
+	RTC_BBAT_CHG_VOLTS,
+	RTC_BBAT_CHG_VOLTS_MIN,
+	RTC_BBAT_CHG_VOLTS_MAX,
+	RTC_BBAT_VOLTS
+};
+
+static int rpi_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	struct rpi_rtc_data *vrtc = dev_get_drvdata(dev);
+	u32 data[2] = {RTC_TIME};
+	int err;
+
+	err = rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_GET_RTC_REG,
+				    &data, sizeof(data));
+	rtc_time64_to_tm(data[1], tm);
+	return err;
+}
+
+static int rpi_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	struct rpi_rtc_data *vrtc = dev_get_drvdata(dev);
+	u32 data[2] = {RTC_TIME, rtc_tm_to_time64(tm)};
+
+	return rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_SET_RTC_REG,
+				     &data, sizeof(data));
+}
+
+static int rpi_rtc_alarm_irq_is_enabled(struct device *dev, unsigned char *enabled)
+{
+	struct rpi_rtc_data *vrtc = dev_get_drvdata(dev);
+	u32 data[2] = {RTC_ALARM_ENABLE};
+	s32 err = 0;
+
+	err = rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_GET_RTC_REG,
+				    &data, sizeof(data));
+	*enabled = data[1] & 0x1;
+	return err;
+}
+
+static int rpi_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+	struct rpi_rtc_data *vrtc = dev_get_drvdata(dev);
+	u32 data[2] = {RTC_ALARM_ENABLE, enabled};
+
+	return rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_SET_RTC_REG,
+				     &data, sizeof(data));
+}
+
+static int rpi_rtc_alarm_clear_pending(struct device *dev)
+{
+	struct rpi_rtc_data *vrtc = dev_get_drvdata(dev);
+	u32 data[2] = {RTC_ALARM_PENDING, 1};
+
+	return rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_SET_RTC_REG,
+				     &data, sizeof(data));
+}
+
+static int rpi_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+	struct rpi_rtc_data *vrtc = dev_get_drvdata(dev);
+	u32 data[2] = {RTC_ALARM};
+	s32 err = 0;
+
+	err = rpi_rtc_alarm_irq_is_enabled(dev, &alarm->enabled);
+	if (!err)
+		err = rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_GET_RTC_REG,
+					    &data, sizeof(data));
+	rtc_time64_to_tm(data[1], &alarm->time);
+
+	return err;
+}
+
+static int rpi_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+	struct rpi_rtc_data *vrtc = dev_get_drvdata(dev);
+	u32 data[2] = {RTC_ALARM, rtc_tm_to_time64(&alarm->time)};
+	int err;
+
+	err = rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_SET_RTC_REG,
+				    &data, sizeof(data));
+
+	if (err == 0)
+		err = rpi_rtc_alarm_irq_enable(dev, alarm->enabled);
+
+	return err;
+}
+
+static const struct rtc_class_ops rpi_rtc_ops = {
+	.read_time = rpi_rtc_read_time,
+	.set_time = rpi_rtc_set_time,
+	.read_alarm = rpi_rtc_read_alarm,
+	.set_alarm = rpi_rtc_set_alarm,
+	.alarm_irq_enable = rpi_rtc_alarm_irq_enable,
+};
+
+static int rpi_rtc_set_charge_voltage(struct device *dev)
+{
+	struct rpi_rtc_data *vrtc = dev_get_drvdata(dev);
+	u32 data[2] = {RTC_BBAT_CHG_VOLTS, vrtc->bbat_vchg_microvolts};
+	int err;
+
+	err = rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_SET_RTC_REG,
+				    &data, sizeof(data));
+
+	if (err)
+		dev_err(dev, "failed to set trickle charge voltage to %uuV: %d\n",
+			vrtc->bbat_vchg_microvolts, err);
+	else if (vrtc->bbat_vchg_microvolts)
+		dev_info(dev, "trickle charging enabled at %uuV\n",
+			 vrtc->bbat_vchg_microvolts);
+
+	return err;
+}
+
+static ssize_t rpi_rtc_print_uint_reg(struct device *dev, char *buf, u32 reg)
+{
+	struct rpi_rtc_data *vrtc = dev_get_drvdata(dev->parent);
+	u32 data[2] = {reg, 0};
+	int ret = 0;
+
+	ret = rpi_firmware_property(vrtc->fw, RPI_FIRMWARE_GET_RTC_REG,
+				    &data, sizeof(data));
+	if (ret < 0)
+		return ret;
+
+	return sprintf(buf, "%u\n", data[1]);
+}
+
+static ssize_t charging_voltage_show(struct device *dev,
+				     struct device_attribute *attr,
+				     char *buf)
+{
+	return rpi_rtc_print_uint_reg(dev, buf, RTC_BBAT_CHG_VOLTS);
+}
+static DEVICE_ATTR_RO(charging_voltage);
+
+static ssize_t charging_voltage_min_show(struct device *dev,
+					 struct device_attribute *attr,
+					 char *buf)
+{
+	return rpi_rtc_print_uint_reg(dev, buf, RTC_BBAT_CHG_VOLTS_MIN);
+}
+static DEVICE_ATTR_RO(charging_voltage_min);
+
+static ssize_t charging_voltage_max_show(struct device *dev,
+					 struct device_attribute *attr,
+					 char *buf)
+{
+	return rpi_rtc_print_uint_reg(dev, buf, RTC_BBAT_CHG_VOLTS_MAX);
+}
+static DEVICE_ATTR_RO(charging_voltage_max);
+
+static ssize_t battery_voltage_show(struct device *dev,
+				    struct device_attribute *attr,
+				    char *buf)
+{
+	return rpi_rtc_print_uint_reg(dev, buf, RTC_BBAT_VOLTS);
+}
+static DEVICE_ATTR_RO(battery_voltage);
+
+static struct attribute *rpi_rtc_attrs[] = {
+	&dev_attr_charging_voltage.attr,
+	&dev_attr_charging_voltage_min.attr,
+	&dev_attr_charging_voltage_max.attr,
+	&dev_attr_battery_voltage.attr,
+	NULL
+};
+
+static const struct attribute_group rpi_rtc_sysfs_files = {
+	.attrs = rpi_rtc_attrs,
+};
+
+static int rpi_rtc_probe(struct platform_device *pdev)
+{
+	struct rpi_rtc_data *vrtc;
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	struct device_node *fw_node;
+	struct rpi_firmware *fw;
+	int ret;
+
+	fw_node = of_parse_phandle(np, "firmware", 0);
+	if (!fw_node) {
+		dev_err(dev, "Missing firmware node\n");
+		return -ENOENT;
+	}
+
+	fw = rpi_firmware_get(fw_node);
+	if (!fw)
+		return -EPROBE_DEFER;
+
+	vrtc = devm_kzalloc(&pdev->dev, sizeof(*vrtc), GFP_KERNEL);
+	if (!vrtc)
+		return -ENOMEM;
+
+	vrtc->fw = fw;
+
+	device_init_wakeup(&pdev->dev, 1);
+
+	platform_set_drvdata(pdev, vrtc);
+
+	vrtc->rtc = devm_rtc_allocate_device(&pdev->dev);
+	if (IS_ERR(vrtc->rtc))
+		return PTR_ERR(vrtc->rtc);
+
+	set_bit(RTC_FEATURE_ALARM_WAKEUP_ONLY, vrtc->rtc->features);
+	clear_bit(RTC_FEATURE_UPDATE_INTERRUPT, vrtc->rtc->features);
+
+	vrtc->rtc->ops = &rpi_rtc_ops;
+	ret = rtc_add_group(vrtc->rtc, &rpi_rtc_sysfs_files);
+	if (ret)
+		return ret;
+
+	rpi_rtc_alarm_clear_pending(dev);
+
+	/*
+	 * Optionally enable trickle charging - if the property isn't
+	 * present (or set to zero), trickle charging is disabled.
+	 */
+	of_property_read_u32(np, "trickle-charge-microvolt",
+			     &vrtc->bbat_vchg_microvolts);
+
+	rpi_rtc_set_charge_voltage(dev);
+
+	return devm_rtc_register_device(vrtc->rtc);
+}
+
+static const struct of_device_id rpi_rtc_dt_match[] = {
+	{ .compatible = "raspberrypi,rpi-rtc"},
+	{},
+};
+MODULE_DEVICE_TABLE(of, rpi_rtc_dt_match);
+
+static struct platform_driver rpi_rtc_driver = {
+	.probe = rpi_rtc_probe,
+	.driver = {
+		.name = "rpi-rtc",
+		.of_match_table = rpi_rtc_dt_match,
+	},
+};
+
+module_platform_driver(rpi_rtc_driver);
+
+MODULE_DESCRIPTION("Raspberry Pi RTC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/rtc/rtc-rv3028.c b/drivers/rtc/rtc-rv3028.c
index 86b7f821e937..e157ba43b613 100644
--- a/drivers/rtc/rtc-rv3028.c
+++ b/drivers/rtc/rtc-rv3028.c
@@ -858,16 +858,17 @@ static const struct regmap_config regmap_config = {
 static u8 rv3028_set_trickle_charger(struct rv3028_data *rv3028,
 				     struct i2c_client *client)
 {
-	int ret, val_old, val;
+	int ret, val_old, val, val_mask;
 	u32 ohms, chargeable;
+	u32 bsm;
 
 	ret = regmap_read(rv3028->regmap, RV3028_BACKUP, &val_old);
 	if (ret < 0)
 		return ret;
 
 	/* mask out only trickle charger bits */
-	val_old = val_old & (RV3028_BACKUP_TCE | RV3028_BACKUP_TCR_MASK);
-	val = val_old;
+	val_mask = RV3028_BACKUP_TCE | RV3028_BACKUP_TCR_MASK;
+	val = val_old & val_mask;
 
 	/* setup trickle charger */
 	if (!device_property_read_u32(&client->dev, "trickle-resistor-ohms",
@@ -902,10 +903,21 @@ static u8 rv3028_set_trickle_charger(struct rv3028_data *rv3028,
 		}
 	}
 
+	/* setup backup switchover mode */
+	if (!device_property_read_u32(&client->dev,
+				      "backup-switchover-mode",
+				      &bsm)) {
+		if (bsm <= 3) {
+			val_mask |= RV3028_BACKUP_BSM;
+			val |= (u8)(bsm << 2);
+		} else {
+			dev_warn(&client->dev, "invalid backup switchover mode value\n");
+		}
+	}
+
 	/* only update EEPROM if changes are necessary */
-	if (val_old != val) {
-		ret = rv3028_update_cfg(rv3028, RV3028_BACKUP, RV3028_BACKUP_TCE |
-						RV3028_BACKUP_TCR_MASK, val);
+	if ((val_old & val_mask) != val) {
+		ret = rv3028_update_cfg(rv3028, RV3028_BACKUP, val_mask, val);
 		if (ret)
 			return ret;
 	}
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 06c740442fa7..c421cec24049 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -873,6 +873,18 @@ config SPI_RB4XX
 	help
 	  SPI controller driver for the Mikrotik RB4xx series boards.
 
+config SPI_RP2040_GPIO_BRIDGE
+	tristate "Raspberry Pi RP2040 GPIO Bridge"
+	depends on I2C && SPI && GPIOLIB && CRYPTO_HASH2
+	help
+	  Support for the Raspberry Pi RP2040 GPIO bridge.
+
+	  This driver provides support for the Raspberry Pi PR2040 GPIO bridge.
+	  It can be used as a GPIO expander and a Tx-only SPI master.
+
+	  Optionally, this driver is able to take advantage of Raspberry Pi RP1
+	  GPIOs to achieve faster than I2C data transfer rates.
+
 config SPI_RPCIF
 	tristate "Renesas RPC-IF SPI driver"
 	depends on RENESAS_RPCIF
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index a9b1bc259b68..274861550d48 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -119,6 +119,7 @@ obj-$(CONFIG_SPI_ROCKCHIP)		+= spi-rockchip.o
 obj-$(CONFIG_SPI_ROCKCHIP_SFC)		+= spi-rockchip-sfc.o
 obj-$(CONFIG_SPI_RB4XX)			+= spi-rb4xx.o
 obj-$(CONFIG_MACH_REALTEK_RTL)		+= spi-realtek-rtl.o
+obj-$(CONFIG_SPI_RP2040_GPIO_BRIDGE)	+= spi-rp2040-gpio-bridge.o
 obj-$(CONFIG_SPI_RPCIF)			+= spi-rpc-if.o
 obj-$(CONFIG_SPI_RSPI)			+= spi-rspi.o
 obj-$(CONFIG_SPI_RZV2M_CSI)		+= spi-rzv2m-csi.o
diff --git a/drivers/spi/spi-bcm2835.c b/drivers/spi/spi-bcm2835.c
index 5926e004d9a6..b98fbe907793 100644
--- a/drivers/spi/spi-bcm2835.c
+++ b/drivers/spi/spi-bcm2835.c
@@ -119,6 +119,7 @@ MODULE_PARM_DESC(polling_limit_us,
  */
 struct bcm2835_spi {
 	void __iomem *regs;
+	phys_addr_t phys_addr;
 	struct clk *clk;
 	struct gpio_desc *cs_gpio;
 	unsigned long clk_hz;
@@ -891,19 +892,8 @@ static int bcm2835_dma_init(struct spi_controller *ctlr, struct device *dev,
 			    struct bcm2835_spi *bs)
 {
 	struct dma_slave_config slave_config;
-	const __be32 *addr;
-	dma_addr_t dma_reg_base;
 	int ret;
 
-	/* base address in dma-space */
-	addr = of_get_address(ctlr->dev.of_node, 0, NULL, NULL);
-	if (!addr) {
-		dev_err(dev, "could not get DMA-register address - not using dma mode\n");
-		/* Fall back to interrupt mode */
-		return 0;
-	}
-	dma_reg_base = be32_to_cpup(addr);
-
 	/* get tx/rx dma */
 	ctlr->dma_tx = dma_request_chan(dev, "tx");
 	if (IS_ERR(ctlr->dma_tx)) {
@@ -925,7 +915,7 @@ static int bcm2835_dma_init(struct spi_controller *ctlr, struct device *dev,
 	 * or, in case of an RX-only transfer, cyclically copies from the zero
 	 * page to the FIFO using a preallocated, reusable descriptor.
 	 */
-	slave_config.dst_addr = (u32)(dma_reg_base + BCM2835_SPI_FIFO);
+	slave_config.dst_addr = bs->phys_addr + BCM2835_SPI_FIFO;
 	slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
 
 	ret = dmaengine_slave_config(ctlr->dma_tx, &slave_config);
@@ -964,9 +954,9 @@ static int bcm2835_dma_init(struct spi_controller *ctlr, struct device *dev,
 	 * RX FIFO or, in case of a TX-only transfer, cyclically writes a
 	 * precalculated value to the CS register to clear the RX FIFO.
 	 */
-	slave_config.src_addr = (u32)(dma_reg_base + BCM2835_SPI_FIFO);
+	slave_config.src_addr = bs->phys_addr + BCM2835_SPI_FIFO;
 	slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
-	slave_config.dst_addr = (u32)(dma_reg_base + BCM2835_SPI_CS);
+	slave_config.dst_addr = bs->phys_addr + BCM2835_SPI_CS;
 	slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
 
 	ret = dmaengine_slave_config(ctlr->dma_rx, &slave_config);
@@ -1059,6 +1049,16 @@ static int bcm2835_spi_transfer_one(struct spi_controller *ctlr,
 	unsigned long hz_per_byte, byte_limit;
 	u32 cs = target->prepare_cs;
 
+	if (unlikely(!tfr->len)) {
+		static int warned;
+
+		if (!warned)
+			dev_warn(&spi->dev,
+				 "zero-length SPI transfer ignored\n");
+		warned = 1;
+		return 0;
+	}
+
 	/* set clock */
 	spi_hz = tfr->speed_hz;
 
@@ -1231,6 +1231,7 @@ static int bcm2835_spi_setup(struct spi_device *spi)
 		"brcm,bcm2711-gpio",
 		"brcm,bcm7211-gpio",
 	};
+	int len;
 	int ret, i;
 	u32 cs;
 
@@ -1304,6 +1305,10 @@ static int bcm2835_spi_setup(struct spi_device *spi)
 	if (i == ARRAY_SIZE(pinctrl_compats))
 		return 0;
 
+	/* Skip forced CS conversion if controller has an empty cs-gpios property */
+	if (of_find_property(ctlr->dev.of_node, "cs-gpios", &len) && len == 0)
+		return 0;
+
 	/*
 	 * TODO: The code below is a slightly better alternative to the utter
 	 * abuse of the GPIO API that I found here before. It creates a
@@ -1350,6 +1355,7 @@ static int bcm2835_spi_probe(struct platform_device *pdev)
 {
 	struct spi_controller *ctlr;
 	struct bcm2835_spi *bs;
+	struct resource *iomem;
 	int err;
 
 	ctlr = devm_spi_alloc_host(&pdev->dev, sizeof(*bs));
@@ -1373,10 +1379,11 @@ static int bcm2835_spi_probe(struct platform_device *pdev)
 	bs = spi_controller_get_devdata(ctlr);
 	bs->ctlr = ctlr;
 
-	bs->regs = devm_platform_ioremap_resource(pdev, 0);
+	bs->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &iomem);
 	if (IS_ERR(bs->regs))
 		return PTR_ERR(bs->regs);
 
+	bs->phys_addr = iomem->start;
 	bs->clk = devm_clk_get_enabled(&pdev->dev, NULL);
 	if (IS_ERR(bs->clk))
 		return dev_err_probe(&pdev->dev, PTR_ERR(bs->clk),
diff --git a/drivers/spi/spi-dw-core.c b/drivers/spi/spi-dw-core.c
index 431788dd848c..5ee7f1fd11df 100644
--- a/drivers/spi/spi-dw-core.c
+++ b/drivers/spi/spi-dw-core.c
@@ -100,7 +100,8 @@ void dw_spi_set_cs(struct spi_device *spi, bool enable)
 	 * support active-high or active-low CS level.
 	 */
 	if (cs_high == enable)
-		dw_writel(dws, DW_SPI_SER, BIT(spi_get_chipselect(spi, 0)));
+		dw_writel(dws, DW_SPI_SER,
+			BIT(spi_get_csgpiod(spi, 0) ? 0 : spi_get_chipselect(spi, 0)));
 	else
 		dw_writel(dws, DW_SPI_SER, 0);
 }
@@ -201,7 +202,18 @@ int dw_spi_check_status(struct dw_spi *dws, bool raw)
 
 	/* Generically handle the erroneous situation */
 	if (ret) {
-		dw_spi_reset_chip(dws);
+		/*
+		 * Forcibly halting the controller can cause DMA to hang.
+		 * Defer to dw_spi_handle_err outside of interrupt context
+		 * and mask further interrupts for the current transfer.
+		 */
+		if (dws->dma_mapped) {
+			dw_spi_mask_intr(dws, 0xff);
+			dw_readl(dws, DW_SPI_ICR);
+		} else {
+			dw_spi_reset_chip(dws);
+		}
+
 		if (dws->host->cur_msg)
 			dws->host->cur_msg->status = ret;
 	}
@@ -210,6 +222,32 @@ int dw_spi_check_status(struct dw_spi *dws, bool raw)
 }
 EXPORT_SYMBOL_NS_GPL(dw_spi_check_status, SPI_DW_CORE);
 
+static inline bool dw_spi_ctlr_busy(struct dw_spi *dws)
+{
+	return dw_readl(dws, DW_SPI_SR) & DW_SPI_SR_BUSY;
+}
+
+static enum hrtimer_restart dw_spi_hrtimer_handler(struct hrtimer *hr)
+{
+	struct dw_spi *dws = container_of(hr, struct dw_spi, hrtimer);
+
+	if (!dw_spi_ctlr_busy(dws)) {
+		spi_finalize_current_transfer(dws->host);
+		return HRTIMER_NORESTART;
+	}
+
+	if (!dws->idle_wait_retries) {
+		dev_err(&dws->host->dev, "controller stuck at busy\n");
+		spi_finalize_current_transfer(dws->host);
+		return HRTIMER_NORESTART;
+	}
+
+	dws->idle_wait_retries--;
+	hrtimer_forward_now(hr, dws->idle_wait_interval);
+
+	return HRTIMER_RESTART;
+}
+
 static irqreturn_t dw_spi_transfer_handler(struct dw_spi *dws)
 {
 	u16 irq_status = dw_readl(dws, DW_SPI_ISR);
@@ -226,12 +264,32 @@ static irqreturn_t dw_spi_transfer_handler(struct dw_spi *dws)
 	 * final stage of the transfer. By doing so we'll get the next IRQ
 	 * right when the leftover incoming data is received.
 	 */
-	dw_reader(dws);
-	if (!dws->rx_len) {
-		dw_spi_mask_intr(dws, 0xff);
-		spi_finalize_current_transfer(dws->host);
-	} else if (dws->rx_len <= dw_readl(dws, DW_SPI_RXFTLR)) {
-		dw_writel(dws, DW_SPI_RXFTLR, dws->rx_len - 1);
+	if (dws->rx_len) {
+		dw_reader(dws);
+		if (!dws->rx_len) {
+			dw_spi_mask_intr(dws, 0xff);
+			spi_finalize_current_transfer(dws->host);
+		} else if (dws->rx_len <= dw_readl(dws, DW_SPI_RXFTLR)) {
+			dw_writel(dws, DW_SPI_RXFTLR, dws->rx_len - 1);
+		}
+	} else if (!dws->tx_len) {
+		dw_spi_mask_intr(dws, DW_SPI_INT_TXEI);
+		if (dw_spi_ctlr_busy(dws)) {
+			ktime_t period = ns_to_ktime(DIV_ROUND_UP(NSEC_PER_SEC, dws->current_freq));
+
+			/*
+			 * Make the initial wait an underestimate of how long the transfer
+			 * should take, then poll rapidly to reduce the delay
+			 */
+			hrtimer_start(&dws->hrtimer,
+				      period * (8 * dws->n_bytes - 1),
+				      HRTIMER_MODE_REL);
+			dws->idle_wait_retries = 10;
+			dws->idle_wait_interval = period;
+		} else {
+			spi_finalize_current_transfer(dws->host);
+		}
+		return IRQ_HANDLED;
 	}
 
 	/*
@@ -241,8 +299,12 @@ static irqreturn_t dw_spi_transfer_handler(struct dw_spi *dws)
 	 */
 	if (irq_status & DW_SPI_INT_TXEI) {
 		dw_writer(dws);
-		if (!dws->tx_len)
-			dw_spi_mask_intr(dws, DW_SPI_INT_TXEI);
+		if (!dws->tx_len) {
+			if (dws->rx_len)
+				dw_spi_mask_intr(dws, DW_SPI_INT_TXEI);
+			else
+				dw_writel(dws, DW_SPI_TXFTLR, 0);
+		}
 	}
 
 	return IRQ_HANDLED;
@@ -337,7 +399,7 @@ void dw_spi_update_config(struct dw_spi *dws, struct spi_device *spi,
 		dw_writel(dws, DW_SPI_CTRLR1, cfg->ndf ? cfg->ndf - 1 : 0);
 
 	/* Note DW APB SSI clock divider doesn't support odd numbers */
-	clk_div = (DIV_ROUND_UP(dws->max_freq, cfg->freq) + 1) & 0xfffe;
+	clk_div = min(DIV_ROUND_UP(dws->max_freq, cfg->freq) + 1, 0xfffe) & 0xfffe;
 	speed_hz = dws->max_freq / clk_div;
 
 	if (dws->current_freq != speed_hz) {
@@ -363,15 +425,18 @@ static void dw_spi_irq_setup(struct dw_spi *dws)
 	 * will be adjusted at the final stage of the IRQ-based SPI transfer
 	 * execution so not to lose the leftover of the incoming data.
 	 */
-	level = min_t(unsigned int, dws->fifo_len / 2, dws->tx_len);
+	level = min_t(unsigned int, dws->fifo_len / 2, dws->tx_len ? dws->tx_len : dws->rx_len);
 	dw_writel(dws, DW_SPI_TXFTLR, level);
 	dw_writel(dws, DW_SPI_RXFTLR, level - 1);
 
 	dws->transfer_handler = dw_spi_transfer_handler;
 
-	imask = DW_SPI_INT_TXEI | DW_SPI_INT_TXOI |
-		DW_SPI_INT_RXUI | DW_SPI_INT_RXOI | DW_SPI_INT_RXFI;
+	imask = DW_SPI_INT_TXEI | DW_SPI_INT_TXOI;
+	if (dws->rx_len)
+		imask |= DW_SPI_INT_RXUI | DW_SPI_INT_RXOI | DW_SPI_INT_RXFI;
 	dw_spi_umask_intr(dws, imask);
+	if (!dws->tx_len)
+		dw_writel(dws, DW_SPI_DR, 0);
 }
 
 /*
@@ -394,18 +459,23 @@ static int dw_spi_poll_transfer(struct dw_spi *dws,
 	delay.unit = SPI_DELAY_UNIT_SCK;
 	nbits = dws->n_bytes * BITS_PER_BYTE;
 
+	if (!dws->tx_len)
+		dw_writel(dws, DW_SPI_DR, 0);
+
 	do {
-		dw_writer(dws);
+		if (dws->tx_len)
+			dw_writer(dws);
 
 		delay.value = nbits * (dws->rx_len - dws->tx_len);
 		spi_delay_exec(&delay, transfer);
 
-		dw_reader(dws);
+		if (dws->rx_len)
+			dw_reader(dws);
 
 		ret = dw_spi_check_status(dws, true);
 		if (ret)
 			return ret;
-	} while (dws->rx_len);
+	} while (dws->rx_len || dws->tx_len || dw_spi_ctlr_busy(dws));
 
 	return 0;
 }
@@ -420,6 +490,7 @@ static int dw_spi_transfer_one(struct spi_controller *host,
 		.dfs = transfer->bits_per_word,
 		.freq = transfer->speed_hz,
 	};
+	int buswidth;
 	int ret;
 
 	dws->dma_mapped = 0;
@@ -429,6 +500,23 @@ static int dw_spi_transfer_one(struct spi_controller *host,
 	dws->rx = transfer->rx_buf;
 	dws->rx_len = dws->tx_len;
 
+	if (!dws->rx) {
+		dws->rx_len = 0;
+		cfg.tmode = DW_SPI_CTRLR0_TMOD_TO;
+	}
+
+	if (!dws->rx) {
+		dws->rx_len = 0;
+		cfg.tmode = DW_SPI_CTRLR0_TMOD_TO;
+	}
+	if (!dws->tx) {
+		dws->tx_len = 0;
+		cfg.tmode = DW_SPI_CTRLR0_TMOD_RO;
+		cfg.ndf = dws->rx_len;
+	}
+	buswidth = transfer->rx_buf ? transfer->rx_nbits :
+		  (transfer->tx_buf ? transfer->tx_nbits : 1);
+
 	/* Ensure the data above is visible for all CPUs */
 	smp_mb();
 
@@ -607,11 +695,6 @@ static int dw_spi_write_then_read(struct dw_spi *dws, struct spi_device *spi)
 	return 0;
 }
 
-static inline bool dw_spi_ctlr_busy(struct dw_spi *dws)
-{
-	return dw_readl(dws, DW_SPI_SR) & DW_SPI_SR_BUSY;
-}
-
 static int dw_spi_wait_mem_op_done(struct dw_spi *dws)
 {
 	int retry = DW_SPI_WAIT_RETRIES;
@@ -959,10 +1042,12 @@ int dw_spi_add_host(struct device *dev, struct dw_spi *dws)
 			dev_warn(dev, "DMA init failed\n");
 		} else {
 			host->can_dma = dws->dma_ops->can_dma;
-			host->flags |= SPI_CONTROLLER_MUST_TX;
 		}
 	}
 
+	hrtimer_init(&dws->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
+	dws->hrtimer.function = dw_spi_hrtimer_handler;
+
 	ret = spi_register_controller(host);
 	if (ret) {
 		dev_err_probe(dev, ret, "problem registering spi host\n");
@@ -988,6 +1073,7 @@ void dw_spi_remove_host(struct dw_spi *dws)
 {
 	dw_spi_debugfs_remove(dws);
 
+	hrtimer_cancel(&dws->hrtimer);
 	spi_unregister_controller(dws->host);
 
 	if (dws->dma_ops && dws->dma_ops->dma_exit)
diff --git a/drivers/spi/spi-dw-dma.c b/drivers/spi/spi-dw-dma.c
index f4c209e5f52b..41b2fec8afae 100644
--- a/drivers/spi/spi-dw-dma.c
+++ b/drivers/spi/spi-dw-dma.c
@@ -6,6 +6,7 @@
  */
 
 #include <linux/completion.h>
+#include <linux/delay.h>
 #include <linux/dma-mapping.h>
 #include <linux/dmaengine.h>
 #include <linux/irqreturn.h>
@@ -303,6 +304,12 @@ static int dw_spi_dma_wait_tx_done(struct dw_spi *dws,
 		return -EIO;
 	}
 
+	if (!xfer->rx_buf) {
+		delay.value = dws->n_bytes * BITS_PER_BYTE;
+		while (dw_readl(dws, DW_SPI_SR) & DW_SPI_SR_BUSY)
+			spi_delay_exec(&delay, xfer);
+	}
+
 	return 0;
 }
 
@@ -329,7 +336,6 @@ static int dw_spi_dma_config_tx(struct dw_spi *dws)
 	txconf.direction = DMA_MEM_TO_DEV;
 	txconf.dst_addr = dws->dma_addr;
 	txconf.dst_maxburst = dws->txburst;
-	txconf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
 	txconf.dst_addr_width = dw_spi_dma_convert_width(dws->n_bytes);
 	txconf.device_fc = false;
 
@@ -430,7 +436,6 @@ static int dw_spi_dma_config_rx(struct dw_spi *dws)
 	rxconf.direction = DMA_DEV_TO_MEM;
 	rxconf.src_addr = dws->dma_addr;
 	rxconf.src_maxburst = dws->rxburst;
-	rxconf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
 	rxconf.src_addr_width = dw_spi_dma_convert_width(dws->n_bytes);
 	rxconf.device_fc = false;
 
@@ -470,13 +475,12 @@ static int dw_spi_dma_setup(struct dw_spi *dws, struct spi_transfer *xfer)
 	u16 imr, dma_ctrl;
 	int ret;
 
-	if (!xfer->tx_buf)
-		return -EINVAL;
-
 	/* Setup DMA channels */
-	ret = dw_spi_dma_config_tx(dws);
-	if (ret)
-		return ret;
+	if (xfer->tx_buf) {
+		ret = dw_spi_dma_config_tx(dws);
+		if (ret)
+			return ret;
+	}
 
 	if (xfer->rx_buf) {
 		ret = dw_spi_dma_config_rx(dws);
@@ -485,13 +489,17 @@ static int dw_spi_dma_setup(struct dw_spi *dws, struct spi_transfer *xfer)
 	}
 
 	/* Set the DMA handshaking interface */
-	dma_ctrl = DW_SPI_DMACR_TDMAE;
+	dma_ctrl = 0;
+	if (xfer->tx_buf)
+		dma_ctrl |= DW_SPI_DMACR_TDMAE;
 	if (xfer->rx_buf)
 		dma_ctrl |= DW_SPI_DMACR_RDMAE;
 	dw_writel(dws, DW_SPI_DMACR, dma_ctrl);
 
 	/* Set the interrupt mask */
-	imr = DW_SPI_INT_TXOI;
+	imr = 0;
+	if (xfer->tx_buf)
+		imr |= DW_SPI_INT_TXOI;
 	if (xfer->rx_buf)
 		imr |= DW_SPI_INT_RXUI | DW_SPI_INT_RXOI;
 	dw_spi_umask_intr(dws, imr);
@@ -508,15 +516,16 @@ static int dw_spi_dma_transfer_all(struct dw_spi *dws,
 {
 	int ret;
 
-	/* Submit the DMA Tx transfer */
-	ret = dw_spi_dma_submit_tx(dws, xfer->tx_sg.sgl, xfer->tx_sg.nents);
-	if (ret)
-		goto err_clear_dmac;
+	/* Submit the DMA Tx transfer if required */
+	if (xfer->tx_buf) {
+		ret = dw_spi_dma_submit_tx(dws, xfer->tx_sg.sgl, xfer->tx_sg.nents);
+		if (ret)
+			goto err_clear_dmac;
+	}
 
 	/* Submit the DMA Rx transfer if required */
 	if (xfer->rx_buf) {
-		ret = dw_spi_dma_submit_rx(dws, xfer->rx_sg.sgl,
-					   xfer->rx_sg.nents);
+		ret = dw_spi_dma_submit_rx(dws, xfer->rx_sg.sgl, xfer->rx_sg.nents);
 		if (ret)
 			goto err_clear_dmac;
 
@@ -524,7 +533,15 @@ static int dw_spi_dma_transfer_all(struct dw_spi *dws,
 		dma_async_issue_pending(dws->rxchan);
 	}
 
-	dma_async_issue_pending(dws->txchan);
+	if (xfer->tx_buf) {
+		dma_async_issue_pending(dws->txchan);
+	} else {
+		/* Pause to allow DMA channel to fetch RX descriptor */
+		usleep_range(5, 10);
+
+		/* Write something to the TX FIFO to start the transfer */
+		dw_writel(dws, DW_SPI_DR, 0);
+	}
 
 	ret = dw_spi_dma_wait(dws, xfer->len, xfer->effective_speed_hz);
 
diff --git a/drivers/spi/spi-dw-mmio.c b/drivers/spi/spi-dw-mmio.c
index 819907e332c4..f5f24af5c4fc 100644
--- a/drivers/spi/spi-dw-mmio.c
+++ b/drivers/spi/spi-dw-mmio.c
@@ -20,6 +20,7 @@
 #include <linux/property.h>
 #include <linux/regmap.h>
 #include <linux/reset.h>
+#include <linux/interrupt.h>
 
 #include "spi-dw.h"
 
@@ -341,8 +342,11 @@ static int dw_spi_mmio_probe(struct platform_device *pdev)
 	dws->paddr = mem->start;
 
 	dws->irq = platform_get_irq(pdev, 0);
-	if (dws->irq < 0)
-		return dws->irq; /* -ENXIO */
+	if (dws->irq < 0) {
+		if (dws->irq != -ENXIO)
+			return dws->irq; /* -ENXIO */
+		dws->irq = IRQ_NOTCONNECTED;
+	}
 
 	dwsmmio->clk = devm_clk_get_enabled(&pdev->dev, NULL);
 	if (IS_ERR(dwsmmio->clk))
diff --git a/drivers/spi/spi-dw.h b/drivers/spi/spi-dw.h
index fc267c6437ae..92e54a51fc46 100644
--- a/drivers/spi/spi-dw.h
+++ b/drivers/spi/spi-dw.h
@@ -180,6 +180,9 @@ struct dw_spi {
 	u32			current_freq;	/* frequency in hz */
 	u32			cur_rx_sample_dly;
 	u32			def_rx_sample_dly_ns;
+	struct hrtimer		hrtimer;
+	ktime_t			idle_wait_interval;
+	int			idle_wait_retries;
 
 	/* Custom memory operations */
 	struct spi_controller_mem_ops mem_ops;
diff --git a/drivers/spi/spi-gpio.c b/drivers/spi/spi-gpio.c
index 4f192e013cd6..139641fd2329 100644
--- a/drivers/spi/spi-gpio.c
+++ b/drivers/spi/spi-gpio.c
@@ -11,6 +11,7 @@
 #include <linux/module.h>
 #include <linux/platform_device.h>
 #include <linux/property.h>
+#include <linux/delay.h>
 
 #include <linux/spi/spi.h>
 #include <linux/spi/spi_bitbang.h>
@@ -35,6 +36,8 @@ struct spi_gpio {
 	struct gpio_desc		*miso;
 	struct gpio_desc		*mosi;
 	struct gpio_desc		**cs_gpios;
+	bool				sck_idle_input;
+	bool                            cs_dont_invert;
 };
 
 /*----------------------------------------------------------------------*/
@@ -108,12 +111,18 @@ static inline int getmiso(const struct spi_device *spi)
 }
 
 /*
- * NOTE:  this clocks "as fast as we can".  It "should" be a function of the
- * requested device clock.  Software overhead means we usually have trouble
- * reaching even one Mbit/sec (except when we can inline bitops), so for now
- * we'll just assume we never need additional per-bit slowdowns.
+ * Generic bit-banged GPIO SPI might free-run at something in the range
+ * 1Mbps ~ 10Mbps (depending on the platform), and some SPI devices may
+ * need to be clocked at a lower rate. ndelay() is often implemented by
+ * udelay() with rounding up, so do the delay only for nsecs >= 500
+ * (<= 1Mbps). The conditional test adds a small overhead.
  */
-#define spidelay(nsecs)	do {} while (0)
+
+static inline void spidelay(unsigned long nsecs)
+{
+	if (nsecs >= 500)
+		ndelay(nsecs);
+}
 
 #include "spi-bitbang-txrx.h"
 
@@ -224,16 +233,29 @@ static void spi_gpio_chipselect(struct spi_device *spi, int is_active)
 	struct spi_gpio *spi_gpio = spi_to_spi_gpio(spi);
 
 	/* set initial clock line level */
-	if (is_active)
-		gpiod_set_value_cansleep(spi_gpio->sck, spi->mode & SPI_CPOL);
+	if (is_active) {
+		if (spi_gpio->sck_idle_input)
+			gpiod_direction_output(spi_gpio->sck, spi->mode & SPI_CPOL);
+		else
+			gpiod_set_value_cansleep(spi_gpio->sck, spi->mode & SPI_CPOL);
+	}
 
-	/* Drive chip select line, if we have one */
+	/*
+	 * Drive chip select line, if we have one.
+	 * SPI chip selects are normally active-low, but when
+	 * cs_dont_invert is set, we assume their polarity is
+	 * controlled by the GPIO, and write '1' to assert.
+	 */
 	if (spi_gpio->cs_gpios) {
 		struct gpio_desc *cs = spi_gpio->cs_gpios[spi_get_chipselect(spi, 0)];
+		int val = ((spi->mode & SPI_CS_HIGH) || spi_gpio->cs_dont_invert) ?
+			is_active : !is_active;
 
-		/* SPI chip selects are normally active-low */
-		gpiod_set_value_cansleep(cs, (spi->mode & SPI_CS_HIGH) ? is_active : !is_active);
+		gpiod_set_value_cansleep(cs, val);
 	}
+
+	if (spi_gpio->sck_idle_input && !is_active)
+		gpiod_direction_input(spi_gpio->sck);
 }
 
 static void spi_gpio_set_mosi_idle(struct spi_device *spi)
@@ -253,11 +275,14 @@ static int spi_gpio_setup(struct spi_device *spi)
 	/*
 	 * The CS GPIOs have already been
 	 * initialized from the descriptor lookup.
+	 * Here we set them to the non-asserted state.
 	 */
 	if (spi_gpio->cs_gpios) {
 		cs = spi_gpio->cs_gpios[spi_get_chipselect(spi, 0)];
 		if (!spi->controller_state && cs) {
-			ret = gpiod_direction_output(cs, !(spi->mode & SPI_CS_HIGH));
+			ret = gpiod_direction_output(cs,
+							!((spi->mode & SPI_CS_HIGH) ||
+							   spi_gpio->cs_dont_invert));
 			if (ret)
 				return ret;
 		}
@@ -329,17 +354,48 @@ static int spi_gpio_request(struct device *dev, struct spi_gpio *spi_gpio)
 	if (IS_ERR(spi_gpio->miso))
 		return PTR_ERR(spi_gpio->miso);
 
+	spi_gpio->sck_idle_input = device_property_read_bool(dev, "sck-idle-input");
 	spi_gpio->sck = devm_gpiod_get(dev, "sck", GPIOD_OUT_LOW);
 	return PTR_ERR_OR_ZERO(spi_gpio->sck);
 }
 
+/*
+ * In order to implement "sck-idle-input" (which requires SCK
+ * direction and CS level to be switched in a particular order),
+ * we need to control GPIO chip selects from within this driver.
+ */
+
+static int spi_gpio_probe_get_cs_gpios(struct device *dev,
+				       struct spi_controller *master,
+				       bool gpio_defines_polarity)
+{
+	int i;
+	struct spi_gpio *spi_gpio = spi_controller_get_devdata(master);
+
+	spi_gpio->cs_dont_invert = gpio_defines_polarity;
+	spi_gpio->cs_gpios = devm_kcalloc(dev, master->num_chipselect,
+					  sizeof(*spi_gpio->cs_gpios),
+					  GFP_KERNEL);
+	if (!spi_gpio->cs_gpios)
+		return -ENOMEM;
+
+	for (i = 0; i < master->num_chipselect; i++) {
+		spi_gpio->cs_gpios[i] =
+			devm_gpiod_get_index(dev, "cs", i,
+					     gpio_defines_polarity ?
+						GPIOD_OUT_LOW : GPIOD_OUT_HIGH);
+		if (IS_ERR(spi_gpio->cs_gpios[i]))
+			return PTR_ERR(spi_gpio->cs_gpios[i]);
+	}
+
+	return 0;
+}
+
 static int spi_gpio_probe_pdata(struct platform_device *pdev,
 				struct spi_controller *host)
 {
 	struct device *dev = &pdev->dev;
 	struct spi_gpio_platform_data *pdata = dev_get_platdata(dev);
-	struct spi_gpio *spi_gpio = spi_controller_get_devdata(host);
-	int i;
 
 #ifdef GENERIC_BITBANG
 	if (!pdata || !pdata->num_chipselect)
@@ -351,20 +407,7 @@ static int spi_gpio_probe_pdata(struct platform_device *pdev,
 	 */
 	host->num_chipselect = pdata->num_chipselect ?: 1;
 
-	spi_gpio->cs_gpios = devm_kcalloc(dev, host->num_chipselect,
-					  sizeof(*spi_gpio->cs_gpios),
-					  GFP_KERNEL);
-	if (!spi_gpio->cs_gpios)
-		return -ENOMEM;
-
-	for (i = 0; i < host->num_chipselect; i++) {
-		spi_gpio->cs_gpios[i] = devm_gpiod_get_index(dev, "cs", i,
-							     GPIOD_OUT_HIGH);
-		if (IS_ERR(spi_gpio->cs_gpios[i]))
-			return PTR_ERR(spi_gpio->cs_gpios[i]);
-	}
-
-	return 0;
+	return spi_gpio_probe_get_cs_gpios(dev, host, false);
 }
 
 static int spi_gpio_probe(struct platform_device *pdev)
diff --git a/drivers/spi/spi-rp2040-gpio-bridge.c b/drivers/spi/spi-rp2040-gpio-bridge.c
new file mode 100644
index 000000000000..ab6349c9e15c
--- /dev/null
+++ b/drivers/spi/spi-rp2040-gpio-bridge.c
@@ -0,0 +1,1258 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * RP2040 GPIO Bridge
+ *
+ * Copyright (C) 2023, 2024, Raspberry Pi Ltd
+ */
+
+#include <crypto/hash.h>
+#include <linux/debugfs.h>
+#include <linux/crypto.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/gpio/driver.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/minmax.h>
+#include <linux/of_address.h>
+#include <linux/pm_runtime.h>
+#include <linux/spi/spi.h>
+#include <linux/stddef.h>
+#include <linux/types.h>
+
+#define MODULE_NAME "rp2040-gpio-bridge"
+
+#define I2C_RETRIES 4U
+
+#define ONE_KIB 1024U
+#define MD5_SUFFIX_SIZE 9U
+
+#define RP2040_GBDG_FLASH_BLOCK_SIZE (8U * ONE_KIB)
+#define RP2040_GBDG_BLOCK_SIZE (RP2040_GBDG_FLASH_BLOCK_SIZE - MD5_SUFFIX_SIZE)
+
+/*
+ * Max value is 4173330 (using a single manifest)
+ *
+ * Internal page-size is 8KiB - 9 = 8183 bytes
+ * Manifests address data by a series of 16-byte MD5 sums (following a 16-byte
+ * manifest header)
+ *
+ * Max permissible data using a single manifest:
+ *   8183 * (8183//16 - 1) = 4173330
+ *
+ * CMD_DAT_EMIT in fast_xfer/bypass_cache mode could potentially allow a max
+ * transfer size of 4GiB, but this is not currently implemented.
+ */
+#define MAX_TRANSFER_SIZE 4173330
+
+#define HALF_BUFFER (4U * ONE_KIB)
+
+#define STATUS_SIZE 4
+#define MD5_DIGEST_SIZE 16
+#define VERSION_SIZE 4
+#define ID_SIZE 8
+#define TOTAL_RD_HDR_SIZE \
+	(STATUS_SIZE + MD5_DIGEST_SIZE + VERSION_SIZE + ID_SIZE)
+
+struct rp2040_gbdg_device_info {
+	u8 md5[MD5_DIGEST_SIZE];
+	u64 id;
+	u32 version;
+	u32 status;
+};
+
+static_assert(sizeof(struct rp2040_gbdg_device_info) == TOTAL_RD_HDR_SIZE);
+
+#define MANIFEST_UNIT_SIZE 16
+static_assert(MD5_DIGEST_SIZE == MANIFEST_UNIT_SIZE);
+#define MANIFEST_HEADER_UNITS 1
+#define MANIFEST_DATA_UNITS \
+	DIV_ROUND_UP(MAX_TRANSFER_SIZE, RP2040_GBDG_BLOCK_SIZE)
+
+#define STATUS_BUSY 0x01
+
+#define DIRECT_PREFIX 0x00
+#define DIRECT_CMD_CS 0x07
+#define DIRECT_CMD_EMIT 0x08
+
+#define WRITE_DATA_PREFIX 0x80
+#define WRITE_DATA_PREFIX_SIZE 1
+
+#define FIXED_SIZE_CMD_PREFIX 0x81
+
+#define WRITE_DATA_UPPER_PREFIX 0x82
+#define WRITE_DATA_UPPER_PREFIX_SIZE 1
+
+#define NUM_GPIO 24
+
+enum rp2040_gbdg_fixed_size_commands {
+	/* 10-byte commands */
+	CMD_SAVE_CACHE = 0x07,
+	CMD_SEND_RB = 0x08,
+	CMD_GPIO_ST_CL = 0x0b,
+	CMD_GPIO_OE = 0x0c,
+	CMD_DAT_RECV = 0x0d,
+	CMD_DAT_EMIT = 0x0e,
+	/* 18-byte commands */
+	CMD_READ_CSUM = 0x11,
+	CMD_SEND_MANI = 0x13,
+};
+
+struct rp2040_gbdg {
+	struct spi_controller *controller;
+
+	struct dentry *debugfs;
+	size_t transfer_progress;
+
+	struct i2c_client *client;
+	struct crypto_shash *shash;
+	struct shash_desc *shash_desc;
+
+	struct regulator *regulator;
+
+	struct gpio_chip gc;
+	u32 gpio_requested;
+	u32 gpio_direction;
+
+	bool fast_xfer_requires_i2c_lock;
+	struct gpio_descs *fast_xfer_gpios;
+	u32 fast_xfer_recv_gpio_base;
+	u8 fast_xfer_data_index;
+	u8 fast_xfer_clock_index;
+	void __iomem *gpio_base;
+	void __iomem *rio_base;
+
+	bool bypass_cache;
+
+	u8 buffer[2 + HALF_BUFFER];
+	u8 manifest_prep[(MANIFEST_HEADER_UNITS + MANIFEST_DATA_UNITS) *
+			 MANIFEST_UNIT_SIZE];
+};
+
+static int rp2040_gbdg_gpio_dir_in(struct gpio_chip *gc, unsigned int offset);
+static void rp2040_gbdg_gpio_set(struct gpio_chip *gc, unsigned int offset,
+				 int value);
+static int rp2040_gbdg_fast_xfer(struct rp2040_gbdg *priv_data, const u8 *data,
+				 size_t len);
+
+static int rp2040_gbdg_rp1_calc_offsets(u8 gpio, size_t *bank_offset,
+					u8 *shift_offset)
+{
+	if (!bank_offset || !shift_offset || gpio >= 54)
+		return -EINVAL;
+	if (gpio < 28) {
+		*bank_offset = 0x0000;
+		*shift_offset = gpio;
+	} else if (gpio < 34) {
+		*bank_offset = 0x4000;
+		*shift_offset = gpio - 28;
+	} else {
+		*bank_offset = 0x8000;
+		*shift_offset = gpio - 34;
+	}
+
+	return 0;
+}
+
+static int rp2040_gbdg_calc_mux_offset(u8 gpio, size_t *offset)
+{
+	size_t bank_offset;
+	u8 shift_offset;
+	int ret;
+
+	ret = rp2040_gbdg_rp1_calc_offsets(gpio, &bank_offset, &shift_offset);
+	if (ret)
+		return ret;
+	*offset = bank_offset + shift_offset * 8 + 0x4;
+
+	return 0;
+}
+
+static int rp2040_gbdg_rp1_read_mux(struct rp2040_gbdg *priv_data, u8 gpio,
+				    u32 *data)
+{
+	size_t offset;
+	int ret;
+
+	ret = rp2040_gbdg_calc_mux_offset(gpio, &offset);
+	if (ret)
+		return ret;
+
+	*data = readl(priv_data->gpio_base + offset);
+
+	return 0;
+}
+
+static int rp2040_gbdg_rp1_write_mux(struct rp2040_gbdg *priv_data, u8 gpio,
+				     u32 val)
+{
+	size_t offset;
+	int ret;
+
+	ret = rp2040_gbdg_calc_mux_offset(gpio, &offset);
+	if (ret)
+		return ret;
+
+	writel(val, priv_data->gpio_base + offset);
+
+	return 0;
+}
+
+static size_t rp2040_gbdg_max_transfer_size(struct spi_device *spi)
+{
+	return MAX_TRANSFER_SIZE;
+}
+
+static int rp2040_gbdg_get_device_info(struct i2c_client *client,
+				       struct rp2040_gbdg_device_info *info)
+{
+	u8 buf[TOTAL_RD_HDR_SIZE];
+	u8 retries = I2C_RETRIES;
+	u8 *read_pos = buf;
+	size_t field_size;
+	int ret;
+
+	do {
+		ret = i2c_master_recv(client, buf, sizeof(buf));
+		if (!retries--)
+			break;
+	} while (ret == -ETIMEDOUT);
+
+	if (ret != sizeof(buf))
+		return ret < 0 ? ret : -EIO;
+
+	field_size = sizeof_field(struct rp2040_gbdg_device_info, status);
+	memcpy(&info->status, read_pos, field_size);
+	read_pos += field_size;
+
+	field_size = sizeof_field(struct rp2040_gbdg_device_info, md5);
+	memcpy(&info->md5, read_pos, field_size);
+	read_pos += field_size;
+
+	field_size = sizeof_field(struct rp2040_gbdg_device_info, version);
+	memcpy(&info->version, read_pos, field_size);
+	read_pos += field_size;
+
+	field_size = sizeof_field(struct rp2040_gbdg_device_info, id);
+	memcpy(&info->id, read_pos, field_size);
+
+	return 0;
+}
+
+static int rp2040_gbdg_poll_device_info(struct i2c_client *client,
+					struct rp2040_gbdg_device_info *info)
+{
+	struct rp2040_gbdg_device_info itnl;
+	int ret;
+
+	itnl.status = STATUS_BUSY;
+
+	while (itnl.status & STATUS_BUSY) {
+		ret = rp2040_gbdg_get_device_info(client, &itnl);
+		if (ret)
+			return ret;
+	}
+	memcpy(info, &itnl, sizeof(itnl));
+
+	return 0;
+}
+
+static int rp2040_gbdg_get_buffer_hash(struct i2c_client *client, u8 *md5)
+{
+	struct rp2040_gbdg_device_info info;
+	int ret;
+
+	ret = rp2040_gbdg_poll_device_info(client, &info);
+	if (ret)
+		return ret;
+
+	memcpy(md5, info.md5, MD5_DIGEST_SIZE);
+
+	return 0;
+}
+
+static int rp2040_gbdg_wait_until_free(struct i2c_client *client, u8 *status)
+{
+	struct rp2040_gbdg_device_info info;
+	int ret;
+
+	ret = rp2040_gbdg_poll_device_info(client, &info);
+	if (ret)
+		return ret;
+
+	if (status)
+		*status = info.status;
+
+	return 0;
+}
+
+static int rp2040_gbdg_i2c_send(struct i2c_client *client, const u8 *buf,
+				size_t len)
+{
+	u8 retries = I2C_RETRIES;
+	int ret;
+
+	ret = rp2040_gbdg_wait_until_free(client, NULL);
+	if (ret) {
+		dev_err(&client->dev,
+			"%s() rp2040_gbdg_wait_until_free failed\n", __func__);
+		return ret;
+	}
+
+	do {
+		ret = i2c_master_send(client, buf, len);
+		if (!retries--)
+			break;
+	} while (ret == -ETIMEDOUT);
+
+	if (ret != len) {
+		dev_err(&client->dev, "%s() i2c_master_send returned %d\n",
+			__func__, ret);
+		return ret < 0 ? ret : -EIO;
+	}
+
+	return 0;
+}
+
+static int rp2040_gbdg_10byte_cmd(struct i2c_client *client, u8 cmd, u32 addr,
+				  u32 len)
+{
+	u8 buffer[10];
+
+	buffer[0] = FIXED_SIZE_CMD_PREFIX;
+	buffer[1] = cmd;
+	memcpy(&buffer[2], &addr, sizeof(addr));
+	memcpy(&buffer[6], &len, sizeof(len));
+
+	return rp2040_gbdg_i2c_send(client, buffer, sizeof(buffer));
+}
+
+static int rp2040_gbdg_18byte_cmd(struct i2c_client *client, u8 cmd,
+				  const u8 *digest)
+{
+	u8 buffer[18];
+
+	buffer[0] = FIXED_SIZE_CMD_PREFIX;
+	buffer[1] = cmd;
+	memcpy(&buffer[2], digest, MD5_DIGEST_SIZE);
+
+	return rp2040_gbdg_i2c_send(client, buffer, sizeof(buffer));
+}
+
+static int rp2040_gbdg_block_hash(struct rp2040_gbdg *priv_data, const u8 *data,
+				  size_t len, u8 *out)
+{
+	size_t remaining = RP2040_GBDG_BLOCK_SIZE;
+	size_t pad;
+	int ret;
+
+	static const u8 padding[64] = {
+		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+		0xFF, 0xFF, 0xFF, 0xFF,
+	};
+
+	if (len > RP2040_GBDG_BLOCK_SIZE) {
+		return -EMSGSIZE;
+	} else if (len == RP2040_GBDG_BLOCK_SIZE) {
+		return crypto_shash_digest(priv_data->shash_desc, data, len,
+					   out);
+	} else {
+		ret = crypto_shash_init(priv_data->shash_desc);
+		if (ret)
+			return ret;
+
+		ret = crypto_shash_update(priv_data->shash_desc, data, len);
+		if (ret)
+			return ret;
+		remaining -= len;
+
+		/* Pad up-to a 64-byte boundary, unless that takes us over. */
+		pad = round_up(len, 64);
+		if (pad != len && pad < RP2040_GBDG_BLOCK_SIZE) {
+			ret = crypto_shash_update(priv_data->shash_desc,
+						  padding, pad - len);
+			if (ret)
+				return ret;
+			remaining -= (pad - len);
+		}
+
+		/* Pad up-to RP2040_GBDG_BLOCK_SIZE in, preferably, 64-byte chunks */
+		while (remaining) {
+			pad = min_t(size_t, remaining, (size_t)64U);
+			ret = crypto_shash_update(priv_data->shash_desc,
+						  padding, pad);
+			if (ret)
+				return ret;
+			remaining -= pad;
+		}
+		return crypto_shash_final(priv_data->shash_desc, out);
+	}
+}
+
+static int rp2040_gbdg_set_remote_buffer_fast(struct rp2040_gbdg *priv_data,
+					      const u8 *data, unsigned int len)
+{
+	struct i2c_client *client = priv_data->client;
+	int ret;
+
+	if (len > RP2040_GBDG_BLOCK_SIZE)
+		return -EMSGSIZE;
+	if (!priv_data->fast_xfer_gpios)
+		return -EIO;
+
+	ret = rp2040_gbdg_10byte_cmd(client, CMD_DAT_RECV,
+				     priv_data->fast_xfer_recv_gpio_base, len);
+	if (ret) {
+		dev_err(&client->dev, "%s() failed to enter fast data mode\n",
+			__func__);
+		return ret;
+	}
+
+	return rp2040_gbdg_fast_xfer(priv_data, data, len);
+}
+
+static int rp2040_gbdg_set_remote_buffer_i2c(struct rp2040_gbdg *priv_data,
+					     const u8 *data, unsigned int len)
+{
+	struct i2c_client *client = priv_data->client;
+	unsigned int write_len;
+	int ret;
+
+	if (len > RP2040_GBDG_BLOCK_SIZE)
+		return -EMSGSIZE;
+
+	priv_data->buffer[0] = WRITE_DATA_PREFIX;
+	write_len = min(len, HALF_BUFFER);
+	memcpy(&priv_data->buffer[1], data, write_len);
+
+	ret = rp2040_gbdg_i2c_send(client, priv_data->buffer, write_len + 1);
+	if (ret)
+		return ret;
+
+	len -= write_len;
+	data += write_len;
+
+	if (!len)
+		return 0;
+
+	priv_data->buffer[0] = WRITE_DATA_UPPER_PREFIX;
+	memcpy(&priv_data->buffer[1], data, len);
+	ret = rp2040_gbdg_i2c_send(client, priv_data->buffer, len + 1);
+
+	return ret;
+}
+
+static int rp2040_gbdg_set_remote_buffer(struct rp2040_gbdg *priv_data,
+					 const u8 *data, unsigned int len)
+{
+	if (priv_data->fast_xfer_gpios)
+		return rp2040_gbdg_set_remote_buffer_fast(priv_data, data, len);
+	else
+		return rp2040_gbdg_set_remote_buffer_i2c(priv_data, data, len);
+}
+
+/* Loads data by checksum if available or resorts to sending byte-by-byte */
+static int rp2040_gbdg_load_block_remote(struct rp2040_gbdg *priv_data,
+					 const void *data, unsigned int len,
+					 u8 *digest, bool persist)
+{
+	u8 ascii_digest[MD5_DIGEST_SIZE * 2 + 1] = { 0 };
+	struct i2c_client *client = priv_data->client;
+	u8 remote_digest[MD5_DIGEST_SIZE];
+	u8 local_digest[MD5_DIGEST_SIZE];
+	int ret;
+
+	if (len > RP2040_GBDG_BLOCK_SIZE)
+		return -EMSGSIZE;
+
+	ret = rp2040_gbdg_block_hash(priv_data, data, len, local_digest);
+	if (ret)
+		return ret;
+
+	if (digest)
+		memcpy(digest, local_digest, MD5_DIGEST_SIZE);
+
+	/* Check if the RP2040 has the data already */
+	ret = rp2040_gbdg_18byte_cmd(client, CMD_READ_CSUM, local_digest);
+	if (ret)
+		return ret;
+
+	ret = rp2040_gbdg_get_buffer_hash(client, remote_digest);
+	if (ret)
+		return ret;
+
+	if (memcmp(local_digest, remote_digest, MD5_DIGEST_SIZE)) {
+		bin2hex(ascii_digest, local_digest, MD5_DIGEST_SIZE);
+		dev_info(&client->dev, "%s() device missing data: %s\n",
+			 __func__, ascii_digest);
+		/*
+		 * N.B. We're fine to send (the potentially shorter) transfer->len
+		 * number of bytes here as the RP2040 will pad with 0xFF up to buffer
+		 * size once we stop sending.
+		 */
+		ret = rp2040_gbdg_set_remote_buffer(priv_data, data, len);
+		if (ret)
+			return ret;
+
+		/* Make sure the data actually arrived. */
+		ret = rp2040_gbdg_get_buffer_hash(client, remote_digest);
+		if (memcmp(local_digest, remote_digest, MD5_DIGEST_SIZE)) {
+			dev_err(&priv_data->client->dev,
+				"%s() unable to send data to device\n",
+				__func__);
+			return -EREMOTEIO;
+		}
+
+		if (persist) {
+			dev_info(&client->dev,
+				 "%s() sent missing data to device, saving\n",
+				 __func__);
+			ret = rp2040_gbdg_10byte_cmd(client, CMD_SAVE_CACHE, 0,
+						     0);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int rp2040_gbdg_transfer_block(struct rp2040_gbdg *priv_data,
+				      const void *data, unsigned int len)
+{
+	struct i2c_client *client = priv_data->client;
+	int ret;
+
+	if (len > RP2040_GBDG_BLOCK_SIZE)
+		return -EMSGSIZE;
+
+	ret = rp2040_gbdg_load_block_remote(priv_data, data, len, NULL, true);
+	if (ret)
+		return ret;
+
+	/* Remote rambuffer now has correct contents, send it */
+	ret = rp2040_gbdg_10byte_cmd(client, CMD_SEND_RB, 0, len);
+	if (ret)
+		return ret;
+
+	/*
+	 * Wait for data to have actually completed sending as we may be de-asserting CS too quickly
+	 * otherwise.
+	 */
+	ret = rp2040_gbdg_wait_until_free(client, NULL);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int rp2040_gbdg_transfer_manifest(struct rp2040_gbdg *priv_data,
+					 const u8 *data, unsigned int len)
+{
+	struct i2c_client *client = priv_data->client;
+	static const char magic[] = "DATA_MANFST";
+	unsigned int remaining = len;
+	const u32 data_length = len;
+	u8 digest[MD5_DIGEST_SIZE];
+	u8 *digest_write_pos;
+	u8 status;
+	int ret;
+
+	memcpy(priv_data->manifest_prep, magic, sizeof(magic));
+	memcpy(priv_data->manifest_prep + sizeof(magic), &data_length,
+	       sizeof(data_length));
+	digest_write_pos =
+		priv_data->manifest_prep + sizeof(magic) + sizeof(data_length);
+
+	while (remaining) {
+		unsigned int size = min(remaining, RP2040_GBDG_BLOCK_SIZE);
+
+		ret = rp2040_gbdg_block_hash(priv_data, data, size,
+					     digest_write_pos);
+		if (ret)
+			return ret;
+
+		remaining -= size;
+		data += size;
+		digest_write_pos += MD5_DIGEST_SIZE;
+	}
+
+	ret = rp2040_gbdg_load_block_remote(
+		priv_data, priv_data->manifest_prep,
+		digest_write_pos - priv_data->manifest_prep, digest, true);
+	if (ret)
+		return ret;
+
+	dev_info(&client->dev, "%s() issue CMD_SEND_MANI\n", __func__);
+	ret = rp2040_gbdg_18byte_cmd(client, CMD_SEND_MANI, digest);
+	if (ret)
+		return ret;
+
+	ret = rp2040_gbdg_wait_until_free(client, &status);
+	if (ret)
+		return ret;
+
+	dev_info(&client->dev, "%s() SEND_MANI response: %02x\n", __func__,
+		 status);
+
+	return status;
+}
+
+/* Precondition: correctly initialised fast_xfer_*, gpio_base, rio_base */
+static int rp2040_gbdg_fast_xfer(struct rp2040_gbdg *priv_data, const u8 *data,
+				 size_t len)
+{
+	struct i2c_client *client = priv_data->client;
+	void __iomem *clock_toggle;
+	void __iomem *data_set;
+	size_t clock_bank;
+	size_t data_bank;
+	u8 clock_offset;
+	u8 data_offset;
+	u32 clock_mux;
+	u32 data_mux;
+
+	if (priv_data->fast_xfer_requires_i2c_lock)
+		i2c_lock_bus(client->adapter, I2C_LOCK_ROOT_ADAPTER);
+
+	rp2040_gbdg_rp1_read_mux(priv_data, priv_data->fast_xfer_data_index,
+				 &data_mux);
+	rp2040_gbdg_rp1_read_mux(priv_data, priv_data->fast_xfer_clock_index,
+				 &clock_mux);
+
+	gpiod_direction_output(priv_data->fast_xfer_gpios->desc[0], 1);
+
+	rp2040_gbdg_rp1_calc_offsets(priv_data->fast_xfer_data_index,
+				     &data_bank, &data_offset);
+	rp2040_gbdg_rp1_calc_offsets(priv_data->fast_xfer_clock_index,
+				     &clock_bank, &clock_offset);
+
+	data_set = priv_data->rio_base + data_bank + 0x2000; /* SET offset */
+	clock_toggle =
+		priv_data->rio_base + clock_bank + 0x1000; /* XOR offset */
+
+	while (len--) {
+		/* MSB first ordering */
+		u32 d = ~(*data++) << 4U;
+		/*
+		 * Clock out each bit of data, LSB first
+		 * (DDR, achieves approx 5 Mbps)
+		 */
+		for (size_t i = 0; i < 8; i++) {
+			/* Branchless set/clr data */
+			writel(1 << data_offset,
+			       data_set + ((d <<= 1) & 0x1000) /* CLR offset */
+			);
+
+			/* Toggle the clock */
+			writel(1 << clock_offset, clock_toggle);
+		}
+	}
+
+	rp2040_gbdg_rp1_write_mux(priv_data, priv_data->fast_xfer_data_index,
+				  data_mux);
+	rp2040_gbdg_rp1_write_mux(priv_data, priv_data->fast_xfer_clock_index,
+				  clock_mux);
+
+	if (priv_data->fast_xfer_requires_i2c_lock)
+		i2c_unlock_bus(client->adapter, I2C_LOCK_ROOT_ADAPTER);
+
+	return 0;
+}
+
+static int rp2040_gbdg_transfer_bypass(struct rp2040_gbdg *priv_data,
+				       const u8 *data, unsigned int length)
+{
+	int ret;
+	u8 *buf;
+
+	if (priv_data->fast_xfer_gpios) {
+		ret = rp2040_gbdg_10byte_cmd(
+			priv_data->client, CMD_DAT_EMIT,
+			priv_data->fast_xfer_recv_gpio_base, length);
+		return ret ? ret :
+			     rp2040_gbdg_fast_xfer(priv_data, data, length);
+	}
+
+	buf = priv_data->buffer;
+
+	while (length) {
+		unsigned int xfer = min(length, HALF_BUFFER);
+
+		buf[0] = DIRECT_PREFIX;
+		buf[1] = DIRECT_CMD_EMIT;
+		memcpy(&buf[2], data, xfer);
+		ret = rp2040_gbdg_i2c_send(priv_data->client, buf, xfer + 2);
+		if (ret)
+			return ret;
+		length -= xfer;
+		data += xfer;
+	}
+
+	return 0;
+}
+
+static int rp2040_gbdg_transfer_cached(struct rp2040_gbdg *priv_data,
+				       const u8 *data, unsigned int length)
+{
+	int ret;
+
+	/*
+	 * Caching mechanism divides data into '8KiB - 9' (8183 byte)
+	 * 'RP2040_GBDG_BLOCK_SIZE' blocks.
+	 *
+	 * If there's a large amount of data to send, instead, attempt to make use
+	 * of a manifest.
+	 */
+	if (length > (2 * RP2040_GBDG_BLOCK_SIZE)) {
+		if (!rp2040_gbdg_transfer_manifest(priv_data, data, length))
+			return 0;
+	}
+
+	priv_data->transfer_progress = 0;
+	while (length) {
+		unsigned int xfer = min(length, RP2040_GBDG_BLOCK_SIZE);
+
+		ret = rp2040_gbdg_transfer_block(priv_data, data, xfer);
+		if (ret)
+			return ret;
+		length -= xfer;
+		data += xfer;
+		priv_data->transfer_progress += xfer;
+	}
+	priv_data->transfer_progress = 0;
+
+	return 0;
+}
+
+static int rp2040_gbdg_transfer_one(struct spi_controller *ctlr,
+				    struct spi_device *spi,
+				    struct spi_transfer *transfer)
+{
+	/* All transfers are performed in a synchronous manner. As such, return '0'
+	 * on success or -ve on failure. (Returning +ve indicates async xfer)
+	 */
+
+	struct rp2040_gbdg *priv_data = spi_controller_get_devdata(ctlr);
+
+	if (priv_data->bypass_cache) {
+		return rp2040_gbdg_transfer_bypass(priv_data, transfer->tx_buf,
+						   transfer->len);
+	} else {
+		return rp2040_gbdg_transfer_cached(priv_data, transfer->tx_buf,
+						   transfer->len);
+	}
+}
+
+static void rp2040_gbdg_set_cs(struct spi_device *spi, bool enable)
+{
+	static const char disable_cs[] = { DIRECT_PREFIX, DIRECT_CMD_CS, 0x00 };
+	static const char enable_cs[] = { DIRECT_PREFIX, DIRECT_CMD_CS, 0x10 };
+	struct rp2040_gbdg *p_data;
+
+	p_data = spi_controller_get_devdata(spi->controller);
+
+	/*
+	 * 'enable' is inverted and instead describes the logic level of an
+	 * active-low CS.
+	 */
+	rp2040_gbdg_i2c_send(p_data->client, enable ? disable_cs : enable_cs,
+			     3);
+}
+
+static int rp2040_gbdg_gpio_request(struct gpio_chip *gc, unsigned int offset)
+{
+	struct rp2040_gbdg *priv_data = gpiochip_get_data(gc);
+	u32 pattern;
+	int ret;
+
+	if (offset >= NUM_GPIO)
+		return -EINVAL;
+
+	pattern = (1 << (offset + 8));
+	if (pattern & priv_data->gpio_requested)
+		return -EBUSY;
+
+	/* Resume if previously no gpio requested */
+	if (!priv_data->gpio_requested) {
+		ret = pm_runtime_resume_and_get(&priv_data->client->dev);
+		if (ret) {
+			dev_err(&priv_data->client->dev,
+				"%s(%u) unable to resume\n", __func__, offset);
+			return ret;
+		}
+	}
+
+	priv_data->gpio_requested |= pattern;
+
+	return 0;
+}
+
+static void rp2040_gbdg_gpio_free(struct gpio_chip *gc, unsigned int offset)
+{
+	struct rp2040_gbdg *priv_data = gpiochip_get_data(gc);
+	u32 pattern;
+	int ret;
+
+	if (offset >= NUM_GPIO || !priv_data->gpio_requested)
+		return;
+
+	pattern = (1 << (offset + 8));
+
+	priv_data->gpio_requested &= ~pattern;
+	rp2040_gbdg_gpio_dir_in(gc, offset);
+	rp2040_gbdg_gpio_set(gc, offset, 0);
+
+	if (!priv_data->gpio_requested) {
+		ret = pm_runtime_put_autosuspend(&priv_data->client->dev);
+		if (ret) {
+			dev_err(&priv_data->client->dev,
+				"%s(%u) unable to put_autosuspend\n", __func__,
+				offset);
+		}
+	}
+}
+
+static int rp2040_gbdg_gpio_get_direction(struct gpio_chip *gc,
+					  unsigned int offset)
+{
+	struct rp2040_gbdg *priv_data = gpiochip_get_data(gc);
+
+	if (offset >= NUM_GPIO)
+		return -EINVAL;
+
+	return (priv_data->gpio_direction & (1 << (offset + 8))) ?
+		       GPIO_LINE_DIRECTION_IN :
+		       GPIO_LINE_DIRECTION_OUT;
+}
+
+static int rp2040_gbdg_gpio_dir_in(struct gpio_chip *gc, unsigned int offset)
+{
+	struct rp2040_gbdg *priv_data = gpiochip_get_data(gc);
+	struct i2c_client *client = priv_data->client;
+
+	if (offset >= NUM_GPIO)
+		return -EINVAL;
+
+	priv_data->gpio_direction |= (1 << (offset + 8));
+
+	return rp2040_gbdg_10byte_cmd(client, CMD_GPIO_OE,
+				      ~priv_data->gpio_direction,
+				      priv_data->gpio_direction);
+}
+
+static int rp2040_gbdg_gpio_dir_out(struct gpio_chip *gc, unsigned int offset,
+				    int value)
+{
+	struct rp2040_gbdg *priv_data = gpiochip_get_data(gc);
+	struct i2c_client *client = priv_data->client;
+	u32 pattern;
+	int ret;
+
+	if (offset >= NUM_GPIO)
+		return -EINVAL;
+
+	pattern = (1 << (offset + 8));
+
+	ret = rp2040_gbdg_10byte_cmd(client, CMD_GPIO_ST_CL,
+				     value ? pattern : 0, !value ? pattern : 0);
+	if (ret) {
+		dev_err(&client->dev, "%s(%u, %d) could not ST_CL\n", __func__,
+			offset, value);
+		return ret;
+	}
+
+	priv_data->gpio_direction &= ~pattern;
+	ret = rp2040_gbdg_10byte_cmd(client, CMD_GPIO_OE,
+				     ~priv_data->gpio_direction,
+				     priv_data->gpio_direction);
+
+	return ret;
+}
+
+static int rp2040_gbdg_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+	struct rp2040_gbdg *priv_data = gpiochip_get_data(gc);
+	struct i2c_client *client = priv_data->client;
+	struct rp2040_gbdg_device_info info;
+	int ret;
+
+	if (offset >= NUM_GPIO)
+		return -EINVAL;
+
+	ret = rp2040_gbdg_get_device_info(client, &info);
+	if (ret)
+		return ret;
+
+	return info.status & (1 << (offset + 8)) ? 1 : 0;
+}
+
+static void rp2040_gbdg_gpio_set(struct gpio_chip *gc, unsigned int offset,
+				 int value)
+{
+	struct rp2040_gbdg *priv_data = gpiochip_get_data(gc);
+	struct i2c_client *client = priv_data->client;
+	u32 pattern;
+
+	if (offset >= NUM_GPIO)
+		return;
+
+	pattern = (1 << (offset + 8));
+	rp2040_gbdg_10byte_cmd(client, CMD_GPIO_ST_CL, value ? pattern : 0,
+			       !value ? pattern : 0);
+}
+
+static int rp2040_gbdg_get_regulator(struct device *dev,
+				     struct rp2040_gbdg *rp2040_gbdg)
+{
+	struct regulator *reg = devm_regulator_get(dev, "power");
+
+	if (IS_ERR(reg))
+		return PTR_ERR(reg);
+
+	rp2040_gbdg->regulator = reg;
+
+	return 0;
+}
+
+static void rp2040_gbdg_parse_dt(struct rp2040_gbdg *rp2040_gbdg)
+{
+	struct i2c_client *client = rp2040_gbdg->client;
+	struct of_phandle_args of_args[2] = { 0 };
+	struct device *dev = &client->dev;
+	struct device_node *dn;
+
+	rp2040_gbdg->bypass_cache =
+		of_property_read_bool(client->dev.of_node, "bypass-cache");
+
+	/* Optionally configure fast_xfer if RP1 is being used */
+	if (of_parse_phandle_with_args(client->dev.of_node, "fast_xfer-gpios",
+				       "#gpio-cells", 0, &of_args[0]) ||
+	    of_parse_phandle_with_args(client->dev.of_node, "fast_xfer-gpios",
+				       "#gpio-cells", 1, &of_args[1])) {
+		dev_info(dev, "Could not parse fast_xfer-gpios phandles\n");
+		goto node_put;
+	}
+
+	if (of_args[0].np != of_args[1].np) {
+		dev_info(
+			dev,
+			"fast_xfer-gpios are not provided by the same controller\n");
+		goto node_put;
+	}
+	dn = of_args[0].np;
+	if (!of_device_is_compatible(dn, "raspberrypi,rp1-gpio")) {
+		dev_info(dev, "fast_xfer-gpios controller is not an rp1\n");
+		goto node_put;
+	}
+	if (of_args[0].args_count != 2 || of_args[1].args_count != 2) {
+		dev_info(dev, "of_args count is %d\n", of_args[0].args_count);
+		goto node_put;
+	}
+
+	if (of_property_read_u32_index(
+		    client->dev.of_node, "fast_xfer_recv_gpio_base", 0,
+		    &rp2040_gbdg->fast_xfer_recv_gpio_base)) {
+		dev_info(dev, "Could not read fast_xfer_recv_gpio_base\n");
+		goto node_put;
+	}
+
+	rp2040_gbdg->fast_xfer_gpios =
+		devm_gpiod_get_array_optional(dev, "fast_xfer", GPIOD_ASIS);
+	if (IS_ERR_OR_NULL(rp2040_gbdg->fast_xfer_gpios)) {
+		rp2040_gbdg->fast_xfer_gpios = NULL;
+		dev_info(dev, "Could not acquire fast_xfer-gpios\n");
+		goto node_put;
+	}
+
+	rp2040_gbdg->fast_xfer_data_index = of_args[0].args[0];
+	rp2040_gbdg->fast_xfer_clock_index = of_args[1].args[0];
+	rp2040_gbdg->fast_xfer_requires_i2c_lock = of_property_read_bool(
+		client->dev.of_node, "fast_xfer_requires_i2c_lock");
+
+	rp2040_gbdg->gpio_base = of_iomap(dn, 0);
+	if (IS_ERR_OR_NULL(rp2040_gbdg->gpio_base)) {
+		dev_info(&client->dev, "%s() unable to map gpio_base\n",
+			 __func__);
+		rp2040_gbdg->gpio_base = NULL;
+		devm_gpiod_put_array(dev, rp2040_gbdg->fast_xfer_gpios);
+		rp2040_gbdg->fast_xfer_gpios = NULL;
+		goto node_put;
+	}
+
+	rp2040_gbdg->rio_base = of_iomap(dn, 1);
+	if (IS_ERR_OR_NULL(rp2040_gbdg->rio_base)) {
+		dev_info(&client->dev, "%s() unable to map rio_base\n",
+			 __func__);
+		rp2040_gbdg->rio_base = NULL;
+		iounmap(rp2040_gbdg->gpio_base);
+		rp2040_gbdg->gpio_base = NULL;
+		devm_gpiod_put_array(dev, rp2040_gbdg->fast_xfer_gpios);
+		rp2040_gbdg->fast_xfer_gpios = NULL;
+		goto node_put;
+	}
+
+	/*
+	 * fast_xfer mode requires first data bit to be clocked on a rising
+	 * edge. Configure as output-low here before fast_xfer mode is entered.
+	 */
+	gpiod_direction_output(rp2040_gbdg->fast_xfer_gpios->desc[1], 0);
+node_put:
+	if (of_args[0].np)
+		of_node_put(of_args[0].np);
+	if (of_args[1].np)
+		of_node_put(of_args[1].np);
+}
+
+static int rp2040_gbdg_power_off(struct rp2040_gbdg *rp2040_gbdg)
+{
+	struct device *dev = &rp2040_gbdg->client->dev;
+	int ret;
+
+	ret = regulator_disable(rp2040_gbdg->regulator);
+	if (ret) {
+		dev_err(dev, "%s: Could not disable regulator\n", __func__);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int rp2040_gbdg_power_on(struct rp2040_gbdg *rp2040_gbdg)
+{
+	struct device *dev = &rp2040_gbdg->client->dev;
+	int ret;
+
+	ret = regulator_enable(rp2040_gbdg->regulator);
+	if (ret) {
+		dev_err(dev, "%s: Could not enable regulator\n", __func__);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int transfer_progress_show(struct seq_file *s, void *data)
+{
+	struct rp2040_gbdg *rp2040_gbdg = s->private;
+
+	seq_printf(s, "%zu\n", rp2040_gbdg->transfer_progress);
+	return 0;
+}
+
+DEFINE_SHOW_ATTRIBUTE(transfer_progress);
+
+static int rp2040_gbdg_probe(struct i2c_client *client)
+{
+	struct rp2040_gbdg_device_info info;
+	struct spi_controller *controller;
+	struct device *dev = &client->dev;
+	struct rp2040_gbdg *rp2040_gbdg;
+	struct device_node *np;
+	char debugfs_name[128];
+	int ret;
+
+	np = dev->of_node;
+
+	controller = devm_spi_alloc_master(dev, sizeof(struct rp2040_gbdg));
+	if (!controller)
+		return dev_err_probe(dev, ENOMEM,
+				     "could not alloc spi controller\n");
+
+	rp2040_gbdg = spi_controller_get_devdata(controller);
+	i2c_set_clientdata(client, rp2040_gbdg);
+	rp2040_gbdg->controller = controller;
+	rp2040_gbdg->client = client;
+
+	ret = rp2040_gbdg_get_regulator(dev, rp2040_gbdg);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "Cannot get regulator\n");
+
+	ret = rp2040_gbdg_power_on(rp2040_gbdg);
+	if (ret)
+		return dev_err_probe(dev, ret, "Could not power on device\n");
+
+	pm_runtime_set_active(dev);
+	pm_runtime_get_noresume(dev);
+	pm_runtime_enable(dev);
+	pm_runtime_set_autosuspend_delay(dev, 1000);
+	pm_runtime_use_autosuspend(dev);
+
+	ret = rp2040_gbdg_get_device_info(client, &info);
+	if (ret) {
+		dev_err(dev, "Could not get device info\n");
+		goto err_pm;
+	}
+
+	dev_info(dev, "%s() found dev ID: %llx, fw ver. %u\n", __func__,
+		 info.id, info.version);
+
+	rp2040_gbdg->shash = crypto_alloc_shash("md5", 0, 0);
+	if (IS_ERR(rp2040_gbdg->shash)) {
+		ret = PTR_ERR(rp2040_gbdg->shash);
+		dev_err(dev, "Could not allocate shash\n");
+		goto err_pm;
+	}
+
+	if (crypto_shash_digestsize(rp2040_gbdg->shash) != MD5_DIGEST_SIZE) {
+		ret = -EINVAL;
+		dev_err(dev, "error: Unexpected hash digest size\n");
+		goto err_shash;
+	}
+
+	rp2040_gbdg->shash_desc =
+		devm_kmalloc(dev,
+			     sizeof(struct shash_desc) +
+				     crypto_shash_descsize(rp2040_gbdg->shash),
+			     0);
+
+	if (!rp2040_gbdg->shash_desc) {
+		ret = -ENOMEM;
+		dev_err(dev,
+			"error: Could not allocate memory for shash_desc\n");
+		goto err_shash;
+	}
+	rp2040_gbdg->shash_desc->tfm = rp2040_gbdg->shash;
+
+	controller->bus_num = -1;
+	controller->num_chipselect = 1;
+	controller->mode_bits = SPI_CPOL | SPI_CPHA;
+	controller->bits_per_word_mask = SPI_BPW_MASK(8);
+	controller->min_speed_hz = 35000000;
+	controller->max_speed_hz = 35000000;
+	controller->max_transfer_size = rp2040_gbdg_max_transfer_size;
+	controller->max_message_size = rp2040_gbdg_max_transfer_size;
+	controller->transfer_one = rp2040_gbdg_transfer_one;
+	controller->set_cs = rp2040_gbdg_set_cs;
+
+	controller->dev.of_node = np;
+	controller->auto_runtime_pm = true;
+
+	ret = devm_spi_register_controller(dev, controller);
+	if (ret) {
+		dev_err(dev, "error: Could not register SPI controller\n");
+		goto err_shash;
+	}
+
+	memset(&rp2040_gbdg->gc, 0, sizeof(struct gpio_chip));
+	rp2040_gbdg->gc.parent = dev;
+	rp2040_gbdg->gc.label = MODULE_NAME;
+	rp2040_gbdg->gc.owner = THIS_MODULE;
+	rp2040_gbdg->gc.base = -1;
+	rp2040_gbdg->gc.ngpio = NUM_GPIO;
+
+	rp2040_gbdg->gc.request = rp2040_gbdg_gpio_request;
+	rp2040_gbdg->gc.free = rp2040_gbdg_gpio_free;
+	rp2040_gbdg->gc.get_direction = rp2040_gbdg_gpio_get_direction;
+	rp2040_gbdg->gc.direction_input = rp2040_gbdg_gpio_dir_in;
+	rp2040_gbdg->gc.direction_output = rp2040_gbdg_gpio_dir_out;
+	rp2040_gbdg->gc.get = rp2040_gbdg_gpio_get;
+	rp2040_gbdg->gc.set = rp2040_gbdg_gpio_set;
+	rp2040_gbdg->gc.can_sleep = true;
+
+	rp2040_gbdg->gpio_requested = 0;
+
+	/* Coming out of reset, all GPIOs are inputs */
+	rp2040_gbdg->gpio_direction = ~0;
+
+	ret = devm_gpiochip_add_data(dev, &rp2040_gbdg->gc, rp2040_gbdg);
+	if (ret) {
+		dev_err(dev, "error: Could not add data to gpiochip\n");
+		goto err_shash;
+	}
+
+	rp2040_gbdg_parse_dt(rp2040_gbdg);
+
+	snprintf(debugfs_name, sizeof(debugfs_name), "rp2040-spi:%s",
+		 dev_name(dev));
+	rp2040_gbdg->debugfs = debugfs_create_dir(debugfs_name, NULL);
+	debugfs_create_file("transfer_progress", 0444, rp2040_gbdg->debugfs,
+			    rp2040_gbdg, &transfer_progress_fops);
+
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+
+	return 0;
+
+err_shash:
+	crypto_free_shash(rp2040_gbdg->shash);
+err_pm:
+	pm_runtime_disable(dev);
+	pm_runtime_put_noidle(dev);
+	rp2040_gbdg_power_off(rp2040_gbdg);
+
+	return ret;
+}
+
+static void rp2040_gbdg_remove(struct i2c_client *client)
+{
+	struct rp2040_gbdg *priv_data = i2c_get_clientdata(client);
+
+	crypto_free_shash(priv_data->shash);
+
+	if (priv_data->gpio_base) {
+		iounmap(priv_data->gpio_base);
+		priv_data->gpio_base = NULL;
+	}
+	if (priv_data->rio_base) {
+		iounmap(priv_data->rio_base);
+		priv_data->rio_base = NULL;
+	}
+
+	pm_runtime_disable(&client->dev);
+	if (!pm_runtime_status_suspended(&client->dev))
+		rp2040_gbdg_power_off(priv_data);
+	pm_runtime_set_suspended(&client->dev);
+}
+
+static const struct i2c_device_id rp2040_gbdg_id[] = {
+	{ "rp2040-gpio-bridge", 0 },
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, rp2040_gbdg_id);
+
+static const struct of_device_id rp2040_gbdg_of_match[] = {
+	{ .compatible = "raspberrypi,rp2040-gpio-bridge" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, rp2040_gbdg_of_match);
+
+static int rp2040_gbdg_runtime_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+
+	return rp2040_gbdg_power_off(i2c_get_clientdata(client));
+}
+
+static int rp2040_gbdg_runtime_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+
+	return rp2040_gbdg_power_on(i2c_get_clientdata(client));
+}
+
+static const struct dev_pm_ops rp2040_gbdg_pm_ops = { SET_RUNTIME_PM_OPS(
+	rp2040_gbdg_runtime_suspend, rp2040_gbdg_runtime_resume, NULL) };
+
+static struct i2c_driver rp2040_gbdg_driver = {
+	.driver = {
+		.name = MODULE_NAME,
+		.of_match_table = of_match_ptr(rp2040_gbdg_of_match),
+		.pm = &rp2040_gbdg_pm_ops,
+	},
+	.probe = rp2040_gbdg_probe,
+	.remove = rp2040_gbdg_remove,
+	.id_table = rp2040_gbdg_id,
+};
+
+module_i2c_driver(rp2040_gbdg_driver);
+
+MODULE_AUTHOR("Richard Oliver <richard.oliver@raspberrypi.com>");
+MODULE_DESCRIPTION("Raspberry Pi RP2040 GPIO Bridge");
+MODULE_LICENSE("GPL");
+MODULE_SOFTDEP("pre: md5");
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 0c3200d08fe4..259c22235320 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -3901,6 +3901,7 @@ static int spi_set_cs_timing(struct spi_device *spi)
  */
 int spi_setup(struct spi_device *spi)
 {
+	struct spi_controller *ctlr = spi->controller;
 	unsigned	bad_bits, ugly_bits;
 	int		status;
 
@@ -3927,6 +3928,14 @@ int spi_setup(struct spi_device *spi)
 			"setup: MOSI configured to idle low and high at the same time.\n");
 		return -EINVAL;
 	}
+
+	if (ctlr->use_gpio_descriptors && ctlr->cs_gpiods &&
+	    ctlr->cs_gpiods[spi->chip_select[0]] && !(spi->mode & SPI_CS_HIGH)) {
+		dev_dbg(&spi->dev,
+			"setup: forcing CS_HIGH (use_gpio_descriptors)\n");
+		spi->mode |= SPI_CS_HIGH;
+	}
+
 	/*
 	 * Help drivers fail *cleanly* when they need options
 	 * that aren't supported with their current controller.
diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c
index 661b84f3ee62..242e0a2649d4 100644
--- a/drivers/spi/spidev.c
+++ b/drivers/spi/spidev.c
@@ -415,7 +415,7 @@ spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
 			}
 
 			if (ctlr->use_gpio_descriptors && spi_get_csgpiod(spi, 0))
-				tmp |= SPI_CS_HIGH;
+				{ /*tmp |= SPI_CS_HIGH;*/ }
 
 			tmp |= spi->mode & ~SPI_MODE_MASK;
 			spi->mode = tmp & SPI_MODE_USER_MASK;
@@ -693,6 +693,7 @@ static const struct spi_device_id spidev_spi_ids[] = {
 	{ .name = "spi-authenta" },
 	{ .name = "em3581" },
 	{ .name = "si3210" },
+	{ .name = "spidev" },
 	{},
 };
 MODULE_DEVICE_TABLE(spi, spidev_spi_ids);
@@ -703,7 +704,7 @@ MODULE_DEVICE_TABLE(spi, spidev_spi_ids);
  */
 static int spidev_of_check(struct device *dev)
 {
-	if (device_property_match_string(dev, "compatible", "spidev") < 0)
+	if (1 || device_property_match_string(dev, "compatible", "spidev") < 0)
 		return 0;
 
 	dev_err(dev, "spidev listed directly in DT is not supported\n");
diff --git a/drivers/staging/fbtft/fb_st7735r.c b/drivers/staging/fbtft/fb_st7735r.c
index 9670a8989b91..a04365d1c75e 100644
--- a/drivers/staging/fbtft/fb_st7735r.c
+++ b/drivers/staging/fbtft/fb_st7735r.c
@@ -16,6 +16,10 @@
 #define DEFAULT_GAMMA   "0F 1A 0F 18 2F 28 20 22 1F 1B 23 37 00 07 02 10\n" \
 			"0F 1B 0F 17 33 2C 29 2E 30 30 39 3F 00 07 03 10"
 
+#define ADAFRUIT18_GAMMA \
+			"02 1c 07 12 37 32 29 2d 29 25 2B 39 00 01 03 10\n" \
+			"03 1d 07 06 2E 2C 29 2D 2E 2E 37 3F 00 00 02 10"
+
 static const s16 default_init_sequence[] = {
 	-1, MIPI_DCS_SOFT_RESET,
 	-2, 150,                               /* delay */
@@ -94,6 +98,14 @@ static void set_addr_win(struct fbtft_par *par, int xs, int ys, int xe, int ye)
 	write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
 }
 
+static void adafruit18_green_tab_set_addr_win(struct fbtft_par *par,
+					      int xs, int ys, int xe, int ye)
+{
+	write_reg(par, 0x2A, 0, xs + 2, 0, xe + 2);
+	write_reg(par, 0x2B, 0, ys + 1, 0, ye + 1);
+	write_reg(par, 0x2C);
+}
+
 #define MY BIT(7)
 #define MX BIT(6)
 #define MV BIT(5)
@@ -174,12 +186,36 @@ static struct fbtft_display display = {
 	},
 };
 
-FBTFT_REGISTER_DRIVER(DRVNAME, "sitronix,st7735r", &display);
+static int variant_adafruit18(struct fbtft_display *display)
+{
+	display->gamma = ADAFRUIT18_GAMMA;
+	return 0;
+}
+
+static int variant_adafruit18_green(struct fbtft_display *display)
+{
+	display->gamma = ADAFRUIT18_GAMMA;
+	display->fbtftops.set_addr_win = adafruit18_green_tab_set_addr_win;
+	return 0;
+}
+
+FBTFT_REGISTER_DRIVER_START(&display)
+FBTFT_COMPATIBLE("sitronix,st7735r")
+FBTFT_COMPATIBLE("fbtft,sainsmart18")
+FBTFT_VARIANT_COMPATIBLE("fbtft,adafruit18", variant_adafruit18)
+FBTFT_VARIANT_COMPATIBLE("fbtft,adafruit18_green", variant_adafruit18_green)
+FBTFT_REGISTER_DRIVER_END(DRVNAME, &display);
 
 MODULE_ALIAS("spi:" DRVNAME);
 MODULE_ALIAS("platform:" DRVNAME);
 MODULE_ALIAS("spi:st7735r");
 MODULE_ALIAS("platform:st7735r");
+MODULE_ALIAS("spi:sainsmart18");
+MODULE_ALIAS("platform:sainsmart");
+MODULE_ALIAS("spi:adafruit18");
+MODULE_ALIAS("platform:adafruit18");
+MODULE_ALIAS("spi:adafruit18_green");
+MODULE_ALIAS("platform:adafruit18_green");
 
 MODULE_DESCRIPTION("FB driver for the ST7735R LCD Controller");
 MODULE_AUTHOR("Noralf Tronnes");
diff --git a/drivers/staging/fbtft/fb_st7789v.c b/drivers/staging/fbtft/fb_st7789v.c
index 861a154144e6..ddc1452c2953 100644
--- a/drivers/staging/fbtft/fb_st7789v.c
+++ b/drivers/staging/fbtft/fb_st7789v.c
@@ -75,6 +75,11 @@ enum st7789v_command {
 
 static struct completion panel_te; /* completion for panel TE line */
 static int irq_te; /* Linux IRQ for LCD TE line */
+static u32 col_offset = 0;
+static u32 row_offset = 0;
+static u8 col_hack_fix_offset = 0;
+static short x_offset = 0;
+static short y_offset = 0;
 
 static irqreturn_t panel_te_handler(int irq, void *data)
 {
@@ -261,6 +266,22 @@ static int write_vmem(struct fbtft_par *par, size_t offset, size_t len)
 	return ret;
 }
 
+static void minipitft13_set_addr_win(struct fbtft_par *par, int xs, int ys,
+				     int xe, int ye)
+{
+	xs += x_offset;
+	xe += x_offset;
+	ys += y_offset;
+	ye += y_offset;
+	write_reg(par, MIPI_DCS_SET_COLUMN_ADDRESS,
+		  xs >> 8, xs & 0xFF, xe >> 8, xe & 0xFF);
+
+	write_reg(par, MIPI_DCS_SET_PAGE_ADDRESS,
+		  ys >> 8, ys & 0xFF, ye >> 8, ye & 0xFF);
+
+	write_reg(par, MIPI_DCS_WRITE_MEMORY_START);
+}
+
 /**
  * set_var() - apply LCD properties like rotation and BGR mode
  *
@@ -271,20 +292,32 @@ static int write_vmem(struct fbtft_par *par, size_t offset, size_t len)
 static int set_var(struct fbtft_par *par)
 {
 	u8 madctl_par = 0;
+	struct fbtft_display *display = &par->pdata->display;
+	u32 width = display->width;
+	u32 height = display->height;
 
 	if (par->bgr)
 		madctl_par |= MADCTL_BGR;
 	switch (par->info->var.rotate) {
 	case 0:
+		x_offset = 0;
+		y_offset = 0;
 		break;
 	case 90:
 		madctl_par |= (MADCTL_MV | MADCTL_MY);
+		x_offset = (320 - height) - row_offset;
+		y_offset = (240 - width) - col_offset;
 		break;
 	case 180:
 		madctl_par |= (MADCTL_MX | MADCTL_MY);
+		x_offset = (240 - width) - col_offset + col_hack_fix_offset;
+		// hack tweak to account for extra pixel width to make even
+		y_offset = (320 - height) - row_offset;
 		break;
 	case 270:
 		madctl_par |= (MADCTL_MV | MADCTL_MX);
+		x_offset = row_offset;
+		y_offset = col_offset;
 		break;
 	default:
 		return -EINVAL;
@@ -382,7 +415,16 @@ static struct fbtft_display display = {
 	},
 };
 
-FBTFT_REGISTER_DRIVER(DRVNAME, "sitronix,st7789v", &display);
+static int variant_minipitft13(struct fbtft_display *display)
+{
+	display->fbtftops.set_addr_win = minipitft13_set_addr_win;
+	return 0;
+}
+
+FBTFT_REGISTER_DRIVER_START(&display)
+FBTFT_COMPATIBLE("sitronix,st7789v")
+FBTFT_VARIANT_COMPATIBLE("fbtft,minipitft13", variant_minipitft13)
+FBTFT_REGISTER_DRIVER_END(DRVNAME, &display);
 
 MODULE_ALIAS("spi:" DRVNAME);
 MODULE_ALIAS("platform:" DRVNAME);
diff --git a/drivers/staging/fbtft/fbtft-core.c b/drivers/staging/fbtft/fbtft-core.c
index 69649c0ef873..eea64fdc739f 100644
--- a/drivers/staging/fbtft/fbtft-core.c
+++ b/drivers/staging/fbtft/fbtft-core.c
@@ -24,6 +24,8 @@
 #include <linux/platform_device.h>
 #include <linux/property.h>
 #include <linux/spinlock.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
 
 #include <video/mipi_display.h>
 
@@ -1132,6 +1134,7 @@ static struct fbtft_platform_data *fbtft_properties_read(struct device *dev)
  * @display: Display properties
  * @sdev: SPI device
  * @pdev: Platform device
+ * @dt_ids: Compatible string table
  *
  * Allocates, initializes and registers a framebuffer
  *
@@ -1141,12 +1144,15 @@ static struct fbtft_platform_data *fbtft_properties_read(struct device *dev)
  */
 int fbtft_probe_common(struct fbtft_display *display,
 		       struct spi_device *sdev,
-		       struct platform_device *pdev)
+		       struct platform_device *pdev,
+		       const struct of_device_id *dt_ids)
 {
 	struct device *dev;
 	struct fb_info *info;
 	struct fbtft_par *par;
 	struct fbtft_platform_data *pdata;
+	const struct of_device_id *match;
+	int (*variant)(struct fbtft_display *);
 	int ret;
 
 	if (sdev)
@@ -1159,6 +1165,14 @@ int fbtft_probe_common(struct fbtft_display *display,
 		pdata = fbtft_properties_read(dev);
 		if (IS_ERR(pdata))
 			return PTR_ERR(pdata);
+		match = of_match_device(dt_ids, dev);
+		if (match && match->data) {
+			/* apply the variant */
+			variant = match->data;
+			ret = (*variant)(display);
+			if (ret)
+				return ret;
+		}
 	}
 
 	info = fbtft_framebuffer_alloc(display, dev, pdata);
diff --git a/drivers/staging/fbtft/fbtft.h b/drivers/staging/fbtft/fbtft.h
index 3e00a26a29d5..7a8fdc487756 100644
--- a/drivers/staging/fbtft/fbtft.h
+++ b/drivers/staging/fbtft/fbtft.h
@@ -253,7 +253,8 @@ void fbtft_register_backlight(struct fbtft_par *par);
 void fbtft_unregister_backlight(struct fbtft_par *par);
 int fbtft_init_display(struct fbtft_par *par);
 int fbtft_probe_common(struct fbtft_display *display, struct spi_device *sdev,
-		       struct platform_device *pdev);
+		       struct platform_device *pdev,
+		       const struct of_device_id *dt_ids);
 void fbtft_remove_common(struct device *dev, struct fb_info *info);
 
 /* fbtft-io.c */
@@ -274,42 +275,25 @@ void fbtft_write_reg8_bus9(struct fbtft_par *par, int len, ...);
 void fbtft_write_reg16_bus8(struct fbtft_par *par, int len, ...);
 void fbtft_write_reg16_bus16(struct fbtft_par *par, int len, ...);
 
-#define FBTFT_DT_TABLE(_compatible)						\
-static const struct of_device_id dt_ids[] = {					\
-	{ .compatible = _compatible },						\
-	{},									\
-};										\
-MODULE_DEVICE_TABLE(of, dt_ids);
-
-#define FBTFT_SPI_DRIVER(_name, _compatible, _display, _spi_ids)		\
-										\
-static int fbtft_driver_probe_spi(struct spi_device *spi)			\
-{										\
-	return fbtft_probe_common(_display, spi, NULL);				\
-}										\
-										\
-static void fbtft_driver_remove_spi(struct spi_device *spi)			\
-{										\
-	struct fb_info *info = spi_get_drvdata(spi);				\
-										\
-	fbtft_remove_common(&spi->dev, info);					\
-}										\
-										\
-static struct spi_driver fbtft_driver_spi_driver = {				\
-	.driver = {								\
-		.name = _name,							\
-		.of_match_table = dt_ids,					\
-	},									\
-	.id_table = _spi_ids,							\
-	.probe = fbtft_driver_probe_spi,					\
-	.remove = fbtft_driver_remove_spi,					\
-};
-
-#define FBTFT_REGISTER_DRIVER(_name, _compatible, _display)                \
+#define FBTFT_REGISTER_DRIVER_START(_display)                              \
+									   \
+static const struct of_device_id dt_ids[];                                 \
+									   \
+static int fbtft_driver_probe_spi(struct spi_device *spi)                  \
+{                                                                          \
+	return fbtft_probe_common(_display, spi, NULL, dt_ids);	           \
+}                                                                          \
+									   \
+static void fbtft_driver_remove_spi(struct spi_device *spi)                 \
+{                                                                          \
+	struct fb_info *info = spi_get_drvdata(spi);                       \
+									   \
+	fbtft_remove_common(&spi->dev, info);                              \
+}                                                                          \
 									   \
 static int fbtft_driver_probe_pdev(struct platform_device *pdev)           \
 {                                                                          \
-	return fbtft_probe_common(_display, NULL, pdev);                   \
+	return fbtft_probe_common(_display, NULL, pdev, dt_ids);           \
 }                                                                          \
 									   \
 static void fbtft_driver_remove_pdev(struct platform_device *pdev)	   \
@@ -319,9 +303,30 @@ static void fbtft_driver_remove_pdev(struct platform_device *pdev)	   \
 	fbtft_remove_common(&pdev->dev, info);                             \
 }                                                                          \
 									   \
-FBTFT_DT_TABLE(_compatible)						   \
+static const struct of_device_id dt_ids[] = {
+
+#define FBTFT_COMPATIBLE(_compatible)                                      \
+	{ .compatible = _compatible },
+
+#define FBTFT_VARIANT_COMPATIBLE(_compatible, _variant)                    \
+	{ .compatible = _compatible, .data = _variant },
+
+#define FBTFT_REGISTER_DRIVER_END(_name, _display)                         \
+									   \
+	{},                                                                \
+};                                                                         \
 									   \
-FBTFT_SPI_DRIVER(_name, _compatible, _display, NULL)			   \
+MODULE_DEVICE_TABLE(of, dt_ids);                                           \
+									   \
+									   \
+static struct spi_driver fbtft_driver_spi_driver = {                       \
+	.driver = {                                                        \
+		.name   = _name,                                           \
+		.of_match_table = dt_ids,                                  \
+	},                                                                 \
+	.probe  = fbtft_driver_probe_spi,                                  \
+	.remove = fbtft_driver_remove_spi,                                 \
+};                                                                         \
 									   \
 static struct platform_driver fbtft_driver_platform_driver = {             \
 	.driver = {                                                        \
@@ -357,18 +362,49 @@ module_exit(fbtft_driver_module_exit);
 
 #define FBTFT_REGISTER_SPI_DRIVER(_name, _comp_vend, _comp_dev, _display)	\
 										\
-FBTFT_DT_TABLE(_comp_vend "," _comp_dev)					\
+static const struct of_device_id dt_ids[] = {					\
+	{ .compatible = _comp_vend "," _comp_dev },				\
+	{},									\
+};										\
+										\
+static int fbtft_driver_probe_spi(struct spi_device *spi)			\
+{										\
+	return fbtft_probe_common(_display, spi, NULL, dt_ids);			\
+}										\
+										\
+static void fbtft_driver_remove_spi(struct spi_device *spi)			\
+{										\
+	struct fb_info *info = spi_get_drvdata(spi);				\
+										\
+	fbtft_remove_common(&spi->dev, info);					\
+}										\
+										\
+MODULE_DEVICE_TABLE(of, dt_ids);						\
 										\
 static const struct spi_device_id spi_ids[] = {					\
 	{ .name = _comp_dev },							\
 	{},									\
 };										\
+										\
 MODULE_DEVICE_TABLE(spi, spi_ids);						\
 										\
-FBTFT_SPI_DRIVER(_name, _comp_vend "," _comp_dev, _display, spi_ids)		\
+static struct spi_driver fbtft_driver_spi_driver = {				\
+	.driver = {								\
+		.name  = _name,							\
+		.of_match_table = dt_ids,					\
+	},									\
+	.id_table = spi_ids,							\
+	.probe  = fbtft_driver_probe_spi,					\
+	.remove = fbtft_driver_remove_spi,					\
+};										\
 										\
 module_spi_driver(fbtft_driver_spi_driver);
 
+#define FBTFT_REGISTER_DRIVER(_name, _compatible, _display)                \
+	FBTFT_REGISTER_DRIVER_START(_display)                              \
+	FBTFT_COMPATIBLE(_compatible)                                      \
+	FBTFT_REGISTER_DRIVER_END(_name, _display)
+
 /* Debug macros */
 
 /* shorthand debug levels */
diff --git a/drivers/staging/vc04_services/Kconfig b/drivers/staging/vc04_services/Kconfig
index ccc8e1588648..d3e43aa8a9e2 100644
--- a/drivers/staging/vc04_services/Kconfig
+++ b/drivers/staging/vc04_services/Kconfig
@@ -50,6 +50,10 @@ source "drivers/staging/vc04_services/bcm2835-audio/Kconfig"
 
 source "drivers/staging/vc04_services/bcm2835-camera/Kconfig"
 
+source "drivers/staging/vc04_services/vc-sm-cma/Kconfig"
+source "drivers/staging/vc04_services/bcm2835-codec/Kconfig"
+source "drivers/staging/vc04_services/bcm2835-isp/Kconfig"
+
 source "drivers/staging/vc04_services/vchiq-mmal/Kconfig"
 
 endif
diff --git a/drivers/staging/vc04_services/Makefile b/drivers/staging/vc04_services/Makefile
index dad3789522b8..49baf05fcf1e 100644
--- a/drivers/staging/vc04_services/Makefile
+++ b/drivers/staging/vc04_services/Makefile
@@ -14,4 +14,7 @@ endif
 obj-$(CONFIG_SND_BCM2835)		+= bcm2835-audio/
 obj-$(CONFIG_VIDEO_BCM2835)		+= bcm2835-camera/
 obj-$(CONFIG_BCM2835_VCHIQ_MMAL)	+= vchiq-mmal/
+obj-$(CONFIG_BCM_VC_SM_CMA)		+= vc-sm-cma/
+obj-$(CONFIG_VIDEO_CODEC_BCM2835)	+= bcm2835-codec/
+obj-$(CONFIG_VIDEO_ISP_BCM2835)		+= bcm2835-isp/
 
diff --git a/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c
index 68e8d491a7ec..29e773fdd7ad 100644
--- a/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c
+++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c
@@ -321,10 +321,11 @@ static const struct snd_pcm_ops snd_bcm2835_playback_spdif_ops = {
 
 /* create a pcm device */
 int snd_bcm2835_new_pcm(struct bcm2835_chip *chip, const char *name,
-			int idx, enum snd_bcm2835_route route,
+			enum snd_bcm2835_route route,
 			u32 numchannels, bool spdif)
 {
 	struct snd_pcm *pcm;
+	int idx = chip->index++;
 	int err;
 
 	err = snd_pcm_new(chip->card, name, idx, numchannels, 0, &pcm);
diff --git a/drivers/staging/vc04_services/bcm2835-audio/bcm2835.c b/drivers/staging/vc04_services/bcm2835-audio/bcm2835.c
index b74cb104e9de..0b2e0e8b2eec 100644
--- a/drivers/staging/vc04_services/bcm2835-audio/bcm2835.c
+++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835.c
@@ -5,11 +5,13 @@
 #include <linux/init.h>
 #include <linux/slab.h>
 #include <linux/module.h>
+#include <linux/of.h>
 
 #include "../interface/vchiq_arm/vchiq_bus.h"
 #include "bcm2835.h"
+#include <soc/bcm2835/raspberrypi-firmware.h>
 
-static bool enable_hdmi;
+static bool enable_hdmi, enable_hdmi0, enable_hdmi1;
 static bool enable_headphones = true;
 static int num_channels = MAX_SUBSTREAMS;
 
@@ -65,14 +67,13 @@ static int bcm2835_audio_dual_newpcm(struct bcm2835_chip *chip,
 				     u32 numchannels)
 {
 	int err;
-
-	err = snd_bcm2835_new_pcm(chip, name, 0, route,
+	err = snd_bcm2835_new_pcm(chip, name, route,
 				  numchannels, false);
 
 	if (err)
 		return err;
 
-	err = snd_bcm2835_new_pcm(chip, "IEC958", 1, route, 1, true);
+	err = snd_bcm2835_new_pcm(chip, name, route, 1, true);
 	if (err)
 		return err;
 
@@ -84,20 +85,33 @@ static int bcm2835_audio_simple_newpcm(struct bcm2835_chip *chip,
 				       enum snd_bcm2835_route route,
 				       u32 numchannels)
 {
-	return snd_bcm2835_new_pcm(chip, name, 0, route, numchannels, false);
+	return snd_bcm2835_new_pcm(chip, name, route, numchannels, false);
 }
 
-static struct bcm2835_audio_driver bcm2835_audio_hdmi = {
+static struct bcm2835_audio_driver bcm2835_audio_hdmi0 = {
+	.driver = {
+		.name = "bcm2835_hdmi",
+		.owner = THIS_MODULE,
+	},
+	.shortname = "bcm2835 HDMI 1",
+	.longname  = "bcm2835 HDMI 1",
+	.minchannels = 1,
+	.newpcm = bcm2835_audio_dual_newpcm,
+	.newctl = snd_bcm2835_new_hdmi_ctl,
+	.route = AUDIO_DEST_HDMI0
+};
+
+static struct bcm2835_audio_driver bcm2835_audio_hdmi1 = {
 	.driver = {
 		.name = "bcm2835_hdmi",
 		.owner = THIS_MODULE,
 	},
-	.shortname = "bcm2835 HDMI",
-	.longname  = "bcm2835 HDMI",
+	.shortname = "bcm2835 HDMI 2",
+	.longname  = "bcm2835 HDMI 2",
 	.minchannels = 1,
 	.newpcm = bcm2835_audio_dual_newpcm,
 	.newctl = snd_bcm2835_new_hdmi_ctl,
-	.route = AUDIO_DEST_HDMI
+	.route = AUDIO_DEST_HDMI1
 };
 
 static struct bcm2835_audio_driver bcm2835_audio_headphones = {
@@ -120,8 +134,12 @@ struct bcm2835_audio_drivers {
 
 static struct bcm2835_audio_drivers children_devices[] = {
 	{
-		.audio_driver = &bcm2835_audio_hdmi,
-		.is_enabled = &enable_hdmi,
+		.audio_driver = &bcm2835_audio_hdmi0,
+		.is_enabled = &enable_hdmi0,
+	},
+	{
+		.audio_driver = &bcm2835_audio_hdmi1,
+		.is_enabled = &enable_hdmi1,
 	},
 	{
 		.audio_driver = &bcm2835_audio_headphones,
@@ -268,10 +286,70 @@ static int snd_add_child_devices(struct device *device, u32 numchans)
 	return 0;
 }
 
+static void set_hdmi_enables(struct device *dev)
+{
+	struct device_node *firmware_node;
+	struct rpi_firmware *firmware = NULL;
+	u32 num_displays, i, display_id;
+	int ret;
+
+	firmware_node = of_find_compatible_node(NULL, NULL,
+					"raspberrypi,bcm2835-firmware");
+	if (firmware_node) {
+		firmware = rpi_firmware_get(firmware_node);
+		of_node_put(firmware_node);
+	}
+
+	if (!firmware) {
+		dev_err(dev, "Failed to get fw structure\n");
+		return;
+	}
+
+	ret = rpi_firmware_property(firmware,
+				    RPI_FIRMWARE_FRAMEBUFFER_GET_NUM_DISPLAYS,
+				    &num_displays, sizeof(u32));
+	if (ret) {
+		dev_err(dev, "Failed to get fw property NUM_DISPLAYS\n");
+		goto out_rpi_fw_put;
+	}
+
+	for (i = 0; i < num_displays; i++) {
+		display_id = i;
+		ret = rpi_firmware_property(firmware,
+				RPI_FIRMWARE_FRAMEBUFFER_GET_DISPLAY_ID,
+				&display_id, sizeof(display_id));
+		if (ret) {
+			dev_err(dev, "Failed to get fw property DISPLAY_ID "
+				"(i = %d)\n", i);
+		} else {
+			if (display_id == 2)
+				enable_hdmi0 = true;
+			if (display_id == 7)
+				enable_hdmi1 = true;
+		}
+	}
+
+	if (!enable_hdmi0 && enable_hdmi1) {
+		/* Swap them over and reassign route. This means
+		 * that if we only have one connected, it is always named
+		 *  HDMI1, irrespective of if its on port HDMI0 or HDMI1.
+		 *  This should match with the naming of HDMI ports in DRM
+		 */
+		enable_hdmi0 = true;
+		enable_hdmi1 = false;
+		bcm2835_audio_hdmi0.route = AUDIO_DEST_HDMI1;
+	}
+
+out_rpi_fw_put:
+	rpi_firmware_put(firmware);
+	return;
+}
+
 static int snd_bcm2835_alsa_probe(struct vchiq_device *device)
 {
 	struct device *dev = &device->dev;
 	int err;
+	u32 disable_headphones = 0;
 
 	err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
 	if (err) {
@@ -285,6 +363,17 @@ static int snd_bcm2835_alsa_probe(struct vchiq_device *device)
 			 num_channels);
 	}
 
+	if (enable_hdmi &&
+	    !of_property_read_bool(dev->of_node, "brcm,disable-hdmi"))
+		set_hdmi_enables(dev);
+
+	if (enable_headphones) {
+		of_property_read_u32(dev->of_node,
+				     "brcm,disable-headphones",
+				     &disable_headphones);
+		enable_headphones = !disable_headphones;
+	}
+
 	err = bcm2835_devm_add_vchi_ctx(dev);
 	if (err)
 		return err;
diff --git a/drivers/staging/vc04_services/bcm2835-audio/bcm2835.h b/drivers/staging/vc04_services/bcm2835-audio/bcm2835.h
index 49ec5b496edb..8e0aa1b5a58a 100644
--- a/drivers/staging/vc04_services/bcm2835-audio/bcm2835.h
+++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835.h
@@ -34,7 +34,8 @@ enum {
 enum snd_bcm2835_route {
 	AUDIO_DEST_AUTO = 0,
 	AUDIO_DEST_HEADPHONES = 1,
-	AUDIO_DEST_HDMI = 2,
+	AUDIO_DEST_HDMI0 = 2,
+	AUDIO_DEST_HDMI1 = 3,
 	AUDIO_DEST_MAX,
 };
 
@@ -59,6 +60,7 @@ struct bcm2835_chip {
 	int volume;
 	int dest;
 	int mute;
+	int index;
 
 	unsigned int opened;
 	unsigned int spdif_status;
@@ -85,7 +87,7 @@ struct bcm2835_alsa_stream {
 };
 
 int snd_bcm2835_new_pcm(struct bcm2835_chip *chip, const char *name,
-			int idx, enum snd_bcm2835_route route,
+			enum snd_bcm2835_route route,
 			u32 numchannels, bool spdif);
 
 int snd_bcm2835_new_hdmi_ctl(struct bcm2835_chip *chip);
diff --git a/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.c b/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.c
index e6724329356b..6428cc916d2d 100644
--- a/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.c
+++ b/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.c
@@ -177,7 +177,7 @@ static struct mmal_fmt formats[] = {
 		.ybbp = 1,
 		.remove_padding = true,
 	}, {
-		.fourcc = V4L2_PIX_FMT_BGR32,
+		.fourcc = V4L2_PIX_FMT_BGRX32,
 		.mmal = MMAL_ENCODING_BGRA,
 		.depth = 32,
 		.mmal_component = COMP_CAMERA,
@@ -1445,6 +1445,7 @@ static const struct v4l2_ioctl_ops camera0_ioctl_ops = {
 	.vidioc_querybuf = vb2_ioctl_querybuf,
 	.vidioc_qbuf = vb2_ioctl_qbuf,
 	.vidioc_dqbuf = vb2_ioctl_dqbuf,
+	.vidioc_expbuf = vb2_ioctl_expbuf,
 	.vidioc_enum_framesizes = vidioc_enum_framesizes,
 	.vidioc_enum_frameintervals = vidioc_enum_frameintervals,
 	.vidioc_g_parm        = vidioc_g_parm,
@@ -1726,6 +1727,12 @@ static int mmal_init(struct bcm2835_mmal_dev *dev)
 					      MMAL_PARAMETER_MINIMISE_FRAGMENTATION,
 					      &enable,
 					      sizeof(enable));
+
+		/* Enable inserting headers into the first frame */
+		vchiq_mmal_port_parameter_set(dev->instance,
+					      &dev->component[COMP_VIDEO_ENCODE]->control,
+					      MMAL_PARAMETER_VIDEO_ENCODE_HEADERS_WITH_FRAME,
+					      &enable, sizeof(enable));
 	}
 	ret = bcm2835_mmal_set_all_camera_controls(dev);
 	if (ret < 0) {
@@ -1925,7 +1932,7 @@ static int bcm2835_mmal_probe(struct vchiq_device *device)
 		q = &dev->capture.vb_vidq;
 		memset(q, 0, sizeof(*q));
 		q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
-		q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
+		q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ | VB2_DMABUF;
 		q->drv_priv = dev;
 		q->buf_struct_size = sizeof(struct vb2_mmal_buffer);
 		q->ops = &bcm2835_mmal_video_qops;
diff --git a/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.h b/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.h
index 0f0c6f7a3764..f27cc8e55c80 100644
--- a/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.h
+++ b/drivers/staging/vc04_services/bcm2835-camera/bcm2835-camera.h
@@ -13,7 +13,7 @@
  * core driver device
  */
 
-#define V4L2_CTRL_COUNT 29 /* number of v4l controls */
+#define V4L2_CTRL_COUNT 32 /* number of v4l controls */
 
 enum {
 	COMP_CAMERA = 0,
diff --git a/drivers/staging/vc04_services/bcm2835-camera/controls.c b/drivers/staging/vc04_services/bcm2835-camera/controls.c
index 6bce45925bf1..40753c8fd5f3 100644
--- a/drivers/staging/vc04_services/bcm2835-camera/controls.c
+++ b/drivers/staging/vc04_services/bcm2835-camera/controls.c
@@ -468,6 +468,10 @@ static int ctrl_set_awb_mode(struct bcm2835_mmal_dev *dev,
 	case V4L2_WHITE_BALANCE_SHADE:
 		u32_value = MMAL_PARAM_AWBMODE_SHADE;
 		break;
+
+	case V4L2_WHITE_BALANCE_GREYWORLD:
+		u32_value = MMAL_PARAM_AWBMODE_GREYWORLD;
+		break;
 	}
 
 	return vchiq_mmal_port_parameter_set(dev->instance, control,
@@ -700,6 +704,8 @@ static int ctrl_set_video_encode_profile_level(struct bcm2835_mmal_dev *dev,
 		case V4L2_MPEG_VIDEO_H264_LEVEL_3_1:
 		case V4L2_MPEG_VIDEO_H264_LEVEL_3_2:
 		case V4L2_MPEG_VIDEO_H264_LEVEL_4_0:
+		case V4L2_MPEG_VIDEO_H264_LEVEL_4_1:
+		case V4L2_MPEG_VIDEO_H264_LEVEL_4_2:
 			dev->capture.enc_level = ctrl->val;
 			break;
 		default:
@@ -765,6 +771,17 @@ static int ctrl_set_video_encode_profile_level(struct bcm2835_mmal_dev *dev,
 		case V4L2_MPEG_VIDEO_H264_LEVEL_4_0:
 			param.level = MMAL_VIDEO_LEVEL_H264_4;
 			break;
+		/*
+		 * Note that the hardware spec is level 4.0. Achieving levels
+		 * above that depend on exactly the resolution and frame rate
+		 * being requested.
+		 */
+		case V4L2_MPEG_VIDEO_H264_LEVEL_4_1:
+			param.level = MMAL_VIDEO_LEVEL_H264_41;
+			break;
+		case V4L2_MPEG_VIDEO_H264_LEVEL_4_2:
+			param.level = MMAL_VIDEO_LEVEL_H264_42;
+			break;
 		default:
 			/* Should never get here */
 			break;
@@ -1046,8 +1063,8 @@ static const struct bcm2835_mmal_v4l2_ctrl v4l2_ctrls[V4L2_CTRL_COUNT] = {
 	{
 		.id = V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE,
 		.type = MMAL_CONTROL_TYPE_STD_MENU,
-		.min = ~0x3ff,
-		.max = V4L2_WHITE_BALANCE_SHADE,
+		.min = ~0x7ff,
+		.max = V4L2_WHITE_BALANCE_GREYWORLD,
 		.def = V4L2_WHITE_BALANCE_AUTO,
 		.step = 0,
 		.imenu = NULL,
@@ -1214,8 +1231,10 @@ static const struct bcm2835_mmal_v4l2_ctrl v4l2_ctrls[V4L2_CTRL_COUNT] = {
 			 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_0) |
 			 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_1) |
 			 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_2) |
-			 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_0)),
-		.max = V4L2_MPEG_VIDEO_H264_LEVEL_4_0,
+			 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_0) |
+			 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_1) |
+			 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_2)),
+		.max = V4L2_MPEG_VIDEO_H264_LEVEL_4_2,
 		.def = V4L2_MPEG_VIDEO_H264_LEVEL_4_0,
 		.step = 1,
 		.imenu = NULL,
@@ -1245,6 +1264,39 @@ static const struct bcm2835_mmal_v4l2_ctrl v4l2_ctrls[V4L2_CTRL_COUNT] = {
 		.mmal_id = MMAL_PARAMETER_INTRAPERIOD,
 		.setter = ctrl_set_video_encode_param_output,
 	},
+	{
+		.id = V4L2_CID_MPEG_VIDEO_H264_MIN_QP,
+		.type = MMAL_CONTROL_TYPE_STD,
+		.min = 0,
+		.max = 51,
+		.def = 0,
+		.step = 1,
+		.imenu = NULL,
+		.mmal_id = MMAL_PARAMETER_VIDEO_ENCODE_MIN_QUANT,
+		.setter = ctrl_set_video_encode_param_output,
+	},
+	{
+		.id = V4L2_CID_MPEG_VIDEO_H264_MAX_QP,
+		.type = MMAL_CONTROL_TYPE_STD,
+		.min = 0,
+		.max = 51,
+		.def = 0,
+		.step = 1,
+		.imenu = NULL,
+		.mmal_id = MMAL_PARAMETER_VIDEO_ENCODE_MAX_QUANT,
+		.setter = ctrl_set_video_encode_param_output,
+	},
+	{
+		.id = V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME,
+		.type = MMAL_CONTROL_TYPE_STD,
+		.min = 0,
+		.max = 0,
+		.def = 0,
+		.step = 0,
+		.imenu = NULL,
+		.mmal_id = MMAL_PARAMETER_VIDEO_REQUEST_I_FRAME,
+		.setter = ctrl_set_video_encode_param_output,
+	},
 };
 
 int bcm2835_mmal_set_all_camera_controls(struct bcm2835_mmal_dev *dev)
diff --git a/drivers/staging/vc04_services/bcm2835-codec/Kconfig b/drivers/staging/vc04_services/bcm2835-codec/Kconfig
new file mode 100644
index 000000000000..761c8ba4b40f
--- /dev/null
+++ b/drivers/staging/vc04_services/bcm2835-codec/Kconfig
@@ -0,0 +1,11 @@
+config VIDEO_CODEC_BCM2835
+	tristate "BCM2835 Video codec support"
+	depends on MEDIA_SUPPORT && MEDIA_CONTROLLER
+	depends on VIDEO_DEV && (ARCH_BCM2835 || COMPILE_TEST)
+	select BCM2835_VCHIQ_MMAL
+	select VIDEOBUF2_DMA_CONTIG
+	select V4L2_MEM2MEM_DEV
+	help
+	  Say Y here to enable the V4L2 video codecs for
+	  Broadcom BCM2835 SoC. This operates over the VCHIQ interface
+	  to a service running on VideoCore.
diff --git a/drivers/staging/vc04_services/bcm2835-codec/Makefile b/drivers/staging/vc04_services/bcm2835-codec/Makefile
new file mode 100644
index 000000000000..b2fe5ebb4a5a
--- /dev/null
+++ b/drivers/staging/vc04_services/bcm2835-codec/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+bcm2835-codec-objs := bcm2835-v4l2-codec.o
+
+obj-$(CONFIG_VIDEO_CODEC_BCM2835) += bcm2835-codec.o
+
+ccflags-y += \
+	-D__VCCOREVER__=0x04000000
diff --git a/drivers/staging/vc04_services/bcm2835-codec/TODO b/drivers/staging/vc04_services/bcm2835-codec/TODO
new file mode 100644
index 000000000000..bc27a04ee9bd
--- /dev/null
+++ b/drivers/staging/vc04_services/bcm2835-codec/TODO
@@ -0,0 +1 @@
+No issues. Depends on VCHIQ which is in staging.
\ No newline at end of file
diff --git a/drivers/staging/vc04_services/bcm2835-codec/bcm2835-v4l2-codec.c b/drivers/staging/vc04_services/bcm2835-codec/bcm2835-v4l2-codec.c
new file mode 100644
index 000000000000..de8f68b66c38
--- /dev/null
+++ b/drivers/staging/vc04_services/bcm2835-codec/bcm2835-v4l2-codec.c
@@ -0,0 +1,4026 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * A v4l2-mem2mem device that wraps the video codec MMAL component.
+ *
+ * Copyright 2018 Raspberry Pi (Trading) Ltd.
+ * Author: Dave Stevenson (dave.stevenson@raspberrypi.com)
+ *
+ * Loosely based on the vim2m virtual driver by Pawel Osciak
+ * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd.
+ * Pawel Osciak, <pawel@osciak.com>
+ * Marek Szyprowski, <m.szyprowski@samsung.com>
+ *
+ * Whilst this driver uses the v4l2_mem2mem framework, it does not need the
+ * scheduling aspects, so will always take the buffers, pass them to the VPU,
+ * and then signal the job as complete.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version
+ */
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <linux/timer.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/syscalls.h>
+
+#include <media/v4l2-mem2mem.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "../interface/vchiq_arm/vchiq_bus.h"
+
+#include "../vchiq-mmal/mmal-encodings.h"
+#include "../vchiq-mmal/mmal-msg.h"
+#include "../vchiq-mmal/mmal-parameters.h"
+#include "../vchiq-mmal/mmal-vchiq.h"
+
+MODULE_IMPORT_NS(DMA_BUF);
+
+/*
+ * Default /dev/videoN node numbers for decode and encode.
+ * Deliberately avoid the very low numbers as these are often taken by webcams
+ * etc, and simple apps tend to only go for /dev/video0.
+ */
+static int decode_video_nr = 10;
+module_param(decode_video_nr, int, 0644);
+MODULE_PARM_DESC(decode_video_nr, "decoder video device number");
+
+static int encode_video_nr = 11;
+module_param(encode_video_nr, int, 0644);
+MODULE_PARM_DESC(encode_video_nr, "encoder video device number");
+
+static int isp_video_nr = 12;
+module_param(isp_video_nr, int, 0644);
+MODULE_PARM_DESC(isp_video_nr, "isp video device number");
+
+static int deinterlace_video_nr = 18;
+module_param(deinterlace_video_nr, int, 0644);
+MODULE_PARM_DESC(deinterlace_video_nr, "deinterlace video device number");
+
+static int encode_image_nr = 31;
+module_param(encode_image_nr, int, 0644);
+MODULE_PARM_DESC(encode_image_nr, "encoder image device number");
+
+/*
+ * Workaround for GStreamer v4l2convert component not considering Bayer formats
+ * as raw, and therefore not considering a V4L2 device that supports them as
+ * a suitable candidate.
+ */
+static bool disable_bayer;
+module_param(disable_bayer, bool, 0644);
+MODULE_PARM_DESC(disable_bayer, "Disable support for Bayer formats");
+
+static unsigned int debug;
+module_param(debug, uint, 0644);
+MODULE_PARM_DESC(debug, "activates debug info (0-3)");
+
+static bool advanced_deinterlace = true;
+module_param(advanced_deinterlace, bool, 0644);
+MODULE_PARM_DESC(advanced_deinterlace, "Use advanced deinterlace");
+
+static int field_override;
+module_param(field_override, int, 0644);
+MODULE_PARM_DESC(field_override, "force TB(8)/BT(9) field");
+
+enum bcm2835_codec_role {
+	DECODE,
+	ENCODE,
+	ISP,
+	DEINTERLACE,
+	ENCODE_IMAGE,
+	NUM_ROLES
+};
+
+static const char * const roles[] = {
+	"decode",
+	"encode",
+	"isp",
+	"image_fx",
+	"encode_image",
+};
+
+static const char * const components[] = {
+	"ril.video_decode",
+	"ril.video_encode",
+	"ril.isp",
+	"ril.image_fx",
+	"ril.image_encode",
+};
+
+/* Timeout for stop_streaming to allow all buffers to return */
+#define COMPLETE_TIMEOUT (2 * HZ)
+
+#define MIN_W		32
+#define MIN_H		32
+#define MAX_W_CODEC	1920
+#define MAX_H_CODEC	1920
+#define MAX_W_ISP	16384
+#define MAX_H_ISP	16384
+#define BPL_ALIGN	32
+/*
+ * The decoder spec supports the V4L2_EVENT_SOURCE_CHANGE event, but the docs
+ * seem to want it to always be generated on startup, which prevents the client
+ * from configuring the CAPTURE queue based on any parsing it has already done
+ * which may save time and allow allocation of CAPTURE buffers early. Surely
+ * SOURCE_CHANGE means something has changed, not just "always notify".
+ *
+ * For those clients that don't set the CAPTURE resolution, adopt a default
+ * resolution that is seriously unlikely to be correct, therefore almost
+ * guaranteed to get the SOURCE_CHANGE event.
+ */
+#define DEFAULT_WIDTH	32
+#define DEFAULT_HEIGHT	32
+/*
+ * The unanswered question - what is the maximum size of a compressed frame?
+ * V4L2 mandates that the encoded frame must fit in a single buffer. Sizing
+ * that buffer is a compromise between wasting memory and risking not fitting.
+ * The 1080P version of Big Buck Bunny has some frames that exceed 512kB.
+ * Adopt a moderately arbitrary split at 720P for switching between 512 and
+ * 768kB buffers.
+ */
+#define DEF_COMP_BUF_SIZE_GREATER_720P	(768 << 10)
+#define DEF_COMP_BUF_SIZE_720P_OR_LESS	(512 << 10)
+/* JPEG image can be very large. For paranoid reasons 4MB is used */
+#define DEF_COMP_BUF_SIZE_JPEG (4096 << 10)
+
+/* Flags that indicate a format can be used for capture/output */
+#define MEM2MEM_CAPTURE		BIT(0)
+#define MEM2MEM_OUTPUT		BIT(1)
+
+#define MEM2MEM_NAME		"bcm2835-codec"
+
+struct bcm2835_codec_fmt {
+	u32	fourcc;
+	int	depth;
+	u8	bytesperline_align[NUM_ROLES];
+	u32	flags;
+	u32	mmal_fmt;
+	int	size_multiplier_x2;
+	bool	is_bayer;
+};
+
+static const struct bcm2835_codec_fmt supported_formats[] = {
+	{
+		/* YUV formats */
+		.fourcc			= V4L2_PIX_FMT_YUV420,
+		.depth			= 8,
+		.bytesperline_align	= { 32, 64, 64, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_I420,
+		.size_multiplier_x2	= 3,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_YVU420,
+		.depth			= 8,
+		.bytesperline_align	= { 32, 64, 64, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_YV12,
+		.size_multiplier_x2	= 3,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_NV12,
+		.depth			= 8,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_NV12,
+		.size_multiplier_x2	= 3,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_NV21,
+		.depth			= 8,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_NV21,
+		.size_multiplier_x2	= 3,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_RGB565,
+		.depth			= 16,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_RGB16,
+		.size_multiplier_x2	= 2,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_YUYV,
+		.depth			= 16,
+		.bytesperline_align	= { 64, 64, 64, 64, 64 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_YUYV,
+		.size_multiplier_x2	= 2,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_UYVY,
+		.depth			= 16,
+		.bytesperline_align	= { 64, 64, 64, 64, 64 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_UYVY,
+		.size_multiplier_x2	= 2,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_YVYU,
+		.depth			= 16,
+		.bytesperline_align	= { 64, 64, 64, 64, 64 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_YVYU,
+		.size_multiplier_x2	= 2,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_VYUY,
+		.depth			= 16,
+		.bytesperline_align	= { 64, 64, 64, 64, 64 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_VYUY,
+		.size_multiplier_x2	= 2,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_NV12_COL128,
+		.depth			= 8,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_YUVUV128,
+		.size_multiplier_x2	= 3,
+	}, {
+		/* RGB formats */
+		.fourcc			= V4L2_PIX_FMT_RGB24,
+		.depth			= 24,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_RGB24,
+		.size_multiplier_x2	= 2,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_BGR24,
+		.depth			= 24,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BGR24,
+		.size_multiplier_x2	= 2,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_BGR32,
+		.depth			= 32,
+		.bytesperline_align	= { 64, 64, 64, 64, 64 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BGRA,
+		.size_multiplier_x2	= 2,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_RGBA32,
+		.depth			= 32,
+		.bytesperline_align	= { 64, 64, 64, 64, 64 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_RGBA,
+		.size_multiplier_x2	= 2,
+	}, {
+		/* Bayer formats */
+		/* 8 bit */
+		.fourcc			= V4L2_PIX_FMT_SRGGB8,
+		.depth			= 8,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SRGGB8,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SBGGR8,
+		.depth			= 8,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SBGGR8,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SGRBG8,
+		.depth			= 8,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SGRBG8,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SGBRG8,
+		.depth			= 8,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SGBRG8,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		/* 10 bit */
+		.fourcc			= V4L2_PIX_FMT_SRGGB10P,
+		.depth			= 10,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SRGGB10P,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SBGGR10P,
+		.depth			= 10,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SBGGR10P,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SGRBG10P,
+		.depth			= 10,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SGRBG10P,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SGBRG10P,
+		.depth			= 10,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SGBRG10P,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		/* 12 bit */
+		.fourcc			= V4L2_PIX_FMT_SRGGB12P,
+		.depth			= 12,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SRGGB12P,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SBGGR12P,
+		.depth			= 12,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SBGGR12P,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SGRBG12P,
+		.depth			= 12,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SGRBG12P,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SGBRG12P,
+		.depth			= 12,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SGBRG12P,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		/* 14 bit */
+		.fourcc			= V4L2_PIX_FMT_SRGGB14P,
+		.depth			= 14,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SRGGB14P,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SBGGR14P,
+		.depth			= 14,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SBGGR14P,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SGRBG14P,
+		.depth			= 14,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SGRBG14P,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SGBRG14P,
+		.depth			= 14,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SGBRG14P,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		/* 16 bit */
+		.fourcc			= V4L2_PIX_FMT_SRGGB16,
+		.depth			= 16,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SRGGB16,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SBGGR16,
+		.depth			= 16,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SBGGR16,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SGRBG16,
+		.depth			= 16,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SGRBG16,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SGBRG16,
+		.depth			= 16,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SGBRG16,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		/* Bayer formats unpacked to 16bpp */
+		/* 10 bit */
+		.fourcc			= V4L2_PIX_FMT_SRGGB10,
+		.depth			= 16,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SRGGB10,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SBGGR10,
+		.depth			= 16,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SBGGR10,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SGRBG10,
+		.depth			= 16,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SGRBG10,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SGBRG10,
+		.depth			= 16,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SGBRG10,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		/* 12 bit */
+		.fourcc			= V4L2_PIX_FMT_SRGGB12,
+		.depth			= 16,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SRGGB12,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SBGGR12,
+		.depth			= 16,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SBGGR12,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SGRBG12,
+		.depth			= 16,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SGRBG12,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SGBRG12,
+		.depth			= 16,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SGBRG12,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		/* 14 bit */
+		.fourcc			= V4L2_PIX_FMT_SRGGB14,
+		.depth			= 16,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SRGGB14,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SBGGR14,
+		.depth			= 16,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SBGGR14,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SGRBG14,
+		.depth			= 16,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SGRBG14,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_SGBRG14,
+		.depth			= 16,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_BAYER_SGBRG14,
+		.size_multiplier_x2	= 2,
+		.is_bayer		= true,
+	}, {
+		/* Monochrome MIPI formats */
+		/* 8 bit */
+		.fourcc			= V4L2_PIX_FMT_GREY,
+		.depth			= 8,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_GREY,
+		.size_multiplier_x2	= 2,
+	}, {
+		/* 10 bit */
+		.fourcc			= V4L2_PIX_FMT_Y10P,
+		.depth			= 10,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_Y10P,
+		.size_multiplier_x2	= 2,
+	}, {
+		/* 12 bit */
+		.fourcc			= V4L2_PIX_FMT_Y12P,
+		.depth			= 12,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_Y12P,
+		.size_multiplier_x2	= 2,
+	}, {
+		/* 14 bit */
+		.fourcc			= V4L2_PIX_FMT_Y14P,
+		.depth			= 14,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_Y14P,
+		.size_multiplier_x2	= 2,
+	}, {
+		/* 16 bit */
+		.fourcc			= V4L2_PIX_FMT_Y16,
+		.depth			= 16,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_Y16,
+		.size_multiplier_x2	= 2,
+	}, {
+		/* 10 bit as 16bpp */
+		.fourcc			= V4L2_PIX_FMT_Y10,
+		.depth			= 16,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_Y10,
+		.size_multiplier_x2	= 2,
+	}, {
+		/* 12 bit as 16bpp */
+		.fourcc			= V4L2_PIX_FMT_Y12,
+		.depth			= 16,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_Y12,
+		.size_multiplier_x2	= 2,
+	}, {
+		/* 14 bit as 16bpp */
+		.fourcc			= V4L2_PIX_FMT_Y14,
+		.depth			= 16,
+		.bytesperline_align	= { 32, 32, 32, 32, 32 },
+		.flags			= 0,
+		.mmal_fmt		= MMAL_ENCODING_Y14,
+		.size_multiplier_x2	= 2,
+	}, {
+		/* Compressed formats */
+		.fourcc			= V4L2_PIX_FMT_H264,
+		.depth			= 0,
+		.flags			= V4L2_FMT_FLAG_COMPRESSED,
+		.mmal_fmt		= MMAL_ENCODING_H264,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_JPEG,
+		.depth			= 0,
+		.flags			= V4L2_FMT_FLAG_COMPRESSED,
+		.mmal_fmt		= MMAL_ENCODING_JPEG,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_MJPEG,
+		.depth			= 0,
+		.flags			= V4L2_FMT_FLAG_COMPRESSED,
+		.mmal_fmt		= MMAL_ENCODING_MJPEG,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_MPEG4,
+		.depth			= 0,
+		.flags			= V4L2_FMT_FLAG_COMPRESSED,
+		.mmal_fmt		= MMAL_ENCODING_MP4V,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_H263,
+		.depth			= 0,
+		.flags			= V4L2_FMT_FLAG_COMPRESSED,
+		.mmal_fmt		= MMAL_ENCODING_H263,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_MPEG2,
+		.depth			= 0,
+		.flags			= V4L2_FMT_FLAG_COMPRESSED,
+		.mmal_fmt		= MMAL_ENCODING_MP2V,
+	}, {
+		.fourcc			= V4L2_PIX_FMT_VC1_ANNEX_G,
+		.depth			= 0,
+		.flags			= V4L2_FMT_FLAG_COMPRESSED,
+		.mmal_fmt		= MMAL_ENCODING_WVC1,
+	}
+};
+
+struct bcm2835_codec_fmt_list {
+	struct bcm2835_codec_fmt *list;
+	unsigned int num_entries;
+};
+
+struct m2m_mmal_buffer {
+	struct v4l2_m2m_buffer	m2m;
+	struct mmal_buffer	mmal;
+};
+
+/* Per-queue, driver-specific private data */
+struct bcm2835_codec_q_data {
+	/*
+	 * These parameters should be treated as gospel, with everything else
+	 * being determined from them.
+	 */
+	/* Buffer width/height */
+	unsigned int		bytesperline;
+	unsigned int		height;
+	/* Crop size used for selection handling */
+	unsigned int		crop_width;
+	unsigned int		crop_height;
+	bool			selection_set;
+	struct v4l2_fract	aspect_ratio;
+	enum v4l2_field		field;
+
+	unsigned int		sizeimage;
+	unsigned int		sequence;
+	struct bcm2835_codec_fmt	*fmt;
+
+	/* One extra buffer header so we can send an EOS. */
+	struct m2m_mmal_buffer	eos_buffer;
+	bool			eos_buffer_in_use;	/* debug only */
+};
+
+struct bcm2835_codec_dev {
+	struct vchiq_device    *device;
+
+	/* v4l2 devices */
+	struct v4l2_device	v4l2_dev;
+	struct video_device	vfd;
+	/* mutex for the v4l2 device */
+	struct mutex		dev_mutex;
+	atomic_t		num_inst;
+
+	/* allocated mmal instance and components */
+	enum bcm2835_codec_role	role;
+	/* The list of formats supported on input and output queues. */
+	struct bcm2835_codec_fmt_list	supported_fmts[2];
+
+	/*
+	 * Max size supported varies based on role. Store during
+	 * bcm2835_codec_create for use later.
+	 */
+	unsigned int max_w;
+	unsigned int max_h;
+
+	struct vchiq_mmal_instance	*instance;
+
+	struct v4l2_m2m_dev	*m2m_dev;
+};
+
+struct bcm2835_codec_ctx {
+	struct v4l2_fh		fh;
+	struct bcm2835_codec_dev	*dev;
+
+	struct v4l2_ctrl_handler hdl;
+	struct v4l2_ctrl *gop_size;
+
+	struct vchiq_mmal_component  *component;
+	bool component_enabled;
+
+	enum v4l2_colorspace	colorspace;
+	enum v4l2_ycbcr_encoding ycbcr_enc;
+	enum v4l2_xfer_func	xfer_func;
+	enum v4l2_quantization	quant;
+
+	int hflip;
+	int vflip;
+
+	/* Source and destination queue data */
+	struct bcm2835_codec_q_data   q_data[2];
+	s32  bitrate;
+	unsigned int	framerate_num;
+	unsigned int	framerate_denom;
+
+	bool aborting;
+	int num_ip_buffers;
+	int num_op_buffers;
+	struct completion frame_cmplt;
+};
+
+struct bcm2835_codec_driver {
+	struct vchiq_device    *device;
+	struct media_device	mdev;
+
+	struct bcm2835_codec_dev *encode;
+	struct bcm2835_codec_dev *decode;
+	struct bcm2835_codec_dev *isp;
+	struct bcm2835_codec_dev *deinterlace;
+	struct bcm2835_codec_dev *encode_image;
+};
+
+enum {
+	V4L2_M2M_SRC = 0,
+	V4L2_M2M_DST = 1,
+};
+
+static const struct bcm2835_codec_fmt *get_fmt(u32 mmal_fmt)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(supported_formats); i++) {
+		if (supported_formats[i].mmal_fmt == mmal_fmt &&
+		    (!disable_bayer || !supported_formats[i].is_bayer))
+			return &supported_formats[i];
+	}
+	return NULL;
+}
+
+static inline
+struct bcm2835_codec_fmt_list *get_format_list(struct bcm2835_codec_dev *dev,
+					       bool capture)
+{
+	return &dev->supported_fmts[capture ? 1 : 0];
+}
+
+static
+struct bcm2835_codec_fmt *get_default_format(struct bcm2835_codec_dev *dev,
+					     bool capture)
+{
+	return &dev->supported_fmts[capture ? 1 : 0].list[0];
+}
+
+static
+struct bcm2835_codec_fmt *find_format_pix_fmt(u32 pix_fmt,
+					      struct bcm2835_codec_dev *dev,
+					      bool capture)
+{
+	struct bcm2835_codec_fmt *fmt;
+	unsigned int k;
+	struct bcm2835_codec_fmt_list *fmts =
+					&dev->supported_fmts[capture ? 1 : 0];
+
+	for (k = 0; k < fmts->num_entries; k++) {
+		fmt = &fmts->list[k];
+		if (fmt->fourcc == pix_fmt)
+			break;
+	}
+	if (k == fmts->num_entries)
+		return NULL;
+
+	return &fmts->list[k];
+}
+
+static inline
+struct bcm2835_codec_fmt *find_format(struct v4l2_format *f,
+				      struct bcm2835_codec_dev *dev,
+				      bool capture)
+{
+	return find_format_pix_fmt(f->fmt.pix_mp.pixelformat, dev, capture);
+}
+
+static inline struct bcm2835_codec_ctx *file2ctx(struct file *file)
+{
+	return container_of(file->private_data, struct bcm2835_codec_ctx, fh);
+}
+
+static struct bcm2835_codec_q_data *get_q_data(struct bcm2835_codec_ctx *ctx,
+					       enum v4l2_buf_type type)
+{
+	switch (type) {
+	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+		return &ctx->q_data[V4L2_M2M_SRC];
+	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+		return &ctx->q_data[V4L2_M2M_DST];
+	default:
+		v4l2_err(&ctx->dev->v4l2_dev, "%s: Invalid queue type %u\n",
+			 __func__, type);
+		break;
+	}
+	return NULL;
+}
+
+static struct vchiq_mmal_port *get_port_data(struct bcm2835_codec_ctx *ctx,
+					     enum v4l2_buf_type type)
+{
+	if (!ctx->component)
+		return NULL;
+
+	switch (type) {
+	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
+		return &ctx->component->input[0];
+	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
+		return &ctx->component->output[0];
+	default:
+		v4l2_err(&ctx->dev->v4l2_dev, "%s: Invalid queue type %u\n",
+			 __func__, type);
+		break;
+	}
+	return NULL;
+}
+
+/*
+ * mem2mem callbacks
+ */
+
+/*
+ * job_ready() - check whether an instance is ready to be scheduled to run
+ */
+static int job_ready(void *priv)
+{
+	struct bcm2835_codec_ctx *ctx = priv;
+
+	if (!v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx) &&
+	    !v4l2_m2m_num_dst_bufs_ready(ctx->fh.m2m_ctx))
+		return 0;
+
+	return 1;
+}
+
+static void job_abort(void *priv)
+{
+	struct bcm2835_codec_ctx *ctx = priv;
+
+	v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s\n", __func__);
+	/* Will cancel the transaction in the next interrupt handler */
+	ctx->aborting = 1;
+}
+
+static inline unsigned int get_sizeimage(int bpl, int width, int height,
+					 struct bcm2835_codec_fmt *fmt)
+{
+	if (fmt->flags & V4L2_FMT_FLAG_COMPRESSED) {
+		if (fmt->fourcc == V4L2_PIX_FMT_JPEG)
+			return DEF_COMP_BUF_SIZE_JPEG;
+
+		if (width * height > 1280 * 720)
+			return DEF_COMP_BUF_SIZE_GREATER_720P;
+		else
+			return DEF_COMP_BUF_SIZE_720P_OR_LESS;
+	}
+
+	if (fmt->fourcc != V4L2_PIX_FMT_NV12_COL128)
+		return (bpl * height * fmt->size_multiplier_x2) >> 1;
+
+	/*
+	 * V4L2_PIX_FMT_NV12_COL128 is 128 pixel wide columns.
+	 * bytesperline is the column stride in lines, so multiply by
+	 * the number of columns and 128.
+	 */
+	return (ALIGN(width, 128) * bpl);
+}
+
+static inline unsigned int get_bytesperline(int width, int height,
+					    struct bcm2835_codec_fmt *fmt,
+					    enum bcm2835_codec_role role)
+{
+	if (fmt->fourcc != V4L2_PIX_FMT_NV12_COL128)
+		return ALIGN((width * fmt->depth) >> 3,
+			     fmt->bytesperline_align[role]);
+
+	/*
+	 * V4L2_PIX_FMT_NV12_COL128 passes the column stride in lines via
+	 * bytesperline.
+	 * The minimum value for this is sufficient for the base luma and chroma
+	 * with no padding.
+	 */
+	return (height * 3) >> 1;
+}
+
+static void setup_mmal_port_format(struct bcm2835_codec_ctx *ctx,
+				   struct bcm2835_codec_q_data *q_data,
+				   struct vchiq_mmal_port *port)
+{
+	port->format.encoding = q_data->fmt->mmal_fmt;
+	port->format.flags = 0;
+
+	if (!(q_data->fmt->flags & V4L2_FMT_FLAG_COMPRESSED)) {
+		if (q_data->fmt->mmal_fmt != MMAL_ENCODING_YUVUV128) {
+			/* Raw image format - set width/height */
+			port->es.video.width = (q_data->bytesperline << 3) /
+							q_data->fmt->depth;
+			port->es.video.height = q_data->height;
+			port->es.video.crop.width = q_data->crop_width;
+			port->es.video.crop.height = q_data->crop_height;
+		} else {
+			/* NV12_COL128 / YUVUV128 column format */
+			/* Column stride in lines */
+			port->es.video.width = q_data->bytesperline;
+			port->es.video.height = q_data->height;
+			port->es.video.crop.width = q_data->crop_width;
+			port->es.video.crop.height = q_data->crop_height;
+			port->format.flags = MMAL_ES_FORMAT_FLAG_COL_FMTS_WIDTH_IS_COL_STRIDE;
+		}
+		port->es.video.frame_rate.numerator = ctx->framerate_num;
+		port->es.video.frame_rate.denominator = ctx->framerate_denom;
+	} else {
+		/* Compressed format - leave resolution as 0 for decode */
+		if (ctx->dev->role == DECODE) {
+			port->es.video.width = 0;
+			port->es.video.height = 0;
+			port->es.video.crop.width = 0;
+			port->es.video.crop.height = 0;
+		} else {
+			port->es.video.width = q_data->crop_width;
+			port->es.video.height = q_data->height;
+			port->es.video.crop.width = q_data->crop_width;
+			port->es.video.crop.height = q_data->crop_height;
+			port->format.bitrate = ctx->bitrate;
+			port->es.video.frame_rate.numerator = ctx->framerate_num;
+			port->es.video.frame_rate.denominator = ctx->framerate_denom;
+		}
+	}
+	port->es.video.crop.x = 0;
+	port->es.video.crop.y = 0;
+
+	port->current_buffer.size = q_data->sizeimage;
+};
+
+static void ip_buffer_cb(struct vchiq_mmal_instance *instance,
+			 struct vchiq_mmal_port *port, int status,
+			 struct mmal_buffer *mmal_buf)
+{
+	struct bcm2835_codec_ctx *ctx = port->cb_ctx/*, *curr_ctx*/;
+	struct m2m_mmal_buffer *buf =
+			container_of(mmal_buf, struct m2m_mmal_buffer, mmal);
+
+	v4l2_dbg(2, debug, &ctx->dev->v4l2_dev, "%s: port %p buf %p length %lu, flags %x\n",
+		 __func__, port, mmal_buf, mmal_buf->length,
+		 mmal_buf->mmal_flags);
+
+	if (buf == &ctx->q_data[V4L2_M2M_SRC].eos_buffer) {
+		/* Do we need to add lcoking to prevent multiple submission of
+		 * the EOS, and therefore handle mutliple return here?
+		 */
+		v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: eos buffer returned.\n",
+			 __func__);
+		ctx->q_data[V4L2_M2M_SRC].eos_buffer_in_use = false;
+		return;
+	}
+
+	if (status) {
+		/* error in transfer */
+		if (buf)
+			/* there was a buffer with the error so return it */
+			vb2_buffer_done(&buf->m2m.vb.vb2_buf,
+					VB2_BUF_STATE_ERROR);
+		return;
+	}
+	if (mmal_buf->cmd) {
+		v4l2_err(&ctx->dev->v4l2_dev, "%s: Not expecting cmd msgs on ip callback - %08x\n",
+			 __func__, mmal_buf->cmd);
+		/*
+		 * CHECKME: Should we return here. The buffer shouldn't have a
+		 * message context or vb2 buf associated.
+		 */
+	}
+
+	v4l2_dbg(3, debug, &ctx->dev->v4l2_dev, "%s: no error. Return buffer %p\n",
+		 __func__, &buf->m2m.vb.vb2_buf);
+	vb2_buffer_done(&buf->m2m.vb.vb2_buf,
+			port->enabled ? VB2_BUF_STATE_DONE :
+					VB2_BUF_STATE_QUEUED);
+
+	ctx->num_ip_buffers++;
+	v4l2_dbg(2, debug, &ctx->dev->v4l2_dev, "%s: done %d input buffers\n",
+		 __func__, ctx->num_ip_buffers);
+
+	if (!port->enabled && atomic_read(&port->buffers_with_vpu))
+		complete(&ctx->frame_cmplt);
+}
+
+static void queue_res_chg_event(struct bcm2835_codec_ctx *ctx)
+{
+	static const struct v4l2_event ev_src_ch = {
+		.type = V4L2_EVENT_SOURCE_CHANGE,
+		.u.src_change.changes =
+		V4L2_EVENT_SRC_CH_RESOLUTION,
+	};
+
+	v4l2_event_queue_fh(&ctx->fh, &ev_src_ch);
+}
+
+static void send_eos_event(struct bcm2835_codec_ctx *ctx)
+{
+	static const struct v4l2_event ev = {
+		.type = V4L2_EVENT_EOS,
+	};
+
+	v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "Sending EOS event\n");
+
+	v4l2_event_queue_fh(&ctx->fh, &ev);
+}
+
+static void color_mmal2v4l(struct bcm2835_codec_ctx *ctx, u32 encoding,
+			   u32 color_space)
+{
+	int is_rgb;
+
+	switch (encoding) {
+	case MMAL_ENCODING_I420:
+	case MMAL_ENCODING_YV12:
+	case MMAL_ENCODING_NV12:
+	case MMAL_ENCODING_NV21:
+	case V4L2_PIX_FMT_YUYV:
+	case V4L2_PIX_FMT_YVYU:
+	case V4L2_PIX_FMT_UYVY:
+	case V4L2_PIX_FMT_VYUY:
+		/* YUV based colourspaces */
+		switch (color_space) {
+		case MMAL_COLOR_SPACE_ITUR_BT601:
+			ctx->colorspace = V4L2_COLORSPACE_SMPTE170M;
+			break;
+
+		case MMAL_COLOR_SPACE_ITUR_BT709:
+			ctx->colorspace = V4L2_COLORSPACE_REC709;
+			break;
+		default:
+			break;
+		}
+		break;
+	default:
+		/* RGB based colourspaces */
+		ctx->colorspace = V4L2_COLORSPACE_SRGB;
+		break;
+	}
+	ctx->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(ctx->colorspace);
+	ctx->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(ctx->colorspace);
+	is_rgb = ctx->colorspace == V4L2_COLORSPACE_SRGB;
+	ctx->quant = V4L2_MAP_QUANTIZATION_DEFAULT(is_rgb, ctx->colorspace,
+						   ctx->ycbcr_enc);
+}
+
+static void handle_fmt_changed(struct bcm2835_codec_ctx *ctx,
+			       struct mmal_buffer *mmal_buf)
+{
+	struct bcm2835_codec_q_data *q_data;
+	struct mmal_msg_event_format_changed *format =
+		(struct mmal_msg_event_format_changed *)mmal_buf->buffer;
+	struct mmal_parameter_video_interlace_type interlace;
+	int interlace_size = sizeof(interlace);
+	struct vb2_queue *vq;
+	int ret;
+
+	v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: Format changed: buff size min %u, rec %u, buff num min %u, rec %u\n",
+		 __func__,
+		 format->buffer_size_min,
+		 format->buffer_size_recommended,
+		 format->buffer_num_min,
+		 format->buffer_num_recommended
+		);
+	if (format->format.type != MMAL_ES_TYPE_VIDEO) {
+		v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: Format changed but not video %u\n",
+			 __func__, format->format.type);
+		return;
+	}
+	v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: Format changed to %ux%u, crop %ux%u, colourspace %08X\n",
+		 __func__, format->es.video.width, format->es.video.height,
+		 format->es.video.crop.width, format->es.video.crop.height,
+		 format->es.video.color_space);
+
+	q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+	v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: Format was %ux%u, crop %ux%u\n",
+		 __func__, q_data->bytesperline, q_data->height,
+		 q_data->crop_width, q_data->crop_height);
+
+	q_data->crop_width = format->es.video.crop.width;
+	q_data->crop_height = format->es.video.crop.height;
+	/*
+	 * Stop S_FMT updating crop_height should it be unaligned.
+	 * Client can still update the crop region via S_SELECTION should it
+	 * really want to, but the decoder is likely to complain that the
+	 * format then doesn't match.
+	 */
+	q_data->selection_set = true;
+	q_data->bytesperline = get_bytesperline(format->es.video.width,
+						format->es.video.height,
+						q_data->fmt, ctx->dev->role);
+
+	q_data->height = format->es.video.height;
+	q_data->sizeimage = format->buffer_size_min;
+	if (format->es.video.color_space)
+		color_mmal2v4l(ctx, format->format.encoding,
+			       format->es.video.color_space);
+
+	q_data->aspect_ratio.numerator = format->es.video.par.numerator;
+	q_data->aspect_ratio.denominator = format->es.video.par.denominator;
+
+	ret = vchiq_mmal_port_parameter_get(ctx->dev->instance,
+					    &ctx->component->output[0],
+					    MMAL_PARAMETER_VIDEO_INTERLACE_TYPE,
+					    &interlace,
+					    &interlace_size);
+	if (!ret) {
+		switch (interlace.mode) {
+		case MMAL_INTERLACE_PROGRESSIVE:
+		default:
+			q_data->field = V4L2_FIELD_NONE;
+			break;
+		case MMAL_INTERLACE_FIELDS_INTERLEAVED_UPPER_FIRST:
+			q_data->field = V4L2_FIELD_INTERLACED_TB;
+			break;
+		case MMAL_INTERLACE_FIELDS_INTERLEAVED_LOWER_FIRST:
+			q_data->field = V4L2_FIELD_INTERLACED_BT;
+			break;
+		}
+		v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: interlace mode %u, v4l2 field %u\n",
+			 __func__, interlace.mode, q_data->field);
+	} else {
+		q_data->field = V4L2_FIELD_NONE;
+	}
+
+	vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+	if (vq->streaming)
+		vq->last_buffer_dequeued = true;
+
+	queue_res_chg_event(ctx);
+}
+
+static void op_buffer_cb(struct vchiq_mmal_instance *instance,
+			 struct vchiq_mmal_port *port, int status,
+			 struct mmal_buffer *mmal_buf)
+{
+	struct bcm2835_codec_ctx *ctx = port->cb_ctx;
+	enum vb2_buffer_state buf_state = VB2_BUF_STATE_DONE;
+	struct m2m_mmal_buffer *buf;
+	struct vb2_v4l2_buffer *vb2;
+
+	v4l2_dbg(2, debug, &ctx->dev->v4l2_dev,
+		 "%s: status:%d, buf:%p, length:%lu, flags %04x, pts %lld\n",
+		 __func__, status, mmal_buf, mmal_buf->length,
+		 mmal_buf->mmal_flags, mmal_buf->pts);
+
+	buf = container_of(mmal_buf, struct m2m_mmal_buffer, mmal);
+	vb2 = &buf->m2m.vb;
+
+	if (status) {
+		/* error in transfer */
+		if (vb2) {
+			/* there was a buffer with the error so return it */
+			vb2_buffer_done(&vb2->vb2_buf, VB2_BUF_STATE_ERROR);
+		}
+		return;
+	}
+
+	if (mmal_buf->cmd) {
+		switch (mmal_buf->cmd) {
+		case MMAL_EVENT_FORMAT_CHANGED:
+		{
+			handle_fmt_changed(ctx, mmal_buf);
+			break;
+		}
+		default:
+			v4l2_err(&ctx->dev->v4l2_dev, "%s: Unexpected event on output callback - %08x\n",
+				 __func__, mmal_buf->cmd);
+			break;
+		}
+		return;
+	}
+
+	v4l2_dbg(3, debug, &ctx->dev->v4l2_dev, "%s: length %lu, flags %x, idx %u\n",
+		 __func__, mmal_buf->length, mmal_buf->mmal_flags,
+		 vb2->vb2_buf.index);
+
+	if (mmal_buf->length == 0) {
+		/* stream ended, or buffer being returned during disable. */
+		v4l2_dbg(2, debug, &ctx->dev->v4l2_dev, "%s: Empty buffer - flags %04x",
+			 __func__, mmal_buf->mmal_flags);
+		if (!(mmal_buf->mmal_flags & MMAL_BUFFER_HEADER_FLAG_EOS)) {
+			if (!port->enabled) {
+				vb2_buffer_done(&vb2->vb2_buf, VB2_BUF_STATE_QUEUED);
+				if (atomic_read(&port->buffers_with_vpu))
+					complete(&ctx->frame_cmplt);
+			} else {
+				vchiq_mmal_submit_buffer(ctx->dev->instance,
+							 &ctx->component->output[0],
+							 mmal_buf);
+			}
+			return;
+		}
+	}
+	if (mmal_buf->mmal_flags & MMAL_BUFFER_HEADER_FLAG_EOS) {
+		/* EOS packet from the VPU */
+		send_eos_event(ctx);
+		vb2->flags |= V4L2_BUF_FLAG_LAST;
+	}
+
+	if (mmal_buf->mmal_flags & MMAL_BUFFER_HEADER_FLAG_CORRUPTED)
+		buf_state = VB2_BUF_STATE_ERROR;
+
+	/* vb2 timestamps in nsecs, mmal in usecs */
+	vb2->vb2_buf.timestamp = mmal_buf->pts * 1000;
+
+	vb2_set_plane_payload(&vb2->vb2_buf, 0, mmal_buf->length);
+	switch (mmal_buf->mmal_flags &
+				(MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED |
+				 MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST)) {
+	case 0:
+	case MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST: /* Bogus */
+		vb2->field = V4L2_FIELD_NONE;
+		break;
+	case MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED:
+		vb2->field = V4L2_FIELD_INTERLACED_BT;
+		break;
+	case (MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED |
+	      MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST):
+		vb2->field = V4L2_FIELD_INTERLACED_TB;
+		break;
+	}
+
+	if (mmal_buf->mmal_flags & MMAL_BUFFER_HEADER_FLAG_KEYFRAME)
+		vb2->flags |= V4L2_BUF_FLAG_KEYFRAME;
+
+	vb2_buffer_done(&vb2->vb2_buf, buf_state);
+	ctx->num_op_buffers++;
+
+	v4l2_dbg(2, debug, &ctx->dev->v4l2_dev, "%s: done %d output buffers\n",
+		 __func__, ctx->num_op_buffers);
+
+	if (!port->enabled && atomic_read(&port->buffers_with_vpu))
+		complete(&ctx->frame_cmplt);
+}
+
+/* vb2_to_mmal_buffer() - converts vb2 buffer header to MMAL
+ *
+ * Copies all the required fields from a VB2 buffer to the MMAL buffer header,
+ * ready for sending to the VPU.
+ */
+static void vb2_to_mmal_buffer(struct m2m_mmal_buffer *buf,
+			       struct vb2_v4l2_buffer *vb2)
+{
+	u64 pts;
+
+	buf->mmal.mmal_flags = 0;
+	if (vb2->flags & V4L2_BUF_FLAG_KEYFRAME)
+		buf->mmal.mmal_flags |= MMAL_BUFFER_HEADER_FLAG_KEYFRAME;
+
+	/*
+	 * Adding this means that the data must be framed correctly as one frame
+	 * per buffer. The underlying decoder has no such requirement, but it
+	 * will reduce latency as the bistream parser will be kicked immediately
+	 * to parse the frame, rather than relying on its own heuristics for
+	 * when to wake up.
+	 */
+	buf->mmal.mmal_flags |= MMAL_BUFFER_HEADER_FLAG_FRAME_END;
+
+	buf->mmal.length = vb2->vb2_buf.planes[0].bytesused;
+	/*
+	 * Minor ambiguity in the V4L2 spec as to whether passing in a 0 length
+	 * buffer, or one with V4L2_BUF_FLAG_LAST set denotes end of stream.
+	 * Handle either.
+	 */
+	if (!buf->mmal.length || vb2->flags & V4L2_BUF_FLAG_LAST)
+		buf->mmal.mmal_flags |= MMAL_BUFFER_HEADER_FLAG_EOS;
+
+	/* vb2 timestamps in nsecs, mmal in usecs */
+	pts = vb2->vb2_buf.timestamp;
+	do_div(pts, 1000);
+	buf->mmal.pts = pts;
+	buf->mmal.dts = MMAL_TIME_UNKNOWN;
+
+	switch (field_override ? field_override : vb2->field) {
+	default:
+	case V4L2_FIELD_NONE:
+		break;
+	case V4L2_FIELD_INTERLACED_BT:
+		buf->mmal.mmal_flags |= MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED;
+		break;
+	case V4L2_FIELD_INTERLACED_TB:
+		buf->mmal.mmal_flags |= MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED |
+					MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST;
+		break;
+	}
+}
+
+/* device_run() - prepares and starts the device
+ *
+ * This simulates all the immediate preparations required before starting
+ * a device. This will be called by the framework when it decides to schedule
+ * a particular instance.
+ */
+static void device_run(void *priv)
+{
+	struct bcm2835_codec_ctx *ctx = priv;
+	struct bcm2835_codec_dev *dev = ctx->dev;
+	struct vb2_v4l2_buffer *src_buf, *dst_buf;
+	struct m2m_mmal_buffer *src_m2m_buf = NULL, *dst_m2m_buf = NULL;
+	struct v4l2_m2m_buffer *m2m;
+	int ret;
+
+	v4l2_dbg(3, debug, &ctx->dev->v4l2_dev, "%s: off we go\n", __func__);
+
+	if (ctx->fh.m2m_ctx->out_q_ctx.q.streaming) {
+		src_buf = v4l2_m2m_buf_remove(&ctx->fh.m2m_ctx->out_q_ctx);
+		if (src_buf) {
+			m2m = container_of(src_buf, struct v4l2_m2m_buffer, vb);
+			src_m2m_buf = container_of(m2m, struct m2m_mmal_buffer,
+						   m2m);
+			vb2_to_mmal_buffer(src_m2m_buf, src_buf);
+
+			ret = vchiq_mmal_submit_buffer(dev->instance,
+						       &ctx->component->input[0],
+						       &src_m2m_buf->mmal);
+			v4l2_dbg(3, debug, &ctx->dev->v4l2_dev,
+				 "%s: Submitted ip buffer len %lu, pts %llu, flags %04x\n",
+				 __func__, src_m2m_buf->mmal.length,
+				 src_m2m_buf->mmal.pts,
+				 src_m2m_buf->mmal.mmal_flags);
+			if (ret)
+				v4l2_err(&ctx->dev->v4l2_dev,
+					 "%s: Failed submitting ip buffer\n",
+					 __func__);
+		}
+	}
+
+	if (ctx->fh.m2m_ctx->cap_q_ctx.q.streaming) {
+		dst_buf = v4l2_m2m_buf_remove(&ctx->fh.m2m_ctx->cap_q_ctx);
+		if (dst_buf) {
+			m2m = container_of(dst_buf, struct v4l2_m2m_buffer, vb);
+			dst_m2m_buf = container_of(m2m, struct m2m_mmal_buffer,
+						   m2m);
+			vb2_to_mmal_buffer(dst_m2m_buf, dst_buf);
+
+			v4l2_dbg(3, debug, &ctx->dev->v4l2_dev,
+				 "%s: Submitted op buffer\n", __func__);
+			ret = vchiq_mmal_submit_buffer(dev->instance,
+						       &ctx->component->output[0],
+						       &dst_m2m_buf->mmal);
+			if (ret)
+				v4l2_err(&ctx->dev->v4l2_dev,
+					 "%s: Failed submitting op buffer\n",
+					 __func__);
+		}
+	}
+
+	v4l2_dbg(3, debug, &ctx->dev->v4l2_dev, "%s: Submitted src %p, dst %p\n",
+		 __func__, src_m2m_buf, dst_m2m_buf);
+
+	/* Complete the job here. */
+	v4l2_m2m_job_finish(ctx->dev->m2m_dev, ctx->fh.m2m_ctx);
+}
+
+/*
+ * video ioctls
+ */
+static int vidioc_querycap(struct file *file, void *priv,
+			   struct v4l2_capability *cap)
+{
+	struct bcm2835_codec_dev *dev = video_drvdata(file);
+
+	strncpy(cap->driver, MEM2MEM_NAME, sizeof(cap->driver) - 1);
+	strncpy(cap->card, dev->vfd.name, sizeof(cap->card) - 1);
+	snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
+		 MEM2MEM_NAME);
+	return 0;
+}
+
+static int enum_fmt(struct v4l2_fmtdesc *f, struct bcm2835_codec_ctx *ctx,
+		    bool capture)
+{
+	struct bcm2835_codec_fmt *fmt;
+	struct bcm2835_codec_fmt_list *fmts =
+					get_format_list(ctx->dev, capture);
+
+	if (f->index < fmts->num_entries) {
+		/* Format found */
+		fmt = &fmts->list[f->index];
+		f->pixelformat = fmt->fourcc;
+		f->flags = fmt->flags;
+		return 0;
+	}
+
+	/* Format not found */
+	return -EINVAL;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
+				   struct v4l2_fmtdesc *f)
+{
+	struct bcm2835_codec_ctx *ctx = file2ctx(file);
+
+	return enum_fmt(f, ctx, true);
+}
+
+static int vidioc_enum_fmt_vid_out(struct file *file, void *priv,
+				   struct v4l2_fmtdesc *f)
+{
+	struct bcm2835_codec_ctx *ctx = file2ctx(file);
+
+	return enum_fmt(f, ctx, false);
+}
+
+static int vidioc_g_fmt(struct bcm2835_codec_ctx *ctx, struct v4l2_format *f)
+{
+	struct vb2_queue *vq;
+	struct bcm2835_codec_q_data *q_data;
+
+	vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
+	if (!vq)
+		return -EINVAL;
+
+	q_data = get_q_data(ctx, f->type);
+
+	f->fmt.pix_mp.width			= q_data->crop_width;
+	f->fmt.pix_mp.height			= q_data->height;
+	f->fmt.pix_mp.pixelformat		= q_data->fmt->fourcc;
+	f->fmt.pix_mp.field			= q_data->field;
+	f->fmt.pix_mp.colorspace		= ctx->colorspace;
+	f->fmt.pix_mp.plane_fmt[0].sizeimage	= q_data->sizeimage;
+	f->fmt.pix_mp.plane_fmt[0].bytesperline	= q_data->bytesperline;
+	f->fmt.pix_mp.num_planes		= 1;
+	f->fmt.pix_mp.ycbcr_enc			= ctx->ycbcr_enc;
+	f->fmt.pix_mp.quantization		= ctx->quant;
+	f->fmt.pix_mp.xfer_func			= ctx->xfer_func;
+
+	memset(f->fmt.pix_mp.plane_fmt[0].reserved, 0,
+	       sizeof(f->fmt.pix_mp.plane_fmt[0].reserved));
+
+	return 0;
+}
+
+static int vidioc_g_fmt_vid_out(struct file *file, void *priv,
+				struct v4l2_format *f)
+{
+	return vidioc_g_fmt(file2ctx(file), f);
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+				struct v4l2_format *f)
+{
+	return vidioc_g_fmt(file2ctx(file), f);
+}
+
+static int vidioc_try_fmt(struct bcm2835_codec_ctx *ctx, struct v4l2_format *f,
+			  struct bcm2835_codec_fmt *fmt)
+{
+	unsigned int sizeimage, min_bytesperline;
+
+	/*
+	 * The V4L2 specification requires the driver to correct the format
+	 * struct if any of the dimensions is unsupported
+	 */
+	if (f->fmt.pix_mp.width > ctx->dev->max_w)
+		f->fmt.pix_mp.width = ctx->dev->max_w;
+	if (f->fmt.pix_mp.height > ctx->dev->max_h)
+		f->fmt.pix_mp.height = ctx->dev->max_h;
+
+	if (!(fmt->flags & V4L2_FMT_FLAG_COMPRESSED)) {
+		/* Only clip min w/h on capture. Treat 0x0 as unknown. */
+		if (f->fmt.pix_mp.width < MIN_W)
+			f->fmt.pix_mp.width = MIN_W;
+		if (f->fmt.pix_mp.height < MIN_H)
+			f->fmt.pix_mp.height = MIN_H;
+
+		/*
+		 * For decoders and image encoders the buffer must have
+		 * a vertical alignment of 16 lines.
+		 * The selection will reflect any cropping rectangle when only
+		 * some of the pixels are active.
+		 */
+		if (ctx->dev->role == DECODE || ctx->dev->role == ENCODE_IMAGE)
+			f->fmt.pix_mp.height = ALIGN(f->fmt.pix_mp.height, 16);
+	}
+	f->fmt.pix_mp.num_planes = 1;
+	min_bytesperline = get_bytesperline(f->fmt.pix_mp.width,
+					    f->fmt.pix_mp.height,
+					    fmt, ctx->dev->role);
+	if (f->fmt.pix_mp.plane_fmt[0].bytesperline < min_bytesperline)
+		f->fmt.pix_mp.plane_fmt[0].bytesperline = min_bytesperline;
+	f->fmt.pix_mp.plane_fmt[0].bytesperline =
+		ALIGN(f->fmt.pix_mp.plane_fmt[0].bytesperline,
+		      fmt->bytesperline_align[ctx->dev->role]);
+
+	sizeimage = get_sizeimage(f->fmt.pix_mp.plane_fmt[0].bytesperline,
+				  f->fmt.pix_mp.width, f->fmt.pix_mp.height,
+				  fmt);
+	/*
+	 * Drivers must set sizeimage for uncompressed formats
+	 * Compressed formats allow the client to request an alternate
+	 * size for the buffer.
+	 */
+	if (!(fmt->flags & V4L2_FMT_FLAG_COMPRESSED) ||
+	    f->fmt.pix_mp.plane_fmt[0].sizeimage < sizeimage)
+		f->fmt.pix_mp.plane_fmt[0].sizeimage = sizeimage;
+
+	memset(f->fmt.pix_mp.plane_fmt[0].reserved, 0,
+	       sizeof(f->fmt.pix_mp.plane_fmt[0].reserved));
+
+	if (ctx->dev->role == DECODE || ctx->dev->role == DEINTERLACE) {
+		switch (f->fmt.pix_mp.field) {
+		/*
+		 * All of this is pretty much guesswork as we'll set the
+		 * interlace format correctly come format changed, and signal
+		 * it appropriately on each buffer.
+		 */
+		default:
+		case V4L2_FIELD_NONE:
+		case V4L2_FIELD_ANY:
+			f->fmt.pix_mp.field = V4L2_FIELD_NONE;
+			break;
+		case V4L2_FIELD_INTERLACED:
+			f->fmt.pix_mp.field = V4L2_FIELD_INTERLACED;
+			break;
+		case V4L2_FIELD_TOP:
+		case V4L2_FIELD_BOTTOM:
+		case V4L2_FIELD_INTERLACED_TB:
+			f->fmt.pix_mp.field = V4L2_FIELD_INTERLACED_TB;
+			break;
+		case V4L2_FIELD_INTERLACED_BT:
+			f->fmt.pix_mp.field = V4L2_FIELD_INTERLACED_BT;
+			break;
+		}
+	} else {
+		f->fmt.pix_mp.field = V4L2_FIELD_NONE;
+	}
+
+	return 0;
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+				  struct v4l2_format *f)
+{
+	struct bcm2835_codec_fmt *fmt;
+	struct bcm2835_codec_ctx *ctx = file2ctx(file);
+
+	fmt = find_format(f, ctx->dev, true);
+	if (!fmt) {
+		f->fmt.pix_mp.pixelformat = get_default_format(ctx->dev,
+							       true)->fourcc;
+		fmt = find_format(f, ctx->dev, true);
+	}
+
+	return vidioc_try_fmt(ctx, f, fmt);
+}
+
+static int vidioc_try_fmt_vid_out(struct file *file, void *priv,
+				  struct v4l2_format *f)
+{
+	struct bcm2835_codec_fmt *fmt;
+	struct bcm2835_codec_ctx *ctx = file2ctx(file);
+
+	fmt = find_format(f, ctx->dev, false);
+	if (!fmt) {
+		f->fmt.pix_mp.pixelformat = get_default_format(ctx->dev,
+							       false)->fourcc;
+		fmt = find_format(f, ctx->dev, false);
+	}
+
+	if (!f->fmt.pix_mp.colorspace)
+		f->fmt.pix_mp.colorspace = ctx->colorspace;
+
+	return vidioc_try_fmt(ctx, f, fmt);
+}
+
+static int vidioc_s_fmt(struct bcm2835_codec_ctx *ctx, struct v4l2_format *f,
+			unsigned int requested_height)
+{
+	struct bcm2835_codec_q_data *q_data;
+	struct vb2_queue *vq;
+	struct vchiq_mmal_port *port;
+	bool update_capture_port = false;
+	bool reenable_port = false;
+	int ret;
+
+	v4l2_dbg(1, debug, &ctx->dev->v4l2_dev,	"Setting format for type %d, wxh: %dx%d, fmt: %08x, size %u\n",
+		 f->type, f->fmt.pix_mp.width, f->fmt.pix_mp.height,
+		 f->fmt.pix_mp.pixelformat,
+		 f->fmt.pix_mp.plane_fmt[0].sizeimage);
+
+	vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
+	if (!vq)
+		return -EINVAL;
+
+	q_data = get_q_data(ctx, f->type);
+	if (!q_data)
+		return -EINVAL;
+
+	if (vb2_is_busy(vq)) {
+		v4l2_err(&ctx->dev->v4l2_dev, "%s queue busy\n", __func__);
+		return -EBUSY;
+	}
+
+	q_data->fmt = find_format(f, ctx->dev,
+				  f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+	q_data->crop_width = f->fmt.pix_mp.width;
+	q_data->height = f->fmt.pix_mp.height;
+	if (!q_data->selection_set ||
+	    (q_data->fmt->flags & V4L2_FMT_FLAG_COMPRESSED))
+		q_data->crop_height = requested_height;
+
+	/*
+	 * Copying the behaviour of vicodec which retains a single set of
+	 * colorspace parameters for both input and output.
+	 */
+	ctx->colorspace = f->fmt.pix_mp.colorspace;
+	ctx->xfer_func = f->fmt.pix_mp.xfer_func;
+	ctx->ycbcr_enc = f->fmt.pix_mp.ycbcr_enc;
+	ctx->quant = f->fmt.pix_mp.quantization;
+
+	q_data->field = f->fmt.pix_mp.field;
+
+	/* All parameters should have been set correctly by try_fmt */
+	q_data->bytesperline = f->fmt.pix_mp.plane_fmt[0].bytesperline;
+	q_data->sizeimage = f->fmt.pix_mp.plane_fmt[0].sizeimage;
+
+	v4l2_dbg(1, debug, &ctx->dev->v4l2_dev,	"Calculated bpl as %u, size %u\n",
+		 q_data->bytesperline, q_data->sizeimage);
+
+	if ((ctx->dev->role == DECODE || ctx->dev->role == ENCODE_IMAGE) &&
+	    q_data->fmt->flags & V4L2_FMT_FLAG_COMPRESSED &&
+	    q_data->crop_width && q_data->height) {
+		/*
+		 * On the decoder or image encoder, if provided with
+		 * a resolution on the input side, then replicate that
+		 * to the output side.
+		 * GStreamer appears not to support V4L2_EVENT_SOURCE_CHANGE,
+		 * nor set up a resolution on the output side, therefore
+		 * we can't decode anything at a resolution other than the
+		 * default one.
+		 */
+		struct bcm2835_codec_q_data *q_data_dst =
+						&ctx->q_data[V4L2_M2M_DST];
+
+		q_data_dst->crop_width = q_data->crop_width;
+		q_data_dst->crop_height = q_data->crop_height;
+		q_data_dst->height = ALIGN(q_data->crop_height, 16);
+
+		q_data_dst->bytesperline =
+			get_bytesperline(f->fmt.pix_mp.width,
+					 f->fmt.pix_mp.height,
+					 q_data_dst->fmt, ctx->dev->role);
+		q_data_dst->sizeimage = get_sizeimage(q_data_dst->bytesperline,
+						      q_data_dst->crop_width,
+						      q_data_dst->height,
+						      q_data_dst->fmt);
+		update_capture_port = true;
+	}
+
+	/* If we have a component then setup the port as well */
+	port = get_port_data(ctx, vq->type);
+	if (!port)
+		return 0;
+
+	if (port->enabled) {
+		unsigned int num_buffers;
+
+		/*
+		 * This should only ever happen with DECODE and the MMAL output
+		 * port that has been enabled for resolution changed events.
+		 * In this case no buffers have been allocated or sent to the
+		 * component, so warn on that.
+		 */
+		WARN_ON(ctx->dev->role != DECODE ||
+			f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE ||
+			atomic_read(&port->buffers_with_vpu));
+
+		/*
+		 * Disable will reread the port format, so retain buffer count.
+		 */
+		num_buffers = port->current_buffer.num;
+
+		ret = vchiq_mmal_port_disable(ctx->dev->instance, port);
+		if (ret)
+			v4l2_err(&ctx->dev->v4l2_dev, "%s: Error disabling port update buffer count, ret %d\n",
+				 __func__, ret);
+
+		port->current_buffer.num = num_buffers;
+
+		reenable_port = true;
+	}
+
+	setup_mmal_port_format(ctx, q_data, port);
+	ret = vchiq_mmal_port_set_format(ctx->dev->instance, port);
+	if (ret) {
+		v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed vchiq_mmal_port_set_format on port, ret %d\n",
+			 __func__, ret);
+		ret = -EINVAL;
+	}
+
+	if (q_data->sizeimage < port->minimum_buffer.size) {
+		v4l2_err(&ctx->dev->v4l2_dev, "%s: Current buffer size of %u < min buf size %u - driver mismatch to MMAL\n",
+			 __func__, q_data->sizeimage,
+			 port->minimum_buffer.size);
+	}
+
+	if (reenable_port) {
+		ret = vchiq_mmal_port_enable(ctx->dev->instance,
+					     port,
+					     op_buffer_cb);
+		if (ret)
+			v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed enabling o/p port, ret %d\n",
+				 __func__, ret);
+	}
+	v4l2_dbg(1, debug, &ctx->dev->v4l2_dev,	"Set format for type %d, wxh: %dx%d, fmt: %08x, size %u\n",
+		 f->type, q_data->crop_width, q_data->height,
+		 q_data->fmt->fourcc, q_data->sizeimage);
+
+	if (update_capture_port) {
+		struct vchiq_mmal_port *port_dst = &ctx->component->output[0];
+		struct bcm2835_codec_q_data *q_data_dst =
+						&ctx->q_data[V4L2_M2M_DST];
+
+		setup_mmal_port_format(ctx, q_data_dst, port_dst);
+		ret = vchiq_mmal_port_set_format(ctx->dev->instance, port_dst);
+		if (ret) {
+			v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed vchiq_mmal_port_set_format on output port, ret %d\n",
+				 __func__, ret);
+			ret = -EINVAL;
+		}
+	}
+	return ret;
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+				struct v4l2_format *f)
+{
+	unsigned int height = f->fmt.pix_mp.height;
+	int ret;
+
+	ret = vidioc_try_fmt_vid_cap(file, priv, f);
+	if (ret)
+		return ret;
+
+	return vidioc_s_fmt(file2ctx(file), f, height);
+}
+
+static int vidioc_s_fmt_vid_out(struct file *file, void *priv,
+				struct v4l2_format *f)
+{
+	unsigned int height = f->fmt.pix_mp.height;
+	int ret;
+
+	ret = vidioc_try_fmt_vid_out(file, priv, f);
+	if (ret)
+		return ret;
+
+	ret = vidioc_s_fmt(file2ctx(file), f, height);
+	return ret;
+}
+
+static int vidioc_g_selection(struct file *file, void *priv,
+			      struct v4l2_selection *s)
+{
+	struct bcm2835_codec_ctx *ctx = file2ctx(file);
+	struct bcm2835_codec_q_data *q_data;
+
+	/*
+	 * The selection API takes V4L2_BUF_TYPE_VIDEO_CAPTURE and
+	 * V4L2_BUF_TYPE_VIDEO_OUTPUT, even if the device implements the MPLANE
+	 * API. The V4L2 core will have converted the MPLANE variants to
+	 * non-MPLANE.
+	 * Open code this instead of using get_q_data in this case.
+	 */
+	switch (s->type) {
+	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+		/* CAPTURE on encoder is not valid. */
+		if (ctx->dev->role == ENCODE || ctx->dev->role == ENCODE_IMAGE)
+			return -EINVAL;
+		q_data = &ctx->q_data[V4L2_M2M_DST];
+		break;
+	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+		/* OUTPUT on deoder is not valid. */
+		if (ctx->dev->role == DECODE)
+			return -EINVAL;
+		q_data = &ctx->q_data[V4L2_M2M_SRC];
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (ctx->dev->role) {
+	case DECODE:
+		switch (s->target) {
+		case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+		case V4L2_SEL_TGT_COMPOSE:
+			s->r.left = 0;
+			s->r.top = 0;
+			s->r.width = q_data->crop_width;
+			s->r.height = q_data->crop_height;
+			break;
+		case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+			s->r.left = 0;
+			s->r.top = 0;
+			s->r.width = q_data->crop_width;
+			s->r.height = q_data->crop_height;
+			break;
+		case V4L2_SEL_TGT_CROP_BOUNDS:
+		case V4L2_SEL_TGT_CROP_DEFAULT:
+			s->r.left = 0;
+			s->r.top = 0;
+			s->r.width = (q_data->bytesperline << 3) /
+						q_data->fmt->depth;
+			s->r.height = q_data->height;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	case ENCODE:
+	case ENCODE_IMAGE:
+		switch (s->target) {
+		case V4L2_SEL_TGT_CROP_DEFAULT:
+		case V4L2_SEL_TGT_CROP_BOUNDS:
+			s->r.top = 0;
+			s->r.left = 0;
+			s->r.width = q_data->bytesperline;
+			s->r.height = q_data->height;
+			break;
+		case V4L2_SEL_TGT_CROP:
+			s->r.top = 0;
+			s->r.left = 0;
+			s->r.width = q_data->crop_width;
+			s->r.height = q_data->crop_height;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	case ISP:
+	case DEINTERLACE:
+		if (s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+			switch (s->target) {
+			case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+			case V4L2_SEL_TGT_COMPOSE:
+				s->r.left = 0;
+				s->r.top = 0;
+				s->r.width = q_data->crop_width;
+				s->r.height = q_data->crop_height;
+				break;
+			case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+				s->r.left = 0;
+				s->r.top = 0;
+				s->r.width = q_data->crop_width;
+				s->r.height = q_data->crop_height;
+				break;
+			default:
+				return -EINVAL;
+			}
+		} else {
+			/* must be V4L2_BUF_TYPE_VIDEO_OUTPUT */
+			switch (s->target) {
+			case V4L2_SEL_TGT_CROP_DEFAULT:
+			case V4L2_SEL_TGT_CROP_BOUNDS:
+				s->r.top = 0;
+				s->r.left = 0;
+				s->r.width = q_data->bytesperline;
+				s->r.height = q_data->height;
+				break;
+			case V4L2_SEL_TGT_CROP:
+				s->r.top = 0;
+				s->r.left = 0;
+				s->r.width = q_data->crop_width;
+				s->r.height = q_data->crop_height;
+				break;
+			default:
+				return -EINVAL;
+			}
+		}
+		break;
+	case NUM_ROLES:
+		break;
+	}
+
+	return 0;
+}
+
+static int vidioc_s_selection(struct file *file, void *priv,
+			      struct v4l2_selection *s)
+{
+	struct bcm2835_codec_ctx *ctx = file2ctx(file);
+	struct bcm2835_codec_q_data *q_data = NULL;
+	struct vchiq_mmal_port *port = NULL;
+	int ret;
+
+	/*
+	 * The selection API takes V4L2_BUF_TYPE_VIDEO_CAPTURE and
+	 * V4L2_BUF_TYPE_VIDEO_OUTPUT, even if the device implements the MPLANE
+	 * API. The V4L2 core will have converted the MPLANE variants to
+	 * non-MPLANE.
+	 *
+	 * Open code this instead of using get_q_data in this case.
+	 */
+	switch (s->type) {
+	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+		/* CAPTURE on encoder is not valid. */
+		if (ctx->dev->role == ENCODE || ctx->dev->role == ENCODE_IMAGE)
+			return -EINVAL;
+		q_data = &ctx->q_data[V4L2_M2M_DST];
+		if (ctx->component)
+			port = &ctx->component->output[0];
+		break;
+	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+		/* OUTPUT on deoder is not valid. */
+		if (ctx->dev->role == DECODE)
+			return -EINVAL;
+		q_data = &ctx->q_data[V4L2_M2M_SRC];
+		if (ctx->component)
+			port = &ctx->component->input[0];
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: ctx %p, type %d, q_data %p, target %d, rect x/y %d/%d, w/h %ux%u\n",
+		 __func__, ctx, s->type, q_data, s->target, s->r.left, s->r.top,
+		 s->r.width, s->r.height);
+
+	switch (ctx->dev->role) {
+	case DECODE:
+		switch (s->target) {
+		case V4L2_SEL_TGT_COMPOSE:
+			/* Accept cropped image */
+			s->r.left = 0;
+			s->r.top = 0;
+			s->r.width = min(s->r.width, q_data->crop_width);
+			s->r.height = min(s->r.height, q_data->height);
+			q_data->crop_width = s->r.width;
+			q_data->crop_height = s->r.height;
+			q_data->selection_set = true;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	case ENCODE:
+	case ENCODE_IMAGE:
+		switch (s->target) {
+		case V4L2_SEL_TGT_CROP:
+			/* Only support crop from (0,0) */
+			s->r.top = 0;
+			s->r.left = 0;
+			s->r.width = min(s->r.width, q_data->crop_width);
+			s->r.height = min(s->r.height, q_data->height);
+			q_data->crop_width = s->r.width;
+			q_data->crop_height = s->r.height;
+			q_data->selection_set = true;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	case ISP:
+	case DEINTERLACE:
+		if (s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+			switch (s->target) {
+			case V4L2_SEL_TGT_COMPOSE:
+				/* Accept cropped image */
+				s->r.left = 0;
+				s->r.top = 0;
+				s->r.width = min(s->r.width, q_data->crop_width);
+				s->r.height = min(s->r.height, q_data->height);
+				q_data->crop_width = s->r.width;
+				q_data->crop_height = s->r.height;
+				q_data->selection_set = true;
+				break;
+			default:
+				return -EINVAL;
+			}
+			break;
+		} else {
+			/* must be V4L2_BUF_TYPE_VIDEO_OUTPUT */
+			switch (s->target) {
+			case V4L2_SEL_TGT_CROP:
+				/* Only support crop from (0,0) */
+				s->r.top = 0;
+				s->r.left = 0;
+				s->r.width = min(s->r.width, q_data->crop_width);
+				s->r.height = min(s->r.height, q_data->height);
+				q_data->crop_width = s->r.width;
+				q_data->crop_height = s->r.height;
+				q_data->selection_set = true;
+				break;
+			default:
+				return -EINVAL;
+			}
+			break;
+		}
+	case NUM_ROLES:
+		break;
+	}
+
+	if (!port)
+		return 0;
+
+	setup_mmal_port_format(ctx, q_data, port);
+	ret = vchiq_mmal_port_set_format(ctx->dev->instance, port);
+	if (ret) {
+		v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed vchiq_mmal_port_set_format on port, ret %d\n",
+			 __func__, ret);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int vidioc_s_parm(struct file *file, void *priv,
+			 struct v4l2_streamparm *parm)
+{
+	struct bcm2835_codec_ctx *ctx = file2ctx(file);
+	struct bcm2835_codec_q_data *q_data = &ctx->q_data[V4L2_M2M_DST];
+	struct vchiq_mmal_port *port;
+	int ret;
+
+	if (parm->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+		return -EINVAL;
+
+	if (!parm->parm.output.timeperframe.denominator ||
+	    !parm->parm.output.timeperframe.numerator)
+		return -EINVAL;
+
+	ctx->framerate_num =
+			parm->parm.output.timeperframe.denominator;
+	ctx->framerate_denom =
+			parm->parm.output.timeperframe.numerator;
+
+	parm->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
+
+	/*
+	 * If we have a component then setup the port as well.
+	 * NB Framerate is passed to MMAL via the DST port, whilst V4L2 uses the
+	 * OUTPUT queue.
+	 */
+	port = get_port_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+	if (!port)
+		return 0;
+
+	if (port->enabled) {
+		struct s32_fract frame_rate = { ctx->framerate_num,
+						ctx->framerate_denom };
+
+		ret = vchiq_mmal_port_parameter_set(ctx->dev->instance,
+						    &ctx->component->output[0],
+						    MMAL_PARAMETER_VIDEO_FRAME_RATE,
+						    &frame_rate,
+						    sizeof(frame_rate));
+
+		return ret;
+	}
+
+	setup_mmal_port_format(ctx, q_data, port);
+	ret = vchiq_mmal_port_set_format(ctx->dev->instance, port);
+	if (ret) {
+		v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed vchiq_mmal_port_set_format on port, ret %d\n",
+			 __func__, ret);
+		ret = -EINVAL;
+	}
+
+	if (q_data->sizeimage < port->minimum_buffer.size) {
+		v4l2_err(&ctx->dev->v4l2_dev, "%s: Current buffer size of %u < min buf size %u - driver mismatch to MMAL\n",
+			 __func__, q_data->sizeimage,
+			 port->minimum_buffer.size);
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int vidioc_g_parm(struct file *file, void *priv,
+			 struct v4l2_streamparm *parm)
+{
+	struct bcm2835_codec_ctx *ctx = file2ctx(file);
+
+	if (parm->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+		return -EINVAL;
+
+	parm->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
+	parm->parm.output.timeperframe.denominator =
+			ctx->framerate_num;
+	parm->parm.output.timeperframe.numerator =
+			ctx->framerate_denom;
+
+	return 0;
+}
+
+static int vidioc_g_pixelaspect(struct file *file, void *fh, int type,
+				struct v4l2_fract *f)
+{
+	struct bcm2835_codec_ctx *ctx = file2ctx(file);
+
+	/*
+	 * The selection API takes V4L2_BUF_TYPE_VIDEO_CAPTURE and
+	 * V4L2_BUF_TYPE_VIDEO_OUTPUT, even if the device implements the MPLANE
+	 * API. The V4L2 core will have converted the MPLANE variants to
+	 * non-MPLANE.
+	 * Open code this instead of using get_q_data in this case.
+	 */
+	if (ctx->dev->role != DECODE)
+		return -ENOIOCTLCMD;
+
+	if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	*f = ctx->q_data[V4L2_M2M_DST].aspect_ratio;
+
+	return 0;
+}
+
+static int vidioc_subscribe_evt(struct v4l2_fh *fh,
+				const struct v4l2_event_subscription *sub)
+{
+	switch (sub->type) {
+	case V4L2_EVENT_EOS:
+		return v4l2_event_subscribe(fh, sub, 2, NULL);
+	case V4L2_EVENT_SOURCE_CHANGE:
+		return v4l2_src_change_event_subscribe(fh, sub);
+	default:
+		return v4l2_ctrl_subscribe_event(fh, sub);
+	}
+}
+
+static int bcm2835_codec_set_level_profile(struct bcm2835_codec_ctx *ctx,
+					   struct v4l2_ctrl *ctrl)
+{
+	struct mmal_parameter_video_profile param;
+	int param_size = sizeof(param);
+	int ret;
+
+	/*
+	 * Level and Profile are set via the same MMAL parameter.
+	 * Retrieve the current settings and amend the one that has changed.
+	 */
+	ret = vchiq_mmal_port_parameter_get(ctx->dev->instance,
+					    &ctx->component->output[0],
+					    MMAL_PARAMETER_PROFILE,
+					    &param,
+					    &param_size);
+	if (ret)
+		return ret;
+
+	switch (ctrl->id) {
+	case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
+		switch (ctrl->val) {
+		case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE:
+			param.profile = MMAL_VIDEO_PROFILE_H264_BASELINE;
+			break;
+		case V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE:
+			param.profile =
+				MMAL_VIDEO_PROFILE_H264_CONSTRAINED_BASELINE;
+			break;
+		case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN:
+			param.profile = MMAL_VIDEO_PROFILE_H264_MAIN;
+			break;
+		case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH:
+			param.profile = MMAL_VIDEO_PROFILE_H264_HIGH;
+			break;
+		default:
+			/* Should never get here */
+			break;
+		}
+		break;
+
+	case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
+		switch (ctrl->val) {
+		case V4L2_MPEG_VIDEO_H264_LEVEL_1_0:
+			param.level = MMAL_VIDEO_LEVEL_H264_1;
+			break;
+		case V4L2_MPEG_VIDEO_H264_LEVEL_1B:
+			param.level = MMAL_VIDEO_LEVEL_H264_1b;
+			break;
+		case V4L2_MPEG_VIDEO_H264_LEVEL_1_1:
+			param.level = MMAL_VIDEO_LEVEL_H264_11;
+			break;
+		case V4L2_MPEG_VIDEO_H264_LEVEL_1_2:
+			param.level = MMAL_VIDEO_LEVEL_H264_12;
+			break;
+		case V4L2_MPEG_VIDEO_H264_LEVEL_1_3:
+			param.level = MMAL_VIDEO_LEVEL_H264_13;
+			break;
+		case V4L2_MPEG_VIDEO_H264_LEVEL_2_0:
+			param.level = MMAL_VIDEO_LEVEL_H264_2;
+			break;
+		case V4L2_MPEG_VIDEO_H264_LEVEL_2_1:
+			param.level = MMAL_VIDEO_LEVEL_H264_21;
+			break;
+		case V4L2_MPEG_VIDEO_H264_LEVEL_2_2:
+			param.level = MMAL_VIDEO_LEVEL_H264_22;
+			break;
+		case V4L2_MPEG_VIDEO_H264_LEVEL_3_0:
+			param.level = MMAL_VIDEO_LEVEL_H264_3;
+			break;
+		case V4L2_MPEG_VIDEO_H264_LEVEL_3_1:
+			param.level = MMAL_VIDEO_LEVEL_H264_31;
+			break;
+		case V4L2_MPEG_VIDEO_H264_LEVEL_3_2:
+			param.level = MMAL_VIDEO_LEVEL_H264_32;
+			break;
+		case V4L2_MPEG_VIDEO_H264_LEVEL_4_0:
+			param.level = MMAL_VIDEO_LEVEL_H264_4;
+			break;
+		/*
+		 * Note that the hardware spec is level 4.0. Levels above that
+		 * are there for correctly encoding the headers and may not
+		 * be able to keep up with real-time.
+		 */
+		case V4L2_MPEG_VIDEO_H264_LEVEL_4_1:
+			param.level = MMAL_VIDEO_LEVEL_H264_41;
+			break;
+		case V4L2_MPEG_VIDEO_H264_LEVEL_4_2:
+			param.level = MMAL_VIDEO_LEVEL_H264_42;
+			break;
+		case V4L2_MPEG_VIDEO_H264_LEVEL_5_0:
+			param.level = MMAL_VIDEO_LEVEL_H264_5;
+			break;
+		case V4L2_MPEG_VIDEO_H264_LEVEL_5_1:
+			param.level = MMAL_VIDEO_LEVEL_H264_51;
+			break;
+		default:
+			/* Should never get here */
+			break;
+		}
+	}
+	ret = vchiq_mmal_port_parameter_set(ctx->dev->instance,
+					    &ctx->component->output[0],
+					    MMAL_PARAMETER_PROFILE,
+					    &param,
+					    param_size);
+
+	return ret;
+}
+
+static int bcm2835_codec_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct bcm2835_codec_ctx *ctx =
+		container_of(ctrl->handler, struct bcm2835_codec_ctx, hdl);
+	int ret = 0;
+
+	if (ctrl->flags & V4L2_CTRL_FLAG_READ_ONLY)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_MPEG_VIDEO_BITRATE:
+		ctx->bitrate = ctrl->val;
+		if (!ctx->component)
+			break;
+
+		ret = vchiq_mmal_port_parameter_set(ctx->dev->instance,
+						    &ctx->component->output[0],
+						    MMAL_PARAMETER_VIDEO_BIT_RATE,
+						    &ctrl->val,
+						    sizeof(ctrl->val));
+		break;
+
+	case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: {
+		u32 bitrate_mode;
+
+		if (!ctx->component)
+			break;
+
+		switch (ctrl->val) {
+		default:
+		case V4L2_MPEG_VIDEO_BITRATE_MODE_VBR:
+			bitrate_mode = MMAL_VIDEO_RATECONTROL_VARIABLE;
+			break;
+		case V4L2_MPEG_VIDEO_BITRATE_MODE_CBR:
+			bitrate_mode = MMAL_VIDEO_RATECONTROL_CONSTANT;
+			break;
+		}
+
+		ret = vchiq_mmal_port_parameter_set(ctx->dev->instance,
+						    &ctx->component->output[0],
+						    MMAL_PARAMETER_RATECONTROL,
+						    &bitrate_mode,
+						    sizeof(bitrate_mode));
+		break;
+	}
+	case V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER:
+		if (!ctx->component)
+			break;
+
+		ret = vchiq_mmal_port_parameter_set(ctx->dev->instance,
+						    &ctx->component->output[0],
+						    MMAL_PARAMETER_VIDEO_ENCODE_INLINE_HEADER,
+						    &ctrl->val,
+						    sizeof(ctrl->val));
+		break;
+
+	case V4L2_CID_MPEG_VIDEO_HEADER_MODE:
+		if (!ctx->component)
+			break;
+
+		ret = vchiq_mmal_port_parameter_set(ctx->dev->instance,
+						    &ctx->component->output[0],
+						    MMAL_PARAMETER_VIDEO_ENCODE_HEADERS_WITH_FRAME,
+						    &ctrl->val,
+						    sizeof(ctrl->val));
+		break;
+
+	case V4L2_CID_MPEG_VIDEO_H264_I_PERIOD:
+		/*
+		 * Incorrect initial implementation meant that H264_I_PERIOD
+		 * was implemented to control intra-I period. As the MMAL
+		 * encoder never produces I-frames that aren't IDR frames, it
+		 * should actually have been GOP_SIZE.
+		 * Support both controls, but writing to H264_I_PERIOD will
+		 * update GOP_SIZE.
+		 */
+		__v4l2_ctrl_s_ctrl(ctx->gop_size, ctrl->val);
+	fallthrough;
+	case V4L2_CID_MPEG_VIDEO_GOP_SIZE:
+		if (!ctx->component)
+			break;
+
+		ret = vchiq_mmal_port_parameter_set(ctx->dev->instance,
+						    &ctx->component->output[0],
+						    MMAL_PARAMETER_INTRAPERIOD,
+						    &ctrl->val,
+						    sizeof(ctrl->val));
+		break;
+
+	case V4L2_CID_MPEG_VIDEO_H264_PROFILE:
+	case V4L2_CID_MPEG_VIDEO_H264_LEVEL:
+		if (!ctx->component)
+			break;
+
+		ret = bcm2835_codec_set_level_profile(ctx, ctrl);
+		break;
+
+	case V4L2_CID_MPEG_VIDEO_H264_MIN_QP:
+		if (!ctx->component)
+			break;
+
+		ret = vchiq_mmal_port_parameter_set(ctx->dev->instance,
+						    &ctx->component->output[0],
+						    MMAL_PARAMETER_VIDEO_ENCODE_MIN_QUANT,
+						    &ctrl->val,
+						    sizeof(ctrl->val));
+		break;
+
+	case V4L2_CID_MPEG_VIDEO_H264_MAX_QP:
+		if (!ctx->component)
+			break;
+
+		ret = vchiq_mmal_port_parameter_set(ctx->dev->instance,
+						    &ctx->component->output[0],
+						    MMAL_PARAMETER_VIDEO_ENCODE_MAX_QUANT,
+						    &ctrl->val,
+						    sizeof(ctrl->val));
+		break;
+
+	case V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME: {
+		u32 mmal_bool = 1;
+
+		if (!ctx->component)
+			break;
+
+		ret = vchiq_mmal_port_parameter_set(ctx->dev->instance,
+						    &ctx->component->output[0],
+						    MMAL_PARAMETER_VIDEO_REQUEST_I_FRAME,
+						    &mmal_bool,
+						    sizeof(mmal_bool));
+		break;
+	}
+	case V4L2_CID_HFLIP:
+	case V4L2_CID_VFLIP: {
+		u32 u32_value;
+
+		if (ctrl->id == V4L2_CID_HFLIP)
+			ctx->hflip = ctrl->val;
+		else
+			ctx->vflip = ctrl->val;
+
+		if (!ctx->component)
+			break;
+
+		if (ctx->hflip && ctx->vflip)
+			u32_value = MMAL_PARAM_MIRROR_BOTH;
+		else if (ctx->hflip)
+			u32_value = MMAL_PARAM_MIRROR_HORIZONTAL;
+		else if (ctx->vflip)
+			u32_value = MMAL_PARAM_MIRROR_VERTICAL;
+		else
+			u32_value = MMAL_PARAM_MIRROR_NONE;
+
+		ret = vchiq_mmal_port_parameter_set(ctx->dev->instance,
+						    &ctx->component->input[0],
+						    MMAL_PARAMETER_MIRROR,
+						    &u32_value,
+						    sizeof(u32_value));
+		break;
+	}
+	case V4L2_CID_MPEG_VIDEO_B_FRAMES:
+		ret = 0;
+		break;
+
+	case V4L2_CID_JPEG_COMPRESSION_QUALITY:
+		if (!ctx->component)
+			break;
+
+		ret = vchiq_mmal_port_parameter_set(ctx->dev->instance,
+						    &ctx->component->output[0],
+						    MMAL_PARAMETER_JPEG_Q_FACTOR,
+						    &ctrl->val,
+						    sizeof(ctrl->val));
+		break;
+
+	default:
+		v4l2_err(&ctx->dev->v4l2_dev, "Invalid control %08x\n", ctrl->id);
+		return -EINVAL;
+	}
+
+	if (ret)
+		v4l2_err(&ctx->dev->v4l2_dev, "Failed setting ctrl %08x, ret %d\n",
+			 ctrl->id, ret);
+	return ret ? -EINVAL : 0;
+}
+
+static const struct v4l2_ctrl_ops bcm2835_codec_ctrl_ops = {
+	.s_ctrl = bcm2835_codec_s_ctrl,
+};
+
+static int vidioc_try_decoder_cmd(struct file *file, void *priv,
+				  struct v4l2_decoder_cmd *cmd)
+{
+	struct bcm2835_codec_ctx *ctx = file2ctx(file);
+
+	if (ctx->dev->role != DECODE)
+		return -EINVAL;
+
+	switch (cmd->cmd) {
+	case V4L2_DEC_CMD_STOP:
+		if (cmd->flags & V4L2_DEC_CMD_STOP_TO_BLACK) {
+			v4l2_err(&ctx->dev->v4l2_dev, "%s: DEC cmd->flags=%u stop to black not supported",
+				 __func__, cmd->flags);
+			return -EINVAL;
+		}
+		break;
+	case V4L2_DEC_CMD_START:
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int vidioc_decoder_cmd(struct file *file, void *priv,
+			      struct v4l2_decoder_cmd *cmd)
+{
+	struct bcm2835_codec_ctx *ctx = file2ctx(file);
+	struct bcm2835_codec_q_data *q_data = &ctx->q_data[V4L2_M2M_SRC];
+	struct vb2_queue *dst_vq;
+	int ret;
+
+	v4l2_dbg(2, debug, &ctx->dev->v4l2_dev, "%s, cmd %u", __func__,
+		 cmd->cmd);
+	ret = vidioc_try_decoder_cmd(file, priv, cmd);
+	if (ret)
+		return ret;
+
+	switch (cmd->cmd) {
+	case V4L2_DEC_CMD_STOP:
+		if (q_data->eos_buffer_in_use)
+			v4l2_err(&ctx->dev->v4l2_dev, "EOS buffers already in use\n");
+		q_data->eos_buffer_in_use = true;
+
+		q_data->eos_buffer.mmal.buffer_size = 0;
+		q_data->eos_buffer.mmal.length = 0;
+		q_data->eos_buffer.mmal.mmal_flags =
+						MMAL_BUFFER_HEADER_FLAG_EOS;
+		q_data->eos_buffer.mmal.pts = 0;
+		q_data->eos_buffer.mmal.dts = 0;
+
+		if (!ctx->component)
+			break;
+
+		ret = vchiq_mmal_submit_buffer(ctx->dev->instance,
+					       &ctx->component->input[0],
+					       &q_data->eos_buffer.mmal);
+		if (ret)
+			v4l2_err(&ctx->dev->v4l2_dev,
+				 "%s: EOS buffer submit failed %d\n",
+				 __func__, ret);
+
+		break;
+
+	case V4L2_DEC_CMD_START:
+		dst_vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx,
+					 V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+		vb2_clear_last_buffer_dequeued(dst_vq);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int vidioc_try_encoder_cmd(struct file *file, void *priv,
+				  struct v4l2_encoder_cmd *cmd)
+{
+	switch (cmd->cmd) {
+	case V4L2_ENC_CMD_STOP:
+		break;
+
+	case V4L2_ENC_CMD_START:
+		/* Do we need to do anything here? */
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int vidioc_encoder_cmd(struct file *file, void *priv,
+			      struct v4l2_encoder_cmd *cmd)
+{
+	struct bcm2835_codec_ctx *ctx = file2ctx(file);
+	struct bcm2835_codec_q_data *q_data = &ctx->q_data[V4L2_M2M_SRC];
+	int ret;
+
+	v4l2_dbg(2, debug, &ctx->dev->v4l2_dev, "%s, cmd %u", __func__,
+		 cmd->cmd);
+	ret = vidioc_try_encoder_cmd(file, priv, cmd);
+	if (ret)
+		return ret;
+
+	switch (cmd->cmd) {
+	case V4L2_ENC_CMD_STOP:
+		if (q_data->eos_buffer_in_use)
+			v4l2_err(&ctx->dev->v4l2_dev, "EOS buffers already in use\n");
+		q_data->eos_buffer_in_use = true;
+
+		q_data->eos_buffer.mmal.buffer_size = 0;
+		q_data->eos_buffer.mmal.length = 0;
+		q_data->eos_buffer.mmal.mmal_flags =
+						MMAL_BUFFER_HEADER_FLAG_EOS;
+		q_data->eos_buffer.mmal.pts = 0;
+		q_data->eos_buffer.mmal.dts = 0;
+
+		if (!ctx->component)
+			break;
+
+		ret = vchiq_mmal_submit_buffer(ctx->dev->instance,
+					       &ctx->component->input[0],
+					       &q_data->eos_buffer.mmal);
+		if (ret)
+			v4l2_err(&ctx->dev->v4l2_dev,
+				 "%s: EOS buffer submit failed %d\n",
+				 __func__, ret);
+
+		break;
+	case V4L2_ENC_CMD_START:
+		/* Do we need to do anything here? */
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int vidioc_enum_framesizes(struct file *file, void *fh,
+				  struct v4l2_frmsizeenum *fsize)
+{
+	struct bcm2835_codec_ctx *ctx = file2ctx(file);
+	struct bcm2835_codec_fmt *fmt;
+
+	fmt = find_format_pix_fmt(fsize->pixel_format, file2ctx(file)->dev,
+				  true);
+	if (!fmt)
+		fmt = find_format_pix_fmt(fsize->pixel_format,
+					  file2ctx(file)->dev,
+					  false);
+
+	if (!fmt)
+		return -EINVAL;
+
+	if (fsize->index)
+		return -EINVAL;
+
+	fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
+
+	fsize->stepwise.min_width = MIN_W;
+	fsize->stepwise.max_width = ctx->dev->max_w;
+	fsize->stepwise.step_width = 2;
+	fsize->stepwise.min_height = MIN_H;
+	fsize->stepwise.max_height = ctx->dev->max_h;
+	fsize->stepwise.step_height = 2;
+
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops bcm2835_codec_ioctl_ops = {
+	.vidioc_querycap	= vidioc_querycap,
+
+	.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap_mplane	= vidioc_g_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap_mplane	= vidioc_try_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap_mplane	= vidioc_s_fmt_vid_cap,
+
+	.vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid_out,
+	.vidioc_g_fmt_vid_out_mplane	= vidioc_g_fmt_vid_out,
+	.vidioc_try_fmt_vid_out_mplane	= vidioc_try_fmt_vid_out,
+	.vidioc_s_fmt_vid_out_mplane	= vidioc_s_fmt_vid_out,
+
+	.vidioc_reqbufs		= v4l2_m2m_ioctl_reqbufs,
+	.vidioc_querybuf	= v4l2_m2m_ioctl_querybuf,
+	.vidioc_qbuf		= v4l2_m2m_ioctl_qbuf,
+	.vidioc_dqbuf		= v4l2_m2m_ioctl_dqbuf,
+	.vidioc_prepare_buf	= v4l2_m2m_ioctl_prepare_buf,
+	.vidioc_create_bufs	= v4l2_m2m_ioctl_create_bufs,
+	.vidioc_expbuf		= v4l2_m2m_ioctl_expbuf,
+
+	.vidioc_streamon	= v4l2_m2m_ioctl_streamon,
+	.vidioc_streamoff	= v4l2_m2m_ioctl_streamoff,
+
+	.vidioc_g_selection	= vidioc_g_selection,
+	.vidioc_s_selection	= vidioc_s_selection,
+
+	.vidioc_g_parm		= vidioc_g_parm,
+	.vidioc_s_parm		= vidioc_s_parm,
+
+	.vidioc_g_pixelaspect	= vidioc_g_pixelaspect,
+
+	.vidioc_subscribe_event = vidioc_subscribe_evt,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+
+	.vidioc_decoder_cmd = vidioc_decoder_cmd,
+	.vidioc_try_decoder_cmd = vidioc_try_decoder_cmd,
+	.vidioc_encoder_cmd = vidioc_encoder_cmd,
+	.vidioc_try_encoder_cmd = vidioc_try_encoder_cmd,
+	.vidioc_enum_framesizes = vidioc_enum_framesizes,
+};
+
+static int bcm2835_codec_create_component(struct bcm2835_codec_ctx *ctx)
+{
+	struct bcm2835_codec_dev *dev = ctx->dev;
+	unsigned int enable = 1;
+	int ret;
+
+	ret = vchiq_mmal_component_init(dev->instance, components[dev->role],
+					&ctx->component);
+	if (ret < 0) {
+		v4l2_err(&dev->v4l2_dev, "%s: failed to create component %s\n",
+			 __func__, components[dev->role]);
+		return -ENOMEM;
+	}
+
+	vchiq_mmal_port_parameter_set(dev->instance, &ctx->component->input[0],
+				      MMAL_PARAMETER_ZERO_COPY, &enable,
+				      sizeof(enable));
+	vchiq_mmal_port_parameter_set(dev->instance, &ctx->component->output[0],
+				      MMAL_PARAMETER_ZERO_COPY, &enable,
+				      sizeof(enable));
+
+	if (dev->role == DECODE) {
+		/*
+		 * Disable firmware option that ensures decoded timestamps
+		 * always increase.
+		 */
+		enable = 0;
+		vchiq_mmal_port_parameter_set(dev->instance,
+					      &ctx->component->output[0],
+					      MMAL_PARAMETER_VIDEO_VALIDATE_TIMESTAMPS,
+					      &enable,
+					      sizeof(enable));
+		/*
+		 * Enable firmware option to stop on colourspace and pixel
+		 * aspect ratio changed
+		 */
+		enable = 1;
+		vchiq_mmal_port_parameter_set(dev->instance,
+					      &ctx->component->control,
+					      MMAL_PARAMETER_VIDEO_STOP_ON_PAR_COLOUR_CHANGE,
+					      &enable,
+					      sizeof(enable));
+	} else if (dev->role == DEINTERLACE) {
+		/* Select the default deinterlace algorithm. */
+		int half_framerate = 0;
+		int default_frame_interval = -1; /* don't interpolate */
+		int frame_type = 5; /* 0=progressive, 3=TFF, 4=BFF, 5=see frame */
+		int use_qpus = 0;
+		enum mmal_parameter_imagefx effect =
+			advanced_deinterlace && ctx->q_data[V4L2_M2M_SRC].crop_width <= 800 ?
+			MMAL_PARAM_IMAGEFX_DEINTERLACE_ADV :
+			MMAL_PARAM_IMAGEFX_DEINTERLACE_FAST;
+		struct mmal_parameter_imagefx_parameters params = {
+			.effect = effect,
+			.num_effect_params = 4,
+			.effect_parameter = { frame_type,
+					      default_frame_interval,
+					      half_framerate,
+					      use_qpus },
+		};
+
+		vchiq_mmal_port_parameter_set(dev->instance,
+					      &ctx->component->output[0],
+					      MMAL_PARAMETER_IMAGE_EFFECT_PARAMETERS,
+					      &params,
+					      sizeof(params));
+
+	} else if (dev->role == ENCODE) {
+		enable = 0;
+		vchiq_mmal_port_parameter_set(dev->instance,
+					      &ctx->component->control,
+					      MMAL_PARAMETER_VIDEO_ENCODE_HEADER_ON_OPEN,
+					      &enable,
+					      sizeof(enable));
+	} else if (dev->role == ENCODE_IMAGE) {
+		enable = 0;
+		vchiq_mmal_port_parameter_set(dev->instance,
+					      &ctx->component->control,
+					      MMAL_PARAMETER_EXIF_DISABLE,
+					      &enable,
+					      sizeof(enable));
+		enable = 1;
+		vchiq_mmal_port_parameter_set(dev->instance,
+					      &ctx->component->output[0],
+						  MMAL_PARAMETER_JPEG_IJG_SCALING,
+					      &enable,
+					      sizeof(enable));
+	}
+
+	setup_mmal_port_format(ctx, &ctx->q_data[V4L2_M2M_SRC],
+			       &ctx->component->input[0]);
+	ctx->component->input[0].cb_ctx = ctx;
+
+	setup_mmal_port_format(ctx, &ctx->q_data[V4L2_M2M_DST],
+			       &ctx->component->output[0]);
+	ctx->component->output[0].cb_ctx = ctx;
+
+	ret = vchiq_mmal_port_set_format(dev->instance,
+					 &ctx->component->input[0]);
+	if (ret < 0) {
+		v4l2_dbg(1, debug, &dev->v4l2_dev,
+			 "%s: vchiq_mmal_port_set_format ip port failed\n",
+			 __func__);
+		goto destroy_component;
+	}
+
+	ret = vchiq_mmal_port_set_format(dev->instance,
+					 &ctx->component->output[0]);
+	if (ret < 0) {
+		v4l2_dbg(1, debug, &dev->v4l2_dev,
+			 "%s: vchiq_mmal_port_set_format op port failed\n",
+			 __func__);
+		goto destroy_component;
+	}
+
+	if (dev->role == ENCODE || dev->role == ENCODE_IMAGE) {
+		u32 param = 1;
+
+		if (ctx->q_data[V4L2_M2M_SRC].sizeimage <
+			ctx->component->output[0].minimum_buffer.size)
+			v4l2_err(&dev->v4l2_dev, "buffer size mismatch sizeimage %u < min size %u\n",
+				 ctx->q_data[V4L2_M2M_SRC].sizeimage,
+				 ctx->component->output[0].minimum_buffer.size);
+
+		if (dev->role == ENCODE) {
+			/* Enable SPS Timing header so framerate information is encoded
+			 * in the H264 header.
+			 */
+			vchiq_mmal_port_parameter_set(ctx->dev->instance,
+						      &ctx->component->output[0],
+						      MMAL_PARAMETER_VIDEO_ENCODE_SPS_TIMING,
+						      &param, sizeof(param));
+
+			/* Enable inserting headers into the first frame */
+			vchiq_mmal_port_parameter_set(ctx->dev->instance,
+						      &ctx->component->control,
+						      MMAL_PARAMETER_VIDEO_ENCODE_HEADERS_WITH_FRAME,
+						      &param, sizeof(param));
+			/*
+			 * Avoid fragmenting the buffers over multiple frames (unless
+			 * the frame is bigger than the whole buffer)
+			 */
+			vchiq_mmal_port_parameter_set(ctx->dev->instance,
+						      &ctx->component->control,
+						      MMAL_PARAMETER_MINIMISE_FRAGMENTATION,
+						      &param, sizeof(param));
+		}
+	} else {
+		if (ctx->q_data[V4L2_M2M_DST].sizeimage <
+			ctx->component->output[0].minimum_buffer.size)
+			v4l2_err(&dev->v4l2_dev, "buffer size mismatch sizeimage %u < min size %u\n",
+				 ctx->q_data[V4L2_M2M_DST].sizeimage,
+				 ctx->component->output[0].minimum_buffer.size);
+	}
+
+	/* Now we have a component we can set all the ctrls */
+	ret = v4l2_ctrl_handler_setup(&ctx->hdl);
+
+	v4l2_dbg(2, debug, &dev->v4l2_dev, "%s: component created as %s\n",
+		 __func__, components[dev->role]);
+
+	return 0;
+
+destroy_component:
+	vchiq_mmal_component_finalise(ctx->dev->instance, ctx->component);
+	ctx->component = NULL;
+
+	return ret;
+}
+
+/*
+ * Queue operations
+ */
+
+static int bcm2835_codec_queue_setup(struct vb2_queue *vq,
+				     unsigned int *nbuffers,
+				     unsigned int *nplanes,
+				     unsigned int sizes[],
+				     struct device *alloc_devs[])
+{
+	struct bcm2835_codec_ctx *ctx = vb2_get_drv_priv(vq);
+	struct bcm2835_codec_q_data *q_data;
+	struct vchiq_mmal_port *port;
+	unsigned int size;
+
+	q_data = get_q_data(ctx, vq->type);
+	if (!q_data)
+		return -EINVAL;
+
+	if (!ctx->component)
+		if (bcm2835_codec_create_component(ctx))
+			return -EINVAL;
+
+	port = get_port_data(ctx, vq->type);
+
+	size = q_data->sizeimage;
+
+	if (*nplanes)
+		return sizes[0] < size ? -EINVAL : 0;
+
+	*nplanes = 1;
+
+	sizes[0] = size;
+	port->current_buffer.size = size;
+
+	if (*nbuffers < port->minimum_buffer.num)
+		*nbuffers = port->minimum_buffer.num;
+
+	/*
+	 * The VPU uses this number to allocate a pool of headers at port_enable.
+	 * We can't increase it later, so use of CREATE_BUFS is going to result
+	 * in bad things happening. Adopt worst-case allocation, and add one
+	 * buffer to take an EOS
+	 */
+	port->current_buffer.num = VB2_MAX_FRAME + 1;
+
+	return 0;
+}
+
+static int bcm2835_codec_mmal_buf_cleanup(struct mmal_buffer *mmal_buf)
+{
+	mmal_vchi_buffer_cleanup(mmal_buf);
+
+	if (mmal_buf->dma_buf) {
+		dma_buf_put(mmal_buf->dma_buf);
+		mmal_buf->dma_buf = NULL;
+	}
+
+	return 0;
+}
+
+static int bcm2835_codec_buf_init(struct vb2_buffer *vb)
+{
+	struct bcm2835_codec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+	struct vb2_v4l2_buffer *vb2 = to_vb2_v4l2_buffer(vb);
+	struct v4l2_m2m_buffer *m2m = container_of(vb2, struct v4l2_m2m_buffer,
+						   vb);
+	struct m2m_mmal_buffer *buf = container_of(m2m, struct m2m_mmal_buffer,
+						   m2m);
+
+	v4l2_dbg(2, debug, &ctx->dev->v4l2_dev, "%s: ctx:%p, vb %p\n",
+		 __func__, ctx, vb);
+	buf->mmal.buffer = vb2_plane_vaddr(&buf->m2m.vb.vb2_buf, 0);
+	buf->mmal.buffer_size = vb2_plane_size(&buf->m2m.vb.vb2_buf, 0);
+
+	mmal_vchi_buffer_init(ctx->dev->instance, &buf->mmal);
+
+	return 0;
+}
+
+static int bcm2835_codec_buf_prepare(struct vb2_buffer *vb)
+{
+	struct bcm2835_codec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+	struct bcm2835_codec_q_data *q_data;
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct v4l2_m2m_buffer *m2m = container_of(vbuf, struct v4l2_m2m_buffer,
+						   vb);
+	struct m2m_mmal_buffer *buf = container_of(m2m, struct m2m_mmal_buffer,
+						   m2m);
+	struct dma_buf *dma_buf;
+	int ret;
+
+	v4l2_dbg(4, debug, &ctx->dev->v4l2_dev, "%s: type: %d ptr %p\n",
+		 __func__, vb->vb2_queue->type, vb);
+
+	q_data = get_q_data(ctx, vb->vb2_queue->type);
+	if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) {
+		if (vbuf->field == V4L2_FIELD_ANY)
+			vbuf->field = V4L2_FIELD_NONE;
+	}
+
+	if (vb2_plane_size(vb, 0) < q_data->sizeimage) {
+		v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s data will not fit into plane (%lu < %lu)\n",
+			 __func__, vb2_plane_size(vb, 0),
+			 (long)q_data->sizeimage);
+		return -EINVAL;
+	}
+
+	if (!V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type))
+		vb2_set_plane_payload(vb, 0, q_data->sizeimage);
+
+	switch (vb->memory) {
+	case VB2_MEMORY_DMABUF:
+		dma_buf = dma_buf_get(vb->planes[0].m.fd);
+
+		if (dma_buf != buf->mmal.dma_buf) {
+			/* dmabuf either hasn't already been mapped, or it has
+			 * changed.
+			 */
+			if (buf->mmal.dma_buf) {
+				v4l2_err(&ctx->dev->v4l2_dev,
+					 "%s Buffer changed - why did the core not call cleanup?\n",
+					 __func__);
+				bcm2835_codec_mmal_buf_cleanup(&buf->mmal);
+			}
+
+			buf->mmal.dma_buf = dma_buf;
+		} else {
+			/* We already have a reference count on the dmabuf, so
+			 * release the one we acquired above.
+			 */
+			dma_buf_put(dma_buf);
+		}
+		ret = 0;
+		break;
+	case VB2_MEMORY_MMAP:
+		/*
+		 * We want to do this at init, but vb2_core_expbuf checks that
+		 * the index < q->num_buffers, and q->num_buffers only gets
+		 * updated once all the buffers are allocated.
+		 */
+		if (!buf->mmal.dma_buf) {
+			ret = vb2_core_expbuf_dmabuf(vb->vb2_queue,
+						     vb->vb2_queue->type,
+						     vb, 0, O_CLOEXEC,
+						     &buf->mmal.dma_buf);
+			if (ret)
+				v4l2_err(&ctx->dev->v4l2_dev,
+					 "%s: Failed to expbuf idx %d, ret %d\n",
+					 __func__, vb->index, ret);
+		} else {
+			ret = 0;
+		}
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static void bcm2835_codec_buf_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct bcm2835_codec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+
+	v4l2_dbg(4, debug, &ctx->dev->v4l2_dev, "%s: type: %d ptr %p vbuf->flags %u, seq %u, bytesused %u\n",
+		 __func__, vb->vb2_queue->type, vb, vbuf->flags, vbuf->sequence,
+		 vb->planes[0].bytesused);
+	v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
+}
+
+static void bcm2835_codec_buffer_cleanup(struct vb2_buffer *vb)
+{
+	struct bcm2835_codec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+	struct vb2_v4l2_buffer *vb2 = to_vb2_v4l2_buffer(vb);
+	struct v4l2_m2m_buffer *m2m = container_of(vb2, struct v4l2_m2m_buffer,
+						   vb);
+	struct m2m_mmal_buffer *buf = container_of(m2m, struct m2m_mmal_buffer,
+						   m2m);
+
+	v4l2_dbg(2, debug, &ctx->dev->v4l2_dev, "%s: ctx:%p, vb %p\n",
+		 __func__, ctx, vb);
+
+	bcm2835_codec_mmal_buf_cleanup(&buf->mmal);
+}
+
+static void bcm2835_codec_flush_buffers(struct bcm2835_codec_ctx *ctx,
+					struct vchiq_mmal_port *port)
+{
+	int ret;
+
+	if (atomic_read(&port->buffers_with_vpu)) {
+		v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: Waiting for buffers to be returned - %d outstanding\n",
+			 __func__, atomic_read(&port->buffers_with_vpu));
+		ret = wait_for_completion_timeout(&ctx->frame_cmplt,
+						  COMPLETE_TIMEOUT);
+		if (ret <= 0) {
+			v4l2_err(&ctx->dev->v4l2_dev, "%s: Timeout waiting for buffers to be returned - %d outstanding\n",
+				 __func__,
+				 atomic_read(&port->buffers_with_vpu));
+		}
+	}
+}
+static int bcm2835_codec_start_streaming(struct vb2_queue *q,
+					 unsigned int count)
+{
+	struct bcm2835_codec_ctx *ctx = vb2_get_drv_priv(q);
+	struct bcm2835_codec_dev *dev = ctx->dev;
+	struct bcm2835_codec_q_data *q_data = get_q_data(ctx, q->type);
+	struct vchiq_mmal_port *port = get_port_data(ctx, q->type);
+	int ret = 0;
+
+	v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: type: %d count %d\n",
+		 __func__, q->type, count);
+	q_data->sequence = 0;
+
+	if (!ctx->component_enabled) {
+		ret = vchiq_mmal_component_enable(dev->instance,
+						  ctx->component);
+		if (ret)
+			v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed enabling component, ret %d\n",
+				 __func__, ret);
+		ctx->component_enabled = true;
+	}
+
+	if (port->enabled) {
+		unsigned int num_buffers;
+
+		init_completion(&ctx->frame_cmplt);
+
+		/*
+		 * This should only ever happen with DECODE and the MMAL output
+		 * port that has been enabled for resolution changed events.
+		 * In this case no buffers have been allocated or sent to the
+		 * component, so warn on that.
+		 */
+		WARN_ON(ctx->dev->role != DECODE);
+		WARN_ON(q->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
+		WARN_ON(atomic_read(&port->buffers_with_vpu));
+
+		/*
+		 * Disable will reread the port format, so retain buffer count.
+		 */
+		num_buffers = port->current_buffer.num;
+
+		ret = vchiq_mmal_port_disable(dev->instance, port);
+		if (ret)
+			v4l2_err(&ctx->dev->v4l2_dev, "%s: Error disabling port update buffer count, ret %d\n",
+				 __func__, ret);
+		bcm2835_codec_flush_buffers(ctx, port);
+		port->current_buffer.num = num_buffers;
+	}
+
+	if (count < port->minimum_buffer.num)
+		count = port->minimum_buffer.num;
+
+	if (port->current_buffer.num < count + 1) {
+		v4l2_dbg(2, debug, &ctx->dev->v4l2_dev, "%s: ctx:%p, buffer count changed %u to %u\n",
+			 __func__, ctx, port->current_buffer.num, count + 1);
+
+		port->current_buffer.num = count + 1;
+		ret = vchiq_mmal_port_set_format(dev->instance, port);
+		if (ret)
+			v4l2_err(&ctx->dev->v4l2_dev, "%s: Error updating buffer count, ret %d\n",
+				 __func__, ret);
+	}
+
+	if (dev->role == DECODE &&
+	    q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE &&
+	    !ctx->component->output[0].enabled) {
+		/*
+		 * Decode needs to enable the MMAL output/V4L2 CAPTURE
+		 * port at this point too so that we have everything
+		 * set up for dynamic resolution changes.
+		 */
+		ret = vchiq_mmal_port_enable(dev->instance,
+					     &ctx->component->output[0],
+					     op_buffer_cb);
+		if (ret)
+			v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed enabling o/p port, ret %d\n",
+				 __func__, ret);
+	}
+
+	if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
+		/*
+		 * Create the EOS buffer.
+		 * We only need the MMAL part, and want to NOT attach a memory
+		 * buffer to it as it should only take flags.
+		 */
+		memset(&q_data->eos_buffer, 0, sizeof(q_data->eos_buffer));
+		mmal_vchi_buffer_init(dev->instance,
+				      &q_data->eos_buffer.mmal);
+		q_data->eos_buffer_in_use = false;
+
+		ret = vchiq_mmal_port_enable(dev->instance,
+					     port,
+					     ip_buffer_cb);
+		if (ret)
+			v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed enabling i/p port, ret %d\n",
+				 __func__, ret);
+	} else {
+		if (!port->enabled) {
+			ret = vchiq_mmal_port_enable(dev->instance,
+						     port,
+						     op_buffer_cb);
+			if (ret)
+				v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed enabling o/p port, ret %d\n",
+					 __func__, ret);
+		}
+	}
+	v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: Done, ret %d\n",
+		 __func__, ret);
+	return ret;
+}
+
+static void bcm2835_codec_stop_streaming(struct vb2_queue *q)
+{
+	struct bcm2835_codec_ctx *ctx = vb2_get_drv_priv(q);
+	struct bcm2835_codec_dev *dev = ctx->dev;
+	struct bcm2835_codec_q_data *q_data = get_q_data(ctx, q->type);
+	struct vchiq_mmal_port *port = get_port_data(ctx, q->type);
+	struct vb2_v4l2_buffer *vbuf;
+	int ret;
+
+	v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: type: %d - return buffers\n",
+		 __func__, q->type);
+
+	init_completion(&ctx->frame_cmplt);
+
+	/* Clear out all buffers held by m2m framework */
+	for (;;) {
+		if (V4L2_TYPE_IS_OUTPUT(q->type))
+			vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
+		else
+			vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
+		if (!vbuf)
+			break;
+		v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: return buffer %p\n",
+			 __func__, vbuf);
+
+		v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_QUEUED);
+	}
+
+	/* Disable MMAL port - this will flush buffers back */
+	ret = vchiq_mmal_port_disable(dev->instance, port);
+	if (ret)
+		v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed disabling %s port, ret %d\n",
+			 __func__, V4L2_TYPE_IS_OUTPUT(q->type) ? "i/p" : "o/p",
+			 ret);
+
+	bcm2835_codec_flush_buffers(ctx, port);
+
+	if (dev->role == DECODE &&
+	    q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE &&
+	    ctx->component->input[0].enabled) {
+		/*
+		 * For decode we need to keep the MMAL output port enabled for
+		 * resolution changed events whenever the input is enabled.
+		 */
+		ret = vchiq_mmal_port_enable(dev->instance,
+					     &ctx->component->output[0],
+					     op_buffer_cb);
+		if (ret)
+			v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed enabling o/p port, ret %d\n",
+				 __func__, ret);
+	}
+
+	/* If both ports disabled, then disable the component */
+	if (ctx->component_enabled &&
+	    !ctx->component->input[0].enabled &&
+	    !ctx->component->output[0].enabled) {
+		ret = vchiq_mmal_component_disable(dev->instance,
+						   ctx->component);
+		if (ret)
+			v4l2_err(&ctx->dev->v4l2_dev, "%s: Failed enabling component, ret %d\n",
+				 __func__, ret);
+		ctx->component_enabled = false;
+	}
+
+	if (V4L2_TYPE_IS_OUTPUT(q->type))
+		mmal_vchi_buffer_cleanup(&q_data->eos_buffer.mmal);
+
+	v4l2_dbg(1, debug, &ctx->dev->v4l2_dev, "%s: done\n", __func__);
+}
+
+static const struct vb2_ops bcm2835_codec_qops = {
+	.queue_setup	 = bcm2835_codec_queue_setup,
+	.buf_init	 = bcm2835_codec_buf_init,
+	.buf_prepare	 = bcm2835_codec_buf_prepare,
+	.buf_queue	 = bcm2835_codec_buf_queue,
+	.buf_cleanup	 = bcm2835_codec_buffer_cleanup,
+	.start_streaming = bcm2835_codec_start_streaming,
+	.stop_streaming  = bcm2835_codec_stop_streaming,
+	.wait_prepare	 = vb2_ops_wait_prepare,
+	.wait_finish	 = vb2_ops_wait_finish,
+};
+
+static int queue_init(void *priv, struct vb2_queue *src_vq,
+		      struct vb2_queue *dst_vq)
+{
+	struct bcm2835_codec_ctx *ctx = priv;
+	int ret;
+
+	src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+	src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+	src_vq->drv_priv = ctx;
+	src_vq->buf_struct_size = sizeof(struct m2m_mmal_buffer);
+	src_vq->ops = &bcm2835_codec_qops;
+	src_vq->mem_ops = &vb2_dma_contig_memops;
+	src_vq->dev = &ctx->dev->device->dev;
+	src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	src_vq->lock = &ctx->dev->dev_mutex;
+
+	ret = vb2_queue_init(src_vq);
+	if (ret)
+		return ret;
+
+	dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+	dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+	dst_vq->drv_priv = ctx;
+	dst_vq->buf_struct_size = sizeof(struct m2m_mmal_buffer);
+	dst_vq->ops = &bcm2835_codec_qops;
+	dst_vq->mem_ops = &vb2_dma_contig_memops;
+	dst_vq->dev = &ctx->dev->device->dev;
+	dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	dst_vq->lock = &ctx->dev->dev_mutex;
+
+	return vb2_queue_init(dst_vq);
+}
+
+static void dec_add_profile_ctrls(struct bcm2835_codec_dev *const dev,
+				  struct v4l2_ctrl_handler *const hdl)
+{
+	struct v4l2_ctrl *ctrl;
+	unsigned int i;
+	const struct bcm2835_codec_fmt_list *const list = &dev->supported_fmts[0];
+
+	for (i = 0; i < list->num_entries; ++i) {
+		switch (list->list[i].fourcc) {
+		case V4L2_PIX_FMT_H264:
+			ctrl = v4l2_ctrl_new_std_menu(hdl, &bcm2835_codec_ctrl_ops,
+						      V4L2_CID_MPEG_VIDEO_H264_LEVEL,
+						      V4L2_MPEG_VIDEO_H264_LEVEL_5_1,
+						      ~(BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_0) |
+							BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1B) |
+							BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_1) |
+							BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_2) |
+							BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_3) |
+							BIT(V4L2_MPEG_VIDEO_H264_LEVEL_2_0) |
+							BIT(V4L2_MPEG_VIDEO_H264_LEVEL_2_1) |
+							BIT(V4L2_MPEG_VIDEO_H264_LEVEL_2_2) |
+							BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_0) |
+							BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_1) |
+							BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_2) |
+							BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_0) |
+							BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_1) |
+							BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_2) |
+							BIT(V4L2_MPEG_VIDEO_H264_LEVEL_5_0) |
+							BIT(V4L2_MPEG_VIDEO_H264_LEVEL_5_1)),
+						       V4L2_MPEG_VIDEO_H264_LEVEL_4_0);
+			ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+			ctrl = v4l2_ctrl_new_std_menu(hdl, &bcm2835_codec_ctrl_ops,
+						      V4L2_CID_MPEG_VIDEO_H264_PROFILE,
+						      V4L2_MPEG_VIDEO_H264_PROFILE_HIGH,
+						      ~(BIT(V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) |
+							BIT(V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE) |
+							BIT(V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) |
+							BIT(V4L2_MPEG_VIDEO_H264_PROFILE_HIGH)),
+						       V4L2_MPEG_VIDEO_H264_PROFILE_HIGH);
+			ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+			break;
+		case V4L2_PIX_FMT_MPEG2:
+			ctrl = v4l2_ctrl_new_std_menu(hdl, &bcm2835_codec_ctrl_ops,
+						      V4L2_CID_MPEG_VIDEO_MPEG2_LEVEL,
+						      V4L2_MPEG_VIDEO_MPEG2_LEVEL_HIGH,
+						      ~(BIT(V4L2_MPEG_VIDEO_MPEG2_LEVEL_LOW) |
+							BIT(V4L2_MPEG_VIDEO_MPEG2_LEVEL_MAIN) |
+							BIT(V4L2_MPEG_VIDEO_MPEG2_LEVEL_HIGH_1440) |
+							BIT(V4L2_MPEG_VIDEO_MPEG2_LEVEL_HIGH)),
+						      V4L2_MPEG_VIDEO_MPEG2_LEVEL_MAIN);
+			ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+			ctrl = v4l2_ctrl_new_std_menu(hdl, &bcm2835_codec_ctrl_ops,
+						      V4L2_CID_MPEG_VIDEO_MPEG2_PROFILE,
+						      V4L2_MPEG_VIDEO_MPEG2_PROFILE_MAIN,
+						      ~(BIT(V4L2_MPEG_VIDEO_MPEG2_PROFILE_SIMPLE) |
+							BIT(V4L2_MPEG_VIDEO_MPEG2_PROFILE_MAIN)),
+						      V4L2_MPEG_VIDEO_MPEG2_PROFILE_MAIN);
+			ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+			break;
+		case V4L2_PIX_FMT_MPEG4:
+			ctrl = v4l2_ctrl_new_std_menu(hdl, &bcm2835_codec_ctrl_ops,
+						      V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL,
+						      V4L2_MPEG_VIDEO_MPEG4_LEVEL_5,
+						      ~(BIT(V4L2_MPEG_VIDEO_MPEG4_LEVEL_0) |
+							BIT(V4L2_MPEG_VIDEO_MPEG4_LEVEL_0B) |
+							BIT(V4L2_MPEG_VIDEO_MPEG4_LEVEL_1) |
+							BIT(V4L2_MPEG_VIDEO_MPEG4_LEVEL_2) |
+							BIT(V4L2_MPEG_VIDEO_MPEG4_LEVEL_3) |
+							BIT(V4L2_MPEG_VIDEO_MPEG4_LEVEL_3B) |
+							BIT(V4L2_MPEG_VIDEO_MPEG4_LEVEL_4) |
+							BIT(V4L2_MPEG_VIDEO_MPEG4_LEVEL_5)),
+						      V4L2_MPEG_VIDEO_MPEG4_LEVEL_4);
+			ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+			ctrl = v4l2_ctrl_new_std_menu(hdl, &bcm2835_codec_ctrl_ops,
+						      V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE,
+						      V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE,
+						      ~(BIT(V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE) |
+							BIT(V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE)),
+						      V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE);
+			ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+			break;
+		/* No profiles defined by V4L2 */
+		case V4L2_PIX_FMT_H263:
+		case V4L2_PIX_FMT_JPEG:
+		case V4L2_PIX_FMT_MJPEG:
+		case V4L2_PIX_FMT_VC1_ANNEX_G:
+		default:
+			break;
+		}
+	}
+}
+
+/*
+ * File operations
+ */
+static int bcm2835_codec_open(struct file *file)
+{
+	struct bcm2835_codec_dev *dev = video_drvdata(file);
+	struct bcm2835_codec_ctx *ctx = NULL;
+	struct v4l2_ctrl_handler *hdl;
+	int rc = 0;
+
+	if (mutex_lock_interruptible(&dev->dev_mutex)) {
+		v4l2_err(&dev->v4l2_dev, "Mutex fail\n");
+		return -ERESTARTSYS;
+	}
+	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+	if (!ctx) {
+		rc = -ENOMEM;
+		goto open_unlock;
+	}
+
+	ctx->q_data[V4L2_M2M_SRC].fmt = get_default_format(dev, false);
+	ctx->q_data[V4L2_M2M_DST].fmt = get_default_format(dev, true);
+
+	ctx->q_data[V4L2_M2M_SRC].crop_width = DEFAULT_WIDTH;
+	ctx->q_data[V4L2_M2M_SRC].crop_height = DEFAULT_HEIGHT;
+	ctx->q_data[V4L2_M2M_SRC].height = DEFAULT_HEIGHT;
+	ctx->q_data[V4L2_M2M_SRC].bytesperline =
+			get_bytesperline(DEFAULT_WIDTH, DEFAULT_HEIGHT,
+					 ctx->q_data[V4L2_M2M_SRC].fmt,
+					 dev->role);
+	ctx->q_data[V4L2_M2M_SRC].sizeimage =
+		get_sizeimage(ctx->q_data[V4L2_M2M_SRC].bytesperline,
+			      ctx->q_data[V4L2_M2M_SRC].crop_width,
+			      ctx->q_data[V4L2_M2M_SRC].height,
+			      ctx->q_data[V4L2_M2M_SRC].fmt);
+	ctx->q_data[V4L2_M2M_SRC].field = V4L2_FIELD_NONE;
+
+	ctx->q_data[V4L2_M2M_DST].crop_width = DEFAULT_WIDTH;
+	ctx->q_data[V4L2_M2M_DST].crop_height = DEFAULT_HEIGHT;
+	ctx->q_data[V4L2_M2M_DST].height = DEFAULT_HEIGHT;
+	ctx->q_data[V4L2_M2M_DST].bytesperline =
+			get_bytesperline(DEFAULT_WIDTH, DEFAULT_HEIGHT,
+					 ctx->q_data[V4L2_M2M_DST].fmt,
+					 dev->role);
+	ctx->q_data[V4L2_M2M_DST].sizeimage =
+		get_sizeimage(ctx->q_data[V4L2_M2M_DST].bytesperline,
+			      ctx->q_data[V4L2_M2M_DST].crop_width,
+			      ctx->q_data[V4L2_M2M_DST].height,
+			      ctx->q_data[V4L2_M2M_DST].fmt);
+	ctx->q_data[V4L2_M2M_DST].aspect_ratio.numerator = 1;
+	ctx->q_data[V4L2_M2M_DST].aspect_ratio.denominator = 1;
+	ctx->q_data[V4L2_M2M_DST].field = V4L2_FIELD_NONE;
+
+	ctx->colorspace = V4L2_COLORSPACE_REC709;
+	ctx->bitrate = 10 * 1000 * 1000;
+
+	ctx->framerate_num = 30;
+	ctx->framerate_denom = 1;
+
+	/* Initialise V4L2 contexts */
+	v4l2_fh_init(&ctx->fh, video_devdata(file));
+	file->private_data = &ctx->fh;
+	ctx->dev = dev;
+	hdl = &ctx->hdl;
+	switch (dev->role) {
+	case ENCODE:
+	{
+		/* Encode controls */
+		v4l2_ctrl_handler_init(hdl, 13);
+
+		v4l2_ctrl_new_std_menu(hdl, &bcm2835_codec_ctrl_ops,
+				       V4L2_CID_MPEG_VIDEO_BITRATE_MODE,
+				       V4L2_MPEG_VIDEO_BITRATE_MODE_CBR, 0,
+				       V4L2_MPEG_VIDEO_BITRATE_MODE_VBR);
+		v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops,
+				  V4L2_CID_MPEG_VIDEO_BITRATE,
+				  25 * 1000, 25 * 1000 * 1000,
+				  25 * 1000, 10 * 1000 * 1000);
+		v4l2_ctrl_new_std_menu(hdl, &bcm2835_codec_ctrl_ops,
+				       V4L2_CID_MPEG_VIDEO_HEADER_MODE,
+				       V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME,
+				       0, V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME);
+		v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops,
+				  V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER,
+				  0, 1,
+				  1, 0);
+		v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops,
+				  V4L2_CID_MPEG_VIDEO_H264_I_PERIOD,
+				  0, 0x7FFFFFFF,
+				  1, 60);
+		v4l2_ctrl_new_std_menu(hdl, &bcm2835_codec_ctrl_ops,
+				       V4L2_CID_MPEG_VIDEO_H264_LEVEL,
+				       V4L2_MPEG_VIDEO_H264_LEVEL_5_1,
+				       ~(BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_0) |
+					 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1B) |
+					 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_1) |
+					 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_2) |
+					 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_3) |
+					 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_2_0) |
+					 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_2_1) |
+					 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_2_2) |
+					 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_0) |
+					 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_1) |
+					 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_2) |
+					 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_0) |
+					 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_1) |
+					 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_2) |
+					 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_5_0) |
+					 BIT(V4L2_MPEG_VIDEO_H264_LEVEL_5_1)),
+				       V4L2_MPEG_VIDEO_H264_LEVEL_4_0);
+		v4l2_ctrl_new_std_menu(hdl, &bcm2835_codec_ctrl_ops,
+				       V4L2_CID_MPEG_VIDEO_H264_PROFILE,
+				       V4L2_MPEG_VIDEO_H264_PROFILE_HIGH,
+				       ~(BIT(V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) |
+					 BIT(V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE) |
+					 BIT(V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) |
+					 BIT(V4L2_MPEG_VIDEO_H264_PROFILE_HIGH)),
+					V4L2_MPEG_VIDEO_H264_PROFILE_HIGH);
+		v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops,
+				  V4L2_CID_MPEG_VIDEO_H264_MIN_QP,
+				  0, 51,
+				  1, 20);
+		v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops,
+				  V4L2_CID_MPEG_VIDEO_H264_MAX_QP,
+				  0, 51,
+				  1, 51);
+		v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops,
+				  V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME,
+				  0, 0, 0, 0);
+		v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops,
+				  V4L2_CID_MPEG_VIDEO_B_FRAMES,
+				  0, 0,
+				  1, 0);
+		ctx->gop_size = v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops,
+						  V4L2_CID_MPEG_VIDEO_GOP_SIZE,
+						  0, 0x7FFFFFFF, 1, 60);
+		if (hdl->error) {
+			rc = hdl->error;
+			goto free_ctrl_handler;
+		}
+		ctx->fh.ctrl_handler = hdl;
+		v4l2_ctrl_handler_setup(hdl);
+	}
+	break;
+	case DECODE:
+	{
+		v4l2_ctrl_handler_init(hdl, 1 + dev->supported_fmts[0].num_entries * 2);
+
+		v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops,
+				  V4L2_CID_MIN_BUFFERS_FOR_CAPTURE,
+				  1, 1, 1, 1);
+		dec_add_profile_ctrls(dev, hdl);
+		if (hdl->error) {
+			rc = hdl->error;
+			goto free_ctrl_handler;
+		}
+		ctx->fh.ctrl_handler = hdl;
+		v4l2_ctrl_handler_setup(hdl);
+	}
+	break;
+	case ISP:
+	{
+		v4l2_ctrl_handler_init(hdl, 2);
+
+		v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops,
+				  V4L2_CID_HFLIP,
+				  1, 0, 1, 0);
+		v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops,
+				  V4L2_CID_VFLIP,
+				  1, 0, 1, 0);
+		if (hdl->error) {
+			rc = hdl->error;
+			goto free_ctrl_handler;
+		}
+		ctx->fh.ctrl_handler = hdl;
+		v4l2_ctrl_handler_setup(hdl);
+	}
+	break;
+	case DEINTERLACE:
+	{
+		v4l2_ctrl_handler_init(hdl, 0);
+	}
+	break;
+	case ENCODE_IMAGE:
+	{
+		/* Encode image controls */
+		v4l2_ctrl_handler_init(hdl, 1);
+
+		v4l2_ctrl_new_std(hdl, &bcm2835_codec_ctrl_ops,
+				  V4L2_CID_JPEG_COMPRESSION_QUALITY,
+				  1, 100,
+				  1, 80);
+		if (hdl->error) {
+			rc = hdl->error;
+			goto free_ctrl_handler;
+		}
+		ctx->fh.ctrl_handler = hdl;
+		v4l2_ctrl_handler_setup(hdl);
+	}
+	break;
+	case NUM_ROLES:
+	break;
+	}
+
+	ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &queue_init);
+
+	if (IS_ERR(ctx->fh.m2m_ctx)) {
+		rc = PTR_ERR(ctx->fh.m2m_ctx);
+
+		goto free_ctrl_handler;
+	}
+
+	/* Set both queues as buffered as we have buffering in the VPU. That
+	 * means that we will be scheduled whenever either an input or output
+	 * buffer is available (otherwise one of each are required).
+	 */
+	v4l2_m2m_set_src_buffered(ctx->fh.m2m_ctx, true);
+	v4l2_m2m_set_dst_buffered(ctx->fh.m2m_ctx, true);
+
+	ctx->fh.m2m_ctx->ignore_cap_streaming = true;
+
+	v4l2_fh_add(&ctx->fh);
+	atomic_inc(&dev->num_inst);
+
+	mutex_unlock(&dev->dev_mutex);
+	return 0;
+
+free_ctrl_handler:
+	v4l2_ctrl_handler_free(hdl);
+	kfree(ctx);
+open_unlock:
+	mutex_unlock(&dev->dev_mutex);
+	return rc;
+}
+
+static int bcm2835_codec_release(struct file *file)
+{
+	struct bcm2835_codec_dev *dev = video_drvdata(file);
+	struct bcm2835_codec_ctx *ctx = file2ctx(file);
+
+	v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: Releasing instance %p\n",
+		 __func__, ctx);
+
+	v4l2_fh_del(&ctx->fh);
+	v4l2_fh_exit(&ctx->fh);
+	v4l2_ctrl_handler_free(&ctx->hdl);
+	mutex_lock(&dev->dev_mutex);
+	v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
+
+	if (ctx->component)
+		vchiq_mmal_component_finalise(dev->instance, ctx->component);
+
+	mutex_unlock(&dev->dev_mutex);
+	kfree(ctx);
+
+	atomic_dec(&dev->num_inst);
+
+	return 0;
+}
+
+static const struct v4l2_file_operations bcm2835_codec_fops = {
+	.owner		= THIS_MODULE,
+	.open		= bcm2835_codec_open,
+	.release	= bcm2835_codec_release,
+	.poll		= v4l2_m2m_fop_poll,
+	.unlocked_ioctl	= video_ioctl2,
+	.mmap		= v4l2_m2m_fop_mmap,
+};
+
+static const struct video_device bcm2835_codec_videodev = {
+	.name		= MEM2MEM_NAME,
+	.vfl_dir	= VFL_DIR_M2M,
+	.fops		= &bcm2835_codec_fops,
+	.ioctl_ops	= &bcm2835_codec_ioctl_ops,
+	.minor		= -1,
+	.release	= video_device_release_empty,
+};
+
+static const struct v4l2_m2m_ops m2m_ops = {
+	.device_run	= device_run,
+	.job_ready	= job_ready,
+	.job_abort	= job_abort,
+};
+
+/* Size of the array to provide to the VPU when asking for the list of supported
+ * formats.
+ * The ISP component currently advertises 62 input formats, so add a small
+ * overhead on that.
+ */
+#define MAX_SUPPORTED_ENCODINGS 70
+
+/* Populate dev->supported_fmts with the formats supported by those ports. */
+static int bcm2835_codec_get_supported_fmts(struct bcm2835_codec_dev *dev)
+{
+	struct bcm2835_codec_fmt *list;
+	struct vchiq_mmal_component *component;
+	u32 fourccs[MAX_SUPPORTED_ENCODINGS];
+	u32 param_size = sizeof(fourccs);
+	unsigned int i, j, num_encodings;
+	int ret;
+
+	ret = vchiq_mmal_component_init(dev->instance, components[dev->role],
+					&component);
+	if (ret < 0) {
+		v4l2_err(&dev->v4l2_dev, "%s: failed to create component %s\n",
+			 __func__, components[dev->role]);
+		return -ENOMEM;
+	}
+
+	ret = vchiq_mmal_port_parameter_get(dev->instance,
+					    &component->input[0],
+					    MMAL_PARAMETER_SUPPORTED_ENCODINGS,
+					    &fourccs,
+					    &param_size);
+
+	if (ret) {
+		if (ret == MMAL_MSG_STATUS_ENOSPC) {
+			v4l2_err(&dev->v4l2_dev,
+				 "%s: port has more encodings than we provided space for. Some are dropped (%zu vs %u).\n",
+				 __func__, param_size / sizeof(u32),
+				 MAX_SUPPORTED_ENCODINGS);
+			num_encodings = MAX_SUPPORTED_ENCODINGS;
+		} else {
+			v4l2_err(&dev->v4l2_dev, "%s: get_param ret %u.\n",
+				 __func__, ret);
+			ret = -EINVAL;
+			goto destroy_component;
+		}
+	} else {
+		num_encodings = param_size / sizeof(u32);
+	}
+
+	/* Assume at this stage that all encodings will be supported in V4L2.
+	 * Any that aren't supported will waste a very small amount of memory.
+	 */
+	list = devm_kzalloc(&dev->device->dev,
+			    sizeof(struct bcm2835_codec_fmt) * num_encodings,
+			    GFP_KERNEL);
+	if (!list) {
+		ret = -ENOMEM;
+		goto destroy_component;
+	}
+	dev->supported_fmts[0].list = list;
+
+	for (i = 0, j = 0; i < num_encodings; i++) {
+		const struct bcm2835_codec_fmt *fmt = get_fmt(fourccs[i]);
+
+		if (fmt) {
+			list[j] = *fmt;
+			j++;
+		}
+	}
+	dev->supported_fmts[0].num_entries = j;
+
+	param_size = sizeof(fourccs);
+	ret = vchiq_mmal_port_parameter_get(dev->instance,
+					    &component->output[0],
+					    MMAL_PARAMETER_SUPPORTED_ENCODINGS,
+					    &fourccs,
+					    &param_size);
+
+	if (ret) {
+		if (ret == MMAL_MSG_STATUS_ENOSPC) {
+			v4l2_err(&dev->v4l2_dev,
+				 "%s: port has more encodings than we provided space for. Some are dropped (%zu vs %u).\n",
+				 __func__, param_size / sizeof(u32),
+				 MAX_SUPPORTED_ENCODINGS);
+			num_encodings = MAX_SUPPORTED_ENCODINGS;
+		} else {
+			ret = -EINVAL;
+			goto destroy_component;
+		}
+	} else {
+		num_encodings = param_size / sizeof(u32);
+	}
+	/* Assume at this stage that all encodings will be supported in V4L2. */
+	list = devm_kzalloc(&dev->device->dev,
+			    sizeof(struct bcm2835_codec_fmt) * num_encodings,
+			    GFP_KERNEL);
+	if (!list) {
+		ret = -ENOMEM;
+		goto destroy_component;
+	}
+	dev->supported_fmts[1].list = list;
+
+	for (i = 0, j = 0; i < num_encodings; i++) {
+		const struct bcm2835_codec_fmt *fmt = get_fmt(fourccs[i]);
+
+		if (fmt) {
+			list[j] = *fmt;
+			j++;
+		}
+	}
+	dev->supported_fmts[1].num_entries = j;
+
+	ret = 0;
+
+destroy_component:
+	vchiq_mmal_component_finalise(dev->instance, component);
+
+	return ret;
+}
+
+static int bcm2835_codec_create(struct bcm2835_codec_driver *drv,
+				struct bcm2835_codec_dev **new_dev,
+				enum bcm2835_codec_role role)
+{
+	struct vchiq_device *device = drv->device;
+	struct bcm2835_codec_dev *dev;
+	struct video_device *vfd;
+	int function;
+	int video_nr;
+	int ret;
+
+	dev = devm_kzalloc(&device->dev, sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	dev->device = device;
+
+	dev->role = role;
+
+	ret = vchiq_mmal_init(&device->dev, &dev->instance);
+	if (ret)
+		return ret;
+
+	ret = bcm2835_codec_get_supported_fmts(dev);
+	if (ret)
+		goto vchiq_finalise;
+
+	atomic_set(&dev->num_inst, 0);
+	mutex_init(&dev->dev_mutex);
+
+	/* Initialise the video device */
+	dev->vfd = bcm2835_codec_videodev;
+
+	vfd = &dev->vfd;
+	vfd->lock = &dev->dev_mutex;
+	vfd->v4l2_dev = &dev->v4l2_dev;
+	vfd->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING;
+	vfd->v4l2_dev->mdev = &drv->mdev;
+
+	ret = v4l2_device_register(&device->dev, &dev->v4l2_dev);
+	if (ret)
+		goto vchiq_finalise;
+
+	dev->max_w = MAX_W_CODEC;
+	dev->max_h = MAX_H_CODEC;
+
+	switch (role) {
+	case DECODE:
+		v4l2_disable_ioctl(vfd, VIDIOC_ENCODER_CMD);
+		v4l2_disable_ioctl(vfd, VIDIOC_TRY_ENCODER_CMD);
+		v4l2_disable_ioctl(vfd, VIDIOC_S_PARM);
+		v4l2_disable_ioctl(vfd, VIDIOC_G_PARM);
+		function = MEDIA_ENT_F_PROC_VIDEO_DECODER;
+		video_nr = decode_video_nr;
+		break;
+	case ENCODE:
+		v4l2_disable_ioctl(vfd, VIDIOC_DECODER_CMD);
+		v4l2_disable_ioctl(vfd, VIDIOC_TRY_DECODER_CMD);
+		function = MEDIA_ENT_F_PROC_VIDEO_ENCODER;
+		video_nr = encode_video_nr;
+		break;
+	case ISP:
+		v4l2_disable_ioctl(vfd, VIDIOC_DECODER_CMD);
+		v4l2_disable_ioctl(vfd, VIDIOC_TRY_DECODER_CMD);
+		v4l2_disable_ioctl(vfd, VIDIOC_S_PARM);
+		v4l2_disable_ioctl(vfd, VIDIOC_G_PARM);
+		function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
+		video_nr = isp_video_nr;
+		dev->max_w = MAX_W_ISP;
+		dev->max_h = MAX_H_ISP;
+		break;
+	case DEINTERLACE:
+		v4l2_disable_ioctl(vfd, VIDIOC_DECODER_CMD);
+		v4l2_disable_ioctl(vfd, VIDIOC_TRY_DECODER_CMD);
+		v4l2_disable_ioctl(vfd, VIDIOC_S_PARM);
+		v4l2_disable_ioctl(vfd, VIDIOC_G_PARM);
+		function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
+		video_nr = deinterlace_video_nr;
+		break;
+	case ENCODE_IMAGE:
+		v4l2_disable_ioctl(vfd, VIDIOC_DECODER_CMD);
+		v4l2_disable_ioctl(vfd, VIDIOC_TRY_DECODER_CMD);
+		function = MEDIA_ENT_F_PROC_VIDEO_ENCODER;
+		video_nr = encode_image_nr;
+		break;
+	default:
+		ret = -EINVAL;
+		goto unreg_dev;
+	}
+
+	ret = video_register_device(vfd, VFL_TYPE_VIDEO, video_nr);
+	if (ret) {
+		v4l2_err(&dev->v4l2_dev, "Failed to register video device\n");
+		goto unreg_dev;
+	}
+
+	video_set_drvdata(vfd, dev);
+	snprintf(vfd->name, sizeof(vfd->name), "%s-%s",
+		 bcm2835_codec_videodev.name, roles[role]);
+	v4l2_info(&dev->v4l2_dev, "Device registered as /dev/video%d\n",
+		  vfd->num);
+
+	*new_dev = dev;
+
+	dev->m2m_dev = v4l2_m2m_init(&m2m_ops);
+	if (IS_ERR(dev->m2m_dev)) {
+		v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem device\n");
+		ret = PTR_ERR(dev->m2m_dev);
+		goto err_m2m;
+	}
+
+	ret = v4l2_m2m_register_media_controller(dev->m2m_dev, vfd, function);
+	if (ret)
+		goto err_m2m;
+
+	v4l2_info(&dev->v4l2_dev, "Loaded V4L2 %s\n",
+		  roles[role]);
+	return 0;
+
+err_m2m:
+	v4l2_m2m_release(dev->m2m_dev);
+	video_unregister_device(&dev->vfd);
+unreg_dev:
+	v4l2_device_unregister(&dev->v4l2_dev);
+vchiq_finalise:
+	vchiq_mmal_finalise(dev->instance);
+	return ret;
+}
+
+static int bcm2835_codec_destroy(struct bcm2835_codec_dev *dev)
+{
+	if (!dev)
+		return -ENODEV;
+
+	v4l2_info(&dev->v4l2_dev, "Removing " MEM2MEM_NAME ", %s\n",
+		  roles[dev->role]);
+	v4l2_m2m_unregister_media_controller(dev->m2m_dev);
+	v4l2_m2m_release(dev->m2m_dev);
+	video_unregister_device(&dev->vfd);
+	v4l2_device_unregister(&dev->v4l2_dev);
+	vchiq_mmal_finalise(dev->instance);
+
+	return 0;
+}
+
+static int bcm2835_codec_probe(struct vchiq_device *device)
+{
+	struct bcm2835_codec_driver *drv;
+	struct media_device *mdev;
+	int ret = 0;
+
+	ret = dma_set_mask_and_coherent(&device->dev, DMA_BIT_MASK(32));
+	if (ret) {
+		dev_err(&device->dev, "dma_set_mask_and_coherent failed: %d\n",
+			ret);
+		return ret;
+	}
+
+	drv = devm_kzalloc(&device->dev, sizeof(*drv), GFP_KERNEL);
+	if (!drv)
+		return -ENOMEM;
+
+	drv->device = device;
+	mdev = &drv->mdev;
+	mdev->dev = &device->dev;
+
+	strscpy(mdev->model, bcm2835_codec_videodev.name, sizeof(mdev->model));
+	strscpy(mdev->serial, "0000", sizeof(mdev->serial));
+	snprintf(mdev->bus_info, sizeof(mdev->bus_info), "vchiq:%s",
+		 MEM2MEM_NAME);
+
+	/* This should return the vgencmd version information or such .. */
+	mdev->hw_revision = 1;
+	media_device_init(mdev);
+
+	ret = bcm2835_codec_create(drv, &drv->decode, DECODE);
+	if (ret)
+		goto out;
+
+	ret = bcm2835_codec_create(drv, &drv->encode, ENCODE);
+	if (ret)
+		goto out;
+
+	ret = bcm2835_codec_create(drv, &drv->isp, ISP);
+	if (ret)
+		goto out;
+
+	ret = bcm2835_codec_create(drv, &drv->deinterlace, DEINTERLACE);
+	if (ret)
+		goto out;
+
+	ret = bcm2835_codec_create(drv, &drv->encode_image, ENCODE_IMAGE);
+	if (ret)
+		goto out;
+
+	/* Register the media device node */
+	if (media_device_register(mdev) < 0)
+		goto out;
+
+	vchiq_set_drvdata(device, drv);
+
+	return 0;
+
+out:
+	if (drv->encode_image) {
+		bcm2835_codec_destroy(drv->encode_image);
+		drv->encode_image = NULL;
+	}
+	if (drv->deinterlace) {
+		bcm2835_codec_destroy(drv->deinterlace);
+		drv->deinterlace = NULL;
+	}
+	if (drv->isp) {
+		bcm2835_codec_destroy(drv->isp);
+		drv->isp = NULL;
+	}
+	if (drv->encode) {
+		bcm2835_codec_destroy(drv->encode);
+		drv->encode = NULL;
+	}
+	if (drv->decode) {
+		bcm2835_codec_destroy(drv->decode);
+		drv->decode = NULL;
+	}
+	return ret;
+}
+
+static void bcm2835_codec_remove(struct vchiq_device *device)
+{
+	struct bcm2835_codec_driver *drv = vchiq_get_drvdata(device);
+
+	media_device_unregister(&drv->mdev);
+
+	bcm2835_codec_destroy(drv->encode_image);
+
+	bcm2835_codec_destroy(drv->deinterlace);
+
+	bcm2835_codec_destroy(drv->isp);
+
+	bcm2835_codec_destroy(drv->encode);
+
+	bcm2835_codec_destroy(drv->decode);
+
+	media_device_cleanup(&drv->mdev);
+}
+
+static struct vchiq_driver bcm2835_v4l2_codec_driver = {
+	.probe = bcm2835_codec_probe,
+	.remove = bcm2835_codec_remove,
+	.driver = {
+		   .name = "bcm2835-codec",
+		   .owner = THIS_MODULE,
+		   },
+};
+
+module_vchiq_driver(bcm2835_v4l2_codec_driver);
+
+MODULE_DESCRIPTION("BCM2835 codec V4L2 driver");
+MODULE_AUTHOR("Dave Stevenson, <dave.stevenson@raspberrypi.com>");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("0.0.1");
+MODULE_ALIAS("vchiq:bcm2835-codec");
diff --git a/drivers/staging/vc04_services/bcm2835-isp/Kconfig b/drivers/staging/vc04_services/bcm2835-isp/Kconfig
new file mode 100644
index 000000000000..6222799ebe16
--- /dev/null
+++ b/drivers/staging/vc04_services/bcm2835-isp/Kconfig
@@ -0,0 +1,14 @@
+config VIDEO_ISP_BCM2835
+	tristate "BCM2835 ISP support"
+	depends on MEDIA_SUPPORT
+	depends on VIDEO_DEV && (ARCH_BCM2835 || COMPILE_TEST)
+	depends on MEDIA_CONTROLLER
+	select BCM2835_VCHIQ_MMAL
+	select VIDEOBUF2_DMA_CONTIG
+	help
+	  This is the V4L2 driver for the Broadcom BCM2835 ISP hardware.
+	  This operates over the VCHIQ interface to a service running on
+	  VideoCore.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called bcm2835-isp.
diff --git a/drivers/staging/vc04_services/bcm2835-isp/Makefile b/drivers/staging/vc04_services/bcm2835-isp/Makefile
new file mode 100644
index 000000000000..1994f12db3fc
--- /dev/null
+++ b/drivers/staging/vc04_services/bcm2835-isp/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+bcm2835-isp-objs := bcm2835-v4l2-isp.o
+
+obj-$(CONFIG_VIDEO_ISP_BCM2835) += bcm2835-isp.o
+
+ccflags-y += \
+	-D__VCCOREVER__=0x04000000
diff --git a/drivers/staging/vc04_services/bcm2835-isp/bcm2835-isp-ctrls.h b/drivers/staging/vc04_services/bcm2835-isp/bcm2835-isp-ctrls.h
new file mode 100644
index 000000000000..172605718cdf
--- /dev/null
+++ b/drivers/staging/vc04_services/bcm2835-isp/bcm2835-isp-ctrls.h
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Broadcom BCM2835 ISP driver
+ *
+ * Copyright © 2019-2020 Raspberry Pi (Trading) Ltd.
+ *
+ * Author: Naushir Patuck (naush@raspberrypi.com)
+ *
+ */
+
+#ifndef BCM2835_ISP_CTRLS
+#define BCM2835_ISP_CTRLS
+
+#include <linux/bcm2835-isp.h>
+
+struct bcm2835_isp_custom_ctrl {
+	const char *name;
+	u32 id;
+	u32 size;
+	u32 flags;
+};
+
+static const struct bcm2835_isp_custom_ctrl custom_ctrls[] = {
+	{
+		.name	= "Colour Correction Matrix",
+		.id	= V4L2_CID_USER_BCM2835_ISP_CC_MATRIX,
+		.size	= sizeof(struct bcm2835_isp_custom_ccm),
+		.flags  = 0
+	}, {
+		.name	= "Lens Shading",
+		.id	= V4L2_CID_USER_BCM2835_ISP_LENS_SHADING,
+		.size	= sizeof(struct bcm2835_isp_lens_shading),
+		.flags  = V4L2_CTRL_FLAG_EXECUTE_ON_WRITE
+	}, {
+		.name	= "Black Level",
+		.id	= V4L2_CID_USER_BCM2835_ISP_BLACK_LEVEL,
+		.size	= sizeof(struct bcm2835_isp_black_level),
+		.flags  = 0
+	}, {
+		.name	= "Green Equalisation",
+		.id	= V4L2_CID_USER_BCM2835_ISP_GEQ,
+		.size	= sizeof(struct bcm2835_isp_geq),
+		.flags  = 0
+	}, {
+		.name	= "Gamma",
+		.id	= V4L2_CID_USER_BCM2835_ISP_GAMMA,
+		.size	= sizeof(struct bcm2835_isp_gamma),
+		.flags  = 0
+	}, {
+		.name	= "Sharpen",
+		.id	= V4L2_CID_USER_BCM2835_ISP_SHARPEN,
+		.size	= sizeof(struct bcm2835_isp_sharpen),
+		.flags  = 0
+	}, {
+		.name	= "Denoise",
+		.id	= V4L2_CID_USER_BCM2835_ISP_DENOISE,
+		.size	= sizeof(struct bcm2835_isp_denoise),
+		.flags  = 0
+	}, {
+		.name	= "Colour Denoise",
+		.id	= V4L2_CID_USER_BCM2835_ISP_CDN,
+		.size	= sizeof(struct bcm2835_isp_cdn),
+		.flags  = 0
+	}, {
+		.name	= "Defective Pixel Correction",
+		.id	= V4L2_CID_USER_BCM2835_ISP_DPC,
+		.size	= sizeof(struct bcm2835_isp_dpc),
+		.flags  = 0
+	}
+};
+
+#endif
diff --git a/drivers/staging/vc04_services/bcm2835-isp/bcm2835-isp-fmts.h b/drivers/staging/vc04_services/bcm2835-isp/bcm2835-isp-fmts.h
new file mode 100644
index 000000000000..5c99d453f092
--- /dev/null
+++ b/drivers/staging/vc04_services/bcm2835-isp/bcm2835-isp-fmts.h
@@ -0,0 +1,558 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Broadcom BCM2835 ISP driver
+ *
+ * Copyright © 2019-2020 Raspberry Pi (Trading) Ltd.
+ *
+ * Author: Naushir Patuck (naush@raspberrypi.com)
+ *
+ */
+
+#ifndef BCM2835_ISP_FMTS
+#define BCM2835_ISP_FMTS
+
+#include <linux/videodev2.h>
+#include "../vchiq-mmal/mmal-encodings.h"
+
+struct bcm2835_isp_fmt {
+	u32 fourcc;
+	int depth;
+	int bytesperline_align;
+	u32 mmal_fmt;
+	int size_multiplier_x2;
+	u32 colorspace_mask;
+	enum v4l2_colorspace colorspace_default;
+	unsigned int step_size;
+};
+
+#define V4L2_COLORSPACE_MASK(colorspace) BIT(colorspace)
+
+#define V4L2_COLORSPACE_MASK_JPEG V4L2_COLORSPACE_MASK(V4L2_COLORSPACE_JPEG)
+#define V4L2_COLORSPACE_MASK_SMPTE170M V4L2_COLORSPACE_MASK(V4L2_COLORSPACE_SMPTE170M)
+#define V4L2_COLORSPACE_MASK_REC709 V4L2_COLORSPACE_MASK(V4L2_COLORSPACE_REC709)
+#define V4L2_COLORSPACE_MASK_SRGB V4L2_COLORSPACE_MASK(V4L2_COLORSPACE_SRGB)
+#define V4L2_COLORSPACE_MASK_RAW V4L2_COLORSPACE_MASK(V4L2_COLORSPACE_RAW)
+
+/*
+ * All three colour spaces JPEG, SMPTE170M and REC709 are fundamentally sRGB
+ * underneath (as near as makes no difference to us), just with different YCbCr
+ * encodings. Therefore the ISP can generate sRGB on its main output and any of
+ * the others on its low resolution output. Applications should, when using both
+ * outputs, program the colour spaces on them to be the same, matching whatever
+ * is requested for the low resolution output, even if the main output is
+ * producing an RGB format. In turn this requires us to allow all these colour
+ * spaces for every YUV/RGB output format.
+ */
+#define V4L2_COLORSPACE_MASK_ALL_SRGB (V4L2_COLORSPACE_MASK_JPEG |	\
+				       V4L2_COLORSPACE_MASK_SRGB |	\
+				       V4L2_COLORSPACE_MASK_SMPTE170M |	\
+				       V4L2_COLORSPACE_MASK_REC709)
+
+static const struct bcm2835_isp_fmt supported_formats[] = {
+	{
+		/* YUV formats */
+		.fourcc		    = V4L2_PIX_FMT_YUV420,
+		.depth		    = 8,
+		.bytesperline_align = 64,
+		.mmal_fmt	    = MMAL_ENCODING_I420,
+		.size_multiplier_x2 = 3,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_ALL_SRGB,
+		.colorspace_default = V4L2_COLORSPACE_JPEG,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_YVU420,
+		.depth		    = 8,
+		.bytesperline_align = 64,
+		.mmal_fmt	    = MMAL_ENCODING_YV12,
+		.size_multiplier_x2 = 3,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_ALL_SRGB,
+		.colorspace_default = V4L2_COLORSPACE_SMPTE170M,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_NV12,
+		.depth		    = 8,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_NV12,
+		.size_multiplier_x2 = 3,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_ALL_SRGB,
+		.colorspace_default = V4L2_COLORSPACE_SMPTE170M,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_NV21,
+		.depth		    = 8,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_NV21,
+		.size_multiplier_x2 = 3,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_ALL_SRGB,
+		.colorspace_default = V4L2_COLORSPACE_SMPTE170M,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_YUYV,
+		.depth		    = 16,
+		.bytesperline_align = 64,
+		.mmal_fmt	    = MMAL_ENCODING_YUYV,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_ALL_SRGB,
+		.colorspace_default = V4L2_COLORSPACE_SMPTE170M,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_UYVY,
+		.depth		    = 16,
+		.bytesperline_align = 64,
+		.mmal_fmt	    = MMAL_ENCODING_UYVY,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_ALL_SRGB,
+		.colorspace_default = V4L2_COLORSPACE_SMPTE170M,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_YVYU,
+		.depth		    = 16,
+		.bytesperline_align = 64,
+		.mmal_fmt	    = MMAL_ENCODING_YVYU,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_ALL_SRGB,
+		.colorspace_default = V4L2_COLORSPACE_SMPTE170M,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_VYUY,
+		.depth		    = 16,
+		.bytesperline_align = 64,
+		.mmal_fmt	    = MMAL_ENCODING_VYUY,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_ALL_SRGB,
+		.colorspace_default = V4L2_COLORSPACE_SMPTE170M,
+		.step_size	    = 2,
+	}, {
+		/* RGB formats */
+		.fourcc		    = V4L2_PIX_FMT_RGB24,
+		.depth		    = 24,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_RGB24,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_ALL_SRGB,
+		.colorspace_default = V4L2_COLORSPACE_SRGB,
+		.step_size	    = 1,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_RGB565,
+		.depth		    = 16,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_RGB16,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_ALL_SRGB,
+		.colorspace_default = V4L2_COLORSPACE_SRGB,
+		.step_size	    = 1,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_BGR24,
+		.depth		    = 24,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BGR24,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_ALL_SRGB,
+		.colorspace_default = V4L2_COLORSPACE_SRGB,
+		.step_size	    = 1,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_XBGR32,
+		.depth		    = 32,
+		.bytesperline_align = 64,
+		.mmal_fmt	    = MMAL_ENCODING_BGRA,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_ALL_SRGB,
+		.colorspace_default = V4L2_COLORSPACE_SRGB,
+		.step_size	    = 1,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_RGBX32,
+		.depth		    = 32,
+		.bytesperline_align = 64,
+		.mmal_fmt	    = MMAL_ENCODING_RGBA,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_ALL_SRGB,
+		.colorspace_default = V4L2_COLORSPACE_SRGB,
+		.step_size	    = 1,
+	}, {
+		/* Bayer formats */
+		/* 8 bit */
+		.fourcc		    = V4L2_PIX_FMT_SRGGB8,
+		.depth		    = 8,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SRGGB8,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SBGGR8,
+		.depth		    = 8,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SBGGR8,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SGRBG8,
+		.depth		    = 8,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SGRBG8,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SGBRG8,
+		.depth		    = 8,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SGBRG8,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		/* 10 bit */
+		.fourcc		    = V4L2_PIX_FMT_SRGGB10P,
+		.depth		    = 10,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SRGGB10P,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SBGGR10P,
+		.depth		    = 10,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SBGGR10P,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SGRBG10P,
+		.depth		    = 10,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SGRBG10P,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SGBRG10P,
+		.depth		    = 10,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SGBRG10P,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		/* 12 bit */
+		.fourcc		    = V4L2_PIX_FMT_SRGGB12P,
+		.depth		    = 12,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SRGGB12P,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SBGGR12P,
+		.depth		    = 12,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SBGGR12P,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SGRBG12P,
+		.depth		    = 12,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SGRBG12P,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SGBRG12P,
+		.depth		    = 12,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SGBRG12P,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		/* 14 bit */
+		.fourcc		    = V4L2_PIX_FMT_SRGGB14P,
+		.depth		    = 14,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SRGGB14P,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SBGGR14P,
+		.depth		    = 14,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SBGGR14P,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SGRBG14P,
+		.depth		    = 14,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SGRBG14P,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SGBRG14P,
+		.depth		    = 14,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SGBRG14P,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		/* 16 bit */
+		.fourcc		    = V4L2_PIX_FMT_SRGGB16,
+		.depth		    = 16,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SRGGB16,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SBGGR16,
+		.depth		    = 16,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SBGGR16,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SGRBG16,
+		.depth		    = 16,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SGRBG16,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SGBRG16,
+		.depth		    = 16,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SGBRG16,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		/* Bayer formats unpacked to 16bpp */
+		/* 10 bit */
+		.fourcc		    = V4L2_PIX_FMT_SRGGB10,
+		.depth		    = 16,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SRGGB10,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SBGGR10,
+		.depth		    = 16,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SBGGR10,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SGRBG10,
+		.depth		    = 16,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SGRBG10,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SGBRG10,
+		.depth		    = 16,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SGBRG10,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		/* 12 bit */
+		.fourcc		    = V4L2_PIX_FMT_SRGGB12,
+		.depth		    = 16,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SRGGB12,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SBGGR12,
+		.depth		    = 16,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SBGGR12,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SGRBG12,
+		.depth		    = 16,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SGRBG12,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SGBRG12,
+		.depth		    = 16,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SGBRG12,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		/* 14 bit */
+		.fourcc		    = V4L2_PIX_FMT_SRGGB14,
+		.depth		    = 16,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SRGGB14,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SBGGR14,
+		.depth		    = 16,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SBGGR14,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SGRBG14,
+		.depth		    = 16,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SGRBG14,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_PIX_FMT_SGBRG14,
+		.depth		    = 16,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_BAYER_SGBRG14,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		/* Monochrome MIPI formats */
+		/* 8 bit */
+		.fourcc		    = V4L2_PIX_FMT_GREY,
+		.depth		    = 8,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_GREY,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		/* 10 bit */
+		.fourcc		    = V4L2_PIX_FMT_Y10P,
+		.depth		    = 10,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_Y10P,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		/* 12 bit */
+		.fourcc		    = V4L2_PIX_FMT_Y12P,
+		.depth		    = 12,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_Y12P,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		/* 14 bit */
+		.fourcc		    = V4L2_PIX_FMT_Y14P,
+		.depth		    = 14,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_Y14P,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		/* 16 bit */
+		.fourcc		    = V4L2_PIX_FMT_Y16,
+		.depth		    = 16,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_Y16,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		/* 10 bit as 16bpp */
+		.fourcc		    = V4L2_PIX_FMT_Y10,
+		.depth		    = 16,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_Y10,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		/* 12 bit as 16bpp */
+		.fourcc		    = V4L2_PIX_FMT_Y12,
+		.depth		    = 16,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_Y12,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		/* 14 bit as 16bpp */
+		.fourcc		    = V4L2_PIX_FMT_Y14,
+		.depth		    = 16,
+		.bytesperline_align = 32,
+		.mmal_fmt	    = MMAL_ENCODING_Y14,
+		.size_multiplier_x2 = 2,
+		.colorspace_mask    = V4L2_COLORSPACE_MASK_RAW,
+		.colorspace_default = V4L2_COLORSPACE_RAW,
+		.step_size	    = 2,
+	}, {
+		.fourcc		    = V4L2_META_FMT_BCM2835_ISP_STATS,
+		.depth		    = 8,
+		.mmal_fmt	    = MMAL_ENCODING_BRCM_STATS,
+		/* The rest are not valid fields for stats. */
+	}
+};
+
+#endif
diff --git a/drivers/staging/vc04_services/bcm2835-isp/bcm2835-v4l2-isp.c b/drivers/staging/vc04_services/bcm2835-isp/bcm2835-v4l2-isp.c
new file mode 100644
index 000000000000..6c3c4fa1f344
--- /dev/null
+++ b/drivers/staging/vc04_services/bcm2835-isp/bcm2835-v4l2-isp.c
@@ -0,0 +1,1839 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Broadcom BCM2835 ISP driver
+ *
+ * Copyright © 2019-2020 Raspberry Pi (Trading) Ltd.
+ *
+ * Author: Naushir Patuck (naush@raspberrypi.com)
+ *
+ */
+
+#include <linux/module.h>
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "../interface/vchiq_arm/vchiq_bus.h"
+
+#include "../vchiq-mmal/mmal-msg.h"
+#include "../vchiq-mmal/mmal-parameters.h"
+#include "../vchiq-mmal/mmal-vchiq.h"
+
+#include "../vc-sm-cma/vc_sm_knl.h"
+
+#include "bcm2835-isp-ctrls.h"
+#include "bcm2835-isp-fmts.h"
+
+/*
+ * We want to instantiate 2 independent instances allowing 2 simultaneous users
+ * of the ISP hardware.
+ */
+#define BCM2835_ISP_NUM_INSTANCES 2
+
+MODULE_IMPORT_NS(DMA_BUF);
+
+static unsigned int debug;
+module_param(debug, uint, 0644);
+MODULE_PARM_DESC(debug, "activates debug info");
+
+static unsigned int video_nr[BCM2835_ISP_NUM_INSTANCES] = { 13, 20 };
+module_param_array(video_nr, uint, NULL, 0644);
+MODULE_PARM_DESC(video_nr, "base video device numbers");
+
+#define BCM2835_ISP_NAME "bcm2835-isp"
+#define BCM2835_ISP_ENTITY_NAME_LEN 32
+
+#define BCM2835_ISP_NUM_OUTPUTS 1
+#define BCM2835_ISP_NUM_CAPTURES 2
+#define BCM2835_ISP_NUM_METADATA 1
+
+#define BCM2835_ISP_NUM_NODES						\
+		(BCM2835_ISP_NUM_OUTPUTS + BCM2835_ISP_NUM_CAPTURES +	\
+		 BCM2835_ISP_NUM_METADATA)
+
+/* Default frame dimension of 1280 pixels. */
+#define DEFAULT_DIM 1280U
+/*
+ * Maximum frame dimension of 16384 pixels.  Even though the ISP runs in tiles,
+ * have a sensible limit so that we do not create an excessive number of tiles
+ * to process.
+ */
+#define MAX_DIM 16384U
+/*
+ * Minimum frame dimension of 64 pixels.  Anything lower, and the tiling
+ * algorithm may not be able to cope when applying filter context.
+ */
+#define MIN_DIM 64U
+
+/* Timeout for stop_streaming to allow all buffers to return */
+#define COMPLETE_TIMEOUT (2 * HZ)
+
+/* Per-queue, driver-specific private data */
+struct bcm2835_isp_q_data {
+	/*
+	 * These parameters should be treated as gospel, with everything else
+	 * being determined from them.
+	 */
+	unsigned int bytesperline;
+	unsigned int width;
+	unsigned int height;
+	unsigned int sizeimage;
+	enum v4l2_colorspace colorspace;
+	const struct bcm2835_isp_fmt *fmt;
+};
+
+/*
+ * Structure to describe a single node /dev/video<N> which represents a single
+ * input or output queue to the ISP device.
+ */
+struct bcm2835_isp_node {
+	int vfl_dir;
+	unsigned int id;
+	const char *name;
+	struct vchiq_mmal_port *port;
+	struct video_device vfd;
+	struct media_pad pad;
+	struct media_intf_devnode *intf_devnode;
+	struct media_link *intf_link;
+	struct mutex lock; /* top level device node lock */
+	struct mutex queue_lock;
+
+	struct vb2_queue queue;
+	unsigned int sequence;
+
+	/* The list of formats supported on the node. */
+	struct bcm2835_isp_fmt const **supported_fmts;
+	unsigned int num_supported_fmts;
+
+	struct bcm2835_isp_q_data q_data;
+
+	/* Parent device structure */
+	struct bcm2835_isp_dev *dev;
+
+	bool registered;
+	bool media_node_registered;
+};
+
+/*
+ * Structure representing the entire ISP device, comprising several input and
+ * output nodes /dev/video<N>.
+ */
+struct bcm2835_isp_dev {
+	struct v4l2_device v4l2_dev;
+	struct device *dev;
+	struct v4l2_ctrl_handler ctrl_handler;
+	struct media_device mdev;
+	struct media_entity entity;
+	bool media_device_registered;
+	bool media_entity_registered;
+	struct vchiq_mmal_instance *mmal_instance;
+	struct vchiq_mmal_component *component;
+	struct completion frame_cmplt;
+
+	struct bcm2835_isp_node node[BCM2835_ISP_NUM_NODES];
+	struct media_pad pad[BCM2835_ISP_NUM_NODES];
+	atomic_t num_streaming;
+
+	/* Image pipeline controls. */
+	int r_gain;
+	int b_gain;
+	struct dma_buf *last_ls_dmabuf;
+	struct mmal_parameter_lens_shading_v2 ls;
+};
+
+struct bcm2835_isp_buffer {
+	struct vb2_v4l2_buffer vb;
+	struct mmal_buffer mmal;
+};
+
+static
+inline struct bcm2835_isp_dev *node_get_dev(struct bcm2835_isp_node *node)
+{
+	return node->dev;
+}
+
+static inline bool node_is_output(struct bcm2835_isp_node *node)
+{
+	return node->queue.type == V4L2_BUF_TYPE_VIDEO_OUTPUT;
+}
+
+static inline bool node_is_capture(struct bcm2835_isp_node *node)
+{
+	return node->queue.type == V4L2_BUF_TYPE_VIDEO_CAPTURE;
+}
+
+static inline bool node_is_stats(struct bcm2835_isp_node *node)
+{
+	return node->queue.type == V4L2_BUF_TYPE_META_CAPTURE;
+}
+
+static inline enum v4l2_buf_type index_to_queue_type(int index)
+{
+	if (index < BCM2835_ISP_NUM_OUTPUTS)
+		return V4L2_BUF_TYPE_VIDEO_OUTPUT;
+	else if (index < BCM2835_ISP_NUM_OUTPUTS + BCM2835_ISP_NUM_CAPTURES)
+		return V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	else
+		return V4L2_BUF_TYPE_META_CAPTURE;
+}
+
+static int set_isp_param(struct bcm2835_isp_node *node, u32 parameter,
+			 void *value, u32 value_size)
+{
+	struct bcm2835_isp_dev *dev = node_get_dev(node);
+
+	return vchiq_mmal_port_parameter_set(dev->mmal_instance, node->port,
+					     parameter, value, value_size);
+}
+
+static int set_wb_gains(struct bcm2835_isp_node *node)
+{
+	struct bcm2835_isp_dev *dev = node_get_dev(node);
+	struct mmal_parameter_awbgains gains = {
+		.r_gain = { dev->r_gain, 1000 },
+		.b_gain = { dev->b_gain, 1000 }
+	};
+
+	return set_isp_param(node, MMAL_PARAMETER_CUSTOM_AWB_GAINS,
+			     &gains, sizeof(gains));
+}
+
+static int set_digital_gain(struct bcm2835_isp_node *node, uint32_t gain)
+{
+	struct s32_fract digital_gain = {
+		.numerator = gain,
+		.denominator = 1000
+	};
+
+	return set_isp_param(node, MMAL_PARAMETER_DIGITAL_GAIN,
+			     &digital_gain, sizeof(digital_gain));
+}
+
+static const struct bcm2835_isp_fmt *get_fmt(u32 mmal_fmt)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(supported_formats); i++) {
+		if (supported_formats[i].mmal_fmt == mmal_fmt)
+			return &supported_formats[i];
+	}
+	return NULL;
+}
+
+static const
+struct bcm2835_isp_fmt *find_format_by_fourcc(unsigned int fourcc,
+					      struct bcm2835_isp_node *node)
+{
+	const struct bcm2835_isp_fmt *fmt;
+	unsigned int i;
+
+	for (i = 0; i < node->num_supported_fmts; i++) {
+		fmt = node->supported_fmts[i];
+		if (fmt->fourcc == fourcc)
+			return fmt;
+	}
+
+	return NULL;
+}
+
+static const
+struct bcm2835_isp_fmt *find_format(struct v4l2_format *f,
+				    struct bcm2835_isp_node *node)
+{
+	return find_format_by_fourcc(node_is_stats(node) ?
+				     f->fmt.meta.dataformat :
+				     f->fmt.pix.pixelformat,
+				     node);
+}
+
+/* vb2_to_mmal_buffer() - converts vb2 buffer header to MMAL
+ *
+ * Copies all the required fields from a VB2 buffer to the MMAL buffer header,
+ * ready for sending to the VPU.
+ */
+static void vb2_to_mmal_buffer(struct mmal_buffer *buf,
+			       struct vb2_v4l2_buffer *vb2)
+{
+	u64 pts;
+
+	buf->mmal_flags = 0;
+	if (vb2->flags & V4L2_BUF_FLAG_KEYFRAME)
+		buf->mmal_flags |= MMAL_BUFFER_HEADER_FLAG_KEYFRAME;
+
+	/* Data must be framed correctly as one frame per buffer. */
+	buf->mmal_flags |= MMAL_BUFFER_HEADER_FLAG_FRAME_END;
+
+	buf->length = vb2->vb2_buf.planes[0].bytesused;
+	/*
+	 * Minor ambiguity in the V4L2 spec as to whether passing in a 0 length
+	 * buffer, or one with V4L2_BUF_FLAG_LAST set denotes end of stream.
+	 * Handle either.
+	 */
+	if (!buf->length || vb2->flags & V4L2_BUF_FLAG_LAST)
+		buf->mmal_flags |= MMAL_BUFFER_HEADER_FLAG_EOS;
+
+	/* vb2 timestamps in nsecs, mmal in usecs */
+	pts = vb2->vb2_buf.timestamp;
+	do_div(pts, 1000);
+	buf->pts = pts;
+	buf->dts = MMAL_TIME_UNKNOWN;
+}
+
+static void mmal_buffer_cb(struct vchiq_mmal_instance *instance,
+			   struct vchiq_mmal_port *port, int status,
+			   struct mmal_buffer *mmal_buf)
+{
+	struct bcm2835_isp_buffer *q_buf;
+	struct bcm2835_isp_node *node = port->cb_ctx;
+	struct bcm2835_isp_dev *dev = node_get_dev(node);
+	struct vb2_v4l2_buffer *vb2;
+
+	q_buf = container_of(mmal_buf, struct bcm2835_isp_buffer, mmal);
+	vb2 = &q_buf->vb;
+	v4l2_dbg(2, debug, &dev->v4l2_dev,
+		 "%s: port:%s[%d], status:%d, buf:%p, dmabuf:%p, length:%lu, flags %u, pts %lld\n",
+		 __func__, node_is_output(node) ? "input" : "output", node->id,
+		 status, mmal_buf, mmal_buf->dma_buf, mmal_buf->length,
+		 mmal_buf->mmal_flags, mmal_buf->pts);
+
+	if (mmal_buf->cmd)
+		v4l2_err(&dev->v4l2_dev,
+			 "%s: Unexpected event on output callback - %08x\n",
+			 __func__, mmal_buf->cmd);
+
+	if (status) {
+		/* error in transfer */
+		if (vb2) {
+			/* there was a buffer with the error so return it */
+			vb2_buffer_done(&vb2->vb2_buf, VB2_BUF_STATE_ERROR);
+		}
+		return;
+	}
+
+	/* vb2 timestamps in nsecs, mmal in usecs */
+	vb2->vb2_buf.timestamp = mmal_buf->pts * 1000;
+	vb2->sequence = node->sequence++;
+	vb2_set_plane_payload(&vb2->vb2_buf, 0, mmal_buf->length);
+	vb2_buffer_done(&vb2->vb2_buf, VB2_BUF_STATE_DONE);
+
+	if (!port->enabled)
+		complete(&dev->frame_cmplt);
+}
+
+struct colorspace_translation {
+	enum v4l2_colorspace v4l2_value;
+	u32 mmal_value;
+};
+
+static u32 translate_color_space(enum v4l2_colorspace color_space)
+{
+	static const struct colorspace_translation translations[] = {
+		{ V4L2_COLORSPACE_DEFAULT, MMAL_COLOR_SPACE_UNKNOWN },
+		{ V4L2_COLORSPACE_SMPTE170M, MMAL_COLOR_SPACE_ITUR_BT601 },
+		{ V4L2_COLORSPACE_SMPTE240M, MMAL_COLOR_SPACE_SMPTE240M },
+		{ V4L2_COLORSPACE_REC709, MMAL_COLOR_SPACE_ITUR_BT709 },
+		/* V4L2_COLORSPACE_BT878 unavailable */
+		{ V4L2_COLORSPACE_470_SYSTEM_M, MMAL_COLOR_SPACE_BT470_2_M },
+		{ V4L2_COLORSPACE_470_SYSTEM_BG, MMAL_COLOR_SPACE_BT470_2_BG },
+		{ V4L2_COLORSPACE_JPEG, MMAL_COLOR_SPACE_JPEG_JFIF },
+		/*
+		 * We don't have an encoding for SRGB as such, but VideoCore
+		 * will do the right thing if it gets "unknown".
+		 */
+		{ V4L2_COLORSPACE_SRGB, MMAL_COLOR_SPACE_UNKNOWN },
+		/* V4L2_COLORSPACE_OPRGB unavailable */
+		/* V4L2_COLORSPACE_BT2020 unavailable */
+		/* V4L2_COLORSPACE_RAW unavailable */
+		/* V4L2_COLORSPACE_DCI_P3 unavailable */
+	};
+
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(translations); i++) {
+		if (color_space == translations[i].v4l2_value)
+			return translations[i].mmal_value;
+	}
+
+	return MMAL_COLOR_SPACE_UNKNOWN;
+}
+
+static void setup_mmal_port_format(struct bcm2835_isp_node *node,
+				   struct vchiq_mmal_port *port)
+{
+	struct bcm2835_isp_q_data *q_data = &node->q_data;
+
+	port->format.encoding = q_data->fmt->mmal_fmt;
+	/* Raw image format - set width/height */
+	port->es.video.width = (q_data->bytesperline << 3) / q_data->fmt->depth;
+	port->es.video.height = q_data->height;
+	port->es.video.crop.width = q_data->width;
+	port->es.video.crop.height = q_data->height;
+	port->es.video.crop.x = 0;
+	port->es.video.crop.y = 0;
+	port->es.video.color_space = translate_color_space(q_data->colorspace);
+};
+
+static int setup_mmal_port(struct bcm2835_isp_node *node)
+{
+	struct bcm2835_isp_dev *dev = node_get_dev(node);
+	unsigned int enable = 1;
+	int ret;
+
+	v4l2_dbg(2, debug, &dev->v4l2_dev, "%s: setup %s[%d]\n", __func__,
+		 node->name, node->id);
+
+	vchiq_mmal_port_parameter_set(dev->mmal_instance, node->port,
+				      MMAL_PARAMETER_ZERO_COPY, &enable,
+				      sizeof(enable));
+	setup_mmal_port_format(node, node->port);
+	ret = vchiq_mmal_port_set_format(dev->mmal_instance, node->port);
+	if (ret < 0) {
+		v4l2_dbg(1, debug, &dev->v4l2_dev,
+			 "%s: vchiq_mmal_port_set_format failed\n",
+			 __func__);
+		return ret;
+	}
+
+	if (node->q_data.sizeimage < node->port->minimum_buffer.size) {
+		v4l2_err(&dev->v4l2_dev,
+			 "buffer size mismatch sizeimage %u < min size %u\n",
+			 node->q_data.sizeimage,
+			 node->port->minimum_buffer.size);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int bcm2835_isp_mmal_buf_cleanup(struct mmal_buffer *mmal_buf)
+{
+	mmal_vchi_buffer_cleanup(mmal_buf);
+
+	if (mmal_buf->dma_buf) {
+		dma_buf_put(mmal_buf->dma_buf);
+		mmal_buf->dma_buf = NULL;
+	}
+
+	return 0;
+}
+
+static int bcm2835_isp_node_queue_setup(struct vb2_queue *q,
+					unsigned int *nbuffers,
+					unsigned int *nplanes,
+					unsigned int sizes[],
+					struct device *alloc_devs[])
+{
+	struct bcm2835_isp_node *node = vb2_get_drv_priv(q);
+	unsigned int size;
+
+	if (setup_mmal_port(node))
+		return -EINVAL;
+
+	size = node->q_data.sizeimage;
+	if (size == 0) {
+		v4l2_info(&node_get_dev(node)->v4l2_dev,
+			  "%s: Image size unset in queue_setup for node %s[%d]\n",
+			  __func__, node->name, node->id);
+		return -EINVAL;
+	}
+
+	if (*nplanes)
+		return sizes[0] < size ? -EINVAL : 0;
+
+	*nplanes = 1;
+	sizes[0] = size;
+
+	node->port->current_buffer.size = size;
+
+	if (*nbuffers < node->port->minimum_buffer.num)
+		*nbuffers = node->port->minimum_buffer.num;
+
+	node->port->current_buffer.num = *nbuffers;
+
+	v4l2_dbg(2, debug, &node_get_dev(node)->v4l2_dev,
+		 "%s: Image size %u, nbuffers %u for node %s[%d]\n",
+		 __func__, sizes[0], *nbuffers, node->name, node->id);
+	return 0;
+}
+
+static int bcm2835_isp_buf_init(struct vb2_buffer *vb)
+{
+	struct bcm2835_isp_node *node = vb2_get_drv_priv(vb->vb2_queue);
+	struct bcm2835_isp_dev *dev = node_get_dev(node);
+	struct vb2_v4l2_buffer *vb2 = to_vb2_v4l2_buffer(vb);
+	struct bcm2835_isp_buffer *buf =
+		container_of(vb2, struct bcm2835_isp_buffer, vb);
+
+	v4l2_dbg(3, debug, &dev->v4l2_dev, "%s: vb %p\n", __func__, vb);
+
+	buf->mmal.buffer = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
+	buf->mmal.buffer_size = vb2_plane_size(&buf->vb.vb2_buf, 0);
+	mmal_vchi_buffer_init(dev->mmal_instance, &buf->mmal);
+	return 0;
+}
+
+static int bcm2835_isp_buf_prepare(struct vb2_buffer *vb)
+{
+	struct bcm2835_isp_node *node = vb2_get_drv_priv(vb->vb2_queue);
+	struct bcm2835_isp_dev *dev = node_get_dev(node);
+	struct vb2_v4l2_buffer *vb2 = to_vb2_v4l2_buffer(vb);
+	struct bcm2835_isp_buffer *buf =
+		container_of(vb2, struct bcm2835_isp_buffer, vb);
+	struct dma_buf *dma_buf;
+	int ret;
+
+	v4l2_dbg(3, debug, &dev->v4l2_dev, "%s: type: %d ptr %p\n",
+		 __func__, vb->vb2_queue->type, vb);
+
+	if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) {
+		if (vb2->field == V4L2_FIELD_ANY)
+			vb2->field = V4L2_FIELD_NONE;
+		if (vb2->field != V4L2_FIELD_NONE) {
+			v4l2_err(&dev->v4l2_dev,
+				 "%s field isn't supported\n", __func__);
+			return -EINVAL;
+		}
+	}
+
+	if (vb2_plane_size(vb, 0) < node->q_data.sizeimage) {
+		v4l2_err(&dev->v4l2_dev,
+			 "%s data will not fit into plane (%lu < %lu)\n",
+			 __func__, vb2_plane_size(vb, 0),
+			 (long)node->q_data.sizeimage);
+		return -EINVAL;
+	}
+
+	if (!V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type))
+		vb2_set_plane_payload(vb, 0, node->q_data.sizeimage);
+
+	switch (vb->memory) {
+	case VB2_MEMORY_DMABUF:
+		dma_buf = dma_buf_get(vb->planes[0].m.fd);
+
+		if (dma_buf != buf->mmal.dma_buf) {
+			/*
+			 * dmabuf either hasn't already been mapped, or it has
+			 * changed.
+			 */
+			if (buf->mmal.dma_buf) {
+				v4l2_err(&dev->v4l2_dev,
+					 "%s Buffer changed - why did the core not call cleanup?\n",
+					 __func__);
+				bcm2835_isp_mmal_buf_cleanup(&buf->mmal);
+			}
+
+			buf->mmal.dma_buf = dma_buf;
+		} else {
+			/*
+			 * Already have a reference to the buffer, so release it
+			 * here.
+			 */
+			dma_buf_put(dma_buf);
+		}
+		ret = 0;
+		break;
+	case VB2_MEMORY_MMAP:
+		/*
+		 * We want to do this at init, but vb2_core_expbuf checks that
+		 * the index < q->num_buffers, and q->num_buffers only gets
+		 * updated once all the buffers are allocated.
+		 */
+		if (!buf->mmal.dma_buf) {
+			ret = vb2_core_expbuf_dmabuf(vb->vb2_queue,
+						     vb->vb2_queue->type,
+						     vb, 0, O_CLOEXEC,
+						     &buf->mmal.dma_buf);
+			v4l2_dbg(3, debug, &dev->v4l2_dev,
+				 "%s: exporting ptr %p to dmabuf %p\n",
+				 __func__, vb, buf->mmal.dma_buf);
+			if (ret)
+				v4l2_err(&dev->v4l2_dev,
+					 "%s: Failed to expbuf idx %d, ret %d\n",
+					 __func__, vb->index, ret);
+		} else {
+			ret = 0;
+		}
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static void bcm2835_isp_node_buffer_queue(struct vb2_buffer *buf)
+{
+	struct bcm2835_isp_node *node = vb2_get_drv_priv(buf->vb2_queue);
+	struct vb2_v4l2_buffer *vbuf =
+		container_of(buf, struct vb2_v4l2_buffer, vb2_buf);
+	struct bcm2835_isp_buffer *buffer =
+		container_of(vbuf, struct bcm2835_isp_buffer, vb);
+	struct bcm2835_isp_dev *dev = node_get_dev(node);
+
+	v4l2_dbg(3, debug, &dev->v4l2_dev, "%s: node %s[%d], buffer %p\n",
+		 __func__, node->name, node->id, buffer);
+
+	vb2_to_mmal_buffer(&buffer->mmal, &buffer->vb);
+	v4l2_dbg(3, debug, &dev->v4l2_dev,
+		 "%s: node %s[%d] - submitting  mmal dmabuf %p\n", __func__,
+		 node->name, node->id, buffer->mmal.dma_buf);
+	vchiq_mmal_submit_buffer(dev->mmal_instance, node->port, &buffer->mmal);
+}
+
+static void bcm2835_isp_buffer_cleanup(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vb2 = to_vb2_v4l2_buffer(vb);
+	struct bcm2835_isp_buffer *buffer =
+		container_of(vb2, struct bcm2835_isp_buffer, vb);
+
+	bcm2835_isp_mmal_buf_cleanup(&buffer->mmal);
+}
+
+static int bcm2835_isp_node_start_streaming(struct vb2_queue *q,
+					    unsigned int count)
+{
+	struct bcm2835_isp_node *node = vb2_get_drv_priv(q);
+	struct bcm2835_isp_dev *dev = node_get_dev(node);
+	int ret;
+
+	v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: node %s[%d] (count %u)\n",
+		 __func__, node->name, node->id, count);
+
+	ret = vchiq_mmal_component_enable(dev->mmal_instance, dev->component);
+	if (ret) {
+		v4l2_err(&dev->v4l2_dev, "%s: Failed enabling component, ret %d\n",
+			 __func__, ret);
+		return -EIO;
+	}
+
+	node->sequence = 0;
+	node->port->cb_ctx = node;
+	ret = vchiq_mmal_port_enable(dev->mmal_instance, node->port,
+				     mmal_buffer_cb);
+	if (!ret)
+		atomic_inc(&dev->num_streaming);
+	else
+		v4l2_err(&dev->v4l2_dev,
+			 "%s: Failed enabling port, ret %d\n", __func__, ret);
+
+	return ret;
+}
+
+static void bcm2835_isp_node_stop_streaming(struct vb2_queue *q)
+{
+	struct bcm2835_isp_node *node = vb2_get_drv_priv(q);
+	struct bcm2835_isp_dev *dev = node_get_dev(node);
+	int ret;
+
+	v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: node %s[%d], mmal port %p\n",
+		 __func__, node->name, node->id, node->port);
+
+	init_completion(&dev->frame_cmplt);
+
+	/* Disable MMAL port - this will flush buffers back */
+	ret = vchiq_mmal_port_disable(dev->mmal_instance, node->port);
+	if (ret)
+		v4l2_err(&dev->v4l2_dev,
+			 "%s: Failed disabling %s port, ret %d\n", __func__,
+			 node_is_output(node) ? "i/p" : "o/p",
+			 ret);
+
+	while (atomic_read(&node->port->buffers_with_vpu)) {
+		v4l2_dbg(1, debug, &dev->v4l2_dev,
+			 "%s: Waiting for buffers to be returned - %d outstanding\n",
+			 __func__, atomic_read(&node->port->buffers_with_vpu));
+		ret = wait_for_completion_timeout(&dev->frame_cmplt,
+						  COMPLETE_TIMEOUT);
+		if (ret <= 0) {
+			v4l2_err(&dev->v4l2_dev,
+				 "%s: Timeout waiting for buffers to be returned - %d outstanding\n",
+				 __func__,
+				 atomic_read(&node->port->buffers_with_vpu));
+			break;
+		}
+	}
+
+	atomic_dec(&dev->num_streaming);
+	/* If all ports disabled, then disable the component */
+	if (atomic_read(&dev->num_streaming) == 0) {
+		/*
+		 * The ISP component on the firmware has a reference to the
+		 * dmabuf handle for the lens shading table.  Pass a null handle
+		 * to remove that reference now.
+		 */
+		memset(&dev->ls, 0, sizeof(dev->ls));
+		/* Must set a valid grid size for the FW */
+		dev->ls.grid_cell_size = 16;
+		set_isp_param(&dev->node[0],
+			      MMAL_PARAMETER_LENS_SHADING_OVERRIDE,
+			      &dev->ls, sizeof(dev->ls));
+		dev->last_ls_dmabuf = NULL;
+
+		ret = vchiq_mmal_component_disable(dev->mmal_instance,
+						   dev->component);
+		if (ret) {
+			v4l2_err(&dev->v4l2_dev,
+				 "%s: Failed disabling component, ret %d\n",
+				 __func__, ret);
+		}
+	}
+
+	/*
+	 * Simply wait for any vb2 buffers to finish. We could take steps to
+	 * make them complete more quickly if we care, or even return them
+	 * ourselves.
+	 */
+	vb2_wait_for_all_buffers(&node->queue);
+}
+
+static const struct vb2_ops bcm2835_isp_node_queue_ops = {
+	.queue_setup		= bcm2835_isp_node_queue_setup,
+	.buf_init		= bcm2835_isp_buf_init,
+	.buf_prepare		= bcm2835_isp_buf_prepare,
+	.buf_queue		= bcm2835_isp_node_buffer_queue,
+	.buf_cleanup		= bcm2835_isp_buffer_cleanup,
+	.start_streaming	= bcm2835_isp_node_start_streaming,
+	.stop_streaming		= bcm2835_isp_node_stop_streaming,
+};
+
+static const
+struct bcm2835_isp_fmt *get_default_format(struct bcm2835_isp_node *node)
+{
+	return node->supported_fmts[0];
+}
+
+static inline unsigned int get_bytesperline(int width,
+					    const struct bcm2835_isp_fmt *fmt)
+{
+	/* GPU aligns 24bpp images to a multiple of 32 pixels (not bytes). */
+	if (fmt->depth == 24)
+		return ALIGN(width, 32) * 3;
+	else
+		return ALIGN((width * fmt->depth) >> 3, fmt->bytesperline_align);
+}
+
+static inline unsigned int get_sizeimage(int bpl, int width, int height,
+					 const struct bcm2835_isp_fmt *fmt)
+{
+	return (bpl * height * fmt->size_multiplier_x2) >> 1;
+}
+
+static int map_ls_table(struct bcm2835_isp_dev *dev, struct dma_buf *dmabuf,
+			const struct bcm2835_isp_lens_shading *v4l2_ls)
+{
+	void *vcsm_handle;
+	int ret;
+
+	if (IS_ERR_OR_NULL(dmabuf))
+		return -EINVAL;
+
+	/*
+	 * struct bcm2835_isp_lens_shading and struct
+	 * mmal_parameter_lens_shading_v2 match so that we can do a
+	 * simple memcpy here.
+	 * Only the dmabuf to the actual table needs any manipulation.
+	 */
+	memcpy(&dev->ls, v4l2_ls, sizeof(dev->ls));
+	ret = vc_sm_cma_import_dmabuf(dmabuf, &vcsm_handle);
+	if (ret) {
+		dma_buf_put(dmabuf);
+		return ret;
+	}
+
+	dev->ls.mem_handle_table = vc_sm_cma_int_handle(vcsm_handle);
+	dev->last_ls_dmabuf = dmabuf;
+
+	vc_sm_cma_free(vcsm_handle);
+
+	return 0;
+}
+
+static int bcm2835_isp_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct bcm2835_isp_dev *dev =
+	      container_of(ctrl->handler, struct bcm2835_isp_dev, ctrl_handler);
+	struct bcm2835_isp_node *node = &dev->node[0];
+	int ret = 0;
+
+	/*
+	 * The ISP firmware driver will ensure these settings are applied on
+	 * a frame boundary, so we are safe to write them as they come in.
+	 *
+	 * Note that the bcm2835_isp_* param structures are identical to the
+	 * mmal-parameters.h definitions.  This avoids the need for unnecessary
+	 * field-by-field copying between structures.
+	 */
+	switch (ctrl->id) {
+	case V4L2_CID_RED_BALANCE:
+		dev->r_gain = ctrl->val;
+		ret = set_wb_gains(node);
+		break;
+	case V4L2_CID_BLUE_BALANCE:
+		dev->b_gain = ctrl->val;
+		ret = set_wb_gains(node);
+		break;
+	case V4L2_CID_DIGITAL_GAIN:
+		ret = set_digital_gain(node, ctrl->val);
+		break;
+	case V4L2_CID_USER_BCM2835_ISP_CC_MATRIX:
+		ret = set_isp_param(node, MMAL_PARAMETER_CUSTOM_CCM,
+				    ctrl->p_new.p_u8,
+				    sizeof(struct bcm2835_isp_custom_ccm));
+		break;
+	case V4L2_CID_USER_BCM2835_ISP_LENS_SHADING:
+	{
+		struct bcm2835_isp_lens_shading *v4l2_ls;
+
+		v4l2_ls = (struct bcm2835_isp_lens_shading *)ctrl->p_new.p_u8;
+		struct dma_buf *dmabuf = dma_buf_get(v4l2_ls->dmabuf);
+
+		if (dmabuf != dev->last_ls_dmabuf)
+			ret = map_ls_table(dev, dmabuf, v4l2_ls);
+
+		if (!ret && dev->ls.mem_handle_table)
+			/*
+			 * The VPU will take a reference on the vcsm handle,
+			 * which in turn will retain a reference on the dmabuf.
+			 * This code can therefore safely release all
+			 * references to the buffer.
+			 */
+			ret =
+			set_isp_param(node,
+				      MMAL_PARAMETER_LENS_SHADING_OVERRIDE,
+				      &dev->ls, sizeof(dev->ls));
+		else
+			ret = -EINVAL;
+
+		dma_buf_put(dmabuf);
+		break;
+	}
+	case V4L2_CID_USER_BCM2835_ISP_BLACK_LEVEL:
+		ret = set_isp_param(node, MMAL_PARAMETER_BLACK_LEVEL,
+				    ctrl->p_new.p_u8,
+				    sizeof(struct bcm2835_isp_black_level));
+		break;
+	case V4L2_CID_USER_BCM2835_ISP_GEQ:
+		ret = set_isp_param(node, MMAL_PARAMETER_GEQ,
+				    ctrl->p_new.p_u8,
+				    sizeof(struct bcm2835_isp_geq));
+		break;
+	case V4L2_CID_USER_BCM2835_ISP_GAMMA:
+		ret = set_isp_param(node, MMAL_PARAMETER_GAMMA,
+				    ctrl->p_new.p_u8,
+				    sizeof(struct bcm2835_isp_gamma));
+		break;
+	case V4L2_CID_USER_BCM2835_ISP_DENOISE:
+		ret = set_isp_param(node, MMAL_PARAMETER_DENOISE,
+				    ctrl->p_new.p_u8,
+				    sizeof(struct bcm2835_isp_denoise));
+		break;
+	case V4L2_CID_USER_BCM2835_ISP_CDN:
+		ret = set_isp_param(node, MMAL_PARAMETER_CDN,
+				    ctrl->p_new.p_u8,
+				    sizeof(struct bcm2835_isp_cdn));
+		break;
+	case V4L2_CID_USER_BCM2835_ISP_SHARPEN:
+		ret = set_isp_param(node, MMAL_PARAMETER_SHARPEN,
+				    ctrl->p_new.p_u8,
+				    sizeof(struct bcm2835_isp_sharpen));
+		break;
+	case V4L2_CID_USER_BCM2835_ISP_DPC:
+		ret = set_isp_param(node, MMAL_PARAMETER_DPC,
+				    ctrl->p_new.p_u8,
+				    sizeof(struct bcm2835_isp_dpc));
+		break;
+	default:
+		v4l2_info(&dev->v4l2_dev, "Unrecognised control\n");
+		ret = -EINVAL;
+	}
+
+	if (ret) {
+		v4l2_err(&dev->v4l2_dev, "%s: Failed setting ctrl \"%s\" (%08x), err %d\n",
+			 __func__, ctrl->name, ctrl->id, ret);
+		ret = -EIO;
+	}
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops bcm2835_isp_ctrl_ops = {
+	.s_ctrl = bcm2835_isp_s_ctrl,
+};
+
+static const struct v4l2_file_operations bcm2835_isp_fops = {
+	.owner		= THIS_MODULE,
+	.open		= v4l2_fh_open,
+	.release	= vb2_fop_release,
+	.poll		= vb2_fop_poll,
+	.unlocked_ioctl = video_ioctl2,
+	.mmap		= vb2_fop_mmap
+};
+
+static int populate_qdata_fmt(struct v4l2_format *f,
+			      struct bcm2835_isp_node *node)
+{
+	struct bcm2835_isp_dev *dev = node_get_dev(node);
+	struct bcm2835_isp_q_data *q_data = &node->q_data;
+	int ret;
+
+	if (!node_is_stats(node)) {
+		v4l2_dbg(1, debug, &dev->v4l2_dev,
+			 "%s: Setting pix format for type %d, wxh: %ux%u, fmt: %08x, size %u\n",
+			 __func__, f->type, f->fmt.pix.width, f->fmt.pix.height,
+			 f->fmt.pix.pixelformat, f->fmt.pix.sizeimage);
+
+		q_data->fmt = find_format(f, node);
+		q_data->width = f->fmt.pix.width;
+		q_data->height = f->fmt.pix.height;
+		q_data->height = f->fmt.pix.height;
+
+		/* All parameters should have been set correctly by try_fmt */
+		q_data->bytesperline = f->fmt.pix.bytesperline;
+		q_data->sizeimage = f->fmt.pix.sizeimage;
+
+		/* We must indicate which of the allowed colour spaces we have. */
+		q_data->colorspace = f->fmt.pix.colorspace;
+	} else {
+		v4l2_dbg(1, debug, &dev->v4l2_dev,
+			 "%s: Setting meta format for fmt: %08x, size %u\n",
+			 __func__, f->fmt.meta.dataformat,
+			 f->fmt.meta.buffersize);
+
+		q_data->fmt = find_format(f, node);
+		q_data->width = 0;
+		q_data->height = 0;
+		q_data->bytesperline = 0;
+		q_data->sizeimage = f->fmt.meta.buffersize;
+
+		/* This won't mean anything for metadata, but may as well fill it in. */
+		q_data->colorspace = V4L2_COLORSPACE_DEFAULT;
+	}
+
+	v4l2_dbg(1, debug, &dev->v4l2_dev,
+		 "%s: Calculated bpl as %u, size %u\n", __func__,
+		 q_data->bytesperline, q_data->sizeimage);
+
+	setup_mmal_port_format(node, node->port);
+	ret = vchiq_mmal_port_set_format(dev->mmal_instance, node->port);
+	if (ret) {
+		v4l2_err(&dev->v4l2_dev,
+			 "%s: Failed vchiq_mmal_port_set_format on port, ret %d\n",
+			 __func__, ret);
+		ret = -EINVAL;
+	}
+
+	if (q_data->sizeimage < node->port->minimum_buffer.size) {
+		v4l2_err(&dev->v4l2_dev,
+			 "%s: Current buffer size of %u < min buf size %u - driver mismatch to MMAL\n",
+			 __func__,
+			 q_data->sizeimage,
+			 node->port->minimum_buffer.size);
+	}
+
+	v4l2_dbg(1, debug, &dev->v4l2_dev,
+		 "%s: Set format for type %d, wxh: %dx%d, fmt: %08x, size %u\n",
+		 __func__, f->type, q_data->width, q_data->height,
+		 q_data->fmt->fourcc, q_data->sizeimage);
+
+	return ret;
+}
+
+static int bcm2835_isp_node_querycap(struct file *file, void *priv,
+				     struct v4l2_capability *cap)
+{
+	strscpy(cap->driver, BCM2835_ISP_NAME, sizeof(cap->driver));
+	strscpy(cap->card, BCM2835_ISP_NAME, sizeof(cap->card));
+	snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
+		 BCM2835_ISP_NAME);
+
+	return 0;
+}
+
+static int bcm2835_isp_node_g_fmt(struct file *file, void *priv,
+				  struct v4l2_format *f)
+{
+	struct bcm2835_isp_node *node = video_drvdata(file);
+
+	if (f->type != node->queue.type)
+		return -EINVAL;
+
+	if (node_is_stats(node)) {
+		f->fmt.meta.dataformat = V4L2_META_FMT_BCM2835_ISP_STATS;
+		f->fmt.meta.buffersize =
+			node->port->minimum_buffer.size;
+	} else {
+		struct bcm2835_isp_q_data *q_data = &node->q_data;
+
+		f->fmt.pix.width = q_data->width;
+		f->fmt.pix.height = q_data->height;
+		f->fmt.pix.field = V4L2_FIELD_NONE;
+		f->fmt.pix.pixelformat = q_data->fmt->fourcc;
+		f->fmt.pix.bytesperline = q_data->bytesperline;
+		f->fmt.pix.sizeimage = q_data->sizeimage;
+		f->fmt.pix.colorspace = q_data->colorspace;
+	}
+
+	return 0;
+}
+
+static int bcm2835_isp_node_enum_fmt(struct file *file, void  *priv,
+				     struct v4l2_fmtdesc *f)
+{
+	struct bcm2835_isp_node *node = video_drvdata(file);
+
+	if (f->type != node->queue.type)
+		return -EINVAL;
+
+	if (f->index < node->num_supported_fmts) {
+		/* Format found */
+		f->pixelformat = node->supported_fmts[f->index]->fourcc;
+		f->flags = 0;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int bcm2835_isp_enum_framesizes(struct file *file, void *priv,
+				       struct v4l2_frmsizeenum *fsize)
+{
+	struct bcm2835_isp_node *node = video_drvdata(file);
+	struct bcm2835_isp_dev *dev = node_get_dev(node);
+	const struct bcm2835_isp_fmt *fmt;
+
+	if (node_is_stats(node) || fsize->index)
+		return -EINVAL;
+
+	fmt = find_format_by_fourcc(fsize->pixel_format, node);
+	if (!fmt) {
+		v4l2_err(&dev->v4l2_dev, "Invalid pixel code: %x\n",
+			 fsize->pixel_format);
+		return -EINVAL;
+	}
+
+	fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
+	fsize->stepwise.min_width = MIN_DIM;
+	fsize->stepwise.max_width = MAX_DIM;
+	fsize->stepwise.step_width = fmt->step_size;
+
+	fsize->stepwise.min_height = MIN_DIM;
+	fsize->stepwise.max_height = MAX_DIM;
+	fsize->stepwise.step_height = fmt->step_size;
+
+	return 0;
+}
+
+static int bcm2835_isp_node_try_fmt(struct file *file, void *priv,
+				    struct v4l2_format *f)
+{
+	struct bcm2835_isp_node *node = video_drvdata(file);
+	const struct bcm2835_isp_fmt *fmt;
+
+	if (f->type != node->queue.type)
+		return -EINVAL;
+
+	fmt = find_format(f, node);
+	if (!fmt)
+		fmt = get_default_format(node);
+
+	if (!node_is_stats(node)) {
+		int is_rgb;
+
+		f->fmt.pix.width = max(min(f->fmt.pix.width, MAX_DIM),
+				       MIN_DIM);
+		f->fmt.pix.height = max(min(f->fmt.pix.height, MAX_DIM),
+					MIN_DIM);
+
+		f->fmt.pix.pixelformat = fmt->fourcc;
+
+		/*
+		 * Fill in the actual colour space when the requested one was
+		 * not supported. This also catches the case when the "default"
+		 * colour space was requested (as that's never in the mask).
+		 */
+		if (!(V4L2_COLORSPACE_MASK(f->fmt.pix.colorspace) & fmt->colorspace_mask))
+			f->fmt.pix.colorspace = fmt->colorspace_default;
+		/* In all cases, we only support the defaults for these: */
+		f->fmt.pix.ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(f->fmt.pix.colorspace);
+		f->fmt.pix.xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(f->fmt.pix.colorspace);
+		/* RAW counts as sRGB here so that we get full range. */
+		is_rgb = f->fmt.pix.colorspace == V4L2_COLORSPACE_SRGB ||
+			f->fmt.pix.colorspace == V4L2_COLORSPACE_RAW;
+		f->fmt.pix.quantization = V4L2_MAP_QUANTIZATION_DEFAULT(is_rgb, f->fmt.pix.colorspace,
+									f->fmt.pix.ycbcr_enc);
+
+		/* Respect any stride value (suitably aligned) that was requested. */
+		f->fmt.pix.bytesperline = max(get_bytesperline(f->fmt.pix.width, fmt),
+					      ALIGN(f->fmt.pix.bytesperline,
+						    fmt->bytesperline_align));
+		f->fmt.pix.field = V4L2_FIELD_NONE;
+		f->fmt.pix.sizeimage =
+			get_sizeimage(f->fmt.pix.bytesperline, f->fmt.pix.width,
+				      f->fmt.pix.height, fmt);
+	} else {
+		f->fmt.meta.dataformat = fmt->fourcc;
+		f->fmt.meta.buffersize = node->port->minimum_buffer.size;
+	}
+
+	return 0;
+}
+
+static int bcm2835_isp_node_s_fmt(struct file *file, void *priv,
+				  struct v4l2_format *f)
+{
+	struct bcm2835_isp_node *node = video_drvdata(file);
+	int ret;
+
+	if (f->type != node->queue.type)
+		return -EINVAL;
+
+	ret = bcm2835_isp_node_try_fmt(file, priv, f);
+	if (ret)
+		return ret;
+
+	v4l2_dbg(1, debug, &node_get_dev(node)->v4l2_dev,
+		 "%s: Set format for node %s[%d]\n",
+		 __func__, node->name, node->id);
+
+	return populate_qdata_fmt(f, node);
+}
+
+static int bcm2835_isp_node_s_selection(struct file *file, void *fh,
+					struct v4l2_selection *s)
+{
+	struct mmal_parameter_crop crop;
+	struct bcm2835_isp_node *node = video_drvdata(file);
+	struct bcm2835_isp_dev *dev = node_get_dev(node);
+
+	/* This return value is required fro V4L2 compliance. */
+	if (node_is_stats(node))
+		return -ENOTTY;
+
+	if (!s->r.width || !s->r.height)
+		return -EINVAL;
+
+	/* We can only set crop on the input. */
+	switch (s->target) {
+	case V4L2_SEL_TGT_CROP:
+		/*
+		 * Adjust the crop window if it goes outside of the frame
+		 * dimensions.
+		 */
+		s->r.left = min((unsigned int)max(s->r.left, 0),
+				node->q_data.width - MIN_DIM);
+		s->r.top = min((unsigned int)max(s->r.top, 0),
+			       node->q_data.height - MIN_DIM);
+		s->r.width = max(min(s->r.width,
+				     node->q_data.width - s->r.left), MIN_DIM);
+		s->r.height = max(min(s->r.height,
+				      node->q_data.height - s->r.top), MIN_DIM);
+		break;
+	case V4L2_SEL_TGT_CROP_DEFAULT:
+		/* Default (i.e. no) crop window. */
+		s->r.left = 0;
+		s->r.top = 0;
+		s->r.width = node->q_data.width;
+		s->r.height = node->q_data.height;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	crop.rect.x = s->r.left;
+	crop.rect.y = s->r.top;
+	crop.rect.width = s->r.width;
+	crop.rect.height = s->r.height;
+
+	return vchiq_mmal_port_parameter_set(dev->mmal_instance, node->port,
+					     MMAL_PARAMETER_CROP,
+					     &crop, sizeof(crop));
+}
+
+static int bcm2835_isp_node_g_selection(struct file *file, void *fh,
+					struct v4l2_selection *s)
+{
+	struct mmal_parameter_crop crop;
+	struct bcm2835_isp_node *node = video_drvdata(file);
+	struct bcm2835_isp_dev *dev = node_get_dev(node);
+	u32 crop_size = sizeof(crop);
+	int ret;
+
+	/* We can only return out an input crop. */
+	switch (s->target) {
+	case V4L2_SEL_TGT_CROP:
+		ret = vchiq_mmal_port_parameter_get(dev->mmal_instance,
+						    node->port,
+						    MMAL_PARAMETER_CROP,
+						    &crop, &crop_size);
+		if (!ret) {
+			s->r.left = crop.rect.x;
+			s->r.top = crop.rect.y;
+			s->r.width = crop.rect.width;
+			s->r.height = crop.rect.height;
+		}
+		break;
+	case V4L2_SEL_TGT_CROP_DEFAULT:
+	case V4L2_SEL_TGT_CROP_BOUNDS:
+		/* Default (i.e. no) crop window. */
+		s->r.left = 0;
+		s->r.top = 0;
+		s->r.width = node->q_data.width;
+		s->r.height = node->q_data.height;
+		ret = 0;
+		break;
+	default:
+		ret =  -EINVAL;
+	}
+
+	return ret;
+}
+
+static int bcm3285_isp_subscribe_event(struct v4l2_fh *fh,
+				       const struct v4l2_event_subscription *s)
+{
+	switch (s->type) {
+	/* Cannot change source parameters dynamically at runtime. */
+	case V4L2_EVENT_SOURCE_CHANGE:
+		return -EINVAL;
+	case V4L2_EVENT_CTRL:
+		return v4l2_ctrl_subscribe_event(fh, s);
+	default:
+		return v4l2_event_subscribe(fh, s, 4, NULL);
+	}
+}
+
+static const struct v4l2_ioctl_ops bcm2835_isp_node_ioctl_ops = {
+	.vidioc_querycap		= bcm2835_isp_node_querycap,
+	.vidioc_g_fmt_vid_cap		= bcm2835_isp_node_g_fmt,
+	.vidioc_g_fmt_vid_out		= bcm2835_isp_node_g_fmt,
+	.vidioc_g_fmt_meta_cap		= bcm2835_isp_node_g_fmt,
+	.vidioc_s_fmt_vid_cap		= bcm2835_isp_node_s_fmt,
+	.vidioc_s_fmt_vid_out		= bcm2835_isp_node_s_fmt,
+	.vidioc_s_fmt_meta_cap		= bcm2835_isp_node_s_fmt,
+	.vidioc_try_fmt_vid_cap		= bcm2835_isp_node_try_fmt,
+	.vidioc_try_fmt_vid_out		= bcm2835_isp_node_try_fmt,
+	.vidioc_try_fmt_meta_cap	= bcm2835_isp_node_try_fmt,
+	.vidioc_s_selection		= bcm2835_isp_node_s_selection,
+	.vidioc_g_selection		= bcm2835_isp_node_g_selection,
+
+	.vidioc_enum_fmt_vid_cap	= bcm2835_isp_node_enum_fmt,
+	.vidioc_enum_fmt_vid_out	= bcm2835_isp_node_enum_fmt,
+	.vidioc_enum_fmt_meta_cap	= bcm2835_isp_node_enum_fmt,
+	.vidioc_enum_framesizes		= bcm2835_isp_enum_framesizes,
+
+	.vidioc_reqbufs			= vb2_ioctl_reqbufs,
+	.vidioc_querybuf		= vb2_ioctl_querybuf,
+	.vidioc_qbuf			= vb2_ioctl_qbuf,
+	.vidioc_dqbuf			= vb2_ioctl_dqbuf,
+	.vidioc_expbuf			= vb2_ioctl_expbuf,
+	.vidioc_create_bufs		= vb2_ioctl_create_bufs,
+	.vidioc_prepare_buf		= vb2_ioctl_prepare_buf,
+
+	.vidioc_streamon		= vb2_ioctl_streamon,
+	.vidioc_streamoff		= vb2_ioctl_streamoff,
+
+	.vidioc_subscribe_event		= bcm3285_isp_subscribe_event,
+	.vidioc_unsubscribe_event	= v4l2_event_unsubscribe,
+};
+
+/*
+ * Size of the array to provide to the VPU when asking for the list of supported
+ * formats.
+ *
+ * The ISP component currently advertises 62 input formats, so add a small
+ * overhead on that. Should the component advertise more formats then the excess
+ * will be dropped and a warning logged.
+ */
+#define MAX_SUPPORTED_ENCODINGS 70
+
+/* Populate node->supported_fmts with the formats supported by those ports. */
+static int bcm2835_isp_get_supported_fmts(struct bcm2835_isp_node *node)
+{
+	struct bcm2835_isp_dev *dev = node_get_dev(node);
+	struct bcm2835_isp_fmt const **list;
+	unsigned int i, j, num_encodings;
+	u32 fourccs[MAX_SUPPORTED_ENCODINGS];
+	u32 param_size = sizeof(fourccs);
+	int ret;
+
+	ret = vchiq_mmal_port_parameter_get(dev->mmal_instance, node->port,
+					    MMAL_PARAMETER_SUPPORTED_ENCODINGS,
+					    &fourccs, &param_size);
+
+	if (ret) {
+		if (ret == MMAL_MSG_STATUS_ENOSPC) {
+			v4l2_err(&dev->v4l2_dev,
+				 "%s: port has more encodings than we provided space for. Some are dropped (%zu vs %u).\n",
+				 __func__, param_size / sizeof(u32),
+				 MAX_SUPPORTED_ENCODINGS);
+			num_encodings = MAX_SUPPORTED_ENCODINGS;
+		} else {
+			v4l2_err(&dev->v4l2_dev, "%s: get_param ret %u.\n",
+				 __func__, ret);
+			return -EINVAL;
+		}
+	} else {
+		num_encodings = param_size / sizeof(u32);
+	}
+
+	/*
+	 * Assume at this stage that all encodings will be supported in V4L2.
+	 * Any that aren't supported will waste a very small amount of memory.
+	 */
+	list = devm_kzalloc(dev->dev,
+			    sizeof(struct bcm2835_isp_fmt *) * num_encodings,
+			    GFP_KERNEL);
+	if (!list)
+		return -ENOMEM;
+	node->supported_fmts = list;
+
+	for (i = 0, j = 0; i < num_encodings; i++) {
+		const struct bcm2835_isp_fmt *fmt = get_fmt(fourccs[i]);
+
+		if (fmt) {
+			list[j] = fmt;
+			j++;
+		}
+	}
+	node->num_supported_fmts = j;
+
+	return 0;
+}
+
+/*
+ * Register a device node /dev/video<N> to go along with one of the ISP's input
+ * or output nodes.
+ */
+static int register_node(struct bcm2835_isp_dev *dev,
+			 unsigned int instance,
+			 struct bcm2835_isp_node *node,
+			 int index)
+{
+	struct video_device *vfd;
+	struct vb2_queue *queue;
+	int ret;
+
+	mutex_init(&node->lock);
+	mutex_init(&node->queue_lock);
+
+	node->dev = dev;
+	vfd = &node->vfd;
+	queue = &node->queue;
+	queue->type = index_to_queue_type(index);
+	/*
+	 * Setup the node type-specific params.
+	 *
+	 * Only the OUTPUT node can set controls and crop windows. However,
+	 * we must allow the s/g_selection ioctl on the stats node as v4l2
+	 * compliance expects it to return a -ENOTTY, and the framework
+	 * does not handle it if the ioctl is disabled.
+	 */
+	switch (queue->type) {
+	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+		vfd->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
+		node->id = index;
+		node->vfl_dir = VFL_DIR_TX;
+		node->name = "output";
+		node->port = &dev->component->input[node->id];
+		break;
+	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+		vfd->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+		/* First Capture node starts at id 0, etc. */
+		node->id = index - BCM2835_ISP_NUM_OUTPUTS;
+		node->vfl_dir = VFL_DIR_RX;
+		node->name = "capture";
+		node->port = &dev->component->output[node->id];
+		v4l2_disable_ioctl(&node->vfd, VIDIOC_S_CTRL);
+		v4l2_disable_ioctl(&node->vfd, VIDIOC_S_SELECTION);
+		v4l2_disable_ioctl(&node->vfd, VIDIOC_G_SELECTION);
+		break;
+	case V4L2_BUF_TYPE_META_CAPTURE:
+		vfd->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING;
+		node->id = index - BCM2835_ISP_NUM_OUTPUTS;
+		node->vfl_dir = VFL_DIR_RX;
+		node->name = "stats";
+		node->port = &dev->component->output[node->id];
+		v4l2_disable_ioctl(&node->vfd, VIDIOC_S_CTRL);
+		v4l2_disable_ioctl(&node->vfd, VIDIOC_S_SELECTION);
+		v4l2_disable_ioctl(&node->vfd, VIDIOC_G_SELECTION);
+		break;
+	}
+
+	/* We use the selection API instead of the old crop API. */
+	v4l2_disable_ioctl(vfd, VIDIOC_CROPCAP);
+	v4l2_disable_ioctl(vfd, VIDIOC_G_CROP);
+	v4l2_disable_ioctl(vfd, VIDIOC_S_CROP);
+
+	ret = bcm2835_isp_get_supported_fmts(node);
+	if (ret)
+		return ret;
+
+	/* Initialise the video node. */
+	vfd->vfl_type	= VFL_TYPE_VIDEO;
+	vfd->fops	= &bcm2835_isp_fops,
+	vfd->ioctl_ops	= &bcm2835_isp_node_ioctl_ops,
+	vfd->minor	= -1,
+	vfd->release	= video_device_release_empty,
+	vfd->queue	= &node->queue;
+	vfd->lock	= &node->lock;
+	vfd->v4l2_dev	= &dev->v4l2_dev;
+	vfd->vfl_dir	= node->vfl_dir;
+
+	node->q_data.fmt = get_default_format(node);
+	node->q_data.width = DEFAULT_DIM;
+	node->q_data.height = DEFAULT_DIM;
+	node->q_data.bytesperline =
+		get_bytesperline(DEFAULT_DIM, node->q_data.fmt);
+	node->q_data.sizeimage = node_is_stats(node) ?
+				 node->port->recommended_buffer.size :
+				 get_sizeimage(node->q_data.bytesperline,
+					       node->q_data.width,
+					       node->q_data.height,
+					       node->q_data.fmt);
+	node->q_data.colorspace = node->q_data.fmt->colorspace_default;
+
+	queue->io_modes = VB2_MMAP | VB2_DMABUF;
+	queue->drv_priv = node;
+	queue->ops = &bcm2835_isp_node_queue_ops;
+	queue->mem_ops = &vb2_dma_contig_memops;
+	queue->buf_struct_size = sizeof(struct bcm2835_isp_buffer);
+	queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+	queue->dev = dev->dev;
+	queue->lock = &node->queue_lock;
+
+	ret = vb2_queue_init(queue);
+	if (ret < 0) {
+		v4l2_info(&dev->v4l2_dev, "vb2_queue_init failed\n");
+		return ret;
+	}
+
+	/* Set some controls and defaults, but only on the VIDEO_OUTPUT node. */
+	if (node_is_output(node)) {
+		unsigned int i;
+
+		/* Use this ctrl template to assign custom ISP ctrls. */
+		struct v4l2_ctrl_config ctrl_template = {
+			.ops		= &bcm2835_isp_ctrl_ops,
+			.type		= V4L2_CTRL_TYPE_U8,
+			.def		= 0,
+			.min		= 0x00,
+			.max		= 0xff,
+			.step		= 1,
+		};
+
+		/* 3 standard controls, and an array of custom controls */
+		ret = v4l2_ctrl_handler_init(&dev->ctrl_handler,
+					     3 + ARRAY_SIZE(custom_ctrls));
+		if (ret) {
+			v4l2_err(&dev->v4l2_dev, "ctrl_handler init failed (%d)\n",
+				 ret);
+			goto queue_cleanup;
+		}
+
+		dev->r_gain = 1000;
+		dev->b_gain = 1000;
+
+		v4l2_ctrl_new_std(&dev->ctrl_handler,  &bcm2835_isp_ctrl_ops,
+				  V4L2_CID_RED_BALANCE, 1, 0xffff, 1,
+				  dev->r_gain);
+
+		v4l2_ctrl_new_std(&dev->ctrl_handler, &bcm2835_isp_ctrl_ops,
+				  V4L2_CID_BLUE_BALANCE, 1, 0xffff, 1,
+				  dev->b_gain);
+
+		v4l2_ctrl_new_std(&dev->ctrl_handler, &bcm2835_isp_ctrl_ops,
+				  V4L2_CID_DIGITAL_GAIN, 1, 0xffff, 1, 1000);
+
+		for (i = 0; i < ARRAY_SIZE(custom_ctrls); i++) {
+			ctrl_template.name = custom_ctrls[i].name;
+			ctrl_template.id = custom_ctrls[i].id;
+			ctrl_template.dims[0] = custom_ctrls[i].size;
+			ctrl_template.flags = custom_ctrls[i].flags;
+			v4l2_ctrl_new_custom(&dev->ctrl_handler,
+					     &ctrl_template, NULL);
+		}
+
+		node->vfd.ctrl_handler = &dev->ctrl_handler;
+		if (dev->ctrl_handler.error) {
+			ret = dev->ctrl_handler.error;
+			v4l2_err(&dev->v4l2_dev, "controls init failed (%d)\n",
+				 ret);
+			v4l2_ctrl_handler_free(&dev->ctrl_handler);
+			goto ctrl_cleanup;
+		}
+	}
+
+	/* Define the device names */
+	snprintf(vfd->name, sizeof(node->vfd.name), "%s-%s%d", BCM2835_ISP_NAME,
+		 node->name, node->id);
+
+	ret = video_register_device(vfd, VFL_TYPE_VIDEO, video_nr[instance]);
+	if (ret) {
+		v4l2_err(&dev->v4l2_dev,
+			 "Failed to register video %s[%d] device node\n",
+			 node->name, node->id);
+		goto ctrl_cleanup;
+	}
+
+	node->registered = true;
+	video_set_drvdata(vfd, node);
+
+	v4l2_info(&dev->v4l2_dev,
+		  "Device node %s[%d] registered as /dev/video%d\n",
+		  node->name, node->id, vfd->num);
+
+	return 0;
+
+ctrl_cleanup:
+	if (node_is_output(node))
+		v4l2_ctrl_handler_free(&dev->ctrl_handler);
+queue_cleanup:
+	vb2_queue_release(&node->queue);
+	return ret;
+}
+
+/* Unregister one of the /dev/video<N> nodes associated with the ISP. */
+static void bcm2835_unregister_node(struct bcm2835_isp_node *node)
+{
+	struct bcm2835_isp_dev *dev = node_get_dev(node);
+
+	v4l2_info(&dev->v4l2_dev,
+		  "Unregistering node %s[%d] device node /dev/video%d\n",
+		  node->name, node->id, node->vfd.num);
+
+	if (node->registered) {
+		video_unregister_device(&node->vfd);
+		if (node_is_output(node))
+			v4l2_ctrl_handler_free(&dev->ctrl_handler);
+		vb2_queue_release(&node->queue);
+	}
+
+	/*
+	 * node->supported_fmts.list is free'd automatically
+	 * as a managed resource.
+	 */
+	node->supported_fmts = NULL;
+	node->num_supported_fmts = 0;
+	node->vfd.ctrl_handler = NULL;
+	node->registered = false;
+}
+
+static void media_controller_unregister(struct bcm2835_isp_dev *dev)
+{
+	unsigned int i;
+
+	v4l2_info(&dev->v4l2_dev, "Unregister from media controller\n");
+
+	if (dev->media_device_registered) {
+		media_device_unregister(&dev->mdev);
+		media_device_cleanup(&dev->mdev);
+		dev->media_device_registered = false;
+	}
+
+	kfree(dev->entity.name);
+	dev->entity.name = NULL;
+
+	if (dev->media_entity_registered) {
+		media_device_unregister_entity(&dev->entity);
+		dev->media_entity_registered = false;
+	}
+
+	for (i = 0; i < BCM2835_ISP_NUM_NODES; i++) {
+		struct bcm2835_isp_node *node = &dev->node[i];
+
+		if (node->media_node_registered) {
+			media_remove_intf_links(node->intf_link->intf);
+			media_entity_remove_links(&dev->node[i].vfd.entity);
+			media_devnode_remove(node->intf_devnode);
+			media_device_unregister_entity(&node->vfd.entity);
+			kfree(node->vfd.entity.name);
+		}
+		node->media_node_registered = false;
+	}
+
+	dev->v4l2_dev.mdev = NULL;
+}
+
+static int media_controller_register_node(struct bcm2835_isp_dev *dev, int num)
+{
+	struct bcm2835_isp_node *node = &dev->node[num];
+	struct media_entity *entity = &node->vfd.entity;
+	int output = node_is_output(node);
+	char *name;
+	int ret;
+
+	v4l2_info(&dev->v4l2_dev,
+		  "Register %s node %d with media controller\n",
+		  output ? "output" : "capture", num);
+	entity->obj_type = MEDIA_ENTITY_TYPE_VIDEO_DEVICE;
+	entity->function = MEDIA_ENT_F_IO_V4L;
+	entity->info.dev.major = VIDEO_MAJOR;
+	entity->info.dev.minor = node->vfd.minor;
+	name = kmalloc(BCM2835_ISP_ENTITY_NAME_LEN, GFP_KERNEL);
+	if (!name) {
+		ret = -ENOMEM;
+		goto error_no_mem;
+	}
+	snprintf(name, BCM2835_ISP_ENTITY_NAME_LEN, "%s0-%s%d",
+		 BCM2835_ISP_NAME, output ? "output" : "capture", num);
+	entity->name = name;
+	node->pad.flags = output ? MEDIA_PAD_FL_SOURCE : MEDIA_PAD_FL_SINK;
+	ret = media_entity_pads_init(entity, 1, &node->pad);
+	if (ret)
+		goto error_pads_init;
+	ret = media_device_register_entity(&dev->mdev, entity);
+	if (ret)
+		goto error_register_entity;
+
+	node->intf_devnode = media_devnode_create(&dev->mdev,
+						  MEDIA_INTF_T_V4L_VIDEO, 0,
+						  VIDEO_MAJOR, node->vfd.minor);
+	if (!node->intf_devnode) {
+		ret = -ENOMEM;
+		goto error_devnode_create;
+	}
+
+	node->intf_link = media_create_intf_link(entity,
+						 &node->intf_devnode->intf,
+						 MEDIA_LNK_FL_IMMUTABLE |
+						 MEDIA_LNK_FL_ENABLED);
+	if (!node->intf_link) {
+		ret = -ENOMEM;
+		goto error_create_intf_link;
+	}
+
+	if (output)
+		ret = media_create_pad_link(entity, 0, &dev->entity, num,
+					    MEDIA_LNK_FL_IMMUTABLE |
+						    MEDIA_LNK_FL_ENABLED);
+	else
+		ret = media_create_pad_link(&dev->entity, num, entity, 0,
+					    MEDIA_LNK_FL_IMMUTABLE |
+					    MEDIA_LNK_FL_ENABLED);
+	if (ret)
+		goto error_create_pad_link;
+
+	dev->node[num].media_node_registered = true;
+	return 0;
+
+error_create_pad_link:
+	media_remove_intf_links(&node->intf_devnode->intf);
+error_create_intf_link:
+	media_devnode_remove(node->intf_devnode);
+error_devnode_create:
+	media_device_unregister_entity(&node->vfd.entity);
+error_register_entity:
+error_pads_init:
+	kfree(entity->name);
+	entity->name = NULL;
+error_no_mem:
+	if (ret)
+		v4l2_info(&dev->v4l2_dev, "Error registering node\n");
+
+	return ret;
+}
+
+static int media_controller_register(struct bcm2835_isp_dev *dev)
+{
+	char *name;
+	unsigned int i;
+	int ret;
+
+	v4l2_dbg(2, debug, &dev->v4l2_dev, "Registering with media controller\n");
+	dev->mdev.dev = dev->dev;
+	strscpy(dev->mdev.model, "bcm2835-isp",
+		sizeof(dev->mdev.model));
+	strscpy(dev->mdev.bus_info, "platform:bcm2835-isp",
+		sizeof(dev->mdev.bus_info));
+	media_device_init(&dev->mdev);
+	dev->v4l2_dev.mdev = &dev->mdev;
+
+	v4l2_dbg(2, debug, &dev->v4l2_dev, "Register entity for nodes\n");
+
+	name = kmalloc(BCM2835_ISP_ENTITY_NAME_LEN, GFP_KERNEL);
+	if (!name) {
+		ret = -ENOMEM;
+		goto done;
+	}
+	snprintf(name, BCM2835_ISP_ENTITY_NAME_LEN, "bcm2835_isp0");
+	dev->entity.name = name;
+	dev->entity.obj_type = MEDIA_ENTITY_TYPE_BASE;
+	dev->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
+
+	for (i = 0; i < BCM2835_ISP_NUM_NODES; i++) {
+		dev->pad[i].flags = node_is_output(&dev->node[i]) ?
+					MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE;
+	}
+
+	ret = media_entity_pads_init(&dev->entity, BCM2835_ISP_NUM_NODES,
+				     dev->pad);
+	if (ret)
+		goto done;
+
+	ret = media_device_register_entity(&dev->mdev, &dev->entity);
+	if (ret)
+		goto done;
+
+	dev->media_entity_registered = true;
+	for (i = 0; i < BCM2835_ISP_NUM_NODES; i++) {
+		ret = media_controller_register_node(dev, i);
+		if (ret)
+			goto done;
+	}
+
+	ret = media_device_register(&dev->mdev);
+	if (!ret)
+		dev->media_device_registered = true;
+done:
+	return ret;
+}
+
+static void bcm2835_isp_remove_instance(struct bcm2835_isp_dev *dev)
+{
+	unsigned int i;
+
+	media_controller_unregister(dev);
+
+	for (i = 0; i < BCM2835_ISP_NUM_NODES; i++)
+		bcm2835_unregister_node(&dev->node[i]);
+
+	v4l2_device_unregister(&dev->v4l2_dev);
+
+	if (dev->component)
+		vchiq_mmal_component_finalise(dev->mmal_instance,
+					      dev->component);
+
+	vchiq_mmal_finalise(dev->mmal_instance);
+}
+
+static int bcm2835_isp_probe_instance(struct vchiq_device *device,
+				      struct bcm2835_isp_dev **dev_int,
+				      unsigned int instance)
+{
+	struct bcm2835_isp_dev *dev;
+	unsigned int i;
+	int ret;
+
+	dev = devm_kzalloc(&device->dev, sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	*dev_int = dev;
+	dev->dev = &device->dev;
+
+	ret = v4l2_device_register(&device->dev, &dev->v4l2_dev);
+	if (ret)
+		return ret;
+
+	ret = vchiq_mmal_init(&device->dev, &dev->mmal_instance);
+	if (ret) {
+		v4l2_device_unregister(&dev->v4l2_dev);
+		return ret;
+	}
+
+	ret = vchiq_mmal_component_init(dev->mmal_instance, "ril.isp",
+					&dev->component);
+	if (ret) {
+		v4l2_err(&dev->v4l2_dev,
+			 "%s: failed to create ril.isp component\n", __func__);
+		return ret;
+	}
+
+	if (dev->component->inputs < BCM2835_ISP_NUM_OUTPUTS ||
+	    dev->component->outputs < BCM2835_ISP_NUM_CAPTURES +
+					BCM2835_ISP_NUM_METADATA) {
+		v4l2_err(&dev->v4l2_dev,
+			 "%s: ril.isp returned %d i/p (%d expected), %d o/p (%d expected) ports\n",
+			  __func__, dev->component->inputs,
+			  BCM2835_ISP_NUM_OUTPUTS,
+			  dev->component->outputs,
+			  BCM2835_ISP_NUM_CAPTURES + BCM2835_ISP_NUM_METADATA);
+		return -EINVAL;
+	}
+
+	atomic_set(&dev->num_streaming, 0);
+
+	for (i = 0; i < BCM2835_ISP_NUM_NODES; i++) {
+		struct bcm2835_isp_node *node = &dev->node[i];
+
+		ret = register_node(dev, instance, node, i);
+		if (ret)
+			return ret;
+	}
+
+	ret = media_controller_register(dev);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static void bcm2835_isp_remove(struct vchiq_device *device)
+{
+	struct bcm2835_isp_dev **bcm2835_isp_instances;
+	unsigned int i;
+
+	bcm2835_isp_instances = vchiq_get_drvdata(device);
+	for (i = 0; i < BCM2835_ISP_NUM_INSTANCES; i++) {
+		if (bcm2835_isp_instances[i])
+			bcm2835_isp_remove_instance(bcm2835_isp_instances[i]);
+	}
+}
+
+static int bcm2835_isp_probe(struct vchiq_device *device)
+{
+	struct bcm2835_isp_dev **bcm2835_isp_instances;
+	unsigned int i;
+	int ret;
+
+	ret = dma_set_mask_and_coherent(&device->dev, DMA_BIT_MASK(32));
+	if (ret) {
+		dev_err(&device->dev, "dma_set_mask_and_coherent failed: %d\n",
+			ret);
+		return ret;
+	}
+
+	bcm2835_isp_instances = devm_kzalloc(&device->dev,
+					     sizeof(bcm2835_isp_instances) *
+						      BCM2835_ISP_NUM_INSTANCES,
+					     GFP_KERNEL);
+	if (!bcm2835_isp_instances)
+		return -ENOMEM;
+
+	vchiq_set_drvdata(device, bcm2835_isp_instances);
+
+	for (i = 0; i < BCM2835_ISP_NUM_INSTANCES; i++) {
+		ret = bcm2835_isp_probe_instance(device,
+						 &bcm2835_isp_instances[i], i);
+		if (ret)
+			goto error;
+	}
+
+	dev_info(&device->dev, "Loaded V4L2 %s\n", BCM2835_ISP_NAME);
+	return 0;
+
+error:
+	bcm2835_isp_remove(device);
+
+	return ret;
+}
+
+static struct vchiq_driver bcm2835_isp_drv = {
+	.probe = bcm2835_isp_probe,
+	.remove = bcm2835_isp_remove,
+	.driver = {
+			.name = BCM2835_ISP_NAME,
+		  },
+};
+
+module_vchiq_driver(bcm2835_isp_drv);
+
+MODULE_DESCRIPTION("BCM2835 ISP driver");
+MODULE_AUTHOR("Naushir Patuck <naush@raspberrypi.com>");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("vchiq:bcm2835-isp");
diff --git a/drivers/staging/vc04_services/include/linux/broadcom/vc_sm_cma_ioctl.h b/drivers/staging/vc04_services/include/linux/broadcom/vc_sm_cma_ioctl.h
new file mode 100644
index 000000000000..107460ad1be3
--- /dev/null
+++ b/drivers/staging/vc04_services/include/linux/broadcom/vc_sm_cma_ioctl.h
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * Copyright 2019 Raspberry Pi (Trading) Ltd.  All rights reserved.
+ *
+ * Based on vmcs_sm_ioctl.h Copyright Broadcom Corporation.
+ */
+
+#ifndef __VC_SM_CMA_IOCTL_H
+#define __VC_SM_CMA_IOCTL_H
+
+/* ---- Include Files ---------------------------------------------------- */
+
+#if defined(__KERNEL__)
+#include <linux/types.h>	/* Needed for standard types */
+#else
+#include <stdint.h>
+#endif
+
+#include <linux/ioctl.h>
+
+/* ---- Constants and Types ---------------------------------------------- */
+
+#define VC_SM_CMA_RESOURCE_NAME               32
+#define VC_SM_CMA_RESOURCE_NAME_DEFAULT       "sm-host-resource"
+
+/* Type define used to create unique IOCTL number */
+#define VC_SM_CMA_MAGIC_TYPE                  'J'
+
+/* IOCTL commands on /dev/vc-sm-cma */
+enum vc_sm_cma_cmd_e {
+	VC_SM_CMA_CMD_ALLOC = 0x5A,	/* Start at 0x5A arbitrarily */
+
+	VC_SM_CMA_CMD_IMPORT_DMABUF,
+
+	VC_SM_CMA_CMD_CLEAN_INVALID2,
+
+	VC_SM_CMA_CMD_LAST	/* Do not delete */
+};
+
+/* Cache type supported, conveniently matches the user space definition in
+ * user-vcsm.h.
+ */
+enum vc_sm_cma_cache_e {
+	VC_SM_CMA_CACHE_NONE,
+	VC_SM_CMA_CACHE_HOST,
+	VC_SM_CMA_CACHE_VC,
+	VC_SM_CMA_CACHE_BOTH,
+};
+
+/* IOCTL Data structures */
+struct vc_sm_cma_ioctl_alloc {
+	/* user -> kernel */
+	__u32 size;
+	__u32 num;
+	__u32 cached;		/* enum vc_sm_cma_cache_e */
+	__u32 pad;
+	__u8 name[VC_SM_CMA_RESOURCE_NAME];
+
+	/* kernel -> user */
+	__s32 handle;
+	__u32 vc_handle;
+	__u64 dma_addr;
+};
+
+struct vc_sm_cma_ioctl_import_dmabuf {
+	/* user -> kernel */
+	__s32 dmabuf_fd;
+	__u32 cached;		/* enum vc_sm_cma_cache_e */
+	__u8 name[VC_SM_CMA_RESOURCE_NAME];
+
+	/* kernel -> user */
+	__s32 handle;
+	__u32 vc_handle;
+	__u32 size;
+	__u32 pad;
+	__u64 dma_addr;
+};
+
+/*
+ * Cache functions to be set to struct vc_sm_cma_ioctl_clean_invalid2
+ * invalidate_mode.
+ */
+#define VC_SM_CACHE_OP_NOP       0x00
+#define VC_SM_CACHE_OP_INV       0x01
+#define VC_SM_CACHE_OP_CLEAN     0x02
+#define VC_SM_CACHE_OP_FLUSH     0x03
+
+struct vc_sm_cma_ioctl_clean_invalid2 {
+	__u32 op_count;
+	__u32 pad;
+	struct vc_sm_cma_ioctl_clean_invalid_block {
+		__u32 invalidate_mode;
+		__u32 block_count;
+		void *  __user start_address;
+		__u32 block_size;
+		__u32 inter_block_stride;
+	} s[0];
+};
+
+/* IOCTL numbers */
+#define VC_SM_CMA_IOCTL_MEM_ALLOC\
+	_IOR(VC_SM_CMA_MAGIC_TYPE, VC_SM_CMA_CMD_ALLOC,\
+	 struct vc_sm_cma_ioctl_alloc)
+
+#define VC_SM_CMA_IOCTL_MEM_IMPORT_DMABUF\
+	_IOR(VC_SM_CMA_MAGIC_TYPE, VC_SM_CMA_CMD_IMPORT_DMABUF,\
+	 struct vc_sm_cma_ioctl_import_dmabuf)
+
+#define VC_SM_CMA_IOCTL_MEM_CLEAN_INVALID2\
+	_IOR(VC_SM_CMA_MAGIC_TYPE, VC_SM_CMA_CMD_CLEAN_INVALID2,\
+	 struct vc_sm_cma_ioctl_clean_invalid2)
+
+#endif /* __VC_SM_CMA_IOCTL_H */
diff --git a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
index 20ad6b1e44bc..81a7fce74870 100644
--- a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
+++ b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
@@ -20,9 +20,11 @@
 #include <linux/completion.h>
 #include <linux/list.h>
 #include <linux/of.h>
+#include <linux/of_platform.h>
 #include <linux/platform_device.h>
 #include <linux/compat.h>
 #include <linux/dma-mapping.h>
+#include <linux/dmapool.h>
 #include <linux/rcupdate.h>
 #include <linux/delay.h>
 #include <linux/slab.h>
@@ -52,6 +54,8 @@
 
 #define ARM_DS_ACTIVE	BIT(2)
 
+#define VCHIQ_DMA_POOL_SIZE PAGE_SIZE
+
 /* Override the default prefix, which would be vchiq_arm (from the filename) */
 #undef MODULE_PARAM_PREFIX
 #define MODULE_PARAM_PREFIX DEVICE_NAME "."
@@ -66,6 +70,9 @@
  */
 static struct vchiq_device *bcm2835_audio;
 static struct vchiq_device *bcm2835_camera;
+static struct vchiq_device *bcm2835_codec;
+static struct vchiq_device *bcm2835_isp;
+static struct vchiq_device *vcsm_cma;
 
 static const struct vchiq_platform_info bcm2835_info = {
 	.cache_line_size = 32,
@@ -75,6 +82,11 @@ static const struct vchiq_platform_info bcm2836_info = {
 	.cache_line_size = 64,
 };
 
+static const struct vchiq_platform_info bcm2711_info = {
+	.cache_line_size = 64,
+	.use_36bit_addrs = true,
+};
+
 struct vchiq_arm_state {
 	/* Keepalive-related data */
 	struct task_struct *ka_thread;
@@ -113,6 +125,7 @@ struct vchiq_pagelist_info {
 	struct pagelist *pagelist;
 	size_t pagelist_buffer_size;
 	dma_addr_t dma_addr;
+	bool is_from_pool;
 	enum dma_data_direction dma_dir;
 	unsigned int num_pages;
 	unsigned int pages_need_release;
@@ -121,6 +134,10 @@ struct vchiq_pagelist_info {
 	unsigned int scatterlist_mapped;
 };
 
+static struct dma_pool *g_dma_pool;
+static unsigned int g_use_36bit_addrs = 0;
+static struct device *g_dma_dev;
+
 static int
 vchiq_blocking_bulk_transfer(struct vchiq_instance *instance, unsigned int handle, void *data,
 			     unsigned int size, enum vchiq_bulk_dir dir);
@@ -150,15 +167,20 @@ static void
 cleanup_pagelistinfo(struct vchiq_instance *instance, struct vchiq_pagelist_info *pagelistinfo)
 {
 	if (pagelistinfo->scatterlist_mapped) {
-		dma_unmap_sg(instance->state->dev, pagelistinfo->scatterlist,
+		dma_unmap_sg(g_dma_dev, pagelistinfo->scatterlist,
 			     pagelistinfo->num_pages, pagelistinfo->dma_dir);
 	}
 
 	if (pagelistinfo->pages_need_release)
 		unpin_user_pages(pagelistinfo->pages, pagelistinfo->num_pages);
 
-	dma_free_coherent(instance->state->dev, pagelistinfo->pagelist_buffer_size,
-			  pagelistinfo->pagelist, pagelistinfo->dma_addr);
+	if (pagelistinfo->is_from_pool) {
+		dma_pool_free(g_dma_pool, pagelistinfo->pagelist,
+			      pagelistinfo->dma_addr);
+	} else {
+		dma_free_coherent(instance->state->dev, pagelistinfo->pagelist_buffer_size,
+				  pagelistinfo->pagelist, pagelistinfo->dma_addr);
+	}
 }
 
 static inline bool
@@ -244,6 +266,7 @@ create_pagelist(struct vchiq_instance *instance, char *buf, char __user *ubuf,
 	u32 *addrs;
 	unsigned int num_pages, offset, i, k;
 	int actual_pages;
+	bool is_from_pool;
 	size_t pagelist_size;
 	struct scatterlist *scatterlist, *sg;
 	int dma_buffers;
@@ -275,8 +298,14 @@ create_pagelist(struct vchiq_instance *instance, char *buf, char __user *ubuf,
 	/* Allocate enough storage to hold the page pointers and the page
 	 * list
 	 */
-	pagelist = dma_alloc_coherent(instance->state->dev, pagelist_size, &dma_addr,
-				      GFP_KERNEL);
+	if (pagelist_size > VCHIQ_DMA_POOL_SIZE) {
+		pagelist = dma_alloc_coherent(instance->state->dev, pagelist_size, &dma_addr,
+					      GFP_KERNEL);
+		is_from_pool = false;
+	} else {
+		pagelist = dma_pool_alloc(g_dma_pool, GFP_KERNEL, &dma_addr);
+		is_from_pool = true;
+	}
 
 	dev_dbg(instance->state->dev, "arm: %pK\n", pagelist);
 
@@ -297,6 +326,7 @@ create_pagelist(struct vchiq_instance *instance, char *buf, char __user *ubuf,
 	pagelistinfo->pagelist = pagelist;
 	pagelistinfo->pagelist_buffer_size = pagelist_size;
 	pagelistinfo->dma_addr = dma_addr;
+	pagelistinfo->is_from_pool = is_from_pool;
 	pagelistinfo->dma_dir =  (type == PAGELIST_WRITE) ?
 				  DMA_TO_DEVICE : DMA_FROM_DEVICE;
 	pagelistinfo->num_pages = num_pages;
@@ -362,7 +392,7 @@ create_pagelist(struct vchiq_instance *instance, char *buf, char __user *ubuf,
 		count -= len;
 	}
 
-	dma_buffers = dma_map_sg(instance->state->dev,
+	dma_buffers = dma_map_sg(g_dma_dev,
 				 scatterlist,
 				 num_pages,
 				 pagelistinfo->dma_dir);
@@ -376,22 +406,58 @@ create_pagelist(struct vchiq_instance *instance, char *buf, char __user *ubuf,
 
 	/* Combine adjacent blocks for performance */
 	k = 0;
-	for_each_sg(scatterlist, sg, dma_buffers, i) {
-		unsigned int len = sg_dma_len(sg);
-		dma_addr_t addr = sg_dma_address(sg);
-
-		/* Note: addrs is the address + page_count - 1
-		 * The firmware expects blocks after the first to be page-
-		 * aligned and a multiple of the page size
-		 */
-		WARN_ON(len == 0);
-		WARN_ON(i && (i != (dma_buffers - 1)) && (len & ~PAGE_MASK));
-		WARN_ON(i && (addr & ~PAGE_MASK));
-		if (is_adjacent_block(addrs, addr, k))
-			addrs[k - 1] += ((len + PAGE_SIZE - 1) >> PAGE_SHIFT);
-		else
-			addrs[k++] = (addr & PAGE_MASK) |
-				(((len + PAGE_SIZE - 1) >> PAGE_SHIFT) - 1);
+	if (g_use_36bit_addrs) {
+		for_each_sg(scatterlist, sg, dma_buffers, i) {
+			unsigned int len = sg_dma_len(sg);
+			dma_addr_t addr = sg_dma_address(sg);
+			u32 page_id = (u32)((addr >> 4) & ~0xff);
+			u32 sg_pages = (len + PAGE_SIZE - 1) >> PAGE_SHIFT;
+
+			/* Note: addrs is the address + page_count - 1
+			 * The firmware expects blocks after the first to be page-
+			 * aligned and a multiple of the page size
+			 */
+			WARN_ON(len == 0);
+			WARN_ON(i &&
+				(i != (dma_buffers - 1)) && (len & ~PAGE_MASK));
+			WARN_ON(i && (addr & ~PAGE_MASK));
+			WARN_ON(upper_32_bits(addr) > 0xf);
+
+			if (k > 0 &&
+			    ((addrs[k - 1] & ~0xff) +
+			     (((addrs[k - 1] & 0xff) + 1) << 8)
+			     == page_id)) {
+				u32 inc_pages = min(sg_pages,
+						    0xff - (addrs[k - 1] & 0xff));
+				addrs[k - 1] += inc_pages;
+				page_id += inc_pages << 8;
+				sg_pages -= inc_pages;
+			}
+			while (sg_pages) {
+				u32 inc_pages = min(sg_pages, 0x100u);
+				addrs[k++] = page_id | (inc_pages - 1);
+				page_id += inc_pages << 8;
+				sg_pages -= inc_pages;
+			}
+		}
+	} else {
+		for_each_sg(scatterlist, sg, dma_buffers, i) {
+			unsigned int len = sg_dma_len(sg);
+			dma_addr_t addr = sg_dma_address(sg);
+
+			/* Note: addrs is the address + page_count - 1
+			 * The firmware expects blocks after the first to be page-
+			 * aligned and a multiple of the page size
+			 */
+			WARN_ON(len == 0);
+			WARN_ON(i && (i != (dma_buffers - 1)) && (len & ~PAGE_MASK));
+			WARN_ON(i && (addr & ~PAGE_MASK));
+			if (is_adjacent_block(addrs, addr, k))
+				addrs[k - 1] += ((len + PAGE_SIZE - 1) >> PAGE_SHIFT);
+			else
+				addrs[k++] = (addr & PAGE_MASK) |
+					(((len + PAGE_SIZE - 1) >> PAGE_SHIFT) - 1);
+		}
 	}
 
 	/* Partial cache lines (fragments) require special measures */
@@ -437,7 +503,7 @@ free_pagelist(struct vchiq_instance *instance, struct vchiq_pagelist_info *pagel
 	 * NOTE: dma_unmap_sg must be called before the
 	 * cpu can touch any of the data/pages.
 	 */
-	dma_unmap_sg(instance->state->dev, pagelistinfo->scatterlist,
+	dma_unmap_sg(g_dma_dev, pagelistinfo->scatterlist,
 		     pagelistinfo->num_pages, pagelistinfo->dma_dir);
 	pagelistinfo->scatterlist_mapped = 0;
 
@@ -492,6 +558,7 @@ free_pagelist(struct vchiq_instance *instance, struct vchiq_pagelist_info *pagel
 static int vchiq_platform_init(struct platform_device *pdev, struct vchiq_state *state)
 {
 	struct device *dev = &pdev->dev;
+	struct device *dma_dev = NULL;
 	struct vchiq_drv_mgmt *drv_mgmt = platform_get_drvdata(pdev);
 	struct rpi_firmware *fw = drv_mgmt->fw;
 	struct vchiq_slot_zero *vchiq_slot_zero;
@@ -512,6 +579,24 @@ static int vchiq_platform_init(struct platform_device *pdev, struct vchiq_state
 
 	drv_mgmt->fragments_size = 2 * drv_mgmt->info->cache_line_size;
 
+	if (drv_mgmt->info->use_36bit_addrs) {
+		struct device_node *dma_node =
+			of_find_compatible_node(NULL, NULL, "brcm,bcm2711-dma");
+
+		if (dma_node) {
+			struct platform_device *pdev;
+
+			pdev = of_find_device_by_node(dma_node);
+			if (pdev)
+				dma_dev = &pdev->dev;
+			of_node_put(dma_node);
+			g_use_36bit_addrs = true;
+		} else {
+			dev_err(dev, "40-bit DMA controller not found\n");
+			return -EINVAL;
+		}
+	}
+
 	/* Allocate space for the channels in coherent memory */
 	slot_mem_size = PAGE_ALIGN(TOTAL_SLOTS * VCHIQ_SLOT_SIZE);
 	frag_mem_size = PAGE_ALIGN(drv_mgmt->fragments_size * MAX_FRAGMENTS);
@@ -579,6 +664,15 @@ static int vchiq_platform_init(struct platform_device *pdev, struct vchiq_state
 		return -ENXIO;
 	}
 
+	g_dma_dev = dma_dev ?: dev;
+	g_dma_pool = dmam_pool_create("vchiq_scatter_pool", dev,
+				      VCHIQ_DMA_POOL_SIZE,
+				      drv_mgmt->info->cache_line_size, 0);
+	if (!g_dma_pool) {
+		dev_err(dev, "failed to create dma pool");
+		return -ENOMEM;
+	}
+
 	dev_dbg(&pdev->dev, "arm: vchiq_init - done (slots %pK, phys %pad)\n",
 		vchiq_slot_zero, &slot_phys);
 
@@ -1708,6 +1802,7 @@ void vchiq_platform_conn_state_changed(struct vchiq_state *state,
 static const struct of_device_id vchiq_of_match[] = {
 	{ .compatible = "brcm,bcm2835-vchiq", .data = &bcm2835_info },
 	{ .compatible = "brcm,bcm2836-vchiq", .data = &bcm2836_info },
+	{ .compatible = "brcm,bcm2711-vchiq", .data = &bcm2711_info },
 	{},
 };
 MODULE_DEVICE_TABLE(of, vchiq_of_match);
@@ -1759,8 +1854,11 @@ static int vchiq_probe(struct platform_device *pdev)
 
 	vchiq_debugfs_init(&mgmt->state);
 
+	vcsm_cma = vchiq_device_register(&pdev->dev, "vcsm-cma");
+	bcm2835_codec = vchiq_device_register(&pdev->dev, "bcm2835-codec");
 	bcm2835_audio = vchiq_device_register(&pdev->dev, "bcm2835-audio");
 	bcm2835_camera = vchiq_device_register(&pdev->dev, "bcm2835-camera");
+	bcm2835_isp = vchiq_device_register(&pdev->dev, "bcm2835-isp");
 
 	return 0;
 
@@ -1775,8 +1873,11 @@ static void vchiq_remove(struct platform_device *pdev)
 	struct vchiq_drv_mgmt *mgmt = dev_get_drvdata(&pdev->dev);
 	struct vchiq_arm_state *arm_state;
 
+	vchiq_device_unregister(bcm2835_isp);
 	vchiq_device_unregister(bcm2835_audio);
 	vchiq_device_unregister(bcm2835_camera);
+	vchiq_device_unregister(bcm2835_codec);
+	vchiq_device_unregister(vcsm_cma);
 	vchiq_debugfs_deinit();
 	vchiq_deregister_chrdev();
 
diff --git a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.h b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.h
index b402aac333d9..f2d11ecf90e0 100644
--- a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.h
+++ b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.h
@@ -32,6 +32,7 @@ enum USE_TYPE_E {
 
 struct vchiq_platform_info {
 	unsigned int cache_line_size;
+	bool use_36bit_addrs;
 };
 
 struct vchiq_drv_mgmt {
diff --git a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_bus.h b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_bus.h
index 9de179b39f85..6eff6b0bf599 100644
--- a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_bus.h
+++ b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_bus.h
@@ -37,6 +37,16 @@ static inline struct vchiq_driver *to_vchiq_driver(struct device_driver *d)
 	return container_of(d, struct vchiq_driver, driver);
 }
 
+static inline void *vchiq_get_drvdata(const struct vchiq_device *device)
+{
+	return dev_get_drvdata(&device->dev);
+}
+
+static inline void vchiq_set_drvdata(struct vchiq_device *device, void *data)
+{
+	dev_set_drvdata(&device->dev, data);
+}
+
 extern const struct bus_type vchiq_bus_type;
 
 struct vchiq_device *
diff --git a/drivers/staging/vc04_services/vc-sm-cma/Kconfig b/drivers/staging/vc04_services/vc-sm-cma/Kconfig
new file mode 100644
index 000000000000..d812021385a0
--- /dev/null
+++ b/drivers/staging/vc04_services/vc-sm-cma/Kconfig
@@ -0,0 +1,10 @@
+config BCM_VC_SM_CMA
+	tristate "VideoCore Shared Memory (CMA) driver"
+	select BCM2835_VCHIQ
+	select RBTREE
+	select DMA_SHARED_BUFFER
+	help
+	  Say Y here to enable the shared memory interface that
+	  supports sharing dmabufs with VideoCore.
+	  This operates over the VCHIQ interface to a service
+	  running on VideoCore.
diff --git a/drivers/staging/vc04_services/vc-sm-cma/Makefile b/drivers/staging/vc04_services/vc-sm-cma/Makefile
new file mode 100644
index 000000000000..bb80a16fdf48
--- /dev/null
+++ b/drivers/staging/vc04_services/vc-sm-cma/Makefile
@@ -0,0 +1,7 @@
+ccflags-y += \
+	-D__VCCOREVER__=0
+
+vc-sm-cma-$(CONFIG_BCM_VC_SM_CMA) := \
+	vc_sm.o vc_sm_cma_vchi.o
+
+obj-$(CONFIG_BCM_VC_SM_CMA) += vc-sm-cma.o
diff --git a/drivers/staging/vc04_services/vc-sm-cma/TODO b/drivers/staging/vc04_services/vc-sm-cma/TODO
new file mode 100644
index 000000000000..ac9b5f8a7389
--- /dev/null
+++ b/drivers/staging/vc04_services/vc-sm-cma/TODO
@@ -0,0 +1 @@
+No currently outstanding tasks except some clean-up.
diff --git a/drivers/staging/vc04_services/vc-sm-cma/vc_sm.c b/drivers/staging/vc04_services/vc-sm-cma/vc_sm.c
new file mode 100644
index 000000000000..1e518e0837ba
--- /dev/null
+++ b/drivers/staging/vc04_services/vc-sm-cma/vc_sm.c
@@ -0,0 +1,1620 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * VideoCore Shared Memory driver using CMA.
+ *
+ * Copyright: 2018, Raspberry Pi (Trading) Ltd
+ * Dave Stevenson <dave.stevenson@raspberrypi.org>
+ *
+ * Based on vmcs_sm driver from Broadcom Corporation for some API,
+ * and taking some code for buffer allocation and dmabuf handling from
+ * videobuf2.
+ *
+ *
+ * This driver has 3 main uses:
+ * 1) Allocating buffers for the kernel or userspace that can be shared with the
+ *    VPU.
+ * 2) Importing dmabufs from elsewhere for sharing with the VPU.
+ * 3) Allocating buffers for use by the VPU.
+ *
+ * In the first and second cases the native handle is a dmabuf. Releasing the
+ * resource inherently comes from releasing the dmabuf, and this will trigger
+ * unmapping on the VPU. The underlying allocation and our buffer structure are
+ * retained until the VPU has confirmed that it has finished with it.
+ *
+ * For the VPU allocations the VPU is responsible for triggering the release,
+ * and therefore the released message decrements the dma_buf refcount (with the
+ * VPU mapping having already been marked as released).
+ */
+
+/* ---- Include Files ----------------------------------------------------- */
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/debugfs.h>
+#include <linux/dma-mapping.h>
+#include <linux/dma-buf.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/of_device.h>
+#include <linux/proc_fs.h>
+#include <linux/slab.h>
+#include <linux/seq_file.h>
+#include <linux/syscalls.h>
+#include <linux/types.h>
+#include <asm/cacheflush.h>
+
+#include "../interface/vchiq_arm/vchiq_arm.h"
+#include "../interface/vchiq_arm/vchiq_bus.h"
+#include "vc_sm_cma_vchi.h"
+
+#include "vc_sm.h"
+#include "vc_sm_knl.h"
+#include "../include/linux/broadcom/vc_sm_cma_ioctl.h"
+
+MODULE_IMPORT_NS(DMA_BUF);
+
+/* ---- Private Constants and Types --------------------------------------- */
+
+#define DEVICE_NAME		"vcsm-cma"
+#define DEVICE_MINOR		0
+
+#define VC_SM_RESOURCE_NAME_DEFAULT       "sm-host-resource"
+
+#define VC_SM_DIR_ROOT_NAME	"vcsm-cma"
+#define VC_SM_STATE		"state"
+
+/* Private file data associated with each opened device. */
+struct vc_sm_privdata_t {
+	pid_t pid;                      /* PID of creator. */
+
+	int restart_sys;		/* Tracks restart on interrupt. */
+	enum vc_sm_msg_type int_action;	/* Interrupted action. */
+	u32 int_trans_id;		/* Interrupted transaction. */
+};
+
+typedef int (*VC_SM_SHOW) (struct seq_file *s, void *v);
+struct sm_pde_t {
+	VC_SM_SHOW show;          /* Debug fs function hookup. */
+	struct dentry *dir_entry; /* Debug fs directory entry. */
+	void *priv_data;          /* Private data */
+};
+
+/* Global state information. */
+struct sm_state_t {
+	struct vchiq_device *device;
+
+	struct miscdevice misc_dev;
+
+	struct sm_instance *sm_handle;	/* Handle for videocore service. */
+
+	struct mutex kernelid_map_lock;	/* Mutex protecting kernelid_map */
+	struct idr kernelid_map;
+
+	struct mutex map_lock;          /* Global map lock. */
+	struct list_head buffer_list;	/* List of buffer. */
+
+	struct vc_sm_privdata_t *data_knl;  /* Kernel internal data tracking. */
+	struct vc_sm_privdata_t *vpu_allocs; /* All allocations from the VPU */
+	struct dentry *dir_root;	/* Debug fs entries root. */
+	struct sm_pde_t dir_state;	/* Debug fs entries state sub-tree. */
+
+	bool require_released_callback;	/* VPU will send a released msg when it
+					 * has finished with a resource.
+					 */
+	u32 int_trans_id;		/* Interrupted transaction. */
+	struct vchiq_instance *vchiq_instance;
+};
+
+struct vc_sm_dma_buf_attachment {
+	struct device *dev;
+	struct sg_table sg_table;
+	struct list_head list;
+	enum dma_data_direction	dma_dir;
+};
+
+/* ---- Private Variables ----------------------------------------------- */
+
+static struct sm_state_t *sm_state;
+static int sm_inited;
+
+/* ---- Private Function Prototypes -------------------------------------- */
+
+/* ---- Private Functions ------------------------------------------------ */
+
+static int get_kernel_id(struct vc_sm_buffer *buffer)
+{
+	int handle;
+
+	mutex_lock(&sm_state->kernelid_map_lock);
+	handle = idr_alloc(&sm_state->kernelid_map, buffer, 0, 0, GFP_KERNEL);
+	mutex_unlock(&sm_state->kernelid_map_lock);
+
+	return handle;
+}
+
+static struct vc_sm_buffer *lookup_kernel_id(int handle)
+{
+	return idr_find(&sm_state->kernelid_map, handle);
+}
+
+static void free_kernel_id(int handle)
+{
+	mutex_lock(&sm_state->kernelid_map_lock);
+	idr_remove(&sm_state->kernelid_map, handle);
+	mutex_unlock(&sm_state->kernelid_map_lock);
+}
+
+static int vc_sm_cma_seq_file_show(struct seq_file *s, void *v)
+{
+	struct sm_pde_t *sm_pde;
+
+	sm_pde = (struct sm_pde_t *)(s->private);
+
+	if (sm_pde && sm_pde->show)
+		sm_pde->show(s, v);
+
+	return 0;
+}
+
+static int vc_sm_cma_single_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, vc_sm_cma_seq_file_show, inode->i_private);
+}
+
+static const struct file_operations vc_sm_cma_debug_fs_fops = {
+	.open = vc_sm_cma_single_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
+static int vc_sm_cma_global_state_show(struct seq_file *s, void *v)
+{
+	struct vc_sm_buffer *resource = NULL;
+	int resource_count = 0;
+
+	if (!sm_state)
+		return 0;
+
+	seq_printf(s, "\nVC-ServiceHandle     %p\n", sm_state->sm_handle);
+
+	/* Log all applicable mapping(s). */
+
+	mutex_lock(&sm_state->map_lock);
+	seq_puts(s, "\nResources\n");
+	if (!list_empty(&sm_state->buffer_list)) {
+		list_for_each_entry(resource, &sm_state->buffer_list,
+				    global_buffer_list) {
+			resource_count++;
+
+			seq_printf(s, "\nResource                %p\n",
+				   resource);
+			seq_printf(s, "           NAME         %s\n",
+				   resource->name);
+			seq_printf(s, "           SIZE         %zu\n",
+				   resource->size);
+			seq_printf(s, "           DMABUF       %p\n",
+				   resource->dma_buf);
+			if (resource->imported) {
+				seq_printf(s, "           ATTACH       %p\n",
+					   resource->import.attach);
+				seq_printf(s, "           SGT          %p\n",
+					   resource->import.sgt);
+			} else {
+				seq_printf(s, "           SGT          %p\n",
+					   resource->alloc.sg_table);
+			}
+			seq_printf(s, "           DMA_ADDR     %pad\n",
+				   &resource->dma_addr);
+			seq_printf(s, "           VC_HANDLE     %08x\n",
+				   resource->vc_handle);
+			seq_printf(s, "           VC_MAPPING    %d\n",
+				   resource->vpu_state);
+		}
+	}
+	seq_printf(s, "\n\nTotal resource count:   %d\n\n", resource_count);
+
+	mutex_unlock(&sm_state->map_lock);
+
+	return 0;
+}
+
+/*
+ * Adds a buffer to the private data list which tracks all the allocated
+ * data.
+ */
+static void vc_sm_add_resource(struct vc_sm_privdata_t *privdata,
+			       struct vc_sm_buffer *buffer)
+{
+	mutex_lock(&sm_state->map_lock);
+	list_add(&buffer->global_buffer_list, &sm_state->buffer_list);
+	mutex_unlock(&sm_state->map_lock);
+
+	pr_debug("[%s]: added buffer %p (name %s, size %zu)\n",
+		 __func__, buffer, buffer->name, buffer->size);
+}
+
+/*
+ * Cleans up imported dmabuf.
+ * Should be called with mutex held.
+ */
+static void vc_sm_clean_up_dmabuf(struct vc_sm_buffer *buffer)
+{
+	if (!buffer->imported)
+		return;
+
+	/* Handle cleaning up imported dmabufs */
+	if (buffer->import.sgt) {
+		dma_buf_unmap_attachment_unlocked(buffer->import.attach,
+						  buffer->import.sgt,
+						  DMA_BIDIRECTIONAL);
+		buffer->import.sgt = NULL;
+	}
+	if (buffer->import.attach) {
+		dma_buf_detach(buffer->import.dma_buf, buffer->import.attach);
+		buffer->import.attach = NULL;
+	}
+}
+
+/*
+ * Instructs VPU to decrement the refcount on a buffer.
+ */
+static void vc_sm_vpu_free(struct vc_sm_buffer *buffer)
+{
+	if (buffer->vc_handle && buffer->vpu_state == VPU_MAPPED) {
+		struct vc_sm_free_t free = { buffer->vc_handle, 0 };
+		int status = vc_sm_cma_vchi_free(sm_state->sm_handle, &free,
+					     &sm_state->int_trans_id);
+		if (status != 0 && status != -EINTR) {
+			pr_err("[%s]: failed to free memory on videocore (status: %u, trans_id: %u)\n",
+			       __func__, status, sm_state->int_trans_id);
+		}
+
+		if (sm_state->require_released_callback) {
+			/* Need to wait for the VPU to confirm the free. */
+
+			/* Retain a reference on this until the VPU has
+			 * released it
+			 */
+			buffer->vpu_state = VPU_UNMAPPING;
+		} else {
+			buffer->vpu_state = VPU_NOT_MAPPED;
+			buffer->vc_handle = 0;
+		}
+	}
+}
+
+/*
+ * Release an allocation.
+ * All refcounting is done via the dma buf object.
+ *
+ * Must be called with the mutex held. The function will either release the
+ * mutex (if defering the release) or destroy it. The caller must therefore not
+ * reuse the buffer on return.
+ */
+static void vc_sm_release_resource(struct vc_sm_buffer *buffer)
+{
+	pr_debug("[%s]: buffer %p (name %s, size %zu), imported %u\n",
+		 __func__, buffer, buffer->name, buffer->size,
+		 buffer->imported);
+
+	if (buffer->vc_handle) {
+		/* We've sent the unmap request but not had the response. */
+		pr_debug("[%s]: Waiting for VPU unmap response on %p\n",
+			 __func__, buffer);
+		goto defer;
+	}
+	if (buffer->in_use) {
+		/* dmabuf still in use - we await the release */
+		pr_debug("[%s]: buffer %p is still in use\n", __func__, buffer);
+		goto defer;
+	}
+
+	/* Release the allocation (whether imported dmabuf or CMA allocation) */
+	if (buffer->imported) {
+		if (buffer->import.dma_buf)
+			dma_buf_put(buffer->import.dma_buf);
+		else
+			pr_err("%s: Imported dmabuf already been put for buf %p\n",
+			       __func__, buffer);
+		buffer->import.dma_buf = NULL;
+	} else {
+		dma_free_coherent(&sm_state->device->dev, buffer->size,
+				  buffer->cookie, buffer->dma_addr);
+	}
+
+	/* Free our buffer. Start by removing it from the list */
+	mutex_lock(&sm_state->map_lock);
+	list_del(&buffer->global_buffer_list);
+	mutex_unlock(&sm_state->map_lock);
+
+	pr_debug("%s: Release our allocation - done\n", __func__);
+	mutex_unlock(&buffer->lock);
+
+	mutex_destroy(&buffer->lock);
+
+	kfree(buffer);
+	return;
+
+defer:
+	mutex_unlock(&buffer->lock);
+}
+
+/* Create support for private data tracking. */
+static struct vc_sm_privdata_t *vc_sm_cma_create_priv_data(pid_t id)
+{
+	char alloc_name[32];
+	struct vc_sm_privdata_t *file_data = NULL;
+
+	/* Allocate private structure. */
+	file_data = kzalloc(sizeof(*file_data), GFP_KERNEL);
+
+	if (!file_data)
+		return NULL;
+
+	snprintf(alloc_name, sizeof(alloc_name), "%d", id);
+
+	file_data->pid = id;
+
+	return file_data;
+}
+
+/* Dma buf operations for use with our own allocations */
+
+static int vc_sm_dma_buf_attach(struct dma_buf *dmabuf,
+				struct dma_buf_attachment *attachment)
+
+{
+	struct vc_sm_dma_buf_attachment *a;
+	struct sg_table *sgt;
+	struct vc_sm_buffer *buf = dmabuf->priv;
+	struct scatterlist *rd, *wr;
+	int ret, i;
+
+	a = kzalloc(sizeof(*a), GFP_KERNEL);
+	if (!a)
+		return -ENOMEM;
+
+	pr_debug("%s dmabuf %p attachment %p\n", __func__, dmabuf, attachment);
+
+	mutex_lock(&buf->lock);
+
+	INIT_LIST_HEAD(&a->list);
+
+	sgt = &a->sg_table;
+
+	/* Copy the buf->base_sgt scatter list to the attachment, as we can't
+	 * map the same scatter list to multiple attachments at the same time.
+	 */
+	ret = sg_alloc_table(sgt, buf->alloc.sg_table->orig_nents, GFP_KERNEL);
+	if (ret) {
+		kfree(a);
+		mutex_unlock(&buf->lock);
+		return -ENOMEM;
+	}
+
+	rd = buf->alloc.sg_table->sgl;
+	wr = sgt->sgl;
+	for (i = 0; i < sgt->orig_nents; ++i) {
+		sg_set_page(wr, sg_page(rd), rd->length, rd->offset);
+		rd = sg_next(rd);
+		wr = sg_next(wr);
+	}
+
+	a->dma_dir = DMA_NONE;
+	attachment->priv = a;
+
+	list_add(&a->list, &buf->attachments);
+	mutex_unlock(&buf->lock);
+
+	return 0;
+}
+
+static void vc_sm_dma_buf_detach(struct dma_buf *dmabuf,
+				 struct dma_buf_attachment *attachment)
+{
+	struct vc_sm_dma_buf_attachment *a = attachment->priv;
+	struct vc_sm_buffer *buf = dmabuf->priv;
+	struct sg_table *sgt;
+
+	pr_debug("%s dmabuf %p attachment %p\n", __func__, dmabuf, attachment);
+	if (!a)
+		return;
+
+	sgt = &a->sg_table;
+
+	/* release the scatterlist cache */
+	if (a->dma_dir != DMA_NONE)
+		dma_unmap_sg(attachment->dev, sgt->sgl, sgt->orig_nents,
+			     a->dma_dir);
+	sg_free_table(sgt);
+
+	mutex_lock(&buf->lock);
+	list_del(&a->list);
+	mutex_unlock(&buf->lock);
+
+	kfree(a);
+}
+
+static struct sg_table *vc_sm_map_dma_buf(struct dma_buf_attachment *attachment,
+					  enum dma_data_direction direction)
+{
+	struct vc_sm_dma_buf_attachment *a = attachment->priv;
+	/* stealing dmabuf mutex to serialize map/unmap operations */
+	struct sg_table *table;
+
+	pr_debug("%s attachment %p\n", __func__, attachment);
+	table = &a->sg_table;
+
+	/* return previously mapped sg table */
+	if (a->dma_dir == direction) {
+		return table;
+	}
+
+	/* release any previous cache */
+	if (a->dma_dir != DMA_NONE) {
+		dma_unmap_sg(attachment->dev, table->sgl, table->orig_nents,
+			     a->dma_dir);
+		a->dma_dir = DMA_NONE;
+	}
+
+	/* mapping to the client with new direction */
+	table->nents = dma_map_sg(attachment->dev, table->sgl,
+				  table->orig_nents, direction);
+	if (!table->nents) {
+		pr_err("failed to map scatterlist\n");
+		return ERR_PTR(-EIO);
+	}
+
+	a->dma_dir = direction;
+
+	pr_debug("%s attachment %p\n", __func__, attachment);
+	return table;
+}
+
+static void vc_sm_unmap_dma_buf(struct dma_buf_attachment *attachment,
+				struct sg_table *table,
+				enum dma_data_direction direction)
+{
+	pr_debug("%s attachment %p\n", __func__, attachment);
+	dma_unmap_sg(attachment->dev, table->sgl, table->nents, direction);
+}
+
+static int vc_sm_dmabuf_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma)
+{
+	struct vc_sm_buffer *buf = dmabuf->priv;
+	int ret;
+
+	pr_debug("%s dmabuf %p, buf %p, vm_start %08lX\n", __func__, dmabuf,
+		 buf, vma->vm_start);
+
+	/* now map it to userspace */
+	vma->vm_pgoff = 0;
+
+	ret = dma_mmap_coherent(&sm_state->device->dev, vma, buf->cookie,
+				buf->dma_addr, buf->size);
+
+	if (ret) {
+		pr_err("Remapping memory failed, error: %d\n", ret);
+		return ret;
+	}
+
+	vm_flags_reset(vma, vma->vm_flags | VM_DONTEXPAND | VM_DONTDUMP);
+
+	if (ret)
+		pr_err("%s: failure mapping buffer to userspace\n",
+		       __func__);
+
+	return ret;
+}
+
+static void vc_sm_dma_buf_release(struct dma_buf *dmabuf)
+{
+	struct vc_sm_buffer *buffer;
+
+	if (!dmabuf)
+		return;
+
+	buffer = (struct vc_sm_buffer *)dmabuf->priv;
+
+	mutex_lock(&buffer->lock);
+
+	pr_debug("%s dmabuf %p, buffer %p\n", __func__, dmabuf, buffer);
+
+	buffer->in_use = false;
+
+	/* Unmap on the VPU */
+	vc_sm_vpu_free(buffer);
+	pr_debug("%s vpu_free done\n", __func__);
+
+	/* Unmap our dma_buf object (the vc_sm_buffer remains until released
+	 * on the VPU).
+	 */
+	vc_sm_clean_up_dmabuf(buffer);
+	pr_debug("%s clean_up dmabuf done\n", __func__);
+
+	/* buffer->lock will be destroyed by vc_sm_release_resource if finished
+	 * with, otherwise unlocked. Do NOT unlock here.
+	 */
+	vc_sm_release_resource(buffer);
+	pr_debug("%s done\n", __func__);
+}
+
+static int vc_sm_dma_buf_begin_cpu_access(struct dma_buf *dmabuf,
+					  enum dma_data_direction direction)
+{
+	struct vc_sm_buffer *buf;
+	struct vc_sm_dma_buf_attachment *a;
+
+	if (!dmabuf)
+		return -EFAULT;
+
+	buf = dmabuf->priv;
+	if (!buf)
+		return -EFAULT;
+
+	mutex_lock(&buf->lock);
+
+	list_for_each_entry(a, &buf->attachments, list) {
+		dma_sync_sg_for_cpu(a->dev, a->sg_table.sgl,
+				    a->sg_table.nents, direction);
+	}
+	mutex_unlock(&buf->lock);
+
+	return 0;
+}
+
+static int vc_sm_dma_buf_end_cpu_access(struct dma_buf *dmabuf,
+					enum dma_data_direction direction)
+{
+	struct vc_sm_buffer *buf;
+	struct vc_sm_dma_buf_attachment *a;
+
+	if (!dmabuf)
+		return -EFAULT;
+	buf = dmabuf->priv;
+	if (!buf)
+		return -EFAULT;
+
+	mutex_lock(&buf->lock);
+
+	list_for_each_entry(a, &buf->attachments, list) {
+		dma_sync_sg_for_device(a->dev, a->sg_table.sgl,
+				       a->sg_table.nents, direction);
+	}
+	mutex_unlock(&buf->lock);
+
+	return 0;
+}
+
+static const struct dma_buf_ops dma_buf_ops = {
+	.map_dma_buf = vc_sm_map_dma_buf,
+	.unmap_dma_buf = vc_sm_unmap_dma_buf,
+	.mmap = vc_sm_dmabuf_mmap,
+	.release = vc_sm_dma_buf_release,
+	.attach = vc_sm_dma_buf_attach,
+	.detach = vc_sm_dma_buf_detach,
+	.begin_cpu_access = vc_sm_dma_buf_begin_cpu_access,
+	.end_cpu_access = vc_sm_dma_buf_end_cpu_access,
+};
+
+/* Dma_buf operations for chaining through to an imported dma_buf */
+
+static
+int vc_sm_import_dma_buf_attach(struct dma_buf *dmabuf,
+				struct dma_buf_attachment *attachment)
+{
+	struct vc_sm_buffer *buf = dmabuf->priv;
+
+	if (!buf->imported)
+		return -EINVAL;
+	return buf->import.dma_buf->ops->attach(buf->import.dma_buf,
+						attachment);
+}
+
+static
+void vc_sm_import_dma_buf_detatch(struct dma_buf *dmabuf,
+				  struct dma_buf_attachment *attachment)
+{
+	struct vc_sm_buffer *buf = dmabuf->priv;
+
+	if (!buf->imported)
+		return;
+	buf->import.dma_buf->ops->detach(buf->import.dma_buf, attachment);
+}
+
+static
+struct sg_table *vc_sm_import_map_dma_buf(struct dma_buf_attachment *attachment,
+					  enum dma_data_direction direction)
+{
+	struct vc_sm_buffer *buf = attachment->dmabuf->priv;
+
+	if (!buf->imported)
+		return NULL;
+	return buf->import.dma_buf->ops->map_dma_buf(attachment,
+						     direction);
+}
+
+static
+void vc_sm_import_unmap_dma_buf(struct dma_buf_attachment *attachment,
+				struct sg_table *table,
+				enum dma_data_direction direction)
+{
+	struct vc_sm_buffer *buf = attachment->dmabuf->priv;
+
+	if (!buf->imported)
+		return;
+	buf->import.dma_buf->ops->unmap_dma_buf(attachment, table, direction);
+}
+
+static
+int vc_sm_import_dmabuf_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma)
+{
+	struct vc_sm_buffer *buf = dmabuf->priv;
+
+	pr_debug("%s: mmap dma_buf %p, buf %p, imported db %p\n", __func__,
+		 dmabuf, buf, buf->import.dma_buf);
+	if (!buf->imported) {
+		pr_err("%s: mmap dma_buf %p- not an imported buffer\n",
+		       __func__, dmabuf);
+		return -EINVAL;
+	}
+	return buf->import.dma_buf->ops->mmap(buf->import.dma_buf, vma);
+}
+
+static
+int vc_sm_import_dma_buf_begin_cpu_access(struct dma_buf *dmabuf,
+					  enum dma_data_direction direction)
+{
+	struct vc_sm_buffer *buf = dmabuf->priv;
+
+	if (!buf->imported)
+		return -EINVAL;
+	return buf->import.dma_buf->ops->begin_cpu_access(buf->import.dma_buf,
+							  direction);
+}
+
+static
+int vc_sm_import_dma_buf_end_cpu_access(struct dma_buf *dmabuf,
+					enum dma_data_direction direction)
+{
+	struct vc_sm_buffer *buf = dmabuf->priv;
+
+	if (!buf->imported)
+		return -EINVAL;
+	return buf->import.dma_buf->ops->end_cpu_access(buf->import.dma_buf,
+							  direction);
+}
+
+static const struct dma_buf_ops dma_buf_import_ops = {
+	.map_dma_buf = vc_sm_import_map_dma_buf,
+	.unmap_dma_buf = vc_sm_import_unmap_dma_buf,
+	.mmap = vc_sm_import_dmabuf_mmap,
+	.release = vc_sm_dma_buf_release,
+	.attach = vc_sm_import_dma_buf_attach,
+	.detach = vc_sm_import_dma_buf_detatch,
+	.begin_cpu_access = vc_sm_import_dma_buf_begin_cpu_access,
+	.end_cpu_access = vc_sm_import_dma_buf_end_cpu_access,
+};
+
+/* Import a dma_buf to be shared with VC. */
+static int
+vc_sm_cma_import_dmabuf_internal(struct vc_sm_privdata_t *private,
+				 struct dma_buf *dma_buf,
+				 int fd,
+				 struct dma_buf **imported_buf)
+{
+	DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
+	struct vc_sm_buffer *buffer = NULL;
+	struct vc_sm_import import = { };
+	struct vc_sm_import_result result = { };
+	struct dma_buf_attachment *attach = NULL;
+	struct sg_table *sgt = NULL;
+	dma_addr_t dma_addr;
+	u32 cache_alias;
+	int ret = 0;
+	int status;
+
+	/* Setup our allocation parameters */
+	pr_debug("%s: importing dma_buf %p/fd %d\n", __func__, dma_buf, fd);
+
+	if (fd < 0)
+		get_dma_buf(dma_buf);
+	else
+		dma_buf = dma_buf_get(fd);
+
+	if (!dma_buf)
+		return -EINVAL;
+
+	attach = dma_buf_attach(dma_buf, &sm_state->device->dev);
+	if (IS_ERR(attach)) {
+		ret = PTR_ERR(attach);
+		goto error;
+	}
+
+	sgt = dma_buf_map_attachment_unlocked(attach, DMA_BIDIRECTIONAL);
+	if (IS_ERR(sgt)) {
+		ret = PTR_ERR(sgt);
+		goto error;
+	}
+
+	/* Verify that the address block is contiguous */
+	if (sgt->nents != 1) {
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	/* Allocate local buffer to track this allocation. */
+	buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
+	if (!buffer) {
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	import.type = VC_SM_ALLOC_NON_CACHED;
+	dma_addr = sg_dma_address(sgt->sgl);
+	import.addr = (u32)dma_addr;
+	cache_alias = import.addr & 0xC0000000;
+	if (cache_alias != 0xC0000000 && cache_alias != 0x80000000) {
+		pr_err("%s: Expecting an uncached alias for dma_addr %pad\n",
+		       __func__, &dma_addr);
+		/* Note that this assumes we're on >= Pi2, but it implies a
+		 * DT configuration error.
+		 */
+		import.addr |= 0xC0000000;
+	}
+	import.size = sg_dma_len(sgt->sgl);
+	import.allocator = current->tgid;
+	import.kernel_id = get_kernel_id(buffer);
+
+	memcpy(import.name, VC_SM_RESOURCE_NAME_DEFAULT,
+	       sizeof(VC_SM_RESOURCE_NAME_DEFAULT));
+
+	pr_debug("[%s]: attempt to import \"%s\" data - type %u, addr %pad, size %u.\n",
+		 __func__, import.name, import.type, &dma_addr, import.size);
+
+	/* Allocate the videocore buffer. */
+	status = vc_sm_cma_vchi_import(sm_state->sm_handle, &import, &result,
+				       &sm_state->int_trans_id);
+	if (status == -EINTR) {
+		pr_debug("[%s]: requesting import memory action restart (trans_id: %u)\n",
+			 __func__, sm_state->int_trans_id);
+		ret = -ERESTARTSYS;
+		private->restart_sys = -EINTR;
+		private->int_action = VC_SM_MSG_TYPE_IMPORT;
+		goto error;
+	} else if (status || !result.res_handle) {
+		pr_debug("[%s]: failed to import memory on videocore (status: %u, trans_id: %u)\n",
+			 __func__, status, sm_state->int_trans_id);
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	mutex_init(&buffer->lock);
+	INIT_LIST_HEAD(&buffer->attachments);
+	memcpy(buffer->name, import.name,
+	       min(sizeof(buffer->name), sizeof(import.name) - 1));
+
+	/* Keep track of the buffer we created. */
+	buffer->private = private;
+	buffer->vc_handle = result.res_handle;
+	buffer->size = import.size;
+	buffer->vpu_state = VPU_MAPPED;
+
+	buffer->imported = true;
+	buffer->import.dma_buf = dma_buf;
+
+	buffer->import.attach = attach;
+	buffer->import.sgt = sgt;
+	buffer->dma_addr = dma_addr;
+	buffer->in_use = true;
+	buffer->kernel_id = import.kernel_id;
+
+	/*
+	 * We're done - we need to export a new dmabuf chaining through most
+	 * functions, but enabling us to release our own internal references
+	 * here.
+	 */
+	exp_info.ops = &dma_buf_import_ops;
+	exp_info.size = import.size;
+	exp_info.flags = O_RDWR;
+	exp_info.priv = buffer;
+
+	buffer->dma_buf = dma_buf_export(&exp_info);
+	if (IS_ERR(buffer->dma_buf)) {
+		ret = PTR_ERR(buffer->dma_buf);
+		goto error;
+	}
+
+	vc_sm_add_resource(private, buffer);
+
+	*imported_buf = buffer->dma_buf;
+
+	return 0;
+
+error:
+	if (result.res_handle) {
+		struct vc_sm_free_t free = { result.res_handle, 0 };
+
+		vc_sm_cma_vchi_free(sm_state->sm_handle, &free,
+				    &sm_state->int_trans_id);
+	}
+	free_kernel_id(import.kernel_id);
+	kfree(buffer);
+	if (sgt)
+		dma_buf_unmap_attachment_unlocked(attach, sgt, DMA_BIDIRECTIONAL);
+	if (attach)
+		dma_buf_detach(dma_buf, attach);
+	dma_buf_put(dma_buf);
+	return ret;
+}
+
+static int vc_sm_cma_vpu_alloc(u32 size, u32 align, const char *name,
+			       u32 mem_handle, struct vc_sm_buffer **ret_buffer)
+{
+	DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
+	struct vc_sm_buffer *buffer = NULL;
+	struct sg_table *sgt;
+	int aligned_size;
+	int ret = 0;
+
+	/* Align to the user requested align */
+	aligned_size = ALIGN(size, align);
+	/* and then to a page boundary */
+	aligned_size = PAGE_ALIGN(aligned_size);
+
+	if (!aligned_size)
+		return -EINVAL;
+
+	/* Allocate local buffer to track this allocation. */
+	buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
+	if (!buffer)
+		return -ENOMEM;
+
+	mutex_init(&buffer->lock);
+	/* Acquire the mutex as vc_sm_release_resource will release it in the
+	 * error path.
+	 */
+	mutex_lock(&buffer->lock);
+
+	buffer->cookie = dma_alloc_coherent(&sm_state->device->dev,
+					    aligned_size, &buffer->dma_addr,
+					    GFP_KERNEL);
+	if (!buffer->cookie) {
+		pr_err("[%s]: dma_alloc_coherent alloc of %d bytes failed\n",
+		       __func__, aligned_size);
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	pr_debug("[%s]: alloc of %d bytes success\n",
+		 __func__, aligned_size);
+
+	sgt = kmalloc(sizeof(*sgt), GFP_KERNEL);
+	if (!sgt) {
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	ret = dma_get_sgtable(&sm_state->device->dev, sgt, buffer->cookie,
+			      buffer->dma_addr, buffer->size);
+	if (ret < 0) {
+		pr_err("failed to get scatterlist from DMA API\n");
+		kfree(sgt);
+		ret = -ENOMEM;
+		goto error;
+	}
+	buffer->alloc.sg_table = sgt;
+
+	INIT_LIST_HEAD(&buffer->attachments);
+
+	memcpy(buffer->name, name,
+	       min(sizeof(buffer->name), strlen(name)));
+
+	exp_info.ops = &dma_buf_ops;
+	exp_info.size = aligned_size;
+	exp_info.flags = O_RDWR;
+	exp_info.priv = buffer;
+
+	buffer->dma_buf = dma_buf_export(&exp_info);
+	if (IS_ERR(buffer->dma_buf)) {
+		ret = PTR_ERR(buffer->dma_buf);
+		goto error;
+	}
+	buffer->dma_addr = (u32)sg_dma_address(buffer->alloc.sg_table->sgl);
+	if ((buffer->dma_addr & 0xC0000000) != 0xC0000000) {
+		pr_warn_once("%s: Expecting an uncached alias for dma_addr %pad\n",
+			     __func__, &buffer->dma_addr);
+		buffer->dma_addr |= 0xC0000000;
+	}
+	buffer->private = sm_state->vpu_allocs;
+
+	buffer->vc_handle = mem_handle;
+	buffer->vpu_state = VPU_MAPPED;
+	buffer->vpu_allocated = 1;
+	buffer->size = size;
+	/*
+	 * Create an ID that will be passed along with our message so
+	 * that when we service the release reply, we can look up which
+	 * resource is being released.
+	 */
+	buffer->kernel_id = get_kernel_id(buffer);
+
+	vc_sm_add_resource(sm_state->vpu_allocs, buffer);
+
+	mutex_unlock(&buffer->lock);
+
+	*ret_buffer = buffer;
+	return 0;
+error:
+	if (buffer)
+		vc_sm_release_resource(buffer);
+	return ret;
+}
+
+static void
+vc_sm_vpu_event(struct sm_instance *instance, struct vc_sm_result_t *reply,
+		int reply_len)
+{
+	switch (reply->trans_id & ~0x80000000) {
+	case VC_SM_MSG_TYPE_CLIENT_VERSION:
+	{
+		/* Acknowledge that the firmware supports the version command */
+		pr_debug("%s: firmware acked version msg. Require release cb\n",
+			 __func__);
+		sm_state->require_released_callback = true;
+	}
+	break;
+	case VC_SM_MSG_TYPE_RELEASED:
+	{
+		struct vc_sm_released *release = (struct vc_sm_released *)reply;
+		struct vc_sm_buffer *buffer =
+					lookup_kernel_id(release->kernel_id);
+		if (!buffer) {
+			pr_err("%s: VC released a buffer that is already released, kernel_id %d\n",
+			       __func__, release->kernel_id);
+			break;
+		}
+		mutex_lock(&buffer->lock);
+
+		pr_debug("%s: Released addr %08x, size %u, id %08x, mem_handle %08x\n",
+			 __func__, release->addr, release->size,
+			 release->kernel_id, release->vc_handle);
+
+		buffer->vc_handle = 0;
+		buffer->vpu_state = VPU_NOT_MAPPED;
+		free_kernel_id(release->kernel_id);
+
+		if (buffer->vpu_allocated) {
+			/* VPU allocation, so release the dmabuf which will
+			 * trigger the clean up.
+			 */
+			mutex_unlock(&buffer->lock);
+			dma_buf_put(buffer->dma_buf);
+		} else {
+			vc_sm_release_resource(buffer);
+		}
+	}
+	break;
+	case VC_SM_MSG_TYPE_VC_MEM_REQUEST:
+	{
+		struct vc_sm_buffer *buffer = NULL;
+		struct vc_sm_vc_mem_request *req =
+					(struct vc_sm_vc_mem_request *)reply;
+		struct vc_sm_vc_mem_request_result reply;
+		int ret;
+
+		pr_debug("%s: Request %u bytes of memory, align %d name %s, trans_id %08x\n",
+			 __func__, req->size, req->align, req->name,
+			 req->trans_id);
+		ret = vc_sm_cma_vpu_alloc(req->size, req->align, req->name,
+					  req->vc_handle, &buffer);
+
+		reply.trans_id = req->trans_id;
+		if (!ret) {
+			reply.addr = buffer->dma_addr;
+			reply.kernel_id = buffer->kernel_id;
+			pr_debug("%s: Allocated resource buffer %p, addr %pad\n",
+				 __func__, buffer, &buffer->dma_addr);
+		} else {
+			pr_err("%s: Allocation failed size %u, name %s, vc_handle %u\n",
+			       __func__, req->size, req->name, req->vc_handle);
+			reply.addr = 0;
+			reply.kernel_id = 0;
+		}
+		vc_sm_vchi_client_vc_mem_req_reply(sm_state->sm_handle, &reply,
+						   &sm_state->int_trans_id);
+		break;
+	}
+	break;
+	default:
+		pr_err("%s: Unknown vpu cmd %x\n", __func__, reply->trans_id);
+		break;
+	}
+}
+
+/* Userspace handling */
+/*
+ * Open the device.  Creates a private state to help track all allocation
+ * associated with this device.
+ */
+static int vc_sm_cma_open(struct inode *inode, struct file *file)
+{
+	/* Make sure the device was started properly. */
+	if (!sm_state) {
+		pr_err("[%s]: invalid device\n", __func__);
+		return -EPERM;
+	}
+
+	file->private_data = vc_sm_cma_create_priv_data(current->tgid);
+	if (!file->private_data) {
+		pr_err("[%s]: failed to create data tracker\n", __func__);
+
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+/*
+ * Close the vcsm-cma device.
+ * All allocations are file descriptors to the dmabuf objects, so we will get
+ * the clean up request on those as those are cleaned up.
+ */
+static int vc_sm_cma_release(struct inode *inode, struct file *file)
+{
+	struct vc_sm_privdata_t *file_data =
+	    (struct vc_sm_privdata_t *)file->private_data;
+	int ret = 0;
+
+	/* Make sure the device was started properly. */
+	if (!sm_state || !file_data) {
+		pr_err("[%s]: invalid device\n", __func__);
+		ret = -EPERM;
+		goto out;
+	}
+
+	pr_debug("[%s]: using private data %p\n", __func__, file_data);
+
+	/* Terminate the private data. */
+	kfree(file_data);
+
+out:
+	return ret;
+}
+
+/*
+ * Allocate a shared memory handle and block.
+ * Allocation is from CMA, and then imported into the VPU mappings.
+ */
+static int vc_sm_cma_ioctl_alloc(struct vc_sm_privdata_t *private,
+			  struct vc_sm_cma_ioctl_alloc *ioparam)
+{
+	DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
+	struct vc_sm_buffer *buffer = NULL;
+	struct vc_sm_import import = { 0 };
+	struct vc_sm_import_result result = { 0 };
+	struct dma_buf *dmabuf = NULL;
+	struct sg_table *sgt;
+	int aligned_size;
+	int ret = 0;
+	int status;
+	int fd = -1;
+
+	aligned_size = PAGE_ALIGN(ioparam->size);
+
+	if (!aligned_size)
+		return -EINVAL;
+
+	/* Allocate local buffer to track this allocation. */
+	buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
+	if (!buffer) {
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	buffer->cookie = dma_alloc_coherent(&sm_state->device->dev,
+					    aligned_size,
+					    &buffer->dma_addr,
+					    GFP_KERNEL);
+	if (!buffer->cookie) {
+		pr_err("[%s]: dma_alloc_coherent alloc of %d bytes failed\n",
+		       __func__, aligned_size);
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	import.type = VC_SM_ALLOC_NON_CACHED;
+	import.allocator = current->tgid;
+
+	if (*ioparam->name)
+		memcpy(import.name, ioparam->name, sizeof(import.name) - 1);
+	else
+		memcpy(import.name, VC_SM_RESOURCE_NAME_DEFAULT,
+		       sizeof(VC_SM_RESOURCE_NAME_DEFAULT));
+
+	mutex_init(&buffer->lock);
+	INIT_LIST_HEAD(&buffer->attachments);
+	memcpy(buffer->name, import.name,
+	       min(sizeof(buffer->name), sizeof(import.name) - 1));
+
+	exp_info.ops = &dma_buf_ops;
+	exp_info.size = aligned_size;
+	exp_info.flags = O_RDWR;
+	exp_info.priv = buffer;
+
+	dmabuf = dma_buf_export(&exp_info);
+	if (IS_ERR(dmabuf)) {
+		ret = PTR_ERR(dmabuf);
+		goto error;
+	}
+	buffer->dma_buf = dmabuf;
+
+	import.addr = buffer->dma_addr;
+	import.size = aligned_size;
+	import.kernel_id = get_kernel_id(buffer);
+
+	/* Wrap it into a videocore buffer. */
+	status = vc_sm_cma_vchi_import(sm_state->sm_handle, &import, &result,
+				       &sm_state->int_trans_id);
+	if (status == -EINTR) {
+		pr_debug("[%s]: requesting import memory action restart (trans_id: %u)\n",
+			 __func__, sm_state->int_trans_id);
+		ret = -ERESTARTSYS;
+		private->restart_sys = -EINTR;
+		private->int_action = VC_SM_MSG_TYPE_IMPORT;
+		goto error;
+	} else if (status || !result.res_handle) {
+		pr_err("[%s]: failed to import memory on videocore (status: %u, trans_id: %u)\n",
+		       __func__, status, sm_state->int_trans_id);
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	/* Keep track of the buffer we created. */
+	buffer->private = private;
+	buffer->vc_handle = result.res_handle;
+	buffer->size = import.size;
+	buffer->vpu_state = VPU_MAPPED;
+	buffer->kernel_id = import.kernel_id;
+
+	sgt = kmalloc(sizeof(*sgt), GFP_KERNEL);
+	if (!sgt) {
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	ret = dma_get_sgtable(&sm_state->device->dev, sgt, buffer->cookie,
+			      buffer->dma_addr, buffer->size);
+	if (ret < 0) {
+		/* FIXME: error handling */
+		pr_err("failed to get scatterlist from DMA API\n");
+		kfree(sgt);
+		ret = -ENOMEM;
+		goto error;
+	}
+	buffer->alloc.sg_table = sgt;
+
+	fd = dma_buf_fd(dmabuf, O_CLOEXEC);
+	if (fd < 0)
+		goto error;
+
+	vc_sm_add_resource(private, buffer);
+
+	pr_debug("[%s]: Added resource as fd %d, buffer %p, private %p, dma_addr %pad\n",
+		 __func__, fd, buffer, private, &buffer->dma_addr);
+
+	/* We're done */
+	ioparam->handle = fd;
+	ioparam->vc_handle = buffer->vc_handle;
+	ioparam->dma_addr = buffer->dma_addr;
+	return 0;
+
+error:
+	pr_err("[%s]: something failed - cleanup. ret %d\n", __func__, ret);
+
+	if (dmabuf) {
+		/* dmabuf has been exported, therefore allow dmabuf cleanup to
+		 * deal with this
+		 */
+		dma_buf_put(dmabuf);
+	} else {
+		/* No dmabuf, therefore just free the buffer here */
+		if (buffer && buffer->cookie)
+			dma_free_coherent(&sm_state->device->dev, buffer->size,
+					  buffer->cookie, buffer->dma_addr);
+		kfree(buffer);
+	}
+	return ret;
+}
+
+static long vc_sm_cma_ioctl(struct file *file, unsigned int cmd,
+			    unsigned long arg)
+{
+	int ret = 0;
+	unsigned int cmdnr = _IOC_NR(cmd);
+	struct vc_sm_privdata_t *file_data =
+	    (struct vc_sm_privdata_t *)file->private_data;
+
+	/* Validate we can work with this device. */
+	if (!sm_state || !file_data) {
+		pr_err("[%s]: invalid device\n", __func__);
+		return -EPERM;
+	}
+
+	/* Action is a re-post of a previously interrupted action? */
+	if (file_data->restart_sys == -EINTR) {
+		pr_debug("[%s]: clean up of action %u (trans_id: %u) following EINTR\n",
+			 __func__, file_data->int_action,
+			 file_data->int_trans_id);
+
+		file_data->restart_sys = 0;
+	}
+
+	/* Now process the command. */
+	switch (cmdnr) {
+		/* New memory allocation.
+		 */
+	case VC_SM_CMA_CMD_ALLOC:
+	{
+		struct vc_sm_cma_ioctl_alloc ioparam;
+
+		/* Get the parameter data. */
+		if (copy_from_user
+		    (&ioparam, (void *)arg, sizeof(ioparam)) != 0) {
+			pr_err("[%s]: failed to copy-from-user for cmd %x\n",
+			       __func__, cmdnr);
+			ret = -EFAULT;
+			break;
+		}
+
+		ret = vc_sm_cma_ioctl_alloc(file_data, &ioparam);
+		if (!ret &&
+		    (copy_to_user((void *)arg, &ioparam,
+				  sizeof(ioparam)) != 0)) {
+			/* FIXME: Release allocation */
+			pr_err("[%s]: failed to copy-to-user for cmd %x\n",
+			       __func__, cmdnr);
+			ret = -EFAULT;
+		}
+		break;
+	}
+
+	case VC_SM_CMA_CMD_IMPORT_DMABUF:
+	{
+		struct vc_sm_cma_ioctl_import_dmabuf ioparam;
+		struct dma_buf *new_dmabuf;
+
+		/* Get the parameter data. */
+		if (copy_from_user
+		    (&ioparam, (void *)arg, sizeof(ioparam)) != 0) {
+			pr_err("[%s]: failed to copy-from-user for cmd %x\n",
+			       __func__, cmdnr);
+			ret = -EFAULT;
+			break;
+		}
+
+		ret = vc_sm_cma_import_dmabuf_internal(file_data,
+						       NULL,
+						       ioparam.dmabuf_fd,
+						       &new_dmabuf);
+
+		if (!ret) {
+			struct vc_sm_buffer *buf = new_dmabuf->priv;
+
+			ioparam.size = buf->size;
+			ioparam.handle = dma_buf_fd(new_dmabuf,
+						    O_CLOEXEC);
+			ioparam.vc_handle = buf->vc_handle;
+			ioparam.dma_addr = buf->dma_addr;
+
+			if (ioparam.handle < 0 ||
+			    (copy_to_user((void *)arg, &ioparam,
+					  sizeof(ioparam)) != 0)) {
+				dma_buf_put(new_dmabuf);
+				/* FIXME: Release allocation */
+				ret = -EFAULT;
+			}
+		}
+		break;
+	}
+
+	default:
+		pr_debug("[%s]: cmd %x tgid %u, owner %u\n", __func__, cmdnr,
+			 current->tgid, file_data->pid);
+
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+#ifdef CONFIG_COMPAT
+struct vc_sm_cma_ioctl_clean_invalid2_32 {
+	u32 op_count;
+	struct vc_sm_cma_ioctl_clean_invalid_block_32 {
+		u16 invalidate_mode;
+		u16 block_count;
+		compat_uptr_t start_address;
+		u32 block_size;
+		u32 inter_block_stride;
+	} s[0];
+};
+
+#define VC_SM_CMA_CMD_CLEAN_INVALID2_32\
+	_IOR(VC_SM_CMA_MAGIC_TYPE, VC_SM_CMA_CMD_CLEAN_INVALID2,\
+	 struct vc_sm_cma_ioctl_clean_invalid2_32)
+
+static long vc_sm_cma_compat_ioctl(struct file *file, unsigned int cmd,
+				   unsigned long arg)
+{
+	switch (cmd) {
+	case VC_SM_CMA_CMD_CLEAN_INVALID2_32:
+		/* FIXME */
+		return -EINVAL;
+
+	default:
+		return vc_sm_cma_ioctl(file, cmd, arg);
+	}
+}
+#endif
+
+/* Device operations that we managed in this driver. */
+static const struct file_operations vc_sm_ops = {
+	.owner = THIS_MODULE,
+	.unlocked_ioctl = vc_sm_cma_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = vc_sm_cma_compat_ioctl,
+#endif
+	.open = vc_sm_cma_open,
+	.release = vc_sm_cma_release,
+};
+
+/* Driver load/unload functions */
+/* Videocore connected.  */
+static void vc_sm_connected_init(void)
+{
+	int ret;
+	struct vc_sm_version version;
+	struct vc_sm_result_t version_result;
+
+	/*
+	 * Digging the vchiq_drv_mgmt, so low here and through a global seems
+	 * suspicious.
+	 *
+	 * The callbacks should be able to pass a parameter or context.
+	 */
+	struct vchiq_drv_mgmt *mgmt = dev_get_drvdata(sm_state->device->dev.parent);
+
+	pr_info("[%s]: start\n", __func__);
+
+	/*
+	 * Initialize and create a VCHI connection for the shared memory service
+	 * running on videocore.
+	 */
+	ret = vchiq_initialise(&mgmt->state, &sm_state->vchiq_instance);
+	if (ret) {
+		pr_err("[%s]: failed to initialise VCHI instance (ret=%d)\n",
+		       __func__, ret);
+
+		return;
+	}
+
+	ret = vchiq_connect(sm_state->vchiq_instance);
+	if (ret) {
+		pr_err("[%s]: failed to connect VCHI instance (ret=%d)\n",
+		       __func__, ret);
+
+		return;
+	}
+
+	/* Initialize an instance of the shared memory service. */
+	sm_state->sm_handle = vc_sm_cma_vchi_init(sm_state->vchiq_instance, 1,
+						  vc_sm_vpu_event);
+	if (!sm_state->sm_handle) {
+		pr_err("[%s]: failed to initialize shared memory service\n",
+		       __func__);
+
+		return;
+	}
+
+	/* Create a debug fs directory entry (root). */
+	sm_state->dir_root = debugfs_create_dir(VC_SM_DIR_ROOT_NAME, NULL);
+
+	sm_state->dir_state.show = &vc_sm_cma_global_state_show;
+	sm_state->dir_state.dir_entry =
+		debugfs_create_file(VC_SM_STATE, 0444, sm_state->dir_root,
+				    &sm_state->dir_state,
+				    &vc_sm_cma_debug_fs_fops);
+
+	INIT_LIST_HEAD(&sm_state->buffer_list);
+
+	/* Create a shared memory device. */
+	sm_state->misc_dev.minor = MISC_DYNAMIC_MINOR;
+	sm_state->misc_dev.name = DEVICE_NAME;
+	sm_state->misc_dev.fops = &vc_sm_ops;
+	sm_state->misc_dev.parent = NULL;
+	/* Temporarily set as 666 until udev rules have been sorted */
+	sm_state->misc_dev.mode = 0666;
+	ret = misc_register(&sm_state->misc_dev);
+	if (ret) {
+		pr_err("vcsm-cma: failed to register misc device.\n");
+		goto err_remove_debugfs;
+	}
+
+	sm_state->data_knl = vc_sm_cma_create_priv_data(0);
+	if (!sm_state->data_knl) {
+		pr_err("[%s]: failed to create kernel private data tracker\n",
+		       __func__);
+		goto err_remove_misc_dev;
+	}
+
+	version.version = 2;
+	ret = vc_sm_cma_vchi_client_version(sm_state->sm_handle, &version,
+					    &version_result,
+					    &sm_state->int_trans_id);
+	if (ret) {
+		pr_err("[%s]: Failed to send version request %d\n", __func__,
+		       ret);
+	}
+
+	/* Done! */
+	sm_inited = 1;
+	pr_info("[%s]: installed successfully\n", __func__);
+	return;
+
+err_remove_misc_dev:
+	misc_deregister(&sm_state->misc_dev);
+err_remove_debugfs:
+	debugfs_remove_recursive(sm_state->dir_root);
+	vc_sm_cma_vchi_stop(sm_state->vchiq_instance, &sm_state->sm_handle);
+}
+
+/* Driver loading. */
+static int bcm2835_vc_sm_cma_probe(struct vchiq_device *device)
+{
+	int err;
+
+	pr_info("%s: Videocore shared memory driver\n", __func__);
+
+	err = dma_set_mask_and_coherent(&device->dev, DMA_BIT_MASK(32));
+	if (err) {
+		dev_err(&device->dev, "dma_set_mask_and_coherent failed: %d\n",
+			err);
+		return err;
+	}
+
+	sm_state = devm_kzalloc(&device->dev, sizeof(*sm_state), GFP_KERNEL);
+	if (!sm_state)
+		return -ENOMEM;
+	sm_state->device = device;
+	mutex_init(&sm_state->map_lock);
+
+	mutex_init(&sm_state->kernelid_map_lock);
+	idr_init_base(&sm_state->kernelid_map, 1);
+
+	device->dev.dma_parms = devm_kzalloc(&device->dev,
+					     sizeof(*device->dev.dma_parms),
+					     GFP_KERNEL);
+	/* dma_set_max_seg_size checks if dma_parms is NULL. */
+	dma_set_max_seg_size(&device->dev, 0x3FFFFFFF);
+
+	vchiq_add_connected_callback(device, vc_sm_connected_init);
+	return 0;
+}
+
+/* Driver unloading. */
+static void bcm2835_vc_sm_cma_remove(struct vchiq_device *device)
+{
+	pr_debug("[%s]: start\n", __func__);
+	if (sm_inited) {
+		misc_deregister(&sm_state->misc_dev);
+
+		/* Remove all proc entries. */
+		debugfs_remove_recursive(sm_state->dir_root);
+
+		/* Stop the videocore shared memory service. */
+		vc_sm_cma_vchi_stop(sm_state->vchiq_instance, &sm_state->sm_handle);
+	}
+
+	if (sm_state) {
+		idr_destroy(&sm_state->kernelid_map);
+
+		/* Free the memory for the state structure. */
+		mutex_destroy(&sm_state->map_lock);
+	}
+
+	pr_debug("[%s]: end\n", __func__);
+}
+
+/* Kernel API calls */
+/* Get an internal resource handle mapped from the external one. */
+int vc_sm_cma_int_handle(void *handle)
+{
+	struct dma_buf *dma_buf = (struct dma_buf *)handle;
+	struct vc_sm_buffer *buf;
+
+	/* Validate we can work with this device. */
+	if (!sm_state || !handle) {
+		pr_err("[%s]: invalid input\n", __func__);
+		return 0;
+	}
+
+	buf = (struct vc_sm_buffer *)dma_buf->priv;
+	return buf->vc_handle;
+}
+EXPORT_SYMBOL_GPL(vc_sm_cma_int_handle);
+
+/* Free a previously allocated shared memory handle and block. */
+int vc_sm_cma_free(void *handle)
+{
+	struct dma_buf *dma_buf = (struct dma_buf *)handle;
+
+	/* Validate we can work with this device. */
+	if (!sm_state || !handle) {
+		pr_err("[%s]: invalid input\n", __func__);
+		return -EPERM;
+	}
+
+	pr_debug("%s: handle %p/dmabuf %p\n", __func__, handle, dma_buf);
+
+	dma_buf_put(dma_buf);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(vc_sm_cma_free);
+
+/* Import a dmabuf to be shared with VC. */
+int vc_sm_cma_import_dmabuf(struct dma_buf *src_dmabuf, void **handle)
+{
+	struct dma_buf *new_dma_buf;
+	int ret;
+
+	/* Validate we can work with this device. */
+	if (!sm_state || !src_dmabuf || !handle) {
+		pr_err("[%s]: invalid input\n", __func__);
+		return -EPERM;
+	}
+
+	ret = vc_sm_cma_import_dmabuf_internal(sm_state->data_knl, src_dmabuf,
+					       -1, &new_dma_buf);
+
+	if (!ret) {
+		pr_debug("%s: imported to ptr %p\n", __func__, new_dma_buf);
+
+		/* Assign valid handle at this time.*/
+		*handle = new_dma_buf;
+	} else {
+		/*
+		 * succeeded in importing the dma_buf, but then
+		 * failed to look it up again. How?
+		 * Release the fd again.
+		 */
+		pr_err("%s: imported vc_sm_cma_get_buffer failed %d\n",
+		       __func__, ret);
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(vc_sm_cma_import_dmabuf);
+
+static struct vchiq_driver bcm2835_vcsm_cma_driver = {
+	.probe = bcm2835_vc_sm_cma_probe,
+	.remove = bcm2835_vc_sm_cma_remove,
+	.driver = {
+		   .name = DEVICE_NAME,
+		   .owner = THIS_MODULE,
+		   },
+};
+
+module_vchiq_driver(bcm2835_vcsm_cma_driver);
+
+MODULE_AUTHOR("Dave Stevenson");
+MODULE_DESCRIPTION("VideoCore CMA Shared Memory Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("vcsm-cma");
diff --git a/drivers/staging/vc04_services/vc-sm-cma/vc_sm.h b/drivers/staging/vc04_services/vc-sm-cma/vc_sm.h
new file mode 100644
index 000000000000..2f0dc7045da6
--- /dev/null
+++ b/drivers/staging/vc04_services/vc-sm-cma/vc_sm.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * VideoCore Shared Memory driver using CMA.
+ *
+ * Copyright: 2018, Raspberry Pi (Trading) Ltd
+ *
+ */
+
+#ifndef VC_SM_H
+#define VC_SM_H
+
+#include <linux/device.h>
+#include <linux/dma-direction.h>
+#include <linux/kref.h>
+#include <linux/mm_types.h>
+#include <linux/mutex.h>
+#include <linux/rbtree.h>
+#include <linux/sched.h>
+#include <linux/shrinker.h>
+#include <linux/types.h>
+#include <linux/miscdevice.h>
+
+#define VC_SM_MAX_NAME_LEN 32
+
+enum vc_sm_vpu_mapping_state {
+	VPU_NOT_MAPPED,
+	VPU_MAPPED,
+	VPU_UNMAPPING
+};
+
+struct vc_sm_alloc_data {
+	unsigned long num_pages;
+	void *priv_virt;
+	struct sg_table *sg_table;
+};
+
+struct vc_sm_imported {
+	struct dma_buf *dma_buf;
+	struct dma_buf_attachment *attach;
+	struct sg_table *sgt;
+};
+
+struct vc_sm_buffer {
+	struct list_head global_buffer_list;	/* Global list of buffers. */
+
+	/* Index in the kernel_id idr so that we can find the
+	 * mmal_msg_context again when servicing the VCHI reply.
+	 */
+	int kernel_id;
+
+	size_t size;
+
+	/* Lock over all the following state for this buffer */
+	struct mutex lock;
+	struct list_head attachments;
+
+	char name[VC_SM_MAX_NAME_LEN];
+
+	bool in_use:1;   /* Kernel is still using this resource */
+	bool imported:1; /* Imported dmabuf */
+
+	enum vc_sm_vpu_mapping_state vpu_state;
+	u32 vc_handle;	/* VideoCore handle for this buffer */
+	int vpu_allocated;	/*
+				 * The VPU made this allocation. Release the
+				 * local dma_buf when the VPU releases the
+				 * resource.
+				 */
+
+	/* DMABUF related fields */
+	struct dma_buf *dma_buf;
+	dma_addr_t dma_addr;
+	void *cookie;
+
+	struct vc_sm_privdata_t *private;
+
+	union {
+		struct vc_sm_alloc_data alloc;
+		struct vc_sm_imported import;
+	};
+};
+
+#endif
diff --git a/drivers/staging/vc04_services/vc-sm-cma/vc_sm_cma_vchi.c b/drivers/staging/vc04_services/vc-sm-cma/vc_sm_cma_vchi.c
new file mode 100644
index 000000000000..ddfef55d289d
--- /dev/null
+++ b/drivers/staging/vc04_services/vc-sm-cma/vc_sm_cma_vchi.c
@@ -0,0 +1,511 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * VideoCore Shared Memory CMA allocator
+ *
+ * Copyright: 2018, Raspberry Pi (Trading) Ltd
+ * Copyright 2011-2012 Broadcom Corporation.  All rights reserved.
+ *
+ * Based on vmcs_sm driver from Broadcom Corporation.
+ *
+ */
+
+/* ---- Include Files ----------------------------------------------------- */
+#include <linux/completion.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/semaphore.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include "vc_sm_cma_vchi.h"
+
+#define VC_SM_VER  1
+#define VC_SM_MIN_VER 0
+
+/* ---- Private Constants and Types -------------------------------------- */
+
+/* Command blocks come from a pool */
+#define SM_MAX_NUM_CMD_RSP_BLKS 32
+
+/* The number of supported connections */
+#define SM_MAX_NUM_CONNECTIONS 3
+
+struct sm_cmd_rsp_blk {
+	struct list_head head;	/* To create lists */
+	/* To be signaled when the response is there */
+	struct completion cmplt;
+
+	u32 id;
+	u16 length;
+
+	u8 msg[VC_SM_MAX_MSG_LEN];
+
+	uint32_t wait:1;
+	uint32_t sent:1;
+	uint32_t alloc:1;
+
+};
+
+struct sm_instance {
+	u32 num_connections;
+	unsigned int service_handle[SM_MAX_NUM_CONNECTIONS];
+	struct task_struct *io_thread;
+	struct completion io_cmplt;
+
+	vpu_event_cb vpu_event;
+
+	/* Mutex over the following lists */
+	struct mutex lock;
+	u32 trans_id;
+	struct list_head cmd_list;
+	struct list_head rsp_list;
+	struct list_head dead_list;
+
+	struct sm_cmd_rsp_blk free_blk[SM_MAX_NUM_CMD_RSP_BLKS];
+
+	/* Mutex over the free_list */
+	struct mutex free_lock;
+	struct list_head free_list;
+
+	struct semaphore free_sema;
+	struct vchiq_instance *vchiq_instance;
+};
+
+/* ---- Private Variables ------------------------------------------------ */
+
+/* ---- Private Function Prototypes -------------------------------------- */
+
+/* ---- Private Functions ------------------------------------------------ */
+static int
+bcm2835_vchi_msg_queue(struct vchiq_instance *vchiq_instance, unsigned int handle,
+		       void *data,
+		       unsigned int size)
+{
+	return vchiq_queue_kernel_message(vchiq_instance, handle, data, size);
+}
+
+static struct
+sm_cmd_rsp_blk *vc_vchi_cmd_create(struct sm_instance *instance,
+				   enum vc_sm_msg_type id, void *msg,
+				   u32 size, int wait)
+{
+	struct sm_cmd_rsp_blk *blk;
+	struct vc_sm_msg_hdr_t *hdr;
+
+	if (down_interruptible(&instance->free_sema)) {
+		blk = kmalloc(sizeof(*blk), GFP_KERNEL);
+		if (!blk)
+			return NULL;
+
+		blk->alloc = 1;
+		init_completion(&blk->cmplt);
+	} else {
+		mutex_lock(&instance->free_lock);
+		blk =
+		    list_first_entry(&instance->free_list,
+				     struct sm_cmd_rsp_blk, head);
+		list_del(&blk->head);
+		mutex_unlock(&instance->free_lock);
+	}
+
+	blk->sent = 0;
+	blk->wait = wait;
+	blk->length = sizeof(*hdr) + size;
+
+	hdr = (struct vc_sm_msg_hdr_t *)blk->msg;
+	hdr->type = id;
+	mutex_lock(&instance->lock);
+	instance->trans_id++;
+	/*
+	 * Retain the top bit for identifying asynchronous events, or VPU cmds.
+	 */
+	instance->trans_id &= ~0x80000000;
+	hdr->trans_id = instance->trans_id;
+	blk->id = instance->trans_id;
+	mutex_unlock(&instance->lock);
+
+	if (size)
+		memcpy(hdr->body, msg, size);
+
+	return blk;
+}
+
+static void
+vc_vchi_cmd_delete(struct sm_instance *instance, struct sm_cmd_rsp_blk *blk)
+{
+	if (blk->alloc) {
+		kfree(blk);
+		return;
+	}
+
+	mutex_lock(&instance->free_lock);
+	list_add(&blk->head, &instance->free_list);
+	mutex_unlock(&instance->free_lock);
+	up(&instance->free_sema);
+}
+
+static void vc_sm_cma_vchi_rx_ack(struct sm_instance *instance,
+				  struct sm_cmd_rsp_blk *cmd,
+				  struct vc_sm_result_t *reply,
+				  u32 reply_len)
+{
+	mutex_lock(&instance->lock);
+	list_for_each_entry(cmd,
+			    &instance->rsp_list,
+			    head) {
+		if (cmd->id == reply->trans_id)
+			break;
+	}
+	mutex_unlock(&instance->lock);
+
+	if (&cmd->head == &instance->rsp_list) {
+		//pr_debug("%s: received response %u, throw away...",
+		pr_err("%s: received response %u, throw away...",
+		       __func__,
+		       reply->trans_id);
+	} else if (reply_len > sizeof(cmd->msg)) {
+		pr_err("%s: reply too big (%u) %u, throw away...",
+		       __func__, reply_len,
+		     reply->trans_id);
+	} else {
+		memcpy(cmd->msg, reply,
+		       reply_len);
+		complete(&cmd->cmplt);
+	}
+}
+
+static int vc_sm_cma_vchi_videocore_io(void *arg)
+{
+	struct sm_instance *instance = arg;
+	struct sm_cmd_rsp_blk *cmd = NULL, *cmd_tmp;
+	struct vc_sm_result_t *reply;
+	struct vchiq_header *header;
+	s32 status;
+	int svc_use = 1;
+
+	while (1) {
+		if (svc_use)
+			vchiq_release_service(instance->vchiq_instance, instance->service_handle[0]);
+		svc_use = 0;
+
+		if (wait_for_completion_interruptible(&instance->io_cmplt))
+			continue;
+		vchiq_use_service(instance->vchiq_instance, instance->service_handle[0]);
+		svc_use = 1;
+
+		do {
+			/*
+			 * Get new command and move it to response list
+			 */
+			mutex_lock(&instance->lock);
+			if (list_empty(&instance->cmd_list)) {
+				/* no more commands to process */
+				mutex_unlock(&instance->lock);
+				break;
+			}
+			cmd = list_first_entry(&instance->cmd_list,
+					       struct sm_cmd_rsp_blk, head);
+			list_move(&cmd->head, &instance->rsp_list);
+			cmd->sent = 1;
+			mutex_unlock(&instance->lock);
+			/* Send the command */
+			status =
+				bcm2835_vchi_msg_queue(instance->vchiq_instance,
+						       instance->service_handle[0],
+						       cmd->msg, cmd->length);
+			if (status) {
+				pr_err("%s: failed to queue message (%d)",
+				       __func__, status);
+			}
+
+			/* If no reply is needed then we're done */
+			if (!cmd->wait) {
+				mutex_lock(&instance->lock);
+				list_del(&cmd->head);
+				mutex_unlock(&instance->lock);
+				vc_vchi_cmd_delete(instance, cmd);
+				continue;
+			}
+
+			if (status) {
+				complete(&cmd->cmplt);
+				continue;
+			}
+
+		} while (1);
+
+		while ((header = vchiq_msg_hold(instance->vchiq_instance,
+						instance->service_handle[0]))) {
+			reply = (struct vc_sm_result_t *)header->data;
+			if (reply->trans_id & 0x80000000) {
+				/* Async event or cmd from the VPU */
+				if (instance->vpu_event)
+					instance->vpu_event(instance, reply,
+							    header->size);
+			} else {
+				vc_sm_cma_vchi_rx_ack(instance, cmd, reply,
+						      header->size);
+			}
+
+			vchiq_release_message(instance->vchiq_instance,
+					      instance->service_handle[0],
+					      header);
+		}
+
+		/* Go through the dead list and free them */
+		mutex_lock(&instance->lock);
+		list_for_each_entry_safe(cmd, cmd_tmp, &instance->dead_list,
+					 head) {
+			list_del(&cmd->head);
+			vc_vchi_cmd_delete(instance, cmd);
+		}
+		mutex_unlock(&instance->lock);
+	}
+
+	return 0;
+}
+
+static int vc_sm_cma_vchi_callback(struct vchiq_instance *vchiq_instance,
+						 enum vchiq_reason reason,
+						 struct vchiq_header *header,
+						 unsigned int handle, void *userdata)
+{
+	struct sm_instance *instance = vchiq_get_service_userdata(vchiq_instance, handle);
+
+	switch (reason) {
+	case VCHIQ_MESSAGE_AVAILABLE:
+		vchiq_msg_queue_push(vchiq_instance, handle, header);
+		complete(&instance->io_cmplt);
+		break;
+
+	case VCHIQ_SERVICE_CLOSED:
+		pr_info("%s: service CLOSED!!", __func__);
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+struct sm_instance *vc_sm_cma_vchi_init(struct vchiq_instance *vchiq_instance,
+					unsigned int num_connections,
+					vpu_event_cb vpu_event)
+{
+	u32 i;
+	struct sm_instance *instance;
+	int status;
+
+	pr_debug("%s: start", __func__);
+
+	if (num_connections > SM_MAX_NUM_CONNECTIONS) {
+		pr_err("%s: unsupported number of connections %u (max=%u)",
+		       __func__, num_connections, SM_MAX_NUM_CONNECTIONS);
+
+		goto err_null;
+	}
+	/* Allocate memory for this instance */
+	instance = kzalloc(sizeof(*instance), GFP_KERNEL);
+
+	/* Misc initialisations */
+	mutex_init(&instance->lock);
+	init_completion(&instance->io_cmplt);
+	INIT_LIST_HEAD(&instance->cmd_list);
+	INIT_LIST_HEAD(&instance->rsp_list);
+	INIT_LIST_HEAD(&instance->dead_list);
+	INIT_LIST_HEAD(&instance->free_list);
+	sema_init(&instance->free_sema, SM_MAX_NUM_CMD_RSP_BLKS);
+	mutex_init(&instance->free_lock);
+	for (i = 0; i < SM_MAX_NUM_CMD_RSP_BLKS; i++) {
+		init_completion(&instance->free_blk[i].cmplt);
+		list_add(&instance->free_blk[i].head, &instance->free_list);
+	}
+
+	instance->vchiq_instance = vchiq_instance;
+
+	/* Open the VCHI service connections */
+	instance->num_connections = num_connections;
+	for (i = 0; i < num_connections; i++) {
+		struct vchiq_service_params_kernel params = {
+			.version = VC_SM_VER,
+			.version_min = VC_SM_MIN_VER,
+			.fourcc = VCHIQ_MAKE_FOURCC('S', 'M', 'E', 'M'),
+			.callback = vc_sm_cma_vchi_callback,
+			.userdata = instance,
+		};
+
+		status = vchiq_open_service(vchiq_instance, &params,
+					    &instance->service_handle[i]);
+		if (status) {
+			pr_err("%s: failed to open VCHI service (%d)",
+			       __func__, status);
+
+			goto err_close_services;
+		}
+	}
+	/* Create the thread which takes care of all io to/from videoocore. */
+	instance->io_thread = kthread_create(&vc_sm_cma_vchi_videocore_io,
+					     (void *)instance, "SMIO");
+	if (!instance->io_thread) {
+		pr_err("%s: failed to create SMIO thread", __func__);
+
+		goto err_close_services;
+	}
+	instance->vpu_event = vpu_event;
+	set_user_nice(instance->io_thread, -10);
+	wake_up_process(instance->io_thread);
+
+	pr_debug("%s: success - instance %p", __func__, instance);
+	return instance;
+
+err_close_services:
+	for (i = 0; i < instance->num_connections; i++) {
+		if (instance->service_handle[i])
+			vchiq_close_service(vchiq_instance, instance->service_handle[i]);
+	}
+	kfree(instance);
+err_null:
+	pr_debug("%s: FAILED", __func__);
+	return NULL;
+}
+
+int vc_sm_cma_vchi_stop(struct vchiq_instance *vchiq_instance, struct sm_instance **handle)
+{
+	struct sm_instance *instance;
+	u32 i;
+
+	if (!handle) {
+		pr_err("%s: invalid pointer to handle %p", __func__, handle);
+		goto lock;
+	}
+
+	if (!*handle) {
+		pr_err("%s: invalid handle %p", __func__, *handle);
+		goto lock;
+	}
+
+	instance = *handle;
+
+	/* Close all VCHI service connections */
+	for (i = 0; i < instance->num_connections; i++) {
+		vchiq_use_service(vchiq_instance, instance->service_handle[i]);
+		vchiq_close_service(vchiq_instance, instance->service_handle[i]);
+	}
+
+	kfree(instance);
+
+	*handle = NULL;
+	return 0;
+
+lock:
+	return -EINVAL;
+}
+
+static int vc_sm_cma_vchi_send_msg(struct sm_instance *handle,
+				   enum vc_sm_msg_type msg_id, void *msg,
+				   u32 msg_size, void *result, u32 result_size,
+				   u32 *cur_trans_id, u8 wait_reply)
+{
+	int status = 0;
+	struct sm_instance *instance = handle;
+	struct sm_cmd_rsp_blk *cmd_blk;
+
+	if (!handle) {
+		pr_err("%s: invalid handle", __func__);
+		return -EINVAL;
+	}
+	if (!msg) {
+		pr_err("%s: invalid msg pointer", __func__);
+		return -EINVAL;
+	}
+
+	cmd_blk =
+	    vc_vchi_cmd_create(instance, msg_id, msg, msg_size, wait_reply);
+	if (!cmd_blk) {
+		pr_err("[%s]: failed to allocate global tracking resource",
+		       __func__);
+		return -ENOMEM;
+	}
+
+	if (cur_trans_id)
+		*cur_trans_id = cmd_blk->id;
+
+	mutex_lock(&instance->lock);
+	list_add_tail(&cmd_blk->head, &instance->cmd_list);
+	mutex_unlock(&instance->lock);
+	complete(&instance->io_cmplt);
+
+	if (!wait_reply)
+		/* We're done */
+		return 0;
+
+	/* Wait for the response */
+	if (wait_for_completion_interruptible(&cmd_blk->cmplt)) {
+		mutex_lock(&instance->lock);
+		if (!cmd_blk->sent) {
+			list_del(&cmd_blk->head);
+			mutex_unlock(&instance->lock);
+			vc_vchi_cmd_delete(instance, cmd_blk);
+			return -ENXIO;
+		}
+
+		list_move(&cmd_blk->head, &instance->dead_list);
+		mutex_unlock(&instance->lock);
+		complete(&instance->io_cmplt);
+		return -EINTR;	/* We're done */
+	}
+
+	if (result && result_size) {
+		memcpy(result, cmd_blk->msg, result_size);
+	} else {
+		struct vc_sm_result_t *res =
+			(struct vc_sm_result_t *)cmd_blk->msg;
+		status = (res->success == 0) ? 0 : -ENXIO;
+	}
+
+	mutex_lock(&instance->lock);
+	list_del(&cmd_blk->head);
+	mutex_unlock(&instance->lock);
+	vc_vchi_cmd_delete(instance, cmd_blk);
+	return status;
+}
+
+int vc_sm_cma_vchi_free(struct sm_instance *handle, struct vc_sm_free_t *msg,
+			u32 *cur_trans_id)
+{
+	return vc_sm_cma_vchi_send_msg(handle, VC_SM_MSG_TYPE_FREE,
+				   msg, sizeof(*msg), 0, 0, cur_trans_id, 0);
+}
+
+int vc_sm_cma_vchi_import(struct sm_instance *handle, struct vc_sm_import *msg,
+			  struct vc_sm_import_result *result, u32 *cur_trans_id)
+{
+	return vc_sm_cma_vchi_send_msg(handle, VC_SM_MSG_TYPE_IMPORT,
+				   msg, sizeof(*msg), result, sizeof(*result),
+				   cur_trans_id, 1);
+}
+
+int vc_sm_cma_vchi_client_version(struct sm_instance *handle,
+				  struct vc_sm_version *msg,
+				  struct vc_sm_result_t *result,
+				  u32 *cur_trans_id)
+{
+	return vc_sm_cma_vchi_send_msg(handle, VC_SM_MSG_TYPE_CLIENT_VERSION,
+				   //msg, sizeof(*msg), result, sizeof(*result),
+				   //cur_trans_id, 1);
+				   msg, sizeof(*msg), NULL, 0,
+				   cur_trans_id, 0);
+}
+
+int vc_sm_vchi_client_vc_mem_req_reply(struct sm_instance *handle,
+				       struct vc_sm_vc_mem_request_result *msg,
+				       uint32_t *cur_trans_id)
+{
+	return vc_sm_cma_vchi_send_msg(handle,
+				       VC_SM_MSG_TYPE_VC_MEM_REQUEST_REPLY,
+				       msg, sizeof(*msg), 0, 0, cur_trans_id,
+				       0);
+}
diff --git a/drivers/staging/vc04_services/vc-sm-cma/vc_sm_cma_vchi.h b/drivers/staging/vc04_services/vc-sm-cma/vc_sm_cma_vchi.h
new file mode 100644
index 000000000000..5dd23ad408fc
--- /dev/null
+++ b/drivers/staging/vc04_services/vc-sm-cma/vc_sm_cma_vchi.h
@@ -0,0 +1,63 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * VideoCore Shared Memory CMA allocator
+ *
+ * Copyright: 2018, Raspberry Pi (Trading) Ltd
+ * Copyright 2011-2012 Broadcom Corporation.  All rights reserved.
+ *
+ * Based on vmcs_sm driver from Broadcom Corporation.
+ *
+ */
+
+#ifndef __VC_SM_CMA_VCHI_H__INCLUDED__
+#define __VC_SM_CMA_VCHI_H__INCLUDED__
+
+#include "../include/linux/raspberrypi/vchiq.h"
+
+#include "vc_sm_defs.h"
+
+/*
+ * Forward declare.
+ */
+struct sm_instance;
+
+typedef void (*vpu_event_cb)(struct sm_instance *instance,
+			     struct vc_sm_result_t *reply, int reply_len);
+
+/*
+ * Initialize the shared memory service, opens up vchi connection to talk to it.
+ */
+struct sm_instance *vc_sm_cma_vchi_init(struct vchiq_instance *vchi_instance,
+					unsigned int num_connections,
+					vpu_event_cb vpu_event);
+
+/*
+ * Terminates the shared memory service.
+ */
+int vc_sm_cma_vchi_stop(struct vchiq_instance *vchi_instance, struct sm_instance **handle);
+
+/*
+ * Ask the shared memory service to free up some memory that was previously
+ * allocated by the vc_sm_cma_vchi_alloc function call.
+ */
+int vc_sm_cma_vchi_free(struct sm_instance *handle, struct vc_sm_free_t *msg,
+			u32 *cur_trans_id);
+
+/*
+ * Import a contiguous block of memory and wrap it in a GPU MEM_HANDLE_T.
+ */
+int vc_sm_cma_vchi_import(struct sm_instance *handle, struct vc_sm_import *msg,
+			  struct vc_sm_import_result *result,
+			  u32 *cur_trans_id);
+
+int vc_sm_cma_vchi_client_version(struct sm_instance *handle,
+				  struct vc_sm_version *msg,
+				  struct vc_sm_result_t *result,
+				  u32 *cur_trans_id);
+
+int vc_sm_vchi_client_vc_mem_req_reply(struct sm_instance *handle,
+				       struct vc_sm_vc_mem_request_result *msg,
+				       uint32_t *cur_trans_id);
+
+#endif /* __VC_SM_CMA_VCHI_H__INCLUDED__ */
diff --git a/drivers/staging/vc04_services/vc-sm-cma/vc_sm_defs.h b/drivers/staging/vc04_services/vc-sm-cma/vc_sm_defs.h
new file mode 100644
index 000000000000..8b6a395c640d
--- /dev/null
+++ b/drivers/staging/vc04_services/vc-sm-cma/vc_sm_defs.h
@@ -0,0 +1,296 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * VideoCore Shared Memory CMA allocator
+ *
+ * Copyright: 2018, Raspberry Pi (Trading) Ltd
+ *
+ * Based on vc_sm_defs.h from the vmcs_sm driver Copyright Broadcom Corporation.
+ * All IPC messages are copied across to this file, even if the vc-sm-cma
+ * driver is not currently using them.
+ *
+ ****************************************************************************
+ */
+
+#ifndef __VC_SM_DEFS_H__INCLUDED__
+#define __VC_SM_DEFS_H__INCLUDED__
+
+/* Maximum message length */
+#define VC_SM_MAX_MSG_LEN (sizeof(union vc_sm_msg_union_t) + \
+	sizeof(struct vc_sm_msg_hdr_t))
+#define VC_SM_MAX_RSP_LEN (sizeof(union vc_sm_msg_union_t))
+
+/* Resource name maximum size */
+#define VC_SM_RESOURCE_NAME 32
+
+/*
+ * Version to be reported to the VPU
+ * VPU assumes 0 (aka 1) which does not require the released callback, nor
+ * expect the client to handle VC_MEM_REQUESTS.
+ * Version 2 requires the released callback, and must support VC_MEM_REQUESTS.
+ */
+#define VC_SM_PROTOCOL_VERSION	2
+
+enum vc_sm_msg_type {
+	/* Message types supported for HOST->VC direction */
+
+	/* Allocate shared memory block */
+	VC_SM_MSG_TYPE_ALLOC,
+	/* Lock allocated shared memory block */
+	VC_SM_MSG_TYPE_LOCK,
+	/* Unlock allocated shared memory block */
+	VC_SM_MSG_TYPE_UNLOCK,
+	/* Unlock allocated shared memory block, do not answer command */
+	VC_SM_MSG_TYPE_UNLOCK_NOANS,
+	/* Free shared memory block */
+	VC_SM_MSG_TYPE_FREE,
+	/* Resize a shared memory block */
+	VC_SM_MSG_TYPE_RESIZE,
+	/* Walk the allocated shared memory block(s) */
+	VC_SM_MSG_TYPE_WALK_ALLOC,
+
+	/* A previously applied action will need to be reverted */
+	VC_SM_MSG_TYPE_ACTION_CLEAN,
+
+	/*
+	 * Import a physical address and wrap into a MEM_HANDLE_T.
+	 * Release with VC_SM_MSG_TYPE_FREE.
+	 */
+	VC_SM_MSG_TYPE_IMPORT,
+	/*
+	 *Tells VC the protocol version supported by this client.
+	 * 2 supports the async/cmd messages from the VPU for final release
+	 * of memory, and for VC allocations.
+	 */
+	VC_SM_MSG_TYPE_CLIENT_VERSION,
+	/* Response to VC request for memory */
+	VC_SM_MSG_TYPE_VC_MEM_REQUEST_REPLY,
+
+	/*
+	 * Asynchronous/cmd messages supported for VC->HOST direction.
+	 * Signalled by setting the top bit in vc_sm_result_t trans_id.
+	 */
+
+	/*
+	 * VC has finished with an imported memory allocation.
+	 * Release any Linux reference counts on the underlying block.
+	 */
+	VC_SM_MSG_TYPE_RELEASED,
+	/* VC request for memory */
+	VC_SM_MSG_TYPE_VC_MEM_REQUEST,
+
+	VC_SM_MSG_TYPE_MAX
+};
+
+/* Type of memory to be allocated */
+enum vc_sm_alloc_type_t {
+	VC_SM_ALLOC_CACHED,
+	VC_SM_ALLOC_NON_CACHED,
+};
+
+/* Message header for all messages in HOST->VC direction */
+struct vc_sm_msg_hdr_t {
+	u32 type;
+	u32 trans_id;
+	u8 body[];
+};
+
+/* Request to allocate memory (HOST->VC) */
+struct vc_sm_alloc_t {
+	/* type of memory to allocate */
+	enum vc_sm_alloc_type_t type;
+	/* byte amount of data to allocate per unit */
+	u32 base_unit;
+	/* number of unit to allocate */
+	u32 num_unit;
+	/* alignment to be applied on allocation */
+	u32 alignment;
+	/* identity of who allocated this block */
+	u32 allocator;
+	/* resource name (for easier tracking on vc side) */
+	char name[VC_SM_RESOURCE_NAME];
+
+};
+
+/* Result of a requested memory allocation (VC->HOST) */
+struct vc_sm_alloc_result_t {
+	/* Transaction identifier */
+	u32 trans_id;
+
+	/* Resource handle */
+	u32 res_handle;
+	/* Pointer to resource buffer */
+	u32 res_mem;
+	/* Resource base size (bytes) */
+	u32 res_base_size;
+	/* Resource number */
+	u32 res_num;
+
+};
+
+/* Request to free a previously allocated memory (HOST->VC) */
+struct vc_sm_free_t {
+	/* Resource handle (returned from alloc) */
+	u32 res_handle;
+	/* Resource buffer (returned from alloc) */
+	u32 res_mem;
+
+};
+
+/* Request to lock a previously allocated memory (HOST->VC) */
+struct vc_sm_lock_unlock_t {
+	/* Resource handle (returned from alloc) */
+	u32 res_handle;
+	/* Resource buffer (returned from alloc) */
+	u32 res_mem;
+
+};
+
+/* Request to resize a previously allocated memory (HOST->VC) */
+struct vc_sm_resize_t {
+	/* Resource handle (returned from alloc) */
+	u32 res_handle;
+	/* Resource buffer (returned from alloc) */
+	u32 res_mem;
+	/* Resource *new* size requested (bytes) */
+	u32 res_new_size;
+
+};
+
+/* Result of a requested memory lock (VC->HOST) */
+struct vc_sm_lock_result_t {
+	/* Transaction identifier */
+	u32 trans_id;
+
+	/* Resource handle */
+	u32 res_handle;
+	/* Pointer to resource buffer */
+	u32 res_mem;
+	/*
+	 * Pointer to former resource buffer if the memory
+	 * was reallocated
+	 */
+	u32 res_old_mem;
+
+};
+
+/* Generic result for a request (VC->HOST) */
+struct vc_sm_result_t {
+	/* Transaction identifier */
+	u32 trans_id;
+
+	s32 success;
+
+};
+
+/* Request to revert a previously applied action (HOST->VC) */
+struct vc_sm_action_clean_t {
+	/* Action of interest */
+	enum vc_sm_msg_type res_action;
+	/* Transaction identifier for the action of interest */
+	u32 action_trans_id;
+
+};
+
+/* Request to remove all data associated with a given allocator (HOST->VC) */
+struct vc_sm_free_all_t {
+	/* Allocator identifier */
+	u32 allocator;
+};
+
+/* Request to import memory (HOST->VC) */
+struct vc_sm_import {
+	/* type of memory to allocate */
+	enum vc_sm_alloc_type_t type;
+	/* pointer to the VC (ie physical) address of the allocated memory */
+	u32 addr;
+	/* size of buffer */
+	u32 size;
+	/* opaque handle returned in RELEASED messages */
+	u32 kernel_id;
+	/* Allocator identifier */
+	u32 allocator;
+	/* resource name (for easier tracking on vc side) */
+	char     name[VC_SM_RESOURCE_NAME];
+};
+
+/* Result of a requested memory import (VC->HOST) */
+struct vc_sm_import_result {
+	/* Transaction identifier */
+	u32 trans_id;
+
+	/* Resource handle */
+	u32 res_handle;
+};
+
+/* Notification that VC has finished with an allocation (VC->HOST) */
+struct vc_sm_released {
+	/* cmd type / trans_id */
+	u32 cmd;
+
+	/* pointer to the VC (ie physical) address of the allocated memory */
+	u32 addr;
+	/* size of buffer */
+	u32 size;
+	/* opaque handle returned in RELEASED messages */
+	u32 kernel_id;
+	u32 vc_handle;
+};
+
+/*
+ * Client informing VC as to the protocol version it supports.
+ * >=2 requires the released callback, and supports VC asking for memory.
+ * Failure means that the firmware doesn't support this call, and therefore the
+ * client should either fail, or NOT rely on getting the released callback.
+ */
+struct vc_sm_version {
+	u32 version;
+};
+
+/* Request FROM VideoCore for some memory */
+struct vc_sm_vc_mem_request {
+	/* cmd type */
+	u32 cmd;
+
+	/* trans_id (from VPU) */
+	u32 trans_id;
+	/* size of buffer */
+	u32 size;
+	/* alignment of buffer */
+	u32 align;
+	/* resource name (for easier tracking) */
+	char     name[VC_SM_RESOURCE_NAME];
+	/* VPU handle for the resource */
+	u32 vc_handle;
+};
+
+/* Response from the kernel to provide the VPU with some memory */
+struct vc_sm_vc_mem_request_result {
+	/* Transaction identifier for the VPU */
+	u32 trans_id;
+	/* pointer to the physical address of the allocated memory */
+	u32 addr;
+	/* opaque handle returned in RELEASED messages */
+	u32 kernel_id;
+};
+
+/* Union of ALL messages */
+union vc_sm_msg_union_t {
+	struct vc_sm_alloc_t alloc;
+	struct vc_sm_alloc_result_t alloc_result;
+	struct vc_sm_free_t free;
+	struct vc_sm_lock_unlock_t lock_unlock;
+	struct vc_sm_action_clean_t action_clean;
+	struct vc_sm_resize_t resize;
+	struct vc_sm_lock_result_t lock_result;
+	struct vc_sm_result_t result;
+	struct vc_sm_free_all_t free_all;
+	struct vc_sm_import import;
+	struct vc_sm_import_result import_result;
+	struct vc_sm_version version;
+	struct vc_sm_released released;
+	struct vc_sm_vc_mem_request vc_request;
+	struct vc_sm_vc_mem_request_result vc_request_result;
+};
+
+#endif /* __VC_SM_DEFS_H__INCLUDED__ */
diff --git a/drivers/staging/vc04_services/vc-sm-cma/vc_sm_knl.h b/drivers/staging/vc04_services/vc-sm-cma/vc_sm_knl.h
new file mode 100644
index 000000000000..988fdd967922
--- /dev/null
+++ b/drivers/staging/vc04_services/vc-sm-cma/vc_sm_knl.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * VideoCore Shared Memory CMA allocator
+ *
+ * Copyright: 2018, Raspberry Pi (Trading) Ltd
+ *
+ * Based on vc_sm_defs.h from the vmcs_sm driver Copyright Broadcom Corporation.
+ *
+ */
+
+#ifndef __VC_SM_KNL_H__INCLUDED__
+#define __VC_SM_KNL_H__INCLUDED__
+
+#if !defined(__KERNEL__)
+#error "This interface is for kernel use only..."
+#endif
+
+/* Free a previously allocated or imported shared memory handle and block. */
+int vc_sm_cma_free(void *handle);
+
+/* Get an internal resource handle mapped from the external one. */
+int vc_sm_cma_int_handle(void *handle);
+
+/* Import a block of memory into the GPU space. */
+int vc_sm_cma_import_dmabuf(struct dma_buf *dmabuf, void **handle);
+
+#endif /* __VC_SM_KNL_H__INCLUDED__ */
diff --git a/drivers/staging/vc04_services/vchiq-mmal/Kconfig b/drivers/staging/vc04_services/vchiq-mmal/Kconfig
index c99525a0bb45..5df9198cdab1 100644
--- a/drivers/staging/vc04_services/vchiq-mmal/Kconfig
+++ b/drivers/staging/vc04_services/vchiq-mmal/Kconfig
@@ -1,6 +1,7 @@
 config BCM2835_VCHIQ_MMAL
 	tristate "BCM2835 MMAL VCHIQ service"
-	depends on BCM2835_VCHIQ
+	select BCM2835_VCHIQ
+	select BCM_VC_SM_CMA
 	help
 	  Enables the MMAL API over VCHIQ interface as used for the
 	  majority of the multimedia services on VideoCore.
diff --git a/drivers/staging/vc04_services/vchiq-mmal/mmal-common.h b/drivers/staging/vc04_services/vchiq-mmal/mmal-common.h
index b33129403a30..a643cad54b12 100644
--- a/drivers/staging/vc04_services/vchiq-mmal/mmal-common.h
+++ b/drivers/staging/vc04_services/vchiq-mmal/mmal-common.h
@@ -50,6 +50,11 @@ struct mmal_buffer {
 
 	struct mmal_msg_context *msg_context;
 
+	struct dma_buf *dma_buf;/* Exported dmabuf fd from videobuf2 */
+	void *vcsm_handle;	/* VCSM handle having imported the dmabuf */
+	u32 vc_handle;		/* VC handle to that dmabuf */
+
+	u32 cmd;		/* MMAL command. 0=data. */
 	unsigned long length;
 	u32 mmal_flags;
 	s64 dts;
diff --git a/drivers/staging/vc04_services/vchiq-mmal/mmal-encodings.h b/drivers/staging/vc04_services/vchiq-mmal/mmal-encodings.h
index e15ae7b24f73..d8d7ec5b962c 100644
--- a/drivers/staging/vc04_services/vchiq-mmal/mmal-encodings.h
+++ b/drivers/staging/vc04_services/vchiq-mmal/mmal-encodings.h
@@ -69,10 +69,76 @@
  */
 #define MMAL_ENCODING_OPAQUE           MMAL_FOURCC('O', 'P', 'Q', 'V')
 
+/* Bayer formats
+ * FourCC values copied from V4L2 where defined.
+ */
+/* 8 bit per pixel Bayer formats. */
+#define MMAL_ENCODING_BAYER_SBGGR8     MMAL_FOURCC('B', 'A', '8', '1')
+#define MMAL_ENCODING_BAYER_SGBRG8     MMAL_FOURCC('G', 'B', 'R', 'G')
+#define MMAL_ENCODING_BAYER_SGRBG8     MMAL_FOURCC('G', 'R', 'B', 'G')
+#define MMAL_ENCODING_BAYER_SRGGB8     MMAL_FOURCC('R', 'G', 'G', 'B')
+
+/* 10 bit per pixel packed Bayer formats. */
+#define MMAL_ENCODING_BAYER_SBGGR10P   MMAL_FOURCC('p', 'B', 'A', 'A')
+#define MMAL_ENCODING_BAYER_SGRBG10P   MMAL_FOURCC('p', 'g', 'A', 'A')
+#define MMAL_ENCODING_BAYER_SGBRG10P   MMAL_FOURCC('p', 'G', 'A', 'A')
+#define MMAL_ENCODING_BAYER_SRGGB10P   MMAL_FOURCC('p', 'R', 'A', 'A')
+
+/* 12 bit per pixel packed Bayer formats. */
+#define MMAL_ENCODING_BAYER_SBGGR12P   MMAL_FOURCC('p', 'B', '1', '2')
+#define MMAL_ENCODING_BAYER_SGRBG12P   MMAL_FOURCC('p', 'g', '1', '2')
+#define MMAL_ENCODING_BAYER_SGBRG12P   MMAL_FOURCC('p', 'G', '1', '2')
+#define MMAL_ENCODING_BAYER_SRGGB12P   MMAL_FOURCC('p', 'R', '1', '2')
+
+//14 bit per pixel Bayer formats.
+#define MMAL_ENCODING_BAYER_SBGGR14P   MMAL_FOURCC('p', 'B', 'E', 'E')
+#define MMAL_ENCODING_BAYER_SGBRG14P   MMAL_FOURCC('p', 'G', 'E', 'E')
+#define MMAL_ENCODING_BAYER_SGRBG14P   MMAL_FOURCC('p', 'g', 'E', 'E')
+#define MMAL_ENCODING_BAYER_SRGGB14P   MMAL_FOURCC('p', 'R', 'E', 'E')
+
+/* 16 bit per pixel Bayer formats. */
+#define MMAL_ENCODING_BAYER_SBGGR16    MMAL_FOURCC('B', 'G', '1', '6')
+#define MMAL_ENCODING_BAYER_SGBRG16    MMAL_FOURCC('G', 'B', '1', '6')
+#define MMAL_ENCODING_BAYER_SGRBG16    MMAL_FOURCC('G', 'R', '1', '6')
+#define MMAL_ENCODING_BAYER_SRGGB16    MMAL_FOURCC('R', 'G', '1', '6')
+
+/* 10 bit per pixel unpacked (16bit) Bayer formats. */
+#define MMAL_ENCODING_BAYER_SBGGR10    MMAL_FOURCC('B', 'G', '1', '0')
+#define MMAL_ENCODING_BAYER_SGRBG10    MMAL_FOURCC('B', 'A', '1', '0')
+#define MMAL_ENCODING_BAYER_SGBRG10    MMAL_FOURCC('G', 'B', '1', '0')
+#define MMAL_ENCODING_BAYER_SRGGB10    MMAL_FOURCC('R', 'G', '1', '0')
+
+/* 12 bit per pixel unpacked (16bit) Bayer formats */
+#define MMAL_ENCODING_BAYER_SBGGR12    MMAL_FOURCC('B', 'G', '1', '2')
+#define MMAL_ENCODING_BAYER_SGRBG12    MMAL_FOURCC('B', 'A', '1', '2')
+#define MMAL_ENCODING_BAYER_SGBRG12    MMAL_FOURCC('G', 'B', '1', '2')
+#define MMAL_ENCODING_BAYER_SRGGB12    MMAL_FOURCC('R', 'G', '1', '2')
+
+/* 14 bit per pixel unpacked (16bit) Bayer formats */
+#define MMAL_ENCODING_BAYER_SBGGR14    MMAL_FOURCC('B', 'G', '1', '4')
+#define MMAL_ENCODING_BAYER_SGBRG14    MMAL_FOURCC('G', 'B', '1', '4')
+#define MMAL_ENCODING_BAYER_SGRBG14    MMAL_FOURCC('G', 'R', '1', '4')
+#define MMAL_ENCODING_BAYER_SRGGB14    MMAL_FOURCC('R', 'G', '1', '4')
+
+/* MIPI packed monochrome images */
+#define MMAL_ENCODING_GREY    MMAL_FOURCC('G', 'R', 'E', 'Y')
+#define MMAL_ENCODING_Y10P    MMAL_FOURCC('Y', '1', '0', 'P')
+#define MMAL_ENCODING_Y12P    MMAL_FOURCC('Y', '1', '2', 'P')
+#define MMAL_ENCODING_Y14P    MMAL_FOURCC('Y', '1', '4', 'P')
+#define MMAL_ENCODING_Y16     MMAL_FOURCC('Y', '1', '6', ' ')
+/* Unpacked monochrome formats (16bit per sample, but only N LSBs used) */
+#define MMAL_ENCODING_Y10     MMAL_FOURCC('Y', '1', '0', ' ')
+#define MMAL_ENCODING_Y12     MMAL_FOURCC('Y', '1', '2', ' ')
+#define MMAL_ENCODING_Y14     MMAL_FOURCC('Y', '1', '4', ' ')
+
 /** An EGL image handle
  */
 #define MMAL_ENCODING_EGL_IMAGE        MMAL_FOURCC('E', 'G', 'L', 'I')
 
+/** ISP image statistics format
+ */
+#define MMAL_ENCODING_BRCM_STATS       MMAL_FOURCC('S', 'T', 'A', 'T')
+
 /* }@ */
 
 /** \name Pre-defined audio encodings */
diff --git a/drivers/staging/vc04_services/vchiq-mmal/mmal-msg-format.h b/drivers/staging/vc04_services/vchiq-mmal/mmal-msg-format.h
index 5569876d8c7d..e8f5ca85a7c4 100644
--- a/drivers/staging/vc04_services/vchiq-mmal/mmal-msg-format.h
+++ b/drivers/staging/vc04_services/vchiq-mmal/mmal-msg-format.h
@@ -53,6 +53,16 @@ union mmal_es_specific_format {
 	struct mmal_subpicture_format subpicture;
 };
 
+/* The elementary stream will already be framed */
+#define MMAL_ES_FORMAT_FLAG_FRAMED				BIT(0)
+/*
+ * For column formats we ideally want to pass in the column stride. This hasn't
+ * been the past behaviour, so require a new flag to be set should
+ * es->video.width be the column stride (in lines) instead of an ignored width
+ * value.
+ */
+#define MMAL_ES_FORMAT_FLAG_COL_FMTS_WIDTH_IS_COL_STRIDE	BIT(1)
+
 /* Definition of an elementary stream format (MMAL_ES_FORMAT_T) */
 struct mmal_es_format_local {
 	u32 type;	/* enum mmal_es_type */
diff --git a/drivers/staging/vc04_services/vchiq-mmal/mmal-msg.h b/drivers/staging/vc04_services/vchiq-mmal/mmal-msg.h
index 471413248a14..baf37254645a 100644
--- a/drivers/staging/vc04_services/vchiq-mmal/mmal-msg.h
+++ b/drivers/staging/vc04_services/vchiq-mmal/mmal-msg.h
@@ -253,6 +253,25 @@ struct mmal_msg_port_action_reply {
 /* Signals that a buffer failed to be transmitted */
 #define MMAL_BUFFER_HEADER_FLAG_TRANSMISSION_FAILED    BIT(10)
 
+/* Video buffer header flags
+ * videobufferheaderflags
+ * The following flags describe properties of a video buffer header.
+ * As there is no collision with the MMAL_BUFFER_HEADER_FLAGS_ defines, these
+ * flags will also be present in the MMAL_BUFFER_HEADER_T flags field.
+ */
+#define MMAL_BUFFER_HEADER_FLAG_FORMAT_SPECIFIC_START_BIT 16
+#define MMAL_BUFFER_HEADER_FLAG_FORMAT_SPECIFIC_START \
+			(1 << MMAL_BUFFER_HEADER_FLAG_FORMAT_SPECIFIC_START_BIT)
+/* Signals an interlaced video frame */
+#define MMAL_BUFFER_HEADER_VIDEO_FLAG_INTERLACED \
+			(MMAL_BUFFER_HEADER_FLAG_FORMAT_SPECIFIC_START << 0)
+/*
+ * Signals that the top field of the current interlaced frame should be
+ * displayed first
+ */
+#define MMAL_BUFFER_HEADER_VIDEO_FLAG_TOP_FIELD_FIRST \
+			(MMAL_BUFFER_HEADER_FLAG_FORMAT_SPECIFIC_START << 1)
+
 struct mmal_driver_buffer {
 	u32 magic;
 	u32 component_handle;
@@ -346,6 +365,41 @@ struct mmal_msg_port_parameter_get_reply {
 /* event messages */
 #define MMAL_WORKER_EVENT_SPACE 256
 
+/* Four CC's for events */
+#define MMAL_FOURCC(a, b, c, d) ((a) | (b << 8) | (c << 16) | (d << 24))
+
+#define MMAL_EVENT_ERROR		MMAL_FOURCC('E', 'R', 'R', 'O')
+#define MMAL_EVENT_EOS			MMAL_FOURCC('E', 'E', 'O', 'S')
+#define MMAL_EVENT_FORMAT_CHANGED	MMAL_FOURCC('E', 'F', 'C', 'H')
+#define MMAL_EVENT_PARAMETER_CHANGED	MMAL_FOURCC('E', 'P', 'C', 'H')
+
+/* Structs for each of the event message payloads */
+struct mmal_msg_event_eos {
+	u32 port_type;	/**< Type of port that received the end of stream */
+	u32 port_index;	/**< Index of port that received the end of stream */
+};
+
+/** Format changed event data. */
+struct mmal_msg_event_format_changed {
+	/* Minimum size of buffers the port requires */
+	u32 buffer_size_min;
+	/* Minimum number of buffers the port requires */
+	u32 buffer_num_min;
+	/* Size of buffers the port recommends for optimal performance.
+	 * A value of zero means no special recommendation.
+	 */
+	u32 buffer_size_recommended;
+	/* Number of buffers the port recommends for optimal
+	 * performance. A value of zero means no special recommendation.
+	 */
+	u32 buffer_num_recommended;
+
+	u32 es_ptr;
+	struct mmal_es_format format;
+	union mmal_es_specific_format es;
+	u8 extradata[MMAL_FORMAT_EXTRADATA_MAX_SIZE];
+};
+
 struct mmal_msg_event_to_host {
 	u32 client_component;	/* component context */
 
diff --git a/drivers/staging/vc04_services/vchiq-mmal/mmal-parameters.h b/drivers/staging/vc04_services/vchiq-mmal/mmal-parameters.h
index a0cdd28101f2..825daadf2fea 100644
--- a/drivers/staging/vc04_services/vchiq-mmal/mmal-parameters.h
+++ b/drivers/staging/vc04_services/vchiq-mmal/mmal-parameters.h
@@ -223,6 +223,66 @@ enum mmal_parameter_camera_type {
 	MMAL_PARAMETER_SHUTTER_SPEED,
 		/**< Takes a @ref MMAL_PARAMETER_AWB_GAINS_T */
 	MMAL_PARAMETER_CUSTOM_AWB_GAINS,
+		/**< Takes a @ref MMAL_PARAMETER_CAMERA_SETTINGS_T */
+	MMAL_PARAMETER_CAMERA_SETTINGS,
+		/**< Takes a @ref MMAL_PARAMETER_PRIVACY_INDICATOR_T */
+	MMAL_PARAMETER_PRIVACY_INDICATOR,
+		/**< Takes a @ref MMAL_PARAMETER_BOOLEAN_T */
+	MMAL_PARAMETER_VIDEO_DENOISE,
+		/**< Takes a @ref MMAL_PARAMETER_BOOLEAN_T */
+	MMAL_PARAMETER_STILLS_DENOISE,
+		/**< Takes a @ref MMAL_PARAMETER_CAMERA_ANNOTATE_T */
+	MMAL_PARAMETER_ANNOTATE,
+		/**< Takes a @ref MMAL_PARAMETER_STEREOSCOPIC_MODE_T */
+	MMAL_PARAMETER_STEREOSCOPIC_MODE,
+		/**< Takes a @ref MMAL_PARAMETER_CAMERA_INTERFACE_T */
+	MMAL_PARAMETER_CAMERA_INTERFACE,
+		/**< Takes a @ref MMAL_PARAMETER_CAMERA_CLOCKING_MODE_T */
+	MMAL_PARAMETER_CAMERA_CLOCKING_MODE,
+		/**< Takes a @ref MMAL_PARAMETER_CAMERA_RX_CONFIG_T */
+	MMAL_PARAMETER_CAMERA_RX_CONFIG,
+		/**< Takes a @ref MMAL_PARAMETER_CAMERA_RX_TIMING_T */
+	MMAL_PARAMETER_CAMERA_RX_TIMING,
+		/**< Takes a @ref MMAL_PARAMETER_UINT32_T */
+	MMAL_PARAMETER_DPF_CONFIG,
+
+	/* 0x50 */
+		/**< Takes a @ref MMAL_PARAMETER_UINT32_T */
+	MMAL_PARAMETER_JPEG_RESTART_INTERVAL,
+		/**< Takes a @ref MMAL_PARAMETER_UINT32_T */
+	MMAL_PARAMETER_CAMERA_ISP_BLOCK_OVERRIDE,
+		/**< Takes a @ref MMAL_PARAMETER_LENS_SHADING_T */
+	MMAL_PARAMETER_LENS_SHADING_OVERRIDE,
+		/**< Takes a @ref MMAL_PARAMETER_UINT32_T */
+	MMAL_PARAMETER_BLACK_LEVEL,
+		/**< Takes a @ref MMAL_PARAMETER_RESIZE_T */
+	MMAL_PARAMETER_RESIZE_PARAMS,
+		/**< Takes a @ref MMAL_PARAMETER_CROP_T */
+	MMAL_PARAMETER_CROP,
+		/**< Takes a @ref MMAL_PARAMETER_INT32_T */
+	MMAL_PARAMETER_OUTPUT_SHIFT,
+		/**< Takes a @ref MMAL_PARAMETER_INT32_T */
+	MMAL_PARAMETER_CCM_SHIFT,
+		/**< Takes a @ref MMAL_PARAMETER_CUSTOM_CCM_T */
+	MMAL_PARAMETER_CUSTOM_CCM,
+		/**< Takes a @ref MMAL_PARAMETER_RATIONAL_T */
+	MMAL_PARAMETER_ANALOG_GAIN,
+		/**< Takes a @ref MMAL_PARAMETER_RATIONAL_T */
+	MMAL_PARAMETER_DIGITAL_GAIN,
+		/**< Takes a @ref MMAL_PARAMETER_DENOISE_T */
+	MMAL_PARAMETER_DENOISE,
+		/**< Takes a @ref MMAL_PARAMETER_SHARPEN_T */
+	MMAL_PARAMETER_SHARPEN,
+		/**< Takes a @ref MMAL_PARAMETER_GEQ_T */
+	MMAL_PARAMETER_GEQ,
+		/**< Tales a @ref MMAP_PARAMETER_DPC_T */
+	MMAL_PARAMETER_DPC,
+		/**< Tales a @ref MMAP_PARAMETER_GAMMA_T */
+	MMAL_PARAMETER_GAMMA,
+		/**< Takes a @ref MMAL_PARAMETER_CDN_T */
+	MMAL_PARAMETER_CDN,
+		/**< Takes a @ref MMAL_PARAMETER_BOOLEAN_T */
+	MMAL_PARAMETER_JPEG_IJG_SCALING,
 };
 
 enum mmal_parameter_camera_config_timestamp_mode {
@@ -310,6 +370,7 @@ enum mmal_parameter_awbmode {
 	MMAL_PARAM_AWBMODE_INCANDESCENT,
 	MMAL_PARAM_AWBMODE_FLASH,
 	MMAL_PARAM_AWBMODE_HORIZON,
+	MMAL_PARAM_AWBMODE_GREYWORLD,
 };
 
 enum mmal_parameter_imagefx {
@@ -336,6 +397,9 @@ enum mmal_parameter_imagefx {
 	MMAL_PARAM_IMAGEFX_COLOURPOINT,
 	MMAL_PARAM_IMAGEFX_COLOURBALANCE,
 	MMAL_PARAM_IMAGEFX_CARTOON,
+	MMAL_PARAM_IMAGEFX_DEINTERLACE_DOUBLE,
+	MMAL_PARAM_IMAGEFX_DEINTERLACE_ADV,
+	MMAL_PARAM_IMAGEFX_DEINTERLACE_FAST,
 };
 
 enum MMAL_PARAM_FLICKERAVOID {
@@ -577,7 +641,49 @@ enum mmal_parameter_video_type {
 	MMAL_PARAMETER_VIDEO_ENCODE_H264_LOW_DELAY_HRD_FLAG,
 
 	/**< @ref MMAL_PARAMETER_BOOLEAN_T */
-	MMAL_PARAMETER_VIDEO_ENCODE_INLINE_HEADER
+	MMAL_PARAMETER_VIDEO_ENCODE_INLINE_HEADER,
+
+	/**< Take a @ref MMAL_PARAMETER_BOOLEAN_T. */
+	MMAL_PARAMETER_VIDEO_ENCODE_SEI_ENABLE,
+
+	/**< Take a @ref MMAL_PARAMETER_BOOLEAN_T. */
+	MMAL_PARAMETER_VIDEO_ENCODE_INLINE_VECTORS,
+
+	/**< Take a @ref MMAL_PARAMETER_VIDEO_RENDER_STATS_T. */
+	MMAL_PARAMETER_VIDEO_RENDER_STATS,
+
+	/**< Take a @ref MMAL_PARAMETER_VIDEO_INTERLACE_TYPE_T. */
+	MMAL_PARAMETER_VIDEO_INTERLACE_TYPE,
+
+	/**< Takes a @ref MMAL_PARAMETER_BOOLEAN_T */
+	MMAL_PARAMETER_VIDEO_INTERPOLATE_TIMESTAMPS,
+
+	/**< Takes a @ref MMAL_PARAMETER_BOOLEAN_T */
+	MMAL_PARAMETER_VIDEO_ENCODE_SPS_TIMING,
+
+	/**< Takes a @ref MMAL_PARAMETER_UINT32_T */
+	MMAL_PARAMETER_VIDEO_MAX_NUM_CALLBACKS,
+
+	/**< Takes a @ref MMAL_PARAMETER_SOURCE_PATTERN_T */
+	MMAL_PARAMETER_VIDEO_SOURCE_PATTERN,
+
+	/**< Takes a @ref MMAL_PARAMETER_BOOLEAN_T */
+	MMAL_PARAMETER_VIDEO_ENCODE_SEPARATE_NAL_BUFS,
+
+	/**< Takes a @ref MMAL_PARAMETER_UINT32_T */
+	MMAL_PARAMETER_VIDEO_DROPPABLE_PFRAME_LENGTH,
+
+	/**< Take a @ref MMAL_PARAMETER_VIDEO_STALL_T */
+	MMAL_PARAMETER_VIDEO_STALL_THRESHOLD,
+
+	/**< Take a @ref MMAL_PARAMETER_BOOLEAN_T */
+	MMAL_PARAMETER_VIDEO_ENCODE_HEADERS_WITH_FRAME,
+
+	/**< Take a @ref MMAL_PARAMETER_BOOLEAN_T */
+	MMAL_PARAMETER_VIDEO_VALIDATE_TIMESTAMPS,
+
+	/**< Takes a @ref MMAL_PARAMETER_BOOLEAN_T */
+	MMAL_PARAMETER_VIDEO_STOP_ON_PAR_COLOUR_CHANGE,
 };
 
 /** Valid mirror modes */
@@ -708,6 +814,43 @@ struct mmal_parameter_displayregion {
 	u32 alpha;
 };
 
+enum mmal_interlace_type {
+	/* The data is not interlaced, it is progressive scan */
+	MMAL_INTERLACE_PROGRESSIVE,
+	/*
+	 * The data is interlaced, fields sent separately in temporal order, with
+	 * upper field first
+	 */
+	MMAL_INTERLACE_FIELD_SINGLE_UPPER_FIRST,
+	/*
+	 * The data is interlaced, fields sent separately in temporal order, with
+	 * lower field first
+	 */
+	MMAL_INTERLACE_FIELD_SINGLE_LOWER_FIRST,
+	/*
+	 * The data is interlaced, two fields sent together line interleaved,
+	 * with the upper field temporally earlier
+	 */
+	MMAL_INTERLACE_FIELDS_INTERLEAVED_UPPER_FIRST,
+	/*
+	 * The data is interlaced, two fields sent together line interleaved,
+	 * with the lower field temporally earlier
+	 */
+	MMAL_INTERLACE_FIELDS_INTERLEAVED_LOWER_FIRST,
+	/*
+	 * The stream may contain a mixture of progressive and interlaced
+	 * frames
+	 */
+	MMAL_INTERLACE_MIXED,
+
+	MMAL_INTERLACE_DUMMY = 0x7FFFFFFF
+};
+
+struct mmal_parameter_video_interlace_type {
+	enum mmal_interlace_type mode;	/* The interlace type of the content */
+	u32 bRepeatFirstField;		/* Whether to repeat the first field */
+};
+
 #define MMAL_MAX_IMAGEFX_PARAMETERS 5
 
 struct mmal_parameter_imagefx_parameters {
@@ -746,7 +889,113 @@ struct mmal_parameter_camera_info {
 	struct mmal_parameter_camera_info_camera
 		cameras[MMAL_PARAMETER_CAMERA_INFO_MAX_CAMERAS];
 	struct mmal_parameter_camera_info_flash
-				flashes[MMAL_PARAMETER_CAMERA_INFO_MAX_FLASHES];
+		flashes[MMAL_PARAMETER_CAMERA_INFO_MAX_FLASHES];
+};
+
+struct mmal_parameter_ccm {
+	struct s32_fract ccm[3][3];
+	s32 offsets[3];
+};
+
+struct mmal_parameter_custom_ccm {
+	u32 enabled; /**< Enable the custom CCM. */
+	struct mmal_parameter_ccm ccm; /**< CCM to be used. */
+};
+
+struct mmal_parameter_lens_shading {
+	u32 enabled;
+	u32 grid_cell_size;
+	u32 grid_width;
+	u32 grid_stride;
+	u32 grid_height;
+	u32 mem_handle_table;
+	u32 ref_transform;
+};
+
+enum mmal_parameter_ls_gain_format_type {
+	MMAL_PARAMETER_LS_GAIN_FORMAT_TYPE_U0P8_1 = 0,
+	MMAL_PARAMETER_LS_GAIN_FORMAT_TYPE_U1P7_0 = 1,
+	MMAL_PARAMETER_LS_GAIN_FORMAT_TYPE_U1P7_1 = 2,
+	MMAL_PARAMETER_LS_GAIN_FORMAT_TYPE_U2P6_0 = 3,
+	MMAL_PARAMETER_LS_GAIN_FORMAT_TYPE_U2P6_1 = 4,
+	MMAL_PARAMETER_LS_GAIN_FORMAT_TYPE_U3P5_0 = 5,
+	MMAL_PARAMETER_LS_GAIN_FORMAT_TYPE_U3P5_1 = 6,
+	MMAL_PARAMETER_LS_GAIN_FORMAT_TYPE_U4P10  = 7,
+	MMAL_PARAMETER_LS_GAIN_FORMAT_TYPE_DUMMY  = 0x7FFFFFFF
+};
+
+struct mmal_parameter_lens_shading_v2 {
+	u32 enabled;
+	u32 grid_cell_size;
+	u32 grid_width;
+	u32 grid_stride;
+	u32 grid_height;
+	u32 mem_handle_table;
+	u32 ref_transform;
+	u32 corner_sampled;
+	enum mmal_parameter_ls_gain_format_type gain_format;
+};
+
+struct mmal_parameter_black_level {
+	u32 enabled;
+	u16 black_level_r;
+	u16 black_level_g;
+	u16 black_level_b;
+	u8 pad_[2]; /* Unused */
+};
+
+struct mmal_parameter_geq {
+	u32 enabled;
+	u32 offset;
+	struct s32_fract slope;
+};
+
+#define MMAL_NUM_GAMMA_PTS 33
+struct mmal_parameter_gamma {
+	u32 enabled;
+	u16 x[MMAL_NUM_GAMMA_PTS];
+	u16 y[MMAL_NUM_GAMMA_PTS];
+};
+
+enum mmal_parameter_cdn_mode {
+	MMAL_PARAM_CDN_FAST = 0,
+	MMAL_PARAM_CDN_HIGH_QUALITY = 1,
+	MMAL_PARAM_CDN_DUMMY  = 0x7FFFFFFF
+};
+
+struct mmal_parameter_colour_denoise {
+	u32 enabled;
+	enum mmal_parameter_cdn_mode mode;
+};
+
+struct mmal_parameter_denoise {
+	u32 enabled;
+	u32 constant;
+	struct s32_fract slope;
+	struct s32_fract strength;
+};
+
+struct mmal_parameter_sharpen {
+	u32 enabled;
+	struct s32_fract threshold;
+	struct s32_fract strength;
+	struct s32_fract limit;
+};
+
+enum mmal_dpc_mode {
+	MMAL_DPC_MODE_OFF = 0,
+	MMAL_DPC_MODE_NORMAL = 1,
+	MMAL_DPC_MODE_STRONG = 2,
+	MMAL_DPC_MODE_MAX = 0x7FFFFFFF,
+};
+
+struct mmal_parameter_dpc {
+	u32 enabled;
+	u32 strength;
+};
+
+struct mmal_parameter_crop {
+	struct vchiq_mmal_rect rect;
 };
 
 #endif
diff --git a/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c b/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c
index 67489c334f7b..8a5f0d32ed2d 100644
--- a/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c
+++ b/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c
@@ -28,9 +28,22 @@
 #include "../include/linux/raspberrypi/vchiq.h"
 #include "../interface/vchiq_arm/vchiq_arm.h"
 #include "mmal-common.h"
+#include "mmal-parameters.h"
 #include "mmal-vchiq.h"
 #include "mmal-msg.h"
 
+#include "../vc-sm-cma/vc_sm_knl.h"
+
+#define pr_dbg_lvl(__level, __debug, __fmt, __arg...)		\
+	do {							\
+		if (__debug >= (__level))			\
+			printk(KERN_DEBUG __fmt, ##__arg);	\
+	} while (0)
+
+static unsigned int debug;
+module_param(debug, uint, 0644);
+MODULE_PARM_DESC(debug, "activates debug info (0-3)");
+
 /*
  * maximum number of components supported.
  * This matches the maximum permitted by default on the VPU
@@ -144,6 +157,8 @@ struct mmal_msg_context {
 			/* Presentation and Decode timestamps */
 			s64 pts;
 			s64 dts;
+			/* MMAL buffer command flag */
+			u32 cmd;
 
 			int status;	/* context status */
 
@@ -231,18 +246,6 @@ release_msg_context(struct mmal_msg_context *msg_context)
 	kfree(msg_context);
 }
 
-/* deals with receipt of event to host message */
-static void event_to_host_cb(struct vchiq_mmal_instance *instance,
-			     struct mmal_msg *msg, u32 msg_len)
-{
-	pr_debug("unhandled event\n");
-	pr_debug("component:%u port type:%d num:%d cmd:0x%x length:%d\n",
-		 msg->u.event_to_host.client_component,
-		 msg->u.event_to_host.port_type,
-		 msg->u.event_to_host.port_num,
-		 msg->u.event_to_host.cmd, msg->u.event_to_host.length);
-}
-
 /* workqueue scheduled callback
  *
  * we do this because it is important we do not call any other vchiq
@@ -264,13 +267,18 @@ static void buffer_work_cb(struct work_struct *work)
 	buffer->mmal_flags = msg_context->u.bulk.mmal_flags;
 	buffer->dts = msg_context->u.bulk.dts;
 	buffer->pts = msg_context->u.bulk.pts;
+	buffer->cmd = msg_context->u.bulk.cmd;
 
-	atomic_dec(&msg_context->u.bulk.port->buffers_with_vpu);
+	if (!buffer->cmd)
+		atomic_dec(&msg_context->u.bulk.port->buffers_with_vpu);
 
 	msg_context->u.bulk.port->buffer_cb(msg_context->u.bulk.instance,
 					    msg_context->u.bulk.port,
 					    msg_context->u.bulk.status,
 					    msg_context->u.bulk.buffer);
+
+	if (buffer->cmd)
+		mutex_unlock(&msg_context->u.bulk.port->event_context_mutex);
 }
 
 /* workqueue scheduled callback to handle receiving buffers
@@ -348,6 +356,7 @@ static int bulk_receive(struct vchiq_mmal_instance *instance,
 	msg_context->u.bulk.buffer_used = rd_len;
 	msg_context->u.bulk.dts = msg->u.buffer_from_host.buffer_header.dts;
 	msg_context->u.bulk.pts = msg->u.buffer_from_host.buffer_header.pts;
+	msg_context->u.bulk.cmd = msg->u.buffer_from_host.buffer_header.cmd;
 
 	queue_work(msg_context->instance->bulk_wq,
 		   &msg_context->u.bulk.buffer_to_host_work);
@@ -382,7 +391,8 @@ buffer_from_host(struct vchiq_mmal_instance *instance,
 	if (!port->enabled)
 		return -EINVAL;
 
-	pr_debug("instance:%u buffer:%p\n", instance->service_handle, buf);
+	pr_dbg_lvl(3, debug, "instance:%u buffer:%p\n",
+		   instance->service_handle, buf);
 
 	/* get context */
 	if (!buf->msg_context) {
@@ -421,14 +431,27 @@ buffer_from_host(struct vchiq_mmal_instance *instance,
 
 	/* buffer header */
 	m.u.buffer_from_host.buffer_header.cmd = 0;
-	m.u.buffer_from_host.buffer_header.data =
-		(u32)(unsigned long)buf->buffer;
+	if (port->zero_copy) {
+		m.u.buffer_from_host.buffer_header.data = buf->vc_handle;
+	} else {
+		m.u.buffer_from_host.buffer_header.data =
+			(u32)(unsigned long)buf->buffer;
+	}
+
 	m.u.buffer_from_host.buffer_header.alloc_size = buf->buffer_size;
-	m.u.buffer_from_host.buffer_header.length = 0;	/* nothing used yet */
-	m.u.buffer_from_host.buffer_header.offset = 0;	/* no offset */
-	m.u.buffer_from_host.buffer_header.flags = 0;	/* no flags */
-	m.u.buffer_from_host.buffer_header.pts = MMAL_TIME_UNKNOWN;
-	m.u.buffer_from_host.buffer_header.dts = MMAL_TIME_UNKNOWN;
+	if (port->type == MMAL_PORT_TYPE_OUTPUT) {
+		m.u.buffer_from_host.buffer_header.length = 0;
+		m.u.buffer_from_host.buffer_header.offset = 0;
+		m.u.buffer_from_host.buffer_header.flags = 0;
+		m.u.buffer_from_host.buffer_header.pts = MMAL_TIME_UNKNOWN;
+		m.u.buffer_from_host.buffer_header.dts = MMAL_TIME_UNKNOWN;
+	} else {
+		m.u.buffer_from_host.buffer_header.length = buf->length;
+		m.u.buffer_from_host.buffer_header.offset = 0;
+		m.u.buffer_from_host.buffer_header.flags = buf->mmal_flags;
+		m.u.buffer_from_host.buffer_header.pts = buf->pts;
+		m.u.buffer_from_host.buffer_header.dts = buf->dts;
+	}
 
 	/* clear buffer type specific data */
 	memset(&m.u.buffer_from_host.buffer_header_type_specific, 0,
@@ -450,6 +473,103 @@ buffer_from_host(struct vchiq_mmal_instance *instance,
 	return ret;
 }
 
+/* deals with receipt of event to host message */
+static void event_to_host_cb(struct vchiq_mmal_instance *instance,
+			     struct mmal_msg *msg, u32 msg_len)
+{
+	int comp_idx = msg->u.event_to_host.client_component;
+	struct vchiq_mmal_component *component =
+					&instance->component[comp_idx];
+	struct vchiq_mmal_port *port = NULL;
+	struct mmal_msg_context *msg_context;
+	u32 port_num = msg->u.event_to_host.port_num;
+
+	if (msg->u.buffer_from_host.drvbuf.magic == MMAL_MAGIC) {
+		pr_err("%s: MMAL_MSG_TYPE_BUFFER_TO_HOST with bad magic\n",
+		       __func__);
+		return;
+	}
+
+	switch (msg->u.event_to_host.port_type) {
+	case MMAL_PORT_TYPE_CONTROL:
+		if (port_num) {
+			pr_err("%s: port_num of %u >= number of ports 1",
+			       __func__, port_num);
+			return;
+		}
+		port = &component->control;
+		break;
+	case MMAL_PORT_TYPE_INPUT:
+		if (port_num >= component->inputs) {
+			pr_err("%s: port_num of %u >= number of ports %u",
+			       __func__, port_num,
+			       port_num >= component->inputs);
+			return;
+		}
+		port = &component->input[port_num];
+		break;
+	case MMAL_PORT_TYPE_OUTPUT:
+		if (port_num >= component->outputs) {
+			pr_err("%s: port_num of %u >= number of ports %u",
+			       __func__, port_num,
+			       port_num >= component->outputs);
+			return;
+		}
+		port = &component->output[port_num];
+		break;
+	case MMAL_PORT_TYPE_CLOCK:
+		if (port_num >= component->clocks) {
+			pr_err("%s: port_num of %u >= number of ports %u",
+			       __func__, port_num,
+			       port_num >= component->clocks);
+			return;
+		}
+		port = &component->clock[port_num];
+		break;
+	default:
+		break;
+	}
+
+	if (!mutex_trylock(&port->event_context_mutex)) {
+		pr_err("dropping event 0x%x\n", msg->u.event_to_host.cmd);
+		return;
+	}
+	msg_context = port->event_context;
+
+	if (msg->h.status != MMAL_MSG_STATUS_SUCCESS) {
+		/* message reception had an error */
+		//pr_warn
+		pr_err("%s: error %d in reply\n", __func__, msg->h.status);
+
+		msg_context->u.bulk.status = msg->h.status;
+	} else if (msg->u.event_to_host.length > MMAL_WORKER_EVENT_SPACE) {
+		/* data is not in message, queue a bulk receive */
+		pr_err("%s: payload not in message - bulk receive??! NOT SUPPORTED\n",
+		       __func__);
+		msg_context->u.bulk.status = -1;
+	} else {
+		memcpy(msg_context->u.bulk.buffer->buffer,
+		       msg->u.event_to_host.data,
+		       msg->u.event_to_host.length);
+
+		msg_context->u.bulk.buffer_used =
+		    msg->u.event_to_host.length;
+
+		msg_context->u.bulk.mmal_flags = 0;
+		msg_context->u.bulk.dts = MMAL_TIME_UNKNOWN;
+		msg_context->u.bulk.pts = MMAL_TIME_UNKNOWN;
+		msg_context->u.bulk.cmd = msg->u.event_to_host.cmd;
+
+		pr_dbg_lvl(3, debug, "event component:%u port type:%d num:%d cmd:0x%x length:%d\n",
+			   msg->u.event_to_host.client_component,
+			   msg->u.event_to_host.port_type,
+			   msg->u.event_to_host.port_num,
+			   msg->u.event_to_host.cmd, msg->u.event_to_host.length);
+	}
+
+	schedule_work(&msg_context->u.bulk.work);
+}
+
 /* deals with receipt of buffer to host message */
 static void buffer_to_host_cb(struct vchiq_mmal_instance *instance,
 			      struct mmal_msg *msg, u32 msg_len)
@@ -457,8 +577,8 @@ static void buffer_to_host_cb(struct vchiq_mmal_instance *instance,
 	struct mmal_msg_context *msg_context;
 	u32 handle;
 
-	pr_debug("%s: instance:%p msg:%p msg_len:%d\n",
-		 __func__, instance, msg, msg_len);
+	pr_dbg_lvl(3, debug, "%s: instance:%p msg:%p msg_len:%d\n",
+		   __func__, instance, msg, msg_len);
 
 	if (msg->u.buffer_from_host.drvbuf.magic == MMAL_MAGIC) {
 		handle = msg->u.buffer_from_host.drvbuf.client_context;
@@ -483,6 +603,22 @@ static void buffer_to_host_cb(struct vchiq_mmal_instance *instance,
 
 		msg_context->u.bulk.status = msg->h.status;
 
+	} else if (msg->u.buffer_from_host.is_zero_copy) {
+		/*
+		 * Zero copy buffer, so nothing to do.
+		 * Copy buffer info and make callback.
+		 */
+		msg_context->u.bulk.buffer_used =
+				msg->u.buffer_from_host.buffer_header.length;
+		msg_context->u.bulk.mmal_flags =
+				msg->u.buffer_from_host.buffer_header.flags;
+		msg_context->u.bulk.dts =
+				msg->u.buffer_from_host.buffer_header.dts;
+		msg_context->u.bulk.pts =
+				msg->u.buffer_from_host.buffer_header.pts;
+		msg_context->u.bulk.cmd =
+				msg->u.buffer_from_host.buffer_header.cmd;
+
 	} else if (msg->u.buffer_from_host.buffer_header.length == 0) {
 		/* empty buffer */
 		if (msg->u.buffer_from_host.buffer_header.flags &
@@ -712,39 +848,42 @@ static int send_synchronous_mmal_msg(struct vchiq_mmal_instance *instance,
 
 static void dump_port_info(struct vchiq_mmal_port *port)
 {
-	pr_debug("port handle:0x%x enabled:%d\n", port->handle, port->enabled);
+	pr_dbg_lvl(3, debug, "port handle:0x%x enabled:%d\n", port->handle,
+		   port->enabled);
 
-	pr_debug("buffer minimum num:%d size:%d align:%d\n",
-		 port->minimum_buffer.num,
-		 port->minimum_buffer.size, port->minimum_buffer.alignment);
+	pr_dbg_lvl(3, debug, "buffer minimum num:%d size:%d align:%d\n",
+		   port->minimum_buffer.num,
+		   port->minimum_buffer.size, port->minimum_buffer.alignment);
 
-	pr_debug("buffer recommended num:%d size:%d align:%d\n",
-		 port->recommended_buffer.num,
-		 port->recommended_buffer.size,
-		 port->recommended_buffer.alignment);
+	pr_dbg_lvl(3, debug, "buffer recommended num:%d size:%d align:%d\n",
+		   port->recommended_buffer.num,
+		   port->recommended_buffer.size,
+		   port->recommended_buffer.alignment);
 
-	pr_debug("buffer current values num:%d size:%d align:%d\n",
-		 port->current_buffer.num,
-		 port->current_buffer.size, port->current_buffer.alignment);
+	pr_dbg_lvl(3, debug, "buffer current values num:%d size:%d align:%d\n",
+		   port->current_buffer.num,
+		   port->current_buffer.size, port->current_buffer.alignment);
 
-	pr_debug("elementary stream: type:%d encoding:0x%x variant:0x%x\n",
-		 port->format.type,
-		 port->format.encoding, port->format.encoding_variant);
+	pr_dbg_lvl(3, debug, "elementary stream: type:%d encoding:0x%x variant:0x%x\n",
+		   port->format.type,
+		   port->format.encoding, port->format.encoding_variant);
 
-	pr_debug("		    bitrate:%d flags:0x%x\n",
-		 port->format.bitrate, port->format.flags);
+	pr_dbg_lvl(3, debug, "		    bitrate:%d flags:0x%x\n",
+		   port->format.bitrate, port->format.flags);
 
 	if (port->format.type == MMAL_ES_TYPE_VIDEO) {
-		pr_debug
-		    ("es video format: width:%d height:%d colourspace:0x%x\n",
+		pr_dbg_lvl(3, debug,
+		    "es video format: width:%d height:%d colourspace:0x%x\n",
 		     port->es.video.width, port->es.video.height,
 		     port->es.video.color_space);
 
-		pr_debug("		 : crop xywh %d,%d,%d,%d\n",
+		pr_dbg_lvl(3, debug,
+			 "		 : crop xywh %d,%d,%d,%d\n",
 			 port->es.video.crop.x,
 			 port->es.video.crop.y,
 			 port->es.video.crop.width, port->es.video.crop.height);
-		pr_debug("		 : framerate %d/%d  aspect %d/%d\n",
+		pr_dbg_lvl(3, debug,
+			 "		 : framerate %d/%d  aspect %d/%d\n",
 			 port->es.video.frame_rate.numerator,
 			 port->es.video.frame_rate.denominator,
 			 port->es.video.par.numerator, port->es.video.par.denominator);
@@ -778,7 +917,7 @@ static int port_info_set(struct vchiq_mmal_instance *instance,
 	struct mmal_msg *rmsg;
 	struct vchiq_header *rmsg_handle;
 
-	pr_debug("setting port info port %p\n", port);
+	pr_dbg_lvl(1, debug, "setting port info port %p\n", port);
 	if (!port)
 		return -1;
 	dump_port_info(port);
@@ -821,8 +960,8 @@ static int port_info_set(struct vchiq_mmal_instance *instance,
 	/* return operation status */
 	ret = -rmsg->u.port_info_get_reply.status;
 
-	pr_debug("%s:result:%d component:0x%x port:%d\n", __func__, ret,
-		 port->component->handle, port->handle);
+	pr_dbg_lvl(1, debug, "%s:result:%d component:0x%x port:%d\n", __func__,
+		   ret, port->component->handle, port->handle);
 
 release_msg:
 	vchiq_release_message(instance->vchiq_instance, instance->service_handle, rmsg_handle);
@@ -912,13 +1051,13 @@ static int port_info_get(struct vchiq_mmal_instance *instance,
 	       rmsg->u.port_info_get_reply.extradata,
 	       port->format.extradata_size);
 
-	pr_debug("received port info\n");
+	pr_dbg_lvl(1, debug, "received port info\n");
 	dump_port_info(port);
 
 release_msg:
 
-	pr_debug("%s:result:%d component:0x%x port:%d\n",
-		 __func__, ret, port->component->handle, port->handle);
+	pr_dbg_lvl(1, debug, "%s:result:%d component:0x%x port:%d\n",
+		   __func__, ret, port->component->handle, port->handle);
 
 	vchiq_release_message(instance->vchiq_instance, instance->service_handle, rmsg_handle);
 
@@ -964,9 +1103,9 @@ static int create_component(struct vchiq_mmal_instance *instance,
 	component->outputs = rmsg->u.component_create_reply.output_num;
 	component->clocks = rmsg->u.component_create_reply.clock_num;
 
-	pr_debug("Component handle:0x%x in:%d out:%d clock:%d\n",
-		 component->handle,
-		 component->inputs, component->outputs, component->clocks);
+	pr_dbg_lvl(2, debug, "Component handle:0x%x in:%d out:%d clock:%d\n",
+		   component->handle,
+		   component->inputs, component->outputs, component->clocks);
 
 release_msg:
 	vchiq_release_message(instance->vchiq_instance, instance->service_handle, rmsg_handle);
@@ -1135,10 +1274,9 @@ static int port_action_port(struct vchiq_mmal_instance *instance,
 
 	ret = -rmsg->u.port_action_reply.status;
 
-	pr_debug("%s:result:%d component:0x%x port:%d action:%s(%d)\n",
-		 __func__,
-		 ret, port->component->handle, port->handle,
-		 port_action_type_names[action_type], action_type);
+	pr_dbg_lvl(2, debug, "%s:result:%d component:0x%x port:%d action:%s(%d)\n",
+		   __func__, ret, port->component->handle, port->handle,
+		   port_action_type_names[action_type], action_type);
 
 release_msg:
 	vchiq_release_message(instance->vchiq_instance, instance->service_handle, rmsg_handle);
@@ -1182,11 +1320,11 @@ static int port_action_handle(struct vchiq_mmal_instance *instance,
 
 	ret = -rmsg->u.port_action_reply.status;
 
-	pr_debug("%s:result:%d component:0x%x port:%d action:%s(%d) connect component:0x%x connect port:%d\n",
-		 __func__,
-		 ret, port->component->handle, port->handle,
-		 port_action_type_names[action_type],
-		 action_type, connect_component_handle, connect_port_handle);
+	pr_dbg_lvl(2, debug,
+		   "%s:result:%d component:0x%x port:%d action:%s(%d) connect component:0x%x connect port:%d\n",
+		   __func__, ret, port->component->handle, port->handle,
+		   port_action_type_names[action_type],
+		   action_type, connect_component_handle, connect_port_handle);
 
 release_msg:
 	vchiq_release_message(instance->vchiq_instance, instance->service_handle, rmsg_handle);
@@ -1225,9 +1363,9 @@ static int port_parameter_set(struct vchiq_mmal_instance *instance,
 
 	ret = -rmsg->u.port_parameter_set_reply.status;
 
-	pr_debug("%s:result:%d component:0x%x port:%d parameter:%d\n",
-		 __func__,
-		 ret, port->component->handle, port->handle, parameter_id);
+	pr_dbg_lvl(1, debug, "%s:result:%d component:0x%x port:%d parameter:%d\n",
+		   __func__, ret, port->component->handle, port->handle,
+		   parameter_id);
 
 release_msg:
 	vchiq_release_message(instance->vchiq_instance, instance->service_handle, rmsg_handle);
@@ -1285,8 +1423,9 @@ static int port_parameter_get(struct vchiq_mmal_instance *instance,
 	/* Always report the size of the returned parameter to the caller */
 	*value_size = rmsg->u.port_parameter_get_reply.size;
 
-	pr_debug("%s:result:%d component:0x%x port:%d parameter:%d\n", __func__,
-		 ret, port->component->handle, port->handle, parameter_id);
+	pr_dbg_lvl(1, debug, "%s:result:%d component:0x%x port:%d parameter:%d\n",
+		   __func__, ret, port->component->handle, port->handle,
+		   parameter_id);
 
 release_msg:
 	vchiq_release_message(instance->vchiq_instance, instance->service_handle, rmsg_handle);
@@ -1331,6 +1470,7 @@ static int port_disable(struct vchiq_mmal_instance *instance,
 				mmalbuf->mmal_flags = 0;
 				mmalbuf->dts = MMAL_TIME_UNKNOWN;
 				mmalbuf->pts = MMAL_TIME_UNKNOWN;
+				mmalbuf->cmd = 0;
 				port->buffer_cb(instance,
 						port, 0, mmalbuf);
 			}
@@ -1362,6 +1502,8 @@ static int port_enable(struct vchiq_mmal_instance *instance,
 
 	port->enabled = true;
 
+	atomic_set(&port->buffers_with_vpu, 0);
+
 	if (port->buffer_cb) {
 		/* send buffer headers to videocore */
 		hdr_count = 1;
@@ -1427,6 +1569,9 @@ int vchiq_mmal_port_parameter_set(struct vchiq_mmal_instance *instance,
 
 	mutex_unlock(&instance->vchiq_mutex);
 
+	if (parameter == MMAL_PARAMETER_ZERO_COPY && !ret)
+		port->zero_copy = !!(*(bool *)value);
+
 	return ret;
 }
 EXPORT_SYMBOL_GPL(vchiq_mmal_port_parameter_set);
@@ -1539,7 +1684,7 @@ int vchiq_mmal_port_connect_tunnel(struct vchiq_mmal_instance *instance,
 	if (!dst) {
 		/* do not make new connection */
 		ret = 0;
-		pr_debug("not making new connection\n");
+		pr_dbg_lvl(3, debug, "not making new connection\n");
 		goto release_unlock;
 	}
 
@@ -1557,14 +1702,14 @@ int vchiq_mmal_port_connect_tunnel(struct vchiq_mmal_instance *instance,
 	/* set new format */
 	ret = port_info_set(instance, dst);
 	if (ret) {
-		pr_debug("setting port info failed\n");
+		pr_dbg_lvl(1, debug, "setting port info failed\n");
 		goto release_unlock;
 	}
 
 	/* read what has actually been set */
 	ret = port_info_get(instance, dst);
 	if (ret) {
-		pr_debug("read back port info failed\n");
+		pr_dbg_lvl(1, debug, "read back port info failed\n");
 		goto release_unlock;
 	}
 
@@ -1573,9 +1718,9 @@ int vchiq_mmal_port_connect_tunnel(struct vchiq_mmal_instance *instance,
 				 MMAL_MSG_PORT_ACTION_TYPE_CONNECT,
 				 dst->component->handle, dst->handle);
 	if (ret < 0) {
-		pr_debug("connecting port %d:%d to %d:%d failed\n",
-			 src->component->handle, src->handle,
-			 dst->component->handle, dst->handle);
+		pr_dbg_lvl(2, debug, "connecting port %d:%d to %d:%d failed\n",
+			   src->component->handle, src->handle,
+			   dst->component->handle, dst->handle);
 		goto release_unlock;
 	}
 	src->connected = dst;
@@ -1595,6 +1740,32 @@ int vchiq_mmal_submit_buffer(struct vchiq_mmal_instance *instance,
 	unsigned long flags = 0;
 	int ret;
 
+	/*
+	 * We really want to do this in mmal_vchi_buffer_init but can't as
+	 * videobuf2 won't let us have the dmabuf there.
+	 */
+	if (port->zero_copy && buffer->dma_buf && !buffer->vcsm_handle) {
+		pr_dbg_lvl(2, debug, "%s: import dmabuf %p\n",
+			   __func__, buffer->dma_buf);
+		ret = vc_sm_cma_import_dmabuf(buffer->dma_buf,
+					      &buffer->vcsm_handle);
+		if (ret) {
+			pr_err("%s: vc_sm_import_dmabuf_fd failed, ret %d\n",
+			       __func__, ret);
+			return ret;
+		}
+
+		buffer->vc_handle = vc_sm_cma_int_handle(buffer->vcsm_handle);
+		if (!buffer->vc_handle) {
+			pr_err("%s: vc_sm_int_handle failed %d\n",
+			       __func__, ret);
+			vc_sm_cma_free(buffer->vcsm_handle);
+			return ret;
+		}
+		pr_dbg_lvl(2, debug, "%s: import dmabuf %p - got vc handle %08X\n",
+			   __func__, buffer->dma_buf, buffer->vc_handle);
+	}
+
 	ret = buffer_from_host(instance, port, buffer);
 	if (ret == -EINVAL) {
 		/* Port is disabled. Queue for when it is enabled. */
@@ -1628,10 +1799,74 @@ int mmal_vchi_buffer_cleanup(struct mmal_buffer *buf)
 		release_msg_context(msg_context);
 	buf->msg_context = NULL;
 
+	if (buf->vcsm_handle) {
+		int ret;
+
+		pr_dbg_lvl(2, debug, "%s: vc_sm_cma_free on handle %p\n", __func__,
+			   buf->vcsm_handle);
+		ret = vc_sm_cma_free(buf->vcsm_handle);
+		if (ret)
+			pr_err("%s: vcsm_free failed, ret %d\n", __func__, ret);
+		buf->vcsm_handle = 0;
+	}
 	return 0;
 }
 EXPORT_SYMBOL_GPL(mmal_vchi_buffer_cleanup);
 
+static void init_event_context(struct vchiq_mmal_instance *instance,
+			       struct vchiq_mmal_port *port)
+{
+	struct mmal_msg_context *ctx = get_msg_context(instance);
+
+	mutex_init(&port->event_context_mutex);
+
+	port->event_context = ctx;
+	ctx->u.bulk.instance = instance;
+	ctx->u.bulk.port = port;
+	ctx->u.bulk.buffer =
+		kzalloc(sizeof(*ctx->u.bulk.buffer), GFP_KERNEL);
+	if (!ctx->u.bulk.buffer)
+		goto release_msg_context;
+	ctx->u.bulk.buffer->buffer = kzalloc(MMAL_WORKER_EVENT_SPACE,
+					     GFP_KERNEL);
+	if (!ctx->u.bulk.buffer->buffer)
+		goto release_buffer;
+
+	INIT_WORK(&ctx->u.bulk.work, buffer_work_cb);
+	return;
+
+release_buffer:
+	kfree(ctx->u.bulk.buffer);
+release_msg_context:
+	release_msg_context(ctx);
+}
+
+static void free_event_context(struct vchiq_mmal_port *port)
+{
+	struct mmal_msg_context *ctx = port->event_context;
+
+	if (!ctx)
+		return;
+
+	kfree(ctx->u.bulk.buffer->buffer);
+	kfree(ctx->u.bulk.buffer);
+	release_msg_context(ctx);
+	port->event_context = NULL;
+}
+
+static void release_all_event_contexts(struct vchiq_mmal_component *component)
+{
+	int idx;
+
+	for (idx = 0; idx < component->inputs; idx++)
+		free_event_context(&component->input[idx]);
+	for (idx = 0; idx < component->outputs; idx++)
+		free_event_context(&component->output[idx]);
+	for (idx = 0; idx < component->clocks; idx++)
+		free_event_context(&component->clock[idx]);
+	free_event_context(&component->control);
+}
+
 /* Initialise a mmal component and its ports
  *
  */
@@ -1681,6 +1916,7 @@ int vchiq_mmal_component_init(struct vchiq_mmal_instance *instance,
 	ret = port_info_get(instance, &component->control);
 	if (ret < 0)
 		goto release_component;
+	init_event_context(instance, &component->control);
 
 	for (idx = 0; idx < component->inputs; idx++) {
 		component->input[idx].type = MMAL_PORT_TYPE_INPUT;
@@ -1691,6 +1927,7 @@ int vchiq_mmal_component_init(struct vchiq_mmal_instance *instance,
 		ret = port_info_get(instance, &component->input[idx]);
 		if (ret < 0)
 			goto release_component;
+		init_event_context(instance, &component->input[idx]);
 	}
 
 	for (idx = 0; idx < component->outputs; idx++) {
@@ -1702,6 +1939,7 @@ int vchiq_mmal_component_init(struct vchiq_mmal_instance *instance,
 		ret = port_info_get(instance, &component->output[idx]);
 		if (ret < 0)
 			goto release_component;
+		init_event_context(instance, &component->output[idx]);
 	}
 
 	for (idx = 0; idx < component->clocks; idx++) {
@@ -1713,6 +1951,7 @@ int vchiq_mmal_component_init(struct vchiq_mmal_instance *instance,
 		ret = port_info_get(instance, &component->clock[idx]);
 		if (ret < 0)
 			goto release_component;
+		init_event_context(instance, &component->clock[idx]);
 	}
 
 	*component_out = component;
@@ -1723,6 +1962,7 @@ int vchiq_mmal_component_init(struct vchiq_mmal_instance *instance,
 
 release_component:
 	destroy_component(instance, component);
+	release_all_event_contexts(component);
 unlock:
 	if (component)
 		component->in_use = false;
@@ -1750,6 +1990,8 @@ int vchiq_mmal_component_finalise(struct vchiq_mmal_instance *instance,
 
 	component->in_use = false;
 
+	release_all_event_contexts(component);
+
 	mutex_unlock(&instance->vchiq_mutex);
 
 	return ret;
@@ -1774,7 +2016,7 @@ int vchiq_mmal_component_enable(struct vchiq_mmal_instance *instance,
 
 	ret = enable_component(instance, component);
 	if (ret == 0)
-		component->enabled = true;
+		component->enabled = 1;
 
 	mutex_unlock(&instance->vchiq_mutex);
 
diff --git a/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.h b/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.h
index 97abe4bdcfc5..56233f54c3cb 100644
--- a/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.h
+++ b/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.h
@@ -49,6 +49,7 @@ typedef void (*vchiq_mmal_buffer_cb)(struct vchiq_mmal_instance  *instance,
 
 struct vchiq_mmal_port {
 	bool enabled;
+	u32 zero_copy:1;
 	u32 handle;
 	u32 type; /* port type, cached to use on port info set */
 	u32 index; /* port index, cached to use on port info set */
@@ -79,6 +80,10 @@ struct vchiq_mmal_port {
 	vchiq_mmal_buffer_cb buffer_cb;
 	/* callback context */
 	void *cb_ctx;
+
+	/* ensure serialised use of the one event context structure */
+	struct mutex event_context_mutex;
+	struct mmal_msg_context *event_context;
 };
 
 struct vchiq_mmal_component {
diff --git a/drivers/thermal/broadcom/bcm2711_thermal.c b/drivers/thermal/broadcom/bcm2711_thermal.c
index 03ac2d02e9d4..de581c31a8f7 100644
--- a/drivers/thermal/broadcom/bcm2711_thermal.c
+++ b/drivers/thermal/broadcom/bcm2711_thermal.c
@@ -92,7 +92,7 @@ static int bcm2711_thermal_probe(struct platform_device *pdev)
 						&bcm2711_thermal_of_ops);
 	if (IS_ERR(thermal)) {
 		ret = PTR_ERR(thermal);
-		dev_err(dev, "could not register sensor: %d\n", ret);
+		dev_err_probe(dev, ret, "could not register sensor: %d\n", ret);
 		return ret;
 	}
 
diff --git a/drivers/thermal/gov_step_wise.c b/drivers/thermal/gov_step_wise.c
index b038f042ed74..af73e05c3c2b 100644
--- a/drivers/thermal/gov_step_wise.c
+++ b/drivers/thermal/gov_step_wise.c
@@ -17,16 +17,16 @@
 #include "thermal_core.h"
 
 /*
- * If the temperature is higher than a trip point,
+ * If the temperature is higher than a hysteresis temperature,
  *    a. if the trend is THERMAL_TREND_RAISING, use higher cooling
  *       state for this trip point
  *    b. if the trend is THERMAL_TREND_DROPPING, use a lower cooling state
  *       for this trip point, but keep the cooling state above the applicable
  *       minimum
- * If the temperature is lower than a trip point,
+ * If the temperature is lower than a hysteresis temperature,
  *    a. if the trend is THERMAL_TREND_RAISING, do nothing
- *    b. if the trend is THERMAL_TREND_DROPPING, use lower cooling
- *       state for this trip point, if the cooling state already
+ *    b. if the trend is THERMAL_TREND_DROPPING, use the minimum applicable
+ *       cooling state for this trip point, or if the cooling state already
  *       equals lower limit, deactivate the thermal instance
  */
 static unsigned long get_target_state(struct thermal_instance *instance,
@@ -83,23 +83,36 @@ static void thermal_zone_trip_update(struct thermal_zone_device *tz,
 				     int trip_threshold)
 {
 	const struct thermal_trip *trip = &td->trip;
+	int hyst_temp = trip->temperature - trip->hysteresis;
+	bool throttle = tz->temperature >= hyst_temp;
 	enum thermal_trend trend = get_tz_trend(tz, trip);
 	int trip_id = thermal_zone_trip_id(tz, trip);
 	struct thermal_instance *instance;
-	bool throttle = false;
 
-	if (tz->temperature >= trip_threshold) {
-		throttle = true;
+	if (throttle)
 		trace_thermal_zone_trip(tz, trip_id, trip->type);
-	}
 
-	dev_dbg(&tz->device, "Trip%d[type=%d,temp=%d]:trend=%d,throttle=%d\n",
-		trip_id, trip->type, trip_threshold, trend, throttle);
+	dev_dbg(&tz->device,
+		"Trip%d[type=%d,temp=%d,hyst=%d]:trend=%d,throttle=%d\n",
+		trip_id, trip->type, trip->temperature, hyst_temp, trend, throttle);
 
 	list_for_each_entry(instance, &td->thermal_instances, trip_node) {
 		int old_target;
 
 		old_target = instance->target;
+		throttle = false;
+
+		/*
+		 * Lower the mitigation only if the temperature
+		 * goes below the hysteresis temperature.
+		 */
+		if (tz->temperature >= trip->temperature ||
+		   (tz->temperature >= hyst_temp &&
+		   old_target == instance->upper)) {
+			throttle = true;
+			trace_thermal_zone_trip(tz, trip_id, trip->type);
+		}
+
 		instance->target = get_target_state(instance, trend, throttle);
 
 		dev_dbg(&instance->cdev->device, "old_target=%d, target=%ld\n",
diff --git a/drivers/tty/serial/8250/8250.h b/drivers/tty/serial/8250/8250.h
index 10a706fe4b24..4c7b7187197d 100644
--- a/drivers/tty/serial/8250/8250.h
+++ b/drivers/tty/serial/8250/8250.h
@@ -92,6 +92,7 @@ struct serial8250_config {
 #define UART_BUG_NOMSR	BIT(2)	/* UART has buggy MSR status bits (Au1x00) */
 #define UART_BUG_THRE	BIT(3)	/* UART has buggy THRE reassertion */
 #define UART_BUG_TXRACE	BIT(5)	/* UART Tx fails to set remote DR */
+#define UART_BUG_NOMSI	BIT(6)	/* UART has no modem status interrupt */
 
 /* Module parameters */
 #define UART_NR	CONFIG_SERIAL_8250_NR_UARTS
diff --git a/drivers/tty/serial/8250/8250_bcm2835aux.c b/drivers/tty/serial/8250/8250_bcm2835aux.c
index d7a0f271263a..4a4b117d244e 100644
--- a/drivers/tty/serial/8250/8250_bcm2835aux.c
+++ b/drivers/tty/serial/8250/8250_bcm2835aux.c
@@ -101,6 +101,7 @@ static int bcm2835aux_serial_probe(struct platform_device *pdev)
 	up.port.flags = UPF_FIXED_PORT | UPF_FIXED_TYPE | UPF_SKIP_TEST | UPF_IOREMAP;
 	up.port.rs485_config = serial8250_em485_config;
 	up.port.rs485_supported = serial8250_em485_supported;
+	up.bugs |= UART_BUG_NOMSI;
 	up.rs485_start_tx = bcm2835aux_rs485_start_tx;
 	up.rs485_stop_tx = bcm2835aux_rs485_stop_tx;
 
@@ -156,6 +157,13 @@ static int bcm2835aux_serial_probe(struct platform_device *pdev)
 	 */
 	up.port.uartclk *= 2;
 
+	/* The clock is only queried at probe time, which means we get one shot
+	 * at this. A zero clock is never going to work and is almost certainly
+	 * due to a parent not being ready, so prefer to defer.
+	 */
+	if (!up.port.uartclk)
+	    return -EPROBE_DEFER;
+
 	/* register the port */
 	ret = serial8250_register_8250_port(&up);
 	if (ret < 0) {
diff --git a/drivers/tty/serial/8250/8250_core.c b/drivers/tty/serial/8250/8250_core.c
index 68baf75bdadc..4b9258698c30 100644
--- a/drivers/tty/serial/8250/8250_core.c
+++ b/drivers/tty/serial/8250/8250_core.c
@@ -212,6 +212,18 @@ static void serial8250_timeout(struct timer_list *t)
 	mod_timer(&up->timer, jiffies + uart_poll_timeout(&up->port));
 }
 
+static void serial8250_cts_poll_timeout(struct timer_list *t)
+{
+	struct uart_8250_port *up = from_timer(up, t, timer);
+	unsigned long flags;
+
+	spin_lock_irqsave(&up->port.lock, flags);
+	serial8250_modem_status(up);
+	spin_unlock_irqrestore(&up->port.lock, flags);
+	if (up->port.hw_stopped)
+		mod_timer(&up->timer, jiffies + 1);
+}
+
 static void serial8250_backup_timeout(struct timer_list *t)
 {
 	struct uart_8250_port *up = from_timer(up, t, timer);
@@ -275,6 +287,9 @@ static void univ8250_setup_timer(struct uart_8250_port *up)
 			  uart_poll_timeout(port) + HZ / 5);
 	}
 
+	if (up->bugs & UART_BUG_NOMSI)
+		up->timer.function = serial8250_cts_poll_timeout;
+
 	/*
 	 * If the "interrupt" for this port doesn't correspond with any
 	 * hardware interrupt, we use a timer-based system.  The original
diff --git a/drivers/tty/serial/8250/8250_port.c b/drivers/tty/serial/8250/8250_port.c
index b4c8388ea6fc..26393c483ce7 100644
--- a/drivers/tty/serial/8250/8250_port.c
+++ b/drivers/tty/serial/8250/8250_port.c
@@ -1510,6 +1510,9 @@ static void serial8250_stop_tx(struct uart_port *port)
 		serial_icr_write(up, UART_ACR, up->acr);
 	}
 	serial8250_rpm_put(up);
+
+	if (port->hw_stopped && (up->bugs & UART_BUG_NOMSI))
+		mod_timer(&up->timer, jiffies + 1);
 }
 
 static inline void __start_tx(struct uart_port *port)
@@ -1623,6 +1626,9 @@ static void serial8250_start_tx(struct uart_port *port)
 	/* Port locked to synchronize UART_IER access against the console. */
 	lockdep_assert_held_once(&port->lock);
 
+	if (up->bugs & UART_BUG_NOMSI)
+		del_timer(&up->timer);
+
 	if (!port->x_char && kfifo_is_empty(&port->state->port.xmit_fifo))
 		return;
 
@@ -1851,6 +1857,9 @@ unsigned int serial8250_modem_status(struct uart_8250_port *up)
 			uart_handle_cts_change(port, status & UART_MSR_CTS);
 
 		wake_up_interruptible(&port->state->port.delta_msr_wait);
+	} else if (up->bugs & UART_BUG_NOMSI &&	port->hw_stopped &&
+		   status & UART_MSR_CTS) {
+		uart_handle_cts_change(port, status & UART_MSR_CTS);
 	}
 
 	return status;
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 1e92b16b6b95..ad212d32a8fc 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -1600,6 +1600,17 @@ config SERIAL_ESP32_ACM
 	  snippet may be used:
 	    earlycon=esp32s3acm,mmio32,0x60038000
 
+config SERIAL_RPI_FW
+	tristate "Raspberry Pi Firmware software UART support"
+	depends on RASPBERRYPI_FIRMWARE || COMPILE_TEST
+	select SERIAL_CORE
+	help
+	  This selects the Raspberry Pi firmware UART. This is a bit-bashed
+	  implementation running on the Raspbery Pi VPU core.
+	  This is not supported on Raspberry Pi 5 or newer platforms.
+
+	  If unsure, say N.
+
 endmenu
 
 config SERIAL_MCTRL_GPIO
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index 6ff74f0a9530..49b23cc3457f 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -71,6 +71,7 @@ obj-$(CONFIG_SERIAL_QCOM_GENI)		+= qcom_geni_serial.o
 obj-$(CONFIG_SERIAL_QE)			+= ucc_uart.o
 obj-$(CONFIG_SERIAL_RDA)		+= rda-uart.o
 obj-$(CONFIG_SERIAL_RP2)		+= rp2.o
+obj-$(CONFIG_SERIAL_RPI_FW) 		+= rpi-fw-uart.o
 obj-$(CONFIG_SERIAL_SA1100)		+= sa1100.o
 obj-$(CONFIG_SERIAL_SAMSUNG)		+= samsung_tty.o
 obj-$(CONFIG_SERIAL_SB1250_DUART)	+= sb1250-duart.o
diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c
index 3713b23536dc..1e24bbc1f006 100644
--- a/drivers/tty/serial/amba-pl011.c
+++ b/drivers/tty/serial/amba-pl011.c
@@ -152,6 +152,20 @@ static const struct vendor_data vendor_sbsa = {
 	.fixed_options		= true,
 };
 
+static struct vendor_data vendor_arm_axi = {
+	.reg_offset		= pl011_std_offsets,
+	.ifls			= UART011_IFLS_RX4_8 | UART011_IFLS_TX4_8,
+	.fr_busy		= UART01x_FR_BUSY,
+	.fr_dsr			= UART01x_FR_DSR,
+	.fr_cts			= UART01x_FR_CTS,
+	.fr_ri			= UART011_FR_RI,
+	.oversampling		= false,
+	.dma_threshold		= false,
+	.cts_event_workaround	= false,
+	.always_enabled		= false,
+	.fixed_options		= false,
+};
+
 #ifdef CONFIG_ACPI_SPCR_TABLE
 static const struct vendor_data vendor_qdt_qdf2400_e44 = {
 	.reg_offset		= pl011_std_offsets,
@@ -451,6 +465,7 @@ static void pl011_dma_probe(struct uart_amba_port *uap)
 			.src_addr = uap->port.mapbase +
 				pl011_reg_to_offset(uap, REG_DR),
 			.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE,
+			.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE,
 			.direction = DMA_DEV_TO_MEM,
 			.src_maxburst = uap->fifosize >> 2,
 			.device_fc = false,
@@ -470,6 +485,12 @@ static void pl011_dma_probe(struct uart_amba_port *uap)
 					 "RX DMA disabled - no residue processing\n");
 				return;
 			}
+			/*
+			 * DMA controllers with smaller burst capabilities than 1/4
+			 * the FIFO depth will leave more bytes than expected in the
+			 * RX FIFO if mismatched.
+			 */
+			rx_conf.src_maxburst = min(caps.max_burst, rx_conf.src_maxburst);
 		}
 		dmaengine_slave_config(chan, &rx_conf);
 		uap->dmarx.chan = chan;
@@ -1433,6 +1454,7 @@ static bool pl011_tx_char(struct uart_amba_port *uap, unsigned char c,
 		return false; /* unable to transmit character */
 
 	pl011_write(c, uap, REG_DR);
+	mb();
 	uap->port.icount.tx++;
 
 	return true;
@@ -1465,6 +1487,10 @@ static bool pl011_tx_chars(struct uart_amba_port *uap, bool from_irq)
 		if (likely(from_irq) && count-- == 0)
 			break;
 
+		if (likely(from_irq) && count == 0 &&
+		    pl011_read(uap, REG_FR) & UART01x_FR_TXFF)
+			break;
+
 		if (!kfifo_peek(&tport->xmit_fifo, &c))
 			break;
 
@@ -2782,6 +2808,11 @@ static int pl011_probe(struct amba_device *dev, const struct amba_id *id)
 	if (IS_ERR(uap->clk))
 		return PTR_ERR(uap->clk);
 
+	if (of_property_read_bool(dev->dev.of_node, "cts-event-workaround")) {
+	    vendor->cts_event_workaround = true;
+	    dev_info(&dev->dev, "cts_event_workaround enabled\n");
+	}
+
 	uap->reg_offset = vendor->reg_offset;
 	uap->vendor = vendor;
 	uap->fifosize = vendor->get_fifosize(dev);
@@ -2954,6 +2985,87 @@ static struct platform_driver arm_sbsa_uart_platform_driver = {
 	},
 };
 
+static int pl011_axi_probe(struct platform_device *pdev)
+{
+	struct uart_amba_port *uap;
+	struct vendor_data *vendor =  &vendor_arm_axi;
+	struct resource *r;
+	unsigned int periphid;
+	int portnr, ret, irq;
+
+	portnr = pl011_find_free_port();
+	if (portnr < 0)
+		return portnr;
+
+	uap = devm_kzalloc(&pdev->dev, sizeof(struct uart_amba_port),
+			   GFP_KERNEL);
+	if (!uap)
+		return -ENOMEM;
+
+	uap->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(uap->clk))
+		return PTR_ERR(uap->clk);
+
+	if (of_property_read_bool(pdev->dev.of_node, "cts-event-workaround")) {
+		vendor->cts_event_workaround = true;
+		dev_info(&pdev->dev, "cts_event_workaround enabled\n");
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	periphid = 0x00241011; /* A safe default */
+	of_property_read_u32(pdev->dev.of_node, "arm,primecell-periphid",
+			     &periphid);
+
+	uap->reg_offset = vendor->reg_offset;
+	uap->vendor = vendor;
+	uap->fifosize = (AMBA_REV_BITS(periphid) < 3) ? 16 : 32;
+	uap->port.iotype = vendor->access_32b ? UPIO_MEM32 : UPIO_MEM;
+	uap->port.irq = irq;
+	uap->port.ops = &amba_pl011_pops;
+	uap->port.rs485_config = pl011_rs485_config;
+	uap->port.rs485_supported = pl011_rs485_supported;
+
+	snprintf(uap->type, sizeof(uap->type), "PL011 AXI");
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	ret = pl011_setup_port(&pdev->dev, uap, r, portnr);
+	if (ret)
+		return ret;
+
+	platform_set_drvdata(pdev, uap);
+
+	return pl011_register_port(uap);
+}
+
+static void pl011_axi_remove(struct platform_device *pdev)
+{
+	struct uart_amba_port *uap = platform_get_drvdata(pdev);
+
+	uart_remove_one_port(&amba_reg, &uap->port);
+	pl011_unregister_port(uap);
+}
+
+static const struct of_device_id pl011_axi_of_match[] = {
+	{ .compatible = "arm,pl011-axi" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, pl011_axi_of_match);
+
+static struct platform_driver pl011_axi_platform_driver = {
+	.probe		= pl011_axi_probe,
+	.remove		= pl011_axi_remove,
+	.driver	= {
+		.name	= "pl011-axi",
+		.pm	= &pl011_dev_pm_ops,
+		.of_match_table = of_match_ptr(pl011_axi_of_match),
+		.suppress_bind_attrs = IS_BUILTIN(CONFIG_SERIAL_AMBA_PL011),
+	},
+};
+
 static const struct amba_id pl011_ids[] = {
 	{
 		.id	= 0x00041011,
@@ -2987,12 +3099,15 @@ static int __init pl011_init(void)
 
 	if (platform_driver_register(&arm_sbsa_uart_platform_driver))
 		pr_warn("could not register SBSA UART platform driver\n");
+	if (platform_driver_register(&pl011_axi_platform_driver))
+		pr_warn("could not register PL011 AXI platform driver\n");
 	return amba_driver_register(&pl011_driver);
 }
 
 static void __exit pl011_exit(void)
 {
 	platform_driver_unregister(&arm_sbsa_uart_platform_driver);
+	platform_driver_unregister(&pl011_axi_platform_driver);
 	amba_driver_unregister(&pl011_driver);
 }
 
diff --git a/drivers/tty/serial/rpi-fw-uart.c b/drivers/tty/serial/rpi-fw-uart.c
new file mode 100644
index 000000000000..b6d3b68927f4
--- /dev/null
+++ b/drivers/tty/serial/rpi-fw-uart.c
@@ -0,0 +1,562 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2024, Raspberry Pi Ltd.  All rights reserved.
+ */
+
+#include <linux/console.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/serial.h>
+#include <linux/serial_core.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <soc/bcm2835/raspberrypi-firmware.h>
+#include <linux/dma-mapping.h>
+
+#define RPI_FW_UART_RX_FIFO_RD	0xb0
+#define RPI_FW_UART_RX_FIFO_WR	0xb4
+#define RPI_FW_UART_TX_FIFO_RD	0xb8
+#define RPI_FW_UART_TX_FIFO_WR	0xbc
+
+#define RPI_FW_UART_FIFO_SIZE		32
+#define RPI_FW_UART_FIFO_SIZE_MASK	(RPI_FW_UART_FIFO_SIZE - 1)
+
+#define RPI_FW_UART_MIN_VERSION	3
+
+struct rpi_fw_uart_params {
+	u32 start;
+	u32 baud;
+	u32 data_bits;
+	u32 stop_bits;
+	u32 gpio_rx;
+	u32 gpio_tx;
+	u32 flags;
+	u32 fifosize;
+	u32 rx_buffer;
+	u32 tx_buffer;
+	u32 version;
+	u32 fifo_reg_base;
+};
+
+struct rpi_fw_uart {
+	struct uart_driver	driver;
+	struct uart_port	port;
+	struct rpi_firmware	*firmware;
+	struct gpio_desc	*rx_gpiod;
+	struct gpio_desc	*tx_gpiod;
+	unsigned int		rx_gpio;
+	unsigned int		tx_gpio;
+	unsigned int		baud;
+	unsigned int		data_bits;
+	unsigned int		stop_bits;
+	unsigned char __iomem	*base;
+	size_t			dma_buffer_size;
+
+	struct hrtimer		trigger_start_rx;
+	ktime_t			rx_poll_delay;
+	void			*rx_buffer;
+	dma_addr_t		rx_buffer_dma_addr;
+	int			rx_stop;
+
+	void			*tx_buffer;
+	dma_addr_t		tx_buffer_dma_addr;
+};
+
+static unsigned int rpi_fw_uart_tx_is_full(struct uart_port *port)
+{
+	struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
+	u32 rd, wr;
+
+	rd = readl(rfu->base + RPI_FW_UART_TX_FIFO_RD);
+	wr = readl(rfu->base + RPI_FW_UART_TX_FIFO_WR);
+	return ((wr + 1) & RPI_FW_UART_FIFO_SIZE_MASK) == rd;
+}
+
+static unsigned int rpi_fw_uart_tx_is_empty(struct uart_port *port)
+{
+	struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
+	u32 rd, wr;
+
+	if (!rfu->tx_buffer)
+		return 1;
+
+	rd = readl(rfu->base + RPI_FW_UART_TX_FIFO_RD);
+	wr = readl(rfu->base + RPI_FW_UART_TX_FIFO_WR);
+
+	return rd == wr;
+}
+
+static unsigned int rpi_fw_uart_rx_is_empty(struct uart_port *port)
+{
+	struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
+	u32 rd, wr;
+
+	if (!rfu->rx_buffer)
+		return 1;
+
+	rd = readl(rfu->base + RPI_FW_UART_RX_FIFO_RD);
+	wr = readl(rfu->base + RPI_FW_UART_RX_FIFO_WR);
+
+	return rd == wr;
+}
+
+static unsigned int rpi_fw_uart_tx_empty(struct uart_port *port)
+{
+	return rpi_fw_uart_tx_is_empty(port) ? TIOCSER_TEMT : 0;
+}
+
+static void rpi_fw_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
+{
+	/*
+	 * No hardware flow control, firmware automatically configures
+	 * TX to output high and RX to input low.
+	 */
+	dev_dbg(port->dev, "%s mctrl %u\n", __func__, mctrl);
+}
+
+static unsigned int rpi_fw_uart_get_mctrl(struct uart_port *port)
+{
+	/* No hardware flow control */
+	return TIOCM_CTS;
+}
+
+static void rpi_fw_uart_stop(struct uart_port *port)
+{
+	struct rpi_fw_uart_params msg = {.start = 0};
+	struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
+
+	hrtimer_cancel(&rfu->trigger_start_rx);
+
+	if (rpi_firmware_property(rfu->firmware,
+				RPI_FIRMWARE_SET_SW_UART,
+				&msg, sizeof(msg)))
+		dev_warn(port->dev,
+			 "Failed to shutdown rpi-fw uart. Firmware not configured?");
+}
+
+static void rpi_fw_uart_stop_tx(struct uart_port *port)
+{
+	/* No supported by the current firmware APIs. */
+}
+
+static void rpi_fw_uart_stop_rx(struct uart_port *port)
+{
+	struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
+
+	rfu->rx_stop = 1;
+}
+
+static unsigned int rpi_fw_write(struct uart_port *port, const char *s,
+				unsigned int count)
+{
+	struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
+	u8 *out = rfu->tx_buffer;
+	unsigned int consumed = 0;
+
+	while (consumed < count && !rpi_fw_uart_tx_is_full(port)) {
+		u32 wp = readl(rfu->base + RPI_FW_UART_TX_FIFO_WR)
+			& RPI_FW_UART_FIFO_SIZE_MASK;
+		out[wp] = s[consumed++];
+		wp = (wp + 1) & RPI_FW_UART_FIFO_SIZE_MASK;
+		writel(wp, rfu->base + RPI_FW_UART_TX_FIFO_WR);
+	}
+	return consumed;
+}
+
+/* Called with port.lock taken */
+static void rpi_fw_uart_start_tx(struct uart_port *port)
+{
+	struct tty_port *tport = &port->state->port;
+
+	for (;;) {
+		unsigned int consumed;
+		unsigned char *tail;
+		unsigned int count;
+
+		count = kfifo_out_linear_ptr(&tport->xmit_fifo, &tail, port->fifosize);
+		if (!count)
+			break;
+
+		consumed = rpi_fw_write(port, tail, count);
+		uart_xmit_advance(port, consumed);
+	}
+	uart_write_wakeup(port);
+}
+
+/* Called with port.lock taken */
+static void rpi_fw_uart_start_rx(struct uart_port *port)
+{
+	struct tty_port *tty_port = &port->state->port;
+	struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
+	int count = 0;
+
+	/*
+	 * RX is polled, read up to a full buffer of data before trying again
+	 * so that this can be interrupted if the firmware is filling the
+	 * buffer too fast
+	 */
+	while (!rpi_fw_uart_rx_is_empty(port) && count < port->fifosize) {
+		const u8 *in = rfu->rx_buffer;
+		u32 rp = readl(rfu->base + RPI_FW_UART_RX_FIFO_RD)
+			& RPI_FW_UART_FIFO_SIZE_MASK;
+
+		tty_insert_flip_char(tty_port, in[rp], TTY_NORMAL);
+		rp = (rp + 1) & RPI_FW_UART_FIFO_SIZE_MASK;
+		writel(rp, rfu->base + RPI_FW_UART_RX_FIFO_RD);
+		count++;
+	}
+	if (count)
+		tty_flip_buffer_push(tty_port);
+}
+
+static enum hrtimer_restart rpi_fw_uart_trigger_rx(struct hrtimer *t)
+{
+	unsigned long flags;
+	struct rpi_fw_uart *rfu = container_of(t, struct rpi_fw_uart,
+					      trigger_start_rx);
+
+	spin_lock_irqsave(&rfu->port.lock, flags);
+	if (rfu->rx_stop) {
+		spin_unlock_irqrestore(&rfu->port.lock, flags);
+		return HRTIMER_NORESTART;
+	}
+
+	rpi_fw_uart_start_rx(&rfu->port);
+	spin_unlock_irqrestore(&rfu->port.lock, flags);
+	hrtimer_forward_now(t, rfu->rx_poll_delay);
+	return HRTIMER_RESTART;
+}
+
+static void rpi_fw_uart_break_ctl(struct uart_port *port, int ctl)
+{
+	dev_dbg(port->dev, "%s ctl %d\n", __func__, ctl);
+}
+
+static int rpi_fw_uart_configure(struct uart_port *port)
+{
+	struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
+	struct rpi_fw_uart_params msg;
+	unsigned long flags;
+	int rc;
+
+	rpi_fw_uart_stop(port);
+
+	memset(&msg, 0, sizeof(msg));
+	msg.start = 1;
+	msg.gpio_rx = rfu->rx_gpio;
+	msg.gpio_tx = rfu->tx_gpio;
+	msg.data_bits = rfu->data_bits;
+	msg.stop_bits = rfu->stop_bits;
+	msg.baud = rfu->baud;
+	msg.fifosize = RPI_FW_UART_FIFO_SIZE;
+	msg.rx_buffer = (u32) rfu->rx_buffer_dma_addr;
+	msg.tx_buffer = (u32) rfu->tx_buffer_dma_addr;
+
+	rfu->rx_poll_delay = ms_to_ktime(50);
+
+	/*
+	 * Reconfigures the firmware UART with the new settings. On the first
+	 * call retrieve the addresses of the FIFO buffers. The buffers are
+	 * allocated at startup and are not de-allocated.
+	 * NB rpi_firmware_property can block
+	 */
+	rc = rpi_firmware_property(rfu->firmware,
+				RPI_FIRMWARE_SET_SW_UART,
+				&msg, sizeof(msg));
+	if (rc)
+		goto fail;
+
+	rc = rpi_firmware_property(rfu->firmware,
+			RPI_FIRMWARE_GET_SW_UART,
+			&msg, sizeof(msg));
+	if (rc)
+		goto fail;
+
+	dev_dbg(port->dev, "version %08x, reg addr %x\n", msg.version,
+		msg.fifo_reg_base);
+
+	dev_dbg(port->dev, "started %d baud %u data %u stop %u rx %u tx %u flags %u fifosize %u\n",
+		msg.start, msg.baud, msg.data_bits, msg.stop_bits,
+		msg.gpio_rx, msg.gpio_tx, msg.flags, msg.fifosize);
+
+	if (msg.fifosize != port->fifosize) {
+		dev_err(port->dev, "Expected fifo size %u actual %u",
+				port->fifosize, msg.fifosize);
+		rc = -EINVAL;
+		goto fail;
+	}
+
+	if (!msg.start) {
+		dev_err(port->dev, "Firmware service not running\n");
+		rc = -EINVAL;
+	}
+
+	spin_lock_irqsave(&rfu->port.lock, flags);
+	rfu->rx_stop = 0;
+	hrtimer_start(&rfu->trigger_start_rx,
+		      rfu->rx_poll_delay, HRTIMER_MODE_REL);
+	spin_unlock_irqrestore(&rfu->port.lock, flags);
+	return 0;
+fail:
+	dev_err(port->dev, "Failed to configure rpi-fw uart. Firmware not configured?");
+	return rc;
+}
+
+static void rpi_fw_uart_free_buffers(struct uart_port *port)
+{
+	struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
+
+	if (rfu->rx_buffer)
+		dma_free_coherent(port->dev, rfu->dma_buffer_size,
+				rfu->rx_buffer, GFP_ATOMIC);
+
+	if (rfu->tx_buffer)
+		dma_free_coherent(port->dev, rfu->dma_buffer_size,
+				rfu->tx_buffer, GFP_ATOMIC);
+
+	rfu->rx_buffer = NULL;
+	rfu->tx_buffer = NULL;
+	rfu->rx_buffer_dma_addr = 0;
+	rfu->tx_buffer_dma_addr = 0;
+}
+
+static int rpi_fw_uart_alloc_buffers(struct uart_port *port)
+{
+	struct rpi_fw_uart *rfu = container_of(port, struct rpi_fw_uart, port);
+
+	if (rfu->tx_buffer)
+		return 0;
+
+	rfu->dma_buffer_size = PAGE_ALIGN(RPI_FW_UART_FIFO_SIZE);
+
+	rfu->rx_buffer = dma_alloc_coherent(port->dev, rfu->dma_buffer_size,
+		&rfu->rx_buffer_dma_addr, GFP_ATOMIC);
+
+	if (!rfu->rx_buffer)
+		goto alloc_fail;
+
+	rfu->tx_buffer = dma_alloc_coherent(port->dev, rfu->dma_buffer_size,
+		&rfu->tx_buffer_dma_addr, GFP_ATOMIC);
+
+	if (!rfu->tx_buffer)
+		goto alloc_fail;
+
+	dev_dbg(port->dev, "alloc-buffers %p %x %p %x\n",
+		rfu->rx_buffer, (u32) rfu->rx_buffer_dma_addr,
+		rfu->tx_buffer, (u32) rfu->tx_buffer_dma_addr);
+	return 0;
+
+alloc_fail:
+	dev_err(port->dev, "%s uart buffer allocation failed\n", __func__);
+	rpi_fw_uart_free_buffers(port);
+	return -ENOMEM;
+}
+
+static int rpi_fw_uart_startup(struct uart_port *port)
+{
+	int rc;
+
+	rc = rpi_fw_uart_alloc_buffers(port);
+	if (rc)
+		dev_err(port->dev, "Failed to start\n");
+	return rc;
+}
+
+static void rpi_fw_uart_shutdown(struct uart_port *port)
+{
+	rpi_fw_uart_stop(port);
+	rpi_fw_uart_free_buffers(port);
+}
+
+static void rpi_fw_uart_set_termios(struct uart_port *port,
+				       struct ktermios *new,
+				       const struct ktermios *old)
+{
+	struct rpi_fw_uart *rfu =
+		container_of(port, struct rpi_fw_uart, port);
+	rfu->baud = uart_get_baud_rate(port, new, old, 50, 115200);
+	rfu->stop_bits = (new->c_cflag & CSTOPB) ? 2 : 1;
+
+	rpi_fw_uart_configure(port);
+}
+
+static const struct uart_ops rpi_fw_uart_ops = {
+	.tx_empty = rpi_fw_uart_tx_empty,
+	.set_mctrl = rpi_fw_uart_set_mctrl,
+	.get_mctrl = rpi_fw_uart_get_mctrl,
+	.stop_rx = rpi_fw_uart_stop_rx,
+	.stop_tx = rpi_fw_uart_stop_tx,
+	.start_tx = rpi_fw_uart_start_tx,
+	.break_ctl = rpi_fw_uart_break_ctl,
+	.startup = rpi_fw_uart_startup,
+	.shutdown = rpi_fw_uart_shutdown,
+	.set_termios = rpi_fw_uart_set_termios,
+};
+
+static int rpi_fw_uart_get_gpio_offset(struct device *dev, const char *name)
+{
+	struct of_phandle_args of_args = { 0 };
+	bool is_bcm28xx;
+
+	/* This really shouldn't fail, given that we have a gpiod */
+	if (of_parse_phandle_with_args(dev->of_node, name, "#gpio-cells", 0, &of_args))
+		return dev_err_probe(dev, -EINVAL, "can't find gpio declaration\n");
+
+	is_bcm28xx = of_device_is_compatible(of_args.np, "brcm,bcm2835-gpio") ||
+		     of_device_is_compatible(of_args.np, "brcm,bcm2711-gpio");
+	of_node_put(of_args.np);
+	if (!is_bcm28xx || of_args.args_count != 2)
+		return dev_err_probe(dev, -EINVAL, "not a BCM28xx gpio\n");
+
+	return of_args.args[0];
+}
+
+static int rpi_fw_uart_probe(struct platform_device *pdev)
+{
+	struct device_node *firmware_node;
+	struct device *dev = &pdev->dev;
+	struct rpi_firmware *firmware;
+	struct uart_port *port;
+	struct rpi_fw_uart *rfu;
+	struct rpi_fw_uart_params msg;
+	int version_major;
+	int err;
+
+	dev_dbg(dev, "%s of_node %p\n", __func__, dev->of_node);
+
+	/*
+	 * We can be probed either through the an old-fashioned
+	 * platform device registration or through a DT node that is a
+	 * child of the firmware node. Handle both cases.
+	 */
+	if (dev->of_node)
+		firmware_node = of_parse_phandle(dev->of_node, "firmware", 0);
+	else
+		firmware_node = of_find_compatible_node(NULL, NULL,
+				"raspberrypi,bcm2835-firmware");
+	if (!firmware_node) {
+		dev_err(dev, "Missing firmware node\n");
+		return -ENOENT;
+	}
+
+	firmware = devm_rpi_firmware_get(dev, firmware_node);
+	of_node_put(firmware_node);
+	if (!firmware)
+		return -EPROBE_DEFER;
+
+	rfu = devm_kzalloc(dev, sizeof(*rfu), GFP_KERNEL);
+	if (!rfu)
+		return -ENOMEM;
+
+	rfu->firmware = firmware;
+
+	err = rpi_firmware_property(rfu->firmware, RPI_FIRMWARE_GET_SW_UART,
+			&msg, sizeof(msg));
+	if (err) {
+		dev_err(dev, "VC firmware does not support rpi-fw-uart\n");
+		return err;
+	}
+
+	version_major = msg.version >> 16;
+	if (msg.version < RPI_FW_UART_MIN_VERSION) {
+		dev_err(dev, "rpi-fw-uart fw version %d is too old min version %d\n",
+				version_major, RPI_FW_UART_MIN_VERSION);
+		return -EINVAL;
+	}
+
+	rfu->rx_gpiod = devm_gpiod_get(dev, "rx", GPIOD_IN);
+	if (IS_ERR(rfu->rx_gpiod))
+		return PTR_ERR(rfu->rx_gpiod);
+
+	rfu->tx_gpiod = devm_gpiod_get(dev, "tx", GPIOD_OUT_HIGH);
+	if (IS_ERR(rfu->tx_gpiod))
+		return PTR_ERR(rfu->tx_gpiod);
+
+	rfu->rx_gpio = rpi_fw_uart_get_gpio_offset(dev, "rx-gpios");
+	if (rfu->rx_gpio < 0)
+		return rfu->rx_gpio;
+	rfu->tx_gpio = rpi_fw_uart_get_gpio_offset(dev, "tx-gpios");
+	if (rfu->tx_gpio < 0)
+		return rfu->tx_gpio;
+
+	rfu->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(rfu->base))
+		return PTR_ERR(rfu->base);
+
+	/* setup the driver */
+	rfu->driver.owner = THIS_MODULE;
+	rfu->driver.driver_name = "ttyRFU";
+	rfu->driver.dev_name = "ttyRFU";
+	rfu->driver.nr = 1;
+	rfu->data_bits = 8;
+
+	/* RX is polled */
+	hrtimer_init(&rfu->trigger_start_rx, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+	rfu->trigger_start_rx.function = rpi_fw_uart_trigger_rx;
+
+	err = uart_register_driver(&rfu->driver);
+	if (err) {
+		dev_err(dev, "failed to register UART driver: %d\n",
+			err);
+		return err;
+	}
+
+	/* setup the port */
+	port = &rfu->port;
+	spin_lock_init(&port->lock);
+	port->dev = &pdev->dev;
+	port->type = PORT_RPI_FW;
+	port->ops = &rpi_fw_uart_ops;
+	port->fifosize = RPI_FW_UART_FIFO_SIZE;
+	port->iotype = UPIO_MEM;
+	port->flags = UPF_BOOT_AUTOCONF;
+	port->private_data = rfu;
+
+	err = uart_add_one_port(&rfu->driver, port);
+	if (err) {
+		dev_err(dev, "failed to add UART port: %d\n", err);
+		goto unregister_uart;
+	}
+	platform_set_drvdata(pdev, rfu);
+
+	dev_info(dev, "version %d.%d gpios tx %u rx %u\n",
+			msg.version >> 16, msg.version & 0xffff,
+			rfu->tx_gpio, rfu->rx_gpio);
+	return 0;
+
+unregister_uart:
+	uart_unregister_driver(&rfu->driver);
+
+	return err;
+}
+
+static void rpi_fw_uart_remove(struct platform_device *pdev)
+{
+	struct rpi_fw_uart *rfu = platform_get_drvdata(pdev);
+
+	uart_remove_one_port(&rfu->driver, &rfu->port);
+	uart_unregister_driver(&rfu->driver);
+}
+
+static const struct of_device_id rpi_fw_match[] = {
+	{ .compatible = "raspberrypi,firmware-uart" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, rpi_fw_match);
+
+static struct platform_driver rpi_fw_driver = {
+	.driver = {
+		.name = "rpi_fw-uart",
+		.of_match_table = rpi_fw_match,
+	},
+	.probe = rpi_fw_uart_probe,
+	.remove = rpi_fw_uart_remove,
+};
+module_platform_driver(rpi_fw_driver);
+
+MODULE_AUTHOR("Tim Gover <tim.gover@rasberrypi.com>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Raspberry Pi Firmware Software UART driver");
diff --git a/drivers/tty/serial/sc16is7xx.c b/drivers/tty/serial/sc16is7xx.c
index 8ed8ee58e318..3aab1b728970 100644
--- a/drivers/tty/serial/sc16is7xx.c
+++ b/drivers/tty/serial/sc16is7xx.c
@@ -814,6 +814,8 @@ static bool sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno)
 
 		if (rxlen)
 			sc16is7xx_handle_rx(port, rxlen, iir);
+		else
+			rc = false;
 		break;
 		/* CTSRTS interrupt comes only when CTS goes inactive */
 	case SC16IS7XX_IIR_CTSRTS_SRC:
@@ -1189,6 +1191,9 @@ static int sc16is7xx_startup(struct uart_port *port)
 	      SC16IS7XX_IER_MSI_BIT;
 	sc16is7xx_port_write(port, SC16IS7XX_IER_REG, val);
 
+	/* Initialize the Modem Control signals to current status */
+	one->old_mctrl = sc16is7xx_get_hwmctrl(port);
+
 	/* Enable modem status polling */
 	uart_port_lock_irqsave(port, &flags);
 	sc16is7xx_enable_ms(port);
@@ -1456,7 +1461,7 @@ static int sc16is7xx_setup_mctrl_ports(struct sc16is7xx_port *s,
 }
 
 static const struct serial_rs485 sc16is7xx_rs485_supported = {
-	.flags = SER_RS485_ENABLED | SER_RS485_RTS_AFTER_SEND,
+	.flags = SER_RS485_ENABLED | SER_RS485_RTS_ON_SEND | SER_RS485_RTS_AFTER_SEND,
 	.delay_rts_before_send = 1,
 	.delay_rts_after_send = 1,	/* Not supported but keep returning -EINVAL */
 };
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index 949eca0adebe..80af388baede 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_USB_COMMON)	+= common/
 obj-$(CONFIG_USB)		+= core/
 obj-$(CONFIG_USB_SUPPORT)	+= phy/
 
+obj-$(CONFIG_USB_DWCOTG)	+= host/
 obj-$(CONFIG_USB_DWC3)		+= dwc3/
 obj-$(CONFIG_USB_DWC2)		+= dwc2/
 obj-$(CONFIG_USB_ISP1760)	+= isp1760/
diff --git a/drivers/usb/core/generic.c b/drivers/usb/core/generic.c
index b134bff5c3fe..f2d342d80bc0 100644
--- a/drivers/usb/core/generic.c
+++ b/drivers/usb/core/generic.c
@@ -206,6 +206,7 @@ int usb_choose_configuration(struct usb_device *udev)
 		dev_warn(&udev->dev,
 			"no configuration chosen from %d choice%s\n",
 			num_configs, plural(num_configs));
+		dev_warn(&udev->dev, "No support over %dmA\n", udev->bus_mA);
 	}
 	return i;
 }
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index bc795257696e..0a839e8fc6f1 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -1951,6 +1951,16 @@ int usb_hcd_alloc_bandwidth(struct usb_device *udev,
 	return ret;
 }
 
+void usb_hcd_fixup_endpoint(struct usb_device *udev,
+			    struct usb_host_endpoint *ep, int interval)
+{
+	struct usb_hcd *hcd;
+
+	hcd = bus_to_hcd(udev->bus);
+	if (hcd->driver->fixup_endpoint)
+		hcd->driver->fixup_endpoint(hcd, udev, ep, interval);
+}
+
 /* Disables the endpoint: synchronizes with the hcd to make sure all
  * endpoint state is gone from hardware.  usb_hcd_flush_endpoint() must
  * have been called previously.  Use for set_configuration, set_interface,
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 090b3a757112..052dfc14a312 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -5786,7 +5786,7 @@ static void port_event(struct usb_hub *hub, int port1)
 		port_dev->over_current_count++;
 		port_over_current_notify(port_dev);
 
-		dev_dbg(&port_dev->dev, "over-current change #%u\n",
+		dev_notice(&port_dev->dev, "over-current change #%u\n",
 			port_dev->over_current_count);
 		usb_clear_port_feature(hdev, port1,
 				USB_PORT_FEAT_C_OVER_CURRENT);
diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c
index 43522f1d6b2b..20d0f1a93586 100644
--- a/drivers/usb/core/message.c
+++ b/drivers/usb/core/message.c
@@ -1325,6 +1325,21 @@ static void remove_intf_ep_devs(struct usb_interface *intf)
 	intf->ep_devs_created = 0;
 }
 
+void usb_fixup_endpoint(struct usb_device *dev, int epaddr, int interval)
+{
+	unsigned int epnum = epaddr & USB_ENDPOINT_NUMBER_MASK;
+	struct usb_host_endpoint *ep;
+
+	if (usb_endpoint_out(epaddr))
+		ep = dev->ep_out[epnum];
+	else
+		ep = dev->ep_in[epnum];
+
+	if (ep && usb_endpoint_xfer_int(&ep->desc))
+		usb_hcd_fixup_endpoint(dev, ep, interval);
+}
+EXPORT_SYMBOL_GPL(usb_fixup_endpoint);
+
 /**
  * usb_disable_endpoint -- Disable an endpoint by address
  * @dev: the device whose endpoint is being disabled
@@ -2238,6 +2253,85 @@ int usb_set_configuration(struct usb_device *dev, int configuration)
 	if (cp->string == NULL &&
 			!(dev->quirks & USB_QUIRK_CONFIG_INTF_STRINGS))
 		cp->string = usb_cache_string(dev, cp->desc.iConfiguration);
+/* Uncomment this define to enable the HS Electrical Test support */
+#define DWC_HS_ELECT_TST 1
+#ifdef DWC_HS_ELECT_TST
+		/* Here we implement the HS Electrical Test support. The
+		 * tester uses a vendor ID of 0x1A0A to indicate we should
+		 * run a special test sequence. The product ID tells us
+		 * which sequence to run. We invoke the test sequence by
+		 * sending a non-standard SetFeature command to our root
+		 * hub port. Our dwc_otg_hcd_hub_control() routine will
+		 * recognize the command and perform the desired test
+		 * sequence.
+		 */
+		if (dev->descriptor.idVendor == 0x1A0A) {
+			/* HSOTG Electrical Test */
+			dev_warn(&dev->dev, "VID from HSOTG Electrical Test Fixture\n");
+
+			if (dev->bus && dev->bus->root_hub) {
+				struct usb_device *hdev = dev->bus->root_hub;
+				dev_warn(&dev->dev, "Got PID 0x%x\n", dev->descriptor.idProduct);
+
+				switch (dev->descriptor.idProduct) {
+				case 0x0101:	/* TEST_SE0_NAK */
+					dev_warn(&dev->dev, "TEST_SE0_NAK\n");
+					usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
+							USB_REQ_SET_FEATURE, USB_RT_PORT,
+							USB_PORT_FEAT_TEST, 0x300, NULL, 0, HZ);
+					break;
+
+				case 0x0102:	/* TEST_J */
+					dev_warn(&dev->dev, "TEST_J\n");
+					usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
+							USB_REQ_SET_FEATURE, USB_RT_PORT,
+							USB_PORT_FEAT_TEST, 0x100, NULL, 0, HZ);
+					break;
+
+				case 0x0103:	/* TEST_K */
+					dev_warn(&dev->dev, "TEST_K\n");
+					usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
+							USB_REQ_SET_FEATURE, USB_RT_PORT,
+							USB_PORT_FEAT_TEST, 0x200, NULL, 0, HZ);
+					break;
+
+				case 0x0104:	/* TEST_PACKET */
+					dev_warn(&dev->dev, "TEST_PACKET\n");
+					usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
+							USB_REQ_SET_FEATURE, USB_RT_PORT,
+							USB_PORT_FEAT_TEST, 0x400, NULL, 0, HZ);
+					break;
+
+				case 0x0105:	/* TEST_FORCE_ENABLE */
+					dev_warn(&dev->dev, "TEST_FORCE_ENABLE\n");
+					usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
+							USB_REQ_SET_FEATURE, USB_RT_PORT,
+							USB_PORT_FEAT_TEST, 0x500, NULL, 0, HZ);
+					break;
+
+				case 0x0106:	/* HS_HOST_PORT_SUSPEND_RESUME */
+					dev_warn(&dev->dev, "HS_HOST_PORT_SUSPEND_RESUME\n");
+					usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
+							USB_REQ_SET_FEATURE, USB_RT_PORT,
+							USB_PORT_FEAT_TEST, 0x600, NULL, 0, 40 * HZ);
+					break;
+
+				case 0x0107:	/* SINGLE_STEP_GET_DEVICE_DESCRIPTOR setup */
+					dev_warn(&dev->dev, "SINGLE_STEP_GET_DEVICE_DESCRIPTOR setup\n");
+					usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
+							USB_REQ_SET_FEATURE, USB_RT_PORT,
+							USB_PORT_FEAT_TEST, 0x700, NULL, 0, 40 * HZ);
+					break;
+
+				case 0x0108:	/* SINGLE_STEP_GET_DEVICE_DESCRIPTOR execute */
+					dev_warn(&dev->dev, "SINGLE_STEP_GET_DEVICE_DESCRIPTOR execute\n");
+					usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
+							USB_REQ_SET_FEATURE, USB_RT_PORT,
+							USB_PORT_FEAT_TEST, 0x800, NULL, 0, 40 * HZ);
+				}
+			}
+		}
+#endif /* DWC_HS_ELECT_TST */
 
 	/* Now that the interfaces are installed, re-enable LPM. */
 	usb_unlocked_enable_lpm(dev);
diff --git a/drivers/usb/core/otg_productlist.h b/drivers/usb/core/otg_productlist.h
index db67df29fb2b..b16e528859a8 100644
--- a/drivers/usb/core/otg_productlist.h
+++ b/drivers/usb/core/otg_productlist.h
@@ -11,33 +11,82 @@
 static struct usb_device_id productlist_table[] = {
 
 /* hubs are optional in OTG, but very handy ... */
+#define CERT_WITHOUT_HUBS
+#if defined(CERT_WITHOUT_HUBS)
+{ USB_DEVICE( 0x0000, 0x0000 ), }, /* Root HUB Only*/
+#else
 { USB_DEVICE_INFO(USB_CLASS_HUB, 0, 0), },
 { USB_DEVICE_INFO(USB_CLASS_HUB, 0, 1), },
+{ USB_DEVICE_INFO(USB_CLASS_HUB, 0, 2), },
+#endif
 
 #ifdef	CONFIG_USB_PRINTER		/* ignoring nonstatic linkage! */
 /* FIXME actually, printers are NOT supposed to use device classes;
  * they're supposed to use interface classes...
  */
-{ USB_DEVICE_INFO(7, 1, 1) },
-{ USB_DEVICE_INFO(7, 1, 2) },
-{ USB_DEVICE_INFO(7, 1, 3) },
+//{ USB_DEVICE_INFO(7, 1, 1) },
+//{ USB_DEVICE_INFO(7, 1, 2) },
+//{ USB_DEVICE_INFO(7, 1, 3) },
 #endif
 
 #ifdef	CONFIG_USB_NET_CDCETHER
 /* Linux-USB CDC Ethernet gadget */
-{ USB_DEVICE(0x0525, 0xa4a1), },
+//{ USB_DEVICE(0x0525, 0xa4a1), },
 /* Linux-USB CDC Ethernet + RNDIS gadget */
-{ USB_DEVICE(0x0525, 0xa4a2), },
+//{ USB_DEVICE(0x0525, 0xa4a2), },
 #endif
 
 #if	IS_ENABLED(CONFIG_USB_TEST)
 /* gadget zero, for testing */
-{ USB_DEVICE(0x0525, 0xa4a0), },
+//{ USB_DEVICE(0x0525, 0xa4a0), },
 #endif
 
+/* OPT Tester */
+{ USB_DEVICE( 0x1a0a, 0x0101 ), }, /* TEST_SE0_NAK */
+{ USB_DEVICE( 0x1a0a, 0x0102 ), }, /* Test_J */
+{ USB_DEVICE( 0x1a0a, 0x0103 ), }, /* Test_K */
+{ USB_DEVICE( 0x1a0a, 0x0104 ), }, /* Test_PACKET */
+{ USB_DEVICE( 0x1a0a, 0x0105 ), }, /* Test_FORCE_ENABLE */
+{ USB_DEVICE( 0x1a0a, 0x0106 ), }, /* HS_PORT_SUSPEND_RESUME  */
+{ USB_DEVICE( 0x1a0a, 0x0107 ), }, /* SINGLE_STEP_GET_DESCRIPTOR setup */
+{ USB_DEVICE( 0x1a0a, 0x0108 ), }, /* SINGLE_STEP_GET_DESCRIPTOR execute */
+
+/* Sony cameras */
+{ USB_DEVICE_VER(0x054c,0x0010,0x0410, 0x0500), },
+
+/* Memory Devices */
+//{ USB_DEVICE( 0x0781, 0x5150 ), }, /* SanDisk */
+//{ USB_DEVICE( 0x05DC, 0x0080 ), }, /* Lexar */
+//{ USB_DEVICE( 0x4146, 0x9281 ), }, /* IOMEGA */
+//{ USB_DEVICE( 0x067b, 0x2507 ), }, /* Hammer 20GB External HD  */
+{ USB_DEVICE( 0x0EA0, 0x2168 ), }, /* Ours Technology Inc. (BUFFALO ClipDrive)*/
+//{ USB_DEVICE( 0x0457, 0x0150 ), }, /* Silicon Integrated Systems Corp. */
+
+/* HP Printers */
+//{ USB_DEVICE( 0x03F0, 0x1102 ), }, /* HP Photosmart 245 */
+//{ USB_DEVICE( 0x03F0, 0x1302 ), }, /* HP Photosmart 370 Series */
+
+/* Speakers */
+//{ USB_DEVICE( 0x0499, 0x3002 ), }, /* YAMAHA YST-MS35D USB Speakers */
+//{ USB_DEVICE( 0x0672, 0x1041 ), }, /* Labtec USB Headset */
+
 { }	/* Terminating entry */
 };
 
+static inline void report_errors(struct usb_device *dev)
+{
+	/* OTG MESSAGE: report errors here, customize to match your product */
+	dev_info(&dev->dev, "device Vendor:%04x Product:%04x is not supported\n",
+		 le16_to_cpu(dev->descriptor.idVendor),
+		 le16_to_cpu(dev->descriptor.idProduct));
+        if (USB_CLASS_HUB == dev->descriptor.bDeviceClass){
+                dev_printk(KERN_CRIT, &dev->dev, "Unsupported Hub Topology\n");
+        } else {
+                dev_printk(KERN_CRIT, &dev->dev, "Attached Device is not Supported\n");
+        }
+}
+
+
 static int is_targeted(struct usb_device *dev)
 {
 	struct usb_device_id	*id = productlist_table;
@@ -87,16 +136,57 @@ static int is_targeted(struct usb_device *dev)
 			continue;
 
 		return 1;
-	}
+		/* NOTE: can't use usb_match_id() since interface caches
+		 * aren't set up yet. this is cut/paste from that code.
+		 */
+		for (id = productlist_table; id->match_flags; id++) {
+#ifdef DEBUG
+			dev_dbg(&dev->dev,
+				"ID: V:%04x P:%04x DC:%04x SC:%04x PR:%04x \n",
+				id->idVendor,
+				id->idProduct,
+				id->bDeviceClass,
+				id->bDeviceSubClass,
+				id->bDeviceProtocol);
+#endif
 
-	/* add other match criteria here ... */
+			if ((id->match_flags & USB_DEVICE_ID_MATCH_VENDOR) &&
+			    id->idVendor != le16_to_cpu(dev->descriptor.idVendor))
+				continue;
 
+			if ((id->match_flags & USB_DEVICE_ID_MATCH_PRODUCT) &&
+			    id->idProduct != le16_to_cpu(dev->descriptor.idProduct))
+				continue;
 
-	/* OTG MESSAGE: report errors here, customize to match your product */
-	dev_err(&dev->dev, "device v%04x p%04x is not supported\n",
-		le16_to_cpu(dev->descriptor.idVendor),
-		le16_to_cpu(dev->descriptor.idProduct));
+			/* No need to test id->bcdDevice_lo != 0, since 0 is never
+			   greater than any unsigned number. */
+			if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_LO) &&
+			    (id->bcdDevice_lo > le16_to_cpu(dev->descriptor.bcdDevice)))
+				continue;
+
+			if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_HI) &&
+			    (id->bcdDevice_hi < le16_to_cpu(dev->descriptor.bcdDevice)))
+				continue;
+
+			if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_CLASS) &&
+			    (id->bDeviceClass != dev->descriptor.bDeviceClass))
+				continue;
+
+			if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_SUBCLASS) &&
+			    (id->bDeviceSubClass != dev->descriptor.bDeviceSubClass))
+				continue;
+
+			if ((id->match_flags & USB_DEVICE_ID_MATCH_DEV_PROTOCOL) &&
+			    (id->bDeviceProtocol != dev->descriptor.bDeviceProtocol))
+				continue;
+
+			return 1;
+		}
+	}
+
+	/* add other match criteria here ... */
 
+	report_errors(dev);
 	return 0;
 }
 
diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c
index 8c3941ecaaf5..a6ef44c42e23 100644
--- a/drivers/usb/dwc2/hcd.c
+++ b/drivers/usb/dwc2/hcd.c
@@ -676,6 +676,19 @@ static void dwc2_hc_init(struct dwc2_hsotg *hsotg, struct dwc2_host_chan *chan)
 		hcchar |= HCCHAR_EPDIR;
 	if (chan->speed == USB_SPEED_LOW)
 		hcchar |= HCCHAR_LSPDDEV;
+
+	/*
+	 * Masquerading Interrupt split transfers as Control puts the transfer
+	 * into the non-periodic handler in the hub. This stops the hub
+	 * dropping complete-split data in the microframe after a CSPLIT
+	 * should have arrived, improving resilience to host IRQ latency.
+	 * Devices are none the wiser - the handshake tokens are the same.
+	 * The fakery is undone in dwc2_hc_n_intr().
+	 */
+	if (chan->ep_is_in && chan->do_split &&
+	    chan->ep_type == USB_ENDPOINT_XFER_INT)
+		chan->ep_type = USB_ENDPOINT_XFER_CONTROL;
+
 	hcchar |= chan->ep_type << HCCHAR_EPTYPE_SHIFT & HCCHAR_EPTYPE_MASK;
 	hcchar |= chan->max_packet << HCCHAR_MPS_SHIFT & HCCHAR_MPS_MASK;
 	dwc2_writel(hsotg, hcchar, HCCHAR(hc_num));
@@ -2338,6 +2351,7 @@ static void dwc2_hc_init_xfer(struct dwc2_hsotg *hsotg,
 			else
 				chan->xfer_buf = urb->setup_packet;
 			chan->xfer_len = 8;
+			chan->max_packet = 8;
 			break;
 
 		case DWC2_CONTROL_DATA:
@@ -2447,7 +2461,7 @@ static void dwc2_free_dma_aligned_buffer(struct urb *urb)
 
 	/* Restore urb->transfer_buffer from the end of the allocated area */
 	memcpy(&stored_xfer_buffer,
-	       PTR_ALIGN(urb->transfer_buffer + urb->transfer_buffer_length,
+	       PTR_ALIGN(urb->transfer_buffer + urb->transfer_buffer_length + DWC2_USB_DMA_ALIGN,
 			 dma_get_cache_alignment()),
 	       sizeof(urb->transfer_buffer));
 
@@ -2470,17 +2484,36 @@ static int dwc2_alloc_dma_aligned_buffer(struct urb *urb, gfp_t mem_flags)
 	void *kmalloc_ptr;
 	size_t kmalloc_size;
 
-	if (urb->num_sgs || urb->sg ||
-	    urb->transfer_buffer_length == 0 ||
+	if (urb->num_sgs || urb->sg || urb->transfer_buffer_length == 0)
+		return 0;
+
+	/*
+	 * Hardware bug: the core will only do DMA writes of full words
+	 * in length, and DMA buffers must start at a word boundary.
+	 * TODO: is this limited to BCM2835 and friends, or other core variants?
+	 */
+	if (!(usb_urb_dir_in(urb) && (urb->transfer_buffer_length & (DWC2_USB_DMA_ALIGN - 1))) &&
 	    !((uintptr_t)urb->transfer_buffer & (DWC2_USB_DMA_ALIGN - 1)))
 		return 0;
 
+	/*
+	 * If the URB already has a DMA mapping, this alignment mechanism won't
+	 * work - the replacement buffer won't be used by the core, as the HCD layer
+	 * skips mapping. Mappings have the granularity of a page, so it's unlikely that the
+	 * DMA length bug will cause data trampling. In any case, warn if there's a driver
+	 * submitting unaligned mapped buffers.
+	 */
+	if (urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) {
+		if (urb->transfer_dma & (DWC2_USB_DMA_ALIGN - 1))
+			WARN_ONCE(1, "Unaligned DMA-mapped buffer");
+		return 0;
+	}
 	/*
 	 * Allocate a buffer with enough padding for original transfer_buffer
 	 * pointer. This allocation is guaranteed to be aligned properly for
 	 * DMA
 	 */
-	kmalloc_size = urb->transfer_buffer_length +
+	kmalloc_size = urb->transfer_buffer_length + DWC2_USB_DMA_ALIGN +
 		(dma_get_cache_alignment() - 1) +
 		sizeof(urb->transfer_buffer);
 
@@ -2492,7 +2525,7 @@ static int dwc2_alloc_dma_aligned_buffer(struct urb *urb, gfp_t mem_flags)
 	 * Position value of original urb->transfer_buffer pointer to the end
 	 * of allocation for later referencing
 	 */
-	memcpy(PTR_ALIGN(kmalloc_ptr + urb->transfer_buffer_length,
+	memcpy(PTR_ALIGN(kmalloc_ptr + urb->transfer_buffer_length + DWC2_USB_DMA_ALIGN,
 			 dma_get_cache_alignment()),
 	       &urb->transfer_buffer, sizeof(urb->transfer_buffer));
 
@@ -2621,6 +2654,10 @@ static int dwc2_assign_and_init_hc(struct dwc2_hsotg *hsotg, struct dwc2_qh *qh)
 	else
 		chan->do_split = 0;
 
+	/* Limit split IN transfers to the remaining buffer space */
+	if (qh->do_split && chan->ep_is_in)
+		chan->max_packet = min_t(u32, chan->max_packet, chan->xfer_len);
+
 	/* Set the transfer attributes */
 	dwc2_hc_init_xfer(hsotg, chan, qtd);
 
@@ -4526,8 +4563,13 @@ static int _dwc2_hcd_resume(struct usb_hcd *hcd)
 static int _dwc2_hcd_get_frame_number(struct usb_hcd *hcd)
 {
 	struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd);
+	u32 hprt0 = dwc2_readl(hsotg, HPRT0);
 
-	return dwc2_hcd_get_frame_number(hsotg);
+	/* HS root port counts microframes, not frames */
+	if ((hprt0 & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT == HPRT0_SPD_HIGH_SPEED)
+		return dwc2_hcd_get_frame_number(hsotg) >> 3;
+	else
+		return dwc2_hcd_get_frame_number(hsotg);
 }
 
 static void dwc2_dump_urb_info(struct usb_hcd *hcd, struct urb *urb,
diff --git a/drivers/usb/dwc2/hcd_intr.c b/drivers/usb/dwc2/hcd_intr.c
index 5c7538d498dd..60622ef76b6b 100644
--- a/drivers/usb/dwc2/hcd_intr.c
+++ b/drivers/usb/dwc2/hcd_intr.c
@@ -2048,6 +2048,9 @@ static void dwc2_hc_n_intr(struct dwc2_hsotg *hsotg, int chnum)
 
 	chan->hcint = hcintraw;
 
+	/* Un-masquerade the transfer type */
+	if (chan->do_split)
+		chan->ep_type = chan->qh->ep_type;
 	/*
 	 * If the channel was halted due to a dequeue, the qtd list might
 	 * be empty or at least the first entry will not be the active qtd.
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 526b6a1fa354..c9dbf859be21 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -1322,6 +1322,24 @@ static void dwc3_config_threshold(struct dwc3 *dwc)
 	}
 }
 
+static void dwc3_set_axi_pipe_limit(struct dwc3 *dwc)
+{
+	struct device *dev = dwc->dev;
+	u32 cfg;
+
+	if (!dwc->axi_pipe_limit)
+		return;
+	if (dwc->axi_pipe_limit > 16) {
+		dev_err(dev, "Invalid axi_pipe_limit property\n");
+		return;
+	}
+	cfg = dwc3_readl(dwc->regs, DWC3_GSBUSCFG1);
+	cfg &= ~DWC3_GSBUSCFG1_PIPETRANSLIMIT(15);
+	cfg |= DWC3_GSBUSCFG1_PIPETRANSLIMIT(dwc->axi_pipe_limit - 1);
+
+	dwc3_writel(dwc->regs, DWC3_GSBUSCFG1, cfg);
+}
+
 /**
  * dwc3_core_init - Low-level initialization of DWC3 Core
  * @dwc: Pointer to our controller context structure
@@ -1389,6 +1407,8 @@ static int dwc3_core_init(struct dwc3 *dwc)
 
 	dwc3_config_soc_bus(dwc);
 
+	dwc3_set_axi_pipe_limit(dwc);
+
 	ret = dwc3_phy_power_on(dwc);
 	if (ret)
 		goto err_exit_phy;
@@ -1462,12 +1482,21 @@ static int dwc3_core_init(struct dwc3 *dwc)
 		if (dwc->dis_tx_ipgap_linecheck_quirk)
 			reg |= DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS;
 
+		if (dwc->enh_nak_fs_quirk)
+			reg |= DWC3_GUCTL1_NAK_PER_ENH_FS;
+
+		if (dwc->enh_nak_hs_quirk)
+			reg |= DWC3_GUCTL1_NAK_PER_ENH_HS;
+
 		if (dwc->parkmode_disable_ss_quirk)
 			reg |= DWC3_GUCTL1_PARKMODE_DISABLE_SS;
 
 		if (dwc->parkmode_disable_hs_quirk)
 			reg |= DWC3_GUCTL1_PARKMODE_DISABLE_HS;
 
+		if (dwc->parkmode_disable_fsls_quirk)
+			reg |= DWC3_GUCTL1_PARKMODE_DISABLE_FSLS;
+
 		if (DWC3_VER_IS_WITHIN(DWC3, 290A, ANY)) {
 			if (dwc->maximum_speed == USB_SPEED_FULL ||
 			    dwc->maximum_speed == USB_SPEED_HIGH)
@@ -1493,6 +1522,24 @@ static int dwc3_core_init(struct dwc3 *dwc)
 		dwc3_writel(dwc->regs, DWC3_LLUCTL, reg);
 	}
 
+	if (DWC3_IP_IS(DWC3) && dwc->dr_mode == USB_DR_MODE_HOST) {
+		u8 tx_thr_num = dwc->tx_thr_num_pkt_prd;
+		u8 tx_maxburst = dwc->tx_max_burst_prd;
+
+		if (tx_thr_num && tx_maxburst) {
+			reg = dwc3_readl(dwc->regs, DWC3_GTXTHRCFG);
+			reg |= DWC3_GTXTHRCFG_PKTCNTSEL;
+
+			reg &= ~DWC3_GTXTHRCFG_TXPKTCNT(~0);
+			reg |= DWC3_GTXTHRCFG_TXPKTCNT(tx_thr_num);
+
+			reg &= ~DWC3_GTXTHRCFG_MAXTXBURSTSIZE(~0);
+			reg |= DWC3_GTXTHRCFG_MAXTXBURSTSIZE(tx_maxburst);
+
+			dwc3_writel(dwc->regs, DWC3_GTXTHRCFG, reg);
+		}
+	}
+
 	return 0;
 
 err_power_off_phy:
@@ -1678,6 +1725,7 @@ static void dwc3_get_properties(struct dwc3 *dwc)
 	u8			tx_thr_num_pkt_prd = 0;
 	u8			tx_max_burst_prd = 0;
 	u8			tx_fifo_resize_max_num;
+	u8			axi_pipe_limit;
 
 	/* default to highest possible threshold */
 	lpm_nyet_threshold = 0xf;
@@ -1698,6 +1746,9 @@ static void dwc3_get_properties(struct dwc3 *dwc)
 	 */
 	tx_fifo_resize_max_num = 6;
 
+	/* Default to 0 (don't override hardware defaults) */
+	axi_pipe_limit = 0;
+
 	dwc->maximum_speed = usb_get_maximum_speed(dev);
 	dwc->max_ssp_rate = usb_get_maximum_ssp_rate(dev);
 	dwc->dr_mode = usb_get_dr_mode(dev);
@@ -1788,10 +1839,16 @@ static void dwc3_get_properties(struct dwc3 *dwc)
 				"snps,resume-hs-terminations");
 	dwc->ulpi_ext_vbus_drv = device_property_read_bool(dev,
 				"snps,ulpi-ext-vbus-drv");
+	dwc->enh_nak_fs_quirk = device_property_read_bool(dev,
+				"snps,enhanced-nak-fs-quirk");
+	dwc->enh_nak_hs_quirk = device_property_read_bool(dev,
+				"snps,enhanced-nak-hs-quirk");
 	dwc->parkmode_disable_ss_quirk = device_property_read_bool(dev,
 				"snps,parkmode-disable-ss-quirk");
 	dwc->parkmode_disable_hs_quirk = device_property_read_bool(dev,
 				"snps,parkmode-disable-hs-quirk");
+	dwc->parkmode_disable_fsls_quirk = device_property_read_bool(dev,
+				"snps,parkmode-disable-fsls-quirk");
 	dwc->gfladj_refclk_lpm_sel = device_property_read_bool(dev,
 				"snps,gfladj-refclk-lpm-sel-quirk");
 
@@ -1812,6 +1869,9 @@ static void dwc3_get_properties(struct dwc3 *dwc)
 	dwc->dis_split_quirk = device_property_read_bool(dev,
 				"snps,dis-split-quirk");
 
+	device_property_read_u8(dev, "snps,axi-pipe-limit",
+				   &axi_pipe_limit);
+
 	dwc->lpm_nyet_threshold = lpm_nyet_threshold;
 	dwc->tx_de_emphasis = tx_de_emphasis;
 
@@ -1829,6 +1889,8 @@ static void dwc3_get_properties(struct dwc3 *dwc)
 	dwc->tx_thr_num_pkt_prd = tx_thr_num_pkt_prd;
 	dwc->tx_max_burst_prd = tx_max_burst_prd;
 
+	dwc->axi_pipe_limit = axi_pipe_limit;
+
 	dwc->tx_fifo_resize_max_num = tx_fifo_resize_max_num;
 }
 
@@ -2202,6 +2264,12 @@ static int dwc3_probe(struct platform_device *pdev)
 	if (IS_ERR(dwc->usb_psy))
 		return dev_err_probe(dev, PTR_ERR(dwc->usb_psy), "couldn't get usb power supply\n");
 
+	if (!dwc->sysdev_is_parent) {
+		ret = dma_set_mask_and_coherent(dwc->sysdev, DMA_BIT_MASK(64));
+		if (ret)
+			return ret;
+	}
+
 	dwc->reset = devm_reset_control_array_get_optional_shared(dev);
 	if (IS_ERR(dwc->reset)) {
 		ret = PTR_ERR(dwc->reset);
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index eb8b8f3e5216..dd3a5feb429e 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -198,6 +198,9 @@
 #define DWC3_GSBUSCFG0_REQINFO(n)	(((n) & 0xffff) << 16)
 #define DWC3_GSBUSCFG0_REQINFO_UNSPECIFIED	0xffffffff
 
+/* Global SoC Bus Configuration Register 1 */
+#define DWC3_GSBUSCFG1_PIPETRANSLIMIT(n)	(((n) & 0xf) << 8)
+
 /* Global Debug LSP MUX Select */
 #define DWC3_GDBGLSPMUX_ENDBC		BIT(15)	/* Host only */
 #define DWC3_GDBGLSPMUX_HOSTSELECT(n)	((n) & 0x3fff)
@@ -279,8 +282,11 @@
 #define DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS	BIT(28)
 #define DWC3_GUCTL1_DEV_FORCE_20_CLK_FOR_30_CLK	BIT(26)
 #define DWC3_GUCTL1_DEV_L1_EXIT_BY_HW		BIT(24)
+#define DWC3_GUCTL1_NAK_PER_ENH_FS		BIT(19)
+#define DWC3_GUCTL1_NAK_PER_ENH_HS		BIT(18)
 #define DWC3_GUCTL1_PARKMODE_DISABLE_SS		BIT(17)
 #define DWC3_GUCTL1_PARKMODE_DISABLE_HS		BIT(16)
+#define DWC3_GUCTL1_PARKMODE_DISABLE_FSLS	BIT(15)
 #define DWC3_GUCTL1_RESUME_OPMODE_HS_HOST	BIT(10)
 
 /* Global Status Register */
@@ -1089,6 +1095,7 @@ struct dwc3_scratchpad_array {
  * @tx_max_burst_prd: max periodic ESS transmit burst size
  * @tx_fifo_resize_max_num: max number of fifos allocated during txfifo resize
  * @clear_stall_protocol: endpoint number that requires a delayed status phase
+ * @axi_max_pipe: set to override the maximum number of pipelined AXI transfers
  * @hsphy_interface: "utmi" or "ulpi"
  * @connected: true when we're connected to a host, false otherwise
  * @softconnect: true when gadget connect is called, false when disconnect runs
@@ -1140,10 +1147,14 @@ struct dwc3_scratchpad_array {
  *			generation after resume from suspend.
  * @ulpi_ext_vbus_drv: Set to confiure the upli chip to drives CPEN pin
  *			VBUS with an external supply.
- * @parkmode_disable_ss_quirk: set if we need to disable all SuperSpeed
- *			instances in park mode.
- * @parkmode_disable_hs_quirk: set if we need to disable all HishSpeed
- *			instances in park mode.
+ * @enh_nak_fs_quirk: Set to schedule more handshakes to Async FS endpoints.
+ * @enh_nak_hs_quirk: Set to schedule more handshakes to Async HS endpoints.
+ * @parkmode_disable_ss_quirk: If set, disable park mode feature for all
+ *			Superspeed instances.
+ * @parkmode_disable_hs_quirk: If set, disable park mode feature for all
+ *			Highspeed instances.
+ * @parkmode_disable_fsls_quirk: If set, disable park mode feature for all
+ *			Full/Lowspeed instances.
  * @gfladj_refclk_lpm_sel: set if we need to enable SOF/ITP counter
  *                          running based on ref_clk
  * @tx_de_emphasis_quirk: set if we enable Tx de-emphasis quirk
@@ -1342,6 +1353,7 @@ struct dwc3 {
 	u8			tx_max_burst_prd;
 	u8			tx_fifo_resize_max_num;
 	u8			clear_stall_protocol;
+	u8			axi_pipe_limit;
 
 	const char		*hsphy_interface;
 
@@ -1383,8 +1395,11 @@ struct dwc3 {
 	unsigned		dis_tx_ipgap_linecheck_quirk:1;
 	unsigned		resume_hs_terminations:1;
 	unsigned		ulpi_ext_vbus_drv:1;
+	unsigned		enh_nak_fs_quirk:1;
+	unsigned		enh_nak_hs_quirk:1;
 	unsigned		parkmode_disable_ss_quirk:1;
 	unsigned		parkmode_disable_hs_quirk:1;
+	unsigned		parkmode_disable_fsls_quirk:1;
 	unsigned		gfladj_refclk_lpm_sel:1;
 
 	unsigned		tx_de_emphasis_quirk:1;
diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c
index 78e391763d72..31e1bd6ec988 100644
--- a/drivers/usb/dwc3/host.c
+++ b/drivers/usb/dwc3/host.c
@@ -129,10 +129,12 @@ static int dwc3_host_get_irq(struct dwc3 *dwc)
 
 int dwc3_host_init(struct dwc3 *dwc)
 {
+	struct platform_device	*pdev = to_platform_device(dwc->dev);
 	struct property_entry	props[6];
 	struct platform_device	*xhci;
 	int			ret, irq;
 	int			prop_idx = 0;
+	int			id;
 
 	/*
 	 * Some platforms need to power off all Root hub ports immediately after DWC3 set to host
@@ -144,7 +146,12 @@ int dwc3_host_init(struct dwc3 *dwc)
 	if (irq < 0)
 		return irq;
 
-	xhci = platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO);
+	id = of_alias_get_id(pdev->dev.of_node, "usb");
+	if (id >= 0)
+		xhci = platform_device_alloc("xhci-hcd", id);
+	else
+		xhci = platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO);
+
 	if (!xhci) {
 		dev_err(dwc->dev, "couldn't allocate xHCI device\n");
 		return -ENOMEM;
diff --git a/drivers/usb/gadget/file_storage.c b/drivers/usb/gadget/file_storage.c
new file mode 100644
index 000000000000..a896d73f7a93
--- /dev/null
+++ b/drivers/usb/gadget/file_storage.c
@@ -0,0 +1,3676 @@
+/*
+ * file_storage.c -- File-backed USB Storage Gadget, for USB development
+ *
+ * Copyright (C) 2003-2008 Alan Stern
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The names of the above-listed copyright holders may not be used
+ *    to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * ALTERNATIVELY, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") as published by the Free Software
+ * Foundation, either version 2 of that License or (at your option) any
+ * later version.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+/*
+ * The File-backed Storage Gadget acts as a USB Mass Storage device,
+ * appearing to the host as a disk drive or as a CD-ROM drive.  In addition
+ * to providing an example of a genuinely useful gadget driver for a USB
+ * device, it also illustrates a technique of double-buffering for increased
+ * throughput.  Last but not least, it gives an easy way to probe the
+ * behavior of the Mass Storage drivers in a USB host.
+ *
+ * Backing storage is provided by a regular file or a block device, specified
+ * by the "file" module parameter.  Access can be limited to read-only by
+ * setting the optional "ro" module parameter.  (For CD-ROM emulation,
+ * access is always read-only.)  The gadget will indicate that it has
+ * removable media if the optional "removable" module parameter is set.
+ *
+ * The gadget supports the Control-Bulk (CB), Control-Bulk-Interrupt (CBI),
+ * and Bulk-Only (also known as Bulk-Bulk-Bulk or BBB) transports, selected
+ * by the optional "transport" module parameter.  It also supports the
+ * following protocols: RBC (0x01), ATAPI or SFF-8020i (0x02), QIC-157 (0c03),
+ * UFI (0x04), SFF-8070i (0x05), and transparent SCSI (0x06), selected by
+ * the optional "protocol" module parameter.  In addition, the default
+ * Vendor ID, Product ID, release number and serial number can be overridden.
+ *
+ * There is support for multiple logical units (LUNs), each of which has
+ * its own backing file.  The number of LUNs can be set using the optional
+ * "luns" module parameter (anywhere from 1 to 8), and the corresponding
+ * files are specified using comma-separated lists for "file" and "ro".
+ * The default number of LUNs is taken from the number of "file" elements;
+ * it is 1 if "file" is not given.  If "removable" is not set then a backing
+ * file must be specified for each LUN.  If it is set, then an unspecified
+ * or empty backing filename means the LUN's medium is not loaded.  Ideally
+ * each LUN would be settable independently as a disk drive or a CD-ROM
+ * drive, but currently all LUNs have to be the same type.  The CD-ROM
+ * emulation includes a single data track and no audio tracks; hence there
+ * need be only one backing file per LUN.
+ *
+ * Requirements are modest; only a bulk-in and a bulk-out endpoint are
+ * needed (an interrupt-out endpoint is also needed for CBI).  The memory
+ * requirement amounts to two 16K buffers, size configurable by a parameter.
+ * Support is included for both full-speed and high-speed operation.
+ *
+ * Note that the driver is slightly non-portable in that it assumes a
+ * single memory/DMA buffer will be useable for bulk-in, bulk-out, and
+ * interrupt-in endpoints.  With most device controllers this isn't an
+ * issue, but there may be some with hardware restrictions that prevent
+ * a buffer from being used by more than one endpoint.
+ *
+ * Module options:
+ *
+ *	file=filename[,filename...]
+ *				Required if "removable" is not set, names of
+ *					the files or block devices used for
+ *					backing storage
+ *	serial=HHHH...		Required serial number (string of hex chars)
+ *	ro=b[,b...]		Default false, booleans for read-only access
+ *	removable		Default false, boolean for removable media
+ *	luns=N			Default N = number of filenames, number of
+ *					LUNs to support
+ *	nofua=b[,b...]		Default false, booleans for ignore FUA flag
+ *					in SCSI WRITE(10,12) commands
+ *	stall			Default determined according to the type of
+ *					USB device controller (usually true),
+ *					boolean to permit the driver to halt
+ *					bulk endpoints
+ *	cdrom			Default false, boolean for whether to emulate
+ *					a CD-ROM drive
+ *	transport=XXX		Default BBB, transport name (CB, CBI, or BBB)
+ *	protocol=YYY		Default SCSI, protocol name (RBC, 8020 or
+ *					ATAPI, QIC, UFI, 8070, or SCSI;
+ *					also 1 - 6)
+ *	vendor=0xVVVV		Default 0x0525 (NetChip), USB Vendor ID
+ *	product=0xPPPP		Default 0xa4a5 (FSG), USB Product ID
+ *	release=0xRRRR		Override the USB release number (bcdDevice)
+ *	buflen=N		Default N=16384, buffer size used (will be
+ *					rounded down to a multiple of
+ *					PAGE_CACHE_SIZE)
+ *
+ * If CONFIG_USB_FILE_STORAGE_TEST is not set, only the "file", "serial", "ro",
+ * "removable", "luns", "nofua", "stall", and "cdrom" options are available;
+ * default values are used for everything else.
+ *
+ * The pathnames of the backing files and the ro settings are available in
+ * the attribute files "file", "nofua", and "ro" in the lun<n> subdirectory of
+ * the gadget's sysfs directory.  If the "removable" option is set, writing to
+ * these files will simulate ejecting/loading the medium (writing an empty
+ * line means eject) and adjusting a write-enable tab.  Changes to the ro
+ * setting are not allowed when the medium is loaded or if CD-ROM emulation
+ * is being used.
+ *
+ * This gadget driver is heavily based on "Gadget Zero" by David Brownell.
+ * The driver's SCSI command interface was based on the "Information
+ * technology - Small Computer System Interface - 2" document from
+ * X3T9.2 Project 375D, Revision 10L, 7-SEP-93, available at
+ * <http://www.t10.org/ftp/t10/drafts/s2/s2-r10l.pdf>.  The single exception
+ * is opcode 0x23 (READ FORMAT CAPACITIES), which was based on the
+ * "Universal Serial Bus Mass Storage Class UFI Command Specification"
+ * document, Revision 1.0, December 14, 1998, available at
+ * <http://www.usb.org/developers/devclass_docs/usbmass-ufi10.pdf>.
+ */
+
+
+/*
+ *				Driver Design
+ *
+ * The FSG driver is fairly straightforward.  There is a main kernel
+ * thread that handles most of the work.  Interrupt routines field
+ * callbacks from the controller driver: bulk- and interrupt-request
+ * completion notifications, endpoint-0 events, and disconnect events.
+ * Completion events are passed to the main thread by wakeup calls.  Many
+ * ep0 requests are handled at interrupt time, but SetInterface,
+ * SetConfiguration, and device reset requests are forwarded to the
+ * thread in the form of "exceptions" using SIGUSR1 signals (since they
+ * should interrupt any ongoing file I/O operations).
+ *
+ * The thread's main routine implements the standard command/data/status
+ * parts of a SCSI interaction.  It and its subroutines are full of tests
+ * for pending signals/exceptions -- all this polling is necessary since
+ * the kernel has no setjmp/longjmp equivalents.  (Maybe this is an
+ * indication that the driver really wants to be running in userspace.)
+ * An important point is that so long as the thread is alive it keeps an
+ * open reference to the backing file.  This will prevent unmounting
+ * the backing file's underlying filesystem and could cause problems
+ * during system shutdown, for example.  To prevent such problems, the
+ * thread catches INT, TERM, and KILL signals and converts them into
+ * an EXIT exception.
+ *
+ * In normal operation the main thread is started during the gadget's
+ * fsg_bind() callback and stopped during fsg_unbind().  But it can also
+ * exit when it receives a signal, and there's no point leaving the
+ * gadget running when the thread is dead.  So just before the thread
+ * exits, it deregisters the gadget driver.  This makes things a little
+ * tricky: The driver is deregistered at two places, and the exiting
+ * thread can indirectly call fsg_unbind() which in turn can tell the
+ * thread to exit.  The first problem is resolved through the use of the
+ * REGISTERED atomic bitflag; the driver will only be deregistered once.
+ * The second problem is resolved by having fsg_unbind() check
+ * fsg->state; it won't try to stop the thread if the state is already
+ * FSG_STATE_TERMINATED.
+ *
+ * To provide maximum throughput, the driver uses a circular pipeline of
+ * buffer heads (struct fsg_buffhd).  In principle the pipeline can be
+ * arbitrarily long; in practice the benefits don't justify having more
+ * than 2 stages (i.e., double buffering).  But it helps to think of the
+ * pipeline as being a long one.  Each buffer head contains a bulk-in and
+ * a bulk-out request pointer (since the buffer can be used for both
+ * output and input -- directions always are given from the host's
+ * point of view) as well as a pointer to the buffer and various state
+ * variables.
+ *
+ * Use of the pipeline follows a simple protocol.  There is a variable
+ * (fsg->next_buffhd_to_fill) that points to the next buffer head to use.
+ * At any time that buffer head may still be in use from an earlier
+ * request, so each buffer head has a state variable indicating whether
+ * it is EMPTY, FULL, or BUSY.  Typical use involves waiting for the
+ * buffer head to be EMPTY, filling the buffer either by file I/O or by
+ * USB I/O (during which the buffer head is BUSY), and marking the buffer
+ * head FULL when the I/O is complete.  Then the buffer will be emptied
+ * (again possibly by USB I/O, during which it is marked BUSY) and
+ * finally marked EMPTY again (possibly by a completion routine).
+ *
+ * A module parameter tells the driver to avoid stalling the bulk
+ * endpoints wherever the transport specification allows.  This is
+ * necessary for some UDCs like the SuperH, which cannot reliably clear a
+ * halt on a bulk endpoint.  However, under certain circumstances the
+ * Bulk-only specification requires a stall.  In such cases the driver
+ * will halt the endpoint and set a flag indicating that it should clear
+ * the halt in software during the next device reset.  Hopefully this
+ * will permit everything to work correctly.  Furthermore, although the
+ * specification allows the bulk-out endpoint to halt when the host sends
+ * too much data, implementing this would cause an unavoidable race.
+ * The driver will always use the "no-stall" approach for OUT transfers.
+ *
+ * One subtle point concerns sending status-stage responses for ep0
+ * requests.  Some of these requests, such as device reset, can involve
+ * interrupting an ongoing file I/O operation, which might take an
+ * arbitrarily long time.  During that delay the host might give up on
+ * the original ep0 request and issue a new one.  When that happens the
+ * driver should not notify the host about completion of the original
+ * request, as the host will no longer be waiting for it.  So the driver
+ * assigns to each ep0 request a unique tag, and it keeps track of the
+ * tag value of the request associated with a long-running exception
+ * (device-reset, interface-change, or configuration-change).  When the
+ * exception handler is finished, the status-stage response is submitted
+ * only if the current ep0 request tag is equal to the exception request
+ * tag.  Thus only the most recently received ep0 request will get a
+ * status-stage response.
+ *
+ * Warning: This driver source file is too long.  It ought to be split up
+ * into a header file plus about 3 separate .c files, to handle the details
+ * of the Gadget, USB Mass Storage, and SCSI protocols.
+ */
+
+
+/* #define VERBOSE_DEBUG */
+/* #define DUMP_MSGS */
+
+
+#include <linux/blkdev.h>
+#include <linux/completion.h>
+#include <linux/dcache.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/fcntl.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/kref.h>
+#include <linux/kthread.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/rwsem.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/freezer.h>
+#include <linux/utsname.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+
+#include "gadget_chips.h"
+
+
+
+/*
+ * Kbuild is not very cooperative with respect to linking separately
+ * compiled library objects into one module.  So for now we won't use
+ * separate compilation ... ensuring init/exit sections work to shrink
+ * the runtime footprint, and giving us at least some parts of what
+ * a "gcc --combine ... part1.c part2.c part3.c ... " build would.
+ */
+#include "usbstring.c"
+#include "config.c"
+#include "epautoconf.c"
+
+/*-------------------------------------------------------------------------*/
+
+#define DRIVER_DESC		"File-backed Storage Gadget"
+#define DRIVER_NAME		"g_file_storage"
+#define DRIVER_VERSION		"1 September 2010"
+
+static       char fsg_string_manufacturer[64];
+static const char fsg_string_product[] = DRIVER_DESC;
+static const char fsg_string_config[] = "Self-powered";
+static const char fsg_string_interface[] = "Mass Storage";
+
+
+#include "storage_common.c"
+
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR("Alan Stern");
+MODULE_LICENSE("Dual BSD/GPL");
+
+/*
+ * This driver assumes self-powered hardware and has no way for users to
+ * trigger remote wakeup.  It uses autoconfiguration to select endpoints
+ * and endpoint addresses.
+ */
+
+
+/*-------------------------------------------------------------------------*/
+
+
+/* Encapsulate the module parameter settings */
+
+static struct {
+	char		*file[FSG_MAX_LUNS];
+	char		*serial;
+	bool		ro[FSG_MAX_LUNS];
+	bool		nofua[FSG_MAX_LUNS];
+	unsigned int	num_filenames;
+	unsigned int	num_ros;
+	unsigned int	num_nofuas;
+	unsigned int	nluns;
+
+	bool		removable;
+	bool		can_stall;
+	bool		cdrom;
+
+	char		*transport_parm;
+	char		*protocol_parm;
+	unsigned short	vendor;
+	unsigned short	product;
+	unsigned short	release;
+	unsigned int	buflen;
+
+	int		transport_type;
+	char		*transport_name;
+	int		protocol_type;
+	char		*protocol_name;
+
+} mod_data = {					// Default values
+	.transport_parm		= "BBB",
+	.protocol_parm		= "SCSI",
+	.removable		= 0,
+	.can_stall		= 1,
+	.cdrom			= 0,
+	.vendor			= FSG_VENDOR_ID,
+	.product		= FSG_PRODUCT_ID,
+	.release		= 0xffff,	// Use controller chip type
+	.buflen			= 16384,
+	};
+
+
+module_param_array_named(file, mod_data.file, charp, &mod_data.num_filenames,
+		S_IRUGO);
+MODULE_PARM_DESC(file, "names of backing files or devices");
+
+module_param_named(serial, mod_data.serial, charp, S_IRUGO);
+MODULE_PARM_DESC(serial, "USB serial number");
+
+module_param_array_named(ro, mod_data.ro, bool, &mod_data.num_ros, S_IRUGO);
+MODULE_PARM_DESC(ro, "true to force read-only");
+
+module_param_array_named(nofua, mod_data.nofua, bool, &mod_data.num_nofuas,
+		S_IRUGO);
+MODULE_PARM_DESC(nofua, "true to ignore SCSI WRITE(10,12) FUA bit");
+
+module_param_named(luns, mod_data.nluns, uint, S_IRUGO);
+MODULE_PARM_DESC(luns, "number of LUNs");
+
+module_param_named(removable, mod_data.removable, bool, S_IRUGO);
+MODULE_PARM_DESC(removable, "true to simulate removable media");
+
+module_param_named(stall, mod_data.can_stall, bool, S_IRUGO);
+MODULE_PARM_DESC(stall, "false to prevent bulk stalls");
+
+module_param_named(cdrom, mod_data.cdrom, bool, S_IRUGO);
+MODULE_PARM_DESC(cdrom, "true to emulate cdrom instead of disk");
+
+/* In the non-TEST version, only the module parameters listed above
+ * are available. */
+#ifdef CONFIG_USB_FILE_STORAGE_TEST
+
+module_param_named(transport, mod_data.transport_parm, charp, S_IRUGO);
+MODULE_PARM_DESC(transport, "type of transport (BBB, CBI, or CB)");
+
+module_param_named(protocol, mod_data.protocol_parm, charp, S_IRUGO);
+MODULE_PARM_DESC(protocol, "type of protocol (RBC, 8020, QIC, UFI, "
+		"8070, or SCSI)");
+
+module_param_named(vendor, mod_data.vendor, ushort, S_IRUGO);
+MODULE_PARM_DESC(vendor, "USB Vendor ID");
+
+module_param_named(product, mod_data.product, ushort, S_IRUGO);
+MODULE_PARM_DESC(product, "USB Product ID");
+
+module_param_named(release, mod_data.release, ushort, S_IRUGO);
+MODULE_PARM_DESC(release, "USB release number");
+
+module_param_named(buflen, mod_data.buflen, uint, S_IRUGO);
+MODULE_PARM_DESC(buflen, "I/O buffer size");
+
+#endif /* CONFIG_USB_FILE_STORAGE_TEST */
+
+
+/*
+ * These definitions will permit the compiler to avoid generating code for
+ * parts of the driver that aren't used in the non-TEST version.  Even gcc
+ * can recognize when a test of a constant expression yields a dead code
+ * path.
+ */
+
+#ifdef CONFIG_USB_FILE_STORAGE_TEST
+
+#define transport_is_bbb()	(mod_data.transport_type == USB_PR_BULK)
+#define transport_is_cbi()	(mod_data.transport_type == USB_PR_CBI)
+#define protocol_is_scsi()	(mod_data.protocol_type == USB_SC_SCSI)
+
+#else
+
+#define transport_is_bbb()	1
+#define transport_is_cbi()	0
+#define protocol_is_scsi()	1
+
+#endif /* CONFIG_USB_FILE_STORAGE_TEST */
+
+
+/*-------------------------------------------------------------------------*/
+
+
+struct fsg_dev {
+	/* lock protects: state, all the req_busy's, and cbbuf_cmnd */
+	spinlock_t		lock;
+	struct usb_gadget	*gadget;
+
+	/* filesem protects: backing files in use */
+	struct rw_semaphore	filesem;
+
+	/* reference counting: wait until all LUNs are released */
+	struct kref		ref;
+
+	struct usb_ep		*ep0;		// Handy copy of gadget->ep0
+	struct usb_request	*ep0req;	// For control responses
+	unsigned int		ep0_req_tag;
+	const char		*ep0req_name;
+
+	struct usb_request	*intreq;	// For interrupt responses
+	int			intreq_busy;
+	struct fsg_buffhd	*intr_buffhd;
+
+	unsigned int		bulk_out_maxpacket;
+	enum fsg_state		state;		// For exception handling
+	unsigned int		exception_req_tag;
+
+	u8			config, new_config;
+
+	unsigned int		running : 1;
+	unsigned int		bulk_in_enabled : 1;
+	unsigned int		bulk_out_enabled : 1;
+	unsigned int		intr_in_enabled : 1;
+	unsigned int		phase_error : 1;
+	unsigned int		short_packet_received : 1;
+	unsigned int		bad_lun_okay : 1;
+
+	unsigned long		atomic_bitflags;
+#define REGISTERED		0
+#define IGNORE_BULK_OUT		1
+#define SUSPENDED		2
+
+	struct usb_ep		*bulk_in;
+	struct usb_ep		*bulk_out;
+	struct usb_ep		*intr_in;
+
+	struct fsg_buffhd	*next_buffhd_to_fill;
+	struct fsg_buffhd	*next_buffhd_to_drain;
+
+	int			thread_wakeup_needed;
+	struct completion	thread_notifier;
+	struct task_struct	*thread_task;
+
+	int			cmnd_size;
+	u8			cmnd[MAX_COMMAND_SIZE];
+	enum data_direction	data_dir;
+	u32			data_size;
+	u32			data_size_from_cmnd;
+	u32			tag;
+	unsigned int		lun;
+	u32			residue;
+	u32			usb_amount_left;
+
+	/* The CB protocol offers no way for a host to know when a command
+	 * has completed.  As a result the next command may arrive early,
+	 * and we will still have to handle it.  For that reason we need
+	 * a buffer to store new commands when using CB (or CBI, which
+	 * does not oblige a host to wait for command completion either). */
+	int			cbbuf_cmnd_size;
+	u8			cbbuf_cmnd[MAX_COMMAND_SIZE];
+
+	unsigned int		nluns;
+	struct fsg_lun		*luns;
+	struct fsg_lun		*curlun;
+	/* Must be the last entry */
+	struct fsg_buffhd	buffhds[];
+};
+
+typedef void (*fsg_routine_t)(struct fsg_dev *);
+
+static int exception_in_progress(struct fsg_dev *fsg)
+{
+	return (fsg->state > FSG_STATE_IDLE);
+}
+
+/* Make bulk-out requests be divisible by the maxpacket size */
+static void set_bulk_out_req_length(struct fsg_dev *fsg,
+		struct fsg_buffhd *bh, unsigned int length)
+{
+	unsigned int	rem;
+
+	bh->bulk_out_intended_length = length;
+	rem = length % fsg->bulk_out_maxpacket;
+	if (rem > 0)
+		length += fsg->bulk_out_maxpacket - rem;
+	bh->outreq->length = length;
+}
+
+static struct fsg_dev			*the_fsg;
+static struct usb_gadget_driver		fsg_driver;
+
+
+/*-------------------------------------------------------------------------*/
+
+static int fsg_set_halt(struct fsg_dev *fsg, struct usb_ep *ep)
+{
+	const char	*name;
+
+	if (ep == fsg->bulk_in)
+		name = "bulk-in";
+	else if (ep == fsg->bulk_out)
+		name = "bulk-out";
+	else
+		name = ep->name;
+	DBG(fsg, "%s set halt\n", name);
+	return usb_ep_set_halt(ep);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * DESCRIPTORS ... most are static, but strings and (full) configuration
+ * descriptors are built on demand.  Also the (static) config and interface
+ * descriptors are adjusted during fsg_bind().
+ */
+
+/* There is only one configuration. */
+#define	CONFIG_VALUE		1
+
+static struct usb_device_descriptor
+device_desc = {
+	.bLength =		sizeof device_desc,
+	.bDescriptorType =	USB_DT_DEVICE,
+
+	.bcdUSB =		cpu_to_le16(0x0200),
+	.bDeviceClass =		USB_CLASS_PER_INTERFACE,
+
+	/* The next three values can be overridden by module parameters */
+	.idVendor =		cpu_to_le16(FSG_VENDOR_ID),
+	.idProduct =		cpu_to_le16(FSG_PRODUCT_ID),
+	.bcdDevice =		cpu_to_le16(0xffff),
+
+	.iManufacturer =	FSG_STRING_MANUFACTURER,
+	.iProduct =		FSG_STRING_PRODUCT,
+	.iSerialNumber =	FSG_STRING_SERIAL,
+	.bNumConfigurations =	1,
+};
+
+static struct usb_config_descriptor
+config_desc = {
+	.bLength =		sizeof config_desc,
+	.bDescriptorType =	USB_DT_CONFIG,
+
+	/* wTotalLength computed by usb_gadget_config_buf() */
+	.bNumInterfaces =	1,
+	.bConfigurationValue =	CONFIG_VALUE,
+	.iConfiguration =	FSG_STRING_CONFIG,
+	.bmAttributes =		USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER,
+	.bMaxPower =		CONFIG_USB_GADGET_VBUS_DRAW / 2,
+};
+
+
+static struct usb_qualifier_descriptor
+dev_qualifier = {
+	.bLength =		sizeof dev_qualifier,
+	.bDescriptorType =	USB_DT_DEVICE_QUALIFIER,
+
+	.bcdUSB =		cpu_to_le16(0x0200),
+	.bDeviceClass =		USB_CLASS_PER_INTERFACE,
+
+	.bNumConfigurations =	1,
+};
+
+static int populate_bos(struct fsg_dev *fsg, u8 *buf)
+{
+	memcpy(buf, &fsg_bos_desc, USB_DT_BOS_SIZE);
+	buf += USB_DT_BOS_SIZE;
+
+	memcpy(buf, &fsg_ext_cap_desc, USB_DT_USB_EXT_CAP_SIZE);
+	buf += USB_DT_USB_EXT_CAP_SIZE;
+
+	memcpy(buf, &fsg_ss_cap_desc, USB_DT_USB_SS_CAP_SIZE);
+
+	return USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE
+		+ USB_DT_USB_EXT_CAP_SIZE;
+}
+
+/*
+ * Config descriptors must agree with the code that sets configurations
+ * and with code managing interfaces and their altsettings.  They must
+ * also handle different speeds and other-speed requests.
+ */
+static int populate_config_buf(struct usb_gadget *gadget,
+		u8 *buf, u8 type, unsigned index)
+{
+	enum usb_device_speed			speed = gadget->speed;
+	int					len;
+	const struct usb_descriptor_header	**function;
+
+	if (index > 0)
+		return -EINVAL;
+
+	if (gadget_is_dualspeed(gadget) && type == USB_DT_OTHER_SPEED_CONFIG)
+		speed = (USB_SPEED_FULL + USB_SPEED_HIGH) - speed;
+	function = gadget_is_dualspeed(gadget) && speed == USB_SPEED_HIGH
+		? (const struct usb_descriptor_header **)fsg_hs_function
+		: (const struct usb_descriptor_header **)fsg_fs_function;
+
+	/* for now, don't advertise srp-only devices */
+	if (!gadget_is_otg(gadget))
+		function++;
+
+	len = usb_gadget_config_buf(&config_desc, buf, EP0_BUFSIZE, function);
+	((struct usb_config_descriptor *) buf)->bDescriptorType = type;
+	return len;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* These routines may be called in process context or in_irq */
+
+/* Caller must hold fsg->lock */
+static void wakeup_thread(struct fsg_dev *fsg)
+{
+	/* Tell the main thread that something has happened */
+	fsg->thread_wakeup_needed = 1;
+	if (fsg->thread_task)
+		wake_up_process(fsg->thread_task);
+}
+
+
+static void raise_exception(struct fsg_dev *fsg, enum fsg_state new_state)
+{
+	unsigned long		flags;
+
+	/* Do nothing if a higher-priority exception is already in progress.
+	 * If a lower-or-equal priority exception is in progress, preempt it
+	 * and notify the main thread by sending it a signal. */
+	spin_lock_irqsave(&fsg->lock, flags);
+	if (fsg->state <= new_state) {
+		fsg->exception_req_tag = fsg->ep0_req_tag;
+		fsg->state = new_state;
+		if (fsg->thread_task)
+			send_sig_info(SIGUSR1, SEND_SIG_FORCED,
+					fsg->thread_task);
+	}
+	spin_unlock_irqrestore(&fsg->lock, flags);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* The disconnect callback and ep0 routines.  These always run in_irq,
+ * except that ep0_queue() is called in the main thread to acknowledge
+ * completion of various requests: set config, set interface, and
+ * Bulk-only device reset. */
+
+static void fsg_disconnect(struct usb_gadget *gadget)
+{
+	struct fsg_dev		*fsg = get_gadget_data(gadget);
+
+	DBG(fsg, "disconnect or port reset\n");
+	raise_exception(fsg, FSG_STATE_DISCONNECT);
+}
+
+
+static int ep0_queue(struct fsg_dev *fsg)
+{
+	int	rc;
+
+	rc = usb_ep_queue(fsg->ep0, fsg->ep0req, GFP_ATOMIC);
+	if (rc != 0 && rc != -ESHUTDOWN) {
+
+		/* We can't do much more than wait for a reset */
+		WARNING(fsg, "error in submission: %s --> %d\n",
+				fsg->ep0->name, rc);
+	}
+	return rc;
+}
+
+static void ep0_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct fsg_dev		*fsg = ep->driver_data;
+
+	if (req->actual > 0)
+		dump_msg(fsg, fsg->ep0req_name, req->buf, req->actual);
+	if (req->status || req->actual != req->length)
+		DBG(fsg, "%s --> %d, %u/%u\n", __func__,
+				req->status, req->actual, req->length);
+	if (req->status == -ECONNRESET)		// Request was cancelled
+		usb_ep_fifo_flush(ep);
+
+	if (req->status == 0 && req->context)
+		((fsg_routine_t) (req->context))(fsg);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* Bulk and interrupt endpoint completion handlers.
+ * These always run in_irq. */
+
+static void bulk_in_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct fsg_dev		*fsg = ep->driver_data;
+	struct fsg_buffhd	*bh = req->context;
+
+	if (req->status || req->actual != req->length)
+		DBG(fsg, "%s --> %d, %u/%u\n", __func__,
+				req->status, req->actual, req->length);
+	if (req->status == -ECONNRESET)		// Request was cancelled
+		usb_ep_fifo_flush(ep);
+
+	/* Hold the lock while we update the request and buffer states */
+	smp_wmb();
+	spin_lock(&fsg->lock);
+	bh->inreq_busy = 0;
+	bh->state = BUF_STATE_EMPTY;
+	wakeup_thread(fsg);
+	spin_unlock(&fsg->lock);
+}
+
+static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct fsg_dev		*fsg = ep->driver_data;
+	struct fsg_buffhd	*bh = req->context;
+
+	dump_msg(fsg, "bulk-out", req->buf, req->actual);
+	if (req->status || req->actual != bh->bulk_out_intended_length)
+		DBG(fsg, "%s --> %d, %u/%u\n", __func__,
+				req->status, req->actual,
+				bh->bulk_out_intended_length);
+	if (req->status == -ECONNRESET)		// Request was cancelled
+		usb_ep_fifo_flush(ep);
+
+	/* Hold the lock while we update the request and buffer states */
+	smp_wmb();
+	spin_lock(&fsg->lock);
+	bh->outreq_busy = 0;
+	bh->state = BUF_STATE_FULL;
+	wakeup_thread(fsg);
+	spin_unlock(&fsg->lock);
+}
+
+
+#ifdef CONFIG_USB_FILE_STORAGE_TEST
+static void intr_in_complete(struct usb_ep *ep, struct usb_request *req)
+{
+	struct fsg_dev		*fsg = ep->driver_data;
+	struct fsg_buffhd	*bh = req->context;
+
+	if (req->status || req->actual != req->length)
+		DBG(fsg, "%s --> %d, %u/%u\n", __func__,
+				req->status, req->actual, req->length);
+	if (req->status == -ECONNRESET)		// Request was cancelled
+		usb_ep_fifo_flush(ep);
+
+	/* Hold the lock while we update the request and buffer states */
+	smp_wmb();
+	spin_lock(&fsg->lock);
+	fsg->intreq_busy = 0;
+	bh->state = BUF_STATE_EMPTY;
+	wakeup_thread(fsg);
+	spin_unlock(&fsg->lock);
+}
+
+#else
+static void intr_in_complete(struct usb_ep *ep, struct usb_request *req)
+{}
+#endif /* CONFIG_USB_FILE_STORAGE_TEST */
+
+
+/*-------------------------------------------------------------------------*/
+
+/* Ep0 class-specific handlers.  These always run in_irq. */
+
+#ifdef CONFIG_USB_FILE_STORAGE_TEST
+static void received_cbi_adsc(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+	struct usb_request	*req = fsg->ep0req;
+	static u8		cbi_reset_cmnd[6] = {
+			SEND_DIAGNOSTIC, 4, 0xff, 0xff, 0xff, 0xff};
+
+	/* Error in command transfer? */
+	if (req->status || req->length != req->actual ||
+			req->actual < 6 || req->actual > MAX_COMMAND_SIZE) {
+
+		/* Not all controllers allow a protocol stall after
+		 * receiving control-out data, but we'll try anyway. */
+		fsg_set_halt(fsg, fsg->ep0);
+		return;			// Wait for reset
+	}
+
+	/* Is it the special reset command? */
+	if (req->actual >= sizeof cbi_reset_cmnd &&
+			memcmp(req->buf, cbi_reset_cmnd,
+				sizeof cbi_reset_cmnd) == 0) {
+
+		/* Raise an exception to stop the current operation
+		 * and reinitialize our state. */
+		DBG(fsg, "cbi reset request\n");
+		raise_exception(fsg, FSG_STATE_RESET);
+		return;
+	}
+
+	VDBG(fsg, "CB[I] accept device-specific command\n");
+	spin_lock(&fsg->lock);
+
+	/* Save the command for later */
+	if (fsg->cbbuf_cmnd_size)
+		WARNING(fsg, "CB[I] overwriting previous command\n");
+	fsg->cbbuf_cmnd_size = req->actual;
+	memcpy(fsg->cbbuf_cmnd, req->buf, fsg->cbbuf_cmnd_size);
+
+	wakeup_thread(fsg);
+	spin_unlock(&fsg->lock);
+}
+
+#else
+static void received_cbi_adsc(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{}
+#endif /* CONFIG_USB_FILE_STORAGE_TEST */
+
+
+static int class_setup_req(struct fsg_dev *fsg,
+		const struct usb_ctrlrequest *ctrl)
+{
+	struct usb_request	*req = fsg->ep0req;
+	int			value = -EOPNOTSUPP;
+	u16			w_index = le16_to_cpu(ctrl->wIndex);
+	u16                     w_value = le16_to_cpu(ctrl->wValue);
+	u16			w_length = le16_to_cpu(ctrl->wLength);
+
+	if (!fsg->config)
+		return value;
+
+	/* Handle Bulk-only class-specific requests */
+	if (transport_is_bbb()) {
+		switch (ctrl->bRequest) {
+
+		case US_BULK_RESET_REQUEST:
+			if (ctrl->bRequestType != (USB_DIR_OUT |
+					USB_TYPE_CLASS | USB_RECIP_INTERFACE))
+				break;
+			if (w_index != 0 || w_value != 0 || w_length != 0) {
+				value = -EDOM;
+				break;
+			}
+
+			/* Raise an exception to stop the current operation
+			 * and reinitialize our state. */
+			DBG(fsg, "bulk reset request\n");
+			raise_exception(fsg, FSG_STATE_RESET);
+			value = DELAYED_STATUS;
+			break;
+
+		case US_BULK_GET_MAX_LUN:
+			if (ctrl->bRequestType != (USB_DIR_IN |
+					USB_TYPE_CLASS | USB_RECIP_INTERFACE))
+				break;
+			if (w_index != 0 || w_value != 0 || w_length != 1) {
+				value = -EDOM;
+				break;
+			}
+			VDBG(fsg, "get max LUN\n");
+			*(u8 *) req->buf = fsg->nluns - 1;
+			value = 1;
+			break;
+		}
+	}
+
+	/* Handle CBI class-specific requests */
+	else {
+		switch (ctrl->bRequest) {
+
+		case USB_CBI_ADSC_REQUEST:
+			if (ctrl->bRequestType != (USB_DIR_OUT |
+					USB_TYPE_CLASS | USB_RECIP_INTERFACE))
+				break;
+			if (w_index != 0 || w_value != 0) {
+				value = -EDOM;
+				break;
+			}
+			if (w_length > MAX_COMMAND_SIZE) {
+				value = -EOVERFLOW;
+				break;
+			}
+			value = w_length;
+			fsg->ep0req->context = received_cbi_adsc;
+			break;
+		}
+	}
+
+	if (value == -EOPNOTSUPP)
+		VDBG(fsg,
+			"unknown class-specific control req "
+			"%02x.%02x v%04x i%04x l%u\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			le16_to_cpu(ctrl->wValue), w_index, w_length);
+	return value;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* Ep0 standard request handlers.  These always run in_irq. */
+
+static int standard_setup_req(struct fsg_dev *fsg,
+		const struct usb_ctrlrequest *ctrl)
+{
+	struct usb_request	*req = fsg->ep0req;
+	int			value = -EOPNOTSUPP;
+	u16			w_index = le16_to_cpu(ctrl->wIndex);
+	u16			w_value = le16_to_cpu(ctrl->wValue);
+
+	/* Usually this just stores reply data in the pre-allocated ep0 buffer,
+	 * but config change events will also reconfigure hardware. */
+	switch (ctrl->bRequest) {
+
+	case USB_REQ_GET_DESCRIPTOR:
+		if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_STANDARD |
+				USB_RECIP_DEVICE))
+			break;
+		switch (w_value >> 8) {
+
+		case USB_DT_DEVICE:
+			VDBG(fsg, "get device descriptor\n");
+			device_desc.bMaxPacketSize0 = fsg->ep0->maxpacket;
+			value = sizeof device_desc;
+			memcpy(req->buf, &device_desc, value);
+			break;
+		case USB_DT_DEVICE_QUALIFIER:
+			VDBG(fsg, "get device qualifier\n");
+			if (!gadget_is_dualspeed(fsg->gadget) ||
+					fsg->gadget->speed == USB_SPEED_SUPER)
+				break;
+			/*
+			 * Assume ep0 uses the same maxpacket value for both
+			 * speeds
+			 */
+			dev_qualifier.bMaxPacketSize0 = fsg->ep0->maxpacket;
+			value = sizeof dev_qualifier;
+			memcpy(req->buf, &dev_qualifier, value);
+			break;
+
+		case USB_DT_OTHER_SPEED_CONFIG:
+			VDBG(fsg, "get other-speed config descriptor\n");
+			if (!gadget_is_dualspeed(fsg->gadget) ||
+					fsg->gadget->speed == USB_SPEED_SUPER)
+				break;
+			goto get_config;
+		case USB_DT_CONFIG:
+			VDBG(fsg, "get configuration descriptor\n");
+get_config:
+			value = populate_config_buf(fsg->gadget,
+					req->buf,
+					w_value >> 8,
+					w_value & 0xff);
+			break;
+
+		case USB_DT_STRING:
+			VDBG(fsg, "get string descriptor\n");
+
+			/* wIndex == language code */
+			value = usb_gadget_get_string(&fsg_stringtab,
+					w_value & 0xff, req->buf);
+			break;
+
+		case USB_DT_BOS:
+			VDBG(fsg, "get bos descriptor\n");
+
+			if (gadget_is_superspeed(fsg->gadget))
+				value = populate_bos(fsg, req->buf);
+			break;
+		}
+
+		break;
+
+	/* One config, two speeds */
+	case USB_REQ_SET_CONFIGURATION:
+		if (ctrl->bRequestType != (USB_DIR_OUT | USB_TYPE_STANDARD |
+				USB_RECIP_DEVICE))
+			break;
+		VDBG(fsg, "set configuration\n");
+		if (w_value == CONFIG_VALUE || w_value == 0) {
+			fsg->new_config = w_value;
+
+			/* Raise an exception to wipe out previous transaction
+			 * state (queued bufs, etc) and set the new config. */
+			raise_exception(fsg, FSG_STATE_CONFIG_CHANGE);
+			value = DELAYED_STATUS;
+		}
+		break;
+	case USB_REQ_GET_CONFIGURATION:
+		if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_STANDARD |
+				USB_RECIP_DEVICE))
+			break;
+		VDBG(fsg, "get configuration\n");
+		*(u8 *) req->buf = fsg->config;
+		value = 1;
+		break;
+
+	case USB_REQ_SET_INTERFACE:
+		if (ctrl->bRequestType != (USB_DIR_OUT| USB_TYPE_STANDARD |
+				USB_RECIP_INTERFACE))
+			break;
+		if (fsg->config && w_index == 0) {
+
+			/* Raise an exception to wipe out previous transaction
+			 * state (queued bufs, etc) and install the new
+			 * interface altsetting. */
+			raise_exception(fsg, FSG_STATE_INTERFACE_CHANGE);
+			value = DELAYED_STATUS;
+		}
+		break;
+	case USB_REQ_GET_INTERFACE:
+		if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_STANDARD |
+				USB_RECIP_INTERFACE))
+			break;
+		if (!fsg->config)
+			break;
+		if (w_index != 0) {
+			value = -EDOM;
+			break;
+		}
+		VDBG(fsg, "get interface\n");
+		*(u8 *) req->buf = 0;
+		value = 1;
+		break;
+
+	default:
+		VDBG(fsg,
+			"unknown control req %02x.%02x v%04x i%04x l%u\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			w_value, w_index, le16_to_cpu(ctrl->wLength));
+	}
+
+	return value;
+}
+
+
+static int fsg_setup(struct usb_gadget *gadget,
+		const struct usb_ctrlrequest *ctrl)
+{
+	struct fsg_dev		*fsg = get_gadget_data(gadget);
+	int			rc;
+	int			w_length = le16_to_cpu(ctrl->wLength);
+
+	++fsg->ep0_req_tag;		// Record arrival of a new request
+	fsg->ep0req->context = NULL;
+	fsg->ep0req->length = 0;
+	dump_msg(fsg, "ep0-setup", (u8 *) ctrl, sizeof(*ctrl));
+
+	if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS)
+		rc = class_setup_req(fsg, ctrl);
+	else
+		rc = standard_setup_req(fsg, ctrl);
+
+	/* Respond with data/status or defer until later? */
+	if (rc >= 0 && rc != DELAYED_STATUS) {
+		rc = min(rc, w_length);
+		fsg->ep0req->length = rc;
+		fsg->ep0req->zero = rc < w_length;
+		fsg->ep0req_name = (ctrl->bRequestType & USB_DIR_IN ?
+				"ep0-in" : "ep0-out");
+		rc = ep0_queue(fsg);
+	}
+
+	/* Device either stalls (rc < 0) or reports success */
+	return rc;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* All the following routines run in process context */
+
+
+/* Use this for bulk or interrupt transfers, not ep0 */
+static void start_transfer(struct fsg_dev *fsg, struct usb_ep *ep,
+		struct usb_request *req, int *pbusy,
+		enum fsg_buffer_state *state)
+{
+	int	rc;
+
+	if (ep == fsg->bulk_in)
+		dump_msg(fsg, "bulk-in", req->buf, req->length);
+	else if (ep == fsg->intr_in)
+		dump_msg(fsg, "intr-in", req->buf, req->length);
+
+	spin_lock_irq(&fsg->lock);
+	*pbusy = 1;
+	*state = BUF_STATE_BUSY;
+	spin_unlock_irq(&fsg->lock);
+	rc = usb_ep_queue(ep, req, GFP_KERNEL);
+	if (rc != 0) {
+		*pbusy = 0;
+		*state = BUF_STATE_EMPTY;
+
+		/* We can't do much more than wait for a reset */
+
+		/* Note: currently the net2280 driver fails zero-length
+		 * submissions if DMA is enabled. */
+		if (rc != -ESHUTDOWN && !(rc == -EOPNOTSUPP &&
+						req->length == 0))
+			WARNING(fsg, "error in submission: %s --> %d\n",
+					ep->name, rc);
+	}
+}
+
+
+static int sleep_thread(struct fsg_dev *fsg)
+{
+	int	rc = 0;
+
+	/* Wait until a signal arrives or we are woken up */
+	for (;;) {
+		try_to_freeze();
+		set_current_state(TASK_INTERRUPTIBLE);
+		if (signal_pending(current)) {
+			rc = -EINTR;
+			break;
+		}
+		if (fsg->thread_wakeup_needed)
+			break;
+		schedule();
+	}
+	__set_current_state(TASK_RUNNING);
+	fsg->thread_wakeup_needed = 0;
+	return rc;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int do_read(struct fsg_dev *fsg)
+{
+	struct fsg_lun		*curlun = fsg->curlun;
+	u32			lba;
+	struct fsg_buffhd	*bh;
+	int			rc;
+	u32			amount_left;
+	loff_t			file_offset, file_offset_tmp;
+	unsigned int		amount;
+	ssize_t			nread;
+
+	/* Get the starting Logical Block Address and check that it's
+	 * not too big */
+	if (fsg->cmnd[0] == READ_6)
+		lba = get_unaligned_be24(&fsg->cmnd[1]);
+	else {
+		lba = get_unaligned_be32(&fsg->cmnd[2]);
+
+		/* We allow DPO (Disable Page Out = don't save data in the
+		 * cache) and FUA (Force Unit Access = don't read from the
+		 * cache), but we don't implement them. */
+		if ((fsg->cmnd[1] & ~0x18) != 0) {
+			curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+			return -EINVAL;
+		}
+	}
+	if (lba >= curlun->num_sectors) {
+		curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+		return -EINVAL;
+	}
+	file_offset = ((loff_t) lba) << curlun->blkbits;
+
+	/* Carry out the file reads */
+	amount_left = fsg->data_size_from_cmnd;
+	if (unlikely(amount_left == 0))
+		return -EIO;		// No default reply
+
+	for (;;) {
+
+		/* Figure out how much we need to read:
+		 * Try to read the remaining amount.
+		 * But don't read more than the buffer size.
+		 * And don't try to read past the end of the file.
+		 */
+		amount = min((unsigned int) amount_left, mod_data.buflen);
+		amount = min((loff_t) amount,
+				curlun->file_length - file_offset);
+
+		/* Wait for the next buffer to become available */
+		bh = fsg->next_buffhd_to_fill;
+		while (bh->state != BUF_STATE_EMPTY) {
+			rc = sleep_thread(fsg);
+			if (rc)
+				return rc;
+		}
+
+		/* If we were asked to read past the end of file,
+		 * end with an empty buffer. */
+		if (amount == 0) {
+			curlun->sense_data =
+					SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+			curlun->sense_data_info = file_offset >> curlun->blkbits;
+			curlun->info_valid = 1;
+			bh->inreq->length = 0;
+			bh->state = BUF_STATE_FULL;
+			break;
+		}
+
+		/* Perform the read */
+		file_offset_tmp = file_offset;
+		nread = vfs_read(curlun->filp,
+				(char __user *) bh->buf,
+				amount, &file_offset_tmp);
+		VLDBG(curlun, "file read %u @ %llu -> %d\n", amount,
+				(unsigned long long) file_offset,
+				(int) nread);
+		if (signal_pending(current))
+			return -EINTR;
+
+		if (nread < 0) {
+			LDBG(curlun, "error in file read: %d\n",
+					(int) nread);
+			nread = 0;
+		} else if (nread < amount) {
+			LDBG(curlun, "partial file read: %d/%u\n",
+					(int) nread, amount);
+			nread = round_down(nread, curlun->blksize);
+		}
+		file_offset  += nread;
+		amount_left  -= nread;
+		fsg->residue -= nread;
+
+		/* Except at the end of the transfer, nread will be
+		 * equal to the buffer size, which is divisible by the
+		 * bulk-in maxpacket size.
+		 */
+		bh->inreq->length = nread;
+		bh->state = BUF_STATE_FULL;
+
+		/* If an error occurred, report it and its position */
+		if (nread < amount) {
+			curlun->sense_data = SS_UNRECOVERED_READ_ERROR;
+			curlun->sense_data_info = file_offset >> curlun->blkbits;
+			curlun->info_valid = 1;
+			break;
+		}
+
+		if (amount_left == 0)
+			break;		// No more left to read
+
+		/* Send this buffer and go read some more */
+		bh->inreq->zero = 0;
+		start_transfer(fsg, fsg->bulk_in, bh->inreq,
+				&bh->inreq_busy, &bh->state);
+		fsg->next_buffhd_to_fill = bh->next;
+	}
+
+	return -EIO;		// No default reply
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int do_write(struct fsg_dev *fsg)
+{
+	struct fsg_lun		*curlun = fsg->curlun;
+	u32			lba;
+	struct fsg_buffhd	*bh;
+	int			get_some_more;
+	u32			amount_left_to_req, amount_left_to_write;
+	loff_t			usb_offset, file_offset, file_offset_tmp;
+	unsigned int		amount;
+	ssize_t			nwritten;
+	int			rc;
+
+	if (curlun->ro) {
+		curlun->sense_data = SS_WRITE_PROTECTED;
+		return -EINVAL;
+	}
+	spin_lock(&curlun->filp->f_lock);
+	curlun->filp->f_flags &= ~O_SYNC;	// Default is not to wait
+	spin_unlock(&curlun->filp->f_lock);
+
+	/* Get the starting Logical Block Address and check that it's
+	 * not too big */
+	if (fsg->cmnd[0] == WRITE_6)
+		lba = get_unaligned_be24(&fsg->cmnd[1]);
+	else {
+		lba = get_unaligned_be32(&fsg->cmnd[2]);
+
+		/* We allow DPO (Disable Page Out = don't save data in the
+		 * cache) and FUA (Force Unit Access = write directly to the
+		 * medium).  We don't implement DPO; we implement FUA by
+		 * performing synchronous output. */
+		if ((fsg->cmnd[1] & ~0x18) != 0) {
+			curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+			return -EINVAL;
+		}
+		/* FUA */
+		if (!curlun->nofua && (fsg->cmnd[1] & 0x08)) {
+			spin_lock(&curlun->filp->f_lock);
+			curlun->filp->f_flags |= O_DSYNC;
+			spin_unlock(&curlun->filp->f_lock);
+		}
+	}
+	if (lba >= curlun->num_sectors) {
+		curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+		return -EINVAL;
+	}
+
+	/* Carry out the file writes */
+	get_some_more = 1;
+	file_offset = usb_offset = ((loff_t) lba) << curlun->blkbits;
+	amount_left_to_req = amount_left_to_write = fsg->data_size_from_cmnd;
+
+	while (amount_left_to_write > 0) {
+
+		/* Queue a request for more data from the host */
+		bh = fsg->next_buffhd_to_fill;
+		if (bh->state == BUF_STATE_EMPTY && get_some_more) {
+
+			/* Figure out how much we want to get:
+			 * Try to get the remaining amount,
+			 * but not more than the buffer size.
+			 */
+			amount = min(amount_left_to_req, mod_data.buflen);
+
+			/* Beyond the end of the backing file? */
+			if (usb_offset >= curlun->file_length) {
+				get_some_more = 0;
+				curlun->sense_data =
+					SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+				curlun->sense_data_info = usb_offset >> curlun->blkbits;
+				curlun->info_valid = 1;
+				continue;
+			}
+
+			/* Get the next buffer */
+			usb_offset += amount;
+			fsg->usb_amount_left -= amount;
+			amount_left_to_req -= amount;
+			if (amount_left_to_req == 0)
+				get_some_more = 0;
+
+			/* Except at the end of the transfer, amount will be
+			 * equal to the buffer size, which is divisible by
+			 * the bulk-out maxpacket size.
+			 */
+			set_bulk_out_req_length(fsg, bh, amount);
+			start_transfer(fsg, fsg->bulk_out, bh->outreq,
+					&bh->outreq_busy, &bh->state);
+			fsg->next_buffhd_to_fill = bh->next;
+			continue;
+		}
+
+		/* Write the received data to the backing file */
+		bh = fsg->next_buffhd_to_drain;
+		if (bh->state == BUF_STATE_EMPTY && !get_some_more)
+			break;			// We stopped early
+		if (bh->state == BUF_STATE_FULL) {
+			smp_rmb();
+			fsg->next_buffhd_to_drain = bh->next;
+			bh->state = BUF_STATE_EMPTY;
+
+			/* Did something go wrong with the transfer? */
+			if (bh->outreq->status != 0) {
+				curlun->sense_data = SS_COMMUNICATION_FAILURE;
+				curlun->sense_data_info = file_offset >> curlun->blkbits;
+				curlun->info_valid = 1;
+				break;
+			}
+
+			amount = bh->outreq->actual;
+			if (curlun->file_length - file_offset < amount) {
+				LERROR(curlun,
+	"write %u @ %llu beyond end %llu\n",
+	amount, (unsigned long long) file_offset,
+	(unsigned long long) curlun->file_length);
+				amount = curlun->file_length - file_offset;
+			}
+
+			/* Don't accept excess data.  The spec doesn't say
+			 * what to do in this case.  We'll ignore the error.
+			 */
+			amount = min(amount, bh->bulk_out_intended_length);
+
+			/* Don't write a partial block */
+			amount = round_down(amount, curlun->blksize);
+			if (amount == 0)
+				goto empty_write;
+
+			/* Perform the write */
+			file_offset_tmp = file_offset;
+			nwritten = vfs_write(curlun->filp,
+					(char __user *) bh->buf,
+					amount, &file_offset_tmp);
+			VLDBG(curlun, "file write %u @ %llu -> %d\n", amount,
+					(unsigned long long) file_offset,
+					(int) nwritten);
+			if (signal_pending(current))
+				return -EINTR;		// Interrupted!
+
+			if (nwritten < 0) {
+				LDBG(curlun, "error in file write: %d\n",
+						(int) nwritten);
+				nwritten = 0;
+			} else if (nwritten < amount) {
+				LDBG(curlun, "partial file write: %d/%u\n",
+						(int) nwritten, amount);
+				nwritten = round_down(nwritten, curlun->blksize);
+			}
+			file_offset += nwritten;
+			amount_left_to_write -= nwritten;
+			fsg->residue -= nwritten;
+
+			/* If an error occurred, report it and its position */
+			if (nwritten < amount) {
+				curlun->sense_data = SS_WRITE_ERROR;
+				curlun->sense_data_info = file_offset >> curlun->blkbits;
+				curlun->info_valid = 1;
+				break;
+			}
+
+ empty_write:
+			/* Did the host decide to stop early? */
+			if (bh->outreq->actual < bh->bulk_out_intended_length) {
+				fsg->short_packet_received = 1;
+				break;
+			}
+			continue;
+		}
+
+		/* Wait for something to happen */
+		rc = sleep_thread(fsg);
+		if (rc)
+			return rc;
+	}
+
+	return -EIO;		// No default reply
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int do_synchronize_cache(struct fsg_dev *fsg)
+{
+	struct fsg_lun	*curlun = fsg->curlun;
+	int		rc;
+
+	/* We ignore the requested LBA and write out all file's
+	 * dirty data buffers. */
+	rc = fsg_lun_fsync_sub(curlun);
+	if (rc)
+		curlun->sense_data = SS_WRITE_ERROR;
+	return 0;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static void invalidate_sub(struct fsg_lun *curlun)
+{
+	struct file	*filp = curlun->filp;
+	struct inode	*inode = filp->f_path.dentry->d_inode;
+	unsigned long	rc;
+
+	rc = invalidate_mapping_pages(inode->i_mapping, 0, -1);
+	VLDBG(curlun, "invalidate_mapping_pages -> %ld\n", rc);
+}
+
+static int do_verify(struct fsg_dev *fsg)
+{
+	struct fsg_lun		*curlun = fsg->curlun;
+	u32			lba;
+	u32			verification_length;
+	struct fsg_buffhd	*bh = fsg->next_buffhd_to_fill;
+	loff_t			file_offset, file_offset_tmp;
+	u32			amount_left;
+	unsigned int		amount;
+	ssize_t			nread;
+
+	/* Get the starting Logical Block Address and check that it's
+	 * not too big */
+	lba = get_unaligned_be32(&fsg->cmnd[2]);
+	if (lba >= curlun->num_sectors) {
+		curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+		return -EINVAL;
+	}
+
+	/* We allow DPO (Disable Page Out = don't save data in the
+	 * cache) but we don't implement it. */
+	if ((fsg->cmnd[1] & ~0x10) != 0) {
+		curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+		return -EINVAL;
+	}
+
+	verification_length = get_unaligned_be16(&fsg->cmnd[7]);
+	if (unlikely(verification_length == 0))
+		return -EIO;		// No default reply
+
+	/* Prepare to carry out the file verify */
+	amount_left = verification_length << curlun->blkbits;
+	file_offset = ((loff_t) lba) << curlun->blkbits;
+
+	/* Write out all the dirty buffers before invalidating them */
+	fsg_lun_fsync_sub(curlun);
+	if (signal_pending(current))
+		return -EINTR;
+
+	invalidate_sub(curlun);
+	if (signal_pending(current))
+		return -EINTR;
+
+	/* Just try to read the requested blocks */
+	while (amount_left > 0) {
+
+		/* Figure out how much we need to read:
+		 * Try to read the remaining amount, but not more than
+		 * the buffer size.
+		 * And don't try to read past the end of the file.
+		 */
+		amount = min((unsigned int) amount_left, mod_data.buflen);
+		amount = min((loff_t) amount,
+				curlun->file_length - file_offset);
+		if (amount == 0) {
+			curlun->sense_data =
+					SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+			curlun->sense_data_info = file_offset >> curlun->blkbits;
+			curlun->info_valid = 1;
+			break;
+		}
+
+		/* Perform the read */
+		file_offset_tmp = file_offset;
+		nread = vfs_read(curlun->filp,
+				(char __user *) bh->buf,
+				amount, &file_offset_tmp);
+		VLDBG(curlun, "file read %u @ %llu -> %d\n", amount,
+				(unsigned long long) file_offset,
+				(int) nread);
+		if (signal_pending(current))
+			return -EINTR;
+
+		if (nread < 0) {
+			LDBG(curlun, "error in file verify: %d\n",
+					(int) nread);
+			nread = 0;
+		} else if (nread < amount) {
+			LDBG(curlun, "partial file verify: %d/%u\n",
+					(int) nread, amount);
+			nread = round_down(nread, curlun->blksize);
+		}
+		if (nread == 0) {
+			curlun->sense_data = SS_UNRECOVERED_READ_ERROR;
+			curlun->sense_data_info = file_offset >> curlun->blkbits;
+			curlun->info_valid = 1;
+			break;
+		}
+		file_offset += nread;
+		amount_left -= nread;
+	}
+	return 0;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int do_inquiry(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+	u8	*buf = (u8 *) bh->buf;
+
+	static char vendor_id[] = "Linux   ";
+	static char product_disk_id[] = "File-Stor Gadget";
+	static char product_cdrom_id[] = "File-CD Gadget  ";
+
+	if (!fsg->curlun) {		// Unsupported LUNs are okay
+		fsg->bad_lun_okay = 1;
+		memset(buf, 0, 36);
+		buf[0] = 0x7f;		// Unsupported, no device-type
+		buf[4] = 31;		// Additional length
+		return 36;
+	}
+
+	memset(buf, 0, 8);
+	buf[0] = (mod_data.cdrom ? TYPE_ROM : TYPE_DISK);
+	if (mod_data.removable)
+		buf[1] = 0x80;
+	buf[2] = 2;		// ANSI SCSI level 2
+	buf[3] = 2;		// SCSI-2 INQUIRY data format
+	buf[4] = 31;		// Additional length
+				// No special options
+	sprintf(buf + 8, "%-8s%-16s%04x", vendor_id,
+			(mod_data.cdrom ? product_cdrom_id :
+				product_disk_id),
+			mod_data.release);
+	return 36;
+}
+
+
+static int do_request_sense(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+	struct fsg_lun	*curlun = fsg->curlun;
+	u8		*buf = (u8 *) bh->buf;
+	u32		sd, sdinfo;
+	int		valid;
+
+	/*
+	 * From the SCSI-2 spec., section 7.9 (Unit attention condition):
+	 *
+	 * If a REQUEST SENSE command is received from an initiator
+	 * with a pending unit attention condition (before the target
+	 * generates the contingent allegiance condition), then the
+	 * target shall either:
+	 *   a) report any pending sense data and preserve the unit
+	 *	attention condition on the logical unit, or,
+	 *   b) report the unit attention condition, may discard any
+	 *	pending sense data, and clear the unit attention
+	 *	condition on the logical unit for that initiator.
+	 *
+	 * FSG normally uses option a); enable this code to use option b).
+	 */
+#if 0
+	if (curlun && curlun->unit_attention_data != SS_NO_SENSE) {
+		curlun->sense_data = curlun->unit_attention_data;
+		curlun->unit_attention_data = SS_NO_SENSE;
+	}
+#endif
+
+	if (!curlun) {		// Unsupported LUNs are okay
+		fsg->bad_lun_okay = 1;
+		sd = SS_LOGICAL_UNIT_NOT_SUPPORTED;
+		sdinfo = 0;
+		valid = 0;
+	} else {
+		sd = curlun->sense_data;
+		sdinfo = curlun->sense_data_info;
+		valid = curlun->info_valid << 7;
+		curlun->sense_data = SS_NO_SENSE;
+		curlun->sense_data_info = 0;
+		curlun->info_valid = 0;
+	}
+
+	memset(buf, 0, 18);
+	buf[0] = valid | 0x70;			// Valid, current error
+	buf[2] = SK(sd);
+	put_unaligned_be32(sdinfo, &buf[3]);	/* Sense information */
+	buf[7] = 18 - 8;			// Additional sense length
+	buf[12] = ASC(sd);
+	buf[13] = ASCQ(sd);
+	return 18;
+}
+
+
+static int do_read_capacity(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+	struct fsg_lun	*curlun = fsg->curlun;
+	u32		lba = get_unaligned_be32(&fsg->cmnd[2]);
+	int		pmi = fsg->cmnd[8];
+	u8		*buf = (u8 *) bh->buf;
+
+	/* Check the PMI and LBA fields */
+	if (pmi > 1 || (pmi == 0 && lba != 0)) {
+		curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+		return -EINVAL;
+	}
+
+	put_unaligned_be32(curlun->num_sectors - 1, &buf[0]);
+						/* Max logical block */
+	put_unaligned_be32(curlun->blksize, &buf[4]);	/* Block length */
+	return 8;
+}
+
+
+static int do_read_header(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+	struct fsg_lun	*curlun = fsg->curlun;
+	int		msf = fsg->cmnd[1] & 0x02;
+	u32		lba = get_unaligned_be32(&fsg->cmnd[2]);
+	u8		*buf = (u8 *) bh->buf;
+
+	if ((fsg->cmnd[1] & ~0x02) != 0) {		/* Mask away MSF */
+		curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+		return -EINVAL;
+	}
+	if (lba >= curlun->num_sectors) {
+		curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE;
+		return -EINVAL;
+	}
+
+	memset(buf, 0, 8);
+	buf[0] = 0x01;		/* 2048 bytes of user data, rest is EC */
+	store_cdrom_address(&buf[4], msf, lba);
+	return 8;
+}
+
+
+static int do_read_toc(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+	struct fsg_lun	*curlun = fsg->curlun;
+	int		msf = fsg->cmnd[1] & 0x02;
+	int		start_track = fsg->cmnd[6];
+	u8		*buf = (u8 *) bh->buf;
+
+	if ((fsg->cmnd[1] & ~0x02) != 0 ||		/* Mask away MSF */
+			start_track > 1) {
+		curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+		return -EINVAL;
+	}
+
+	memset(buf, 0, 20);
+	buf[1] = (20-2);		/* TOC data length */
+	buf[2] = 1;			/* First track number */
+	buf[3] = 1;			/* Last track number */
+	buf[5] = 0x16;			/* Data track, copying allowed */
+	buf[6] = 0x01;			/* Only track is number 1 */
+	store_cdrom_address(&buf[8], msf, 0);
+
+	buf[13] = 0x16;			/* Lead-out track is data */
+	buf[14] = 0xAA;			/* Lead-out track number */
+	store_cdrom_address(&buf[16], msf, curlun->num_sectors);
+	return 20;
+}
+
+
+static int do_mode_sense(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+	struct fsg_lun	*curlun = fsg->curlun;
+	int		mscmnd = fsg->cmnd[0];
+	u8		*buf = (u8 *) bh->buf;
+	u8		*buf0 = buf;
+	int		pc, page_code;
+	int		changeable_values, all_pages;
+	int		valid_page = 0;
+	int		len, limit;
+
+	if ((fsg->cmnd[1] & ~0x08) != 0) {		// Mask away DBD
+		curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+		return -EINVAL;
+	}
+	pc = fsg->cmnd[2] >> 6;
+	page_code = fsg->cmnd[2] & 0x3f;
+	if (pc == 3) {
+		curlun->sense_data = SS_SAVING_PARAMETERS_NOT_SUPPORTED;
+		return -EINVAL;
+	}
+	changeable_values = (pc == 1);
+	all_pages = (page_code == 0x3f);
+
+	/* Write the mode parameter header.  Fixed values are: default
+	 * medium type, no cache control (DPOFUA), and no block descriptors.
+	 * The only variable value is the WriteProtect bit.  We will fill in
+	 * the mode data length later. */
+	memset(buf, 0, 8);
+	if (mscmnd == MODE_SENSE) {
+		buf[2] = (curlun->ro ? 0x80 : 0x00);		// WP, DPOFUA
+		buf += 4;
+		limit = 255;
+	} else {			// MODE_SENSE_10
+		buf[3] = (curlun->ro ? 0x80 : 0x00);		// WP, DPOFUA
+		buf += 8;
+		limit = 65535;		// Should really be mod_data.buflen
+	}
+
+	/* No block descriptors */
+
+	/* The mode pages, in numerical order.  The only page we support
+	 * is the Caching page. */
+	if (page_code == 0x08 || all_pages) {
+		valid_page = 1;
+		buf[0] = 0x08;		// Page code
+		buf[1] = 10;		// Page length
+		memset(buf+2, 0, 10);	// None of the fields are changeable
+
+		if (!changeable_values) {
+			buf[2] = 0x04;	// Write cache enable,
+					// Read cache not disabled
+					// No cache retention priorities
+			put_unaligned_be16(0xffff, &buf[4]);
+					/* Don't disable prefetch */
+					/* Minimum prefetch = 0 */
+			put_unaligned_be16(0xffff, &buf[8]);
+					/* Maximum prefetch */
+			put_unaligned_be16(0xffff, &buf[10]);
+					/* Maximum prefetch ceiling */
+		}
+		buf += 12;
+	}
+
+	/* Check that a valid page was requested and the mode data length
+	 * isn't too long. */
+	len = buf - buf0;
+	if (!valid_page || len > limit) {
+		curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+		return -EINVAL;
+	}
+
+	/*  Store the mode data length */
+	if (mscmnd == MODE_SENSE)
+		buf0[0] = len - 1;
+	else
+		put_unaligned_be16(len - 2, buf0);
+	return len;
+}
+
+
+static int do_start_stop(struct fsg_dev *fsg)
+{
+	struct fsg_lun	*curlun = fsg->curlun;
+	int		loej, start;
+
+	if (!mod_data.removable) {
+		curlun->sense_data = SS_INVALID_COMMAND;
+		return -EINVAL;
+	}
+
+	// int immed = fsg->cmnd[1] & 0x01;
+	loej = fsg->cmnd[4] & 0x02;
+	start = fsg->cmnd[4] & 0x01;
+
+#ifdef CONFIG_USB_FILE_STORAGE_TEST
+	if ((fsg->cmnd[1] & ~0x01) != 0 ||		// Mask away Immed
+			(fsg->cmnd[4] & ~0x03) != 0) {	// Mask LoEj, Start
+		curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+		return -EINVAL;
+	}
+
+	if (!start) {
+
+		/* Are we allowed to unload the media? */
+		if (curlun->prevent_medium_removal) {
+			LDBG(curlun, "unload attempt prevented\n");
+			curlun->sense_data = SS_MEDIUM_REMOVAL_PREVENTED;
+			return -EINVAL;
+		}
+		if (loej) {		// Simulate an unload/eject
+			up_read(&fsg->filesem);
+			down_write(&fsg->filesem);
+			fsg_lun_close(curlun);
+			up_write(&fsg->filesem);
+			down_read(&fsg->filesem);
+		}
+	} else {
+
+		/* Our emulation doesn't support mounting; the medium is
+		 * available for use as soon as it is loaded. */
+		if (!fsg_lun_is_open(curlun)) {
+			curlun->sense_data = SS_MEDIUM_NOT_PRESENT;
+			return -EINVAL;
+		}
+	}
+#endif
+	return 0;
+}
+
+
+static int do_prevent_allow(struct fsg_dev *fsg)
+{
+	struct fsg_lun	*curlun = fsg->curlun;
+	int		prevent;
+
+	if (!mod_data.removable) {
+		curlun->sense_data = SS_INVALID_COMMAND;
+		return -EINVAL;
+	}
+
+	prevent = fsg->cmnd[4] & 0x01;
+	if ((fsg->cmnd[4] & ~0x01) != 0) {		// Mask away Prevent
+		curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+		return -EINVAL;
+	}
+
+	if (curlun->prevent_medium_removal && !prevent)
+		fsg_lun_fsync_sub(curlun);
+	curlun->prevent_medium_removal = prevent;
+	return 0;
+}
+
+
+static int do_read_format_capacities(struct fsg_dev *fsg,
+			struct fsg_buffhd *bh)
+{
+	struct fsg_lun	*curlun = fsg->curlun;
+	u8		*buf = (u8 *) bh->buf;
+
+	buf[0] = buf[1] = buf[2] = 0;
+	buf[3] = 8;		// Only the Current/Maximum Capacity Descriptor
+	buf += 4;
+
+	put_unaligned_be32(curlun->num_sectors, &buf[0]);
+						/* Number of blocks */
+	put_unaligned_be32(curlun->blksize, &buf[4]);	/* Block length */
+	buf[4] = 0x02;				/* Current capacity */
+	return 12;
+}
+
+
+static int do_mode_select(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+	struct fsg_lun	*curlun = fsg->curlun;
+
+	/* We don't support MODE SELECT */
+	curlun->sense_data = SS_INVALID_COMMAND;
+	return -EINVAL;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int halt_bulk_in_endpoint(struct fsg_dev *fsg)
+{
+	int	rc;
+
+	rc = fsg_set_halt(fsg, fsg->bulk_in);
+	if (rc == -EAGAIN)
+		VDBG(fsg, "delayed bulk-in endpoint halt\n");
+	while (rc != 0) {
+		if (rc != -EAGAIN) {
+			WARNING(fsg, "usb_ep_set_halt -> %d\n", rc);
+			rc = 0;
+			break;
+		}
+
+		/* Wait for a short time and then try again */
+		if (msleep_interruptible(100) != 0)
+			return -EINTR;
+		rc = usb_ep_set_halt(fsg->bulk_in);
+	}
+	return rc;
+}
+
+static int wedge_bulk_in_endpoint(struct fsg_dev *fsg)
+{
+	int	rc;
+
+	DBG(fsg, "bulk-in set wedge\n");
+	rc = usb_ep_set_wedge(fsg->bulk_in);
+	if (rc == -EAGAIN)
+		VDBG(fsg, "delayed bulk-in endpoint wedge\n");
+	while (rc != 0) {
+		if (rc != -EAGAIN) {
+			WARNING(fsg, "usb_ep_set_wedge -> %d\n", rc);
+			rc = 0;
+			break;
+		}
+
+		/* Wait for a short time and then try again */
+		if (msleep_interruptible(100) != 0)
+			return -EINTR;
+		rc = usb_ep_set_wedge(fsg->bulk_in);
+	}
+	return rc;
+}
+
+static int throw_away_data(struct fsg_dev *fsg)
+{
+	struct fsg_buffhd	*bh;
+	u32			amount;
+	int			rc;
+
+	while ((bh = fsg->next_buffhd_to_drain)->state != BUF_STATE_EMPTY ||
+			fsg->usb_amount_left > 0) {
+
+		/* Throw away the data in a filled buffer */
+		if (bh->state == BUF_STATE_FULL) {
+			smp_rmb();
+			bh->state = BUF_STATE_EMPTY;
+			fsg->next_buffhd_to_drain = bh->next;
+
+			/* A short packet or an error ends everything */
+			if (bh->outreq->actual < bh->bulk_out_intended_length ||
+					bh->outreq->status != 0) {
+				raise_exception(fsg, FSG_STATE_ABORT_BULK_OUT);
+				return -EINTR;
+			}
+			continue;
+		}
+
+		/* Try to submit another request if we need one */
+		bh = fsg->next_buffhd_to_fill;
+		if (bh->state == BUF_STATE_EMPTY && fsg->usb_amount_left > 0) {
+			amount = min(fsg->usb_amount_left,
+					(u32) mod_data.buflen);
+
+			/* Except at the end of the transfer, amount will be
+			 * equal to the buffer size, which is divisible by
+			 * the bulk-out maxpacket size.
+			 */
+			set_bulk_out_req_length(fsg, bh, amount);
+			start_transfer(fsg, fsg->bulk_out, bh->outreq,
+					&bh->outreq_busy, &bh->state);
+			fsg->next_buffhd_to_fill = bh->next;
+			fsg->usb_amount_left -= amount;
+			continue;
+		}
+
+		/* Otherwise wait for something to happen */
+		rc = sleep_thread(fsg);
+		if (rc)
+			return rc;
+	}
+	return 0;
+}
+
+
+static int finish_reply(struct fsg_dev *fsg)
+{
+	struct fsg_buffhd	*bh = fsg->next_buffhd_to_fill;
+	int			rc = 0;
+
+	switch (fsg->data_dir) {
+	case DATA_DIR_NONE:
+		break;			// Nothing to send
+
+	/* If we don't know whether the host wants to read or write,
+	 * this must be CB or CBI with an unknown command.  We mustn't
+	 * try to send or receive any data.  So stall both bulk pipes
+	 * if we can and wait for a reset. */
+	case DATA_DIR_UNKNOWN:
+		if (mod_data.can_stall) {
+			fsg_set_halt(fsg, fsg->bulk_out);
+			rc = halt_bulk_in_endpoint(fsg);
+		}
+		break;
+
+	/* All but the last buffer of data must have already been sent */
+	case DATA_DIR_TO_HOST:
+		if (fsg->data_size == 0)
+			;		// Nothing to send
+
+		/* If there's no residue, simply send the last buffer */
+		else if (fsg->residue == 0) {
+			bh->inreq->zero = 0;
+			start_transfer(fsg, fsg->bulk_in, bh->inreq,
+					&bh->inreq_busy, &bh->state);
+			fsg->next_buffhd_to_fill = bh->next;
+		}
+
+		/* There is a residue.  For CB and CBI, simply mark the end
+		 * of the data with a short packet.  However, if we are
+		 * allowed to stall, there was no data at all (residue ==
+		 * data_size), and the command failed (invalid LUN or
+		 * sense data is set), then halt the bulk-in endpoint
+		 * instead. */
+		else if (!transport_is_bbb()) {
+			if (mod_data.can_stall &&
+					fsg->residue == fsg->data_size &&
+	(!fsg->curlun || fsg->curlun->sense_data != SS_NO_SENSE)) {
+				bh->state = BUF_STATE_EMPTY;
+				rc = halt_bulk_in_endpoint(fsg);
+			} else {
+				bh->inreq->zero = 1;
+				start_transfer(fsg, fsg->bulk_in, bh->inreq,
+						&bh->inreq_busy, &bh->state);
+				fsg->next_buffhd_to_fill = bh->next;
+			}
+		}
+
+		/*
+		 * For Bulk-only, mark the end of the data with a short
+		 * packet.  If we are allowed to stall, halt the bulk-in
+		 * endpoint.  (Note: This violates the Bulk-Only Transport
+		 * specification, which requires us to pad the data if we
+		 * don't halt the endpoint.  Presumably nobody will mind.)
+		 */
+		else {
+			bh->inreq->zero = 1;
+			start_transfer(fsg, fsg->bulk_in, bh->inreq,
+					&bh->inreq_busy, &bh->state);
+			fsg->next_buffhd_to_fill = bh->next;
+			if (mod_data.can_stall)
+				rc = halt_bulk_in_endpoint(fsg);
+		}
+		break;
+
+	/* We have processed all we want from the data the host has sent.
+	 * There may still be outstanding bulk-out requests. */
+	case DATA_DIR_FROM_HOST:
+		if (fsg->residue == 0)
+			;		// Nothing to receive
+
+		/* Did the host stop sending unexpectedly early? */
+		else if (fsg->short_packet_received) {
+			raise_exception(fsg, FSG_STATE_ABORT_BULK_OUT);
+			rc = -EINTR;
+		}
+
+		/* We haven't processed all the incoming data.  Even though
+		 * we may be allowed to stall, doing so would cause a race.
+		 * The controller may already have ACK'ed all the remaining
+		 * bulk-out packets, in which case the host wouldn't see a
+		 * STALL.  Not realizing the endpoint was halted, it wouldn't
+		 * clear the halt -- leading to problems later on. */
+#if 0
+		else if (mod_data.can_stall) {
+			fsg_set_halt(fsg, fsg->bulk_out);
+			raise_exception(fsg, FSG_STATE_ABORT_BULK_OUT);
+			rc = -EINTR;
+		}
+#endif
+
+		/* We can't stall.  Read in the excess data and throw it
+		 * all away. */
+		else
+			rc = throw_away_data(fsg);
+		break;
+	}
+	return rc;
+}
+
+
+static int send_status(struct fsg_dev *fsg)
+{
+	struct fsg_lun		*curlun = fsg->curlun;
+	struct fsg_buffhd	*bh;
+	int			rc;
+	u8			status = US_BULK_STAT_OK;
+	u32			sd, sdinfo = 0;
+
+	/* Wait for the next buffer to become available */
+	bh = fsg->next_buffhd_to_fill;
+	while (bh->state != BUF_STATE_EMPTY) {
+		rc = sleep_thread(fsg);
+		if (rc)
+			return rc;
+	}
+
+	if (curlun) {
+		sd = curlun->sense_data;
+		sdinfo = curlun->sense_data_info;
+	} else if (fsg->bad_lun_okay)
+		sd = SS_NO_SENSE;
+	else
+		sd = SS_LOGICAL_UNIT_NOT_SUPPORTED;
+
+	if (fsg->phase_error) {
+		DBG(fsg, "sending phase-error status\n");
+		status = US_BULK_STAT_PHASE;
+		sd = SS_INVALID_COMMAND;
+	} else if (sd != SS_NO_SENSE) {
+		DBG(fsg, "sending command-failure status\n");
+		status = US_BULK_STAT_FAIL;
+		VDBG(fsg, "  sense data: SK x%02x, ASC x%02x, ASCQ x%02x;"
+				"  info x%x\n",
+				SK(sd), ASC(sd), ASCQ(sd), sdinfo);
+	}
+
+	if (transport_is_bbb()) {
+		struct bulk_cs_wrap	*csw = bh->buf;
+
+		/* Store and send the Bulk-only CSW */
+		csw->Signature = cpu_to_le32(US_BULK_CS_SIGN);
+		csw->Tag = fsg->tag;
+		csw->Residue = cpu_to_le32(fsg->residue);
+		csw->Status = status;
+
+		bh->inreq->length = US_BULK_CS_WRAP_LEN;
+		bh->inreq->zero = 0;
+		start_transfer(fsg, fsg->bulk_in, bh->inreq,
+				&bh->inreq_busy, &bh->state);
+
+	} else if (mod_data.transport_type == USB_PR_CB) {
+
+		/* Control-Bulk transport has no status phase! */
+		return 0;
+
+	} else {			// USB_PR_CBI
+		struct interrupt_data	*buf = bh->buf;
+
+		/* Store and send the Interrupt data.  UFI sends the ASC
+		 * and ASCQ bytes.  Everything else sends a Type (which
+		 * is always 0) and the status Value. */
+		if (mod_data.protocol_type == USB_SC_UFI) {
+			buf->bType = ASC(sd);
+			buf->bValue = ASCQ(sd);
+		} else {
+			buf->bType = 0;
+			buf->bValue = status;
+		}
+		fsg->intreq->length = CBI_INTERRUPT_DATA_LEN;
+
+		fsg->intr_buffhd = bh;		// Point to the right buffhd
+		fsg->intreq->buf = bh->inreq->buf;
+		fsg->intreq->context = bh;
+		start_transfer(fsg, fsg->intr_in, fsg->intreq,
+				&fsg->intreq_busy, &bh->state);
+	}
+
+	fsg->next_buffhd_to_fill = bh->next;
+	return 0;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* Check whether the command is properly formed and whether its data size
+ * and direction agree with the values we already have. */
+static int check_command(struct fsg_dev *fsg, int cmnd_size,
+		enum data_direction data_dir, unsigned int mask,
+		int needs_medium, const char *name)
+{
+	int			i;
+	int			lun = fsg->cmnd[1] >> 5;
+	static const char	dirletter[4] = {'u', 'o', 'i', 'n'};
+	char			hdlen[20];
+	struct fsg_lun		*curlun;
+
+	/* Adjust the expected cmnd_size for protocol encapsulation padding.
+	 * Transparent SCSI doesn't pad. */
+	if (protocol_is_scsi())
+		;
+
+	/* There's some disagreement as to whether RBC pads commands or not.
+	 * We'll play it safe and accept either form. */
+	else if (mod_data.protocol_type == USB_SC_RBC) {
+		if (fsg->cmnd_size == 12)
+			cmnd_size = 12;
+
+	/* All the other protocols pad to 12 bytes */
+	} else
+		cmnd_size = 12;
+
+	hdlen[0] = 0;
+	if (fsg->data_dir != DATA_DIR_UNKNOWN)
+		sprintf(hdlen, ", H%c=%u", dirletter[(int) fsg->data_dir],
+				fsg->data_size);
+	VDBG(fsg, "SCSI command: %s;  Dc=%d, D%c=%u;  Hc=%d%s\n",
+			name, cmnd_size, dirletter[(int) data_dir],
+			fsg->data_size_from_cmnd, fsg->cmnd_size, hdlen);
+
+	/* We can't reply at all until we know the correct data direction
+	 * and size. */
+	if (fsg->data_size_from_cmnd == 0)
+		data_dir = DATA_DIR_NONE;
+	if (fsg->data_dir == DATA_DIR_UNKNOWN) {	// CB or CBI
+		fsg->data_dir = data_dir;
+		fsg->data_size = fsg->data_size_from_cmnd;
+
+	} else {					// Bulk-only
+		if (fsg->data_size < fsg->data_size_from_cmnd) {
+
+			/* Host data size < Device data size is a phase error.
+			 * Carry out the command, but only transfer as much
+			 * as we are allowed. */
+			fsg->data_size_from_cmnd = fsg->data_size;
+			fsg->phase_error = 1;
+		}
+	}
+	fsg->residue = fsg->usb_amount_left = fsg->data_size;
+
+	/* Conflicting data directions is a phase error */
+	if (fsg->data_dir != data_dir && fsg->data_size_from_cmnd > 0) {
+		fsg->phase_error = 1;
+		return -EINVAL;
+	}
+
+	/* Verify the length of the command itself */
+	if (cmnd_size != fsg->cmnd_size) {
+
+		/* Special case workaround: There are plenty of buggy SCSI
+		 * implementations. Many have issues with cbw->Length
+		 * field passing a wrong command size. For those cases we
+		 * always try to work around the problem by using the length
+		 * sent by the host side provided it is at least as large
+		 * as the correct command length.
+		 * Examples of such cases would be MS-Windows, which issues
+		 * REQUEST SENSE with cbw->Length == 12 where it should
+		 * be 6, and xbox360 issuing INQUIRY, TEST UNIT READY and
+		 * REQUEST SENSE with cbw->Length == 10 where it should
+		 * be 6 as well.
+		 */
+		if (cmnd_size <= fsg->cmnd_size) {
+			DBG(fsg, "%s is buggy! Expected length %d "
+					"but we got %d\n", name,
+					cmnd_size, fsg->cmnd_size);
+			cmnd_size = fsg->cmnd_size;
+		} else {
+			fsg->phase_error = 1;
+			return -EINVAL;
+		}
+	}
+
+	/* Check that the LUN values are consistent */
+	if (transport_is_bbb()) {
+		if (fsg->lun != lun)
+			DBG(fsg, "using LUN %d from CBW, "
+					"not LUN %d from CDB\n",
+					fsg->lun, lun);
+	}
+
+	/* Check the LUN */
+	curlun = fsg->curlun;
+	if (curlun) {
+		if (fsg->cmnd[0] != REQUEST_SENSE) {
+			curlun->sense_data = SS_NO_SENSE;
+			curlun->sense_data_info = 0;
+			curlun->info_valid = 0;
+		}
+	} else {
+		fsg->bad_lun_okay = 0;
+
+		/* INQUIRY and REQUEST SENSE commands are explicitly allowed
+		 * to use unsupported LUNs; all others may not. */
+		if (fsg->cmnd[0] != INQUIRY &&
+				fsg->cmnd[0] != REQUEST_SENSE) {
+			DBG(fsg, "unsupported LUN %d\n", fsg->lun);
+			return -EINVAL;
+		}
+	}
+
+	/* If a unit attention condition exists, only INQUIRY and
+	 * REQUEST SENSE commands are allowed; anything else must fail. */
+	if (curlun && curlun->unit_attention_data != SS_NO_SENSE &&
+			fsg->cmnd[0] != INQUIRY &&
+			fsg->cmnd[0] != REQUEST_SENSE) {
+		curlun->sense_data = curlun->unit_attention_data;
+		curlun->unit_attention_data = SS_NO_SENSE;
+		return -EINVAL;
+	}
+
+	/* Check that only command bytes listed in the mask are non-zero */
+	fsg->cmnd[1] &= 0x1f;			// Mask away the LUN
+	for (i = 1; i < cmnd_size; ++i) {
+		if (fsg->cmnd[i] && !(mask & (1 << i))) {
+			if (curlun)
+				curlun->sense_data = SS_INVALID_FIELD_IN_CDB;
+			return -EINVAL;
+		}
+	}
+
+	/* If the medium isn't mounted and the command needs to access
+	 * it, return an error. */
+	if (curlun && !fsg_lun_is_open(curlun) && needs_medium) {
+		curlun->sense_data = SS_MEDIUM_NOT_PRESENT;
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* wrapper of check_command for data size in blocks handling */
+static int check_command_size_in_blocks(struct fsg_dev *fsg, int cmnd_size,
+		enum data_direction data_dir, unsigned int mask,
+		int needs_medium, const char *name)
+{
+	if (fsg->curlun)
+		fsg->data_size_from_cmnd <<= fsg->curlun->blkbits;
+	return check_command(fsg, cmnd_size, data_dir,
+			mask, needs_medium, name);
+}
+
+static int do_scsi_command(struct fsg_dev *fsg)
+{
+	struct fsg_buffhd	*bh;
+	int			rc;
+	int			reply = -EINVAL;
+	int			i;
+	static char		unknown[16];
+
+	dump_cdb(fsg);
+
+	/* Wait for the next buffer to become available for data or status */
+	bh = fsg->next_buffhd_to_drain = fsg->next_buffhd_to_fill;
+	while (bh->state != BUF_STATE_EMPTY) {
+		rc = sleep_thread(fsg);
+		if (rc)
+			return rc;
+	}
+	fsg->phase_error = 0;
+	fsg->short_packet_received = 0;
+
+	down_read(&fsg->filesem);	// We're using the backing file
+	switch (fsg->cmnd[0]) {
+
+	case INQUIRY:
+		fsg->data_size_from_cmnd = fsg->cmnd[4];
+		if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST,
+				(1<<4), 0,
+				"INQUIRY")) == 0)
+			reply = do_inquiry(fsg, bh);
+		break;
+
+	case MODE_SELECT:
+		fsg->data_size_from_cmnd = fsg->cmnd[4];
+		if ((reply = check_command(fsg, 6, DATA_DIR_FROM_HOST,
+				(1<<1) | (1<<4), 0,
+				"MODE SELECT(6)")) == 0)
+			reply = do_mode_select(fsg, bh);
+		break;
+
+	case MODE_SELECT_10:
+		fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]);
+		if ((reply = check_command(fsg, 10, DATA_DIR_FROM_HOST,
+				(1<<1) | (3<<7), 0,
+				"MODE SELECT(10)")) == 0)
+			reply = do_mode_select(fsg, bh);
+		break;
+
+	case MODE_SENSE:
+		fsg->data_size_from_cmnd = fsg->cmnd[4];
+		if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST,
+				(1<<1) | (1<<2) | (1<<4), 0,
+				"MODE SENSE(6)")) == 0)
+			reply = do_mode_sense(fsg, bh);
+		break;
+
+	case MODE_SENSE_10:
+		fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]);
+		if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST,
+				(1<<1) | (1<<2) | (3<<7), 0,
+				"MODE SENSE(10)")) == 0)
+			reply = do_mode_sense(fsg, bh);
+		break;
+
+	case ALLOW_MEDIUM_REMOVAL:
+		fsg->data_size_from_cmnd = 0;
+		if ((reply = check_command(fsg, 6, DATA_DIR_NONE,
+				(1<<4), 0,
+				"PREVENT-ALLOW MEDIUM REMOVAL")) == 0)
+			reply = do_prevent_allow(fsg);
+		break;
+
+	case READ_6:
+		i = fsg->cmnd[4];
+		fsg->data_size_from_cmnd = (i == 0) ? 256 : i;
+		if ((reply = check_command_size_in_blocks(fsg, 6,
+				DATA_DIR_TO_HOST,
+				(7<<1) | (1<<4), 1,
+				"READ(6)")) == 0)
+			reply = do_read(fsg);
+		break;
+
+	case READ_10:
+		fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]);
+		if ((reply = check_command_size_in_blocks(fsg, 10,
+				DATA_DIR_TO_HOST,
+				(1<<1) | (0xf<<2) | (3<<7), 1,
+				"READ(10)")) == 0)
+			reply = do_read(fsg);
+		break;
+
+	case READ_12:
+		fsg->data_size_from_cmnd = get_unaligned_be32(&fsg->cmnd[6]);
+		if ((reply = check_command_size_in_blocks(fsg, 12,
+				DATA_DIR_TO_HOST,
+				(1<<1) | (0xf<<2) | (0xf<<6), 1,
+				"READ(12)")) == 0)
+			reply = do_read(fsg);
+		break;
+
+	case READ_CAPACITY:
+		fsg->data_size_from_cmnd = 8;
+		if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST,
+				(0xf<<2) | (1<<8), 1,
+				"READ CAPACITY")) == 0)
+			reply = do_read_capacity(fsg, bh);
+		break;
+
+	case READ_HEADER:
+		if (!mod_data.cdrom)
+			goto unknown_cmnd;
+		fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]);
+		if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST,
+				(3<<7) | (0x1f<<1), 1,
+				"READ HEADER")) == 0)
+			reply = do_read_header(fsg, bh);
+		break;
+
+	case READ_TOC:
+		if (!mod_data.cdrom)
+			goto unknown_cmnd;
+		fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]);
+		if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST,
+				(7<<6) | (1<<1), 1,
+				"READ TOC")) == 0)
+			reply = do_read_toc(fsg, bh);
+		break;
+
+	case READ_FORMAT_CAPACITIES:
+		fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]);
+		if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST,
+				(3<<7), 1,
+				"READ FORMAT CAPACITIES")) == 0)
+			reply = do_read_format_capacities(fsg, bh);
+		break;
+
+	case REQUEST_SENSE:
+		fsg->data_size_from_cmnd = fsg->cmnd[4];
+		if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST,
+				(1<<4), 0,
+				"REQUEST SENSE")) == 0)
+			reply = do_request_sense(fsg, bh);
+		break;
+
+	case START_STOP:
+		fsg->data_size_from_cmnd = 0;
+		if ((reply = check_command(fsg, 6, DATA_DIR_NONE,
+				(1<<1) | (1<<4), 0,
+				"START-STOP UNIT")) == 0)
+			reply = do_start_stop(fsg);
+		break;
+
+	case SYNCHRONIZE_CACHE:
+		fsg->data_size_from_cmnd = 0;
+		if ((reply = check_command(fsg, 10, DATA_DIR_NONE,
+				(0xf<<2) | (3<<7), 1,
+				"SYNCHRONIZE CACHE")) == 0)
+			reply = do_synchronize_cache(fsg);
+		break;
+
+	case TEST_UNIT_READY:
+		fsg->data_size_from_cmnd = 0;
+		reply = check_command(fsg, 6, DATA_DIR_NONE,
+				0, 1,
+				"TEST UNIT READY");
+		break;
+
+	/* Although optional, this command is used by MS-Windows.  We
+	 * support a minimal version: BytChk must be 0. */
+	case VERIFY:
+		fsg->data_size_from_cmnd = 0;
+		if ((reply = check_command(fsg, 10, DATA_DIR_NONE,
+				(1<<1) | (0xf<<2) | (3<<7), 1,
+				"VERIFY")) == 0)
+			reply = do_verify(fsg);
+		break;
+
+	case WRITE_6:
+		i = fsg->cmnd[4];
+		fsg->data_size_from_cmnd = (i == 0) ? 256 : i;
+		if ((reply = check_command_size_in_blocks(fsg, 6,
+				DATA_DIR_FROM_HOST,
+				(7<<1) | (1<<4), 1,
+				"WRITE(6)")) == 0)
+			reply = do_write(fsg);
+		break;
+
+	case WRITE_10:
+		fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]);
+		if ((reply = check_command_size_in_blocks(fsg, 10,
+				DATA_DIR_FROM_HOST,
+				(1<<1) | (0xf<<2) | (3<<7), 1,
+				"WRITE(10)")) == 0)
+			reply = do_write(fsg);
+		break;
+
+	case WRITE_12:
+		fsg->data_size_from_cmnd = get_unaligned_be32(&fsg->cmnd[6]);
+		if ((reply = check_command_size_in_blocks(fsg, 12,
+				DATA_DIR_FROM_HOST,
+				(1<<1) | (0xf<<2) | (0xf<<6), 1,
+				"WRITE(12)")) == 0)
+			reply = do_write(fsg);
+		break;
+
+	/* Some mandatory commands that we recognize but don't implement.
+	 * They don't mean much in this setting.  It's left as an exercise
+	 * for anyone interested to implement RESERVE and RELEASE in terms
+	 * of Posix locks. */
+	case FORMAT_UNIT:
+	case RELEASE:
+	case RESERVE:
+	case SEND_DIAGNOSTIC:
+		// Fall through
+
+	default:
+ unknown_cmnd:
+		fsg->data_size_from_cmnd = 0;
+		sprintf(unknown, "Unknown x%02x", fsg->cmnd[0]);
+		if ((reply = check_command(fsg, fsg->cmnd_size,
+				DATA_DIR_UNKNOWN, ~0, 0, unknown)) == 0) {
+			fsg->curlun->sense_data = SS_INVALID_COMMAND;
+			reply = -EINVAL;
+		}
+		break;
+	}
+	up_read(&fsg->filesem);
+
+	if (reply == -EINTR || signal_pending(current))
+		return -EINTR;
+
+	/* Set up the single reply buffer for finish_reply() */
+	if (reply == -EINVAL)
+		reply = 0;		// Error reply length
+	if (reply >= 0 && fsg->data_dir == DATA_DIR_TO_HOST) {
+		reply = min((u32) reply, fsg->data_size_from_cmnd);
+		bh->inreq->length = reply;
+		bh->state = BUF_STATE_FULL;
+		fsg->residue -= reply;
+	}				// Otherwise it's already set
+
+	return 0;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int received_cbw(struct fsg_dev *fsg, struct fsg_buffhd *bh)
+{
+	struct usb_request		*req = bh->outreq;
+	struct bulk_cb_wrap	*cbw = req->buf;
+
+	/* Was this a real packet?  Should it be ignored? */
+	if (req->status || test_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags))
+		return -EINVAL;
+
+	/* Is the CBW valid? */
+	if (req->actual != US_BULK_CB_WRAP_LEN ||
+			cbw->Signature != cpu_to_le32(
+				US_BULK_CB_SIGN)) {
+		DBG(fsg, "invalid CBW: len %u sig 0x%x\n",
+				req->actual,
+				le32_to_cpu(cbw->Signature));
+
+		/* The Bulk-only spec says we MUST stall the IN endpoint
+		 * (6.6.1), so it's unavoidable.  It also says we must
+		 * retain this state until the next reset, but there's
+		 * no way to tell the controller driver it should ignore
+		 * Clear-Feature(HALT) requests.
+		 *
+		 * We aren't required to halt the OUT endpoint; instead
+		 * we can simply accept and discard any data received
+		 * until the next reset. */
+		wedge_bulk_in_endpoint(fsg);
+		set_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags);
+		return -EINVAL;
+	}
+
+	/* Is the CBW meaningful? */
+	if (cbw->Lun >= FSG_MAX_LUNS || cbw->Flags & ~US_BULK_FLAG_IN ||
+			cbw->Length <= 0 || cbw->Length > MAX_COMMAND_SIZE) {
+		DBG(fsg, "non-meaningful CBW: lun = %u, flags = 0x%x, "
+				"cmdlen %u\n",
+				cbw->Lun, cbw->Flags, cbw->Length);
+
+		/* We can do anything we want here, so let's stall the
+		 * bulk pipes if we are allowed to. */
+		if (mod_data.can_stall) {
+			fsg_set_halt(fsg, fsg->bulk_out);
+			halt_bulk_in_endpoint(fsg);
+		}
+		return -EINVAL;
+	}
+
+	/* Save the command for later */
+	fsg->cmnd_size = cbw->Length;
+	memcpy(fsg->cmnd, cbw->CDB, fsg->cmnd_size);
+	if (cbw->Flags & US_BULK_FLAG_IN)
+		fsg->data_dir = DATA_DIR_TO_HOST;
+	else
+		fsg->data_dir = DATA_DIR_FROM_HOST;
+	fsg->data_size = le32_to_cpu(cbw->DataTransferLength);
+	if (fsg->data_size == 0)
+		fsg->data_dir = DATA_DIR_NONE;
+	fsg->lun = cbw->Lun;
+	fsg->tag = cbw->Tag;
+	return 0;
+}
+
+
+static int get_next_command(struct fsg_dev *fsg)
+{
+	struct fsg_buffhd	*bh;
+	int			rc = 0;
+
+	if (transport_is_bbb()) {
+
+		/* Wait for the next buffer to become available */
+		bh = fsg->next_buffhd_to_fill;
+		while (bh->state != BUF_STATE_EMPTY) {
+			rc = sleep_thread(fsg);
+			if (rc)
+				return rc;
+		}
+
+		/* Queue a request to read a Bulk-only CBW */
+		set_bulk_out_req_length(fsg, bh, US_BULK_CB_WRAP_LEN);
+		start_transfer(fsg, fsg->bulk_out, bh->outreq,
+				&bh->outreq_busy, &bh->state);
+
+		/* We will drain the buffer in software, which means we
+		 * can reuse it for the next filling.  No need to advance
+		 * next_buffhd_to_fill. */
+
+		/* Wait for the CBW to arrive */
+		while (bh->state != BUF_STATE_FULL) {
+			rc = sleep_thread(fsg);
+			if (rc)
+				return rc;
+		}
+		smp_rmb();
+		rc = received_cbw(fsg, bh);
+		bh->state = BUF_STATE_EMPTY;
+
+	} else {		// USB_PR_CB or USB_PR_CBI
+
+		/* Wait for the next command to arrive */
+		while (fsg->cbbuf_cmnd_size == 0) {
+			rc = sleep_thread(fsg);
+			if (rc)
+				return rc;
+		}
+
+		/* Is the previous status interrupt request still busy?
+		 * The host is allowed to skip reading the status,
+		 * so we must cancel it. */
+		if (fsg->intreq_busy)
+			usb_ep_dequeue(fsg->intr_in, fsg->intreq);
+
+		/* Copy the command and mark the buffer empty */
+		fsg->data_dir = DATA_DIR_UNKNOWN;
+		spin_lock_irq(&fsg->lock);
+		fsg->cmnd_size = fsg->cbbuf_cmnd_size;
+		memcpy(fsg->cmnd, fsg->cbbuf_cmnd, fsg->cmnd_size);
+		fsg->cbbuf_cmnd_size = 0;
+		spin_unlock_irq(&fsg->lock);
+
+		/* Use LUN from the command */
+		fsg->lun = fsg->cmnd[1] >> 5;
+	}
+
+	/* Update current lun */
+	if (fsg->lun >= 0 && fsg->lun < fsg->nluns)
+		fsg->curlun = &fsg->luns[fsg->lun];
+	else
+		fsg->curlun = NULL;
+
+	return rc;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int enable_endpoint(struct fsg_dev *fsg, struct usb_ep *ep,
+		const struct usb_endpoint_descriptor *d)
+{
+	int	rc;
+
+	ep->driver_data = fsg;
+	ep->desc = d;
+	rc = usb_ep_enable(ep);
+	if (rc)
+		ERROR(fsg, "can't enable %s, result %d\n", ep->name, rc);
+	return rc;
+}
+
+static int alloc_request(struct fsg_dev *fsg, struct usb_ep *ep,
+		struct usb_request **preq)
+{
+	*preq = usb_ep_alloc_request(ep, GFP_ATOMIC);
+	if (*preq)
+		return 0;
+	ERROR(fsg, "can't allocate request for %s\n", ep->name);
+	return -ENOMEM;
+}
+
+/*
+ * Reset interface setting and re-init endpoint state (toggle etc).
+ * Call with altsetting < 0 to disable the interface.  The only other
+ * available altsetting is 0, which enables the interface.
+ */
+static int do_set_interface(struct fsg_dev *fsg, int altsetting)
+{
+	int	rc = 0;
+	int	i;
+	const struct usb_endpoint_descriptor	*d;
+
+	if (fsg->running)
+		DBG(fsg, "reset interface\n");
+
+reset:
+	/* Deallocate the requests */
+	for (i = 0; i < fsg_num_buffers; ++i) {
+		struct fsg_buffhd *bh = &fsg->buffhds[i];
+
+		if (bh->inreq) {
+			usb_ep_free_request(fsg->bulk_in, bh->inreq);
+			bh->inreq = NULL;
+		}
+		if (bh->outreq) {
+			usb_ep_free_request(fsg->bulk_out, bh->outreq);
+			bh->outreq = NULL;
+		}
+	}
+	if (fsg->intreq) {
+		usb_ep_free_request(fsg->intr_in, fsg->intreq);
+		fsg->intreq = NULL;
+	}
+
+	/* Disable the endpoints */
+	if (fsg->bulk_in_enabled) {
+		usb_ep_disable(fsg->bulk_in);
+		fsg->bulk_in_enabled = 0;
+	}
+	if (fsg->bulk_out_enabled) {
+		usb_ep_disable(fsg->bulk_out);
+		fsg->bulk_out_enabled = 0;
+	}
+	if (fsg->intr_in_enabled) {
+		usb_ep_disable(fsg->intr_in);
+		fsg->intr_in_enabled = 0;
+	}
+
+	fsg->running = 0;
+	if (altsetting < 0 || rc != 0)
+		return rc;
+
+	DBG(fsg, "set interface %d\n", altsetting);
+
+	/* Enable the endpoints */
+	d = fsg_ep_desc(fsg->gadget,
+			&fsg_fs_bulk_in_desc, &fsg_hs_bulk_in_desc,
+			&fsg_ss_bulk_in_desc);
+	if ((rc = enable_endpoint(fsg, fsg->bulk_in, d)) != 0)
+		goto reset;
+	fsg->bulk_in_enabled = 1;
+
+	d = fsg_ep_desc(fsg->gadget,
+			&fsg_fs_bulk_out_desc, &fsg_hs_bulk_out_desc,
+			&fsg_ss_bulk_out_desc);
+	if ((rc = enable_endpoint(fsg, fsg->bulk_out, d)) != 0)
+		goto reset;
+	fsg->bulk_out_enabled = 1;
+	fsg->bulk_out_maxpacket = usb_endpoint_maxp(d);
+	clear_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags);
+
+	if (transport_is_cbi()) {
+		d = fsg_ep_desc(fsg->gadget,
+				&fsg_fs_intr_in_desc, &fsg_hs_intr_in_desc,
+				&fsg_ss_intr_in_desc);
+		if ((rc = enable_endpoint(fsg, fsg->intr_in, d)) != 0)
+			goto reset;
+		fsg->intr_in_enabled = 1;
+	}
+
+	/* Allocate the requests */
+	for (i = 0; i < fsg_num_buffers; ++i) {
+		struct fsg_buffhd	*bh = &fsg->buffhds[i];
+
+		if ((rc = alloc_request(fsg, fsg->bulk_in, &bh->inreq)) != 0)
+			goto reset;
+		if ((rc = alloc_request(fsg, fsg->bulk_out, &bh->outreq)) != 0)
+			goto reset;
+		bh->inreq->buf = bh->outreq->buf = bh->buf;
+		bh->inreq->context = bh->outreq->context = bh;
+		bh->inreq->complete = bulk_in_complete;
+		bh->outreq->complete = bulk_out_complete;
+	}
+	if (transport_is_cbi()) {
+		if ((rc = alloc_request(fsg, fsg->intr_in, &fsg->intreq)) != 0)
+			goto reset;
+		fsg->intreq->complete = intr_in_complete;
+	}
+
+	fsg->running = 1;
+	for (i = 0; i < fsg->nluns; ++i)
+		fsg->luns[i].unit_attention_data = SS_RESET_OCCURRED;
+	return rc;
+}
+
+
+/*
+ * Change our operational configuration.  This code must agree with the code
+ * that returns config descriptors, and with interface altsetting code.
+ *
+ * It's also responsible for power management interactions.  Some
+ * configurations might not work with our current power sources.
+ * For now we just assume the gadget is always self-powered.
+ */
+static int do_set_config(struct fsg_dev *fsg, u8 new_config)
+{
+	int	rc = 0;
+
+	/* Disable the single interface */
+	if (fsg->config != 0) {
+		DBG(fsg, "reset config\n");
+		fsg->config = 0;
+		rc = do_set_interface(fsg, -1);
+	}
+
+	/* Enable the interface */
+	if (new_config != 0) {
+		fsg->config = new_config;
+		if ((rc = do_set_interface(fsg, 0)) != 0)
+			fsg->config = 0;	// Reset on errors
+		else
+			INFO(fsg, "%s config #%d\n",
+			     usb_speed_string(fsg->gadget->speed),
+			     fsg->config);
+	}
+	return rc;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static void handle_exception(struct fsg_dev *fsg)
+{
+	siginfo_t		info;
+	int			sig;
+	int			i;
+	int			num_active;
+	struct fsg_buffhd	*bh;
+	enum fsg_state		old_state;
+	u8			new_config;
+	struct fsg_lun		*curlun;
+	unsigned int		exception_req_tag;
+	int			rc;
+
+	/* Clear the existing signals.  Anything but SIGUSR1 is converted
+	 * into a high-priority EXIT exception. */
+	for (;;) {
+		sig = dequeue_signal_lock(current, &current->blocked, &info);
+		if (!sig)
+			break;
+		if (sig != SIGUSR1) {
+			if (fsg->state < FSG_STATE_EXIT)
+				DBG(fsg, "Main thread exiting on signal\n");
+			raise_exception(fsg, FSG_STATE_EXIT);
+		}
+	}
+
+	/* Cancel all the pending transfers */
+	if (fsg->intreq_busy)
+		usb_ep_dequeue(fsg->intr_in, fsg->intreq);
+	for (i = 0; i < fsg_num_buffers; ++i) {
+		bh = &fsg->buffhds[i];
+		if (bh->inreq_busy)
+			usb_ep_dequeue(fsg->bulk_in, bh->inreq);
+		if (bh->outreq_busy)
+			usb_ep_dequeue(fsg->bulk_out, bh->outreq);
+	}
+
+	/* Wait until everything is idle */
+	for (;;) {
+		num_active = fsg->intreq_busy;
+		for (i = 0; i < fsg_num_buffers; ++i) {
+			bh = &fsg->buffhds[i];
+			num_active += bh->inreq_busy + bh->outreq_busy;
+		}
+		if (num_active == 0)
+			break;
+		if (sleep_thread(fsg))
+			return;
+	}
+
+	/* Clear out the controller's fifos */
+	if (fsg->bulk_in_enabled)
+		usb_ep_fifo_flush(fsg->bulk_in);
+	if (fsg->bulk_out_enabled)
+		usb_ep_fifo_flush(fsg->bulk_out);
+	if (fsg->intr_in_enabled)
+		usb_ep_fifo_flush(fsg->intr_in);
+
+	/* Reset the I/O buffer states and pointers, the SCSI
+	 * state, and the exception.  Then invoke the handler. */
+	spin_lock_irq(&fsg->lock);
+
+	for (i = 0; i < fsg_num_buffers; ++i) {
+		bh = &fsg->buffhds[i];
+		bh->state = BUF_STATE_EMPTY;
+	}
+	fsg->next_buffhd_to_fill = fsg->next_buffhd_to_drain =
+			&fsg->buffhds[0];
+
+	exception_req_tag = fsg->exception_req_tag;
+	new_config = fsg->new_config;
+	old_state = fsg->state;
+
+	if (old_state == FSG_STATE_ABORT_BULK_OUT)
+		fsg->state = FSG_STATE_STATUS_PHASE;
+	else {
+		for (i = 0; i < fsg->nluns; ++i) {
+			curlun = &fsg->luns[i];
+			curlun->prevent_medium_removal = 0;
+			curlun->sense_data = curlun->unit_attention_data =
+					SS_NO_SENSE;
+			curlun->sense_data_info = 0;
+			curlun->info_valid = 0;
+		}
+		fsg->state = FSG_STATE_IDLE;
+	}
+	spin_unlock_irq(&fsg->lock);
+
+	/* Carry out any extra actions required for the exception */
+	switch (old_state) {
+	default:
+		break;
+
+	case FSG_STATE_ABORT_BULK_OUT:
+		send_status(fsg);
+		spin_lock_irq(&fsg->lock);
+		if (fsg->state == FSG_STATE_STATUS_PHASE)
+			fsg->state = FSG_STATE_IDLE;
+		spin_unlock_irq(&fsg->lock);
+		break;
+
+	case FSG_STATE_RESET:
+		/* In case we were forced against our will to halt a
+		 * bulk endpoint, clear the halt now.  (The SuperH UDC
+		 * requires this.) */
+		if (test_and_clear_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags))
+			usb_ep_clear_halt(fsg->bulk_in);
+
+		if (transport_is_bbb()) {
+			if (fsg->ep0_req_tag == exception_req_tag)
+				ep0_queue(fsg);	// Complete the status stage
+
+		} else if (transport_is_cbi())
+			send_status(fsg);	// Status by interrupt pipe
+
+		/* Technically this should go here, but it would only be
+		 * a waste of time.  Ditto for the INTERFACE_CHANGE and
+		 * CONFIG_CHANGE cases. */
+		// for (i = 0; i < fsg->nluns; ++i)
+		//	fsg->luns[i].unit_attention_data = SS_RESET_OCCURRED;
+		break;
+
+	case FSG_STATE_INTERFACE_CHANGE:
+		rc = do_set_interface(fsg, 0);
+		if (fsg->ep0_req_tag != exception_req_tag)
+			break;
+		if (rc != 0)			// STALL on errors
+			fsg_set_halt(fsg, fsg->ep0);
+		else				// Complete the status stage
+			ep0_queue(fsg);
+		break;
+
+	case FSG_STATE_CONFIG_CHANGE:
+		rc = do_set_config(fsg, new_config);
+		if (fsg->ep0_req_tag != exception_req_tag)
+			break;
+		if (rc != 0)			// STALL on errors
+			fsg_set_halt(fsg, fsg->ep0);
+		else				// Complete the status stage
+			ep0_queue(fsg);
+		break;
+
+	case FSG_STATE_DISCONNECT:
+		for (i = 0; i < fsg->nluns; ++i)
+			fsg_lun_fsync_sub(fsg->luns + i);
+		do_set_config(fsg, 0);		// Unconfigured state
+		break;
+
+	case FSG_STATE_EXIT:
+	case FSG_STATE_TERMINATED:
+		do_set_config(fsg, 0);			// Free resources
+		spin_lock_irq(&fsg->lock);
+		fsg->state = FSG_STATE_TERMINATED;	// Stop the thread
+		spin_unlock_irq(&fsg->lock);
+		break;
+	}
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static int fsg_main_thread(void *fsg_)
+{
+	struct fsg_dev		*fsg = fsg_;
+
+	/* Allow the thread to be killed by a signal, but set the signal mask
+	 * to block everything but INT, TERM, KILL, and USR1. */
+	allow_signal(SIGINT);
+	allow_signal(SIGTERM);
+	allow_signal(SIGKILL);
+	allow_signal(SIGUSR1);
+
+	/* Allow the thread to be frozen */
+	set_freezable();
+
+	/* Arrange for userspace references to be interpreted as kernel
+	 * pointers.  That way we can pass a kernel pointer to a routine
+	 * that expects a __user pointer and it will work okay. */
+	set_fs(get_ds());
+
+	/* The main loop */
+	while (fsg->state != FSG_STATE_TERMINATED) {
+		if (exception_in_progress(fsg) || signal_pending(current)) {
+			handle_exception(fsg);
+			continue;
+		}
+
+		if (!fsg->running) {
+			sleep_thread(fsg);
+			continue;
+		}
+
+		if (get_next_command(fsg))
+			continue;
+
+		spin_lock_irq(&fsg->lock);
+		if (!exception_in_progress(fsg))
+			fsg->state = FSG_STATE_DATA_PHASE;
+		spin_unlock_irq(&fsg->lock);
+
+		if (do_scsi_command(fsg) || finish_reply(fsg))
+			continue;
+
+		spin_lock_irq(&fsg->lock);
+		if (!exception_in_progress(fsg))
+			fsg->state = FSG_STATE_STATUS_PHASE;
+		spin_unlock_irq(&fsg->lock);
+
+		if (send_status(fsg))
+			continue;
+
+		spin_lock_irq(&fsg->lock);
+		if (!exception_in_progress(fsg))
+			fsg->state = FSG_STATE_IDLE;
+		spin_unlock_irq(&fsg->lock);
+		}
+
+	spin_lock_irq(&fsg->lock);
+	fsg->thread_task = NULL;
+	spin_unlock_irq(&fsg->lock);
+
+	/* If we are exiting because of a signal, unregister the
+	 * gadget driver. */
+	if (test_and_clear_bit(REGISTERED, &fsg->atomic_bitflags))
+		usb_gadget_unregister_driver(&fsg_driver);
+
+	/* Let the unbind and cleanup routines know the thread has exited */
+	complete_and_exit(&fsg->thread_notifier, 0);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+
+/* The write permissions and store_xxx pointers are set in fsg_bind() */
+static DEVICE_ATTR(ro, 0444, fsg_show_ro, NULL);
+static DEVICE_ATTR(nofua, 0644, fsg_show_nofua, NULL);
+static DEVICE_ATTR(file, 0444, fsg_show_file, NULL);
+
+
+/*-------------------------------------------------------------------------*/
+
+static void fsg_release(struct kref *ref)
+{
+	struct fsg_dev	*fsg = container_of(ref, struct fsg_dev, ref);
+
+	kfree(fsg->luns);
+	kfree(fsg);
+}
+
+static void lun_release(struct device *dev)
+{
+	struct rw_semaphore	*filesem = dev_get_drvdata(dev);
+	struct fsg_dev		*fsg =
+		container_of(filesem, struct fsg_dev, filesem);
+
+	kref_put(&fsg->ref, fsg_release);
+}
+
+static void /* __init_or_exit */ fsg_unbind(struct usb_gadget *gadget)
+{
+	struct fsg_dev		*fsg = get_gadget_data(gadget);
+	int			i;
+	struct fsg_lun		*curlun;
+	struct usb_request	*req = fsg->ep0req;
+
+	DBG(fsg, "unbind\n");
+	clear_bit(REGISTERED, &fsg->atomic_bitflags);
+
+	/* If the thread isn't already dead, tell it to exit now */
+	if (fsg->state != FSG_STATE_TERMINATED) {
+		raise_exception(fsg, FSG_STATE_EXIT);
+		wait_for_completion(&fsg->thread_notifier);
+
+		/* The cleanup routine waits for this completion also */
+		complete(&fsg->thread_notifier);
+	}
+
+	/* Unregister the sysfs attribute files and the LUNs */
+	for (i = 0; i < fsg->nluns; ++i) {
+		curlun = &fsg->luns[i];
+		if (curlun->registered) {
+			device_remove_file(&curlun->dev, &dev_attr_nofua);
+			device_remove_file(&curlun->dev, &dev_attr_ro);
+			device_remove_file(&curlun->dev, &dev_attr_file);
+			fsg_lun_close(curlun);
+			device_unregister(&curlun->dev);
+			curlun->registered = 0;
+		}
+	}
+
+	/* Free the data buffers */
+	for (i = 0; i < fsg_num_buffers; ++i)
+		kfree(fsg->buffhds[i].buf);
+
+	/* Free the request and buffer for endpoint 0 */
+	if (req) {
+		kfree(req->buf);
+		usb_ep_free_request(fsg->ep0, req);
+	}
+
+	set_gadget_data(gadget, NULL);
+}
+
+
+static int __init check_parameters(struct fsg_dev *fsg)
+{
+	int	prot;
+	int	gcnum;
+
+	/* Store the default values */
+	mod_data.transport_type = USB_PR_BULK;
+	mod_data.transport_name = "Bulk-only";
+	mod_data.protocol_type = USB_SC_SCSI;
+	mod_data.protocol_name = "Transparent SCSI";
+
+	/* Some peripheral controllers are known not to be able to
+	 * halt bulk endpoints correctly.  If one of them is present,
+	 * disable stalls.
+	 */
+	if (gadget_is_at91(fsg->gadget))
+		mod_data.can_stall = 0;
+
+	if (mod_data.release == 0xffff) {	// Parameter wasn't set
+		gcnum = usb_gadget_controller_number(fsg->gadget);
+		if (gcnum >= 0)
+			mod_data.release = 0x0300 + gcnum;
+		else {
+			WARNING(fsg, "controller '%s' not recognized\n",
+				fsg->gadget->name);
+			mod_data.release = 0x0399;
+		}
+	}
+
+	prot = simple_strtol(mod_data.protocol_parm, NULL, 0);
+
+#ifdef CONFIG_USB_FILE_STORAGE_TEST
+	if (strnicmp(mod_data.transport_parm, "BBB", 10) == 0) {
+		;		// Use default setting
+	} else if (strnicmp(mod_data.transport_parm, "CB", 10) == 0) {
+		mod_data.transport_type = USB_PR_CB;
+		mod_data.transport_name = "Control-Bulk";
+	} else if (strnicmp(mod_data.transport_parm, "CBI", 10) == 0) {
+		mod_data.transport_type = USB_PR_CBI;
+		mod_data.transport_name = "Control-Bulk-Interrupt";
+	} else {
+		ERROR(fsg, "invalid transport: %s\n", mod_data.transport_parm);
+		return -EINVAL;
+	}
+
+	if (strnicmp(mod_data.protocol_parm, "SCSI", 10) == 0 ||
+			prot == USB_SC_SCSI) {
+		;		// Use default setting
+	} else if (strnicmp(mod_data.protocol_parm, "RBC", 10) == 0 ||
+			prot == USB_SC_RBC) {
+		mod_data.protocol_type = USB_SC_RBC;
+		mod_data.protocol_name = "RBC";
+	} else if (strnicmp(mod_data.protocol_parm, "8020", 4) == 0 ||
+			strnicmp(mod_data.protocol_parm, "ATAPI", 10) == 0 ||
+			prot == USB_SC_8020) {
+		mod_data.protocol_type = USB_SC_8020;
+		mod_data.protocol_name = "8020i (ATAPI)";
+	} else if (strnicmp(mod_data.protocol_parm, "QIC", 3) == 0 ||
+			prot == USB_SC_QIC) {
+		mod_data.protocol_type = USB_SC_QIC;
+		mod_data.protocol_name = "QIC-157";
+	} else if (strnicmp(mod_data.protocol_parm, "UFI", 10) == 0 ||
+			prot == USB_SC_UFI) {
+		mod_data.protocol_type = USB_SC_UFI;
+		mod_data.protocol_name = "UFI";
+	} else if (strnicmp(mod_data.protocol_parm, "8070", 4) == 0 ||
+			prot == USB_SC_8070) {
+		mod_data.protocol_type = USB_SC_8070;
+		mod_data.protocol_name = "8070i";
+	} else {
+		ERROR(fsg, "invalid protocol: %s\n", mod_data.protocol_parm);
+		return -EINVAL;
+	}
+
+	mod_data.buflen &= PAGE_CACHE_MASK;
+	if (mod_data.buflen <= 0) {
+		ERROR(fsg, "invalid buflen\n");
+		return -ETOOSMALL;
+	}
+
+#endif /* CONFIG_USB_FILE_STORAGE_TEST */
+
+	/* Serial string handling.
+	 * On a real device, the serial string would be loaded
+	 * from permanent storage. */
+	if (mod_data.serial) {
+		const char *ch;
+		unsigned len = 0;
+
+		/* Sanity check :
+		 * The CB[I] specification limits the serial string to
+		 * 12 uppercase hexadecimal characters.
+		 * BBB need at least 12 uppercase hexadecimal characters,
+		 * with a maximum of 126. */
+		for (ch = mod_data.serial; *ch; ++ch) {
+			++len;
+			if ((*ch < '0' || *ch > '9') &&
+			    (*ch < 'A' || *ch > 'F')) { /* not uppercase hex */
+				WARNING(fsg,
+					"Invalid serial string character: %c\n",
+					*ch);
+				goto no_serial;
+			}
+		}
+		if (len > 126 ||
+		    (mod_data.transport_type == USB_PR_BULK && len < 12) ||
+		    (mod_data.transport_type != USB_PR_BULK && len > 12)) {
+			WARNING(fsg, "Invalid serial string length!\n");
+			goto no_serial;
+		}
+		fsg_strings[FSG_STRING_SERIAL - 1].s = mod_data.serial;
+	} else {
+		WARNING(fsg, "No serial-number string provided!\n");
+ no_serial:
+		device_desc.iSerialNumber = 0;
+	}
+
+	return 0;
+}
+
+
+static int __init fsg_bind(struct usb_gadget *gadget)
+{
+	struct fsg_dev		*fsg = the_fsg;
+	int			rc;
+	int			i;
+	struct fsg_lun		*curlun;
+	struct usb_ep		*ep;
+	struct usb_request	*req;
+	char			*pathbuf, *p;
+
+	fsg->gadget = gadget;
+	set_gadget_data(gadget, fsg);
+	fsg->ep0 = gadget->ep0;
+	fsg->ep0->driver_data = fsg;
+
+	if ((rc = check_parameters(fsg)) != 0)
+		goto out;
+
+	if (mod_data.removable) {	// Enable the store_xxx attributes
+		dev_attr_file.attr.mode = 0644;
+		dev_attr_file.store = fsg_store_file;
+		if (!mod_data.cdrom) {
+			dev_attr_ro.attr.mode = 0644;
+			dev_attr_ro.store = fsg_store_ro;
+		}
+	}
+
+	/* Only for removable media? */
+	dev_attr_nofua.attr.mode = 0644;
+	dev_attr_nofua.store = fsg_store_nofua;
+
+	/* Find out how many LUNs there should be */
+	i = mod_data.nluns;
+	if (i == 0)
+		i = max(mod_data.num_filenames, 1u);
+	if (i > FSG_MAX_LUNS) {
+		ERROR(fsg, "invalid number of LUNs: %d\n", i);
+		rc = -EINVAL;
+		goto out;
+	}
+
+	/* Create the LUNs, open their backing files, and register the
+	 * LUN devices in sysfs. */
+	fsg->luns = kzalloc(i * sizeof(struct fsg_lun), GFP_KERNEL);
+	if (!fsg->luns) {
+		rc = -ENOMEM;
+		goto out;
+	}
+	fsg->nluns = i;
+
+	for (i = 0; i < fsg->nluns; ++i) {
+		curlun = &fsg->luns[i];
+		curlun->cdrom = !!mod_data.cdrom;
+		curlun->ro = mod_data.cdrom || mod_data.ro[i];
+		curlun->initially_ro = curlun->ro;
+		curlun->removable = mod_data.removable;
+		curlun->nofua = mod_data.nofua[i];
+		curlun->dev.release = lun_release;
+		curlun->dev.parent = &gadget->dev;
+		curlun->dev.driver = &fsg_driver.driver;
+		dev_set_drvdata(&curlun->dev, &fsg->filesem);
+		dev_set_name(&curlun->dev,"%s-lun%d",
+			     dev_name(&gadget->dev), i);
+
+		kref_get(&fsg->ref);
+		rc = device_register(&curlun->dev);
+		if (rc) {
+			INFO(fsg, "failed to register LUN%d: %d\n", i, rc);
+			put_device(&curlun->dev);
+			goto out;
+		}
+		curlun->registered = 1;
+
+		rc = device_create_file(&curlun->dev, &dev_attr_ro);
+		if (rc)
+			goto out;
+		rc = device_create_file(&curlun->dev, &dev_attr_nofua);
+		if (rc)
+			goto out;
+		rc = device_create_file(&curlun->dev, &dev_attr_file);
+		if (rc)
+			goto out;
+
+		if (mod_data.file[i] && *mod_data.file[i]) {
+			rc = fsg_lun_open(curlun, mod_data.file[i]);
+			if (rc)
+				goto out;
+		} else if (!mod_data.removable) {
+			ERROR(fsg, "no file given for LUN%d\n", i);
+			rc = -EINVAL;
+			goto out;
+		}
+	}
+
+	/* Find all the endpoints we will use */
+	usb_ep_autoconfig_reset(gadget);
+	ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_in_desc);
+	if (!ep)
+		goto autoconf_fail;
+	ep->driver_data = fsg;		// claim the endpoint
+	fsg->bulk_in = ep;
+
+	ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_out_desc);
+	if (!ep)
+		goto autoconf_fail;
+	ep->driver_data = fsg;		// claim the endpoint
+	fsg->bulk_out = ep;
+
+	if (transport_is_cbi()) {
+		ep = usb_ep_autoconfig(gadget, &fsg_fs_intr_in_desc);
+		if (!ep)
+			goto autoconf_fail;
+		ep->driver_data = fsg;		// claim the endpoint
+		fsg->intr_in = ep;
+	}
+
+	/* Fix up the descriptors */
+	device_desc.idVendor = cpu_to_le16(mod_data.vendor);
+	device_desc.idProduct = cpu_to_le16(mod_data.product);
+	device_desc.bcdDevice = cpu_to_le16(mod_data.release);
+
+	i = (transport_is_cbi() ? 3 : 2);	// Number of endpoints
+	fsg_intf_desc.bNumEndpoints = i;
+	fsg_intf_desc.bInterfaceSubClass = mod_data.protocol_type;
+	fsg_intf_desc.bInterfaceProtocol = mod_data.transport_type;
+	fsg_fs_function[i + FSG_FS_FUNCTION_PRE_EP_ENTRIES] = NULL;
+
+	if (gadget_is_dualspeed(gadget)) {
+		fsg_hs_function[i + FSG_HS_FUNCTION_PRE_EP_ENTRIES] = NULL;
+
+		/* Assume endpoint addresses are the same for both speeds */
+		fsg_hs_bulk_in_desc.bEndpointAddress =
+			fsg_fs_bulk_in_desc.bEndpointAddress;
+		fsg_hs_bulk_out_desc.bEndpointAddress =
+			fsg_fs_bulk_out_desc.bEndpointAddress;
+		fsg_hs_intr_in_desc.bEndpointAddress =
+			fsg_fs_intr_in_desc.bEndpointAddress;
+	}
+
+	if (gadget_is_superspeed(gadget)) {
+		unsigned		max_burst;
+
+		fsg_ss_function[i + FSG_SS_FUNCTION_PRE_EP_ENTRIES] = NULL;
+
+		/* Calculate bMaxBurst, we know packet size is 1024 */
+		max_burst = min_t(unsigned, mod_data.buflen / 1024, 15);
+
+		/* Assume endpoint addresses are the same for both speeds */
+		fsg_ss_bulk_in_desc.bEndpointAddress =
+			fsg_fs_bulk_in_desc.bEndpointAddress;
+		fsg_ss_bulk_in_comp_desc.bMaxBurst = max_burst;
+
+		fsg_ss_bulk_out_desc.bEndpointAddress =
+			fsg_fs_bulk_out_desc.bEndpointAddress;
+		fsg_ss_bulk_out_comp_desc.bMaxBurst = max_burst;
+	}
+
+	if (gadget_is_otg(gadget))
+		fsg_otg_desc.bmAttributes |= USB_OTG_HNP;
+
+	rc = -ENOMEM;
+
+	/* Allocate the request and buffer for endpoint 0 */
+	fsg->ep0req = req = usb_ep_alloc_request(fsg->ep0, GFP_KERNEL);
+	if (!req)
+		goto out;
+	req->buf = kmalloc(EP0_BUFSIZE, GFP_KERNEL);
+	if (!req->buf)
+		goto out;
+	req->complete = ep0_complete;
+
+	/* Allocate the data buffers */
+	for (i = 0; i < fsg_num_buffers; ++i) {
+		struct fsg_buffhd	*bh = &fsg->buffhds[i];
+
+		/* Allocate for the bulk-in endpoint.  We assume that
+		 * the buffer will also work with the bulk-out (and
+		 * interrupt-in) endpoint. */
+		bh->buf = kmalloc(mod_data.buflen, GFP_KERNEL);
+		if (!bh->buf)
+			goto out;
+		bh->next = bh + 1;
+	}
+	fsg->buffhds[fsg_num_buffers - 1].next = &fsg->buffhds[0];
+
+	/* This should reflect the actual gadget power source */
+	usb_gadget_set_selfpowered(gadget);
+
+	snprintf(fsg_string_manufacturer, sizeof fsg_string_manufacturer,
+			"%s %s with %s",
+			init_utsname()->sysname, init_utsname()->release,
+			gadget->name);
+
+	fsg->thread_task = kthread_create(fsg_main_thread, fsg,
+			"file-storage-gadget");
+	if (IS_ERR(fsg->thread_task)) {
+		rc = PTR_ERR(fsg->thread_task);
+		goto out;
+	}
+
+	INFO(fsg, DRIVER_DESC ", version: " DRIVER_VERSION "\n");
+	INFO(fsg, "NOTE: This driver is deprecated.  "
+			"Consider using g_mass_storage instead.\n");
+	INFO(fsg, "Number of LUNs=%d\n", fsg->nluns);
+
+	pathbuf = kmalloc(PATH_MAX, GFP_KERNEL);
+	for (i = 0; i < fsg->nluns; ++i) {
+		curlun = &fsg->luns[i];
+		if (fsg_lun_is_open(curlun)) {
+			p = NULL;
+			if (pathbuf) {
+				p = d_path(&curlun->filp->f_path,
+					   pathbuf, PATH_MAX);
+				if (IS_ERR(p))
+					p = NULL;
+			}
+			LINFO(curlun, "ro=%d, nofua=%d, file: %s\n",
+			      curlun->ro, curlun->nofua, (p ? p : "(error)"));
+		}
+	}
+	kfree(pathbuf);
+
+	DBG(fsg, "transport=%s (x%02x)\n",
+			mod_data.transport_name, mod_data.transport_type);
+	DBG(fsg, "protocol=%s (x%02x)\n",
+			mod_data.protocol_name, mod_data.protocol_type);
+	DBG(fsg, "VendorID=x%04x, ProductID=x%04x, Release=x%04x\n",
+			mod_data.vendor, mod_data.product, mod_data.release);
+	DBG(fsg, "removable=%d, stall=%d, cdrom=%d, buflen=%u\n",
+			mod_data.removable, mod_data.can_stall,
+			mod_data.cdrom, mod_data.buflen);
+	DBG(fsg, "I/O thread pid: %d\n", task_pid_nr(fsg->thread_task));
+
+	set_bit(REGISTERED, &fsg->atomic_bitflags);
+
+	/* Tell the thread to start working */
+	wake_up_process(fsg->thread_task);
+	return 0;
+
+autoconf_fail:
+	ERROR(fsg, "unable to autoconfigure all endpoints\n");
+	rc = -ENOTSUPP;
+
+out:
+	fsg->state = FSG_STATE_TERMINATED;	// The thread is dead
+	fsg_unbind(gadget);
+	complete(&fsg->thread_notifier);
+	return rc;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static void fsg_suspend(struct usb_gadget *gadget)
+{
+	struct fsg_dev		*fsg = get_gadget_data(gadget);
+
+	DBG(fsg, "suspend\n");
+	set_bit(SUSPENDED, &fsg->atomic_bitflags);
+}
+
+static void fsg_resume(struct usb_gadget *gadget)
+{
+	struct fsg_dev		*fsg = get_gadget_data(gadget);
+
+	DBG(fsg, "resume\n");
+	clear_bit(SUSPENDED, &fsg->atomic_bitflags);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static struct usb_gadget_driver		fsg_driver = {
+	.max_speed	= USB_SPEED_SUPER,
+	.function	= (char *) fsg_string_product,
+	.unbind		= fsg_unbind,
+	.disconnect	= fsg_disconnect,
+	.setup		= fsg_setup,
+	.suspend	= fsg_suspend,
+	.resume		= fsg_resume,
+
+	.driver		= {
+		.name		= DRIVER_NAME,
+		.owner		= THIS_MODULE,
+		// .release = ...
+		// .suspend = ...
+		// .resume = ...
+	},
+};
+
+
+static int __init fsg_alloc(void)
+{
+	struct fsg_dev		*fsg;
+
+	fsg = kzalloc(sizeof *fsg +
+		      fsg_num_buffers * sizeof *(fsg->buffhds), GFP_KERNEL);
+
+	if (!fsg)
+		return -ENOMEM;
+	spin_lock_init(&fsg->lock);
+	init_rwsem(&fsg->filesem);
+	kref_init(&fsg->ref);
+	init_completion(&fsg->thread_notifier);
+
+	the_fsg = fsg;
+	return 0;
+}
+
+
+static int __init fsg_init(void)
+{
+	int		rc;
+	struct fsg_dev	*fsg;
+
+	rc = fsg_num_buffers_validate();
+	if (rc != 0)
+		return rc;
+
+	if ((rc = fsg_alloc()) != 0)
+		return rc;
+	fsg = the_fsg;
+	if ((rc = usb_gadget_probe_driver(&fsg_driver, fsg_bind)) != 0)
+		kref_put(&fsg->ref, fsg_release);
+	return rc;
+}
+module_init(fsg_init);
+
+
+static void __exit fsg_cleanup(void)
+{
+	struct fsg_dev	*fsg = the_fsg;
+
+	/* Unregister the driver iff the thread hasn't already done so */
+	if (test_and_clear_bit(REGISTERED, &fsg->atomic_bitflags))
+		usb_gadget_unregister_driver(&fsg_driver);
+
+	/* Wait for the thread to finish up */
+	wait_for_completion(&fsg->thread_notifier);
+
+	kref_put(&fsg->ref, fsg_release);
+}
+module_exit(fsg_cleanup);
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index d011d6c753ed..9455a3c26e7c 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -678,6 +678,16 @@ config USB_RENESAS_USBHS_HCD
 	  To compile this driver as a module, choose M here: the
 	  module will be called renesas-usbhs.
 
+config USB_DWCOTG
+	bool "Synopsis DWC host support"
+	depends on USB=y && (FIQ || ARM64)
+	help
+	  The Synopsis DWC controller is a dual-role
+	  host/peripheral/OTG ("On The Go") USB controllers.
+
+	  Enable this option to support this IP in host controller mode.
+	  If unsure, say N.
+
 config USB_HCD_BCMA
 	tristate "BCMA usb host driver"
 	depends on BCMA
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index be4e5245c52f..929af76224d5 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -77,6 +77,7 @@ obj-$(CONFIG_USB_XHCI_TEGRA)	+= xhci-tegra.o
 obj-$(CONFIG_USB_SL811_HCD)	+= sl811-hcd.o
 obj-$(CONFIG_USB_SL811_CS)	+= sl811_cs.o
 obj-$(CONFIG_USB_R8A66597_HCD)	+= r8a66597-hcd.o
+obj-$(CONFIG_USB_DWCOTG)        += dwc_otg/ dwc_common_port/
 obj-$(CONFIG_USB_FSL_USB2)	+= fsl-mph-dr-of.o
 obj-$(CONFIG_USB_EHCI_FSL)	+= fsl-mph-dr-of.o
 obj-$(CONFIG_USB_EHCI_FSL)	+= ehci-fsl.o
diff --git a/drivers/usb/host/dwc_common_port/Makefile b/drivers/usb/host/dwc_common_port/Makefile
new file mode 100644
index 000000000000..f10d466d1aea
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/Makefile
@@ -0,0 +1,58 @@
+#
+# Makefile for DWC_common library
+#
+
+ifneq ($(KERNELRELEASE),)
+
+ccflags-y	+= -DDWC_LINUX
+#ccflags-y	+= -DDEBUG
+#ccflags-y	+= -DDWC_DEBUG_REGS
+#ccflags-y	+= -DDWC_DEBUG_MEMORY
+
+ccflags-y	+= -DDWC_LIBMODULE
+ccflags-y	+= -DDWC_CCLIB
+#ccflags-y	+= -DDWC_CRYPTOLIB
+ccflags-y	+= -DDWC_NOTIFYLIB
+ccflags-y	+= -DDWC_UTFLIB
+
+obj-$(CONFIG_USB_DWCOTG)	+= dwc_common_port_lib.o
+dwc_common_port_lib-objs := dwc_cc.o dwc_modpow.o dwc_dh.o \
+			    dwc_crypto.o dwc_notifier.o \
+			    dwc_common_linux.o dwc_mem.o
+
+kernrelwd := $(subst ., ,$(KERNELRELEASE))
+kernrel3 := $(word 1,$(kernrelwd)).$(word 2,$(kernrelwd)).$(word 3,$(kernrelwd))
+
+ifneq ($(kernrel3),2.6.20)
+# grayg - I only know that we use ccflags-y in 2.6.31 actually
+ccflags-y += $(CPPFLAGS)
+endif
+
+else
+
+#ifeq ($(KDIR),)
+#$(error Must give "KDIR=/path/to/kernel/source" on command line or in environment)
+#endif
+
+ifeq ($(ARCH),)
+$(error Must give "ARCH=<arch>" on command line or in environment. Also, if \
+ cross-compiling, must give "CROSS_COMPILE=/path/to/compiler/plus/tool-prefix-")
+endif
+
+ifeq ($(DOXYGEN),)
+DOXYGEN		:= doxygen
+endif
+
+default:
+	$(MAKE) -C$(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules
+
+docs:	$(wildcard *.[hc]) doc/doxygen.cfg
+	$(DOXYGEN) doc/doxygen.cfg
+
+tags:	$(wildcard *.[hc])
+	$(CTAGS) -e $(wildcard *.[hc]) $(wildcard linux/*.[hc]) $(wildcard $(KDIR)/include/linux/usb*.h)
+
+endif
+
+clean:
+	rm -rf *.o *.ko .*.cmd *.mod.c .*.o.d .*.o.tmp modules.order Module.markers Module.symvers .tmp_versions/
diff --git a/drivers/usb/host/dwc_common_port/Makefile.fbsd b/drivers/usb/host/dwc_common_port/Makefile.fbsd
new file mode 100644
index 000000000000..45db9915b9d3
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/Makefile.fbsd
@@ -0,0 +1,17 @@
+CFLAGS	+= -I/sys/i386/compile/GENERIC -I/sys/i386/include -I/usr/include
+CFLAGS	+= -DDWC_FREEBSD
+CFLAGS	+= -DDEBUG
+#CFLAGS	+= -DDWC_DEBUG_REGS
+#CFLAGS	+= -DDWC_DEBUG_MEMORY
+
+#CFLAGS	+= -DDWC_LIBMODULE
+#CFLAGS	+= -DDWC_CCLIB
+#CFLAGS	+= -DDWC_CRYPTOLIB
+#CFLAGS	+= -DDWC_NOTIFYLIB
+#CFLAGS	+= -DDWC_UTFLIB
+
+KMOD = dwc_common_port_lib
+SRCS = dwc_cc.c dwc_modpow.c dwc_dh.c dwc_crypto.c dwc_notifier.c \
+       dwc_common_fbsd.c dwc_mem.c
+
+.include <bsd.kmod.mk>
diff --git a/drivers/usb/host/dwc_common_port/Makefile.linux b/drivers/usb/host/dwc_common_port/Makefile.linux
new file mode 100644
index 000000000000..0cef7b461bd5
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/Makefile.linux
@@ -0,0 +1,49 @@
+#
+# Makefile for DWC_common library
+#
+ifneq ($(KERNELRELEASE),)
+
+ccflags-y	+= -DDWC_LINUX
+#ccflags-y	+= -DDEBUG
+#ccflags-y	+= -DDWC_DEBUG_REGS
+#ccflags-y	+= -DDWC_DEBUG_MEMORY
+
+ccflags-y	+= -DDWC_LIBMODULE
+ccflags-y	+= -DDWC_CCLIB
+ccflags-y	+= -DDWC_CRYPTOLIB
+ccflags-y	+= -DDWC_NOTIFYLIB
+ccflags-y	+= -DDWC_UTFLIB
+
+obj-m			 := dwc_common_port_lib.o
+dwc_common_port_lib-objs := dwc_cc.o dwc_modpow.o dwc_dh.o \
+			    dwc_crypto.o dwc_notifier.o \
+			    dwc_common_linux.o dwc_mem.o
+
+else
+
+ifeq ($(KDIR),)
+$(error Must give "KDIR=/path/to/kernel/source" on command line or in environment)
+endif
+
+ifeq ($(ARCH),)
+$(error Must give "ARCH=<arch>" on command line or in environment. Also, if \
+ cross-compiling, must give "CROSS_COMPILE=/path/to/compiler/plus/tool-prefix-")
+endif
+
+ifeq ($(DOXYGEN),)
+DOXYGEN		:= doxygen
+endif
+
+default:
+	$(MAKE) -C$(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules
+
+docs:	$(wildcard *.[hc]) doc/doxygen.cfg
+	$(DOXYGEN) doc/doxygen.cfg
+
+tags:	$(wildcard *.[hc])
+	$(CTAGS) -e $(wildcard *.[hc]) $(wildcard linux/*.[hc]) $(wildcard $(KDIR)/include/linux/usb*.h)
+
+endif
+
+clean:
+	rm -rf *.o *.ko .*.cmd *.mod.c .*.o.d .*.o.tmp modules.order Module.markers Module.symvers .tmp_versions/
diff --git a/drivers/usb/host/dwc_common_port/changes.txt b/drivers/usb/host/dwc_common_port/changes.txt
new file mode 100644
index 000000000000..f6839f92c276
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/changes.txt
@@ -0,0 +1,174 @@
+
+dwc_read_reg32() and friends now take an additional parameter, a pointer to an
+IO context struct. The IO context struct should live in an os-dependent struct
+in your driver. As an example, the dwc_usb3 driver has an os-dependent struct
+named 'os_dep' embedded in the main device struct. So there these calls look
+like this:
+
+	dwc_read_reg32(&usb3_dev->os_dep.ioctx, &pcd->dev_global_regs->dcfg);
+
+	dwc_write_reg32(&usb3_dev->os_dep.ioctx,
+			&pcd->dev_global_regs->dcfg, 0);
+
+Note that for the existing Linux driver ports, it is not necessary to actually
+define the 'ioctx' member in the os-dependent struct. Since Linux does not
+require an IO context, its macros for dwc_read_reg32() and friends do not
+use the context pointer, so it is optimized away by the compiler. But it is
+necessary to add the pointer parameter to all of the call sites, to be ready
+for any future ports (such as FreeBSD) which do require an IO context.
+
+
+Similarly, dwc_alloc(), dwc_alloc_atomic(), dwc_strdup(), and dwc_free() now
+take an additional parameter, a pointer to a memory context. Examples:
+
+	addr = dwc_alloc(&usb3_dev->os_dep.memctx, size);
+
+	dwc_free(&usb3_dev->os_dep.memctx, addr);
+
+Again, for the Linux ports, it is not necessary to actually define the memctx
+member, but it is necessary to add the pointer parameter to all of the call
+sites.
+
+
+Same for dwc_dma_alloc() and dwc_dma_free(). Examples:
+
+	virt_addr = dwc_dma_alloc(&usb3_dev->os_dep.dmactx, size, &phys_addr);
+
+	dwc_dma_free(&usb3_dev->os_dep.dmactx, size, virt_addr, phys_addr);
+
+
+Same for dwc_mutex_alloc() and dwc_mutex_free(). Examples:
+
+	mutex = dwc_mutex_alloc(&usb3_dev->os_dep.mtxctx);
+
+	dwc_mutex_free(&usb3_dev->os_dep.mtxctx, mutex);
+
+
+Same for dwc_spinlock_alloc() and dwc_spinlock_free(). Examples:
+
+	lock = dwc_spinlock_alloc(&usb3_dev->osdep.splctx);
+
+	dwc_spinlock_free(&usb3_dev->osdep.splctx, lock);
+
+
+Same for dwc_timer_alloc(). Example:
+
+	timer = dwc_timer_alloc(&usb3_dev->os_dep.tmrctx, "dwc_usb3_tmr1",
+				cb_func, cb_data);
+
+
+Same for dwc_waitq_alloc(). Example:
+
+	waitq = dwc_waitq_alloc(&usb3_dev->os_dep.wtqctx);
+
+
+Same for dwc_thread_run(). Example:
+
+	thread = dwc_thread_run(&usb3_dev->os_dep.thdctx, func,
+				"dwc_usb3_thd1", data);
+
+
+Same for dwc_workq_alloc(). Example:
+
+	workq = dwc_workq_alloc(&usb3_dev->osdep.wkqctx, "dwc_usb3_wkq1");
+
+
+Same for dwc_task_alloc(). Example:
+
+	task = dwc_task_alloc(&usb3_dev->os_dep.tskctx, "dwc_usb3_tsk1",
+			      cb_func, cb_data);
+
+
+In addition to the context pointer additions, a few core functions have had
+other changes made to their parameters:
+
+The 'flags' parameter to dwc_spinlock_irqsave() and dwc_spinunlock_irqrestore()
+has been changed from a uint64_t to a dwc_irqflags_t.
+
+dwc_thread_should_stop() now takes a 'dwc_thread_t *' parameter, because the
+FreeBSD equivalent of that function requires it.
+
+And, in addition to the context pointer, dwc_task_alloc() also adds a
+'char *name' parameter, to be consistent with dwc_thread_run() and
+dwc_workq_alloc(), and because the FreeBSD equivalent of that function
+requires a unique name.
+
+
+Here is a complete list of the core functions that now take a pointer to a
+context as their first parameter:
+
+	dwc_read_reg32
+	dwc_read_reg64
+	dwc_write_reg32
+	dwc_write_reg64
+	dwc_modify_reg32
+	dwc_modify_reg64
+	dwc_alloc
+	dwc_alloc_atomic
+	dwc_strdup
+	dwc_free
+	dwc_dma_alloc
+	dwc_dma_free
+	dwc_mutex_alloc
+	dwc_mutex_free
+	dwc_spinlock_alloc
+	dwc_spinlock_free
+	dwc_timer_alloc
+	dwc_waitq_alloc
+	dwc_thread_run
+	dwc_workq_alloc
+	dwc_task_alloc     Also adds a 'char *name' as its 2nd parameter
+
+And here are the core functions that have other changes to their parameters:
+
+	dwc_spinlock_irqsave      'flags' param is now a 'dwc_irqflags_t *'
+	dwc_spinunlock_irqrestore 'flags' param is now a 'dwc_irqflags_t'
+	dwc_thread_should_stop    Adds a 'dwc_thread_t *' parameter
+
+
+
+The changes to the core functions also require some of the other library
+functions to change:
+
+	dwc_cc_if_alloc() and dwc_cc_if_free() now take a 'void *memctx'
+	(for memory allocation) as the 1st param and a 'void *mtxctx'
+	(for mutex allocation) as the 2nd param.
+
+	dwc_cc_clear(), dwc_cc_add(), dwc_cc_change(), dwc_cc_remove(),
+	dwc_cc_data_for_save(), and dwc_cc_restore_from_data() now take a
+	'void *memctx' as the 1st param.
+
+	dwc_dh_modpow(), dwc_dh_pk(), and dwc_dh_derive_keys() now take a
+	'void *memctx' as the 1st param.
+
+	dwc_modpow() now takes a 'void *memctx' as the 1st param.
+
+	dwc_alloc_notification_manager() now takes a 'void *memctx' as the
+	1st param and a 'void *wkqctx' (for work queue allocation) as the 2nd
+	param, and also now returns an integer value that is non-zero if
+	allocation of its data structures or work queue fails.
+
+	dwc_register_notifier() now takes a 'void *memctx' as the 1st param.
+
+	dwc_memory_debug_start() now takes a 'void *mem_ctx' as the first
+	param, and also now returns an integer value that is non-zero if
+	allocation of its data structures fails.
+
+
+
+Other miscellaneous changes:
+
+The DEBUG_MEMORY and DEBUG_REGS #define's have been renamed to
+DWC_DEBUG_MEMORY and DWC_DEBUG_REGS.
+
+The following #define's have been added to allow selectively compiling library
+features:
+
+	DWC_CCLIB
+	DWC_CRYPTOLIB
+	DWC_NOTIFYLIB
+	DWC_UTFLIB
+
+A DWC_LIBMODULE #define has also been added. If this is not defined, then the
+module code in dwc_common_linux.c is not compiled in. This allows linking the
+library code directly into a driver module, instead of as a standalone module.
diff --git a/drivers/usb/host/dwc_common_port/doc/doxygen.cfg b/drivers/usb/host/dwc_common_port/doc/doxygen.cfg
new file mode 100644
index 000000000000..89aa887af29d
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/doc/doxygen.cfg
@@ -0,0 +1,270 @@
+# Doxyfile 1.4.5
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+PROJECT_NAME           = "Synopsys DWC Portability and Common Library for UWB"
+PROJECT_NUMBER         =
+OUTPUT_DIRECTORY       = doc
+CREATE_SUBDIRS         = NO
+OUTPUT_LANGUAGE        = English
+BRIEF_MEMBER_DESC      = YES
+REPEAT_BRIEF           = YES
+ABBREVIATE_BRIEF       = "The $name class" \
+                         "The $name widget" \
+                         "The $name file" \
+                         is \
+                         provides \
+                         specifies \
+                         contains \
+                         represents \
+                         a \
+                         an \
+                         the
+ALWAYS_DETAILED_SEC    = YES
+INLINE_INHERITED_MEMB  = NO
+FULL_PATH_NAMES        = NO
+STRIP_FROM_PATH        = ..
+STRIP_FROM_INC_PATH    =
+SHORT_NAMES            = NO
+JAVADOC_AUTOBRIEF      = YES
+MULTILINE_CPP_IS_BRIEF = NO
+DETAILS_AT_TOP         = YES
+INHERIT_DOCS           = YES
+SEPARATE_MEMBER_PAGES  = NO
+TAB_SIZE               = 8
+ALIASES                =
+OPTIMIZE_OUTPUT_FOR_C  = YES
+OPTIMIZE_OUTPUT_JAVA   = NO
+BUILTIN_STL_SUPPORT    = NO
+DISTRIBUTE_GROUP_DOC   = NO
+SUBGROUPING            = NO
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+EXTRACT_ALL            = NO
+EXTRACT_PRIVATE        = NO
+EXTRACT_STATIC         = YES
+EXTRACT_LOCAL_CLASSES  = NO
+EXTRACT_LOCAL_METHODS  = NO
+HIDE_UNDOC_MEMBERS     = NO
+HIDE_UNDOC_CLASSES     = NO
+HIDE_FRIEND_COMPOUNDS  = NO
+HIDE_IN_BODY_DOCS      = NO
+INTERNAL_DOCS          = NO
+CASE_SENSE_NAMES       = YES
+HIDE_SCOPE_NAMES       = NO
+SHOW_INCLUDE_FILES     = NO
+INLINE_INFO            = YES
+SORT_MEMBER_DOCS       = NO
+SORT_BRIEF_DOCS        = NO
+SORT_BY_SCOPE_NAME     = NO
+GENERATE_TODOLIST      = YES
+GENERATE_TESTLIST      = YES
+GENERATE_BUGLIST       = YES
+GENERATE_DEPRECATEDLIST= YES
+ENABLED_SECTIONS       =
+MAX_INITIALIZER_LINES  = 30
+SHOW_USED_FILES        = YES
+SHOW_DIRECTORIES       = YES
+FILE_VERSION_FILTER    =
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+QUIET                  = YES
+WARNINGS               = YES
+WARN_IF_UNDOCUMENTED   = NO
+WARN_IF_DOC_ERROR      = YES
+WARN_NO_PARAMDOC       = YES
+WARN_FORMAT            = "$file:$line: $text"
+WARN_LOGFILE           =
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+INPUT                  = .
+FILE_PATTERNS          = *.c \
+                         *.cc \
+                         *.cxx \
+                         *.cpp \
+                         *.c++ \
+                         *.d \
+                         *.java \
+                         *.ii \
+                         *.ixx \
+                         *.ipp \
+                         *.i++ \
+                         *.inl \
+                         *.h \
+                         *.hh \
+                         *.hxx \
+                         *.hpp \
+                         *.h++ \
+                         *.idl \
+                         *.odl \
+                         *.cs \
+                         *.php \
+                         *.php3 \
+                         *.inc \
+                         *.m \
+                         *.mm \
+                         *.dox \
+                         *.py \
+                         *.C \
+                         *.CC \
+                         *.C++ \
+                         *.II \
+                         *.I++ \
+                         *.H \
+                         *.HH \
+                         *.H++ \
+                         *.CS \
+                         *.PHP \
+                         *.PHP3 \
+                         *.M \
+                         *.MM \
+                         *.PY
+RECURSIVE              = NO
+EXCLUDE                =
+EXCLUDE_SYMLINKS       = NO
+EXCLUDE_PATTERNS       =
+EXAMPLE_PATH           =
+EXAMPLE_PATTERNS       = *
+EXAMPLE_RECURSIVE      = NO
+IMAGE_PATH             =
+INPUT_FILTER           =
+FILTER_PATTERNS        =
+FILTER_SOURCE_FILES    = NO
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+SOURCE_BROWSER         = NO
+INLINE_SOURCES         = NO
+STRIP_CODE_COMMENTS    = YES
+REFERENCED_BY_RELATION = YES
+REFERENCES_RELATION    = YES
+USE_HTAGS              = NO
+VERBATIM_HEADERS       = NO
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+ALPHABETICAL_INDEX     = NO
+COLS_IN_ALPHA_INDEX    = 5
+IGNORE_PREFIX          =
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+GENERATE_HTML          = YES
+HTML_OUTPUT            = html
+HTML_FILE_EXTENSION    = .html
+HTML_HEADER            =
+HTML_FOOTER            =
+HTML_STYLESHEET        =
+HTML_ALIGN_MEMBERS     = YES
+GENERATE_HTMLHELP      = NO
+CHM_FILE               =
+HHC_LOCATION           =
+GENERATE_CHI           = NO
+BINARY_TOC             = NO
+TOC_EXPAND             = NO
+DISABLE_INDEX          = NO
+ENUM_VALUES_PER_LINE   = 4
+GENERATE_TREEVIEW      = YES
+TREEVIEW_WIDTH         = 250
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+GENERATE_LATEX         = NO
+LATEX_OUTPUT           = latex
+LATEX_CMD_NAME         = latex
+MAKEINDEX_CMD_NAME     = makeindex
+COMPACT_LATEX          = NO
+PAPER_TYPE             = a4wide
+EXTRA_PACKAGES         =
+LATEX_HEADER           =
+PDF_HYPERLINKS         = NO
+USE_PDFLATEX           = NO
+LATEX_BATCHMODE        = NO
+LATEX_HIDE_INDICES     = NO
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+GENERATE_RTF           = NO
+RTF_OUTPUT             = rtf
+COMPACT_RTF            = NO
+RTF_HYPERLINKS         = NO
+RTF_STYLESHEET_FILE    =
+RTF_EXTENSIONS_FILE    =
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+GENERATE_MAN           = NO
+MAN_OUTPUT             = man
+MAN_EXTENSION          = .3
+MAN_LINKS              = NO
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+GENERATE_XML           = NO
+XML_OUTPUT             = xml
+XML_SCHEMA             =
+XML_DTD                =
+XML_PROGRAMLISTING     = YES
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+GENERATE_AUTOGEN_DEF   = NO
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+GENERATE_PERLMOD       = NO
+PERLMOD_LATEX          = NO
+PERLMOD_PRETTY         = YES
+PERLMOD_MAKEVAR_PREFIX =
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+ENABLE_PREPROCESSING   = YES
+MACRO_EXPANSION        = NO
+EXPAND_ONLY_PREDEF     = NO
+SEARCH_INCLUDES        = YES
+INCLUDE_PATH           =
+INCLUDE_FILE_PATTERNS  =
+PREDEFINED             = DEBUG DEBUG_MEMORY
+EXPAND_AS_DEFINED      =
+SKIP_FUNCTION_MACROS   = YES
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+TAGFILES               =
+GENERATE_TAGFILE       =
+ALLEXTERNALS           = NO
+EXTERNAL_GROUPS        = YES
+PERL_PATH              = /usr/bin/perl
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+CLASS_DIAGRAMS         = YES
+HIDE_UNDOC_RELATIONS   = YES
+HAVE_DOT               = NO
+CLASS_GRAPH            = YES
+COLLABORATION_GRAPH    = YES
+GROUP_GRAPHS           = YES
+UML_LOOK               = NO
+TEMPLATE_RELATIONS     = NO
+INCLUDE_GRAPH          = NO
+INCLUDED_BY_GRAPH      = YES
+CALL_GRAPH             = NO
+GRAPHICAL_HIERARCHY    = YES
+DIRECTORY_GRAPH        = YES
+DOT_IMAGE_FORMAT       = png
+DOT_PATH               =
+DOTFILE_DIRS           =
+MAX_DOT_GRAPH_DEPTH    = 1000
+DOT_TRANSPARENT        = NO
+DOT_MULTI_TARGETS      = NO
+GENERATE_LEGEND        = YES
+DOT_CLEANUP            = YES
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine
+#---------------------------------------------------------------------------
+SEARCHENGINE           = NO
diff --git a/drivers/usb/host/dwc_common_port/dwc_cc.c b/drivers/usb/host/dwc_common_port/dwc_cc.c
new file mode 100644
index 000000000000..5ec2ae28698c
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/dwc_cc.c
@@ -0,0 +1,532 @@
+/* =========================================================================
+ * $File: //dwh/usb_iip/dev/software/dwc_common_port_2/dwc_cc.c $
+ * $Revision: #4 $
+ * $Date: 2010/11/04 $
+ * $Change: 1621692 $
+ *
+ * Synopsys Portability Library Software and documentation
+ * (hereinafter, "Software") is an Unsupported proprietary work of
+ * Synopsys, Inc. unless otherwise expressly agreed to in writing
+ * between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product
+ * under any End User Software License Agreement or Agreement for
+ * Licensed Product with Synopsys or any supplement thereto. You are
+ * permitted to use and redistribute this Software in source and binary
+ * forms, with or without modification, provided that redistributions
+ * of source code must retain this notice. You may not view, use,
+ * disclose, copy or distribute this file or any information contained
+ * herein except pursuant to this license grant from Synopsys. If you
+ * do not agree with this notice, including the disclaimer below, then
+ * you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS"
+ * BASIS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE HEREBY DISCLAIMED. IN NO EVENT SHALL
+ * SYNOPSYS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================= */
+#ifdef DWC_CCLIB
+
+#include "dwc_cc.h"
+
+typedef struct dwc_cc
+{
+	uint32_t uid;
+	uint8_t chid[16];
+	uint8_t cdid[16];
+	uint8_t ck[16];
+	uint8_t *name;
+	uint8_t length;
+        DWC_CIRCLEQ_ENTRY(dwc_cc) list_entry;
+} dwc_cc_t;
+
+DWC_CIRCLEQ_HEAD(context_list, dwc_cc);
+
+/** The main structure for CC management.  */
+struct dwc_cc_if
+{
+	dwc_mutex_t *mutex;
+	char *filename;
+
+	unsigned is_host:1;
+
+	dwc_notifier_t *notifier;
+
+	struct context_list list;
+};
+
+#ifdef DEBUG
+static inline void dump_bytes(char *name, uint8_t *bytes, int len)
+{
+	int i;
+	DWC_PRINTF("%s: ", name);
+	for (i=0; i<len; i++) {
+		DWC_PRINTF("%02x ", bytes[i]);
+	}
+	DWC_PRINTF("\n");
+}
+#else
+#define dump_bytes(x...)
+#endif
+
+static dwc_cc_t *alloc_cc(void *mem_ctx, uint8_t *name, uint32_t length)
+{
+	dwc_cc_t *cc = dwc_alloc(mem_ctx, sizeof(dwc_cc_t));
+	if (!cc) {
+		return NULL;
+	}
+	DWC_MEMSET(cc, 0, sizeof(dwc_cc_t));
+
+	if (name) {
+		cc->length = length;
+		cc->name = dwc_alloc(mem_ctx, length);
+		if (!cc->name) {
+			dwc_free(mem_ctx, cc);
+			return NULL;
+		}
+
+		DWC_MEMCPY(cc->name, name, length);
+	}
+
+	return cc;
+}
+
+static void free_cc(void *mem_ctx, dwc_cc_t *cc)
+{
+	if (cc->name) {
+		dwc_free(mem_ctx, cc->name);
+	}
+	dwc_free(mem_ctx, cc);
+}
+
+static uint32_t next_uid(dwc_cc_if_t *cc_if)
+{
+	uint32_t uid = 0;
+	dwc_cc_t *cc;
+	DWC_CIRCLEQ_FOREACH(cc, &cc_if->list, list_entry) {
+		if (cc->uid > uid) {
+			uid = cc->uid;
+		}
+	}
+
+	if (uid == 0) {
+		uid = 255;
+	}
+
+	return uid + 1;
+}
+
+static dwc_cc_t *cc_find(dwc_cc_if_t *cc_if, uint32_t uid)
+{
+	dwc_cc_t *cc;
+	DWC_CIRCLEQ_FOREACH(cc, &cc_if->list, list_entry) {
+		if (cc->uid == uid) {
+			return cc;
+		}
+	}
+	return NULL;
+}
+
+static unsigned int cc_data_size(dwc_cc_if_t *cc_if)
+{
+	unsigned int size = 0;
+	dwc_cc_t *cc;
+	DWC_CIRCLEQ_FOREACH(cc, &cc_if->list, list_entry) {
+		size += (48 + 1);
+		if (cc->name) {
+			size += cc->length;
+		}
+	}
+	return size;
+}
+
+static uint32_t cc_match_chid(dwc_cc_if_t *cc_if, uint8_t *chid)
+{
+	uint32_t uid = 0;
+	dwc_cc_t *cc;
+
+	DWC_CIRCLEQ_FOREACH(cc, &cc_if->list, list_entry) {
+		if (DWC_MEMCMP(cc->chid, chid, 16) == 0) {
+			uid = cc->uid;
+			break;
+		}
+	}
+	return uid;
+}
+static uint32_t cc_match_cdid(dwc_cc_if_t *cc_if, uint8_t *cdid)
+{
+	uint32_t uid = 0;
+	dwc_cc_t *cc;
+
+	DWC_CIRCLEQ_FOREACH(cc, &cc_if->list, list_entry) {
+		if (DWC_MEMCMP(cc->cdid, cdid, 16) == 0) {
+			uid = cc->uid;
+			break;
+		}
+	}
+	return uid;
+}
+
+/* Internal cc_add */
+static int32_t cc_add(void *mem_ctx, dwc_cc_if_t *cc_if, uint8_t *chid,
+		      uint8_t *cdid, uint8_t *ck, uint8_t *name, uint8_t length)
+{
+	dwc_cc_t *cc;
+	uint32_t uid;
+
+	if (cc_if->is_host) {
+		uid = cc_match_cdid(cc_if, cdid);
+	}
+	else {
+		uid = cc_match_chid(cc_if, chid);
+	}
+
+	if (uid) {
+		DWC_DEBUGC("Replacing previous connection context id=%d name=%p name_len=%d", uid, name, length);
+		cc = cc_find(cc_if, uid);
+	}
+	else {
+		cc = alloc_cc(mem_ctx, name, length);
+		cc->uid = next_uid(cc_if);
+		DWC_CIRCLEQ_INSERT_TAIL(&cc_if->list, cc, list_entry);
+	}
+
+	DWC_MEMCPY(&(cc->chid[0]), chid, 16);
+	DWC_MEMCPY(&(cc->cdid[0]), cdid, 16);
+	DWC_MEMCPY(&(cc->ck[0]), ck, 16);
+
+	DWC_DEBUGC("Added connection context id=%d name=%p name_len=%d", cc->uid, name, length);
+	dump_bytes("CHID", cc->chid, 16);
+	dump_bytes("CDID", cc->cdid, 16);
+	dump_bytes("CK", cc->ck, 16);
+	return cc->uid;
+}
+
+/* Internal cc_clear */
+static void cc_clear(void *mem_ctx, dwc_cc_if_t *cc_if)
+{
+	while (!DWC_CIRCLEQ_EMPTY(&cc_if->list)) {
+		dwc_cc_t *cc = DWC_CIRCLEQ_FIRST(&cc_if->list);
+		DWC_CIRCLEQ_REMOVE_INIT(&cc_if->list, cc, list_entry);
+		free_cc(mem_ctx, cc);
+	}
+}
+
+dwc_cc_if_t *dwc_cc_if_alloc(void *mem_ctx, void *mtx_ctx,
+			     dwc_notifier_t *notifier, unsigned is_host)
+{
+	dwc_cc_if_t *cc_if = NULL;
+
+	/* Allocate a common_cc_if structure */
+	cc_if = dwc_alloc(mem_ctx, sizeof(dwc_cc_if_t));
+
+	if (!cc_if)
+		return NULL;
+
+#if (defined(DWC_LINUX) && defined(CONFIG_DEBUG_MUTEXES))
+	DWC_MUTEX_ALLOC_LINUX_DEBUG(cc_if->mutex);
+#else
+	cc_if->mutex = dwc_mutex_alloc(mtx_ctx);
+#endif
+	if (!cc_if->mutex) {
+		dwc_free(mem_ctx, cc_if);
+		return NULL;
+	}
+
+	DWC_CIRCLEQ_INIT(&cc_if->list);
+	cc_if->is_host = is_host;
+	cc_if->notifier = notifier;
+	return cc_if;
+}
+
+void dwc_cc_if_free(void *mem_ctx, void *mtx_ctx, dwc_cc_if_t *cc_if)
+{
+#if (defined(DWC_LINUX) && defined(CONFIG_DEBUG_MUTEXES))
+	DWC_MUTEX_FREE(cc_if->mutex);
+#else
+	dwc_mutex_free(mtx_ctx, cc_if->mutex);
+#endif
+	cc_clear(mem_ctx, cc_if);
+	dwc_free(mem_ctx, cc_if);
+}
+
+static void cc_changed(dwc_cc_if_t *cc_if)
+{
+	if (cc_if->notifier) {
+		dwc_notify(cc_if->notifier, DWC_CC_LIST_CHANGED_NOTIFICATION, cc_if);
+	}
+}
+
+void dwc_cc_clear(void *mem_ctx, dwc_cc_if_t *cc_if)
+{
+	DWC_MUTEX_LOCK(cc_if->mutex);
+	cc_clear(mem_ctx, cc_if);
+	DWC_MUTEX_UNLOCK(cc_if->mutex);
+	cc_changed(cc_if);
+}
+
+int32_t dwc_cc_add(void *mem_ctx, dwc_cc_if_t *cc_if, uint8_t *chid,
+		   uint8_t *cdid, uint8_t *ck, uint8_t *name, uint8_t length)
+{
+	uint32_t uid;
+
+	DWC_MUTEX_LOCK(cc_if->mutex);
+	uid = cc_add(mem_ctx, cc_if, chid, cdid, ck, name, length);
+	DWC_MUTEX_UNLOCK(cc_if->mutex);
+	cc_changed(cc_if);
+
+	return uid;
+}
+
+void dwc_cc_change(void *mem_ctx, dwc_cc_if_t *cc_if, int32_t id, uint8_t *chid,
+		   uint8_t *cdid, uint8_t *ck, uint8_t *name, uint8_t length)
+{
+	dwc_cc_t* cc;
+
+	DWC_DEBUGC("Change connection context %d", id);
+
+	DWC_MUTEX_LOCK(cc_if->mutex);
+	cc = cc_find(cc_if, id);
+	if (!cc) {
+		DWC_ERROR("Uid %d not found in cc list\n", id);
+		DWC_MUTEX_UNLOCK(cc_if->mutex);
+		return;
+	}
+
+	if (chid) {
+		DWC_MEMCPY(&(cc->chid[0]), chid, 16);
+	}
+	if (cdid) {
+		DWC_MEMCPY(&(cc->cdid[0]), cdid, 16);
+	}
+	if (ck) {
+		DWC_MEMCPY(&(cc->ck[0]), ck, 16);
+	}
+
+	if (name) {
+		if (cc->name) {
+			dwc_free(mem_ctx, cc->name);
+		}
+		cc->name = dwc_alloc(mem_ctx, length);
+		if (!cc->name) {
+			DWC_ERROR("Out of memory in dwc_cc_change()\n");
+			DWC_MUTEX_UNLOCK(cc_if->mutex);
+			return;
+		}
+		cc->length = length;
+		DWC_MEMCPY(cc->name, name, length);
+	}
+
+	DWC_MUTEX_UNLOCK(cc_if->mutex);
+
+	cc_changed(cc_if);
+
+	DWC_DEBUGC("Changed connection context id=%d\n", id);
+	dump_bytes("New CHID", cc->chid, 16);
+	dump_bytes("New CDID", cc->cdid, 16);
+	dump_bytes("New CK", cc->ck, 16);
+}
+
+void dwc_cc_remove(void *mem_ctx, dwc_cc_if_t *cc_if, int32_t id)
+{
+	dwc_cc_t *cc;
+
+	DWC_DEBUGC("Removing connection context %d", id);
+
+	DWC_MUTEX_LOCK(cc_if->mutex);
+	cc = cc_find(cc_if, id);
+	if (!cc) {
+		DWC_ERROR("Uid %d not found in cc list\n", id);
+		DWC_MUTEX_UNLOCK(cc_if->mutex);
+		return;
+	}
+
+	DWC_CIRCLEQ_REMOVE_INIT(&cc_if->list, cc, list_entry);
+	DWC_MUTEX_UNLOCK(cc_if->mutex);
+	free_cc(mem_ctx, cc);
+
+	cc_changed(cc_if);
+}
+
+uint8_t *dwc_cc_data_for_save(void *mem_ctx, dwc_cc_if_t *cc_if, unsigned int *length)
+{
+	uint8_t *buf, *x;
+	uint8_t zero = 0;
+	dwc_cc_t *cc;
+
+	DWC_MUTEX_LOCK(cc_if->mutex);
+	*length = cc_data_size(cc_if);
+	if (!(*length)) {
+		DWC_MUTEX_UNLOCK(cc_if->mutex);
+		return NULL;
+	}
+
+	DWC_DEBUGC("Creating data for saving (length=%d)", *length);
+
+	buf = dwc_alloc(mem_ctx, *length);
+	if (!buf) {
+		*length = 0;
+		DWC_MUTEX_UNLOCK(cc_if->mutex);
+		return NULL;
+	}
+
+	x = buf;
+	DWC_CIRCLEQ_FOREACH(cc, &cc_if->list, list_entry) {
+		DWC_MEMCPY(x, cc->chid, 16);
+		x += 16;
+		DWC_MEMCPY(x, cc->cdid, 16);
+		x += 16;
+		DWC_MEMCPY(x, cc->ck, 16);
+		x += 16;
+		if (cc->name) {
+			DWC_MEMCPY(x, &cc->length, 1);
+			x += 1;
+			DWC_MEMCPY(x, cc->name, cc->length);
+			x += cc->length;
+		}
+		else {
+			DWC_MEMCPY(x, &zero, 1);
+			x += 1;
+		}
+	}
+	DWC_MUTEX_UNLOCK(cc_if->mutex);
+
+	return buf;
+}
+
+void dwc_cc_restore_from_data(void *mem_ctx, dwc_cc_if_t *cc_if, uint8_t *data, uint32_t length)
+{
+	uint8_t name_length;
+	uint8_t *name;
+	uint8_t *chid;
+	uint8_t *cdid;
+	uint8_t *ck;
+	uint32_t i = 0;
+
+	DWC_MUTEX_LOCK(cc_if->mutex);
+	cc_clear(mem_ctx, cc_if);
+
+	while (i < length) {
+		chid = &data[i];
+		i += 16;
+		cdid = &data[i];
+		i += 16;
+		ck = &data[i];
+		i += 16;
+
+		name_length = data[i];
+		i ++;
+
+		if (name_length) {
+			name = &data[i];
+			i += name_length;
+		}
+		else {
+			name = NULL;
+		}
+
+		/* check to see if we haven't overflown the buffer */
+		if (i > length) {
+			DWC_ERROR("Data format error while attempting to load CCs "
+				  "(nlen=%d, iter=%d, buflen=%d).\n", name_length, i, length);
+			break;
+		}
+
+		cc_add(mem_ctx, cc_if, chid, cdid, ck, name, name_length);
+	}
+	DWC_MUTEX_UNLOCK(cc_if->mutex);
+
+	cc_changed(cc_if);
+}
+
+uint32_t dwc_cc_match_chid(dwc_cc_if_t *cc_if, uint8_t *chid)
+{
+	uint32_t uid = 0;
+
+	DWC_MUTEX_LOCK(cc_if->mutex);
+	uid = cc_match_chid(cc_if, chid);
+	DWC_MUTEX_UNLOCK(cc_if->mutex);
+	return uid;
+}
+uint32_t dwc_cc_match_cdid(dwc_cc_if_t *cc_if, uint8_t *cdid)
+{
+	uint32_t uid = 0;
+
+	DWC_MUTEX_LOCK(cc_if->mutex);
+	uid = cc_match_cdid(cc_if, cdid);
+	DWC_MUTEX_UNLOCK(cc_if->mutex);
+	return uid;
+}
+
+uint8_t *dwc_cc_ck(dwc_cc_if_t *cc_if, int32_t id)
+{
+	uint8_t *ck = NULL;
+	dwc_cc_t *cc;
+
+	DWC_MUTEX_LOCK(cc_if->mutex);
+	cc = cc_find(cc_if, id);
+	if (cc) {
+		ck = cc->ck;
+	}
+	DWC_MUTEX_UNLOCK(cc_if->mutex);
+
+	return ck;
+
+}
+
+uint8_t *dwc_cc_chid(dwc_cc_if_t *cc_if, int32_t id)
+{
+	uint8_t *retval = NULL;
+	dwc_cc_t *cc;
+
+	DWC_MUTEX_LOCK(cc_if->mutex);
+	cc = cc_find(cc_if, id);
+	if (cc) {
+		retval = cc->chid;
+	}
+	DWC_MUTEX_UNLOCK(cc_if->mutex);
+
+	return retval;
+}
+
+uint8_t *dwc_cc_cdid(dwc_cc_if_t *cc_if, int32_t id)
+{
+	uint8_t *retval = NULL;
+	dwc_cc_t *cc;
+
+	DWC_MUTEX_LOCK(cc_if->mutex);
+	cc = cc_find(cc_if, id);
+	if (cc) {
+		retval = cc->cdid;
+	}
+	DWC_MUTEX_UNLOCK(cc_if->mutex);
+
+	return retval;
+}
+
+uint8_t *dwc_cc_name(dwc_cc_if_t *cc_if, int32_t id, uint8_t *length)
+{
+	uint8_t *retval = NULL;
+	dwc_cc_t *cc;
+
+	DWC_MUTEX_LOCK(cc_if->mutex);
+	*length = 0;
+	cc = cc_find(cc_if, id);
+	if (cc) {
+		*length = cc->length;
+		retval = cc->name;
+	}
+	DWC_MUTEX_UNLOCK(cc_if->mutex);
+
+	return retval;
+}
+
+#endif	/* DWC_CCLIB */
diff --git a/drivers/usb/host/dwc_common_port/dwc_cc.h b/drivers/usb/host/dwc_common_port/dwc_cc.h
new file mode 100644
index 000000000000..f86e6f21792b
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/dwc_cc.h
@@ -0,0 +1,224 @@
+/* =========================================================================
+ * $File: //dwh/usb_iip/dev/software/dwc_common_port_2/dwc_cc.h $
+ * $Revision: #4 $
+ * $Date: 2010/09/28 $
+ * $Change: 1596182 $
+ *
+ * Synopsys Portability Library Software and documentation
+ * (hereinafter, "Software") is an Unsupported proprietary work of
+ * Synopsys, Inc. unless otherwise expressly agreed to in writing
+ * between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product
+ * under any End User Software License Agreement or Agreement for
+ * Licensed Product with Synopsys or any supplement thereto. You are
+ * permitted to use and redistribute this Software in source and binary
+ * forms, with or without modification, provided that redistributions
+ * of source code must retain this notice. You may not view, use,
+ * disclose, copy or distribute this file or any information contained
+ * herein except pursuant to this license grant from Synopsys. If you
+ * do not agree with this notice, including the disclaimer below, then
+ * you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS"
+ * BASIS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE HEREBY DISCLAIMED. IN NO EVENT SHALL
+ * SYNOPSYS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================= */
+#ifndef _DWC_CC_H_
+#define _DWC_CC_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @file
+ *
+ * This file defines the Context Context library.
+ *
+ * The main data structure is dwc_cc_if_t which is returned by either the
+ * dwc_cc_if_alloc function or returned by the module to the user via a provided
+ * function. The data structure is opaque and should only be manipulated via the
+ * functions provied in this API.
+ *
+ * It manages a list of connection contexts and operations can be performed to
+ * add, remove, query, search, and change, those contexts.  Additionally,
+ * a dwc_notifier_t object can be requested from the manager so that
+ * the user can be notified whenever the context list has changed.
+ */
+
+#include "dwc_os.h"
+#include "dwc_list.h"
+#include "dwc_notifier.h"
+
+
+/* Notifications */
+#define DWC_CC_LIST_CHANGED_NOTIFICATION "DWC_CC_LIST_CHANGED_NOTIFICATION"
+
+struct dwc_cc_if;
+typedef struct dwc_cc_if dwc_cc_if_t;
+
+
+/** @name Connection Context Operations */
+/** @{ */
+
+/** This function allocates memory for a dwc_cc_if_t structure, initializes
+ * fields to default values, and returns a pointer to the structure or NULL on
+ * error. */
+extern dwc_cc_if_t *dwc_cc_if_alloc(void *mem_ctx, void *mtx_ctx,
+				    dwc_notifier_t *notifier, unsigned is_host);
+
+/** Frees the memory for the specified CC structure allocated from
+ * dwc_cc_if_alloc(). */
+extern void dwc_cc_if_free(void *mem_ctx, void *mtx_ctx, dwc_cc_if_t *cc_if);
+
+/** Removes all contexts from the connection context list */
+extern void dwc_cc_clear(void *mem_ctx, dwc_cc_if_t *cc_if);
+
+/** Adds a connection context (CHID, CK, CDID, Name) to the connection context list.
+ * If a CHID already exists, the CK and name are overwritten.  Statistics are
+ * not overwritten.
+ *
+ * @param cc_if The cc_if structure.
+ * @param chid A pointer to the 16-byte CHID.  This value will be copied.
+ * @param ck A pointer to the 16-byte CK.  This value will be copied.
+ * @param cdid A pointer to the 16-byte CDID.  This value will be copied.
+ * @param name An optional host friendly name as defined in the association model
+ * spec.  Must be a UTF16-LE unicode string.  Can be NULL to indicated no name.
+ * @param length The length othe unicode string.
+ * @return A unique identifier used to refer to this context that is valid for
+ * as long as this context is still in the list. */
+extern int32_t dwc_cc_add(void *mem_ctx, dwc_cc_if_t *cc_if, uint8_t *chid,
+			  uint8_t *cdid, uint8_t *ck, uint8_t *name,
+			  uint8_t length);
+
+/** Changes the CHID, CK, CDID, or Name values of a connection context in the
+ * list, preserving any accumulated statistics.  This would typically be called
+ * if the host decideds to change the context with a SET_CONNECTION request.
+ *
+ * @param cc_if The cc_if structure.
+ * @param id The identifier of the connection context.
+ * @param chid A pointer to the 16-byte CHID.  This value will be copied.  NULL
+ * indicates no change.
+ * @param cdid A pointer to the 16-byte CDID.  This value will be copied.  NULL
+ * indicates no change.
+ * @param ck A pointer to the 16-byte CK.  This value will be copied.  NULL
+ * indicates no change.
+ * @param name Host friendly name UTF16-LE.  NULL indicates no change.
+ * @param length Length of name. */
+extern void dwc_cc_change(void *mem_ctx, dwc_cc_if_t *cc_if, int32_t id,
+			  uint8_t *chid, uint8_t *cdid, uint8_t *ck,
+			  uint8_t *name, uint8_t length);
+
+/** Remove the specified connection context.
+ * @param cc_if The cc_if structure.
+ * @param id The identifier of the connection context to remove. */
+extern void dwc_cc_remove(void *mem_ctx, dwc_cc_if_t *cc_if, int32_t id);
+
+/** Get a binary block of data for the connection context list and attributes.
+ * This data can be used by the OS specific driver to save the connection
+ * context list into non-volatile memory.
+ *
+ * @param cc_if The cc_if structure.
+ * @param length Return the length of the data buffer.
+ * @return A pointer to the data buffer.  The memory for this buffer should be
+ * freed with DWC_FREE() after use. */
+extern uint8_t *dwc_cc_data_for_save(void *mem_ctx, dwc_cc_if_t *cc_if,
+				     unsigned int *length);
+
+/** Restore the connection context list from the binary data that was previously
+ * returned from a call to dwc_cc_data_for_save.  This can be used by the OS specific
+ * driver to load a connection context list from non-volatile memory.
+ *
+ * @param cc_if The cc_if structure.
+ * @param data The data bytes as returned from dwc_cc_data_for_save.
+ * @param length The length of the data. */
+extern void dwc_cc_restore_from_data(void *mem_ctx, dwc_cc_if_t *cc_if,
+				     uint8_t *data, unsigned int length);
+
+/** Find the connection context from the specified CHID.
+ *
+ * @param cc_if The cc_if structure.
+ * @param chid A pointer to the CHID data.
+ * @return A non-zero identifier of the connection context if the CHID matches.
+ * Otherwise returns 0. */
+extern uint32_t dwc_cc_match_chid(dwc_cc_if_t *cc_if, uint8_t *chid);
+
+/** Find the connection context from the specified CDID.
+ *
+ * @param cc_if The cc_if structure.
+ * @param cdid A pointer to the CDID data.
+ * @return A non-zero identifier of the connection context if the CHID matches.
+ * Otherwise returns 0. */
+extern uint32_t dwc_cc_match_cdid(dwc_cc_if_t *cc_if, uint8_t *cdid);
+
+/** Retrieve the CK from the specified connection context.
+ *
+ * @param cc_if The cc_if structure.
+ * @param id The identifier of the connection context.
+ * @return A pointer to the CK data.  The memory does not need to be freed. */
+extern uint8_t *dwc_cc_ck(dwc_cc_if_t *cc_if, int32_t id);
+
+/** Retrieve the CHID from the specified connection context.
+ *
+ * @param cc_if The cc_if structure.
+ * @param id The identifier of the connection context.
+ * @return A pointer to the CHID data.  The memory does not need to be freed. */
+extern uint8_t *dwc_cc_chid(dwc_cc_if_t *cc_if, int32_t id);
+
+/** Retrieve the CDID from the specified connection context.
+ *
+ * @param cc_if The cc_if structure.
+ * @param id The identifier of the connection context.
+ * @return A pointer to the CDID data.  The memory does not need to be freed. */
+extern uint8_t *dwc_cc_cdid(dwc_cc_if_t *cc_if, int32_t id);
+
+extern uint8_t *dwc_cc_name(dwc_cc_if_t *cc_if, int32_t id, uint8_t *length);
+
+/** Checks a buffer for non-zero.
+ * @param id A pointer to a 16 byte buffer.
+ * @return true if the 16 byte value is non-zero. */
+static inline unsigned dwc_assoc_is_not_zero_id(uint8_t *id) {
+	int i;
+	for (i=0; i<16; i++) {
+		if (id[i]) return 1;
+	}
+	return 0;
+}
+
+/** Checks a buffer for zero.
+ * @param id A pointer to a 16 byte buffer.
+ * @return true if the 16 byte value is zero. */
+static inline unsigned dwc_assoc_is_zero_id(uint8_t *id) {
+	return !dwc_assoc_is_not_zero_id(id);
+}
+
+/** Prints an ASCII representation for the 16-byte chid, cdid, or ck, into
+ * buffer. */
+static inline int dwc_print_id_string(char *buffer, uint8_t *id) {
+	char *ptr = buffer;
+	int i;
+	for (i=0; i<16; i++) {
+		ptr += DWC_SPRINTF(ptr, "%02x", id[i]);
+		if (i < 15) {
+			ptr += DWC_SPRINTF(ptr, " ");
+		}
+	}
+	return ptr - buffer;
+}
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _DWC_CC_H_ */
diff --git a/drivers/usb/host/dwc_common_port/dwc_common_fbsd.c b/drivers/usb/host/dwc_common_port/dwc_common_fbsd.c
new file mode 100644
index 000000000000..6dd04b58f8f6
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/dwc_common_fbsd.c
@@ -0,0 +1,1308 @@
+#include "dwc_os.h"
+#include "dwc_list.h"
+
+#ifdef DWC_CCLIB
+# include "dwc_cc.h"
+#endif
+
+#ifdef DWC_CRYPTOLIB
+# include "dwc_modpow.h"
+# include "dwc_dh.h"
+# include "dwc_crypto.h"
+#endif
+
+#ifdef DWC_NOTIFYLIB
+# include "dwc_notifier.h"
+#endif
+
+/* OS-Level Implementations */
+
+/* This is the FreeBSD 7.0 kernel implementation of the DWC platform library. */
+
+
+/* MISC */
+
+void *DWC_MEMSET(void *dest, uint8_t byte, uint32_t size)
+{
+	return memset(dest, byte, size);
+}
+
+void *DWC_MEMCPY(void *dest, void const *src, uint32_t size)
+{
+	return memcpy(dest, src, size);
+}
+
+void *DWC_MEMMOVE(void *dest, void *src, uint32_t size)
+{
+	bcopy(src, dest, size);
+	return dest;
+}
+
+int DWC_MEMCMP(void *m1, void *m2, uint32_t size)
+{
+	return memcmp(m1, m2, size);
+}
+
+int DWC_STRNCMP(void *s1, void *s2, uint32_t size)
+{
+	return strncmp(s1, s2, size);
+}
+
+int DWC_STRCMP(void *s1, void *s2)
+{
+	return strcmp(s1, s2);
+}
+
+int DWC_STRLEN(char const *str)
+{
+	return strlen(str);
+}
+
+char *DWC_STRCPY(char *to, char const *from)
+{
+	return strcpy(to, from);
+}
+
+char *DWC_STRDUP(char const *str)
+{
+	int len = DWC_STRLEN(str) + 1;
+	char *new = DWC_ALLOC_ATOMIC(len);
+
+	if (!new) {
+		return NULL;
+	}
+
+	DWC_MEMCPY(new, str, len);
+	return new;
+}
+
+int DWC_ATOI(char *str, int32_t *value)
+{
+	char *end = NULL;
+
+	*value = strtol(str, &end, 0);
+	if (*end == '\0') {
+		return 0;
+	}
+
+	return -1;
+}
+
+int DWC_ATOUI(char *str, uint32_t *value)
+{
+	char *end = NULL;
+
+	*value = strtoul(str, &end, 0);
+	if (*end == '\0') {
+		return 0;
+	}
+
+	return -1;
+}
+
+
+#ifdef DWC_UTFLIB
+/* From usbstring.c */
+
+int DWC_UTF8_TO_UTF16LE(uint8_t const *s, uint16_t *cp, unsigned len)
+{
+	int	count = 0;
+	u8	c;
+	u16	uchar;
+
+	/* this insists on correct encodings, though not minimal ones.
+	 * BUT it currently rejects legit 4-byte UTF-8 code points,
+	 * which need surrogate pairs.  (Unicode 3.1 can use them.)
+	 */
+	while (len != 0 && (c = (u8) *s++) != 0) {
+		if (unlikely(c & 0x80)) {
+			// 2-byte sequence:
+			// 00000yyyyyxxxxxx = 110yyyyy 10xxxxxx
+			if ((c & 0xe0) == 0xc0) {
+				uchar = (c & 0x1f) << 6;
+
+				c = (u8) *s++;
+				if ((c & 0xc0) != 0xc0)
+					goto fail;
+				c &= 0x3f;
+				uchar |= c;
+
+			// 3-byte sequence (most CJKV characters):
+			// zzzzyyyyyyxxxxxx = 1110zzzz 10yyyyyy 10xxxxxx
+			} else if ((c & 0xf0) == 0xe0) {
+				uchar = (c & 0x0f) << 12;
+
+				c = (u8) *s++;
+				if ((c & 0xc0) != 0xc0)
+					goto fail;
+				c &= 0x3f;
+				uchar |= c << 6;
+
+				c = (u8) *s++;
+				if ((c & 0xc0) != 0xc0)
+					goto fail;
+				c &= 0x3f;
+				uchar |= c;
+
+				/* no bogus surrogates */
+				if (0xd800 <= uchar && uchar <= 0xdfff)
+					goto fail;
+
+			// 4-byte sequence (surrogate pairs, currently rare):
+			// 11101110wwwwzzzzyy + 110111yyyyxxxxxx
+			//     = 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx
+			// (uuuuu = wwww + 1)
+			// FIXME accept the surrogate code points (only)
+			} else
+				goto fail;
+		} else
+			uchar = c;
+		put_unaligned (cpu_to_le16 (uchar), cp++);
+		count++;
+		len--;
+	}
+	return count;
+fail:
+	return -1;
+}
+
+#endif	/* DWC_UTFLIB */
+
+
+/* dwc_debug.h */
+
+dwc_bool_t DWC_IN_IRQ(void)
+{
+//	return in_irq();
+	return 0;
+}
+
+dwc_bool_t DWC_IN_BH(void)
+{
+//	return in_softirq();
+	return 0;
+}
+
+void DWC_VPRINTF(char *format, va_list args)
+{
+	vprintf(format, args);
+}
+
+int DWC_VSNPRINTF(char *str, int size, char *format, va_list args)
+{
+	return vsnprintf(str, size, format, args);
+}
+
+void DWC_PRINTF(char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	DWC_VPRINTF(format, args);
+	va_end(args);
+}
+
+int DWC_SPRINTF(char *buffer, char *format, ...)
+{
+	int retval;
+	va_list args;
+
+	va_start(args, format);
+	retval = vsprintf(buffer, format, args);
+	va_end(args);
+	return retval;
+}
+
+int DWC_SNPRINTF(char *buffer, int size, char *format, ...)
+{
+	int retval;
+	va_list args;
+
+	va_start(args, format);
+	retval = vsnprintf(buffer, size, format, args);
+	va_end(args);
+	return retval;
+}
+
+void __DWC_WARN(char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	DWC_VPRINTF(format, args);
+	va_end(args);
+}
+
+void __DWC_ERROR(char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	DWC_VPRINTF(format, args);
+	va_end(args);
+}
+
+void DWC_EXCEPTION(char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	DWC_VPRINTF(format, args);
+	va_end(args);
+//	BUG_ON(1);	???
+}
+
+#ifdef DEBUG
+void __DWC_DEBUG(char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	DWC_VPRINTF(format, args);
+	va_end(args);
+}
+#endif
+
+
+/* dwc_mem.h */
+
+#if 0
+dwc_pool_t *DWC_DMA_POOL_CREATE(uint32_t size,
+				uint32_t align,
+				uint32_t alloc)
+{
+	struct dma_pool *pool = dma_pool_create("Pool", NULL,
+						size, align, alloc);
+	return (dwc_pool_t *)pool;
+}
+
+void DWC_DMA_POOL_DESTROY(dwc_pool_t *pool)
+{
+	dma_pool_destroy((struct dma_pool *)pool);
+}
+
+void *DWC_DMA_POOL_ALLOC(dwc_pool_t *pool, uint64_t *dma_addr)
+{
+//	return dma_pool_alloc((struct dma_pool *)pool, GFP_KERNEL, dma_addr);
+	return dma_pool_alloc((struct dma_pool *)pool, M_WAITOK, dma_addr);
+}
+
+void *DWC_DMA_POOL_ZALLOC(dwc_pool_t *pool, uint64_t *dma_addr)
+{
+	void *vaddr = DWC_DMA_POOL_ALLOC(pool, dma_addr);
+	memset(..);
+}
+
+void DWC_DMA_POOL_FREE(dwc_pool_t *pool, void *vaddr, void *daddr)
+{
+	dma_pool_free(pool, vaddr, daddr);
+}
+#endif
+
+static void dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error)
+{
+	if (error)
+		return;
+	*(bus_addr_t *)arg = segs[0].ds_addr;
+}
+
+void *__DWC_DMA_ALLOC(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr)
+{
+	dwc_dmactx_t *dma = (dwc_dmactx_t *)dma_ctx;
+	int error;
+
+	error = bus_dma_tag_create(
+#if __FreeBSD_version >= 700000
+			bus_get_dma_tag(dma->dev),	/* parent */
+#else
+			NULL,				/* parent */
+#endif
+			4, 0,				/* alignment, bounds */
+			BUS_SPACE_MAXADDR_32BIT,	/* lowaddr */
+			BUS_SPACE_MAXADDR,		/* highaddr */
+			NULL, NULL,			/* filter, filterarg */
+			size,				/* maxsize */
+			1,				/* nsegments */
+			size,				/* maxsegsize */
+			0,				/* flags */
+			NULL,				/* lockfunc */
+			NULL,				/* lockarg */
+			&dma->dma_tag);
+	if (error) {
+		device_printf(dma->dev, "%s: bus_dma_tag_create failed: %d\n",
+			      __func__, error);
+		goto fail_0;
+	}
+
+	error = bus_dmamem_alloc(dma->dma_tag, &dma->dma_vaddr,
+				 BUS_DMA_NOWAIT | BUS_DMA_COHERENT, &dma->dma_map);
+	if (error) {
+		device_printf(dma->dev, "%s: bus_dmamem_alloc(%ju) failed: %d\n",
+			      __func__, (uintmax_t)size, error);
+		goto fail_1;
+	}
+
+	dma->dma_paddr = 0;
+	error = bus_dmamap_load(dma->dma_tag, dma->dma_map, dma->dma_vaddr, size,
+				dmamap_cb, &dma->dma_paddr, BUS_DMA_NOWAIT);
+	if (error || dma->dma_paddr == 0) {
+		device_printf(dma->dev, "%s: bus_dmamap_load failed: %d\n",
+			      __func__, error);
+		goto fail_2;
+	}
+
+	*dma_addr = dma->dma_paddr;
+	return dma->dma_vaddr;
+
+fail_2:
+	bus_dmamap_unload(dma->dma_tag, dma->dma_map);
+fail_1:
+	bus_dmamem_free(dma->dma_tag, dma->dma_vaddr, dma->dma_map);
+	bus_dma_tag_destroy(dma->dma_tag);
+fail_0:
+	dma->dma_map = NULL;
+	dma->dma_tag = NULL;
+
+	return NULL;
+}
+
+void __DWC_DMA_FREE(void *dma_ctx, uint32_t size, void *virt_addr, dwc_dma_t dma_addr)
+{
+	dwc_dmactx_t *dma = (dwc_dmactx_t *)dma_ctx;
+
+	if (dma->dma_tag == NULL)
+		return;
+	if (dma->dma_map != NULL) {
+		bus_dmamap_sync(dma->dma_tag, dma->dma_map,
+				BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
+		bus_dmamap_unload(dma->dma_tag, dma->dma_map);
+		bus_dmamem_free(dma->dma_tag, dma->dma_vaddr, dma->dma_map);
+		dma->dma_map = NULL;
+	}
+
+	bus_dma_tag_destroy(dma->dma_tag);
+	dma->dma_tag = NULL;
+}
+
+void *__DWC_ALLOC(void *mem_ctx, uint32_t size)
+{
+	return malloc(size, M_DEVBUF, M_WAITOK | M_ZERO);
+}
+
+void *__DWC_ALLOC_ATOMIC(void *mem_ctx, uint32_t size)
+{
+	return malloc(size, M_DEVBUF, M_NOWAIT | M_ZERO);
+}
+
+void __DWC_FREE(void *mem_ctx, void *addr)
+{
+	free(addr, M_DEVBUF);
+}
+
+
+#ifdef DWC_CRYPTOLIB
+/* dwc_crypto.h */
+
+void DWC_RANDOM_BYTES(uint8_t *buffer, uint32_t length)
+{
+	get_random_bytes(buffer, length);
+}
+
+int DWC_AES_CBC(uint8_t *message, uint32_t messagelen, uint8_t *key, uint32_t keylen, uint8_t iv[16], uint8_t *out)
+{
+	struct crypto_blkcipher *tfm;
+	struct blkcipher_desc desc;
+	struct scatterlist sgd;
+	struct scatterlist sgs;
+
+	tfm = crypto_alloc_blkcipher("cbc(aes)", 0, CRYPTO_ALG_ASYNC);
+	if (tfm == NULL) {
+		printk("failed to load transform for aes CBC\n");
+		return -1;
+	}
+
+	crypto_blkcipher_setkey(tfm, key, keylen);
+	crypto_blkcipher_set_iv(tfm, iv, 16);
+
+	sg_init_one(&sgd, out, messagelen);
+	sg_init_one(&sgs, message, messagelen);
+
+	desc.tfm = tfm;
+	desc.flags = 0;
+
+	if (crypto_blkcipher_encrypt(&desc, &sgd, &sgs, messagelen)) {
+		crypto_free_blkcipher(tfm);
+		DWC_ERROR("AES CBC encryption failed");
+		return -1;
+	}
+
+	crypto_free_blkcipher(tfm);
+	return 0;
+}
+
+int DWC_SHA256(uint8_t *message, uint32_t len, uint8_t *out)
+{
+	struct crypto_hash *tfm;
+	struct hash_desc desc;
+	struct scatterlist sg;
+
+	tfm = crypto_alloc_hash("sha256", 0, CRYPTO_ALG_ASYNC);
+	if (IS_ERR(tfm)) {
+		DWC_ERROR("Failed to load transform for sha256: %ld", PTR_ERR(tfm));
+		return 0;
+	}
+	desc.tfm = tfm;
+	desc.flags = 0;
+
+	sg_init_one(&sg, message, len);
+	crypto_hash_digest(&desc, &sg, len, out);
+	crypto_free_hash(tfm);
+
+	return 1;
+}
+
+int DWC_HMAC_SHA256(uint8_t *message, uint32_t messagelen,
+		    uint8_t *key, uint32_t keylen, uint8_t *out)
+{
+	struct crypto_hash *tfm;
+	struct hash_desc desc;
+	struct scatterlist sg;
+
+	tfm = crypto_alloc_hash("hmac(sha256)", 0, CRYPTO_ALG_ASYNC);
+	if (IS_ERR(tfm)) {
+		DWC_ERROR("Failed to load transform for hmac(sha256): %ld", PTR_ERR(tfm));
+		return 0;
+	}
+	desc.tfm = tfm;
+	desc.flags = 0;
+
+	sg_init_one(&sg, message, messagelen);
+	crypto_hash_setkey(tfm, key, keylen);
+	crypto_hash_digest(&desc, &sg, messagelen, out);
+	crypto_free_hash(tfm);
+
+	return 1;
+}
+
+#endif	/* DWC_CRYPTOLIB */
+
+
+/* Byte Ordering Conversions */
+
+uint32_t DWC_CPU_TO_LE32(uint32_t *p)
+{
+#ifdef __LITTLE_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+
+	return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24));
+#endif
+}
+
+uint32_t DWC_CPU_TO_BE32(uint32_t *p)
+{
+#ifdef __BIG_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+
+	return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24));
+#endif
+}
+
+uint32_t DWC_LE32_TO_CPU(uint32_t *p)
+{
+#ifdef __LITTLE_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+
+	return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24));
+#endif
+}
+
+uint32_t DWC_BE32_TO_CPU(uint32_t *p)
+{
+#ifdef __BIG_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+
+	return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24));
+#endif
+}
+
+uint16_t DWC_CPU_TO_LE16(uint16_t *p)
+{
+#ifdef __LITTLE_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+	return (u_p[1] | (u_p[0] << 8));
+#endif
+}
+
+uint16_t DWC_CPU_TO_BE16(uint16_t *p)
+{
+#ifdef __BIG_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+	return (u_p[1] | (u_p[0] << 8));
+#endif
+}
+
+uint16_t DWC_LE16_TO_CPU(uint16_t *p)
+{
+#ifdef __LITTLE_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+	return (u_p[1] | (u_p[0] << 8));
+#endif
+}
+
+uint16_t DWC_BE16_TO_CPU(uint16_t *p)
+{
+#ifdef __BIG_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+	return (u_p[1] | (u_p[0] << 8));
+#endif
+}
+
+
+/* Registers */
+
+uint32_t DWC_READ_REG32(void *io_ctx, uint32_t volatile *reg)
+{
+	dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx;
+	bus_size_t ior = (bus_size_t)reg;
+
+	return bus_space_read_4(io->iot, io->ioh, ior);
+}
+
+#if 0
+uint64_t DWC_READ_REG64(void *io_ctx, uint64_t volatile *reg)
+{
+	dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx;
+	bus_size_t ior = (bus_size_t)reg;
+
+	return bus_space_read_8(io->iot, io->ioh, ior);
+}
+#endif
+
+void DWC_WRITE_REG32(void *io_ctx, uint32_t volatile *reg, uint32_t value)
+{
+	dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx;
+	bus_size_t ior = (bus_size_t)reg;
+
+	bus_space_write_4(io->iot, io->ioh, ior, value);
+}
+
+#if 0
+void DWC_WRITE_REG64(void *io_ctx, uint64_t volatile *reg, uint64_t value)
+{
+	dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx;
+	bus_size_t ior = (bus_size_t)reg;
+
+	bus_space_write_8(io->iot, io->ioh, ior, value);
+}
+#endif
+
+void DWC_MODIFY_REG32(void *io_ctx, uint32_t volatile *reg, uint32_t clear_mask,
+		      uint32_t set_mask)
+{
+	dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx;
+	bus_size_t ior = (bus_size_t)reg;
+
+	bus_space_write_4(io->iot, io->ioh, ior,
+			  (bus_space_read_4(io->iot, io->ioh, ior) &
+			   ~clear_mask) | set_mask);
+}
+
+#if 0
+void DWC_MODIFY_REG64(void *io_ctx, uint64_t volatile *reg, uint64_t clear_mask,
+		      uint64_t set_mask)
+{
+	dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx;
+	bus_size_t ior = (bus_size_t)reg;
+
+	bus_space_write_8(io->iot, io->ioh, ior,
+			  (bus_space_read_8(io->iot, io->ioh, ior) &
+			   ~clear_mask) | set_mask);
+}
+#endif
+
+
+/* Locking */
+
+dwc_spinlock_t *DWC_SPINLOCK_ALLOC(void)
+{
+	struct mtx *sl = DWC_ALLOC(sizeof(*sl));
+
+	if (!sl) {
+		DWC_ERROR("Cannot allocate memory for spinlock");
+		return NULL;
+	}
+
+	mtx_init(sl, "dw3spn", NULL, MTX_SPIN);
+	return (dwc_spinlock_t *)sl;
+}
+
+void DWC_SPINLOCK_FREE(dwc_spinlock_t *lock)
+{
+	struct mtx *sl = (struct mtx *)lock;
+
+	mtx_destroy(sl);
+	DWC_FREE(sl);
+}
+
+void DWC_SPINLOCK(dwc_spinlock_t *lock)
+{
+	mtx_lock_spin((struct mtx *)lock);	// ???
+}
+
+void DWC_SPINUNLOCK(dwc_spinlock_t *lock)
+{
+	mtx_unlock_spin((struct mtx *)lock);	// ???
+}
+
+void DWC_SPINLOCK_IRQSAVE(dwc_spinlock_t *lock, dwc_irqflags_t *flags)
+{
+	mtx_lock_spin((struct mtx *)lock);
+}
+
+void DWC_SPINUNLOCK_IRQRESTORE(dwc_spinlock_t *lock, dwc_irqflags_t flags)
+{
+	mtx_unlock_spin((struct mtx *)lock);
+}
+
+dwc_mutex_t *DWC_MUTEX_ALLOC(void)
+{
+	struct mtx *m;
+	dwc_mutex_t *mutex = (dwc_mutex_t *)DWC_ALLOC(sizeof(struct mtx));
+
+	if (!mutex) {
+		DWC_ERROR("Cannot allocate memory for mutex");
+		return NULL;
+	}
+
+	m = (struct mtx *)mutex;
+	mtx_init(m, "dw3mtx", NULL, MTX_DEF);
+	return mutex;
+}
+
+#if (defined(DWC_LINUX) && defined(CONFIG_DEBUG_MUTEXES))
+#else
+void DWC_MUTEX_FREE(dwc_mutex_t *mutex)
+{
+	mtx_destroy((struct mtx *)mutex);
+	DWC_FREE(mutex);
+}
+#endif
+
+void DWC_MUTEX_LOCK(dwc_mutex_t *mutex)
+{
+	struct mtx *m = (struct mtx *)mutex;
+
+	mtx_lock(m);
+}
+
+int DWC_MUTEX_TRYLOCK(dwc_mutex_t *mutex)
+{
+	struct mtx *m = (struct mtx *)mutex;
+
+	return mtx_trylock(m);
+}
+
+void DWC_MUTEX_UNLOCK(dwc_mutex_t *mutex)
+{
+	struct mtx *m = (struct mtx *)mutex;
+
+	mtx_unlock(m);
+}
+
+
+/* Timing */
+
+void DWC_UDELAY(uint32_t usecs)
+{
+	DELAY(usecs);
+}
+
+void DWC_MDELAY(uint32_t msecs)
+{
+	do {
+		DELAY(1000);
+	} while (--msecs);
+}
+
+void DWC_MSLEEP(uint32_t msecs)
+{
+	struct timeval tv;
+
+	tv.tv_sec = msecs / 1000;
+	tv.tv_usec = (msecs - tv.tv_sec * 1000) * 1000;
+	pause("dw3slp", tvtohz(&tv));
+}
+
+uint32_t DWC_TIME(void)
+{
+	struct timeval tv;
+
+	microuptime(&tv);	// or getmicrouptime? (less precise, but faster)
+	return tv.tv_sec * 1000 + tv.tv_usec / 1000;
+}
+
+
+/* Timers */
+
+struct dwc_timer {
+	struct callout t;
+	char *name;
+	dwc_spinlock_t *lock;
+	dwc_timer_callback_t cb;
+	void *data;
+};
+
+dwc_timer_t *DWC_TIMER_ALLOC(char *name, dwc_timer_callback_t cb, void *data)
+{
+	dwc_timer_t *t = DWC_ALLOC(sizeof(*t));
+
+	if (!t) {
+		DWC_ERROR("Cannot allocate memory for timer");
+		return NULL;
+	}
+
+	callout_init(&t->t, 1);
+
+	t->name = DWC_STRDUP(name);
+	if (!t->name) {
+		DWC_ERROR("Cannot allocate memory for timer->name");
+		goto no_name;
+	}
+
+	t->lock = DWC_SPINLOCK_ALLOC();
+	if (!t->lock) {
+		DWC_ERROR("Cannot allocate memory for lock");
+		goto no_lock;
+	}
+
+	t->cb = cb;
+	t->data = data;
+
+	return t;
+
+ no_lock:
+	DWC_FREE(t->name);
+ no_name:
+	DWC_FREE(t);
+
+	return NULL;
+}
+
+void DWC_TIMER_FREE(dwc_timer_t *timer)
+{
+	callout_stop(&timer->t);
+	DWC_SPINLOCK_FREE(timer->lock);
+	DWC_FREE(timer->name);
+	DWC_FREE(timer);
+}
+
+void DWC_TIMER_SCHEDULE(dwc_timer_t *timer, uint32_t time)
+{
+	struct timeval tv;
+
+	tv.tv_sec = time / 1000;
+	tv.tv_usec = (time - tv.tv_sec * 1000) * 1000;
+	callout_reset(&timer->t, tvtohz(&tv), timer->cb, timer->data);
+}
+
+void DWC_TIMER_CANCEL(dwc_timer_t *timer)
+{
+	callout_stop(&timer->t);
+}
+
+
+/* Wait Queues */
+
+struct dwc_waitq {
+	struct mtx lock;
+	int abort;
+};
+
+dwc_waitq_t *DWC_WAITQ_ALLOC(void)
+{
+	dwc_waitq_t *wq = DWC_ALLOC(sizeof(*wq));
+
+	if (!wq) {
+		DWC_ERROR("Cannot allocate memory for waitqueue");
+		return NULL;
+	}
+
+	mtx_init(&wq->lock, "dw3wtq", NULL, MTX_DEF);
+	wq->abort = 0;
+
+	return wq;
+}
+
+void DWC_WAITQ_FREE(dwc_waitq_t *wq)
+{
+	mtx_destroy(&wq->lock);
+	DWC_FREE(wq);
+}
+
+int32_t DWC_WAITQ_WAIT(dwc_waitq_t *wq, dwc_waitq_condition_t cond, void *data)
+{
+//	intrmask_t ipl;
+	int result = 0;
+
+	mtx_lock(&wq->lock);
+//	ipl = splbio();
+
+	/* Skip the sleep if already aborted or triggered */
+	if (!wq->abort && !cond(data)) {
+//		splx(ipl);
+		result = msleep(wq, &wq->lock, PCATCH, "dw3wat", 0); // infinite timeout
+//		ipl = splbio();
+	}
+
+	if (result == ERESTART) {	// signaled - restart
+		result = -DWC_E_RESTART;
+
+	} else if (result == EINTR) {	// signaled - interrupt
+		result = -DWC_E_ABORT;
+
+	} else if (wq->abort) {
+		result = -DWC_E_ABORT;
+
+	} else {
+		result = 0;
+	}
+
+	wq->abort = 0;
+//	splx(ipl);
+	mtx_unlock(&wq->lock);
+	return result;
+}
+
+int32_t DWC_WAITQ_WAIT_TIMEOUT(dwc_waitq_t *wq, dwc_waitq_condition_t cond,
+			       void *data, int32_t msecs)
+{
+	struct timeval tv, tv1, tv2;
+//	intrmask_t ipl;
+	int result = 0;
+
+	tv.tv_sec = msecs / 1000;
+	tv.tv_usec = (msecs - tv.tv_sec * 1000) * 1000;
+
+	mtx_lock(&wq->lock);
+//	ipl = splbio();
+
+	/* Skip the sleep if already aborted or triggered */
+	if (!wq->abort && !cond(data)) {
+//		splx(ipl);
+		getmicrouptime(&tv1);
+		result = msleep(wq, &wq->lock, PCATCH, "dw3wto", tvtohz(&tv));
+		getmicrouptime(&tv2);
+//		ipl = splbio();
+	}
+
+	if (result == 0) {			// awoken
+		if (wq->abort) {
+			result = -DWC_E_ABORT;
+		} else {
+			tv2.tv_usec -= tv1.tv_usec;
+			if (tv2.tv_usec < 0) {
+				tv2.tv_usec += 1000000;
+				tv2.tv_sec--;
+			}
+
+			tv2.tv_sec -= tv1.tv_sec;
+			result = tv2.tv_sec * 1000 + tv2.tv_usec / 1000;
+			result = msecs - result;
+			if (result <= 0)
+				result = 1;
+		}
+	} else if (result == ERESTART) {	// signaled - restart
+		result = -DWC_E_RESTART;
+
+	} else if (result == EINTR) {		// signaled - interrupt
+		result = -DWC_E_ABORT;
+
+	} else {				// timed out
+		result = -DWC_E_TIMEOUT;
+	}
+
+	wq->abort = 0;
+//	splx(ipl);
+	mtx_unlock(&wq->lock);
+	return result;
+}
+
+void DWC_WAITQ_TRIGGER(dwc_waitq_t *wq)
+{
+	wakeup(wq);
+}
+
+void DWC_WAITQ_ABORT(dwc_waitq_t *wq)
+{
+//	intrmask_t ipl;
+
+	mtx_lock(&wq->lock);
+//	ipl = splbio();
+	wq->abort = 1;
+	wakeup(wq);
+//	splx(ipl);
+	mtx_unlock(&wq->lock);
+}
+
+
+/* Threading */
+
+struct dwc_thread {
+	struct proc *proc;
+	int abort;
+};
+
+dwc_thread_t *DWC_THREAD_RUN(dwc_thread_function_t func, char *name, void *data)
+{
+	int retval;
+	dwc_thread_t *thread = DWC_ALLOC(sizeof(*thread));
+
+	if (!thread) {
+		return NULL;
+	}
+
+	thread->abort = 0;
+	retval = kthread_create((void (*)(void *))func, data, &thread->proc,
+				RFPROC | RFNOWAIT, 0, "%s", name);
+	if (retval) {
+		DWC_FREE(thread);
+		return NULL;
+	}
+
+	return thread;
+}
+
+int DWC_THREAD_STOP(dwc_thread_t *thread)
+{
+	int retval;
+
+	thread->abort = 1;
+	retval = tsleep(&thread->abort, 0, "dw3stp", 60 * hz);
+
+	if (retval == 0) {
+		/* DWC_THREAD_EXIT() will free the thread struct */
+		return 0;
+	}
+
+	/* NOTE: We leak the thread struct if thread doesn't die */
+
+	if (retval == EWOULDBLOCK) {
+		return -DWC_E_TIMEOUT;
+	}
+
+	return -DWC_E_UNKNOWN;
+}
+
+dwc_bool_t DWC_THREAD_SHOULD_STOP(dwc_thread_t *thread)
+{
+	return thread->abort;
+}
+
+void DWC_THREAD_EXIT(dwc_thread_t *thread)
+{
+	wakeup(&thread->abort);
+	DWC_FREE(thread);
+	kthread_exit(0);
+}
+
+
+/* tasklets
+ - Runs in interrupt context (cannot sleep)
+ - Each tasklet runs on a single CPU [ How can we ensure this on FreeBSD? Does it matter? ]
+ - Different tasklets can be running simultaneously on different CPUs [ shouldn't matter ]
+ */
+struct dwc_tasklet {
+	struct task t;
+	dwc_tasklet_callback_t cb;
+	void *data;
+};
+
+static void tasklet_callback(void *data, int pending)	// what to do with pending ???
+{
+	dwc_tasklet_t *task = (dwc_tasklet_t *)data;
+
+	task->cb(task->data);
+}
+
+dwc_tasklet_t *DWC_TASK_ALLOC(char *name, dwc_tasklet_callback_t cb, void *data)
+{
+	dwc_tasklet_t *task = DWC_ALLOC(sizeof(*task));
+
+	if (task) {
+		task->cb = cb;
+		task->data = data;
+		TASK_INIT(&task->t, 0, tasklet_callback, task);
+	} else {
+		DWC_ERROR("Cannot allocate memory for tasklet");
+	}
+
+	return task;
+}
+
+void DWC_TASK_FREE(dwc_tasklet_t *task)
+{
+	taskqueue_drain(taskqueue_fast, &task->t);	// ???
+	DWC_FREE(task);
+}
+
+void DWC_TASK_SCHEDULE(dwc_tasklet_t *task)
+{
+	/* Uses predefined system queue */
+	taskqueue_enqueue_fast(taskqueue_fast, &task->t);
+}
+
+
+/* workqueues
+ - Runs in process context (can sleep)
+ */
+typedef struct work_container {
+	dwc_work_callback_t cb;
+	void *data;
+	dwc_workq_t *wq;
+	char *name;
+	int hz;
+
+#ifdef DEBUG
+	DWC_CIRCLEQ_ENTRY(work_container) entry;
+#endif
+	struct task task;
+} work_container_t;
+
+#ifdef DEBUG
+DWC_CIRCLEQ_HEAD(work_container_queue, work_container);
+#endif
+
+struct dwc_workq {
+	struct taskqueue *taskq;
+	dwc_spinlock_t *lock;
+	dwc_waitq_t *waitq;
+	int pending;
+
+#ifdef DEBUG
+	struct work_container_queue entries;
+#endif
+};
+
+static void do_work(void *data, int pending)	// what to do with pending ???
+{
+	work_container_t *container = (work_container_t *)data;
+	dwc_workq_t *wq = container->wq;
+	dwc_irqflags_t flags;
+
+	if (container->hz) {
+		pause("dw3wrk", container->hz);
+	}
+
+	container->cb(container->data);
+	DWC_DEBUG("Work done: %s, container=%p", container->name, container);
+
+	DWC_SPINLOCK_IRQSAVE(wq->lock, &flags);
+
+#ifdef DEBUG
+	DWC_CIRCLEQ_REMOVE(&wq->entries, container, entry);
+#endif
+	if (container->name)
+		DWC_FREE(container->name);
+	DWC_FREE(container);
+	wq->pending--;
+	DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags);
+	DWC_WAITQ_TRIGGER(wq->waitq);
+}
+
+static int work_done(void *data)
+{
+	dwc_workq_t *workq = (dwc_workq_t *)data;
+
+	return workq->pending == 0;
+}
+
+int DWC_WORKQ_WAIT_WORK_DONE(dwc_workq_t *workq, int timeout)
+{
+	return DWC_WAITQ_WAIT_TIMEOUT(workq->waitq, work_done, workq, timeout);
+}
+
+dwc_workq_t *DWC_WORKQ_ALLOC(char *name)
+{
+	dwc_workq_t *wq = DWC_ALLOC(sizeof(*wq));
+
+	if (!wq) {
+		DWC_ERROR("Cannot allocate memory for workqueue");
+		return NULL;
+	}
+
+	wq->taskq = taskqueue_create(name, M_NOWAIT, taskqueue_thread_enqueue, &wq->taskq);
+	if (!wq->taskq) {
+		DWC_ERROR("Cannot allocate memory for taskqueue");
+		goto no_taskq;
+	}
+
+	wq->pending = 0;
+
+	wq->lock = DWC_SPINLOCK_ALLOC();
+	if (!wq->lock) {
+		DWC_ERROR("Cannot allocate memory for spinlock");
+		goto no_lock;
+	}
+
+	wq->waitq = DWC_WAITQ_ALLOC();
+	if (!wq->waitq) {
+		DWC_ERROR("Cannot allocate memory for waitqueue");
+		goto no_waitq;
+	}
+
+	taskqueue_start_threads(&wq->taskq, 1, PWAIT, "%s taskq", "dw3tsk");
+
+#ifdef DEBUG
+	DWC_CIRCLEQ_INIT(&wq->entries);
+#endif
+	return wq;
+
+ no_waitq:
+	DWC_SPINLOCK_FREE(wq->lock);
+ no_lock:
+	taskqueue_free(wq->taskq);
+ no_taskq:
+	DWC_FREE(wq);
+
+	return NULL;
+}
+
+void DWC_WORKQ_FREE(dwc_workq_t *wq)
+{
+#ifdef DEBUG
+	dwc_irqflags_t flags;
+
+	DWC_SPINLOCK_IRQSAVE(wq->lock, &flags);
+
+	if (wq->pending != 0) {
+		struct work_container *container;
+
+		DWC_ERROR("Destroying work queue with pending work");
+
+		DWC_CIRCLEQ_FOREACH(container, &wq->entries, entry) {
+			DWC_ERROR("Work %s still pending", container->name);
+		}
+	}
+
+	DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags);
+#endif
+	DWC_WAITQ_FREE(wq->waitq);
+	DWC_SPINLOCK_FREE(wq->lock);
+	taskqueue_free(wq->taskq);
+	DWC_FREE(wq);
+}
+
+void DWC_WORKQ_SCHEDULE(dwc_workq_t *wq, dwc_work_callback_t cb, void *data,
+			char *format, ...)
+{
+	dwc_irqflags_t flags;
+	work_container_t *container;
+	static char name[128];
+	va_list args;
+
+	va_start(args, format);
+	DWC_VSNPRINTF(name, 128, format, args);
+	va_end(args);
+
+	DWC_SPINLOCK_IRQSAVE(wq->lock, &flags);
+	wq->pending++;
+	DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags);
+	DWC_WAITQ_TRIGGER(wq->waitq);
+
+	container = DWC_ALLOC_ATOMIC(sizeof(*container));
+	if (!container) {
+		DWC_ERROR("Cannot allocate memory for container");
+		return;
+	}
+
+	container->name = DWC_STRDUP(name);
+	if (!container->name) {
+		DWC_ERROR("Cannot allocate memory for container->name");
+		DWC_FREE(container);
+		return;
+	}
+
+	container->cb = cb;
+	container->data = data;
+	container->wq = wq;
+	container->hz = 0;
+
+	DWC_DEBUG("Queueing work: %s, container=%p", container->name, container);
+
+	TASK_INIT(&container->task, 0, do_work, container);
+
+#ifdef DEBUG
+	DWC_CIRCLEQ_INSERT_TAIL(&wq->entries, container, entry);
+#endif
+	taskqueue_enqueue_fast(wq->taskq, &container->task);
+}
+
+void DWC_WORKQ_SCHEDULE_DELAYED(dwc_workq_t *wq, dwc_work_callback_t cb,
+				void *data, uint32_t time, char *format, ...)
+{
+	dwc_irqflags_t flags;
+	work_container_t *container;
+	static char name[128];
+	struct timeval tv;
+	va_list args;
+
+	va_start(args, format);
+	DWC_VSNPRINTF(name, 128, format, args);
+	va_end(args);
+
+	DWC_SPINLOCK_IRQSAVE(wq->lock, &flags);
+	wq->pending++;
+	DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags);
+	DWC_WAITQ_TRIGGER(wq->waitq);
+
+	container = DWC_ALLOC_ATOMIC(sizeof(*container));
+	if (!container) {
+		DWC_ERROR("Cannot allocate memory for container");
+		return;
+	}
+
+	container->name = DWC_STRDUP(name);
+	if (!container->name) {
+		DWC_ERROR("Cannot allocate memory for container->name");
+		DWC_FREE(container);
+		return;
+	}
+
+	container->cb = cb;
+	container->data = data;
+	container->wq = wq;
+
+	tv.tv_sec = time / 1000;
+	tv.tv_usec = (time - tv.tv_sec * 1000) * 1000;
+	container->hz = tvtohz(&tv);
+
+	DWC_DEBUG("Queueing work: %s, container=%p", container->name, container);
+
+	TASK_INIT(&container->task, 0, do_work, container);
+
+#ifdef DEBUG
+	DWC_CIRCLEQ_INSERT_TAIL(&wq->entries, container, entry);
+#endif
+	taskqueue_enqueue_fast(wq->taskq, &container->task);
+}
+
+int DWC_WORKQ_PENDING(dwc_workq_t *wq)
+{
+	return wq->pending;
+}
diff --git a/drivers/usb/host/dwc_common_port/dwc_common_linux.c b/drivers/usb/host/dwc_common_port/dwc_common_linux.c
new file mode 100644
index 000000000000..673b3b75755d
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/dwc_common_linux.c
@@ -0,0 +1,1409 @@
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kthread.h>
+
+#ifdef DWC_CCLIB
+# include "dwc_cc.h"
+#endif
+
+#ifdef DWC_CRYPTOLIB
+# include "dwc_modpow.h"
+# include "dwc_dh.h"
+# include "dwc_crypto.h"
+#endif
+
+#ifdef DWC_NOTIFYLIB
+# include "dwc_notifier.h"
+#endif
+
+/* OS-Level Implementations */
+
+/* This is the Linux kernel implementation of the DWC platform library. */
+#include <linux/moduleparam.h>
+#include <linux/ctype.h>
+#include <linux/crypto.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/cdev.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/jiffies.h>
+#include <linux/list.h>
+#include <linux/pci.h>
+#include <linux/random.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+#include <linux/stat.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/usb.h>
+
+#include <linux/version.h>
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)
+# include <linux/usb/gadget.h>
+#else
+# include <linux/usb_gadget.h>
+#endif
+
+#include <asm/io.h>
+#include <asm/page.h>
+#include <asm/uaccess.h>
+#include <linux/unaligned.h>
+
+#include "dwc_os.h"
+#include "dwc_list.h"
+
+
+/* MISC */
+
+void *DWC_MEMSET(void *dest, uint8_t byte, uint32_t size)
+{
+	return memset(dest, byte, size);
+}
+
+void *DWC_MEMCPY(void *dest, void const *src, uint32_t size)
+{
+	return memcpy(dest, src, size);
+}
+
+void *DWC_MEMMOVE(void *dest, void *src, uint32_t size)
+{
+	return memmove(dest, src, size);
+}
+
+int DWC_MEMCMP(void *m1, void *m2, uint32_t size)
+{
+	return memcmp(m1, m2, size);
+}
+
+int DWC_STRNCMP(void *s1, void *s2, uint32_t size)
+{
+	return strncmp(s1, s2, size);
+}
+
+int DWC_STRCMP(void *s1, void *s2)
+{
+	return strcmp(s1, s2);
+}
+
+int DWC_STRLEN(char const *str)
+{
+	return strlen(str);
+}
+
+char *DWC_STRCPY(char *to, char const *from)
+{
+	return strcpy(to, from);
+}
+
+char *DWC_STRDUP(char const *str)
+{
+	int len = DWC_STRLEN(str) + 1;
+	char *new = DWC_ALLOC_ATOMIC(len);
+
+	if (!new) {
+		return NULL;
+	}
+
+	DWC_MEMCPY(new, str, len);
+	return new;
+}
+
+int DWC_ATOI(const char *str, int32_t *value)
+{
+	char *end = NULL;
+
+	*value = simple_strtol(str, &end, 0);
+	if (*end == '\0') {
+		return 0;
+	}
+
+	return -1;
+}
+
+int DWC_ATOUI(const char *str, uint32_t *value)
+{
+	char *end = NULL;
+
+	*value = simple_strtoul(str, &end, 0);
+	if (*end == '\0') {
+		return 0;
+	}
+
+	return -1;
+}
+
+
+#ifdef DWC_UTFLIB
+/* From usbstring.c */
+
+int DWC_UTF8_TO_UTF16LE(uint8_t const *s, uint16_t *cp, unsigned len)
+{
+	int	count = 0;
+	u8	c;
+	u16	uchar;
+
+	/* this insists on correct encodings, though not minimal ones.
+	 * BUT it currently rejects legit 4-byte UTF-8 code points,
+	 * which need surrogate pairs.  (Unicode 3.1 can use them.)
+	 */
+	while (len != 0 && (c = (u8) *s++) != 0) {
+		if (unlikely(c & 0x80)) {
+			// 2-byte sequence:
+			// 00000yyyyyxxxxxx = 110yyyyy 10xxxxxx
+			if ((c & 0xe0) == 0xc0) {
+				uchar = (c & 0x1f) << 6;
+
+				c = (u8) *s++;
+				if ((c & 0xc0) != 0xc0)
+					goto fail;
+				c &= 0x3f;
+				uchar |= c;
+
+			// 3-byte sequence (most CJKV characters):
+			// zzzzyyyyyyxxxxxx = 1110zzzz 10yyyyyy 10xxxxxx
+			} else if ((c & 0xf0) == 0xe0) {
+				uchar = (c & 0x0f) << 12;
+
+				c = (u8) *s++;
+				if ((c & 0xc0) != 0xc0)
+					goto fail;
+				c &= 0x3f;
+				uchar |= c << 6;
+
+				c = (u8) *s++;
+				if ((c & 0xc0) != 0xc0)
+					goto fail;
+				c &= 0x3f;
+				uchar |= c;
+
+				/* no bogus surrogates */
+				if (0xd800 <= uchar && uchar <= 0xdfff)
+					goto fail;
+
+			// 4-byte sequence (surrogate pairs, currently rare):
+			// 11101110wwwwzzzzyy + 110111yyyyxxxxxx
+			//     = 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx
+			// (uuuuu = wwww + 1)
+			// FIXME accept the surrogate code points (only)
+			} else
+				goto fail;
+		} else
+			uchar = c;
+		put_unaligned (cpu_to_le16 (uchar), cp++);
+		count++;
+		len--;
+	}
+	return count;
+fail:
+	return -1;
+}
+#endif	/* DWC_UTFLIB */
+
+
+/* dwc_debug.h */
+
+dwc_bool_t DWC_IN_IRQ(void)
+{
+	return in_irq();
+}
+
+dwc_bool_t DWC_IN_BH(void)
+{
+	return in_softirq();
+}
+
+void DWC_VPRINTF(char *format, va_list args)
+{
+	vprintk(format, args);
+}
+
+int DWC_VSNPRINTF(char *str, int size, char *format, va_list args)
+{
+	return vsnprintf(str, size, format, args);
+}
+
+void DWC_PRINTF(char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	DWC_VPRINTF(format, args);
+	va_end(args);
+}
+
+int DWC_SPRINTF(char *buffer, char *format, ...)
+{
+	int retval;
+	va_list args;
+
+	va_start(args, format);
+	retval = vsprintf(buffer, format, args);
+	va_end(args);
+	return retval;
+}
+
+int DWC_SNPRINTF(char *buffer, int size, char *format, ...)
+{
+	int retval;
+	va_list args;
+
+	va_start(args, format);
+	retval = vsnprintf(buffer, size, format, args);
+	va_end(args);
+	return retval;
+}
+
+void __DWC_WARN(char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	DWC_PRINTF(KERN_WARNING);
+	DWC_VPRINTF(format, args);
+	va_end(args);
+}
+
+void __DWC_ERROR(char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	DWC_PRINTF(KERN_ERR);
+	DWC_VPRINTF(format, args);
+	va_end(args);
+}
+
+void DWC_EXCEPTION(char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	DWC_PRINTF(KERN_ERR);
+	DWC_VPRINTF(format, args);
+	va_end(args);
+	BUG_ON(1);
+}
+
+#ifdef DEBUG
+void __DWC_DEBUG(char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	DWC_PRINTF(KERN_DEBUG);
+	DWC_VPRINTF(format, args);
+	va_end(args);
+}
+#endif
+
+
+/* dwc_mem.h */
+
+#if 0
+dwc_pool_t *DWC_DMA_POOL_CREATE(uint32_t size,
+				uint32_t align,
+				uint32_t alloc)
+{
+	struct dma_pool *pool = dma_pool_create("Pool", NULL,
+						size, align, alloc);
+	return (dwc_pool_t *)pool;
+}
+
+void DWC_DMA_POOL_DESTROY(dwc_pool_t *pool)
+{
+	dma_pool_destroy((struct dma_pool *)pool);
+}
+
+void *DWC_DMA_POOL_ALLOC(dwc_pool_t *pool, uint64_t *dma_addr)
+{
+	return dma_pool_alloc((struct dma_pool *)pool, GFP_KERNEL, dma_addr);
+}
+
+void *DWC_DMA_POOL_ZALLOC(dwc_pool_t *pool, uint64_t *dma_addr)
+{
+	void *vaddr = DWC_DMA_POOL_ALLOC(pool, dma_addr);
+	memset(..);
+}
+
+void DWC_DMA_POOL_FREE(dwc_pool_t *pool, void *vaddr, void *daddr)
+{
+	dma_pool_free(pool, vaddr, daddr);
+}
+#endif
+
+void *__DWC_DMA_ALLOC(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr)
+{
+	return dma_alloc_coherent(dma_ctx, size, dma_addr, GFP_KERNEL | GFP_DMA32);
+}
+
+void *__DWC_DMA_ALLOC_ATOMIC(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr)
+{
+	return dma_alloc_coherent(dma_ctx, size, dma_addr, GFP_ATOMIC);
+}
+
+void __DWC_DMA_FREE(void *dma_ctx, uint32_t size, void *virt_addr, dwc_dma_t dma_addr)
+{
+	dma_free_coherent(dma_ctx, size, virt_addr, dma_addr);
+}
+
+void *__DWC_ALLOC(void *mem_ctx, uint32_t size)
+{
+	return kzalloc(size, GFP_KERNEL);
+}
+
+void *__DWC_ALLOC_ATOMIC(void *mem_ctx, uint32_t size)
+{
+	return kzalloc(size, GFP_ATOMIC);
+}
+
+void __DWC_FREE(void *mem_ctx, void *addr)
+{
+	kfree(addr);
+}
+
+
+#ifdef DWC_CRYPTOLIB
+/* dwc_crypto.h */
+
+void DWC_RANDOM_BYTES(uint8_t *buffer, uint32_t length)
+{
+	get_random_bytes(buffer, length);
+}
+
+int DWC_AES_CBC(uint8_t *message, uint32_t messagelen, uint8_t *key, uint32_t keylen, uint8_t iv[16], uint8_t *out)
+{
+	struct crypto_blkcipher *tfm;
+	struct blkcipher_desc desc;
+	struct scatterlist sgd;
+	struct scatterlist sgs;
+
+	tfm = crypto_alloc_blkcipher("cbc(aes)", 0, CRYPTO_ALG_ASYNC);
+	if (tfm == NULL) {
+		printk("failed to load transform for aes CBC\n");
+		return -1;
+	}
+
+	crypto_blkcipher_setkey(tfm, key, keylen);
+	crypto_blkcipher_set_iv(tfm, iv, 16);
+
+	sg_init_one(&sgd, out, messagelen);
+	sg_init_one(&sgs, message, messagelen);
+
+	desc.tfm = tfm;
+	desc.flags = 0;
+
+	if (crypto_blkcipher_encrypt(&desc, &sgd, &sgs, messagelen)) {
+		crypto_free_blkcipher(tfm);
+		DWC_ERROR("AES CBC encryption failed");
+		return -1;
+	}
+
+	crypto_free_blkcipher(tfm);
+	return 0;
+}
+
+int DWC_SHA256(uint8_t *message, uint32_t len, uint8_t *out)
+{
+	struct crypto_hash *tfm;
+	struct hash_desc desc;
+	struct scatterlist sg;
+
+	tfm = crypto_alloc_hash("sha256", 0, CRYPTO_ALG_ASYNC);
+	if (IS_ERR(tfm)) {
+		DWC_ERROR("Failed to load transform for sha256: %ld\n", PTR_ERR(tfm));
+		return 0;
+	}
+	desc.tfm = tfm;
+	desc.flags = 0;
+
+	sg_init_one(&sg, message, len);
+	crypto_hash_digest(&desc, &sg, len, out);
+	crypto_free_hash(tfm);
+
+	return 1;
+}
+
+int DWC_HMAC_SHA256(uint8_t *message, uint32_t messagelen,
+		    uint8_t *key, uint32_t keylen, uint8_t *out)
+{
+	struct crypto_hash *tfm;
+	struct hash_desc desc;
+	struct scatterlist sg;
+
+	tfm = crypto_alloc_hash("hmac(sha256)", 0, CRYPTO_ALG_ASYNC);
+	if (IS_ERR(tfm)) {
+		DWC_ERROR("Failed to load transform for hmac(sha256): %ld\n", PTR_ERR(tfm));
+		return 0;
+	}
+	desc.tfm = tfm;
+	desc.flags = 0;
+
+	sg_init_one(&sg, message, messagelen);
+	crypto_hash_setkey(tfm, key, keylen);
+	crypto_hash_digest(&desc, &sg, messagelen, out);
+	crypto_free_hash(tfm);
+
+	return 1;
+}
+#endif	/* DWC_CRYPTOLIB */
+
+
+/* Byte Ordering Conversions */
+
+uint32_t DWC_CPU_TO_LE32(uint32_t *p)
+{
+#ifdef __LITTLE_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+
+	return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24));
+#endif
+}
+
+uint32_t DWC_CPU_TO_BE32(uint32_t *p)
+{
+#ifdef __BIG_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+
+	return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24));
+#endif
+}
+
+uint32_t DWC_LE32_TO_CPU(uint32_t *p)
+{
+#ifdef __LITTLE_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+
+	return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24));
+#endif
+}
+
+uint32_t DWC_BE32_TO_CPU(uint32_t *p)
+{
+#ifdef __BIG_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+
+	return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24));
+#endif
+}
+
+uint16_t DWC_CPU_TO_LE16(uint16_t *p)
+{
+#ifdef __LITTLE_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+	return (u_p[1] | (u_p[0] << 8));
+#endif
+}
+
+uint16_t DWC_CPU_TO_BE16(uint16_t *p)
+{
+#ifdef __BIG_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+	return (u_p[1] | (u_p[0] << 8));
+#endif
+}
+
+uint16_t DWC_LE16_TO_CPU(uint16_t *p)
+{
+#ifdef __LITTLE_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+	return (u_p[1] | (u_p[0] << 8));
+#endif
+}
+
+uint16_t DWC_BE16_TO_CPU(uint16_t *p)
+{
+#ifdef __BIG_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+	return (u_p[1] | (u_p[0] << 8));
+#endif
+}
+
+
+/* Registers */
+
+uint32_t DWC_READ_REG32(uint32_t volatile *reg)
+{
+	return readl(reg);
+}
+
+#if 0
+uint64_t DWC_READ_REG64(uint64_t volatile *reg)
+{
+}
+#endif
+
+void DWC_WRITE_REG32(uint32_t volatile *reg, uint32_t value)
+{
+	writel(value, reg);
+}
+
+#if 0
+void DWC_WRITE_REG64(uint64_t volatile *reg, uint64_t value)
+{
+}
+#endif
+
+void DWC_MODIFY_REG32(uint32_t volatile *reg, uint32_t clear_mask, uint32_t set_mask)
+{
+	writel((readl(reg) & ~clear_mask) | set_mask, reg);
+}
+
+#if 0
+void DWC_MODIFY_REG64(uint64_t volatile *reg, uint64_t clear_mask, uint64_t set_mask)
+{
+}
+#endif
+
+
+/* Locking */
+
+dwc_spinlock_t *DWC_SPINLOCK_ALLOC(void)
+{
+	spinlock_t *sl = (spinlock_t *)1;
+
+#if defined(CONFIG_PREEMPT) || defined(CONFIG_SMP)
+	sl = DWC_ALLOC(sizeof(*sl));
+	if (!sl) {
+		DWC_ERROR("Cannot allocate memory for spinlock\n");
+		return NULL;
+	}
+
+	spin_lock_init(sl);
+#endif
+	return (dwc_spinlock_t *)sl;
+}
+
+void DWC_SPINLOCK_FREE(dwc_spinlock_t *lock)
+{
+#if defined(CONFIG_PREEMPT) || defined(CONFIG_SMP)
+	DWC_FREE(lock);
+#endif
+}
+
+void DWC_SPINLOCK(dwc_spinlock_t *lock)
+{
+#if defined(CONFIG_PREEMPT) || defined(CONFIG_SMP)
+	spin_lock((spinlock_t *)lock);
+#endif
+}
+
+void DWC_SPINUNLOCK(dwc_spinlock_t *lock)
+{
+#if defined(CONFIG_PREEMPT) || defined(CONFIG_SMP)
+	spin_unlock((spinlock_t *)lock);
+#endif
+}
+
+void DWC_SPINLOCK_IRQSAVE(dwc_spinlock_t *lock, dwc_irqflags_t *flags)
+{
+	dwc_irqflags_t f;
+
+#if defined(CONFIG_PREEMPT) || defined(CONFIG_SMP)
+	spin_lock_irqsave((spinlock_t *)lock, f);
+#else
+	local_irq_save(f);
+#endif
+	*flags = f;
+}
+
+void DWC_SPINUNLOCK_IRQRESTORE(dwc_spinlock_t *lock, dwc_irqflags_t flags)
+{
+#if defined(CONFIG_PREEMPT) || defined(CONFIG_SMP)
+	spin_unlock_irqrestore((spinlock_t *)lock, flags);
+#else
+	local_irq_restore(flags);
+#endif
+}
+
+dwc_mutex_t *DWC_MUTEX_ALLOC(void)
+{
+	struct mutex *m;
+	dwc_mutex_t *mutex = (dwc_mutex_t *)DWC_ALLOC(sizeof(struct mutex));
+
+	if (!mutex) {
+		DWC_ERROR("Cannot allocate memory for mutex\n");
+		return NULL;
+	}
+
+	m = (struct mutex *)mutex;
+	mutex_init(m);
+	return mutex;
+}
+
+#if (defined(DWC_LINUX) && defined(CONFIG_DEBUG_MUTEXES))
+#else
+void DWC_MUTEX_FREE(dwc_mutex_t *mutex)
+{
+	mutex_destroy((struct mutex *)mutex);
+	DWC_FREE(mutex);
+}
+#endif
+
+void DWC_MUTEX_LOCK(dwc_mutex_t *mutex)
+{
+	struct mutex *m = (struct mutex *)mutex;
+	mutex_lock(m);
+}
+
+int DWC_MUTEX_TRYLOCK(dwc_mutex_t *mutex)
+{
+	struct mutex *m = (struct mutex *)mutex;
+	return mutex_trylock(m);
+}
+
+void DWC_MUTEX_UNLOCK(dwc_mutex_t *mutex)
+{
+	struct mutex *m = (struct mutex *)mutex;
+	mutex_unlock(m);
+}
+
+
+/* Timing */
+
+void DWC_UDELAY(uint32_t usecs)
+{
+	udelay(usecs);
+}
+
+void DWC_MDELAY(uint32_t msecs)
+{
+	mdelay(msecs);
+}
+
+void DWC_MSLEEP(uint32_t msecs)
+{
+	msleep(msecs);
+}
+
+uint32_t DWC_TIME(void)
+{
+	return jiffies_to_msecs(jiffies);
+}
+
+
+/* Timers */
+
+struct dwc_timer {
+	struct timer_list t;
+	char *name;
+	dwc_timer_callback_t cb;
+	void *data;
+	uint8_t scheduled;
+	dwc_spinlock_t *lock;
+};
+
+static void timer_callback(struct timer_list *tt)
+{
+	dwc_timer_t *timer = from_timer(timer, tt, t);
+	dwc_irqflags_t flags;
+
+	DWC_SPINLOCK_IRQSAVE(timer->lock, &flags);
+	timer->scheduled = 0;
+	DWC_SPINUNLOCK_IRQRESTORE(timer->lock, flags);
+	DWC_DEBUGC("Timer %s callback", timer->name);
+	timer->cb(timer->data);
+}
+
+dwc_timer_t *DWC_TIMER_ALLOC(char *name, dwc_timer_callback_t cb, void *data)
+{
+	dwc_timer_t *t = DWC_ALLOC(sizeof(*t));
+
+	if (!t) {
+		DWC_ERROR("Cannot allocate memory for timer");
+		return NULL;
+	}
+
+	t->name = DWC_STRDUP(name);
+	if (!t->name) {
+		DWC_ERROR("Cannot allocate memory for timer->name");
+		goto no_name;
+	}
+
+#if (defined(DWC_LINUX) && defined(CONFIG_DEBUG_SPINLOCK))
+	DWC_SPINLOCK_ALLOC_LINUX_DEBUG(t->lock);
+#else
+	t->lock = DWC_SPINLOCK_ALLOC();
+#endif
+	if (!t->lock) {
+		DWC_ERROR("Cannot allocate memory for lock");
+		goto no_lock;
+	}
+
+	t->scheduled = 0;
+	t->t.expires = jiffies;
+	timer_setup(&t->t, timer_callback, 0);
+
+	t->cb = cb;
+	t->data = data;
+
+	return t;
+
+ no_lock:
+	DWC_FREE(t->name);
+ no_name:
+	DWC_FREE(t);
+	return NULL;
+}
+
+void DWC_TIMER_FREE(dwc_timer_t *timer)
+{
+	dwc_irqflags_t flags;
+
+	DWC_SPINLOCK_IRQSAVE(timer->lock, &flags);
+
+	if (timer->scheduled) {
+		del_timer(&timer->t);
+		timer->scheduled = 0;
+	}
+
+	DWC_SPINUNLOCK_IRQRESTORE(timer->lock, flags);
+	DWC_SPINLOCK_FREE(timer->lock);
+	DWC_FREE(timer->name);
+	DWC_FREE(timer);
+}
+
+void DWC_TIMER_SCHEDULE(dwc_timer_t *timer, uint32_t time)
+{
+	dwc_irqflags_t flags;
+
+	DWC_SPINLOCK_IRQSAVE(timer->lock, &flags);
+
+	if (!timer->scheduled) {
+		timer->scheduled = 1;
+		DWC_DEBUGC("Scheduling timer %s to expire in +%d msec", timer->name, time);
+		timer->t.expires = jiffies + msecs_to_jiffies(time);
+		add_timer(&timer->t);
+	} else {
+		DWC_DEBUGC("Modifying timer %s to expire in +%d msec", timer->name, time);
+		mod_timer(&timer->t, jiffies + msecs_to_jiffies(time));
+	}
+
+	DWC_SPINUNLOCK_IRQRESTORE(timer->lock, flags);
+}
+
+void DWC_TIMER_CANCEL(dwc_timer_t *timer)
+{
+	del_timer(&timer->t);
+}
+
+
+/* Wait Queues */
+
+struct dwc_waitq {
+	wait_queue_head_t queue;
+	int abort;
+};
+
+dwc_waitq_t *DWC_WAITQ_ALLOC(void)
+{
+	dwc_waitq_t *wq = DWC_ALLOC(sizeof(*wq));
+
+	if (!wq) {
+		DWC_ERROR("Cannot allocate memory for waitqueue\n");
+		return NULL;
+	}
+
+	init_waitqueue_head(&wq->queue);
+	wq->abort = 0;
+	return wq;
+}
+
+void DWC_WAITQ_FREE(dwc_waitq_t *wq)
+{
+	DWC_FREE(wq);
+}
+
+int32_t DWC_WAITQ_WAIT(dwc_waitq_t *wq, dwc_waitq_condition_t cond, void *data)
+{
+	int result = wait_event_interruptible(wq->queue,
+					      cond(data) || wq->abort);
+	if (result == -ERESTARTSYS) {
+		wq->abort = 0;
+		return -DWC_E_RESTART;
+	}
+
+	if (wq->abort == 1) {
+		wq->abort = 0;
+		return -DWC_E_ABORT;
+	}
+
+	wq->abort = 0;
+
+	if (result == 0) {
+		return 0;
+	}
+
+	return -DWC_E_UNKNOWN;
+}
+
+int32_t DWC_WAITQ_WAIT_TIMEOUT(dwc_waitq_t *wq, dwc_waitq_condition_t cond,
+			       void *data, int32_t msecs)
+{
+	int32_t tmsecs;
+	int result = wait_event_interruptible_timeout(wq->queue,
+						      cond(data) || wq->abort,
+						      msecs_to_jiffies(msecs));
+	if (result == -ERESTARTSYS) {
+		wq->abort = 0;
+		return -DWC_E_RESTART;
+	}
+
+	if (wq->abort == 1) {
+		wq->abort = 0;
+		return -DWC_E_ABORT;
+	}
+
+	wq->abort = 0;
+
+	if (result > 0) {
+		tmsecs = jiffies_to_msecs(result);
+		if (!tmsecs) {
+			return 1;
+		}
+
+		return tmsecs;
+	}
+
+	if (result == 0) {
+		return -DWC_E_TIMEOUT;
+	}
+
+	return -DWC_E_UNKNOWN;
+}
+
+void DWC_WAITQ_TRIGGER(dwc_waitq_t *wq)
+{
+	wq->abort = 0;
+	wake_up_interruptible(&wq->queue);
+}
+
+void DWC_WAITQ_ABORT(dwc_waitq_t *wq)
+{
+	wq->abort = 1;
+	wake_up_interruptible(&wq->queue);
+}
+
+
+/* Threading */
+
+dwc_thread_t *DWC_THREAD_RUN(dwc_thread_function_t func, char *name, void *data)
+{
+	struct task_struct *thread = kthread_run(func, data, name);
+
+	if (thread == ERR_PTR(-ENOMEM)) {
+		return NULL;
+	}
+
+	return (dwc_thread_t *)thread;
+}
+
+int DWC_THREAD_STOP(dwc_thread_t *thread)
+{
+	return kthread_stop((struct task_struct *)thread);
+}
+
+dwc_bool_t DWC_THREAD_SHOULD_STOP(void)
+{
+	return kthread_should_stop();
+}
+
+
+/* tasklets
+ - run in interrupt context (cannot sleep)
+ - each tasklet runs on a single CPU
+ - different tasklets can be running simultaneously on different CPUs
+ */
+struct dwc_tasklet {
+	struct tasklet_struct t;
+	dwc_tasklet_callback_t cb;
+	void *data;
+};
+
+static void tasklet_callback(unsigned long data)
+{
+	dwc_tasklet_t *t = (dwc_tasklet_t *)data;
+	t->cb(t->data);
+}
+
+dwc_tasklet_t *DWC_TASK_ALLOC(char *name, dwc_tasklet_callback_t cb, void *data)
+{
+	dwc_tasklet_t *t = DWC_ALLOC(sizeof(*t));
+
+	if (t) {
+		t->cb = cb;
+		t->data = data;
+		tasklet_init(&t->t, tasklet_callback, (unsigned long)t);
+	} else {
+		DWC_ERROR("Cannot allocate memory for tasklet\n");
+	}
+
+	return t;
+}
+
+void DWC_TASK_FREE(dwc_tasklet_t *task)
+{
+	DWC_FREE(task);
+}
+
+void DWC_TASK_SCHEDULE(dwc_tasklet_t *task)
+{
+	tasklet_schedule(&task->t);
+}
+
+void DWC_TASK_HI_SCHEDULE(dwc_tasklet_t *task)
+{
+	tasklet_hi_schedule(&task->t);
+}
+
+
+/* workqueues
+ - run in process context (can sleep)
+ */
+typedef struct work_container {
+	dwc_work_callback_t cb;
+	void *data;
+	dwc_workq_t *wq;
+	char *name;
+
+#ifdef DEBUG
+	DWC_CIRCLEQ_ENTRY(work_container) entry;
+#endif
+	struct delayed_work work;
+} work_container_t;
+
+#ifdef DEBUG
+DWC_CIRCLEQ_HEAD(work_container_queue, work_container);
+#endif
+
+struct dwc_workq {
+	struct workqueue_struct *wq;
+	dwc_spinlock_t *lock;
+	dwc_waitq_t *waitq;
+	int pending;
+
+#ifdef DEBUG
+	struct work_container_queue entries;
+#endif
+};
+
+static void do_work(struct work_struct *work)
+{
+	dwc_irqflags_t flags;
+	struct delayed_work *dw = container_of(work, struct delayed_work, work);
+	work_container_t *container = container_of(dw, struct work_container, work);
+	dwc_workq_t *wq = container->wq;
+
+	container->cb(container->data);
+
+#ifdef DEBUG
+	DWC_CIRCLEQ_REMOVE(&wq->entries, container, entry);
+#endif
+	DWC_DEBUGC("Work done: %s, container=%p", container->name, container);
+	if (container->name) {
+		DWC_FREE(container->name);
+	}
+	DWC_FREE(container);
+
+	DWC_SPINLOCK_IRQSAVE(wq->lock, &flags);
+	wq->pending--;
+	DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags);
+	DWC_WAITQ_TRIGGER(wq->waitq);
+}
+
+static int work_done(void *data)
+{
+	dwc_workq_t *workq = (dwc_workq_t *)data;
+	return workq->pending == 0;
+}
+
+int DWC_WORKQ_WAIT_WORK_DONE(dwc_workq_t *workq, int timeout)
+{
+	return DWC_WAITQ_WAIT_TIMEOUT(workq->waitq, work_done, workq, timeout);
+}
+
+dwc_workq_t *DWC_WORKQ_ALLOC(char *name)
+{
+	dwc_workq_t *wq = DWC_ALLOC(sizeof(*wq));
+
+	if (!wq) {
+		return NULL;
+	}
+
+	wq->wq = create_singlethread_workqueue(name);
+	if (!wq->wq) {
+		goto no_wq;
+	}
+
+	wq->pending = 0;
+
+#if (defined(DWC_LINUX) && defined(CONFIG_DEBUG_SPINLOCK))
+	DWC_SPINLOCK_ALLOC_LINUX_DEBUG(wq->lock);
+#else
+	wq->lock = DWC_SPINLOCK_ALLOC();
+#endif
+	if (!wq->lock) {
+		goto no_lock;
+	}
+
+	wq->waitq = DWC_WAITQ_ALLOC();
+	if (!wq->waitq) {
+		goto no_waitq;
+	}
+
+#ifdef DEBUG
+	DWC_CIRCLEQ_INIT(&wq->entries);
+#endif
+	return wq;
+
+ no_waitq:
+	DWC_SPINLOCK_FREE(wq->lock);
+ no_lock:
+	destroy_workqueue(wq->wq);
+ no_wq:
+	DWC_FREE(wq);
+
+	return NULL;
+}
+
+void DWC_WORKQ_FREE(dwc_workq_t *wq)
+{
+#ifdef DEBUG
+	if (wq->pending != 0) {
+		struct work_container *wc;
+		DWC_ERROR("Destroying work queue with pending work");
+		DWC_CIRCLEQ_FOREACH(wc, &wq->entries, entry) {
+			DWC_ERROR("Work %s still pending", wc->name);
+		}
+	}
+#endif
+	destroy_workqueue(wq->wq);
+	DWC_SPINLOCK_FREE(wq->lock);
+	DWC_WAITQ_FREE(wq->waitq);
+	DWC_FREE(wq);
+}
+
+void DWC_WORKQ_SCHEDULE(dwc_workq_t *wq, dwc_work_callback_t cb, void *data,
+			char *format, ...)
+{
+	dwc_irqflags_t flags;
+	work_container_t *container;
+	static char name[128];
+	va_list args;
+
+	va_start(args, format);
+	DWC_VSNPRINTF(name, 128, format, args);
+	va_end(args);
+
+	DWC_SPINLOCK_IRQSAVE(wq->lock, &flags);
+	wq->pending++;
+	DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags);
+	DWC_WAITQ_TRIGGER(wq->waitq);
+
+	container = DWC_ALLOC_ATOMIC(sizeof(*container));
+	if (!container) {
+		DWC_ERROR("Cannot allocate memory for container\n");
+		return;
+	}
+
+	container->name = DWC_STRDUP(name);
+	if (!container->name) {
+		DWC_ERROR("Cannot allocate memory for container->name\n");
+		DWC_FREE(container);
+		return;
+	}
+
+	container->cb = cb;
+	container->data = data;
+	container->wq = wq;
+	DWC_DEBUGC("Queueing work: %s, container=%p", container->name, container);
+	INIT_WORK(&container->work.work, do_work);
+
+#ifdef DEBUG
+	DWC_CIRCLEQ_INSERT_TAIL(&wq->entries, container, entry);
+#endif
+	queue_work(wq->wq, &container->work.work);
+}
+
+void DWC_WORKQ_SCHEDULE_DELAYED(dwc_workq_t *wq, dwc_work_callback_t cb,
+				void *data, uint32_t time, char *format, ...)
+{
+	dwc_irqflags_t flags;
+	work_container_t *container;
+	static char name[128];
+	va_list args;
+
+	va_start(args, format);
+	DWC_VSNPRINTF(name, 128, format, args);
+	va_end(args);
+
+	DWC_SPINLOCK_IRQSAVE(wq->lock, &flags);
+	wq->pending++;
+	DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags);
+	DWC_WAITQ_TRIGGER(wq->waitq);
+
+	container = DWC_ALLOC_ATOMIC(sizeof(*container));
+	if (!container) {
+		DWC_ERROR("Cannot allocate memory for container\n");
+		return;
+	}
+
+	container->name = DWC_STRDUP(name);
+	if (!container->name) {
+		DWC_ERROR("Cannot allocate memory for container->name\n");
+		DWC_FREE(container);
+		return;
+	}
+
+	container->cb = cb;
+	container->data = data;
+	container->wq = wq;
+	DWC_DEBUGC("Queueing work: %s, container=%p", container->name, container);
+	INIT_DELAYED_WORK(&container->work, do_work);
+
+#ifdef DEBUG
+	DWC_CIRCLEQ_INSERT_TAIL(&wq->entries, container, entry);
+#endif
+	queue_delayed_work(wq->wq, &container->work, msecs_to_jiffies(time));
+}
+
+int DWC_WORKQ_PENDING(dwc_workq_t *wq)
+{
+	return wq->pending;
+}
+
+
+#ifdef DWC_LIBMODULE
+
+#ifdef DWC_CCLIB
+/* CC */
+EXPORT_SYMBOL(dwc_cc_if_alloc);
+EXPORT_SYMBOL(dwc_cc_if_free);
+EXPORT_SYMBOL(dwc_cc_clear);
+EXPORT_SYMBOL(dwc_cc_add);
+EXPORT_SYMBOL(dwc_cc_remove);
+EXPORT_SYMBOL(dwc_cc_change);
+EXPORT_SYMBOL(dwc_cc_data_for_save);
+EXPORT_SYMBOL(dwc_cc_restore_from_data);
+EXPORT_SYMBOL(dwc_cc_match_chid);
+EXPORT_SYMBOL(dwc_cc_match_cdid);
+EXPORT_SYMBOL(dwc_cc_ck);
+EXPORT_SYMBOL(dwc_cc_chid);
+EXPORT_SYMBOL(dwc_cc_cdid);
+EXPORT_SYMBOL(dwc_cc_name);
+#endif	/* DWC_CCLIB */
+
+#ifdef DWC_CRYPTOLIB
+# ifndef CONFIG_MACH_IPMATE
+/* Modpow */
+EXPORT_SYMBOL(dwc_modpow);
+
+/* DH */
+EXPORT_SYMBOL(dwc_dh_modpow);
+EXPORT_SYMBOL(dwc_dh_derive_keys);
+EXPORT_SYMBOL(dwc_dh_pk);
+# endif	/* CONFIG_MACH_IPMATE */
+
+/* Crypto */
+EXPORT_SYMBOL(dwc_wusb_aes_encrypt);
+EXPORT_SYMBOL(dwc_wusb_cmf);
+EXPORT_SYMBOL(dwc_wusb_prf);
+EXPORT_SYMBOL(dwc_wusb_fill_ccm_nonce);
+EXPORT_SYMBOL(dwc_wusb_gen_nonce);
+EXPORT_SYMBOL(dwc_wusb_gen_key);
+EXPORT_SYMBOL(dwc_wusb_gen_mic);
+#endif	/* DWC_CRYPTOLIB */
+
+/* Notification */
+#ifdef DWC_NOTIFYLIB
+EXPORT_SYMBOL(dwc_alloc_notification_manager);
+EXPORT_SYMBOL(dwc_free_notification_manager);
+EXPORT_SYMBOL(dwc_register_notifier);
+EXPORT_SYMBOL(dwc_unregister_notifier);
+EXPORT_SYMBOL(dwc_add_observer);
+EXPORT_SYMBOL(dwc_remove_observer);
+EXPORT_SYMBOL(dwc_notify);
+#endif
+
+/* Memory Debugging Routines */
+#ifdef DWC_DEBUG_MEMORY
+EXPORT_SYMBOL(dwc_alloc_debug);
+EXPORT_SYMBOL(dwc_alloc_atomic_debug);
+EXPORT_SYMBOL(dwc_free_debug);
+EXPORT_SYMBOL(dwc_dma_alloc_debug);
+EXPORT_SYMBOL(dwc_dma_free_debug);
+#endif
+
+EXPORT_SYMBOL(DWC_MEMSET);
+EXPORT_SYMBOL(DWC_MEMCPY);
+EXPORT_SYMBOL(DWC_MEMMOVE);
+EXPORT_SYMBOL(DWC_MEMCMP);
+EXPORT_SYMBOL(DWC_STRNCMP);
+EXPORT_SYMBOL(DWC_STRCMP);
+EXPORT_SYMBOL(DWC_STRLEN);
+EXPORT_SYMBOL(DWC_STRCPY);
+EXPORT_SYMBOL(DWC_STRDUP);
+EXPORT_SYMBOL(DWC_ATOI);
+EXPORT_SYMBOL(DWC_ATOUI);
+
+#ifdef DWC_UTFLIB
+EXPORT_SYMBOL(DWC_UTF8_TO_UTF16LE);
+#endif	/* DWC_UTFLIB */
+
+EXPORT_SYMBOL(DWC_IN_IRQ);
+EXPORT_SYMBOL(DWC_IN_BH);
+EXPORT_SYMBOL(DWC_VPRINTF);
+EXPORT_SYMBOL(DWC_VSNPRINTF);
+EXPORT_SYMBOL(DWC_PRINTF);
+EXPORT_SYMBOL(DWC_SPRINTF);
+EXPORT_SYMBOL(DWC_SNPRINTF);
+EXPORT_SYMBOL(__DWC_WARN);
+EXPORT_SYMBOL(__DWC_ERROR);
+EXPORT_SYMBOL(DWC_EXCEPTION);
+
+#ifdef DEBUG
+EXPORT_SYMBOL(__DWC_DEBUG);
+#endif
+
+EXPORT_SYMBOL(__DWC_DMA_ALLOC);
+EXPORT_SYMBOL(__DWC_DMA_ALLOC_ATOMIC);
+EXPORT_SYMBOL(__DWC_DMA_FREE);
+EXPORT_SYMBOL(__DWC_ALLOC);
+EXPORT_SYMBOL(__DWC_ALLOC_ATOMIC);
+EXPORT_SYMBOL(__DWC_FREE);
+
+#ifdef DWC_CRYPTOLIB
+EXPORT_SYMBOL(DWC_RANDOM_BYTES);
+EXPORT_SYMBOL(DWC_AES_CBC);
+EXPORT_SYMBOL(DWC_SHA256);
+EXPORT_SYMBOL(DWC_HMAC_SHA256);
+#endif
+
+EXPORT_SYMBOL(DWC_CPU_TO_LE32);
+EXPORT_SYMBOL(DWC_CPU_TO_BE32);
+EXPORT_SYMBOL(DWC_LE32_TO_CPU);
+EXPORT_SYMBOL(DWC_BE32_TO_CPU);
+EXPORT_SYMBOL(DWC_CPU_TO_LE16);
+EXPORT_SYMBOL(DWC_CPU_TO_BE16);
+EXPORT_SYMBOL(DWC_LE16_TO_CPU);
+EXPORT_SYMBOL(DWC_BE16_TO_CPU);
+EXPORT_SYMBOL(DWC_READ_REG32);
+EXPORT_SYMBOL(DWC_WRITE_REG32);
+EXPORT_SYMBOL(DWC_MODIFY_REG32);
+
+#if 0
+EXPORT_SYMBOL(DWC_READ_REG64);
+EXPORT_SYMBOL(DWC_WRITE_REG64);
+EXPORT_SYMBOL(DWC_MODIFY_REG64);
+#endif
+
+EXPORT_SYMBOL(DWC_SPINLOCK_ALLOC);
+EXPORT_SYMBOL(DWC_SPINLOCK_FREE);
+EXPORT_SYMBOL(DWC_SPINLOCK);
+EXPORT_SYMBOL(DWC_SPINUNLOCK);
+EXPORT_SYMBOL(DWC_SPINLOCK_IRQSAVE);
+EXPORT_SYMBOL(DWC_SPINUNLOCK_IRQRESTORE);
+EXPORT_SYMBOL(DWC_MUTEX_ALLOC);
+
+#if (!defined(DWC_LINUX) || !defined(CONFIG_DEBUG_MUTEXES))
+EXPORT_SYMBOL(DWC_MUTEX_FREE);
+#endif
+
+EXPORT_SYMBOL(DWC_MUTEX_LOCK);
+EXPORT_SYMBOL(DWC_MUTEX_TRYLOCK);
+EXPORT_SYMBOL(DWC_MUTEX_UNLOCK);
+EXPORT_SYMBOL(DWC_UDELAY);
+EXPORT_SYMBOL(DWC_MDELAY);
+EXPORT_SYMBOL(DWC_MSLEEP);
+EXPORT_SYMBOL(DWC_TIME);
+EXPORT_SYMBOL(DWC_TIMER_ALLOC);
+EXPORT_SYMBOL(DWC_TIMER_FREE);
+EXPORT_SYMBOL(DWC_TIMER_SCHEDULE);
+EXPORT_SYMBOL(DWC_TIMER_CANCEL);
+EXPORT_SYMBOL(DWC_WAITQ_ALLOC);
+EXPORT_SYMBOL(DWC_WAITQ_FREE);
+EXPORT_SYMBOL(DWC_WAITQ_WAIT);
+EXPORT_SYMBOL(DWC_WAITQ_WAIT_TIMEOUT);
+EXPORT_SYMBOL(DWC_WAITQ_TRIGGER);
+EXPORT_SYMBOL(DWC_WAITQ_ABORT);
+EXPORT_SYMBOL(DWC_THREAD_RUN);
+EXPORT_SYMBOL(DWC_THREAD_STOP);
+EXPORT_SYMBOL(DWC_THREAD_SHOULD_STOP);
+EXPORT_SYMBOL(DWC_TASK_ALLOC);
+EXPORT_SYMBOL(DWC_TASK_FREE);
+EXPORT_SYMBOL(DWC_TASK_SCHEDULE);
+EXPORT_SYMBOL(DWC_WORKQ_WAIT_WORK_DONE);
+EXPORT_SYMBOL(DWC_WORKQ_ALLOC);
+EXPORT_SYMBOL(DWC_WORKQ_FREE);
+EXPORT_SYMBOL(DWC_WORKQ_SCHEDULE);
+EXPORT_SYMBOL(DWC_WORKQ_SCHEDULE_DELAYED);
+EXPORT_SYMBOL(DWC_WORKQ_PENDING);
+
+static int dwc_common_port_init_module(void)
+{
+	int result = 0;
+
+	printk(KERN_DEBUG "Module dwc_common_port init\n" );
+
+#ifdef DWC_DEBUG_MEMORY
+	result = dwc_memory_debug_start(NULL);
+	if (result) {
+		printk(KERN_ERR
+		       "dwc_memory_debug_start() failed with error %d\n",
+		       result);
+		return result;
+	}
+#endif
+
+#ifdef DWC_NOTIFYLIB
+	result = dwc_alloc_notification_manager(NULL, NULL);
+	if (result) {
+		printk(KERN_ERR
+		       "dwc_alloc_notification_manager() failed with error %d\n",
+		       result);
+		return result;
+	}
+#endif
+	return result;
+}
+
+static void dwc_common_port_exit_module(void)
+{
+	printk(KERN_DEBUG "Module dwc_common_port exit\n" );
+
+#ifdef DWC_NOTIFYLIB
+	dwc_free_notification_manager();
+#endif
+
+#ifdef DWC_DEBUG_MEMORY
+	dwc_memory_debug_stop();
+#endif
+}
+
+module_init(dwc_common_port_init_module);
+module_exit(dwc_common_port_exit_module);
+
+MODULE_DESCRIPTION("DWC Common Library - Portable version");
+MODULE_AUTHOR("Synopsys Inc.");
+MODULE_LICENSE ("GPL");
+
+#endif	/* DWC_LIBMODULE */
diff --git a/drivers/usb/host/dwc_common_port/dwc_common_nbsd.c b/drivers/usb/host/dwc_common_port/dwc_common_nbsd.c
new file mode 100644
index 000000000000..49b07e172264
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/dwc_common_nbsd.c
@@ -0,0 +1,1275 @@
+#include "dwc_os.h"
+#include "dwc_list.h"
+
+#ifdef DWC_CCLIB
+# include "dwc_cc.h"
+#endif
+
+#ifdef DWC_CRYPTOLIB
+# include "dwc_modpow.h"
+# include "dwc_dh.h"
+# include "dwc_crypto.h"
+#endif
+
+#ifdef DWC_NOTIFYLIB
+# include "dwc_notifier.h"
+#endif
+
+/* OS-Level Implementations */
+
+/* This is the NetBSD 4.0.1 kernel implementation of the DWC platform library. */
+
+
+/* MISC */
+
+void *DWC_MEMSET(void *dest, uint8_t byte, uint32_t size)
+{
+	return memset(dest, byte, size);
+}
+
+void *DWC_MEMCPY(void *dest, void const *src, uint32_t size)
+{
+	return memcpy(dest, src, size);
+}
+
+void *DWC_MEMMOVE(void *dest, void *src, uint32_t size)
+{
+	bcopy(src, dest, size);
+	return dest;
+}
+
+int DWC_MEMCMP(void *m1, void *m2, uint32_t size)
+{
+	return memcmp(m1, m2, size);
+}
+
+int DWC_STRNCMP(void *s1, void *s2, uint32_t size)
+{
+	return strncmp(s1, s2, size);
+}
+
+int DWC_STRCMP(void *s1, void *s2)
+{
+	return strcmp(s1, s2);
+}
+
+int DWC_STRLEN(char const *str)
+{
+	return strlen(str);
+}
+
+char *DWC_STRCPY(char *to, char const *from)
+{
+	return strcpy(to, from);
+}
+
+char *DWC_STRDUP(char const *str)
+{
+	int len = DWC_STRLEN(str) + 1;
+	char *new = DWC_ALLOC_ATOMIC(len);
+
+	if (!new) {
+		return NULL;
+	}
+
+	DWC_MEMCPY(new, str, len);
+	return new;
+}
+
+int DWC_ATOI(char *str, int32_t *value)
+{
+	char *end = NULL;
+
+	/* NetBSD doesn't have 'strtol' in the kernel, but 'strtoul'
+	 * should be equivalent on 2's complement machines
+	 */
+	*value = strtoul(str, &end, 0);
+	if (*end == '\0') {
+		return 0;
+	}
+
+	return -1;
+}
+
+int DWC_ATOUI(char *str, uint32_t *value)
+{
+	char *end = NULL;
+
+	*value = strtoul(str, &end, 0);
+	if (*end == '\0') {
+		return 0;
+	}
+
+	return -1;
+}
+
+
+#ifdef DWC_UTFLIB
+/* From usbstring.c */
+
+int DWC_UTF8_TO_UTF16LE(uint8_t const *s, uint16_t *cp, unsigned len)
+{
+	int	count = 0;
+	u8	c;
+	u16	uchar;
+
+	/* this insists on correct encodings, though not minimal ones.
+	 * BUT it currently rejects legit 4-byte UTF-8 code points,
+	 * which need surrogate pairs.  (Unicode 3.1 can use them.)
+	 */
+	while (len != 0 && (c = (u8) *s++) != 0) {
+		if (unlikely(c & 0x80)) {
+			// 2-byte sequence:
+			// 00000yyyyyxxxxxx = 110yyyyy 10xxxxxx
+			if ((c & 0xe0) == 0xc0) {
+				uchar = (c & 0x1f) << 6;
+
+				c = (u8) *s++;
+				if ((c & 0xc0) != 0xc0)
+					goto fail;
+				c &= 0x3f;
+				uchar |= c;
+
+			// 3-byte sequence (most CJKV characters):
+			// zzzzyyyyyyxxxxxx = 1110zzzz 10yyyyyy 10xxxxxx
+			} else if ((c & 0xf0) == 0xe0) {
+				uchar = (c & 0x0f) << 12;
+
+				c = (u8) *s++;
+				if ((c & 0xc0) != 0xc0)
+					goto fail;
+				c &= 0x3f;
+				uchar |= c << 6;
+
+				c = (u8) *s++;
+				if ((c & 0xc0) != 0xc0)
+					goto fail;
+				c &= 0x3f;
+				uchar |= c;
+
+				/* no bogus surrogates */
+				if (0xd800 <= uchar && uchar <= 0xdfff)
+					goto fail;
+
+			// 4-byte sequence (surrogate pairs, currently rare):
+			// 11101110wwwwzzzzyy + 110111yyyyxxxxxx
+			//     = 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx
+			// (uuuuu = wwww + 1)
+			// FIXME accept the surrogate code points (only)
+			} else
+				goto fail;
+		} else
+			uchar = c;
+		put_unaligned (cpu_to_le16 (uchar), cp++);
+		count++;
+		len--;
+	}
+	return count;
+fail:
+	return -1;
+}
+
+#endif	/* DWC_UTFLIB */
+
+
+/* dwc_debug.h */
+
+dwc_bool_t DWC_IN_IRQ(void)
+{
+//	return in_irq();
+	return 0;
+}
+
+dwc_bool_t DWC_IN_BH(void)
+{
+//	return in_softirq();
+	return 0;
+}
+
+void DWC_VPRINTF(char *format, va_list args)
+{
+	vprintf(format, args);
+}
+
+int DWC_VSNPRINTF(char *str, int size, char *format, va_list args)
+{
+	return vsnprintf(str, size, format, args);
+}
+
+void DWC_PRINTF(char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	DWC_VPRINTF(format, args);
+	va_end(args);
+}
+
+int DWC_SPRINTF(char *buffer, char *format, ...)
+{
+	int retval;
+	va_list args;
+
+	va_start(args, format);
+	retval = vsprintf(buffer, format, args);
+	va_end(args);
+	return retval;
+}
+
+int DWC_SNPRINTF(char *buffer, int size, char *format, ...)
+{
+	int retval;
+	va_list args;
+
+	va_start(args, format);
+	retval = vsnprintf(buffer, size, format, args);
+	va_end(args);
+	return retval;
+}
+
+void __DWC_WARN(char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	DWC_VPRINTF(format, args);
+	va_end(args);
+}
+
+void __DWC_ERROR(char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	DWC_VPRINTF(format, args);
+	va_end(args);
+}
+
+void DWC_EXCEPTION(char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	DWC_VPRINTF(format, args);
+	va_end(args);
+//	BUG_ON(1);	???
+}
+
+#ifdef DEBUG
+void __DWC_DEBUG(char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	DWC_VPRINTF(format, args);
+	va_end(args);
+}
+#endif
+
+
+/* dwc_mem.h */
+
+#if 0
+dwc_pool_t *DWC_DMA_POOL_CREATE(uint32_t size,
+				uint32_t align,
+				uint32_t alloc)
+{
+	struct dma_pool *pool = dma_pool_create("Pool", NULL,
+						size, align, alloc);
+	return (dwc_pool_t *)pool;
+}
+
+void DWC_DMA_POOL_DESTROY(dwc_pool_t *pool)
+{
+	dma_pool_destroy((struct dma_pool *)pool);
+}
+
+void *DWC_DMA_POOL_ALLOC(dwc_pool_t *pool, uint64_t *dma_addr)
+{
+//	return dma_pool_alloc((struct dma_pool *)pool, GFP_KERNEL, dma_addr);
+	return dma_pool_alloc((struct dma_pool *)pool, M_WAITOK, dma_addr);
+}
+
+void *DWC_DMA_POOL_ZALLOC(dwc_pool_t *pool, uint64_t *dma_addr)
+{
+	void *vaddr = DWC_DMA_POOL_ALLOC(pool, dma_addr);
+	memset(..);
+}
+
+void DWC_DMA_POOL_FREE(dwc_pool_t *pool, void *vaddr, void *daddr)
+{
+	dma_pool_free(pool, vaddr, daddr);
+}
+#endif
+
+void *__DWC_DMA_ALLOC(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr)
+{
+	dwc_dmactx_t *dma = (dwc_dmactx_t *)dma_ctx;
+	int error;
+
+	error = bus_dmamem_alloc(dma->dma_tag, size, 1, size, dma->segs,
+				 sizeof(dma->segs) / sizeof(dma->segs[0]),
+				 &dma->nsegs, BUS_DMA_NOWAIT);
+	if (error) {
+		printf("%s: bus_dmamem_alloc(%ju) failed: %d\n", __func__,
+		       (uintmax_t)size, error);
+		goto fail_0;
+	}
+
+	error = bus_dmamem_map(dma->dma_tag, dma->segs, dma->nsegs, size,
+			       (caddr_t *)&dma->dma_vaddr,
+			       BUS_DMA_NOWAIT | BUS_DMA_COHERENT);
+	if (error) {
+		printf("%s: bus_dmamem_map failed: %d\n", __func__, error);
+		goto fail_1;
+	}
+
+	error = bus_dmamap_create(dma->dma_tag, size, 1, size, 0,
+				  BUS_DMA_NOWAIT, &dma->dma_map);
+	if (error) {
+		printf("%s: bus_dmamap_create failed: %d\n", __func__, error);
+		goto fail_2;
+	}
+
+	error = bus_dmamap_load(dma->dma_tag, dma->dma_map, dma->dma_vaddr,
+				size, NULL, BUS_DMA_NOWAIT);
+	if (error) {
+		printf("%s: bus_dmamap_load failed: %d\n", __func__, error);
+		goto fail_3;
+	}
+
+	dma->dma_paddr = (bus_addr_t)dma->segs[0].ds_addr;
+	*dma_addr = dma->dma_paddr;
+	return dma->dma_vaddr;
+
+fail_3:
+	bus_dmamap_destroy(dma->dma_tag, dma->dma_map);
+fail_2:
+	bus_dmamem_unmap(dma->dma_tag, dma->dma_vaddr, size);
+fail_1:
+	bus_dmamem_free(dma->dma_tag, dma->segs, dma->nsegs);
+fail_0:
+	dma->dma_map = NULL;
+	dma->dma_vaddr = NULL;
+	dma->nsegs = 0;
+
+	return NULL;
+}
+
+void __DWC_DMA_FREE(void *dma_ctx, uint32_t size, void *virt_addr, dwc_dma_t dma_addr)
+{
+	dwc_dmactx_t *dma = (dwc_dmactx_t *)dma_ctx;
+
+	if (dma->dma_map != NULL) {
+		bus_dmamap_sync(dma->dma_tag, dma->dma_map, 0, size,
+				BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
+		bus_dmamap_unload(dma->dma_tag, dma->dma_map);
+		bus_dmamap_destroy(dma->dma_tag, dma->dma_map);
+		bus_dmamem_unmap(dma->dma_tag, dma->dma_vaddr, size);
+		bus_dmamem_free(dma->dma_tag, dma->segs, dma->nsegs);
+		dma->dma_paddr = 0;
+		dma->dma_map = NULL;
+		dma->dma_vaddr = NULL;
+		dma->nsegs = 0;
+	}
+}
+
+void *__DWC_ALLOC(void *mem_ctx, uint32_t size)
+{
+	return malloc(size, M_DEVBUF, M_WAITOK | M_ZERO);
+}
+
+void *__DWC_ALLOC_ATOMIC(void *mem_ctx, uint32_t size)
+{
+	return malloc(size, M_DEVBUF, M_NOWAIT | M_ZERO);
+}
+
+void __DWC_FREE(void *mem_ctx, void *addr)
+{
+	free(addr, M_DEVBUF);
+}
+
+
+#ifdef DWC_CRYPTOLIB
+/* dwc_crypto.h */
+
+void DWC_RANDOM_BYTES(uint8_t *buffer, uint32_t length)
+{
+	get_random_bytes(buffer, length);
+}
+
+int DWC_AES_CBC(uint8_t *message, uint32_t messagelen, uint8_t *key, uint32_t keylen, uint8_t iv[16], uint8_t *out)
+{
+	struct crypto_blkcipher *tfm;
+	struct blkcipher_desc desc;
+	struct scatterlist sgd;
+	struct scatterlist sgs;
+
+	tfm = crypto_alloc_blkcipher("cbc(aes)", 0, CRYPTO_ALG_ASYNC);
+	if (tfm == NULL) {
+		printk("failed to load transform for aes CBC\n");
+		return -1;
+	}
+
+	crypto_blkcipher_setkey(tfm, key, keylen);
+	crypto_blkcipher_set_iv(tfm, iv, 16);
+
+	sg_init_one(&sgd, out, messagelen);
+	sg_init_one(&sgs, message, messagelen);
+
+	desc.tfm = tfm;
+	desc.flags = 0;
+
+	if (crypto_blkcipher_encrypt(&desc, &sgd, &sgs, messagelen)) {
+		crypto_free_blkcipher(tfm);
+		DWC_ERROR("AES CBC encryption failed");
+		return -1;
+	}
+
+	crypto_free_blkcipher(tfm);
+	return 0;
+}
+
+int DWC_SHA256(uint8_t *message, uint32_t len, uint8_t *out)
+{
+	struct crypto_hash *tfm;
+	struct hash_desc desc;
+	struct scatterlist sg;
+
+	tfm = crypto_alloc_hash("sha256", 0, CRYPTO_ALG_ASYNC);
+	if (IS_ERR(tfm)) {
+		DWC_ERROR("Failed to load transform for sha256: %ld", PTR_ERR(tfm));
+		return 0;
+	}
+	desc.tfm = tfm;
+	desc.flags = 0;
+
+	sg_init_one(&sg, message, len);
+	crypto_hash_digest(&desc, &sg, len, out);
+	crypto_free_hash(tfm);
+
+	return 1;
+}
+
+int DWC_HMAC_SHA256(uint8_t *message, uint32_t messagelen,
+		    uint8_t *key, uint32_t keylen, uint8_t *out)
+{
+	struct crypto_hash *tfm;
+	struct hash_desc desc;
+	struct scatterlist sg;
+
+	tfm = crypto_alloc_hash("hmac(sha256)", 0, CRYPTO_ALG_ASYNC);
+	if (IS_ERR(tfm)) {
+		DWC_ERROR("Failed to load transform for hmac(sha256): %ld", PTR_ERR(tfm));
+		return 0;
+	}
+	desc.tfm = tfm;
+	desc.flags = 0;
+
+	sg_init_one(&sg, message, messagelen);
+	crypto_hash_setkey(tfm, key, keylen);
+	crypto_hash_digest(&desc, &sg, messagelen, out);
+	crypto_free_hash(tfm);
+
+	return 1;
+}
+
+#endif	/* DWC_CRYPTOLIB */
+
+
+/* Byte Ordering Conversions */
+
+uint32_t DWC_CPU_TO_LE32(uint32_t *p)
+{
+#ifdef __LITTLE_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+
+	return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24));
+#endif
+}
+
+uint32_t DWC_CPU_TO_BE32(uint32_t *p)
+{
+#ifdef __BIG_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+
+	return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24));
+#endif
+}
+
+uint32_t DWC_LE32_TO_CPU(uint32_t *p)
+{
+#ifdef __LITTLE_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+
+	return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24));
+#endif
+}
+
+uint32_t DWC_BE32_TO_CPU(uint32_t *p)
+{
+#ifdef __BIG_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+
+	return (u_p[3] | (u_p[2] << 8) | (u_p[1] << 16) | (u_p[0] << 24));
+#endif
+}
+
+uint16_t DWC_CPU_TO_LE16(uint16_t *p)
+{
+#ifdef __LITTLE_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+	return (u_p[1] | (u_p[0] << 8));
+#endif
+}
+
+uint16_t DWC_CPU_TO_BE16(uint16_t *p)
+{
+#ifdef __BIG_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+	return (u_p[1] | (u_p[0] << 8));
+#endif
+}
+
+uint16_t DWC_LE16_TO_CPU(uint16_t *p)
+{
+#ifdef __LITTLE_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+	return (u_p[1] | (u_p[0] << 8));
+#endif
+}
+
+uint16_t DWC_BE16_TO_CPU(uint16_t *p)
+{
+#ifdef __BIG_ENDIAN
+	return *p;
+#else
+	uint8_t *u_p = (uint8_t *)p;
+	return (u_p[1] | (u_p[0] << 8));
+#endif
+}
+
+
+/* Registers */
+
+uint32_t DWC_READ_REG32(void *io_ctx, uint32_t volatile *reg)
+{
+	dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx;
+	bus_size_t ior = (bus_size_t)reg;
+
+	return bus_space_read_4(io->iot, io->ioh, ior);
+}
+
+#if 0
+uint64_t DWC_READ_REG64(void *io_ctx, uint64_t volatile *reg)
+{
+	dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx;
+	bus_size_t ior = (bus_size_t)reg;
+
+	return bus_space_read_8(io->iot, io->ioh, ior);
+}
+#endif
+
+void DWC_WRITE_REG32(void *io_ctx, uint32_t volatile *reg, uint32_t value)
+{
+	dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx;
+	bus_size_t ior = (bus_size_t)reg;
+
+	bus_space_write_4(io->iot, io->ioh, ior, value);
+}
+
+#if 0
+void DWC_WRITE_REG64(void *io_ctx, uint64_t volatile *reg, uint64_t value)
+{
+	dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx;
+	bus_size_t ior = (bus_size_t)reg;
+
+	bus_space_write_8(io->iot, io->ioh, ior, value);
+}
+#endif
+
+void DWC_MODIFY_REG32(void *io_ctx, uint32_t volatile *reg, uint32_t clear_mask,
+		      uint32_t set_mask)
+{
+	dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx;
+	bus_size_t ior = (bus_size_t)reg;
+
+	bus_space_write_4(io->iot, io->ioh, ior,
+			  (bus_space_read_4(io->iot, io->ioh, ior) &
+			   ~clear_mask) | set_mask);
+}
+
+#if 0
+void DWC_MODIFY_REG64(void *io_ctx, uint64_t volatile *reg, uint64_t clear_mask,
+		      uint64_t set_mask)
+{
+	dwc_ioctx_t *io = (dwc_ioctx_t *)io_ctx;
+	bus_size_t ior = (bus_size_t)reg;
+
+	bus_space_write_8(io->iot, io->ioh, ior,
+			  (bus_space_read_8(io->iot, io->ioh, ior) &
+			   ~clear_mask) | set_mask);
+}
+#endif
+
+
+/* Locking */
+
+dwc_spinlock_t *DWC_SPINLOCK_ALLOC(void)
+{
+	struct simplelock *sl = DWC_ALLOC(sizeof(*sl));
+
+	if (!sl) {
+		DWC_ERROR("Cannot allocate memory for spinlock");
+		return NULL;
+	}
+
+	simple_lock_init(sl);
+	return (dwc_spinlock_t *)sl;
+}
+
+void DWC_SPINLOCK_FREE(dwc_spinlock_t *lock)
+{
+	struct simplelock *sl = (struct simplelock *)lock;
+
+	DWC_FREE(sl);
+}
+
+void DWC_SPINLOCK(dwc_spinlock_t *lock)
+{
+	simple_lock((struct simplelock *)lock);
+}
+
+void DWC_SPINUNLOCK(dwc_spinlock_t *lock)
+{
+	simple_unlock((struct simplelock *)lock);
+}
+
+void DWC_SPINLOCK_IRQSAVE(dwc_spinlock_t *lock, dwc_irqflags_t *flags)
+{
+	simple_lock((struct simplelock *)lock);
+	*flags = splbio();
+}
+
+void DWC_SPINUNLOCK_IRQRESTORE(dwc_spinlock_t *lock, dwc_irqflags_t flags)
+{
+	splx(flags);
+	simple_unlock((struct simplelock *)lock);
+}
+
+dwc_mutex_t *DWC_MUTEX_ALLOC(void)
+{
+	dwc_mutex_t *mutex = DWC_ALLOC(sizeof(struct lock));
+
+	if (!mutex) {
+		DWC_ERROR("Cannot allocate memory for mutex");
+		return NULL;
+	}
+
+	lockinit((struct lock *)mutex, 0, "dw3mtx", 0, 0);
+	return mutex;
+}
+
+#if (defined(DWC_LINUX) && defined(CONFIG_DEBUG_MUTEXES))
+#else
+void DWC_MUTEX_FREE(dwc_mutex_t *mutex)
+{
+	DWC_FREE(mutex);
+}
+#endif
+
+void DWC_MUTEX_LOCK(dwc_mutex_t *mutex)
+{
+	lockmgr((struct lock *)mutex, LK_EXCLUSIVE, NULL);
+}
+
+int DWC_MUTEX_TRYLOCK(dwc_mutex_t *mutex)
+{
+	int status;
+
+	status = lockmgr((struct lock *)mutex, LK_EXCLUSIVE | LK_NOWAIT, NULL);
+	return status == 0;
+}
+
+void DWC_MUTEX_UNLOCK(dwc_mutex_t *mutex)
+{
+	lockmgr((struct lock *)mutex, LK_RELEASE, NULL);
+}
+
+
+/* Timing */
+
+void DWC_UDELAY(uint32_t usecs)
+{
+	DELAY(usecs);
+}
+
+void DWC_MDELAY(uint32_t msecs)
+{
+	do {
+		DELAY(1000);
+	} while (--msecs);
+}
+
+void DWC_MSLEEP(uint32_t msecs)
+{
+	struct timeval tv;
+
+	tv.tv_sec = msecs / 1000;
+	tv.tv_usec = (msecs - tv.tv_sec * 1000) * 1000;
+	tsleep(&tv, 0, "dw3slp", tvtohz(&tv));
+}
+
+uint32_t DWC_TIME(void)
+{
+	struct timeval tv;
+
+	microuptime(&tv);	// or getmicrouptime? (less precise, but faster)
+	return tv.tv_sec * 1000 + tv.tv_usec / 1000;
+}
+
+
+/* Timers */
+
+struct dwc_timer {
+	struct callout t;
+	char *name;
+	dwc_spinlock_t *lock;
+	dwc_timer_callback_t cb;
+	void *data;
+};
+
+dwc_timer_t *DWC_TIMER_ALLOC(char *name, dwc_timer_callback_t cb, void *data)
+{
+	dwc_timer_t *t = DWC_ALLOC(sizeof(*t));
+
+	if (!t) {
+		DWC_ERROR("Cannot allocate memory for timer");
+		return NULL;
+	}
+
+	callout_init(&t->t);
+
+	t->name = DWC_STRDUP(name);
+	if (!t->name) {
+		DWC_ERROR("Cannot allocate memory for timer->name");
+		goto no_name;
+	}
+
+	t->lock = DWC_SPINLOCK_ALLOC();
+	if (!t->lock) {
+		DWC_ERROR("Cannot allocate memory for timer->lock");
+		goto no_lock;
+	}
+
+	t->cb = cb;
+	t->data = data;
+
+	return t;
+
+ no_lock:
+	DWC_FREE(t->name);
+ no_name:
+	DWC_FREE(t);
+
+	return NULL;
+}
+
+void DWC_TIMER_FREE(dwc_timer_t *timer)
+{
+	callout_stop(&timer->t);
+	DWC_SPINLOCK_FREE(timer->lock);
+	DWC_FREE(timer->name);
+	DWC_FREE(timer);
+}
+
+void DWC_TIMER_SCHEDULE(dwc_timer_t *timer, uint32_t time)
+{
+	struct timeval tv;
+
+	tv.tv_sec = time / 1000;
+	tv.tv_usec = (time - tv.tv_sec * 1000) * 1000;
+	callout_reset(&timer->t, tvtohz(&tv), timer->cb, timer->data);
+}
+
+void DWC_TIMER_CANCEL(dwc_timer_t *timer)
+{
+	callout_stop(&timer->t);
+}
+
+
+/* Wait Queues */
+
+struct dwc_waitq {
+	struct simplelock lock;
+	int abort;
+};
+
+dwc_waitq_t *DWC_WAITQ_ALLOC(void)
+{
+	dwc_waitq_t *wq = DWC_ALLOC(sizeof(*wq));
+
+	if (!wq) {
+		DWC_ERROR("Cannot allocate memory for waitqueue");
+		return NULL;
+	}
+
+	simple_lock_init(&wq->lock);
+	wq->abort = 0;
+
+	return wq;
+}
+
+void DWC_WAITQ_FREE(dwc_waitq_t *wq)
+{
+	DWC_FREE(wq);
+}
+
+int32_t DWC_WAITQ_WAIT(dwc_waitq_t *wq, dwc_waitq_condition_t cond, void *data)
+{
+	int ipl;
+	int result = 0;
+
+	simple_lock(&wq->lock);
+	ipl = splbio();
+
+	/* Skip the sleep if already aborted or triggered */
+	if (!wq->abort && !cond(data)) {
+		splx(ipl);
+		result = ltsleep(wq, PCATCH, "dw3wat", 0, &wq->lock); // infinite timeout
+		ipl = splbio();
+	}
+
+	if (result == 0) {			// awoken
+		if (wq->abort) {
+			wq->abort = 0;
+			result = -DWC_E_ABORT;
+		} else {
+			result = 0;
+		}
+
+		splx(ipl);
+		simple_unlock(&wq->lock);
+	} else {
+		wq->abort = 0;
+		splx(ipl);
+		simple_unlock(&wq->lock);
+
+		if (result == ERESTART) {	// signaled - restart
+			result = -DWC_E_RESTART;
+		} else {			// signaled - must be EINTR
+			result = -DWC_E_ABORT;
+		}
+	}
+
+	return result;
+}
+
+int32_t DWC_WAITQ_WAIT_TIMEOUT(dwc_waitq_t *wq, dwc_waitq_condition_t cond,
+			       void *data, int32_t msecs)
+{
+	struct timeval tv, tv1, tv2;
+	int ipl;
+	int result = 0;
+
+	tv.tv_sec = msecs / 1000;
+	tv.tv_usec = (msecs - tv.tv_sec * 1000) * 1000;
+
+	simple_lock(&wq->lock);
+	ipl = splbio();
+
+	/* Skip the sleep if already aborted or triggered */
+	if (!wq->abort && !cond(data)) {
+		splx(ipl);
+		getmicrouptime(&tv1);
+		result = ltsleep(wq, PCATCH, "dw3wto", tvtohz(&tv), &wq->lock);
+		getmicrouptime(&tv2);
+		ipl = splbio();
+	}
+
+	if (result == 0) {			// awoken
+		if (wq->abort) {
+			wq->abort = 0;
+			splx(ipl);
+			simple_unlock(&wq->lock);
+			result = -DWC_E_ABORT;
+		} else {
+			splx(ipl);
+			simple_unlock(&wq->lock);
+
+			tv2.tv_usec -= tv1.tv_usec;
+			if (tv2.tv_usec < 0) {
+				tv2.tv_usec += 1000000;
+				tv2.tv_sec--;
+			}
+
+			tv2.tv_sec -= tv1.tv_sec;
+			result = tv2.tv_sec * 1000 + tv2.tv_usec / 1000;
+			result = msecs - result;
+			if (result <= 0)
+				result = 1;
+		}
+	} else {
+		wq->abort = 0;
+		splx(ipl);
+		simple_unlock(&wq->lock);
+
+		if (result == ERESTART) {	// signaled - restart
+			result = -DWC_E_RESTART;
+
+		} else if (result == EINTR) {		// signaled - interrupt
+			result = -DWC_E_ABORT;
+
+		} else {				// timed out
+			result = -DWC_E_TIMEOUT;
+		}
+	}
+
+	return result;
+}
+
+void DWC_WAITQ_TRIGGER(dwc_waitq_t *wq)
+{
+	wakeup(wq);
+}
+
+void DWC_WAITQ_ABORT(dwc_waitq_t *wq)
+{
+	int ipl;
+
+	simple_lock(&wq->lock);
+	ipl = splbio();
+	wq->abort = 1;
+	wakeup(wq);
+	splx(ipl);
+	simple_unlock(&wq->lock);
+}
+
+
+/* Threading */
+
+struct dwc_thread {
+	struct proc *proc;
+	int abort;
+};
+
+dwc_thread_t *DWC_THREAD_RUN(dwc_thread_function_t func, char *name, void *data)
+{
+	int retval;
+	dwc_thread_t *thread = DWC_ALLOC(sizeof(*thread));
+
+	if (!thread) {
+		return NULL;
+	}
+
+	thread->abort = 0;
+	retval = kthread_create1((void (*)(void *))func, data, &thread->proc,
+				 "%s", name);
+	if (retval) {
+		DWC_FREE(thread);
+		return NULL;
+	}
+
+	return thread;
+}
+
+int DWC_THREAD_STOP(dwc_thread_t *thread)
+{
+	int retval;
+
+	thread->abort = 1;
+	retval = tsleep(&thread->abort, 0, "dw3stp", 60 * hz);
+
+	if (retval == 0) {
+		/* DWC_THREAD_EXIT() will free the thread struct */
+		return 0;
+	}
+
+	/* NOTE: We leak the thread struct if thread doesn't die */
+
+	if (retval == EWOULDBLOCK) {
+		return -DWC_E_TIMEOUT;
+	}
+
+	return -DWC_E_UNKNOWN;
+}
+
+dwc_bool_t DWC_THREAD_SHOULD_STOP(dwc_thread_t *thread)
+{
+	return thread->abort;
+}
+
+void DWC_THREAD_EXIT(dwc_thread_t *thread)
+{
+	wakeup(&thread->abort);
+	DWC_FREE(thread);
+	kthread_exit(0);
+}
+
+/* tasklets
+ - Runs in interrupt context (cannot sleep)
+ - Each tasklet runs on a single CPU
+ - Different tasklets can be running simultaneously on different CPUs
+ [ On NetBSD there is no corresponding mechanism, drivers don't have bottom-
+   halves. So we just call the callback directly from DWC_TASK_SCHEDULE() ]
+ */
+struct dwc_tasklet {
+	dwc_tasklet_callback_t cb;
+	void *data;
+};
+
+static void tasklet_callback(void *data)
+{
+	dwc_tasklet_t *task = (dwc_tasklet_t *)data;
+
+	task->cb(task->data);
+}
+
+dwc_tasklet_t *DWC_TASK_ALLOC(char *name, dwc_tasklet_callback_t cb, void *data)
+{
+	dwc_tasklet_t *task = DWC_ALLOC(sizeof(*task));
+
+	if (task) {
+		task->cb = cb;
+		task->data = data;
+	} else {
+		DWC_ERROR("Cannot allocate memory for tasklet");
+	}
+
+	return task;
+}
+
+void DWC_TASK_FREE(dwc_tasklet_t *task)
+{
+	DWC_FREE(task);
+}
+
+void DWC_TASK_SCHEDULE(dwc_tasklet_t *task)
+{
+	tasklet_callback(task);
+}
+
+
+/* workqueues
+ - Runs in process context (can sleep)
+ */
+typedef struct work_container {
+	dwc_work_callback_t cb;
+	void *data;
+	dwc_workq_t *wq;
+	char *name;
+	int hz;
+	struct work task;
+} work_container_t;
+
+struct dwc_workq {
+	struct workqueue *taskq;
+	dwc_spinlock_t *lock;
+	dwc_waitq_t *waitq;
+	int pending;
+	struct work_container *container;
+};
+
+static void do_work(struct work *task, void *data)
+{
+	dwc_workq_t *wq = (dwc_workq_t *)data;
+	work_container_t *container = wq->container;
+	dwc_irqflags_t flags;
+
+	if (container->hz) {
+		tsleep(container, 0, "dw3wrk", container->hz);
+	}
+
+	container->cb(container->data);
+	DWC_DEBUG("Work done: %s, container=%p", container->name, container);
+
+	DWC_SPINLOCK_IRQSAVE(wq->lock, &flags);
+	if (container->name)
+		DWC_FREE(container->name);
+	DWC_FREE(container);
+	wq->pending--;
+	DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags);
+	DWC_WAITQ_TRIGGER(wq->waitq);
+}
+
+static int work_done(void *data)
+{
+	dwc_workq_t *workq = (dwc_workq_t *)data;
+
+	return workq->pending == 0;
+}
+
+int DWC_WORKQ_WAIT_WORK_DONE(dwc_workq_t *workq, int timeout)
+{
+	return DWC_WAITQ_WAIT_TIMEOUT(workq->waitq, work_done, workq, timeout);
+}
+
+dwc_workq_t *DWC_WORKQ_ALLOC(char *name)
+{
+	int result;
+	dwc_workq_t *wq = DWC_ALLOC(sizeof(*wq));
+
+	if (!wq) {
+		DWC_ERROR("Cannot allocate memory for workqueue");
+		return NULL;
+	}
+
+	result = workqueue_create(&wq->taskq, name, do_work, wq, 0 /*PWAIT*/,
+				  IPL_BIO, 0);
+	if (result) {
+		DWC_ERROR("Cannot create workqueue");
+		goto no_taskq;
+	}
+
+	wq->pending = 0;
+
+	wq->lock = DWC_SPINLOCK_ALLOC();
+	if (!wq->lock) {
+		DWC_ERROR("Cannot allocate memory for spinlock");
+		goto no_lock;
+	}
+
+	wq->waitq = DWC_WAITQ_ALLOC();
+	if (!wq->waitq) {
+		DWC_ERROR("Cannot allocate memory for waitqueue");
+		goto no_waitq;
+	}
+
+	return wq;
+
+ no_waitq:
+	DWC_SPINLOCK_FREE(wq->lock);
+ no_lock:
+	workqueue_destroy(wq->taskq);
+ no_taskq:
+	DWC_FREE(wq);
+
+	return NULL;
+}
+
+void DWC_WORKQ_FREE(dwc_workq_t *wq)
+{
+#ifdef DEBUG
+	dwc_irqflags_t flags;
+
+	DWC_SPINLOCK_IRQSAVE(wq->lock, &flags);
+
+	if (wq->pending != 0) {
+		struct work_container *container = wq->container;
+
+		DWC_ERROR("Destroying work queue with pending work");
+
+		if (container && container->name) {
+			DWC_ERROR("Work %s still pending", container->name);
+		}
+	}
+
+	DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags);
+#endif
+	DWC_WAITQ_FREE(wq->waitq);
+	DWC_SPINLOCK_FREE(wq->lock);
+	workqueue_destroy(wq->taskq);
+	DWC_FREE(wq);
+}
+
+void DWC_WORKQ_SCHEDULE(dwc_workq_t *wq, dwc_work_callback_t cb, void *data,
+			char *format, ...)
+{
+	dwc_irqflags_t flags;
+	work_container_t *container;
+	static char name[128];
+	va_list args;
+
+	va_start(args, format);
+	DWC_VSNPRINTF(name, 128, format, args);
+	va_end(args);
+
+	DWC_SPINLOCK_IRQSAVE(wq->lock, &flags);
+	wq->pending++;
+	DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags);
+	DWC_WAITQ_TRIGGER(wq->waitq);
+
+	container = DWC_ALLOC_ATOMIC(sizeof(*container));
+	if (!container) {
+		DWC_ERROR("Cannot allocate memory for container");
+		return;
+	}
+
+	container->name = DWC_STRDUP(name);
+	if (!container->name) {
+		DWC_ERROR("Cannot allocate memory for container->name");
+		DWC_FREE(container);
+		return;
+	}
+
+	container->cb = cb;
+	container->data = data;
+	container->wq = wq;
+	container->hz = 0;
+	wq->container = container;
+
+	DWC_DEBUG("Queueing work: %s, container=%p", container->name, container);
+	workqueue_enqueue(wq->taskq, &container->task);
+}
+
+void DWC_WORKQ_SCHEDULE_DELAYED(dwc_workq_t *wq, dwc_work_callback_t cb,
+				void *data, uint32_t time, char *format, ...)
+{
+	dwc_irqflags_t flags;
+	work_container_t *container;
+	static char name[128];
+	struct timeval tv;
+	va_list args;
+
+	va_start(args, format);
+	DWC_VSNPRINTF(name, 128, format, args);
+	va_end(args);
+
+	DWC_SPINLOCK_IRQSAVE(wq->lock, &flags);
+	wq->pending++;
+	DWC_SPINUNLOCK_IRQRESTORE(wq->lock, flags);
+	DWC_WAITQ_TRIGGER(wq->waitq);
+
+	container = DWC_ALLOC_ATOMIC(sizeof(*container));
+	if (!container) {
+		DWC_ERROR("Cannot allocate memory for container");
+		return;
+	}
+
+	container->name = DWC_STRDUP(name);
+	if (!container->name) {
+		DWC_ERROR("Cannot allocate memory for container->name");
+		DWC_FREE(container);
+		return;
+	}
+
+	container->cb = cb;
+	container->data = data;
+	container->wq = wq;
+	tv.tv_sec = time / 1000;
+	tv.tv_usec = (time - tv.tv_sec * 1000) * 1000;
+	container->hz = tvtohz(&tv);
+	wq->container = container;
+
+	DWC_DEBUG("Queueing work: %s, container=%p", container->name, container);
+	workqueue_enqueue(wq->taskq, &container->task);
+}
+
+int DWC_WORKQ_PENDING(dwc_workq_t *wq)
+{
+	return wq->pending;
+}
diff --git a/drivers/usb/host/dwc_common_port/dwc_crypto.c b/drivers/usb/host/dwc_common_port/dwc_crypto.c
new file mode 100644
index 000000000000..3b0353296148
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/dwc_crypto.c
@@ -0,0 +1,308 @@
+/* =========================================================================
+ * $File: //dwh/usb_iip/dev/software/dwc_common_port_2/dwc_crypto.c $
+ * $Revision: #5 $
+ * $Date: 2010/09/28 $
+ * $Change: 1596182 $
+ *
+ * Synopsys Portability Library Software and documentation
+ * (hereinafter, "Software") is an Unsupported proprietary work of
+ * Synopsys, Inc. unless otherwise expressly agreed to in writing
+ * between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product
+ * under any End User Software License Agreement or Agreement for
+ * Licensed Product with Synopsys or any supplement thereto. You are
+ * permitted to use and redistribute this Software in source and binary
+ * forms, with or without modification, provided that redistributions
+ * of source code must retain this notice. You may not view, use,
+ * disclose, copy or distribute this file or any information contained
+ * herein except pursuant to this license grant from Synopsys. If you
+ * do not agree with this notice, including the disclaimer below, then
+ * you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS"
+ * BASIS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE HEREBY DISCLAIMED. IN NO EVENT SHALL
+ * SYNOPSYS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================= */
+
+/** @file
+ * This file contains the WUSB cryptographic routines.
+ */
+
+#ifdef DWC_CRYPTOLIB
+
+#include "dwc_crypto.h"
+#include "usb.h"
+
+#ifdef DEBUG
+static inline void dump_bytes(char *name, uint8_t *bytes, int len)
+{
+	int i;
+	DWC_PRINTF("%s: ", name);
+	for (i=0; i<len; i++) {
+		DWC_PRINTF("%02x ", bytes[i]);
+	}
+	DWC_PRINTF("\n");
+}
+#else
+#define dump_bytes(x...)
+#endif
+
+/* Display a block */
+void show_block(const u8 *blk, const char *prefix, const char *suffix, int a)
+{
+#ifdef DWC_DEBUG_CRYPTO
+	int i, blksize = 16;
+
+	DWC_DEBUG("%s", prefix);
+
+	if (suffix == NULL) {
+		suffix = "\n";
+		blksize = a;
+	}
+
+	for (i = 0; i < blksize; i++)
+		DWC_PRINT("%02x%s", *blk++, ((i & 3) == 3) ? "  " : " ");
+	DWC_PRINT(suffix);
+#endif
+}
+
+/**
+ * Encrypts an array of bytes using the AES encryption engine.
+ * If <code>dst</code> == <code>src</code>, then the bytes will be encrypted
+ * in-place.
+ *
+ * @return  0 on success, negative error code on error.
+ */
+int dwc_wusb_aes_encrypt(u8 *src, u8 *key, u8 *dst)
+{
+	u8 block_t[16];
+	DWC_MEMSET(block_t, 0, 16);
+
+	return DWC_AES_CBC(src, 16, key, 16, block_t, dst);
+}
+
+/**
+ * The CCM-MAC-FUNCTION described in section 6.5 of the WUSB spec.
+ * This function takes a data string and returns the encrypted CBC
+ * Counter-mode MIC.
+ *
+ * @param key     The 128-bit symmetric key.
+ * @param nonce   The CCM nonce.
+ * @param label   The unique 14-byte ASCII text label.
+ * @param bytes   The byte array to be encrypted.
+ * @param len     Length of the byte array.
+ * @param result  Byte array to receive the 8-byte encrypted MIC.
+ */
+void dwc_wusb_cmf(u8 *key, u8 *nonce,
+		  char *label, u8 *bytes, int len, u8 *result)
+{
+	u8 block_m[16];
+	u8 block_x[16];
+	u8 block_t[8];
+	int idx, blkNum;
+	u16 la = (u16)(len + 14);
+
+	/* Set the AES-128 key */
+	//dwc_aes_setkey(tfm, key, 16);
+
+	/* Fill block B0 from flags = 0x59, N, and l(m) = 0 */
+	block_m[0] = 0x59;
+	for (idx = 0; idx < 13; idx++)
+		block_m[idx + 1] = nonce[idx];
+	block_m[14] = 0;
+	block_m[15] = 0;
+
+	/* Produce the CBC IV */
+	dwc_wusb_aes_encrypt(block_m, key, block_x);
+	show_block(block_m, "CBC IV in: ", "\n", 0);
+	show_block(block_x, "CBC IV out:", "\n", 0);
+
+	/* Fill block B1 from l(a) = Blen + 14, and A */
+	block_x[0] ^= (u8)(la >> 8);
+	block_x[1] ^= (u8)la;
+	for (idx = 0; idx < 14; idx++)
+		block_x[idx + 2] ^= label[idx];
+	show_block(block_x, "After xor: ", "b1\n", 16);
+
+	dwc_wusb_aes_encrypt(block_x, key, block_x);
+	show_block(block_x, "After AES: ", "b1\n", 16);
+
+	idx = 0;
+	blkNum = 0;
+
+	/* Fill remaining blocks with B */
+	while (len-- > 0) {
+		block_x[idx] ^= *bytes++;
+		if (++idx >= 16) {
+			idx = 0;
+			show_block(block_x, "After xor: ", "\n", blkNum);
+			dwc_wusb_aes_encrypt(block_x, key, block_x);
+			show_block(block_x, "After AES: ", "\n", blkNum);
+			blkNum++;
+		}
+	}
+
+	/* Handle partial last block */
+	if (idx > 0) {
+		show_block(block_x, "After xor: ", "\n", blkNum);
+		dwc_wusb_aes_encrypt(block_x, key, block_x);
+		show_block(block_x, "After AES: ", "\n", blkNum);
+	}
+
+	/* Save the MIC tag */
+	DWC_MEMCPY(block_t, block_x, 8);
+	show_block(block_t, "MIC tag  : ", NULL, 8);
+
+	/* Fill block A0 from flags = 0x01, N, and counter = 0 */
+	block_m[0] = 0x01;
+	block_m[14] = 0;
+	block_m[15] = 0;
+
+	/* Encrypt the counter */
+	dwc_wusb_aes_encrypt(block_m, key, block_x);
+	show_block(block_x, "CTR[MIC] : ", NULL, 8);
+
+	/* XOR with MIC tag */
+	for (idx = 0; idx < 8; idx++) {
+		block_t[idx] ^= block_x[idx];
+	}
+
+	/* Return result to caller */
+	DWC_MEMCPY(result, block_t, 8);
+	show_block(result, "CCM-MIC  : ", NULL, 8);
+
+}
+
+/**
+ * The PRF function described in section 6.5 of the WUSB spec. This function
+ * concatenates MIC values returned from dwc_cmf() to create a value of
+ * the requested length.
+ *
+ * @param prf_len  Length of the PRF function in bits (64, 128, or 256).
+ * @param key, nonce, label, bytes, len  Same as for dwc_cmf().
+ * @param result   Byte array to receive the result.
+ */
+void dwc_wusb_prf(int prf_len, u8 *key,
+		  u8 *nonce, char *label, u8 *bytes, int len, u8 *result)
+{
+	int i;
+
+	nonce[0] = 0;
+	for (i = 0; i < prf_len >> 6; i++, nonce[0]++) {
+		dwc_wusb_cmf(key, nonce, label, bytes, len, result);
+		result += 8;
+	}
+}
+
+/**
+ * Fills in CCM Nonce per the WUSB spec.
+ *
+ * @param[in] haddr Host address.
+ * @param[in] daddr Device address.
+ * @param[in] tkid Session Key(PTK) identifier.
+ * @param[out] nonce Pointer to where the CCM Nonce output is to be written.
+ */
+void dwc_wusb_fill_ccm_nonce(uint16_t haddr, uint16_t daddr, uint8_t *tkid,
+			     uint8_t *nonce)
+{
+
+	DWC_DEBUG("%s %x %x\n", __func__, daddr, haddr);
+
+	DWC_MEMSET(&nonce[0], 0, 16);
+
+	DWC_MEMCPY(&nonce[6], tkid, 3);
+	nonce[9] = daddr & 0xFF;
+	nonce[10] = (daddr >> 8) & 0xFF;
+	nonce[11] = haddr & 0xFF;
+	nonce[12] = (haddr >> 8) & 0xFF;
+
+	dump_bytes("CCM nonce", nonce, 16);
+}
+
+/**
+ * Generates a 16-byte cryptographic-grade random number for the Host/Device
+ * Nonce.
+ */
+void dwc_wusb_gen_nonce(uint16_t addr, uint8_t *nonce)
+{
+	uint8_t inonce[16];
+	uint32_t temp[4];
+
+	/* Fill in the Nonce */
+	DWC_MEMSET(&inonce[0], 0, sizeof(inonce));
+	inonce[9] = addr & 0xFF;
+	inonce[10] = (addr >> 8) & 0xFF;
+	inonce[11] = inonce[9];
+	inonce[12] = inonce[10];
+
+	/* Collect "randomness samples" */
+	DWC_RANDOM_BYTES((uint8_t *)temp, 16);
+
+	dwc_wusb_prf_128((uint8_t *)temp, nonce,
+			 "Random Numbers", (uint8_t *)temp, sizeof(temp),
+			 nonce);
+}
+
+/**
+ * Generates the Session Key (PTK) and Key Confirmation Key (KCK) per the
+ * WUSB spec.
+ *
+ * @param[in] ccm_nonce Pointer to CCM Nonce.
+ * @param[in] mk Master Key to derive the session from
+ * @param[in] hnonce Pointer to Host Nonce.
+ * @param[in] dnonce Pointer to Device Nonce.
+ * @param[out] kck Pointer to where the KCK output is to be written.
+ * @param[out] ptk Pointer to where the PTK output is to be written.
+ */
+void dwc_wusb_gen_key(uint8_t *ccm_nonce, uint8_t *mk, uint8_t *hnonce,
+		      uint8_t *dnonce, uint8_t *kck, uint8_t *ptk)
+{
+	uint8_t idata[32];
+	uint8_t odata[32];
+
+	dump_bytes("ck", mk, 16);
+	dump_bytes("hnonce", hnonce, 16);
+	dump_bytes("dnonce", dnonce, 16);
+
+	/* The data is the HNonce and DNonce concatenated */
+	DWC_MEMCPY(&idata[0], hnonce, 16);
+	DWC_MEMCPY(&idata[16], dnonce, 16);
+
+	dwc_wusb_prf_256(mk, ccm_nonce, "Pair-wise keys", idata, 32, odata);
+
+	/* Low 16 bytes of the result is the KCK, high 16 is the PTK */
+	DWC_MEMCPY(kck, &odata[0], 16);
+	DWC_MEMCPY(ptk, &odata[16], 16);
+
+	dump_bytes("kck", kck, 16);
+	dump_bytes("ptk", ptk, 16);
+}
+
+/**
+ * Generates the Message Integrity Code over the Handshake data per the
+ * WUSB spec.
+ *
+ * @param ccm_nonce Pointer to CCM Nonce.
+ * @param kck   Pointer to Key Confirmation Key.
+ * @param data  Pointer to Handshake data to be checked.
+ * @param mic   Pointer to where the MIC output is to be written.
+ */
+void dwc_wusb_gen_mic(uint8_t *ccm_nonce, uint8_t *kck,
+		      uint8_t *data, uint8_t *mic)
+{
+
+	dwc_wusb_prf_64(kck, ccm_nonce, "out-of-bandMIC",
+			data, WUSB_HANDSHAKE_LEN_FOR_MIC, mic);
+}
+
+#endif	/* DWC_CRYPTOLIB */
diff --git a/drivers/usb/host/dwc_common_port/dwc_crypto.h b/drivers/usb/host/dwc_common_port/dwc_crypto.h
new file mode 100644
index 000000000000..26fcddcfe9ba
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/dwc_crypto.h
@@ -0,0 +1,111 @@
+/* =========================================================================
+ * $File: //dwh/usb_iip/dev/software/dwc_common_port_2/dwc_crypto.h $
+ * $Revision: #3 $
+ * $Date: 2010/09/28 $
+ * $Change: 1596182 $
+ *
+ * Synopsys Portability Library Software and documentation
+ * (hereinafter, "Software") is an Unsupported proprietary work of
+ * Synopsys, Inc. unless otherwise expressly agreed to in writing
+ * between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product
+ * under any End User Software License Agreement or Agreement for
+ * Licensed Product with Synopsys or any supplement thereto. You are
+ * permitted to use and redistribute this Software in source and binary
+ * forms, with or without modification, provided that redistributions
+ * of source code must retain this notice. You may not view, use,
+ * disclose, copy or distribute this file or any information contained
+ * herein except pursuant to this license grant from Synopsys. If you
+ * do not agree with this notice, including the disclaimer below, then
+ * you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS"
+ * BASIS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE HEREBY DISCLAIMED. IN NO EVENT SHALL
+ * SYNOPSYS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================= */
+
+#ifndef _DWC_CRYPTO_H_
+#define _DWC_CRYPTO_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @file
+ *
+ * This file contains declarations for the WUSB Cryptographic routines as
+ * defined in the WUSB spec.  They are only to be used internally by the DWC UWB
+ * modules.
+ */
+
+#include "dwc_os.h"
+
+int dwc_wusb_aes_encrypt(u8 *src, u8 *key, u8 *dst);
+
+void dwc_wusb_cmf(u8 *key, u8 *nonce,
+		  char *label, u8 *bytes, int len, u8 *result);
+void dwc_wusb_prf(int prf_len, u8 *key,
+		  u8 *nonce, char *label, u8 *bytes, int len, u8 *result);
+
+/**
+ * The PRF-64 function described in section 6.5 of the WUSB spec.
+ *
+ * @param key, nonce, label, bytes, len, result  Same as for dwc_prf().
+ */
+static inline void dwc_wusb_prf_64(u8 *key, u8 *nonce,
+				   char *label, u8 *bytes, int len, u8 *result)
+{
+	dwc_wusb_prf(64, key, nonce, label, bytes, len, result);
+}
+
+/**
+ * The PRF-128 function described in section 6.5 of the WUSB spec.
+ *
+ * @param key, nonce, label, bytes, len, result  Same as for dwc_prf().
+ */
+static inline void dwc_wusb_prf_128(u8 *key, u8 *nonce,
+				    char *label, u8 *bytes, int len, u8 *result)
+{
+	dwc_wusb_prf(128, key, nonce, label, bytes, len, result);
+}
+
+/**
+ * The PRF-256 function described in section 6.5 of the WUSB spec.
+ *
+ * @param key, nonce, label, bytes, len, result  Same as for dwc_prf().
+ */
+static inline void dwc_wusb_prf_256(u8 *key, u8 *nonce,
+				    char *label, u8 *bytes, int len, u8 *result)
+{
+	dwc_wusb_prf(256, key, nonce, label, bytes, len, result);
+}
+
+
+void dwc_wusb_fill_ccm_nonce(uint16_t haddr, uint16_t daddr, uint8_t *tkid,
+			       uint8_t *nonce);
+void dwc_wusb_gen_nonce(uint16_t addr,
+			  uint8_t *nonce);
+
+void dwc_wusb_gen_key(uint8_t *ccm_nonce, uint8_t *mk,
+			uint8_t *hnonce, uint8_t *dnonce,
+			uint8_t *kck, uint8_t *ptk);
+
+
+void dwc_wusb_gen_mic(uint8_t *ccm_nonce, uint8_t
+			*kck, uint8_t *data, uint8_t *mic);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _DWC_CRYPTO_H_ */
diff --git a/drivers/usb/host/dwc_common_port/dwc_dh.c b/drivers/usb/host/dwc_common_port/dwc_dh.c
new file mode 100644
index 000000000000..2b429a32aaf0
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/dwc_dh.c
@@ -0,0 +1,291 @@
+/* =========================================================================
+ * $File: //dwh/usb_iip/dev/software/dwc_common_port_2/dwc_dh.c $
+ * $Revision: #3 $
+ * $Date: 2010/09/28 $
+ * $Change: 1596182 $
+ *
+ * Synopsys Portability Library Software and documentation
+ * (hereinafter, "Software") is an Unsupported proprietary work of
+ * Synopsys, Inc. unless otherwise expressly agreed to in writing
+ * between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product
+ * under any End User Software License Agreement or Agreement for
+ * Licensed Product with Synopsys or any supplement thereto. You are
+ * permitted to use and redistribute this Software in source and binary
+ * forms, with or without modification, provided that redistributions
+ * of source code must retain this notice. You may not view, use,
+ * disclose, copy or distribute this file or any information contained
+ * herein except pursuant to this license grant from Synopsys. If you
+ * do not agree with this notice, including the disclaimer below, then
+ * you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS"
+ * BASIS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE HEREBY DISCLAIMED. IN NO EVENT SHALL
+ * SYNOPSYS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================= */
+#ifdef DWC_CRYPTOLIB
+
+#ifndef CONFIG_MACH_IPMATE
+
+#include "dwc_dh.h"
+#include "dwc_modpow.h"
+
+#ifdef DEBUG
+/* This function prints out a buffer in the format described in the Association
+ * Model specification. */
+static void dh_dump(char *str, void *_num, int len)
+{
+	uint8_t *num = _num;
+	int i;
+	DWC_PRINTF("%s\n", str);
+	for (i = 0; i < len; i ++) {
+		DWC_PRINTF("%02x", num[i]);
+		if (((i + 1) % 2) == 0) DWC_PRINTF(" ");
+		if (((i + 1) % 26) == 0) DWC_PRINTF("\n");
+	}
+
+	DWC_PRINTF("\n");
+}
+#else
+#define dh_dump(_x...) do {; } while(0)
+#endif
+
+/* Constant g value */
+static __u32 dh_g[] = {
+	0x02000000,
+};
+
+/* Constant p value */
+static __u32 dh_p[] = {
+	0xFFFFFFFF, 0xFFFFFFFF, 0xA2DA0FC9, 0x34C26821, 0x8B62C6C4, 0xD11CDC80, 0x084E0229, 0x74CC678A,
+	0xA6BE0B02, 0x229B133B, 0x79084A51, 0xDD04348E, 0xB31995EF, 0x1B433ACD, 0x6D0A2B30, 0x37145FF2,
+	0x6D35E14F, 0x45C2516D, 0x76B585E4, 0xC67E5E62, 0xE9424CF4, 0x6BED37A6, 0xB65CFF0B, 0xEDB706F4,
+	0xFB6B38EE, 0xA59F895A, 0x11249FAE, 0xE61F4B7C, 0x51662849, 0x3D5BE4EC, 0xB87C00C2, 0x05BF63A1,
+	0x3648DA98, 0x9AD3551C, 0xA83F1669, 0x5FCF24FD, 0x235D6583, 0x96ADA3DC, 0x56F3621C, 0xBB528520,
+	0x0729D59E, 0x6D969670, 0x4E350C67, 0x0498BC4A, 0x086C74F1, 0x7C2118CA, 0x465E9032, 0x3BCE362E,
+	0x2C779EE3, 0x03860E18, 0xA283279B, 0x8FA207EC, 0xF05DC5B5, 0xC9524C6F, 0xF6CB2BDE, 0x18175895,
+	0x7C499539, 0xE56A95EA, 0x1826D215, 0x1005FA98, 0x5A8E7215, 0x2DC4AA8A, 0x0D1733AD, 0x337A5004,
+	0xAB2155A8, 0x64BA1CDF, 0x0485FBEC, 0x0AEFDB58, 0x5771EA8A, 0x7D0C065D, 0x850F97B3, 0xC7E4E1A6,
+	0x8CAEF5AB, 0xD73309DB, 0xE0948C1E, 0x9D61254A, 0x26D2E3CE, 0x6BEED21A, 0x06FA2FF1, 0x64088AD9,
+	0x730276D8, 0x646AC83E, 0x182B1F52, 0x0C207B17, 0x5717E1BB, 0x6C5D617A, 0xC0880977, 0xE246D9BA,
+	0xA04FE208, 0x31ABE574, 0xFC5BDB43, 0x8E10FDE0, 0x20D1824B, 0xCAD23AA9, 0xFFFFFFFF, 0xFFFFFFFF,
+};
+
+static void dh_swap_bytes(void *_in, void *_out, uint32_t len)
+{
+	uint8_t *in = _in;
+	uint8_t *out = _out;
+	int i;
+	for (i=0; i<len; i++) {
+		out[i] = in[len-1-i];
+	}
+}
+
+/* Computes the modular exponentiation (num^exp % mod).  num, exp, and mod are
+ * big endian numbers of size len, in bytes.  Each len value must be a multiple
+ * of 4. */
+int dwc_dh_modpow(void *mem_ctx, void *num, uint32_t num_len,
+		  void *exp, uint32_t exp_len,
+		  void *mod, uint32_t mod_len,
+		  void *out)
+{
+	/* modpow() takes little endian numbers.  AM uses big-endian.  This
+	 * function swaps bytes of numbers before passing onto modpow. */
+
+	int retval = 0;
+	uint32_t *result;
+
+	uint32_t *bignum_num = dwc_alloc(mem_ctx, num_len + 4);
+	uint32_t *bignum_exp = dwc_alloc(mem_ctx, exp_len + 4);
+	uint32_t *bignum_mod = dwc_alloc(mem_ctx, mod_len + 4);
+
+	dh_swap_bytes(num, &bignum_num[1], num_len);
+	bignum_num[0] = num_len / 4;
+
+	dh_swap_bytes(exp, &bignum_exp[1], exp_len);
+	bignum_exp[0] = exp_len / 4;
+
+	dh_swap_bytes(mod, &bignum_mod[1], mod_len);
+	bignum_mod[0] = mod_len / 4;
+
+	result = dwc_modpow(mem_ctx, bignum_num, bignum_exp, bignum_mod);
+	if (!result) {
+		retval = -1;
+		goto dh_modpow_nomem;
+	}
+
+	dh_swap_bytes(&result[1], out, result[0] * 4);
+	dwc_free(mem_ctx, result);
+
+ dh_modpow_nomem:
+	dwc_free(mem_ctx, bignum_num);
+	dwc_free(mem_ctx, bignum_exp);
+	dwc_free(mem_ctx, bignum_mod);
+	return retval;
+}
+
+
+int dwc_dh_pk(void *mem_ctx, uint8_t nd, uint8_t *exp, uint8_t *pk, uint8_t *hash)
+{
+	int retval;
+	uint8_t m3[385];
+
+#ifndef DH_TEST_VECTORS
+	DWC_RANDOM_BYTES(exp, 32);
+#endif
+
+	/* Compute the pkd */
+	if ((retval = dwc_dh_modpow(mem_ctx, dh_g, 4,
+				    exp, 32,
+				    dh_p, 384, pk))) {
+		return retval;
+	}
+
+	m3[384] = nd;
+	DWC_MEMCPY(&m3[0], pk, 384);
+	DWC_SHA256(m3, 385, hash);
+
+	dh_dump("PK", pk, 384);
+	dh_dump("SHA-256(M3)", hash, 32);
+	return 0;
+}
+
+int dwc_dh_derive_keys(void *mem_ctx, uint8_t nd, uint8_t *pkh, uint8_t *pkd,
+		       uint8_t *exp, int is_host,
+		       char *dd, uint8_t *ck, uint8_t *kdk)
+{
+	int retval;
+	uint8_t mv[784];
+	uint8_t sha_result[32];
+	uint8_t dhkey[384];
+	uint8_t shared_secret[384];
+	char *message;
+	uint32_t vd;
+
+	uint8_t *pk;
+
+	if (is_host) {
+		pk = pkd;
+	}
+	else {
+		pk = pkh;
+	}
+
+	if ((retval = dwc_dh_modpow(mem_ctx, pk, 384,
+				    exp, 32,
+				    dh_p, 384, shared_secret))) {
+		return retval;
+	}
+	dh_dump("Shared Secret", shared_secret, 384);
+
+	DWC_SHA256(shared_secret, 384, dhkey);
+	dh_dump("DHKEY", dhkey, 384);
+
+	DWC_MEMCPY(&mv[0], pkd, 384);
+	DWC_MEMCPY(&mv[384], pkh, 384);
+	DWC_MEMCPY(&mv[768], "displayed digest", 16);
+	dh_dump("MV", mv, 784);
+
+	DWC_SHA256(mv, 784, sha_result);
+	dh_dump("SHA-256(MV)", sha_result, 32);
+	dh_dump("First 32-bits of SHA-256(MV)", sha_result, 4);
+
+	dh_swap_bytes(sha_result, &vd, 4);
+#ifdef DEBUG
+	DWC_PRINTF("Vd (decimal) = %d\n", vd);
+#endif
+
+	switch (nd) {
+	case 2:
+		vd = vd % 100;
+		DWC_SPRINTF(dd, "%02d", vd);
+		break;
+	case 3:
+		vd = vd % 1000;
+		DWC_SPRINTF(dd, "%03d", vd);
+		break;
+	case 4:
+		vd = vd % 10000;
+		DWC_SPRINTF(dd, "%04d", vd);
+		break;
+	}
+#ifdef DEBUG
+	DWC_PRINTF("Display Digits: %s\n", dd);
+#endif
+
+	message = "connection key";
+	DWC_HMAC_SHA256(message, DWC_STRLEN(message), dhkey, 32, sha_result);
+	dh_dump("HMAC(SHA-256, DHKey, connection key)", sha_result, 32);
+	DWC_MEMCPY(ck, sha_result, 16);
+
+	message = "key derivation key";
+	DWC_HMAC_SHA256(message, DWC_STRLEN(message), dhkey, 32, sha_result);
+	dh_dump("HMAC(SHA-256, DHKey, key derivation key)", sha_result, 32);
+	DWC_MEMCPY(kdk, sha_result, 32);
+
+	return 0;
+}
+
+
+#ifdef DH_TEST_VECTORS
+
+static __u8 dh_a[] = {
+	0x44, 0x00, 0x51, 0xd6,
+	0xf0, 0xb5, 0x5e, 0xa9,
+	0x67, 0xab, 0x31, 0xc6,
+	0x8a, 0x8b, 0x5e, 0x37,
+	0xd9, 0x10, 0xda, 0xe0,
+	0xe2, 0xd4, 0x59, 0xa4,
+	0x86, 0x45, 0x9c, 0xaa,
+	0xdf, 0x36, 0x75, 0x16,
+};
+
+static __u8 dh_b[] = {
+	0x5d, 0xae, 0xc7, 0x86,
+	0x79, 0x80, 0xa3, 0x24,
+	0x8c, 0xe3, 0x57, 0x8f,
+	0xc7, 0x5f, 0x1b, 0x0f,
+	0x2d, 0xf8, 0x9d, 0x30,
+	0x6f, 0xa4, 0x52, 0xcd,
+	0xe0, 0x7a, 0x04, 0x8a,
+	0xde, 0xd9, 0x26, 0x56,
+};
+
+void dwc_run_dh_test_vectors(void *mem_ctx)
+{
+	uint8_t pkd[384];
+	uint8_t pkh[384];
+	uint8_t hashd[32];
+	uint8_t hashh[32];
+	uint8_t ck[16];
+	uint8_t kdk[32];
+	char dd[5];
+
+	DWC_PRINTF("\n\n\nDH_TEST_VECTORS\n\n");
+
+	/* compute the PKd and SHA-256(PKd || Nd) */
+	DWC_PRINTF("Computing PKd\n");
+	dwc_dh_pk(mem_ctx, 2, dh_a, pkd, hashd);
+
+	/* compute the PKd and SHA-256(PKh || Nd) */
+	DWC_PRINTF("Computing PKh\n");
+	dwc_dh_pk(mem_ctx, 2, dh_b, pkh, hashh);
+
+	/* compute the dhkey */
+	dwc_dh_derive_keys(mem_ctx, 2, pkh, pkd, dh_a, 0, dd, ck, kdk);
+}
+#endif /* DH_TEST_VECTORS */
+
+#endif /* !CONFIG_MACH_IPMATE */
+
+#endif /* DWC_CRYPTOLIB */
diff --git a/drivers/usb/host/dwc_common_port/dwc_dh.h b/drivers/usb/host/dwc_common_port/dwc_dh.h
new file mode 100644
index 000000000000..25c1cc0d588a
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/dwc_dh.h
@@ -0,0 +1,106 @@
+/* =========================================================================
+ * $File: //dwh/usb_iip/dev/software/dwc_common_port_2/dwc_dh.h $
+ * $Revision: #4 $
+ * $Date: 2010/09/28 $
+ * $Change: 1596182 $
+ *
+ * Synopsys Portability Library Software and documentation
+ * (hereinafter, "Software") is an Unsupported proprietary work of
+ * Synopsys, Inc. unless otherwise expressly agreed to in writing
+ * between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product
+ * under any End User Software License Agreement or Agreement for
+ * Licensed Product with Synopsys or any supplement thereto. You are
+ * permitted to use and redistribute this Software in source and binary
+ * forms, with or without modification, provided that redistributions
+ * of source code must retain this notice. You may not view, use,
+ * disclose, copy or distribute this file or any information contained
+ * herein except pursuant to this license grant from Synopsys. If you
+ * do not agree with this notice, including the disclaimer below, then
+ * you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS"
+ * BASIS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE HEREBY DISCLAIMED. IN NO EVENT SHALL
+ * SYNOPSYS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================= */
+#ifndef _DWC_DH_H_
+#define _DWC_DH_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "dwc_os.h"
+
+/** @file
+ *
+ * This file defines the common functions on device and host for performing
+ * numeric association as defined in the WUSB spec.  They are only to be
+ * used internally by the DWC UWB modules. */
+
+extern int dwc_dh_sha256(uint8_t *message, uint32_t len, uint8_t *out);
+extern int dwc_dh_hmac_sha256(uint8_t *message, uint32_t messagelen,
+			      uint8_t *key, uint32_t keylen,
+			      uint8_t *out);
+extern int dwc_dh_modpow(void *mem_ctx, void *num, uint32_t num_len,
+			 void *exp, uint32_t exp_len,
+			 void *mod, uint32_t mod_len,
+			 void *out);
+
+/** Computes PKD or PKH, and SHA-256(PKd || Nd)
+ *
+ * PK = g^exp mod p.
+ *
+ * Input:
+ * Nd = Number of digits on the device.
+ *
+ * Output:
+ * exp = A 32-byte buffer to be filled with a randomly generated number.
+ *       used as either A or B.
+ * pk = A 384-byte buffer to be filled with the PKH or PKD.
+ * hash = A 32-byte buffer to be filled with SHA-256(PK || ND).
+ */
+extern int dwc_dh_pk(void *mem_ctx, uint8_t nd, uint8_t *exp, uint8_t *pkd, uint8_t *hash);
+
+/** Computes the DHKEY, and VD.
+ *
+ * If called from host, then it will comput DHKEY=PKD^exp % p.
+ * If called from device, then it will comput DHKEY=PKH^exp % p.
+ *
+ * Input:
+ * pkd = The PKD value.
+ * pkh = The PKH value.
+ * exp = The A value (if device) or B value (if host) generated in dwc_wudev_dh_pk.
+ * is_host = Set to non zero if a WUSB host is calling this function.
+ *
+ * Output:
+
+ * dd = A pointer to an buffer to be set to the displayed digits string to be shown
+ *      to the user.  This buffer should be at 5 bytes long to hold 4 digits plus a
+ *      null termination character.  This buffer can be used directly for display.
+ * ck = A 16-byte buffer to be filled with the CK.
+ * kdk = A 32-byte buffer to be filled with the KDK.
+ */
+extern int dwc_dh_derive_keys(void *mem_ctx, uint8_t nd, uint8_t *pkh, uint8_t *pkd,
+			      uint8_t *exp, int is_host,
+			      char *dd, uint8_t *ck, uint8_t *kdk);
+
+#ifdef DH_TEST_VECTORS
+extern void dwc_run_dh_test_vectors(void);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _DWC_DH_H_ */
diff --git a/drivers/usb/host/dwc_common_port/dwc_list.h b/drivers/usb/host/dwc_common_port/dwc_list.h
new file mode 100644
index 000000000000..4ce560df0cae
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/dwc_list.h
@@ -0,0 +1,594 @@
+/*	$OpenBSD: queue.h,v 1.26 2004/05/04 16:59:32 grange Exp $	*/
+/*	$NetBSD: queue.h,v 1.11 1996/05/16 05:17:14 mycroft Exp $	*/
+
+/*
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)queue.h	8.5 (Berkeley) 8/20/94
+ */
+
+#ifndef _DWC_LIST_H_
+#define _DWC_LIST_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @file
+ *
+ * This file defines linked list operations.  It is derived from BSD with
+ * only the MACRO names being prefixed with DWC_.  This is because a few of
+ * these names conflict with those on Linux.  For documentation on use, see the
+ * inline comments in the source code.  The original license for this source
+ * code applies and is preserved in the dwc_list.h source file.
+ */
+
+/*
+ * This file defines five types of data structures: singly-linked lists,
+ * lists, simple queues, tail queues, and circular queues.
+ *
+ *
+ * A singly-linked list is headed by a single forward pointer. The elements
+ * are singly linked for minimum space and pointer manipulation overhead at
+ * the expense of O(n) removal for arbitrary elements. New elements can be
+ * added to the list after an existing element or at the head of the list.
+ * Elements being removed from the head of the list should use the explicit
+ * macro for this purpose for optimum efficiency. A singly-linked list may
+ * only be traversed in the forward direction.  Singly-linked lists are ideal
+ * for applications with large datasets and few or no removals or for
+ * implementing a LIFO queue.
+ *
+ * A list is headed by a single forward pointer (or an array of forward
+ * pointers for a hash table header). The elements are doubly linked
+ * so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before
+ * or after an existing element or at the head of the list. A list
+ * may only be traversed in the forward direction.
+ *
+ * A simple queue is headed by a pair of pointers, one the head of the
+ * list and the other to the tail of the list. The elements are singly
+ * linked to save space, so elements can only be removed from the
+ * head of the list. New elements can be added to the list before or after
+ * an existing element, at the head of the list, or at the end of the
+ * list. A simple queue may only be traversed in the forward direction.
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may be traversed in either direction.
+ *
+ * A circle queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or after
+ * an existing element, at the head of the list, or at the end of the list.
+ * A circle queue may be traversed in either direction, but has a more
+ * complex end of list detection.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ */
+
+/*
+ * Double-linked List.
+ */
+
+typedef struct dwc_list_link {
+	struct dwc_list_link *next;
+	struct dwc_list_link *prev;
+} dwc_list_link_t;
+
+#define DWC_LIST_INIT(link) do {	\
+	(link)->next = (link);		\
+	(link)->prev = (link);		\
+} while (0)
+
+#define DWC_LIST_FIRST(link)	((link)->next)
+#define DWC_LIST_LAST(link)	((link)->prev)
+#define DWC_LIST_END(link)	(link)
+#define DWC_LIST_NEXT(link)	((link)->next)
+#define DWC_LIST_PREV(link)	((link)->prev)
+#define DWC_LIST_EMPTY(link)	\
+	(DWC_LIST_FIRST(link) == DWC_LIST_END(link))
+#define DWC_LIST_ENTRY(link, type, field)			\
+	(type *)((uint8_t *)(link) - (size_t)(&((type *)0)->field))
+
+#if 0
+#define DWC_LIST_INSERT_HEAD(list, link) do {			\
+	(link)->next = (list)->next;				\
+	(link)->prev = (list);					\
+	(list)->next->prev = (link);				\
+	(list)->next = (link);					\
+} while (0)
+
+#define DWC_LIST_INSERT_TAIL(list, link) do {			\
+	(link)->next = (list);					\
+	(link)->prev = (list)->prev;				\
+	(list)->prev->next = (link);				\
+	(list)->prev = (link);					\
+} while (0)
+#else
+#define DWC_LIST_INSERT_HEAD(list, link) do {			\
+	dwc_list_link_t *__next__ = (list)->next;		\
+	__next__->prev = (link);				\
+	(link)->next = __next__;				\
+	(link)->prev = (list);					\
+	(list)->next = (link);					\
+} while (0)
+
+#define DWC_LIST_INSERT_TAIL(list, link) do {			\
+	dwc_list_link_t *__prev__ = (list)->prev;		\
+	(list)->prev = (link);					\
+	(link)->next = (list);					\
+	(link)->prev = __prev__;				\
+	__prev__->next = (link);				\
+} while (0)
+#endif
+
+#if 0
+static inline void __list_add(struct list_head *new,
+                              struct list_head *prev,
+                              struct list_head *next)
+{
+        next->prev = new;
+        new->next = next;
+        new->prev = prev;
+        prev->next = new;
+}
+
+static inline void list_add(struct list_head *new, struct list_head *head)
+{
+        __list_add(new, head, head->next);
+}
+
+static inline void list_add_tail(struct list_head *new, struct list_head *head)
+{
+        __list_add(new, head->prev, head);
+}
+
+static inline void __list_del(struct list_head * prev, struct list_head * next)
+{
+        next->prev = prev;
+        prev->next = next;
+}
+
+static inline void list_del(struct list_head *entry)
+{
+        __list_del(entry->prev, entry->next);
+        entry->next = LIST_POISON1;
+        entry->prev = LIST_POISON2;
+}
+#endif
+
+#define DWC_LIST_REMOVE(link) do {				\
+	(link)->next->prev = (link)->prev;			\
+	(link)->prev->next = (link)->next;			\
+} while (0)
+
+#define DWC_LIST_REMOVE_INIT(link) do {				\
+	DWC_LIST_REMOVE(link);					\
+	DWC_LIST_INIT(link);					\
+} while (0)
+
+#define DWC_LIST_MOVE_HEAD(list, link) do {			\
+	DWC_LIST_REMOVE(link);					\
+	DWC_LIST_INSERT_HEAD(list, link);			\
+} while (0)
+
+#define DWC_LIST_MOVE_TAIL(list, link) do {			\
+	DWC_LIST_REMOVE(link);					\
+	DWC_LIST_INSERT_TAIL(list, link);			\
+} while (0)
+
+#define DWC_LIST_FOREACH(var, list)				\
+	for((var) = DWC_LIST_FIRST(list);			\
+	    (var) != DWC_LIST_END(list);			\
+	    (var) = DWC_LIST_NEXT(var))
+
+#define DWC_LIST_FOREACH_SAFE(var, var2, list)			\
+	for((var) = DWC_LIST_FIRST(list), (var2) = DWC_LIST_NEXT(var);	\
+	    (var) != DWC_LIST_END(list);			\
+	    (var) = (var2), (var2) = DWC_LIST_NEXT(var2))
+
+#define DWC_LIST_FOREACH_REVERSE(var, list)			\
+	for((var) = DWC_LIST_LAST(list);			\
+	    (var) != DWC_LIST_END(list);			\
+	    (var) = DWC_LIST_PREV(var))
+
+/*
+ * Singly-linked List definitions.
+ */
+#define DWC_SLIST_HEAD(name, type)					\
+struct name {								\
+	struct type *slh_first;	/* first element */			\
+}
+
+#define DWC_SLIST_HEAD_INITIALIZER(head)				\
+	{ NULL }
+
+#define DWC_SLIST_ENTRY(type)						\
+struct {								\
+	struct type *sle_next;	/* next element */			\
+}
+
+/*
+ * Singly-linked List access methods.
+ */
+#define DWC_SLIST_FIRST(head)	((head)->slh_first)
+#define DWC_SLIST_END(head)		NULL
+#define DWC_SLIST_EMPTY(head)	(SLIST_FIRST(head) == SLIST_END(head))
+#define DWC_SLIST_NEXT(elm, field)	((elm)->field.sle_next)
+
+#define DWC_SLIST_FOREACH(var, head, field)				\
+	for((var) = SLIST_FIRST(head);					\
+	    (var) != SLIST_END(head);					\
+	    (var) = SLIST_NEXT(var, field))
+
+#define DWC_SLIST_FOREACH_PREVPTR(var, varp, head, field)		\
+	for((varp) = &SLIST_FIRST((head));				\
+	    ((var) = *(varp)) != SLIST_END(head);			\
+	    (varp) = &SLIST_NEXT((var), field))
+
+/*
+ * Singly-linked List functions.
+ */
+#define DWC_SLIST_INIT(head) {						\
+	SLIST_FIRST(head) = SLIST_END(head);				\
+}
+
+#define DWC_SLIST_INSERT_AFTER(slistelm, elm, field) do {		\
+	(elm)->field.sle_next = (slistelm)->field.sle_next;		\
+	(slistelm)->field.sle_next = (elm);				\
+} while (0)
+
+#define DWC_SLIST_INSERT_HEAD(head, elm, field) do {			\
+	(elm)->field.sle_next = (head)->slh_first;			\
+	(head)->slh_first = (elm);					\
+} while (0)
+
+#define DWC_SLIST_REMOVE_NEXT(head, elm, field) do {			\
+	(elm)->field.sle_next = (elm)->field.sle_next->field.sle_next;	\
+} while (0)
+
+#define DWC_SLIST_REMOVE_HEAD(head, field) do {				\
+	(head)->slh_first = (head)->slh_first->field.sle_next;		\
+} while (0)
+
+#define DWC_SLIST_REMOVE(head, elm, type, field) do {			\
+	if ((head)->slh_first == (elm)) {				\
+		SLIST_REMOVE_HEAD((head), field);			\
+	}								\
+	else {								\
+		struct type *curelm = (head)->slh_first;		\
+		while( curelm->field.sle_next != (elm) )		\
+			curelm = curelm->field.sle_next;		\
+		curelm->field.sle_next =				\
+		    curelm->field.sle_next->field.sle_next;		\
+	}								\
+} while (0)
+
+/*
+ * Simple queue definitions.
+ */
+#define DWC_SIMPLEQ_HEAD(name, type)					\
+struct name {								\
+	struct type *sqh_first;	/* first element */			\
+	struct type **sqh_last;	/* addr of last next element */		\
+}
+
+#define DWC_SIMPLEQ_HEAD_INITIALIZER(head)				\
+	{ NULL, &(head).sqh_first }
+
+#define DWC_SIMPLEQ_ENTRY(type)						\
+struct {								\
+	struct type *sqe_next;	/* next element */			\
+}
+
+/*
+ * Simple queue access methods.
+ */
+#define DWC_SIMPLEQ_FIRST(head)	    ((head)->sqh_first)
+#define DWC_SIMPLEQ_END(head)	    NULL
+#define DWC_SIMPLEQ_EMPTY(head)	    (SIMPLEQ_FIRST(head) == SIMPLEQ_END(head))
+#define DWC_SIMPLEQ_NEXT(elm, field)    ((elm)->field.sqe_next)
+
+#define DWC_SIMPLEQ_FOREACH(var, head, field)				\
+	for((var) = SIMPLEQ_FIRST(head);				\
+	    (var) != SIMPLEQ_END(head);					\
+	    (var) = SIMPLEQ_NEXT(var, field))
+
+/*
+ * Simple queue functions.
+ */
+#define DWC_SIMPLEQ_INIT(head) do {					\
+	(head)->sqh_first = NULL;					\
+	(head)->sqh_last = &(head)->sqh_first;				\
+} while (0)
+
+#define DWC_SIMPLEQ_INSERT_HEAD(head, elm, field) do {			\
+	if (((elm)->field.sqe_next = (head)->sqh_first) == NULL)	\
+		(head)->sqh_last = &(elm)->field.sqe_next;		\
+	(head)->sqh_first = (elm);					\
+} while (0)
+
+#define DWC_SIMPLEQ_INSERT_TAIL(head, elm, field) do {			\
+	(elm)->field.sqe_next = NULL;					\
+	*(head)->sqh_last = (elm);					\
+	(head)->sqh_last = &(elm)->field.sqe_next;			\
+} while (0)
+
+#define DWC_SIMPLEQ_INSERT_AFTER(head, listelm, elm, field) do {	\
+	if (((elm)->field.sqe_next = (listelm)->field.sqe_next) == NULL)\
+		(head)->sqh_last = &(elm)->field.sqe_next;		\
+	(listelm)->field.sqe_next = (elm);				\
+} while (0)
+
+#define DWC_SIMPLEQ_REMOVE_HEAD(head, field) do {			\
+	if (((head)->sqh_first = (head)->sqh_first->field.sqe_next) == NULL) \
+		(head)->sqh_last = &(head)->sqh_first;			\
+} while (0)
+
+/*
+ * Tail queue definitions.
+ */
+#define DWC_TAILQ_HEAD(name, type)					\
+struct name {								\
+	struct type *tqh_first;	/* first element */			\
+	struct type **tqh_last;	/* addr of last next element */		\
+}
+
+#define DWC_TAILQ_HEAD_INITIALIZER(head)				\
+	{ NULL, &(head).tqh_first }
+
+#define DWC_TAILQ_ENTRY(type)						\
+struct {								\
+	struct type *tqe_next;	/* next element */			\
+	struct type **tqe_prev;	/* address of previous next element */	\
+}
+
+/*
+ * tail queue access methods
+ */
+#define DWC_TAILQ_FIRST(head)		((head)->tqh_first)
+#define DWC_TAILQ_END(head)		NULL
+#define DWC_TAILQ_NEXT(elm, field)	((elm)->field.tqe_next)
+#define DWC_TAILQ_LAST(head, headname)					\
+	(*(((struct headname *)((head)->tqh_last))->tqh_last))
+/* XXX */
+#define DWC_TAILQ_PREV(elm, headname, field)				\
+	(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+#define DWC_TAILQ_EMPTY(head)						\
+	(DWC_TAILQ_FIRST(head) == DWC_TAILQ_END(head))
+
+#define DWC_TAILQ_FOREACH(var, head, field)				\
+	for ((var) = DWC_TAILQ_FIRST(head);				\
+	    (var) != DWC_TAILQ_END(head);				\
+	    (var) = DWC_TAILQ_NEXT(var, field))
+
+#define DWC_TAILQ_FOREACH_REVERSE(var, head, headname, field)		\
+	for ((var) = DWC_TAILQ_LAST(head, headname);			\
+	    (var) != DWC_TAILQ_END(head);				\
+	    (var) = DWC_TAILQ_PREV(var, headname, field))
+
+/*
+ * Tail queue functions.
+ */
+#define DWC_TAILQ_INIT(head) do {					\
+	(head)->tqh_first = NULL;					\
+	(head)->tqh_last = &(head)->tqh_first;				\
+} while (0)
+
+#define DWC_TAILQ_INSERT_HEAD(head, elm, field) do {			\
+	if (((elm)->field.tqe_next = (head)->tqh_first) != NULL)	\
+		(head)->tqh_first->field.tqe_prev =			\
+		    &(elm)->field.tqe_next;				\
+	else								\
+		(head)->tqh_last = &(elm)->field.tqe_next;		\
+	(head)->tqh_first = (elm);					\
+	(elm)->field.tqe_prev = &(head)->tqh_first;			\
+} while (0)
+
+#define DWC_TAILQ_INSERT_TAIL(head, elm, field) do {			\
+	(elm)->field.tqe_next = NULL;					\
+	(elm)->field.tqe_prev = (head)->tqh_last;			\
+	*(head)->tqh_last = (elm);					\
+	(head)->tqh_last = &(elm)->field.tqe_next;			\
+} while (0)
+
+#define DWC_TAILQ_INSERT_AFTER(head, listelm, elm, field) do {		\
+	if (((elm)->field.tqe_next = (listelm)->field.tqe_next) != NULL)\
+		(elm)->field.tqe_next->field.tqe_prev =			\
+		    &(elm)->field.tqe_next;				\
+	else								\
+		(head)->tqh_last = &(elm)->field.tqe_next;		\
+	(listelm)->field.tqe_next = (elm);				\
+	(elm)->field.tqe_prev = &(listelm)->field.tqe_next;		\
+} while (0)
+
+#define DWC_TAILQ_INSERT_BEFORE(listelm, elm, field) do {		\
+	(elm)->field.tqe_prev = (listelm)->field.tqe_prev;		\
+	(elm)->field.tqe_next = (listelm);				\
+	*(listelm)->field.tqe_prev = (elm);				\
+	(listelm)->field.tqe_prev = &(elm)->field.tqe_next;		\
+} while (0)
+
+#define DWC_TAILQ_REMOVE(head, elm, field) do {				\
+	if (((elm)->field.tqe_next) != NULL)				\
+		(elm)->field.tqe_next->field.tqe_prev =			\
+		    (elm)->field.tqe_prev;				\
+	else								\
+		(head)->tqh_last = (elm)->field.tqe_prev;		\
+	*(elm)->field.tqe_prev = (elm)->field.tqe_next;			\
+} while (0)
+
+#define DWC_TAILQ_REPLACE(head, elm, elm2, field) do {			\
+	if (((elm2)->field.tqe_next = (elm)->field.tqe_next) != NULL)	\
+		(elm2)->field.tqe_next->field.tqe_prev =		\
+		    &(elm2)->field.tqe_next;				\
+	else								\
+		(head)->tqh_last = &(elm2)->field.tqe_next;		\
+	(elm2)->field.tqe_prev = (elm)->field.tqe_prev;			\
+	*(elm2)->field.tqe_prev = (elm2);				\
+} while (0)
+
+/*
+ * Circular queue definitions.
+ */
+#define DWC_CIRCLEQ_HEAD(name, type)					\
+struct name {								\
+	struct type *cqh_first;		/* first element */		\
+	struct type *cqh_last;		/* last element */		\
+}
+
+#define DWC_CIRCLEQ_HEAD_INITIALIZER(head)				\
+	{ DWC_CIRCLEQ_END(&head), DWC_CIRCLEQ_END(&head) }
+
+#define DWC_CIRCLEQ_ENTRY(type)						\
+struct {								\
+	struct type *cqe_next;		/* next element */		\
+	struct type *cqe_prev;		/* previous element */		\
+}
+
+/*
+ * Circular queue access methods
+ */
+#define DWC_CIRCLEQ_FIRST(head)		((head)->cqh_first)
+#define DWC_CIRCLEQ_LAST(head)		((head)->cqh_last)
+#define DWC_CIRCLEQ_END(head)		((void *)(head))
+#define DWC_CIRCLEQ_NEXT(elm, field)	((elm)->field.cqe_next)
+#define DWC_CIRCLEQ_PREV(elm, field)	((elm)->field.cqe_prev)
+#define DWC_CIRCLEQ_EMPTY(head)						\
+	(DWC_CIRCLEQ_FIRST(head) == DWC_CIRCLEQ_END(head))
+
+#define DWC_CIRCLEQ_EMPTY_ENTRY(elm, field) (((elm)->field.cqe_next == NULL) && ((elm)->field.cqe_prev == NULL))
+
+#define DWC_CIRCLEQ_FOREACH(var, head, field)				\
+	for((var) = DWC_CIRCLEQ_FIRST(head);				\
+	    (var) != DWC_CIRCLEQ_END(head);				\
+	    (var) = DWC_CIRCLEQ_NEXT(var, field))
+
+#define DWC_CIRCLEQ_FOREACH_SAFE(var, var2, head, field)			\
+	for((var) = DWC_CIRCLEQ_FIRST(head), var2 = DWC_CIRCLEQ_NEXT(var, field); \
+	    (var) != DWC_CIRCLEQ_END(head);					\
+	    (var) = var2, var2 = DWC_CIRCLEQ_NEXT(var, field))
+
+#define DWC_CIRCLEQ_FOREACH_REVERSE(var, head, field)			\
+	for((var) = DWC_CIRCLEQ_LAST(head);				\
+	    (var) != DWC_CIRCLEQ_END(head);				\
+	    (var) = DWC_CIRCLEQ_PREV(var, field))
+
+/*
+ * Circular queue functions.
+ */
+#define DWC_CIRCLEQ_INIT(head) do {					\
+	(head)->cqh_first = DWC_CIRCLEQ_END(head);			\
+	(head)->cqh_last = DWC_CIRCLEQ_END(head);			\
+} while (0)
+
+#define DWC_CIRCLEQ_INIT_ENTRY(elm, field) do {				\
+	(elm)->field.cqe_next = NULL;					\
+	(elm)->field.cqe_prev = NULL;					\
+} while (0)
+
+#define DWC_CIRCLEQ_INSERT_AFTER(head, listelm, elm, field) do {	\
+	(elm)->field.cqe_next = (listelm)->field.cqe_next;		\
+	(elm)->field.cqe_prev = (listelm);				\
+	if ((listelm)->field.cqe_next == DWC_CIRCLEQ_END(head))		\
+		(head)->cqh_last = (elm);				\
+	else								\
+		(listelm)->field.cqe_next->field.cqe_prev = (elm);	\
+	(listelm)->field.cqe_next = (elm);				\
+} while (0)
+
+#define DWC_CIRCLEQ_INSERT_BEFORE(head, listelm, elm, field) do {	\
+	(elm)->field.cqe_next = (listelm);				\
+	(elm)->field.cqe_prev = (listelm)->field.cqe_prev;		\
+	if ((listelm)->field.cqe_prev == DWC_CIRCLEQ_END(head))		\
+		(head)->cqh_first = (elm);				\
+	else								\
+		(listelm)->field.cqe_prev->field.cqe_next = (elm);	\
+	(listelm)->field.cqe_prev = (elm);				\
+} while (0)
+
+#define DWC_CIRCLEQ_INSERT_HEAD(head, elm, field) do {			\
+	(elm)->field.cqe_next = (head)->cqh_first;			\
+	(elm)->field.cqe_prev = DWC_CIRCLEQ_END(head);			\
+	if ((head)->cqh_last == DWC_CIRCLEQ_END(head))			\
+		(head)->cqh_last = (elm);				\
+	else								\
+		(head)->cqh_first->field.cqe_prev = (elm);		\
+	(head)->cqh_first = (elm);					\
+} while (0)
+
+#define DWC_CIRCLEQ_INSERT_TAIL(head, elm, field) do {			\
+	(elm)->field.cqe_next = DWC_CIRCLEQ_END(head);			\
+	(elm)->field.cqe_prev = (head)->cqh_last;			\
+	if ((head)->cqh_first == DWC_CIRCLEQ_END(head))			\
+		(head)->cqh_first = (elm);				\
+	else								\
+		(head)->cqh_last->field.cqe_next = (elm);		\
+	(head)->cqh_last = (elm);					\
+} while (0)
+
+#define DWC_CIRCLEQ_REMOVE(head, elm, field) do {			\
+	if ((elm)->field.cqe_next == DWC_CIRCLEQ_END(head))		\
+		(head)->cqh_last = (elm)->field.cqe_prev;		\
+	else								\
+		(elm)->field.cqe_next->field.cqe_prev =			\
+		    (elm)->field.cqe_prev;				\
+	if ((elm)->field.cqe_prev == DWC_CIRCLEQ_END(head))		\
+		(head)->cqh_first = (elm)->field.cqe_next;		\
+	else								\
+		(elm)->field.cqe_prev->field.cqe_next =			\
+		    (elm)->field.cqe_next;				\
+} while (0)
+
+#define DWC_CIRCLEQ_REMOVE_INIT(head, elm, field) do {			\
+	DWC_CIRCLEQ_REMOVE(head, elm, field);				\
+	DWC_CIRCLEQ_INIT_ENTRY(elm, field);				\
+} while (0)
+
+#define DWC_CIRCLEQ_REPLACE(head, elm, elm2, field) do {		\
+	if (((elm2)->field.cqe_next = (elm)->field.cqe_next) ==		\
+	    DWC_CIRCLEQ_END(head))					\
+		(head).cqh_last = (elm2);				\
+	else								\
+		(elm2)->field.cqe_next->field.cqe_prev = (elm2);	\
+	if (((elm2)->field.cqe_prev = (elm)->field.cqe_prev) ==		\
+	    DWC_CIRCLEQ_END(head))					\
+		(head).cqh_first = (elm2);				\
+	else								\
+		(elm2)->field.cqe_prev->field.cqe_next = (elm2);	\
+} while (0)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _DWC_LIST_H_ */
diff --git a/drivers/usb/host/dwc_common_port/dwc_mem.c b/drivers/usb/host/dwc_common_port/dwc_mem.c
new file mode 100644
index 000000000000..ad645ff1ba7e
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/dwc_mem.c
@@ -0,0 +1,245 @@
+/* Memory Debugging */
+#ifdef DWC_DEBUG_MEMORY
+
+#include "dwc_os.h"
+#include "dwc_list.h"
+
+struct allocation {
+	void *addr;
+	void *ctx;
+	char *func;
+	int line;
+	uint32_t size;
+	int dma;
+	DWC_CIRCLEQ_ENTRY(allocation) entry;
+};
+
+DWC_CIRCLEQ_HEAD(allocation_queue, allocation);
+
+struct allocation_manager {
+	void *mem_ctx;
+	struct allocation_queue allocations;
+
+	/* statistics */
+	int num;
+	int num_freed;
+	int num_active;
+	uint32_t total;
+	uint32_t cur;
+	uint32_t max;
+};
+
+static struct allocation_manager *manager = NULL;
+
+static int add_allocation(void *ctx, uint32_t size, char const *func, int line, void *addr,
+			  int dma)
+{
+	struct allocation *a;
+
+	DWC_ASSERT(manager != NULL, "manager not allocated");
+
+	a = __DWC_ALLOC_ATOMIC(manager->mem_ctx, sizeof(*a));
+	if (!a) {
+		return -DWC_E_NO_MEMORY;
+	}
+
+	a->func = __DWC_ALLOC_ATOMIC(manager->mem_ctx, DWC_STRLEN(func) + 1);
+	if (!a->func) {
+		__DWC_FREE(manager->mem_ctx, a);
+		return -DWC_E_NO_MEMORY;
+	}
+
+	DWC_MEMCPY(a->func, func, DWC_STRLEN(func) + 1);
+	a->addr = addr;
+	a->ctx = ctx;
+	a->line = line;
+	a->size = size;
+	a->dma = dma;
+	DWC_CIRCLEQ_INSERT_TAIL(&manager->allocations, a, entry);
+
+	/* Update stats */
+	manager->num++;
+	manager->num_active++;
+	manager->total += size;
+	manager->cur += size;
+
+	if (manager->max < manager->cur) {
+		manager->max = manager->cur;
+	}
+
+	return 0;
+}
+
+static struct allocation *find_allocation(void *ctx, void *addr)
+{
+	struct allocation *a;
+
+	DWC_CIRCLEQ_FOREACH(a, &manager->allocations, entry) {
+		if (a->ctx == ctx && a->addr == addr) {
+			return a;
+		}
+	}
+
+	return NULL;
+}
+
+static void free_allocation(void *ctx, void *addr, char const *func, int line)
+{
+	struct allocation *a = find_allocation(ctx, addr);
+
+	if (!a) {
+		DWC_ASSERT(0,
+			   "Free of address %p that was never allocated or already freed %s:%d",
+			   addr, func, line);
+		return;
+	}
+
+	DWC_CIRCLEQ_REMOVE(&manager->allocations, a, entry);
+
+	manager->num_active--;
+	manager->num_freed++;
+	manager->cur -= a->size;
+	__DWC_FREE(manager->mem_ctx, a->func);
+	__DWC_FREE(manager->mem_ctx, a);
+}
+
+int dwc_memory_debug_start(void *mem_ctx)
+{
+	DWC_ASSERT(manager == NULL, "Memory debugging has already started\n");
+
+	if (manager) {
+		return -DWC_E_BUSY;
+	}
+
+	manager = __DWC_ALLOC(mem_ctx, sizeof(*manager));
+	if (!manager) {
+		return -DWC_E_NO_MEMORY;
+	}
+
+	DWC_CIRCLEQ_INIT(&manager->allocations);
+	manager->mem_ctx = mem_ctx;
+	manager->num = 0;
+	manager->num_freed = 0;
+	manager->num_active = 0;
+	manager->total = 0;
+	manager->cur = 0;
+	manager->max = 0;
+
+	return 0;
+}
+
+void dwc_memory_debug_stop(void)
+{
+	struct allocation *a;
+
+	dwc_memory_debug_report();
+
+	DWC_CIRCLEQ_FOREACH(a, &manager->allocations, entry) {
+		DWC_ERROR("Memory leaked from %s:%d\n", a->func, a->line);
+		free_allocation(a->ctx, a->addr, NULL, -1);
+	}
+
+	__DWC_FREE(manager->mem_ctx, manager);
+}
+
+void dwc_memory_debug_report(void)
+{
+	struct allocation *a;
+
+	DWC_PRINTF("\n\n\n----------------- Memory Debugging Report -----------------\n\n");
+	DWC_PRINTF("Num Allocations = %d\n", manager->num);
+	DWC_PRINTF("Freed = %d\n", manager->num_freed);
+	DWC_PRINTF("Active = %d\n", manager->num_active);
+	DWC_PRINTF("Current Memory Used = %d\n", manager->cur);
+	DWC_PRINTF("Total Memory Used = %d\n", manager->total);
+	DWC_PRINTF("Maximum Memory Used at Once = %d\n", manager->max);
+	DWC_PRINTF("Unfreed allocations:\n");
+
+	DWC_CIRCLEQ_FOREACH(a, &manager->allocations, entry) {
+		DWC_PRINTF("    addr=%p, size=%d from %s:%d, DMA=%d\n",
+			   a->addr, a->size, a->func, a->line, a->dma);
+	}
+}
+
+/* The replacement functions */
+void *dwc_alloc_debug(void *mem_ctx, uint32_t size, char const *func, int line)
+{
+	void *addr = __DWC_ALLOC(mem_ctx, size);
+
+	if (!addr) {
+		return NULL;
+	}
+
+	if (add_allocation(mem_ctx, size, func, line, addr, 0)) {
+		__DWC_FREE(mem_ctx, addr);
+		return NULL;
+	}
+
+	return addr;
+}
+
+void *dwc_alloc_atomic_debug(void *mem_ctx, uint32_t size, char const *func,
+			     int line)
+{
+	void *addr = __DWC_ALLOC_ATOMIC(mem_ctx, size);
+
+	if (!addr) {
+		return NULL;
+	}
+
+	if (add_allocation(mem_ctx, size, func, line, addr, 0)) {
+		__DWC_FREE(mem_ctx, addr);
+		return NULL;
+	}
+
+	return addr;
+}
+
+void dwc_free_debug(void *mem_ctx, void *addr, char const *func, int line)
+{
+	free_allocation(mem_ctx, addr, func, line);
+	__DWC_FREE(mem_ctx, addr);
+}
+
+void *dwc_dma_alloc_debug(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr,
+			  char const *func, int line)
+{
+	void *addr = __DWC_DMA_ALLOC(dma_ctx, size, dma_addr);
+
+	if (!addr) {
+		return NULL;
+	}
+
+	if (add_allocation(dma_ctx, size, func, line, addr, 1)) {
+		__DWC_DMA_FREE(dma_ctx, size, addr, *dma_addr);
+		return NULL;
+	}
+
+	return addr;
+}
+
+void *dwc_dma_alloc_atomic_debug(void *dma_ctx, uint32_t size,
+				 dwc_dma_t *dma_addr, char const *func, int line)
+{
+	void *addr = __DWC_DMA_ALLOC_ATOMIC(dma_ctx, size, dma_addr);
+
+	if (!addr) {
+		return NULL;
+	}
+
+	if (add_allocation(dma_ctx, size, func, line, addr, 1)) {
+		__DWC_DMA_FREE(dma_ctx, size, addr, *dma_addr);
+		return NULL;
+	}
+
+	return addr;
+}
+
+void dwc_dma_free_debug(void *dma_ctx, uint32_t size, void *virt_addr,
+			dwc_dma_t dma_addr, char const *func, int line)
+{
+	free_allocation(dma_ctx, virt_addr, func, line);
+	__DWC_DMA_FREE(dma_ctx, size, virt_addr, dma_addr);
+}
+
+#endif /* DWC_DEBUG_MEMORY */
diff --git a/drivers/usb/host/dwc_common_port/dwc_modpow.c b/drivers/usb/host/dwc_common_port/dwc_modpow.c
new file mode 100644
index 000000000000..20045381208a
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/dwc_modpow.c
@@ -0,0 +1,636 @@
+/* Bignum routines adapted from PUTTY sources.  PuTTY copyright notice follows.
+ *
+ * PuTTY is copyright 1997-2007 Simon Tatham.
+ *
+ * Portions copyright Robert de Bath, Joris van Rantwijk, Delian
+ * Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry,
+ * Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus
+ * Kuhn, and CORE SDI S.A.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE
+ * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+#ifdef DWC_CRYPTOLIB
+
+#ifndef CONFIG_MACH_IPMATE
+
+#include "dwc_modpow.h"
+
+#define BIGNUM_INT_MASK  0xFFFFFFFFUL
+#define BIGNUM_TOP_BIT   0x80000000UL
+#define BIGNUM_INT_BITS  32
+
+
+static void *snmalloc(void *mem_ctx, size_t n, size_t size)
+{
+    void *p;
+    size *= n;
+    if (size == 0) size = 1;
+    p = dwc_alloc(mem_ctx, size);
+    return p;
+}
+
+#define snewn(ctx, n, type) ((type *)snmalloc((ctx), (n), sizeof(type)))
+#define sfree dwc_free
+
+/*
+ * Usage notes:
+ *  * Do not call the DIVMOD_WORD macro with expressions such as array
+ *    subscripts, as some implementations object to this (see below).
+ *  * Note that none of the division methods below will cope if the
+ *    quotient won't fit into BIGNUM_INT_BITS. Callers should be careful
+ *    to avoid this case.
+ *    If this condition occurs, in the case of the x86 DIV instruction,
+ *    an overflow exception will occur, which (according to a correspondent)
+ *    will manifest on Windows as something like
+ *      0xC0000095: Integer overflow
+ *    The C variant won't give the right answer, either.
+ */
+
+#define MUL_WORD(w1, w2) ((BignumDblInt)w1 * w2)
+
+#if defined __GNUC__ && defined __i386__
+#define DIVMOD_WORD(q, r, hi, lo, w) \
+    __asm__("div %2" : \
+	    "=d" (r), "=a" (q) : \
+	    "r" (w), "d" (hi), "a" (lo))
+#else
+#define DIVMOD_WORD(q, r, hi, lo, w) do { \
+    BignumDblInt n = (((BignumDblInt)hi) << BIGNUM_INT_BITS) | lo; \
+    q = n / w; \
+    r = n % w; \
+} while (0)
+#endif
+
+//    q = n / w;
+//    r = n % w;
+
+#define BIGNUM_INT_BYTES (BIGNUM_INT_BITS / 8)
+
+#define BIGNUM_INTERNAL
+
+static Bignum newbn(void *mem_ctx, int length)
+{
+    Bignum b = snewn(mem_ctx, length + 1, BignumInt);
+    //if (!b)
+    //abort();		       /* FIXME */
+    DWC_MEMSET(b, 0, (length + 1) * sizeof(*b));
+    b[0] = length;
+    return b;
+}
+
+void freebn(void *mem_ctx, Bignum b)
+{
+    /*
+     * Burn the evidence, just in case.
+     */
+    DWC_MEMSET(b, 0, sizeof(b[0]) * (b[0] + 1));
+    sfree(mem_ctx, b);
+}
+
+/*
+ * Compute c = a * b.
+ * Input is in the first len words of a and b.
+ * Result is returned in the first 2*len words of c.
+ */
+static void internal_mul(BignumInt *a, BignumInt *b,
+			 BignumInt *c, int len)
+{
+    int i, j;
+    BignumDblInt t;
+
+    for (j = 0; j < 2 * len; j++)
+	c[j] = 0;
+
+    for (i = len - 1; i >= 0; i--) {
+	t = 0;
+	for (j = len - 1; j >= 0; j--) {
+	    t += MUL_WORD(a[i], (BignumDblInt) b[j]);
+	    t += (BignumDblInt) c[i + j + 1];
+	    c[i + j + 1] = (BignumInt) t;
+	    t = t >> BIGNUM_INT_BITS;
+	}
+	c[i] = (BignumInt) t;
+    }
+}
+
+static void internal_add_shifted(BignumInt *number,
+				 unsigned n, int shift)
+{
+    int word = 1 + (shift / BIGNUM_INT_BITS);
+    int bshift = shift % BIGNUM_INT_BITS;
+    BignumDblInt addend;
+
+    addend = (BignumDblInt)n << bshift;
+
+    while (addend) {
+	addend += number[word];
+	number[word] = (BignumInt) addend & BIGNUM_INT_MASK;
+	addend >>= BIGNUM_INT_BITS;
+	word++;
+    }
+}
+
+/*
+ * Compute a = a % m.
+ * Input in first alen words of a and first mlen words of m.
+ * Output in first alen words of a
+ * (of which first alen-mlen words will be zero).
+ * The MSW of m MUST have its high bit set.
+ * Quotient is accumulated in the `quotient' array, which is a Bignum
+ * rather than the internal bigendian format. Quotient parts are shifted
+ * left by `qshift' before adding into quot.
+ */
+static void internal_mod(BignumInt *a, int alen,
+			 BignumInt *m, int mlen,
+			 BignumInt *quot, int qshift)
+{
+    BignumInt m0, m1;
+    unsigned int h;
+    int i, k;
+
+    m0 = m[0];
+    if (mlen > 1)
+	m1 = m[1];
+    else
+	m1 = 0;
+
+    for (i = 0; i <= alen - mlen; i++) {
+	BignumDblInt t;
+	unsigned int q, r, c, ai1;
+
+	if (i == 0) {
+	    h = 0;
+	} else {
+	    h = a[i - 1];
+	    a[i - 1] = 0;
+	}
+
+	if (i == alen - 1)
+	    ai1 = 0;
+	else
+	    ai1 = a[i + 1];
+
+	/* Find q = h:a[i] / m0 */
+	if (h >= m0) {
+	    /*
+	     * Special case.
+	     *
+	     * To illustrate it, suppose a BignumInt is 8 bits, and
+	     * we are dividing (say) A1:23:45:67 by A1:B2:C3. Then
+	     * our initial division will be 0xA123 / 0xA1, which
+	     * will give a quotient of 0x100 and a divide overflow.
+	     * However, the invariants in this division algorithm
+	     * are not violated, since the full number A1:23:... is
+	     * _less_ than the quotient prefix A1:B2:... and so the
+	     * following correction loop would have sorted it out.
+	     *
+	     * In this situation we set q to be the largest
+	     * quotient we _can_ stomach (0xFF, of course).
+	     */
+	    q = BIGNUM_INT_MASK;
+	} else {
+	    /* Macro doesn't want an array subscript expression passed
+	     * into it (see definition), so use a temporary. */
+	    BignumInt tmplo = a[i];
+	    DIVMOD_WORD(q, r, h, tmplo, m0);
+
+	    /* Refine our estimate of q by looking at
+	     h:a[i]:a[i+1] / m0:m1 */
+	    t = MUL_WORD(m1, q);
+	    if (t > ((BignumDblInt) r << BIGNUM_INT_BITS) + ai1) {
+		q--;
+		t -= m1;
+		r = (r + m0) & BIGNUM_INT_MASK;     /* overflow? */
+		if (r >= (BignumDblInt) m0 &&
+		    t > ((BignumDblInt) r << BIGNUM_INT_BITS) + ai1) q--;
+	    }
+	}
+
+	/* Subtract q * m from a[i...] */
+	c = 0;
+	for (k = mlen - 1; k >= 0; k--) {
+	    t = MUL_WORD(q, m[k]);
+	    t += c;
+	    c = (unsigned)(t >> BIGNUM_INT_BITS);
+	    if ((BignumInt) t > a[i + k])
+		c++;
+	    a[i + k] -= (BignumInt) t;
+	}
+
+	/* Add back m in case of borrow */
+	if (c != h) {
+	    t = 0;
+	    for (k = mlen - 1; k >= 0; k--) {
+		t += m[k];
+		t += a[i + k];
+		a[i + k] = (BignumInt) t;
+		t = t >> BIGNUM_INT_BITS;
+	    }
+	    q--;
+	}
+	if (quot)
+	    internal_add_shifted(quot, q, qshift + BIGNUM_INT_BITS * (alen - mlen - i));
+    }
+}
+
+/*
+ * Compute p % mod.
+ * The most significant word of mod MUST be non-zero.
+ * We assume that the result array is the same size as the mod array.
+ * We optionally write out a quotient if `quotient' is non-NULL.
+ * We can avoid writing out the result if `result' is NULL.
+ */
+void bigdivmod(void *mem_ctx, Bignum p, Bignum mod, Bignum result, Bignum quotient)
+{
+    BignumInt *n, *m;
+    int mshift;
+    int plen, mlen, i, j;
+
+    /* Allocate m of size mlen, copy mod to m */
+    /* We use big endian internally */
+    mlen = mod[0];
+    m = snewn(mem_ctx, mlen, BignumInt);
+    //if (!m)
+    //abort();		       /* FIXME */
+    for (j = 0; j < mlen; j++)
+	m[j] = mod[mod[0] - j];
+
+    /* Shift m left to make msb bit set */
+    for (mshift = 0; mshift < BIGNUM_INT_BITS-1; mshift++)
+	if ((m[0] << mshift) & BIGNUM_TOP_BIT)
+	    break;
+    if (mshift) {
+	for (i = 0; i < mlen - 1; i++)
+	    m[i] = (m[i] << mshift) | (m[i + 1] >> (BIGNUM_INT_BITS - mshift));
+	m[mlen - 1] = m[mlen - 1] << mshift;
+    }
+
+    plen = p[0];
+    /* Ensure plen > mlen */
+    if (plen <= mlen)
+	plen = mlen + 1;
+
+    /* Allocate n of size plen, copy p to n */
+    n = snewn(mem_ctx, plen, BignumInt);
+    //if (!n)
+    //abort();		       /* FIXME */
+    for (j = 0; j < plen; j++)
+	n[j] = 0;
+    for (j = 1; j <= (int)p[0]; j++)
+	n[plen - j] = p[j];
+
+    /* Main computation */
+    internal_mod(n, plen, m, mlen, quotient, mshift);
+
+    /* Fixup result in case the modulus was shifted */
+    if (mshift) {
+	for (i = plen - mlen - 1; i < plen - 1; i++)
+	    n[i] = (n[i] << mshift) | (n[i + 1] >> (BIGNUM_INT_BITS - mshift));
+	n[plen - 1] = n[plen - 1] << mshift;
+	internal_mod(n, plen, m, mlen, quotient, 0);
+	for (i = plen - 1; i >= plen - mlen; i--)
+	    n[i] = (n[i] >> mshift) | (n[i - 1] << (BIGNUM_INT_BITS - mshift));
+    }
+
+    /* Copy result to buffer */
+    if (result) {
+	for (i = 1; i <= (int)result[0]; i++) {
+	    int j = plen - i;
+	    result[i] = j >= 0 ? n[j] : 0;
+	}
+    }
+
+    /* Free temporary arrays */
+    for (i = 0; i < mlen; i++)
+	m[i] = 0;
+    sfree(mem_ctx, m);
+    for (i = 0; i < plen; i++)
+	n[i] = 0;
+    sfree(mem_ctx, n);
+}
+
+/*
+ * Simple remainder.
+ */
+Bignum bigmod(void *mem_ctx, Bignum a, Bignum b)
+{
+    Bignum r = newbn(mem_ctx, b[0]);
+    bigdivmod(mem_ctx, a, b, r, NULL);
+    return r;
+}
+
+/*
+ * Compute (base ^ exp) % mod.
+ */
+Bignum dwc_modpow(void *mem_ctx, Bignum base_in, Bignum exp, Bignum mod)
+{
+    BignumInt *a, *b, *n, *m;
+    int mshift;
+    int mlen, i, j;
+    Bignum base, result;
+
+    /*
+     * The most significant word of mod needs to be non-zero. It
+     * should already be, but let's make sure.
+     */
+    //assert(mod[mod[0]] != 0);
+
+    /*
+     * Make sure the base is smaller than the modulus, by reducing
+     * it modulo the modulus if not.
+     */
+    base = bigmod(mem_ctx, base_in, mod);
+
+    /* Allocate m of size mlen, copy mod to m */
+    /* We use big endian internally */
+    mlen = mod[0];
+    m = snewn(mem_ctx, mlen, BignumInt);
+    //if (!m)
+    //abort();		       /* FIXME */
+    for (j = 0; j < mlen; j++)
+	m[j] = mod[mod[0] - j];
+
+    /* Shift m left to make msb bit set */
+    for (mshift = 0; mshift < BIGNUM_INT_BITS - 1; mshift++)
+	if ((m[0] << mshift) & BIGNUM_TOP_BIT)
+	    break;
+    if (mshift) {
+	for (i = 0; i < mlen - 1; i++)
+	    m[i] =
+		(m[i] << mshift) | (m[i + 1] >>
+				    (BIGNUM_INT_BITS - mshift));
+	m[mlen - 1] = m[mlen - 1] << mshift;
+    }
+
+    /* Allocate n of size mlen, copy base to n */
+    n = snewn(mem_ctx, mlen, BignumInt);
+    //if (!n)
+    //abort();		       /* FIXME */
+    i = mlen - base[0];
+    for (j = 0; j < i; j++)
+	n[j] = 0;
+    for (j = 0; j < base[0]; j++)
+	n[i + j] = base[base[0] - j];
+
+    /* Allocate a and b of size 2*mlen. Set a = 1 */
+    a = snewn(mem_ctx, 2 * mlen, BignumInt);
+    //if (!a)
+    //abort();		       /* FIXME */
+    b = snewn(mem_ctx, 2 * mlen, BignumInt);
+    //if (!b)
+    //abort();		       /* FIXME */
+    for (i = 0; i < 2 * mlen; i++)
+	a[i] = 0;
+    a[2 * mlen - 1] = 1;
+
+    /* Skip leading zero bits of exp. */
+    i = 0;
+    j = BIGNUM_INT_BITS - 1;
+    while (i < exp[0] && (exp[exp[0] - i] & (1 << j)) == 0) {
+	j--;
+	if (j < 0) {
+	    i++;
+	    j = BIGNUM_INT_BITS - 1;
+	}
+    }
+
+    /* Main computation */
+    while (i < exp[0]) {
+	while (j >= 0) {
+	    internal_mul(a + mlen, a + mlen, b, mlen);
+	    internal_mod(b, mlen * 2, m, mlen, NULL, 0);
+	    if ((exp[exp[0] - i] & (1 << j)) != 0) {
+		internal_mul(b + mlen, n, a, mlen);
+		internal_mod(a, mlen * 2, m, mlen, NULL, 0);
+	    } else {
+		BignumInt *t;
+		t = a;
+		a = b;
+		b = t;
+	    }
+	    j--;
+	}
+	i++;
+	j = BIGNUM_INT_BITS - 1;
+    }
+
+    /* Fixup result in case the modulus was shifted */
+    if (mshift) {
+	for (i = mlen - 1; i < 2 * mlen - 1; i++)
+	    a[i] =
+		(a[i] << mshift) | (a[i + 1] >>
+				    (BIGNUM_INT_BITS - mshift));
+	a[2 * mlen - 1] = a[2 * mlen - 1] << mshift;
+	internal_mod(a, mlen * 2, m, mlen, NULL, 0);
+	for (i = 2 * mlen - 1; i >= mlen; i--)
+	    a[i] =
+		(a[i] >> mshift) | (a[i - 1] <<
+				    (BIGNUM_INT_BITS - mshift));
+    }
+
+    /* Copy result to buffer */
+    result = newbn(mem_ctx, mod[0]);
+    for (i = 0; i < mlen; i++)
+	result[result[0] - i] = a[i + mlen];
+    while (result[0] > 1 && result[result[0]] == 0)
+	result[0]--;
+
+    /* Free temporary arrays */
+    for (i = 0; i < 2 * mlen; i++)
+	a[i] = 0;
+    sfree(mem_ctx, a);
+    for (i = 0; i < 2 * mlen; i++)
+	b[i] = 0;
+    sfree(mem_ctx, b);
+    for (i = 0; i < mlen; i++)
+	m[i] = 0;
+    sfree(mem_ctx, m);
+    for (i = 0; i < mlen; i++)
+	n[i] = 0;
+    sfree(mem_ctx, n);
+
+    freebn(mem_ctx, base);
+
+    return result;
+}
+
+
+#ifdef UNITTEST
+
+static __u32 dh_p[] = {
+	96,
+	0xFFFFFFFF,
+	0xFFFFFFFF,
+	0xA93AD2CA,
+	0x4B82D120,
+	0xE0FD108E,
+	0x43DB5BFC,
+	0x74E5AB31,
+	0x08E24FA0,
+	0xBAD946E2,
+	0x770988C0,
+	0x7A615D6C,
+	0xBBE11757,
+	0x177B200C,
+	0x521F2B18,
+	0x3EC86A64,
+	0xD8760273,
+	0xD98A0864,
+	0xF12FFA06,
+	0x1AD2EE6B,
+	0xCEE3D226,
+	0x4A25619D,
+	0x1E8C94E0,
+	0xDB0933D7,
+	0xABF5AE8C,
+	0xA6E1E4C7,
+	0xB3970F85,
+	0x5D060C7D,
+	0x8AEA7157,
+	0x58DBEF0A,
+	0xECFB8504,
+	0xDF1CBA64,
+	0xA85521AB,
+	0x04507A33,
+	0xAD33170D,
+	0x8AAAC42D,
+	0x15728E5A,
+	0x98FA0510,
+	0x15D22618,
+	0xEA956AE5,
+	0x3995497C,
+	0x95581718,
+	0xDE2BCBF6,
+	0x6F4C52C9,
+	0xB5C55DF0,
+	0xEC07A28F,
+	0x9B2783A2,
+	0x180E8603,
+	0xE39E772C,
+	0x2E36CE3B,
+	0x32905E46,
+	0xCA18217C,
+	0xF1746C08,
+	0x4ABC9804,
+	0x670C354E,
+	0x7096966D,
+	0x9ED52907,
+	0x208552BB,
+	0x1C62F356,
+	0xDCA3AD96,
+	0x83655D23,
+	0xFD24CF5F,
+	0x69163FA8,
+	0x1C55D39A,
+	0x98DA4836,
+	0xA163BF05,
+	0xC2007CB8,
+	0xECE45B3D,
+	0x49286651,
+	0x7C4B1FE6,
+	0xAE9F2411,
+	0x5A899FA5,
+	0xEE386BFB,
+	0xF406B7ED,
+	0x0BFF5CB6,
+	0xA637ED6B,
+	0xF44C42E9,
+	0x625E7EC6,
+	0xE485B576,
+	0x6D51C245,
+	0x4FE1356D,
+	0xF25F1437,
+	0x302B0A6D,
+	0xCD3A431B,
+	0xEF9519B3,
+	0x8E3404DD,
+	0x514A0879,
+	0x3B139B22,
+	0x020BBEA6,
+	0x8A67CC74,
+	0x29024E08,
+	0x80DC1CD1,
+	0xC4C6628B,
+	0x2168C234,
+	0xC90FDAA2,
+	0xFFFFFFFF,
+	0xFFFFFFFF,
+};
+
+static __u32 dh_a[] = {
+	8,
+	0xdf367516,
+	0x86459caa,
+	0xe2d459a4,
+	0xd910dae0,
+	0x8a8b5e37,
+	0x67ab31c6,
+	0xf0b55ea9,
+	0x440051d6,
+};
+
+static __u32 dh_b[] = {
+	8,
+	0xded92656,
+	0xe07a048a,
+	0x6fa452cd,
+	0x2df89d30,
+	0xc75f1b0f,
+	0x8ce3578f,
+	0x7980a324,
+	0x5daec786,
+};
+
+static __u32 dh_g[] = {
+	1,
+	2,
+};
+
+int main(void)
+{
+	int i;
+	__u32 *k;
+	k = dwc_modpow(NULL, dh_g, dh_a, dh_p);
+
+	printf("\n\n");
+	for (i=0; i<k[0]; i++) {
+		__u32 word32 = k[k[0] - i];
+		__u16 l = word32 & 0xffff;
+		__u16 m = (word32 & 0xffff0000) >> 16;
+		printf("%04x %04x ", m, l);
+		if (!((i + 1)%13)) printf("\n");
+	}
+	printf("\n\n");
+
+	if ((k[0] == 0x60) && (k[1] == 0x28e490e5) && (k[0x60] == 0x5a0d3d4e)) {
+		printf("PASS\n\n");
+	}
+	else {
+		printf("FAIL\n\n");
+	}
+
+}
+
+#endif /* UNITTEST */
+
+#endif /* CONFIG_MACH_IPMATE */
+
+#endif /*DWC_CRYPTOLIB */
diff --git a/drivers/usb/host/dwc_common_port/dwc_modpow.h b/drivers/usb/host/dwc_common_port/dwc_modpow.h
new file mode 100644
index 000000000000..64f00c276e71
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/dwc_modpow.h
@@ -0,0 +1,34 @@
+/*
+ * dwc_modpow.h
+ * See dwc_modpow.c for license and changes
+ */
+#ifndef _DWC_MODPOW_H
+#define _DWC_MODPOW_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "dwc_os.h"
+
+/** @file
+ *
+ * This file defines the module exponentiation function which is only used
+ * internally by the DWC UWB modules for calculation of PKs during numeric
+ * association.  The routine is taken from the PUTTY, an open source terminal
+ * emulator.  The PUTTY License is preserved in the dwc_modpow.c file.
+ *
+ */
+
+typedef uint32_t BignumInt;
+typedef uint64_t BignumDblInt;
+typedef BignumInt *Bignum;
+
+/* Compute modular exponentiaion */
+extern Bignum dwc_modpow(void *mem_ctx, Bignum base_in, Bignum exp, Bignum mod);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LINUX_BIGNUM_H */
diff --git a/drivers/usb/host/dwc_common_port/dwc_notifier.c b/drivers/usb/host/dwc_common_port/dwc_notifier.c
new file mode 100644
index 000000000000..8b3772afe11d
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/dwc_notifier.c
@@ -0,0 +1,319 @@
+#ifdef DWC_NOTIFYLIB
+
+#include "dwc_notifier.h"
+#include "dwc_list.h"
+
+typedef struct dwc_observer {
+	void *observer;
+	dwc_notifier_callback_t callback;
+	void *data;
+	char *notification;
+	DWC_CIRCLEQ_ENTRY(dwc_observer) list_entry;
+} observer_t;
+
+DWC_CIRCLEQ_HEAD(observer_queue, dwc_observer);
+
+typedef struct dwc_notifier {
+	void *mem_ctx;
+	void *object;
+	struct observer_queue observers;
+	DWC_CIRCLEQ_ENTRY(dwc_notifier) list_entry;
+} notifier_t;
+
+DWC_CIRCLEQ_HEAD(notifier_queue, dwc_notifier);
+
+typedef struct manager {
+	void *mem_ctx;
+	void *wkq_ctx;
+	dwc_workq_t *wq;
+//	dwc_mutex_t *mutex;
+	struct notifier_queue notifiers;
+} manager_t;
+
+static manager_t *manager = NULL;
+
+static int create_manager(void *mem_ctx, void *wkq_ctx)
+{
+	manager = dwc_alloc(mem_ctx, sizeof(manager_t));
+	if (!manager) {
+		return -DWC_E_NO_MEMORY;
+	}
+
+	DWC_CIRCLEQ_INIT(&manager->notifiers);
+
+	manager->wq = dwc_workq_alloc(wkq_ctx, "DWC Notification WorkQ");
+	if (!manager->wq) {
+		return -DWC_E_NO_MEMORY;
+	}
+
+	return 0;
+}
+
+static void free_manager(void)
+{
+	dwc_workq_free(manager->wq);
+
+	/* All notifiers must have unregistered themselves before this module
+	 * can be removed.  Hitting this assertion indicates a programmer
+	 * error. */
+	DWC_ASSERT(DWC_CIRCLEQ_EMPTY(&manager->notifiers),
+		   "Notification manager being freed before all notifiers have been removed");
+	dwc_free(manager->mem_ctx, manager);
+}
+
+#ifdef DEBUG
+static void dump_manager(void)
+{
+	notifier_t *n;
+	observer_t *o;
+
+	DWC_ASSERT(manager, "Notification manager not found");
+
+	DWC_DEBUG("List of all notifiers and observers:\n");
+	DWC_CIRCLEQ_FOREACH(n, &manager->notifiers, list_entry) {
+		DWC_DEBUG("Notifier %p has observers:\n", n->object);
+		DWC_CIRCLEQ_FOREACH(o, &n->observers, list_entry) {
+			DWC_DEBUG("    %p watching %s\n", o->observer, o->notification);
+		}
+	}
+}
+#else
+#define dump_manager(...)
+#endif
+
+static observer_t *alloc_observer(void *mem_ctx, void *observer, char *notification,
+				  dwc_notifier_callback_t callback, void *data)
+{
+	observer_t *new_observer = dwc_alloc(mem_ctx, sizeof(observer_t));
+
+	if (!new_observer) {
+		return NULL;
+	}
+
+	DWC_CIRCLEQ_INIT_ENTRY(new_observer, list_entry);
+	new_observer->observer = observer;
+	new_observer->notification = notification;
+	new_observer->callback = callback;
+	new_observer->data = data;
+	return new_observer;
+}
+
+static void free_observer(void *mem_ctx, observer_t *observer)
+{
+	dwc_free(mem_ctx, observer);
+}
+
+static notifier_t *alloc_notifier(void *mem_ctx, void *object)
+{
+	notifier_t *notifier;
+
+	if (!object) {
+		return NULL;
+	}
+
+	notifier = dwc_alloc(mem_ctx, sizeof(notifier_t));
+	if (!notifier) {
+		return NULL;
+	}
+
+	DWC_CIRCLEQ_INIT(&notifier->observers);
+	DWC_CIRCLEQ_INIT_ENTRY(notifier, list_entry);
+
+	notifier->mem_ctx = mem_ctx;
+	notifier->object = object;
+	return notifier;
+}
+
+static void free_notifier(notifier_t *notifier)
+{
+	observer_t *observer;
+
+	DWC_CIRCLEQ_FOREACH(observer, &notifier->observers, list_entry) {
+		free_observer(notifier->mem_ctx, observer);
+	}
+
+	dwc_free(notifier->mem_ctx, notifier);
+}
+
+static notifier_t *find_notifier(void *object)
+{
+	notifier_t *notifier;
+
+	DWC_ASSERT(manager, "Notification manager not found");
+
+	if (!object) {
+		return NULL;
+	}
+
+	DWC_CIRCLEQ_FOREACH(notifier, &manager->notifiers, list_entry) {
+		if (notifier->object == object) {
+			return notifier;
+		}
+	}
+
+	return NULL;
+}
+
+int dwc_alloc_notification_manager(void *mem_ctx, void *wkq_ctx)
+{
+	return create_manager(mem_ctx, wkq_ctx);
+}
+
+void dwc_free_notification_manager(void)
+{
+	free_manager();
+}
+
+dwc_notifier_t *dwc_register_notifier(void *mem_ctx, void *object)
+{
+	notifier_t *notifier;
+
+	DWC_ASSERT(manager, "Notification manager not found");
+
+	notifier = find_notifier(object);
+	if (notifier) {
+		DWC_ERROR("Notifier %p is already registered\n", object);
+		return NULL;
+	}
+
+	notifier = alloc_notifier(mem_ctx, object);
+	if (!notifier) {
+		return NULL;
+	}
+
+	DWC_CIRCLEQ_INSERT_TAIL(&manager->notifiers, notifier, list_entry);
+
+	DWC_INFO("Notifier %p registered", object);
+	dump_manager();
+
+	return notifier;
+}
+
+void dwc_unregister_notifier(dwc_notifier_t *notifier)
+{
+	DWC_ASSERT(manager, "Notification manager not found");
+
+	if (!DWC_CIRCLEQ_EMPTY(&notifier->observers)) {
+		observer_t *o;
+
+		DWC_ERROR("Notifier %p has active observers when removing\n", notifier->object);
+		DWC_CIRCLEQ_FOREACH(o, &notifier->observers, list_entry) {
+			DWC_DEBUGC("    %p watching %s\n", o->observer, o->notification);
+		}
+
+		DWC_ASSERT(DWC_CIRCLEQ_EMPTY(&notifier->observers),
+			   "Notifier %p has active observers when removing", notifier);
+	}
+
+	DWC_CIRCLEQ_REMOVE_INIT(&manager->notifiers, notifier, list_entry);
+	free_notifier(notifier);
+
+	DWC_INFO("Notifier unregistered");
+	dump_manager();
+}
+
+/* Add an observer to observe the notifier for a particular state, event, or notification. */
+int dwc_add_observer(void *observer, void *object, char *notification,
+		     dwc_notifier_callback_t callback, void *data)
+{
+	notifier_t *notifier = find_notifier(object);
+	observer_t *new_observer;
+
+	if (!notifier) {
+		DWC_ERROR("Notifier %p is not found when adding observer\n", object);
+		return -DWC_E_INVALID;
+	}
+
+	new_observer = alloc_observer(notifier->mem_ctx, observer, notification, callback, data);
+	if (!new_observer) {
+		return -DWC_E_NO_MEMORY;
+	}
+
+	DWC_CIRCLEQ_INSERT_TAIL(&notifier->observers, new_observer, list_entry);
+
+	DWC_INFO("Added observer %p to notifier %p observing notification %s, callback=%p, data=%p",
+		 observer, object, notification, callback, data);
+
+	dump_manager();
+	return 0;
+}
+
+int dwc_remove_observer(void *observer)
+{
+	notifier_t *n;
+
+	DWC_ASSERT(manager, "Notification manager not found");
+
+	DWC_CIRCLEQ_FOREACH(n, &manager->notifiers, list_entry) {
+		observer_t *o;
+		observer_t *o2;
+
+		DWC_CIRCLEQ_FOREACH_SAFE(o, o2, &n->observers, list_entry) {
+			if (o->observer == observer) {
+				DWC_CIRCLEQ_REMOVE_INIT(&n->observers, o, list_entry);
+				DWC_INFO("Removing observer %p from notifier %p watching notification %s:",
+					 o->observer, n->object, o->notification);
+				free_observer(n->mem_ctx, o);
+			}
+		}
+	}
+
+	dump_manager();
+	return 0;
+}
+
+typedef struct callback_data {
+	void *mem_ctx;
+	dwc_notifier_callback_t cb;
+	void *observer;
+	void *data;
+	void *object;
+	char *notification;
+	void *notification_data;
+} cb_data_t;
+
+static void cb_task(void *data)
+{
+	cb_data_t *cb = (cb_data_t *)data;
+
+	cb->cb(cb->object, cb->notification, cb->observer, cb->notification_data, cb->data);
+	dwc_free(cb->mem_ctx, cb);
+}
+
+void dwc_notify(dwc_notifier_t *notifier, char *notification, void *notification_data)
+{
+	observer_t *o;
+
+	DWC_ASSERT(manager, "Notification manager not found");
+
+	DWC_CIRCLEQ_FOREACH(o, &notifier->observers, list_entry) {
+		int len = DWC_STRLEN(notification);
+
+		if (DWC_STRLEN(o->notification) != len) {
+			continue;
+		}
+
+		if (DWC_STRNCMP(o->notification, notification, len) == 0) {
+			cb_data_t *cb_data = dwc_alloc(notifier->mem_ctx, sizeof(cb_data_t));
+
+			if (!cb_data) {
+				DWC_ERROR("Failed to allocate callback data\n");
+				return;
+			}
+
+			cb_data->mem_ctx = notifier->mem_ctx;
+			cb_data->cb = o->callback;
+			cb_data->observer = o->observer;
+			cb_data->data = o->data;
+			cb_data->object = notifier->object;
+			cb_data->notification = notification;
+			cb_data->notification_data = notification_data;
+			DWC_DEBUGC("Observer found %p for notification %s\n", o->observer, notification);
+			DWC_WORKQ_SCHEDULE(manager->wq, cb_task, cb_data,
+					   "Notify callback from %p for Notification %s, to observer %p",
+					   cb_data->object, notification, cb_data->observer);
+		}
+	}
+}
+
+#endif	/* DWC_NOTIFYLIB */
diff --git a/drivers/usb/host/dwc_common_port/dwc_notifier.h b/drivers/usb/host/dwc_common_port/dwc_notifier.h
new file mode 100644
index 000000000000..4a8cdfe565b1
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/dwc_notifier.h
@@ -0,0 +1,122 @@
+
+#ifndef __DWC_NOTIFIER_H__
+#define __DWC_NOTIFIER_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "dwc_os.h"
+
+/** @file
+ *
+ * A simple implementation of the Observer pattern.  Any "module" can
+ * register as an observer or notifier.  The notion of "module" is abstract and
+ * can mean anything used to identify either an observer or notifier.  Usually
+ * it will be a pointer to a data structure which contains some state, ie an
+ * object.
+ *
+ * Before any notifiers can be added, the global notification manager must be
+ * brought up with dwc_alloc_notification_manager().
+ * dwc_free_notification_manager() will bring it down and free all resources.
+ * These would typically be called upon module load and unload.  The
+ * notification manager is a single global instance that handles all registered
+ * observable modules and observers so this should be done only once.
+ *
+ * A module can be observable by using Notifications to publicize some general
+ * information about it's state or operation.  It does not care who listens, or
+ * even if anyone listens, or what they do with the information.  The observable
+ * modules do not need to know any information about it's observers or their
+ * interface, or their state or data.
+ *
+ * Any module can register to emit Notifications.  It should publish a list of
+ * notifications that it can emit and their behavior, such as when they will get
+ * triggered, and what information will be provided to the observer.  Then it
+ * should register itself as an observable module. See dwc_register_notifier().
+ *
+ * Any module can observe any observable, registered module, provided it has a
+ * handle to the other module and knows what notifications to observe.  See
+ * dwc_add_observer().
+ *
+ * A function of type dwc_notifier_callback_t is called whenever a notification
+ * is triggered with one or more observers observing it.  This function is
+ * called in it's own process so it may sleep or block if needed.  It is
+ * guaranteed to be called sometime after the notification has occurred and will
+ * be called once per each time the notification is triggered.  It will NOT be
+ * called in the same process context used to trigger the notification.
+ *
+ * @section Limitiations
+ *
+ * Keep in mind that Notifications that can be triggered in rapid sucession may
+ * schedule too many processes too handle.  Be aware of this limitation when
+ * designing to use notifications, and only add notifications for appropriate
+ * observable information.
+ *
+ * Also Notification callbacks are not synchronous.  If you need to synchronize
+ * the behavior between module/observer you must use other means.  And perhaps
+ * that will mean Notifications are not the proper solution.
+ */
+
+struct dwc_notifier;
+typedef struct dwc_notifier dwc_notifier_t;
+
+/** The callback function must be of this type.
+ *
+ * @param object This is the object that is being observed.
+ * @param notification This is the notification that was triggered.
+ * @param observer This is the observer
+ * @param notification_data This is notification-specific data that the notifier
+ * has included in this notification.  The value of this should be published in
+ * the documentation of the observable module with the notifications.
+ * @param user_data This is any custom data that the observer provided when
+ * adding itself as an observer to the notification. */
+typedef void (*dwc_notifier_callback_t)(void *object, char *notification, void *observer,
+					void *notification_data, void *user_data);
+
+/** Brings up the notification manager. */
+extern int dwc_alloc_notification_manager(void *mem_ctx, void *wkq_ctx);
+/** Brings down the notification manager. */
+extern void dwc_free_notification_manager(void);
+
+/** This function registers an observable module.  A dwc_notifier_t object is
+ * returned to the observable module.  This is an opaque object that is used by
+ * the observable module to trigger notifications.  This object should only be
+ * accessible to functions that are authorized to trigger notifications for this
+ * module.  Observers do not need this object. */
+extern dwc_notifier_t *dwc_register_notifier(void *mem_ctx, void *object);
+
+/** This function unregisters an observable module.  All observers have to be
+ * removed prior to unregistration. */
+extern void dwc_unregister_notifier(dwc_notifier_t *notifier);
+
+/** Add a module as an observer to the observable module.  The observable module
+ * needs to have previously registered with the notification manager.
+ *
+ * @param observer The observer module
+ * @param object The module to observe
+ * @param notification The notification to observe
+ * @param callback The callback function to call
+ * @param user_data Any additional user data to pass into the callback function */
+extern int dwc_add_observer(void *observer, void *object, char *notification,
+			    dwc_notifier_callback_t callback, void *user_data);
+
+/** Removes the specified observer from all notifications that it is currently
+ * observing. */
+extern int dwc_remove_observer(void *observer);
+
+/** This function triggers a Notification.  It should be called by the
+ * observable module, or any module or library which the observable module
+ * allows to trigger notification on it's behalf.  Such as the dwc_cc_t.
+ *
+ * dwc_notify is a non-blocking function.  Callbacks are scheduled called in
+ * their own process context for each trigger.  Callbacks can be blocking.
+ * dwc_notify can be called from interrupt context if needed.
+ *
+ */
+void dwc_notify(dwc_notifier_t *notifier, char *notification, void *notification_data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __DWC_NOTIFIER_H__ */
diff --git a/drivers/usb/host/dwc_common_port/dwc_os.h b/drivers/usb/host/dwc_common_port/dwc_os.h
new file mode 100644
index 000000000000..7a4052964e95
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/dwc_os.h
@@ -0,0 +1,1275 @@
+/* =========================================================================
+ * $File: //dwh/usb_iip/dev/software/dwc_common_port_2/dwc_os.h $
+ * $Revision: #14 $
+ * $Date: 2010/11/04 $
+ * $Change: 1621695 $
+ *
+ * Synopsys Portability Library Software and documentation
+ * (hereinafter, "Software") is an Unsupported proprietary work of
+ * Synopsys, Inc. unless otherwise expressly agreed to in writing
+ * between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product
+ * under any End User Software License Agreement or Agreement for
+ * Licensed Product with Synopsys or any supplement thereto. You are
+ * permitted to use and redistribute this Software in source and binary
+ * forms, with or without modification, provided that redistributions
+ * of source code must retain this notice. You may not view, use,
+ * disclose, copy or distribute this file or any information contained
+ * herein except pursuant to this license grant from Synopsys. If you
+ * do not agree with this notice, including the disclaimer below, then
+ * you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS"
+ * BASIS AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE HEREBY DISCLAIMED. IN NO EVENT SHALL
+ * SYNOPSYS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================= */
+#ifndef _DWC_OS_H_
+#define _DWC_OS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @file
+ *
+ * DWC portability library, low level os-wrapper functions
+ *
+ */
+
+/* These basic types need to be defined by some OS header file or custom header
+ * file for your specific target architecture.
+ *
+ * uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t, uint64_t, int64_t
+ *
+ * Any custom or alternate header file must be added and enabled here.
+ */
+
+#ifdef DWC_LINUX
+# include <linux/types.h>
+# ifdef CONFIG_DEBUG_MUTEXES
+#  include <linux/mutex.h>
+# endif
+# include <linux/spinlock.h>
+# include <linux/errno.h>
+#endif
+
+#if defined(DWC_FREEBSD) || defined(DWC_NETBSD)
+# include <os_dep.h>
+#endif
+
+
+/** @name Primitive Types and Values */
+
+/** We define a boolean type for consistency.  Can be either YES or NO */
+typedef uint8_t dwc_bool_t;
+#define YES  1
+#define NO   0
+
+#ifdef DWC_LINUX
+
+/** @name Error Codes */
+#define DWC_E_INVALID		EINVAL
+#define DWC_E_NO_MEMORY		ENOMEM
+#define DWC_E_NO_DEVICE		ENODEV
+#define DWC_E_NOT_SUPPORTED	EOPNOTSUPP
+#define DWC_E_TIMEOUT		ETIMEDOUT
+#define DWC_E_BUSY		EBUSY
+#define DWC_E_AGAIN		EAGAIN
+#define DWC_E_RESTART		ERESTART
+#define DWC_E_ABORT		ECONNABORTED
+#define DWC_E_SHUTDOWN		ESHUTDOWN
+#define DWC_E_NO_DATA		ENODATA
+#define DWC_E_DISCONNECT	ECONNRESET
+#define DWC_E_UNKNOWN		EINVAL
+#define DWC_E_NO_STREAM_RES	ENOSR
+#define DWC_E_COMMUNICATION	ECOMM
+#define DWC_E_OVERFLOW		EOVERFLOW
+#define DWC_E_PROTOCOL		EPROTO
+#define DWC_E_IN_PROGRESS	EINPROGRESS
+#define DWC_E_PIPE		EPIPE
+#define DWC_E_IO		EIO
+#define DWC_E_NO_SPACE		ENOSPC
+
+#else
+
+/** @name Error Codes */
+#define DWC_E_INVALID		1001
+#define DWC_E_NO_MEMORY		1002
+#define DWC_E_NO_DEVICE		1003
+#define DWC_E_NOT_SUPPORTED	1004
+#define DWC_E_TIMEOUT		1005
+#define DWC_E_BUSY		1006
+#define DWC_E_AGAIN		1007
+#define DWC_E_RESTART		1008
+#define DWC_E_ABORT		1009
+#define DWC_E_SHUTDOWN		1010
+#define DWC_E_NO_DATA		1011
+#define DWC_E_DISCONNECT	2000
+#define DWC_E_UNKNOWN		3000
+#define DWC_E_NO_STREAM_RES	4001
+#define DWC_E_COMMUNICATION	4002
+#define DWC_E_OVERFLOW		4003
+#define DWC_E_PROTOCOL		4004
+#define DWC_E_IN_PROGRESS	4005
+#define DWC_E_PIPE		4006
+#define DWC_E_IO		4007
+#define DWC_E_NO_SPACE		4008
+
+#endif
+
+
+/** @name Tracing/Logging Functions
+ *
+ * These function provide the capability to add tracing, debugging, and error
+ * messages, as well exceptions as assertions.  The WUDEV uses these
+ * extensively.  These could be logged to the main console, the serial port, an
+ * internal buffer, etc.  These functions could also be no-op if they are too
+ * expensive on your system.  By default undefining the DEBUG macro already
+ * no-ops some of these functions. */
+
+/** Returns non-zero if in interrupt context. */
+extern dwc_bool_t DWC_IN_IRQ(void);
+#define dwc_in_irq DWC_IN_IRQ
+
+/** Returns "IRQ" if DWC_IN_IRQ is true. */
+static inline char *dwc_irq(void) {
+	return DWC_IN_IRQ() ? "IRQ" : "";
+}
+
+/** Returns non-zero if in bottom-half context. */
+extern dwc_bool_t DWC_IN_BH(void);
+#define dwc_in_bh DWC_IN_BH
+
+/** Returns "BH" if DWC_IN_BH is true. */
+static inline char *dwc_bh(void) {
+	return DWC_IN_BH() ? "BH" : "";
+}
+
+/**
+ * A vprintf() clone.  Just call vprintf if you've got it.
+ */
+extern void DWC_VPRINTF(char *format, va_list args);
+#define dwc_vprintf DWC_VPRINTF
+
+/**
+ * A vsnprintf() clone.  Just call vprintf if you've got it.
+ */
+extern int DWC_VSNPRINTF(char *str, int size, char *format, va_list args);
+#define dwc_vsnprintf DWC_VSNPRINTF
+
+/**
+ * printf() clone.  Just call printf if you've go it.
+ */
+extern void DWC_PRINTF(char *format, ...)
+/* This provides compiler level static checking of the parameters if you're
+ * using GCC. */
+#ifdef __GNUC__
+	__attribute__ ((format(printf, 1, 2)));
+#else
+	;
+#endif
+#define dwc_printf DWC_PRINTF
+
+/**
+ * sprintf() clone.  Just call sprintf if you've got it.
+ */
+extern int DWC_SPRINTF(char *string, char *format, ...)
+#ifdef __GNUC__
+	__attribute__ ((format(printf, 2, 3)));
+#else
+	;
+#endif
+#define dwc_sprintf DWC_SPRINTF
+
+/**
+ * snprintf() clone.  Just call snprintf if you've got it.
+ */
+extern int DWC_SNPRINTF(char *string, int size, char *format, ...)
+#ifdef __GNUC__
+	__attribute__ ((format(printf, 3, 4)));
+#else
+	;
+#endif
+#define dwc_snprintf DWC_SNPRINTF
+
+/**
+ * Prints a WARNING message.  On systems that don't differentiate between
+ * warnings and regular log messages, just print it.  Indicates that something
+ * may be wrong with the driver.  Works like printf().
+ *
+ * Use the DWC_WARN macro to call this function.
+ */
+extern void __DWC_WARN(char *format, ...)
+#ifdef __GNUC__
+	__attribute__ ((format(printf, 1, 2)));
+#else
+	;
+#endif
+
+/**
+ * Prints an error message.  On systems that don't differentiate between errors
+ * and regular log messages, just print it.  Indicates that something went wrong
+ * with the driver.  Works like printf().
+ *
+ * Use the DWC_ERROR macro to call this function.
+ */
+extern void __DWC_ERROR(char *format, ...)
+#ifdef __GNUC__
+	__attribute__ ((format(printf, 1, 2)));
+#else
+	;
+#endif
+
+/**
+ * Prints an exception error message and takes some user-defined action such as
+ * print out a backtrace or trigger a breakpoint.  Indicates that something went
+ * abnormally wrong with the driver such as programmer error, or other
+ * exceptional condition.  It should not be ignored so even on systems without
+ * printing capability, some action should be taken to notify the developer of
+ * it.  Works like printf().
+ */
+extern void DWC_EXCEPTION(char *format, ...)
+#ifdef __GNUC__
+	__attribute__ ((format(printf, 1, 2)));
+#else
+	;
+#endif
+#define dwc_exception DWC_EXCEPTION
+
+#ifndef DWC_OTG_DEBUG_LEV
+#define DWC_OTG_DEBUG_LEV 0
+#endif
+
+#ifdef DEBUG
+/**
+ * Prints out a debug message.  Used for logging/trace messages.
+ *
+ * Use the DWC_DEBUG macro to call this function
+ */
+extern void __DWC_DEBUG(char *format, ...)
+#ifdef __GNUC__
+	__attribute__ ((format(printf, 1, 2)));
+#else
+	;
+#endif
+#else
+#define __DWC_DEBUG printk
+#endif
+
+/**
+ * Prints out a Debug message.
+ */
+#define DWC_DEBUG(_format, _args...) __DWC_DEBUG("DEBUG:%s:%s: " _format "\n", \
+						 __func__, dwc_irq(), ## _args)
+#define dwc_debug DWC_DEBUG
+/**
+ * Prints out a Debug message if enabled at compile time.
+ */
+#if DWC_OTG_DEBUG_LEV > 0
+#define DWC_DEBUGC(_format, _args...) DWC_DEBUG(_format, ##_args )
+#else
+#define DWC_DEBUGC(_format, _args...)
+#endif
+#define dwc_debugc DWC_DEBUGC
+/**
+ * Prints out an informative message.
+ */
+#define DWC_INFO(_format, _args...) DWC_PRINTF("INFO:%s: " _format "\n", \
+					       dwc_irq(), ## _args)
+#define dwc_info DWC_INFO
+/**
+ * Prints out an informative message if enabled at compile time.
+ */
+#if DWC_OTG_DEBUG_LEV > 1
+#define DWC_INFOC(_format, _args...) DWC_INFO(_format, ##_args )
+#else
+#define DWC_INFOC(_format, _args...)
+#endif
+#define dwc_infoc DWC_INFOC
+/**
+ * Prints out a warning message.
+ */
+#define DWC_WARN(_format, _args...) __DWC_WARN("WARN:%s:%s:%d: " _format "\n", \
+					dwc_irq(), __func__, __LINE__, ## _args)
+#define dwc_warn DWC_WARN
+/**
+ * Prints out an error message.
+ */
+#define DWC_ERROR(_format, _args...) __DWC_ERROR("ERROR:%s:%s:%d: " _format "\n", \
+					dwc_irq(), __func__, __LINE__, ## _args)
+#define dwc_error DWC_ERROR
+
+#define DWC_PROTO_ERROR(_format, _args...) __DWC_WARN("ERROR:%s:%s:%d: " _format "\n", \
+						dwc_irq(), __func__, __LINE__, ## _args)
+#define dwc_proto_error DWC_PROTO_ERROR
+
+#ifdef DEBUG
+/** Prints out a exception error message if the _expr expression fails.  Disabled
+ * if DEBUG is not enabled. */
+#define DWC_ASSERT(_expr, _format, _args...) do { \
+	if (!(_expr)) { DWC_EXCEPTION("%s:%s:%d: " _format "\n", dwc_irq(), \
+				      __FILE__, __LINE__, ## _args); } \
+	} while (0)
+#else
+#define DWC_ASSERT(_x...)
+#endif
+#define dwc_assert DWC_ASSERT
+
+
+/** @name Byte Ordering
+ * The following functions are for conversions between processor's byte ordering
+ * and specific ordering you want.
+ */
+
+/** Converts 32 bit data in CPU byte ordering to little endian. */
+extern uint32_t DWC_CPU_TO_LE32(uint32_t *p);
+#define dwc_cpu_to_le32 DWC_CPU_TO_LE32
+
+/** Converts 32 bit data in CPU byte orderint to big endian. */
+extern uint32_t DWC_CPU_TO_BE32(uint32_t *p);
+#define dwc_cpu_to_be32 DWC_CPU_TO_BE32
+
+/** Converts 32 bit little endian data to CPU byte ordering. */
+extern uint32_t DWC_LE32_TO_CPU(uint32_t *p);
+#define dwc_le32_to_cpu DWC_LE32_TO_CPU
+
+/** Converts 32 bit big endian data to CPU byte ordering. */
+extern uint32_t DWC_BE32_TO_CPU(uint32_t *p);
+#define dwc_be32_to_cpu DWC_BE32_TO_CPU
+
+/** Converts 16 bit data in CPU byte ordering to little endian. */
+extern uint16_t DWC_CPU_TO_LE16(uint16_t *p);
+#define dwc_cpu_to_le16 DWC_CPU_TO_LE16
+
+/** Converts 16 bit data in CPU byte orderint to big endian. */
+extern uint16_t DWC_CPU_TO_BE16(uint16_t *p);
+#define dwc_cpu_to_be16 DWC_CPU_TO_BE16
+
+/** Converts 16 bit little endian data to CPU byte ordering. */
+extern uint16_t DWC_LE16_TO_CPU(uint16_t *p);
+#define dwc_le16_to_cpu DWC_LE16_TO_CPU
+
+/** Converts 16 bit bi endian data to CPU byte ordering. */
+extern uint16_t DWC_BE16_TO_CPU(uint16_t *p);
+#define dwc_be16_to_cpu DWC_BE16_TO_CPU
+
+
+/** @name Register Read/Write
+ *
+ * The following six functions should be implemented to read/write registers of
+ * 32-bit and 64-bit sizes.  All modules use this to read/write register values.
+ * The reg value is a pointer to the register calculated from the void *base
+ * variable passed into the driver when it is started.  */
+
+#ifdef DWC_LINUX
+/* Linux doesn't need any extra parameters for register read/write, so we
+ * just throw away the IO context parameter.
+ */
+/** Reads the content of a 32-bit register. */
+extern uint32_t DWC_READ_REG32(uint32_t volatile *reg);
+#define dwc_read_reg32(_ctx_,_reg_) DWC_READ_REG32(_reg_)
+
+/** Reads the content of a 64-bit register. */
+extern uint64_t DWC_READ_REG64(uint64_t volatile *reg);
+#define dwc_read_reg64(_ctx_,_reg_) DWC_READ_REG64(_reg_)
+
+/** Writes to a 32-bit register. */
+extern void DWC_WRITE_REG32(uint32_t volatile *reg, uint32_t value);
+#define dwc_write_reg32(_ctx_,_reg_,_val_) DWC_WRITE_REG32(_reg_, _val_)
+
+/** Writes to a 64-bit register. */
+extern void DWC_WRITE_REG64(uint64_t volatile *reg, uint64_t value);
+#define dwc_write_reg64(_ctx_,_reg_,_val_) DWC_WRITE_REG64(_reg_, _val_)
+
+/**
+ * Modify bit values in a register.  Using the
+ * algorithm: (reg_contents & ~clear_mask) | set_mask.
+ */
+extern void DWC_MODIFY_REG32(uint32_t volatile *reg, uint32_t clear_mask, uint32_t set_mask);
+#define dwc_modify_reg32(_ctx_,_reg_,_cmsk_,_smsk_) DWC_MODIFY_REG32(_reg_,_cmsk_,_smsk_)
+extern void DWC_MODIFY_REG64(uint64_t volatile *reg, uint64_t clear_mask, uint64_t set_mask);
+#define dwc_modify_reg64(_ctx_,_reg_,_cmsk_,_smsk_) DWC_MODIFY_REG64(_reg_,_cmsk_,_smsk_)
+
+#endif	/* DWC_LINUX */
+
+#if defined(DWC_FREEBSD) || defined(DWC_NETBSD)
+typedef struct dwc_ioctx {
+	struct device *dev;
+	bus_space_tag_t iot;
+	bus_space_handle_t ioh;
+} dwc_ioctx_t;
+
+/** BSD needs two extra parameters for register read/write, so we pass
+ * them in using the IO context parameter.
+ */
+/** Reads the content of a 32-bit register. */
+extern uint32_t DWC_READ_REG32(void *io_ctx, uint32_t volatile *reg);
+#define dwc_read_reg32 DWC_READ_REG32
+
+/** Reads the content of a 64-bit register. */
+extern uint64_t DWC_READ_REG64(void *io_ctx, uint64_t volatile *reg);
+#define dwc_read_reg64 DWC_READ_REG64
+
+/** Writes to a 32-bit register. */
+extern void DWC_WRITE_REG32(void *io_ctx, uint32_t volatile *reg, uint32_t value);
+#define dwc_write_reg32 DWC_WRITE_REG32
+
+/** Writes to a 64-bit register. */
+extern void DWC_WRITE_REG64(void *io_ctx, uint64_t volatile *reg, uint64_t value);
+#define dwc_write_reg64 DWC_WRITE_REG64
+
+/**
+ * Modify bit values in a register.  Using the
+ * algorithm: (reg_contents & ~clear_mask) | set_mask.
+ */
+extern void DWC_MODIFY_REG32(void *io_ctx, uint32_t volatile *reg, uint32_t clear_mask, uint32_t set_mask);
+#define dwc_modify_reg32 DWC_MODIFY_REG32
+extern void DWC_MODIFY_REG64(void *io_ctx, uint64_t volatile *reg, uint64_t clear_mask, uint64_t set_mask);
+#define dwc_modify_reg64 DWC_MODIFY_REG64
+
+#endif	/* DWC_FREEBSD || DWC_NETBSD */
+
+/** @cond */
+
+/** @name Some convenience MACROS used internally.  Define DWC_DEBUG_REGS to log the
+ * register writes. */
+
+#ifdef DWC_LINUX
+
+# ifdef DWC_DEBUG_REGS
+
+#define dwc_define_read_write_reg_n(_reg,_container_type) \
+static inline uint32_t dwc_read_##_reg##_n(_container_type *container, int num) { \
+	return DWC_READ_REG32(&container->regs->_reg[num]); \
+} \
+static inline void dwc_write_##_reg##_n(_container_type *container, int num, uint32_t data) { \
+	DWC_DEBUG("WRITING %8s[%d]: %p: %08x", #_reg, num, \
+		  &(((uint32_t*)container->regs->_reg)[num]), data); \
+	DWC_WRITE_REG32(&(((uint32_t*)container->regs->_reg)[num]), data); \
+}
+
+#define dwc_define_read_write_reg(_reg,_container_type) \
+static inline uint32_t dwc_read_##_reg(_container_type *container) { \
+	return DWC_READ_REG32(&container->regs->_reg); \
+} \
+static inline void dwc_write_##_reg(_container_type *container, uint32_t data) { \
+	DWC_DEBUG("WRITING %11s: %p: %08x", #_reg, &container->regs->_reg, data); \
+	DWC_WRITE_REG32(&container->regs->_reg, data); \
+}
+
+# else	/* DWC_DEBUG_REGS */
+
+#define dwc_define_read_write_reg_n(_reg,_container_type) \
+static inline uint32_t dwc_read_##_reg##_n(_container_type *container, int num) { \
+	return DWC_READ_REG32(&container->regs->_reg[num]); \
+} \
+static inline void dwc_write_##_reg##_n(_container_type *container, int num, uint32_t data) { \
+	DWC_WRITE_REG32(&(((uint32_t*)container->regs->_reg)[num]), data); \
+}
+
+#define dwc_define_read_write_reg(_reg,_container_type) \
+static inline uint32_t dwc_read_##_reg(_container_type *container) { \
+	return DWC_READ_REG32(&container->regs->_reg); \
+} \
+static inline void dwc_write_##_reg(_container_type *container, uint32_t data) { \
+	DWC_WRITE_REG32(&container->regs->_reg, data); \
+}
+
+# endif	/* DWC_DEBUG_REGS */
+
+#endif	/* DWC_LINUX */
+
+#if defined(DWC_FREEBSD) || defined(DWC_NETBSD)
+
+# ifdef DWC_DEBUG_REGS
+
+#define dwc_define_read_write_reg_n(_reg,_container_type) \
+static inline uint32_t dwc_read_##_reg##_n(void *io_ctx, _container_type *container, int num) { \
+	return DWC_READ_REG32(io_ctx, &container->regs->_reg[num]); \
+} \
+static inline void dwc_write_##_reg##_n(void *io_ctx, _container_type *container, int num, uint32_t data) { \
+	DWC_DEBUG("WRITING %8s[%d]: %p: %08x", #_reg, num, \
+		  &(((uint32_t*)container->regs->_reg)[num]), data); \
+	DWC_WRITE_REG32(io_ctx, &(((uint32_t*)container->regs->_reg)[num]), data); \
+}
+
+#define dwc_define_read_write_reg(_reg,_container_type) \
+static inline uint32_t dwc_read_##_reg(void *io_ctx, _container_type *container) { \
+	return DWC_READ_REG32(io_ctx, &container->regs->_reg); \
+} \
+static inline void dwc_write_##_reg(void *io_ctx, _container_type *container, uint32_t data) { \
+	DWC_DEBUG("WRITING %11s: %p: %08x", #_reg, &container->regs->_reg, data); \
+	DWC_WRITE_REG32(io_ctx, &container->regs->_reg, data); \
+}
+
+# else	/* DWC_DEBUG_REGS */
+
+#define dwc_define_read_write_reg_n(_reg,_container_type) \
+static inline uint32_t dwc_read_##_reg##_n(void *io_ctx, _container_type *container, int num) { \
+	return DWC_READ_REG32(io_ctx, &container->regs->_reg[num]); \
+} \
+static inline void dwc_write_##_reg##_n(void *io_ctx, _container_type *container, int num, uint32_t data) { \
+	DWC_WRITE_REG32(io_ctx, &(((uint32_t*)container->regs->_reg)[num]), data); \
+}
+
+#define dwc_define_read_write_reg(_reg,_container_type) \
+static inline uint32_t dwc_read_##_reg(void *io_ctx, _container_type *container) { \
+	return DWC_READ_REG32(io_ctx, &container->regs->_reg); \
+} \
+static inline void dwc_write_##_reg(void *io_ctx, _container_type *container, uint32_t data) { \
+	DWC_WRITE_REG32(io_ctx, &container->regs->_reg, data); \
+}
+
+# endif	/* DWC_DEBUG_REGS */
+
+#endif	/* DWC_FREEBSD || DWC_NETBSD */
+
+/** @endcond */
+
+
+#ifdef DWC_CRYPTOLIB
+/** @name Crypto Functions
+ *
+ * These are the low-level cryptographic functions used by the driver. */
+
+/** Perform AES CBC */
+extern int DWC_AES_CBC(uint8_t *message, uint32_t messagelen, uint8_t *key, uint32_t keylen, uint8_t iv[16], uint8_t *out);
+#define dwc_aes_cbc DWC_AES_CBC
+
+/** Fill the provided buffer with random bytes.  These should be cryptographic grade random numbers. */
+extern void DWC_RANDOM_BYTES(uint8_t *buffer, uint32_t length);
+#define dwc_random_bytes DWC_RANDOM_BYTES
+
+/** Perform the SHA-256 hash function */
+extern int DWC_SHA256(uint8_t *message, uint32_t len, uint8_t *out);
+#define dwc_sha256 DWC_SHA256
+
+/** Calculated the HMAC-SHA256 */
+extern int DWC_HMAC_SHA256(uint8_t *message, uint32_t messagelen, uint8_t *key, uint32_t keylen, uint8_t *out);
+#define dwc_hmac_sha256 DWC_HMAC_SHA256
+
+#endif	/* DWC_CRYPTOLIB */
+
+
+/** @name Memory Allocation
+ *
+ * These function provide access to memory allocation.  There are only 2 DMA
+ * functions and 3 Regular memory functions that need to be implemented.  None
+ * of the memory debugging routines need to be implemented.  The allocation
+ * routines all ZERO the contents of the memory.
+ *
+ * Defining DWC_DEBUG_MEMORY turns on memory debugging and statistic gathering.
+ * This checks for memory leaks, keeping track of alloc/free pairs.  It also
+ * keeps track of how much memory the driver is using at any given time. */
+
+#define DWC_PAGE_SIZE 4096
+#define DWC_PAGE_OFFSET(addr) (((uint32_t)addr) & 0xfff)
+#define DWC_PAGE_ALIGNED(addr) ((((uint32_t)addr) & 0xfff) == 0)
+
+#define DWC_INVALID_DMA_ADDR 0x0
+
+#ifdef DWC_LINUX
+/** Type for a DMA address */
+typedef dma_addr_t dwc_dma_t;
+#endif
+
+#if defined(DWC_FREEBSD) || defined(DWC_NETBSD)
+typedef bus_addr_t dwc_dma_t;
+#endif
+
+#ifdef DWC_FREEBSD
+typedef struct dwc_dmactx {
+	struct device *dev;
+	bus_dma_tag_t dma_tag;
+	bus_dmamap_t dma_map;
+	bus_addr_t dma_paddr;
+	void *dma_vaddr;
+} dwc_dmactx_t;
+#endif
+
+#ifdef DWC_NETBSD
+typedef struct dwc_dmactx {
+	struct device *dev;
+	bus_dma_tag_t dma_tag;
+	bus_dmamap_t dma_map;
+	bus_dma_segment_t segs[1];
+	int nsegs;
+	bus_addr_t dma_paddr;
+	void *dma_vaddr;
+} dwc_dmactx_t;
+#endif
+
+/* @todo these functions will be added in the future */
+#if 0
+/**
+ * Creates a DMA pool from which you can allocate DMA buffers.  Buffers
+ * allocated from this pool will be guaranteed to meet the size, alignment, and
+ * boundary requirements specified.
+ *
+ * @param[in] size Specifies the size of the buffers that will be allocated from
+ * this pool.
+ * @param[in] align Specifies the byte alignment requirements of the buffers
+ * allocated from this pool.  Must be a power of 2.
+ * @param[in] boundary Specifies the N-byte boundary that buffers allocated from
+ * this pool must not cross.
+ *
+ * @returns A pointer to an internal opaque structure which is not to be
+ * accessed outside of these library functions.  Use this handle to specify
+ * which pools to allocate/free DMA buffers from and also to destroy the pool,
+ * when you are done with it.
+ */
+extern dwc_pool_t *DWC_DMA_POOL_CREATE(uint32_t size, uint32_t align, uint32_t boundary);
+
+/**
+ * Destroy a DMA pool.  All buffers allocated from that pool must be freed first.
+ */
+extern void DWC_DMA_POOL_DESTROY(dwc_pool_t *pool);
+
+/**
+ * Allocate a buffer from the specified DMA pool and zeros its contents.
+ */
+extern void *DWC_DMA_POOL_ALLOC(dwc_pool_t *pool, uint64_t *dma_addr);
+
+/**
+ * Free a previously allocated buffer from the DMA pool.
+ */
+extern void DWC_DMA_POOL_FREE(dwc_pool_t *pool, void *vaddr, void *daddr);
+#endif
+
+/** Allocates a DMA capable buffer and zeroes its contents. */
+extern void *__DWC_DMA_ALLOC(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr);
+
+/** Allocates a DMA capable buffer and zeroes its contents in atomic contest */
+extern void *__DWC_DMA_ALLOC_ATOMIC(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr);
+
+/** Frees a previously allocated buffer. */
+extern void __DWC_DMA_FREE(void *dma_ctx, uint32_t size, void *virt_addr, dwc_dma_t dma_addr);
+
+/** Allocates a block of memory and zeroes its contents. */
+extern void *__DWC_ALLOC(void *mem_ctx, uint32_t size);
+
+/** Allocates a block of memory and zeroes its contents, in an atomic manner
+ * which can be used inside interrupt context.  The size should be sufficiently
+ * small, a few KB at most, such that failures are not likely to occur.  Can just call
+ * __DWC_ALLOC if it is atomic. */
+extern void *__DWC_ALLOC_ATOMIC(void *mem_ctx, uint32_t size);
+
+/** Frees a previously allocated buffer. */
+extern void __DWC_FREE(void *mem_ctx, void *addr);
+
+#ifndef DWC_DEBUG_MEMORY
+
+#define DWC_ALLOC(_size_) __DWC_ALLOC(NULL, _size_)
+#define DWC_ALLOC_ATOMIC(_size_) __DWC_ALLOC_ATOMIC(NULL, _size_)
+#define DWC_FREE(_addr_) __DWC_FREE(NULL, _addr_)
+
+# ifdef DWC_LINUX
+#define DWC_DMA_ALLOC(_dev, _size_, _dma_) __DWC_DMA_ALLOC(_dev, _size_, _dma_)
+#define DWC_DMA_ALLOC_ATOMIC(_dev, _size_, _dma_) __DWC_DMA_ALLOC_ATOMIC(_dev, _size_, _dma_)
+#define DWC_DMA_FREE(_dev, _size_,_virt_, _dma_) __DWC_DMA_FREE(_dev, _size_, _virt_, _dma_)
+# endif
+
+# if defined(DWC_FREEBSD) || defined(DWC_NETBSD)
+#define DWC_DMA_ALLOC __DWC_DMA_ALLOC
+#define DWC_DMA_FREE __DWC_DMA_FREE
+# endif
+extern void *dwc_dma_alloc_atomic_debug(uint32_t size, dwc_dma_t *dma_addr, char const *func, int line);
+
+#else	/* DWC_DEBUG_MEMORY */
+
+extern void *dwc_alloc_debug(void *mem_ctx, uint32_t size, char const *func, int line);
+extern void *dwc_alloc_atomic_debug(void *mem_ctx, uint32_t size, char const *func, int line);
+extern void dwc_free_debug(void *mem_ctx, void *addr, char const *func, int line);
+extern void *dwc_dma_alloc_debug(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr,
+				 char const *func, int line);
+extern void *dwc_dma_alloc_atomic_debug(void *dma_ctx, uint32_t size, dwc_dma_t *dma_addr,
+				char const *func, int line);
+extern void dwc_dma_free_debug(void *dma_ctx, uint32_t size, void *virt_addr,
+			       dwc_dma_t dma_addr, char const *func, int line);
+
+extern int dwc_memory_debug_start(void *mem_ctx);
+extern void dwc_memory_debug_stop(void);
+extern void dwc_memory_debug_report(void);
+
+#define DWC_ALLOC(_size_) dwc_alloc_debug(NULL, _size_, __func__, __LINE__)
+#define DWC_ALLOC_ATOMIC(_size_) dwc_alloc_atomic_debug(NULL, _size_, \
+							__func__, __LINE__)
+#define DWC_FREE(_addr_) dwc_free_debug(NULL, _addr_, __func__, __LINE__)
+
+# ifdef DWC_LINUX
+#define DWC_DMA_ALLOC(_dev, _size_, _dma_) \
+	dwc_dma_alloc_debug(_dev, _size_, _dma_, __func__, __LINE__)
+#define DWC_DMA_ALLOC_ATOMIC(_dev, _size_, _dma_) \
+	dwc_dma_alloc_atomic_debug(_dev, _size_, _dma_, __func__, __LINE__)
+#define DWC_DMA_FREE(_dev, _size_, _virt_, _dma_) \
+	dwc_dma_free_debug(_dev, _size_, _virt_, _dma_, __func__, __LINE__)
+# endif
+
+# if defined(DWC_FREEBSD) || defined(DWC_NETBSD)
+#define DWC_DMA_ALLOC(_ctx_,_size_,_dma_) dwc_dma_alloc_debug(_ctx_, _size_, \
+						_dma_, __func__, __LINE__)
+#define DWC_DMA_FREE(_ctx_,_size_,_virt_,_dma_) dwc_dma_free_debug(_ctx_, _size_, \
+						 _virt_, _dma_, __func__, __LINE__)
+# endif
+
+#endif /* DWC_DEBUG_MEMORY */
+
+#define dwc_alloc(_ctx_,_size_) DWC_ALLOC(_size_)
+#define dwc_alloc_atomic(_ctx_,_size_) DWC_ALLOC_ATOMIC(_size_)
+#define dwc_free(_ctx_,_addr_) DWC_FREE(_addr_)
+
+#ifdef DWC_LINUX
+/* Linux doesn't need any extra parameters for DMA buffer allocation, so we
+ * just throw away the DMA context parameter.
+ */
+#define dwc_dma_alloc(_ctx_,_size_,_dma_) DWC_DMA_ALLOC(_size_, _dma_)
+#define dwc_dma_alloc_atomic(_ctx_,_size_,_dma_) DWC_DMA_ALLOC_ATOMIC(_size_, _dma_)
+#define dwc_dma_free(_ctx_,_size_,_virt_,_dma_) DWC_DMA_FREE(_size_, _virt_, _dma_)
+#endif
+
+#if defined(DWC_FREEBSD) || defined(DWC_NETBSD)
+/** BSD needs several extra parameters for DMA buffer allocation, so we pass
+ * them in using the DMA context parameter.
+ */
+#define dwc_dma_alloc DWC_DMA_ALLOC
+#define dwc_dma_free DWC_DMA_FREE
+#endif
+
+
+/** @name Memory and String Processing */
+
+/** memset() clone */
+extern void *DWC_MEMSET(void *dest, uint8_t byte, uint32_t size);
+#define dwc_memset DWC_MEMSET
+
+/** memcpy() clone */
+extern void *DWC_MEMCPY(void *dest, void const *src, uint32_t size);
+#define dwc_memcpy DWC_MEMCPY
+
+/** memmove() clone */
+extern void *DWC_MEMMOVE(void *dest, void *src, uint32_t size);
+#define dwc_memmove DWC_MEMMOVE
+
+/** memcmp() clone */
+extern int DWC_MEMCMP(void *m1, void *m2, uint32_t size);
+#define dwc_memcmp DWC_MEMCMP
+
+/** strcmp() clone */
+extern int DWC_STRCMP(void *s1, void *s2);
+#define dwc_strcmp DWC_STRCMP
+
+/** strncmp() clone */
+extern int DWC_STRNCMP(void *s1, void *s2, uint32_t size);
+#define dwc_strncmp DWC_STRNCMP
+
+/** strlen() clone, for NULL terminated ASCII strings */
+extern int DWC_STRLEN(char const *str);
+#define dwc_strlen DWC_STRLEN
+
+/** strcpy() clone, for NULL terminated ASCII strings */
+extern char *DWC_STRCPY(char *to, const char *from);
+#define dwc_strcpy DWC_STRCPY
+
+/** strdup() clone.  If you wish to use memory allocation debugging, this
+ * implementation of strdup should use the DWC_* memory routines instead of
+ * calling a predefined strdup.  Otherwise the memory allocated by this routine
+ * will not be seen by the debugging routines. */
+extern char *DWC_STRDUP(char const *str);
+#define dwc_strdup(_ctx_,_str_) DWC_STRDUP(_str_)
+
+/** NOT an atoi() clone.  Read the description carefully.  Returns an integer
+ * converted from the string str in base 10 unless the string begins with a "0x"
+ * in which case it is base 16.  String must be a NULL terminated sequence of
+ * ASCII characters and may optionally begin with whitespace, a + or -, and a
+ * "0x" prefix if base 16.  The remaining characters must be valid digits for
+ * the number and end with a NULL character.  If any invalid characters are
+ * encountered or it returns with a negative error code and the results of the
+ * conversion are undefined.  On sucess it returns 0.  Overflow conditions are
+ * undefined.  An example implementation using atoi() can be referenced from the
+ * Linux implementation. */
+extern int DWC_ATOI(const char *str, int32_t *value);
+#define dwc_atoi DWC_ATOI
+
+/** Same as above but for unsigned. */
+extern int DWC_ATOUI(const char *str, uint32_t *value);
+#define dwc_atoui DWC_ATOUI
+
+#ifdef DWC_UTFLIB
+/** This routine returns a UTF16LE unicode encoded string from a UTF8 string. */
+extern int DWC_UTF8_TO_UTF16LE(uint8_t const *utf8string, uint16_t *utf16string, unsigned len);
+#define dwc_utf8_to_utf16le DWC_UTF8_TO_UTF16LE
+#endif
+
+
+/** @name Wait queues
+ *
+ * Wait queues provide a means of synchronizing between threads or processes.  A
+ * process can block on a waitq if some condition is not true, waiting for it to
+ * become true.  When the waitq is triggered all waiting process will get
+ * unblocked and the condition will be check again.  Waitqs should be triggered
+ * every time a condition can potentially change.*/
+struct dwc_waitq;
+
+/** Type for a waitq */
+typedef struct dwc_waitq dwc_waitq_t;
+
+/** The type of waitq condition callback function.  This is called every time
+ * condition is evaluated. */
+typedef int (*dwc_waitq_condition_t)(void *data);
+
+/** Allocate a waitq */
+extern dwc_waitq_t *DWC_WAITQ_ALLOC(void);
+#define dwc_waitq_alloc(_ctx_) DWC_WAITQ_ALLOC()
+
+/** Free a waitq */
+extern void DWC_WAITQ_FREE(dwc_waitq_t *wq);
+#define dwc_waitq_free DWC_WAITQ_FREE
+
+/** Check the condition and if it is false, block on the waitq.  When unblocked, check the
+ * condition again.  The function returns when the condition becomes true.  The return value
+ * is 0 on condition true, DWC_WAITQ_ABORTED on abort or killed, or DWC_WAITQ_UNKNOWN on error. */
+extern int32_t DWC_WAITQ_WAIT(dwc_waitq_t *wq, dwc_waitq_condition_t cond, void *data);
+#define dwc_waitq_wait DWC_WAITQ_WAIT
+
+/** Check the condition and if it is false, block on the waitq.  When unblocked,
+ * check the condition again.  The function returns when the condition become
+ * true or the timeout has passed.  The return value is 0 on condition true or
+ * DWC_TIMED_OUT on timeout, or DWC_WAITQ_ABORTED, or DWC_WAITQ_UNKNOWN on
+ * error. */
+extern int32_t DWC_WAITQ_WAIT_TIMEOUT(dwc_waitq_t *wq, dwc_waitq_condition_t cond,
+				      void *data, int32_t msecs);
+#define dwc_waitq_wait_timeout DWC_WAITQ_WAIT_TIMEOUT
+
+/** Trigger a waitq, unblocking all processes.  This should be called whenever a condition
+ * has potentially changed. */
+extern void DWC_WAITQ_TRIGGER(dwc_waitq_t *wq);
+#define dwc_waitq_trigger DWC_WAITQ_TRIGGER
+
+/** Unblock all processes waiting on the waitq with an ABORTED result. */
+extern void DWC_WAITQ_ABORT(dwc_waitq_t *wq);
+#define dwc_waitq_abort DWC_WAITQ_ABORT
+
+
+/** @name Threads
+ *
+ * A thread must be explicitly stopped.  It must check DWC_THREAD_SHOULD_STOP
+ * whenever it is woken up, and then return.  The DWC_THREAD_STOP function
+ * returns the value from the thread.
+ */
+
+struct dwc_thread;
+
+/** Type for a thread */
+typedef struct dwc_thread dwc_thread_t;
+
+/** The thread function */
+typedef int (*dwc_thread_function_t)(void *data);
+
+/** Create a thread and start it running the thread_function.  Returns a handle
+ * to the thread */
+extern dwc_thread_t *DWC_THREAD_RUN(dwc_thread_function_t func, char *name, void *data);
+#define dwc_thread_run(_ctx_,_func_,_name_,_data_) DWC_THREAD_RUN(_func_, _name_, _data_)
+
+/** Stops a thread.  Return the value returned by the thread.  Or will return
+ * DWC_ABORT if the thread never started. */
+extern int DWC_THREAD_STOP(dwc_thread_t *thread);
+#define dwc_thread_stop DWC_THREAD_STOP
+
+/** Signifies to the thread that it must stop. */
+#ifdef DWC_LINUX
+/* Linux doesn't need any parameters for kthread_should_stop() */
+extern dwc_bool_t DWC_THREAD_SHOULD_STOP(void);
+#define dwc_thread_should_stop(_thrd_) DWC_THREAD_SHOULD_STOP()
+
+/* No thread_exit function in Linux */
+#define dwc_thread_exit(_thrd_)
+#endif
+
+#if defined(DWC_FREEBSD) || defined(DWC_NETBSD)
+/** BSD needs the thread pointer for kthread_suspend_check() */
+extern dwc_bool_t DWC_THREAD_SHOULD_STOP(dwc_thread_t *thread);
+#define dwc_thread_should_stop DWC_THREAD_SHOULD_STOP
+
+/** The thread must call this to exit. */
+extern void DWC_THREAD_EXIT(dwc_thread_t *thread);
+#define dwc_thread_exit DWC_THREAD_EXIT
+#endif
+
+
+/** @name Work queues
+ *
+ * Workqs are used to queue a callback function to be called at some later time,
+ * in another thread. */
+struct dwc_workq;
+
+/** Type for a workq */
+typedef struct dwc_workq dwc_workq_t;
+
+/** The type of the callback function to be called. */
+typedef void (*dwc_work_callback_t)(void *data);
+
+/** Allocate a workq */
+extern dwc_workq_t *DWC_WORKQ_ALLOC(char *name);
+#define dwc_workq_alloc(_ctx_,_name_) DWC_WORKQ_ALLOC(_name_)
+
+/** Free a workq.  All work must be completed before being freed. */
+extern void DWC_WORKQ_FREE(dwc_workq_t *workq);
+#define dwc_workq_free DWC_WORKQ_FREE
+
+/** Schedule a callback on the workq, passing in data.  The function will be
+ * scheduled at some later time. */
+extern void DWC_WORKQ_SCHEDULE(dwc_workq_t *workq, dwc_work_callback_t cb,
+			       void *data, char *format, ...)
+#ifdef __GNUC__
+	__attribute__ ((format(printf, 4, 5)));
+#else
+	;
+#endif
+#define dwc_workq_schedule DWC_WORKQ_SCHEDULE
+
+/** Schedule a callback on the workq, that will be called until at least
+ * given number miliseconds have passed. */
+extern void DWC_WORKQ_SCHEDULE_DELAYED(dwc_workq_t *workq, dwc_work_callback_t cb,
+				       void *data, uint32_t time, char *format, ...)
+#ifdef __GNUC__
+	__attribute__ ((format(printf, 5, 6)));
+#else
+	;
+#endif
+#define dwc_workq_schedule_delayed DWC_WORKQ_SCHEDULE_DELAYED
+
+/** The number of processes in the workq */
+extern int DWC_WORKQ_PENDING(dwc_workq_t *workq);
+#define dwc_workq_pending DWC_WORKQ_PENDING
+
+/** Blocks until all the work in the workq is complete or timed out.  Returns <
+ * 0 on timeout. */
+extern int DWC_WORKQ_WAIT_WORK_DONE(dwc_workq_t *workq, int timeout);
+#define dwc_workq_wait_work_done DWC_WORKQ_WAIT_WORK_DONE
+
+
+/** @name Tasklets
+ *
+ */
+struct dwc_tasklet;
+
+/** Type for a tasklet */
+typedef struct dwc_tasklet dwc_tasklet_t;
+
+/** The type of the callback function to be called */
+typedef void (*dwc_tasklet_callback_t)(void *data);
+
+/** Allocates a tasklet */
+extern dwc_tasklet_t *DWC_TASK_ALLOC(char *name, dwc_tasklet_callback_t cb, void *data);
+#define dwc_task_alloc(_ctx_,_name_,_cb_,_data_) DWC_TASK_ALLOC(_name_, _cb_, _data_)
+
+/** Frees a tasklet */
+extern void DWC_TASK_FREE(dwc_tasklet_t *task);
+#define dwc_task_free DWC_TASK_FREE
+
+/** Schedules a tasklet to run */
+extern void DWC_TASK_SCHEDULE(dwc_tasklet_t *task);
+#define dwc_task_schedule DWC_TASK_SCHEDULE
+
+extern void DWC_TASK_HI_SCHEDULE(dwc_tasklet_t *task);
+#define dwc_task_hi_schedule DWC_TASK_HI_SCHEDULE
+
+/** @name Timer
+ *
+ * Callbacks must be small and atomic.
+ */
+struct dwc_timer;
+
+/** Type for a timer */
+typedef struct dwc_timer dwc_timer_t;
+
+/** The type of the callback function to be called */
+typedef void (*dwc_timer_callback_t)(void *data);
+
+/** Allocates a timer */
+extern dwc_timer_t *DWC_TIMER_ALLOC(char *name, dwc_timer_callback_t cb, void *data);
+#define dwc_timer_alloc(_ctx_,_name_,_cb_,_data_) DWC_TIMER_ALLOC(_name_,_cb_,_data_)
+
+/** Frees a timer */
+extern void DWC_TIMER_FREE(dwc_timer_t *timer);
+#define dwc_timer_free DWC_TIMER_FREE
+
+/** Schedules the timer to run at time ms from now.  And will repeat at every
+ * repeat_interval msec therafter
+ *
+ * Modifies a timer that is still awaiting execution to a new expiration time.
+ * The mod_time is added to the old time.  */
+extern void DWC_TIMER_SCHEDULE(dwc_timer_t *timer, uint32_t time);
+#define dwc_timer_schedule DWC_TIMER_SCHEDULE
+
+/** Disables the timer from execution. */
+extern void DWC_TIMER_CANCEL(dwc_timer_t *timer);
+#define dwc_timer_cancel DWC_TIMER_CANCEL
+
+
+/** @name Spinlocks
+ *
+ * These locks are used when the work between the lock/unlock is atomic and
+ * short.  Interrupts are also disabled during the lock/unlock and thus they are
+ * suitable to lock between interrupt/non-interrupt context.  They also lock
+ * between processes if you have multiple CPUs or Preemption.  If you don't have
+ * multiple CPUS or Preemption, then the you can simply implement the
+ * DWC_SPINLOCK and DWC_SPINUNLOCK to disable and enable interrupts.  Because
+ * the work between the lock/unlock is atomic, the process context will never
+ * change, and so you never have to lock between processes.  */
+
+struct dwc_spinlock;
+
+/** Type for a spinlock */
+typedef struct dwc_spinlock dwc_spinlock_t;
+
+/** Type for the 'flags' argument to spinlock funtions */
+typedef unsigned long dwc_irqflags_t;
+
+/** Returns an initialized lock variable.  This function should allocate and
+ * initialize the OS-specific data structure used for locking.  This data
+ * structure is to be used for the DWC_LOCK and DWC_UNLOCK functions and should
+ * be freed by the DWC_FREE_LOCK when it is no longer used.
+ *
+ * For Linux Spinlock Debugging make it macro because the debugging routines use
+ * the symbol name to determine recursive locking. Using a wrapper function
+ * makes it falsely think recursive locking occurs. */
+#if defined(DWC_LINUX) && defined(CONFIG_DEBUG_SPINLOCK)
+#define DWC_SPINLOCK_ALLOC_LINUX_DEBUG(lock) ({ \
+	lock = DWC_ALLOC(sizeof(spinlock_t)); \
+	if (lock) { \
+		spin_lock_init((spinlock_t *)lock); \
+	} \
+})
+#else
+extern dwc_spinlock_t *DWC_SPINLOCK_ALLOC(void);
+#define dwc_spinlock_alloc(_ctx_) DWC_SPINLOCK_ALLOC()
+#endif
+
+/** Frees an initialized lock variable. */
+extern void DWC_SPINLOCK_FREE(dwc_spinlock_t *lock);
+#define dwc_spinlock_free(_ctx_,_lock_) DWC_SPINLOCK_FREE(_lock_)
+
+/** Disables interrupts and blocks until it acquires the lock.
+ *
+ * @param lock Pointer to the spinlock.
+ * @param flags Unsigned long for irq flags storage.
+ */
+extern void DWC_SPINLOCK_IRQSAVE(dwc_spinlock_t *lock, dwc_irqflags_t *flags);
+#define dwc_spinlock_irqsave DWC_SPINLOCK_IRQSAVE
+
+/** Re-enables the interrupt and releases the lock.
+ *
+ * @param lock Pointer to the spinlock.
+ * @param flags Unsigned long for irq flags storage.  Must be the same as was
+ * passed into DWC_LOCK.
+ */
+extern void DWC_SPINUNLOCK_IRQRESTORE(dwc_spinlock_t *lock, dwc_irqflags_t flags);
+#define dwc_spinunlock_irqrestore DWC_SPINUNLOCK_IRQRESTORE
+
+/** Blocks until it acquires the lock.
+ *
+ * @param lock Pointer to the spinlock.
+ */
+extern void DWC_SPINLOCK(dwc_spinlock_t *lock);
+#define dwc_spinlock DWC_SPINLOCK
+
+/** Releases the lock.
+ *
+ * @param lock Pointer to the spinlock.
+ */
+extern void DWC_SPINUNLOCK(dwc_spinlock_t *lock);
+#define dwc_spinunlock DWC_SPINUNLOCK
+
+
+/** @name Mutexes
+ *
+ * Unlike spinlocks Mutexes lock only between processes and the work between the
+ * lock/unlock CAN block, therefore it CANNOT be called from interrupt context.
+ */
+
+struct dwc_mutex;
+
+/** Type for a mutex */
+typedef struct dwc_mutex dwc_mutex_t;
+
+/* For Linux Mutex Debugging make it inline because the debugging routines use
+ * the symbol to determine recursive locking.  This makes it falsely think
+ * recursive locking occurs. */
+#if defined(DWC_LINUX) && defined(CONFIG_DEBUG_MUTEXES)
+#define DWC_MUTEX_ALLOC_LINUX_DEBUG(__mutexp) ({ \
+	__mutexp = (dwc_mutex_t *)DWC_ALLOC(sizeof(struct mutex)); \
+	mutex_init((struct mutex *)__mutexp); \
+})
+#endif
+
+/** Allocate a mutex */
+extern dwc_mutex_t *DWC_MUTEX_ALLOC(void);
+#define dwc_mutex_alloc(_ctx_) DWC_MUTEX_ALLOC()
+
+/* For memory leak debugging when using Linux Mutex Debugging */
+#if defined(DWC_LINUX) && defined(CONFIG_DEBUG_MUTEXES)
+#define DWC_MUTEX_FREE(__mutexp) do { \
+	mutex_destroy((struct mutex *)__mutexp); \
+	DWC_FREE(__mutexp); \
+} while(0)
+#else
+/** Free a mutex */
+extern void DWC_MUTEX_FREE(dwc_mutex_t *mutex);
+#define dwc_mutex_free(_ctx_,_mutex_) DWC_MUTEX_FREE(_mutex_)
+#endif
+
+/** Lock a mutex */
+extern void DWC_MUTEX_LOCK(dwc_mutex_t *mutex);
+#define dwc_mutex_lock DWC_MUTEX_LOCK
+
+/** Non-blocking lock returns 1 on successful lock. */
+extern int DWC_MUTEX_TRYLOCK(dwc_mutex_t *mutex);
+#define dwc_mutex_trylock DWC_MUTEX_TRYLOCK
+
+/** Unlock a mutex */
+extern void DWC_MUTEX_UNLOCK(dwc_mutex_t *mutex);
+#define dwc_mutex_unlock DWC_MUTEX_UNLOCK
+
+
+/** @name Time */
+
+/** Microsecond delay.
+ *
+ * @param usecs  Microseconds to delay.
+ */
+extern void DWC_UDELAY(uint32_t usecs);
+#define dwc_udelay DWC_UDELAY
+
+/** Millisecond delay.
+ *
+ * @param msecs  Milliseconds to delay.
+ */
+extern void DWC_MDELAY(uint32_t msecs);
+#define dwc_mdelay DWC_MDELAY
+
+/** Non-busy waiting.
+ * Sleeps for specified number of milliseconds.
+ *
+ * @param msecs Milliseconds to sleep.
+ */
+extern void DWC_MSLEEP(uint32_t msecs);
+#define dwc_msleep DWC_MSLEEP
+
+/**
+ * Returns number of milliseconds since boot.
+ */
+extern uint32_t DWC_TIME(void);
+#define dwc_time DWC_TIME
+
+
+
+
+/* @mainpage DWC Portability and Common Library
+ *
+ * This is the documentation for the DWC Portability and Common Library.
+ *
+ * @section intro Introduction
+ *
+ * The DWC Portability library consists of wrapper calls and data structures to
+ * all low-level functions which are typically provided by the OS.  The WUDEV
+ * driver uses only these functions.  In order to port the WUDEV driver, only
+ * the functions in this library need to be re-implemented, with the same
+ * behavior as documented here.
+ *
+ * The Common library consists of higher level functions, which rely only on
+ * calling the functions from the DWC Portability library.  These common
+ * routines are shared across modules.  Some of the common libraries need to be
+ * used directly by the driver programmer when porting WUDEV.  Such as the
+ * parameter and notification libraries.
+ *
+ * @section low Portability Library OS Wrapper Functions
+ *
+ * Any function starting with DWC and in all CAPS is a low-level OS-wrapper that
+ * needs to be implemented when porting, for example DWC_MUTEX_ALLOC().  All of
+ * these functions are included in the dwc_os.h file.
+ *
+ * There are many functions here covering a wide array of OS services.  Please
+ * see dwc_os.h for details, and implementation notes for each function.
+ *
+ * @section common Common Library Functions
+ *
+ * Any function starting with dwc and in all lowercase is a common library
+ * routine.  These functions have a portable implementation and do not need to
+ * be reimplemented when porting.  The common routines can be used by any
+ * driver, and some must be used by the end user to control the drivers.  For
+ * example, you must use the Parameter common library in order to set the
+ * parameters in the WUDEV module.
+ *
+ * The common libraries consist of the following:
+ *
+ * - Connection Contexts - Used internally and can be used by end-user.  See dwc_cc.h
+ * - Parameters - Used internally and can be used by end-user.  See dwc_params.h
+ * - Notifications - Used internally and can be used by end-user.  See dwc_notifier.h
+ * - Lists - Used internally and can be used by end-user.  See dwc_list.h
+ * - Memory Debugging - Used internally and can be used by end-user.  See dwc_os.h
+ * - Modpow - Used internally only.  See dwc_modpow.h
+ * - DH - Used internally only.  See dwc_dh.h
+ * - Crypto - Used internally only.  See dwc_crypto.h
+ *
+ *
+ * @section prereq Prerequistes For dwc_os.h
+ * @subsection types Data Types
+ *
+ * The dwc_os.h file assumes that several low-level data types are pre defined for the
+ * compilation environment.  These data types are:
+ *
+ * - uint8_t - unsigned 8-bit data type
+ * - int8_t - signed 8-bit data type
+ * - uint16_t - unsigned 16-bit data type
+ * - int16_t - signed 16-bit data type
+ * - uint32_t - unsigned 32-bit data type
+ * - int32_t - signed 32-bit data type
+ * - uint64_t - unsigned 64-bit data type
+ * - int64_t - signed 64-bit data type
+ *
+ * Ensure that these are defined before using dwc_os.h.  The easiest way to do
+ * that is to modify the top of the file to include the appropriate header.
+ * This is already done for the Linux environment.  If the DWC_LINUX macro is
+ * defined, the correct header will be added.  A standard header <stdint.h> is
+ * also used for environments where standard C headers are available.
+ *
+ * @subsection stdarg Variable Arguments
+ *
+ * Variable arguments are provided by a standard C header <stdarg.h>.  it is
+ * available in Both the Linux and ANSI C enviornment.  An equivalent must be
+ * provided in your enviornment in order to use dwc_os.h with the debug and
+ * tracing message functionality.
+ *
+ * @subsection thread Threading
+ *
+ * WUDEV Core must be run on an operating system that provides for multiple
+ * threads/processes.  Threading can be implemented in many ways, even in
+ * embedded systems without an operating system.  At the bare minimum, the
+ * system should be able to start any number of processes at any time to handle
+ * special work.  It need not be a pre-emptive system.  Process context can
+ * change upon a call to a blocking function.  The hardware interrupt context
+ * that calls the module's ISR() function must be differentiable from process
+ * context, even if your processes are impemented via a hardware interrupt.
+ * Further locking mechanism between process must exist (or be implemented), and
+ * process context must have a way to disable interrupts for a period of time to
+ * lock them out.  If all of this exists, the functions in dwc_os.h related to
+ * threading should be able to be implemented with the defined behavior.
+ *
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _DWC_OS_H_ */
diff --git a/drivers/usb/host/dwc_common_port/usb.h b/drivers/usb/host/dwc_common_port/usb.h
new file mode 100644
index 000000000000..b1cedb1876b7
--- /dev/null
+++ b/drivers/usb/host/dwc_common_port/usb.h
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 1998 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Lennart Augustsson (lennart@augustsson.net) at
+ * Carlstedt Research & Technology.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* Modified by Synopsys, Inc, 12/12/2007 */
+
+
+#ifndef _USB_H_
+#define _USB_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * The USB records contain some unaligned little-endian word
+ * components.  The U[SG]ETW macros take care of both the alignment
+ * and endian problem and should always be used to access non-byte
+ * values.
+ */
+typedef u_int8_t uByte;
+typedef u_int8_t uWord[2];
+typedef u_int8_t uDWord[4];
+
+#define UGETW(w) ((w)[0] | ((w)[1] << 8))
+#define USETW(w,v) ((w)[0] = (u_int8_t)(v), (w)[1] = (u_int8_t)((v) >> 8))
+#define UGETDW(w) ((w)[0] | ((w)[1] << 8) | ((w)[2] << 16) | ((w)[3] << 24))
+#define USETDW(w,v) ((w)[0] = (u_int8_t)(v), \
+		     (w)[1] = (u_int8_t)((v) >> 8), \
+		     (w)[2] = (u_int8_t)((v) >> 16), \
+		     (w)[3] = (u_int8_t)((v) >> 24))
+
+#define UPACKED __attribute__((__packed__))
+
+typedef struct {
+	uByte		bmRequestType;
+	uByte		bRequest;
+	uWord		wValue;
+	uWord		wIndex;
+	uWord		wLength;
+} UPACKED usb_device_request_t;
+
+#define UT_GET_DIR(a) ((a) & 0x80)
+#define UT_WRITE		0x00
+#define UT_READ			0x80
+
+#define UT_GET_TYPE(a) ((a) & 0x60)
+#define UT_STANDARD		0x00
+#define UT_CLASS		0x20
+#define UT_VENDOR		0x40
+
+#define UT_GET_RECIPIENT(a) ((a) & 0x1f)
+#define UT_DEVICE		0x00
+#define UT_INTERFACE		0x01
+#define UT_ENDPOINT		0x02
+#define UT_OTHER		0x03
+
+/* Requests */
+#define UR_GET_STATUS		0x00
+#define  USTAT_STANDARD_STATUS  0x00
+#define  WUSTAT_WUSB_FEATURE    0x01
+#define  WUSTAT_CHANNEL_INFO    0x02
+#define  WUSTAT_RECEIVED_DATA   0x03
+#define  WUSTAT_MAS_AVAILABILITY 0x04
+#define  WUSTAT_CURRENT_TRANSMIT_POWER 0x05
+#define UR_CLEAR_FEATURE	0x01
+#define UR_SET_FEATURE		0x03
+#define UR_SET_AND_TEST_FEATURE 0x0c
+#define UR_SET_ADDRESS		0x05
+#define UR_GET_DESCRIPTOR	0x06
+#define  UDESC_DEVICE		0x01
+#define  UDESC_CONFIG		0x02
+#define  UDESC_STRING		0x03
+#define  UDESC_INTERFACE	0x04
+#define  UDESC_ENDPOINT		0x05
+#define  UDESC_SS_USB_COMPANION	0x30
+#define  UDESC_DEVICE_QUALIFIER	0x06
+#define  UDESC_OTHER_SPEED_CONFIGURATION 0x07
+#define  UDESC_INTERFACE_POWER	0x08
+#define  UDESC_OTG		0x09
+#define  WUDESC_SECURITY	0x0c
+#define  WUDESC_KEY		0x0d
+#define   WUD_GET_KEY_INDEX(_wValue_) ((_wValue_) & 0xf)
+#define   WUD_GET_KEY_TYPE(_wValue_) (((_wValue_) & 0x30) >> 4)
+#define    WUD_KEY_TYPE_ASSOC    0x01
+#define    WUD_KEY_TYPE_GTK      0x02
+#define   WUD_GET_KEY_ORIGIN(_wValue_) (((_wValue_) & 0x40) >> 6)
+#define    WUD_KEY_ORIGIN_HOST   0x00
+#define    WUD_KEY_ORIGIN_DEVICE 0x01
+#define  WUDESC_ENCRYPTION_TYPE	0x0e
+#define  WUDESC_BOS		0x0f
+#define  WUDESC_DEVICE_CAPABILITY 0x10
+#define  WUDESC_WIRELESS_ENDPOINT_COMPANION 0x11
+#define  UDESC_BOS		0x0f
+#define  UDESC_DEVICE_CAPABILITY 0x10
+#define  UDESC_CS_DEVICE	0x21	/* class specific */
+#define  UDESC_CS_CONFIG	0x22
+#define  UDESC_CS_STRING	0x23
+#define  UDESC_CS_INTERFACE	0x24
+#define  UDESC_CS_ENDPOINT	0x25
+#define  UDESC_HUB		0x29
+#define UR_SET_DESCRIPTOR	0x07
+#define UR_GET_CONFIG		0x08
+#define UR_SET_CONFIG		0x09
+#define UR_GET_INTERFACE	0x0a
+#define UR_SET_INTERFACE	0x0b
+#define UR_SYNCH_FRAME		0x0c
+#define WUR_SET_ENCRYPTION      0x0d
+#define WUR_GET_ENCRYPTION	0x0e
+#define WUR_SET_HANDSHAKE	0x0f
+#define WUR_GET_HANDSHAKE	0x10
+#define WUR_SET_CONNECTION	0x11
+#define WUR_SET_SECURITY_DATA	0x12
+#define WUR_GET_SECURITY_DATA	0x13
+#define WUR_SET_WUSB_DATA	0x14
+#define  WUDATA_DRPIE_INFO	0x01
+#define  WUDATA_TRANSMIT_DATA	0x02
+#define  WUDATA_TRANSMIT_PARAMS	0x03
+#define  WUDATA_RECEIVE_PARAMS	0x04
+#define  WUDATA_TRANSMIT_POWER	0x05
+#define WUR_LOOPBACK_DATA_WRITE	0x15
+#define WUR_LOOPBACK_DATA_READ	0x16
+#define WUR_SET_INTERFACE_DS	0x17
+
+/* Feature numbers */
+#define UF_ENDPOINT_HALT	0
+#define UF_DEVICE_REMOTE_WAKEUP	1
+#define UF_TEST_MODE		2
+#define UF_DEVICE_B_HNP_ENABLE	3
+#define UF_DEVICE_A_HNP_SUPPORT	4
+#define UF_DEVICE_A_ALT_HNP_SUPPORT 5
+#define WUF_WUSB		3
+#define  WUF_TX_DRPIE		0x0
+#define  WUF_DEV_XMIT_PACKET	0x1
+#define  WUF_COUNT_PACKETS	0x2
+#define  WUF_CAPTURE_PACKETS	0x3
+#define UF_FUNCTION_SUSPEND	0
+#define UF_U1_ENABLE		48
+#define UF_U2_ENABLE		49
+#define UF_LTM_ENABLE		50
+
+/* Class requests from the USB 2.0 hub spec, table 11-15 */
+#define UCR_CLEAR_HUB_FEATURE		(0x2000 | UR_CLEAR_FEATURE)
+#define UCR_CLEAR_PORT_FEATURE		(0x2300 | UR_CLEAR_FEATURE)
+#define UCR_GET_HUB_DESCRIPTOR		(0xa000 | UR_GET_DESCRIPTOR)
+#define UCR_GET_HUB_STATUS		(0xa000 | UR_GET_STATUS)
+#define UCR_GET_PORT_STATUS		(0xa300 | UR_GET_STATUS)
+#define UCR_SET_HUB_FEATURE		(0x2000 | UR_SET_FEATURE)
+#define UCR_SET_PORT_FEATURE		(0x2300 | UR_SET_FEATURE)
+#define UCR_SET_AND_TEST_PORT_FEATURE	(0xa300 | UR_SET_AND_TEST_FEATURE)
+
+#ifdef _MSC_VER
+#include <pshpack1.h>
+#endif
+
+typedef struct {
+	uByte		bLength;
+	uByte		bDescriptorType;
+	uByte		bEndpointAddress;
+#define UE_GET_DIR(a)	((a) & 0x80)
+#define UE_SET_DIR(a,d)	((a) | (((d)&1) << 7))
+#define UE_DIR_IN	0x80
+#define UE_DIR_OUT	0x00
+#define UE_ADDR		0x0f
+#define UE_GET_ADDR(a)	((a) & UE_ADDR)
+	uByte		bmAttributes;
+#define UE_XFERTYPE	0x03
+#define  UE_CONTROL	0x00
+#define  UE_ISOCHRONOUS	0x01
+#define  UE_BULK	0x02
+#define  UE_INTERRUPT	0x03
+#define UE_GET_XFERTYPE(a)	((a) & UE_XFERTYPE)
+#define UE_ISO_TYPE	0x0c
+#define  UE_ISO_ASYNC	0x04
+#define  UE_ISO_ADAPT	0x08
+#define  UE_ISO_SYNC	0x0c
+#define UE_GET_ISO_TYPE(a)	((a) & UE_ISO_TYPE)
+	uWord		wMaxPacketSize;
+	uByte		bInterval;
+} UPACKED usb_endpoint_descriptor_t;
+#define USB_ENDPOINT_DESCRIPTOR_SIZE 7
+
+/* Hub specific request */
+#define UR_GET_BUS_STATE	0x02
+#define UR_CLEAR_TT_BUFFER	0x08
+#define UR_RESET_TT		0x09
+#define UR_GET_TT_STATE		0x0a
+#define UR_STOP_TT		0x0b
+
+/* Hub features */
+#define UHF_C_HUB_LOCAL_POWER	0
+#define UHF_C_HUB_OVER_CURRENT	1
+#define UHF_PORT_CONNECTION	0
+#define UHF_PORT_ENABLE		1
+#define UHF_PORT_SUSPEND	2
+#define UHF_PORT_OVER_CURRENT	3
+#define UHF_PORT_RESET		4
+#define UHF_PORT_L1		5
+#define UHF_PORT_POWER		8
+#define UHF_PORT_LOW_SPEED	9
+#define UHF_PORT_HIGH_SPEED	10
+#define UHF_C_PORT_CONNECTION	16
+#define UHF_C_PORT_ENABLE	17
+#define UHF_C_PORT_SUSPEND	18
+#define UHF_C_PORT_OVER_CURRENT	19
+#define UHF_C_PORT_RESET	20
+#define UHF_C_PORT_L1		23
+#define UHF_PORT_TEST		21
+#define UHF_PORT_INDICATOR	22
+
+typedef struct {
+	uByte		bDescLength;
+	uByte		bDescriptorType;
+	uByte		bNbrPorts;
+	uWord		wHubCharacteristics;
+#define UHD_PWR			0x0003
+#define  UHD_PWR_GANGED		0x0000
+#define  UHD_PWR_INDIVIDUAL	0x0001
+#define  UHD_PWR_NO_SWITCH	0x0002
+#define UHD_COMPOUND		0x0004
+#define UHD_OC			0x0018
+#define  UHD_OC_GLOBAL		0x0000
+#define  UHD_OC_INDIVIDUAL	0x0008
+#define  UHD_OC_NONE		0x0010
+#define UHD_TT_THINK		0x0060
+#define  UHD_TT_THINK_8		0x0000
+#define  UHD_TT_THINK_16	0x0020
+#define  UHD_TT_THINK_24	0x0040
+#define  UHD_TT_THINK_32	0x0060
+#define UHD_PORT_IND		0x0080
+	uByte		bPwrOn2PwrGood;	/* delay in 2 ms units */
+#define UHD_PWRON_FACTOR 2
+	uByte		bHubContrCurrent;
+	uByte		DeviceRemovable[32]; /* max 255 ports */
+#define UHD_NOT_REMOV(desc, i) \
+    (((desc)->DeviceRemovable[(i)/8] >> ((i) % 8)) & 1)
+	/* deprecated */ uByte		PortPowerCtrlMask[1];
+} UPACKED usb_hub_descriptor_t;
+#define USB_HUB_DESCRIPTOR_SIZE 9 /* includes deprecated PortPowerCtrlMask */
+
+#ifdef _MSC_VER
+#include <poppack.h>
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _USB_H_ */
diff --git a/drivers/usb/host/dwc_otg/Makefile b/drivers/usb/host/dwc_otg/Makefile
new file mode 100644
index 000000000000..8cbe3e684f26
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/Makefile
@@ -0,0 +1,86 @@
+#
+# Makefile for DWC_otg Highspeed USB controller driver
+#
+
+ifneq ($(KERNELRELEASE),)
+
+# Use the BUS_INTERFACE variable to compile the software for either
+# PCI(PCI_INTERFACE) or LM(LM_INTERFACE) bus.
+ifeq ($(BUS_INTERFACE),)
+#	BUS_INTERFACE = -DPCI_INTERFACE
+#	BUS_INTERFACE = -DLM_INTERFACE
+        BUS_INTERFACE = -DPLATFORM_INTERFACE
+endif
+
+#ccflags-y	+= -DDEBUG
+#ccflags-y	+= -DDWC_OTG_DEBUGLEV=1 # reduce common debug msgs
+
+# Use one of the following flags to compile the software in host-only or
+# device-only mode.
+#ccflags-y        += -DDWC_HOST_ONLY
+#ccflags-y        += -DDWC_DEVICE_ONLY
+
+ccflags-y	+= -Dlinux -DDWC_HS_ELECT_TST
+#ccflags-y	+= -DDWC_EN_ISOC
+ccflags-y   	+= -I$(srctree)/drivers/usb/host/dwc_common_port
+#ccflags-y   	+= -I$(PORTLIB)
+ccflags-y   	+= -DDWC_LINUX
+ccflags-y   	+= $(CFI)
+ccflags-y	+= $(BUS_INTERFACE)
+#ccflags-y	+= -DDWC_DEV_SRPCAP
+CFLAGS_dwc_otg_fiq_fsm.o += -fno-stack-protector
+
+obj-$(CONFIG_USB_DWCOTG) += dwc_otg.o
+
+dwc_otg-objs	:= dwc_otg_driver.o dwc_otg_attr.o
+dwc_otg-objs	+= dwc_otg_cil.o dwc_otg_cil_intr.o
+dwc_otg-objs	+= dwc_otg_pcd_linux.o dwc_otg_pcd.o dwc_otg_pcd_intr.o
+dwc_otg-objs	+= dwc_otg_hcd.o dwc_otg_hcd_linux.o dwc_otg_hcd_intr.o dwc_otg_hcd_queue.o dwc_otg_hcd_ddma.o
+dwc_otg-objs	+= dwc_otg_adp.o
+dwc_otg-objs	+= dwc_otg_fiq_fsm.o
+ifneq ($(CONFIG_ARM64),y)
+dwc_otg-objs	+= dwc_otg_fiq_stub.o
+endif
+
+ifneq ($(CFI),)
+dwc_otg-objs	+= dwc_otg_cfi.o
+endif
+
+kernrelwd := $(subst ., ,$(KERNELRELEASE))
+kernrel3 := $(word 1,$(kernrelwd)).$(word 2,$(kernrelwd)).$(word 3,$(kernrelwd))
+
+ifneq ($(kernrel3),2.6.20)
+ccflags-y += $(CPPFLAGS)
+endif
+
+else
+
+PWD		:= $(shell pwd)
+PORTLIB		:= $(PWD)/../dwc_common_port
+
+# Command paths
+CTAGS		:= $(CTAGS)
+DOXYGEN		:= $(DOXYGEN)
+
+default: portlib
+	$(MAKE) -C$(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules
+
+install: default
+	$(MAKE) -C$(KDIR) M=$(PORTLIB) modules_install
+	$(MAKE) -C$(KDIR) M=$(PWD) modules_install
+
+portlib:
+	$(MAKE) -C$(KDIR) M=$(PORTLIB) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules
+	cp $(PORTLIB)/Module.symvers $(PWD)/
+
+docs:	$(wildcard *.[hc]) doc/doxygen.cfg
+	$(DOXYGEN) doc/doxygen.cfg
+
+tags:	$(wildcard *.[hc])
+	$(CTAGS) -e $(wildcard *.[hc]) $(wildcard linux/*.[hc]) $(wildcard $(KDIR)/include/linux/usb*.h)
+
+
+clean:
+	rm -rf   *.o *.ko .*cmd *.mod.c .tmp_versions Module.symvers
+
+endif
diff --git a/drivers/usb/host/dwc_otg/doc/doxygen.cfg b/drivers/usb/host/dwc_otg/doc/doxygen.cfg
new file mode 100644
index 000000000000..712b057ef7c2
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/doc/doxygen.cfg
@@ -0,0 +1,224 @@
+# Doxyfile 1.3.9.1
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+PROJECT_NAME           = "DesignWare USB 2.0 OTG Controller (DWC_otg) Device Driver"
+PROJECT_NUMBER         = v3.00a
+OUTPUT_DIRECTORY       = ./doc/
+CREATE_SUBDIRS         = NO
+OUTPUT_LANGUAGE        = English
+BRIEF_MEMBER_DESC      = YES
+REPEAT_BRIEF           = YES
+ABBREVIATE_BRIEF       = "The $name class" \
+                         "The $name widget" \
+                         "The $name file" \
+                         is \
+                         provides \
+                         specifies \
+                         contains \
+                         represents \
+                         a \
+                         an \
+                         the
+ALWAYS_DETAILED_SEC    = NO
+INLINE_INHERITED_MEMB  = NO
+FULL_PATH_NAMES        = NO
+STRIP_FROM_PATH        =
+STRIP_FROM_INC_PATH    =
+SHORT_NAMES            = NO
+JAVADOC_AUTOBRIEF      = YES
+MULTILINE_CPP_IS_BRIEF = NO
+INHERIT_DOCS           = YES
+DISTRIBUTE_GROUP_DOC   = NO
+TAB_SIZE               = 8
+ALIASES                =
+OPTIMIZE_OUTPUT_FOR_C  = YES
+OPTIMIZE_OUTPUT_JAVA   = NO
+SUBGROUPING            = YES
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+EXTRACT_ALL            = NO
+EXTRACT_PRIVATE        = YES
+EXTRACT_STATIC         = YES
+EXTRACT_LOCAL_CLASSES  = YES
+EXTRACT_LOCAL_METHODS  = NO
+HIDE_UNDOC_MEMBERS     = NO
+HIDE_UNDOC_CLASSES     = NO
+HIDE_FRIEND_COMPOUNDS  = NO
+HIDE_IN_BODY_DOCS      = NO
+INTERNAL_DOCS          = NO
+CASE_SENSE_NAMES       = NO
+HIDE_SCOPE_NAMES       = NO
+SHOW_INCLUDE_FILES     = YES
+INLINE_INFO            = YES
+SORT_MEMBER_DOCS       = NO
+SORT_BRIEF_DOCS        = NO
+SORT_BY_SCOPE_NAME     = NO
+GENERATE_TODOLIST      = YES
+GENERATE_TESTLIST      = YES
+GENERATE_BUGLIST       = YES
+GENERATE_DEPRECATEDLIST= YES
+ENABLED_SECTIONS       =
+MAX_INITIALIZER_LINES  = 30
+SHOW_USED_FILES        = YES
+SHOW_DIRECTORIES       = YES
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+QUIET                  = YES
+WARNINGS               = YES
+WARN_IF_UNDOCUMENTED   = NO
+WARN_IF_DOC_ERROR      = YES
+WARN_FORMAT            = "$file:$line: $text"
+WARN_LOGFILE           =
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+INPUT                  = .
+FILE_PATTERNS          = *.c \
+                         *.h \
+                         ./linux/*.c \
+                         ./linux/*.h
+RECURSIVE              = NO
+EXCLUDE                = ./test/ \
+                         ./dwc_otg/.AppleDouble/
+EXCLUDE_SYMLINKS       = YES
+EXCLUDE_PATTERNS       = *.mod.*
+EXAMPLE_PATH           =
+EXAMPLE_PATTERNS       = *
+EXAMPLE_RECURSIVE      = NO
+IMAGE_PATH             =
+INPUT_FILTER           =
+FILTER_PATTERNS        =
+FILTER_SOURCE_FILES    = NO
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+SOURCE_BROWSER         = YES
+INLINE_SOURCES         = NO
+STRIP_CODE_COMMENTS    = YES
+REFERENCED_BY_RELATION = NO
+REFERENCES_RELATION    = NO
+VERBATIM_HEADERS       = NO
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+ALPHABETICAL_INDEX     = NO
+COLS_IN_ALPHA_INDEX    = 5
+IGNORE_PREFIX          =
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+GENERATE_HTML          = YES
+HTML_OUTPUT            = html
+HTML_FILE_EXTENSION    = .html
+HTML_HEADER            =
+HTML_FOOTER            =
+HTML_STYLESHEET        =
+HTML_ALIGN_MEMBERS     = YES
+GENERATE_HTMLHELP      = NO
+CHM_FILE               =
+HHC_LOCATION           =
+GENERATE_CHI           = NO
+BINARY_TOC             = NO
+TOC_EXPAND             = NO
+DISABLE_INDEX          = NO
+ENUM_VALUES_PER_LINE   = 4
+GENERATE_TREEVIEW      = YES
+TREEVIEW_WIDTH         = 250
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+GENERATE_LATEX         = NO
+LATEX_OUTPUT           = latex
+LATEX_CMD_NAME         = latex
+MAKEINDEX_CMD_NAME     = makeindex
+COMPACT_LATEX          = NO
+PAPER_TYPE             = a4wide
+EXTRA_PACKAGES         =
+LATEX_HEADER           =
+PDF_HYPERLINKS         = NO
+USE_PDFLATEX           = NO
+LATEX_BATCHMODE        = NO
+LATEX_HIDE_INDICES     = NO
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+GENERATE_RTF           = NO
+RTF_OUTPUT             = rtf
+COMPACT_RTF            = NO
+RTF_HYPERLINKS         = NO
+RTF_STYLESHEET_FILE    =
+RTF_EXTENSIONS_FILE    =
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+GENERATE_MAN           = NO
+MAN_OUTPUT             = man
+MAN_EXTENSION          = .3
+MAN_LINKS              = NO
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+GENERATE_XML           = NO
+XML_OUTPUT             = xml
+XML_SCHEMA             =
+XML_DTD                =
+XML_PROGRAMLISTING     = YES
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+GENERATE_AUTOGEN_DEF   = NO
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+GENERATE_PERLMOD       = NO
+PERLMOD_LATEX          = NO
+PERLMOD_PRETTY         = YES
+PERLMOD_MAKEVAR_PREFIX =
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+ENABLE_PREPROCESSING   = YES
+MACRO_EXPANSION        = YES
+EXPAND_ONLY_PREDEF     = YES
+SEARCH_INCLUDES        = YES
+INCLUDE_PATH           =
+INCLUDE_FILE_PATTERNS  =
+PREDEFINED             = DEVICE_ATTR DWC_EN_ISOC
+EXPAND_AS_DEFINED      = DWC_OTG_DEVICE_ATTR_BITFIELD_SHOW DWC_OTG_DEVICE_ATTR_BITFIELD_STORE DWC_OTG_DEVICE_ATTR_BITFIELD_RW DWC_OTG_DEVICE_ATTR_BITFIELD_RO DWC_OTG_DEVICE_ATTR_REG_SHOW DWC_OTG_DEVICE_ATTR_REG_STORE DWC_OTG_DEVICE_ATTR_REG32_RW DWC_OTG_DEVICE_ATTR_REG32_RO DWC_EN_ISOC
+SKIP_FUNCTION_MACROS   = NO
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+TAGFILES               =
+GENERATE_TAGFILE       =
+ALLEXTERNALS           = NO
+EXTERNAL_GROUPS        = YES
+PERL_PATH              = /usr/bin/perl
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+CLASS_DIAGRAMS         = YES
+HIDE_UNDOC_RELATIONS   = YES
+HAVE_DOT               = NO
+CLASS_GRAPH            = YES
+COLLABORATION_GRAPH    = YES
+UML_LOOK               = NO
+TEMPLATE_RELATIONS     = NO
+INCLUDE_GRAPH          = YES
+INCLUDED_BY_GRAPH      = YES
+CALL_GRAPH             = NO
+GRAPHICAL_HIERARCHY    = YES
+DOT_IMAGE_FORMAT       = png
+DOT_PATH               =
+DOTFILE_DIRS           =
+MAX_DOT_GRAPH_DEPTH    = 1000
+GENERATE_LEGEND        = YES
+DOT_CLEANUP            = YES
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine
+#---------------------------------------------------------------------------
+SEARCHENGINE           = NO
diff --git a/drivers/usb/host/dwc_otg/dummy_audio.c b/drivers/usb/host/dwc_otg/dummy_audio.c
new file mode 100644
index 000000000000..d1d03da60e38
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dummy_audio.c
@@ -0,0 +1,1574 @@
+/*
+ * zero.c -- Gadget Zero, for USB development
+ *
+ * Copyright (C) 2003-2004 David Brownell
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The names of the above-listed copyright holders may not be used
+ *    to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * ALTERNATIVELY, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") as published by the Free Software
+ * Foundation, either version 2 of that License or (at your option) any
+ * later version.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+/*
+ * Gadget Zero only needs two bulk endpoints, and is an example of how you
+ * can write a hardware-agnostic gadget driver running inside a USB device.
+ *
+ * Hardware details are visible (see CONFIG_USB_ZERO_* below) but don't
+ * affect most of the driver.
+ *
+ * Use it with the Linux host/master side "usbtest" driver to get a basic
+ * functional test of your device-side usb stack, or with "usb-skeleton".
+ *
+ * It supports two similar configurations.  One sinks whatever the usb host
+ * writes, and in return sources zeroes.  The other loops whatever the host
+ * writes back, so the host can read it.  Module options include:
+ *
+ *   buflen=N		default N=4096, buffer size used
+ *   qlen=N		default N=32, how many buffers in the loopback queue
+ *   loopdefault	default false, list loopback config first
+ *
+ * Many drivers will only have one configuration, letting them be much
+ * simpler if they also don't support high speed operation (like this
+ * driver does).
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/smp_lock.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/timer.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/uts.h>
+#include <linux/version.h>
+#include <linux/device.h>
+#include <linux/moduleparam.h>
+#include <linux/proc_fs.h>
+
+#include <asm/byteorder.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/system.h>
+#include <linux/unaligned.h>
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21)
+# include <linux/usb/ch9.h>
+#else
+# include <linux/usb_ch9.h>
+#endif
+
+#include <linux/usb_gadget.h>
+
+
+/*-------------------------------------------------------------------------*/
+/*-------------------------------------------------------------------------*/
+
+
+static int utf8_to_utf16le(const char *s, u16 *cp, unsigned len)
+{
+	int	count = 0;
+	u8	c;
+	u16	uchar;
+
+	/* this insists on correct encodings, though not minimal ones.
+	 * BUT it currently rejects legit 4-byte UTF-8 code points,
+	 * which need surrogate pairs.  (Unicode 3.1 can use them.)
+	 */
+	while (len != 0 && (c = (u8) *s++) != 0) {
+		if (unlikely(c & 0x80)) {
+			// 2-byte sequence:
+			// 00000yyyyyxxxxxx = 110yyyyy 10xxxxxx
+			if ((c & 0xe0) == 0xc0) {
+				uchar = (c & 0x1f) << 6;
+
+				c = (u8) *s++;
+				if ((c & 0xc0) != 0xc0)
+					goto fail;
+				c &= 0x3f;
+				uchar |= c;
+
+			// 3-byte sequence (most CJKV characters):
+			// zzzzyyyyyyxxxxxx = 1110zzzz 10yyyyyy 10xxxxxx
+			} else if ((c & 0xf0) == 0xe0) {
+				uchar = (c & 0x0f) << 12;
+
+				c = (u8) *s++;
+				if ((c & 0xc0) != 0xc0)
+					goto fail;
+				c &= 0x3f;
+				uchar |= c << 6;
+
+				c = (u8) *s++;
+				if ((c & 0xc0) != 0xc0)
+					goto fail;
+				c &= 0x3f;
+				uchar |= c;
+
+				/* no bogus surrogates */
+				if (0xd800 <= uchar && uchar <= 0xdfff)
+					goto fail;
+
+			// 4-byte sequence (surrogate pairs, currently rare):
+			// 11101110wwwwzzzzyy + 110111yyyyxxxxxx
+			//     = 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx
+			// (uuuuu = wwww + 1)
+			// FIXME accept the surrogate code points (only)
+
+			} else
+				goto fail;
+		} else
+			uchar = c;
+		put_unaligned (cpu_to_le16 (uchar), cp++);
+		count++;
+		len--;
+	}
+	return count;
+fail:
+	return -1;
+}
+
+
+/**
+ * usb_gadget_get_string - fill out a string descriptor
+ * @table: of c strings encoded using UTF-8
+ * @id: string id, from low byte of wValue in get string descriptor
+ * @buf: at least 256 bytes
+ *
+ * Finds the UTF-8 string matching the ID, and converts it into a
+ * string descriptor in utf16-le.
+ * Returns length of descriptor (always even) or negative errno
+ *
+ * If your driver needs stings in multiple languages, you'll probably
+ * "switch (wIndex) { ... }"  in your ep0 string descriptor logic,
+ * using this routine after choosing which set of UTF-8 strings to use.
+ * Note that US-ASCII is a strict subset of UTF-8; any string bytes with
+ * the eighth bit set will be multibyte UTF-8 characters, not ISO-8859/1
+ * characters (which are also widely used in C strings).
+ */
+int
+usb_gadget_get_string (struct usb_gadget_strings *table, int id, u8 *buf)
+{
+	struct usb_string	*s;
+	int			len;
+
+	/* descriptor 0 has the language id */
+	if (id == 0) {
+		buf [0] = 4;
+		buf [1] = USB_DT_STRING;
+		buf [2] = (u8) table->language;
+		buf [3] = (u8) (table->language >> 8);
+		return 4;
+	}
+	for (s = table->strings; s && s->s; s++)
+		if (s->id == id)
+			break;
+
+	/* unrecognized: stall. */
+	if (!s || !s->s)
+		return -EINVAL;
+
+	/* string descriptors have length, tag, then UTF16-LE text */
+	len = min ((size_t) 126, strlen (s->s));
+	memset (buf + 2, 0, 2 * len);	/* zero all the bytes */
+	len = utf8_to_utf16le(s->s, (u16 *)&buf[2], len);
+	if (len < 0)
+		return -EINVAL;
+	buf [0] = (len + 1) * 2;
+	buf [1] = USB_DT_STRING;
+	return buf [0];
+}
+
+
+/*-------------------------------------------------------------------------*/
+/*-------------------------------------------------------------------------*/
+
+
+/**
+ * usb_descriptor_fillbuf - fill buffer with descriptors
+ * @buf: Buffer to be filled
+ * @buflen: Size of buf
+ * @src: Array of descriptor pointers, terminated by null pointer.
+ *
+ * Copies descriptors into the buffer, returning the length or a
+ * negative error code if they can't all be copied.  Useful when
+ * assembling descriptors for an associated set of interfaces used
+ * as part of configuring a composite device; or in other cases where
+ * sets of descriptors need to be marshaled.
+ */
+int
+usb_descriptor_fillbuf(void *buf, unsigned buflen,
+		const struct usb_descriptor_header **src)
+{
+	u8	*dest = buf;
+
+	if (!src)
+		return -EINVAL;
+
+	/* fill buffer from src[] until null descriptor ptr */
+	for (; 0 != *src; src++) {
+		unsigned		len = (*src)->bLength;
+
+		if (len > buflen)
+			return -EINVAL;
+		memcpy(dest, *src, len);
+		buflen -= len;
+		dest += len;
+	}
+	return dest - (u8 *)buf;
+}
+
+
+/**
+ * usb_gadget_config_buf - builts a complete configuration descriptor
+ * @config: Header for the descriptor, including characteristics such
+ *	as power requirements and number of interfaces.
+ * @desc: Null-terminated vector of pointers to the descriptors (interface,
+ *	endpoint, etc) defining all functions in this device configuration.
+ * @buf: Buffer for the resulting configuration descriptor.
+ * @length: Length of buffer.  If this is not big enough to hold the
+ *	entire configuration descriptor, an error code will be returned.
+ *
+ * This copies descriptors into the response buffer, building a descriptor
+ * for that configuration.  It returns the buffer length or a negative
+ * status code.  The config.wTotalLength field is set to match the length
+ * of the result, but other descriptor fields (including power usage and
+ * interface count) must be set by the caller.
+ *
+ * Gadget drivers could use this when constructing a config descriptor
+ * in response to USB_REQ_GET_DESCRIPTOR.  They will need to patch the
+ * resulting bDescriptorType value if USB_DT_OTHER_SPEED_CONFIG is needed.
+ */
+int usb_gadget_config_buf(
+	const struct usb_config_descriptor	*config,
+	void					*buf,
+	unsigned				length,
+	const struct usb_descriptor_header	**desc
+)
+{
+	struct usb_config_descriptor		*cp = buf;
+	int					len;
+
+	/* config descriptor first */
+	if (length < USB_DT_CONFIG_SIZE || !desc)
+		return -EINVAL;
+	*cp = *config;
+
+	/* then interface/endpoint/class/vendor/... */
+	len = usb_descriptor_fillbuf(USB_DT_CONFIG_SIZE + (u8*)buf,
+			length - USB_DT_CONFIG_SIZE, desc);
+	if (len < 0)
+		return len;
+	len += USB_DT_CONFIG_SIZE;
+	if (len > 0xffff)
+		return -EINVAL;
+
+	/* patch up the config descriptor */
+	cp->bLength = USB_DT_CONFIG_SIZE;
+	cp->bDescriptorType = USB_DT_CONFIG;
+	cp->wTotalLength = cpu_to_le16(len);
+	cp->bmAttributes |= USB_CONFIG_ATT_ONE;
+	return len;
+}
+
+/*-------------------------------------------------------------------------*/
+/*-------------------------------------------------------------------------*/
+
+
+#define RBUF_LEN (1024*1024)
+static int rbuf_start;
+static int rbuf_len;
+static __u8 rbuf[RBUF_LEN];
+
+/*-------------------------------------------------------------------------*/
+
+#define DRIVER_VERSION		"St Patrick's Day 2004"
+
+static const char shortname [] = "zero";
+static const char longname [] = "YAMAHA YST-MS35D USB Speaker  ";
+
+static const char source_sink [] = "source and sink data";
+static const char loopback [] = "loop input to output";
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * driver assumes self-powered hardware, and
+ * has no way for users to trigger remote wakeup.
+ *
+ * this version autoconfigures as much as possible,
+ * which is reasonable for most "bulk-only" drivers.
+ */
+static const char *EP_IN_NAME;		/* source */
+static const char *EP_OUT_NAME;		/* sink */
+
+/*-------------------------------------------------------------------------*/
+
+/* big enough to hold our biggest descriptor */
+#define USB_BUFSIZ	512
+
+struct zero_dev {
+	spinlock_t		lock;
+	struct usb_gadget	*gadget;
+	struct usb_request	*req;		/* for control responses */
+
+	/* when configured, we have one of two configs:
+	 * - source data (in to host) and sink it (out from host)
+	 * - or loop it back (out from host back in to host)
+	 */
+	u8			config;
+	struct usb_ep		*in_ep, *out_ep;
+
+	/* autoresume timer */
+	struct timer_list	resume;
+};
+
+#define xprintk(d,level,fmt,args...) \
+	dev_printk(level , &(d)->gadget->dev , fmt , ## args)
+
+#ifdef DEBUG
+#define DBG(dev,fmt,args...) \
+	xprintk(dev , KERN_DEBUG , fmt , ## args)
+#else
+#define DBG(dev,fmt,args...) \
+	do { } while (0)
+#endif /* DEBUG */
+
+#ifdef VERBOSE
+#define VDBG	DBG
+#else
+#define VDBG(dev,fmt,args...) \
+	do { } while (0)
+#endif /* VERBOSE */
+
+#define ERROR(dev,fmt,args...) \
+	xprintk(dev , KERN_ERR , fmt , ## args)
+#define WARN(dev,fmt,args...) \
+	xprintk(dev , KERN_WARNING , fmt , ## args)
+#define INFO(dev,fmt,args...) \
+	xprintk(dev , KERN_INFO , fmt , ## args)
+
+/*-------------------------------------------------------------------------*/
+
+static unsigned buflen = 4096;
+static unsigned qlen = 32;
+static unsigned pattern = 0;
+
+module_param (buflen, uint, S_IRUGO|S_IWUSR);
+module_param (qlen, uint, S_IRUGO|S_IWUSR);
+module_param (pattern, uint, S_IRUGO|S_IWUSR);
+
+/*
+ * if it's nonzero, autoresume says how many seconds to wait
+ * before trying to wake up the host after suspend.
+ */
+static unsigned autoresume = 0;
+module_param (autoresume, uint, 0);
+
+/*
+ * Normally the "loopback" configuration is second (index 1) so
+ * it's not the default.  Here's where to change that order, to
+ * work better with hosts where config changes are problematic.
+ * Or controllers (like superh) that only support one config.
+ */
+static int loopdefault = 0;
+
+module_param (loopdefault, bool, S_IRUGO|S_IWUSR);
+
+/*-------------------------------------------------------------------------*/
+
+/* Thanks to NetChip Technologies for donating this product ID.
+ *
+ * DO NOT REUSE THESE IDs with a protocol-incompatible driver!!  Ever!!
+ * Instead:  allocate your own, using normal USB-IF procedures.
+ */
+#ifndef	CONFIG_USB_ZERO_HNPTEST
+#define DRIVER_VENDOR_NUM	0x0525		/* NetChip */
+#define DRIVER_PRODUCT_NUM	0xa4a0		/* Linux-USB "Gadget Zero" */
+#else
+#define DRIVER_VENDOR_NUM	0x1a0a		/* OTG test device IDs */
+#define DRIVER_PRODUCT_NUM	0xbadd
+#endif
+
+/*-------------------------------------------------------------------------*/
+
+/*
+ * DESCRIPTORS ... most are static, but strings and (full)
+ * configuration descriptors are built on demand.
+ */
+
+/*
+#define STRING_MANUFACTURER		25
+#define STRING_PRODUCT			42
+#define STRING_SERIAL			101
+*/
+#define STRING_MANUFACTURER		1
+#define STRING_PRODUCT			2
+#define STRING_SERIAL			3
+
+#define STRING_SOURCE_SINK		250
+#define STRING_LOOPBACK			251
+
+/*
+ * This device advertises two configurations; these numbers work
+ * on a pxa250 as well as more flexible hardware.
+ */
+#define	CONFIG_SOURCE_SINK	3
+#define	CONFIG_LOOPBACK		2
+
+/*
+static struct usb_device_descriptor
+device_desc = {
+	.bLength =		sizeof device_desc,
+	.bDescriptorType =	USB_DT_DEVICE,
+
+	.bcdUSB =		__constant_cpu_to_le16 (0x0200),
+	.bDeviceClass =		USB_CLASS_VENDOR_SPEC,
+
+	.idVendor =		__constant_cpu_to_le16 (DRIVER_VENDOR_NUM),
+	.idProduct =		__constant_cpu_to_le16 (DRIVER_PRODUCT_NUM),
+	.iManufacturer =	STRING_MANUFACTURER,
+	.iProduct =		STRING_PRODUCT,
+	.iSerialNumber =	STRING_SERIAL,
+	.bNumConfigurations =	2,
+};
+*/
+static struct usb_device_descriptor
+device_desc = {
+	.bLength =		sizeof device_desc,
+	.bDescriptorType =	USB_DT_DEVICE,
+	.bcdUSB =		__constant_cpu_to_le16 (0x0100),
+	.bDeviceClass =		USB_CLASS_PER_INTERFACE,
+	.bDeviceSubClass =      0,
+	.bDeviceProtocol =      0,
+	.bMaxPacketSize0 =      64,
+	.bcdDevice =            __constant_cpu_to_le16 (0x0100),
+	.idVendor =		__constant_cpu_to_le16 (0x0499),
+	.idProduct =		__constant_cpu_to_le16 (0x3002),
+	.iManufacturer =	STRING_MANUFACTURER,
+	.iProduct =		STRING_PRODUCT,
+	.iSerialNumber =	STRING_SERIAL,
+	.bNumConfigurations =	1,
+};
+
+static struct usb_config_descriptor
+z_config = {
+	.bLength =		sizeof z_config,
+	.bDescriptorType =	USB_DT_CONFIG,
+
+	/* compute wTotalLength on the fly */
+	.bNumInterfaces =	2,
+	.bConfigurationValue =	1,
+	.iConfiguration =	0,
+	.bmAttributes =		0x40,
+	.bMaxPower =		0,	/* self-powered */
+};
+
+
+static struct usb_otg_descriptor
+otg_descriptor = {
+	.bLength =		sizeof otg_descriptor,
+	.bDescriptorType =	USB_DT_OTG,
+
+	.bmAttributes =		USB_OTG_SRP,
+};
+
+/* one interface in each configuration */
+#ifdef	CONFIG_USB_GADGET_DUALSPEED
+
+/*
+ * usb 2.0 devices need to expose both high speed and full speed
+ * descriptors, unless they only run at full speed.
+ *
+ * that means alternate endpoint descriptors (bigger packets)
+ * and a "device qualifier" ... plus more construction options
+ * for the config descriptor.
+ */
+
+static struct usb_qualifier_descriptor
+dev_qualifier = {
+	.bLength =		sizeof dev_qualifier,
+	.bDescriptorType =	USB_DT_DEVICE_QUALIFIER,
+
+	.bcdUSB =		__constant_cpu_to_le16 (0x0200),
+	.bDeviceClass =		USB_CLASS_VENDOR_SPEC,
+
+	.bNumConfigurations =	2,
+};
+
+
+struct usb_cs_as_general_descriptor {
+	__u8  bLength;
+	__u8  bDescriptorType;
+
+	__u8  bDescriptorSubType;
+	__u8  bTerminalLink;
+	__u8  bDelay;
+	__u16  wFormatTag;
+} __attribute__ ((packed));
+
+struct usb_cs_as_format_descriptor {
+	__u8  bLength;
+	__u8  bDescriptorType;
+
+	__u8  bDescriptorSubType;
+	__u8  bFormatType;
+	__u8  bNrChannels;
+	__u8  bSubframeSize;
+	__u8  bBitResolution;
+	__u8  bSamfreqType;
+	__u8  tLowerSamFreq[3];
+	__u8  tUpperSamFreq[3];
+} __attribute__ ((packed));
+
+static const struct usb_interface_descriptor
+z_audio_control_if_desc = {
+	.bLength =		sizeof z_audio_control_if_desc,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bInterfaceNumber = 0,
+	.bAlternateSetting = 0,
+	.bNumEndpoints = 0,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = 0x1,
+	.bInterfaceProtocol = 0,
+	.iInterface = 0,
+};
+
+static const struct usb_interface_descriptor
+z_audio_if_desc = {
+	.bLength =		sizeof z_audio_if_desc,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bInterfaceNumber = 1,
+	.bAlternateSetting = 0,
+	.bNumEndpoints = 0,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = 0x2,
+	.bInterfaceProtocol = 0,
+	.iInterface = 0,
+};
+
+static const struct usb_interface_descriptor
+z_audio_if_desc2 = {
+	.bLength =		sizeof z_audio_if_desc,
+	.bDescriptorType =	USB_DT_INTERFACE,
+	.bInterfaceNumber = 1,
+	.bAlternateSetting = 1,
+	.bNumEndpoints = 1,
+	.bInterfaceClass = USB_CLASS_AUDIO,
+	.bInterfaceSubClass = 0x2,
+	.bInterfaceProtocol = 0,
+	.iInterface = 0,
+};
+
+static const struct usb_cs_as_general_descriptor
+z_audio_cs_as_if_desc = {
+	.bLength = 7,
+	.bDescriptorType = 0x24,
+
+	.bDescriptorSubType = 0x01,
+	.bTerminalLink = 0x01,
+	.bDelay = 0x0,
+	.wFormatTag = __constant_cpu_to_le16 (0x0001)
+};
+
+
+static const struct usb_cs_as_format_descriptor
+z_audio_cs_as_format_desc = {
+	.bLength = 0xe,
+	.bDescriptorType = 0x24,
+
+	.bDescriptorSubType = 2,
+	.bFormatType = 1,
+	.bNrChannels = 1,
+	.bSubframeSize = 1,
+	.bBitResolution = 8,
+	.bSamfreqType = 0,
+	.tLowerSamFreq = {0x7e, 0x13, 0x00},
+	.tUpperSamFreq = {0xe2, 0xd6, 0x00},
+};
+
+static const struct usb_endpoint_descriptor
+z_iso_ep = {
+	.bLength = 0x09,
+	.bDescriptorType = 0x05,
+	.bEndpointAddress = 0x04,
+	.bmAttributes = 0x09,
+	.wMaxPacketSize = 0x0038,
+	.bInterval = 0x01,
+	.bRefresh = 0x00,
+	.bSynchAddress = 0x00,
+};
+
+static char z_iso_ep2[] = {0x07, 0x25, 0x01, 0x00, 0x02, 0x00, 0x02};
+
+// 9 bytes
+static char z_ac_interface_header_desc[] =
+{ 0x09, 0x24, 0x01, 0x00, 0x01, 0x2b, 0x00, 0x01, 0x01 };
+
+// 12 bytes
+static char z_0[] = {0x0c, 0x24, 0x02, 0x01, 0x01, 0x01, 0x00, 0x02,
+		     0x03, 0x00, 0x00, 0x00};
+// 13 bytes
+static char z_1[] = {0x0d, 0x24, 0x06, 0x02, 0x01, 0x02, 0x15, 0x00,
+		     0x02, 0x00, 0x02, 0x00, 0x00};
+// 9 bytes
+static char z_2[] = {0x09, 0x24, 0x03, 0x03, 0x01, 0x03, 0x00, 0x02,
+		     0x00};
+
+static char za_0[] = {0x09, 0x04, 0x01, 0x02, 0x01, 0x01, 0x02, 0x00,
+		      0x00};
+
+static char za_1[] = {0x07, 0x24, 0x01, 0x01, 0x00, 0x01, 0x00};
+
+static char za_2[] = {0x0e, 0x24, 0x02, 0x01, 0x02, 0x01, 0x08, 0x00,
+		      0x7e, 0x13, 0x00, 0xe2, 0xd6, 0x00};
+
+static char za_3[] = {0x09, 0x05, 0x04, 0x09, 0x70, 0x00, 0x01, 0x00,
+		      0x00};
+
+static char za_4[] = {0x07, 0x25, 0x01, 0x00, 0x02, 0x00, 0x02};
+
+static char za_5[] = {0x09, 0x04, 0x01, 0x03, 0x01, 0x01, 0x02, 0x00,
+		      0x00};
+
+static char za_6[] = {0x07, 0x24, 0x01, 0x01, 0x00, 0x01, 0x00};
+
+static char za_7[] = {0x0e, 0x24, 0x02, 0x01, 0x01, 0x02, 0x10, 0x00,
+		      0x7e, 0x13, 0x00, 0xe2, 0xd6, 0x00};
+
+static char za_8[] = {0x09, 0x05, 0x04, 0x09, 0x70, 0x00, 0x01, 0x00,
+		      0x00};
+
+static char za_9[] = {0x07, 0x25, 0x01, 0x00, 0x02, 0x00, 0x02};
+
+static char za_10[] = {0x09, 0x04, 0x01, 0x04, 0x01, 0x01, 0x02, 0x00,
+		       0x00};
+
+static char za_11[] = {0x07, 0x24, 0x01, 0x01, 0x00, 0x01, 0x00};
+
+static char za_12[] = {0x0e, 0x24, 0x02, 0x01, 0x02, 0x02, 0x10, 0x00,
+		       0x73, 0x13, 0x00, 0xe2, 0xd6, 0x00};
+
+static char za_13[] = {0x09, 0x05, 0x04, 0x09, 0xe0, 0x00, 0x01, 0x00,
+		       0x00};
+
+static char za_14[] = {0x07, 0x25, 0x01, 0x00, 0x02, 0x00, 0x02};
+
+static char za_15[] = {0x09, 0x04, 0x01, 0x05, 0x01, 0x01, 0x02, 0x00,
+		       0x00};
+
+static char za_16[] = {0x07, 0x24, 0x01, 0x01, 0x00, 0x01, 0x00};
+
+static char za_17[] = {0x0e, 0x24, 0x02, 0x01, 0x01, 0x03, 0x14, 0x00,
+		       0x7e, 0x13, 0x00, 0xe2, 0xd6, 0x00};
+
+static char za_18[] = {0x09, 0x05, 0x04, 0x09, 0xa8, 0x00, 0x01, 0x00,
+		       0x00};
+
+static char za_19[] = {0x07, 0x25, 0x01, 0x00, 0x02, 0x00, 0x02};
+
+static char za_20[] = {0x09, 0x04, 0x01, 0x06, 0x01, 0x01, 0x02, 0x00,
+		       0x00};
+
+static char za_21[] = {0x07, 0x24, 0x01, 0x01, 0x00, 0x01, 0x00};
+
+static char za_22[] = {0x0e, 0x24, 0x02, 0x01, 0x02, 0x03, 0x14, 0x00,
+		       0x7e, 0x13, 0x00, 0xe2, 0xd6, 0x00};
+
+static char za_23[] = {0x09, 0x05, 0x04, 0x09, 0x50, 0x01, 0x01, 0x00,
+		       0x00};
+
+static char za_24[] = {0x07, 0x25, 0x01, 0x00, 0x02, 0x00, 0x02};
+
+
+
+static const struct usb_descriptor_header *z_function [] = {
+	(struct usb_descriptor_header *) &z_audio_control_if_desc,
+	(struct usb_descriptor_header *) &z_ac_interface_header_desc,
+	(struct usb_descriptor_header *) &z_0,
+	(struct usb_descriptor_header *) &z_1,
+	(struct usb_descriptor_header *) &z_2,
+	(struct usb_descriptor_header *) &z_audio_if_desc,
+	(struct usb_descriptor_header *) &z_audio_if_desc2,
+	(struct usb_descriptor_header *) &z_audio_cs_as_if_desc,
+	(struct usb_descriptor_header *) &z_audio_cs_as_format_desc,
+	(struct usb_descriptor_header *) &z_iso_ep,
+	(struct usb_descriptor_header *) &z_iso_ep2,
+	(struct usb_descriptor_header *) &za_0,
+	(struct usb_descriptor_header *) &za_1,
+	(struct usb_descriptor_header *) &za_2,
+	(struct usb_descriptor_header *) &za_3,
+	(struct usb_descriptor_header *) &za_4,
+	(struct usb_descriptor_header *) &za_5,
+	(struct usb_descriptor_header *) &za_6,
+	(struct usb_descriptor_header *) &za_7,
+	(struct usb_descriptor_header *) &za_8,
+	(struct usb_descriptor_header *) &za_9,
+	(struct usb_descriptor_header *) &za_10,
+	(struct usb_descriptor_header *) &za_11,
+	(struct usb_descriptor_header *) &za_12,
+	(struct usb_descriptor_header *) &za_13,
+	(struct usb_descriptor_header *) &za_14,
+	(struct usb_descriptor_header *) &za_15,
+	(struct usb_descriptor_header *) &za_16,
+	(struct usb_descriptor_header *) &za_17,
+	(struct usb_descriptor_header *) &za_18,
+	(struct usb_descriptor_header *) &za_19,
+	(struct usb_descriptor_header *) &za_20,
+	(struct usb_descriptor_header *) &za_21,
+	(struct usb_descriptor_header *) &za_22,
+	(struct usb_descriptor_header *) &za_23,
+	(struct usb_descriptor_header *) &za_24,
+	NULL,
+};
+
+/* maxpacket and other transfer characteristics vary by speed. */
+#define ep_desc(g,hs,fs) (((g)->speed==USB_SPEED_HIGH)?(hs):(fs))
+
+#else
+
+/* if there's no high speed support, maxpacket doesn't change. */
+#define ep_desc(g,hs,fs) fs
+
+#endif	/* !CONFIG_USB_GADGET_DUALSPEED */
+
+static char				manufacturer [40];
+//static char				serial [40];
+static char				serial [] = "Ser 00 em";
+
+/* static strings, in UTF-8 */
+static struct usb_string		strings [] = {
+	{ STRING_MANUFACTURER, manufacturer, },
+	{ STRING_PRODUCT, longname, },
+	{ STRING_SERIAL, serial, },
+	{ STRING_LOOPBACK, loopback, },
+	{ STRING_SOURCE_SINK, source_sink, },
+	{  }			/* end of list */
+};
+
+static struct usb_gadget_strings	stringtab = {
+	.language	= 0x0409,	/* en-us */
+	.strings	= strings,
+};
+
+/*
+ * config descriptors are also handcrafted.  these must agree with code
+ * that sets configurations, and with code managing interfaces and their
+ * altsettings.  other complexity may come from:
+ *
+ *  - high speed support, including "other speed config" rules
+ *  - multiple configurations
+ *  - interfaces with alternate settings
+ *  - embedded class or vendor-specific descriptors
+ *
+ * this handles high speed, and has a second config that could as easily
+ * have been an alternate interface setting (on most hardware).
+ *
+ * NOTE:  to demonstrate (and test) more USB capabilities, this driver
+ * should include an altsetting to test interrupt transfers, including
+ * high bandwidth modes at high speed.  (Maybe work like Intel's test
+ * device?)
+ */
+static int
+config_buf (struct usb_gadget *gadget, u8 *buf, u8 type, unsigned index)
+{
+	int len;
+	const struct usb_descriptor_header **function;
+
+	function = z_function;
+	len = usb_gadget_config_buf (&z_config, buf, USB_BUFSIZ, function);
+	if (len < 0)
+		return len;
+	((struct usb_config_descriptor *) buf)->bDescriptorType = type;
+	return len;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static struct usb_request *
+alloc_ep_req (struct usb_ep *ep, unsigned length)
+{
+	struct usb_request	*req;
+
+	req = usb_ep_alloc_request (ep, GFP_ATOMIC);
+	if (req) {
+		req->length = length;
+		req->buf = usb_ep_alloc_buffer (ep, length,
+				&req->dma, GFP_ATOMIC);
+		if (!req->buf) {
+			usb_ep_free_request (ep, req);
+			req = NULL;
+		}
+	}
+	return req;
+}
+
+static void free_ep_req (struct usb_ep *ep, struct usb_request *req)
+{
+	if (req->buf)
+		usb_ep_free_buffer (ep, req->buf, req->dma, req->length);
+	usb_ep_free_request (ep, req);
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* optionally require specific source/sink data patterns  */
+
+static int
+check_read_data (
+	struct zero_dev		*dev,
+	struct usb_ep		*ep,
+	struct usb_request	*req
+)
+{
+	unsigned	i;
+	u8		*buf = req->buf;
+
+	for (i = 0; i < req->actual; i++, buf++) {
+		switch (pattern) {
+		/* all-zeroes has no synchronization issues */
+		case 0:
+			if (*buf == 0)
+				continue;
+			break;
+		/* mod63 stays in sync with short-terminated transfers,
+		 * or otherwise when host and gadget agree on how large
+		 * each usb transfer request should be.  resync is done
+		 * with set_interface or set_config.
+		 */
+		case 1:
+			if (*buf == (u8)(i % 63))
+				continue;
+			break;
+		}
+		ERROR (dev, "bad OUT byte, buf [%d] = %d\n", i, *buf);
+		usb_ep_set_halt (ep);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void zero_reset_config (struct zero_dev *dev)
+{
+	if (dev->config == 0)
+		return;
+
+	DBG (dev, "reset config\n");
+
+	/* just disable endpoints, forcing completion of pending i/o.
+	 * all our completion handlers free their requests in this case.
+	 */
+	if (dev->in_ep) {
+		usb_ep_disable (dev->in_ep);
+		dev->in_ep = NULL;
+	}
+	if (dev->out_ep) {
+		usb_ep_disable (dev->out_ep);
+		dev->out_ep = NULL;
+	}
+	dev->config = 0;
+	del_timer (&dev->resume);
+}
+
+#define _write(f, buf, sz) (f->f_op->write(f, buf, sz, &f->f_pos))
+
+static void
+zero_isoc_complete (struct usb_ep *ep, struct usb_request *req)
+{
+	struct zero_dev	*dev = ep->driver_data;
+	int		status = req->status;
+	int i, j;
+
+	switch (status) {
+
+	case 0: 			/* normal completion? */
+		//printk ("\nzero ---------------> isoc normal completion %d bytes\n", req->actual);
+		for (i=0, j=rbuf_start; i<req->actual; i++) {
+			//printk ("%02x ", ((__u8*)req->buf)[i]);
+			rbuf[j] = ((__u8*)req->buf)[i];
+			j++;
+			if (j >= RBUF_LEN) j=0;
+		}
+		rbuf_start = j;
+		//printk ("\n\n");
+
+		if (rbuf_len < RBUF_LEN) {
+			rbuf_len += req->actual;
+			if (rbuf_len > RBUF_LEN) {
+				rbuf_len = RBUF_LEN;
+			}
+		}
+
+		break;
+
+	/* this endpoint is normally active while we're configured */
+	case -ECONNABORTED: 		/* hardware forced ep reset */
+	case -ECONNRESET:		/* request dequeued */
+	case -ESHUTDOWN:		/* disconnect from host */
+		VDBG (dev, "%s gone (%d), %d/%d\n", ep->name, status,
+				req->actual, req->length);
+		if (ep == dev->out_ep)
+			check_read_data (dev, ep, req);
+		free_ep_req (ep, req);
+		return;
+
+	case -EOVERFLOW:		/* buffer overrun on read means that
+					 * we didn't provide a big enough
+					 * buffer.
+					 */
+	default:
+#if 1
+		DBG (dev, "%s complete --> %d, %d/%d\n", ep->name,
+				status, req->actual, req->length);
+#endif
+	case -EREMOTEIO:		/* short read */
+		break;
+	}
+
+	status = usb_ep_queue (ep, req, GFP_ATOMIC);
+	if (status) {
+		ERROR (dev, "kill %s:  resubmit %d bytes --> %d\n",
+				ep->name, req->length, status);
+		usb_ep_set_halt (ep);
+		/* FIXME recover later ... somehow */
+	}
+}
+
+static struct usb_request *
+zero_start_isoc_ep (struct usb_ep *ep, int gfp_flags)
+{
+	struct usb_request	*req;
+	int			status;
+
+	req = alloc_ep_req (ep, 512);
+	if (!req)
+		return NULL;
+
+	req->complete = zero_isoc_complete;
+
+	status = usb_ep_queue (ep, req, gfp_flags);
+	if (status) {
+		struct zero_dev	*dev = ep->driver_data;
+
+		ERROR (dev, "start %s --> %d\n", ep->name, status);
+		free_ep_req (ep, req);
+		req = NULL;
+	}
+
+	return req;
+}
+
+/* change our operational config.  this code must agree with the code
+ * that returns config descriptors, and altsetting code.
+ *
+ * it's also responsible for power management interactions. some
+ * configurations might not work with our current power sources.
+ *
+ * note that some device controller hardware will constrain what this
+ * code can do, perhaps by disallowing more than one configuration or
+ * by limiting configuration choices (like the pxa2xx).
+ */
+static int
+zero_set_config (struct zero_dev *dev, unsigned number, int gfp_flags)
+{
+	int			result = 0;
+	struct usb_gadget	*gadget = dev->gadget;
+	const struct usb_endpoint_descriptor	*d;
+	struct usb_ep		*ep;
+
+	if (number == dev->config)
+		return 0;
+
+	zero_reset_config (dev);
+
+	gadget_for_each_ep (ep, gadget) {
+
+		if (strcmp (ep->name, "ep4") == 0) {
+
+			d = (struct usb_endpoint_descripter *)&za_23; // isoc ep desc for audio i/f alt setting 6
+			result = usb_ep_enable (ep, d);
+
+			if (result == 0) {
+				ep->driver_data = dev;
+				dev->in_ep = ep;
+
+				if (zero_start_isoc_ep (ep, gfp_flags) != 0) {
+
+					dev->in_ep = ep;
+					continue;
+				}
+
+				usb_ep_disable (ep);
+				result = -EIO;
+			}
+		}
+
+	}
+
+	dev->config = number;
+	return result;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void zero_setup_complete (struct usb_ep *ep, struct usb_request *req)
+{
+	if (req->status || req->actual != req->length)
+		DBG ((struct zero_dev *) ep->driver_data,
+				"setup complete --> %d, %d/%d\n",
+				req->status, req->actual, req->length);
+}
+
+/*
+ * The setup() callback implements all the ep0 functionality that's
+ * not handled lower down, in hardware or the hardware driver (like
+ * device and endpoint feature flags, and their status).  It's all
+ * housekeeping for the gadget function we're implementing.  Most of
+ * the work is in config-specific setup.
+ */
+static int
+zero_setup (struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl)
+{
+	struct zero_dev		*dev = get_gadget_data (gadget);
+	struct usb_request	*req = dev->req;
+	int			value = -EOPNOTSUPP;
+
+	/* usually this stores reply data in the pre-allocated ep0 buffer,
+	 * but config change events will reconfigure hardware.
+	 */
+	req->zero = 0;
+	switch (ctrl->bRequest) {
+
+	case USB_REQ_GET_DESCRIPTOR:
+
+		switch (ctrl->wValue >> 8) {
+
+		case USB_DT_DEVICE:
+			value = min (ctrl->wLength, (u16) sizeof device_desc);
+			memcpy (req->buf, &device_desc, value);
+			break;
+#ifdef CONFIG_USB_GADGET_DUALSPEED
+		case USB_DT_DEVICE_QUALIFIER:
+			if (!gadget->is_dualspeed)
+				break;
+			value = min (ctrl->wLength, (u16) sizeof dev_qualifier);
+			memcpy (req->buf, &dev_qualifier, value);
+			break;
+
+		case USB_DT_OTHER_SPEED_CONFIG:
+			if (!gadget->is_dualspeed)
+				break;
+			// FALLTHROUGH
+#endif /* CONFIG_USB_GADGET_DUALSPEED */
+		case USB_DT_CONFIG:
+			value = config_buf (gadget, req->buf,
+					ctrl->wValue >> 8,
+					ctrl->wValue & 0xff);
+			if (value >= 0)
+				value = min (ctrl->wLength, (u16) value);
+			break;
+
+		case USB_DT_STRING:
+			/* wIndex == language code.
+			 * this driver only handles one language, you can
+			 * add string tables for other languages, using
+			 * any UTF-8 characters
+			 */
+			value = usb_gadget_get_string (&stringtab,
+					ctrl->wValue & 0xff, req->buf);
+			if (value >= 0) {
+				value = min (ctrl->wLength, (u16) value);
+			}
+			break;
+		}
+		break;
+
+	/* currently two configs, two speeds */
+	case USB_REQ_SET_CONFIGURATION:
+		if (ctrl->bRequestType != 0)
+			goto unknown;
+
+		spin_lock (&dev->lock);
+		value = zero_set_config (dev, ctrl->wValue, GFP_ATOMIC);
+		spin_unlock (&dev->lock);
+		break;
+	case USB_REQ_GET_CONFIGURATION:
+		if (ctrl->bRequestType != USB_DIR_IN)
+			goto unknown;
+		*(u8 *)req->buf = dev->config;
+		value = min (ctrl->wLength, (u16) 1);
+		break;
+
+	/* until we add altsetting support, or other interfaces,
+	 * only 0/0 are possible.  pxa2xx only supports 0/0 (poorly)
+	 * and already killed pending endpoint I/O.
+	 */
+	case USB_REQ_SET_INTERFACE:
+
+		if (ctrl->bRequestType != USB_RECIP_INTERFACE)
+			goto unknown;
+		spin_lock (&dev->lock);
+		if (dev->config) {
+			u8		config = dev->config;
+
+			/* resets interface configuration, forgets about
+			 * previous transaction state (queued bufs, etc)
+			 * and re-inits endpoint state (toggle etc)
+			 * no response queued, just zero status == success.
+			 * if we had more than one interface we couldn't
+			 * use this "reset the config" shortcut.
+			 */
+			zero_reset_config (dev);
+			zero_set_config (dev, config, GFP_ATOMIC);
+			value = 0;
+		}
+		spin_unlock (&dev->lock);
+		break;
+	case USB_REQ_GET_INTERFACE:
+		if ((ctrl->bRequestType == 0x21) && (ctrl->wIndex == 0x02)) {
+			value = ctrl->wLength;
+			break;
+		}
+		else {
+			if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE))
+				goto unknown;
+			if (!dev->config)
+				break;
+			if (ctrl->wIndex != 0) {
+				value = -EDOM;
+				break;
+			}
+			*(u8 *)req->buf = 0;
+			value = min (ctrl->wLength, (u16) 1);
+		}
+		break;
+
+	/*
+	 * These are the same vendor-specific requests supported by
+	 * Intel's USB 2.0 compliance test devices.  We exceed that
+	 * device spec by allowing multiple-packet requests.
+	 */
+	case 0x5b:	/* control WRITE test -- fill the buffer */
+		if (ctrl->bRequestType != (USB_DIR_OUT|USB_TYPE_VENDOR))
+			goto unknown;
+		if (ctrl->wValue || ctrl->wIndex)
+			break;
+		/* just read that many bytes into the buffer */
+		if (ctrl->wLength > USB_BUFSIZ)
+			break;
+		value = ctrl->wLength;
+		break;
+	case 0x5c:	/* control READ test -- return the buffer */
+		if (ctrl->bRequestType != (USB_DIR_IN|USB_TYPE_VENDOR))
+			goto unknown;
+		if (ctrl->wValue || ctrl->wIndex)
+			break;
+		/* expect those bytes are still in the buffer; send back */
+		if (ctrl->wLength > USB_BUFSIZ
+				|| ctrl->wLength != req->length)
+			break;
+		value = ctrl->wLength;
+		break;
+
+	case 0x01: // SET_CUR
+	case 0x02:
+	case 0x03:
+	case 0x04:
+	case 0x05:
+		value = ctrl->wLength;
+		break;
+	case 0x81:
+		switch (ctrl->wValue) {
+		case 0x0201:
+		case 0x0202:
+			((u8*)req->buf)[0] = 0x00;
+			((u8*)req->buf)[1] = 0xe3;
+			break;
+		case 0x0300:
+		case 0x0500:
+			((u8*)req->buf)[0] = 0x00;
+			break;
+		}
+		//((u8*)req->buf)[0] = 0x81;
+		//((u8*)req->buf)[1] = 0x81;
+		value = ctrl->wLength;
+		break;
+	case 0x82:
+		switch (ctrl->wValue) {
+		case 0x0201:
+		case 0x0202:
+			((u8*)req->buf)[0] = 0x00;
+			((u8*)req->buf)[1] = 0xc3;
+			break;
+		case 0x0300:
+		case 0x0500:
+			((u8*)req->buf)[0] = 0x00;
+			break;
+		}
+		//((u8*)req->buf)[0] = 0x82;
+		//((u8*)req->buf)[1] = 0x82;
+		value = ctrl->wLength;
+		break;
+	case 0x83:
+		switch (ctrl->wValue) {
+		case 0x0201:
+		case 0x0202:
+			((u8*)req->buf)[0] = 0x00;
+			((u8*)req->buf)[1] = 0x00;
+			break;
+		case 0x0300:
+			((u8*)req->buf)[0] = 0x60;
+			break;
+		case 0x0500:
+			((u8*)req->buf)[0] = 0x18;
+			break;
+		}
+		//((u8*)req->buf)[0] = 0x83;
+		//((u8*)req->buf)[1] = 0x83;
+		value = ctrl->wLength;
+		break;
+	case 0x84:
+		switch (ctrl->wValue) {
+		case 0x0201:
+		case 0x0202:
+			((u8*)req->buf)[0] = 0x00;
+			((u8*)req->buf)[1] = 0x01;
+			break;
+		case 0x0300:
+		case 0x0500:
+			((u8*)req->buf)[0] = 0x08;
+			break;
+		}
+		//((u8*)req->buf)[0] = 0x84;
+		//((u8*)req->buf)[1] = 0x84;
+		value = ctrl->wLength;
+		break;
+	case 0x85:
+		((u8*)req->buf)[0] = 0x85;
+		((u8*)req->buf)[1] = 0x85;
+		value = ctrl->wLength;
+		break;
+
+
+	default:
+unknown:
+		printk("unknown control req%02x.%02x v%04x i%04x l%d\n",
+			ctrl->bRequestType, ctrl->bRequest,
+			ctrl->wValue, ctrl->wIndex, ctrl->wLength);
+	}
+
+	/* respond with data transfer before status phase? */
+	if (value >= 0) {
+		req->length = value;
+		req->zero = value < ctrl->wLength
+				&& (value % gadget->ep0->maxpacket) == 0;
+		value = usb_ep_queue (gadget->ep0, req, GFP_ATOMIC);
+		if (value < 0) {
+			DBG (dev, "ep_queue < 0 --> %d\n", value);
+			req->status = 0;
+			zero_setup_complete (gadget->ep0, req);
+		}
+	}
+
+	/* device either stalls (value < 0) or reports success */
+	return value;
+}
+
+static void
+zero_disconnect (struct usb_gadget *gadget)
+{
+	struct zero_dev		*dev = get_gadget_data (gadget);
+	unsigned long		flags;
+
+	spin_lock_irqsave (&dev->lock, flags);
+	zero_reset_config (dev);
+
+	/* a more significant application might have some non-usb
+	 * activities to quiesce here, saving resources like power
+	 * or pushing the notification up a network stack.
+	 */
+	spin_unlock_irqrestore (&dev->lock, flags);
+
+	/* next we may get setup() calls to enumerate new connections;
+	 * or an unbind() during shutdown (including removing module).
+	 */
+}
+
+static void
+zero_autoresume (unsigned long _dev)
+{
+	struct zero_dev	*dev = (struct zero_dev *) _dev;
+	int		status;
+
+	/* normally the host would be woken up for something
+	 * more significant than just a timer firing...
+	 */
+	if (dev->gadget->speed != USB_SPEED_UNKNOWN) {
+		status = usb_gadget_wakeup (dev->gadget);
+		DBG (dev, "wakeup --> %d\n", status);
+	}
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void
+zero_unbind (struct usb_gadget *gadget)
+{
+	struct zero_dev		*dev = get_gadget_data (gadget);
+
+	DBG (dev, "unbind\n");
+
+	/* we've already been disconnected ... no i/o is active */
+	if (dev->req)
+		free_ep_req (gadget->ep0, dev->req);
+	del_timer_sync (&dev->resume);
+	kfree (dev);
+	set_gadget_data (gadget, NULL);
+}
+
+static int
+zero_bind (struct usb_gadget *gadget)
+{
+	struct zero_dev		*dev;
+	//struct usb_ep		*ep;
+
+	printk("binding\n");
+	/*
+	 * DRIVER POLICY CHOICE:  you may want to do this differently.
+	 * One thing to avoid is reusing a bcdDevice revision code
+	 * with different host-visible configurations or behavior
+	 * restrictions -- using ep1in/ep2out vs ep1out/ep3in, etc
+	 */
+	//device_desc.bcdDevice = __constant_cpu_to_le16 (0x0201);
+
+
+	/* ok, we made sense of the hardware ... */
+	dev = kzalloc (sizeof *dev, SLAB_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+	spin_lock_init (&dev->lock);
+	dev->gadget = gadget;
+	set_gadget_data (gadget, dev);
+
+	/* preallocate control response and buffer */
+	dev->req = usb_ep_alloc_request (gadget->ep0, GFP_KERNEL);
+	if (!dev->req)
+		goto enomem;
+	dev->req->buf = usb_ep_alloc_buffer (gadget->ep0, USB_BUFSIZ,
+				&dev->req->dma, GFP_KERNEL);
+	if (!dev->req->buf)
+		goto enomem;
+
+	dev->req->complete = zero_setup_complete;
+
+	device_desc.bMaxPacketSize0 = gadget->ep0->maxpacket;
+
+#ifdef CONFIG_USB_GADGET_DUALSPEED
+	/* assume ep0 uses the same value for both speeds ... */
+	dev_qualifier.bMaxPacketSize0 = device_desc.bMaxPacketSize0;
+
+	/* and that all endpoints are dual-speed */
+	//hs_source_desc.bEndpointAddress = fs_source_desc.bEndpointAddress;
+	//hs_sink_desc.bEndpointAddress = fs_sink_desc.bEndpointAddress;
+#endif
+
+	usb_gadget_set_selfpowered (gadget);
+
+	init_timer (&dev->resume);
+	dev->resume.function = zero_autoresume;
+	dev->resume.data = (unsigned long) dev;
+
+	gadget->ep0->driver_data = dev;
+
+	INFO (dev, "%s, version: " DRIVER_VERSION "\n", longname);
+	INFO (dev, "using %s, OUT %s IN %s\n", gadget->name,
+		EP_OUT_NAME, EP_IN_NAME);
+
+	snprintf (manufacturer, sizeof manufacturer,
+		UTS_SYSNAME " " UTS_RELEASE " with %s",
+		gadget->name);
+
+	return 0;
+
+enomem:
+	zero_unbind (gadget);
+	return -ENOMEM;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void
+zero_suspend (struct usb_gadget *gadget)
+{
+	struct zero_dev		*dev = get_gadget_data (gadget);
+
+	if (gadget->speed == USB_SPEED_UNKNOWN)
+		return;
+
+	if (autoresume) {
+		mod_timer (&dev->resume, jiffies + (HZ * autoresume));
+		DBG (dev, "suspend, wakeup in %d seconds\n", autoresume);
+	} else
+		DBG (dev, "suspend\n");
+}
+
+static void
+zero_resume (struct usb_gadget *gadget)
+{
+	struct zero_dev		*dev = get_gadget_data (gadget);
+
+	DBG (dev, "resume\n");
+	del_timer (&dev->resume);
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+static struct usb_gadget_driver zero_driver = {
+#ifdef CONFIG_USB_GADGET_DUALSPEED
+	.speed		= USB_SPEED_HIGH,
+#else
+	.speed		= USB_SPEED_FULL,
+#endif
+	.function	= (char *) longname,
+	.bind		= zero_bind,
+	.unbind		= zero_unbind,
+
+	.setup		= zero_setup,
+	.disconnect	= zero_disconnect,
+
+	.suspend	= zero_suspend,
+	.resume		= zero_resume,
+
+	.driver 	= {
+		.name		= (char *) shortname,
+		// .shutdown = ...
+		// .suspend = ...
+		// .resume = ...
+	},
+};
+
+MODULE_AUTHOR ("David Brownell");
+MODULE_LICENSE ("Dual BSD/GPL");
+
+static struct proc_dir_entry *pdir, *pfile;
+
+static int isoc_read_data (char *page, char **start,
+			   off_t off, int count,
+			   int *eof, void *data)
+{
+	int i;
+	static int c = 0;
+	static int done = 0;
+	static int s = 0;
+
+/*
+	printk ("\ncount: %d\n", count);
+	printk ("rbuf_start: %d\n", rbuf_start);
+	printk ("rbuf_len: %d\n", rbuf_len);
+	printk ("off: %d\n", off);
+	printk ("start: %p\n\n", *start);
+*/
+	if (done) {
+		c = 0;
+		done = 0;
+		*eof = 1;
+		return 0;
+	}
+
+	if (c == 0) {
+		if (rbuf_len == RBUF_LEN)
+			s = rbuf_start;
+		else s = 0;
+	}
+
+	for (i=0; i<count && c<rbuf_len; i++, c++) {
+		page[i] = rbuf[(c+s) % RBUF_LEN];
+	}
+	*start = page;
+
+	if (c >= rbuf_len) {
+		*eof = 1;
+		done = 1;
+	}
+
+
+	return i;
+}
+
+static int __init init (void)
+{
+
+	int retval = 0;
+
+	pdir = proc_mkdir("isoc_test", NULL);
+	if(pdir == NULL) {
+		retval = -ENOMEM;
+		printk("Error creating dir\n");
+		goto done;
+	}
+	pdir->owner = THIS_MODULE;
+
+	pfile = create_proc_read_entry("isoc_data",
+				       0444, pdir,
+				       isoc_read_data,
+				       NULL);
+	if (pfile == NULL) {
+		retval = -ENOMEM;
+		printk("Error creating file\n");
+		goto no_file;
+	}
+	pfile->owner = THIS_MODULE;
+
+	return usb_gadget_register_driver (&zero_driver);
+
+ no_file:
+	remove_proc_entry("isoc_data", NULL);
+ done:
+	return retval;
+}
+module_init (init);
+
+static void __exit cleanup (void)
+{
+
+	usb_gadget_unregister_driver (&zero_driver);
+
+	remove_proc_entry("isoc_data", pdir);
+	remove_proc_entry("isoc_test", NULL);
+}
+module_exit (cleanup);
diff --git a/drivers/usb/host/dwc_otg/dwc_cfi_common.h b/drivers/usb/host/dwc_otg/dwc_cfi_common.h
new file mode 100644
index 000000000000..7770e201ad3b
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_cfi_common.h
@@ -0,0 +1,142 @@
+/* ==========================================================================
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+
+#if !defined(__DWC_CFI_COMMON_H__)
+#define __DWC_CFI_COMMON_H__
+
+//#include <linux/types.h>
+
+/**
+ * @file
+ *
+ * This file contains the CFI specific common constants, interfaces
+ * (functions and macros) and structures for Linux. No PCD specific
+ * data structure or definition is to be included in this file.
+ *
+ */
+
+/** This is a request for all Core Features */
+#define VEN_CORE_GET_FEATURES		0xB1
+
+/** This is a request to get the value of a specific Core Feature */
+#define VEN_CORE_GET_FEATURE		0xB2
+
+/** This command allows the host to set the value of a specific Core Feature */
+#define VEN_CORE_SET_FEATURE		0xB3
+
+/** This command allows the host to set the default values of
+ * either all or any specific Core Feature
+ */
+#define VEN_CORE_RESET_FEATURES		0xB4
+
+/** This command forces the PCD to write the deferred values of a Core Features */
+#define VEN_CORE_ACTIVATE_FEATURES	0xB5
+
+/** This request reads a DWORD value from a register at the specified offset */
+#define VEN_CORE_READ_REGISTER		0xB6
+
+/** This request writes a DWORD value into a register at the specified offset */
+#define VEN_CORE_WRITE_REGISTER		0xB7
+
+/** This structure is the header of the Core Features dataset returned to
+ *  the Host
+ */
+struct cfi_all_features_header {
+/** The features header structure length is */
+#define CFI_ALL_FEATURES_HDR_LEN		8
+	/**
+	 * The total length of the features dataset returned to the Host
+	 */
+	uint16_t wTotalLen;
+
+	/**
+	 * CFI version number inBinary-Coded Decimal (i.e., 1.00 is 100H).
+	 * This field identifies the version of the CFI Specification with which
+	 * the device is compliant.
+	 */
+	uint16_t wVersion;
+
+	/** The ID of the Core */
+	uint16_t wCoreID;
+#define CFI_CORE_ID_UDC		1
+#define CFI_CORE_ID_OTG		2
+#define CFI_CORE_ID_WUDEV	3
+
+	/** Number of features returned by VEN_CORE_GET_FEATURES request */
+	uint16_t wNumFeatures;
+} UPACKED;
+
+typedef struct cfi_all_features_header cfi_all_features_header_t;
+
+/** This structure is a header of the Core Feature descriptor dataset returned to
+ *  the Host after the VEN_CORE_GET_FEATURES request
+ */
+struct cfi_feature_desc_header {
+#define CFI_FEATURE_DESC_HDR_LEN	8
+
+	/** The feature ID */
+	uint16_t wFeatureID;
+
+	/** Length of this feature descriptor in bytes - including the
+	 * length of the feature name string
+	 */
+	uint16_t wLength;
+
+	/** The data length of this feature in bytes */
+	uint16_t wDataLength;
+
+	/**
+	 * Attributes of this features
+	 * D0: Access rights
+	 * 0 - Read/Write
+	 * 1 - Read only
+	 */
+	uint8_t bmAttributes;
+#define CFI_FEATURE_ATTR_RO		1
+#define CFI_FEATURE_ATTR_RW		0
+
+	/** Length of the feature name in bytes */
+	uint8_t bNameLen;
+
+	/** The feature name buffer */
+	//uint8_t *name;
+} UPACKED;
+
+typedef struct cfi_feature_desc_header cfi_feature_desc_header_t;
+
+/**
+ * This structure describes a NULL terminated string referenced by its id field.
+ * It is very similar to usb_string structure but has the id field type set to 16-bit.
+ */
+struct cfi_string {
+	uint16_t id;
+	const uint8_t *s;
+};
+typedef struct cfi_string cfi_string_t;
+
+#endif
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_adp.c b/drivers/usb/host/dwc_otg/dwc_otg_adp.c
new file mode 100644
index 000000000000..cb49aedd51f6
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_adp.c
@@ -0,0 +1,842 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_adp.c $
+ * $Revision: #12 $
+ * $Date: 2011/10/26 $
+ * $Change: 1873028 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+
+#include "dwc_os.h"
+#include "dwc_otg_regs.h"
+#include "dwc_otg_cil.h"
+#include "dwc_otg_adp.h"
+
+/** @file
+ *
+ * This file contains the most of the Attach Detect Protocol implementation for
+ * the driver to support OTG Rev2.0.
+ *
+ */
+
+void dwc_otg_adp_write_reg(dwc_otg_core_if_t * core_if, uint32_t value)
+{
+	adpctl_data_t adpctl;
+
+	adpctl.d32 = value;
+	adpctl.b.ar = 0x2;
+
+	DWC_WRITE_REG32(&core_if->core_global_regs->adpctl, adpctl.d32);
+
+	while (adpctl.b.ar) {
+		adpctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->adpctl);
+	}
+
+}
+
+/**
+ * Function is called to read ADP registers
+ */
+uint32_t dwc_otg_adp_read_reg(dwc_otg_core_if_t * core_if)
+{
+	adpctl_data_t adpctl;
+
+	adpctl.d32 = 0;
+	adpctl.b.ar = 0x1;
+
+	DWC_WRITE_REG32(&core_if->core_global_regs->adpctl, adpctl.d32);
+
+	while (adpctl.b.ar) {
+		adpctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->adpctl);
+	}
+
+	return adpctl.d32;
+}
+
+/**
+ * Function is called to read ADPCTL register and filter Write-clear bits
+ */
+static uint32_t dwc_otg_adp_read_reg_filter(dwc_otg_core_if_t * core_if)
+{
+	adpctl_data_t adpctl;
+
+	adpctl.d32 = dwc_otg_adp_read_reg(core_if);
+	adpctl.b.adp_tmout_int = 0;
+	adpctl.b.adp_prb_int = 0;
+	adpctl.b.adp_tmout_int = 0;
+
+	return adpctl.d32;
+}
+
+static void adp_sense_timeout(void *ptr)
+{
+	dwc_otg_core_if_t *core_if = (dwc_otg_core_if_t *) ptr;
+	core_if->adp.sense_timer_started = 0;
+	DWC_PRINTF("ADP SENSE TIMEOUT\n");
+	if (core_if->adp_enable) {
+		dwc_otg_adp_sense_stop(core_if);
+		dwc_otg_adp_probe_start(core_if);
+	}
+}
+
+/**
+ * This function is called when the ADP vbus timer expires. Timeout is 1.1s.
+ */
+static void adp_vbuson_timeout(void *ptr)
+{
+	gpwrdn_data_t gpwrdn;
+	dwc_otg_core_if_t *core_if = (dwc_otg_core_if_t *) ptr;
+	hprt0_data_t hprt0 = {.d32 = 0 };
+	pcgcctl_data_t pcgcctl = {.d32 = 0 };
+	DWC_PRINTF("%s: 1.1 seconds expire after turning on VBUS\n",__FUNCTION__);
+	if (core_if) {
+		core_if->adp.vbuson_timer_started = 0;
+		/* Turn off vbus */
+		hprt0.b.prtpwr = 1;
+		DWC_MODIFY_REG32(core_if->host_if->hprt0, hprt0.d32, 0);
+		gpwrdn.d32 = 0;
+
+		/* Power off the core */
+		if (core_if->power_down == 2) {
+			/* Enable Wakeup Logic */
+//                      gpwrdn.b.wkupactiv = 1;
+			gpwrdn.b.pmuactv = 0;
+			gpwrdn.b.pwrdnrstn = 1;
+			gpwrdn.b.pwrdnclmp = 1;
+			DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0,
+					 gpwrdn.d32);
+
+			/* Suspend the Phy Clock */
+			pcgcctl.b.stoppclk = 1;
+			DWC_MODIFY_REG32(core_if->pcgcctl, 0, pcgcctl.d32);
+
+			/* Switch on VDD */
+//                      gpwrdn.b.wkupactiv = 1;
+			gpwrdn.b.pmuactv = 1;
+			gpwrdn.b.pwrdnrstn = 1;
+			gpwrdn.b.pwrdnclmp = 1;
+			DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0,
+					 gpwrdn.d32);
+		} else {
+			/* Enable Power Down Logic */
+			gpwrdn.b.pmuintsel = 1;
+			gpwrdn.b.pmuactv = 1;
+			DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32);
+		}
+
+		/* Power off the core */
+		if (core_if->power_down == 2) {
+			gpwrdn.d32 = 0;
+			gpwrdn.b.pwrdnswtch = 1;
+			DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn,
+					 gpwrdn.d32, 0);
+		}
+
+		/* Unmask SRP detected interrupt from Power Down Logic */
+		gpwrdn.d32 = 0;
+		gpwrdn.b.srp_det_msk = 1;
+		DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32);
+
+		dwc_otg_adp_probe_start(core_if);
+		dwc_otg_dump_global_registers(core_if);
+		dwc_otg_dump_host_registers(core_if);
+	}
+
+}
+
+/**
+ * Start the ADP Initial Probe timer to detect if Port Connected interrupt is
+ * not asserted within 1.1 seconds.
+ *
+ * @param core_if the pointer to core_if strucure.
+ */
+static void dwc_otg_adp_vbuson_timer_start(dwc_otg_core_if_t * core_if)
+{
+	core_if->adp.vbuson_timer_started = 1;
+	if (core_if->adp.vbuson_timer)
+	{
+		DWC_PRINTF("SCHEDULING VBUSON TIMER\n");
+		/* 1.1 secs + 60ms necessary for cil_hcd_start*/
+		DWC_TIMER_SCHEDULE(core_if->adp.vbuson_timer, 1160);
+	} else {
+		DWC_WARN("VBUSON_TIMER = %p\n",core_if->adp.vbuson_timer);
+	}
+}
+
+#if 0
+/**
+ * Masks all DWC OTG core interrupts
+ *
+ */
+static void mask_all_interrupts(dwc_otg_core_if_t * core_if)
+{
+	int i;
+	gahbcfg_data_t ahbcfg = {.d32 = 0 };
+
+	/* Mask Host Interrupts */
+
+	/* Clear and disable HCINTs */
+	for (i = 0; i < core_if->core_params->host_channels; i++) {
+		DWC_WRITE_REG32(&core_if->host_if->hc_regs[i]->hcintmsk, 0);
+		DWC_WRITE_REG32(&core_if->host_if->hc_regs[i]->hcint, 0xFFFFFFFF);
+
+	}
+
+	/* Clear and disable HAINT */
+	DWC_WRITE_REG32(&core_if->host_if->host_global_regs->haintmsk, 0x0000);
+	DWC_WRITE_REG32(&core_if->host_if->host_global_regs->haint, 0xFFFFFFFF);
+
+	/* Mask Device Interrupts */
+	if (!core_if->multiproc_int_enable) {
+		/* Clear and disable IN Endpoint interrupts */
+		DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->diepmsk, 0);
+		for (i = 0; i <= core_if->dev_if->num_in_eps; i++) {
+			DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[i]->
+					diepint, 0xFFFFFFFF);
+		}
+
+		/* Clear and disable OUT Endpoint interrupts */
+		DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->doepmsk, 0);
+		for (i = 0; i <= core_if->dev_if->num_out_eps; i++) {
+			DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[i]->
+					doepint, 0xFFFFFFFF);
+		}
+
+		/* Clear and disable DAINT */
+		DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->daint,
+				0xFFFFFFFF);
+		DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->daintmsk, 0);
+	} else {
+		for (i = 0; i < core_if->dev_if->num_in_eps; ++i) {
+			DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->
+					diepeachintmsk[i], 0);
+			DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[i]->
+					diepint, 0xFFFFFFFF);
+		}
+
+		for (i = 0; i < core_if->dev_if->num_out_eps; ++i) {
+			DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->
+					doepeachintmsk[i], 0);
+			DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[i]->
+					doepint, 0xFFFFFFFF);
+		}
+
+		DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->deachintmsk,
+				0);
+		DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->deachint,
+				0xFFFFFFFF);
+
+	}
+
+	/* Disable interrupts */
+	ahbcfg.b.glblintrmsk = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gahbcfg, ahbcfg.d32, 0);
+
+	/* Disable all interrupts. */
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, 0);
+
+	/* Clear any pending interrupts */
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF);
+
+	/* Clear any pending OTG Interrupts */
+	DWC_WRITE_REG32(&core_if->core_global_regs->gotgint, 0xFFFFFFFF);
+}
+
+/**
+ * Unmask Port Connection Detected interrupt
+ *
+ */
+static void unmask_conn_det_intr(dwc_otg_core_if_t * core_if)
+{
+	gintmsk_data_t gintmsk = {.d32 = 0,.b.portintr = 1 };
+
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, gintmsk.d32);
+}
+#endif
+
+/**
+ * Starts the ADP Probing
+ *
+ * @param core_if the pointer to core_if structure.
+ */
+uint32_t dwc_otg_adp_probe_start(dwc_otg_core_if_t * core_if)
+{
+
+	adpctl_data_t adpctl = {.d32 = 0};
+	gpwrdn_data_t gpwrdn;
+#if 0
+	adpctl_data_t adpctl_int = {.d32 = 0, .b.adp_prb_int = 1,
+								.b.adp_sns_int = 1, b.adp_tmout_int};
+#endif
+	dwc_otg_disable_global_interrupts(core_if);
+	DWC_PRINTF("ADP Probe Start\n");
+	core_if->adp.probe_enabled = 1;
+
+	adpctl.b.adpres = 1;
+	dwc_otg_adp_write_reg(core_if, adpctl.d32);
+
+	while (adpctl.b.adpres) {
+		adpctl.d32 = dwc_otg_adp_read_reg(core_if);
+	}
+
+	adpctl.d32 = 0;
+	gpwrdn.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn);
+
+	/* In Host mode unmask SRP detected interrupt */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.sts_chngint_msk = 1;
+	if (!gpwrdn.b.idsts) {
+		gpwrdn.b.srp_det_msk = 1;
+	}
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32);
+
+	adpctl.b.adp_tmout_int_msk = 1;
+	adpctl.b.adp_prb_int_msk = 1;
+	adpctl.b.prb_dschg = 1;
+	adpctl.b.prb_delta = 1;
+	adpctl.b.prb_per = 1;
+	adpctl.b.adpen = 1;
+	adpctl.b.enaprb = 1;
+
+	dwc_otg_adp_write_reg(core_if, adpctl.d32);
+	DWC_PRINTF("ADP Probe Finish\n");
+	return 0;
+}
+
+/**
+ * Starts the ADP Sense timer to detect if ADP Sense interrupt is not asserted
+ * within 3 seconds.
+ *
+ * @param core_if the pointer to core_if strucure.
+ */
+static void dwc_otg_adp_sense_timer_start(dwc_otg_core_if_t * core_if)
+{
+	core_if->adp.sense_timer_started = 1;
+	DWC_TIMER_SCHEDULE(core_if->adp.sense_timer, 3000 /* 3 secs */ );
+}
+
+/**
+ * Starts the ADP Sense
+ *
+ * @param core_if the pointer to core_if strucure.
+ */
+uint32_t dwc_otg_adp_sense_start(dwc_otg_core_if_t * core_if)
+{
+	adpctl_data_t adpctl;
+
+	DWC_PRINTF("ADP Sense Start\n");
+
+	/* Unmask ADP sense interrupt and mask all other from the core */
+	adpctl.d32 = dwc_otg_adp_read_reg_filter(core_if);
+	adpctl.b.adp_sns_int_msk = 1;
+	dwc_otg_adp_write_reg(core_if, adpctl.d32);
+	dwc_otg_disable_global_interrupts(core_if); // vahrama
+
+	/* Set ADP reset bit*/
+	adpctl.d32 = dwc_otg_adp_read_reg_filter(core_if);
+	adpctl.b.adpres = 1;
+	dwc_otg_adp_write_reg(core_if, adpctl.d32);
+
+	while (adpctl.b.adpres) {
+		adpctl.d32 = dwc_otg_adp_read_reg(core_if);
+	}
+
+	adpctl.b.adpres = 0;
+	adpctl.b.adpen = 1;
+	adpctl.b.enasns = 1;
+	dwc_otg_adp_write_reg(core_if, adpctl.d32);
+
+	dwc_otg_adp_sense_timer_start(core_if);
+
+	return 0;
+}
+
+/**
+ * Stops the ADP Probing
+ *
+ * @param core_if the pointer to core_if strucure.
+ */
+uint32_t dwc_otg_adp_probe_stop(dwc_otg_core_if_t * core_if)
+{
+
+	adpctl_data_t adpctl;
+	DWC_PRINTF("Stop ADP probe\n");
+	core_if->adp.probe_enabled = 0;
+	core_if->adp.probe_counter = 0;
+	adpctl.d32 = dwc_otg_adp_read_reg(core_if);
+
+	adpctl.b.adpen = 0;
+	adpctl.b.adp_prb_int = 1;
+	adpctl.b.adp_tmout_int = 1;
+	adpctl.b.adp_sns_int = 1;
+	dwc_otg_adp_write_reg(core_if, adpctl.d32);
+
+	return 0;
+}
+
+/**
+ * Stops the ADP Sensing
+ *
+ * @param core_if the pointer to core_if strucure.
+ */
+uint32_t dwc_otg_adp_sense_stop(dwc_otg_core_if_t * core_if)
+{
+	adpctl_data_t adpctl;
+
+	core_if->adp.sense_enabled = 0;
+
+	adpctl.d32 = dwc_otg_adp_read_reg_filter(core_if);
+	adpctl.b.enasns = 0;
+	adpctl.b.adp_sns_int = 1;
+	dwc_otg_adp_write_reg(core_if, adpctl.d32);
+
+	return 0;
+}
+
+/**
+ * Called to turn on the VBUS after initial ADP probe in host mode.
+ * If port power was already enabled in cil_hcd_start function then
+ * only schedule a timer.
+ *
+ * @param core_if the pointer to core_if structure.
+ */
+static void dwc_otg_adp_turnon_vbus(dwc_otg_core_if_t * core_if)
+{
+	hprt0_data_t hprt0 = {.d32 = 0 };
+	hprt0.d32 = dwc_otg_read_hprt0(core_if);
+	DWC_PRINTF("Turn on VBUS for 1.1s, port power is %d\n", hprt0.b.prtpwr);
+
+	if (hprt0.b.prtpwr == 0) {
+		hprt0.b.prtpwr = 1;
+		//DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+	}
+
+	dwc_otg_adp_vbuson_timer_start(core_if);
+}
+
+/**
+ * Called right after driver is loaded
+ * to perform initial actions for ADP
+ *
+ * @param core_if the pointer to core_if structure.
+ * @param is_host - flag for current mode of operation either from GINTSTS or GPWRDN
+ */
+void dwc_otg_adp_start(dwc_otg_core_if_t * core_if, uint8_t is_host)
+{
+	gpwrdn_data_t gpwrdn;
+
+	DWC_PRINTF("ADP Initial Start\n");
+	core_if->adp.adp_started = 1;
+
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF);
+	dwc_otg_disable_global_interrupts(core_if);
+	if (is_host) {
+		DWC_PRINTF("HOST MODE\n");
+		/* Enable Power Down Logic Interrupt*/
+		gpwrdn.d32 = 0;
+		gpwrdn.b.pmuintsel = 1;
+		gpwrdn.b.pmuactv = 1;
+		DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32);
+		/* Initialize first ADP probe to obtain Ramp Time value */
+		core_if->adp.initial_probe = 1;
+		dwc_otg_adp_probe_start(core_if);
+	} else {
+		gotgctl_data_t gotgctl;
+		gotgctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->gotgctl);
+		DWC_PRINTF("DEVICE MODE\n");
+		if (gotgctl.b.bsesvld == 0) {
+			/* Enable Power Down Logic Interrupt*/
+			gpwrdn.d32 = 0;
+			DWC_PRINTF("VBUS is not valid - start ADP probe\n");
+			gpwrdn.b.pmuintsel = 1;
+			gpwrdn.b.pmuactv = 1;
+			DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32);
+			core_if->adp.initial_probe = 1;
+			dwc_otg_adp_probe_start(core_if);
+		} else {
+			DWC_PRINTF("VBUS is valid - initialize core as a Device\n");
+			core_if->op_state = B_PERIPHERAL;
+			dwc_otg_core_init(core_if);
+			dwc_otg_enable_global_interrupts(core_if);
+			cil_pcd_start(core_if);
+			dwc_otg_dump_global_registers(core_if);
+			dwc_otg_dump_dev_registers(core_if);
+		}
+	}
+}
+
+void dwc_otg_adp_init(dwc_otg_core_if_t * core_if)
+{
+	core_if->adp.adp_started = 0;
+	core_if->adp.initial_probe = 0;
+	core_if->adp.probe_timer_values[0] = -1;
+	core_if->adp.probe_timer_values[1] = -1;
+	core_if->adp.probe_enabled = 0;
+	core_if->adp.sense_enabled = 0;
+	core_if->adp.sense_timer_started = 0;
+	core_if->adp.vbuson_timer_started = 0;
+	core_if->adp.probe_counter = 0;
+	core_if->adp.gpwrdn = 0;
+	core_if->adp.attached = DWC_OTG_ADP_UNKOWN;
+	/* Initialize timers */
+	core_if->adp.sense_timer =
+	    DWC_TIMER_ALLOC("ADP SENSE TIMER", adp_sense_timeout, core_if);
+	core_if->adp.vbuson_timer =
+	    DWC_TIMER_ALLOC("ADP VBUS ON TIMER", adp_vbuson_timeout, core_if);
+	if (!core_if->adp.sense_timer || !core_if->adp.vbuson_timer)
+	{
+		DWC_ERROR("Could not allocate memory for ADP timers\n");
+	}
+}
+
+void dwc_otg_adp_remove(dwc_otg_core_if_t * core_if)
+{
+	gpwrdn_data_t gpwrdn = { .d32 = 0 };
+	gpwrdn.b.pmuintsel = 1;
+	gpwrdn.b.pmuactv = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+	if (core_if->adp.probe_enabled)
+		dwc_otg_adp_probe_stop(core_if);
+	if (core_if->adp.sense_enabled)
+		dwc_otg_adp_sense_stop(core_if);
+	if (core_if->adp.sense_timer_started)
+		DWC_TIMER_CANCEL(core_if->adp.sense_timer);
+	if (core_if->adp.vbuson_timer_started)
+		DWC_TIMER_CANCEL(core_if->adp.vbuson_timer);
+	DWC_TIMER_FREE(core_if->adp.sense_timer);
+	DWC_TIMER_FREE(core_if->adp.vbuson_timer);
+}
+
+/////////////////////////////////////////////////////////////////////
+////////////// ADP Interrupt Handlers ///////////////////////////////
+/////////////////////////////////////////////////////////////////////
+/**
+ * This function sets Ramp Timer values
+ */
+static uint32_t set_timer_value(dwc_otg_core_if_t * core_if, uint32_t val)
+{
+	if (core_if->adp.probe_timer_values[0] == -1) {
+		core_if->adp.probe_timer_values[0] = val;
+		core_if->adp.probe_timer_values[1] = -1;
+		return 1;
+	} else {
+		core_if->adp.probe_timer_values[1] =
+		    core_if->adp.probe_timer_values[0];
+		core_if->adp.probe_timer_values[0] = val;
+		return 0;
+	}
+}
+
+/**
+ * This function compares Ramp Timer values
+ */
+static uint32_t compare_timer_values(dwc_otg_core_if_t * core_if)
+{
+	uint32_t diff;
+	if (core_if->adp.probe_timer_values[0]>=core_if->adp.probe_timer_values[1])
+			diff = core_if->adp.probe_timer_values[0]-core_if->adp.probe_timer_values[1];
+	else
+			diff = core_if->adp.probe_timer_values[1]-core_if->adp.probe_timer_values[0];
+	if(diff < 2) {
+		return 0;
+	} else {
+		return 1;
+	}
+}
+
+/**
+ * This function handles ADP Probe Interrupts
+ */
+static int32_t dwc_otg_adp_handle_prb_intr(dwc_otg_core_if_t * core_if,
+						 uint32_t val)
+{
+	adpctl_data_t adpctl = {.d32 = 0 };
+	gpwrdn_data_t gpwrdn, temp;
+	adpctl.d32 = val;
+
+	temp.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn);
+	core_if->adp.probe_counter++;
+	core_if->adp.gpwrdn = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn);
+	if (adpctl.b.rtim == 0 && !temp.b.idsts){
+		DWC_PRINTF("RTIM value is 0\n");
+		goto exit;
+	}
+	if (set_timer_value(core_if, adpctl.b.rtim) &&
+	    core_if->adp.initial_probe) {
+		core_if->adp.initial_probe = 0;
+		dwc_otg_adp_probe_stop(core_if);
+		gpwrdn.d32 = 0;
+		gpwrdn.b.pmuactv = 1;
+		gpwrdn.b.pmuintsel = 1;
+		DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+		DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF);
+
+		/* check which value is for device mode and which for Host mode */
+		if (!temp.b.idsts) {	/* considered host mode value is 0 */
+			/*
+			 * Turn on VBUS after initial ADP probe.
+			 */
+			core_if->op_state = A_HOST;
+			dwc_otg_enable_global_interrupts(core_if);
+			DWC_SPINUNLOCK(core_if->lock);
+			cil_hcd_start(core_if);
+			dwc_otg_adp_turnon_vbus(core_if);
+			DWC_SPINLOCK(core_if->lock);
+		} else {
+			/*
+			 * Initiate SRP after initial ADP probe.
+			 */
+			dwc_otg_enable_global_interrupts(core_if);
+			dwc_otg_initiate_srp(core_if);
+		}
+	} else if (core_if->adp.probe_counter > 2){
+		gpwrdn.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn);
+		if (compare_timer_values(core_if)) {
+			DWC_PRINTF("Difference in timer values !!! \n");
+//                      core_if->adp.attached = DWC_OTG_ADP_ATTACHED;
+			dwc_otg_adp_probe_stop(core_if);
+
+			/* Power on the core */
+			if (core_if->power_down == 2) {
+				gpwrdn.b.pwrdnswtch = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->
+						 gpwrdn, 0, gpwrdn.d32);
+			}
+
+			/* check which value is for device mode and which for Host mode */
+			if (!temp.b.idsts) {	/* considered host mode value is 0 */
+				/* Disable Interrupt from Power Down Logic */
+				gpwrdn.d32 = 0;
+				gpwrdn.b.pmuintsel = 1;
+				gpwrdn.b.pmuactv = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->
+						 gpwrdn, gpwrdn.d32, 0);
+
+				/*
+				 * Initialize the Core for Host mode.
+				 */
+				core_if->op_state = A_HOST;
+				dwc_otg_core_init(core_if);
+				dwc_otg_enable_global_interrupts(core_if);
+				cil_hcd_start(core_if);
+			} else {
+				gotgctl_data_t gotgctl;
+				/* Mask SRP detected interrupt from Power Down Logic */
+				gpwrdn.d32 = 0;
+				gpwrdn.b.srp_det_msk = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->
+						 gpwrdn, gpwrdn.d32, 0);
+
+				/* Disable Power Down Logic */
+				gpwrdn.d32 = 0;
+				gpwrdn.b.pmuintsel = 1;
+				gpwrdn.b.pmuactv = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->
+						 gpwrdn, gpwrdn.d32, 0);
+
+				/*
+				 * Initialize the Core for Device mode.
+				 */
+				core_if->op_state = B_PERIPHERAL;
+				dwc_otg_core_init(core_if);
+				dwc_otg_enable_global_interrupts(core_if);
+				cil_pcd_start(core_if);
+
+				gotgctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->gotgctl);
+				if (!gotgctl.b.bsesvld) {
+					dwc_otg_initiate_srp(core_if);
+				}
+			}
+		}
+		if (core_if->power_down == 2) {
+			if (gpwrdn.b.bsessvld) {
+				/* Mask SRP detected interrupt from Power Down Logic */
+				gpwrdn.d32 = 0;
+				gpwrdn.b.srp_det_msk = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+				/* Disable Power Down Logic */
+				gpwrdn.d32 = 0;
+				gpwrdn.b.pmuactv = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+				/*
+				 * Initialize the Core for Device mode.
+				 */
+				core_if->op_state = B_PERIPHERAL;
+				dwc_otg_core_init(core_if);
+				dwc_otg_enable_global_interrupts(core_if);
+				cil_pcd_start(core_if);
+			}
+		}
+	}
+exit:
+	/* Clear interrupt */
+	adpctl.d32 = dwc_otg_adp_read_reg(core_if);
+	adpctl.b.adp_prb_int = 1;
+	dwc_otg_adp_write_reg(core_if, adpctl.d32);
+
+	return 0;
+}
+
+/**
+ * This function hadles ADP Sense Interrupt
+ */
+static int32_t dwc_otg_adp_handle_sns_intr(dwc_otg_core_if_t * core_if)
+{
+	adpctl_data_t adpctl;
+	/* Stop ADP Sense timer */
+	DWC_TIMER_CANCEL(core_if->adp.sense_timer);
+
+	/* Restart ADP Sense timer */
+	dwc_otg_adp_sense_timer_start(core_if);
+
+	/* Clear interrupt */
+	adpctl.d32 = dwc_otg_adp_read_reg(core_if);
+	adpctl.b.adp_sns_int = 1;
+	dwc_otg_adp_write_reg(core_if, adpctl.d32);
+
+	return 0;
+}
+
+/**
+ * This function handles ADP Probe Interrupts
+ */
+static int32_t dwc_otg_adp_handle_prb_tmout_intr(dwc_otg_core_if_t * core_if,
+						 uint32_t val)
+{
+	adpctl_data_t adpctl = {.d32 = 0 };
+	adpctl.d32 = val;
+	set_timer_value(core_if, adpctl.b.rtim);
+
+	/* Clear interrupt */
+	adpctl.d32 = dwc_otg_adp_read_reg(core_if);
+	adpctl.b.adp_tmout_int = 1;
+	dwc_otg_adp_write_reg(core_if, adpctl.d32);
+
+	return 0;
+}
+
+/**
+ * ADP Interrupt handler.
+ *
+ */
+int32_t dwc_otg_adp_handle_intr(dwc_otg_core_if_t * core_if)
+{
+	int retval = 0;
+	adpctl_data_t adpctl = {.d32 = 0};
+
+	adpctl.d32 = dwc_otg_adp_read_reg(core_if);
+	DWC_PRINTF("ADPCTL = %08x\n",adpctl.d32);
+
+	if (adpctl.b.adp_sns_int & adpctl.b.adp_sns_int_msk) {
+		DWC_PRINTF("ADP Sense interrupt\n");
+		retval |= dwc_otg_adp_handle_sns_intr(core_if);
+	}
+	if (adpctl.b.adp_tmout_int & adpctl.b.adp_tmout_int_msk) {
+		DWC_PRINTF("ADP timeout interrupt\n");
+		retval |= dwc_otg_adp_handle_prb_tmout_intr(core_if, adpctl.d32);
+	}
+	if (adpctl.b.adp_prb_int & adpctl.b.adp_prb_int_msk) {
+		DWC_PRINTF("ADP Probe interrupt\n");
+		adpctl.b.adp_prb_int = 1;
+		retval |= dwc_otg_adp_handle_prb_intr(core_if, adpctl.d32);
+	}
+
+	DWC_PRINTF("RETURN FROM ADP ISR\n");
+
+	return retval;
+}
+
+/**
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+int32_t dwc_otg_adp_handle_srp_intr(dwc_otg_core_if_t * core_if)
+{
+
+#ifndef DWC_HOST_ONLY
+	hprt0_data_t hprt0;
+	gpwrdn_data_t gpwrdn;
+	DWC_DEBUGPL(DBG_ANY, "++ Power Down Logic Session Request Interrupt++\n");
+
+	gpwrdn.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn);
+	/* check which value is for device mode and which for Host mode */
+	if (!gpwrdn.b.idsts) {	/* considered host mode value is 0 */
+		DWC_PRINTF("SRP: Host mode\n");
+
+		if (core_if->adp_enable) {
+			dwc_otg_adp_probe_stop(core_if);
+
+			/* Power on the core */
+			if (core_if->power_down == 2) {
+				gpwrdn.b.pwrdnswtch = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->
+						 gpwrdn, 0, gpwrdn.d32);
+			}
+
+			core_if->op_state = A_HOST;
+			dwc_otg_core_init(core_if);
+			dwc_otg_enable_global_interrupts(core_if);
+			cil_hcd_start(core_if);
+		}
+
+		/* Turn on the port power bit. */
+		hprt0.d32 = dwc_otg_read_hprt0(core_if);
+		hprt0.b.prtpwr = 1;
+		DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+
+		/* Start the Connection timer. So a message can be displayed
+		 * if connect does not occur within 10 seconds. */
+		cil_hcd_session_start(core_if);
+	} else {
+		DWC_PRINTF("SRP: Device mode %s\n", __FUNCTION__);
+		if (core_if->adp_enable) {
+			dwc_otg_adp_probe_stop(core_if);
+
+			/* Power on the core */
+			if (core_if->power_down == 2) {
+				gpwrdn.b.pwrdnswtch = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->
+						 gpwrdn, 0, gpwrdn.d32);
+			}
+
+			gpwrdn.d32 = 0;
+			gpwrdn.b.pmuactv = 0;
+			DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0,
+					 gpwrdn.d32);
+
+			core_if->op_state = B_PERIPHERAL;
+			dwc_otg_core_init(core_if);
+			dwc_otg_enable_global_interrupts(core_if);
+			cil_pcd_start(core_if);
+		}
+	}
+#endif
+	return 1;
+}
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_adp.h b/drivers/usb/host/dwc_otg/dwc_otg_adp.h
new file mode 100644
index 000000000000..4110b25d2002
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_adp.h
@@ -0,0 +1,80 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_adp.h $
+ * $Revision: #7 $
+ * $Date: 2011/10/24 $
+ * $Change: 1871159 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+
+#ifndef __DWC_OTG_ADP_H__
+#define __DWC_OTG_ADP_H__
+
+/**
+ * @file
+ *
+ * This file contains the Attach Detect Protocol interfaces and defines
+ * (functions) and structures for Linux.
+ *
+ */
+
+#define DWC_OTG_ADP_UNATTACHED	0
+#define DWC_OTG_ADP_ATTACHED	1
+#define DWC_OTG_ADP_UNKOWN	2
+
+typedef struct dwc_otg_adp {
+	uint32_t adp_started;
+	uint32_t initial_probe;
+	int32_t probe_timer_values[2];
+	uint32_t probe_enabled;
+	uint32_t sense_enabled;
+	dwc_timer_t *sense_timer;
+	uint32_t sense_timer_started;
+	dwc_timer_t *vbuson_timer;
+	uint32_t vbuson_timer_started;
+	uint32_t attached;
+	uint32_t probe_counter;
+	uint32_t gpwrdn;
+} dwc_otg_adp_t;
+
+/**
+ * Attach Detect Protocol functions
+ */
+
+extern void dwc_otg_adp_write_reg(dwc_otg_core_if_t * core_if, uint32_t value);
+extern uint32_t dwc_otg_adp_read_reg(dwc_otg_core_if_t * core_if);
+extern uint32_t dwc_otg_adp_probe_start(dwc_otg_core_if_t * core_if);
+extern uint32_t dwc_otg_adp_sense_start(dwc_otg_core_if_t * core_if);
+extern uint32_t dwc_otg_adp_probe_stop(dwc_otg_core_if_t * core_if);
+extern uint32_t dwc_otg_adp_sense_stop(dwc_otg_core_if_t * core_if);
+extern void dwc_otg_adp_start(dwc_otg_core_if_t * core_if, uint8_t is_host);
+extern void dwc_otg_adp_init(dwc_otg_core_if_t * core_if);
+extern void dwc_otg_adp_remove(dwc_otg_core_if_t * core_if);
+extern int32_t dwc_otg_adp_handle_intr(dwc_otg_core_if_t * core_if);
+extern int32_t dwc_otg_adp_handle_srp_intr(dwc_otg_core_if_t * core_if);
+
+#endif //__DWC_OTG_ADP_H__
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_attr.c b/drivers/usb/host/dwc_otg/dwc_otg_attr.c
new file mode 100644
index 000000000000..2f8ea77c3892
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_attr.c
@@ -0,0 +1,1212 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_attr.c $
+ * $Revision: #44 $
+ * $Date: 2010/11/29 $
+ * $Change: 1636033 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+
+/** @file
+ *
+ * The diagnostic interface will provide access to the controller for
+ * bringing up the hardware and testing.  The Linux driver attributes
+ * feature will be used to provide the Linux Diagnostic
+ * Interface. These attributes are accessed through sysfs.
+ */
+
+/** @page "Linux Module Attributes"
+ *
+ * The Linux module attributes feature is used to provide the Linux
+ * Diagnostic Interface.  These attributes are accessed through sysfs.
+ * The diagnostic interface will provide access to the controller for
+ * bringing up the hardware and testing.
+
+ The following table shows the attributes.
+ <table>
+ <tr>
+ <td><b> Name</b></td>
+ <td><b> Description</b></td>
+ <td><b> Access</b></td>
+ </tr>
+
+ <tr>
+ <td> mode </td>
+ <td> Returns the current mode: 0 for device mode, 1 for host mode</td>
+ <td> Read</td>
+ </tr>
+
+ <tr>
+ <td> hnpcapable </td>
+ <td> Gets or sets the "HNP-capable" bit in the Core USB Configuraton Register.
+ Read returns the current value.</td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> srpcapable </td>
+ <td> Gets or sets the "SRP-capable" bit in the Core USB Configuraton Register.
+ Read returns the current value.</td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> hsic_connect </td>
+ <td> Gets or sets the "HSIC-Connect" bit in the GLPMCFG Register.
+ Read returns the current value.</td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> inv_sel_hsic </td>
+ <td> Gets or sets the "Invert Select HSIC" bit in the GLPMFG Register.
+ Read returns the current value.</td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> hnp </td>
+ <td> Initiates the Host Negotiation Protocol.  Read returns the status.</td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> srp </td>
+ <td> Initiates the Session Request Protocol.  Read returns the status.</td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> buspower </td>
+ <td> Gets or sets the Power State of the bus (0 - Off or 1 - On)</td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> bussuspend </td>
+ <td> Suspends the USB bus.</td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> busconnected </td>
+ <td> Gets the connection status of the bus</td>
+ <td> Read</td>
+ </tr>
+
+ <tr>
+ <td> gotgctl </td>
+ <td> Gets or sets the Core Control Status Register.</td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> gusbcfg </td>
+ <td> Gets or sets the Core USB Configuration Register</td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> grxfsiz </td>
+ <td> Gets or sets the Receive FIFO Size Register</td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> gnptxfsiz </td>
+ <td> Gets or sets the non-periodic Transmit Size Register</td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> gpvndctl </td>
+ <td> Gets or sets the PHY Vendor Control Register</td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> ggpio </td>
+ <td> Gets the value in the lower 16-bits of the General Purpose IO Register
+ or sets the upper 16 bits.</td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> guid </td>
+ <td> Gets or sets the value of the User ID Register</td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> gsnpsid </td>
+ <td> Gets the value of the Synopsys ID Regester</td>
+ <td> Read</td>
+ </tr>
+
+ <tr>
+ <td> devspeed </td>
+ <td> Gets or sets the device speed setting in the DCFG register</td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> enumspeed </td>
+ <td> Gets the device enumeration Speed.</td>
+ <td> Read</td>
+ </tr>
+
+ <tr>
+ <td> hptxfsiz </td>
+ <td> Gets the value of the Host Periodic Transmit FIFO</td>
+ <td> Read</td>
+ </tr>
+
+ <tr>
+ <td> hprt0 </td>
+ <td> Gets or sets the value in the Host Port Control and Status Register</td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> regoffset </td>
+ <td> Sets the register offset for the next Register Access</td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> regvalue </td>
+ <td> Gets or sets the value of the register at the offset in the regoffset attribute.</td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> remote_wakeup </td>
+ <td> On read, shows the status of Remote Wakeup. On write, initiates a remote
+ wakeup of the host. When bit 0 is 1 and Remote Wakeup is enabled, the Remote
+ Wakeup signalling bit in the Device Control Register is set for 1
+ milli-second.</td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> rem_wakeup_pwrdn </td>
+ <td> On read, shows the status core - hibernated or not. On write, initiates
+ a remote wakeup of the device from Hibernation. </td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> mode_ch_tim_en </td>
+ <td> This bit is used to enable or disable the host core to wait for 200 PHY
+ clock cycles at the end of Resume to change the opmode signal to the PHY to 00
+ after Suspend or LPM. </td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> fr_interval </td>
+ <td> On read, shows the value of HFIR Frame Interval. On write, dynamically
+ reload HFIR register during runtime. The application can write a value to this
+ register only after the Port Enable bit of the Host Port Control and Status
+ register (HPRT.PrtEnaPort) has been set </td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> disconnect_us </td>
+ <td> On read, shows the status of disconnect_device_us. On write, sets disconnect_us
+ which causes soft disconnect for 100us. Applicable only for device mode of operation.</td>
+ <td> Read/Write</td>
+ </tr>
+
+ <tr>
+ <td> regdump </td>
+ <td> Dumps the contents of core registers.</td>
+ <td> Read</td>
+ </tr>
+
+ <tr>
+ <td> spramdump </td>
+ <td> Dumps the contents of core registers.</td>
+ <td> Read</td>
+ </tr>
+
+ <tr>
+ <td> hcddump </td>
+ <td> Dumps the current HCD state.</td>
+ <td> Read</td>
+ </tr>
+
+ <tr>
+ <td> hcd_frrem </td>
+ <td> Shows the average value of the Frame Remaining
+ field in the Host Frame Number/Frame Remaining register when an SOF interrupt
+ occurs. This can be used to determine the average interrupt latency. Also
+ shows the average Frame Remaining value for start_transfer and the "a" and
+ "b" sample points. The "a" and "b" sample points may be used during debugging
+ bto determine how long it takes to execute a section of the HCD code.</td>
+ <td> Read</td>
+ </tr>
+
+ <tr>
+ <td> rd_reg_test </td>
+ <td> Displays the time required to read the GNPTXFSIZ register many times
+ (the output shows the number of times the register is read).
+ <td> Read</td>
+ </tr>
+
+ <tr>
+ <td> wr_reg_test </td>
+ <td> Displays the time required to write the GNPTXFSIZ register many times
+ (the output shows the number of times the register is written).
+ <td> Read</td>
+ </tr>
+
+ <tr>
+ <td> lpm_response </td>
+ <td> Gets or sets lpm_response mode. Applicable only in device mode.
+ <td> Write</td>
+ </tr>
+
+ <tr>
+ <td> sleep_status </td>
+ <td> Shows sleep status of device.
+ <td> Read</td>
+ </tr>
+
+ </table>
+
+ Example usage:
+ To get the current mode:
+ cat /sys/devices/lm0/mode
+
+ To power down the USB:
+ echo 0 > /sys/devices/lm0/buspower
+ */
+
+#include "dwc_otg_os_dep.h"
+#include "dwc_os.h"
+#include "dwc_otg_driver.h"
+#include "dwc_otg_attr.h"
+#include "dwc_otg_core_if.h"
+#include "dwc_otg_pcd_if.h"
+#include "dwc_otg_hcd_if.h"
+
+/*
+ * MACROs for defining sysfs attribute
+ */
+#ifdef LM_INTERFACE
+
+#define DWC_OTG_DEVICE_ATTR_BITFIELD_SHOW(_otg_attr_name_,_string_) \
+static ssize_t _otg_attr_name_##_show (struct device *_dev, struct device_attribute *attr, char *buf) \
+{ \
+	struct lm_device *lm_dev = container_of(_dev, struct lm_device, dev); \
+	dwc_otg_device_t *otg_dev = lm_get_drvdata(lm_dev);		\
+	uint32_t val; \
+	val = dwc_otg_get_##_otg_attr_name_ (otg_dev->core_if); \
+	return sprintf (buf, "%s = 0x%x\n", _string_, val); \
+}
+#define DWC_OTG_DEVICE_ATTR_BITFIELD_STORE(_otg_attr_name_,_string_) \
+static ssize_t _otg_attr_name_##_store (struct device *_dev, struct device_attribute *attr, \
+					const char *buf, size_t count) \
+{ \
+	struct lm_device *lm_dev = container_of(_dev, struct lm_device, dev); \
+	dwc_otg_device_t *otg_dev = lm_get_drvdata(lm_dev); \
+	uint32_t set = simple_strtoul(buf, NULL, 16); \
+	dwc_otg_set_##_otg_attr_name_(otg_dev->core_if, set);\
+	return count; \
+}
+
+#elif defined(PCI_INTERFACE)
+
+#define DWC_OTG_DEVICE_ATTR_BITFIELD_SHOW(_otg_attr_name_,_string_) \
+static ssize_t _otg_attr_name_##_show (struct device *_dev, struct device_attribute *attr, char *buf) \
+{ \
+	dwc_otg_device_t *otg_dev = dev_get_drvdata(_dev);	\
+	uint32_t val; \
+	val = dwc_otg_get_##_otg_attr_name_ (otg_dev->core_if); \
+	return sprintf (buf, "%s = 0x%x\n", _string_, val); \
+}
+#define DWC_OTG_DEVICE_ATTR_BITFIELD_STORE(_otg_attr_name_,_string_) \
+static ssize_t _otg_attr_name_##_store (struct device *_dev, struct device_attribute *attr, \
+					const char *buf, size_t count) \
+{ \
+	dwc_otg_device_t *otg_dev = dev_get_drvdata(_dev);  \
+	uint32_t set = simple_strtoul(buf, NULL, 16); \
+	dwc_otg_set_##_otg_attr_name_(otg_dev->core_if, set);\
+	return count; \
+}
+
+#elif defined(PLATFORM_INTERFACE)
+
+#define DWC_OTG_DEVICE_ATTR_BITFIELD_SHOW(_otg_attr_name_,_string_) \
+static ssize_t _otg_attr_name_##_show (struct device *_dev, struct device_attribute *attr, char *buf) \
+{ \
+        struct platform_device *platform_dev = \
+                container_of(_dev, struct platform_device, dev); \
+        dwc_otg_device_t *otg_dev = platform_get_drvdata(platform_dev);  \
+	uint32_t val; \
+	DWC_PRINTF("%s(%p) -> platform_dev %p, otg_dev %p\n", \
+                    __func__, _dev, platform_dev, otg_dev); \
+	val = dwc_otg_get_##_otg_attr_name_ (otg_dev->core_if); \
+	return sprintf (buf, "%s = 0x%x\n", _string_, val); \
+}
+#define DWC_OTG_DEVICE_ATTR_BITFIELD_STORE(_otg_attr_name_,_string_) \
+static ssize_t _otg_attr_name_##_store (struct device *_dev, struct device_attribute *attr, \
+					const char *buf, size_t count) \
+{ \
+        struct platform_device *platform_dev = container_of(_dev, struct platform_device, dev); \
+        dwc_otg_device_t *otg_dev = platform_get_drvdata(platform_dev); \
+	uint32_t set = simple_strtoul(buf, NULL, 16); \
+	dwc_otg_set_##_otg_attr_name_(otg_dev->core_if, set);\
+	return count; \
+}
+#endif
+
+/*
+ * MACROs for defining sysfs attribute for 32-bit registers
+ */
+#ifdef LM_INTERFACE
+#define DWC_OTG_DEVICE_ATTR_REG_SHOW(_otg_attr_name_,_string_) \
+static ssize_t _otg_attr_name_##_show (struct device *_dev, struct device_attribute *attr, char *buf) \
+{ \
+	struct lm_device *lm_dev = container_of(_dev, struct lm_device, dev); \
+	dwc_otg_device_t *otg_dev = lm_get_drvdata(lm_dev); \
+	uint32_t val; \
+	val = dwc_otg_get_##_otg_attr_name_ (otg_dev->core_if); \
+	return sprintf (buf, "%s = 0x%08x\n", _string_, val); \
+}
+#define DWC_OTG_DEVICE_ATTR_REG_STORE(_otg_attr_name_,_string_) \
+static ssize_t _otg_attr_name_##_store (struct device *_dev, struct device_attribute *attr, \
+					const char *buf, size_t count) \
+{ \
+	struct lm_device *lm_dev = container_of(_dev, struct lm_device, dev); \
+	dwc_otg_device_t *otg_dev = lm_get_drvdata(lm_dev); \
+	uint32_t val = simple_strtoul(buf, NULL, 16); \
+	dwc_otg_set_##_otg_attr_name_ (otg_dev->core_if, val); \
+	return count; \
+}
+#elif defined(PCI_INTERFACE)
+#define DWC_OTG_DEVICE_ATTR_REG_SHOW(_otg_attr_name_,_string_) \
+static ssize_t _otg_attr_name_##_show (struct device *_dev, struct device_attribute *attr, char *buf) \
+{ \
+	dwc_otg_device_t *otg_dev = dev_get_drvdata(_dev);  \
+	uint32_t val; \
+	val = dwc_otg_get_##_otg_attr_name_ (otg_dev->core_if); \
+	return sprintf (buf, "%s = 0x%08x\n", _string_, val); \
+}
+#define DWC_OTG_DEVICE_ATTR_REG_STORE(_otg_attr_name_,_string_) \
+static ssize_t _otg_attr_name_##_store (struct device *_dev, struct device_attribute *attr, \
+					const char *buf, size_t count) \
+{ \
+	dwc_otg_device_t *otg_dev = dev_get_drvdata(_dev);  \
+	uint32_t val = simple_strtoul(buf, NULL, 16); \
+	dwc_otg_set_##_otg_attr_name_ (otg_dev->core_if, val); \
+	return count; \
+}
+
+#elif defined(PLATFORM_INTERFACE)
+#include "dwc_otg_dbg.h"
+#define DWC_OTG_DEVICE_ATTR_REG_SHOW(_otg_attr_name_,_string_) \
+static ssize_t _otg_attr_name_##_show (struct device *_dev, struct device_attribute *attr, char *buf) \
+{ \
+	struct platform_device *platform_dev = container_of(_dev, struct platform_device, dev); \
+	dwc_otg_device_t *otg_dev = platform_get_drvdata(platform_dev); \
+	uint32_t val; \
+	DWC_PRINTF("%s(%p) -> platform_dev %p, otg_dev %p\n", \
+                    __func__, _dev, platform_dev, otg_dev); \
+	val = dwc_otg_get_##_otg_attr_name_ (otg_dev->core_if); \
+	return sprintf (buf, "%s = 0x%08x\n", _string_, val); \
+}
+#define DWC_OTG_DEVICE_ATTR_REG_STORE(_otg_attr_name_,_string_) \
+static ssize_t _otg_attr_name_##_store (struct device *_dev, struct device_attribute *attr, \
+					const char *buf, size_t count) \
+{ \
+	struct platform_device *platform_dev = container_of(_dev, struct platform_device, dev); \
+	dwc_otg_device_t *otg_dev = platform_get_drvdata(platform_dev); \
+	uint32_t val = simple_strtoul(buf, NULL, 16); \
+	dwc_otg_set_##_otg_attr_name_ (otg_dev->core_if, val); \
+	return count; \
+}
+
+#endif
+
+#define DWC_OTG_DEVICE_ATTR_BITFIELD_RW(_otg_attr_name_,_string_) \
+DWC_OTG_DEVICE_ATTR_BITFIELD_SHOW(_otg_attr_name_,_string_) \
+DWC_OTG_DEVICE_ATTR_BITFIELD_STORE(_otg_attr_name_,_string_) \
+DEVICE_ATTR(_otg_attr_name_,0644,_otg_attr_name_##_show,_otg_attr_name_##_store);
+
+#define DWC_OTG_DEVICE_ATTR_BITFIELD_RO(_otg_attr_name_,_string_) \
+DWC_OTG_DEVICE_ATTR_BITFIELD_SHOW(_otg_attr_name_,_string_) \
+DEVICE_ATTR(_otg_attr_name_,0444,_otg_attr_name_##_show,NULL);
+
+#define DWC_OTG_DEVICE_ATTR_REG32_RW(_otg_attr_name_,_addr_,_string_) \
+DWC_OTG_DEVICE_ATTR_REG_SHOW(_otg_attr_name_,_string_) \
+DWC_OTG_DEVICE_ATTR_REG_STORE(_otg_attr_name_,_string_) \
+DEVICE_ATTR(_otg_attr_name_,0644,_otg_attr_name_##_show,_otg_attr_name_##_store);
+
+#define DWC_OTG_DEVICE_ATTR_REG32_RO(_otg_attr_name_,_addr_,_string_) \
+DWC_OTG_DEVICE_ATTR_REG_SHOW(_otg_attr_name_,_string_) \
+DEVICE_ATTR(_otg_attr_name_,0444,_otg_attr_name_##_show,NULL);
+
+/** @name Functions for Show/Store of Attributes */
+/**@{*/
+
+/**
+ * Helper function returning the otg_device structure of the given device
+ */
+static dwc_otg_device_t *dwc_otg_drvdev(struct device *_dev)
+{
+        dwc_otg_device_t *otg_dev;
+        DWC_OTG_GETDRVDEV(otg_dev, _dev);
+        return otg_dev;
+}
+
+/**
+ * Show the register offset of the Register Access.
+ */
+static ssize_t regoffset_show(struct device *_dev,
+			      struct device_attribute *attr, char *buf)
+{
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	return snprintf(buf, sizeof("0xFFFFFFFF\n") + 1, "0x%08x\n",
+			otg_dev->os_dep.reg_offset);
+}
+
+/**
+ * Set the register offset for the next Register Access 	Read/Write
+ */
+static ssize_t regoffset_store(struct device *_dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	uint32_t offset = simple_strtoul(buf, NULL, 16);
+#if defined(LM_INTERFACE) || defined(PLATFORM_INTERFACE)
+	if (offset < SZ_256K) {
+#elif  defined(PCI_INTERFACE)
+	if (offset < 0x00040000) {
+#endif
+		otg_dev->os_dep.reg_offset = offset;
+	} else {
+		dev_err(_dev, "invalid offset\n");
+	}
+
+	return count;
+}
+
+DEVICE_ATTR(regoffset, S_IRUGO | S_IWUSR, regoffset_show, regoffset_store);
+
+/**
+ * Show the value of the register at the offset in the reg_offset
+ * attribute.
+ */
+static ssize_t regvalue_show(struct device *_dev,
+			     struct device_attribute *attr, char *buf)
+{
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	uint32_t val;
+	volatile uint32_t *addr;
+
+	if (otg_dev->os_dep.reg_offset != 0xFFFFFFFF && 0 != otg_dev->os_dep.base) {
+		/* Calculate the address */
+		addr = (uint32_t *) (otg_dev->os_dep.reg_offset +
+				     (uint8_t *) otg_dev->os_dep.base);
+		val = DWC_READ_REG32(addr);
+		return snprintf(buf,
+				sizeof("Reg@0xFFFFFFFF = 0xFFFFFFFF\n") + 1,
+				"Reg@0x%06x = 0x%08x\n", otg_dev->os_dep.reg_offset,
+				val);
+	} else {
+		dev_err(_dev, "Invalid offset (0x%0x)\n", otg_dev->os_dep.reg_offset);
+		return sprintf(buf, "invalid offset\n");
+	}
+}
+
+/**
+ * Store the value in the register at the offset in the reg_offset
+ * attribute.
+ *
+ */
+static ssize_t regvalue_store(struct device *_dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	volatile uint32_t *addr;
+	uint32_t val = simple_strtoul(buf, NULL, 16);
+	//dev_dbg(_dev, "Offset=0x%08x Val=0x%08x\n", otg_dev->reg_offset, val);
+	if (otg_dev->os_dep.reg_offset != 0xFFFFFFFF && 0 != otg_dev->os_dep.base) {
+		/* Calculate the address */
+		addr = (uint32_t *) (otg_dev->os_dep.reg_offset +
+				     (uint8_t *) otg_dev->os_dep.base);
+		DWC_WRITE_REG32(addr, val);
+	} else {
+		dev_err(_dev, "Invalid Register Offset (0x%08x)\n",
+			otg_dev->os_dep.reg_offset);
+	}
+	return count;
+}
+
+DEVICE_ATTR(regvalue, S_IRUGO | S_IWUSR, regvalue_show, regvalue_store);
+
+/*
+ * Attributes
+ */
+DWC_OTG_DEVICE_ATTR_BITFIELD_RO(mode, "Mode");
+DWC_OTG_DEVICE_ATTR_BITFIELD_RW(hnpcapable, "HNPCapable");
+DWC_OTG_DEVICE_ATTR_BITFIELD_RW(srpcapable, "SRPCapable");
+DWC_OTG_DEVICE_ATTR_BITFIELD_RW(hsic_connect, "HSIC Connect");
+DWC_OTG_DEVICE_ATTR_BITFIELD_RW(inv_sel_hsic, "Invert Select HSIC");
+
+//DWC_OTG_DEVICE_ATTR_BITFIELD_RW(buspower,&(otg_dev->core_if->core_global_regs->gotgctl),(1<<8),8,"Mode");
+//DWC_OTG_DEVICE_ATTR_BITFIELD_RW(bussuspend,&(otg_dev->core_if->core_global_regs->gotgctl),(1<<8),8,"Mode");
+DWC_OTG_DEVICE_ATTR_BITFIELD_RO(busconnected, "Bus Connected");
+
+DWC_OTG_DEVICE_ATTR_REG32_RW(gotgctl, 0, "GOTGCTL");
+DWC_OTG_DEVICE_ATTR_REG32_RW(gusbcfg,
+			     &(otg_dev->core_if->core_global_regs->gusbcfg),
+			     "GUSBCFG");
+DWC_OTG_DEVICE_ATTR_REG32_RW(grxfsiz,
+			     &(otg_dev->core_if->core_global_regs->grxfsiz),
+			     "GRXFSIZ");
+DWC_OTG_DEVICE_ATTR_REG32_RW(gnptxfsiz,
+			     &(otg_dev->core_if->core_global_regs->gnptxfsiz),
+			     "GNPTXFSIZ");
+DWC_OTG_DEVICE_ATTR_REG32_RW(gpvndctl,
+			     &(otg_dev->core_if->core_global_regs->gpvndctl),
+			     "GPVNDCTL");
+DWC_OTG_DEVICE_ATTR_REG32_RW(ggpio,
+			     &(otg_dev->core_if->core_global_regs->ggpio),
+			     "GGPIO");
+DWC_OTG_DEVICE_ATTR_REG32_RW(guid, &(otg_dev->core_if->core_global_regs->guid),
+			     "GUID");
+DWC_OTG_DEVICE_ATTR_REG32_RO(gsnpsid,
+			     &(otg_dev->core_if->core_global_regs->gsnpsid),
+			     "GSNPSID");
+DWC_OTG_DEVICE_ATTR_BITFIELD_RW(devspeed, "Device Speed");
+DWC_OTG_DEVICE_ATTR_BITFIELD_RO(enumspeed, "Device Enumeration Speed");
+
+DWC_OTG_DEVICE_ATTR_REG32_RO(hptxfsiz,
+			     &(otg_dev->core_if->core_global_regs->hptxfsiz),
+			     "HPTXFSIZ");
+DWC_OTG_DEVICE_ATTR_REG32_RW(hprt0, otg_dev->core_if->host_if->hprt0, "HPRT0");
+
+/**
+ * @todo Add code to initiate the HNP.
+ */
+/**
+ * Show the HNP status bit
+ */
+static ssize_t hnp_show(struct device *_dev,
+			struct device_attribute *attr, char *buf)
+{
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	return sprintf(buf, "HstNegScs = 0x%x\n",
+		       dwc_otg_get_hnpstatus(otg_dev->core_if));
+}
+
+/**
+ * Set the HNP Request bit
+ */
+static ssize_t hnp_store(struct device *_dev,
+			 struct device_attribute *attr,
+			 const char *buf, size_t count)
+{
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	uint32_t in = simple_strtoul(buf, NULL, 16);
+	dwc_otg_set_hnpreq(otg_dev->core_if, in);
+	return count;
+}
+
+DEVICE_ATTR(hnp, 0644, hnp_show, hnp_store);
+
+/**
+ * @todo Add code to initiate the SRP.
+ */
+/**
+ * Show the SRP status bit
+ */
+static ssize_t srp_show(struct device *_dev,
+			struct device_attribute *attr, char *buf)
+{
+#ifndef DWC_HOST_ONLY
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	return sprintf(buf, "SesReqScs = 0x%x\n",
+		       dwc_otg_get_srpstatus(otg_dev->core_if));
+#else
+	return sprintf(buf, "Host Only Mode!\n");
+#endif
+}
+
+/**
+ * Set the SRP Request bit
+ */
+static ssize_t srp_store(struct device *_dev,
+			 struct device_attribute *attr,
+			 const char *buf, size_t count)
+{
+#ifndef DWC_HOST_ONLY
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	dwc_otg_pcd_initiate_srp(otg_dev->pcd);
+#endif
+	return count;
+}
+
+DEVICE_ATTR(srp, 0644, srp_show, srp_store);
+
+/**
+ * @todo Need to do more for power on/off?
+ */
+/**
+ * Show the Bus Power status
+ */
+static ssize_t buspower_show(struct device *_dev,
+			     struct device_attribute *attr, char *buf)
+{
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	return sprintf(buf, "Bus Power = 0x%x\n",
+		       dwc_otg_get_prtpower(otg_dev->core_if));
+}
+
+/**
+ * Set the Bus Power status
+ */
+static ssize_t buspower_store(struct device *_dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	uint32_t on = simple_strtoul(buf, NULL, 16);
+	dwc_otg_set_prtpower(otg_dev->core_if, on);
+	return count;
+}
+
+DEVICE_ATTR(buspower, 0644, buspower_show, buspower_store);
+
+/**
+ * @todo Need to do more for suspend?
+ */
+/**
+ * Show the Bus Suspend status
+ */
+static ssize_t bussuspend_show(struct device *_dev,
+			       struct device_attribute *attr, char *buf)
+{
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	return sprintf(buf, "Bus Suspend = 0x%x\n",
+		       dwc_otg_get_prtsuspend(otg_dev->core_if));
+}
+
+/**
+ * Set the Bus Suspend status
+ */
+static ssize_t bussuspend_store(struct device *_dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	uint32_t in = simple_strtoul(buf, NULL, 16);
+	dwc_otg_set_prtsuspend(otg_dev->core_if, in);
+	return count;
+}
+
+DEVICE_ATTR(bussuspend, 0644, bussuspend_show, bussuspend_store);
+
+/**
+ * Show the Mode Change Ready Timer status
+ */
+static ssize_t mode_ch_tim_en_show(struct device *_dev,
+				   struct device_attribute *attr, char *buf)
+{
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	return sprintf(buf, "Mode Change Ready Timer Enable = 0x%x\n",
+		       dwc_otg_get_mode_ch_tim(otg_dev->core_if));
+}
+
+/**
+ * Set the Mode Change Ready Timer status
+ */
+static ssize_t mode_ch_tim_en_store(struct device *_dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	uint32_t in = simple_strtoul(buf, NULL, 16);
+	dwc_otg_set_mode_ch_tim(otg_dev->core_if, in);
+	return count;
+}
+
+DEVICE_ATTR(mode_ch_tim_en, 0644, mode_ch_tim_en_show, mode_ch_tim_en_store);
+
+/**
+ * Show the value of HFIR Frame Interval bitfield
+ */
+static ssize_t fr_interval_show(struct device *_dev,
+				struct device_attribute *attr, char *buf)
+{
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	return sprintf(buf, "Frame Interval = 0x%x\n",
+		       dwc_otg_get_fr_interval(otg_dev->core_if));
+}
+
+/**
+ * Set the HFIR Frame Interval value
+ */
+static ssize_t fr_interval_store(struct device *_dev,
+				 struct device_attribute *attr,
+				 const char *buf, size_t count)
+{
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	uint32_t in = simple_strtoul(buf, NULL, 10);
+	dwc_otg_set_fr_interval(otg_dev->core_if, in);
+	return count;
+}
+
+DEVICE_ATTR(fr_interval, 0644, fr_interval_show, fr_interval_store);
+
+/**
+ * Show the status of Remote Wakeup.
+ */
+static ssize_t remote_wakeup_show(struct device *_dev,
+				  struct device_attribute *attr, char *buf)
+{
+#ifndef DWC_HOST_ONLY
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+
+	return sprintf(buf,
+		       "Remote Wakeup Sig = %d Enabled = %d LPM Remote Wakeup = %d\n",
+		       dwc_otg_get_remotewakesig(otg_dev->core_if),
+		       dwc_otg_pcd_get_rmwkup_enable(otg_dev->pcd),
+		       dwc_otg_get_lpm_remotewakeenabled(otg_dev->core_if));
+#else
+	return sprintf(buf, "Host Only Mode!\n");
+#endif /* DWC_HOST_ONLY */
+}
+
+/**
+ * Initiate a remote wakeup of the host.  The Device control register
+ * Remote Wakeup Signal bit is written if the PCD Remote wakeup enable
+ * flag is set.
+ *
+ */
+static ssize_t remote_wakeup_store(struct device *_dev,
+				   struct device_attribute *attr,
+				   const char *buf, size_t count)
+{
+#ifndef DWC_HOST_ONLY
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	uint32_t val = simple_strtoul(buf, NULL, 16);
+
+	if (val & 1) {
+		dwc_otg_pcd_remote_wakeup(otg_dev->pcd, 1);
+	} else {
+		dwc_otg_pcd_remote_wakeup(otg_dev->pcd, 0);
+	}
+#endif /* DWC_HOST_ONLY */
+	return count;
+}
+
+DEVICE_ATTR(remote_wakeup, S_IRUGO | S_IWUSR, remote_wakeup_show,
+	    remote_wakeup_store);
+
+/**
+ * Show the whether core is hibernated or not.
+ */
+static ssize_t rem_wakeup_pwrdn_show(struct device *_dev,
+				     struct device_attribute *attr, char *buf)
+{
+#ifndef DWC_HOST_ONLY
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+
+	if (dwc_otg_get_core_state(otg_dev->core_if)) {
+		DWC_PRINTF("Core is in hibernation\n");
+	} else {
+		DWC_PRINTF("Core is not in hibernation\n");
+	}
+#endif /* DWC_HOST_ONLY */
+	return 0;
+}
+
+extern int dwc_otg_device_hibernation_restore(dwc_otg_core_if_t * core_if,
+					      int rem_wakeup, int reset);
+
+/**
+ * Initiate a remote wakeup of the device to exit from hibernation.
+ */
+static ssize_t rem_wakeup_pwrdn_store(struct device *_dev,
+				      struct device_attribute *attr,
+				      const char *buf, size_t count)
+{
+#ifndef DWC_HOST_ONLY
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	dwc_otg_device_hibernation_restore(otg_dev->core_if, 1, 0);
+#endif
+	return count;
+}
+
+DEVICE_ATTR(rem_wakeup_pwrdn, S_IRUGO | S_IWUSR, rem_wakeup_pwrdn_show,
+	    rem_wakeup_pwrdn_store);
+
+static ssize_t disconnect_us(struct device *_dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t count)
+{
+
+#ifndef DWC_HOST_ONLY
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	uint32_t val = simple_strtoul(buf, NULL, 16);
+	DWC_PRINTF("The Passed value is %04x\n", val);
+
+	dwc_otg_pcd_disconnect_us(otg_dev->pcd, 50);
+
+#endif /* DWC_HOST_ONLY */
+	return count;
+}
+
+DEVICE_ATTR(disconnect_us, S_IWUSR, 0, disconnect_us);
+
+/**
+ * Dump global registers and either host or device registers (depending on the
+ * current mode of the core).
+ */
+static ssize_t regdump_show(struct device *_dev,
+			    struct device_attribute *attr, char *buf)
+{
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+
+	dwc_otg_dump_global_registers(otg_dev->core_if);
+	if (dwc_otg_is_host_mode(otg_dev->core_if)) {
+		dwc_otg_dump_host_registers(otg_dev->core_if);
+	} else {
+		dwc_otg_dump_dev_registers(otg_dev->core_if);
+
+	}
+	return sprintf(buf, "Register Dump\n");
+}
+
+DEVICE_ATTR(regdump, S_IRUGO, regdump_show, 0);
+
+/**
+ * Dump global registers and either host or device registers (depending on the
+ * current mode of the core).
+ */
+static ssize_t spramdump_show(struct device *_dev,
+			      struct device_attribute *attr, char *buf)
+{
+#if 0
+	dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+
+	dwc_otg_dump_spram(otg_dev->core_if);
+#endif
+
+	return sprintf(buf, "SPRAM Dump\n");
+}
+
+DEVICE_ATTR(spramdump, S_IRUGO, spramdump_show, 0);
+
+/**
+ * Dump the current hcd state.
+ */
+static ssize_t hcddump_show(struct device *_dev,
+			    struct device_attribute *attr, char *buf)
+{
+#ifndef DWC_DEVICE_ONLY
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	dwc_otg_hcd_dump_state(otg_dev->hcd);
+#endif /* DWC_DEVICE_ONLY */
+	return sprintf(buf, "HCD Dump\n");
+}
+
+DEVICE_ATTR(hcddump, S_IRUGO, hcddump_show, 0);
+
+/**
+ * Dump the average frame remaining at SOF. This can be used to
+ * determine average interrupt latency. Frame remaining is also shown for
+ * start transfer and two additional sample points.
+ */
+static ssize_t hcd_frrem_show(struct device *_dev,
+			      struct device_attribute *attr, char *buf)
+{
+#ifndef DWC_DEVICE_ONLY
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+
+	dwc_otg_hcd_dump_frrem(otg_dev->hcd);
+#endif /* DWC_DEVICE_ONLY */
+	return sprintf(buf, "HCD Dump Frame Remaining\n");
+}
+
+DEVICE_ATTR(hcd_frrem, S_IRUGO, hcd_frrem_show, 0);
+
+/**
+ * Displays the time required to read the GNPTXFSIZ register many times (the
+ * output shows the number of times the register is read).
+ */
+#define RW_REG_COUNT 10000000
+#define MSEC_PER_JIFFIE 1000/HZ
+static ssize_t rd_reg_test_show(struct device *_dev,
+				struct device_attribute *attr, char *buf)
+{
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	int i;
+	int time;
+	int start_jiffies;
+
+	printk("HZ %d, MSEC_PER_JIFFIE %d, loops_per_jiffy %lu\n",
+	       HZ, MSEC_PER_JIFFIE, loops_per_jiffy);
+	start_jiffies = jiffies;
+	for (i = 0; i < RW_REG_COUNT; i++) {
+		dwc_otg_get_gnptxfsiz(otg_dev->core_if);
+	}
+	time = jiffies - start_jiffies;
+	return sprintf(buf,
+		       "Time to read GNPTXFSIZ reg %d times: %d msecs (%d jiffies)\n",
+		       RW_REG_COUNT, time * MSEC_PER_JIFFIE, time);
+}
+
+DEVICE_ATTR(rd_reg_test, S_IRUGO, rd_reg_test_show, 0);
+
+/**
+ * Displays the time required to write the GNPTXFSIZ register many times (the
+ * output shows the number of times the register is written).
+ */
+static ssize_t wr_reg_test_show(struct device *_dev,
+				struct device_attribute *attr, char *buf)
+{
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	uint32_t reg_val;
+	int i;
+	int time;
+	int start_jiffies;
+
+	printk("HZ %d, MSEC_PER_JIFFIE %d, loops_per_jiffy %lu\n",
+	       HZ, MSEC_PER_JIFFIE, loops_per_jiffy);
+	reg_val = dwc_otg_get_gnptxfsiz(otg_dev->core_if);
+	start_jiffies = jiffies;
+	for (i = 0; i < RW_REG_COUNT; i++) {
+		dwc_otg_set_gnptxfsiz(otg_dev->core_if, reg_val);
+	}
+	time = jiffies - start_jiffies;
+	return sprintf(buf,
+		       "Time to write GNPTXFSIZ reg %d times: %d msecs (%d jiffies)\n",
+		       RW_REG_COUNT, time * MSEC_PER_JIFFIE, time);
+}
+
+DEVICE_ATTR(wr_reg_test, S_IRUGO, wr_reg_test_show, 0);
+
+#ifdef CONFIG_USB_DWC_OTG_LPM
+
+/**
+* Show the lpm_response attribute.
+*/
+static ssize_t lpmresp_show(struct device *_dev,
+			    struct device_attribute *attr, char *buf)
+{
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+
+	if (!dwc_otg_get_param_lpm_enable(otg_dev->core_if))
+		return sprintf(buf, "** LPM is DISABLED **\n");
+
+	if (!dwc_otg_is_device_mode(otg_dev->core_if)) {
+		return sprintf(buf, "** Current mode is not device mode\n");
+	}
+	return sprintf(buf, "lpm_response = %d\n",
+		       dwc_otg_get_lpmresponse(otg_dev->core_if));
+}
+
+/**
+* Store the lpm_response attribute.
+*/
+static ssize_t lpmresp_store(struct device *_dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t count)
+{
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	uint32_t val = simple_strtoul(buf, NULL, 16);
+
+	if (!dwc_otg_get_param_lpm_enable(otg_dev->core_if)) {
+		return 0;
+	}
+
+	if (!dwc_otg_is_device_mode(otg_dev->core_if)) {
+		return 0;
+	}
+
+	dwc_otg_set_lpmresponse(otg_dev->core_if, val);
+	return count;
+}
+
+DEVICE_ATTR(lpm_response, S_IRUGO | S_IWUSR, lpmresp_show, lpmresp_store);
+
+/**
+* Show the sleep_status attribute.
+*/
+static ssize_t sleepstatus_show(struct device *_dev,
+				struct device_attribute *attr, char *buf)
+{
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	return sprintf(buf, "Sleep Status = %d\n",
+		       dwc_otg_get_lpm_portsleepstatus(otg_dev->core_if));
+}
+
+/**
+ * Store the sleep_status attribure.
+ */
+static ssize_t sleepstatus_store(struct device *_dev,
+				 struct device_attribute *attr,
+				 const char *buf, size_t count)
+{
+        dwc_otg_device_t *otg_dev = dwc_otg_drvdev(_dev);
+	dwc_otg_core_if_t *core_if = otg_dev->core_if;
+
+	if (dwc_otg_get_lpm_portsleepstatus(otg_dev->core_if)) {
+		if (dwc_otg_is_host_mode(core_if)) {
+
+			DWC_PRINTF("Host initiated resume\n");
+			dwc_otg_set_prtresume(otg_dev->core_if, 1);
+		}
+	}
+
+	return count;
+}
+
+DEVICE_ATTR(sleep_status, S_IRUGO | S_IWUSR, sleepstatus_show,
+	    sleepstatus_store);
+
+#endif /* CONFIG_USB_DWC_OTG_LPM_ENABLE */
+
+/**@}*/
+
+/**
+ * Create the device files
+ */
+void dwc_otg_attr_create(
+#ifdef LM_INTERFACE
+	struct lm_device *dev
+#elif  defined(PCI_INTERFACE)
+	struct pci_dev *dev
+#elif  defined(PLATFORM_INTERFACE)
+        struct platform_device *dev
+#endif
+    )
+{
+	int error;
+
+	error = device_create_file(&dev->dev, &dev_attr_regoffset);
+	error = device_create_file(&dev->dev, &dev_attr_regvalue);
+	error = device_create_file(&dev->dev, &dev_attr_mode);
+	error = device_create_file(&dev->dev, &dev_attr_hnpcapable);
+	error = device_create_file(&dev->dev, &dev_attr_srpcapable);
+	error = device_create_file(&dev->dev, &dev_attr_hsic_connect);
+	error = device_create_file(&dev->dev, &dev_attr_inv_sel_hsic);
+	error = device_create_file(&dev->dev, &dev_attr_hnp);
+	error = device_create_file(&dev->dev, &dev_attr_srp);
+	error = device_create_file(&dev->dev, &dev_attr_buspower);
+	error = device_create_file(&dev->dev, &dev_attr_bussuspend);
+	error = device_create_file(&dev->dev, &dev_attr_mode_ch_tim_en);
+	error = device_create_file(&dev->dev, &dev_attr_fr_interval);
+	error = device_create_file(&dev->dev, &dev_attr_busconnected);
+	error = device_create_file(&dev->dev, &dev_attr_gotgctl);
+	error = device_create_file(&dev->dev, &dev_attr_gusbcfg);
+	error = device_create_file(&dev->dev, &dev_attr_grxfsiz);
+	error = device_create_file(&dev->dev, &dev_attr_gnptxfsiz);
+	error = device_create_file(&dev->dev, &dev_attr_gpvndctl);
+	error = device_create_file(&dev->dev, &dev_attr_ggpio);
+	error = device_create_file(&dev->dev, &dev_attr_guid);
+	error = device_create_file(&dev->dev, &dev_attr_gsnpsid);
+	error = device_create_file(&dev->dev, &dev_attr_devspeed);
+	error = device_create_file(&dev->dev, &dev_attr_enumspeed);
+	error = device_create_file(&dev->dev, &dev_attr_hptxfsiz);
+	error = device_create_file(&dev->dev, &dev_attr_hprt0);
+	error = device_create_file(&dev->dev, &dev_attr_remote_wakeup);
+	error = device_create_file(&dev->dev, &dev_attr_rem_wakeup_pwrdn);
+	error = device_create_file(&dev->dev, &dev_attr_disconnect_us);
+	error = device_create_file(&dev->dev, &dev_attr_regdump);
+	error = device_create_file(&dev->dev, &dev_attr_spramdump);
+	error = device_create_file(&dev->dev, &dev_attr_hcddump);
+	error = device_create_file(&dev->dev, &dev_attr_hcd_frrem);
+	error = device_create_file(&dev->dev, &dev_attr_rd_reg_test);
+	error = device_create_file(&dev->dev, &dev_attr_wr_reg_test);
+#ifdef CONFIG_USB_DWC_OTG_LPM
+	error = device_create_file(&dev->dev, &dev_attr_lpm_response);
+	error = device_create_file(&dev->dev, &dev_attr_sleep_status);
+#endif
+}
+
+/**
+ * Remove the device files
+ */
+void dwc_otg_attr_remove(
+#ifdef LM_INTERFACE
+	struct lm_device *dev
+#elif  defined(PCI_INTERFACE)
+	struct pci_dev *dev
+#elif  defined(PLATFORM_INTERFACE)
+	struct platform_device *dev
+#endif
+    )
+{
+	device_remove_file(&dev->dev, &dev_attr_regoffset);
+	device_remove_file(&dev->dev, &dev_attr_regvalue);
+	device_remove_file(&dev->dev, &dev_attr_mode);
+	device_remove_file(&dev->dev, &dev_attr_hnpcapable);
+	device_remove_file(&dev->dev, &dev_attr_srpcapable);
+	device_remove_file(&dev->dev, &dev_attr_hsic_connect);
+	device_remove_file(&dev->dev, &dev_attr_inv_sel_hsic);
+	device_remove_file(&dev->dev, &dev_attr_hnp);
+	device_remove_file(&dev->dev, &dev_attr_srp);
+	device_remove_file(&dev->dev, &dev_attr_buspower);
+	device_remove_file(&dev->dev, &dev_attr_bussuspend);
+	device_remove_file(&dev->dev, &dev_attr_mode_ch_tim_en);
+	device_remove_file(&dev->dev, &dev_attr_fr_interval);
+	device_remove_file(&dev->dev, &dev_attr_busconnected);
+	device_remove_file(&dev->dev, &dev_attr_gotgctl);
+	device_remove_file(&dev->dev, &dev_attr_gusbcfg);
+	device_remove_file(&dev->dev, &dev_attr_grxfsiz);
+	device_remove_file(&dev->dev, &dev_attr_gnptxfsiz);
+	device_remove_file(&dev->dev, &dev_attr_gpvndctl);
+	device_remove_file(&dev->dev, &dev_attr_ggpio);
+	device_remove_file(&dev->dev, &dev_attr_guid);
+	device_remove_file(&dev->dev, &dev_attr_gsnpsid);
+	device_remove_file(&dev->dev, &dev_attr_devspeed);
+	device_remove_file(&dev->dev, &dev_attr_enumspeed);
+	device_remove_file(&dev->dev, &dev_attr_hptxfsiz);
+	device_remove_file(&dev->dev, &dev_attr_hprt0);
+	device_remove_file(&dev->dev, &dev_attr_remote_wakeup);
+	device_remove_file(&dev->dev, &dev_attr_rem_wakeup_pwrdn);
+	device_remove_file(&dev->dev, &dev_attr_disconnect_us);
+	device_remove_file(&dev->dev, &dev_attr_regdump);
+	device_remove_file(&dev->dev, &dev_attr_spramdump);
+	device_remove_file(&dev->dev, &dev_attr_hcddump);
+	device_remove_file(&dev->dev, &dev_attr_hcd_frrem);
+	device_remove_file(&dev->dev, &dev_attr_rd_reg_test);
+	device_remove_file(&dev->dev, &dev_attr_wr_reg_test);
+#ifdef CONFIG_USB_DWC_OTG_LPM
+	device_remove_file(&dev->dev, &dev_attr_lpm_response);
+	device_remove_file(&dev->dev, &dev_attr_sleep_status);
+#endif
+}
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_attr.h b/drivers/usb/host/dwc_otg/dwc_otg_attr.h
new file mode 100644
index 000000000000..e10b67f97c52
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_attr.h
@@ -0,0 +1,89 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_attr.h $
+ * $Revision: #13 $
+ * $Date: 2010/06/21 $
+ * $Change: 1532021 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+
+#if !defined(__DWC_OTG_ATTR_H__)
+#define __DWC_OTG_ATTR_H__
+
+/** @file
+ * This file contains the interface to the Linux device attributes.
+ */
+extern struct device_attribute dev_attr_regoffset;
+extern struct device_attribute dev_attr_regvalue;
+
+extern struct device_attribute dev_attr_mode;
+extern struct device_attribute dev_attr_hnpcapable;
+extern struct device_attribute dev_attr_srpcapable;
+extern struct device_attribute dev_attr_hnp;
+extern struct device_attribute dev_attr_srp;
+extern struct device_attribute dev_attr_buspower;
+extern struct device_attribute dev_attr_bussuspend;
+extern struct device_attribute dev_attr_mode_ch_tim_en;
+extern struct device_attribute dev_attr_fr_interval;
+extern struct device_attribute dev_attr_busconnected;
+extern struct device_attribute dev_attr_gotgctl;
+extern struct device_attribute dev_attr_gusbcfg;
+extern struct device_attribute dev_attr_grxfsiz;
+extern struct device_attribute dev_attr_gnptxfsiz;
+extern struct device_attribute dev_attr_gpvndctl;
+extern struct device_attribute dev_attr_ggpio;
+extern struct device_attribute dev_attr_guid;
+extern struct device_attribute dev_attr_gsnpsid;
+extern struct device_attribute dev_attr_devspeed;
+extern struct device_attribute dev_attr_enumspeed;
+extern struct device_attribute dev_attr_hptxfsiz;
+extern struct device_attribute dev_attr_hprt0;
+#ifdef CONFIG_USB_DWC_OTG_LPM
+extern struct device_attribute dev_attr_lpm_response;
+extern struct device_attribute devi_attr_sleep_status;
+#endif
+
+void dwc_otg_attr_create(
+#ifdef LM_INTERFACE
+				struct lm_device *dev
+#elif  defined(PCI_INTERFACE)
+				struct pci_dev *dev
+#elif  defined(PLATFORM_INTERFACE)
+	struct platform_device *dev
+#endif
+    );
+
+void dwc_otg_attr_remove(
+#ifdef LM_INTERFACE
+				struct lm_device *dev
+#elif  defined(PCI_INTERFACE)
+				struct pci_dev *dev
+#elif  defined(PLATFORM_INTERFACE)
+	struct platform_device *dev
+#endif
+    );
+#endif
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_cfi.c b/drivers/usb/host/dwc_otg/dwc_otg_cfi.c
new file mode 100644
index 000000000000..e73016ef863d
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_cfi.c
@@ -0,0 +1,1874 @@
+/* ==========================================================================
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+
+/** @file
+ *
+ * This file contains the most of the CFI(Core Feature Interface)
+ * implementation for the OTG.
+ */
+
+#ifdef DWC_UTE_CFI
+
+#include "dwc_otg_pcd.h"
+#include "dwc_otg_cfi.h"
+
+/** This definition should actually migrate to the Portability Library */
+#define DWC_CONSTANT_CPU_TO_LE16(x) (x)
+
+static int cfi_core_features_buf(uint8_t * buf, uint16_t buflen);
+static int cfi_get_feature_value(uint8_t * buf, uint16_t buflen,
+				 struct dwc_otg_pcd *pcd,
+				 struct cfi_usb_ctrlrequest *ctrl_req);
+static int cfi_set_feature_value(struct dwc_otg_pcd *pcd);
+static int cfi_ep_get_sg_val(uint8_t * buf, struct dwc_otg_pcd *pcd,
+			     struct cfi_usb_ctrlrequest *req);
+static int cfi_ep_get_concat_val(uint8_t * buf, struct dwc_otg_pcd *pcd,
+				 struct cfi_usb_ctrlrequest *req);
+static int cfi_ep_get_align_val(uint8_t * buf, struct dwc_otg_pcd *pcd,
+				struct cfi_usb_ctrlrequest *req);
+static int cfi_preproc_reset(struct dwc_otg_pcd *pcd,
+			     struct cfi_usb_ctrlrequest *req);
+static void cfi_free_ep_bs_dyn_data(cfi_ep_t * cfiep);
+
+static uint16_t get_dfifo_size(dwc_otg_core_if_t * core_if);
+static int32_t get_rxfifo_size(dwc_otg_core_if_t * core_if, uint16_t wValue);
+static int32_t get_txfifo_size(struct dwc_otg_pcd *pcd, uint16_t wValue);
+
+static uint8_t resize_fifos(dwc_otg_core_if_t * core_if);
+
+/** This is the header of the all features descriptor */
+static cfi_all_features_header_t all_props_desc_header = {
+	.wVersion = DWC_CONSTANT_CPU_TO_LE16(0x100),
+	.wCoreID = DWC_CONSTANT_CPU_TO_LE16(CFI_CORE_ID_OTG),
+	.wNumFeatures = DWC_CONSTANT_CPU_TO_LE16(9),
+};
+
+/** This is an array of statically allocated feature descriptors */
+static cfi_feature_desc_header_t prop_descs[] = {
+
+	/* FT_ID_DMA_MODE */
+	{
+	 .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_DMA_MODE),
+	 .bmAttributes = CFI_FEATURE_ATTR_RW,
+	 .wDataLength = DWC_CONSTANT_CPU_TO_LE16(1),
+	 },
+
+	/* FT_ID_DMA_BUFFER_SETUP */
+	{
+	 .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_DMA_BUFFER_SETUP),
+	 .bmAttributes = CFI_FEATURE_ATTR_RW,
+	 .wDataLength = DWC_CONSTANT_CPU_TO_LE16(6),
+	 },
+
+	/* FT_ID_DMA_BUFF_ALIGN */
+	{
+	 .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_DMA_BUFF_ALIGN),
+	 .bmAttributes = CFI_FEATURE_ATTR_RW,
+	 .wDataLength = DWC_CONSTANT_CPU_TO_LE16(2),
+	 },
+
+	/* FT_ID_DMA_CONCAT_SETUP */
+	{
+	 .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_DMA_CONCAT_SETUP),
+	 .bmAttributes = CFI_FEATURE_ATTR_RW,
+	 //.wDataLength  = DWC_CONSTANT_CPU_TO_LE16(6),
+	 },
+
+	/* FT_ID_DMA_CIRCULAR */
+	{
+	 .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_DMA_CIRCULAR),
+	 .bmAttributes = CFI_FEATURE_ATTR_RW,
+	 .wDataLength = DWC_CONSTANT_CPU_TO_LE16(6),
+	 },
+
+	/* FT_ID_THRESHOLD_SETUP */
+	{
+	 .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_THRESHOLD_SETUP),
+	 .bmAttributes = CFI_FEATURE_ATTR_RW,
+	 .wDataLength = DWC_CONSTANT_CPU_TO_LE16(6),
+	 },
+
+	/* FT_ID_DFIFO_DEPTH */
+	{
+	 .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_DFIFO_DEPTH),
+	 .bmAttributes = CFI_FEATURE_ATTR_RO,
+	 .wDataLength = DWC_CONSTANT_CPU_TO_LE16(2),
+	 },
+
+	/* FT_ID_TX_FIFO_DEPTH */
+	{
+	 .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_TX_FIFO_DEPTH),
+	 .bmAttributes = CFI_FEATURE_ATTR_RW,
+	 .wDataLength = DWC_CONSTANT_CPU_TO_LE16(2),
+	 },
+
+	/* FT_ID_RX_FIFO_DEPTH */
+	{
+	 .wFeatureID = DWC_CONSTANT_CPU_TO_LE16(FT_ID_RX_FIFO_DEPTH),
+	 .bmAttributes = CFI_FEATURE_ATTR_RW,
+	 .wDataLength = DWC_CONSTANT_CPU_TO_LE16(2),
+	 }
+};
+
+/** The table of feature names */
+cfi_string_t prop_name_table[] = {
+	{FT_ID_DMA_MODE, "dma_mode"},
+	{FT_ID_DMA_BUFFER_SETUP, "buffer_setup"},
+	{FT_ID_DMA_BUFF_ALIGN, "buffer_align"},
+	{FT_ID_DMA_CONCAT_SETUP, "concat_setup"},
+	{FT_ID_DMA_CIRCULAR, "buffer_circular"},
+	{FT_ID_THRESHOLD_SETUP, "threshold_setup"},
+	{FT_ID_DFIFO_DEPTH, "dfifo_depth"},
+	{FT_ID_TX_FIFO_DEPTH, "txfifo_depth"},
+	{FT_ID_RX_FIFO_DEPTH, "rxfifo_depth"},
+	{}
+};
+
+/************************************************************************/
+
+/**
+ * Returns the name of the feature by its ID
+ * or NULL if no featute ID matches.
+ *
+ */
+const uint8_t *get_prop_name(uint16_t prop_id, int *len)
+{
+	cfi_string_t *pstr;
+	*len = 0;
+
+	for (pstr = prop_name_table; pstr && pstr->s; pstr++) {
+		if (pstr->id == prop_id) {
+			*len = DWC_STRLEN(pstr->s);
+			return pstr->s;
+		}
+	}
+	return NULL;
+}
+
+/**
+ * This function handles all CFI specific control requests.
+ *
+ * Return a negative value to stall the DCE.
+ */
+int cfi_setup(struct dwc_otg_pcd *pcd, struct cfi_usb_ctrlrequest *ctrl)
+{
+	int retval = 0;
+	dwc_otg_pcd_ep_t *ep = NULL;
+	cfiobject_t *cfi = pcd->cfi;
+	struct dwc_otg_core_if *coreif = GET_CORE_IF(pcd);
+	uint16_t wLen = DWC_LE16_TO_CPU(&ctrl->wLength);
+	uint16_t wValue = DWC_LE16_TO_CPU(&ctrl->wValue);
+	uint16_t wIndex = DWC_LE16_TO_CPU(&ctrl->wIndex);
+	uint32_t regaddr = 0;
+	uint32_t regval = 0;
+
+	/* Save this Control Request in the CFI object.
+	 * The data field will be assigned in the data stage completion CB function.
+	 */
+	cfi->ctrl_req = *ctrl;
+	cfi->ctrl_req.data = NULL;
+
+	cfi->need_gadget_att = 0;
+	cfi->need_status_in_complete = 0;
+
+	switch (ctrl->bRequest) {
+	case VEN_CORE_GET_FEATURES:
+		retval = cfi_core_features_buf(cfi->buf_in.buf, CFI_IN_BUF_LEN);
+		if (retval >= 0) {
+			//dump_msg(cfi->buf_in.buf, retval);
+			ep = &pcd->ep0;
+
+			retval = min((uint16_t) retval, wLen);
+			/* Transfer this buffer to the host through the EP0-IN EP */
+			ep->dwc_ep.dma_addr = cfi->buf_in.addr;
+			ep->dwc_ep.start_xfer_buff = cfi->buf_in.buf;
+			ep->dwc_ep.xfer_buff = cfi->buf_in.buf;
+			ep->dwc_ep.xfer_len = retval;
+			ep->dwc_ep.xfer_count = 0;
+			ep->dwc_ep.sent_zlp = 0;
+			ep->dwc_ep.total_len = ep->dwc_ep.xfer_len;
+
+			pcd->ep0_pending = 1;
+			dwc_otg_ep0_start_transfer(coreif, &ep->dwc_ep);
+		}
+		retval = 0;
+		break;
+
+	case VEN_CORE_GET_FEATURE:
+		CFI_INFO("VEN_CORE_GET_FEATURE\n");
+		retval = cfi_get_feature_value(cfi->buf_in.buf, CFI_IN_BUF_LEN,
+					       pcd, ctrl);
+		if (retval >= 0) {
+			ep = &pcd->ep0;
+
+			retval = min((uint16_t) retval, wLen);
+			/* Transfer this buffer to the host through the EP0-IN EP */
+			ep->dwc_ep.dma_addr = cfi->buf_in.addr;
+			ep->dwc_ep.start_xfer_buff = cfi->buf_in.buf;
+			ep->dwc_ep.xfer_buff = cfi->buf_in.buf;
+			ep->dwc_ep.xfer_len = retval;
+			ep->dwc_ep.xfer_count = 0;
+			ep->dwc_ep.sent_zlp = 0;
+			ep->dwc_ep.total_len = ep->dwc_ep.xfer_len;
+
+			pcd->ep0_pending = 1;
+			dwc_otg_ep0_start_transfer(coreif, &ep->dwc_ep);
+		}
+		CFI_INFO("VEN_CORE_GET_FEATURE=%d\n", retval);
+		dump_msg(cfi->buf_in.buf, retval);
+		break;
+
+	case VEN_CORE_SET_FEATURE:
+		CFI_INFO("VEN_CORE_SET_FEATURE\n");
+		/* Set up an XFER to get the data stage of the control request,
+		 * which is the new value of the feature to be modified.
+		 */
+		ep = &pcd->ep0;
+		ep->dwc_ep.is_in = 0;
+		ep->dwc_ep.dma_addr = cfi->buf_out.addr;
+		ep->dwc_ep.start_xfer_buff = cfi->buf_out.buf;
+		ep->dwc_ep.xfer_buff = cfi->buf_out.buf;
+		ep->dwc_ep.xfer_len = wLen;
+		ep->dwc_ep.xfer_count = 0;
+		ep->dwc_ep.sent_zlp = 0;
+		ep->dwc_ep.total_len = ep->dwc_ep.xfer_len;
+
+		pcd->ep0_pending = 1;
+		/* Read the control write's data stage */
+		dwc_otg_ep0_start_transfer(coreif, &ep->dwc_ep);
+		retval = 0;
+		break;
+
+	case VEN_CORE_RESET_FEATURES:
+		CFI_INFO("VEN_CORE_RESET_FEATURES\n");
+		cfi->need_gadget_att = 1;
+		cfi->need_status_in_complete = 1;
+		retval = cfi_preproc_reset(pcd, ctrl);
+		CFI_INFO("VEN_CORE_RESET_FEATURES = (%d)\n", retval);
+		break;
+
+	case VEN_CORE_ACTIVATE_FEATURES:
+		CFI_INFO("VEN_CORE_ACTIVATE_FEATURES\n");
+		break;
+
+	case VEN_CORE_READ_REGISTER:
+		CFI_INFO("VEN_CORE_READ_REGISTER\n");
+		/* wValue optionally contains the HI WORD of the register offset and
+		 * wIndex contains the LOW WORD of the register offset
+		 */
+		if (wValue == 0) {
+			/* @TODO - MAS - fix the access to the base field */
+			regaddr = 0;
+			//regaddr = (uint32_t) pcd->otg_dev->os_dep.base;
+			//GET_CORE_IF(pcd)->co
+			regaddr |= wIndex;
+		} else {
+			regaddr = (wValue << 16) | wIndex;
+		}
+
+		/* Read a 32-bit value of the memory at the regaddr */
+		regval = DWC_READ_REG32((uint32_t *) regaddr);
+
+		ep = &pcd->ep0;
+		dwc_memcpy(cfi->buf_in.buf, &regval, sizeof(uint32_t));
+		ep->dwc_ep.is_in = 1;
+		ep->dwc_ep.dma_addr = cfi->buf_in.addr;
+		ep->dwc_ep.start_xfer_buff = cfi->buf_in.buf;
+		ep->dwc_ep.xfer_buff = cfi->buf_in.buf;
+		ep->dwc_ep.xfer_len = wLen;
+		ep->dwc_ep.xfer_count = 0;
+		ep->dwc_ep.sent_zlp = 0;
+		ep->dwc_ep.total_len = ep->dwc_ep.xfer_len;
+
+		pcd->ep0_pending = 1;
+		dwc_otg_ep0_start_transfer(coreif, &ep->dwc_ep);
+		cfi->need_gadget_att = 0;
+		retval = 0;
+		break;
+
+	case VEN_CORE_WRITE_REGISTER:
+		CFI_INFO("VEN_CORE_WRITE_REGISTER\n");
+		/* Set up an XFER to get the data stage of the control request,
+		 * which is the new value of the register to be modified.
+		 */
+		ep = &pcd->ep0;
+		ep->dwc_ep.is_in = 0;
+		ep->dwc_ep.dma_addr = cfi->buf_out.addr;
+		ep->dwc_ep.start_xfer_buff = cfi->buf_out.buf;
+		ep->dwc_ep.xfer_buff = cfi->buf_out.buf;
+		ep->dwc_ep.xfer_len = wLen;
+		ep->dwc_ep.xfer_count = 0;
+		ep->dwc_ep.sent_zlp = 0;
+		ep->dwc_ep.total_len = ep->dwc_ep.xfer_len;
+
+		pcd->ep0_pending = 1;
+		/* Read the control write's data stage */
+		dwc_otg_ep0_start_transfer(coreif, &ep->dwc_ep);
+		retval = 0;
+		break;
+
+	default:
+		retval = -DWC_E_NOT_SUPPORTED;
+		break;
+	}
+
+	return retval;
+}
+
+/**
+ * This function prepares the core features descriptors and copies its
+ * raw representation into the buffer <buf>.
+ *
+ * The buffer structure is as follows:
+ *	all_features_header (8 bytes)
+ *	features_#1 (8 bytes + feature name string length)
+ *	features_#2 (8 bytes + feature name string length)
+ *	.....
+ *	features_#n - where n=the total count of feature descriptors
+ */
+static int cfi_core_features_buf(uint8_t * buf, uint16_t buflen)
+{
+	cfi_feature_desc_header_t *prop_hdr = prop_descs;
+	cfi_feature_desc_header_t *prop;
+	cfi_all_features_header_t *all_props_hdr = &all_props_desc_header;
+	cfi_all_features_header_t *tmp;
+	uint8_t *tmpbuf = buf;
+	const uint8_t *pname = NULL;
+	int i, j, namelen = 0, totlen;
+
+	/* Prepare and copy the core features into the buffer */
+	CFI_INFO("%s:\n", __func__);
+
+	tmp = (cfi_all_features_header_t *) tmpbuf;
+	*tmp = *all_props_hdr;
+	tmpbuf += CFI_ALL_FEATURES_HDR_LEN;
+
+	j = sizeof(prop_descs) / sizeof(cfi_all_features_header_t);
+	for (i = 0; i < j; i++, prop_hdr++) {
+		pname = get_prop_name(prop_hdr->wFeatureID, &namelen);
+		prop = (cfi_feature_desc_header_t *) tmpbuf;
+		*prop = *prop_hdr;
+
+		prop->bNameLen = namelen;
+		prop->wLength =
+		    DWC_CONSTANT_CPU_TO_LE16(CFI_FEATURE_DESC_HDR_LEN +
+					     namelen);
+
+		tmpbuf += CFI_FEATURE_DESC_HDR_LEN;
+		dwc_memcpy(tmpbuf, pname, namelen);
+		tmpbuf += namelen;
+	}
+
+	totlen = tmpbuf - buf;
+
+	if (totlen > 0) {
+		tmp = (cfi_all_features_header_t *) buf;
+		tmp->wTotalLen = DWC_CONSTANT_CPU_TO_LE16(totlen);
+	}
+
+	return totlen;
+}
+
+/**
+ * This function releases all the dynamic memory in the CFI object.
+ */
+static void cfi_release(cfiobject_t * cfiobj)
+{
+	cfi_ep_t *cfiep;
+	dwc_list_link_t *tmp;
+
+	CFI_INFO("%s\n", __func__);
+
+	if (cfiobj->buf_in.buf) {
+		DWC_DMA_FREE(CFI_IN_BUF_LEN, cfiobj->buf_in.buf,
+			     cfiobj->buf_in.addr);
+		cfiobj->buf_in.buf = NULL;
+	}
+
+	if (cfiobj->buf_out.buf) {
+		DWC_DMA_FREE(CFI_OUT_BUF_LEN, cfiobj->buf_out.buf,
+			     cfiobj->buf_out.addr);
+		cfiobj->buf_out.buf = NULL;
+	}
+
+	/* Free the Buffer Setup values for each EP */
+	//list_for_each_entry(cfiep, &cfiobj->active_eps, lh) {
+	DWC_LIST_FOREACH(tmp, &cfiobj->active_eps) {
+		cfiep = DWC_LIST_ENTRY(tmp, struct cfi_ep, lh);
+		cfi_free_ep_bs_dyn_data(cfiep);
+	}
+}
+
+/**
+ * This function frees the dynamically allocated EP buffer setup data.
+ */
+static void cfi_free_ep_bs_dyn_data(cfi_ep_t * cfiep)
+{
+	if (cfiep->bm_sg) {
+		DWC_FREE(cfiep->bm_sg);
+		cfiep->bm_sg = NULL;
+	}
+
+	if (cfiep->bm_align) {
+		DWC_FREE(cfiep->bm_align);
+		cfiep->bm_align = NULL;
+	}
+
+	if (cfiep->bm_concat) {
+		if (NULL != cfiep->bm_concat->wTxBytes) {
+			DWC_FREE(cfiep->bm_concat->wTxBytes);
+			cfiep->bm_concat->wTxBytes = NULL;
+		}
+		DWC_FREE(cfiep->bm_concat);
+		cfiep->bm_concat = NULL;
+	}
+}
+
+/**
+ * This function initializes the default values of the features
+ * for a specific endpoint and should be called only once when
+ * the EP is enabled first time.
+ */
+static int cfi_ep_init_defaults(struct dwc_otg_pcd *pcd, cfi_ep_t * cfiep)
+{
+	int retval = 0;
+
+	cfiep->bm_sg = DWC_ALLOC(sizeof(ddma_sg_buffer_setup_t));
+	if (NULL == cfiep->bm_sg) {
+		CFI_INFO("Failed to allocate memory for SG feature value\n");
+		return -DWC_E_NO_MEMORY;
+	}
+	dwc_memset(cfiep->bm_sg, 0, sizeof(ddma_sg_buffer_setup_t));
+
+	/* For the Concatenation feature's default value we do not allocate
+	 * memory for the wTxBytes field - it will be done in the set_feature_value
+	 * request handler.
+	 */
+	cfiep->bm_concat = DWC_ALLOC(sizeof(ddma_concat_buffer_setup_t));
+	if (NULL == cfiep->bm_concat) {
+		CFI_INFO
+		    ("Failed to allocate memory for CONCATENATION feature value\n");
+		DWC_FREE(cfiep->bm_sg);
+		return -DWC_E_NO_MEMORY;
+	}
+	dwc_memset(cfiep->bm_concat, 0, sizeof(ddma_concat_buffer_setup_t));
+
+	cfiep->bm_align = DWC_ALLOC(sizeof(ddma_align_buffer_setup_t));
+	if (NULL == cfiep->bm_align) {
+		CFI_INFO
+		    ("Failed to allocate memory for Alignment feature value\n");
+		DWC_FREE(cfiep->bm_sg);
+		DWC_FREE(cfiep->bm_concat);
+		return -DWC_E_NO_MEMORY;
+	}
+	dwc_memset(cfiep->bm_align, 0, sizeof(ddma_align_buffer_setup_t));
+
+	return retval;
+}
+
+/**
+ * The callback function that notifies the CFI on the activation of
+ * an endpoint in the PCD. The following steps are done in this function:
+ *
+ *	Create a dynamically allocated cfi_ep_t object (a CFI wrapper to the PCD's
+ *		active endpoint)
+ *	Create MAX_DMA_DESCS_PER_EP count DMA Descriptors for the EP
+ *	Set the Buffer Mode to standard
+ *	Initialize the default values for all EP modes (SG, Circular, Concat, Align)
+ *	Add the cfi_ep_t object to the list of active endpoints in the CFI object
+ */
+static int cfi_ep_enable(struct cfiobject *cfi, struct dwc_otg_pcd *pcd,
+			 struct dwc_otg_pcd_ep *ep)
+{
+	cfi_ep_t *cfiep;
+	int retval = -DWC_E_NOT_SUPPORTED;
+
+	CFI_INFO("%s: epname=%s; epnum=0x%02x\n", __func__,
+		 "EP_" /*ep->ep.name */ , ep->desc->bEndpointAddress);
+	/* MAS - Check whether this endpoint already is in the list */
+	cfiep = get_cfi_ep_by_pcd_ep(cfi, ep);
+
+	if (NULL == cfiep) {
+		/* Allocate a cfi_ep_t object */
+		cfiep = DWC_ALLOC(sizeof(cfi_ep_t));
+		if (NULL == cfiep) {
+			CFI_INFO
+			    ("Unable to allocate memory for <cfiep> in function %s\n",
+			     __func__);
+			return -DWC_E_NO_MEMORY;
+		}
+		dwc_memset(cfiep, 0, sizeof(cfi_ep_t));
+
+		/* Save the dwc_otg_pcd_ep pointer in the cfiep object */
+		cfiep->ep = ep;
+
+		/* Allocate the DMA Descriptors chain of MAX_DMA_DESCS_PER_EP count */
+		ep->dwc_ep.descs =
+		    DWC_DMA_ALLOC(MAX_DMA_DESCS_PER_EP *
+				  sizeof(dwc_otg_dma_desc_t),
+				  &ep->dwc_ep.descs_dma_addr);
+
+		if (NULL == ep->dwc_ep.descs) {
+			DWC_FREE(cfiep);
+			return -DWC_E_NO_MEMORY;
+		}
+
+		DWC_LIST_INIT(&cfiep->lh);
+
+		/* Set the buffer mode to BM_STANDARD. It will be modified
+		 * when building descriptors for a specific buffer mode */
+		ep->dwc_ep.buff_mode = BM_STANDARD;
+
+		/* Create and initialize the default values for this EP's Buffer modes */
+		if ((retval = cfi_ep_init_defaults(pcd, cfiep)) < 0)
+			return retval;
+
+		/* Add the cfi_ep_t object to the CFI object's list of active endpoints */
+		DWC_LIST_INSERT_TAIL(&cfi->active_eps, &cfiep->lh);
+		retval = 0;
+	} else {		/* The sought EP already is in the list */
+		CFI_INFO("%s: The sought EP already is in the list\n",
+			 __func__);
+	}
+
+	return retval;
+}
+
+/**
+ * This function is called when the data stage of a 3-stage Control Write request
+ * is complete.
+ *
+ */
+static int cfi_ctrl_write_complete(struct cfiobject *cfi,
+				   struct dwc_otg_pcd *pcd)
+{
+	uint32_t addr, reg_value;
+	uint16_t wIndex, wValue;
+	uint8_t bRequest;
+	uint8_t *buf = cfi->buf_out.buf;
+	//struct usb_ctrlrequest *ctrl_req = &cfi->ctrl_req_saved;
+	struct cfi_usb_ctrlrequest *ctrl_req = &cfi->ctrl_req;
+	int retval = -DWC_E_NOT_SUPPORTED;
+
+	CFI_INFO("%s\n", __func__);
+
+	bRequest = ctrl_req->bRequest;
+	wIndex = DWC_CONSTANT_CPU_TO_LE16(ctrl_req->wIndex);
+	wValue = DWC_CONSTANT_CPU_TO_LE16(ctrl_req->wValue);
+
+	/*
+	 * Save the pointer to the data stage in the ctrl_req's <data> field.
+	 * The request should be already saved in the command stage by now.
+	 */
+	ctrl_req->data = cfi->buf_out.buf;
+	cfi->need_status_in_complete = 0;
+	cfi->need_gadget_att = 0;
+
+	switch (bRequest) {
+	case VEN_CORE_WRITE_REGISTER:
+		/* The buffer contains raw data of the new value for the register */
+		reg_value = *((uint32_t *) buf);
+		if (wValue == 0) {
+			addr = 0;
+			//addr = (uint32_t) pcd->otg_dev->os_dep.base;
+			addr += wIndex;
+		} else {
+			addr = (wValue << 16) | wIndex;
+		}
+
+		//writel(reg_value, addr);
+
+		retval = 0;
+		cfi->need_status_in_complete = 1;
+		break;
+
+	case VEN_CORE_SET_FEATURE:
+		/* The buffer contains raw data of the new value of the feature */
+		retval = cfi_set_feature_value(pcd);
+		if (retval < 0)
+			return retval;
+
+		cfi->need_status_in_complete = 1;
+		break;
+
+	default:
+		break;
+	}
+
+	return retval;
+}
+
+/**
+ * This function builds the DMA descriptors for the SG buffer mode.
+ */
+static void cfi_build_sg_descs(struct cfiobject *cfi, cfi_ep_t * cfiep,
+			       dwc_otg_pcd_request_t * req)
+{
+	struct dwc_otg_pcd_ep *ep = cfiep->ep;
+	ddma_sg_buffer_setup_t *sgval = cfiep->bm_sg;
+	struct dwc_otg_dma_desc *desc = cfiep->ep->dwc_ep.descs;
+	struct dwc_otg_dma_desc *desc_last = cfiep->ep->dwc_ep.descs;
+	dma_addr_t buff_addr = req->dma;
+	int i;
+	uint32_t txsize, off;
+
+	txsize = sgval->wSize;
+	off = sgval->bOffset;
+
+//      CFI_INFO("%s: %s TXSIZE=0x%08x; OFFSET=0x%08x\n",
+//              __func__, cfiep->ep->ep.name, txsize, off);
+
+	for (i = 0; i < sgval->bCount; i++) {
+		desc->status.b.bs = BS_HOST_BUSY;
+		desc->buf = buff_addr;
+		desc->status.b.l = 0;
+		desc->status.b.ioc = 0;
+		desc->status.b.sp = 0;
+		desc->status.b.bytes = txsize;
+		desc->status.b.bs = BS_HOST_READY;
+
+		/* Set the next address of the buffer */
+		buff_addr += txsize + off;
+		desc_last = desc;
+		desc++;
+	}
+
+	/* Set the last, ioc and sp bits on the Last DMA Descriptor */
+	desc_last->status.b.l = 1;
+	desc_last->status.b.ioc = 1;
+	desc_last->status.b.sp = ep->dwc_ep.sent_zlp;
+	/* Save the last DMA descriptor pointer */
+	cfiep->dma_desc_last = desc_last;
+	cfiep->desc_count = sgval->bCount;
+}
+
+/**
+ * This function builds the DMA descriptors for the Concatenation buffer mode.
+ */
+static void cfi_build_concat_descs(struct cfiobject *cfi, cfi_ep_t * cfiep,
+				   dwc_otg_pcd_request_t * req)
+{
+	struct dwc_otg_pcd_ep *ep = cfiep->ep;
+	ddma_concat_buffer_setup_t *concatval = cfiep->bm_concat;
+	struct dwc_otg_dma_desc *desc = cfiep->ep->dwc_ep.descs;
+	struct dwc_otg_dma_desc *desc_last = cfiep->ep->dwc_ep.descs;
+	dma_addr_t buff_addr = req->dma;
+	int i;
+	uint16_t *txsize;
+
+	txsize = concatval->wTxBytes;
+
+	for (i = 0; i < concatval->hdr.bDescCount; i++) {
+		desc->buf = buff_addr;
+		desc->status.b.bs = BS_HOST_BUSY;
+		desc->status.b.l = 0;
+		desc->status.b.ioc = 0;
+		desc->status.b.sp = 0;
+		desc->status.b.bytes = *txsize;
+		desc->status.b.bs = BS_HOST_READY;
+
+		txsize++;
+		/* Set the next address of the buffer */
+		buff_addr += UGETW(ep->desc->wMaxPacketSize);
+		desc_last = desc;
+		desc++;
+	}
+
+	/* Set the last, ioc and sp bits on the Last DMA Descriptor */
+	desc_last->status.b.l = 1;
+	desc_last->status.b.ioc = 1;
+	desc_last->status.b.sp = ep->dwc_ep.sent_zlp;
+	cfiep->dma_desc_last = desc_last;
+	cfiep->desc_count = concatval->hdr.bDescCount;
+}
+
+/**
+ * This function builds the DMA descriptors for the Circular buffer mode
+ */
+static void cfi_build_circ_descs(struct cfiobject *cfi, cfi_ep_t * cfiep,
+				 dwc_otg_pcd_request_t * req)
+{
+	/* @todo: MAS - add implementation when this feature needs to be tested */
+}
+
+/**
+ * This function builds the DMA descriptors for the Alignment buffer mode
+ */
+static void cfi_build_align_descs(struct cfiobject *cfi, cfi_ep_t * cfiep,
+				  dwc_otg_pcd_request_t * req)
+{
+	struct dwc_otg_pcd_ep *ep = cfiep->ep;
+	ddma_align_buffer_setup_t *alignval = cfiep->bm_align;
+	struct dwc_otg_dma_desc *desc = cfiep->ep->dwc_ep.descs;
+	dma_addr_t buff_addr = req->dma;
+
+	desc->status.b.bs = BS_HOST_BUSY;
+	desc->status.b.l = 1;
+	desc->status.b.ioc = 1;
+	desc->status.b.sp = ep->dwc_ep.sent_zlp;
+	desc->status.b.bytes = req->length;
+	/* Adjust the buffer alignment */
+	desc->buf = (buff_addr + alignval->bAlign);
+	desc->status.b.bs = BS_HOST_READY;
+	cfiep->dma_desc_last = desc;
+	cfiep->desc_count = 1;
+}
+
+/**
+ * This function builds the DMA descriptors chain for different modes of the
+ * buffer setup of an endpoint.
+ */
+static void cfi_build_descriptors(struct cfiobject *cfi,
+				  struct dwc_otg_pcd *pcd,
+				  struct dwc_otg_pcd_ep *ep,
+				  dwc_otg_pcd_request_t * req)
+{
+	cfi_ep_t *cfiep;
+
+	/* Get the cfiep by the dwc_otg_pcd_ep */
+	cfiep = get_cfi_ep_by_pcd_ep(cfi, ep);
+	if (NULL == cfiep) {
+		CFI_INFO("%s: Unable to find a matching active endpoint\n",
+			 __func__);
+		return;
+	}
+
+	cfiep->xfer_len = req->length;
+
+	/* Iterate through all the DMA descriptors */
+	switch (cfiep->ep->dwc_ep.buff_mode) {
+	case BM_SG:
+		cfi_build_sg_descs(cfi, cfiep, req);
+		break;
+
+	case BM_CONCAT:
+		cfi_build_concat_descs(cfi, cfiep, req);
+		break;
+
+	case BM_CIRCULAR:
+		cfi_build_circ_descs(cfi, cfiep, req);
+		break;
+
+	case BM_ALIGN:
+		cfi_build_align_descs(cfi, cfiep, req);
+		break;
+
+	default:
+		break;
+	}
+}
+
+/**
+ * Allocate DMA buffer for different Buffer modes.
+ */
+static void *cfi_ep_alloc_buf(struct cfiobject *cfi, struct dwc_otg_pcd *pcd,
+			      struct dwc_otg_pcd_ep *ep, dma_addr_t * dma,
+			      unsigned size, gfp_t flags)
+{
+	return DWC_DMA_ALLOC(size, dma);
+}
+
+/**
+ * This function initializes the CFI object.
+ */
+int init_cfi(cfiobject_t * cfiobj)
+{
+	CFI_INFO("%s\n", __func__);
+
+	/* Allocate a buffer for IN XFERs */
+	cfiobj->buf_in.buf =
+	    DWC_DMA_ALLOC(CFI_IN_BUF_LEN, &cfiobj->buf_in.addr);
+	if (NULL == cfiobj->buf_in.buf) {
+		CFI_INFO("Unable to allocate buffer for INs\n");
+		return -DWC_E_NO_MEMORY;
+	}
+
+	/* Allocate a buffer for OUT XFERs */
+	cfiobj->buf_out.buf =
+	    DWC_DMA_ALLOC(CFI_OUT_BUF_LEN, &cfiobj->buf_out.addr);
+	if (NULL == cfiobj->buf_out.buf) {
+		CFI_INFO("Unable to allocate buffer for OUT\n");
+		return -DWC_E_NO_MEMORY;
+	}
+
+	/* Initialize the callback function pointers */
+	cfiobj->ops.release = cfi_release;
+	cfiobj->ops.ep_enable = cfi_ep_enable;
+	cfiobj->ops.ctrl_write_complete = cfi_ctrl_write_complete;
+	cfiobj->ops.build_descriptors = cfi_build_descriptors;
+	cfiobj->ops.ep_alloc_buf = cfi_ep_alloc_buf;
+
+	/* Initialize the list of active endpoints in the CFI object */
+	DWC_LIST_INIT(&cfiobj->active_eps);
+
+	return 0;
+}
+
+/**
+ * This function reads the required feature's current value into the buffer
+ *
+ * @retval: Returns negative as error, or the data length of the feature
+ */
+static int cfi_get_feature_value(uint8_t * buf, uint16_t buflen,
+				 struct dwc_otg_pcd *pcd,
+				 struct cfi_usb_ctrlrequest *ctrl_req)
+{
+	int retval = -DWC_E_NOT_SUPPORTED;
+	struct dwc_otg_core_if *coreif = GET_CORE_IF(pcd);
+	uint16_t dfifo, rxfifo, txfifo;
+
+	switch (ctrl_req->wIndex) {
+		/* Whether the DDMA is enabled or not */
+	case FT_ID_DMA_MODE:
+		*buf = (coreif->dma_enable && coreif->dma_desc_enable) ? 1 : 0;
+		retval = 1;
+		break;
+
+	case FT_ID_DMA_BUFFER_SETUP:
+		retval = cfi_ep_get_sg_val(buf, pcd, ctrl_req);
+		break;
+
+	case FT_ID_DMA_BUFF_ALIGN:
+		retval = cfi_ep_get_align_val(buf, pcd, ctrl_req);
+		break;
+
+	case FT_ID_DMA_CONCAT_SETUP:
+		retval = cfi_ep_get_concat_val(buf, pcd, ctrl_req);
+		break;
+
+	case FT_ID_DMA_CIRCULAR:
+		CFI_INFO("GetFeature value (FT_ID_DMA_CIRCULAR)\n");
+		break;
+
+	case FT_ID_THRESHOLD_SETUP:
+		CFI_INFO("GetFeature value (FT_ID_THRESHOLD_SETUP)\n");
+		break;
+
+	case FT_ID_DFIFO_DEPTH:
+		dfifo = get_dfifo_size(coreif);
+		*((uint16_t *) buf) = dfifo;
+		retval = sizeof(uint16_t);
+		break;
+
+	case FT_ID_TX_FIFO_DEPTH:
+		retval = get_txfifo_size(pcd, ctrl_req->wValue);
+		if (retval >= 0) {
+			txfifo = retval;
+			*((uint16_t *) buf) = txfifo;
+			retval = sizeof(uint16_t);
+		}
+		break;
+
+	case FT_ID_RX_FIFO_DEPTH:
+		retval = get_rxfifo_size(coreif, ctrl_req->wValue);
+		if (retval >= 0) {
+			rxfifo = retval;
+			*((uint16_t *) buf) = rxfifo;
+			retval = sizeof(uint16_t);
+		}
+		break;
+	}
+
+	return retval;
+}
+
+/**
+ * This function resets the SG for the specified EP to its default value
+ */
+static int cfi_reset_sg_val(cfi_ep_t * cfiep)
+{
+	dwc_memset(cfiep->bm_sg, 0, sizeof(ddma_sg_buffer_setup_t));
+	return 0;
+}
+
+/**
+ * This function resets the Alignment for the specified EP to its default value
+ */
+static int cfi_reset_align_val(cfi_ep_t * cfiep)
+{
+	dwc_memset(cfiep->bm_sg, 0, sizeof(ddma_sg_buffer_setup_t));
+	return 0;
+}
+
+/**
+ * This function resets the Concatenation for the specified EP to its default value
+ * This function will also set the value of the wTxBytes field to NULL after
+ * freeing the memory previously allocated for this field.
+ */
+static int cfi_reset_concat_val(cfi_ep_t * cfiep)
+{
+	/* First we need to free the wTxBytes field */
+	if (cfiep->bm_concat->wTxBytes) {
+		DWC_FREE(cfiep->bm_concat->wTxBytes);
+		cfiep->bm_concat->wTxBytes = NULL;
+	}
+
+	dwc_memset(cfiep->bm_concat, 0, sizeof(ddma_concat_buffer_setup_t));
+	return 0;
+}
+
+/**
+ * This function resets all the buffer setups of the specified endpoint
+ */
+static int cfi_ep_reset_all_setup_vals(cfi_ep_t * cfiep)
+{
+	cfi_reset_sg_val(cfiep);
+	cfi_reset_align_val(cfiep);
+	cfi_reset_concat_val(cfiep);
+	return 0;
+}
+
+static int cfi_handle_reset_fifo_val(struct dwc_otg_pcd *pcd, uint8_t ep_addr,
+				     uint8_t rx_rst, uint8_t tx_rst)
+{
+	int retval = -DWC_E_INVALID;
+	uint16_t tx_siz[15];
+	uint16_t rx_siz = 0;
+	dwc_otg_pcd_ep_t *ep = NULL;
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+	dwc_otg_core_params_t *params = GET_CORE_IF(pcd)->core_params;
+
+	if (rx_rst) {
+		rx_siz = params->dev_rx_fifo_size;
+		params->dev_rx_fifo_size = GET_CORE_IF(pcd)->init_rxfsiz;
+	}
+
+	if (tx_rst) {
+		if (ep_addr == 0) {
+			int i;
+
+			for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) {
+				tx_siz[i] =
+				    core_if->core_params->dev_tx_fifo_size[i];
+				core_if->core_params->dev_tx_fifo_size[i] =
+				    core_if->init_txfsiz[i];
+			}
+		} else {
+
+			ep = get_ep_by_addr(pcd, ep_addr);
+
+			if (NULL == ep) {
+				CFI_INFO
+				    ("%s: Unable to get the endpoint addr=0x%02x\n",
+				     __func__, ep_addr);
+				return -DWC_E_INVALID;
+			}
+
+			tx_siz[0] =
+			    params->dev_tx_fifo_size[ep->dwc_ep.tx_fifo_num -
+						     1];
+			params->dev_tx_fifo_size[ep->dwc_ep.tx_fifo_num - 1] =
+			    GET_CORE_IF(pcd)->init_txfsiz[ep->
+							  dwc_ep.tx_fifo_num -
+							  1];
+		}
+	}
+
+	if (resize_fifos(GET_CORE_IF(pcd))) {
+		retval = 0;
+	} else {
+		CFI_INFO
+		    ("%s: Error resetting the feature Reset All(FIFO size)\n",
+		     __func__);
+		if (rx_rst) {
+			params->dev_rx_fifo_size = rx_siz;
+		}
+
+		if (tx_rst) {
+			if (ep_addr == 0) {
+				int i;
+				for (i = 0; i < core_if->hwcfg4.b.num_in_eps;
+				     i++) {
+					core_if->
+					    core_params->dev_tx_fifo_size[i] =
+					    tx_siz[i];
+				}
+			} else {
+				params->dev_tx_fifo_size[ep->
+							 dwc_ep.tx_fifo_num -
+							 1] = tx_siz[0];
+			}
+		}
+		retval = -DWC_E_INVALID;
+	}
+	return retval;
+}
+
+static int cfi_handle_reset_all(struct dwc_otg_pcd *pcd, uint8_t addr)
+{
+	int retval = 0;
+	cfi_ep_t *cfiep;
+	cfiobject_t *cfi = pcd->cfi;
+	dwc_list_link_t *tmp;
+
+	retval = cfi_handle_reset_fifo_val(pcd, addr, 1, 1);
+	if (retval < 0) {
+		return retval;
+	}
+
+	/* If the EP address is known then reset the features for only that EP */
+	if (addr) {
+		cfiep = get_cfi_ep_by_addr(pcd->cfi, addr);
+		if (NULL == cfiep) {
+			CFI_INFO("%s: Error getting the EP address 0x%02x\n",
+				 __func__, addr);
+			return -DWC_E_INVALID;
+		}
+		retval = cfi_ep_reset_all_setup_vals(cfiep);
+		cfiep->ep->dwc_ep.buff_mode = BM_STANDARD;
+	}
+	/* Otherwise (wValue == 0), reset all features of all EP's */
+	else {
+		/* Traverse all the active EP's and reset the feature(s) value(s) */
+		//list_for_each_entry(cfiep, &cfi->active_eps, lh) {
+		DWC_LIST_FOREACH(tmp, &cfi->active_eps) {
+			cfiep = DWC_LIST_ENTRY(tmp, struct cfi_ep, lh);
+			retval = cfi_ep_reset_all_setup_vals(cfiep);
+			cfiep->ep->dwc_ep.buff_mode = BM_STANDARD;
+			if (retval < 0) {
+				CFI_INFO
+				    ("%s: Error resetting the feature Reset All\n",
+				     __func__);
+				return retval;
+			}
+		}
+	}
+	return retval;
+}
+
+static int cfi_handle_reset_dma_buff_setup(struct dwc_otg_pcd *pcd,
+					   uint8_t addr)
+{
+	int retval = 0;
+	cfi_ep_t *cfiep;
+	cfiobject_t *cfi = pcd->cfi;
+	dwc_list_link_t *tmp;
+
+	/* If the EP address is known then reset the features for only that EP */
+	if (addr) {
+		cfiep = get_cfi_ep_by_addr(pcd->cfi, addr);
+		if (NULL == cfiep) {
+			CFI_INFO("%s: Error getting the EP address 0x%02x\n",
+				 __func__, addr);
+			return -DWC_E_INVALID;
+		}
+		retval = cfi_reset_sg_val(cfiep);
+	}
+	/* Otherwise (wValue == 0), reset all features of all EP's */
+	else {
+		/* Traverse all the active EP's and reset the feature(s) value(s) */
+		//list_for_each_entry(cfiep, &cfi->active_eps, lh) {
+		DWC_LIST_FOREACH(tmp, &cfi->active_eps) {
+			cfiep = DWC_LIST_ENTRY(tmp, struct cfi_ep, lh);
+			retval = cfi_reset_sg_val(cfiep);
+			if (retval < 0) {
+				CFI_INFO
+				    ("%s: Error resetting the feature Buffer Setup\n",
+				     __func__);
+				return retval;
+			}
+		}
+	}
+	return retval;
+}
+
+static int cfi_handle_reset_concat_val(struct dwc_otg_pcd *pcd, uint8_t addr)
+{
+	int retval = 0;
+	cfi_ep_t *cfiep;
+	cfiobject_t *cfi = pcd->cfi;
+	dwc_list_link_t *tmp;
+
+	/* If the EP address is known then reset the features for only that EP */
+	if (addr) {
+		cfiep = get_cfi_ep_by_addr(pcd->cfi, addr);
+		if (NULL == cfiep) {
+			CFI_INFO("%s: Error getting the EP address 0x%02x\n",
+				 __func__, addr);
+			return -DWC_E_INVALID;
+		}
+		retval = cfi_reset_concat_val(cfiep);
+	}
+	/* Otherwise (wValue == 0), reset all features of all EP's */
+	else {
+		/* Traverse all the active EP's and reset the feature(s) value(s) */
+		//list_for_each_entry(cfiep, &cfi->active_eps, lh) {
+		DWC_LIST_FOREACH(tmp, &cfi->active_eps) {
+			cfiep = DWC_LIST_ENTRY(tmp, struct cfi_ep, lh);
+			retval = cfi_reset_concat_val(cfiep);
+			if (retval < 0) {
+				CFI_INFO
+				    ("%s: Error resetting the feature Concatenation Value\n",
+				     __func__);
+				return retval;
+			}
+		}
+	}
+	return retval;
+}
+
+static int cfi_handle_reset_align_val(struct dwc_otg_pcd *pcd, uint8_t addr)
+{
+	int retval = 0;
+	cfi_ep_t *cfiep;
+	cfiobject_t *cfi = pcd->cfi;
+	dwc_list_link_t *tmp;
+
+	/* If the EP address is known then reset the features for only that EP */
+	if (addr) {
+		cfiep = get_cfi_ep_by_addr(pcd->cfi, addr);
+		if (NULL == cfiep) {
+			CFI_INFO("%s: Error getting the EP address 0x%02x\n",
+				 __func__, addr);
+			return -DWC_E_INVALID;
+		}
+		retval = cfi_reset_align_val(cfiep);
+	}
+	/* Otherwise (wValue == 0), reset all features of all EP's */
+	else {
+		/* Traverse all the active EP's and reset the feature(s) value(s) */
+		//list_for_each_entry(cfiep, &cfi->active_eps, lh) {
+		DWC_LIST_FOREACH(tmp, &cfi->active_eps) {
+			cfiep = DWC_LIST_ENTRY(tmp, struct cfi_ep, lh);
+			retval = cfi_reset_align_val(cfiep);
+			if (retval < 0) {
+				CFI_INFO
+				    ("%s: Error resetting the feature Aliignment Value\n",
+				     __func__);
+				return retval;
+			}
+		}
+	}
+	return retval;
+
+}
+
+static int cfi_preproc_reset(struct dwc_otg_pcd *pcd,
+			     struct cfi_usb_ctrlrequest *req)
+{
+	int retval = 0;
+
+	switch (req->wIndex) {
+	case 0:
+		/* Reset all features */
+		retval = cfi_handle_reset_all(pcd, req->wValue & 0xff);
+		break;
+
+	case FT_ID_DMA_BUFFER_SETUP:
+		/* Reset the SG buffer setup */
+		retval =
+		    cfi_handle_reset_dma_buff_setup(pcd, req->wValue & 0xff);
+		break;
+
+	case FT_ID_DMA_CONCAT_SETUP:
+		/* Reset the Concatenation buffer setup */
+		retval = cfi_handle_reset_concat_val(pcd, req->wValue & 0xff);
+		break;
+
+	case FT_ID_DMA_BUFF_ALIGN:
+		/* Reset the Alignment buffer setup */
+		retval = cfi_handle_reset_align_val(pcd, req->wValue & 0xff);
+		break;
+
+	case FT_ID_TX_FIFO_DEPTH:
+		retval =
+		    cfi_handle_reset_fifo_val(pcd, req->wValue & 0xff, 0, 1);
+		pcd->cfi->need_gadget_att = 0;
+		break;
+
+	case FT_ID_RX_FIFO_DEPTH:
+		retval = cfi_handle_reset_fifo_val(pcd, 0, 1, 0);
+		pcd->cfi->need_gadget_att = 0;
+		break;
+	default:
+		break;
+	}
+	return retval;
+}
+
+/**
+ * This function sets a new value for the SG buffer setup.
+ */
+static int cfi_ep_set_sg_val(uint8_t * buf, struct dwc_otg_pcd *pcd)
+{
+	uint8_t inaddr, outaddr;
+	cfi_ep_t *epin, *epout;
+	ddma_sg_buffer_setup_t *psgval;
+	uint32_t desccount, size;
+
+	CFI_INFO("%s\n", __func__);
+
+	psgval = (ddma_sg_buffer_setup_t *) buf;
+	desccount = (uint32_t) psgval->bCount;
+	size = (uint32_t) psgval->wSize;
+
+	/* Check the DMA descriptor count */
+	if ((desccount > MAX_DMA_DESCS_PER_EP) || (desccount == 0)) {
+		CFI_INFO
+		    ("%s: The count of DMA Descriptors should be between 1 and %d\n",
+		     __func__, MAX_DMA_DESCS_PER_EP);
+		return -DWC_E_INVALID;
+	}
+
+	/* Check the DMA descriptor count */
+
+	if (size == 0) {
+
+		CFI_INFO("%s: The transfer size should be at least 1 byte\n",
+			 __func__);
+
+		return -DWC_E_INVALID;
+
+	}
+
+	inaddr = psgval->bInEndpointAddress;
+	outaddr = psgval->bOutEndpointAddress;
+
+	epin = get_cfi_ep_by_addr(pcd->cfi, inaddr);
+	epout = get_cfi_ep_by_addr(pcd->cfi, outaddr);
+
+	if (NULL == epin || NULL == epout) {
+		CFI_INFO
+		    ("%s: Unable to get the endpoints inaddr=0x%02x outaddr=0x%02x\n",
+		     __func__, inaddr, outaddr);
+		return -DWC_E_INVALID;
+	}
+
+	epin->ep->dwc_ep.buff_mode = BM_SG;
+	dwc_memcpy(epin->bm_sg, psgval, sizeof(ddma_sg_buffer_setup_t));
+
+	epout->ep->dwc_ep.buff_mode = BM_SG;
+	dwc_memcpy(epout->bm_sg, psgval, sizeof(ddma_sg_buffer_setup_t));
+
+	return 0;
+}
+
+/**
+ * This function sets a new value for the buffer Alignment setup.
+ */
+static int cfi_ep_set_alignment_val(uint8_t * buf, struct dwc_otg_pcd *pcd)
+{
+	cfi_ep_t *ep;
+	uint8_t addr;
+	ddma_align_buffer_setup_t *palignval;
+
+	palignval = (ddma_align_buffer_setup_t *) buf;
+	addr = palignval->bEndpointAddress;
+
+	ep = get_cfi_ep_by_addr(pcd->cfi, addr);
+
+	if (NULL == ep) {
+		CFI_INFO("%s: Unable to get the endpoint addr=0x%02x\n",
+			 __func__, addr);
+		return -DWC_E_INVALID;
+	}
+
+	ep->ep->dwc_ep.buff_mode = BM_ALIGN;
+	dwc_memcpy(ep->bm_align, palignval, sizeof(ddma_align_buffer_setup_t));
+
+	return 0;
+}
+
+/**
+ * This function sets a new value for the Concatenation buffer setup.
+ */
+static int cfi_ep_set_concat_val(uint8_t * buf, struct dwc_otg_pcd *pcd)
+{
+	uint8_t addr;
+	cfi_ep_t *ep;
+	struct _ddma_concat_buffer_setup_hdr *pConcatValHdr;
+	uint16_t *pVals;
+	uint32_t desccount;
+	int i;
+	uint16_t mps;
+
+	pConcatValHdr = (struct _ddma_concat_buffer_setup_hdr *)buf;
+	desccount = (uint32_t) pConcatValHdr->bDescCount;
+	pVals = (uint16_t *) (buf + BS_CONCAT_VAL_HDR_LEN);
+
+	/* Check the DMA descriptor count */
+	if (desccount > MAX_DMA_DESCS_PER_EP) {
+		CFI_INFO("%s: Maximum DMA Descriptor count should be %d\n",
+			 __func__, MAX_DMA_DESCS_PER_EP);
+		return -DWC_E_INVALID;
+	}
+
+	addr = pConcatValHdr->bEndpointAddress;
+	ep = get_cfi_ep_by_addr(pcd->cfi, addr);
+	if (NULL == ep) {
+		CFI_INFO("%s: Unable to get the endpoint addr=0x%02x\n",
+			 __func__, addr);
+		return -DWC_E_INVALID;
+	}
+
+	mps = UGETW(ep->ep->desc->wMaxPacketSize);
+
+#if 0
+	for (i = 0; i < desccount; i++) {
+		CFI_INFO("%s: wTxSize[%d]=0x%04x\n", __func__, i, pVals[i]);
+	}
+	CFI_INFO("%s: epname=%s; mps=%d\n", __func__, ep->ep->ep.name, mps);
+#endif
+
+	/* Check the wTxSizes to be less than or equal to the mps */
+	for (i = 0; i < desccount; i++) {
+		if (pVals[i] > mps) {
+			CFI_INFO
+			    ("%s: ERROR - the wTxSize[%d] should be <= MPS (wTxSize=%d)\n",
+			     __func__, i, pVals[i]);
+			return -DWC_E_INVALID;
+		}
+	}
+
+	ep->ep->dwc_ep.buff_mode = BM_CONCAT;
+	dwc_memcpy(ep->bm_concat, pConcatValHdr, BS_CONCAT_VAL_HDR_LEN);
+
+	/* Free the previously allocated storage for the wTxBytes */
+	if (ep->bm_concat->wTxBytes) {
+		DWC_FREE(ep->bm_concat->wTxBytes);
+	}
+
+	/* Allocate a new storage for the wTxBytes field */
+	ep->bm_concat->wTxBytes =
+	    DWC_ALLOC(sizeof(uint16_t) * pConcatValHdr->bDescCount);
+	if (NULL == ep->bm_concat->wTxBytes) {
+		CFI_INFO("%s: Unable to allocate memory\n", __func__);
+		return -DWC_E_NO_MEMORY;
+	}
+
+	/* Copy the new values into the wTxBytes filed */
+	dwc_memcpy(ep->bm_concat->wTxBytes, buf + BS_CONCAT_VAL_HDR_LEN,
+		   sizeof(uint16_t) * pConcatValHdr->bDescCount);
+
+	return 0;
+}
+
+/**
+ * This function calculates the total of all FIFO sizes
+ *
+ * @param core_if Programming view of DWC_otg controller
+ *
+ * @return The total of data FIFO sizes.
+ *
+ */
+static uint16_t get_dfifo_size(dwc_otg_core_if_t * core_if)
+{
+	dwc_otg_core_params_t *params = core_if->core_params;
+	uint16_t dfifo_total = 0;
+	int i;
+
+	/* The shared RxFIFO size */
+	dfifo_total =
+	    params->dev_rx_fifo_size + params->dev_nperio_tx_fifo_size;
+
+	/* Add up each TxFIFO size to the total */
+	for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) {
+		dfifo_total += params->dev_tx_fifo_size[i];
+	}
+
+	return dfifo_total;
+}
+
+/**
+ * This function returns Rx FIFO size
+ *
+ * @param core_if Programming view of DWC_otg controller
+ *
+ * @return The total of data FIFO sizes.
+ *
+ */
+static int32_t get_rxfifo_size(dwc_otg_core_if_t * core_if, uint16_t wValue)
+{
+	switch (wValue >> 8) {
+	case 0:
+		return (core_if->pwron_rxfsiz <
+			32768) ? core_if->pwron_rxfsiz : 32768;
+		break;
+	case 1:
+		return core_if->core_params->dev_rx_fifo_size;
+		break;
+	default:
+		return -DWC_E_INVALID;
+		break;
+	}
+}
+
+/**
+ * This function returns Tx FIFO size for IN EP
+ *
+ * @param core_if Programming view of DWC_otg controller
+ *
+ * @return The total of data FIFO sizes.
+ *
+ */
+static int32_t get_txfifo_size(struct dwc_otg_pcd *pcd, uint16_t wValue)
+{
+	dwc_otg_pcd_ep_t *ep;
+
+	ep = get_ep_by_addr(pcd, wValue & 0xff);
+
+	if (NULL == ep) {
+		CFI_INFO("%s: Unable to get the endpoint addr=0x%02x\n",
+			 __func__, wValue & 0xff);
+		return -DWC_E_INVALID;
+	}
+
+	if (!ep->dwc_ep.is_in) {
+		CFI_INFO
+		    ("%s: No Tx FIFO assingned to the Out endpoint addr=0x%02x\n",
+		     __func__, wValue & 0xff);
+		return -DWC_E_INVALID;
+	}
+
+	switch (wValue >> 8) {
+	case 0:
+		return (GET_CORE_IF(pcd)->pwron_txfsiz
+			[ep->dwc_ep.tx_fifo_num - 1] <
+			768) ? GET_CORE_IF(pcd)->pwron_txfsiz[ep->
+							      dwc_ep.tx_fifo_num
+							      - 1] : 32768;
+		break;
+	case 1:
+		return GET_CORE_IF(pcd)->core_params->
+		    dev_tx_fifo_size[ep->dwc_ep.num - 1];
+		break;
+	default:
+		return -DWC_E_INVALID;
+		break;
+	}
+}
+
+/**
+ * This function checks if the submitted combination of
+ * device mode FIFO sizes is possible or not.
+ *
+ * @param core_if Programming view of DWC_otg controller
+ *
+ * @return 1 if possible, 0 otherwise.
+ *
+ */
+static uint8_t check_fifo_sizes(dwc_otg_core_if_t * core_if)
+{
+	uint16_t dfifo_actual = 0;
+	dwc_otg_core_params_t *params = core_if->core_params;
+	uint16_t start_addr = 0;
+	int i;
+
+	dfifo_actual =
+	    params->dev_rx_fifo_size + params->dev_nperio_tx_fifo_size;
+
+	for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) {
+		dfifo_actual += params->dev_tx_fifo_size[i];
+	}
+
+	if (dfifo_actual > core_if->total_fifo_size) {
+		return 0;
+	}
+
+	if (params->dev_rx_fifo_size > 32768 || params->dev_rx_fifo_size < 16)
+		return 0;
+
+	if (params->dev_nperio_tx_fifo_size > 32768
+	    || params->dev_nperio_tx_fifo_size < 16)
+		return 0;
+
+	for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) {
+
+		if (params->dev_tx_fifo_size[i] > 768
+		    || params->dev_tx_fifo_size[i] < 4)
+			return 0;
+	}
+
+	if (params->dev_rx_fifo_size > core_if->pwron_rxfsiz)
+		return 0;
+	start_addr = params->dev_rx_fifo_size;
+
+	if (params->dev_nperio_tx_fifo_size > core_if->pwron_gnptxfsiz)
+		return 0;
+	start_addr += params->dev_nperio_tx_fifo_size;
+
+	for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) {
+
+		if (params->dev_tx_fifo_size[i] > core_if->pwron_txfsiz[i])
+			return 0;
+		start_addr += params->dev_tx_fifo_size[i];
+	}
+
+	return 1;
+}
+
+/**
+ * This function resizes Device mode FIFOs
+ *
+ * @param core_if Programming view of DWC_otg controller
+ *
+ * @return 1 if successful, 0 otherwise
+ *
+ */
+static uint8_t resize_fifos(dwc_otg_core_if_t * core_if)
+{
+	int i = 0;
+	dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs;
+	dwc_otg_core_params_t *params = core_if->core_params;
+	uint32_t rx_fifo_size;
+	fifosize_data_t nptxfifosize;
+	fifosize_data_t txfifosize[15];
+
+	uint32_t rx_fsz_bak;
+	uint32_t nptxfsz_bak;
+	uint32_t txfsz_bak[15];
+
+	uint16_t start_address;
+	uint8_t retval = 1;
+
+	if (!check_fifo_sizes(core_if)) {
+		return 0;
+	}
+
+	/* Configure data FIFO sizes */
+	if (core_if->hwcfg2.b.dynamic_fifo && params->enable_dynamic_fifo) {
+		rx_fsz_bak = DWC_READ_REG32(&global_regs->grxfsiz);
+		rx_fifo_size = params->dev_rx_fifo_size;
+		DWC_WRITE_REG32(&global_regs->grxfsiz, rx_fifo_size);
+
+		/*
+		 * Tx FIFOs These FIFOs are numbered from 1 to 15.
+		 * Indexes of the FIFO size module parameters in the
+		 * dev_tx_fifo_size array and the FIFO size registers in
+		 * the dtxfsiz array run from 0 to 14.
+		 */
+
+		/* Non-periodic Tx FIFO */
+		nptxfsz_bak = DWC_READ_REG32(&global_regs->gnptxfsiz);
+		nptxfifosize.b.depth = params->dev_nperio_tx_fifo_size;
+		start_address = params->dev_rx_fifo_size;
+		nptxfifosize.b.startaddr = start_address;
+
+		DWC_WRITE_REG32(&global_regs->gnptxfsiz, nptxfifosize.d32);
+
+		start_address += nptxfifosize.b.depth;
+
+		for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) {
+			txfsz_bak[i] = DWC_READ_REG32(&global_regs->dtxfsiz[i]);
+
+			txfifosize[i].b.depth = params->dev_tx_fifo_size[i];
+			txfifosize[i].b.startaddr = start_address;
+			DWC_WRITE_REG32(&global_regs->dtxfsiz[i],
+					txfifosize[i].d32);
+
+			start_address += txfifosize[i].b.depth;
+		}
+
+		/** Check if register values are set correctly */
+		if (rx_fifo_size != DWC_READ_REG32(&global_regs->grxfsiz)) {
+			retval = 0;
+		}
+
+		if (nptxfifosize.d32 != DWC_READ_REG32(&global_regs->gnptxfsiz)) {
+			retval = 0;
+		}
+
+		for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) {
+			if (txfifosize[i].d32 !=
+			    DWC_READ_REG32(&global_regs->dtxfsiz[i])) {
+				retval = 0;
+			}
+		}
+
+		/** If register values are not set correctly, reset old values */
+		if (retval == 0) {
+			DWC_WRITE_REG32(&global_regs->grxfsiz, rx_fsz_bak);
+
+			/* Non-periodic Tx FIFO */
+			DWC_WRITE_REG32(&global_regs->gnptxfsiz, nptxfsz_bak);
+
+			for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) {
+				DWC_WRITE_REG32(&global_regs->dtxfsiz[i],
+						txfsz_bak[i]);
+			}
+		}
+	} else {
+		return 0;
+	}
+
+	/* Flush the FIFOs */
+	dwc_otg_flush_tx_fifo(core_if, 0x10);	/* all Tx FIFOs */
+	dwc_otg_flush_rx_fifo(core_if);
+
+	return retval;
+}
+
+/**
+ * This function sets a new value for the buffer Alignment setup.
+ */
+static int cfi_ep_set_tx_fifo_val(uint8_t * buf, dwc_otg_pcd_t * pcd)
+{
+	int retval;
+	uint32_t fsiz;
+	uint16_t size;
+	uint16_t ep_addr;
+	dwc_otg_pcd_ep_t *ep;
+	dwc_otg_core_params_t *params = GET_CORE_IF(pcd)->core_params;
+	tx_fifo_size_setup_t *ptxfifoval;
+
+	ptxfifoval = (tx_fifo_size_setup_t *) buf;
+	ep_addr = ptxfifoval->bEndpointAddress;
+	size = ptxfifoval->wDepth;
+
+	ep = get_ep_by_addr(pcd, ep_addr);
+
+	CFI_INFO
+	    ("%s: Set Tx FIFO size: endpoint addr=0x%02x, depth=%d, FIFO Num=%d\n",
+	     __func__, ep_addr, size, ep->dwc_ep.tx_fifo_num);
+
+	if (NULL == ep) {
+		CFI_INFO("%s: Unable to get the endpoint addr=0x%02x\n",
+			 __func__, ep_addr);
+		return -DWC_E_INVALID;
+	}
+
+	fsiz = params->dev_tx_fifo_size[ep->dwc_ep.tx_fifo_num - 1];
+	params->dev_tx_fifo_size[ep->dwc_ep.tx_fifo_num - 1] = size;
+
+	if (resize_fifos(GET_CORE_IF(pcd))) {
+		retval = 0;
+	} else {
+		CFI_INFO
+		    ("%s: Error setting the feature Tx FIFO Size for EP%d\n",
+		     __func__, ep_addr);
+		params->dev_tx_fifo_size[ep->dwc_ep.tx_fifo_num - 1] = fsiz;
+		retval = -DWC_E_INVALID;
+	}
+
+	return retval;
+}
+
+/**
+ * This function sets a new value for the buffer Alignment setup.
+ */
+static int cfi_set_rx_fifo_val(uint8_t * buf, dwc_otg_pcd_t * pcd)
+{
+	int retval;
+	uint32_t fsiz;
+	uint16_t size;
+	dwc_otg_core_params_t *params = GET_CORE_IF(pcd)->core_params;
+	rx_fifo_size_setup_t *prxfifoval;
+
+	prxfifoval = (rx_fifo_size_setup_t *) buf;
+	size = prxfifoval->wDepth;
+
+	fsiz = params->dev_rx_fifo_size;
+	params->dev_rx_fifo_size = size;
+
+	if (resize_fifos(GET_CORE_IF(pcd))) {
+		retval = 0;
+	} else {
+		CFI_INFO("%s: Error setting the feature Rx FIFO Size\n",
+			 __func__);
+		params->dev_rx_fifo_size = fsiz;
+		retval = -DWC_E_INVALID;
+	}
+
+	return retval;
+}
+
+/**
+ * This function reads the SG of an EP's buffer setup into the buffer buf
+ */
+static int cfi_ep_get_sg_val(uint8_t * buf, struct dwc_otg_pcd *pcd,
+			     struct cfi_usb_ctrlrequest *req)
+{
+	int retval = -DWC_E_INVALID;
+	uint8_t addr;
+	cfi_ep_t *ep;
+
+	/* The Low Byte of the wValue contains a non-zero address of the endpoint */
+	addr = req->wValue & 0xFF;
+	if (addr == 0)		/* The address should be non-zero */
+		return retval;
+
+	ep = get_cfi_ep_by_addr(pcd->cfi, addr);
+	if (NULL == ep) {
+		CFI_INFO("%s: Unable to get the endpoint address(0x%02x)\n",
+			 __func__, addr);
+		return retval;
+	}
+
+	dwc_memcpy(buf, ep->bm_sg, BS_SG_VAL_DESC_LEN);
+	retval = BS_SG_VAL_DESC_LEN;
+	return retval;
+}
+
+/**
+ * This function reads the Concatenation value of an EP's buffer mode into
+ * the buffer buf
+ */
+static int cfi_ep_get_concat_val(uint8_t * buf, struct dwc_otg_pcd *pcd,
+				 struct cfi_usb_ctrlrequest *req)
+{
+	int retval = -DWC_E_INVALID;
+	uint8_t addr;
+	cfi_ep_t *ep;
+	uint8_t desc_count;
+
+	/* The Low Byte of the wValue contains a non-zero address of the endpoint */
+	addr = req->wValue & 0xFF;
+	if (addr == 0)		/* The address should be non-zero */
+		return retval;
+
+	ep = get_cfi_ep_by_addr(pcd->cfi, addr);
+	if (NULL == ep) {
+		CFI_INFO("%s: Unable to get the endpoint address(0x%02x)\n",
+			 __func__, addr);
+		return retval;
+	}
+
+	/* Copy the header to the buffer */
+	dwc_memcpy(buf, ep->bm_concat, BS_CONCAT_VAL_HDR_LEN);
+	/* Advance the buffer pointer by the header size */
+	buf += BS_CONCAT_VAL_HDR_LEN;
+
+	desc_count = ep->bm_concat->hdr.bDescCount;
+	/* Copy alll the wTxBytes to the buffer */
+	dwc_memcpy(buf, ep->bm_concat->wTxBytes, sizeof(uid16_t) * desc_count);
+
+	retval = BS_CONCAT_VAL_HDR_LEN + sizeof(uid16_t) * desc_count;
+	return retval;
+}
+
+/**
+ * This function reads the buffer Alignment value of an EP's buffer mode into
+ * the buffer buf
+ *
+ * @return The total number of bytes copied to the buffer or negative error code.
+ */
+static int cfi_ep_get_align_val(uint8_t * buf, struct dwc_otg_pcd *pcd,
+				struct cfi_usb_ctrlrequest *req)
+{
+	int retval = -DWC_E_INVALID;
+	uint8_t addr;
+	cfi_ep_t *ep;
+
+	/* The Low Byte of the wValue contains a non-zero address of the endpoint */
+	addr = req->wValue & 0xFF;
+	if (addr == 0)		/* The address should be non-zero */
+		return retval;
+
+	ep = get_cfi_ep_by_addr(pcd->cfi, addr);
+	if (NULL == ep) {
+		CFI_INFO("%s: Unable to get the endpoint address(0x%02x)\n",
+			 __func__, addr);
+		return retval;
+	}
+
+	dwc_memcpy(buf, ep->bm_align, BS_ALIGN_VAL_HDR_LEN);
+	retval = BS_ALIGN_VAL_HDR_LEN;
+
+	return retval;
+}
+
+/**
+ * This function sets a new value for the specified feature
+ *
+ * @param	pcd	A pointer to the PCD object
+ *
+ * @return 0 if successful, negative error code otherwise to stall the DCE.
+ */
+static int cfi_set_feature_value(struct dwc_otg_pcd *pcd)
+{
+	int retval = -DWC_E_NOT_SUPPORTED;
+	uint16_t wIndex, wValue;
+	uint8_t bRequest;
+	struct dwc_otg_core_if *coreif;
+	cfiobject_t *cfi = pcd->cfi;
+	struct cfi_usb_ctrlrequest *ctrl_req;
+	uint8_t *buf;
+	ctrl_req = &cfi->ctrl_req;
+
+	buf = pcd->cfi->ctrl_req.data;
+
+	coreif = GET_CORE_IF(pcd);
+	bRequest = ctrl_req->bRequest;
+	wIndex = DWC_CONSTANT_CPU_TO_LE16(ctrl_req->wIndex);
+	wValue = DWC_CONSTANT_CPU_TO_LE16(ctrl_req->wValue);
+
+	/* See which feature is to be modified */
+	switch (wIndex) {
+	case FT_ID_DMA_BUFFER_SETUP:
+		/* Modify the feature */
+		if ((retval = cfi_ep_set_sg_val(buf, pcd)) < 0)
+			return retval;
+
+		/* And send this request to the gadget */
+		cfi->need_gadget_att = 1;
+		break;
+
+	case FT_ID_DMA_BUFF_ALIGN:
+		if ((retval = cfi_ep_set_alignment_val(buf, pcd)) < 0)
+			return retval;
+		cfi->need_gadget_att = 1;
+		break;
+
+	case FT_ID_DMA_CONCAT_SETUP:
+		/* Modify the feature */
+		if ((retval = cfi_ep_set_concat_val(buf, pcd)) < 0)
+			return retval;
+		cfi->need_gadget_att = 1;
+		break;
+
+	case FT_ID_DMA_CIRCULAR:
+		CFI_INFO("FT_ID_DMA_CIRCULAR\n");
+		break;
+
+	case FT_ID_THRESHOLD_SETUP:
+		CFI_INFO("FT_ID_THRESHOLD_SETUP\n");
+		break;
+
+	case FT_ID_DFIFO_DEPTH:
+		CFI_INFO("FT_ID_DFIFO_DEPTH\n");
+		break;
+
+	case FT_ID_TX_FIFO_DEPTH:
+		CFI_INFO("FT_ID_TX_FIFO_DEPTH\n");
+		if ((retval = cfi_ep_set_tx_fifo_val(buf, pcd)) < 0)
+			return retval;
+		cfi->need_gadget_att = 0;
+		break;
+
+	case FT_ID_RX_FIFO_DEPTH:
+		CFI_INFO("FT_ID_RX_FIFO_DEPTH\n");
+		if ((retval = cfi_set_rx_fifo_val(buf, pcd)) < 0)
+			return retval;
+		cfi->need_gadget_att = 0;
+		break;
+	}
+
+	return retval;
+}
+
+#endif //DWC_UTE_CFI
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_cfi.h b/drivers/usb/host/dwc_otg/dwc_otg_cfi.h
new file mode 100644
index 000000000000..55fd337a283c
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_cfi.h
@@ -0,0 +1,320 @@
+/* ==========================================================================
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+
+#if !defined(__DWC_OTG_CFI_H__)
+#define __DWC_OTG_CFI_H__
+
+#include "dwc_otg_pcd.h"
+#include "dwc_cfi_common.h"
+
+/**
+ * @file
+ * This file contains the CFI related OTG PCD specific common constants,
+ * interfaces(functions and macros) and data structures.The CFI Protocol is an
+ * optional interface for internal testing purposes that a DUT may implement to
+ * support testing of configurable features.
+ *
+ */
+
+struct dwc_otg_pcd;
+struct dwc_otg_pcd_ep;
+
+/** OTG CFI Features (properties) ID constants */
+/** This is a request for all Core Features */
+#define FT_ID_DMA_MODE					0x0001
+#define FT_ID_DMA_BUFFER_SETUP			0x0002
+#define FT_ID_DMA_BUFF_ALIGN			0x0003
+#define FT_ID_DMA_CONCAT_SETUP			0x0004
+#define FT_ID_DMA_CIRCULAR				0x0005
+#define FT_ID_THRESHOLD_SETUP			0x0006
+#define FT_ID_DFIFO_DEPTH				0x0007
+#define FT_ID_TX_FIFO_DEPTH				0x0008
+#define FT_ID_RX_FIFO_DEPTH				0x0009
+
+/**********************************************************/
+#define CFI_INFO_DEF
+
+#ifdef CFI_INFO_DEF
+#define CFI_INFO(fmt...)	DWC_PRINTF("CFI: " fmt);
+#else
+#define CFI_INFO(fmt...)
+#endif
+
+#define min(x,y) ({ \
+	x < y ? x : y; })
+
+#define max(x,y) ({ \
+	x > y ? x : y; })
+
+/**
+ * Descriptor DMA SG Buffer setup structure (SG buffer). This structure is
+ * also used for setting up a buffer for Circular DDMA.
+ */
+struct _ddma_sg_buffer_setup {
+#define BS_SG_VAL_DESC_LEN	6
+	/* The OUT EP address */
+	uint8_t bOutEndpointAddress;
+	/* The IN EP address */
+	uint8_t bInEndpointAddress;
+	/* Number of bytes to put between transfer segments (must be DWORD boundaries) */
+	uint8_t bOffset;
+	/* The number of transfer segments (a DMA descriptors per each segment) */
+	uint8_t bCount;
+	/* Size (in byte) of each transfer segment */
+	uint16_t wSize;
+} __attribute__ ((packed));
+typedef struct _ddma_sg_buffer_setup ddma_sg_buffer_setup_t;
+
+/** Descriptor DMA Concatenation Buffer setup structure */
+struct _ddma_concat_buffer_setup_hdr {
+#define BS_CONCAT_VAL_HDR_LEN	4
+	/* The endpoint for which the buffer is to be set up */
+	uint8_t bEndpointAddress;
+	/* The count of descriptors to be used */
+	uint8_t bDescCount;
+	/* The total size of the transfer */
+	uint16_t wSize;
+} __attribute__ ((packed));
+typedef struct _ddma_concat_buffer_setup_hdr ddma_concat_buffer_setup_hdr_t;
+
+/** Descriptor DMA Concatenation Buffer setup structure */
+struct _ddma_concat_buffer_setup {
+	/* The SG header */
+	ddma_concat_buffer_setup_hdr_t hdr;
+
+	/* The XFER sizes pointer (allocated dynamically) */
+	uint16_t *wTxBytes;
+} __attribute__ ((packed));
+typedef struct _ddma_concat_buffer_setup ddma_concat_buffer_setup_t;
+
+/** Descriptor DMA Alignment Buffer setup structure */
+struct _ddma_align_buffer_setup {
+#define BS_ALIGN_VAL_HDR_LEN	2
+	uint8_t bEndpointAddress;
+	uint8_t bAlign;
+} __attribute__ ((packed));
+typedef struct _ddma_align_buffer_setup ddma_align_buffer_setup_t;
+
+/** Transmit FIFO Size setup structure */
+struct _tx_fifo_size_setup {
+	uint8_t bEndpointAddress;
+	uint16_t wDepth;
+} __attribute__ ((packed));
+typedef struct _tx_fifo_size_setup tx_fifo_size_setup_t;
+
+/** Transmit FIFO Size setup structure */
+struct _rx_fifo_size_setup {
+	uint16_t wDepth;
+} __attribute__ ((packed));
+typedef struct _rx_fifo_size_setup rx_fifo_size_setup_t;
+
+/**
+ * struct cfi_usb_ctrlrequest - the CFI implementation of the struct usb_ctrlrequest
+ * This structure encapsulates the standard usb_ctrlrequest and adds a pointer
+ * to the data returned in the data stage of a 3-stage Control Write requests.
+ */
+struct cfi_usb_ctrlrequest {
+	uint8_t bRequestType;
+	uint8_t bRequest;
+	uint16_t wValue;
+	uint16_t wIndex;
+	uint16_t wLength;
+	uint8_t *data;
+} UPACKED;
+
+/*---------------------------------------------------------------------------*/
+
+/**
+ * The CFI wrapper of the enabled and activated dwc_otg_pcd_ep structures.
+ * This structure is used to store the buffer setup data for any
+ * enabled endpoint in the PCD.
+ */
+struct cfi_ep {
+	/* Entry for the list container */
+	dwc_list_link_t lh;
+	/* Pointer to the active PCD endpoint structure */
+	struct dwc_otg_pcd_ep *ep;
+	/* The last descriptor in the chain of DMA descriptors of the endpoint */
+	struct dwc_otg_dma_desc *dma_desc_last;
+	/* The SG feature value */
+	ddma_sg_buffer_setup_t *bm_sg;
+	/* The Circular feature value */
+	ddma_sg_buffer_setup_t *bm_circ;
+	/* The Concatenation feature value */
+	ddma_concat_buffer_setup_t *bm_concat;
+	/* The Alignment feature value */
+	ddma_align_buffer_setup_t *bm_align;
+	/* XFER length */
+	uint32_t xfer_len;
+	/*
+	 * Count of DMA descriptors currently used.
+	 * The total should not exceed the MAX_DMA_DESCS_PER_EP value
+	 * defined in the dwc_otg_cil.h
+	 */
+	uint32_t desc_count;
+};
+typedef struct cfi_ep cfi_ep_t;
+
+typedef struct cfi_dma_buff {
+#define CFI_IN_BUF_LEN	1024
+#define CFI_OUT_BUF_LEN	1024
+	dma_addr_t addr;
+	uint8_t *buf;
+} cfi_dma_buff_t;
+
+struct cfiobject;
+
+/**
+ * This is the interface for the CFI operations.
+ *
+ * @param	ep_enable			Called when any endpoint is enabled and activated.
+ * @param	release				Called when the CFI object is released and it needs to correctly
+ *								deallocate the dynamic memory
+ * @param	ctrl_write_complete	Called when the data stage of the request is complete
+ */
+typedef struct cfi_ops {
+	int (*ep_enable) (struct cfiobject * cfi, struct dwc_otg_pcd * pcd,
+			  struct dwc_otg_pcd_ep * ep);
+	void *(*ep_alloc_buf) (struct cfiobject * cfi, struct dwc_otg_pcd * pcd,
+			       struct dwc_otg_pcd_ep * ep, dma_addr_t * dma,
+			       unsigned size, gfp_t flags);
+	void (*release) (struct cfiobject * cfi);
+	int (*ctrl_write_complete) (struct cfiobject * cfi,
+				    struct dwc_otg_pcd * pcd);
+	void (*build_descriptors) (struct cfiobject * cfi,
+				   struct dwc_otg_pcd * pcd,
+				   struct dwc_otg_pcd_ep * ep,
+				   dwc_otg_pcd_request_t * req);
+} cfi_ops_t;
+
+struct cfiobject {
+	cfi_ops_t ops;
+	struct dwc_otg_pcd *pcd;
+	struct usb_gadget *gadget;
+
+	/* Buffers used to send/receive CFI-related request data */
+	cfi_dma_buff_t buf_in;
+	cfi_dma_buff_t buf_out;
+
+	/* CFI specific Control request wrapper */
+	struct cfi_usb_ctrlrequest ctrl_req;
+
+	/* The list of active EP's in the PCD of type cfi_ep_t */
+	dwc_list_link_t active_eps;
+
+	/* This flag shall control the propagation of a specific request
+	 * to the gadget's processing routines.
+	 * 0 - no gadget handling
+	 * 1 - the gadget needs to know about this request (w/o completing a status
+	 * phase - just return a 0 to the _setup callback)
+	 */
+	uint8_t need_gadget_att;
+
+	/* Flag indicating whether the status IN phase needs to be
+	 * completed by the PCD
+	 */
+	uint8_t need_status_in_complete;
+};
+typedef struct cfiobject cfiobject_t;
+
+#define DUMP_MSG
+
+#if defined(DUMP_MSG)
+static inline void dump_msg(const u8 * buf, unsigned int length)
+{
+	unsigned int start, num, i;
+	char line[52], *p;
+
+	if (length >= 512)
+		return;
+
+	start = 0;
+	while (length > 0) {
+		num = min(length, 16u);
+		p = line;
+		for (i = 0; i < num; ++i) {
+			if (i == 8)
+				*p++ = ' ';
+			DWC_SPRINTF(p, " %02x", buf[i]);
+			p += 3;
+		}
+		*p = 0;
+		DWC_DEBUG("%6x: %s\n", start, line);
+		buf += num;
+		start += num;
+		length -= num;
+	}
+}
+#else
+static inline void dump_msg(const u8 * buf, unsigned int length)
+{
+}
+#endif
+
+/**
+ * This function returns a pointer to cfi_ep_t object with the addr address.
+ */
+static inline struct cfi_ep *get_cfi_ep_by_addr(struct cfiobject *cfi,
+						uint8_t addr)
+{
+	struct cfi_ep *pcfiep;
+	dwc_list_link_t *tmp;
+
+	DWC_LIST_FOREACH(tmp, &cfi->active_eps) {
+		pcfiep = DWC_LIST_ENTRY(tmp, struct cfi_ep, lh);
+
+		if (pcfiep->ep->desc->bEndpointAddress == addr) {
+			return pcfiep;
+		}
+	}
+
+	return NULL;
+}
+
+/**
+ * This function returns a pointer to cfi_ep_t object that matches
+ * the dwc_otg_pcd_ep object.
+ */
+static inline struct cfi_ep *get_cfi_ep_by_pcd_ep(struct cfiobject *cfi,
+						  struct dwc_otg_pcd_ep *ep)
+{
+	struct cfi_ep *pcfiep = NULL;
+	dwc_list_link_t *tmp;
+
+	DWC_LIST_FOREACH(tmp, &cfi->active_eps) {
+		pcfiep = DWC_LIST_ENTRY(tmp, struct cfi_ep, lh);
+		if (pcfiep->ep == ep) {
+			return pcfiep;
+		}
+	}
+	return NULL;
+}
+
+int cfi_setup(struct dwc_otg_pcd *pcd, struct cfi_usb_ctrlrequest *ctrl);
+
+#endif /* (__DWC_OTG_CFI_H__) */
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_cil.c b/drivers/usb/host/dwc_otg/dwc_otg_cil.c
new file mode 100644
index 000000000000..b3dd2ad5237c
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_cil.c
@@ -0,0 +1,7121 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_cil.c $
+ * $Revision: #191 $
+ * $Date: 2012/08/10 $
+ * $Change: 2047372 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+
+/** @file
+ *
+ * The Core Interface Layer provides basic services for accessing and
+ * managing the DWC_otg hardware. These services are used by both the
+ * Host Controller Driver and the Peripheral Controller Driver.
+ *
+ * The CIL manages the memory map for the core so that the HCD and PCD
+ * don't have to do this separately. It also handles basic tasks like
+ * reading/writing the registers and data FIFOs in the controller.
+ * Some of the data access functions provide encapsulation of several
+ * operations required to perform a task, such as writing multiple
+ * registers to start a transfer. Finally, the CIL performs basic
+ * services that are not specific to either the host or device modes
+ * of operation. These services include management of the OTG Host
+ * Negotiation Protocol (HNP) and Session Request Protocol (SRP). A
+ * Diagnostic API is also provided to allow testing of the controller
+ * hardware.
+ *
+ * The Core Interface Layer has the following requirements:
+ * - Provides basic controller operations.
+ * - Minimal use of OS services.
+ * - The OS services used will be abstracted by using inline functions
+ *	 or macros.
+ *
+ */
+
+#include "dwc_os.h"
+#include "dwc_otg_regs.h"
+#include "dwc_otg_cil.h"
+#include "dwc_otg_os_dep.h"
+#include "dwc_otg_hcd_if.h"
+
+extern bool cil_force_host;
+
+static int dwc_otg_setup_params(dwc_otg_core_if_t * core_if);
+
+/**
+ * This function is called to initialize the DWC_otg CSR data
+ * structures. The register addresses in the device and host
+ * structures are initialized from the base address supplied by the
+ * caller. The calling function must make the OS calls to get the
+ * base address of the DWC_otg controller registers. The core_params
+ * argument holds the parameters that specify how the core should be
+ * configured.
+ *
+ * @param reg_base_addr Base address of DWC_otg core registers
+ *
+ */
+dwc_otg_core_if_t *dwc_otg_cil_init(const uint32_t * reg_base_addr)
+{
+	dwc_otg_core_if_t *core_if = 0;
+	dwc_otg_dev_if_t *dev_if = 0;
+	dwc_otg_host_if_t *host_if = 0;
+	uint8_t *reg_base = (uint8_t *) reg_base_addr;
+	int i = 0;
+
+	DWC_DEBUGPL(DBG_CILV, "%s(%p)\n", __func__, reg_base_addr);
+
+	core_if = DWC_ALLOC(sizeof(dwc_otg_core_if_t));
+
+	if (core_if == NULL) {
+		DWC_DEBUGPL(DBG_CIL,
+			    "Allocation of dwc_otg_core_if_t failed\n");
+		return 0;
+	}
+	core_if->core_global_regs = (dwc_otg_core_global_regs_t *) reg_base;
+
+	/*
+	 * Allocate the Device Mode structures.
+	 */
+	dev_if = DWC_ALLOC(sizeof(dwc_otg_dev_if_t));
+
+	if (dev_if == NULL) {
+		DWC_DEBUGPL(DBG_CIL, "Allocation of dwc_otg_dev_if_t failed\n");
+		DWC_FREE(core_if);
+		return 0;
+	}
+
+	dev_if->dev_global_regs =
+	    (dwc_otg_device_global_regs_t *) (reg_base +
+					      DWC_DEV_GLOBAL_REG_OFFSET);
+
+	for (i = 0; i < MAX_EPS_CHANNELS; i++) {
+		dev_if->in_ep_regs[i] = (dwc_otg_dev_in_ep_regs_t *)
+		    (reg_base + DWC_DEV_IN_EP_REG_OFFSET +
+		     (i * DWC_EP_REG_OFFSET));
+
+		dev_if->out_ep_regs[i] = (dwc_otg_dev_out_ep_regs_t *)
+		    (reg_base + DWC_DEV_OUT_EP_REG_OFFSET +
+		     (i * DWC_EP_REG_OFFSET));
+		DWC_DEBUGPL(DBG_CILV, "in_ep_regs[%d]->diepctl=%p\n",
+			    i, &dev_if->in_ep_regs[i]->diepctl);
+		DWC_DEBUGPL(DBG_CILV, "out_ep_regs[%d]->doepctl=%p\n",
+			    i, &dev_if->out_ep_regs[i]->doepctl);
+	}
+
+	dev_if->speed = 0;	// unknown
+
+	core_if->dev_if = dev_if;
+
+	/*
+	 * Allocate the Host Mode structures.
+	 */
+	host_if = DWC_ALLOC(sizeof(dwc_otg_host_if_t));
+
+	if (host_if == NULL) {
+		DWC_DEBUGPL(DBG_CIL,
+			    "Allocation of dwc_otg_host_if_t failed\n");
+		DWC_FREE(dev_if);
+		DWC_FREE(core_if);
+		return 0;
+	}
+
+	host_if->host_global_regs = (dwc_otg_host_global_regs_t *)
+	    (reg_base + DWC_OTG_HOST_GLOBAL_REG_OFFSET);
+
+	host_if->hprt0 =
+	    (uint32_t *) (reg_base + DWC_OTG_HOST_PORT_REGS_OFFSET);
+
+	for (i = 0; i < MAX_EPS_CHANNELS; i++) {
+		host_if->hc_regs[i] = (dwc_otg_hc_regs_t *)
+		    (reg_base + DWC_OTG_HOST_CHAN_REGS_OFFSET +
+		     (i * DWC_OTG_CHAN_REGS_OFFSET));
+		DWC_DEBUGPL(DBG_CILV, "hc_reg[%d]->hcchar=%p\n",
+			    i, &host_if->hc_regs[i]->hcchar);
+	}
+
+	host_if->num_host_channels = MAX_EPS_CHANNELS;
+	core_if->host_if = host_if;
+
+	for (i = 0; i < MAX_EPS_CHANNELS; i++) {
+		core_if->data_fifo[i] =
+		    (uint32_t *) (reg_base + DWC_OTG_DATA_FIFO_OFFSET +
+				  (i * DWC_OTG_DATA_FIFO_SIZE));
+		DWC_DEBUGPL(DBG_CILV, "data_fifo[%d]=0x%08lx\n",
+			    i, (unsigned long)core_if->data_fifo[i]);
+	}
+
+	core_if->pcgcctl = (uint32_t *) (reg_base + DWC_OTG_PCGCCTL_OFFSET);
+
+	/* Initiate lx_state to L3 disconnected state */
+	core_if->lx_state = DWC_OTG_L3;
+	/*
+	 * Store the contents of the hardware configuration registers here for
+	 * easy access later.
+	 */
+	core_if->hwcfg1.d32 =
+	    DWC_READ_REG32(&core_if->core_global_regs->ghwcfg1);
+	core_if->hwcfg2.d32 =
+	    DWC_READ_REG32(&core_if->core_global_regs->ghwcfg2);
+	core_if->hwcfg3.d32 =
+	    DWC_READ_REG32(&core_if->core_global_regs->ghwcfg3);
+	core_if->hwcfg4.d32 =
+	    DWC_READ_REG32(&core_if->core_global_regs->ghwcfg4);
+
+	/* Force host mode to get HPTXFSIZ exact power on value */
+	{
+		gusbcfg_data_t gusbcfg = {.d32 = 0 };
+		gusbcfg.d32 =  DWC_READ_REG32(&core_if->core_global_regs->gusbcfg);
+		gusbcfg.b.force_host_mode = 1;
+		DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, gusbcfg.d32);
+		dwc_mdelay(100);
+		core_if->hptxfsiz.d32 =
+		DWC_READ_REG32(&core_if->core_global_regs->hptxfsiz);
+		gusbcfg.d32 =  DWC_READ_REG32(&core_if->core_global_regs->gusbcfg);
+		if (cil_force_host)
+			gusbcfg.b.force_host_mode = 1;
+		else
+			gusbcfg.b.force_host_mode = 0;
+		DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, gusbcfg.d32);
+		dwc_mdelay(100);
+	}
+
+	DWC_DEBUGPL(DBG_CILV, "hwcfg1=%08x\n", core_if->hwcfg1.d32);
+	DWC_DEBUGPL(DBG_CILV, "hwcfg2=%08x\n", core_if->hwcfg2.d32);
+	DWC_DEBUGPL(DBG_CILV, "hwcfg3=%08x\n", core_if->hwcfg3.d32);
+	DWC_DEBUGPL(DBG_CILV, "hwcfg4=%08x\n", core_if->hwcfg4.d32);
+
+	core_if->hcfg.d32 =
+	    DWC_READ_REG32(&core_if->host_if->host_global_regs->hcfg);
+	core_if->dcfg.d32 =
+	    DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg);
+
+	DWC_DEBUGPL(DBG_CILV, "hcfg=%08x\n", core_if->hcfg.d32);
+	DWC_DEBUGPL(DBG_CILV, "dcfg=%08x\n", core_if->dcfg.d32);
+
+	DWC_DEBUGPL(DBG_CILV, "op_mode=%0x\n", core_if->hwcfg2.b.op_mode);
+	DWC_DEBUGPL(DBG_CILV, "arch=%0x\n", core_if->hwcfg2.b.architecture);
+	DWC_DEBUGPL(DBG_CILV, "num_dev_ep=%d\n", core_if->hwcfg2.b.num_dev_ep);
+	DWC_DEBUGPL(DBG_CILV, "num_host_chan=%d\n",
+		    core_if->hwcfg2.b.num_host_chan);
+	DWC_DEBUGPL(DBG_CILV, "nonperio_tx_q_depth=0x%0x\n",
+		    core_if->hwcfg2.b.nonperio_tx_q_depth);
+	DWC_DEBUGPL(DBG_CILV, "host_perio_tx_q_depth=0x%0x\n",
+		    core_if->hwcfg2.b.host_perio_tx_q_depth);
+	DWC_DEBUGPL(DBG_CILV, "dev_token_q_depth=0x%0x\n",
+		    core_if->hwcfg2.b.dev_token_q_depth);
+
+	DWC_DEBUGPL(DBG_CILV, "Total FIFO SZ=%d\n",
+		    core_if->hwcfg3.b.dfifo_depth);
+	DWC_DEBUGPL(DBG_CILV, "xfer_size_cntr_width=%0x\n",
+		    core_if->hwcfg3.b.xfer_size_cntr_width);
+
+	/*
+	 * Set the SRP sucess bit for FS-I2c
+	 */
+	core_if->srp_success = 0;
+	core_if->srp_timer_started = 0;
+
+	/*
+	 * Create new workqueue and init works
+	 */
+	core_if->wq_otg = DWC_WORKQ_ALLOC("dwc_otg");
+	if (core_if->wq_otg == 0) {
+		DWC_WARN("DWC_WORKQ_ALLOC failed\n");
+		DWC_FREE(host_if);
+		DWC_FREE(dev_if);
+		DWC_FREE(core_if);
+		return 0;
+	}
+
+	core_if->snpsid = DWC_READ_REG32(&core_if->core_global_regs->gsnpsid);
+
+	DWC_PRINTF("Core Release: %x.%x%x%x\n",
+		   (core_if->snpsid >> 12 & 0xF),
+		   (core_if->snpsid >> 8 & 0xF),
+		   (core_if->snpsid >> 4 & 0xF), (core_if->snpsid & 0xF));
+
+	core_if->wkp_timer = DWC_TIMER_ALLOC("Wake Up Timer",
+					     w_wakeup_detected, core_if);
+	if (core_if->wkp_timer == 0) {
+		DWC_WARN("DWC_TIMER_ALLOC failed\n");
+		DWC_FREE(host_if);
+		DWC_FREE(dev_if);
+		DWC_WORKQ_FREE(core_if->wq_otg);
+		DWC_FREE(core_if);
+		return 0;
+	}
+
+	if (dwc_otg_setup_params(core_if)) {
+		DWC_WARN("Error while setting core params\n");
+	}
+
+	core_if->hibernation_suspend = 0;
+
+	/** ADP initialization */
+	dwc_otg_adp_init(core_if);
+
+	return core_if;
+}
+
+/**
+ * This function frees the structures allocated by dwc_otg_cil_init().
+ *
+ * @param core_if The core interface pointer returned from
+ * 		  dwc_otg_cil_init().
+ *
+ */
+void dwc_otg_cil_remove(dwc_otg_core_if_t * core_if)
+{
+	dctl_data_t dctl = {.d32 = 0 };
+	DWC_DEBUGPL(DBG_CILV, "%s(%p)\n", __func__, core_if);
+
+	/* Disable all interrupts */
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gahbcfg, 1, 0);
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, 0);
+
+	dctl.b.sftdiscon = 1;
+	if (core_if->snpsid >= OTG_CORE_REV_3_00a) {
+		DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, 0,
+				 dctl.d32);
+	}
+
+	if (core_if->wq_otg) {
+		DWC_WORKQ_WAIT_WORK_DONE(core_if->wq_otg, 500);
+		DWC_WORKQ_FREE(core_if->wq_otg);
+	}
+	if (core_if->dev_if) {
+		DWC_FREE(core_if->dev_if);
+	}
+	if (core_if->host_if) {
+		DWC_FREE(core_if->host_if);
+	}
+
+	/** Remove ADP Stuff  */
+	dwc_otg_adp_remove(core_if);
+	if (core_if->core_params) {
+		DWC_FREE(core_if->core_params);
+	}
+	if (core_if->wkp_timer) {
+		DWC_TIMER_FREE(core_if->wkp_timer);
+	}
+	if (core_if->srp_timer) {
+		DWC_TIMER_FREE(core_if->srp_timer);
+	}
+	DWC_FREE(core_if);
+}
+
+/**
+ * This function enables the controller's Global Interrupt in the AHB Config
+ * register.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+void dwc_otg_enable_global_interrupts(dwc_otg_core_if_t * core_if)
+{
+	gahbcfg_data_t ahbcfg = {.d32 = 0 };
+	ahbcfg.b.glblintrmsk = 1;	/* Enable interrupts */
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gahbcfg, 0, ahbcfg.d32);
+}
+
+/**
+ * This function disables the controller's Global Interrupt in the AHB Config
+ * register.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+void dwc_otg_disable_global_interrupts(dwc_otg_core_if_t * core_if)
+{
+	gahbcfg_data_t ahbcfg = {.d32 = 0 };
+	ahbcfg.b.glblintrmsk = 1;	/* Disable interrupts */
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gahbcfg, ahbcfg.d32, 0);
+}
+
+/**
+ * This function initializes the commmon interrupts, used in both
+ * device and host modes.
+ *
+ * @param core_if Programming view of the DWC_otg controller
+ *
+ */
+static void dwc_otg_enable_common_interrupts(dwc_otg_core_if_t * core_if)
+{
+	dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs;
+	gintmsk_data_t intr_mask = {.d32 = 0 };
+
+	/* Clear any pending OTG Interrupts */
+	DWC_WRITE_REG32(&global_regs->gotgint, 0xFFFFFFFF);
+
+	/* Clear any pending interrupts */
+	DWC_WRITE_REG32(&global_regs->gintsts, 0xFFFFFFFF);
+
+	/*
+	 * Enable the interrupts in the GINTMSK.
+	 */
+	intr_mask.b.modemismatch = 1;
+	intr_mask.b.otgintr = 1;
+
+	if (!core_if->dma_enable) {
+		intr_mask.b.rxstsqlvl = 1;
+	}
+
+	intr_mask.b.conidstschng = 1;
+	intr_mask.b.wkupintr = 1;
+	intr_mask.b.disconnect = 0;
+	intr_mask.b.usbsuspend = 1;
+	intr_mask.b.sessreqintr = 1;
+#ifdef CONFIG_USB_DWC_OTG_LPM
+	if (core_if->core_params->lpm_enable) {
+		intr_mask.b.lpmtranrcvd = 1;
+	}
+#endif
+	DWC_WRITE_REG32(&global_regs->gintmsk, intr_mask.d32);
+}
+
+/*
+ * The restore operation is modified to support Synopsys Emulated Powerdown and
+ * Hibernation. This function is for exiting from Device mode hibernation by
+ * Host Initiated Resume/Reset and Device Initiated Remote-Wakeup.
+ * @param core_if Programming view of DWC_otg controller.
+ * @param rem_wakeup - indicates whether resume is initiated by Device or Host.
+ * @param reset - indicates whether resume is initiated by Reset.
+ */
+int dwc_otg_device_hibernation_restore(dwc_otg_core_if_t * core_if,
+				       int rem_wakeup, int reset)
+{
+	gpwrdn_data_t gpwrdn = {.d32 = 0 };
+	pcgcctl_data_t pcgcctl = {.d32 = 0 };
+	dctl_data_t dctl = {.d32 = 0 };
+
+	int timeout = 2000;
+
+	if (!core_if->hibernation_suspend) {
+		DWC_PRINTF("Already exited from Hibernation\n");
+		return 1;
+	}
+
+	DWC_DEBUGPL(DBG_PCD, "%s called\n", __FUNCTION__);
+	/* Switch-on voltage to the core */
+	gpwrdn.b.pwrdnswtch = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+	dwc_udelay(10);
+
+	/* Reset core */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pwrdnrstn = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+	dwc_udelay(10);
+
+	/* Assert Restore signal */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.restore = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32);
+	dwc_udelay(10);
+
+	/* Disable power clamps */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pwrdnclmp = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+	if (rem_wakeup) {
+		dwc_udelay(70);
+	}
+
+	/* Deassert Reset core */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pwrdnrstn = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32);
+	dwc_udelay(10);
+
+	/* Disable PMU interrupt */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pmuintsel = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+	/* Mask interrupts from gpwrdn */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.connect_det_msk = 1;
+	gpwrdn.b.srp_det_msk = 1;
+	gpwrdn.b.disconn_det_msk = 1;
+	gpwrdn.b.rst_det_msk = 1;
+	gpwrdn.b.lnstchng_msk = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+	/* Indicates that we are going out from hibernation */
+	core_if->hibernation_suspend = 0;
+
+	/*
+	 * Set Restore Essential Regs bit in PCGCCTL register, restore_mode = 1
+	 * indicates restore from remote_wakeup
+	 */
+	restore_essential_regs(core_if, rem_wakeup, 0);
+
+	/*
+	 * Wait a little for seeing new value of variable hibernation_suspend if
+	 * Restore done interrupt received before polling
+	 */
+	dwc_udelay(10);
+
+	if (core_if->hibernation_suspend == 0) {
+		/*
+		 * Wait For Restore_done Interrupt. This mechanism of polling the
+		 * interrupt is introduced to avoid any possible race conditions
+		 */
+		do {
+			gintsts_data_t gintsts;
+			gintsts.d32 =
+			    DWC_READ_REG32(&core_if->core_global_regs->gintsts);
+			if (gintsts.b.restoredone) {
+				gintsts.d32 = 0;
+				gintsts.b.restoredone = 1;
+				DWC_WRITE_REG32(&core_if->core_global_regs->
+						gintsts, gintsts.d32);
+				DWC_PRINTF("Restore Done Interrupt seen\n");
+				break;
+			}
+			dwc_udelay(10);
+		} while (--timeout);
+		if (!timeout) {
+			DWC_PRINTF("Restore Done interrupt wasn't generated here\n");
+		}
+	}
+	/* Clear all pending interupts */
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF);
+
+	/* De-assert Restore */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.restore = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+	dwc_udelay(10);
+
+	if (!rem_wakeup) {
+		pcgcctl.d32 = 0;
+		pcgcctl.b.rstpdwnmodule = 1;
+		DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, 0);
+	}
+
+	/* Restore GUSBCFG and DCFG */
+	DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg,
+			core_if->gr_backup->gusbcfg_local);
+	DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dcfg,
+			core_if->dr_backup->dcfg);
+
+	/* De-assert Wakeup Logic */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pmuactv = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+	dwc_udelay(10);
+
+	if (!rem_wakeup) {
+		/* Set Device programming done bit */
+		dctl.b.pwronprgdone = 1;
+		DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, 0, dctl.d32);
+	} else {
+		/* Start Remote Wakeup Signaling */
+		dctl.d32 = core_if->dr_backup->dctl;
+		dctl.b.rmtwkupsig = 1;
+		DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32);
+	}
+
+	dwc_mdelay(2);
+	/* Clear all pending interupts */
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF);
+
+	/* Restore global registers */
+	dwc_otg_restore_global_regs(core_if);
+	/* Restore device global registers */
+	dwc_otg_restore_dev_regs(core_if, rem_wakeup);
+
+	if (rem_wakeup) {
+		dwc_mdelay(7);
+		dctl.d32 = 0;
+		dctl.b.rmtwkupsig = 1;
+		DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32, 0);
+	}
+
+	core_if->hibernation_suspend = 0;
+	/* The core will be in ON STATE */
+	core_if->lx_state = DWC_OTG_L0;
+	DWC_PRINTF("Hibernation recovery completes here\n");
+
+	return 1;
+}
+
+/*
+ * The restore operation is modified to support Synopsys Emulated Powerdown and
+ * Hibernation. This function is for exiting from Host mode hibernation by
+ * Host Initiated Resume/Reset and Device Initiated Remote-Wakeup.
+ * @param core_if Programming view of DWC_otg controller.
+ * @param rem_wakeup - indicates whether resume is initiated by Device or Host.
+ * @param reset - indicates whether resume is initiated by Reset.
+ */
+int dwc_otg_host_hibernation_restore(dwc_otg_core_if_t * core_if,
+				     int rem_wakeup, int reset)
+{
+	gpwrdn_data_t gpwrdn = {.d32 = 0 };
+	hprt0_data_t hprt0 = {.d32 = 0 };
+
+	int timeout = 2000;
+
+	DWC_DEBUGPL(DBG_HCD, "%s called\n", __FUNCTION__);
+	/* Switch-on voltage to the core */
+	gpwrdn.b.pwrdnswtch = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+	dwc_udelay(10);
+
+	/* Reset core */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pwrdnrstn = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+	dwc_udelay(10);
+
+	/* Assert Restore signal */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.restore = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32);
+	dwc_udelay(10);
+
+	/* Disable power clamps */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pwrdnclmp = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+	if (!rem_wakeup) {
+		dwc_udelay(50);
+	}
+
+	/* Deassert Reset core */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pwrdnrstn = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32);
+	dwc_udelay(10);
+
+	/* Disable PMU interrupt */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pmuintsel = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+	gpwrdn.d32 = 0;
+	gpwrdn.b.connect_det_msk = 1;
+	gpwrdn.b.srp_det_msk = 1;
+	gpwrdn.b.disconn_det_msk = 1;
+	gpwrdn.b.rst_det_msk = 1;
+	gpwrdn.b.lnstchng_msk = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+	/* Indicates that we are going out from hibernation */
+	core_if->hibernation_suspend = 0;
+
+	/* Set Restore Essential Regs bit in PCGCCTL register */
+	restore_essential_regs(core_if, rem_wakeup, 1);
+
+	/* Wait a little for seeing new value of variable hibernation_suspend if
+	 * Restore done interrupt received before polling */
+	dwc_udelay(10);
+
+	if (core_if->hibernation_suspend == 0) {
+		/* Wait For Restore_done Interrupt. This mechanism of polling the
+		 * interrupt is introduced to avoid any possible race conditions
+		 */
+		do {
+			gintsts_data_t gintsts;
+			gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts);
+			if (gintsts.b.restoredone) {
+				gintsts.d32 = 0;
+				gintsts.b.restoredone = 1;
+			DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32);
+				DWC_DEBUGPL(DBG_HCD,"Restore Done Interrupt seen\n");
+				break;
+			}
+			dwc_udelay(10);
+		} while (--timeout);
+		if (!timeout) {
+			DWC_WARN("Restore Done interrupt wasn't generated\n");
+		}
+	}
+
+	/* Set the flag's value to 0 again after receiving restore done interrupt */
+	core_if->hibernation_suspend = 0;
+
+	/* This step is not described in functional spec but if not wait for this
+	 * delay, mismatch interrupts occurred because just after restore core is
+	 * in Device mode(gintsts.curmode == 0) */
+	dwc_mdelay(100);
+
+	/* Clear all pending interrupts */
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF);
+
+	/* De-assert Restore */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.restore = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+	dwc_udelay(10);
+
+	/* Restore GUSBCFG and HCFG */
+	DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg,
+			core_if->gr_backup->gusbcfg_local);
+	DWC_WRITE_REG32(&core_if->host_if->host_global_regs->hcfg,
+			core_if->hr_backup->hcfg_local);
+
+	/* De-assert Wakeup Logic */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pmuactv = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+	dwc_udelay(10);
+
+	/* Start the Resume operation by programming HPRT0 */
+	hprt0.d32 = core_if->hr_backup->hprt0_local;
+	hprt0.b.prtpwr = 1;
+	hprt0.b.prtena = 0;
+	hprt0.b.prtsusp = 0;
+	DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+
+	DWC_PRINTF("Resume Starts Now\n");
+	if (!reset) {		// Indicates it is Resume Operation
+		hprt0.d32 = core_if->hr_backup->hprt0_local;
+		hprt0.b.prtres = 1;
+		hprt0.b.prtpwr = 1;
+		hprt0.b.prtena = 0;
+		hprt0.b.prtsusp = 0;
+		DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+
+		if (!rem_wakeup)
+			hprt0.b.prtres = 0;
+		/* Wait for Resume time and then program HPRT again */
+		dwc_mdelay(100);
+		DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+
+	} else {		// Indicates it is Reset Operation
+		hprt0.d32 = core_if->hr_backup->hprt0_local;
+		hprt0.b.prtrst = 1;
+		hprt0.b.prtpwr = 1;
+		hprt0.b.prtena = 0;
+		hprt0.b.prtsusp = 0;
+		DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+		/* Wait for Reset time and then program HPRT again */
+		dwc_mdelay(60);
+		hprt0.b.prtrst = 0;
+		DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+	}
+	/* Clear all interrupt status */
+	hprt0.d32 = dwc_otg_read_hprt0(core_if);
+	hprt0.b.prtconndet = 1;
+	hprt0.b.prtenchng = 1;
+	DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+
+	/* Clear all pending interupts */
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF);
+
+	/* Restore global registers */
+	dwc_otg_restore_global_regs(core_if);
+	/* Restore host global registers */
+	dwc_otg_restore_host_regs(core_if, reset);
+
+	/* The core will be in ON STATE */
+	core_if->lx_state = DWC_OTG_L0;
+	DWC_PRINTF("Hibernation recovery is complete here\n");
+	return 0;
+}
+
+/** Saves some register values into system memory. */
+int dwc_otg_save_global_regs(dwc_otg_core_if_t * core_if)
+{
+	struct dwc_otg_global_regs_backup *gr;
+	int i;
+
+	gr = core_if->gr_backup;
+	if (!gr) {
+		gr = DWC_ALLOC(sizeof(*gr));
+		if (!gr) {
+			return -DWC_E_NO_MEMORY;
+		}
+		core_if->gr_backup = gr;
+	}
+
+	gr->gotgctl_local = DWC_READ_REG32(&core_if->core_global_regs->gotgctl);
+	gr->gintmsk_local = DWC_READ_REG32(&core_if->core_global_regs->gintmsk);
+	gr->gahbcfg_local = DWC_READ_REG32(&core_if->core_global_regs->gahbcfg);
+	gr->gusbcfg_local = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg);
+	gr->grxfsiz_local = DWC_READ_REG32(&core_if->core_global_regs->grxfsiz);
+	gr->gnptxfsiz_local = DWC_READ_REG32(&core_if->core_global_regs->gnptxfsiz);
+	gr->hptxfsiz_local = DWC_READ_REG32(&core_if->core_global_regs->hptxfsiz);
+#ifdef CONFIG_USB_DWC_OTG_LPM
+	gr->glpmcfg_local = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg);
+#endif
+	gr->gi2cctl_local = DWC_READ_REG32(&core_if->core_global_regs->gi2cctl);
+	gr->pcgcctl_local = DWC_READ_REG32(core_if->pcgcctl);
+	gr->gdfifocfg_local =
+	    DWC_READ_REG32(&core_if->core_global_regs->gdfifocfg);
+	for (i = 0; i < MAX_EPS_CHANNELS; i++) {
+		gr->dtxfsiz_local[i] =
+		    DWC_READ_REG32(&(core_if->core_global_regs->dtxfsiz[i]));
+	}
+
+	DWC_DEBUGPL(DBG_ANY, "===========Backing Global registers==========\n");
+	DWC_DEBUGPL(DBG_ANY, "Backed up gotgctl   = %08x\n", gr->gotgctl_local);
+	DWC_DEBUGPL(DBG_ANY, "Backed up gintmsk   = %08x\n", gr->gintmsk_local);
+	DWC_DEBUGPL(DBG_ANY, "Backed up gahbcfg   = %08x\n", gr->gahbcfg_local);
+	DWC_DEBUGPL(DBG_ANY, "Backed up gusbcfg   = %08x\n", gr->gusbcfg_local);
+	DWC_DEBUGPL(DBG_ANY, "Backed up grxfsiz   = %08x\n", gr->grxfsiz_local);
+	DWC_DEBUGPL(DBG_ANY, "Backed up gnptxfsiz = %08x\n",
+		    gr->gnptxfsiz_local);
+	DWC_DEBUGPL(DBG_ANY, "Backed up hptxfsiz  = %08x\n",
+		    gr->hptxfsiz_local);
+#ifdef CONFIG_USB_DWC_OTG_LPM
+	DWC_DEBUGPL(DBG_ANY, "Backed up glpmcfg   = %08x\n", gr->glpmcfg_local);
+#endif
+	DWC_DEBUGPL(DBG_ANY, "Backed up gi2cctl   = %08x\n", gr->gi2cctl_local);
+	DWC_DEBUGPL(DBG_ANY, "Backed up pcgcctl   = %08x\n", gr->pcgcctl_local);
+	DWC_DEBUGPL(DBG_ANY,"Backed up gdfifocfg   = %08x\n",gr->gdfifocfg_local);
+
+	return 0;
+}
+
+int dwc_otg_save_dev_regs(dwc_otg_core_if_t * core_if)
+{
+	struct dwc_otg_dev_regs_backup *dr;
+	int i;
+
+	dr = core_if->dr_backup;
+	if (!dr) {
+		dr = DWC_ALLOC(sizeof(*dr));
+		if (!dr) {
+			return -DWC_E_NO_MEMORY;
+		}
+		core_if->dr_backup = dr;
+	}
+
+	dr->dcfg = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg);
+	dr->dctl = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dctl);
+	dr->daintmsk =
+	    DWC_READ_REG32(&core_if->dev_if->dev_global_regs->daintmsk);
+	dr->diepmsk =
+	    DWC_READ_REG32(&core_if->dev_if->dev_global_regs->diepmsk);
+	dr->doepmsk =
+	    DWC_READ_REG32(&core_if->dev_if->dev_global_regs->doepmsk);
+
+	for (i = 0; i < core_if->dev_if->num_in_eps; ++i) {
+		dr->diepctl[i] =
+		    DWC_READ_REG32(&core_if->dev_if->in_ep_regs[i]->diepctl);
+		dr->dieptsiz[i] =
+		    DWC_READ_REG32(&core_if->dev_if->in_ep_regs[i]->dieptsiz);
+		dr->diepdma[i] =
+		    DWC_READ_REG32(&core_if->dev_if->in_ep_regs[i]->diepdma);
+	}
+
+	DWC_DEBUGPL(DBG_ANY,
+		    "=============Backing Host registers==============\n");
+	DWC_DEBUGPL(DBG_ANY, "Backed up dcfg            = %08x\n", dr->dcfg);
+	DWC_DEBUGPL(DBG_ANY, "Backed up dctl        = %08x\n", dr->dctl);
+	DWC_DEBUGPL(DBG_ANY, "Backed up daintmsk            = %08x\n",
+		    dr->daintmsk);
+	DWC_DEBUGPL(DBG_ANY, "Backed up diepmsk        = %08x\n", dr->diepmsk);
+	DWC_DEBUGPL(DBG_ANY, "Backed up doepmsk        = %08x\n", dr->doepmsk);
+	for (i = 0; i < core_if->dev_if->num_in_eps; ++i) {
+		DWC_DEBUGPL(DBG_ANY, "Backed up diepctl[%d]        = %08x\n", i,
+			    dr->diepctl[i]);
+		DWC_DEBUGPL(DBG_ANY, "Backed up dieptsiz[%d]        = %08x\n",
+			    i, dr->dieptsiz[i]);
+		DWC_DEBUGPL(DBG_ANY, "Backed up diepdma[%d]        = %08x\n", i,
+			    dr->diepdma[i]);
+	}
+
+	return 0;
+}
+
+int dwc_otg_save_host_regs(dwc_otg_core_if_t * core_if)
+{
+	struct dwc_otg_host_regs_backup *hr;
+	int i;
+
+	hr = core_if->hr_backup;
+	if (!hr) {
+		hr = DWC_ALLOC(sizeof(*hr));
+		if (!hr) {
+			return -DWC_E_NO_MEMORY;
+		}
+		core_if->hr_backup = hr;
+	}
+
+	hr->hcfg_local =
+	    DWC_READ_REG32(&core_if->host_if->host_global_regs->hcfg);
+	hr->haintmsk_local =
+	    DWC_READ_REG32(&core_if->host_if->host_global_regs->haintmsk);
+	for (i = 0; i < dwc_otg_get_param_host_channels(core_if); ++i) {
+		hr->hcintmsk_local[i] =
+		    DWC_READ_REG32(&core_if->host_if->hc_regs[i]->hcintmsk);
+	}
+	hr->hprt0_local = DWC_READ_REG32(core_if->host_if->hprt0);
+	hr->hfir_local =
+	    DWC_READ_REG32(&core_if->host_if->host_global_regs->hfir);
+
+	DWC_DEBUGPL(DBG_ANY,
+		    "=============Backing Host registers===============\n");
+	DWC_DEBUGPL(DBG_ANY, "Backed up hcfg		= %08x\n",
+		    hr->hcfg_local);
+	DWC_DEBUGPL(DBG_ANY, "Backed up haintmsk = %08x\n", hr->haintmsk_local);
+	for (i = 0; i < dwc_otg_get_param_host_channels(core_if); ++i) {
+		DWC_DEBUGPL(DBG_ANY, "Backed up hcintmsk[%02d]=%08x\n", i,
+			    hr->hcintmsk_local[i]);
+	}
+	DWC_DEBUGPL(DBG_ANY, "Backed up hprt0           = %08x\n",
+		    hr->hprt0_local);
+	DWC_DEBUGPL(DBG_ANY, "Backed up hfir           = %08x\n",
+		    hr->hfir_local);
+
+	return 0;
+}
+
+int dwc_otg_restore_global_regs(dwc_otg_core_if_t *core_if)
+{
+	struct dwc_otg_global_regs_backup *gr;
+	int i;
+
+	gr = core_if->gr_backup;
+	if (!gr) {
+		return -DWC_E_INVALID;
+	}
+
+	DWC_WRITE_REG32(&core_if->core_global_regs->gotgctl, gr->gotgctl_local);
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, gr->gintmsk_local);
+	DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, gr->gusbcfg_local);
+	DWC_WRITE_REG32(&core_if->core_global_regs->gahbcfg, gr->gahbcfg_local);
+	DWC_WRITE_REG32(&core_if->core_global_regs->grxfsiz, gr->grxfsiz_local);
+	DWC_WRITE_REG32(&core_if->core_global_regs->gnptxfsiz,
+			gr->gnptxfsiz_local);
+	DWC_WRITE_REG32(&core_if->core_global_regs->hptxfsiz,
+			gr->hptxfsiz_local);
+	DWC_WRITE_REG32(&core_if->core_global_regs->gdfifocfg,
+			gr->gdfifocfg_local);
+	for (i = 0; i < MAX_EPS_CHANNELS; i++) {
+		DWC_WRITE_REG32(&core_if->core_global_regs->dtxfsiz[i],
+				gr->dtxfsiz_local[i]);
+	}
+
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF);
+	DWC_WRITE_REG32(core_if->host_if->hprt0, 0x0000100A);
+	DWC_WRITE_REG32(&core_if->core_global_regs->gahbcfg,
+			(gr->gahbcfg_local));
+	return 0;
+}
+
+int dwc_otg_restore_dev_regs(dwc_otg_core_if_t * core_if, int rem_wakeup)
+{
+	struct dwc_otg_dev_regs_backup *dr;
+	int i;
+
+	dr = core_if->dr_backup;
+
+	if (!dr) {
+		return -DWC_E_INVALID;
+	}
+
+	if (!rem_wakeup) {
+		DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl,
+				dr->dctl);
+	}
+
+	DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->daintmsk, dr->daintmsk);
+	DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->diepmsk, dr->diepmsk);
+	DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->doepmsk, dr->doepmsk);
+
+	for (i = 0; i < core_if->dev_if->num_in_eps; ++i) {
+		DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[i]->dieptsiz, dr->dieptsiz[i]);
+		DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[i]->diepdma, dr->diepdma[i]);
+		DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[i]->diepctl, dr->diepctl[i]);
+	}
+
+	return 0;
+}
+
+int dwc_otg_restore_host_regs(dwc_otg_core_if_t * core_if, int reset)
+{
+	struct dwc_otg_host_regs_backup *hr;
+	int i;
+	hr = core_if->hr_backup;
+
+	if (!hr) {
+		return -DWC_E_INVALID;
+	}
+
+	DWC_WRITE_REG32(&core_if->host_if->host_global_regs->hcfg, hr->hcfg_local);
+	//if (!reset)
+	//{
+	//      DWC_WRITE_REG32(&core_if->host_if->host_global_regs->hfir, hr->hfir_local);
+	//}
+
+	DWC_WRITE_REG32(&core_if->host_if->host_global_regs->haintmsk,
+			hr->haintmsk_local);
+	for (i = 0; i < dwc_otg_get_param_host_channels(core_if); ++i) {
+		DWC_WRITE_REG32(&core_if->host_if->hc_regs[i]->hcintmsk,
+				hr->hcintmsk_local[i]);
+	}
+
+	return 0;
+}
+
+int restore_lpm_i2c_regs(dwc_otg_core_if_t * core_if)
+{
+	struct dwc_otg_global_regs_backup *gr;
+
+	gr = core_if->gr_backup;
+
+	/* Restore values for LPM and I2C */
+#ifdef CONFIG_USB_DWC_OTG_LPM
+	DWC_WRITE_REG32(&core_if->core_global_regs->glpmcfg, gr->glpmcfg_local);
+#endif
+	DWC_WRITE_REG32(&core_if->core_global_regs->gi2cctl, gr->gi2cctl_local);
+
+	return 0;
+}
+
+int restore_essential_regs(dwc_otg_core_if_t * core_if, int rmode, int is_host)
+{
+	struct dwc_otg_global_regs_backup *gr;
+	pcgcctl_data_t pcgcctl = {.d32 = 0 };
+	gahbcfg_data_t gahbcfg = {.d32 = 0 };
+	gusbcfg_data_t gusbcfg = {.d32 = 0 };
+	gintmsk_data_t gintmsk = {.d32 = 0 };
+
+	/* Restore LPM and I2C registers */
+	restore_lpm_i2c_regs(core_if);
+
+	/* Set PCGCCTL to 0 */
+	DWC_WRITE_REG32(core_if->pcgcctl, 0x00000000);
+
+	gr = core_if->gr_backup;
+	/* Load restore values for [31:14] bits */
+	DWC_WRITE_REG32(core_if->pcgcctl,
+			((gr->pcgcctl_local & 0xffffc000) | 0x00020000));
+
+	/* Umnask global Interrupt in GAHBCFG and restore it */
+	gahbcfg.d32 = gr->gahbcfg_local;
+	gahbcfg.b.glblintrmsk = 1;
+	DWC_WRITE_REG32(&core_if->core_global_regs->gahbcfg, gahbcfg.d32);
+
+	/* Clear all pending interupts */
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF);
+
+	/* Unmask restore done interrupt */
+	gintmsk.b.restoredone = 1;
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, gintmsk.d32);
+
+	/* Restore GUSBCFG and HCFG/DCFG */
+	gusbcfg.d32 = core_if->gr_backup->gusbcfg_local;
+	DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, gusbcfg.d32);
+
+	if (is_host) {
+		hcfg_data_t hcfg = {.d32 = 0 };
+		hcfg.d32 = core_if->hr_backup->hcfg_local;
+		DWC_WRITE_REG32(&core_if->host_if->host_global_regs->hcfg,
+				hcfg.d32);
+
+		/* Load restore values for [31:14] bits */
+		pcgcctl.d32 = gr->pcgcctl_local & 0xffffc000;
+		pcgcctl.d32 = gr->pcgcctl_local | 0x00020000;
+
+		if (rmode)
+			pcgcctl.b.restoremode = 1;
+		DWC_WRITE_REG32(core_if->pcgcctl, pcgcctl.d32);
+		dwc_udelay(10);
+
+		/* Load restore values for [31:14] bits and set EssRegRestored bit */
+		pcgcctl.d32 = gr->pcgcctl_local | 0xffffc000;
+		pcgcctl.d32 = gr->pcgcctl_local & 0xffffc000;
+		pcgcctl.b.ess_reg_restored = 1;
+		if (rmode)
+			pcgcctl.b.restoremode = 1;
+		DWC_WRITE_REG32(core_if->pcgcctl, pcgcctl.d32);
+	} else {
+		dcfg_data_t dcfg = {.d32 = 0 };
+		dcfg.d32 = core_if->dr_backup->dcfg;
+		DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dcfg, dcfg.d32);
+
+		/* Load restore values for [31:14] bits */
+		pcgcctl.d32 = gr->pcgcctl_local & 0xffffc000;
+		pcgcctl.d32 = gr->pcgcctl_local | 0x00020000;
+		if (!rmode) {
+			pcgcctl.d32 |= 0x208;
+		}
+		DWC_WRITE_REG32(core_if->pcgcctl, pcgcctl.d32);
+		dwc_udelay(10);
+
+		/* Load restore values for [31:14] bits */
+		pcgcctl.d32 = gr->pcgcctl_local & 0xffffc000;
+		pcgcctl.d32 = gr->pcgcctl_local | 0x00020000;
+		pcgcctl.b.ess_reg_restored = 1;
+		if (!rmode)
+			pcgcctl.d32 |= 0x208;
+		DWC_WRITE_REG32(core_if->pcgcctl, pcgcctl.d32);
+	}
+
+	return 0;
+}
+
+/**
+ * Initializes the FSLSPClkSel field of the HCFG register depending on the PHY
+ * type.
+ */
+static void init_fslspclksel(dwc_otg_core_if_t * core_if)
+{
+	uint32_t val;
+	hcfg_data_t hcfg;
+
+	if (((core_if->hwcfg2.b.hs_phy_type == 2) &&
+	     (core_if->hwcfg2.b.fs_phy_type == 1) &&
+	     (core_if->core_params->ulpi_fs_ls)) ||
+	    (core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS)) {
+		/* Full speed PHY */
+		val = DWC_HCFG_48_MHZ;
+	} else {
+		/* High speed PHY running at full speed or high speed */
+		val = DWC_HCFG_30_60_MHZ;
+	}
+
+	DWC_DEBUGPL(DBG_CIL, "Initializing HCFG.FSLSPClkSel to 0x%1x\n", val);
+	hcfg.d32 = DWC_READ_REG32(&core_if->host_if->host_global_regs->hcfg);
+	hcfg.b.fslspclksel = val;
+	DWC_WRITE_REG32(&core_if->host_if->host_global_regs->hcfg, hcfg.d32);
+}
+
+/**
+ * Initializes the DevSpd field of the DCFG register depending on the PHY type
+ * and the enumeration speed of the device.
+ */
+static void init_devspd(dwc_otg_core_if_t * core_if)
+{
+	uint32_t val;
+	dcfg_data_t dcfg;
+
+	if (((core_if->hwcfg2.b.hs_phy_type == 2) &&
+	     (core_if->hwcfg2.b.fs_phy_type == 1) &&
+	     (core_if->core_params->ulpi_fs_ls)) ||
+	    (core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS)) {
+		/* Full speed PHY */
+		val = 0x3;
+	} else if (core_if->core_params->speed == DWC_SPEED_PARAM_FULL) {
+		/* High speed PHY running at full speed */
+		val = 0x1;
+	} else {
+		/* High speed PHY running at high speed */
+		val = 0x0;
+	}
+
+	DWC_DEBUGPL(DBG_CIL, "Initializing DCFG.DevSpd to 0x%1x\n", val);
+
+	dcfg.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg);
+	dcfg.b.devspd = val;
+	DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dcfg, dcfg.d32);
+}
+
+/**
+ * This function calculates the number of IN EPS
+ * using GHWCFG1 and GHWCFG2 registers values
+ *
+ * @param core_if Programming view of the DWC_otg controller
+ */
+static uint32_t calc_num_in_eps(dwc_otg_core_if_t * core_if)
+{
+	uint32_t num_in_eps = 0;
+	uint32_t num_eps = core_if->hwcfg2.b.num_dev_ep;
+	uint32_t hwcfg1 = core_if->hwcfg1.d32 >> 3;
+	uint32_t num_tx_fifos = core_if->hwcfg4.b.num_in_eps;
+	int i;
+
+	for (i = 0; i < num_eps; ++i) {
+		if (!(hwcfg1 & 0x1))
+			num_in_eps++;
+
+		hwcfg1 >>= 2;
+	}
+
+	if (core_if->hwcfg4.b.ded_fifo_en) {
+		num_in_eps =
+		    (num_in_eps > num_tx_fifos) ? num_tx_fifos : num_in_eps;
+	}
+
+	return num_in_eps;
+}
+
+/**
+ * This function calculates the number of OUT EPS
+ * using GHWCFG1 and GHWCFG2 registers values
+ *
+ * @param core_if Programming view of the DWC_otg controller
+ */
+static uint32_t calc_num_out_eps(dwc_otg_core_if_t * core_if)
+{
+	uint32_t num_out_eps = 0;
+	uint32_t num_eps = core_if->hwcfg2.b.num_dev_ep;
+	uint32_t hwcfg1 = core_if->hwcfg1.d32 >> 2;
+	int i;
+
+	for (i = 0; i < num_eps; ++i) {
+		if (!(hwcfg1 & 0x1))
+			num_out_eps++;
+
+		hwcfg1 >>= 2;
+	}
+	return num_out_eps;
+}
+
+/**
+ * This function initializes the DWC_otg controller registers and
+ * prepares the core for device mode or host mode operation.
+ *
+ * @param core_if Programming view of the DWC_otg controller
+ *
+ */
+void dwc_otg_core_init(dwc_otg_core_if_t * core_if)
+{
+	int i = 0;
+	dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs;
+	dwc_otg_dev_if_t *dev_if = core_if->dev_if;
+	gahbcfg_data_t ahbcfg = {.d32 = 0 };
+	gusbcfg_data_t usbcfg = {.d32 = 0 };
+	gi2cctl_data_t i2cctl = {.d32 = 0 };
+
+	DWC_DEBUGPL(DBG_CILV, "dwc_otg_core_init(%p) regs at %p\n",
+                    core_if, global_regs);
+
+	/* Common Initialization */
+	usbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg);
+
+	/* Program the ULPI External VBUS bit if needed */
+	usbcfg.b.ulpi_ext_vbus_drv =
+	    (core_if->core_params->phy_ulpi_ext_vbus ==
+	     DWC_PHY_ULPI_EXTERNAL_VBUS) ? 1 : 0;
+
+	/* Set external TS Dline pulsing */
+	usbcfg.b.term_sel_dl_pulse =
+	    (core_if->core_params->ts_dline == 1) ? 1 : 0;
+	DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32);
+
+	/* Reset the Controller */
+	dwc_otg_core_reset(core_if);
+
+	core_if->adp_enable = core_if->core_params->adp_supp_enable;
+	core_if->power_down = core_if->core_params->power_down;
+	core_if->otg_sts = 0;
+
+	/* Initialize parameters from Hardware configuration registers. */
+	dev_if->num_in_eps = calc_num_in_eps(core_if);
+	dev_if->num_out_eps = calc_num_out_eps(core_if);
+
+	DWC_DEBUGPL(DBG_CIL, "num_dev_perio_in_ep=%d\n",
+		    core_if->hwcfg4.b.num_dev_perio_in_ep);
+
+	for (i = 0; i < core_if->hwcfg4.b.num_dev_perio_in_ep; i++) {
+		dev_if->perio_tx_fifo_size[i] =
+		    DWC_READ_REG32(&global_regs->dtxfsiz[i]) >> 16;
+		DWC_DEBUGPL(DBG_CIL, "Periodic Tx FIFO SZ #%d=0x%0x\n",
+			    i, dev_if->perio_tx_fifo_size[i]);
+	}
+
+	for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) {
+		dev_if->tx_fifo_size[i] =
+		    DWC_READ_REG32(&global_regs->dtxfsiz[i]) >> 16;
+		DWC_DEBUGPL(DBG_CIL, "Tx FIFO SZ #%d=0x%0x\n",
+			    i, dev_if->tx_fifo_size[i]);
+	}
+
+	core_if->total_fifo_size = core_if->hwcfg3.b.dfifo_depth;
+	core_if->rx_fifo_size = DWC_READ_REG32(&global_regs->grxfsiz);
+	core_if->nperio_tx_fifo_size =
+	    DWC_READ_REG32(&global_regs->gnptxfsiz) >> 16;
+
+	DWC_DEBUGPL(DBG_CIL, "Total FIFO SZ=%d\n", core_if->total_fifo_size);
+	DWC_DEBUGPL(DBG_CIL, "Rx FIFO SZ=%d\n", core_if->rx_fifo_size);
+	DWC_DEBUGPL(DBG_CIL, "NP Tx FIFO SZ=%d\n",
+		    core_if->nperio_tx_fifo_size);
+
+	/* This programming sequence needs to happen in FS mode before any other
+	 * programming occurs */
+	if ((core_if->core_params->speed == DWC_SPEED_PARAM_FULL) &&
+	    (core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS)) {
+		/* If FS mode with FS PHY */
+
+		/* core_init() is now called on every switch so only call the
+		 * following for the first time through. */
+		if (!core_if->phy_init_done) {
+			core_if->phy_init_done = 1;
+			DWC_DEBUGPL(DBG_CIL, "FS_PHY detected\n");
+			usbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg);
+			usbcfg.b.physel = 1;
+			DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32);
+
+			/* Reset after a PHY select */
+			dwc_otg_core_reset(core_if);
+		}
+
+		/* Program DCFG.DevSpd or HCFG.FSLSPclkSel to 48Mhz in FS.      Also
+		 * do this on HNP Dev/Host mode switches (done in dev_init and
+		 * host_init). */
+		if (dwc_otg_is_host_mode(core_if)) {
+			init_fslspclksel(core_if);
+		} else {
+			init_devspd(core_if);
+		}
+
+		if (core_if->core_params->i2c_enable) {
+			DWC_DEBUGPL(DBG_CIL, "FS_PHY Enabling I2c\n");
+			/* Program GUSBCFG.OtgUtmifsSel to I2C */
+			usbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg);
+			usbcfg.b.otgutmifssel = 1;
+			DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32);
+
+			/* Program GI2CCTL.I2CEn */
+			i2cctl.d32 = DWC_READ_REG32(&global_regs->gi2cctl);
+			i2cctl.b.i2cdevaddr = 1;
+			i2cctl.b.i2cen = 0;
+			DWC_WRITE_REG32(&global_regs->gi2cctl, i2cctl.d32);
+			i2cctl.b.i2cen = 1;
+			DWC_WRITE_REG32(&global_regs->gi2cctl, i2cctl.d32);
+		}
+
+	} /* endif speed == DWC_SPEED_PARAM_FULL */
+	else {
+		/* High speed PHY. */
+		if (!core_if->phy_init_done) {
+			core_if->phy_init_done = 1;
+			/* HS PHY parameters.  These parameters are preserved
+			 * during soft reset so only program the first time.  Do
+			 * a soft reset immediately after setting phyif.  */
+
+			if (core_if->core_params->phy_type == 2) {
+				/* ULPI interface */
+				usbcfg.b.ulpi_utmi_sel = 1;
+				usbcfg.b.phyif = 0;
+				usbcfg.b.ddrsel =
+				    core_if->core_params->phy_ulpi_ddr;
+			} else if (core_if->core_params->phy_type == 1) {
+				/* UTMI+ interface */
+				usbcfg.b.ulpi_utmi_sel = 0;
+				if (core_if->core_params->phy_utmi_width == 16) {
+					usbcfg.b.phyif = 1;
+
+				} else {
+					usbcfg.b.phyif = 0;
+				}
+			} else {
+				DWC_ERROR("FS PHY TYPE\n");
+			}
+			DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32);
+			/* Reset after setting the PHY parameters */
+			dwc_otg_core_reset(core_if);
+		}
+	}
+
+	if ((core_if->hwcfg2.b.hs_phy_type == 2) &&
+	    (core_if->hwcfg2.b.fs_phy_type == 1) &&
+	    (core_if->core_params->ulpi_fs_ls)) {
+		DWC_DEBUGPL(DBG_CIL, "Setting ULPI FSLS\n");
+		usbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg);
+		usbcfg.b.ulpi_fsls = 1;
+		usbcfg.b.ulpi_clk_sus_m = 1;
+		DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32);
+	} else {
+		usbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg);
+		usbcfg.b.ulpi_fsls = 0;
+		usbcfg.b.ulpi_clk_sus_m = 0;
+		DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32);
+	}
+
+	/* Program the GAHBCFG Register. */
+	switch (core_if->hwcfg2.b.architecture) {
+
+	case DWC_SLAVE_ONLY_ARCH:
+		DWC_DEBUGPL(DBG_CIL, "Slave Only Mode\n");
+		ahbcfg.b.nptxfemplvl_txfemplvl =
+		    DWC_GAHBCFG_TXFEMPTYLVL_HALFEMPTY;
+		ahbcfg.b.ptxfemplvl = DWC_GAHBCFG_TXFEMPTYLVL_HALFEMPTY;
+		core_if->dma_enable = 0;
+		core_if->dma_desc_enable = 0;
+		break;
+
+	case DWC_EXT_DMA_ARCH:
+		DWC_DEBUGPL(DBG_CIL, "External DMA Mode\n");
+		{
+			uint8_t brst_sz = core_if->core_params->dma_burst_size;
+			ahbcfg.b.hburstlen = 0;
+			while (brst_sz > 1) {
+				ahbcfg.b.hburstlen++;
+				brst_sz >>= 1;
+			}
+		}
+		core_if->dma_enable = (core_if->core_params->dma_enable != 0);
+		core_if->dma_desc_enable =
+		    (core_if->core_params->dma_desc_enable != 0);
+		break;
+
+	case DWC_INT_DMA_ARCH:
+		DWC_DEBUGPL(DBG_CIL, "Internal DMA Mode\n");
+		/* Old value was DWC_GAHBCFG_INT_DMA_BURST_INCR - done for
+		  Host mode ISOC in issue fix - vahrama */
+		/* Broadcom had altered to (1<<3)|(0<<0) - WRESP=1, max 4 beats */
+		ahbcfg.b.hburstlen = (1<<3)|(0<<0);//DWC_GAHBCFG_INT_DMA_BURST_INCR4;
+		core_if->dma_enable = (core_if->core_params->dma_enable != 0);
+		core_if->dma_desc_enable =
+		    (core_if->core_params->dma_desc_enable != 0);
+		break;
+
+	}
+	if (core_if->dma_enable) {
+		if (core_if->dma_desc_enable) {
+			DWC_PRINTF("Using Descriptor DMA mode\n");
+		} else {
+			DWC_PRINTF("Using Buffer DMA mode\n");
+
+		}
+	} else {
+		DWC_PRINTF("Using Slave mode\n");
+		core_if->dma_desc_enable = 0;
+	}
+
+	if (core_if->core_params->ahb_single) {
+		ahbcfg.b.ahbsingle = 1;
+	}
+
+	ahbcfg.b.dmaenable = core_if->dma_enable;
+	DWC_WRITE_REG32(&global_regs->gahbcfg, ahbcfg.d32);
+
+	core_if->en_multiple_tx_fifo = core_if->hwcfg4.b.ded_fifo_en;
+
+	core_if->pti_enh_enable = core_if->core_params->pti_enable != 0;
+	core_if->multiproc_int_enable = core_if->core_params->mpi_enable;
+	DWC_PRINTF("Periodic Transfer Interrupt Enhancement - %s\n",
+		   ((core_if->pti_enh_enable) ? "enabled" : "disabled"));
+	DWC_PRINTF("Multiprocessor Interrupt Enhancement - %s\n",
+		   ((core_if->multiproc_int_enable) ? "enabled" : "disabled"));
+
+	/*
+	 * Program the GUSBCFG register.
+	 */
+	usbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg);
+
+	switch (core_if->hwcfg2.b.op_mode) {
+	case DWC_MODE_HNP_SRP_CAPABLE:
+		usbcfg.b.hnpcap = (core_if->core_params->otg_cap ==
+				   DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE);
+		usbcfg.b.srpcap = (core_if->core_params->otg_cap !=
+				   DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE);
+		break;
+
+	case DWC_MODE_SRP_ONLY_CAPABLE:
+		usbcfg.b.hnpcap = 0;
+		usbcfg.b.srpcap = (core_if->core_params->otg_cap !=
+				   DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE);
+		break;
+
+	case DWC_MODE_NO_HNP_SRP_CAPABLE:
+		usbcfg.b.hnpcap = 0;
+		usbcfg.b.srpcap = 0;
+		break;
+
+	case DWC_MODE_SRP_CAPABLE_DEVICE:
+		usbcfg.b.hnpcap = 0;
+		usbcfg.b.srpcap = (core_if->core_params->otg_cap !=
+				   DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE);
+		break;
+
+	case DWC_MODE_NO_SRP_CAPABLE_DEVICE:
+		usbcfg.b.hnpcap = 0;
+		usbcfg.b.srpcap = 0;
+		break;
+
+	case DWC_MODE_SRP_CAPABLE_HOST:
+		usbcfg.b.hnpcap = 0;
+		usbcfg.b.srpcap = (core_if->core_params->otg_cap !=
+				   DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE);
+		break;
+
+	case DWC_MODE_NO_SRP_CAPABLE_HOST:
+		usbcfg.b.hnpcap = 0;
+		usbcfg.b.srpcap = 0;
+		break;
+	}
+
+	DWC_WRITE_REG32(&global_regs->gusbcfg, usbcfg.d32);
+
+#ifdef CONFIG_USB_DWC_OTG_LPM
+	if (core_if->core_params->lpm_enable) {
+		glpmcfg_data_t lpmcfg = {.d32 = 0 };
+
+		/* To enable LPM support set lpm_cap_en bit */
+		lpmcfg.b.lpm_cap_en = 1;
+
+		/* Make AppL1Res ACK */
+		lpmcfg.b.appl_resp = 1;
+
+		/* Retry 3 times */
+		lpmcfg.b.retry_count = 3;
+
+		DWC_MODIFY_REG32(&core_if->core_global_regs->glpmcfg,
+				 0, lpmcfg.d32);
+
+	}
+#endif
+	if (core_if->core_params->ic_usb_cap) {
+		gusbcfg_data_t gusbcfg = {.d32 = 0 };
+		gusbcfg.b.ic_usb_cap = 1;
+		DWC_MODIFY_REG32(&core_if->core_global_regs->gusbcfg,
+				 0, gusbcfg.d32);
+	}
+	{
+		gotgctl_data_t gotgctl = {.d32 = 0 };
+		gotgctl.b.otgver = core_if->core_params->otg_ver;
+		DWC_MODIFY_REG32(&core_if->core_global_regs->gotgctl, 0,
+				 gotgctl.d32);
+		/* Set OTG version supported */
+		core_if->otg_ver = core_if->core_params->otg_ver;
+		DWC_PRINTF("OTG VER PARAM: %d, OTG VER FLAG: %d\n",
+			   core_if->core_params->otg_ver, core_if->otg_ver);
+	}
+
+
+	/* Enable common interrupts */
+	dwc_otg_enable_common_interrupts(core_if);
+
+	/* Do device or host intialization based on mode during PCD
+	 * and HCD initialization  */
+	if (dwc_otg_is_host_mode(core_if)) {
+		DWC_DEBUGPL(DBG_ANY, "Host Mode\n");
+		core_if->op_state = A_HOST;
+	} else {
+		DWC_DEBUGPL(DBG_ANY, "Device Mode\n");
+		core_if->op_state = B_PERIPHERAL;
+#ifdef DWC_DEVICE_ONLY
+		dwc_otg_core_dev_init(core_if);
+#endif
+	}
+}
+
+/**
+ * This function enables the Device mode interrupts.
+ *
+ * @param core_if Programming view of DWC_otg controller
+ */
+void dwc_otg_enable_device_interrupts(dwc_otg_core_if_t * core_if)
+{
+	gintmsk_data_t intr_mask = {.d32 = 0 };
+	dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs;
+
+	DWC_DEBUGPL(DBG_CIL, "%s()\n", __func__);
+
+	/* Disable all interrupts. */
+	DWC_WRITE_REG32(&global_regs->gintmsk, 0);
+
+	/* Clear any pending interrupts */
+	DWC_WRITE_REG32(&global_regs->gintsts, 0xFFFFFFFF);
+
+	/* Enable the common interrupts */
+	dwc_otg_enable_common_interrupts(core_if);
+
+	/* Enable interrupts */
+	intr_mask.b.usbreset = 1;
+	intr_mask.b.enumdone = 1;
+	/* Disable Disconnect interrupt in Device mode */
+	intr_mask.b.disconnect = 0;
+
+	if (!core_if->multiproc_int_enable) {
+		intr_mask.b.inepintr = 1;
+		intr_mask.b.outepintr = 1;
+	}
+
+	intr_mask.b.erlysuspend = 1;
+
+	if (core_if->en_multiple_tx_fifo == 0) {
+		intr_mask.b.epmismatch = 1;
+	}
+
+	//intr_mask.b.incomplisoout = 1;
+	intr_mask.b.incomplisoin = 1;
+
+/* Enable the ignore frame number for ISOC xfers - MAS */
+/* Disable to support high bandwith ISOC transfers - manukz */
+#if 0
+#ifdef DWC_UTE_PER_IO
+	if (core_if->dma_enable) {
+		if (core_if->dma_desc_enable) {
+			dctl_data_t dctl1 = {.d32 = 0 };
+			dctl1.b.ifrmnum = 1;
+			DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->
+					 dctl, 0, dctl1.d32);
+			DWC_DEBUG("----Enabled Ignore frame number (0x%08x)",
+				  DWC_READ_REG32(&core_if->dev_if->
+						 dev_global_regs->dctl));
+		}
+	}
+#endif
+#endif
+#ifdef DWC_EN_ISOC
+	if (core_if->dma_enable) {
+		if (core_if->dma_desc_enable == 0) {
+			if (core_if->pti_enh_enable) {
+				dctl_data_t dctl = {.d32 = 0 };
+				dctl.b.ifrmnum = 1;
+				DWC_MODIFY_REG32(&core_if->
+						 dev_if->dev_global_regs->dctl,
+						 0, dctl.d32);
+			} else {
+				intr_mask.b.incomplisoin = 1;
+				intr_mask.b.incomplisoout = 1;
+			}
+		}
+	} else {
+		intr_mask.b.incomplisoin = 1;
+		intr_mask.b.incomplisoout = 1;
+	}
+#endif /* DWC_EN_ISOC */
+
+	/** @todo NGS: Should this be a module parameter? */
+#ifdef USE_PERIODIC_EP
+	intr_mask.b.isooutdrop = 1;
+	intr_mask.b.eopframe = 1;
+	intr_mask.b.incomplisoin = 1;
+	intr_mask.b.incomplisoout = 1;
+#endif
+
+	DWC_MODIFY_REG32(&global_regs->gintmsk, intr_mask.d32, intr_mask.d32);
+
+	DWC_DEBUGPL(DBG_CIL, "%s() gintmsk=%0x\n", __func__,
+		    DWC_READ_REG32(&global_regs->gintmsk));
+}
+
+/**
+ * This function initializes the DWC_otg controller registers for
+ * device mode.
+ *
+ * @param core_if Programming view of DWC_otg controller
+ *
+ */
+void dwc_otg_core_dev_init(dwc_otg_core_if_t * core_if)
+{
+	int i;
+	dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs;
+	dwc_otg_dev_if_t *dev_if = core_if->dev_if;
+	dwc_otg_core_params_t *params = core_if->core_params;
+	dcfg_data_t dcfg = {.d32 = 0 };
+	depctl_data_t diepctl = {.d32 = 0 };
+	grstctl_t resetctl = {.d32 = 0 };
+	uint32_t rx_fifo_size;
+	fifosize_data_t nptxfifosize;
+	fifosize_data_t txfifosize;
+	dthrctl_data_t dthrctl;
+	fifosize_data_t ptxfifosize;
+	uint16_t rxfsiz, nptxfsiz;
+	gdfifocfg_data_t gdfifocfg = {.d32 = 0 };
+	hwcfg3_data_t hwcfg3 = {.d32 = 0 };
+
+	/* Restart the Phy Clock */
+	DWC_WRITE_REG32(core_if->pcgcctl, 0);
+
+	/* Device configuration register */
+	init_devspd(core_if);
+	dcfg.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->dcfg);
+	dcfg.b.descdma = (core_if->dma_desc_enable) ? 1 : 0;
+	dcfg.b.perfrint = DWC_DCFG_FRAME_INTERVAL_80;
+	/* Enable Device OUT NAK in case of DDMA mode*/
+	if (core_if->core_params->dev_out_nak) {
+		dcfg.b.endevoutnak = 1;
+	}
+
+	if (core_if->core_params->cont_on_bna) {
+		dctl_data_t dctl = {.d32 = 0 };
+		dctl.b.encontonbna = 1;
+		DWC_MODIFY_REG32(&dev_if->dev_global_regs->dctl, 0, dctl.d32);
+	}
+
+
+	DWC_WRITE_REG32(&dev_if->dev_global_regs->dcfg, dcfg.d32);
+
+	/* Configure data FIFO sizes */
+	if (core_if->hwcfg2.b.dynamic_fifo && params->enable_dynamic_fifo) {
+		DWC_DEBUGPL(DBG_CIL, "Total FIFO Size=%d\n",
+			    core_if->total_fifo_size);
+		DWC_DEBUGPL(DBG_CIL, "Rx FIFO Size=%d\n",
+			    params->dev_rx_fifo_size);
+		DWC_DEBUGPL(DBG_CIL, "NP Tx FIFO Size=%d\n",
+			    params->dev_nperio_tx_fifo_size);
+
+		/* Rx FIFO */
+		DWC_DEBUGPL(DBG_CIL, "initial grxfsiz=%08x\n",
+			    DWC_READ_REG32(&global_regs->grxfsiz));
+
+#ifdef DWC_UTE_CFI
+		core_if->pwron_rxfsiz = DWC_READ_REG32(&global_regs->grxfsiz);
+		core_if->init_rxfsiz = params->dev_rx_fifo_size;
+#endif
+		rx_fifo_size = params->dev_rx_fifo_size;
+		DWC_WRITE_REG32(&global_regs->grxfsiz, rx_fifo_size);
+
+		DWC_DEBUGPL(DBG_CIL, "new grxfsiz=%08x\n",
+			    DWC_READ_REG32(&global_regs->grxfsiz));
+
+		/** Set Periodic Tx FIFO Mask all bits 0 */
+		core_if->p_tx_msk = 0;
+
+		/** Set Tx FIFO Mask all bits 0 */
+		core_if->tx_msk = 0;
+
+		if (core_if->en_multiple_tx_fifo == 0) {
+			/* Non-periodic Tx FIFO */
+			DWC_DEBUGPL(DBG_CIL, "initial gnptxfsiz=%08x\n",
+				    DWC_READ_REG32(&global_regs->gnptxfsiz));
+
+			nptxfifosize.b.depth = params->dev_nperio_tx_fifo_size;
+			nptxfifosize.b.startaddr = params->dev_rx_fifo_size;
+
+			DWC_WRITE_REG32(&global_regs->gnptxfsiz,
+					nptxfifosize.d32);
+
+			DWC_DEBUGPL(DBG_CIL, "new gnptxfsiz=%08x\n",
+				    DWC_READ_REG32(&global_regs->gnptxfsiz));
+
+			/**@todo NGS: Fix Periodic FIFO Sizing! */
+			/*
+			 * Periodic Tx FIFOs These FIFOs are numbered from 1 to 15.
+			 * Indexes of the FIFO size module parameters in the
+			 * dev_perio_tx_fifo_size array and the FIFO size registers in
+			 * the dptxfsiz array run from 0 to 14.
+			 */
+			/** @todo Finish debug of this */
+			ptxfifosize.b.startaddr =
+			    nptxfifosize.b.startaddr + nptxfifosize.b.depth;
+			for (i = 0; i < core_if->hwcfg4.b.num_dev_perio_in_ep; i++) {
+				ptxfifosize.b.depth =
+				    params->dev_perio_tx_fifo_size[i];
+				DWC_DEBUGPL(DBG_CIL,
+					    "initial dtxfsiz[%d]=%08x\n", i,
+					    DWC_READ_REG32(&global_regs->dtxfsiz
+							   [i]));
+				DWC_WRITE_REG32(&global_regs->dtxfsiz[i],
+						ptxfifosize.d32);
+				DWC_DEBUGPL(DBG_CIL, "new dtxfsiz[%d]=%08x\n",
+					    i,
+					    DWC_READ_REG32(&global_regs->dtxfsiz
+							   [i]));
+				ptxfifosize.b.startaddr += ptxfifosize.b.depth;
+			}
+		} else {
+			/*
+			 * Tx FIFOs These FIFOs are numbered from 1 to 15.
+			 * Indexes of the FIFO size module parameters in the
+			 * dev_tx_fifo_size array and the FIFO size registers in
+			 * the dtxfsiz array run from 0 to 14.
+			 */
+
+			/* Non-periodic Tx FIFO */
+			DWC_DEBUGPL(DBG_CIL, "initial gnptxfsiz=%08x\n",
+				    DWC_READ_REG32(&global_regs->gnptxfsiz));
+
+#ifdef DWC_UTE_CFI
+			core_if->pwron_gnptxfsiz =
+			    (DWC_READ_REG32(&global_regs->gnptxfsiz) >> 16);
+			core_if->init_gnptxfsiz =
+			    params->dev_nperio_tx_fifo_size;
+#endif
+			nptxfifosize.b.depth = params->dev_nperio_tx_fifo_size;
+			nptxfifosize.b.startaddr = params->dev_rx_fifo_size;
+
+			DWC_WRITE_REG32(&global_regs->gnptxfsiz,
+					nptxfifosize.d32);
+
+			DWC_DEBUGPL(DBG_CIL, "new gnptxfsiz=%08x\n",
+				    DWC_READ_REG32(&global_regs->gnptxfsiz));
+
+			txfifosize.b.startaddr =
+			    nptxfifosize.b.startaddr + nptxfifosize.b.depth;
+
+			for (i = 0; i < core_if->hwcfg4.b.num_in_eps; i++) {
+
+				txfifosize.b.depth =
+				    params->dev_tx_fifo_size[i];
+
+				DWC_DEBUGPL(DBG_CIL,
+					    "initial dtxfsiz[%d]=%08x\n",
+					    i,
+					    DWC_READ_REG32(&global_regs->dtxfsiz
+							   [i]));
+
+#ifdef DWC_UTE_CFI
+				core_if->pwron_txfsiz[i] =
+				    (DWC_READ_REG32
+				     (&global_regs->dtxfsiz[i]) >> 16);
+				core_if->init_txfsiz[i] =
+				    params->dev_tx_fifo_size[i];
+#endif
+				DWC_WRITE_REG32(&global_regs->dtxfsiz[i],
+						txfifosize.d32);
+
+				DWC_DEBUGPL(DBG_CIL,
+					    "new dtxfsiz[%d]=%08x\n",
+					    i,
+					    DWC_READ_REG32(&global_regs->dtxfsiz
+							   [i]));
+
+				txfifosize.b.startaddr += txfifosize.b.depth;
+			}
+			if (core_if->snpsid <= OTG_CORE_REV_2_94a) {
+				/* Calculating DFIFOCFG for Device mode to include RxFIFO and NPTXFIFO */
+				gdfifocfg.d32 = DWC_READ_REG32(&global_regs->gdfifocfg);
+				hwcfg3.d32 = DWC_READ_REG32(&global_regs->ghwcfg3);
+				gdfifocfg.b.gdfifocfg = (DWC_READ_REG32(&global_regs->ghwcfg3) >> 16);
+				DWC_WRITE_REG32(&global_regs->gdfifocfg, gdfifocfg.d32);
+				rxfsiz = (DWC_READ_REG32(&global_regs->grxfsiz) & 0x0000ffff);
+				nptxfsiz = (DWC_READ_REG32(&global_regs->gnptxfsiz) >> 16);
+				gdfifocfg.b.epinfobase = rxfsiz + nptxfsiz;
+				DWC_WRITE_REG32(&global_regs->gdfifocfg, gdfifocfg.d32);
+			}
+		}
+
+		/* Flush the FIFOs */
+		dwc_otg_flush_tx_fifo(core_if, 0x10);	/* all Tx FIFOs */
+		dwc_otg_flush_rx_fifo(core_if);
+
+		/* Flush the Learning Queue. */
+		resetctl.b.intknqflsh = 1;
+		DWC_WRITE_REG32(&core_if->core_global_regs->grstctl, resetctl.d32);
+
+		if (!core_if->core_params->en_multiple_tx_fifo && core_if->dma_enable) {
+			core_if->start_predict = 0;
+			for (i = 0; i<= core_if->dev_if->num_in_eps; ++i) {
+				core_if->nextep_seq[i] = 0xff;	// 0xff - EP not active
+			}
+			core_if->nextep_seq[0] = 0;
+			core_if->first_in_nextep_seq = 0;
+			diepctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[0]->diepctl);
+			diepctl.b.nextep = 0;
+			DWC_WRITE_REG32(&dev_if->in_ep_regs[0]->diepctl, diepctl.d32);
+
+			/* Update IN Endpoint Mismatch Count by active IN NP EP count + 1 */
+			dcfg.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->dcfg);
+			dcfg.b.epmscnt = 2;
+			DWC_WRITE_REG32(&dev_if->dev_global_regs->dcfg, dcfg.d32);
+
+			DWC_DEBUGPL(DBG_CILV,"%s first_in_nextep_seq= %2d; nextep_seq[]:\n",
+				__func__, core_if->first_in_nextep_seq);
+			for (i=0; i <= core_if->dev_if->num_in_eps; i++) {
+				DWC_DEBUGPL(DBG_CILV, "%2d ", core_if->nextep_seq[i]);
+			}
+			DWC_DEBUGPL(DBG_CILV,"\n");
+		}
+
+		/* Clear all pending Device Interrupts */
+		/** @todo - if the condition needed to be checked
+		 *  or in any case all pending interrutps should be cleared?
+	     */
+		if (core_if->multiproc_int_enable) {
+			for (i = 0; i < core_if->dev_if->num_in_eps; ++i) {
+				DWC_WRITE_REG32(&dev_if->
+						dev_global_regs->diepeachintmsk[i], 0);
+			}
+		}
+
+		for (i = 0; i < core_if->dev_if->num_out_eps; ++i) {
+			DWC_WRITE_REG32(&dev_if->
+					dev_global_regs->doepeachintmsk[i], 0);
+		}
+
+		DWC_WRITE_REG32(&dev_if->dev_global_regs->deachint, 0xFFFFFFFF);
+		DWC_WRITE_REG32(&dev_if->dev_global_regs->deachintmsk, 0);
+	} else {
+		DWC_WRITE_REG32(&dev_if->dev_global_regs->diepmsk, 0);
+		DWC_WRITE_REG32(&dev_if->dev_global_regs->doepmsk, 0);
+		DWC_WRITE_REG32(&dev_if->dev_global_regs->daint, 0xFFFFFFFF);
+		DWC_WRITE_REG32(&dev_if->dev_global_regs->daintmsk, 0);
+	}
+
+	for (i = 0; i <= dev_if->num_in_eps; i++) {
+		depctl_data_t depctl;
+		depctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl);
+		if (depctl.b.epena) {
+			depctl.d32 = 0;
+			depctl.b.epdis = 1;
+			depctl.b.snak = 1;
+		} else {
+			depctl.d32 = 0;
+		}
+
+		DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->diepctl, depctl.d32);
+
+		DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->dieptsiz, 0);
+		DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->diepdma, 0);
+		DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->diepint, 0xFF);
+	}
+
+	for (i = 0; i <= dev_if->num_out_eps; i++) {
+		depctl_data_t depctl;
+		depctl.d32 = DWC_READ_REG32(&dev_if->out_ep_regs[i]->doepctl);
+		if (depctl.b.epena) {
+			dctl_data_t dctl = {.d32 = 0 };
+			gintmsk_data_t gintsts = {.d32 = 0 };
+			doepint_data_t doepint = {.d32 = 0 };
+			dctl.b.sgoutnak = 1;
+			DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, 0, dctl.d32);
+			do {
+				dwc_udelay(10);
+				gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts);
+			} while (!gintsts.b.goutnakeff);
+			gintsts.d32 = 0;
+			gintsts.b.goutnakeff = 1;
+			DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32);
+
+			depctl.d32 = 0;
+			depctl.b.epdis = 1;
+			depctl.b.snak = 1;
+			DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[i]->doepctl, depctl.d32);
+			do {
+				dwc_udelay(10);
+				doepint.d32 = DWC_READ_REG32(&core_if->dev_if->
+					out_ep_regs[i]->doepint);
+			} while (!doepint.b.epdisabled);
+
+			doepint.b.epdisabled = 1;
+			DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[i]->doepint, doepint.d32);
+
+			dctl.d32 = 0;
+			dctl.b.cgoutnak = 1;
+			DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, 0, dctl.d32);
+		} else {
+			depctl.d32 = 0;
+		}
+
+		DWC_WRITE_REG32(&dev_if->out_ep_regs[i]->doepctl, depctl.d32);
+
+		DWC_WRITE_REG32(&dev_if->out_ep_regs[i]->doeptsiz, 0);
+		DWC_WRITE_REG32(&dev_if->out_ep_regs[i]->doepdma, 0);
+		DWC_WRITE_REG32(&dev_if->out_ep_regs[i]->doepint, 0xFF);
+	}
+
+	if (core_if->en_multiple_tx_fifo && core_if->dma_enable) {
+		dev_if->non_iso_tx_thr_en = params->thr_ctl & 0x1;
+		dev_if->iso_tx_thr_en = (params->thr_ctl >> 1) & 0x1;
+		dev_if->rx_thr_en = (params->thr_ctl >> 2) & 0x1;
+
+		dev_if->rx_thr_length = params->rx_thr_length;
+		dev_if->tx_thr_length = params->tx_thr_length;
+
+		dev_if->setup_desc_index = 0;
+
+		dthrctl.d32 = 0;
+		dthrctl.b.non_iso_thr_en = dev_if->non_iso_tx_thr_en;
+		dthrctl.b.iso_thr_en = dev_if->iso_tx_thr_en;
+		dthrctl.b.tx_thr_len = dev_if->tx_thr_length;
+		dthrctl.b.rx_thr_en = dev_if->rx_thr_en;
+		dthrctl.b.rx_thr_len = dev_if->rx_thr_length;
+		dthrctl.b.ahb_thr_ratio = params->ahb_thr_ratio;
+
+		DWC_WRITE_REG32(&dev_if->dev_global_regs->dtknqr3_dthrctl,
+				dthrctl.d32);
+
+		DWC_DEBUGPL(DBG_CIL,
+			    "Non ISO Tx Thr - %d\nISO Tx Thr - %d\nRx Thr - %d\nTx Thr Len - %d\nRx Thr Len - %d\n",
+			    dthrctl.b.non_iso_thr_en, dthrctl.b.iso_thr_en,
+			    dthrctl.b.rx_thr_en, dthrctl.b.tx_thr_len,
+			    dthrctl.b.rx_thr_len);
+
+	}
+
+	dwc_otg_enable_device_interrupts(core_if);
+
+	{
+		diepmsk_data_t msk = {.d32 = 0 };
+		msk.b.txfifoundrn = 1;
+		if (core_if->multiproc_int_enable) {
+			DWC_MODIFY_REG32(&dev_if->dev_global_regs->
+					 diepeachintmsk[0], msk.d32, msk.d32);
+		} else {
+			DWC_MODIFY_REG32(&dev_if->dev_global_regs->diepmsk,
+					 msk.d32, msk.d32);
+		}
+	}
+
+	if (core_if->multiproc_int_enable) {
+		/* Set NAK on Babble */
+		dctl_data_t dctl = {.d32 = 0 };
+		dctl.b.nakonbble = 1;
+		DWC_MODIFY_REG32(&dev_if->dev_global_regs->dctl, 0, dctl.d32);
+	}
+
+	if (core_if->snpsid >= OTG_CORE_REV_2_94a) {
+		dctl_data_t dctl = {.d32 = 0 };
+		dctl.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->dctl);
+		dctl.b.sftdiscon = 0;
+		DWC_WRITE_REG32(&dev_if->dev_global_regs->dctl, dctl.d32);
+	}
+}
+
+/**
+ * This function enables the Host mode interrupts.
+ *
+ * @param core_if Programming view of DWC_otg controller
+ */
+void dwc_otg_enable_host_interrupts(dwc_otg_core_if_t * core_if)
+{
+	dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs;
+	gintmsk_data_t intr_mask = {.d32 = 0 };
+
+	DWC_DEBUGPL(DBG_CIL, "%s(%p)\n", __func__, core_if);
+
+	/* Disable all interrupts. */
+	DWC_WRITE_REG32(&global_regs->gintmsk, 0);
+
+	/* Clear any pending interrupts. */
+	DWC_WRITE_REG32(&global_regs->gintsts, 0xFFFFFFFF);
+
+	/* Enable the common interrupts */
+	dwc_otg_enable_common_interrupts(core_if);
+
+	/*
+	 * Enable host mode interrupts without disturbing common
+	 * interrupts.
+	 */
+
+	intr_mask.b.disconnect = 1;
+	intr_mask.b.portintr = 1;
+	intr_mask.b.hcintr = 1;
+
+	DWC_MODIFY_REG32(&global_regs->gintmsk, intr_mask.d32, intr_mask.d32);
+}
+
+/**
+ * This function disables the Host Mode interrupts.
+ *
+ * @param core_if Programming view of DWC_otg controller
+ */
+void dwc_otg_disable_host_interrupts(dwc_otg_core_if_t * core_if)
+{
+	dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs;
+	gintmsk_data_t intr_mask = {.d32 = 0 };
+
+	DWC_DEBUGPL(DBG_CILV, "%s()\n", __func__);
+
+	/*
+	 * Disable host mode interrupts without disturbing common
+	 * interrupts.
+	 */
+	intr_mask.b.sofintr = 1;
+	intr_mask.b.portintr = 1;
+	intr_mask.b.hcintr = 1;
+	intr_mask.b.ptxfempty = 1;
+	intr_mask.b.nptxfempty = 1;
+
+	DWC_MODIFY_REG32(&global_regs->gintmsk, intr_mask.d32, 0);
+}
+
+/**
+ * This function initializes the DWC_otg controller registers for
+ * host mode.
+ *
+ * This function flushes the Tx and Rx FIFOs and it flushes any entries in the
+ * request queues. Host channels are reset to ensure that they are ready for
+ * performing transfers.
+ *
+ * @param core_if Programming view of DWC_otg controller
+ *
+ */
+void dwc_otg_core_host_init(dwc_otg_core_if_t * core_if)
+{
+	dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs;
+	dwc_otg_host_if_t *host_if = core_if->host_if;
+	dwc_otg_core_params_t *params = core_if->core_params;
+	hprt0_data_t hprt0 = {.d32 = 0 };
+	fifosize_data_t nptxfifosize;
+	fifosize_data_t ptxfifosize;
+	uint16_t rxfsiz, nptxfsiz, hptxfsiz;
+	gdfifocfg_data_t gdfifocfg = {.d32 = 0 };
+	int i;
+	hcchar_data_t hcchar;
+	hcfg_data_t hcfg;
+	hfir_data_t hfir;
+	dwc_otg_hc_regs_t *hc_regs;
+	int num_channels;
+	gotgctl_data_t gotgctl = {.d32 = 0 };
+
+	DWC_DEBUGPL(DBG_CILV, "%s(%p)\n", __func__, core_if);
+
+	/* Restart the Phy Clock */
+	DWC_WRITE_REG32(core_if->pcgcctl, 0);
+
+	/* Initialize Host Configuration Register */
+	init_fslspclksel(core_if);
+	if (core_if->core_params->speed == DWC_SPEED_PARAM_FULL) {
+		hcfg.d32 = DWC_READ_REG32(&host_if->host_global_regs->hcfg);
+		hcfg.b.fslssupp = 1;
+		DWC_WRITE_REG32(&host_if->host_global_regs->hcfg, hcfg.d32);
+
+	}
+
+	/* This bit allows dynamic reloading of the HFIR register
+	 * during runtime. This bit needs to be programmed during
+	 * initial configuration and its value must not be changed
+	 * during runtime.*/
+	if (core_if->core_params->reload_ctl == 1) {
+		hfir.d32 = DWC_READ_REG32(&host_if->host_global_regs->hfir);
+		hfir.b.hfirrldctrl = 1;
+		DWC_WRITE_REG32(&host_if->host_global_regs->hfir, hfir.d32);
+	}
+
+	if (core_if->core_params->dma_desc_enable) {
+		uint8_t op_mode = core_if->hwcfg2.b.op_mode;
+		if (!
+		    (core_if->hwcfg4.b.desc_dma
+		     && (core_if->snpsid >= OTG_CORE_REV_2_90a)
+		     && ((op_mode == DWC_HWCFG2_OP_MODE_HNP_SRP_CAPABLE_OTG)
+			 || (op_mode == DWC_HWCFG2_OP_MODE_SRP_ONLY_CAPABLE_OTG)
+			 || (op_mode ==
+			     DWC_HWCFG2_OP_MODE_NO_HNP_SRP_CAPABLE_OTG)
+			 || (op_mode == DWC_HWCFG2_OP_MODE_SRP_CAPABLE_HOST)
+			 || (op_mode ==
+			     DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST)))) {
+
+			DWC_ERROR("Host can't operate in Descriptor DMA mode.\n"
+				  "Either core version is below 2.90a or "
+				  "GHWCFG2, GHWCFG4 registers' values do not allow Descriptor DMA in host mode.\n"
+				  "To run the driver in Buffer DMA host mode set dma_desc_enable "
+				  "module parameter to 0.\n");
+			return;
+		}
+		hcfg.d32 = DWC_READ_REG32(&host_if->host_global_regs->hcfg);
+		hcfg.b.descdma = 1;
+		DWC_WRITE_REG32(&host_if->host_global_regs->hcfg, hcfg.d32);
+	}
+
+	/* Configure data FIFO sizes */
+	if (core_if->hwcfg2.b.dynamic_fifo && params->enable_dynamic_fifo) {
+		DWC_DEBUGPL(DBG_CIL, "Total FIFO Size=%d\n",
+			    core_if->total_fifo_size);
+		DWC_DEBUGPL(DBG_CIL, "Rx FIFO Size=%d\n",
+			    params->host_rx_fifo_size);
+		DWC_DEBUGPL(DBG_CIL, "NP Tx FIFO Size=%d\n",
+			    params->host_nperio_tx_fifo_size);
+		DWC_DEBUGPL(DBG_CIL, "P Tx FIFO Size=%d\n",
+			    params->host_perio_tx_fifo_size);
+
+		/* Rx FIFO */
+		DWC_DEBUGPL(DBG_CIL, "initial grxfsiz=%08x\n",
+			    DWC_READ_REG32(&global_regs->grxfsiz));
+		DWC_WRITE_REG32(&global_regs->grxfsiz,
+				params->host_rx_fifo_size);
+		DWC_DEBUGPL(DBG_CIL, "new grxfsiz=%08x\n",
+			    DWC_READ_REG32(&global_regs->grxfsiz));
+
+		/* Non-periodic Tx FIFO */
+		DWC_DEBUGPL(DBG_CIL, "initial gnptxfsiz=%08x\n",
+			    DWC_READ_REG32(&global_regs->gnptxfsiz));
+		nptxfifosize.b.depth = params->host_nperio_tx_fifo_size;
+		nptxfifosize.b.startaddr = params->host_rx_fifo_size;
+		DWC_WRITE_REG32(&global_regs->gnptxfsiz, nptxfifosize.d32);
+		DWC_DEBUGPL(DBG_CIL, "new gnptxfsiz=%08x\n",
+			    DWC_READ_REG32(&global_regs->gnptxfsiz));
+
+		/* Periodic Tx FIFO */
+		DWC_DEBUGPL(DBG_CIL, "initial hptxfsiz=%08x\n",
+			    DWC_READ_REG32(&global_regs->hptxfsiz));
+		ptxfifosize.b.depth = params->host_perio_tx_fifo_size;
+		ptxfifosize.b.startaddr =
+		    nptxfifosize.b.startaddr + nptxfifosize.b.depth;
+		DWC_WRITE_REG32(&global_regs->hptxfsiz, ptxfifosize.d32);
+		DWC_DEBUGPL(DBG_CIL, "new hptxfsiz=%08x\n",
+			    DWC_READ_REG32(&global_regs->hptxfsiz));
+
+		if (core_if->en_multiple_tx_fifo
+		    && core_if->snpsid <= OTG_CORE_REV_2_94a) {
+			/* Global DFIFOCFG calculation for Host mode - include RxFIFO, NPTXFIFO and HPTXFIFO */
+			gdfifocfg.d32 = DWC_READ_REG32(&global_regs->gdfifocfg);
+			rxfsiz = (DWC_READ_REG32(&global_regs->grxfsiz) & 0x0000ffff);
+			nptxfsiz = (DWC_READ_REG32(&global_regs->gnptxfsiz) >> 16);
+			hptxfsiz = (DWC_READ_REG32(&global_regs->hptxfsiz) >> 16);
+			gdfifocfg.b.epinfobase = rxfsiz + nptxfsiz + hptxfsiz;
+			DWC_WRITE_REG32(&global_regs->gdfifocfg, gdfifocfg.d32);
+		}
+	}
+
+	/* TODO - check this */
+	/* Clear Host Set HNP Enable in the OTG Control Register */
+	gotgctl.b.hstsethnpen = 1;
+	DWC_MODIFY_REG32(&global_regs->gotgctl, gotgctl.d32, 0);
+	/* Make sure the FIFOs are flushed. */
+	dwc_otg_flush_tx_fifo(core_if, 0x10 /* all TX FIFOs */ );
+	dwc_otg_flush_rx_fifo(core_if);
+
+	/* Clear Host Set HNP Enable in the OTG Control Register */
+	gotgctl.b.hstsethnpen = 1;
+	DWC_MODIFY_REG32(&global_regs->gotgctl, gotgctl.d32, 0);
+
+	if (!core_if->core_params->dma_desc_enable) {
+		/* Flush out any leftover queued requests. */
+		num_channels = core_if->core_params->host_channels;
+
+		for (i = 0; i < num_channels; i++) {
+			hc_regs = core_if->host_if->hc_regs[i];
+			hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+			hcchar.b.chen = 0;
+			hcchar.b.chdis = 1;
+			hcchar.b.epdir = 0;
+			DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32);
+		}
+
+		/* Halt all channels to put them into a known state. */
+		for (i = 0; i < num_channels; i++) {
+			int count = 0;
+			hc_regs = core_if->host_if->hc_regs[i];
+			hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+			hcchar.b.chen = 1;
+			hcchar.b.chdis = 1;
+			hcchar.b.epdir = 0;
+			DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32);
+			DWC_DEBUGPL(DBG_HCDV, "%s: Halt channel %d regs %p\n", __func__, i, hc_regs);
+			do {
+				hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+				if (++count > 1000) {
+					DWC_ERROR
+					    ("%s: Unable to clear halt on channel %d (timeout HCCHAR 0x%X @%p)\n",
+					     __func__, i, hcchar.d32, &hc_regs->hcchar);
+					break;
+				}
+				dwc_udelay(1);
+			} while (hcchar.b.chen);
+		}
+	}
+
+	/* Turn on the vbus power. */
+	DWC_PRINTF("Init: Port Power? op_state=%d\n", core_if->op_state);
+	if (core_if->op_state == A_HOST) {
+		hprt0.d32 = dwc_otg_read_hprt0(core_if);
+		DWC_PRINTF("Init: Power Port (%d)\n", hprt0.b.prtpwr);
+		if (hprt0.b.prtpwr == 0) {
+			hprt0.b.prtpwr = 1;
+			DWC_WRITE_REG32(host_if->hprt0, hprt0.d32);
+		}
+	}
+
+	dwc_otg_enable_host_interrupts(core_if);
+}
+
+/**
+ * Prepares a host channel for transferring packets to/from a specific
+ * endpoint. The HCCHARn register is set up with the characteristics specified
+ * in _hc. Host channel interrupts that may need to be serviced while this
+ * transfer is in progress are enabled.
+ *
+ * @param core_if Programming view of DWC_otg controller
+ * @param hc Information needed to initialize the host channel
+ */
+void dwc_otg_hc_init(dwc_otg_core_if_t * core_if, dwc_hc_t * hc)
+{
+	hcintmsk_data_t hc_intr_mask;
+	hcchar_data_t hcchar;
+	hcsplt_data_t hcsplt;
+
+	uint8_t hc_num = hc->hc_num;
+	dwc_otg_host_if_t *host_if = core_if->host_if;
+	dwc_otg_hc_regs_t *hc_regs = host_if->hc_regs[hc_num];
+
+	/* Clear old interrupt conditions for this host channel. */
+	hc_intr_mask.d32 = 0xFFFFFFFF;
+	hc_intr_mask.b.reserved14_31 = 0;
+	DWC_WRITE_REG32(&hc_regs->hcint, hc_intr_mask.d32);
+
+	/* Enable channel interrupts required for this transfer. */
+	hc_intr_mask.d32 = 0;
+	hc_intr_mask.b.chhltd = 1;
+	if (core_if->dma_enable) {
+		/* For Descriptor DMA mode core halts the channel on AHB error. Interrupt is not required */
+		if (!core_if->dma_desc_enable)
+			hc_intr_mask.b.ahberr = 1;
+		else {
+			if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC)
+				hc_intr_mask.b.xfercompl = 1;
+		}
+
+		if (hc->error_state && !hc->do_split &&
+		    hc->ep_type != DWC_OTG_EP_TYPE_ISOC) {
+			hc_intr_mask.b.ack = 1;
+			if (hc->ep_is_in) {
+				hc_intr_mask.b.datatglerr = 1;
+				if (hc->ep_type != DWC_OTG_EP_TYPE_INTR) {
+					hc_intr_mask.b.nak = 1;
+				}
+			}
+		}
+	} else {
+		switch (hc->ep_type) {
+		case DWC_OTG_EP_TYPE_CONTROL:
+		case DWC_OTG_EP_TYPE_BULK:
+			hc_intr_mask.b.xfercompl = 1;
+			hc_intr_mask.b.stall = 1;
+			hc_intr_mask.b.xacterr = 1;
+			hc_intr_mask.b.datatglerr = 1;
+			if (hc->ep_is_in) {
+				hc_intr_mask.b.bblerr = 1;
+			} else {
+				hc_intr_mask.b.nak = 1;
+				hc_intr_mask.b.nyet = 1;
+				if (hc->do_ping) {
+					hc_intr_mask.b.ack = 1;
+				}
+			}
+
+			if (hc->do_split) {
+				hc_intr_mask.b.nak = 1;
+				if (hc->complete_split) {
+					hc_intr_mask.b.nyet = 1;
+				} else {
+					hc_intr_mask.b.ack = 1;
+				}
+			}
+
+			if (hc->error_state) {
+				hc_intr_mask.b.ack = 1;
+			}
+			break;
+		case DWC_OTG_EP_TYPE_INTR:
+			hc_intr_mask.b.xfercompl = 1;
+			hc_intr_mask.b.nak = 1;
+			hc_intr_mask.b.stall = 1;
+			hc_intr_mask.b.xacterr = 1;
+			hc_intr_mask.b.datatglerr = 1;
+			hc_intr_mask.b.frmovrun = 1;
+
+			if (hc->ep_is_in) {
+				hc_intr_mask.b.bblerr = 1;
+			}
+			if (hc->error_state) {
+				hc_intr_mask.b.ack = 1;
+			}
+			if (hc->do_split) {
+				if (hc->complete_split) {
+					hc_intr_mask.b.nyet = 1;
+				} else {
+					hc_intr_mask.b.ack = 1;
+				}
+			}
+			break;
+		case DWC_OTG_EP_TYPE_ISOC:
+			hc_intr_mask.b.xfercompl = 1;
+			hc_intr_mask.b.frmovrun = 1;
+			hc_intr_mask.b.ack = 1;
+
+			if (hc->ep_is_in) {
+				hc_intr_mask.b.xacterr = 1;
+				hc_intr_mask.b.bblerr = 1;
+			}
+			break;
+		}
+	}
+	DWC_WRITE_REG32(&hc_regs->hcintmsk, hc_intr_mask.d32);
+
+	/*
+	 * Program the HCCHARn register with the endpoint characteristics for
+	 * the current transfer.
+	 */
+	hcchar.d32 = 0;
+	hcchar.b.devaddr = hc->dev_addr;
+	hcchar.b.epnum = hc->ep_num;
+	hcchar.b.epdir = hc->ep_is_in;
+	hcchar.b.lspddev = (hc->speed == DWC_OTG_EP_SPEED_LOW);
+	hcchar.b.eptype = hc->ep_type;
+	hcchar.b.mps = hc->max_packet;
+
+	DWC_WRITE_REG32(&host_if->hc_regs[hc_num]->hcchar, hcchar.d32);
+
+	DWC_DEBUGPL(DBG_HCDV, "%s: Channel %d, Dev Addr %d, EP #%d\n",
+                    __func__, hc->hc_num, hcchar.b.devaddr, hcchar.b.epnum);
+	DWC_DEBUGPL(DBG_HCDV, "	 Is In %d, Is Low Speed %d, EP Type %d, "
+                                "Max Pkt %d, Multi Cnt %d\n",
+                    hcchar.b.epdir, hcchar.b.lspddev, hcchar.b.eptype,
+                    hcchar.b.mps, hcchar.b.multicnt);
+
+	/*
+	 * Program the HCSPLIT register for SPLITs
+	 */
+	hcsplt.d32 = 0;
+	if (hc->do_split) {
+		DWC_DEBUGPL(DBG_HCDV, "Programming HC %d with split --> %s\n",
+			    hc->hc_num,
+			    hc->complete_split ? "CSPLIT" : "SSPLIT");
+		hcsplt.b.compsplt = hc->complete_split;
+		hcsplt.b.xactpos = hc->xact_pos;
+		hcsplt.b.hubaddr = hc->hub_addr;
+		hcsplt.b.prtaddr = hc->port_addr;
+		DWC_DEBUGPL(DBG_HCDV, "\t  comp split %d\n", hc->complete_split);
+		DWC_DEBUGPL(DBG_HCDV, "\t  xact pos %d\n", hc->xact_pos);
+		DWC_DEBUGPL(DBG_HCDV, "\t  hub addr %d\n", hc->hub_addr);
+		DWC_DEBUGPL(DBG_HCDV, "\t  port addr %d\n", hc->port_addr);
+		DWC_DEBUGPL(DBG_HCDV, "\t  is_in %d\n", hc->ep_is_in);
+		DWC_DEBUGPL(DBG_HCDV, "\t  Max Pkt: %d\n", hcchar.b.mps);
+		DWC_DEBUGPL(DBG_HCDV, "\t  xferlen: %d\n", hc->xfer_len);
+	}
+	DWC_WRITE_REG32(&host_if->hc_regs[hc_num]->hcsplt, hcsplt.d32);
+
+}
+
+/**
+ * Attempts to halt a host channel. This function should only be called in
+ * Slave mode or to abort a transfer in either Slave mode or DMA mode. Under
+ * normal circumstances in DMA mode, the controller halts the channel when the
+ * transfer is complete or a condition occurs that requires application
+ * intervention.
+ *
+ * In slave mode, checks for a free request queue entry, then sets the Channel
+ * Enable and Channel Disable bits of the Host Channel Characteristics
+ * register of the specified channel to intiate the halt. If there is no free
+ * request queue entry, sets only the Channel Disable bit of the HCCHARn
+ * register to flush requests for this channel. In the latter case, sets a
+ * flag to indicate that the host channel needs to be halted when a request
+ * queue slot is open.
+ *
+ * In DMA mode, always sets the Channel Enable and Channel Disable bits of the
+ * HCCHARn register. The controller ensures there is space in the request
+ * queue before submitting the halt request.
+ *
+ * Some time may elapse before the core flushes any posted requests for this
+ * host channel and halts. The Channel Halted interrupt handler completes the
+ * deactivation of the host channel.
+ *
+ * @param core_if Controller register interface.
+ * @param hc Host channel to halt.
+ * @param halt_status Reason for halting the channel.
+ */
+void dwc_otg_hc_halt(dwc_otg_core_if_t * core_if,
+		     dwc_hc_t * hc, dwc_otg_halt_status_e halt_status)
+{
+	gnptxsts_data_t nptxsts;
+	hptxsts_data_t hptxsts;
+	hcchar_data_t hcchar;
+	dwc_otg_hc_regs_t *hc_regs;
+	dwc_otg_core_global_regs_t *global_regs;
+	dwc_otg_host_global_regs_t *host_global_regs;
+
+	hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+	global_regs = core_if->core_global_regs;
+	host_global_regs = core_if->host_if->host_global_regs;
+
+	DWC_ASSERT(!(halt_status == DWC_OTG_HC_XFER_NO_HALT_STATUS),
+		   "halt_status = %d\n", halt_status);
+
+	if (halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE ||
+	    halt_status == DWC_OTG_HC_XFER_AHB_ERR) {
+		/*
+		 * Disable all channel interrupts except Ch Halted. The QTD
+		 * and QH state associated with this transfer has been cleared
+		 * (in the case of URB_DEQUEUE), so the channel needs to be
+		 * shut down carefully to prevent crashes.
+		 */
+		hcintmsk_data_t hcintmsk;
+		hcintmsk.d32 = 0;
+		hcintmsk.b.chhltd = 1;
+		DWC_WRITE_REG32(&hc_regs->hcintmsk, hcintmsk.d32);
+
+		/*
+		 * Make sure no other interrupts besides halt are currently
+		 * pending. Handling another interrupt could cause a crash due
+		 * to the QTD and QH state.
+		 */
+		DWC_WRITE_REG32(&hc_regs->hcint, ~hcintmsk.d32);
+
+		/*
+		 * Make sure the halt status is set to URB_DEQUEUE or AHB_ERR
+		 * even if the channel was already halted for some other
+		 * reason.
+		 */
+		hc->halt_status = halt_status;
+
+		hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+		if (hcchar.b.chen == 0) {
+			/*
+			 * The channel is either already halted or it hasn't
+			 * started yet. In DMA mode, the transfer may halt if
+			 * it finishes normally or a condition occurs that
+			 * requires driver intervention. Don't want to halt
+			 * the channel again. In either Slave or DMA mode,
+			 * it's possible that the transfer has been assigned
+			 * to a channel, but not started yet when an URB is
+			 * dequeued. Don't want to halt a channel that hasn't
+			 * started yet.
+			 */
+			return;
+		}
+	}
+	if (hc->halt_pending) {
+		/*
+		 * A halt has already been issued for this channel. This might
+		 * happen when a transfer is aborted by a higher level in
+		 * the stack.
+		 */
+#ifdef DEBUG
+		DWC_PRINTF
+		    ("*** %s: Channel %d, _hc->halt_pending already set ***\n",
+		     __func__, hc->hc_num);
+
+#endif
+		return;
+	}
+
+	hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+
+	/* No need to set the bit in DDMA for disabling the channel */
+	//TODO check it everywhere channel is disabled
+	if (!core_if->core_params->dma_desc_enable)
+		hcchar.b.chen = 1;
+	hcchar.b.chdis = 1;
+
+	if (!core_if->dma_enable) {
+		/* Check for space in the request queue to issue the halt. */
+		if (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL ||
+		    hc->ep_type == DWC_OTG_EP_TYPE_BULK) {
+			nptxsts.d32 = DWC_READ_REG32(&global_regs->gnptxsts);
+			if (nptxsts.b.nptxqspcavail == 0) {
+				hcchar.b.chen = 0;
+			}
+		} else {
+			hptxsts.d32 =
+			    DWC_READ_REG32(&host_global_regs->hptxsts);
+			if ((hptxsts.b.ptxqspcavail == 0)
+			    || (core_if->queuing_high_bandwidth)) {
+				hcchar.b.chen = 0;
+			}
+		}
+	}
+	DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32);
+
+	hc->halt_status = halt_status;
+
+	if (hcchar.b.chen) {
+		hc->halt_pending = 1;
+		hc->halt_on_queue = 0;
+	} else {
+		hc->halt_on_queue = 1;
+	}
+
+	DWC_DEBUGPL(DBG_HCDV, "%s: Channel %d\n", __func__, hc->hc_num);
+	DWC_DEBUGPL(DBG_HCDV, "	 hcchar: 0x%08x\n", hcchar.d32);
+	DWC_DEBUGPL(DBG_HCDV, "	 halt_pending: %d\n", hc->halt_pending);
+	DWC_DEBUGPL(DBG_HCDV, "	 halt_on_queue: %d\n", hc->halt_on_queue);
+	DWC_DEBUGPL(DBG_HCDV, "	 halt_status: %d\n", hc->halt_status);
+
+	return;
+}
+
+/**
+ * Clears the transfer state for a host channel. This function is normally
+ * called after a transfer is done and the host channel is being released.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param hc Identifies the host channel to clean up.
+ */
+void dwc_otg_hc_cleanup(dwc_otg_core_if_t * core_if, dwc_hc_t * hc)
+{
+	dwc_otg_hc_regs_t *hc_regs;
+
+	hc->xfer_started = 0;
+
+	/*
+	 * Clear channel interrupt enables and any unhandled channel interrupt
+	 * conditions.
+	 */
+	hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+	DWC_WRITE_REG32(&hc_regs->hcintmsk, 0);
+	DWC_WRITE_REG32(&hc_regs->hcint, 0xFFFFFFFF);
+#ifdef DEBUG
+	DWC_TIMER_CANCEL(core_if->hc_xfer_timer[hc->hc_num]);
+#endif
+}
+
+/**
+ * Sets the channel property that indicates in which frame a periodic transfer
+ * should occur. This is always set to the _next_ frame. This function has no
+ * effect on non-periodic transfers.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param hc Identifies the host channel to set up and its properties.
+ * @param hcchar Current value of the HCCHAR register for the specified host
+ * channel.
+ */
+static inline void hc_set_even_odd_frame(dwc_otg_core_if_t * core_if,
+					 dwc_hc_t * hc, hcchar_data_t * hcchar)
+{
+	if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+	    hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+		hfnum_data_t hfnum;
+		hfnum.d32 =
+		    DWC_READ_REG32(&core_if->host_if->host_global_regs->hfnum);
+
+		/* 1 if _next_ frame is odd, 0 if it's even */
+		hcchar->b.oddfrm = (hfnum.b.frnum & 0x1) ? 0 : 1;
+#ifdef DEBUG
+		if (hc->ep_type == DWC_OTG_EP_TYPE_INTR && hc->do_split
+		    && !hc->complete_split) {
+			switch (hfnum.b.frnum & 0x7) {
+			case 7:
+				core_if->hfnum_7_samples++;
+				core_if->hfnum_7_frrem_accum += hfnum.b.frrem;
+				break;
+			case 0:
+				core_if->hfnum_0_samples++;
+				core_if->hfnum_0_frrem_accum += hfnum.b.frrem;
+				break;
+			default:
+				core_if->hfnum_other_samples++;
+				core_if->hfnum_other_frrem_accum +=
+				    hfnum.b.frrem;
+				break;
+			}
+		}
+#endif
+	}
+}
+
+#ifdef DEBUG
+void hc_xfer_timeout(void *ptr)
+{
+	hc_xfer_info_t *xfer_info = NULL;
+	int hc_num = 0;
+
+	if (ptr)
+		xfer_info = (hc_xfer_info_t *) ptr;
+
+	if (!xfer_info->hc) {
+		DWC_ERROR("xfer_info->hc = %p\n", xfer_info->hc);
+		return;
+	}
+
+	hc_num = xfer_info->hc->hc_num;
+	DWC_WARN("%s: timeout on channel %d\n", __func__, hc_num);
+	DWC_WARN("	start_hcchar_val 0x%08x\n",
+		 xfer_info->core_if->start_hcchar_val[hc_num]);
+}
+#endif
+
+void ep_xfer_timeout(void *ptr)
+{
+	ep_xfer_info_t *xfer_info = NULL;
+	int ep_num = 0;
+	dctl_data_t dctl = {.d32 = 0 };
+	gintsts_data_t gintsts = {.d32 = 0 };
+	gintmsk_data_t gintmsk = {.d32 = 0 };
+
+	if (ptr)
+		xfer_info = (ep_xfer_info_t *) ptr;
+
+	if (!xfer_info->ep) {
+		DWC_ERROR("xfer_info->ep = %p\n", xfer_info->ep);
+		return;
+	}
+
+	ep_num = xfer_info->ep->num;
+	DWC_WARN("%s: timeout on endpoit %d\n", __func__, ep_num);
+	/* Put the sate to 2 as it was time outed */
+	xfer_info->state = 2;
+
+	dctl.d32 =
+	    DWC_READ_REG32(&xfer_info->core_if->dev_if->dev_global_regs->dctl);
+	gintsts.d32 =
+	    DWC_READ_REG32(&xfer_info->core_if->core_global_regs->gintsts);
+	gintmsk.d32 =
+	    DWC_READ_REG32(&xfer_info->core_if->core_global_regs->gintmsk);
+
+	if (!gintmsk.b.goutnakeff) {
+		/* Unmask it */
+		gintmsk.b.goutnakeff = 1;
+		DWC_WRITE_REG32(&xfer_info->core_if->core_global_regs->gintmsk,
+				gintmsk.d32);
+
+	}
+
+	if (!gintsts.b.goutnakeff) {
+		dctl.b.sgoutnak = 1;
+	}
+	DWC_WRITE_REG32(&xfer_info->core_if->dev_if->dev_global_regs->dctl,
+			dctl.d32);
+
+}
+
+static void set_pid_isoc(dwc_hc_t * hc)
+{
+	/* Set up the initial PID for the transfer. */
+	if (hc->speed == DWC_OTG_EP_SPEED_HIGH) {
+		if (hc->ep_is_in) {
+			if (hc->multi_count == 1) {
+				hc->data_pid_start = DWC_OTG_HC_PID_DATA0;
+			} else if (hc->multi_count == 2) {
+				hc->data_pid_start = DWC_OTG_HC_PID_DATA1;
+			} else {
+				hc->data_pid_start = DWC_OTG_HC_PID_DATA2;
+			}
+		} else {
+			if (hc->multi_count == 1) {
+				hc->data_pid_start = DWC_OTG_HC_PID_DATA0;
+			} else {
+				hc->data_pid_start = DWC_OTG_HC_PID_MDATA;
+			}
+		}
+	} else {
+		hc->data_pid_start = DWC_OTG_HC_PID_DATA0;
+	}
+}
+
+/**
+ * This function does the setup for a data transfer for a host channel and
+ * starts the transfer. May be called in either Slave mode or DMA mode. In
+ * Slave mode, the caller must ensure that there is sufficient space in the
+ * request queue and Tx Data FIFO.
+ *
+ * For an OUT transfer in Slave mode, it loads a data packet into the
+ * appropriate FIFO. If necessary, additional data packets will be loaded in
+ * the Host ISR.
+ *
+ * For an IN transfer in Slave mode, a data packet is requested. The data
+ * packets are unloaded from the Rx FIFO in the Host ISR. If necessary,
+ * additional data packets are requested in the Host ISR.
+ *
+ * For a PING transfer in Slave mode, the Do Ping bit is set in the HCTSIZ
+ * register along with a packet count of 1 and the channel is enabled. This
+ * causes a single PING transaction to occur. Other fields in HCTSIZ are
+ * simply set to 0 since no data transfer occurs in this case.
+ *
+ * For a PING transfer in DMA mode, the HCTSIZ register is initialized with
+ * all the information required to perform the subsequent data transfer. In
+ * addition, the Do Ping bit is set in the HCTSIZ register. In this case, the
+ * controller performs the entire PING protocol, then starts the data
+ * transfer.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param hc Information needed to initialize the host channel. The xfer_len
+ * value may be reduced to accommodate the max widths of the XferSize and
+ * PktCnt fields in the HCTSIZn register. The multi_count value may be changed
+ * to reflect the final xfer_len value.
+ */
+void dwc_otg_hc_start_transfer(dwc_otg_core_if_t * core_if, dwc_hc_t * hc)
+{
+	hcchar_data_t hcchar;
+	hctsiz_data_t hctsiz;
+	uint16_t num_packets;
+	uint32_t max_hc_xfer_size = core_if->core_params->max_transfer_size;
+	uint16_t max_hc_pkt_count = core_if->core_params->max_packet_count;
+	dwc_otg_hc_regs_t *hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+
+	hctsiz.d32 = 0;
+
+	if (hc->do_ping) {
+		if (!core_if->dma_enable) {
+			dwc_otg_hc_do_ping(core_if, hc);
+			hc->xfer_started = 1;
+			return;
+		} else {
+			hctsiz.b.dopng = 1;
+		}
+	}
+
+	if (hc->do_split) {
+		num_packets = 1;
+
+		if (hc->complete_split && !hc->ep_is_in) {
+			/* For CSPLIT OUT Transfer, set the size to 0 so the
+			 * core doesn't expect any data written to the FIFO */
+			hc->xfer_len = 0;
+		} else if (hc->ep_is_in || (hc->xfer_len > hc->max_packet)) {
+			hc->xfer_len = hc->max_packet;
+		} else if (!hc->ep_is_in && (hc->xfer_len > 188)) {
+			hc->xfer_len = 188;
+		}
+
+		hctsiz.b.xfersize = hc->xfer_len;
+	} else {
+		/*
+		 * Ensure that the transfer length and packet count will fit
+		 * in the widths allocated for them in the HCTSIZn register.
+		 */
+		if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+		    hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+			/*
+			 * Make sure the transfer size is no larger than one
+			 * (micro)frame's worth of data. (A check was done
+			 * when the periodic transfer was accepted to ensure
+			 * that a (micro)frame's worth of data can be
+			 * programmed into a channel.)
+			 */
+			uint32_t max_periodic_len =
+			    hc->multi_count * hc->max_packet;
+			if (hc->xfer_len > max_periodic_len) {
+				hc->xfer_len = max_periodic_len;
+			} else {
+			}
+		} else if (hc->xfer_len > max_hc_xfer_size) {
+			/* Make sure that xfer_len is a multiple of max packet size. */
+			hc->xfer_len = max_hc_xfer_size - hc->max_packet + 1;
+		}
+
+		if (hc->xfer_len > 0) {
+			num_packets =
+			    (hc->xfer_len + hc->max_packet -
+			     1) / hc->max_packet;
+			if (num_packets > max_hc_pkt_count) {
+				num_packets = max_hc_pkt_count;
+				hc->xfer_len = num_packets * hc->max_packet;
+			}
+		} else {
+			/* Need 1 packet for transfer length of 0. */
+			num_packets = 1;
+		}
+
+		if (hc->ep_is_in) {
+			/* Always program an integral # of max packets for IN transfers. */
+			hc->xfer_len = num_packets * hc->max_packet;
+		}
+
+		if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+		    hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+			/*
+			 * Make sure that the multi_count field matches the
+			 * actual transfer length.
+			 */
+			hc->multi_count = num_packets;
+		}
+
+		if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC)
+			set_pid_isoc(hc);
+
+		hctsiz.b.xfersize = hc->xfer_len;
+	}
+
+	hc->start_pkt_count = num_packets;
+	hctsiz.b.pktcnt = num_packets;
+	hctsiz.b.pid = hc->data_pid_start;
+	DWC_WRITE_REG32(&hc_regs->hctsiz, hctsiz.d32);
+
+	DWC_DEBUGPL(DBG_HCDV, "%s: Channel %d\n", __func__, hc->hc_num);
+	DWC_DEBUGPL(DBG_HCDV, "	 Xfer Size: %d\n", hctsiz.b.xfersize);
+	DWC_DEBUGPL(DBG_HCDV, "	 Num Pkts: %d\n", hctsiz.b.pktcnt);
+	DWC_DEBUGPL(DBG_HCDV, "	 Start PID: %d\n", hctsiz.b.pid);
+
+	if (core_if->dma_enable) {
+		dwc_dma_t dma_addr;
+		if (hc->align_buff) {
+			dma_addr = hc->align_buff;
+		} else {
+			dma_addr = ((unsigned long)hc->xfer_buff & 0xffffffff);
+		}
+		DWC_WRITE_REG32(&hc_regs->hcdma, dma_addr);
+	}
+
+	/* Start the split */
+	if (hc->do_split) {
+		hcsplt_data_t hcsplt;
+		hcsplt.d32 = DWC_READ_REG32(&hc_regs->hcsplt);
+		hcsplt.b.spltena = 1;
+		DWC_WRITE_REG32(&hc_regs->hcsplt, hcsplt.d32);
+	}
+
+	hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+	hcchar.b.multicnt = hc->multi_count;
+	hc_set_even_odd_frame(core_if, hc, &hcchar);
+#ifdef DEBUG
+	core_if->start_hcchar_val[hc->hc_num] = hcchar.d32;
+	if (hcchar.b.chdis) {
+		DWC_WARN("%s: chdis set, channel %d, hcchar 0x%08x\n",
+			 __func__, hc->hc_num, hcchar.d32);
+	}
+#endif
+
+	/* Set host channel enable after all other setup is complete. */
+	hcchar.b.chen = 1;
+	hcchar.b.chdis = 0;
+	DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32);
+
+	hc->xfer_started = 1;
+	hc->requests++;
+
+	if (!core_if->dma_enable && !hc->ep_is_in && hc->xfer_len > 0) {
+		/* Load OUT packet into the appropriate Tx FIFO. */
+		dwc_otg_hc_write_packet(core_if, hc);
+	}
+#ifdef DEBUG
+	if (hc->ep_type != DWC_OTG_EP_TYPE_INTR) {
+                DWC_DEBUGPL(DBG_HCDV, "transfer %d from core_if %p\n",
+                            hc->hc_num, core_if);//GRAYG
+		core_if->hc_xfer_info[hc->hc_num].core_if = core_if;
+		core_if->hc_xfer_info[hc->hc_num].hc = hc;
+
+		/* Start a timer for this transfer. */
+		DWC_TIMER_SCHEDULE(core_if->hc_xfer_timer[hc->hc_num], 10000);
+	}
+#endif
+}
+
+/**
+ * This function does the setup for a data transfer for a host channel
+ * and starts the transfer in Descriptor DMA mode.
+ *
+ * Initializes HCTSIZ register. For a PING transfer the Do Ping bit is set.
+ * Sets PID and NTD values. For periodic transfers
+ * initializes SCHED_INFO field with micro-frame bitmap.
+ *
+ * Initializes HCDMA register with descriptor list address and CTD value
+ * then starts the transfer via enabling the channel.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param hc Information needed to initialize the host channel.
+ */
+void dwc_otg_hc_start_transfer_ddma(dwc_otg_core_if_t * core_if, dwc_hc_t * hc)
+{
+	dwc_otg_hc_regs_t *hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+	hcchar_data_t hcchar;
+	hctsiz_data_t hctsiz;
+	hcdma_data_t hcdma;
+
+	hctsiz.d32 = 0;
+
+	if (hc->do_ping)
+		hctsiz.b_ddma.dopng = 1;
+
+	if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC)
+		set_pid_isoc(hc);
+
+	/* Packet Count and Xfer Size are not used in Descriptor DMA mode */
+	hctsiz.b_ddma.pid = hc->data_pid_start;
+	hctsiz.b_ddma.ntd = hc->ntd - 1;	/* 0 - 1 descriptor, 1 - 2 descriptors, etc. */
+	hctsiz.b_ddma.schinfo = hc->schinfo;	/* Non-zero only for high-speed interrupt endpoints */
+
+	DWC_DEBUGPL(DBG_HCDV, "%s: Channel %d\n", __func__, hc->hc_num);
+	DWC_DEBUGPL(DBG_HCDV, "	 Start PID: %d\n", hctsiz.b.pid);
+	DWC_DEBUGPL(DBG_HCDV, "	 NTD: %d\n", hctsiz.b_ddma.ntd);
+
+	DWC_WRITE_REG32(&hc_regs->hctsiz, hctsiz.d32);
+
+	hcdma.d32 = 0;
+	hcdma.b.dma_addr = ((uint32_t) hc->desc_list_addr) >> 11;
+
+	/* Always start from first descriptor. */
+	hcdma.b.ctd = 0;
+	DWC_WRITE_REG32(&hc_regs->hcdma, hcdma.d32);
+
+	hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+	hcchar.b.multicnt = hc->multi_count;
+
+#ifdef DEBUG
+	core_if->start_hcchar_val[hc->hc_num] = hcchar.d32;
+	if (hcchar.b.chdis) {
+		DWC_WARN("%s: chdis set, channel %d, hcchar 0x%08x\n",
+			 __func__, hc->hc_num, hcchar.d32);
+	}
+#endif
+
+	/* Set host channel enable after all other setup is complete. */
+	hcchar.b.chen = 1;
+	hcchar.b.chdis = 0;
+
+	DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32);
+
+	hc->xfer_started = 1;
+	hc->requests++;
+
+#ifdef DEBUG
+	if ((hc->ep_type != DWC_OTG_EP_TYPE_INTR)
+	    && (hc->ep_type != DWC_OTG_EP_TYPE_ISOC)) {
+                DWC_DEBUGPL(DBG_HCDV, "DMA transfer %d from core_if %p\n",
+                            hc->hc_num, core_if);//GRAYG
+		core_if->hc_xfer_info[hc->hc_num].core_if = core_if;
+		core_if->hc_xfer_info[hc->hc_num].hc = hc;
+		/* Start a timer for this transfer. */
+		DWC_TIMER_SCHEDULE(core_if->hc_xfer_timer[hc->hc_num], 10000);
+	}
+#endif
+
+}
+
+/**
+ * This function continues a data transfer that was started by previous call
+ * to <code>dwc_otg_hc_start_transfer</code>. The caller must ensure there is
+ * sufficient space in the request queue and Tx Data FIFO. This function
+ * should only be called in Slave mode. In DMA mode, the controller acts
+ * autonomously to complete transfers programmed to a host channel.
+ *
+ * For an OUT transfer, a new data packet is loaded into the appropriate FIFO
+ * if there is any data remaining to be queued. For an IN transfer, another
+ * data packet is always requested. For the SETUP phase of a control transfer,
+ * this function does nothing.
+ *
+ * @return 1 if a new request is queued, 0 if no more requests are required
+ * for this transfer.
+ */
+int dwc_otg_hc_continue_transfer(dwc_otg_core_if_t * core_if, dwc_hc_t * hc)
+{
+	DWC_DEBUGPL(DBG_HCDV, "%s: Channel %d\n", __func__, hc->hc_num);
+
+	if (hc->do_split) {
+		/* SPLITs always queue just once per channel */
+		return 0;
+	} else if (hc->data_pid_start == DWC_OTG_HC_PID_SETUP) {
+		/* SETUPs are queued only once since they can't be NAKed. */
+		return 0;
+	} else if (hc->ep_is_in) {
+		/*
+		 * Always queue another request for other IN transfers. If
+		 * back-to-back INs are issued and NAKs are received for both,
+		 * the driver may still be processing the first NAK when the
+		 * second NAK is received. When the interrupt handler clears
+		 * the NAK interrupt for the first NAK, the second NAK will
+		 * not be seen. So we can't depend on the NAK interrupt
+		 * handler to requeue a NAKed request. Instead, IN requests
+		 * are issued each time this function is called. When the
+		 * transfer completes, the extra requests for the channel will
+		 * be flushed.
+		 */
+		hcchar_data_t hcchar;
+		dwc_otg_hc_regs_t *hc_regs =
+		    core_if->host_if->hc_regs[hc->hc_num];
+
+		hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+		hc_set_even_odd_frame(core_if, hc, &hcchar);
+		hcchar.b.chen = 1;
+		hcchar.b.chdis = 0;
+		DWC_DEBUGPL(DBG_HCDV, "	 IN xfer: hcchar = 0x%08x\n",
+			    hcchar.d32);
+		DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32);
+		hc->requests++;
+		return 1;
+	} else {
+		/* OUT transfers. */
+		if (hc->xfer_count < hc->xfer_len) {
+			if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+			    hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+				hcchar_data_t hcchar;
+				dwc_otg_hc_regs_t *hc_regs;
+				hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+				hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+				hc_set_even_odd_frame(core_if, hc, &hcchar);
+			}
+
+			/* Load OUT packet into the appropriate Tx FIFO. */
+			dwc_otg_hc_write_packet(core_if, hc);
+			hc->requests++;
+			return 1;
+		} else {
+			return 0;
+		}
+	}
+}
+
+/**
+ * Starts a PING transfer. This function should only be called in Slave mode.
+ * The Do Ping bit is set in the HCTSIZ register, then the channel is enabled.
+ */
+void dwc_otg_hc_do_ping(dwc_otg_core_if_t * core_if, dwc_hc_t * hc)
+{
+	hcchar_data_t hcchar;
+	hctsiz_data_t hctsiz;
+	dwc_otg_hc_regs_t *hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+
+	DWC_DEBUGPL(DBG_HCDV, "%s: Channel %d\n", __func__, hc->hc_num);
+
+	hctsiz.d32 = 0;
+	hctsiz.b.dopng = 1;
+	hctsiz.b.pktcnt = 1;
+	DWC_WRITE_REG32(&hc_regs->hctsiz, hctsiz.d32);
+
+	hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+	hcchar.b.chen = 1;
+	hcchar.b.chdis = 0;
+	DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32);
+}
+
+/*
+ * This function writes a packet into the Tx FIFO associated with the Host
+ * Channel. For a channel associated with a non-periodic EP, the non-periodic
+ * Tx FIFO is written. For a channel associated with a periodic EP, the
+ * periodic Tx FIFO is written. This function should only be called in Slave
+ * mode.
+ *
+ * Upon return the xfer_buff and xfer_count fields in _hc are incremented by
+ * then number of bytes written to the Tx FIFO.
+ */
+void dwc_otg_hc_write_packet(dwc_otg_core_if_t * core_if, dwc_hc_t * hc)
+{
+	uint32_t i;
+	uint32_t remaining_count;
+	uint32_t byte_count;
+	uint32_t dword_count;
+
+	uint32_t *data_buff = (uint32_t *) (hc->xfer_buff);
+	uint32_t *data_fifo = core_if->data_fifo[hc->hc_num];
+
+	remaining_count = hc->xfer_len - hc->xfer_count;
+	if (remaining_count > hc->max_packet) {
+		byte_count = hc->max_packet;
+	} else {
+		byte_count = remaining_count;
+	}
+
+	dword_count = (byte_count + 3) / 4;
+
+	if ((((unsigned long)data_buff) & 0x3) == 0) {
+		/* xfer_buff is DWORD aligned. */
+		for (i = 0; i < dword_count; i++, data_buff++) {
+			DWC_WRITE_REG32(data_fifo, *data_buff);
+		}
+	} else {
+		/* xfer_buff is not DWORD aligned. */
+		for (i = 0; i < dword_count; i++, data_buff++) {
+			uint32_t data;
+			data =
+			    (data_buff[0] | data_buff[1] << 8 | data_buff[2] <<
+			     16 | data_buff[3] << 24);
+			DWC_WRITE_REG32(data_fifo, data);
+		}
+	}
+
+	hc->xfer_count += byte_count;
+	hc->xfer_buff += byte_count;
+}
+
+/**
+ * Gets the current USB frame number. This is the frame number from the last
+ * SOF packet.
+ */
+uint32_t dwc_otg_get_frame_number(dwc_otg_core_if_t * core_if)
+{
+	dsts_data_t dsts;
+	dsts.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts);
+
+	/* read current frame/microframe number from DSTS register */
+	return dsts.b.soffn;
+}
+
+/**
+ * Calculates and gets the frame Interval value of HFIR register according PHY
+ * type and speed.The application can modify a value of HFIR register only after
+ * the Port Enable bit of the Host Port Control and Status register
+ * (HPRT.PrtEnaPort) has been set.
+*/
+
+uint32_t calc_frame_interval(dwc_otg_core_if_t * core_if)
+{
+	gusbcfg_data_t usbcfg;
+	hwcfg2_data_t hwcfg2;
+	hprt0_data_t hprt0;
+	int clock = 60;		// default value
+	usbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg);
+	hwcfg2.d32 = DWC_READ_REG32(&core_if->core_global_regs->ghwcfg2);
+	hprt0.d32 = DWC_READ_REG32(core_if->host_if->hprt0);
+	if (!usbcfg.b.physel && usbcfg.b.ulpi_utmi_sel && !usbcfg.b.phyif)
+		clock = 60;
+	if (usbcfg.b.physel && hwcfg2.b.fs_phy_type == 3)
+		clock = 48;
+	if (!usbcfg.b.phylpwrclksel && !usbcfg.b.physel &&
+	    !usbcfg.b.ulpi_utmi_sel && usbcfg.b.phyif)
+		clock = 30;
+	if (!usbcfg.b.phylpwrclksel && !usbcfg.b.physel &&
+	    !usbcfg.b.ulpi_utmi_sel && !usbcfg.b.phyif)
+		clock = 60;
+	if (usbcfg.b.phylpwrclksel && !usbcfg.b.physel &&
+	    !usbcfg.b.ulpi_utmi_sel && usbcfg.b.phyif)
+		clock = 48;
+	if (usbcfg.b.physel && !usbcfg.b.phyif && hwcfg2.b.fs_phy_type == 2)
+		clock = 48;
+	if (usbcfg.b.physel && hwcfg2.b.fs_phy_type == 1)
+		clock = 48;
+	if (hprt0.b.prtspd == 0)
+		/* High speed case */
+		return 125 * clock - 1;
+	else
+		/* FS/LS case */
+		return 1000 * clock - 1;
+}
+
+/**
+ * This function reads a setup packet from the Rx FIFO into the destination
+ * buffer. This function is called from the Rx Status Queue Level (RxStsQLvl)
+ * Interrupt routine when a SETUP packet has been received in Slave mode.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param dest Destination buffer for packet data.
+ */
+void dwc_otg_read_setup_packet(dwc_otg_core_if_t * core_if, uint32_t * dest)
+{
+	device_grxsts_data_t status;
+	/* Get the 8 bytes of a setup transaction data */
+
+	/* Pop 2 DWORDS off the receive data FIFO into memory */
+	dest[0] = DWC_READ_REG32(core_if->data_fifo[0]);
+	dest[1] = DWC_READ_REG32(core_if->data_fifo[0]);
+	if (core_if->snpsid >= OTG_CORE_REV_3_00a) {
+		status.d32 =
+		    DWC_READ_REG32(&core_if->core_global_regs->grxstsp);
+		DWC_DEBUGPL(DBG_ANY,
+			    "EP:%d BCnt:%d " "pktsts:%x Frame:%d(0x%0x)\n",
+			    status.b.epnum, status.b.bcnt, status.b.pktsts,
+			    status.b.fn, status.b.fn);
+	}
+}
+
+/**
+ * This function enables EP0 OUT to receive SETUP packets and configures EP0
+ * IN for transmitting packets. It is normally called when the
+ * "Enumeration Done" interrupt occurs.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param ep The EP0 data.
+ */
+void dwc_otg_ep0_activate(dwc_otg_core_if_t * core_if, dwc_ep_t * ep)
+{
+	dwc_otg_dev_if_t *dev_if = core_if->dev_if;
+	dsts_data_t dsts;
+	depctl_data_t diepctl;
+	depctl_data_t doepctl;
+	dctl_data_t dctl = {.d32 = 0 };
+
+	ep->stp_rollover = 0;
+	/* Read the Device Status and Endpoint 0 Control registers */
+	dsts.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->dsts);
+	diepctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[0]->diepctl);
+	doepctl.d32 = DWC_READ_REG32(&dev_if->out_ep_regs[0]->doepctl);
+
+	/* Set the MPS of the IN EP based on the enumeration speed */
+	switch (dsts.b.enumspd) {
+	case DWC_DSTS_ENUMSPD_HS_PHY_30MHZ_OR_60MHZ:
+	case DWC_DSTS_ENUMSPD_FS_PHY_30MHZ_OR_60MHZ:
+	case DWC_DSTS_ENUMSPD_FS_PHY_48MHZ:
+		diepctl.b.mps = DWC_DEP0CTL_MPS_64;
+		break;
+	case DWC_DSTS_ENUMSPD_LS_PHY_6MHZ:
+		diepctl.b.mps = DWC_DEP0CTL_MPS_8;
+		break;
+	}
+
+	DWC_WRITE_REG32(&dev_if->in_ep_regs[0]->diepctl, diepctl.d32);
+
+	/* Enable OUT EP for receive */
+	if (core_if->snpsid <= OTG_CORE_REV_2_94a) {
+	doepctl.b.epena = 1;
+	DWC_WRITE_REG32(&dev_if->out_ep_regs[0]->doepctl, doepctl.d32);
+	}
+#ifdef VERBOSE
+	DWC_DEBUGPL(DBG_PCDV, "doepctl0=%0x\n",
+		    DWC_READ_REG32(&dev_if->out_ep_regs[0]->doepctl));
+	DWC_DEBUGPL(DBG_PCDV, "diepctl0=%0x\n",
+		    DWC_READ_REG32(&dev_if->in_ep_regs[0]->diepctl));
+#endif
+	dctl.b.cgnpinnak = 1;
+
+	DWC_MODIFY_REG32(&dev_if->dev_global_regs->dctl, dctl.d32, dctl.d32);
+	DWC_DEBUGPL(DBG_PCDV, "dctl=%0x\n",
+		    DWC_READ_REG32(&dev_if->dev_global_regs->dctl));
+
+}
+
+/**
+ * This function activates an EP.  The Device EP control register for
+ * the EP is configured as defined in the ep structure. Note: This
+ * function is not used for EP0.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param ep The EP to activate.
+ */
+void dwc_otg_ep_activate(dwc_otg_core_if_t * core_if, dwc_ep_t * ep)
+{
+	dwc_otg_dev_if_t *dev_if = core_if->dev_if;
+	depctl_data_t depctl;
+	volatile uint32_t *addr;
+	daint_data_t daintmsk = {.d32 = 0 };
+	dcfg_data_t dcfg;
+	uint8_t i;
+
+	DWC_DEBUGPL(DBG_PCDV, "%s() EP%d-%s\n", __func__, ep->num,
+		    (ep->is_in ? "IN" : "OUT"));
+
+#ifdef DWC_UTE_PER_IO
+	ep->xiso_frame_num = 0xFFFFFFFF;
+	ep->xiso_active_xfers = 0;
+	ep->xiso_queued_xfers = 0;
+#endif
+	/* Read DEPCTLn register */
+	if (ep->is_in == 1) {
+		addr = &dev_if->in_ep_regs[ep->num]->diepctl;
+		daintmsk.ep.in = 1 << ep->num;
+	} else {
+		addr = &dev_if->out_ep_regs[ep->num]->doepctl;
+		daintmsk.ep.out = 1 << ep->num;
+	}
+
+	/* If the EP is already active don't change the EP Control
+	 * register. */
+	depctl.d32 = DWC_READ_REG32(addr);
+	if (!depctl.b.usbactep) {
+		depctl.b.mps = ep->maxpacket;
+		depctl.b.eptype = ep->type;
+		depctl.b.txfnum = ep->tx_fifo_num;
+
+		if (ep->type == DWC_OTG_EP_TYPE_ISOC) {
+			depctl.b.setd0pid = 1;	// ???
+		} else {
+			depctl.b.setd0pid = 1;
+		}
+		depctl.b.usbactep = 1;
+
+		/* Update nextep_seq array and EPMSCNT in DCFG*/
+		if (!(depctl.b.eptype & 1) && (ep->is_in == 1)) {	// NP IN EP
+			for (i = 0; i <= core_if->dev_if->num_in_eps; i++) {
+				if (core_if->nextep_seq[i] == core_if->first_in_nextep_seq)
+				break;
+			}
+			core_if->nextep_seq[i] = ep->num;
+			core_if->nextep_seq[ep->num] = core_if->first_in_nextep_seq;
+			depctl.b.nextep = core_if->nextep_seq[ep->num];
+			dcfg.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->dcfg);
+			dcfg.b.epmscnt++;
+			DWC_WRITE_REG32(&dev_if->dev_global_regs->dcfg, dcfg.d32);
+
+			DWC_DEBUGPL(DBG_PCDV,
+				    "%s first_in_nextep_seq= %2d; nextep_seq[]:\n",
+				__func__, core_if->first_in_nextep_seq);
+			for (i=0; i <= core_if->dev_if->num_in_eps; i++) {
+				DWC_DEBUGPL(DBG_PCDV, "%2d\n",
+					    core_if->nextep_seq[i]);
+			}
+
+		}
+
+
+		DWC_WRITE_REG32(addr, depctl.d32);
+		DWC_DEBUGPL(DBG_PCDV, "DEPCTL=%08x\n", DWC_READ_REG32(addr));
+	}
+
+	/* Enable the Interrupt for this EP */
+	if (core_if->multiproc_int_enable) {
+		if (ep->is_in == 1) {
+			diepmsk_data_t diepmsk = {.d32 = 0 };
+			diepmsk.b.xfercompl = 1;
+			diepmsk.b.timeout = 1;
+			diepmsk.b.epdisabled = 1;
+			diepmsk.b.ahberr = 1;
+			diepmsk.b.intknepmis = 1;
+			if (!core_if->en_multiple_tx_fifo && core_if->dma_enable)
+				diepmsk.b.intknepmis = 0;
+			diepmsk.b.txfifoundrn = 1;	//?????
+			if (ep->type == DWC_OTG_EP_TYPE_ISOC) {
+				diepmsk.b.nak = 1;
+			}
+
+
+
+/*
+			if (core_if->dma_desc_enable) {
+				diepmsk.b.bna = 1;
+			}
+*/
+/*
+			if (core_if->dma_enable) {
+				doepmsk.b.nak = 1;
+			}
+*/
+			DWC_WRITE_REG32(&dev_if->dev_global_regs->
+					diepeachintmsk[ep->num], diepmsk.d32);
+
+		} else {
+			doepmsk_data_t doepmsk = {.d32 = 0 };
+			doepmsk.b.xfercompl = 1;
+			doepmsk.b.ahberr = 1;
+			doepmsk.b.epdisabled = 1;
+			if (ep->type == DWC_OTG_EP_TYPE_ISOC)
+				doepmsk.b.outtknepdis = 1;
+
+/*
+
+			if (core_if->dma_desc_enable) {
+				doepmsk.b.bna = 1;
+			}
+*/
+/*
+			doepmsk.b.babble = 1;
+			doepmsk.b.nyet = 1;
+			doepmsk.b.nak = 1;
+*/
+			DWC_WRITE_REG32(&dev_if->dev_global_regs->
+					doepeachintmsk[ep->num], doepmsk.d32);
+		}
+		DWC_MODIFY_REG32(&dev_if->dev_global_regs->deachintmsk,
+				 0, daintmsk.d32);
+	} else {
+		if (ep->type == DWC_OTG_EP_TYPE_ISOC) {
+			if (ep->is_in) {
+				diepmsk_data_t diepmsk = {.d32 = 0 };
+				diepmsk.b.nak = 1;
+				DWC_MODIFY_REG32(&dev_if->dev_global_regs->diepmsk, 0, diepmsk.d32);
+			} else {
+				doepmsk_data_t doepmsk = {.d32 = 0 };
+				doepmsk.b.outtknepdis = 1;
+				DWC_MODIFY_REG32(&dev_if->dev_global_regs->doepmsk, 0, doepmsk.d32);
+			}
+		}
+		DWC_MODIFY_REG32(&dev_if->dev_global_regs->daintmsk,
+				 0, daintmsk.d32);
+	}
+
+	DWC_DEBUGPL(DBG_PCDV, "DAINTMSK=%0x\n",
+		    DWC_READ_REG32(&dev_if->dev_global_regs->daintmsk));
+
+	ep->stall_clear_flag = 0;
+
+	return;
+}
+
+/**
+ * This function deactivates an EP. This is done by clearing the USB Active
+ * EP bit in the Device EP control register. Note: This function is not used
+ * for EP0. EP0 cannot be deactivated.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param ep The EP to deactivate.
+ */
+void dwc_otg_ep_deactivate(dwc_otg_core_if_t * core_if, dwc_ep_t * ep)
+{
+	depctl_data_t depctl = {.d32 = 0 };
+	volatile uint32_t *addr;
+	daint_data_t daintmsk = {.d32 = 0 };
+	dcfg_data_t dcfg;
+	uint8_t i = 0;
+
+#ifdef DWC_UTE_PER_IO
+	ep->xiso_frame_num = 0xFFFFFFFF;
+	ep->xiso_active_xfers = 0;
+	ep->xiso_queued_xfers = 0;
+#endif
+
+	/* Read DEPCTLn register */
+	if (ep->is_in == 1) {
+		addr = &core_if->dev_if->in_ep_regs[ep->num]->diepctl;
+		daintmsk.ep.in = 1 << ep->num;
+	} else {
+		addr = &core_if->dev_if->out_ep_regs[ep->num]->doepctl;
+		daintmsk.ep.out = 1 << ep->num;
+	}
+
+	depctl.d32 = DWC_READ_REG32(addr);
+
+	depctl.b.usbactep = 0;
+
+	/* Update nextep_seq array and EPMSCNT in DCFG*/
+	if (!(depctl.b.eptype & 1) && ep->is_in == 1) {	// NP EP IN
+		for (i = 0; i <= core_if->dev_if->num_in_eps; i++) {
+			if (core_if->nextep_seq[i] == ep->num)
+			break;
+		}
+		core_if->nextep_seq[i] = core_if->nextep_seq[ep->num];
+		if (core_if->first_in_nextep_seq == ep->num)
+			core_if->first_in_nextep_seq = i;
+		core_if->nextep_seq[ep->num] = 0xff;
+		depctl.b.nextep = 0;
+		dcfg.d32 =
+		    DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg);
+		dcfg.b.epmscnt--;
+		DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dcfg,
+				dcfg.d32);
+
+		DWC_DEBUGPL(DBG_PCDV,
+			    "%s first_in_nextep_seq= %2d; nextep_seq[]:\n",
+				__func__, core_if->first_in_nextep_seq);
+			for (i=0; i <= core_if->dev_if->num_in_eps; i++) {
+				DWC_DEBUGPL(DBG_PCDV, "%2d\n", core_if->nextep_seq[i]);
+			}
+	}
+
+	if (ep->is_in == 1)
+		depctl.b.txfnum = 0;
+
+	if (core_if->dma_desc_enable)
+		depctl.b.epdis = 1;
+
+	DWC_WRITE_REG32(addr, depctl.d32);
+	depctl.d32 = DWC_READ_REG32(addr);
+	if (core_if->dma_enable && ep->type == DWC_OTG_EP_TYPE_ISOC
+	    && depctl.b.epena) {
+		depctl_data_t depctl = {.d32 = 0};
+		if (ep->is_in) {
+			diepint_data_t diepint = {.d32 = 0};
+
+			depctl.b.snak = 1;
+			DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[ep->num]->
+					diepctl, depctl.d32);
+			do {
+				dwc_udelay(10);
+				diepint.d32 =
+				    DWC_READ_REG32(&core_if->
+						   dev_if->in_ep_regs[ep->num]->
+						   diepint);
+			} while (!diepint.b.inepnakeff);
+			diepint.b.inepnakeff = 1;
+			DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[ep->num]->
+					diepint, diepint.d32);
+			depctl.d32 = 0;
+			depctl.b.epdis = 1;
+			DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[ep->num]->
+					diepctl, depctl.d32);
+			do {
+				dwc_udelay(10);
+				diepint.d32 =
+				    DWC_READ_REG32(&core_if->
+						   dev_if->in_ep_regs[ep->num]->
+						   diepint);
+			} while (!diepint.b.epdisabled);
+			diepint.b.epdisabled = 1;
+			DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[ep->num]->
+					diepint, diepint.d32);
+		} else {
+			dctl_data_t dctl = {.d32 = 0};
+			gintmsk_data_t gintsts = {.d32 = 0};
+			doepint_data_t doepint = {.d32 = 0};
+			dctl.b.sgoutnak = 1;
+			DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->
+					 dctl, 0, dctl.d32);
+			do {
+				dwc_udelay(10);
+				gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts);
+			} while (!gintsts.b.goutnakeff);
+			gintsts.d32 = 0;
+			gintsts.b.goutnakeff = 1;
+			DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32);
+
+			depctl.d32 = 0;
+			depctl.b.epdis = 1;
+			depctl.b.snak = 1;
+			DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[ep->num]->doepctl, depctl.d32);
+			do
+			{
+				dwc_udelay(10);
+				doepint.d32 = DWC_READ_REG32(&core_if->dev_if->
+											out_ep_regs[ep->num]->doepint);
+			} while (!doepint.b.epdisabled);
+
+			doepint.b.epdisabled = 1;
+			DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[ep->num]->doepint, doepint.d32);
+
+			dctl.d32 = 0;
+			dctl.b.cgoutnak = 1;
+			DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, 0, dctl.d32);
+		}
+	}
+
+	/* Disable the Interrupt for this EP */
+	if (core_if->multiproc_int_enable) {
+		DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->deachintmsk,
+				 daintmsk.d32, 0);
+
+		if (ep->is_in == 1) {
+			DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->
+					diepeachintmsk[ep->num], 0);
+		} else {
+			DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->
+					doepeachintmsk[ep->num], 0);
+		}
+	} else {
+		DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->daintmsk,
+				 daintmsk.d32, 0);
+	}
+
+}
+
+/**
+ * This function initializes dma descriptor chain.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param ep The EP to start the transfer on.
+ */
+static void init_dma_desc_chain(dwc_otg_core_if_t * core_if, dwc_ep_t * ep)
+{
+	dwc_otg_dev_dma_desc_t *dma_desc;
+	uint32_t offset;
+	uint32_t xfer_est;
+	int i;
+	unsigned maxxfer_local, total_len;
+
+	if (!ep->is_in && ep->type == DWC_OTG_EP_TYPE_INTR &&
+					(ep->maxpacket%4)) {
+		maxxfer_local = ep->maxpacket;
+		total_len = ep->xfer_len;
+	} else {
+		maxxfer_local = ep->maxxfer;
+		total_len = ep->total_len;
+	}
+
+	ep->desc_cnt = (total_len / maxxfer_local) +
+            ((total_len % maxxfer_local) ? 1 : 0);
+
+	if (!ep->desc_cnt)
+		ep->desc_cnt = 1;
+
+	if (ep->desc_cnt > MAX_DMA_DESC_CNT)
+		ep->desc_cnt = MAX_DMA_DESC_CNT;
+
+	dma_desc = ep->desc_addr;
+	if (maxxfer_local == ep->maxpacket) {
+		if ((total_len % maxxfer_local) &&
+				(total_len/maxxfer_local < MAX_DMA_DESC_CNT)) {
+			xfer_est = (ep->desc_cnt - 1) * maxxfer_local +
+					(total_len % maxxfer_local);
+		} else
+			xfer_est = ep->desc_cnt * maxxfer_local;
+	} else
+		xfer_est = total_len;
+	offset = 0;
+	for (i = 0; i < ep->desc_cnt; ++i) {
+		/** DMA Descriptor Setup */
+		if (xfer_est > maxxfer_local) {
+			dma_desc->status.b.bs = BS_HOST_BUSY;
+			dma_desc->status.b.l = 0;
+			dma_desc->status.b.ioc = 0;
+			dma_desc->status.b.sp = 0;
+			dma_desc->status.b.bytes = maxxfer_local;
+			dma_desc->buf = ep->dma_addr + offset;
+			dma_desc->status.b.sts = 0;
+			dma_desc->status.b.bs = BS_HOST_READY;
+
+			xfer_est -= maxxfer_local;
+			offset += maxxfer_local;
+		} else {
+			dma_desc->status.b.bs = BS_HOST_BUSY;
+			dma_desc->status.b.l = 1;
+			dma_desc->status.b.ioc = 1;
+			if (ep->is_in) {
+				dma_desc->status.b.sp =
+				    (xfer_est %
+				     ep->maxpacket) ? 1 : ((ep->
+							    sent_zlp) ? 1 : 0);
+				dma_desc->status.b.bytes = xfer_est;
+			} else {
+				if (maxxfer_local == ep->maxpacket)
+					dma_desc->status.b.bytes = xfer_est;
+				else
+					dma_desc->status.b.bytes =
+						xfer_est + ((4 - (xfer_est & 0x3)) & 0x3);
+			}
+
+			dma_desc->buf = ep->dma_addr + offset;
+			dma_desc->status.b.sts = 0;
+			dma_desc->status.b.bs = BS_HOST_READY;
+		}
+		dma_desc++;
+	}
+}
+/**
+ * This function is called when to write ISOC data into appropriate dedicated
+ * periodic FIFO.
+ */
+static int32_t write_isoc_tx_fifo(dwc_otg_core_if_t * core_if, dwc_ep_t * dwc_ep)
+{
+	dwc_otg_dev_if_t *dev_if = core_if->dev_if;
+	dwc_otg_dev_in_ep_regs_t *ep_regs;
+	dtxfsts_data_t txstatus = {.d32 = 0 };
+	uint32_t len = 0;
+	int epnum = dwc_ep->num;
+	int dwords;
+
+	DWC_DEBUGPL(DBG_PCD, "Dedicated TxFifo Empty: %d \n", epnum);
+
+	ep_regs = core_if->dev_if->in_ep_regs[epnum];
+
+	len = dwc_ep->xfer_len - dwc_ep->xfer_count;
+
+	if (len > dwc_ep->maxpacket) {
+		len = dwc_ep->maxpacket;
+	}
+
+	dwords = (len + 3) / 4;
+
+	/* While there is space in the queue and space in the FIFO and
+	 * More data to tranfer, Write packets to the Tx FIFO */
+	txstatus.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dtxfsts);
+	DWC_DEBUGPL(DBG_PCDV, "b4 dtxfsts[%d]=0x%08x\n", epnum, txstatus.d32);
+
+	while (txstatus.b.txfspcavail > dwords &&
+	       dwc_ep->xfer_count < dwc_ep->xfer_len && dwc_ep->xfer_len != 0) {
+		/* Write the FIFO */
+		dwc_otg_ep_write_packet(core_if, dwc_ep, 0);
+
+		len = dwc_ep->xfer_len - dwc_ep->xfer_count;
+		if (len > dwc_ep->maxpacket) {
+			len = dwc_ep->maxpacket;
+		}
+
+		dwords = (len + 3) / 4;
+		txstatus.d32 =
+		    DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dtxfsts);
+		DWC_DEBUGPL(DBG_PCDV, "dtxfsts[%d]=0x%08x\n", epnum,
+			    txstatus.d32);
+	}
+
+	DWC_DEBUGPL(DBG_PCDV, "b4 dtxfsts[%d]=0x%08x\n", epnum,
+		    DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dtxfsts));
+
+	return 1;
+}
+/**
+ * This function does the setup for a data transfer for an EP and
+ * starts the transfer. For an IN transfer, the packets will be
+ * loaded into the appropriate Tx FIFO in the ISR. For OUT transfers,
+ * the packets are unloaded from the Rx FIFO in the ISR.  the ISR.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param ep The EP to start the transfer on.
+ */
+
+void dwc_otg_ep_start_transfer(dwc_otg_core_if_t * core_if, dwc_ep_t * ep)
+{
+	depctl_data_t depctl;
+	deptsiz_data_t deptsiz;
+	gintmsk_data_t intr_mask = {.d32 = 0 };
+
+	DWC_DEBUGPL((DBG_PCDV | DBG_CILV), "%s()\n", __func__);
+	DWC_DEBUGPL(DBG_PCD, "ep%d-%s xfer_len=%d xfer_cnt=%d "
+		    "xfer_buff=%p start_xfer_buff=%p, total_len = %d\n",
+		    ep->num, (ep->is_in ? "IN" : "OUT"), ep->xfer_len,
+		    ep->xfer_count, ep->xfer_buff, ep->start_xfer_buff,
+		    ep->total_len);
+	/* IN endpoint */
+	if (ep->is_in == 1) {
+		dwc_otg_dev_in_ep_regs_t *in_regs =
+		    core_if->dev_if->in_ep_regs[ep->num];
+
+		gnptxsts_data_t gtxstatus;
+
+		gtxstatus.d32 =
+		    DWC_READ_REG32(&core_if->core_global_regs->gnptxsts);
+
+		if (core_if->en_multiple_tx_fifo == 0
+		    && gtxstatus.b.nptxqspcavail == 0 && !core_if->dma_enable) {
+#ifdef DEBUG
+			DWC_PRINTF("TX Queue Full (0x%0x)\n", gtxstatus.d32);
+#endif
+			return;
+		}
+
+		depctl.d32 = DWC_READ_REG32(&(in_regs->diepctl));
+		deptsiz.d32 = DWC_READ_REG32(&(in_regs->dieptsiz));
+
+		if (ep->maxpacket > ep->maxxfer / MAX_PKT_CNT)
+			ep->xfer_len += (ep->maxxfer < (ep->total_len - ep->xfer_len)) ?
+				ep->maxxfer : (ep->total_len - ep->xfer_len);
+		else
+			ep->xfer_len += (MAX_PKT_CNT * ep->maxpacket < (ep->total_len - ep->xfer_len)) ?
+				 MAX_PKT_CNT * ep->maxpacket : (ep->total_len - ep->xfer_len);
+
+
+		/* Zero Length Packet? */
+		if ((ep->xfer_len - ep->xfer_count) == 0) {
+			deptsiz.b.xfersize = 0;
+			deptsiz.b.pktcnt = 1;
+		} else {
+			/* Program the transfer size and packet count
+			 *      as follows: xfersize = N * maxpacket +
+			 *      short_packet pktcnt = N + (short_packet
+			 *      exist ? 1 : 0)
+			 */
+			deptsiz.b.xfersize = ep->xfer_len - ep->xfer_count;
+			deptsiz.b.pktcnt =
+			    (ep->xfer_len - ep->xfer_count - 1 +
+			     ep->maxpacket) / ep->maxpacket;
+			if (deptsiz.b.pktcnt > MAX_PKT_CNT) {
+				deptsiz.b.pktcnt = MAX_PKT_CNT;
+				deptsiz.b.xfersize = deptsiz.b.pktcnt * ep->maxpacket;
+			}
+			if (ep->type == DWC_OTG_EP_TYPE_ISOC)
+				deptsiz.b.mc = deptsiz.b.pktcnt;
+		}
+
+		/* Write the DMA register */
+		if (core_if->dma_enable) {
+			if (core_if->dma_desc_enable == 0) {
+				if (ep->type != DWC_OTG_EP_TYPE_ISOC)
+					deptsiz.b.mc = 1;
+				DWC_WRITE_REG32(&in_regs->dieptsiz,
+						deptsiz.d32);
+				DWC_WRITE_REG32(&(in_regs->diepdma),
+						(uint32_t) ep->dma_addr);
+			} else {
+#ifdef DWC_UTE_CFI
+				/* The descriptor chain should be already initialized by now */
+				if (ep->buff_mode != BM_STANDARD) {
+					DWC_WRITE_REG32(&in_regs->diepdma,
+							ep->descs_dma_addr);
+				} else {
+#endif
+					init_dma_desc_chain(core_if, ep);
+				/** DIEPDMAn Register write */
+					DWC_WRITE_REG32(&in_regs->diepdma,
+							ep->dma_desc_addr);
+#ifdef DWC_UTE_CFI
+				}
+#endif
+			}
+		} else {
+			DWC_WRITE_REG32(&in_regs->dieptsiz, deptsiz.d32);
+			if (ep->type != DWC_OTG_EP_TYPE_ISOC) {
+				/**
+				 * Enable the Non-Periodic Tx FIFO empty interrupt,
+				 * or the Tx FIFO epmty interrupt in dedicated Tx FIFO mode,
+				 * the data will be written into the fifo by the ISR.
+				 */
+				if (core_if->en_multiple_tx_fifo == 0) {
+					intr_mask.b.nptxfempty = 1;
+					DWC_MODIFY_REG32
+					    (&core_if->core_global_regs->gintmsk,
+					     intr_mask.d32, intr_mask.d32);
+				} else {
+					/* Enable the Tx FIFO Empty Interrupt for this EP */
+					if (ep->xfer_len > 0) {
+						uint32_t fifoemptymsk = 0;
+						fifoemptymsk = 1 << ep->num;
+						DWC_MODIFY_REG32
+						    (&core_if->dev_if->dev_global_regs->dtknqr4_fifoemptymsk,
+						     0, fifoemptymsk);
+
+					}
+				}
+			}  else {
+					 write_isoc_tx_fifo(core_if, ep);
+			}
+		}
+		if (!core_if->core_params->en_multiple_tx_fifo && core_if->dma_enable)
+			depctl.b.nextep = core_if->nextep_seq[ep->num];
+
+		if (ep->type == DWC_OTG_EP_TYPE_ISOC) {
+			dsts_data_t dsts = {.d32 = 0};
+			if (ep->bInterval == 1) {
+				dsts.d32 =
+				    DWC_READ_REG32(&core_if->dev_if->
+						   dev_global_regs->dsts);
+				ep->frame_num = dsts.b.soffn + ep->bInterval;
+				if (ep->frame_num > 0x3FFF) {
+					ep->frm_overrun = 1;
+					ep->frame_num &= 0x3FFF;
+				} else
+					ep->frm_overrun = 0;
+				if (ep->frame_num & 0x1) {
+					depctl.b.setd1pid = 1;
+				} else {
+					depctl.b.setd0pid = 1;
+				}
+			}
+		}
+		/* EP enable, IN data in FIFO */
+		depctl.b.cnak = 1;
+		depctl.b.epena = 1;
+		DWC_WRITE_REG32(&in_regs->diepctl, depctl.d32);
+
+	} else {
+		/* OUT endpoint */
+		dwc_otg_dev_out_ep_regs_t *out_regs =
+		    core_if->dev_if->out_ep_regs[ep->num];
+
+		depctl.d32 = DWC_READ_REG32(&(out_regs->doepctl));
+		deptsiz.d32 = DWC_READ_REG32(&(out_regs->doeptsiz));
+
+		if (!core_if->dma_desc_enable) {
+			if (ep->maxpacket > ep->maxxfer / MAX_PKT_CNT)
+				ep->xfer_len += (ep->maxxfer < (ep->total_len - ep->xfer_len)) ?
+				ep->maxxfer : (ep->total_len - ep->xfer_len);
+                else
+					ep->xfer_len += (MAX_PKT_CNT * ep->maxpacket < (ep->total_len
+					- ep->xfer_len)) ? MAX_PKT_CNT * ep->maxpacket : (ep->total_len - ep->xfer_len);
+		}
+
+		/* Program the transfer size and packet count as follows:
+		 *
+		 *      pktcnt = N
+		 *      xfersize = N * maxpacket
+		 */
+		if ((ep->xfer_len - ep->xfer_count) == 0) {
+			/* Zero Length Packet */
+			deptsiz.b.xfersize = ep->maxpacket;
+			deptsiz.b.pktcnt = 1;
+		} else {
+			deptsiz.b.pktcnt =
+			    (ep->xfer_len - ep->xfer_count +
+			     (ep->maxpacket - 1)) / ep->maxpacket;
+			if (deptsiz.b.pktcnt > MAX_PKT_CNT) {
+				deptsiz.b.pktcnt = MAX_PKT_CNT;
+			}
+			if (!core_if->dma_desc_enable) {
+				ep->xfer_len =
+					deptsiz.b.pktcnt * ep->maxpacket + ep->xfer_count;
+			}
+			deptsiz.b.xfersize = ep->xfer_len - ep->xfer_count;
+		}
+
+		DWC_DEBUGPL(DBG_PCDV, "ep%d xfersize=%d pktcnt=%d\n",
+			    ep->num, deptsiz.b.xfersize, deptsiz.b.pktcnt);
+
+		if (core_if->dma_enable) {
+			if (!core_if->dma_desc_enable) {
+				DWC_WRITE_REG32(&out_regs->doeptsiz,
+						deptsiz.d32);
+
+				DWC_WRITE_REG32(&(out_regs->doepdma),
+						(uint32_t) ep->dma_addr);
+			} else {
+#ifdef DWC_UTE_CFI
+				/* The descriptor chain should be already initialized by now */
+				if (ep->buff_mode != BM_STANDARD) {
+					DWC_WRITE_REG32(&out_regs->doepdma,
+							ep->descs_dma_addr);
+				} else {
+#endif
+					/** This is used for interrupt out transfers*/
+					if (!ep->xfer_len)
+						ep->xfer_len = ep->total_len;
+					init_dma_desc_chain(core_if, ep);
+
+					if (core_if->core_params->dev_out_nak) {
+						if (ep->type == DWC_OTG_EP_TYPE_BULK) {
+							deptsiz.b.pktcnt = (ep->total_len +
+								(ep->maxpacket - 1)) / ep->maxpacket;
+							deptsiz.b.xfersize = ep->total_len;
+							/* Remember initial value of doeptsiz */
+							core_if->start_doeptsiz_val[ep->num] = deptsiz.d32;
+							DWC_WRITE_REG32(&out_regs->doeptsiz,
+								deptsiz.d32);
+						}
+					}
+				/** DOEPDMAn Register write */
+					DWC_WRITE_REG32(&out_regs->doepdma,
+							ep->dma_desc_addr);
+#ifdef DWC_UTE_CFI
+				}
+#endif
+			}
+		} else {
+			DWC_WRITE_REG32(&out_regs->doeptsiz, deptsiz.d32);
+		}
+
+		if (ep->type == DWC_OTG_EP_TYPE_ISOC) {
+			dsts_data_t dsts = {.d32 = 0};
+			if (ep->bInterval == 1) {
+				dsts.d32 =
+				    DWC_READ_REG32(&core_if->dev_if->
+						   dev_global_regs->dsts);
+				ep->frame_num = dsts.b.soffn + ep->bInterval;
+				if (ep->frame_num > 0x3FFF) {
+					ep->frm_overrun = 1;
+					ep->frame_num &= 0x3FFF;
+				} else
+					ep->frm_overrun = 0;
+
+				if (ep->frame_num & 0x1) {
+					depctl.b.setd1pid = 1;
+				} else {
+					depctl.b.setd0pid = 1;
+				}
+			}
+		}
+
+		/* EP enable */
+		depctl.b.cnak = 1;
+		depctl.b.epena = 1;
+
+		DWC_WRITE_REG32(&out_regs->doepctl, depctl.d32);
+
+		DWC_DEBUGPL(DBG_PCD, "DOEPCTL=%08x DOEPTSIZ=%08x\n",
+			    DWC_READ_REG32(&out_regs->doepctl),
+			    DWC_READ_REG32(&out_regs->doeptsiz));
+		DWC_DEBUGPL(DBG_PCD, "DAINTMSK=%08x GINTMSK=%08x\n",
+			    DWC_READ_REG32(&core_if->dev_if->dev_global_regs->
+					   daintmsk),
+			    DWC_READ_REG32(&core_if->core_global_regs->
+					   gintmsk));
+
+		/* Timer is scheduling only for out bulk transfers for
+		 * "Device DDMA OUT NAK Enhancement" feature to inform user
+		 * about received data payload in case of timeout
+		 */
+		if (core_if->core_params->dev_out_nak) {
+			if (ep->type == DWC_OTG_EP_TYPE_BULK) {
+				core_if->ep_xfer_info[ep->num].core_if = core_if;
+				core_if->ep_xfer_info[ep->num].ep = ep;
+				core_if->ep_xfer_info[ep->num].state = 1;
+
+				/* Start a timer for this transfer. */
+				DWC_TIMER_SCHEDULE(core_if->ep_xfer_timer[ep->num], 10000);
+			}
+		}
+	}
+}
+
+/**
+ * This function setup a zero length transfer in Buffer DMA and
+ * Slave modes for usb requests with zero field set
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param ep The EP to start the transfer on.
+ *
+ */
+void dwc_otg_ep_start_zl_transfer(dwc_otg_core_if_t * core_if, dwc_ep_t * ep)
+{
+
+	depctl_data_t depctl;
+	deptsiz_data_t deptsiz;
+	gintmsk_data_t intr_mask = {.d32 = 0 };
+
+	DWC_DEBUGPL((DBG_PCDV | DBG_CILV), "%s()\n", __func__);
+	DWC_PRINTF("zero length transfer is called\n");
+
+	/* IN endpoint */
+	if (ep->is_in == 1) {
+		dwc_otg_dev_in_ep_regs_t *in_regs =
+		    core_if->dev_if->in_ep_regs[ep->num];
+
+		depctl.d32 = DWC_READ_REG32(&(in_regs->diepctl));
+		deptsiz.d32 = DWC_READ_REG32(&(in_regs->dieptsiz));
+
+		deptsiz.b.xfersize = 0;
+		deptsiz.b.pktcnt = 1;
+
+		/* Write the DMA register */
+		if (core_if->dma_enable) {
+			if (core_if->dma_desc_enable == 0) {
+				deptsiz.b.mc = 1;
+				DWC_WRITE_REG32(&in_regs->dieptsiz,
+						deptsiz.d32);
+				DWC_WRITE_REG32(&(in_regs->diepdma),
+						(uint32_t) ep->dma_addr);
+			}
+		} else {
+			DWC_WRITE_REG32(&in_regs->dieptsiz, deptsiz.d32);
+			/**
+			 * Enable the Non-Periodic Tx FIFO empty interrupt,
+			 * or the Tx FIFO epmty interrupt in dedicated Tx FIFO mode,
+			 * the data will be written into the fifo by the ISR.
+			 */
+			if (core_if->en_multiple_tx_fifo == 0) {
+				intr_mask.b.nptxfempty = 1;
+				DWC_MODIFY_REG32(&core_if->
+						 core_global_regs->gintmsk,
+						 intr_mask.d32, intr_mask.d32);
+			} else {
+				/* Enable the Tx FIFO Empty Interrupt for this EP */
+				if (ep->xfer_len > 0) {
+					uint32_t fifoemptymsk = 0;
+					fifoemptymsk = 1 << ep->num;
+					DWC_MODIFY_REG32(&core_if->
+							 dev_if->dev_global_regs->dtknqr4_fifoemptymsk,
+							 0, fifoemptymsk);
+				}
+			}
+		}
+
+		if (!core_if->core_params->en_multiple_tx_fifo && core_if->dma_enable)
+			depctl.b.nextep = core_if->nextep_seq[ep->num];
+		/* EP enable, IN data in FIFO */
+		depctl.b.cnak = 1;
+		depctl.b.epena = 1;
+		DWC_WRITE_REG32(&in_regs->diepctl, depctl.d32);
+
+	} else {
+		/* OUT endpoint */
+		dwc_otg_dev_out_ep_regs_t *out_regs =
+		    core_if->dev_if->out_ep_regs[ep->num];
+
+		depctl.d32 = DWC_READ_REG32(&(out_regs->doepctl));
+		deptsiz.d32 = DWC_READ_REG32(&(out_regs->doeptsiz));
+
+		/* Zero Length Packet */
+		deptsiz.b.xfersize = ep->maxpacket;
+		deptsiz.b.pktcnt = 1;
+
+		if (core_if->dma_enable) {
+			if (!core_if->dma_desc_enable) {
+				DWC_WRITE_REG32(&out_regs->doeptsiz,
+						deptsiz.d32);
+
+				DWC_WRITE_REG32(&(out_regs->doepdma),
+						(uint32_t) ep->dma_addr);
+			}
+		} else {
+			DWC_WRITE_REG32(&out_regs->doeptsiz, deptsiz.d32);
+		}
+
+		/* EP enable */
+		depctl.b.cnak = 1;
+		depctl.b.epena = 1;
+
+		DWC_WRITE_REG32(&out_regs->doepctl, depctl.d32);
+
+	}
+}
+
+/**
+ * This function does the setup for a data transfer for EP0 and starts
+ * the transfer.  For an IN transfer, the packets will be loaded into
+ * the appropriate Tx FIFO in the ISR. For OUT transfers, the packets are
+ * unloaded from the Rx FIFO in the ISR.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param ep The EP0 data.
+ */
+void dwc_otg_ep0_start_transfer(dwc_otg_core_if_t * core_if, dwc_ep_t * ep)
+{
+	depctl_data_t depctl;
+	deptsiz0_data_t deptsiz;
+	gintmsk_data_t intr_mask = {.d32 = 0 };
+	dwc_otg_dev_dma_desc_t *dma_desc;
+
+	DWC_DEBUGPL(DBG_PCD, "ep%d-%s xfer_len=%d xfer_cnt=%d "
+		    "xfer_buff=%p start_xfer_buff=%p \n",
+		    ep->num, (ep->is_in ? "IN" : "OUT"), ep->xfer_len,
+		    ep->xfer_count, ep->xfer_buff, ep->start_xfer_buff);
+
+	ep->total_len = ep->xfer_len;
+
+	/* IN endpoint */
+	if (ep->is_in == 1) {
+		dwc_otg_dev_in_ep_regs_t *in_regs =
+		    core_if->dev_if->in_ep_regs[0];
+
+		gnptxsts_data_t gtxstatus;
+
+		if (core_if->snpsid >= OTG_CORE_REV_3_00a) {
+			depctl.d32 = DWC_READ_REG32(&in_regs->diepctl);
+			if (depctl.b.epena)
+				return;
+		}
+
+		gtxstatus.d32 =
+		    DWC_READ_REG32(&core_if->core_global_regs->gnptxsts);
+
+		/* If dedicated FIFO every time flush fifo before enable ep*/
+		if (core_if->en_multiple_tx_fifo && core_if->snpsid >= OTG_CORE_REV_3_00a)
+			dwc_otg_flush_tx_fifo(core_if, ep->tx_fifo_num);
+
+		if (core_if->en_multiple_tx_fifo == 0
+		    && gtxstatus.b.nptxqspcavail == 0
+		    && !core_if->dma_enable) {
+#ifdef DEBUG
+			deptsiz.d32 = DWC_READ_REG32(&in_regs->dieptsiz);
+			DWC_DEBUGPL(DBG_PCD, "DIEPCTL0=%0x\n",
+				    DWC_READ_REG32(&in_regs->diepctl));
+			DWC_DEBUGPL(DBG_PCD, "DIEPTSIZ0=%0x (sz=%d, pcnt=%d)\n",
+				    deptsiz.d32,
+				    deptsiz.b.xfersize, deptsiz.b.pktcnt);
+			DWC_PRINTF("TX Queue or FIFO Full (0x%0x)\n",
+				   gtxstatus.d32);
+#endif
+			return;
+		}
+
+		depctl.d32 = DWC_READ_REG32(&in_regs->diepctl);
+		deptsiz.d32 = DWC_READ_REG32(&in_regs->dieptsiz);
+
+		/* Zero Length Packet? */
+		if (ep->xfer_len == 0) {
+			deptsiz.b.xfersize = 0;
+			deptsiz.b.pktcnt = 1;
+		} else {
+			/* Program the transfer size and packet count
+			 *      as follows: xfersize = N * maxpacket +
+			 *      short_packet pktcnt = N + (short_packet
+			 *      exist ? 1 : 0)
+			 */
+			if (ep->xfer_len > ep->maxpacket) {
+				ep->xfer_len = ep->maxpacket;
+				deptsiz.b.xfersize = ep->maxpacket;
+			} else {
+				deptsiz.b.xfersize = ep->xfer_len;
+			}
+			deptsiz.b.pktcnt = 1;
+
+		}
+		DWC_DEBUGPL(DBG_PCDV,
+			    "IN len=%d  xfersize=%d pktcnt=%d [%08x]\n",
+			    ep->xfer_len, deptsiz.b.xfersize, deptsiz.b.pktcnt,
+			    deptsiz.d32);
+
+		/* Write the DMA register */
+		if (core_if->dma_enable) {
+			if (core_if->dma_desc_enable == 0) {
+				DWC_WRITE_REG32(&in_regs->dieptsiz,
+						deptsiz.d32);
+
+				DWC_WRITE_REG32(&(in_regs->diepdma),
+						(uint32_t) ep->dma_addr);
+			} else {
+				dma_desc = core_if->dev_if->in_desc_addr;
+
+				/** DMA Descriptor Setup */
+				dma_desc->status.b.bs = BS_HOST_BUSY;
+				dma_desc->status.b.l = 1;
+				dma_desc->status.b.ioc = 1;
+				dma_desc->status.b.sp =
+				    (ep->xfer_len == ep->maxpacket) ? 0 : 1;
+				dma_desc->status.b.bytes = ep->xfer_len;
+				dma_desc->buf = ep->dma_addr;
+				dma_desc->status.b.sts = 0;
+				dma_desc->status.b.bs = BS_HOST_READY;
+
+				/** DIEPDMA0 Register write */
+				DWC_WRITE_REG32(&in_regs->diepdma,
+						core_if->
+						dev_if->dma_in_desc_addr);
+			}
+		} else {
+			DWC_WRITE_REG32(&in_regs->dieptsiz, deptsiz.d32);
+		}
+
+		if (!core_if->core_params->en_multiple_tx_fifo && core_if->dma_enable)
+			depctl.b.nextep = core_if->nextep_seq[ep->num];
+		/* EP enable, IN data in FIFO */
+		depctl.b.cnak = 1;
+		depctl.b.epena = 1;
+		DWC_WRITE_REG32(&in_regs->diepctl, depctl.d32);
+
+		/**
+		 * Enable the Non-Periodic Tx FIFO empty interrupt, the
+		 * data will be written into the fifo by the ISR.
+		 */
+		if (!core_if->dma_enable) {
+			if (core_if->en_multiple_tx_fifo == 0) {
+				intr_mask.b.nptxfempty = 1;
+				DWC_MODIFY_REG32(&core_if->
+						 core_global_regs->gintmsk,
+						 intr_mask.d32, intr_mask.d32);
+			} else {
+				/* Enable the Tx FIFO Empty Interrupt for this EP */
+				if (ep->xfer_len > 0) {
+					uint32_t fifoemptymsk = 0;
+					fifoemptymsk |= 1 << ep->num;
+					DWC_MODIFY_REG32(&core_if->
+							 dev_if->dev_global_regs->dtknqr4_fifoemptymsk,
+							 0, fifoemptymsk);
+				}
+			}
+		}
+	} else {
+		/* OUT endpoint */
+		dwc_otg_dev_out_ep_regs_t *out_regs =
+		    core_if->dev_if->out_ep_regs[0];
+
+		depctl.d32 = DWC_READ_REG32(&out_regs->doepctl);
+		deptsiz.d32 = DWC_READ_REG32(&out_regs->doeptsiz);
+
+		/* Program the transfer size and packet count as follows:
+		 *      xfersize = N * (maxpacket + 4 - (maxpacket % 4))
+		 *      pktcnt = N                                                                                      */
+		/* Zero Length Packet */
+		deptsiz.b.xfersize = ep->maxpacket;
+		deptsiz.b.pktcnt = 1;
+		if (core_if->snpsid >= OTG_CORE_REV_3_00a)
+			deptsiz.b.supcnt = 3;
+
+		DWC_DEBUGPL(DBG_PCDV, "len=%d  xfersize=%d pktcnt=%d\n",
+			    ep->xfer_len, deptsiz.b.xfersize, deptsiz.b.pktcnt);
+
+		if (core_if->dma_enable) {
+			if (!core_if->dma_desc_enable) {
+				DWC_WRITE_REG32(&out_regs->doeptsiz,
+						deptsiz.d32);
+
+				DWC_WRITE_REG32(&(out_regs->doepdma),
+						(uint32_t) ep->dma_addr);
+			} else {
+				dma_desc = core_if->dev_if->out_desc_addr;
+
+				/** DMA Descriptor Setup */
+				dma_desc->status.b.bs = BS_HOST_BUSY;
+				if (core_if->snpsid >= OTG_CORE_REV_3_00a) {
+					dma_desc->status.b.mtrf = 0;
+					dma_desc->status.b.sr = 0;
+				}
+				dma_desc->status.b.l = 1;
+				dma_desc->status.b.ioc = 1;
+				dma_desc->status.b.bytes = ep->maxpacket;
+				dma_desc->buf = ep->dma_addr;
+				dma_desc->status.b.sts = 0;
+				dma_desc->status.b.bs = BS_HOST_READY;
+
+				/** DOEPDMA0 Register write */
+				DWC_WRITE_REG32(&out_regs->doepdma,
+						core_if->dev_if->
+						dma_out_desc_addr);
+			}
+		} else {
+			DWC_WRITE_REG32(&out_regs->doeptsiz, deptsiz.d32);
+		}
+
+		/* EP enable */
+		depctl.b.cnak = 1;
+		depctl.b.epena = 1;
+		DWC_WRITE_REG32(&(out_regs->doepctl), depctl.d32);
+	}
+}
+
+/**
+ * This function continues control IN transfers started by
+ * dwc_otg_ep0_start_transfer, when the transfer does not fit in a
+ * single packet.  NOTE: The DIEPCTL0/DOEPCTL0 registers only have one
+ * bit for the packet count.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param ep The EP0 data.
+ */
+void dwc_otg_ep0_continue_transfer(dwc_otg_core_if_t * core_if, dwc_ep_t * ep)
+{
+	depctl_data_t depctl;
+	deptsiz0_data_t deptsiz;
+	gintmsk_data_t intr_mask = {.d32 = 0 };
+	dwc_otg_dev_dma_desc_t *dma_desc;
+
+	if (ep->is_in == 1) {
+		dwc_otg_dev_in_ep_regs_t *in_regs =
+		    core_if->dev_if->in_ep_regs[0];
+		gnptxsts_data_t tx_status = {.d32 = 0 };
+
+		tx_status.d32 =
+		    DWC_READ_REG32(&core_if->core_global_regs->gnptxsts);
+		/** @todo Should there be check for room in the Tx
+		 * Status Queue.  If not remove the code above this comment. */
+
+		depctl.d32 = DWC_READ_REG32(&in_regs->diepctl);
+		deptsiz.d32 = DWC_READ_REG32(&in_regs->dieptsiz);
+
+		/* Program the transfer size and packet count
+		 *      as follows: xfersize = N * maxpacket +
+		 *      short_packet pktcnt = N + (short_packet
+		 *      exist ? 1 : 0)
+		 */
+
+		if (core_if->dma_desc_enable == 0) {
+			deptsiz.b.xfersize =
+			    (ep->total_len - ep->xfer_count) >
+			    ep->maxpacket ? ep->maxpacket : (ep->total_len -
+							     ep->xfer_count);
+			deptsiz.b.pktcnt = 1;
+			if (core_if->dma_enable == 0) {
+				ep->xfer_len += deptsiz.b.xfersize;
+			} else {
+				ep->xfer_len = deptsiz.b.xfersize;
+			}
+			DWC_WRITE_REG32(&in_regs->dieptsiz, deptsiz.d32);
+		} else {
+			ep->xfer_len =
+			    (ep->total_len - ep->xfer_count) >
+			    ep->maxpacket ? ep->maxpacket : (ep->total_len -
+							     ep->xfer_count);
+
+			dma_desc = core_if->dev_if->in_desc_addr;
+
+			/** DMA Descriptor Setup */
+			dma_desc->status.b.bs = BS_HOST_BUSY;
+			dma_desc->status.b.l = 1;
+			dma_desc->status.b.ioc = 1;
+			dma_desc->status.b.sp =
+			    (ep->xfer_len == ep->maxpacket) ? 0 : 1;
+			dma_desc->status.b.bytes = ep->xfer_len;
+			dma_desc->buf = ep->dma_addr;
+			dma_desc->status.b.sts = 0;
+			dma_desc->status.b.bs = BS_HOST_READY;
+
+			/** DIEPDMA0 Register write */
+			DWC_WRITE_REG32(&in_regs->diepdma,
+					core_if->dev_if->dma_in_desc_addr);
+		}
+
+		DWC_DEBUGPL(DBG_PCDV,
+			    "IN len=%d  xfersize=%d pktcnt=%d [%08x]\n",
+			    ep->xfer_len, deptsiz.b.xfersize, deptsiz.b.pktcnt,
+			    deptsiz.d32);
+
+		/* Write the DMA register */
+		if (core_if->hwcfg2.b.architecture == DWC_INT_DMA_ARCH) {
+			if (core_if->dma_desc_enable == 0)
+				DWC_WRITE_REG32(&(in_regs->diepdma),
+						(uint32_t) ep->dma_addr);
+		}
+		if (!core_if->core_params->en_multiple_tx_fifo && core_if->dma_enable)
+			depctl.b.nextep = core_if->nextep_seq[ep->num];
+		/* EP enable, IN data in FIFO */
+		depctl.b.cnak = 1;
+		depctl.b.epena = 1;
+		DWC_WRITE_REG32(&in_regs->diepctl, depctl.d32);
+
+		/**
+		 * Enable the Non-Periodic Tx FIFO empty interrupt, the
+		 * data will be written into the fifo by the ISR.
+		 */
+		if (!core_if->dma_enable) {
+			if (core_if->en_multiple_tx_fifo == 0) {
+				/* First clear it from GINTSTS */
+				intr_mask.b.nptxfempty = 1;
+				DWC_MODIFY_REG32(&core_if->
+						 core_global_regs->gintmsk,
+						 intr_mask.d32, intr_mask.d32);
+
+			} else {
+				/* Enable the Tx FIFO Empty Interrupt for this EP */
+				if (ep->xfer_len > 0) {
+					uint32_t fifoemptymsk = 0;
+					fifoemptymsk |= 1 << ep->num;
+					DWC_MODIFY_REG32(&core_if->
+							 dev_if->dev_global_regs->dtknqr4_fifoemptymsk,
+							 0, fifoemptymsk);
+				}
+			}
+		}
+	} else {
+		dwc_otg_dev_out_ep_regs_t *out_regs =
+		    core_if->dev_if->out_ep_regs[0];
+
+		depctl.d32 = DWC_READ_REG32(&out_regs->doepctl);
+		deptsiz.d32 = DWC_READ_REG32(&out_regs->doeptsiz);
+
+		/* Program the transfer size and packet count
+		 *      as follows: xfersize = N * maxpacket +
+		 *      short_packet pktcnt = N + (short_packet
+		 *      exist ? 1 : 0)
+		 */
+		deptsiz.b.xfersize = ep->maxpacket;
+		deptsiz.b.pktcnt = 1;
+
+		if (core_if->dma_desc_enable == 0) {
+			DWC_WRITE_REG32(&out_regs->doeptsiz, deptsiz.d32);
+		} else {
+			dma_desc = core_if->dev_if->out_desc_addr;
+
+			/** DMA Descriptor Setup */
+			dma_desc->status.b.bs = BS_HOST_BUSY;
+			dma_desc->status.b.l = 1;
+			dma_desc->status.b.ioc = 1;
+			dma_desc->status.b.bytes = ep->maxpacket;
+			dma_desc->buf = ep->dma_addr;
+			dma_desc->status.b.sts = 0;
+			dma_desc->status.b.bs = BS_HOST_READY;
+
+			/** DOEPDMA0 Register write */
+			DWC_WRITE_REG32(&out_regs->doepdma,
+					core_if->dev_if->dma_out_desc_addr);
+		}
+
+		DWC_DEBUGPL(DBG_PCDV,
+			    "IN len=%d  xfersize=%d pktcnt=%d [%08x]\n",
+			    ep->xfer_len, deptsiz.b.xfersize, deptsiz.b.pktcnt,
+			    deptsiz.d32);
+
+		/* Write the DMA register */
+		if (core_if->hwcfg2.b.architecture == DWC_INT_DMA_ARCH) {
+			if (core_if->dma_desc_enable == 0)
+				DWC_WRITE_REG32(&(out_regs->doepdma),
+						(uint32_t) ep->dma_addr);
+
+		}
+
+		/* EP enable, IN data in FIFO */
+		depctl.b.cnak = 1;
+		depctl.b.epena = 1;
+		DWC_WRITE_REG32(&out_regs->doepctl, depctl.d32);
+
+	}
+}
+
+#ifdef DEBUG
+void dump_msg(const u8 * buf, unsigned int length)
+{
+	unsigned int start, num, i;
+	char line[52], *p;
+
+	if (length >= 512)
+		return;
+	start = 0;
+	while (length > 0) {
+		num = length < 16u ? length : 16u;
+		p = line;
+		for (i = 0; i < num; ++i) {
+			if (i == 8)
+				*p++ = ' ';
+			DWC_SPRINTF(p, " %02x", buf[i]);
+			p += 3;
+		}
+		*p = 0;
+		DWC_PRINTF("%6x: %s\n", start, line);
+		buf += num;
+		start += num;
+		length -= num;
+	}
+}
+#else
+static inline void dump_msg(const u8 * buf, unsigned int length)
+{
+}
+#endif
+
+/**
+ * This function writes a packet into the Tx FIFO associated with the
+ * EP. For non-periodic EPs the non-periodic Tx FIFO is written.  For
+ * periodic EPs the periodic Tx FIFO associated with the EP is written
+ * with all packets for the next micro-frame.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param ep The EP to write packet for.
+ * @param dma Indicates if DMA is being used.
+ */
+void dwc_otg_ep_write_packet(dwc_otg_core_if_t * core_if, dwc_ep_t * ep,
+			     int dma)
+{
+	/**
+	 * The buffer is padded to DWORD on a per packet basis in
+	 * slave/dma mode if the MPS is not DWORD aligned. The last
+	 * packet, if short, is also padded to a multiple of DWORD.
+	 *
+	 * ep->xfer_buff always starts DWORD aligned in memory and is a
+	 * multiple of DWORD in length
+	 *
+	 * ep->xfer_len can be any number of bytes
+	 *
+	 * ep->xfer_count is a multiple of ep->maxpacket until the last
+	 *	packet
+	 *
+	 * FIFO access is DWORD */
+
+	uint32_t i;
+	uint32_t byte_count;
+	uint32_t dword_count;
+	uint32_t *fifo;
+	uint32_t *data_buff = (uint32_t *) ep->xfer_buff;
+
+	DWC_DEBUGPL((DBG_PCDV | DBG_CILV), "%s(%p,%p)\n", __func__, core_if,
+		    ep);
+	if (ep->xfer_count >= ep->xfer_len) {
+		DWC_WARN("%s() No data for EP%d!!!\n", __func__, ep->num);
+		return;
+	}
+
+	/* Find the byte length of the packet either short packet or MPS */
+	if ((ep->xfer_len - ep->xfer_count) < ep->maxpacket) {
+		byte_count = ep->xfer_len - ep->xfer_count;
+	} else {
+		byte_count = ep->maxpacket;
+	}
+
+	/* Find the DWORD length, padded by extra bytes as neccessary if MPS
+	 * is not a multiple of DWORD */
+	dword_count = (byte_count + 3) / 4;
+
+#ifdef VERBOSE
+	dump_msg(ep->xfer_buff, byte_count);
+#endif
+
+	/**@todo NGS Where are the Periodic Tx FIFO addresses
+	 * intialized?	What should this be? */
+
+	fifo = core_if->data_fifo[ep->num];
+
+	DWC_DEBUGPL((DBG_PCDV | DBG_CILV), "fifo=%p buff=%p *p=%08x bc=%d\n",
+		    fifo, data_buff, *data_buff, byte_count);
+
+	if (!dma) {
+		for (i = 0; i < dword_count; i++, data_buff++) {
+			DWC_WRITE_REG32(fifo, *data_buff);
+		}
+	}
+
+	ep->xfer_count += byte_count;
+	ep->xfer_buff += byte_count;
+	ep->dma_addr += byte_count;
+}
+
+/**
+ * Set the EP STALL.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param ep The EP to set the stall on.
+ */
+void dwc_otg_ep_set_stall(dwc_otg_core_if_t * core_if, dwc_ep_t * ep)
+{
+	depctl_data_t depctl;
+	volatile uint32_t *depctl_addr;
+
+	DWC_DEBUGPL(DBG_PCD, "%s ep%d-%s\n", __func__, ep->num,
+		    (ep->is_in ? "IN" : "OUT"));
+
+	if (ep->is_in == 1) {
+		depctl_addr = &(core_if->dev_if->in_ep_regs[ep->num]->diepctl);
+		depctl.d32 = DWC_READ_REG32(depctl_addr);
+
+		/* set the disable and stall bits */
+		if (depctl.b.epena) {
+			depctl.b.epdis = 1;
+		}
+		depctl.b.stall = 1;
+		DWC_WRITE_REG32(depctl_addr, depctl.d32);
+	} else {
+		depctl_addr = &(core_if->dev_if->out_ep_regs[ep->num]->doepctl);
+		depctl.d32 = DWC_READ_REG32(depctl_addr);
+
+		/* set the stall bit */
+		depctl.b.stall = 1;
+		DWC_WRITE_REG32(depctl_addr, depctl.d32);
+	}
+
+	DWC_DEBUGPL(DBG_PCD, "DEPCTL=%0x\n", DWC_READ_REG32(depctl_addr));
+
+	return;
+}
+
+/**
+ * Clear the EP STALL.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param ep The EP to clear stall from.
+ */
+void dwc_otg_ep_clear_stall(dwc_otg_core_if_t * core_if, dwc_ep_t * ep)
+{
+	depctl_data_t depctl;
+	volatile uint32_t *depctl_addr;
+
+	DWC_DEBUGPL(DBG_PCD, "%s ep%d-%s\n", __func__, ep->num,
+		    (ep->is_in ? "IN" : "OUT"));
+
+	if (ep->is_in == 1) {
+		depctl_addr = &(core_if->dev_if->in_ep_regs[ep->num]->diepctl);
+	} else {
+		depctl_addr = &(core_if->dev_if->out_ep_regs[ep->num]->doepctl);
+	}
+
+	depctl.d32 = DWC_READ_REG32(depctl_addr);
+
+	/* clear the stall bits */
+	depctl.b.stall = 0;
+
+	/*
+	 * USB Spec 9.4.5: For endpoints using data toggle, regardless
+	 * of whether an endpoint has the Halt feature set, a
+	 * ClearFeature(ENDPOINT_HALT) request always results in the
+	 * data toggle being reinitialized to DATA0.
+	 */
+	if (ep->type == DWC_OTG_EP_TYPE_INTR ||
+	    ep->type == DWC_OTG_EP_TYPE_BULK) {
+		depctl.b.setd0pid = 1;	/* DATA0 */
+	}
+
+	DWC_WRITE_REG32(depctl_addr, depctl.d32);
+	DWC_DEBUGPL(DBG_PCD, "DEPCTL=%0x\n", DWC_READ_REG32(depctl_addr));
+	return;
+}
+
+/**
+ * This function reads a packet from the Rx FIFO into the destination
+ * buffer. To read SETUP data use dwc_otg_read_setup_packet.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param dest	  Destination buffer for the packet.
+ * @param bytes  Number of bytes to copy to the destination.
+ */
+void dwc_otg_read_packet(dwc_otg_core_if_t * core_if,
+			 uint8_t * dest, uint16_t bytes)
+{
+	int i;
+	int word_count = (bytes + 3) / 4;
+
+	volatile uint32_t *fifo = core_if->data_fifo[0];
+	uint32_t *data_buff = (uint32_t *) dest;
+
+	/**
+	 * @todo Account for the case where _dest is not dword aligned. This
+	 * requires reading data from the FIFO into a uint32_t temp buffer,
+	 * then moving it into the data buffer.
+	 */
+
+	DWC_DEBUGPL((DBG_PCDV | DBG_CILV), "%s(%p,%p,%d)\n", __func__,
+		    core_if, dest, bytes);
+
+	for (i = 0; i < word_count; i++, data_buff++) {
+		*data_buff = DWC_READ_REG32(fifo);
+	}
+
+	return;
+}
+
+/**
+ * This functions reads the device registers and prints them
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+void dwc_otg_dump_dev_registers(dwc_otg_core_if_t * core_if)
+{
+	int i;
+	volatile uint32_t *addr;
+
+	DWC_PRINTF("Device Global Registers\n");
+	addr = &core_if->dev_if->dev_global_regs->dcfg;
+	DWC_PRINTF("DCFG		 @0x%08lX : 0x%08X\n",
+		   (unsigned long)addr, DWC_READ_REG32(addr));
+	addr = &core_if->dev_if->dev_global_regs->dctl;
+	DWC_PRINTF("DCTL		 @0x%08lX : 0x%08X\n",
+		   (unsigned long)addr, DWC_READ_REG32(addr));
+	addr = &core_if->dev_if->dev_global_regs->dsts;
+	DWC_PRINTF("DSTS		 @0x%08lX : 0x%08X\n",
+		   (unsigned long)addr, DWC_READ_REG32(addr));
+	addr = &core_if->dev_if->dev_global_regs->diepmsk;
+	DWC_PRINTF("DIEPMSK	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->dev_if->dev_global_regs->doepmsk;
+	DWC_PRINTF("DOEPMSK	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->dev_if->dev_global_regs->daint;
+	DWC_PRINTF("DAINT	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->dev_if->dev_global_regs->daintmsk;
+	DWC_PRINTF("DAINTMSK	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->dev_if->dev_global_regs->dtknqr1;
+	DWC_PRINTF("DTKNQR1	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	if (core_if->hwcfg2.b.dev_token_q_depth > 6) {
+		addr = &core_if->dev_if->dev_global_regs->dtknqr2;
+		DWC_PRINTF("DTKNQR2	 @0x%08lX : 0x%08X\n",
+			   (unsigned long)addr, DWC_READ_REG32(addr));
+	}
+
+	addr = &core_if->dev_if->dev_global_regs->dvbusdis;
+	DWC_PRINTF("DVBUSID	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+
+	addr = &core_if->dev_if->dev_global_regs->dvbuspulse;
+	DWC_PRINTF("DVBUSPULSE	@0x%08lX : 0x%08X\n",
+		   (unsigned long)addr, DWC_READ_REG32(addr));
+
+	addr = &core_if->dev_if->dev_global_regs->dtknqr3_dthrctl;
+	DWC_PRINTF("DTKNQR3_DTHRCTL	 @0x%08lX : 0x%08X\n",
+		   (unsigned long)addr, DWC_READ_REG32(addr));
+
+	if (core_if->hwcfg2.b.dev_token_q_depth > 22) {
+		addr = &core_if->dev_if->dev_global_regs->dtknqr4_fifoemptymsk;
+		DWC_PRINTF("DTKNQR4	 @0x%08lX : 0x%08X\n",
+			   (unsigned long)addr, DWC_READ_REG32(addr));
+	}
+
+	addr = &core_if->dev_if->dev_global_regs->dtknqr4_fifoemptymsk;
+	DWC_PRINTF("FIFOEMPMSK	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+
+	if (core_if->hwcfg2.b.multi_proc_int) {
+
+		addr = &core_if->dev_if->dev_global_regs->deachint;
+		DWC_PRINTF("DEACHINT	 @0x%08lX : 0x%08X\n",
+			   (unsigned long)addr, DWC_READ_REG32(addr));
+		addr = &core_if->dev_if->dev_global_regs->deachintmsk;
+		DWC_PRINTF("DEACHINTMSK	 @0x%08lX : 0x%08X\n",
+			   (unsigned long)addr, DWC_READ_REG32(addr));
+
+		for (i = 0; i <= core_if->dev_if->num_in_eps; i++) {
+			addr =
+			    &core_if->dev_if->
+			    dev_global_regs->diepeachintmsk[i];
+			DWC_PRINTF("DIEPEACHINTMSK[%d]	 @0x%08lX : 0x%08X\n",
+				   i, (unsigned long)addr,
+				   DWC_READ_REG32(addr));
+		}
+
+		for (i = 0; i <= core_if->dev_if->num_out_eps; i++) {
+			addr =
+			    &core_if->dev_if->
+			    dev_global_regs->doepeachintmsk[i];
+			DWC_PRINTF("DOEPEACHINTMSK[%d]	 @0x%08lX : 0x%08X\n",
+				   i, (unsigned long)addr,
+				   DWC_READ_REG32(addr));
+		}
+	}
+
+	for (i = 0; i <= core_if->dev_if->num_in_eps; i++) {
+		DWC_PRINTF("Device IN EP %d Registers\n", i);
+		addr = &core_if->dev_if->in_ep_regs[i]->diepctl;
+		DWC_PRINTF("DIEPCTL	 @0x%08lX : 0x%08X\n",
+			   (unsigned long)addr, DWC_READ_REG32(addr));
+		addr = &core_if->dev_if->in_ep_regs[i]->diepint;
+		DWC_PRINTF("DIEPINT	 @0x%08lX : 0x%08X\n",
+			   (unsigned long)addr, DWC_READ_REG32(addr));
+		addr = &core_if->dev_if->in_ep_regs[i]->dieptsiz;
+		DWC_PRINTF("DIETSIZ	 @0x%08lX : 0x%08X\n",
+			   (unsigned long)addr, DWC_READ_REG32(addr));
+		addr = &core_if->dev_if->in_ep_regs[i]->diepdma;
+		DWC_PRINTF("DIEPDMA	 @0x%08lX : 0x%08X\n",
+			   (unsigned long)addr, DWC_READ_REG32(addr));
+		addr = &core_if->dev_if->in_ep_regs[i]->dtxfsts;
+		DWC_PRINTF("DTXFSTS	 @0x%08lX : 0x%08X\n",
+			   (unsigned long)addr, DWC_READ_REG32(addr));
+		addr = &core_if->dev_if->in_ep_regs[i]->diepdmab;
+		DWC_PRINTF("DIEPDMAB	 @0x%08lX : 0x%08X\n",
+			   (unsigned long)addr, 0 /*DWC_READ_REG32(addr) */ );
+	}
+
+	for (i = 0; i <= core_if->dev_if->num_out_eps; i++) {
+		DWC_PRINTF("Device OUT EP %d Registers\n", i);
+		addr = &core_if->dev_if->out_ep_regs[i]->doepctl;
+		DWC_PRINTF("DOEPCTL	 @0x%08lX : 0x%08X\n",
+			   (unsigned long)addr, DWC_READ_REG32(addr));
+		addr = &core_if->dev_if->out_ep_regs[i]->doepint;
+		DWC_PRINTF("DOEPINT	 @0x%08lX : 0x%08X\n",
+			   (unsigned long)addr, DWC_READ_REG32(addr));
+		addr = &core_if->dev_if->out_ep_regs[i]->doeptsiz;
+		DWC_PRINTF("DOETSIZ	 @0x%08lX : 0x%08X\n",
+			   (unsigned long)addr, DWC_READ_REG32(addr));
+		addr = &core_if->dev_if->out_ep_regs[i]->doepdma;
+		DWC_PRINTF("DOEPDMA	 @0x%08lX : 0x%08X\n",
+			   (unsigned long)addr, DWC_READ_REG32(addr));
+		if (core_if->dma_enable) {	/* Don't access this register in SLAVE mode */
+			addr = &core_if->dev_if->out_ep_regs[i]->doepdmab;
+			DWC_PRINTF("DOEPDMAB	 @0x%08lX : 0x%08X\n",
+				   (unsigned long)addr, DWC_READ_REG32(addr));
+		}
+
+	}
+}
+
+/**
+ * This functions reads the SPRAM and prints its content
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+void dwc_otg_dump_spram(dwc_otg_core_if_t * core_if)
+{
+	volatile uint8_t *addr, *start_addr, *end_addr;
+
+	DWC_PRINTF("SPRAM Data:\n");
+	start_addr = (void *)core_if->core_global_regs;
+	DWC_PRINTF("Base Address: 0x%8lX\n", (unsigned long)start_addr);
+	start_addr += 0x00028000;
+	end_addr = (void *)core_if->core_global_regs;
+	end_addr += 0x000280e0;
+
+	for (addr = start_addr; addr < end_addr; addr += 16) {
+		DWC_PRINTF
+		    ("0x%8lX:\t%2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X %2X\n",
+		     (unsigned long)addr, addr[0], addr[1], addr[2], addr[3],
+		     addr[4], addr[5], addr[6], addr[7], addr[8], addr[9],
+		     addr[10], addr[11], addr[12], addr[13], addr[14], addr[15]
+		    );
+	}
+
+	return;
+}
+
+/**
+ * This function reads the host registers and prints them
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+void dwc_otg_dump_host_registers(dwc_otg_core_if_t * core_if)
+{
+	int i;
+	volatile uint32_t *addr;
+
+	DWC_PRINTF("Host Global Registers\n");
+	addr = &core_if->host_if->host_global_regs->hcfg;
+	DWC_PRINTF("HCFG		 @0x%08lX : 0x%08X\n",
+		   (unsigned long)addr, DWC_READ_REG32(addr));
+	addr = &core_if->host_if->host_global_regs->hfir;
+	DWC_PRINTF("HFIR		 @0x%08lX : 0x%08X\n",
+		   (unsigned long)addr, DWC_READ_REG32(addr));
+	addr = &core_if->host_if->host_global_regs->hfnum;
+	DWC_PRINTF("HFNUM	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->host_if->host_global_regs->hptxsts;
+	DWC_PRINTF("HPTXSTS	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->host_if->host_global_regs->haint;
+	DWC_PRINTF("HAINT	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->host_if->host_global_regs->haintmsk;
+	DWC_PRINTF("HAINTMSK	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	if (core_if->dma_desc_enable) {
+		addr = &core_if->host_if->host_global_regs->hflbaddr;
+		DWC_PRINTF("HFLBADDR	 @0x%08lX : 0x%08X\n",
+			   (unsigned long)addr, DWC_READ_REG32(addr));
+	}
+
+	addr = core_if->host_if->hprt0;
+	DWC_PRINTF("HPRT0	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+
+	for (i = 0; i < core_if->core_params->host_channels; i++) {
+		DWC_PRINTF("Host Channel %d Specific Registers\n", i);
+		addr = &core_if->host_if->hc_regs[i]->hcchar;
+		DWC_PRINTF("HCCHAR	 @0x%08lX : 0x%08X\n",
+			   (unsigned long)addr, DWC_READ_REG32(addr));
+		addr = &core_if->host_if->hc_regs[i]->hcsplt;
+		DWC_PRINTF("HCSPLT	 @0x%08lX : 0x%08X\n",
+			   (unsigned long)addr, DWC_READ_REG32(addr));
+		addr = &core_if->host_if->hc_regs[i]->hcint;
+		DWC_PRINTF("HCINT	 @0x%08lX : 0x%08X\n",
+			   (unsigned long)addr, DWC_READ_REG32(addr));
+		addr = &core_if->host_if->hc_regs[i]->hcintmsk;
+		DWC_PRINTF("HCINTMSK	 @0x%08lX : 0x%08X\n",
+			   (unsigned long)addr, DWC_READ_REG32(addr));
+		addr = &core_if->host_if->hc_regs[i]->hctsiz;
+		DWC_PRINTF("HCTSIZ	 @0x%08lX : 0x%08X\n",
+			   (unsigned long)addr, DWC_READ_REG32(addr));
+		addr = &core_if->host_if->hc_regs[i]->hcdma;
+		DWC_PRINTF("HCDMA	 @0x%08lX : 0x%08X\n",
+			   (unsigned long)addr, DWC_READ_REG32(addr));
+		if (core_if->dma_desc_enable) {
+			addr = &core_if->host_if->hc_regs[i]->hcdmab;
+			DWC_PRINTF("HCDMAB	 @0x%08lX : 0x%08X\n",
+				   (unsigned long)addr, DWC_READ_REG32(addr));
+		}
+
+	}
+	return;
+}
+
+/**
+ * This function reads the core global registers and prints them
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+void dwc_otg_dump_global_registers(dwc_otg_core_if_t * core_if)
+{
+	int i, ep_num;
+	volatile uint32_t *addr;
+	char *txfsiz;
+
+	DWC_PRINTF("Core Global Registers\n");
+	addr = &core_if->core_global_regs->gotgctl;
+	DWC_PRINTF("GOTGCTL	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->gotgint;
+	DWC_PRINTF("GOTGINT	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->gahbcfg;
+	DWC_PRINTF("GAHBCFG	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->gusbcfg;
+	DWC_PRINTF("GUSBCFG	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->grstctl;
+	DWC_PRINTF("GRSTCTL	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->gintsts;
+	DWC_PRINTF("GINTSTS	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->gintmsk;
+	DWC_PRINTF("GINTMSK	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->grxstsr;
+	DWC_PRINTF("GRXSTSR	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->grxfsiz;
+	DWC_PRINTF("GRXFSIZ	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->gnptxfsiz;
+	DWC_PRINTF("GNPTXFSIZ @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->gnptxsts;
+	DWC_PRINTF("GNPTXSTS	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->gi2cctl;
+	DWC_PRINTF("GI2CCTL	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->gpvndctl;
+	DWC_PRINTF("GPVNDCTL	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->ggpio;
+	DWC_PRINTF("GGPIO	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->guid;
+	DWC_PRINTF("GUID		 @0x%08lX : 0x%08X\n",
+		   (unsigned long)addr, DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->gsnpsid;
+	DWC_PRINTF("GSNPSID	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->ghwcfg1;
+	DWC_PRINTF("GHWCFG1	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->ghwcfg2;
+	DWC_PRINTF("GHWCFG2	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->ghwcfg3;
+	DWC_PRINTF("GHWCFG3	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->ghwcfg4;
+	DWC_PRINTF("GHWCFG4	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->glpmcfg;
+	DWC_PRINTF("GLPMCFG	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->gpwrdn;
+	DWC_PRINTF("GPWRDN	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->gdfifocfg;
+	DWC_PRINTF("GDFIFOCFG	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+	addr = &core_if->core_global_regs->adpctl;
+	DWC_PRINTF("ADPCTL	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   dwc_otg_adp_read_reg(core_if));
+	addr = &core_if->core_global_regs->hptxfsiz;
+	DWC_PRINTF("HPTXFSIZ	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+
+	if (core_if->en_multiple_tx_fifo == 0) {
+		ep_num = core_if->hwcfg4.b.num_dev_perio_in_ep;
+		txfsiz = "DPTXFSIZ";
+	} else {
+		ep_num = core_if->hwcfg4.b.num_in_eps;
+		txfsiz = "DIENPTXF";
+	}
+	for (i = 0; i < ep_num; i++) {
+		addr = &core_if->core_global_regs->dtxfsiz[i];
+		DWC_PRINTF("%s[%d] @0x%08lX : 0x%08X\n", txfsiz, i + 1,
+			   (unsigned long)addr, DWC_READ_REG32(addr));
+	}
+	addr = core_if->pcgcctl;
+	DWC_PRINTF("PCGCCTL	 @0x%08lX : 0x%08X\n", (unsigned long)addr,
+		   DWC_READ_REG32(addr));
+}
+
+/**
+ * Flush a Tx FIFO.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param num Tx FIFO to flush.
+ */
+void dwc_otg_flush_tx_fifo(dwc_otg_core_if_t * core_if, const int num)
+{
+	dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs;
+	volatile grstctl_t greset = {.d32 = 0 };
+	int count = 0;
+
+	DWC_DEBUGPL((DBG_CIL | DBG_PCDV), "Flush Tx FIFO %d\n", num);
+
+	greset.b.txfflsh = 1;
+	greset.b.txfnum = num;
+	DWC_WRITE_REG32(&global_regs->grstctl, greset.d32);
+
+	do {
+		greset.d32 = DWC_READ_REG32(&global_regs->grstctl);
+		if (++count > 10000) {
+			DWC_WARN("%s() HANG! GRSTCTL=%0x GNPTXSTS=0x%08x\n",
+				 __func__, greset.d32,
+				 DWC_READ_REG32(&global_regs->gnptxsts));
+			break;
+		}
+		dwc_udelay(1);
+	} while (greset.b.txfflsh == 1);
+
+	/* Wait for 3 PHY Clocks */
+	dwc_udelay(1);
+}
+
+/**
+ * Flush Rx FIFO.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+void dwc_otg_flush_rx_fifo(dwc_otg_core_if_t * core_if)
+{
+	dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs;
+	volatile grstctl_t greset = {.d32 = 0 };
+	int count = 0;
+
+	DWC_DEBUGPL((DBG_CIL | DBG_PCDV), "%s\n", __func__);
+	/*
+	 *
+	 */
+	greset.b.rxfflsh = 1;
+	DWC_WRITE_REG32(&global_regs->grstctl, greset.d32);
+
+	do {
+		greset.d32 = DWC_READ_REG32(&global_regs->grstctl);
+		if (++count > 10000) {
+			DWC_WARN("%s() HANG! GRSTCTL=%0x\n", __func__,
+				 greset.d32);
+			break;
+		}
+		dwc_udelay(1);
+	} while (greset.b.rxfflsh == 1);
+
+	/* Wait for 3 PHY Clocks */
+	dwc_udelay(1);
+}
+
+/**
+ * Do core a soft reset of the core.  Be careful with this because it
+ * resets all the internal state machines of the core.
+ */
+void dwc_otg_core_reset(dwc_otg_core_if_t * core_if)
+{
+	dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs;
+	volatile grstctl_t greset = {.d32 = 0 };
+	int count = 0;
+
+	DWC_DEBUGPL(DBG_CILV, "%s\n", __func__);
+	/* Wait for AHB master IDLE state. */
+	do {
+		dwc_udelay(10);
+		greset.d32 = DWC_READ_REG32(&global_regs->grstctl);
+		if (++count > 100000) {
+			DWC_WARN("%s() HANG! AHB Idle GRSTCTL=%0x\n", __func__,
+				 greset.d32);
+			return;
+		}
+	}
+	while (greset.b.ahbidle == 0);
+
+	/* Core Soft Reset */
+	count = 0;
+	greset.b.csftrst = 1;
+	DWC_WRITE_REG32(&global_regs->grstctl, greset.d32);
+	do {
+		greset.d32 = DWC_READ_REG32(&global_regs->grstctl);
+		if (++count > 10000) {
+			DWC_WARN("%s() HANG! Soft Reset GRSTCTL=%0x\n",
+				 __func__, greset.d32);
+			break;
+		}
+		dwc_udelay(1);
+	}
+	while (greset.b.csftrst == 1);
+
+	/* Wait for 3 PHY Clocks */
+	dwc_mdelay(100);
+}
+
+uint8_t dwc_otg_is_device_mode(dwc_otg_core_if_t * _core_if)
+{
+	return (dwc_otg_mode(_core_if) != DWC_HOST_MODE);
+}
+
+uint8_t dwc_otg_is_host_mode(dwc_otg_core_if_t * _core_if)
+{
+	return (dwc_otg_mode(_core_if) == DWC_HOST_MODE);
+}
+
+/**
+ * Register HCD callbacks. The callbacks are used to start and stop
+ * the HCD for interrupt processing.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param cb the HCD callback structure.
+ * @param p pointer to be passed to callback function (usb_hcd*).
+ */
+void dwc_otg_cil_register_hcd_callbacks(dwc_otg_core_if_t * core_if,
+					dwc_otg_cil_callbacks_t * cb, void *p)
+{
+	core_if->hcd_cb = cb;
+	cb->p = p;
+}
+
+/**
+ * Register PCD callbacks. The callbacks are used to start and stop
+ * the PCD for interrupt processing.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param cb the PCD callback structure.
+ * @param p pointer to be passed to callback function (pcd*).
+ */
+void dwc_otg_cil_register_pcd_callbacks(dwc_otg_core_if_t * core_if,
+					dwc_otg_cil_callbacks_t * cb, void *p)
+{
+	core_if->pcd_cb = cb;
+	cb->p = p;
+}
+
+#ifdef DWC_EN_ISOC
+
+/**
+ * This function writes isoc data per 1 (micro)frame into tx fifo
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param ep The EP to start the transfer on.
+ *
+ */
+void write_isoc_frame_data(dwc_otg_core_if_t * core_if, dwc_ep_t * ep)
+{
+	dwc_otg_dev_in_ep_regs_t *ep_regs;
+	dtxfsts_data_t txstatus = {.d32 = 0 };
+	uint32_t len = 0;
+	uint32_t dwords;
+
+	ep->xfer_len = ep->data_per_frame;
+	ep->xfer_count = 0;
+
+	ep_regs = core_if->dev_if->in_ep_regs[ep->num];
+
+	len = ep->xfer_len - ep->xfer_count;
+
+	if (len > ep->maxpacket) {
+		len = ep->maxpacket;
+	}
+
+	dwords = (len + 3) / 4;
+
+	/* While there is space in the queue and space in the FIFO and
+	 * More data to tranfer, Write packets to the Tx FIFO */
+	txstatus.d32 =
+	    DWC_READ_REG32(&core_if->dev_if->in_ep_regs[ep->num]->dtxfsts);
+	DWC_DEBUGPL(DBG_PCDV, "b4 dtxfsts[%d]=0x%08x\n", ep->num, txstatus.d32);
+
+	while (txstatus.b.txfspcavail > dwords &&
+	       ep->xfer_count < ep->xfer_len && ep->xfer_len != 0) {
+		/* Write the FIFO */
+		dwc_otg_ep_write_packet(core_if, ep, 0);
+
+		len = ep->xfer_len - ep->xfer_count;
+		if (len > ep->maxpacket) {
+			len = ep->maxpacket;
+		}
+
+		dwords = (len + 3) / 4;
+		txstatus.d32 =
+		    DWC_READ_REG32(&core_if->dev_if->in_ep_regs[ep->num]->
+				   dtxfsts);
+		DWC_DEBUGPL(DBG_PCDV, "dtxfsts[%d]=0x%08x\n", ep->num,
+			    txstatus.d32);
+	}
+}
+
+/**
+ * This function initializes a descriptor chain for Isochronous transfer
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param ep The EP to start the transfer on.
+ *
+ */
+void dwc_otg_iso_ep_start_frm_transfer(dwc_otg_core_if_t * core_if,
+				       dwc_ep_t * ep)
+{
+	deptsiz_data_t deptsiz = {.d32 = 0 };
+	depctl_data_t depctl = {.d32 = 0 };
+	dsts_data_t dsts = {.d32 = 0 };
+	volatile uint32_t *addr;
+
+	if (ep->is_in) {
+		addr = &core_if->dev_if->in_ep_regs[ep->num]->diepctl;
+	} else {
+		addr = &core_if->dev_if->out_ep_regs[ep->num]->doepctl;
+	}
+
+	ep->xfer_len = ep->data_per_frame;
+	ep->xfer_count = 0;
+	ep->xfer_buff = ep->cur_pkt_addr;
+	ep->dma_addr = ep->cur_pkt_dma_addr;
+
+	if (ep->is_in) {
+		/* Program the transfer size and packet count
+		 *      as follows: xfersize = N * maxpacket +
+		 *      short_packet pktcnt = N + (short_packet
+		 *      exist ? 1 : 0)
+		 */
+		deptsiz.b.xfersize = ep->xfer_len;
+		deptsiz.b.pktcnt =
+		    (ep->xfer_len - 1 + ep->maxpacket) / ep->maxpacket;
+		deptsiz.b.mc = deptsiz.b.pktcnt;
+		DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[ep->num]->dieptsiz,
+				deptsiz.d32);
+
+		/* Write the DMA register */
+		if (core_if->dma_enable) {
+			DWC_WRITE_REG32(&
+					(core_if->dev_if->in_ep_regs[ep->num]->
+					 diepdma), (uint32_t) ep->dma_addr);
+		}
+	} else {
+		deptsiz.b.pktcnt =
+		    (ep->xfer_len + (ep->maxpacket - 1)) / ep->maxpacket;
+		deptsiz.b.xfersize = deptsiz.b.pktcnt * ep->maxpacket;
+
+		DWC_WRITE_REG32(&core_if->dev_if->
+				out_ep_regs[ep->num]->doeptsiz, deptsiz.d32);
+
+		if (core_if->dma_enable) {
+			DWC_WRITE_REG32(&
+					(core_if->dev_if->
+					 out_ep_regs[ep->num]->doepdma),
+					(uint32_t) ep->dma_addr);
+		}
+	}
+
+	/** Enable endpoint, clear nak  */
+
+	depctl.d32 = 0;
+	if (ep->bInterval == 1) {
+		dsts.d32 =
+		    DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts);
+		ep->next_frame = dsts.b.soffn + ep->bInterval;
+
+		if (ep->next_frame & 0x1) {
+			depctl.b.setd1pid = 1;
+		} else {
+			depctl.b.setd0pid = 1;
+		}
+	} else {
+		ep->next_frame += ep->bInterval;
+
+		if (ep->next_frame & 0x1) {
+			depctl.b.setd1pid = 1;
+		} else {
+			depctl.b.setd0pid = 1;
+		}
+	}
+	depctl.b.epena = 1;
+	depctl.b.cnak = 1;
+
+	DWC_MODIFY_REG32(addr, 0, depctl.d32);
+	depctl.d32 = DWC_READ_REG32(addr);
+
+	if (ep->is_in && core_if->dma_enable == 0) {
+		write_isoc_frame_data(core_if, ep);
+	}
+
+}
+#endif /* DWC_EN_ISOC */
+
+static void dwc_otg_set_uninitialized(int32_t * p, int size)
+{
+	int i;
+	for (i = 0; i < size; i++) {
+		p[i] = -1;
+	}
+}
+
+static int dwc_otg_param_initialized(int32_t val)
+{
+	return val != -1;
+}
+
+static int dwc_otg_setup_params(dwc_otg_core_if_t * core_if)
+{
+	int i;
+	core_if->core_params = DWC_ALLOC(sizeof(*core_if->core_params));
+	if (!core_if->core_params) {
+		return -DWC_E_NO_MEMORY;
+	}
+	dwc_otg_set_uninitialized((int32_t *) core_if->core_params,
+				  sizeof(*core_if->core_params) /
+				  sizeof(int32_t));
+	DWC_PRINTF("Setting default values for core params\n");
+	dwc_otg_set_param_otg_cap(core_if, dwc_param_otg_cap_default);
+	dwc_otg_set_param_dma_enable(core_if, dwc_param_dma_enable_default);
+	dwc_otg_set_param_dma_desc_enable(core_if,
+					  dwc_param_dma_desc_enable_default);
+	dwc_otg_set_param_opt(core_if, dwc_param_opt_default);
+	dwc_otg_set_param_dma_burst_size(core_if,
+					 dwc_param_dma_burst_size_default);
+	dwc_otg_set_param_host_support_fs_ls_low_power(core_if,
+						       dwc_param_host_support_fs_ls_low_power_default);
+	dwc_otg_set_param_enable_dynamic_fifo(core_if,
+					      dwc_param_enable_dynamic_fifo_default);
+	dwc_otg_set_param_data_fifo_size(core_if,
+					 dwc_param_data_fifo_size_default);
+	dwc_otg_set_param_dev_rx_fifo_size(core_if,
+					   dwc_param_dev_rx_fifo_size_default);
+	dwc_otg_set_param_dev_nperio_tx_fifo_size(core_if,
+						  dwc_param_dev_nperio_tx_fifo_size_default);
+	dwc_otg_set_param_host_rx_fifo_size(core_if,
+					    dwc_param_host_rx_fifo_size_default);
+	dwc_otg_set_param_host_nperio_tx_fifo_size(core_if,
+						   dwc_param_host_nperio_tx_fifo_size_default);
+	dwc_otg_set_param_host_perio_tx_fifo_size(core_if,
+						  dwc_param_host_perio_tx_fifo_size_default);
+	dwc_otg_set_param_max_transfer_size(core_if,
+					    dwc_param_max_transfer_size_default);
+	dwc_otg_set_param_max_packet_count(core_if,
+					   dwc_param_max_packet_count_default);
+	dwc_otg_set_param_host_channels(core_if,
+					dwc_param_host_channels_default);
+	dwc_otg_set_param_dev_endpoints(core_if,
+					dwc_param_dev_endpoints_default);
+	dwc_otg_set_param_phy_type(core_if, dwc_param_phy_type_default);
+	dwc_otg_set_param_speed(core_if, dwc_param_speed_default);
+	dwc_otg_set_param_host_ls_low_power_phy_clk(core_if,
+						    dwc_param_host_ls_low_power_phy_clk_default);
+	dwc_otg_set_param_phy_ulpi_ddr(core_if, dwc_param_phy_ulpi_ddr_default);
+	dwc_otg_set_param_phy_ulpi_ext_vbus(core_if,
+					    dwc_param_phy_ulpi_ext_vbus_default);
+	dwc_otg_set_param_phy_utmi_width(core_if,
+					 dwc_param_phy_utmi_width_default);
+	dwc_otg_set_param_ts_dline(core_if, dwc_param_ts_dline_default);
+	dwc_otg_set_param_i2c_enable(core_if, dwc_param_i2c_enable_default);
+	dwc_otg_set_param_ulpi_fs_ls(core_if, dwc_param_ulpi_fs_ls_default);
+	dwc_otg_set_param_en_multiple_tx_fifo(core_if,
+					      dwc_param_en_multiple_tx_fifo_default);
+	for (i = 0; i < 15; i++) {
+		dwc_otg_set_param_dev_perio_tx_fifo_size(core_if,
+							 dwc_param_dev_perio_tx_fifo_size_default,
+							 i);
+	}
+
+	for (i = 0; i < 15; i++) {
+		dwc_otg_set_param_dev_tx_fifo_size(core_if,
+						   dwc_param_dev_tx_fifo_size_default,
+						   i);
+	}
+	dwc_otg_set_param_thr_ctl(core_if, dwc_param_thr_ctl_default);
+	dwc_otg_set_param_mpi_enable(core_if, dwc_param_mpi_enable_default);
+	dwc_otg_set_param_pti_enable(core_if, dwc_param_pti_enable_default);
+	dwc_otg_set_param_lpm_enable(core_if, dwc_param_lpm_enable_default);
+	dwc_otg_set_param_ic_usb_cap(core_if, dwc_param_ic_usb_cap_default);
+	dwc_otg_set_param_tx_thr_length(core_if,
+					dwc_param_tx_thr_length_default);
+	dwc_otg_set_param_rx_thr_length(core_if,
+					dwc_param_rx_thr_length_default);
+	dwc_otg_set_param_ahb_thr_ratio(core_if,
+					dwc_param_ahb_thr_ratio_default);
+	dwc_otg_set_param_power_down(core_if, dwc_param_power_down_default);
+	dwc_otg_set_param_reload_ctl(core_if, dwc_param_reload_ctl_default);
+	dwc_otg_set_param_dev_out_nak(core_if, dwc_param_dev_out_nak_default);
+	dwc_otg_set_param_cont_on_bna(core_if, dwc_param_cont_on_bna_default);
+	dwc_otg_set_param_ahb_single(core_if, dwc_param_ahb_single_default);
+	dwc_otg_set_param_otg_ver(core_if, dwc_param_otg_ver_default);
+	dwc_otg_set_param_adp_enable(core_if, dwc_param_adp_enable_default);
+	DWC_PRINTF("Finished setting default values for core params\n");
+
+	return 0;
+}
+
+uint8_t dwc_otg_is_dma_enable(dwc_otg_core_if_t * core_if)
+{
+	return core_if->dma_enable;
+}
+
+/* Checks if the parameter is outside of its valid range of values */
+#define DWC_OTG_PARAM_TEST(_param_, _low_, _high_) \
+		(((_param_) < (_low_)) || \
+		((_param_) > (_high_)))
+
+/* Parameter access functions */
+int dwc_otg_set_param_otg_cap(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int valid;
+	int retval = 0;
+	if (DWC_OTG_PARAM_TEST(val, 0, 2)) {
+		DWC_WARN("Wrong value for otg_cap parameter\n");
+		DWC_WARN("otg_cap parameter must be 0,1 or 2\n");
+		retval = -DWC_E_INVALID;
+		goto out;
+	}
+
+	valid = 1;
+	switch (val) {
+	case DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE:
+		if (core_if->hwcfg2.b.op_mode !=
+		    DWC_HWCFG2_OP_MODE_HNP_SRP_CAPABLE_OTG)
+			valid = 0;
+		break;
+	case DWC_OTG_CAP_PARAM_SRP_ONLY_CAPABLE:
+		if ((core_if->hwcfg2.b.op_mode !=
+		     DWC_HWCFG2_OP_MODE_HNP_SRP_CAPABLE_OTG)
+		    && (core_if->hwcfg2.b.op_mode !=
+			DWC_HWCFG2_OP_MODE_SRP_ONLY_CAPABLE_OTG)
+		    && (core_if->hwcfg2.b.op_mode !=
+			DWC_HWCFG2_OP_MODE_SRP_CAPABLE_DEVICE)
+		    && (core_if->hwcfg2.b.op_mode !=
+			DWC_HWCFG2_OP_MODE_SRP_CAPABLE_HOST)) {
+			valid = 0;
+		}
+		break;
+	case DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE:
+		/* always valid */
+		break;
+	}
+	if (!valid) {
+		if (dwc_otg_param_initialized(core_if->core_params->otg_cap)) {
+			DWC_ERROR
+			    ("%d invalid for otg_cap paremter. Check HW configuration.\n",
+			     val);
+		}
+		val =
+		    (((core_if->hwcfg2.b.op_mode ==
+		       DWC_HWCFG2_OP_MODE_HNP_SRP_CAPABLE_OTG)
+		      || (core_if->hwcfg2.b.op_mode ==
+			  DWC_HWCFG2_OP_MODE_SRP_ONLY_CAPABLE_OTG)
+		      || (core_if->hwcfg2.b.op_mode ==
+			  DWC_HWCFG2_OP_MODE_SRP_CAPABLE_DEVICE)
+		      || (core_if->hwcfg2.b.op_mode ==
+			  DWC_HWCFG2_OP_MODE_SRP_CAPABLE_HOST)) ?
+		     DWC_OTG_CAP_PARAM_SRP_ONLY_CAPABLE :
+		     DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE);
+		retval = -DWC_E_INVALID;
+	}
+
+	core_if->core_params->otg_cap = val;
+out:
+	return retval;
+}
+
+int32_t dwc_otg_get_param_otg_cap(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->otg_cap;
+}
+
+int dwc_otg_set_param_opt(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("Wrong value for opt parameter\n");
+		return -DWC_E_INVALID;
+	}
+	core_if->core_params->opt = val;
+	return 0;
+}
+
+int32_t dwc_otg_get_param_opt(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->opt;
+}
+
+int dwc_otg_set_param_dma_enable(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("Wrong value for dma enable\n");
+		return -DWC_E_INVALID;
+	}
+
+	if ((val == 1) && (core_if->hwcfg2.b.architecture == 0)) {
+		if (dwc_otg_param_initialized(core_if->core_params->dma_enable)) {
+			DWC_ERROR
+			    ("%d invalid for dma_enable paremter. Check HW configuration.\n",
+			     val);
+		}
+		val = 0;
+		retval = -DWC_E_INVALID;
+	}
+
+	core_if->core_params->dma_enable = val;
+	if (val == 0) {
+		dwc_otg_set_param_dma_desc_enable(core_if, 0);
+	}
+	return retval;
+}
+
+int32_t dwc_otg_get_param_dma_enable(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->dma_enable;
+}
+
+int dwc_otg_set_param_dma_desc_enable(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("Wrong value for dma_enable\n");
+		DWC_WARN("dma_desc_enable must be 0 or 1\n");
+		return -DWC_E_INVALID;
+	}
+
+	if ((val == 1)
+	    && ((dwc_otg_get_param_dma_enable(core_if) == 0)
+		|| (core_if->hwcfg4.b.desc_dma == 0))) {
+		if (dwc_otg_param_initialized
+		    (core_if->core_params->dma_desc_enable)) {
+			DWC_ERROR
+			    ("%d invalid for dma_desc_enable paremter. Check HW configuration.\n",
+			     val);
+		}
+		val = 0;
+		retval = -DWC_E_INVALID;
+	}
+	core_if->core_params->dma_desc_enable = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_dma_desc_enable(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->dma_desc_enable;
+}
+
+int dwc_otg_set_param_host_support_fs_ls_low_power(dwc_otg_core_if_t * core_if,
+						   int32_t val)
+{
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("Wrong value for host_support_fs_low_power\n");
+		DWC_WARN("host_support_fs_low_power must be 0 or 1\n");
+		return -DWC_E_INVALID;
+	}
+	core_if->core_params->host_support_fs_ls_low_power = val;
+	return 0;
+}
+
+int32_t dwc_otg_get_param_host_support_fs_ls_low_power(dwc_otg_core_if_t *
+						       core_if)
+{
+	return core_if->core_params->host_support_fs_ls_low_power;
+}
+
+int dwc_otg_set_param_enable_dynamic_fifo(dwc_otg_core_if_t * core_if,
+					  int32_t val)
+{
+	int retval = 0;
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("Wrong value for enable_dynamic_fifo\n");
+		DWC_WARN("enable_dynamic_fifo must be 0 or 1\n");
+		return -DWC_E_INVALID;
+	}
+
+	if ((val == 1) && (core_if->hwcfg2.b.dynamic_fifo == 0)) {
+		if (dwc_otg_param_initialized
+		    (core_if->core_params->enable_dynamic_fifo)) {
+			DWC_ERROR
+			    ("%d invalid for enable_dynamic_fifo paremter. Check HW configuration.\n",
+			     val);
+		}
+		val = 0;
+		retval = -DWC_E_INVALID;
+	}
+	core_if->core_params->enable_dynamic_fifo = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_enable_dynamic_fifo(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->enable_dynamic_fifo;
+}
+
+int dwc_otg_set_param_data_fifo_size(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+	if (DWC_OTG_PARAM_TEST(val, 32, 32768)) {
+		DWC_WARN("Wrong value for data_fifo_size\n");
+		DWC_WARN("data_fifo_size must be 32-32768\n");
+		return -DWC_E_INVALID;
+	}
+
+	if (val > core_if->hwcfg3.b.dfifo_depth) {
+		if (dwc_otg_param_initialized
+		    (core_if->core_params->data_fifo_size)) {
+			DWC_ERROR
+			    ("%d invalid for data_fifo_size parameter. Check HW configuration.\n",
+			     val);
+		}
+		val = core_if->hwcfg3.b.dfifo_depth;
+		retval = -DWC_E_INVALID;
+	}
+
+	core_if->core_params->data_fifo_size = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_data_fifo_size(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->data_fifo_size;
+}
+
+int dwc_otg_set_param_dev_rx_fifo_size(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+	if (DWC_OTG_PARAM_TEST(val, 16, 32768)) {
+		DWC_WARN("Wrong value for dev_rx_fifo_size\n");
+		DWC_WARN("dev_rx_fifo_size must be 16-32768\n");
+		return -DWC_E_INVALID;
+	}
+
+	if (val > DWC_READ_REG32(&core_if->core_global_regs->grxfsiz)) {
+		if (dwc_otg_param_initialized(core_if->core_params->dev_rx_fifo_size)) {
+		DWC_WARN("%d invalid for dev_rx_fifo_size parameter\n", val);
+		}
+		val = DWC_READ_REG32(&core_if->core_global_regs->grxfsiz);
+		retval = -DWC_E_INVALID;
+	}
+
+	core_if->core_params->dev_rx_fifo_size = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_dev_rx_fifo_size(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->dev_rx_fifo_size;
+}
+
+int dwc_otg_set_param_dev_nperio_tx_fifo_size(dwc_otg_core_if_t * core_if,
+					      int32_t val)
+{
+	int retval = 0;
+
+	if (DWC_OTG_PARAM_TEST(val, 16, 32768)) {
+		DWC_WARN("Wrong value for dev_nperio_tx_fifo\n");
+		DWC_WARN("dev_nperio_tx_fifo must be 16-32768\n");
+		return -DWC_E_INVALID;
+	}
+
+	if (val > (DWC_READ_REG32(&core_if->core_global_regs->gnptxfsiz) >> 16)) {
+		if (dwc_otg_param_initialized
+		    (core_if->core_params->dev_nperio_tx_fifo_size)) {
+			DWC_ERROR
+			    ("%d invalid for dev_nperio_tx_fifo_size. Check HW configuration.\n",
+			     val);
+		}
+		val =
+		    (DWC_READ_REG32(&core_if->core_global_regs->gnptxfsiz) >>
+		     16);
+		retval = -DWC_E_INVALID;
+	}
+
+	core_if->core_params->dev_nperio_tx_fifo_size = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_dev_nperio_tx_fifo_size(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->dev_nperio_tx_fifo_size;
+}
+
+int dwc_otg_set_param_host_rx_fifo_size(dwc_otg_core_if_t * core_if,
+					int32_t val)
+{
+	int retval = 0;
+
+	if (DWC_OTG_PARAM_TEST(val, 16, 32768)) {
+		DWC_WARN("Wrong value for host_rx_fifo_size\n");
+		DWC_WARN("host_rx_fifo_size must be 16-32768\n");
+		return -DWC_E_INVALID;
+	}
+
+	if (val > DWC_READ_REG32(&core_if->core_global_regs->grxfsiz)) {
+		if (dwc_otg_param_initialized
+		    (core_if->core_params->host_rx_fifo_size)) {
+			DWC_ERROR
+			    ("%d invalid for host_rx_fifo_size. Check HW configuration.\n",
+			     val);
+		}
+		val = DWC_READ_REG32(&core_if->core_global_regs->grxfsiz);
+		retval = -DWC_E_INVALID;
+	}
+
+	core_if->core_params->host_rx_fifo_size = val;
+	return retval;
+
+}
+
+int32_t dwc_otg_get_param_host_rx_fifo_size(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->host_rx_fifo_size;
+}
+
+int dwc_otg_set_param_host_nperio_tx_fifo_size(dwc_otg_core_if_t * core_if,
+					       int32_t val)
+{
+	int retval = 0;
+
+	if (DWC_OTG_PARAM_TEST(val, 16, 32768)) {
+		DWC_WARN("Wrong value for host_nperio_tx_fifo_size\n");
+		DWC_WARN("host_nperio_tx_fifo_size must be 16-32768\n");
+		return -DWC_E_INVALID;
+	}
+
+	if (val > (DWC_READ_REG32(&core_if->core_global_regs->gnptxfsiz) >> 16)) {
+		if (dwc_otg_param_initialized
+		    (core_if->core_params->host_nperio_tx_fifo_size)) {
+			DWC_ERROR
+			    ("%d invalid for host_nperio_tx_fifo_size. Check HW configuration.\n",
+			     val);
+		}
+		val =
+		    (DWC_READ_REG32(&core_if->core_global_regs->gnptxfsiz) >>
+		     16);
+		retval = -DWC_E_INVALID;
+	}
+
+	core_if->core_params->host_nperio_tx_fifo_size = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_host_nperio_tx_fifo_size(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->host_nperio_tx_fifo_size;
+}
+
+int dwc_otg_set_param_host_perio_tx_fifo_size(dwc_otg_core_if_t * core_if,
+					      int32_t val)
+{
+	int retval = 0;
+	if (DWC_OTG_PARAM_TEST(val, 16, 32768)) {
+		DWC_WARN("Wrong value for host_perio_tx_fifo_size\n");
+		DWC_WARN("host_perio_tx_fifo_size must be 16-32768\n");
+		return -DWC_E_INVALID;
+	}
+
+	if (val > ((core_if->hptxfsiz.d32) >> 16)) {
+		if (dwc_otg_param_initialized
+		    (core_if->core_params->host_perio_tx_fifo_size)) {
+			DWC_ERROR
+			    ("%d invalid for host_perio_tx_fifo_size. Check HW configuration.\n",
+			     val);
+		}
+		val = (core_if->hptxfsiz.d32) >> 16;
+		retval = -DWC_E_INVALID;
+	}
+
+	core_if->core_params->host_perio_tx_fifo_size = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_host_perio_tx_fifo_size(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->host_perio_tx_fifo_size;
+}
+
+int dwc_otg_set_param_max_transfer_size(dwc_otg_core_if_t * core_if,
+					int32_t val)
+{
+	int retval = 0;
+
+	if (DWC_OTG_PARAM_TEST(val, 2047, 524288)) {
+		DWC_WARN("Wrong value for max_transfer_size\n");
+		DWC_WARN("max_transfer_size must be 2047-524288\n");
+		return -DWC_E_INVALID;
+	}
+
+	if (val >= (1 << (core_if->hwcfg3.b.xfer_size_cntr_width + 11))) {
+		if (dwc_otg_param_initialized
+		    (core_if->core_params->max_transfer_size)) {
+			DWC_ERROR
+			    ("%d invalid for max_transfer_size. Check HW configuration.\n",
+			     val);
+		}
+		val =
+		    ((1 << (core_if->hwcfg3.b.packet_size_cntr_width + 11)) -
+		     1);
+		retval = -DWC_E_INVALID;
+	}
+
+	core_if->core_params->max_transfer_size = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_max_transfer_size(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->max_transfer_size;
+}
+
+int dwc_otg_set_param_max_packet_count(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+
+	if (DWC_OTG_PARAM_TEST(val, 15, 511)) {
+		DWC_WARN("Wrong value for max_packet_count\n");
+		DWC_WARN("max_packet_count must be 15-511\n");
+		return -DWC_E_INVALID;
+	}
+
+	if (val > (1 << (core_if->hwcfg3.b.packet_size_cntr_width + 4))) {
+		if (dwc_otg_param_initialized
+		    (core_if->core_params->max_packet_count)) {
+			DWC_ERROR
+			    ("%d invalid for max_packet_count. Check HW configuration.\n",
+			     val);
+		}
+		val =
+		    ((1 << (core_if->hwcfg3.b.packet_size_cntr_width + 4)) - 1);
+		retval = -DWC_E_INVALID;
+	}
+
+	core_if->core_params->max_packet_count = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_max_packet_count(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->max_packet_count;
+}
+
+int dwc_otg_set_param_host_channels(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+
+	if (DWC_OTG_PARAM_TEST(val, 1, 16)) {
+		DWC_WARN("Wrong value for host_channels\n");
+		DWC_WARN("host_channels must be 1-16\n");
+		return -DWC_E_INVALID;
+	}
+
+	if (val > (core_if->hwcfg2.b.num_host_chan + 1)) {
+		if (dwc_otg_param_initialized
+		    (core_if->core_params->host_channels)) {
+			DWC_ERROR
+			    ("%d invalid for host_channels. Check HW configurations.\n",
+			     val);
+		}
+		val = (core_if->hwcfg2.b.num_host_chan + 1);
+		retval = -DWC_E_INVALID;
+	}
+
+	core_if->core_params->host_channels = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_host_channels(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->host_channels;
+}
+
+int dwc_otg_set_param_dev_endpoints(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+
+	if (DWC_OTG_PARAM_TEST(val, 1, 15)) {
+		DWC_WARN("Wrong value for dev_endpoints\n");
+		DWC_WARN("dev_endpoints must be 1-15\n");
+		return -DWC_E_INVALID;
+	}
+
+	if (val > (core_if->hwcfg2.b.num_dev_ep)) {
+		if (dwc_otg_param_initialized
+		    (core_if->core_params->dev_endpoints)) {
+			DWC_ERROR
+			    ("%d invalid for dev_endpoints. Check HW configurations.\n",
+			     val);
+		}
+		val = core_if->hwcfg2.b.num_dev_ep;
+		retval = -DWC_E_INVALID;
+	}
+
+	core_if->core_params->dev_endpoints = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_dev_endpoints(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->dev_endpoints;
+}
+
+int dwc_otg_set_param_phy_type(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+	int valid = 0;
+
+	if (DWC_OTG_PARAM_TEST(val, 0, 2)) {
+		DWC_WARN("Wrong value for phy_type\n");
+		DWC_WARN("phy_type must be 0,1 or 2\n");
+		return -DWC_E_INVALID;
+	}
+#ifndef NO_FS_PHY_HW_CHECKS
+	if ((val == DWC_PHY_TYPE_PARAM_UTMI) &&
+	    ((core_if->hwcfg2.b.hs_phy_type == 1) ||
+	     (core_if->hwcfg2.b.hs_phy_type == 3))) {
+		valid = 1;
+	} else if ((val == DWC_PHY_TYPE_PARAM_ULPI) &&
+		   ((core_if->hwcfg2.b.hs_phy_type == 2) ||
+		    (core_if->hwcfg2.b.hs_phy_type == 3))) {
+		valid = 1;
+	} else if ((val == DWC_PHY_TYPE_PARAM_FS) &&
+		   (core_if->hwcfg2.b.fs_phy_type == 1)) {
+		valid = 1;
+	}
+	if (!valid) {
+		if (dwc_otg_param_initialized(core_if->core_params->phy_type)) {
+			DWC_ERROR
+			    ("%d invalid for phy_type. Check HW configurations.\n",
+			     val);
+		}
+		if (core_if->hwcfg2.b.hs_phy_type) {
+			if ((core_if->hwcfg2.b.hs_phy_type == 3) ||
+			    (core_if->hwcfg2.b.hs_phy_type == 1)) {
+				val = DWC_PHY_TYPE_PARAM_UTMI;
+			} else {
+				val = DWC_PHY_TYPE_PARAM_ULPI;
+			}
+		}
+		retval = -DWC_E_INVALID;
+	}
+#endif
+	core_if->core_params->phy_type = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_phy_type(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->phy_type;
+}
+
+int dwc_otg_set_param_speed(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("Wrong value for speed parameter\n");
+		DWC_WARN("max_speed parameter must be 0 or 1\n");
+		return -DWC_E_INVALID;
+	}
+	if ((val == 0)
+	    && dwc_otg_get_param_phy_type(core_if) == DWC_PHY_TYPE_PARAM_FS) {
+		if (dwc_otg_param_initialized(core_if->core_params->speed)) {
+			DWC_ERROR
+			    ("%d invalid for speed paremter. Check HW configuration.\n",
+			     val);
+		}
+		val =
+		    (dwc_otg_get_param_phy_type(core_if) ==
+		     DWC_PHY_TYPE_PARAM_FS ? 1 : 0);
+		retval = -DWC_E_INVALID;
+	}
+	core_if->core_params->speed = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_speed(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->speed;
+}
+
+int dwc_otg_set_param_host_ls_low_power_phy_clk(dwc_otg_core_if_t * core_if,
+						int32_t val)
+{
+	int retval = 0;
+
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN
+		    ("Wrong value for host_ls_low_power_phy_clk parameter\n");
+		DWC_WARN("host_ls_low_power_phy_clk must be 0 or 1\n");
+		return -DWC_E_INVALID;
+	}
+
+	if ((val == DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ)
+	    && (dwc_otg_get_param_phy_type(core_if) == DWC_PHY_TYPE_PARAM_FS)) {
+		if (dwc_otg_param_initialized
+		    (core_if->core_params->host_ls_low_power_phy_clk)) {
+			DWC_ERROR
+			    ("%d invalid for host_ls_low_power_phy_clk. Check HW configuration.\n",
+			     val);
+		}
+		val =
+		    (dwc_otg_get_param_phy_type(core_if) ==
+		     DWC_PHY_TYPE_PARAM_FS) ?
+		    DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ :
+		    DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ;
+		retval = -DWC_E_INVALID;
+	}
+
+	core_if->core_params->host_ls_low_power_phy_clk = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_host_ls_low_power_phy_clk(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->host_ls_low_power_phy_clk;
+}
+
+int dwc_otg_set_param_phy_ulpi_ddr(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("Wrong value for phy_ulpi_ddr\n");
+		DWC_WARN("phy_upli_ddr must be 0 or 1\n");
+		return -DWC_E_INVALID;
+	}
+
+	core_if->core_params->phy_ulpi_ddr = val;
+	return 0;
+}
+
+int32_t dwc_otg_get_param_phy_ulpi_ddr(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->phy_ulpi_ddr;
+}
+
+int dwc_otg_set_param_phy_ulpi_ext_vbus(dwc_otg_core_if_t * core_if,
+					int32_t val)
+{
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("Wrong valaue for phy_ulpi_ext_vbus\n");
+		DWC_WARN("phy_ulpi_ext_vbus must be 0 or 1\n");
+		return -DWC_E_INVALID;
+	}
+
+	core_if->core_params->phy_ulpi_ext_vbus = val;
+	return 0;
+}
+
+int32_t dwc_otg_get_param_phy_ulpi_ext_vbus(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->phy_ulpi_ext_vbus;
+}
+
+int dwc_otg_set_param_phy_utmi_width(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	if (DWC_OTG_PARAM_TEST(val, 8, 8) && DWC_OTG_PARAM_TEST(val, 16, 16)) {
+		DWC_WARN("Wrong valaue for phy_utmi_width\n");
+		DWC_WARN("phy_utmi_width must be 8 or 16\n");
+		return -DWC_E_INVALID;
+	}
+
+	core_if->core_params->phy_utmi_width = val;
+	return 0;
+}
+
+int32_t dwc_otg_get_param_phy_utmi_width(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->phy_utmi_width;
+}
+
+int dwc_otg_set_param_ulpi_fs_ls(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("Wrong valaue for ulpi_fs_ls\n");
+		DWC_WARN("ulpi_fs_ls must be 0 or 1\n");
+		return -DWC_E_INVALID;
+	}
+
+	core_if->core_params->ulpi_fs_ls = val;
+	return 0;
+}
+
+int32_t dwc_otg_get_param_ulpi_fs_ls(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->ulpi_fs_ls;
+}
+
+int dwc_otg_set_param_ts_dline(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("Wrong valaue for ts_dline\n");
+		DWC_WARN("ts_dline must be 0 or 1\n");
+		return -DWC_E_INVALID;
+	}
+
+	core_if->core_params->ts_dline = val;
+	return 0;
+}
+
+int32_t dwc_otg_get_param_ts_dline(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->ts_dline;
+}
+
+int dwc_otg_set_param_i2c_enable(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("Wrong valaue for i2c_enable\n");
+		DWC_WARN("i2c_enable must be 0 or 1\n");
+		return -DWC_E_INVALID;
+	}
+#ifndef NO_FS_PHY_HW_CHECK
+	if (val == 1 && core_if->hwcfg3.b.i2c == 0) {
+		if (dwc_otg_param_initialized(core_if->core_params->i2c_enable)) {
+			DWC_ERROR
+			    ("%d invalid for i2c_enable. Check HW configuration.\n",
+			     val);
+		}
+		val = 0;
+		retval = -DWC_E_INVALID;
+	}
+#endif
+
+	core_if->core_params->i2c_enable = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_i2c_enable(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->i2c_enable;
+}
+
+int dwc_otg_set_param_dev_perio_tx_fifo_size(dwc_otg_core_if_t * core_if,
+					     int32_t val, int fifo_num)
+{
+	int retval = 0;
+
+	if (DWC_OTG_PARAM_TEST(val, 4, 768)) {
+		DWC_WARN("Wrong value for dev_perio_tx_fifo_size\n");
+		DWC_WARN("dev_perio_tx_fifo_size must be 4-768\n");
+		return -DWC_E_INVALID;
+	}
+
+	if (val >
+	    (DWC_READ_REG32(&core_if->core_global_regs->dtxfsiz[fifo_num]))) {
+		if (dwc_otg_param_initialized
+		    (core_if->core_params->dev_perio_tx_fifo_size[fifo_num])) {
+			DWC_ERROR
+			    ("`%d' invalid for parameter `dev_perio_fifo_size_%d'. Check HW configuration.\n",
+			     val, fifo_num);
+		}
+		val = (DWC_READ_REG32(&core_if->core_global_regs->dtxfsiz[fifo_num]));
+		retval = -DWC_E_INVALID;
+	}
+
+	core_if->core_params->dev_perio_tx_fifo_size[fifo_num] = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_dev_perio_tx_fifo_size(dwc_otg_core_if_t * core_if,
+						 int fifo_num)
+{
+	return core_if->core_params->dev_perio_tx_fifo_size[fifo_num];
+}
+
+int dwc_otg_set_param_en_multiple_tx_fifo(dwc_otg_core_if_t * core_if,
+					  int32_t val)
+{
+	int retval = 0;
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("Wrong valaue for en_multiple_tx_fifo,\n");
+		DWC_WARN("en_multiple_tx_fifo must be 0 or 1\n");
+		return -DWC_E_INVALID;
+	}
+
+	if (val == 1 && core_if->hwcfg4.b.ded_fifo_en == 0) {
+		if (dwc_otg_param_initialized
+		    (core_if->core_params->en_multiple_tx_fifo)) {
+			DWC_ERROR
+			    ("%d invalid for parameter en_multiple_tx_fifo. Check HW configuration.\n",
+			     val);
+		}
+		val = 0;
+		retval = -DWC_E_INVALID;
+	}
+
+	core_if->core_params->en_multiple_tx_fifo = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_en_multiple_tx_fifo(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->en_multiple_tx_fifo;
+}
+
+int dwc_otg_set_param_dev_tx_fifo_size(dwc_otg_core_if_t * core_if, int32_t val,
+				       int fifo_num)
+{
+	int retval = 0;
+
+	if (DWC_OTG_PARAM_TEST(val, 4, 768)) {
+		DWC_WARN("Wrong value for dev_tx_fifo_size\n");
+		DWC_WARN("dev_tx_fifo_size must be 4-768\n");
+		return -DWC_E_INVALID;
+	}
+
+	if (val >
+	    (DWC_READ_REG32(&core_if->core_global_regs->dtxfsiz[fifo_num]))) {
+		if (dwc_otg_param_initialized
+		    (core_if->core_params->dev_tx_fifo_size[fifo_num])) {
+			DWC_ERROR
+			    ("`%d' invalid for parameter `dev_tx_fifo_size_%d'. Check HW configuration.\n",
+			     val, fifo_num);
+		}
+		val = (DWC_READ_REG32(&core_if->core_global_regs->dtxfsiz[fifo_num]));
+		retval = -DWC_E_INVALID;
+	}
+
+	core_if->core_params->dev_tx_fifo_size[fifo_num] = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_dev_tx_fifo_size(dwc_otg_core_if_t * core_if,
+					   int fifo_num)
+{
+	return core_if->core_params->dev_tx_fifo_size[fifo_num];
+}
+
+int dwc_otg_set_param_thr_ctl(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+
+	if (DWC_OTG_PARAM_TEST(val, 0, 7)) {
+		DWC_WARN("Wrong value for thr_ctl\n");
+		DWC_WARN("thr_ctl must be 0-7\n");
+		return -DWC_E_INVALID;
+	}
+
+	if ((val != 0) &&
+	    (!dwc_otg_get_param_dma_enable(core_if) ||
+	     !core_if->hwcfg4.b.ded_fifo_en)) {
+		if (dwc_otg_param_initialized(core_if->core_params->thr_ctl)) {
+			DWC_ERROR
+			    ("%d invalid for parameter thr_ctl. Check HW configuration.\n",
+			     val);
+		}
+		val = 0;
+		retval = -DWC_E_INVALID;
+	}
+
+	core_if->core_params->thr_ctl = val;
+	return retval;
+}
+
+static int32_t dwc_otg_get_param_thr_ctl(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->thr_ctl;
+}
+
+int dwc_otg_set_param_lpm_enable(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("Wrong value for lpm_enable\n");
+		DWC_WARN("lpm_enable must be 0 or 1\n");
+		return -DWC_E_INVALID;
+	}
+
+	if (val && !core_if->hwcfg3.b.otg_lpm_en) {
+		if (dwc_otg_param_initialized(core_if->core_params->lpm_enable)) {
+			DWC_ERROR
+			    ("%d invalid for parameter lpm_enable. Check HW configuration.\n",
+			     val);
+		}
+		val = 0;
+		retval = -DWC_E_INVALID;
+	}
+
+	core_if->core_params->lpm_enable = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_lpm_enable(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->lpm_enable;
+}
+
+int dwc_otg_set_param_tx_thr_length(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	if (DWC_OTG_PARAM_TEST(val, 8, 128)) {
+		DWC_WARN("Wrong valaue for tx_thr_length\n");
+		DWC_WARN("tx_thr_length must be 8 - 128\n");
+		return -DWC_E_INVALID;
+	}
+
+	core_if->core_params->tx_thr_length = val;
+	return 0;
+}
+
+static int32_t dwc_otg_get_param_tx_thr_length(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->tx_thr_length;
+}
+
+int dwc_otg_set_param_rx_thr_length(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	if (DWC_OTG_PARAM_TEST(val, 8, 128)) {
+		DWC_WARN("Wrong valaue for rx_thr_length\n");
+		DWC_WARN("rx_thr_length must be 8 - 128\n");
+		return -DWC_E_INVALID;
+	}
+
+	core_if->core_params->rx_thr_length = val;
+	return 0;
+}
+
+int dwc_otg_set_param_dma_burst_size(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	if (DWC_OTG_PARAM_TEST(val, 1, 1) &&
+	    DWC_OTG_PARAM_TEST(val, 4, 4) &&
+	    DWC_OTG_PARAM_TEST(val, 8, 8) &&
+	    DWC_OTG_PARAM_TEST(val, 16, 16) &&
+	    DWC_OTG_PARAM_TEST(val, 32, 32) &&
+	    DWC_OTG_PARAM_TEST(val, 64, 64) &&
+	    DWC_OTG_PARAM_TEST(val, 128, 128) &&
+	    DWC_OTG_PARAM_TEST(val, 256, 256)) {
+		DWC_WARN("`%d' invalid for parameter `dma_burst_size'\n", val);
+		return -DWC_E_INVALID;
+	}
+	core_if->core_params->dma_burst_size = val;
+	return 0;
+}
+
+int32_t dwc_otg_get_param_dma_burst_size(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->dma_burst_size;
+}
+
+int dwc_otg_set_param_pti_enable(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("`%d' invalid for parameter `pti_enable'\n", val);
+		return -DWC_E_INVALID;
+	}
+	if (val && (core_if->snpsid < OTG_CORE_REV_2_72a)) {
+		if (dwc_otg_param_initialized(core_if->core_params->pti_enable)) {
+			DWC_ERROR
+			    ("%d invalid for parameter pti_enable. Check HW configuration.\n",
+			     val);
+		}
+		retval = -DWC_E_INVALID;
+		val = 0;
+	}
+	core_if->core_params->pti_enable = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_pti_enable(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->pti_enable;
+}
+
+int dwc_otg_set_param_mpi_enable(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("`%d' invalid for parameter `mpi_enable'\n", val);
+		return -DWC_E_INVALID;
+	}
+	if (val && (core_if->hwcfg2.b.multi_proc_int == 0)) {
+		if (dwc_otg_param_initialized(core_if->core_params->mpi_enable)) {
+			DWC_ERROR
+			    ("%d invalid for parameter mpi_enable. Check HW configuration.\n",
+			     val);
+		}
+		retval = -DWC_E_INVALID;
+		val = 0;
+	}
+	core_if->core_params->mpi_enable = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_mpi_enable(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->mpi_enable;
+}
+
+int dwc_otg_set_param_adp_enable(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("`%d' invalid for parameter `adp_enable'\n", val);
+		return -DWC_E_INVALID;
+	}
+	if (val && (core_if->hwcfg3.b.adp_supp == 0)) {
+		if (dwc_otg_param_initialized
+		    (core_if->core_params->adp_supp_enable)) {
+			DWC_ERROR
+			    ("%d invalid for parameter adp_enable. Check HW configuration.\n",
+			     val);
+		}
+		retval = -DWC_E_INVALID;
+		val = 0;
+	}
+	core_if->core_params->adp_supp_enable = val;
+	/*Set OTG version 2.0 in case of enabling ADP*/
+	if (val)
+		dwc_otg_set_param_otg_ver(core_if, 1);
+
+	return retval;
+}
+
+int32_t dwc_otg_get_param_adp_enable(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->adp_supp_enable;
+}
+
+int dwc_otg_set_param_ic_usb_cap(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("`%d' invalid for parameter `ic_usb_cap'\n", val);
+		DWC_WARN("ic_usb_cap must be 0 or 1\n");
+		return -DWC_E_INVALID;
+	}
+
+	if (val && (core_if->hwcfg2.b.otg_enable_ic_usb == 0)) {
+		if (dwc_otg_param_initialized(core_if->core_params->ic_usb_cap)) {
+			DWC_ERROR
+			    ("%d invalid for parameter ic_usb_cap. Check HW configuration.\n",
+			     val);
+		}
+		retval = -DWC_E_INVALID;
+		val = 0;
+	}
+	core_if->core_params->ic_usb_cap = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_ic_usb_cap(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->ic_usb_cap;
+}
+
+int dwc_otg_set_param_ahb_thr_ratio(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+	int valid = 1;
+
+	if (DWC_OTG_PARAM_TEST(val, 0, 3)) {
+		DWC_WARN("`%d' invalid for parameter `ahb_thr_ratio'\n", val);
+		DWC_WARN("ahb_thr_ratio must be 0 - 3\n");
+		return -DWC_E_INVALID;
+	}
+
+	if (val
+	    && (core_if->snpsid < OTG_CORE_REV_2_81a
+		|| !dwc_otg_get_param_thr_ctl(core_if))) {
+		valid = 0;
+	} else if (val
+		   && ((dwc_otg_get_param_tx_thr_length(core_if) / (1 << val)) <
+		       4)) {
+		valid = 0;
+	}
+	if (valid == 0) {
+		if (dwc_otg_param_initialized
+		    (core_if->core_params->ahb_thr_ratio)) {
+			DWC_ERROR
+			    ("%d invalid for parameter ahb_thr_ratio. Check HW configuration.\n",
+			     val);
+		}
+		retval = -DWC_E_INVALID;
+		val = 0;
+	}
+
+	core_if->core_params->ahb_thr_ratio = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_ahb_thr_ratio(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->ahb_thr_ratio;
+}
+
+int dwc_otg_set_param_power_down(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+	int valid = 1;
+	hwcfg4_data_t hwcfg4 = {.d32 = 0 };
+	hwcfg4.d32 = DWC_READ_REG32(&core_if->core_global_regs->ghwcfg4);
+
+	if (DWC_OTG_PARAM_TEST(val, 0, 3)) {
+		DWC_WARN("`%d' invalid for parameter `power_down'\n", val);
+		DWC_WARN("power_down must be 0 - 2\n");
+		return -DWC_E_INVALID;
+	}
+
+	if ((val == 2) && (core_if->snpsid < OTG_CORE_REV_2_91a)) {
+		valid = 0;
+	}
+	if ((val == 3)
+	    && ((core_if->snpsid < OTG_CORE_REV_3_00a)
+		|| (hwcfg4.b.xhiber == 0))) {
+		valid = 0;
+	}
+	if (valid == 0) {
+		if (dwc_otg_param_initialized(core_if->core_params->power_down)) {
+			DWC_ERROR
+			    ("%d invalid for parameter power_down. Check HW configuration.\n",
+			     val);
+		}
+		retval = -DWC_E_INVALID;
+		val = 0;
+	}
+	core_if->core_params->power_down = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_power_down(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->power_down;
+}
+
+int dwc_otg_set_param_reload_ctl(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+	int valid = 1;
+
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("`%d' invalid for parameter `reload_ctl'\n", val);
+		DWC_WARN("reload_ctl must be 0 or 1\n");
+		return -DWC_E_INVALID;
+	}
+
+	if ((val == 1) && (core_if->snpsid < OTG_CORE_REV_2_92a)) {
+		valid = 0;
+	}
+	if (valid == 0) {
+		if (dwc_otg_param_initialized(core_if->core_params->reload_ctl)) {
+			DWC_ERROR("%d invalid for parameter reload_ctl."
+				  "Check HW configuration.\n", val);
+		}
+		retval = -DWC_E_INVALID;
+		val = 0;
+	}
+	core_if->core_params->reload_ctl = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_reload_ctl(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->reload_ctl;
+}
+
+int dwc_otg_set_param_dev_out_nak(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+	int valid = 1;
+
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("`%d' invalid for parameter `dev_out_nak'\n", val);
+		DWC_WARN("dev_out_nak must be 0 or 1\n");
+		return -DWC_E_INVALID;
+	}
+
+	if ((val == 1) && ((core_if->snpsid < OTG_CORE_REV_2_93a) ||
+		!(core_if->core_params->dma_desc_enable))) {
+		valid = 0;
+	}
+	if (valid == 0) {
+		if (dwc_otg_param_initialized(core_if->core_params->dev_out_nak)) {
+			DWC_ERROR("%d invalid for parameter dev_out_nak."
+				"Check HW configuration.\n", val);
+		}
+		retval = -DWC_E_INVALID;
+		val = 0;
+	}
+	core_if->core_params->dev_out_nak = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_dev_out_nak(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->dev_out_nak;
+}
+
+int dwc_otg_set_param_cont_on_bna(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+	int valid = 1;
+
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("`%d' invalid for parameter `cont_on_bna'\n", val);
+		DWC_WARN("cont_on_bna must be 0 or 1\n");
+		return -DWC_E_INVALID;
+	}
+
+	if ((val == 1) && ((core_if->snpsid < OTG_CORE_REV_2_94a) ||
+		!(core_if->core_params->dma_desc_enable))) {
+			valid = 0;
+	}
+	if (valid == 0) {
+		if (dwc_otg_param_initialized(core_if->core_params->cont_on_bna)) {
+			DWC_ERROR("%d invalid for parameter cont_on_bna."
+				"Check HW configuration.\n", val);
+		}
+		retval = -DWC_E_INVALID;
+		val = 0;
+	}
+	core_if->core_params->cont_on_bna = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_cont_on_bna(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->cont_on_bna;
+}
+
+int dwc_otg_set_param_ahb_single(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+	int valid = 1;
+
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("`%d' invalid for parameter `ahb_single'\n", val);
+		DWC_WARN("ahb_single must be 0 or 1\n");
+		return -DWC_E_INVALID;
+	}
+
+	if ((val == 1) && (core_if->snpsid < OTG_CORE_REV_2_94a)) {
+			valid = 0;
+	}
+	if (valid == 0) {
+		if (dwc_otg_param_initialized(core_if->core_params->ahb_single)) {
+			DWC_ERROR("%d invalid for parameter ahb_single."
+				"Check HW configuration.\n", val);
+		}
+		retval = -DWC_E_INVALID;
+		val = 0;
+	}
+	core_if->core_params->ahb_single = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_ahb_single(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->ahb_single;
+}
+
+int dwc_otg_set_param_otg_ver(dwc_otg_core_if_t * core_if, int32_t val)
+{
+	int retval = 0;
+
+	if (DWC_OTG_PARAM_TEST(val, 0, 1)) {
+		DWC_WARN("`%d' invalid for parameter `otg_ver'\n", val);
+		DWC_WARN
+		    ("otg_ver must be 0(for OTG 1.3 support) or 1(for OTG 2.0 support)\n");
+		return -DWC_E_INVALID;
+	}
+
+	core_if->core_params->otg_ver = val;
+	return retval;
+}
+
+int32_t dwc_otg_get_param_otg_ver(dwc_otg_core_if_t * core_if)
+{
+	return core_if->core_params->otg_ver;
+}
+
+uint32_t dwc_otg_get_hnpstatus(dwc_otg_core_if_t * core_if)
+{
+	gotgctl_data_t otgctl;
+	otgctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->gotgctl);
+	return otgctl.b.hstnegscs;
+}
+
+uint32_t dwc_otg_get_srpstatus(dwc_otg_core_if_t * core_if)
+{
+	gotgctl_data_t otgctl;
+	otgctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->gotgctl);
+	return otgctl.b.sesreqscs;
+}
+
+void dwc_otg_set_hnpreq(dwc_otg_core_if_t * core_if, uint32_t val)
+{
+	if(core_if->otg_ver == 0) {
+		gotgctl_data_t otgctl;
+		otgctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->gotgctl);
+		otgctl.b.hnpreq = val;
+		DWC_WRITE_REG32(&core_if->core_global_regs->gotgctl, otgctl.d32);
+	} else {
+		core_if->otg_sts = val;
+	}
+}
+
+uint32_t dwc_otg_get_gsnpsid(dwc_otg_core_if_t * core_if)
+{
+	return core_if->snpsid;
+}
+
+uint32_t dwc_otg_get_mode(dwc_otg_core_if_t * core_if)
+{
+	gintsts_data_t gintsts;
+	gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts);
+	return gintsts.b.curmode;
+}
+
+uint32_t dwc_otg_get_hnpcapable(dwc_otg_core_if_t * core_if)
+{
+	gusbcfg_data_t usbcfg;
+	usbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg);
+	return usbcfg.b.hnpcap;
+}
+
+void dwc_otg_set_hnpcapable(dwc_otg_core_if_t * core_if, uint32_t val)
+{
+	gusbcfg_data_t usbcfg;
+	usbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg);
+	usbcfg.b.hnpcap = val;
+	DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, usbcfg.d32);
+}
+
+uint32_t dwc_otg_get_srpcapable(dwc_otg_core_if_t * core_if)
+{
+	gusbcfg_data_t usbcfg;
+	usbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg);
+	return usbcfg.b.srpcap;
+}
+
+void dwc_otg_set_srpcapable(dwc_otg_core_if_t * core_if, uint32_t val)
+{
+	gusbcfg_data_t usbcfg;
+	usbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg);
+	usbcfg.b.srpcap = val;
+	DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, usbcfg.d32);
+}
+
+uint32_t dwc_otg_get_devspeed(dwc_otg_core_if_t * core_if)
+{
+	dcfg_data_t dcfg;
+	/* originally: dcfg.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg); */
+
+        dcfg.d32 = -1; //GRAYG
+        DWC_DEBUGPL(DBG_CILV, "%s - core_if(%p)\n", __func__, core_if);
+        if (NULL == core_if)
+                DWC_ERROR("reg request with NULL core_if\n");
+        DWC_DEBUGPL(DBG_CILV, "%s - core_if(%p)->dev_if(%p)\n", __func__,
+                    core_if, core_if->dev_if);
+        if (NULL == core_if->dev_if)
+                DWC_ERROR("reg request with NULL dev_if\n");
+        DWC_DEBUGPL(DBG_CILV, "%s - core_if(%p)->dev_if(%p)->"
+                    "dev_global_regs(%p)\n", __func__,
+                    core_if, core_if->dev_if,
+                    core_if->dev_if->dev_global_regs);
+        if (NULL == core_if->dev_if->dev_global_regs)
+                DWC_ERROR("reg request with NULL dev_global_regs\n");
+        else {
+                DWC_DEBUGPL(DBG_CILV, "%s - &core_if(%p)->dev_if(%p)->"
+                            "dev_global_regs(%p)->dcfg = %p\n", __func__,
+                            core_if, core_if->dev_if,
+                            core_if->dev_if->dev_global_regs,
+                            &core_if->dev_if->dev_global_regs->dcfg);
+		dcfg.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg);
+        }
+	return dcfg.b.devspd;
+}
+
+void dwc_otg_set_devspeed(dwc_otg_core_if_t * core_if, uint32_t val)
+{
+	dcfg_data_t dcfg;
+	dcfg.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg);
+	dcfg.b.devspd = val;
+	DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dcfg, dcfg.d32);
+}
+
+uint32_t dwc_otg_get_busconnected(dwc_otg_core_if_t * core_if)
+{
+	hprt0_data_t hprt0;
+	hprt0.d32 = DWC_READ_REG32(core_if->host_if->hprt0);
+	return hprt0.b.prtconnsts;
+}
+
+uint32_t dwc_otg_get_enumspeed(dwc_otg_core_if_t * core_if)
+{
+	dsts_data_t dsts;
+	dsts.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts);
+	return dsts.b.enumspd;
+}
+
+uint32_t dwc_otg_get_prtpower(dwc_otg_core_if_t * core_if)
+{
+	hprt0_data_t hprt0;
+	hprt0.d32 = DWC_READ_REG32(core_if->host_if->hprt0);
+	return hprt0.b.prtpwr;
+
+}
+
+uint32_t dwc_otg_get_core_state(dwc_otg_core_if_t * core_if)
+{
+	return core_if->hibernation_suspend;
+}
+
+void dwc_otg_set_prtpower(dwc_otg_core_if_t * core_if, uint32_t val)
+{
+	hprt0_data_t hprt0;
+	hprt0.d32 = dwc_otg_read_hprt0(core_if);
+	hprt0.b.prtpwr = val;
+	DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+}
+
+uint32_t dwc_otg_get_prtsuspend(dwc_otg_core_if_t * core_if)
+{
+	hprt0_data_t hprt0;
+	hprt0.d32 = DWC_READ_REG32(core_if->host_if->hprt0);
+	return hprt0.b.prtsusp;
+
+}
+
+void dwc_otg_set_prtsuspend(dwc_otg_core_if_t * core_if, uint32_t val)
+{
+	hprt0_data_t hprt0;
+	hprt0.d32 = dwc_otg_read_hprt0(core_if);
+	hprt0.b.prtsusp = val;
+	DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+}
+
+uint32_t dwc_otg_get_fr_interval(dwc_otg_core_if_t * core_if)
+{
+	hfir_data_t hfir;
+	hfir.d32 = DWC_READ_REG32(&core_if->host_if->host_global_regs->hfir);
+	return hfir.b.frint;
+
+}
+
+void dwc_otg_set_fr_interval(dwc_otg_core_if_t * core_if, uint32_t val)
+{
+	hfir_data_t hfir;
+	uint32_t fram_int;
+	fram_int = calc_frame_interval(core_if);
+	hfir.d32 = DWC_READ_REG32(&core_if->host_if->host_global_regs->hfir);
+	if (!core_if->core_params->reload_ctl) {
+		DWC_WARN("\nCannot reload HFIR register.HFIR.HFIRRldCtrl bit is"
+			 "not set to 1.\nShould load driver with reload_ctl=1"
+			 " module parameter\n");
+		return;
+	}
+	switch (fram_int) {
+	case 3750:
+		if ((val < 3350) || (val > 4150)) {
+			DWC_WARN("HFIR interval for HS core and 30 MHz"
+				 "clock freq should be from 3350 to 4150\n");
+			return;
+		}
+		break;
+	case 30000:
+		if ((val < 26820) || (val > 33180)) {
+			DWC_WARN("HFIR interval for FS/LS core and 30 MHz"
+				 "clock freq should be from 26820 to 33180\n");
+			return;
+		}
+		break;
+	case 6000:
+		if ((val < 5360) || (val > 6640)) {
+			DWC_WARN("HFIR interval for HS core and 48 MHz"
+				 "clock freq should be from 5360 to 6640\n");
+			return;
+		}
+		break;
+	case 48000:
+		if ((val < 42912) || (val > 53088)) {
+			DWC_WARN("HFIR interval for FS/LS core and 48 MHz"
+				 "clock freq should be from 42912 to 53088\n");
+			return;
+		}
+		break;
+	case 7500:
+		if ((val < 6700) || (val > 8300)) {
+			DWC_WARN("HFIR interval for HS core and 60 MHz"
+				 "clock freq should be from 6700 to 8300\n");
+			return;
+		}
+		break;
+	case 60000:
+		if ((val < 53640) || (val > 65536)) {
+			DWC_WARN("HFIR interval for FS/LS core and 60 MHz"
+				 "clock freq should be from 53640 to 65536\n");
+			return;
+		}
+		break;
+	default:
+		DWC_WARN("Unknown frame interval\n");
+		return;
+		break;
+
+	}
+	hfir.b.frint = val;
+	DWC_WRITE_REG32(&core_if->host_if->host_global_regs->hfir, hfir.d32);
+}
+
+uint32_t dwc_otg_get_mode_ch_tim(dwc_otg_core_if_t * core_if)
+{
+	hcfg_data_t hcfg;
+	hcfg.d32 = DWC_READ_REG32(&core_if->host_if->host_global_regs->hcfg);
+	return hcfg.b.modechtimen;
+
+}
+
+void dwc_otg_set_mode_ch_tim(dwc_otg_core_if_t * core_if, uint32_t val)
+{
+	hcfg_data_t hcfg;
+	hcfg.d32 = DWC_READ_REG32(&core_if->host_if->host_global_regs->hcfg);
+	hcfg.b.modechtimen = val;
+	DWC_WRITE_REG32(&core_if->host_if->host_global_regs->hcfg, hcfg.d32);
+}
+
+void dwc_otg_set_prtresume(dwc_otg_core_if_t * core_if, uint32_t val)
+{
+	hprt0_data_t hprt0;
+	hprt0.d32 = dwc_otg_read_hprt0(core_if);
+	hprt0.b.prtres = val;
+	DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+}
+
+uint32_t dwc_otg_get_remotewakesig(dwc_otg_core_if_t * core_if)
+{
+	dctl_data_t dctl;
+	dctl.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dctl);
+	return dctl.b.rmtwkupsig;
+}
+
+uint32_t dwc_otg_get_lpm_portsleepstatus(dwc_otg_core_if_t * core_if)
+{
+	glpmcfg_data_t lpmcfg;
+	lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg);
+
+	DWC_ASSERT(!
+		   ((core_if->lx_state == DWC_OTG_L1) ^ lpmcfg.b.prt_sleep_sts),
+		   "lx_state = %d, lmpcfg.prt_sleep_sts = %d\n",
+		   core_if->lx_state, lpmcfg.b.prt_sleep_sts);
+
+	return lpmcfg.b.prt_sleep_sts;
+}
+
+uint32_t dwc_otg_get_lpm_remotewakeenabled(dwc_otg_core_if_t * core_if)
+{
+	glpmcfg_data_t lpmcfg;
+	lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg);
+	return lpmcfg.b.rem_wkup_en;
+}
+
+uint32_t dwc_otg_get_lpmresponse(dwc_otg_core_if_t * core_if)
+{
+	glpmcfg_data_t lpmcfg;
+	lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg);
+	return lpmcfg.b.appl_resp;
+}
+
+void dwc_otg_set_lpmresponse(dwc_otg_core_if_t * core_if, uint32_t val)
+{
+	glpmcfg_data_t lpmcfg;
+	lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg);
+	lpmcfg.b.appl_resp = val;
+	DWC_WRITE_REG32(&core_if->core_global_regs->glpmcfg, lpmcfg.d32);
+}
+
+uint32_t dwc_otg_get_hsic_connect(dwc_otg_core_if_t * core_if)
+{
+	glpmcfg_data_t lpmcfg;
+	lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg);
+	return lpmcfg.b.hsic_connect;
+}
+
+void dwc_otg_set_hsic_connect(dwc_otg_core_if_t * core_if, uint32_t val)
+{
+	glpmcfg_data_t lpmcfg;
+	lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg);
+	lpmcfg.b.hsic_connect = val;
+	DWC_WRITE_REG32(&core_if->core_global_regs->glpmcfg, lpmcfg.d32);
+}
+
+uint32_t dwc_otg_get_inv_sel_hsic(dwc_otg_core_if_t * core_if)
+{
+	glpmcfg_data_t lpmcfg;
+	lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg);
+	return lpmcfg.b.inv_sel_hsic;
+
+}
+
+void dwc_otg_set_inv_sel_hsic(dwc_otg_core_if_t * core_if, uint32_t val)
+{
+	glpmcfg_data_t lpmcfg;
+	lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg);
+	lpmcfg.b.inv_sel_hsic = val;
+	DWC_WRITE_REG32(&core_if->core_global_regs->glpmcfg, lpmcfg.d32);
+}
+
+uint32_t dwc_otg_get_gotgctl(dwc_otg_core_if_t * core_if)
+{
+	return DWC_READ_REG32(&core_if->core_global_regs->gotgctl);
+}
+
+void dwc_otg_set_gotgctl(dwc_otg_core_if_t * core_if, uint32_t val)
+{
+	DWC_WRITE_REG32(&core_if->core_global_regs->gotgctl, val);
+}
+
+uint32_t dwc_otg_get_gusbcfg(dwc_otg_core_if_t * core_if)
+{
+	return DWC_READ_REG32(&core_if->core_global_regs->gusbcfg);
+}
+
+void dwc_otg_set_gusbcfg(dwc_otg_core_if_t * core_if, uint32_t val)
+{
+	DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, val);
+}
+
+uint32_t dwc_otg_get_grxfsiz(dwc_otg_core_if_t * core_if)
+{
+	return DWC_READ_REG32(&core_if->core_global_regs->grxfsiz);
+}
+
+void dwc_otg_set_grxfsiz(dwc_otg_core_if_t * core_if, uint32_t val)
+{
+	DWC_WRITE_REG32(&core_if->core_global_regs->grxfsiz, val);
+}
+
+uint32_t dwc_otg_get_gnptxfsiz(dwc_otg_core_if_t * core_if)
+{
+	return DWC_READ_REG32(&core_if->core_global_regs->gnptxfsiz);
+}
+
+void dwc_otg_set_gnptxfsiz(dwc_otg_core_if_t * core_if, uint32_t val)
+{
+	DWC_WRITE_REG32(&core_if->core_global_regs->gnptxfsiz, val);
+}
+
+uint32_t dwc_otg_get_gpvndctl(dwc_otg_core_if_t * core_if)
+{
+	return DWC_READ_REG32(&core_if->core_global_regs->gpvndctl);
+}
+
+void dwc_otg_set_gpvndctl(dwc_otg_core_if_t * core_if, uint32_t val)
+{
+	DWC_WRITE_REG32(&core_if->core_global_regs->gpvndctl, val);
+}
+
+uint32_t dwc_otg_get_ggpio(dwc_otg_core_if_t * core_if)
+{
+	return DWC_READ_REG32(&core_if->core_global_regs->ggpio);
+}
+
+void dwc_otg_set_ggpio(dwc_otg_core_if_t * core_if, uint32_t val)
+{
+	DWC_WRITE_REG32(&core_if->core_global_regs->ggpio, val);
+}
+
+uint32_t dwc_otg_get_hprt0(dwc_otg_core_if_t * core_if)
+{
+	return DWC_READ_REG32(core_if->host_if->hprt0);
+
+}
+
+void dwc_otg_set_hprt0(dwc_otg_core_if_t * core_if, uint32_t val)
+{
+	DWC_WRITE_REG32(core_if->host_if->hprt0, val);
+}
+
+uint32_t dwc_otg_get_guid(dwc_otg_core_if_t * core_if)
+{
+	return DWC_READ_REG32(&core_if->core_global_regs->guid);
+}
+
+void dwc_otg_set_guid(dwc_otg_core_if_t * core_if, uint32_t val)
+{
+	DWC_WRITE_REG32(&core_if->core_global_regs->guid, val);
+}
+
+uint32_t dwc_otg_get_hptxfsiz(dwc_otg_core_if_t * core_if)
+{
+	return DWC_READ_REG32(&core_if->core_global_regs->hptxfsiz);
+}
+
+uint16_t dwc_otg_get_otg_version(dwc_otg_core_if_t * core_if)
+{
+	return ((core_if->otg_ver == 1) ? (uint16_t)0x0200 : (uint16_t)0x0103);
+}
+
+/**
+ * Start the SRP timer to detect when the SRP does not complete within
+ * 6 seconds.
+ *
+ * @param core_if the pointer to core_if strucure.
+ */
+static void dwc_otg_pcd_start_srp_timer(dwc_otg_core_if_t * core_if)
+{
+	core_if->srp_timer_started = 1;
+	DWC_TIMER_SCHEDULE(core_if->srp_timer, 6000 /* 6 secs */ );
+}
+
+void dwc_otg_initiate_srp(dwc_otg_core_if_t * core_if)
+{
+	uint32_t *addr = (uint32_t *) & (core_if->core_global_regs->gotgctl);
+	gotgctl_data_t mem;
+	gotgctl_data_t val;
+
+	val.d32 = DWC_READ_REG32(addr);
+	if (val.b.sesreq) {
+		DWC_ERROR("Session Request Already active!\n");
+		return;
+	}
+
+	DWC_INFO("Session Request Initated\n");	//NOTICE
+	mem.d32 = DWC_READ_REG32(addr);
+	mem.b.sesreq = 1;
+	DWC_WRITE_REG32(addr, mem.d32);
+
+	/* Start the SRP timer */
+	dwc_otg_pcd_start_srp_timer(core_if);
+	return;
+}
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_cil.h b/drivers/usb/host/dwc_otg/dwc_otg_cil.h
new file mode 100644
index 000000000000..79dbf8374f02
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_cil.h
@@ -0,0 +1,1464 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_cil.h $
+ * $Revision: #123 $
+ * $Date: 2012/08/10 $
+ * $Change: 2047372 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+
+#if !defined(__DWC_CIL_H__)
+#define __DWC_CIL_H__
+
+#include "dwc_list.h"
+#include "dwc_otg_dbg.h"
+#include "dwc_otg_regs.h"
+
+#include "dwc_otg_core_if.h"
+#include "dwc_otg_adp.h"
+
+/**
+ * @file
+ * This file contains the interface to the Core Interface Layer.
+ */
+
+#ifdef DWC_UTE_CFI
+
+#define MAX_DMA_DESCS_PER_EP	256
+
+/**
+ * Enumeration for the data buffer mode
+ */
+typedef enum _data_buffer_mode {
+	BM_STANDARD = 0,	/* data buffer is in normal mode */
+	BM_SG = 1,		/* data buffer uses the scatter/gather mode */
+	BM_CONCAT = 2,		/* data buffer uses the concatenation mode */
+	BM_CIRCULAR = 3,	/* data buffer uses the circular DMA mode */
+	BM_ALIGN = 4		/* data buffer is in buffer alignment mode */
+} data_buffer_mode_e;
+#endif //DWC_UTE_CFI
+
+/** Macros defined for DWC OTG HW Release version */
+
+#define OTG_CORE_REV_2_60a	0x4F54260A
+#define OTG_CORE_REV_2_71a	0x4F54271A
+#define OTG_CORE_REV_2_72a	0x4F54272A
+#define OTG_CORE_REV_2_80a	0x4F54280A
+#define OTG_CORE_REV_2_81a	0x4F54281A
+#define OTG_CORE_REV_2_90a	0x4F54290A
+#define OTG_CORE_REV_2_91a	0x4F54291A
+#define OTG_CORE_REV_2_92a	0x4F54292A
+#define OTG_CORE_REV_2_93a	0x4F54293A
+#define OTG_CORE_REV_2_94a	0x4F54294A
+#define OTG_CORE_REV_3_00a	0x4F54300A
+
+/**
+ * Information for each ISOC packet.
+ */
+typedef struct iso_pkt_info {
+	uint32_t offset;
+	uint32_t length;
+	int32_t status;
+} iso_pkt_info_t;
+
+/**
+ * The <code>dwc_ep</code> structure represents the state of a single
+ * endpoint when acting in device mode. It contains the data items
+ * needed for an endpoint to be activated and transfer packets.
+ */
+typedef struct dwc_ep {
+	/** EP number used for register address lookup */
+	uint8_t num;
+	/** EP direction 0 = OUT */
+	unsigned is_in:1;
+	/** EP active. */
+	unsigned active:1;
+
+	/**
+	 * Periodic Tx FIFO # for IN EPs For INTR EP set to 0 to use non-periodic
+	 * Tx FIFO. If dedicated Tx FIFOs are enabled Tx FIFO # FOR IN EPs*/
+	unsigned tx_fifo_num:4;
+	/** EP type: 0 - Control, 1 - ISOC,	 2 - BULK,	3 - INTR */
+	unsigned type:2;
+#define DWC_OTG_EP_TYPE_CONTROL	   0
+#define DWC_OTG_EP_TYPE_ISOC	   1
+#define DWC_OTG_EP_TYPE_BULK	   2
+#define DWC_OTG_EP_TYPE_INTR	   3
+
+	/** DATA start PID for INTR and BULK EP */
+	unsigned data_pid_start:1;
+	/** Frame (even/odd) for ISOC EP */
+	unsigned even_odd_frame:1;
+	/** Max Packet bytes */
+	unsigned maxpacket:11;
+
+	/** Max Transfer size */
+	uint32_t maxxfer;
+
+	/** @name Transfer state */
+	/** @{ */
+
+	/**
+	 * Pointer to the beginning of the transfer buffer -- do not modify
+	 * during transfer.
+	 */
+
+	dwc_dma_t dma_addr;
+
+	dwc_dma_t dma_desc_addr;
+	dwc_otg_dev_dma_desc_t *desc_addr;
+
+	uint8_t *start_xfer_buff;
+	/** pointer to the transfer buffer */
+	uint8_t *xfer_buff;
+	/** Number of bytes to transfer */
+	unsigned xfer_len:19;
+	/** Number of bytes transferred. */
+	unsigned xfer_count:19;
+	/** Sent ZLP */
+	unsigned sent_zlp:1;
+	/** Total len for control transfer */
+	unsigned total_len:19;
+
+	/** stall clear flag */
+	unsigned stall_clear_flag:1;
+
+	/** SETUP pkt cnt rollover flag for EP0 out*/
+	unsigned stp_rollover;
+
+#ifdef DWC_UTE_CFI
+	/* The buffer mode */
+	data_buffer_mode_e buff_mode;
+
+	/* The chain of DMA descriptors.
+	 * MAX_DMA_DESCS_PER_EP will be allocated for each active EP.
+	 */
+	dwc_otg_dma_desc_t *descs;
+
+	/* The DMA address of the descriptors chain start */
+	dma_addr_t descs_dma_addr;
+	/** This variable stores the length of the last enqueued request */
+	uint32_t cfi_req_len;
+#endif				//DWC_UTE_CFI
+
+/** Max DMA Descriptor count for any EP */
+#define MAX_DMA_DESC_CNT 256
+	/** Allocated DMA Desc count */
+	uint32_t desc_cnt;
+
+	/** bInterval */
+	uint32_t bInterval;
+	/** Next frame num to setup next ISOC transfer */
+	uint32_t frame_num;
+	/** Indicates SOF number overrun in DSTS */
+	uint8_t frm_overrun;
+
+#ifdef DWC_UTE_PER_IO
+	/** Next frame num for which will be setup DMA Desc */
+	uint32_t xiso_frame_num;
+	/** bInterval */
+	uint32_t xiso_bInterval;
+	/** Count of currently active transfers - shall be either 0 or 1 */
+	int xiso_active_xfers;
+	int xiso_queued_xfers;
+#endif
+#ifdef DWC_EN_ISOC
+	/**
+	 * Variables specific for ISOC EPs
+	 *
+	 */
+	/** DMA addresses of ISOC buffers */
+	dwc_dma_t dma_addr0;
+	dwc_dma_t dma_addr1;
+
+	dwc_dma_t iso_dma_desc_addr;
+	dwc_otg_dev_dma_desc_t *iso_desc_addr;
+
+	/** pointer to the transfer buffers */
+	uint8_t *xfer_buff0;
+	uint8_t *xfer_buff1;
+
+	/** number of ISOC Buffer is processing */
+	uint32_t proc_buf_num;
+	/** Interval of ISOC Buffer processing */
+	uint32_t buf_proc_intrvl;
+	/** Data size for regular frame */
+	uint32_t data_per_frame;
+
+	/* todo - pattern data support is to be implemented in the future */
+	/** Data size for pattern frame */
+	uint32_t data_pattern_frame;
+	/** Frame number of pattern data */
+	uint32_t sync_frame;
+
+	/** bInterval */
+	uint32_t bInterval;
+	/** ISO Packet number per frame */
+	uint32_t pkt_per_frm;
+	/** Next frame num for which will be setup DMA Desc */
+	uint32_t next_frame;
+	/** Number of packets per buffer processing */
+	uint32_t pkt_cnt;
+	/** Info for all isoc packets */
+	iso_pkt_info_t *pkt_info;
+	/** current pkt number */
+	uint32_t cur_pkt;
+	/** current pkt number */
+	uint8_t *cur_pkt_addr;
+	/** current pkt number */
+	uint32_t cur_pkt_dma_addr;
+#endif				/* DWC_EN_ISOC */
+
+/** @} */
+} dwc_ep_t;
+
+/*
+ * Reasons for halting a host channel.
+ */
+typedef enum dwc_otg_halt_status {
+	DWC_OTG_HC_XFER_NO_HALT_STATUS,
+	DWC_OTG_HC_XFER_COMPLETE,
+	DWC_OTG_HC_XFER_URB_COMPLETE,
+	DWC_OTG_HC_XFER_ACK,
+	DWC_OTG_HC_XFER_NAK,
+	DWC_OTG_HC_XFER_NYET,
+	DWC_OTG_HC_XFER_STALL,
+	DWC_OTG_HC_XFER_XACT_ERR,
+	DWC_OTG_HC_XFER_FRAME_OVERRUN,
+	DWC_OTG_HC_XFER_BABBLE_ERR,
+	DWC_OTG_HC_XFER_DATA_TOGGLE_ERR,
+	DWC_OTG_HC_XFER_AHB_ERR,
+	DWC_OTG_HC_XFER_PERIODIC_INCOMPLETE,
+	DWC_OTG_HC_XFER_URB_DEQUEUE
+} dwc_otg_halt_status_e;
+
+/**
+ * Host channel descriptor. This structure represents the state of a single
+ * host channel when acting in host mode. It contains the data items needed to
+ * transfer packets to an endpoint via a host channel.
+ */
+typedef struct dwc_hc {
+	/** Host channel number used for register address lookup */
+	uint8_t hc_num;
+
+	/** Device to access */
+	unsigned dev_addr:7;
+
+	/** EP to access */
+	unsigned ep_num:4;
+
+	/** EP direction. 0: OUT, 1: IN */
+	unsigned ep_is_in:1;
+
+	/**
+	 * EP speed.
+	 * One of the following values:
+	 *	- DWC_OTG_EP_SPEED_LOW
+	 *	- DWC_OTG_EP_SPEED_FULL
+	 *	- DWC_OTG_EP_SPEED_HIGH
+	 */
+	unsigned speed:2;
+#define DWC_OTG_EP_SPEED_LOW	0
+#define DWC_OTG_EP_SPEED_FULL	1
+#define DWC_OTG_EP_SPEED_HIGH	2
+
+	/**
+	 * Endpoint type.
+	 * One of the following values:
+	 *	- DWC_OTG_EP_TYPE_CONTROL: 0
+	 *	- DWC_OTG_EP_TYPE_ISOC: 1
+	 *	- DWC_OTG_EP_TYPE_BULK: 2
+	 *	- DWC_OTG_EP_TYPE_INTR: 3
+	 */
+	unsigned ep_type:2;
+
+	/** Max packet size in bytes */
+	unsigned max_packet:11;
+
+	/**
+	 * PID for initial transaction.
+	 * 0: DATA0,<br>
+	 * 1: DATA2,<br>
+	 * 2: DATA1,<br>
+	 * 3: MDATA (non-Control EP),
+	 *	  SETUP (Control EP)
+	 */
+	unsigned data_pid_start:2;
+#define DWC_OTG_HC_PID_DATA0 0
+#define DWC_OTG_HC_PID_DATA2 1
+#define DWC_OTG_HC_PID_DATA1 2
+#define DWC_OTG_HC_PID_MDATA 3
+#define DWC_OTG_HC_PID_SETUP 3
+
+	/** Number of periodic transactions per (micro)frame */
+	unsigned multi_count:2;
+
+	/** @name Transfer State */
+	/** @{ */
+
+	/** Pointer to the current transfer buffer position. */
+	uint8_t *xfer_buff;
+	/**
+	 * In Buffer DMA mode this buffer will be used
+	 * if xfer_buff is not DWORD aligned.
+	 */
+	dwc_dma_t align_buff;
+	/** Total number of bytes to transfer. */
+	uint32_t xfer_len;
+	/** Number of bytes transferred so far. */
+	uint32_t xfer_count;
+	/** Packet count at start of transfer.*/
+	uint16_t start_pkt_count;
+
+	/**
+	 * Flag to indicate whether the transfer has been started. Set to 1 if
+	 * it has been started, 0 otherwise.
+	 */
+	uint8_t xfer_started;
+
+	/**
+	 * Set to 1 to indicate that a PING request should be issued on this
+	 * channel. If 0, process normally.
+	 */
+	uint8_t do_ping;
+
+	/**
+	 * Set to 1 to indicate that the error count for this transaction is
+	 * non-zero. Set to 0 if the error count is 0.
+	 */
+	uint8_t error_state;
+
+	/**
+	 * Set to 1 to indicate that this channel should be halted the next
+	 * time a request is queued for the channel. This is necessary in
+	 * slave mode if no request queue space is available when an attempt
+	 * is made to halt the channel.
+	 */
+	uint8_t halt_on_queue;
+
+	/**
+	 * Set to 1 if the host channel has been halted, but the core is not
+	 * finished flushing queued requests. Otherwise 0.
+	 */
+	uint8_t halt_pending;
+
+	/**
+	 * Reason for halting the host channel.
+	 */
+	dwc_otg_halt_status_e halt_status;
+
+	/*
+	 * Split settings for the host channel
+	 */
+	uint8_t do_split;		   /**< Enable split for the channel */
+	uint8_t complete_split;	   /**< Enable complete split */
+	uint8_t hub_addr;		   /**< Address of high speed hub */
+
+	uint8_t port_addr;		   /**< Port of the low/full speed device */
+	/** Split transaction position
+	 * One of the following values:
+	 *	  - DWC_HCSPLIT_XACTPOS_MID
+	 *	  - DWC_HCSPLIT_XACTPOS_BEGIN
+	 *	  - DWC_HCSPLIT_XACTPOS_END
+	 *	  - DWC_HCSPLIT_XACTPOS_ALL */
+	uint8_t xact_pos;
+
+	/** Set when the host channel does a short read. */
+	uint8_t short_read;
+
+	/**
+	 * Number of requests issued for this channel since it was assigned to
+	 * the current transfer (not counting PINGs).
+	 */
+	uint8_t requests;
+
+	/**
+	 * Queue Head for the transfer being processed by this channel.
+	 */
+	struct dwc_otg_qh *qh;
+
+	/** @} */
+
+	/** Entry in list of host channels. */
+	 DWC_CIRCLEQ_ENTRY(dwc_hc) hc_list_entry;
+
+	/** @name Descriptor DMA support */
+	/** @{ */
+
+	/** Number of Transfer Descriptors */
+	uint16_t ntd;
+
+	/** Descriptor List DMA address */
+	dwc_dma_t desc_list_addr;
+
+	/** Scheduling micro-frame bitmap. */
+	uint8_t schinfo;
+
+	/** @} */
+} dwc_hc_t;
+
+/**
+ * The following parameters may be specified when starting the module. These
+ * parameters define how the DWC_otg controller should be configured.
+ */
+typedef struct dwc_otg_core_params {
+	int32_t opt;
+
+	/**
+	 * Specifies the OTG capabilities. The driver will automatically
+	 * detect the value for this parameter if none is specified.
+	 * 0 - HNP and SRP capable (default)
+	 * 1 - SRP Only capable
+	 * 2 - No HNP/SRP capable
+	 */
+	int32_t otg_cap;
+
+	/**
+	 * Specifies whether to use slave or DMA mode for accessing the data
+	 * FIFOs. The driver will automatically detect the value for this
+	 * parameter if none is specified.
+	 * 0 - Slave
+	 * 1 - DMA (default, if available)
+	 */
+	int32_t dma_enable;
+
+	/**
+	 * When DMA mode is enabled specifies whether to use address DMA or DMA
+	 * Descriptor mode for accessing the data FIFOs in device mode. The driver
+	 * will automatically detect the value for this if none is specified.
+	 * 0 - address DMA
+	 * 1 - DMA Descriptor(default, if available)
+	 */
+	int32_t dma_desc_enable;
+	/** The DMA Burst size (applicable only for External DMA
+	 * Mode). 1, 4, 8 16, 32, 64, 128, 256 (default 32)
+	 */
+	int32_t dma_burst_size;	/* Translate this to GAHBCFG values */
+
+	/**
+	 * Specifies the maximum speed of operation in host and device mode.
+	 * The actual speed depends on the speed of the attached device and
+	 * the value of phy_type. The actual speed depends on the speed of the
+	 * attached device.
+	 * 0 - High Speed (default)
+	 * 1 - Full Speed
+	 */
+	int32_t speed;
+	/** Specifies whether low power mode is supported when attached
+	 *	to a Full Speed or Low Speed device in host mode.
+	 * 0 - Don't support low power mode (default)
+	 * 1 - Support low power mode
+	 */
+	int32_t host_support_fs_ls_low_power;
+
+	/** Specifies the PHY clock rate in low power mode when connected to a
+	 * Low Speed device in host mode. This parameter is applicable only if
+	 * HOST_SUPPORT_FS_LS_LOW_POWER is enabled. If PHY_TYPE is set to FS
+	 * then defaults to 6 MHZ otherwise 48 MHZ.
+	 *
+	 * 0 - 48 MHz
+	 * 1 - 6 MHz
+	 */
+	int32_t host_ls_low_power_phy_clk;
+
+	/**
+	 * 0 - Use cC FIFO size parameters
+	 * 1 - Allow dynamic FIFO sizing (default)
+	 */
+	int32_t enable_dynamic_fifo;
+
+	/** Total number of 4-byte words in the data FIFO memory. This
+	 * memory includes the Rx FIFO, non-periodic Tx FIFO, and periodic
+	 * Tx FIFOs.
+	 * 32 to 32768 (default 8192)
+	 * Note: The total FIFO memory depth in the FPGA configuration is 8192.
+	 */
+	int32_t data_fifo_size;
+
+	/** Number of 4-byte words in the Rx FIFO in device mode when dynamic
+	 * FIFO sizing is enabled.
+	 * 16 to 32768 (default 1064)
+	 */
+	int32_t dev_rx_fifo_size;
+
+	/** Number of 4-byte words in the non-periodic Tx FIFO in device mode
+	 * when dynamic FIFO sizing is enabled.
+	 * 16 to 32768 (default 1024)
+	 */
+	int32_t dev_nperio_tx_fifo_size;
+
+	/** Number of 4-byte words in each of the periodic Tx FIFOs in device
+	 * mode when dynamic FIFO sizing is enabled.
+	 * 4 to 768 (default 256)
+	 */
+	uint32_t dev_perio_tx_fifo_size[MAX_PERIO_FIFOS];
+
+	/** Number of 4-byte words in the Rx FIFO in host mode when dynamic
+	 * FIFO sizing is enabled.
+	 * 16 to 32768 (default 1024)
+	 */
+	int32_t host_rx_fifo_size;
+
+	/** Number of 4-byte words in the non-periodic Tx FIFO in host mode
+	 * when Dynamic FIFO sizing is enabled in the core.
+	 * 16 to 32768 (default 1024)
+	 */
+	int32_t host_nperio_tx_fifo_size;
+
+	/** Number of 4-byte words in the host periodic Tx FIFO when dynamic
+	 * FIFO sizing is enabled.
+	 * 16 to 32768 (default 1024)
+	 */
+	int32_t host_perio_tx_fifo_size;
+
+	/** The maximum transfer size supported in bytes.
+	 * 2047 to 65,535  (default 65,535)
+	 */
+	int32_t max_transfer_size;
+
+	/** The maximum number of packets in a transfer.
+	 * 15 to 511  (default 511)
+	 */
+	int32_t max_packet_count;
+
+	/** The number of host channel registers to use.
+	 * 1 to 16 (default 12)
+	 * Note: The FPGA configuration supports a maximum of 12 host channels.
+	 */
+	int32_t host_channels;
+
+	/** The number of endpoints in addition to EP0 available for device
+	 * mode operations.
+	 * 1 to 15 (default 6 IN and OUT)
+	 * Note: The FPGA configuration supports a maximum of 6 IN and OUT
+	 * endpoints in addition to EP0.
+	 */
+	int32_t dev_endpoints;
+
+		/**
+		 * Specifies the type of PHY interface to use. By default, the driver
+		 * will automatically detect the phy_type.
+		 *
+		 * 0 - Full Speed PHY
+		 * 1 - UTMI+ (default)
+		 * 2 - ULPI
+		 */
+	int32_t phy_type;
+
+	/**
+	 * Specifies the UTMI+ Data Width. This parameter is
+	 * applicable for a PHY_TYPE of UTMI+ or ULPI. (For a ULPI
+	 * PHY_TYPE, this parameter indicates the data width between
+	 * the MAC and the ULPI Wrapper.) Also, this parameter is
+	 * applicable only if the OTG_HSPHY_WIDTH cC parameter was set
+	 * to "8 and 16 bits", meaning that the core has been
+	 * configured to work at either data path width.
+	 *
+	 * 8 or 16 bits (default 16)
+	 */
+	int32_t phy_utmi_width;
+
+	/**
+	 * Specifies whether the ULPI operates at double or single
+	 * data rate. This parameter is only applicable if PHY_TYPE is
+	 * ULPI.
+	 *
+	 * 0 - single data rate ULPI interface with 8 bit wide data
+	 * bus (default)
+	 * 1 - double data rate ULPI interface with 4 bit wide data
+	 * bus
+	 */
+	int32_t phy_ulpi_ddr;
+
+	/**
+	 * Specifies whether to use the internal or external supply to
+	 * drive the vbus with a ULPI phy.
+	 */
+	int32_t phy_ulpi_ext_vbus;
+
+	/**
+	 * Specifies whether to use the I2Cinterface for full speed PHY. This
+	 * parameter is only applicable if PHY_TYPE is FS.
+	 * 0 - No (default)
+	 * 1 - Yes
+	 */
+	int32_t i2c_enable;
+
+	int32_t ulpi_fs_ls;
+
+	int32_t ts_dline;
+
+	/**
+	 * Specifies whether dedicated transmit FIFOs are
+	 * enabled for non periodic IN endpoints in device mode
+	 * 0 - No
+	 * 1 - Yes
+	 */
+	int32_t en_multiple_tx_fifo;
+
+	/** Number of 4-byte words in each of the Tx FIFOs in device
+	 * mode when dynamic FIFO sizing is enabled.
+	 * 4 to 768 (default 256)
+	 */
+	uint32_t dev_tx_fifo_size[MAX_TX_FIFOS];
+
+	/** Thresholding enable flag-
+	 * bit 0 - enable non-ISO Tx thresholding
+	 * bit 1 - enable ISO Tx thresholding
+	 * bit 2 - enable Rx thresholding
+	 */
+	uint32_t thr_ctl;
+
+	/** Thresholding length for Tx
+	 *	FIFOs in 32 bit DWORDs
+	 */
+	uint32_t tx_thr_length;
+
+	/** Thresholding length for Rx
+	 *	FIFOs in 32 bit DWORDs
+	 */
+	uint32_t rx_thr_length;
+
+	/**
+	 * Specifies whether LPM (Link Power Management) support is enabled
+	 */
+	int32_t lpm_enable;
+
+	/** Per Transfer Interrupt
+	 *	mode enable flag
+	 * 1 - Enabled
+	 * 0 - Disabled
+	 */
+	int32_t pti_enable;
+
+	/** Multi Processor Interrupt
+	 *	mode enable flag
+	 * 1 - Enabled
+	 * 0 - Disabled
+	 */
+	int32_t mpi_enable;
+
+	/** IS_USB Capability
+	 * 1 - Enabled
+	 * 0 - Disabled
+	 */
+	int32_t ic_usb_cap;
+
+	/** AHB Threshold Ratio
+	 * 2'b00 AHB Threshold = 	MAC Threshold
+	 * 2'b01 AHB Threshold = 1/2 	MAC Threshold
+	 * 2'b10 AHB Threshold = 1/4	MAC Threshold
+	 * 2'b11 AHB Threshold = 1/8	MAC Threshold
+	 */
+	int32_t ahb_thr_ratio;
+
+	/** ADP Support
+	 * 1 - Enabled
+	 * 0 - Disabled
+	 */
+	int32_t adp_supp_enable;
+
+	/** HFIR Reload Control
+	 * 0 - The HFIR cannot be reloaded dynamically.
+	 * 1 - Allow dynamic reloading of the HFIR register during runtime.
+	 */
+	int32_t reload_ctl;
+
+	/** DCFG: Enable device Out NAK
+	 * 0 - The core does not set NAK after Bulk Out transfer complete.
+	 * 1 - The core sets NAK after Bulk OUT transfer complete.
+	 */
+	int32_t dev_out_nak;
+
+	/** DCFG: Enable Continue on BNA
+	 * After receiving BNA interrupt the core disables the endpoint,when the
+	 * endpoint is re-enabled by the application the core starts processing
+	 * 0 - from the DOEPDMA descriptor
+	 * 1 - from the descriptor which received the BNA.
+	 */
+	int32_t cont_on_bna;
+
+	/** GAHBCFG: AHB Single Support
+	 * This bit when programmed supports SINGLE transfers for remainder
+	 * data in a transfer for DMA mode of operation.
+	 * 0 - in this case the remainder data will be sent using INCR burst size.
+	 * 1 - in this case the remainder data will be sent using SINGLE burst size.
+	 */
+	int32_t ahb_single;
+
+	/** Core Power down mode
+	 * 0 - No Power Down is enabled
+	 * 1 - Reserved
+	 * 2 - Complete Power Down (Hibernation)
+	 */
+	int32_t power_down;
+
+	/** OTG revision supported
+	 * 0 - OTG 1.3 revision
+	 * 1 - OTG 2.0 revision
+	 */
+	int32_t otg_ver;
+
+} dwc_otg_core_params_t;
+
+#ifdef DEBUG
+struct dwc_otg_core_if;
+typedef struct hc_xfer_info {
+	struct dwc_otg_core_if *core_if;
+	dwc_hc_t *hc;
+} hc_xfer_info_t;
+#endif
+
+typedef struct ep_xfer_info {
+	struct dwc_otg_core_if *core_if;
+	dwc_ep_t *ep;
+	uint8_t state;
+} ep_xfer_info_t;
+/*
+ * Device States
+ */
+typedef enum dwc_otg_lx_state {
+	/** On state */
+	DWC_OTG_L0,
+	/** LPM sleep state*/
+	DWC_OTG_L1,
+	/** USB suspend state*/
+	DWC_OTG_L2,
+	/** Off state*/
+	DWC_OTG_L3
+} dwc_otg_lx_state_e;
+
+struct dwc_otg_global_regs_backup {
+	uint32_t gotgctl_local;
+	uint32_t gintmsk_local;
+	uint32_t gahbcfg_local;
+	uint32_t gusbcfg_local;
+	uint32_t grxfsiz_local;
+	uint32_t gnptxfsiz_local;
+#ifdef CONFIG_USB_DWC_OTG_LPM
+	uint32_t glpmcfg_local;
+#endif
+	uint32_t gi2cctl_local;
+	uint32_t hptxfsiz_local;
+	uint32_t pcgcctl_local;
+	uint32_t gdfifocfg_local;
+	uint32_t dtxfsiz_local[MAX_EPS_CHANNELS];
+	uint32_t gpwrdn_local;
+	uint32_t xhib_pcgcctl;
+	uint32_t xhib_gpwrdn;
+};
+
+struct dwc_otg_host_regs_backup {
+	uint32_t hcfg_local;
+	uint32_t haintmsk_local;
+	uint32_t hcintmsk_local[MAX_EPS_CHANNELS];
+	uint32_t hprt0_local;
+	uint32_t hfir_local;
+};
+
+struct dwc_otg_dev_regs_backup {
+	uint32_t dcfg;
+	uint32_t dctl;
+	uint32_t daintmsk;
+	uint32_t diepmsk;
+	uint32_t doepmsk;
+	uint32_t diepctl[MAX_EPS_CHANNELS];
+	uint32_t dieptsiz[MAX_EPS_CHANNELS];
+	uint32_t diepdma[MAX_EPS_CHANNELS];
+};
+/**
+ * The <code>dwc_otg_core_if</code> structure contains information needed to manage
+ * the DWC_otg controller acting in either host or device mode. It
+ * represents the programming view of the controller as a whole.
+ */
+struct dwc_otg_core_if {
+	/** Parameters that define how the core should be configured.*/
+	dwc_otg_core_params_t *core_params;
+
+	/** Core Global registers starting at offset 000h. */
+	dwc_otg_core_global_regs_t *core_global_regs;
+
+	/** Device-specific information */
+	dwc_otg_dev_if_t *dev_if;
+	/** Host-specific information */
+	dwc_otg_host_if_t *host_if;
+
+	/** Value from SNPSID register */
+	uint32_t snpsid;
+
+	/*
+	 * Set to 1 if the core PHY interface bits in USBCFG have been
+	 * initialized.
+	 */
+	uint8_t phy_init_done;
+
+	/*
+	 * SRP Success flag, set by srp success interrupt in FS I2C mode
+	 */
+	uint8_t srp_success;
+	uint8_t srp_timer_started;
+	/** Timer for SRP. If it expires before SRP is successful
+	 * clear the SRP. */
+	dwc_timer_t *srp_timer;
+
+#ifdef DWC_DEV_SRPCAP
+	/* This timer is needed to power on the hibernated host core if SRP is not
+	 * initiated on connected SRP capable device for limited period of time
+	 */
+	uint8_t pwron_timer_started;
+	dwc_timer_t *pwron_timer;
+#endif
+	/* Common configuration information */
+	/** Power and Clock Gating Control Register */
+	volatile uint32_t *pcgcctl;
+#define DWC_OTG_PCGCCTL_OFFSET 0xE00
+
+	/** Push/pop addresses for endpoints or host channels.*/
+	uint32_t *data_fifo[MAX_EPS_CHANNELS];
+#define DWC_OTG_DATA_FIFO_OFFSET 0x1000
+#define DWC_OTG_DATA_FIFO_SIZE 0x1000
+
+	/** Total RAM for FIFOs (Bytes) */
+	uint16_t total_fifo_size;
+	/** Size of Rx FIFO (Bytes) */
+	uint16_t rx_fifo_size;
+	/** Size of Non-periodic Tx FIFO (Bytes) */
+	uint16_t nperio_tx_fifo_size;
+
+	/** 1 if DMA is enabled, 0 otherwise. */
+	uint8_t dma_enable;
+
+	/** 1 if DMA descriptor is enabled, 0 otherwise. */
+	uint8_t dma_desc_enable;
+
+	/** 1 if PTI Enhancement mode is enabled, 0 otherwise. */
+	uint8_t pti_enh_enable;
+
+	/** 1 if MPI Enhancement mode is enabled, 0 otherwise. */
+	uint8_t multiproc_int_enable;
+
+	/** 1 if dedicated Tx FIFOs are enabled, 0 otherwise. */
+	uint8_t en_multiple_tx_fifo;
+
+	/** Set to 1 if multiple packets of a high-bandwidth transfer is in
+	 * process of being queued */
+	uint8_t queuing_high_bandwidth;
+
+	/** Hardware Configuration -- stored here for convenience.*/
+	hwcfg1_data_t hwcfg1;
+	hwcfg2_data_t hwcfg2;
+	hwcfg3_data_t hwcfg3;
+	hwcfg4_data_t hwcfg4;
+	fifosize_data_t hptxfsiz;
+
+	/** Host and Device Configuration -- stored here for convenience.*/
+	hcfg_data_t hcfg;
+	dcfg_data_t dcfg;
+
+	/** The operational State, during transations
+	 * (a_host>>a_peripherial and b_device=>b_host) this may not
+	 * match the core but allows the software to determine
+	 * transitions.
+	 */
+	uint8_t op_state;
+
+	/**
+	 * Set to 1 if the HCD needs to be restarted on a session request
+	 * interrupt. This is required if no connector ID status change has
+	 * occurred since the HCD was last disconnected.
+	 */
+	uint8_t restart_hcd_on_session_req;
+
+	/** HCD callbacks */
+	/** A-Device is a_host */
+#define A_HOST		(1)
+	/** A-Device is a_suspend */
+#define A_SUSPEND	(2)
+	/** A-Device is a_peripherial */
+#define A_PERIPHERAL	(3)
+	/** B-Device is operating as a Peripheral. */
+#define B_PERIPHERAL	(4)
+	/** B-Device is operating as a Host. */
+#define B_HOST		(5)
+
+	/** HCD callbacks */
+	struct dwc_otg_cil_callbacks *hcd_cb;
+	/** PCD callbacks */
+	struct dwc_otg_cil_callbacks *pcd_cb;
+
+	/** Device mode Periodic Tx FIFO Mask */
+	uint32_t p_tx_msk;
+	/** Device mode Periodic Tx FIFO Mask */
+	uint32_t tx_msk;
+
+	/** Workqueue object used for handling several interrupts */
+	dwc_workq_t *wq_otg;
+
+	/** Timer object used for handling "Wakeup Detected" Interrupt */
+	dwc_timer_t *wkp_timer;
+	/** This arrays used for debug purposes for DEV OUT NAK enhancement */
+	uint32_t start_doeptsiz_val[MAX_EPS_CHANNELS];
+	ep_xfer_info_t ep_xfer_info[MAX_EPS_CHANNELS];
+	dwc_timer_t *ep_xfer_timer[MAX_EPS_CHANNELS];
+#ifdef DEBUG
+	uint32_t start_hcchar_val[MAX_EPS_CHANNELS];
+
+	hc_xfer_info_t hc_xfer_info[MAX_EPS_CHANNELS];
+	dwc_timer_t *hc_xfer_timer[MAX_EPS_CHANNELS];
+
+	uint32_t hfnum_7_samples;
+	uint64_t hfnum_7_frrem_accum;
+	uint32_t hfnum_0_samples;
+	uint64_t hfnum_0_frrem_accum;
+	uint32_t hfnum_other_samples;
+	uint64_t hfnum_other_frrem_accum;
+#endif
+
+#ifdef DWC_UTE_CFI
+	uint16_t pwron_rxfsiz;
+	uint16_t pwron_gnptxfsiz;
+	uint16_t pwron_txfsiz[15];
+
+	uint16_t init_rxfsiz;
+	uint16_t init_gnptxfsiz;
+	uint16_t init_txfsiz[15];
+#endif
+
+	/** Lx state of device */
+	dwc_otg_lx_state_e lx_state;
+
+	/** Saved Core Global registers */
+	struct dwc_otg_global_regs_backup *gr_backup;
+	/** Saved Host registers */
+	struct dwc_otg_host_regs_backup *hr_backup;
+	/** Saved Device registers */
+	struct dwc_otg_dev_regs_backup *dr_backup;
+
+	/** Power Down Enable */
+	uint32_t power_down;
+
+	/** ADP support Enable */
+	uint32_t adp_enable;
+
+	/** ADP structure object */
+	dwc_otg_adp_t adp;
+
+	/** hibernation/suspend flag */
+	int hibernation_suspend;
+
+	/** Device mode extended hibernation flag */
+	int xhib;
+
+	/** OTG revision supported */
+	uint32_t otg_ver;
+
+	/** OTG status flag used for HNP polling */
+	uint8_t otg_sts;
+
+	/** Pointer to either hcd->lock or pcd->lock */
+	dwc_spinlock_t *lock;
+
+	/** Start predict NextEP based on Learning Queue if equal 1,
+	 * also used as counter of disabled NP IN EP's */
+	uint8_t start_predict;
+
+	/** NextEp sequence, including EP0: nextep_seq[] = EP if non-periodic and
+	 * active, 0xff otherwise */
+	uint8_t nextep_seq[MAX_EPS_CHANNELS];
+
+	/** Index of fisrt EP in nextep_seq array which should be re-enabled **/
+	uint8_t first_in_nextep_seq;
+
+	/** Frame number while entering to ISR - needed for ISOCs **/
+	uint32_t frame_num;
+
+};
+
+#ifdef DEBUG
+/*
+ * This function is called when transfer is timed out.
+ */
+extern void hc_xfer_timeout(void *ptr);
+#endif
+
+/*
+ * This function is called when transfer is timed out on endpoint.
+ */
+extern void ep_xfer_timeout(void *ptr);
+
+/*
+ * The following functions are functions for works
+ * using during handling some interrupts
+ */
+extern void w_conn_id_status_change(void *p);
+
+extern void w_wakeup_detected(void *p);
+
+/** Saves global register values into system memory. */
+extern int dwc_otg_save_global_regs(dwc_otg_core_if_t * core_if);
+/** Saves device register values into system memory. */
+extern int dwc_otg_save_dev_regs(dwc_otg_core_if_t * core_if);
+/** Saves host register values into system memory. */
+extern int dwc_otg_save_host_regs(dwc_otg_core_if_t * core_if);
+/** Restore global register values. */
+extern int dwc_otg_restore_global_regs(dwc_otg_core_if_t * core_if);
+/** Restore host register values. */
+extern int dwc_otg_restore_host_regs(dwc_otg_core_if_t * core_if, int reset);
+/** Restore device register values. */
+extern int dwc_otg_restore_dev_regs(dwc_otg_core_if_t * core_if,
+				    int rem_wakeup);
+extern int restore_lpm_i2c_regs(dwc_otg_core_if_t * core_if);
+extern int restore_essential_regs(dwc_otg_core_if_t * core_if, int rmode,
+				  int is_host);
+
+extern int dwc_otg_host_hibernation_restore(dwc_otg_core_if_t * core_if,
+					    int restore_mode, int reset);
+extern int dwc_otg_device_hibernation_restore(dwc_otg_core_if_t * core_if,
+					      int rem_wakeup, int reset);
+
+/*
+ * The following functions support initialization of the CIL driver component
+ * and the DWC_otg controller.
+ */
+extern void dwc_otg_core_host_init(dwc_otg_core_if_t * _core_if);
+extern void dwc_otg_core_dev_init(dwc_otg_core_if_t * _core_if);
+
+/** @name Device CIL Functions
+ * The following functions support managing the DWC_otg controller in device
+ * mode.
+ */
+/**@{*/
+extern void dwc_otg_wakeup(dwc_otg_core_if_t * _core_if);
+extern void dwc_otg_read_setup_packet(dwc_otg_core_if_t * _core_if,
+				      uint32_t * _dest);
+extern uint32_t dwc_otg_get_frame_number(dwc_otg_core_if_t * _core_if);
+extern void dwc_otg_ep0_activate(dwc_otg_core_if_t * _core_if, dwc_ep_t * _ep);
+extern void dwc_otg_ep_activate(dwc_otg_core_if_t * _core_if, dwc_ep_t * _ep);
+extern void dwc_otg_ep_deactivate(dwc_otg_core_if_t * _core_if, dwc_ep_t * _ep);
+extern void dwc_otg_ep_start_transfer(dwc_otg_core_if_t * _core_if,
+				      dwc_ep_t * _ep);
+extern void dwc_otg_ep_start_zl_transfer(dwc_otg_core_if_t * _core_if,
+					 dwc_ep_t * _ep);
+extern void dwc_otg_ep0_start_transfer(dwc_otg_core_if_t * _core_if,
+				       dwc_ep_t * _ep);
+extern void dwc_otg_ep0_continue_transfer(dwc_otg_core_if_t * _core_if,
+					  dwc_ep_t * _ep);
+extern void dwc_otg_ep_write_packet(dwc_otg_core_if_t * _core_if,
+				    dwc_ep_t * _ep, int _dma);
+extern void dwc_otg_ep_set_stall(dwc_otg_core_if_t * _core_if, dwc_ep_t * _ep);
+extern void dwc_otg_ep_clear_stall(dwc_otg_core_if_t * _core_if,
+				   dwc_ep_t * _ep);
+extern void dwc_otg_enable_device_interrupts(dwc_otg_core_if_t * _core_if);
+
+#ifdef DWC_EN_ISOC
+extern void dwc_otg_iso_ep_start_frm_transfer(dwc_otg_core_if_t * core_if,
+					      dwc_ep_t * ep);
+extern void dwc_otg_iso_ep_start_buf_transfer(dwc_otg_core_if_t * core_if,
+					      dwc_ep_t * ep);
+#endif /* DWC_EN_ISOC */
+/**@}*/
+
+/** @name Host CIL Functions
+ * The following functions support managing the DWC_otg controller in host
+ * mode.
+ */
+/**@{*/
+extern void dwc_otg_hc_init(dwc_otg_core_if_t * _core_if, dwc_hc_t * _hc);
+extern void dwc_otg_hc_halt(dwc_otg_core_if_t * _core_if,
+			    dwc_hc_t * _hc, dwc_otg_halt_status_e _halt_status);
+extern void dwc_otg_hc_cleanup(dwc_otg_core_if_t * _core_if, dwc_hc_t * _hc);
+extern void dwc_otg_hc_start_transfer(dwc_otg_core_if_t * _core_if,
+				      dwc_hc_t * _hc);
+extern int dwc_otg_hc_continue_transfer(dwc_otg_core_if_t * _core_if,
+					dwc_hc_t * _hc);
+extern void dwc_otg_hc_do_ping(dwc_otg_core_if_t * _core_if, dwc_hc_t * _hc);
+extern void dwc_otg_hc_write_packet(dwc_otg_core_if_t * _core_if,
+				    dwc_hc_t * _hc);
+extern void dwc_otg_enable_host_interrupts(dwc_otg_core_if_t * _core_if);
+extern void dwc_otg_disable_host_interrupts(dwc_otg_core_if_t * _core_if);
+
+extern void dwc_otg_hc_start_transfer_ddma(dwc_otg_core_if_t * core_if,
+					   dwc_hc_t * hc);
+
+extern uint32_t calc_frame_interval(dwc_otg_core_if_t * core_if);
+
+/* Macro used to clear one channel interrupt */
+#define clear_hc_int(_hc_regs_, _intr_) \
+do { \
+	hcint_data_t hcint_clear = {.d32 = 0}; \
+	hcint_clear.b._intr_ = 1; \
+	DWC_WRITE_REG32(&(_hc_regs_)->hcint, hcint_clear.d32); \
+} while (0)
+
+/*
+ * Macro used to disable one channel interrupt. Channel interrupts are
+ * disabled when the channel is halted or released by the interrupt handler.
+ * There is no need to handle further interrupts of that type until the
+ * channel is re-assigned. In fact, subsequent handling may cause crashes
+ * because the channel structures are cleaned up when the channel is released.
+ */
+#define disable_hc_int(_hc_regs_, _intr_) \
+do { \
+	hcintmsk_data_t hcintmsk = {.d32 = 0}; \
+	hcintmsk.b._intr_ = 1; \
+	DWC_MODIFY_REG32(&(_hc_regs_)->hcintmsk, hcintmsk.d32, 0); \
+} while (0)
+
+/**
+ * This function Reads HPRT0 in preparation to modify. It keeps the
+ * WC bits 0 so that if they are read as 1, they won't clear when you
+ * write it back
+ */
+static inline uint32_t dwc_otg_read_hprt0(dwc_otg_core_if_t * _core_if)
+{
+	hprt0_data_t hprt0;
+	hprt0.d32 = DWC_READ_REG32(_core_if->host_if->hprt0);
+	hprt0.b.prtena = 0;
+	hprt0.b.prtconndet = 0;
+	hprt0.b.prtenchng = 0;
+	hprt0.b.prtovrcurrchng = 0;
+	return hprt0.d32;
+}
+
+/**@}*/
+
+/** @name Common CIL Functions
+ * The following functions support managing the DWC_otg controller in either
+ * device or host mode.
+ */
+/**@{*/
+
+extern void dwc_otg_read_packet(dwc_otg_core_if_t * core_if,
+				uint8_t * dest, uint16_t bytes);
+
+extern void dwc_otg_flush_tx_fifo(dwc_otg_core_if_t * _core_if, const int _num);
+extern void dwc_otg_flush_rx_fifo(dwc_otg_core_if_t * _core_if);
+extern void dwc_otg_core_reset(dwc_otg_core_if_t * _core_if);
+
+/**
+ * This function returns the Core Interrupt register.
+ */
+static inline uint32_t dwc_otg_read_core_intr(dwc_otg_core_if_t * core_if)
+{
+	return (DWC_READ_REG32(&core_if->core_global_regs->gintsts) &
+		DWC_READ_REG32(&core_if->core_global_regs->gintmsk));
+}
+
+/**
+ * This function returns the OTG Interrupt register.
+ */
+static inline uint32_t dwc_otg_read_otg_intr(dwc_otg_core_if_t * core_if)
+{
+	return (DWC_READ_REG32(&core_if->core_global_regs->gotgint));
+}
+
+/**
+ * This function reads the Device All Endpoints Interrupt register and
+ * returns the IN endpoint interrupt bits.
+ */
+static inline uint32_t dwc_otg_read_dev_all_in_ep_intr(dwc_otg_core_if_t *
+						       core_if)
+{
+
+	uint32_t v;
+
+	if (core_if->multiproc_int_enable) {
+		v = DWC_READ_REG32(&core_if->dev_if->
+				   dev_global_regs->deachint) &
+		    DWC_READ_REG32(&core_if->
+				   dev_if->dev_global_regs->deachintmsk);
+	} else {
+		v = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->daint) &
+		    DWC_READ_REG32(&core_if->dev_if->dev_global_regs->daintmsk);
+	}
+	return (v & 0xffff);
+}
+
+/**
+ * This function reads the Device All Endpoints Interrupt register and
+ * returns the OUT endpoint interrupt bits.
+ */
+static inline uint32_t dwc_otg_read_dev_all_out_ep_intr(dwc_otg_core_if_t *
+							core_if)
+{
+	uint32_t v;
+
+	if (core_if->multiproc_int_enable) {
+		v = DWC_READ_REG32(&core_if->dev_if->
+				   dev_global_regs->deachint) &
+		    DWC_READ_REG32(&core_if->
+				   dev_if->dev_global_regs->deachintmsk);
+	} else {
+		v = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->daint) &
+		    DWC_READ_REG32(&core_if->dev_if->dev_global_regs->daintmsk);
+	}
+
+	return ((v & 0xffff0000) >> 16);
+}
+
+/**
+ * This function returns the Device IN EP Interrupt register
+ */
+static inline uint32_t dwc_otg_read_dev_in_ep_intr(dwc_otg_core_if_t * core_if,
+						   dwc_ep_t * ep)
+{
+	dwc_otg_dev_if_t *dev_if = core_if->dev_if;
+	uint32_t v, msk, emp;
+
+	if (core_if->multiproc_int_enable) {
+		msk =
+		    DWC_READ_REG32(&dev_if->
+				   dev_global_regs->diepeachintmsk[ep->num]);
+		emp =
+		    DWC_READ_REG32(&dev_if->
+				   dev_global_regs->dtknqr4_fifoemptymsk);
+		msk |= ((emp >> ep->num) & 0x1) << 7;
+		v = DWC_READ_REG32(&dev_if->in_ep_regs[ep->num]->diepint) & msk;
+	} else {
+		msk = DWC_READ_REG32(&dev_if->dev_global_regs->diepmsk);
+		emp =
+		    DWC_READ_REG32(&dev_if->
+				   dev_global_regs->dtknqr4_fifoemptymsk);
+		msk |= ((emp >> ep->num) & 0x1) << 7;
+		v = DWC_READ_REG32(&dev_if->in_ep_regs[ep->num]->diepint) & msk;
+	}
+
+	return v;
+}
+
+/**
+ * This function returns the Device OUT EP Interrupt register
+ */
+static inline uint32_t dwc_otg_read_dev_out_ep_intr(dwc_otg_core_if_t *
+						    _core_if, dwc_ep_t * _ep)
+{
+	dwc_otg_dev_if_t *dev_if = _core_if->dev_if;
+	uint32_t v;
+	doepmsk_data_t msk = {.d32 = 0 };
+
+	if (_core_if->multiproc_int_enable) {
+		msk.d32 =
+		    DWC_READ_REG32(&dev_if->
+				   dev_global_regs->doepeachintmsk[_ep->num]);
+		if (_core_if->pti_enh_enable) {
+			msk.b.pktdrpsts = 1;
+		}
+		v = DWC_READ_REG32(&dev_if->
+				   out_ep_regs[_ep->num]->doepint) & msk.d32;
+	} else {
+		msk.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->doepmsk);
+		if (_core_if->pti_enh_enable) {
+			msk.b.pktdrpsts = 1;
+		}
+		v = DWC_READ_REG32(&dev_if->
+				   out_ep_regs[_ep->num]->doepint) & msk.d32;
+	}
+	return v;
+}
+
+/**
+ * This function returns the Host All Channel Interrupt register
+ */
+static inline uint32_t dwc_otg_read_host_all_channels_intr(dwc_otg_core_if_t *
+							   _core_if)
+{
+	return (DWC_READ_REG32(&_core_if->host_if->host_global_regs->haint));
+}
+
+static inline uint32_t dwc_otg_read_host_channel_intr(dwc_otg_core_if_t *
+						      _core_if, dwc_hc_t * _hc)
+{
+	return (DWC_READ_REG32
+		(&_core_if->host_if->hc_regs[_hc->hc_num]->hcint));
+}
+
+/**
+ * This function returns the mode of the operation, host or device.
+ *
+ * @return 0 - Device Mode, 1 - Host Mode
+ */
+static inline uint32_t dwc_otg_mode(dwc_otg_core_if_t * _core_if)
+{
+	return (DWC_READ_REG32(&_core_if->core_global_regs->gintsts) & 0x1);
+}
+
+/**@}*/
+
+/**
+ * DWC_otg CIL callback structure. This structure allows the HCD and
+ * PCD to register functions used for starting and stopping the PCD
+ * and HCD for role change on for a DRD.
+ */
+typedef struct dwc_otg_cil_callbacks {
+	/** Start function for role change */
+	int (*start) (void *_p);
+	/** Stop Function for role change */
+	int (*stop) (void *_p);
+	/** Disconnect Function for role change */
+	int (*disconnect) (void *_p);
+	/** Resume/Remote wakeup Function */
+	int (*resume_wakeup) (void *_p);
+	/** Suspend function */
+	int (*suspend) (void *_p);
+	/** Session Start (SRP) */
+	int (*session_start) (void *_p);
+#ifdef CONFIG_USB_DWC_OTG_LPM
+	/** Sleep (switch to L0 state) */
+	int (*sleep) (void *_p);
+#endif
+	/** Pointer passed to start() and stop() */
+	void *p;
+} dwc_otg_cil_callbacks_t;
+
+extern void dwc_otg_cil_register_pcd_callbacks(dwc_otg_core_if_t * _core_if,
+					       dwc_otg_cil_callbacks_t * _cb,
+					       void *_p);
+extern void dwc_otg_cil_register_hcd_callbacks(dwc_otg_core_if_t * _core_if,
+					       dwc_otg_cil_callbacks_t * _cb,
+					       void *_p);
+
+void dwc_otg_initiate_srp(dwc_otg_core_if_t * core_if);
+
+//////////////////////////////////////////////////////////////////////
+/** Start the HCD.  Helper function for using the HCD callbacks.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+static inline void cil_hcd_start(dwc_otg_core_if_t * core_if)
+{
+	if (core_if->hcd_cb && core_if->hcd_cb->start) {
+		core_if->hcd_cb->start(core_if->hcd_cb->p);
+	}
+}
+
+/** Stop the HCD.  Helper function for using the HCD callbacks.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+static inline void cil_hcd_stop(dwc_otg_core_if_t * core_if)
+{
+	if (core_if->hcd_cb && core_if->hcd_cb->stop) {
+		core_if->hcd_cb->stop(core_if->hcd_cb->p);
+	}
+}
+
+/** Disconnect the HCD.  Helper function for using the HCD callbacks.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+static inline void cil_hcd_disconnect(dwc_otg_core_if_t * core_if)
+{
+	if (core_if->hcd_cb && core_if->hcd_cb->disconnect) {
+		core_if->hcd_cb->disconnect(core_if->hcd_cb->p);
+	}
+}
+
+/** Inform the HCD the a New Session has begun.  Helper function for
+ * using the HCD callbacks.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+static inline void cil_hcd_session_start(dwc_otg_core_if_t * core_if)
+{
+	if (core_if->hcd_cb && core_if->hcd_cb->session_start) {
+		core_if->hcd_cb->session_start(core_if->hcd_cb->p);
+	}
+}
+
+#ifdef CONFIG_USB_DWC_OTG_LPM
+/**
+ * Inform the HCD about LPM sleep.
+ * Helper function for using the HCD callbacks.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+static inline void cil_hcd_sleep(dwc_otg_core_if_t * core_if)
+{
+	if (core_if->hcd_cb && core_if->hcd_cb->sleep) {
+		core_if->hcd_cb->sleep(core_if->hcd_cb->p);
+	}
+}
+#endif
+
+/** Resume the HCD.  Helper function for using the HCD callbacks.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+static inline void cil_hcd_resume(dwc_otg_core_if_t * core_if)
+{
+	if (core_if->hcd_cb && core_if->hcd_cb->resume_wakeup) {
+		core_if->hcd_cb->resume_wakeup(core_if->hcd_cb->p);
+	}
+}
+
+/** Start the PCD.  Helper function for using the PCD callbacks.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+static inline void cil_pcd_start(dwc_otg_core_if_t * core_if)
+{
+	if (core_if->pcd_cb && core_if->pcd_cb->start) {
+		core_if->pcd_cb->start(core_if->pcd_cb->p);
+	}
+}
+
+/** Stop the PCD.  Helper function for using the PCD callbacks.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+static inline void cil_pcd_stop(dwc_otg_core_if_t * core_if)
+{
+	if (core_if->pcd_cb && core_if->pcd_cb->stop) {
+		core_if->pcd_cb->stop(core_if->pcd_cb->p);
+	}
+}
+
+/** Suspend the PCD.  Helper function for using the PCD callbacks.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+static inline void cil_pcd_suspend(dwc_otg_core_if_t * core_if)
+{
+	if (core_if->pcd_cb && core_if->pcd_cb->suspend) {
+		core_if->pcd_cb->suspend(core_if->pcd_cb->p);
+	}
+}
+
+/** Resume the PCD.  Helper function for using the PCD callbacks.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+static inline void cil_pcd_resume(dwc_otg_core_if_t * core_if)
+{
+	if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup) {
+		core_if->pcd_cb->resume_wakeup(core_if->pcd_cb->p);
+	}
+}
+
+//////////////////////////////////////////////////////////////////////
+
+#endif
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_cil_intr.c b/drivers/usb/host/dwc_otg/dwc_otg_cil_intr.c
new file mode 100644
index 000000000000..57a9fe4e5ee0
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_cil_intr.c
@@ -0,0 +1,1578 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_cil_intr.c $
+ * $Revision: #32 $
+ * $Date: 2012/08/10 $
+ * $Change: 2047372 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+
+/** @file
+ *
+ * The Core Interface Layer provides basic services for accessing and
+ * managing the DWC_otg hardware. These services are used by both the
+ * Host Controller Driver and the Peripheral Controller Driver.
+ *
+ * This file contains the Common Interrupt handlers.
+ */
+#include "dwc_os.h"
+#include "dwc_otg_regs.h"
+#include "dwc_otg_cil.h"
+#include "dwc_otg_driver.h"
+#include "dwc_otg_pcd.h"
+#include "dwc_otg_hcd.h"
+
+#ifdef DEBUG
+inline const char *op_state_str(dwc_otg_core_if_t * core_if)
+{
+	return (core_if->op_state == A_HOST ? "a_host" :
+		(core_if->op_state == A_SUSPEND ? "a_suspend" :
+		 (core_if->op_state == A_PERIPHERAL ? "a_peripheral" :
+		  (core_if->op_state == B_PERIPHERAL ? "b_peripheral" :
+		   (core_if->op_state == B_HOST ? "b_host" : "unknown")))));
+}
+#endif
+
+/** This function will log a debug message
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+static int32_t dwc_otg_handle_mode_mismatch_intr(dwc_otg_core_if_t * core_if)
+{
+	gintsts_data_t gintsts;
+	DWC_WARN("Mode Mismatch Interrupt: currently in %s mode\n",
+		 dwc_otg_mode(core_if) ? "Host" : "Device");
+
+	/* Clear interrupt */
+	gintsts.d32 = 0;
+	gintsts.b.modemismatch = 1;
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32);
+	return 1;
+}
+
+/**
+ * This function handles the OTG Interrupts. It reads the OTG
+ * Interrupt Register (GOTGINT) to determine what interrupt has
+ * occurred.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+static int32_t dwc_otg_handle_otg_intr(dwc_otg_core_if_t * core_if)
+{
+	dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs;
+	gotgint_data_t gotgint;
+	gotgctl_data_t gotgctl;
+	gintmsk_data_t gintmsk;
+	gpwrdn_data_t gpwrdn;
+
+	gotgint.d32 = DWC_READ_REG32(&global_regs->gotgint);
+	gotgctl.d32 = DWC_READ_REG32(&global_regs->gotgctl);
+	DWC_DEBUGPL(DBG_CIL, "++OTG Interrupt gotgint=%0x [%s]\n", gotgint.d32,
+		    op_state_str(core_if));
+
+	if (gotgint.b.sesenddet) {
+		DWC_DEBUGPL(DBG_ANY, " ++OTG Interrupt: "
+			    "Session End Detected++ (%s)\n",
+			    op_state_str(core_if));
+		gotgctl.d32 = DWC_READ_REG32(&global_regs->gotgctl);
+
+		if (core_if->op_state == B_HOST) {
+			cil_pcd_start(core_if);
+			core_if->op_state = B_PERIPHERAL;
+		} else {
+			/* If not B_HOST and Device HNP still set. HNP
+			 * Did not succeed!*/
+			if (gotgctl.b.devhnpen) {
+				DWC_DEBUGPL(DBG_ANY, "Session End Detected\n");
+				__DWC_ERROR("Device Not Connected/Responding!\n");
+			}
+
+			/* If Session End Detected the B-Cable has
+			 * been disconnected. */
+			/* Reset PCD and Gadget driver to a
+			 * clean state. */
+			core_if->lx_state = DWC_OTG_L0;
+			DWC_SPINUNLOCK(core_if->lock);
+			cil_pcd_stop(core_if);
+			DWC_SPINLOCK(core_if->lock);
+
+			if (core_if->adp_enable) {
+				if (core_if->power_down == 2) {
+					gpwrdn.d32 = 0;
+					gpwrdn.b.pwrdnswtch = 1;
+					DWC_MODIFY_REG32(&core_if->
+							 core_global_regs->
+							 gpwrdn, gpwrdn.d32, 0);
+				}
+
+				gpwrdn.d32 = 0;
+				gpwrdn.b.pmuintsel = 1;
+				gpwrdn.b.pmuactv = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->
+						 gpwrdn, 0, gpwrdn.d32);
+
+				dwc_otg_adp_sense_start(core_if);
+			}
+		}
+
+		gotgctl.d32 = 0;
+		gotgctl.b.devhnpen = 1;
+		DWC_MODIFY_REG32(&global_regs->gotgctl, gotgctl.d32, 0);
+	}
+	if (gotgint.b.sesreqsucstschng) {
+		DWC_DEBUGPL(DBG_ANY, " ++OTG Interrupt: "
+			    "Session Reqeust Success Status Change++\n");
+		gotgctl.d32 = DWC_READ_REG32(&global_regs->gotgctl);
+		if (gotgctl.b.sesreqscs) {
+
+			if ((core_if->core_params->phy_type ==
+			     DWC_PHY_TYPE_PARAM_FS) && (core_if->core_params->i2c_enable)) {
+				core_if->srp_success = 1;
+			} else {
+				DWC_SPINUNLOCK(core_if->lock);
+				cil_pcd_resume(core_if);
+				DWC_SPINLOCK(core_if->lock);
+				/* Clear Session Request */
+				gotgctl.d32 = 0;
+				gotgctl.b.sesreq = 1;
+				DWC_MODIFY_REG32(&global_regs->gotgctl,
+						 gotgctl.d32, 0);
+			}
+		}
+	}
+	if (gotgint.b.hstnegsucstschng) {
+		/* Print statements during the HNP interrupt handling
+		 * can cause it to fail.*/
+		gotgctl.d32 = DWC_READ_REG32(&global_regs->gotgctl);
+		/* WA for 3.00a- HW is not setting cur_mode, even sometimes
+		 * this does not help*/
+		if (core_if->snpsid >= OTG_CORE_REV_3_00a)
+			dwc_udelay(100);
+		if (gotgctl.b.hstnegscs) {
+			if (dwc_otg_is_host_mode(core_if)) {
+				core_if->op_state = B_HOST;
+				/*
+				 * Need to disable SOF interrupt immediately.
+				 * When switching from device to host, the PCD
+				 * interrupt handler won't handle the
+				 * interrupt if host mode is already set. The
+				 * HCD interrupt handler won't get called if
+				 * the HCD state is HALT. This means that the
+				 * interrupt does not get handled and Linux
+				 * complains loudly.
+				 */
+				gintmsk.d32 = 0;
+				gintmsk.b.sofintr = 1;
+				DWC_MODIFY_REG32(&global_regs->gintmsk,
+						 gintmsk.d32, 0);
+				/* Call callback function with spin lock released */
+				DWC_SPINUNLOCK(core_if->lock);
+				cil_pcd_stop(core_if);
+				/*
+				 * Initialize the Core for Host mode.
+				 */
+				cil_hcd_start(core_if);
+				DWC_SPINLOCK(core_if->lock);
+				core_if->op_state = B_HOST;
+			}
+		} else {
+			gotgctl.d32 = 0;
+			gotgctl.b.hnpreq = 1;
+			gotgctl.b.devhnpen = 1;
+			DWC_MODIFY_REG32(&global_regs->gotgctl, gotgctl.d32, 0);
+			DWC_DEBUGPL(DBG_ANY, "HNP Failed\n");
+			__DWC_ERROR("Device Not Connected/Responding\n");
+		}
+	}
+	if (gotgint.b.hstnegdet) {
+		/* The disconnect interrupt is set at the same time as
+		 * Host Negotiation Detected.  During the mode
+		 * switch all interrupts are cleared so the disconnect
+		 * interrupt handler will not get executed.
+		 */
+		DWC_DEBUGPL(DBG_ANY, " ++OTG Interrupt: "
+			    "Host Negotiation Detected++ (%s)\n",
+			    (dwc_otg_is_host_mode(core_if) ? "Host" :
+			     "Device"));
+		if (dwc_otg_is_device_mode(core_if)) {
+			DWC_DEBUGPL(DBG_ANY, "a_suspend->a_peripheral (%d)\n",
+				    core_if->op_state);
+			DWC_SPINUNLOCK(core_if->lock);
+			cil_hcd_disconnect(core_if);
+			cil_pcd_start(core_if);
+			DWC_SPINLOCK(core_if->lock);
+			core_if->op_state = A_PERIPHERAL;
+		} else {
+			/*
+			 * Need to disable SOF interrupt immediately. When
+			 * switching from device to host, the PCD interrupt
+			 * handler won't handle the interrupt if host mode is
+			 * already set. The HCD interrupt handler won't get
+			 * called if the HCD state is HALT. This means that
+			 * the interrupt does not get handled and Linux
+			 * complains loudly.
+			 */
+			gintmsk.d32 = 0;
+			gintmsk.b.sofintr = 1;
+			DWC_MODIFY_REG32(&global_regs->gintmsk, gintmsk.d32, 0);
+			DWC_SPINUNLOCK(core_if->lock);
+			cil_pcd_stop(core_if);
+			cil_hcd_start(core_if);
+			DWC_SPINLOCK(core_if->lock);
+			core_if->op_state = A_HOST;
+		}
+	}
+	if (gotgint.b.adevtoutchng) {
+		DWC_DEBUGPL(DBG_ANY, " ++OTG Interrupt: "
+			    "A-Device Timeout Change++\n");
+	}
+	if (gotgint.b.debdone) {
+		DWC_DEBUGPL(DBG_ANY, " ++OTG Interrupt: " "Debounce Done++\n");
+	}
+
+	/* Clear GOTGINT */
+	DWC_WRITE_REG32(&core_if->core_global_regs->gotgint, gotgint.d32);
+
+	return 1;
+}
+
+void w_conn_id_status_change(void *p)
+{
+	dwc_otg_core_if_t *core_if = p;
+	uint32_t count = 0;
+	gotgctl_data_t gotgctl = {.d32 = 0 };
+
+	gotgctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->gotgctl);
+	DWC_DEBUGPL(DBG_CIL, "gotgctl=%0x\n", gotgctl.d32);
+	DWC_DEBUGPL(DBG_CIL, "gotgctl.b.conidsts=%d\n", gotgctl.b.conidsts);
+
+	/* B-Device connector (Device Mode) */
+	if (gotgctl.b.conidsts) {
+		/* Wait for switch to device mode. */
+		while (!dwc_otg_is_device_mode(core_if)) {
+			DWC_PRINTF("Waiting for Peripheral Mode, Mode=%s\n",
+				   (dwc_otg_is_host_mode(core_if) ? "Host" :
+				    "Peripheral"));
+			dwc_mdelay(100);
+			if (++count > 10000)
+				break;
+		}
+		DWC_ASSERT(++count < 10000,
+			   "Connection id status change timed out");
+		core_if->op_state = B_PERIPHERAL;
+		dwc_otg_core_init(core_if);
+		dwc_otg_enable_global_interrupts(core_if);
+		cil_pcd_start(core_if);
+	} else {
+		/* A-Device connector (Host Mode) */
+		while (!dwc_otg_is_host_mode(core_if)) {
+			DWC_PRINTF("Waiting for Host Mode, Mode=%s\n",
+				   (dwc_otg_is_host_mode(core_if) ? "Host" :
+				    "Peripheral"));
+			dwc_mdelay(100);
+			if (++count > 10000)
+				break;
+		}
+		DWC_ASSERT(++count < 10000,
+			   "Connection id status change timed out");
+		core_if->op_state = A_HOST;
+		/*
+		 * Initialize the Core for Host mode.
+		 */
+		dwc_otg_core_init(core_if);
+		dwc_otg_enable_global_interrupts(core_if);
+		cil_hcd_start(core_if);
+	}
+}
+
+/**
+ * This function handles the Connector ID Status Change Interrupt.  It
+ * reads the OTG Interrupt Register (GOTCTL) to determine whether this
+ * is a Device to Host Mode transition or a Host Mode to Device
+ * Transition.
+ *
+ * This only occurs when the cable is connected/removed from the PHY
+ * connector.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+static int32_t dwc_otg_handle_conn_id_status_change_intr(dwc_otg_core_if_t * core_if)
+{
+
+	/*
+	 * Need to disable SOF interrupt immediately. If switching from device
+	 * to host, the PCD interrupt handler won't handle the interrupt if
+	 * host mode is already set. The HCD interrupt handler won't get
+	 * called if the HCD state is HALT. This means that the interrupt does
+	 * not get handled and Linux complains loudly.
+	 */
+	gintmsk_data_t gintmsk = {.d32 = 0 };
+	gintsts_data_t gintsts = {.d32 = 0 };
+
+	gintmsk.b.sofintr = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, gintmsk.d32, 0);
+
+	DWC_DEBUGPL(DBG_CIL,
+		    " ++Connector ID Status Change Interrupt++  (%s)\n",
+		    (dwc_otg_is_host_mode(core_if) ? "Host" : "Device"));
+
+	DWC_SPINUNLOCK(core_if->lock);
+
+	/*
+	 * Need to schedule a work, as there are possible DELAY function calls
+	 * Release lock before scheduling workq as it holds spinlock during scheduling
+	 */
+
+	DWC_WORKQ_SCHEDULE(core_if->wq_otg, w_conn_id_status_change,
+			   core_if, "connection id status change");
+	DWC_SPINLOCK(core_if->lock);
+
+	/* Set flag and clear interrupt */
+	gintsts.b.conidstschng = 1;
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32);
+
+	return 1;
+}
+
+/**
+ * This interrupt indicates that a device is initiating the Session
+ * Request Protocol to request the host to turn on bus power so a new
+ * session can begin. The handler responds by turning on bus power. If
+ * the DWC_otg controller is in low power mode, the handler brings the
+ * controller out of low power mode before turning on bus power.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+static int32_t dwc_otg_handle_session_req_intr(dwc_otg_core_if_t * core_if)
+{
+	gintsts_data_t gintsts;
+
+#ifndef DWC_HOST_ONLY
+	DWC_DEBUGPL(DBG_ANY, "++Session Request Interrupt++\n");
+
+	if (dwc_otg_is_device_mode(core_if)) {
+		DWC_PRINTF("SRP: Device mode\n");
+	} else {
+		hprt0_data_t hprt0;
+		DWC_PRINTF("SRP: Host mode\n");
+
+		/* Turn on the port power bit. */
+		hprt0.d32 = dwc_otg_read_hprt0(core_if);
+		hprt0.b.prtpwr = 1;
+		DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+
+		/* Start the Connection timer. So a message can be displayed
+		 * if connect does not occur within 10 seconds. */
+		cil_hcd_session_start(core_if);
+	}
+#endif
+
+	/* Clear interrupt */
+	gintsts.d32 = 0;
+	gintsts.b.sessreqintr = 1;
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32);
+
+	return 1;
+}
+
+void w_wakeup_detected(void *p)
+{
+	dwc_otg_core_if_t *core_if = (dwc_otg_core_if_t *) p;
+	/*
+	 * Clear the Resume after 70ms. (Need 20 ms minimum. Use 70 ms
+	 * so that OPT tests pass with all PHYs).
+	 */
+	hprt0_data_t hprt0 = {.d32 = 0 };
+#if 0
+	pcgcctl_data_t pcgcctl = {.d32 = 0 };
+	/* Restart the Phy Clock */
+	pcgcctl.b.stoppclk = 1;
+	DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, 0);
+	dwc_udelay(10);
+#endif //0
+	hprt0.d32 = dwc_otg_read_hprt0(core_if);
+	DWC_DEBUGPL(DBG_ANY, "Resume: HPRT0=%0x\n", hprt0.d32);
+//      dwc_mdelay(70);
+	hprt0.b.prtres = 0;	/* Resume */
+	DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+	DWC_DEBUGPL(DBG_ANY, "Clear Resume: HPRT0=%0x\n",
+		    DWC_READ_REG32(core_if->host_if->hprt0));
+
+	cil_hcd_resume(core_if);
+
+	/** Change to L0 state*/
+	core_if->lx_state = DWC_OTG_L0;
+}
+
+/**
+ * This interrupt indicates that the DWC_otg controller has detected a
+ * resume or remote wakeup sequence. If the DWC_otg controller is in
+ * low power mode, the handler must brings the controller out of low
+ * power mode. The controller automatically begins resume
+ * signaling. The handler schedules a time to stop resume signaling.
+ */
+static int32_t dwc_otg_handle_wakeup_detected_intr(dwc_otg_core_if_t * core_if)
+{
+	gintsts_data_t gintsts;
+
+	DWC_DEBUGPL(DBG_ANY,
+		    "++Resume and Remote Wakeup Detected Interrupt++\n");
+
+	DWC_PRINTF("%s lxstate = %d\n", __func__, core_if->lx_state);
+
+	if (dwc_otg_is_device_mode(core_if)) {
+		dctl_data_t dctl = {.d32 = 0 };
+		DWC_DEBUGPL(DBG_PCD, "DSTS=0x%0x\n",
+			    DWC_READ_REG32(&core_if->dev_if->dev_global_regs->
+					   dsts));
+		if (core_if->lx_state == DWC_OTG_L2) {
+#ifdef PARTIAL_POWER_DOWN
+			if (core_if->hwcfg4.b.power_optimiz) {
+				pcgcctl_data_t power = {.d32 = 0 };
+
+				power.d32 = DWC_READ_REG32(core_if->pcgcctl);
+				DWC_DEBUGPL(DBG_CIL, "PCGCCTL=%0x\n",
+					    power.d32);
+
+				power.b.stoppclk = 0;
+				DWC_WRITE_REG32(core_if->pcgcctl, power.d32);
+
+				power.b.pwrclmp = 0;
+				DWC_WRITE_REG32(core_if->pcgcctl, power.d32);
+
+				power.b.rstpdwnmodule = 0;
+				DWC_WRITE_REG32(core_if->pcgcctl, power.d32);
+			}
+#endif
+			/* Clear the Remote Wakeup Signaling */
+			dctl.b.rmtwkupsig = 1;
+			DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->
+					 dctl, dctl.d32, 0);
+
+			DWC_SPINUNLOCK(core_if->lock);
+			if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup) {
+				core_if->pcd_cb->resume_wakeup(core_if->pcd_cb->p);
+			}
+			DWC_SPINLOCK(core_if->lock);
+		} else {
+			glpmcfg_data_t lpmcfg;
+			lpmcfg.d32 =
+			    DWC_READ_REG32(&core_if->core_global_regs->glpmcfg);
+			lpmcfg.b.hird_thres &= (~(1 << 4));
+			DWC_WRITE_REG32(&core_if->core_global_regs->glpmcfg,
+					lpmcfg.d32);
+		}
+		/** Change to L0 state*/
+		core_if->lx_state = DWC_OTG_L0;
+	} else {
+		if (core_if->lx_state != DWC_OTG_L1) {
+			pcgcctl_data_t pcgcctl = {.d32 = 0 };
+
+			/* Restart the Phy Clock */
+			pcgcctl.b.stoppclk = 1;
+			DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, 0);
+			DWC_TIMER_SCHEDULE(core_if->wkp_timer, 71);
+		} else {
+			/** Change to L0 state*/
+			core_if->lx_state = DWC_OTG_L0;
+		}
+	}
+
+	/* Clear interrupt */
+	gintsts.d32 = 0;
+	gintsts.b.wkupintr = 1;
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32);
+
+	return 1;
+}
+
+/**
+ * This interrupt indicates that the Wakeup Logic has detected a
+ * Device disconnect.
+ */
+static int32_t dwc_otg_handle_pwrdn_disconnect_intr(dwc_otg_core_if_t *core_if)
+{
+	gpwrdn_data_t gpwrdn = { .d32 = 0 };
+	gpwrdn_data_t gpwrdn_temp = { .d32 = 0 };
+	gpwrdn_temp.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn);
+
+	DWC_PRINTF("%s called\n", __FUNCTION__);
+
+	if (!core_if->hibernation_suspend) {
+		DWC_PRINTF("Already exited from Hibernation\n");
+		return 1;
+	}
+
+	/* Switch on the voltage to the core */
+	gpwrdn.b.pwrdnswtch = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+	dwc_udelay(10);
+
+	/* Reset the core */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pwrdnrstn = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+	dwc_udelay(10);
+
+	/* Disable power clamps*/
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pwrdnclmp = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+	/* Remove reset the core signal */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pwrdnrstn = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32);
+	dwc_udelay(10);
+
+	/* Disable PMU interrupt */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pmuintsel = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+	core_if->hibernation_suspend = 0;
+
+	/* Disable PMU */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pmuactv = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+	dwc_udelay(10);
+
+	if (gpwrdn_temp.b.idsts) {
+		core_if->op_state = B_PERIPHERAL;
+		dwc_otg_core_init(core_if);
+		dwc_otg_enable_global_interrupts(core_if);
+		cil_pcd_start(core_if);
+	} else {
+		core_if->op_state = A_HOST;
+		dwc_otg_core_init(core_if);
+		dwc_otg_enable_global_interrupts(core_if);
+		cil_hcd_start(core_if);
+	}
+
+	return 1;
+}
+
+/**
+ * This interrupt indicates that the Wakeup Logic has detected a
+ * remote wakeup sequence.
+ */
+static int32_t dwc_otg_handle_pwrdn_wakeup_detected_intr(dwc_otg_core_if_t * core_if)
+{
+	gpwrdn_data_t gpwrdn = {.d32 = 0 };
+	DWC_DEBUGPL(DBG_ANY,
+		    "++Powerdown Remote Wakeup Detected Interrupt++\n");
+
+	if (!core_if->hibernation_suspend) {
+		DWC_PRINTF("Already exited from Hibernation\n");
+		return 1;
+	}
+
+	gpwrdn.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn);
+	if (gpwrdn.b.idsts) {	// Device Mode
+		if ((core_if->power_down == 2)
+		    && (core_if->hibernation_suspend == 1)) {
+			dwc_otg_device_hibernation_restore(core_if, 0, 0);
+		}
+	} else {
+		if ((core_if->power_down == 2)
+		    && (core_if->hibernation_suspend == 1)) {
+			dwc_otg_host_hibernation_restore(core_if, 1, 0);
+		}
+	}
+	return 1;
+}
+
+static int32_t dwc_otg_handle_pwrdn_idsts_change(dwc_otg_device_t *otg_dev)
+{
+	gpwrdn_data_t gpwrdn = {.d32 = 0 };
+	gpwrdn_data_t gpwrdn_temp = {.d32 = 0 };
+	dwc_otg_core_if_t *core_if = otg_dev->core_if;
+
+	DWC_DEBUGPL(DBG_ANY, "%s called\n", __FUNCTION__);
+	gpwrdn_temp.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn);
+	if (core_if->power_down == 2) {
+		if (!core_if->hibernation_suspend) {
+			DWC_PRINTF("Already exited from Hibernation\n");
+			return 1;
+		}
+		DWC_DEBUGPL(DBG_ANY, "Exit from hibernation on ID sts change\n");
+		/* Switch on the voltage to the core */
+		gpwrdn.b.pwrdnswtch = 1;
+		DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+		dwc_udelay(10);
+
+		/* Reset the core */
+		gpwrdn.d32 = 0;
+		gpwrdn.b.pwrdnrstn = 1;
+		DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+		dwc_udelay(10);
+
+		/* Disable power clamps */
+		gpwrdn.d32 = 0;
+		gpwrdn.b.pwrdnclmp = 1;
+		DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+		/* Remove reset the core signal */
+		gpwrdn.d32 = 0;
+		gpwrdn.b.pwrdnrstn = 1;
+		DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32);
+		dwc_udelay(10);
+
+		/* Disable PMU interrupt */
+		gpwrdn.d32 = 0;
+		gpwrdn.b.pmuintsel = 1;
+		DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+		/*Indicates that we are exiting from hibernation */
+		core_if->hibernation_suspend = 0;
+
+		/* Disable PMU */
+		gpwrdn.d32 = 0;
+		gpwrdn.b.pmuactv = 1;
+		DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+		dwc_udelay(10);
+
+		gpwrdn.d32 = core_if->gr_backup->gpwrdn_local;
+		if (gpwrdn.b.dis_vbus == 1) {
+			gpwrdn.d32 = 0;
+			gpwrdn.b.dis_vbus = 1;
+			DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+		}
+
+		if (gpwrdn_temp.b.idsts) {
+			core_if->op_state = B_PERIPHERAL;
+			dwc_otg_core_init(core_if);
+			dwc_otg_enable_global_interrupts(core_if);
+			cil_pcd_start(core_if);
+		} else {
+			core_if->op_state = A_HOST;
+			dwc_otg_core_init(core_if);
+			dwc_otg_enable_global_interrupts(core_if);
+			cil_hcd_start(core_if);
+		}
+	}
+
+	if (core_if->adp_enable) {
+		uint8_t is_host = 0;
+		DWC_SPINUNLOCK(core_if->lock);
+		/* Change the core_if's lock to hcd/pcd lock depend on mode? */
+#ifndef DWC_HOST_ONLY
+		if (gpwrdn_temp.b.idsts)
+			core_if->lock = otg_dev->pcd->lock;
+#endif
+#ifndef DWC_DEVICE_ONLY
+		if (!gpwrdn_temp.b.idsts) {
+				core_if->lock = otg_dev->hcd->lock;
+				is_host = 1;
+		}
+#endif
+		DWC_PRINTF("RESTART ADP\n");
+		if (core_if->adp.probe_enabled)
+			dwc_otg_adp_probe_stop(core_if);
+		if (core_if->adp.sense_enabled)
+			dwc_otg_adp_sense_stop(core_if);
+		if (core_if->adp.sense_timer_started)
+			DWC_TIMER_CANCEL(core_if->adp.sense_timer);
+		if (core_if->adp.vbuson_timer_started)
+			DWC_TIMER_CANCEL(core_if->adp.vbuson_timer);
+		core_if->adp.probe_timer_values[0] = -1;
+		core_if->adp.probe_timer_values[1] = -1;
+		core_if->adp.sense_timer_started = 0;
+		core_if->adp.vbuson_timer_started = 0;
+		core_if->adp.probe_counter = 0;
+		core_if->adp.gpwrdn = 0;
+
+		/* Disable PMU and restart ADP */
+		gpwrdn_temp.d32 = 0;
+		gpwrdn_temp.b.pmuactv = 1;
+		gpwrdn_temp.b.pmuintsel = 1;
+		DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+		DWC_PRINTF("Check point 1\n");
+		dwc_mdelay(110);
+		dwc_otg_adp_start(core_if, is_host);
+		DWC_SPINLOCK(core_if->lock);
+	}
+
+
+	return 1;
+}
+
+static int32_t dwc_otg_handle_pwrdn_session_change(dwc_otg_core_if_t * core_if)
+{
+	gpwrdn_data_t gpwrdn = {.d32 = 0 };
+	int32_t otg_cap_param = core_if->core_params->otg_cap;
+	DWC_DEBUGPL(DBG_ANY, "%s called\n", __FUNCTION__);
+
+	gpwrdn.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn);
+	if (core_if->power_down == 2) {
+		if (!core_if->hibernation_suspend) {
+			DWC_PRINTF("Already exited from Hibernation\n");
+			return 1;
+		}
+
+		if ((otg_cap_param != DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE ||
+			 otg_cap_param != DWC_OTG_CAP_PARAM_SRP_ONLY_CAPABLE) &&
+			gpwrdn.b.bsessvld == 0) {
+			/* Save gpwrdn register for further usage if stschng interrupt */
+			core_if->gr_backup->gpwrdn_local =
+				DWC_READ_REG32(&core_if->core_global_regs->gpwrdn);
+			/*Exit from ISR and wait for stschng interrupt with bsessvld = 1 */
+			return 1;
+		}
+
+		/* Switch on the voltage to the core */
+		gpwrdn.d32 = 0;
+		gpwrdn.b.pwrdnswtch = 1;
+		DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+		dwc_udelay(10);
+
+		/* Reset the core */
+		gpwrdn.d32 = 0;
+		gpwrdn.b.pwrdnrstn = 1;
+		DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+		dwc_udelay(10);
+
+		/* Disable power clamps */
+		gpwrdn.d32 = 0;
+		gpwrdn.b.pwrdnclmp = 1;
+		DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+		/* Remove reset the core signal */
+		gpwrdn.d32 = 0;
+		gpwrdn.b.pwrdnrstn = 1;
+		DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32);
+		dwc_udelay(10);
+
+		/* Disable PMU interrupt */
+		gpwrdn.d32 = 0;
+		gpwrdn.b.pmuintsel = 1;
+		DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+		dwc_udelay(10);
+
+		/*Indicates that we are exiting from hibernation */
+		core_if->hibernation_suspend = 0;
+
+		/* Disable PMU */
+		gpwrdn.d32 = 0;
+		gpwrdn.b.pmuactv = 1;
+		DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+		dwc_udelay(10);
+
+		core_if->op_state = B_PERIPHERAL;
+		dwc_otg_core_init(core_if);
+		dwc_otg_enable_global_interrupts(core_if);
+		cil_pcd_start(core_if);
+
+		if (otg_cap_param == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE ||
+			otg_cap_param == DWC_OTG_CAP_PARAM_SRP_ONLY_CAPABLE) {
+			/*
+			 * Initiate SRP after initial ADP probe.
+			 */
+			dwc_otg_initiate_srp(core_if);
+		}
+	}
+
+	return 1;
+}
+/**
+ * This interrupt indicates that the Wakeup Logic has detected a
+ * status change either on IDDIG or BSessVld.
+ */
+static uint32_t dwc_otg_handle_pwrdn_stschng_intr(dwc_otg_device_t *otg_dev)
+{
+	uint32_t retval = 0;
+	gpwrdn_data_t gpwrdn = {.d32 = 0 };
+	gpwrdn_data_t gpwrdn_temp = {.d32 = 0 };
+	dwc_otg_core_if_t *core_if = otg_dev->core_if;
+
+	DWC_PRINTF("%s called\n", __FUNCTION__);
+
+	if (core_if->power_down == 2) {
+		if (core_if->hibernation_suspend <= 0) {
+			DWC_PRINTF("Already exited from Hibernation\n");
+			return 1;
+		} else
+			gpwrdn_temp.d32 = core_if->gr_backup->gpwrdn_local;
+
+	} else {
+		gpwrdn_temp.d32 = core_if->adp.gpwrdn;
+	}
+
+	gpwrdn.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn);
+
+	if (gpwrdn.b.idsts ^ gpwrdn_temp.b.idsts) {
+		retval = dwc_otg_handle_pwrdn_idsts_change(otg_dev);
+	} else if (gpwrdn.b.bsessvld ^ gpwrdn_temp.b.bsessvld) {
+		retval = dwc_otg_handle_pwrdn_session_change(core_if);
+	}
+
+	return retval;
+}
+
+/**
+ * This interrupt indicates that the Wakeup Logic has detected a
+ * SRP.
+ */
+static int32_t dwc_otg_handle_pwrdn_srp_intr(dwc_otg_core_if_t * core_if)
+{
+	gpwrdn_data_t gpwrdn = {.d32 = 0 };
+
+	DWC_PRINTF("%s called\n", __FUNCTION__);
+
+	if (!core_if->hibernation_suspend) {
+		DWC_PRINTF("Already exited from Hibernation\n");
+		return 1;
+	}
+#ifdef DWC_DEV_SRPCAP
+	if (core_if->pwron_timer_started) {
+		core_if->pwron_timer_started = 0;
+		DWC_TIMER_CANCEL(core_if->pwron_timer);
+	}
+#endif
+
+	/* Switch on the voltage to the core */
+	gpwrdn.b.pwrdnswtch = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+	dwc_udelay(10);
+
+	/* Reset the core */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pwrdnrstn = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+	dwc_udelay(10);
+
+	/* Disable power clamps */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pwrdnclmp = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+	/* Remove reset the core signal */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pwrdnrstn = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32);
+	dwc_udelay(10);
+
+	/* Disable PMU interrupt */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pmuintsel = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+	/* Indicates that we are exiting from hibernation */
+	core_if->hibernation_suspend = 0;
+
+	/* Disable PMU */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pmuactv = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+	dwc_udelay(10);
+
+	/* Programm Disable VBUS to 0 */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.dis_vbus = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+	/*Initialize the core as Host */
+	core_if->op_state = A_HOST;
+	dwc_otg_core_init(core_if);
+	dwc_otg_enable_global_interrupts(core_if);
+	cil_hcd_start(core_if);
+
+	return 1;
+}
+
+/**
+ * This interrupt indicates that a device has been disconnected from
+ * the root port.
+ */
+static int32_t dwc_otg_handle_disconnect_intr(dwc_otg_core_if_t * core_if)
+{
+	gintsts_data_t gintsts;
+
+	DWC_DEBUGPL(DBG_ANY, "++Disconnect Detected Interrupt++ (%s) %s\n",
+		    (dwc_otg_is_host_mode(core_if) ? "Host" : "Device"),
+		    op_state_str(core_if));
+
+/** @todo Consolidate this if statement. */
+#ifndef DWC_HOST_ONLY
+	if (core_if->op_state == B_HOST) {
+		/* If in device mode Disconnect and stop the HCD, then
+		 * start the PCD. */
+		DWC_SPINUNLOCK(core_if->lock);
+		cil_hcd_disconnect(core_if);
+		cil_pcd_start(core_if);
+		DWC_SPINLOCK(core_if->lock);
+		core_if->op_state = B_PERIPHERAL;
+	} else if (dwc_otg_is_device_mode(core_if)) {
+		gotgctl_data_t gotgctl = {.d32 = 0 };
+		gotgctl.d32 =
+		    DWC_READ_REG32(&core_if->core_global_regs->gotgctl);
+		if (gotgctl.b.hstsethnpen == 1) {
+			/* Do nothing, if HNP in process the OTG
+			 * interrupt "Host Negotiation Detected"
+			 * interrupt will do the mode switch.
+			 */
+		} else if (gotgctl.b.devhnpen == 0) {
+			/* If in device mode Disconnect and stop the HCD, then
+			 * start the PCD. */
+			DWC_SPINUNLOCK(core_if->lock);
+			cil_hcd_disconnect(core_if);
+			cil_pcd_start(core_if);
+			DWC_SPINLOCK(core_if->lock);
+			core_if->op_state = B_PERIPHERAL;
+		} else {
+			DWC_DEBUGPL(DBG_ANY, "!a_peripheral && !devhnpen\n");
+		}
+	} else {
+		if (core_if->op_state == A_HOST) {
+			/* A-Cable still connected but device disconnected. */
+			DWC_SPINUNLOCK(core_if->lock);
+			cil_hcd_disconnect(core_if);
+			DWC_SPINLOCK(core_if->lock);
+			if (core_if->adp_enable) {
+				gpwrdn_data_t gpwrdn = { .d32 = 0 };
+				cil_hcd_stop(core_if);
+				/* Enable Power Down Logic */
+				gpwrdn.b.pmuintsel = 1;
+				gpwrdn.b.pmuactv = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->
+						 gpwrdn, 0, gpwrdn.d32);
+				dwc_otg_adp_probe_start(core_if);
+
+				/* Power off the core */
+				if (core_if->power_down == 2) {
+					gpwrdn.d32 = 0;
+					gpwrdn.b.pwrdnswtch = 1;
+					DWC_MODIFY_REG32
+					    (&core_if->core_global_regs->gpwrdn,
+					     gpwrdn.d32, 0);
+				}
+			}
+		}
+	}
+#endif
+	/* Change to L3(OFF) state */
+	core_if->lx_state = DWC_OTG_L3;
+
+	gintsts.d32 = 0;
+	gintsts.b.disconnect = 1;
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32);
+	return 1;
+}
+
+/**
+ * This interrupt indicates that SUSPEND state has been detected on
+ * the USB.
+ *
+ * For HNP the USB Suspend interrupt signals the change from
+ * "a_peripheral" to "a_host".
+ *
+ * When power management is enabled the core will be put in low power
+ * mode.
+ */
+static int32_t dwc_otg_handle_usb_suspend_intr(dwc_otg_core_if_t * core_if)
+{
+	dsts_data_t dsts;
+	gintsts_data_t gintsts;
+	dcfg_data_t dcfg;
+
+	DWC_DEBUGPL(DBG_ANY, "USB SUSPEND\n");
+
+	if (dwc_otg_is_device_mode(core_if)) {
+		/* Check the Device status register to determine if the Suspend
+		 * state is active. */
+		dsts.d32 =
+		    DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts);
+		DWC_DEBUGPL(DBG_PCD, "DSTS=0x%0x\n", dsts.d32);
+		DWC_DEBUGPL(DBG_PCD, "DSTS.Suspend Status=%d "
+			    "HWCFG4.power Optimize=%d\n",
+			    dsts.b.suspsts, core_if->hwcfg4.b.power_optimiz);
+
+#ifdef PARTIAL_POWER_DOWN
+/** @todo Add a module parameter for power management. */
+
+		if (dsts.b.suspsts && core_if->hwcfg4.b.power_optimiz) {
+			pcgcctl_data_t power = {.d32 = 0 };
+			DWC_DEBUGPL(DBG_CIL, "suspend\n");
+
+			power.b.pwrclmp = 1;
+			DWC_WRITE_REG32(core_if->pcgcctl, power.d32);
+
+			power.b.rstpdwnmodule = 1;
+			DWC_MODIFY_REG32(core_if->pcgcctl, 0, power.d32);
+
+			power.b.stoppclk = 1;
+			DWC_MODIFY_REG32(core_if->pcgcctl, 0, power.d32);
+
+		} else {
+			DWC_DEBUGPL(DBG_ANY, "disconnect?\n");
+		}
+#endif
+		/* PCD callback for suspend. Release the lock inside of callback function */
+		cil_pcd_suspend(core_if);
+		if (core_if->power_down == 2)
+		{
+			dcfg.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg);
+			DWC_DEBUGPL(DBG_ANY,"lx_state = %08x\n",core_if->lx_state);
+			DWC_DEBUGPL(DBG_ANY," device address = %08d\n",dcfg.b.devaddr);
+
+			if (core_if->lx_state != DWC_OTG_L3 && dcfg.b.devaddr) {
+				pcgcctl_data_t pcgcctl = {.d32 = 0 };
+				gpwrdn_data_t gpwrdn = {.d32 = 0 };
+				gusbcfg_data_t gusbcfg = {.d32 = 0 };
+
+				/* Change to L2(suspend) state */
+				core_if->lx_state = DWC_OTG_L2;
+
+				/* Clear interrupt in gintsts */
+				gintsts.d32 = 0;
+				gintsts.b.usbsuspend = 1;
+				DWC_WRITE_REG32(&core_if->core_global_regs->
+						gintsts, gintsts.d32);
+				DWC_PRINTF("Start of hibernation completed\n");
+				dwc_otg_save_global_regs(core_if);
+				dwc_otg_save_dev_regs(core_if);
+
+				gusbcfg.d32 =
+				    DWC_READ_REG32(&core_if->core_global_regs->
+						   gusbcfg);
+				if (gusbcfg.b.ulpi_utmi_sel == 1) {
+					/* ULPI interface */
+					/* Suspend the Phy Clock */
+					pcgcctl.d32 = 0;
+					pcgcctl.b.stoppclk = 1;
+					DWC_MODIFY_REG32(core_if->pcgcctl, 0,
+							 pcgcctl.d32);
+					dwc_udelay(10);
+					gpwrdn.b.pmuactv = 1;
+					DWC_MODIFY_REG32(&core_if->
+							 core_global_regs->
+							 gpwrdn, 0, gpwrdn.d32);
+				} else {
+					/* UTMI+ Interface */
+					gpwrdn.b.pmuactv = 1;
+					DWC_MODIFY_REG32(&core_if->
+							 core_global_regs->
+							 gpwrdn, 0, gpwrdn.d32);
+					dwc_udelay(10);
+					pcgcctl.b.stoppclk = 1;
+					DWC_MODIFY_REG32(core_if->pcgcctl, 0,
+							 pcgcctl.d32);
+					dwc_udelay(10);
+				}
+
+				/* Set flag to indicate that we are in hibernation */
+				core_if->hibernation_suspend = 1;
+				/* Enable interrupts from wake up logic */
+				gpwrdn.d32 = 0;
+				gpwrdn.b.pmuintsel = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->
+						 gpwrdn, 0, gpwrdn.d32);
+				dwc_udelay(10);
+
+				/* Unmask device mode interrupts in GPWRDN */
+				gpwrdn.d32 = 0;
+				gpwrdn.b.rst_det_msk = 1;
+				gpwrdn.b.lnstchng_msk = 1;
+				gpwrdn.b.sts_chngint_msk = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->
+						 gpwrdn, 0, gpwrdn.d32);
+				dwc_udelay(10);
+
+				/* Enable Power Down Clamp */
+				gpwrdn.d32 = 0;
+				gpwrdn.b.pwrdnclmp = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->
+						 gpwrdn, 0, gpwrdn.d32);
+				dwc_udelay(10);
+
+				/* Switch off VDD */
+				gpwrdn.d32 = 0;
+				gpwrdn.b.pwrdnswtch = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->
+						 gpwrdn, 0, gpwrdn.d32);
+
+				/* Save gpwrdn register for further usage if stschng interrupt */
+				core_if->gr_backup->gpwrdn_local =
+							DWC_READ_REG32(&core_if->core_global_regs->gpwrdn);
+				DWC_PRINTF("Hibernation completed\n");
+
+				return 1;
+			}
+		} else if (core_if->power_down == 3) {
+			pcgcctl_data_t pcgcctl = {.d32 = 0 };
+			dcfg.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dcfg);
+			DWC_DEBUGPL(DBG_ANY, "lx_state = %08x\n",core_if->lx_state);
+			DWC_DEBUGPL(DBG_ANY, " device address = %08d\n",dcfg.b.devaddr);
+
+			if (core_if->lx_state != DWC_OTG_L3 && dcfg.b.devaddr) {
+				DWC_DEBUGPL(DBG_ANY, "Start entering to extended hibernation\n");
+				core_if->xhib = 1;
+
+				/* Clear interrupt in gintsts */
+				gintsts.d32 = 0;
+				gintsts.b.usbsuspend = 1;
+				DWC_WRITE_REG32(&core_if->core_global_regs->
+					gintsts, gintsts.d32);
+
+				dwc_otg_save_global_regs(core_if);
+				dwc_otg_save_dev_regs(core_if);
+
+				/* Wait for 10 PHY clocks */
+				dwc_udelay(10);
+
+				/* Program GPIO register while entering to xHib */
+				DWC_WRITE_REG32(&core_if->core_global_regs->ggpio, 0x1);
+
+				pcgcctl.b.enbl_extnd_hiber = 1;
+				DWC_MODIFY_REG32(core_if->pcgcctl, 0, pcgcctl.d32);
+				DWC_MODIFY_REG32(core_if->pcgcctl, 0, pcgcctl.d32);
+
+				pcgcctl.d32 = 0;
+				pcgcctl.b.extnd_hiber_pwrclmp = 1;
+				DWC_MODIFY_REG32(core_if->pcgcctl, 0, pcgcctl.d32);
+
+				pcgcctl.d32 = 0;
+				pcgcctl.b.extnd_hiber_switch = 1;
+				core_if->gr_backup->xhib_gpwrdn = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn);
+				core_if->gr_backup->xhib_pcgcctl = DWC_READ_REG32(core_if->pcgcctl) | pcgcctl.d32;
+				DWC_MODIFY_REG32(core_if->pcgcctl, 0, pcgcctl.d32);
+
+				DWC_DEBUGPL(DBG_ANY, "Finished entering to extended hibernation\n");
+
+				return 1;
+			}
+		}
+	} else {
+		if (core_if->op_state == A_PERIPHERAL) {
+			DWC_DEBUGPL(DBG_ANY, "a_peripheral->a_host\n");
+			/* Clear the a_peripheral flag, back to a_host. */
+			DWC_SPINUNLOCK(core_if->lock);
+			cil_pcd_stop(core_if);
+			cil_hcd_start(core_if);
+			DWC_SPINLOCK(core_if->lock);
+			core_if->op_state = A_HOST;
+		}
+	}
+
+	/* Change to L2(suspend) state */
+	core_if->lx_state = DWC_OTG_L2;
+
+	/* Clear interrupt */
+	gintsts.d32 = 0;
+	gintsts.b.usbsuspend = 1;
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32);
+
+	return 1;
+}
+
+static int32_t dwc_otg_handle_xhib_exit_intr(dwc_otg_core_if_t * core_if)
+{
+	gpwrdn_data_t gpwrdn = {.d32 = 0 };
+	pcgcctl_data_t pcgcctl = {.d32 = 0 };
+	gahbcfg_data_t gahbcfg = {.d32 = 0 };
+
+	dwc_udelay(10);
+
+	/* Program GPIO register while entering to xHib */
+	DWC_WRITE_REG32(&core_if->core_global_regs->ggpio, 0x0);
+
+	pcgcctl.d32 = core_if->gr_backup->xhib_pcgcctl;
+	pcgcctl.b.extnd_hiber_pwrclmp = 0;
+	DWC_WRITE_REG32(core_if->pcgcctl, pcgcctl.d32);
+	dwc_udelay(10);
+
+	gpwrdn.d32 = core_if->gr_backup->xhib_gpwrdn;
+	gpwrdn.b.restore = 1;
+	DWC_WRITE_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32);
+	dwc_udelay(10);
+
+	restore_lpm_i2c_regs(core_if);
+
+	pcgcctl.d32 = core_if->gr_backup->pcgcctl_local & (0x3FFFF << 14);
+	pcgcctl.b.max_xcvrselect = 1;
+	pcgcctl.b.ess_reg_restored = 0;
+	pcgcctl.b.extnd_hiber_switch = 0;
+	pcgcctl.b.extnd_hiber_pwrclmp = 0;
+	pcgcctl.b.enbl_extnd_hiber = 1;
+	DWC_WRITE_REG32(core_if->pcgcctl, pcgcctl.d32);
+
+	gahbcfg.d32 = core_if->gr_backup->gahbcfg_local;
+	gahbcfg.b.glblintrmsk = 1;
+	DWC_WRITE_REG32(&core_if->core_global_regs->gahbcfg, gahbcfg.d32);
+
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, 0xFFFFFFFF);
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, 0x1 << 16);
+
+	DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg,
+			core_if->gr_backup->gusbcfg_local);
+	DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dcfg,
+			core_if->dr_backup->dcfg);
+
+	pcgcctl.d32 = 0;
+	pcgcctl.d32 = core_if->gr_backup->pcgcctl_local & (0x3FFFF << 14);
+	pcgcctl.b.max_xcvrselect = 1;
+	pcgcctl.d32 |= 0x608;
+	DWC_WRITE_REG32(core_if->pcgcctl, pcgcctl.d32);
+	dwc_udelay(10);
+
+	pcgcctl.d32 = 0;
+	pcgcctl.d32 = core_if->gr_backup->pcgcctl_local & (0x3FFFF << 14);
+	pcgcctl.b.max_xcvrselect = 1;
+	pcgcctl.b.ess_reg_restored = 1;
+	pcgcctl.b.enbl_extnd_hiber = 1;
+	pcgcctl.b.rstpdwnmodule = 1;
+	pcgcctl.b.restoremode = 1;
+	DWC_WRITE_REG32(core_if->pcgcctl, pcgcctl.d32);
+
+	DWC_DEBUGPL(DBG_ANY, "%s called\n", __FUNCTION__);
+
+	return 1;
+}
+
+#ifdef CONFIG_USB_DWC_OTG_LPM
+/**
+ * This function hadles LPM transaction received interrupt.
+ */
+static int32_t dwc_otg_handle_lpm_intr(dwc_otg_core_if_t * core_if)
+{
+	glpmcfg_data_t lpmcfg;
+	gintsts_data_t gintsts;
+
+	if (!core_if->core_params->lpm_enable) {
+		DWC_PRINTF("Unexpected LPM interrupt\n");
+	}
+
+	lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg);
+	DWC_PRINTF("LPM config register = 0x%08x\n", lpmcfg.d32);
+
+	if (dwc_otg_is_host_mode(core_if)) {
+		cil_hcd_sleep(core_if);
+	} else {
+		lpmcfg.b.hird_thres |= (1 << 4);
+		DWC_WRITE_REG32(&core_if->core_global_regs->glpmcfg,
+				lpmcfg.d32);
+	}
+
+	/* Examine prt_sleep_sts after TL1TokenTetry period max (10 us) */
+	dwc_udelay(10);
+	lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg);
+	if (lpmcfg.b.prt_sleep_sts) {
+		/* Save the current state */
+		core_if->lx_state = DWC_OTG_L1;
+	}
+
+	/* Clear interrupt  */
+	gintsts.d32 = 0;
+	gintsts.b.lpmtranrcvd = 1;
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32);
+	return 1;
+}
+#endif /* CONFIG_USB_DWC_OTG_LPM */
+
+/**
+ * This function returns the Core Interrupt register.
+ */
+static inline uint32_t dwc_otg_read_common_intr(dwc_otg_core_if_t * core_if, gintmsk_data_t *reenable_gintmsk, dwc_otg_hcd_t *hcd)
+{
+	gahbcfg_data_t gahbcfg = {.d32 = 0 };
+	gintsts_data_t gintsts;
+	gintmsk_data_t gintmsk;
+	gintmsk_data_t gintmsk_common = {.d32 = 0 };
+	gintmsk_common.b.wkupintr = 1;
+	gintmsk_common.b.sessreqintr = 1;
+	gintmsk_common.b.conidstschng = 1;
+	gintmsk_common.b.otgintr = 1;
+	gintmsk_common.b.modemismatch = 1;
+	gintmsk_common.b.disconnect = 1;
+	gintmsk_common.b.usbsuspend = 1;
+#ifdef CONFIG_USB_DWC_OTG_LPM
+	gintmsk_common.b.lpmtranrcvd = 1;
+#endif
+	gintmsk_common.b.restoredone = 1;
+	if(dwc_otg_is_device_mode(core_if))
+	{
+		/** @todo: The port interrupt occurs while in device
+		 * mode. Added code to CIL to clear the interrupt for now!
+		 */
+		gintmsk_common.b.portintr = 1;
+	}
+	if(fiq_enable) {
+		local_fiq_disable();
+		fiq_fsm_spin_lock(&hcd->fiq_state->lock);
+		gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts);
+		gintmsk.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintmsk);
+		/* Pull in the interrupts that the FIQ has masked */
+		gintmsk.d32 |= ~(hcd->fiq_state->gintmsk_saved.d32);
+		gintmsk.d32 |= gintmsk_common.d32;
+		/* for the upstairs function to reenable - have to read it here in case FIQ triggers again */
+		reenable_gintmsk->d32 = gintmsk.d32;
+		fiq_fsm_spin_unlock(&hcd->fiq_state->lock);
+		local_fiq_enable();
+	} else {
+		gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts);
+		gintmsk.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintmsk);
+	}
+
+	gahbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gahbcfg);
+
+#ifdef DEBUG
+	/* if any common interrupts set */
+	if (gintsts.d32 & gintmsk_common.d32) {
+		DWC_DEBUGPL(DBG_ANY, "common_intr: gintsts=%08x  gintmsk=%08x\n",
+			    gintsts.d32, gintmsk.d32);
+	}
+#endif
+	if (!fiq_enable){
+		if (gahbcfg.b.glblintrmsk)
+			return ((gintsts.d32 & gintmsk.d32) & gintmsk_common.d32);
+		else
+			return 0;
+	} else {
+		/* Our IRQ kicker is no longer the USB hardware, it's the MPHI interface.
+		 * Can't trust the global interrupt mask bit in this case.
+		 */
+		return ((gintsts.d32 & gintmsk.d32) & gintmsk_common.d32);
+	}
+
+}
+
+/* MACRO for clearing interupt bits in GPWRDN register */
+#define CLEAR_GPWRDN_INTR(__core_if,__intr) \
+do { \
+		gpwrdn_data_t gpwrdn = {.d32=0}; \
+		gpwrdn.b.__intr = 1; \
+		DWC_MODIFY_REG32(&__core_if->core_global_regs->gpwrdn, \
+		0, gpwrdn.d32); \
+} while (0)
+
+/**
+ * Common interrupt handler.
+ *
+ * The common interrupts are those that occur in both Host and Device mode.
+ * This handler handles the following interrupts:
+ * - Mode Mismatch Interrupt
+ * - Disconnect Interrupt
+ * - OTG Interrupt
+ * - Connector ID Status Change Interrupt
+ * - Session Request Interrupt.
+ * - Resume / Remote Wakeup Detected Interrupt.
+ * - LPM Transaction Received Interrupt
+ * - ADP Transaction Received Interrupt
+ *
+ */
+int32_t dwc_otg_handle_common_intr(void *dev)
+{
+	int retval = 0;
+	gintsts_data_t gintsts;
+	gintmsk_data_t gintmsk_reenable = { .d32 = 0 };
+	gpwrdn_data_t gpwrdn = {.d32 = 0 };
+	dwc_otg_device_t *otg_dev = dev;
+	dwc_otg_core_if_t *core_if = otg_dev->core_if;
+	gpwrdn.d32 = DWC_READ_REG32(&core_if->core_global_regs->gpwrdn);
+	if (dwc_otg_is_device_mode(core_if))
+		core_if->frame_num = dwc_otg_get_frame_number(core_if);
+
+	if (core_if->lock)
+		DWC_SPINLOCK(core_if->lock);
+
+	if (core_if->power_down == 3 && core_if->xhib == 1) {
+		DWC_DEBUGPL(DBG_ANY, "Exiting from xHIB state\n");
+		retval |= dwc_otg_handle_xhib_exit_intr(core_if);
+		core_if->xhib = 2;
+		if (core_if->lock)
+			DWC_SPINUNLOCK(core_if->lock);
+
+		return retval;
+	}
+
+	if (core_if->hibernation_suspend <= 0) {
+		/* read_common will have to poke the FIQ's saved mask. We must then clear this mask at the end
+		 * of this handler - god only knows why it's done like this
+		 */
+		gintsts.d32 = dwc_otg_read_common_intr(core_if, &gintmsk_reenable, otg_dev->hcd);
+
+		if (gintsts.b.modemismatch) {
+			retval |= dwc_otg_handle_mode_mismatch_intr(core_if);
+		}
+		if (gintsts.b.otgintr) {
+			retval |= dwc_otg_handle_otg_intr(core_if);
+		}
+		if (gintsts.b.conidstschng) {
+			retval |=
+			    dwc_otg_handle_conn_id_status_change_intr(core_if);
+		}
+		if (gintsts.b.disconnect) {
+			retval |= dwc_otg_handle_disconnect_intr(core_if);
+		}
+		if (gintsts.b.sessreqintr) {
+			retval |= dwc_otg_handle_session_req_intr(core_if);
+		}
+		if (gintsts.b.wkupintr) {
+			retval |= dwc_otg_handle_wakeup_detected_intr(core_if);
+		}
+		if (gintsts.b.usbsuspend) {
+			retval |= dwc_otg_handle_usb_suspend_intr(core_if);
+		}
+#ifdef CONFIG_USB_DWC_OTG_LPM
+		if (gintsts.b.lpmtranrcvd) {
+			retval |= dwc_otg_handle_lpm_intr(core_if);
+		}
+#endif
+		if (gintsts.b.restoredone) {
+			gintsts.d32 = 0;
+	                if (core_if->power_down == 2)
+				core_if->hibernation_suspend = -1;
+			else if (core_if->power_down == 3 && core_if->xhib == 2) {
+				gpwrdn_data_t gpwrdn = {.d32 = 0 };
+				pcgcctl_data_t pcgcctl = {.d32 = 0 };
+				dctl_data_t dctl = {.d32 = 0 };
+
+				DWC_WRITE_REG32(&core_if->core_global_regs->
+						gintsts, 0xFFFFFFFF);
+
+				DWC_DEBUGPL(DBG_ANY,
+					    "RESTORE DONE generated\n");
+
+				gpwrdn.b.restore = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+				dwc_udelay(10);
+
+				pcgcctl.b.rstpdwnmodule = 1;
+				DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, 0);
+
+				DWC_WRITE_REG32(&core_if->core_global_regs->gusbcfg, core_if->gr_backup->gusbcfg_local);
+				DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dcfg, core_if->dr_backup->dcfg);
+				DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl, core_if->dr_backup->dctl);
+				dwc_udelay(50);
+
+				dctl.b.pwronprgdone = 1;
+				DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, 0, dctl.d32);
+				dwc_udelay(10);
+
+				dwc_otg_restore_global_regs(core_if);
+				dwc_otg_restore_dev_regs(core_if, 0);
+
+				dctl.d32 = 0;
+				dctl.b.pwronprgdone = 1;
+				DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32, 0);
+				dwc_udelay(10);
+
+				pcgcctl.d32 = 0;
+				pcgcctl.b.enbl_extnd_hiber = 1;
+				DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, 0);
+
+				/* The core will be in ON STATE */
+				core_if->lx_state = DWC_OTG_L0;
+				core_if->xhib = 0;
+
+				DWC_SPINUNLOCK(core_if->lock);
+				if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup) {
+					core_if->pcd_cb->resume_wakeup(core_if->pcd_cb->p);
+				}
+				DWC_SPINLOCK(core_if->lock);
+
+			}
+
+			gintsts.b.restoredone = 1;
+			DWC_WRITE_REG32(&core_if->core_global_regs->gintsts,gintsts.d32);
+			DWC_PRINTF(" --Restore done interrupt received-- \n");
+			retval |= 1;
+		}
+		if (gintsts.b.portintr && dwc_otg_is_device_mode(core_if)) {
+			/* The port interrupt occurs while in device mode with HPRT0
+			 * Port Enable/Disable.
+			 */
+			gintsts.d32 = 0;
+			gintsts.b.portintr = 1;
+			DWC_WRITE_REG32(&core_if->core_global_regs->gintsts,gintsts.d32);
+			retval |= 1;
+			gintmsk_reenable.b.portintr = 1;
+
+		}
+		/* Did we actually handle anything? if so, unmask the interrupt */
+//		fiq_print(FIQDBG_INT, otg_dev->hcd->fiq_state, "CILOUT %1d", retval);
+//		fiq_print(FIQDBG_INT, otg_dev->hcd->fiq_state, "%08x", gintsts.d32);
+//		fiq_print(FIQDBG_INT, otg_dev->hcd->fiq_state, "%08x", gintmsk_reenable.d32);
+		if (retval && fiq_enable) {
+			DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, gintmsk_reenable.d32);
+		}
+
+	} else {
+		DWC_DEBUGPL(DBG_ANY, "gpwrdn=%08x\n", gpwrdn.d32);
+
+		if (gpwrdn.b.disconn_det && gpwrdn.b.disconn_det_msk) {
+			CLEAR_GPWRDN_INTR(core_if, disconn_det);
+			if (gpwrdn.b.linestate == 0) {
+				dwc_otg_handle_pwrdn_disconnect_intr(core_if);
+			} else {
+				DWC_PRINTF("Disconnect detected while linestate is not 0\n");
+			}
+
+			retval |= 1;
+		}
+		if (gpwrdn.b.lnstschng && gpwrdn.b.lnstchng_msk) {
+			CLEAR_GPWRDN_INTR(core_if, lnstschng);
+			/* remote wakeup from hibernation */
+			if (gpwrdn.b.linestate == 2 || gpwrdn.b.linestate == 1) {
+				dwc_otg_handle_pwrdn_wakeup_detected_intr(core_if);
+			} else {
+				DWC_PRINTF("gpwrdn.linestate = %d\n", gpwrdn.b.linestate);
+			}
+			retval |= 1;
+		}
+		if (gpwrdn.b.rst_det && gpwrdn.b.rst_det_msk) {
+			CLEAR_GPWRDN_INTR(core_if, rst_det);
+			if (gpwrdn.b.linestate == 0) {
+				DWC_PRINTF("Reset detected\n");
+				retval |= dwc_otg_device_hibernation_restore(core_if, 0, 1);
+			}
+		}
+		if (gpwrdn.b.srp_det && gpwrdn.b.srp_det_msk) {
+			CLEAR_GPWRDN_INTR(core_if, srp_det);
+			dwc_otg_handle_pwrdn_srp_intr(core_if);
+			retval |= 1;
+		}
+	}
+	/* Handle ADP interrupt here */
+	if (gpwrdn.b.adp_int) {
+		DWC_PRINTF("ADP interrupt\n");
+		CLEAR_GPWRDN_INTR(core_if, adp_int);
+		dwc_otg_adp_handle_intr(core_if);
+		retval |= 1;
+	}
+	if (gpwrdn.b.sts_chngint && gpwrdn.b.sts_chngint_msk) {
+		DWC_PRINTF("STS CHNG interrupt asserted\n");
+		CLEAR_GPWRDN_INTR(core_if, sts_chngint);
+		dwc_otg_handle_pwrdn_stschng_intr(otg_dev);
+
+		retval |= 1;
+	}
+	if (core_if->lock)
+		DWC_SPINUNLOCK(core_if->lock);
+	return retval;
+}
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_core_if.h b/drivers/usb/host/dwc_otg/dwc_otg_core_if.h
new file mode 100644
index 000000000000..4138fd173337
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_core_if.h
@@ -0,0 +1,705 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_core_if.h $
+ * $Revision: #13 $
+ * $Date: 2012/08/10 $
+ * $Change: 2047372 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+#if !defined(__DWC_CORE_IF_H__)
+#define __DWC_CORE_IF_H__
+
+#include "dwc_os.h"
+
+/** @file
+ * This file defines DWC_OTG Core API
+ */
+
+struct dwc_otg_core_if;
+typedef struct dwc_otg_core_if dwc_otg_core_if_t;
+
+/** Maximum number of Periodic FIFOs */
+#define MAX_PERIO_FIFOS 15
+/** Maximum number of Periodic FIFOs */
+#define MAX_TX_FIFOS 15
+
+/** Maximum number of Endpoints/HostChannels */
+#define MAX_EPS_CHANNELS 16
+
+extern dwc_otg_core_if_t *dwc_otg_cil_init(const uint32_t * _reg_base_addr);
+extern void dwc_otg_core_init(dwc_otg_core_if_t * _core_if);
+extern void dwc_otg_cil_remove(dwc_otg_core_if_t * _core_if);
+
+extern void dwc_otg_enable_global_interrupts(dwc_otg_core_if_t * _core_if);
+extern void dwc_otg_disable_global_interrupts(dwc_otg_core_if_t * _core_if);
+
+extern uint8_t dwc_otg_is_device_mode(dwc_otg_core_if_t * _core_if);
+extern uint8_t dwc_otg_is_host_mode(dwc_otg_core_if_t * _core_if);
+
+extern uint8_t dwc_otg_is_dma_enable(dwc_otg_core_if_t * core_if);
+
+/** This function should be called on every hardware interrupt. */
+extern int32_t dwc_otg_handle_common_intr(void *otg_dev);
+
+/** @name OTG Core Parameters */
+/** @{ */
+
+/**
+ * Specifies the OTG capabilities. The driver will automatically
+ * detect the value for this parameter if none is specified.
+ * 0 - HNP and SRP capable (default)
+ * 1 - SRP Only capable
+ * 2 - No HNP/SRP capable
+ */
+extern int dwc_otg_set_param_otg_cap(dwc_otg_core_if_t * core_if, int32_t val);
+extern int32_t dwc_otg_get_param_otg_cap(dwc_otg_core_if_t * core_if);
+#define DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE 0
+#define DWC_OTG_CAP_PARAM_SRP_ONLY_CAPABLE 1
+#define DWC_OTG_CAP_PARAM_NO_HNP_SRP_CAPABLE 2
+#define dwc_param_otg_cap_default DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE
+
+extern int dwc_otg_set_param_opt(dwc_otg_core_if_t * core_if, int32_t val);
+extern int32_t dwc_otg_get_param_opt(dwc_otg_core_if_t * core_if);
+#define dwc_param_opt_default 1
+
+/**
+ * Specifies whether to use slave or DMA mode for accessing the data
+ * FIFOs. The driver will automatically detect the value for this
+ * parameter if none is specified.
+ * 0 - Slave
+ * 1 - DMA (default, if available)
+ */
+extern int dwc_otg_set_param_dma_enable(dwc_otg_core_if_t * core_if,
+					int32_t val);
+extern int32_t dwc_otg_get_param_dma_enable(dwc_otg_core_if_t * core_if);
+#define dwc_param_dma_enable_default 1
+
+/**
+ * When DMA mode is enabled specifies whether to use
+ * address DMA or DMA Descritor mode for accessing the data
+ * FIFOs in device mode. The driver will automatically detect
+ * the value for this parameter if none is specified.
+ * 0 - address DMA
+ * 1 - DMA Descriptor(default, if available)
+ */
+extern int dwc_otg_set_param_dma_desc_enable(dwc_otg_core_if_t * core_if,
+					     int32_t val);
+extern int32_t dwc_otg_get_param_dma_desc_enable(dwc_otg_core_if_t * core_if);
+//#define dwc_param_dma_desc_enable_default 1
+#define dwc_param_dma_desc_enable_default 0 // Broadcom BCM2708
+
+/** The DMA Burst size (applicable only for External DMA
+ * Mode). 1, 4, 8 16, 32, 64, 128, 256 (default 32)
+ */
+extern int dwc_otg_set_param_dma_burst_size(dwc_otg_core_if_t * core_if,
+					    int32_t val);
+extern int32_t dwc_otg_get_param_dma_burst_size(dwc_otg_core_if_t * core_if);
+#define dwc_param_dma_burst_size_default 32
+
+/**
+ * Specifies the maximum speed of operation in host and device mode.
+ * The actual speed depends on the speed of the attached device and
+ * the value of phy_type. The actual speed depends on the speed of the
+ * attached device.
+ * 0 - High Speed (default)
+ * 1 - Full Speed
+ */
+extern int dwc_otg_set_param_speed(dwc_otg_core_if_t * core_if, int32_t val);
+extern int32_t dwc_otg_get_param_speed(dwc_otg_core_if_t * core_if);
+#define dwc_param_speed_default 0
+#define DWC_SPEED_PARAM_HIGH 0
+#define DWC_SPEED_PARAM_FULL 1
+
+/** Specifies whether low power mode is supported when attached
+ *	to a Full Speed or Low Speed device in host mode.
+ * 0 - Don't support low power mode (default)
+ * 1 - Support low power mode
+ */
+extern int dwc_otg_set_param_host_support_fs_ls_low_power(dwc_otg_core_if_t *
+							  core_if, int32_t val);
+extern int32_t dwc_otg_get_param_host_support_fs_ls_low_power(dwc_otg_core_if_t
+							      * core_if);
+#define dwc_param_host_support_fs_ls_low_power_default 0
+
+/** Specifies the PHY clock rate in low power mode when connected to a
+ * Low Speed device in host mode. This parameter is applicable only if
+ * HOST_SUPPORT_FS_LS_LOW_POWER is enabled. If PHY_TYPE is set to FS
+ * then defaults to 6 MHZ otherwise 48 MHZ.
+ *
+ * 0 - 48 MHz
+ * 1 - 6 MHz
+ */
+extern int dwc_otg_set_param_host_ls_low_power_phy_clk(dwc_otg_core_if_t *
+						       core_if, int32_t val);
+extern int32_t dwc_otg_get_param_host_ls_low_power_phy_clk(dwc_otg_core_if_t *
+							   core_if);
+#define dwc_param_host_ls_low_power_phy_clk_default 0
+#define DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_48MHZ 0
+#define DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ 1
+
+/**
+ * 0 - Use cC FIFO size parameters
+ * 1 - Allow dynamic FIFO sizing (default)
+ */
+extern int dwc_otg_set_param_enable_dynamic_fifo(dwc_otg_core_if_t * core_if,
+						 int32_t val);
+extern int32_t dwc_otg_get_param_enable_dynamic_fifo(dwc_otg_core_if_t *
+						     core_if);
+#define dwc_param_enable_dynamic_fifo_default 1
+
+/** Total number of 4-byte words in the data FIFO memory. This
+ * memory includes the Rx FIFO, non-periodic Tx FIFO, and periodic
+ * Tx FIFOs.
+ * 32 to 32768 (default 8192)
+ * Note: The total FIFO memory depth in the FPGA configuration is 8192.
+ */
+extern int dwc_otg_set_param_data_fifo_size(dwc_otg_core_if_t * core_if,
+					    int32_t val);
+extern int32_t dwc_otg_get_param_data_fifo_size(dwc_otg_core_if_t * core_if);
+//#define dwc_param_data_fifo_size_default 8192
+#define dwc_param_data_fifo_size_default 0xFF0 // Broadcom BCM2708
+
+/** Number of 4-byte words in the Rx FIFO in device mode when dynamic
+ * FIFO sizing is enabled.
+ * 16 to 32768 (default 1064)
+ */
+extern int dwc_otg_set_param_dev_rx_fifo_size(dwc_otg_core_if_t * core_if,
+					      int32_t val);
+extern int32_t dwc_otg_get_param_dev_rx_fifo_size(dwc_otg_core_if_t * core_if);
+#define dwc_param_dev_rx_fifo_size_default 1064
+
+/** Number of 4-byte words in the non-periodic Tx FIFO in device mode
+ * when dynamic FIFO sizing is enabled.
+ * 16 to 32768 (default 1024)
+ */
+extern int dwc_otg_set_param_dev_nperio_tx_fifo_size(dwc_otg_core_if_t *
+						     core_if, int32_t val);
+extern int32_t dwc_otg_get_param_dev_nperio_tx_fifo_size(dwc_otg_core_if_t *
+							 core_if);
+#define dwc_param_dev_nperio_tx_fifo_size_default 1024
+
+/** Number of 4-byte words in each of the periodic Tx FIFOs in device
+ * mode when dynamic FIFO sizing is enabled.
+ * 4 to 768 (default 256)
+ */
+extern int dwc_otg_set_param_dev_perio_tx_fifo_size(dwc_otg_core_if_t * core_if,
+						    int32_t val, int fifo_num);
+extern int32_t dwc_otg_get_param_dev_perio_tx_fifo_size(dwc_otg_core_if_t *
+							core_if, int fifo_num);
+#define dwc_param_dev_perio_tx_fifo_size_default 256
+
+/** Number of 4-byte words in the Rx FIFO in host mode when dynamic
+ * FIFO sizing is enabled.
+ * 16 to 32768 (default 1024)
+ */
+extern int dwc_otg_set_param_host_rx_fifo_size(dwc_otg_core_if_t * core_if,
+					       int32_t val);
+extern int32_t dwc_otg_get_param_host_rx_fifo_size(dwc_otg_core_if_t * core_if);
+//#define dwc_param_host_rx_fifo_size_default 1024
+#define dwc_param_host_rx_fifo_size_default 774 // Broadcom BCM2708
+
+/** Number of 4-byte words in the non-periodic Tx FIFO in host mode
+ * when Dynamic FIFO sizing is enabled in the core.
+ * 16 to 32768 (default 1024)
+ */
+extern int dwc_otg_set_param_host_nperio_tx_fifo_size(dwc_otg_core_if_t *
+						      core_if, int32_t val);
+extern int32_t dwc_otg_get_param_host_nperio_tx_fifo_size(dwc_otg_core_if_t *
+							  core_if);
+//#define dwc_param_host_nperio_tx_fifo_size_default 1024
+#define dwc_param_host_nperio_tx_fifo_size_default 0x100 // Broadcom BCM2708
+
+/** Number of 4-byte words in the host periodic Tx FIFO when dynamic
+ * FIFO sizing is enabled.
+ * 16 to 32768 (default 1024)
+ */
+extern int dwc_otg_set_param_host_perio_tx_fifo_size(dwc_otg_core_if_t *
+						     core_if, int32_t val);
+extern int32_t dwc_otg_get_param_host_perio_tx_fifo_size(dwc_otg_core_if_t *
+							 core_if);
+//#define dwc_param_host_perio_tx_fifo_size_default 1024
+#define dwc_param_host_perio_tx_fifo_size_default 0x200 // Broadcom BCM2708
+
+/** The maximum transfer size supported in bytes.
+ * 2047 to 65,535  (default 65,535)
+ */
+extern int dwc_otg_set_param_max_transfer_size(dwc_otg_core_if_t * core_if,
+					       int32_t val);
+extern int32_t dwc_otg_get_param_max_transfer_size(dwc_otg_core_if_t * core_if);
+#define dwc_param_max_transfer_size_default 65535
+
+/** The maximum number of packets in a transfer.
+ * 15 to 511  (default 511)
+ */
+extern int dwc_otg_set_param_max_packet_count(dwc_otg_core_if_t * core_if,
+					      int32_t val);
+extern int32_t dwc_otg_get_param_max_packet_count(dwc_otg_core_if_t * core_if);
+#define dwc_param_max_packet_count_default 511
+
+/** The number of host channel registers to use.
+ * 1 to 16 (default 12)
+ * Note: The FPGA configuration supports a maximum of 12 host channels.
+ */
+extern int dwc_otg_set_param_host_channels(dwc_otg_core_if_t * core_if,
+					   int32_t val);
+extern int32_t dwc_otg_get_param_host_channels(dwc_otg_core_if_t * core_if);
+//#define dwc_param_host_channels_default 12
+#define dwc_param_host_channels_default 8 // Broadcom BCM2708
+
+/** The number of endpoints in addition to EP0 available for device
+ * mode operations.
+ * 1 to 15 (default 6 IN and OUT)
+ * Note: The FPGA configuration supports a maximum of 6 IN and OUT
+ * endpoints in addition to EP0.
+ */
+extern int dwc_otg_set_param_dev_endpoints(dwc_otg_core_if_t * core_if,
+					   int32_t val);
+extern int32_t dwc_otg_get_param_dev_endpoints(dwc_otg_core_if_t * core_if);
+#define dwc_param_dev_endpoints_default 6
+
+/**
+ * Specifies the type of PHY interface to use. By default, the driver
+ * will automatically detect the phy_type.
+ *
+ * 0 - Full Speed PHY
+ * 1 - UTMI+ (default)
+ * 2 - ULPI
+ */
+extern int dwc_otg_set_param_phy_type(dwc_otg_core_if_t * core_if, int32_t val);
+extern int32_t dwc_otg_get_param_phy_type(dwc_otg_core_if_t * core_if);
+#define DWC_PHY_TYPE_PARAM_FS 0
+#define DWC_PHY_TYPE_PARAM_UTMI 1
+#define DWC_PHY_TYPE_PARAM_ULPI 2
+#define dwc_param_phy_type_default DWC_PHY_TYPE_PARAM_UTMI
+
+/**
+ * Specifies the UTMI+ Data Width. This parameter is
+ * applicable for a PHY_TYPE of UTMI+ or ULPI. (For a ULPI
+ * PHY_TYPE, this parameter indicates the data width between
+ * the MAC and the ULPI Wrapper.) Also, this parameter is
+ * applicable only if the OTG_HSPHY_WIDTH cC parameter was set
+ * to "8 and 16 bits", meaning that the core has been
+ * configured to work at either data path width.
+ *
+ * 8 or 16 bits (default 16)
+ */
+extern int dwc_otg_set_param_phy_utmi_width(dwc_otg_core_if_t * core_if,
+					    int32_t val);
+extern int32_t dwc_otg_get_param_phy_utmi_width(dwc_otg_core_if_t * core_if);
+//#define dwc_param_phy_utmi_width_default 16
+#define dwc_param_phy_utmi_width_default 8 // Broadcom BCM2708
+
+/**
+ * Specifies whether the ULPI operates at double or single
+ * data rate. This parameter is only applicable if PHY_TYPE is
+ * ULPI.
+ *
+ * 0 - single data rate ULPI interface with 8 bit wide data
+ * bus (default)
+ * 1 - double data rate ULPI interface with 4 bit wide data
+ * bus
+ */
+extern int dwc_otg_set_param_phy_ulpi_ddr(dwc_otg_core_if_t * core_if,
+					  int32_t val);
+extern int32_t dwc_otg_get_param_phy_ulpi_ddr(dwc_otg_core_if_t * core_if);
+#define dwc_param_phy_ulpi_ddr_default 0
+
+/**
+ * Specifies whether to use the internal or external supply to
+ * drive the vbus with a ULPI phy.
+ */
+extern int dwc_otg_set_param_phy_ulpi_ext_vbus(dwc_otg_core_if_t * core_if,
+					       int32_t val);
+extern int32_t dwc_otg_get_param_phy_ulpi_ext_vbus(dwc_otg_core_if_t * core_if);
+#define DWC_PHY_ULPI_INTERNAL_VBUS 0
+#define DWC_PHY_ULPI_EXTERNAL_VBUS 1
+#define dwc_param_phy_ulpi_ext_vbus_default DWC_PHY_ULPI_INTERNAL_VBUS
+
+/**
+ * Specifies whether to use the I2Cinterface for full speed PHY. This
+ * parameter is only applicable if PHY_TYPE is FS.
+ * 0 - No (default)
+ * 1 - Yes
+ */
+extern int dwc_otg_set_param_i2c_enable(dwc_otg_core_if_t * core_if,
+					int32_t val);
+extern int32_t dwc_otg_get_param_i2c_enable(dwc_otg_core_if_t * core_if);
+#define dwc_param_i2c_enable_default 0
+
+extern int dwc_otg_set_param_ulpi_fs_ls(dwc_otg_core_if_t * core_if,
+					int32_t val);
+extern int32_t dwc_otg_get_param_ulpi_fs_ls(dwc_otg_core_if_t * core_if);
+#define dwc_param_ulpi_fs_ls_default 0
+
+extern int dwc_otg_set_param_ts_dline(dwc_otg_core_if_t * core_if, int32_t val);
+extern int32_t dwc_otg_get_param_ts_dline(dwc_otg_core_if_t * core_if);
+#define dwc_param_ts_dline_default 0
+
+/**
+ * Specifies whether dedicated transmit FIFOs are
+ * enabled for non periodic IN endpoints in device mode
+ * 0 - No
+ * 1 - Yes
+ */
+extern int dwc_otg_set_param_en_multiple_tx_fifo(dwc_otg_core_if_t * core_if,
+						 int32_t val);
+extern int32_t dwc_otg_get_param_en_multiple_tx_fifo(dwc_otg_core_if_t *
+						     core_if);
+#define dwc_param_en_multiple_tx_fifo_default 1
+
+/** Number of 4-byte words in each of the Tx FIFOs in device
+ * mode when dynamic FIFO sizing is enabled.
+ * 4 to 768 (default 256)
+ */
+extern int dwc_otg_set_param_dev_tx_fifo_size(dwc_otg_core_if_t * core_if,
+					      int fifo_num, int32_t val);
+extern int32_t dwc_otg_get_param_dev_tx_fifo_size(dwc_otg_core_if_t * core_if,
+						  int fifo_num);
+#define dwc_param_dev_tx_fifo_size_default 768
+
+/** Thresholding enable flag-
+ * bit 0 - enable non-ISO Tx thresholding
+ * bit 1 - enable ISO Tx thresholding
+ * bit 2 - enable Rx thresholding
+ */
+extern int dwc_otg_set_param_thr_ctl(dwc_otg_core_if_t * core_if, int32_t val);
+extern int32_t dwc_otg_get_thr_ctl(dwc_otg_core_if_t * core_if, int fifo_num);
+#define dwc_param_thr_ctl_default 0
+
+/** Thresholding length for Tx
+ * FIFOs in 32 bit DWORDs
+ */
+extern int dwc_otg_set_param_tx_thr_length(dwc_otg_core_if_t * core_if,
+					   int32_t val);
+extern int32_t dwc_otg_get_tx_thr_length(dwc_otg_core_if_t * core_if);
+#define dwc_param_tx_thr_length_default 64
+
+/** Thresholding length for Rx
+ *	FIFOs in 32 bit DWORDs
+ */
+extern int dwc_otg_set_param_rx_thr_length(dwc_otg_core_if_t * core_if,
+					   int32_t val);
+extern int32_t dwc_otg_get_rx_thr_length(dwc_otg_core_if_t * core_if);
+#define dwc_param_rx_thr_length_default 64
+
+/**
+ * Specifies whether LPM (Link Power Management) support is enabled
+ */
+extern int dwc_otg_set_param_lpm_enable(dwc_otg_core_if_t * core_if,
+					int32_t val);
+extern int32_t dwc_otg_get_param_lpm_enable(dwc_otg_core_if_t * core_if);
+#define dwc_param_lpm_enable_default 1
+
+/**
+ * Specifies whether PTI enhancement is enabled
+ */
+extern int dwc_otg_set_param_pti_enable(dwc_otg_core_if_t * core_if,
+					int32_t val);
+extern int32_t dwc_otg_get_param_pti_enable(dwc_otg_core_if_t * core_if);
+#define dwc_param_pti_enable_default 0
+
+/**
+ * Specifies whether MPI enhancement is enabled
+ */
+extern int dwc_otg_set_param_mpi_enable(dwc_otg_core_if_t * core_if,
+					int32_t val);
+extern int32_t dwc_otg_get_param_mpi_enable(dwc_otg_core_if_t * core_if);
+#define dwc_param_mpi_enable_default 0
+
+/**
+ * Specifies whether ADP capability is enabled
+ */
+extern int dwc_otg_set_param_adp_enable(dwc_otg_core_if_t * core_if,
+					int32_t val);
+extern int32_t dwc_otg_get_param_adp_enable(dwc_otg_core_if_t * core_if);
+#define dwc_param_adp_enable_default 0
+
+/**
+ * Specifies whether IC_USB capability is enabled
+ */
+
+extern int dwc_otg_set_param_ic_usb_cap(dwc_otg_core_if_t * core_if,
+					int32_t val);
+extern int32_t dwc_otg_get_param_ic_usb_cap(dwc_otg_core_if_t * core_if);
+#define dwc_param_ic_usb_cap_default 0
+
+extern int dwc_otg_set_param_ahb_thr_ratio(dwc_otg_core_if_t * core_if,
+					   int32_t val);
+extern int32_t dwc_otg_get_param_ahb_thr_ratio(dwc_otg_core_if_t * core_if);
+#define dwc_param_ahb_thr_ratio_default 0
+
+extern int dwc_otg_set_param_power_down(dwc_otg_core_if_t * core_if,
+					int32_t val);
+extern int32_t dwc_otg_get_param_power_down(dwc_otg_core_if_t * core_if);
+#define dwc_param_power_down_default 0
+
+extern int dwc_otg_set_param_reload_ctl(dwc_otg_core_if_t * core_if,
+					int32_t val);
+extern int32_t dwc_otg_get_param_reload_ctl(dwc_otg_core_if_t * core_if);
+#define dwc_param_reload_ctl_default 0
+
+extern int dwc_otg_set_param_dev_out_nak(dwc_otg_core_if_t * core_if,
+										int32_t val);
+extern int32_t dwc_otg_get_param_dev_out_nak(dwc_otg_core_if_t * core_if);
+#define dwc_param_dev_out_nak_default 0
+
+extern int dwc_otg_set_param_cont_on_bna(dwc_otg_core_if_t * core_if,
+										 int32_t val);
+extern int32_t dwc_otg_get_param_cont_on_bna(dwc_otg_core_if_t * core_if);
+#define dwc_param_cont_on_bna_default 0
+
+extern int dwc_otg_set_param_ahb_single(dwc_otg_core_if_t * core_if,
+										 int32_t val);
+extern int32_t dwc_otg_get_param_ahb_single(dwc_otg_core_if_t * core_if);
+#define dwc_param_ahb_single_default 0
+
+extern int dwc_otg_set_param_otg_ver(dwc_otg_core_if_t * core_if, int32_t val);
+extern int32_t dwc_otg_get_param_otg_ver(dwc_otg_core_if_t * core_if);
+#define dwc_param_otg_ver_default 0
+
+/** @} */
+
+/** @name Access to registers and bit-fields */
+
+/**
+ * Dump core registers and SPRAM
+ */
+extern void dwc_otg_dump_dev_registers(dwc_otg_core_if_t * _core_if);
+extern void dwc_otg_dump_spram(dwc_otg_core_if_t * _core_if);
+extern void dwc_otg_dump_host_registers(dwc_otg_core_if_t * _core_if);
+extern void dwc_otg_dump_global_registers(dwc_otg_core_if_t * _core_if);
+
+/**
+ * Get host negotiation status.
+ */
+extern uint32_t dwc_otg_get_hnpstatus(dwc_otg_core_if_t * core_if);
+
+/**
+ * Get srp status
+ */
+extern uint32_t dwc_otg_get_srpstatus(dwc_otg_core_if_t * core_if);
+
+/**
+ * Set hnpreq bit in the GOTGCTL register.
+ */
+extern void dwc_otg_set_hnpreq(dwc_otg_core_if_t * core_if, uint32_t val);
+
+/**
+ * Get Content of SNPSID register.
+ */
+extern uint32_t dwc_otg_get_gsnpsid(dwc_otg_core_if_t * core_if);
+
+/**
+ * Get current mode.
+ * Returns 0 if in device mode, and 1 if in host mode.
+ */
+extern uint32_t dwc_otg_get_mode(dwc_otg_core_if_t * core_if);
+
+/**
+ * Get value of hnpcapable field in the GUSBCFG register
+ */
+extern uint32_t dwc_otg_get_hnpcapable(dwc_otg_core_if_t * core_if);
+/**
+ * Set value of hnpcapable field in the GUSBCFG register
+ */
+extern void dwc_otg_set_hnpcapable(dwc_otg_core_if_t * core_if, uint32_t val);
+
+/**
+ * Get value of srpcapable field in the GUSBCFG register
+ */
+extern uint32_t dwc_otg_get_srpcapable(dwc_otg_core_if_t * core_if);
+/**
+ * Set value of srpcapable field in the GUSBCFG register
+ */
+extern void dwc_otg_set_srpcapable(dwc_otg_core_if_t * core_if, uint32_t val);
+
+/**
+ * Get value of devspeed field in the DCFG register
+ */
+extern uint32_t dwc_otg_get_devspeed(dwc_otg_core_if_t * core_if);
+/**
+ * Set value of devspeed field in the DCFG register
+ */
+extern void dwc_otg_set_devspeed(dwc_otg_core_if_t * core_if, uint32_t val);
+
+/**
+ * Get the value of busconnected field from the HPRT0 register
+ */
+extern uint32_t dwc_otg_get_busconnected(dwc_otg_core_if_t * core_if);
+
+/**
+ * Gets the device enumeration Speed.
+ */
+extern uint32_t dwc_otg_get_enumspeed(dwc_otg_core_if_t * core_if);
+
+/**
+ * Get value of prtpwr field from the HPRT0 register
+ */
+extern uint32_t dwc_otg_get_prtpower(dwc_otg_core_if_t * core_if);
+
+/**
+ * Get value of flag indicating core state - hibernated or not
+ */
+extern uint32_t dwc_otg_get_core_state(dwc_otg_core_if_t * core_if);
+
+/**
+ * Set value of prtpwr field from the HPRT0 register
+ */
+extern void dwc_otg_set_prtpower(dwc_otg_core_if_t * core_if, uint32_t val);
+
+/**
+ * Get value of prtsusp field from the HPRT0 regsiter
+ */
+extern uint32_t dwc_otg_get_prtsuspend(dwc_otg_core_if_t * core_if);
+/**
+ * Set value of prtpwr field from the HPRT0 register
+ */
+extern void dwc_otg_set_prtsuspend(dwc_otg_core_if_t * core_if, uint32_t val);
+
+/**
+ * Get value of ModeChTimEn field from the HCFG regsiter
+ */
+extern uint32_t dwc_otg_get_mode_ch_tim(dwc_otg_core_if_t * core_if);
+/**
+ * Set value of ModeChTimEn field from the HCFG regsiter
+ */
+extern void dwc_otg_set_mode_ch_tim(dwc_otg_core_if_t * core_if, uint32_t val);
+
+/**
+ * Get value of Fram Interval field from the HFIR regsiter
+ */
+extern uint32_t dwc_otg_get_fr_interval(dwc_otg_core_if_t * core_if);
+/**
+ * Set value of Frame Interval field from the HFIR regsiter
+ */
+extern void dwc_otg_set_fr_interval(dwc_otg_core_if_t * core_if, uint32_t val);
+
+/**
+ * Set value of prtres field from the HPRT0 register
+ *FIXME Remove?
+ */
+extern void dwc_otg_set_prtresume(dwc_otg_core_if_t * core_if, uint32_t val);
+
+/**
+ * Get value of rmtwkupsig bit in DCTL register
+ */
+extern uint32_t dwc_otg_get_remotewakesig(dwc_otg_core_if_t * core_if);
+
+/**
+ * Get value of prt_sleep_sts field from the GLPMCFG register
+ */
+extern uint32_t dwc_otg_get_lpm_portsleepstatus(dwc_otg_core_if_t * core_if);
+
+/**
+ * Get value of rem_wkup_en field from the GLPMCFG register
+ */
+extern uint32_t dwc_otg_get_lpm_remotewakeenabled(dwc_otg_core_if_t * core_if);
+
+/**
+ * Get value of appl_resp field from the GLPMCFG register
+ */
+extern uint32_t dwc_otg_get_lpmresponse(dwc_otg_core_if_t * core_if);
+/**
+ * Set value of appl_resp field from the GLPMCFG register
+ */
+extern void dwc_otg_set_lpmresponse(dwc_otg_core_if_t * core_if, uint32_t val);
+
+/**
+ * Get value of hsic_connect field from the GLPMCFG register
+ */
+extern uint32_t dwc_otg_get_hsic_connect(dwc_otg_core_if_t * core_if);
+/**
+ * Set value of hsic_connect field from the GLPMCFG register
+ */
+extern void dwc_otg_set_hsic_connect(dwc_otg_core_if_t * core_if, uint32_t val);
+
+/**
+ * Get value of inv_sel_hsic field from the GLPMCFG register.
+ */
+extern uint32_t dwc_otg_get_inv_sel_hsic(dwc_otg_core_if_t * core_if);
+/**
+ * Set value of inv_sel_hsic field from the GLPMFG register.
+ */
+extern void dwc_otg_set_inv_sel_hsic(dwc_otg_core_if_t * core_if, uint32_t val);
+
+/*
+ * Some functions for accessing registers
+ */
+
+/**
+ *  GOTGCTL register
+ */
+extern uint32_t dwc_otg_get_gotgctl(dwc_otg_core_if_t * core_if);
+extern void dwc_otg_set_gotgctl(dwc_otg_core_if_t * core_if, uint32_t val);
+
+/**
+ * GUSBCFG register
+ */
+extern uint32_t dwc_otg_get_gusbcfg(dwc_otg_core_if_t * core_if);
+extern void dwc_otg_set_gusbcfg(dwc_otg_core_if_t * core_if, uint32_t val);
+
+/**
+ * GRXFSIZ register
+ */
+extern uint32_t dwc_otg_get_grxfsiz(dwc_otg_core_if_t * core_if);
+extern void dwc_otg_set_grxfsiz(dwc_otg_core_if_t * core_if, uint32_t val);
+
+/**
+ * GNPTXFSIZ register
+ */
+extern uint32_t dwc_otg_get_gnptxfsiz(dwc_otg_core_if_t * core_if);
+extern void dwc_otg_set_gnptxfsiz(dwc_otg_core_if_t * core_if, uint32_t val);
+
+extern uint32_t dwc_otg_get_gpvndctl(dwc_otg_core_if_t * core_if);
+extern void dwc_otg_set_gpvndctl(dwc_otg_core_if_t * core_if, uint32_t val);
+
+/**
+ * GGPIO register
+ */
+extern uint32_t dwc_otg_get_ggpio(dwc_otg_core_if_t * core_if);
+extern void dwc_otg_set_ggpio(dwc_otg_core_if_t * core_if, uint32_t val);
+
+/**
+ * GUID register
+ */
+extern uint32_t dwc_otg_get_guid(dwc_otg_core_if_t * core_if);
+extern void dwc_otg_set_guid(dwc_otg_core_if_t * core_if, uint32_t val);
+
+/**
+ * HPRT0 register
+ */
+extern uint32_t dwc_otg_get_hprt0(dwc_otg_core_if_t * core_if);
+extern void dwc_otg_set_hprt0(dwc_otg_core_if_t * core_if, uint32_t val);
+
+/**
+ * GHPTXFSIZE
+ */
+extern uint32_t dwc_otg_get_hptxfsiz(dwc_otg_core_if_t * core_if);
+
+/** @} */
+
+#endif				/* __DWC_CORE_IF_H__ */
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_dbg.h b/drivers/usb/host/dwc_otg/dwc_otg_dbg.h
new file mode 100644
index 000000000000..ccc24e010e44
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_dbg.h
@@ -0,0 +1,117 @@
+/* ==========================================================================
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+
+#ifndef __DWC_OTG_DBG_H__
+#define __DWC_OTG_DBG_H__
+
+/** @file
+ * This file defines debug levels.
+ * Debugging support vanishes in non-debug builds.
+ */
+
+/**
+ * The Debug Level bit-mask variable.
+ */
+extern uint32_t g_dbg_lvl;
+/**
+ * Set the Debug Level variable.
+ */
+static inline uint32_t SET_DEBUG_LEVEL(const uint32_t new)
+{
+	uint32_t old = g_dbg_lvl;
+	g_dbg_lvl = new;
+	return old;
+}
+
+#define DBG_USER	(0x1)
+/** When debug level has the DBG_CIL bit set, display CIL Debug messages. */
+#define DBG_CIL		(0x2)
+/** When debug level has the DBG_CILV bit set, display CIL Verbose debug
+ * messages */
+#define DBG_CILV	(0x20)
+/**  When debug level has the DBG_PCD bit set, display PCD (Device) debug
+ *  messages */
+#define DBG_PCD		(0x4)
+/** When debug level has the DBG_PCDV set, display PCD (Device) Verbose debug
+ * messages */
+#define DBG_PCDV	(0x40)
+/** When debug level has the DBG_HCD bit set, display Host debug messages */
+#define DBG_HCD		(0x8)
+/** When debug level has the DBG_HCDV bit set, display Verbose Host debug
+ * messages */
+#define DBG_HCDV	(0x80)
+/** When debug level has the DBG_HCD_URB bit set, display enqueued URBs in host
+ *  mode. */
+#define DBG_HCD_URB	(0x800)
+/** When debug level has the DBG_HCDI bit set, display host interrupt
+ *  messages. */
+#define DBG_HCDI	(0x1000)
+
+/** When debug level has any bit set, display debug messages */
+#define DBG_ANY		(0xFF)
+
+/** All debug messages off */
+#define DBG_OFF		0
+
+/** Prefix string for DWC_DEBUG print macros. */
+#define USB_DWC "DWC_otg: "
+
+/**
+ * Print a debug message when the Global debug level variable contains
+ * the bit defined in <code>lvl</code>.
+ *
+ * @param[in] lvl - Debug level, use one of the DBG_ constants above.
+ * @param[in] x - like printf
+ *
+ *    Example:<p>
+ * <code>
+ *      DWC_DEBUGPL( DBG_ANY, "%s(%p)\n", __func__, _reg_base_addr);
+ * </code>
+ * <br>
+ * results in:<br>
+ * <code>
+ * usb-DWC_otg: dwc_otg_cil_init(ca867000)
+ * </code>
+ */
+#ifdef DEBUG
+
+# define DWC_DEBUGPL(lvl, x...) do{ if ((lvl)&g_dbg_lvl)__DWC_DEBUG(USB_DWC x ); }while(0)
+# define DWC_DEBUGP(x...)	DWC_DEBUGPL(DBG_ANY, x )
+
+# define CHK_DEBUG_LEVEL(level) ((level) & g_dbg_lvl)
+
+#else
+
+# define DWC_DEBUGPL(lvl, x...) do{}while(0)
+# define DWC_DEBUGP(x...)
+
+# define CHK_DEBUG_LEVEL(level) (0)
+
+#endif /*DEBUG*/
+#endif
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_driver.c b/drivers/usb/host/dwc_otg/dwc_otg_driver.c
new file mode 100644
index 000000000000..b3e72bffafa1
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_driver.c
@@ -0,0 +1,1725 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_driver.c $
+ * $Revision: #92 $
+ * $Date: 2012/08/10 $
+ * $Change: 2047372 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+
+/** @file
+ * The dwc_otg_driver module provides the initialization and cleanup entry
+ * points for the DWC_otg driver. This module will be dynamically installed
+ * after Linux is booted using the insmod command. When the module is
+ * installed, the dwc_otg_driver_init function is called. When the module is
+ * removed (using rmmod), the dwc_otg_driver_cleanup function is called.
+ *
+ * This module also defines a data structure for the dwc_otg_driver, which is
+ * used in conjunction with the standard ARM lm_device structure. These
+ * structures allow the OTG driver to comply with the standard Linux driver
+ * model in which devices and drivers are registered with a bus driver. This
+ * has the benefit that Linux can expose attributes of the driver and device
+ * in its special sysfs file system. Users can then read or write files in
+ * this file system to perform diagnostics on the driver components or the
+ * device.
+ */
+
+#include "dwc_otg_os_dep.h"
+#include "dwc_os.h"
+#include "dwc_otg_dbg.h"
+#include "dwc_otg_driver.h"
+#include "dwc_otg_attr.h"
+#include "dwc_otg_core_if.h"
+#include "dwc_otg_pcd_if.h"
+#include "dwc_otg_hcd_if.h"
+#include "dwc_otg_fiq_fsm.h"
+#include "dwc_otg_adp.h"
+
+#define DWC_DRIVER_VERSION	"3.00a 10-AUG-2012"
+#define DWC_DRIVER_DESC		"HS OTG USB Controller driver"
+
+bool microframe_schedule=true;
+
+static const char dwc_driver_name[] = "dwc_otg";
+
+/*-------------------------------------------------------------------------*/
+/* Encapsulate the module parameter settings */
+
+struct dwc_otg_driver_module_params {
+	int32_t opt;
+	int32_t otg_cap;
+	int32_t dma_enable;
+	int32_t dma_desc_enable;
+	int32_t dma_burst_size;
+	int32_t speed;
+	int32_t host_support_fs_ls_low_power;
+	int32_t host_ls_low_power_phy_clk;
+	int32_t enable_dynamic_fifo;
+	int32_t data_fifo_size;
+	int32_t dev_rx_fifo_size;
+	int32_t dev_nperio_tx_fifo_size;
+	uint32_t dev_perio_tx_fifo_size[MAX_PERIO_FIFOS];
+	int32_t host_rx_fifo_size;
+	int32_t host_nperio_tx_fifo_size;
+	int32_t host_perio_tx_fifo_size;
+	int32_t max_transfer_size;
+	int32_t max_packet_count;
+	int32_t host_channels;
+	int32_t dev_endpoints;
+	int32_t phy_type;
+	int32_t phy_utmi_width;
+	int32_t phy_ulpi_ddr;
+	int32_t phy_ulpi_ext_vbus;
+	int32_t i2c_enable;
+	int32_t ulpi_fs_ls;
+	int32_t ts_dline;
+	int32_t en_multiple_tx_fifo;
+	uint32_t dev_tx_fifo_size[MAX_TX_FIFOS];
+	uint32_t thr_ctl;
+	uint32_t tx_thr_length;
+	uint32_t rx_thr_length;
+	int32_t pti_enable;
+	int32_t mpi_enable;
+	int32_t lpm_enable;
+	int32_t ic_usb_cap;
+	int32_t ahb_thr_ratio;
+	int32_t power_down;
+	int32_t reload_ctl;
+	int32_t dev_out_nak;
+	int32_t cont_on_bna;
+	int32_t ahb_single;
+	int32_t otg_ver;
+	int32_t adp_enable;
+};
+
+static struct dwc_otg_driver_module_params dwc_otg_module_params = {
+	.opt = -1,
+	.otg_cap = -1,
+	.dma_enable = -1,
+	.dma_desc_enable = -1,
+	.dma_burst_size = -1,
+	.speed = -1,
+	.host_support_fs_ls_low_power = -1,
+	.host_ls_low_power_phy_clk = -1,
+	.enable_dynamic_fifo = -1,
+	.data_fifo_size = -1,
+	.dev_rx_fifo_size = -1,
+	.dev_nperio_tx_fifo_size = -1,
+	.dev_perio_tx_fifo_size = {
+				   /* dev_perio_tx_fifo_size_1 */
+				   -1,
+				   -1,
+				   -1,
+				   -1,
+				   -1,
+				   -1,
+				   -1,
+				   -1,
+				   -1,
+				   -1,
+				   -1,
+				   -1,
+				   -1,
+				   -1,
+				   -1
+				   /* 15 */
+				   },
+	.host_rx_fifo_size = -1,
+	.host_nperio_tx_fifo_size = -1,
+	.host_perio_tx_fifo_size = -1,
+	.max_transfer_size = -1,
+	.max_packet_count = -1,
+	.host_channels = -1,
+	.dev_endpoints = -1,
+	.phy_type = -1,
+	.phy_utmi_width = -1,
+	.phy_ulpi_ddr = -1,
+	.phy_ulpi_ext_vbus = -1,
+	.i2c_enable = -1,
+	.ulpi_fs_ls = -1,
+	.ts_dline = -1,
+	.en_multiple_tx_fifo = -1,
+	.dev_tx_fifo_size = {
+			     /* dev_tx_fifo_size */
+			     -1,
+			     -1,
+			     -1,
+			     -1,
+			     -1,
+			     -1,
+			     -1,
+			     -1,
+			     -1,
+			     -1,
+			     -1,
+			     -1,
+			     -1,
+			     -1,
+			     -1
+			     /* 15 */
+			     },
+	.thr_ctl = -1,
+	.tx_thr_length = -1,
+	.rx_thr_length = -1,
+	.pti_enable = -1,
+	.mpi_enable = -1,
+	.lpm_enable = 0,
+	.ic_usb_cap = -1,
+	.ahb_thr_ratio = -1,
+	.power_down = -1,
+	.reload_ctl = -1,
+	.dev_out_nak = -1,
+	.cont_on_bna = -1,
+	.ahb_single = -1,
+	.otg_ver = -1,
+	.adp_enable = -1,
+};
+
+//Global variable to switch the fiq fix on or off
+bool fiq_enable = 1;
+// Global variable to enable the split transaction fix
+bool fiq_fsm_enable = true;
+//Bulk split-transaction NAK holdoff in microframes
+uint16_t nak_holdoff = 8;
+
+//Force host mode during CIL re-init
+bool cil_force_host = true;
+
+unsigned short fiq_fsm_mask = 0x0F;
+
+unsigned short int_ep_interval_min = 0;
+/**
+ * This function shows the Driver Version.
+ */
+static ssize_t version_show(struct device_driver *dev, char *buf)
+{
+	return snprintf(buf, sizeof(DWC_DRIVER_VERSION) + 2, "%s\n",
+			DWC_DRIVER_VERSION);
+}
+
+static DRIVER_ATTR_RO(version);
+
+/**
+ * Global Debug Level Mask.
+ */
+uint32_t g_dbg_lvl = 0;		/* OFF */
+
+/**
+ * This function shows the driver Debug Level.
+ */
+static ssize_t debuglevel_show(struct device_driver *drv, char *buf)
+{
+	return sprintf(buf, "0x%0x\n", g_dbg_lvl);
+}
+
+/**
+ * This function stores the driver Debug Level.
+ */
+static ssize_t debuglevel_store(struct device_driver *drv, const char *buf,
+			       size_t count)
+{
+	g_dbg_lvl = simple_strtoul(buf, NULL, 16);
+	return count;
+}
+
+static DRIVER_ATTR_RW(debuglevel);
+
+/**
+ * This function is called during module intialization
+ * to pass module parameters to the DWC_OTG CORE.
+ */
+static int set_parameters(dwc_otg_core_if_t * core_if)
+{
+	int retval = 0;
+	int i;
+
+	if (dwc_otg_module_params.otg_cap != -1) {
+		retval +=
+		    dwc_otg_set_param_otg_cap(core_if,
+					      dwc_otg_module_params.otg_cap);
+	}
+	if (dwc_otg_module_params.dma_enable != -1) {
+		retval +=
+		    dwc_otg_set_param_dma_enable(core_if,
+						 dwc_otg_module_params.
+						 dma_enable);
+	}
+	if (dwc_otg_module_params.dma_desc_enable != -1) {
+		retval +=
+		    dwc_otg_set_param_dma_desc_enable(core_if,
+						      dwc_otg_module_params.
+						      dma_desc_enable);
+	}
+	if (dwc_otg_module_params.opt != -1) {
+		retval +=
+		    dwc_otg_set_param_opt(core_if, dwc_otg_module_params.opt);
+	}
+	if (dwc_otg_module_params.dma_burst_size != -1) {
+		retval +=
+		    dwc_otg_set_param_dma_burst_size(core_if,
+						     dwc_otg_module_params.
+						     dma_burst_size);
+	}
+	if (dwc_otg_module_params.host_support_fs_ls_low_power != -1) {
+		retval +=
+		    dwc_otg_set_param_host_support_fs_ls_low_power(core_if,
+								   dwc_otg_module_params.
+								   host_support_fs_ls_low_power);
+	}
+	if (dwc_otg_module_params.enable_dynamic_fifo != -1) {
+		retval +=
+		    dwc_otg_set_param_enable_dynamic_fifo(core_if,
+							  dwc_otg_module_params.
+							  enable_dynamic_fifo);
+	}
+	if (dwc_otg_module_params.data_fifo_size != -1) {
+		retval +=
+		    dwc_otg_set_param_data_fifo_size(core_if,
+						     dwc_otg_module_params.
+						     data_fifo_size);
+	}
+	if (dwc_otg_module_params.dev_rx_fifo_size != -1) {
+		retval +=
+		    dwc_otg_set_param_dev_rx_fifo_size(core_if,
+						       dwc_otg_module_params.
+						       dev_rx_fifo_size);
+	}
+	if (dwc_otg_module_params.dev_nperio_tx_fifo_size != -1) {
+		retval +=
+		    dwc_otg_set_param_dev_nperio_tx_fifo_size(core_if,
+							      dwc_otg_module_params.
+							      dev_nperio_tx_fifo_size);
+	}
+	if (dwc_otg_module_params.host_rx_fifo_size != -1) {
+		retval +=
+		    dwc_otg_set_param_host_rx_fifo_size(core_if,
+							dwc_otg_module_params.host_rx_fifo_size);
+	}
+	if (dwc_otg_module_params.host_nperio_tx_fifo_size != -1) {
+		retval +=
+		    dwc_otg_set_param_host_nperio_tx_fifo_size(core_if,
+							       dwc_otg_module_params.
+							       host_nperio_tx_fifo_size);
+	}
+	if (dwc_otg_module_params.host_perio_tx_fifo_size != -1) {
+		retval +=
+		    dwc_otg_set_param_host_perio_tx_fifo_size(core_if,
+							      dwc_otg_module_params.
+							      host_perio_tx_fifo_size);
+	}
+	if (dwc_otg_module_params.max_transfer_size != -1) {
+		retval +=
+		    dwc_otg_set_param_max_transfer_size(core_if,
+							dwc_otg_module_params.
+							max_transfer_size);
+	}
+	if (dwc_otg_module_params.max_packet_count != -1) {
+		retval +=
+		    dwc_otg_set_param_max_packet_count(core_if,
+						       dwc_otg_module_params.
+						       max_packet_count);
+	}
+	if (dwc_otg_module_params.host_channels != -1) {
+		retval +=
+		    dwc_otg_set_param_host_channels(core_if,
+						    dwc_otg_module_params.
+						    host_channels);
+	}
+	if (dwc_otg_module_params.dev_endpoints != -1) {
+		retval +=
+		    dwc_otg_set_param_dev_endpoints(core_if,
+						    dwc_otg_module_params.
+						    dev_endpoints);
+	}
+	if (dwc_otg_module_params.phy_type != -1) {
+		retval +=
+		    dwc_otg_set_param_phy_type(core_if,
+					       dwc_otg_module_params.phy_type);
+	}
+	if (dwc_otg_module_params.speed != -1) {
+		retval +=
+		    dwc_otg_set_param_speed(core_if,
+					    dwc_otg_module_params.speed);
+	}
+	if (dwc_otg_module_params.host_ls_low_power_phy_clk != -1) {
+		retval +=
+		    dwc_otg_set_param_host_ls_low_power_phy_clk(core_if,
+								dwc_otg_module_params.
+								host_ls_low_power_phy_clk);
+	}
+	if (dwc_otg_module_params.phy_ulpi_ddr != -1) {
+		retval +=
+		    dwc_otg_set_param_phy_ulpi_ddr(core_if,
+						   dwc_otg_module_params.
+						   phy_ulpi_ddr);
+	}
+	if (dwc_otg_module_params.phy_ulpi_ext_vbus != -1) {
+		retval +=
+		    dwc_otg_set_param_phy_ulpi_ext_vbus(core_if,
+							dwc_otg_module_params.
+							phy_ulpi_ext_vbus);
+	}
+	if (dwc_otg_module_params.phy_utmi_width != -1) {
+		retval +=
+		    dwc_otg_set_param_phy_utmi_width(core_if,
+						     dwc_otg_module_params.
+						     phy_utmi_width);
+	}
+	if (dwc_otg_module_params.ulpi_fs_ls != -1) {
+		retval +=
+		    dwc_otg_set_param_ulpi_fs_ls(core_if,
+						 dwc_otg_module_params.ulpi_fs_ls);
+	}
+	if (dwc_otg_module_params.ts_dline != -1) {
+		retval +=
+		    dwc_otg_set_param_ts_dline(core_if,
+					       dwc_otg_module_params.ts_dline);
+	}
+	if (dwc_otg_module_params.i2c_enable != -1) {
+		retval +=
+		    dwc_otg_set_param_i2c_enable(core_if,
+						 dwc_otg_module_params.
+						 i2c_enable);
+	}
+	if (dwc_otg_module_params.en_multiple_tx_fifo != -1) {
+		retval +=
+		    dwc_otg_set_param_en_multiple_tx_fifo(core_if,
+							  dwc_otg_module_params.
+							  en_multiple_tx_fifo);
+	}
+	for (i = 0; i < 15; i++) {
+		if (dwc_otg_module_params.dev_perio_tx_fifo_size[i] != -1) {
+			retval +=
+			    dwc_otg_set_param_dev_perio_tx_fifo_size(core_if,
+								     dwc_otg_module_params.
+								     dev_perio_tx_fifo_size
+								     [i], i);
+		}
+	}
+
+	for (i = 0; i < 15; i++) {
+		if (dwc_otg_module_params.dev_tx_fifo_size[i] != -1) {
+			retval += dwc_otg_set_param_dev_tx_fifo_size(core_if,
+								     dwc_otg_module_params.
+								     dev_tx_fifo_size
+								     [i], i);
+		}
+	}
+	if (dwc_otg_module_params.thr_ctl != -1) {
+		retval +=
+		    dwc_otg_set_param_thr_ctl(core_if,
+					      dwc_otg_module_params.thr_ctl);
+	}
+	if (dwc_otg_module_params.mpi_enable != -1) {
+		retval +=
+		    dwc_otg_set_param_mpi_enable(core_if,
+						 dwc_otg_module_params.
+						 mpi_enable);
+	}
+	if (dwc_otg_module_params.pti_enable != -1) {
+		retval +=
+		    dwc_otg_set_param_pti_enable(core_if,
+						 dwc_otg_module_params.
+						 pti_enable);
+	}
+	if (dwc_otg_module_params.lpm_enable != -1) {
+		retval +=
+		    dwc_otg_set_param_lpm_enable(core_if,
+						 dwc_otg_module_params.
+						 lpm_enable);
+	}
+	if (dwc_otg_module_params.ic_usb_cap != -1) {
+		retval +=
+		    dwc_otg_set_param_ic_usb_cap(core_if,
+						 dwc_otg_module_params.
+						 ic_usb_cap);
+	}
+	if (dwc_otg_module_params.tx_thr_length != -1) {
+		retval +=
+		    dwc_otg_set_param_tx_thr_length(core_if,
+						    dwc_otg_module_params.tx_thr_length);
+	}
+	if (dwc_otg_module_params.rx_thr_length != -1) {
+		retval +=
+		    dwc_otg_set_param_rx_thr_length(core_if,
+						    dwc_otg_module_params.
+						    rx_thr_length);
+	}
+	if (dwc_otg_module_params.ahb_thr_ratio != -1) {
+		retval +=
+		    dwc_otg_set_param_ahb_thr_ratio(core_if,
+						    dwc_otg_module_params.ahb_thr_ratio);
+	}
+	if (dwc_otg_module_params.power_down != -1) {
+		retval +=
+		    dwc_otg_set_param_power_down(core_if,
+						 dwc_otg_module_params.power_down);
+	}
+	if (dwc_otg_module_params.reload_ctl != -1) {
+		retval +=
+		    dwc_otg_set_param_reload_ctl(core_if,
+						 dwc_otg_module_params.reload_ctl);
+	}
+
+	if (dwc_otg_module_params.dev_out_nak != -1) {
+		retval +=
+			dwc_otg_set_param_dev_out_nak(core_if,
+			dwc_otg_module_params.dev_out_nak);
+	}
+
+	if (dwc_otg_module_params.cont_on_bna != -1) {
+		retval +=
+			dwc_otg_set_param_cont_on_bna(core_if,
+			dwc_otg_module_params.cont_on_bna);
+	}
+
+	if (dwc_otg_module_params.ahb_single != -1) {
+		retval +=
+			dwc_otg_set_param_ahb_single(core_if,
+			dwc_otg_module_params.ahb_single);
+	}
+
+	if (dwc_otg_module_params.otg_ver != -1) {
+		retval +=
+		    dwc_otg_set_param_otg_ver(core_if,
+					      dwc_otg_module_params.otg_ver);
+	}
+	if (dwc_otg_module_params.adp_enable != -1) {
+		retval +=
+		    dwc_otg_set_param_adp_enable(core_if,
+						 dwc_otg_module_params.
+						 adp_enable);
+	}
+	return retval;
+}
+
+/**
+ * This function is the top level interrupt handler for the Common
+ * (Device and host modes) interrupts.
+ */
+static irqreturn_t dwc_otg_common_irq(int irq, void *dev)
+{
+	int32_t retval = IRQ_NONE;
+
+	retval = dwc_otg_handle_common_intr(dev);
+	if (retval != 0) {
+		S3C2410X_CLEAR_EINTPEND();
+	}
+	return IRQ_RETVAL(retval);
+}
+
+/**
+ * This function is called when a lm_device is unregistered with the
+ * dwc_otg_driver. This happens, for example, when the rmmod command is
+ * executed. The device may or may not be electrically present. If it is
+ * present, the driver stops device processing. Any resources used on behalf
+ * of this device are freed.
+ *
+ * @param _dev
+ */
+#ifdef LM_INTERFACE
+#define REM_RETVAL(n)
+static void dwc_otg_driver_remove(	 struct lm_device *_dev )
+{       dwc_otg_device_t *otg_dev = lm_get_drvdata(_dev);
+#elif  defined(PCI_INTERFACE)
+#define REM_RETVAL(n)
+static void dwc_otg_driver_remove(	 struct pci_dev *_dev )
+{	dwc_otg_device_t *otg_dev = pci_get_drvdata(_dev);
+#elif  defined(PLATFORM_INTERFACE)
+#define REM_RETVAL(n) n
+static void dwc_otg_driver_remove(        struct platform_device *_dev )
+{       dwc_otg_device_t *otg_dev = platform_get_drvdata(_dev);
+#endif
+
+	DWC_DEBUGPL(DBG_ANY, "%s(%p) otg_dev %p\n", __func__, _dev, otg_dev);
+
+	if (!otg_dev) {
+		/* Memory allocation for the dwc_otg_device failed. */
+		DWC_DEBUGPL(DBG_ANY, "%s: otg_dev NULL!\n", __func__);
+	}
+#ifndef DWC_DEVICE_ONLY
+	if (otg_dev->hcd) {
+		hcd_remove(_dev);
+	} else {
+		DWC_DEBUGPL(DBG_ANY, "%s: otg_dev->hcd NULL!\n", __func__);
+	}
+#endif
+
+#ifndef DWC_HOST_ONLY
+	if (otg_dev->pcd) {
+		pcd_remove(_dev);
+	} else {
+		DWC_DEBUGPL(DBG_ANY, "%s: otg_dev->pcd NULL!\n", __func__);
+	}
+#endif
+	/*
+	 * Free the IRQ
+	 */
+	if (otg_dev->common_irq_installed) {
+		free_irq(otg_dev->os_dep.irq_num, otg_dev);
+        } else {
+		DWC_DEBUGPL(DBG_ANY, "%s: There is no installed irq!\n", __func__);
+	}
+
+	if (otg_dev->core_if) {
+		dwc_otg_cil_remove(otg_dev->core_if);
+	} else {
+		DWC_DEBUGPL(DBG_ANY, "%s: otg_dev->core_if NULL!\n", __func__);
+	}
+
+	/*
+	 * Remove the device attributes
+	 */
+	dwc_otg_attr_remove(_dev);
+
+	/*
+	 * Return the memory.
+	 */
+	if (otg_dev->os_dep.base) {
+		iounmap(otg_dev->os_dep.base);
+	}
+	DWC_FREE(otg_dev);
+
+	/*
+	 * Clear the drvdata pointer.
+	 */
+#ifdef LM_INTERFACE
+	lm_set_drvdata(_dev, 0);
+#elif defined(PCI_INTERFACE)
+        release_mem_region(otg_dev->os_dep.rsrc_start,
+                           otg_dev->os_dep.rsrc_len);
+	pci_set_drvdata(_dev, 0);
+#elif  defined(PLATFORM_INTERFACE)
+        platform_set_drvdata(_dev, 0);
+#endif
+}
+
+/**
+ * This function is called when an lm_device is bound to a
+ * dwc_otg_driver. It creates the driver components required to
+ * control the device (CIL, HCD, and PCD) and it initializes the
+ * device. The driver components are stored in a dwc_otg_device
+ * structure. A reference to the dwc_otg_device is saved in the
+ * lm_device. This allows the driver to access the dwc_otg_device
+ * structure on subsequent calls to driver methods for this device.
+ *
+ * @param _dev Bus device
+ */
+static int dwc_otg_driver_probe(
+#ifdef LM_INTERFACE
+				       struct lm_device *_dev
+#elif defined(PCI_INTERFACE)
+				       struct pci_dev *_dev,
+				       const struct pci_device_id *id
+#elif  defined(PLATFORM_INTERFACE)
+                                       struct platform_device *_dev
+#endif
+    )
+{
+	int retval = 0;
+	dwc_otg_device_t *dwc_otg_device;
+        int devirq;
+
+	dev_dbg(&_dev->dev, "dwc_otg_driver_probe(%p)\n", _dev);
+#ifdef LM_INTERFACE
+	dev_dbg(&_dev->dev, "start=0x%08x\n", (unsigned)_dev->resource.start);
+#elif defined(PCI_INTERFACE)
+	if (!id) {
+		DWC_ERROR("Invalid pci_device_id %p", id);
+		return -EINVAL;
+	}
+
+	if (!_dev || (pci_enable_device(_dev) < 0)) {
+		DWC_ERROR("Invalid pci_device %p", _dev);
+		return -ENODEV;
+	}
+	dev_dbg(&_dev->dev, "start=0x%08x\n", (unsigned)pci_resource_start(_dev,0));
+	/* other stuff needed as well? */
+
+#elif  defined(PLATFORM_INTERFACE)
+	dev_dbg(&_dev->dev, "start=0x%08x (len 0x%x)\n",
+                (unsigned)_dev->resource->start,
+                (unsigned)(_dev->resource->end - _dev->resource->start));
+#endif
+
+	dwc_otg_device = DWC_ALLOC(sizeof(dwc_otg_device_t));
+
+	if (!dwc_otg_device) {
+		dev_err(&_dev->dev, "kmalloc of dwc_otg_device failed\n");
+		return -ENOMEM;
+	}
+
+	memset(dwc_otg_device, 0, sizeof(*dwc_otg_device));
+	dwc_otg_device->os_dep.reg_offset = 0xFFFFFFFF;
+	dwc_otg_device->os_dep.platformdev = _dev;
+
+	/*
+	 * Map the DWC_otg Core memory into virtual address space.
+	 */
+#ifdef LM_INTERFACE
+	dwc_otg_device->os_dep.base = ioremap(_dev->resource.start, SZ_256K);
+
+	if (!dwc_otg_device->os_dep.base) {
+		dev_err(&_dev->dev, "ioremap() failed\n");
+		DWC_FREE(dwc_otg_device);
+		return -ENOMEM;
+	}
+	dev_dbg(&_dev->dev, "base=0x%08x\n",
+		(unsigned)dwc_otg_device->os_dep.base);
+#elif defined(PCI_INTERFACE)
+	_dev->current_state = PCI_D0;
+	_dev->dev.power.power_state = PMSG_ON;
+
+	if (!_dev->irq) {
+		DWC_ERROR("Found HC with no IRQ. Check BIOS/PCI %s setup!",
+			  pci_name(_dev));
+		iounmap(dwc_otg_device->os_dep.base);
+		DWC_FREE(dwc_otg_device);
+		return -ENODEV;
+	}
+
+	dwc_otg_device->os_dep.rsrc_start = pci_resource_start(_dev, 0);
+	dwc_otg_device->os_dep.rsrc_len = pci_resource_len(_dev, 0);
+	DWC_DEBUGPL(DBG_ANY, "PCI resource: start=%08x, len=%08x\n",
+		    (unsigned)dwc_otg_device->os_dep.rsrc_start,
+		    (unsigned)dwc_otg_device->os_dep.rsrc_len);
+	if (!request_mem_region
+	    (dwc_otg_device->os_dep.rsrc_start, dwc_otg_device->os_dep.rsrc_len,
+	     "dwc_otg")) {
+		dev_dbg(&_dev->dev, "error requesting memory\n");
+		iounmap(dwc_otg_device->os_dep.base);
+		DWC_FREE(dwc_otg_device);
+		return -EFAULT;
+	}
+
+	dwc_otg_device->os_dep.base =
+	    ioremap(dwc_otg_device->os_dep.rsrc_start,
+			    dwc_otg_device->os_dep.rsrc_len);
+	if (dwc_otg_device->os_dep.base == NULL) {
+		dev_dbg(&_dev->dev, "error mapping memory\n");
+		release_mem_region(dwc_otg_device->os_dep.rsrc_start,
+				   dwc_otg_device->os_dep.rsrc_len);
+		iounmap(dwc_otg_device->os_dep.base);
+		DWC_FREE(dwc_otg_device);
+		return -EFAULT;
+	}
+	dev_dbg(&_dev->dev, "base=0x%p (before adjust) \n",
+		dwc_otg_device->os_dep.base);
+	dwc_otg_device->os_dep.base = (char *)dwc_otg_device->os_dep.base;
+	dev_dbg(&_dev->dev, "base=0x%p (after adjust) \n",
+		dwc_otg_device->os_dep.base);
+	dev_dbg(&_dev->dev, "%s: mapped PA 0x%x to VA 0x%p\n", __func__,
+		(unsigned)dwc_otg_device->os_dep.rsrc_start,
+		dwc_otg_device->os_dep.base);
+
+	pci_set_master(_dev);
+	pci_set_drvdata(_dev, dwc_otg_device);
+#elif defined(PLATFORM_INTERFACE)
+        DWC_DEBUGPL(DBG_ANY,"Platform resource: start=%08x, len=%08x\n",
+                    _dev->resource->start,
+                    _dev->resource->end - _dev->resource->start + 1);
+#if 1
+        if (!request_mem_region(_dev->resource[0].start,
+                                _dev->resource[0].end - _dev->resource[0].start + 1,
+                                "dwc_otg")) {
+          dev_dbg(&_dev->dev, "error reserving mapped memory\n");
+          retval = -EFAULT;
+          goto fail;
+        }
+
+	dwc_otg_device->os_dep.base = ioremap(_dev->resource[0].start,
+                                                      _dev->resource[0].end -
+                                                      _dev->resource[0].start+1);
+	if (fiq_enable)
+	{
+		if (!request_mem_region(_dev->resource[1].start,
+	                                _dev->resource[1].end - _dev->resource[1].start + 1,
+	                                "dwc_otg")) {
+			dev_dbg(&_dev->dev, "error reserving mapped memory\n");
+			retval = -EFAULT;
+			goto fail;
+		}
+
+		dwc_otg_device->os_dep.mphi_base = ioremap(_dev->resource[1].start,
+							    _dev->resource[1].end -
+							    _dev->resource[1].start + 1);
+		dwc_otg_device->os_dep.use_swirq = (_dev->resource[1].end - _dev->resource[1].start) == 0x200;
+	}
+
+#else
+        {
+                struct map_desc desc = {
+                    .virtual = IO_ADDRESS((unsigned)_dev->resource->start),
+                    .pfn     = __phys_to_pfn((unsigned)_dev->resource->start),
+                    .length  = SZ_128K,
+                    .type    = MT_DEVICE
+                };
+                iotable_init(&desc, 1);
+                dwc_otg_device->os_dep.base = (void *)desc.virtual;
+        }
+#endif
+	if (!dwc_otg_device->os_dep.base) {
+		dev_err(&_dev->dev, "ioremap() failed\n");
+		retval = -ENOMEM;
+		goto fail;
+	}
+#endif
+
+	/*
+	 * Initialize driver data to point to the global DWC_otg
+	 * Device structure.
+	 */
+#ifdef LM_INTERFACE
+	lm_set_drvdata(_dev, dwc_otg_device);
+#elif defined(PLATFORM_INTERFACE)
+	platform_set_drvdata(_dev, dwc_otg_device);
+#endif
+	dev_dbg(&_dev->dev, "dwc_otg_device=0x%p\n", dwc_otg_device);
+
+	dwc_otg_device->core_if = dwc_otg_cil_init(dwc_otg_device->os_dep.base);
+        DWC_DEBUGPL(DBG_HCDV, "probe of device %p given core_if %p\n",
+                    dwc_otg_device, dwc_otg_device->core_if);//GRAYG
+
+	if (!dwc_otg_device->core_if) {
+		dev_err(&_dev->dev, "CIL initialization failed!\n");
+		retval = -ENOMEM;
+		goto fail;
+	}
+
+	dev_dbg(&_dev->dev, "Calling get_gsnpsid\n");
+	/*
+	 * Attempt to ensure this device is really a DWC_otg Controller.
+	 * Read and verify the SNPSID register contents. The value should be
+	 * 0x45F42XXX or 0x45F42XXX, which corresponds to either "OT2" or "OTG3",
+	 * as in "OTG version 2.XX" or "OTG version 3.XX".
+	 */
+
+	if (((dwc_otg_get_gsnpsid(dwc_otg_device->core_if) & 0xFFFFF000) !=	0x4F542000) &&
+		((dwc_otg_get_gsnpsid(dwc_otg_device->core_if) & 0xFFFFF000) != 0x4F543000)) {
+		dev_err(&_dev->dev, "Bad value for SNPSID: 0x%08x\n",
+			dwc_otg_get_gsnpsid(dwc_otg_device->core_if));
+		retval = -EINVAL;
+		goto fail;
+	}
+
+	/*
+	 * Validate parameter values.
+	 */
+	dev_dbg(&_dev->dev, "Calling set_parameters\n");
+	if (set_parameters(dwc_otg_device->core_if)) {
+		retval = -EINVAL;
+		goto fail;
+	}
+
+	/*
+	 * Create Device Attributes in sysfs
+	 */
+	dev_dbg(&_dev->dev, "Calling attr_create\n");
+	dwc_otg_attr_create(_dev);
+
+	/*
+	 * Disable the global interrupt until all the interrupt
+	 * handlers are installed.
+	 */
+	dev_dbg(&_dev->dev, "Calling disable_global_interrupts\n");
+	dwc_otg_disable_global_interrupts(dwc_otg_device->core_if);
+
+	/*
+	 * Install the interrupt handler for the common interrupts before
+	 * enabling common interrupts in core_init below.
+	 */
+
+#if defined(PLATFORM_INTERFACE)
+	devirq = platform_get_irq_byname(_dev, fiq_enable ? "soft" : "usb");
+	if (devirq < 0)
+	    devirq = platform_get_irq(_dev, fiq_enable ? 0 : 1);
+#else
+	devirq = _dev->irq;
+#endif
+	DWC_DEBUGPL(DBG_CIL, "registering (common) handler for irq%d\n",
+		    devirq);
+	dev_dbg(&_dev->dev, "Calling request_irq(%d)\n", devirq);
+	retval = request_irq(devirq, dwc_otg_common_irq,
+                             IRQF_SHARED,
+                             "dwc_otg", dwc_otg_device);
+	if (retval) {
+		DWC_ERROR("request of irq%d failed\n", devirq);
+		retval = -EBUSY;
+		goto fail;
+	} else {
+		dwc_otg_device->common_irq_installed = 1;
+	}
+	dwc_otg_device->os_dep.irq_num = devirq;
+	dwc_otg_device->os_dep.fiq_num = -EINVAL;
+	if (fiq_enable) {
+		int devfiq = platform_get_irq_byname(_dev, "usb");
+		if (devfiq < 0)
+			devfiq = platform_get_irq(_dev, 1);
+		dwc_otg_device->os_dep.fiq_num = devfiq;
+	}
+
+#ifndef IRQF_TRIGGER_LOW
+#if defined(LM_INTERFACE) || defined(PLATFORM_INTERFACE)
+	dev_dbg(&_dev->dev, "Calling set_irq_type\n");
+	set_irq_type(devirq,
+#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30))
+                     IRQT_LOW
+#else
+                     IRQ_TYPE_LEVEL_LOW
+#endif
+                    );
+#endif
+#endif /*IRQF_TRIGGER_LOW*/
+
+	/*
+	 * Initialize the DWC_otg core.
+	 */
+	dev_dbg(&_dev->dev, "Calling dwc_otg_core_init\n");
+	dwc_otg_core_init(dwc_otg_device->core_if);
+
+#ifndef DWC_HOST_ONLY
+	/*
+	 * Initialize the PCD
+	 */
+	dev_dbg(&_dev->dev, "Calling pcd_init\n");
+	retval = pcd_init(_dev);
+	if (retval != 0) {
+		DWC_ERROR("pcd_init failed\n");
+		dwc_otg_device->pcd = NULL;
+		goto fail;
+	}
+#endif
+#ifndef DWC_DEVICE_ONLY
+	/*
+	 * Initialize the HCD
+	 */
+	dev_dbg(&_dev->dev, "Calling hcd_init\n");
+	retval = hcd_init(_dev);
+	if (retval != 0) {
+		DWC_ERROR("hcd_init failed\n");
+		dwc_otg_device->hcd = NULL;
+		goto fail;
+	}
+#endif
+        /* Recover from drvdata having been overwritten by hcd_init() */
+#ifdef LM_INTERFACE
+	lm_set_drvdata(_dev, dwc_otg_device);
+#elif defined(PLATFORM_INTERFACE)
+	platform_set_drvdata(_dev, dwc_otg_device);
+#elif defined(PCI_INTERFACE)
+	pci_set_drvdata(_dev, dwc_otg_device);
+	dwc_otg_device->os_dep.pcidev = _dev;
+#endif
+
+	/*
+	 * Enable the global interrupt after all the interrupt
+	 * handlers are installed if there is no ADP support else
+	 * perform initial actions required for Internal ADP logic.
+	 */
+	if (!dwc_otg_get_param_adp_enable(dwc_otg_device->core_if)) {
+	        dev_dbg(&_dev->dev, "Calling enable_global_interrupts\n");
+		dwc_otg_enable_global_interrupts(dwc_otg_device->core_if);
+	        dev_dbg(&_dev->dev, "Done\n");
+	} else
+		dwc_otg_adp_start(dwc_otg_device->core_if,
+							dwc_otg_is_host_mode(dwc_otg_device->core_if));
+
+	return 0;
+
+fail:
+	dwc_otg_driver_remove(_dev);
+	return retval;
+}
+
+/**
+ * This structure defines the methods to be called by a bus driver
+ * during the lifecycle of a device on that bus. Both drivers and
+ * devices are registered with a bus driver. The bus driver matches
+ * devices to drivers based on information in the device and driver
+ * structures.
+ *
+ * The probe function is called when the bus driver matches a device
+ * to this driver. The remove function is called when a device is
+ * unregistered with the bus driver.
+ */
+#ifdef LM_INTERFACE
+static struct lm_driver dwc_otg_driver = {
+	.drv = {.name = (char *)dwc_driver_name,},
+	.probe = dwc_otg_driver_probe,
+	.remove = dwc_otg_driver_remove,
+        // 'suspend' and 'resume' absent
+};
+#elif defined(PCI_INTERFACE)
+static const struct pci_device_id pci_ids[] = { {
+						 PCI_DEVICE(0x16c3, 0xabcd),
+						 .driver_data =
+						 (unsigned long)0xdeadbeef,
+						 }, { /* end: all zeroes */ }
+};
+
+MODULE_DEVICE_TABLE(pci, pci_ids);
+
+/* pci driver glue; this is a "new style" PCI driver module */
+static struct pci_driver dwc_otg_driver = {
+	.name = "dwc_otg",
+	.id_table = pci_ids,
+
+	.probe = dwc_otg_driver_probe,
+	.remove = dwc_otg_driver_remove,
+
+	.driver = {
+		   .name = (char *)dwc_driver_name,
+		   },
+};
+#elif defined(PLATFORM_INTERFACE)
+static struct platform_device_id platform_ids[] = {
+        {
+              .name = "bcm2708_usb",
+              .driver_data = (kernel_ulong_t) 0xdeadbeef,
+        },
+        { /* end: all zeroes */ }
+};
+MODULE_DEVICE_TABLE(platform, platform_ids);
+
+static const struct of_device_id dwc_otg_of_match_table[] = {
+	{ .compatible = "brcm,bcm2708-usb", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, dwc_otg_of_match_table);
+
+static struct platform_driver dwc_otg_driver = {
+	.driver = {
+		.name = (char *)dwc_driver_name,
+		.of_match_table = dwc_otg_of_match_table,
+		},
+        .id_table = platform_ids,
+
+	.probe = dwc_otg_driver_probe,
+	.remove = dwc_otg_driver_remove,
+        // no 'shutdown', 'suspend', 'resume', 'suspend_late' or 'resume_early'
+};
+#endif
+
+/**
+ * This function is called when the dwc_otg_driver is installed with the
+ * insmod command. It registers the dwc_otg_driver structure with the
+ * appropriate bus driver. This will cause the dwc_otg_driver_probe function
+ * to be called. In addition, the bus driver will automatically expose
+ * attributes defined for the device and driver in the special sysfs file
+ * system.
+ *
+ * @return
+ */
+static int __init dwc_otg_driver_init(void)
+{
+	int retval = 0;
+	int error;
+        struct device_driver *drv;
+
+	if(fiq_fsm_enable && !fiq_enable) {
+		printk(KERN_WARNING "dwc_otg: fiq_fsm_enable was set without fiq_enable! Correcting.\n");
+		fiq_enable = 1;
+	}
+
+	printk(KERN_INFO "%s: version %s (%s bus)\n", dwc_driver_name,
+	       DWC_DRIVER_VERSION,
+#ifdef LM_INTERFACE
+               "logicmodule");
+	retval = lm_driver_register(&dwc_otg_driver);
+        drv = &dwc_otg_driver.drv;
+#elif defined(PCI_INTERFACE)
+               "pci");
+	retval = pci_register_driver(&dwc_otg_driver);
+        drv = &dwc_otg_driver.driver;
+#elif defined(PLATFORM_INTERFACE)
+               "platform");
+	retval = platform_driver_register(&dwc_otg_driver);
+        drv = &dwc_otg_driver.driver;
+#endif
+	if (retval < 0) {
+		printk(KERN_ERR "%s retval=%d\n", __func__, retval);
+		return retval;
+	}
+	printk(KERN_DEBUG "dwc_otg: FIQ %s\n", fiq_enable ? "enabled":"disabled");
+	printk(KERN_DEBUG "dwc_otg: NAK holdoff %s\n", nak_holdoff ? "enabled":"disabled");
+	printk(KERN_DEBUG "dwc_otg: FIQ split-transaction FSM %s\n", fiq_fsm_enable ? "enabled":"disabled");
+
+	error = driver_create_file(drv, &driver_attr_version);
+#ifdef DEBUG
+	error = driver_create_file(drv, &driver_attr_debuglevel);
+#endif
+	return retval;
+}
+
+module_init(dwc_otg_driver_init);
+
+/**
+ * This function is called when the driver is removed from the kernel
+ * with the rmmod command. The driver unregisters itself with its bus
+ * driver.
+ *
+ */
+static void __exit dwc_otg_driver_cleanup(void)
+{
+	printk(KERN_DEBUG "dwc_otg_driver_cleanup()\n");
+
+#ifdef LM_INTERFACE
+	driver_remove_file(&dwc_otg_driver.drv, &driver_attr_debuglevel);
+	driver_remove_file(&dwc_otg_driver.drv, &driver_attr_version);
+	lm_driver_unregister(&dwc_otg_driver);
+#elif defined(PCI_INTERFACE)
+	driver_remove_file(&dwc_otg_driver.driver, &driver_attr_debuglevel);
+	driver_remove_file(&dwc_otg_driver.driver, &driver_attr_version);
+	pci_unregister_driver(&dwc_otg_driver);
+#elif defined(PLATFORM_INTERFACE)
+	driver_remove_file(&dwc_otg_driver.driver, &driver_attr_debuglevel);
+	driver_remove_file(&dwc_otg_driver.driver, &driver_attr_version);
+	platform_driver_unregister(&dwc_otg_driver);
+#endif
+
+	printk(KERN_INFO "%s module removed\n", dwc_driver_name);
+}
+
+module_exit(dwc_otg_driver_cleanup);
+
+MODULE_DESCRIPTION(DWC_DRIVER_DESC);
+MODULE_AUTHOR("Synopsys Inc.");
+MODULE_LICENSE("GPL");
+
+module_param_named(otg_cap, dwc_otg_module_params.otg_cap, int, 0444);
+MODULE_PARM_DESC(otg_cap, "OTG Capabilities 0=HNP&SRP 1=SRP Only 2=None");
+module_param_named(opt, dwc_otg_module_params.opt, int, 0444);
+MODULE_PARM_DESC(opt, "OPT Mode");
+module_param_named(dma_enable, dwc_otg_module_params.dma_enable, int, 0444);
+MODULE_PARM_DESC(dma_enable, "DMA Mode 0=Slave 1=DMA enabled");
+
+module_param_named(dma_desc_enable, dwc_otg_module_params.dma_desc_enable, int,
+		   0444);
+MODULE_PARM_DESC(dma_desc_enable,
+		 "DMA Desc Mode 0=Address DMA 1=DMA Descriptor enabled");
+
+module_param_named(dma_burst_size, dwc_otg_module_params.dma_burst_size, int,
+		   0444);
+MODULE_PARM_DESC(dma_burst_size,
+		 "DMA Burst Size 1, 4, 8, 16, 32, 64, 128, 256");
+module_param_named(speed, dwc_otg_module_params.speed, int, 0444);
+MODULE_PARM_DESC(speed, "Speed 0=High Speed 1=Full Speed");
+module_param_named(host_support_fs_ls_low_power,
+		   dwc_otg_module_params.host_support_fs_ls_low_power, int,
+		   0444);
+MODULE_PARM_DESC(host_support_fs_ls_low_power,
+		 "Support Low Power w/FS or LS 0=Support 1=Don't Support");
+module_param_named(host_ls_low_power_phy_clk,
+		   dwc_otg_module_params.host_ls_low_power_phy_clk, int, 0444);
+MODULE_PARM_DESC(host_ls_low_power_phy_clk,
+		 "Low Speed Low Power Clock 0=48Mhz 1=6Mhz");
+module_param_named(enable_dynamic_fifo,
+		   dwc_otg_module_params.enable_dynamic_fifo, int, 0444);
+MODULE_PARM_DESC(enable_dynamic_fifo, "0=cC Setting 1=Allow Dynamic Sizing");
+module_param_named(data_fifo_size, dwc_otg_module_params.data_fifo_size, int,
+		   0444);
+MODULE_PARM_DESC(data_fifo_size,
+		 "Total number of words in the data FIFO memory 32-32768");
+module_param_named(dev_rx_fifo_size, dwc_otg_module_params.dev_rx_fifo_size,
+		   int, 0444);
+MODULE_PARM_DESC(dev_rx_fifo_size, "Number of words in the Rx FIFO 16-32768");
+module_param_named(dev_nperio_tx_fifo_size,
+		   dwc_otg_module_params.dev_nperio_tx_fifo_size, int, 0444);
+MODULE_PARM_DESC(dev_nperio_tx_fifo_size,
+		 "Number of words in the non-periodic Tx FIFO 16-32768");
+module_param_named(dev_perio_tx_fifo_size_1,
+		   dwc_otg_module_params.dev_perio_tx_fifo_size[0], int, 0444);
+MODULE_PARM_DESC(dev_perio_tx_fifo_size_1,
+		 "Number of words in the periodic Tx FIFO 4-768");
+module_param_named(dev_perio_tx_fifo_size_2,
+		   dwc_otg_module_params.dev_perio_tx_fifo_size[1], int, 0444);
+MODULE_PARM_DESC(dev_perio_tx_fifo_size_2,
+		 "Number of words in the periodic Tx FIFO 4-768");
+module_param_named(dev_perio_tx_fifo_size_3,
+		   dwc_otg_module_params.dev_perio_tx_fifo_size[2], int, 0444);
+MODULE_PARM_DESC(dev_perio_tx_fifo_size_3,
+		 "Number of words in the periodic Tx FIFO 4-768");
+module_param_named(dev_perio_tx_fifo_size_4,
+		   dwc_otg_module_params.dev_perio_tx_fifo_size[3], int, 0444);
+MODULE_PARM_DESC(dev_perio_tx_fifo_size_4,
+		 "Number of words in the periodic Tx FIFO 4-768");
+module_param_named(dev_perio_tx_fifo_size_5,
+		   dwc_otg_module_params.dev_perio_tx_fifo_size[4], int, 0444);
+MODULE_PARM_DESC(dev_perio_tx_fifo_size_5,
+		 "Number of words in the periodic Tx FIFO 4-768");
+module_param_named(dev_perio_tx_fifo_size_6,
+		   dwc_otg_module_params.dev_perio_tx_fifo_size[5], int, 0444);
+MODULE_PARM_DESC(dev_perio_tx_fifo_size_6,
+		 "Number of words in the periodic Tx FIFO 4-768");
+module_param_named(dev_perio_tx_fifo_size_7,
+		   dwc_otg_module_params.dev_perio_tx_fifo_size[6], int, 0444);
+MODULE_PARM_DESC(dev_perio_tx_fifo_size_7,
+		 "Number of words in the periodic Tx FIFO 4-768");
+module_param_named(dev_perio_tx_fifo_size_8,
+		   dwc_otg_module_params.dev_perio_tx_fifo_size[7], int, 0444);
+MODULE_PARM_DESC(dev_perio_tx_fifo_size_8,
+		 "Number of words in the periodic Tx FIFO 4-768");
+module_param_named(dev_perio_tx_fifo_size_9,
+		   dwc_otg_module_params.dev_perio_tx_fifo_size[8], int, 0444);
+MODULE_PARM_DESC(dev_perio_tx_fifo_size_9,
+		 "Number of words in the periodic Tx FIFO 4-768");
+module_param_named(dev_perio_tx_fifo_size_10,
+		   dwc_otg_module_params.dev_perio_tx_fifo_size[9], int, 0444);
+MODULE_PARM_DESC(dev_perio_tx_fifo_size_10,
+		 "Number of words in the periodic Tx FIFO 4-768");
+module_param_named(dev_perio_tx_fifo_size_11,
+		   dwc_otg_module_params.dev_perio_tx_fifo_size[10], int, 0444);
+MODULE_PARM_DESC(dev_perio_tx_fifo_size_11,
+		 "Number of words in the periodic Tx FIFO 4-768");
+module_param_named(dev_perio_tx_fifo_size_12,
+		   dwc_otg_module_params.dev_perio_tx_fifo_size[11], int, 0444);
+MODULE_PARM_DESC(dev_perio_tx_fifo_size_12,
+		 "Number of words in the periodic Tx FIFO 4-768");
+module_param_named(dev_perio_tx_fifo_size_13,
+		   dwc_otg_module_params.dev_perio_tx_fifo_size[12], int, 0444);
+MODULE_PARM_DESC(dev_perio_tx_fifo_size_13,
+		 "Number of words in the periodic Tx FIFO 4-768");
+module_param_named(dev_perio_tx_fifo_size_14,
+		   dwc_otg_module_params.dev_perio_tx_fifo_size[13], int, 0444);
+MODULE_PARM_DESC(dev_perio_tx_fifo_size_14,
+		 "Number of words in the periodic Tx FIFO 4-768");
+module_param_named(dev_perio_tx_fifo_size_15,
+		   dwc_otg_module_params.dev_perio_tx_fifo_size[14], int, 0444);
+MODULE_PARM_DESC(dev_perio_tx_fifo_size_15,
+		 "Number of words in the periodic Tx FIFO 4-768");
+module_param_named(host_rx_fifo_size, dwc_otg_module_params.host_rx_fifo_size,
+		   int, 0444);
+MODULE_PARM_DESC(host_rx_fifo_size, "Number of words in the Rx FIFO 16-32768");
+module_param_named(host_nperio_tx_fifo_size,
+		   dwc_otg_module_params.host_nperio_tx_fifo_size, int, 0444);
+MODULE_PARM_DESC(host_nperio_tx_fifo_size,
+		 "Number of words in the non-periodic Tx FIFO 16-32768");
+module_param_named(host_perio_tx_fifo_size,
+		   dwc_otg_module_params.host_perio_tx_fifo_size, int, 0444);
+MODULE_PARM_DESC(host_perio_tx_fifo_size,
+		 "Number of words in the host periodic Tx FIFO 16-32768");
+module_param_named(max_transfer_size, dwc_otg_module_params.max_transfer_size,
+		   int, 0444);
+/** @todo Set the max to 512K, modify checks */
+MODULE_PARM_DESC(max_transfer_size,
+		 "The maximum transfer size supported in bytes 2047-65535");
+module_param_named(max_packet_count, dwc_otg_module_params.max_packet_count,
+		   int, 0444);
+MODULE_PARM_DESC(max_packet_count,
+		 "The maximum number of packets in a transfer 15-511");
+module_param_named(host_channels, dwc_otg_module_params.host_channels, int,
+		   0444);
+MODULE_PARM_DESC(host_channels,
+		 "The number of host channel registers to use 1-16");
+module_param_named(dev_endpoints, dwc_otg_module_params.dev_endpoints, int,
+		   0444);
+MODULE_PARM_DESC(dev_endpoints,
+		 "The number of endpoints in addition to EP0 available for device mode 1-15");
+module_param_named(phy_type, dwc_otg_module_params.phy_type, int, 0444);
+MODULE_PARM_DESC(phy_type, "0=Reserved 1=UTMI+ 2=ULPI");
+module_param_named(phy_utmi_width, dwc_otg_module_params.phy_utmi_width, int,
+		   0444);
+MODULE_PARM_DESC(phy_utmi_width, "Specifies the UTMI+ Data Width 8 or 16 bits");
+module_param_named(phy_ulpi_ddr, dwc_otg_module_params.phy_ulpi_ddr, int, 0444);
+MODULE_PARM_DESC(phy_ulpi_ddr,
+		 "ULPI at double or single data rate 0=Single 1=Double");
+module_param_named(phy_ulpi_ext_vbus, dwc_otg_module_params.phy_ulpi_ext_vbus,
+		   int, 0444);
+MODULE_PARM_DESC(phy_ulpi_ext_vbus,
+		 "ULPI PHY using internal or external vbus 0=Internal");
+module_param_named(i2c_enable, dwc_otg_module_params.i2c_enable, int, 0444);
+MODULE_PARM_DESC(i2c_enable, "FS PHY Interface");
+module_param_named(ulpi_fs_ls, dwc_otg_module_params.ulpi_fs_ls, int, 0444);
+MODULE_PARM_DESC(ulpi_fs_ls, "ULPI PHY FS/LS mode only");
+module_param_named(ts_dline, dwc_otg_module_params.ts_dline, int, 0444);
+MODULE_PARM_DESC(ts_dline, "Term select Dline pulsing for all PHYs");
+module_param_named(debug, g_dbg_lvl, int, 0444);
+MODULE_PARM_DESC(debug, "");
+
+module_param_named(en_multiple_tx_fifo,
+		   dwc_otg_module_params.en_multiple_tx_fifo, int, 0444);
+MODULE_PARM_DESC(en_multiple_tx_fifo,
+		 "Dedicated Non Periodic Tx FIFOs 0=disabled 1=enabled");
+module_param_named(dev_tx_fifo_size_1,
+		   dwc_otg_module_params.dev_tx_fifo_size[0], int, 0444);
+MODULE_PARM_DESC(dev_tx_fifo_size_1, "Number of words in the Tx FIFO 4-768");
+module_param_named(dev_tx_fifo_size_2,
+		   dwc_otg_module_params.dev_tx_fifo_size[1], int, 0444);
+MODULE_PARM_DESC(dev_tx_fifo_size_2, "Number of words in the Tx FIFO 4-768");
+module_param_named(dev_tx_fifo_size_3,
+		   dwc_otg_module_params.dev_tx_fifo_size[2], int, 0444);
+MODULE_PARM_DESC(dev_tx_fifo_size_3, "Number of words in the Tx FIFO 4-768");
+module_param_named(dev_tx_fifo_size_4,
+		   dwc_otg_module_params.dev_tx_fifo_size[3], int, 0444);
+MODULE_PARM_DESC(dev_tx_fifo_size_4, "Number of words in the Tx FIFO 4-768");
+module_param_named(dev_tx_fifo_size_5,
+		   dwc_otg_module_params.dev_tx_fifo_size[4], int, 0444);
+MODULE_PARM_DESC(dev_tx_fifo_size_5, "Number of words in the Tx FIFO 4-768");
+module_param_named(dev_tx_fifo_size_6,
+		   dwc_otg_module_params.dev_tx_fifo_size[5], int, 0444);
+MODULE_PARM_DESC(dev_tx_fifo_size_6, "Number of words in the Tx FIFO 4-768");
+module_param_named(dev_tx_fifo_size_7,
+		   dwc_otg_module_params.dev_tx_fifo_size[6], int, 0444);
+MODULE_PARM_DESC(dev_tx_fifo_size_7, "Number of words in the Tx FIFO 4-768");
+module_param_named(dev_tx_fifo_size_8,
+		   dwc_otg_module_params.dev_tx_fifo_size[7], int, 0444);
+MODULE_PARM_DESC(dev_tx_fifo_size_8, "Number of words in the Tx FIFO 4-768");
+module_param_named(dev_tx_fifo_size_9,
+		   dwc_otg_module_params.dev_tx_fifo_size[8], int, 0444);
+MODULE_PARM_DESC(dev_tx_fifo_size_9, "Number of words in the Tx FIFO 4-768");
+module_param_named(dev_tx_fifo_size_10,
+		   dwc_otg_module_params.dev_tx_fifo_size[9], int, 0444);
+MODULE_PARM_DESC(dev_tx_fifo_size_10, "Number of words in the Tx FIFO 4-768");
+module_param_named(dev_tx_fifo_size_11,
+		   dwc_otg_module_params.dev_tx_fifo_size[10], int, 0444);
+MODULE_PARM_DESC(dev_tx_fifo_size_11, "Number of words in the Tx FIFO 4-768");
+module_param_named(dev_tx_fifo_size_12,
+		   dwc_otg_module_params.dev_tx_fifo_size[11], int, 0444);
+MODULE_PARM_DESC(dev_tx_fifo_size_12, "Number of words in the Tx FIFO 4-768");
+module_param_named(dev_tx_fifo_size_13,
+		   dwc_otg_module_params.dev_tx_fifo_size[12], int, 0444);
+MODULE_PARM_DESC(dev_tx_fifo_size_13, "Number of words in the Tx FIFO 4-768");
+module_param_named(dev_tx_fifo_size_14,
+		   dwc_otg_module_params.dev_tx_fifo_size[13], int, 0444);
+MODULE_PARM_DESC(dev_tx_fifo_size_14, "Number of words in the Tx FIFO 4-768");
+module_param_named(dev_tx_fifo_size_15,
+		   dwc_otg_module_params.dev_tx_fifo_size[14], int, 0444);
+MODULE_PARM_DESC(dev_tx_fifo_size_15, "Number of words in the Tx FIFO 4-768");
+
+module_param_named(thr_ctl, dwc_otg_module_params.thr_ctl, int, 0444);
+MODULE_PARM_DESC(thr_ctl,
+		 "Thresholding enable flag bit 0 - non ISO Tx thr., 1 - ISO Tx thr., 2 - Rx thr.- bit 0=disabled 1=enabled");
+module_param_named(tx_thr_length, dwc_otg_module_params.tx_thr_length, int,
+		   0444);
+MODULE_PARM_DESC(tx_thr_length, "Tx Threshold length in 32 bit DWORDs");
+module_param_named(rx_thr_length, dwc_otg_module_params.rx_thr_length, int,
+		   0444);
+MODULE_PARM_DESC(rx_thr_length, "Rx Threshold length in 32 bit DWORDs");
+
+module_param_named(pti_enable, dwc_otg_module_params.pti_enable, int, 0444);
+module_param_named(mpi_enable, dwc_otg_module_params.mpi_enable, int, 0444);
+module_param_named(lpm_enable, dwc_otg_module_params.lpm_enable, int, 0444);
+MODULE_PARM_DESC(lpm_enable, "LPM Enable 0=LPM Disabled 1=LPM Enabled");
+module_param_named(ic_usb_cap, dwc_otg_module_params.ic_usb_cap, int, 0444);
+MODULE_PARM_DESC(ic_usb_cap,
+		 "IC_USB Capability 0=IC_USB Disabled 1=IC_USB Enabled");
+module_param_named(ahb_thr_ratio, dwc_otg_module_params.ahb_thr_ratio, int,
+		   0444);
+MODULE_PARM_DESC(ahb_thr_ratio, "AHB Threshold Ratio");
+module_param_named(power_down, dwc_otg_module_params.power_down, int, 0444);
+MODULE_PARM_DESC(power_down, "Power Down Mode");
+module_param_named(reload_ctl, dwc_otg_module_params.reload_ctl, int, 0444);
+MODULE_PARM_DESC(reload_ctl, "HFIR Reload Control");
+module_param_named(dev_out_nak, dwc_otg_module_params.dev_out_nak, int, 0444);
+MODULE_PARM_DESC(dev_out_nak, "Enable Device OUT NAK");
+module_param_named(cont_on_bna, dwc_otg_module_params.cont_on_bna, int, 0444);
+MODULE_PARM_DESC(cont_on_bna, "Enable Enable Continue on BNA");
+module_param_named(ahb_single, dwc_otg_module_params.ahb_single, int, 0444);
+MODULE_PARM_DESC(ahb_single, "Enable AHB Single Support");
+module_param_named(adp_enable, dwc_otg_module_params.adp_enable, int, 0444);
+MODULE_PARM_DESC(adp_enable, "ADP Enable 0=ADP Disabled 1=ADP Enabled");
+module_param_named(otg_ver, dwc_otg_module_params.otg_ver, int, 0444);
+MODULE_PARM_DESC(otg_ver, "OTG revision supported 0=OTG 1.3 1=OTG 2.0");
+module_param(microframe_schedule, bool, 0444);
+MODULE_PARM_DESC(microframe_schedule, "Enable the microframe scheduler");
+
+module_param(fiq_enable, bool, 0444);
+MODULE_PARM_DESC(fiq_enable, "Enable the FIQ");
+module_param(nak_holdoff, ushort, 0644);
+MODULE_PARM_DESC(nak_holdoff, "Throttle duration for bulk split-transaction endpoints on a NAK. Default 8");
+module_param(fiq_fsm_enable, bool, 0444);
+MODULE_PARM_DESC(fiq_fsm_enable, "Enable the FIQ to perform split transactions as defined by fiq_fsm_mask");
+module_param(fiq_fsm_mask, ushort, 0444);
+MODULE_PARM_DESC(fiq_fsm_mask, "Bitmask of transactions to perform in the FIQ.\n"
+					"Bit 0 : Non-periodic split transactions\n"
+					"Bit 1 : Periodic split transactions\n"
+					"Bit 2 : High-speed multi-transfer isochronous\n"
+					"All other bits should be set 0.");
+module_param(int_ep_interval_min, ushort, 0644);
+MODULE_PARM_DESC(int_ep_interval_min, "Clamp high-speed Interrupt endpoints to a minimum polling interval.\n"
+					"0..1 = Use endpoint default\n"
+					"2..n = Minimum interval n microframes. Use powers of 2.\n");
+
+module_param(cil_force_host, bool, 0644);
+MODULE_PARM_DESC(cil_force_host, "On a connector-ID status change, "
+					"force Host Mode regardless of OTG state.");
+
+/** @page "Module Parameters"
+ *
+ * The following parameters may be specified when starting the module.
+ * These parameters define how the DWC_otg controller should be
+ * configured. Parameter values are passed to the CIL initialization
+ * function dwc_otg_cil_init
+ *
+ * Example: <code>modprobe dwc_otg speed=1 otg_cap=1</code>
+ *
+
+ <table>
+ <tr><td>Parameter Name</td><td>Meaning</td></tr>
+
+ <tr>
+ <td>otg_cap</td>
+ <td>Specifies the OTG capabilities. The driver will automatically detect the
+ value for this parameter if none is specified.
+ - 0: HNP and SRP capable (default, if available)
+ - 1: SRP Only capable
+ - 2: No HNP/SRP capable
+ </td></tr>
+
+ <tr>
+ <td>dma_enable</td>
+ <td>Specifies whether to use slave or DMA mode for accessing the data FIFOs.
+ The driver will automatically detect the value for this parameter if none is
+ specified.
+ - 0: Slave
+ - 1: DMA (default, if available)
+ </td></tr>
+
+ <tr>
+ <td>dma_burst_size</td>
+ <td>The DMA Burst size (applicable only for External DMA Mode).
+ - Values: 1, 4, 8 16, 32, 64, 128, 256 (default 32)
+ </td></tr>
+
+ <tr>
+ <td>speed</td>
+ <td>Specifies the maximum speed of operation in host and device mode. The
+ actual speed depends on the speed of the attached device and the value of
+ phy_type.
+ - 0: High Speed (default)
+ - 1: Full Speed
+ </td></tr>
+
+ <tr>
+ <td>host_support_fs_ls_low_power</td>
+ <td>Specifies whether low power mode is supported when attached to a Full
+ Speed or Low Speed device in host mode.
+ - 0: Don't support low power mode (default)
+ - 1: Support low power mode
+ </td></tr>
+
+ <tr>
+ <td>host_ls_low_power_phy_clk</td>
+ <td>Specifies the PHY clock rate in low power mode when connected to a Low
+ Speed device in host mode. This parameter is applicable only if
+ HOST_SUPPORT_FS_LS_LOW_POWER is enabled.
+ - 0: 48 MHz (default)
+ - 1: 6 MHz
+ </td></tr>
+
+ <tr>
+ <td>enable_dynamic_fifo</td>
+ <td> Specifies whether FIFOs may be resized by the driver software.
+ - 0: Use cC FIFO size parameters
+ - 1: Allow dynamic FIFO sizing (default)
+ </td></tr>
+
+ <tr>
+ <td>data_fifo_size</td>
+ <td>Total number of 4-byte words in the data FIFO memory. This memory
+ includes the Rx FIFO, non-periodic Tx FIFO, and periodic Tx FIFOs.
+ - Values: 32 to 32768 (default 8192)
+
+ Note: The total FIFO memory depth in the FPGA configuration is 8192.
+ </td></tr>
+
+ <tr>
+ <td>dev_rx_fifo_size</td>
+ <td>Number of 4-byte words in the Rx FIFO in device mode when dynamic
+ FIFO sizing is enabled.
+ - Values: 16 to 32768 (default 1064)
+ </td></tr>
+
+ <tr>
+ <td>dev_nperio_tx_fifo_size</td>
+ <td>Number of 4-byte words in the non-periodic Tx FIFO in device mode when
+ dynamic FIFO sizing is enabled.
+ - Values: 16 to 32768 (default 1024)
+ </td></tr>
+
+ <tr>
+ <td>dev_perio_tx_fifo_size_n (n = 1 to 15)</td>
+ <td>Number of 4-byte words in each of the periodic Tx FIFOs in device mode
+ when dynamic FIFO sizing is enabled.
+ - Values: 4 to 768 (default 256)
+ </td></tr>
+
+ <tr>
+ <td>host_rx_fifo_size</td>
+ <td>Number of 4-byte words in the Rx FIFO in host mode when dynamic FIFO
+ sizing is enabled.
+ - Values: 16 to 32768 (default 1024)
+ </td></tr>
+
+ <tr>
+ <td>host_nperio_tx_fifo_size</td>
+ <td>Number of 4-byte words in the non-periodic Tx FIFO in host mode when
+ dynamic FIFO sizing is enabled in the core.
+ - Values: 16 to 32768 (default 1024)
+ </td></tr>
+
+ <tr>
+ <td>host_perio_tx_fifo_size</td>
+ <td>Number of 4-byte words in the host periodic Tx FIFO when dynamic FIFO
+ sizing is enabled.
+ - Values: 16 to 32768 (default 1024)
+ </td></tr>
+
+ <tr>
+ <td>max_transfer_size</td>
+ <td>The maximum transfer size supported in bytes.
+ - Values: 2047 to 65,535 (default 65,535)
+ </td></tr>
+
+ <tr>
+ <td>max_packet_count</td>
+ <td>The maximum number of packets in a transfer.
+ - Values: 15 to 511 (default 511)
+ </td></tr>
+
+ <tr>
+ <td>host_channels</td>
+ <td>The number of host channel registers to use.
+ - Values: 1 to 16 (default 12)
+
+ Note: The FPGA configuration supports a maximum of 12 host channels.
+ </td></tr>
+
+ <tr>
+ <td>dev_endpoints</td>
+ <td>The number of endpoints in addition to EP0 available for device mode
+ operations.
+ - Values: 1 to 15 (default 6 IN and OUT)
+
+ Note: The FPGA configuration supports a maximum of 6 IN and OUT endpoints in
+ addition to EP0.
+ </td></tr>
+
+ <tr>
+ <td>phy_type</td>
+ <td>Specifies the type of PHY interface to use. By default, the driver will
+ automatically detect the phy_type.
+ - 0: Full Speed
+ - 1: UTMI+ (default, if available)
+ - 2: ULPI
+ </td></tr>
+
+ <tr>
+ <td>phy_utmi_width</td>
+ <td>Specifies the UTMI+ Data Width. This parameter is applicable for a
+ phy_type of UTMI+. Also, this parameter is applicable only if the
+ OTG_HSPHY_WIDTH cC parameter was set to "8 and 16 bits", meaning that the
+ core has been configured to work at either data path width.
+ - Values: 8 or 16 bits (default 16)
+ </td></tr>
+
+ <tr>
+ <td>phy_ulpi_ddr</td>
+ <td>Specifies whether the ULPI operates at double or single data rate. This
+ parameter is only applicable if phy_type is ULPI.
+ - 0: single data rate ULPI interface with 8 bit wide data bus (default)
+ - 1: double data rate ULPI interface with 4 bit wide data bus
+ </td></tr>
+
+ <tr>
+ <td>i2c_enable</td>
+ <td>Specifies whether to use the I2C interface for full speed PHY. This
+ parameter is only applicable if PHY_TYPE is FS.
+ - 0: Disabled (default)
+ - 1: Enabled
+ </td></tr>
+
+ <tr>
+ <td>ulpi_fs_ls</td>
+ <td>Specifies whether to use ULPI FS/LS mode only.
+ - 0: Disabled (default)
+ - 1: Enabled
+ </td></tr>
+
+ <tr>
+ <td>ts_dline</td>
+ <td>Specifies whether term select D-Line pulsing for all PHYs is enabled.
+ - 0: Disabled (default)
+ - 1: Enabled
+ </td></tr>
+
+ <tr>
+ <td>en_multiple_tx_fifo</td>
+ <td>Specifies whether dedicatedto tx fifos are enabled for non periodic IN EPs.
+ The driver will automatically detect the value for this parameter if none is
+ specified.
+ - 0: Disabled
+ - 1: Enabled (default, if available)
+ </td></tr>
+
+ <tr>
+ <td>dev_tx_fifo_size_n (n = 1 to 15)</td>
+ <td>Number of 4-byte words in each of the Tx FIFOs in device mode
+ when dynamic FIFO sizing is enabled.
+ - Values: 4 to 768 (default 256)
+ </td></tr>
+
+ <tr>
+ <td>tx_thr_length</td>
+ <td>Transmit Threshold length in 32 bit double words
+ - Values: 8 to 128 (default 64)
+ </td></tr>
+
+ <tr>
+ <td>rx_thr_length</td>
+ <td>Receive Threshold length in 32 bit double words
+ - Values: 8 to 128 (default 64)
+ </td></tr>
+
+<tr>
+ <td>thr_ctl</td>
+ <td>Specifies whether to enable Thresholding for Device mode. Bits 0, 1, 2 of
+ this parmater specifies if thresholding is enabled for non-Iso Tx, Iso Tx and
+ Rx transfers accordingly.
+ The driver will automatically detect the value for this parameter if none is
+ specified.
+ - Values: 0 to 7 (default 0)
+ Bit values indicate:
+ - 0: Thresholding disabled
+ - 1: Thresholding enabled
+ </td></tr>
+
+<tr>
+ <td>dma_desc_enable</td>
+ <td>Specifies whether to enable Descriptor DMA mode.
+ The driver will automatically detect the value for this parameter if none is
+ specified.
+ - 0: Descriptor DMA disabled
+ - 1: Descriptor DMA (default, if available)
+ </td></tr>
+
+<tr>
+ <td>mpi_enable</td>
+ <td>Specifies whether to enable MPI enhancement mode.
+ The driver will automatically detect the value for this parameter if none is
+ specified.
+ - 0: MPI disabled (default)
+ - 1: MPI enable
+ </td></tr>
+
+<tr>
+ <td>pti_enable</td>
+ <td>Specifies whether to enable PTI enhancement support.
+ The driver will automatically detect the value for this parameter if none is
+ specified.
+ - 0: PTI disabled (default)
+ - 1: PTI enable
+ </td></tr>
+
+<tr>
+ <td>lpm_enable</td>
+ <td>Specifies whether to enable LPM support.
+ The driver will automatically detect the value for this parameter if none is
+ specified.
+ - 0: LPM disabled
+ - 1: LPM enable (default, if available)
+ </td></tr>
+
+<tr>
+ <td>ic_usb_cap</td>
+ <td>Specifies whether to enable IC_USB capability.
+ The driver will automatically detect the value for this parameter if none is
+ specified.
+ - 0: IC_USB disabled (default, if available)
+ - 1: IC_USB enable
+ </td></tr>
+
+<tr>
+ <td>ahb_thr_ratio</td>
+ <td>Specifies AHB Threshold ratio.
+ - Values: 0 to 3 (default 0)
+ </td></tr>
+
+<tr>
+ <td>power_down</td>
+ <td>Specifies Power Down(Hibernation) Mode.
+ The driver will automatically detect the value for this parameter if none is
+ specified.
+ - 0: Power Down disabled (default)
+ - 2: Power Down enabled
+ </td></tr>
+
+ <tr>
+ <td>reload_ctl</td>
+ <td>Specifies whether dynamic reloading of the HFIR register is allowed during
+ run time. The driver will automatically detect the value for this parameter if
+ none is specified. In case the HFIR value is reloaded when HFIR.RldCtrl == 1'b0
+ the core might misbehave.
+ - 0: Reload Control disabled (default)
+ - 1: Reload Control enabled
+ </td></tr>
+
+ <tr>
+ <td>dev_out_nak</td>
+ <td>Specifies whether  Device OUT NAK enhancement enabled or no.
+ The driver will automatically detect the value for this parameter if
+ none is specified. This parameter is valid only when OTG_EN_DESC_DMA == 1b1.
+ - 0: The core does not set NAK after Bulk OUT transfer complete (default)
+ - 1: The core sets NAK after Bulk OUT transfer complete
+ </td></tr>
+
+ <tr>
+ <td>cont_on_bna</td>
+ <td>Specifies whether Enable Continue on BNA enabled or no.
+ After receiving BNA interrupt the core disables the endpoint,when the
+ endpoint is re-enabled by the application the
+ - 0: Core starts processing from the DOEPDMA descriptor (default)
+ - 1: Core starts processing from the descriptor which received the BNA.
+ This parameter is valid only when OTG_EN_DESC_DMA == 1b1.
+ </td></tr>
+
+ <tr>
+ <td>ahb_single</td>
+ <td>This bit when programmed supports SINGLE transfers for remainder data
+ in a transfer for DMA mode of operation.
+ - 0: The remainder data will be sent using INCR burst size (default)
+ - 1: The remainder data will be sent using SINGLE burst size.
+ </td></tr>
+
+<tr>
+ <td>adp_enable</td>
+ <td>Specifies whether ADP feature is enabled.
+ The driver will automatically detect the value for this parameter if none is
+ specified.
+ - 0: ADP feature disabled (default)
+ - 1: ADP feature enabled
+ </td></tr>
+
+  <tr>
+ <td>otg_ver</td>
+ <td>Specifies whether OTG is performing as USB OTG Revision 2.0 or Revision 1.3
+ USB OTG device.
+ - 0: OTG 2.0 support disabled (default)
+ - 1: OTG 2.0 support enabled
+ </td></tr>
+
+*/
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_driver.h b/drivers/usb/host/dwc_otg/dwc_otg_driver.h
new file mode 100644
index 000000000000..6a8be63a0ab2
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_driver.h
@@ -0,0 +1,86 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_driver.h $
+ * $Revision: #19 $
+ * $Date: 2010/11/15 $
+ * $Change: 1627671 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+
+#ifndef __DWC_OTG_DRIVER_H__
+#define __DWC_OTG_DRIVER_H__
+
+/** @file
+ * This file contains the interface to the Linux driver.
+ */
+#include "dwc_otg_os_dep.h"
+#include "dwc_otg_core_if.h"
+
+/* Type declarations */
+struct dwc_otg_pcd;
+struct dwc_otg_hcd;
+
+/**
+ * This structure is a wrapper that encapsulates the driver components used to
+ * manage a single DWC_otg controller.
+ */
+typedef struct dwc_otg_device {
+	/** Structure containing OS-dependent stuff. KEEP THIS STRUCT AT THE
+	 * VERY BEGINNING OF THE DEVICE STRUCT. OSes such as FreeBSD and NetBSD
+	 * require this. */
+	struct os_dependent os_dep;
+
+	/** Pointer to the core interface structure. */
+	dwc_otg_core_if_t *core_if;
+
+	/** Pointer to the PCD structure. */
+	struct dwc_otg_pcd *pcd;
+
+	/** Pointer to the HCD structure. */
+	struct dwc_otg_hcd *hcd;
+
+	/** Flag to indicate whether the common IRQ handler is installed. */
+	uint8_t common_irq_installed;
+
+} dwc_otg_device_t;
+
+/*We must clear S3C24XX_EINTPEND external interrupt register
+ * because after clearing in this register trigerred IRQ from
+ * H/W core in kernel interrupt can be occured again before OTG
+ * handlers clear all IRQ sources of Core registers because of
+ * timing latencies and Low Level IRQ Type.
+ */
+#ifdef CONFIG_MACH_IPMATE
+#define  S3C2410X_CLEAR_EINTPEND()   \
+do { \
+	__raw_writel(1UL << 11,S3C24XX_EINTPEND); \
+} while (0)
+#else
+#define  S3C2410X_CLEAR_EINTPEND()   do { } while (0)
+#endif
+
+#endif
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.c b/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.c
new file mode 100644
index 000000000000..98e4563fee02
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.c
@@ -0,0 +1,1370 @@
+/*
+ * dwc_otg_fiq_fsm.c - The finite state machine FIQ
+ *
+ * Copyright (c) 2013 Raspberry Pi Foundation
+ *
+ * Author: Jonathan Bell <jonathan@raspberrypi.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *	* Redistributions of source code must retain the above copyright
+ *	  notice, this list of conditions and the following disclaimer.
+ *	* Redistributions in binary form must reproduce the above copyright
+ *	  notice, this list of conditions and the following disclaimer in the
+ *	  documentation and/or other materials provided with the distribution.
+ *	* Neither the name of Raspberry Pi nor the
+ *	  names of its contributors may be used to endorse or promote products
+ *	  derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This FIQ implements functionality that performs split transactions on
+ * the dwc_otg hardware without any outside intervention. A split transaction
+ * is "queued" by nominating a specific host channel to perform the entirety
+ * of a split transaction. This FIQ will then perform the microframe-precise
+ * scheduling required in each phase of the transaction until completion.
+ *
+ * The FIQ functionality is glued into the Synopsys driver via the entry point
+ * in the FSM enqueue function, and at the exit point in handling a HC interrupt
+ * for a FSM-enabled channel.
+ *
+ * NB: Large parts of this implementation have architecture-specific code.
+ * For porting this functionality to other ARM machines, the minimum is required:
+ * - An interrupt controller allowing the top-level dwc USB interrupt to be routed
+ *   to the FIQ
+ * - A method of forcing a software generated interrupt from FIQ mode that then
+ *   triggers an IRQ entry (with the dwc USB handler called by this IRQ number)
+ * - Guaranteed interrupt routing such that both the FIQ and SGI occur on the same
+ *   processor core - there is no locking between the FIQ and IRQ (aside from
+ *   local_fiq_disable)
+ *
+ */
+
+#include "dwc_otg_fiq_fsm.h"
+
+
+char buffer[1000*16];
+int wptr;
+void notrace _fiq_print(enum fiq_debug_level dbg_lvl, volatile struct fiq_state *state, char *fmt, ...)
+{
+	enum fiq_debug_level dbg_lvl_req = FIQDBG_ERR;
+	va_list args;
+	char text[17];
+	hfnum_data_t hfnum = { .d32 = FIQ_READ(state->dwc_regs_base + 0x408) };
+
+	if((dbg_lvl & dbg_lvl_req) || dbg_lvl == FIQDBG_ERR)
+	{
+		snprintf(text, 9, " %4d:%1u ", hfnum.b.frnum/8, hfnum.b.frnum & 7);
+		va_start(args, fmt);
+		vsnprintf(text+8, 9, fmt, args);
+		va_end(args);
+
+		memcpy(buffer + wptr, text, 16);
+		wptr = (wptr + 16) % sizeof(buffer);
+	}
+}
+
+/**
+ * fiq_fsm_restart_channel() - Poke channel enable bit for a split transaction
+ * @channel: channel to re-enable
+ */
+static void notrace fiq_fsm_restart_channel(struct fiq_state *st, int n, int force)
+{
+	hcchar_data_t hcchar = { .d32 = FIQ_READ(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCCHAR) };
+
+	hcchar.b.chen = 0;
+	if (st->channel[n].hcchar_copy.b.eptype & 0x1) {
+		hfnum_data_t hfnum = { .d32 = FIQ_READ(st->dwc_regs_base + HFNUM) };
+		/* Hardware bug workaround: update the ssplit index */
+		if (st->channel[n].hcsplt_copy.b.spltena)
+			st->channel[n].expected_uframe = (hfnum.b.frnum + 1) & 0x3FFF;
+
+		hcchar.b.oddfrm = (hfnum.b.frnum & 0x1) ? 0	: 1;
+	}
+
+	FIQ_WRITE(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCCHAR, hcchar.d32);
+	hcchar.d32 = FIQ_READ(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCCHAR);
+	hcchar.b.chen = 1;
+
+	FIQ_WRITE(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCCHAR, hcchar.d32);
+	fiq_print(FIQDBG_INT, st, "HCGO %01d %01d", n, force);
+}
+
+/**
+ * fiq_fsm_setup_csplit() - Prepare a host channel for a CSplit transaction stage
+ * @st: Pointer to the channel's state
+ * @n : channel number
+ *
+ * Change host channel registers to perform a complete-split transaction. Being mindful of the
+ * endpoint direction, set control regs up correctly.
+ */
+static void notrace fiq_fsm_setup_csplit(struct fiq_state *st, int n)
+{
+	hcsplt_data_t hcsplt = { .d32 = FIQ_READ(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCSPLT) };
+	hctsiz_data_t hctsiz = { .d32 = FIQ_READ(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCTSIZ) };
+
+	hcsplt.b.compsplt = 1;
+	if (st->channel[n].hcchar_copy.b.epdir == 1) {
+		// If IN, the CSPLIT result contains the data or a hub handshake. hctsiz = maxpacket.
+		hctsiz.b.xfersize = st->channel[n].hctsiz_copy.b.xfersize;
+	} else {
+		// If OUT, the CSPLIT result contains handshake only.
+		hctsiz.b.xfersize = 0;
+	}
+	FIQ_WRITE(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCSPLT, hcsplt.d32);
+	FIQ_WRITE(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCTSIZ, hctsiz.d32);
+	mb();
+}
+
+/**
+ * fiq_fsm_restart_np_pending() - Restart a single non-periodic contended transfer
+ * @st: Pointer to the channel's state
+ * @num_channels: Total number of host channels
+ * @orig_channel: Channel index of completed transfer
+ *
+ * In the case where an IN and OUT transfer are simultaneously scheduled to the
+ * same device/EP, inadequate hub implementations will misbehave. Once the first
+ * transfer is complete, a pending non-periodic split can then be issued.
+ */
+static void notrace fiq_fsm_restart_np_pending(struct fiq_state *st, int num_channels, int orig_channel)
+{
+	int i;
+	int dev_addr = st->channel[orig_channel].hcchar_copy.b.devaddr;
+	int ep_num = st->channel[orig_channel].hcchar_copy.b.epnum;
+	for (i = 0; i < num_channels; i++) {
+		if (st->channel[i].fsm == FIQ_NP_SSPLIT_PENDING &&
+			st->channel[i].hcchar_copy.b.devaddr == dev_addr &&
+			st->channel[i].hcchar_copy.b.epnum == ep_num) {
+			st->channel[i].fsm = FIQ_NP_SSPLIT_STARTED;
+			fiq_fsm_restart_channel(st, i, 0);
+			break;
+		}
+	}
+}
+
+static inline int notrace fiq_get_xfer_len(struct fiq_state *st, int n)
+{
+	/* The xfersize register is a bit wonky. For IN transfers, it decrements by the packet size. */
+	hctsiz_data_t hctsiz = { .d32 = FIQ_READ(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCTSIZ) };
+
+	if (st->channel[n].hcchar_copy.b.epdir == 0) {
+		return st->channel[n].hctsiz_copy.b.xfersize;
+	} else {
+		return st->channel[n].hctsiz_copy.b.xfersize - hctsiz.b.xfersize;
+	}
+
+}
+
+
+/**
+ * fiq_increment_dma_buf() - update DMA address for bounce buffers after a CSPLIT
+ *
+ * Of use only for IN periodic transfers.
+ */
+static int notrace fiq_increment_dma_buf(struct fiq_state *st, int num_channels, int n)
+{
+	hcdma_data_t hcdma;
+	int i = st->channel[n].dma_info.index;
+	int len;
+	struct fiq_dma_channel *split_dma =
+		(struct fiq_dma_channel *)(uintptr_t)st->dma_base;
+
+	len = fiq_get_xfer_len(st, n);
+	fiq_print(FIQDBG_INT, st, "LEN: %03d", len);
+	st->channel[n].dma_info.slot_len[i] = len;
+	i++;
+	if (i > 6)
+		BUG();
+
+	hcdma.d32 = lower_32_bits((uintptr_t)&split_dma[n].index[i].buf[0]);
+	FIQ_WRITE(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HC_DMA, hcdma.d32);
+	st->channel[n].dma_info.index = i;
+	return 0;
+}
+
+/**
+ * fiq_reload_hctsiz() - for IN transactions, reset HCTSIZ
+ */
+static void notrace fiq_fsm_reload_hctsiz(struct fiq_state *st, int n)
+{
+	hctsiz_data_t hctsiz = { .d32 = FIQ_READ(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCTSIZ) };
+	hctsiz.b.xfersize = st->channel[n].hctsiz_copy.b.xfersize;
+	hctsiz.b.pktcnt = 1;
+	FIQ_WRITE(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCTSIZ, hctsiz.d32);
+}
+
+/**
+ * fiq_fsm_reload_hcdma() - for OUT transactions, rewind DMA pointer
+ */
+static void notrace fiq_fsm_reload_hcdma(struct fiq_state *st, int n)
+{
+	hcdma_data_t hcdma = st->channel[n].hcdma_copy;
+	FIQ_WRITE(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HC_DMA, hcdma.d32);
+}
+
+/**
+ * fiq_iso_out_advance() - update DMA address and split position bits
+ * for isochronous OUT transactions.
+ *
+ * Returns 1 if this is the last packet queued, 0 otherwise. Split-ALL and
+ * Split-BEGIN states are not handled - this is done when the transaction was queued.
+ *
+ * This function must only be called from the FIQ_ISO_OUT_ACTIVE state.
+ */
+static int notrace fiq_iso_out_advance(struct fiq_state *st, int num_channels, int n)
+{
+	hcsplt_data_t hcsplt;
+	hctsiz_data_t hctsiz;
+	hcdma_data_t hcdma;
+	struct fiq_dma_channel *split_dma =
+		(struct fiq_dma_channel *)(uintptr_t)st->dma_base;
+	int last = 0;
+	int i = st->channel[n].dma_info.index;
+
+	fiq_print(FIQDBG_INT, st, "ADV %01d %01d ", n, i);
+	i++;
+	if (i == 4)
+		last = 1;
+	if (st->channel[n].dma_info.slot_len[i+1] == 255)
+		last = 1;
+
+	/* New DMA address - address of bounce buffer referred to in index */
+	hcdma.d32 = lower_32_bits((uintptr_t)&split_dma[n].index[i].buf[0]);
+	//hcdma.d32 = FIQ_READ(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HC_DMA);
+	//hcdma.d32 += st->channel[n].dma_info.slot_len[i];
+	fiq_print(FIQDBG_INT, st, "LAST: %01d ", last);
+	fiq_print(FIQDBG_INT, st, "LEN: %03d", st->channel[n].dma_info.slot_len[i]);
+	hcsplt.d32 = FIQ_READ(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCSPLT);
+	hctsiz.d32 = FIQ_READ(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCTSIZ);
+	hcsplt.b.xactpos = (last) ? ISOC_XACTPOS_END : ISOC_XACTPOS_MID;
+	/* Set up new packet length */
+	hctsiz.b.pktcnt = 1;
+	hctsiz.b.xfersize = st->channel[n].dma_info.slot_len[i];
+	fiq_print(FIQDBG_INT, st, "%08x", hctsiz.d32);
+
+	st->channel[n].dma_info.index++;
+	FIQ_WRITE(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCSPLT, hcsplt.d32);
+	FIQ_WRITE(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCTSIZ, hctsiz.d32);
+	FIQ_WRITE(st->dwc_regs_base + HC_START + (HC_OFFSET * n) + HC_DMA, hcdma.d32);
+	return last;
+}
+
+/**
+ * fiq_fsm_tt_next_isoc() - queue next pending isochronous out start-split on a TT
+ *
+ * Despite the limitations of the DWC core, we can force a microframe pipeline of
+ * isochronous OUT start-split transactions while waiting for a corresponding other-type
+ * of endpoint to finish its CSPLITs. TTs have big periodic buffers therefore it
+ * is very unlikely that filling the start-split FIFO will cause data loss.
+ * This allows much better interleaving of transactions in an order-independent way-
+ * there is no requirement to prioritise isochronous, just a state-space search has
+ * to be performed on each periodic start-split complete interrupt.
+ */
+static int notrace fiq_fsm_tt_next_isoc(struct fiq_state *st, int num_channels, int n)
+{
+	int hub_addr = st->channel[n].hub_addr;
+	int port_addr = st->channel[n].port_addr;
+	int i, poked = 0;
+	for (i = 0; i < num_channels; i++) {
+		if (i == n || st->channel[i].fsm == FIQ_PASSTHROUGH)
+			continue;
+		if (st->channel[i].hub_addr == hub_addr &&
+			st->channel[i].port_addr == port_addr) {
+			switch (st->channel[i].fsm) {
+			case FIQ_PER_ISO_OUT_PENDING:
+				if (st->channel[i].nrpackets == 1) {
+					st->channel[i].fsm = FIQ_PER_ISO_OUT_LAST;
+				} else {
+					st->channel[i].fsm = FIQ_PER_ISO_OUT_ACTIVE;
+				}
+				fiq_fsm_restart_channel(st, i, 0);
+				poked = 1;
+				break;
+
+			default:
+				break;
+			}
+		}
+		if (poked)
+			break;
+	}
+	return poked;
+}
+
+/**
+ * fiq_fsm_tt_in_use() - search for host channels using this TT
+ * @n: Channel to use as reference
+ *
+ */
+int notrace noinline fiq_fsm_tt_in_use(struct fiq_state *st, int num_channels, int n)
+{
+	int hub_addr = st->channel[n].hub_addr;
+	int port_addr = st->channel[n].port_addr;
+	int i, in_use = 0;
+	for (i = 0; i < num_channels; i++) {
+		if (i == n || st->channel[i].fsm == FIQ_PASSTHROUGH)
+			continue;
+		switch (st->channel[i].fsm) {
+		/* TT is reserved for channels that are in the middle of a periodic
+		 * split transaction.
+		 */
+		case FIQ_PER_SSPLIT_STARTED:
+		case FIQ_PER_CSPLIT_WAIT:
+		case FIQ_PER_CSPLIT_NYET1:
+		//case FIQ_PER_CSPLIT_POLL:
+		case FIQ_PER_ISO_OUT_ACTIVE:
+		case FIQ_PER_ISO_OUT_LAST:
+			if (st->channel[i].hub_addr == hub_addr &&
+				st->channel[i].port_addr == port_addr) {
+				in_use = 1;
+			}
+			break;
+		default:
+			break;
+		}
+		if (in_use)
+			break;
+	}
+	return in_use;
+}
+
+/**
+ * fiq_fsm_more_csplits() - determine whether additional CSPLITs need
+ * 			to be issued for this IN transaction.
+ *
+ * We cannot tell the inbound PID of a data packet due to hardware limitations.
+ * we need to make an educated guess as to whether we need to queue another CSPLIT
+ * or not. A no-brainer is when we have received enough data to fill the endpoint
+ * size, but for endpoints that give variable-length data then we have to resort
+ * to heuristics.
+ *
+ * We also return whether this is the last CSPLIT to be queued, again based on
+ * heuristics. This is to allow a 1-uframe overlap of periodic split transactions.
+ * Note: requires at least 1 CSPLIT to have been performed prior to being called.
+ */
+
+/*
+ * We need some way of guaranteeing if a returned periodic packet of size X
+ * has a DATA0 PID.
+ * The heuristic value of 144 bytes assumes that the received data has maximal
+ * bit-stuffing and the clock frequency of the transmitting device is at the lowest
+ * permissible limit. If the transfer length results in a final packet size
+ * 144 < p <= 188, then an erroneous CSPLIT will be issued.
+ * Also used to ensure that an endpoint will nominally only return a single
+ * complete-split worth of data.
+ */
+#define DATA0_PID_HEURISTIC 144
+
+static int notrace noinline fiq_fsm_more_csplits(struct fiq_state *state, int n, int *probably_last)
+{
+
+	int i;
+	int total_len = 0;
+	int more_needed = 1;
+	struct fiq_channel_state *st = &state->channel[n];
+
+	for (i = 0; i < st->dma_info.index; i++) {
+			total_len += st->dma_info.slot_len[i];
+	}
+
+	*probably_last = 0;
+
+	if (st->hcchar_copy.b.eptype == 0x3) {
+		/*
+		 * An interrupt endpoint will take max 2 CSPLITs. if we are receiving data
+		 * then this is definitely the last CSPLIT.
+		 */
+		*probably_last = 1;
+	} else {
+		/* Isoc IN. This is a bit risky if we are the first transaction:
+		 * we may have been held off slightly. */
+		if (i > 1 && st->dma_info.slot_len[st->dma_info.index-1] <= DATA0_PID_HEURISTIC) {
+			more_needed = 0;
+		}
+		/* If in the next uframe we will receive enough data to fill the endpoint,
+		 * then only issue 1 more csplit.
+		 */
+		if (st->hctsiz_copy.b.xfersize - total_len <= DATA0_PID_HEURISTIC)
+			*probably_last = 1;
+	}
+
+	if (total_len >= st->hctsiz_copy.b.xfersize ||
+		i == 6 || total_len == 0)
+		/* Note: due to bit stuffing it is possible to have > 6 CSPLITs for
+		 * a single endpoint. Accepting more would completely break our scheduling mechanism though
+		 * - in these extreme cases we will pass through a truncated packet.
+		 */
+		more_needed = 0;
+
+	return more_needed;
+}
+
+/**
+ * fiq_fsm_too_late() - Test transaction for lateness
+ *
+ * If a SSPLIT for a large IN transaction is issued too late in a frame,
+ * the hub will disable the port to the device and respond with ERR handshakes.
+ * The hub status endpoint will not reflect this change.
+ * Returns 1 if we will issue a SSPLIT that will result in a device babble.
+ */
+int notrace fiq_fsm_too_late(struct fiq_state *st, int n)
+{
+	int uframe;
+	hfnum_data_t hfnum = { .d32 = FIQ_READ(st->dwc_regs_base + HFNUM) };
+	uframe = hfnum.b.frnum & 0x7;
+	if ((uframe < 6) && (st->channel[n].nrpackets + 1 + uframe > 7)) {
+		return 1;
+	} else {
+		return 0;
+	}
+}
+
+
+/**
+ * fiq_fsm_start_next_periodic() - A half-arsed attempt at a microframe pipeline
+ *
+ * Search pending transactions in the start-split pending state and queue them.
+ * Don't queue packets in uframe .5 (comes out in .6) (USB2.0 11.18.4).
+ * Note: we specifically don't do isochronous OUT transactions first because better
+ * use of the TT's start-split fifo can be achieved by pipelining an IN before an OUT.
+ */
+static void notrace noinline fiq_fsm_start_next_periodic(struct fiq_state *st, int num_channels)
+{
+	int n;
+	hfnum_data_t hfnum = { .d32 = FIQ_READ(st->dwc_regs_base + HFNUM) };
+	if ((hfnum.b.frnum & 0x7) == 5)
+		return;
+	for (n = 0; n < num_channels; n++) {
+		if (st->channel[n].fsm == FIQ_PER_SSPLIT_QUEUED) {
+			/* Check to see if any other transactions are using this TT */
+			if(!fiq_fsm_tt_in_use(st, num_channels, n)) {
+				if (!fiq_fsm_too_late(st, n)) {
+					st->channel[n].fsm = FIQ_PER_SSPLIT_STARTED;
+					fiq_print(FIQDBG_INT, st, "NEXTPER ");
+					fiq_fsm_restart_channel(st, n, 0);
+				} else {
+					st->channel[n].fsm = FIQ_PER_SPLIT_TIMEOUT;
+				}
+				break;
+			}
+		}
+	}
+	for (n = 0; n < num_channels; n++) {
+		if (st->channel[n].fsm == FIQ_PER_ISO_OUT_PENDING) {
+			if (!fiq_fsm_tt_in_use(st, num_channels, n)) {
+				fiq_print(FIQDBG_INT, st, "NEXTISO ");
+				if (st->channel[n].nrpackets == 1)
+					st->channel[n].fsm = FIQ_PER_ISO_OUT_LAST;
+				else
+					st->channel[n].fsm = FIQ_PER_ISO_OUT_ACTIVE;
+				fiq_fsm_restart_channel(st, n, 0);
+				break;
+			}
+		}
+	}
+}
+
+/**
+ * fiq_fsm_update_hs_isoc() - update isochronous frame and transfer data
+ * @state:	Pointer to fiq_state
+ * @n:		Channel transaction is active on
+ * @hcint:	Copy of host channel interrupt register
+ *
+ * Returns 0 if there are no more transactions for this HC to do, 1
+ * otherwise.
+ */
+static int notrace noinline fiq_fsm_update_hs_isoc(struct fiq_state *state, int n, hcint_data_t hcint)
+{
+	struct fiq_channel_state *st = &state->channel[n];
+	int xfer_len = 0, nrpackets = 0;
+	hcdma_data_t hcdma;
+	fiq_print(FIQDBG_INT, state, "HSISO %02d", n);
+
+	xfer_len = fiq_get_xfer_len(state, n);
+	st->hs_isoc_info.iso_desc[st->hs_isoc_info.index].actual_length = xfer_len;
+
+	st->hs_isoc_info.iso_desc[st->hs_isoc_info.index].status = hcint.d32;
+
+	st->hs_isoc_info.index++;
+	if (st->hs_isoc_info.index == st->hs_isoc_info.nrframes) {
+		return 0;
+	}
+
+	/* grab the next DMA address offset from the array */
+	hcdma.d32 = st->hcdma_copy.d32 + st->hs_isoc_info.iso_desc[st->hs_isoc_info.index].offset;
+	FIQ_WRITE(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HC_DMA, hcdma.d32);
+
+	/* We need to set multi_count. This is a bit tricky - has to be set per-transaction as
+	 * the core needs to be told to send the correct number. Caution: for IN transfers,
+	 * this is always set to the maximum size of the endpoint. */
+	xfer_len = st->hs_isoc_info.iso_desc[st->hs_isoc_info.index].length;
+	/* Integer divide in a FIQ: fun. FIXME: make this not suck */
+	nrpackets = (xfer_len + st->hcchar_copy.b.mps - 1) / st->hcchar_copy.b.mps;
+	if (nrpackets == 0)
+		nrpackets = 1;
+	st->hcchar_copy.b.multicnt = nrpackets;
+	st->hctsiz_copy.b.pktcnt = nrpackets;
+
+	/* Initial PID also needs to be set */
+	if (st->hcchar_copy.b.epdir == 0) {
+		st->hctsiz_copy.b.xfersize = xfer_len;
+		switch (st->hcchar_copy.b.multicnt) {
+		case 1:
+			st->hctsiz_copy.b.pid = DWC_PID_DATA0;
+			break;
+		case 2:
+		case 3:
+			st->hctsiz_copy.b.pid = DWC_PID_MDATA;
+			break;
+		}
+
+	} else {
+		st->hctsiz_copy.b.xfersize = nrpackets * st->hcchar_copy.b.mps;
+		switch (st->hcchar_copy.b.multicnt) {
+		case 1:
+			st->hctsiz_copy.b.pid = DWC_PID_DATA0;
+			break;
+		case 2:
+			st->hctsiz_copy.b.pid = DWC_PID_DATA1;
+			break;
+		case 3:
+			st->hctsiz_copy.b.pid = DWC_PID_DATA2;
+			break;
+		}
+	}
+	FIQ_WRITE(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCTSIZ, st->hctsiz_copy.d32);
+	FIQ_WRITE(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCCHAR, st->hcchar_copy.d32);
+	/* Channel is enabled on hcint handler exit */
+	fiq_print(FIQDBG_INT, state, "HSISOOUT");
+	return 1;
+}
+
+
+/**
+ * fiq_fsm_do_sof() - FSM start-of-frame interrupt handler
+ * @state:	Pointer to the state struct passed from banked FIQ mode registers.
+ * @num_channels:	set according to the DWC hardware configuration
+ *
+ * The SOF handler in FSM mode has two functions
+ * 1. Hold off SOF from causing schedule advancement in IRQ context if there's
+ *    nothing to do
+ * 2. Advance certain FSM states that require either a microframe delay, or a microframe
+ *    of holdoff.
+ *
+ * The second part is architecture-specific to mach-bcm2835 -
+ * a sane interrupt controller would have a mask register for ARM interrupt sources
+ * to be promoted to the nFIQ line, but it doesn't. Instead a single interrupt
+ * number (USB) can be enabled. This means that certain parts of the USB specification
+ * that require "wait a little while, then issue another packet" cannot be fulfilled with
+ * the timing granularity required to achieve optimal throughout. The workaround is to use
+ * the SOF "timer" (125uS) to perform this task.
+ */
+static int notrace noinline fiq_fsm_do_sof(struct fiq_state *state, int num_channels)
+{
+	hfnum_data_t hfnum = { .d32 = FIQ_READ(state->dwc_regs_base + HFNUM) };
+	int n;
+	int kick_irq = 0;
+
+	if ((hfnum.b.frnum & 0x7) == 1) {
+		/* We cannot issue csplits for transactions in the last frame past (n+1).1
+		 * Check to see if there are any transactions that are stale.
+		 * Boot them out.
+		 */
+		for (n = 0; n < num_channels; n++) {
+			switch (state->channel[n].fsm) {
+			case FIQ_PER_CSPLIT_WAIT:
+			case FIQ_PER_CSPLIT_NYET1:
+			case FIQ_PER_CSPLIT_POLL:
+			case FIQ_PER_CSPLIT_LAST:
+				/* Check if we are no longer in the same full-speed frame. */
+				if (((state->channel[n].expected_uframe & 0x3FFF) & ~0x7) <
+						(hfnum.b.frnum & ~0x7))
+					state->channel[n].fsm = FIQ_PER_SPLIT_TIMEOUT;
+				break;
+			default:
+				break;
+			}
+		}
+	}
+
+	for (n = 0; n < num_channels; n++) {
+		switch (state->channel[n].fsm) {
+
+		case FIQ_NP_SSPLIT_RETRY:
+		case FIQ_NP_IN_CSPLIT_RETRY:
+		case FIQ_NP_OUT_CSPLIT_RETRY:
+			fiq_fsm_restart_channel(state, n, 0);
+			break;
+
+		case FIQ_HS_ISOC_SLEEPING:
+			/* Is it time to wake this channel yet? */
+			if (--state->channel[n].uframe_sleeps == 0) {
+				state->channel[n].fsm = FIQ_HS_ISOC_TURBO;
+				fiq_fsm_restart_channel(state, n, 0);
+			}
+			break;
+
+		case FIQ_PER_SSPLIT_QUEUED:
+			if ((hfnum.b.frnum & 0x7) == 5)
+				break;
+			if(!fiq_fsm_tt_in_use(state, num_channels, n)) {
+				if (!fiq_fsm_too_late(state, n)) {
+					fiq_print(FIQDBG_INT, state, "SOF GO %01d", n);
+					fiq_fsm_restart_channel(state, n, 0);
+					state->channel[n].fsm = FIQ_PER_SSPLIT_STARTED;
+				} else {
+					/* Transaction cannot be started without risking a device babble error */
+					state->channel[n].fsm = FIQ_PER_SPLIT_TIMEOUT;
+					state->haintmsk_saved.b2.chint &= ~(1 << n);
+					FIQ_WRITE(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCINTMSK, 0);
+					kick_irq |= 1;
+				}
+			}
+			break;
+
+		case FIQ_PER_ISO_OUT_PENDING:
+			/* Ordinarily, this should be poked after the SSPLIT
+			 * complete interrupt for a competing transfer on the same
+			 * TT. Doesn't happen for aborted transactions though.
+			 */
+			if ((hfnum.b.frnum & 0x7) >= 5)
+				break;
+			if (!fiq_fsm_tt_in_use(state, num_channels, n)) {
+				/* Hardware bug. SOF can sometimes occur after the channel halt interrupt
+				 * that caused this.
+				 */
+					fiq_fsm_restart_channel(state, n, 0);
+					fiq_print(FIQDBG_INT, state, "SOF ISOC");
+					if (state->channel[n].nrpackets == 1) {
+						state->channel[n].fsm = FIQ_PER_ISO_OUT_LAST;
+					} else {
+						state->channel[n].fsm = FIQ_PER_ISO_OUT_ACTIVE;
+					}
+			}
+			break;
+
+		case FIQ_PER_CSPLIT_WAIT:
+			/* we are guaranteed to be in this state if and only if the SSPLIT interrupt
+			 * occurred when the bus transaction occurred. The SOF interrupt reversal bug
+			 * will utterly bugger this up though.
+			 */
+			if (hfnum.b.frnum != state->channel[n].expected_uframe) {
+				fiq_print(FIQDBG_INT, state, "SOFCS %d ", n);
+				state->channel[n].fsm = FIQ_PER_CSPLIT_POLL;
+				fiq_fsm_restart_channel(state, n, 0);
+				fiq_fsm_start_next_periodic(state, num_channels);
+
+			}
+			break;
+
+		case FIQ_PER_SPLIT_TIMEOUT:
+		case FIQ_DEQUEUE_ISSUED:
+			/* Ugly: we have to force a HCD interrupt.
+			 * Poke the mask for the channel in question.
+			 * We will take a fake SOF because of this, but
+			 * that's OK.
+			 */
+			state->haintmsk_saved.b2.chint &= ~(1 << n);
+			FIQ_WRITE(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCINTMSK, 0);
+			kick_irq |= 1;
+			break;
+
+		default:
+			break;
+		}
+	}
+
+	if (state->kick_np_queues ||
+			dwc_frame_num_le(state->next_sched_frame, hfnum.b.frnum))
+		kick_irq |= 1;
+
+	return !kick_irq;
+}
+
+
+/**
+ * fiq_fsm_do_hcintr() - FSM host channel interrupt handler
+ * @state: Pointer to the FIQ state struct
+ * @num_channels: Number of channels as per hardware config
+ * @n: channel for which HAINT(i) was raised
+ *
+ * An important property is that only the CHHLT interrupt is unmasked. Unfortunately, AHBerr is as well.
+ */
+static int notrace noinline fiq_fsm_do_hcintr(struct fiq_state *state, int num_channels, int n)
+{
+	hcint_data_t hcint;
+	hcintmsk_data_t hcintmsk;
+	hcint_data_t hcint_probe;
+	hcchar_data_t hcchar;
+	int handled = 0;
+	int restart = 0;
+	int last_csplit = 0;
+	int start_next_periodic = 0;
+	struct fiq_channel_state *st = &state->channel[n];
+	hfnum_data_t hfnum;
+
+	hcint.d32 = FIQ_READ(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCINT);
+	hcintmsk.d32 = FIQ_READ(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCINTMSK);
+	hcint_probe.d32 = hcint.d32 & hcintmsk.d32;
+
+	if (st->fsm != FIQ_PASSTHROUGH) {
+		fiq_print(FIQDBG_INT, state, "HC%01d ST%02d", n, st->fsm);
+		fiq_print(FIQDBG_INT, state, "%08x", hcint.d32);
+	}
+
+	switch (st->fsm) {
+
+	case FIQ_PASSTHROUGH:
+	case FIQ_DEQUEUE_ISSUED:
+		/* doesn't belong to us, kick it upstairs */
+		break;
+
+	case FIQ_PASSTHROUGH_ERRORSTATE:
+		/* We are here to emulate the error recovery mechanism of the dwc HCD.
+		 * Several interrupts are unmasked if a previous transaction failed - it's
+		 * death for the FIQ to attempt to handle them as the channel isn't halted.
+		 * Emulate what the HCD does in this situation: mask and continue.
+		 * The FSM has no other state setup so this has to be handled out-of-band.
+		 */
+		fiq_print(FIQDBG_ERR, state, "ERRST %02d", n);
+		if (hcint_probe.b.nak || hcint_probe.b.ack || hcint_probe.b.datatglerr) {
+			fiq_print(FIQDBG_ERR, state, "RESET %02d", n);
+			/* In some random cases we can get a NAK interrupt coincident with a Xacterr
+			 * interrupt, after the device has disappeared.
+			 */
+			if (!hcint.b.xacterr)
+				st->nr_errors = 0;
+			hcintmsk.b.nak = 0;
+			hcintmsk.b.ack = 0;
+			hcintmsk.b.datatglerr = 0;
+			FIQ_WRITE(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCINTMSK, hcintmsk.d32);
+			return 1;
+		}
+		if (hcint_probe.b.chhltd) {
+			fiq_print(FIQDBG_ERR, state, "CHHLT %02d", n);
+			fiq_print(FIQDBG_ERR, state, "%08x", hcint.d32);
+			return 0;
+		}
+		break;
+
+	/* Non-periodic state groups */
+	case FIQ_NP_SSPLIT_STARTED:
+	case FIQ_NP_SSPLIT_RETRY:
+		/* Got a HCINT for a NP SSPLIT. Expected ACK / NAK / fail */
+		if (hcint.b.ack) {
+			/* SSPLIT complete. For OUT, the data has been sent. For IN, the LS transaction
+			 * will start shortly. SOF needs to kick the transaction to prevent a NYET flood.
+			 */
+			if(st->hcchar_copy.b.epdir == 1)
+				st->fsm = FIQ_NP_IN_CSPLIT_RETRY;
+			else
+				st->fsm = FIQ_NP_OUT_CSPLIT_RETRY;
+			st->nr_errors = 0;
+			handled = 1;
+			fiq_fsm_setup_csplit(state, n);
+		} else if (hcint.b.nak) {
+			// No buffer space in TT. Retry on a uframe boundary.
+			fiq_fsm_reload_hcdma(state, n);
+			st->fsm = FIQ_NP_SSPLIT_RETRY;
+			handled = 1;
+		} else if (hcint.b.xacterr) {
+			// The only other one we care about is xacterr. This implies HS bus error - retry.
+			st->nr_errors++;
+			if(st->hcchar_copy.b.epdir == 0)
+				fiq_fsm_reload_hcdma(state, n);
+			st->fsm = FIQ_NP_SSPLIT_RETRY;
+			if (st->nr_errors >= 3) {
+				st->fsm = FIQ_NP_SPLIT_HS_ABORTED;
+			} else {
+				handled = 1;
+				restart = 1;
+			}
+		} else {
+			st->fsm = FIQ_NP_SPLIT_LS_ABORTED;
+			handled = 0;
+			restart = 0;
+		}
+		break;
+
+	case FIQ_NP_IN_CSPLIT_RETRY:
+		/* Received a CSPLIT done interrupt.
+		 * Expected Data/NAK/STALL/NYET for IN.
+		 */
+		if (hcint.b.xfercomp) {
+			/* For IN, data is present. */
+			st->fsm = FIQ_NP_SPLIT_DONE;
+		} else if (hcint.b.nak) {
+			/* no endpoint data. Punt it upstairs */
+			st->fsm = FIQ_NP_SPLIT_DONE;
+		} else if (hcint.b.nyet) {
+			/* CSPLIT NYET - retry on a uframe boundary. */
+			handled = 1;
+			st->nr_errors = 0;
+		} else if (hcint.b.datatglerr) {
+			/* data toggle errors do not set the xfercomp bit. */
+			st->fsm = FIQ_NP_SPLIT_LS_ABORTED;
+		} else if (hcint.b.xacterr) {
+			/* HS error. Retry immediate */
+			st->fsm = FIQ_NP_IN_CSPLIT_RETRY;
+			st->nr_errors++;
+			if (st->nr_errors >= 3) {
+				st->fsm = FIQ_NP_SPLIT_HS_ABORTED;
+			} else {
+				handled = 1;
+				restart = 1;
+			}
+		} else if (hcint.b.stall || hcint.b.bblerr) {
+			/* A STALL implies either a LS bus error or a genuine STALL. */
+			st->fsm = FIQ_NP_SPLIT_LS_ABORTED;
+		} else {
+			/*  Hardware bug. It's possible in some cases to
+			 *  get a channel halt with nothing else set when
+			 *  the response was a NYET. Treat as local 3-strikes retry.
+			 */
+			hcint_data_t hcint_test = hcint;
+			hcint_test.b.chhltd = 0;
+			if (!hcint_test.d32) {
+				st->nr_errors++;
+				if (st->nr_errors >= 3) {
+					st->fsm = FIQ_NP_SPLIT_HS_ABORTED;
+				} else {
+					handled = 1;
+				}
+			} else {
+				/* Bail out if something unexpected happened */
+				st->fsm = FIQ_NP_SPLIT_HS_ABORTED;
+			}
+		}
+		if (st->fsm != FIQ_NP_IN_CSPLIT_RETRY) {
+			fiq_fsm_restart_np_pending(state, num_channels, n);
+		}
+		break;
+
+	case FIQ_NP_OUT_CSPLIT_RETRY:
+		/* Received a CSPLIT done interrupt.
+		 * Expected ACK/NAK/STALL/NYET/XFERCOMP for OUT.*/
+		if (hcint.b.xfercomp) {
+			st->fsm = FIQ_NP_SPLIT_DONE;
+		} else if (hcint.b.nak) {
+			// The HCD will implement the holdoff on frame boundaries.
+			st->fsm = FIQ_NP_SPLIT_DONE;
+		} else if (hcint.b.nyet) {
+			// Hub still processing.
+			st->fsm = FIQ_NP_OUT_CSPLIT_RETRY;
+			handled = 1;
+			st->nr_errors = 0;
+			//restart = 1;
+		} else if (hcint.b.xacterr) {
+			/* HS error. retry immediate */
+			st->fsm = FIQ_NP_OUT_CSPLIT_RETRY;
+			st->nr_errors++;
+			if (st->nr_errors >= 3) {
+				st->fsm = FIQ_NP_SPLIT_HS_ABORTED;
+			} else {
+				handled = 1;
+				restart = 1;
+			}
+		} else if (hcint.b.stall) {
+			/* LS bus error or genuine stall */
+			st->fsm = FIQ_NP_SPLIT_LS_ABORTED;
+		} else {
+			/*
+			 * Hardware bug. It's possible in some cases to get a
+			 * channel halt with nothing else set when the response was a NYET.
+			 * Treat as local 3-strikes retry.
+			 */
+			hcint_data_t hcint_test = hcint;
+			hcint_test.b.chhltd = 0;
+			if (!hcint_test.d32) {
+				st->nr_errors++;
+				if (st->nr_errors >= 3) {
+					st->fsm = FIQ_NP_SPLIT_HS_ABORTED;
+				} else {
+					handled = 1;
+				}
+			} else {
+				// Something unexpected happened. AHBerror or babble perhaps. Let the IRQ deal with it.
+				st->fsm = FIQ_NP_SPLIT_HS_ABORTED;
+			}
+		}
+		if (st->fsm != FIQ_NP_OUT_CSPLIT_RETRY) {
+			fiq_fsm_restart_np_pending(state, num_channels, n);
+		}
+		break;
+
+	/* Periodic split states (except isoc out) */
+	case FIQ_PER_SSPLIT_STARTED:
+		/* Expect an ACK or failure for SSPLIT */
+		if (hcint.b.ack) {
+			/*
+			 * SSPLIT transfer complete interrupt - the generation of this interrupt is fraught with bugs.
+			 * For a packet queued in microframe n-3 to appear in n-2, if the channel is enabled near the EOF1
+			 * point for microframe n-3, the packet will not appear on the bus until microframe n.
+			 * Additionally, the generation of the actual interrupt is dodgy. For a packet appearing on the bus
+			 * in microframe n, sometimes the interrupt is generated immediately. Sometimes, it appears in n+1
+			 * coincident with SOF for n+1.
+			 * SOF is also buggy. It can sometimes be raised AFTER the first bus transaction has taken place.
+			 * These appear to be caused by timing/clock crossing bugs within the core itself.
+			 * State machine workaround.
+			 */
+			hfnum.d32 = FIQ_READ(state->dwc_regs_base + HFNUM);
+			hcchar.d32 = FIQ_READ(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCCHAR);
+			fiq_fsm_setup_csplit(state, n);
+			/* Poke the oddfrm bit. If we are equivalent, we received the interrupt at the correct
+			 * time. If not, then we're in the next SOF.
+			 */
+			if ((hfnum.b.frnum & 0x1) == hcchar.b.oddfrm) {
+				fiq_print(FIQDBG_INT, state, "CSWAIT %01d", n);
+				st->expected_uframe = hfnum.b.frnum;
+				st->fsm = FIQ_PER_CSPLIT_WAIT;
+			} else {
+				fiq_print(FIQDBG_INT, state, "CSPOL  %01d", n);
+				/* For isochronous IN endpoints,
+				 * we need to hold off if we are expecting a lot of data */
+				if (st->hcchar_copy.b.mps < DATA0_PID_HEURISTIC) {
+					start_next_periodic = 1;
+				}
+				/* Danger will robinson: we are in a broken state. If our first interrupt after
+				 * this is a NYET, it will be delayed by 1 uframe and result in an unrecoverable
+				 * lag. Unmask the NYET interrupt.
+				 */
+				st->expected_uframe = (hfnum.b.frnum + 1) & 0x3FFF;
+				st->fsm = FIQ_PER_CSPLIT_BROKEN_NYET1;
+				restart = 1;
+			}
+			handled = 1;
+		} else if (hcint.b.xacterr) {
+			/* 3-strikes retry is enabled, we have hit our max nr_errors */
+			st->fsm = FIQ_PER_SPLIT_HS_ABORTED;
+			start_next_periodic = 1;
+		} else {
+			st->fsm = FIQ_PER_SPLIT_HS_ABORTED;
+			start_next_periodic = 1;
+		}
+		/* We can now queue the next isochronous OUT transaction, if one is pending. */
+		if(fiq_fsm_tt_next_isoc(state, num_channels, n)) {
+			fiq_print(FIQDBG_INT, state, "NEXTISO ");
+		}
+		break;
+
+	case FIQ_PER_CSPLIT_NYET1:
+		/* First CSPLIT attempt was a NYET. If we get a subsequent NYET,
+		 * we are too late and the TT has dropped its CSPLIT fifo.
+		 */
+		hfnum.d32 = FIQ_READ(state->dwc_regs_base + HFNUM);
+		hcchar.d32 = FIQ_READ(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCCHAR);
+		start_next_periodic = 1;
+		if (hcint.b.nak) {
+			st->fsm = FIQ_PER_SPLIT_DONE;
+		} else if (hcint.b.xfercomp) {
+			fiq_increment_dma_buf(state, num_channels, n);
+			st->fsm = FIQ_PER_CSPLIT_POLL;
+			st->nr_errors = 0;
+			if (fiq_fsm_more_csplits(state, n, &last_csplit)) {
+				handled = 1;
+				restart = 1;
+				if (!last_csplit)
+					start_next_periodic = 0;
+			} else {
+				st->fsm = FIQ_PER_SPLIT_DONE;
+			}
+		} else if (hcint.b.nyet) {
+			/* Doh. Data lost. */
+			st->fsm = FIQ_PER_SPLIT_NYET_ABORTED;
+		} else if (hcint.b.xacterr || hcint.b.stall || hcint.b.bblerr) {
+			st->fsm = FIQ_PER_SPLIT_LS_ABORTED;
+		} else {
+			st->fsm = FIQ_PER_SPLIT_HS_ABORTED;
+		}
+		break;
+
+	case FIQ_PER_CSPLIT_BROKEN_NYET1:
+		/*
+		 * we got here because our host channel is in the delayed-interrupt
+		 * state and we cannot take a NYET interrupt any later than when it
+		 * occurred. Disable then re-enable the channel if this happens to force
+		 * CSPLITs to occur at the right time.
+		 */
+		hfnum.d32 = FIQ_READ(state->dwc_regs_base + HFNUM);
+		hcchar.d32 = FIQ_READ(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCCHAR);
+		fiq_print(FIQDBG_INT, state, "BROK: %01d ", n);
+		if (hcint.b.nak) {
+			st->fsm = FIQ_PER_SPLIT_DONE;
+			start_next_periodic = 1;
+		} else if (hcint.b.xfercomp) {
+			fiq_increment_dma_buf(state, num_channels, n);
+			if (fiq_fsm_more_csplits(state, n, &last_csplit)) {
+				st->fsm = FIQ_PER_CSPLIT_POLL;
+				handled = 1;
+				restart = 1;
+				start_next_periodic = 1;
+				/* Reload HCTSIZ for the next transfer */
+				fiq_fsm_reload_hctsiz(state, n);
+				if (!last_csplit)
+					start_next_periodic = 0;
+			} else {
+				st->fsm = FIQ_PER_SPLIT_DONE;
+			}
+		} else if (hcint.b.nyet) {
+			st->fsm = FIQ_PER_SPLIT_NYET_ABORTED;
+			start_next_periodic = 1;
+		} else if (hcint.b.xacterr || hcint.b.stall || hcint.b.bblerr) {
+			/* Local 3-strikes retry is handled by the core. This is a ERR response.*/
+			st->fsm = FIQ_PER_SPLIT_LS_ABORTED;
+		} else {
+			st->fsm = FIQ_PER_SPLIT_HS_ABORTED;
+		}
+		break;
+
+	case FIQ_PER_CSPLIT_POLL:
+		hfnum.d32 = FIQ_READ(state->dwc_regs_base + HFNUM);
+		hcchar.d32 = FIQ_READ(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCCHAR);
+		start_next_periodic = 1;
+		if (hcint.b.nak) {
+			st->fsm = FIQ_PER_SPLIT_DONE;
+		} else if (hcint.b.xfercomp) {
+			fiq_increment_dma_buf(state, num_channels, n);
+			if (fiq_fsm_more_csplits(state, n, &last_csplit)) {
+				handled = 1;
+				restart = 1;
+				/* Reload HCTSIZ for the next transfer */
+				fiq_fsm_reload_hctsiz(state, n);
+				if (!last_csplit)
+					start_next_periodic = 0;
+			} else {
+				st->fsm = FIQ_PER_SPLIT_DONE;
+			}
+		} else if (hcint.b.nyet) {
+			/* Are we a NYET after the first data packet? */
+			if (st->nrpackets == 0) {
+				st->fsm = FIQ_PER_CSPLIT_NYET1;
+				handled = 1;
+				restart = 1;
+			} else {
+				/* We got a NYET when polling CSPLITs. Can happen
+				 * if our heuristic fails, or if someone disables us
+				 * for any significant length of time.
+				 */
+				if (st->nr_errors >= 3) {
+					st->fsm = FIQ_PER_SPLIT_NYET_ABORTED;
+				} else {
+					st->fsm = FIQ_PER_SPLIT_DONE;
+				}
+			}
+		} else if (hcint.b.xacterr || hcint.b.stall || hcint.b.bblerr) {
+			/* For xacterr, Local 3-strikes retry is handled by the core. This is a ERR response.*/
+			st->fsm = FIQ_PER_SPLIT_LS_ABORTED;
+		} else {
+			st->fsm = FIQ_PER_SPLIT_HS_ABORTED;
+		}
+		break;
+
+	case FIQ_HS_ISOC_TURBO:
+		if (fiq_fsm_update_hs_isoc(state, n, hcint)) {
+			/* more transactions to come */
+			handled = 1;
+			fiq_print(FIQDBG_INT, state, "HSISO M ");
+			/* For strided transfers, put ourselves to sleep */
+			if (st->hs_isoc_info.stride > 1) {
+				st->uframe_sleeps = st->hs_isoc_info.stride - 1;
+				st->fsm = FIQ_HS_ISOC_SLEEPING;
+			} else {
+				restart = 1;
+			}
+		} else {
+			st->fsm = FIQ_HS_ISOC_DONE;
+			fiq_print(FIQDBG_INT, state, "HSISO F ");
+		}
+		break;
+
+	case FIQ_HS_ISOC_ABORTED:
+		/* This abort is called by the driver rewriting the state mid-transaction
+		 * which allows the dequeue mechanism to work more effectively.
+		 */
+		break;
+
+	case FIQ_PER_ISO_OUT_ACTIVE:
+		if (hcint.b.ack) {
+			if(fiq_iso_out_advance(state, num_channels, n)) {
+				/* last OUT transfer */
+				st->fsm = FIQ_PER_ISO_OUT_LAST;
+				/*
+				 * Assuming the periodic FIFO in the dwc core
+				 * actually does its job properly, we can queue
+				 * the next ssplit now and in theory, the wire
+				 * transactions will be in-order.
+				 */
+				// No it doesn't. It appears to process requests in host channel order.
+				//start_next_periodic = 1;
+			}
+			handled = 1;
+			restart = 1;
+		} else {
+			/*
+			 * Isochronous transactions carry on regardless. Log the error
+			 * and continue.
+			 */
+			//explode += 1;
+			st->nr_errors++;
+			if(fiq_iso_out_advance(state, num_channels, n)) {
+				st->fsm = FIQ_PER_ISO_OUT_LAST;
+				//start_next_periodic = 1;
+			}
+			handled = 1;
+			restart = 1;
+		}
+		break;
+
+	case FIQ_PER_ISO_OUT_LAST:
+		if (hcint.b.ack) {
+			/* All done here */
+			st->fsm = FIQ_PER_ISO_OUT_DONE;
+		} else {
+			st->fsm = FIQ_PER_ISO_OUT_DONE;
+			st->nr_errors++;
+		}
+		start_next_periodic = 1;
+		break;
+
+	case FIQ_PER_SPLIT_TIMEOUT:
+		/* SOF kicked us because we overran. */
+		start_next_periodic = 1;
+		break;
+
+	default:
+		break;
+	}
+
+	if (handled) {
+		FIQ_WRITE(state->dwc_regs_base + HC_START + (HC_OFFSET * n) + HCINT, hcint.d32);
+	} else {
+		/* Copy the regs into the state so the IRQ knows what to do */
+		st->hcint_copy.d32 = hcint.d32;
+	}
+
+	if (restart) {
+		/* Restart always implies handled. */
+		if (restart == 2) {
+			/* For complete-split INs, the show must go on.
+			 * Force a channel restart */
+			fiq_fsm_restart_channel(state, n, 1);
+		} else {
+			fiq_fsm_restart_channel(state, n, 0);
+		}
+	}
+	if (start_next_periodic) {
+		fiq_fsm_start_next_periodic(state, num_channels);
+	}
+	if (st->fsm != FIQ_PASSTHROUGH) {
+		fiq_print(FIQDBG_INT, state, "FSMOUT%02d", st->fsm);
+	}
+
+	return handled;
+}
+
+
+/**
+ * dwc_otg_fiq_fsm() - Flying State Machine (monster) FIQ
+ * @state:		pointer to state struct passed from the banked FIQ mode registers.
+ * @num_channels:	set according to the DWC hardware configuration
+ * @dma:		pointer to DMA bounce buffers for split transaction slots
+ *
+ * The FSM FIQ performs the low-level tasks that normally would be performed by the microcode
+ * inside an EHCI or similar host controller regarding split transactions. The DWC core
+ * interrupts each and every time a split transaction packet is received or sent successfully.
+ * This results in either an interrupt storm when everything is working "properly", or
+ * the interrupt latency of the system in general breaks time-sensitive periodic split
+ * transactions. Pushing the low-level, but relatively easy state machine work into the FIQ
+ * solves these problems.
+ *
+ * Return: void
+ */
+void notrace dwc_otg_fiq_fsm(struct fiq_state *state, int num_channels)
+{
+	gintsts_data_t gintsts, gintsts_handled;
+	gintmsk_data_t gintmsk;
+	//hfnum_data_t hfnum;
+	haint_data_t haint, haint_handled;
+	haintmsk_data_t haintmsk;
+	int kick_irq = 0;
+
+	/* Ensure peripheral reads issued prior to FIQ entry are complete */
+	dsb(sy);
+
+	gintsts_handled.d32 = 0;
+	haint_handled.d32 = 0;
+
+	fiq_fsm_spin_lock(&state->lock);
+	gintsts.d32 = FIQ_READ(state->dwc_regs_base + GINTSTS);
+	gintmsk.d32 = FIQ_READ(state->dwc_regs_base + GINTMSK);
+	gintsts.d32 &= gintmsk.d32;
+
+	if (gintsts.b.sofintr) {
+		/* For FSM mode, SOF is required to keep the state machine advance for
+		 * certain stages of the periodic pipeline. It's death to mask this
+		 * interrupt in that case.
+		 */
+
+		if (!fiq_fsm_do_sof(state, num_channels)) {
+			/* Kick IRQ once. Queue advancement means that all pending transactions
+			 * will get serviced when the IRQ finally executes.
+			 */
+			if (state->gintmsk_saved.b.sofintr == 1)
+				kick_irq |= 1;
+			state->gintmsk_saved.b.sofintr = 0;
+		}
+		gintsts_handled.b.sofintr = 1;
+	}
+
+	if (gintsts.b.hcintr) {
+		int i;
+		haint.d32 = FIQ_READ(state->dwc_regs_base + HAINT);
+		haintmsk.d32 = FIQ_READ(state->dwc_regs_base + HAINTMSK);
+		haint.d32 &= haintmsk.d32;
+		haint_handled.d32 = 0;
+		for (i=0; i<num_channels; i++) {
+			if (haint.b2.chint & (1 << i)) {
+				if(!fiq_fsm_do_hcintr(state, num_channels, i)) {
+					/* HCINT was not handled in FIQ
+					 * HAINT is level-sensitive, leading to level-sensitive ginststs.b.hcint bit.
+					 * Mask HAINT(i) but keep top-level hcint unmasked.
+					 */
+					state->haintmsk_saved.b2.chint &= ~(1 << i);
+				} else {
+					/* do_hcintr cleaned up after itself, but clear haint */
+					haint_handled.b2.chint |= (1 << i);
+				}
+			}
+		}
+
+		if (haint_handled.b2.chint) {
+			FIQ_WRITE(state->dwc_regs_base + HAINT, haint_handled.d32);
+		}
+
+		if (haintmsk.d32 != (haintmsk.d32 & state->haintmsk_saved.d32)) {
+			/*
+			 * This is necessary to avoid multiple retriggers of the MPHI in the case
+			 * where interrupts are held off and HCINTs start to pile up.
+			 * Only wake up the IRQ if a new interrupt came in, was not handled and was
+			 * masked.
+			 */
+			haintmsk.d32 &= state->haintmsk_saved.d32;
+			FIQ_WRITE(state->dwc_regs_base + HAINTMSK, haintmsk.d32);
+			kick_irq |= 1;
+		}
+		/* Top-Level interrupt - always handled because it's level-sensitive */
+		gintsts_handled.b.hcintr = 1;
+	}
+
+
+	/* Clear the bits in the saved register that were not handled but were triggered. */
+	state->gintmsk_saved.d32 &= ~(gintsts.d32 & ~gintsts_handled.d32);
+
+	/* FIQ didn't handle something - mask has changed - write new mask */
+	if (gintmsk.d32 != (gintmsk.d32 & state->gintmsk_saved.d32)) {
+		gintmsk.d32 &= state->gintmsk_saved.d32;
+		gintmsk.b.sofintr = 1;
+		FIQ_WRITE(state->dwc_regs_base + GINTMSK, gintmsk.d32);
+//		fiq_print(FIQDBG_INT, state, "KICKGINT");
+//		fiq_print(FIQDBG_INT, state, "%08x", gintmsk.d32);
+//		fiq_print(FIQDBG_INT, state, "%08x", state->gintmsk_saved.d32);
+		kick_irq |= 1;
+	}
+
+	if (gintsts_handled.d32) {
+		/* Only applies to edge-sensitive bits in GINTSTS */
+		FIQ_WRITE(state->dwc_regs_base + GINTSTS, gintsts_handled.d32);
+	}
+
+	/* We got an interrupt, didn't handle it. */
+	if (kick_irq) {
+		state->mphi_int_count++;
+		if (state->mphi_regs.swirq_set) {
+			FIQ_WRITE(state->mphi_regs.swirq_set, 1);
+		} else {
+			FIQ_WRITE(state->mphi_regs.outdda, state->dummy_send_dma);
+			FIQ_WRITE(state->mphi_regs.outddb, (1<<29));
+		}
+
+	}
+	state->fiq_done++;
+	mb();
+	fiq_fsm_spin_unlock(&state->lock);
+}
+
+
+/**
+ * dwc_otg_fiq_nop() - FIQ "lite"
+ * @state:	pointer to state struct passed from the banked FIQ mode registers.
+ *
+ * The "nop" handler does not intervene on any interrupts other than SOF.
+ * It is limited in scope to deciding at each SOF if the IRQ SOF handler (which deals
+ * with non-periodic/periodic queues) needs to be kicked.
+ *
+ * This is done to hold off the SOF interrupt, which occurs at a rate of 8000 per second.
+ *
+ * Return: void
+ */
+void notrace dwc_otg_fiq_nop(struct fiq_state *state)
+{
+	gintsts_data_t gintsts, gintsts_handled;
+	gintmsk_data_t gintmsk;
+	hfnum_data_t hfnum;
+
+	/* Ensure peripheral reads issued prior to FIQ entry are complete */
+	dsb(sy);
+
+	fiq_fsm_spin_lock(&state->lock);
+	hfnum.d32 = FIQ_READ(state->dwc_regs_base + HFNUM);
+	gintsts.d32 = FIQ_READ(state->dwc_regs_base + GINTSTS);
+	gintmsk.d32 = FIQ_READ(state->dwc_regs_base + GINTMSK);
+	gintsts.d32 &= gintmsk.d32;
+	gintsts_handled.d32 = 0;
+
+	if (gintsts.b.sofintr) {
+		if (!state->kick_np_queues &&
+				dwc_frame_num_gt(state->next_sched_frame, hfnum.b.frnum)) {
+			/* SOF handled, no work to do, just ACK interrupt */
+			gintsts_handled.b.sofintr = 1;
+		} else {
+			/* Kick IRQ */
+			state->gintmsk_saved.b.sofintr = 0;
+		}
+	}
+
+	/* Reset handled interrupts */
+	if(gintsts_handled.d32) {
+		FIQ_WRITE(state->dwc_regs_base + GINTSTS, gintsts_handled.d32);
+	}
+
+	/* Clear the bits in the saved register that were not handled but were triggered. */
+	state->gintmsk_saved.d32 &= ~(gintsts.d32 & ~gintsts_handled.d32);
+
+	/* We got an interrupt, didn't handle it and want to mask it */
+	if (~(state->gintmsk_saved.d32)) {
+		state->mphi_int_count++;
+		gintmsk.d32 &= state->gintmsk_saved.d32;
+		FIQ_WRITE(state->dwc_regs_base + GINTMSK, gintmsk.d32);
+		if (state->mphi_regs.swirq_set) {
+			FIQ_WRITE(state->mphi_regs.swirq_set, 1);
+		} else {
+			/* Force a clear before another dummy send */
+			FIQ_WRITE(state->mphi_regs.intstat, (1<<29));
+			FIQ_WRITE(state->mphi_regs.outdda, state->dummy_send_dma);
+			FIQ_WRITE(state->mphi_regs.outddb, (1<<29));
+		}
+	}
+	state->fiq_done++;
+	mb();
+	fiq_fsm_spin_unlock(&state->lock);
+}
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.h b/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.h
new file mode 100644
index 000000000000..1399d579eb58
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_fiq_fsm.h
@@ -0,0 +1,476 @@
+/*
+ * dwc_otg_fiq_fsm.h - Finite state machine FIQ header definitions
+ *
+ * Copyright (c) 2013 Raspberry Pi Foundation
+ *
+ * Author: Jonathan Bell <jonathan@raspberrypi.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *	* Redistributions of source code must retain the above copyright
+ *	  notice, this list of conditions and the following disclaimer.
+ *	* Redistributions in binary form must reproduce the above copyright
+ *	  notice, this list of conditions and the following disclaimer in the
+ *	  documentation and/or other materials provided with the distribution.
+ *	* Neither the name of Raspberry Pi nor the
+ *	  names of its contributors may be used to endorse or promote products
+ *	  derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This FIQ implements functionality that performs split transactions on
+ * the dwc_otg hardware without any outside intervention. A split transaction
+ * is "queued" by nominating a specific host channel to perform the entirety
+ * of a split transaction. This FIQ will then perform the microframe-precise
+ * scheduling required in each phase of the transaction until completion.
+ *
+ * The FIQ functionality has been surgically implanted into the Synopsys
+ * vendor-provided driver.
+ *
+ */
+
+#ifndef DWC_OTG_FIQ_FSM_H_
+#define DWC_OTG_FIQ_FSM_H_
+
+#include "dwc_otg_regs.h"
+#include "dwc_otg_cil.h"
+#include "dwc_otg_hcd.h"
+#include <linux/kernel.h>
+#include <linux/irqflags.h>
+#include <linux/string.h>
+#include <asm/barrier.h>
+
+#if 0
+#define FLAME_ON(x)					\
+do {							\
+	int gpioreg;                                    \
+							\
+	gpioreg = readl(__io_address(0x20200000+0x8));	\
+	gpioreg &= ~(7 << (x-20)*3);			\
+	gpioreg |= 0x1 << (x-20)*3;			\
+	writel(gpioreg, __io_address(0x20200000+0x8));	\
+							\
+	writel(1<<x, __io_address(0x20200000+(0x1C)));	\
+} while (0)
+
+#define FLAME_OFF(x)					\
+do {							\
+	writel(1<<x, __io_address(0x20200000+(0x28)));	\
+} while (0)
+#else
+#define FLAME_ON(x) do { } while (0)
+#define FLAME_OFF(X) do { } while (0)
+#endif
+
+/* This is a quick-and-dirty arch-specific register read/write. We know that
+ * writes to a peripheral on BCM2835 will always arrive in-order, also that
+ * reads and writes are executed in-order therefore the need for memory barriers
+ * is obviated if we're only talking to USB.
+ */
+#define FIQ_WRITE(_addr_,_data_) (*(volatile unsigned int *) (_addr_) = (_data_))
+#define FIQ_READ(_addr_) (*(volatile unsigned int *) (_addr_))
+
+/* FIQ-ified register definitions. Offsets are from dwc_regs_base. */
+#define GINTSTS		0x014
+#define GINTMSK		0x018
+/* Debug register. Poll the top of the received packets FIFO. */
+#define GRXSTSR		0x01C
+#define HFNUM		0x408
+#define HAINT		0x414
+#define HAINTMSK	0x418
+#define HPRT0		0x440
+
+/* HC_regs start from an offset of 0x500 */
+#define HC_START	0x500
+#define HC_OFFSET	0x020
+
+#define HC_DMA		0x14
+
+#define HCCHAR		0x00
+#define HCSPLT		0x04
+#define HCINT		0x08
+#define HCINTMSK	0x0C
+#define HCTSIZ		0x10
+
+#define ISOC_XACTPOS_ALL 	0b11
+#define ISOC_XACTPOS_BEGIN	0b10
+#define ISOC_XACTPOS_MID	0b00
+#define ISOC_XACTPOS_END	0b01
+
+#define DWC_PID_DATA2	0b01
+#define DWC_PID_MDATA	0b11
+#define DWC_PID_DATA1	0b10
+#define DWC_PID_DATA0	0b00
+
+typedef struct {
+	volatile void* base;
+	volatile void* ctrl;
+	volatile void* outdda;
+	volatile void* outddb;
+	volatile void* intstat;
+	volatile void* swirq_set;
+	volatile void* swirq_clr;
+} mphi_regs_t;
+
+enum fiq_debug_level {
+	FIQDBG_SCHED = (1 << 0),
+	FIQDBG_INT   = (1 << 1),
+	FIQDBG_ERR   = (1 << 2),
+	FIQDBG_PORTHUB = (1 << 3),
+};
+
+#define TICKET_SHIFT 16
+typedef struct {
+	union {
+		uint32_t slock;
+		struct _tickets {
+			uint16_t owner;
+			uint16_t next;
+		} tickets;
+	};
+} __aligned(4) fiq_lock_t;
+
+#if defined(CONFIG_ARM64)
+static inline void fiq_fsm_spin_lock(fiq_lock_t *lock)
+{
+	unsigned int tmp;
+	fiq_lock_t lockval, newval;
+
+	asm volatile(
+	/* Atomically increment the next ticket. */
+	"	prfm	pstl1strm, %3\n"
+	"1:	ldaxr	%w0, %3\n"
+	"	add	%w1, %w0, %w5\n"
+	"	stxr	%w2, %w1, %3\n"
+	"	cbnz	%w2, 1b\n"
+	/* Did we get the lock? */
+	"	eor	%w1, %w0, %w0, ror #16\n"
+	"	cbz	%w1, 3f\n"
+	/*
+	 * No: spin on the owner. Send a local event to avoid missing an
+	 * unlock before the exclusive load.
+	 */
+	"	sevl\n"
+	"2:	wfe\n"
+	"	ldaxrh	%w2, %4\n"
+	"	eor	%w1, %w2, %w0, lsr #16\n"
+	"	cbnz	%w1, 2b\n"
+	/* We got the lock. Critical section starts here. */
+	"3:"
+	: "=&r" (lockval), "=&r" (newval), "=&r" (tmp), "+Q" (*lock)
+	: "Q" (lock->tickets.owner), "I" (1 << TICKET_SHIFT)
+	: "memory");
+}
+
+static inline void fiq_fsm_spin_unlock(fiq_lock_t *lock)
+{
+	asm volatile(
+	"	stlrh	%w1, %0\n"
+	: "=Q" (lock->tickets.owner)
+	: "r" (lock->tickets.owner + 1)
+	: "memory");
+}
+
+#else
+
+/**
+ * fiq_fsm_spin_lock() - ARMv6+ bare bones spinlock
+ * Must be called with local interrupts and FIQ disabled.
+ */
+#if defined(CONFIG_ARCH_BCM2835) && defined(CONFIG_SMP)
+static inline void fiq_fsm_spin_lock(fiq_lock_t *lock)
+{
+	unsigned long tmp;
+	uint32_t newval;
+	fiq_lock_t lockval;
+	/* Nested locking, yay. If we are on the same CPU as the fiq, then the disable
+	 * will be sufficient. If we are on a different CPU, then the lock protects us. */
+	prefetchw(&lock->slock);
+	asm volatile (
+	"1:     ldrex   %0, [%3]\n"
+	"       add     %1, %0, %4\n"
+	"       strex   %2, %1, [%3]\n"
+	"       teq     %2, #0\n"
+	"       bne     1b"
+	: "=&r" (lockval), "=&r" (newval), "=&r" (tmp)
+	: "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
+	: "cc");
+
+	while (lockval.tickets.next != lockval.tickets.owner) {
+		wfe();
+		lockval.tickets.owner = READ_ONCE(lock->tickets.owner);
+	}
+	smp_mb();
+}
+#else
+static inline void fiq_fsm_spin_lock(fiq_lock_t *lock) { }
+#endif
+
+/**
+ * fiq_fsm_spin_unlock() - ARMv6+ bare bones spinunlock
+ */
+#if defined(CONFIG_ARCH_BCM2835) && defined(CONFIG_SMP)
+static inline void fiq_fsm_spin_unlock(fiq_lock_t *lock)
+{
+	smp_mb();
+	lock->tickets.owner++;
+	dsb_sev();
+}
+#else
+static inline void fiq_fsm_spin_unlock(fiq_lock_t *lock) { }
+#endif
+
+#endif
+
+struct fiq_state;
+
+extern void _fiq_print (enum fiq_debug_level dbg_lvl, volatile struct fiq_state *state, char *fmt, ...);
+#if 0
+#define fiq_print _fiq_print
+#else
+#define fiq_print(x, y, ...)
+#endif
+
+extern bool fiq_enable, fiq_fsm_enable;
+extern ushort nak_holdoff;
+
+/**
+ * enum fiq_fsm_state - The FIQ FSM states.
+ *
+ * This is the "core" of the FIQ FSM. Broadly, the FSM states follow the
+ * USB2.0 specification for host responses to various transaction states.
+ * There are modifications to this host state machine because of a variety of
+ * quirks and limitations in the dwc_otg hardware.
+ *
+ * The fsm state is also used to communicate back to the driver on completion of
+ * a split transaction. The end states are used in conjunction with the interrupts
+ * raised by the final transaction.
+ */
+enum fiq_fsm_state {
+	/* FIQ isn't enabled for this host channel */
+	FIQ_PASSTHROUGH = 0,
+	/* For the first interrupt received for this channel,
+	 * the FIQ has to ack any interrupts indicating success. */
+	FIQ_PASSTHROUGH_ERRORSTATE = 31,
+	/* Nonperiodic state groups */
+	FIQ_NP_SSPLIT_STARTED = 1,
+	FIQ_NP_SSPLIT_RETRY = 2,
+	/* TT contention - working around hub bugs */
+	FIQ_NP_SSPLIT_PENDING = 33,
+	FIQ_NP_OUT_CSPLIT_RETRY = 3,
+	FIQ_NP_IN_CSPLIT_RETRY = 4,
+	FIQ_NP_SPLIT_DONE = 5,
+	FIQ_NP_SPLIT_LS_ABORTED = 6,
+	/* This differentiates a HS transaction error from a LS one
+	 * (handling the hub state is different) */
+	FIQ_NP_SPLIT_HS_ABORTED = 7,
+
+	/* Periodic state groups */
+	/* Periodic transactions are either started directly by the IRQ handler
+	 * or deferred if the TT is already in use.
+	 */
+	FIQ_PER_SSPLIT_QUEUED = 8,
+	FIQ_PER_SSPLIT_STARTED = 9,
+	FIQ_PER_SSPLIT_LAST = 10,
+
+
+	FIQ_PER_ISO_OUT_PENDING = 11,
+	FIQ_PER_ISO_OUT_ACTIVE = 12,
+	FIQ_PER_ISO_OUT_LAST = 13,
+	FIQ_PER_ISO_OUT_DONE = 27,
+
+	FIQ_PER_CSPLIT_WAIT = 14,
+	FIQ_PER_CSPLIT_NYET1 = 15,
+	FIQ_PER_CSPLIT_BROKEN_NYET1 = 28,
+	FIQ_PER_CSPLIT_NYET_FAFF = 29,
+	/* For multiple CSPLITs (large isoc IN, or delayed interrupt) */
+	FIQ_PER_CSPLIT_POLL = 16,
+	/* The last CSPLIT for a transaction has been issued, differentiates
+	 * for the state machine to queue the next packet.
+	 */
+	FIQ_PER_CSPLIT_LAST = 17,
+
+	FIQ_PER_SPLIT_DONE = 18,
+	FIQ_PER_SPLIT_LS_ABORTED = 19,
+	FIQ_PER_SPLIT_HS_ABORTED = 20,
+	FIQ_PER_SPLIT_NYET_ABORTED = 21,
+	/* Frame rollover has occurred without the transaction finishing. */
+	FIQ_PER_SPLIT_TIMEOUT = 22,
+
+	/* FIQ-accelerated HS Isochronous state groups */
+	FIQ_HS_ISOC_TURBO = 23,
+	/* For interval > 1, SOF wakes up the isochronous FSM */
+	FIQ_HS_ISOC_SLEEPING = 24,
+	FIQ_HS_ISOC_DONE = 25,
+	FIQ_HS_ISOC_ABORTED = 26,
+	FIQ_DEQUEUE_ISSUED = 30,
+	FIQ_TEST = 32,
+};
+
+struct fiq_stack {
+	int magic1;
+	uint8_t stack[2048];
+	int magic2;
+};
+
+
+/**
+ * struct fiq_dma_info - DMA bounce buffer utilisation information (per-channel)
+ * @index:	Number of slots reported used for IN transactions / number of slots
+ *			transmitted for an OUT transaction
+ * @slot_len[6]: Number of actual transfer bytes in each slot (255 if unused)
+ *
+ * Split transaction transfers can have variable length depending on other bus
+ * traffic. The OTG core DMA engine requires 4-byte aligned addresses therefore
+ * each transaction needs a guaranteed aligned address. A maximum of 6 split transfers
+ * can happen per-frame.
+ */
+struct fiq_dma_info {
+	u8 index;
+	u8 slot_len[6];
+};
+
+struct fiq_split_dma_slot {
+	u8 buf[188];
+} __attribute__((packed));
+
+struct fiq_dma_channel {
+	struct fiq_split_dma_slot index[6];
+} __attribute__((packed));
+
+/**
+ * struct fiq_hs_isoc_info - USB2.0 isochronous data
+ * @iso_frame:	Pointer to the array of OTG URB iso_frame_descs.
+ * @nrframes:	Total length of iso_frame_desc array
+ * @index:	Current index (FIQ-maintained)
+ * @stride:	Interval in uframes between HS isoc transactions
+ */
+struct fiq_hs_isoc_info {
+	struct dwc_otg_hcd_iso_packet_desc *iso_desc;
+	unsigned int nrframes;
+	unsigned int index;
+	unsigned int stride;
+};
+
+/**
+ * struct fiq_channel_state - FIQ state machine storage
+ * @fsm:	Current state of the channel as understood by the FIQ
+ * @nr_errors:	Number of transaction errors on this split-transaction
+ * @hub_addr:   SSPLIT/CSPLIT destination hub
+ * @port_addr:  SSPLIT/CSPLIT destination port - always 1 if single TT hub
+ * @nrpackets:  For isoc OUT, the number of split-OUT packets to transmit. For
+ * 		split-IN, number of CSPLIT data packets that were received.
+ * @hcchar_copy:
+ * @hcsplt_copy:
+ * @hcintmsk_copy:
+ * @hctsiz_copy:	Copies of the host channel registers.
+ * 			For use as scratch, or for returning state.
+ *
+ * The fiq_channel_state is state storage between interrupts for a host channel. The
+ * FSM state is stored here. Members of this structure must only be set up by the
+ * driver prior to enabling the FIQ for this host channel, and not touched until the FIQ
+ * has updated the state to either a COMPLETE state group or ABORT state group.
+ */
+
+struct fiq_channel_state {
+	enum fiq_fsm_state fsm;
+	unsigned int nr_errors;
+	unsigned int hub_addr;
+	unsigned int port_addr;
+	/* Hardware bug workaround: sometimes channel halt interrupts are
+	 * delayed until the next SOF. Keep track of when we expected to get interrupted. */
+	unsigned int expected_uframe;
+	/* number of uframes remaining (for interval > 1 HS isoc transfers) before next transfer */
+	unsigned int uframe_sleeps;
+	/* in/out for communicating number of dma buffers used, or number of ISOC to do */
+	unsigned int nrpackets;
+	struct fiq_dma_info dma_info;
+	struct fiq_hs_isoc_info hs_isoc_info;
+	/* Copies of HC registers - in/out communication from/to IRQ handler
+	 * and for ease of channel setup. A bit of mungeing is performed - for
+	 * example the hctsiz.b.maxp is _always_ the max packet size of the endpoint.
+	 */
+	hcchar_data_t hcchar_copy;
+	hcsplt_data_t hcsplt_copy;
+	hcint_data_t hcint_copy;
+	hcintmsk_data_t hcintmsk_copy;
+	hctsiz_data_t hctsiz_copy;
+	hcdma_data_t hcdma_copy;
+};
+
+/**
+ * struct fiq_state - top-level FIQ state machine storage
+ * @mphi_regs:		virtual address of the MPHI peripheral register file
+ * @dwc_regs_base:	virtual address of the base of the DWC core register file
+ * @dma_base:		physical address for the base of the DMA bounce buffers
+ * @dummy_send:		Scratch area for sending a fake message to the MPHI peripheral
+ * @gintmsk_saved:	Top-level mask of interrupts that the FIQ has not handled.
+ * 			Used for determining which interrupts fired to set off the IRQ handler.
+ * @haintmsk_saved:	Mask of interrupts from host channels that the FIQ did not handle internally.
+ * @np_count:		Non-periodic transactions in the active queue
+ * @np_sent:		Count of non-periodic transactions that have completed
+ * @next_sched_frame:	For periodic transactions handled by the driver's SOF-driven queuing mechanism,
+ * 			this is the next frame on which a SOF interrupt is required. Used to hold off
+ * 			passing SOF through to the driver until necessary.
+ * @channel[n]:		Per-channel FIQ state. Allocated during init depending on the number of host
+ * 			channels configured into the core logic.
+ *
+ * This is passed as the first argument to the dwc_otg_fiq_fsm top-level FIQ handler from the asm stub.
+ * It contains top-level state information.
+ */
+struct fiq_state {
+	fiq_lock_t lock;
+	mphi_regs_t mphi_regs;
+	void *dwc_regs_base;
+	dma_addr_t dma_base;
+	struct fiq_dma_channel *fiq_dmab;
+	void *dummy_send;
+	dma_addr_t dummy_send_dma;
+	gintmsk_data_t gintmsk_saved;
+	haintmsk_data_t haintmsk_saved;
+	int mphi_int_count;
+	unsigned int fiq_done;
+	unsigned int kick_np_queues;
+	unsigned int next_sched_frame;
+#ifdef FIQ_DEBUG
+	char * buffer;
+	unsigned int bufsiz;
+#endif
+	struct fiq_channel_state channel[];
+};
+
+#ifdef CONFIG_ARM64
+
+#ifdef local_fiq_enable
+#undef local_fiq_enable
+#endif
+
+#ifdef local_fiq_disable
+#undef local_fiq_disable
+#endif
+
+extern void local_fiq_enable(void);
+
+extern void local_fiq_disable(void);
+
+#endif
+
+extern int fiq_fsm_too_late(struct fiq_state *st, int n);
+
+extern int fiq_fsm_tt_in_use(struct fiq_state *st, int num_channels, int n);
+
+extern void dwc_otg_fiq_fsm(struct fiq_state *state, int num_channels);
+
+extern void dwc_otg_fiq_nop(struct fiq_state *state);
+
+#endif /* DWC_OTG_FIQ_FSM_H_ */
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_fiq_stub.S b/drivers/usb/host/dwc_otg/dwc_otg_fiq_stub.S
new file mode 100644
index 000000000000..ffa8d21bc61e
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_fiq_stub.S
@@ -0,0 +1,80 @@
+/*
+ * dwc_otg_fiq_fsm.S - assembly stub for the FSM FIQ
+ *
+ * Copyright (c) 2013 Raspberry Pi Foundation
+ *
+ * Author: Jonathan Bell <jonathan@raspberrypi.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *	* Redistributions of source code must retain the above copyright
+ *	  notice, this list of conditions and the following disclaimer.
+ *	* Redistributions in binary form must reproduce the above copyright
+ *	  notice, this list of conditions and the following disclaimer in the
+ *	  documentation and/or other materials provided with the distribution.
+ *	* Neither the name of Raspberry Pi nor the
+ *	  names of its contributors may be used to endorse or promote products
+ *	  derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include <asm/assembler.h>
+#include <linux/linkage.h>
+
+
+.text
+
+.global _dwc_otg_fiq_stub_end;
+
+/**
+  * _dwc_otg_fiq_stub() - entry copied to the FIQ vector page to allow
+  * a C-style function call with arguments from the FIQ banked registers.
+  * r0 = &hcd->fiq_state
+  * r1 = &hcd->num_channels
+  * r2 = &hcd->dma_buffers
+  * Tramples: r0, r1, r2, r4, fp, ip
+  */
+
+ENTRY(_dwc_otg_fiq_stub)
+	/* Stash unbanked regs - SP will have been set up for us */
+	mov ip, sp;
+	stmdb sp!, {r0-r12, lr};
+#ifdef FIQ_DEBUG
+	// Cycle profiling - read cycle counter at start
+	mrc p15, 0, r5, c15, c12, 1;
+#endif
+	/* r11 = fp, don't trample it */
+	mov r4, fp;
+	/* set EABI frame size */
+	sub fp, ip, #512;
+
+	/* for fiq NOP mode - just need state */
+	mov r0, r8;
+	/* r9 = num_channels */
+	mov r1, r9;
+	/* r10 = struct *dma_bufs */
+//	mov r2, r10;
+
+	/* r4 = &fiq_c_function */
+	blx r4;
+#ifdef FIQ_DEBUG
+	mrc p15, 0, r4, c15, c12, 1;
+	subs r5, r5, r4;
+	// r5 is now the cycle count time for executing the FIQ. Store it somewhere?
+#endif
+	ldmia sp!, {r0-r12, lr};
+	subs pc, lr, #4;
+_dwc_otg_fiq_stub_end:
+END(_dwc_otg_fiq_stub)
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd.c
new file mode 100644
index 000000000000..9e05e075172b
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd.c
@@ -0,0 +1,4362 @@
+
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd.c $
+ * $Revision: #104 $
+ * $Date: 2011/10/24 $
+ * $Change: 1871159 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+#ifndef DWC_DEVICE_ONLY
+
+/** @file
+ * This file implements HCD Core. All code in this file is portable and doesn't
+ * use any OS specific functions.
+ * Interface provided by HCD Core is defined in <code><hcd_if.h></code>
+ * header file.
+ */
+
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+
+#include "dwc_otg_hcd.h"
+#include "dwc_otg_regs.h"
+#include "dwc_otg_fiq_fsm.h"
+
+extern bool microframe_schedule;
+extern uint16_t fiq_fsm_mask, nak_holdoff;
+
+//#define DEBUG_HOST_CHANNELS
+#ifdef DEBUG_HOST_CHANNELS
+static int last_sel_trans_num_per_scheduled = 0;
+static int last_sel_trans_num_nonper_scheduled = 0;
+static int last_sel_trans_num_avail_hc_at_start = 0;
+static int last_sel_trans_num_avail_hc_at_end = 0;
+#endif /* DEBUG_HOST_CHANNELS */
+
+static_assert(FIQ_PASSTHROUGH == 0);
+
+dwc_otg_hcd_t *dwc_otg_hcd_alloc_hcd(void)
+{
+	return DWC_ALLOC(sizeof(dwc_otg_hcd_t));
+}
+
+/**
+ * Connection timeout function.  An OTG host is required to display a
+ * message if the device does not connect within 10 seconds.
+ */
+static void dwc_otg_hcd_connect_timeout(void *ptr)
+{
+	DWC_DEBUGPL(DBG_HCDV, "%s(%p)\n", __func__, ptr);
+	DWC_PRINTF("Connect Timeout\n");
+	__DWC_ERROR("Device Not Connected/Responding\n");
+}
+
+#if defined(DEBUG)
+static void dump_channel_info(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh)
+{
+	if (qh->channel != NULL) {
+		dwc_hc_t *hc = qh->channel;
+		dwc_list_link_t *item;
+		dwc_otg_qh_t *qh_item;
+		int num_channels = hcd->core_if->core_params->host_channels;
+		int i;
+
+		dwc_otg_hc_regs_t *hc_regs;
+		hcchar_data_t hcchar;
+		hcsplt_data_t hcsplt;
+		hctsiz_data_t hctsiz;
+		uint32_t hcdma;
+
+		hc_regs = hcd->core_if->host_if->hc_regs[hc->hc_num];
+		hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+		hcsplt.d32 = DWC_READ_REG32(&hc_regs->hcsplt);
+		hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz);
+		hcdma = DWC_READ_REG32(&hc_regs->hcdma);
+
+		DWC_PRINTF("  Assigned to channel %p:\n", hc);
+		DWC_PRINTF("    hcchar 0x%08x, hcsplt 0x%08x\n", hcchar.d32,
+			   hcsplt.d32);
+		DWC_PRINTF("    hctsiz 0x%08x, hcdma 0x%08x\n", hctsiz.d32,
+			   hcdma);
+		DWC_PRINTF("    dev_addr: %d, ep_num: %d, ep_is_in: %d\n",
+			   hc->dev_addr, hc->ep_num, hc->ep_is_in);
+		DWC_PRINTF("    ep_type: %d\n", hc->ep_type);
+		DWC_PRINTF("    max_packet: %d\n", hc->max_packet);
+		DWC_PRINTF("    data_pid_start: %d\n", hc->data_pid_start);
+		DWC_PRINTF("    xfer_started: %d\n", hc->xfer_started);
+		DWC_PRINTF("    halt_status: %d\n", hc->halt_status);
+		DWC_PRINTF("    xfer_buff: %p\n", hc->xfer_buff);
+		DWC_PRINTF("    xfer_len: %d\n", hc->xfer_len);
+		DWC_PRINTF("    qh: %p\n", hc->qh);
+		DWC_PRINTF("  NP inactive sched:\n");
+		DWC_LIST_FOREACH(item, &hcd->non_periodic_sched_inactive) {
+			qh_item =
+			    DWC_LIST_ENTRY(item, dwc_otg_qh_t, qh_list_entry);
+			DWC_PRINTF("    %p\n", qh_item);
+		}
+		DWC_PRINTF("  NP active sched:\n");
+		DWC_LIST_FOREACH(item, &hcd->non_periodic_sched_active) {
+			qh_item =
+			    DWC_LIST_ENTRY(item, dwc_otg_qh_t, qh_list_entry);
+			DWC_PRINTF("    %p\n", qh_item);
+		}
+		DWC_PRINTF("  Channels: \n");
+		for (i = 0; i < num_channels; i++) {
+			dwc_hc_t *hc = hcd->hc_ptr_array[i];
+			DWC_PRINTF("    %2d: %p\n", i, hc);
+		}
+	}
+}
+#else
+#define dump_channel_info(hcd, qh)
+#endif /* DEBUG */
+
+/**
+ * Work queue function for starting the HCD when A-Cable is connected.
+ * The hcd_start() must be called in a process context.
+ */
+static void hcd_start_func(void *_vp)
+{
+	dwc_otg_hcd_t *hcd = (dwc_otg_hcd_t *) _vp;
+
+	DWC_DEBUGPL(DBG_HCDV, "%s() %p\n", __func__, hcd);
+	if (hcd) {
+		hcd->fops->start(hcd);
+	}
+}
+
+static void del_xfer_timers(dwc_otg_hcd_t * hcd)
+{
+#ifdef DEBUG
+	int i;
+	int num_channels = hcd->core_if->core_params->host_channels;
+	for (i = 0; i < num_channels; i++) {
+		DWC_TIMER_CANCEL(hcd->core_if->hc_xfer_timer[i]);
+	}
+#endif
+}
+
+static void del_timers(dwc_otg_hcd_t * hcd)
+{
+	del_xfer_timers(hcd);
+	DWC_TIMER_CANCEL(hcd->conn_timer);
+}
+
+/**
+ * Processes all the URBs in a single list of QHs. Completes them with
+ * -ESHUTDOWN and frees the QTD.
+ */
+static void kill_urbs_in_qh_list(dwc_otg_hcd_t * hcd, dwc_list_link_t * qh_list)
+{
+	dwc_list_link_t *qh_item, *qh_tmp;
+	dwc_otg_qh_t *qh;
+	dwc_otg_qtd_t *qtd, *qtd_tmp;
+	int quiesced = 0;
+
+	DWC_LIST_FOREACH_SAFE(qh_item, qh_tmp, qh_list) {
+		qh = DWC_LIST_ENTRY(qh_item, dwc_otg_qh_t, qh_list_entry);
+		DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp,
+					 &qh->qtd_list, qtd_list_entry) {
+			qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list);
+			if (qtd->urb != NULL) {
+				hcd->fops->complete(hcd, qtd->urb->priv,
+						    qtd->urb, -DWC_E_SHUTDOWN);
+				dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh);
+			}
+
+		}
+		if(qh->channel) {
+			int n = qh->channel->hc_num;
+			/* Using hcchar.chen == 1 is not a reliable test.
+			 * It is possible that the channel has already halted
+			 * but not yet been through the IRQ handler.
+			 */
+			if (fiq_fsm_enable && (hcd->fiq_state->channel[qh->channel->hc_num].fsm != FIQ_PASSTHROUGH)) {
+				qh->channel->halt_status = DWC_OTG_HC_XFER_URB_DEQUEUE;
+				qh->channel->halt_pending = 1;
+				if (hcd->fiq_state->channel[n].fsm == FIQ_HS_ISOC_TURBO ||
+				    hcd->fiq_state->channel[n].fsm == FIQ_HS_ISOC_SLEEPING)
+					hcd->fiq_state->channel[n].fsm = FIQ_HS_ISOC_ABORTED;
+				/* We're called from disconnect callback or in the middle of freeing the HCD here,
+				 * so FIQ is disabled, top-level interrupts masked and we're holding the spinlock.
+				 * No further URBs will be submitted, but wait 1 microframe for any previously
+				 * submitted periodic DMA to finish.
+				 */
+				if (!quiesced) {
+					udelay(125);
+					quiesced = 1;
+				}
+			} else {
+				dwc_otg_hc_halt(hcd->core_if, qh->channel,
+						DWC_OTG_HC_XFER_URB_DEQUEUE);
+			}
+			qh->channel = NULL;
+		}
+		dwc_otg_hcd_qh_remove(hcd, qh);
+	}
+}
+
+/**
+ * Responds with an error status of ESHUTDOWN to all URBs in the non-periodic
+ * and periodic schedules. The QTD associated with each URB is removed from
+ * the schedule and freed. This function may be called when a disconnect is
+ * detected or when the HCD is being stopped.
+ */
+static void kill_all_urbs(dwc_otg_hcd_t * hcd)
+{
+	kill_urbs_in_qh_list(hcd, &hcd->non_periodic_sched_inactive);
+	kill_urbs_in_qh_list(hcd, &hcd->non_periodic_sched_active);
+	kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_inactive);
+	kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_ready);
+	kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_assigned);
+	kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_queued);
+}
+
+/**
+ * Start the connection timer.  An OTG host is required to display a
+ * message if the device does not connect within 10 seconds.  The
+ * timer is deleted if a port connect interrupt occurs before the
+ * timer expires.
+ */
+static void dwc_otg_hcd_start_connect_timer(dwc_otg_hcd_t * hcd)
+{
+	DWC_TIMER_SCHEDULE(hcd->conn_timer, 10000 /* 10 secs */ );
+}
+
+/**
+ * HCD Callback function for disconnect of the HCD.
+ *
+ * @param p void pointer to the <code>struct usb_hcd</code>
+ */
+static int32_t dwc_otg_hcd_session_start_cb(void *p)
+{
+	dwc_otg_hcd_t *dwc_otg_hcd;
+	DWC_DEBUGPL(DBG_HCDV, "%s(%p)\n", __func__, p);
+	dwc_otg_hcd = p;
+	dwc_otg_hcd_start_connect_timer(dwc_otg_hcd);
+	return 1;
+}
+
+/**
+ * HCD Callback function for starting the HCD when A-Cable is
+ * connected.
+ *
+ * @param p void pointer to the <code>struct usb_hcd</code>
+ */
+static int32_t dwc_otg_hcd_start_cb(void *p)
+{
+	dwc_otg_hcd_t *dwc_otg_hcd = p;
+	dwc_otg_core_if_t *core_if;
+	hprt0_data_t hprt0;
+
+	core_if = dwc_otg_hcd->core_if;
+
+	if (core_if->op_state == B_HOST) {
+		/*
+		 * Reset the port.  During a HNP mode switch the reset
+		 * needs to occur within 1ms and have a duration of at
+		 * least 50ms.
+		 */
+		hprt0.d32 = dwc_otg_read_hprt0(core_if);
+		hprt0.b.prtrst = 1;
+		DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+	}
+	DWC_WORKQ_SCHEDULE_DELAYED(core_if->wq_otg,
+				   hcd_start_func, dwc_otg_hcd, 50,
+				   "start hcd");
+
+	return 1;
+}
+
+/**
+ * HCD Callback function for disconnect of the HCD.
+ *
+ * @param p void pointer to the <code>struct usb_hcd</code>
+ */
+static int32_t dwc_otg_hcd_disconnect_cb(void *p)
+{
+	gintsts_data_t intr;
+	dwc_otg_hcd_t *dwc_otg_hcd = p;
+
+	DWC_SPINLOCK(dwc_otg_hcd->lock);
+	/*
+	 * Set status flags for the hub driver.
+	 */
+	dwc_otg_hcd->flags.b.port_connect_status_change = 1;
+	dwc_otg_hcd->flags.b.port_connect_status = 0;
+	if(fiq_enable) {
+		local_fiq_disable();
+		fiq_fsm_spin_lock(&dwc_otg_hcd->fiq_state->lock);
+	}
+	/*
+	 * Shutdown any transfers in process by clearing the Tx FIFO Empty
+	 * interrupt mask and status bits and disabling subsequent host
+	 * channel interrupts.
+	 */
+	intr.d32 = 0;
+	intr.b.nptxfempty = 1;
+	intr.b.ptxfempty = 1;
+	intr.b.hcintr = 1;
+	DWC_MODIFY_REG32(&dwc_otg_hcd->core_if->core_global_regs->gintmsk,
+			 intr.d32, 0);
+	DWC_MODIFY_REG32(&dwc_otg_hcd->core_if->core_global_regs->gintsts,
+			 intr.d32, 0);
+
+	del_timers(dwc_otg_hcd);
+
+	/*
+	 * Turn off the vbus power only if the core has transitioned to device
+	 * mode. If still in host mode, need to keep power on to detect a
+	 * reconnection.
+	 */
+	if (dwc_otg_is_device_mode(dwc_otg_hcd->core_if)) {
+		if (dwc_otg_hcd->core_if->op_state != A_SUSPEND) {
+			hprt0_data_t hprt0 = {.d32 = 0 };
+			DWC_PRINTF("Disconnect: PortPower off\n");
+			hprt0.b.prtpwr = 0;
+			DWC_WRITE_REG32(dwc_otg_hcd->core_if->host_if->hprt0,
+					hprt0.d32);
+		}
+
+		dwc_otg_disable_host_interrupts(dwc_otg_hcd->core_if);
+	}
+
+	/* Respond with an error status to all URBs in the schedule. */
+	kill_all_urbs(dwc_otg_hcd);
+
+	if (dwc_otg_is_host_mode(dwc_otg_hcd->core_if)) {
+		/* Clean up any host channels that were in use. */
+		int num_channels;
+		int i;
+		dwc_hc_t *channel;
+		dwc_otg_hc_regs_t *hc_regs;
+		hcchar_data_t hcchar;
+
+		num_channels = dwc_otg_hcd->core_if->core_params->host_channels;
+
+		if (!dwc_otg_hcd->core_if->dma_enable) {
+			/* Flush out any channel requests in slave mode. */
+			for (i = 0; i < num_channels; i++) {
+				channel = dwc_otg_hcd->hc_ptr_array[i];
+				if (DWC_CIRCLEQ_EMPTY_ENTRY
+				    (channel, hc_list_entry)) {
+					hc_regs =
+					    dwc_otg_hcd->core_if->
+					    host_if->hc_regs[i];
+					hcchar.d32 =
+					    DWC_READ_REG32(&hc_regs->hcchar);
+					if (hcchar.b.chen) {
+						hcchar.b.chen = 0;
+						hcchar.b.chdis = 1;
+						hcchar.b.epdir = 0;
+						DWC_WRITE_REG32
+						    (&hc_regs->hcchar,
+						     hcchar.d32);
+					}
+				}
+			}
+		}
+
+		if(fiq_fsm_enable) {
+			for(i=0; i < 128; i++) {
+				dwc_otg_hcd->hub_port[i] = 0;
+			}
+		}
+	}
+
+	if(fiq_enable) {
+		fiq_fsm_spin_unlock(&dwc_otg_hcd->fiq_state->lock);
+		local_fiq_enable();
+	}
+
+	if (dwc_otg_hcd->fops->disconnect) {
+		dwc_otg_hcd->fops->disconnect(dwc_otg_hcd);
+	}
+
+	DWC_SPINUNLOCK(dwc_otg_hcd->lock);
+	return 1;
+}
+
+/**
+ * HCD Callback function for stopping the HCD.
+ *
+ * @param p void pointer to the <code>struct usb_hcd</code>
+ */
+static int32_t dwc_otg_hcd_stop_cb(void *p)
+{
+	dwc_otg_hcd_t *dwc_otg_hcd = p;
+
+	DWC_DEBUGPL(DBG_HCDV, "%s(%p)\n", __func__, p);
+	dwc_otg_hcd_stop(dwc_otg_hcd);
+	return 1;
+}
+
+#ifdef CONFIG_USB_DWC_OTG_LPM
+/**
+ * HCD Callback function for sleep of HCD.
+ *
+ * @param p void pointer to the <code>struct usb_hcd</code>
+ */
+static int dwc_otg_hcd_sleep_cb(void *p)
+{
+	dwc_otg_hcd_t *hcd = p;
+
+	dwc_otg_hcd_free_hc_from_lpm(hcd);
+
+	return 0;
+}
+#endif
+
+
+/**
+ * HCD Callback function for Remote Wakeup.
+ *
+ * @param p void pointer to the <code>struct usb_hcd</code>
+ */
+static int dwc_otg_hcd_rem_wakeup_cb(void *p)
+{
+	dwc_otg_hcd_t *hcd = p;
+
+	if (hcd->core_if->lx_state == DWC_OTG_L2) {
+		hcd->flags.b.port_suspend_change = 1;
+	}
+#ifdef CONFIG_USB_DWC_OTG_LPM
+	else {
+		hcd->flags.b.port_l1_change = 1;
+	}
+#endif
+	return 0;
+}
+
+/**
+ * Halts the DWC_otg host mode operations in a clean manner. USB transfers are
+ * stopped.
+ */
+void dwc_otg_hcd_stop(dwc_otg_hcd_t * hcd)
+{
+	hprt0_data_t hprt0 = {.d32 = 0 };
+
+	DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD STOP\n");
+
+	/*
+	 * The root hub should be disconnected before this function is called.
+	 * The disconnect will clear the QTD lists (via ..._hcd_urb_dequeue)
+	 * and the QH lists (via ..._hcd_endpoint_disable).
+	 */
+
+	/* Turn off all host-specific interrupts. */
+	dwc_otg_disable_host_interrupts(hcd->core_if);
+
+	/* Turn off the vbus power */
+	DWC_PRINTF("PortPower off\n");
+	hprt0.b.prtpwr = 0;
+	DWC_WRITE_REG32(hcd->core_if->host_if->hprt0, hprt0.d32);
+	dwc_mdelay(1);
+}
+
+int dwc_otg_hcd_urb_enqueue(dwc_otg_hcd_t * hcd,
+			    dwc_otg_hcd_urb_t * dwc_otg_urb, void **ep_handle,
+			    int atomic_alloc)
+{
+	int retval = 0;
+	uint8_t needs_scheduling = 0;
+	dwc_otg_transaction_type_e tr_type;
+	dwc_otg_qtd_t *qtd;
+	gintmsk_data_t intr_mask = {.d32 = 0 };
+	hprt0_data_t hprt0 = { .d32 = 0 };
+
+#ifdef DEBUG /* integrity checks (Broadcom) */
+	if (NULL == hcd->core_if) {
+		DWC_ERROR("**** DWC OTG HCD URB Enqueue - HCD has NULL core_if\n");
+		/* No longer connected. */
+		return -DWC_E_INVALID;
+	}
+#endif
+	if (!hcd->flags.b.port_connect_status) {
+		/* No longer connected. */
+		DWC_ERROR("Not connected\n");
+		return -DWC_E_NO_DEVICE;
+	}
+
+	/* Some core configurations cannot support LS traffic on a FS root port */
+	if ((hcd->fops->speed(hcd, dwc_otg_urb->priv) == USB_SPEED_LOW) &&
+		(hcd->core_if->hwcfg2.b.fs_phy_type == 1) &&
+		(hcd->core_if->hwcfg2.b.hs_phy_type == 1)) {
+			hprt0.d32 = DWC_READ_REG32(hcd->core_if->host_if->hprt0);
+			if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_FULL_SPEED) {
+				return -DWC_E_NO_DEVICE;
+			}
+	}
+
+	qtd = dwc_otg_hcd_qtd_create(dwc_otg_urb, atomic_alloc);
+	if (qtd == NULL) {
+		DWC_ERROR("DWC OTG HCD URB Enqueue failed creating QTD\n");
+		return -DWC_E_NO_MEMORY;
+	}
+#ifdef DEBUG /* integrity checks (Broadcom) */
+	if (qtd->urb == NULL) {
+		DWC_ERROR("**** DWC OTG HCD URB Enqueue created QTD with no URBs\n");
+		return -DWC_E_NO_MEMORY;
+	}
+	if (qtd->urb->priv == NULL) {
+		DWC_ERROR("**** DWC OTG HCD URB Enqueue created QTD URB with no URB handle\n");
+		return -DWC_E_NO_MEMORY;
+	}
+#endif
+	intr_mask.d32 = DWC_READ_REG32(&hcd->core_if->core_global_regs->gintmsk);
+	if(!intr_mask.b.sofintr || fiq_enable) needs_scheduling = 1;
+	if((((dwc_otg_qh_t *)ep_handle)->ep_type == UE_BULK) && !(qtd->urb->flags & URB_GIVEBACK_ASAP))
+		/* Do not schedule SG transactions until qtd has URB_GIVEBACK_ASAP set */
+		needs_scheduling = 0;
+
+	retval = dwc_otg_hcd_qtd_add(qtd, hcd, (dwc_otg_qh_t **) ep_handle, atomic_alloc);
+            // creates a new queue in ep_handle if it doesn't exist already
+	if (retval < 0) {
+		DWC_ERROR("DWC OTG HCD URB Enqueue failed adding QTD. "
+			  "Error status %d\n", retval);
+		dwc_otg_hcd_qtd_free(qtd);
+		return retval;
+	}
+
+	if(needs_scheduling) {
+		tr_type = dwc_otg_hcd_select_transactions(hcd);
+		if (tr_type != DWC_OTG_TRANSACTION_NONE) {
+			dwc_otg_hcd_queue_transactions(hcd, tr_type);
+		}
+	}
+	return retval;
+}
+
+int dwc_otg_hcd_urb_dequeue(dwc_otg_hcd_t * hcd,
+			    dwc_otg_hcd_urb_t * dwc_otg_urb)
+{
+	dwc_otg_qh_t *qh;
+	dwc_otg_qtd_t *urb_qtd;
+	BUG_ON(!hcd);
+	BUG_ON(!dwc_otg_urb);
+
+#ifdef DEBUG /* integrity checks (Broadcom) */
+
+	if (hcd == NULL) {
+		DWC_ERROR("**** DWC OTG HCD URB Dequeue has NULL HCD\n");
+		return -DWC_E_INVALID;
+	}
+	if (dwc_otg_urb == NULL) {
+		DWC_ERROR("**** DWC OTG HCD URB Dequeue has NULL URB\n");
+		return -DWC_E_INVALID;
+	}
+	if (dwc_otg_urb->qtd == NULL) {
+		DWC_ERROR("**** DWC OTG HCD URB Dequeue with NULL QTD\n");
+		return -DWC_E_INVALID;
+	}
+	urb_qtd = dwc_otg_urb->qtd;
+	BUG_ON(!urb_qtd);
+	if (urb_qtd->qh == NULL) {
+		DWC_ERROR("**** DWC OTG HCD URB Dequeue with QTD with NULL Q handler\n");
+		return -DWC_E_INVALID;
+	}
+#else
+	urb_qtd = dwc_otg_urb->qtd;
+	BUG_ON(!urb_qtd);
+#endif
+	qh = urb_qtd->qh;
+	BUG_ON(!qh);
+	if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) {
+		if (urb_qtd->in_process) {
+			dump_channel_info(hcd, qh);
+		}
+	}
+#ifdef DEBUG /* integrity checks (Broadcom) */
+	if (hcd->core_if == NULL) {
+		DWC_ERROR("**** DWC OTG HCD URB Dequeue HCD has NULL core_if\n");
+		return -DWC_E_INVALID;
+	}
+#endif
+	if (urb_qtd->in_process && qh->channel) {
+		/* The QTD is in process (it has been assigned to a channel). */
+		if (hcd->flags.b.port_connect_status) {
+			int n = qh->channel->hc_num;
+			/*
+			 * If still connected (i.e. in host mode), halt the
+			 * channel so it can be used for other transfers. If
+			 * no longer connected, the host registers can't be
+			 * written to halt the channel since the core is in
+			 * device mode.
+			 */
+			/* In FIQ FSM mode, we need to shut down carefully.
+			 * The FIQ may attempt to restart a disabled channel */
+			if (fiq_fsm_enable && (hcd->fiq_state->channel[n].fsm != FIQ_PASSTHROUGH)) {
+				int retries = 3;
+				int running = 0;
+				enum fiq_fsm_state state;
+
+				local_fiq_disable();
+				fiq_fsm_spin_lock(&hcd->fiq_state->lock);
+				qh->channel->halt_status = DWC_OTG_HC_XFER_URB_DEQUEUE;
+				qh->channel->halt_pending = 1;
+				if (hcd->fiq_state->channel[n].fsm == FIQ_HS_ISOC_TURBO ||
+				    hcd->fiq_state->channel[n].fsm == FIQ_HS_ISOC_SLEEPING)
+					hcd->fiq_state->channel[n].fsm = FIQ_HS_ISOC_ABORTED;
+				fiq_fsm_spin_unlock(&hcd->fiq_state->lock);
+				local_fiq_enable();
+
+				if (dwc_qh_is_non_per(qh)) {
+					do {
+						state = READ_ONCE(hcd->fiq_state->channel[n].fsm);
+						running = (state != FIQ_NP_SPLIT_DONE) &&
+							  (state != FIQ_NP_SPLIT_LS_ABORTED) &&
+							  (state != FIQ_NP_SPLIT_HS_ABORTED);
+						if (!running)
+							break;
+						udelay(125);
+					} while(--retries);
+					if (!retries)
+						DWC_WARN("Timed out waiting for FSM NP transfer to complete on %d",
+							 qh->channel->hc_num);
+				}
+			} else {
+				dwc_otg_hc_halt(hcd->core_if, qh->channel,
+						DWC_OTG_HC_XFER_URB_DEQUEUE);
+			}
+		}
+	}
+
+	/*
+	 * Free the QTD and clean up the associated QH. Leave the QH in the
+	 * schedule if it has any remaining QTDs.
+	 */
+
+	DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD URB Dequeue - "
+                    "delete %sQueue handler\n",
+                    hcd->core_if->dma_desc_enable?"DMA ":"");
+	if (!hcd->core_if->dma_desc_enable) {
+		uint8_t b = urb_qtd->in_process;
+		if (nak_holdoff && qh->do_split && dwc_qh_is_non_per(qh))
+			qh->nak_frame = 0xFFFF;
+		dwc_otg_hcd_qtd_remove_and_free(hcd, urb_qtd, qh);
+		if (b) {
+			dwc_otg_hcd_qh_deactivate(hcd, qh, 0);
+			qh->channel = NULL;
+		} else if (DWC_CIRCLEQ_EMPTY(&qh->qtd_list)) {
+			dwc_otg_hcd_qh_remove(hcd, qh);
+		}
+	} else {
+		dwc_otg_hcd_qtd_remove_and_free(hcd, urb_qtd, qh);
+	}
+	return 0;
+}
+
+int dwc_otg_hcd_endpoint_disable(dwc_otg_hcd_t * hcd, void *ep_handle,
+				 int retry)
+{
+	dwc_otg_qh_t *qh = (dwc_otg_qh_t *) ep_handle;
+	int retval = 0;
+	dwc_irqflags_t flags;
+
+	if (retry < 0) {
+		retval = -DWC_E_INVALID;
+		goto done;
+	}
+
+	if (!qh) {
+		retval = -DWC_E_INVALID;
+		goto done;
+	}
+
+	DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags);
+
+	while (!DWC_CIRCLEQ_EMPTY(&qh->qtd_list) && retry) {
+		DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags);
+		retry--;
+		dwc_msleep(5);
+		DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags);
+	}
+
+	dwc_otg_hcd_qh_remove(hcd, qh);
+
+	DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags);
+	/*
+	 * Split dwc_otg_hcd_qh_remove_and_free() into qh_remove
+	 * and qh_free to prevent stack dump on DWC_DMA_FREE() with
+	 * irq_disabled (spinlock_irqsave) in dwc_otg_hcd_desc_list_free()
+	 * and dwc_otg_hcd_frame_list_alloc().
+	 */
+	dwc_otg_hcd_qh_free(hcd, qh);
+
+done:
+	return retval;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30)
+int dwc_otg_hcd_endpoint_reset(dwc_otg_hcd_t * hcd, void *ep_handle)
+{
+	int retval = 0;
+	dwc_otg_qh_t *qh = (dwc_otg_qh_t *) ep_handle;
+	if (!qh)
+		return -DWC_E_INVALID;
+
+	qh->data_toggle = DWC_OTG_HC_PID_DATA0;
+	return retval;
+}
+#endif
+
+/**
+ * HCD Callback structure for handling mode switching.
+ */
+static dwc_otg_cil_callbacks_t hcd_cil_callbacks = {
+	.start = dwc_otg_hcd_start_cb,
+	.stop = dwc_otg_hcd_stop_cb,
+	.disconnect = dwc_otg_hcd_disconnect_cb,
+	.session_start = dwc_otg_hcd_session_start_cb,
+	.resume_wakeup = dwc_otg_hcd_rem_wakeup_cb,
+#ifdef CONFIG_USB_DWC_OTG_LPM
+	.sleep = dwc_otg_hcd_sleep_cb,
+#endif
+	.p = 0,
+};
+
+/**
+ * Reset tasklet function
+ */
+static void reset_tasklet_func(void *data)
+{
+	dwc_otg_hcd_t *dwc_otg_hcd = (dwc_otg_hcd_t *) data;
+	dwc_otg_core_if_t *core_if = dwc_otg_hcd->core_if;
+	hprt0_data_t hprt0;
+
+	DWC_DEBUGPL(DBG_HCDV, "USB RESET tasklet called\n");
+
+	hprt0.d32 = dwc_otg_read_hprt0(core_if);
+	hprt0.b.prtrst = 1;
+	DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+	dwc_mdelay(60);
+
+	hprt0.b.prtrst = 0;
+	DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+	dwc_otg_hcd->flags.b.port_reset_change = 1;
+}
+
+static void completion_tasklet_func(void *ptr)
+{
+	dwc_otg_hcd_t *hcd = (dwc_otg_hcd_t *) ptr;
+	struct urb *urb;
+	urb_tq_entry_t *item;
+	dwc_irqflags_t flags;
+
+	/* This could just be spin_lock_irq */
+	DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags);
+	while (!DWC_TAILQ_EMPTY(&hcd->completed_urb_list)) {
+		item = DWC_TAILQ_FIRST(&hcd->completed_urb_list);
+		urb = item->urb;
+		DWC_TAILQ_REMOVE(&hcd->completed_urb_list, item,
+				urb_tq_entries);
+		DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags);
+		DWC_FREE(item);
+
+		usb_hcd_giveback_urb(hcd->priv, urb, urb->status);
+
+
+		DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags);
+	}
+	DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags);
+	return;
+}
+
+static void qh_list_free(dwc_otg_hcd_t * hcd, dwc_list_link_t * qh_list)
+{
+	dwc_list_link_t *item;
+	dwc_otg_qh_t *qh;
+	dwc_irqflags_t flags;
+
+	if (!qh_list->next) {
+		/* The list hasn't been initialized yet. */
+		return;
+	}
+	/*
+	 * Hold spinlock here. Not needed in that case if bellow
+	 * function is being called from ISR
+	 */
+	DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags);
+	/* Ensure there are no QTDs or URBs left. */
+	kill_urbs_in_qh_list(hcd, qh_list);
+	DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags);
+
+	DWC_LIST_FOREACH(item, qh_list) {
+		qh = DWC_LIST_ENTRY(item, dwc_otg_qh_t, qh_list_entry);
+		dwc_otg_hcd_qh_remove_and_free(hcd, qh);
+	}
+}
+
+/**
+ * Exit from Hibernation if Host did not detect SRP from connected SRP capable
+ * Device during SRP time by host power up.
+ */
+#ifdef DWC_DEV_SRPCAP
+static void dwc_otg_hcd_power_up(void *ptr)
+{
+	gpwrdn_data_t gpwrdn = {.d32 = 0 };
+	dwc_otg_core_if_t *core_if = (dwc_otg_core_if_t *) ptr;
+
+	DWC_PRINTF("%s called\n", __FUNCTION__);
+
+	if (!core_if->hibernation_suspend) {
+		DWC_PRINTF("Already exited from Hibernation\n");
+		return;
+	}
+
+	/* Switch on the voltage to the core */
+	gpwrdn.b.pwrdnswtch = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+	dwc_udelay(10);
+
+	/* Reset the core */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pwrdnrstn = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+	dwc_udelay(10);
+
+	/* Disable power clamps */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pwrdnclmp = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+	/* Remove reset the core signal */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pwrdnrstn = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0, gpwrdn.d32);
+	dwc_udelay(10);
+
+	/* Disable PMU interrupt */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pmuintsel = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+	core_if->hibernation_suspend = 0;
+
+	/* Disable PMU */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.pmuactv = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+	dwc_udelay(10);
+
+	/* Enable VBUS */
+	gpwrdn.d32 = 0;
+	gpwrdn.b.dis_vbus = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, gpwrdn.d32, 0);
+
+	core_if->op_state = A_HOST;
+	dwc_otg_core_init(core_if);
+	dwc_otg_enable_global_interrupts(core_if);
+	cil_hcd_start(core_if);
+}
+#endif
+
+void dwc_otg_cleanup_fiq_channel(dwc_otg_hcd_t *hcd, uint32_t num)
+{
+	struct fiq_channel_state *st = &hcd->fiq_state->channel[num];
+	struct fiq_dma_channel *split_dma = hcd->fiq_dmab;
+	int i;
+
+	st->fsm = FIQ_PASSTHROUGH;
+	st->hcchar_copy.d32 = 0;
+	st->hcsplt_copy.d32 = 0;
+	st->hcint_copy.d32 = 0;
+	st->hcintmsk_copy.d32 = 0;
+	st->hctsiz_copy.d32 = 0;
+	st->hcdma_copy.d32 = 0;
+	st->nr_errors = 0;
+	st->hub_addr = 0;
+	st->port_addr = 0;
+	st->expected_uframe = 0;
+	st->nrpackets = 0;
+	st->dma_info.index = 0;
+	for (i = 0; i < 6; i++)
+		st->dma_info.slot_len[i] = 255;
+	st->hs_isoc_info.index = 0;
+	st->hs_isoc_info.iso_desc = NULL;
+	st->hs_isoc_info.nrframes = 0;
+
+	DWC_MEMSET(&split_dma[num].index[0], 0x6b, 1128);
+}
+
+/**
+ * Frees secondary storage associated with the dwc_otg_hcd structure contained
+ * in the struct usb_hcd field.
+ */
+static void dwc_otg_hcd_free(dwc_otg_hcd_t * dwc_otg_hcd)
+{
+	struct device *dev = dwc_otg_hcd_to_dev(dwc_otg_hcd);
+	int i;
+
+	DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD FREE\n");
+
+	del_timers(dwc_otg_hcd);
+
+	/* Free memory for QH/QTD lists */
+	qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->non_periodic_sched_inactive);
+	qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->non_periodic_sched_active);
+	qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->periodic_sched_inactive);
+	qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->periodic_sched_ready);
+	qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->periodic_sched_assigned);
+	qh_list_free(dwc_otg_hcd, &dwc_otg_hcd->periodic_sched_queued);
+
+	/* Free memory for the host channels. */
+	for (i = 0; i < MAX_EPS_CHANNELS; i++) {
+		dwc_hc_t *hc = dwc_otg_hcd->hc_ptr_array[i];
+
+#ifdef DEBUG
+		if (dwc_otg_hcd->core_if->hc_xfer_timer[i]) {
+			DWC_TIMER_FREE(dwc_otg_hcd->core_if->hc_xfer_timer[i]);
+		}
+#endif
+		if (hc != NULL) {
+			DWC_DEBUGPL(DBG_HCDV, "HCD Free channel #%i, hc=%p\n",
+				    i, hc);
+			DWC_FREE(hc);
+		}
+	}
+
+	if (dwc_otg_hcd->core_if->dma_enable) {
+		if (dwc_otg_hcd->status_buf_dma) {
+			DWC_DMA_FREE(dev, DWC_OTG_HCD_STATUS_BUF_SIZE,
+				     dwc_otg_hcd->status_buf,
+				     dwc_otg_hcd->status_buf_dma);
+		}
+	} else if (dwc_otg_hcd->status_buf != NULL) {
+		DWC_FREE(dwc_otg_hcd->status_buf);
+	}
+	DWC_SPINLOCK_FREE(dwc_otg_hcd->lock);
+	/* Set core_if's lock pointer to NULL */
+	dwc_otg_hcd->core_if->lock = NULL;
+
+	DWC_TIMER_FREE(dwc_otg_hcd->conn_timer);
+	DWC_TASK_FREE(dwc_otg_hcd->reset_tasklet);
+	DWC_TASK_FREE(dwc_otg_hcd->completion_tasklet);
+	DWC_DMA_FREE(dev, 16, dwc_otg_hcd->fiq_state->dummy_send,
+		     dwc_otg_hcd->fiq_state->dummy_send_dma);
+	DWC_FREE(dwc_otg_hcd->fiq_state);
+
+#ifdef DWC_DEV_SRPCAP
+	if (dwc_otg_hcd->core_if->power_down == 2 &&
+	    dwc_otg_hcd->core_if->pwron_timer) {
+		DWC_TIMER_FREE(dwc_otg_hcd->core_if->pwron_timer);
+	}
+#endif
+	DWC_FREE(dwc_otg_hcd);
+}
+
+int dwc_otg_hcd_init(dwc_otg_hcd_t * hcd, dwc_otg_core_if_t * core_if)
+{
+	struct device *dev = dwc_otg_hcd_to_dev(hcd);
+	int retval = 0;
+	int num_channels;
+	int i;
+	dwc_hc_t *channel;
+
+#if (defined(DWC_LINUX) && defined(CONFIG_DEBUG_SPINLOCK))
+	DWC_SPINLOCK_ALLOC_LINUX_DEBUG(hcd->lock);
+#else
+	hcd->lock = DWC_SPINLOCK_ALLOC();
+#endif
+        DWC_DEBUGPL(DBG_HCDV, "init of HCD %p given core_if %p\n",
+                    hcd, core_if);
+	if (!hcd->lock) {
+		DWC_ERROR("Could not allocate lock for pcd");
+		DWC_FREE(hcd);
+		retval = -DWC_E_NO_MEMORY;
+		goto out;
+	}
+	hcd->core_if = core_if;
+
+	/* Register the HCD CIL Callbacks */
+	dwc_otg_cil_register_hcd_callbacks(hcd->core_if,
+					   &hcd_cil_callbacks, hcd);
+
+	/* Initialize the non-periodic schedule. */
+	DWC_LIST_INIT(&hcd->non_periodic_sched_inactive);
+	DWC_LIST_INIT(&hcd->non_periodic_sched_active);
+
+	/* Initialize the periodic schedule. */
+	DWC_LIST_INIT(&hcd->periodic_sched_inactive);
+	DWC_LIST_INIT(&hcd->periodic_sched_ready);
+	DWC_LIST_INIT(&hcd->periodic_sched_assigned);
+	DWC_LIST_INIT(&hcd->periodic_sched_queued);
+	DWC_TAILQ_INIT(&hcd->completed_urb_list);
+	/*
+	 * Create a host channel descriptor for each host channel implemented
+	 * in the controller. Initialize the channel descriptor array.
+	 */
+	DWC_CIRCLEQ_INIT(&hcd->free_hc_list);
+	num_channels = hcd->core_if->core_params->host_channels;
+	DWC_MEMSET(hcd->hc_ptr_array, 0, sizeof(hcd->hc_ptr_array));
+	for (i = 0; i < num_channels; i++) {
+		channel = DWC_ALLOC(sizeof(dwc_hc_t));
+		if (channel == NULL) {
+			retval = -DWC_E_NO_MEMORY;
+			DWC_ERROR("%s: host channel allocation failed\n",
+				  __func__);
+			dwc_otg_hcd_free(hcd);
+			goto out;
+		}
+		channel->hc_num = i;
+		hcd->hc_ptr_array[i] = channel;
+#ifdef DEBUG
+		hcd->core_if->hc_xfer_timer[i] =
+		    DWC_TIMER_ALLOC("hc timer", hc_xfer_timeout,
+				    &hcd->core_if->hc_xfer_info[i]);
+#endif
+		DWC_DEBUGPL(DBG_HCDV, "HCD Added channel #%d, hc=%p\n", i,
+			    channel);
+	}
+
+	if (fiq_enable) {
+		hcd->fiq_state = DWC_ALLOC(sizeof(struct fiq_state) + (sizeof(struct fiq_channel_state) * num_channels));
+		if (!hcd->fiq_state) {
+			retval = -DWC_E_NO_MEMORY;
+			DWC_ERROR("%s: cannot allocate fiq_state structure\n", __func__);
+			dwc_otg_hcd_free(hcd);
+			goto out;
+		}
+		DWC_MEMSET(hcd->fiq_state, 0, (sizeof(struct fiq_state) + (sizeof(struct fiq_channel_state) * num_channels)));
+
+		hcd->fiq_state->dummy_send = DWC_DMA_ALLOC_ATOMIC(dev, 16,
+							 &hcd->fiq_state->dummy_send_dma);
+
+		hcd->fiq_stack = DWC_ALLOC(sizeof(struct fiq_stack));
+		if (!hcd->fiq_stack) {
+			retval = -DWC_E_NO_MEMORY;
+			DWC_ERROR("%s: cannot allocate fiq_stack structure\n", __func__);
+			dwc_otg_hcd_free(hcd);
+			goto out;
+		}
+		hcd->fiq_stack->magic1 = 0xDEADBEEF;
+		hcd->fiq_stack->magic2 = 0xD00DFEED;
+		hcd->fiq_state->gintmsk_saved.d32 = ~0;
+		hcd->fiq_state->haintmsk_saved.b2.chint = ~0;
+
+		/* This bit is terrible and uses no API, but necessary. The FIQ has no concept of DMA pools
+		 * (and if it did, would be a lot slower). This allocates a chunk of memory (~9kiB for 8 host channels)
+		 * for use as transaction bounce buffers in a 2-D array. Our access into this chunk is done by some
+		 * moderately readable array casts.
+		 */
+		hcd->fiq_dmab = DWC_DMA_ALLOC(dev, (sizeof(struct fiq_dma_channel) * num_channels), &hcd->fiq_state->dma_base);
+		DWC_INFO("FIQ DMA bounce buffers: virt = %px dma = %pad len=%zu",
+				hcd->fiq_dmab, &hcd->fiq_state->dma_base,
+				sizeof(struct fiq_dma_channel) * num_channels);
+
+		DWC_MEMSET(hcd->fiq_dmab, 0x6b, 9024);
+
+		/* pointer for debug in fiq_print */
+		hcd->fiq_state->fiq_dmab = hcd->fiq_dmab;
+		if (fiq_fsm_enable) {
+			int i;
+			for (i=0; i < hcd->core_if->core_params->host_channels; i++) {
+				dwc_otg_cleanup_fiq_channel(hcd, i);
+			}
+			DWC_PRINTF("FIQ FSM acceleration enabled for :\n%s%s%s%s",
+				(fiq_fsm_mask & 0x1) ? "Non-periodic Split Transactions\n" : "",
+				(fiq_fsm_mask & 0x2) ? "Periodic Split Transactions\n" : "",
+				(fiq_fsm_mask & 0x4) ? "High-Speed Isochronous Endpoints\n" : "",
+				(fiq_fsm_mask & 0x8) ? "Interrupt/Control Split Transaction hack enabled\n" : "");
+		}
+	}
+
+	/* Initialize the Connection timeout timer. */
+	hcd->conn_timer = DWC_TIMER_ALLOC("Connection timer",
+					  dwc_otg_hcd_connect_timeout, 0);
+
+	printk(KERN_DEBUG "dwc_otg: Microframe scheduler %s\n", microframe_schedule ? "enabled":"disabled");
+	if (microframe_schedule)
+		init_hcd_usecs(hcd);
+
+	/* Initialize reset tasklet. */
+	hcd->reset_tasklet = DWC_TASK_ALLOC("reset_tasklet", reset_tasklet_func, hcd);
+
+	hcd->completion_tasklet = DWC_TASK_ALLOC("completion_tasklet",
+						completion_tasklet_func, hcd);
+#ifdef DWC_DEV_SRPCAP
+	if (hcd->core_if->power_down == 2) {
+		/* Initialize Power on timer for Host power up in case hibernation */
+		hcd->core_if->pwron_timer = DWC_TIMER_ALLOC("PWRON TIMER",
+									dwc_otg_hcd_power_up, core_if);
+	}
+#endif
+
+	/*
+	 * Allocate space for storing data on status transactions. Normally no
+	 * data is sent, but this space acts as a bit bucket. This must be
+	 * done after usb_add_hcd since that function allocates the DMA buffer
+	 * pool.
+	 */
+	if (hcd->core_if->dma_enable) {
+		hcd->status_buf =
+		    DWC_DMA_ALLOC(dev, DWC_OTG_HCD_STATUS_BUF_SIZE,
+				  &hcd->status_buf_dma);
+	} else {
+		hcd->status_buf = DWC_ALLOC(DWC_OTG_HCD_STATUS_BUF_SIZE);
+	}
+	if (!hcd->status_buf) {
+		retval = -DWC_E_NO_MEMORY;
+		DWC_ERROR("%s: status_buf allocation failed\n", __func__);
+		dwc_otg_hcd_free(hcd);
+		goto out;
+	}
+
+	hcd->otg_port = 1;
+	hcd->frame_list = NULL;
+	hcd->frame_list_dma = 0;
+	hcd->periodic_qh_count = 0;
+
+	DWC_MEMSET(hcd->hub_port, 0, sizeof(hcd->hub_port));
+#ifdef FIQ_DEBUG
+	DWC_MEMSET(hcd->hub_port_alloc, -1, sizeof(hcd->hub_port_alloc));
+#endif
+
+out:
+	return retval;
+}
+
+void dwc_otg_hcd_remove(dwc_otg_hcd_t * hcd)
+{
+	/* Turn off all host-specific interrupts. */
+	dwc_otg_disable_host_interrupts(hcd->core_if);
+
+	dwc_otg_hcd_free(hcd);
+}
+
+/**
+ * Initializes dynamic portions of the DWC_otg HCD state.
+ */
+static void dwc_otg_hcd_reinit(dwc_otg_hcd_t * hcd)
+{
+	int num_channels;
+	int i;
+	dwc_hc_t *channel;
+	dwc_hc_t *channel_tmp;
+
+	hcd->flags.d32 = 0;
+
+	hcd->non_periodic_qh_ptr = &hcd->non_periodic_sched_active;
+	if (!microframe_schedule) {
+		hcd->non_periodic_channels = 0;
+		hcd->periodic_channels = 0;
+	} else {
+		hcd->available_host_channels = hcd->core_if->core_params->host_channels;
+	}
+	/*
+	 * Put all channels in the free channel list and clean up channel
+	 * states.
+	 */
+	DWC_CIRCLEQ_FOREACH_SAFE(channel, channel_tmp,
+				 &hcd->free_hc_list, hc_list_entry) {
+		DWC_CIRCLEQ_REMOVE(&hcd->free_hc_list, channel, hc_list_entry);
+	}
+
+	num_channels = hcd->core_if->core_params->host_channels;
+	for (i = 0; i < num_channels; i++) {
+		channel = hcd->hc_ptr_array[i];
+		DWC_CIRCLEQ_INSERT_TAIL(&hcd->free_hc_list, channel,
+					hc_list_entry);
+		dwc_otg_hc_cleanup(hcd->core_if, channel);
+	}
+
+	/* Initialize the DWC core for host mode operation. */
+	dwc_otg_core_host_init(hcd->core_if);
+
+	/* Set core_if's lock pointer to the hcd->lock */
+	hcd->core_if->lock = hcd->lock;
+}
+
+/**
+ * Assigns transactions from a QTD to a free host channel and initializes the
+ * host channel to perform the transactions. The host channel is removed from
+ * the free list.
+ *
+ * @param hcd The HCD state structure.
+ * @param qh Transactions from the first QTD for this QH are selected and
+ * assigned to a free host channel.
+ */
+static void assign_and_init_hc(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh)
+{
+	dwc_hc_t *hc;
+	dwc_otg_qtd_t *qtd;
+	dwc_otg_hcd_urb_t *urb;
+	void* ptr = NULL;
+	uint16_t wLength;
+	uint32_t intr_enable;
+	unsigned long flags;
+	gintmsk_data_t gintmsk = { .d32 = 0, };
+	struct device *dev = dwc_otg_hcd_to_dev(hcd);
+
+	qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list);
+
+	urb = qtd->urb;
+
+	DWC_DEBUGPL(DBG_HCDV, "%s(%p,%p) - urb %x, actual_length %d\n", __func__, hcd, qh, (unsigned int)urb, urb->actual_length);
+
+	if (((urb->actual_length < 0) || (urb->actual_length > urb->length)) && !dwc_otg_hcd_is_pipe_in(&urb->pipe_info))
+		urb->actual_length = urb->length;
+
+
+	hc = DWC_CIRCLEQ_FIRST(&hcd->free_hc_list);
+
+	/* Remove the host channel from the free list. */
+	DWC_CIRCLEQ_REMOVE_INIT(&hcd->free_hc_list, hc, hc_list_entry);
+
+	qh->channel = hc;
+
+	qtd->in_process = 1;
+
+	/*
+	 * Use usb_pipedevice to determine device address. This address is
+	 * 0 before the SET_ADDRESS command and the correct address afterward.
+	 */
+	hc->dev_addr = dwc_otg_hcd_get_dev_addr(&urb->pipe_info);
+	hc->ep_num = dwc_otg_hcd_get_ep_num(&urb->pipe_info);
+	hc->speed = qh->dev_speed;
+	hc->max_packet = dwc_max_packet(qh->maxp);
+
+	hc->xfer_started = 0;
+	hc->halt_status = DWC_OTG_HC_XFER_NO_HALT_STATUS;
+	hc->error_state = (qtd->error_count > 0);
+	hc->halt_on_queue = 0;
+	hc->halt_pending = 0;
+	hc->requests = 0;
+
+	/*
+	 * The following values may be modified in the transfer type section
+	 * below. The xfer_len value may be reduced when the transfer is
+	 * started to accommodate the max widths of the XferSize and PktCnt
+	 * fields in the HCTSIZn register.
+	 */
+
+	hc->ep_is_in = (dwc_otg_hcd_is_pipe_in(&urb->pipe_info) != 0);
+	if (hc->ep_is_in) {
+		hc->do_ping = 0;
+	} else {
+		hc->do_ping = qh->ping_state;
+	}
+
+	hc->data_pid_start = qh->data_toggle;
+	hc->multi_count = 1;
+
+	if (hcd->core_if->dma_enable) {
+		hc->xfer_buff =
+		    (uint8_t *)(uintptr_t)urb->dma + urb->actual_length;
+
+		/* For non-dword aligned case */
+		if (((unsigned long)hc->xfer_buff & 0x3)
+		    && !hcd->core_if->dma_desc_enable) {
+			ptr = (uint8_t *) urb->buf + urb->actual_length;
+		}
+	} else {
+		hc->xfer_buff = (uint8_t *) urb->buf + urb->actual_length;
+	}
+	hc->xfer_len = urb->length - urb->actual_length;
+	hc->xfer_count = 0;
+
+	/*
+	 * Set the split attributes
+	 */
+	hc->do_split = 0;
+	if (qh->do_split) {
+		uint32_t hub_addr, port_addr;
+		hc->do_split = 1;
+		hc->start_pkt_count = 1;
+		hc->xact_pos = qtd->isoc_split_pos;
+		/* We don't need to do complete splits anymore */
+//		if(fiq_fsm_enable)
+		if (0)
+			hc->complete_split = qtd->complete_split = 0;
+		else
+			hc->complete_split = qtd->complete_split;
+
+		hcd->fops->hub_info(hcd, urb->priv, &hub_addr, &port_addr);
+		hc->hub_addr = (uint8_t) hub_addr;
+		hc->port_addr = (uint8_t) port_addr;
+	}
+
+	switch (dwc_otg_hcd_get_pipe_type(&urb->pipe_info)) {
+	case UE_CONTROL:
+		hc->ep_type = DWC_OTG_EP_TYPE_CONTROL;
+		switch (qtd->control_phase) {
+		case DWC_OTG_CONTROL_SETUP:
+			DWC_DEBUGPL(DBG_HCDV, "  Control setup transaction\n");
+			hc->do_ping = 0;
+			hc->ep_is_in = 0;
+			hc->data_pid_start = DWC_OTG_HC_PID_SETUP;
+			if (hcd->core_if->dma_enable) {
+				hc->xfer_buff =
+					(uint8_t *)(uintptr_t)urb->setup_dma;
+			} else {
+				hc->xfer_buff = (uint8_t *) urb->setup_packet;
+			}
+			hc->xfer_len = 8;
+			ptr = NULL;
+			break;
+		case DWC_OTG_CONTROL_DATA:
+			DWC_DEBUGPL(DBG_HCDV, "  Control data transaction\n");
+			/*
+			 * Hardware bug: small IN packets with length < 4
+			 * cause a 4-byte write to memory. We can only catch
+			 * the case where we know a short packet is going to be
+			 * returned in a control transfer, as the length is
+			 * specified in the setup packet. This is only an issue
+			 * for drivers that insist on packing a device's various
+			 * properties into a struct and querying them one at a
+			 * time (uvcvideo).
+			 * Force the use of align_buf so that the subsequent
+			 * memcpy puts the right number of bytes in the URB's
+			 * buffer.
+			 */
+			wLength = ((uint16_t *)urb->setup_packet)[3];
+			#if 0
+			if (hc->ep_is_in && wLength < 4)
+				ptr = hc->xfer_buff;
+			#endif
+
+			hc->data_pid_start = qtd->data_toggle;
+			break;
+		case DWC_OTG_CONTROL_STATUS:
+			/*
+			 * Direction is opposite of data direction or IN if no
+			 * data.
+			 */
+			DWC_DEBUGPL(DBG_HCDV, "  Control status transaction\n");
+			if (urb->length == 0) {
+				hc->ep_is_in = 1;
+			} else {
+				hc->ep_is_in =
+				    dwc_otg_hcd_is_pipe_out(&urb->pipe_info);
+			}
+			if (hc->ep_is_in) {
+				hc->do_ping = 0;
+			}
+
+			hc->data_pid_start = DWC_OTG_HC_PID_DATA1;
+
+			hc->xfer_len = 0;
+			if (hcd->core_if->dma_enable) {
+				hc->xfer_buff = (uint8_t *)
+					(uintptr_t)hcd->status_buf_dma;
+			} else {
+				hc->xfer_buff = (uint8_t *) hcd->status_buf;
+			}
+			ptr = NULL;
+			break;
+		}
+		break;
+	case UE_BULK:
+		hc->ep_type = DWC_OTG_EP_TYPE_BULK;
+		break;
+	case UE_INTERRUPT:
+		hc->ep_type = DWC_OTG_EP_TYPE_INTR;
+		break;
+	case UE_ISOCHRONOUS:
+		{
+			struct dwc_otg_hcd_iso_packet_desc *frame_desc;
+
+			hc->ep_type = DWC_OTG_EP_TYPE_ISOC;
+
+			if (hcd->core_if->dma_desc_enable)
+				break;
+
+			frame_desc = &urb->iso_descs[qtd->isoc_frame_index];
+
+			frame_desc->status = 0;
+
+			if (hcd->core_if->dma_enable) {
+				hc->xfer_buff = (uint8_t *)(uintptr_t)urb->dma;
+			} else {
+				hc->xfer_buff = (uint8_t *) urb->buf;
+			}
+			hc->xfer_buff +=
+			    frame_desc->offset + qtd->isoc_split_offset;
+			hc->xfer_len =
+			    frame_desc->length - qtd->isoc_split_offset;
+
+			/* For non-dword aligned buffers */
+			if (((unsigned long)hc->xfer_buff & 0x3)
+			    && hcd->core_if->dma_enable) {
+				ptr =
+				    (uint8_t *) urb->buf + frame_desc->offset +
+				    qtd->isoc_split_offset;
+			} else
+				ptr = NULL;
+
+			if (hc->xact_pos == DWC_HCSPLIT_XACTPOS_ALL) {
+				if (hc->xfer_len <= 188) {
+					hc->xact_pos = DWC_HCSPLIT_XACTPOS_ALL;
+				} else {
+					hc->xact_pos =
+					    DWC_HCSPLIT_XACTPOS_BEGIN;
+				}
+			}
+		}
+		break;
+	}
+	/* non DWORD-aligned buffer case */
+	if (ptr) {
+		uint32_t buf_size;
+		if (hc->ep_type != DWC_OTG_EP_TYPE_ISOC) {
+			buf_size = hcd->core_if->core_params->max_transfer_size;
+		} else {
+			buf_size = 4096;
+		}
+		if (!qh->dw_align_buf) {
+			qh->dw_align_buf = DWC_DMA_ALLOC_ATOMIC(dev, buf_size,
+							 &qh->dw_align_buf_dma);
+			if (!qh->dw_align_buf) {
+				DWC_ERROR
+				    ("%s: Failed to allocate memory to handle "
+				     "non-dword aligned buffer case\n",
+				     __func__);
+				return;
+			}
+		}
+		if (!hc->ep_is_in) {
+			dwc_memcpy(qh->dw_align_buf, ptr, hc->xfer_len);
+		}
+		hc->align_buff = qh->dw_align_buf_dma;
+	} else {
+		hc->align_buff = 0;
+	}
+
+	if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+	    hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+		/*
+		 * This value may be modified when the transfer is started to
+		 * reflect the actual transfer length.
+		 */
+		hc->multi_count = dwc_hb_mult(qh->maxp);
+	}
+
+	if (hcd->core_if->dma_desc_enable)
+		hc->desc_list_addr = qh->desc_list_dma;
+
+	dwc_otg_hc_init(hcd->core_if, hc);
+
+	local_irq_save(flags);
+
+	if (fiq_enable) {
+		local_fiq_disable();
+		fiq_fsm_spin_lock(&hcd->fiq_state->lock);
+	}
+
+	/* Enable the top level host channel interrupt. */
+	intr_enable = (1 << hc->hc_num);
+	DWC_MODIFY_REG32(&hcd->core_if->host_if->host_global_regs->haintmsk, 0, intr_enable);
+
+	/* Make sure host channel interrupts are enabled. */
+	gintmsk.b.hcintr = 1;
+	DWC_MODIFY_REG32(&hcd->core_if->core_global_regs->gintmsk, 0, gintmsk.d32);
+
+	if (fiq_enable) {
+		fiq_fsm_spin_unlock(&hcd->fiq_state->lock);
+		local_fiq_enable();
+	}
+
+	local_irq_restore(flags);
+	hc->qh = qh;
+}
+
+
+/**
+ * fiq_fsm_transaction_suitable() - Test a QH for compatibility with the FIQ
+ * @hcd:	Pointer to the dwc_otg_hcd struct
+ * @qh:	pointer to the endpoint's queue head
+ *
+ * Transaction start/end control flow is grafted onto the existing dwc_otg
+ * mechanisms, to avoid spaghettifying the functions more than they already are.
+ * This function's eligibility check is altered by debug parameter.
+ *
+ * Returns: 0 for unsuitable, 1 implies the FIQ can be enabled for this transaction.
+ */
+
+int fiq_fsm_transaction_suitable(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
+{
+	if (qh->do_split) {
+		switch (qh->ep_type) {
+		case UE_CONTROL:
+		case UE_BULK:
+			if (fiq_fsm_mask & (1 << 0))
+				return 1;
+			break;
+		case UE_INTERRUPT:
+		case UE_ISOCHRONOUS:
+			if (fiq_fsm_mask & (1 << 1))
+				return 1;
+			break;
+		default:
+			break;
+		}
+	} else if (qh->ep_type == UE_ISOCHRONOUS) {
+		if (fiq_fsm_mask & (1 << 2)) {
+			/* ISOCH support. We test for compatibility:
+			 * - DWORD aligned buffers
+			 * - Must be at least 2 transfers (otherwise pointless to use the FIQ)
+			 * If yes, then the fsm enqueue function will handle the state machine setup.
+			 */
+			dwc_otg_qtd_t *qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list);
+			dwc_otg_hcd_urb_t *urb = qtd->urb;
+			dwc_dma_t ptr;
+			int i;
+
+			if (urb->packet_count < 2)
+				return 0;
+			for (i = 0; i < urb->packet_count; i++) {
+				ptr = urb->dma + urb->iso_descs[i].offset;
+				if (ptr & 0x3)
+					return 0;
+			}
+			return 1;
+		}
+	}
+	return 0;
+}
+
+/**
+ * fiq_fsm_setup_periodic_dma() - Set up DMA bounce buffers
+ * @hcd: Pointer to the dwc_otg_hcd struct
+ * @qh: Pointer to the endpoint's queue head
+ *
+ * Periodic split transactions are transmitted modulo 188 bytes.
+ * This necessitates slicing data up into buckets for isochronous out
+ * and fixing up the DMA address for all IN transfers.
+ *
+ * Returns 1 if the DMA bounce buffers have been used, 0 if the default
+ * HC buffer has been used.
+ */
+static int fiq_fsm_setup_periodic_dma(dwc_otg_hcd_t *hcd, struct fiq_channel_state *st, dwc_otg_qh_t *qh)
+ {
+	int frame_length, i = 0;
+	uint8_t *ptr = NULL;
+	dwc_hc_t *hc = qh->channel;
+	struct fiq_dma_channel *split_dma;
+	struct dwc_otg_hcd_iso_packet_desc *frame_desc;
+
+	for (i = 0; i < 6; i++) {
+		st->dma_info.slot_len[i] = 255;
+	}
+	st->dma_info.index = 0;
+	i = 0;
+	if (hc->ep_is_in) {
+		/*
+		 * Set dma_regs to bounce buffer. FIQ will update the
+		 * state depending on transaction progress.
+		 * Pointer arithmetic on hcd->fiq_state->dma_base (a dma_addr_t)
+		 * to point it to the correct offset in the allocated buffers.
+		 */
+		split_dma = (struct fiq_dma_channel *)
+			(uintptr_t)hcd->fiq_state->dma_base;
+		st->hcdma_copy.d32 = lower_32_bits((uintptr_t)
+			&split_dma[hc->hc_num].index[0].buf[0]);
+
+		/* Calculate the max number of CSPLITS such that the FIQ can time out
+		 * a transaction if it fails.
+		 */
+		frame_length = st->hcchar_copy.b.mps;
+		do {
+			i++;
+			frame_length -= 188;
+		} while (frame_length >= 0);
+		st->nrpackets = i;
+		return 1;
+	} else {
+		if (qh->ep_type == UE_ISOCHRONOUS) {
+
+			dwc_otg_qtd_t *qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list);
+
+			frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index];
+			frame_length = frame_desc->length;
+
+			/* Virtual address for bounce buffers */
+			split_dma = hcd->fiq_dmab;
+
+			ptr = qtd->urb->buf + frame_desc->offset;
+			if (frame_length == 0) {
+				/*
+				 * for isochronous transactions, we must still transmit a packet
+				 * even if the length is zero.
+				 */
+				st->dma_info.slot_len[0] = 0;
+				st->nrpackets = 1;
+			} else {
+				do {
+					if (frame_length <= 188) {
+						dwc_memcpy(&split_dma[hc->hc_num].index[i].buf[0], ptr, frame_length);
+						st->dma_info.slot_len[i] = frame_length;
+						ptr += frame_length;
+					} else {
+						dwc_memcpy(&split_dma[hc->hc_num].index[i].buf[0], ptr, 188);
+						st->dma_info.slot_len[i] = 188;
+						ptr += 188;
+					}
+					i++;
+					frame_length -= 188;
+				} while (frame_length > 0);
+				st->nrpackets = i;
+			}
+			ptr = qtd->urb->buf + frame_desc->offset;
+			/*
+			 * Point the HC at the DMA address of the bounce buffers
+			 *
+			 * Pointer arithmetic on hcd->fiq_state->dma_base (a
+			 * dma_addr_t) to point it to the correct offset in the
+			 * allocated buffers.
+			 */
+			split_dma = (struct fiq_dma_channel *)
+				(uintptr_t)hcd->fiq_state->dma_base;
+			st->hcdma_copy.d32 = lower_32_bits((uintptr_t)
+				&split_dma[hc->hc_num].index[0].buf[0]);
+
+			/* fixup xfersize to the actual packet size */
+			st->hctsiz_copy.b.pid = 0;
+			st->hctsiz_copy.b.xfersize = st->dma_info.slot_len[0];
+			return 1;
+		} else {
+			/* For interrupt, single OUT packet required, goes in the SSPLIT from hc_buff. */
+			return 0;
+		}
+	}
+}
+
+/**
+ * fiq_fsm_np_tt_contended() - Avoid performing contended non-periodic transfers
+ * @hcd: Pointer to the dwc_otg_hcd struct
+ * @qh: Pointer to the endpoint's queue head
+ *
+ * Certain hub chips don't differentiate between IN and OUT non-periodic pipes
+ * with the same endpoint number. If transfers get completed out of order
+ * (disregarding the direction token) then the hub can lock up
+ * or return erroneous responses.
+ *
+ * Returns 1 if initiating the transfer would cause contention, 0 otherwise.
+ */
+static int fiq_fsm_np_tt_contended(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
+{
+	int i;
+	struct fiq_channel_state *st;
+	int dev_addr = qh->channel->dev_addr;
+	int ep_num = qh->channel->ep_num;
+	for (i = 0; i < hcd->core_if->core_params->host_channels; i++) {
+		if (i == qh->channel->hc_num)
+			continue;
+		st = &hcd->fiq_state->channel[i];
+		switch (st->fsm) {
+		case FIQ_NP_SSPLIT_STARTED:
+		case FIQ_NP_SSPLIT_RETRY:
+		case FIQ_NP_SSPLIT_PENDING:
+		case FIQ_NP_OUT_CSPLIT_RETRY:
+		case FIQ_NP_IN_CSPLIT_RETRY:
+			if (st->hcchar_copy.b.devaddr == dev_addr &&
+				st->hcchar_copy.b.epnum == ep_num)
+				return 1;
+			break;
+		default:
+			break;
+		}
+	}
+	return 0;
+}
+
+/*
+ * Pushing a periodic request into the queue near the EOF1 point
+ * in a microframe causes erroneous behaviour (frmovrun) interrupt.
+ * Usually, the request goes out on the bus causing a transfer but
+ * the core does not transfer the data to memory.
+ * This guard interval (in number of 60MHz clocks) is required which
+ * must cater for CPU latency between reading the value and enabling
+ * the channel.
+ */
+#define PERIODIC_FRREM_BACKOFF 1000
+
+static int fiq_fsm_queue_isoc_transaction(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
+{
+	dwc_hc_t *hc = qh->channel;
+	dwc_otg_hc_regs_t *hc_regs = hcd->core_if->host_if->hc_regs[hc->hc_num];
+	dwc_otg_qtd_t *qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list);
+	int frame;
+	struct fiq_channel_state *st = &hcd->fiq_state->channel[hc->hc_num];
+	int xfer_len, nrpackets;
+	hcdma_data_t hcdma;
+	hfnum_data_t hfnum;
+
+	if (st->fsm != FIQ_PASSTHROUGH)
+		return 0;
+
+	st->nr_errors = 0;
+
+	st->hcchar_copy.d32 = 0;
+	st->hcchar_copy.b.mps = hc->max_packet;
+	st->hcchar_copy.b.epdir = hc->ep_is_in;
+	st->hcchar_copy.b.devaddr = hc->dev_addr;
+	st->hcchar_copy.b.epnum = hc->ep_num;
+	st->hcchar_copy.b.eptype = hc->ep_type;
+
+	st->hcintmsk_copy.b.chhltd = 1;
+
+	frame = dwc_otg_hcd_get_frame_number(hcd);
+	st->hcchar_copy.b.oddfrm = (frame & 0x1) ? 0 : 1;
+
+	st->hcchar_copy.b.lspddev = 0;
+	/* Enable the channel later as a final register write. */
+
+	st->hcsplt_copy.d32 = 0;
+
+	st->hs_isoc_info.iso_desc = (struct dwc_otg_hcd_iso_packet_desc *) &qtd->urb->iso_descs;
+	st->hs_isoc_info.nrframes = qtd->urb->packet_count;
+	/* grab the next DMA address offset from the array */
+	st->hcdma_copy.d32 = qtd->urb->dma;
+	hcdma.d32 = st->hcdma_copy.d32 + st->hs_isoc_info.iso_desc[0].offset;
+
+	/* We need to set multi_count. This is a bit tricky - has to be set per-transaction as
+	 * the core needs to be told to send the correct number. Caution: for IN transfers,
+	 * this is always set to the maximum size of the endpoint. */
+	xfer_len = st->hs_isoc_info.iso_desc[0].length;
+	nrpackets = (xfer_len + st->hcchar_copy.b.mps - 1) / st->hcchar_copy.b.mps;
+	if (nrpackets == 0)
+		nrpackets = 1;
+	st->hcchar_copy.b.multicnt = nrpackets;
+	st->hctsiz_copy.b.pktcnt = nrpackets;
+
+	/* Initial PID also needs to be set */
+	if (st->hcchar_copy.b.epdir == 0) {
+		st->hctsiz_copy.b.xfersize = xfer_len;
+		switch (st->hcchar_copy.b.multicnt) {
+		case 1:
+			st->hctsiz_copy.b.pid = DWC_PID_DATA0;
+			break;
+		case 2:
+		case 3:
+			st->hctsiz_copy.b.pid = DWC_PID_MDATA;
+			break;
+		}
+
+	} else {
+		st->hctsiz_copy.b.xfersize = nrpackets * st->hcchar_copy.b.mps;
+		switch (st->hcchar_copy.b.multicnt) {
+		case 1:
+			st->hctsiz_copy.b.pid = DWC_PID_DATA0;
+			break;
+		case 2:
+			st->hctsiz_copy.b.pid = DWC_PID_DATA1;
+			break;
+		case 3:
+			st->hctsiz_copy.b.pid = DWC_PID_DATA2;
+			break;
+		}
+	}
+
+	st->hs_isoc_info.stride = qh->interval;
+	st->uframe_sleeps = 0;
+
+	fiq_print(FIQDBG_INT, hcd->fiq_state, "FSMQ  %01d ", hc->hc_num);
+	fiq_print(FIQDBG_INT, hcd->fiq_state, "%08x", st->hcchar_copy.d32);
+	fiq_print(FIQDBG_INT, hcd->fiq_state, "%08x", st->hctsiz_copy.d32);
+	fiq_print(FIQDBG_INT, hcd->fiq_state, "%08x", st->hcdma_copy.d32);
+	hfnum.d32 = DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hfnum);
+	local_fiq_disable();
+	fiq_fsm_spin_lock(&hcd->fiq_state->lock);
+	DWC_WRITE_REG32(&hc_regs->hctsiz, st->hctsiz_copy.d32);
+	DWC_WRITE_REG32(&hc_regs->hcsplt, st->hcsplt_copy.d32);
+	DWC_WRITE_REG32(&hc_regs->hcdma, st->hcdma_copy.d32);
+	DWC_WRITE_REG32(&hc_regs->hcchar, st->hcchar_copy.d32);
+	DWC_WRITE_REG32(&hc_regs->hcintmsk, st->hcintmsk_copy.d32);
+	if (hfnum.b.frrem < PERIODIC_FRREM_BACKOFF) {
+		/* Prevent queueing near EOF1. Bad things happen if a periodic
+		 * split transaction is queued very close to EOF. SOF interrupt handler
+		 * will wake this channel at the next interrupt.
+		 */
+		st->fsm = FIQ_HS_ISOC_SLEEPING;
+		st->uframe_sleeps = 1;
+	} else {
+		st->fsm = FIQ_HS_ISOC_TURBO;
+		st->hcchar_copy.b.chen = 1;
+		DWC_WRITE_REG32(&hc_regs->hcchar, st->hcchar_copy.d32);
+	}
+	mb();
+	st->hcchar_copy.b.chen = 0;
+	fiq_fsm_spin_unlock(&hcd->fiq_state->lock);
+	local_fiq_enable();
+	return 0;
+}
+
+
+/**
+ * fiq_fsm_queue_split_transaction() - Set up a host channel and FIQ state
+ * @hcd: Pointer to the dwc_otg_hcd struct
+ * @qh: Pointer to the endpoint's queue head
+ *
+ * This overrides the dwc_otg driver's normal method of queueing a transaction.
+ * Called from dwc_otg_hcd_queue_transactions(), this performs specific setup
+ * for the nominated host channel.
+ *
+ * For periodic transfers, it also peeks at the FIQ state to see if an immediate
+ * start is possible. If not, then the FIQ is left to start the transfer.
+ */
+static int fiq_fsm_queue_split_transaction(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh)
+{
+	int start_immediate = 1, i;
+	hfnum_data_t hfnum;
+	dwc_hc_t *hc = qh->channel;
+	dwc_otg_hc_regs_t *hc_regs = hcd->core_if->host_if->hc_regs[hc->hc_num];
+	/* Program HC registers, setup FIQ_state, examine FIQ if periodic, start transfer (not if uframe 5) */
+	int hub_addr, port_addr, frame, uframe;
+	struct fiq_channel_state *st = &hcd->fiq_state->channel[hc->hc_num];
+
+	/*
+	 * Non-periodic channel assignments stay in the non_periodic_active queue.
+	 * Therefore we get repeatedly called until the FIQ's done processing this channel.
+	 */
+	if (qh->channel->xfer_started == 1)
+		return 0;
+
+	if (st->fsm != FIQ_PASSTHROUGH) {
+		pr_warn_ratelimited("%s:%d: Queue called for an active channel\n", __func__, __LINE__);
+		return 0;
+	}
+
+	qh->channel->xfer_started = 1;
+
+	st->nr_errors = 0;
+
+	st->hcchar_copy.d32 = 0;
+	st->hcchar_copy.b.mps = min_t(uint32_t, hc->xfer_len, hc->max_packet);
+	st->hcchar_copy.b.epdir = hc->ep_is_in;
+	st->hcchar_copy.b.devaddr = hc->dev_addr;
+	st->hcchar_copy.b.epnum = hc->ep_num;
+	st->hcchar_copy.b.eptype = hc->ep_type;
+	if (hc->ep_type & 0x1) {
+		if (hc->ep_is_in)
+			st->hcchar_copy.b.multicnt = 3;
+		else
+			/* Docs say set this to 1, but driver sets to 0! */
+			st->hcchar_copy.b.multicnt = 0;
+	} else {
+		st->hcchar_copy.b.multicnt = 1;
+		st->hcchar_copy.b.oddfrm = 0;
+	}
+	st->hcchar_copy.b.lspddev = (hc->speed == DWC_OTG_EP_SPEED_LOW) ? 1 : 0;
+	/* Enable the channel later as a final register write. */
+
+	st->hcsplt_copy.d32 = 0;
+	if(qh->do_split) {
+		hcd->fops->hub_info(hcd, DWC_CIRCLEQ_FIRST(&qh->qtd_list)->urb->priv, &hub_addr, &port_addr);
+		st->hcsplt_copy.b.compsplt = 0;
+		st->hcsplt_copy.b.spltena = 1;
+		// XACTPOS is for isoc-out only but needs initialising anyway.
+		st->hcsplt_copy.b.xactpos = ISOC_XACTPOS_ALL;
+		if((qh->ep_type == DWC_OTG_EP_TYPE_ISOC) && (!qh->ep_is_in)) {
+			/* For packetsize 0 < L < 188, ISOC_XACTPOS_ALL.
+			 * for longer than this, ISOC_XACTPOS_BEGIN and the FIQ
+			 * will update as necessary.
+			 */
+			if (hc->xfer_len > 188) {
+				st->hcsplt_copy.b.xactpos = ISOC_XACTPOS_BEGIN;
+			}
+		}
+		st->hcsplt_copy.b.hubaddr = (uint8_t) hub_addr;
+		st->hcsplt_copy.b.prtaddr = (uint8_t) port_addr;
+		st->hub_addr = hub_addr;
+		st->port_addr = port_addr;
+	}
+
+	st->hctsiz_copy.d32 = 0;
+	st->hctsiz_copy.b.dopng = 0;
+	st->hctsiz_copy.b.pid = hc->data_pid_start;
+
+	if (hc->ep_is_in || (hc->xfer_len > hc->max_packet)) {
+		hc->xfer_len = min_t(uint32_t, hc->xfer_len, hc->max_packet);
+	} else if (!hc->ep_is_in && (hc->xfer_len > 188)) {
+		hc->xfer_len = 188;
+	}
+	st->hctsiz_copy.b.xfersize = hc->xfer_len;
+
+	st->hctsiz_copy.b.pktcnt = 1;
+
+	if (hc->ep_type & 0x1) {
+		/*
+		 * For potentially multi-packet transfers, must use the DMA bounce buffers. For IN transfers,
+		 * the DMA address is the address of the first 188byte slot buffer in the bounce buffer array.
+		 * For multi-packet OUT transfers, we need to copy the data into the bounce buffer array so the FIQ can punt
+		 * the right address out as necessary. hc->xfer_buff and hc->xfer_len have already been set
+		 * in assign_and_init_hc(), but this is for the eventual transaction completion only. The FIQ
+		 * must not touch internal driver state.
+		 */
+		if(!fiq_fsm_setup_periodic_dma(hcd, st, qh)) {
+			if (hc->align_buff) {
+				st->hcdma_copy.d32 = hc->align_buff;
+			} else {
+				st->hcdma_copy.d32 = lower_32_bits((uintptr_t)hc->xfer_buff);
+			}
+		}
+	} else {
+		if (hc->align_buff) {
+			st->hcdma_copy.d32 = hc->align_buff;
+		} else {
+			st->hcdma_copy.d32 = lower_32_bits((uintptr_t)hc->xfer_buff);
+		}
+	}
+	/* The FIQ depends upon no other interrupts being enabled except channel halt.
+	 * Fixup channel interrupt mask. */
+	st->hcintmsk_copy.d32 = 0;
+	st->hcintmsk_copy.b.chhltd = 1;
+	st->hcintmsk_copy.b.ahberr = 1;
+
+	/* Hack courtesy of FreeBSD: apparently forcing Interrupt Split transactions
+	 * as Control puts the transfer into the non-periodic request queue and the
+	 * non-periodic handler in the hub. Makes things lots easier.
+	 */
+	if ((fiq_fsm_mask & 0x8) && hc->ep_type == UE_INTERRUPT) {
+		st->hcchar_copy.b.multicnt = 0;
+		st->hcchar_copy.b.oddfrm = 0;
+		st->hcchar_copy.b.eptype = UE_CONTROL;
+		if (hc->align_buff) {
+			st->hcdma_copy.d32 = hc->align_buff;
+		} else {
+			st->hcdma_copy.d32 = lower_32_bits((uintptr_t)hc->xfer_buff);
+		}
+	}
+	DWC_WRITE_REG32(&hc_regs->hcdma, st->hcdma_copy.d32);
+	DWC_WRITE_REG32(&hc_regs->hctsiz, st->hctsiz_copy.d32);
+	DWC_WRITE_REG32(&hc_regs->hcsplt, st->hcsplt_copy.d32);
+	DWC_WRITE_REG32(&hc_regs->hcchar, st->hcchar_copy.d32);
+	DWC_WRITE_REG32(&hc_regs->hcintmsk, st->hcintmsk_copy.d32);
+
+	local_fiq_disable();
+	fiq_fsm_spin_lock(&hcd->fiq_state->lock);
+
+	if (hc->ep_type & 0x1) {
+		hfnum.d32 = DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hfnum);
+		frame = (hfnum.b.frnum & ~0x7) >> 3;
+		uframe = hfnum.b.frnum & 0x7;
+		if (hfnum.b.frrem < PERIODIC_FRREM_BACKOFF) {
+			/* Prevent queueing near EOF1. Bad things happen if a periodic
+			 * split transaction is queued very close to EOF.
+			 */
+			start_immediate = 0;
+		} else if (uframe == 5) {
+			start_immediate = 0;
+		} else if (hc->ep_type == UE_ISOCHRONOUS && !hc->ep_is_in) {
+			start_immediate = 0;
+		} else if (hc->ep_is_in && fiq_fsm_too_late(hcd->fiq_state, hc->hc_num)) {
+			start_immediate = 0;
+		} else {
+			/* Search through all host channels to determine if a transaction
+			 * is currently in progress */
+			for (i = 0; i < hcd->core_if->core_params->host_channels; i++) {
+				if (i == hc->hc_num || hcd->fiq_state->channel[i].fsm == FIQ_PASSTHROUGH)
+					continue;
+				switch (hcd->fiq_state->channel[i].fsm) {
+				/* TT is reserved for channels that are in the middle of a periodic
+				 * split transaction.
+				 */
+				case FIQ_PER_SSPLIT_STARTED:
+				case FIQ_PER_CSPLIT_WAIT:
+				case FIQ_PER_CSPLIT_NYET1:
+				case FIQ_PER_CSPLIT_POLL:
+				case FIQ_PER_ISO_OUT_ACTIVE:
+				case FIQ_PER_ISO_OUT_LAST:
+					if (hcd->fiq_state->channel[i].hub_addr == hub_addr &&
+							hcd->fiq_state->channel[i].port_addr == port_addr) {
+						start_immediate = 0;
+					}
+					break;
+				default:
+					break;
+				}
+				if (!start_immediate)
+					break;
+			}
+		}
+	}
+	if ((fiq_fsm_mask & 0x8) && hc->ep_type == UE_INTERRUPT)
+		start_immediate = 1;
+
+	fiq_print(FIQDBG_INT, hcd->fiq_state, "FSMQ %01d %01d", hc->hc_num, start_immediate);
+	fiq_print(FIQDBG_INT, hcd->fiq_state, "%08d", hfnum.b.frrem);
+	//fiq_print(FIQDBG_INT, hcd->fiq_state, "H:%02dP:%02d", hub_addr, port_addr);
+	//fiq_print(FIQDBG_INT, hcd->fiq_state, "%08x", st->hctsiz_copy.d32);
+	//fiq_print(FIQDBG_INT, hcd->fiq_state, "%08x", st->hcdma_copy.d32);
+	switch (hc->ep_type) {
+		case UE_CONTROL:
+		case UE_BULK:
+			if (fiq_fsm_np_tt_contended(hcd, qh)) {
+				st->fsm = FIQ_NP_SSPLIT_PENDING;
+				start_immediate = 0;
+			} else {
+				st->fsm = FIQ_NP_SSPLIT_STARTED;
+			}
+			break;
+		case UE_ISOCHRONOUS:
+			if (hc->ep_is_in) {
+				if (start_immediate) {
+					st->fsm = FIQ_PER_SSPLIT_STARTED;
+				} else {
+					st->fsm = FIQ_PER_SSPLIT_QUEUED;
+				}
+			} else {
+				if (start_immediate) {
+					/* Single-isoc OUT packets don't require FIQ involvement */
+					if (st->nrpackets == 1) {
+						st->fsm = FIQ_PER_ISO_OUT_LAST;
+					} else {
+						st->fsm = FIQ_PER_ISO_OUT_ACTIVE;
+					}
+				} else {
+					st->fsm = FIQ_PER_ISO_OUT_PENDING;
+				}
+			}
+			break;
+		case UE_INTERRUPT:
+			if (fiq_fsm_mask & 0x8) {
+				if (fiq_fsm_np_tt_contended(hcd, qh)) {
+					st->fsm = FIQ_NP_SSPLIT_PENDING;
+					start_immediate = 0;
+				} else {
+					st->fsm = FIQ_NP_SSPLIT_STARTED;
+				}
+			} else if (start_immediate) {
+					st->fsm = FIQ_PER_SSPLIT_STARTED;
+			} else {
+				st->fsm = FIQ_PER_SSPLIT_QUEUED;
+			}
+			break;
+		default:
+			break;
+	}
+	if (start_immediate) {
+		/* Set the oddfrm bit as close as possible to actual queueing */
+		frame = dwc_otg_hcd_get_frame_number(hcd);
+		st->expected_uframe = (frame + 1) & 0x3FFF;
+		st->hcchar_copy.b.oddfrm = (frame & 0x1) ? 0 : 1;
+		st->hcchar_copy.b.chen = 1;
+		DWC_WRITE_REG32(&hc_regs->hcchar, st->hcchar_copy.d32);
+	}
+	mb();
+	fiq_fsm_spin_unlock(&hcd->fiq_state->lock);
+	local_fiq_enable();
+	return 0;
+}
+
+
+/**
+ * This function selects transactions from the HCD transfer schedule and
+ * assigns them to available host channels. It is called from HCD interrupt
+ * handler functions.
+ *
+ * @param hcd The HCD state structure.
+ *
+ * @return The types of new transactions that were assigned to host channels.
+ */
+dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t * hcd)
+{
+	dwc_list_link_t *qh_ptr;
+	dwc_otg_qh_t *qh;
+	int num_channels;
+	dwc_otg_transaction_type_e ret_val = DWC_OTG_TRANSACTION_NONE;
+
+#ifdef DEBUG_HOST_CHANNELS
+	last_sel_trans_num_per_scheduled = 0;
+	last_sel_trans_num_nonper_scheduled = 0;
+	last_sel_trans_num_avail_hc_at_start = hcd->available_host_channels;
+#endif /* DEBUG_HOST_CHANNELS */
+
+	/* Process entries in the periodic ready list. */
+	qh_ptr = DWC_LIST_FIRST(&hcd->periodic_sched_ready);
+
+	while (qh_ptr != &hcd->periodic_sched_ready &&
+	       !DWC_CIRCLEQ_EMPTY(&hcd->free_hc_list)) {
+
+		qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry);
+
+		if (microframe_schedule) {
+			// Make sure we leave one channel for non periodic transactions.
+			if (hcd->available_host_channels <= 1) {
+				break;
+			}
+			hcd->available_host_channels--;
+#ifdef DEBUG_HOST_CHANNELS
+			last_sel_trans_num_per_scheduled++;
+#endif /* DEBUG_HOST_CHANNELS */
+		}
+		qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry);
+		assign_and_init_hc(hcd, qh);
+
+		/*
+		 * Move the QH from the periodic ready schedule to the
+		 * periodic assigned schedule.
+		 */
+		qh_ptr = DWC_LIST_NEXT(qh_ptr);
+		DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_assigned,
+				   &qh->qh_list_entry);
+	}
+
+	/*
+	 * Process entries in the inactive portion of the non-periodic
+	 * schedule. Some free host channels may not be used if they are
+	 * reserved for periodic transfers.
+	 */
+	qh_ptr = hcd->non_periodic_sched_inactive.next;
+	num_channels = hcd->core_if->core_params->host_channels;
+	while (qh_ptr != &hcd->non_periodic_sched_inactive &&
+	       (microframe_schedule || hcd->non_periodic_channels <
+		num_channels - hcd->periodic_channels) &&
+	       !DWC_CIRCLEQ_EMPTY(&hcd->free_hc_list)) {
+
+		qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry);
+		/*
+		 * Check to see if this is a NAK'd retransmit, in which case ignore for retransmission
+		 * we hold off on bulk retransmissions to reduce NAK interrupt overhead for full-speed
+		 * cheeky devices that just hold off using NAKs
+		 */
+		if (fiq_enable && nak_holdoff && qh->do_split) {
+			if (qh->nak_frame != 0xffff) {
+				uint16_t next_frame = dwc_frame_num_inc(qh->nak_frame, (qh->ep_type == UE_BULK) ? nak_holdoff : 8);
+				uint16_t frame = dwc_otg_hcd_get_frame_number(hcd);
+				if (dwc_frame_num_le(frame, next_frame)) {
+					if(dwc_frame_num_le(next_frame, hcd->fiq_state->next_sched_frame)) {
+						hcd->fiq_state->next_sched_frame = next_frame;
+					}
+					qh_ptr = DWC_LIST_NEXT(qh_ptr);
+					continue;
+				} else {
+					qh->nak_frame = 0xFFFF;
+				}
+			}
+		}
+
+		if (microframe_schedule) {
+				if (hcd->available_host_channels < 1) {
+					break;
+				}
+				hcd->available_host_channels--;
+#ifdef DEBUG_HOST_CHANNELS
+				last_sel_trans_num_nonper_scheduled++;
+#endif /* DEBUG_HOST_CHANNELS */
+		}
+
+		assign_and_init_hc(hcd, qh);
+
+		/*
+		 * Move the QH from the non-periodic inactive schedule to the
+		 * non-periodic active schedule.
+		 */
+		qh_ptr = DWC_LIST_NEXT(qh_ptr);
+		DWC_LIST_MOVE_HEAD(&hcd->non_periodic_sched_active,
+				   &qh->qh_list_entry);
+
+		if (!microframe_schedule)
+			hcd->non_periodic_channels++;
+	}
+	/* we moved a non-periodic QH to the active schedule. If the inactive queue is empty,
+	 * stop the FIQ from kicking us. We could potentially still have elements here if we
+	 * ran out of host channels.
+	 */
+	if (fiq_enable) {
+		if (DWC_LIST_EMPTY(&hcd->non_periodic_sched_inactive)) {
+			hcd->fiq_state->kick_np_queues = 0;
+		} else {
+			/* For each entry remaining in the NP inactive queue,
+			* if this a NAK'd retransmit then don't set the kick flag.
+			*/
+			if(nak_holdoff) {
+				DWC_LIST_FOREACH(qh_ptr, &hcd->non_periodic_sched_inactive) {
+					qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry);
+					if (qh->nak_frame == 0xFFFF) {
+						hcd->fiq_state->kick_np_queues = 1;
+					}
+				}
+			}
+		}
+	}
+	if(!DWC_LIST_EMPTY(&hcd->periodic_sched_assigned))
+		ret_val |= DWC_OTG_TRANSACTION_PERIODIC;
+
+	if(!DWC_LIST_EMPTY(&hcd->non_periodic_sched_active))
+		ret_val |= DWC_OTG_TRANSACTION_NON_PERIODIC;
+
+
+#ifdef DEBUG_HOST_CHANNELS
+	last_sel_trans_num_avail_hc_at_end = hcd->available_host_channels;
+#endif /* DEBUG_HOST_CHANNELS */
+	return ret_val;
+}
+
+/**
+ * Attempts to queue a single transaction request for a host channel
+ * associated with either a periodic or non-periodic transfer. This function
+ * assumes that there is space available in the appropriate request queue. For
+ * an OUT transfer or SETUP transaction in Slave mode, it checks whether space
+ * is available in the appropriate Tx FIFO.
+ *
+ * @param hcd The HCD state structure.
+ * @param hc Host channel descriptor associated with either a periodic or
+ * non-periodic transfer.
+ * @param fifo_dwords_avail Number of DWORDs available in the periodic Tx
+ * FIFO for periodic transfers or the non-periodic Tx FIFO for non-periodic
+ * transfers.
+ *
+ * @return 1 if a request is queued and more requests may be needed to
+ * complete the transfer, 0 if no more requests are required for this
+ * transfer, -1 if there is insufficient space in the Tx FIFO.
+ */
+static int queue_transaction(dwc_otg_hcd_t * hcd,
+			     dwc_hc_t * hc, uint16_t fifo_dwords_avail)
+{
+	int retval;
+
+	if (hcd->core_if->dma_enable) {
+		if (hcd->core_if->dma_desc_enable) {
+			if (!hc->xfer_started
+			    || (hc->ep_type == DWC_OTG_EP_TYPE_ISOC)) {
+				dwc_otg_hcd_start_xfer_ddma(hcd, hc->qh);
+				hc->qh->ping_state = 0;
+			}
+		} else if (!hc->xfer_started) {
+			if (fiq_fsm_enable && hc->error_state) {
+				hcd->fiq_state->channel[hc->hc_num].nr_errors =
+					DWC_CIRCLEQ_FIRST(&hc->qh->qtd_list)->error_count;
+				hcd->fiq_state->channel[hc->hc_num].fsm =
+					FIQ_PASSTHROUGH_ERRORSTATE;
+			}
+			dwc_otg_hc_start_transfer(hcd->core_if, hc);
+			hc->qh->ping_state = 0;
+		}
+		retval = 0;
+	} else if (hc->halt_pending) {
+		/* Don't queue a request if the channel has been halted. */
+		retval = 0;
+	} else if (hc->halt_on_queue) {
+		dwc_otg_hc_halt(hcd->core_if, hc, hc->halt_status);
+		retval = 0;
+	} else if (hc->do_ping) {
+		if (!hc->xfer_started) {
+			dwc_otg_hc_start_transfer(hcd->core_if, hc);
+		}
+		retval = 0;
+	} else if (!hc->ep_is_in || hc->data_pid_start == DWC_OTG_HC_PID_SETUP) {
+		if ((fifo_dwords_avail * 4) >= hc->max_packet) {
+			if (!hc->xfer_started) {
+				dwc_otg_hc_start_transfer(hcd->core_if, hc);
+				retval = 1;
+			} else {
+				retval =
+				    dwc_otg_hc_continue_transfer(hcd->core_if,
+								 hc);
+			}
+		} else {
+			retval = -1;
+		}
+	} else {
+		if (!hc->xfer_started) {
+			dwc_otg_hc_start_transfer(hcd->core_if, hc);
+			retval = 1;
+		} else {
+			retval = dwc_otg_hc_continue_transfer(hcd->core_if, hc);
+		}
+	}
+
+	return retval;
+}
+
+/**
+ * Processes periodic channels for the next frame and queues transactions for
+ * these channels to the DWC_otg controller. After queueing transactions, the
+ * Periodic Tx FIFO Empty interrupt is enabled if there are more transactions
+ * to queue as Periodic Tx FIFO or request queue space becomes available.
+ * Otherwise, the Periodic Tx FIFO Empty interrupt is disabled.
+ */
+static void process_periodic_channels(dwc_otg_hcd_t * hcd)
+{
+	hptxsts_data_t tx_status;
+	dwc_list_link_t *qh_ptr;
+	dwc_otg_qh_t *qh;
+	int status = 0;
+	int no_queue_space = 0;
+	int no_fifo_space = 0;
+
+	dwc_otg_host_global_regs_t *host_regs;
+	host_regs = hcd->core_if->host_if->host_global_regs;
+
+	DWC_DEBUGPL(DBG_HCDV, "Queue periodic transactions\n");
+#ifdef DEBUG
+	tx_status.d32 = DWC_READ_REG32(&host_regs->hptxsts);
+	DWC_DEBUGPL(DBG_HCDV,
+		    "  P Tx Req Queue Space Avail (before queue): %d\n",
+		    tx_status.b.ptxqspcavail);
+	DWC_DEBUGPL(DBG_HCDV, "  P Tx FIFO Space Avail (before queue): %d\n",
+		    tx_status.b.ptxfspcavail);
+#endif
+
+	qh_ptr = hcd->periodic_sched_assigned.next;
+	while (qh_ptr != &hcd->periodic_sched_assigned) {
+		tx_status.d32 = DWC_READ_REG32(&host_regs->hptxsts);
+		if (tx_status.b.ptxqspcavail == 0) {
+			no_queue_space = 1;
+			break;
+		}
+
+		qh = DWC_LIST_ENTRY(qh_ptr, dwc_otg_qh_t, qh_list_entry);
+
+		// Do not send a split start transaction any later than frame .6
+		// Note, we have to schedule a periodic in .5 to make it go in .6
+		if(fiq_fsm_enable && qh->do_split && ((dwc_otg_hcd_get_frame_number(hcd) + 1) & 7) > 6)
+		{
+			qh_ptr = qh_ptr->next;
+			hcd->fiq_state->next_sched_frame = dwc_otg_hcd_get_frame_number(hcd) | 7;
+			continue;
+		}
+
+		if (fiq_fsm_enable && fiq_fsm_transaction_suitable(hcd, qh)) {
+			if (qh->do_split)
+				fiq_fsm_queue_split_transaction(hcd, qh);
+			else
+				fiq_fsm_queue_isoc_transaction(hcd, qh);
+		} else {
+
+			/*
+			 * Set a flag if we're queueing high-bandwidth in slave mode.
+			 * The flag prevents any halts to get into the request queue in
+			 * the middle of multiple high-bandwidth packets getting queued.
+			 */
+			if (!hcd->core_if->dma_enable && qh->channel->multi_count > 1) {
+				hcd->core_if->queuing_high_bandwidth = 1;
+			}
+			status = queue_transaction(hcd, qh->channel,
+							tx_status.b.ptxfspcavail);
+			if (status < 0) {
+				no_fifo_space = 1;
+				break;
+			}
+		}
+
+		/*
+		 * In Slave mode, stay on the current transfer until there is
+		 * nothing more to do or the high-bandwidth request count is
+		 * reached. In DMA mode, only need to queue one request. The
+		 * controller automatically handles multiple packets for
+		 * high-bandwidth transfers.
+		 */
+		if (hcd->core_if->dma_enable || status == 0 ||
+		    qh->channel->requests == qh->channel->multi_count) {
+			qh_ptr = qh_ptr->next;
+			/*
+			 * Move the QH from the periodic assigned schedule to
+			 * the periodic queued schedule.
+			 */
+			DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_queued,
+					   &qh->qh_list_entry);
+
+			/* done queuing high bandwidth */
+			hcd->core_if->queuing_high_bandwidth = 0;
+		}
+	}
+
+	if (!hcd->core_if->dma_enable) {
+		dwc_otg_core_global_regs_t *global_regs;
+		gintmsk_data_t intr_mask = {.d32 = 0 };
+
+		global_regs = hcd->core_if->core_global_regs;
+		intr_mask.b.ptxfempty = 1;
+#ifdef DEBUG
+		tx_status.d32 = DWC_READ_REG32(&host_regs->hptxsts);
+		DWC_DEBUGPL(DBG_HCDV,
+			    "  P Tx Req Queue Space Avail (after queue): %d\n",
+			    tx_status.b.ptxqspcavail);
+		DWC_DEBUGPL(DBG_HCDV,
+			    "  P Tx FIFO Space Avail (after queue): %d\n",
+			    tx_status.b.ptxfspcavail);
+#endif
+		if (!DWC_LIST_EMPTY(&hcd->periodic_sched_assigned) ||
+		    no_queue_space || no_fifo_space) {
+			/*
+			 * May need to queue more transactions as the request
+			 * queue or Tx FIFO empties. Enable the periodic Tx
+			 * FIFO empty interrupt. (Always use the half-empty
+			 * level to ensure that new requests are loaded as
+			 * soon as possible.)
+			 */
+			DWC_MODIFY_REG32(&global_regs->gintmsk, 0,
+					 intr_mask.d32);
+		} else {
+			/*
+			 * Disable the Tx FIFO empty interrupt since there are
+			 * no more transactions that need to be queued right
+			 * now. This function is called from interrupt
+			 * handlers to queue more transactions as transfer
+			 * states change.
+			 */
+			DWC_MODIFY_REG32(&global_regs->gintmsk, intr_mask.d32,
+					 0);
+		}
+	}
+}
+
+/**
+ * Processes active non-periodic channels and queues transactions for these
+ * channels to the DWC_otg controller. After queueing transactions, the NP Tx
+ * FIFO Empty interrupt is enabled if there are more transactions to queue as
+ * NP Tx FIFO or request queue space becomes available. Otherwise, the NP Tx
+ * FIFO Empty interrupt is disabled.
+ */
+static void process_non_periodic_channels(dwc_otg_hcd_t * hcd)
+{
+	gnptxsts_data_t tx_status;
+	dwc_list_link_t *orig_qh_ptr;
+	dwc_otg_qh_t *qh;
+	int status;
+	int no_queue_space = 0;
+	int no_fifo_space = 0;
+	int more_to_do = 0;
+
+	dwc_otg_core_global_regs_t *global_regs =
+	    hcd->core_if->core_global_regs;
+
+	DWC_DEBUGPL(DBG_HCDV, "Queue non-periodic transactions\n");
+#ifdef DEBUG
+	tx_status.d32 = DWC_READ_REG32(&global_regs->gnptxsts);
+	DWC_DEBUGPL(DBG_HCDV,
+		    "  NP Tx Req Queue Space Avail (before queue): %d\n",
+		    tx_status.b.nptxqspcavail);
+	DWC_DEBUGPL(DBG_HCDV, "  NP Tx FIFO Space Avail (before queue): %d\n",
+		    tx_status.b.nptxfspcavail);
+#endif
+	/*
+	 * Keep track of the starting point. Skip over the start-of-list
+	 * entry.
+	 */
+	if (hcd->non_periodic_qh_ptr == &hcd->non_periodic_sched_active) {
+		hcd->non_periodic_qh_ptr = hcd->non_periodic_qh_ptr->next;
+	}
+	orig_qh_ptr = hcd->non_periodic_qh_ptr;
+
+	/*
+	 * Process once through the active list or until no more space is
+	 * available in the request queue or the Tx FIFO.
+	 */
+	do {
+		tx_status.d32 = DWC_READ_REG32(&global_regs->gnptxsts);
+		if (!hcd->core_if->dma_enable && tx_status.b.nptxqspcavail == 0) {
+			no_queue_space = 1;
+			break;
+		}
+
+		qh = DWC_LIST_ENTRY(hcd->non_periodic_qh_ptr, dwc_otg_qh_t,
+				    qh_list_entry);
+
+		if(fiq_fsm_enable && fiq_fsm_transaction_suitable(hcd, qh)) {
+			fiq_fsm_queue_split_transaction(hcd, qh);
+		} else {
+			status = queue_transaction(hcd, qh->channel,
+						tx_status.b.nptxfspcavail);
+
+			if (status > 0) {
+				more_to_do = 1;
+			} else if (status < 0) {
+				no_fifo_space = 1;
+				break;
+			}
+		}
+		/* Advance to next QH, skipping start-of-list entry. */
+		hcd->non_periodic_qh_ptr = hcd->non_periodic_qh_ptr->next;
+		if (hcd->non_periodic_qh_ptr == &hcd->non_periodic_sched_active) {
+			hcd->non_periodic_qh_ptr =
+			    hcd->non_periodic_qh_ptr->next;
+		}
+
+	} while (hcd->non_periodic_qh_ptr != orig_qh_ptr);
+
+	if (!hcd->core_if->dma_enable) {
+		gintmsk_data_t intr_mask = {.d32 = 0 };
+		intr_mask.b.nptxfempty = 1;
+
+#ifdef DEBUG
+		tx_status.d32 = DWC_READ_REG32(&global_regs->gnptxsts);
+		DWC_DEBUGPL(DBG_HCDV,
+			    "  NP Tx Req Queue Space Avail (after queue): %d\n",
+			    tx_status.b.nptxqspcavail);
+		DWC_DEBUGPL(DBG_HCDV,
+			    "  NP Tx FIFO Space Avail (after queue): %d\n",
+			    tx_status.b.nptxfspcavail);
+#endif
+		if (more_to_do || no_queue_space || no_fifo_space) {
+			/*
+			 * May need to queue more transactions as the request
+			 * queue or Tx FIFO empties. Enable the non-periodic
+			 * Tx FIFO empty interrupt. (Always use the half-empty
+			 * level to ensure that new requests are loaded as
+			 * soon as possible.)
+			 */
+			DWC_MODIFY_REG32(&global_regs->gintmsk, 0,
+					 intr_mask.d32);
+		} else {
+			/*
+			 * Disable the Tx FIFO empty interrupt since there are
+			 * no more transactions that need to be queued right
+			 * now. This function is called from interrupt
+			 * handlers to queue more transactions as transfer
+			 * states change.
+			 */
+			DWC_MODIFY_REG32(&global_regs->gintmsk, intr_mask.d32,
+					 0);
+		}
+	}
+}
+
+/**
+ * This function processes the currently active host channels and queues
+ * transactions for these channels to the DWC_otg controller. It is called
+ * from HCD interrupt handler functions.
+ *
+ * @param hcd The HCD state structure.
+ * @param tr_type The type(s) of transactions to queue (non-periodic,
+ * periodic, or both).
+ */
+void dwc_otg_hcd_queue_transactions(dwc_otg_hcd_t * hcd,
+				    dwc_otg_transaction_type_e tr_type)
+{
+#ifdef DEBUG_SOF
+	DWC_DEBUGPL(DBG_HCD, "Queue Transactions\n");
+#endif
+	/* Process host channels associated with periodic transfers. */
+	if ((tr_type == DWC_OTG_TRANSACTION_PERIODIC ||
+	     tr_type == DWC_OTG_TRANSACTION_ALL) &&
+	    !DWC_LIST_EMPTY(&hcd->periodic_sched_assigned)) {
+
+		process_periodic_channels(hcd);
+	}
+
+	/* Process host channels associated with non-periodic transfers. */
+	if (tr_type == DWC_OTG_TRANSACTION_NON_PERIODIC ||
+	    tr_type == DWC_OTG_TRANSACTION_ALL) {
+		if (!DWC_LIST_EMPTY(&hcd->non_periodic_sched_active)) {
+			process_non_periodic_channels(hcd);
+		} else {
+			/*
+			 * Ensure NP Tx FIFO empty interrupt is disabled when
+			 * there are no non-periodic transfers to process.
+			 */
+			gintmsk_data_t gintmsk = {.d32 = 0 };
+			gintmsk.b.nptxfempty = 1;
+
+			if (fiq_enable) {
+				local_fiq_disable();
+				fiq_fsm_spin_lock(&hcd->fiq_state->lock);
+				DWC_MODIFY_REG32(&hcd->core_if->core_global_regs->gintmsk, gintmsk.d32, 0);
+				fiq_fsm_spin_unlock(&hcd->fiq_state->lock);
+				local_fiq_enable();
+			} else {
+				DWC_MODIFY_REG32(&hcd->core_if->core_global_regs->gintmsk, gintmsk.d32, 0);
+			}
+		}
+	}
+}
+
+#ifdef DWC_HS_ELECT_TST
+/*
+ * Quick and dirty hack to implement the HS Electrical Test
+ * SINGLE_STEP_GET_DEVICE_DESCRIPTOR feature.
+ *
+ * This code was copied from our userspace app "hset". It sends a
+ * Get Device Descriptor control sequence in two parts, first the
+ * Setup packet by itself, followed some time later by the In and
+ * Ack packets. Rather than trying to figure out how to add this
+ * functionality to the normal driver code, we just hijack the
+ * hardware, using these two function to drive the hardware
+ * directly.
+ */
+
+static dwc_otg_core_global_regs_t *global_regs;
+static dwc_otg_host_global_regs_t *hc_global_regs;
+static dwc_otg_hc_regs_t *hc_regs;
+static uint32_t *data_fifo;
+
+static void do_setup(void)
+{
+	gintsts_data_t gintsts;
+	hctsiz_data_t hctsiz;
+	hcchar_data_t hcchar;
+	haint_data_t haint;
+	hcint_data_t hcint;
+
+	/* Enable HAINTs */
+	DWC_WRITE_REG32(&hc_global_regs->haintmsk, 0x0001);
+
+	/* Enable HCINTs */
+	DWC_WRITE_REG32(&hc_regs->hcintmsk, 0x04a3);
+
+	/* Read GINTSTS */
+	gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+	/* Read HAINT */
+	haint.d32 = DWC_READ_REG32(&hc_global_regs->haint);
+
+	/* Read HCINT */
+	hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+
+	/* Read HCCHAR */
+	hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+
+	/* Clear HCINT */
+	DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32);
+
+	/* Clear HAINT */
+	DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32);
+
+	/* Clear GINTSTS */
+	DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+	/* Read GINTSTS */
+	gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+	/*
+	 * Send Setup packet (Get Device Descriptor)
+	 */
+
+	/* Make sure channel is disabled */
+	hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+	if (hcchar.b.chen) {
+		hcchar.b.chdis = 1;
+//              hcchar.b.chen = 1;
+		DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32);
+		//sleep(1);
+		dwc_mdelay(1000);
+
+		/* Read GINTSTS */
+		gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+		/* Read HAINT */
+		haint.d32 = DWC_READ_REG32(&hc_global_regs->haint);
+
+		/* Read HCINT */
+		hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+
+		/* Read HCCHAR */
+		hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+
+		/* Clear HCINT */
+		DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32);
+
+		/* Clear HAINT */
+		DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32);
+
+		/* Clear GINTSTS */
+		DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+		hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+	}
+
+	/* Set HCTSIZ */
+	hctsiz.d32 = 0;
+	hctsiz.b.xfersize = 8;
+	hctsiz.b.pktcnt = 1;
+	hctsiz.b.pid = DWC_OTG_HC_PID_SETUP;
+	DWC_WRITE_REG32(&hc_regs->hctsiz, hctsiz.d32);
+
+	/* Set HCCHAR */
+	hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+	hcchar.b.eptype = DWC_OTG_EP_TYPE_CONTROL;
+	hcchar.b.epdir = 0;
+	hcchar.b.epnum = 0;
+	hcchar.b.mps = 8;
+	hcchar.b.chen = 1;
+	DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32);
+
+	/* Fill FIFO with Setup data for Get Device Descriptor */
+	data_fifo = (uint32_t *) ((char *)global_regs + 0x1000);
+	DWC_WRITE_REG32(data_fifo++, 0x01000680);
+	DWC_WRITE_REG32(data_fifo++, 0x00080000);
+
+	gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+	/* Wait for host channel interrupt */
+	do {
+		gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+	} while (gintsts.b.hcintr == 0);
+
+	/* Disable HCINTs */
+	DWC_WRITE_REG32(&hc_regs->hcintmsk, 0x0000);
+
+	/* Disable HAINTs */
+	DWC_WRITE_REG32(&hc_global_regs->haintmsk, 0x0000);
+
+	/* Read HAINT */
+	haint.d32 = DWC_READ_REG32(&hc_global_regs->haint);
+
+	/* Read HCINT */
+	hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+
+	/* Read HCCHAR */
+	hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+
+	/* Clear HCINT */
+	DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32);
+
+	/* Clear HAINT */
+	DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32);
+
+	/* Clear GINTSTS */
+	DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+	/* Read GINTSTS */
+	gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+}
+
+static void do_in_ack(void)
+{
+	gintsts_data_t gintsts;
+	hctsiz_data_t hctsiz;
+	hcchar_data_t hcchar;
+	haint_data_t haint;
+	hcint_data_t hcint;
+	host_grxsts_data_t grxsts;
+
+	/* Enable HAINTs */
+	DWC_WRITE_REG32(&hc_global_regs->haintmsk, 0x0001);
+
+	/* Enable HCINTs */
+	DWC_WRITE_REG32(&hc_regs->hcintmsk, 0x04a3);
+
+	/* Read GINTSTS */
+	gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+	/* Read HAINT */
+	haint.d32 = DWC_READ_REG32(&hc_global_regs->haint);
+
+	/* Read HCINT */
+	hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+
+	/* Read HCCHAR */
+	hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+
+	/* Clear HCINT */
+	DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32);
+
+	/* Clear HAINT */
+	DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32);
+
+	/* Clear GINTSTS */
+	DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+	/* Read GINTSTS */
+	gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+	/*
+	 * Receive Control In packet
+	 */
+
+	/* Make sure channel is disabled */
+	hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+	if (hcchar.b.chen) {
+		hcchar.b.chdis = 1;
+		hcchar.b.chen = 1;
+		DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32);
+		//sleep(1);
+		dwc_mdelay(1000);
+
+		/* Read GINTSTS */
+		gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+		/* Read HAINT */
+		haint.d32 = DWC_READ_REG32(&hc_global_regs->haint);
+
+		/* Read HCINT */
+		hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+
+		/* Read HCCHAR */
+		hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+
+		/* Clear HCINT */
+		DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32);
+
+		/* Clear HAINT */
+		DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32);
+
+		/* Clear GINTSTS */
+		DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+		hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+	}
+
+	/* Set HCTSIZ */
+	hctsiz.d32 = 0;
+	hctsiz.b.xfersize = 8;
+	hctsiz.b.pktcnt = 1;
+	hctsiz.b.pid = DWC_OTG_HC_PID_DATA1;
+	DWC_WRITE_REG32(&hc_regs->hctsiz, hctsiz.d32);
+
+	/* Set HCCHAR */
+	hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+	hcchar.b.eptype = DWC_OTG_EP_TYPE_CONTROL;
+	hcchar.b.epdir = 1;
+	hcchar.b.epnum = 0;
+	hcchar.b.mps = 8;
+	hcchar.b.chen = 1;
+	DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32);
+
+	gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+	/* Wait for receive status queue interrupt */
+	do {
+		gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+	} while (gintsts.b.rxstsqlvl == 0);
+
+	/* Read RXSTS */
+	grxsts.d32 = DWC_READ_REG32(&global_regs->grxstsp);
+
+	/* Clear RXSTSQLVL in GINTSTS */
+	gintsts.d32 = 0;
+	gintsts.b.rxstsqlvl = 1;
+	DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+	switch (grxsts.b.pktsts) {
+	case DWC_GRXSTS_PKTSTS_IN:
+		/* Read the data into the host buffer */
+		if (grxsts.b.bcnt > 0) {
+			int i;
+			int word_count = (grxsts.b.bcnt + 3) / 4;
+
+			data_fifo = (uint32_t *) ((char *)global_regs + 0x1000);
+
+			for (i = 0; i < word_count; i++) {
+				(void)DWC_READ_REG32(data_fifo++);
+			}
+		}
+		break;
+
+	default:
+		break;
+	}
+
+	gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+	/* Wait for receive status queue interrupt */
+	do {
+		gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+	} while (gintsts.b.rxstsqlvl == 0);
+
+	/* Read RXSTS */
+	grxsts.d32 = DWC_READ_REG32(&global_regs->grxstsp);
+
+	/* Clear RXSTSQLVL in GINTSTS */
+	gintsts.d32 = 0;
+	gintsts.b.rxstsqlvl = 1;
+	DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+	switch (grxsts.b.pktsts) {
+	case DWC_GRXSTS_PKTSTS_IN_XFER_COMP:
+		break;
+
+	default:
+		break;
+	}
+
+	gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+	/* Wait for host channel interrupt */
+	do {
+		gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+	} while (gintsts.b.hcintr == 0);
+
+	/* Read HAINT */
+	haint.d32 = DWC_READ_REG32(&hc_global_regs->haint);
+
+	/* Read HCINT */
+	hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+
+	/* Read HCCHAR */
+	hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+
+	/* Clear HCINT */
+	DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32);
+
+	/* Clear HAINT */
+	DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32);
+
+	/* Clear GINTSTS */
+	DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+	/* Read GINTSTS */
+	gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+//      usleep(100000);
+//      mdelay(100);
+	dwc_mdelay(1);
+
+	/*
+	 * Send handshake packet
+	 */
+
+	/* Read HAINT */
+	haint.d32 = DWC_READ_REG32(&hc_global_regs->haint);
+
+	/* Read HCINT */
+	hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+
+	/* Read HCCHAR */
+	hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+
+	/* Clear HCINT */
+	DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32);
+
+	/* Clear HAINT */
+	DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32);
+
+	/* Clear GINTSTS */
+	DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+	/* Read GINTSTS */
+	gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+	/* Make sure channel is disabled */
+	hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+	if (hcchar.b.chen) {
+		hcchar.b.chdis = 1;
+		hcchar.b.chen = 1;
+		DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32);
+		//sleep(1);
+		dwc_mdelay(1000);
+
+		/* Read GINTSTS */
+		gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+		/* Read HAINT */
+		haint.d32 = DWC_READ_REG32(&hc_global_regs->haint);
+
+		/* Read HCINT */
+		hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+
+		/* Read HCCHAR */
+		hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+
+		/* Clear HCINT */
+		DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32);
+
+		/* Clear HAINT */
+		DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32);
+
+		/* Clear GINTSTS */
+		DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+		hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+	}
+
+	/* Set HCTSIZ */
+	hctsiz.d32 = 0;
+	hctsiz.b.xfersize = 0;
+	hctsiz.b.pktcnt = 1;
+	hctsiz.b.pid = DWC_OTG_HC_PID_DATA1;
+	DWC_WRITE_REG32(&hc_regs->hctsiz, hctsiz.d32);
+
+	/* Set HCCHAR */
+	hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+	hcchar.b.eptype = DWC_OTG_EP_TYPE_CONTROL;
+	hcchar.b.epdir = 0;
+	hcchar.b.epnum = 0;
+	hcchar.b.mps = 8;
+	hcchar.b.chen = 1;
+	DWC_WRITE_REG32(&hc_regs->hcchar, hcchar.d32);
+
+	gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+
+	/* Wait for host channel interrupt */
+	do {
+		gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+	} while (gintsts.b.hcintr == 0);
+
+	/* Disable HCINTs */
+	DWC_WRITE_REG32(&hc_regs->hcintmsk, 0x0000);
+
+	/* Disable HAINTs */
+	DWC_WRITE_REG32(&hc_global_regs->haintmsk, 0x0000);
+
+	/* Read HAINT */
+	haint.d32 = DWC_READ_REG32(&hc_global_regs->haint);
+
+	/* Read HCINT */
+	hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+
+	/* Read HCCHAR */
+	hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+
+	/* Clear HCINT */
+	DWC_WRITE_REG32(&hc_regs->hcint, hcint.d32);
+
+	/* Clear HAINT */
+	DWC_WRITE_REG32(&hc_global_regs->haint, haint.d32);
+
+	/* Clear GINTSTS */
+	DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+	/* Read GINTSTS */
+	gintsts.d32 = DWC_READ_REG32(&global_regs->gintsts);
+}
+#endif
+
+/** Handles hub class-specific requests. */
+int dwc_otg_hcd_hub_control(dwc_otg_hcd_t * dwc_otg_hcd,
+			    uint16_t typeReq,
+			    uint16_t wValue,
+			    uint16_t wIndex, uint8_t * buf, uint16_t wLength)
+{
+	int retval = 0;
+
+	dwc_otg_core_if_t *core_if = dwc_otg_hcd->core_if;
+	usb_hub_descriptor_t *hub_desc;
+	hprt0_data_t hprt0 = {.d32 = 0 };
+
+	uint32_t port_status;
+
+	switch (typeReq) {
+	case UCR_CLEAR_HUB_FEATURE:
+		DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+			    "ClearHubFeature 0x%x\n", wValue);
+		switch (wValue) {
+		case UHF_C_HUB_LOCAL_POWER:
+		case UHF_C_HUB_OVER_CURRENT:
+			/* Nothing required here */
+			break;
+		default:
+			retval = -DWC_E_INVALID;
+			DWC_ERROR("DWC OTG HCD - "
+				  "ClearHubFeature request %xh unknown\n",
+				  wValue);
+		}
+		break;
+	case UCR_CLEAR_PORT_FEATURE:
+#ifdef CONFIG_USB_DWC_OTG_LPM
+		if (wValue != UHF_PORT_L1)
+#endif
+			if (!wIndex || wIndex > 1)
+				goto error;
+
+		switch (wValue) {
+		case UHF_PORT_ENABLE:
+			DWC_DEBUGPL(DBG_ANY, "DWC OTG HCD HUB CONTROL - "
+				    "ClearPortFeature USB_PORT_FEAT_ENABLE\n");
+			hprt0.d32 = dwc_otg_read_hprt0(core_if);
+			hprt0.b.prtena = 1;
+			DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+			break;
+		case UHF_PORT_SUSPEND:
+			DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+				    "ClearPortFeature USB_PORT_FEAT_SUSPEND\n");
+
+			if (core_if->power_down == 2) {
+				dwc_otg_host_hibernation_restore(core_if, 0, 0);
+			} else {
+				DWC_WRITE_REG32(core_if->pcgcctl, 0);
+				dwc_mdelay(5);
+
+				hprt0.d32 = dwc_otg_read_hprt0(core_if);
+				hprt0.b.prtres = 1;
+				DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+				hprt0.b.prtsusp = 0;
+				/* Clear Resume bit */
+				dwc_mdelay(100);
+				hprt0.b.prtres = 0;
+				DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+			}
+			break;
+#ifdef CONFIG_USB_DWC_OTG_LPM
+		case UHF_PORT_L1:
+			{
+				pcgcctl_data_t pcgcctl = {.d32 = 0 };
+				glpmcfg_data_t lpmcfg = {.d32 = 0 };
+
+				lpmcfg.d32 =
+				    DWC_READ_REG32(&core_if->
+						   core_global_regs->glpmcfg);
+				lpmcfg.b.en_utmi_sleep = 0;
+				lpmcfg.b.hird_thres &= (~(1 << 4));
+				lpmcfg.b.prt_sleep_sts = 1;
+				DWC_WRITE_REG32(&core_if->
+						core_global_regs->glpmcfg,
+						lpmcfg.d32);
+
+				/* Clear Enbl_L1Gating bit. */
+				pcgcctl.b.enbl_sleep_gating = 1;
+				DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32,
+						 0);
+
+				dwc_mdelay(5);
+
+				hprt0.d32 = dwc_otg_read_hprt0(core_if);
+				hprt0.b.prtres = 1;
+				DWC_WRITE_REG32(core_if->host_if->hprt0,
+						hprt0.d32);
+				/* This bit will be cleared in wakeup interrupt handle */
+				break;
+			}
+#endif
+		case UHF_PORT_POWER:
+			DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+				    "ClearPortFeature USB_PORT_FEAT_POWER\n");
+			hprt0.d32 = dwc_otg_read_hprt0(core_if);
+			hprt0.b.prtpwr = 0;
+			DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+			break;
+		case UHF_PORT_INDICATOR:
+			DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+				    "ClearPortFeature USB_PORT_FEAT_INDICATOR\n");
+			/* Port inidicator not supported */
+			break;
+		case UHF_C_PORT_CONNECTION:
+			/* Clears drivers internal connect status change
+			 * flag */
+			DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+				    "ClearPortFeature USB_PORT_FEAT_C_CONNECTION\n");
+			dwc_otg_hcd->flags.b.port_connect_status_change = 0;
+			break;
+		case UHF_C_PORT_RESET:
+			/* Clears the driver's internal Port Reset Change
+			 * flag */
+			DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+				    "ClearPortFeature USB_PORT_FEAT_C_RESET\n");
+			dwc_otg_hcd->flags.b.port_reset_change = 0;
+			break;
+		case UHF_C_PORT_ENABLE:
+			/* Clears the driver's internal Port
+			 * Enable/Disable Change flag */
+			DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+				    "ClearPortFeature USB_PORT_FEAT_C_ENABLE\n");
+			dwc_otg_hcd->flags.b.port_enable_change = 0;
+			break;
+		case UHF_C_PORT_SUSPEND:
+			/* Clears the driver's internal Port Suspend
+			 * Change flag, which is set when resume signaling on
+			 * the host port is complete */
+			DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+				    "ClearPortFeature USB_PORT_FEAT_C_SUSPEND\n");
+			dwc_otg_hcd->flags.b.port_suspend_change = 0;
+			break;
+#ifdef CONFIG_USB_DWC_OTG_LPM
+		case UHF_C_PORT_L1:
+			dwc_otg_hcd->flags.b.port_l1_change = 0;
+			break;
+#endif
+		case UHF_C_PORT_OVER_CURRENT:
+			DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+				    "ClearPortFeature USB_PORT_FEAT_C_OVER_CURRENT\n");
+			dwc_otg_hcd->flags.b.port_over_current_change = 0;
+			break;
+		default:
+			retval = -DWC_E_INVALID;
+			DWC_ERROR("DWC OTG HCD - "
+				  "ClearPortFeature request %xh "
+				  "unknown or unsupported\n", wValue);
+		}
+		break;
+	case UCR_GET_HUB_DESCRIPTOR:
+		DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+			    "GetHubDescriptor\n");
+		hub_desc = (usb_hub_descriptor_t *) buf;
+		hub_desc->bDescLength = 9;
+		hub_desc->bDescriptorType = 0x29;
+		hub_desc->bNbrPorts = 1;
+		USETW(hub_desc->wHubCharacteristics, 0x08);
+		hub_desc->bPwrOn2PwrGood = 1;
+		hub_desc->bHubContrCurrent = 0;
+		hub_desc->DeviceRemovable[0] = 0;
+		hub_desc->DeviceRemovable[1] = 0xff;
+		break;
+	case UCR_GET_HUB_STATUS:
+		DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+			    "GetHubStatus\n");
+		DWC_MEMSET(buf, 0, 4);
+		break;
+	case UCR_GET_PORT_STATUS:
+		DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+			    "GetPortStatus wIndex = 0x%04x FLAGS=0x%08x\n",
+			    wIndex, dwc_otg_hcd->flags.d32);
+		if (!wIndex || wIndex > 1)
+			goto error;
+
+		port_status = 0;
+
+		if (dwc_otg_hcd->flags.b.port_connect_status_change)
+			port_status |= (1 << UHF_C_PORT_CONNECTION);
+
+		if (dwc_otg_hcd->flags.b.port_enable_change)
+			port_status |= (1 << UHF_C_PORT_ENABLE);
+
+		if (dwc_otg_hcd->flags.b.port_suspend_change)
+			port_status |= (1 << UHF_C_PORT_SUSPEND);
+
+		if (dwc_otg_hcd->flags.b.port_l1_change)
+			port_status |= (1 << UHF_C_PORT_L1);
+
+		if (dwc_otg_hcd->flags.b.port_reset_change) {
+			port_status |= (1 << UHF_C_PORT_RESET);
+		}
+
+		if (dwc_otg_hcd->flags.b.port_over_current_change) {
+			DWC_WARN("Overcurrent change detected\n");
+			port_status |= (1 << UHF_C_PORT_OVER_CURRENT);
+		}
+
+		if (!dwc_otg_hcd->flags.b.port_connect_status) {
+			/*
+			 * The port is disconnected, which means the core is
+			 * either in device mode or it soon will be. Just
+			 * return 0's for the remainder of the port status
+			 * since the port register can't be read if the core
+			 * is in device mode.
+			 */
+			*((__le32 *) buf) = dwc_cpu_to_le32(&port_status);
+			break;
+		}
+
+		hprt0.d32 = DWC_READ_REG32(core_if->host_if->hprt0);
+		DWC_DEBUGPL(DBG_HCDV, "  HPRT0: 0x%08x\n", hprt0.d32);
+
+		if (hprt0.b.prtconnsts)
+			port_status |= (1 << UHF_PORT_CONNECTION);
+
+		if (hprt0.b.prtena)
+			port_status |= (1 << UHF_PORT_ENABLE);
+
+		if (hprt0.b.prtsusp)
+			port_status |= (1 << UHF_PORT_SUSPEND);
+
+		if (hprt0.b.prtovrcurract)
+			port_status |= (1 << UHF_PORT_OVER_CURRENT);
+
+		if (hprt0.b.prtrst)
+			port_status |= (1 << UHF_PORT_RESET);
+
+		if (hprt0.b.prtpwr)
+			port_status |= (1 << UHF_PORT_POWER);
+
+		if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_HIGH_SPEED)
+			port_status |= (1 << UHF_PORT_HIGH_SPEED);
+		else if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_LOW_SPEED)
+			port_status |= (1 << UHF_PORT_LOW_SPEED);
+
+		if (hprt0.b.prttstctl)
+			port_status |= (1 << UHF_PORT_TEST);
+		if (dwc_otg_get_lpm_portsleepstatus(dwc_otg_hcd->core_if)) {
+			port_status |= (1 << UHF_PORT_L1);
+		}
+		/*
+		   For Synopsys HW emulation of Power down wkup_control asserts the
+		   hreset_n and prst_n on suspned. This causes the HPRT0 to be zero.
+		   We intentionally tell the software that port is in L2Suspend state.
+		   Only for STE.
+		*/
+		if ((core_if->power_down == 2)
+		    && (core_if->hibernation_suspend == 1)) {
+			port_status |= (1 << UHF_PORT_SUSPEND);
+		}
+		/* USB_PORT_FEAT_INDICATOR unsupported always 0 */
+
+		*((__le32 *) buf) = dwc_cpu_to_le32(&port_status);
+
+		break;
+	case UCR_SET_HUB_FEATURE:
+		DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+			    "SetHubFeature\n");
+		/* No HUB features supported */
+		break;
+	case UCR_SET_PORT_FEATURE:
+		if (wValue != UHF_PORT_TEST && (!wIndex || wIndex > 1))
+			goto error;
+
+		if (!dwc_otg_hcd->flags.b.port_connect_status) {
+			/*
+			 * The port is disconnected, which means the core is
+			 * either in device mode or it soon will be. Just
+			 * return without doing anything since the port
+			 * register can't be written if the core is in device
+			 * mode.
+			 */
+			break;
+		}
+
+		switch (wValue) {
+		case UHF_PORT_SUSPEND:
+			DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+				    "SetPortFeature - USB_PORT_FEAT_SUSPEND\n");
+			if (dwc_otg_hcd_otg_port(dwc_otg_hcd) != wIndex) {
+				goto error;
+			}
+			if (core_if->power_down == 2) {
+				int timeout = 300;
+				dwc_irqflags_t flags;
+				pcgcctl_data_t pcgcctl = {.d32 = 0 };
+				gpwrdn_data_t gpwrdn = {.d32 = 0 };
+				gusbcfg_data_t gusbcfg = {.d32 = 0 };
+#ifdef DWC_DEV_SRPCAP
+				int32_t otg_cap_param = core_if->core_params->otg_cap;
+#endif
+				DWC_PRINTF("Preparing for complete power-off\n");
+
+				/* Save registers before hibernation */
+				dwc_otg_save_global_regs(core_if);
+				dwc_otg_save_host_regs(core_if);
+
+				hprt0.d32 = dwc_otg_read_hprt0(core_if);
+				hprt0.b.prtsusp = 1;
+				hprt0.b.prtena = 0;
+				DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+				/* Spin hprt0.b.prtsusp to became 1 */
+				do {
+					hprt0.d32 = dwc_otg_read_hprt0(core_if);
+					if (hprt0.b.prtsusp) {
+						break;
+					}
+					dwc_mdelay(1);
+				} while (--timeout);
+				if (!timeout) {
+					DWC_WARN("Suspend wasn't genereted\n");
+				}
+				dwc_udelay(10);
+
+				/*
+				 * We need to disable interrupts to prevent servicing of any IRQ
+				 * during going to hibernation
+				 */
+				DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags);
+				core_if->lx_state = DWC_OTG_L2;
+#ifdef DWC_DEV_SRPCAP
+				hprt0.d32 = dwc_otg_read_hprt0(core_if);
+				hprt0.b.prtpwr = 0;
+				hprt0.b.prtena = 0;
+				DWC_WRITE_REG32(core_if->host_if->hprt0,
+						hprt0.d32);
+#endif
+				gusbcfg.d32 =
+				    DWC_READ_REG32(&core_if->core_global_regs->
+						   gusbcfg);
+				if (gusbcfg.b.ulpi_utmi_sel == 1) {
+					/* ULPI interface */
+					/* Suspend the Phy Clock */
+					pcgcctl.d32 = 0;
+					pcgcctl.b.stoppclk = 1;
+					DWC_MODIFY_REG32(core_if->pcgcctl, 0,
+							 pcgcctl.d32);
+					dwc_udelay(10);
+					gpwrdn.b.pmuactv = 1;
+					DWC_MODIFY_REG32(&core_if->
+							 core_global_regs->
+							 gpwrdn, 0, gpwrdn.d32);
+				} else {
+					/* UTMI+ Interface */
+					gpwrdn.b.pmuactv = 1;
+					DWC_MODIFY_REG32(&core_if->
+							 core_global_regs->
+							 gpwrdn, 0, gpwrdn.d32);
+					dwc_udelay(10);
+					pcgcctl.b.stoppclk = 1;
+					DWC_MODIFY_REG32(core_if->pcgcctl, 0, pcgcctl.d32);
+					dwc_udelay(10);
+				}
+#ifdef DWC_DEV_SRPCAP
+				gpwrdn.d32 = 0;
+				gpwrdn.b.dis_vbus = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->
+						 gpwrdn, 0, gpwrdn.d32);
+#endif
+				gpwrdn.d32 = 0;
+				gpwrdn.b.pmuintsel = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->
+						 gpwrdn, 0, gpwrdn.d32);
+				dwc_udelay(10);
+
+				gpwrdn.d32 = 0;
+#ifdef DWC_DEV_SRPCAP
+				gpwrdn.b.srp_det_msk = 1;
+#endif
+				gpwrdn.b.disconn_det_msk = 1;
+				gpwrdn.b.lnstchng_msk = 1;
+				gpwrdn.b.sts_chngint_msk = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->
+						 gpwrdn, 0, gpwrdn.d32);
+				dwc_udelay(10);
+
+				/* Enable Power Down Clamp and all interrupts in GPWRDN */
+				gpwrdn.d32 = 0;
+				gpwrdn.b.pwrdnclmp = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->
+						 gpwrdn, 0, gpwrdn.d32);
+				dwc_udelay(10);
+
+				/* Switch off VDD */
+				gpwrdn.d32 = 0;
+				gpwrdn.b.pwrdnswtch = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->
+						 gpwrdn, 0, gpwrdn.d32);
+
+#ifdef DWC_DEV_SRPCAP
+				if (otg_cap_param == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE)
+				{
+					core_if->pwron_timer_started = 1;
+					DWC_TIMER_SCHEDULE(core_if->pwron_timer, 6000 /* 6 secs */ );
+				}
+#endif
+				/* Save gpwrdn register for further usage if stschng interrupt */
+				core_if->gr_backup->gpwrdn_local =
+						DWC_READ_REG32(&core_if->core_global_regs->gpwrdn);
+
+				/* Set flag to indicate that we are in hibernation */
+				core_if->hibernation_suspend = 1;
+				DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock,flags);
+
+				DWC_PRINTF("Host hibernation completed\n");
+				// Exit from case statement
+				break;
+
+			}
+			if (dwc_otg_hcd_otg_port(dwc_otg_hcd) == wIndex &&
+			    dwc_otg_hcd->fops->get_b_hnp_enable(dwc_otg_hcd)) {
+				gotgctl_data_t gotgctl = {.d32 = 0 };
+				gotgctl.b.hstsethnpen = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->
+						 gotgctl, 0, gotgctl.d32);
+				core_if->op_state = A_SUSPEND;
+			}
+			hprt0.d32 = dwc_otg_read_hprt0(core_if);
+			hprt0.b.prtsusp = 1;
+			DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+			{
+				dwc_irqflags_t flags;
+				/* Update lx_state */
+				DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags);
+				core_if->lx_state = DWC_OTG_L2;
+				DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, flags);
+			}
+			/* Suspend the Phy Clock */
+			{
+				pcgcctl_data_t pcgcctl = {.d32 = 0 };
+				pcgcctl.b.stoppclk = 1;
+				DWC_MODIFY_REG32(core_if->pcgcctl, 0,
+						 pcgcctl.d32);
+				dwc_udelay(10);
+			}
+
+			/* For HNP the bus must be suspended for at least 200ms. */
+			if (dwc_otg_hcd->fops->get_b_hnp_enable(dwc_otg_hcd)) {
+				pcgcctl_data_t pcgcctl = {.d32 = 0 };
+				pcgcctl.b.stoppclk = 1;
+                DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, 0);
+				dwc_mdelay(200);
+			}
+
+			/** @todo - check how sw can wait for 1 sec to check asesvld??? */
+#if 0 //vahrama !!!!!!!!!!!!!!!!!!
+			if (core_if->adp_enable) {
+				gotgctl_data_t gotgctl = {.d32 = 0 };
+				gpwrdn_data_t gpwrdn;
+
+				while (gotgctl.b.asesvld == 1) {
+					gotgctl.d32 =
+					    DWC_READ_REG32(&core_if->
+							   core_global_regs->
+							   gotgctl);
+					dwc_mdelay(100);
+				}
+
+				/* Enable Power Down Logic */
+				gpwrdn.d32 = 0;
+				gpwrdn.b.pmuactv = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->
+						 gpwrdn, 0, gpwrdn.d32);
+
+				/* Unmask SRP detected interrupt from Power Down Logic */
+				gpwrdn.d32 = 0;
+				gpwrdn.b.srp_det_msk = 1;
+				DWC_MODIFY_REG32(&core_if->core_global_regs->
+						 gpwrdn, 0, gpwrdn.d32);
+
+				dwc_otg_adp_probe_start(core_if);
+			}
+#endif
+			break;
+		case UHF_PORT_POWER:
+			DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+				    "SetPortFeature - USB_PORT_FEAT_POWER\n");
+			hprt0.d32 = dwc_otg_read_hprt0(core_if);
+			hprt0.b.prtpwr = 1;
+			DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+			break;
+		case UHF_PORT_RESET:
+			if ((core_if->power_down == 2)
+			    && (core_if->hibernation_suspend == 1)) {
+				/* If we are going to exit from Hibernated
+				 * state via USB RESET.
+				 */
+				dwc_otg_host_hibernation_restore(core_if, 0, 1);
+			} else {
+				hprt0.d32 = dwc_otg_read_hprt0(core_if);
+
+				DWC_DEBUGPL(DBG_HCD,
+					    "DWC OTG HCD HUB CONTROL - "
+					    "SetPortFeature - USB_PORT_FEAT_RESET\n");
+				{
+					pcgcctl_data_t pcgcctl = {.d32 = 0 };
+					pcgcctl.b.enbl_sleep_gating = 1;
+					pcgcctl.b.stoppclk = 1;
+					DWC_MODIFY_REG32(core_if->pcgcctl, pcgcctl.d32, 0);
+					DWC_WRITE_REG32(core_if->pcgcctl, 0);
+				}
+#ifdef CONFIG_USB_DWC_OTG_LPM
+				{
+					glpmcfg_data_t lpmcfg;
+					lpmcfg.d32 =
+						DWC_READ_REG32(&core_if->core_global_regs->glpmcfg);
+					if (lpmcfg.b.prt_sleep_sts) {
+						lpmcfg.b.en_utmi_sleep = 0;
+						lpmcfg.b.hird_thres &= (~(1 << 4));
+						DWC_WRITE_REG32
+						    (&core_if->core_global_regs->glpmcfg,
+						     lpmcfg.d32);
+						dwc_mdelay(1);
+					}
+				}
+#endif
+				hprt0.d32 = dwc_otg_read_hprt0(core_if);
+				/* Clear suspend bit if resetting from suspended state. */
+				hprt0.b.prtsusp = 0;
+				/* When B-Host the Port reset bit is set in
+				 * the Start HCD Callback function, so that
+				 * the reset is started within 1ms of the HNP
+				 * success interrupt. */
+				if (!dwc_otg_hcd_is_b_host(dwc_otg_hcd)) {
+					hprt0.b.prtpwr = 1;
+					hprt0.b.prtrst = 1;
+					DWC_PRINTF("Indeed it is in host mode hprt0 = %08x\n",hprt0.d32);
+					DWC_WRITE_REG32(core_if->host_if->hprt0,
+							hprt0.d32);
+				}
+				/* Clear reset bit in 10ms (FS/LS) or 50ms (HS) */
+				dwc_mdelay(60);
+				hprt0.b.prtrst = 0;
+				DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+				core_if->lx_state = DWC_OTG_L0;	/* Now back to the on state */
+			}
+			break;
+#ifdef DWC_HS_ELECT_TST
+		case UHF_PORT_TEST:
+			{
+				uint32_t t;
+				gintmsk_data_t gintmsk;
+
+				t = (wIndex >> 8);	/* MSB wIndex USB */
+				DWC_DEBUGPL(DBG_HCD,
+					    "DWC OTG HCD HUB CONTROL - "
+					    "SetPortFeature - USB_PORT_FEAT_TEST %d\n",
+					    t);
+				DWC_WARN("USB_PORT_FEAT_TEST %d\n", t);
+				if (t < 6) {
+					hprt0.d32 = dwc_otg_read_hprt0(core_if);
+					hprt0.b.prttstctl = t;
+					DWC_WRITE_REG32(core_if->host_if->hprt0,
+							hprt0.d32);
+				} else {
+					/* Setup global vars with reg addresses (quick and
+					 * dirty hack, should be cleaned up)
+					 */
+					global_regs = core_if->core_global_regs;
+					hc_global_regs =
+					    core_if->host_if->host_global_regs;
+					hc_regs =
+					    (dwc_otg_hc_regs_t *) ((char *)
+								   global_regs +
+								   0x500);
+					data_fifo =
+					    (uint32_t *) ((char *)global_regs +
+							  0x1000);
+
+					if (t == 6) {	/* HS_HOST_PORT_SUSPEND_RESUME */
+						/* Save current interrupt mask */
+						gintmsk.d32 =
+						    DWC_READ_REG32
+						    (&global_regs->gintmsk);
+
+						/* Disable all interrupts while we muck with
+						 * the hardware directly
+						 */
+						DWC_WRITE_REG32(&global_regs->gintmsk, 0);
+
+						/* 15 second delay per the test spec */
+						dwc_mdelay(15000);
+
+						/* Drive suspend on the root port */
+						hprt0.d32 =
+						    dwc_otg_read_hprt0(core_if);
+						hprt0.b.prtsusp = 1;
+						hprt0.b.prtres = 0;
+						DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+
+						/* 15 second delay per the test spec */
+						dwc_mdelay(15000);
+
+						/* Drive resume on the root port */
+						hprt0.d32 =
+						    dwc_otg_read_hprt0(core_if);
+						hprt0.b.prtsusp = 0;
+						hprt0.b.prtres = 1;
+						DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+						dwc_mdelay(100);
+
+						/* Clear the resume bit */
+						hprt0.b.prtres = 0;
+						DWC_WRITE_REG32(core_if->host_if->hprt0, hprt0.d32);
+
+						/* Restore interrupts */
+						DWC_WRITE_REG32(&global_regs->gintmsk, gintmsk.d32);
+					} else if (t == 7) {	/* SINGLE_STEP_GET_DEVICE_DESCRIPTOR setup */
+						/* Save current interrupt mask */
+						gintmsk.d32 =
+						    DWC_READ_REG32
+						    (&global_regs->gintmsk);
+
+						/* Disable all interrupts while we muck with
+						 * the hardware directly
+						 */
+						DWC_WRITE_REG32(&global_regs->gintmsk, 0);
+
+						/* 15 second delay per the test spec */
+						dwc_mdelay(15000);
+
+						/* Send the Setup packet */
+						do_setup();
+
+						/* 15 second delay so nothing else happens for awhile */
+						dwc_mdelay(15000);
+
+						/* Restore interrupts */
+						DWC_WRITE_REG32(&global_regs->gintmsk, gintmsk.d32);
+					} else if (t == 8) {	/* SINGLE_STEP_GET_DEVICE_DESCRIPTOR execute */
+						/* Save current interrupt mask */
+						gintmsk.d32 =
+						    DWC_READ_REG32
+						    (&global_regs->gintmsk);
+
+						/* Disable all interrupts while we muck with
+						 * the hardware directly
+						 */
+						DWC_WRITE_REG32(&global_regs->gintmsk, 0);
+
+						/* Send the Setup packet */
+						do_setup();
+
+						/* 15 second delay so nothing else happens for awhile */
+						dwc_mdelay(15000);
+
+						/* Send the In and Ack packets */
+						do_in_ack();
+
+						/* 15 second delay so nothing else happens for awhile */
+						dwc_mdelay(15000);
+
+						/* Restore interrupts */
+						DWC_WRITE_REG32(&global_regs->gintmsk, gintmsk.d32);
+					}
+				}
+				break;
+			}
+#endif /* DWC_HS_ELECT_TST */
+
+		case UHF_PORT_INDICATOR:
+			DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB CONTROL - "
+				    "SetPortFeature - USB_PORT_FEAT_INDICATOR\n");
+			/* Not supported */
+			break;
+		default:
+			retval = -DWC_E_INVALID;
+			DWC_ERROR("DWC OTG HCD - "
+				  "SetPortFeature request %xh "
+				  "unknown or unsupported\n", wValue);
+			break;
+		}
+		break;
+#ifdef CONFIG_USB_DWC_OTG_LPM
+	case UCR_SET_AND_TEST_PORT_FEATURE:
+		if (wValue != UHF_PORT_L1) {
+			goto error;
+		}
+		{
+			int portnum, hird, devaddr, remwake;
+			glpmcfg_data_t lpmcfg;
+			uint32_t time_usecs;
+			gintsts_data_t gintsts;
+			gintmsk_data_t gintmsk;
+
+			if (!dwc_otg_get_param_lpm_enable(core_if)) {
+				goto error;
+			}
+			if (wValue != UHF_PORT_L1 || wLength != 1) {
+				goto error;
+			}
+			/* Check if the port currently is in SLEEP state */
+			lpmcfg.d32 =
+			    DWC_READ_REG32(&core_if->core_global_regs->glpmcfg);
+			if (lpmcfg.b.prt_sleep_sts) {
+				DWC_INFO("Port is already in sleep mode\n");
+				buf[0] = 0;	/* Return success */
+				break;
+			}
+
+			portnum = wIndex & 0xf;
+			hird = (wIndex >> 4) & 0xf;
+			devaddr = (wIndex >> 8) & 0x7f;
+			remwake = (wIndex >> 15);
+
+			if (portnum != 1) {
+				retval = -DWC_E_INVALID;
+				DWC_WARN
+				    ("Wrong port number(%d) in SetandTestPortFeature request\n",
+				     portnum);
+				break;
+			}
+
+			DWC_PRINTF
+			    ("SetandTestPortFeature request: portnum = %d, hird = %d, devaddr = %d, rewake = %d\n",
+			     portnum, hird, devaddr, remwake);
+			/* Disable LPM interrupt */
+			gintmsk.d32 = 0;
+			gintmsk.b.lpmtranrcvd = 1;
+			DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk,
+					 gintmsk.d32, 0);
+
+			if (dwc_otg_hcd_send_lpm
+			    (dwc_otg_hcd, devaddr, hird, remwake)) {
+				retval = -DWC_E_INVALID;
+				break;
+			}
+
+			time_usecs = 10 * (lpmcfg.b.retry_count + 1);
+			/* We will consider timeout if time_usecs microseconds pass,
+			 * and we don't receive LPM transaction status.
+			 * After receiving non-error responce(ACK/NYET/STALL) from device,
+			 *  core will set lpmtranrcvd bit.
+			 */
+			do {
+				gintsts.d32 =
+				    DWC_READ_REG32(&core_if->core_global_regs->gintsts);
+				if (gintsts.b.lpmtranrcvd) {
+					break;
+				}
+				dwc_udelay(1);
+			} while (--time_usecs);
+			/* lpm_int bit will be cleared in LPM interrupt handler */
+
+			/* Now fill status
+			 * 0x00 - Success
+			 * 0x10 - NYET
+			 * 0x11 - Timeout
+			 */
+			if (!gintsts.b.lpmtranrcvd) {
+				buf[0] = 0x3;	/* Completion code is Timeout */
+				dwc_otg_hcd_free_hc_from_lpm(dwc_otg_hcd);
+			} else {
+				lpmcfg.d32 =
+				    DWC_READ_REG32(&core_if->core_global_regs->glpmcfg);
+				if (lpmcfg.b.lpm_resp == 0x3) {
+					/* ACK responce from the device */
+					buf[0] = 0x00;	/* Success */
+				} else if (lpmcfg.b.lpm_resp == 0x2) {
+					/* NYET responce from the device */
+					buf[0] = 0x2;
+				} else {
+					/* Otherwise responce with Timeout */
+					buf[0] = 0x3;
+				}
+			}
+			DWC_PRINTF("Device responce to LPM trans is %x\n",
+				   lpmcfg.b.lpm_resp);
+			DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, 0,
+					 gintmsk.d32);
+
+			break;
+		}
+#endif /* CONFIG_USB_DWC_OTG_LPM */
+	default:
+error:
+		retval = -DWC_E_INVALID;
+		DWC_WARN("DWC OTG HCD - "
+			 "Unknown hub control request type or invalid typeReq: %xh wIndex: %xh wValue: %xh\n",
+			 typeReq, wIndex, wValue);
+		break;
+	}
+
+	return retval;
+}
+
+#ifdef CONFIG_USB_DWC_OTG_LPM
+/** Returns index of host channel to perform LPM transaction. */
+int dwc_otg_hcd_get_hc_for_lpm_tran(dwc_otg_hcd_t * hcd, uint8_t devaddr)
+{
+	dwc_otg_core_if_t *core_if = hcd->core_if;
+	dwc_hc_t *hc;
+	hcchar_data_t hcchar;
+	gintmsk_data_t gintmsk = {.d32 = 0 };
+
+	if (DWC_CIRCLEQ_EMPTY(&hcd->free_hc_list)) {
+		DWC_PRINTF("No free channel to select for LPM transaction\n");
+		return -1;
+	}
+
+	hc = DWC_CIRCLEQ_FIRST(&hcd->free_hc_list);
+
+	/* Mask host channel interrupts. */
+	gintmsk.b.hcintr = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, gintmsk.d32, 0);
+
+	/* Fill fields that core needs for LPM transaction */
+	hcchar.b.devaddr = devaddr;
+	hcchar.b.epnum = 0;
+	hcchar.b.eptype = DWC_OTG_EP_TYPE_CONTROL;
+	hcchar.b.mps = 64;
+	hcchar.b.lspddev = (hc->speed == DWC_OTG_EP_SPEED_LOW);
+	hcchar.b.epdir = 0;	/* OUT */
+	DWC_WRITE_REG32(&core_if->host_if->hc_regs[hc->hc_num]->hcchar,
+			hcchar.d32);
+
+	/* Remove the host channel from the free list. */
+	DWC_CIRCLEQ_REMOVE_INIT(&hcd->free_hc_list, hc, hc_list_entry);
+
+	DWC_PRINTF("hcnum = %d devaddr = %d\n", hc->hc_num, devaddr);
+
+	return hc->hc_num;
+}
+
+/** Release hc after performing LPM transaction */
+void dwc_otg_hcd_free_hc_from_lpm(dwc_otg_hcd_t * hcd)
+{
+	dwc_hc_t *hc;
+	glpmcfg_data_t lpmcfg;
+	uint8_t hc_num;
+
+	lpmcfg.d32 = DWC_READ_REG32(&hcd->core_if->core_global_regs->glpmcfg);
+	hc_num = lpmcfg.b.lpm_chan_index;
+
+	hc = hcd->hc_ptr_array[hc_num];
+
+	DWC_PRINTF("Freeing channel %d after LPM\n", hc_num);
+	/* Return host channel to free list */
+	DWC_CIRCLEQ_INSERT_TAIL(&hcd->free_hc_list, hc, hc_list_entry);
+}
+
+int dwc_otg_hcd_send_lpm(dwc_otg_hcd_t * hcd, uint8_t devaddr, uint8_t hird,
+			 uint8_t bRemoteWake)
+{
+	glpmcfg_data_t lpmcfg;
+	pcgcctl_data_t pcgcctl = {.d32 = 0 };
+	int channel;
+
+	channel = dwc_otg_hcd_get_hc_for_lpm_tran(hcd, devaddr);
+	if (channel < 0) {
+		return channel;
+	}
+
+	pcgcctl.b.enbl_sleep_gating = 1;
+	DWC_MODIFY_REG32(hcd->core_if->pcgcctl, 0, pcgcctl.d32);
+
+	/* Read LPM config register */
+	lpmcfg.d32 = DWC_READ_REG32(&hcd->core_if->core_global_regs->glpmcfg);
+
+	/* Program LPM transaction fields */
+	lpmcfg.b.rem_wkup_en = bRemoteWake;
+	lpmcfg.b.hird = hird;
+	lpmcfg.b.hird_thres = 0x1c;
+	lpmcfg.b.lpm_chan_index = channel;
+	lpmcfg.b.en_utmi_sleep = 1;
+	/* Program LPM config register */
+	DWC_WRITE_REG32(&hcd->core_if->core_global_regs->glpmcfg, lpmcfg.d32);
+
+	/* Send LPM transaction */
+	lpmcfg.b.send_lpm = 1;
+	DWC_WRITE_REG32(&hcd->core_if->core_global_regs->glpmcfg, lpmcfg.d32);
+
+	return 0;
+}
+
+#endif /* CONFIG_USB_DWC_OTG_LPM */
+
+int dwc_otg_hcd_is_status_changed(dwc_otg_hcd_t * hcd, int port)
+{
+	int retval;
+
+	if (port != 1) {
+		return -DWC_E_INVALID;
+	}
+
+	retval = (hcd->flags.b.port_connect_status_change ||
+		  hcd->flags.b.port_reset_change ||
+		  hcd->flags.b.port_enable_change ||
+		  hcd->flags.b.port_suspend_change ||
+		  hcd->flags.b.port_over_current_change);
+#ifdef DEBUG
+	if (retval) {
+		DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD HUB STATUS DATA:"
+			    " Root port status changed\n");
+		DWC_DEBUGPL(DBG_HCDV, "  port_connect_status_change: %d\n",
+			    hcd->flags.b.port_connect_status_change);
+		DWC_DEBUGPL(DBG_HCDV, "  port_reset_change: %d\n",
+			    hcd->flags.b.port_reset_change);
+		DWC_DEBUGPL(DBG_HCDV, "  port_enable_change: %d\n",
+			    hcd->flags.b.port_enable_change);
+		DWC_DEBUGPL(DBG_HCDV, "  port_suspend_change: %d\n",
+			    hcd->flags.b.port_suspend_change);
+		DWC_DEBUGPL(DBG_HCDV, "  port_over_current_change: %d\n",
+			    hcd->flags.b.port_over_current_change);
+	}
+#endif
+	return retval;
+}
+
+int dwc_otg_hcd_get_frame_number(dwc_otg_hcd_t * dwc_otg_hcd)
+{
+	hfnum_data_t hfnum;
+	hfnum.d32 =
+	    DWC_READ_REG32(&dwc_otg_hcd->core_if->host_if->host_global_regs->
+			   hfnum);
+
+#ifdef DEBUG_SOF
+	DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD GET FRAME NUMBER %d\n",
+		    hfnum.b.frnum);
+#endif
+	return hfnum.b.frnum;
+}
+
+int dwc_otg_hcd_start(dwc_otg_hcd_t * hcd,
+		      struct dwc_otg_hcd_function_ops *fops)
+{
+	int retval = 0;
+
+	hcd->fops = fops;
+	if (!dwc_otg_is_device_mode(hcd->core_if) &&
+		(!hcd->core_if->adp_enable || hcd->core_if->adp.adp_started)) {
+		dwc_otg_hcd_reinit(hcd);
+	} else {
+		retval = -DWC_E_NO_DEVICE;
+	}
+
+	return retval;
+}
+
+void *dwc_otg_hcd_get_priv_data(dwc_otg_hcd_t * hcd)
+{
+	return hcd->priv;
+}
+
+void dwc_otg_hcd_set_priv_data(dwc_otg_hcd_t * hcd, void *priv_data)
+{
+	hcd->priv = priv_data;
+}
+
+uint32_t dwc_otg_hcd_otg_port(dwc_otg_hcd_t * hcd)
+{
+	return hcd->otg_port;
+}
+
+uint32_t dwc_otg_hcd_is_b_host(dwc_otg_hcd_t * hcd)
+{
+	uint32_t is_b_host;
+	if (hcd->core_if->op_state == B_HOST) {
+		is_b_host = 1;
+	} else {
+		is_b_host = 0;
+	}
+
+	return is_b_host;
+}
+
+dwc_otg_hcd_urb_t *dwc_otg_hcd_urb_alloc(dwc_otg_hcd_t * hcd,
+					 int iso_desc_count, int atomic_alloc)
+{
+	dwc_otg_hcd_urb_t *dwc_otg_urb;
+	uint32_t size;
+
+	size =
+	    sizeof(*dwc_otg_urb) +
+	    iso_desc_count * sizeof(struct dwc_otg_hcd_iso_packet_desc);
+	if (atomic_alloc)
+		dwc_otg_urb = DWC_ALLOC_ATOMIC(size);
+	else
+		dwc_otg_urb = DWC_ALLOC(size);
+
+        if (dwc_otg_urb)
+		dwc_otg_urb->packet_count = iso_desc_count;
+        else {
+		DWC_ERROR("**** DWC OTG HCD URB alloc - "
+			"%salloc of %db failed\n",
+			atomic_alloc?"atomic ":"", size);
+	}
+	return dwc_otg_urb;
+}
+
+void dwc_otg_hcd_urb_set_pipeinfo(dwc_otg_hcd_urb_t * dwc_otg_urb,
+				  uint8_t dev_addr, uint8_t ep_num,
+				  uint8_t ep_type, uint8_t ep_dir, uint16_t mps)
+{
+	dwc_otg_hcd_fill_pipe(&dwc_otg_urb->pipe_info, dev_addr, ep_num,
+			      ep_type, ep_dir, mps);
+#if 0
+	DWC_PRINTF
+	    ("addr = %d, ep_num = %d, ep_dir = 0x%x, ep_type = 0x%x, mps = %d\n",
+	     dev_addr, ep_num, ep_dir, ep_type, mps);
+#endif
+}
+
+void dwc_otg_hcd_urb_set_params(dwc_otg_hcd_urb_t * dwc_otg_urb,
+				void *urb_handle, void *buf, dwc_dma_t dma,
+				uint32_t buflen, void *setup_packet,
+				dwc_dma_t setup_dma, uint32_t flags,
+				uint16_t interval)
+{
+	dwc_otg_urb->priv = urb_handle;
+	dwc_otg_urb->buf = buf;
+	dwc_otg_urb->dma = dma;
+	dwc_otg_urb->length = buflen;
+	dwc_otg_urb->setup_packet = setup_packet;
+	dwc_otg_urb->setup_dma = setup_dma;
+	dwc_otg_urb->flags = flags;
+	dwc_otg_urb->interval = interval;
+	dwc_otg_urb->status = -DWC_E_IN_PROGRESS;
+}
+
+uint32_t dwc_otg_hcd_urb_get_status(dwc_otg_hcd_urb_t * dwc_otg_urb)
+{
+	return dwc_otg_urb->status;
+}
+
+uint32_t dwc_otg_hcd_urb_get_actual_length(dwc_otg_hcd_urb_t * dwc_otg_urb)
+{
+	return dwc_otg_urb->actual_length;
+}
+
+uint32_t dwc_otg_hcd_urb_get_error_count(dwc_otg_hcd_urb_t * dwc_otg_urb)
+{
+	return dwc_otg_urb->error_count;
+}
+
+void dwc_otg_hcd_urb_set_iso_desc_params(dwc_otg_hcd_urb_t * dwc_otg_urb,
+					 int desc_num, uint32_t offset,
+					 uint32_t length)
+{
+	dwc_otg_urb->iso_descs[desc_num].offset = offset;
+	dwc_otg_urb->iso_descs[desc_num].length = length;
+}
+
+uint32_t dwc_otg_hcd_urb_get_iso_desc_status(dwc_otg_hcd_urb_t * dwc_otg_urb,
+					     int desc_num)
+{
+	return dwc_otg_urb->iso_descs[desc_num].status;
+}
+
+uint32_t dwc_otg_hcd_urb_get_iso_desc_actual_length(dwc_otg_hcd_urb_t *
+						    dwc_otg_urb, int desc_num)
+{
+	return dwc_otg_urb->iso_descs[desc_num].actual_length;
+}
+
+int dwc_otg_hcd_is_bandwidth_allocated(dwc_otg_hcd_t * hcd, void *ep_handle)
+{
+	int allocated = 0;
+	dwc_otg_qh_t *qh = (dwc_otg_qh_t *) ep_handle;
+
+	if (qh) {
+		if (!DWC_LIST_EMPTY(&qh->qh_list_entry)) {
+			allocated = 1;
+		}
+	}
+	return allocated;
+}
+
+int dwc_otg_hcd_is_bandwidth_freed(dwc_otg_hcd_t * hcd, void *ep_handle)
+{
+	dwc_otg_qh_t *qh = (dwc_otg_qh_t *) ep_handle;
+	int freed = 0;
+	DWC_ASSERT(qh, "qh is not allocated\n");
+
+	if (DWC_LIST_EMPTY(&qh->qh_list_entry)) {
+		freed = 1;
+	}
+
+	return freed;
+}
+
+uint8_t dwc_otg_hcd_get_ep_bandwidth(dwc_otg_hcd_t * hcd, void *ep_handle)
+{
+	dwc_otg_qh_t *qh = (dwc_otg_qh_t *) ep_handle;
+	DWC_ASSERT(qh, "qh is not allocated\n");
+	return qh->usecs;
+}
+
+void dwc_otg_hcd_dump_state(dwc_otg_hcd_t * hcd)
+{
+#ifdef DEBUG
+	int num_channels;
+	int i;
+	gnptxsts_data_t np_tx_status;
+	hptxsts_data_t p_tx_status;
+
+	num_channels = hcd->core_if->core_params->host_channels;
+	DWC_PRINTF("\n");
+	DWC_PRINTF
+	    ("************************************************************\n");
+	DWC_PRINTF("HCD State:\n");
+	DWC_PRINTF("  Num channels: %d\n", num_channels);
+	for (i = 0; i < num_channels; i++) {
+		dwc_hc_t *hc = hcd->hc_ptr_array[i];
+		DWC_PRINTF("  Channel %d:\n", i);
+		DWC_PRINTF("    dev_addr: %d, ep_num: %d, ep_is_in: %d\n",
+			   hc->dev_addr, hc->ep_num, hc->ep_is_in);
+		DWC_PRINTF("    speed: %d\n", hc->speed);
+		DWC_PRINTF("    ep_type: %d\n", hc->ep_type);
+		DWC_PRINTF("    max_packet: %d\n", hc->max_packet);
+		DWC_PRINTF("    data_pid_start: %d\n", hc->data_pid_start);
+		DWC_PRINTF("    multi_count: %d\n", hc->multi_count);
+		DWC_PRINTF("    xfer_started: %d\n", hc->xfer_started);
+		DWC_PRINTF("    xfer_buff: %p\n", hc->xfer_buff);
+		DWC_PRINTF("    xfer_len: %d\n", hc->xfer_len);
+		DWC_PRINTF("    xfer_count: %d\n", hc->xfer_count);
+		DWC_PRINTF("    halt_on_queue: %d\n", hc->halt_on_queue);
+		DWC_PRINTF("    halt_pending: %d\n", hc->halt_pending);
+		DWC_PRINTF("    halt_status: %d\n", hc->halt_status);
+		DWC_PRINTF("    do_split: %d\n", hc->do_split);
+		DWC_PRINTF("    complete_split: %d\n", hc->complete_split);
+		DWC_PRINTF("    hub_addr: %d\n", hc->hub_addr);
+		DWC_PRINTF("    port_addr: %d\n", hc->port_addr);
+		DWC_PRINTF("    xact_pos: %d\n", hc->xact_pos);
+		DWC_PRINTF("    requests: %d\n", hc->requests);
+		DWC_PRINTF("    qh: %p\n", hc->qh);
+		if (hc->xfer_started) {
+			hfnum_data_t hfnum;
+			hcchar_data_t hcchar;
+			hctsiz_data_t hctsiz;
+			hcint_data_t hcint;
+			hcintmsk_data_t hcintmsk;
+			hfnum.d32 =
+			    DWC_READ_REG32(&hcd->core_if->
+					   host_if->host_global_regs->hfnum);
+			hcchar.d32 =
+			    DWC_READ_REG32(&hcd->core_if->host_if->
+					   hc_regs[i]->hcchar);
+			hctsiz.d32 =
+			    DWC_READ_REG32(&hcd->core_if->host_if->
+					   hc_regs[i]->hctsiz);
+			hcint.d32 =
+			    DWC_READ_REG32(&hcd->core_if->host_if->
+					   hc_regs[i]->hcint);
+			hcintmsk.d32 =
+			    DWC_READ_REG32(&hcd->core_if->host_if->
+					   hc_regs[i]->hcintmsk);
+			DWC_PRINTF("    hfnum: 0x%08x\n", hfnum.d32);
+			DWC_PRINTF("    hcchar: 0x%08x\n", hcchar.d32);
+			DWC_PRINTF("    hctsiz: 0x%08x\n", hctsiz.d32);
+			DWC_PRINTF("    hcint: 0x%08x\n", hcint.d32);
+			DWC_PRINTF("    hcintmsk: 0x%08x\n", hcintmsk.d32);
+		}
+		if (hc->xfer_started && hc->qh) {
+			dwc_otg_qtd_t *qtd;
+			dwc_otg_hcd_urb_t *urb;
+
+			DWC_CIRCLEQ_FOREACH(qtd, &hc->qh->qtd_list, qtd_list_entry) {
+				if (!qtd->in_process)
+					break;
+
+				urb = qtd->urb;
+			DWC_PRINTF("    URB Info:\n");
+			DWC_PRINTF("      qtd: %p, urb: %p\n", qtd, urb);
+			if (urb) {
+				DWC_PRINTF("      Dev: %d, EP: %d %s\n",
+					   dwc_otg_hcd_get_dev_addr(&urb->
+								    pipe_info),
+					   dwc_otg_hcd_get_ep_num(&urb->
+								  pipe_info),
+					   dwc_otg_hcd_is_pipe_in(&urb->
+								  pipe_info) ?
+					   "IN" : "OUT");
+				DWC_PRINTF("      Max packet size: %d\n",
+					   dwc_otg_hcd_get_mps(&urb->
+							       pipe_info));
+				DWC_PRINTF("      transfer_buffer: %p\n",
+					   urb->buf);
+				DWC_PRINTF("      transfer_dma: %p\n",
+					   (void *)urb->dma);
+				DWC_PRINTF("      transfer_buffer_length: %d\n",
+					   urb->length);
+					DWC_PRINTF("      actual_length: %d\n",
+						   urb->actual_length);
+				}
+			}
+		}
+	}
+	DWC_PRINTF("  non_periodic_channels: %d\n", hcd->non_periodic_channels);
+	DWC_PRINTF("  periodic_channels: %d\n", hcd->periodic_channels);
+	DWC_PRINTF("  periodic_usecs: %d\n", hcd->periodic_usecs);
+	np_tx_status.d32 =
+	    DWC_READ_REG32(&hcd->core_if->core_global_regs->gnptxsts);
+	DWC_PRINTF("  NP Tx Req Queue Space Avail: %d\n",
+		   np_tx_status.b.nptxqspcavail);
+	DWC_PRINTF("  NP Tx FIFO Space Avail: %d\n",
+		   np_tx_status.b.nptxfspcavail);
+	p_tx_status.d32 =
+	    DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hptxsts);
+	DWC_PRINTF("  P Tx Req Queue Space Avail: %d\n",
+		   p_tx_status.b.ptxqspcavail);
+	DWC_PRINTF("  P Tx FIFO Space Avail: %d\n", p_tx_status.b.ptxfspcavail);
+	dwc_otg_hcd_dump_frrem(hcd);
+	dwc_otg_dump_global_registers(hcd->core_if);
+	dwc_otg_dump_host_registers(hcd->core_if);
+	DWC_PRINTF
+	    ("************************************************************\n");
+	DWC_PRINTF("\n");
+#endif
+}
+
+#ifdef DEBUG
+void dwc_print_setup_data(uint8_t * setup)
+{
+	int i;
+	if (CHK_DEBUG_LEVEL(DBG_HCD)) {
+		DWC_PRINTF("Setup Data = MSB ");
+		for (i = 7; i >= 0; i--)
+			DWC_PRINTF("%02x ", setup[i]);
+		DWC_PRINTF("\n");
+		DWC_PRINTF("  bmRequestType Tranfer = %s\n",
+			   (setup[0] & 0x80) ? "Device-to-Host" :
+			   "Host-to-Device");
+		DWC_PRINTF("  bmRequestType Type = ");
+		switch ((setup[0] & 0x60) >> 5) {
+		case 0:
+			DWC_PRINTF("Standard\n");
+			break;
+		case 1:
+			DWC_PRINTF("Class\n");
+			break;
+		case 2:
+			DWC_PRINTF("Vendor\n");
+			break;
+		case 3:
+			DWC_PRINTF("Reserved\n");
+			break;
+		}
+		DWC_PRINTF("  bmRequestType Recipient = ");
+		switch (setup[0] & 0x1f) {
+		case 0:
+			DWC_PRINTF("Device\n");
+			break;
+		case 1:
+			DWC_PRINTF("Interface\n");
+			break;
+		case 2:
+			DWC_PRINTF("Endpoint\n");
+			break;
+		case 3:
+			DWC_PRINTF("Other\n");
+			break;
+		default:
+			DWC_PRINTF("Reserved\n");
+			break;
+		}
+		DWC_PRINTF("  bRequest = 0x%0x\n", setup[1]);
+		DWC_PRINTF("  wValue = 0x%0x\n", *((uint16_t *) & setup[2]));
+		DWC_PRINTF("  wIndex = 0x%0x\n", *((uint16_t *) & setup[4]));
+		DWC_PRINTF("  wLength = 0x%0x\n\n", *((uint16_t *) & setup[6]));
+	}
+}
+#endif
+
+void dwc_otg_hcd_dump_frrem(dwc_otg_hcd_t * hcd)
+{
+#if 0
+	DWC_PRINTF("Frame remaining at SOF:\n");
+	DWC_PRINTF("  samples %u, accum %llu, avg %llu\n",
+		   hcd->frrem_samples, hcd->frrem_accum,
+		   (hcd->frrem_samples > 0) ?
+		   hcd->frrem_accum / hcd->frrem_samples : 0);
+
+	DWC_PRINTF("\n");
+	DWC_PRINTF("Frame remaining at start_transfer (uframe 7):\n");
+	DWC_PRINTF("  samples %u, accum %llu, avg %llu\n",
+		   hcd->core_if->hfnum_7_samples,
+		   hcd->core_if->hfnum_7_frrem_accum,
+		   (hcd->core_if->hfnum_7_samples >
+		    0) ? hcd->core_if->hfnum_7_frrem_accum /
+		   hcd->core_if->hfnum_7_samples : 0);
+	DWC_PRINTF("Frame remaining at start_transfer (uframe 0):\n");
+	DWC_PRINTF("  samples %u, accum %llu, avg %llu\n",
+		   hcd->core_if->hfnum_0_samples,
+		   hcd->core_if->hfnum_0_frrem_accum,
+		   (hcd->core_if->hfnum_0_samples >
+		    0) ? hcd->core_if->hfnum_0_frrem_accum /
+		   hcd->core_if->hfnum_0_samples : 0);
+	DWC_PRINTF("Frame remaining at start_transfer (uframe 1-6):\n");
+	DWC_PRINTF("  samples %u, accum %llu, avg %llu\n",
+		   hcd->core_if->hfnum_other_samples,
+		   hcd->core_if->hfnum_other_frrem_accum,
+		   (hcd->core_if->hfnum_other_samples >
+		    0) ? hcd->core_if->hfnum_other_frrem_accum /
+		   hcd->core_if->hfnum_other_samples : 0);
+
+	DWC_PRINTF("\n");
+	DWC_PRINTF("Frame remaining at sample point A (uframe 7):\n");
+	DWC_PRINTF("  samples %u, accum %llu, avg %llu\n",
+		   hcd->hfnum_7_samples_a, hcd->hfnum_7_frrem_accum_a,
+		   (hcd->hfnum_7_samples_a > 0) ?
+		   hcd->hfnum_7_frrem_accum_a / hcd->hfnum_7_samples_a : 0);
+	DWC_PRINTF("Frame remaining at sample point A (uframe 0):\n");
+	DWC_PRINTF("  samples %u, accum %llu, avg %llu\n",
+		   hcd->hfnum_0_samples_a, hcd->hfnum_0_frrem_accum_a,
+		   (hcd->hfnum_0_samples_a > 0) ?
+		   hcd->hfnum_0_frrem_accum_a / hcd->hfnum_0_samples_a : 0);
+	DWC_PRINTF("Frame remaining at sample point A (uframe 1-6):\n");
+	DWC_PRINTF("  samples %u, accum %llu, avg %llu\n",
+		   hcd->hfnum_other_samples_a, hcd->hfnum_other_frrem_accum_a,
+		   (hcd->hfnum_other_samples_a > 0) ?
+		   hcd->hfnum_other_frrem_accum_a /
+		   hcd->hfnum_other_samples_a : 0);
+
+	DWC_PRINTF("\n");
+	DWC_PRINTF("Frame remaining at sample point B (uframe 7):\n");
+	DWC_PRINTF("  samples %u, accum %llu, avg %llu\n",
+		   hcd->hfnum_7_samples_b, hcd->hfnum_7_frrem_accum_b,
+		   (hcd->hfnum_7_samples_b > 0) ?
+		   hcd->hfnum_7_frrem_accum_b / hcd->hfnum_7_samples_b : 0);
+	DWC_PRINTF("Frame remaining at sample point B (uframe 0):\n");
+	DWC_PRINTF("  samples %u, accum %llu, avg %llu\n",
+		   hcd->hfnum_0_samples_b, hcd->hfnum_0_frrem_accum_b,
+		   (hcd->hfnum_0_samples_b > 0) ?
+		   hcd->hfnum_0_frrem_accum_b / hcd->hfnum_0_samples_b : 0);
+	DWC_PRINTF("Frame remaining at sample point B (uframe 1-6):\n");
+	DWC_PRINTF("  samples %u, accum %llu, avg %llu\n",
+		   hcd->hfnum_other_samples_b, hcd->hfnum_other_frrem_accum_b,
+		   (hcd->hfnum_other_samples_b > 0) ?
+		   hcd->hfnum_other_frrem_accum_b /
+		   hcd->hfnum_other_samples_b : 0);
+#endif
+}
+
+#endif /* DWC_DEVICE_ONLY */
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd.h b/drivers/usb/host/dwc_otg/dwc_otg_hcd.h
new file mode 100644
index 000000000000..e0611c1592b1
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd.h
@@ -0,0 +1,870 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd.h $
+ * $Revision: #58 $
+ * $Date: 2011/09/15 $
+ * $Change: 1846647 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+#ifndef DWC_DEVICE_ONLY
+#ifndef __DWC_HCD_H__
+#define __DWC_HCD_H__
+
+#include "dwc_otg_os_dep.h"
+#include "usb.h"
+#include "dwc_otg_hcd_if.h"
+#include "dwc_otg_core_if.h"
+#include "dwc_list.h"
+#include "dwc_otg_cil.h"
+#include "dwc_otg_fiq_fsm.h"
+#include "dwc_otg_driver.h"
+
+
+/**
+ * @file
+ *
+ * This file contains the structures, constants, and interfaces for
+ * the Host Contoller Driver (HCD).
+ *
+ * The Host Controller Driver (HCD) is responsible for translating requests
+ * from the USB Driver into the appropriate actions on the DWC_otg controller.
+ * It isolates the USBD from the specifics of the controller by providing an
+ * API to the USBD.
+ */
+
+struct dwc_otg_hcd_pipe_info {
+	uint8_t dev_addr;
+	uint8_t ep_num;
+	uint8_t pipe_type;
+	uint8_t pipe_dir;
+	uint16_t mps;
+};
+
+struct dwc_otg_hcd_iso_packet_desc {
+	uint32_t offset;
+	uint32_t length;
+	uint32_t actual_length;
+	uint32_t status;
+};
+
+struct dwc_otg_qtd;
+
+struct dwc_otg_hcd_urb {
+	void *priv;
+	struct dwc_otg_qtd *qtd;
+	void *buf;
+	dwc_dma_t dma;
+	void *setup_packet;
+	dwc_dma_t setup_dma;
+	uint32_t length;
+	uint32_t actual_length;
+	uint32_t status;
+	uint32_t error_count;
+	uint32_t packet_count;
+	uint32_t flags;
+	uint16_t interval;
+	struct dwc_otg_hcd_pipe_info pipe_info;
+	struct dwc_otg_hcd_iso_packet_desc iso_descs[];
+};
+
+static inline uint8_t dwc_otg_hcd_get_ep_num(struct dwc_otg_hcd_pipe_info *pipe)
+{
+	return pipe->ep_num;
+}
+
+static inline uint8_t dwc_otg_hcd_get_pipe_type(struct dwc_otg_hcd_pipe_info
+						*pipe)
+{
+	return pipe->pipe_type;
+}
+
+static inline uint16_t dwc_otg_hcd_get_mps(struct dwc_otg_hcd_pipe_info *pipe)
+{
+	return pipe->mps;
+}
+
+static inline uint8_t dwc_otg_hcd_get_dev_addr(struct dwc_otg_hcd_pipe_info
+					       *pipe)
+{
+	return pipe->dev_addr;
+}
+
+static inline uint8_t dwc_otg_hcd_is_pipe_isoc(struct dwc_otg_hcd_pipe_info
+					       *pipe)
+{
+	return (pipe->pipe_type == UE_ISOCHRONOUS);
+}
+
+static inline uint8_t dwc_otg_hcd_is_pipe_int(struct dwc_otg_hcd_pipe_info
+					      *pipe)
+{
+	return (pipe->pipe_type == UE_INTERRUPT);
+}
+
+static inline uint8_t dwc_otg_hcd_is_pipe_bulk(struct dwc_otg_hcd_pipe_info
+					       *pipe)
+{
+	return (pipe->pipe_type == UE_BULK);
+}
+
+static inline uint8_t dwc_otg_hcd_is_pipe_control(struct dwc_otg_hcd_pipe_info
+						  *pipe)
+{
+	return (pipe->pipe_type == UE_CONTROL);
+}
+
+static inline uint8_t dwc_otg_hcd_is_pipe_in(struct dwc_otg_hcd_pipe_info *pipe)
+{
+	return (pipe->pipe_dir == UE_DIR_IN);
+}
+
+static inline uint8_t dwc_otg_hcd_is_pipe_out(struct dwc_otg_hcd_pipe_info
+					      *pipe)
+{
+	return (!dwc_otg_hcd_is_pipe_in(pipe));
+}
+
+static inline void dwc_otg_hcd_fill_pipe(struct dwc_otg_hcd_pipe_info *pipe,
+					 uint8_t devaddr, uint8_t ep_num,
+					 uint8_t pipe_type, uint8_t pipe_dir,
+					 uint16_t mps)
+{
+	pipe->dev_addr = devaddr;
+	pipe->ep_num = ep_num;
+	pipe->pipe_type = pipe_type;
+	pipe->pipe_dir = pipe_dir;
+	pipe->mps = mps;
+}
+
+/**
+ * Phases for control transfers.
+ */
+typedef enum dwc_otg_control_phase {
+	DWC_OTG_CONTROL_SETUP,
+	DWC_OTG_CONTROL_DATA,
+	DWC_OTG_CONTROL_STATUS
+} dwc_otg_control_phase_e;
+
+/** Transaction types. */
+typedef enum dwc_otg_transaction_type {
+	DWC_OTG_TRANSACTION_NONE          = 0,
+	DWC_OTG_TRANSACTION_PERIODIC      = 1,
+	DWC_OTG_TRANSACTION_NON_PERIODIC  = 2,
+	DWC_OTG_TRANSACTION_ALL           = DWC_OTG_TRANSACTION_PERIODIC + DWC_OTG_TRANSACTION_NON_PERIODIC
+} dwc_otg_transaction_type_e;
+
+struct dwc_otg_qh;
+
+/**
+ * A Queue Transfer Descriptor (QTD) holds the state of a bulk, control,
+ * interrupt, or isochronous transfer. A single QTD is created for each URB
+ * (of one of these types) submitted to the HCD. The transfer associated with
+ * a QTD may require one or multiple transactions.
+ *
+ * A QTD is linked to a Queue Head, which is entered in either the
+ * non-periodic or periodic schedule for execution. When a QTD is chosen for
+ * execution, some or all of its transactions may be executed. After
+ * execution, the state of the QTD is updated. The QTD may be retired if all
+ * its transactions are complete or if an error occurred. Otherwise, it
+ * remains in the schedule so more transactions can be executed later.
+ */
+typedef struct dwc_otg_qtd {
+	/**
+	 * Determines the PID of the next data packet for the data phase of
+	 * control transfers. Ignored for other transfer types.<br>
+	 * One of the following values:
+	 *	- DWC_OTG_HC_PID_DATA0
+	 *	- DWC_OTG_HC_PID_DATA1
+	 */
+	uint8_t data_toggle;
+
+	/** Current phase for control transfers (Setup, Data, or Status). */
+	dwc_otg_control_phase_e control_phase;
+
+	/** Keep track of the current split type
+	 * for FS/LS endpoints on a HS Hub */
+	uint8_t complete_split;
+
+	/** How many bytes transferred during SSPLIT OUT */
+	uint32_t ssplit_out_xfer_count;
+
+	/**
+	 * Holds the number of bus errors that have occurred for a transaction
+	 * within this transfer.
+	 */
+	uint8_t error_count;
+
+	/**
+	 * Index of the next frame descriptor for an isochronous transfer. A
+	 * frame descriptor describes the buffer position and length of the
+	 * data to be transferred in the next scheduled (micro)frame of an
+	 * isochronous transfer. It also holds status for that transaction.
+	 * The frame index starts at 0.
+	 */
+	uint16_t isoc_frame_index;
+
+	/** Position of the ISOC split on full/low speed */
+	uint8_t isoc_split_pos;
+
+	/** Position of the ISOC split in the buffer for the current frame */
+	uint16_t isoc_split_offset;
+
+	/** URB for this transfer */
+	struct dwc_otg_hcd_urb *urb;
+
+	struct dwc_otg_qh *qh;
+
+	/** This list of QTDs */
+	 DWC_CIRCLEQ_ENTRY(dwc_otg_qtd) qtd_list_entry;
+
+	/** Indicates if this QTD is currently processed by HW. */
+	uint8_t in_process;
+
+	/** Number of DMA descriptors for this QTD */
+	uint8_t n_desc;
+
+	/**
+	 * Last activated frame(packet) index.
+	 * Used in Descriptor DMA mode only.
+	 */
+	uint16_t isoc_frame_index_last;
+
+} dwc_otg_qtd_t;
+
+DWC_CIRCLEQ_HEAD(dwc_otg_qtd_list, dwc_otg_qtd);
+
+/**
+ * A Queue Head (QH) holds the static characteristics of an endpoint and
+ * maintains a list of transfers (QTDs) for that endpoint. A QH structure may
+ * be entered in either the non-periodic or periodic schedule.
+ */
+typedef struct dwc_otg_qh {
+	/**
+	 * Endpoint type.
+	 * One of the following values:
+	 *	- UE_CONTROL
+	 *	- UE_BULK
+	 *	- UE_INTERRUPT
+	 *	- UE_ISOCHRONOUS
+	 */
+	uint8_t ep_type;
+	uint8_t ep_is_in;
+
+	/** wMaxPacketSize Field of Endpoint Descriptor. */
+	uint16_t maxp;
+
+	/**
+	 * Device speed.
+	 * One of the following values:
+	 *	- DWC_OTG_EP_SPEED_LOW
+	 *	- DWC_OTG_EP_SPEED_FULL
+	 *	- DWC_OTG_EP_SPEED_HIGH
+	 */
+	uint8_t dev_speed;
+
+	/**
+	 * Determines the PID of the next data packet for non-control
+	 * transfers. Ignored for control transfers.<br>
+	 * One of the following values:
+	 *	- DWC_OTG_HC_PID_DATA0
+	 *	- DWC_OTG_HC_PID_DATA1
+	 */
+	uint8_t data_toggle;
+
+	/** Ping state if 1. */
+	uint8_t ping_state;
+
+	/**
+	 * List of QTDs for this QH.
+	 */
+	struct dwc_otg_qtd_list qtd_list;
+
+	/** Host channel currently processing transfers for this QH. */
+	struct dwc_hc *channel;
+
+	/** Full/low speed endpoint on high-speed hub requires split. */
+	uint8_t do_split;
+
+	/** @name Periodic schedule information */
+	/** @{ */
+
+	/** Bandwidth in microseconds per (micro)frame. */
+	uint16_t usecs;
+
+	/** Interval between transfers in (micro)frames. */
+	uint16_t interval;
+
+	/**
+	 * (micro)frame to initialize a periodic transfer. The transfer
+	 * executes in the following (micro)frame.
+	 */
+	uint16_t sched_frame;
+
+	/*
+	** Frame a NAK was received on this queue head, used to minimise NAK retransmission
+	*/
+	uint16_t nak_frame;
+
+	/** (micro)frame at which last start split was initialized. */
+	uint16_t start_split_frame;
+
+	/** @} */
+
+	/**
+	 * Used instead of original buffer if
+	 * it(physical address) is not dword-aligned.
+	 */
+	uint8_t *dw_align_buf;
+	dwc_dma_t dw_align_buf_dma;
+
+	/** Entry for QH in either the periodic or non-periodic schedule. */
+	dwc_list_link_t qh_list_entry;
+
+	/** @name Descriptor DMA support */
+	/** @{ */
+
+	/** Descriptor List. */
+	dwc_otg_host_dma_desc_t *desc_list;
+
+	/** Descriptor List physical address. */
+	dwc_dma_t desc_list_dma;
+
+	/**
+	 * Xfer Bytes array.
+	 * Each element corresponds to a descriptor and indicates
+	 * original XferSize size value for the descriptor.
+	 */
+	uint32_t *n_bytes;
+
+	/** Actual number of transfer descriptors in a list. */
+	uint16_t ntd;
+
+	/** First activated isochronous transfer descriptor index. */
+	uint8_t td_first;
+	/** Last activated isochronous transfer descriptor index. */
+	uint8_t td_last;
+
+	/** @} */
+
+
+	uint16_t speed;
+	uint16_t frame_usecs[8];
+
+	uint32_t skip_count;
+} dwc_otg_qh_t;
+
+DWC_CIRCLEQ_HEAD(hc_list, dwc_hc);
+
+typedef struct urb_tq_entry {
+	struct urb *urb;
+	DWC_TAILQ_ENTRY(urb_tq_entry) urb_tq_entries;
+} urb_tq_entry_t;
+
+DWC_TAILQ_HEAD(urb_list, urb_tq_entry);
+
+/**
+ * This structure holds the state of the HCD, including the non-periodic and
+ * periodic schedules.
+ */
+struct dwc_otg_hcd {
+	/** The DWC otg device pointer */
+	struct dwc_otg_device *otg_dev;
+	/** DWC OTG Core Interface Layer */
+	dwc_otg_core_if_t *core_if;
+
+	/** Function HCD driver callbacks */
+	struct dwc_otg_hcd_function_ops *fops;
+
+	/** Internal DWC HCD Flags */
+	volatile union dwc_otg_hcd_internal_flags {
+		uint32_t d32;
+		struct {
+			unsigned port_connect_status_change:1;
+			unsigned port_connect_status:1;
+			unsigned port_reset_change:1;
+			unsigned port_enable_change:1;
+			unsigned port_suspend_change:1;
+			unsigned port_over_current_change:1;
+			unsigned port_l1_change:1;
+			unsigned port_speed:2;
+			unsigned reserved:24;
+		} b;
+	} flags;
+
+	/**
+	 * Inactive items in the non-periodic schedule. This is a list of
+	 * Queue Heads. Transfers associated with these Queue Heads are not
+	 * currently assigned to a host channel.
+	 */
+	dwc_list_link_t non_periodic_sched_inactive;
+
+	/**
+	 * Active items in the non-periodic schedule. This is a list of
+	 * Queue Heads. Transfers associated with these Queue Heads are
+	 * currently assigned to a host channel.
+	 */
+	dwc_list_link_t non_periodic_sched_active;
+
+	/**
+	 * Pointer to the next Queue Head to process in the active
+	 * non-periodic schedule.
+	 */
+	dwc_list_link_t *non_periodic_qh_ptr;
+
+	/**
+	 * Inactive items in the periodic schedule. This is a list of QHs for
+	 * periodic transfers that are _not_ scheduled for the next frame.
+	 * Each QH in the list has an interval counter that determines when it
+	 * needs to be scheduled for execution. This scheduling mechanism
+	 * allows only a simple calculation for periodic bandwidth used (i.e.
+	 * must assume that all periodic transfers may need to execute in the
+	 * same frame). However, it greatly simplifies scheduling and should
+	 * be sufficient for the vast majority of OTG hosts, which need to
+	 * connect to a small number of peripherals at one time.
+	 *
+	 * Items move from this list to periodic_sched_ready when the QH
+	 * interval counter is 0 at SOF.
+	 */
+	dwc_list_link_t periodic_sched_inactive;
+
+	/**
+	 * List of periodic QHs that are ready for execution in the next
+	 * frame, but have not yet been assigned to host channels.
+	 *
+	 * Items move from this list to periodic_sched_assigned as host
+	 * channels become available during the current frame.
+	 */
+	dwc_list_link_t periodic_sched_ready;
+
+	/**
+	 * List of periodic QHs to be executed in the next frame that are
+	 * assigned to host channels.
+	 *
+	 * Items move from this list to periodic_sched_queued as the
+	 * transactions for the QH are queued to the DWC_otg controller.
+	 */
+	dwc_list_link_t periodic_sched_assigned;
+
+	/**
+	 * List of periodic QHs that have been queued for execution.
+	 *
+	 * Items move from this list to either periodic_sched_inactive or
+	 * periodic_sched_ready when the channel associated with the transfer
+	 * is released. If the interval for the QH is 1, the item moves to
+	 * periodic_sched_ready because it must be rescheduled for the next
+	 * frame. Otherwise, the item moves to periodic_sched_inactive.
+	 */
+	dwc_list_link_t periodic_sched_queued;
+
+	/**
+	 * Total bandwidth claimed so far for periodic transfers. This value
+	 * is in microseconds per (micro)frame. The assumption is that all
+	 * periodic transfers may occur in the same (micro)frame.
+	 */
+	uint16_t periodic_usecs;
+
+	/**
+	 * Total bandwidth claimed so far for all periodic transfers
+	 * in a frame.
+	 * This will include a mixture of HS and FS transfers.
+	 * Units are microseconds per (micro)frame.
+	 * We have a budget per frame and have to schedule
+	 * transactions accordingly.
+	 * Watch out for the fact that things are actually scheduled for the
+	 * "next frame".
+	 */
+	uint16_t                frame_usecs[8];
+
+
+	/**
+	 * Frame number read from the core at SOF. The value ranges from 0 to
+	 * DWC_HFNUM_MAX_FRNUM.
+	 */
+	uint16_t frame_number;
+
+	/**
+	 * Count of periodic QHs, if using several eps. For SOF enable/disable.
+	 */
+	uint16_t periodic_qh_count;
+
+	/**
+	 * Free host channels in the controller. This is a list of
+	 * dwc_hc_t items.
+	 */
+	struct hc_list free_hc_list;
+	/**
+	 * Number of host channels assigned to periodic transfers. Currently
+	 * assuming that there is a dedicated host channel for each periodic
+	 * transaction and at least one host channel available for
+	 * non-periodic transactions.
+	 */
+	int periodic_channels; /* microframe_schedule==0 */
+
+	/**
+	 * Number of host channels assigned to non-periodic transfers.
+	 */
+	int non_periodic_channels; /* microframe_schedule==0 */
+
+	/**
+	 * Number of host channels assigned to non-periodic transfers.
+	 */
+	int available_host_channels;
+
+	/**
+	 * Array of pointers to the host channel descriptors. Allows accessing
+	 * a host channel descriptor given the host channel number. This is
+	 * useful in interrupt handlers.
+	 */
+	struct dwc_hc *hc_ptr_array[MAX_EPS_CHANNELS];
+
+	/**
+	 * Buffer to use for any data received during the status phase of a
+	 * control transfer. Normally no data is transferred during the status
+	 * phase. This buffer is used as a bit bucket.
+	 */
+	uint8_t *status_buf;
+
+	/**
+	 * DMA address for status_buf.
+	 */
+	dma_addr_t status_buf_dma;
+#define DWC_OTG_HCD_STATUS_BUF_SIZE 64
+
+	/**
+	 * Connection timer. An OTG host must display a message if the device
+	 * does not connect. Started when the VBus power is turned on via
+	 * sysfs attribute "buspower".
+	 */
+	dwc_timer_t *conn_timer;
+
+	/* Tasket to do a reset */
+	dwc_tasklet_t *reset_tasklet;
+
+	dwc_tasklet_t *completion_tasklet;
+	struct urb_list completed_urb_list;
+
+	/*  */
+	dwc_spinlock_t *lock;
+	/**
+	 * Private data that could be used by OS wrapper.
+	 */
+	void *priv;
+
+	uint8_t otg_port;
+
+	/** Frame List */
+	uint32_t *frame_list;
+
+	/** Hub - Port assignment */
+	int hub_port[128];
+#ifdef FIQ_DEBUG
+	int hub_port_alloc[2048];
+#endif
+
+	/** Frame List DMA address */
+	dma_addr_t frame_list_dma;
+
+	struct fiq_stack *fiq_stack;
+	struct fiq_state *fiq_state;
+
+	/** Virtual address for split transaction DMA bounce buffers */
+	struct fiq_dma_channel *fiq_dmab;
+
+#ifdef DEBUG
+	uint32_t frrem_samples;
+	uint64_t frrem_accum;
+
+	uint32_t hfnum_7_samples_a;
+	uint64_t hfnum_7_frrem_accum_a;
+	uint32_t hfnum_0_samples_a;
+	uint64_t hfnum_0_frrem_accum_a;
+	uint32_t hfnum_other_samples_a;
+	uint64_t hfnum_other_frrem_accum_a;
+
+	uint32_t hfnum_7_samples_b;
+	uint64_t hfnum_7_frrem_accum_b;
+	uint32_t hfnum_0_samples_b;
+	uint64_t hfnum_0_frrem_accum_b;
+	uint32_t hfnum_other_samples_b;
+	uint64_t hfnum_other_frrem_accum_b;
+#endif
+};
+
+static inline struct device *dwc_otg_hcd_to_dev(struct dwc_otg_hcd *hcd)
+{
+	return &hcd->otg_dev->os_dep.platformdev->dev;
+}
+
+/** @name Transaction Execution Functions */
+/** @{ */
+extern dwc_otg_transaction_type_e dwc_otg_hcd_select_transactions(dwc_otg_hcd_t
+								  * hcd);
+extern void dwc_otg_hcd_queue_transactions(dwc_otg_hcd_t * hcd,
+					   dwc_otg_transaction_type_e tr_type);
+
+int dwc_otg_hcd_allocate_port(dwc_otg_hcd_t * hcd, dwc_otg_qh_t *qh);
+void dwc_otg_hcd_release_port(dwc_otg_hcd_t * dwc_otg_hcd, dwc_otg_qh_t *qh);
+
+extern int fiq_fsm_queue_transaction(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh);
+extern int fiq_fsm_transaction_suitable(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh);
+extern void dwc_otg_cleanup_fiq_channel(dwc_otg_hcd_t *hcd, uint32_t num);
+
+/** @} */
+
+/** @name Interrupt Handler Functions */
+/** @{ */
+extern int32_t dwc_otg_hcd_handle_intr(dwc_otg_hcd_t * dwc_otg_hcd);
+extern int32_t dwc_otg_hcd_handle_sof_intr(dwc_otg_hcd_t * dwc_otg_hcd);
+extern int32_t dwc_otg_hcd_handle_rx_status_q_level_intr(dwc_otg_hcd_t *
+							 dwc_otg_hcd);
+extern int32_t dwc_otg_hcd_handle_np_tx_fifo_empty_intr(dwc_otg_hcd_t *
+							dwc_otg_hcd);
+extern int32_t dwc_otg_hcd_handle_perio_tx_fifo_empty_intr(dwc_otg_hcd_t *
+							   dwc_otg_hcd);
+extern int32_t dwc_otg_hcd_handle_incomplete_periodic_intr(dwc_otg_hcd_t *
+							   dwc_otg_hcd);
+extern int32_t dwc_otg_hcd_handle_port_intr(dwc_otg_hcd_t * dwc_otg_hcd);
+extern int32_t dwc_otg_hcd_handle_conn_id_status_change_intr(dwc_otg_hcd_t *
+							     dwc_otg_hcd);
+extern int32_t dwc_otg_hcd_handle_disconnect_intr(dwc_otg_hcd_t * dwc_otg_hcd);
+extern int32_t dwc_otg_hcd_handle_hc_intr(dwc_otg_hcd_t * dwc_otg_hcd);
+extern int32_t dwc_otg_hcd_handle_hc_n_intr(dwc_otg_hcd_t * dwc_otg_hcd,
+					    uint32_t num);
+extern int32_t dwc_otg_hcd_handle_session_req_intr(dwc_otg_hcd_t * dwc_otg_hcd);
+extern int32_t dwc_otg_hcd_handle_wakeup_detected_intr(dwc_otg_hcd_t *
+						       dwc_otg_hcd);
+/** @} */
+
+/** @name Schedule Queue Functions */
+/** @{ */
+
+/* Implemented in dwc_otg_hcd_queue.c */
+extern dwc_otg_qh_t *dwc_otg_hcd_qh_create(dwc_otg_hcd_t * hcd,
+					   dwc_otg_hcd_urb_t * urb, int atomic_alloc);
+extern void dwc_otg_hcd_qh_free(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh);
+extern int dwc_otg_hcd_qh_add(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh);
+extern void dwc_otg_hcd_qh_remove(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh);
+extern void dwc_otg_hcd_qh_deactivate(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh,
+				      int sched_csplit);
+
+/** Remove and free a QH */
+static inline void dwc_otg_hcd_qh_remove_and_free(dwc_otg_hcd_t * hcd,
+						  dwc_otg_qh_t * qh)
+{
+	dwc_irqflags_t flags;
+	DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags);
+	dwc_otg_hcd_qh_remove(hcd, qh);
+	DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags);
+	dwc_otg_hcd_qh_free(hcd, qh);
+}
+
+/** Allocates memory for a QH structure.
+ * @return Returns the memory allocate or NULL on error. */
+static inline dwc_otg_qh_t *dwc_otg_hcd_qh_alloc(int atomic_alloc)
+{
+	if (atomic_alloc)
+		return (dwc_otg_qh_t *) DWC_ALLOC_ATOMIC(sizeof(dwc_otg_qh_t));
+	else
+		return (dwc_otg_qh_t *) DWC_ALLOC(sizeof(dwc_otg_qh_t));
+}
+
+extern dwc_otg_qtd_t *dwc_otg_hcd_qtd_create(dwc_otg_hcd_urb_t * urb,
+					     int atomic_alloc);
+extern void dwc_otg_hcd_qtd_init(dwc_otg_qtd_t * qtd, dwc_otg_hcd_urb_t * urb);
+extern int dwc_otg_hcd_qtd_add(dwc_otg_qtd_t * qtd, dwc_otg_hcd_t * dwc_otg_hcd,
+			       dwc_otg_qh_t ** qh, int atomic_alloc);
+
+/** Allocates memory for a QTD structure.
+ * @return Returns the memory allocate or NULL on error. */
+static inline dwc_otg_qtd_t *dwc_otg_hcd_qtd_alloc(int atomic_alloc)
+{
+	if (atomic_alloc)
+		return (dwc_otg_qtd_t *) DWC_ALLOC_ATOMIC(sizeof(dwc_otg_qtd_t));
+	else
+		return (dwc_otg_qtd_t *) DWC_ALLOC(sizeof(dwc_otg_qtd_t));
+}
+
+/** Frees the memory for a QTD structure.  QTD should already be removed from
+ * list.
+ * @param qtd QTD to free.*/
+static inline void dwc_otg_hcd_qtd_free(dwc_otg_qtd_t * qtd)
+{
+	DWC_FREE(qtd);
+}
+
+/** Removes a QTD from list.
+ * @param hcd HCD instance.
+ * @param qtd QTD to remove from list.
+ * @param qh QTD belongs to.
+ */
+static inline void dwc_otg_hcd_qtd_remove(dwc_otg_hcd_t * hcd,
+					  dwc_otg_qtd_t * qtd,
+					  dwc_otg_qh_t * qh)
+{
+	DWC_CIRCLEQ_REMOVE(&qh->qtd_list, qtd, qtd_list_entry);
+}
+
+/** Remove and free a QTD
+  * Need to disable IRQ and hold hcd lock while calling this function out of
+  * interrupt servicing chain */
+static inline void dwc_otg_hcd_qtd_remove_and_free(dwc_otg_hcd_t * hcd,
+						   dwc_otg_qtd_t * qtd,
+						   dwc_otg_qh_t * qh)
+{
+	dwc_otg_hcd_qtd_remove(hcd, qtd, qh);
+	dwc_otg_hcd_qtd_free(qtd);
+}
+
+/** @} */
+
+/** @name Descriptor DMA Supporting Functions */
+/** @{ */
+
+extern void dwc_otg_hcd_start_xfer_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh);
+extern void dwc_otg_hcd_complete_xfer_ddma(dwc_otg_hcd_t * hcd,
+					   dwc_hc_t * hc,
+					   dwc_otg_hc_regs_t * hc_regs,
+					   dwc_otg_halt_status_e halt_status);
+
+extern int dwc_otg_hcd_qh_init_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh);
+extern void dwc_otg_hcd_qh_free_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh);
+
+/** @} */
+
+/** @name Internal Functions */
+/** @{ */
+dwc_otg_qh_t *dwc_urb_to_qh(dwc_otg_hcd_urb_t * urb);
+/** @} */
+
+#ifdef CONFIG_USB_DWC_OTG_LPM
+extern int dwc_otg_hcd_get_hc_for_lpm_tran(dwc_otg_hcd_t * hcd,
+					   uint8_t devaddr);
+extern void dwc_otg_hcd_free_hc_from_lpm(dwc_otg_hcd_t * hcd);
+#endif
+
+/** Gets the QH that contains the list_head */
+#define dwc_list_to_qh(_list_head_ptr_) container_of(_list_head_ptr_, dwc_otg_qh_t, qh_list_entry)
+
+/** Gets the QTD that contains the list_head */
+#define dwc_list_to_qtd(_list_head_ptr_) container_of(_list_head_ptr_, dwc_otg_qtd_t, qtd_list_entry)
+
+/** Check if QH is non-periodic  */
+#define dwc_qh_is_non_per(_qh_ptr_) ((_qh_ptr_->ep_type == UE_BULK) || \
+				     (_qh_ptr_->ep_type == UE_CONTROL))
+
+/** High bandwidth multiplier as encoded in highspeed endpoint descriptors */
+#define dwc_hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03))
+
+/** Packet size for any kind of endpoint descriptor */
+#define dwc_max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff)
+
+/**
+ * Returns true if _frame1 is less than or equal to _frame2. The comparison is
+ * done modulo DWC_HFNUM_MAX_FRNUM. This accounts for the rollover of the
+ * frame number when the max frame number is reached.
+ */
+static inline int dwc_frame_num_le(uint16_t frame1, uint16_t frame2)
+{
+	return ((frame2 - frame1) & DWC_HFNUM_MAX_FRNUM) <=
+	    (DWC_HFNUM_MAX_FRNUM >> 1);
+}
+
+/**
+ * Returns true if _frame1 is greater than _frame2. The comparison is done
+ * modulo DWC_HFNUM_MAX_FRNUM. This accounts for the rollover of the frame
+ * number when the max frame number is reached.
+ */
+static inline int dwc_frame_num_gt(uint16_t frame1, uint16_t frame2)
+{
+	return (frame1 != frame2) &&
+	    (((frame1 - frame2) & DWC_HFNUM_MAX_FRNUM) <
+	     (DWC_HFNUM_MAX_FRNUM >> 1));
+}
+
+/**
+ * Increments _frame by the amount specified by _inc. The addition is done
+ * modulo DWC_HFNUM_MAX_FRNUM. Returns the incremented value.
+ */
+static inline uint16_t dwc_frame_num_inc(uint16_t frame, uint16_t inc)
+{
+	return (frame + inc) & DWC_HFNUM_MAX_FRNUM;
+}
+
+static inline uint16_t dwc_full_frame_num(uint16_t frame)
+{
+	return (frame & DWC_HFNUM_MAX_FRNUM) >> 3;
+}
+
+static inline uint16_t dwc_micro_frame_num(uint16_t frame)
+{
+	return frame & 0x7;
+}
+
+extern void init_hcd_usecs(dwc_otg_hcd_t *_hcd);
+
+void dwc_otg_hcd_save_data_toggle(dwc_hc_t * hc,
+				  dwc_otg_hc_regs_t * hc_regs,
+				  dwc_otg_qtd_t * qtd);
+
+#ifdef DEBUG
+/**
+ * Macro to sample the remaining PHY clocks left in the current frame. This
+ * may be used during debugging to determine the average time it takes to
+ * execute sections of code. There are two possible sample points, "a" and
+ * "b", so the _letter argument must be one of these values.
+ *
+ * To dump the average sample times, read the "hcd_frrem" sysfs attribute. For
+ * example, "cat /sys/devices/lm0/hcd_frrem".
+ */
+#define dwc_sample_frrem(_hcd, _qh, _letter) \
+{ \
+	hfnum_data_t hfnum; \
+	dwc_otg_qtd_t *qtd; \
+	qtd = list_entry(_qh->qtd_list.next, dwc_otg_qtd_t, qtd_list_entry); \
+	if (usb_pipeint(qtd->urb->pipe) && _qh->start_split_frame != 0 && !qtd->complete_split) { \
+		hfnum.d32 = DWC_READ_REG32(&_hcd->core_if->host_if->host_global_regs->hfnum); \
+		switch (hfnum.b.frnum & 0x7) { \
+		case 7: \
+			_hcd->hfnum_7_samples_##_letter++; \
+			_hcd->hfnum_7_frrem_accum_##_letter += hfnum.b.frrem; \
+			break; \
+		case 0: \
+			_hcd->hfnum_0_samples_##_letter++; \
+			_hcd->hfnum_0_frrem_accum_##_letter += hfnum.b.frrem; \
+			break; \
+		default: \
+			_hcd->hfnum_other_samples_##_letter++; \
+			_hcd->hfnum_other_frrem_accum_##_letter += hfnum.b.frrem; \
+			break; \
+		} \
+	} \
+}
+#else
+#define dwc_sample_frrem(_hcd, _qh, _letter)
+#endif
+#endif
+#endif /* DWC_DEVICE_ONLY */
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd_ddma.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd_ddma.c
new file mode 100644
index 000000000000..0cf505019090
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_ddma.c
@@ -0,0 +1,1135 @@
+/*==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_ddma.c $
+ * $Revision: #10 $
+ * $Date: 2011/10/20 $
+ * $Change: 1869464 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+#ifndef DWC_DEVICE_ONLY
+
+/** @file
+ * This file contains Descriptor DMA support implementation for host mode.
+ */
+
+#include "dwc_otg_hcd.h"
+#include "dwc_otg_regs.h"
+
+extern bool microframe_schedule;
+
+static inline uint8_t frame_list_idx(uint16_t frame)
+{
+	return (frame & (MAX_FRLIST_EN_NUM - 1));
+}
+
+static inline uint16_t desclist_idx_inc(uint16_t idx, uint16_t inc, uint8_t speed)
+{
+	return (idx + inc) &
+	    (((speed ==
+	       DWC_OTG_EP_SPEED_HIGH) ? MAX_DMA_DESC_NUM_HS_ISOC :
+	      MAX_DMA_DESC_NUM_GENERIC) - 1);
+}
+
+static inline uint16_t desclist_idx_dec(uint16_t idx, uint16_t inc, uint8_t speed)
+{
+	return (idx - inc) &
+	    (((speed ==
+	       DWC_OTG_EP_SPEED_HIGH) ? MAX_DMA_DESC_NUM_HS_ISOC :
+	      MAX_DMA_DESC_NUM_GENERIC) - 1);
+}
+
+static inline uint16_t max_desc_num(dwc_otg_qh_t * qh)
+{
+	return (((qh->ep_type == UE_ISOCHRONOUS)
+		 && (qh->dev_speed == DWC_OTG_EP_SPEED_HIGH))
+		? MAX_DMA_DESC_NUM_HS_ISOC : MAX_DMA_DESC_NUM_GENERIC);
+}
+static inline uint16_t frame_incr_val(dwc_otg_qh_t * qh)
+{
+	return ((qh->dev_speed == DWC_OTG_EP_SPEED_HIGH)
+		? ((qh->interval + 8 - 1) / 8)
+		: qh->interval);
+}
+
+static int desc_list_alloc(struct device *dev, dwc_otg_qh_t * qh)
+{
+	int retval = 0;
+
+	qh->desc_list = (dwc_otg_host_dma_desc_t *)
+	    DWC_DMA_ALLOC(dev, sizeof(dwc_otg_host_dma_desc_t) * max_desc_num(qh),
+			  &qh->desc_list_dma);
+
+	if (!qh->desc_list) {
+		retval = -DWC_E_NO_MEMORY;
+		DWC_ERROR("%s: DMA descriptor list allocation failed\n", __func__);
+
+	}
+
+	dwc_memset(qh->desc_list, 0x00,
+		   sizeof(dwc_otg_host_dma_desc_t) * max_desc_num(qh));
+
+	qh->n_bytes =
+	    (uint32_t *) DWC_ALLOC(sizeof(uint32_t) * max_desc_num(qh));
+
+	if (!qh->n_bytes) {
+		retval = -DWC_E_NO_MEMORY;
+		DWC_ERROR
+		    ("%s: Failed to allocate array for descriptors' size actual values\n",
+		     __func__);
+
+	}
+	return retval;
+
+}
+
+static void desc_list_free(struct device *dev, dwc_otg_qh_t * qh)
+{
+	if (qh->desc_list) {
+		DWC_DMA_FREE(dev, max_desc_num(qh), qh->desc_list,
+			     qh->desc_list_dma);
+		qh->desc_list = NULL;
+	}
+
+	if (qh->n_bytes) {
+		DWC_FREE(qh->n_bytes);
+		qh->n_bytes = NULL;
+	}
+}
+
+static int frame_list_alloc(dwc_otg_hcd_t * hcd)
+{
+	struct device *dev = dwc_otg_hcd_to_dev(hcd);
+	int retval = 0;
+
+	if (hcd->frame_list)
+		return 0;
+
+	hcd->frame_list = DWC_DMA_ALLOC(dev, 4 * MAX_FRLIST_EN_NUM,
+					&hcd->frame_list_dma);
+	if (!hcd->frame_list) {
+		retval = -DWC_E_NO_MEMORY;
+		DWC_ERROR("%s: Frame List allocation failed\n", __func__);
+	}
+
+	dwc_memset(hcd->frame_list, 0x00, 4 * MAX_FRLIST_EN_NUM);
+
+	return retval;
+}
+
+static void frame_list_free(dwc_otg_hcd_t * hcd)
+{
+	struct device *dev = dwc_otg_hcd_to_dev(hcd);
+
+	if (!hcd->frame_list)
+		return;
+
+	DWC_DMA_FREE(dev, 4 * MAX_FRLIST_EN_NUM, hcd->frame_list, hcd->frame_list_dma);
+	hcd->frame_list = NULL;
+}
+
+static void per_sched_enable(dwc_otg_hcd_t * hcd, uint16_t fr_list_en)
+{
+
+	hcfg_data_t hcfg;
+
+	hcfg.d32 = DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hcfg);
+
+	if (hcfg.b.perschedena) {
+		/* already enabled */
+		return;
+	}
+
+	DWC_WRITE_REG32(&hcd->core_if->host_if->host_global_regs->hflbaddr,
+			hcd->frame_list_dma);
+
+	switch (fr_list_en) {
+	case 64:
+		hcfg.b.frlisten = 3;
+		break;
+	case 32:
+		hcfg.b.frlisten = 2;
+		break;
+	case 16:
+		hcfg.b.frlisten = 1;
+		break;
+	case 8:
+		hcfg.b.frlisten = 0;
+		break;
+	default:
+		break;
+	}
+
+	hcfg.b.perschedena = 1;
+
+	DWC_DEBUGPL(DBG_HCD, "Enabling Periodic schedule\n");
+	DWC_WRITE_REG32(&hcd->core_if->host_if->host_global_regs->hcfg, hcfg.d32);
+
+}
+
+static void per_sched_disable(dwc_otg_hcd_t * hcd)
+{
+	hcfg_data_t hcfg;
+
+	hcfg.d32 = DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hcfg);
+
+	if (!hcfg.b.perschedena) {
+		/* already disabled */
+		return;
+	}
+	hcfg.b.perschedena = 0;
+
+	DWC_DEBUGPL(DBG_HCD, "Disabling Periodic schedule\n");
+	DWC_WRITE_REG32(&hcd->core_if->host_if->host_global_regs->hcfg, hcfg.d32);
+}
+
+/*
+ * Activates/Deactivates FrameList entries for the channel
+ * based on endpoint servicing period.
+ */
+static void update_frame_list(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, uint8_t enable)
+{
+	uint16_t i, j, inc;
+	dwc_hc_t *hc = NULL;
+
+	if (!qh->channel) {
+		DWC_ERROR("qh->channel = %p", qh->channel);
+		return;
+	}
+
+	if (!hcd) {
+		DWC_ERROR("------hcd = %p", hcd);
+		return;
+	}
+
+	if (!hcd->frame_list) {
+		DWC_ERROR("-------hcd->frame_list = %p", hcd->frame_list);
+		return;
+	}
+
+	hc = qh->channel;
+	inc = frame_incr_val(qh);
+	if (qh->ep_type == UE_ISOCHRONOUS)
+		i = frame_list_idx(qh->sched_frame);
+	else
+		i = 0;
+
+	j = i;
+	do {
+		if (enable)
+			hcd->frame_list[j] |= (1 << hc->hc_num);
+		else
+			hcd->frame_list[j] &= ~(1 << hc->hc_num);
+		j = (j + inc) & (MAX_FRLIST_EN_NUM - 1);
+	}
+	while (j != i);
+	if (!enable)
+		return;
+	hc->schinfo = 0;
+	if (qh->channel->speed == DWC_OTG_EP_SPEED_HIGH) {
+		j = 1;
+		/* TODO - check this */
+		inc = (8 + qh->interval - 1) / qh->interval;
+		for (i = 0; i < inc; i++) {
+			hc->schinfo |= j;
+			j = j << qh->interval;
+		}
+	} else {
+		hc->schinfo = 0xff;
+	}
+}
+
+#if 0
+static void dump_frame_list(dwc_otg_hcd_t * hcd)
+{
+	int i = 0;
+	DWC_PRINTF("--FRAME LIST (hex) --\n");
+	for (i = 0; i < MAX_FRLIST_EN_NUM; i++) {
+		DWC_PRINTF("%x\t", hcd->frame_list[i]);
+		if (!(i % 8) && i)
+			DWC_PRINTF("\n");
+	}
+	DWC_PRINTF("\n----\n");
+
+}
+#endif
+
+static void release_channel_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh)
+{
+	dwc_hc_t *hc = qh->channel;
+	if (dwc_qh_is_non_per(qh)) {
+		if (!microframe_schedule)
+			hcd->non_periodic_channels--;
+		else
+			hcd->available_host_channels++;
+	} else
+		update_frame_list(hcd, qh, 0);
+
+	/*
+	 * The condition is added to prevent double cleanup try in case of device
+	 * disconnect. See channel cleanup in dwc_otg_hcd_disconnect_cb().
+	 */
+	if (hc->qh) {
+		dwc_otg_hc_cleanup(hcd->core_if, hc);
+		DWC_CIRCLEQ_INSERT_TAIL(&hcd->free_hc_list, hc, hc_list_entry);
+		hc->qh = NULL;
+	}
+
+	qh->channel = NULL;
+	qh->ntd = 0;
+
+	if (qh->desc_list) {
+		dwc_memset(qh->desc_list, 0x00,
+			   sizeof(dwc_otg_host_dma_desc_t) * max_desc_num(qh));
+	}
+}
+
+/**
+ * Initializes a QH structure's Descriptor DMA related members.
+ * Allocates memory for descriptor list.
+ * On first periodic QH, allocates memory for FrameList
+ * and enables periodic scheduling.
+ *
+ * @param hcd The HCD state structure for the DWC OTG controller.
+ * @param qh The QH to init.
+ *
+ * @return 0 if successful, negative error code otherwise.
+ */
+int dwc_otg_hcd_qh_init_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh)
+{
+	struct device *dev = dwc_otg_hcd_to_dev(hcd);
+	int retval = 0;
+
+	if (qh->do_split) {
+		DWC_ERROR("SPLIT Transfers are not supported in Descriptor DMA.\n");
+		return -1;
+	}
+
+	retval = desc_list_alloc(dev, qh);
+
+	if ((retval == 0)
+	    && (qh->ep_type == UE_ISOCHRONOUS || qh->ep_type == UE_INTERRUPT)) {
+		if (!hcd->frame_list) {
+			retval = frame_list_alloc(hcd);
+			/* Enable periodic schedule on first periodic QH */
+			if (retval == 0)
+				per_sched_enable(hcd, MAX_FRLIST_EN_NUM);
+		}
+	}
+
+	qh->ntd = 0;
+
+	return retval;
+}
+
+/**
+ * Frees descriptor list memory associated with the QH.
+ * If QH is periodic and the last, frees FrameList memory
+ * and disables periodic scheduling.
+ *
+ * @param hcd The HCD state structure for the DWC OTG controller.
+ * @param qh The QH to init.
+ */
+void dwc_otg_hcd_qh_free_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh)
+{
+	struct device *dev = dwc_otg_hcd_to_dev(hcd);
+
+	desc_list_free(dev, qh);
+
+	/*
+	 * Channel still assigned due to some reasons.
+	 * Seen on Isoc URB dequeue. Channel halted but no subsequent
+	 * ChHalted interrupt to release the channel. Afterwards
+	 * when it comes here from endpoint disable routine
+	 * channel remains assigned.
+	 */
+	if (qh->channel)
+		release_channel_ddma(hcd, qh);
+
+	if ((qh->ep_type == UE_ISOCHRONOUS || qh->ep_type == UE_INTERRUPT)
+	    && (microframe_schedule || !hcd->periodic_channels) && hcd->frame_list) {
+
+		per_sched_disable(hcd);
+		frame_list_free(hcd);
+	}
+}
+
+static uint8_t frame_to_desc_idx(dwc_otg_qh_t * qh, uint16_t frame_idx)
+{
+	if (qh->dev_speed == DWC_OTG_EP_SPEED_HIGH) {
+		/*
+		 * Descriptor set(8 descriptors) index
+		 * which is 8-aligned.
+		 */
+		return (frame_idx & ((MAX_DMA_DESC_NUM_HS_ISOC / 8) - 1)) * 8;
+	} else {
+		return (frame_idx & (MAX_DMA_DESC_NUM_GENERIC - 1));
+	}
+}
+
+/*
+ * Determine starting frame for Isochronous transfer.
+ * Few frames skipped to prevent race condition with HC.
+ */
+static uint8_t calc_starting_frame(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh,
+				   uint8_t * skip_frames)
+{
+	uint16_t frame = 0;
+	hcd->frame_number = dwc_otg_hcd_get_frame_number(hcd);
+
+	/* sched_frame is always frame number(not uFrame) both in FS and HS !! */
+
+	/*
+	 * skip_frames is used to limit activated descriptors number
+	 * to avoid the situation when HC services the last activated
+	 * descriptor firstly.
+	 * Example for FS:
+	 * Current frame is 1, scheduled frame is 3. Since HC always fetches the descriptor
+	 * corresponding to curr_frame+1, the descriptor corresponding to frame 2
+	 * will be fetched. If the number of descriptors is max=64 (or greather) the
+	 * list will be fully programmed with Active descriptors and it is possible
+	 * case(rare) that the latest descriptor(considering rollback) corresponding
+	 * to frame 2 will be serviced first. HS case is more probable because, in fact,
+	 * up to 11 uframes(16 in the code) may be skipped.
+	 */
+	if (qh->dev_speed == DWC_OTG_EP_SPEED_HIGH) {
+		/*
+		 * Consider uframe counter also, to start xfer asap.
+		 * If half of the frame elapsed skip 2 frames otherwise
+		 * just 1 frame.
+		 * Starting descriptor index must be 8-aligned, so
+		 * if the current frame is near to complete the next one
+		 * is skipped as well.
+		 */
+
+		if (dwc_micro_frame_num(hcd->frame_number) >= 5) {
+			*skip_frames = 2 * 8;
+			frame = dwc_frame_num_inc(hcd->frame_number, *skip_frames);
+		} else {
+			*skip_frames = 1 * 8;
+			frame = dwc_frame_num_inc(hcd->frame_number, *skip_frames);
+		}
+
+		frame = dwc_full_frame_num(frame);
+	} else {
+		/*
+		 * Two frames are skipped for FS - the current and the next.
+		 * But for descriptor programming, 1 frame(descriptor) is enough,
+		 * see example above.
+		 */
+		*skip_frames = 1;
+		frame = dwc_frame_num_inc(hcd->frame_number, 2);
+	}
+
+	return frame;
+}
+
+/*
+ * Calculate initial descriptor index for isochronous transfer
+ * based on scheduled frame.
+ */
+static uint8_t recalc_initial_desc_idx(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh)
+{
+	uint16_t frame = 0, fr_idx, fr_idx_tmp;
+	uint8_t skip_frames = 0;
+	/*
+	 * With current ISOC processing algorithm the channel is being
+	 * released when no more QTDs in the list(qh->ntd == 0).
+	 * Thus this function is called only when qh->ntd == 0 and qh->channel == 0.
+	 *
+	 * So qh->channel != NULL branch is not used and just not removed from the
+	 * source file. It is required for another possible approach which is,
+	 * do not disable and release the channel when ISOC session completed,
+	 * just move QH to inactive schedule until new QTD arrives.
+	 * On new QTD, the QH moved back to 'ready' schedule,
+	 * starting frame and therefore starting desc_index are recalculated.
+	 * In this case channel is released only on ep_disable.
+	 */
+
+	/* Calculate starting descriptor index. For INTERRUPT endpoint it is always 0. */
+	if (qh->channel) {
+		frame = calc_starting_frame(hcd, qh, &skip_frames);
+		/*
+		 * Calculate initial descriptor index based on FrameList current bitmap
+		 * and servicing period.
+		 */
+		fr_idx_tmp = frame_list_idx(frame);
+		fr_idx =
+		    (MAX_FRLIST_EN_NUM + frame_list_idx(qh->sched_frame) -
+		     fr_idx_tmp)
+		    % frame_incr_val(qh);
+		fr_idx = (fr_idx + fr_idx_tmp) % MAX_FRLIST_EN_NUM;
+	} else {
+		qh->sched_frame = calc_starting_frame(hcd, qh, &skip_frames);
+		fr_idx = frame_list_idx(qh->sched_frame);
+	}
+
+	qh->td_first = qh->td_last = frame_to_desc_idx(qh, fr_idx);
+
+	return skip_frames;
+}
+
+#define	ISOC_URB_GIVEBACK_ASAP
+
+#define MAX_ISOC_XFER_SIZE_FS 1023
+#define MAX_ISOC_XFER_SIZE_HS 3072
+#define DESCNUM_THRESHOLD 4
+
+static void init_isoc_dma_desc(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh,
+			       uint8_t skip_frames)
+{
+	struct dwc_otg_hcd_iso_packet_desc *frame_desc;
+	dwc_otg_qtd_t *qtd;
+	dwc_otg_host_dma_desc_t *dma_desc;
+	uint16_t idx, inc, n_desc, ntd_max, max_xfer_size;
+
+	idx = qh->td_last;
+	inc = qh->interval;
+	n_desc = 0;
+
+	ntd_max = (max_desc_num(qh) + qh->interval - 1) / qh->interval;
+	if (skip_frames && !qh->channel)
+		ntd_max = ntd_max - skip_frames / qh->interval;
+
+	max_xfer_size =
+	    (qh->dev_speed ==
+	     DWC_OTG_EP_SPEED_HIGH) ? MAX_ISOC_XFER_SIZE_HS :
+	    MAX_ISOC_XFER_SIZE_FS;
+
+	DWC_CIRCLEQ_FOREACH(qtd, &qh->qtd_list, qtd_list_entry) {
+		while ((qh->ntd < ntd_max)
+		       && (qtd->isoc_frame_index_last <
+			   qtd->urb->packet_count)) {
+
+			dma_desc = &qh->desc_list[idx];
+			dwc_memset(dma_desc, 0x00, sizeof(dwc_otg_host_dma_desc_t));
+
+			frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index_last];
+
+			if (frame_desc->length > max_xfer_size)
+				qh->n_bytes[idx] = max_xfer_size;
+			else
+				qh->n_bytes[idx] = frame_desc->length;
+			dma_desc->status.b_isoc.n_bytes = qh->n_bytes[idx];
+			dma_desc->status.b_isoc.a = 1;
+			dma_desc->status.b_isoc.sts = 0;
+
+			dma_desc->buf = qtd->urb->dma + frame_desc->offset;
+
+			qh->ntd++;
+
+			qtd->isoc_frame_index_last++;
+
+#ifdef	ISOC_URB_GIVEBACK_ASAP
+			/*
+			 * Set IOC for each descriptor corresponding to the
+			 * last frame of the URB.
+			 */
+			if (qtd->isoc_frame_index_last ==
+			    qtd->urb->packet_count)
+				dma_desc->status.b_isoc.ioc = 1;
+
+#endif
+			idx = desclist_idx_inc(idx, inc, qh->dev_speed);
+			n_desc++;
+
+		}
+		qtd->in_process = 1;
+	}
+
+	qh->td_last = idx;
+
+#ifdef	ISOC_URB_GIVEBACK_ASAP
+	/* Set IOC for the last descriptor if descriptor list is full */
+	if (qh->ntd == ntd_max) {
+		idx = desclist_idx_dec(qh->td_last, inc, qh->dev_speed);
+		qh->desc_list[idx].status.b_isoc.ioc = 1;
+	}
+#else
+	/*
+	 * Set IOC bit only for one descriptor.
+	 * Always try to be ahead of HW processing,
+	 * i.e. on IOC generation driver activates next descriptors but
+	 * core continues to process descriptors followed the one with IOC set.
+	 */
+
+	if (n_desc > DESCNUM_THRESHOLD) {
+		/*
+		 * Move IOC "up". Required even if there is only one QTD
+		 * in the list, cause QTDs migth continue to be queued,
+		 * but during the activation it was only one queued.
+		 * Actually more than one QTD might be in the list if this function called
+		 * from XferCompletion - QTDs was queued during HW processing of the previous
+		 * descriptor chunk.
+		 */
+		idx = dwc_desclist_idx_dec(idx, inc * ((qh->ntd + 1) / 2), qh->dev_speed);
+	} else {
+		/*
+		 * Set the IOC for the latest descriptor
+		 * if either number of descriptor is not greather than threshold
+		 * or no more new descriptors activated.
+		 */
+		idx = dwc_desclist_idx_dec(qh->td_last, inc, qh->dev_speed);
+	}
+
+	qh->desc_list[idx].status.b_isoc.ioc = 1;
+#endif
+}
+
+static void init_non_isoc_dma_desc(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh)
+{
+
+	dwc_hc_t *hc;
+	dwc_otg_host_dma_desc_t *dma_desc;
+	dwc_otg_qtd_t *qtd;
+	int num_packets, len, n_desc = 0;
+
+	hc = qh->channel;
+
+	/*
+	 * Start with hc->xfer_buff initialized in
+	 * assign_and_init_hc(), then if SG transfer consists of multiple URBs,
+	 * this pointer re-assigned to the buffer of the currently processed QTD.
+	 * For non-SG request there is always one QTD active.
+	 */
+
+	DWC_CIRCLEQ_FOREACH(qtd, &qh->qtd_list, qtd_list_entry) {
+
+		if (n_desc) {
+			/* SG request - more than 1 QTDs */
+			hc->xfer_buff = (uint8_t *)(uintptr_t)qtd->urb->dma +
+					qtd->urb->actual_length;
+			hc->xfer_len = qtd->urb->length - qtd->urb->actual_length;
+		}
+
+		qtd->n_desc = 0;
+
+		do {
+			dma_desc = &qh->desc_list[n_desc];
+			len = hc->xfer_len;
+
+			if (len > MAX_DMA_DESC_SIZE)
+				len = MAX_DMA_DESC_SIZE - hc->max_packet + 1;
+
+			if (hc->ep_is_in) {
+				if (len > 0) {
+					num_packets = (len + hc->max_packet - 1) / hc->max_packet;
+				} else {
+					/* Need 1 packet for transfer length of 0. */
+					num_packets = 1;
+				}
+				/* Always program an integral # of max packets for IN transfers. */
+				len = num_packets * hc->max_packet;
+			}
+
+			dma_desc->status.b.n_bytes = len;
+
+			qh->n_bytes[n_desc] = len;
+
+			if ((qh->ep_type == UE_CONTROL)
+			    && (qtd->control_phase == DWC_OTG_CONTROL_SETUP))
+				dma_desc->status.b.sup = 1;	/* Setup Packet */
+
+			dma_desc->status.b.a = 1;	/* Active descriptor */
+			dma_desc->status.b.sts = 0;
+
+			dma_desc->buf =
+			    ((unsigned long)hc->xfer_buff & 0xffffffff);
+
+			/*
+			 * Last descriptor(or single) of IN transfer
+			 * with actual size less than MaxPacket.
+			 */
+			if (len > hc->xfer_len) {
+				hc->xfer_len = 0;
+			} else {
+				hc->xfer_buff += len;
+				hc->xfer_len -= len;
+			}
+
+			qtd->n_desc++;
+			n_desc++;
+		}
+		while ((hc->xfer_len > 0) && (n_desc != MAX_DMA_DESC_NUM_GENERIC));
+
+
+		qtd->in_process = 1;
+
+		if (qh->ep_type == UE_CONTROL)
+			break;
+
+		if (n_desc == MAX_DMA_DESC_NUM_GENERIC)
+			break;
+	}
+
+	if (n_desc) {
+		/* Request Transfer Complete interrupt for the last descriptor */
+		qh->desc_list[n_desc - 1].status.b.ioc = 1;
+		/* End of List indicator */
+		qh->desc_list[n_desc - 1].status.b.eol = 1;
+
+		hc->ntd = n_desc;
+	}
+}
+
+/**
+ * For Control and Bulk endpoints initializes descriptor list
+ * and starts the transfer.
+ *
+ * For Interrupt and Isochronous endpoints initializes descriptor list
+ * then updates FrameList, marking appropriate entries as active.
+ * In case of Isochronous, the starting descriptor index is calculated based
+ * on the scheduled frame, but only on the first transfer descriptor within a session.
+ * Then starts the transfer via enabling the channel.
+ * For Isochronous endpoint the channel is not halted on XferComplete
+ * interrupt so remains assigned to the endpoint(QH) until session is done.
+ *
+ * @param hcd The HCD state structure for the DWC OTG controller.
+ * @param qh The QH to init.
+ *
+ * @return 0 if successful, negative error code otherwise.
+ */
+void dwc_otg_hcd_start_xfer_ddma(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh)
+{
+	/* Channel is already assigned */
+	dwc_hc_t *hc = qh->channel;
+	uint8_t skip_frames = 0;
+
+	switch (hc->ep_type) {
+	case DWC_OTG_EP_TYPE_CONTROL:
+	case DWC_OTG_EP_TYPE_BULK:
+		init_non_isoc_dma_desc(hcd, qh);
+
+		dwc_otg_hc_start_transfer_ddma(hcd->core_if, hc);
+		break;
+	case DWC_OTG_EP_TYPE_INTR:
+		init_non_isoc_dma_desc(hcd, qh);
+
+		update_frame_list(hcd, qh, 1);
+
+		dwc_otg_hc_start_transfer_ddma(hcd->core_if, hc);
+		break;
+	case DWC_OTG_EP_TYPE_ISOC:
+
+		if (!qh->ntd)
+			skip_frames = recalc_initial_desc_idx(hcd, qh);
+
+		init_isoc_dma_desc(hcd, qh, skip_frames);
+
+		if (!hc->xfer_started) {
+
+			update_frame_list(hcd, qh, 1);
+
+			/*
+			 * Always set to max, instead of actual size.
+			 * Otherwise ntd will be changed with
+			 * channel being enabled. Not recommended.
+			 *
+			 */
+			hc->ntd = max_desc_num(qh);
+			/* Enable channel only once for ISOC */
+			dwc_otg_hc_start_transfer_ddma(hcd->core_if, hc);
+		}
+
+		break;
+	default:
+
+		break;
+	}
+}
+
+static void complete_isoc_xfer_ddma(dwc_otg_hcd_t * hcd,
+				    dwc_hc_t * hc,
+				    dwc_otg_hc_regs_t * hc_regs,
+				    dwc_otg_halt_status_e halt_status)
+{
+	struct dwc_otg_hcd_iso_packet_desc *frame_desc;
+	dwc_otg_qtd_t *qtd, *qtd_tmp;
+	dwc_otg_qh_t *qh;
+	dwc_otg_host_dma_desc_t *dma_desc;
+	uint16_t idx, remain;
+	uint8_t urb_compl;
+
+	qh = hc->qh;
+	idx = qh->td_first;
+
+	if (hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE) {
+		DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &hc->qh->qtd_list, qtd_list_entry)
+		    qtd->in_process = 0;
+		return;
+	} else if ((halt_status == DWC_OTG_HC_XFER_AHB_ERR) ||
+		   (halt_status == DWC_OTG_HC_XFER_BABBLE_ERR)) {
+		/*
+		 * Channel is halted in these error cases.
+		 * Considered as serious issues.
+		 * Complete all URBs marking all frames as failed,
+		 * irrespective whether some of the descriptors(frames) succeeded or no.
+		 * Pass error code to completion routine as well, to
+		 * update urb->status, some of class drivers might use it to stop
+		 * queing transfer requests.
+		 */
+		int err = (halt_status == DWC_OTG_HC_XFER_AHB_ERR)
+		    ? (-DWC_E_IO)
+		    : (-DWC_E_OVERFLOW);
+
+		DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &hc->qh->qtd_list, qtd_list_entry) {
+			for (idx = 0; idx < qtd->urb->packet_count; idx++) {
+				frame_desc = &qtd->urb->iso_descs[idx];
+				frame_desc->status = err;
+			}
+			hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, err);
+			dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh);
+		}
+		return;
+	}
+
+	DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &hc->qh->qtd_list, qtd_list_entry) {
+
+		if (!qtd->in_process)
+			break;
+
+		urb_compl = 0;
+
+		do {
+
+			dma_desc = &qh->desc_list[idx];
+
+			frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index];
+			remain = hc->ep_is_in ? dma_desc->status.b_isoc.n_bytes : 0;
+
+			if (dma_desc->status.b_isoc.sts == DMA_DESC_STS_PKTERR) {
+				/*
+				 * XactError or, unable to complete all the transactions
+				 * in the scheduled micro-frame/frame,
+				 * both indicated by DMA_DESC_STS_PKTERR.
+				 */
+				qtd->urb->error_count++;
+				frame_desc->actual_length = qh->n_bytes[idx] - remain;
+				frame_desc->status = -DWC_E_PROTOCOL;
+			} else {
+				/* Success */
+
+				frame_desc->actual_length = qh->n_bytes[idx] - remain;
+				frame_desc->status = 0;
+			}
+
+			if (++qtd->isoc_frame_index == qtd->urb->packet_count) {
+				/*
+				 * urb->status is not used for isoc transfers here.
+				 * The individual frame_desc status are used instead.
+				 */
+
+				hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, 0);
+				dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh);
+
+				/*
+				 * This check is necessary because urb_dequeue can be called
+				 * from urb complete callback(sound driver example).
+				 * All pending URBs are dequeued there, so no need for
+				 * further processing.
+				 */
+				if (hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE) {
+					return;
+				}
+
+				urb_compl = 1;
+
+			}
+
+			qh->ntd--;
+
+			/* Stop if IOC requested descriptor reached */
+			if (dma_desc->status.b_isoc.ioc) {
+				idx = desclist_idx_inc(idx, qh->interval, hc->speed);
+				goto stop_scan;
+			}
+
+			idx = desclist_idx_inc(idx, qh->interval, hc->speed);
+
+			if (urb_compl)
+				break;
+		}
+		while (idx != qh->td_first);
+	}
+stop_scan:
+	qh->td_first = idx;
+}
+
+static uint8_t update_non_isoc_urb_state_ddma(dwc_otg_hcd_t * hcd,
+				       dwc_hc_t * hc,
+				       dwc_otg_qtd_t * qtd,
+				       dwc_otg_host_dma_desc_t * dma_desc,
+				       dwc_otg_halt_status_e halt_status,
+				       uint32_t n_bytes, uint8_t * xfer_done)
+{
+
+	uint16_t remain = hc->ep_is_in ? dma_desc->status.b.n_bytes : 0;
+	dwc_otg_hcd_urb_t *urb = qtd->urb;
+
+	if (halt_status == DWC_OTG_HC_XFER_AHB_ERR) {
+		urb->status = -DWC_E_IO;
+		return 1;
+	}
+	if (dma_desc->status.b.sts == DMA_DESC_STS_PKTERR) {
+		switch (halt_status) {
+		case DWC_OTG_HC_XFER_STALL:
+			urb->status = -DWC_E_PIPE;
+			break;
+		case DWC_OTG_HC_XFER_BABBLE_ERR:
+			urb->status = -DWC_E_OVERFLOW;
+			break;
+		case DWC_OTG_HC_XFER_XACT_ERR:
+			urb->status = -DWC_E_PROTOCOL;
+			break;
+		default:
+			DWC_ERROR("%s: Unhandled descriptor error status (%d)\n", __func__,
+				  halt_status);
+			break;
+		}
+		return 1;
+	}
+
+	if (dma_desc->status.b.a == 1) {
+		DWC_DEBUGPL(DBG_HCDV,
+			    "Active descriptor encountered on channel %d\n",
+			    hc->hc_num);
+		return 0;
+	}
+
+	if (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL) {
+		if (qtd->control_phase == DWC_OTG_CONTROL_DATA) {
+			urb->actual_length += n_bytes - remain;
+			if (remain || urb->actual_length == urb->length) {
+				/*
+				 * For Control Data stage do not set urb->status=0 to prevent
+				 * URB callback. Set it when Status phase done. See below.
+				 */
+				*xfer_done = 1;
+			}
+
+		} else if (qtd->control_phase == DWC_OTG_CONTROL_STATUS) {
+			urb->status = 0;
+			*xfer_done = 1;
+		}
+		/* No handling for SETUP stage */
+	} else {
+		/* BULK and INTR */
+		urb->actual_length += n_bytes - remain;
+		if (remain || urb->actual_length == urb->length) {
+			urb->status = 0;
+			*xfer_done = 1;
+		}
+	}
+
+	return 0;
+}
+
+static void complete_non_isoc_xfer_ddma(dwc_otg_hcd_t * hcd,
+					dwc_hc_t * hc,
+					dwc_otg_hc_regs_t * hc_regs,
+					dwc_otg_halt_status_e halt_status)
+{
+	dwc_otg_hcd_urb_t *urb = NULL;
+	dwc_otg_qtd_t *qtd, *qtd_tmp;
+	dwc_otg_qh_t *qh;
+	dwc_otg_host_dma_desc_t *dma_desc;
+	uint32_t n_bytes, n_desc, i;
+	uint8_t failed = 0, xfer_done;
+
+	n_desc = 0;
+
+	qh = hc->qh;
+
+	if (hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE) {
+		DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &hc->qh->qtd_list, qtd_list_entry) {
+			qtd->in_process = 0;
+		}
+		return;
+	}
+
+	DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &qh->qtd_list, qtd_list_entry) {
+
+		urb = qtd->urb;
+
+		n_bytes = 0;
+		xfer_done = 0;
+
+		for (i = 0; i < qtd->n_desc; i++) {
+			dma_desc = &qh->desc_list[n_desc];
+
+			n_bytes = qh->n_bytes[n_desc];
+
+			failed =
+			    update_non_isoc_urb_state_ddma(hcd, hc, qtd,
+							   dma_desc,
+							   halt_status, n_bytes,
+							   &xfer_done);
+
+			if (failed
+			    || (xfer_done
+				&& (urb->status != -DWC_E_IN_PROGRESS))) {
+
+				hcd->fops->complete(hcd, urb->priv, urb,
+						    urb->status);
+				dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh);
+
+				if (failed)
+					goto stop_scan;
+			} else if (qh->ep_type == UE_CONTROL) {
+				if (qtd->control_phase == DWC_OTG_CONTROL_SETUP) {
+					if (urb->length > 0) {
+						qtd->control_phase = DWC_OTG_CONTROL_DATA;
+					} else {
+						qtd->control_phase = DWC_OTG_CONTROL_STATUS;
+					}
+					DWC_DEBUGPL(DBG_HCDV, "  Control setup transaction done\n");
+				} else if (qtd->control_phase == DWC_OTG_CONTROL_DATA) {
+					if (xfer_done) {
+						qtd->control_phase = DWC_OTG_CONTROL_STATUS;
+						DWC_DEBUGPL(DBG_HCDV, "  Control data transfer done\n");
+					} else if (i + 1 == qtd->n_desc) {
+						/*
+						 * Last descriptor for Control data stage which is
+						 * not completed yet.
+						 */
+						dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd);
+					}
+				}
+			}
+
+			n_desc++;
+		}
+
+	}
+
+stop_scan:
+
+	if (qh->ep_type != UE_CONTROL) {
+		/*
+		 * Resetting the data toggle for bulk
+		 * and interrupt endpoints in case of stall. See handle_hc_stall_intr()
+		 */
+		if (halt_status == DWC_OTG_HC_XFER_STALL)
+			qh->data_toggle = DWC_OTG_HC_PID_DATA0;
+		else
+			dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd);
+	}
+
+	if (halt_status == DWC_OTG_HC_XFER_COMPLETE) {
+		hcint_data_t hcint;
+		hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+		if (hcint.b.nyet) {
+			/*
+			 * Got a NYET on the last transaction of the transfer. It
+			 * means that the endpoint should be in the PING state at the
+			 * beginning of the next transfer.
+			 */
+			qh->ping_state = 1;
+			clear_hc_int(hc_regs, nyet);
+		}
+
+	}
+
+}
+
+/**
+ * This function is called from interrupt handlers.
+ * Scans the descriptor list, updates URB's status and
+ * calls completion routine for the URB if it's done.
+ * Releases the channel to be used by other transfers.
+ * In case of Isochronous endpoint the channel is not halted until
+ * the end of the session, i.e. QTD list is empty.
+ * If periodic channel released the FrameList is updated accordingly.
+ *
+ * Calls transaction selection routines to activate pending transfers.
+ *
+ * @param hcd The HCD state structure for the DWC OTG controller.
+ * @param hc Host channel, the transfer is completed on.
+ * @param hc_regs Host channel registers.
+ * @param halt_status Reason the channel is being halted,
+ *		      or just XferComplete for isochronous transfer
+ */
+void dwc_otg_hcd_complete_xfer_ddma(dwc_otg_hcd_t * hcd,
+				    dwc_hc_t * hc,
+				    dwc_otg_hc_regs_t * hc_regs,
+				    dwc_otg_halt_status_e halt_status)
+{
+	uint8_t continue_isoc_xfer = 0;
+	dwc_otg_transaction_type_e tr_type;
+	dwc_otg_qh_t *qh = hc->qh;
+
+	if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+
+		complete_isoc_xfer_ddma(hcd, hc, hc_regs, halt_status);
+
+		/* Release the channel if halted or session completed */
+		if (halt_status != DWC_OTG_HC_XFER_COMPLETE ||
+		    DWC_CIRCLEQ_EMPTY(&qh->qtd_list)) {
+
+			/* Halt the channel if session completed */
+			if (halt_status == DWC_OTG_HC_XFER_COMPLETE) {
+				dwc_otg_hc_halt(hcd->core_if, hc, halt_status);
+			}
+
+			release_channel_ddma(hcd, qh);
+			dwc_otg_hcd_qh_remove(hcd, qh);
+		} else {
+			/* Keep in assigned schedule to continue transfer */
+			DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_assigned,
+					   &qh->qh_list_entry);
+			continue_isoc_xfer = 1;
+
+		}
+		/** @todo Consider the case when period exceeds FrameList size.
+		 *  Frame Rollover interrupt should be used.
+		 */
+	} else {
+		/* Scan descriptor list to complete the URB(s), then release the channel */
+		complete_non_isoc_xfer_ddma(hcd, hc, hc_regs, halt_status);
+
+		release_channel_ddma(hcd, qh);
+		dwc_otg_hcd_qh_remove(hcd, qh);
+
+		if (!DWC_CIRCLEQ_EMPTY(&qh->qtd_list)) {
+			/* Add back to inactive non-periodic schedule on normal completion */
+			dwc_otg_hcd_qh_add(hcd, qh);
+		}
+
+	}
+	tr_type = dwc_otg_hcd_select_transactions(hcd);
+	if (tr_type != DWC_OTG_TRANSACTION_NONE || continue_isoc_xfer) {
+		if (continue_isoc_xfer) {
+			if (tr_type == DWC_OTG_TRANSACTION_NONE) {
+				tr_type = DWC_OTG_TRANSACTION_PERIODIC;
+			} else if (tr_type == DWC_OTG_TRANSACTION_NON_PERIODIC) {
+				tr_type = DWC_OTG_TRANSACTION_ALL;
+			}
+		}
+		dwc_otg_hcd_queue_transactions(hcd, tr_type);
+	}
+}
+
+#endif /* DWC_DEVICE_ONLY */
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd_if.h b/drivers/usb/host/dwc_otg/dwc_otg_hcd_if.h
new file mode 100644
index 000000000000..847c547d7ebb
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_if.h
@@ -0,0 +1,441 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_if.h $
+ * $Revision: #12 $
+ * $Date: 2011/10/26 $
+ * $Change: 1873028 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+#ifndef DWC_DEVICE_ONLY
+#ifndef __DWC_HCD_IF_H__
+#define __DWC_HCD_IF_H__
+
+#include "dwc_otg_core_if.h"
+
+/** @file
+ * This file defines DWC_OTG HCD Core API.
+ */
+
+struct dwc_otg_hcd;
+typedef struct dwc_otg_hcd dwc_otg_hcd_t;
+
+struct dwc_otg_hcd_urb;
+typedef struct dwc_otg_hcd_urb dwc_otg_hcd_urb_t;
+
+/** @name HCD Function Driver Callbacks */
+/** @{ */
+
+/** This function is called whenever core switches to host mode. */
+typedef int (*dwc_otg_hcd_start_cb_t) (dwc_otg_hcd_t * hcd);
+
+/** This function is called when device has been disconnected */
+typedef int (*dwc_otg_hcd_disconnect_cb_t) (dwc_otg_hcd_t * hcd);
+
+/** Wrapper provides this function to HCD to core, so it can get hub information to which device is connected */
+typedef int (*dwc_otg_hcd_hub_info_from_urb_cb_t) (dwc_otg_hcd_t * hcd,
+						   void *urb_handle,
+						   uint32_t * hub_addr,
+						   uint32_t * port_addr);
+/** Via this function HCD core gets device speed */
+typedef int (*dwc_otg_hcd_speed_from_urb_cb_t) (dwc_otg_hcd_t * hcd,
+						void *urb_handle);
+
+/** This function is called when urb is completed */
+typedef int (*dwc_otg_hcd_complete_urb_cb_t) (dwc_otg_hcd_t * hcd,
+					      void *urb_handle,
+					      dwc_otg_hcd_urb_t * dwc_otg_urb,
+					      int32_t status);
+
+/** Via this function HCD core gets b_hnp_enable parameter */
+typedef int (*dwc_otg_hcd_get_b_hnp_enable) (dwc_otg_hcd_t * hcd);
+
+struct dwc_otg_hcd_function_ops {
+	dwc_otg_hcd_start_cb_t start;
+	dwc_otg_hcd_disconnect_cb_t disconnect;
+	dwc_otg_hcd_hub_info_from_urb_cb_t hub_info;
+	dwc_otg_hcd_speed_from_urb_cb_t speed;
+	dwc_otg_hcd_complete_urb_cb_t complete;
+	dwc_otg_hcd_get_b_hnp_enable get_b_hnp_enable;
+};
+/** @} */
+
+/** @name HCD Core API */
+/** @{ */
+/** This function allocates dwc_otg_hcd structure and returns pointer on it. */
+extern dwc_otg_hcd_t *dwc_otg_hcd_alloc_hcd(void);
+
+/** This function should be called to initiate HCD Core.
+ *
+ * @param hcd The HCD
+ * @param core_if The DWC_OTG Core
+ *
+ * Returns -DWC_E_NO_MEMORY if no enough memory.
+ * Returns 0 on success
+ */
+extern int dwc_otg_hcd_init(dwc_otg_hcd_t * hcd, dwc_otg_core_if_t * core_if);
+
+/** Frees HCD
+ *
+ * @param hcd The HCD
+ */
+extern void dwc_otg_hcd_remove(dwc_otg_hcd_t * hcd);
+
+/** This function should be called on every hardware interrupt.
+ *
+ * @param dwc_otg_hcd The HCD
+ *
+ * Returns non zero if interrupt is handled
+ * Return 0 if interrupt is not handled
+ */
+extern int32_t dwc_otg_hcd_handle_intr(dwc_otg_hcd_t * dwc_otg_hcd);
+
+/** This function is used to handle the fast interrupt
+ *
+ */
+#ifdef CONFIG_ARM64
+extern void dwc_otg_hcd_handle_fiq(void);
+#else
+extern void __attribute__ ((naked)) dwc_otg_hcd_handle_fiq(void);
+#endif
+
+/**
+ * Returns private data set by
+ * dwc_otg_hcd_set_priv_data function.
+ *
+ * @param hcd The HCD
+ */
+extern void *dwc_otg_hcd_get_priv_data(dwc_otg_hcd_t * hcd);
+
+/**
+ * Set private data.
+ *
+ * @param hcd The HCD
+ * @param priv_data pointer to be stored in private data
+ */
+extern void dwc_otg_hcd_set_priv_data(dwc_otg_hcd_t * hcd, void *priv_data);
+
+/**
+ * This function initializes the HCD Core.
+ *
+ * @param hcd The HCD
+ * @param fops The Function Driver Operations data structure containing pointers to all callbacks.
+ *
+ * Returns -DWC_E_NO_DEVICE if Core is currently is in device mode.
+ * Returns 0 on success
+ */
+extern int dwc_otg_hcd_start(dwc_otg_hcd_t * hcd,
+			     struct dwc_otg_hcd_function_ops *fops);
+
+/**
+ * Halts the DWC_otg host mode operations in a clean manner. USB transfers are
+ * stopped.
+ *
+ * @param hcd The HCD
+ */
+extern void dwc_otg_hcd_stop(dwc_otg_hcd_t * hcd);
+
+/**
+ * Handles hub class-specific requests.
+ *
+ * @param dwc_otg_hcd The HCD
+ * @param typeReq Request Type
+ * @param wValue wValue from control request
+ * @param wIndex wIndex from control request
+ * @param buf data buffer
+ * @param wLength data buffer length
+ *
+ * Returns -DWC_E_INVALID if invalid argument is passed
+ * Returns 0 on success
+ */
+extern int dwc_otg_hcd_hub_control(dwc_otg_hcd_t * dwc_otg_hcd,
+				   uint16_t typeReq, uint16_t wValue,
+				   uint16_t wIndex, uint8_t * buf,
+				   uint16_t wLength);
+
+/**
+ * Returns otg port number.
+ *
+ * @param hcd The HCD
+ */
+extern uint32_t dwc_otg_hcd_otg_port(dwc_otg_hcd_t * hcd);
+
+/**
+ * Returns OTG version - either 1.3 or 2.0.
+ *
+ * @param core_if The core_if structure pointer
+ */
+extern uint16_t dwc_otg_get_otg_version(dwc_otg_core_if_t * core_if);
+
+/**
+ * Returns 1 if currently core is acting as B host, and 0 otherwise.
+ *
+ * @param hcd The HCD
+ */
+extern uint32_t dwc_otg_hcd_is_b_host(dwc_otg_hcd_t * hcd);
+
+/**
+ * Returns current frame number.
+ *
+ * @param hcd The HCD
+ */
+extern int dwc_otg_hcd_get_frame_number(dwc_otg_hcd_t * hcd);
+
+/**
+ * Dumps hcd state.
+ *
+ * @param hcd The HCD
+ */
+extern void dwc_otg_hcd_dump_state(dwc_otg_hcd_t * hcd);
+
+/**
+ * Dump the average frame remaining at SOF. This can be used to
+ * determine average interrupt latency. Frame remaining is also shown for
+ * start transfer and two additional sample points.
+ * Currently this function is not implemented.
+ *
+ * @param hcd The HCD
+ */
+extern void dwc_otg_hcd_dump_frrem(dwc_otg_hcd_t * hcd);
+
+/**
+ * Sends LPM transaction to the local device.
+ *
+ * @param hcd The HCD
+ * @param devaddr Device Address
+ * @param hird Host initiated resume duration
+ * @param bRemoteWake Value of bRemoteWake field in LPM transaction
+ *
+ * Returns negative value if sending LPM transaction was not succeeded.
+ * Returns 0 on success.
+ */
+extern int dwc_otg_hcd_send_lpm(dwc_otg_hcd_t * hcd, uint8_t devaddr,
+				uint8_t hird, uint8_t bRemoteWake);
+
+/* URB interface */
+
+/**
+ * Allocates memory for dwc_otg_hcd_urb structure.
+ * Allocated memory should be freed by call of DWC_FREE.
+ *
+ * @param hcd The HCD
+ * @param iso_desc_count Count of ISOC descriptors
+ * @param atomic_alloc Specefies whether to perform atomic allocation.
+ */
+extern dwc_otg_hcd_urb_t *dwc_otg_hcd_urb_alloc(dwc_otg_hcd_t * hcd,
+						int iso_desc_count,
+						int atomic_alloc);
+
+/**
+ * Set pipe information in URB.
+ *
+ * @param hcd_urb DWC_OTG URB
+ * @param devaddr Device Address
+ * @param ep_num Endpoint Number
+ * @param ep_type Endpoint Type
+ * @param ep_dir Endpoint Direction
+ * @param mps Max Packet Size
+ */
+extern void dwc_otg_hcd_urb_set_pipeinfo(dwc_otg_hcd_urb_t * hcd_urb,
+					 uint8_t devaddr, uint8_t ep_num,
+					 uint8_t ep_type, uint8_t ep_dir,
+					 uint16_t mps);
+
+/* Transfer flags */
+#define URB_GIVEBACK_ASAP 0x1
+#define URB_SEND_ZERO_PACKET 0x2
+
+/**
+ * Sets dwc_otg_hcd_urb parameters.
+ *
+ * @param urb DWC_OTG URB allocated by dwc_otg_hcd_urb_alloc function.
+ * @param urb_handle Unique handle for request, this will be passed back
+ * to function driver in completion callback.
+ * @param buf The buffer for the data
+ * @param dma The DMA buffer for the data
+ * @param buflen Transfer length
+ * @param sp Buffer for setup data
+ * @param sp_dma DMA address of setup data buffer
+ * @param flags Transfer flags
+ * @param interval Polling interval for interrupt or isochronous transfers.
+ */
+extern void dwc_otg_hcd_urb_set_params(dwc_otg_hcd_urb_t * urb,
+				       void *urb_handle, void *buf,
+				       dwc_dma_t dma, uint32_t buflen, void *sp,
+				       dwc_dma_t sp_dma, uint32_t flags,
+				       uint16_t interval);
+
+/** Gets status from dwc_otg_hcd_urb
+ *
+ * @param dwc_otg_urb DWC_OTG URB
+ */
+extern uint32_t dwc_otg_hcd_urb_get_status(dwc_otg_hcd_urb_t * dwc_otg_urb);
+
+/** Gets actual length from dwc_otg_hcd_urb
+ *
+ * @param dwc_otg_urb DWC_OTG URB
+ */
+extern uint32_t dwc_otg_hcd_urb_get_actual_length(dwc_otg_hcd_urb_t *
+						  dwc_otg_urb);
+
+/** Gets error count from dwc_otg_hcd_urb. Only for ISOC URBs
+ *
+ * @param dwc_otg_urb DWC_OTG URB
+ */
+extern uint32_t dwc_otg_hcd_urb_get_error_count(dwc_otg_hcd_urb_t *
+						dwc_otg_urb);
+
+/** Set ISOC descriptor offset and length
+ *
+ * @param dwc_otg_urb DWC_OTG URB
+ * @param desc_num ISOC descriptor number
+ * @param offset Offset from beginig of buffer.
+ * @param length Transaction length
+ */
+extern void dwc_otg_hcd_urb_set_iso_desc_params(dwc_otg_hcd_urb_t * dwc_otg_urb,
+						int desc_num, uint32_t offset,
+						uint32_t length);
+
+/** Get status of ISOC descriptor, specified by desc_num
+ *
+ * @param dwc_otg_urb DWC_OTG URB
+ * @param desc_num ISOC descriptor number
+ */
+extern uint32_t dwc_otg_hcd_urb_get_iso_desc_status(dwc_otg_hcd_urb_t *
+						    dwc_otg_urb, int desc_num);
+
+/** Get actual length of ISOC descriptor, specified by desc_num
+ *
+ * @param dwc_otg_urb DWC_OTG URB
+ * @param desc_num ISOC descriptor number
+ */
+extern uint32_t dwc_otg_hcd_urb_get_iso_desc_actual_length(dwc_otg_hcd_urb_t *
+							   dwc_otg_urb,
+							   int desc_num);
+
+/** Queue URB. After transfer is completes, the complete callback will be called with the URB status
+ *
+ * @param dwc_otg_hcd The HCD
+ * @param dwc_otg_urb DWC_OTG URB
+ * @param ep_handle Out parameter for returning endpoint handle
+ * @param atomic_alloc Flag to do atomic allocation if needed
+ *
+ * Returns -DWC_E_NO_DEVICE if no device is connected.
+ * Returns -DWC_E_NO_MEMORY if there is no enough memory.
+ * Returns 0 on success.
+ */
+extern int dwc_otg_hcd_urb_enqueue(dwc_otg_hcd_t * dwc_otg_hcd,
+				   dwc_otg_hcd_urb_t * dwc_otg_urb,
+				   void **ep_handle, int atomic_alloc);
+
+/** De-queue the specified URB
+ *
+ * @param dwc_otg_hcd The HCD
+ * @param dwc_otg_urb DWC_OTG URB
+ */
+extern int dwc_otg_hcd_urb_dequeue(dwc_otg_hcd_t * dwc_otg_hcd,
+				   dwc_otg_hcd_urb_t * dwc_otg_urb);
+
+/** Frees resources in the DWC_otg controller related to a given endpoint.
+ * Any URBs for the endpoint must already be dequeued.
+ *
+ * @param hcd The HCD
+ * @param ep_handle Endpoint handle, returned by dwc_otg_hcd_urb_enqueue function
+ * @param retry Number of retries if there are queued transfers.
+ *
+ * Returns -DWC_E_INVALID if invalid arguments are passed.
+ * Returns 0 on success
+ */
+extern int dwc_otg_hcd_endpoint_disable(dwc_otg_hcd_t * hcd, void *ep_handle,
+					int retry);
+
+/* Resets the data toggle in qh structure. This function can be called from
+ * usb_clear_halt routine.
+ *
+ * @param hcd The HCD
+ * @param ep_handle Endpoint handle, returned by dwc_otg_hcd_urb_enqueue function
+ *
+ * Returns -DWC_E_INVALID if invalid arguments are passed.
+ * Returns 0 on success
+ */
+extern int dwc_otg_hcd_endpoint_reset(dwc_otg_hcd_t * hcd, void *ep_handle);
+
+/** Returns 1 if status of specified port is changed and 0 otherwise.
+ *
+ * @param hcd The HCD
+ * @param port Port number
+ */
+extern int dwc_otg_hcd_is_status_changed(dwc_otg_hcd_t * hcd, int port);
+
+/** Call this function to check if bandwidth was allocated for specified endpoint.
+ * Only for ISOC and INTERRUPT endpoints.
+ *
+ * @param hcd The HCD
+ * @param ep_handle Endpoint handle
+ */
+extern int dwc_otg_hcd_is_bandwidth_allocated(dwc_otg_hcd_t * hcd,
+					      void *ep_handle);
+
+/** Call this function to check if bandwidth was freed for specified endpoint.
+ *
+ * @param hcd The HCD
+ * @param ep_handle Endpoint handle
+ */
+extern int dwc_otg_hcd_is_bandwidth_freed(dwc_otg_hcd_t * hcd, void *ep_handle);
+
+/** Returns bandwidth allocated for specified endpoint in microseconds.
+ * Only for ISOC and INTERRUPT endpoints.
+ *
+ * @param hcd The HCD
+ * @param ep_handle Endpoint handle
+ */
+extern uint8_t dwc_otg_hcd_get_ep_bandwidth(dwc_otg_hcd_t * hcd,
+					    void *ep_handle);
+
+extern int hcd_init(
+#ifdef LM_INTERFACE
+			   struct lm_device *_dev
+#elif  defined(PCI_INTERFACE)
+			   struct pci_dev *_dev
+#elif  defined(PLATFORM_INTERFACE)
+	struct platform_device *dev
+#endif
+    );
+
+extern void hcd_remove(
+#ifdef LM_INTERFACE
+			      struct lm_device *_dev
+#elif  defined(PCI_INTERFACE)
+			      struct pci_dev *_dev
+#elif  defined(PLATFORM_INTERFACE)
+	struct platform_device *_dev
+#endif
+    );
+
+/** @} */
+
+#endif /* __DWC_HCD_IF_H__ */
+#endif /* DWC_DEVICE_ONLY */
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c
new file mode 100644
index 000000000000..2a1617b475fc
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_intr.c
@@ -0,0 +1,2757 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_intr.c $
+ * $Revision: #89 $
+ * $Date: 2011/10/20 $
+ * $Change: 1869487 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+#ifndef DWC_DEVICE_ONLY
+
+#include "dwc_otg_hcd.h"
+#include "dwc_otg_regs.h"
+
+#include <linux/jiffies.h>
+#ifdef CONFIG_ARM
+#include <asm/fiq.h>
+#endif
+
+extern bool microframe_schedule;
+
+/** @file
+ * This file contains the implementation of the HCD Interrupt handlers.
+ */
+
+int fiq_done, int_done;
+
+#ifdef FIQ_DEBUG
+char buffer[1000*16];
+int wptr;
+void notrace _fiq_print(FIQDBG_T dbg_lvl, char *fmt, ...)
+{
+	FIQDBG_T dbg_lvl_req = FIQDBG_PORTHUB;
+	va_list args;
+	char text[17];
+	hfnum_data_t hfnum = { .d32 = FIQ_READ(dwc_regs_base + 0x408) };
+
+	if(dbg_lvl & dbg_lvl_req || dbg_lvl == FIQDBG_ERR)
+	{
+		local_fiq_disable();
+		snprintf(text, 9, "%4d%d:%d ", hfnum.b.frnum/8, hfnum.b.frnum%8, 8 - hfnum.b.frrem/937);
+		va_start(args, fmt);
+		vsnprintf(text+8, 9, fmt, args);
+		va_end(args);
+
+		memcpy(buffer + wptr, text, 16);
+		wptr = (wptr + 16) % sizeof(buffer);
+		local_fiq_enable();
+	}
+}
+#endif
+
+/** This function handles interrupts for the HCD. */
+int32_t dwc_otg_hcd_handle_intr(dwc_otg_hcd_t * dwc_otg_hcd)
+{
+	int retval = 0;
+	static int last_time;
+	dwc_otg_core_if_t *core_if = dwc_otg_hcd->core_if;
+	gintsts_data_t gintsts;
+	gintmsk_data_t gintmsk;
+	hfnum_data_t hfnum;
+	haintmsk_data_t haintmsk;
+
+#ifdef DEBUG
+	dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs;
+
+#endif
+
+	gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts);
+	gintmsk.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintmsk);
+
+	/* Exit from ISR if core is hibernated */
+	if (core_if->hibernation_suspend == 1) {
+		goto exit_handler_routine;
+	}
+	DWC_SPINLOCK(dwc_otg_hcd->lock);
+	/* Check if HOST Mode */
+	if (dwc_otg_is_host_mode(core_if)) {
+		if (fiq_enable) {
+			local_fiq_disable();
+			fiq_fsm_spin_lock(&dwc_otg_hcd->fiq_state->lock);
+			/* Pull in from the FIQ's disabled mask */
+			gintmsk.d32 = gintmsk.d32 | ~(dwc_otg_hcd->fiq_state->gintmsk_saved.d32);
+			dwc_otg_hcd->fiq_state->gintmsk_saved.d32 = ~0;
+		}
+
+		if (fiq_fsm_enable && ( 0x0000FFFF & ~(dwc_otg_hcd->fiq_state->haintmsk_saved.b2.chint))) {
+			gintsts.b.hcintr = 1;
+		}
+
+		/* Danger will robinson: fake a SOF if necessary */
+		if (fiq_fsm_enable && (dwc_otg_hcd->fiq_state->gintmsk_saved.b.sofintr == 1)) {
+			gintsts.b.sofintr = 1;
+		}
+		gintsts.d32 &= gintmsk.d32;
+
+		if (fiq_enable) {
+			fiq_fsm_spin_unlock(&dwc_otg_hcd->fiq_state->lock);
+			local_fiq_enable();
+		}
+
+		if (!gintsts.d32) {
+			goto exit_handler_routine;
+		}
+
+#ifdef DEBUG
+		// We should be OK doing this because the common interrupts should already have been serviced
+		/* Don't print debug message in the interrupt handler on SOF */
+#ifndef DEBUG_SOF
+		if (gintsts.d32 != DWC_SOF_INTR_MASK)
+#endif
+			DWC_DEBUGPL(DBG_HCDI, "\n");
+#endif
+
+#ifdef DEBUG
+#ifndef DEBUG_SOF
+		if (gintsts.d32 != DWC_SOF_INTR_MASK)
+#endif
+			DWC_DEBUGPL(DBG_HCDI,
+				    "DWC OTG HCD Interrupt Detected gintsts&gintmsk=0x%08x core_if=%p\n",
+				    gintsts.d32, core_if);
+#endif
+		hfnum.d32 = DWC_READ_REG32(&dwc_otg_hcd->core_if->host_if->host_global_regs->hfnum);
+		if (gintsts.b.sofintr) {
+			retval |= dwc_otg_hcd_handle_sof_intr(dwc_otg_hcd);
+		}
+
+		if (gintsts.b.rxstsqlvl) {
+			retval |=
+			    dwc_otg_hcd_handle_rx_status_q_level_intr
+			    (dwc_otg_hcd);
+		}
+		if (gintsts.b.nptxfempty) {
+			retval |=
+			    dwc_otg_hcd_handle_np_tx_fifo_empty_intr
+			    (dwc_otg_hcd);
+		}
+		if (gintsts.b.i2cintr) {
+			/** @todo Implement i2cintr handler. */
+		}
+		if (gintsts.b.portintr) {
+
+			gintmsk_data_t gintmsk = { .b.portintr = 1};
+			retval |= dwc_otg_hcd_handle_port_intr(dwc_otg_hcd);
+			if (fiq_enable) {
+				local_fiq_disable();
+				fiq_fsm_spin_lock(&dwc_otg_hcd->fiq_state->lock);
+				DWC_MODIFY_REG32(&dwc_otg_hcd->core_if->core_global_regs->gintmsk, 0, gintmsk.d32);
+				fiq_fsm_spin_unlock(&dwc_otg_hcd->fiq_state->lock);
+				local_fiq_enable();
+			} else {
+				DWC_MODIFY_REG32(&dwc_otg_hcd->core_if->core_global_regs->gintmsk, 0, gintmsk.d32);
+			}
+		}
+		if (gintsts.b.hcintr) {
+			retval |= dwc_otg_hcd_handle_hc_intr(dwc_otg_hcd);
+		}
+		if (gintsts.b.ptxfempty) {
+			retval |=
+			    dwc_otg_hcd_handle_perio_tx_fifo_empty_intr
+			    (dwc_otg_hcd);
+		}
+#ifdef DEBUG
+#ifndef DEBUG_SOF
+		if (gintsts.d32 != DWC_SOF_INTR_MASK)
+#endif
+		{
+			DWC_DEBUGPL(DBG_HCDI,
+				    "DWC OTG HCD Finished Servicing Interrupts\n");
+			DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD gintsts=0x%08x\n",
+				    DWC_READ_REG32(&global_regs->gintsts));
+			DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD gintmsk=0x%08x\n",
+				    DWC_READ_REG32(&global_regs->gintmsk));
+		}
+#endif
+
+#ifdef DEBUG
+#ifndef DEBUG_SOF
+		if (gintsts.d32 != DWC_SOF_INTR_MASK)
+#endif
+			DWC_DEBUGPL(DBG_HCDI, "\n");
+#endif
+
+	}
+
+exit_handler_routine:
+	if (fiq_enable)	{
+		gintmsk_data_t gintmsk_new;
+		haintmsk_data_t haintmsk_new;
+		local_fiq_disable();
+		fiq_fsm_spin_lock(&dwc_otg_hcd->fiq_state->lock);
+		gintmsk_new.d32 = *(volatile uint32_t *)&dwc_otg_hcd->fiq_state->gintmsk_saved.d32;
+		if(fiq_fsm_enable)
+			haintmsk_new.d32 = *(volatile uint32_t *)&dwc_otg_hcd->fiq_state->haintmsk_saved.d32;
+		else
+			haintmsk_new.d32 = 0x0000FFFF;
+
+		/* The FIQ could have sneaked another interrupt in. If so, don't clear MPHI */
+		if ((gintmsk_new.d32 == ~0) && (haintmsk_new.d32 == 0x0000FFFF)) {
+			if (dwc_otg_hcd->fiq_state->mphi_regs.swirq_clr) {
+				DWC_WRITE_REG32(dwc_otg_hcd->fiq_state->mphi_regs.swirq_clr, 1);
+			} else {
+				DWC_WRITE_REG32(dwc_otg_hcd->fiq_state->mphi_regs.intstat, (1<<16));
+			}
+			if (dwc_otg_hcd->fiq_state->mphi_int_count >= 50) {
+				fiq_print(FIQDBG_INT, dwc_otg_hcd->fiq_state, "MPHI CLR");
+					DWC_WRITE_REG32(dwc_otg_hcd->fiq_state->mphi_regs.ctrl, ((1<<31) + (1<<16)));
+					while (!(DWC_READ_REG32(dwc_otg_hcd->fiq_state->mphi_regs.ctrl) & (1 << 17)))
+						;
+					DWC_WRITE_REG32(dwc_otg_hcd->fiq_state->mphi_regs.ctrl, (1<<31));
+					dwc_otg_hcd->fiq_state->mphi_int_count = 0;
+			}
+			int_done++;
+		}
+		haintmsk.d32 = DWC_READ_REG32(&core_if->host_if->host_global_regs->haintmsk);
+		/* Re-enable interrupts that the FIQ masked (first time round) */
+		FIQ_WRITE(dwc_otg_hcd->fiq_state->dwc_regs_base + GINTMSK, gintmsk.d32);
+		fiq_fsm_spin_unlock(&dwc_otg_hcd->fiq_state->lock);
+		local_fiq_enable();
+
+		if ((jiffies / HZ) > last_time) {
+			//dwc_otg_qh_t *qh;
+			//dwc_list_link_t *cur;
+			/* Once a second output the fiq and irq numbers, useful for debug */
+			last_time = jiffies / HZ;
+		//	 DWC_WARN("np_kick=%d AHC=%d sched_frame=%d cur_frame=%d int_done=%d fiq_done=%d",
+		//	dwc_otg_hcd->fiq_state->kick_np_queues, dwc_otg_hcd->available_host_channels,
+		//	dwc_otg_hcd->fiq_state->next_sched_frame, hfnum.b.frnum, int_done, dwc_otg_hcd->fiq_state->fiq_done);
+			 //printk(KERN_WARNING "Periodic queues:\n");
+		}
+	}
+
+	DWC_SPINUNLOCK(dwc_otg_hcd->lock);
+	return retval;
+}
+
+#ifdef DWC_TRACK_MISSED_SOFS
+
+#warning Compiling code to track missed SOFs
+#define FRAME_NUM_ARRAY_SIZE 1000
+/**
+ * This function is for debug only.
+ */
+static inline void track_missed_sofs(uint16_t curr_frame_number)
+{
+	static uint16_t frame_num_array[FRAME_NUM_ARRAY_SIZE];
+	static uint16_t last_frame_num_array[FRAME_NUM_ARRAY_SIZE];
+	static int frame_num_idx = 0;
+	static uint16_t last_frame_num = DWC_HFNUM_MAX_FRNUM;
+	static int dumped_frame_num_array = 0;
+
+	if (frame_num_idx < FRAME_NUM_ARRAY_SIZE) {
+		if (((last_frame_num + 1) & DWC_HFNUM_MAX_FRNUM) !=
+		    curr_frame_number) {
+			frame_num_array[frame_num_idx] = curr_frame_number;
+			last_frame_num_array[frame_num_idx++] = last_frame_num;
+		}
+	} else if (!dumped_frame_num_array) {
+		int i;
+		DWC_PRINTF("Frame     Last Frame\n");
+		DWC_PRINTF("-----     ----------\n");
+		for (i = 0; i < FRAME_NUM_ARRAY_SIZE; i++) {
+			DWC_PRINTF("0x%04x    0x%04x\n",
+				   frame_num_array[i], last_frame_num_array[i]);
+		}
+		dumped_frame_num_array = 1;
+	}
+	last_frame_num = curr_frame_number;
+}
+#endif
+
+/**
+ * Handles the start-of-frame interrupt in host mode. Non-periodic
+ * transactions may be queued to the DWC_otg controller for the current
+ * (micro)frame. Periodic transactions may be queued to the controller for the
+ * next (micro)frame.
+ */
+int32_t dwc_otg_hcd_handle_sof_intr(dwc_otg_hcd_t * hcd)
+{
+	hfnum_data_t hfnum;
+	gintsts_data_t gintsts = { .d32 = 0 };
+	dwc_list_link_t *qh_entry;
+	dwc_otg_qh_t *qh;
+	dwc_otg_transaction_type_e tr_type;
+	int did_something = 0;
+	int32_t next_sched_frame = -1;
+
+	hfnum.d32 =
+	    DWC_READ_REG32(&hcd->core_if->host_if->host_global_regs->hfnum);
+
+#ifdef DEBUG_SOF
+	DWC_DEBUGPL(DBG_HCD, "--Start of Frame Interrupt--\n");
+#endif
+	hcd->frame_number = hfnum.b.frnum;
+
+#ifdef DEBUG
+	hcd->frrem_accum += hfnum.b.frrem;
+	hcd->frrem_samples++;
+#endif
+
+#ifdef DWC_TRACK_MISSED_SOFS
+	track_missed_sofs(hcd->frame_number);
+#endif
+	/* Determine whether any periodic QHs should be executed. */
+	qh_entry = DWC_LIST_FIRST(&hcd->periodic_sched_inactive);
+	while (qh_entry != &hcd->periodic_sched_inactive) {
+		qh = DWC_LIST_ENTRY(qh_entry, dwc_otg_qh_t, qh_list_entry);
+		qh_entry = qh_entry->next;
+		if (dwc_frame_num_le(qh->sched_frame, hcd->frame_number)) {
+
+			/*
+			 * Move QH to the ready list to be executed next
+			 * (micro)frame.
+			 */
+			DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_ready,
+					   &qh->qh_list_entry);
+
+			did_something = 1;
+		}
+		else
+		{
+			if(next_sched_frame < 0 || dwc_frame_num_le(qh->sched_frame, next_sched_frame))
+			{
+				next_sched_frame = qh->sched_frame;
+			}
+		}
+	}
+	if (fiq_enable)
+		hcd->fiq_state->next_sched_frame = next_sched_frame;
+
+	tr_type = dwc_otg_hcd_select_transactions(hcd);
+	if (tr_type != DWC_OTG_TRANSACTION_NONE) {
+		dwc_otg_hcd_queue_transactions(hcd, tr_type);
+		did_something = 1;
+	}
+
+	/* Clear interrupt - but do not trample on the FIQ sof */
+	if (!fiq_fsm_enable) {
+		gintsts.b.sofintr = 1;
+		DWC_WRITE_REG32(&hcd->core_if->core_global_regs->gintsts, gintsts.d32);
+	}
+	return 1;
+}
+
+/** Handles the Rx Status Queue Level Interrupt, which indicates that there is at
+ * least one packet in the Rx FIFO.  The packets are moved from the FIFO to
+ * memory if the DWC_otg controller is operating in Slave mode. */
+int32_t dwc_otg_hcd_handle_rx_status_q_level_intr(dwc_otg_hcd_t * dwc_otg_hcd)
+{
+	host_grxsts_data_t grxsts;
+	dwc_hc_t *hc = NULL;
+
+	DWC_DEBUGPL(DBG_HCD, "--RxStsQ Level Interrupt--\n");
+
+	grxsts.d32 =
+	    DWC_READ_REG32(&dwc_otg_hcd->core_if->core_global_regs->grxstsp);
+
+	hc = dwc_otg_hcd->hc_ptr_array[grxsts.b.chnum];
+	if (!hc) {
+		DWC_ERROR("Unable to get corresponding channel\n");
+		return 0;
+	}
+
+	/* Packet Status */
+	DWC_DEBUGPL(DBG_HCDV, "    Ch num = %d\n", grxsts.b.chnum);
+	DWC_DEBUGPL(DBG_HCDV, "    Count = %d\n", grxsts.b.bcnt);
+	DWC_DEBUGPL(DBG_HCDV, "    DPID = %d, hc.dpid = %d\n", grxsts.b.dpid,
+		    hc->data_pid_start);
+	DWC_DEBUGPL(DBG_HCDV, "    PStatus = %d\n", grxsts.b.pktsts);
+
+	switch (grxsts.b.pktsts) {
+	case DWC_GRXSTS_PKTSTS_IN:
+		/* Read the data into the host buffer. */
+		if (grxsts.b.bcnt > 0) {
+			dwc_otg_read_packet(dwc_otg_hcd->core_if,
+					    hc->xfer_buff, grxsts.b.bcnt);
+
+			/* Update the HC fields for the next packet received. */
+			hc->xfer_count += grxsts.b.bcnt;
+			hc->xfer_buff += grxsts.b.bcnt;
+		}
+		break;
+	case DWC_GRXSTS_PKTSTS_IN_XFER_COMP:
+	case DWC_GRXSTS_PKTSTS_DATA_TOGGLE_ERR:
+	case DWC_GRXSTS_PKTSTS_CH_HALTED:
+		/* Handled in interrupt, just ignore data */
+		break;
+	default:
+		DWC_ERROR("RX_STS_Q Interrupt: Unknown status %d\n",
+			  grxsts.b.pktsts);
+		break;
+	}
+
+	return 1;
+}
+
+/** This interrupt occurs when the non-periodic Tx FIFO is half-empty. More
+ * data packets may be written to the FIFO for OUT transfers. More requests
+ * may be written to the non-periodic request queue for IN transfers. This
+ * interrupt is enabled only in Slave mode. */
+int32_t dwc_otg_hcd_handle_np_tx_fifo_empty_intr(dwc_otg_hcd_t * dwc_otg_hcd)
+{
+	DWC_DEBUGPL(DBG_HCD, "--Non-Periodic TxFIFO Empty Interrupt--\n");
+	dwc_otg_hcd_queue_transactions(dwc_otg_hcd,
+				       DWC_OTG_TRANSACTION_NON_PERIODIC);
+	return 1;
+}
+
+/** This interrupt occurs when the periodic Tx FIFO is half-empty. More data
+ * packets may be written to the FIFO for OUT transfers. More requests may be
+ * written to the periodic request queue for IN transfers. This interrupt is
+ * enabled only in Slave mode. */
+int32_t dwc_otg_hcd_handle_perio_tx_fifo_empty_intr(dwc_otg_hcd_t * dwc_otg_hcd)
+{
+	DWC_DEBUGPL(DBG_HCD, "--Periodic TxFIFO Empty Interrupt--\n");
+	dwc_otg_hcd_queue_transactions(dwc_otg_hcd,
+				       DWC_OTG_TRANSACTION_PERIODIC);
+	return 1;
+}
+
+/** There are multiple conditions that can cause a port interrupt. This function
+ * determines which interrupt conditions have occurred and handles them
+ * appropriately. */
+int32_t dwc_otg_hcd_handle_port_intr(dwc_otg_hcd_t * dwc_otg_hcd)
+{
+	int retval = 0;
+	hprt0_data_t hprt0;
+	hprt0_data_t hprt0_modify;
+
+	hprt0.d32 = DWC_READ_REG32(dwc_otg_hcd->core_if->host_if->hprt0);
+	hprt0_modify.d32 = DWC_READ_REG32(dwc_otg_hcd->core_if->host_if->hprt0);
+
+	/* Clear appropriate bits in HPRT0 to clear the interrupt bit in
+	 * GINTSTS */
+
+	hprt0_modify.b.prtena = 0;
+	hprt0_modify.b.prtconndet = 0;
+	hprt0_modify.b.prtenchng = 0;
+	hprt0_modify.b.prtovrcurrchng = 0;
+
+	/* Port Connect Detected
+	 * Set flag and clear if detected */
+	if (dwc_otg_hcd->core_if->hibernation_suspend == 1) {
+		// Dont modify port status if we are in hibernation state
+		hprt0_modify.b.prtconndet = 1;
+		hprt0_modify.b.prtenchng = 1;
+		DWC_WRITE_REG32(dwc_otg_hcd->core_if->host_if->hprt0, hprt0_modify.d32);
+		hprt0.d32 = DWC_READ_REG32(dwc_otg_hcd->core_if->host_if->hprt0);
+		return retval;
+	}
+
+	if (hprt0.b.prtconndet) {
+		/** @todo - check if steps performed in 'else' block should be perfromed regardles adp */
+		if (dwc_otg_hcd->core_if->adp_enable &&
+				dwc_otg_hcd->core_if->adp.vbuson_timer_started == 1) {
+			DWC_PRINTF("PORT CONNECT DETECTED ----------------\n");
+			DWC_TIMER_CANCEL(dwc_otg_hcd->core_if->adp.vbuson_timer);
+			dwc_otg_hcd->core_if->adp.vbuson_timer_started = 0;
+			/* TODO - check if this is required, as
+			 * host initialization was already performed
+			 * after initial ADP probing
+			 */
+			/*dwc_otg_hcd->core_if->adp.vbuson_timer_started = 0;
+			dwc_otg_core_init(dwc_otg_hcd->core_if);
+			dwc_otg_enable_global_interrupts(dwc_otg_hcd->core_if);
+			cil_hcd_start(dwc_otg_hcd->core_if);*/
+		} else {
+
+			DWC_DEBUGPL(DBG_HCD, "--Port Interrupt HPRT0=0x%08x "
+				    "Port Connect Detected--\n", hprt0.d32);
+			dwc_otg_hcd->flags.b.port_connect_status_change = 1;
+			dwc_otg_hcd->flags.b.port_connect_status = 1;
+			hprt0_modify.b.prtconndet = 1;
+
+			/* B-Device has connected, Delete the connection timer. */
+			DWC_TIMER_CANCEL(dwc_otg_hcd->conn_timer);
+		}
+		/* The Hub driver asserts a reset when it sees port connect
+		 * status change flag */
+		retval |= 1;
+	}
+
+	/* Port Enable Changed
+	 * Clear if detected - Set internal flag if disabled */
+	if (hprt0.b.prtenchng) {
+		DWC_DEBUGPL(DBG_HCD, "  --Port Interrupt HPRT0=0x%08x "
+			    "Port Enable Changed--\n", hprt0.d32);
+		hprt0_modify.b.prtenchng = 1;
+		if (hprt0.b.prtena == 1) {
+			hfir_data_t hfir;
+			int do_reset = 0;
+			dwc_otg_core_params_t *params =
+			    dwc_otg_hcd->core_if->core_params;
+			dwc_otg_core_global_regs_t *global_regs =
+			    dwc_otg_hcd->core_if->core_global_regs;
+			dwc_otg_host_if_t *host_if =
+			    dwc_otg_hcd->core_if->host_if;
+
+			dwc_otg_hcd->flags.b.port_speed = hprt0.b.prtspd;
+			if (microframe_schedule)
+				init_hcd_usecs(dwc_otg_hcd);
+
+			/* Every time when port enables calculate
+			 * HFIR.FrInterval
+			 */
+			hfir.d32 = DWC_READ_REG32(&host_if->host_global_regs->hfir);
+			hfir.b.frint = calc_frame_interval(dwc_otg_hcd->core_if);
+			DWC_WRITE_REG32(&host_if->host_global_regs->hfir, hfir.d32);
+
+			/* Check if we need to adjust the PHY clock speed for
+			 * low power and adjust it */
+			if (params->host_support_fs_ls_low_power) {
+				gusbcfg_data_t usbcfg;
+
+				usbcfg.d32 =
+				    DWC_READ_REG32(&global_regs->gusbcfg);
+
+				if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_LOW_SPEED
+				    || hprt0.b.prtspd ==
+				    DWC_HPRT0_PRTSPD_FULL_SPEED) {
+					/*
+					 * Low power
+					 */
+					hcfg_data_t hcfg;
+					if (usbcfg.b.phylpwrclksel == 0) {
+						/* Set PHY low power clock select for FS/LS devices */
+						usbcfg.b.phylpwrclksel = 1;
+						DWC_WRITE_REG32
+						    (&global_regs->gusbcfg,
+						     usbcfg.d32);
+						do_reset = 1;
+					}
+
+					hcfg.d32 =
+					    DWC_READ_REG32
+					    (&host_if->host_global_regs->hcfg);
+
+					if (hprt0.b.prtspd ==
+					    DWC_HPRT0_PRTSPD_LOW_SPEED
+					    && params->host_ls_low_power_phy_clk
+					    ==
+					    DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ)
+					{
+						/* 6 MHZ */
+						DWC_DEBUGPL(DBG_CIL,
+							    "FS_PHY programming HCFG to 6 MHz (Low Power)\n");
+						if (hcfg.b.fslspclksel !=
+						    DWC_HCFG_6_MHZ) {
+							hcfg.b.fslspclksel =
+							    DWC_HCFG_6_MHZ;
+							DWC_WRITE_REG32
+							    (&host_if->host_global_regs->hcfg,
+							     hcfg.d32);
+							do_reset = 1;
+						}
+					} else {
+						/* 48 MHZ */
+						DWC_DEBUGPL(DBG_CIL,
+							    "FS_PHY programming HCFG to 48 MHz ()\n");
+						if (hcfg.b.fslspclksel !=
+						    DWC_HCFG_48_MHZ) {
+							hcfg.b.fslspclksel =
+							    DWC_HCFG_48_MHZ;
+							DWC_WRITE_REG32
+							    (&host_if->host_global_regs->hcfg,
+							     hcfg.d32);
+							do_reset = 1;
+						}
+					}
+				} else {
+					/*
+					 * Not low power
+					 */
+					if (usbcfg.b.phylpwrclksel == 1) {
+						usbcfg.b.phylpwrclksel = 0;
+						DWC_WRITE_REG32
+						    (&global_regs->gusbcfg,
+						     usbcfg.d32);
+						do_reset = 1;
+					}
+				}
+
+				if (do_reset) {
+					DWC_TASK_SCHEDULE(dwc_otg_hcd->reset_tasklet);
+				}
+			}
+
+			if (!do_reset) {
+				/* Port has been enabled set the reset change flag */
+				dwc_otg_hcd->flags.b.port_reset_change = 1;
+			}
+		} else {
+			dwc_otg_hcd->flags.b.port_enable_change = 1;
+		}
+		retval |= 1;
+	}
+
+	/** Overcurrent Change Interrupt */
+	if (hprt0.b.prtovrcurrchng) {
+		DWC_DEBUGPL(DBG_HCD, "  --Port Interrupt HPRT0=0x%08x "
+			    "Port Overcurrent Changed--\n", hprt0.d32);
+		dwc_otg_hcd->flags.b.port_over_current_change = 1;
+		hprt0_modify.b.prtovrcurrchng = 1;
+		retval |= 1;
+	}
+
+	/* Clear Port Interrupts */
+	DWC_WRITE_REG32(dwc_otg_hcd->core_if->host_if->hprt0, hprt0_modify.d32);
+
+	return retval;
+}
+
+/** This interrupt indicates that one or more host channels has a pending
+ * interrupt. There are multiple conditions that can cause each host channel
+ * interrupt. This function determines which conditions have occurred for each
+ * host channel interrupt and handles them appropriately. */
+int32_t dwc_otg_hcd_handle_hc_intr(dwc_otg_hcd_t * dwc_otg_hcd)
+{
+	int i;
+	int retval = 0;
+	haint_data_t haint = { .d32 = 0 } ;
+
+	/* Clear appropriate bits in HCINTn to clear the interrupt bit in
+	 * GINTSTS */
+
+	if (!fiq_fsm_enable)
+		haint.d32 = dwc_otg_read_host_all_channels_intr(dwc_otg_hcd->core_if);
+
+	// Overwrite with saved interrupts from fiq handler
+	if(fiq_fsm_enable)
+	{
+		/* check the mask? */
+		local_fiq_disable();
+		fiq_fsm_spin_lock(&dwc_otg_hcd->fiq_state->lock);
+		haint.b2.chint |= ~(dwc_otg_hcd->fiq_state->haintmsk_saved.b2.chint);
+		dwc_otg_hcd->fiq_state->haintmsk_saved.b2.chint = ~0;
+		fiq_fsm_spin_unlock(&dwc_otg_hcd->fiq_state->lock);
+		local_fiq_enable();
+	}
+
+	for (i = 0; i < dwc_otg_hcd->core_if->core_params->host_channels; i++) {
+		if (haint.b2.chint & (1 << i)) {
+			retval |= dwc_otg_hcd_handle_hc_n_intr(dwc_otg_hcd, i);
+		}
+	}
+
+	return retval;
+}
+
+/**
+ * Gets the actual length of a transfer after the transfer halts. _halt_status
+ * holds the reason for the halt.
+ *
+ * For IN transfers where halt_status is DWC_OTG_HC_XFER_COMPLETE,
+ * *short_read is set to 1 upon return if less than the requested
+ * number of bytes were transferred. Otherwise, *short_read is set to 0 upon
+ * return. short_read may also be NULL on entry, in which case it remains
+ * unchanged.
+ */
+static uint32_t get_actual_xfer_length(dwc_hc_t * hc,
+				       dwc_otg_hc_regs_t * hc_regs,
+				       dwc_otg_qtd_t * qtd,
+				       dwc_otg_halt_status_e halt_status,
+				       int *short_read)
+{
+	hctsiz_data_t hctsiz;
+	uint32_t length;
+
+	if (short_read != NULL) {
+		*short_read = 0;
+	}
+	hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz);
+
+	if (halt_status == DWC_OTG_HC_XFER_COMPLETE) {
+		if (hc->ep_is_in) {
+			length = hc->xfer_len - hctsiz.b.xfersize;
+			if (short_read != NULL) {
+				*short_read = (hctsiz.b.xfersize != 0);
+			}
+		} else if (hc->qh->do_split) {
+				//length = split_out_xfersize[hc->hc_num];
+				length = qtd->ssplit_out_xfer_count;
+		} else {
+			length = hc->xfer_len;
+		}
+	} else {
+		/*
+		 * Must use the hctsiz.pktcnt field to determine how much data
+		 * has been transferred. This field reflects the number of
+		 * packets that have been transferred via the USB. This is
+		 * always an integral number of packets if the transfer was
+		 * halted before its normal completion. (Can't use the
+		 * hctsiz.xfersize field because that reflects the number of
+		 * bytes transferred via the AHB, not the USB).
+		 */
+		length =
+		    (hc->start_pkt_count - hctsiz.b.pktcnt) * hc->max_packet;
+	}
+
+	return length;
+}
+
+/**
+ * Updates the state of the URB after a Transfer Complete interrupt on the
+ * host channel. Updates the actual_length field of the URB based on the
+ * number of bytes transferred via the host channel. Sets the URB status
+ * if the data transfer is finished.
+ *
+ * @return 1 if the data transfer specified by the URB is completely finished,
+ * 0 otherwise.
+ */
+static int update_urb_state_xfer_comp(dwc_hc_t * hc,
+				      dwc_otg_hc_regs_t * hc_regs,
+				      dwc_otg_hcd_urb_t * urb,
+				      dwc_otg_qtd_t * qtd)
+{
+	int xfer_done = 0;
+	int short_read = 0;
+
+	int xfer_length;
+
+	xfer_length = get_actual_xfer_length(hc, hc_regs, qtd,
+					     DWC_OTG_HC_XFER_COMPLETE,
+					     &short_read);
+
+	if (urb->actual_length + xfer_length > urb->length) {
+		printk_once(KERN_DEBUG "dwc_otg: DEVICE:%03d : %s:%d:trimming xfer length\n",
+			hc->dev_addr, __func__, __LINE__);
+		xfer_length = urb->length - urb->actual_length;
+	}
+
+	/* non DWORD-aligned buffer case handling. */
+	if (hc->align_buff && xfer_length && hc->ep_is_in) {
+		dwc_memcpy(urb->buf + urb->actual_length, hc->qh->dw_align_buf,
+			   xfer_length);
+	}
+
+	urb->actual_length += xfer_length;
+
+	if (xfer_length && (hc->ep_type == DWC_OTG_EP_TYPE_BULK) &&
+	    (urb->flags & URB_SEND_ZERO_PACKET)
+	    && (urb->actual_length == urb->length)
+	    && !(urb->length % hc->max_packet)) {
+		xfer_done = 0;
+	} else if (short_read || urb->actual_length >= urb->length) {
+		xfer_done = 1;
+		urb->status = 0;
+	}
+
+#ifdef DEBUG
+	{
+		hctsiz_data_t hctsiz;
+		hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz);
+		DWC_DEBUGPL(DBG_HCDV, "DWC_otg: %s: %s, channel %d\n",
+			    __func__, (hc->ep_is_in ? "IN" : "OUT"),
+			    hc->hc_num);
+		DWC_DEBUGPL(DBG_HCDV, "  hc->xfer_len %d\n", hc->xfer_len);
+		DWC_DEBUGPL(DBG_HCDV, "  hctsiz.xfersize %d\n",
+			    hctsiz.b.xfersize);
+		DWC_DEBUGPL(DBG_HCDV, "  urb->transfer_buffer_length %d\n",
+			    urb->length);
+		DWC_DEBUGPL(DBG_HCDV, "  urb->actual_length %d\n",
+			    urb->actual_length);
+		DWC_DEBUGPL(DBG_HCDV, "  short_read %d, xfer_done %d\n",
+			    short_read, xfer_done);
+	}
+#endif
+
+	return xfer_done;
+}
+
+/*
+ * Save the starting data toggle for the next transfer. The data toggle is
+ * saved in the QH for non-control transfers and it's saved in the QTD for
+ * control transfers.
+ */
+void dwc_otg_hcd_save_data_toggle(dwc_hc_t * hc,
+			     dwc_otg_hc_regs_t * hc_regs, dwc_otg_qtd_t * qtd)
+{
+	hctsiz_data_t hctsiz;
+	hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz);
+
+	if (hc->ep_type != DWC_OTG_EP_TYPE_CONTROL) {
+		dwc_otg_qh_t *qh = hc->qh;
+		if (hctsiz.b.pid == DWC_HCTSIZ_DATA0) {
+			qh->data_toggle = DWC_OTG_HC_PID_DATA0;
+		} else {
+			qh->data_toggle = DWC_OTG_HC_PID_DATA1;
+		}
+	} else {
+		if (hctsiz.b.pid == DWC_HCTSIZ_DATA0) {
+			qtd->data_toggle = DWC_OTG_HC_PID_DATA0;
+		} else {
+			qtd->data_toggle = DWC_OTG_HC_PID_DATA1;
+		}
+	}
+}
+
+/**
+ * Updates the state of an Isochronous URB when the transfer is stopped for
+ * any reason. The fields of the current entry in the frame descriptor array
+ * are set based on the transfer state and the input _halt_status. Completes
+ * the Isochronous URB if all the URB frames have been completed.
+ *
+ * @return DWC_OTG_HC_XFER_COMPLETE if there are more frames remaining to be
+ * transferred in the URB. Otherwise return DWC_OTG_HC_XFER_URB_COMPLETE.
+ */
+static dwc_otg_halt_status_e
+update_isoc_urb_state(dwc_otg_hcd_t * hcd,
+		      dwc_hc_t * hc,
+		      dwc_otg_hc_regs_t * hc_regs,
+		      dwc_otg_qtd_t * qtd, dwc_otg_halt_status_e halt_status)
+{
+	dwc_otg_hcd_urb_t *urb = qtd->urb;
+	dwc_otg_halt_status_e ret_val = halt_status;
+	struct dwc_otg_hcd_iso_packet_desc *frame_desc;
+
+	frame_desc = &urb->iso_descs[qtd->isoc_frame_index];
+	switch (halt_status) {
+	case DWC_OTG_HC_XFER_COMPLETE:
+		frame_desc->status = 0;
+		frame_desc->actual_length =
+		    get_actual_xfer_length(hc, hc_regs, qtd, halt_status, NULL);
+
+		/* non DWORD-aligned buffer case handling. */
+		if (hc->align_buff && frame_desc->actual_length && hc->ep_is_in) {
+			dwc_memcpy(urb->buf + frame_desc->offset + qtd->isoc_split_offset,
+				   hc->qh->dw_align_buf, frame_desc->actual_length);
+		}
+
+		break;
+	case DWC_OTG_HC_XFER_FRAME_OVERRUN:
+		urb->error_count++;
+		if (hc->ep_is_in) {
+			frame_desc->status = -DWC_E_NO_STREAM_RES;
+		} else {
+			frame_desc->status = -DWC_E_COMMUNICATION;
+		}
+		frame_desc->actual_length = 0;
+		break;
+	case DWC_OTG_HC_XFER_BABBLE_ERR:
+		urb->error_count++;
+		frame_desc->status = -DWC_E_OVERFLOW;
+		/* Don't need to update actual_length in this case. */
+		break;
+	case DWC_OTG_HC_XFER_XACT_ERR:
+		urb->error_count++;
+		frame_desc->status = -DWC_E_PROTOCOL;
+		frame_desc->actual_length =
+		    get_actual_xfer_length(hc, hc_regs, qtd, halt_status, NULL);
+
+		/* non DWORD-aligned buffer case handling. */
+		if (hc->align_buff && frame_desc->actual_length && hc->ep_is_in) {
+			dwc_memcpy(urb->buf + frame_desc->offset + qtd->isoc_split_offset,
+				   hc->qh->dw_align_buf, frame_desc->actual_length);
+		}
+		/* Skip whole frame */
+		if (hc->qh->do_split && (hc->ep_type == DWC_OTG_EP_TYPE_ISOC) &&
+		    hc->ep_is_in && hcd->core_if->dma_enable) {
+			qtd->complete_split = 0;
+			qtd->isoc_split_offset = 0;
+		}
+
+		break;
+	default:
+		DWC_ASSERT(1, "Unhandled _halt_status (%d)\n", halt_status);
+		break;
+	}
+	if (++qtd->isoc_frame_index == urb->packet_count) {
+		/*
+		 * urb->status is not used for isoc transfers.
+		 * The individual frame_desc statuses are used instead.
+		 */
+		hcd->fops->complete(hcd, urb->priv, urb, 0);
+		ret_val = DWC_OTG_HC_XFER_URB_COMPLETE;
+	} else {
+		ret_val = DWC_OTG_HC_XFER_COMPLETE;
+	}
+	return ret_val;
+}
+
+/**
+ * Frees the first QTD in the QH's list if free_qtd is 1. For non-periodic
+ * QHs, removes the QH from the active non-periodic schedule. If any QTDs are
+ * still linked to the QH, the QH is added to the end of the inactive
+ * non-periodic schedule. For periodic QHs, removes the QH from the periodic
+ * schedule if no more QTDs are linked to the QH.
+ */
+static void deactivate_qh(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, int free_qtd)
+{
+	int continue_split = 0;
+	dwc_otg_qtd_t *qtd;
+
+	DWC_DEBUGPL(DBG_HCDV, "  %s(%p,%p,%d)\n", __func__, hcd, qh, free_qtd);
+
+	qtd = DWC_CIRCLEQ_FIRST(&qh->qtd_list);
+
+	if (qtd->complete_split) {
+		continue_split = 1;
+	} else if (qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_MID ||
+		   qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_END) {
+		continue_split = 1;
+	}
+
+	if (free_qtd) {
+		dwc_otg_hcd_qtd_remove_and_free(hcd, qtd, qh);
+		continue_split = 0;
+	}
+
+	qh->channel = NULL;
+	dwc_otg_hcd_qh_deactivate(hcd, qh, continue_split);
+}
+
+/**
+ * Releases a host channel for use by other transfers. Attempts to select and
+ * queue more transactions since at least one host channel is available.
+ *
+ * @param hcd The HCD state structure.
+ * @param hc The host channel to release.
+ * @param qtd The QTD associated with the host channel. This QTD may be freed
+ * if the transfer is complete or an error has occurred.
+ * @param halt_status Reason the channel is being released. This status
+ * determines the actions taken by this function.
+ */
+static void release_channel(dwc_otg_hcd_t * hcd,
+			    dwc_hc_t * hc,
+			    dwc_otg_qtd_t * qtd,
+			    dwc_otg_halt_status_e halt_status)
+{
+	dwc_otg_transaction_type_e tr_type;
+	int free_qtd;
+
+	int hog_port = 0;
+
+	DWC_DEBUGPL(DBG_HCDV, "  %s: channel %d, halt_status %d, xfer_len %d\n",
+		    __func__, hc->hc_num, halt_status, hc->xfer_len);
+
+	if(fiq_fsm_enable && hc->do_split) {
+		if(!hc->ep_is_in && hc->ep_type == UE_ISOCHRONOUS) {
+			if(hc->xact_pos == DWC_HCSPLIT_XACTPOS_MID ||
+					hc->xact_pos == DWC_HCSPLIT_XACTPOS_BEGIN) {
+				hog_port = 0;
+			}
+		}
+	}
+
+	switch (halt_status) {
+	case DWC_OTG_HC_XFER_URB_COMPLETE:
+		free_qtd = 1;
+		break;
+	case DWC_OTG_HC_XFER_AHB_ERR:
+	case DWC_OTG_HC_XFER_STALL:
+	case DWC_OTG_HC_XFER_BABBLE_ERR:
+		free_qtd = 1;
+		break;
+	case DWC_OTG_HC_XFER_XACT_ERR:
+		if (qtd->error_count >= 3) {
+			DWC_DEBUGPL(DBG_HCDV,
+				    "  Complete URB with transaction error\n");
+			free_qtd = 1;
+			qtd->urb->status = -DWC_E_PROTOCOL;
+			hcd->fops->complete(hcd, qtd->urb->priv,
+					    qtd->urb, -DWC_E_PROTOCOL);
+		} else {
+			free_qtd = 0;
+		}
+		break;
+	case DWC_OTG_HC_XFER_URB_DEQUEUE:
+		/*
+		 * The QTD has already been removed and the QH has been
+		 * deactivated. Don't want to do anything except release the
+		 * host channel and try to queue more transfers.
+		 */
+		goto cleanup;
+	case DWC_OTG_HC_XFER_NO_HALT_STATUS:
+		free_qtd = 0;
+		break;
+	case DWC_OTG_HC_XFER_PERIODIC_INCOMPLETE:
+		DWC_DEBUGPL(DBG_HCDV,
+			"  Complete URB with I/O error\n");
+		free_qtd = 1;
+		qtd->urb->status = -DWC_E_IO;
+		hcd->fops->complete(hcd, qtd->urb->priv,
+			qtd->urb, -DWC_E_IO);
+		break;
+	default:
+		free_qtd = 0;
+		break;
+	}
+
+	deactivate_qh(hcd, hc->qh, free_qtd);
+
+cleanup:
+	/*
+	 * Release the host channel for use by other transfers. The cleanup
+	 * function clears the channel interrupt enables and conditions, so
+	 * there's no need to clear the Channel Halted interrupt separately.
+	 */
+	if (fiq_fsm_enable && hcd->fiq_state->channel[hc->hc_num].fsm != FIQ_PASSTHROUGH)
+		dwc_otg_cleanup_fiq_channel(hcd, hc->hc_num);
+	dwc_otg_hc_cleanup(hcd->core_if, hc);
+	DWC_CIRCLEQ_INSERT_TAIL(&hcd->free_hc_list, hc, hc_list_entry);
+
+	if (!microframe_schedule) {
+		switch (hc->ep_type) {
+		case DWC_OTG_EP_TYPE_CONTROL:
+		case DWC_OTG_EP_TYPE_BULK:
+			hcd->non_periodic_channels--;
+			break;
+
+		default:
+			/*
+			 * Don't release reservations for periodic channels here.
+			 * That's done when a periodic transfer is descheduled (i.e.
+			 * when the QH is removed from the periodic schedule).
+			 */
+			break;
+		}
+	} else {
+		hcd->available_host_channels++;
+		fiq_print(FIQDBG_INT, hcd->fiq_state, "AHC = %d ", hcd->available_host_channels);
+	}
+
+	/* Try to queue more transfers now that there's a free channel. */
+	tr_type = dwc_otg_hcd_select_transactions(hcd);
+	if (tr_type != DWC_OTG_TRANSACTION_NONE) {
+		dwc_otg_hcd_queue_transactions(hcd, tr_type);
+	}
+}
+
+/**
+ * Halts a host channel. If the channel cannot be halted immediately because
+ * the request queue is full, this function ensures that the FIFO empty
+ * interrupt for the appropriate queue is enabled so that the halt request can
+ * be queued when there is space in the request queue.
+ *
+ * This function may also be called in DMA mode. In that case, the channel is
+ * simply released since the core always halts the channel automatically in
+ * DMA mode.
+ */
+static void halt_channel(dwc_otg_hcd_t * hcd,
+			 dwc_hc_t * hc,
+			 dwc_otg_qtd_t * qtd, dwc_otg_halt_status_e halt_status)
+{
+	if (hcd->core_if->dma_enable) {
+		release_channel(hcd, hc, qtd, halt_status);
+		return;
+	}
+
+	/* Slave mode processing... */
+	dwc_otg_hc_halt(hcd->core_if, hc, halt_status);
+
+	if (hc->halt_on_queue) {
+		gintmsk_data_t gintmsk = {.d32 = 0 };
+		dwc_otg_core_global_regs_t *global_regs;
+		global_regs = hcd->core_if->core_global_regs;
+
+		if (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL ||
+		    hc->ep_type == DWC_OTG_EP_TYPE_BULK) {
+			/*
+			 * Make sure the Non-periodic Tx FIFO empty interrupt
+			 * is enabled so that the non-periodic schedule will
+			 * be processed.
+			 */
+			gintmsk.b.nptxfempty = 1;
+			if (fiq_enable) {
+				local_fiq_disable();
+				fiq_fsm_spin_lock(&hcd->fiq_state->lock);
+				DWC_MODIFY_REG32(&global_regs->gintmsk, 0, gintmsk.d32);
+				fiq_fsm_spin_unlock(&hcd->fiq_state->lock);
+				local_fiq_enable();
+			} else {
+				DWC_MODIFY_REG32(&global_regs->gintmsk, 0, gintmsk.d32);
+			}
+		} else {
+			/*
+			 * Move the QH from the periodic queued schedule to
+			 * the periodic assigned schedule. This allows the
+			 * halt to be queued when the periodic schedule is
+			 * processed.
+			 */
+			DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_assigned,
+					   &hc->qh->qh_list_entry);
+
+			/*
+			 * Make sure the Periodic Tx FIFO Empty interrupt is
+			 * enabled so that the periodic schedule will be
+			 * processed.
+			 */
+			gintmsk.b.ptxfempty = 1;
+			if (fiq_enable) {
+				local_fiq_disable();
+				fiq_fsm_spin_lock(&hcd->fiq_state->lock);
+				DWC_MODIFY_REG32(&global_regs->gintmsk, 0, gintmsk.d32);
+				fiq_fsm_spin_unlock(&hcd->fiq_state->lock);
+				local_fiq_enable();
+			} else {
+				DWC_MODIFY_REG32(&global_regs->gintmsk, 0, gintmsk.d32);
+			}
+		}
+	}
+}
+
+/**
+ * Performs common cleanup for non-periodic transfers after a Transfer
+ * Complete interrupt. This function should be called after any endpoint type
+ * specific handling is finished to release the host channel.
+ */
+static void complete_non_periodic_xfer(dwc_otg_hcd_t * hcd,
+				       dwc_hc_t * hc,
+				       dwc_otg_hc_regs_t * hc_regs,
+				       dwc_otg_qtd_t * qtd,
+				       dwc_otg_halt_status_e halt_status)
+{
+	hcint_data_t hcint;
+
+	qtd->error_count = 0;
+
+	hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+	if (hcint.b.nyet) {
+		/*
+		 * Got a NYET on the last transaction of the transfer. This
+		 * means that the endpoint should be in the PING state at the
+		 * beginning of the next transfer.
+		 */
+		hc->qh->ping_state = 1;
+		clear_hc_int(hc_regs, nyet);
+	}
+
+	/*
+	 * Always halt and release the host channel to make it available for
+	 * more transfers. There may still be more phases for a control
+	 * transfer or more data packets for a bulk transfer at this point,
+	 * but the host channel is still halted. A channel will be reassigned
+	 * to the transfer when the non-periodic schedule is processed after
+	 * the channel is released. This allows transactions to be queued
+	 * properly via dwc_otg_hcd_queue_transactions, which also enables the
+	 * Tx FIFO Empty interrupt if necessary.
+	 */
+	if (hc->ep_is_in) {
+		/*
+		 * IN transfers in Slave mode require an explicit disable to
+		 * halt the channel. (In DMA mode, this call simply releases
+		 * the channel.)
+		 */
+		halt_channel(hcd, hc, qtd, halt_status);
+	} else {
+		/*
+		 * The channel is automatically disabled by the core for OUT
+		 * transfers in Slave mode.
+		 */
+		release_channel(hcd, hc, qtd, halt_status);
+	}
+}
+
+/**
+ * Performs common cleanup for periodic transfers after a Transfer Complete
+ * interrupt. This function should be called after any endpoint type specific
+ * handling is finished to release the host channel.
+ */
+static void complete_periodic_xfer(dwc_otg_hcd_t * hcd,
+				   dwc_hc_t * hc,
+				   dwc_otg_hc_regs_t * hc_regs,
+				   dwc_otg_qtd_t * qtd,
+				   dwc_otg_halt_status_e halt_status)
+{
+	hctsiz_data_t hctsiz;
+	qtd->error_count = 0;
+
+	hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz);
+	if (!hc->ep_is_in || hctsiz.b.pktcnt == 0) {
+		/* Core halts channel in these cases. */
+		release_channel(hcd, hc, qtd, halt_status);
+	} else {
+		/* Flush any outstanding requests from the Tx queue. */
+		halt_channel(hcd, hc, qtd, halt_status);
+	}
+}
+
+static int32_t handle_xfercomp_isoc_split_in(dwc_otg_hcd_t * hcd,
+					     dwc_hc_t * hc,
+					     dwc_otg_hc_regs_t * hc_regs,
+					     dwc_otg_qtd_t * qtd)
+{
+	uint32_t len;
+	struct dwc_otg_hcd_iso_packet_desc *frame_desc;
+	frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index];
+
+	len = get_actual_xfer_length(hc, hc_regs, qtd,
+				     DWC_OTG_HC_XFER_COMPLETE, NULL);
+
+	if (!len) {
+		qtd->complete_split = 0;
+		qtd->isoc_split_offset = 0;
+		return 0;
+	}
+	frame_desc->actual_length += len;
+
+	if (hc->align_buff && len)
+		dwc_memcpy(qtd->urb->buf + frame_desc->offset +
+			   qtd->isoc_split_offset, hc->qh->dw_align_buf, len);
+	qtd->isoc_split_offset += len;
+
+	if (frame_desc->length == frame_desc->actual_length) {
+		frame_desc->status = 0;
+		qtd->isoc_frame_index++;
+		qtd->complete_split = 0;
+		qtd->isoc_split_offset = 0;
+	}
+
+	if (qtd->isoc_frame_index == qtd->urb->packet_count) {
+		hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, 0);
+		release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE);
+	} else {
+		release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS);
+	}
+
+	return 1;		/* Indicates that channel released */
+}
+
+/**
+ * Handles a host channel Transfer Complete interrupt. This handler may be
+ * called in either DMA mode or Slave mode.
+ */
+static int32_t handle_hc_xfercomp_intr(dwc_otg_hcd_t * hcd,
+				       dwc_hc_t * hc,
+				       dwc_otg_hc_regs_t * hc_regs,
+				       dwc_otg_qtd_t * qtd)
+{
+	int urb_xfer_done;
+	dwc_otg_halt_status_e halt_status = DWC_OTG_HC_XFER_COMPLETE;
+	dwc_otg_hcd_urb_t *urb = qtd->urb;
+	int pipe_type = dwc_otg_hcd_get_pipe_type(&urb->pipe_info);
+
+	DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: "
+		    "Transfer Complete--\n", hc->hc_num);
+
+	if (hcd->core_if->dma_desc_enable) {
+		dwc_otg_hcd_complete_xfer_ddma(hcd, hc, hc_regs, halt_status);
+		if (pipe_type == UE_ISOCHRONOUS) {
+			/* Do not disable the interrupt, just clear it */
+			clear_hc_int(hc_regs, xfercomp);
+			return 1;
+		}
+		goto handle_xfercomp_done;
+	}
+
+	/*
+	 * Handle xfer complete on CSPLIT.
+	 */
+
+	if (hc->qh->do_split) {
+		if ((hc->ep_type == DWC_OTG_EP_TYPE_ISOC) && hc->ep_is_in
+		    && hcd->core_if->dma_enable) {
+			if (qtd->complete_split
+			    && handle_xfercomp_isoc_split_in(hcd, hc, hc_regs,
+							     qtd))
+				goto handle_xfercomp_done;
+		} else {
+			qtd->complete_split = 0;
+		}
+	}
+
+	/* Update the QTD and URB states. */
+	switch (pipe_type) {
+	case UE_CONTROL:
+		switch (qtd->control_phase) {
+		case DWC_OTG_CONTROL_SETUP:
+			if (urb->length > 0) {
+				qtd->control_phase = DWC_OTG_CONTROL_DATA;
+			} else {
+				qtd->control_phase = DWC_OTG_CONTROL_STATUS;
+			}
+			DWC_DEBUGPL(DBG_HCDV,
+				    "  Control setup transaction done\n");
+			halt_status = DWC_OTG_HC_XFER_COMPLETE;
+			break;
+		case DWC_OTG_CONTROL_DATA:{
+				urb_xfer_done =
+				    update_urb_state_xfer_comp(hc, hc_regs, urb,
+							       qtd);
+				if (urb_xfer_done) {
+					qtd->control_phase =
+					    DWC_OTG_CONTROL_STATUS;
+					DWC_DEBUGPL(DBG_HCDV,
+						    "  Control data transfer done\n");
+				} else {
+					dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd);
+				}
+				halt_status = DWC_OTG_HC_XFER_COMPLETE;
+				break;
+			}
+		case DWC_OTG_CONTROL_STATUS:
+			DWC_DEBUGPL(DBG_HCDV, "  Control transfer complete\n");
+			if (urb->status == -DWC_E_IN_PROGRESS) {
+				urb->status = 0;
+			}
+			hcd->fops->complete(hcd, urb->priv, urb, urb->status);
+			halt_status = DWC_OTG_HC_XFER_URB_COMPLETE;
+			break;
+		}
+
+		complete_non_periodic_xfer(hcd, hc, hc_regs, qtd, halt_status);
+		break;
+	case UE_BULK:
+		DWC_DEBUGPL(DBG_HCDV, "  Bulk transfer complete\n");
+		urb_xfer_done =
+		    update_urb_state_xfer_comp(hc, hc_regs, urb, qtd);
+		if (urb_xfer_done) {
+			hcd->fops->complete(hcd, urb->priv, urb, urb->status);
+			halt_status = DWC_OTG_HC_XFER_URB_COMPLETE;
+		} else {
+			halt_status = DWC_OTG_HC_XFER_COMPLETE;
+		}
+
+		dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd);
+		complete_non_periodic_xfer(hcd, hc, hc_regs, qtd, halt_status);
+		break;
+	case UE_INTERRUPT:
+		DWC_DEBUGPL(DBG_HCDV, "  Interrupt transfer complete\n");
+		urb_xfer_done =
+			update_urb_state_xfer_comp(hc, hc_regs, urb, qtd);
+
+		/*
+		 * Interrupt URB is done on the first transfer complete
+		 * interrupt.
+		 */
+		if (urb_xfer_done) {
+				hcd->fops->complete(hcd, urb->priv, urb, urb->status);
+				halt_status = DWC_OTG_HC_XFER_URB_COMPLETE;
+		} else {
+				halt_status = DWC_OTG_HC_XFER_COMPLETE;
+		}
+
+		dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd);
+		complete_periodic_xfer(hcd, hc, hc_regs, qtd, halt_status);
+		break;
+	case UE_ISOCHRONOUS:
+		DWC_DEBUGPL(DBG_HCDV, "  Isochronous transfer complete\n");
+		if (qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_ALL) {
+			halt_status =
+			    update_isoc_urb_state(hcd, hc, hc_regs, qtd,
+						  DWC_OTG_HC_XFER_COMPLETE);
+		}
+		complete_periodic_xfer(hcd, hc, hc_regs, qtd, halt_status);
+		break;
+	}
+
+handle_xfercomp_done:
+	disable_hc_int(hc_regs, xfercompl);
+
+	return 1;
+}
+
+/**
+ * Handles a host channel STALL interrupt. This handler may be called in
+ * either DMA mode or Slave mode.
+ */
+static int32_t handle_hc_stall_intr(dwc_otg_hcd_t * hcd,
+				    dwc_hc_t * hc,
+				    dwc_otg_hc_regs_t * hc_regs,
+				    dwc_otg_qtd_t * qtd)
+{
+	dwc_otg_hcd_urb_t *urb = qtd->urb;
+	int pipe_type = dwc_otg_hcd_get_pipe_type(&urb->pipe_info);
+
+	DWC_DEBUGPL(DBG_HCD, "--Host Channel %d Interrupt: "
+		    "STALL Received--\n", hc->hc_num);
+
+	if (hcd->core_if->dma_desc_enable) {
+		dwc_otg_hcd_complete_xfer_ddma(hcd, hc, hc_regs, DWC_OTG_HC_XFER_STALL);
+		goto handle_stall_done;
+	}
+
+	if (pipe_type == UE_CONTROL) {
+		hcd->fops->complete(hcd, urb->priv, urb, -DWC_E_PIPE);
+	}
+
+	if (pipe_type == UE_BULK || pipe_type == UE_INTERRUPT) {
+		hcd->fops->complete(hcd, urb->priv, urb, -DWC_E_PIPE);
+		/*
+		 * USB protocol requires resetting the data toggle for bulk
+		 * and interrupt endpoints when a CLEAR_FEATURE(ENDPOINT_HALT)
+		 * setup command is issued to the endpoint. Anticipate the
+		 * CLEAR_FEATURE command since a STALL has occurred and reset
+		 * the data toggle now.
+		 */
+		hc->qh->data_toggle = 0;
+	}
+
+	halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_STALL);
+
+handle_stall_done:
+	disable_hc_int(hc_regs, stall);
+
+	return 1;
+}
+
+/*
+ * Updates the state of the URB when a transfer has been stopped due to an
+ * abnormal condition before the transfer completes. Modifies the
+ * actual_length field of the URB to reflect the number of bytes that have
+ * actually been transferred via the host channel.
+ */
+static void update_urb_state_xfer_intr(dwc_hc_t * hc,
+				       dwc_otg_hc_regs_t * hc_regs,
+				       dwc_otg_hcd_urb_t * urb,
+				       dwc_otg_qtd_t * qtd,
+				       dwc_otg_halt_status_e halt_status)
+{
+	uint32_t bytes_transferred = get_actual_xfer_length(hc, hc_regs, qtd,
+							    halt_status, NULL);
+
+	if (urb->actual_length + bytes_transferred > urb->length) {
+		printk_once(KERN_DEBUG "dwc_otg: DEVICE:%03d : %s:%d:trimming xfer length\n",
+			hc->dev_addr, __func__, __LINE__);
+		bytes_transferred = urb->length - urb->actual_length;
+	}
+
+	/* non DWORD-aligned buffer case handling. */
+	if (hc->align_buff && bytes_transferred && hc->ep_is_in) {
+		dwc_memcpy(urb->buf + urb->actual_length, hc->qh->dw_align_buf,
+			   bytes_transferred);
+	}
+
+	urb->actual_length += bytes_transferred;
+
+#ifdef DEBUG
+	{
+		hctsiz_data_t hctsiz;
+		hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz);
+		DWC_DEBUGPL(DBG_HCDV, "DWC_otg: %s: %s, channel %d\n",
+			    __func__, (hc->ep_is_in ? "IN" : "OUT"),
+			    hc->hc_num);
+		DWC_DEBUGPL(DBG_HCDV, "  hc->start_pkt_count %d\n",
+			    hc->start_pkt_count);
+		DWC_DEBUGPL(DBG_HCDV, "  hctsiz.pktcnt %d\n", hctsiz.b.pktcnt);
+		DWC_DEBUGPL(DBG_HCDV, "  hc->max_packet %d\n", hc->max_packet);
+		DWC_DEBUGPL(DBG_HCDV, "  bytes_transferred %d\n",
+			    bytes_transferred);
+		DWC_DEBUGPL(DBG_HCDV, "  urb->actual_length %d\n",
+			    urb->actual_length);
+		DWC_DEBUGPL(DBG_HCDV, "  urb->transfer_buffer_length %d\n",
+			    urb->length);
+	}
+#endif
+}
+
+/**
+ * Handles a host channel NAK interrupt. This handler may be called in either
+ * DMA mode or Slave mode.
+ */
+static int32_t handle_hc_nak_intr(dwc_otg_hcd_t * hcd,
+				  dwc_hc_t * hc,
+				  dwc_otg_hc_regs_t * hc_regs,
+				  dwc_otg_qtd_t * qtd)
+{
+	DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: "
+		    "NAK Received--\n", hc->hc_num);
+
+	/*
+	 * When we get bulk NAKs then remember this so we holdoff on this qh until
+	 * the beginning of the next frame
+	 */
+	switch(dwc_otg_hcd_get_pipe_type(&qtd->urb->pipe_info)) {
+		case UE_BULK:
+		case UE_CONTROL:
+		if (nak_holdoff && qtd->qh->do_split)
+			hc->qh->nak_frame = dwc_otg_hcd_get_frame_number(hcd);
+	}
+
+	/*
+	 * Handle NAK for IN/OUT SSPLIT/CSPLIT transfers, bulk, control, and
+	 * interrupt.  Re-start the SSPLIT transfer.
+	 */
+	if (hc->do_split) {
+		if (hc->complete_split) {
+			qtd->error_count = 0;
+		}
+		qtd->complete_split = 0;
+		halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NAK);
+		goto handle_nak_done;
+	}
+
+	switch (dwc_otg_hcd_get_pipe_type(&qtd->urb->pipe_info)) {
+	case UE_CONTROL:
+	case UE_BULK:
+		if (hcd->core_if->dma_enable && hc->ep_is_in) {
+			/*
+			 * NAK interrupts are enabled on bulk/control IN
+			 * transfers in DMA mode for the sole purpose of
+			 * resetting the error count after a transaction error
+			 * occurs. The core will continue transferring data.
+			 * Disable other interrupts unmasked for the same
+			 * reason.
+			 */
+			disable_hc_int(hc_regs, datatglerr);
+			disable_hc_int(hc_regs, ack);
+			qtd->error_count = 0;
+			goto handle_nak_done;
+		}
+
+		/*
+		 * NAK interrupts normally occur during OUT transfers in DMA
+		 * or Slave mode. For IN transfers, more requests will be
+		 * queued as request queue space is available.
+		 */
+		qtd->error_count = 0;
+
+		if (!hc->qh->ping_state) {
+			update_urb_state_xfer_intr(hc, hc_regs,
+						   qtd->urb, qtd,
+						   DWC_OTG_HC_XFER_NAK);
+			dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd);
+
+			if (hc->speed == DWC_OTG_EP_SPEED_HIGH)
+				hc->qh->ping_state = 1;
+		}
+
+		/*
+		 * Halt the channel so the transfer can be re-started from
+		 * the appropriate point or the PING protocol will
+		 * start/continue.
+		 */
+		halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NAK);
+		break;
+	case UE_INTERRUPT:
+		qtd->error_count = 0;
+		halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NAK);
+		break;
+	case UE_ISOCHRONOUS:
+		/* Should never get called for isochronous transfers. */
+		DWC_ASSERT(1, "NACK interrupt for ISOC transfer\n");
+		break;
+	}
+
+handle_nak_done:
+	disable_hc_int(hc_regs, nak);
+
+	return 1;
+}
+
+/**
+ * Handles a host channel ACK interrupt. This interrupt is enabled when
+ * performing the PING protocol in Slave mode, when errors occur during
+ * either Slave mode or DMA mode, and during Start Split transactions.
+ */
+static int32_t handle_hc_ack_intr(dwc_otg_hcd_t * hcd,
+				  dwc_hc_t * hc,
+				  dwc_otg_hc_regs_t * hc_regs,
+				  dwc_otg_qtd_t * qtd)
+{
+	DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: "
+		    "ACK Received--\n", hc->hc_num);
+
+	if (hc->do_split) {
+		/*
+		 * Handle ACK on SSPLIT.
+		 * ACK should not occur in CSPLIT.
+		 */
+		if (!hc->ep_is_in && hc->data_pid_start != DWC_OTG_HC_PID_SETUP) {
+			qtd->ssplit_out_xfer_count = hc->xfer_len;
+		}
+		if (!(hc->ep_type == DWC_OTG_EP_TYPE_ISOC && !hc->ep_is_in)) {
+			/* Don't need complete for isochronous out transfers. */
+			qtd->complete_split = 1;
+		}
+
+		/* ISOC OUT */
+		if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC && !hc->ep_is_in) {
+			switch (hc->xact_pos) {
+			case DWC_HCSPLIT_XACTPOS_ALL:
+				break;
+			case DWC_HCSPLIT_XACTPOS_END:
+				qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_ALL;
+				qtd->isoc_split_offset = 0;
+				break;
+			case DWC_HCSPLIT_XACTPOS_BEGIN:
+			case DWC_HCSPLIT_XACTPOS_MID:
+				/*
+				 * For BEGIN or MID, calculate the length for
+				 * the next microframe to determine the correct
+				 * SSPLIT token, either MID or END.
+				 */
+				{
+					struct dwc_otg_hcd_iso_packet_desc
+					*frame_desc;
+
+					frame_desc =
+					    &qtd->urb->
+					    iso_descs[qtd->isoc_frame_index];
+					qtd->isoc_split_offset += 188;
+
+					if ((frame_desc->length -
+					     qtd->isoc_split_offset) <= 188) {
+						qtd->isoc_split_pos =
+						    DWC_HCSPLIT_XACTPOS_END;
+					} else {
+						qtd->isoc_split_pos =
+						    DWC_HCSPLIT_XACTPOS_MID;
+					}
+
+				}
+				break;
+			}
+		} else {
+			halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_ACK);
+		}
+	} else {
+		/*
+		 * An unmasked ACK on a non-split DMA transaction is
+		 * for the sole purpose of resetting error counts. Disable other
+		 * interrupts unmasked for the same reason.
+		 */
+		if(hcd->core_if->dma_enable) {
+			disable_hc_int(hc_regs, datatglerr);
+			disable_hc_int(hc_regs, nak);
+		}
+		qtd->error_count = 0;
+
+		if (hc->qh->ping_state) {
+			hc->qh->ping_state = 0;
+			/*
+			 * Halt the channel so the transfer can be re-started
+			 * from the appropriate point. This only happens in
+			 * Slave mode. In DMA mode, the ping_state is cleared
+			 * when the transfer is started because the core
+			 * automatically executes the PING, then the transfer.
+			 */
+			halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_ACK);
+		}
+	}
+
+	/*
+	 * If the ACK occurred when _not_ in the PING state, let the channel
+	 * continue transferring data after clearing the error count.
+	 */
+
+	disable_hc_int(hc_regs, ack);
+
+	return 1;
+}
+
+/**
+ * Handles a host channel NYET interrupt. This interrupt should only occur on
+ * Bulk and Control OUT endpoints and for complete split transactions. If a
+ * NYET occurs at the same time as a Transfer Complete interrupt, it is
+ * handled in the xfercomp interrupt handler, not here. This handler may be
+ * called in either DMA mode or Slave mode.
+ */
+static int32_t handle_hc_nyet_intr(dwc_otg_hcd_t * hcd,
+				   dwc_hc_t * hc,
+				   dwc_otg_hc_regs_t * hc_regs,
+				   dwc_otg_qtd_t * qtd)
+{
+	DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: "
+		    "NYET Received--\n", hc->hc_num);
+
+	/*
+	 * NYET on CSPLIT
+	 * re-do the CSPLIT immediately on non-periodic
+	 */
+	if (hc->do_split && hc->complete_split) {
+		if (hc->ep_is_in && (hc->ep_type == DWC_OTG_EP_TYPE_ISOC)
+		    && hcd->core_if->dma_enable) {
+			qtd->complete_split = 0;
+			qtd->isoc_split_offset = 0;
+			if (++qtd->isoc_frame_index == qtd->urb->packet_count) {
+				hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, 0);
+				release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE);
+			}
+			else
+				release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS);
+			goto handle_nyet_done;
+		}
+
+		if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+		    hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+			int frnum = dwc_otg_hcd_get_frame_number(hcd);
+
+			// With the FIQ running we only ever see the failed NYET
+			if (dwc_full_frame_num(frnum) !=
+			    dwc_full_frame_num(hc->qh->sched_frame) ||
+			    fiq_fsm_enable) {
+				/*
+				 * No longer in the same full speed frame.
+				 * Treat this as a transaction error.
+				 */
+#if 0
+				/** @todo Fix system performance so this can
+				 * be treated as an error. Right now complete
+				 * splits cannot be scheduled precisely enough
+				 * due to other system activity, so this error
+				 * occurs regularly in Slave mode.
+				 */
+				qtd->error_count++;
+#endif
+				qtd->complete_split = 0;
+				halt_channel(hcd, hc, qtd,
+					     DWC_OTG_HC_XFER_XACT_ERR);
+				/** @todo add support for isoc release */
+				goto handle_nyet_done;
+			}
+		}
+
+		halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NYET);
+		goto handle_nyet_done;
+	}
+
+	hc->qh->ping_state = 1;
+	qtd->error_count = 0;
+
+	update_urb_state_xfer_intr(hc, hc_regs, qtd->urb, qtd,
+				   DWC_OTG_HC_XFER_NYET);
+	dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd);
+
+	/*
+	 * Halt the channel and re-start the transfer so the PING
+	 * protocol will start.
+	 */
+	halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NYET);
+
+handle_nyet_done:
+	disable_hc_int(hc_regs, nyet);
+	return 1;
+}
+
+/**
+ * Handles a host channel babble interrupt. This handler may be called in
+ * either DMA mode or Slave mode.
+ */
+static int32_t handle_hc_babble_intr(dwc_otg_hcd_t * hcd,
+				     dwc_hc_t * hc,
+				     dwc_otg_hc_regs_t * hc_regs,
+				     dwc_otg_qtd_t * qtd)
+{
+	DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: "
+		    "Babble Error--\n", hc->hc_num);
+
+	if (hcd->core_if->dma_desc_enable) {
+		dwc_otg_hcd_complete_xfer_ddma(hcd, hc, hc_regs,
+					       DWC_OTG_HC_XFER_BABBLE_ERR);
+		goto handle_babble_done;
+	}
+
+	if (hc->ep_type != DWC_OTG_EP_TYPE_ISOC) {
+		hcd->fops->complete(hcd, qtd->urb->priv,
+				    qtd->urb, -DWC_E_OVERFLOW);
+		halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_BABBLE_ERR);
+	} else {
+		dwc_otg_halt_status_e halt_status;
+		halt_status = update_isoc_urb_state(hcd, hc, hc_regs, qtd,
+						    DWC_OTG_HC_XFER_BABBLE_ERR);
+		halt_channel(hcd, hc, qtd, halt_status);
+	}
+
+handle_babble_done:
+	disable_hc_int(hc_regs, bblerr);
+	return 1;
+}
+
+/**
+ * Handles a host channel AHB error interrupt. This handler is only called in
+ * DMA mode.
+ */
+static int32_t handle_hc_ahberr_intr(dwc_otg_hcd_t * hcd,
+				     dwc_hc_t * hc,
+				     dwc_otg_hc_regs_t * hc_regs,
+				     dwc_otg_qtd_t * qtd)
+{
+	hcchar_data_t hcchar;
+	hcsplt_data_t hcsplt;
+	hctsiz_data_t hctsiz;
+	uint32_t hcdma;
+	char *pipetype, *speed;
+
+	dwc_otg_hcd_urb_t *urb = qtd->urb;
+
+	DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: "
+		    "AHB Error--\n", hc->hc_num);
+
+	hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+	hcsplt.d32 = DWC_READ_REG32(&hc_regs->hcsplt);
+	hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz);
+	hcdma = DWC_READ_REG32(&hc_regs->hcdma);
+
+	DWC_ERROR("AHB ERROR, Channel %d\n", hc->hc_num);
+	DWC_ERROR("  hcchar 0x%08x, hcsplt 0x%08x\n", hcchar.d32, hcsplt.d32);
+	DWC_ERROR("  hctsiz 0x%08x, hcdma 0x%08x\n", hctsiz.d32, hcdma);
+	DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD URB Enqueue\n");
+	DWC_ERROR("  Device address: %d\n",
+		  dwc_otg_hcd_get_dev_addr(&urb->pipe_info));
+	DWC_ERROR("  Endpoint: %d, %s\n",
+		  dwc_otg_hcd_get_ep_num(&urb->pipe_info),
+		  (dwc_otg_hcd_is_pipe_in(&urb->pipe_info) ? "IN" : "OUT"));
+
+	switch (dwc_otg_hcd_get_pipe_type(&urb->pipe_info)) {
+	case UE_CONTROL:
+		pipetype = "CONTROL";
+		break;
+	case UE_BULK:
+		pipetype = "BULK";
+		break;
+	case UE_INTERRUPT:
+		pipetype = "INTERRUPT";
+		break;
+	case UE_ISOCHRONOUS:
+		pipetype = "ISOCHRONOUS";
+		break;
+	default:
+		pipetype = "UNKNOWN";
+		break;
+	}
+
+	DWC_ERROR("  Endpoint type: %s\n", pipetype);
+
+	switch (hc->speed) {
+	case DWC_OTG_EP_SPEED_HIGH:
+		speed = "HIGH";
+		break;
+	case DWC_OTG_EP_SPEED_FULL:
+		speed = "FULL";
+		break;
+	case DWC_OTG_EP_SPEED_LOW:
+		speed = "LOW";
+		break;
+	default:
+		speed = "UNKNOWN";
+		break;
+	};
+
+	DWC_ERROR("  Speed: %s\n", speed);
+
+	DWC_ERROR("  Max packet size: %d\n",
+		  dwc_otg_hcd_get_mps(&urb->pipe_info));
+	DWC_ERROR("  Data buffer length: %d\n", urb->length);
+	DWC_ERROR("  Transfer buffer: %p, Transfer DMA: %pad\n",
+		  urb->buf, &urb->dma);
+	DWC_ERROR("  Setup buffer: %p, Setup DMA: %pad\n",
+		  urb->setup_packet, &urb->setup_dma);
+	DWC_ERROR("  Interval: %d\n", urb->interval);
+
+	/* Core haltes the channel for Descriptor DMA mode */
+	if (hcd->core_if->dma_desc_enable) {
+		dwc_otg_hcd_complete_xfer_ddma(hcd, hc, hc_regs,
+					       DWC_OTG_HC_XFER_AHB_ERR);
+		goto handle_ahberr_done;
+	}
+
+	hcd->fops->complete(hcd, urb->priv, urb, -DWC_E_IO);
+
+	/*
+	 * Force a channel halt. Don't call halt_channel because that won't
+	 * write to the HCCHARn register in DMA mode to force the halt.
+	 */
+	dwc_otg_hc_halt(hcd->core_if, hc, DWC_OTG_HC_XFER_AHB_ERR);
+handle_ahberr_done:
+	disable_hc_int(hc_regs, ahberr);
+	return 1;
+}
+
+/**
+ * Handles a host channel transaction error interrupt. This handler may be
+ * called in either DMA mode or Slave mode.
+ */
+static int32_t handle_hc_xacterr_intr(dwc_otg_hcd_t * hcd,
+				      dwc_hc_t * hc,
+				      dwc_otg_hc_regs_t * hc_regs,
+				      dwc_otg_qtd_t * qtd)
+{
+	DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: "
+		    "Transaction Error--\n", hc->hc_num);
+
+	if (hcd->core_if->dma_desc_enable) {
+		dwc_otg_hcd_complete_xfer_ddma(hcd, hc, hc_regs,
+					       DWC_OTG_HC_XFER_XACT_ERR);
+		goto handle_xacterr_done;
+	}
+
+	switch (dwc_otg_hcd_get_pipe_type(&qtd->urb->pipe_info)) {
+	case UE_CONTROL:
+	case UE_BULK:
+		qtd->error_count++;
+		if (!hc->qh->ping_state) {
+
+			update_urb_state_xfer_intr(hc, hc_regs,
+						   qtd->urb, qtd,
+						   DWC_OTG_HC_XFER_XACT_ERR);
+			dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd);
+			if (!hc->ep_is_in && hc->speed == DWC_OTG_EP_SPEED_HIGH) {
+				hc->qh->ping_state = 1;
+			}
+		}
+
+		/*
+		 * Halt the channel so the transfer can be re-started from
+		 * the appropriate point or the PING protocol will start.
+		 */
+		halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_XACT_ERR);
+		break;
+	case UE_INTERRUPT:
+		qtd->error_count++;
+		if (hc->do_split && hc->complete_split) {
+			qtd->complete_split = 0;
+		}
+		halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_XACT_ERR);
+		break;
+	case UE_ISOCHRONOUS:
+		{
+			dwc_otg_halt_status_e halt_status;
+			halt_status =
+			    update_isoc_urb_state(hcd, hc, hc_regs, qtd,
+						  DWC_OTG_HC_XFER_XACT_ERR);
+
+			halt_channel(hcd, hc, qtd, halt_status);
+		}
+		break;
+	}
+handle_xacterr_done:
+	disable_hc_int(hc_regs, xacterr);
+
+	return 1;
+}
+
+/**
+ * Handles a host channel frame overrun interrupt. This handler may be called
+ * in either DMA mode or Slave mode.
+ */
+static int32_t handle_hc_frmovrun_intr(dwc_otg_hcd_t * hcd,
+				       dwc_hc_t * hc,
+				       dwc_otg_hc_regs_t * hc_regs,
+				       dwc_otg_qtd_t * qtd)
+{
+	DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: "
+		    "Frame Overrun--\n", hc->hc_num);
+
+	switch (dwc_otg_hcd_get_pipe_type(&qtd->urb->pipe_info)) {
+	case UE_CONTROL:
+	case UE_BULK:
+		break;
+	case UE_INTERRUPT:
+		halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_FRAME_OVERRUN);
+		break;
+	case UE_ISOCHRONOUS:
+		{
+			dwc_otg_halt_status_e halt_status;
+			halt_status =
+			    update_isoc_urb_state(hcd, hc, hc_regs, qtd,
+						  DWC_OTG_HC_XFER_FRAME_OVERRUN);
+
+			halt_channel(hcd, hc, qtd, halt_status);
+		}
+		break;
+	}
+
+	disable_hc_int(hc_regs, frmovrun);
+
+	return 1;
+}
+
+/**
+ * Handles a host channel data toggle error interrupt. This handler may be
+ * called in either DMA mode or Slave mode.
+ */
+static int32_t handle_hc_datatglerr_intr(dwc_otg_hcd_t * hcd,
+					 dwc_hc_t * hc,
+					 dwc_otg_hc_regs_t * hc_regs,
+					 dwc_otg_qtd_t * qtd)
+{
+	DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: "
+		"Data Toggle Error on %s transfer--\n",
+		hc->hc_num, (hc->ep_is_in ? "IN" : "OUT"));
+
+	/* Data toggles on split transactions cause the hc to halt.
+	 * restart transfer */
+	if(hc->qh->do_split)
+	{
+		qtd->error_count++;
+		dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd);
+		update_urb_state_xfer_intr(hc, hc_regs,
+			qtd->urb, qtd, DWC_OTG_HC_XFER_XACT_ERR);
+		halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_XACT_ERR);
+	} else if (hc->ep_is_in) {
+		/* An unmasked data toggle error on a non-split DMA transaction is
+		 * for the sole purpose of resetting error counts. Disable other
+		 * interrupts unmasked for the same reason.
+		 */
+		if(hcd->core_if->dma_enable) {
+			disable_hc_int(hc_regs, ack);
+			disable_hc_int(hc_regs, nak);
+		}
+		qtd->error_count = 0;
+	}
+
+	disable_hc_int(hc_regs, datatglerr);
+
+	return 1;
+}
+
+#ifdef DEBUG
+/**
+ * This function is for debug only. It checks that a valid halt status is set
+ * and that HCCHARn.chdis is clear. If there's a problem, corrective action is
+ * taken and a warning is issued.
+ * @return 1 if halt status is ok, 0 otherwise.
+ */
+static inline int halt_status_ok(dwc_otg_hcd_t * hcd,
+				 dwc_hc_t * hc,
+				 dwc_otg_hc_regs_t * hc_regs,
+				 dwc_otg_qtd_t * qtd)
+{
+	hcchar_data_t hcchar;
+	hctsiz_data_t hctsiz;
+	hcint_data_t hcint;
+	hcintmsk_data_t hcintmsk;
+	hcsplt_data_t hcsplt;
+
+	if (hc->halt_status == DWC_OTG_HC_XFER_NO_HALT_STATUS) {
+		/*
+		 * This code is here only as a check. This condition should
+		 * never happen. Ignore the halt if it does occur.
+		 */
+		hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+		hctsiz.d32 = DWC_READ_REG32(&hc_regs->hctsiz);
+		hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+		hcintmsk.d32 = DWC_READ_REG32(&hc_regs->hcintmsk);
+		hcsplt.d32 = DWC_READ_REG32(&hc_regs->hcsplt);
+		DWC_WARN
+		    ("%s: hc->halt_status == DWC_OTG_HC_XFER_NO_HALT_STATUS, "
+		     "channel %d, hcchar 0x%08x, hctsiz 0x%08x, "
+		     "hcint 0x%08x, hcintmsk 0x%08x, "
+		     "hcsplt 0x%08x, qtd->complete_split %d\n", __func__,
+		     hc->hc_num, hcchar.d32, hctsiz.d32, hcint.d32,
+		     hcintmsk.d32, hcsplt.d32, qtd->complete_split);
+
+		DWC_WARN("%s: no halt status, channel %d, ignoring interrupt\n",
+			 __func__, hc->hc_num);
+		DWC_WARN("\n");
+		clear_hc_int(hc_regs, chhltd);
+		return 0;
+	}
+
+	/*
+	 * This code is here only as a check. hcchar.chdis should
+	 * never be set when the halt interrupt occurs. Halt the
+	 * channel again if it does occur.
+	 */
+	hcchar.d32 = DWC_READ_REG32(&hc_regs->hcchar);
+	if (hcchar.b.chdis) {
+		DWC_WARN("%s: hcchar.chdis set unexpectedly, "
+			 "hcchar 0x%08x, trying to halt again\n",
+			 __func__, hcchar.d32);
+		clear_hc_int(hc_regs, chhltd);
+		hc->halt_pending = 0;
+		halt_channel(hcd, hc, qtd, hc->halt_status);
+		return 0;
+	}
+
+	return 1;
+}
+#endif
+
+/**
+ * Handles a host Channel Halted interrupt in DMA mode. This handler
+ * determines the reason the channel halted and proceeds accordingly.
+ */
+static void handle_hc_chhltd_intr_dma(dwc_otg_hcd_t * hcd,
+				      dwc_hc_t * hc,
+				      dwc_otg_hc_regs_t * hc_regs,
+				      dwc_otg_qtd_t * qtd)
+{
+	int out_nak_enh = 0;
+	hcint_data_t hcint;
+	hcintmsk_data_t hcintmsk;
+	/* For core with OUT NAK enhancement, the flow for high-
+	 * speed CONTROL/BULK OUT is handled a little differently.
+	 */
+	if (hcd->core_if->snpsid >= OTG_CORE_REV_2_71a) {
+		if (hc->speed == DWC_OTG_EP_SPEED_HIGH && !hc->ep_is_in &&
+		    (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL ||
+		     hc->ep_type == DWC_OTG_EP_TYPE_BULK)) {
+			out_nak_enh = 1;
+		}
+	}
+
+	if (hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE ||
+	    (hc->halt_status == DWC_OTG_HC_XFER_AHB_ERR
+	     && !hcd->core_if->dma_desc_enable)) {
+		/*
+		 * Just release the channel. A dequeue can happen on a
+		 * transfer timeout. In the case of an AHB Error, the channel
+		 * was forced to halt because there's no way to gracefully
+		 * recover.
+		 */
+		if (hcd->core_if->dma_desc_enable)
+			dwc_otg_hcd_complete_xfer_ddma(hcd, hc, hc_regs,
+						       hc->halt_status);
+		else
+			release_channel(hcd, hc, qtd, hc->halt_status);
+		return;
+	}
+
+	/* Read the HCINTn register to determine the cause for the halt. */
+
+	hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+	hcintmsk.d32 = DWC_READ_REG32(&hc_regs->hcintmsk);
+
+	if (hcint.b.xfercomp) {
+		/** @todo This is here because of a possible hardware bug.  Spec
+		 * says that on SPLIT-ISOC OUT transfers in DMA mode that a HALT
+		 * interrupt w/ACK bit set should occur, but I only see the
+		 * XFERCOMP bit, even with it masked out.  This is a workaround
+		 * for that behavior.  Should fix this when hardware is fixed.
+		 */
+		if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC && !hc->ep_is_in) {
+			handle_hc_ack_intr(hcd, hc, hc_regs, qtd);
+		}
+		handle_hc_xfercomp_intr(hcd, hc, hc_regs, qtd);
+	} else if (hcint.b.stall) {
+		handle_hc_stall_intr(hcd, hc, hc_regs, qtd);
+	} else if (hcint.b.xacterr && !hcd->core_if->dma_desc_enable) {
+		if (out_nak_enh) {
+			if (hcint.b.nyet || hcint.b.nak || hcint.b.ack) {
+				DWC_DEBUGPL(DBG_HCD, "XactErr with NYET/NAK/ACK\n");
+				qtd->error_count = 0;
+			} else {
+				DWC_DEBUGPL(DBG_HCD, "XactErr without NYET/NAK/ACK\n");
+			}
+		}
+
+		/*
+		 * Must handle xacterr before nak or ack. Could get a xacterr
+		 * at the same time as either of these on a BULK/CONTROL OUT
+		 * that started with a PING. The xacterr takes precedence.
+		 */
+		handle_hc_xacterr_intr(hcd, hc, hc_regs, qtd);
+	} else if (hcint.b.xcs_xact && hcd->core_if->dma_desc_enable) {
+		handle_hc_xacterr_intr(hcd, hc, hc_regs, qtd);
+	} else if (hcint.b.ahberr && hcd->core_if->dma_desc_enable) {
+		handle_hc_ahberr_intr(hcd, hc, hc_regs, qtd);
+	} else if (hcint.b.bblerr) {
+		handle_hc_babble_intr(hcd, hc, hc_regs, qtd);
+	} else if (hcint.b.frmovrun) {
+		handle_hc_frmovrun_intr(hcd, hc, hc_regs, qtd);
+	} else if (hcint.b.datatglerr) {
+		handle_hc_datatglerr_intr(hcd, hc, hc_regs, qtd);
+	} else if (!out_nak_enh) {
+		if (hcint.b.nyet) {
+			/*
+			 * Must handle nyet before nak or ack. Could get a nyet at the
+			 * same time as either of those on a BULK/CONTROL OUT that
+			 * started with a PING. The nyet takes precedence.
+			 */
+			handle_hc_nyet_intr(hcd, hc, hc_regs, qtd);
+		} else if (hcint.b.nak && !hcintmsk.b.nak) {
+			/*
+			 * If nak is not masked, it's because a non-split IN transfer
+			 * is in an error state. In that case, the nak is handled by
+			 * the nak interrupt handler, not here. Handle nak here for
+			 * BULK/CONTROL OUT transfers, which halt on a NAK to allow
+			 * rewinding the buffer pointer.
+			 */
+			handle_hc_nak_intr(hcd, hc, hc_regs, qtd);
+		} else if (hcint.b.ack && !hcintmsk.b.ack) {
+			/*
+			 * If ack is not masked, it's because a non-split IN transfer
+			 * is in an error state. In that case, the ack is handled by
+			 * the ack interrupt handler, not here. Handle ack here for
+			 * split transfers. Start splits halt on ACK.
+			 */
+			handle_hc_ack_intr(hcd, hc, hc_regs, qtd);
+		} else {
+			if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+			    hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+				/*
+				 * A periodic transfer halted with no other channel
+				 * interrupts set. Assume it was halted by the core
+				 * because it could not be completed in its scheduled
+				 * (micro)frame.
+				 */
+#ifdef DEBUG
+				DWC_PRINTF
+				    ("%s: Halt channel %d (assume incomplete periodic transfer)\n",
+				     __func__, hc->hc_num);
+#endif
+				halt_channel(hcd, hc, qtd,
+					     DWC_OTG_HC_XFER_PERIODIC_INCOMPLETE);
+			} else {
+				DWC_ERROR
+				    ("%s: Channel %d, DMA Mode -- ChHltd set, but reason "
+				     "for halting is unknown, hcint 0x%08x, intsts 0x%08x\n",
+				     __func__, hc->hc_num, hcint.d32,
+				     DWC_READ_REG32(&hcd->
+						    core_if->core_global_regs->
+						    gintsts));
+				/* Failthrough: use 3-strikes rule */
+				qtd->error_count++;
+				dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd);
+				update_urb_state_xfer_intr(hc, hc_regs,
+					   qtd->urb, qtd, DWC_OTG_HC_XFER_XACT_ERR);
+				halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_XACT_ERR);
+			}
+
+		}
+	} else {
+		DWC_PRINTF("NYET/NAK/ACK/other in non-error case, 0x%08x\n",
+			   hcint.d32);
+		/* Failthrough: use 3-strikes rule */
+		qtd->error_count++;
+		dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd);
+		update_urb_state_xfer_intr(hc, hc_regs,
+			   qtd->urb, qtd, DWC_OTG_HC_XFER_XACT_ERR);
+		halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_XACT_ERR);
+	}
+}
+
+/**
+ * Handles a host channel Channel Halted interrupt.
+ *
+ * In slave mode, this handler is called only when the driver specifically
+ * requests a halt. This occurs during handling other host channel interrupts
+ * (e.g. nak, xacterr, stall, nyet, etc.).
+ *
+ * In DMA mode, this is the interrupt that occurs when the core has finished
+ * processing a transfer on a channel. Other host channel interrupts (except
+ * ahberr) are disabled in DMA mode.
+ */
+static int32_t handle_hc_chhltd_intr(dwc_otg_hcd_t * hcd,
+				     dwc_hc_t * hc,
+				     dwc_otg_hc_regs_t * hc_regs,
+				     dwc_otg_qtd_t * qtd)
+{
+	DWC_DEBUGPL(DBG_HCDI, "--Host Channel %d Interrupt: "
+		    "Channel Halted--\n", hc->hc_num);
+
+	if (hcd->core_if->dma_enable) {
+		handle_hc_chhltd_intr_dma(hcd, hc, hc_regs, qtd);
+	} else {
+#ifdef DEBUG
+		if (!halt_status_ok(hcd, hc, hc_regs, qtd)) {
+			return 1;
+		}
+#endif
+		release_channel(hcd, hc, qtd, hc->halt_status);
+	}
+
+	return 1;
+}
+
+
+/**
+ * dwc_otg_fiq_unmangle_isoc() - Update the iso_frame_desc structure on
+ * FIQ transfer completion
+ * @hcd:	Pointer to dwc_otg_hcd struct
+ * @num:	Host channel number
+ *
+ * 1. Un-mangle the status as recorded in each iso_frame_desc status
+ * 2. Copy it from the dwc_otg_urb into the real URB
+ */
+static void dwc_otg_fiq_unmangle_isoc(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh, dwc_otg_qtd_t *qtd, uint32_t num)
+{
+	struct dwc_otg_hcd_urb *dwc_urb = qtd->urb;
+	int nr_frames = dwc_urb->packet_count;
+	int i;
+	hcint_data_t frame_hcint;
+
+	for (i = 0; i < nr_frames; i++) {
+		frame_hcint.d32 = dwc_urb->iso_descs[i].status;
+		if (frame_hcint.b.xfercomp) {
+			dwc_urb->iso_descs[i].status = 0;
+			dwc_urb->actual_length += dwc_urb->iso_descs[i].actual_length;
+		} else if (frame_hcint.b.frmovrun) {
+			if (qh->ep_is_in)
+				dwc_urb->iso_descs[i].status = -DWC_E_NO_STREAM_RES;
+			else
+				dwc_urb->iso_descs[i].status = -DWC_E_COMMUNICATION;
+			dwc_urb->error_count++;
+			dwc_urb->iso_descs[i].actual_length = 0;
+		} else if (frame_hcint.b.xacterr) {
+			dwc_urb->iso_descs[i].status = -DWC_E_PROTOCOL;
+			dwc_urb->error_count++;
+			dwc_urb->iso_descs[i].actual_length = 0;
+		} else if (frame_hcint.b.bblerr) {
+			dwc_urb->iso_descs[i].status = -DWC_E_OVERFLOW;
+			dwc_urb->error_count++;
+			dwc_urb->iso_descs[i].actual_length = 0;
+		} else {
+			/* Something went wrong */
+			dwc_urb->iso_descs[i].status = -1;
+			dwc_urb->iso_descs[i].actual_length = 0;
+			dwc_urb->error_count++;
+		}
+	}
+	qh->sched_frame = dwc_frame_num_inc(qh->sched_frame, qh->interval * (nr_frames - 1));
+
+	//printk_ratelimited(KERN_INFO "%s: HS isochronous of %d/%d frames with %d errors complete\n",
+	//			__FUNCTION__, i, dwc_urb->packet_count, dwc_urb->error_count);
+}
+
+/**
+ * dwc_otg_fiq_unsetup_per_dma() - Remove data from bounce buffers for split transactions
+ * @hcd:	Pointer to dwc_otg_hcd struct
+ * @num:	Host channel number
+ *
+ * Copies data from the FIQ bounce buffers into the URB's transfer buffer. Does not modify URB state.
+ * Returns total length of data or -1 if the buffers were not used.
+ *
+ */
+static int dwc_otg_fiq_unsetup_per_dma(dwc_otg_hcd_t *hcd, dwc_otg_qh_t *qh, dwc_otg_qtd_t *qtd, uint32_t num)
+{
+	dwc_hc_t *hc = qh->channel;
+	struct fiq_dma_channel *split_dma = hcd->fiq_dmab;
+	struct fiq_channel_state *st = &hcd->fiq_state->channel[num];
+	uint8_t *ptr = NULL;
+	int index = 0, len = 0;
+	int i = 0;
+	if (hc->ep_is_in) {
+		/* Copy data out of the DMA bounce buffers to the URB's buffer.
+		 * The align_buf is ignored as this is ignored on FSM enqueue. */
+		ptr = qtd->urb->buf;
+		if (qh->ep_type == UE_ISOCHRONOUS) {
+			/* Isoc IN transactions - grab the offset of the iso_frame_desc into the URB transfer buffer */
+			index = qtd->isoc_frame_index;
+			ptr += qtd->urb->iso_descs[index].offset;
+		} else {
+			/* Need to increment by actual_length for interrupt IN */
+			ptr += qtd->urb->actual_length;
+		}
+
+		for (i = 0; i < st->dma_info.index; i++) {
+			len += st->dma_info.slot_len[i];
+			dwc_memcpy(ptr, &split_dma[num].index[i].buf[0], st->dma_info.slot_len[i]);
+			ptr += st->dma_info.slot_len[i];
+		}
+		return len;
+	} else {
+		/* OUT endpoints - nothing to do. */
+		return -1;
+	}
+
+}
+/**
+ * dwc_otg_hcd_handle_hc_fsm() - handle an unmasked channel interrupt
+ * 				 from a channel handled in the FIQ
+ * @hcd:	Pointer to dwc_otg_hcd struct
+ * @num:	Host channel number
+ *
+ * If a host channel interrupt was received by the IRQ and this was a channel
+ * used by the FIQ, the execution flow for transfer completion is substantially
+ * different from the normal (messy) path. This function and its friends handles
+ * channel cleanup and transaction completion from a FIQ transaction.
+ */
+static void dwc_otg_hcd_handle_hc_fsm(dwc_otg_hcd_t *hcd, uint32_t num)
+{
+	struct fiq_channel_state *st = &hcd->fiq_state->channel[num];
+	dwc_hc_t *hc = hcd->hc_ptr_array[num];
+	dwc_otg_qtd_t *qtd;
+	dwc_otg_hc_regs_t *hc_regs = hcd->core_if->host_if->hc_regs[num];
+	hcint_data_t hcint = hcd->fiq_state->channel[num].hcint_copy;
+	hctsiz_data_t hctsiz = hcd->fiq_state->channel[num].hctsiz_copy;
+	int hostchannels  = 0;
+	fiq_print(FIQDBG_INT, hcd->fiq_state, "OUT %01d %01d ", num , st->fsm);
+
+	hostchannels = hcd->available_host_channels;
+	if (hc->halt_pending) {
+		/* Dequeue: The FIQ was allowed to complete the transfer but state has been cleared. */
+		if (hc->qh && st->fsm == FIQ_NP_SPLIT_DONE &&
+				hcint.b.xfercomp && hc->qh->ep_type == UE_BULK) {
+			if (hctsiz.b.pid == DWC_HCTSIZ_DATA0) {
+				hc->qh->data_toggle = DWC_OTG_HC_PID_DATA1;
+			} else {
+				hc->qh->data_toggle = DWC_OTG_HC_PID_DATA0;
+			}
+		}
+		release_channel(hcd, hc, NULL, hc->halt_status);
+		return;
+	}
+
+	qtd = DWC_CIRCLEQ_FIRST(&hc->qh->qtd_list);
+	switch (st->fsm) {
+	case FIQ_TEST:
+		break;
+
+	case FIQ_DEQUEUE_ISSUED:
+		/* Handled above, but keep for posterity */
+		release_channel(hcd, hc, NULL, hc->halt_status);
+		break;
+
+	case FIQ_NP_SPLIT_DONE:
+		/* Nonperiodic transaction complete. */
+		if (!hc->ep_is_in) {
+			qtd->ssplit_out_xfer_count = hc->xfer_len;
+		}
+		if (hcint.b.xfercomp) {
+			handle_hc_xfercomp_intr(hcd, hc, hc_regs, qtd);
+		} else if (hcint.b.nak) {
+			handle_hc_nak_intr(hcd, hc, hc_regs, qtd);
+		} else {
+			DWC_WARN("Unexpected IRQ state on FSM transaction:"
+					"dev_addr=%d ep=%d fsm=%d, hcint=0x%08x\n",
+				hc->dev_addr, hc->ep_num, st->fsm, hcint.d32);
+			release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS);
+		}
+		break;
+
+	case FIQ_NP_SPLIT_HS_ABORTED:
+		/* A HS abort is a 3-strikes on the HS bus at any point in the transaction.
+		 * Normally a CLEAR_TT_BUFFER hub command would be required: we can't do that
+		 * because there's no guarantee which order a non-periodic split happened in.
+		 * We could end up clearing a perfectly good transaction out of the buffer.
+		 */
+		if (hcint.b.xacterr) {
+			qtd->error_count += st->nr_errors;
+			handle_hc_xacterr_intr(hcd, hc, hc_regs, qtd);
+		} else if (hcint.b.ahberr) {
+			handle_hc_ahberr_intr(hcd, hc, hc_regs, qtd);
+		} else {
+			DWC_WARN("Unexpected IRQ state on FSM transaction:"
+					"dev_addr=%d ep=%d fsm=%d, hcint=0x%08x\n",
+				hc->dev_addr, hc->ep_num, st->fsm, hcint.d32);
+			release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS);
+		}
+		break;
+
+	case FIQ_NP_SPLIT_LS_ABORTED:
+		/* A few cases can cause this - either an unknown state on a SSPLIT or
+		 * STALL/data toggle error response on a CSPLIT */
+		if (hcint.b.stall) {
+			handle_hc_stall_intr(hcd, hc, hc_regs, qtd);
+		} else if (hcint.b.datatglerr) {
+			handle_hc_datatglerr_intr(hcd, hc, hc_regs, qtd);
+		} else if (hcint.b.bblerr) {
+			handle_hc_babble_intr(hcd, hc, hc_regs, qtd);
+		} else if (hcint.b.ahberr) {
+			handle_hc_ahberr_intr(hcd, hc, hc_regs, qtd);
+		} else {
+			DWC_WARN("Unexpected IRQ state on FSM transaction:"
+					"dev_addr=%d ep=%d fsm=%d, hcint=0x%08x\n",
+				hc->dev_addr, hc->ep_num, st->fsm, hcint.d32);
+			release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS);
+		}
+		break;
+
+	case FIQ_PER_SPLIT_DONE:
+		/* Isoc IN or Interrupt IN/OUT */
+
+		/* Flow control here is different from the normal execution by the driver.
+		* We need to completely ignore most of the driver's method of handling
+		* split transactions and do it ourselves.
+		*/
+		if (hc->ep_type == UE_INTERRUPT) {
+			if (hcint.b.nak) {
+					handle_hc_nak_intr(hcd, hc, hc_regs, qtd);
+			} else if (hc->ep_is_in) {
+				int len;
+				len = dwc_otg_fiq_unsetup_per_dma(hcd, hc->qh, qtd, num);
+				//printk(KERN_NOTICE "FIQ Transaction: hc=%d len=%d urb_len = %d\n", num, len, qtd->urb->length);
+				qtd->urb->actual_length += len;
+				if (qtd->urb->actual_length >= qtd->urb->length) {
+					qtd->urb->status = 0;
+					hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, qtd->urb->status);
+					release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE);
+				} else {
+					/* Interrupt transfer not complete yet - is it a short read? */
+					if (len < hc->max_packet) {
+						/* Interrupt transaction complete */
+						qtd->urb->status = 0;
+						hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, qtd->urb->status);
+						release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE);
+					} else {
+						/* Further transactions required */
+						release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_COMPLETE);
+					}
+				}
+			} else {
+				/* Interrupt OUT complete. */
+				dwc_otg_hcd_save_data_toggle(hc, hc_regs, qtd);
+				qtd->urb->actual_length += hc->xfer_len;
+				if (qtd->urb->actual_length >= qtd->urb->length) {
+					qtd->urb->status = 0;
+					hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, qtd->urb->status);
+					release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE);
+				} else {
+					release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_COMPLETE);
+				}
+			}
+		} else {
+			/* ISOC IN complete. */
+			struct dwc_otg_hcd_iso_packet_desc *frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index];
+			int len = 0;
+			/* Record errors, update qtd. */
+			if (st->nr_errors) {
+				frame_desc->actual_length = 0;
+				frame_desc->status = -DWC_E_PROTOCOL;
+			} else {
+				frame_desc->status = 0;
+				/* Unswizzle dma */
+				len = dwc_otg_fiq_unsetup_per_dma(hcd, hc->qh, qtd, num);
+				frame_desc->actual_length = len;
+			}
+			qtd->isoc_frame_index++;
+			if (qtd->isoc_frame_index == qtd->urb->packet_count) {
+				hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, 0);
+				release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE);
+			} else {
+				release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_COMPLETE);
+			}
+		}
+		break;
+
+	case FIQ_PER_ISO_OUT_DONE: {
+			struct dwc_otg_hcd_iso_packet_desc *frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index];
+			/* Record errors, update qtd. */
+			if (st->nr_errors) {
+				frame_desc->actual_length = 0;
+				frame_desc->status = -DWC_E_PROTOCOL;
+			} else {
+				frame_desc->status = 0;
+				frame_desc->actual_length = frame_desc->length;
+			}
+			qtd->isoc_frame_index++;
+			qtd->isoc_split_offset = 0;
+			if (qtd->isoc_frame_index == qtd->urb->packet_count) {
+				hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, 0);
+				release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE);
+			} else {
+				release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_COMPLETE);
+			}
+		}
+		break;
+
+	case FIQ_PER_SPLIT_NYET_ABORTED:
+		/* Doh. lost the data. */
+		printk_ratelimited(KERN_INFO "Transfer to device %d endpoint 0x%x frame %d failed "
+				"- FIQ reported NYET. Data may have been lost.\n",
+				hc->dev_addr, hc->ep_num, dwc_otg_hcd_get_frame_number(hcd) >> 3);
+		if (hc->ep_type == UE_ISOCHRONOUS) {
+			struct dwc_otg_hcd_iso_packet_desc *frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index];
+			/* Record errors, update qtd. */
+			frame_desc->actual_length = 0;
+			frame_desc->status = -DWC_E_PROTOCOL;
+			qtd->isoc_frame_index++;
+			qtd->isoc_split_offset = 0;
+			if (qtd->isoc_frame_index == qtd->urb->packet_count) {
+				hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, 0);
+				release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE);
+			} else {
+				release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_COMPLETE);
+			}
+		} else {
+			release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS);
+		}
+		break;
+
+	case FIQ_HS_ISOC_DONE:
+		/* The FIQ has performed a whole pile of isochronous transactions.
+		 * The status is recorded as the interrupt state should the transaction
+		 * fail.
+		 */
+		dwc_otg_fiq_unmangle_isoc(hcd, hc->qh, qtd, num);
+		hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, 0);
+		release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE);
+		break;
+
+	case FIQ_PER_SPLIT_LS_ABORTED:
+		if (hcint.b.xacterr) {
+			/* Hub has responded with an ERR packet. Device
+			 * has been unplugged or the port has been disabled.
+			 * TODO: need to issue a reset to the hub port. */
+			qtd->error_count += 3;
+			handle_hc_xacterr_intr(hcd, hc, hc_regs, qtd);
+		} else if (hcint.b.stall) {
+			handle_hc_stall_intr(hcd, hc, hc_regs, qtd);
+		} else if (hcint.b.bblerr) {
+			handle_hc_babble_intr(hcd, hc, hc_regs, qtd);
+		} else {
+			printk_ratelimited(KERN_INFO "Transfer to device %d endpoint 0x%x failed "
+				"- FIQ reported FSM=%d. Data may have been lost.\n",
+				st->fsm, hc->dev_addr, hc->ep_num);
+			release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS);
+		}
+		break;
+
+	case FIQ_PER_SPLIT_HS_ABORTED:
+		/* Either the SSPLIT phase suffered transaction errors or something
+		 * unexpected happened.
+		 */
+		qtd->error_count += 3;
+		handle_hc_xacterr_intr(hcd, hc, hc_regs, qtd);
+		release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS);
+		break;
+
+	case FIQ_PER_SPLIT_TIMEOUT:
+		/* Couldn't complete in the nominated frame */
+		printk(KERN_INFO "Transfer to device %d endpoint 0x%x frame %d failed "
+				"- FIQ timed out. Data may have been lost.\n",
+				hc->dev_addr, hc->ep_num, dwc_otg_hcd_get_frame_number(hcd) >> 3);
+		if (hc->ep_type == UE_ISOCHRONOUS) {
+			struct dwc_otg_hcd_iso_packet_desc *frame_desc = &qtd->urb->iso_descs[qtd->isoc_frame_index];
+			/* Record errors, update qtd. */
+			frame_desc->actual_length = 0;
+			if (hc->ep_is_in) {
+				frame_desc->status = -DWC_E_NO_STREAM_RES;
+			} else {
+				frame_desc->status = -DWC_E_COMMUNICATION;
+			}
+			qtd->isoc_frame_index++;
+			if (qtd->isoc_frame_index == qtd->urb->packet_count) {
+				hcd->fops->complete(hcd, qtd->urb->priv, qtd->urb, 0);
+				release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_URB_COMPLETE);
+			} else {
+				release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_COMPLETE);
+			}
+		} else {
+			release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS);
+		}
+		break;
+
+	default:
+		DWC_WARN("Unexpected state received on hc=%d fsm=%d on transfer to device %d ep 0x%x", 
+					hc->hc_num, st->fsm, hc->dev_addr, hc->ep_num);
+		qtd->error_count++;
+		release_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NO_HALT_STATUS);
+	}
+	return;
+}
+
+/** Handles interrupt for a specific Host Channel */
+int32_t dwc_otg_hcd_handle_hc_n_intr(dwc_otg_hcd_t * dwc_otg_hcd, uint32_t num)
+{
+	int retval = 0;
+	hcint_data_t hcint;
+	hcintmsk_data_t hcintmsk;
+	dwc_hc_t *hc;
+	dwc_otg_hc_regs_t *hc_regs;
+	dwc_otg_qtd_t *qtd;
+
+	DWC_DEBUGPL(DBG_HCDV, "--Host Channel Interrupt--, Channel %d\n", num);
+
+	hc = dwc_otg_hcd->hc_ptr_array[num];
+	hc_regs = dwc_otg_hcd->core_if->host_if->hc_regs[num];
+	if(hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE) {
+		/* A dequeue was issued for this transfer. Our QTD has gone away
+		 * but in the case of a FIQ transfer, the transfer would have run
+		 * to completion.
+		 */
+		if (fiq_fsm_enable && dwc_otg_hcd->fiq_state->channel[num].fsm != FIQ_PASSTHROUGH) {
+			dwc_otg_hcd_handle_hc_fsm(dwc_otg_hcd, num);
+		} else {
+			release_channel(dwc_otg_hcd, hc, NULL, hc->halt_status);
+		}
+		return 1;
+	}
+	qtd = DWC_CIRCLEQ_FIRST(&hc->qh->qtd_list);
+
+	/*
+	 * FSM mode: Check to see if this is a HC interrupt from a channel handled by the FIQ.
+	 * Execution path is fundamentally different for the channels after a FIQ has completed
+	 * a split transaction.
+	 */
+	if (fiq_fsm_enable) {
+		switch (dwc_otg_hcd->fiq_state->channel[num].fsm) {
+			case FIQ_PASSTHROUGH:
+				break;
+			case FIQ_PASSTHROUGH_ERRORSTATE:
+				/* Hook into the error count */
+				fiq_print(FIQDBG_ERR, dwc_otg_hcd->fiq_state, "HCDERR%02d", num);
+				if (!dwc_otg_hcd->fiq_state->channel[num].nr_errors) {
+					qtd->error_count = 0;
+					fiq_print(FIQDBG_ERR, dwc_otg_hcd->fiq_state, "RESET   ");
+				}
+				break;
+			default:
+				dwc_otg_hcd_handle_hc_fsm(dwc_otg_hcd, num);
+				return 1;
+		}
+	}
+
+	hcint.d32 = DWC_READ_REG32(&hc_regs->hcint);
+	hcintmsk.d32 = DWC_READ_REG32(&hc_regs->hcintmsk);
+	hcint.d32 = hcint.d32 & hcintmsk.d32;
+	if (!dwc_otg_hcd->core_if->dma_enable) {
+		if (hcint.b.chhltd && hcint.d32 != 0x2) {
+			hcint.b.chhltd = 0;
+		}
+	}
+
+	if (hcint.b.xfercomp) {
+		retval |=
+		    handle_hc_xfercomp_intr(dwc_otg_hcd, hc, hc_regs, qtd);
+		/*
+		 * If NYET occurred at same time as Xfer Complete, the NYET is
+		 * handled by the Xfer Complete interrupt handler. Don't want
+		 * to call the NYET interrupt handler in this case.
+		 */
+		hcint.b.nyet = 0;
+	}
+	if (hcint.b.chhltd) {
+		retval |= handle_hc_chhltd_intr(dwc_otg_hcd, hc, hc_regs, qtd);
+	}
+	if (hcint.b.ahberr) {
+		retval |= handle_hc_ahberr_intr(dwc_otg_hcd, hc, hc_regs, qtd);
+	}
+	if (hcint.b.stall) {
+		retval |= handle_hc_stall_intr(dwc_otg_hcd, hc, hc_regs, qtd);
+	}
+	if (hcint.b.nak) {
+		retval |= handle_hc_nak_intr(dwc_otg_hcd, hc, hc_regs, qtd);
+	}
+	if (hcint.b.ack) {
+		if(!hcint.b.chhltd)
+			retval |= handle_hc_ack_intr(dwc_otg_hcd, hc, hc_regs, qtd);
+	}
+	if (hcint.b.nyet) {
+		retval |= handle_hc_nyet_intr(dwc_otg_hcd, hc, hc_regs, qtd);
+	}
+	if (hcint.b.xacterr) {
+		retval |= handle_hc_xacterr_intr(dwc_otg_hcd, hc, hc_regs, qtd);
+	}
+	if (hcint.b.bblerr) {
+		retval |= handle_hc_babble_intr(dwc_otg_hcd, hc, hc_regs, qtd);
+	}
+	if (hcint.b.frmovrun) {
+		retval |=
+		    handle_hc_frmovrun_intr(dwc_otg_hcd, hc, hc_regs, qtd);
+	}
+	if (hcint.b.datatglerr) {
+		retval |=
+		    handle_hc_datatglerr_intr(dwc_otg_hcd, hc, hc_regs, qtd);
+	}
+
+	return retval;
+}
+#endif /* DWC_DEVICE_ONLY */
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c
new file mode 100644
index 000000000000..430a1a510295
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c
@@ -0,0 +1,1081 @@
+
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_linux.c $
+ * $Revision: #20 $
+ * $Date: 2011/10/26 $
+ * $Change: 1872981 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+#ifndef DWC_DEVICE_ONLY
+
+/**
+ * @file
+ *
+ * This file contains the implementation of the HCD. In Linux, the HCD
+ * implements the hc_driver API.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/string.h>
+#include <linux/dma-mapping.h>
+#include <linux/version.h>
+#include <asm/io.h>
+#ifdef CONFIG_ARM
+#include <asm/fiq.h>
+#endif
+#include <linux/usb.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35)
+#include <../drivers/usb/core/hcd.h>
+#else
+#include <linux/usb/hcd.h>
+#endif
+#include <asm/bug.h>
+
+#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30))
+#define USB_URB_EP_LINKING 1
+#else
+#define USB_URB_EP_LINKING 0
+#endif
+
+#include "dwc_otg_hcd_if.h"
+#include "dwc_otg_dbg.h"
+#include "dwc_otg_driver.h"
+#include "dwc_otg_hcd.h"
+
+#ifndef __virt_to_bus
+#define __virt_to_bus	__virt_to_phys
+#define __bus_to_virt	__phys_to_virt
+#define __pfn_to_bus(x)	__pfn_to_phys(x)
+#define __bus_to_pfn(x)	__phys_to_pfn(x)
+#endif
+
+extern unsigned char  _dwc_otg_fiq_stub, _dwc_otg_fiq_stub_end;
+
+/**
+ * Gets the endpoint number from a _bEndpointAddress argument. The endpoint is
+ * qualified with its direction (possible 32 endpoints per device).
+ */
+#define dwc_ep_addr_to_endpoint(_bEndpointAddress_) ((_bEndpointAddress_ & USB_ENDPOINT_NUMBER_MASK) | \
+						     ((_bEndpointAddress_ & USB_DIR_IN) != 0) << 4)
+
+static const char dwc_otg_hcd_name[] = "dwc_otg_hcd";
+
+extern bool fiq_enable;
+
+/** @name Linux HC Driver API Functions */
+/** @{ */
+/* manage i/o requests, device state */
+static int dwc_otg_urb_enqueue(struct usb_hcd *hcd,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+		       struct usb_host_endpoint *ep,
+#endif
+		       struct urb *urb, gfp_t mem_flags);
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30)
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+static int dwc_otg_urb_dequeue(struct usb_hcd *hcd, struct urb *urb);
+#endif
+#else /* kernels at or post 2.6.30 */
+static int dwc_otg_urb_dequeue(struct usb_hcd *hcd,
+                               struct urb *urb, int status);
+#endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30) */
+
+static void endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *ep);
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30)
+static void endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep);
+#endif
+static irqreturn_t dwc_otg_hcd_irq(struct usb_hcd *hcd);
+extern int hcd_start(struct usb_hcd *hcd);
+extern void hcd_stop(struct usb_hcd *hcd);
+static int get_frame_number(struct usb_hcd *hcd);
+extern int hub_status_data(struct usb_hcd *hcd, char *buf);
+extern int hub_control(struct usb_hcd *hcd,
+		       u16 typeReq,
+		       u16 wValue, u16 wIndex, char *buf, u16 wLength);
+
+struct wrapper_priv_data {
+	dwc_otg_hcd_t *dwc_otg_hcd;
+};
+
+/** @} */
+
+static struct hc_driver dwc_otg_hc_driver = {
+
+	.description = dwc_otg_hcd_name,
+	.product_desc = "DWC OTG Controller",
+	.hcd_priv_size = sizeof(struct wrapper_priv_data),
+
+	.irq = dwc_otg_hcd_irq,
+
+	.flags = HCD_MEMORY | HCD_DMA | HCD_USB2,
+
+	//.reset =
+	.start = hcd_start,
+	//.suspend =
+	//.resume =
+	.stop = hcd_stop,
+
+	.urb_enqueue = dwc_otg_urb_enqueue,
+	.urb_dequeue = dwc_otg_urb_dequeue,
+	.endpoint_disable = endpoint_disable,
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30)
+	.endpoint_reset = endpoint_reset,
+#endif
+	.get_frame_number = get_frame_number,
+
+	.hub_status_data = hub_status_data,
+	.hub_control = hub_control,
+	//.bus_suspend =
+	//.bus_resume =
+};
+
+/** Gets the dwc_otg_hcd from a struct usb_hcd */
+static inline dwc_otg_hcd_t *hcd_to_dwc_otg_hcd(struct usb_hcd *hcd)
+{
+	struct wrapper_priv_data *p;
+	p = (struct wrapper_priv_data *)(hcd->hcd_priv);
+	return p->dwc_otg_hcd;
+}
+
+/** Gets the struct usb_hcd that contains a dwc_otg_hcd_t. */
+static inline struct usb_hcd *dwc_otg_hcd_to_hcd(dwc_otg_hcd_t * dwc_otg_hcd)
+{
+	return dwc_otg_hcd_get_priv_data(dwc_otg_hcd);
+}
+
+/** Gets the usb_host_endpoint associated with an URB. */
+inline struct usb_host_endpoint *dwc_urb_to_endpoint(struct urb *urb)
+{
+	struct usb_device *dev = urb->dev;
+	int ep_num = usb_pipeendpoint(urb->pipe);
+
+	if (usb_pipein(urb->pipe))
+		return dev->ep_in[ep_num];
+	else
+		return dev->ep_out[ep_num];
+}
+
+static int _disconnect(dwc_otg_hcd_t * hcd)
+{
+	struct usb_hcd *usb_hcd = dwc_otg_hcd_to_hcd(hcd);
+
+	usb_hcd->self.is_b_host = 0;
+	return 0;
+}
+
+static int _start(dwc_otg_hcd_t * hcd)
+{
+	struct usb_hcd *usb_hcd = dwc_otg_hcd_to_hcd(hcd);
+
+	usb_hcd->self.is_b_host = dwc_otg_hcd_is_b_host(hcd);
+	hcd_start(usb_hcd);
+
+	return 0;
+}
+
+static int _hub_info(dwc_otg_hcd_t * hcd, void *urb_handle, uint32_t * hub_addr,
+		     uint32_t * port_addr)
+{
+   struct urb *urb = (struct urb *)urb_handle;
+   struct usb_bus *bus;
+#if 1 //GRAYG - temporary
+   if (NULL == urb_handle)
+      DWC_ERROR("**** %s - NULL URB handle\n", __func__);//GRAYG
+   if (NULL == urb->dev)
+      DWC_ERROR("**** %s - URB has no device\n", __func__);//GRAYG
+   if (NULL == port_addr)
+      DWC_ERROR("**** %s - NULL port_address\n", __func__);//GRAYG
+#endif
+   if (urb->dev->tt) {
+        if (NULL == urb->dev->tt->hub) {
+                DWC_ERROR("**** %s - (URB's transactor has no TT - giving no hub)\n",
+                           __func__); //GRAYG
+                //*hub_addr = (u8)usb_pipedevice(urb->pipe); //GRAYG
+                *hub_addr = 0; //GRAYG
+                // we probably shouldn't have a transaction translator if
+                // there's no associated hub?
+        } else {
+		bus = hcd_to_bus(dwc_otg_hcd_to_hcd(hcd));
+		if (urb->dev->tt->hub == bus->root_hub)
+			*hub_addr = 0;
+		else
+			*hub_addr = urb->dev->tt->hub->devnum;
+	}
+	*port_addr = urb->dev->ttport;
+   } else {
+        *hub_addr = 0;
+	*port_addr = urb->dev->ttport;
+   }
+   return 0;
+}
+
+static int _speed(dwc_otg_hcd_t * hcd, void *urb_handle)
+{
+	struct urb *urb = (struct urb *)urb_handle;
+	return urb->dev->speed;
+}
+
+static int _get_b_hnp_enable(dwc_otg_hcd_t * hcd)
+{
+	struct usb_hcd *usb_hcd = dwc_otg_hcd_to_hcd(hcd);
+	return usb_hcd->self.b_hnp_enable;
+}
+
+static void allocate_bus_bandwidth(struct usb_hcd *hcd, uint32_t bw,
+				   struct urb *urb)
+{
+	hcd_to_bus(hcd)->bandwidth_allocated += bw / urb->interval;
+	if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) {
+		hcd_to_bus(hcd)->bandwidth_isoc_reqs++;
+	} else {
+		hcd_to_bus(hcd)->bandwidth_int_reqs++;
+	}
+}
+
+static void free_bus_bandwidth(struct usb_hcd *hcd, uint32_t bw,
+			       struct urb *urb)
+{
+	hcd_to_bus(hcd)->bandwidth_allocated -= bw / urb->interval;
+	if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) {
+		hcd_to_bus(hcd)->bandwidth_isoc_reqs--;
+	} else {
+		hcd_to_bus(hcd)->bandwidth_int_reqs--;
+	}
+}
+
+/**
+ * Sets the final status of an URB and returns it to the device driver. Any
+ * required cleanup of the URB is performed.  The HCD lock should be held on
+ * entry.
+ */
+static int _complete(dwc_otg_hcd_t * hcd, void *urb_handle,
+		     dwc_otg_hcd_urb_t * dwc_otg_urb, int32_t status)
+{
+	struct urb *urb = (struct urb *)urb_handle;
+	urb_tq_entry_t *new_entry;
+	int rc = 0;
+	if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) {
+		DWC_PRINTF("%s: urb %p, device %d, ep %d %s, status=%d\n",
+			   __func__, urb, usb_pipedevice(urb->pipe),
+			   usb_pipeendpoint(urb->pipe),
+			   usb_pipein(urb->pipe) ? "IN" : "OUT", status);
+		if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) {
+			int i;
+			for (i = 0; i < urb->number_of_packets; i++) {
+				DWC_PRINTF("  ISO Desc %d status: %d\n",
+					   i, urb->iso_frame_desc[i].status);
+			}
+		}
+	}
+	new_entry = DWC_ALLOC_ATOMIC(sizeof(urb_tq_entry_t));
+	urb->actual_length = dwc_otg_hcd_urb_get_actual_length(dwc_otg_urb);
+	/* Convert status value. */
+	switch (status) {
+	case -DWC_E_PROTOCOL:
+		status = -EPROTO;
+		break;
+	case -DWC_E_IN_PROGRESS:
+		status = -EINPROGRESS;
+		break;
+	case -DWC_E_PIPE:
+		status = -EPIPE;
+		break;
+	case -DWC_E_IO:
+		status = -EIO;
+		break;
+	case -DWC_E_TIMEOUT:
+		status = -ETIMEDOUT;
+		break;
+	case -DWC_E_OVERFLOW:
+		status = -EOVERFLOW;
+		break;
+	case -DWC_E_SHUTDOWN:
+		status = -ESHUTDOWN;
+		break;
+	default:
+		if (status) {
+			DWC_PRINTF("Uknown urb status %d\n", status);
+
+		}
+	}
+
+	if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) {
+		int i;
+
+		urb->error_count = dwc_otg_hcd_urb_get_error_count(dwc_otg_urb);
+		urb->actual_length = 0;
+		for (i = 0; i < urb->number_of_packets; ++i) {
+			urb->iso_frame_desc[i].actual_length =
+			    dwc_otg_hcd_urb_get_iso_desc_actual_length
+			    (dwc_otg_urb, i);
+			urb->actual_length += urb->iso_frame_desc[i].actual_length;
+			urb->iso_frame_desc[i].status =
+			    dwc_otg_hcd_urb_get_iso_desc_status(dwc_otg_urb, i);
+		}
+	}
+
+	urb->status = status;
+	urb->hcpriv = NULL;
+	if (!status) {
+		if ((urb->transfer_flags & URB_SHORT_NOT_OK) &&
+		    (urb->actual_length < urb->transfer_buffer_length)) {
+			urb->status = -EREMOTEIO;
+		}
+	}
+
+	if ((usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) ||
+	    (usb_pipetype(urb->pipe) == PIPE_INTERRUPT)) {
+		struct usb_host_endpoint *ep = dwc_urb_to_endpoint(urb);
+		if (ep) {
+			free_bus_bandwidth(dwc_otg_hcd_to_hcd(hcd),
+					   dwc_otg_hcd_get_ep_bandwidth(hcd,
+									ep->hcpriv),
+					   urb);
+		}
+	}
+	DWC_FREE(dwc_otg_urb);
+	if (!new_entry) {
+		DWC_ERROR("dwc_otg_hcd: complete: cannot allocate URB TQ entry\n");
+		urb->status = -EPROTO;
+		/* don't schedule the tasklet -
+		 * directly return the packet here with error. */
+#if USB_URB_EP_LINKING
+		usb_hcd_unlink_urb_from_ep(dwc_otg_hcd_to_hcd(hcd), urb);
+#endif
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+		usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb);
+#else
+		usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb, urb->status);
+#endif
+	} else {
+		new_entry->urb = urb;
+#if USB_URB_EP_LINKING
+		rc = usb_hcd_check_unlink_urb(dwc_otg_hcd_to_hcd(hcd), urb, urb->status);
+		if(0 == rc) {
+			usb_hcd_unlink_urb_from_ep(dwc_otg_hcd_to_hcd(hcd), urb);
+		}
+#endif
+		if(0 == rc) {
+			DWC_TAILQ_INSERT_TAIL(&hcd->completed_urb_list, new_entry,
+						urb_tq_entries);
+			DWC_TASK_HI_SCHEDULE(hcd->completion_tasklet);
+		}
+	}
+	return 0;
+}
+
+static struct dwc_otg_hcd_function_ops hcd_fops = {
+	.start = _start,
+	.disconnect = _disconnect,
+	.hub_info = _hub_info,
+	.speed = _speed,
+	.complete = _complete,
+	.get_b_hnp_enable = _get_b_hnp_enable,
+};
+
+#ifdef CONFIG_ARM64
+
+/*
+ * With no FIQ support on AARCH64, the "FIQ handler" is demoted to a
+ * regular IRQ handler. With a nested spinlock preventing the two
+ * handlers from racing against each other, and a HCD lock preventing
+ * thread context from racing against the "bottom half" IRQ, there's no
+ * point manipulating global IRQ enable/disable state - so these two
+ * functions are no-ops.
+ */
+void local_fiq_enable(void) { }
+
+void local_fiq_disable(void) { }
+
+static irqreturn_t fiq_irq_handler(int irq, void *dev_id)
+{
+	dwc_otg_hcd_t *dwc_otg_hcd = (dwc_otg_hcd_t *)dev_id;
+
+	if (fiq_fsm_enable)
+		dwc_otg_fiq_fsm(dwc_otg_hcd->fiq_state, dwc_otg_hcd->core_if->core_params->host_channels);
+	else
+		dwc_otg_fiq_nop(dwc_otg_hcd->fiq_state);
+
+	return IRQ_HANDLED;
+}
+
+#else
+static struct fiq_handler fh = {
+  .name = "usb_fiq",
+};
+
+#endif
+
+static void hcd_init_fiq(void *cookie)
+{
+	dwc_otg_device_t *otg_dev = cookie;
+	dwc_otg_hcd_t *dwc_otg_hcd = otg_dev->hcd;
+#ifdef CONFIG_ARM64
+	int retval = 0;
+	int irq;
+#else
+	struct pt_regs regs;
+	int irq;
+
+	if (claim_fiq(&fh)) {
+		DWC_ERROR("Can't claim FIQ");
+		BUG();
+	}
+	DWC_INFO("FIQ on core %d", smp_processor_id());
+	DWC_INFO("FIQ ASM at %px length %d", &_dwc_otg_fiq_stub, (int)(&_dwc_otg_fiq_stub_end - &_dwc_otg_fiq_stub));
+	set_fiq_handler((void *) &_dwc_otg_fiq_stub, &_dwc_otg_fiq_stub_end - &_dwc_otg_fiq_stub);
+	memset(&regs,0,sizeof(regs));
+
+	regs.ARM_r8 = (long) dwc_otg_hcd->fiq_state;
+	if (fiq_fsm_enable) {
+		regs.ARM_r9 = dwc_otg_hcd->core_if->core_params->host_channels;
+		//regs.ARM_r10 = dwc_otg_hcd->dma;
+		regs.ARM_fp = (long) dwc_otg_fiq_fsm;
+	} else {
+		regs.ARM_fp = (long) dwc_otg_fiq_nop;
+	}
+
+	regs.ARM_sp = (long) dwc_otg_hcd->fiq_stack + (sizeof(struct fiq_stack) - 4);
+
+//		__show_regs(&regs);
+	set_fiq_regs(&regs);
+#endif
+
+	dwc_otg_hcd->fiq_state->dwc_regs_base = otg_dev->os_dep.base;
+	//Set the mphi periph to the required registers
+	dwc_otg_hcd->fiq_state->mphi_regs.base    = otg_dev->os_dep.mphi_base;
+	if (otg_dev->os_dep.use_swirq) {
+		dwc_otg_hcd->fiq_state->mphi_regs.swirq_set =
+			otg_dev->os_dep.mphi_base + 0x1f0;
+		dwc_otg_hcd->fiq_state->mphi_regs.swirq_clr =
+			otg_dev->os_dep.mphi_base + 0x1f4;
+		DWC_INFO("Fake MPHI regs_base at %px",
+			 dwc_otg_hcd->fiq_state->mphi_regs.base);
+	} else {
+		dwc_otg_hcd->fiq_state->mphi_regs.ctrl =
+			otg_dev->os_dep.mphi_base + 0x4c;
+		dwc_otg_hcd->fiq_state->mphi_regs.outdda
+			= otg_dev->os_dep.mphi_base + 0x28;
+		dwc_otg_hcd->fiq_state->mphi_regs.outddb
+			= otg_dev->os_dep.mphi_base + 0x2c;
+		dwc_otg_hcd->fiq_state->mphi_regs.intstat
+			= otg_dev->os_dep.mphi_base + 0x50;
+		DWC_INFO("MPHI regs_base at %px",
+			 dwc_otg_hcd->fiq_state->mphi_regs.base);
+
+		//Enable mphi peripheral
+		writel((1<<31),dwc_otg_hcd->fiq_state->mphi_regs.ctrl);
+#ifdef DEBUG
+		if (readl(dwc_otg_hcd->fiq_state->mphi_regs.ctrl) & 0x80000000)
+			DWC_INFO("MPHI periph has been enabled");
+		else
+			DWC_INFO("MPHI periph has NOT been enabled");
+#endif
+	}
+	// Enable FIQ interrupt from USB peripheral
+#ifdef CONFIG_ARM64
+	irq = otg_dev->os_dep.fiq_num;
+
+	if (irq < 0) {
+		DWC_ERROR("Can't get SIM-FIQ irq");
+		return;
+	}
+
+	retval = request_irq(irq, fiq_irq_handler, 0, "dwc_otg_sim-fiq", dwc_otg_hcd);
+
+	if (retval < 0) {
+		DWC_ERROR("Unable to request SIM-FIQ irq\n");
+		return;
+	}
+
+#else
+#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
+	irq = otg_dev->os_dep.fiq_num;
+#else
+	irq = INTERRUPT_VC_USB;
+#endif
+	if (irq < 0) {
+		DWC_ERROR("Can't get FIQ irq");
+		return;
+	}
+	/*
+	 * We could take an interrupt immediately after enabling the FIQ.
+	 * Ensure coherency of hcd->fiq_state.
+	 */
+	smp_mb();
+	enable_fiq(irq);
+	local_fiq_enable();
+#endif
+
+}
+
+/**
+ * Initializes the HCD. This function allocates memory for and initializes the
+ * static parts of the usb_hcd and dwc_otg_hcd structures. It also registers the
+ * USB bus with the core and calls the hc_driver->start() function. It returns
+ * a negative error on failure.
+ */
+int hcd_init(dwc_bus_dev_t *_dev)
+{
+	struct usb_hcd *hcd = NULL;
+	dwc_otg_hcd_t *dwc_otg_hcd = NULL;
+	dwc_otg_device_t *otg_dev = DWC_OTG_BUSDRVDATA(_dev);
+	int retval = 0;
+        u64 dmamask;
+
+	DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD INIT otg_dev=%p\n", otg_dev);
+
+	/* Set device flags indicating whether the HCD supports DMA. */
+	if (dwc_otg_is_dma_enable(otg_dev->core_if))
+                dmamask = DMA_BIT_MASK(32);
+        else
+                dmamask = 0;
+
+#if    defined(LM_INTERFACE) || defined(PLATFORM_INTERFACE)
+        dma_set_mask(&_dev->dev, dmamask);
+        dma_set_coherent_mask(&_dev->dev, dmamask);
+#elif  defined(PCI_INTERFACE)
+        pci_set_dma_mask(_dev, dmamask);
+        pci_set_consistent_dma_mask(_dev, dmamask);
+#endif
+
+	/*
+	 * Allocate memory for the base HCD plus the DWC OTG HCD.
+	 * Initialize the base HCD.
+	 */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30)
+	hcd = usb_create_hcd(&dwc_otg_hc_driver, &_dev->dev, _dev->dev.bus_id);
+#else
+	hcd = usb_create_hcd(&dwc_otg_hc_driver, &_dev->dev, dev_name(&_dev->dev));
+	hcd->has_tt = 1;
+//      hcd->uses_new_polling = 1;
+//      hcd->poll_rh = 0;
+#endif
+	if (!hcd) {
+		retval = -ENOMEM;
+		goto error1;
+	}
+
+	hcd->regs = otg_dev->os_dep.base;
+
+
+	/* Initialize the DWC OTG HCD. */
+	dwc_otg_hcd = dwc_otg_hcd_alloc_hcd();
+	if (!dwc_otg_hcd) {
+		goto error2;
+	}
+	((struct wrapper_priv_data *)(hcd->hcd_priv))->dwc_otg_hcd =
+	    dwc_otg_hcd;
+	otg_dev->hcd = dwc_otg_hcd;
+	otg_dev->hcd->otg_dev = otg_dev;
+
+#ifdef CONFIG_ARM64
+	if (dwc_otg_hcd_init(dwc_otg_hcd, otg_dev->core_if))
+		goto error2;
+
+	if (fiq_enable)
+		hcd_init_fiq(otg_dev);
+#else
+	if (dwc_otg_hcd_init(dwc_otg_hcd, otg_dev->core_if)) {
+		goto error2;
+	}
+
+	if (fiq_enable) {
+		if (num_online_cpus() > 1) {
+			/*
+			 * bcm2709: can run the FIQ on a separate core to IRQs.
+			 * Ensure driver state is visible to other cores before setting up the FIQ.
+			 */
+			smp_mb();
+			smp_call_function_single(1, hcd_init_fiq, otg_dev, 1);
+		} else {
+			smp_call_function_single(0, hcd_init_fiq, otg_dev, 1);
+		}
+	}
+#endif
+
+	hcd->self.otg_port = dwc_otg_hcd_otg_port(dwc_otg_hcd);
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,33) //don't support for LM(with 2.6.20.1 kernel)
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35) //version field absent later
+	hcd->self.otg_version = dwc_otg_get_otg_version(otg_dev->core_if);
+#endif
+	/* Don't support SG list at this point */
+	hcd->self.sg_tablesize = 0;
+#endif
+	/*
+	 * Finish generic HCD initialization and start the HCD. This function
+	 * allocates the DMA buffer pool, registers the USB bus, requests the
+	 * IRQ line, and calls hcd_start method.
+	 */
+	retval = usb_add_hcd(hcd, otg_dev->os_dep.irq_num, IRQF_SHARED);
+	if (retval < 0) {
+		goto error2;
+	}
+
+	dwc_otg_hcd_set_priv_data(dwc_otg_hcd, hcd);
+	return 0;
+
+error2:
+	usb_put_hcd(hcd);
+error1:
+	return retval;
+}
+
+/**
+ * Removes the HCD.
+ * Frees memory and resources associated with the HCD and deregisters the bus.
+ */
+void hcd_remove(dwc_bus_dev_t *_dev)
+{
+	dwc_otg_device_t *otg_dev = DWC_OTG_BUSDRVDATA(_dev);
+	dwc_otg_hcd_t *dwc_otg_hcd;
+	struct usb_hcd *hcd;
+
+	DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD REMOVE otg_dev=%p\n", otg_dev);
+
+	if (!otg_dev) {
+		DWC_DEBUGPL(DBG_ANY, "%s: otg_dev NULL!\n", __func__);
+		return;
+	}
+
+	dwc_otg_hcd = otg_dev->hcd;
+
+	if (!dwc_otg_hcd) {
+		DWC_DEBUGPL(DBG_ANY, "%s: otg_dev->hcd NULL!\n", __func__);
+		return;
+	}
+
+	hcd = dwc_otg_hcd_to_hcd(dwc_otg_hcd);
+
+	if (!hcd) {
+		DWC_DEBUGPL(DBG_ANY,
+			    "%s: dwc_otg_hcd_to_hcd(dwc_otg_hcd) NULL!\n",
+			    __func__);
+		return;
+	}
+	usb_remove_hcd(hcd);
+	dwc_otg_hcd_set_priv_data(dwc_otg_hcd, NULL);
+	dwc_otg_hcd_remove(dwc_otg_hcd);
+	usb_put_hcd(hcd);
+}
+
+/* =========================================================================
+ *  Linux HC Driver Functions
+ * ========================================================================= */
+
+/** Initializes the DWC_otg controller and its root hub and prepares it for host
+ * mode operation. Activates the root port. Returns 0 on success and a negative
+ * error code on failure. */
+int hcd_start(struct usb_hcd *hcd)
+{
+	dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
+	struct usb_bus *bus;
+
+	DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD START\n");
+	bus = hcd_to_bus(hcd);
+
+	hcd->state = HC_STATE_RUNNING;
+	if (dwc_otg_hcd_start(dwc_otg_hcd, &hcd_fops)) {
+		return 0;
+	}
+
+	/* Initialize and connect root hub if one is not already attached */
+	if (bus->root_hub) {
+		DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD Has Root Hub\n");
+		/* Inform the HUB driver to resume. */
+		usb_hcd_resume_root_hub(hcd);
+	}
+
+	return 0;
+}
+
+/**
+ * Halts the DWC_otg host mode operations in a clean manner. USB transfers are
+ * stopped.
+ */
+void hcd_stop(struct usb_hcd *hcd)
+{
+	dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
+
+	dwc_otg_hcd_stop(dwc_otg_hcd);
+}
+
+/** Returns the current frame number. */
+static int get_frame_number(struct usb_hcd *hcd)
+{
+	hprt0_data_t hprt0;
+	dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
+	hprt0.d32 = DWC_READ_REG32(dwc_otg_hcd->core_if->host_if->hprt0);
+	if (hprt0.b.prtspd == DWC_HPRT0_PRTSPD_HIGH_SPEED)
+		return dwc_otg_hcd_get_frame_number(dwc_otg_hcd) >> 3;
+	else
+		return dwc_otg_hcd_get_frame_number(dwc_otg_hcd);
+}
+
+#ifdef DEBUG
+static void dump_urb_info(struct urb *urb, char *fn_name)
+{
+	DWC_PRINTF("%s, urb %p\n", fn_name, urb);
+	DWC_PRINTF("  Device address: %d\n", usb_pipedevice(urb->pipe));
+	DWC_PRINTF("  Endpoint: %d, %s\n", usb_pipeendpoint(urb->pipe),
+		   (usb_pipein(urb->pipe) ? "IN" : "OUT"));
+	DWC_PRINTF("  Endpoint type: %s\n", ( {
+					     char *pipetype;
+					     switch (usb_pipetype(urb->pipe)) {
+case PIPE_CONTROL:
+pipetype = "CONTROL"; break; case PIPE_BULK:
+pipetype = "BULK"; break; case PIPE_INTERRUPT:
+pipetype = "INTERRUPT"; break; case PIPE_ISOCHRONOUS:
+pipetype = "ISOCHRONOUS"; break; default:
+					     pipetype = "UNKNOWN"; break;};
+					     pipetype;}
+		   )) ;
+	DWC_PRINTF("  Speed: %s\n", ( {
+				     char *speed; switch (urb->dev->speed) {
+case USB_SPEED_HIGH:
+speed = "HIGH"; break; case USB_SPEED_FULL:
+speed = "FULL"; break; case USB_SPEED_LOW:
+speed = "LOW"; break; default:
+				     speed = "UNKNOWN"; break;};
+				     speed;}
+		   )) ;
+	DWC_PRINTF("  Max packet size: %d\n",
+		   usb_maxpacket(urb->dev, urb->pipe);
+	DWC_PRINTF("  Data buffer length: %d\n", urb->transfer_buffer_length);
+	DWC_PRINTF("  Transfer buffer: %p, Transfer DMA: %p\n",
+		   urb->transfer_buffer, (void *)urb->transfer_dma);
+	DWC_PRINTF("  Setup buffer: %p, Setup DMA: %p\n",
+		   urb->setup_packet, (void *)urb->setup_dma);
+	DWC_PRINTF("  Interval: %d\n", urb->interval);
+	if (usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS) {
+		int i;
+		for (i = 0; i < urb->number_of_packets; i++) {
+			DWC_PRINTF("  ISO Desc %d:\n", i);
+			DWC_PRINTF("    offset: %d, length %d\n",
+				   urb->iso_frame_desc[i].offset,
+				   urb->iso_frame_desc[i].length);
+		}
+	}
+}
+#endif
+
+/** Starts processing a USB transfer request specified by a USB Request Block
+ * (URB). mem_flags indicates the type of memory allocation to use while
+ * processing this URB. */
+static int dwc_otg_urb_enqueue(struct usb_hcd *hcd,
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+		       struct usb_host_endpoint *ep,
+#endif
+		       struct urb *urb, gfp_t mem_flags)
+{
+	int retval = 0;
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28)
+	struct usb_host_endpoint *ep = urb->ep;
+#endif
+	dwc_irqflags_t irqflags;
+	dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
+	dwc_otg_hcd_urb_t *dwc_otg_urb;
+	int i;
+	int alloc_bandwidth = 0;
+	uint8_t ep_type = 0;
+	uint32_t flags = 0;
+	void *buf;
+
+#ifdef DEBUG
+	if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) {
+		dump_urb_info(urb, "dwc_otg_urb_enqueue");
+	}
+#endif
+	if ((usb_pipetype(urb->pipe) == PIPE_ISOCHRONOUS)
+	    || (usb_pipetype(urb->pipe) == PIPE_INTERRUPT)) {
+		if (!dwc_otg_hcd_is_bandwidth_allocated
+		    (dwc_otg_hcd, ep->hcpriv)) {
+			alloc_bandwidth = 1;
+		}
+	}
+
+	switch (usb_pipetype(urb->pipe)) {
+	case PIPE_CONTROL:
+		ep_type = USB_ENDPOINT_XFER_CONTROL;
+		break;
+	case PIPE_ISOCHRONOUS:
+		ep_type = USB_ENDPOINT_XFER_ISOC;
+		break;
+	case PIPE_BULK:
+		ep_type = USB_ENDPOINT_XFER_BULK;
+		break;
+	case PIPE_INTERRUPT:
+		ep_type = USB_ENDPOINT_XFER_INT;
+		break;
+	default:
+                DWC_WARN("Wrong EP type - %d\n", usb_pipetype(urb->pipe));
+	}
+
+        /* # of packets is often 0 - do we really need to call this then? */
+	dwc_otg_urb = dwc_otg_hcd_urb_alloc(dwc_otg_hcd,
+					    urb->number_of_packets,
+					    mem_flags == GFP_ATOMIC ? 1 : 0);
+
+	if(dwc_otg_urb == NULL)
+		return -ENOMEM;
+
+	if (!dwc_otg_urb && urb->number_of_packets)
+		return -ENOMEM;
+
+	dwc_otg_hcd_urb_set_pipeinfo(dwc_otg_urb, usb_pipedevice(urb->pipe),
+				     usb_pipeendpoint(urb->pipe), ep_type,
+				     usb_pipein(urb->pipe),
+				     usb_maxpacket(urb->dev, urb->pipe));
+
+	buf = urb->transfer_buffer;
+	if (hcd_uses_dma(hcd) && !buf && urb->transfer_buffer_length) {
+		/*
+		 * Calculate virtual address from physical address,
+		 * because some class driver may not fill transfer_buffer.
+		 * In Buffer DMA mode virual address is used,
+		 * when handling non DWORD aligned buffers.
+		 */
+		buf = (void *)__bus_to_virt((unsigned long)urb->transfer_dma);
+		dev_warn_once(&urb->dev->dev,
+			      "USB transfer_buffer was NULL, will use __bus_to_virt(%pad)=%p\n",
+			      &urb->transfer_dma, buf);
+	}
+
+	if (!buf && urb->transfer_buffer_length) {
+		DWC_FREE(dwc_otg_urb);
+		DWC_ERROR("transfer_buffer is NULL in PIO mode or both "
+			   "transfer_buffer and transfer_dma are NULL in DMA mode\n");
+		return -EINVAL;
+	}
+
+	if (!(urb->transfer_flags & URB_NO_INTERRUPT))
+		flags |= URB_GIVEBACK_ASAP;
+	if (urb->transfer_flags & URB_ZERO_PACKET)
+		flags |= URB_SEND_ZERO_PACKET;
+
+	dwc_otg_hcd_urb_set_params(dwc_otg_urb, urb, buf,
+				   urb->transfer_dma,
+				   urb->transfer_buffer_length,
+				   urb->setup_packet,
+				   urb->setup_dma, flags, urb->interval);
+
+	for (i = 0; i < urb->number_of_packets; ++i) {
+		dwc_otg_hcd_urb_set_iso_desc_params(dwc_otg_urb, i,
+						    urb->
+						    iso_frame_desc[i].offset,
+						    urb->
+						    iso_frame_desc[i].length);
+	}
+
+	DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &irqflags);
+	urb->hcpriv = dwc_otg_urb;
+#if USB_URB_EP_LINKING
+	retval = usb_hcd_link_urb_to_ep(hcd, urb);
+	if (0 == retval)
+#endif
+	{
+		retval = dwc_otg_hcd_urb_enqueue(dwc_otg_hcd, dwc_otg_urb,
+						&ep->hcpriv, 1);
+		if (0 == retval) {
+			if (alloc_bandwidth) {
+				allocate_bus_bandwidth(hcd,
+						dwc_otg_hcd_get_ep_bandwidth(
+							dwc_otg_hcd, ep->hcpriv),
+						urb);
+			}
+		} else {
+			DWC_DEBUGPL(DBG_HCD, "DWC OTG dwc_otg_hcd_urb_enqueue failed rc %d\n", retval);
+#if USB_URB_EP_LINKING
+			usb_hcd_unlink_urb_from_ep(hcd, urb);
+#endif
+			DWC_FREE(dwc_otg_urb);
+			urb->hcpriv = NULL;
+			if (retval == -DWC_E_NO_DEVICE)
+				retval = -ENODEV;
+		}
+	}
+#if USB_URB_EP_LINKING
+	else
+	{
+		DWC_FREE(dwc_otg_urb);
+		urb->hcpriv = NULL;
+	}
+#endif
+	DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, irqflags);
+	return retval;
+}
+
+/** Aborts/cancels a USB transfer request. Always returns 0 to indicate
+ * success.  */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+static int dwc_otg_urb_dequeue(struct usb_hcd *hcd, struct urb *urb)
+#else
+static int dwc_otg_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
+#endif
+{
+	dwc_irqflags_t flags;
+	dwc_otg_hcd_t *dwc_otg_hcd;
+        int rc;
+
+	DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD URB Dequeue\n");
+
+	dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
+
+#ifdef DEBUG
+	if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) {
+		dump_urb_info(urb, "dwc_otg_urb_dequeue");
+	}
+#endif
+
+	DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags);
+	rc = usb_hcd_check_unlink_urb(hcd, urb, status);
+	if (0 == rc) {
+		if(urb->hcpriv != NULL) {
+	                dwc_otg_hcd_urb_dequeue(dwc_otg_hcd,
+	                                    (dwc_otg_hcd_urb_t *)urb->hcpriv);
+
+		        DWC_FREE(urb->hcpriv);
+			urb->hcpriv = NULL;
+		}
+        }
+
+        if (0 == rc) {
+		/* Higher layer software sets URB status. */
+#if USB_URB_EP_LINKING
+                usb_hcd_unlink_urb_from_ep(hcd, urb);
+#endif
+		DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, flags);
+
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+                usb_hcd_giveback_urb(hcd, urb);
+#else
+                usb_hcd_giveback_urb(hcd, urb, status);
+#endif
+                if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) {
+                        DWC_PRINTF("Called usb_hcd_giveback_urb() \n");
+                        DWC_PRINTF("  1urb->status = %d\n", urb->status);
+                }
+                DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD URB Dequeue OK\n");
+        } else {
+		DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, flags);
+                DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD URB Dequeue failed - rc %d\n",
+                            rc);
+        }
+
+	return rc;
+}
+
+/* Frees resources in the DWC_otg controller related to a given endpoint. Also
+ * clears state in the HCD related to the endpoint. Any URBs for the endpoint
+ * must already be dequeued. */
+static void endpoint_disable(struct usb_hcd *hcd, struct usb_host_endpoint *ep)
+{
+	dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
+
+	DWC_DEBUGPL(DBG_HCD,
+		    "DWC OTG HCD EP DISABLE: _bEndpointAddress=0x%02x, "
+		    "endpoint=%d\n", ep->desc.bEndpointAddress,
+		    dwc_ep_addr_to_endpoint(ep->desc.bEndpointAddress));
+	dwc_otg_hcd_endpoint_disable(dwc_otg_hcd, ep->hcpriv, 250);
+	ep->hcpriv = NULL;
+}
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30)
+/* Resets endpoint specific parameter values, in current version used to reset
+ * the data toggle(as a WA). This function can be called from usb_clear_halt routine */
+static void endpoint_reset(struct usb_hcd *hcd, struct usb_host_endpoint *ep)
+{
+	dwc_irqflags_t flags;
+	dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
+
+	DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD EP RESET: Endpoint Num=0x%02d\n",
+		    ep->desc.bEndpointAddress);
+
+	DWC_SPINLOCK_IRQSAVE(dwc_otg_hcd->lock, &flags);
+	if (ep->hcpriv) {
+		dwc_otg_hcd_endpoint_reset(dwc_otg_hcd, ep->hcpriv);
+	}
+	DWC_SPINUNLOCK_IRQRESTORE(dwc_otg_hcd->lock, flags);
+}
+#endif
+
+/** Handles host mode interrupts for the DWC_otg controller. Returns IRQ_NONE if
+ * there was no interrupt to handle. Returns IRQ_HANDLED if there was a valid
+ * interrupt.
+ *
+ * This function is called by the USB core when an interrupt occurs */
+static irqreturn_t dwc_otg_hcd_irq(struct usb_hcd *hcd)
+{
+	dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
+	int32_t retval = dwc_otg_hcd_handle_intr(dwc_otg_hcd);
+	if (retval != 0) {
+		S3C2410X_CLEAR_EINTPEND();
+	}
+	return IRQ_RETVAL(retval);
+}
+
+/** Creates Status Change bitmap for the root hub and root port. The bitmap is
+ * returned in buf. Bit 0 is the status change indicator for the root hub. Bit 1
+ * is the status change indicator for the single root port. Returns 1 if either
+ * change indicator is 1, otherwise returns 0. */
+int hub_status_data(struct usb_hcd *hcd, char *buf)
+{
+	dwc_otg_hcd_t *dwc_otg_hcd = hcd_to_dwc_otg_hcd(hcd);
+
+	buf[0] = 0;
+	buf[0] |= (dwc_otg_hcd_is_status_changed(dwc_otg_hcd, 1)) << 1;
+
+	return (buf[0] != 0);
+}
+
+/** Handles hub class-specific requests. */
+int hub_control(struct usb_hcd *hcd,
+		u16 typeReq, u16 wValue, u16 wIndex, char *buf, u16 wLength)
+{
+	int retval;
+
+	retval = dwc_otg_hcd_hub_control(hcd_to_dwc_otg_hcd(hcd),
+					 typeReq, wValue, wIndex, buf, wLength);
+
+	switch (retval) {
+	case -DWC_E_INVALID:
+		retval = -EINVAL;
+		break;
+	}
+
+	return retval;
+}
+
+#endif /* DWC_DEVICE_ONLY */
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd_queue.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd_queue.c
new file mode 100644
index 000000000000..d8a49a8568f2
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_queue.c
@@ -0,0 +1,974 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_hcd_queue.c $
+ * $Revision: #44 $
+ * $Date: 2011/10/26 $
+ * $Change: 1873028 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+#ifndef DWC_DEVICE_ONLY
+
+/**
+ * @file
+ *
+ * This file contains the functions to manage Queue Heads and Queue
+ * Transfer Descriptors.
+ */
+
+#include "dwc_otg_hcd.h"
+#include "dwc_otg_regs.h"
+
+extern bool microframe_schedule;
+extern unsigned short int_ep_interval_min;
+
+/**
+ * Free each QTD in the QH's QTD-list then free the QH.  QH should already be
+ * removed from a list.  QTD list should already be empty if called from URB
+ * Dequeue.
+ *
+ * @param hcd HCD instance.
+ * @param qh The QH to free.
+ */
+void dwc_otg_hcd_qh_free(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh)
+{
+	dwc_otg_qtd_t *qtd, *qtd_tmp;
+	dwc_irqflags_t flags;
+	uint32_t buf_size = 0;
+	uint8_t *align_buf_virt = NULL;
+	dwc_dma_t align_buf_dma;
+	struct device *dev = dwc_otg_hcd_to_dev(hcd);
+
+	/* Free each QTD in the QTD list */
+	DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags);
+	DWC_CIRCLEQ_FOREACH_SAFE(qtd, qtd_tmp, &qh->qtd_list, qtd_list_entry) {
+		DWC_CIRCLEQ_REMOVE(&qh->qtd_list, qtd, qtd_list_entry);
+		dwc_otg_hcd_qtd_free(qtd);
+	}
+
+	if (hcd->core_if->dma_desc_enable) {
+		dwc_otg_hcd_qh_free_ddma(hcd, qh);
+	} else if (qh->dw_align_buf) {
+		if (qh->ep_type == UE_ISOCHRONOUS) {
+			buf_size = 4096;
+		} else {
+			buf_size = hcd->core_if->core_params->max_transfer_size;
+		}
+		align_buf_virt = qh->dw_align_buf;
+		align_buf_dma = qh->dw_align_buf_dma;
+	}
+
+	DWC_FREE(qh);
+	DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags);
+	if (align_buf_virt)
+		DWC_DMA_FREE(dev, buf_size, align_buf_virt, align_buf_dma);
+	return;
+}
+
+#define BitStuffTime(bytecount)  ((8 * 7* bytecount) / 6)
+#define HS_HOST_DELAY		5	/* nanoseconds */
+#define FS_LS_HOST_DELAY	1000	/* nanoseconds */
+#define HUB_LS_SETUP		333	/* nanoseconds */
+#define NS_TO_US(ns)		((ns + 500) / 1000)
+				/* convert & round nanoseconds to microseconds */
+
+static uint32_t calc_bus_time(int speed, int is_in, int is_isoc, int bytecount)
+{
+	unsigned long retval;
+
+	switch (speed) {
+	case USB_SPEED_HIGH:
+		if (is_isoc) {
+			retval =
+			    ((38 * 8 * 2083) +
+			     (2083 * (3 + BitStuffTime(bytecount)))) / 1000 +
+			    HS_HOST_DELAY;
+		} else {
+			retval =
+			    ((55 * 8 * 2083) +
+			     (2083 * (3 + BitStuffTime(bytecount)))) / 1000 +
+			    HS_HOST_DELAY;
+		}
+		break;
+	case USB_SPEED_FULL:
+		if (is_isoc) {
+			retval =
+			    (8354 * (31 + 10 * BitStuffTime(bytecount))) / 1000;
+			if (is_in) {
+				retval = 7268 + FS_LS_HOST_DELAY + retval;
+			} else {
+				retval = 6265 + FS_LS_HOST_DELAY + retval;
+			}
+		} else {
+			retval =
+			    (8354 * (31 + 10 * BitStuffTime(bytecount))) / 1000;
+			retval = 9107 + FS_LS_HOST_DELAY + retval;
+		}
+		break;
+	case USB_SPEED_LOW:
+		if (is_in) {
+			retval =
+			    (67667 * (31 + 10 * BitStuffTime(bytecount))) /
+			    1000;
+			retval =
+			    64060 + (2 * HUB_LS_SETUP) + FS_LS_HOST_DELAY +
+			    retval;
+		} else {
+			retval =
+			    (66700 * (31 + 10 * BitStuffTime(bytecount))) /
+			    1000;
+			retval =
+			    64107 + (2 * HUB_LS_SETUP) + FS_LS_HOST_DELAY +
+			    retval;
+		}
+		break;
+	default:
+		DWC_WARN("Unknown device speed\n");
+		retval = -1;
+	}
+
+	return NS_TO_US(retval);
+}
+
+/**
+ * Initializes a QH structure.
+ *
+ * @param hcd The HCD state structure for the DWC OTG controller.
+ * @param qh  The QH to init.
+ * @param urb Holds the information about the device/endpoint that we need
+ * 	      to initialize the QH.
+ */
+#define SCHEDULE_SLOP 10
+static void qh_init(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh, dwc_otg_hcd_urb_t * urb)
+{
+	char *speed, *type;
+	int dev_speed;
+	uint32_t hub_addr, hub_port;
+	hprt0_data_t hprt;
+
+	dwc_memset(qh, 0, sizeof(dwc_otg_qh_t));
+	hprt.d32 = DWC_READ_REG32(hcd->core_if->host_if->hprt0);
+
+	/* Initialize QH */
+	qh->ep_type = dwc_otg_hcd_get_pipe_type(&urb->pipe_info);
+	qh->ep_is_in = dwc_otg_hcd_is_pipe_in(&urb->pipe_info) ? 1 : 0;
+
+	qh->data_toggle = DWC_OTG_HC_PID_DATA0;
+	qh->maxp = dwc_otg_hcd_get_mps(&urb->pipe_info);
+	DWC_CIRCLEQ_INIT(&qh->qtd_list);
+	DWC_LIST_INIT(&qh->qh_list_entry);
+	qh->channel = NULL;
+
+	/* FS/LS Enpoint on HS Hub
+	 * NOT virtual root hub */
+	dev_speed = hcd->fops->speed(hcd, urb->priv);
+
+	hcd->fops->hub_info(hcd, urb->priv, &hub_addr, &hub_port);
+	qh->do_split = 0;
+	if (microframe_schedule)
+		qh->speed = dev_speed;
+
+	qh->nak_frame = 0xffff;
+
+	if (hprt.b.prtspd == DWC_HPRT0_PRTSPD_HIGH_SPEED &&
+			dev_speed != USB_SPEED_HIGH) {
+		DWC_DEBUGPL(DBG_HCD,
+			    "QH init: EP %d: TT found at hub addr %d, for port %d\n",
+			    dwc_otg_hcd_get_ep_num(&urb->pipe_info), hub_addr,
+			    hub_port);
+		qh->do_split = 1;
+		qh->skip_count = 0;
+	}
+
+	if (qh->ep_type == UE_INTERRUPT || qh->ep_type == UE_ISOCHRONOUS) {
+		/* Compute scheduling parameters once and save them. */
+
+		/** @todo Account for split transfers in the bus time. */
+		int bytecount =
+		    dwc_hb_mult(qh->maxp) * dwc_max_packet(qh->maxp);
+
+		qh->usecs =
+		    calc_bus_time((qh->do_split ? USB_SPEED_HIGH : dev_speed),
+				  qh->ep_is_in, (qh->ep_type == UE_ISOCHRONOUS),
+				  bytecount);
+		/* Start in a slightly future (micro)frame. */
+		qh->sched_frame = dwc_frame_num_inc(hcd->frame_number,
+						    SCHEDULE_SLOP);
+		qh->interval = urb->interval;
+
+		if (hprt.b.prtspd == DWC_HPRT0_PRTSPD_HIGH_SPEED) {
+			if (dev_speed == USB_SPEED_LOW ||
+					dev_speed == USB_SPEED_FULL) {
+				qh->interval *= 8;
+				qh->sched_frame |= 0x7;
+				qh->start_split_frame = qh->sched_frame;
+			} else if (int_ep_interval_min >= 2 &&
+					qh->interval < int_ep_interval_min &&
+					qh->ep_type == UE_INTERRUPT) {
+				qh->interval = int_ep_interval_min;
+			}
+		}
+	}
+
+	DWC_DEBUGPL(DBG_HCD, "DWC OTG HCD QH Initialized\n");
+	DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH  - qh = %p\n", qh);
+	DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH  - Device Address = %d\n",
+		    dwc_otg_hcd_get_dev_addr(&urb->pipe_info));
+	DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH  - Endpoint %d, %s\n",
+		    dwc_otg_hcd_get_ep_num(&urb->pipe_info),
+		    dwc_otg_hcd_is_pipe_in(&urb->pipe_info) ? "IN" : "OUT");
+	switch (dev_speed) {
+	case USB_SPEED_LOW:
+		qh->dev_speed = DWC_OTG_EP_SPEED_LOW;
+		speed = "low";
+		break;
+	case USB_SPEED_FULL:
+		qh->dev_speed = DWC_OTG_EP_SPEED_FULL;
+		speed = "full";
+		break;
+	case USB_SPEED_HIGH:
+		qh->dev_speed = DWC_OTG_EP_SPEED_HIGH;
+		speed = "high";
+		break;
+	default:
+		speed = "?";
+		break;
+	}
+	DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH  - Speed = %s\n", speed);
+
+	switch (qh->ep_type) {
+	case UE_ISOCHRONOUS:
+		type = "isochronous";
+		break;
+	case UE_INTERRUPT:
+		type = "interrupt";
+		break;
+	case UE_CONTROL:
+		type = "control";
+		break;
+	case UE_BULK:
+		type = "bulk";
+		break;
+	default:
+		type = "?";
+		break;
+	}
+
+	DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH  - Type = %s\n", type);
+
+#ifdef DEBUG
+	if (qh->ep_type == UE_INTERRUPT) {
+		DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - usecs = %d\n",
+			    qh->usecs);
+		DWC_DEBUGPL(DBG_HCDV, "DWC OTG HCD QH - interval = %d\n",
+			    qh->interval);
+	}
+#endif
+
+}
+
+/**
+ * This function allocates and initializes a QH.
+ *
+ * @param hcd The HCD state structure for the DWC OTG controller.
+ * @param urb Holds the information about the device/endpoint that we need
+ * 	      to initialize the QH.
+ * @param atomic_alloc Flag to do atomic allocation if needed
+ *
+ * @return Returns pointer to the newly allocated QH, or NULL on error. */
+dwc_otg_qh_t *dwc_otg_hcd_qh_create(dwc_otg_hcd_t * hcd,
+				    dwc_otg_hcd_urb_t * urb, int atomic_alloc)
+{
+	dwc_otg_qh_t *qh;
+
+	/* Allocate memory */
+	/** @todo add memflags argument */
+	qh = dwc_otg_hcd_qh_alloc(atomic_alloc);
+	if (qh == NULL) {
+		DWC_ERROR("qh allocation failed");
+		return NULL;
+	}
+
+	qh_init(hcd, qh, urb);
+
+	if (hcd->core_if->dma_desc_enable
+	    && (dwc_otg_hcd_qh_init_ddma(hcd, qh) < 0)) {
+		dwc_otg_hcd_qh_free(hcd, qh);
+		return NULL;
+	}
+
+	return qh;
+}
+
+/* microframe_schedule=0 start */
+
+/**
+ * Checks that a channel is available for a periodic transfer.
+ *
+ * @return 0 if successful, negative error code otherise.
+ */
+static int periodic_channel_available(dwc_otg_hcd_t * hcd)
+{
+	/*
+	 * Currently assuming that there is a dedicated host channnel for each
+	 * periodic transaction plus at least one host channel for
+	 * non-periodic transactions.
+	 */
+	int status;
+	int num_channels;
+
+	num_channels = hcd->core_if->core_params->host_channels;
+	if ((hcd->periodic_channels + hcd->non_periodic_channels < num_channels)
+	    && (hcd->periodic_channels < num_channels - 1)) {
+		status = 0;
+	} else {
+		DWC_INFO("%s: Total channels: %d, Periodic: %d, Non-periodic: %d\n",
+			__func__, num_channels, hcd->periodic_channels, hcd->non_periodic_channels);	//NOTICE
+		status = -DWC_E_NO_SPACE;
+	}
+
+	return status;
+}
+
+/**
+ * Checks that there is sufficient bandwidth for the specified QH in the
+ * periodic schedule. For simplicity, this calculation assumes that all the
+ * transfers in the periodic schedule may occur in the same (micro)frame.
+ *
+ * @param hcd The HCD state structure for the DWC OTG controller.
+ * @param qh QH containing periodic bandwidth required.
+ *
+ * @return 0 if successful, negative error code otherwise.
+ */
+static int check_periodic_bandwidth(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh)
+{
+	int status;
+	int16_t max_claimed_usecs;
+
+	status = 0;
+
+	if ((qh->dev_speed == DWC_OTG_EP_SPEED_HIGH) || qh->do_split) {
+		/*
+		 * High speed mode.
+		 * Max periodic usecs is 80% x 125 usec = 100 usec.
+		 */
+
+		max_claimed_usecs = 100 - qh->usecs;
+	} else {
+		/*
+		 * Full speed mode.
+		 * Max periodic usecs is 90% x 1000 usec = 900 usec.
+		 */
+		max_claimed_usecs = 900 - qh->usecs;
+	}
+
+	if (hcd->periodic_usecs > max_claimed_usecs) {
+		DWC_INFO("%s: already claimed usecs %d, required usecs %d\n", __func__, hcd->periodic_usecs, qh->usecs);	//NOTICE
+		status = -DWC_E_NO_SPACE;
+	}
+
+	return status;
+}
+
+/* microframe_schedule=0 end */
+
+/**
+ * Microframe scheduler
+ * track the total use in hcd->frame_usecs
+ * keep each qh use in qh->frame_usecs
+ * when surrendering the qh then donate the time back
+ */
+const unsigned short max_uframe_usecs[]={ 100, 100, 100, 100, 100, 100, 30, 0 };
+
+/*
+ * called from dwc_otg_hcd.c:dwc_otg_hcd_init
+ */
+void init_hcd_usecs(dwc_otg_hcd_t *_hcd)
+{
+	int i;
+	if (_hcd->flags.b.port_speed == DWC_HPRT0_PRTSPD_FULL_SPEED) {
+		_hcd->frame_usecs[0] = 900;
+		for (i = 1; i < 8; i++)
+			_hcd->frame_usecs[i] = 0;
+	} else {
+		for (i = 0; i < 8; i++)
+			_hcd->frame_usecs[i] = max_uframe_usecs[i];
+	}
+}
+
+static int find_single_uframe(dwc_otg_hcd_t * _hcd, dwc_otg_qh_t * _qh)
+{
+	int i;
+	unsigned short utime;
+	int t_left;
+	int ret;
+	int done;
+
+	ret = -1;
+	utime = _qh->usecs;
+	t_left = utime;
+	i = 0;
+	done = 0;
+	while (done == 0) {
+		/* At the start _hcd->frame_usecs[i] = max_uframe_usecs[i]; */
+		if (utime <= _hcd->frame_usecs[i]) {
+			_hcd->frame_usecs[i] -= utime;
+			_qh->frame_usecs[i] += utime;
+			t_left -= utime;
+			ret = i;
+			done = 1;
+			return ret;
+		} else {
+			i++;
+			if (i == 8) {
+				done = 1;
+				ret = -1;
+			}
+		}
+	}
+	return ret;
+ }
+
+/*
+ * use this for FS apps that can span multiple uframes
+  */
+static int find_multi_uframe(dwc_otg_hcd_t * _hcd, dwc_otg_qh_t * _qh)
+{
+	int i;
+	int j;
+	unsigned short utime;
+	int t_left;
+	int ret;
+	int done;
+	unsigned short xtime;
+
+	ret = -1;
+	utime = _qh->usecs;
+	t_left = utime;
+	i = 0;
+	done = 0;
+loop:
+	while (done == 0) {
+		if(_hcd->frame_usecs[i] <= 0) {
+			i++;
+			if (i == 8) {
+				done = 1;
+				ret = -1;
+			}
+			goto loop;
+		}
+
+		/*
+		 * we need n consecutive slots
+		 * so use j as a start slot j plus j+1 must be enough time (for now)
+		 */
+		xtime= _hcd->frame_usecs[i];
+		for (j = i+1 ; j < 8 ; j++ ) {
+                       /*
+                        * if we add this frame remaining time to xtime we may
+                        * be OK, if not we need to test j for a complete frame
+                        */
+                       if ((xtime+_hcd->frame_usecs[j]) < utime) {
+                               if (_hcd->frame_usecs[j] < max_uframe_usecs[j]) {
+                                       j = 8;
+                                       ret = -1;
+                                       continue;
+                               }
+                       }
+                       if (xtime >= utime) {
+                               ret = i;
+                               j = 8;  /* stop loop with a good value ret */
+                               continue;
+                       }
+                       /* add the frame time to x time */
+                       xtime += _hcd->frame_usecs[j];
+		       /* we must have a fully available next frame or break */
+		       if ((xtime < utime)
+				       && (_hcd->frame_usecs[j] == max_uframe_usecs[j])) {
+			       ret = -1;
+			       j = 8;  /* stop loop with a bad value ret */
+			       continue;
+		       }
+		}
+		if (ret >= 0) {
+			t_left = utime;
+			for (j = i; (t_left>0) && (j < 8); j++ ) {
+				t_left -= _hcd->frame_usecs[j];
+				if ( t_left <= 0 ) {
+					_qh->frame_usecs[j] += _hcd->frame_usecs[j] + t_left;
+					_hcd->frame_usecs[j]= -t_left;
+					ret = i;
+					done = 1;
+				} else {
+					_qh->frame_usecs[j] += _hcd->frame_usecs[j];
+					_hcd->frame_usecs[j] = 0;
+				}
+			}
+		} else {
+			i++;
+			if (i == 8) {
+				done = 1;
+				ret = -1;
+			}
+		}
+	}
+	return ret;
+}
+
+static int find_uframe(dwc_otg_hcd_t * _hcd, dwc_otg_qh_t * _qh)
+{
+	int ret;
+	ret = -1;
+
+	if (_qh->speed == USB_SPEED_HIGH ||
+		_hcd->flags.b.port_speed == DWC_HPRT0_PRTSPD_FULL_SPEED) {
+		/* if this is a hs transaction we need a full frame - or account for FS usecs */
+		ret = find_single_uframe(_hcd, _qh);
+	} else {
+		/* if this is a fs transaction we may need a sequence of frames */
+		ret = find_multi_uframe(_hcd, _qh);
+	}
+	return ret;
+}
+
+/**
+ * Checks that the max transfer size allowed in a host channel is large enough
+ * to handle the maximum data transfer in a single (micro)frame for a periodic
+ * transfer.
+ *
+ * @param hcd The HCD state structure for the DWC OTG controller.
+ * @param qh QH for a periodic endpoint.
+ *
+ * @return 0 if successful, negative error code otherwise.
+ */
+static int check_max_xfer_size(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh)
+{
+	int status;
+	uint32_t max_xfer_size;
+	uint32_t max_channel_xfer_size;
+
+	status = 0;
+
+	max_xfer_size = dwc_max_packet(qh->maxp) * dwc_hb_mult(qh->maxp);
+	max_channel_xfer_size = hcd->core_if->core_params->max_transfer_size;
+
+	if (max_xfer_size > max_channel_xfer_size) {
+		DWC_INFO("%s: Periodic xfer length %d > " "max xfer length for channel %d\n",
+				__func__, max_xfer_size, max_channel_xfer_size);	//NOTICE
+		status = -DWC_E_NO_SPACE;
+	}
+
+	return status;
+}
+
+
+
+/**
+ * Schedules an interrupt or isochronous transfer in the periodic schedule.
+ *
+ * @param hcd The HCD state structure for the DWC OTG controller.
+ * @param qh QH for the periodic transfer. The QH should already contain the
+ * scheduling information.
+ *
+ * @return 0 if successful, negative error code otherwise.
+ */
+static int schedule_periodic(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh)
+{
+	int status = 0;
+
+	if (microframe_schedule) {
+		int frame;
+		status = find_uframe(hcd, qh);
+		frame = -1;
+		if (status == 0) {
+			frame = 7;
+		} else {
+			if (status > 0 )
+				frame = status-1;
+		}
+
+		/* Set the new frame up */
+		if (frame > -1) {
+			qh->sched_frame &= ~0x7;
+			qh->sched_frame |= (frame & 7);
+		}
+
+		if (status != -1)
+			status = 0;
+	} else {
+		status = periodic_channel_available(hcd);
+		if (status) {
+			DWC_INFO("%s: No host channel available for periodic " "transfer.\n", __func__);	//NOTICE
+			return status;
+		}
+
+		status = check_periodic_bandwidth(hcd, qh);
+	}
+	if (status) {
+		DWC_INFO("%s: Insufficient periodic bandwidth for "
+			    "periodic transfer.\n", __func__);
+		return -DWC_E_NO_SPACE;
+	}
+	status = check_max_xfer_size(hcd, qh);
+	if (status) {
+		DWC_INFO("%s: Channel max transfer size too small "
+			    "for periodic transfer.\n", __func__);
+		return status;
+	}
+
+	if (hcd->core_if->dma_desc_enable) {
+		/* Don't rely on SOF and start in ready schedule */
+		DWC_LIST_INSERT_TAIL(&hcd->periodic_sched_ready, &qh->qh_list_entry);
+	}
+	else {
+		if(fiq_enable && (DWC_LIST_EMPTY(&hcd->periodic_sched_inactive) || dwc_frame_num_le(qh->sched_frame, hcd->fiq_state->next_sched_frame)))
+		{
+			hcd->fiq_state->next_sched_frame = qh->sched_frame;
+
+		}
+		/* Always start in the inactive schedule. */
+		DWC_LIST_INSERT_TAIL(&hcd->periodic_sched_inactive, &qh->qh_list_entry);
+	}
+
+	if (!microframe_schedule) {
+		/* Reserve the periodic channel. */
+		hcd->periodic_channels++;
+	}
+
+	/* Update claimed usecs per (micro)frame. */
+	hcd->periodic_usecs += qh->usecs;
+
+	return status;
+}
+
+
+/**
+ * This function adds a QH to either the non periodic or periodic schedule if
+ * it is not already in the schedule. If the QH is already in the schedule, no
+ * action is taken.
+ *
+ * @return 0 if successful, negative error code otherwise.
+ */
+int dwc_otg_hcd_qh_add(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh)
+{
+	int status = 0;
+	gintmsk_data_t intr_mask = {.d32 = 0 };
+
+	if (!DWC_LIST_EMPTY(&qh->qh_list_entry)) {
+		/* QH already in a schedule. */
+		return status;
+	}
+
+	/* Add the new QH to the appropriate schedule */
+	if (dwc_qh_is_non_per(qh)) {
+		/* Always start in the inactive schedule. */
+		DWC_LIST_INSERT_TAIL(&hcd->non_periodic_sched_inactive,
+				     &qh->qh_list_entry);
+		//hcd->fiq_state->kick_np_queues = 1;
+	} else {
+		/* If the QH wasn't in a schedule, then sched_frame is stale. */
+		qh->sched_frame = dwc_frame_num_inc(dwc_otg_hcd_get_frame_number(hcd),
+						    max_t(uint32_t, qh->interval, SCHEDULE_SLOP));
+		status = schedule_periodic(hcd, qh);
+		qh->start_split_frame = qh->sched_frame;
+		if ( !hcd->periodic_qh_count ) {
+			intr_mask.b.sofintr = 1;
+			if (fiq_enable) {
+				local_fiq_disable();
+				fiq_fsm_spin_lock(&hcd->fiq_state->lock);
+				DWC_MODIFY_REG32(&hcd->core_if->core_global_regs->gintmsk, intr_mask.d32, intr_mask.d32);
+				fiq_fsm_spin_unlock(&hcd->fiq_state->lock);
+				local_fiq_enable();
+			} else {
+				DWC_MODIFY_REG32(&hcd->core_if->core_global_regs->gintmsk, intr_mask.d32, intr_mask.d32);
+			}
+		}
+		hcd->periodic_qh_count++;
+	}
+
+	return status;
+}
+
+/**
+ * Removes an interrupt or isochronous transfer from the periodic schedule.
+ *
+ * @param hcd The HCD state structure for the DWC OTG controller.
+ * @param qh QH for the periodic transfer.
+ */
+static void deschedule_periodic(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh)
+{
+	int i;
+	DWC_LIST_REMOVE_INIT(&qh->qh_list_entry);
+
+	/* Update claimed usecs per (micro)frame. */
+	hcd->periodic_usecs -= qh->usecs;
+
+	if (!microframe_schedule) {
+		/* Release the periodic channel reservation. */
+		hcd->periodic_channels--;
+	} else {
+		for (i = 0; i < 8; i++) {
+			hcd->frame_usecs[i] += qh->frame_usecs[i];
+			qh->frame_usecs[i] = 0;
+		}
+	}
+}
+
+/**
+ * Removes a QH from either the non-periodic or periodic schedule.  Memory is
+ * not freed.
+ *
+ * @param hcd The HCD state structure.
+ * @param qh QH to remove from schedule. */
+void dwc_otg_hcd_qh_remove(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh)
+{
+	gintmsk_data_t intr_mask = {.d32 = 0 };
+
+	if (DWC_LIST_EMPTY(&qh->qh_list_entry)) {
+		/* QH is not in a schedule. */
+		return;
+	}
+
+	if (dwc_qh_is_non_per(qh)) {
+		if (hcd->non_periodic_qh_ptr == &qh->qh_list_entry) {
+			hcd->non_periodic_qh_ptr =
+			    hcd->non_periodic_qh_ptr->next;
+		}
+		DWC_LIST_REMOVE_INIT(&qh->qh_list_entry);
+		//if (!DWC_LIST_EMPTY(&hcd->non_periodic_sched_inactive))
+		//	hcd->fiq_state->kick_np_queues = 1;
+	} else {
+		deschedule_periodic(hcd, qh);
+		hcd->periodic_qh_count--;
+		if( !hcd->periodic_qh_count && !fiq_fsm_enable ) {
+			intr_mask.b.sofintr = 1;
+			if (fiq_enable) {
+				local_fiq_disable();
+				fiq_fsm_spin_lock(&hcd->fiq_state->lock);
+				DWC_MODIFY_REG32(&hcd->core_if->core_global_regs->gintmsk, intr_mask.d32, 0);
+				fiq_fsm_spin_unlock(&hcd->fiq_state->lock);
+				local_fiq_enable();
+			} else {
+				DWC_MODIFY_REG32(&hcd->core_if->core_global_regs->gintmsk, intr_mask.d32, 0);
+			}
+		}
+	}
+}
+
+/**
+ * Deactivates a QH. For non-periodic QHs, removes the QH from the active
+ * non-periodic schedule. The QH is added to the inactive non-periodic
+ * schedule if any QTDs are still attached to the QH.
+ *
+ * For periodic QHs, the QH is removed from the periodic queued schedule. If
+ * there are any QTDs still attached to the QH, the QH is added to either the
+ * periodic inactive schedule or the periodic ready schedule and its next
+ * scheduled frame is calculated. The QH is placed in the ready schedule if
+ * the scheduled frame has been reached already. Otherwise it's placed in the
+ * inactive schedule. If there are no QTDs attached to the QH, the QH is
+ * completely removed from the periodic schedule.
+ */
+void dwc_otg_hcd_qh_deactivate(dwc_otg_hcd_t * hcd, dwc_otg_qh_t * qh,
+			       int sched_next_periodic_split)
+{
+	if (dwc_qh_is_non_per(qh)) {
+		dwc_otg_hcd_qh_remove(hcd, qh);
+		if (!DWC_CIRCLEQ_EMPTY(&qh->qtd_list)) {
+			/* Add back to inactive non-periodic schedule. */
+			dwc_otg_hcd_qh_add(hcd, qh);
+			//hcd->fiq_state->kick_np_queues = 1;
+		} else {
+			if(nak_holdoff && qh->do_split) {
+				qh->nak_frame = 0xFFFF;
+			}
+		}
+	} else {
+		uint16_t frame_number = dwc_otg_hcd_get_frame_number(hcd);
+
+		if (qh->do_split) {
+			/* Schedule the next continuing periodic split transfer */
+			if (sched_next_periodic_split) {
+
+				qh->sched_frame = frame_number;
+
+				if (dwc_frame_num_le(frame_number,
+						     dwc_frame_num_inc
+						     (qh->start_split_frame,
+						      1))) {
+					/*
+					 * Allow one frame to elapse after start
+					 * split microframe before scheduling
+					 * complete split, but DONT if we are
+					 * doing the next start split in the
+					 * same frame for an ISOC out.
+					 */
+					if ((qh->ep_type != UE_ISOCHRONOUS) ||
+					    (qh->ep_is_in != 0)) {
+						qh->sched_frame =
+						    dwc_frame_num_inc(qh->sched_frame, 1);
+					}
+				}
+			} else {
+				qh->sched_frame =
+				    dwc_frame_num_inc(qh->start_split_frame,
+						      qh->interval);
+				if (dwc_frame_num_le
+				    (qh->sched_frame, frame_number)) {
+					qh->sched_frame = frame_number;
+				}
+				qh->sched_frame |= 0x7;
+				qh->start_split_frame = qh->sched_frame;
+			}
+		} else {
+			qh->sched_frame =
+			    dwc_frame_num_inc(qh->sched_frame, qh->interval);
+			if (dwc_frame_num_le(qh->sched_frame, frame_number)) {
+				qh->sched_frame = frame_number;
+			}
+		}
+
+		if (DWC_CIRCLEQ_EMPTY(&qh->qtd_list)) {
+			dwc_otg_hcd_qh_remove(hcd, qh);
+		} else {
+			/*
+			 * Remove from periodic_sched_queued and move to
+			 * appropriate queue.
+			 */
+			if ((microframe_schedule && dwc_frame_num_le(qh->sched_frame, frame_number)) ||
+			(!microframe_schedule && qh->sched_frame == frame_number)) {
+				DWC_LIST_MOVE_HEAD(&hcd->periodic_sched_ready,
+						   &qh->qh_list_entry);
+			} else {
+				if(fiq_enable && !dwc_frame_num_le(hcd->fiq_state->next_sched_frame, qh->sched_frame))
+				{
+					hcd->fiq_state->next_sched_frame = qh->sched_frame;
+				}
+
+				DWC_LIST_MOVE_HEAD
+				    (&hcd->periodic_sched_inactive,
+				     &qh->qh_list_entry);
+			}
+		}
+	}
+}
+
+/**
+ * This function allocates and initializes a QTD.
+ *
+ * @param urb The URB to create a QTD from.  Each URB-QTD pair will end up
+ * 	      pointing to each other so each pair should have a unique correlation.
+ * @param atomic_alloc Flag to do atomic alloc if needed
+ *
+ * @return Returns pointer to the newly allocated QTD, or NULL on error. */
+dwc_otg_qtd_t *dwc_otg_hcd_qtd_create(dwc_otg_hcd_urb_t * urb, int atomic_alloc)
+{
+	dwc_otg_qtd_t *qtd;
+
+	qtd = dwc_otg_hcd_qtd_alloc(atomic_alloc);
+	if (qtd == NULL) {
+		return NULL;
+	}
+
+	dwc_otg_hcd_qtd_init(qtd, urb);
+	return qtd;
+}
+
+/**
+ * Initializes a QTD structure.
+ *
+ * @param qtd The QTD to initialize.
+ * @param urb The URB to use for initialization.  */
+void dwc_otg_hcd_qtd_init(dwc_otg_qtd_t * qtd, dwc_otg_hcd_urb_t * urb)
+{
+	dwc_memset(qtd, 0, sizeof(dwc_otg_qtd_t));
+	qtd->urb = urb;
+	if (dwc_otg_hcd_get_pipe_type(&urb->pipe_info) == UE_CONTROL) {
+		/*
+		 * The only time the QTD data toggle is used is on the data
+		 * phase of control transfers. This phase always starts with
+		 * DATA1.
+		 */
+		qtd->data_toggle = DWC_OTG_HC_PID_DATA1;
+		qtd->control_phase = DWC_OTG_CONTROL_SETUP;
+	}
+
+	/* start split */
+	qtd->complete_split = 0;
+	qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_ALL;
+	qtd->isoc_split_offset = 0;
+	qtd->in_process = 0;
+
+	/* Store the qtd ptr in the urb to reference what QTD. */
+	urb->qtd = qtd;
+	return;
+}
+
+/**
+ * This function adds a QTD to the QTD-list of a QH.  It will find the correct
+ * QH to place the QTD into.  If it does not find a QH, then it will create a
+ * new QH. If the QH to which the QTD is added is not currently scheduled, it
+ * is placed into the proper schedule based on its EP type.
+ * HCD lock must be held and interrupts must be disabled on entry
+ *
+ * @param[in] qtd The QTD to add
+ * @param[in] hcd The DWC HCD structure
+ * @param[out] qh out parameter to return queue head
+ * @param atomic_alloc Flag to do atomic alloc if needed
+ *
+ * @return 0 if successful, negative error code otherwise.
+ */
+int dwc_otg_hcd_qtd_add(dwc_otg_qtd_t * qtd,
+			dwc_otg_hcd_t * hcd, dwc_otg_qh_t ** qh, int atomic_alloc)
+{
+	int retval = 0;
+	dwc_otg_hcd_urb_t *urb = qtd->urb;
+
+	/*
+	 * Get the QH which holds the QTD-list to insert to. Create QH if it
+	 * doesn't exist.
+	 */
+	if (*qh == NULL) {
+		*qh = dwc_otg_hcd_qh_create(hcd, urb, atomic_alloc);
+		if (*qh == NULL) {
+			retval = -DWC_E_NO_MEMORY;
+			goto done;
+		} else {
+			if (fiq_enable)
+				hcd->fiq_state->kick_np_queues = 1;
+		}
+	}
+	retval = dwc_otg_hcd_qh_add(hcd, *qh);
+	if (retval == 0) {
+		DWC_CIRCLEQ_INSERT_TAIL(&((*qh)->qtd_list), qtd,
+					qtd_list_entry);
+		qtd->qh = *qh;
+	}
+done:
+
+	return retval;
+}
+
+#endif /* DWC_DEVICE_ONLY */
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_os_dep.h b/drivers/usb/host/dwc_otg/dwc_otg_os_dep.h
new file mode 100644
index 000000000000..0bfa98157169
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_os_dep.h
@@ -0,0 +1,200 @@
+#ifndef _DWC_OS_DEP_H_
+#define _DWC_OS_DEP_H_
+
+/**
+ * @file
+ *
+ * This file contains OS dependent structures.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/ctype.h>
+#include <linux/string.h>
+#include <linux/dma-mapping.h>
+#include <linux/jiffies.h>
+#include <linux/delay.h>
+#include <linux/timer.h>
+#include <linux/workqueue.h>
+#include <linux/stat.h>
+#include <linux/pci.h>
+#include <linux/compiler.h>
+
+#include <linux/version.h>
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)
+# include <linux/irq.h>
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,21)
+# include <linux/usb/ch9.h>
+#else
+# include <linux/usb_ch9.h>
+#endif
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)
+# include <linux/usb/gadget.h>
+#else
+# include <linux/usb_gadget.h>
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+# include <asm/irq.h>
+#endif
+
+#ifdef PCI_INTERFACE
+# include <asm/io.h>
+#endif
+
+#ifdef LM_INTERFACE
+# include <linux/unaligned.h>
+# include <asm/sizes.h>
+# include <asm/param.h>
+# include <asm/io.h>
+# if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30))
+#  include <asm/arch/hardware.h>
+#  include <asm/arch/lm.h>
+#  include <asm/arch/irqs.h>
+#  include <asm/arch/regs-irq.h>
+# else
+/* in 2.6.31, at least, we seem to have lost the generic LM infrastructure -
+   here we assume that the machine architecture provides definitions
+   in its own header
+*/
+#  include <mach/lm.h>
+#  include <mach/hardware.h>
+# endif
+#endif
+
+#ifdef PLATFORM_INTERFACE
+#include <linux/platform_device.h>
+#ifdef CONFIG_ARM
+#include <asm/mach/map.h>
+#endif
+#endif
+
+/** The OS page size */
+#define DWC_OS_PAGE_SIZE	PAGE_SIZE
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14)
+typedef int gfp_t;
+#endif
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,18)
+# define IRQF_SHARED SA_SHIRQ
+#endif
+
+typedef struct os_dependent {
+	/** Base address returned from ioremap() */
+	void *base;
+
+	/** Register offset for Diagnostic API */
+	uint32_t reg_offset;
+
+	/** Base address for MPHI peripheral */
+	void *mphi_base;
+
+	/** mphi_base actually points to the SWIRQ block */
+	bool use_swirq;
+
+	/** IRQ number (<0 if not valid) */
+	int irq_num;
+
+	/** FIQ number (<0 if not valid) */
+	int fiq_num;
+
+#ifdef LM_INTERFACE
+	struct lm_device *lmdev;
+#elif  defined(PCI_INTERFACE)
+	struct pci_dev *pcidev;
+
+	/** Start address of a PCI region */
+	resource_size_t rsrc_start;
+
+	/** Length address of a PCI region */
+	resource_size_t rsrc_len;
+#elif  defined(PLATFORM_INTERFACE)
+	struct platform_device *platformdev;
+#endif
+
+} os_dependent_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+
+
+/* Type for the our device on the chosen bus */
+#if   defined(LM_INTERFACE)
+typedef struct lm_device       dwc_bus_dev_t;
+#elif defined(PCI_INTERFACE)
+typedef struct pci_dev         dwc_bus_dev_t;
+#elif defined(PLATFORM_INTERFACE)
+typedef struct platform_device dwc_bus_dev_t;
+#endif
+
+/* Helper macro to retrieve drvdata from the device on the chosen bus */
+#if    defined(LM_INTERFACE)
+#define DWC_OTG_BUSDRVDATA(_dev) lm_get_drvdata(_dev)
+#elif  defined(PCI_INTERFACE)
+#define DWC_OTG_BUSDRVDATA(_dev) pci_get_drvdata(_dev)
+#elif  defined(PLATFORM_INTERFACE)
+#define DWC_OTG_BUSDRVDATA(_dev) platform_get_drvdata(_dev)
+#endif
+
+/**
+ * Helper macro returning the otg_device structure of a given struct device
+ *
+ * c.f. static dwc_otg_device_t *dwc_otg_drvdev(struct device *_dev)
+ */
+#ifdef LM_INTERFACE
+#define DWC_OTG_GETDRVDEV(_var, _dev) do { \
+                struct lm_device *lm_dev = \
+                        container_of(_dev, struct lm_device, dev); \
+                _var = lm_get_drvdata(lm_dev); \
+        } while (0)
+
+#elif defined(PCI_INTERFACE)
+#define DWC_OTG_GETDRVDEV(_var, _dev) do { \
+                _var = dev_get_drvdata(_dev); \
+        } while (0)
+
+#elif defined(PLATFORM_INTERFACE)
+#define DWC_OTG_GETDRVDEV(_var, _dev) do { \
+                struct platform_device *platform_dev = \
+                        container_of(_dev, struct platform_device, dev); \
+                _var = platform_get_drvdata(platform_dev); \
+        } while (0)
+#endif
+
+
+/**
+ * Helper macro returning the struct dev of the given struct os_dependent
+ *
+ * c.f. static struct device *dwc_otg_getdev(struct os_dependent *osdep)
+ */
+#ifdef LM_INTERFACE
+#define DWC_OTG_OS_GETDEV(_osdep) \
+        ((_osdep).lmdev == NULL? NULL: &(_osdep).lmdev->dev)
+#elif defined(PCI_INTERFACE)
+#define DWC_OTG_OS_GETDEV(_osdep) \
+        ((_osdep).pci_dev == NULL? NULL: &(_osdep).pci_dev->dev)
+#elif defined(PLATFORM_INTERFACE)
+#define DWC_OTG_OS_GETDEV(_osdep) \
+        ((_osdep).platformdev == NULL? NULL: &(_osdep).platformdev->dev)
+#endif
+
+
+
+
+#endif /* _DWC_OS_DEP_H_ */
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_pcd.c b/drivers/usb/host/dwc_otg/dwc_otg_pcd.c
new file mode 100644
index 000000000000..ec2d45167a0f
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_pcd.c
@@ -0,0 +1,2723 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_pcd.c $
+ * $Revision: #101 $
+ * $Date: 2012/08/10 $
+ * $Change: 2047372 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+#ifndef DWC_HOST_ONLY
+
+/** @file
+ * This file implements PCD Core. All code in this file is portable and doesn't
+ * use any OS specific functions.
+ * PCD Core provides Interface, defined in <code><dwc_otg_pcd_if.h></code>
+ * header file, which can be used to implement OS specific PCD interface.
+ *
+ * An important function of the PCD is managing interrupts generated
+ * by the DWC_otg controller. The implementation of the DWC_otg device
+ * mode interrupt service routines is in dwc_otg_pcd_intr.c.
+ *
+ * @todo Add Device Mode test modes (Test J mode, Test K mode, etc).
+ * @todo Does it work when the request size is greater than DEPTSIZ
+ * transfer size
+ *
+ */
+
+#include "dwc_otg_pcd.h"
+
+#ifdef DWC_UTE_CFI
+#include "dwc_otg_cfi.h"
+
+extern int init_cfi(cfiobject_t * cfiobj);
+#endif
+
+/**
+ * Choose endpoint from ep arrays using usb_ep structure.
+ */
+static dwc_otg_pcd_ep_t *get_ep_from_handle(dwc_otg_pcd_t * pcd, void *handle)
+{
+	int i;
+	if (pcd->ep0.priv == handle) {
+		return &pcd->ep0;
+	}
+	for (i = 0; i < MAX_EPS_CHANNELS - 1; i++) {
+		if (pcd->in_ep[i].priv == handle)
+			return &pcd->in_ep[i];
+		if (pcd->out_ep[i].priv == handle)
+			return &pcd->out_ep[i];
+	}
+
+	return NULL;
+}
+
+/**
+ * This function completes a request.  It call's the request call back.
+ */
+void dwc_otg_request_done(dwc_otg_pcd_ep_t * ep, dwc_otg_pcd_request_t * req,
+			  int32_t status)
+{
+	unsigned stopped = ep->stopped;
+
+	DWC_DEBUGPL(DBG_PCDV, "%s(ep %p req %p)\n", __func__, ep, req);
+	DWC_CIRCLEQ_REMOVE_INIT(&ep->queue, req, queue_entry);
+
+	/* don't modify queue heads during completion callback */
+	ep->stopped = 1;
+	/* spin_unlock/spin_lock now done in fops->complete() */
+	ep->pcd->fops->complete(ep->pcd, ep->priv, req->priv, status,
+				req->actual);
+
+	if (ep->pcd->request_pending > 0) {
+		--ep->pcd->request_pending;
+	}
+
+	ep->stopped = stopped;
+	DWC_FREE(req);
+}
+
+/**
+ * This function terminates all the requsts in the EP request queue.
+ */
+void dwc_otg_request_nuke(dwc_otg_pcd_ep_t * ep)
+{
+	dwc_otg_pcd_request_t *req;
+
+	ep->stopped = 1;
+
+	/* called with irqs blocked?? */
+	while (!DWC_CIRCLEQ_EMPTY(&ep->queue)) {
+		req = DWC_CIRCLEQ_FIRST(&ep->queue);
+		dwc_otg_request_done(ep, req, -DWC_E_SHUTDOWN);
+	}
+}
+
+void dwc_otg_pcd_start(dwc_otg_pcd_t * pcd,
+		       const struct dwc_otg_pcd_function_ops *fops)
+{
+	pcd->fops = fops;
+}
+
+/**
+ * PCD Callback function for initializing the PCD when switching to
+ * device mode.
+ *
+ * @param p void pointer to the <code>dwc_otg_pcd_t</code>
+ */
+static int32_t dwc_otg_pcd_start_cb(void *p)
+{
+	dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *) p;
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+
+	/*
+	 * Initialized the Core for Device mode.
+	 */
+	if (dwc_otg_is_device_mode(core_if)) {
+		dwc_otg_core_dev_init(core_if);
+		/* Set core_if's lock pointer to the pcd->lock */
+		core_if->lock = pcd->lock;
+	}
+	return 1;
+}
+
+/** CFI-specific buffer allocation function for EP */
+#ifdef DWC_UTE_CFI
+uint8_t *cfiw_ep_alloc_buffer(dwc_otg_pcd_t * pcd, void *pep, dwc_dma_t * addr,
+			      size_t buflen, int flags)
+{
+	dwc_otg_pcd_ep_t *ep;
+	ep = get_ep_from_handle(pcd, pep);
+	if (!ep) {
+		DWC_WARN("bad ep\n");
+		return -DWC_E_INVALID;
+	}
+
+	return pcd->cfi->ops.ep_alloc_buf(pcd->cfi, pcd, ep, addr, buflen,
+					  flags);
+}
+#else
+uint8_t *cfiw_ep_alloc_buffer(dwc_otg_pcd_t * pcd, void *pep, dwc_dma_t * addr,
+			      size_t buflen, int flags);
+#endif
+
+/**
+ * PCD Callback function for notifying the PCD when resuming from
+ * suspend.
+ *
+ * @param p void pointer to the <code>dwc_otg_pcd_t</code>
+ */
+static int32_t dwc_otg_pcd_resume_cb(void *p)
+{
+	dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *) p;
+
+	if (pcd->fops->resume) {
+		pcd->fops->resume(pcd);
+	}
+
+	/* Stop the SRP timeout timer. */
+	if ((GET_CORE_IF(pcd)->core_params->phy_type != DWC_PHY_TYPE_PARAM_FS)
+	    || (!GET_CORE_IF(pcd)->core_params->i2c_enable)) {
+		if (GET_CORE_IF(pcd)->srp_timer_started) {
+			GET_CORE_IF(pcd)->srp_timer_started = 0;
+			DWC_TIMER_CANCEL(GET_CORE_IF(pcd)->srp_timer);
+		}
+	}
+	return 1;
+}
+
+/**
+ * PCD Callback function for notifying the PCD device is suspended.
+ *
+ * @param p void pointer to the <code>dwc_otg_pcd_t</code>
+ */
+static int32_t dwc_otg_pcd_suspend_cb(void *p)
+{
+	dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *) p;
+
+	if (pcd->fops->suspend) {
+		DWC_SPINUNLOCK(pcd->lock);
+		pcd->fops->suspend(pcd);
+		DWC_SPINLOCK(pcd->lock);
+	}
+
+	return 1;
+}
+
+/**
+ * PCD Callback function for stopping the PCD when switching to Host
+ * mode.
+ *
+ * @param p void pointer to the <code>dwc_otg_pcd_t</code>
+ */
+static int32_t dwc_otg_pcd_stop_cb(void *p)
+{
+	dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *) p;
+	extern void dwc_otg_pcd_stop(dwc_otg_pcd_t * _pcd);
+
+	dwc_otg_pcd_stop(pcd);
+	return 1;
+}
+
+/**
+ * PCD Callback structure for handling mode switching.
+ */
+static dwc_otg_cil_callbacks_t pcd_callbacks = {
+	.start = dwc_otg_pcd_start_cb,
+	.stop = dwc_otg_pcd_stop_cb,
+	.suspend = dwc_otg_pcd_suspend_cb,
+	.resume_wakeup = dwc_otg_pcd_resume_cb,
+	.p = 0,			/* Set at registration */
+};
+
+/**
+ * This function allocates a DMA Descriptor chain for the Endpoint
+ * buffer to be used for a transfer to/from the specified endpoint.
+ */
+static dwc_otg_dev_dma_desc_t *dwc_otg_ep_alloc_desc_chain(struct device *dev,
+						    dwc_dma_t * dma_desc_addr,
+						    uint32_t count)
+{
+	return DWC_DMA_ALLOC_ATOMIC(dev, count * sizeof(dwc_otg_dev_dma_desc_t),
+							dma_desc_addr);
+}
+
+/**
+ * This function frees a DMA Descriptor chain that was allocated by ep_alloc_desc.
+ */
+static void dwc_otg_ep_free_desc_chain(struct device *dev,
+				dwc_otg_dev_dma_desc_t * desc_addr,
+				uint32_t dma_desc_addr, uint32_t count)
+{
+	DWC_DMA_FREE(dev, count * sizeof(dwc_otg_dev_dma_desc_t), desc_addr,
+		     dma_desc_addr);
+}
+
+#ifdef DWC_EN_ISOC
+
+/**
+ * This function initializes a descriptor chain for Isochronous transfer
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param dwc_ep The EP to start the transfer on.
+ *
+ */
+void dwc_otg_iso_ep_start_ddma_transfer(dwc_otg_core_if_t * core_if,
+					dwc_ep_t * dwc_ep)
+{
+
+	dsts_data_t dsts = {.d32 = 0 };
+	depctl_data_t depctl = {.d32 = 0 };
+	volatile uint32_t *addr;
+	int i, j;
+	uint32_t len;
+
+	if (dwc_ep->is_in)
+		dwc_ep->desc_cnt = dwc_ep->buf_proc_intrvl / dwc_ep->bInterval;
+	else
+		dwc_ep->desc_cnt =
+		    dwc_ep->buf_proc_intrvl * dwc_ep->pkt_per_frm /
+		    dwc_ep->bInterval;
+
+	/** Allocate descriptors for double buffering */
+	dwc_ep->iso_desc_addr =
+	    dwc_otg_ep_alloc_desc_chain(&dwc_ep->iso_dma_desc_addr,
+					dwc_ep->desc_cnt * 2);
+	if (dwc_ep->desc_addr) {
+		DWC_WARN("%s, can't allocate DMA descriptor chain\n", __func__);
+		return;
+	}
+
+	dsts.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts);
+
+	/** ISO OUT EP */
+	if (dwc_ep->is_in == 0) {
+		dev_dma_desc_sts_t sts = {.d32 = 0 };
+		dwc_otg_dev_dma_desc_t *dma_desc = dwc_ep->iso_desc_addr;
+		dma_addr_t dma_ad;
+		uint32_t data_per_desc;
+		dwc_otg_dev_out_ep_regs_t *out_regs =
+		    core_if->dev_if->out_ep_regs[dwc_ep->num];
+		int offset;
+
+		addr = &core_if->dev_if->out_ep_regs[dwc_ep->num]->doepctl;
+		dma_ad = (dma_addr_t) DWC_READ_REG32(&(out_regs->doepdma));
+
+		/** Buffer 0 descriptors setup */
+		dma_ad = dwc_ep->dma_addr0;
+
+		sts.b_iso_out.bs = BS_HOST_READY;
+		sts.b_iso_out.rxsts = 0;
+		sts.b_iso_out.l = 0;
+		sts.b_iso_out.sp = 0;
+		sts.b_iso_out.ioc = 0;
+		sts.b_iso_out.pid = 0;
+		sts.b_iso_out.framenum = 0;
+
+		offset = 0;
+		for (i = 0; i < dwc_ep->desc_cnt - dwc_ep->pkt_per_frm;
+		     i += dwc_ep->pkt_per_frm) {
+
+			for (j = 0; j < dwc_ep->pkt_per_frm; ++j) {
+				uint32_t len = (j + 1) * dwc_ep->maxpacket;
+				if (len > dwc_ep->data_per_frame)
+					data_per_desc =
+					    dwc_ep->data_per_frame -
+					    j * dwc_ep->maxpacket;
+				else
+					data_per_desc = dwc_ep->maxpacket;
+				len = data_per_desc % 4;
+				if (len)
+					data_per_desc += 4 - len;
+
+				sts.b_iso_out.rxbytes = data_per_desc;
+				dma_desc->buf = dma_ad;
+				dma_desc->status.d32 = sts.d32;
+
+				offset += data_per_desc;
+				dma_desc++;
+				dma_ad += data_per_desc;
+			}
+		}
+
+		for (j = 0; j < dwc_ep->pkt_per_frm - 1; ++j) {
+			uint32_t len = (j + 1) * dwc_ep->maxpacket;
+			if (len > dwc_ep->data_per_frame)
+				data_per_desc =
+				    dwc_ep->data_per_frame -
+				    j * dwc_ep->maxpacket;
+			else
+				data_per_desc = dwc_ep->maxpacket;
+			len = data_per_desc % 4;
+			if (len)
+				data_per_desc += 4 - len;
+			sts.b_iso_out.rxbytes = data_per_desc;
+			dma_desc->buf = dma_ad;
+			dma_desc->status.d32 = sts.d32;
+
+			offset += data_per_desc;
+			dma_desc++;
+			dma_ad += data_per_desc;
+		}
+
+		sts.b_iso_out.ioc = 1;
+		len = (j + 1) * dwc_ep->maxpacket;
+		if (len > dwc_ep->data_per_frame)
+			data_per_desc =
+			    dwc_ep->data_per_frame - j * dwc_ep->maxpacket;
+		else
+			data_per_desc = dwc_ep->maxpacket;
+		len = data_per_desc % 4;
+		if (len)
+			data_per_desc += 4 - len;
+		sts.b_iso_out.rxbytes = data_per_desc;
+
+		dma_desc->buf = dma_ad;
+		dma_desc->status.d32 = sts.d32;
+		dma_desc++;
+
+		/** Buffer 1 descriptors setup */
+		sts.b_iso_out.ioc = 0;
+		dma_ad = dwc_ep->dma_addr1;
+
+		offset = 0;
+		for (i = 0; i < dwc_ep->desc_cnt - dwc_ep->pkt_per_frm;
+		     i += dwc_ep->pkt_per_frm) {
+			for (j = 0; j < dwc_ep->pkt_per_frm; ++j) {
+				uint32_t len = (j + 1) * dwc_ep->maxpacket;
+				if (len > dwc_ep->data_per_frame)
+					data_per_desc =
+					    dwc_ep->data_per_frame -
+					    j * dwc_ep->maxpacket;
+				else
+					data_per_desc = dwc_ep->maxpacket;
+				len = data_per_desc % 4;
+				if (len)
+					data_per_desc += 4 - len;
+
+				data_per_desc =
+				    sts.b_iso_out.rxbytes = data_per_desc;
+				dma_desc->buf = dma_ad;
+				dma_desc->status.d32 = sts.d32;
+
+				offset += data_per_desc;
+				dma_desc++;
+				dma_ad += data_per_desc;
+			}
+		}
+		for (j = 0; j < dwc_ep->pkt_per_frm - 1; ++j) {
+			data_per_desc =
+			    ((j + 1) * dwc_ep->maxpacket >
+			     dwc_ep->data_per_frame) ? dwc_ep->data_per_frame -
+			    j * dwc_ep->maxpacket : dwc_ep->maxpacket;
+			data_per_desc +=
+			    (data_per_desc % 4) ? (4 - data_per_desc % 4) : 0;
+			sts.b_iso_out.rxbytes = data_per_desc;
+			dma_desc->buf = dma_ad;
+			dma_desc->status.d32 = sts.d32;
+
+			offset += data_per_desc;
+			dma_desc++;
+			dma_ad += data_per_desc;
+		}
+
+		sts.b_iso_out.ioc = 1;
+		sts.b_iso_out.l = 1;
+		data_per_desc =
+		    ((j + 1) * dwc_ep->maxpacket >
+		     dwc_ep->data_per_frame) ? dwc_ep->data_per_frame -
+		    j * dwc_ep->maxpacket : dwc_ep->maxpacket;
+		data_per_desc +=
+		    (data_per_desc % 4) ? (4 - data_per_desc % 4) : 0;
+		sts.b_iso_out.rxbytes = data_per_desc;
+
+		dma_desc->buf = dma_ad;
+		dma_desc->status.d32 = sts.d32;
+
+		dwc_ep->next_frame = 0;
+
+		/** Write dma_ad into DOEPDMA register */
+		DWC_WRITE_REG32(&(out_regs->doepdma),
+				(uint32_t) dwc_ep->iso_dma_desc_addr);
+
+	}
+	/** ISO IN EP */
+	else {
+		dev_dma_desc_sts_t sts = {.d32 = 0 };
+		dwc_otg_dev_dma_desc_t *dma_desc = dwc_ep->iso_desc_addr;
+		dma_addr_t dma_ad;
+		dwc_otg_dev_in_ep_regs_t *in_regs =
+		    core_if->dev_if->in_ep_regs[dwc_ep->num];
+		unsigned int frmnumber;
+		fifosize_data_t txfifosize, rxfifosize;
+
+		txfifosize.d32 =
+		    DWC_READ_REG32(&core_if->dev_if->in_ep_regs[dwc_ep->num]->
+				   dtxfsts);
+		rxfifosize.d32 =
+		    DWC_READ_REG32(&core_if->core_global_regs->grxfsiz);
+
+		addr = &core_if->dev_if->in_ep_regs[dwc_ep->num]->diepctl;
+
+		dma_ad = dwc_ep->dma_addr0;
+
+		dsts.d32 =
+		    DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts);
+
+		sts.b_iso_in.bs = BS_HOST_READY;
+		sts.b_iso_in.txsts = 0;
+		sts.b_iso_in.sp =
+		    (dwc_ep->data_per_frame % dwc_ep->maxpacket) ? 1 : 0;
+		sts.b_iso_in.ioc = 0;
+		sts.b_iso_in.pid = dwc_ep->pkt_per_frm;
+
+		frmnumber = dwc_ep->next_frame;
+
+		sts.b_iso_in.framenum = frmnumber;
+		sts.b_iso_in.txbytes = dwc_ep->data_per_frame;
+		sts.b_iso_in.l = 0;
+
+		/** Buffer 0 descriptors setup */
+		for (i = 0; i < dwc_ep->desc_cnt - 1; i++) {
+			dma_desc->buf = dma_ad;
+			dma_desc->status.d32 = sts.d32;
+			dma_desc++;
+
+			dma_ad += dwc_ep->data_per_frame;
+			sts.b_iso_in.framenum += dwc_ep->bInterval;
+		}
+
+		sts.b_iso_in.ioc = 1;
+		dma_desc->buf = dma_ad;
+		dma_desc->status.d32 = sts.d32;
+		++dma_desc;
+
+		/** Buffer 1 descriptors setup */
+		sts.b_iso_in.ioc = 0;
+		dma_ad = dwc_ep->dma_addr1;
+
+		for (i = 0; i < dwc_ep->desc_cnt - dwc_ep->pkt_per_frm;
+		     i += dwc_ep->pkt_per_frm) {
+			dma_desc->buf = dma_ad;
+			dma_desc->status.d32 = sts.d32;
+			dma_desc++;
+
+			dma_ad += dwc_ep->data_per_frame;
+			sts.b_iso_in.framenum += dwc_ep->bInterval;
+
+			sts.b_iso_in.ioc = 0;
+		}
+		sts.b_iso_in.ioc = 1;
+		sts.b_iso_in.l = 1;
+
+		dma_desc->buf = dma_ad;
+		dma_desc->status.d32 = sts.d32;
+
+		dwc_ep->next_frame = sts.b_iso_in.framenum + dwc_ep->bInterval;
+
+		/** Write dma_ad into diepdma register */
+		DWC_WRITE_REG32(&(in_regs->diepdma),
+				(uint32_t) dwc_ep->iso_dma_desc_addr);
+	}
+	/** Enable endpoint, clear nak  */
+	depctl.d32 = 0;
+	depctl.b.epena = 1;
+	depctl.b.usbactep = 1;
+	depctl.b.cnak = 1;
+
+	DWC_MODIFY_REG32(addr, depctl.d32, depctl.d32);
+	depctl.d32 = DWC_READ_REG32(addr);
+}
+
+/**
+ * This function initializes a descriptor chain for Isochronous transfer
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param ep The EP to start the transfer on.
+ *
+ */
+void dwc_otg_iso_ep_start_buf_transfer(dwc_otg_core_if_t * core_if,
+				       dwc_ep_t * ep)
+{
+	depctl_data_t depctl = {.d32 = 0 };
+	volatile uint32_t *addr;
+
+	if (ep->is_in) {
+		addr = &core_if->dev_if->in_ep_regs[ep->num]->diepctl;
+	} else {
+		addr = &core_if->dev_if->out_ep_regs[ep->num]->doepctl;
+	}
+
+	if (core_if->dma_enable == 0 || core_if->dma_desc_enable != 0) {
+		return;
+	} else {
+		deptsiz_data_t deptsiz = {.d32 = 0 };
+
+		ep->xfer_len =
+		    ep->data_per_frame * ep->buf_proc_intrvl / ep->bInterval;
+		ep->pkt_cnt =
+		    (ep->xfer_len - 1 + ep->maxpacket) / ep->maxpacket;
+		ep->xfer_count = 0;
+		ep->xfer_buff =
+		    (ep->proc_buf_num) ? ep->xfer_buff1 : ep->xfer_buff0;
+		ep->dma_addr =
+		    (ep->proc_buf_num) ? ep->dma_addr1 : ep->dma_addr0;
+
+		if (ep->is_in) {
+			/* Program the transfer size and packet count
+			 *      as follows: xfersize = N * maxpacket +
+			 *      short_packet pktcnt = N + (short_packet
+			 *      exist ? 1 : 0)
+			 */
+			deptsiz.b.mc = ep->pkt_per_frm;
+			deptsiz.b.xfersize = ep->xfer_len;
+			deptsiz.b.pktcnt =
+			    (ep->xfer_len - 1 + ep->maxpacket) / ep->maxpacket;
+			DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[ep->num]->
+					dieptsiz, deptsiz.d32);
+
+			/* Write the DMA register */
+			DWC_WRITE_REG32(&
+					(core_if->dev_if->in_ep_regs[ep->num]->
+					 diepdma), (uint32_t) ep->dma_addr);
+
+		} else {
+			deptsiz.b.pktcnt =
+			    (ep->xfer_len + (ep->maxpacket - 1)) /
+			    ep->maxpacket;
+			deptsiz.b.xfersize = deptsiz.b.pktcnt * ep->maxpacket;
+
+			DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[ep->num]->
+					doeptsiz, deptsiz.d32);
+
+			/* Write the DMA register */
+			DWC_WRITE_REG32(&
+					(core_if->dev_if->out_ep_regs[ep->num]->
+					 doepdma), (uint32_t) ep->dma_addr);
+
+		}
+		/** Enable endpoint, clear nak  */
+		depctl.d32 = 0;
+		depctl.b.epena = 1;
+		depctl.b.cnak = 1;
+
+		DWC_MODIFY_REG32(addr, depctl.d32, depctl.d32);
+	}
+}
+
+/**
+ * This function does the setup for a data transfer for an EP and
+ * starts the transfer. For an IN transfer, the packets will be
+ * loaded into the appropriate Tx FIFO in the ISR. For OUT transfers,
+ * the packets are unloaded from the Rx FIFO in the ISR.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param ep The EP to start the transfer on.
+ */
+
+static void dwc_otg_iso_ep_start_transfer(dwc_otg_core_if_t * core_if,
+					  dwc_ep_t * ep)
+{
+	if (core_if->dma_enable) {
+		if (core_if->dma_desc_enable) {
+			if (ep->is_in) {
+				ep->desc_cnt = ep->pkt_cnt / ep->pkt_per_frm;
+			} else {
+				ep->desc_cnt = ep->pkt_cnt;
+			}
+			dwc_otg_iso_ep_start_ddma_transfer(core_if, ep);
+		} else {
+			if (core_if->pti_enh_enable) {
+				dwc_otg_iso_ep_start_buf_transfer(core_if, ep);
+			} else {
+				ep->cur_pkt_addr =
+				    (ep->proc_buf_num) ? ep->xfer_buff1 : ep->
+				    xfer_buff0;
+				ep->cur_pkt_dma_addr =
+				    (ep->proc_buf_num) ? ep->dma_addr1 : ep->
+				    dma_addr0;
+				dwc_otg_iso_ep_start_frm_transfer(core_if, ep);
+			}
+		}
+	} else {
+		ep->cur_pkt_addr =
+		    (ep->proc_buf_num) ? ep->xfer_buff1 : ep->xfer_buff0;
+		ep->cur_pkt_dma_addr =
+		    (ep->proc_buf_num) ? ep->dma_addr1 : ep->dma_addr0;
+		dwc_otg_iso_ep_start_frm_transfer(core_if, ep);
+	}
+}
+
+/**
+ * This function stops transfer for an EP and
+ * resets the ep's variables.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param ep The EP to start the transfer on.
+ */
+
+void dwc_otg_iso_ep_stop_transfer(dwc_otg_core_if_t * core_if, dwc_ep_t * ep)
+{
+	depctl_data_t depctl = {.d32 = 0 };
+	volatile uint32_t *addr;
+
+	if (ep->is_in == 1) {
+		addr = &core_if->dev_if->in_ep_regs[ep->num]->diepctl;
+	} else {
+		addr = &core_if->dev_if->out_ep_regs[ep->num]->doepctl;
+	}
+
+	/* disable the ep */
+	depctl.d32 = DWC_READ_REG32(addr);
+
+	depctl.b.epdis = 1;
+	depctl.b.snak = 1;
+
+	DWC_WRITE_REG32(addr, depctl.d32);
+
+	if (core_if->dma_desc_enable &&
+	    ep->iso_desc_addr && ep->iso_dma_desc_addr) {
+		dwc_otg_ep_free_desc_chain(ep->iso_desc_addr,
+					   ep->iso_dma_desc_addr,
+					   ep->desc_cnt * 2);
+	}
+
+	/* reset varibales */
+	ep->dma_addr0 = 0;
+	ep->dma_addr1 = 0;
+	ep->xfer_buff0 = 0;
+	ep->xfer_buff1 = 0;
+	ep->data_per_frame = 0;
+	ep->data_pattern_frame = 0;
+	ep->sync_frame = 0;
+	ep->buf_proc_intrvl = 0;
+	ep->bInterval = 0;
+	ep->proc_buf_num = 0;
+	ep->pkt_per_frm = 0;
+	ep->pkt_per_frm = 0;
+	ep->desc_cnt = 0;
+	ep->iso_desc_addr = 0;
+	ep->iso_dma_desc_addr = 0;
+}
+
+int dwc_otg_pcd_iso_ep_start(dwc_otg_pcd_t * pcd, void *ep_handle,
+			     uint8_t * buf0, uint8_t * buf1, dwc_dma_t dma0,
+			     dwc_dma_t dma1, int sync_frame, int dp_frame,
+			     int data_per_frame, int start_frame,
+			     int buf_proc_intrvl, void *req_handle,
+			     int atomic_alloc)
+{
+	dwc_otg_pcd_ep_t *ep;
+	dwc_irqflags_t flags = 0;
+	dwc_ep_t *dwc_ep;
+	int32_t frm_data;
+	dsts_data_t dsts;
+	dwc_otg_core_if_t *core_if;
+
+	ep = get_ep_from_handle(pcd, ep_handle);
+
+	if (!ep || !ep->desc || ep->dwc_ep.num == 0) {
+		DWC_WARN("bad ep\n");
+		return -DWC_E_INVALID;
+	}
+
+	DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags);
+	core_if = GET_CORE_IF(pcd);
+	dwc_ep = &ep->dwc_ep;
+
+	if (ep->iso_req_handle) {
+		DWC_WARN("ISO request in progress\n");
+	}
+
+	dwc_ep->dma_addr0 = dma0;
+	dwc_ep->dma_addr1 = dma1;
+
+	dwc_ep->xfer_buff0 = buf0;
+	dwc_ep->xfer_buff1 = buf1;
+
+	dwc_ep->data_per_frame = data_per_frame;
+
+	/** @todo - pattern data support is to be implemented in the future */
+	dwc_ep->data_pattern_frame = dp_frame;
+	dwc_ep->sync_frame = sync_frame;
+
+	dwc_ep->buf_proc_intrvl = buf_proc_intrvl;
+
+	dwc_ep->bInterval = 1 << (ep->desc->bInterval - 1);
+
+	dwc_ep->proc_buf_num = 0;
+
+	dwc_ep->pkt_per_frm = 0;
+	frm_data = ep->dwc_ep.data_per_frame;
+	while (frm_data > 0) {
+		dwc_ep->pkt_per_frm++;
+		frm_data -= ep->dwc_ep.maxpacket;
+	}
+
+	dsts.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts);
+
+	if (start_frame == -1) {
+		dwc_ep->next_frame = dsts.b.soffn + 1;
+		if (dwc_ep->bInterval != 1) {
+			dwc_ep->next_frame =
+			    dwc_ep->next_frame + (dwc_ep->bInterval - 1 -
+						  dwc_ep->next_frame %
+						  dwc_ep->bInterval);
+		}
+	} else {
+		dwc_ep->next_frame = start_frame;
+	}
+
+	if (!core_if->pti_enh_enable) {
+		dwc_ep->pkt_cnt =
+		    dwc_ep->buf_proc_intrvl * dwc_ep->pkt_per_frm /
+		    dwc_ep->bInterval;
+	} else {
+		dwc_ep->pkt_cnt =
+		    (dwc_ep->data_per_frame *
+		     (dwc_ep->buf_proc_intrvl / dwc_ep->bInterval)
+		     - 1 + dwc_ep->maxpacket) / dwc_ep->maxpacket;
+	}
+
+	if (core_if->dma_desc_enable) {
+		dwc_ep->desc_cnt =
+		    dwc_ep->buf_proc_intrvl * dwc_ep->pkt_per_frm /
+		    dwc_ep->bInterval;
+	}
+
+	if (atomic_alloc) {
+		dwc_ep->pkt_info =
+		    DWC_ALLOC_ATOMIC(sizeof(iso_pkt_info_t) * dwc_ep->pkt_cnt);
+	} else {
+		dwc_ep->pkt_info =
+		    DWC_ALLOC(sizeof(iso_pkt_info_t) * dwc_ep->pkt_cnt);
+	}
+	if (!dwc_ep->pkt_info) {
+		DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags);
+		return -DWC_E_NO_MEMORY;
+	}
+	if (core_if->pti_enh_enable) {
+		dwc_memset(dwc_ep->pkt_info, 0,
+			   sizeof(iso_pkt_info_t) * dwc_ep->pkt_cnt);
+	}
+
+	dwc_ep->cur_pkt = 0;
+	ep->iso_req_handle = req_handle;
+
+	DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags);
+	dwc_otg_iso_ep_start_transfer(core_if, dwc_ep);
+	return 0;
+}
+
+int dwc_otg_pcd_iso_ep_stop(dwc_otg_pcd_t * pcd, void *ep_handle,
+			    void *req_handle)
+{
+	dwc_irqflags_t flags = 0;
+	dwc_otg_pcd_ep_t *ep;
+	dwc_ep_t *dwc_ep;
+
+	ep = get_ep_from_handle(pcd, ep_handle);
+	if (!ep || !ep->desc || ep->dwc_ep.num == 0) {
+		DWC_WARN("bad ep\n");
+		return -DWC_E_INVALID;
+	}
+	dwc_ep = &ep->dwc_ep;
+
+	dwc_otg_iso_ep_stop_transfer(GET_CORE_IF(pcd), dwc_ep);
+
+	DWC_FREE(dwc_ep->pkt_info);
+	DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags);
+	if (ep->iso_req_handle != req_handle) {
+		DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags);
+		return -DWC_E_INVALID;
+	}
+
+	DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags);
+
+	ep->iso_req_handle = 0;
+	return 0;
+}
+
+/**
+ * This function is used for perodical data exchnage between PCD and gadget drivers.
+ * for Isochronous EPs
+ *
+ *	- Every time a sync period completes this function is called to
+ *	  perform data exchange between PCD and gadget
+ */
+void dwc_otg_iso_buffer_done(dwc_otg_pcd_t * pcd, dwc_otg_pcd_ep_t * ep,
+			     void *req_handle)
+{
+	int i;
+	dwc_ep_t *dwc_ep;
+
+	dwc_ep = &ep->dwc_ep;
+
+	DWC_SPINUNLOCK(ep->pcd->lock);
+	pcd->fops->isoc_complete(pcd, ep->priv, ep->iso_req_handle,
+				 dwc_ep->proc_buf_num ^ 0x1);
+	DWC_SPINLOCK(ep->pcd->lock);
+
+	for (i = 0; i < dwc_ep->pkt_cnt; ++i) {
+		dwc_ep->pkt_info[i].status = 0;
+		dwc_ep->pkt_info[i].offset = 0;
+		dwc_ep->pkt_info[i].length = 0;
+	}
+}
+
+int dwc_otg_pcd_get_iso_packet_count(dwc_otg_pcd_t * pcd, void *ep_handle,
+				     void *iso_req_handle)
+{
+	dwc_otg_pcd_ep_t *ep;
+	dwc_ep_t *dwc_ep;
+
+	ep = get_ep_from_handle(pcd, ep_handle);
+	if (!ep->desc || ep->dwc_ep.num == 0) {
+		DWC_WARN("bad ep\n");
+		return -DWC_E_INVALID;
+	}
+	dwc_ep = &ep->dwc_ep;
+
+	return dwc_ep->pkt_cnt;
+}
+
+void dwc_otg_pcd_get_iso_packet_params(dwc_otg_pcd_t * pcd, void *ep_handle,
+				       void *iso_req_handle, int packet,
+				       int *status, int *actual, int *offset)
+{
+	dwc_otg_pcd_ep_t *ep;
+	dwc_ep_t *dwc_ep;
+
+	ep = get_ep_from_handle(pcd, ep_handle);
+	if (!ep)
+		DWC_WARN("bad ep\n");
+
+	dwc_ep = &ep->dwc_ep;
+
+	*status = dwc_ep->pkt_info[packet].status;
+	*actual = dwc_ep->pkt_info[packet].length;
+	*offset = dwc_ep->pkt_info[packet].offset;
+}
+
+#endif /* DWC_EN_ISOC */
+
+static void dwc_otg_pcd_init_ep(dwc_otg_pcd_t * pcd, dwc_otg_pcd_ep_t * pcd_ep,
+				uint32_t is_in, uint32_t ep_num)
+{
+	/* Init EP structure */
+	pcd_ep->desc = 0;
+	pcd_ep->pcd = pcd;
+	pcd_ep->stopped = 1;
+	pcd_ep->queue_sof = 0;
+
+	/* Init DWC ep structure */
+	pcd_ep->dwc_ep.is_in = is_in;
+	pcd_ep->dwc_ep.num = ep_num;
+	pcd_ep->dwc_ep.active = 0;
+	pcd_ep->dwc_ep.tx_fifo_num = 0;
+	/* Control until ep is actvated */
+	pcd_ep->dwc_ep.type = DWC_OTG_EP_TYPE_CONTROL;
+	pcd_ep->dwc_ep.maxpacket = MAX_PACKET_SIZE;
+	pcd_ep->dwc_ep.dma_addr = 0;
+	pcd_ep->dwc_ep.start_xfer_buff = 0;
+	pcd_ep->dwc_ep.xfer_buff = 0;
+	pcd_ep->dwc_ep.xfer_len = 0;
+	pcd_ep->dwc_ep.xfer_count = 0;
+	pcd_ep->dwc_ep.sent_zlp = 0;
+	pcd_ep->dwc_ep.total_len = 0;
+	pcd_ep->dwc_ep.desc_addr = 0;
+	pcd_ep->dwc_ep.dma_desc_addr = 0;
+	DWC_CIRCLEQ_INIT(&pcd_ep->queue);
+}
+
+/**
+ * Initialize ep's
+ */
+static void dwc_otg_pcd_reinit(dwc_otg_pcd_t * pcd)
+{
+	int i;
+	uint32_t hwcfg1;
+	dwc_otg_pcd_ep_t *ep;
+	int in_ep_cntr, out_ep_cntr;
+	uint32_t num_in_eps = (GET_CORE_IF(pcd))->dev_if->num_in_eps;
+	uint32_t num_out_eps = (GET_CORE_IF(pcd))->dev_if->num_out_eps;
+
+	/**
+	 * Initialize the EP0 structure.
+	 */
+	ep = &pcd->ep0;
+	dwc_otg_pcd_init_ep(pcd, ep, 0, 0);
+
+	in_ep_cntr = 0;
+	hwcfg1 = (GET_CORE_IF(pcd))->hwcfg1.d32 >> 3;
+	for (i = 1; in_ep_cntr < num_in_eps; i++) {
+		if ((hwcfg1 & 0x1) == 0) {
+			dwc_otg_pcd_ep_t *ep = &pcd->in_ep[in_ep_cntr];
+			in_ep_cntr++;
+			/**
+			 * @todo NGS: Add direction to EP, based on contents
+			 * of HWCFG1.  Need a copy of HWCFG1 in pcd structure?
+			 * sprintf(";r
+			 */
+			dwc_otg_pcd_init_ep(pcd, ep, 1 /* IN */ , i);
+
+			DWC_CIRCLEQ_INIT(&ep->queue);
+		}
+		hwcfg1 >>= 2;
+	}
+
+	out_ep_cntr = 0;
+	hwcfg1 = (GET_CORE_IF(pcd))->hwcfg1.d32 >> 2;
+	for (i = 1; out_ep_cntr < num_out_eps; i++) {
+		if ((hwcfg1 & 0x1) == 0) {
+			dwc_otg_pcd_ep_t *ep = &pcd->out_ep[out_ep_cntr];
+			out_ep_cntr++;
+			/**
+			 * @todo NGS: Add direction to EP, based on contents
+			 * of HWCFG1.  Need a copy of HWCFG1 in pcd structure?
+			 * sprintf(";r
+			 */
+			dwc_otg_pcd_init_ep(pcd, ep, 0 /* OUT */ , i);
+			DWC_CIRCLEQ_INIT(&ep->queue);
+		}
+		hwcfg1 >>= 2;
+	}
+
+	pcd->ep0state = EP0_DISCONNECT;
+	pcd->ep0.dwc_ep.maxpacket = MAX_EP0_SIZE;
+	pcd->ep0.dwc_ep.type = DWC_OTG_EP_TYPE_CONTROL;
+}
+
+/**
+ * This function is called when the SRP timer expires. The SRP should
+ * complete within 6 seconds.
+ */
+static void srp_timeout(void *ptr)
+{
+	gotgctl_data_t gotgctl;
+	dwc_otg_core_if_t *core_if = (dwc_otg_core_if_t *) ptr;
+	volatile uint32_t *addr = &core_if->core_global_regs->gotgctl;
+
+	gotgctl.d32 = DWC_READ_REG32(addr);
+
+	core_if->srp_timer_started = 0;
+
+	if (core_if->adp_enable) {
+		if (gotgctl.b.bsesvld == 0) {
+			gpwrdn_data_t gpwrdn = {.d32 = 0 };
+			DWC_PRINTF("SRP Timeout BSESSVLD = 0\n");
+			/* Power off the core */
+			if (core_if->power_down == 2) {
+				gpwrdn.b.pwrdnswtch = 1;
+				DWC_MODIFY_REG32(&core_if->
+						 core_global_regs->gpwrdn,
+						 gpwrdn.d32, 0);
+			}
+
+			gpwrdn.d32 = 0;
+			gpwrdn.b.pmuintsel = 1;
+			gpwrdn.b.pmuactv = 1;
+			DWC_MODIFY_REG32(&core_if->core_global_regs->gpwrdn, 0,
+					 gpwrdn.d32);
+			dwc_otg_adp_probe_start(core_if);
+		} else {
+			DWC_PRINTF("SRP Timeout BSESSVLD = 1\n");
+			core_if->op_state = B_PERIPHERAL;
+			dwc_otg_core_init(core_if);
+			dwc_otg_enable_global_interrupts(core_if);
+			cil_pcd_start(core_if);
+		}
+	}
+
+	if ((core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS) &&
+	    (core_if->core_params->i2c_enable)) {
+		DWC_PRINTF("SRP Timeout\n");
+
+		if ((core_if->srp_success) && (gotgctl.b.bsesvld)) {
+			if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup) {
+				core_if->pcd_cb->resume_wakeup(core_if->pcd_cb->p);
+			}
+
+			/* Clear Session Request */
+			gotgctl.d32 = 0;
+			gotgctl.b.sesreq = 1;
+			DWC_MODIFY_REG32(&core_if->core_global_regs->gotgctl,
+					 gotgctl.d32, 0);
+
+			core_if->srp_success = 0;
+		} else {
+			__DWC_ERROR("Device not connected/responding\n");
+			gotgctl.b.sesreq = 0;
+			DWC_WRITE_REG32(addr, gotgctl.d32);
+		}
+	} else if (gotgctl.b.sesreq) {
+		DWC_PRINTF("SRP Timeout\n");
+
+		__DWC_ERROR("Device not connected/responding\n");
+		gotgctl.b.sesreq = 0;
+		DWC_WRITE_REG32(addr, gotgctl.d32);
+	} else {
+		DWC_PRINTF(" SRP GOTGCTL=%0x\n", gotgctl.d32);
+	}
+}
+
+/**
+ * Tasklet
+ *
+ */
+static void start_xfer_tasklet_func(void *data)
+{
+	dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *) data;
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+
+	int i;
+	depctl_data_t diepctl;
+
+	DWC_DEBUGPL(DBG_PCDV, "Start xfer tasklet\n");
+
+	diepctl.d32 = DWC_READ_REG32(&core_if->dev_if->in_ep_regs[0]->diepctl);
+
+	if (pcd->ep0.queue_sof) {
+		pcd->ep0.queue_sof = 0;
+		start_next_request(&pcd->ep0);
+		// break;
+	}
+
+	for (i = 0; i < core_if->dev_if->num_in_eps; i++) {
+		depctl_data_t diepctl;
+		diepctl.d32 =
+		    DWC_READ_REG32(&core_if->dev_if->in_ep_regs[i]->diepctl);
+
+		if (pcd->in_ep[i].queue_sof) {
+			pcd->in_ep[i].queue_sof = 0;
+			start_next_request(&pcd->in_ep[i]);
+			// break;
+		}
+	}
+
+	return;
+}
+
+/**
+ * This function initialized the PCD portion of the driver.
+ *
+ */
+dwc_otg_pcd_t *dwc_otg_pcd_init(dwc_otg_device_t *otg_dev)
+{
+	struct device *dev = &otg_dev->os_dep.platformdev->dev;
+	dwc_otg_core_if_t *core_if = otg_dev->core_if;
+	dwc_otg_pcd_t *pcd = NULL;
+	dwc_otg_dev_if_t *dev_if;
+	int i;
+
+	/*
+	 * Allocate PCD structure
+	 */
+	pcd = DWC_ALLOC(sizeof(dwc_otg_pcd_t));
+
+	if (pcd == NULL) {
+		return NULL;
+	}
+
+#if (defined(DWC_LINUX) && defined(CONFIG_DEBUG_SPINLOCK))
+	DWC_SPINLOCK_ALLOC_LINUX_DEBUG(pcd->lock);
+#else
+	pcd->lock = DWC_SPINLOCK_ALLOC();
+#endif
+        DWC_DEBUGPL(DBG_HCDV, "Init of PCD %p given core_if %p\n",
+                    pcd, core_if);//GRAYG
+	if (!pcd->lock) {
+		DWC_ERROR("Could not allocate lock for pcd");
+		DWC_FREE(pcd);
+		return NULL;
+	}
+	/* Set core_if's lock pointer to hcd->lock */
+	core_if->lock = pcd->lock;
+	pcd->core_if = core_if;
+
+	dev_if = core_if->dev_if;
+	dev_if->isoc_ep = NULL;
+
+	if (core_if->hwcfg4.b.ded_fifo_en) {
+		DWC_PRINTF("Dedicated Tx FIFOs mode\n");
+	} else {
+		DWC_PRINTF("Shared Tx FIFO mode\n");
+	}
+
+	/*
+	 * Initialized the Core for Device mode here if there is nod ADP support.
+	 * Otherwise it will be done later in dwc_otg_adp_start routine.
+	 */
+	if (dwc_otg_is_device_mode(core_if) /*&& !core_if->adp_enable*/) {
+		dwc_otg_core_dev_init(core_if);
+	}
+
+	/*
+	 * Register the PCD Callbacks.
+	 */
+	dwc_otg_cil_register_pcd_callbacks(core_if, &pcd_callbacks, pcd);
+
+	/*
+	 * Initialize the DMA buffer for SETUP packets
+	 */
+	if (GET_CORE_IF(pcd)->dma_enable) {
+		pcd->setup_pkt =
+		    DWC_DMA_ALLOC(dev, sizeof(*pcd->setup_pkt) * 5,
+				  &pcd->setup_pkt_dma_handle);
+		if (pcd->setup_pkt == NULL) {
+			DWC_FREE(pcd);
+			return NULL;
+		}
+
+		pcd->status_buf =
+		    DWC_DMA_ALLOC(dev, sizeof(uint16_t),
+				  &pcd->status_buf_dma_handle);
+		if (pcd->status_buf == NULL) {
+			DWC_DMA_FREE(dev, sizeof(*pcd->setup_pkt) * 5,
+				     pcd->setup_pkt, pcd->setup_pkt_dma_handle);
+			DWC_FREE(pcd);
+			return NULL;
+		}
+
+		if (GET_CORE_IF(pcd)->dma_desc_enable) {
+			dev_if->setup_desc_addr[0] =
+			    dwc_otg_ep_alloc_desc_chain(dev,
+				&dev_if->dma_setup_desc_addr[0], 1);
+			dev_if->setup_desc_addr[1] =
+			    dwc_otg_ep_alloc_desc_chain(dev,
+				&dev_if->dma_setup_desc_addr[1], 1);
+			dev_if->in_desc_addr =
+			    dwc_otg_ep_alloc_desc_chain(dev,
+				&dev_if->dma_in_desc_addr, 1);
+			dev_if->out_desc_addr =
+			    dwc_otg_ep_alloc_desc_chain(dev,
+				&dev_if->dma_out_desc_addr, 1);
+			pcd->data_terminated = 0;
+
+			if (dev_if->setup_desc_addr[0] == 0
+			    || dev_if->setup_desc_addr[1] == 0
+			    || dev_if->in_desc_addr == 0
+			    || dev_if->out_desc_addr == 0) {
+
+				if (dev_if->out_desc_addr)
+					dwc_otg_ep_free_desc_chain(dev,
+					     dev_if->out_desc_addr,
+					     dev_if->dma_out_desc_addr, 1);
+				if (dev_if->in_desc_addr)
+					dwc_otg_ep_free_desc_chain(dev,
+					     dev_if->in_desc_addr,
+					     dev_if->dma_in_desc_addr, 1);
+				if (dev_if->setup_desc_addr[1])
+					dwc_otg_ep_free_desc_chain(dev,
+					     dev_if->setup_desc_addr[1],
+					     dev_if->dma_setup_desc_addr[1], 1);
+				if (dev_if->setup_desc_addr[0])
+					dwc_otg_ep_free_desc_chain(dev,
+					     dev_if->setup_desc_addr[0],
+					     dev_if->dma_setup_desc_addr[0], 1);
+
+				DWC_DMA_FREE(dev, sizeof(*pcd->setup_pkt) * 5,
+					     pcd->setup_pkt,
+					     pcd->setup_pkt_dma_handle);
+				DWC_DMA_FREE(dev, sizeof(*pcd->status_buf),
+					     pcd->status_buf,
+					     pcd->status_buf_dma_handle);
+
+				DWC_FREE(pcd);
+
+				return NULL;
+			}
+		}
+	} else {
+		pcd->setup_pkt = DWC_ALLOC(sizeof(*pcd->setup_pkt) * 5);
+		if (pcd->setup_pkt == NULL) {
+			DWC_FREE(pcd);
+			return NULL;
+		}
+
+		pcd->status_buf = DWC_ALLOC(sizeof(uint16_t));
+		if (pcd->status_buf == NULL) {
+			DWC_FREE(pcd->setup_pkt);
+			DWC_FREE(pcd);
+			return NULL;
+		}
+	}
+
+	dwc_otg_pcd_reinit(pcd);
+
+	/* Allocate the cfi object for the PCD */
+#ifdef DWC_UTE_CFI
+	pcd->cfi = DWC_ALLOC(sizeof(cfiobject_t));
+	if (NULL == pcd->cfi)
+		goto fail;
+	if (init_cfi(pcd->cfi)) {
+		CFI_INFO("%s: Failed to init the CFI object\n", __func__);
+		goto fail;
+	}
+#endif
+
+	/* Initialize tasklets */
+	pcd->start_xfer_tasklet = DWC_TASK_ALLOC("xfer_tasklet",
+						 start_xfer_tasklet_func, pcd);
+	pcd->test_mode_tasklet = DWC_TASK_ALLOC("test_mode_tasklet",
+						do_test_mode, pcd);
+
+	/* Initialize SRP timer */
+	core_if->srp_timer = DWC_TIMER_ALLOC("SRP TIMER", srp_timeout, core_if);
+
+	if (core_if->core_params->dev_out_nak) {
+		/**
+		* Initialize xfer timeout timer. Implemented for
+		* 2.93a feature "Device DDMA OUT NAK Enhancement"
+		*/
+		for(i = 0; i < MAX_EPS_CHANNELS; i++) {
+			pcd->core_if->ep_xfer_timer[i] =
+				DWC_TIMER_ALLOC("ep timer", ep_xfer_timeout,
+				&pcd->core_if->ep_xfer_info[i]);
+		}
+	}
+
+	return pcd;
+#ifdef DWC_UTE_CFI
+fail:
+#endif
+	if (pcd->setup_pkt)
+		DWC_FREE(pcd->setup_pkt);
+	if (pcd->status_buf)
+		DWC_FREE(pcd->status_buf);
+#ifdef DWC_UTE_CFI
+	if (pcd->cfi)
+		DWC_FREE(pcd->cfi);
+#endif
+	if (pcd)
+		DWC_FREE(pcd);
+	return NULL;
+
+}
+
+/**
+ * Remove PCD specific data
+ */
+void dwc_otg_pcd_remove(dwc_otg_pcd_t * pcd)
+{
+	dwc_otg_dev_if_t *dev_if = GET_CORE_IF(pcd)->dev_if;
+	struct device *dev = dwc_otg_pcd_to_dev(pcd);
+	int i;
+
+	if (pcd->core_if->core_params->dev_out_nak) {
+		for (i = 0; i < MAX_EPS_CHANNELS; i++) {
+			DWC_TIMER_CANCEL(pcd->core_if->ep_xfer_timer[i]);
+			pcd->core_if->ep_xfer_info[i].state = 0;
+		}
+	}
+
+	if (GET_CORE_IF(pcd)->dma_enable) {
+		DWC_DMA_FREE(dev, sizeof(*pcd->setup_pkt) * 5, pcd->setup_pkt,
+			     pcd->setup_pkt_dma_handle);
+		DWC_DMA_FREE(dev, sizeof(uint16_t), pcd->status_buf,
+			     pcd->status_buf_dma_handle);
+		if (GET_CORE_IF(pcd)->dma_desc_enable) {
+			dwc_otg_ep_free_desc_chain(dev,
+						   dev_if->setup_desc_addr[0],
+						   dev_if->dma_setup_desc_addr
+						   [0], 1);
+			dwc_otg_ep_free_desc_chain(dev,
+						   dev_if->setup_desc_addr[1],
+						   dev_if->dma_setup_desc_addr
+						   [1], 1);
+			dwc_otg_ep_free_desc_chain(dev,
+						   dev_if->in_desc_addr,
+						   dev_if->dma_in_desc_addr, 1);
+			dwc_otg_ep_free_desc_chain(dev,
+						   dev_if->out_desc_addr,
+						   dev_if->dma_out_desc_addr,
+						   1);
+		}
+	} else {
+		DWC_FREE(pcd->setup_pkt);
+		DWC_FREE(pcd->status_buf);
+	}
+	DWC_SPINLOCK_FREE(pcd->lock);
+	/* Set core_if's lock pointer to NULL */
+	pcd->core_if->lock = NULL;
+
+	DWC_TASK_FREE(pcd->start_xfer_tasklet);
+	DWC_TASK_FREE(pcd->test_mode_tasklet);
+	if (pcd->core_if->core_params->dev_out_nak) {
+		for (i = 0; i < MAX_EPS_CHANNELS; i++) {
+			if (pcd->core_if->ep_xfer_timer[i]) {
+					DWC_TIMER_FREE(pcd->core_if->ep_xfer_timer[i]);
+			}
+		}
+	}
+
+/* Release the CFI object's dynamic memory */
+#ifdef DWC_UTE_CFI
+	if (pcd->cfi->ops.release) {
+		pcd->cfi->ops.release(pcd->cfi);
+	}
+#endif
+
+	DWC_FREE(pcd);
+}
+
+/**
+ * Returns whether registered pcd is dual speed or not
+ */
+uint32_t dwc_otg_pcd_is_dualspeed(dwc_otg_pcd_t * pcd)
+{
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+
+	if ((core_if->core_params->speed == DWC_SPEED_PARAM_FULL) ||
+	    ((core_if->hwcfg2.b.hs_phy_type == 2) &&
+	     (core_if->hwcfg2.b.fs_phy_type == 1) &&
+	     (core_if->core_params->ulpi_fs_ls))) {
+		return 0;
+	}
+
+	return 1;
+}
+
+/**
+ * Returns whether registered pcd is OTG capable or not
+ */
+uint32_t dwc_otg_pcd_is_otg(dwc_otg_pcd_t * pcd)
+{
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+	gusbcfg_data_t usbcfg = {.d32 = 0 };
+
+	usbcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->gusbcfg);
+	if (!usbcfg.b.srpcap || !usbcfg.b.hnpcap) {
+		return 0;
+	}
+
+	return 1;
+}
+
+/**
+ * This function assigns periodic Tx FIFO to an periodic EP
+ * in shared Tx FIFO mode
+ */
+static uint32_t assign_tx_fifo(dwc_otg_core_if_t * core_if)
+{
+	uint32_t TxMsk = 1;
+	int i;
+
+	for (i = 0; i < core_if->hwcfg4.b.num_in_eps; ++i) {
+		if ((TxMsk & core_if->tx_msk) == 0) {
+			core_if->tx_msk |= TxMsk;
+			return i + 1;
+		}
+		TxMsk <<= 1;
+	}
+	return 0;
+}
+
+/**
+ * This function assigns periodic Tx FIFO to an periodic EP
+ * in shared Tx FIFO mode
+ */
+static uint32_t assign_perio_tx_fifo(dwc_otg_core_if_t * core_if)
+{
+	uint32_t PerTxMsk = 1;
+	int i;
+	for (i = 0; i < core_if->hwcfg4.b.num_dev_perio_in_ep; ++i) {
+		if ((PerTxMsk & core_if->p_tx_msk) == 0) {
+			core_if->p_tx_msk |= PerTxMsk;
+			return i + 1;
+		}
+		PerTxMsk <<= 1;
+	}
+	return 0;
+}
+
+/**
+ * This function releases periodic Tx FIFO
+ * in shared Tx FIFO mode
+ */
+static void release_perio_tx_fifo(dwc_otg_core_if_t * core_if,
+				  uint32_t fifo_num)
+{
+	core_if->p_tx_msk =
+	    (core_if->p_tx_msk & (1 << (fifo_num - 1))) ^ core_if->p_tx_msk;
+}
+
+/**
+ * This function releases periodic Tx FIFO
+ * in shared Tx FIFO mode
+ */
+static void release_tx_fifo(dwc_otg_core_if_t * core_if, uint32_t fifo_num)
+{
+	core_if->tx_msk =
+	    (core_if->tx_msk & (1 << (fifo_num - 1))) ^ core_if->tx_msk;
+}
+
+/**
+ * This function is being called from gadget
+ * to enable PCD endpoint.
+ */
+int dwc_otg_pcd_ep_enable(dwc_otg_pcd_t * pcd,
+			  const uint8_t * ep_desc, void *usb_ep)
+{
+	int num, dir;
+	dwc_otg_pcd_ep_t *ep = NULL;
+	const usb_endpoint_descriptor_t *desc;
+	dwc_irqflags_t flags;
+	fifosize_data_t dptxfsiz = {.d32 = 0 };
+	gdfifocfg_data_t gdfifocfg = {.d32 = 0 };
+	gdfifocfg_data_t gdfifocfgbase = {.d32 = 0 };
+	int retval = 0;
+	int i, epcount;
+	struct device *dev = dwc_otg_pcd_to_dev(pcd);
+
+	desc = (const usb_endpoint_descriptor_t *)ep_desc;
+
+	if (!desc) {
+		pcd->ep0.priv = usb_ep;
+		ep = &pcd->ep0;
+		retval = -DWC_E_INVALID;
+		goto out;
+	}
+
+	num = UE_GET_ADDR(desc->bEndpointAddress);
+	dir = UE_GET_DIR(desc->bEndpointAddress);
+
+	if (!UGETW(desc->wMaxPacketSize)) {
+		DWC_WARN("bad maxpacketsize\n");
+		retval = -DWC_E_INVALID;
+		goto out;
+	}
+
+	if (dir == UE_DIR_IN) {
+		epcount = pcd->core_if->dev_if->num_in_eps;
+		for (i = 0; i < epcount; i++) {
+			if (num == pcd->in_ep[i].dwc_ep.num) {
+				ep = &pcd->in_ep[i];
+				break;
+			}
+		}
+	} else {
+		epcount = pcd->core_if->dev_if->num_out_eps;
+		for (i = 0; i < epcount; i++) {
+			if (num == pcd->out_ep[i].dwc_ep.num) {
+				ep = &pcd->out_ep[i];
+				break;
+			}
+		}
+	}
+
+	if (!ep) {
+		DWC_WARN("bad address\n");
+		retval = -DWC_E_INVALID;
+		goto out;
+	}
+
+	DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags);
+
+	ep->desc = desc;
+	ep->priv = usb_ep;
+
+	/*
+	 * Activate the EP
+	 */
+	ep->stopped = 0;
+
+	ep->dwc_ep.is_in = (dir == UE_DIR_IN);
+	ep->dwc_ep.maxpacket = UGETW(desc->wMaxPacketSize);
+
+	ep->dwc_ep.type = desc->bmAttributes & UE_XFERTYPE;
+
+	if (ep->dwc_ep.is_in) {
+		if (!GET_CORE_IF(pcd)->en_multiple_tx_fifo) {
+			ep->dwc_ep.tx_fifo_num = 0;
+
+			if (ep->dwc_ep.type == UE_ISOCHRONOUS) {
+				/*
+				 * if ISOC EP then assign a Periodic Tx FIFO.
+				 */
+				ep->dwc_ep.tx_fifo_num =
+				    assign_perio_tx_fifo(GET_CORE_IF(pcd));
+			}
+		} else {
+			/*
+			 * if Dedicated FIFOs mode is on then assign a Tx FIFO.
+			 */
+			ep->dwc_ep.tx_fifo_num =
+			    assign_tx_fifo(GET_CORE_IF(pcd));
+		}
+
+		/* Calculating EP info controller base address */
+		if (ep->dwc_ep.tx_fifo_num
+		    && GET_CORE_IF(pcd)->en_multiple_tx_fifo) {
+			gdfifocfg.d32 =
+			    DWC_READ_REG32(&GET_CORE_IF(pcd)->
+					   core_global_regs->gdfifocfg);
+			gdfifocfgbase.d32 = gdfifocfg.d32 >> 16;
+			dptxfsiz.d32 =
+			    (DWC_READ_REG32
+			     (&GET_CORE_IF(pcd)->core_global_regs->
+			      dtxfsiz[ep->dwc_ep.tx_fifo_num - 1]) >> 16);
+			gdfifocfg.b.epinfobase =
+			    gdfifocfgbase.d32 + dptxfsiz.d32;
+			if (GET_CORE_IF(pcd)->snpsid <= OTG_CORE_REV_2_94a) {
+				DWC_WRITE_REG32(&GET_CORE_IF(pcd)->
+						core_global_regs->gdfifocfg,
+						gdfifocfg.d32);
+			}
+		}
+	}
+	/* Set initial data PID. */
+	if (ep->dwc_ep.type == UE_BULK) {
+		ep->dwc_ep.data_pid_start = 0;
+	}
+
+	/* Alloc DMA Descriptors */
+	if (GET_CORE_IF(pcd)->dma_desc_enable) {
+#ifndef DWC_UTE_PER_IO
+		if (ep->dwc_ep.type != UE_ISOCHRONOUS) {
+#endif
+			ep->dwc_ep.desc_addr =
+			    dwc_otg_ep_alloc_desc_chain(dev,
+						&ep->dwc_ep.dma_desc_addr,
+						MAX_DMA_DESC_CNT);
+			if (!ep->dwc_ep.desc_addr) {
+				DWC_WARN("%s, can't allocate DMA descriptor\n",
+					 __func__);
+				retval = -DWC_E_SHUTDOWN;
+				DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags);
+				goto out;
+			}
+#ifndef DWC_UTE_PER_IO
+		}
+#endif
+	}
+
+	DWC_DEBUGPL(DBG_PCD, "Activate %s: type=%d, mps=%d desc=%p\n",
+		    (ep->dwc_ep.is_in ? "IN" : "OUT"),
+		    ep->dwc_ep.type, ep->dwc_ep.maxpacket, ep->desc);
+#ifdef DWC_UTE_PER_IO
+	ep->dwc_ep.xiso_bInterval = 1 << (ep->desc->bInterval - 1);
+#endif
+	if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) {
+		ep->dwc_ep.bInterval = 1 << (ep->desc->bInterval - 1);
+		ep->dwc_ep.frame_num = 0xFFFFFFFF;
+	}
+
+	dwc_otg_ep_activate(GET_CORE_IF(pcd), &ep->dwc_ep);
+
+#ifdef DWC_UTE_CFI
+	if (pcd->cfi->ops.ep_enable) {
+		pcd->cfi->ops.ep_enable(pcd->cfi, pcd, ep);
+	}
+#endif
+
+	DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags);
+
+out:
+	return retval;
+}
+
+/**
+ * This function is being called from gadget
+ * to disable PCD endpoint.
+ */
+int dwc_otg_pcd_ep_disable(dwc_otg_pcd_t * pcd, void *ep_handle)
+{
+	dwc_otg_pcd_ep_t *ep;
+	dwc_irqflags_t flags;
+	dwc_otg_dev_dma_desc_t *desc_addr;
+	dwc_dma_t dma_desc_addr;
+	gdfifocfg_data_t gdfifocfgbase = {.d32 = 0 };
+	gdfifocfg_data_t gdfifocfg = {.d32 = 0 };
+	fifosize_data_t dptxfsiz = {.d32 = 0 };
+	struct device *dev = dwc_otg_pcd_to_dev(pcd);
+
+	ep = get_ep_from_handle(pcd, ep_handle);
+
+	if (!ep || !ep->desc) {
+		DWC_DEBUGPL(DBG_PCD, "bad ep address\n");
+		return -DWC_E_INVALID;
+	}
+
+	DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags);
+
+	dwc_otg_request_nuke(ep);
+
+	dwc_otg_ep_deactivate(GET_CORE_IF(pcd), &ep->dwc_ep);
+	if (pcd->core_if->core_params->dev_out_nak) {
+		DWC_TIMER_CANCEL(pcd->core_if->ep_xfer_timer[ep->dwc_ep.num]);
+		pcd->core_if->ep_xfer_info[ep->dwc_ep.num].state = 0;
+	}
+	ep->desc = NULL;
+	ep->stopped = 1;
+
+	gdfifocfg.d32 =
+	    DWC_READ_REG32(&GET_CORE_IF(pcd)->core_global_regs->gdfifocfg);
+	gdfifocfgbase.d32 = gdfifocfg.d32 >> 16;
+
+	if (ep->dwc_ep.is_in) {
+		if (GET_CORE_IF(pcd)->en_multiple_tx_fifo) {
+			/* Flush the Tx FIFO */
+			dwc_otg_flush_tx_fifo(GET_CORE_IF(pcd),
+					      ep->dwc_ep.tx_fifo_num);
+		}
+		release_perio_tx_fifo(GET_CORE_IF(pcd), ep->dwc_ep.tx_fifo_num);
+		release_tx_fifo(GET_CORE_IF(pcd), ep->dwc_ep.tx_fifo_num);
+		if (GET_CORE_IF(pcd)->en_multiple_tx_fifo) {
+			/* Decreasing EPinfo Base Addr */
+			dptxfsiz.d32 =
+			    (DWC_READ_REG32
+			     (&GET_CORE_IF(pcd)->
+				core_global_regs->dtxfsiz[ep->dwc_ep.tx_fifo_num-1]) >> 16);
+			gdfifocfg.b.epinfobase = gdfifocfgbase.d32 - dptxfsiz.d32;
+			if (GET_CORE_IF(pcd)->snpsid <= OTG_CORE_REV_2_94a) {
+				DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gdfifocfg,
+					gdfifocfg.d32);
+			}
+		}
+	}
+
+	/* Free DMA Descriptors */
+	if (GET_CORE_IF(pcd)->dma_desc_enable) {
+		if (ep->dwc_ep.type != UE_ISOCHRONOUS) {
+			desc_addr = ep->dwc_ep.desc_addr;
+			dma_desc_addr = ep->dwc_ep.dma_desc_addr;
+
+			/* Cannot call dma_free_coherent() with IRQs disabled */
+			DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags);
+			dwc_otg_ep_free_desc_chain(dev, desc_addr, dma_desc_addr,
+						   MAX_DMA_DESC_CNT);
+
+			goto out_unlocked;
+		}
+	}
+	DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags);
+
+out_unlocked:
+	DWC_DEBUGPL(DBG_PCD, "%d %s disabled\n", ep->dwc_ep.num,
+		    ep->dwc_ep.is_in ? "IN" : "OUT");
+	return 0;
+
+}
+
+/******************************************************************************/
+#ifdef DWC_UTE_PER_IO
+
+/**
+ * Free the request and its extended parts
+ *
+ */
+void dwc_pcd_xiso_ereq_free(dwc_otg_pcd_ep_t * ep, dwc_otg_pcd_request_t * req)
+{
+	DWC_FREE(req->ext_req.per_io_frame_descs);
+	DWC_FREE(req);
+}
+
+/**
+ * Start the next request in the endpoint's queue.
+ *
+ */
+int dwc_otg_pcd_xiso_start_next_request(dwc_otg_pcd_t * pcd,
+					dwc_otg_pcd_ep_t * ep)
+{
+	int i;
+	dwc_otg_pcd_request_t *req = NULL;
+	dwc_ep_t *dwcep = NULL;
+	struct dwc_iso_xreq_port *ereq = NULL;
+	struct dwc_iso_pkt_desc_port *ddesc_iso;
+	uint16_t nat;
+	depctl_data_t diepctl;
+
+	dwcep = &ep->dwc_ep;
+
+	if (dwcep->xiso_active_xfers > 0) {
+#if 0	//Disable this to decrease s/w overhead that is crucial for Isoc transfers
+		DWC_WARN("There are currently active transfers for EP%d \
+				(active=%d; queued=%d)", dwcep->num, dwcep->xiso_active_xfers,
+				dwcep->xiso_queued_xfers);
+#endif
+		return 0;
+	}
+
+	nat = UGETW(ep->desc->wMaxPacketSize);
+	nat = (nat >> 11) & 0x03;
+
+	if (!DWC_CIRCLEQ_EMPTY(&ep->queue)) {
+		req = DWC_CIRCLEQ_FIRST(&ep->queue);
+		ereq = &req->ext_req;
+		ep->stopped = 0;
+
+		/* Get the frame number */
+		dwcep->xiso_frame_num =
+		    dwc_otg_get_frame_number(GET_CORE_IF(pcd));
+		DWC_DEBUG("FRM_NUM=%d", dwcep->xiso_frame_num);
+
+		ddesc_iso = ereq->per_io_frame_descs;
+
+		if (dwcep->is_in) {
+			/* Setup DMA Descriptor chain for IN Isoc request */
+			for (i = 0; i < ereq->pio_pkt_count; i++) {
+				//if ((i % (nat + 1)) == 0)
+				if ( i > 0 )
+					dwcep->xiso_frame_num =
+					    (dwcep->xiso_bInterval +
+										dwcep->xiso_frame_num) & 0x3FFF;
+				dwcep->desc_addr[i].buf =
+				    req->dma + ddesc_iso[i].offset;
+				dwcep->desc_addr[i].status.b_iso_in.txbytes =
+				    ddesc_iso[i].length;
+				dwcep->desc_addr[i].status.b_iso_in.framenum =
+				    dwcep->xiso_frame_num;
+				dwcep->desc_addr[i].status.b_iso_in.bs =
+				    BS_HOST_READY;
+				dwcep->desc_addr[i].status.b_iso_in.txsts = 0;
+				dwcep->desc_addr[i].status.b_iso_in.sp =
+				    (ddesc_iso[i].length %
+				     dwcep->maxpacket) ? 1 : 0;
+				dwcep->desc_addr[i].status.b_iso_in.ioc = 0;
+				dwcep->desc_addr[i].status.b_iso_in.pid = nat + 1;
+				dwcep->desc_addr[i].status.b_iso_in.l = 0;
+
+				/* Process the last descriptor */
+				if (i == ereq->pio_pkt_count - 1) {
+					dwcep->desc_addr[i].status.b_iso_in.ioc = 1;
+					dwcep->desc_addr[i].status.b_iso_in.l = 1;
+				}
+			}
+
+			/* Setup and start the transfer for this endpoint */
+			dwcep->xiso_active_xfers++;
+			DWC_WRITE_REG32(&GET_CORE_IF(pcd)->dev_if->
+					in_ep_regs[dwcep->num]->diepdma,
+					dwcep->dma_desc_addr);
+			diepctl.d32 = 0;
+			diepctl.b.epena = 1;
+			diepctl.b.cnak = 1;
+			DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->dev_if->
+					 in_ep_regs[dwcep->num]->diepctl, 0,
+					 diepctl.d32);
+		} else {
+			/* Setup DMA Descriptor chain for OUT Isoc request */
+			for (i = 0; i < ereq->pio_pkt_count; i++) {
+				//if ((i % (nat + 1)) == 0)
+				dwcep->xiso_frame_num = (dwcep->xiso_bInterval +
+										dwcep->xiso_frame_num) & 0x3FFF;
+				dwcep->desc_addr[i].buf =
+				    req->dma + ddesc_iso[i].offset;
+				dwcep->desc_addr[i].status.b_iso_out.rxbytes =
+				    ddesc_iso[i].length;
+				dwcep->desc_addr[i].status.b_iso_out.framenum =
+				    dwcep->xiso_frame_num;
+				dwcep->desc_addr[i].status.b_iso_out.bs =
+				    BS_HOST_READY;
+				dwcep->desc_addr[i].status.b_iso_out.rxsts = 0;
+				dwcep->desc_addr[i].status.b_iso_out.sp =
+				    (ddesc_iso[i].length %
+				     dwcep->maxpacket) ? 1 : 0;
+				dwcep->desc_addr[i].status.b_iso_out.ioc = 0;
+				dwcep->desc_addr[i].status.b_iso_out.pid = nat + 1;
+				dwcep->desc_addr[i].status.b_iso_out.l = 0;
+
+				/* Process the last descriptor */
+				if (i == ereq->pio_pkt_count - 1) {
+					dwcep->desc_addr[i].status.b_iso_out.ioc = 1;
+					dwcep->desc_addr[i].status.b_iso_out.l = 1;
+				}
+			}
+
+			/* Setup and start the transfer for this endpoint */
+			dwcep->xiso_active_xfers++;
+			DWC_WRITE_REG32(&GET_CORE_IF(pcd)->
+					dev_if->out_ep_regs[dwcep->num]->
+					doepdma, dwcep->dma_desc_addr);
+			diepctl.d32 = 0;
+			diepctl.b.epena = 1;
+			diepctl.b.cnak = 1;
+			DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->
+					 dev_if->out_ep_regs[dwcep->num]->
+					 doepctl, 0, diepctl.d32);
+		}
+
+	} else {
+		ep->stopped = 1;
+	}
+
+	return 0;
+}
+
+/**
+ *	- Remove the request from the queue
+ */
+void complete_xiso_ep(dwc_otg_pcd_ep_t * ep)
+{
+	dwc_otg_pcd_request_t *req = NULL;
+	struct dwc_iso_xreq_port *ereq = NULL;
+	struct dwc_iso_pkt_desc_port *ddesc_iso = NULL;
+	dwc_ep_t *dwcep = NULL;
+	int i;
+
+	//DWC_DEBUG();
+	dwcep = &ep->dwc_ep;
+
+	/* Get the first pending request from the queue */
+	if (!DWC_CIRCLEQ_EMPTY(&ep->queue)) {
+		req = DWC_CIRCLEQ_FIRST(&ep->queue);
+		if (!req) {
+			DWC_PRINTF("complete_ep 0x%p, req = NULL!\n", ep);
+			return;
+		}
+		dwcep->xiso_active_xfers--;
+		dwcep->xiso_queued_xfers--;
+		/* Remove this request from the queue */
+		DWC_CIRCLEQ_REMOVE_INIT(&ep->queue, req, queue_entry);
+	} else {
+		DWC_PRINTF("complete_ep 0x%p, ep->queue empty!\n", ep);
+		return;
+	}
+
+	ep->stopped = 1;
+	ereq = &req->ext_req;
+	ddesc_iso = ereq->per_io_frame_descs;
+
+	if (dwcep->xiso_active_xfers < 0) {
+		DWC_WARN("EP#%d (xiso_active_xfers=%d)", dwcep->num,
+			 dwcep->xiso_active_xfers);
+	}
+
+	/* Fill the Isoc descs of portable extended req from dma descriptors */
+	for (i = 0; i < ereq->pio_pkt_count; i++) {
+		if (dwcep->is_in) {	/* IN endpoints */
+			ddesc_iso[i].actual_length = ddesc_iso[i].length -
+			    dwcep->desc_addr[i].status.b_iso_in.txbytes;
+			ddesc_iso[i].status =
+			    dwcep->desc_addr[i].status.b_iso_in.txsts;
+		} else {	/* OUT endpoints */
+			ddesc_iso[i].actual_length = ddesc_iso[i].length -
+			    dwcep->desc_addr[i].status.b_iso_out.rxbytes;
+			ddesc_iso[i].status =
+			    dwcep->desc_addr[i].status.b_iso_out.rxsts;
+		}
+	}
+
+	DWC_SPINUNLOCK(ep->pcd->lock);
+
+	/* Call the completion function in the non-portable logic */
+	ep->pcd->fops->xisoc_complete(ep->pcd, ep->priv, req->priv, 0,
+				      &req->ext_req);
+
+	DWC_SPINLOCK(ep->pcd->lock);
+
+	/* Free the request - specific freeing needed for extended request object */
+	dwc_pcd_xiso_ereq_free(ep, req);
+
+	/* Start the next request */
+	dwc_otg_pcd_xiso_start_next_request(ep->pcd, ep);
+
+	return;
+}
+
+/**
+ * Create and initialize the Isoc pkt descriptors of the extended request.
+ *
+ */
+static int dwc_otg_pcd_xiso_create_pkt_descs(dwc_otg_pcd_request_t * req,
+					     void *ereq_nonport,
+					     int atomic_alloc)
+{
+	struct dwc_iso_xreq_port *ereq = NULL;
+	struct dwc_iso_xreq_port *req_mapped = NULL;
+	struct dwc_iso_pkt_desc_port *ipds = NULL;	/* To be created in this function */
+	uint32_t pkt_count;
+	int i;
+
+	ereq = &req->ext_req;
+	req_mapped = (struct dwc_iso_xreq_port *)ereq_nonport;
+	pkt_count = req_mapped->pio_pkt_count;
+
+	/* Create the isoc descs */
+	if (atomic_alloc) {
+		ipds = DWC_ALLOC_ATOMIC(sizeof(*ipds) * pkt_count);
+	} else {
+		ipds = DWC_ALLOC(sizeof(*ipds) * pkt_count);
+	}
+
+	if (!ipds) {
+		DWC_ERROR("Failed to allocate isoc descriptors");
+		return -DWC_E_NO_MEMORY;
+	}
+
+	/* Initialize the extended request fields */
+	ereq->per_io_frame_descs = ipds;
+	ereq->error_count = 0;
+	ereq->pio_alloc_pkt_count = pkt_count;
+	ereq->pio_pkt_count = pkt_count;
+	ereq->tr_sub_flags = req_mapped->tr_sub_flags;
+
+	/* Init the Isoc descriptors */
+	for (i = 0; i < pkt_count; i++) {
+		ipds[i].length = req_mapped->per_io_frame_descs[i].length;
+		ipds[i].offset = req_mapped->per_io_frame_descs[i].offset;
+		ipds[i].status = req_mapped->per_io_frame_descs[i].status;	/* 0 */
+		ipds[i].actual_length =
+		    req_mapped->per_io_frame_descs[i].actual_length;
+	}
+
+	return 0;
+}
+
+static void prn_ext_request(struct dwc_iso_xreq_port *ereq)
+{
+	struct dwc_iso_pkt_desc_port *xfd = NULL;
+	int i;
+
+	DWC_DEBUG("per_io_frame_descs=%p", ereq->per_io_frame_descs);
+	DWC_DEBUG("tr_sub_flags=%d", ereq->tr_sub_flags);
+	DWC_DEBUG("error_count=%d", ereq->error_count);
+	DWC_DEBUG("pio_alloc_pkt_count=%d", ereq->pio_alloc_pkt_count);
+	DWC_DEBUG("pio_pkt_count=%d", ereq->pio_pkt_count);
+	DWC_DEBUG("res=%d", ereq->res);
+
+	for (i = 0; i < ereq->pio_pkt_count; i++) {
+		xfd = &ereq->per_io_frame_descs[0];
+		DWC_DEBUG("FD #%d", i);
+
+		DWC_DEBUG("xfd->actual_length=%d", xfd->actual_length);
+		DWC_DEBUG("xfd->length=%d", xfd->length);
+		DWC_DEBUG("xfd->offset=%d", xfd->offset);
+		DWC_DEBUG("xfd->status=%d", xfd->status);
+	}
+}
+
+/**
+ *
+ */
+int dwc_otg_pcd_xiso_ep_queue(dwc_otg_pcd_t * pcd, void *ep_handle,
+			      uint8_t * buf, dwc_dma_t dma_buf, uint32_t buflen,
+			      int zero, void *req_handle, int atomic_alloc,
+			      void *ereq_nonport)
+{
+	dwc_otg_pcd_request_t *req = NULL;
+	dwc_otg_pcd_ep_t *ep;
+	dwc_irqflags_t flags;
+	int res;
+
+	ep = get_ep_from_handle(pcd, ep_handle);
+	if (!ep) {
+		DWC_WARN("bad ep\n");
+		return -DWC_E_INVALID;
+	}
+
+	/* We support this extension only for DDMA mode */
+	if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC)
+		if (!GET_CORE_IF(pcd)->dma_desc_enable)
+			return -DWC_E_INVALID;
+
+	/* Create a dwc_otg_pcd_request_t object */
+	if (atomic_alloc) {
+		req = DWC_ALLOC_ATOMIC(sizeof(*req));
+	} else {
+		req = DWC_ALLOC(sizeof(*req));
+	}
+
+	if (!req) {
+		return -DWC_E_NO_MEMORY;
+	}
+
+	/* Create the Isoc descs for this request which shall be the exact match
+	 * of the structure sent to us from the non-portable logic */
+	res =
+	    dwc_otg_pcd_xiso_create_pkt_descs(req, ereq_nonport, atomic_alloc);
+	if (res) {
+		DWC_WARN("Failed to init the Isoc descriptors");
+		DWC_FREE(req);
+		return res;
+	}
+
+	DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags);
+
+	DWC_CIRCLEQ_INIT_ENTRY(req, queue_entry);
+	req->buf = buf;
+	req->dma = dma_buf;
+	req->length = buflen;
+	req->sent_zlp = zero;
+	req->priv = req_handle;
+
+	//DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags);
+	ep->dwc_ep.dma_addr = dma_buf;
+	ep->dwc_ep.start_xfer_buff = buf;
+	ep->dwc_ep.xfer_buff = buf;
+	ep->dwc_ep.xfer_len = 0;
+	ep->dwc_ep.xfer_count = 0;
+	ep->dwc_ep.sent_zlp = 0;
+	ep->dwc_ep.total_len = buflen;
+
+	/* Add this request to the tail */
+	DWC_CIRCLEQ_INSERT_TAIL(&ep->queue, req, queue_entry);
+	ep->dwc_ep.xiso_queued_xfers++;
+
+//DWC_DEBUG("CP_0");
+//DWC_DEBUG("req->ext_req.tr_sub_flags=%d", req->ext_req.tr_sub_flags);
+//prn_ext_request((struct dwc_iso_xreq_port *) ereq_nonport);
+//prn_ext_request(&req->ext_req);
+
+	//DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags);
+
+	/* If the req->status == ASAP  then check if there is any active transfer
+	 * for this endpoint. If no active transfers, then get the first entry
+	 * from the queue and start that transfer
+	 */
+	if (req->ext_req.tr_sub_flags == DWC_EREQ_TF_ASAP) {
+		res = dwc_otg_pcd_xiso_start_next_request(pcd, ep);
+		if (res) {
+			DWC_WARN("Failed to start the next Isoc transfer");
+			DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags);
+			DWC_FREE(req);
+			return res;
+		}
+	}
+
+	DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags);
+	return 0;
+}
+
+#endif
+/* END ifdef DWC_UTE_PER_IO ***************************************************/
+int dwc_otg_pcd_ep_queue(dwc_otg_pcd_t * pcd, void *ep_handle,
+			 uint8_t * buf, dwc_dma_t dma_buf, uint32_t buflen,
+			 int zero, void *req_handle, int atomic_alloc)
+{
+	struct device *dev = dwc_otg_pcd_to_dev(pcd);
+	dwc_irqflags_t flags;
+	dwc_otg_pcd_request_t *req;
+	dwc_otg_pcd_ep_t *ep;
+	uint32_t max_transfer;
+
+	ep = get_ep_from_handle(pcd, ep_handle);
+	if (!ep || (!ep->desc && ep->dwc_ep.num != 0)) {
+		DWC_WARN("bad ep\n");
+		return -DWC_E_INVALID;
+	}
+
+	if (atomic_alloc) {
+		req = DWC_ALLOC_ATOMIC(sizeof(*req));
+	} else {
+		req = DWC_ALLOC(sizeof(*req));
+	}
+
+	if (!req) {
+		return -DWC_E_NO_MEMORY;
+	}
+	DWC_CIRCLEQ_INIT_ENTRY(req, queue_entry);
+	if (!GET_CORE_IF(pcd)->core_params->opt) {
+		if (ep->dwc_ep.num != 0) {
+			DWC_ERROR("queue req %p, len %d buf %p\n",
+				  req_handle, buflen, buf);
+		}
+	}
+
+	req->buf = buf;
+	req->dma = dma_buf;
+	req->length = buflen;
+	req->sent_zlp = zero;
+	req->priv = req_handle;
+	req->dw_align_buf = NULL;
+	if ((dma_buf & 0x3) && GET_CORE_IF(pcd)->dma_enable
+			&& !GET_CORE_IF(pcd)->dma_desc_enable)
+		req->dw_align_buf = DWC_DMA_ALLOC(dev, buflen,
+				 &req->dw_align_buf_dma);
+	DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags);
+
+	/*
+	 * After adding request to the queue for IN ISOC wait for In Token Received
+	 * when TX FIFO is empty interrupt and for OUT ISOC wait for OUT Token
+	 * Received when EP is disabled interrupt to obtain starting microframe
+	 * (odd/even) start transfer
+	 */
+	if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) {
+		if (req != 0) {
+			depctl_data_t depctl = {.d32 =
+				    DWC_READ_REG32(&pcd->core_if->dev_if->
+						   in_ep_regs[ep->dwc_ep.num]->
+						   diepctl) };
+			++pcd->request_pending;
+
+			DWC_CIRCLEQ_INSERT_TAIL(&ep->queue, req, queue_entry);
+			if (ep->dwc_ep.is_in) {
+				depctl.b.cnak = 1;
+				DWC_WRITE_REG32(&pcd->core_if->dev_if->
+						in_ep_regs[ep->dwc_ep.num]->
+						diepctl, depctl.d32);
+			}
+
+			DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags);
+		}
+		return 0;
+	}
+
+	/*
+	 * For EP0 IN without premature status, zlp is required?
+	 */
+	if (ep->dwc_ep.num == 0 && ep->dwc_ep.is_in) {
+		DWC_DEBUGPL(DBG_PCDV, "%d-OUT ZLP\n", ep->dwc_ep.num);
+		//_req->zero = 1;
+	}
+
+	/* Start the transfer */
+	if (DWC_CIRCLEQ_EMPTY(&ep->queue) && !ep->stopped) {
+		/* EP0 Transfer? */
+		if (ep->dwc_ep.num == 0) {
+			switch (pcd->ep0state) {
+			case EP0_IN_DATA_PHASE:
+				DWC_DEBUGPL(DBG_PCD,
+					    "%s ep0: EP0_IN_DATA_PHASE\n",
+					    __func__);
+				break;
+
+			case EP0_OUT_DATA_PHASE:
+				DWC_DEBUGPL(DBG_PCD,
+					    "%s ep0: EP0_OUT_DATA_PHASE\n",
+					    __func__);
+				if (pcd->request_config) {
+					/* Complete STATUS PHASE */
+					ep->dwc_ep.is_in = 1;
+					pcd->ep0state = EP0_IN_STATUS_PHASE;
+				}
+				break;
+
+			case EP0_IN_STATUS_PHASE:
+				DWC_DEBUGPL(DBG_PCD,
+					    "%s ep0: EP0_IN_STATUS_PHASE\n",
+					    __func__);
+				break;
+
+			default:
+				DWC_DEBUGPL(DBG_ANY, "ep0: odd state %d\n",
+					    pcd->ep0state);
+				DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags);
+				return -DWC_E_SHUTDOWN;
+			}
+
+			ep->dwc_ep.dma_addr = dma_buf;
+			ep->dwc_ep.start_xfer_buff = buf;
+			ep->dwc_ep.xfer_buff = buf;
+			ep->dwc_ep.xfer_len = buflen;
+			ep->dwc_ep.xfer_count = 0;
+			ep->dwc_ep.sent_zlp = 0;
+			ep->dwc_ep.total_len = ep->dwc_ep.xfer_len;
+
+			if (zero) {
+				if ((ep->dwc_ep.xfer_len %
+				     ep->dwc_ep.maxpacket == 0)
+				    && (ep->dwc_ep.xfer_len != 0)) {
+					ep->dwc_ep.sent_zlp = 1;
+				}
+
+			}
+
+			dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd),
+						   &ep->dwc_ep);
+		}		// non-ep0 endpoints
+		else {
+#ifdef DWC_UTE_CFI
+			if (ep->dwc_ep.buff_mode != BM_STANDARD) {
+				/* store the request length */
+				ep->dwc_ep.cfi_req_len = buflen;
+				pcd->cfi->ops.build_descriptors(pcd->cfi, pcd,
+								ep, req);
+			} else {
+#endif
+				max_transfer =
+				    GET_CORE_IF(ep->pcd)->core_params->
+				    max_transfer_size;
+
+				/* Setup and start the Transfer */
+				if (req->dw_align_buf){
+					if (ep->dwc_ep.is_in)
+						dwc_memcpy(req->dw_align_buf,
+							   buf, buflen);
+					ep->dwc_ep.dma_addr =
+					    req->dw_align_buf_dma;
+					ep->dwc_ep.start_xfer_buff =
+					    req->dw_align_buf;
+					ep->dwc_ep.xfer_buff =
+					    req->dw_align_buf;
+				} else {
+					ep->dwc_ep.dma_addr = dma_buf;
+					ep->dwc_ep.start_xfer_buff = buf;
+                                        ep->dwc_ep.xfer_buff = buf;
+				}
+				ep->dwc_ep.xfer_len = 0;
+				ep->dwc_ep.xfer_count = 0;
+				ep->dwc_ep.sent_zlp = 0;
+				ep->dwc_ep.total_len = buflen;
+
+				ep->dwc_ep.maxxfer = max_transfer;
+				if (GET_CORE_IF(pcd)->dma_desc_enable) {
+					uint32_t out_max_xfer =
+					    DDMA_MAX_TRANSFER_SIZE -
+					    (DDMA_MAX_TRANSFER_SIZE % 4);
+					if (ep->dwc_ep.is_in) {
+						if (ep->dwc_ep.maxxfer >
+						    DDMA_MAX_TRANSFER_SIZE) {
+							ep->dwc_ep.maxxfer =
+							    DDMA_MAX_TRANSFER_SIZE;
+						}
+					} else {
+						if (ep->dwc_ep.maxxfer >
+						    out_max_xfer) {
+							ep->dwc_ep.maxxfer =
+							    out_max_xfer;
+						}
+					}
+				}
+				if (ep->dwc_ep.maxxfer < ep->dwc_ep.total_len) {
+					ep->dwc_ep.maxxfer -=
+					    (ep->dwc_ep.maxxfer %
+					     ep->dwc_ep.maxpacket);
+				}
+
+				if (zero) {
+					if ((ep->dwc_ep.total_len %
+					     ep->dwc_ep.maxpacket == 0)
+					    && (ep->dwc_ep.total_len != 0)) {
+						ep->dwc_ep.sent_zlp = 1;
+					}
+				}
+#ifdef DWC_UTE_CFI
+			}
+#endif
+			dwc_otg_ep_start_transfer(GET_CORE_IF(pcd),
+						  &ep->dwc_ep);
+		}
+	}
+
+	if (req != 0) {
+		++pcd->request_pending;
+		DWC_CIRCLEQ_INSERT_TAIL(&ep->queue, req, queue_entry);
+		if (ep->dwc_ep.is_in && ep->stopped
+		    && !(GET_CORE_IF(pcd)->dma_enable)) {
+			/** @todo NGS Create a function for this. */
+			diepmsk_data_t diepmsk = {.d32 = 0 };
+			diepmsk.b.intktxfemp = 1;
+			if (GET_CORE_IF(pcd)->multiproc_int_enable) {
+				DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->
+						 dev_if->dev_global_regs->diepeachintmsk
+						 [ep->dwc_ep.num], 0,
+						 diepmsk.d32);
+			} else {
+				DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->
+						 dev_if->dev_global_regs->
+						 diepmsk, 0, diepmsk.d32);
+			}
+
+		}
+	}
+	DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags);
+
+	return 0;
+}
+
+int dwc_otg_pcd_ep_dequeue(dwc_otg_pcd_t * pcd, void *ep_handle,
+			   void *req_handle)
+{
+	dwc_irqflags_t flags;
+	dwc_otg_pcd_request_t *req;
+	dwc_otg_pcd_ep_t *ep;
+
+	ep = get_ep_from_handle(pcd, ep_handle);
+	if (!ep || (!ep->desc && ep->dwc_ep.num != 0)) {
+		DWC_WARN("bad argument\n");
+		return -DWC_E_INVALID;
+	}
+
+	DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags);
+
+	/* make sure it's actually queued on this endpoint */
+	DWC_CIRCLEQ_FOREACH(req, &ep->queue, queue_entry) {
+		if (req->priv == (void *)req_handle) {
+			break;
+		}
+	}
+
+	if (req->priv != (void *)req_handle) {
+		DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags);
+		return -DWC_E_INVALID;
+	}
+
+	if (!DWC_CIRCLEQ_EMPTY_ENTRY(req, queue_entry)) {
+		dwc_otg_request_done(ep, req, -DWC_E_RESTART);
+	} else {
+		req = NULL;
+	}
+
+	DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags);
+
+	return req ? 0 : -DWC_E_SHUTDOWN;
+
+}
+
+/**
+ * dwc_otg_pcd_ep_wedge - sets the halt feature and ignores clear requests
+ *
+ * Use this to stall an endpoint and ignore CLEAR_FEATURE(HALT_ENDPOINT)
+ * requests. If the gadget driver clears the halt status, it will
+ * automatically unwedge the endpoint.
+ *
+ * Returns zero on success, else negative DWC error code.
+ */
+int dwc_otg_pcd_ep_wedge(dwc_otg_pcd_t * pcd, void *ep_handle)
+{
+	dwc_otg_pcd_ep_t *ep;
+	dwc_irqflags_t flags;
+	int retval = 0;
+
+	ep = get_ep_from_handle(pcd, ep_handle);
+
+	if ((!ep->desc && ep != &pcd->ep0) ||
+	    (ep->desc && (ep->desc->bmAttributes == UE_ISOCHRONOUS))) {
+		DWC_WARN("%s, bad ep\n", __func__);
+		return -DWC_E_INVALID;
+	}
+
+	DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags);
+	if (!DWC_CIRCLEQ_EMPTY(&ep->queue)) {
+		DWC_WARN("%d %s XFer In process\n", ep->dwc_ep.num,
+			 ep->dwc_ep.is_in ? "IN" : "OUT");
+		retval = -DWC_E_AGAIN;
+	} else {
+                /* This code needs to be reviewed */
+		if (ep->dwc_ep.is_in == 1 && GET_CORE_IF(pcd)->dma_desc_enable) {
+			dtxfsts_data_t txstatus;
+			fifosize_data_t txfifosize;
+
+			txfifosize.d32 =
+			    DWC_READ_REG32(&GET_CORE_IF(pcd)->
+					   core_global_regs->dtxfsiz[ep->dwc_ep.
+								     tx_fifo_num]);
+			txstatus.d32 =
+			    DWC_READ_REG32(&GET_CORE_IF(pcd)->
+					   dev_if->in_ep_regs[ep->dwc_ep.num]->
+					   dtxfsts);
+
+			if (txstatus.b.txfspcavail < txfifosize.b.depth) {
+				DWC_WARN("%s() Data In Tx Fifo\n", __func__);
+				retval = -DWC_E_AGAIN;
+			} else {
+				if (ep->dwc_ep.num == 0) {
+					pcd->ep0state = EP0_STALL;
+				}
+
+				ep->stopped = 1;
+				dwc_otg_ep_set_stall(GET_CORE_IF(pcd),
+						     &ep->dwc_ep);
+			}
+		} else {
+			if (ep->dwc_ep.num == 0) {
+				pcd->ep0state = EP0_STALL;
+			}
+
+			ep->stopped = 1;
+			dwc_otg_ep_set_stall(GET_CORE_IF(pcd), &ep->dwc_ep);
+		}
+	}
+
+	DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags);
+
+	return retval;
+}
+
+int dwc_otg_pcd_ep_halt(dwc_otg_pcd_t * pcd, void *ep_handle, int value)
+{
+	dwc_otg_pcd_ep_t *ep;
+	dwc_irqflags_t flags;
+	int retval = 0;
+
+	ep = get_ep_from_handle(pcd, ep_handle);
+
+	if (!ep || (!ep->desc && ep != &pcd->ep0) ||
+	    (ep->desc && (ep->desc->bmAttributes == UE_ISOCHRONOUS))) {
+		DWC_WARN("%s, bad ep\n", __func__);
+		return -DWC_E_INVALID;
+	}
+
+	DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags);
+	if (!DWC_CIRCLEQ_EMPTY(&ep->queue)) {
+		DWC_WARN("%d %s XFer In process\n", ep->dwc_ep.num,
+			 ep->dwc_ep.is_in ? "IN" : "OUT");
+		retval = -DWC_E_AGAIN;
+	} else if (value == 0) {
+		dwc_otg_ep_clear_stall(GET_CORE_IF(pcd), &ep->dwc_ep);
+	} else if (value == 1) {
+		if (ep->dwc_ep.is_in == 1 && GET_CORE_IF(pcd)->dma_desc_enable) {
+			dtxfsts_data_t txstatus;
+			fifosize_data_t txfifosize;
+
+			txfifosize.d32 =
+			    DWC_READ_REG32(&GET_CORE_IF(pcd)->core_global_regs->
+					   dtxfsiz[ep->dwc_ep.tx_fifo_num]);
+			txstatus.d32 =
+			    DWC_READ_REG32(&GET_CORE_IF(pcd)->dev_if->
+					   in_ep_regs[ep->dwc_ep.num]->dtxfsts);
+
+			if (txstatus.b.txfspcavail < txfifosize.b.depth) {
+				DWC_WARN("%s() Data In Tx Fifo\n", __func__);
+				retval = -DWC_E_AGAIN;
+			} else {
+				if (ep->dwc_ep.num == 0) {
+					pcd->ep0state = EP0_STALL;
+				}
+
+				ep->stopped = 1;
+				dwc_otg_ep_set_stall(GET_CORE_IF(pcd),
+						     &ep->dwc_ep);
+			}
+		} else {
+			if (ep->dwc_ep.num == 0) {
+				pcd->ep0state = EP0_STALL;
+			}
+
+			ep->stopped = 1;
+			dwc_otg_ep_set_stall(GET_CORE_IF(pcd), &ep->dwc_ep);
+		}
+	} else if (value == 2) {
+		ep->dwc_ep.stall_clear_flag = 0;
+	} else if (value == 3) {
+		ep->dwc_ep.stall_clear_flag = 1;
+	}
+
+	DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags);
+
+	return retval;
+}
+
+/**
+ * This function initiates remote wakeup of the host from suspend state.
+ */
+static void dwc_otg_pcd_rem_wkup_from_suspend(dwc_otg_pcd_t * pcd, int set)
+{
+	dctl_data_t dctl = { 0 };
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+	dsts_data_t dsts;
+
+	dsts.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts);
+	if (!dsts.b.suspsts) {
+		DWC_WARN("Remote wakeup while is not in suspend state\n");
+	}
+	/* Check if DEVICE_REMOTE_WAKEUP feature enabled */
+	if (pcd->remote_wakeup_enable) {
+		if (set) {
+
+			if (core_if->adp_enable) {
+				gpwrdn_data_t gpwrdn;
+
+				dwc_otg_adp_probe_stop(core_if);
+
+				/* Mask SRP detected interrupt from Power Down Logic */
+				gpwrdn.d32 = 0;
+				gpwrdn.b.srp_det_msk = 1;
+				DWC_MODIFY_REG32(&core_if->
+						 core_global_regs->gpwrdn,
+						 gpwrdn.d32, 0);
+
+				/* Disable Power Down Logic */
+				gpwrdn.d32 = 0;
+				gpwrdn.b.pmuactv = 1;
+				DWC_MODIFY_REG32(&core_if->
+						 core_global_regs->gpwrdn,
+						 gpwrdn.d32, 0);
+
+				/*
+				 * Initialize the Core for Device mode.
+				 */
+				core_if->op_state = B_PERIPHERAL;
+				dwc_otg_core_init(core_if);
+				dwc_otg_enable_global_interrupts(core_if);
+				cil_pcd_start(core_if);
+
+				dwc_otg_initiate_srp(core_if);
+			}
+
+			dctl.b.rmtwkupsig = 1;
+			DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->
+					 dctl, 0, dctl.d32);
+			DWC_DEBUGPL(DBG_PCD, "Set Remote Wakeup\n");
+
+			dwc_mdelay(2);
+			DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->
+					 dctl, dctl.d32, 0);
+			DWC_DEBUGPL(DBG_PCD, "Clear Remote Wakeup\n");
+		}
+	} else {
+		DWC_DEBUGPL(DBG_PCD, "Remote Wakeup is disabled\n");
+	}
+}
+
+#ifdef CONFIG_USB_DWC_OTG_LPM
+/**
+ * This function initiates remote wakeup of the host from L1 sleep state.
+ */
+void dwc_otg_pcd_rem_wkup_from_sleep(dwc_otg_pcd_t * pcd, int set)
+{
+	glpmcfg_data_t lpmcfg;
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+
+	lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg);
+
+	/* Check if we are in L1 state */
+	if (!lpmcfg.b.prt_sleep_sts) {
+		DWC_DEBUGPL(DBG_PCD, "Device is not in sleep state\n");
+		return;
+	}
+
+	/* Check if host allows remote wakeup */
+	if (!lpmcfg.b.rem_wkup_en) {
+		DWC_DEBUGPL(DBG_PCD, "Host does not allow remote wakeup\n");
+		return;
+	}
+
+	/* Check if Resume OK */
+	if (!lpmcfg.b.sleep_state_resumeok) {
+		DWC_DEBUGPL(DBG_PCD, "Sleep state resume is not OK\n");
+		return;
+	}
+
+	lpmcfg.d32 = DWC_READ_REG32(&core_if->core_global_regs->glpmcfg);
+	lpmcfg.b.en_utmi_sleep = 0;
+	lpmcfg.b.hird_thres &= (~(1 << 4));
+	DWC_WRITE_REG32(&core_if->core_global_regs->glpmcfg, lpmcfg.d32);
+
+	if (set) {
+		dctl_data_t dctl = {.d32 = 0 };
+		dctl.b.rmtwkupsig = 1;
+		/* Set RmtWkUpSig bit to start remote wakup signaling.
+		 * Hardware will automatically clear this bit.
+		 */
+		DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl,
+				 0, dctl.d32);
+		DWC_DEBUGPL(DBG_PCD, "Set Remote Wakeup\n");
+	}
+
+}
+#endif
+
+/**
+ * Performs remote wakeup.
+ */
+void dwc_otg_pcd_remote_wakeup(dwc_otg_pcd_t * pcd, int set)
+{
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+	dwc_irqflags_t flags;
+	if (dwc_otg_is_device_mode(core_if)) {
+		DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags);
+#ifdef CONFIG_USB_DWC_OTG_LPM
+		if (core_if->lx_state == DWC_OTG_L1) {
+			dwc_otg_pcd_rem_wkup_from_sleep(pcd, set);
+		} else {
+#endif
+			dwc_otg_pcd_rem_wkup_from_suspend(pcd, set);
+#ifdef CONFIG_USB_DWC_OTG_LPM
+		}
+#endif
+		DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags);
+	}
+	return;
+}
+
+void dwc_otg_pcd_disconnect_us(dwc_otg_pcd_t * pcd, int no_of_usecs)
+{
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+	dctl_data_t dctl = { 0 };
+
+	if (dwc_otg_is_device_mode(core_if)) {
+		dctl.b.sftdiscon = 1;
+		DWC_PRINTF("Soft disconnect for %d useconds\n",no_of_usecs);
+		DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, 0, dctl.d32);
+		dwc_udelay(no_of_usecs);
+		DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32,0);
+
+	} else{
+		DWC_PRINTF("NOT SUPPORTED IN HOST MODE\n");
+	}
+	return;
+
+}
+
+int dwc_otg_pcd_wakeup(dwc_otg_pcd_t * pcd)
+{
+	dsts_data_t dsts;
+	gotgctl_data_t gotgctl;
+
+	/*
+	 * This function starts the Protocol if no session is in progress. If
+	 * a session is already in progress, but the device is suspended,
+	 * remote wakeup signaling is started.
+	 */
+
+	/* Check if valid session */
+	gotgctl.d32 =
+	    DWC_READ_REG32(&(GET_CORE_IF(pcd)->core_global_regs->gotgctl));
+	if (gotgctl.b.bsesvld) {
+		/* Check if suspend state */
+		dsts.d32 =
+		    DWC_READ_REG32(&
+				   (GET_CORE_IF(pcd)->dev_if->
+				    dev_global_regs->dsts));
+		if (dsts.b.suspsts) {
+			dwc_otg_pcd_remote_wakeup(pcd, 1);
+		}
+	} else {
+		dwc_otg_pcd_initiate_srp(pcd);
+	}
+
+	return 0;
+
+}
+
+/**
+ * Start the SRP timer to detect when the SRP does not complete within
+ * 6 seconds.
+ *
+ * @param pcd the pcd structure.
+ */
+void dwc_otg_pcd_initiate_srp(dwc_otg_pcd_t * pcd)
+{
+	dwc_irqflags_t flags;
+	DWC_SPINLOCK_IRQSAVE(pcd->lock, &flags);
+	dwc_otg_initiate_srp(GET_CORE_IF(pcd));
+	DWC_SPINUNLOCK_IRQRESTORE(pcd->lock, flags);
+}
+
+int dwc_otg_pcd_get_frame_number(dwc_otg_pcd_t * pcd)
+{
+	return dwc_otg_get_frame_number(GET_CORE_IF(pcd));
+}
+
+int dwc_otg_pcd_is_lpm_enabled(dwc_otg_pcd_t * pcd)
+{
+	return GET_CORE_IF(pcd)->core_params->lpm_enable;
+}
+
+uint32_t get_b_hnp_enable(dwc_otg_pcd_t * pcd)
+{
+	return pcd->b_hnp_enable;
+}
+
+uint32_t get_a_hnp_support(dwc_otg_pcd_t * pcd)
+{
+	return pcd->a_hnp_support;
+}
+
+uint32_t get_a_alt_hnp_support(dwc_otg_pcd_t * pcd)
+{
+	return pcd->a_alt_hnp_support;
+}
+
+int dwc_otg_pcd_get_rmwkup_enable(dwc_otg_pcd_t * pcd)
+{
+	return pcd->remote_wakeup_enable;
+}
+
+#endif /* DWC_HOST_ONLY */
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_pcd.h b/drivers/usb/host/dwc_otg/dwc_otg_pcd.h
new file mode 100644
index 000000000000..fffc4c9d8bc6
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_pcd.h
@@ -0,0 +1,277 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_pcd.h $
+ * $Revision: #48 $
+ * $Date: 2012/08/10 $
+ * $Change: 2047372 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+#ifndef DWC_HOST_ONLY
+#if !defined(__DWC_PCD_H__)
+#define __DWC_PCD_H__
+
+#include "dwc_otg_os_dep.h"
+#include "usb.h"
+#include "dwc_otg_cil.h"
+#include "dwc_otg_pcd_if.h"
+#include "dwc_otg_driver.h"
+
+struct cfiobject;
+
+/**
+ * @file
+ *
+ * This file contains the structures, constants, and interfaces for
+ * the Perpherial Contoller Driver (PCD).
+ *
+ * The Peripheral Controller Driver (PCD) for Linux will implement the
+ * Gadget API, so that the existing Gadget drivers can be used. For
+ * the Mass Storage Function driver the File-backed USB Storage Gadget
+ * (FBS) driver will be used.  The FBS driver supports the
+ * Control-Bulk (CB), Control-Bulk-Interrupt (CBI), and Bulk-Only
+ * transports.
+ *
+ */
+
+/** Invalid DMA Address */
+#define DWC_DMA_ADDR_INVALID	(~(dwc_dma_t)0)
+
+/** Max Transfer size for any EP */
+#define DDMA_MAX_TRANSFER_SIZE 65535
+
+/**
+ * Get the pointer to the core_if from the pcd pointer.
+ */
+#define GET_CORE_IF( _pcd ) (_pcd->core_if)
+
+/**
+ * States of EP0.
+ */
+typedef enum ep0_state {
+	EP0_DISCONNECT,		/* no host */
+	EP0_IDLE,
+	EP0_IN_DATA_PHASE,
+	EP0_OUT_DATA_PHASE,
+	EP0_IN_STATUS_PHASE,
+	EP0_OUT_STATUS_PHASE,
+	EP0_STALL,
+} ep0state_e;
+
+/** Fordward declaration.*/
+struct dwc_otg_pcd;
+
+/** DWC_otg iso request structure.
+ *
+ */
+typedef struct usb_iso_request dwc_otg_pcd_iso_request_t;
+
+#ifdef DWC_UTE_PER_IO
+
+/**
+ * This shall be the exact analogy of the same type structure defined in the
+ * usb_gadget.h. Each descriptor contains
+ */
+struct dwc_iso_pkt_desc_port {
+	uint32_t offset;
+	uint32_t length;	/* expected length */
+	uint32_t actual_length;
+	uint32_t status;
+};
+
+struct dwc_iso_xreq_port {
+	/** transfer/submission flag */
+	uint32_t tr_sub_flags;
+	/** Start the request ASAP */
+#define DWC_EREQ_TF_ASAP		0x00000002
+	/** Just enqueue the request w/o initiating a transfer */
+#define DWC_EREQ_TF_ENQUEUE		0x00000004
+
+	/**
+	* count of ISO packets attached to this request - shall
+	* not exceed the pio_alloc_pkt_count
+	*/
+	uint32_t pio_pkt_count;
+	/** count of ISO packets allocated for this request */
+	uint32_t pio_alloc_pkt_count;
+	/** number of ISO packet errors */
+	uint32_t error_count;
+	/** reserved for future extension */
+	uint32_t res;
+	/** Will be allocated and freed in the UTE gadget and based on the CFC value */
+	struct dwc_iso_pkt_desc_port *per_io_frame_descs;
+};
+#endif
+/** DWC_otg request structure.
+ * This structure is a list of requests.
+ */
+typedef struct dwc_otg_pcd_request {
+	void *priv;
+	void *buf;
+	dwc_dma_t dma;
+	uint32_t length;
+	uint32_t actual;
+	unsigned sent_zlp:1;
+    /**
+     * Used instead of original buffer if
+     * it(physical address) is not dword-aligned.
+     **/
+     uint8_t *dw_align_buf;
+     dwc_dma_t dw_align_buf_dma;
+
+	 DWC_CIRCLEQ_ENTRY(dwc_otg_pcd_request) queue_entry;
+#ifdef DWC_UTE_PER_IO
+	struct dwc_iso_xreq_port ext_req;
+	//void *priv_ereq_nport; /*  */
+#endif
+} dwc_otg_pcd_request_t;
+
+DWC_CIRCLEQ_HEAD(req_list, dwc_otg_pcd_request);
+
+/**	  PCD EP structure.
+ * This structure describes an EP, there is an array of EPs in the PCD
+ * structure.
+ */
+typedef struct dwc_otg_pcd_ep {
+	/** USB EP Descriptor */
+	const usb_endpoint_descriptor_t *desc;
+
+	/** queue of dwc_otg_pcd_requests. */
+	struct req_list queue;
+	unsigned stopped:1;
+	unsigned disabling:1;
+	unsigned dma:1;
+	unsigned queue_sof:1;
+
+#ifdef DWC_EN_ISOC
+	/** ISOC req handle passed */
+	void *iso_req_handle;
+#endif				//_EN_ISOC_
+
+	/** DWC_otg ep data. */
+	dwc_ep_t dwc_ep;
+
+	/** Pointer to PCD */
+	struct dwc_otg_pcd *pcd;
+
+	void *priv;
+} dwc_otg_pcd_ep_t;
+
+/** DWC_otg PCD Structure.
+ * This structure encapsulates the data for the dwc_otg PCD.
+ */
+struct dwc_otg_pcd {
+	const struct dwc_otg_pcd_function_ops *fops;
+	/** The DWC otg device pointer */
+	struct dwc_otg_device *otg_dev;
+	/** Core Interface */
+	dwc_otg_core_if_t *core_if;
+	/** State of EP0 */
+	ep0state_e ep0state;
+	/** EP0 Request is pending */
+	unsigned ep0_pending:1;
+	/** Indicates when SET CONFIGURATION Request is in process */
+	unsigned request_config:1;
+	/** The state of the Remote Wakeup Enable. */
+	unsigned remote_wakeup_enable:1;
+	/** The state of the B-Device HNP Enable. */
+	unsigned b_hnp_enable:1;
+	/** The state of A-Device HNP Support. */
+	unsigned a_hnp_support:1;
+	/** The state of the A-Device Alt HNP support. */
+	unsigned a_alt_hnp_support:1;
+	/** Count of pending Requests */
+	unsigned request_pending;
+
+	/** SETUP packet for EP0
+	 * This structure is allocated as a DMA buffer on PCD initialization
+	 * with enough space for up to 3 setup packets.
+	 */
+	union {
+		usb_device_request_t req;
+		uint32_t d32[2];
+	} *setup_pkt;
+
+	dwc_dma_t setup_pkt_dma_handle;
+
+	/* Additional buffer and flag for CTRL_WR premature case */
+	uint8_t *backup_buf;
+	unsigned data_terminated;
+
+	/** 2-byte dma buffer used to return status from GET_STATUS */
+	uint16_t *status_buf;
+	dwc_dma_t status_buf_dma_handle;
+
+	/** EP0 */
+	dwc_otg_pcd_ep_t ep0;
+
+	/** Array of IN EPs. */
+	dwc_otg_pcd_ep_t in_ep[MAX_EPS_CHANNELS - 1];
+	/** Array of OUT EPs. */
+	dwc_otg_pcd_ep_t out_ep[MAX_EPS_CHANNELS - 1];
+	/** number of valid EPs in the above array. */
+//        unsigned      num_eps : 4;
+	dwc_spinlock_t *lock;
+
+	/** Tasklet to defer starting of TEST mode transmissions until
+	 *	Status Phase has been completed.
+	 */
+	dwc_tasklet_t *test_mode_tasklet;
+
+	/** Tasklet to delay starting of xfer in DMA mode */
+	dwc_tasklet_t *start_xfer_tasklet;
+
+	/** The test mode to enter when the tasklet is executed. */
+	unsigned test_mode;
+	/** The cfi_api structure that implements most of the CFI API
+	 * and OTG specific core configuration functionality
+	 */
+#ifdef DWC_UTE_CFI
+	struct cfiobject *cfi;
+#endif
+
+};
+
+static inline struct device *dwc_otg_pcd_to_dev(struct dwc_otg_pcd *pcd)
+{
+	return &pcd->otg_dev->os_dep.platformdev->dev;
+}
+
+extern void dwc_otg_pcd_stop(dwc_otg_pcd_t * pcd);
+extern dwc_otg_pcd_ep_t *get_ep_by_addr(dwc_otg_pcd_t * pcd, u16 wIndex);
+extern void start_next_request(dwc_otg_pcd_ep_t * ep);
+
+//FIXME this functions should be static, and this prototypes should be removed
+extern void dwc_otg_request_nuke(dwc_otg_pcd_ep_t * ep);
+extern void dwc_otg_request_done(dwc_otg_pcd_ep_t * ep,
+				 dwc_otg_pcd_request_t * req, int32_t status);
+
+void dwc_otg_iso_buffer_done(dwc_otg_pcd_t * pcd, dwc_otg_pcd_ep_t * ep,
+			     void *req_handle);
+
+extern void do_test_mode(void *data);
+#endif
+#endif /* DWC_HOST_ONLY */
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_pcd_if.h b/drivers/usb/host/dwc_otg/dwc_otg_pcd_if.h
new file mode 100644
index 000000000000..badc93a41c38
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_pcd_if.h
@@ -0,0 +1,381 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_pcd_if.h $
+ * $Revision: #11 $
+ * $Date: 2011/10/26 $
+ * $Change: 1873028 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+#ifndef DWC_HOST_ONLY
+
+#if !defined(__DWC_PCD_IF_H__)
+#define __DWC_PCD_IF_H__
+
+//#include "dwc_os.h"
+#include "dwc_otg_core_if.h"
+#include "dwc_otg_driver.h"
+
+/** @file
+ * This file defines DWC_OTG PCD Core API.
+ */
+
+struct dwc_otg_pcd;
+typedef struct dwc_otg_pcd dwc_otg_pcd_t;
+
+/** Maxpacket size for EP0 */
+#define MAX_EP0_SIZE	64
+/** Maxpacket size for any EP */
+#define MAX_PACKET_SIZE 1024
+
+/** @name Function Driver Callbacks */
+/** @{ */
+
+/** This function will be called whenever a previously queued request has
+ * completed.  The status value will be set to -DWC_E_SHUTDOWN to indicated a
+ * failed or aborted transfer, or -DWC_E_RESTART to indicate the device was reset,
+ * or -DWC_E_TIMEOUT to indicate it timed out, or -DWC_E_INVALID to indicate invalid
+ * parameters. */
+typedef int (*dwc_completion_cb_t) (dwc_otg_pcd_t * pcd, void *ep_handle,
+				    void *req_handle, int32_t status,
+				    uint32_t actual);
+/**
+ * This function will be called whenever a previousle queued ISOC request has
+ * completed. Count of ISOC packets could be read using dwc_otg_pcd_get_iso_packet_count
+ * function.
+ * The status of each ISOC packet could be read using dwc_otg_pcd_get_iso_packet_*
+ * functions.
+ */
+typedef int (*dwc_isoc_completion_cb_t) (dwc_otg_pcd_t * pcd, void *ep_handle,
+					 void *req_handle, int proc_buf_num);
+/** This function should handle any SETUP request that cannot be handled by the
+ * PCD Core.  This includes most GET_DESCRIPTORs, SET_CONFIGS, Any
+ * class-specific requests, etc.  The function must non-blocking.
+ *
+ * Returns 0 on success.
+ * Returns -DWC_E_NOT_SUPPORTED if the request is not supported.
+ * Returns -DWC_E_INVALID if the setup request had invalid parameters or bytes.
+ * Returns -DWC_E_SHUTDOWN on any other error. */
+typedef int (*dwc_setup_cb_t) (dwc_otg_pcd_t * pcd, uint8_t * bytes);
+/** This is called whenever the device has been disconnected.  The function
+ * driver should take appropriate action to clean up all pending requests in the
+ * PCD Core, remove all endpoints (except ep0), and initialize back to reset
+ * state. */
+typedef int (*dwc_disconnect_cb_t) (dwc_otg_pcd_t * pcd);
+/** This function is called when device has been connected. */
+typedef int (*dwc_connect_cb_t) (dwc_otg_pcd_t * pcd, int speed);
+/** This function is called when device has been suspended */
+typedef int (*dwc_suspend_cb_t) (dwc_otg_pcd_t * pcd);
+/** This function is called when device has received LPM tokens, i.e.
+ * device has been sent to sleep state. */
+typedef int (*dwc_sleep_cb_t) (dwc_otg_pcd_t * pcd);
+/** This function is called when device has been resumed
+ * from suspend(L2) or L1 sleep state. */
+typedef int (*dwc_resume_cb_t) (dwc_otg_pcd_t * pcd);
+/** This function is called whenever hnp params has been changed.
+ * User can call get_b_hnp_enable, get_a_hnp_support, get_a_alt_hnp_support functions
+ * to get hnp parameters. */
+typedef int (*dwc_hnp_params_changed_cb_t) (dwc_otg_pcd_t * pcd);
+/** This function is called whenever USB RESET is detected. */
+typedef int (*dwc_reset_cb_t) (dwc_otg_pcd_t * pcd);
+
+typedef int (*cfi_setup_cb_t) (dwc_otg_pcd_t * pcd, void *ctrl_req_bytes);
+
+/**
+ *
+ * @param ep_handle	Void pointer to the usb_ep structure
+ * @param ereq_port Pointer to the extended request structure created in the
+ *					portable part.
+ */
+typedef int (*xiso_completion_cb_t) (dwc_otg_pcd_t * pcd, void *ep_handle,
+				     void *req_handle, int32_t status,
+				     void *ereq_port);
+/** Function Driver Ops Data Structure */
+struct dwc_otg_pcd_function_ops {
+	dwc_connect_cb_t connect;
+	dwc_disconnect_cb_t disconnect;
+	dwc_setup_cb_t setup;
+	dwc_completion_cb_t complete;
+	dwc_isoc_completion_cb_t isoc_complete;
+	dwc_suspend_cb_t suspend;
+	dwc_sleep_cb_t sleep;
+	dwc_resume_cb_t resume;
+	dwc_reset_cb_t reset;
+	dwc_hnp_params_changed_cb_t hnp_changed;
+	cfi_setup_cb_t cfi_setup;
+#ifdef DWC_UTE_PER_IO
+	xiso_completion_cb_t xisoc_complete;
+#endif
+};
+/** @} */
+
+/** @name Function Driver Functions */
+/** @{ */
+
+/** Call this function to get pointer on dwc_otg_pcd_t,
+ * this pointer will be used for all PCD API functions.
+ *
+ * @param core_if The DWC_OTG Core
+ */
+extern dwc_otg_pcd_t *dwc_otg_pcd_init(dwc_otg_device_t *otg_dev);
+
+/** Frees PCD allocated by dwc_otg_pcd_init
+ *
+ * @param pcd The PCD
+ */
+extern void dwc_otg_pcd_remove(dwc_otg_pcd_t * pcd);
+
+/** Call this to bind the function driver to the PCD Core.
+ *
+ * @param pcd Pointer on dwc_otg_pcd_t returned by dwc_otg_pcd_init function.
+ * @param fops The Function Driver Ops data structure containing pointers to all callbacks.
+ */
+extern void dwc_otg_pcd_start(dwc_otg_pcd_t * pcd,
+			      const struct dwc_otg_pcd_function_ops *fops);
+
+/** Enables an endpoint for use.  This function enables an endpoint in
+ * the PCD.  The endpoint is described by the ep_desc which has the
+ * same format as a USB ep descriptor.  The ep_handle parameter is used to refer
+ * to the endpoint from other API functions and in callbacks.  Normally this
+ * should be called after a SET_CONFIGURATION/SET_INTERFACE to configure the
+ * core for that interface.
+ *
+ * Returns -DWC_E_INVALID if invalid parameters were passed.
+ * Returns -DWC_E_SHUTDOWN if any other error ocurred.
+ * Returns 0 on success.
+ *
+ * @param pcd The PCD
+ * @param ep_desc Endpoint descriptor
+ * @param usb_ep Handle on endpoint, that will be used to identify endpoint.
+ */
+extern int dwc_otg_pcd_ep_enable(dwc_otg_pcd_t * pcd,
+				 const uint8_t * ep_desc, void *usb_ep);
+
+/** Disable the endpoint referenced by ep_handle.
+ *
+ * Returns -DWC_E_INVALID if invalid parameters were passed.
+ * Returns -DWC_E_SHUTDOWN if any other error occurred.
+ * Returns 0 on success. */
+extern int dwc_otg_pcd_ep_disable(dwc_otg_pcd_t * pcd, void *ep_handle);
+
+/** Queue a data transfer request on the endpoint referenced by ep_handle.
+ * After the transfer is completes, the complete callback will be called with
+ * the request status.
+ *
+ * @param pcd The PCD
+ * @param ep_handle The handle of the endpoint
+ * @param buf The buffer for the data
+ * @param dma_buf The DMA buffer for the data
+ * @param buflen The length of the data transfer
+ * @param zero Specifies whether to send zero length last packet.
+ * @param req_handle Set this handle to any value to use to reference this
+ * request in the ep_dequeue function or from the complete callback
+ * @param atomic_alloc If driver need to perform atomic allocations
+ * for internal data structures.
+ *
+ * Returns -DWC_E_INVALID if invalid parameters were passed.
+ * Returns -DWC_E_SHUTDOWN if any other error ocurred.
+ * Returns 0 on success. */
+extern int dwc_otg_pcd_ep_queue(dwc_otg_pcd_t * pcd, void *ep_handle,
+				uint8_t * buf, dwc_dma_t dma_buf,
+				uint32_t buflen, int zero, void *req_handle,
+				int atomic_alloc);
+#ifdef DWC_UTE_PER_IO
+/**
+ *
+ * @param ereq_nonport	Pointer to the extended request part of the
+ *						usb_request structure defined in usb_gadget.h file.
+ */
+extern int dwc_otg_pcd_xiso_ep_queue(dwc_otg_pcd_t * pcd, void *ep_handle,
+				     uint8_t * buf, dwc_dma_t dma_buf,
+				     uint32_t buflen, int zero,
+				     void *req_handle, int atomic_alloc,
+				     void *ereq_nonport);
+
+#endif
+
+/** De-queue the specified data transfer that has not yet completed.
+ *
+ * Returns -DWC_E_INVALID if invalid parameters were passed.
+ * Returns -DWC_E_SHUTDOWN if any other error ocurred.
+ * Returns 0 on success. */
+extern int dwc_otg_pcd_ep_dequeue(dwc_otg_pcd_t * pcd, void *ep_handle,
+				  void *req_handle);
+
+/** Halt (STALL) an endpoint or clear it.
+ *
+ * Returns -DWC_E_INVALID if invalid parameters were passed.
+ * Returns -DWC_E_SHUTDOWN if any other error ocurred.
+ * Returns -DWC_E_AGAIN if the STALL cannot be sent and must be tried again later
+ * Returns 0 on success. */
+extern int dwc_otg_pcd_ep_halt(dwc_otg_pcd_t * pcd, void *ep_handle, int value);
+
+/** This function */
+extern int dwc_otg_pcd_ep_wedge(dwc_otg_pcd_t * pcd, void *ep_handle);
+
+/** This function should be called on every hardware interrupt */
+extern int32_t dwc_otg_pcd_handle_intr(dwc_otg_pcd_t * pcd);
+
+/** This function returns current frame number */
+extern int dwc_otg_pcd_get_frame_number(dwc_otg_pcd_t * pcd);
+
+/**
+ * Start isochronous transfers on the endpoint referenced by ep_handle.
+ * For isochronous transfers duble buffering is used.
+ * After processing each of buffers comlete callback will be called with
+ * status for each transaction.
+ *
+ * @param pcd The PCD
+ * @param ep_handle The handle of the endpoint
+ * @param buf0 The virtual address of first data buffer
+ * @param buf1 The virtual address of second data buffer
+ * @param dma0 The DMA address of first data buffer
+ * @param dma1 The DMA address of second data buffer
+ * @param sync_frame Data pattern frame number
+ * @param dp_frame Data size for pattern frame
+ * @param data_per_frame Data size for regular frame
+ * @param start_frame Frame number to start transfers, if -1 then start transfers ASAP.
+ * @param buf_proc_intrvl Interval of ISOC Buffer processing
+ * @param req_handle Handle of ISOC request
+ * @param atomic_alloc Specefies whether to perform atomic allocation for
+ * 			internal data structures.
+ *
+ * Returns -DWC_E_NO_MEMORY if there is no enough memory.
+ * Returns -DWC_E_INVALID if incorrect arguments are passed to the function.
+ * Returns -DW_E_SHUTDOWN for any other error.
+ * Returns 0 on success
+ */
+extern int dwc_otg_pcd_iso_ep_start(dwc_otg_pcd_t * pcd, void *ep_handle,
+				    uint8_t * buf0, uint8_t * buf1,
+				    dwc_dma_t dma0, dwc_dma_t dma1,
+				    int sync_frame, int dp_frame,
+				    int data_per_frame, int start_frame,
+				    int buf_proc_intrvl, void *req_handle,
+				    int atomic_alloc);
+
+/** Stop ISOC transfers on endpoint referenced by ep_handle.
+ *
+ * @param pcd The PCD
+ * @param ep_handle The handle of the endpoint
+ * @param req_handle Handle of ISOC request
+ *
+ * Returns -DWC_E_INVALID if incorrect arguments are passed to the function
+ * Returns 0 on success
+ */
+int dwc_otg_pcd_iso_ep_stop(dwc_otg_pcd_t * pcd, void *ep_handle,
+			    void *req_handle);
+
+/** Get ISOC packet status.
+ *
+ * @param pcd The PCD
+ * @param ep_handle The handle of the endpoint
+ * @param iso_req_handle Isochronoush request handle
+ * @param packet Number of packet
+ * @param status Out parameter for returning status
+ * @param actual Out parameter for returning actual length
+ * @param offset Out parameter for returning offset
+ *
+ */
+extern void dwc_otg_pcd_get_iso_packet_params(dwc_otg_pcd_t * pcd,
+					      void *ep_handle,
+					      void *iso_req_handle, int packet,
+					      int *status, int *actual,
+					      int *offset);
+
+/** Get ISOC packet count.
+ *
+ * @param pcd The PCD
+ * @param ep_handle The handle of the endpoint
+ * @param iso_req_handle
+ */
+extern int dwc_otg_pcd_get_iso_packet_count(dwc_otg_pcd_t * pcd,
+					    void *ep_handle,
+					    void *iso_req_handle);
+
+/** This function starts the SRP Protocol if no session is in progress. If
+ * a session is already in progress, but the device is suspended,
+ * remote wakeup signaling is started.
+ */
+extern int dwc_otg_pcd_wakeup(dwc_otg_pcd_t * pcd);
+
+/** This function returns 1 if LPM support is enabled, and 0 otherwise. */
+extern int dwc_otg_pcd_is_lpm_enabled(dwc_otg_pcd_t * pcd);
+
+/** This function returns 1 if remote wakeup is allowed and 0, otherwise. */
+extern int dwc_otg_pcd_get_rmwkup_enable(dwc_otg_pcd_t * pcd);
+
+/** Initiate SRP */
+extern void dwc_otg_pcd_initiate_srp(dwc_otg_pcd_t * pcd);
+
+/** Starts remote wakeup signaling. */
+extern void dwc_otg_pcd_remote_wakeup(dwc_otg_pcd_t * pcd, int set);
+
+/** Starts micorsecond soft disconnect. */
+extern void dwc_otg_pcd_disconnect_us(dwc_otg_pcd_t * pcd, int no_of_usecs);
+/** This function returns whether device is dualspeed.*/
+extern uint32_t dwc_otg_pcd_is_dualspeed(dwc_otg_pcd_t * pcd);
+
+/** This function returns whether device is otg. */
+extern uint32_t dwc_otg_pcd_is_otg(dwc_otg_pcd_t * pcd);
+
+/** These functions allow to get hnp parameters */
+extern uint32_t get_b_hnp_enable(dwc_otg_pcd_t * pcd);
+extern uint32_t get_a_hnp_support(dwc_otg_pcd_t * pcd);
+extern uint32_t get_a_alt_hnp_support(dwc_otg_pcd_t * pcd);
+
+/** CFI specific Interface functions */
+/** Allocate a cfi buffer */
+extern uint8_t *cfiw_ep_alloc_buffer(dwc_otg_pcd_t * pcd, void *pep,
+				     dwc_dma_t * addr, size_t buflen,
+				     int flags);
+
+extern int pcd_init(
+#ifdef LM_INTERFACE
+			   struct lm_device *_dev
+#elif  defined(PCI_INTERFACE)
+			   struct pci_dev *_dev
+#elif  defined(PLATFORM_INTERFACE)
+	struct platform_device *dev
+#endif
+    );
+
+extern void pcd_remove(
+#ifdef LM_INTERFACE
+			     struct lm_device *_dev
+#elif  defined(PCI_INTERFACE)
+			     struct pci_dev *_dev
+#elif  defined(PLATFORM_INTERFACE)
+	struct platform_device *_dev
+#endif
+    );
+
+/******************************************************************************/
+
+/** @} */
+
+#endif				/* __DWC_PCD_IF_H__ */
+
+#endif				/* DWC_HOST_ONLY */
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_pcd_intr.c b/drivers/usb/host/dwc_otg/dwc_otg_pcd_intr.c
new file mode 100644
index 000000000000..9a255a96c209
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_pcd_intr.c
@@ -0,0 +1,5148 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_pcd_intr.c $
+ * $Revision: #116 $
+ * $Date: 2012/08/10 $
+ * $Change: 2047372 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+#ifndef DWC_HOST_ONLY
+
+#include "dwc_otg_pcd.h"
+
+#ifdef DWC_UTE_CFI
+#include "dwc_otg_cfi.h"
+#endif
+
+#ifdef DWC_UTE_PER_IO
+extern void complete_xiso_ep(dwc_otg_pcd_ep_t * ep);
+#endif
+//#define PRINT_CFI_DMA_DESCS
+
+#define DEBUG_EP0
+
+/**
+ * This function updates OTG.
+ */
+static void dwc_otg_pcd_update_otg(dwc_otg_pcd_t * pcd, const unsigned reset)
+{
+
+	if (reset) {
+		pcd->b_hnp_enable = 0;
+		pcd->a_hnp_support = 0;
+		pcd->a_alt_hnp_support = 0;
+	}
+
+	if (pcd->fops->hnp_changed) {
+		pcd->fops->hnp_changed(pcd);
+	}
+}
+
+/** @file
+ * This file contains the implementation of the PCD Interrupt handlers.
+ *
+ * The PCD handles the device interrupts.  Many conditions can cause a
+ * device interrupt. When an interrupt occurs, the device interrupt
+ * service routine determines the cause of the interrupt and
+ * dispatches handling to the appropriate function. These interrupt
+ * handling functions are described below.
+ * All interrupt registers are processed from LSB to MSB.
+ */
+
+/**
+ * This function prints the ep0 state for debug purposes.
+ */
+static inline void print_ep0_state(dwc_otg_pcd_t * pcd)
+{
+#ifdef DEBUG
+	char str[40];
+
+	switch (pcd->ep0state) {
+	case EP0_DISCONNECT:
+		dwc_strcpy(str, "EP0_DISCONNECT");
+		break;
+	case EP0_IDLE:
+		dwc_strcpy(str, "EP0_IDLE");
+		break;
+	case EP0_IN_DATA_PHASE:
+		dwc_strcpy(str, "EP0_IN_DATA_PHASE");
+		break;
+	case EP0_OUT_DATA_PHASE:
+		dwc_strcpy(str, "EP0_OUT_DATA_PHASE");
+		break;
+	case EP0_IN_STATUS_PHASE:
+		dwc_strcpy(str, "EP0_IN_STATUS_PHASE");
+		break;
+	case EP0_OUT_STATUS_PHASE:
+		dwc_strcpy(str, "EP0_OUT_STATUS_PHASE");
+		break;
+	case EP0_STALL:
+		dwc_strcpy(str, "EP0_STALL");
+		break;
+	default:
+		dwc_strcpy(str, "EP0_INVALID");
+	}
+
+	DWC_DEBUGPL(DBG_ANY, "%s(%d)\n", str, pcd->ep0state);
+#endif
+}
+
+/**
+ * This function calculate the size of the payload in the memory
+ * for out endpoints and prints size for debug purposes(used in
+ * 2.93a DevOutNak feature).
+ */
+static inline void print_memory_payload(dwc_otg_pcd_t * pcd,  dwc_ep_t * ep)
+{
+#ifdef DEBUG
+	deptsiz_data_t deptsiz_init = {.d32 = 0 };
+	deptsiz_data_t deptsiz_updt = {.d32 = 0 };
+	int pack_num;
+	unsigned payload;
+
+	deptsiz_init.d32 = pcd->core_if->start_doeptsiz_val[ep->num];
+	deptsiz_updt.d32 =
+		DWC_READ_REG32(&pcd->core_if->dev_if->
+						out_ep_regs[ep->num]->doeptsiz);
+	/* Payload will be */
+	payload = deptsiz_init.b.xfersize - deptsiz_updt.b.xfersize;
+	/* Packet count is decremented every time a packet
+	 * is written to the RxFIFO not in to the external memory
+	 * So, if payload == 0, then it means no packet was sent to ext memory*/
+	pack_num = (!payload) ? 0 : (deptsiz_init.b.pktcnt - deptsiz_updt.b.pktcnt);
+	DWC_DEBUGPL(DBG_PCDV,
+		"Payload for EP%d-%s\n",
+		ep->num, (ep->is_in ? "IN" : "OUT"));
+	DWC_DEBUGPL(DBG_PCDV,
+		"Number of transfered bytes = 0x%08x\n", payload);
+	DWC_DEBUGPL(DBG_PCDV,
+		"Number of transfered packets = %d\n", pack_num);
+#endif
+}
+
+
+#ifdef DWC_UTE_CFI
+static inline void print_desc(struct dwc_otg_dma_desc *ddesc,
+			      const uint8_t * epname, int descnum)
+{
+	CFI_INFO
+	    ("%s DMA_DESC(%d) buf=0x%08x bytes=0x%04x; sp=0x%x; l=0x%x; sts=0x%02x; bs=0x%02x\n",
+	     epname, descnum, ddesc->buf, ddesc->status.b.bytes,
+	     ddesc->status.b.sp, ddesc->status.b.l, ddesc->status.b.sts,
+	     ddesc->status.b.bs);
+}
+#endif
+
+/**
+ * This function returns pointer to in ep struct with number ep_num
+ */
+static inline dwc_otg_pcd_ep_t *get_in_ep(dwc_otg_pcd_t * pcd, uint32_t ep_num)
+{
+	int i;
+	int num_in_eps = GET_CORE_IF(pcd)->dev_if->num_in_eps;
+	if (ep_num == 0) {
+		return &pcd->ep0;
+	} else {
+		for (i = 0; i < num_in_eps; ++i) {
+			if (pcd->in_ep[i].dwc_ep.num == ep_num)
+				return &pcd->in_ep[i];
+		}
+		return 0;
+	}
+}
+
+/**
+ * This function returns pointer to out ep struct with number ep_num
+ */
+static inline dwc_otg_pcd_ep_t *get_out_ep(dwc_otg_pcd_t * pcd, uint32_t ep_num)
+{
+	int i;
+	int num_out_eps = GET_CORE_IF(pcd)->dev_if->num_out_eps;
+	if (ep_num == 0) {
+		return &pcd->ep0;
+	} else {
+		for (i = 0; i < num_out_eps; ++i) {
+			if (pcd->out_ep[i].dwc_ep.num == ep_num)
+				return &pcd->out_ep[i];
+		}
+		return 0;
+	}
+}
+
+/**
+ * This functions gets a pointer to an EP from the wIndex address
+ * value of the control request.
+ */
+dwc_otg_pcd_ep_t *get_ep_by_addr(dwc_otg_pcd_t * pcd, u16 wIndex)
+{
+	dwc_otg_pcd_ep_t *ep;
+	uint32_t ep_num = UE_GET_ADDR(wIndex);
+
+	if (ep_num == 0) {
+		ep = &pcd->ep0;
+	} else if (UE_GET_DIR(wIndex) == UE_DIR_IN) {	/* in ep */
+		ep = &pcd->in_ep[ep_num - 1];
+	} else {
+		ep = &pcd->out_ep[ep_num - 1];
+	}
+
+	return ep;
+}
+
+/**
+ * This function checks the EP request queue, if the queue is not
+ * empty the next request is started.
+ */
+void start_next_request(dwc_otg_pcd_ep_t * ep)
+{
+	dwc_otg_pcd_request_t *req = 0;
+	uint32_t max_transfer =
+	    GET_CORE_IF(ep->pcd)->core_params->max_transfer_size;
+
+#ifdef DWC_UTE_CFI
+	struct dwc_otg_pcd *pcd;
+	pcd = ep->pcd;
+#endif
+
+	if (!DWC_CIRCLEQ_EMPTY(&ep->queue)) {
+		req = DWC_CIRCLEQ_FIRST(&ep->queue);
+
+#ifdef DWC_UTE_CFI
+		if (ep->dwc_ep.buff_mode != BM_STANDARD) {
+			ep->dwc_ep.cfi_req_len = req->length;
+			pcd->cfi->ops.build_descriptors(pcd->cfi, pcd, ep, req);
+		} else {
+#endif
+			/* Setup and start the Transfer */
+			if (req->dw_align_buf) {
+				ep->dwc_ep.dma_addr = req->dw_align_buf_dma;
+				ep->dwc_ep.start_xfer_buff = req->dw_align_buf;
+				ep->dwc_ep.xfer_buff = req->dw_align_buf;
+			} else {
+				ep->dwc_ep.dma_addr = req->dma;
+				ep->dwc_ep.start_xfer_buff = req->buf;
+				ep->dwc_ep.xfer_buff = req->buf;
+			}
+			ep->dwc_ep.sent_zlp = 0;
+			ep->dwc_ep.total_len = req->length;
+			ep->dwc_ep.xfer_len = 0;
+			ep->dwc_ep.xfer_count = 0;
+
+			ep->dwc_ep.maxxfer = max_transfer;
+			if (GET_CORE_IF(ep->pcd)->dma_desc_enable) {
+				uint32_t out_max_xfer = DDMA_MAX_TRANSFER_SIZE
+				    - (DDMA_MAX_TRANSFER_SIZE % 4);
+				if (ep->dwc_ep.is_in) {
+					if (ep->dwc_ep.maxxfer >
+					    DDMA_MAX_TRANSFER_SIZE) {
+						ep->dwc_ep.maxxfer =
+						    DDMA_MAX_TRANSFER_SIZE;
+					}
+				} else {
+					if (ep->dwc_ep.maxxfer > out_max_xfer) {
+						ep->dwc_ep.maxxfer =
+						    out_max_xfer;
+					}
+				}
+			}
+			if (ep->dwc_ep.maxxfer < ep->dwc_ep.total_len) {
+				ep->dwc_ep.maxxfer -=
+				    (ep->dwc_ep.maxxfer % ep->dwc_ep.maxpacket);
+			}
+			if (req->sent_zlp) {
+				if ((ep->dwc_ep.total_len %
+				     ep->dwc_ep.maxpacket == 0)
+				    && (ep->dwc_ep.total_len != 0)) {
+					ep->dwc_ep.sent_zlp = 1;
+				}
+
+			}
+#ifdef DWC_UTE_CFI
+		}
+#endif
+		dwc_otg_ep_start_transfer(GET_CORE_IF(ep->pcd), &ep->dwc_ep);
+	} else if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) {
+		DWC_PRINTF("There are no more ISOC requests \n");
+		ep->dwc_ep.frame_num = 0xFFFFFFFF;
+	}
+}
+
+/**
+ * This function handles the SOF Interrupts. At this time the SOF
+ * Interrupt is disabled.
+ */
+static int32_t dwc_otg_pcd_handle_sof_intr(dwc_otg_pcd_t * pcd)
+{
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+
+	gintsts_data_t gintsts;
+
+	DWC_DEBUGPL(DBG_PCD, "SOF\n");
+
+	/* Clear interrupt */
+	gintsts.d32 = 0;
+	gintsts.b.sofintr = 1;
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32);
+
+	return 1;
+}
+
+/**
+ * This function handles the Rx Status Queue Level Interrupt, which
+ * indicates that there is a least one packet in the Rx FIFO.  The
+ * packets are moved from the FIFO to memory, where they will be
+ * processed when the Endpoint Interrupt Register indicates Transfer
+ * Complete or SETUP Phase Done.
+ *
+ * Repeat the following until the Rx Status Queue is empty:
+ *	 -# Read the Receive Status Pop Register (GRXSTSP) to get Packet
+ *		info
+ *	 -# If Receive FIFO is empty then skip to step Clear the interrupt
+ *		and exit
+ *	 -# If SETUP Packet call dwc_otg_read_setup_packet to copy the
+ *		SETUP data to the buffer
+ *	 -# If OUT Data Packet call dwc_otg_read_packet to copy the data
+ *		to the destination buffer
+ */
+static int32_t dwc_otg_pcd_handle_rx_status_q_level_intr(dwc_otg_pcd_t * pcd)
+{
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+	dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs;
+	gintmsk_data_t gintmask = {.d32 = 0 };
+	device_grxsts_data_t status;
+	dwc_otg_pcd_ep_t *ep;
+	gintsts_data_t gintsts;
+#ifdef DEBUG
+	static char *dpid_str[] = { "D0", "D2", "D1", "MDATA" };
+#endif
+
+	//DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, _pcd);
+	/* Disable the Rx Status Queue Level interrupt */
+	gintmask.b.rxstsqlvl = 1;
+	DWC_MODIFY_REG32(&global_regs->gintmsk, gintmask.d32, 0);
+
+	/* Get the Status from the top of the FIFO */
+	status.d32 = DWC_READ_REG32(&global_regs->grxstsp);
+
+	DWC_DEBUGPL(DBG_PCD, "EP:%d BCnt:%d DPID:%s "
+		    "pktsts:%x Frame:%d(0x%0x)\n",
+		    status.b.epnum, status.b.bcnt,
+		    dpid_str[status.b.dpid],
+		    status.b.pktsts, status.b.fn, status.b.fn);
+	/* Get pointer to EP structure */
+	ep = get_out_ep(pcd, status.b.epnum);
+
+	switch (status.b.pktsts) {
+	case DWC_DSTS_GOUT_NAK:
+		DWC_DEBUGPL(DBG_PCDV, "Global OUT NAK\n");
+		break;
+	case DWC_STS_DATA_UPDT:
+		DWC_DEBUGPL(DBG_PCDV, "OUT Data Packet\n");
+		if (status.b.bcnt && ep->dwc_ep.xfer_buff) {
+			/** @todo NGS Check for buffer overflow? */
+			dwc_otg_read_packet(core_if,
+					    ep->dwc_ep.xfer_buff,
+					    status.b.bcnt);
+			ep->dwc_ep.xfer_count += status.b.bcnt;
+			ep->dwc_ep.xfer_buff += status.b.bcnt;
+		}
+		break;
+	case DWC_STS_XFER_COMP:
+		DWC_DEBUGPL(DBG_PCDV, "OUT Complete\n");
+		break;
+	case DWC_DSTS_SETUP_COMP:
+#ifdef DEBUG_EP0
+		DWC_DEBUGPL(DBG_PCDV, "Setup Complete\n");
+#endif
+		break;
+	case DWC_DSTS_SETUP_UPDT:
+		dwc_otg_read_setup_packet(core_if, pcd->setup_pkt->d32);
+#ifdef DEBUG_EP0
+		DWC_DEBUGPL(DBG_PCD,
+			    "SETUP PKT: %02x.%02x v%04x i%04x l%04x\n",
+			    pcd->setup_pkt->req.bmRequestType,
+			    pcd->setup_pkt->req.bRequest,
+			    UGETW(pcd->setup_pkt->req.wValue),
+			    UGETW(pcd->setup_pkt->req.wIndex),
+			    UGETW(pcd->setup_pkt->req.wLength));
+#endif
+		ep->dwc_ep.xfer_count += status.b.bcnt;
+		break;
+	default:
+		DWC_DEBUGPL(DBG_PCDV, "Invalid Packet Status (0x%0x)\n",
+			    status.b.pktsts);
+		break;
+	}
+
+	/* Enable the Rx Status Queue Level interrupt */
+	DWC_MODIFY_REG32(&global_regs->gintmsk, 0, gintmask.d32);
+	/* Clear interrupt */
+	gintsts.d32 = 0;
+	gintsts.b.rxstsqlvl = 1;
+	DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+	//DWC_DEBUGPL(DBG_PCDV, "EXIT: %s\n", __func__);
+	return 1;
+}
+
+/**
+ * This function examines the Device IN Token Learning Queue to
+ * determine the EP number of the last IN token received.  This
+ * implementation is for the Mass Storage device where there are only
+ * 2 IN EPs (Control-IN and BULK-IN).
+ *
+ * The EP numbers for the first six IN Tokens are in DTKNQR1 and there
+ * are 8 EP Numbers in each of the other possible DTKNQ Registers.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ *
+ */
+static inline int get_ep_of_last_in_token(dwc_otg_core_if_t * core_if)
+{
+	dwc_otg_device_global_regs_t *dev_global_regs =
+	    core_if->dev_if->dev_global_regs;
+	const uint32_t TOKEN_Q_DEPTH = core_if->hwcfg2.b.dev_token_q_depth;
+	/* Number of Token Queue Registers */
+	const int DTKNQ_REG_CNT = (TOKEN_Q_DEPTH + 7) / 8;
+	dtknq1_data_t dtknqr1;
+	uint32_t in_tkn_epnums[4];
+	int ndx = 0;
+	int i = 0;
+	volatile uint32_t *addr = &dev_global_regs->dtknqr1;
+	int epnum = 0;
+
+	//DWC_DEBUGPL(DBG_PCD,"dev_token_q_depth=%d\n",TOKEN_Q_DEPTH);
+
+	/* Read the DTKNQ Registers */
+	for (i = 0; i < DTKNQ_REG_CNT; i++) {
+		in_tkn_epnums[i] = DWC_READ_REG32(addr);
+		DWC_DEBUGPL(DBG_PCDV, "DTKNQR%d=0x%08x\n", i + 1,
+			    in_tkn_epnums[i]);
+		if (addr == &dev_global_regs->dvbusdis) {
+			addr = &dev_global_regs->dtknqr3_dthrctl;
+		} else {
+			++addr;
+		}
+
+	}
+
+	/* Copy the DTKNQR1 data to the bit field. */
+	dtknqr1.d32 = in_tkn_epnums[0];
+	/* Get the EP numbers */
+	in_tkn_epnums[0] = dtknqr1.b.epnums0_5;
+	ndx = dtknqr1.b.intknwptr - 1;
+
+	//DWC_DEBUGPL(DBG_PCDV,"ndx=%d\n",ndx);
+	if (ndx == -1) {
+		/** @todo Find a simpler way to calculate the max
+		 * queue position.*/
+		int cnt = TOKEN_Q_DEPTH;
+		if (TOKEN_Q_DEPTH <= 6) {
+			cnt = TOKEN_Q_DEPTH - 1;
+		} else if (TOKEN_Q_DEPTH <= 14) {
+			cnt = TOKEN_Q_DEPTH - 7;
+		} else if (TOKEN_Q_DEPTH <= 22) {
+			cnt = TOKEN_Q_DEPTH - 15;
+		} else {
+			cnt = TOKEN_Q_DEPTH - 23;
+		}
+		epnum = (in_tkn_epnums[DTKNQ_REG_CNT - 1] >> (cnt * 4)) & 0xF;
+	} else {
+		if (ndx <= 5) {
+			epnum = (in_tkn_epnums[0] >> (ndx * 4)) & 0xF;
+		} else if (ndx <= 13) {
+			ndx -= 6;
+			epnum = (in_tkn_epnums[1] >> (ndx * 4)) & 0xF;
+		} else if (ndx <= 21) {
+			ndx -= 14;
+			epnum = (in_tkn_epnums[2] >> (ndx * 4)) & 0xF;
+		} else if (ndx <= 29) {
+			ndx -= 22;
+			epnum = (in_tkn_epnums[3] >> (ndx * 4)) & 0xF;
+		}
+	}
+	//DWC_DEBUGPL(DBG_PCD,"epnum=%d\n",epnum);
+	return epnum;
+}
+
+/**
+ * This interrupt occurs when the non-periodic Tx FIFO is half-empty.
+ * The active request is checked for the next packet to be loaded into
+ * the non-periodic Tx FIFO.
+ */
+static int32_t dwc_otg_pcd_handle_np_tx_fifo_empty_intr(dwc_otg_pcd_t * pcd)
+{
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+	dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs;
+	dwc_otg_dev_in_ep_regs_t *ep_regs;
+	gnptxsts_data_t txstatus = {.d32 = 0 };
+	gintsts_data_t gintsts;
+
+	int epnum = 0;
+	dwc_otg_pcd_ep_t *ep = 0;
+	uint32_t len = 0;
+	int dwords;
+
+	/* Get the epnum from the IN Token Learning Queue. */
+	epnum = get_ep_of_last_in_token(core_if);
+	ep = get_in_ep(pcd, epnum);
+
+	DWC_DEBUGPL(DBG_PCD, "NP TxFifo Empty: %d \n", epnum);
+
+	ep_regs = core_if->dev_if->in_ep_regs[epnum];
+
+	len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count;
+	if (len > ep->dwc_ep.maxpacket) {
+		len = ep->dwc_ep.maxpacket;
+	}
+	dwords = (len + 3) / 4;
+
+	/* While there is space in the queue and space in the FIFO and
+	 * More data to tranfer, Write packets to the Tx FIFO */
+	txstatus.d32 = DWC_READ_REG32(&global_regs->gnptxsts);
+	DWC_DEBUGPL(DBG_PCDV, "b4 GNPTXSTS=0x%08x\n", txstatus.d32);
+
+	while (txstatus.b.nptxqspcavail > 0 &&
+	       txstatus.b.nptxfspcavail > dwords &&
+	       ep->dwc_ep.xfer_count < ep->dwc_ep.xfer_len) {
+		/* Write the FIFO */
+		dwc_otg_ep_write_packet(core_if, &ep->dwc_ep, 0);
+		len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count;
+
+		if (len > ep->dwc_ep.maxpacket) {
+			len = ep->dwc_ep.maxpacket;
+		}
+
+		dwords = (len + 3) / 4;
+		txstatus.d32 = DWC_READ_REG32(&global_regs->gnptxsts);
+		DWC_DEBUGPL(DBG_PCDV, "GNPTXSTS=0x%08x\n", txstatus.d32);
+	}
+
+	DWC_DEBUGPL(DBG_PCDV, "GNPTXSTS=0x%08x\n",
+		    DWC_READ_REG32(&global_regs->gnptxsts));
+
+	/* Clear interrupt */
+	gintsts.d32 = 0;
+	gintsts.b.nptxfempty = 1;
+	DWC_WRITE_REG32(&global_regs->gintsts, gintsts.d32);
+
+	return 1;
+}
+
+/**
+ * This function is called when dedicated Tx FIFO Empty interrupt occurs.
+ * The active request is checked for the next packet to be loaded into
+ * apropriate Tx FIFO.
+ */
+static int32_t write_empty_tx_fifo(dwc_otg_pcd_t * pcd, uint32_t epnum)
+{
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+	dwc_otg_dev_if_t *dev_if = core_if->dev_if;
+	dwc_otg_dev_in_ep_regs_t *ep_regs;
+	dtxfsts_data_t txstatus = {.d32 = 0 };
+	dwc_otg_pcd_ep_t *ep = 0;
+	uint32_t len = 0;
+	int dwords;
+
+	ep = get_in_ep(pcd, epnum);
+
+	DWC_DEBUGPL(DBG_PCD, "Dedicated TxFifo Empty: %d \n", epnum);
+
+	ep_regs = core_if->dev_if->in_ep_regs[epnum];
+
+	len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count;
+
+	if (len > ep->dwc_ep.maxpacket) {
+		len = ep->dwc_ep.maxpacket;
+	}
+
+	dwords = (len + 3) / 4;
+
+	/* While there is space in the queue and space in the FIFO and
+	 * More data to tranfer, Write packets to the Tx FIFO */
+	txstatus.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dtxfsts);
+	DWC_DEBUGPL(DBG_PCDV, "b4 dtxfsts[%d]=0x%08x\n", epnum, txstatus.d32);
+
+	while (txstatus.b.txfspcavail > dwords &&
+	       ep->dwc_ep.xfer_count < ep->dwc_ep.xfer_len &&
+	       ep->dwc_ep.xfer_len != 0) {
+		/* Write the FIFO */
+		dwc_otg_ep_write_packet(core_if, &ep->dwc_ep, 0);
+
+		len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count;
+		if (len > ep->dwc_ep.maxpacket) {
+			len = ep->dwc_ep.maxpacket;
+		}
+
+		dwords = (len + 3) / 4;
+		txstatus.d32 =
+		    DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dtxfsts);
+		DWC_DEBUGPL(DBG_PCDV, "dtxfsts[%d]=0x%08x\n", epnum,
+			    txstatus.d32);
+	}
+
+	DWC_DEBUGPL(DBG_PCDV, "b4 dtxfsts[%d]=0x%08x\n", epnum,
+		    DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dtxfsts));
+
+	return 1;
+}
+
+/**
+ * This function is called when the Device is disconnected. It stops
+ * any active requests and informs the Gadget driver of the
+ * disconnect.
+ */
+void dwc_otg_pcd_stop(dwc_otg_pcd_t * pcd)
+{
+	int i, num_in_eps, num_out_eps;
+	dwc_otg_pcd_ep_t *ep;
+
+	gintmsk_data_t intr_mask = {.d32 = 0 };
+
+	DWC_SPINLOCK(pcd->lock);
+
+	num_in_eps = GET_CORE_IF(pcd)->dev_if->num_in_eps;
+	num_out_eps = GET_CORE_IF(pcd)->dev_if->num_out_eps;
+
+	DWC_DEBUGPL(DBG_PCDV, "%s() \n", __func__);
+	/* don't disconnect drivers more than once */
+	if (pcd->ep0state == EP0_DISCONNECT) {
+		DWC_DEBUGPL(DBG_ANY, "%s() Already Disconnected\n", __func__);
+		DWC_SPINUNLOCK(pcd->lock);
+		return;
+	}
+	pcd->ep0state = EP0_DISCONNECT;
+
+	/* Reset the OTG state. */
+	dwc_otg_pcd_update_otg(pcd, 1);
+
+	/* Disable the NP Tx Fifo Empty Interrupt. */
+	intr_mask.b.nptxfempty = 1;
+	DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintmsk,
+			 intr_mask.d32, 0);
+
+	/* Flush the FIFOs */
+	/**@todo NGS Flush Periodic FIFOs */
+	dwc_otg_flush_tx_fifo(GET_CORE_IF(pcd), 0x10);
+	dwc_otg_flush_rx_fifo(GET_CORE_IF(pcd));
+
+	/* prevent new request submissions, kill any outstanding requests  */
+	ep = &pcd->ep0;
+	dwc_otg_request_nuke(ep);
+	/* prevent new request submissions, kill any outstanding requests  */
+	for (i = 0; i < num_in_eps; i++) {
+		dwc_otg_pcd_ep_t *ep = &pcd->in_ep[i];
+		dwc_otg_request_nuke(ep);
+	}
+	/* prevent new request submissions, kill any outstanding requests  */
+	for (i = 0; i < num_out_eps; i++) {
+		dwc_otg_pcd_ep_t *ep = &pcd->out_ep[i];
+		dwc_otg_request_nuke(ep);
+	}
+
+	/* report disconnect; the driver is already quiesced */
+	if (pcd->fops->disconnect) {
+		DWC_SPINUNLOCK(pcd->lock);
+		pcd->fops->disconnect(pcd);
+		DWC_SPINLOCK(pcd->lock);
+	}
+	DWC_SPINUNLOCK(pcd->lock);
+}
+
+/**
+ * This interrupt indicates that ...
+ */
+static int32_t dwc_otg_pcd_handle_i2c_intr(dwc_otg_pcd_t * pcd)
+{
+	gintmsk_data_t intr_mask = {.d32 = 0 };
+	gintsts_data_t gintsts;
+
+	DWC_PRINTF("INTERRUPT Handler not implemented for %s\n", "i2cintr");
+	intr_mask.b.i2cintr = 1;
+	DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintmsk,
+			 intr_mask.d32, 0);
+
+	/* Clear interrupt */
+	gintsts.d32 = 0;
+	gintsts.b.i2cintr = 1;
+	DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts,
+			gintsts.d32);
+	return 1;
+}
+
+/**
+ * This interrupt indicates that ...
+ */
+static int32_t dwc_otg_pcd_handle_early_suspend_intr(dwc_otg_pcd_t * pcd)
+{
+	gintsts_data_t gintsts;
+#if defined(VERBOSE)
+	DWC_PRINTF("Early Suspend Detected\n");
+#endif
+
+	/* Clear interrupt */
+	gintsts.d32 = 0;
+	gintsts.b.erlysuspend = 1;
+	DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts,
+			gintsts.d32);
+	return 1;
+}
+
+/**
+ * This function configures EPO to receive SETUP packets.
+ *
+ * @todo NGS: Update the comments from the HW FS.
+ *
+ *	-# Program the following fields in the endpoint specific registers
+ *	for Control OUT EP 0, in order to receive a setup packet
+ *	- DOEPTSIZ0.Packet Count = 3 (To receive up to 3 back to back
+ *	  setup packets)
+ *	- DOEPTSIZE0.Transfer Size = 24 Bytes (To receive up to 3 back
+ *	  to back setup packets)
+ *		- In DMA mode, DOEPDMA0 Register with a memory address to
+ *		  store any setup packets received
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param pcd	  Programming view of the PCD.
+ */
+static inline void ep0_out_start(dwc_otg_core_if_t * core_if,
+				 dwc_otg_pcd_t * pcd)
+{
+	dwc_otg_dev_if_t *dev_if = core_if->dev_if;
+	deptsiz0_data_t doeptsize0 = {.d32 = 0 };
+	dwc_otg_dev_dma_desc_t *dma_desc;
+	depctl_data_t doepctl = {.d32 = 0 };
+
+#ifdef VERBOSE
+	DWC_DEBUGPL(DBG_PCDV, "%s() doepctl0=%0x\n", __func__,
+		    DWC_READ_REG32(&dev_if->out_ep_regs[0]->doepctl));
+#endif
+	if (core_if->snpsid >= OTG_CORE_REV_3_00a) {
+		doepctl.d32 = DWC_READ_REG32(&dev_if->out_ep_regs[0]->doepctl);
+		if (doepctl.b.epena) {
+			return;
+		}
+	}
+
+	doeptsize0.b.supcnt = 3;
+	doeptsize0.b.pktcnt = 1;
+	doeptsize0.b.xfersize = 8 * 3;
+
+	if (core_if->dma_enable) {
+		if (!core_if->dma_desc_enable) {
+			/** put here as for Hermes mode deptisz register should not be written */
+			DWC_WRITE_REG32(&dev_if->out_ep_regs[0]->doeptsiz,
+					doeptsize0.d32);
+
+			/** @todo dma needs to handle multiple setup packets (up to 3) */
+			DWC_WRITE_REG32(&dev_if->out_ep_regs[0]->doepdma,
+					pcd->setup_pkt_dma_handle);
+		} else {
+			dev_if->setup_desc_index =
+			    (dev_if->setup_desc_index + 1) & 1;
+			dma_desc =
+			    dev_if->setup_desc_addr[dev_if->setup_desc_index];
+
+			/** DMA Descriptor Setup */
+			dma_desc->status.b.bs = BS_HOST_BUSY;
+			if (core_if->snpsid >= OTG_CORE_REV_3_00a) {
+				dma_desc->status.b.sr = 0;
+				dma_desc->status.b.mtrf = 0;
+			}
+			dma_desc->status.b.l = 1;
+			dma_desc->status.b.ioc = 1;
+			dma_desc->status.b.bytes = pcd->ep0.dwc_ep.maxpacket;
+			dma_desc->buf = pcd->setup_pkt_dma_handle;
+			dma_desc->status.b.sts = 0;
+			dma_desc->status.b.bs = BS_HOST_READY;
+
+			/** DOEPDMA0 Register write */
+			DWC_WRITE_REG32(&dev_if->out_ep_regs[0]->doepdma,
+					dev_if->dma_setup_desc_addr
+					[dev_if->setup_desc_index]);
+		}
+
+	} else {
+		/** put here as for Hermes mode deptisz register should not be written */
+		DWC_WRITE_REG32(&dev_if->out_ep_regs[0]->doeptsiz,
+				doeptsize0.d32);
+	}
+
+	/** DOEPCTL0 Register write cnak will be set after setup interrupt */
+	doepctl.d32 = 0;
+	doepctl.b.epena = 1;
+	if (core_if->snpsid <= OTG_CORE_REV_2_94a) {
+	doepctl.b.cnak = 1;
+	DWC_WRITE_REG32(&dev_if->out_ep_regs[0]->doepctl, doepctl.d32);
+	} else {
+		DWC_MODIFY_REG32(&dev_if->out_ep_regs[0]->doepctl, 0, doepctl.d32);
+	}
+
+#ifdef VERBOSE
+	DWC_DEBUGPL(DBG_PCDV, "doepctl0=%0x\n",
+		    DWC_READ_REG32(&dev_if->out_ep_regs[0]->doepctl));
+	DWC_DEBUGPL(DBG_PCDV, "diepctl0=%0x\n",
+		    DWC_READ_REG32(&dev_if->in_ep_regs[0]->diepctl));
+#endif
+}
+
+/**
+ * This interrupt occurs when a USB Reset is detected. When the USB
+ * Reset Interrupt occurs the device state is set to DEFAULT and the
+ * EP0 state is set to IDLE.
+ *	-#	Set the NAK bit for all OUT endpoints (DOEPCTLn.SNAK = 1)
+ *	-#	Unmask the following interrupt bits
+ *		- DAINTMSK.INEP0 = 1 (Control 0 IN endpoint)
+ *	- DAINTMSK.OUTEP0 = 1 (Control 0 OUT endpoint)
+ *	- DOEPMSK.SETUP = 1
+ *	- DOEPMSK.XferCompl = 1
+ *	- DIEPMSK.XferCompl = 1
+ *	- DIEPMSK.TimeOut = 1
+ *	-# Program the following fields in the endpoint specific registers
+ *	for Control OUT EP 0, in order to receive a setup packet
+ *	- DOEPTSIZ0.Packet Count = 3 (To receive up to 3 back to back
+ *	  setup packets)
+ *	- DOEPTSIZE0.Transfer Size = 24 Bytes (To receive up to 3 back
+ *	  to back setup packets)
+ *		- In DMA mode, DOEPDMA0 Register with a memory address to
+ *		  store any setup packets received
+ * At this point, all the required initialization, except for enabling
+ * the control 0 OUT endpoint is done, for receiving SETUP packets.
+ */
+static int32_t dwc_otg_pcd_handle_usb_reset_intr(dwc_otg_pcd_t * pcd)
+{
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+	dwc_otg_dev_if_t *dev_if = core_if->dev_if;
+	depctl_data_t doepctl = {.d32 = 0 };
+	depctl_data_t diepctl = {.d32 = 0 };
+	daint_data_t daintmsk = {.d32 = 0 };
+	doepmsk_data_t doepmsk = {.d32 = 0 };
+	diepmsk_data_t diepmsk = {.d32 = 0 };
+	dcfg_data_t dcfg = {.d32 = 0 };
+	grstctl_t resetctl = {.d32 = 0 };
+	dctl_data_t dctl = {.d32 = 0 };
+	int i = 0;
+	gintsts_data_t gintsts;
+	pcgcctl_data_t power = {.d32 = 0 };
+
+	power.d32 = DWC_READ_REG32(core_if->pcgcctl);
+	if (power.b.stoppclk) {
+		power.d32 = 0;
+		power.b.stoppclk = 1;
+		DWC_MODIFY_REG32(core_if->pcgcctl, power.d32, 0);
+
+		power.b.pwrclmp = 1;
+		DWC_MODIFY_REG32(core_if->pcgcctl, power.d32, 0);
+
+		power.b.rstpdwnmodule = 1;
+		DWC_MODIFY_REG32(core_if->pcgcctl, power.d32, 0);
+	}
+
+	core_if->lx_state = DWC_OTG_L0;
+
+	DWC_PRINTF("USB RESET\n");
+#ifdef DWC_EN_ISOC
+	for (i = 1; i < 16; ++i) {
+		dwc_otg_pcd_ep_t *ep;
+		dwc_ep_t *dwc_ep;
+		ep = get_in_ep(pcd, i);
+		if (ep != 0) {
+			dwc_ep = &ep->dwc_ep;
+			dwc_ep->next_frame = 0xffffffff;
+		}
+	}
+#endif /* DWC_EN_ISOC */
+
+	/* reset the HNP settings */
+	dwc_otg_pcd_update_otg(pcd, 1);
+
+	/* Clear the Remote Wakeup Signalling */
+	dctl.b.rmtwkupsig = 1;
+	DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32, 0);
+
+	/* Set NAK for all OUT EPs */
+	doepctl.b.snak = 1;
+	for (i = 0; i <= dev_if->num_out_eps; i++) {
+		DWC_WRITE_REG32(&dev_if->out_ep_regs[i]->doepctl, doepctl.d32);
+	}
+
+	/* Flush the NP Tx FIFO */
+	dwc_otg_flush_tx_fifo(core_if, 0x10);
+	/* Flush the Learning Queue */
+	resetctl.b.intknqflsh = 1;
+	DWC_WRITE_REG32(&core_if->core_global_regs->grstctl, resetctl.d32);
+
+	if (!core_if->core_params->en_multiple_tx_fifo && core_if->dma_enable) {
+		core_if->start_predict = 0;
+		for (i = 0; i<= core_if->dev_if->num_in_eps; ++i) {
+			core_if->nextep_seq[i] = 0xff;	// 0xff - EP not active
+		}
+		core_if->nextep_seq[0] = 0;
+		core_if->first_in_nextep_seq = 0;
+		diepctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[0]->diepctl);
+		diepctl.b.nextep = 0;
+		DWC_WRITE_REG32(&dev_if->in_ep_regs[0]->diepctl, diepctl.d32);
+
+		/* Update IN Endpoint Mismatch Count by active IN NP EP count + 1 */
+		dcfg.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->dcfg);
+		dcfg.b.epmscnt = 2;
+		DWC_WRITE_REG32(&dev_if->dev_global_regs->dcfg, dcfg.d32);
+
+		DWC_DEBUGPL(DBG_PCDV,
+			    "%s first_in_nextep_seq= %2d; nextep_seq[]:\n",
+			__func__, core_if->first_in_nextep_seq);
+		for (i=0; i <= core_if->dev_if->num_in_eps; i++) {
+			DWC_DEBUGPL(DBG_PCDV, "%2d\n", core_if->nextep_seq[i]);
+		}
+	}
+
+	if (core_if->multiproc_int_enable) {
+		daintmsk.b.inep0 = 1;
+		daintmsk.b.outep0 = 1;
+		DWC_WRITE_REG32(&dev_if->dev_global_regs->deachintmsk,
+				daintmsk.d32);
+
+		doepmsk.b.setup = 1;
+		doepmsk.b.xfercompl = 1;
+		doepmsk.b.ahberr = 1;
+		doepmsk.b.epdisabled = 1;
+
+		if ((core_if->dma_desc_enable) ||
+		    (core_if->dma_enable
+		     && core_if->snpsid >= OTG_CORE_REV_3_00a)) {
+			doepmsk.b.stsphsercvd = 1;
+		}
+		if (core_if->dma_desc_enable)
+			doepmsk.b.bna = 1;
+/*
+		doepmsk.b.babble = 1;
+		doepmsk.b.nyet = 1;
+
+		if (core_if->dma_enable) {
+			doepmsk.b.nak = 1;
+		}
+*/
+		DWC_WRITE_REG32(&dev_if->dev_global_regs->doepeachintmsk[0],
+				doepmsk.d32);
+
+		diepmsk.b.xfercompl = 1;
+		diepmsk.b.timeout = 1;
+		diepmsk.b.epdisabled = 1;
+		diepmsk.b.ahberr = 1;
+		diepmsk.b.intknepmis = 1;
+		if (!core_if->en_multiple_tx_fifo && core_if->dma_enable)
+			diepmsk.b.intknepmis = 0;
+
+/*		if (core_if->dma_desc_enable) {
+			diepmsk.b.bna = 1;
+		}
+*/
+/*
+		if (core_if->dma_enable) {
+			diepmsk.b.nak = 1;
+		}
+*/
+		DWC_WRITE_REG32(&dev_if->dev_global_regs->diepeachintmsk[0],
+				diepmsk.d32);
+	} else {
+		daintmsk.b.inep0 = 1;
+		daintmsk.b.outep0 = 1;
+		DWC_WRITE_REG32(&dev_if->dev_global_regs->daintmsk,
+				daintmsk.d32);
+
+		doepmsk.b.setup = 1;
+		doepmsk.b.xfercompl = 1;
+		doepmsk.b.ahberr = 1;
+		doepmsk.b.epdisabled = 1;
+
+		if ((core_if->dma_desc_enable) ||
+		    (core_if->dma_enable
+		     && core_if->snpsid >= OTG_CORE_REV_3_00a)) {
+			doepmsk.b.stsphsercvd = 1;
+		}
+		if (core_if->dma_desc_enable)
+			doepmsk.b.bna = 1;
+		DWC_WRITE_REG32(&dev_if->dev_global_regs->doepmsk, doepmsk.d32);
+
+		diepmsk.b.xfercompl = 1;
+		diepmsk.b.timeout = 1;
+		diepmsk.b.epdisabled = 1;
+		diepmsk.b.ahberr = 1;
+		if (!core_if->en_multiple_tx_fifo && core_if->dma_enable)
+			diepmsk.b.intknepmis = 0;
+/*
+		if (core_if->dma_desc_enable) {
+			diepmsk.b.bna = 1;
+		}
+*/
+
+		DWC_WRITE_REG32(&dev_if->dev_global_regs->diepmsk, diepmsk.d32);
+	}
+
+	/* Reset Device Address */
+	dcfg.d32 = DWC_READ_REG32(&dev_if->dev_global_regs->dcfg);
+	dcfg.b.devaddr = 0;
+	DWC_WRITE_REG32(&dev_if->dev_global_regs->dcfg, dcfg.d32);
+
+	/* setup EP0 to receive SETUP packets */
+	if (core_if->snpsid <= OTG_CORE_REV_2_94a)
+		ep0_out_start(core_if, pcd);
+
+	/* Clear interrupt */
+	gintsts.d32 = 0;
+	gintsts.b.usbreset = 1;
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32);
+
+	return 1;
+}
+
+/**
+ * Get the device speed from the device status register and convert it
+ * to USB speed constant.
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ */
+static int get_device_speed(dwc_otg_core_if_t * core_if)
+{
+	dsts_data_t dsts;
+	int speed = 0;
+	dsts.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dsts);
+
+	switch (dsts.b.enumspd) {
+	case DWC_DSTS_ENUMSPD_HS_PHY_30MHZ_OR_60MHZ:
+		speed = USB_SPEED_HIGH;
+		break;
+	case DWC_DSTS_ENUMSPD_FS_PHY_30MHZ_OR_60MHZ:
+	case DWC_DSTS_ENUMSPD_FS_PHY_48MHZ:
+		speed = USB_SPEED_FULL;
+		break;
+
+	case DWC_DSTS_ENUMSPD_LS_PHY_6MHZ:
+		speed = USB_SPEED_LOW;
+		break;
+	}
+
+	return speed;
+}
+
+/**
+ * Read the device status register and set the device speed in the
+ * data structure.
+ * Set up EP0 to receive SETUP packets by calling dwc_ep0_activate.
+ */
+static int32_t dwc_otg_pcd_handle_enum_done_intr(dwc_otg_pcd_t * pcd)
+{
+	dwc_otg_pcd_ep_t *ep0 = &pcd->ep0;
+	gintsts_data_t gintsts;
+	gusbcfg_data_t gusbcfg;
+	dwc_otg_core_global_regs_t *global_regs =
+	    GET_CORE_IF(pcd)->core_global_regs;
+	uint8_t utmi16b, utmi8b;
+	int speed;
+	DWC_DEBUGPL(DBG_PCD, "SPEED ENUM\n");
+
+	if (GET_CORE_IF(pcd)->snpsid >= OTG_CORE_REV_2_60a) {
+		utmi16b = 6;	//vahrama old value was 6;
+		utmi8b = 9;
+	} else {
+		utmi16b = 4;
+		utmi8b = 8;
+	}
+	dwc_otg_ep0_activate(GET_CORE_IF(pcd), &ep0->dwc_ep);
+	if (GET_CORE_IF(pcd)->snpsid >= OTG_CORE_REV_3_00a) {
+		ep0_out_start(GET_CORE_IF(pcd), pcd);
+	}
+
+#ifdef DEBUG_EP0
+	print_ep0_state(pcd);
+#endif
+
+	if (pcd->ep0state == EP0_DISCONNECT) {
+		pcd->ep0state = EP0_IDLE;
+	} else if (pcd->ep0state == EP0_STALL) {
+		pcd->ep0state = EP0_IDLE;
+	}
+
+	pcd->ep0state = EP0_IDLE;
+
+	ep0->stopped = 0;
+
+	speed = get_device_speed(GET_CORE_IF(pcd));
+	pcd->fops->connect(pcd, speed);
+
+	/* Set USB turnaround time based on device speed and PHY interface. */
+	gusbcfg.d32 = DWC_READ_REG32(&global_regs->gusbcfg);
+	if (speed == USB_SPEED_HIGH) {
+		if (GET_CORE_IF(pcd)->hwcfg2.b.hs_phy_type ==
+		    DWC_HWCFG2_HS_PHY_TYPE_ULPI) {
+			/* ULPI interface */
+			gusbcfg.b.usbtrdtim = 9;
+		}
+		if (GET_CORE_IF(pcd)->hwcfg2.b.hs_phy_type ==
+		    DWC_HWCFG2_HS_PHY_TYPE_UTMI) {
+			/* UTMI+ interface */
+			if (GET_CORE_IF(pcd)->hwcfg4.b.utmi_phy_data_width == 0) {
+				gusbcfg.b.usbtrdtim = utmi8b;
+			} else if (GET_CORE_IF(pcd)->hwcfg4.
+				   b.utmi_phy_data_width == 1) {
+				gusbcfg.b.usbtrdtim = utmi16b;
+			} else if (GET_CORE_IF(pcd)->
+				   core_params->phy_utmi_width == 8) {
+				gusbcfg.b.usbtrdtim = utmi8b;
+			} else {
+				gusbcfg.b.usbtrdtim = utmi16b;
+			}
+		}
+		if (GET_CORE_IF(pcd)->hwcfg2.b.hs_phy_type ==
+		    DWC_HWCFG2_HS_PHY_TYPE_UTMI_ULPI) {
+			/* UTMI+  OR  ULPI interface */
+			if (gusbcfg.b.ulpi_utmi_sel == 1) {
+				/* ULPI interface */
+				gusbcfg.b.usbtrdtim = 9;
+			} else {
+				/* UTMI+ interface */
+				if (GET_CORE_IF(pcd)->
+				    core_params->phy_utmi_width == 16) {
+					gusbcfg.b.usbtrdtim = utmi16b;
+				} else {
+					gusbcfg.b.usbtrdtim = utmi8b;
+				}
+			}
+		}
+	} else {
+		/* Full or low speed */
+		gusbcfg.b.usbtrdtim = 9;
+	}
+	DWC_WRITE_REG32(&global_regs->gusbcfg, gusbcfg.d32);
+
+	/* Clear interrupt */
+	gintsts.d32 = 0;
+	gintsts.b.enumdone = 1;
+	DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts,
+			gintsts.d32);
+	return 1;
+}
+
+/**
+ * This interrupt indicates that the ISO OUT Packet was dropped due to
+ * Rx FIFO full or Rx Status Queue Full.  If this interrupt occurs
+ * read all the data from the Rx FIFO.
+ */
+static int32_t dwc_otg_pcd_handle_isoc_out_packet_dropped_intr(dwc_otg_pcd_t * pcd)
+{
+	gintmsk_data_t intr_mask = {.d32 = 0 };
+	gintsts_data_t gintsts;
+
+	DWC_WARN("INTERRUPT Handler not implemented for %s\n",
+		 "ISOC Out Dropped");
+
+	intr_mask.b.isooutdrop = 1;
+	DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintmsk,
+			 intr_mask.d32, 0);
+
+	/* Clear interrupt */
+	gintsts.d32 = 0;
+	gintsts.b.isooutdrop = 1;
+	DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts,
+			gintsts.d32);
+
+	return 1;
+}
+
+/**
+ * This interrupt indicates the end of the portion of the micro-frame
+ * for periodic transactions.  If there is a periodic transaction for
+ * the next frame, load the packets into the EP periodic Tx FIFO.
+ */
+static int32_t dwc_otg_pcd_handle_end_periodic_frame_intr(dwc_otg_pcd_t * pcd)
+{
+	gintmsk_data_t intr_mask = {.d32 = 0 };
+	gintsts_data_t gintsts;
+	DWC_PRINTF("INTERRUPT Handler not implemented for %s\n", "EOP");
+
+	intr_mask.b.eopframe = 1;
+	DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintmsk,
+			 intr_mask.d32, 0);
+
+	/* Clear interrupt */
+	gintsts.d32 = 0;
+	gintsts.b.eopframe = 1;
+	DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts,
+			gintsts.d32);
+
+	return 1;
+}
+
+/**
+ * This interrupt indicates that EP of the packet on the top of the
+ * non-periodic Tx FIFO does not match EP of the IN Token received.
+ *
+ * The "Device IN Token Queue" Registers are read to determine the
+ * order the IN Tokens have been received. The non-periodic Tx FIFO
+ * is flushed, so it can be reloaded in the order seen in the IN Token
+ * Queue.
+ */
+static int32_t dwc_otg_pcd_handle_ep_mismatch_intr(dwc_otg_pcd_t * pcd)
+{
+	gintsts_data_t gintsts;
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+	dctl_data_t dctl;
+	gintmsk_data_t intr_mask = {.d32 = 0 };
+
+	if (!core_if->en_multiple_tx_fifo && core_if->dma_enable) {
+		core_if->start_predict = 1;
+
+		DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, core_if);
+
+		gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts);
+		if (!gintsts.b.ginnakeff) {
+			/* Disable EP Mismatch interrupt */
+			intr_mask.d32 = 0;
+			intr_mask.b.epmismatch = 1;
+			DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, intr_mask.d32, 0);
+			/* Enable the Global IN NAK Effective Interrupt */
+			intr_mask.d32 = 0;
+			intr_mask.b.ginnakeff = 1;
+			DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, 0, intr_mask.d32);
+			/* Set the global non-periodic IN NAK handshake */
+			dctl.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dctl);
+			dctl.b.sgnpinnak = 1;
+			DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32);
+		} else {
+			DWC_PRINTF("gintsts.b.ginnakeff = 1! dctl.b.sgnpinnak not set\n");
+		}
+		/* Disabling of all EP's will be done in dwc_otg_pcd_handle_in_nak_effective()
+		 * handler after Global IN NAK Effective interrupt will be asserted */
+	}
+	/* Clear interrupt */
+	gintsts.d32 = 0;
+	gintsts.b.epmismatch = 1;
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32);
+
+	return 1;
+}
+
+/**
+ * This interrupt is valid only in DMA mode. This interrupt indicates that the
+ * core has stopped fetching data for IN endpoints due to the unavailability of
+ * TxFIFO space or Request Queue space. This interrupt is used by the
+ * application for an endpoint mismatch algorithm.
+ *
+ * @param pcd The PCD
+ */
+static int32_t dwc_otg_pcd_handle_ep_fetsusp_intr(dwc_otg_pcd_t * pcd)
+{
+	gintsts_data_t gintsts;
+	gintmsk_data_t gintmsk_data;
+	dctl_data_t dctl;
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+	DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, core_if);
+
+	/* Clear the global non-periodic IN NAK handshake */
+	dctl.d32 = 0;
+	dctl.b.cgnpinnak = 1;
+	DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32, dctl.d32);
+
+	/* Mask GINTSTS.FETSUSP interrupt */
+	gintmsk_data.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintmsk);
+	gintmsk_data.b.fetsusp = 0;
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, gintmsk_data.d32);
+
+	/* Clear interrupt */
+	gintsts.d32 = 0;
+	gintsts.b.fetsusp = 1;
+	DWC_WRITE_REG32(&core_if->core_global_regs->gintsts, gintsts.d32);
+
+	return 1;
+}
+/**
+ * This funcion stalls EP0.
+ */
+static inline void ep0_do_stall(dwc_otg_pcd_t * pcd, const int err_val)
+{
+	dwc_otg_pcd_ep_t *ep0 = &pcd->ep0;
+	usb_device_request_t *ctrl = &pcd->setup_pkt->req;
+	DWC_WARN("req %02x.%02x protocol STALL; err %d\n",
+		 ctrl->bmRequestType, ctrl->bRequest, err_val);
+
+	ep0->dwc_ep.is_in = 1;
+	dwc_otg_ep_set_stall(GET_CORE_IF(pcd), &ep0->dwc_ep);
+	pcd->ep0.stopped = 1;
+	pcd->ep0state = EP0_IDLE;
+	ep0_out_start(GET_CORE_IF(pcd), pcd);
+}
+
+/**
+ * This functions delegates the setup command to the gadget driver.
+ */
+static inline void do_gadget_setup(dwc_otg_pcd_t * pcd,
+				   usb_device_request_t * ctrl)
+{
+	int ret = 0;
+	DWC_SPINUNLOCK(pcd->lock);
+	ret = pcd->fops->setup(pcd, (uint8_t *) ctrl);
+	DWC_SPINLOCK(pcd->lock);
+	if (ret < 0) {
+		ep0_do_stall(pcd, ret);
+	}
+
+	/** @todo This is a g_file_storage gadget driver specific
+	 * workaround: a DELAYED_STATUS result from the fsg_setup
+	 * routine will result in the gadget queueing a EP0 IN status
+	 * phase for a two-stage control transfer. Exactly the same as
+	 * a SET_CONFIGURATION/SET_INTERFACE except that this is a class
+	 * specific request.  Need a generic way to know when the gadget
+	 * driver will queue the status phase. Can we assume when we
+	 * call the gadget driver setup() function that it will always
+	 * queue and require the following flag? Need to look into
+	 * this.
+	 */
+
+	if (ret == 256 + 999) {
+		pcd->request_config = 1;
+	}
+}
+
+#ifdef DWC_UTE_CFI
+/**
+ * This functions delegates the CFI setup commands to the gadget driver.
+ * This function will return a negative value to indicate a failure.
+ */
+static inline int cfi_gadget_setup(dwc_otg_pcd_t * pcd,
+				   struct cfi_usb_ctrlrequest *ctrl_req)
+{
+	int ret = 0;
+
+	if (pcd->fops && pcd->fops->cfi_setup) {
+		DWC_SPINUNLOCK(pcd->lock);
+		ret = pcd->fops->cfi_setup(pcd, ctrl_req);
+		DWC_SPINLOCK(pcd->lock);
+		if (ret < 0) {
+			ep0_do_stall(pcd, ret);
+			return ret;
+		}
+	}
+
+	return ret;
+}
+#endif
+
+/**
+ * This function starts the Zero-Length Packet for the IN status phase
+ * of a 2 stage control transfer.
+ */
+static inline void do_setup_in_status_phase(dwc_otg_pcd_t * pcd)
+{
+	dwc_otg_pcd_ep_t *ep0 = &pcd->ep0;
+	if (pcd->ep0state == EP0_STALL) {
+		return;
+	}
+
+	pcd->ep0state = EP0_IN_STATUS_PHASE;
+
+	/* Prepare for more SETUP Packets */
+	DWC_DEBUGPL(DBG_PCD, "EP0 IN ZLP\n");
+	if ((GET_CORE_IF(pcd)->snpsid >= OTG_CORE_REV_3_00a)
+	    && (pcd->core_if->dma_desc_enable)
+	    && (ep0->dwc_ep.xfer_count < ep0->dwc_ep.total_len)) {
+		DWC_DEBUGPL(DBG_PCDV,
+			    "Data terminated wait next packet in out_desc_addr\n");
+		pcd->backup_buf = phys_to_virt(ep0->dwc_ep.dma_addr);
+		pcd->data_terminated = 1;
+	}
+	ep0->dwc_ep.xfer_len = 0;
+	ep0->dwc_ep.xfer_count = 0;
+	ep0->dwc_ep.is_in = 1;
+	ep0->dwc_ep.dma_addr = pcd->setup_pkt_dma_handle;
+	dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), &ep0->dwc_ep);
+
+	/* Prepare for more SETUP Packets */
+	//ep0_out_start(GET_CORE_IF(pcd), pcd);
+}
+
+/**
+ * This function starts the Zero-Length Packet for the OUT status phase
+ * of a 2 stage control transfer.
+ */
+static inline void do_setup_out_status_phase(dwc_otg_pcd_t * pcd)
+{
+	dwc_otg_pcd_ep_t *ep0 = &pcd->ep0;
+	if (pcd->ep0state == EP0_STALL) {
+		DWC_DEBUGPL(DBG_PCD, "EP0 STALLED\n");
+		return;
+	}
+	pcd->ep0state = EP0_OUT_STATUS_PHASE;
+
+	DWC_DEBUGPL(DBG_PCD, "EP0 OUT ZLP\n");
+	ep0->dwc_ep.xfer_len = 0;
+	ep0->dwc_ep.xfer_count = 0;
+	ep0->dwc_ep.is_in = 0;
+	ep0->dwc_ep.dma_addr = pcd->setup_pkt_dma_handle;
+	dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), &ep0->dwc_ep);
+
+	/* Prepare for more SETUP Packets */
+	if (GET_CORE_IF(pcd)->dma_enable == 0) {
+		ep0_out_start(GET_CORE_IF(pcd), pcd);
+	}
+}
+
+/**
+ * Clear the EP halt (STALL) and if pending requests start the
+ * transfer.
+ */
+static inline void pcd_clear_halt(dwc_otg_pcd_t * pcd, dwc_otg_pcd_ep_t * ep)
+{
+	if (ep->dwc_ep.stall_clear_flag == 0)
+		dwc_otg_ep_clear_stall(GET_CORE_IF(pcd), &ep->dwc_ep);
+
+	/* Reactive the EP */
+	dwc_otg_ep_activate(GET_CORE_IF(pcd), &ep->dwc_ep);
+	if (ep->stopped) {
+		ep->stopped = 0;
+		/* If there is a request in the EP queue start it */
+
+		/** @todo FIXME: this causes an EP mismatch in DMA mode.
+		 * epmismatch not yet implemented. */
+
+		/*
+		 * Above fixme is solved by implmenting a tasklet to call the
+		 * start_next_request(), outside of interrupt context at some
+		 * time after the current time, after a clear-halt setup packet.
+		 * Still need to implement ep mismatch in the future if a gadget
+		 * ever uses more than one endpoint at once
+		 */
+		ep->queue_sof = 1;
+		DWC_TASK_SCHEDULE(pcd->start_xfer_tasklet);
+	}
+	/* Start Control Status Phase */
+	do_setup_in_status_phase(pcd);
+}
+
+/**
+ * This function is called when the SET_FEATURE TEST_MODE Setup packet
+ * is sent from the host.  The Device Control register is written with
+ * the Test Mode bits set to the specified Test Mode.  This is done as
+ * a tasklet so that the "Status" phase of the control transfer
+ * completes before transmitting the TEST packets.
+ *
+ * @todo This has not been tested since the tasklet struct was put
+ * into the PCD struct!
+ *
+ */
+void do_test_mode(void *data)
+{
+	dctl_data_t dctl;
+	dwc_otg_pcd_t *pcd = (dwc_otg_pcd_t *) data;
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+	int test_mode = pcd->test_mode;
+
+//        DWC_WARN("%s() has not been tested since being rewritten!\n", __func__);
+
+	dctl.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dctl);
+	switch (test_mode) {
+	case 1:		// TEST_J
+		dctl.b.tstctl = 1;
+		break;
+
+	case 2:		// TEST_K
+		dctl.b.tstctl = 2;
+		break;
+
+	case 3:		// TEST_SE0_NAK
+		dctl.b.tstctl = 3;
+		break;
+
+	case 4:		// TEST_PACKET
+		dctl.b.tstctl = 4;
+		break;
+
+	case 5:		// TEST_FORCE_ENABLE
+		dctl.b.tstctl = 5;
+		break;
+	}
+	DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32);
+}
+
+/**
+ * This function process the GET_STATUS Setup Commands.
+ */
+static inline void do_get_status(dwc_otg_pcd_t * pcd)
+{
+	usb_device_request_t ctrl = pcd->setup_pkt->req;
+	dwc_otg_pcd_ep_t *ep;
+	dwc_otg_pcd_ep_t *ep0 = &pcd->ep0;
+	uint16_t *status = pcd->status_buf;
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+
+#ifdef DEBUG_EP0
+	DWC_DEBUGPL(DBG_PCD,
+		    "GET_STATUS %02x.%02x v%04x i%04x l%04x\n",
+		    ctrl.bmRequestType, ctrl.bRequest,
+		    UGETW(ctrl.wValue), UGETW(ctrl.wIndex),
+		    UGETW(ctrl.wLength));
+#endif
+
+	switch (UT_GET_RECIPIENT(ctrl.bmRequestType)) {
+	case UT_DEVICE:
+		if(UGETW(ctrl.wIndex) == 0xF000) { /* OTG Status selector */
+			DWC_PRINTF("wIndex - %d\n", UGETW(ctrl.wIndex));
+			DWC_PRINTF("OTG VERSION - %d\n", core_if->otg_ver);
+			DWC_PRINTF("OTG CAP - %d, %d\n",
+				   core_if->core_params->otg_cap,
+						DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE);
+			if (core_if->otg_ver == 1
+			    && core_if->core_params->otg_cap ==
+			    DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) {
+				uint8_t *otgsts = (uint8_t*)pcd->status_buf;
+				*otgsts = (core_if->otg_sts & 0x1);
+				pcd->ep0_pending = 1;
+				ep0->dwc_ep.start_xfer_buff =
+				    (uint8_t *) otgsts;
+				ep0->dwc_ep.xfer_buff = (uint8_t *) otgsts;
+				ep0->dwc_ep.dma_addr =
+				    pcd->status_buf_dma_handle;
+				ep0->dwc_ep.xfer_len = 1;
+				ep0->dwc_ep.xfer_count = 0;
+				ep0->dwc_ep.total_len = ep0->dwc_ep.xfer_len;
+				dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd),
+							   &ep0->dwc_ep);
+				return;
+			} else {
+				ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED);
+				return;
+			}
+			break;
+		} else {
+			*status = 0x1;	/* Self powered */
+			*status |= pcd->remote_wakeup_enable << 1;
+			break;
+		}
+	case UT_INTERFACE:
+		*status = 0;
+		break;
+
+	case UT_ENDPOINT:
+		ep = get_ep_by_addr(pcd, UGETW(ctrl.wIndex));
+		if (ep == 0 || UGETW(ctrl.wLength) > 2) {
+			ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED);
+			return;
+		}
+		/** @todo check for EP stall */
+		*status = ep->stopped;
+		break;
+	}
+	pcd->ep0_pending = 1;
+	ep0->dwc_ep.start_xfer_buff = (uint8_t *) status;
+	ep0->dwc_ep.xfer_buff = (uint8_t *) status;
+	ep0->dwc_ep.dma_addr = pcd->status_buf_dma_handle;
+	ep0->dwc_ep.xfer_len = 2;
+	ep0->dwc_ep.xfer_count = 0;
+	ep0->dwc_ep.total_len = ep0->dwc_ep.xfer_len;
+	dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), &ep0->dwc_ep);
+}
+
+/**
+ * This function process the SET_FEATURE Setup Commands.
+ */
+static inline void do_set_feature(dwc_otg_pcd_t * pcd)
+{
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+	dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs;
+	usb_device_request_t ctrl = pcd->setup_pkt->req;
+	dwc_otg_pcd_ep_t *ep = 0;
+	int32_t otg_cap_param = core_if->core_params->otg_cap;
+	gotgctl_data_t gotgctl = {.d32 = 0 };
+
+	DWC_DEBUGPL(DBG_PCD, "SET_FEATURE:%02x.%02x v%04x i%04x l%04x\n",
+		    ctrl.bmRequestType, ctrl.bRequest,
+		    UGETW(ctrl.wValue), UGETW(ctrl.wIndex),
+		    UGETW(ctrl.wLength));
+	DWC_DEBUGPL(DBG_PCD, "otg_cap=%d\n", otg_cap_param);
+
+	switch (UT_GET_RECIPIENT(ctrl.bmRequestType)) {
+	case UT_DEVICE:
+		switch (UGETW(ctrl.wValue)) {
+		case UF_DEVICE_REMOTE_WAKEUP:
+			pcd->remote_wakeup_enable = 1;
+			break;
+
+		case UF_TEST_MODE:
+			/* Setup the Test Mode tasklet to do the Test
+			 * Packet generation after the SETUP Status
+			 * phase has completed. */
+
+			/** @todo This has not been tested since the
+			 * tasklet struct was put into the PCD
+			 * struct! */
+			pcd->test_mode = UGETW(ctrl.wIndex) >> 8;
+			DWC_TASK_SCHEDULE(pcd->test_mode_tasklet);
+			break;
+
+		case UF_DEVICE_B_HNP_ENABLE:
+			DWC_DEBUGPL(DBG_PCDV,
+				    "SET_FEATURE: USB_DEVICE_B_HNP_ENABLE\n");
+
+			/* dev may initiate HNP */
+			if (otg_cap_param == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) {
+				pcd->b_hnp_enable = 1;
+				dwc_otg_pcd_update_otg(pcd, 0);
+				DWC_DEBUGPL(DBG_PCD, "Request B HNP\n");
+				/**@todo Is the gotgctl.devhnpen cleared
+				 * by a USB Reset? */
+				gotgctl.b.devhnpen = 1;
+				gotgctl.b.hnpreq = 1;
+				DWC_WRITE_REG32(&global_regs->gotgctl,
+						gotgctl.d32);
+			} else {
+				ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED);
+				return;
+			}
+			break;
+
+		case UF_DEVICE_A_HNP_SUPPORT:
+			/* RH port supports HNP */
+			DWC_DEBUGPL(DBG_PCDV,
+				    "SET_FEATURE: USB_DEVICE_A_HNP_SUPPORT\n");
+			if (otg_cap_param == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) {
+				pcd->a_hnp_support = 1;
+				dwc_otg_pcd_update_otg(pcd, 0);
+			} else {
+				ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED);
+				return;
+			}
+			break;
+
+		case UF_DEVICE_A_ALT_HNP_SUPPORT:
+			/* other RH port does */
+			DWC_DEBUGPL(DBG_PCDV,
+				    "SET_FEATURE: USB_DEVICE_A_ALT_HNP_SUPPORT\n");
+			if (otg_cap_param == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) {
+				pcd->a_alt_hnp_support = 1;
+				dwc_otg_pcd_update_otg(pcd, 0);
+			} else {
+				ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED);
+				return;
+			}
+			break;
+
+		default:
+			ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED);
+			return;
+
+		}
+		do_setup_in_status_phase(pcd);
+		break;
+
+	case UT_INTERFACE:
+		do_gadget_setup(pcd, &ctrl);
+		break;
+
+	case UT_ENDPOINT:
+		if (UGETW(ctrl.wValue) == UF_ENDPOINT_HALT) {
+			ep = get_ep_by_addr(pcd, UGETW(ctrl.wIndex));
+			if (ep == 0) {
+				ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED);
+				return;
+			}
+			ep->stopped = 1;
+			dwc_otg_ep_set_stall(core_if, &ep->dwc_ep);
+		}
+		do_setup_in_status_phase(pcd);
+		break;
+	}
+}
+
+/**
+ * This function process the CLEAR_FEATURE Setup Commands.
+ */
+static inline void do_clear_feature(dwc_otg_pcd_t * pcd)
+{
+	usb_device_request_t ctrl = pcd->setup_pkt->req;
+	dwc_otg_pcd_ep_t *ep = 0;
+
+	DWC_DEBUGPL(DBG_PCD,
+		    "CLEAR_FEATURE:%02x.%02x v%04x i%04x l%04x\n",
+		    ctrl.bmRequestType, ctrl.bRequest,
+		    UGETW(ctrl.wValue), UGETW(ctrl.wIndex),
+		    UGETW(ctrl.wLength));
+
+	switch (UT_GET_RECIPIENT(ctrl.bmRequestType)) {
+	case UT_DEVICE:
+		switch (UGETW(ctrl.wValue)) {
+		case UF_DEVICE_REMOTE_WAKEUP:
+			pcd->remote_wakeup_enable = 0;
+			break;
+
+		case UF_TEST_MODE:
+			/** @todo Add CLEAR_FEATURE for TEST modes. */
+			break;
+
+		default:
+			ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED);
+			return;
+		}
+		do_setup_in_status_phase(pcd);
+		break;
+
+	case UT_ENDPOINT:
+		ep = get_ep_by_addr(pcd, UGETW(ctrl.wIndex));
+		if (ep == 0) {
+			ep0_do_stall(pcd, -DWC_E_NOT_SUPPORTED);
+			return;
+		}
+
+		pcd_clear_halt(pcd, ep);
+
+		break;
+	}
+}
+
+/**
+ * This function process the SET_ADDRESS Setup Commands.
+ */
+static inline void do_set_address(dwc_otg_pcd_t * pcd)
+{
+	dwc_otg_dev_if_t *dev_if = GET_CORE_IF(pcd)->dev_if;
+	usb_device_request_t ctrl = pcd->setup_pkt->req;
+
+	if (ctrl.bmRequestType == UT_DEVICE) {
+		dcfg_data_t dcfg = {.d32 = 0 };
+
+#ifdef DEBUG_EP0
+//                      DWC_DEBUGPL(DBG_PCDV, "SET_ADDRESS:%d\n", ctrl.wValue);
+#endif
+		dcfg.b.devaddr = UGETW(ctrl.wValue);
+		DWC_MODIFY_REG32(&dev_if->dev_global_regs->dcfg, 0, dcfg.d32);
+		do_setup_in_status_phase(pcd);
+	}
+}
+
+/**
+ *	This function processes SETUP commands. In Linux, the USB Command
+ *	processing is done in two places - the first being the PCD and the
+ *	second in the Gadget Driver (for example, the File-Backed Storage
+ *	Gadget Driver).
+ *
+ * <table>
+ * <tr><td>Command	</td><td>Driver </td><td>Description</td></tr>
+ *
+ * <tr><td>GET_STATUS </td><td>PCD </td><td>Command is processed as
+ * defined in chapter 9 of the USB 2.0 Specification chapter 9
+ * </td></tr>
+ *
+ * <tr><td>CLEAR_FEATURE </td><td>PCD </td><td>The Device and Endpoint
+ * requests are the ENDPOINT_HALT feature is procesed, all others the
+ * interface requests are ignored.</td></tr>
+ *
+ * <tr><td>SET_FEATURE </td><td>PCD </td><td>The Device and Endpoint
+ * requests are processed by the PCD.  Interface requests are passed
+ * to the Gadget Driver.</td></tr>
+ *
+ * <tr><td>SET_ADDRESS </td><td>PCD </td><td>Program the DCFG reg,
+ * with device address received </td></tr>
+ *
+ * <tr><td>GET_DESCRIPTOR </td><td>Gadget Driver </td><td>Return the
+ * requested descriptor</td></tr>
+ *
+ * <tr><td>SET_DESCRIPTOR </td><td>Gadget Driver </td><td>Optional -
+ * not implemented by any of the existing Gadget Drivers.</td></tr>
+ *
+ * <tr><td>SET_CONFIGURATION </td><td>Gadget Driver </td><td>Disable
+ * all EPs and enable EPs for new configuration.</td></tr>
+ *
+ * <tr><td>GET_CONFIGURATION </td><td>Gadget Driver </td><td>Return
+ * the current configuration</td></tr>
+ *
+ * <tr><td>SET_INTERFACE </td><td>Gadget Driver </td><td>Disable all
+ * EPs and enable EPs for new configuration.</td></tr>
+ *
+ * <tr><td>GET_INTERFACE </td><td>Gadget Driver </td><td>Return the
+ * current interface.</td></tr>
+ *
+ * <tr><td>SYNC_FRAME </td><td>PCD </td><td>Display debug
+ * message.</td></tr>
+ * </table>
+ *
+ * When the SETUP Phase Done interrupt occurs, the PCD SETUP commands are
+ * processed by pcd_setup. Calling the Function Driver's setup function from
+ * pcd_setup processes the gadget SETUP commands.
+ */
+static inline void pcd_setup(dwc_otg_pcd_t * pcd)
+{
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+	dwc_otg_dev_if_t *dev_if = core_if->dev_if;
+	usb_device_request_t ctrl = pcd->setup_pkt->req;
+	dwc_otg_pcd_ep_t *ep0 = &pcd->ep0;
+
+	deptsiz0_data_t doeptsize0 = {.d32 = 0 };
+
+#ifdef DWC_UTE_CFI
+	int retval = 0;
+	struct cfi_usb_ctrlrequest cfi_req;
+#endif
+
+	doeptsize0.d32 = DWC_READ_REG32(&dev_if->out_ep_regs[0]->doeptsiz);
+
+	/** In BDMA more then 1 setup packet is not supported till 3.00a */
+	if (core_if->dma_enable && core_if->dma_desc_enable == 0
+	    && (doeptsize0.b.supcnt < 2)
+	    && (core_if->snpsid < OTG_CORE_REV_2_94a)) {
+		DWC_ERROR
+		    ("\n\n-----------	 CANNOT handle > 1 setup packet in DMA mode\n\n");
+	}
+	if ((core_if->snpsid >= OTG_CORE_REV_3_00a)
+	    && (core_if->dma_enable == 1) && (core_if->dma_desc_enable == 0)) {
+		ctrl =
+		    (pcd->setup_pkt +
+		     (3 - doeptsize0.b.supcnt - 1 +
+		      ep0->dwc_ep.stp_rollover))->req;
+	}
+#ifdef DEBUG_EP0
+	DWC_DEBUGPL(DBG_PCD, "SETUP %02x.%02x v%04x i%04x l%04x\n",
+		    ctrl.bmRequestType, ctrl.bRequest,
+		    UGETW(ctrl.wValue), UGETW(ctrl.wIndex),
+		    UGETW(ctrl.wLength));
+#endif
+
+	/* Clean up the request queue */
+	dwc_otg_request_nuke(ep0);
+	ep0->stopped = 0;
+
+	if (ctrl.bmRequestType & UE_DIR_IN) {
+		ep0->dwc_ep.is_in = 1;
+		pcd->ep0state = EP0_IN_DATA_PHASE;
+	} else {
+		ep0->dwc_ep.is_in = 0;
+		pcd->ep0state = EP0_OUT_DATA_PHASE;
+	}
+
+	if (UGETW(ctrl.wLength) == 0) {
+		ep0->dwc_ep.is_in = 1;
+		pcd->ep0state = EP0_IN_STATUS_PHASE;
+	}
+
+	if (UT_GET_TYPE(ctrl.bmRequestType) != UT_STANDARD) {
+
+#ifdef DWC_UTE_CFI
+		DWC_MEMCPY(&cfi_req, &ctrl, sizeof(usb_device_request_t));
+
+		//printk(KERN_ALERT "CFI: req_type=0x%02x; req=0x%02x\n",
+				ctrl.bRequestType, ctrl.bRequest);
+		if (UT_GET_TYPE(cfi_req.bRequestType) == UT_VENDOR) {
+			if (cfi_req.bRequest > 0xB0 && cfi_req.bRequest < 0xBF) {
+				retval = cfi_setup(pcd, &cfi_req);
+				if (retval < 0) {
+					ep0_do_stall(pcd, retval);
+					pcd->ep0_pending = 0;
+					return;
+				}
+
+				/* if need gadget setup then call it and check the retval */
+				if (pcd->cfi->need_gadget_att) {
+					retval =
+					    cfi_gadget_setup(pcd,
+							     &pcd->
+							     cfi->ctrl_req);
+					if (retval < 0) {
+						pcd->ep0_pending = 0;
+						return;
+					}
+				}
+
+				if (pcd->cfi->need_status_in_complete) {
+					do_setup_in_status_phase(pcd);
+				}
+				return;
+			}
+		}
+#endif
+
+		/* handle non-standard (class/vendor) requests in the gadget driver */
+		do_gadget_setup(pcd, &ctrl);
+		return;
+	}
+
+	/** @todo NGS: Handle bad setup packet? */
+
+///////////////////////////////////////////
+//// --- Standard Request handling --- ////
+
+	switch (ctrl.bRequest) {
+	case UR_GET_STATUS:
+		do_get_status(pcd);
+		break;
+
+	case UR_CLEAR_FEATURE:
+		do_clear_feature(pcd);
+		break;
+
+	case UR_SET_FEATURE:
+		do_set_feature(pcd);
+		break;
+
+	case UR_SET_ADDRESS:
+		do_set_address(pcd);
+		break;
+
+	case UR_SET_INTERFACE:
+	case UR_SET_CONFIG:
+//              _pcd->request_config = 1;       /* Configuration changed */
+		do_gadget_setup(pcd, &ctrl);
+		break;
+
+	case UR_SYNCH_FRAME:
+		do_gadget_setup(pcd, &ctrl);
+		break;
+
+	default:
+		/* Call the Gadget Driver's setup functions */
+		do_gadget_setup(pcd, &ctrl);
+		break;
+	}
+}
+
+/**
+ * This function completes the ep0 control transfer.
+ */
+static int32_t ep0_complete_request(dwc_otg_pcd_ep_t * ep)
+{
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(ep->pcd);
+	dwc_otg_dev_if_t *dev_if = core_if->dev_if;
+	dwc_otg_dev_in_ep_regs_t *in_ep_regs =
+	    dev_if->in_ep_regs[ep->dwc_ep.num];
+#ifdef DEBUG_EP0
+	dwc_otg_dev_out_ep_regs_t *out_ep_regs =
+	    dev_if->out_ep_regs[ep->dwc_ep.num];
+#endif
+	deptsiz0_data_t deptsiz;
+	dev_dma_desc_sts_t desc_sts;
+	dwc_otg_pcd_request_t *req;
+	int is_last = 0;
+	dwc_otg_pcd_t *pcd = ep->pcd;
+
+#ifdef DWC_UTE_CFI
+	struct cfi_usb_ctrlrequest *ctrlreq;
+	int retval = -DWC_E_NOT_SUPPORTED;
+#endif
+
+        desc_sts.b.bytes = 0;
+
+	if (pcd->ep0_pending && DWC_CIRCLEQ_EMPTY(&ep->queue)) {
+		if (ep->dwc_ep.is_in) {
+#ifdef DEBUG_EP0
+			DWC_DEBUGPL(DBG_PCDV, "Do setup OUT status phase\n");
+#endif
+			do_setup_out_status_phase(pcd);
+		} else {
+#ifdef DEBUG_EP0
+			DWC_DEBUGPL(DBG_PCDV, "Do setup IN status phase\n");
+#endif
+
+#ifdef DWC_UTE_CFI
+			ctrlreq = &pcd->cfi->ctrl_req;
+
+			if (UT_GET_TYPE(ctrlreq->bRequestType) == UT_VENDOR) {
+				if (ctrlreq->bRequest > 0xB0
+				    && ctrlreq->bRequest < 0xBF) {
+
+					/* Return if the PCD failed to handle the request */
+					if ((retval =
+					     pcd->cfi->ops.
+					     ctrl_write_complete(pcd->cfi,
+								 pcd)) < 0) {
+						CFI_INFO
+						    ("ERROR setting a new value in the PCD(%d)\n",
+						     retval);
+						ep0_do_stall(pcd, retval);
+						pcd->ep0_pending = 0;
+						return 0;
+					}
+
+					/* If the gadget needs to be notified on the request */
+					if (pcd->cfi->need_gadget_att == 1) {
+						//retval = do_gadget_setup(pcd, &pcd->cfi->ctrl_req);
+						retval =
+						    cfi_gadget_setup(pcd,
+								     &pcd->cfi->
+								     ctrl_req);
+
+						/* Return from the function if the gadget failed to process
+						 * the request properly - this should never happen !!!
+						 */
+						if (retval < 0) {
+							CFI_INFO
+							    ("ERROR setting a new value in the gadget(%d)\n",
+							     retval);
+							pcd->ep0_pending = 0;
+							return 0;
+						}
+					}
+
+					CFI_INFO("%s: RETVAL=%d\n", __func__,
+						 retval);
+					/* If we hit here then the PCD and the gadget has properly
+					 * handled the request - so send the ZLP IN to the host.
+					 */
+					/* @todo: MAS - decide whether we need to start the setup
+					 * stage based on the need_setup value of the cfi object
+					 */
+					do_setup_in_status_phase(pcd);
+					pcd->ep0_pending = 0;
+					return 1;
+				}
+			}
+#endif
+
+			do_setup_in_status_phase(pcd);
+		}
+		pcd->ep0_pending = 0;
+		return 1;
+	}
+
+	if (DWC_CIRCLEQ_EMPTY(&ep->queue)) {
+		return 0;
+	}
+	req = DWC_CIRCLEQ_FIRST(&ep->queue);
+
+	if (pcd->ep0state == EP0_OUT_STATUS_PHASE
+	    || pcd->ep0state == EP0_IN_STATUS_PHASE) {
+		is_last = 1;
+	} else if (ep->dwc_ep.is_in) {
+		deptsiz.d32 = DWC_READ_REG32(&in_ep_regs->dieptsiz);
+		if (core_if->dma_desc_enable != 0)
+			desc_sts = dev_if->in_desc_addr->status;
+#ifdef DEBUG_EP0
+		DWC_DEBUGPL(DBG_PCDV, "%d len=%d  xfersize=%d pktcnt=%d\n",
+			    ep->dwc_ep.num, ep->dwc_ep.xfer_len,
+			    deptsiz.b.xfersize, deptsiz.b.pktcnt);
+#endif
+
+		if (((core_if->dma_desc_enable == 0)
+		     && (deptsiz.b.xfersize == 0))
+		    || ((core_if->dma_desc_enable != 0)
+			&& (desc_sts.b.bytes == 0))) {
+			req->actual = ep->dwc_ep.xfer_count;
+			/* Is a Zero Len Packet needed? */
+			if (req->sent_zlp) {
+#ifdef DEBUG_EP0
+				DWC_DEBUGPL(DBG_PCD, "Setup Rx ZLP\n");
+#endif
+				req->sent_zlp = 0;
+			}
+			do_setup_out_status_phase(pcd);
+		}
+	} else {
+		/* ep0-OUT */
+#ifdef DEBUG_EP0
+		deptsiz.d32 = DWC_READ_REG32(&out_ep_regs->doeptsiz);
+		DWC_DEBUGPL(DBG_PCDV, "%d len=%d xsize=%d pktcnt=%d\n",
+			    ep->dwc_ep.num, ep->dwc_ep.xfer_len,
+			    deptsiz.b.xfersize, deptsiz.b.pktcnt);
+#endif
+		req->actual = ep->dwc_ep.xfer_count;
+
+		/* Is a Zero Len Packet needed? */
+		if (req->sent_zlp) {
+#ifdef DEBUG_EP0
+			DWC_DEBUGPL(DBG_PCDV, "Setup Tx ZLP\n");
+#endif
+			req->sent_zlp = 0;
+		}
+		/* For older cores do setup in status phase in Slave/BDMA modes,
+		 * starting from 3.00 do that only in slave, and for DMA modes
+		 * just re-enable ep 0 OUT here*/
+		if (core_if->dma_enable == 0
+		    || (core_if->dma_desc_enable == 0
+			&& core_if->snpsid <= OTG_CORE_REV_2_94a)) {
+			do_setup_in_status_phase(pcd);
+		} else if (core_if->snpsid >= OTG_CORE_REV_3_00a) {
+			DWC_DEBUGPL(DBG_PCDV,
+				    "Enable out ep before in status phase\n");
+			ep0_out_start(core_if, pcd);
+		}
+	}
+
+	/* Complete the request */
+	if (is_last) {
+		dwc_otg_request_done(ep, req, 0);
+		ep->dwc_ep.start_xfer_buff = 0;
+		ep->dwc_ep.xfer_buff = 0;
+		ep->dwc_ep.xfer_len = 0;
+		return 1;
+	}
+	return 0;
+}
+
+#ifdef DWC_UTE_CFI
+/**
+ * This function calculates traverses all the CFI DMA descriptors and
+ * and accumulates the bytes that are left to be transfered.
+ *
+ * @return The total bytes left to transfered, or a negative value as failure
+ */
+static inline int cfi_calc_desc_residue(dwc_otg_pcd_ep_t * ep)
+{
+	int32_t ret = 0;
+	int i;
+	struct dwc_otg_dma_desc *ddesc = NULL;
+	struct cfi_ep *cfiep;
+
+	/* See if the pcd_ep has its respective cfi_ep mapped */
+	cfiep = get_cfi_ep_by_pcd_ep(ep->pcd->cfi, ep);
+	if (!cfiep) {
+		CFI_INFO("%s: Failed to find ep\n", __func__);
+		return -1;
+	}
+
+	ddesc = ep->dwc_ep.descs;
+
+	for (i = 0; (i < cfiep->desc_count) && (i < MAX_DMA_DESCS_PER_EP); i++) {
+
+#if defined(PRINT_CFI_DMA_DESCS)
+		print_desc(ddesc, ep->ep.name, i);
+#endif
+		ret += ddesc->status.b.bytes;
+		ddesc++;
+	}
+
+	if (ret)
+		CFI_INFO("!!!!!!!!!! WARNING (%s) - residue=%d\n", __func__,
+			 ret);
+
+	return ret;
+}
+#endif
+
+/**
+ * This function completes the request for the EP. If there are
+ * additional requests for the EP in the queue they will be started.
+ */
+static void complete_ep(dwc_otg_pcd_ep_t * ep)
+{
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(ep->pcd);
+	struct device *dev = dwc_otg_pcd_to_dev(ep->pcd);
+	dwc_otg_dev_if_t *dev_if = core_if->dev_if;
+	dwc_otg_dev_in_ep_regs_t *in_ep_regs =
+	    dev_if->in_ep_regs[ep->dwc_ep.num];
+	deptsiz_data_t deptsiz;
+	dev_dma_desc_sts_t desc_sts;
+	dwc_otg_pcd_request_t *req = 0;
+	dwc_otg_dev_dma_desc_t *dma_desc;
+	uint32_t byte_count = 0;
+	int is_last = 0;
+	int i;
+
+	DWC_DEBUGPL(DBG_PCDV, "%s() %d-%s\n", __func__, ep->dwc_ep.num,
+		    (ep->dwc_ep.is_in ? "IN" : "OUT"));
+
+	/* Get any pending requests */
+	if (!DWC_CIRCLEQ_EMPTY(&ep->queue)) {
+		req = DWC_CIRCLEQ_FIRST(&ep->queue);
+		if (!req) {
+			DWC_PRINTF("complete_ep 0x%p, req = NULL!\n", ep);
+			return;
+		}
+	} else {
+		DWC_PRINTF("complete_ep 0x%p, ep->queue empty!\n", ep);
+		return;
+	}
+
+	DWC_DEBUGPL(DBG_PCD, "Requests %d\n", ep->pcd->request_pending);
+
+	if (ep->dwc_ep.is_in) {
+		deptsiz.d32 = DWC_READ_REG32(&in_ep_regs->dieptsiz);
+
+		if (core_if->dma_enable) {
+			if (core_if->dma_desc_enable == 0) {
+				if (deptsiz.b.xfersize == 0
+				    && deptsiz.b.pktcnt == 0) {
+					byte_count =
+					    ep->dwc_ep.xfer_len -
+					    ep->dwc_ep.xfer_count;
+
+					ep->dwc_ep.xfer_buff += byte_count;
+					ep->dwc_ep.dma_addr += byte_count;
+					ep->dwc_ep.xfer_count += byte_count;
+
+					DWC_DEBUGPL(DBG_PCDV,
+						    "%d-%s len=%d  xfersize=%d pktcnt=%d\n",
+						    ep->dwc_ep.num,
+						    (ep->dwc_ep.
+						     is_in ? "IN" : "OUT"),
+						    ep->dwc_ep.xfer_len,
+						    deptsiz.b.xfersize,
+						    deptsiz.b.pktcnt);
+
+					if (ep->dwc_ep.xfer_len <
+					    ep->dwc_ep.total_len) {
+						dwc_otg_ep_start_transfer
+						    (core_if, &ep->dwc_ep);
+					} else if (ep->dwc_ep.sent_zlp) {
+						/*
+						 * This fragment of code should initiate 0
+						 * length transfer in case if it is queued
+						 * a transfer with size divisible to EPs max
+						 * packet size and with usb_request zero field
+						 * is set, which means that after data is transfered,
+						 * it is also should be transfered
+						 * a 0 length packet at the end. For Slave and
+						 * Buffer DMA modes in this case SW has
+						 * to initiate 2 transfers one with transfer size,
+						 * and the second with 0 size. For Descriptor
+						 * DMA mode SW is able to initiate a transfer,
+						 * which will handle all the packets including
+						 * the last  0 length.
+						 */
+						ep->dwc_ep.sent_zlp = 0;
+						dwc_otg_ep_start_zl_transfer
+						    (core_if, &ep->dwc_ep);
+					} else {
+						is_last = 1;
+					}
+				} else {
+					if (ep->dwc_ep.type ==
+					    DWC_OTG_EP_TYPE_ISOC) {
+						req->actual = 0;
+						dwc_otg_request_done(ep, req, 0);
+
+						ep->dwc_ep.start_xfer_buff = 0;
+						ep->dwc_ep.xfer_buff = 0;
+						ep->dwc_ep.xfer_len = 0;
+
+						/* If there is a request in the queue start it. */
+						start_next_request(ep);
+					} else
+						DWC_WARN
+						("Incomplete transfer (%d - %s [siz=%d pkt=%d])\n",
+						ep->dwc_ep.num,
+						(ep->dwc_ep.is_in ? "IN" : "OUT"),
+						deptsiz.b.xfersize,
+						deptsiz.b.pktcnt);
+				}
+			} else {
+				dma_desc = ep->dwc_ep.desc_addr;
+				byte_count = 0;
+				ep->dwc_ep.sent_zlp = 0;
+
+#ifdef DWC_UTE_CFI
+				CFI_INFO("%s: BUFFER_MODE=%d\n", __func__,
+					 ep->dwc_ep.buff_mode);
+				if (ep->dwc_ep.buff_mode != BM_STANDARD) {
+					int residue;
+
+					residue = cfi_calc_desc_residue(ep);
+					if (residue < 0)
+						return;
+
+					byte_count = residue;
+				} else {
+#endif
+					for (i = 0; i < ep->dwc_ep.desc_cnt;
+					     ++i) {
+					desc_sts = dma_desc->status;
+					byte_count += desc_sts.b.bytes;
+					dma_desc++;
+				}
+#ifdef DWC_UTE_CFI
+				}
+#endif
+				if (byte_count == 0) {
+					ep->dwc_ep.xfer_count =
+					    ep->dwc_ep.total_len;
+					is_last = 1;
+				} else {
+					DWC_WARN("Incomplete transfer\n");
+				}
+			}
+		} else {
+			if (deptsiz.b.xfersize == 0 && deptsiz.b.pktcnt == 0) {
+				DWC_DEBUGPL(DBG_PCDV,
+					    "%d-%s len=%d  xfersize=%d pktcnt=%d\n",
+					    ep->dwc_ep.num,
+					    ep->dwc_ep.is_in ? "IN" : "OUT",
+					    ep->dwc_ep.xfer_len,
+					    deptsiz.b.xfersize,
+					    deptsiz.b.pktcnt);
+
+				/*      Check if the whole transfer was completed,
+				 *      if no, setup transfer for next portion of data
+				 */
+				if (ep->dwc_ep.xfer_len < ep->dwc_ep.total_len) {
+					dwc_otg_ep_start_transfer(core_if,
+								  &ep->dwc_ep);
+				} else if (ep->dwc_ep.sent_zlp) {
+					/*
+					 * This fragment of code should initiate 0
+					 * length trasfer in case if it is queued
+					 * a trasfer with size divisible to EPs max
+					 * packet size and with usb_request zero field
+					 * is set, which means that after data is transfered,
+					 * it is also should be transfered
+					 * a 0 length packet at the end. For Slave and
+					 * Buffer DMA modes in this case SW has
+					 * to initiate 2 transfers one with transfer size,
+					 * and the second with 0 size. For Desriptor
+					 * DMA mode SW is able to initiate a transfer,
+					 * which will handle all the packets including
+					 * the last  0 legth.
+					 */
+					ep->dwc_ep.sent_zlp = 0;
+					dwc_otg_ep_start_zl_transfer(core_if,
+								     &ep->dwc_ep);
+				} else {
+					is_last = 1;
+				}
+			} else {
+				DWC_WARN
+				    ("Incomplete transfer (%d-%s [siz=%d pkt=%d])\n",
+				     ep->dwc_ep.num,
+				     (ep->dwc_ep.is_in ? "IN" : "OUT"),
+				     deptsiz.b.xfersize, deptsiz.b.pktcnt);
+			}
+		}
+	} else {
+		dwc_otg_dev_out_ep_regs_t *out_ep_regs =
+		    dev_if->out_ep_regs[ep->dwc_ep.num];
+		desc_sts.d32 = 0;
+		if (core_if->dma_enable) {
+			if (core_if->dma_desc_enable) {
+				dma_desc = ep->dwc_ep.desc_addr;
+				byte_count = 0;
+				ep->dwc_ep.sent_zlp = 0;
+
+#ifdef DWC_UTE_CFI
+				CFI_INFO("%s: BUFFER_MODE=%d\n", __func__,
+					 ep->dwc_ep.buff_mode);
+				if (ep->dwc_ep.buff_mode != BM_STANDARD) {
+					int residue;
+					residue = cfi_calc_desc_residue(ep);
+					if (residue < 0)
+						return;
+					byte_count = residue;
+				} else {
+#endif
+
+					for (i = 0; i < ep->dwc_ep.desc_cnt;
+					     ++i) {
+						desc_sts = dma_desc->status;
+						byte_count += desc_sts.b.bytes;
+						dma_desc++;
+					}
+
+#ifdef DWC_UTE_CFI
+				}
+#endif
+				/* Checking for interrupt Out transfers with not
+				 * dword aligned mps sizes
+				 */
+				if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_INTR &&
+							(ep->dwc_ep.maxpacket%4)) {
+					ep->dwc_ep.xfer_count =
+					    ep->dwc_ep.total_len - byte_count;
+					if ((ep->dwc_ep.xfer_len %
+					     ep->dwc_ep.maxpacket)
+					    && (ep->dwc_ep.xfer_len /
+						ep->dwc_ep.maxpacket <
+						MAX_DMA_DESC_CNT))
+						ep->dwc_ep.xfer_len -=
+						    (ep->dwc_ep.desc_cnt -
+						     1) * ep->dwc_ep.maxpacket +
+						    ep->dwc_ep.xfer_len %
+						    ep->dwc_ep.maxpacket;
+					else
+						ep->dwc_ep.xfer_len -=
+						    ep->dwc_ep.desc_cnt *
+						    ep->dwc_ep.maxpacket;
+					if (ep->dwc_ep.xfer_len > 0) {
+						dwc_otg_ep_start_transfer
+						    (core_if, &ep->dwc_ep);
+					} else {
+						is_last = 1;
+					}
+				} else {
+					ep->dwc_ep.xfer_count =
+					    ep->dwc_ep.total_len - byte_count +
+					    ((4 -
+					      (ep->dwc_ep.
+					       total_len & 0x3)) & 0x3);
+					is_last = 1;
+				}
+			} else {
+				deptsiz.d32 = 0;
+				deptsiz.d32 =
+				    DWC_READ_REG32(&out_ep_regs->doeptsiz);
+
+				byte_count = (ep->dwc_ep.xfer_len -
+					      ep->dwc_ep.xfer_count -
+					      deptsiz.b.xfersize);
+				ep->dwc_ep.xfer_buff += byte_count;
+				ep->dwc_ep.dma_addr += byte_count;
+				ep->dwc_ep.xfer_count += byte_count;
+
+				/*      Check if the whole transfer was completed,
+				 *      if no, setup transfer for next portion of data
+				 */
+				if (ep->dwc_ep.xfer_len < ep->dwc_ep.total_len) {
+					dwc_otg_ep_start_transfer(core_if,
+								  &ep->dwc_ep);
+				} else if (ep->dwc_ep.sent_zlp) {
+					/*
+					 * This fragment of code should initiate 0
+					 * length trasfer in case if it is queued
+					 * a trasfer with size divisible to EPs max
+					 * packet size and with usb_request zero field
+					 * is set, which means that after data is transfered,
+					 * it is also should be transfered
+					 * a 0 length packet at the end. For Slave and
+					 * Buffer DMA modes in this case SW has
+					 * to initiate 2 transfers one with transfer size,
+					 * and the second with 0 size. For Desriptor
+					 * DMA mode SW is able to initiate a transfer,
+					 * which will handle all the packets including
+					 * the last  0 legth.
+					 */
+					ep->dwc_ep.sent_zlp = 0;
+					dwc_otg_ep_start_zl_transfer(core_if,
+								     &ep->dwc_ep);
+				} else {
+					is_last = 1;
+				}
+			}
+		} else {
+			/*      Check if the whole transfer was completed,
+			 *      if no, setup transfer for next portion of data
+			 */
+			if (ep->dwc_ep.xfer_len < ep->dwc_ep.total_len) {
+				dwc_otg_ep_start_transfer(core_if, &ep->dwc_ep);
+			} else if (ep->dwc_ep.sent_zlp) {
+				/*
+				 * This fragment of code should initiate 0
+				 * length transfer in case if it is queued
+				 * a transfer with size divisible to EPs max
+				 * packet size and with usb_request zero field
+				 * is set, which means that after data is transfered,
+				 * it is also should be transfered
+				 * a 0 length packet at the end. For Slave and
+				 * Buffer DMA modes in this case SW has
+				 * to initiate 2 transfers one with transfer size,
+				 * and the second with 0 size. For Descriptor
+				 * DMA mode SW is able to initiate a transfer,
+				 * which will handle all the packets including
+				 * the last  0 length.
+				 */
+				ep->dwc_ep.sent_zlp = 0;
+				dwc_otg_ep_start_zl_transfer(core_if,
+							     &ep->dwc_ep);
+			} else {
+				is_last = 1;
+			}
+		}
+
+		DWC_DEBUGPL(DBG_PCDV,
+			    "addr %p,	 %d-%s len=%d cnt=%d xsize=%d pktcnt=%d\n",
+			    &out_ep_regs->doeptsiz, ep->dwc_ep.num,
+			    ep->dwc_ep.is_in ? "IN" : "OUT",
+			    ep->dwc_ep.xfer_len, ep->dwc_ep.xfer_count,
+			    deptsiz.b.xfersize, deptsiz.b.pktcnt);
+	}
+
+	/* Complete the request */
+	if (is_last) {
+#ifdef DWC_UTE_CFI
+		if (ep->dwc_ep.buff_mode != BM_STANDARD) {
+			req->actual = ep->dwc_ep.cfi_req_len - byte_count;
+		} else {
+#endif
+			req->actual = ep->dwc_ep.xfer_count;
+#ifdef DWC_UTE_CFI
+		}
+#endif
+		if (req->dw_align_buf) {
+			if (!ep->dwc_ep.is_in) {
+				dwc_memcpy(req->buf, req->dw_align_buf, req->length);
+			}
+			DWC_DMA_FREE(dev, req->length, req->dw_align_buf,
+				     req->dw_align_buf_dma);
+		}
+
+		dwc_otg_request_done(ep, req, 0);
+
+		ep->dwc_ep.start_xfer_buff = 0;
+		ep->dwc_ep.xfer_buff = 0;
+		ep->dwc_ep.xfer_len = 0;
+
+		/* If there is a request in the queue start it. */
+		start_next_request(ep);
+	}
+}
+
+#ifdef DWC_EN_ISOC
+
+/**
+ * This function BNA interrupt for Isochronous EPs
+ *
+ */
+static void dwc_otg_pcd_handle_iso_bna(dwc_otg_pcd_ep_t * ep)
+{
+	dwc_ep_t *dwc_ep = &ep->dwc_ep;
+	volatile uint32_t *addr;
+	depctl_data_t depctl = {.d32 = 0 };
+	dwc_otg_pcd_t *pcd = ep->pcd;
+	dwc_otg_dev_dma_desc_t *dma_desc;
+	int i;
+
+	dma_desc =
+	    dwc_ep->iso_desc_addr + dwc_ep->desc_cnt * (dwc_ep->proc_buf_num);
+
+	if (dwc_ep->is_in) {
+		dev_dma_desc_sts_t sts = {.d32 = 0 };
+		for (i = 0; i < dwc_ep->desc_cnt; ++i, ++dma_desc) {
+			sts.d32 = dma_desc->status.d32;
+			sts.b_iso_in.bs = BS_HOST_READY;
+			dma_desc->status.d32 = sts.d32;
+		}
+	} else {
+		dev_dma_desc_sts_t sts = {.d32 = 0 };
+		for (i = 0; i < dwc_ep->desc_cnt; ++i, ++dma_desc) {
+			sts.d32 = dma_desc->status.d32;
+			sts.b_iso_out.bs = BS_HOST_READY;
+			dma_desc->status.d32 = sts.d32;
+		}
+	}
+
+	if (dwc_ep->is_in == 0) {
+		addr =
+		    &GET_CORE_IF(pcd)->dev_if->out_ep_regs[dwc_ep->
+							   num]->doepctl;
+	} else {
+		addr =
+		    &GET_CORE_IF(pcd)->dev_if->in_ep_regs[dwc_ep->num]->diepctl;
+	}
+	depctl.b.epena = 1;
+	DWC_MODIFY_REG32(addr, depctl.d32, depctl.d32);
+}
+
+/**
+ * This function sets latest iso packet information(non-PTI mode)
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param ep The EP to start the transfer on.
+ *
+ */
+void set_current_pkt_info(dwc_otg_core_if_t * core_if, dwc_ep_t * ep)
+{
+	deptsiz_data_t deptsiz = {.d32 = 0 };
+	dma_addr_t dma_addr;
+	uint32_t offset;
+
+	if (ep->proc_buf_num)
+		dma_addr = ep->dma_addr1;
+	else
+		dma_addr = ep->dma_addr0;
+
+	if (ep->is_in) {
+		deptsiz.d32 =
+		    DWC_READ_REG32(&core_if->dev_if->
+				   in_ep_regs[ep->num]->dieptsiz);
+		offset = ep->data_per_frame;
+	} else {
+		deptsiz.d32 =
+		    DWC_READ_REG32(&core_if->dev_if->
+				   out_ep_regs[ep->num]->doeptsiz);
+		offset =
+		    ep->data_per_frame +
+		    (0x4 & (0x4 - (ep->data_per_frame & 0x3)));
+	}
+
+	if (!deptsiz.b.xfersize) {
+		ep->pkt_info[ep->cur_pkt].length = ep->data_per_frame;
+		ep->pkt_info[ep->cur_pkt].offset =
+		    ep->cur_pkt_dma_addr - dma_addr;
+		ep->pkt_info[ep->cur_pkt].status = 0;
+	} else {
+		ep->pkt_info[ep->cur_pkt].length = ep->data_per_frame;
+		ep->pkt_info[ep->cur_pkt].offset =
+		    ep->cur_pkt_dma_addr - dma_addr;
+		ep->pkt_info[ep->cur_pkt].status = -DWC_E_NO_DATA;
+	}
+	ep->cur_pkt_addr += offset;
+	ep->cur_pkt_dma_addr += offset;
+	ep->cur_pkt++;
+}
+
+/**
+ * This function sets latest iso packet information(DDMA mode)
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param dwc_ep The EP to start the transfer on.
+ *
+ */
+static void set_ddma_iso_pkts_info(dwc_otg_core_if_t * core_if,
+				   dwc_ep_t * dwc_ep)
+{
+	dwc_otg_dev_dma_desc_t *dma_desc;
+	dev_dma_desc_sts_t sts = {.d32 = 0 };
+	iso_pkt_info_t *iso_packet;
+	uint32_t data_per_desc;
+	uint32_t offset;
+	int i, j;
+
+	iso_packet = dwc_ep->pkt_info;
+
+	/** Reinit closed DMA Descriptors*/
+	/** ISO OUT EP */
+	if (dwc_ep->is_in == 0) {
+		dma_desc =
+		    dwc_ep->iso_desc_addr +
+		    dwc_ep->desc_cnt * dwc_ep->proc_buf_num;
+		offset = 0;
+
+		for (i = 0; i < dwc_ep->desc_cnt - dwc_ep->pkt_per_frm;
+		     i += dwc_ep->pkt_per_frm) {
+			for (j = 0; j < dwc_ep->pkt_per_frm; ++j) {
+				data_per_desc =
+				    ((j + 1) * dwc_ep->maxpacket >
+				     dwc_ep->
+				     data_per_frame) ? dwc_ep->data_per_frame -
+				    j * dwc_ep->maxpacket : dwc_ep->maxpacket;
+				data_per_desc +=
+				    (data_per_desc % 4) ? (4 -
+							   data_per_desc %
+							   4) : 0;
+
+				sts.d32 = dma_desc->status.d32;
+
+				/* Write status in iso_packet_decsriptor  */
+				iso_packet->status =
+				    sts.b_iso_out.rxsts +
+				    (sts.b_iso_out.bs ^ BS_DMA_DONE);
+				if (iso_packet->status) {
+					iso_packet->status = -DWC_E_NO_DATA;
+				}
+
+				/* Received data length */
+				if (!sts.b_iso_out.rxbytes) {
+					iso_packet->length =
+					    data_per_desc -
+					    sts.b_iso_out.rxbytes;
+				} else {
+					iso_packet->length =
+					    data_per_desc -
+					    sts.b_iso_out.rxbytes + (4 -
+								     dwc_ep->data_per_frame
+								     % 4);
+				}
+
+				iso_packet->offset = offset;
+
+				offset += data_per_desc;
+				dma_desc++;
+				iso_packet++;
+			}
+		}
+
+		for (j = 0; j < dwc_ep->pkt_per_frm - 1; ++j) {
+			data_per_desc =
+			    ((j + 1) * dwc_ep->maxpacket >
+			     dwc_ep->data_per_frame) ? dwc_ep->data_per_frame -
+			    j * dwc_ep->maxpacket : dwc_ep->maxpacket;
+			data_per_desc +=
+			    (data_per_desc % 4) ? (4 - data_per_desc % 4) : 0;
+
+			sts.d32 = dma_desc->status.d32;
+
+			/* Write status in iso_packet_decsriptor  */
+			iso_packet->status =
+			    sts.b_iso_out.rxsts +
+			    (sts.b_iso_out.bs ^ BS_DMA_DONE);
+			if (iso_packet->status) {
+				iso_packet->status = -DWC_E_NO_DATA;
+			}
+
+			/* Received data length */
+			iso_packet->length =
+			    dwc_ep->data_per_frame - sts.b_iso_out.rxbytes;
+
+			iso_packet->offset = offset;
+
+			offset += data_per_desc;
+			iso_packet++;
+			dma_desc++;
+		}
+
+		sts.d32 = dma_desc->status.d32;
+
+		/* Write status in iso_packet_decsriptor  */
+		iso_packet->status =
+		    sts.b_iso_out.rxsts + (sts.b_iso_out.bs ^ BS_DMA_DONE);
+		if (iso_packet->status) {
+			iso_packet->status = -DWC_E_NO_DATA;
+		}
+		/* Received data length */
+		if (!sts.b_iso_out.rxbytes) {
+			iso_packet->length =
+			    dwc_ep->data_per_frame - sts.b_iso_out.rxbytes;
+		} else {
+			iso_packet->length =
+			    dwc_ep->data_per_frame - sts.b_iso_out.rxbytes +
+			    (4 - dwc_ep->data_per_frame % 4);
+		}
+
+		iso_packet->offset = offset;
+	} else {
+/** ISO IN EP */
+
+		dma_desc =
+		    dwc_ep->iso_desc_addr +
+		    dwc_ep->desc_cnt * dwc_ep->proc_buf_num;
+
+		for (i = 0; i < dwc_ep->desc_cnt - 1; i++) {
+			sts.d32 = dma_desc->status.d32;
+
+			/* Write status in iso packet descriptor */
+			iso_packet->status =
+			    sts.b_iso_in.txsts +
+			    (sts.b_iso_in.bs ^ BS_DMA_DONE);
+			if (iso_packet->status != 0) {
+				iso_packet->status = -DWC_E_NO_DATA;
+
+			}
+			/* Bytes has been transfered */
+			iso_packet->length =
+			    dwc_ep->data_per_frame - sts.b_iso_in.txbytes;
+
+			dma_desc++;
+			iso_packet++;
+		}
+
+		sts.d32 = dma_desc->status.d32;
+		while (sts.b_iso_in.bs == BS_DMA_BUSY) {
+			sts.d32 = dma_desc->status.d32;
+		}
+
+		/* Write status in iso packet descriptor ??? do be done with ERROR codes */
+		iso_packet->status =
+		    sts.b_iso_in.txsts + (sts.b_iso_in.bs ^ BS_DMA_DONE);
+		if (iso_packet->status != 0) {
+			iso_packet->status = -DWC_E_NO_DATA;
+		}
+
+		/* Bytes has been transfered */
+		iso_packet->length =
+		    dwc_ep->data_per_frame - sts.b_iso_in.txbytes;
+	}
+}
+
+/**
+ * This function reinitialize DMA Descriptors for Isochronous transfer
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param dwc_ep The EP to start the transfer on.
+ *
+ */
+static void reinit_ddma_iso_xfer(dwc_otg_core_if_t * core_if, dwc_ep_t * dwc_ep)
+{
+	int i, j;
+	dwc_otg_dev_dma_desc_t *dma_desc;
+	dma_addr_t dma_ad;
+	volatile uint32_t *addr;
+	dev_dma_desc_sts_t sts = {.d32 = 0 };
+	uint32_t data_per_desc;
+
+	if (dwc_ep->is_in == 0) {
+		addr = &core_if->dev_if->out_ep_regs[dwc_ep->num]->doepctl;
+	} else {
+		addr = &core_if->dev_if->in_ep_regs[dwc_ep->num]->diepctl;
+	}
+
+	if (dwc_ep->proc_buf_num == 0) {
+		/** Buffer 0 descriptors setup */
+		dma_ad = dwc_ep->dma_addr0;
+	} else {
+		/** Buffer 1 descriptors setup */
+		dma_ad = dwc_ep->dma_addr1;
+	}
+
+	/** Reinit closed DMA Descriptors*/
+	/** ISO OUT EP */
+	if (dwc_ep->is_in == 0) {
+		dma_desc =
+		    dwc_ep->iso_desc_addr +
+		    dwc_ep->desc_cnt * dwc_ep->proc_buf_num;
+
+		sts.b_iso_out.bs = BS_HOST_READY;
+		sts.b_iso_out.rxsts = 0;
+		sts.b_iso_out.l = 0;
+		sts.b_iso_out.sp = 0;
+		sts.b_iso_out.ioc = 0;
+		sts.b_iso_out.pid = 0;
+		sts.b_iso_out.framenum = 0;
+
+		for (i = 0; i < dwc_ep->desc_cnt - dwc_ep->pkt_per_frm;
+		     i += dwc_ep->pkt_per_frm) {
+			for (j = 0; j < dwc_ep->pkt_per_frm; ++j) {
+				data_per_desc =
+				    ((j + 1) * dwc_ep->maxpacket >
+				     dwc_ep->
+				     data_per_frame) ? dwc_ep->data_per_frame -
+				    j * dwc_ep->maxpacket : dwc_ep->maxpacket;
+				data_per_desc +=
+				    (data_per_desc % 4) ? (4 -
+							   data_per_desc %
+							   4) : 0;
+				sts.b_iso_out.rxbytes = data_per_desc;
+				dma_desc->buf = dma_ad;
+				dma_desc->status.d32 = sts.d32;
+
+				dma_ad += data_per_desc;
+				dma_desc++;
+			}
+		}
+
+		for (j = 0; j < dwc_ep->pkt_per_frm - 1; ++j) {
+
+			data_per_desc =
+			    ((j + 1) * dwc_ep->maxpacket >
+			     dwc_ep->data_per_frame) ? dwc_ep->data_per_frame -
+			    j * dwc_ep->maxpacket : dwc_ep->maxpacket;
+			data_per_desc +=
+			    (data_per_desc % 4) ? (4 - data_per_desc % 4) : 0;
+			sts.b_iso_out.rxbytes = data_per_desc;
+
+			dma_desc->buf = dma_ad;
+			dma_desc->status.d32 = sts.d32;
+
+			dma_desc++;
+			dma_ad += data_per_desc;
+		}
+
+		sts.b_iso_out.ioc = 1;
+		sts.b_iso_out.l = dwc_ep->proc_buf_num;
+
+		data_per_desc =
+		    ((j + 1) * dwc_ep->maxpacket >
+		     dwc_ep->data_per_frame) ? dwc_ep->data_per_frame -
+		    j * dwc_ep->maxpacket : dwc_ep->maxpacket;
+		data_per_desc +=
+		    (data_per_desc % 4) ? (4 - data_per_desc % 4) : 0;
+		sts.b_iso_out.rxbytes = data_per_desc;
+
+		dma_desc->buf = dma_ad;
+		dma_desc->status.d32 = sts.d32;
+	} else {
+/** ISO IN EP */
+
+		dma_desc =
+		    dwc_ep->iso_desc_addr +
+		    dwc_ep->desc_cnt * dwc_ep->proc_buf_num;
+
+		sts.b_iso_in.bs = BS_HOST_READY;
+		sts.b_iso_in.txsts = 0;
+		sts.b_iso_in.sp = 0;
+		sts.b_iso_in.ioc = 0;
+		sts.b_iso_in.pid = dwc_ep->pkt_per_frm;
+		sts.b_iso_in.framenum = dwc_ep->next_frame;
+		sts.b_iso_in.txbytes = dwc_ep->data_per_frame;
+		sts.b_iso_in.l = 0;
+
+		for (i = 0; i < dwc_ep->desc_cnt - 1; i++) {
+			dma_desc->buf = dma_ad;
+			dma_desc->status.d32 = sts.d32;
+
+			sts.b_iso_in.framenum += dwc_ep->bInterval;
+			dma_ad += dwc_ep->data_per_frame;
+			dma_desc++;
+		}
+
+		sts.b_iso_in.ioc = 1;
+		sts.b_iso_in.l = dwc_ep->proc_buf_num;
+
+		dma_desc->buf = dma_ad;
+		dma_desc->status.d32 = sts.d32;
+
+		dwc_ep->next_frame =
+		    sts.b_iso_in.framenum + dwc_ep->bInterval * 1;
+	}
+	dwc_ep->proc_buf_num = (dwc_ep->proc_buf_num ^ 1) & 0x1;
+}
+
+/**
+ * This function is to handle Iso EP transfer complete interrupt
+ * in case Iso out packet was dropped
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param dwc_ep The EP for wihich transfer complete was asserted
+ *
+ */
+static uint32_t handle_iso_out_pkt_dropped(dwc_otg_core_if_t * core_if,
+					   dwc_ep_t * dwc_ep)
+{
+	uint32_t dma_addr;
+	uint32_t drp_pkt;
+	uint32_t drp_pkt_cnt;
+	deptsiz_data_t deptsiz = {.d32 = 0 };
+	depctl_data_t depctl = {.d32 = 0 };
+	int i;
+
+	deptsiz.d32 =
+	    DWC_READ_REG32(&core_if->dev_if->
+			   out_ep_regs[dwc_ep->num]->doeptsiz);
+
+	drp_pkt = dwc_ep->pkt_cnt - deptsiz.b.pktcnt;
+	drp_pkt_cnt = dwc_ep->pkt_per_frm - (drp_pkt % dwc_ep->pkt_per_frm);
+
+	/* Setting dropped packets status */
+	for (i = 0; i < drp_pkt_cnt; ++i) {
+		dwc_ep->pkt_info[drp_pkt].status = -DWC_E_NO_DATA;
+		drp_pkt++;
+		deptsiz.b.pktcnt--;
+	}
+
+	if (deptsiz.b.pktcnt > 0) {
+		deptsiz.b.xfersize =
+		    dwc_ep->xfer_len - (dwc_ep->pkt_cnt -
+					deptsiz.b.pktcnt) * dwc_ep->maxpacket;
+	} else {
+		deptsiz.b.xfersize = 0;
+		deptsiz.b.pktcnt = 0;
+	}
+
+	DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[dwc_ep->num]->doeptsiz,
+			deptsiz.d32);
+
+	if (deptsiz.b.pktcnt > 0) {
+		if (dwc_ep->proc_buf_num) {
+			dma_addr =
+			    dwc_ep->dma_addr1 + dwc_ep->xfer_len -
+			    deptsiz.b.xfersize;
+		} else {
+			dma_addr =
+			    dwc_ep->dma_addr0 + dwc_ep->xfer_len -
+			    deptsiz.b.xfersize;;
+		}
+
+		DWC_WRITE_REG32(&core_if->dev_if->
+				out_ep_regs[dwc_ep->num]->doepdma, dma_addr);
+
+		/** Re-enable endpoint, clear nak  */
+		depctl.d32 = 0;
+		depctl.b.epena = 1;
+		depctl.b.cnak = 1;
+
+		DWC_MODIFY_REG32(&core_if->dev_if->
+				 out_ep_regs[dwc_ep->num]->doepctl, depctl.d32,
+				 depctl.d32);
+		return 0;
+	} else {
+		return 1;
+	}
+}
+
+/**
+ * This function sets iso packets information(PTI mode)
+ *
+ * @param core_if Programming view of DWC_otg controller.
+ * @param ep The EP to start the transfer on.
+ *
+ */
+static uint32_t set_iso_pkts_info(dwc_otg_core_if_t * core_if, dwc_ep_t * ep)
+{
+	int i, j;
+	dma_addr_t dma_ad;
+	iso_pkt_info_t *packet_info = ep->pkt_info;
+	uint32_t offset;
+	uint32_t frame_data;
+	deptsiz_data_t deptsiz;
+
+	if (ep->proc_buf_num == 0) {
+		/** Buffer 0 descriptors setup */
+		dma_ad = ep->dma_addr0;
+	} else {
+		/** Buffer 1 descriptors setup */
+		dma_ad = ep->dma_addr1;
+	}
+
+	if (ep->is_in) {
+		deptsiz.d32 =
+		    DWC_READ_REG32(&core_if->dev_if->in_ep_regs[ep->num]->
+				   dieptsiz);
+	} else {
+		deptsiz.d32 =
+		    DWC_READ_REG32(&core_if->dev_if->out_ep_regs[ep->num]->
+				   doeptsiz);
+	}
+
+	if (!deptsiz.b.xfersize) {
+		offset = 0;
+		for (i = 0; i < ep->pkt_cnt; i += ep->pkt_per_frm) {
+			frame_data = ep->data_per_frame;
+			for (j = 0; j < ep->pkt_per_frm; ++j) {
+
+				/* Packet status - is not set as initially
+				 * it is set to 0 and if packet was sent
+				 successfully, status field will remain 0*/
+
+				/* Bytes has been transfered */
+				packet_info->length =
+				    (ep->maxpacket <
+				     frame_data) ? ep->maxpacket : frame_data;
+
+				/* Received packet offset */
+				packet_info->offset = offset;
+				offset += packet_info->length;
+				frame_data -= packet_info->length;
+
+				packet_info++;
+			}
+		}
+		return 1;
+	} else {
+		/* This is a workaround for in case of Transfer Complete with
+		 * PktDrpSts interrupts merging - in this case Transfer complete
+		 * interrupt for Isoc Out Endpoint is asserted without PktDrpSts
+		 * set and with DOEPTSIZ register non zero. Investigations showed,
+		 * that this happens when Out packet is dropped, but because of
+		 * interrupts merging during first interrupt handling PktDrpSts
+		 * bit is cleared and for next merged interrupts it is not reset.
+		 * In this case SW hadles the interrupt as if PktDrpSts bit is set.
+		 */
+		if (ep->is_in) {
+			return 1;
+		} else {
+			return handle_iso_out_pkt_dropped(core_if, ep);
+		}
+	}
+}
+
+/**
+ * This function is to handle Iso EP transfer complete interrupt
+ *
+ * @param pcd The PCD
+ * @param ep The EP for which transfer complete was asserted
+ *
+ */
+static void complete_iso_ep(dwc_otg_pcd_t * pcd, dwc_otg_pcd_ep_t * ep)
+{
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(ep->pcd);
+	dwc_ep_t *dwc_ep = &ep->dwc_ep;
+	uint8_t is_last = 0;
+
+	if (ep->dwc_ep.next_frame == 0xffffffff) {
+		DWC_WARN("Next frame is not set!\n");
+		return;
+	}
+
+	if (core_if->dma_enable) {
+		if (core_if->dma_desc_enable) {
+			set_ddma_iso_pkts_info(core_if, dwc_ep);
+			reinit_ddma_iso_xfer(core_if, dwc_ep);
+			is_last = 1;
+		} else {
+			if (core_if->pti_enh_enable) {
+				if (set_iso_pkts_info(core_if, dwc_ep)) {
+					dwc_ep->proc_buf_num =
+					    (dwc_ep->proc_buf_num ^ 1) & 0x1;
+					dwc_otg_iso_ep_start_buf_transfer
+					    (core_if, dwc_ep);
+					is_last = 1;
+				}
+			} else {
+				set_current_pkt_info(core_if, dwc_ep);
+				if (dwc_ep->cur_pkt >= dwc_ep->pkt_cnt) {
+					is_last = 1;
+					dwc_ep->cur_pkt = 0;
+					dwc_ep->proc_buf_num =
+					    (dwc_ep->proc_buf_num ^ 1) & 0x1;
+					if (dwc_ep->proc_buf_num) {
+						dwc_ep->cur_pkt_addr =
+						    dwc_ep->xfer_buff1;
+						dwc_ep->cur_pkt_dma_addr =
+						    dwc_ep->dma_addr1;
+					} else {
+						dwc_ep->cur_pkt_addr =
+						    dwc_ep->xfer_buff0;
+						dwc_ep->cur_pkt_dma_addr =
+						    dwc_ep->dma_addr0;
+					}
+
+				}
+				dwc_otg_iso_ep_start_frm_transfer(core_if,
+								  dwc_ep);
+			}
+		}
+	} else {
+		set_current_pkt_info(core_if, dwc_ep);
+		if (dwc_ep->cur_pkt >= dwc_ep->pkt_cnt) {
+			is_last = 1;
+			dwc_ep->cur_pkt = 0;
+			dwc_ep->proc_buf_num = (dwc_ep->proc_buf_num ^ 1) & 0x1;
+			if (dwc_ep->proc_buf_num) {
+				dwc_ep->cur_pkt_addr = dwc_ep->xfer_buff1;
+				dwc_ep->cur_pkt_dma_addr = dwc_ep->dma_addr1;
+			} else {
+				dwc_ep->cur_pkt_addr = dwc_ep->xfer_buff0;
+				dwc_ep->cur_pkt_dma_addr = dwc_ep->dma_addr0;
+			}
+
+		}
+		dwc_otg_iso_ep_start_frm_transfer(core_if, dwc_ep);
+	}
+	if (is_last)
+		dwc_otg_iso_buffer_done(pcd, ep, ep->iso_req_handle);
+}
+#endif /* DWC_EN_ISOC */
+
+/**
+ * This function handle BNA interrupt for Non Isochronous EPs
+ *
+ */
+static void dwc_otg_pcd_handle_noniso_bna(dwc_otg_pcd_ep_t * ep)
+{
+	dwc_ep_t *dwc_ep = &ep->dwc_ep;
+	volatile uint32_t *addr;
+	depctl_data_t depctl = {.d32 = 0 };
+	dwc_otg_pcd_t *pcd = ep->pcd;
+	dwc_otg_dev_dma_desc_t *dma_desc;
+	dev_dma_desc_sts_t sts = {.d32 = 0 };
+	dwc_otg_core_if_t *core_if = ep->pcd->core_if;
+	int i, start;
+
+	if (!dwc_ep->desc_cnt)
+		DWC_WARN("Ep%d %s Descriptor count = %d \n", dwc_ep->num,
+			 (dwc_ep->is_in ? "IN" : "OUT"), dwc_ep->desc_cnt);
+
+	if (core_if->core_params->cont_on_bna && !dwc_ep->is_in
+							&& dwc_ep->type != DWC_OTG_EP_TYPE_CONTROL) {
+		uint32_t doepdma;
+		dwc_otg_dev_out_ep_regs_t *out_regs =
+			core_if->dev_if->out_ep_regs[dwc_ep->num];
+		doepdma = DWC_READ_REG32(&(out_regs->doepdma));
+		start = (doepdma - dwc_ep->dma_desc_addr)/sizeof(dwc_otg_dev_dma_desc_t);
+		dma_desc = &(dwc_ep->desc_addr[start]);
+	} else {
+		start = 0;
+		dma_desc = dwc_ep->desc_addr;
+	}
+
+
+	for (i = start; i < dwc_ep->desc_cnt; ++i, ++dma_desc) {
+		sts.d32 = dma_desc->status.d32;
+		sts.b.bs = BS_HOST_READY;
+		dma_desc->status.d32 = sts.d32;
+	}
+
+	if (dwc_ep->is_in == 0) {
+		addr =
+		    &GET_CORE_IF(pcd)->dev_if->out_ep_regs[dwc_ep->num]->
+		    doepctl;
+	} else {
+		addr =
+		    &GET_CORE_IF(pcd)->dev_if->in_ep_regs[dwc_ep->num]->diepctl;
+	}
+	depctl.b.epena = 1;
+	depctl.b.cnak = 1;
+	DWC_MODIFY_REG32(addr, 0, depctl.d32);
+}
+
+/**
+ * This function handles EP0 Control transfers.
+ *
+ * The state of the control transfers are tracked in
+ * <code>ep0state</code>.
+ */
+static void handle_ep0(dwc_otg_pcd_t * pcd)
+{
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+	dwc_otg_pcd_ep_t *ep0 = &pcd->ep0;
+	dev_dma_desc_sts_t desc_sts;
+	deptsiz0_data_t deptsiz;
+	uint32_t byte_count;
+
+#ifdef DEBUG_EP0
+	DWC_DEBUGPL(DBG_PCDV, "%s()\n", __func__);
+	print_ep0_state(pcd);
+#endif
+
+//      DWC_PRINTF("HANDLE EP0\n");
+
+	switch (pcd->ep0state) {
+	case EP0_DISCONNECT:
+		break;
+
+	case EP0_IDLE:
+		pcd->request_config = 0;
+
+		pcd_setup(pcd);
+		break;
+
+	case EP0_IN_DATA_PHASE:
+#ifdef DEBUG_EP0
+		DWC_DEBUGPL(DBG_PCD, "DATA_IN EP%d-%s: type=%d, mps=%d\n",
+			    ep0->dwc_ep.num, (ep0->dwc_ep.is_in ? "IN" : "OUT"),
+			    ep0->dwc_ep.type, ep0->dwc_ep.maxpacket);
+#endif
+
+		if (core_if->dma_enable != 0) {
+			/*
+			 * For EP0 we can only program 1 packet at a time so we
+			 * need to do the make calculations after each complete.
+			 * Call write_packet to make the calculations, as in
+			 * slave mode, and use those values to determine if we
+			 * can complete.
+			 */
+			if (core_if->dma_desc_enable == 0) {
+				deptsiz.d32 =
+				    DWC_READ_REG32(&core_if->
+						   dev_if->in_ep_regs[0]->
+						   dieptsiz);
+				byte_count =
+				    ep0->dwc_ep.xfer_len - deptsiz.b.xfersize;
+			} else {
+				desc_sts =
+				    core_if->dev_if->in_desc_addr->status;
+				byte_count =
+				    ep0->dwc_ep.xfer_len - desc_sts.b.bytes;
+			}
+			ep0->dwc_ep.xfer_count += byte_count;
+			ep0->dwc_ep.xfer_buff += byte_count;
+			ep0->dwc_ep.dma_addr += byte_count;
+		}
+		if (ep0->dwc_ep.xfer_count < ep0->dwc_ep.total_len) {
+			dwc_otg_ep0_continue_transfer(GET_CORE_IF(pcd),
+						      &ep0->dwc_ep);
+			DWC_DEBUGPL(DBG_PCD, "CONTINUE TRANSFER\n");
+		} else if (ep0->dwc_ep.sent_zlp) {
+			dwc_otg_ep0_continue_transfer(GET_CORE_IF(pcd),
+						      &ep0->dwc_ep);
+			ep0->dwc_ep.sent_zlp = 0;
+			DWC_DEBUGPL(DBG_PCD, "CONTINUE TRANSFER sent zlp\n");
+		} else {
+			ep0_complete_request(ep0);
+			DWC_DEBUGPL(DBG_PCD, "COMPLETE TRANSFER\n");
+		}
+		break;
+	case EP0_OUT_DATA_PHASE:
+#ifdef DEBUG_EP0
+		DWC_DEBUGPL(DBG_PCD, "DATA_OUT EP%d-%s: type=%d, mps=%d\n",
+			    ep0->dwc_ep.num, (ep0->dwc_ep.is_in ? "IN" : "OUT"),
+			    ep0->dwc_ep.type, ep0->dwc_ep.maxpacket);
+#endif
+		if (core_if->dma_enable != 0) {
+			if (core_if->dma_desc_enable == 0) {
+				deptsiz.d32 =
+				    DWC_READ_REG32(&core_if->
+						   dev_if->out_ep_regs[0]->
+						   doeptsiz);
+				byte_count =
+				    ep0->dwc_ep.maxpacket - deptsiz.b.xfersize;
+			} else {
+				desc_sts =
+				    core_if->dev_if->out_desc_addr->status;
+				byte_count =
+				    ep0->dwc_ep.maxpacket - desc_sts.b.bytes;
+			}
+			ep0->dwc_ep.xfer_count += byte_count;
+			ep0->dwc_ep.xfer_buff += byte_count;
+			ep0->dwc_ep.dma_addr += byte_count;
+		}
+		if (ep0->dwc_ep.xfer_count < ep0->dwc_ep.total_len) {
+			dwc_otg_ep0_continue_transfer(GET_CORE_IF(pcd),
+						      &ep0->dwc_ep);
+			DWC_DEBUGPL(DBG_PCD, "CONTINUE TRANSFER\n");
+		} else if (ep0->dwc_ep.sent_zlp) {
+			dwc_otg_ep0_continue_transfer(GET_CORE_IF(pcd),
+						      &ep0->dwc_ep);
+			ep0->dwc_ep.sent_zlp = 0;
+			DWC_DEBUGPL(DBG_PCD, "CONTINUE TRANSFER sent zlp\n");
+		} else {
+			ep0_complete_request(ep0);
+			DWC_DEBUGPL(DBG_PCD, "COMPLETE TRANSFER\n");
+		}
+		break;
+
+	case EP0_IN_STATUS_PHASE:
+	case EP0_OUT_STATUS_PHASE:
+		DWC_DEBUGPL(DBG_PCD, "CASE: EP0_STATUS\n");
+		ep0_complete_request(ep0);
+		pcd->ep0state = EP0_IDLE;
+		ep0->stopped = 1;
+		ep0->dwc_ep.is_in = 0;	/* OUT for next SETUP */
+
+		/* Prepare for more SETUP Packets */
+		if (core_if->dma_enable) {
+			ep0_out_start(core_if, pcd);
+		}
+		break;
+
+	case EP0_STALL:
+		DWC_ERROR("EP0 STALLed, should not get here pcd_setup()\n");
+		break;
+	}
+#ifdef DEBUG_EP0
+	print_ep0_state(pcd);
+#endif
+}
+
+/**
+ * Restart transfer
+ */
+static void restart_transfer(dwc_otg_pcd_t * pcd, const uint32_t epnum)
+{
+	dwc_otg_core_if_t *core_if;
+	dwc_otg_dev_if_t *dev_if;
+	deptsiz_data_t dieptsiz = {.d32 = 0 };
+	dwc_otg_pcd_ep_t *ep;
+
+	ep = get_in_ep(pcd, epnum);
+
+#ifdef DWC_EN_ISOC
+	if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) {
+		return;
+	}
+#endif /* DWC_EN_ISOC  */
+
+	core_if = GET_CORE_IF(pcd);
+	dev_if = core_if->dev_if;
+
+	dieptsiz.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dieptsiz);
+
+	DWC_DEBUGPL(DBG_PCD, "xfer_buff=%p xfer_count=%0x xfer_len=%0x"
+		    " stopped=%d\n", ep->dwc_ep.xfer_buff,
+		    ep->dwc_ep.xfer_count, ep->dwc_ep.xfer_len, ep->stopped);
+	/*
+	 * If xfersize is 0 and pktcnt in not 0, resend the last packet.
+	 */
+	if (dieptsiz.b.pktcnt && dieptsiz.b.xfersize == 0 &&
+	    ep->dwc_ep.start_xfer_buff != 0) {
+		if (ep->dwc_ep.total_len <= ep->dwc_ep.maxpacket) {
+			ep->dwc_ep.xfer_count = 0;
+			ep->dwc_ep.xfer_buff = ep->dwc_ep.start_xfer_buff;
+			ep->dwc_ep.xfer_len = ep->dwc_ep.xfer_count;
+		} else {
+			ep->dwc_ep.xfer_count -= ep->dwc_ep.maxpacket;
+			/* convert packet size to dwords. */
+			ep->dwc_ep.xfer_buff -= ep->dwc_ep.maxpacket;
+			ep->dwc_ep.xfer_len = ep->dwc_ep.xfer_count;
+		}
+		ep->stopped = 0;
+		DWC_DEBUGPL(DBG_PCD, "xfer_buff=%p xfer_count=%0x "
+			    "xfer_len=%0x stopped=%d\n",
+			    ep->dwc_ep.xfer_buff,
+			    ep->dwc_ep.xfer_count, ep->dwc_ep.xfer_len,
+			    ep->stopped);
+		if (epnum == 0) {
+			dwc_otg_ep0_start_transfer(core_if, &ep->dwc_ep);
+		} else {
+			dwc_otg_ep_start_transfer(core_if, &ep->dwc_ep);
+		}
+	}
+}
+
+/*
+ * This function create new nextep sequnce based on Learn Queue.
+ *
+ * @param core_if Programming view of DWC_otg controller
+ */
+static void predict_nextep_seq( dwc_otg_core_if_t * core_if)
+{
+	dwc_otg_device_global_regs_t *dev_global_regs =
+	    core_if->dev_if->dev_global_regs;
+	const uint32_t TOKEN_Q_DEPTH = core_if->hwcfg2.b.dev_token_q_depth;
+	/* Number of Token Queue Registers */
+	const int DTKNQ_REG_CNT = (TOKEN_Q_DEPTH + 7) / 8;
+	dtknq1_data_t dtknqr1;
+	uint32_t in_tkn_epnums[4];
+	uint8_t seqnum[MAX_EPS_CHANNELS];
+	uint8_t intkn_seq[1 << 5];
+	grstctl_t resetctl = {.d32 = 0 };
+	uint8_t temp;
+	int ndx = 0;
+	int start = 0;
+	int end = 0;
+	int sort_done = 0;
+	int i = 0;
+	volatile uint32_t *addr = &dev_global_regs->dtknqr1;
+
+
+	DWC_DEBUGPL(DBG_PCD,"dev_token_q_depth=%d\n",TOKEN_Q_DEPTH);
+
+	/* Read the DTKNQ Registers */
+	for (i = 0; i < DTKNQ_REG_CNT; i++) {
+		in_tkn_epnums[i] = DWC_READ_REG32(addr);
+		DWC_DEBUGPL(DBG_PCDV, "DTKNQR%d=0x%08x\n", i + 1,
+			    in_tkn_epnums[i]);
+		if (addr == &dev_global_regs->dvbusdis) {
+			addr = &dev_global_regs->dtknqr3_dthrctl;
+		} else {
+			++addr;
+		}
+
+	}
+
+	/* Copy the DTKNQR1 data to the bit field. */
+	dtknqr1.d32 = in_tkn_epnums[0];
+	if (dtknqr1.b.wrap_bit) {
+		ndx = dtknqr1.b.intknwptr;
+		end = ndx -1;
+		if (end < 0)
+			end = TOKEN_Q_DEPTH -1;
+	} else {
+		ndx = 0;
+		end = dtknqr1.b.intknwptr -1;
+		if (end < 0)
+			end = 0;
+	}
+	start = ndx;
+
+	/* Fill seqnum[] by initial values: EP number + 31 */
+	for (i=0; i <= core_if->dev_if->num_in_eps; i++) {
+		seqnum[i] = i +31;
+	}
+
+	/* Fill intkn_seq[] from in_tkn_epnums[0] */
+	for (i=0; i < 6; i++)
+		intkn_seq[i] = (in_tkn_epnums[0] >> ((7-i) * 4)) & 0xf;
+
+	if (TOKEN_Q_DEPTH > 6) {
+		/* Fill intkn_seq[] from in_tkn_epnums[1] */
+		for (i=6; i < 14; i++)
+			intkn_seq[i] =
+			    (in_tkn_epnums[1] >> ((7 - (i - 6)) * 4)) & 0xf;
+	}
+
+	if (TOKEN_Q_DEPTH > 14) {
+		/* Fill intkn_seq[] from in_tkn_epnums[1] */
+		for (i=14; i < 22; i++)
+			intkn_seq[i] =
+			    (in_tkn_epnums[2] >> ((7 - (i - 14)) * 4)) & 0xf;
+	}
+
+	if (TOKEN_Q_DEPTH > 22) {
+		/* Fill intkn_seq[] from in_tkn_epnums[1] */
+		for (i=22; i < 30; i++)
+			intkn_seq[i] =
+			    (in_tkn_epnums[3] >> ((7 - (i - 22)) * 4)) & 0xf;
+	}
+
+	DWC_DEBUGPL(DBG_PCDV, "%s start=%d end=%d intkn_seq[]:\n", __func__,
+		    start, end);
+	for (i=0; i<TOKEN_Q_DEPTH; i++)
+		DWC_DEBUGPL(DBG_PCDV,"%d\n", intkn_seq[i]);
+
+	/* Update seqnum based on intkn_seq[] */
+	i = 0;
+	do {
+		seqnum[intkn_seq[ndx]] = i;
+		ndx++;
+		i++;
+		if (ndx == TOKEN_Q_DEPTH)
+			ndx = 0;
+	} while ( i < TOKEN_Q_DEPTH );
+
+	/* Mark non active EP's in seqnum[] by 0xff */
+	for (i=0; i<=core_if->dev_if->num_in_eps; i++) {
+		if (core_if->nextep_seq[i] == 0xff )
+			seqnum[i] = 0xff;
+	}
+
+	/* Sort seqnum[] */
+	sort_done = 0;
+	while (!sort_done) {
+		sort_done = 1;
+		for (i=0; i<core_if->dev_if->num_in_eps; i++) {
+			if (seqnum[i] > seqnum[i+1]) {
+				temp = seqnum[i];
+				seqnum[i] = seqnum[i+1];
+				seqnum[i+1] = temp;
+				sort_done = 0;
+			}
+		}
+	}
+
+	ndx = start + seqnum[0];
+	if (ndx >= TOKEN_Q_DEPTH)
+		ndx = ndx % TOKEN_Q_DEPTH;
+	core_if->first_in_nextep_seq = intkn_seq[ndx];
+
+	/* Update seqnum[] by EP numbers  */
+	for (i=0; i<=core_if->dev_if->num_in_eps; i++) {
+		ndx = start + i;
+		if (seqnum[i] < 31) {
+			ndx = start + seqnum[i];
+			if (ndx >= TOKEN_Q_DEPTH)
+				ndx = ndx % TOKEN_Q_DEPTH;
+			seqnum[i] = intkn_seq[ndx];
+		} else {
+			if (seqnum[i] < 0xff) {
+				seqnum[i] = seqnum[i] - 31;
+			} else {
+				break;
+			}
+		}
+	}
+
+	/* Update nextep_seq[] based on seqnum[] */
+	for (i=0; i<core_if->dev_if->num_in_eps; i++) {
+		if (seqnum[i] != 0xff) {
+			if (seqnum[i+1] != 0xff) {
+				core_if->nextep_seq[seqnum[i]] = seqnum[i+1];
+			} else {
+				core_if->nextep_seq[seqnum[i]] = core_if->first_in_nextep_seq;
+				break;
+			}
+		} else {
+			break;
+		}
+	}
+
+	DWC_DEBUGPL(DBG_PCDV, "%s first_in_nextep_seq= %2d; nextep_seq[]:\n",
+		__func__, core_if->first_in_nextep_seq);
+	for (i=0; i <= core_if->dev_if->num_in_eps; i++) {
+		DWC_DEBUGPL(DBG_PCDV,"%2d\n", core_if->nextep_seq[i]);
+	}
+
+	/* Flush the Learning Queue */
+	resetctl.d32 = DWC_READ_REG32(&core_if->core_global_regs->grstctl);
+	resetctl.b.intknqflsh = 1;
+	DWC_WRITE_REG32(&core_if->core_global_regs->grstctl, resetctl.d32);
+
+
+}
+
+/**
+ * handle the IN EP disable interrupt.
+ */
+static inline void handle_in_ep_disable_intr(dwc_otg_pcd_t * pcd,
+					     const uint32_t epnum)
+{
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+	dwc_otg_dev_if_t *dev_if = core_if->dev_if;
+	deptsiz_data_t dieptsiz = {.d32 = 0 };
+	dctl_data_t dctl = {.d32 = 0 };
+	dwc_otg_pcd_ep_t *ep;
+	dwc_ep_t *dwc_ep;
+	gintmsk_data_t gintmsk_data;
+	depctl_data_t depctl;
+	uint32_t diepdma;
+	uint32_t remain_to_transfer = 0;
+	uint8_t i;
+	uint32_t xfer_size;
+
+	ep = get_in_ep(pcd, epnum);
+	dwc_ep = &ep->dwc_ep;
+
+	if (dwc_ep->type == DWC_OTG_EP_TYPE_ISOC) {
+		dwc_otg_flush_tx_fifo(core_if, dwc_ep->tx_fifo_num);
+		complete_ep(ep);
+		return;
+	}
+
+	DWC_DEBUGPL(DBG_PCD, "diepctl%d=%0x\n", epnum,
+		    DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->diepctl));
+	dieptsiz.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->dieptsiz);
+	depctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->diepctl);
+
+	DWC_DEBUGPL(DBG_ANY, "pktcnt=%d size=%d\n",
+		    dieptsiz.b.pktcnt, dieptsiz.b.xfersize);
+
+	if ((core_if->start_predict == 0) || (depctl.b.eptype & 1)) {
+		if (ep->stopped) {
+			if (core_if->en_multiple_tx_fifo)
+				/* Flush the Tx FIFO */
+				dwc_otg_flush_tx_fifo(core_if, dwc_ep->tx_fifo_num);
+			/* Clear the Global IN NP NAK */
+			dctl.d32 = 0;
+			dctl.b.cgnpinnak = 1;
+			DWC_MODIFY_REG32(&dev_if->dev_global_regs->dctl, dctl.d32, dctl.d32);
+			/* Restart the transaction */
+			if (dieptsiz.b.pktcnt != 0 || dieptsiz.b.xfersize != 0) {
+				restart_transfer(pcd, epnum);
+			}
+		} else {
+			/* Restart the transaction */
+			if (dieptsiz.b.pktcnt != 0 || dieptsiz.b.xfersize != 0) {
+				restart_transfer(pcd, epnum);
+			}
+			DWC_DEBUGPL(DBG_ANY, "STOPPED!!!\n");
+		}
+		return;
+	}
+
+	if (core_if->start_predict > 2) {	// NP IN EP
+		core_if->start_predict--;
+		return;
+	}
+
+	core_if->start_predict--;
+
+	if (core_if->start_predict == 1) {	// All NP IN Ep's disabled now
+
+		predict_nextep_seq(core_if);
+
+		/* Update all active IN EP's NextEP field based of nextep_seq[] */
+		for ( i = 0; i <= core_if->dev_if->num_in_eps; i++) {
+			depctl.d32 =
+			    DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl);
+			if (core_if->nextep_seq[i] != 0xff) {	// Active NP IN EP
+				depctl.b.nextep = core_if->nextep_seq[i];
+				DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->diepctl, depctl.d32);
+			}
+		}
+		/* Flush Shared NP TxFIFO */
+		dwc_otg_flush_tx_fifo(core_if, 0);
+		/* Rewind buffers */
+		if (!core_if->dma_desc_enable) {
+			i = core_if->first_in_nextep_seq;
+			do {
+				ep = get_in_ep(pcd, i);
+				dieptsiz.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[i]->dieptsiz);
+				xfer_size = ep->dwc_ep.total_len - ep->dwc_ep.xfer_count;
+				if (xfer_size > ep->dwc_ep.maxxfer)
+					xfer_size = ep->dwc_ep.maxxfer;
+				depctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl);
+				if (dieptsiz.b.pktcnt != 0) {
+					if (xfer_size == 0) {
+						remain_to_transfer = 0;
+					} else {
+						if ((xfer_size % ep->dwc_ep.maxpacket) == 0) {
+							remain_to_transfer =
+								dieptsiz.b.pktcnt * ep->dwc_ep.maxpacket;
+						} else {
+							remain_to_transfer = ((dieptsiz.b.pktcnt -1) * ep->dwc_ep.maxpacket)
+								+ (xfer_size % ep->dwc_ep.maxpacket);
+						}
+					}
+					diepdma = DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepdma);
+					dieptsiz.b.xfersize = remain_to_transfer;
+					DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->dieptsiz, dieptsiz.d32);
+					diepdma = ep->dwc_ep.dma_addr + (xfer_size - remain_to_transfer);
+					DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->diepdma, diepdma);
+				}
+				i = core_if->nextep_seq[i];
+			} while (i != core_if->first_in_nextep_seq);
+		} else { // dma_desc_enable
+				DWC_PRINTF("%s Learning Queue not supported in DDMA\n", __func__);
+		}
+
+		/* Restart transfers in predicted sequences */
+		i = core_if->first_in_nextep_seq;
+		do {
+			dieptsiz.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[i]->dieptsiz);
+			depctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl);
+			if (dieptsiz.b.pktcnt != 0) {
+				depctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl);
+				depctl.b.epena = 1;
+				depctl.b.cnak = 1;
+				DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->diepctl, depctl.d32);
+			}
+			i = core_if->nextep_seq[i];
+		} while (i != core_if->first_in_nextep_seq);
+
+		/* Clear the global non-periodic IN NAK handshake */
+		dctl.d32 = 0;
+		dctl.b.cgnpinnak = 1;
+		DWC_MODIFY_REG32(&dev_if->dev_global_regs->dctl, dctl.d32, dctl.d32);
+
+		/* Unmask EP Mismatch interrupt */
+		gintmsk_data.d32 = 0;
+		gintmsk_data.b.epmismatch = 1;
+		DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, 0, gintmsk_data.d32);
+
+		core_if->start_predict = 0;
+
+	}
+}
+
+/**
+ * Handler for the IN EP timeout handshake interrupt.
+ */
+static inline void handle_in_ep_timeout_intr(dwc_otg_pcd_t * pcd,
+					     const uint32_t epnum)
+{
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+	dwc_otg_dev_if_t *dev_if = core_if->dev_if;
+
+#ifdef DEBUG
+	deptsiz_data_t dieptsiz = {.d32 = 0 };
+	uint32_t num = 0;
+#endif
+	dctl_data_t dctl = {.d32 = 0 };
+	dwc_otg_pcd_ep_t *ep;
+
+	gintmsk_data_t intr_mask = {.d32 = 0 };
+
+	ep = get_in_ep(pcd, epnum);
+
+	/* Disable the NP Tx Fifo Empty Interrrupt */
+	if (!core_if->dma_enable) {
+		intr_mask.b.nptxfempty = 1;
+		DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk,
+				 intr_mask.d32, 0);
+	}
+	/** @todo NGS Check EP type.
+	 * Implement for Periodic EPs */
+	/*
+	 * Non-periodic EP
+	 */
+	/* Enable the Global IN NAK Effective Interrupt */
+	intr_mask.b.ginnakeff = 1;
+	DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk, 0, intr_mask.d32);
+
+	/* Set Global IN NAK */
+	dctl.b.sgnpinnak = 1;
+	DWC_MODIFY_REG32(&dev_if->dev_global_regs->dctl, dctl.d32, dctl.d32);
+
+	ep->stopped = 1;
+
+#ifdef DEBUG
+	dieptsiz.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[num]->dieptsiz);
+	DWC_DEBUGPL(DBG_ANY, "pktcnt=%d size=%d\n",
+		    dieptsiz.b.pktcnt, dieptsiz.b.xfersize);
+#endif
+
+#ifdef DISABLE_PERIODIC_EP
+	/*
+	 * Set the NAK bit for this EP to
+	 * start the disable process.
+	 */
+	diepctl.d32 = 0;
+	diepctl.b.snak = 1;
+	DWC_MODIFY_REG32(&dev_if->in_ep_regs[num]->diepctl, diepctl.d32,
+			 diepctl.d32);
+	ep->disabling = 1;
+	ep->stopped = 1;
+#endif
+}
+
+/**
+ * Handler for the IN EP NAK interrupt.
+ */
+static inline int32_t handle_in_ep_nak_intr(dwc_otg_pcd_t * pcd,
+					    const uint32_t epnum)
+{
+	/** @todo implement ISR */
+	dwc_otg_core_if_t *core_if;
+	diepmsk_data_t intr_mask = {.d32 = 0 };
+
+	DWC_PRINTF("INTERRUPT Handler not implemented for %s\n", "IN EP NAK");
+	core_if = GET_CORE_IF(pcd);
+	intr_mask.b.nak = 1;
+
+	if (core_if->multiproc_int_enable) {
+		DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->
+				 diepeachintmsk[epnum], intr_mask.d32, 0);
+	} else {
+		DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->diepmsk,
+				 intr_mask.d32, 0);
+	}
+
+	return 1;
+}
+
+/**
+ * Handler for the OUT EP Babble interrupt.
+ */
+static inline int32_t handle_out_ep_babble_intr(dwc_otg_pcd_t * pcd,
+						const uint32_t epnum)
+{
+	/** @todo implement ISR */
+	dwc_otg_core_if_t *core_if;
+	doepmsk_data_t intr_mask = {.d32 = 0 };
+
+	DWC_PRINTF("INTERRUPT Handler not implemented for %s\n",
+		   "OUT EP Babble");
+	core_if = GET_CORE_IF(pcd);
+	intr_mask.b.babble = 1;
+
+	if (core_if->multiproc_int_enable) {
+		DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->
+				 doepeachintmsk[epnum], intr_mask.d32, 0);
+	} else {
+		DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->doepmsk,
+				 intr_mask.d32, 0);
+	}
+
+	return 1;
+}
+
+/**
+ * Handler for the OUT EP NAK interrupt.
+ */
+static inline int32_t handle_out_ep_nak_intr(dwc_otg_pcd_t * pcd,
+					     const uint32_t epnum)
+{
+	/** @todo implement ISR */
+	dwc_otg_core_if_t *core_if;
+	doepmsk_data_t intr_mask = {.d32 = 0 };
+
+	DWC_DEBUGPL(DBG_ANY, "INTERRUPT Handler not implemented for %s\n", "OUT EP NAK");
+	core_if = GET_CORE_IF(pcd);
+	intr_mask.b.nak = 1;
+
+	if (core_if->multiproc_int_enable) {
+		DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->
+				 doepeachintmsk[epnum], intr_mask.d32, 0);
+	} else {
+		DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->doepmsk,
+				 intr_mask.d32, 0);
+	}
+
+	return 1;
+}
+
+/**
+ * Handler for the OUT EP NYET interrupt.
+ */
+static inline int32_t handle_out_ep_nyet_intr(dwc_otg_pcd_t * pcd,
+					      const uint32_t epnum)
+{
+	/** @todo implement ISR */
+	dwc_otg_core_if_t *core_if;
+	doepmsk_data_t intr_mask = {.d32 = 0 };
+
+	DWC_PRINTF("INTERRUPT Handler not implemented for %s\n", "OUT EP NYET");
+	core_if = GET_CORE_IF(pcd);
+	intr_mask.b.nyet = 1;
+
+	if (core_if->multiproc_int_enable) {
+		DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->
+				 doepeachintmsk[epnum], intr_mask.d32, 0);
+	} else {
+		DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->doepmsk,
+				 intr_mask.d32, 0);
+	}
+
+	return 1;
+}
+
+/**
+ * This interrupt indicates that an IN EP has a pending Interrupt.
+ * The sequence for handling the IN EP interrupt is shown below:
+ * -#	Read the Device All Endpoint Interrupt register
+ * -#	Repeat the following for each IN EP interrupt bit set (from
+ *		LSB to MSB).
+ * -#	Read the Device Endpoint Interrupt (DIEPINTn) register
+ * -#	If "Transfer Complete" call the request complete function
+ * -#	If "Endpoint Disabled" complete the EP disable procedure.
+ * -#	If "AHB Error Interrupt" log error
+ * -#	If "Time-out Handshake" log error
+ * -#	If "IN Token Received when TxFIFO Empty" write packet to Tx
+ *		FIFO.
+ * -#	If "IN Token EP Mismatch" (disable, this is handled by EP
+ *		Mismatch Interrupt)
+ */
+static int32_t dwc_otg_pcd_handle_in_ep_intr(dwc_otg_pcd_t * pcd)
+{
+#define CLEAR_IN_EP_INTR(__core_if,__epnum,__intr) \
+do { \
+		diepint_data_t diepint = {.d32=0}; \
+		diepint.b.__intr = 1; \
+		DWC_WRITE_REG32(&__core_if->dev_if->in_ep_regs[__epnum]->diepint, \
+		diepint.d32); \
+} while (0)
+
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+	dwc_otg_dev_if_t *dev_if = core_if->dev_if;
+	diepint_data_t diepint = {.d32 = 0 };
+	depctl_data_t depctl = {.d32 = 0 };
+	uint32_t ep_intr;
+	uint32_t epnum = 0;
+	dwc_otg_pcd_ep_t *ep;
+	dwc_ep_t *dwc_ep;
+	gintmsk_data_t intr_mask = {.d32 = 0 };
+
+	DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, pcd);
+
+	/* Read in the device interrupt bits */
+	ep_intr = dwc_otg_read_dev_all_in_ep_intr(core_if);
+
+	/* Service the Device IN interrupts for each endpoint */
+	while (ep_intr) {
+		if (ep_intr & 0x1) {
+			uint32_t empty_msk;
+			/* Get EP pointer */
+			ep = get_in_ep(pcd, epnum);
+			dwc_ep = &ep->dwc_ep;
+
+			depctl.d32 =
+			    DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->diepctl);
+			empty_msk =
+			    DWC_READ_REG32(&dev_if->
+					   dev_global_regs->dtknqr4_fifoemptymsk);
+
+			DWC_DEBUGPL(DBG_PCDV,
+				    "IN EP INTERRUPT - %d\nepmty_msk - %8x  diepctl - %8x\n",
+				    epnum, empty_msk, depctl.d32);
+
+			DWC_DEBUGPL(DBG_PCD,
+				    "EP%d-%s: type=%d, mps=%d\n",
+				    dwc_ep->num, (dwc_ep->is_in ? "IN" : "OUT"),
+				    dwc_ep->type, dwc_ep->maxpacket);
+
+			diepint.d32 =
+			    dwc_otg_read_dev_in_ep_intr(core_if, dwc_ep);
+
+			DWC_DEBUGPL(DBG_PCDV,
+				    "EP %d Interrupt Register - 0x%x\n", epnum,
+				    diepint.d32);
+			/* Transfer complete */
+			if (diepint.b.xfercompl) {
+				/* Disable the NP Tx FIFO Empty
+				 * Interrupt */
+				if (core_if->en_multiple_tx_fifo == 0) {
+					intr_mask.b.nptxfempty = 1;
+					DWC_MODIFY_REG32
+					    (&core_if->core_global_regs->gintmsk,
+					     intr_mask.d32, 0);
+				} else {
+					/* Disable the Tx FIFO Empty Interrupt for this EP */
+					uint32_t fifoemptymsk =
+					    0x1 << dwc_ep->num;
+					DWC_MODIFY_REG32(&core_if->
+							 dev_if->dev_global_regs->dtknqr4_fifoemptymsk,
+							 fifoemptymsk, 0);
+				}
+				/* Clear the bit in DIEPINTn for this interrupt */
+				CLEAR_IN_EP_INTR(core_if, epnum, xfercompl);
+
+				/* Complete the transfer */
+				if (epnum == 0) {
+					handle_ep0(pcd);
+				}
+#ifdef DWC_EN_ISOC
+				else if (dwc_ep->type == DWC_OTG_EP_TYPE_ISOC) {
+					if (!ep->stopped)
+						complete_iso_ep(pcd, ep);
+				}
+#endif /* DWC_EN_ISOC */
+#ifdef DWC_UTE_PER_IO
+				else if (dwc_ep->type == DWC_OTG_EP_TYPE_ISOC) {
+					if (!ep->stopped)
+						complete_xiso_ep(ep);
+				}
+#endif /* DWC_UTE_PER_IO */
+				else {
+					if (dwc_ep->type == DWC_OTG_EP_TYPE_ISOC &&
+							dwc_ep->bInterval > 1) {
+						dwc_ep->frame_num += dwc_ep->bInterval;
+						if (dwc_ep->frame_num > 0x3FFF)
+						{
+							dwc_ep->frm_overrun = 1;
+							dwc_ep->frame_num &= 0x3FFF;
+						} else
+							dwc_ep->frm_overrun = 0;
+					}
+					complete_ep(ep);
+					if(diepint.b.nak)
+						CLEAR_IN_EP_INTR(core_if, epnum, nak);
+				}
+			}
+			/* Endpoint disable      */
+			if (diepint.b.epdisabled) {
+				DWC_DEBUGPL(DBG_ANY, "EP%d IN disabled\n",
+					    epnum);
+				handle_in_ep_disable_intr(pcd, epnum);
+
+				/* Clear the bit in DIEPINTn for this interrupt */
+				CLEAR_IN_EP_INTR(core_if, epnum, epdisabled);
+			}
+			/* AHB Error */
+			if (diepint.b.ahberr) {
+				DWC_ERROR("EP%d IN AHB Error\n", epnum);
+				/* Clear the bit in DIEPINTn for this interrupt */
+				CLEAR_IN_EP_INTR(core_if, epnum, ahberr);
+			}
+			/* TimeOUT Handshake (non-ISOC IN EPs) */
+			if (diepint.b.timeout) {
+				DWC_ERROR("EP%d IN Time-out\n", epnum);
+				handle_in_ep_timeout_intr(pcd, epnum);
+
+				CLEAR_IN_EP_INTR(core_if, epnum, timeout);
+			}
+			/** IN Token received with TxF Empty */
+			if (diepint.b.intktxfemp) {
+				DWC_DEBUGPL(DBG_ANY,
+					    "EP%d IN TKN TxFifo Empty\n",
+					    epnum);
+				if (!ep->stopped && epnum != 0) {
+
+					diepmsk_data_t diepmsk = {.d32 = 0 };
+					diepmsk.b.intktxfemp = 1;
+
+					if (core_if->multiproc_int_enable) {
+						DWC_MODIFY_REG32
+						    (&dev_if->dev_global_regs->diepeachintmsk
+						     [epnum], diepmsk.d32, 0);
+					} else {
+						DWC_MODIFY_REG32
+						    (&dev_if->dev_global_regs->diepmsk,
+						     diepmsk.d32, 0);
+					}
+				} else if (core_if->dma_desc_enable
+					   && epnum == 0
+					   && pcd->ep0state ==
+					   EP0_OUT_STATUS_PHASE) {
+					// EP0 IN set STALL
+					depctl.d32 =
+					    DWC_READ_REG32(&dev_if->in_ep_regs
+							   [epnum]->diepctl);
+
+					/* set the disable and stall bits */
+					if (depctl.b.epena) {
+						depctl.b.epdis = 1;
+					}
+					depctl.b.stall = 1;
+					DWC_WRITE_REG32(&dev_if->in_ep_regs
+							[epnum]->diepctl,
+							depctl.d32);
+				}
+				CLEAR_IN_EP_INTR(core_if, epnum, intktxfemp);
+			}
+			/** IN Token Received with EP mismatch */
+			if (diepint.b.intknepmis) {
+				DWC_DEBUGPL(DBG_ANY,
+					    "EP%d IN TKN EP Mismatch\n", epnum);
+				CLEAR_IN_EP_INTR(core_if, epnum, intknepmis);
+			}
+			/** IN Endpoint NAK Effective */
+			if (diepint.b.inepnakeff) {
+				DWC_DEBUGPL(DBG_ANY,
+					    "EP%d IN EP NAK Effective\n",
+					    epnum);
+				/* Periodic EP */
+				if (ep->disabling) {
+					depctl.d32 = 0;
+					depctl.b.snak = 1;
+					depctl.b.epdis = 1;
+					DWC_MODIFY_REG32(&dev_if->in_ep_regs
+							 [epnum]->diepctl,
+							 depctl.d32,
+							 depctl.d32);
+				}
+				CLEAR_IN_EP_INTR(core_if, epnum, inepnakeff);
+
+			}
+
+			/** IN EP Tx FIFO Empty Intr */
+			if (diepint.b.emptyintr) {
+				DWC_DEBUGPL(DBG_ANY,
+					    "EP%d Tx FIFO Empty Intr \n",
+					    epnum);
+				write_empty_tx_fifo(pcd, epnum);
+
+				CLEAR_IN_EP_INTR(core_if, epnum, emptyintr);
+
+			}
+
+			/** IN EP BNA Intr */
+			if (diepint.b.bna) {
+				CLEAR_IN_EP_INTR(core_if, epnum, bna);
+				if (core_if->dma_desc_enable) {
+#ifdef DWC_EN_ISOC
+					if (dwc_ep->type ==
+					    DWC_OTG_EP_TYPE_ISOC) {
+						/*
+						 * This checking is performed to prevent first "false" BNA
+						 * handling occuring right after reconnect
+						 */
+						if (dwc_ep->next_frame !=
+						    0xffffffff)
+							dwc_otg_pcd_handle_iso_bna(ep);
+					} else
+#endif				/* DWC_EN_ISOC */
+					{
+						dwc_otg_pcd_handle_noniso_bna(ep);
+					}
+				}
+			}
+			/* NAK Interrutp */
+			if (diepint.b.nak) {
+				DWC_DEBUGPL(DBG_ANY, "EP%d IN NAK Interrupt\n",
+					    epnum);
+				if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) {
+					depctl_data_t depctl;
+					if (ep->dwc_ep.frame_num == 0xFFFFFFFF) {
+						ep->dwc_ep.frame_num = core_if->frame_num;
+						if (ep->dwc_ep.bInterval > 1) {
+							depctl.d32 = 0;
+							depctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[epnum]->diepctl);
+							if (ep->dwc_ep.frame_num & 0x1) {
+								depctl.b.setd1pid = 1;
+								depctl.b.setd0pid = 0;
+							} else {
+								depctl.b.setd0pid = 1;
+								depctl.b.setd1pid = 0;
+							}
+							DWC_WRITE_REG32(&dev_if->in_ep_regs[epnum]->diepctl, depctl.d32);
+						}
+						start_next_request(ep);
+					}
+					ep->dwc_ep.frame_num += ep->dwc_ep.bInterval;
+					if (dwc_ep->frame_num > 0x3FFF)	{
+						dwc_ep->frm_overrun = 1;
+						dwc_ep->frame_num &= 0x3FFF;
+					} else
+						dwc_ep->frm_overrun = 0;
+				}
+
+				CLEAR_IN_EP_INTR(core_if, epnum, nak);
+			}
+		}
+		epnum++;
+		ep_intr >>= 1;
+	}
+
+	return 1;
+#undef CLEAR_IN_EP_INTR
+}
+
+/**
+ * This interrupt indicates that an OUT EP has a pending Interrupt.
+ * The sequence for handling the OUT EP interrupt is shown below:
+ * -#	Read the Device All Endpoint Interrupt register
+ * -#	Repeat the following for each OUT EP interrupt bit set (from
+ *		LSB to MSB).
+ * -#	Read the Device Endpoint Interrupt (DOEPINTn) register
+ * -#	If "Transfer Complete" call the request complete function
+ * -#	If "Endpoint Disabled" complete the EP disable procedure.
+ * -#	If "AHB Error Interrupt" log error
+ * -#	If "Setup Phase Done" process Setup Packet (See Standard USB
+ *		Command Processing)
+ */
+static int32_t dwc_otg_pcd_handle_out_ep_intr(dwc_otg_pcd_t * pcd)
+{
+#define CLEAR_OUT_EP_INTR(__core_if,__epnum,__intr) \
+do { \
+		doepint_data_t doepint = {.d32=0}; \
+		doepint.b.__intr = 1; \
+		DWC_WRITE_REG32(&__core_if->dev_if->out_ep_regs[__epnum]->doepint, \
+		doepint.d32); \
+} while (0)
+
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+	uint32_t ep_intr;
+	doepint_data_t doepint = {.d32 = 0 };
+	uint32_t epnum = 0;
+	dwc_otg_pcd_ep_t *ep;
+	dwc_ep_t *dwc_ep;
+	dctl_data_t dctl = {.d32 = 0 };
+	gintmsk_data_t gintmsk = {.d32 = 0 };
+
+
+	DWC_DEBUGPL(DBG_PCDV, "%s()\n", __func__);
+
+	/* Read in the device interrupt bits */
+	ep_intr = dwc_otg_read_dev_all_out_ep_intr(core_if);
+
+	while (ep_intr) {
+		if (ep_intr & 0x1) {
+			/* Get EP pointer */
+			ep = get_out_ep(pcd, epnum);
+			dwc_ep = &ep->dwc_ep;
+
+#ifdef VERBOSE
+			DWC_DEBUGPL(DBG_PCDV,
+				    "EP%d-%s: type=%d, mps=%d\n",
+				    dwc_ep->num, (dwc_ep->is_in ? "IN" : "OUT"),
+				    dwc_ep->type, dwc_ep->maxpacket);
+#endif
+			doepint.d32 =
+			    dwc_otg_read_dev_out_ep_intr(core_if, dwc_ep);
+			/* Moved this interrupt upper due to core deffect of asserting
+			 * OUT EP 0 xfercompl along with stsphsrcvd in BDMA */
+			if (doepint.b.stsphsercvd) {
+				deptsiz0_data_t deptsiz;
+				CLEAR_OUT_EP_INTR(core_if, epnum, stsphsercvd);
+				deptsiz.d32 =
+				    DWC_READ_REG32(&core_if->dev_if->
+						   out_ep_regs[0]->doeptsiz);
+				if (core_if->snpsid >= OTG_CORE_REV_3_00a
+				    && core_if->dma_enable
+				    && core_if->dma_desc_enable == 0
+				    && doepint.b.xfercompl
+				    && deptsiz.b.xfersize == 24) {
+					CLEAR_OUT_EP_INTR(core_if, epnum,
+							  xfercompl);
+					doepint.b.xfercompl = 0;
+					ep0_out_start(core_if, pcd);
+				}
+				if ((core_if->dma_desc_enable) ||
+				    (core_if->dma_enable
+				     && core_if->snpsid >=
+				     OTG_CORE_REV_3_00a)) {
+					do_setup_in_status_phase(pcd);
+				}
+			}
+			/* Transfer complete */
+			if (doepint.b.xfercompl) {
+
+				if (epnum == 0) {
+					/* Clear the bit in DOEPINTn for this interrupt */
+					CLEAR_OUT_EP_INTR(core_if, epnum, xfercompl);
+					if (core_if->snpsid >= OTG_CORE_REV_3_00a) {
+						DWC_DEBUGPL(DBG_PCDV, "DOEPINT=%x doepint=%x\n",
+							DWC_READ_REG32(&core_if->dev_if->out_ep_regs[0]->doepint),
+							doepint.d32);
+						DWC_DEBUGPL(DBG_PCDV, "DOEPCTL=%x \n",
+							DWC_READ_REG32(&core_if->dev_if->out_ep_regs[0]->doepctl));
+
+						if (core_if->snpsid >= OTG_CORE_REV_3_00a
+							&& core_if->dma_enable == 0) {
+							doepint_data_t doepint;
+							doepint.d32 = DWC_READ_REG32(&core_if->dev_if->
+														out_ep_regs[0]->doepint);
+							if (pcd->ep0state == EP0_IDLE && doepint.b.sr) {
+								CLEAR_OUT_EP_INTR(core_if, epnum, sr);
+								goto exit_xfercompl;
+							}
+						}
+						/* In case of DDMA  look at SR bit to go to the Data Stage */
+						if (core_if->dma_desc_enable) {
+							dev_dma_desc_sts_t status = {.d32 = 0};
+							if (pcd->ep0state == EP0_IDLE) {
+								status.d32 = core_if->dev_if->setup_desc_addr[core_if->
+											dev_if->setup_desc_index]->status.d32;
+								if(pcd->data_terminated) {
+									 pcd->data_terminated = 0;
+									 status.d32 = core_if->dev_if->out_desc_addr->status.d32;
+									 dwc_memcpy(&pcd->setup_pkt->req, pcd->backup_buf, 8);
+								}
+								if (status.b.sr) {
+									if (doepint.b.setup) {
+										DWC_DEBUGPL(DBG_PCDV, "DMA DESC EP0_IDLE SR=1 setup=1\n");
+										/* Already started data stage, clear setup */
+										CLEAR_OUT_EP_INTR(core_if, epnum, setup);
+										doepint.b.setup = 0;
+										handle_ep0(pcd);
+										/* Prepare for more setup packets */
+										if (pcd->ep0state == EP0_IN_STATUS_PHASE ||
+											pcd->ep0state == EP0_IN_DATA_PHASE) {
+											ep0_out_start(core_if, pcd);
+										}
+
+										goto exit_xfercompl;
+									} else {
+										/* Prepare for more setup packets */
+										DWC_DEBUGPL(DBG_PCDV,
+											"EP0_IDLE SR=1 setup=0 new setup comes\n");
+										ep0_out_start(core_if, pcd);
+									}
+								}
+							} else {
+								dwc_otg_pcd_request_t *req;
+								dev_dma_desc_sts_t status = {.d32 = 0};
+								diepint_data_t diepint0;
+								diepint0.d32 = DWC_READ_REG32(&core_if->dev_if->
+															in_ep_regs[0]->diepint);
+
+								if (pcd->ep0state == EP0_STALL || pcd->ep0state == EP0_DISCONNECT) {
+									DWC_ERROR("EP0 is stalled/disconnected\n");
+								}
+
+								/* Clear IN xfercompl if set */
+								if (diepint0.b.xfercompl && (pcd->ep0state == EP0_IN_STATUS_PHASE
+									|| pcd->ep0state == EP0_IN_DATA_PHASE)) {
+									DWC_WRITE_REG32(&core_if->dev_if->
+										in_ep_regs[0]->diepint, diepint0.d32);
+								}
+
+								status.d32 = core_if->dev_if->setup_desc_addr[core_if->
+									dev_if->setup_desc_index]->status.d32;
+
+								if (ep->dwc_ep.xfer_count != ep->dwc_ep.total_len
+									&& (pcd->ep0state == EP0_OUT_DATA_PHASE))
+									status.d32 = core_if->dev_if->out_desc_addr->status.d32;
+								if (pcd->ep0state == EP0_OUT_STATUS_PHASE)
+									status.d32 = core_if->dev_if->
+									out_desc_addr->status.d32;
+
+								if (status.b.sr) {
+									if (DWC_CIRCLEQ_EMPTY(&ep->queue)) {
+										DWC_DEBUGPL(DBG_PCDV, "Request queue empty!!\n");
+									} else {
+										DWC_DEBUGPL(DBG_PCDV, "complete req!!\n");
+										req = DWC_CIRCLEQ_FIRST(&ep->queue);
+										if (ep->dwc_ep.xfer_count != ep->dwc_ep.total_len &&
+											pcd->ep0state == EP0_OUT_DATA_PHASE) {
+												/* Read arrived setup packet from req->buf */
+												dwc_memcpy(&pcd->setup_pkt->req,
+													req->buf + ep->dwc_ep.xfer_count, 8);
+										}
+										req->actual = ep->dwc_ep.xfer_count;
+										dwc_otg_request_done(ep, req, -ECONNRESET);
+										ep->dwc_ep.start_xfer_buff = 0;
+										ep->dwc_ep.xfer_buff = 0;
+										ep->dwc_ep.xfer_len = 0;
+									}
+									pcd->ep0state = EP0_IDLE;
+									if (doepint.b.setup) {
+										DWC_DEBUGPL(DBG_PCDV, "EP0_IDLE SR=1 setup=1\n");
+										/* Data stage started, clear setup */
+										CLEAR_OUT_EP_INTR(core_if, epnum, setup);
+										doepint.b.setup = 0;
+										handle_ep0(pcd);
+										/* Prepare for setup packets if ep0in was enabled*/
+										if (pcd->ep0state == EP0_IN_STATUS_PHASE) {
+											ep0_out_start(core_if, pcd);
+										}
+
+										goto exit_xfercompl;
+									} else {
+										/* Prepare for more setup packets */
+										DWC_DEBUGPL(DBG_PCDV,
+											"EP0_IDLE SR=1 setup=0 new setup comes 2\n");
+										ep0_out_start(core_if, pcd);
+									}
+								}
+							}
+						}
+						if (core_if->snpsid >= OTG_CORE_REV_2_94a && core_if->dma_enable
+							&& core_if->dma_desc_enable == 0) {
+							doepint_data_t doepint_temp = {.d32 = 0};
+							deptsiz0_data_t doeptsize0 = {.d32 = 0 };
+							doepint_temp.d32 = DWC_READ_REG32(&core_if->dev_if->
+															out_ep_regs[ep->dwc_ep.num]->doepint);
+							doeptsize0.d32 = DWC_READ_REG32(&core_if->dev_if->
+															out_ep_regs[ep->dwc_ep.num]->doeptsiz);
+							if (pcd->ep0state == EP0_IDLE) {
+								if (doepint_temp.b.sr) {
+									CLEAR_OUT_EP_INTR(core_if, epnum, sr);
+								}
+									doepint.d32 = DWC_READ_REG32(&core_if->dev_if->
+																	out_ep_regs[0]->doepint);
+									if (doeptsize0.b.supcnt == 3) {
+										DWC_DEBUGPL(DBG_ANY, "Rolling over!!!!!!!\n");
+										ep->dwc_ep.stp_rollover = 1;
+									}
+									if (doepint.b.setup) {
+retry:
+										/* Already started data stage, clear setup */
+										CLEAR_OUT_EP_INTR(core_if, epnum, setup);
+										doepint.b.setup = 0;
+										handle_ep0(pcd);
+										ep->dwc_ep.stp_rollover = 0;
+										/* Prepare for more setup packets */
+										if (pcd->ep0state == EP0_IN_STATUS_PHASE ||
+											pcd->ep0state == EP0_IN_DATA_PHASE) {
+											ep0_out_start(core_if, pcd);
+										}
+										goto exit_xfercompl;
+									} else {
+										/* Prepare for more setup packets */
+										DWC_DEBUGPL(DBG_ANY,
+											"EP0_IDLE SR=1 setup=0 new setup comes\n");
+										doepint.d32 = DWC_READ_REG32(&core_if->dev_if->
+																	out_ep_regs[0]->doepint);
+										if(doepint.b.setup)
+											goto retry;
+										ep0_out_start(core_if, pcd);
+									}
+							} else {
+								dwc_otg_pcd_request_t *req;
+								diepint_data_t diepint0 = {.d32 = 0};
+								doepint_data_t doepint_temp = {.d32 = 0};
+								depctl_data_t diepctl0;
+								diepint0.d32 = DWC_READ_REG32(&core_if->dev_if->
+																in_ep_regs[0]->diepint);
+								diepctl0.d32 = DWC_READ_REG32(&core_if->dev_if->
+																in_ep_regs[0]->diepctl);
+
+								if (pcd->ep0state == EP0_IN_DATA_PHASE
+									|| pcd->ep0state == EP0_IN_STATUS_PHASE) {
+									if (diepint0.b.xfercompl) {
+										DWC_WRITE_REG32(&core_if->dev_if->
+											in_ep_regs[0]->diepint, diepint0.d32);
+									}
+									if (diepctl0.b.epena) {
+										diepint_data_t diepint = {.d32 = 0};
+										diepctl0.b.snak = 1;
+										DWC_WRITE_REG32(&core_if->dev_if->
+														in_ep_regs[0]->diepctl, diepctl0.d32);
+										do {
+											dwc_udelay(10);
+											diepint.d32 = DWC_READ_REG32(&core_if->dev_if->
+												in_ep_regs[0]->diepint);
+										} while (!diepint.b.inepnakeff);
+										diepint.b.inepnakeff = 1;
+										DWC_WRITE_REG32(&core_if->dev_if->
+											in_ep_regs[0]->diepint, diepint.d32);
+										diepctl0.d32 = 0;
+										diepctl0.b.epdis = 1;
+										DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[0]->diepctl,
+														diepctl0.d32);
+										do {
+											dwc_udelay(10);
+											diepint.d32 = DWC_READ_REG32(&core_if->dev_if->
+												in_ep_regs[0]->diepint);
+										} while (!diepint.b.epdisabled);
+										diepint.b.epdisabled = 1;
+										DWC_WRITE_REG32(&core_if->dev_if->in_ep_regs[0]->diepint,
+															diepint.d32);
+									}
+								}
+								doepint_temp.d32 = DWC_READ_REG32(&core_if->dev_if->
+																out_ep_regs[ep->dwc_ep.num]->doepint);
+								if (doepint_temp.b.sr) {
+									CLEAR_OUT_EP_INTR(core_if, epnum, sr);
+									if (DWC_CIRCLEQ_EMPTY(&ep->queue)) {
+										DWC_DEBUGPL(DBG_PCDV, "Request queue empty!!\n");
+									} else {
+										DWC_DEBUGPL(DBG_PCDV, "complete req!!\n");
+										req = DWC_CIRCLEQ_FIRST(&ep->queue);
+										if (ep->dwc_ep.xfer_count != ep->dwc_ep.total_len &&
+											pcd->ep0state == EP0_OUT_DATA_PHASE) {
+												/* Read arrived setup packet from req->buf */
+												dwc_memcpy(&pcd->setup_pkt->req,
+													req->buf + ep->dwc_ep.xfer_count, 8);
+										}
+										req->actual = ep->dwc_ep.xfer_count;
+										dwc_otg_request_done(ep, req, -ECONNRESET);
+										ep->dwc_ep.start_xfer_buff = 0;
+										ep->dwc_ep.xfer_buff = 0;
+										ep->dwc_ep.xfer_len = 0;
+									}
+									pcd->ep0state = EP0_IDLE;
+									if (doepint.b.setup) {
+										DWC_DEBUGPL(DBG_PCDV, "EP0_IDLE SR=1 setup=1\n");
+										/* Data stage started, clear setup */
+										CLEAR_OUT_EP_INTR(core_if, epnum, setup);
+										doepint.b.setup = 0;
+										handle_ep0(pcd);
+										/* Prepare for setup packets if ep0in was enabled*/
+										if (pcd->ep0state == EP0_IN_STATUS_PHASE) {
+											ep0_out_start(core_if, pcd);
+										}
+										goto exit_xfercompl;
+									} else {
+										/* Prepare for more setup packets */
+										DWC_DEBUGPL(DBG_PCDV,
+											"EP0_IDLE SR=1 setup=0 new setup comes 2\n");
+										ep0_out_start(core_if, pcd);
+									}
+								}
+							}
+						}
+						if (core_if->dma_enable == 0 || pcd->ep0state != EP0_IDLE)
+							handle_ep0(pcd);
+exit_xfercompl:
+						DWC_DEBUGPL(DBG_PCDV, "DOEPINT=%x doepint=%x\n",
+							dwc_otg_read_dev_out_ep_intr(core_if, dwc_ep), doepint.d32);
+					} else {
+					if (core_if->dma_desc_enable == 0
+					    || pcd->ep0state != EP0_IDLE)
+						handle_ep0(pcd);
+					}
+#ifdef DWC_EN_ISOC
+				} else if (dwc_ep->type == DWC_OTG_EP_TYPE_ISOC) {
+					if (doepint.b.pktdrpsts == 0) {
+						/* Clear the bit in DOEPINTn for this interrupt */
+						CLEAR_OUT_EP_INTR(core_if,
+								  epnum,
+								  xfercompl);
+						complete_iso_ep(pcd, ep);
+					} else {
+
+						doepint_data_t doepint = {.d32 = 0 };
+						doepint.b.xfercompl = 1;
+						doepint.b.pktdrpsts = 1;
+						DWC_WRITE_REG32
+						    (&core_if->dev_if->out_ep_regs
+						     [epnum]->doepint,
+						     doepint.d32);
+						if (handle_iso_out_pkt_dropped
+						    (core_if, dwc_ep)) {
+							complete_iso_ep(pcd,
+									ep);
+						}
+					}
+#endif /* DWC_EN_ISOC */
+#ifdef DWC_UTE_PER_IO
+				} else if (dwc_ep->type == DWC_OTG_EP_TYPE_ISOC) {
+					CLEAR_OUT_EP_INTR(core_if, epnum, xfercompl);
+					if (!ep->stopped)
+						complete_xiso_ep(ep);
+#endif /* DWC_UTE_PER_IO */
+				} else {
+					/* Clear the bit in DOEPINTn for this interrupt */
+					CLEAR_OUT_EP_INTR(core_if, epnum,
+							  xfercompl);
+
+					if (core_if->core_params->dev_out_nak) {
+						DWC_TIMER_CANCEL(pcd->core_if->ep_xfer_timer[epnum]);
+						pcd->core_if->ep_xfer_info[epnum].state = 0;
+#ifdef DEBUG
+						print_memory_payload(pcd, dwc_ep);
+#endif
+					}
+					complete_ep(ep);
+				}
+
+			}
+
+			/* Endpoint disable      */
+			if (doepint.b.epdisabled) {
+
+				/* Clear the bit in DOEPINTn for this interrupt */
+				CLEAR_OUT_EP_INTR(core_if, epnum, epdisabled);
+				if (core_if->core_params->dev_out_nak) {
+#ifdef DEBUG
+					print_memory_payload(pcd, dwc_ep);
+#endif
+					/* In case of timeout condition */
+					if (core_if->ep_xfer_info[epnum].state == 2) {
+						dctl.d32 = DWC_READ_REG32(&core_if->dev_if->
+										dev_global_regs->dctl);
+						dctl.b.cgoutnak = 1;
+						DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl,
+																dctl.d32);
+						/* Unmask goutnakeff interrupt which was masked
+						 * during handle nak out interrupt */
+						gintmsk.b.goutnakeff = 1;
+						DWC_MODIFY_REG32(&core_if->core_global_regs->gintmsk,
+																0, gintmsk.d32);
+
+						complete_ep(ep);
+					}
+				}
+				if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC)
+				{
+					dctl_data_t dctl;
+					gintmsk_data_t intr_mask = {.d32 = 0};
+					dwc_otg_pcd_request_t *req = 0;
+
+					dctl.d32 = DWC_READ_REG32(&core_if->dev_if->
+						dev_global_regs->dctl);
+					dctl.b.cgoutnak = 1;
+					DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl,
+						dctl.d32);
+
+					intr_mask.d32 = 0;
+					intr_mask.b.incomplisoout = 1;
+
+					/* Get any pending requests */
+					if (!DWC_CIRCLEQ_EMPTY(&ep->queue)) {
+						req = DWC_CIRCLEQ_FIRST(&ep->queue);
+						if (!req) {
+							DWC_PRINTF("complete_ep 0x%p, req = NULL!\n", ep);
+						} else {
+							dwc_otg_request_done(ep, req, 0);
+							start_next_request(ep);
+						}
+					} else {
+						DWC_PRINTF("complete_ep 0x%p, ep->queue empty!\n", ep);
+					}
+				}
+			}
+			/* AHB Error */
+			if (doepint.b.ahberr) {
+				DWC_ERROR("EP%d OUT AHB Error\n", epnum);
+				DWC_ERROR("EP%d DEPDMA=0x%08x \n",
+					  epnum, core_if->dev_if->out_ep_regs[epnum]->doepdma);
+				CLEAR_OUT_EP_INTR(core_if, epnum, ahberr);
+			}
+			/* Setup Phase Done (contorl EPs) */
+			if (doepint.b.setup) {
+#ifdef DEBUG_EP0
+				DWC_DEBUGPL(DBG_PCD, "EP%d SETUP Done\n", epnum);
+#endif
+				CLEAR_OUT_EP_INTR(core_if, epnum, setup);
+
+				handle_ep0(pcd);
+			}
+
+			/** OUT EP BNA Intr */
+			if (doepint.b.bna) {
+				CLEAR_OUT_EP_INTR(core_if, epnum, bna);
+				if (core_if->dma_desc_enable) {
+#ifdef DWC_EN_ISOC
+					if (dwc_ep->type ==
+					    DWC_OTG_EP_TYPE_ISOC) {
+						/*
+						 * This checking is performed to prevent first "false" BNA
+						 * handling occuring right after reconnect
+						 */
+						if (dwc_ep->next_frame !=
+						    0xffffffff)
+							dwc_otg_pcd_handle_iso_bna(ep);
+					} else
+#endif				/* DWC_EN_ISOC */
+					{
+						dwc_otg_pcd_handle_noniso_bna(ep);
+					}
+				}
+			}
+			/* Babble Interrupt */
+			if (doepint.b.babble) {
+				DWC_DEBUGPL(DBG_ANY, "EP%d OUT Babble\n",
+					    epnum);
+				handle_out_ep_babble_intr(pcd, epnum);
+
+				CLEAR_OUT_EP_INTR(core_if, epnum, babble);
+			}
+			if (doepint.b.outtknepdis) {
+				DWC_DEBUGPL(DBG_ANY, "EP%d OUT Token received when EP is \
+					disabled\n",epnum);
+				if (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) {
+					doepmsk_data_t doepmsk = {.d32 = 0};
+					ep->dwc_ep.frame_num = core_if->frame_num;
+					if (ep->dwc_ep.bInterval > 1) {
+						depctl_data_t depctl;
+						depctl.d32 = DWC_READ_REG32(&core_if->dev_if->
+													out_ep_regs[epnum]->doepctl);
+						if (ep->dwc_ep.frame_num & 0x1) {
+							depctl.b.setd1pid = 1;
+							depctl.b.setd0pid = 0;
+						} else {
+							depctl.b.setd0pid = 1;
+							depctl.b.setd1pid = 0;
+						}
+						DWC_WRITE_REG32(&core_if->dev_if->
+										out_ep_regs[epnum]->doepctl, depctl.d32);
+					}
+					start_next_request(ep);
+					doepmsk.b.outtknepdis = 1;
+					DWC_MODIFY_REG32(&core_if->dev_if->dev_global_regs->doepmsk,
+								 doepmsk.d32, 0);
+				}
+				CLEAR_OUT_EP_INTR(core_if, epnum, outtknepdis);
+			}
+
+			/* NAK Interrutp */
+			if (doepint.b.nak) {
+				DWC_DEBUGPL(DBG_ANY, "EP%d OUT NAK\n", epnum);
+				handle_out_ep_nak_intr(pcd, epnum);
+
+				CLEAR_OUT_EP_INTR(core_if, epnum, nak);
+			}
+			/* NYET Interrutp */
+			if (doepint.b.nyet) {
+				DWC_DEBUGPL(DBG_ANY, "EP%d OUT NYET\n", epnum);
+				handle_out_ep_nyet_intr(pcd, epnum);
+
+				CLEAR_OUT_EP_INTR(core_if, epnum, nyet);
+			}
+		}
+
+		epnum++;
+		ep_intr >>= 1;
+	}
+
+	return 1;
+
+#undef CLEAR_OUT_EP_INTR
+}
+static int drop_transfer(uint32_t trgt_fr, uint32_t curr_fr, uint8_t frm_overrun)
+{
+	int retval = 0;
+	if(!frm_overrun && curr_fr >= trgt_fr)
+		retval = 1;
+	else if (frm_overrun
+		 && (curr_fr >= trgt_fr && ((curr_fr - trgt_fr) < 0x3FFF / 2)))
+		retval = 1;
+	return retval;
+}
+/**
+ * Incomplete ISO IN Transfer Interrupt.
+ * This interrupt indicates one of the following conditions occurred
+ * while transmitting an ISOC transaction.
+ * - Corrupted IN Token for ISOC EP.
+ * - Packet not complete in FIFO.
+ * The follow actions will be taken:
+ *	-#	Determine the EP
+ *	-#	Set incomplete flag in dwc_ep structure
+ *	-#	Disable EP; when "Endpoint Disabled" interrupt is received
+ *		Flush FIFO
+ */
+static int32_t dwc_otg_pcd_handle_incomplete_isoc_in_intr(dwc_otg_pcd_t * pcd)
+{
+	gintsts_data_t gintsts;
+
+#ifdef DWC_EN_ISOC
+	dwc_otg_dev_if_t *dev_if;
+	deptsiz_data_t deptsiz = {.d32 = 0 };
+	depctl_data_t depctl = {.d32 = 0 };
+	dsts_data_t dsts = {.d32 = 0 };
+	dwc_ep_t *dwc_ep;
+	int i;
+
+	dev_if = GET_CORE_IF(pcd)->dev_if;
+
+	for (i = 1; i <= dev_if->num_in_eps; ++i) {
+		dwc_ep = &pcd->in_ep[i].dwc_ep;
+		if (dwc_ep->active && dwc_ep->type == DWC_OTG_EP_TYPE_ISOC) {
+			deptsiz.d32 =
+			    DWC_READ_REG32(&dev_if->in_ep_regs[i]->dieptsiz);
+			depctl.d32 =
+			    DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl);
+
+			if (depctl.b.epdis && deptsiz.d32) {
+				set_current_pkt_info(GET_CORE_IF(pcd), dwc_ep);
+				if (dwc_ep->cur_pkt >= dwc_ep->pkt_cnt) {
+					dwc_ep->cur_pkt = 0;
+					dwc_ep->proc_buf_num =
+					    (dwc_ep->proc_buf_num ^ 1) & 0x1;
+
+					if (dwc_ep->proc_buf_num) {
+						dwc_ep->cur_pkt_addr =
+						    dwc_ep->xfer_buff1;
+						dwc_ep->cur_pkt_dma_addr =
+						    dwc_ep->dma_addr1;
+					} else {
+						dwc_ep->cur_pkt_addr =
+						    dwc_ep->xfer_buff0;
+						dwc_ep->cur_pkt_dma_addr =
+						    dwc_ep->dma_addr0;
+					}
+
+				}
+
+				dsts.d32 =
+				    DWC_READ_REG32(&GET_CORE_IF(pcd)->dev_if->
+						   dev_global_regs->dsts);
+				dwc_ep->next_frame = dsts.b.soffn;
+
+				dwc_otg_iso_ep_start_frm_transfer(GET_CORE_IF
+								  (pcd),
+								  dwc_ep);
+			}
+		}
+	}
+
+#else
+	depctl_data_t depctl = {.d32 = 0 };
+	dwc_ep_t *dwc_ep;
+	dwc_otg_dev_if_t *dev_if;
+	int i;
+	dev_if = GET_CORE_IF(pcd)->dev_if;
+
+	DWC_DEBUGPL(DBG_PCD,"Incomplete ISO IN \n");
+
+	for (i = 1; i <= dev_if->num_in_eps; ++i) {
+		dwc_ep = &pcd->in_ep[i-1].dwc_ep;
+		depctl.d32 =
+			DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl);
+		if (depctl.b.epena && dwc_ep->type == DWC_OTG_EP_TYPE_ISOC) {
+			if (drop_transfer(dwc_ep->frame_num, GET_CORE_IF(pcd)->frame_num,
+							dwc_ep->frm_overrun))
+			{
+				depctl.d32 =
+					DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl);
+				depctl.b.snak = 1;
+				depctl.b.epdis = 1;
+				DWC_MODIFY_REG32(&dev_if->in_ep_regs[i]->diepctl, depctl.d32, depctl.d32);
+			}
+		}
+	}
+
+	/*intr_mask.b.incomplisoin = 1;
+	DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintmsk,
+			 intr_mask.d32, 0);	 */
+#endif				//DWC_EN_ISOC
+
+	/* Clear interrupt */
+	gintsts.d32 = 0;
+	gintsts.b.incomplisoin = 1;
+	DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts,
+			gintsts.d32);
+
+	return 1;
+}
+
+/**
+ * Incomplete ISO OUT Transfer Interrupt.
+ *
+ * This interrupt indicates that the core has dropped an ISO OUT
+ * packet. The following conditions can be the cause:
+ * - FIFO Full, the entire packet would not fit in the FIFO.
+ * - CRC Error
+ * - Corrupted Token
+ * The follow actions will be taken:
+ *	-#	Determine the EP
+ *	-#	Set incomplete flag in dwc_ep structure
+ *	-#	Read any data from the FIFO
+ *	-#	Disable EP. When "Endpoint Disabled" interrupt is received
+ *		re-enable EP.
+ */
+static int32_t dwc_otg_pcd_handle_incomplete_isoc_out_intr(dwc_otg_pcd_t * pcd)
+{
+
+	gintsts_data_t gintsts;
+
+#ifdef DWC_EN_ISOC
+	dwc_otg_dev_if_t *dev_if;
+	deptsiz_data_t deptsiz = {.d32 = 0 };
+	depctl_data_t depctl = {.d32 = 0 };
+	dsts_data_t dsts = {.d32 = 0 };
+	dwc_ep_t *dwc_ep;
+	int i;
+
+	dev_if = GET_CORE_IF(pcd)->dev_if;
+
+	for (i = 1; i <= dev_if->num_out_eps; ++i) {
+		dwc_ep = &pcd->in_ep[i].dwc_ep;
+		if (pcd->out_ep[i].dwc_ep.active &&
+		    pcd->out_ep[i].dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) {
+			deptsiz.d32 =
+			    DWC_READ_REG32(&dev_if->out_ep_regs[i]->doeptsiz);
+			depctl.d32 =
+			    DWC_READ_REG32(&dev_if->out_ep_regs[i]->doepctl);
+
+			if (depctl.b.epdis && deptsiz.d32) {
+				set_current_pkt_info(GET_CORE_IF(pcd),
+						     &pcd->out_ep[i].dwc_ep);
+				if (dwc_ep->cur_pkt >= dwc_ep->pkt_cnt) {
+					dwc_ep->cur_pkt = 0;
+					dwc_ep->proc_buf_num =
+					    (dwc_ep->proc_buf_num ^ 1) & 0x1;
+
+					if (dwc_ep->proc_buf_num) {
+						dwc_ep->cur_pkt_addr =
+						    dwc_ep->xfer_buff1;
+						dwc_ep->cur_pkt_dma_addr =
+						    dwc_ep->dma_addr1;
+					} else {
+						dwc_ep->cur_pkt_addr =
+						    dwc_ep->xfer_buff0;
+						dwc_ep->cur_pkt_dma_addr =
+						    dwc_ep->dma_addr0;
+					}
+
+				}
+
+				dsts.d32 =
+				    DWC_READ_REG32(&GET_CORE_IF(pcd)->dev_if->
+						   dev_global_regs->dsts);
+				dwc_ep->next_frame = dsts.b.soffn;
+
+				dwc_otg_iso_ep_start_frm_transfer(GET_CORE_IF
+								  (pcd),
+								  dwc_ep);
+			}
+		}
+	}
+#else
+	/** @todo implement ISR */
+	gintmsk_data_t intr_mask = {.d32 = 0 };
+	dwc_otg_core_if_t *core_if;
+	deptsiz_data_t deptsiz = {.d32 = 0 };
+	depctl_data_t depctl = {.d32 = 0 };
+	dctl_data_t dctl = {.d32 = 0 };
+	dwc_ep_t *dwc_ep = NULL;
+	int i;
+	core_if = GET_CORE_IF(pcd);
+
+	for (i = 0; i < core_if->dev_if->num_out_eps; ++i) {
+		dwc_ep = &pcd->out_ep[i].dwc_ep;
+		depctl.d32 =
+			DWC_READ_REG32(&core_if->dev_if->out_ep_regs[dwc_ep->num]->doepctl);
+		if (depctl.b.epena && depctl.b.dpid == (core_if->frame_num & 0x1)) {
+			core_if->dev_if->isoc_ep = dwc_ep;
+			deptsiz.d32 =
+					DWC_READ_REG32(&core_if->dev_if->out_ep_regs[dwc_ep->num]->doeptsiz);
+				break;
+		}
+	}
+	dctl.d32 = DWC_READ_REG32(&core_if->dev_if->dev_global_regs->dctl);
+	gintsts.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintsts);
+	intr_mask.d32 = DWC_READ_REG32(&core_if->core_global_regs->gintmsk);
+
+	if (!intr_mask.b.goutnakeff) {
+		/* Unmask it */
+		intr_mask.b.goutnakeff = 1;
+		DWC_WRITE_REG32(&core_if->core_global_regs->gintmsk, intr_mask.d32);
+	}
+	if (!gintsts.b.goutnakeff) {
+		dctl.b.sgoutnak = 1;
+	}
+	DWC_WRITE_REG32(&core_if->dev_if->dev_global_regs->dctl, dctl.d32);
+
+	depctl.d32 = DWC_READ_REG32(&core_if->dev_if->out_ep_regs[dwc_ep->num]->doepctl);
+	if (depctl.b.epena) {
+		depctl.b.epdis = 1;
+		depctl.b.snak = 1;
+	}
+	DWC_WRITE_REG32(&core_if->dev_if->out_ep_regs[dwc_ep->num]->doepctl, depctl.d32);
+
+	intr_mask.d32 = 0;
+	intr_mask.b.incomplisoout = 1;
+
+#endif /* DWC_EN_ISOC */
+
+	/* Clear interrupt */
+	gintsts.d32 = 0;
+	gintsts.b.incomplisoout = 1;
+	DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts,
+			gintsts.d32);
+
+	return 1;
+}
+
+/**
+ * This function handles the Global IN NAK Effective interrupt.
+ *
+ */
+static int32_t dwc_otg_pcd_handle_in_nak_effective(dwc_otg_pcd_t * pcd)
+{
+	dwc_otg_dev_if_t *dev_if = GET_CORE_IF(pcd)->dev_if;
+	depctl_data_t diepctl = {.d32 = 0 };
+	gintmsk_data_t intr_mask = {.d32 = 0 };
+	gintsts_data_t gintsts;
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+	int i;
+
+	DWC_DEBUGPL(DBG_PCD, "Global IN NAK Effective\n");
+
+	/* Disable all active IN EPs */
+	for (i = 0; i <= dev_if->num_in_eps; i++) {
+		diepctl.d32 = DWC_READ_REG32(&dev_if->in_ep_regs[i]->diepctl);
+		if (!(diepctl.b.eptype & 1) && diepctl.b.epena) {
+			if (core_if->start_predict > 0)
+				core_if->start_predict++;
+			diepctl.b.epdis = 1;
+			diepctl.b.snak = 1;
+			DWC_WRITE_REG32(&dev_if->in_ep_regs[i]->diepctl, diepctl.d32);
+		}
+	}
+
+
+	/* Disable the Global IN NAK Effective Interrupt */
+	intr_mask.b.ginnakeff = 1;
+	DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintmsk,
+			 intr_mask.d32, 0);
+
+	/* Clear interrupt */
+	gintsts.d32 = 0;
+	gintsts.b.ginnakeff = 1;
+	DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts,
+			gintsts.d32);
+
+	return 1;
+}
+
+/**
+ * OUT NAK Effective.
+ *
+ */
+static int32_t dwc_otg_pcd_handle_out_nak_effective(dwc_otg_pcd_t * pcd)
+{
+	dwc_otg_dev_if_t *dev_if = GET_CORE_IF(pcd)->dev_if;
+	gintmsk_data_t intr_mask = {.d32 = 0 };
+	gintsts_data_t gintsts;
+	depctl_data_t doepctl;
+	int i;
+
+	/* Disable the Global OUT NAK Effective Interrupt */
+	intr_mask.b.goutnakeff = 1;
+	DWC_MODIFY_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintmsk,
+		intr_mask.d32, 0);
+
+	/* If DEV OUT NAK enabled*/
+	if (pcd->core_if->core_params->dev_out_nak) {
+		/* Run over all out endpoints to determine the ep number on
+		 * which the timeout has happened
+		 */
+		for (i = 0; i <= dev_if->num_out_eps; i++) {
+			if ( pcd->core_if->ep_xfer_info[i].state == 2 )
+				break;
+		}
+		if (i > dev_if->num_out_eps) {
+			dctl_data_t dctl;
+			dctl.d32 =
+			    DWC_READ_REG32(&dev_if->dev_global_regs->dctl);
+			dctl.b.cgoutnak = 1;
+			DWC_WRITE_REG32(&dev_if->dev_global_regs->dctl,
+				dctl.d32);
+			goto out;
+		}
+
+		/* Disable the endpoint */
+		doepctl.d32 = DWC_READ_REG32(&dev_if->out_ep_regs[i]->doepctl);
+		if (doepctl.b.epena) {
+			doepctl.b.epdis = 1;
+			doepctl.b.snak = 1;
+		}
+		DWC_WRITE_REG32(&dev_if->out_ep_regs[i]->doepctl, doepctl.d32);
+		return 1;
+	}
+	/* We come here from Incomplete ISO OUT handler */
+	if (dev_if->isoc_ep) {
+		dwc_ep_t *dwc_ep = (dwc_ep_t *)dev_if->isoc_ep;
+		uint32_t epnum = dwc_ep->num;
+		doepint_data_t doepint;
+		doepint.d32 =
+		    DWC_READ_REG32(&dev_if->out_ep_regs[dwc_ep->num]->doepint);
+		dev_if->isoc_ep = NULL;
+		doepctl.d32 =
+		    DWC_READ_REG32(&dev_if->out_ep_regs[epnum]->doepctl);
+		DWC_PRINTF("Before disable DOEPCTL = %08x\n", doepctl.d32);
+		if (doepctl.b.epena) {
+			doepctl.b.epdis = 1;
+			doepctl.b.snak = 1;
+		}
+		DWC_WRITE_REG32(&dev_if->out_ep_regs[epnum]->doepctl,
+				doepctl.d32);
+		return 1;
+	} else
+		DWC_PRINTF("INTERRUPT Handler not implemented for %s\n",
+			   "Global OUT NAK Effective\n");
+
+out:
+	/* Clear interrupt */
+	gintsts.d32 = 0;
+	gintsts.b.goutnakeff = 1;
+	DWC_WRITE_REG32(&GET_CORE_IF(pcd)->core_global_regs->gintsts,
+			gintsts.d32);
+
+	return 1;
+}
+
+/**
+ * PCD interrupt handler.
+ *
+ * The PCD handles the device interrupts.  Many conditions can cause a
+ * device interrupt. When an interrupt occurs, the device interrupt
+ * service routine determines the cause of the interrupt and
+ * dispatches handling to the appropriate function. These interrupt
+ * handling functions are described below.
+ *
+ * All interrupt registers are processed from LSB to MSB.
+ *
+ */
+int32_t dwc_otg_pcd_handle_intr(dwc_otg_pcd_t * pcd)
+{
+	dwc_otg_core_if_t *core_if = GET_CORE_IF(pcd);
+#ifdef VERBOSE
+	dwc_otg_core_global_regs_t *global_regs = core_if->core_global_regs;
+#endif
+	gintsts_data_t gintr_status;
+	int32_t retval = 0;
+
+	/* Exit from ISR if core is hibernated */
+	if (core_if->hibernation_suspend == 1) {
+		return retval;
+	}
+#ifdef VERBOSE
+	DWC_DEBUGPL(DBG_ANY, "%s() gintsts=%08x	 gintmsk=%08x\n",
+		    __func__,
+		    DWC_READ_REG32(&global_regs->gintsts),
+		    DWC_READ_REG32(&global_regs->gintmsk));
+#endif
+
+	if (dwc_otg_is_device_mode(core_if)) {
+		DWC_SPINLOCK(pcd->lock);
+#ifdef VERBOSE
+		DWC_DEBUGPL(DBG_PCDV, "%s() gintsts=%08x  gintmsk=%08x\n",
+			    __func__,
+			    DWC_READ_REG32(&global_regs->gintsts),
+			    DWC_READ_REG32(&global_regs->gintmsk));
+#endif
+
+		gintr_status.d32 = dwc_otg_read_core_intr(core_if);
+
+		DWC_DEBUGPL(DBG_PCDV, "%s: gintsts&gintmsk=%08x\n",
+			    __func__, gintr_status.d32);
+
+		if (gintr_status.b.sofintr) {
+			retval |= dwc_otg_pcd_handle_sof_intr(pcd);
+		}
+		if (gintr_status.b.rxstsqlvl) {
+			retval |=
+			    dwc_otg_pcd_handle_rx_status_q_level_intr(pcd);
+		}
+		if (gintr_status.b.nptxfempty) {
+			retval |= dwc_otg_pcd_handle_np_tx_fifo_empty_intr(pcd);
+		}
+		if (gintr_status.b.goutnakeff) {
+			retval |= dwc_otg_pcd_handle_out_nak_effective(pcd);
+		}
+		if (gintr_status.b.i2cintr) {
+			retval |= dwc_otg_pcd_handle_i2c_intr(pcd);
+		}
+		if (gintr_status.b.erlysuspend) {
+			retval |= dwc_otg_pcd_handle_early_suspend_intr(pcd);
+		}
+		if (gintr_status.b.usbreset) {
+			retval |= dwc_otg_pcd_handle_usb_reset_intr(pcd);
+		}
+		if (gintr_status.b.enumdone) {
+			retval |= dwc_otg_pcd_handle_enum_done_intr(pcd);
+		}
+		if (gintr_status.b.isooutdrop) {
+			retval |=
+			    dwc_otg_pcd_handle_isoc_out_packet_dropped_intr
+			    (pcd);
+		}
+		if (gintr_status.b.eopframe) {
+			retval |=
+			    dwc_otg_pcd_handle_end_periodic_frame_intr(pcd);
+		}
+		if (gintr_status.b.inepint) {
+			if (!core_if->multiproc_int_enable) {
+				retval |= dwc_otg_pcd_handle_in_ep_intr(pcd);
+			}
+		}
+		if (gintr_status.b.outepintr) {
+			if (!core_if->multiproc_int_enable) {
+				retval |= dwc_otg_pcd_handle_out_ep_intr(pcd);
+			}
+		}
+		if (gintr_status.b.epmismatch) {
+			retval |= dwc_otg_pcd_handle_ep_mismatch_intr(pcd);
+		}
+		if (gintr_status.b.fetsusp) {
+			retval |= dwc_otg_pcd_handle_ep_fetsusp_intr(pcd);
+		}
+		if (gintr_status.b.ginnakeff) {
+			retval |= dwc_otg_pcd_handle_in_nak_effective(pcd);
+		}
+		if (gintr_status.b.incomplisoin) {
+			retval |=
+			    dwc_otg_pcd_handle_incomplete_isoc_in_intr(pcd);
+		}
+		if (gintr_status.b.incomplisoout) {
+			retval |=
+			    dwc_otg_pcd_handle_incomplete_isoc_out_intr(pcd);
+		}
+
+		/* In MPI mode Device Endpoints interrupts are asserted
+		 * without setting outepintr and inepint bits set, so these
+		 * Interrupt handlers are called without checking these bit-fields
+		 */
+		if (core_if->multiproc_int_enable) {
+			retval |= dwc_otg_pcd_handle_in_ep_intr(pcd);
+			retval |= dwc_otg_pcd_handle_out_ep_intr(pcd);
+		}
+#ifdef VERBOSE
+		DWC_DEBUGPL(DBG_PCDV, "%s() gintsts=%0x\n", __func__,
+			    DWC_READ_REG32(&global_regs->gintsts));
+#endif
+		DWC_SPINUNLOCK(pcd->lock);
+	}
+	return retval;
+}
+
+#endif /* DWC_HOST_ONLY */
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_pcd_linux.c b/drivers/usb/host/dwc_otg/dwc_otg_pcd_linux.c
new file mode 100644
index 000000000000..e214955d6914
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_pcd_linux.c
@@ -0,0 +1,1262 @@
+ /* ==========================================================================
+  * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_pcd_linux.c $
+  * $Revision: #21 $
+  * $Date: 2012/08/10 $
+  * $Change: 2047372 $
+  *
+  * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+  * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+  * otherwise expressly agreed to in writing between Synopsys and you.
+  *
+  * The Software IS NOT an item of Licensed Software or Licensed Product under
+  * any End User Software License Agreement or Agreement for Licensed Product
+  * with Synopsys or any supplement thereto. You are permitted to use and
+  * redistribute this Software in source and binary forms, with or without
+  * modification, provided that redistributions of source code must retain this
+  * notice. You may not view, use, disclose, copy or distribute this file or
+  * any information contained herein except pursuant to this license grant from
+  * Synopsys. If you do not agree with this notice, including the disclaimer
+  * below, then you are not authorized to use the Software.
+  *
+  * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+  * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+  * DAMAGE.
+  * ========================================================================== */
+#ifndef DWC_HOST_ONLY
+
+/** @file
+ * This file implements the Peripheral Controller Driver.
+ *
+ * The Peripheral Controller Driver (PCD) is responsible for
+ * translating requests from the Function Driver into the appropriate
+ * actions on the DWC_otg controller. It isolates the Function Driver
+ * from the specifics of the controller by providing an API to the
+ * Function Driver.
+ *
+ * The Peripheral Controller Driver for Linux will implement the
+ * Gadget API, so that the existing Gadget drivers can be used.
+ * (Gadget Driver is the Linux terminology for a Function Driver.)
+ *
+ * The Linux Gadget API is defined in the header file
+ * <code><linux/usb_gadget.h></code>.  The USB EP operations API is
+ * defined in the structure <code>usb_ep_ops</code> and the USB
+ * Controller API is defined in the structure
+ * <code>usb_gadget_ops</code>.
+ *
+ */
+
+#include "dwc_otg_os_dep.h"
+#include "dwc_otg_pcd_if.h"
+#include "dwc_otg_pcd.h"
+#include "dwc_otg_driver.h"
+#include "dwc_otg_dbg.h"
+
+extern bool fiq_enable;
+
+static struct gadget_wrapper {
+	dwc_otg_pcd_t *pcd;
+
+	struct usb_gadget gadget;
+	struct usb_gadget_driver *driver;
+
+	struct usb_ep ep0;
+	struct usb_ep in_ep[16];
+	struct usb_ep out_ep[16];
+
+} *gadget_wrapper;
+
+/* Display the contents of the buffer */
+extern void dump_msg(const u8 * buf, unsigned int length);
+/**
+ * Get the dwc_otg_pcd_ep_t* from usb_ep* pointer - NULL in case
+ * if the endpoint is not found
+ */
+static struct dwc_otg_pcd_ep *ep_from_handle(dwc_otg_pcd_t * pcd, void *handle)
+{
+	int i;
+	if (pcd->ep0.priv == handle) {
+		return &pcd->ep0;
+	}
+
+	for (i = 0; i < MAX_EPS_CHANNELS - 1; i++) {
+		if (pcd->in_ep[i].priv == handle)
+			return &pcd->in_ep[i];
+		if (pcd->out_ep[i].priv == handle)
+			return &pcd->out_ep[i];
+	}
+
+	return NULL;
+}
+
+/* USB Endpoint Operations */
+/*
+ * The following sections briefly describe the behavior of the Gadget
+ * API endpoint operations implemented in the DWC_otg driver
+ * software. Detailed descriptions of the generic behavior of each of
+ * these functions can be found in the Linux header file
+ * include/linux/usb_gadget.h.
+ *
+ * The Gadget API provides wrapper functions for each of the function
+ * pointers defined in usb_ep_ops. The Gadget Driver calls the wrapper
+ * function, which then calls the underlying PCD function. The
+ * following sections are named according to the wrapper
+ * functions. Within each section, the corresponding DWC_otg PCD
+ * function name is specified.
+ *
+ */
+
+/**
+ * This function is called by the Gadget Driver for each EP to be
+ * configured for the current configuration (SET_CONFIGURATION).
+ *
+ * This function initializes the dwc_otg_ep_t data structure, and then
+ * calls dwc_otg_ep_activate.
+ */
+static int ep_enable(struct usb_ep *usb_ep,
+		     const struct usb_endpoint_descriptor *ep_desc)
+{
+	int retval;
+
+	DWC_DEBUGPL(DBG_PCDV, "%s(%p,%p)\n", __func__, usb_ep, ep_desc);
+
+	if (!usb_ep || !ep_desc || ep_desc->bDescriptorType != USB_DT_ENDPOINT) {
+		DWC_WARN("%s, bad ep or descriptor\n", __func__);
+		return -EINVAL;
+	}
+	if (usb_ep == &gadget_wrapper->ep0) {
+		DWC_WARN("%s, bad ep(0)\n", __func__);
+		return -EINVAL;
+	}
+
+	/* Check FIFO size? */
+	if (!ep_desc->wMaxPacketSize) {
+		DWC_WARN("%s, bad %s maxpacket\n", __func__, usb_ep->name);
+		return -ERANGE;
+	}
+
+	if (!gadget_wrapper->driver ||
+	    gadget_wrapper->gadget.speed == USB_SPEED_UNKNOWN) {
+		DWC_WARN("%s, bogus device state\n", __func__);
+		return -ESHUTDOWN;
+	}
+
+	/* Delete after check - MAS */
+#if 0
+	nat = (uint32_t) ep_desc->wMaxPacketSize;
+	printk(KERN_ALERT "%s: nat (before) =%d\n", __func__, nat);
+	nat = (nat >> 11) & 0x03;
+	printk(KERN_ALERT "%s: nat (after) =%d\n", __func__, nat);
+#endif
+	retval = dwc_otg_pcd_ep_enable(gadget_wrapper->pcd,
+				       (const uint8_t *)ep_desc,
+				       (void *)usb_ep);
+	if (retval) {
+		DWC_WARN("dwc_otg_pcd_ep_enable failed\n");
+		return -EINVAL;
+	}
+
+	usb_ep->maxpacket = le16_to_cpu(ep_desc->wMaxPacketSize);
+
+	return 0;
+}
+
+/**
+ * This function is called when an EP is disabled due to disconnect or
+ * change in configuration. Any pending requests will terminate with a
+ * status of -ESHUTDOWN.
+ *
+ * This function modifies the dwc_otg_ep_t data structure for this EP,
+ * and then calls dwc_otg_ep_deactivate.
+ */
+static int ep_disable(struct usb_ep *usb_ep)
+{
+	int retval;
+
+	DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, usb_ep);
+	if (!usb_ep) {
+		DWC_DEBUGPL(DBG_PCD, "%s, %s not enabled\n", __func__,
+			    usb_ep ? usb_ep->name : NULL);
+		return -EINVAL;
+	}
+
+	retval = dwc_otg_pcd_ep_disable(gadget_wrapper->pcd, usb_ep);
+	if (retval) {
+		retval = -EINVAL;
+	}
+
+	return retval;
+}
+
+/**
+ * This function allocates a request object to use with the specified
+ * endpoint.
+ *
+ * @param ep The endpoint to be used with with the request
+ * @param gfp_flags the GFP_* flags to use.
+ */
+static struct usb_request *dwc_otg_pcd_alloc_request(struct usb_ep *ep,
+						     gfp_t gfp_flags)
+{
+	struct usb_request *usb_req;
+
+	DWC_DEBUGPL(DBG_PCDV, "%s(%p,%d)\n", __func__, ep, gfp_flags);
+	if (0 == ep) {
+		DWC_WARN("%s() %s\n", __func__, "Invalid EP!\n");
+		return 0;
+	}
+	usb_req = kzalloc(sizeof(*usb_req), gfp_flags);
+	if (0 == usb_req) {
+		DWC_WARN("%s() %s\n", __func__, "request allocation failed!\n");
+		return 0;
+	}
+	usb_req->dma = DWC_DMA_ADDR_INVALID;
+
+	return usb_req;
+}
+
+/**
+ * This function frees a request object.
+ *
+ * @param ep The endpoint associated with the request
+ * @param req The request being freed
+ */
+static void dwc_otg_pcd_free_request(struct usb_ep *ep, struct usb_request *req)
+{
+	DWC_DEBUGPL(DBG_PCDV, "%s(%p,%p)\n", __func__, ep, req);
+
+	if (0 == ep || 0 == req) {
+		DWC_WARN("%s() %s\n", __func__,
+			 "Invalid ep or req argument!\n");
+		return;
+	}
+
+	kfree(req);
+}
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+/**
+ * This function allocates an I/O buffer to be used for a transfer
+ * to/from the specified endpoint.
+ *
+ * @param usb_ep The endpoint to be used with with the request
+ * @param bytes The desired number of bytes for the buffer
+ * @param dma Pointer to the buffer's DMA address; must be valid
+ * @param gfp_flags the GFP_* flags to use.
+ * @return address of a new buffer or null is buffer could not be allocated.
+ */
+static void *dwc_otg_pcd_alloc_buffer(struct usb_ep *usb_ep, unsigned bytes,
+				      dma_addr_t * dma, gfp_t gfp_flags)
+{
+	void *buf;
+	dwc_otg_pcd_t *pcd = 0;
+
+	pcd = gadget_wrapper->pcd;
+
+	DWC_DEBUGPL(DBG_PCDV, "%s(%p,%d,%p,%0x)\n", __func__, usb_ep, bytes,
+		    dma, gfp_flags);
+
+	/* Check dword alignment */
+	if ((bytes & 0x3UL) != 0) {
+		DWC_WARN("%s() Buffer size is not a multiple of"
+			 "DWORD size (%d)", __func__, bytes);
+	}
+
+	buf = dma_alloc_coherent(NULL, bytes, dma, gfp_flags);
+	WARN_ON(!buf);
+
+	/* Check dword alignment */
+	if (((int)buf & 0x3UL) != 0) {
+		DWC_WARN("%s() Buffer is not DWORD aligned (%p)",
+			 __func__, buf);
+	}
+
+	return buf;
+}
+
+/**
+ * This function frees an I/O buffer that was allocated by alloc_buffer.
+ *
+ * @param usb_ep the endpoint associated with the buffer
+ * @param buf address of the buffer
+ * @param dma The buffer's DMA address
+ * @param bytes The number of bytes of the buffer
+ */
+static void dwc_otg_pcd_free_buffer(struct usb_ep *usb_ep, void *buf,
+				    dma_addr_t dma, unsigned bytes)
+{
+	dwc_otg_pcd_t *pcd = 0;
+
+	pcd = gadget_wrapper->pcd;
+
+	DWC_DEBUGPL(DBG_PCDV, "%s(%p,%0x,%d)\n", __func__, buf, dma, bytes);
+
+	dma_free_coherent(NULL, bytes, buf, dma);
+}
+#endif
+
+/**
+ * This function is used to submit an I/O Request to an EP.
+ *
+ *	- When the request completes the request's completion callback
+ *	  is called to return the request to the driver.
+ *	- An EP, except control EPs, may have multiple requests
+ *	  pending.
+ *	- Once submitted the request cannot be examined or modified.
+ *	- Each request is turned into one or more packets.
+ *	- A BULK EP can queue any amount of data; the transfer is
+ *	  packetized.
+ *	- Zero length Packets are specified with the request 'zero'
+ *	  flag.
+ */
+static int ep_queue(struct usb_ep *usb_ep, struct usb_request *usb_req,
+		    gfp_t gfp_flags)
+{
+	dwc_otg_pcd_t *pcd;
+	struct dwc_otg_pcd_ep *ep = NULL;
+	int retval = 0, is_isoc_ep = 0;
+	dma_addr_t dma_addr = DWC_DMA_ADDR_INVALID;
+
+	DWC_DEBUGPL(DBG_PCDV, "%s(%p,%p,%d)\n",
+		    __func__, usb_ep, usb_req, gfp_flags);
+
+	if (!usb_req || !usb_req->complete || !usb_req->buf) {
+		DWC_WARN("bad params\n");
+		return -EINVAL;
+	}
+
+	if (!usb_ep) {
+		DWC_WARN("bad ep\n");
+		return -EINVAL;
+	}
+
+	pcd = gadget_wrapper->pcd;
+	if (!gadget_wrapper->driver ||
+	    gadget_wrapper->gadget.speed == USB_SPEED_UNKNOWN) {
+		DWC_DEBUGPL(DBG_PCDV, "gadget.speed=%d\n",
+			    gadget_wrapper->gadget.speed);
+		DWC_WARN("bogus device state\n");
+		return -ESHUTDOWN;
+	}
+
+	DWC_DEBUGPL(DBG_PCD, "%s queue req %p, len %d buf %p\n",
+		    usb_ep->name, usb_req, usb_req->length, usb_req->buf);
+
+	usb_req->status = -EINPROGRESS;
+	usb_req->actual = 0;
+
+	ep = ep_from_handle(pcd, usb_ep);
+	if (ep == NULL)
+		is_isoc_ep = 0;
+	else
+		is_isoc_ep = (ep->dwc_ep.type == DWC_OTG_EP_TYPE_ISOC) ? 1 : 0;
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+	dma_addr = usb_req->dma;
+#else
+	if (GET_CORE_IF(pcd)->dma_enable) {
+                dwc_otg_device_t *otg_dev = gadget_wrapper->pcd->otg_dev;
+                struct device *dev = NULL;
+
+                if (otg_dev != NULL)
+                        dev = DWC_OTG_OS_GETDEV(otg_dev->os_dep);
+
+		if (usb_req->length != 0 &&
+                    usb_req->dma == DWC_DMA_ADDR_INVALID) {
+                        dma_addr = dma_map_single(dev, usb_req->buf,
+                                                  usb_req->length,
+                                                  ep->dwc_ep.is_in ?
+                                                        DMA_TO_DEVICE:
+                                                        DMA_FROM_DEVICE);
+		}
+	}
+#endif
+
+#ifdef DWC_UTE_PER_IO
+	if (is_isoc_ep == 1) {
+		retval = dwc_otg_pcd_xiso_ep_queue(pcd, usb_ep, usb_req->buf, dma_addr,
+			usb_req->length, usb_req->zero, usb_req,
+			gfp_flags == GFP_ATOMIC ? 1 : 0, &usb_req->ext_req);
+		if (retval)
+			return -EINVAL;
+
+		return 0;
+	}
+#endif
+	retval = dwc_otg_pcd_ep_queue(pcd, usb_ep, usb_req->buf, dma_addr,
+				      usb_req->length, usb_req->zero, usb_req,
+				      gfp_flags == GFP_ATOMIC ? 1 : 0);
+	if (retval) {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/**
+ * This function cancels an I/O request from an EP.
+ */
+static int ep_dequeue(struct usb_ep *usb_ep, struct usb_request *usb_req)
+{
+	DWC_DEBUGPL(DBG_PCDV, "%s(%p,%p)\n", __func__, usb_ep, usb_req);
+
+	if (!usb_ep || !usb_req) {
+		DWC_WARN("bad argument\n");
+		return -EINVAL;
+	}
+	if (!gadget_wrapper->driver ||
+	    gadget_wrapper->gadget.speed == USB_SPEED_UNKNOWN) {
+		DWC_WARN("bogus device state\n");
+		return -ESHUTDOWN;
+	}
+	if (dwc_otg_pcd_ep_dequeue(gadget_wrapper->pcd, usb_ep, usb_req)) {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/**
+ * usb_ep_set_halt stalls an endpoint.
+ *
+ * usb_ep_clear_halt clears an endpoint halt and resets its data
+ * toggle.
+ *
+ * Both of these functions are implemented with the same underlying
+ * function. The behavior depends on the value argument.
+ *
+ * @param[in] usb_ep the Endpoint to halt or clear halt.
+ * @param[in] value
+ *	- 0 means clear_halt.
+ *	- 1 means set_halt,
+ *	- 2 means clear stall lock flag.
+ *	- 3 means set  stall lock flag.
+ */
+static int ep_halt(struct usb_ep *usb_ep, int value)
+{
+	int retval = 0;
+
+	DWC_DEBUGPL(DBG_PCD, "HALT %s %d\n", usb_ep->name, value);
+
+	if (!usb_ep) {
+		DWC_WARN("bad ep\n");
+		return -EINVAL;
+	}
+
+	retval = dwc_otg_pcd_ep_halt(gadget_wrapper->pcd, usb_ep, value);
+	if (retval == -DWC_E_AGAIN) {
+		return -EAGAIN;
+	} else if (retval) {
+		retval = -EINVAL;
+	}
+
+	return retval;
+}
+
+//#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,30))
+#if 0
+/**
+ * ep_wedge: sets the halt feature and ignores clear requests
+ *
+ * @usb_ep: the endpoint being wedged
+ *
+ * Use this to stall an endpoint and ignore CLEAR_FEATURE(HALT_ENDPOINT)
+ * requests. If the gadget driver clears the halt status, it will
+ * automatically unwedge the endpoint.
+ *
+ * Returns zero on success, else negative errno. *
+ * Check usb_ep_set_wedge() at "usb_gadget.h" for details
+ */
+static int ep_wedge(struct usb_ep *usb_ep)
+{
+	int retval = 0;
+
+	DWC_DEBUGPL(DBG_PCD, "WEDGE %s\n", usb_ep->name);
+
+	if (!usb_ep) {
+		DWC_WARN("bad ep\n");
+		return -EINVAL;
+	}
+
+	retval = dwc_otg_pcd_ep_wedge(gadget_wrapper->pcd, usb_ep);
+	if (retval == -DWC_E_AGAIN) {
+		retval = -EAGAIN;
+	} else if (retval) {
+		retval = -EINVAL;
+	}
+
+	return retval;
+}
+#endif
+
+#ifdef DWC_EN_ISOC
+/**
+ * This function is used to submit an ISOC Transfer Request to an EP.
+ *
+ *	- Every time a sync period completes the request's completion callback
+ *	  is called to provide data to the gadget driver.
+ *	- Once submitted the request cannot be modified.
+ *	- Each request is turned into periodic data packets untill ISO
+ *	  Transfer is stopped..
+ */
+static int iso_ep_start(struct usb_ep *usb_ep, struct usb_iso_request *req,
+			gfp_t gfp_flags)
+{
+	int retval = 0;
+
+	if (!req || !req->process_buffer || !req->buf0 || !req->buf1) {
+		DWC_WARN("bad params\n");
+		return -EINVAL;
+	}
+
+	if (!usb_ep) {
+		DWC_PRINTF("bad params\n");
+		return -EINVAL;
+	}
+
+	req->status = -EINPROGRESS;
+
+	retval =
+	    dwc_otg_pcd_iso_ep_start(gadget_wrapper->pcd, usb_ep, req->buf0,
+				     req->buf1, req->dma0, req->dma1,
+				     req->sync_frame, req->data_pattern_frame,
+				     req->data_per_frame,
+				     req->
+				     flags & USB_REQ_ISO_ASAP ? -1 :
+				     req->start_frame, req->buf_proc_intrvl,
+				     req, gfp_flags == GFP_ATOMIC ? 1 : 0);
+
+	if (retval) {
+		return -EINVAL;
+	}
+
+	return retval;
+}
+
+/**
+ * This function stops ISO EP Periodic Data Transfer.
+ */
+static int iso_ep_stop(struct usb_ep *usb_ep, struct usb_iso_request *req)
+{
+	int retval = 0;
+	if (!usb_ep) {
+		DWC_WARN("bad ep\n");
+	}
+
+	if (!gadget_wrapper->driver ||
+	    gadget_wrapper->gadget.speed == USB_SPEED_UNKNOWN) {
+		DWC_DEBUGPL(DBG_PCDV, "gadget.speed=%d\n",
+			    gadget_wrapper->gadget.speed);
+		DWC_WARN("bogus device state\n");
+	}
+
+	dwc_otg_pcd_iso_ep_stop(gadget_wrapper->pcd, usb_ep, req);
+	if (retval) {
+		retval = -EINVAL;
+	}
+
+	return retval;
+}
+
+static struct usb_iso_request *alloc_iso_request(struct usb_ep *ep,
+						 int packets, gfp_t gfp_flags)
+{
+	struct usb_iso_request *pReq = NULL;
+	uint32_t req_size;
+
+	req_size = sizeof(struct usb_iso_request);
+	req_size +=
+	    (2 * packets * (sizeof(struct usb_gadget_iso_packet_descriptor)));
+
+	pReq = kmalloc(req_size, gfp_flags);
+	if (!pReq) {
+		DWC_WARN("Can't allocate Iso Request\n");
+		return 0;
+	}
+	pReq->iso_packet_desc0 = (void *)(pReq + 1);
+
+	pReq->iso_packet_desc1 = pReq->iso_packet_desc0 + packets;
+
+	return pReq;
+}
+
+static void free_iso_request(struct usb_ep *ep, struct usb_iso_request *req)
+{
+	kfree(req);
+}
+
+static struct usb_isoc_ep_ops dwc_otg_pcd_ep_ops = {
+	.ep_ops = {
+		   .enable = ep_enable,
+		   .disable = ep_disable,
+
+		   .alloc_request = dwc_otg_pcd_alloc_request,
+		   .free_request = dwc_otg_pcd_free_request,
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+		   .alloc_buffer = dwc_otg_pcd_alloc_buffer,
+		   .free_buffer = dwc_otg_pcd_free_buffer,
+#endif
+
+		   .queue = ep_queue,
+		   .dequeue = ep_dequeue,
+
+		   .set_halt = ep_halt,
+		   .fifo_status = 0,
+		   .fifo_flush = 0,
+		   },
+	.iso_ep_start = iso_ep_start,
+	.iso_ep_stop = iso_ep_stop,
+	.alloc_iso_request = alloc_iso_request,
+	.free_iso_request = free_iso_request,
+};
+
+#else
+
+	int (*enable) (struct usb_ep *ep,
+		const struct usb_endpoint_descriptor *desc);
+	int (*disable) (struct usb_ep *ep);
+
+	struct usb_request *(*alloc_request) (struct usb_ep *ep,
+		gfp_t gfp_flags);
+	void (*free_request) (struct usb_ep *ep, struct usb_request *req);
+
+	int (*queue) (struct usb_ep *ep, struct usb_request *req,
+		gfp_t gfp_flags);
+	int (*dequeue) (struct usb_ep *ep, struct usb_request *req);
+
+	int (*set_halt) (struct usb_ep *ep, int value);
+	int (*set_wedge) (struct usb_ep *ep);
+
+	int (*fifo_status) (struct usb_ep *ep);
+	void (*fifo_flush) (struct usb_ep *ep);
+static struct usb_ep_ops dwc_otg_pcd_ep_ops = {
+	.enable = ep_enable,
+	.disable = ep_disable,
+
+	.alloc_request = dwc_otg_pcd_alloc_request,
+	.free_request = dwc_otg_pcd_free_request,
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+	.alloc_buffer = dwc_otg_pcd_alloc_buffer,
+	.free_buffer = dwc_otg_pcd_free_buffer,
+#else
+	/* .set_wedge = ep_wedge, */
+        .set_wedge = NULL, /* uses set_halt instead */
+#endif
+
+	.queue = ep_queue,
+	.dequeue = ep_dequeue,
+
+	.set_halt = ep_halt,
+	.fifo_status = 0,
+	.fifo_flush = 0,
+
+};
+
+#endif /* _EN_ISOC_ */
+/*	Gadget Operations */
+/**
+ * The following gadget operations will be implemented in the DWC_otg
+ * PCD. Functions in the API that are not described below are not
+ * implemented.
+ *
+ * The Gadget API provides wrapper functions for each of the function
+ * pointers defined in usb_gadget_ops. The Gadget Driver calls the
+ * wrapper function, which then calls the underlying PCD function. The
+ * following sections are named according to the wrapper functions
+ * (except for ioctl, which doesn't have a wrapper function). Within
+ * each section, the corresponding DWC_otg PCD function name is
+ * specified.
+ *
+ */
+
+/**
+ *Gets the USB Frame number of the last SOF.
+ */
+static int get_frame_number(struct usb_gadget *gadget)
+{
+	struct gadget_wrapper *d;
+
+	DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, gadget);
+
+	if (gadget == 0) {
+		return -ENODEV;
+	}
+
+	d = container_of(gadget, struct gadget_wrapper, gadget);
+	return dwc_otg_pcd_get_frame_number(d->pcd);
+}
+
+#ifdef CONFIG_USB_DWC_OTG_LPM
+static int test_lpm_enabled(struct usb_gadget *gadget)
+{
+	struct gadget_wrapper *d;
+
+	d = container_of(gadget, struct gadget_wrapper, gadget);
+
+	return dwc_otg_pcd_is_lpm_enabled(d->pcd);
+}
+#endif
+
+/**
+ * Initiates Session Request Protocol (SRP) to wakeup the host if no
+ * session is in progress. If a session is already in progress, but
+ * the device is suspended, remote wakeup signaling is started.
+ *
+ */
+static int wakeup(struct usb_gadget *gadget)
+{
+	struct gadget_wrapper *d;
+
+	DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, gadget);
+
+	if (gadget == 0) {
+		return -ENODEV;
+	} else {
+		d = container_of(gadget, struct gadget_wrapper, gadget);
+	}
+	dwc_otg_pcd_wakeup(d->pcd);
+	return 0;
+}
+
+static const struct usb_gadget_ops dwc_otg_pcd_ops = {
+	.get_frame = get_frame_number,
+	.wakeup = wakeup,
+#ifdef CONFIG_USB_DWC_OTG_LPM
+	.lpm_support = test_lpm_enabled,
+#endif
+	// current versions must always be self-powered
+};
+
+static int _setup(dwc_otg_pcd_t * pcd, uint8_t * bytes)
+{
+	int retval = -DWC_E_NOT_SUPPORTED;
+	if (gadget_wrapper->driver && gadget_wrapper->driver->setup) {
+		retval = gadget_wrapper->driver->setup(&gadget_wrapper->gadget,
+						       (struct usb_ctrlrequest
+							*)bytes);
+	}
+
+	if (retval == -ENOTSUPP) {
+		retval = -DWC_E_NOT_SUPPORTED;
+	} else if (retval < 0) {
+		retval = -DWC_E_INVALID;
+	}
+
+	return retval;
+}
+
+#ifdef DWC_EN_ISOC
+static int _isoc_complete(dwc_otg_pcd_t * pcd, void *ep_handle,
+			  void *req_handle, int proc_buf_num)
+{
+	int i, packet_count;
+	struct usb_gadget_iso_packet_descriptor *iso_packet = 0;
+	struct usb_iso_request *iso_req = req_handle;
+
+	if (proc_buf_num) {
+		iso_packet = iso_req->iso_packet_desc1;
+	} else {
+		iso_packet = iso_req->iso_packet_desc0;
+	}
+	packet_count =
+	    dwc_otg_pcd_get_iso_packet_count(pcd, ep_handle, req_handle);
+	for (i = 0; i < packet_count; ++i) {
+		int status;
+		int actual;
+		int offset;
+		dwc_otg_pcd_get_iso_packet_params(pcd, ep_handle, req_handle,
+						  i, &status, &actual, &offset);
+		switch (status) {
+		case -DWC_E_NO_DATA:
+			status = -ENODATA;
+			break;
+		default:
+			if (status) {
+				DWC_PRINTF("unknown status in isoc packet\n");
+			}
+
+		}
+		iso_packet[i].status = status;
+		iso_packet[i].offset = offset;
+		iso_packet[i].actual_length = actual;
+	}
+
+	iso_req->status = 0;
+	iso_req->process_buffer(ep_handle, iso_req);
+
+	return 0;
+}
+#endif /* DWC_EN_ISOC */
+
+#ifdef DWC_UTE_PER_IO
+/**
+ * Copy the contents of the extended request to the Linux usb_request's
+ * extended part and call the gadget's completion.
+ *
+ * @param pcd			Pointer to the pcd structure
+ * @param ep_handle		Void pointer to the usb_ep structure
+ * @param req_handle	Void pointer to the usb_request structure
+ * @param status		Request status returned from the portable logic
+ * @param ereq_port		Void pointer to the extended request structure
+ *						created in the the portable part that contains the
+ *						results of the processed iso packets.
+ */
+static int _xisoc_complete(dwc_otg_pcd_t * pcd, void *ep_handle,
+			   void *req_handle, int32_t status, void *ereq_port)
+{
+	struct dwc_ute_iso_req_ext *ereqorg = NULL;
+	struct dwc_iso_xreq_port *ereqport = NULL;
+	struct dwc_ute_iso_packet_descriptor *desc_org = NULL;
+	int i;
+	struct usb_request *req;
+	//struct dwc_ute_iso_packet_descriptor *
+	//int status = 0;
+
+	req = (struct usb_request *)req_handle;
+	ereqorg = &req->ext_req;
+	ereqport = (struct dwc_iso_xreq_port *)ereq_port;
+	desc_org = ereqorg->per_io_frame_descs;
+
+	if (req && req->complete) {
+		/* Copy the request data from the portable logic to our request */
+		for (i = 0; i < ereqport->pio_pkt_count; i++) {
+			desc_org[i].actual_length =
+			    ereqport->per_io_frame_descs[i].actual_length;
+			desc_org[i].status =
+			    ereqport->per_io_frame_descs[i].status;
+		}
+
+		switch (status) {
+		case -DWC_E_SHUTDOWN:
+			req->status = -ESHUTDOWN;
+			break;
+		case -DWC_E_RESTART:
+			req->status = -ECONNRESET;
+			break;
+		case -DWC_E_INVALID:
+			req->status = -EINVAL;
+			break;
+		case -DWC_E_TIMEOUT:
+			req->status = -ETIMEDOUT;
+			break;
+		default:
+			req->status = status;
+		}
+
+		/* And call the gadget's completion */
+		req->complete(ep_handle, req);
+	}
+
+	return 0;
+}
+#endif /* DWC_UTE_PER_IO */
+
+static int _complete(dwc_otg_pcd_t * pcd, void *ep_handle,
+		     void *req_handle, int32_t status, uint32_t actual)
+{
+	struct usb_request *req = (struct usb_request *)req_handle;
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,27)
+	struct dwc_otg_pcd_ep *ep = NULL;
+#endif
+
+	if (req && req->complete) {
+		switch (status) {
+		case -DWC_E_SHUTDOWN:
+			req->status = -ESHUTDOWN;
+			break;
+		case -DWC_E_RESTART:
+			req->status = -ECONNRESET;
+			break;
+		case -DWC_E_INVALID:
+			req->status = -EINVAL;
+			break;
+		case -DWC_E_TIMEOUT:
+			req->status = -ETIMEDOUT;
+			break;
+		default:
+			req->status = status;
+
+		}
+
+		req->actual = actual;
+		DWC_SPINUNLOCK(pcd->lock);
+		req->complete(ep_handle, req);
+		DWC_SPINLOCK(pcd->lock);
+	}
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,27)
+	ep = ep_from_handle(pcd, ep_handle);
+	if (GET_CORE_IF(pcd)->dma_enable) {
+                if (req->length != 0) {
+                        dwc_otg_device_t *otg_dev = gadget_wrapper->pcd->otg_dev;
+                        struct device *dev = NULL;
+
+                        if (otg_dev != NULL)
+                                  dev = DWC_OTG_OS_GETDEV(otg_dev->os_dep);
+
+			dma_unmap_single(dev, req->dma, req->length,
+                                         ep->dwc_ep.is_in ?
+                                                DMA_TO_DEVICE: DMA_FROM_DEVICE);
+                }
+	}
+#endif
+
+	return 0;
+}
+
+static int _connect(dwc_otg_pcd_t * pcd, int speed)
+{
+	gadget_wrapper->gadget.speed = speed;
+	return 0;
+}
+
+static int _disconnect(dwc_otg_pcd_t * pcd)
+{
+	if (gadget_wrapper->driver && gadget_wrapper->driver->disconnect) {
+		gadget_wrapper->driver->disconnect(&gadget_wrapper->gadget);
+	}
+	return 0;
+}
+
+static int _resume(dwc_otg_pcd_t * pcd)
+{
+	if (gadget_wrapper->driver && gadget_wrapper->driver->resume) {
+		gadget_wrapper->driver->resume(&gadget_wrapper->gadget);
+	}
+
+	return 0;
+}
+
+static int _suspend(dwc_otg_pcd_t * pcd)
+{
+	if (gadget_wrapper->driver && gadget_wrapper->driver->suspend) {
+		gadget_wrapper->driver->suspend(&gadget_wrapper->gadget);
+	}
+	return 0;
+}
+
+/**
+ * This function updates the otg values in the gadget structure.
+ */
+static int _hnp_changed(dwc_otg_pcd_t * pcd)
+{
+
+	if (!gadget_wrapper->gadget.is_otg)
+		return 0;
+
+	gadget_wrapper->gadget.b_hnp_enable = get_b_hnp_enable(pcd);
+	gadget_wrapper->gadget.a_hnp_support = get_a_hnp_support(pcd);
+	gadget_wrapper->gadget.a_alt_hnp_support = get_a_alt_hnp_support(pcd);
+	return 0;
+}
+
+static int _reset(dwc_otg_pcd_t * pcd)
+{
+	return 0;
+}
+
+#ifdef DWC_UTE_CFI
+static int _cfi_setup(dwc_otg_pcd_t * pcd, void *cfi_req)
+{
+	int retval = -DWC_E_INVALID;
+	if (gadget_wrapper->driver->cfi_feature_setup) {
+		retval =
+		    gadget_wrapper->driver->
+		    cfi_feature_setup(&gadget_wrapper->gadget,
+				      (struct cfi_usb_ctrlrequest *)cfi_req);
+	}
+
+	return retval;
+}
+#endif
+
+static const struct dwc_otg_pcd_function_ops fops = {
+	.complete = _complete,
+#ifdef DWC_EN_ISOC
+	.isoc_complete = _isoc_complete,
+#endif
+	.setup = _setup,
+	.disconnect = _disconnect,
+	.connect = _connect,
+	.resume = _resume,
+	.suspend = _suspend,
+	.hnp_changed = _hnp_changed,
+	.reset = _reset,
+#ifdef DWC_UTE_CFI
+	.cfi_setup = _cfi_setup,
+#endif
+#ifdef DWC_UTE_PER_IO
+	.xisoc_complete = _xisoc_complete,
+#endif
+};
+
+/**
+ * This function is the top level PCD interrupt handler.
+ */
+static irqreturn_t dwc_otg_pcd_irq(int irq, void *dev)
+{
+	dwc_otg_pcd_t *pcd = dev;
+	int32_t retval = IRQ_NONE;
+
+	retval = dwc_otg_pcd_handle_intr(pcd);
+	if (retval != 0) {
+		S3C2410X_CLEAR_EINTPEND();
+	}
+	return IRQ_RETVAL(retval);
+}
+
+/**
+ * This function initialized the usb_ep structures to there default
+ * state.
+ *
+ * @param d Pointer on gadget_wrapper.
+ */
+static void gadget_add_eps(struct gadget_wrapper *d)
+{
+	static const char *names[] = {
+
+		"ep0",
+		"ep1in",
+		"ep2in",
+		"ep3in",
+		"ep4in",
+		"ep5in",
+		"ep6in",
+		"ep7in",
+		"ep8in",
+		"ep9in",
+		"ep10in",
+		"ep11in",
+		"ep12in",
+		"ep13in",
+		"ep14in",
+		"ep15in",
+		"ep1out",
+		"ep2out",
+		"ep3out",
+		"ep4out",
+		"ep5out",
+		"ep6out",
+		"ep7out",
+		"ep8out",
+		"ep9out",
+		"ep10out",
+		"ep11out",
+		"ep12out",
+		"ep13out",
+		"ep14out",
+		"ep15out"
+	};
+
+	int i;
+	struct usb_ep *ep;
+	int8_t dev_endpoints;
+
+	DWC_DEBUGPL(DBG_PCDV, "%s\n", __func__);
+
+	INIT_LIST_HEAD(&d->gadget.ep_list);
+	d->gadget.ep0 = &d->ep0;
+	d->gadget.speed = USB_SPEED_UNKNOWN;
+
+	INIT_LIST_HEAD(&d->gadget.ep0->ep_list);
+
+	/**
+	 * Initialize the EP0 structure.
+	 */
+	ep = &d->ep0;
+
+	/* Init the usb_ep structure. */
+	ep->name = names[0];
+	ep->ops = (struct usb_ep_ops *)&dwc_otg_pcd_ep_ops;
+
+	/**
+	 * @todo NGS: What should the max packet size be set to
+	 * here?  Before EP type is set?
+	 */
+	ep->maxpacket = MAX_PACKET_SIZE;
+	dwc_otg_pcd_ep_enable(d->pcd, NULL, ep);
+
+	list_add_tail(&ep->ep_list, &d->gadget.ep_list);
+
+	/**
+	 * Initialize the EP structures.
+	 */
+	dev_endpoints = d->pcd->core_if->dev_if->num_in_eps;
+
+	for (i = 0; i < dev_endpoints; i++) {
+		ep = &d->in_ep[i];
+
+		/* Init the usb_ep structure. */
+		ep->name = names[d->pcd->in_ep[i].dwc_ep.num];
+		ep->ops = (struct usb_ep_ops *)&dwc_otg_pcd_ep_ops;
+
+		/**
+		 * @todo NGS: What should the max packet size be set to
+		 * here?  Before EP type is set?
+		 */
+		ep->maxpacket = MAX_PACKET_SIZE;
+		list_add_tail(&ep->ep_list, &d->gadget.ep_list);
+	}
+
+	dev_endpoints = d->pcd->core_if->dev_if->num_out_eps;
+
+	for (i = 0; i < dev_endpoints; i++) {
+		ep = &d->out_ep[i];
+
+		/* Init the usb_ep structure. */
+		ep->name = names[15 + d->pcd->out_ep[i].dwc_ep.num];
+		ep->ops = (struct usb_ep_ops *)&dwc_otg_pcd_ep_ops;
+
+		/**
+		 * @todo NGS: What should the max packet size be set to
+		 * here?  Before EP type is set?
+		 */
+		ep->maxpacket = MAX_PACKET_SIZE;
+
+		list_add_tail(&ep->ep_list, &d->gadget.ep_list);
+	}
+
+	/* remove ep0 from the list.  There is a ep0 pointer. */
+	list_del_init(&d->ep0.ep_list);
+
+	d->ep0.maxpacket = MAX_EP0_SIZE;
+}
+
+/**
+ * This function releases the Gadget device.
+ * required by device_unregister().
+ *
+ * @todo Should this do something?	Should it free the PCD?
+ */
+static void dwc_otg_pcd_gadget_release(struct device *dev)
+{
+	DWC_DEBUGPL(DBG_PCDV, "%s(%p)\n", __func__, dev);
+}
+
+static struct gadget_wrapper *alloc_wrapper(dwc_bus_dev_t *_dev)
+{
+	static char pcd_name[] = "dwc_otg_pcd";
+	dwc_otg_device_t *otg_dev = DWC_OTG_BUSDRVDATA(_dev);
+	struct gadget_wrapper *d;
+	int retval;
+
+	d = DWC_ALLOC(sizeof(*d));
+	if (d == NULL) {
+		return NULL;
+	}
+
+	memset(d, 0, sizeof(*d));
+
+	d->gadget.name = pcd_name;
+	d->pcd = otg_dev->pcd;
+
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30)
+	strcpy(d->gadget.dev.bus_id, "gadget");
+#else
+	dev_set_name(&d->gadget.dev, "%s", "gadget");
+#endif
+
+	d->gadget.dev.parent = &_dev->dev;
+	d->gadget.dev.release = dwc_otg_pcd_gadget_release;
+	d->gadget.ops = &dwc_otg_pcd_ops;
+	d->gadget.max_speed = dwc_otg_pcd_is_dualspeed(otg_dev->pcd) ? USB_SPEED_HIGH:USB_SPEED_FULL;
+	d->gadget.is_otg = dwc_otg_pcd_is_otg(otg_dev->pcd);
+
+	d->driver = 0;
+	/* Register the gadget device */
+	retval = device_register(&d->gadget.dev);
+	if (retval != 0) {
+		DWC_ERROR("device_register failed\n");
+		DWC_FREE(d);
+		return NULL;
+	}
+
+	return d;
+}
+
+static void free_wrapper(struct gadget_wrapper *d)
+{
+	if (d->driver) {
+		/* should have been done already by driver model core */
+		DWC_WARN("driver '%s' is still registered\n",
+			 d->driver->driver.name);
+#ifdef CONFIG_USB_GADGET
+		usb_gadget_unregister_driver(d->driver);
+#endif
+	}
+
+	device_unregister(&d->gadget.dev);
+	DWC_FREE(d);
+}
+
+/**
+ * This function initialized the PCD portion of the driver.
+ *
+ */
+int pcd_init(dwc_bus_dev_t *_dev)
+{
+	dwc_otg_device_t *otg_dev = DWC_OTG_BUSDRVDATA(_dev);
+	int retval = 0;
+
+	DWC_DEBUGPL(DBG_PCDV, "%s(%p) otg_dev=%p\n", __func__, _dev, otg_dev);
+
+	otg_dev->pcd = dwc_otg_pcd_init(otg_dev);
+
+	if (!otg_dev->pcd) {
+		DWC_ERROR("dwc_otg_pcd_init failed\n");
+		return -ENOMEM;
+	}
+
+	otg_dev->pcd->otg_dev = otg_dev;
+	gadget_wrapper = alloc_wrapper(_dev);
+
+	/*
+	 * Initialize EP structures
+	 */
+	gadget_add_eps(gadget_wrapper);
+	/*
+	 * Setup interupt handler
+	 */
+	DWC_DEBUGPL(DBG_ANY, "registering handler for irq%d\n",
+                    otg_dev->os_dep.irq_num);
+	retval = request_irq(otg_dev->os_dep.irq_num, dwc_otg_pcd_irq,
+			     IRQF_SHARED, gadget_wrapper->gadget.name,
+			     otg_dev->pcd);
+	if (retval != 0) {
+		DWC_ERROR("request of irq%d failed\n", otg_dev->os_dep.irq_num);
+		free_wrapper(gadget_wrapper);
+		return -EBUSY;
+	}
+
+	dwc_otg_pcd_start(gadget_wrapper->pcd, &fops);
+
+	return retval;
+}
+
+/**
+ * Cleanup the PCD.
+ */
+void pcd_remove(dwc_bus_dev_t *_dev)
+{
+	dwc_otg_device_t *otg_dev = DWC_OTG_BUSDRVDATA(_dev);
+	dwc_otg_pcd_t *pcd = otg_dev->pcd;
+
+	DWC_DEBUGPL(DBG_PCDV, "%s(%p) otg_dev %p\n", __func__, _dev, otg_dev);
+
+	/*
+	 * Free the IRQ
+	 */
+	free_irq(otg_dev->os_dep.irq_num, pcd);
+	dwc_otg_pcd_remove(otg_dev->pcd);
+	free_wrapper(gadget_wrapper);
+	otg_dev->pcd = 0;
+}
+
+#endif /* DWC_HOST_ONLY */
diff --git a/drivers/usb/host/dwc_otg/dwc_otg_regs.h b/drivers/usb/host/dwc_otg/dwc_otg_regs.h
new file mode 100644
index 000000000000..8e0e7b569f1a
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/dwc_otg_regs.h
@@ -0,0 +1,2550 @@
+/* ==========================================================================
+ * $File: //dwh/usb_iip/dev/software/otg/linux/drivers/dwc_otg_regs.h $
+ * $Revision: #98 $
+ * $Date: 2012/08/10 $
+ * $Change: 2047372 $
+ *
+ * Synopsys HS OTG Linux Software Driver and documentation (hereinafter,
+ * "Software") is an Unsupported proprietary work of Synopsys, Inc. unless
+ * otherwise expressly agreed to in writing between Synopsys and you.
+ *
+ * The Software IS NOT an item of Licensed Software or Licensed Product under
+ * any End User Software License Agreement or Agreement for Licensed Product
+ * with Synopsys or any supplement thereto. You are permitted to use and
+ * redistribute this Software in source and binary forms, with or without
+ * modification, provided that redistributions of source code must retain this
+ * notice. You may not view, use, disclose, copy or distribute this file or
+ * any information contained herein except pursuant to this license grant from
+ * Synopsys. If you do not agree with this notice, including the disclaimer
+ * below, then you are not authorized to use the Software.
+ *
+ * THIS SOFTWARE IS BEING DISTRIBUTED BY SYNOPSYS SOLELY ON AN "AS IS" BASIS
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE HEREBY DISCLAIMED. IN NO EVENT SHALL SYNOPSYS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ * ========================================================================== */
+
+#ifndef __DWC_OTG_REGS_H__
+#define __DWC_OTG_REGS_H__
+
+#include "dwc_otg_core_if.h"
+
+/**
+ * @file
+ *
+ * This file contains the data structures for accessing the DWC_otg core registers.
+ *
+ * The application interfaces with the HS OTG core by reading from and
+ * writing to the Control and Status Register (CSR) space through the
+ * AHB Slave interface. These registers are 32 bits wide, and the
+ * addresses are 32-bit-block aligned.
+ * CSRs are classified as follows:
+ * - Core Global Registers
+ * - Device Mode Registers
+ * - Device Global Registers
+ * - Device Endpoint Specific Registers
+ * - Host Mode Registers
+ * - Host Global Registers
+ * - Host Port CSRs
+ * - Host Channel Specific Registers
+ *
+ * Only the Core Global registers can be accessed in both Device and
+ * Host modes. When the HS OTG core is operating in one mode, either
+ * Device or Host, the application must not access registers from the
+ * other mode. When the core switches from one mode to another, the
+ * registers in the new mode of operation must be reprogrammed as they
+ * would be after a power-on reset.
+ */
+
+/****************************************************************************/
+/** DWC_otg Core registers .
+ * The dwc_otg_core_global_regs structure defines the size
+ * and relative field offsets for the Core Global registers.
+ */
+typedef struct dwc_otg_core_global_regs {
+	/** OTG Control and Status Register.  <i>Offset: 000h</i> */
+	volatile uint32_t gotgctl;
+	/** OTG Interrupt Register.	 <i>Offset: 004h</i> */
+	volatile uint32_t gotgint;
+	/**Core AHB Configuration Register.	 <i>Offset: 008h</i> */
+	volatile uint32_t gahbcfg;
+
+#define DWC_GLBINTRMASK		0x0001
+#define DWC_DMAENABLE		0x0020
+#define DWC_NPTXEMPTYLVL_EMPTY	0x0080
+#define DWC_NPTXEMPTYLVL_HALFEMPTY	0x0000
+#define DWC_PTXEMPTYLVL_EMPTY	0x0100
+#define DWC_PTXEMPTYLVL_HALFEMPTY	0x0000
+
+	/**Core USB Configuration Register.	 <i>Offset: 00Ch</i> */
+	volatile uint32_t gusbcfg;
+	/**Core Reset Register.	 <i>Offset: 010h</i> */
+	volatile uint32_t grstctl;
+	/**Core Interrupt Register.	 <i>Offset: 014h</i> */
+	volatile uint32_t gintsts;
+	/**Core Interrupt Mask Register.  <i>Offset: 018h</i> */
+	volatile uint32_t gintmsk;
+	/**Receive Status Queue Read Register (Read Only).	<i>Offset: 01Ch</i> */
+	volatile uint32_t grxstsr;
+	/**Receive Status Queue Read & POP Register (Read Only).  <i>Offset: 020h</i>*/
+	volatile uint32_t grxstsp;
+	/**Receive FIFO Size Register.	<i>Offset: 024h</i> */
+	volatile uint32_t grxfsiz;
+	/**Non Periodic Transmit FIFO Size Register.  <i>Offset: 028h</i> */
+	volatile uint32_t gnptxfsiz;
+	/**Non Periodic Transmit FIFO/Queue Status Register (Read
+	 * Only). <i>Offset: 02Ch</i> */
+	volatile uint32_t gnptxsts;
+	/**I2C Access Register.	 <i>Offset: 030h</i> */
+	volatile uint32_t gi2cctl;
+	/**PHY Vendor Control Register.	 <i>Offset: 034h</i> */
+	volatile uint32_t gpvndctl;
+	/**General Purpose Input/Output Register.  <i>Offset: 038h</i> */
+	volatile uint32_t ggpio;
+	/**User ID Register.  <i>Offset: 03Ch</i> */
+	volatile uint32_t guid;
+	/**Synopsys ID Register (Read Only).  <i>Offset: 040h</i> */
+	volatile uint32_t gsnpsid;
+	/**User HW Config1 Register (Read Only).  <i>Offset: 044h</i> */
+	volatile uint32_t ghwcfg1;
+	/**User HW Config2 Register (Read Only).  <i>Offset: 048h</i> */
+	volatile uint32_t ghwcfg2;
+#define DWC_SLAVE_ONLY_ARCH 0
+#define DWC_EXT_DMA_ARCH 1
+#define DWC_INT_DMA_ARCH 2
+
+#define DWC_MODE_HNP_SRP_CAPABLE	0
+#define DWC_MODE_SRP_ONLY_CAPABLE	1
+#define DWC_MODE_NO_HNP_SRP_CAPABLE		2
+#define DWC_MODE_SRP_CAPABLE_DEVICE		3
+#define DWC_MODE_NO_SRP_CAPABLE_DEVICE	4
+#define DWC_MODE_SRP_CAPABLE_HOST	5
+#define DWC_MODE_NO_SRP_CAPABLE_HOST	6
+
+	/**User HW Config3 Register (Read Only).  <i>Offset: 04Ch</i> */
+	volatile uint32_t ghwcfg3;
+	/**User HW Config4 Register (Read Only).  <i>Offset: 050h</i>*/
+	volatile uint32_t ghwcfg4;
+	/** Core LPM Configuration register <i>Offset: 054h</i>*/
+	volatile uint32_t glpmcfg;
+	/** Global PowerDn Register <i>Offset: 058h</i> */
+	volatile uint32_t gpwrdn;
+	/** Global DFIFO SW Config Register  <i>Offset: 05Ch</i> */
+	volatile uint32_t gdfifocfg;
+	/** ADP Control Register  <i>Offset: 060h</i> */
+	volatile uint32_t adpctl;
+	/** Reserved  <i>Offset: 064h-0FFh</i> */
+	volatile uint32_t reserved39[39];
+	/** Host Periodic Transmit FIFO Size Register. <i>Offset: 100h</i> */
+	volatile uint32_t hptxfsiz;
+	/** Device Periodic Transmit FIFO#n Register if dedicated fifos are disabled,
+		otherwise Device Transmit FIFO#n Register.
+	 * <i>Offset: 104h + (FIFO_Number-1)*04h, 1 <= FIFO Number <= 15 (1<=n<=15).</i> */
+	volatile uint32_t dtxfsiz[15];
+} dwc_otg_core_global_regs_t;
+
+/**
+ * This union represents the bit fields of the Core OTG Control
+ * and Status Register (GOTGCTL).  Set the bits using the bit
+ * fields then write the <i>d32</i> value to the register.
+ */
+typedef union gotgctl_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		unsigned sesreqscs:1;
+		unsigned sesreq:1;
+		unsigned vbvalidoven:1;
+		unsigned vbvalidovval:1;
+		unsigned avalidoven:1;
+		unsigned avalidovval:1;
+		unsigned bvalidoven:1;
+		unsigned bvalidovval:1;
+		unsigned hstnegscs:1;
+		unsigned hnpreq:1;
+		unsigned hstsethnpen:1;
+		unsigned devhnpen:1;
+		unsigned reserved12_15:4;
+		unsigned conidsts:1;
+		unsigned dbnctime:1;
+		unsigned asesvld:1;
+		unsigned bsesvld:1;
+		unsigned otgver:1;
+		unsigned reserved1:1;
+		unsigned multvalidbc:5;
+		unsigned chirpen:1;
+		unsigned reserved28_31:4;
+	} b;
+} gotgctl_data_t;
+
+/**
+ * This union represents the bit fields of the Core OTG Interrupt Register
+ * (GOTGINT).  Set/clear the bits using the bit fields then write the <i>d32</i>
+ * value to the register.
+ */
+typedef union gotgint_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		/** Current Mode */
+		unsigned reserved0_1:2;
+
+		/** Session End Detected */
+		unsigned sesenddet:1;
+
+		unsigned reserved3_7:5;
+
+		/** Session Request Success Status Change */
+		unsigned sesreqsucstschng:1;
+		/** Host Negotiation Success Status Change */
+		unsigned hstnegsucstschng:1;
+
+		unsigned reserved10_16:7;
+
+		/** Host Negotiation Detected */
+		unsigned hstnegdet:1;
+		/** A-Device Timeout Change */
+		unsigned adevtoutchng:1;
+		/** Debounce Done */
+		unsigned debdone:1;
+		/** Multi-Valued input changed */
+		unsigned mvic:1;
+
+		unsigned reserved31_21:11;
+
+	} b;
+} gotgint_data_t;
+
+/**
+ * This union represents the bit fields of the Core AHB Configuration
+ * Register (GAHBCFG). Set/clear the bits using the bit fields then
+ * write the <i>d32</i> value to the register.
+ */
+typedef union gahbcfg_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		unsigned glblintrmsk:1;
+#define DWC_GAHBCFG_GLBINT_ENABLE		1
+
+		unsigned hburstlen:4;
+#define DWC_GAHBCFG_INT_DMA_BURST_SINGLE	0
+#define DWC_GAHBCFG_INT_DMA_BURST_INCR		1
+#define DWC_GAHBCFG_INT_DMA_BURST_INCR4		3
+#define DWC_GAHBCFG_INT_DMA_BURST_INCR8		5
+#define DWC_GAHBCFG_INT_DMA_BURST_INCR16	7
+
+		unsigned dmaenable:1;
+#define DWC_GAHBCFG_DMAENABLE			1
+		unsigned reserved:1;
+		unsigned nptxfemplvl_txfemplvl:1;
+		unsigned ptxfemplvl:1;
+#define DWC_GAHBCFG_TXFEMPTYLVL_EMPTY		1
+#define DWC_GAHBCFG_TXFEMPTYLVL_HALFEMPTY	0
+		unsigned reserved9_20:12;
+		unsigned remmemsupp:1;
+		unsigned notialldmawrit:1;
+		unsigned ahbsingle:1;
+		unsigned reserved24_31:8;
+	} b;
+} gahbcfg_data_t;
+
+/**
+ * This union represents the bit fields of the Core USB Configuration
+ * Register (GUSBCFG). Set the bits using the bit fields then write
+ * the <i>d32</i> value to the register.
+ */
+typedef union gusbcfg_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		unsigned toutcal:3;
+		unsigned phyif:1;
+		unsigned ulpi_utmi_sel:1;
+		unsigned fsintf:1;
+		unsigned physel:1;
+		unsigned ddrsel:1;
+		unsigned srpcap:1;
+		unsigned hnpcap:1;
+		unsigned usbtrdtim:4;
+		unsigned reserved1:1;
+		unsigned phylpwrclksel:1;
+		unsigned otgutmifssel:1;
+		unsigned ulpi_fsls:1;
+		unsigned ulpi_auto_res:1;
+		unsigned ulpi_clk_sus_m:1;
+		unsigned ulpi_ext_vbus_drv:1;
+		unsigned ulpi_int_vbus_indicator:1;
+		unsigned term_sel_dl_pulse:1;
+		unsigned indicator_complement:1;
+		unsigned indicator_pass_through:1;
+		unsigned ulpi_int_prot_dis:1;
+		unsigned ic_usb_cap:1;
+		unsigned ic_traffic_pull_remove:1;
+		unsigned tx_end_delay:1;
+		unsigned force_host_mode:1;
+		unsigned force_dev_mode:1;
+		unsigned reserved31:1;
+	} b;
+} gusbcfg_data_t;
+
+/**
+ * This union represents the bit fields of the Core Reset Register
+ * (GRSTCTL).  Set/clear the bits using the bit fields then write the
+ * <i>d32</i> value to the register.
+ */
+typedef union grstctl_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		/** Core Soft Reset (CSftRst) (Device and Host)
+		 *
+		 * The application can flush the control logic in the
+		 * entire core using this bit. This bit resets the
+		 * pipelines in the AHB Clock domain as well as the
+		 * PHY Clock domain.
+		 *
+		 * The state machines are reset to an IDLE state, the
+		 * control bits in the CSRs are cleared, all the
+		 * transmit FIFOs and the receive FIFO are flushed.
+		 *
+		 * The status mask bits that control the generation of
+		 * the interrupt, are cleared, to clear the
+		 * interrupt. The interrupt status bits are not
+		 * cleared, so the application can get the status of
+		 * any events that occurred in the core after it has
+		 * set this bit.
+		 *
+		 * Any transactions on the AHB are terminated as soon
+		 * as possible following the protocol. Any
+		 * transactions on the USB are terminated immediately.
+		 *
+		 * The configuration settings in the CSRs are
+		 * unchanged, so the software doesn't have to
+		 * reprogram these registers (Device
+		 * Configuration/Host Configuration/Core System
+		 * Configuration/Core PHY Configuration).
+		 *
+		 * The application can write to this bit, any time it
+		 * wants to reset the core. This is a self clearing
+		 * bit and the core clears this bit after all the
+		 * necessary logic is reset in the core, which may
+		 * take several clocks, depending on the current state
+		 * of the core.
+		 */
+		unsigned csftrst:1;
+		/** Hclk Soft Reset
+		 *
+		 * The application uses this bit to reset the control logic in
+		 * the AHB clock domain. Only AHB clock domain pipelines are
+		 * reset.
+		 */
+		unsigned hsftrst:1;
+		/** Host Frame Counter Reset (Host Only)<br>
+		 *
+		 * The application can reset the (micro)frame number
+		 * counter inside the core, using this bit. When the
+		 * (micro)frame counter is reset, the subsequent SOF
+		 * sent out by the core, will have a (micro)frame
+		 * number of 0.
+		 */
+		unsigned hstfrm:1;
+		/** In Token Sequence Learning Queue Flush
+		 * (INTknQFlsh) (Device Only)
+		 */
+		unsigned intknqflsh:1;
+		/** RxFIFO Flush (RxFFlsh) (Device and Host)
+		 *
+		 * The application can flush the entire Receive FIFO
+		 * using this bit. The application must first
+		 * ensure that the core is not in the middle of a
+		 * transaction. The application should write into
+		 * this bit, only after making sure that neither the
+		 * DMA engine is reading from the RxFIFO nor the MAC
+		 * is writing the data in to the FIFO. The
+		 * application should wait until the bit is cleared
+		 * before performing any other operations. This bit
+		 * will takes 8 clocks (slowest of PHY or AHB clock)
+		 * to clear.
+		 */
+		unsigned rxfflsh:1;
+		/** TxFIFO Flush (TxFFlsh) (Device and Host).
+		 *
+		 * This bit is used to selectively flush a single or
+		 * all transmit FIFOs. The application must first
+		 * ensure that the core is not in the middle of a
+		 * transaction. The application should write into
+		 * this bit, only after making sure that neither the
+		 * DMA engine is writing into the TxFIFO nor the MAC
+		 * is reading the data out of the FIFO. The
+		 * application should wait until the core clears this
+		 * bit, before performing any operations. This bit
+		 * will takes 8 clocks (slowest of PHY or AHB clock)
+		 * to clear.
+		 */
+		unsigned txfflsh:1;
+
+		/** TxFIFO Number (TxFNum) (Device and Host).
+		 *
+		 * This is the FIFO number which needs to be flushed,
+		 * using the TxFIFO Flush bit. This field should not
+		 * be changed until the TxFIFO Flush bit is cleared by
+		 * the core.
+		 *	 - 0x0 : Non Periodic TxFIFO Flush
+		 *	 - 0x1 : Periodic TxFIFO #1 Flush in device mode
+		 *	   or Periodic TxFIFO in host mode
+		 *	 - 0x2 : Periodic TxFIFO #2 Flush in device mode.
+		 *	 - ...
+		 *	 - 0xF : Periodic TxFIFO #15 Flush in device mode
+		 *	 - 0x10: Flush all the Transmit NonPeriodic and
+		 *	   Transmit Periodic FIFOs in the core
+		 */
+		unsigned txfnum:5;
+		/** Reserved */
+		unsigned reserved11_29:19;
+		/** DMA Request Signal.	 Indicated DMA request is in
+		 * probress. Used for debug purpose. */
+		unsigned dmareq:1;
+		/** AHB Master Idle.  Indicates the AHB Master State
+		 * Machine is in IDLE condition. */
+		unsigned ahbidle:1;
+	} b;
+} grstctl_t;
+
+/**
+ * This union represents the bit fields of the Core Interrupt Mask
+ * Register (GINTMSK). Set/clear the bits using the bit fields then
+ * write the <i>d32</i> value to the register.
+ */
+typedef union gintmsk_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		unsigned reserved0:1;
+		unsigned modemismatch:1;
+		unsigned otgintr:1;
+		unsigned sofintr:1;
+		unsigned rxstsqlvl:1;
+		unsigned nptxfempty:1;
+		unsigned ginnakeff:1;
+		unsigned goutnakeff:1;
+		unsigned ulpickint:1;
+		unsigned i2cintr:1;
+		unsigned erlysuspend:1;
+		unsigned usbsuspend:1;
+		unsigned usbreset:1;
+		unsigned enumdone:1;
+		unsigned isooutdrop:1;
+		unsigned eopframe:1;
+		unsigned restoredone:1;
+		unsigned epmismatch:1;
+		unsigned inepintr:1;
+		unsigned outepintr:1;
+		unsigned incomplisoin:1;
+		unsigned incomplisoout:1;
+		unsigned fetsusp:1;
+		unsigned resetdet:1;
+		unsigned portintr:1;
+		unsigned hcintr:1;
+		unsigned ptxfempty:1;
+		unsigned lpmtranrcvd:1;
+		unsigned conidstschng:1;
+		unsigned disconnect:1;
+		unsigned sessreqintr:1;
+		unsigned wkupintr:1;
+	} b;
+} gintmsk_data_t;
+/**
+ * This union represents the bit fields of the Core Interrupt Register
+ * (GINTSTS).  Set/clear the bits using the bit fields then write the
+ * <i>d32</i> value to the register.
+ */
+typedef union gintsts_data {
+	/** raw register data */
+	uint32_t d32;
+#define DWC_SOF_INTR_MASK 0x0008
+	/** register bits */
+	struct {
+#define DWC_HOST_MODE 1
+		unsigned curmode:1;
+		unsigned modemismatch:1;
+		unsigned otgintr:1;
+		unsigned sofintr:1;
+		unsigned rxstsqlvl:1;
+		unsigned nptxfempty:1;
+		unsigned ginnakeff:1;
+		unsigned goutnakeff:1;
+		unsigned ulpickint:1;
+		unsigned i2cintr:1;
+		unsigned erlysuspend:1;
+		unsigned usbsuspend:1;
+		unsigned usbreset:1;
+		unsigned enumdone:1;
+		unsigned isooutdrop:1;
+		unsigned eopframe:1;
+		unsigned restoredone:1;
+		unsigned epmismatch:1;
+		unsigned inepint:1;
+		unsigned outepintr:1;
+		unsigned incomplisoin:1;
+		unsigned incomplisoout:1;
+		unsigned fetsusp:1;
+		unsigned resetdet:1;
+		unsigned portintr:1;
+		unsigned hcintr:1;
+		unsigned ptxfempty:1;
+		unsigned lpmtranrcvd:1;
+		unsigned conidstschng:1;
+		unsigned disconnect:1;
+		unsigned sessreqintr:1;
+		unsigned wkupintr:1;
+	} b;
+} gintsts_data_t;
+
+/**
+ * This union represents the bit fields in the Device Receive Status Read and
+ * Pop Registers (GRXSTSR, GRXSTSP) Read the register into the <i>d32</i>
+ * element then read out the bits using the <i>b</i>it elements.
+ */
+typedef union device_grxsts_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		unsigned epnum:4;
+		unsigned bcnt:11;
+		unsigned dpid:2;
+
+#define DWC_STS_DATA_UPDT		0x2	// OUT Data Packet
+#define DWC_STS_XFER_COMP		0x3	// OUT Data Transfer Complete
+
+#define DWC_DSTS_GOUT_NAK		0x1	// Global OUT NAK
+#define DWC_DSTS_SETUP_COMP		0x4	// Setup Phase Complete
+#define DWC_DSTS_SETUP_UPDT 0x6	// SETUP Packet
+		unsigned pktsts:4;
+		unsigned fn:4;
+		unsigned reserved25_31:7;
+	} b;
+} device_grxsts_data_t;
+
+/**
+ * This union represents the bit fields in the Host Receive Status Read and
+ * Pop Registers (GRXSTSR, GRXSTSP) Read the register into the <i>d32</i>
+ * element then read out the bits using the <i>b</i>it elements.
+ */
+typedef union host_grxsts_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		unsigned chnum:4;
+		unsigned bcnt:11;
+		unsigned dpid:2;
+
+		unsigned pktsts:4;
+#define DWC_GRXSTS_PKTSTS_IN			  0x2
+#define DWC_GRXSTS_PKTSTS_IN_XFER_COMP	  0x3
+#define DWC_GRXSTS_PKTSTS_DATA_TOGGLE_ERR 0x5
+#define DWC_GRXSTS_PKTSTS_CH_HALTED		  0x7
+
+		unsigned reserved21_31:11;
+	} b;
+} host_grxsts_data_t;
+
+/**
+ * This union represents the bit fields in the FIFO Size Registers (HPTXFSIZ,
+ * GNPTXFSIZ, DPTXFSIZn, DIEPTXFn). Read the register into the <i>d32</i> element
+ * then read out the bits using the <i>b</i>it elements.
+ */
+typedef union fifosize_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		unsigned startaddr:16;
+		unsigned depth:16;
+	} b;
+} fifosize_data_t;
+
+/**
+ * This union represents the bit fields in the Non-Periodic Transmit
+ * FIFO/Queue Status Register (GNPTXSTS). Read the register into the
+ * <i>d32</i> element then read out the bits using the <i>b</i>it
+ * elements.
+ */
+typedef union gnptxsts_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		unsigned nptxfspcavail:16;
+		unsigned nptxqspcavail:8;
+		/** Top of the Non-Periodic Transmit Request Queue
+		 *	- bit 24 - Terminate (Last entry for the selected
+		 *	  channel/EP)
+		 *	- bits 26:25 - Token Type
+		 *	  - 2'b00 - IN/OUT
+		 *	  - 2'b01 - Zero Length OUT
+		 *	  - 2'b10 - PING/Complete Split
+		 *	  - 2'b11 - Channel Halt
+		 *	- bits 30:27 - Channel/EP Number
+		 */
+		unsigned nptxqtop_terminate:1;
+		unsigned nptxqtop_token:2;
+		unsigned nptxqtop_chnep:4;
+		unsigned reserved:1;
+	} b;
+} gnptxsts_data_t;
+
+/**
+ * This union represents the bit fields in the Transmit
+ * FIFO Status Register (DTXFSTS). Read the register into the
+ * <i>d32</i> element then read out the bits using the <i>b</i>it
+ * elements.
+ */
+typedef union dtxfsts_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		unsigned txfspcavail:16;
+		unsigned reserved:16;
+	} b;
+} dtxfsts_data_t;
+
+/**
+ * This union represents the bit fields in the I2C Control Register
+ * (I2CCTL). Read the register into the <i>d32</i> element then read out the
+ * bits using the <i>b</i>it elements.
+ */
+typedef union gi2cctl_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		unsigned rwdata:8;
+		unsigned regaddr:8;
+		unsigned addr:7;
+		unsigned i2cen:1;
+		unsigned ack:1;
+		unsigned i2csuspctl:1;
+		unsigned i2cdevaddr:2;
+		unsigned i2cdatse0:1;
+		unsigned reserved:1;
+		unsigned rw:1;
+		unsigned bsydne:1;
+	} b;
+} gi2cctl_data_t;
+
+/**
+ * This union represents the bit fields in the PHY Vendor Control Register
+ * (GPVNDCTL). Read the register into the <i>d32</i> element then read out the
+ * bits using the <i>b</i>it elements.
+ */
+typedef union gpvndctl_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		unsigned regdata:8;
+		unsigned vctrl:8;
+		unsigned regaddr16_21:6;
+		unsigned regwr:1;
+		unsigned reserved23_24:2;
+		unsigned newregreq:1;
+		unsigned vstsbsy:1;
+		unsigned vstsdone:1;
+		unsigned reserved28_30:3;
+		unsigned disulpidrvr:1;
+	} b;
+} gpvndctl_data_t;
+
+/**
+ * This union represents the bit fields in the General Purpose
+ * Input/Output Register (GGPIO).
+ * Read the register into the <i>d32</i> element then read out the
+ * bits using the <i>b</i>it elements.
+ */
+typedef union ggpio_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		unsigned gpi:16;
+		unsigned gpo:16;
+	} b;
+} ggpio_data_t;
+
+/**
+ * This union represents the bit fields in the User ID Register
+ * (GUID). Read the register into the <i>d32</i> element then read out the
+ * bits using the <i>b</i>it elements.
+ */
+typedef union guid_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		unsigned rwdata:32;
+	} b;
+} guid_data_t;
+
+/**
+ * This union represents the bit fields in the Synopsys ID Register
+ * (GSNPSID). Read the register into the <i>d32</i> element then read out the
+ * bits using the <i>b</i>it elements.
+ */
+typedef union gsnpsid_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		unsigned rwdata:32;
+	} b;
+} gsnpsid_data_t;
+
+/**
+ * This union represents the bit fields in the User HW Config1
+ * Register.  Read the register into the <i>d32</i> element then read
+ * out the bits using the <i>b</i>it elements.
+ */
+typedef union hwcfg1_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		unsigned ep_dir0:2;
+		unsigned ep_dir1:2;
+		unsigned ep_dir2:2;
+		unsigned ep_dir3:2;
+		unsigned ep_dir4:2;
+		unsigned ep_dir5:2;
+		unsigned ep_dir6:2;
+		unsigned ep_dir7:2;
+		unsigned ep_dir8:2;
+		unsigned ep_dir9:2;
+		unsigned ep_dir10:2;
+		unsigned ep_dir11:2;
+		unsigned ep_dir12:2;
+		unsigned ep_dir13:2;
+		unsigned ep_dir14:2;
+		unsigned ep_dir15:2;
+	} b;
+} hwcfg1_data_t;
+
+/**
+ * This union represents the bit fields in the User HW Config2
+ * Register.  Read the register into the <i>d32</i> element then read
+ * out the bits using the <i>b</i>it elements.
+ */
+typedef union hwcfg2_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		/* GHWCFG2 */
+		unsigned op_mode:3;
+#define DWC_HWCFG2_OP_MODE_HNP_SRP_CAPABLE_OTG 0
+#define DWC_HWCFG2_OP_MODE_SRP_ONLY_CAPABLE_OTG 1
+#define DWC_HWCFG2_OP_MODE_NO_HNP_SRP_CAPABLE_OTG 2
+#define DWC_HWCFG2_OP_MODE_SRP_CAPABLE_DEVICE 3
+#define DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE 4
+#define DWC_HWCFG2_OP_MODE_SRP_CAPABLE_HOST 5
+#define DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST 6
+
+		unsigned architecture:2;
+		unsigned point2point:1;
+		unsigned hs_phy_type:2;
+#define DWC_HWCFG2_HS_PHY_TYPE_NOT_SUPPORTED 0
+#define DWC_HWCFG2_HS_PHY_TYPE_UTMI 1
+#define DWC_HWCFG2_HS_PHY_TYPE_ULPI 2
+#define DWC_HWCFG2_HS_PHY_TYPE_UTMI_ULPI 3
+
+		unsigned fs_phy_type:2;
+		unsigned num_dev_ep:4;
+		unsigned num_host_chan:4;
+		unsigned perio_ep_supported:1;
+		unsigned dynamic_fifo:1;
+		unsigned multi_proc_int:1;
+		unsigned reserved21:1;
+		unsigned nonperio_tx_q_depth:2;
+		unsigned host_perio_tx_q_depth:2;
+		unsigned dev_token_q_depth:5;
+		unsigned otg_enable_ic_usb:1;
+	} b;
+} hwcfg2_data_t;
+
+/**
+ * This union represents the bit fields in the User HW Config3
+ * Register.  Read the register into the <i>d32</i> element then read
+ * out the bits using the <i>b</i>it elements.
+ */
+typedef union hwcfg3_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		/* GHWCFG3 */
+		unsigned xfer_size_cntr_width:4;
+		unsigned packet_size_cntr_width:3;
+		unsigned otg_func:1;
+		unsigned i2c:1;
+		unsigned vendor_ctrl_if:1;
+		unsigned optional_features:1;
+		unsigned synch_reset_type:1;
+		unsigned adp_supp:1;
+		unsigned otg_enable_hsic:1;
+		unsigned bc_support:1;
+		unsigned otg_lpm_en:1;
+		unsigned dfifo_depth:16;
+	} b;
+} hwcfg3_data_t;
+
+/**
+ * This union represents the bit fields in the User HW Config4
+ * Register.  Read the register into the <i>d32</i> element then read
+ * out the bits using the <i>b</i>it elements.
+ */
+typedef union hwcfg4_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		unsigned num_dev_perio_in_ep:4;
+		unsigned power_optimiz:1;
+		unsigned min_ahb_freq:1;
+		unsigned hiber:1;
+		unsigned xhiber:1;
+		unsigned reserved:6;
+		unsigned utmi_phy_data_width:2;
+		unsigned num_dev_mode_ctrl_ep:4;
+		unsigned iddig_filt_en:1;
+		unsigned vbus_valid_filt_en:1;
+		unsigned a_valid_filt_en:1;
+		unsigned b_valid_filt_en:1;
+		unsigned session_end_filt_en:1;
+		unsigned ded_fifo_en:1;
+		unsigned num_in_eps:4;
+		unsigned desc_dma:1;
+		unsigned desc_dma_dyn:1;
+	} b;
+} hwcfg4_data_t;
+
+/**
+ * This union represents the bit fields of the Core LPM Configuration
+ * Register (GLPMCFG). Set the bits using bit fields then write
+ * the <i>d32</i> value to the register.
+ */
+typedef union glpmctl_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		/** LPM-Capable (LPMCap) (Device and Host)
+		 * The application uses this bit to control
+		 * the DWC_otg core LPM capabilities.
+		 */
+		unsigned lpm_cap_en:1;
+		/** LPM response programmed by application (AppL1Res) (Device)
+		 * Handshake response to LPM token pre-programmed
+		 * by device application software.
+		 */
+		unsigned appl_resp:1;
+		/** Host Initiated Resume Duration (HIRD) (Device and Host)
+		 * In Host mode this field indicates the value of HIRD
+		 * to be sent in an LPM transaction.
+		 * In Device mode this field is updated with the
+		 * Received LPM Token HIRD bmAttribute
+		 * when an ACK/NYET/STALL response is sent
+		 * to an LPM transaction.
+		 */
+		unsigned hird:4;
+		/** RemoteWakeEnable (bRemoteWake) (Device and Host)
+		 * In Host mode this bit indicates the value of remote
+		 * wake up to be sent in wIndex field of LPM transaction.
+		 * In Device mode this field is updated with the
+		 * Received LPM Token bRemoteWake bmAttribute
+		 * when an ACK/NYET/STALL response is sent
+		 * to an LPM transaction.
+		 */
+		unsigned rem_wkup_en:1;
+		/** Enable utmi_sleep_n (EnblSlpM) (Device and Host)
+		 * The application uses this bit to control
+		 * the utmi_sleep_n assertion to the PHY when in L1 state.
+		 */
+		unsigned en_utmi_sleep:1;
+		/** HIRD Threshold (HIRD_Thres) (Device and Host)
+		 */
+		unsigned hird_thres:5;
+		/** LPM Response (CoreL1Res) (Device and Host)
+		 * In Host mode this bit contains handsake response to
+		 * LPM transaction.
+		 * In Device mode the response of the core to
+		 * LPM transaction received is reflected in these two bits.
+			- 0x0 : ERROR (No handshake response)
+			- 0x1 : STALL
+			- 0x2 : NYET
+			- 0x3 : ACK
+		 */
+		unsigned lpm_resp:2;
+		/** Port Sleep Status (SlpSts) (Device and Host)
+		 * This bit is set as long as a Sleep condition
+		 * is present on the USB bus.
+		 */
+		unsigned prt_sleep_sts:1;
+		/** Sleep State Resume OK (L1ResumeOK) (Device and Host)
+		 * Indicates that the application or host
+		 * can start resume from Sleep state.
+		 */
+		unsigned sleep_state_resumeok:1;
+		/** LPM channel Index (LPM_Chnl_Indx) (Host)
+		 * The channel number on which the LPM transaction
+		 * has to be applied while sending
+		 * an LPM transaction to the local device.
+		 */
+		unsigned lpm_chan_index:4;
+		/** LPM Retry Count (LPM_Retry_Cnt) (Host)
+		 * Number host retries that would be performed
+		 * if the device response was not valid response.
+		 */
+		unsigned retry_count:3;
+		/** Send LPM Transaction (SndLPM) (Host)
+		 * When set by application software,
+		 * an LPM transaction containing two tokens
+		 * is sent.
+		 */
+		unsigned send_lpm:1;
+		/** LPM Retry status (LPM_RetryCnt_Sts) (Host)
+		 * Number of LPM Host Retries still remaining
+		 * to be transmitted for the current LPM sequence
+		 */
+		unsigned retry_count_sts:3;
+		unsigned reserved28_29:2;
+		/** In host mode once this bit is set, the host
+		 * configures to drive the HSIC Idle state on the bus.
+		 * It then waits for the  device to initiate the Connect sequence.
+		 * In device mode once this bit is set, the device waits for
+		 * the HSIC Idle line state on the bus. Upon receving the Idle
+		 * line state, it initiates the HSIC Connect sequence.
+		 */
+		unsigned hsic_connect:1;
+		/** This bit overrides and functionally inverts
+		 * the if_select_hsic input port signal.
+		 */
+		unsigned inv_sel_hsic:1;
+	} b;
+} glpmcfg_data_t;
+
+/**
+ * This union represents the bit fields of the Core ADP Timer, Control and
+ * Status Register (ADPTIMCTLSTS). Set the bits using bit fields then write
+ * the <i>d32</i> value to the register.
+ */
+typedef union adpctl_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		/** Probe Discharge (PRB_DSCHG)
+		 *  These bits set the times for TADP_DSCHG.
+		 *  These bits are defined as follows:
+		 *  2'b00 - 4 msec
+		 *  2'b01 - 8 msec
+		 *  2'b10 - 16 msec
+		 *  2'b11 - 32 msec
+		 */
+		unsigned prb_dschg:2;
+		/** Probe Delta (PRB_DELTA)
+		 *  These bits set the resolution for RTIM   value.
+		 *  The bits are defined in units of 32 kHz clock cycles as follows:
+		 *  2'b00  -  1 cycles
+		 *  2'b01  -  2 cycles
+		 *  2'b10 -  3 cycles
+		 *  2'b11 - 4 cycles
+		 *  For example if this value is chosen to 2'b01, it means that RTIM
+		 *  increments for every 3(three) 32Khz clock cycles.
+		 */
+		unsigned prb_delta:2;
+		/** Probe Period (PRB_PER)
+		 *  These bits sets the TADP_PRD as shown in Figure 4 as follows:
+		 *  2'b00  -  0.625 to 0.925 sec (typical 0.775 sec)
+		 *  2'b01  -  1.25 to 1.85 sec (typical 1.55 sec)
+		 *  2'b10  -  1.9 to 2.6 sec (typical 2.275 sec)
+		 *  2'b11  -  Reserved
+		 */
+		unsigned prb_per:2;
+		/** These bits capture the latest time it took for VBUS to ramp from
+		 *  VADP_SINK to VADP_PRB.
+		 *  0x000  -  1 cycles
+		 *  0x001  -  2 cycles
+		 *  0x002  -  3 cycles
+		 *  etc
+		 *  0x7FF  -  2048 cycles
+		 *  A time of 1024 cycles at 32 kHz corresponds to a time of 32 msec.
+		*/
+		unsigned rtim:11;
+		/** Enable Probe (EnaPrb)
+		 *  When programmed to 1'b1, the core performs a probe operation.
+		 *  This bit is valid only if OTG_Ver = 1'b1.
+		 */
+		unsigned enaprb:1;
+		/** Enable Sense (EnaSns)
+		 *  When programmed to 1'b1, the core performs a Sense operation.
+		 *  This bit is valid only if OTG_Ver = 1'b1.
+		 */
+		unsigned enasns:1;
+		/** ADP Reset (ADPRes)
+		 *  When set, ADP controller is reset.
+		 *  This bit is valid only if OTG_Ver = 1'b1.
+		 */
+		unsigned adpres:1;
+		/** ADP Enable (ADPEn)
+		 *  When set, the core performs either ADP probing or sensing
+		 *  based on EnaPrb or EnaSns.
+		 *  This bit is valid only if OTG_Ver = 1'b1.
+		 */
+		unsigned adpen:1;
+		/** ADP Probe Interrupt (ADP_PRB_INT)
+		 *  When this bit is set, it means that the VBUS
+		 *  voltage is greater than VADP_PRB or VADP_PRB is reached.
+		 *  This bit is valid only if OTG_Ver = 1'b1.
+		 */
+		unsigned adp_prb_int:1;
+		/**
+		 *  ADP Sense Interrupt (ADP_SNS_INT)
+		 *  When this bit is set, it means that the VBUS voltage is greater than
+		 *  VADP_SNS value or VADP_SNS is reached.
+		 *  This bit is valid only if OTG_Ver = 1'b1.
+		 */
+		unsigned adp_sns_int:1;
+		/** ADP Tomeout Interrupt (ADP_TMOUT_INT)
+		 *  This bit is relevant only for an ADP probe.
+		 *  When this bit is set, it means that the ramp time has
+		 *  completed ie ADPCTL.RTIM has reached its terminal value
+		 *  of 0x7FF.  This is a debug feature that allows software
+		 *  to read the ramp time after each cycle.
+		 *  This bit is valid only if OTG_Ver = 1'b1.
+		 */
+		unsigned adp_tmout_int:1;
+		/** ADP Probe Interrupt Mask (ADP_PRB_INT_MSK)
+		 *  When this bit is set, it unmasks the interrupt due to ADP_PRB_INT.
+		 *  This bit is valid only if OTG_Ver = 1'b1.
+		 */
+		unsigned adp_prb_int_msk:1;
+		/** ADP Sense Interrupt Mask (ADP_SNS_INT_MSK)
+		 *  When this bit is set, it unmasks the interrupt due to ADP_SNS_INT.
+		 *  This bit is valid only if OTG_Ver = 1'b1.
+		 */
+		unsigned adp_sns_int_msk:1;
+		/** ADP Timoeout Interrupt Mask (ADP_TMOUT_MSK)
+		 *  When this bit is set, it unmasks the interrupt due to ADP_TMOUT_INT.
+		 *  This bit is valid only if OTG_Ver = 1'b1.
+		 */
+		unsigned adp_tmout_int_msk:1;
+		/** Access Request
+		 * 2'b00 - Read/Write Valid (updated by the core)
+		 * 2'b01 - Read
+		 * 2'b00 - Write
+		 * 2'b00 - Reserved
+		 */
+		unsigned ar:2;
+		 /** Reserved */
+		unsigned reserved29_31:3;
+	} b;
+} adpctl_data_t;
+
+////////////////////////////////////////////
+// Device Registers
+/**
+ * Device Global Registers. <i>Offsets 800h-BFFh</i>
+ *
+ * The following structures define the size and relative field offsets
+ * for the Device Mode Registers.
+ *
+ * <i>These registers are visible only in Device mode and must not be
+ * accessed in Host mode, as the results are unknown.</i>
+ */
+typedef struct dwc_otg_dev_global_regs {
+	/** Device Configuration Register. <i>Offset 800h</i> */
+	volatile uint32_t dcfg;
+	/** Device Control Register. <i>Offset: 804h</i> */
+	volatile uint32_t dctl;
+	/** Device Status Register (Read Only). <i>Offset: 808h</i> */
+	volatile uint32_t dsts;
+	/** Reserved. <i>Offset: 80Ch</i> */
+	uint32_t unused;
+	/** Device IN Endpoint Common Interrupt Mask
+	 * Register. <i>Offset: 810h</i> */
+	volatile uint32_t diepmsk;
+	/** Device OUT Endpoint Common Interrupt Mask
+	 * Register. <i>Offset: 814h</i> */
+	volatile uint32_t doepmsk;
+	/** Device All Endpoints Interrupt Register.  <i>Offset: 818h</i> */
+	volatile uint32_t daint;
+	/** Device All Endpoints Interrupt Mask Register.  <i>Offset:
+	 * 81Ch</i> */
+	volatile uint32_t daintmsk;
+	/** Device IN Token Queue Read Register-1 (Read Only).
+	 * <i>Offset: 820h</i> */
+	volatile uint32_t dtknqr1;
+	/** Device IN Token Queue Read Register-2 (Read Only).
+	 * <i>Offset: 824h</i> */
+	volatile uint32_t dtknqr2;
+	/** Device VBUS	 discharge Register.  <i>Offset: 828h</i> */
+	volatile uint32_t dvbusdis;
+	/** Device VBUS Pulse Register.	 <i>Offset: 82Ch</i> */
+	volatile uint32_t dvbuspulse;
+	/** Device IN Token Queue Read Register-3 (Read Only). /
+	 *	Device Thresholding control register (Read/Write)
+	 * <i>Offset: 830h</i> */
+	volatile uint32_t dtknqr3_dthrctl;
+	/** Device IN Token Queue Read Register-4 (Read Only). /
+	 *	Device IN EPs empty Inr. Mask Register (Read/Write)
+	 * <i>Offset: 834h</i> */
+	volatile uint32_t dtknqr4_fifoemptymsk;
+	/** Device Each Endpoint Interrupt Register (Read Only). /
+	 * <i>Offset: 838h</i> */
+	volatile uint32_t deachint;
+	/** Device Each Endpoint Interrupt mask Register (Read/Write). /
+	 * <i>Offset: 83Ch</i> */
+	volatile uint32_t deachintmsk;
+	/** Device Each In Endpoint Interrupt mask Register (Read/Write). /
+	 * <i>Offset: 840h</i> */
+	volatile uint32_t diepeachintmsk[MAX_EPS_CHANNELS];
+	/** Device Each Out Endpoint Interrupt mask Register (Read/Write). /
+	 * <i>Offset: 880h</i> */
+	volatile uint32_t doepeachintmsk[MAX_EPS_CHANNELS];
+} dwc_otg_device_global_regs_t;
+
+/**
+ * This union represents the bit fields in the Device Configuration
+ * Register.  Read the register into the <i>d32</i> member then
+ * set/clear the bits using the <i>b</i>it elements.  Write the
+ * <i>d32</i> member to the dcfg register.
+ */
+typedef union dcfg_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		/** Device Speed */
+		unsigned devspd:2;
+		/** Non Zero Length Status OUT Handshake */
+		unsigned nzstsouthshk:1;
+#define DWC_DCFG_SEND_STALL 1
+
+		unsigned ena32khzs:1;
+		/** Device Addresses */
+		unsigned devaddr:7;
+		/** Periodic Frame Interval */
+		unsigned perfrint:2;
+#define DWC_DCFG_FRAME_INTERVAL_80 0
+#define DWC_DCFG_FRAME_INTERVAL_85 1
+#define DWC_DCFG_FRAME_INTERVAL_90 2
+#define DWC_DCFG_FRAME_INTERVAL_95 3
+
+		/** Enable Device OUT NAK for bulk in DDMA mode */
+		unsigned endevoutnak:1;
+
+		unsigned reserved14_17:4;
+		/** In Endpoint Mis-match count */
+		unsigned epmscnt:5;
+		/** Enable Descriptor DMA in Device mode */
+		unsigned descdma:1;
+		unsigned perschintvl:2;
+		unsigned resvalid:6;
+	} b;
+} dcfg_data_t;
+
+/**
+ * This union represents the bit fields in the Device Control
+ * Register.  Read the register into the <i>d32</i> member then
+ * set/clear the bits using the <i>b</i>it elements.
+ */
+typedef union dctl_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		/** Remote Wakeup */
+		unsigned rmtwkupsig:1;
+		/** Soft Disconnect */
+		unsigned sftdiscon:1;
+		/** Global Non-Periodic IN NAK Status */
+		unsigned gnpinnaksts:1;
+		/** Global OUT NAK Status */
+		unsigned goutnaksts:1;
+		/** Test Control */
+		unsigned tstctl:3;
+		/** Set Global Non-Periodic IN NAK */
+		unsigned sgnpinnak:1;
+		/** Clear Global Non-Periodic IN NAK */
+		unsigned cgnpinnak:1;
+		/** Set Global OUT NAK */
+		unsigned sgoutnak:1;
+		/** Clear Global OUT NAK */
+		unsigned cgoutnak:1;
+		/** Power-On Programming Done */
+		unsigned pwronprgdone:1;
+		/** Reserved */
+		unsigned reserved:1;
+		/** Global Multi Count */
+		unsigned gmc:2;
+		/** Ignore Frame Number for ISOC EPs */
+		unsigned ifrmnum:1;
+		/** NAK on Babble */
+		unsigned nakonbble:1;
+		/** Enable Continue on BNA */
+		unsigned encontonbna:1;
+
+		unsigned reserved18_31:14;
+	} b;
+} dctl_data_t;
+
+/**
+ * This union represents the bit fields in the Device Status
+ * Register.  Read the register into the <i>d32</i> member then
+ * set/clear the bits using the <i>b</i>it elements.
+ */
+typedef union dsts_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		/** Suspend Status */
+		unsigned suspsts:1;
+		/** Enumerated Speed */
+		unsigned enumspd:2;
+#define DWC_DSTS_ENUMSPD_HS_PHY_30MHZ_OR_60MHZ 0
+#define DWC_DSTS_ENUMSPD_FS_PHY_30MHZ_OR_60MHZ 1
+#define DWC_DSTS_ENUMSPD_LS_PHY_6MHZ		   2
+#define DWC_DSTS_ENUMSPD_FS_PHY_48MHZ		   3
+		/** Erratic Error */
+		unsigned errticerr:1;
+		unsigned reserved4_7:4;
+		/** Frame or Microframe Number of the received SOF */
+		unsigned soffn:14;
+		unsigned reserved22_31:10;
+	} b;
+} dsts_data_t;
+
+/**
+ * This union represents the bit fields in the Device IN EP Interrupt
+ * Register and the Device IN EP Common Mask Register.
+ *
+ * - Read the register into the <i>d32</i> member then set/clear the
+ *	 bits using the <i>b</i>it elements.
+ */
+typedef union diepint_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		/** Transfer complete mask */
+		unsigned xfercompl:1;
+		/** Endpoint disable mask */
+		unsigned epdisabled:1;
+		/** AHB Error mask */
+		unsigned ahberr:1;
+		/** TimeOUT Handshake mask (non-ISOC EPs) */
+		unsigned timeout:1;
+		/** IN Token received with TxF Empty mask */
+		unsigned intktxfemp:1;
+		/** IN Token Received with EP mismatch mask */
+		unsigned intknepmis:1;
+		/** IN Endpoint NAK Effective mask */
+		unsigned inepnakeff:1;
+		/** Reserved */
+		unsigned emptyintr:1;
+
+		unsigned txfifoundrn:1;
+
+		/** BNA Interrupt mask */
+		unsigned bna:1;
+
+		unsigned reserved10_12:3;
+		/** BNA Interrupt mask */
+		unsigned nak:1;
+
+		unsigned reserved14_31:18;
+	} b;
+} diepint_data_t;
+
+/**
+ * This union represents the bit fields in the Device IN EP
+ * Common/Dedicated Interrupt Mask Register.
+ */
+typedef union diepint_data diepmsk_data_t;
+
+/**
+ * This union represents the bit fields in the Device OUT EP Interrupt
+ * Registerand Device OUT EP Common Interrupt Mask Register.
+ *
+ * - Read the register into the <i>d32</i> member then set/clear the
+ *	 bits using the <i>b</i>it elements.
+ */
+typedef union doepint_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		/** Transfer complete */
+		unsigned xfercompl:1;
+		/** Endpoint disable  */
+		unsigned epdisabled:1;
+		/** AHB Error */
+		unsigned ahberr:1;
+		/** Setup Phase Done (contorl EPs) */
+		unsigned setup:1;
+		/** OUT Token Received when Endpoint Disabled */
+		unsigned outtknepdis:1;
+
+		unsigned stsphsercvd:1;
+		/** Back-to-Back SETUP Packets Received */
+		unsigned back2backsetup:1;
+
+		unsigned reserved7:1;
+		/** OUT packet Error */
+		unsigned outpkterr:1;
+		/** BNA Interrupt */
+		unsigned bna:1;
+
+		unsigned reserved10:1;
+		/** Packet Drop Status */
+		unsigned pktdrpsts:1;
+		/** Babble Interrupt */
+		unsigned babble:1;
+		/** NAK Interrupt */
+		unsigned nak:1;
+		/** NYET Interrupt */
+		unsigned nyet:1;
+		/** Bit indicating setup packet received */
+		unsigned sr:1;
+
+		unsigned reserved16_31:16;
+	} b;
+} doepint_data_t;
+
+/**
+ * This union represents the bit fields in the Device OUT EP
+ * Common/Dedicated Interrupt Mask Register.
+ */
+typedef union doepint_data doepmsk_data_t;
+
+/**
+ * This union represents the bit fields in the Device All EP Interrupt
+ * and Mask Registers.
+ * - Read the register into the <i>d32</i> member then set/clear the
+ *	 bits using the <i>b</i>it elements.
+ */
+typedef union daint_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		/** IN Endpoint bits */
+		unsigned in:16;
+		/** OUT Endpoint bits */
+		unsigned out:16;
+	} ep;
+	struct {
+		/** IN Endpoint bits */
+		unsigned inep0:1;
+		unsigned inep1:1;
+		unsigned inep2:1;
+		unsigned inep3:1;
+		unsigned inep4:1;
+		unsigned inep5:1;
+		unsigned inep6:1;
+		unsigned inep7:1;
+		unsigned inep8:1;
+		unsigned inep9:1;
+		unsigned inep10:1;
+		unsigned inep11:1;
+		unsigned inep12:1;
+		unsigned inep13:1;
+		unsigned inep14:1;
+		unsigned inep15:1;
+		/** OUT Endpoint bits */
+		unsigned outep0:1;
+		unsigned outep1:1;
+		unsigned outep2:1;
+		unsigned outep3:1;
+		unsigned outep4:1;
+		unsigned outep5:1;
+		unsigned outep6:1;
+		unsigned outep7:1;
+		unsigned outep8:1;
+		unsigned outep9:1;
+		unsigned outep10:1;
+		unsigned outep11:1;
+		unsigned outep12:1;
+		unsigned outep13:1;
+		unsigned outep14:1;
+		unsigned outep15:1;
+	} b;
+} daint_data_t;
+
+/**
+ * This union represents the bit fields in the Device IN Token Queue
+ * Read Registers.
+ * - Read the register into the <i>d32</i> member.
+ * - READ-ONLY Register
+ */
+typedef union dtknq1_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		/** In Token Queue Write Pointer */
+		unsigned intknwptr:5;
+		/** Reserved */
+		unsigned reserved05_06:2;
+		/** write pointer has wrapped. */
+		unsigned wrap_bit:1;
+		/** EP Numbers of IN Tokens 0 ... 4 */
+		unsigned epnums0_5:24;
+	} b;
+} dtknq1_data_t;
+
+/**
+ * This union represents Threshold control Register
+ * - Read and write the register into the <i>d32</i> member.
+ * - READ-WRITABLE Register
+ */
+typedef union dthrctl_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		/** non ISO Tx Thr. Enable */
+		unsigned non_iso_thr_en:1;
+		/** ISO Tx Thr. Enable */
+		unsigned iso_thr_en:1;
+		/** Tx Thr. Length */
+		unsigned tx_thr_len:9;
+		/** AHB Threshold ratio */
+		unsigned ahb_thr_ratio:2;
+		/** Reserved */
+		unsigned reserved13_15:3;
+		/** Rx Thr. Enable */
+		unsigned rx_thr_en:1;
+		/** Rx Thr. Length */
+		unsigned rx_thr_len:9;
+		unsigned reserved26:1;
+		/** Arbiter Parking Enable*/
+		unsigned arbprken:1;
+		/** Reserved */
+		unsigned reserved28_31:4;
+	} b;
+} dthrctl_data_t;
+
+/**
+ * Device Logical IN Endpoint-Specific Registers. <i>Offsets
+ * 900h-AFCh</i>
+ *
+ * There will be one set of endpoint registers per logical endpoint
+ * implemented.
+ *
+ * <i>These registers are visible only in Device mode and must not be
+ * accessed in Host mode, as the results are unknown.</i>
+ */
+typedef struct dwc_otg_dev_in_ep_regs {
+	/** Device IN Endpoint Control Register. <i>Offset:900h +
+	 * (ep_num * 20h) + 00h</i> */
+	volatile uint32_t diepctl;
+	/** Reserved. <i>Offset:900h + (ep_num * 20h) + 04h</i> */
+	uint32_t reserved04;
+	/** Device IN Endpoint Interrupt Register. <i>Offset:900h +
+	 * (ep_num * 20h) + 08h</i> */
+	volatile uint32_t diepint;
+	/** Reserved. <i>Offset:900h + (ep_num * 20h) + 0Ch</i> */
+	uint32_t reserved0C;
+	/** Device IN Endpoint Transfer Size
+	 * Register. <i>Offset:900h + (ep_num * 20h) + 10h</i> */
+	volatile uint32_t dieptsiz;
+	/** Device IN Endpoint DMA Address Register. <i>Offset:900h +
+	 * (ep_num * 20h) + 14h</i> */
+	volatile uint32_t diepdma;
+	/** Device IN Endpoint Transmit FIFO Status Register. <i>Offset:900h +
+	 * (ep_num * 20h) + 18h</i> */
+	volatile uint32_t dtxfsts;
+	/** Device IN Endpoint DMA Buffer Register. <i>Offset:900h +
+	 * (ep_num * 20h) + 1Ch</i> */
+	volatile uint32_t diepdmab;
+} dwc_otg_dev_in_ep_regs_t;
+
+/**
+ * Device Logical OUT Endpoint-Specific Registers. <i>Offsets:
+ * B00h-CFCh</i>
+ *
+ * There will be one set of endpoint registers per logical endpoint
+ * implemented.
+ *
+ * <i>These registers are visible only in Device mode and must not be
+ * accessed in Host mode, as the results are unknown.</i>
+ */
+typedef struct dwc_otg_dev_out_ep_regs {
+	/** Device OUT Endpoint Control Register. <i>Offset:B00h +
+	 * (ep_num * 20h) + 00h</i> */
+	volatile uint32_t doepctl;
+	/** Reserved. <i>Offset:B00h + (ep_num * 20h) + 04h</i> */
+	uint32_t reserved04;
+	/** Device OUT Endpoint Interrupt Register. <i>Offset:B00h +
+	 * (ep_num * 20h) + 08h</i> */
+	volatile uint32_t doepint;
+	/** Reserved. <i>Offset:B00h + (ep_num * 20h) + 0Ch</i> */
+	uint32_t reserved0C;
+	/** Device OUT Endpoint Transfer Size Register. <i>Offset:
+	 * B00h + (ep_num * 20h) + 10h</i> */
+	volatile uint32_t doeptsiz;
+	/** Device OUT Endpoint DMA Address Register. <i>Offset:B00h
+	 * + (ep_num * 20h) + 14h</i> */
+	volatile uint32_t doepdma;
+	/** Reserved. <i>Offset:B00h + 	 * (ep_num * 20h) + 18h</i> */
+	uint32_t unused;
+	/** Device OUT Endpoint DMA Buffer Register. <i>Offset:B00h
+	 * + (ep_num * 20h) + 1Ch</i> */
+	uint32_t doepdmab;
+} dwc_otg_dev_out_ep_regs_t;
+
+/**
+ * This union represents the bit fields in the Device EP Control
+ * Register.  Read the register into the <i>d32</i> member then
+ * set/clear the bits using the <i>b</i>it elements.
+ */
+typedef union depctl_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		/** Maximum Packet Size
+		 * IN/OUT EPn
+		 * IN/OUT EP0 - 2 bits
+		 *	 2'b00: 64 Bytes
+		 *	 2'b01: 32
+		 *	 2'b10: 16
+		 *	 2'b11: 8 */
+		unsigned mps:11;
+#define DWC_DEP0CTL_MPS_64	 0
+#define DWC_DEP0CTL_MPS_32	 1
+#define DWC_DEP0CTL_MPS_16	 2
+#define DWC_DEP0CTL_MPS_8	 3
+
+		/** Next Endpoint
+		 * IN EPn/IN EP0
+		 * OUT EPn/OUT EP0 - reserved */
+		unsigned nextep:4;
+
+		/** USB Active Endpoint */
+		unsigned usbactep:1;
+
+		/** Endpoint DPID (INTR/Bulk IN and OUT endpoints)
+		 * This field contains the PID of the packet going to
+		 * be received or transmitted on this endpoint. The
+		 * application should program the PID of the first
+		 * packet going to be received or transmitted on this
+		 * endpoint , after the endpoint is
+		 * activated. Application use the SetD1PID and
+		 * SetD0PID fields of this register to program either
+		 * D0 or D1 PID.
+		 *
+		 * The encoding for this field is
+		 *	 - 0: D0
+		 *	 - 1: D1
+		 */
+		unsigned dpid:1;
+
+		/** NAK Status */
+		unsigned naksts:1;
+
+		/** Endpoint Type
+		 *	2'b00: Control
+		 *	2'b01: Isochronous
+		 *	2'b10: Bulk
+		 *	2'b11: Interrupt */
+		unsigned eptype:2;
+
+		/** Snoop Mode
+		 * OUT EPn/OUT EP0
+		 * IN EPn/IN EP0 - reserved */
+		unsigned snp:1;
+
+		/** Stall Handshake */
+		unsigned stall:1;
+
+		/** Tx Fifo Number
+		 * IN EPn/IN EP0
+		 * OUT EPn/OUT EP0 - reserved */
+		unsigned txfnum:4;
+
+		/** Clear NAK */
+		unsigned cnak:1;
+		/** Set NAK */
+		unsigned snak:1;
+		/** Set DATA0 PID (INTR/Bulk IN and OUT endpoints)
+		 * Writing to this field sets the Endpoint DPID (DPID)
+		 * field in this register to DATA0. Set Even
+		 * (micro)frame (SetEvenFr) (ISO IN and OUT Endpoints)
+		 * Writing to this field sets the Even/Odd
+		 * (micro)frame (EO_FrNum) field to even (micro)
+		 * frame.
+		 */
+		unsigned setd0pid:1;
+		/** Set DATA1 PID (INTR/Bulk IN and OUT endpoints)
+		 * Writing to this field sets the Endpoint DPID (DPID)
+		 * field in this register to DATA1 Set Odd
+		 * (micro)frame (SetOddFr) (ISO IN and OUT Endpoints)
+		 * Writing to this field sets the Even/Odd
+		 * (micro)frame (EO_FrNum) field to odd (micro) frame.
+		 */
+		unsigned setd1pid:1;
+
+		/** Endpoint Disable */
+		unsigned epdis:1;
+		/** Endpoint Enable */
+		unsigned epena:1;
+	} b;
+} depctl_data_t;
+
+/**
+ * This union represents the bit fields in the Device EP Transfer
+ * Size Register.  Read the register into the <i>d32</i> member then
+ * set/clear the bits using the <i>b</i>it elements.
+ */
+typedef union deptsiz_data {
+		/** raw register data */
+	uint32_t d32;
+		/** register bits */
+	struct {
+		/** Transfer size */
+		unsigned xfersize:19;
+/** Max packet count for EP (pow(2,10)-1) */
+#define MAX_PKT_CNT 1023
+		/** Packet Count */
+		unsigned pktcnt:10;
+		/** Multi Count - Periodic IN endpoints */
+		unsigned mc:2;
+		unsigned reserved:1;
+	} b;
+} deptsiz_data_t;
+
+/**
+ * This union represents the bit fields in the Device EP 0 Transfer
+ * Size Register.  Read the register into the <i>d32</i> member then
+ * set/clear the bits using the <i>b</i>it elements.
+ */
+typedef union deptsiz0_data {
+		/** raw register data */
+	uint32_t d32;
+		/** register bits */
+	struct {
+		/** Transfer size */
+		unsigned xfersize:7;
+				/** Reserved */
+		unsigned reserved7_18:12;
+		/** Packet Count */
+		unsigned pktcnt:2;
+				/** Reserved */
+		unsigned reserved21_28:8;
+				/**Setup Packet Count (DOEPTSIZ0 Only) */
+		unsigned supcnt:2;
+		unsigned reserved31;
+	} b;
+} deptsiz0_data_t;
+
+/////////////////////////////////////////////////
+// DMA Descriptor Specific Structures
+//
+
+/** Buffer status definitions */
+
+#define BS_HOST_READY	0x0
+#define BS_DMA_BUSY		0x1
+#define BS_DMA_DONE		0x2
+#define BS_HOST_BUSY	0x3
+
+/** Receive/Transmit status definitions */
+
+#define RTS_SUCCESS		0x0
+#define RTS_BUFFLUSH	0x1
+#define RTS_RESERVED	0x2
+#define RTS_BUFERR		0x3
+
+/**
+ * This union represents the bit fields in the DMA Descriptor
+ * status quadlet. Read the quadlet into the <i>d32</i> member then
+ * set/clear the bits using the <i>b</i>it, <i>b_iso_out</i> and
+ * <i>b_iso_in</i> elements.
+ */
+typedef union dev_dma_desc_sts {
+		/** raw register data */
+	uint32_t d32;
+		/** quadlet bits */
+	struct {
+		/** Received number of bytes */
+		unsigned bytes:16;
+		/** NAK bit - only for OUT EPs */
+		unsigned nak:1;
+		unsigned reserved17_22:6;
+		/** Multiple Transfer - only for OUT EPs */
+		unsigned mtrf:1;
+		/** Setup Packet received - only for OUT EPs */
+		unsigned sr:1;
+		/** Interrupt On Complete */
+		unsigned ioc:1;
+		/** Short Packet */
+		unsigned sp:1;
+		/** Last */
+		unsigned l:1;
+		/** Receive Status */
+		unsigned sts:2;
+		/** Buffer Status */
+		unsigned bs:2;
+	} b;
+
+//#ifdef DWC_EN_ISOC
+		/** iso out quadlet bits */
+	struct {
+		/** Received number of bytes */
+		unsigned rxbytes:11;
+
+		unsigned reserved11:1;
+		/** Frame Number */
+		unsigned framenum:11;
+		/** Received ISO Data PID */
+		unsigned pid:2;
+		/** Interrupt On Complete */
+		unsigned ioc:1;
+		/** Short Packet */
+		unsigned sp:1;
+		/** Last */
+		unsigned l:1;
+		/** Receive Status */
+		unsigned rxsts:2;
+		/** Buffer Status */
+		unsigned bs:2;
+	} b_iso_out;
+
+		/** iso in quadlet bits */
+	struct {
+		/** Transmited number of bytes */
+		unsigned txbytes:12;
+		/** Frame Number */
+		unsigned framenum:11;
+		/** Transmited ISO Data PID */
+		unsigned pid:2;
+		/** Interrupt On Complete */
+		unsigned ioc:1;
+		/** Short Packet */
+		unsigned sp:1;
+		/** Last */
+		unsigned l:1;
+		/** Transmit Status */
+		unsigned txsts:2;
+		/** Buffer Status */
+		unsigned bs:2;
+	} b_iso_in;
+//#endif                                /* DWC_EN_ISOC */
+} dev_dma_desc_sts_t;
+
+/**
+ * DMA Descriptor structure
+ *
+ * DMA Descriptor structure contains two quadlets:
+ * Status quadlet and Data buffer pointer.
+ */
+typedef struct dwc_otg_dev_dma_desc {
+	/** DMA Descriptor status quadlet */
+	dev_dma_desc_sts_t status;
+	/** DMA Descriptor data buffer pointer */
+	uint32_t buf;
+} dwc_otg_dev_dma_desc_t;
+
+/**
+ * The dwc_otg_dev_if structure contains information needed to manage
+ * the DWC_otg controller acting in device mode. It represents the
+ * programming view of the device-specific aspects of the controller.
+ */
+typedef struct dwc_otg_dev_if {
+	/** Pointer to device Global registers.
+	 * Device Global Registers starting at offset 800h
+	 */
+	dwc_otg_device_global_regs_t *dev_global_regs;
+#define DWC_DEV_GLOBAL_REG_OFFSET 0x800
+
+	/**
+	 * Device Logical IN Endpoint-Specific Registers 900h-AFCh
+	 */
+	dwc_otg_dev_in_ep_regs_t *in_ep_regs[MAX_EPS_CHANNELS];
+#define DWC_DEV_IN_EP_REG_OFFSET 0x900
+#define DWC_EP_REG_OFFSET 0x20
+
+	/** Device Logical OUT Endpoint-Specific Registers B00h-CFCh */
+	dwc_otg_dev_out_ep_regs_t *out_ep_regs[MAX_EPS_CHANNELS];
+#define DWC_DEV_OUT_EP_REG_OFFSET 0xB00
+
+	/* Device configuration information */
+	uint8_t speed;				 /**< Device Speed	0: Unknown, 1: LS, 2:FS, 3: HS */
+	uint8_t num_in_eps;		 /**< Number # of Tx EP range: 0-15 exept ep0 */
+	uint8_t num_out_eps;		 /**< Number # of Rx EP range: 0-15 exept ep 0*/
+
+	/** Size of periodic FIFOs (Bytes) */
+	uint16_t perio_tx_fifo_size[MAX_PERIO_FIFOS];
+
+	/** Size of Tx FIFOs (Bytes) */
+	uint16_t tx_fifo_size[MAX_TX_FIFOS];
+
+	/** Thresholding enable flags and length varaiables **/
+	uint16_t rx_thr_en;
+	uint16_t iso_tx_thr_en;
+	uint16_t non_iso_tx_thr_en;
+
+	uint16_t rx_thr_length;
+	uint16_t tx_thr_length;
+
+	/**
+	 * Pointers to the DMA Descriptors for EP0 Control
+	 * transfers (virtual and physical)
+	 */
+
+	/** 2 descriptors for SETUP packets */
+	dwc_dma_t dma_setup_desc_addr[2];
+	dwc_otg_dev_dma_desc_t *setup_desc_addr[2];
+
+	/** Pointer to Descriptor with latest SETUP packet */
+	dwc_otg_dev_dma_desc_t *psetup;
+
+	/** Index of current SETUP handler descriptor */
+	uint32_t setup_desc_index;
+
+	/** Descriptor for Data In or Status In phases */
+	dwc_dma_t dma_in_desc_addr;
+	dwc_otg_dev_dma_desc_t *in_desc_addr;
+
+	/** Descriptor for Data Out or Status Out phases */
+	dwc_dma_t dma_out_desc_addr;
+	dwc_otg_dev_dma_desc_t *out_desc_addr;
+
+	/** Setup Packet Detected - if set clear NAK when queueing */
+	uint32_t spd;
+	/** Isoc ep pointer on which incomplete happens */
+	void *isoc_ep;
+
+} dwc_otg_dev_if_t;
+
+/////////////////////////////////////////////////
+// Host Mode Register Structures
+//
+/**
+ * The Host Global Registers structure defines the size and relative
+ * field offsets for the Host Mode Global Registers.  Host Global
+ * Registers offsets 400h-7FFh.
+*/
+typedef struct dwc_otg_host_global_regs {
+	/** Host Configuration Register.   <i>Offset: 400h</i> */
+	volatile uint32_t hcfg;
+	/** Host Frame Interval Register.	<i>Offset: 404h</i> */
+	volatile uint32_t hfir;
+	/** Host Frame Number / Frame Remaining Register. <i>Offset: 408h</i> */
+	volatile uint32_t hfnum;
+	/** Reserved.	<i>Offset: 40Ch</i> */
+	uint32_t reserved40C;
+	/** Host Periodic Transmit FIFO/ Queue Status Register. <i>Offset: 410h</i> */
+	volatile uint32_t hptxsts;
+	/** Host All Channels Interrupt Register. <i>Offset: 414h</i> */
+	volatile uint32_t haint;
+	/** Host All Channels Interrupt Mask Register. <i>Offset: 418h</i> */
+	volatile uint32_t haintmsk;
+	/** Host Frame List Base Address Register . <i>Offset: 41Ch</i> */
+	volatile uint32_t hflbaddr;
+} dwc_otg_host_global_regs_t;
+
+/**
+ * This union represents the bit fields in the Host Configuration Register.
+ * Read the register into the <i>d32</i> member then set/clear the bits using
+ * the <i>b</i>it elements. Write the <i>d32</i> member to the hcfg register.
+ */
+typedef union hcfg_data {
+	/** raw register data */
+	uint32_t d32;
+
+	/** register bits */
+	struct {
+		/** FS/LS Phy Clock Select */
+		unsigned fslspclksel:2;
+#define DWC_HCFG_30_60_MHZ 0
+#define DWC_HCFG_48_MHZ	   1
+#define DWC_HCFG_6_MHZ	   2
+
+		/** FS/LS Only Support */
+		unsigned fslssupp:1;
+		unsigned reserved3_6:4;
+		/** Enable 32-KHz Suspend Mode */
+		unsigned ena32khzs:1;
+		/** Resume Validation Periiod */
+		unsigned resvalid:8;
+		unsigned reserved16_22:7;
+		/** Enable Scatter/gather DMA in Host mode */
+		unsigned descdma:1;
+		/** Frame List Entries */
+		unsigned frlisten:2;
+		/** Enable Periodic Scheduling */
+		unsigned perschedena:1;
+		unsigned reserved27_30:4;
+		unsigned modechtimen:1;
+	} b;
+} hcfg_data_t;
+
+/**
+ * This union represents the bit fields in the Host Frame Remaing/Number
+ * Register.
+ */
+typedef union hfir_data {
+	/** raw register data */
+	uint32_t d32;
+
+	/** register bits */
+	struct {
+		unsigned frint:16;
+		unsigned hfirrldctrl:1;
+		unsigned reserved:15;
+	} b;
+} hfir_data_t;
+
+/**
+ * This union represents the bit fields in the Host Frame Remaing/Number
+ * Register.
+ */
+typedef union hfnum_data {
+	/** raw register data */
+	uint32_t d32;
+
+	/** register bits */
+	struct {
+		unsigned frnum:16;
+#define DWC_HFNUM_MAX_FRNUM 0x3FFF
+		unsigned frrem:16;
+	} b;
+} hfnum_data_t;
+
+typedef union hptxsts_data {
+	/** raw register data */
+	uint32_t d32;
+
+	/** register bits */
+	struct {
+		unsigned ptxfspcavail:16;
+		unsigned ptxqspcavail:8;
+		/** Top of the Periodic Transmit Request Queue
+		 *	- bit 24 - Terminate (last entry for the selected channel)
+		 *	- bits 26:25 - Token Type
+		 *	  - 2'b00 - Zero length
+		 *	  - 2'b01 - Ping
+		 *	  - 2'b10 - Disable
+		 *	- bits 30:27 - Channel Number
+		 *	- bit 31 - Odd/even microframe
+		 */
+		unsigned ptxqtop_terminate:1;
+		unsigned ptxqtop_token:2;
+		unsigned ptxqtop_chnum:4;
+		unsigned ptxqtop_odd:1;
+	} b;
+} hptxsts_data_t;
+
+/**
+ * This union represents the bit fields in the Host Port Control and Status
+ * Register. Read the register into the <i>d32</i> member then set/clear the
+ * bits using the <i>b</i>it elements. Write the <i>d32</i> member to the
+ * hprt0 register.
+ */
+typedef union hprt0_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		unsigned prtconnsts:1;
+		unsigned prtconndet:1;
+		unsigned prtena:1;
+		unsigned prtenchng:1;
+		unsigned prtovrcurract:1;
+		unsigned prtovrcurrchng:1;
+		unsigned prtres:1;
+		unsigned prtsusp:1;
+		unsigned prtrst:1;
+		unsigned reserved9:1;
+		unsigned prtlnsts:2;
+		unsigned prtpwr:1;
+		unsigned prttstctl:4;
+		unsigned prtspd:2;
+#define DWC_HPRT0_PRTSPD_HIGH_SPEED 0
+#define DWC_HPRT0_PRTSPD_FULL_SPEED 1
+#define DWC_HPRT0_PRTSPD_LOW_SPEED	2
+		unsigned reserved19_31:13;
+	} b;
+} hprt0_data_t;
+
+/**
+ * This union represents the bit fields in the Host All Interrupt
+ * Register.
+ */
+typedef union haint_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		unsigned ch0:1;
+		unsigned ch1:1;
+		unsigned ch2:1;
+		unsigned ch3:1;
+		unsigned ch4:1;
+		unsigned ch5:1;
+		unsigned ch6:1;
+		unsigned ch7:1;
+		unsigned ch8:1;
+		unsigned ch9:1;
+		unsigned ch10:1;
+		unsigned ch11:1;
+		unsigned ch12:1;
+		unsigned ch13:1;
+		unsigned ch14:1;
+		unsigned ch15:1;
+		unsigned reserved:16;
+	} b;
+
+	struct {
+		unsigned chint:16;
+		unsigned reserved:16;
+	} b2;
+} haint_data_t;
+
+/**
+ * This union represents the bit fields in the Host All Interrupt
+ * Register.
+ */
+typedef union haintmsk_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		unsigned ch0:1;
+		unsigned ch1:1;
+		unsigned ch2:1;
+		unsigned ch3:1;
+		unsigned ch4:1;
+		unsigned ch5:1;
+		unsigned ch6:1;
+		unsigned ch7:1;
+		unsigned ch8:1;
+		unsigned ch9:1;
+		unsigned ch10:1;
+		unsigned ch11:1;
+		unsigned ch12:1;
+		unsigned ch13:1;
+		unsigned ch14:1;
+		unsigned ch15:1;
+		unsigned reserved:16;
+	} b;
+
+	struct {
+		unsigned chint:16;
+		unsigned reserved:16;
+	} b2;
+} haintmsk_data_t;
+
+/**
+ * Host Channel Specific Registers. <i>500h-5FCh</i>
+ */
+typedef struct dwc_otg_hc_regs {
+	/** Host Channel 0 Characteristic Register. <i>Offset: 500h + (chan_num * 20h) + 00h</i> */
+	volatile uint32_t hcchar;
+	/** Host Channel 0 Split Control Register. <i>Offset: 500h + (chan_num * 20h) + 04h</i> */
+	volatile uint32_t hcsplt;
+	/** Host Channel 0 Interrupt Register. <i>Offset: 500h + (chan_num * 20h) + 08h</i> */
+	volatile uint32_t hcint;
+	/** Host Channel 0 Interrupt Mask Register. <i>Offset: 500h + (chan_num * 20h) + 0Ch</i> */
+	volatile uint32_t hcintmsk;
+	/** Host Channel 0 Transfer Size Register. <i>Offset: 500h + (chan_num * 20h) + 10h</i> */
+	volatile uint32_t hctsiz;
+	/** Host Channel 0 DMA Address Register. <i>Offset: 500h + (chan_num * 20h) + 14h</i> */
+	volatile uint32_t hcdma;
+	volatile uint32_t reserved;
+	/** Host Channel 0 DMA Buffer Address Register. <i>Offset: 500h + (chan_num * 20h) + 1Ch</i> */
+	volatile uint32_t hcdmab;
+} dwc_otg_hc_regs_t;
+
+/**
+ * This union represents the bit fields in the Host Channel Characteristics
+ * Register. Read the register into the <i>d32</i> member then set/clear the
+ * bits using the <i>b</i>it elements. Write the <i>d32</i> member to the
+ * hcchar register.
+ */
+typedef union hcchar_data {
+	/** raw register data */
+	uint32_t d32;
+
+	/** register bits */
+	struct {
+		/** Maximum packet size in bytes */
+		unsigned mps:11;
+
+		/** Endpoint number */
+		unsigned epnum:4;
+
+		/** 0: OUT, 1: IN */
+		unsigned epdir:1;
+
+		unsigned reserved:1;
+
+		/** 0: Full/high speed device, 1: Low speed device */
+		unsigned lspddev:1;
+
+		/** 0: Control, 1: Isoc, 2: Bulk, 3: Intr */
+		unsigned eptype:2;
+
+		/** Packets per frame for periodic transfers. 0 is reserved. */
+		unsigned multicnt:2;
+
+		/** Device address */
+		unsigned devaddr:7;
+
+		/**
+		 * Frame to transmit periodic transaction.
+		 * 0: even, 1: odd
+		 */
+		unsigned oddfrm:1;
+
+		/** Channel disable */
+		unsigned chdis:1;
+
+		/** Channel enable */
+		unsigned chen:1;
+	} b;
+} hcchar_data_t;
+
+typedef union hcsplt_data {
+	/** raw register data */
+	uint32_t d32;
+
+	/** register bits */
+	struct {
+		/** Port Address */
+		unsigned prtaddr:7;
+
+		/** Hub Address */
+		unsigned hubaddr:7;
+
+		/** Transaction Position */
+		unsigned xactpos:2;
+#define DWC_HCSPLIT_XACTPOS_MID 0
+#define DWC_HCSPLIT_XACTPOS_END 1
+#define DWC_HCSPLIT_XACTPOS_BEGIN 2
+#define DWC_HCSPLIT_XACTPOS_ALL 3
+
+		/** Do Complete Split */
+		unsigned compsplt:1;
+
+		/** Reserved */
+		unsigned reserved:14;
+
+		/** Split Enble */
+		unsigned spltena:1;
+	} b;
+} hcsplt_data_t;
+
+/**
+ * This union represents the bit fields in the Host All Interrupt
+ * Register.
+ */
+typedef union hcint_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		/** Transfer Complete */
+		unsigned xfercomp:1;
+		/** Channel Halted */
+		unsigned chhltd:1;
+		/** AHB Error */
+		unsigned ahberr:1;
+		/** STALL Response Received */
+		unsigned stall:1;
+		/** NAK Response Received */
+		unsigned nak:1;
+		/** ACK Response Received */
+		unsigned ack:1;
+		/** NYET Response Received */
+		unsigned nyet:1;
+		/** Transaction Err */
+		unsigned xacterr:1;
+		/** Babble Error */
+		unsigned bblerr:1;
+		/** Frame Overrun */
+		unsigned frmovrun:1;
+		/** Data Toggle Error */
+		unsigned datatglerr:1;
+		/** Buffer Not Available (only for DDMA mode) */
+		unsigned bna:1;
+		/** Exessive transaction error (only for DDMA mode) */
+		unsigned xcs_xact:1;
+		/** Frame List Rollover interrupt */
+		unsigned frm_list_roll:1;
+		/** Reserved */
+		unsigned reserved14_31:18;
+	} b;
+} hcint_data_t;
+
+/**
+ * This union represents the bit fields in the Host Channel Interrupt Mask
+ * Register. Read the register into the <i>d32</i> member then set/clear the
+ * bits using the <i>b</i>it elements. Write the <i>d32</i> member to the
+ * hcintmsk register.
+ */
+typedef union hcintmsk_data {
+	/** raw register data */
+	uint32_t d32;
+
+	/** register bits */
+	struct {
+		unsigned xfercompl:1;
+		unsigned chhltd:1;
+		unsigned ahberr:1;
+		unsigned stall:1;
+		unsigned nak:1;
+		unsigned ack:1;
+		unsigned nyet:1;
+		unsigned xacterr:1;
+		unsigned bblerr:1;
+		unsigned frmovrun:1;
+		unsigned datatglerr:1;
+		unsigned bna:1;
+		unsigned xcs_xact:1;
+		unsigned frm_list_roll:1;
+		unsigned reserved14_31:18;
+	} b;
+} hcintmsk_data_t;
+
+/**
+ * This union represents the bit fields in the Host Channel Transfer Size
+ * Register. Read the register into the <i>d32</i> member then set/clear the
+ * bits using the <i>b</i>it elements. Write the <i>d32</i> member to the
+ * hcchar register.
+ */
+
+typedef union hctsiz_data {
+	/** raw register data */
+	uint32_t d32;
+
+	/** register bits */
+	struct {
+		/** Total transfer size in bytes */
+		unsigned xfersize:19;
+
+		/** Data packets to transfer */
+		unsigned pktcnt:10;
+
+		/**
+		 * Packet ID for next data packet
+		 * 0: DATA0
+		 * 1: DATA2
+		 * 2: DATA1
+		 * 3: MDATA (non-Control), SETUP (Control)
+		 */
+		unsigned pid:2;
+#define DWC_HCTSIZ_DATA0 0
+#define DWC_HCTSIZ_DATA1 2
+#define DWC_HCTSIZ_DATA2 1
+#define DWC_HCTSIZ_MDATA 3
+#define DWC_HCTSIZ_SETUP 3
+
+		/** Do PING protocol when 1 */
+		unsigned dopng:1;
+	} b;
+
+	/** register bits */
+	struct {
+		/** Scheduling information */
+		unsigned schinfo:8;
+
+		/** Number of transfer descriptors.
+		 * Max value:
+		 * 64 in general,
+		 * 256 only for HS isochronous endpoint.
+		 */
+		unsigned ntd:8;
+
+		/** Data packets to transfer */
+		unsigned reserved16_28:13;
+
+		/**
+		 * Packet ID for next data packet
+		 * 0: DATA0
+		 * 1: DATA2
+		 * 2: DATA1
+		 * 3: MDATA (non-Control)
+		 */
+		unsigned pid:2;
+
+		/** Do PING protocol when 1 */
+		unsigned dopng:1;
+	} b_ddma;
+} hctsiz_data_t;
+
+/**
+ * This union represents the bit fields in the Host DMA Address
+ * Register used in Descriptor DMA mode.
+ */
+typedef union hcdma_data {
+	/** raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		unsigned reserved0_2:3;
+		/** Current Transfer Descriptor. Not used for ISOC */
+		unsigned ctd:8;
+		/** Start Address of Descriptor List */
+		unsigned dma_addr:21;
+	} b;
+} hcdma_data_t;
+
+/**
+ * This union represents the bit fields in the DMA Descriptor
+ * status quadlet for host mode. Read the quadlet into the <i>d32</i> member then
+ * set/clear the bits using the <i>b</i>it elements.
+ */
+typedef union host_dma_desc_sts {
+	/** raw register data */
+	uint32_t d32;
+	/** quadlet bits */
+
+	/* for non-isochronous  */
+	struct {
+		/** Number of bytes */
+		unsigned n_bytes:17;
+		/** QTD offset to jump when Short Packet received - only for IN EPs */
+		unsigned qtd_offset:6;
+		/**
+		 * Set to request the core to jump to alternate QTD if
+		 * Short Packet received - only for IN EPs
+		 */
+		unsigned a_qtd:1;
+		 /**
+		  * Setup Packet bit. When set indicates that buffer contains
+		  * setup packet.
+		  */
+		unsigned sup:1;
+		/** Interrupt On Complete */
+		unsigned ioc:1;
+		/** End of List */
+		unsigned eol:1;
+		unsigned reserved27:1;
+		/** Rx/Tx Status */
+		unsigned sts:2;
+#define DMA_DESC_STS_PKTERR	1
+		unsigned reserved30:1;
+		/** Active Bit */
+		unsigned a:1;
+	} b;
+	/* for isochronous */
+	struct {
+		/** Number of bytes */
+		unsigned n_bytes:12;
+		unsigned reserved12_24:13;
+		/** Interrupt On Complete */
+		unsigned ioc:1;
+		unsigned reserved26_27:2;
+		/** Rx/Tx Status */
+		unsigned sts:2;
+		unsigned reserved30:1;
+		/** Active Bit */
+		unsigned a:1;
+	} b_isoc;
+} host_dma_desc_sts_t;
+
+#define	MAX_DMA_DESC_SIZE		131071
+#define MAX_DMA_DESC_NUM_GENERIC	64
+#define MAX_DMA_DESC_NUM_HS_ISOC	256
+#define MAX_FRLIST_EN_NUM		64
+/**
+ * Host-mode DMA Descriptor structure
+ *
+ * DMA Descriptor structure contains two quadlets:
+ * Status quadlet and Data buffer pointer.
+ */
+typedef struct dwc_otg_host_dma_desc {
+	/** DMA Descriptor status quadlet */
+	host_dma_desc_sts_t status;
+	/** DMA Descriptor data buffer pointer */
+	uint32_t buf;
+} dwc_otg_host_dma_desc_t;
+
+/** OTG Host Interface Structure.
+ *
+ * The OTG Host Interface Structure structure contains information
+ * needed to manage the DWC_otg controller acting in host mode. It
+ * represents the programming view of the host-specific aspects of the
+ * controller.
+ */
+typedef struct dwc_otg_host_if {
+	/** Host Global Registers starting at offset 400h.*/
+	dwc_otg_host_global_regs_t *host_global_regs;
+#define DWC_OTG_HOST_GLOBAL_REG_OFFSET 0x400
+
+	/** Host Port 0 Control and Status Register */
+	volatile uint32_t *hprt0;
+#define DWC_OTG_HOST_PORT_REGS_OFFSET 0x440
+
+	/** Host Channel Specific Registers at offsets 500h-5FCh. */
+	dwc_otg_hc_regs_t *hc_regs[MAX_EPS_CHANNELS];
+#define DWC_OTG_HOST_CHAN_REGS_OFFSET 0x500
+#define DWC_OTG_CHAN_REGS_OFFSET 0x20
+
+	/* Host configuration information */
+	/** Number of Host Channels (range: 1-16) */
+	uint8_t num_host_channels;
+	/** Periodic EPs supported (0: no, 1: yes) */
+	uint8_t perio_eps_supported;
+	/** Periodic Tx FIFO Size (Only 1 host periodic Tx FIFO) */
+	uint16_t perio_tx_fifo_size;
+
+} dwc_otg_host_if_t;
+
+/**
+ * This union represents the bit fields in the Power and Clock Gating Control
+ * Register. Read the register into the <i>d32</i> member then set/clear the
+ * bits using the <i>b</i>it elements.
+ */
+typedef union pcgcctl_data {
+	/** raw register data */
+	uint32_t d32;
+
+	/** register bits */
+	struct {
+		/** Stop Pclk */
+		unsigned stoppclk:1;
+		/** Gate Hclk */
+		unsigned gatehclk:1;
+		/** Power Clamp */
+		unsigned pwrclmp:1;
+		/** Reset Power Down Modules */
+		unsigned rstpdwnmodule:1;
+		/** Reserved */
+		unsigned reserved:1;
+		/** Enable Sleep Clock Gating (Enbl_L1Gating) */
+		unsigned enbl_sleep_gating:1;
+		/** PHY In Sleep (PhySleep) */
+		unsigned phy_in_sleep:1;
+		/** Deep Sleep*/
+		unsigned deep_sleep:1;
+		unsigned resetaftsusp:1;
+		unsigned restoremode:1;
+		unsigned enbl_extnd_hiber:1;
+		unsigned extnd_hiber_pwrclmp:1;
+		unsigned extnd_hiber_switch:1;
+		unsigned ess_reg_restored:1;
+		unsigned prt_clk_sel:2;
+		unsigned port_power:1;
+		unsigned max_xcvrselect:2;
+		unsigned max_termsel:1;
+		unsigned mac_dev_addr:7;
+		unsigned p2hd_dev_enum_spd:2;
+		unsigned p2hd_prt_spd:2;
+		unsigned if_dev_mode:1;
+	} b;
+} pcgcctl_data_t;
+
+/**
+ * This union represents the bit fields in the Global Data FIFO Software
+ * Configuration Register. Read the register into the <i>d32</i> member then
+ * set/clear the bits using the <i>b</i>it elements.
+ */
+typedef union gdfifocfg_data {
+	/* raw register data */
+	uint32_t d32;
+	/** register bits */
+	struct {
+		/** OTG Data FIFO depth */
+		unsigned gdfifocfg:16;
+		/** Start address of EP info controller */
+		unsigned epinfobase:16;
+	} b;
+} gdfifocfg_data_t;
+
+/**
+ * This union represents the bit fields in the Global Power Down Register
+ * Register. Read the register into the <i>d32</i> member then set/clear the
+ * bits using the <i>b</i>it elements.
+ */
+typedef union gpwrdn_data {
+	/* raw register data */
+	uint32_t d32;
+
+	/** register bits */
+	struct {
+		/** PMU Interrupt Select */
+		unsigned pmuintsel:1;
+		/** PMU Active */
+		unsigned pmuactv:1;
+		/** Restore */
+		unsigned restore:1;
+		/** Power Down Clamp */
+		unsigned pwrdnclmp:1;
+		/** Power Down Reset */
+		unsigned pwrdnrstn:1;
+		/** Power Down Switch */
+		unsigned pwrdnswtch:1;
+		/** Disable VBUS */
+		unsigned dis_vbus:1;
+		/** Line State Change */
+		unsigned lnstschng:1;
+		/** Line state change mask */
+		unsigned lnstchng_msk:1;
+		/** Reset Detected */
+		unsigned rst_det:1;
+		/** Reset Detect mask */
+		unsigned rst_det_msk:1;
+		/** Disconnect Detected */
+		unsigned disconn_det:1;
+		/** Disconnect Detect mask */
+		unsigned disconn_det_msk:1;
+		/** Connect Detected*/
+		unsigned connect_det:1;
+		/** Connect Detected Mask*/
+		unsigned connect_det_msk:1;
+		/** SRP Detected */
+		unsigned srp_det:1;
+		/** SRP Detect mask */
+		unsigned srp_det_msk:1;
+		/** Status Change Interrupt */
+		unsigned sts_chngint:1;
+		/** Status Change Interrupt Mask */
+		unsigned sts_chngint_msk:1;
+		/** Line State */
+		unsigned linestate:2;
+		/** Indicates current mode(status of IDDIG signal) */
+		unsigned idsts:1;
+		/** B Session Valid signal status*/
+		unsigned bsessvld:1;
+		/** ADP Event Detected */
+		unsigned adp_int:1;
+		/** Multi Valued ID pin */
+		unsigned mult_val_id_bc:5;
+		/** Reserved 24_31 */
+		unsigned reserved29_31:3;
+	} b;
+} gpwrdn_data_t;
+
+#endif
diff --git a/drivers/usb/host/dwc_otg/test/Makefile b/drivers/usb/host/dwc_otg/test/Makefile
new file mode 100644
index 000000000000..fc453759dea3
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/test/Makefile
@@ -0,0 +1,16 @@
+
+PERL=/usr/bin/perl
+PL_TESTS=test_sysfs.pl test_mod_param.pl
+
+.PHONY : test
+test : perl_tests
+
+perl_tests :
+	@echo
+	@echo Running perl tests
+	@for test in $(PL_TESTS); do \
+	  if $(PERL) ./$$test ; then \
+	    echo "=======> $$test, PASSED" ; \
+	  else echo "=======> $$test, FAILED" ; \
+	  fi \
+	done
diff --git a/drivers/usb/host/dwc_otg/test/dwc_otg_test.pm b/drivers/usb/host/dwc_otg/test/dwc_otg_test.pm
new file mode 100644
index 000000000000..85e55fd6ddbc
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/test/dwc_otg_test.pm
@@ -0,0 +1,337 @@
+package dwc_otg_test;
+
+use strict;
+use Exporter ();
+
+use vars qw(@ISA @EXPORT
+$sysfsdir $paramdir $errors $params
+);
+
+@ISA = qw(Exporter);
+
+#
+# Globals
+#
+$sysfsdir = "/sys/devices/lm0";
+$paramdir = "/sys/module/dwc_otg";
+$errors = 0;
+
+$params = [
+	   {
+	    NAME => "otg_cap",
+	    DEFAULT => 0,
+	    ENUM => [],
+	    LOW => 0,
+	    HIGH => 2
+	   },
+	   {
+	    NAME => "dma_enable",
+	    DEFAULT => 0,
+	    ENUM => [],
+	    LOW => 0,
+	    HIGH => 1
+	   },
+	   {
+	    NAME => "dma_burst_size",
+	    DEFAULT => 32,
+	    ENUM => [1, 4, 8, 16, 32, 64, 128, 256],
+	    LOW => 1,
+	    HIGH => 256
+	   },
+	   {
+	    NAME => "host_speed",
+	    DEFAULT => 0,
+	    ENUM => [],
+	    LOW => 0,
+	    HIGH => 1
+	   },
+	   {
+	    NAME => "host_support_fs_ls_low_power",
+	    DEFAULT => 0,
+	    ENUM => [],
+	    LOW => 0,
+	    HIGH => 1
+	   },
+	   {
+	    NAME => "host_ls_low_power_phy_clk",
+	    DEFAULT => 0,
+	    ENUM => [],
+	    LOW => 0,
+	    HIGH => 1
+	   },
+	   {
+	    NAME => "dev_speed",
+	    DEFAULT => 0,
+	    ENUM => [],
+	    LOW => 0,
+	    HIGH => 1
+	   },
+	   {
+	    NAME => "enable_dynamic_fifo",
+	    DEFAULT => 1,
+	    ENUM => [],
+	    LOW => 0,
+	    HIGH => 1
+	   },
+	   {
+	    NAME => "data_fifo_size",
+	    DEFAULT => 8192,
+	    ENUM => [],
+	    LOW => 32,
+	    HIGH => 32768
+	   },
+	   {
+	    NAME => "dev_rx_fifo_size",
+	    DEFAULT => 1064,
+	    ENUM => [],
+	    LOW => 16,
+	    HIGH => 32768
+	   },
+	   {
+	    NAME => "dev_nperio_tx_fifo_size",
+	    DEFAULT => 1024,
+	    ENUM => [],
+	    LOW => 16,
+	    HIGH => 32768
+	   },
+	   {
+	    NAME => "dev_perio_tx_fifo_size_1",
+	    DEFAULT => 256,
+	    ENUM => [],
+	    LOW => 4,
+	    HIGH => 768
+	   },
+	   {
+	    NAME => "dev_perio_tx_fifo_size_2",
+	    DEFAULT => 256,
+	    ENUM => [],
+	    LOW => 4,
+	    HIGH => 768
+	   },
+	   {
+	    NAME => "dev_perio_tx_fifo_size_3",
+	    DEFAULT => 256,
+	    ENUM => [],
+	    LOW => 4,
+	    HIGH => 768
+	   },
+	   {
+	    NAME => "dev_perio_tx_fifo_size_4",
+	    DEFAULT => 256,
+	    ENUM => [],
+	    LOW => 4,
+	    HIGH => 768
+	   },
+	   {
+	    NAME => "dev_perio_tx_fifo_size_5",
+	    DEFAULT => 256,
+	    ENUM => [],
+	    LOW => 4,
+	    HIGH => 768
+	   },
+	   {
+	    NAME => "dev_perio_tx_fifo_size_6",
+	    DEFAULT => 256,
+	    ENUM => [],
+	    LOW => 4,
+	    HIGH => 768
+	   },
+	   {
+	    NAME => "dev_perio_tx_fifo_size_7",
+	    DEFAULT => 256,
+	    ENUM => [],
+	    LOW => 4,
+	    HIGH => 768
+	   },
+	   {
+	    NAME => "dev_perio_tx_fifo_size_8",
+	    DEFAULT => 256,
+	    ENUM => [],
+	    LOW => 4,
+	    HIGH => 768
+	   },
+	   {
+	    NAME => "dev_perio_tx_fifo_size_9",
+	    DEFAULT => 256,
+	    ENUM => [],
+	    LOW => 4,
+	    HIGH => 768
+	   },
+	   {
+	    NAME => "dev_perio_tx_fifo_size_10",
+	    DEFAULT => 256,
+	    ENUM => [],
+	    LOW => 4,
+	    HIGH => 768
+	   },
+	   {
+	    NAME => "dev_perio_tx_fifo_size_11",
+	    DEFAULT => 256,
+	    ENUM => [],
+	    LOW => 4,
+	    HIGH => 768
+	   },
+	   {
+	    NAME => "dev_perio_tx_fifo_size_12",
+	    DEFAULT => 256,
+	    ENUM => [],
+	    LOW => 4,
+	    HIGH => 768
+	   },
+	   {
+	    NAME => "dev_perio_tx_fifo_size_13",
+	    DEFAULT => 256,
+	    ENUM => [],
+	    LOW => 4,
+	    HIGH => 768
+	   },
+	   {
+	    NAME => "dev_perio_tx_fifo_size_14",
+	    DEFAULT => 256,
+	    ENUM => [],
+	    LOW => 4,
+	    HIGH => 768
+	   },
+	   {
+	    NAME => "dev_perio_tx_fifo_size_15",
+	    DEFAULT => 256,
+	    ENUM => [],
+	    LOW => 4,
+	    HIGH => 768
+	   },
+	   {
+	    NAME => "host_rx_fifo_size",
+	    DEFAULT => 1024,
+	    ENUM => [],
+	    LOW => 16,
+	    HIGH => 32768
+	   },
+	   {
+	    NAME => "host_nperio_tx_fifo_size",
+	    DEFAULT => 1024,
+	    ENUM => [],
+	    LOW => 16,
+	    HIGH => 32768
+	   },
+	   {
+	    NAME => "host_perio_tx_fifo_size",
+	    DEFAULT => 1024,
+	    ENUM => [],
+	    LOW => 16,
+	    HIGH => 32768
+	   },
+	   {
+	    NAME => "max_transfer_size",
+	    DEFAULT => 65535,
+	    ENUM => [],
+	    LOW => 2047,
+	    HIGH => 65535
+	   },
+	   {
+	    NAME => "max_packet_count",
+	    DEFAULT => 511,
+	    ENUM => [],
+	    LOW => 15,
+	    HIGH => 511
+	   },
+	   {
+	    NAME => "host_channels",
+	    DEFAULT => 12,
+	    ENUM => [],
+	    LOW => 1,
+	    HIGH => 16
+	   },
+	   {
+	    NAME => "dev_endpoints",
+	    DEFAULT => 6,
+	    ENUM => [],
+	    LOW => 1,
+	    HIGH => 15
+	   },
+	   {
+	    NAME => "phy_type",
+	    DEFAULT => 1,
+	    ENUM => [],
+	    LOW => 0,
+	    HIGH => 2
+	   },
+	   {
+	    NAME => "phy_utmi_width",
+	    DEFAULT => 16,
+	    ENUM => [8, 16],
+	    LOW => 8,
+	    HIGH => 16
+	   },
+	   {
+	    NAME => "phy_ulpi_ddr",
+	    DEFAULT => 0,
+	    ENUM => [],
+	    LOW => 0,
+	    HIGH => 1
+	   },
+	  ];
+
+
+#
+#
+sub check_arch {
+  $_ = `uname -m`;
+  chomp;
+  unless (m/armv4tl/) {
+    warn "# \n# Can't execute on $_.  Run on integrator platform.\n# \n";
+    return 0;
+  }
+  return 1;
+}
+
+#
+#
+sub load_module {
+  my $params = shift;
+  print "\nRemoving Module\n";
+  system "rmmod dwc_otg";
+  print "Loading Module\n";
+  if ($params ne "") {
+    print "Module Parameters: $params\n";
+  }
+  if (system("modprobe dwc_otg $params")) {
+    warn "Unable to load module\n";
+    return 0;
+  }
+  return 1;
+}
+
+#
+#
+sub test_status {
+  my $arg = shift;
+
+  print "\n";
+
+  if (defined $arg) {
+    warn "WARNING: $arg\n";
+  }
+
+  if ($errors > 0) {
+    warn "TEST FAILED with $errors errors\n";
+    return 0;
+  } else {
+    print "TEST PASSED\n";
+    return 0 if (defined $arg);
+  }
+  return 1;
+}
+
+#
+#
+@EXPORT = qw(
+$sysfsdir
+$paramdir
+$params
+$errors
+check_arch
+load_module
+test_status
+);
+
+1;
diff --git a/drivers/usb/host/dwc_otg/test/test_mod_param.pl b/drivers/usb/host/dwc_otg/test/test_mod_param.pl
new file mode 100644
index 000000000000..dc3820df577b
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/test/test_mod_param.pl
@@ -0,0 +1,133 @@
+#!/usr/bin/perl -w
+#
+# Run this program on the integrator.
+#
+# - Tests module parameter default values.
+# - Tests setting of valid module parameter values via modprobe.
+# - Tests invalid module parameter values.
+# -----------------------------------------------------------------------------
+use strict;
+use dwc_otg_test;
+
+check_arch() or die;
+
+#
+#
+sub test {
+  my ($param,$expected) = @_;
+  my $value = get($param);
+
+  if ($value == $expected) {
+    print "$param = $value, okay\n";
+  }
+
+  else {
+    warn "ERROR: value of $param != $expected, $value\n";
+    $errors ++;
+  }
+}
+
+#
+#
+sub get {
+  my $param = shift;
+  my $tmp = `cat $paramdir/$param`;
+  chomp $tmp;
+  return $tmp;
+}
+
+#
+#
+sub test_main {
+
+  print "\nTesting Module Parameters\n";
+
+  load_module("") or die;
+
+  # Test initial values
+  print "\nTesting Default Values\n";
+  foreach (@{$params}) {
+    test ($_->{NAME}, $_->{DEFAULT});
+  }
+
+  # Test low value
+  print "\nTesting Low Value\n";
+  my $cmd_params = "";
+  foreach (@{$params}) {
+    $cmd_params = $cmd_params . "$_->{NAME}=$_->{LOW} ";
+  }
+  load_module($cmd_params) or die;
+
+  foreach (@{$params}) {
+    test ($_->{NAME}, $_->{LOW});
+  }
+
+  # Test high value
+  print "\nTesting High Value\n";
+  $cmd_params = "";
+  foreach (@{$params}) {
+    $cmd_params = $cmd_params . "$_->{NAME}=$_->{HIGH} ";
+  }
+  load_module($cmd_params) or die;
+
+  foreach (@{$params}) {
+    test ($_->{NAME}, $_->{HIGH});
+  }
+
+  # Test Enum
+  print "\nTesting Enumerated\n";
+  foreach (@{$params}) {
+    if (defined $_->{ENUM}) {
+      my $value;
+      foreach $value (@{$_->{ENUM}}) {
+	$cmd_params = "$_->{NAME}=$value";
+	load_module($cmd_params) or die;
+	test ($_->{NAME}, $value);
+      }
+    }
+  }
+
+  # Test Invalid Values
+  print "\nTesting Invalid Values\n";
+  $cmd_params = "";
+  foreach (@{$params}) {
+    $cmd_params = $cmd_params . sprintf "$_->{NAME}=%d ", $_->{LOW}-1;
+  }
+  load_module($cmd_params) or die;
+
+  foreach (@{$params}) {
+    test ($_->{NAME}, $_->{DEFAULT});
+  }
+
+  $cmd_params = "";
+  foreach (@{$params}) {
+    $cmd_params = $cmd_params . sprintf "$_->{NAME}=%d ", $_->{HIGH}+1;
+  }
+  load_module($cmd_params) or die;
+
+  foreach (@{$params}) {
+    test ($_->{NAME}, $_->{DEFAULT});
+  }
+
+  print "\nTesting Enumerated\n";
+  foreach (@{$params}) {
+    if (defined $_->{ENUM}) {
+      my $value;
+      foreach $value (@{$_->{ENUM}}) {
+	$value = $value + 1;
+	$cmd_params = "$_->{NAME}=$value";
+	load_module($cmd_params) or die;
+	test ($_->{NAME}, $_->{DEFAULT});
+	$value = $value - 2;
+	$cmd_params = "$_->{NAME}=$value";
+	load_module($cmd_params) or die;
+	test ($_->{NAME}, $_->{DEFAULT});
+      }
+    }
+  }
+
+  test_status() or die;
+}
+
+test_main();
+0;
diff --git a/drivers/usb/host/dwc_otg/test/test_sysfs.pl b/drivers/usb/host/dwc_otg/test/test_sysfs.pl
new file mode 100644
index 000000000000..cdc9963176e5
--- /dev/null
+++ b/drivers/usb/host/dwc_otg/test/test_sysfs.pl
@@ -0,0 +1,193 @@
+#!/usr/bin/perl -w
+#
+# Run this program on the integrator
+# - Tests select sysfs attributes.
+# - Todo ... test more attributes, hnp/srp, buspower/bussuspend, etc.
+# -----------------------------------------------------------------------------
+use strict;
+use dwc_otg_test;
+
+check_arch() or die;
+
+#
+#
+sub test {
+  my ($attr,$expected) = @_;
+  my $string = get($attr);
+
+  if ($string eq $expected) {
+    printf("$attr = $string, okay\n");
+  }
+  else {
+    warn "ERROR: value of $attr != $expected, $string\n";
+    $errors ++;
+  }
+}
+
+#
+#
+sub set {
+  my ($reg, $value) = @_;
+  system "echo $value > $sysfsdir/$reg";
+}
+
+#
+#
+sub get {
+  my $attr = shift;
+  my $string = `cat $sysfsdir/$attr`;
+  chomp $string;
+  if ($string =~ m/\s\=\s/) {
+    my $tmp;
+    ($tmp, $string) = split /\s=\s/, $string;
+  }
+  return $string;
+}
+
+#
+#
+sub test_main {
+  print("\nTesting Sysfs Attributes\n");
+
+  load_module("") or die;
+
+  # Test initial values of regoffset/regvalue/guid/gsnpsid
+  print("\nTesting Default Values\n");
+
+  test("regoffset", "0xffffffff");
+  test("regvalue", "invalid offset");
+  test("guid", "0x12345678");	# this will fail if it has been changed
+  test("gsnpsid", "0x4f54200a");
+
+  # Test operation of regoffset/regvalue
+  print("\nTesting regoffset\n");
+  set('regoffset', '5a5a5a5a');
+  test("regoffset", "0xffffffff");
+
+  set('regoffset', '0');
+  test("regoffset", "0x00000000");
+
+  set('regoffset', '40000');
+  test("regoffset", "0x00000000");
+
+  set('regoffset', '3ffff');
+  test("regoffset", "0x0003ffff");
+
+  set('regoffset', '1');
+  test("regoffset", "0x00000001");
+
+  print("\nTesting regvalue\n");
+  set('regoffset', '3c');
+  test("regvalue", "0x12345678");
+  set('regvalue', '5a5a5a5a');
+  test("regvalue", "0x5a5a5a5a");
+  set('regvalue','a5a5a5a5');
+  test("regvalue", "0xa5a5a5a5");
+  set('guid','12345678');
+
+  # Test HNP Capable
+  print("\nTesting HNP Capable bit\n");
+  set('hnpcapable', '1');
+  test("hnpcapable", "0x1");
+  set('hnpcapable','0');
+  test("hnpcapable", "0x0");
+
+  set('regoffset','0c');
+
+  my $old = get('gusbcfg');
+  print("setting hnpcapable\n");
+  set('hnpcapable', '1');
+  test("hnpcapable", "0x1");
+  test('gusbcfg', sprintf "0x%08x", (oct ($old) | (1<<9)));
+  test('regvalue', sprintf "0x%08x", (oct ($old) | (1<<9)));
+
+  $old = get('gusbcfg');
+  print("clearing hnpcapable\n");
+  set('hnpcapable', '0');
+  test("hnpcapable", "0x0");
+  test ('gusbcfg', sprintf "0x%08x", oct ($old) & (~(1<<9)));
+  test ('regvalue', sprintf "0x%08x", oct ($old) & (~(1<<9)));
+
+  # Test SRP Capable
+  print("\nTesting SRP Capable bit\n");
+  set('srpcapable', '1');
+  test("srpcapable", "0x1");
+  set('srpcapable','0');
+  test("srpcapable", "0x0");
+
+  set('regoffset','0c');
+
+  $old = get('gusbcfg');
+  print("setting srpcapable\n");
+  set('srpcapable', '1');
+  test("srpcapable", "0x1");
+  test('gusbcfg', sprintf "0x%08x", (oct ($old) | (1<<8)));
+  test('regvalue', sprintf "0x%08x", (oct ($old) | (1<<8)));
+
+  $old = get('gusbcfg');
+  print("clearing srpcapable\n");
+  set('srpcapable', '0');
+  test("srpcapable", "0x0");
+  test('gusbcfg', sprintf "0x%08x", oct ($old) & (~(1<<8)));
+  test('regvalue', sprintf "0x%08x", oct ($old) & (~(1<<8)));
+
+  # Test GGPIO
+  print("\nTesting GGPIO\n");
+  set('ggpio','5a5a5a5a');
+  test('ggpio','0x5a5a0000');
+  set('ggpio','a5a5a5a5');
+  test('ggpio','0xa5a50000');
+  set('ggpio','11110000');
+  test('ggpio','0x11110000');
+  set('ggpio','00001111');
+  test('ggpio','0x00000000');
+
+  # Test DEVSPEED
+  print("\nTesting DEVSPEED\n");
+  set('regoffset','800');
+  $old = get('regvalue');
+  set('devspeed','0');
+  test('devspeed','0x0');
+  test('regvalue',sprintf("0x%08x", oct($old) & ~(0x3)));
+  set('devspeed','1');
+  test('devspeed','0x1');
+  test('regvalue',sprintf("0x%08x", oct($old) & ~(0x3) | 1));
+  set('devspeed','2');
+  test('devspeed','0x2');
+  test('regvalue',sprintf("0x%08x", oct($old) & ~(0x3) | 2));
+  set('devspeed','3');
+  test('devspeed','0x3');
+  test('regvalue',sprintf("0x%08x", oct($old) & ~(0x3) | 3));
+  set('devspeed','4');
+  test('devspeed','0x0');
+  test('regvalue',sprintf("0x%08x", oct($old) & ~(0x3)));
+  set('devspeed','5');
+  test('devspeed','0x1');
+  test('regvalue',sprintf("0x%08x", oct($old) & ~(0x3) | 1));
+
+
+  #  mode	Returns the current mode:0 for device mode1 for host mode	Read
+  #  hnp	Initiate the Host Negotiation Protocol.  Read returns the status.	Read/Write
+  #  srp	Initiate the Session Request Protocol.  Read returns the status.	Read/Write
+  #  buspower	Get or Set the Power State of the bus (0 - Off or 1 - On) 	Read/Write
+  #  bussuspend	Suspend the USB bus.	Read/Write
+  #  busconnected	Get the connection status of the bus 	Read
+
+  #  gotgctl	Get or set the Core Control Status Register.	Read/Write
+  ##  gusbcfg	Get or set the Core USB Configuration Register	Read/Write
+  #  grxfsiz	Get or set the Receive FIFO Size Register	Read/Write
+  #  gnptxfsiz	Get or set the non-periodic Transmit Size Register	Read/Write
+  #  gpvndctl	Get or set the PHY Vendor Control Register	Read/Write
+  ##  ggpio	Get the value in the lower 16-bits of the General Purpose IO Register or Set the upper 16 bits.	Read/Write
+  ##  guid	Get or set the value of the User ID Register	Read/Write
+  ##  gsnpsid	Get the value of the Synopsys ID Regester	Read
+  ##  devspeed	Get or set the device speed setting in the DCFG register	Read/Write
+  #  enumspeed	Gets the device enumeration Speed.	Read
+  #  hptxfsiz	Get the value of the Host Periodic Transmit FIFO	Read
+  #  hprt0	Get or Set the value in the Host Port Control and Status Register	Read/Write
+
+  test_status("TEST NYI") or die;
+}
+
+test_main();
+0;
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index f0ed38da6a0c..48fc3658a4e0 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -705,6 +705,14 @@ void xhci_setup_streams_ep_input_ctx(struct xhci_hcd *xhci,
 	ep_ctx->ep_info &= cpu_to_le32(~EP_MAXPSTREAMS_MASK);
 	ep_ctx->ep_info |= cpu_to_le32(EP_MAXPSTREAMS(max_primary_streams)
 				       | EP_HAS_LSA);
+
+	/*
+	 * Set Host Initiated Data Move Disable to always defer stream
+	 * selection to the device. xHC implementations may treat this
+	 * field as "don't care, forced to 1" anyway - xHCI 1.2 s4.12.1.
+	 */
+	ep_ctx->ep_info2 |= EP_HID;
+
 	ep_ctx->deq  = cpu_to_le64(stream_info->ctx_array_dma);
 }
 
@@ -1387,6 +1395,7 @@ int xhci_endpoint_init(struct xhci_hcd *xhci,
 	unsigned int ep_index;
 	struct xhci_ep_ctx *ep_ctx;
 	struct xhci_ring *ep_ring;
+	struct usb_interface_cache *intfc;
 	unsigned int max_packet;
 	enum xhci_ring_type ring_type;
 	u32 max_esit_payload;
@@ -1396,6 +1405,8 @@ int xhci_endpoint_init(struct xhci_hcd *xhci,
 	unsigned int mult;
 	unsigned int avg_trb_len;
 	unsigned int err_count = 0;
+	unsigned int is_ums_dev = 0;
+	unsigned int i;
 
 	ep_index = xhci_get_endpoint_index(&ep->desc);
 	ep_ctx = xhci_get_ep_ctx(xhci, virt_dev->in_ctx, ep_index);
@@ -1431,9 +1442,35 @@ int xhci_endpoint_init(struct xhci_hcd *xhci,
 
 	mult = xhci_get_endpoint_mult(udev, ep);
 	max_packet = usb_endpoint_maxp(&ep->desc);
-	max_burst = xhci_get_endpoint_max_burst(udev, ep);
 	avg_trb_len = max_esit_payload;
 
+	/*
+	 * VL805 errata - Bulk OUT bursts to superspeed mass-storage
+	 * devices behind hub ports can cause data corruption with
+	 * non-wMaxPacket-multiple transfers.
+	 */
+	for (i = 0; i < udev->config->desc.bNumInterfaces; i++) {
+		intfc = udev->config->intf_cache[i];
+		/*
+		 * Slight hack - look at interface altsetting 0, which
+		 * should be the UMS bulk-only interface. If the class
+		 * matches, then we disable out bursts for all OUT
+		 * endpoints because endpoint assignments may change
+		 * between alternate settings.
+		 */
+		if (intfc->altsetting[0].desc.bInterfaceClass ==
+		    USB_CLASS_MASS_STORAGE) {
+			is_ums_dev = 1;
+			break;
+		}
+	}
+	if (xhci->quirks & XHCI_VLI_SS_BULK_OUT_BUG &&
+	    usb_endpoint_is_bulk_out(&ep->desc) && is_ums_dev &&
+	    udev->route)
+		max_burst = 0;
+	else
+		max_burst = xhci_get_endpoint_max_burst(udev, ep);
+
 	/* FIXME dig Mult and streams info out of ep companion desc */
 
 	/* Allow 3 retries for everything but isoc, set CErr = 3 */
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c
index 5f60528453e9..b270a89f203d 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -27,6 +27,8 @@
 #define SPARSE_DISABLE_BIT	17
 #define SPARSE_CNTL_ENABLE	0xC12C
 
+#define VL805_FW_VER_0138C0	0x0138C0
+
 /* Device for a quirk */
 #define PCI_VENDOR_ID_FRESCO_LOGIC		0x1b73
 #define PCI_DEVICE_ID_FRESCO_LOGIC_PDK		0x1000
@@ -254,6 +256,16 @@ static int xhci_pci_reinit(struct xhci_hcd *xhci, struct pci_dev *pdev)
 	return 0;
 }
 
+static u32 xhci_vl805_get_fw_version(struct pci_dev *dev)
+{
+	int ret;
+	u32 ver;
+
+	ret = pci_read_config_dword(dev, 0x50, &ver);
+	/* Default to a fw version of 0 instead of ~0 */
+	return ret ? 0 : ver;
+}
+
 static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci)
 {
 	struct pci_dev                  *pdev = to_pci_dev(dev);
@@ -454,6 +466,11 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci)
 	if (pdev->vendor == PCI_VENDOR_ID_VIA && pdev->device == PCI_DEVICE_ID_VIA_VL805) {
 		xhci->quirks |= XHCI_LPM_SUPPORT;
 		xhci->quirks |= XHCI_TRB_OVERFETCH;
+		xhci->quirks |= XHCI_EP_CTX_BROKEN_DCS;
+		xhci->quirks |= XHCI_AVOID_DQ_ON_LINK;
+		xhci->quirks |= XHCI_VLI_SS_BULK_OUT_BUG;
+		if (xhci_vl805_get_fw_version(pdev) < VL805_FW_VER_0138C0)
+			xhci->quirks |= XHCI_VLI_HUB_TT_QUIRK;
 	}
 
 	if (pdev->vendor == PCI_VENDOR_ID_ASMEDIA &&
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 9d506409fdf6..5fe19240d2b9 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -509,6 +509,19 @@ void xhci_ring_ep_doorbell(struct xhci_hcd *xhci,
 
 	trace_xhci_ring_ep_doorbell(slot_id, DB_VALUE(ep_index, stream_id));
 
+	/*
+	 * For non-coherent systems with PCIe DMA (such as Pi 4, Pi 5) there
+	 * is a theoretical race between the TRB write and barrier, which
+	 * is reported complete as soon as the write leaves the CPU domain,
+	 * the doorbell write, which may be reported as complete by the RC
+	 * at some arbitrary point, and the visibility of new TRBs in system
+	 * RAM by the endpoint DMA engine.
+	 *
+	 * This read before the write positively serialises the CPU state
+	 * by incurring a round-trip across the link.
+	 */
+	readl(db_addr);
+
 	writel(DB_VALUE(ep_index, stream_id), db_addr);
 	/* flush the write */
 	readl(db_addr);
@@ -637,8 +650,11 @@ static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci,
 	struct xhci_ring *ep_ring;
 	struct xhci_command *cmd;
 	struct xhci_segment *new_seg;
+	struct xhci_segment *halted_seg = NULL;
 	union xhci_trb *new_deq;
 	int new_cycle;
+	union xhci_trb *halted_trb;
+	int index = 0;
 	dma_addr_t addr;
 	u64 hw_dequeue;
 	bool cycle_found = false;
@@ -657,7 +673,25 @@ static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci,
 	hw_dequeue = xhci_get_hw_deq(xhci, dev, ep_index, stream_id);
 	new_seg = ep_ring->deq_seg;
 	new_deq = ep_ring->dequeue;
-	new_cycle = hw_dequeue & 0x1;
+
+	/*
+	 * Quirk: xHC write-back of the DCS field in the hardware dequeue
+	 * pointer is wrong - use the cycle state of the TRB pointed to by
+	 * the dequeue pointer.
+	 */
+	if (xhci->quirks & XHCI_EP_CTX_BROKEN_DCS &&
+	    !(ep->ep_state & EP_HAS_STREAMS))
+		halted_seg = trb_in_td(xhci, td, hw_dequeue & ~0xf, false);
+	if (halted_seg) {
+		index = ((dma_addr_t)(hw_dequeue & ~0xf) - halted_seg->dma) /
+			 sizeof(*halted_trb);
+		halted_trb = &halted_seg->trbs[index];
+		new_cycle = halted_trb->generic.field[3] & 0x1;
+		xhci_dbg(xhci, "Endpoint DCS = %d TRB index = %d cycle = %d\n",
+			 (u8)(hw_dequeue & 0x1), index, new_cycle);
+	} else {
+		new_cycle = hw_dequeue & 0x1;
+	}
 
 	/*
 	 * We want to find the pointer, segment and cycle state of the new trb
@@ -689,6 +723,16 @@ static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci,
 
 	} while (!cycle_found || !td_last_trb_found);
 
+	/*
+	 * Quirk: the xHC does not correctly parse link TRBs if the HW Dequeue
+	 * pointer is set to one. Advance to the next TRB (and next segment).
+	 */
+	if (xhci->quirks & XHCI_AVOID_DQ_ON_LINK && trb_is_link(new_deq)) {
+		if (link_trb_toggles_cycle(new_deq))
+			new_cycle ^= 0x1;
+		next_trb(xhci, ep_ring, &new_seg, &new_deq);
+	}
+
 	/* Don't update the ring cycle state for the producer (us). */
 	addr = xhci_trb_virt_to_dma(new_seg, new_deq);
 	if (addr == 0) {
@@ -698,9 +742,9 @@ static int xhci_move_dequeue_past_td(struct xhci_hcd *xhci,
 	}
 
 	if ((ep->ep_state & SET_DEQ_PENDING)) {
-		xhci_warn(xhci, "Set TR Deq already pending, don't submit for 0x%pad\n",
-			  &addr);
-		return -EBUSY;
+		xhci_warn(xhci, "WARN A Set TR Deq Ptr command is pending for slot %u ep %u\n",
+			  slot_id, ep_index);
+		ep->ep_state &= ~SET_DEQ_PENDING;
 	}
 
 	/* This function gets called from contexts where it cannot sleep */
@@ -3636,6 +3680,48 @@ static int xhci_align_td(struct xhci_hcd *xhci, struct urb *urb, u32 enqd_len,
 	return 1;
 }
 
+static void xhci_vl805_hub_tt_quirk(struct xhci_hcd *xhci, struct urb *urb,
+				    struct xhci_ring *ring)
+{
+	struct list_head *tmp;
+	struct usb_device *udev = urb->dev;
+	unsigned int timeout = 0;
+	unsigned int single_td = 0;
+
+	/*
+	 * Adding a TD to an Idle ring for a FS nonperiodic endpoint
+	 * that is behind the internal hub's TT will run the risk of causing a
+	 * downstream port babble if submitted late in uFrame 7.
+	 * Wait until we've moved on into at least uFrame 0
+	 * (MFINDEX references the next SOF to be transmitted).
+	 *
+	 * Rings for IN endpoints in the Running state also risk causing
+	 * babble if the returned data is large, but there's not much we can do
+	 * about it here.
+	 */
+	if (udev->route & 0xffff0 || udev->speed != USB_SPEED_FULL)
+		return;
+
+	list_for_each(tmp, &ring->td_list) {
+		single_td++;
+		if (single_td == 2) {
+			single_td = 0;
+			break;
+		}
+	}
+	if (single_td) {
+		while (timeout < 20 &&
+		       (readl(&xhci->run_regs->microframe_index) & 0x7) == 0) {
+			udelay(10);
+			timeout++;
+		}
+		if (timeout >= 20)
+			xhci_warn(xhci, "MFINDEX didn't advance - %u.%u dodged\n",
+				  readl(&xhci->run_regs->microframe_index) >> 3,
+				  readl(&xhci->run_regs->microframe_index) & 7);
+	}
+}
+
 /* This is very similar to what ehci-q.c qtd_fill() does */
 int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
 		struct urb *urb, int slot_id, unsigned int ep_index)
@@ -3790,6 +3876,8 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
 	}
 
 	check_trb_math(urb, enqd_len);
+	if (xhci->quirks & XHCI_VLI_HUB_TT_QUIRK)
+		xhci_vl805_hub_tt_quirk(xhci, urb, ring);
 	giveback_first_trb(xhci, slot_id, ep_index, urb->stream_id,
 			start_cycle, start_trb);
 	return 0;
@@ -3938,6 +4026,8 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
 			/* Event on completion */
 			field | TRB_IOC | TRB_TYPE(TRB_STATUS) | ep_ring->cycle_state);
 
+	if (xhci->quirks & XHCI_VLI_HUB_TT_QUIRK)
+		xhci_vl805_hub_tt_quirk(xhci, urb, ep_ring);
 	giveback_first_trb(xhci, slot_id, ep_index, 0,
 			start_cycle, start_trb);
 	return 0;
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 45e786852920..0a1aca6e0e76 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -39,6 +39,10 @@ static unsigned long long quirks;
 module_param(quirks, ullong, S_IRUGO);
 MODULE_PARM_DESC(quirks, "Bit flags for quirks to be enabled as default");
 
+static int sandbag_lpm = 1;
+module_param(sandbag_lpm, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(sandbag_lpm, "Use relaxed U1/U2 port LPM timeouts");
+
 static bool td_on_ring(struct xhci_td *td, struct xhci_ring *ring)
 {
 	struct xhci_segment *seg;
@@ -1514,6 +1518,114 @@ static int xhci_check_ep0_maxpacket(struct xhci_hcd *xhci, struct xhci_virt_devi
 	return ret;
 }
 
+/*
+ * RPI: Fixup endpoint intervals when requested
+ * - Check interval versus the (cached) endpoint context
+ * - set the endpoint interval to the new value
+ * - force an endpoint configure command
+ * XXX: bandwidth is not recalculated. We should probably do that.
+ */
+
+static void xhci_setup_input_ctx_for_config_ep(struct xhci_hcd *xhci,
+		struct xhci_container_ctx *in_ctx,
+		struct xhci_container_ctx *out_ctx,
+		struct xhci_input_control_ctx *ctrl_ctx,
+		u32 add_flags, u32 drop_flags);
+
+static void xhci_fixup_endpoint(struct usb_hcd *hcd, struct usb_device *udev,
+				struct usb_host_endpoint *ep, int interval)
+{
+	struct xhci_hcd *xhci;
+	struct xhci_ep_ctx *ep_ctx_out, *ep_ctx_in;
+	struct xhci_command *command;
+	struct xhci_input_control_ctx *ctrl_ctx;
+	struct xhci_virt_device *vdev;
+	int xhci_interval;
+	int ret;
+	int ep_index;
+	unsigned long flags;
+	u32 ep_flag, ep_info_tmp;
+
+	xhci = hcd_to_xhci(hcd);
+	ep_index = xhci_get_endpoint_index(&ep->desc);
+	ep_flag = xhci_get_endpoint_flag(&ep->desc);
+
+	if (ep_flag == SLOT_FLAG || ep_flag == EP0_FLAG)
+		return;
+
+	/* FS/LS interval translations */
+	if ((udev->speed == USB_SPEED_FULL ||
+	     udev->speed == USB_SPEED_LOW))
+		interval *= 8;
+
+	mutex_lock(&xhci->mutex);
+
+	spin_lock_irqsave(&xhci->lock, flags);
+
+	vdev = xhci->devs[udev->slot_id];
+	/* Get context-derived endpoint interval */
+	ep_ctx_out = xhci_get_ep_ctx(xhci, vdev->out_ctx, ep_index);
+	ep_ctx_in = xhci_get_ep_ctx(xhci, vdev->in_ctx, ep_index);
+	xhci_interval = EP_INTERVAL_TO_UFRAMES(le32_to_cpu(ep_ctx_out->ep_info));
+
+	if (interval == xhci_interval) {
+		spin_unlock_irqrestore(&xhci->lock, flags);
+		mutex_unlock(&xhci->mutex);
+		return;
+	}
+
+	xhci_dbg(xhci, "Fixup interval=%d xhci_interval=%d\n",
+		 interval, xhci_interval);
+	command = xhci_alloc_command_with_ctx(xhci, true, GFP_ATOMIC);
+	if (!command) {
+		/* Failure here is benign, poll at the original rate */
+		spin_unlock_irqrestore(&xhci->lock, flags);
+		mutex_unlock(&xhci->mutex);
+		return;
+	}
+
+	/* xHCI uses exponents for intervals... */
+	xhci_interval = fls(interval) - 1;
+	xhci_interval = clamp_val(xhci_interval, 3, 10);
+	ep_info_tmp = le32_to_cpu(ep_ctx_out->ep_info);
+	ep_info_tmp &= ~EP_INTERVAL(255);
+	ep_info_tmp |= EP_INTERVAL(xhci_interval);
+
+	/* Keep the endpoint context up-to-date while issuing the command. */
+	xhci_endpoint_copy(xhci, vdev->in_ctx,
+			   vdev->out_ctx, ep_index);
+	ep_ctx_in->ep_info = cpu_to_le32(ep_info_tmp);
+
+	/*
+	 * We need to drop the lock, so take an explicit copy
+	 * of the ep context.
+	 */
+	xhci_endpoint_copy(xhci, command->in_ctx, vdev->in_ctx, ep_index);
+
+	ctrl_ctx = xhci_get_input_control_ctx(command->in_ctx);
+	if (!ctrl_ctx) {
+		xhci_warn(xhci,
+			  "%s: Could not get input context, bad type.\n",
+			  __func__);
+		spin_unlock_irqrestore(&xhci->lock, flags);
+		xhci_free_command(xhci, command);
+		mutex_unlock(&xhci->mutex);
+		return;
+	}
+	xhci_setup_input_ctx_for_config_ep(xhci, command->in_ctx, vdev->out_ctx,
+					   ctrl_ctx, ep_flag, ep_flag);
+
+	spin_unlock_irqrestore(&xhci->lock, flags);
+
+	ret = xhci_configure_endpoint(xhci, udev, command,
+				      false, false);
+	if (ret)
+		xhci_warn(xhci, "%s: Configure endpoint failed: %d\n",
+			  __func__, ret);
+	xhci_free_command(xhci, command);
+	mutex_unlock(&xhci->mutex);
+}
+
 /*
  * non-error returns are a promise to giveback() the urb later
  * we drop ownership so next owner (or urb unlink) can get it
@@ -4689,7 +4801,7 @@ static u16 xhci_calculate_u1_timeout(struct xhci_hcd *xhci,
 		}
 	}
 
-	if (xhci->quirks & (XHCI_INTEL_HOST | XHCI_ZHAOXIN_HOST))
+	if (sandbag_lpm || xhci->quirks & (XHCI_INTEL_HOST | XHCI_ZHAOXIN_HOST))
 		timeout_ns = xhci_calculate_intel_u1_timeout(udev, desc);
 	else
 		timeout_ns = udev->u1_params.sel;
@@ -4753,7 +4865,7 @@ static u16 xhci_calculate_u2_timeout(struct xhci_hcd *xhci,
 		}
 	}
 
-	if (xhci->quirks & (XHCI_INTEL_HOST | XHCI_ZHAOXIN_HOST))
+	if (sandbag_lpm || xhci->quirks & (XHCI_INTEL_HOST | XHCI_ZHAOXIN_HOST))
 		timeout_ns = xhci_calculate_intel_u2_timeout(udev, desc);
 	else
 		timeout_ns = udev->u2_params.sel;
@@ -5387,6 +5499,7 @@ static const struct hc_driver xhci_hc_driver = {
 	.endpoint_reset =	xhci_endpoint_reset,
 	.check_bandwidth =	xhci_check_bandwidth,
 	.reset_bandwidth =	xhci_reset_bandwidth,
+	.fixup_endpoint =	xhci_fixup_endpoint,
 	.address_device =	xhci_address_device,
 	.enable_device =	xhci_enable_device,
 	.update_hub_device =	xhci_update_hub_device,
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index b4fa8e7e4376..fe9e52d85e67 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -468,6 +468,8 @@ struct xhci_ep_ctx {
 #define CTX_TO_EP_MAXPSTREAMS(p)	(((p) & EP_MAXPSTREAMS_MASK) >> 10)
 /* Endpoint is set up with a Linear Stream Array (vs. Secondary Stream Array) */
 #define	EP_HAS_LSA		(1 << 15)
+/* Host initiated data move disable in info2 */
+#define EP_HID			(1 << 7)
 /* hosts with LEC=1 use bits 31:24 as ESIT high bits. */
 #define CTX_TO_MAX_ESIT_PAYLOAD_HI(p)	(((p) >> 24) & 0xff)
 
@@ -1398,7 +1400,7 @@ struct urb_priv {
 };
 
 /* Number of Event Ring segments to allocate, when amount is not specified. (spec allows 32k) */
-#define	ERST_DEFAULT_SEGS	2
+#define	ERST_DEFAULT_SEGS	8
 /* Poll every 60 seconds */
 #define	POLL_TIMEOUT	60
 /* Stop endpoint command timeout (secs) for URB cancellation watchdog timer */
@@ -1631,6 +1633,11 @@ struct xhci_hcd {
 #define XHCI_ETRON_HOST	BIT_ULL(49)
 #define XHCI_LIMIT_ENDPOINT_INTERVAL_9 BIT_ULL(50)
 
+/* Downstream VLI fixes */
+#define XHCI_AVOID_DQ_ON_LINK	BIT_ULL(56)
+#define XHCI_VLI_SS_BULK_OUT_BUG	BIT_ULL(57)
+#define XHCI_VLI_HUB_TT_QUIRK	BIT_ULL(58)
+
 	unsigned int		num_active_eps;
 	unsigned int		limit_active_eps;
 	struct xhci_port	*hw_ports;
diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c
index aadf98f65c60..59131f28b11b 100644
--- a/drivers/usb/phy/phy-generic.c
+++ b/drivers/usb/phy/phy-generic.c
@@ -256,13 +256,6 @@ int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop)
 		return dev_err_probe(dev, PTR_ERR(nop->vcc),
 				     "could not get vcc regulator\n");
 
-	nop->vbus_draw = devm_regulator_get_exclusive(dev, "vbus");
-	if (PTR_ERR(nop->vbus_draw) == -ENODEV)
-		nop->vbus_draw = NULL;
-	if (IS_ERR(nop->vbus_draw))
-		return dev_err_probe(dev, PTR_ERR(nop->vbus_draw),
-				     "could not get vbus regulator\n");
-
 	nop->dev		= dev;
 	nop->phy.dev		= nop->dev;
 	nop->phy.label		= "nop-xceiv";
diff --git a/drivers/usb/serial/pl2303.c b/drivers/usb/serial/pl2303.c
index 9708c3d40f07..22579d0d8ab8 100644
--- a/drivers/usb/serial/pl2303.c
+++ b/drivers/usb/serial/pl2303.c
@@ -31,6 +31,7 @@
 #define PL2303_QUIRK_UART_STATE_IDX0		BIT(0)
 #define PL2303_QUIRK_LEGACY			BIT(1)
 #define PL2303_QUIRK_ENDPOINT_HACK		BIT(2)
+#define PL2303_QUIRK_NO_BREAK_GETLINE		BIT(3)
 
 static const struct usb_device_id id_table[] = {
 	{ USB_DEVICE(PL2303_VENDOR_ID, PL2303_PRODUCT_ID),
@@ -469,6 +470,25 @@ static int pl2303_detect_type(struct usb_serial *serial)
 	return -ENODEV;
 }
 
+static bool pl2303_is_hxd_clone(struct usb_serial *serial)
+{
+	struct usb_device *udev = serial->dev;
+	unsigned char *buf;
+	int ret;
+
+	buf = kmalloc(7, GFP_KERNEL);
+	if (!buf)
+		return false;
+
+	ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+			      GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE,
+			      0, 0, buf, 7, 100);
+
+	kfree(buf);
+
+	return ret == -EPIPE;
+}
+
 static int pl2303_startup(struct usb_serial *serial)
 {
 	struct pl2303_serial_private *spriv;
@@ -491,6 +511,9 @@ static int pl2303_startup(struct usb_serial *serial)
 	spriv->quirks = (unsigned long)usb_get_serial_data(serial);
 	spriv->quirks |= spriv->type->quirks;
 
+	if (type == TYPE_HXD && pl2303_is_hxd_clone(serial))
+		spriv->quirks |= PL2303_QUIRK_NO_BREAK_GETLINE;
+
 	usb_set_serial_data(serial, spriv);
 
 	if (type != TYPE_HXN) {
@@ -727,9 +750,18 @@ static void pl2303_encode_baud_rate(struct tty_struct *tty,
 static int pl2303_get_line_request(struct usb_serial_port *port,
 							unsigned char buf[7])
 {
-	struct usb_device *udev = port->serial->dev;
+	struct usb_serial *serial = port->serial;
+	struct pl2303_serial_private *spriv = usb_get_serial_data(serial);
+	struct usb_device *udev = serial->dev;
 	int ret;
 
+	if (spriv->quirks & PL2303_QUIRK_NO_BREAK_GETLINE) {
+		struct pl2303_private *priv = usb_get_serial_port_data(port);
+
+		memcpy(buf, priv->line_settings, 7);
+		return 0;
+	}
+
 	ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
 				GET_LINE_REQUEST, GET_LINE_REQUEST_TYPE,
 				0, 0, buf, 7, 100);
@@ -1066,9 +1098,13 @@ static int pl2303_carrier_raised(struct usb_serial_port *port)
 static int pl2303_set_break(struct usb_serial_port *port, bool enable)
 {
 	struct usb_serial *serial = port->serial;
+	struct pl2303_serial_private *spriv = usb_get_serial_data(serial);
 	u16 state;
 	int result;
 
+	if (spriv->quirks & PL2303_QUIRK_NO_BREAK_GETLINE)
+		return -ENOTTY;
+
 	if (enable)
 		state = BREAK_ON;
 	else
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 3614a5d29c71..d0951c7f375a 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -249,6 +249,13 @@ config BACKLIGHT_PWM
 	  If you have a LCD backlight adjustable by PWM, say Y to enable
 	  this driver.
 
+config BACKLIGHT_RPI
+	tristate "Raspberry Pi display firmware driven backlight"
+	depends on RASPBERRYPI_FIRMWARE
+	help
+	  If you have the Raspberry Pi DSI touchscreen display, say Y to
+	  enable the mailbox-controlled backlight driver.
+
 config BACKLIGHT_DA903X
 	tristate "Backlight Driver for DA9030/DA9034 using WLED"
 	depends on PMIC_DA903X
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 8fc98f760a8a..631227dfc1aa 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -52,6 +52,7 @@ obj-$(CONFIG_BACKLIGHT_PANDORA)		+= pandora_bl.o
 obj-$(CONFIG_BACKLIGHT_PCF50633)	+= pcf50633-backlight.o
 obj-$(CONFIG_BACKLIGHT_PWM)		+= pwm_bl.o
 obj-$(CONFIG_BACKLIGHT_QCOM_WLED)	+= qcom-wled.o
+obj-$(CONFIG_BACKLIGHT_RPI)			+= rpi_backlight.o
 obj-$(CONFIG_BACKLIGHT_RT4831)		+= rt4831-backlight.o
 obj-$(CONFIG_BACKLIGHT_SAHARA)		+= kb3886_bl.o
 obj-$(CONFIG_BACKLIGHT_SKY81452)	+= sky81452-backlight.o
diff --git a/drivers/video/backlight/backlight.c b/drivers/video/backlight/backlight.c
index a82934694d05..eb7af55d2537 100644
--- a/drivers/video/backlight/backlight.c
+++ b/drivers/video/backlight/backlight.c
@@ -287,6 +287,15 @@ static ssize_t max_brightness_show(struct device *dev,
 }
 static DEVICE_ATTR_RO(max_brightness);
 
+static ssize_t display_name_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct backlight_device *bd = to_backlight_device(dev);
+
+	return sprintf(buf, "%s\n", bd->props.display_name);
+}
+static DEVICE_ATTR_RO(display_name);
+
 static ssize_t actual_brightness_show(struct device *dev,
 		struct device_attribute *attr, char *buf)
 {
@@ -365,6 +374,7 @@ static struct attribute *bl_device_attrs[] = {
 	&dev_attr_max_brightness.attr,
 	&dev_attr_scale.attr,
 	&dev_attr_type.attr,
+	&dev_attr_display_name.attr,
 	NULL,
 };
 ATTRIBUTE_GROUPS(bl_device);
@@ -668,6 +678,17 @@ static int of_parent_match(struct device *dev, const void *data)
 	return dev->parent && dev->parent->of_node == data;
 }
 
+int backlight_set_display_name(struct backlight_device *bd, const char *name)
+{
+	if (!bd)
+		return -EINVAL;
+
+	strscpy_pad(bd->props.display_name, name, sizeof(bd->props.display_name));
+
+	return 0;
+}
+EXPORT_SYMBOL(backlight_set_display_name);
+
 /**
  * of_find_backlight_by_node() - find backlight device by device-tree node
  * @node: device-tree node of the backlight device
diff --git a/drivers/video/backlight/rpi_backlight.c b/drivers/video/backlight/rpi_backlight.c
new file mode 100644
index 000000000000..877f29f1d7b0
--- /dev/null
+++ b/drivers/video/backlight/rpi_backlight.c
@@ -0,0 +1,118 @@
+/*
+ * rpi_bl.c - Backlight controller through VPU
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/backlight.h>
+#include <linux/err.h>
+#include <linux/fb.h>
+#include <linux/gpio.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <soc/bcm2835/raspberrypi-firmware.h>
+
+struct rpi_backlight {
+	struct device *dev;
+	struct device *fbdev;
+	struct rpi_firmware *fw;
+};
+
+static int rpi_backlight_update_status(struct backlight_device *bl)
+{
+	struct rpi_backlight *gbl = bl_get_data(bl);
+	int brightness = bl->props.brightness;
+	int ret;
+
+	if (bl->props.power != FB_BLANK_UNBLANK ||
+	    bl->props.state & (BL_CORE_SUSPENDED | BL_CORE_FBBLANK))
+		brightness = 0;
+
+	ret = rpi_firmware_property(gbl->fw,
+			RPI_FIRMWARE_FRAMEBUFFER_SET_BACKLIGHT,
+			&brightness, sizeof(brightness));
+	if (ret) {
+		dev_err(gbl->dev, "Failed to set brightness\n");
+		return ret;
+	}
+
+	if (brightness < 0) {
+		dev_err(gbl->dev, "Backlight change failed\n");
+		return -EAGAIN;
+	}
+
+	return 0;
+}
+
+static const struct backlight_ops rpi_backlight_ops = {
+	.options	= BL_CORE_SUSPENDRESUME,
+	.update_status	= rpi_backlight_update_status,
+};
+
+static int rpi_backlight_probe(struct platform_device *pdev)
+{
+	struct backlight_properties props;
+	struct backlight_device *bl;
+	struct rpi_backlight *gbl;
+	struct device_node *fw_node;
+
+	gbl = devm_kzalloc(&pdev->dev, sizeof(*gbl), GFP_KERNEL);
+	if (gbl == NULL)
+		return -ENOMEM;
+
+	gbl->dev = &pdev->dev;
+
+	fw_node = of_parse_phandle(pdev->dev.of_node, "firmware", 0);
+	if (!fw_node) {
+		dev_err(&pdev->dev, "Missing firmware node\n");
+		return -ENOENT;
+	}
+
+	gbl->fw = rpi_firmware_get(fw_node);
+	if (!gbl->fw)
+		return -EPROBE_DEFER;
+
+	memset(&props, 0, sizeof(props));
+	props.type = BACKLIGHT_RAW;
+	props.max_brightness = 255;
+	bl = devm_backlight_device_register(&pdev->dev, dev_name(&pdev->dev),
+					&pdev->dev, gbl, &rpi_backlight_ops,
+					&props);
+	if (IS_ERR(bl)) {
+		dev_err(&pdev->dev, "failed to register backlight\n");
+		return PTR_ERR(bl);
+	}
+
+	bl->props.brightness = 255;
+	backlight_update_status(bl);
+
+	platform_set_drvdata(pdev, bl);
+	return 0;
+}
+
+static const struct of_device_id rpi_backlight_of_match[] = {
+	{ .compatible = "raspberrypi,rpi-backlight" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rpi_backlight_of_match);
+
+static struct platform_driver rpi_backlight_driver = {
+	.driver		= {
+		.name		= "rpi-backlight",
+		.of_match_table = of_match_ptr(rpi_backlight_of_match),
+	},
+	.probe		= rpi_backlight_probe,
+};
+
+module_platform_driver(rpi_backlight_driver);
+
+MODULE_AUTHOR("Gordon Hollingworth <gordon@raspberrypi.org>");
+MODULE_DESCRIPTION("Raspberry Pi mailbox based Backlight Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/fbdev/Kconfig b/drivers/video/fbdev/Kconfig
index de035071fedb..8e5400ee3ce3 100644
--- a/drivers/video/fbdev/Kconfig
+++ b/drivers/video/fbdev/Kconfig
@@ -61,6 +61,19 @@ config FB_MACMODES
 	tristate
 	depends on FB
 
+config FB_BCM2708
+	tristate "BCM2708 framebuffer support"
+	depends on FB && RASPBERRYPI_FIRMWARE
+	select FB_DEVICE
+	select FB_IOMEM_HELPERS
+	help
+	  This framebuffer device driver is for the BCM2708 framebuffer.
+
+	  If you want to compile this as a module (=code which can be
+	  inserted into and removed from the running kernel), say M
+	  here and read <file:Documentation/kbuild/modules.txt>.  The module
+	  will be called bcm2708_fb.
+
 config FB_GRVGA
 	tristate "Aeroflex Gaisler framebuffer support"
 	depends on FB && SPARC
@@ -1811,6 +1824,19 @@ config FB_SM712
 	  called sm712fb. If you want to compile it as a module, say M
 	  here and read <file:Documentation/kbuild/modules.rst>.
 
+config FB_RPISENSE
+	tristate "Raspberry Pi Sense HAT framebuffer"
+	depends on FB && I2C
+	select MFD_SIMPLE_MFD_I2C
+	select FB_SYS_FOPS
+	select FB_SYS_FILLRECT
+	select FB_SYS_COPYAREA
+	select FB_SYS_IMAGEBLIT
+	select FB_DEFERRED_IO
+
+	help
+	  This is the framebuffer driver for the Raspberry Pi Sense HAT
+
 source "drivers/video/fbdev/omap/Kconfig"
 source "drivers/video/fbdev/omap2/Kconfig"
 source "drivers/video/fbdev/mmp/Kconfig"
diff --git a/drivers/video/fbdev/Makefile b/drivers/video/fbdev/Makefile
index b3d12f977c06..ea305adc3f3c 100644
--- a/drivers/video/fbdev/Makefile
+++ b/drivers/video/fbdev/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_FB_SBUS)          += sbuslib.o
 obj-$(CONFIG_FB_WMT_GE_ROPS)   += wmt_ge_rops.o
 
 # Hardware specific drivers go first
+obj-$(CONFIG_FB_BCM2708)	  += bcm2708_fb.o
 obj-$(CONFIG_FB_AMIGA)            += amifb.o c2p_planar.o
 obj-$(CONFIG_FB_ARC)              += arcfb.o
 obj-$(CONFIG_FB_CLPS711X)	  += clps711x-fb.o
@@ -123,6 +124,7 @@ obj-$(CONFIG_FB_VGA16)            += vga16fb.o
 obj-$(CONFIG_FB_OF)               += offb.o
 obj-$(CONFIG_FB_SSD1307)	  += ssd1307fb.o
 obj-$(CONFIG_FB_SIMPLE)           += simplefb.o
+obj-$(CONFIG_FB_RPISENSE)	  += rpisense-fb.o
 
 # the test framebuffer is last
 obj-$(CONFIG_FB_VIRTUAL)          += vfb.o
diff --git a/drivers/video/fbdev/bcm2708_fb.c b/drivers/video/fbdev/bcm2708_fb.c
new file mode 100644
index 000000000000..91f3e55538bf
--- /dev/null
+++ b/drivers/video/fbdev/bcm2708_fb.c
@@ -0,0 +1,1262 @@
+/*
+ *  linux/drivers/video/bcm2708_fb.c
+ *
+ * Copyright (C) 2010 Broadcom
+ * Copyright (C) 2018 Raspberry Pi (Trading) Ltd
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file COPYING in the main directory of this archive
+ * for more details.
+ *
+ * Broadcom simple framebuffer driver
+ *
+ * This file is derived from cirrusfb.c
+ * Copyright 1999-2001 Jeff Garzik <jgarzik@pobox.com>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/list.h>
+#include <linux/platform_data/dma-bcm2708.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/printk.h>
+#include <linux/console.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <linux/dma-mapping.h>
+#include <linux/cred.h>
+#include <soc/bcm2835/raspberrypi-firmware.h>
+#include <linux/mutex.h>
+#include <linux/compat.h>
+
+//#define BCM2708_FB_DEBUG
+#define MODULE_NAME "bcm2708_fb"
+
+#ifdef BCM2708_FB_DEBUG
+#define print_debug(fmt, ...) pr_debug("%s:%s:%d: " fmt, \
+			MODULE_NAME, __func__, __LINE__, ##__VA_ARGS__)
+#else
+#define print_debug(fmt, ...)
+#endif
+
+/* This is limited to 16 characters when displayed by X startup */
+static const char *bcm2708_name = "BCM2708 FB";
+
+#define DRIVER_NAME "bcm2708_fb"
+
+static int fbwidth = 800;	/* module parameter */
+static int fbheight = 480;	/* module parameter */
+static int fbdepth = 32;	/* module parameter */
+static int fbswap;		/* module parameter */
+
+static u32 dma_busy_wait_threshold = 1 << 15;
+module_param(dma_busy_wait_threshold, int, 0644);
+MODULE_PARM_DESC(dma_busy_wait_threshold, "Busy-wait for DMA completion below this area");
+
+struct fb_alloc_tags {
+	struct rpi_firmware_property_tag_header tag1;
+	u32 xres, yres;
+	struct rpi_firmware_property_tag_header tag2;
+	u32 xres_virtual, yres_virtual;
+	struct rpi_firmware_property_tag_header tag3;
+	u32 bpp;
+	struct rpi_firmware_property_tag_header tag4;
+	u32 xoffset, yoffset;
+	struct rpi_firmware_property_tag_header tag5;
+	u32 base, screen_size;
+	struct rpi_firmware_property_tag_header tag6;
+	u32 pitch;
+};
+
+struct bcm2708_fb_stats {
+	struct debugfs_regset32 regset;
+	u32 dma_copies;
+	u32 dma_irqs;
+};
+
+struct vc4_display_settings_t {
+	u32 display_num;
+	u32 width;
+	u32 height;
+	u32 depth;
+	u32 pitch;
+	u32 virtual_width;
+	u32 virtual_height;
+	u32 virtual_width_offset;
+	u32 virtual_height_offset;
+	unsigned long fb_bus_address;
+};
+
+struct bcm2708_fb_dev;
+
+struct bcm2708_fb {
+	struct fb_info fb;
+	struct platform_device *dev;
+	u32 cmap[16];
+	u32 gpu_cmap[256];
+	struct dentry *debugfs_dir;
+	struct dentry *debugfs_subdir;
+	unsigned long fb_bus_address;
+	struct { u32 base, length; } gpu;
+	struct vc4_display_settings_t display_settings;
+	struct debugfs_regset32 screeninfo_regset;
+	struct bcm2708_fb_dev *fbdev;
+	unsigned int image_size;
+	dma_addr_t dma_addr;
+	void *cpuaddr;
+};
+
+#define MAX_FRAMEBUFFERS 3
+
+struct bcm2708_fb_dev {
+	int firmware_supports_multifb;
+	/* Protects the DMA system from multiple FB access */
+	struct mutex dma_mutex;
+	int dma_chan;
+	int dma_irq;
+	void __iomem *dma_chan_base;
+	wait_queue_head_t dma_waitq;
+	bool disable_arm_alloc;
+	struct bcm2708_fb_stats dma_stats;
+	void *cb_base;	/* DMA control blocks */
+	dma_addr_t cb_handle;
+	int instance_count;
+	int num_displays;
+	struct rpi_firmware *fw;
+	struct bcm2708_fb displays[MAX_FRAMEBUFFERS];
+};
+
+#define to_bcm2708(info)	container_of(info, struct bcm2708_fb, fb)
+
+static void bcm2708_fb_debugfs_deinit(struct bcm2708_fb *fb)
+{
+	debugfs_remove_recursive(fb->debugfs_subdir);
+	fb->debugfs_subdir = NULL;
+
+	fb->fbdev->instance_count--;
+
+	if (!fb->fbdev->instance_count) {
+		debugfs_remove_recursive(fb->debugfs_dir);
+		fb->debugfs_dir = NULL;
+	}
+}
+
+static int bcm2708_fb_debugfs_init(struct bcm2708_fb *fb)
+{
+	char buf[3];
+	struct bcm2708_fb_dev *fbdev = fb->fbdev;
+
+	static struct debugfs_reg32 stats_registers[] = {
+	{"dma_copies", offsetof(struct bcm2708_fb_stats, dma_copies)},
+	{"dma_irqs",   offsetof(struct bcm2708_fb_stats, dma_irqs)},
+	};
+
+	static struct debugfs_reg32 screeninfo[] = {
+	{"width",	 offsetof(struct fb_var_screeninfo, xres)},
+	{"height",	 offsetof(struct fb_var_screeninfo, yres)},
+	{"bpp",		 offsetof(struct fb_var_screeninfo, bits_per_pixel)},
+	{"xres_virtual", offsetof(struct fb_var_screeninfo, xres_virtual)},
+	{"yres_virtual", offsetof(struct fb_var_screeninfo, yres_virtual)},
+	{"xoffset",	 offsetof(struct fb_var_screeninfo, xoffset)},
+	{"yoffset",	 offsetof(struct fb_var_screeninfo, yoffset)},
+	};
+
+	fb->debugfs_dir = debugfs_lookup(DRIVER_NAME, NULL);
+
+	if (!fb->debugfs_dir)
+		fb->debugfs_dir = debugfs_create_dir(DRIVER_NAME, NULL);
+
+	if (!fb->debugfs_dir) {
+		dev_warn(fb->fb.dev, "%s: could not create debugfs folder\n",
+			 __func__);
+		return -EFAULT;
+	}
+
+	snprintf(buf, sizeof(buf), "%u", fb->display_settings.display_num);
+
+	fb->debugfs_subdir = debugfs_create_dir(buf, fb->debugfs_dir);
+
+	if (!fb->debugfs_subdir) {
+		dev_warn(fb->fb.dev, "%s: could not create debugfs entry %u\n",
+			 __func__, fb->display_settings.display_num);
+		return -EFAULT;
+	}
+
+	fbdev->dma_stats.regset.regs = stats_registers;
+	fbdev->dma_stats.regset.nregs = ARRAY_SIZE(stats_registers);
+	fbdev->dma_stats.regset.base = &fbdev->dma_stats;
+
+	debugfs_create_regset32("dma_stats", 0444, fb->debugfs_subdir,
+				&fbdev->dma_stats.regset);
+
+	fb->screeninfo_regset.regs = screeninfo;
+	fb->screeninfo_regset.nregs = ARRAY_SIZE(screeninfo);
+	fb->screeninfo_regset.base = &fb->fb.var;
+
+	debugfs_create_regset32("screeninfo", 0444, fb->debugfs_subdir,
+				&fb->screeninfo_regset);
+
+	fbdev->instance_count++;
+
+	return 0;
+}
+
+static void set_display_num(struct bcm2708_fb *fb)
+{
+	if (fb && fb->fbdev && fb->fbdev->firmware_supports_multifb) {
+		u32 tmp = fb->display_settings.display_num;
+
+		if (rpi_firmware_property(fb->fbdev->fw,
+					  RPI_FIRMWARE_FRAMEBUFFER_SET_DISPLAY_NUM,
+					  &tmp,
+					  sizeof(tmp)))
+			dev_warn_once(fb->fb.dev,
+				      "Set display number call failed. Old GPU firmware?");
+	}
+}
+
+static int bcm2708_fb_set_bitfields(struct fb_var_screeninfo *var)
+{
+	int ret = 0;
+
+	memset(&var->transp, 0, sizeof(var->transp));
+
+	var->red.msb_right = 0;
+	var->green.msb_right = 0;
+	var->blue.msb_right = 0;
+
+	switch (var->bits_per_pixel) {
+	case 1:
+	case 2:
+	case 4:
+	case 8:
+		var->red.length = var->bits_per_pixel;
+		var->red.offset = 0;
+		var->green.length = var->bits_per_pixel;
+		var->green.offset = 0;
+		var->blue.length = var->bits_per_pixel;
+		var->blue.offset = 0;
+		break;
+	case 16:
+		var->red.length = 5;
+		var->blue.length = 5;
+		/*
+		 * Green length can be 5 or 6 depending whether
+		 * we're operating in RGB555 or RGB565 mode.
+		 */
+		if (var->green.length != 5 && var->green.length != 6)
+			var->green.length = 6;
+		break;
+	case 24:
+		var->red.length = 8;
+		var->blue.length = 8;
+		var->green.length = 8;
+		break;
+	case 32:
+		var->red.length = 8;
+		var->green.length = 8;
+		var->blue.length = 8;
+		var->transp.length = 8;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	/*
+	 * >= 16bpp displays have separate colour component bitfields
+	 * encoded in the pixel data.  Calculate their position from
+	 * the bitfield length defined above.
+	 */
+	if (ret == 0 && var->bits_per_pixel >= 24 && fbswap) {
+		var->blue.offset = 0;
+		var->green.offset = var->blue.offset + var->blue.length;
+		var->red.offset = var->green.offset + var->green.length;
+		var->transp.offset = var->red.offset + var->red.length;
+	} else if (ret == 0 && var->bits_per_pixel >= 24) {
+		var->red.offset = 0;
+		var->green.offset = var->red.offset + var->red.length;
+		var->blue.offset = var->green.offset + var->green.length;
+		var->transp.offset = var->blue.offset + var->blue.length;
+	} else if (ret == 0 && var->bits_per_pixel >= 16) {
+		var->blue.offset = 0;
+		var->green.offset = var->blue.offset + var->blue.length;
+		var->red.offset = var->green.offset + var->green.length;
+		var->transp.offset = var->red.offset + var->red.length;
+	}
+
+	return ret;
+}
+
+static int bcm2708_fb_check_var(struct fb_var_screeninfo *var,
+				struct fb_info *info)
+{
+	/* info input, var output */
+	print_debug("%s(%p) %ux%u (%ux%u), %ul, %u\n",
+		    __func__, info, info->var.xres, info->var.yres,
+		    info->var.xres_virtual, info->var.yres_virtual,
+		    info->screen_size, info->var.bits_per_pixel);
+	print_debug("%s(%p) %ux%u (%ux%u), %u\n", __func__, var, var->xres,
+		    var->yres, var->xres_virtual, var->yres_virtual,
+		    var->bits_per_pixel);
+
+	if (!var->bits_per_pixel)
+		var->bits_per_pixel = 16;
+
+	if (bcm2708_fb_set_bitfields(var) != 0) {
+		pr_err("%s: invalid bits_per_pixel %d\n", __func__,
+		       var->bits_per_pixel);
+		return -EINVAL;
+	}
+
+	if (var->xres_virtual < var->xres)
+		var->xres_virtual = var->xres;
+	/* use highest possible virtual resolution */
+	if (var->yres_virtual == -1) {
+		var->yres_virtual = 480;
+
+		pr_err("%s: virtual resolution set to maximum of %dx%d\n",
+		       __func__, var->xres_virtual, var->yres_virtual);
+	}
+	if (var->yres_virtual < var->yres)
+		var->yres_virtual = var->yres;
+
+	if (var->xoffset < 0)
+		var->xoffset = 0;
+	if (var->yoffset < 0)
+		var->yoffset = 0;
+
+	/* truncate xoffset and yoffset to maximum if too high */
+	if (var->xoffset > var->xres_virtual - var->xres)
+		var->xoffset = var->xres_virtual - var->xres - 1;
+	if (var->yoffset > var->yres_virtual - var->yres)
+		var->yoffset = var->yres_virtual - var->yres - 1;
+
+	return 0;
+}
+
+static int bcm2708_fb_set_par(struct fb_info *info)
+{
+	struct bcm2708_fb *fb = to_bcm2708(info);
+	struct fb_alloc_tags fbinfo = {
+		.tag1 = { RPI_FIRMWARE_FRAMEBUFFER_SET_PHYSICAL_WIDTH_HEIGHT,
+			  8, 0, },
+			.xres = info->var.xres,
+			.yres = info->var.yres,
+		.tag2 = { RPI_FIRMWARE_FRAMEBUFFER_SET_VIRTUAL_WIDTH_HEIGHT,
+			  8, 0, },
+			.xres_virtual = info->var.xres_virtual,
+			.yres_virtual = info->var.yres_virtual,
+		.tag3 = { RPI_FIRMWARE_FRAMEBUFFER_SET_DEPTH, 4, 0 },
+			.bpp = info->var.bits_per_pixel,
+		.tag4 = { RPI_FIRMWARE_FRAMEBUFFER_SET_VIRTUAL_OFFSET, 8, 0 },
+			.xoffset = info->var.xoffset,
+			.yoffset = info->var.yoffset,
+		.tag5 = { RPI_FIRMWARE_FRAMEBUFFER_ALLOCATE, 8, 0 },
+			/* base and screen_size will be initialised later */
+		.tag6 = { RPI_FIRMWARE_FRAMEBUFFER_SET_PITCH, 4, 0 },
+			/* pitch will be initialised later */
+	};
+	int ret, image_size;
+
+	print_debug("%s(%p) %dx%d (%dx%d), %d, %d (display %d)\n", __func__,
+		    info,
+		    info->var.xres, info->var.yres, info->var.xres_virtual,
+		    info->var.yres_virtual, (int)info->screen_size,
+		    info->var.bits_per_pixel, value);
+
+	/* Need to set the display number to act on first
+	 * Cannot do it in the tag list because on older firmware the call
+	 * will fail and stop the rest of the list being executed.
+	 * We can ignore this call failing as the default at other end is 0
+	 */
+	set_display_num(fb);
+
+	/* Try allocating our own buffer. We can specify all the parameters */
+	image_size = ((info->var.xres * info->var.yres) *
+		      info->var.bits_per_pixel) >> 3;
+
+	if (!fb->fbdev->disable_arm_alloc &&
+	    (image_size != fb->image_size || !fb->dma_addr)) {
+		if (fb->dma_addr) {
+			dma_free_coherent(info->device, fb->image_size,
+					  fb->cpuaddr, fb->dma_addr);
+			fb->image_size = 0;
+			fb->cpuaddr = NULL;
+			fb->dma_addr = 0;
+		}
+
+		fb->cpuaddr = dma_alloc_coherent(info->device, image_size,
+						 &fb->dma_addr, GFP_KERNEL);
+
+		if (!fb->cpuaddr) {
+			fb->dma_addr = 0;
+			fb->fbdev->disable_arm_alloc = true;
+		} else {
+			fb->image_size = image_size;
+		}
+	}
+
+	if (fb->cpuaddr) {
+		fbinfo.base = fb->dma_addr;
+		fbinfo.screen_size = image_size;
+		fbinfo.pitch = (info->var.xres * info->var.bits_per_pixel) >> 3;
+
+		ret = rpi_firmware_property_list(fb->fbdev->fw, &fbinfo,
+						 sizeof(fbinfo));
+		if (ret || fbinfo.base != fb->dma_addr) {
+			/* Firmware either failed, or assigned a different base
+			 * address (ie it doesn't support being passed an FB
+			 * allocation).
+			 * Destroy the allocation, and don't try again.
+			 */
+			dma_free_coherent(info->device, fb->image_size,
+					  fb->cpuaddr, fb->dma_addr);
+			fb->image_size = 0;
+			fb->cpuaddr = NULL;
+			fb->dma_addr = 0;
+			fb->fbdev->disable_arm_alloc = true;
+		}
+	} else {
+		/* Our allocation failed - drop into the old scheme of
+		 * allocation by the VPU.
+		 */
+		ret = -ENOMEM;
+	}
+
+	if (ret) {
+		/* Old scheme:
+		 * - FRAMEBUFFER_ALLOCATE passes 0 for base and screen_size.
+		 * - GET_PITCH instead of SET_PITCH.
+		 */
+		fbinfo.base = 0;
+		fbinfo.screen_size = 0;
+		fbinfo.tag6.tag = RPI_FIRMWARE_FRAMEBUFFER_GET_PITCH;
+		fbinfo.pitch = 0;
+
+		ret = rpi_firmware_property_list(fb->fbdev->fw, &fbinfo,
+						 sizeof(fbinfo));
+		if (ret) {
+			dev_err(info->device,
+				"Failed to allocate GPU framebuffer (%d)\n",
+				ret);
+			return ret;
+		}
+	}
+
+	if (info->var.bits_per_pixel <= 8)
+		fb->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR;
+	else
+		fb->fb.fix.visual = FB_VISUAL_TRUECOLOR;
+
+	fb->fb.fix.line_length = fbinfo.pitch;
+	fbinfo.base |= 0x40000000;
+	fb->fb_bus_address = fbinfo.base;
+	fbinfo.base &= ~0xc0000000;
+	fb->fb.fix.smem_start = fbinfo.base;
+	fb->fb.fix.smem_len = fbinfo.pitch * fbinfo.yres_virtual;
+	fb->fb.screen_size = fbinfo.screen_size;
+
+	if (!fb->dma_addr) {
+		if (fb->fb.screen_base)
+			iounmap(fb->fb.screen_base);
+
+		fb->fb.screen_base = ioremap_wc(fbinfo.base,
+						fb->fb.screen_size);
+	} else {
+		fb->fb.screen_base = fb->cpuaddr;
+	}
+
+	if (!fb->fb.screen_base) {
+		/* the console may currently be locked */
+		console_trylock();
+		console_unlock();
+		dev_err(info->device, "Failed to set screen_base\n");
+		return -ENOMEM;
+	}
+
+	print_debug("%s: start = %p,%p width=%d, height=%d, bpp=%d, pitch=%d size=%d\n",
+		    __func__, (void *)fb->fb.screen_base,
+		    (void *)fb->fb_bus_address, fbinfo.xres, fbinfo.yres,
+		    fbinfo.bpp, fbinfo.pitch, (int)fb->fb.screen_size);
+
+	return 0;
+}
+
+static inline u32 convert_bitfield(int val, struct fb_bitfield *bf)
+{
+	unsigned int mask = (1 << bf->length) - 1;
+
+	return (val >> (16 - bf->length) & mask) << bf->offset;
+}
+
+static int bcm2708_fb_setcolreg(unsigned int regno, unsigned int red,
+				unsigned int green, unsigned int blue,
+				unsigned int transp, struct fb_info *info)
+{
+	struct bcm2708_fb *fb = to_bcm2708(info);
+
+	if (fb->fb.var.bits_per_pixel <= 8) {
+		if (regno < 256) {
+			/* blue [23:16], green [15:8], red [7:0] */
+			fb->gpu_cmap[regno] = ((red   >> 8) & 0xff) << 0 |
+					      ((green >> 8) & 0xff) << 8 |
+					      ((blue  >> 8) & 0xff) << 16;
+		}
+		/* Hack: we need to tell GPU the palette has changed, but
+		 * currently bcm2708_fb_set_par takes noticeable time when
+		 * called for every (256) colour
+		 * So just call it for what looks like the last colour in a
+		 * list for now.
+		 */
+		if (regno == 15 || regno == 255) {
+			struct packet {
+				u32 offset;
+				u32 length;
+				u32 cmap[256];
+			} *packet;
+			int ret;
+
+			packet = kmalloc(sizeof(*packet), GFP_KERNEL);
+			if (!packet)
+				return -ENOMEM;
+			packet->offset = 0;
+			packet->length = regno + 1;
+			memcpy(packet->cmap, fb->gpu_cmap,
+			       sizeof(packet->cmap));
+
+			set_display_num(fb);
+
+			ret = rpi_firmware_property(fb->fbdev->fw,
+						    RPI_FIRMWARE_FRAMEBUFFER_SET_PALETTE,
+						    packet,
+						    (2 + packet->length) * sizeof(u32));
+			if (ret || packet->offset)
+				dev_err(info->device,
+					"Failed to set palette (%d,%u)\n",
+					ret, packet->offset);
+			kfree(packet);
+		}
+	} else if (regno < 16) {
+		fb->cmap[regno] = convert_bitfield(transp, &fb->fb.var.transp) |
+				  convert_bitfield(blue, &fb->fb.var.blue) |
+				  convert_bitfield(green, &fb->fb.var.green) |
+				  convert_bitfield(red, &fb->fb.var.red);
+	}
+	return regno > 255;
+}
+
+static int bcm2708_fb_blank(int blank_mode, struct fb_info *info)
+{
+	struct bcm2708_fb *fb = to_bcm2708(info);
+	u32 value;
+	int ret;
+
+	switch (blank_mode) {
+	case FB_BLANK_UNBLANK:
+		value = 0;
+		break;
+	case FB_BLANK_NORMAL:
+	case FB_BLANK_VSYNC_SUSPEND:
+	case FB_BLANK_HSYNC_SUSPEND:
+	case FB_BLANK_POWERDOWN:
+		value = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	set_display_num(fb);
+
+	ret = rpi_firmware_property(fb->fbdev->fw, RPI_FIRMWARE_FRAMEBUFFER_BLANK,
+				    &value, sizeof(value));
+
+	if (ret)
+		dev_err(info->device, "%s(%d) failed: %d\n", __func__,
+			blank_mode, ret);
+
+	return ret;
+}
+
+static int bcm2708_fb_pan_display(struct fb_var_screeninfo *var,
+				  struct fb_info *info)
+{
+	s32 result;
+
+	info->var.xoffset = var->xoffset;
+	info->var.yoffset = var->yoffset;
+	result = bcm2708_fb_set_par(info);
+	if (result != 0)
+		pr_err("%s(%u,%u) returns=%d\n", __func__, var->xoffset,
+		       var->yoffset, result);
+	return result;
+}
+
+static void dma_memcpy(struct bcm2708_fb *fb, dma_addr_t dst, dma_addr_t src,
+		       int size)
+{
+	struct bcm2708_fb_dev *fbdev = fb->fbdev;
+	struct bcm2708_dma_cb *cb = fbdev->cb_base;
+	int burst_size = (fbdev->dma_chan == 0) ? 8 : 2;
+
+	cb->info = BCM2708_DMA_BURST(burst_size) | BCM2708_DMA_S_WIDTH |
+		   BCM2708_DMA_S_INC | BCM2708_DMA_D_WIDTH |
+		   BCM2708_DMA_D_INC;
+	cb->dst = dst;
+	cb->src = src;
+	cb->length = size;
+	cb->stride = 0;
+	cb->pad[0] = 0;
+	cb->pad[1] = 0;
+	cb->next = 0;
+
+	// Not sure what to do if this gets a signal whilst waiting
+	if (mutex_lock_interruptible(&fbdev->dma_mutex))
+		return;
+
+	if (size < dma_busy_wait_threshold) {
+		bcm_dma_start(fbdev->dma_chan_base, fbdev->cb_handle);
+		bcm_dma_wait_idle(fbdev->dma_chan_base);
+	} else {
+		void __iomem *local_dma_chan = fbdev->dma_chan_base;
+
+		cb->info |= BCM2708_DMA_INT_EN;
+		bcm_dma_start(fbdev->dma_chan_base, fbdev->cb_handle);
+		while (bcm_dma_is_busy(local_dma_chan)) {
+			wait_event_interruptible(fbdev->dma_waitq,
+						 !bcm_dma_is_busy(local_dma_chan));
+		}
+		fbdev->dma_stats.dma_irqs++;
+	}
+	fbdev->dma_stats.dma_copies++;
+
+	mutex_unlock(&fbdev->dma_mutex);
+}
+
+/* address with no aliases */
+#define INTALIAS_NORMAL(x) ((x) & ~0xc0000000)
+/* cache coherent but non-allocating in L1 and L2 */
+#define INTALIAS_L1L2_NONALLOCATING(x) (((x) & ~0xc0000000) | 0x80000000)
+
+static long vc_mem_copy(struct bcm2708_fb *fb, struct fb_dmacopy *ioparam)
+{
+	size_t size = PAGE_SIZE;
+	u32 *buf = NULL;
+	dma_addr_t bus_addr;
+	long rc = 0;
+	size_t offset;
+
+	/* restrict this to root user */
+	if (!uid_eq(current_euid(), GLOBAL_ROOT_UID)) {
+		rc = -EFAULT;
+		goto out;
+	}
+
+	if (!fb->gpu.base || !fb->gpu.length) {
+		pr_err("[%s]: Unable to determine gpu memory (%x,%x)\n",
+		       __func__, fb->gpu.base, fb->gpu.length);
+		return -EFAULT;
+	}
+
+	if (INTALIAS_NORMAL(ioparam->src) < fb->gpu.base ||
+	    INTALIAS_NORMAL(ioparam->src) >= fb->gpu.base + fb->gpu.length) {
+		pr_err("[%s]: Invalid memory access %x (%x-%x)", __func__,
+		       INTALIAS_NORMAL(ioparam->src), fb->gpu.base,
+		       fb->gpu.base + fb->gpu.length);
+		return -EFAULT;
+	}
+
+	buf = dma_alloc_coherent(fb->fb.device, PAGE_ALIGN(size), &bus_addr,
+				 GFP_ATOMIC);
+	if (!buf) {
+		pr_err("[%s]: failed to dma_alloc_coherent(%zd)\n", __func__,
+		       size);
+		rc = -ENOMEM;
+		goto out;
+	}
+
+	for (offset = 0; offset < ioparam->length; offset += size) {
+		size_t remaining = ioparam->length - offset;
+		size_t s = min(size, remaining);
+		u8 *p = (u8 *)((uintptr_t)ioparam->src + offset);
+		u8 *q = (u8 *)ioparam->dst + offset;
+
+		dma_memcpy(fb, bus_addr,
+			   INTALIAS_L1L2_NONALLOCATING((u32)(uintptr_t)p),
+						       size);
+		if (copy_to_user(q, buf, s) != 0) {
+			pr_err("[%s]: failed to copy-to-user\n", __func__);
+			rc = -EFAULT;
+			goto out;
+		}
+	}
+out:
+	if (buf)
+		dma_free_coherent(fb->fb.device, PAGE_ALIGN(size), buf,
+				  bus_addr);
+	return rc;
+}
+
+static int bcm2708_ioctl(struct fb_info *info, unsigned int cmd,
+			 unsigned long arg)
+{
+	struct bcm2708_fb *fb = to_bcm2708(info);
+	u32 dummy = 0;
+	int ret;
+
+	switch (cmd) {
+	case FBIO_WAITFORVSYNC:
+		set_display_num(fb);
+
+		ret = rpi_firmware_property(fb->fbdev->fw,
+					    RPI_FIRMWARE_FRAMEBUFFER_SET_VSYNC,
+					    &dummy, sizeof(dummy));
+		break;
+
+	case FBIODMACOPY:
+	{
+		struct fb_dmacopy ioparam;
+		/* Get the parameter data.
+		 */
+		if (copy_from_user
+		    (&ioparam, (void *)arg, sizeof(ioparam))) {
+			pr_err("[%s]: failed to copy-from-user\n", __func__);
+			ret = -EFAULT;
+			break;
+		}
+		ret = vc_mem_copy(fb, &ioparam);
+		break;
+	}
+	default:
+		dev_dbg(info->device, "Unknown ioctl 0x%x\n", cmd);
+		return -ENOTTY;
+	}
+
+	if (ret)
+		dev_err(info->device, "ioctl 0x%x failed (%d)\n", cmd, ret);
+
+	return ret;
+}
+
+#ifdef CONFIG_COMPAT
+struct fb_dmacopy32 {
+	compat_uptr_t dst;
+	__u32 src;
+	__u32 length;
+};
+
+#define FBIODMACOPY32		_IOW('z', 0x22, struct fb_dmacopy32)
+
+static int bcm2708_compat_ioctl(struct fb_info *info, unsigned int cmd,
+				unsigned long arg)
+{
+	struct bcm2708_fb *fb = to_bcm2708(info);
+	int ret;
+
+	switch (cmd) {
+	case FBIODMACOPY32:
+	{
+		struct fb_dmacopy32 param32;
+		struct fb_dmacopy param;
+		/* Get the parameter data.
+		 */
+		if (copy_from_user(&param32, (void *)arg, sizeof(param32))) {
+			pr_err("[%s]: failed to copy-from-user\n", __func__);
+			ret = -EFAULT;
+			break;
+		}
+		param.dst = compat_ptr(param32.dst);
+		param.src = param32.src;
+		param.length = param32.length;
+		ret = vc_mem_copy(fb, &param);
+		break;
+	}
+	default:
+		ret = bcm2708_ioctl(info, cmd, arg);
+		break;
+	}
+	return ret;
+}
+#endif
+
+/* A helper function for configuring dma control block */
+static void set_dma_cb(struct bcm2708_dma_cb *cb,
+		int        burst_size,
+		dma_addr_t dst,
+		int        dst_stride,
+		dma_addr_t src,
+		int        src_stride,
+		int        w,
+		int        h)
+{
+	cb->info = BCM2708_DMA_BURST(burst_size) | BCM2708_DMA_S_WIDTH |
+		BCM2708_DMA_S_INC | BCM2708_DMA_D_WIDTH |
+		BCM2708_DMA_D_INC | BCM2708_DMA_TDMODE;
+	cb->dst = dst;
+	cb->src = src;
+	/*
+	 * This is not really obvious from the DMA documentation,
+	 * but the top 16 bits must be programmmed to "height -1"
+	 * and not "height" in 2D mode.
+	 */
+	cb->length = ((h - 1) << 16) | w;
+	cb->stride = ((dst_stride - w) << 16) | (u16)(src_stride - w);
+	cb->pad[0] = 0;
+	cb->pad[1] = 0;
+}
+
+static void bcm2708_fb_copyarea(struct fb_info *info,
+				const struct fb_copyarea *region)
+{
+	struct bcm2708_fb *fb = to_bcm2708(info);
+	struct bcm2708_fb_dev *fbdev = fb->fbdev;
+	struct bcm2708_dma_cb *cb = fbdev->cb_base;
+	int bytes_per_pixel = (info->var.bits_per_pixel + 7) >> 3;
+
+	/* Channel 0 supports larger bursts and is a bit faster */
+	int burst_size = (fbdev->dma_chan == 0) ? 8 : 2;
+	int pixels = region->width * region->height;
+
+	/* If DMA is currently in use (ie being used on another FB), then
+	 * rather than wait for it to finish, just use the cfb_copyarea
+	 */
+	if (!mutex_trylock(&fbdev->dma_mutex) ||
+	    bytes_per_pixel > 4 ||
+	    info->var.xres * info->var.yres > 1920 * 1200 ||
+	    region->width <= 0 || region->width > info->var.xres ||
+	    region->height <= 0 || region->height > info->var.yres ||
+	    region->sx < 0 || region->sx >= info->var.xres ||
+	    region->sy < 0 || region->sy >= info->var.yres ||
+	    region->dx < 0 || region->dx >= info->var.xres ||
+	    region->dy < 0 || region->dy >= info->var.yres ||
+	    region->sx + region->width > info->var.xres ||
+	    region->dx + region->width > info->var.xres ||
+	    region->sy + region->height > info->var.yres ||
+	    region->dy + region->height > info->var.yres) {
+		cfb_copyarea(info, region);
+		return;
+	}
+
+	if (region->dy == region->sy && region->dx > region->sx) {
+		/*
+		 * A difficult case of overlapped copy. Because DMA can't
+		 * copy individual scanlines in backwards direction, we need
+		 * two-pass processing. We do it by programming a chain of dma
+		 * control blocks in the first 16K part of the buffer and use
+		 * the remaining 48K as the intermediate temporary scratch
+		 * buffer. The buffer size is sufficient to handle up to
+		 * 1920x1200 resolution at 32bpp pixel depth.
+		 */
+		int y;
+		dma_addr_t control_block_pa = fbdev->cb_handle;
+		dma_addr_t scratchbuf = fbdev->cb_handle + 16 * 1024;
+		int scanline_size = bytes_per_pixel * region->width;
+		int scanlines_per_cb = (64 * 1024 - 16 * 1024) / scanline_size;
+
+		for (y = 0; y < region->height; y += scanlines_per_cb) {
+			dma_addr_t src =
+				fb->fb_bus_address +
+				bytes_per_pixel * region->sx +
+				(region->sy + y) * fb->fb.fix.line_length;
+			dma_addr_t dst =
+				fb->fb_bus_address +
+				bytes_per_pixel * region->dx +
+				(region->dy + y) * fb->fb.fix.line_length;
+
+			if (region->height - y < scanlines_per_cb)
+				scanlines_per_cb = region->height - y;
+
+			set_dma_cb(cb, burst_size, scratchbuf, scanline_size,
+				   src, fb->fb.fix.line_length,
+				   scanline_size, scanlines_per_cb);
+			control_block_pa += sizeof(struct bcm2708_dma_cb);
+			cb->next = control_block_pa;
+			cb++;
+
+			set_dma_cb(cb, burst_size, dst, fb->fb.fix.line_length,
+				   scratchbuf, scanline_size,
+				   scanline_size, scanlines_per_cb);
+			control_block_pa += sizeof(struct bcm2708_dma_cb);
+			cb->next = control_block_pa;
+			cb++;
+		}
+		/* move the pointer back to the last dma control block */
+		cb--;
+	} else {
+		/* A single dma control block is enough. */
+		int sy, dy, stride;
+
+		if (region->dy <= region->sy) {
+			/* processing from top to bottom */
+			dy = region->dy;
+			sy = region->sy;
+			stride = fb->fb.fix.line_length;
+		} else {
+			/* processing from bottom to top */
+			dy = region->dy + region->height - 1;
+			sy = region->sy + region->height - 1;
+			stride = -fb->fb.fix.line_length;
+		}
+		set_dma_cb(cb, burst_size,
+			   fb->fb_bus_address + dy * fb->fb.fix.line_length +
+			   bytes_per_pixel * region->dx,
+			   stride,
+			   fb->fb_bus_address + sy * fb->fb.fix.line_length +
+			   bytes_per_pixel * region->sx,
+			   stride,
+			   region->width * bytes_per_pixel,
+			   region->height);
+	}
+
+	/* end of dma control blocks chain */
+	cb->next = 0;
+
+	if (pixels < dma_busy_wait_threshold) {
+		bcm_dma_start(fbdev->dma_chan_base, fbdev->cb_handle);
+		bcm_dma_wait_idle(fbdev->dma_chan_base);
+	} else {
+		void __iomem *local_dma_chan = fbdev->dma_chan_base;
+
+		cb->info |= BCM2708_DMA_INT_EN;
+		bcm_dma_start(fbdev->dma_chan_base, fbdev->cb_handle);
+		while (bcm_dma_is_busy(local_dma_chan)) {
+			wait_event_interruptible(fbdev->dma_waitq,
+						 !bcm_dma_is_busy(local_dma_chan));
+		}
+		fbdev->dma_stats.dma_irqs++;
+	}
+	fbdev->dma_stats.dma_copies++;
+
+	mutex_unlock(&fbdev->dma_mutex);
+}
+
+static irqreturn_t bcm2708_fb_dma_irq(int irq, void *cxt)
+{
+	struct bcm2708_fb_dev *fbdev = cxt;
+
+	/* FIXME: should read status register to check if this is
+	 * actually interrupting us or not, in case this interrupt
+	 * ever becomes shared amongst several DMA channels
+	 *
+	 * readl(dma_chan_base + BCM2708_DMA_CS) & BCM2708_DMA_IRQ;
+	 */
+
+	/* acknowledge the interrupt */
+	writel(BCM2708_DMA_INT, fbdev->dma_chan_base + BCM2708_DMA_CS);
+
+	wake_up(&fbdev->dma_waitq);
+	return IRQ_HANDLED;
+}
+
+static struct fb_ops bcm2708_fb_ops = {
+	.owner = THIS_MODULE,
+	__FB_DEFAULT_IOMEM_OPS_RDWR,
+	.fb_check_var = bcm2708_fb_check_var,
+	.fb_set_par = bcm2708_fb_set_par,
+	.fb_setcolreg = bcm2708_fb_setcolreg,
+	.fb_blank = bcm2708_fb_blank,
+	.fb_fillrect = cfb_fillrect,
+	.fb_copyarea = bcm2708_fb_copyarea,
+	.fb_imageblit = cfb_imageblit,
+	.fb_pan_display = bcm2708_fb_pan_display,
+	.fb_ioctl = bcm2708_ioctl,
+#ifdef CONFIG_COMPAT
+	.fb_compat_ioctl = bcm2708_compat_ioctl,
+#endif
+	__FB_DEFAULT_IOMEM_OPS_MMAP,
+};
+
+static int bcm2708_fb_register(struct bcm2708_fb *fb)
+{
+	int ret;
+
+	fb->fb.fbops = &bcm2708_fb_ops;
+	fb->fb.flags = FBINFO_HWACCEL_COPYAREA;
+	fb->fb.pseudo_palette = fb->cmap;
+
+	strncpy(fb->fb.fix.id, bcm2708_name, sizeof(fb->fb.fix.id));
+	fb->fb.fix.type = FB_TYPE_PACKED_PIXELS;
+	fb->fb.fix.type_aux = 0;
+	fb->fb.fix.xpanstep = 1;
+	fb->fb.fix.ypanstep = 1;
+	fb->fb.fix.ywrapstep = 0;
+	fb->fb.fix.accel = FB_ACCEL_NONE;
+
+	/* If we have data from the VC4 on FB's, use that, otherwise use the
+	 * module parameters
+	 */
+	if (fb->display_settings.width) {
+		fb->fb.var.xres = fb->display_settings.width;
+		fb->fb.var.yres = fb->display_settings.height;
+		fb->fb.var.xres_virtual = fb->fb.var.xres;
+		fb->fb.var.yres_virtual = fb->fb.var.yres;
+		fb->fb.var.bits_per_pixel = fb->display_settings.depth;
+	} else {
+		fb->fb.var.xres = fbwidth;
+		fb->fb.var.yres = fbheight;
+		fb->fb.var.xres_virtual = fbwidth;
+		fb->fb.var.yres_virtual = fbheight;
+		fb->fb.var.bits_per_pixel = fbdepth;
+	}
+
+	fb->fb.var.vmode = FB_VMODE_NONINTERLACED;
+	fb->fb.var.activate = FB_ACTIVATE_NOW;
+	fb->fb.var.nonstd = 0;
+	fb->fb.var.height = -1;		/* height of picture in mm    */
+	fb->fb.var.width = -1;		/* width of picture in mm    */
+	fb->fb.var.accel_flags = 0;
+
+	fb->fb.monspecs.hfmin = 0;
+	fb->fb.monspecs.hfmax = 100000;
+	fb->fb.monspecs.vfmin = 0;
+	fb->fb.monspecs.vfmax = 400;
+	fb->fb.monspecs.dclkmin = 1000000;
+	fb->fb.monspecs.dclkmax = 100000000;
+
+	bcm2708_fb_set_bitfields(&fb->fb.var);
+
+	/*
+	 * Allocate colourmap.
+	 */
+	fb_set_var(&fb->fb, &fb->fb.var);
+
+	ret = bcm2708_fb_set_par(&fb->fb);
+
+	if (ret)
+		return ret;
+
+	ret = register_framebuffer(&fb->fb);
+
+	if (ret == 0)
+		goto out;
+
+	dev_warn(fb->fb.dev, "Unable to register framebuffer (%d)\n", ret);
+out:
+	return ret;
+}
+
+static int bcm2708_fb_probe(struct platform_device *dev)
+{
+	struct device_node *fw_np;
+	struct rpi_firmware *fw;
+	int ret, i;
+	u32 num_displays;
+	struct bcm2708_fb_dev *fbdev;
+	struct { u32 base, length; } gpu_mem;
+
+	fbdev = devm_kzalloc(&dev->dev, sizeof(*fbdev), GFP_KERNEL);
+
+	if (!fbdev)
+		return -ENOMEM;
+
+	fw_np = of_parse_phandle(dev->dev.of_node, "firmware", 0);
+
+/* Remove comment when booting without Device Tree is no longer supported
+ *	if (!fw_np) {
+ *		dev_err(&dev->dev, "Missing firmware node\n");
+ *		return -ENOENT;
+ *	}
+ */
+	fw = rpi_firmware_get(fw_np);
+	fbdev->fw = fw;
+
+	if (!fw)
+		return -EPROBE_DEFER;
+
+	ret = rpi_firmware_property(fw,
+				    RPI_FIRMWARE_FRAMEBUFFER_GET_NUM_DISPLAYS,
+				    &num_displays, sizeof(u32));
+
+	/* If we fail to get the number of displays, or it returns 0, then
+	 * assume old firmware that doesn't have the mailbox call, so just
+	 * set one display
+	 */
+	if (ret || num_displays == 0) {
+		dev_err(&dev->dev,
+			"Unable to determine number of FBs. Disabling driver.\n");
+		return -ENOENT;
+	} else {
+		fbdev->firmware_supports_multifb = 1;
+	}
+
+	if (num_displays > MAX_FRAMEBUFFERS) {
+		dev_warn(&dev->dev,
+			 "More displays reported from firmware than supported in driver (%u vs %u)",
+			 num_displays, MAX_FRAMEBUFFERS);
+		num_displays = MAX_FRAMEBUFFERS;
+	}
+
+	dev_info(&dev->dev, "FB found %d display(s)\n", num_displays);
+
+	/* Set up the DMA information. Note we have just one set of DMA
+	 * parameters to work with all the FB's so requires synchronising when
+	 * being used
+	 */
+
+	mutex_init(&fbdev->dma_mutex);
+
+	fbdev->cb_base = dma_alloc_wc(&dev->dev, SZ_64K,
+						&fbdev->cb_handle,
+						GFP_KERNEL);
+	if (!fbdev->cb_base) {
+		dev_err(&dev->dev, "cannot allocate DMA CBs\n");
+		ret = -ENOMEM;
+		goto free_fb;
+	}
+
+	ret = bcm_dma_chan_alloc(BCM_DMA_FEATURE_BULK,
+				 &fbdev->dma_chan_base,
+				 &fbdev->dma_irq);
+	if (ret < 0) {
+		dev_err(&dev->dev, "Couldn't allocate a DMA channel\n");
+		goto free_cb;
+	}
+	fbdev->dma_chan = ret;
+
+	ret = request_irq(fbdev->dma_irq, bcm2708_fb_dma_irq,
+			  0, "bcm2708_fb DMA", fbdev);
+	if (ret) {
+		dev_err(&dev->dev,
+			"Failed to request DMA irq\n");
+		goto free_dma_chan;
+	}
+
+	rpi_firmware_property(fbdev->fw,
+			      RPI_FIRMWARE_GET_VC_MEMORY,
+			      &gpu_mem, sizeof(gpu_mem));
+
+	for (i = 0; i < num_displays; i++) {
+		struct bcm2708_fb *fb = &fbdev->displays[i];
+
+		fb->display_settings.display_num = i;
+		fb->dev = dev;
+		fb->fb.device = &dev->dev;
+		fb->fbdev = fbdev;
+
+		fb->gpu.base = gpu_mem.base;
+		fb->gpu.length = gpu_mem.length;
+
+		if (fbdev->firmware_supports_multifb) {
+			ret = rpi_firmware_property(fw,
+						    RPI_FIRMWARE_FRAMEBUFFER_GET_DISPLAY_SETTINGS,
+						    &fb->display_settings,
+						    GET_DISPLAY_SETTINGS_PAYLOAD_SIZE);
+		} else {
+			memset(&fb->display_settings, 0,
+			       sizeof(fb->display_settings));
+		}
+
+		ret = bcm2708_fb_register(fb);
+
+		if (ret == 0) {
+			bcm2708_fb_debugfs_init(fb);
+
+			fbdev->num_displays++;
+
+			dev_info(&dev->dev,
+				 "Registered framebuffer for display %u, size %ux%u\n",
+				 fb->display_settings.display_num,
+				 fb->fb.var.xres,
+				 fb->fb.var.yres);
+		} else {
+			// Use this to flag if this FB entry is in use.
+			fb->fbdev = NULL;
+		}
+	}
+
+	// Did we actually successfully create any FB's?
+	if (fbdev->num_displays) {
+		init_waitqueue_head(&fbdev->dma_waitq);
+		platform_set_drvdata(dev, fbdev);
+		return ret;
+	}
+
+free_dma_chan:
+	bcm_dma_chan_free(fbdev->dma_chan);
+free_cb:
+	dma_free_wc(&dev->dev, SZ_64K, fbdev->cb_base,
+			      fbdev->cb_handle);
+free_fb:
+	dev_err(&dev->dev, "probe failed, err %d\n", ret);
+
+	return ret;
+}
+
+static void bcm2708_fb_remove(struct platform_device *dev)
+{
+	struct bcm2708_fb_dev *fbdev = platform_get_drvdata(dev);
+	int i;
+
+	platform_set_drvdata(dev, NULL);
+
+	for (i = 0; i < fbdev->num_displays; i++) {
+		if (fbdev->displays[i].fb.screen_base)
+			iounmap(fbdev->displays[i].fb.screen_base);
+
+		if (fbdev->displays[i].fbdev) {
+			unregister_framebuffer(&fbdev->displays[i].fb);
+			bcm2708_fb_debugfs_deinit(&fbdev->displays[i]);
+		}
+	}
+
+	dma_free_wc(&dev->dev, SZ_64K, fbdev->cb_base,
+			      fbdev->cb_handle);
+	bcm_dma_chan_free(fbdev->dma_chan);
+	free_irq(fbdev->dma_irq, fbdev);
+
+	mutex_destroy(&fbdev->dma_mutex);
+}
+
+static const struct of_device_id bcm2708_fb_of_match_table[] = {
+	{ .compatible = "brcm,bcm2708-fb", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, bcm2708_fb_of_match_table);
+
+static struct platform_driver bcm2708_fb_driver = {
+	.probe = bcm2708_fb_probe,
+	.remove = bcm2708_fb_remove,
+	.driver = {
+		  .name = DRIVER_NAME,
+		  .owner = THIS_MODULE,
+		  .of_match_table = bcm2708_fb_of_match_table,
+		  },
+};
+
+static int __init bcm2708_fb_init(void)
+{
+	return platform_driver_register(&bcm2708_fb_driver);
+}
+
+module_init(bcm2708_fb_init);
+
+static void __exit bcm2708_fb_exit(void)
+{
+	platform_driver_unregister(&bcm2708_fb_driver);
+}
+
+module_exit(bcm2708_fb_exit);
+
+module_param(fbwidth, int, 0644);
+module_param(fbheight, int, 0644);
+module_param(fbdepth, int, 0644);
+module_param(fbswap, int, 0644);
+
+MODULE_DESCRIPTION("BCM2708 framebuffer driver");
+MODULE_LICENSE("GPL");
+
+MODULE_PARM_DESC(fbwidth, "Width of ARM Framebuffer");
+MODULE_PARM_DESC(fbheight, "Height of ARM Framebuffer");
+MODULE_PARM_DESC(fbdepth, "Bit depth of ARM Framebuffer");
+MODULE_PARM_DESC(fbswap, "Swap order of red and blue in 24 and 32 bit modes");
diff --git a/drivers/video/fbdev/core/fb_chrdev.c b/drivers/video/fbdev/core/fb_chrdev.c
index 4ebd16b7e3b8..e3dd94bb7ef5 100644
--- a/drivers/video/fbdev/core/fb_chrdev.c
+++ b/drivers/video/fbdev/core/fb_chrdev.c
@@ -59,6 +59,30 @@ static ssize_t fb_write(struct file *file, const char __user *buf, size_t count,
 	return info->fbops->fb_write(info, buf, count, ppos);
 }
 
+static int fb_copyarea_user(struct fb_info *info,
+			    struct fb_copyarea *copy)
+{
+	int ret = 0;
+	lock_fb_info(info);
+	if (copy->dx >= info->var.xres ||
+	    copy->sx >= info->var.xres ||
+	    copy->width > info->var.xres ||
+	    copy->dy >= info->var.yres ||
+	    copy->sy >= info->var.yres ||
+	    copy->height > info->var.yres ||
+	    copy->dx + copy->width > info->var.xres ||
+	    copy->sx + copy->width > info->var.xres ||
+	    copy->dy + copy->height > info->var.yres ||
+	    copy->sy + copy->height > info->var.yres) {
+		ret = -EINVAL;
+		goto out;
+	}
+	info->fbops->fb_copyarea(info, copy);
+out:
+	unlock_fb_info(info);
+	return ret;
+}
+
 static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
 			unsigned long arg)
 {
@@ -67,6 +91,7 @@ static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
 	struct fb_fix_screeninfo fix;
 	struct fb_cmap cmap_from;
 	struct fb_cmap_user cmap;
+	struct fb_copyarea copy;
 	void __user *argp = (void __user *)arg;
 	long ret = 0;
 
@@ -148,6 +173,15 @@ static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
 		unlock_fb_info(info);
 		console_unlock();
 		break;
+	case FBIOCOPYAREA:
+		if (info->flags & FBINFO_HWACCEL_COPYAREA) {
+			/* only provide this ioctl if it is accelerated */
+			if (copy_from_user(&copy, argp, sizeof(copy)))
+				return -EFAULT;
+			ret = fb_copyarea_user(info, &copy);
+			break;
+		}
+	fallthrough;
 	default:
 		lock_fb_info(info);
 		fb = info->fbops;
@@ -287,6 +321,7 @@ static long fb_compat_ioctl(struct file *file, unsigned int cmd,
 	case FBIOPAN_DISPLAY:
 	case FBIOGET_CON2FBMAP:
 	case FBIOPUT_CON2FBMAP:
+	case FBIOCOPYAREA:
 		arg = (unsigned long) compat_ptr(arg);
 		fallthrough;
 	case FBIOBLANK:
diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
index 65363df8e81b..605632f7c245 100644
--- a/drivers/video/fbdev/core/fb_defio.c
+++ b/drivers/video/fbdev/core/fb_defio.c
@@ -346,7 +346,8 @@ static void fb_deferred_io_lastclose(struct fb_info *info)
 {
 	unsigned long i;
 
-	flush_delayed_work(&info->deferred_work);
+	if (!list_empty(&info->fbdefio->pagereflist))
+		flush_delayed_work(&info->deferred_work);
 
 	/* clear out the mapping that we setup */
 	for (i = 0; i < info->npagerefs; ++i)
diff --git a/drivers/video/fbdev/core/fb_logo.c b/drivers/video/fbdev/core/fb_logo.c
index 0bab8352b684..566692884495 100644
--- a/drivers/video/fbdev/core/fb_logo.c
+++ b/drivers/video/fbdev/core/fb_logo.c
@@ -2,11 +2,24 @@
 
 #include <linux/fb.h>
 #include <linux/linux_logo.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <linux/file.h>
+#include <linux/kernel.h>
+#include <linux/firmware.h>
 
 #include "fb_internal.h"
 
 bool fb_center_logo __read_mostly;
 int fb_logo_count __read_mostly = -1;
+static int fullscreen_logo_enabled;
+static char *fullscreen_logo_path;
+
+struct image_palette {
+	u8 colors[224][3];
+};
 
 static inline unsigned int safe_shift(unsigned int d, int n)
 {
@@ -79,6 +92,32 @@ static void  fb_set_logo_truepalette(struct fb_info *info,
 	}
 }
 
+static void fb_set_logo_RGB_palette(struct fb_info *info,
+				    struct image_palette *pal_in,
+				    u32 *pal_out, int current_rows)
+{
+	// Set the kernel palette from an array of RGB values
+	static const unsigned char mask[] = {
+		0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff
+	};
+	unsigned char redmask, greenmask, bluemask;
+	int redshift, greenshift, blueshift;
+	int i;
+
+	redmask   = mask[info->var.red.length   < 8 ? info->var.red.length   : 8];
+	greenmask = mask[info->var.green.length < 8 ? info->var.green.length : 8];
+	bluemask  = mask[info->var.blue.length  < 8 ? info->var.blue.length  : 8];
+	redshift   = info->var.red.offset   - (8 - info->var.red.length);
+	greenshift = info->var.green.offset - (8 - info->var.green.length);
+	blueshift  = info->var.blue.offset  - (8 - info->var.blue.length);
+
+	for (i = 0; i < current_rows; i++) {
+		pal_out[i + 32] = (safe_shift((pal_in->colors[i][0] & redmask), redshift) |
+				   safe_shift((pal_in->colors[i][1] & greenmask), greenshift) |
+				   safe_shift((pal_in->colors[i][2] & bluemask), blueshift));
+	}
+}
+
 static void fb_set_logo_directpalette(struct fb_info *info,
 					     const struct linux_logo *logo,
 					     u32 *palette)
@@ -275,6 +314,171 @@ static void fb_do_show_logo(struct fb_info *info, struct fb_image *image,
 	}
 }
 
+static int __init fb_fullscreen_logo_setup(char *str)
+{
+	fullscreen_logo_enabled = 1;
+	fullscreen_logo_path = str;
+	pr_info("Fullscreen splash enabled, using image path: %s", fullscreen_logo_path);
+	return 1;
+}
+
+__setup("fullscreen_logo_name=", fb_fullscreen_logo_setup);
+
+static bool fb_palette_contains_entry(struct image_palette *palette, int num_existing_rows,
+					   u8 *entry_to_add, int cols, int *index)
+{
+	for (int i = 0; i < num_existing_rows; i++) {
+		bool match = true;
+
+		for (int j = 0; j < cols; j++) {
+			if (palette->colors[i][j] != entry_to_add[j]) {
+				match = false;
+				break;
+			}
+		}
+		if (match) {
+			*index = i; // Update the index
+			return true; // Found a duplicate
+		}
+	}
+	return false; // No duplicate found
+}
+
+static void fb_set_logo_from_file(struct fb_info *info, const char *filepath,
+				   struct fb_image *image, u32 *palette)
+{
+	int current_rows = 0, palette_index = 0, actual_row, skip_x = 0, skip_y = 0, ret;
+	unsigned char *read_logo = NULL, *header;
+	const char *file_content;
+	const struct firmware *fw = NULL;
+	struct image_palette image_palette;
+	const char *current_ptr, *end_ptr;
+	long width = 0, height = 0;
+	bool top_to_bottom;
+	u8 B, G, R;
+	u8 entry[3];
+	ssize_t len;
+
+	ret = firmware_request_nowarn(&fw, filepath, info->device);
+	if (ret) {
+		pr_info("Failed to load logo file '%s': %d\n", filepath, ret);
+		goto cleanup;
+	}
+	len = fw->size;
+	file_content = fw->data;
+
+	if (!len) {
+		pr_err("Error: logo TGA file is empty. Not drawing fullscreen logo.\n");
+		goto cleanup;
+	}
+
+	current_ptr = file_content;
+	end_ptr = file_content + len;
+	if (len < 18) {
+		pr_err("Invalid logo file: TGA file too small for header\n");
+		goto cleanup;
+	}
+	header = (unsigned char *)file_content;
+
+	// Skip color map info (bytes 3-7)
+	// Skip image origin (bytes 8-11)
+	width = header[12] | (header[13] << 8);
+	height = header[14] | (header[15] << 8);
+
+	// Only supports uncompressed true-color images (type 2) with 24-bit depth
+	if (header[2] != 2 || header[16] != 24) {
+		pr_err("Unsupported TGA logo format: Type=%d, Depth=%d (only support Type=2, Depth=24)\n",
+		       header[2], header[16]);
+		goto cleanup;
+	}
+	// Skip header + ID field
+	current_ptr = file_content + 18 + header[0];
+
+	read_logo = kmalloc_array(width, height, GFP_KERNEL);
+	if (!read_logo)
+		goto cleanup;
+
+	image->data = read_logo;
+
+	// TGA pixels are stored bottom-to-top by default, unless bit 5 of
+	// image_descriptor is set
+	top_to_bottom = (header[17] & 0x20) != 0;
+	skip_x = 0;
+	skip_y = 0;
+
+	if (width > info->var.xres) {
+		pr_info("Logo is larger than screen (%lu vs %u), clipping horizontally\n",
+			width, info->var.xres);
+		skip_x = (width - info->var.xres) / 2;
+	}
+	if (height > info->var.yres) {
+		pr_info("Logo is larger than screen (%lu vs %u), clipping vertically\n",
+			height, info->var.yres);
+		skip_y = (height - info->var.yres) / 2;
+	}
+	current_ptr += skip_y * width * 3 + skip_x * 3;
+	// Parse pixel data (BGR format in TGA)
+	for (int i = 0; i < height - 2 * skip_y; i++) {
+		for (int j = 0; j < width - 2 * skip_x; j++) {
+			if (current_ptr + 3 > end_ptr) {
+				pr_info("TGA: Unexpected end of file\n");
+				goto cleanup;
+			}
+			B = (unsigned char)*current_ptr++;
+			G = (unsigned char)*current_ptr++;
+			R = (unsigned char)*current_ptr++;
+			entry[0] = R;
+			entry[1] = G;
+			entry[2] = B;
+			palette_index = 0;
+
+			if (!fb_palette_contains_entry(&image_palette, current_rows,
+						       entry, 3, &palette_index)) {
+				if (current_rows < 224) {
+					for (int k = 0; k < 3; k++)
+						image_palette.colors[current_rows][k] =
+								entry[k];
+					palette_index = current_rows;
+				}
+				current_rows++;
+			}
+			actual_row = top_to_bottom ? i : (height - 1 - i);
+
+			read_logo[actual_row * (width - 2 * skip_x) + j] =
+				palette_index + 32;
+		}
+		current_ptr += skip_x * 3 * 2;
+	}
+
+	if (current_rows >= 224)
+		pr_err("Palette overflow. Entries clipped\n");
+
+	// Set logo palette
+	palette = kzalloc(256 * 4, GFP_KERNEL);
+	if (!palette)
+		goto cleanup;
+	fb_set_logo_RGB_palette(info, &image_palette, palette, current_rows);
+	if (info->pseudo_palette)
+		memcpy(palette, info->pseudo_palette, 32 * sizeof(uint32_t));
+	info->pseudo_palette = palette;
+
+	image->width = min_t(unsigned int, width, info->var.xres);
+	image->height = min_t(unsigned int, height, info->var.yres);
+	image->dx = 0;
+	image->dy = 0;
+	image->depth = 8;
+
+	if (image->height < info->var.yres)
+		image->dy = (info->var.yres - image->height) / 2;
+	if (image->width < info->var.xres)
+		image->dx = (info->var.xres - image->width) / 2;
+
+cleanup:
+	kfree(read_logo);
+	release_firmware(fw);
+}
+
+
 static int fb_show_logo_line(struct fb_info *info, int rotate,
 			     const struct linux_logo *logo, int y,
 			     unsigned int n)
@@ -288,66 +492,87 @@ static int fb_show_logo_line(struct fb_info *info, int rotate,
 	    info->fbops->owner)
 		return 0;
 
-	image.depth = 8;
-	image.data = logo->data;
+	if (fullscreen_logo_enabled) {
+		fb_set_logo_from_file(info, fullscreen_logo_path,
+			&image, palette);
+	} else {
+		image.depth = 8;
+		image.data = logo->data;
 
-	if (fb_logo.needs_cmapreset)
-		fb_set_logocmap(info, logo);
+		if (fb_logo.needs_cmapreset)
+			fb_set_logocmap(info, logo);
 
-	if (fb_logo.needs_truepalette ||
-	    fb_logo.needs_directpalette) {
-		palette = kmalloc(256 * 4, GFP_KERNEL);
-		if (palette == NULL)
-			return 0;
+		if (fb_logo.needs_truepalette ||
+			fb_logo.needs_directpalette) {
+			palette = kmalloc(256 * 4, GFP_KERNEL);
+			if (palette == NULL)
+				return 0;
 
-		if (fb_logo.needs_truepalette)
-			fb_set_logo_truepalette(info, logo, palette);
-		else
-			fb_set_logo_directpalette(info, logo, palette);
+			if (fb_logo.needs_truepalette)
+				fb_set_logo_truepalette(info, logo, palette);
+			else
+				fb_set_logo_directpalette(info, logo, palette);
 
-		saved_pseudo_palette = info->pseudo_palette;
-		info->pseudo_palette = palette;
-	}
+			saved_pseudo_palette = info->pseudo_palette;
+			info->pseudo_palette = palette;
+		}
 
-	if (fb_logo.depth <= 4) {
-		logo_new = kmalloc_array(logo->width, logo->height,
-					 GFP_KERNEL);
-		if (logo_new == NULL) {
-			kfree(palette);
-			if (saved_pseudo_palette)
-				info->pseudo_palette = saved_pseudo_palette;
-			return 0;
+		if (fb_logo.depth <= 4) {
+			logo_new = kmalloc_array(logo->width, logo->height,
+						GFP_KERNEL);
+			if (logo_new == NULL) {
+				kfree(palette);
+				if (saved_pseudo_palette)
+					info->pseudo_palette = saved_pseudo_palette;
+				return 0;
+			}
+			image.data = logo_new;
+			fb_set_logo(info, logo, logo_new, fb_logo.depth);
 		}
-		image.data = logo_new;
-		fb_set_logo(info, logo, logo_new, fb_logo.depth);
-	}
 
-	if (fb_center_logo) {
-		int xres = info->var.xres;
-		int yres = info->var.yres;
+		if (fb_center_logo) {
+			int xres = info->var.xres;
+			int yres = info->var.yres;
 
-		if (rotate == FB_ROTATE_CW || rotate == FB_ROTATE_CCW) {
-			xres = info->var.yres;
-			yres = info->var.xres;
-		}
+			if (rotate == FB_ROTATE_CW || rotate == FB_ROTATE_CCW) {
+				xres = info->var.yres;
+				yres = info->var.xres;
+			}
 
-		while (n && (n * (logo->width + 8) - 8 > xres))
-			--n;
-		image.dx = (xres - (n * (logo->width + 8) - 8)) / 2;
-		image.dy = y ?: (yres - logo->height) / 2;
-	} else {
-		image.dx = 0;
-		image.dy = y;
-	}
+			while (n && (n * (logo->width + 8) - 8 > xres))
+				--n;
+			image.dx = (xres - (n * (logo->width + 8) - 8)) / 2;
+			image.dy = y ?: (yres - logo->height) / 2;
+		} else {
+			image.dx = 0;
+			image.dy = y;
+		}
 
-	image.width = logo->width;
-	image.height = logo->height;
+		image.width = logo->width;
+		image.height = logo->height;
 
-	if (rotate) {
-		logo_rotate = kmalloc_array(logo->width, logo->height,
-					    GFP_KERNEL);
-		if (logo_rotate)
-			fb_rotate_logo(info, logo_rotate, &image, rotate);
+		if (rotate) {
+			logo_rotate = kmalloc_array(logo->width, logo->height,
+							GFP_KERNEL);
+			if (logo_rotate)
+				fb_rotate_logo(info, logo_rotate, &image, rotate);
+		}
+	}
+	if (fullscreen_logo_enabled) {
+		// Fullscreen logo data may not fill screen
+		// Fill remainder of screen with border color of logo for continuous feel
+		u32 fill_color = image.data[0];
+		struct fb_fillrect region;
+
+		region.color = fill_color;
+		region.dx = 0;
+		region.dy = 0;
+		region.width = info->var.xres;
+		region.height = info->var.yres;
+		region.rop = ROP_COPY;
+		info->fbops->fb_fillrect(info, &region);
+		// Enforce only one draw of the logo
+		n = 1;
 	}
 
 	fb_do_show_logo(info, &image, rotate, n);
diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index abe9e0ad9b43..39ffb02635d2 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -109,6 +109,7 @@ static struct fbcon_display fb_display[MAX_NR_CONSOLES];
 
 static struct fb_info *fbcon_registered_fb[FB_MAX];
 static int fbcon_num_registered_fb;
+static int fullscreen_logo_enabled;
 
 #define fbcon_for_each_registered_fb(i)		\
 	for (i = 0; WARN_CONSOLE_UNLOCKED(), i < FB_MAX; i++)		\
@@ -512,6 +513,15 @@ static int __init fb_console_setup(char *this_opt)
 }
 
 __setup("fbcon=", fb_console_setup);
+
+static int __init fullscreen_logo_setup(char *str)
+{
+	pr_info("Fullscreen Logo Enabled");
+	fullscreen_logo_enabled = 1;
+	return 1;
+}
+
+__setup("fullscreen_logo=", fullscreen_logo_setup);
 #endif
 
 static int search_fb_in_map(int idx)
@@ -595,6 +605,8 @@ static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info,
 	if (fb_get_color_depth(&info->var, &info->fix) == 1)
 		erase &= ~0x400;
 	logo_height = fb_prepare_logo(info, ops->rotate);
+	if (fullscreen_logo_enabled)
+		logo_height = info->var.yres - vc->vc_font.height;
 	logo_lines = DIV_ROUND_UP(logo_height, vc->vc_font.height);
 	q = (unsigned short *) (vc->vc_origin +
 				vc->vc_size_row * rows);
diff --git a/drivers/video/fbdev/core/fbmem.c b/drivers/video/fbdev/core/fbmem.c
index 86b9a371645b..7be8beca0b7e 100644
--- a/drivers/video/fbdev/core/fbmem.c
+++ b/drivers/video/fbdev/core/fbmem.c
@@ -31,6 +31,7 @@ struct class *fb_class;
 DEFINE_MUTEX(registration_lock);
 struct fb_info *registered_fb[FB_MAX] __read_mostly;
 int num_registered_fb __read_mostly;
+int min_dynamic_fb __read_mostly;
 #define for_each_registered_fb(i)		\
 	for (i = 0; i < FB_MAX; i++)		\
 		if (!registered_fb[i]) {} else
@@ -399,9 +400,12 @@ static int do_register_framebuffer(struct fb_info *fb_info)
 	if (num_registered_fb == FB_MAX)
 		return -ENXIO;
 
-	for (i = 0 ; i < FB_MAX; i++)
-		if (!registered_fb[i])
-			break;
+	i = fb_info->node;
+	if (!fb_info->custom_fb_num || fb_info->node >= FB_MAX || registered_fb[fb_info->node]) {
+		for (i = min_dynamic_fb ; i < FB_MAX; i++)
+			if (!registered_fb[i])
+				break;
+	}
 
 	if (i >= FB_MAX)
 		return -ENXIO;
@@ -507,6 +511,12 @@ static void do_unregister_framebuffer(struct fb_info *fb_info)
 	put_fb_info(fb_info);
 }
 
+void fb_set_lowest_dynamic_fb(int min_fb_dev)
+{
+	min_dynamic_fb = min_fb_dev;
+}
+EXPORT_SYMBOL(fb_set_lowest_dynamic_fb);
+
 /**
  *	register_framebuffer - registers a frame buffer device
  *	@fb_info: frame buffer info structure
diff --git a/drivers/video/fbdev/rpisense-fb.c b/drivers/video/fbdev/rpisense-fb.c
new file mode 100644
index 000000000000..a2a63ddbe012
--- /dev/null
+++ b/drivers/video/fbdev/rpisense-fb.c
@@ -0,0 +1,295 @@
+/*
+ * Raspberry Pi Sense HAT framebuffer driver
+ * http://raspberrypi.org
+ *
+ * Copyright (C) 2015 Raspberry Pi
+ *
+ * Author: Serge Schneider
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/vmalloc.h>
+#include <linux/platform_device.h>
+
+#include <linux/mfd/rpisense/framebuffer.h>
+
+static bool lowlight;
+module_param(lowlight, bool, 0);
+MODULE_PARM_DESC(lowlight, "Reduce LED matrix brightness to one third");
+
+struct rpisense_fb_param {
+	char __iomem *vmem;
+	u8 *vmem_work;
+	u32 vmemsize;
+	u8 *gamma;
+};
+
+static u8 gamma_default[32] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01,
+			       0x02, 0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07,
+			       0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0E, 0x0F, 0x11,
+			       0x12, 0x14, 0x15, 0x17, 0x19, 0x1B, 0x1D, 0x1F,};
+
+static u8 gamma_low[32] = {0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+			   0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02,
+			   0x03, 0x03, 0x03, 0x04, 0x04, 0x05, 0x05, 0x06,
+			   0x06, 0x07, 0x07, 0x08, 0x08, 0x09, 0x0A, 0x0A,};
+
+static u8 gamma_user[32];
+
+static u32 pseudo_palette[16];
+
+static struct rpisense_fb_param rpisense_fb_param = {
+	.vmem = NULL,
+	.vmemsize = 128,
+	.gamma = gamma_default,
+};
+
+static struct fb_deferred_io rpisense_fb_defio;
+
+static struct fb_fix_screeninfo rpisense_fb_fix = {
+	.id =		"RPi-Sense FB",
+	.type =		FB_TYPE_PACKED_PIXELS,
+	.visual =	FB_VISUAL_TRUECOLOR,
+	.xpanstep =	0,
+	.ypanstep =	0,
+	.ywrapstep =	0,
+	.accel =	FB_ACCEL_NONE,
+	.line_length =	16,
+};
+
+static struct fb_var_screeninfo rpisense_fb_var = {
+	.xres		= 8,
+	.yres		= 8,
+	.xres_virtual	= 8,
+	.yres_virtual	= 8,
+	.bits_per_pixel = 16,
+	.red		= {11, 5, 0},
+	.green		= {5, 6, 0},
+	.blue		= {0, 5, 0},
+};
+
+static ssize_t rpisense_fb_write(struct fb_info *info,
+				 const char __user *buf, size_t count,
+				 loff_t *ppos)
+{
+	ssize_t res = fb_sys_write(info, buf, count, ppos);
+
+	schedule_delayed_work(&info->deferred_work, rpisense_fb_defio.delay);
+	return res;
+}
+
+static void rpisense_fb_fillrect(struct fb_info *info,
+				 const struct fb_fillrect *rect)
+{
+	sys_fillrect(info, rect);
+	schedule_delayed_work(&info->deferred_work, rpisense_fb_defio.delay);
+}
+
+static void rpisense_fb_copyarea(struct fb_info *info,
+				 const struct fb_copyarea *area)
+{
+	sys_copyarea(info, area);
+	schedule_delayed_work(&info->deferred_work, rpisense_fb_defio.delay);
+}
+
+static void rpisense_fb_imageblit(struct fb_info *info,
+				  const struct fb_image *image)
+{
+	sys_imageblit(info, image);
+	schedule_delayed_work(&info->deferred_work, rpisense_fb_defio.delay);
+}
+
+static void rpisense_fb_deferred_io(struct fb_info *info,
+				    struct list_head *pagelist)
+{
+	int i;
+	int j;
+	u8 *vmem_work = rpisense_fb_param.vmem_work;
+	u16 *mem = (u16 *)rpisense_fb_param.vmem;
+	u8 *gamma = rpisense_fb_param.gamma;
+	struct rpisense_fb *rpisense_fb = info->par;
+
+	for (j = 0; j < 8; j++) {
+		for (i = 0; i < 8; i++) {
+			vmem_work[(j * 24) + i] =
+				gamma[(mem[(j * 8) + i] >> 11) & 0x1F];
+			vmem_work[(j * 24) + (i + 8)] =
+				gamma[(mem[(j * 8) + i] >> 6) & 0x1F];
+			vmem_work[(j * 24) + (i + 16)] =
+				gamma[(mem[(j * 8) + i]) & 0x1F];
+		}
+	}
+	regmap_bulk_write(rpisense_fb->regmap, 0, vmem_work, 192);
+}
+
+static struct fb_deferred_io rpisense_fb_defio = {
+	.delay		= HZ/100,
+	.deferred_io	= rpisense_fb_deferred_io,
+};
+
+static int rpisense_fb_ioctl(struct fb_info *info, unsigned int cmd,
+			     unsigned long arg)
+{
+	switch (cmd) {
+	case SENSEFB_FBIOGET_GAMMA:
+		if (copy_to_user((void __user *) arg, rpisense_fb_param.gamma,
+				 sizeof(u8[32])))
+			return -EFAULT;
+		return 0;
+	case SENSEFB_FBIOSET_GAMMA:
+		if (copy_from_user(gamma_user, (void __user *)arg,
+				   sizeof(u8[32])))
+			return -EFAULT;
+		rpisense_fb_param.gamma = gamma_user;
+		schedule_delayed_work(&info->deferred_work,
+				      rpisense_fb_defio.delay);
+		return 0;
+	case SENSEFB_FBIORESET_GAMMA:
+		switch (arg) {
+		case 0:
+			rpisense_fb_param.gamma = gamma_default;
+			break;
+		case 1:
+			rpisense_fb_param.gamma = gamma_low;
+			break;
+		case 2:
+			rpisense_fb_param.gamma = gamma_user;
+			break;
+		default:
+			return -EINVAL;
+		}
+		schedule_delayed_work(&info->deferred_work,
+				      rpisense_fb_defio.delay);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static struct fb_ops rpisense_fb_ops = {
+	.owner		= THIS_MODULE,
+	.fb_read	= fb_sys_read,
+	.fb_write	= rpisense_fb_write,
+	.fb_fillrect	= rpisense_fb_fillrect,
+	.fb_copyarea	= rpisense_fb_copyarea,
+	.fb_imageblit	= rpisense_fb_imageblit,
+	.fb_ioctl	= rpisense_fb_ioctl,
+	.fb_mmap	= fb_deferred_io_mmap,
+};
+
+static int rpisense_fb_probe(struct platform_device *pdev)
+{
+	struct fb_info *info;
+	int ret = -ENOMEM;
+	struct rpisense_fb *rpisense_fb;
+
+	info = framebuffer_alloc(sizeof(*rpisense_fb), &pdev->dev);
+	if (!info) {
+		dev_err(&pdev->dev, "Could not allocate framebuffer.\n");
+		goto err_malloc;
+	}
+
+	rpisense_fb = info->par;
+	platform_set_drvdata(pdev, rpisense_fb);
+
+	rpisense_fb->pdev = pdev;
+	rpisense_fb->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+	if (!rpisense_fb->regmap) {
+		dev_err(&pdev->dev,
+			"unable to get sensehat regmap");
+		return -ENODEV;
+	}
+
+	rpisense_fb_param.vmem = vzalloc(rpisense_fb_param.vmemsize);
+	if (!rpisense_fb_param.vmem)
+		return ret;
+
+	rpisense_fb_param.vmem_work = devm_kmalloc(&pdev->dev, 193, GFP_KERNEL);
+	if (!rpisense_fb_param.vmem_work)
+		goto err_malloc;
+
+
+	rpisense_fb_fix.smem_start = (unsigned long)rpisense_fb_param.vmem;
+	rpisense_fb_fix.smem_len = rpisense_fb_param.vmemsize;
+
+	info->fbops = &rpisense_fb_ops;
+	info->fix = rpisense_fb_fix;
+	info->var = rpisense_fb_var;
+	info->fbdefio = &rpisense_fb_defio;
+	info->flags = FBINFO_VIRTFB;
+	info->screen_base = rpisense_fb_param.vmem;
+	info->screen_size = rpisense_fb_param.vmemsize;
+	info->pseudo_palette = pseudo_palette;
+
+	if (lowlight)
+		rpisense_fb_param.gamma = gamma_low;
+
+	fb_deferred_io_init(info);
+
+	ret = register_framebuffer(info);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "Could not register framebuffer.\n");
+		goto err_fballoc;
+	}
+
+	fb_info(info, "%s frame buffer device\n", info->fix.id);
+	schedule_delayed_work(&info->deferred_work, rpisense_fb_defio.delay);
+	return 0;
+err_fballoc:
+	framebuffer_release(info);
+err_malloc:
+	vfree(rpisense_fb_param.vmem);
+	return ret;
+}
+
+static void rpisense_fb_remove(struct platform_device *pdev)
+{
+	struct rpisense_fb *rpisense_fb = platform_get_drvdata(pdev);
+	struct fb_info *info = rpisense_fb->info;
+
+	if (info) {
+		unregister_framebuffer(info);
+		fb_deferred_io_cleanup(info);
+		framebuffer_release(info);
+		vfree(rpisense_fb_param.vmem);
+	}
+}
+
+static const struct of_device_id rpisense_fb_id[] = {
+	{ .compatible = "raspberrypi,rpi-sense-fb" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, rpisense_fb_id);
+
+static struct platform_driver rpisense_fb_driver = {
+	.probe = rpisense_fb_probe,
+	.remove = rpisense_fb_remove,
+	.driver = {
+		.name = "rpi-sense-fb",
+		.owner = THIS_MODULE,
+		.of_match_table = rpisense_fb_id,
+	},
+};
+
+module_platform_driver(rpisense_fb_driver);
+
+MODULE_DESCRIPTION("Raspberry Pi Sense HAT framebuffer driver");
+MODULE_AUTHOR("Serge Schneider <serge@raspberrypi.org>");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/video/logo/logo_linux_clut224.ppm b/drivers/video/logo/logo_linux_clut224.ppm
index 3c14e43b82fe..7626beb6a5bb 100644
--- a/drivers/video/logo/logo_linux_clut224.ppm
+++ b/drivers/video/logo/logo_linux_clut224.ppm
@@ -1,1604 +1,883 @@
 P3
-# Standard 224-color Linux logo
-80 80
+63 80
 255
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  6   6   6   6   6   6  10  10  10  10  10  10
- 10  10  10   6   6   6   6   6   6   6   6   6
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   6   6   6  10  10  10  14  14  14
- 22  22  22  26  26  26  30  30  30  34  34  34
- 30  30  30  30  30  30  26  26  26  18  18  18
- 14  14  14  10  10  10   6   6   6   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   1   0   0   1   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  6   6   6  14  14  14  26  26  26  42  42  42
- 54  54  54  66  66  66  78  78  78  78  78  78
- 78  78  78  74  74  74  66  66  66  54  54  54
- 42  42  42  26  26  26  18  18  18  10  10  10
-  6   6   6   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   1   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  10  10  10
- 22  22  22  42  42  42  66  66  66  86  86  86
- 66  66  66  38  38  38  38  38  38  22  22  22
- 26  26  26  34  34  34  54  54  54  66  66  66
- 86  86  86  70  70  70  46  46  46  26  26  26
- 14  14  14   6   6   6   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   1   0   0   1   0   0   1   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0  10  10  10  26  26  26
- 50  50  50  82  82  82  58  58  58   6   6   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  6   6   6  54  54  54  86  86  86  66  66  66
- 38  38  38  18  18  18   6   6   6   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   6   6   6  22  22  22  50  50  50
- 78  78  78  34  34  34   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   6   6   6  70  70  70
- 78  78  78  46  46  46  22  22  22   6   6   6
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   1   0   0   1   0   0   1   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  6   6   6  18  18  18  42  42  42  82  82  82
- 26  26  26   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6  14  14  14
- 46  46  46  34  34  34   6   6   6   2   2   6
- 42  42  42  78  78  78  42  42  42  18  18  18
-  6   6   6   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   1   0   0   0   0   0   1   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
- 10  10  10  30  30  30  66  66  66  58  58  58
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6  26  26  26
- 86  86  86 101 101 101  46  46  46  10  10  10
-  2   2   6  58  58  58  70  70  70  34  34  34
- 10  10  10   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   1   0   0   1   0   0   1   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
- 14  14  14  42  42  42  86  86  86  10  10  10
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6  30  30  30
- 94  94  94  94  94  94  58  58  58  26  26  26
-  2   2   6   6   6   6  78  78  78  54  54  54
- 22  22  22   6   6   6   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   6   6   6
- 22  22  22  62  62  62  62  62  62   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6  26  26  26
- 54  54  54  38  38  38  18  18  18  10  10  10
-  2   2   6   2   2   6  34  34  34  82  82  82
- 38  38  38  14  14  14   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   1   0   0   1   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   6   6   6
- 30  30  30  78  78  78  30  30  30   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6  10  10  10
- 10  10  10   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6  78  78  78
- 50  50  50  18  18  18   6   6   6   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   1   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  10  10  10
- 38  38  38  86  86  86  14  14  14   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6  54  54  54
- 66  66  66  26  26  26   6   6   6   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   1   0   0   1   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  14  14  14
- 42  42  42  82  82  82   2   2   6   2   2   6
-  2   2   6   6   6   6  10  10  10   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   6   6   6
- 14  14  14  10  10  10   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6  18  18  18
- 82  82  82  34  34  34  10  10  10   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   1   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  14  14  14
- 46  46  46  86  86  86   2   2   6   2   2   6
-  6   6   6   6   6   6  22  22  22  34  34  34
-  6   6   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6  18  18  18  34  34  34
- 10  10  10  50  50  50  22  22  22   2   2   6
-  2   2   6   2   2   6   2   2   6  10  10  10
- 86  86  86  42  42  42  14  14  14   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   1   0   0   1   0   0   1   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  14  14  14
- 46  46  46  86  86  86   2   2   6   2   2   6
- 38  38  38 116 116 116  94  94  94  22  22  22
- 22  22  22   2   2   6   2   2   6   2   2   6
- 14  14  14  86  86  86 138 138 138 162 162 162
-154 154 154  38  38  38  26  26  26   6   6   6
-  2   2   6   2   2   6   2   2   6   2   2   6
- 86  86  86  46  46  46  14  14  14   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  14  14  14
- 46  46  46  86  86  86   2   2   6  14  14  14
-134 134 134 198 198 198 195 195 195 116 116 116
- 10  10  10   2   2   6   2   2   6   6   6   6
-101  98  89 187 187 187 210 210 210 218 218 218
-214 214 214 134 134 134  14  14  14   6   6   6
-  2   2   6   2   2   6   2   2   6   2   2   6
- 86  86  86  50  50  50  18  18  18   6   6   6
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   1   0   0   0
-  0   0   1   0   0   1   0   0   1   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  14  14  14
- 46  46  46  86  86  86   2   2   6  54  54  54
-218 218 218 195 195 195 226 226 226 246 246 246
- 58  58  58   2   2   6   2   2   6  30  30  30
-210 210 210 253 253 253 174 174 174 123 123 123
-221 221 221 234 234 234  74  74  74   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
- 70  70  70  58  58  58  22  22  22   6   6   6
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  14  14  14
- 46  46  46  82  82  82   2   2   6 106 106 106
-170 170 170  26  26  26  86  86  86 226 226 226
-123 123 123  10  10  10  14  14  14  46  46  46
-231 231 231 190 190 190   6   6   6  70  70  70
- 90  90  90 238 238 238 158 158 158   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
- 70  70  70  58  58  58  22  22  22   6   6   6
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   1   0   0   0
-  0   0   1   0   0   1   0   0   1   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  14  14  14
- 42  42  42  86  86  86   6   6   6 116 116 116
-106 106 106   6   6   6  70  70  70 149 149 149
-128 128 128  18  18  18  38  38  38  54  54  54
-221 221 221 106 106 106   2   2   6  14  14  14
- 46  46  46 190 190 190 198 198 198   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
- 74  74  74  62  62  62  22  22  22   6   6   6
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   1   0   0   0
-  0   0   1   0   0   0   0   0   1   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  14  14  14
- 42  42  42  94  94  94  14  14  14 101 101 101
-128 128 128   2   2   6  18  18  18 116 116 116
-118  98  46 121  92   8 121  92   8  98  78  10
-162 162 162 106 106 106   2   2   6   2   2   6
-  2   2   6 195 195 195 195 195 195   6   6   6
-  2   2   6   2   2   6   2   2   6   2   2   6
- 74  74  74  62  62  62  22  22  22   6   6   6
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   1   0   0   1
-  0   0   1   0   0   0   0   0   1   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  10  10  10
- 38  38  38  90  90  90  14  14  14  58  58  58
-210 210 210  26  26  26  54  38   6 154 114  10
-226 170  11 236 186  11 225 175  15 184 144  12
-215 174  15 175 146  61  37  26   9   2   2   6
- 70  70  70 246 246 246 138 138 138   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
- 70  70  70  66  66  66  26  26  26   6   6   6
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  10  10  10
- 38  38  38  86  86  86  14  14  14  10  10  10
-195 195 195 188 164 115 192 133   9 225 175  15
-239 182  13 234 190  10 232 195  16 232 200  30
-245 207  45 241 208  19 232 195  16 184 144  12
-218 194 134 211 206 186  42  42  42   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
- 50  50  50  74  74  74  30  30  30   6   6   6
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  10  10  10
- 34  34  34  86  86  86  14  14  14   2   2   6
-121  87  25 192 133   9 219 162  10 239 182  13
-236 186  11 232 195  16 241 208  19 244 214  54
-246 218  60 246 218  38 246 215  20 241 208  19
-241 208  19 226 184  13 121  87  25   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
- 50  50  50  82  82  82  34  34  34  10  10  10
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  10  10  10
- 34  34  34  82  82  82  30  30  30  61  42   6
-180 123   7 206 145  10 230 174  11 239 182  13
-234 190  10 238 202  15 241 208  19 246 218  74
-246 218  38 246 215  20 246 215  20 246 215  20
-226 184  13 215 174  15 184 144  12   6   6   6
-  2   2   6   2   2   6   2   2   6   2   2   6
- 26  26  26  94  94  94  42  42  42  14  14  14
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  10  10  10
- 30  30  30  78  78  78  50  50  50 104  69   6
-192 133   9 216 158  10 236 178  12 236 186  11
-232 195  16 241 208  19 244 214  54 245 215  43
-246 215  20 246 215  20 241 208  19 198 155  10
-200 144  11 216 158  10 156 118  10   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  6   6   6  90  90  90  54  54  54  18  18  18
-  6   6   6   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  10  10  10
- 30  30  30  78  78  78  46  46  46  22  22  22
-137  92   6 210 162  10 239 182  13 238 190  10
-238 202  15 241 208  19 246 215  20 246 215  20
-241 208  19 203 166  17 185 133  11 210 150  10
-216 158  10 210 150  10 102  78  10   2   2   6
-  6   6   6  54  54  54  14  14  14   2   2   6
-  2   2   6  62  62  62  74  74  74  30  30  30
- 10  10  10   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  10  10  10
- 34  34  34  78  78  78  50  50  50   6   6   6
- 94  70  30 139 102  15 190 146  13 226 184  13
-232 200  30 232 195  16 215 174  15 190 146  13
-168 122  10 192 133   9 210 150  10 213 154  11
-202 150  34 182 157 106 101  98  89   2   2   6
-  2   2   6  78  78  78 116 116 116  58  58  58
-  2   2   6  22  22  22  90  90  90  46  46  46
- 18  18  18   6   6   6   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  10  10  10
- 38  38  38  86  86  86  50  50  50   6   6   6
-128 128 128 174 154 114 156 107  11 168 122  10
-198 155  10 184 144  12 197 138  11 200 144  11
-206 145  10 206 145  10 197 138  11 188 164 115
-195 195 195 198 198 198 174 174 174  14  14  14
-  2   2   6  22  22  22 116 116 116 116 116 116
- 22  22  22   2   2   6  74  74  74  70  70  70
- 30  30  30  10  10  10   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   6   6   6  18  18  18
- 50  50  50 101 101 101  26  26  26  10  10  10
-138 138 138 190 190 190 174 154 114 156 107  11
-197 138  11 200 144  11 197 138  11 192 133   9
-180 123   7 190 142  34 190 178 144 187 187 187
-202 202 202 221 221 221 214 214 214  66  66  66
-  2   2   6   2   2   6  50  50  50  62  62  62
-  6   6   6   2   2   6  10  10  10  90  90  90
- 50  50  50  18  18  18   6   6   6   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0  10  10  10  34  34  34
- 74  74  74  74  74  74   2   2   6   6   6   6
-144 144 144 198 198 198 190 190 190 178 166 146
-154 121  60 156 107  11 156 107  11 168 124  44
-174 154 114 187 187 187 190 190 190 210 210 210
-246 246 246 253 253 253 253 253 253 182 182 182
-  6   6   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6  62  62  62
- 74  74  74  34  34  34  14  14  14   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0  10  10  10  22  22  22  54  54  54
- 94  94  94  18  18  18   2   2   6  46  46  46
-234 234 234 221 221 221 190 190 190 190 190 190
-190 190 190 187 187 187 187 187 187 190 190 190
-190 190 190 195 195 195 214 214 214 242 242 242
-253 253 253 253 253 253 253 253 253 253 253 253
- 82  82  82   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6  14  14  14
- 86  86  86  54  54  54  22  22  22   6   6   6
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  6   6   6  18  18  18  46  46  46  90  90  90
- 46  46  46  18  18  18   6   6   6 182 182 182
-253 253 253 246 246 246 206 206 206 190 190 190
-190 190 190 190 190 190 190 190 190 190 190 190
-206 206 206 231 231 231 250 250 250 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-202 202 202  14  14  14   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
- 42  42  42  86  86  86  42  42  42  18  18  18
-  6   6   6   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   6   6   6
- 14  14  14  38  38  38  74  74  74  66  66  66
-  2   2   6   6   6   6  90  90  90 250 250 250
-253 253 253 253 253 253 238 238 238 198 198 198
-190 190 190 190 190 190 195 195 195 221 221 221
-246 246 246 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253  82  82  82   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6  78  78  78  70  70  70  34  34  34
- 14  14  14   6   6   6   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  14  14  14
- 34  34  34  66  66  66  78  78  78   6   6   6
-  2   2   6  18  18  18 218 218 218 253 253 253
-253 253 253 253 253 253 253 253 253 246 246 246
-226 226 226 231 231 231 246 246 246 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 178 178 178   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6  18  18  18  90  90  90  62  62  62
- 30  30  30  10  10  10   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0  10  10  10  26  26  26
- 58  58  58  90  90  90  18  18  18   2   2   6
-  2   2   6 110 110 110 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-250 250 250 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 231 231 231  18  18  18   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6  18  18  18  94  94  94
- 54  54  54  26  26  26  10  10  10   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   6   6   6  22  22  22  50  50  50
- 90  90  90  26  26  26   2   2   6   2   2   6
- 14  14  14 195 195 195 250 250 250 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-250 250 250 242 242 242  54  54  54   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6  38  38  38
- 86  86  86  50  50  50  22  22  22   6   6   6
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  6   6   6  14  14  14  38  38  38  82  82  82
- 34  34  34   2   2   6   2   2   6   2   2   6
- 42  42  42 195 195 195 246 246 246 253 253 253
-253 253 253 253 253 253 253 253 253 250 250 250
-242 242 242 242 242 242 250 250 250 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 250 250 250 246 246 246 238 238 238
-226 226 226 231 231 231 101 101 101   6   6   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
- 38  38  38  82  82  82  42  42  42  14  14  14
-  6   6   6   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
- 10  10  10  26  26  26  62  62  62  66  66  66
-  2   2   6   2   2   6   2   2   6   6   6   6
- 70  70  70 170 170 170 206 206 206 234 234 234
-246 246 246 250 250 250 250 250 250 238 238 238
-226 226 226 231 231 231 238 238 238 250 250 250
-250 250 250 250 250 250 246 246 246 231 231 231
-214 214 214 206 206 206 202 202 202 202 202 202
-198 198 198 202 202 202 182 182 182  18  18  18
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6  62  62  62  66  66  66  30  30  30
- 10  10  10   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
- 14  14  14  42  42  42  82  82  82  18  18  18
-  2   2   6   2   2   6   2   2   6  10  10  10
- 94  94  94 182 182 182 218 218 218 242 242 242
-250 250 250 253 253 253 253 253 253 250 250 250
-234 234 234 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 246 246 246
-238 238 238 226 226 226 210 210 210 202 202 202
-195 195 195 195 195 195 210 210 210 158 158 158
-  6   6   6  14  14  14  50  50  50  14  14  14
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   6   6   6  86  86  86  46  46  46
- 18  18  18   6   6   6   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   6   6   6
- 22  22  22  54  54  54  70  70  70   2   2   6
-  2   2   6  10  10  10   2   2   6  22  22  22
-166 166 166 231 231 231 250 250 250 253 253 253
-253 253 253 253 253 253 253 253 253 250 250 250
-242 242 242 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 246 246 246
-231 231 231 206 206 206 198 198 198 226 226 226
- 94  94  94   2   2   6   6   6   6  38  38  38
- 30  30  30   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6  62  62  62  66  66  66
- 26  26  26  10  10  10   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  10  10  10
- 30  30  30  74  74  74  50  50  50   2   2   6
- 26  26  26  26  26  26   2   2   6 106 106 106
-238 238 238 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 246 246 246 218 218 218 202 202 202
-210 210 210  14  14  14   2   2   6   2   2   6
- 30  30  30  22  22  22   2   2   6   2   2   6
-  2   2   6   2   2   6  18  18  18  86  86  86
- 42  42  42  14  14  14   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  14  14  14
- 42  42  42  90  90  90  22  22  22   2   2   6
- 42  42  42   2   2   6  18  18  18 218 218 218
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 250 250 250 221 221 221
-218 218 218 101 101 101   2   2   6  14  14  14
- 18  18  18  38  38  38  10  10  10   2   2   6
-  2   2   6   2   2   6   2   2   6  78  78  78
- 58  58  58  22  22  22   6   6   6   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   6   6   6  18  18  18
- 54  54  54  82  82  82   2   2   6  26  26  26
- 22  22  22   2   2   6 123 123 123 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 250 250 250
-238 238 238 198 198 198   6   6   6  38  38  38
- 58  58  58  26  26  26  38  38  38   2   2   6
-  2   2   6   2   2   6   2   2   6  46  46  46
- 78  78  78  30  30  30  10  10  10   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0  10  10  10  30  30  30
- 74  74  74  58  58  58   2   2   6  42  42  42
-  2   2   6  22  22  22 231 231 231 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 250 250 250
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 246 246 246  46  46  46  38  38  38
- 42  42  42  14  14  14  38  38  38  14  14  14
-  2   2   6   2   2   6   2   2   6   6   6   6
- 86  86  86  46  46  46  14  14  14   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   6   6   6  14  14  14  42  42  42
- 90  90  90  18  18  18  18  18  18  26  26  26
-  2   2   6 116 116 116 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 250 250 250 238 238 238
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253  94  94  94   6   6   6
-  2   2   6   2   2   6  10  10  10  34  34  34
-  2   2   6   2   2   6   2   2   6   2   2   6
- 74  74  74  58  58  58  22  22  22   6   6   6
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0  10  10  10  26  26  26  66  66  66
- 82  82  82   2   2   6  38  38  38   6   6   6
- 14  14  14 210 210 210 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 246 246 246 242 242 242
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 144 144 144   2   2   6
-  2   2   6   2   2   6   2   2   6  46  46  46
-  2   2   6   2   2   6   2   2   6   2   2   6
- 42  42  42  74  74  74  30  30  30  10  10  10
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  6   6   6  14  14  14  42  42  42  90  90  90
- 26  26  26   6   6   6  42  42  42   2   2   6
- 74  74  74 250 250 250 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 242 242 242 242 242 242
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 182 182 182   2   2   6
-  2   2   6   2   2   6   2   2   6  46  46  46
-  2   2   6   2   2   6   2   2   6   2   2   6
- 10  10  10  86  86  86  38  38  38  10  10  10
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
- 10  10  10  26  26  26  66  66  66  82  82  82
-  2   2   6  22  22  22  18  18  18   2   2   6
-149 149 149 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 234 234 234 242 242 242
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 206 206 206   2   2   6
-  2   2   6   2   2   6   2   2   6  38  38  38
-  2   2   6   2   2   6   2   2   6   2   2   6
-  6   6   6  86  86  86  46  46  46  14  14  14
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   6   6   6
- 18  18  18  46  46  46  86  86  86  18  18  18
-  2   2   6  34  34  34  10  10  10   6   6   6
-210 210 210 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 234 234 234 242 242 242
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 221 221 221   6   6   6
-  2   2   6   2   2   6   6   6   6  30  30  30
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6  82  82  82  54  54  54  18  18  18
-  6   6   6   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  10  10  10
- 26  26  26  66  66  66  62  62  62   2   2   6
-  2   2   6  38  38  38  10  10  10  26  26  26
-238 238 238 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 231 231 231 238 238 238
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 231 231 231   6   6   6
-  2   2   6   2   2   6  10  10  10  30  30  30
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6  66  66  66  58  58  58  22  22  22
-  6   6   6   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  10  10  10
- 38  38  38  78  78  78   6   6   6   2   2   6
-  2   2   6  46  46  46  14  14  14  42  42  42
-246 246 246 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 231 231 231 242 242 242
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 234 234 234  10  10  10
-  2   2   6   2   2   6  22  22  22  14  14  14
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6  66  66  66  62  62  62  22  22  22
-  6   6   6   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   6   6   6  18  18  18
- 50  50  50  74  74  74   2   2   6   2   2   6
- 14  14  14  70  70  70  34  34  34  62  62  62
-250 250 250 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 231 231 231 246 246 246
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 234 234 234  14  14  14
-  2   2   6   2   2   6  30  30  30   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6  66  66  66  62  62  62  22  22  22
-  6   6   6   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   6   6   6  18  18  18
- 54  54  54  62  62  62   2   2   6   2   2   6
-  2   2   6  30  30  30  46  46  46  70  70  70
-250 250 250 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 231 231 231 246 246 246
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 226 226 226  10  10  10
-  2   2   6   6   6   6  30  30  30   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6  66  66  66  58  58  58  22  22  22
-  6   6   6   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   6   6   6  22  22  22
- 58  58  58  62  62  62   2   2   6   2   2   6
-  2   2   6   2   2   6  30  30  30  78  78  78
-250 250 250 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 231 231 231 246 246 246
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 206 206 206   2   2   6
- 22  22  22  34  34  34  18  14   6  22  22  22
- 26  26  26  18  18  18   6   6   6   2   2   6
-  2   2   6  82  82  82  54  54  54  18  18  18
-  6   6   6   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   6   6   6  26  26  26
- 62  62  62 106 106 106  74  54  14 185 133  11
-210 162  10 121  92   8   6   6   6  62  62  62
-238 238 238 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 231 231 231 246 246 246
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 158 158 158  18  18  18
- 14  14  14   2   2   6   2   2   6   2   2   6
-  6   6   6  18  18  18  66  66  66  38  38  38
-  6   6   6  94  94  94  50  50  50  18  18  18
-  6   6   6   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   6   6   6
- 10  10  10  10  10  10  18  18  18  38  38  38
- 78  78  78 142 134 106 216 158  10 242 186  14
-246 190  14 246 190  14 156 118  10  10  10  10
- 90  90  90 238 238 238 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 231 231 231 250 250 250
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 246 230 190
-238 204  91 238 204  91 181 142  44  37  26   9
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6  38  38  38  46  46  46
- 26  26  26 106 106 106  54  54  54  18  18  18
-  6   6   6   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   6   6   6  14  14  14  22  22  22
- 30  30  30  38  38  38  50  50  50  70  70  70
-106 106 106 190 142  34 226 170  11 242 186  14
-246 190  14 246 190  14 246 190  14 154 114  10
-  6   6   6  74  74  74 226 226 226 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 231 231 231 250 250 250
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 228 184  62
-241 196  14 241 208  19 232 195  16  38  30  10
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   6   6   6  30  30  30  26  26  26
-203 166  17 154 142  90  66  66  66  26  26  26
-  6   6   6   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  6   6   6  18  18  18  38  38  38  58  58  58
- 78  78  78  86  86  86 101 101 101 123 123 123
-175 146  61 210 150  10 234 174  13 246 186  14
-246 190  14 246 190  14 246 190  14 238 190  10
-102  78  10   2   2   6  46  46  46 198 198 198
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 234 234 234 242 242 242
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 224 178  62
-242 186  14 241 196  14 210 166  10  22  18   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   6   6   6 121  92   8
-238 202  15 232 195  16  82  82  82  34  34  34
- 10  10  10   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
- 14  14  14  38  38  38  70  70  70 154 122  46
-190 142  34 200 144  11 197 138  11 197 138  11
-213 154  11 226 170  11 242 186  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-225 175  15  46  32   6   2   2   6  22  22  22
-158 158 158 250 250 250 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 250 250 250 242 242 242 224 178  62
-239 182  13 236 186  11 213 154  11  46  32   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6  61  42   6 225 175  15
-238 190  10 236 186  11 112 100  78  42  42  42
- 14  14  14   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   6   6   6
- 22  22  22  54  54  54 154 122  46 213 154  11
-226 170  11 230 174  11 226 170  11 226 170  11
-236 178  12 242 186  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-241 196  14 184 144  12  10  10  10   2   2   6
-  6   6   6 116 116 116 242 242 242 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 231 231 231 198 198 198 214 170  54
-236 178  12 236 178  12 210 150  10 137  92   6
- 18  14   6   2   2   6   2   2   6   2   2   6
-  6   6   6  70  47   6 200 144  11 236 178  12
-239 182  13 239 182  13 124 112  88  58  58  58
- 22  22  22   6   6   6   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  10  10  10
- 30  30  30  70  70  70 180 133  36 226 170  11
-239 182  13 242 186  14 242 186  14 246 186  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 232 195  16  98  70   6   2   2   6
-  2   2   6   2   2   6  66  66  66 221 221 221
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 206 206 206 198 198 198 214 166  58
-230 174  11 230 174  11 216 158  10 192 133   9
-163 110   8 116  81   8 102  78  10 116  81   8
-167 114   7 197 138  11 226 170  11 239 182  13
-242 186  14 242 186  14 162 146  94  78  78  78
- 34  34  34  14  14  14   6   6   6   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   6   6   6
- 30  30  30  78  78  78 190 142  34 226 170  11
-239 182  13 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 241 196  14 203 166  17  22  18   6
-  2   2   6   2   2   6   2   2   6  38  38  38
-218 218 218 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-250 250 250 206 206 206 198 198 198 202 162  69
-226 170  11 236 178  12 224 166  10 210 150  10
-200 144  11 197 138  11 192 133   9 197 138  11
-210 150  10 226 170  11 242 186  14 246 190  14
-246 190  14 246 186  14 225 175  15 124 112  88
- 62  62  62  30  30  30  14  14  14   6   6   6
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  10  10  10
- 30  30  30  78  78  78 174 135  50 224 166  10
-239 182  13 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 241 196  14 139 102  15
-  2   2   6   2   2   6   2   2   6   2   2   6
- 78  78  78 250 250 250 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-250 250 250 214 214 214 198 198 198 190 150  46
-219 162  10 236 178  12 234 174  13 224 166  10
-216 158  10 213 154  11 213 154  11 216 158  10
-226 170  11 239 182  13 246 190  14 246 190  14
-246 190  14 246 190  14 242 186  14 206 162  42
-101 101 101  58  58  58  30  30  30  14  14  14
-  6   6   6   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  10  10  10
- 30  30  30  74  74  74 174 135  50 216 158  10
-236 178  12 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 241 196  14 226 184  13
- 61  42   6   2   2   6   2   2   6   2   2   6
- 22  22  22 238 238 238 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 226 226 226 187 187 187 180 133  36
-216 158  10 236 178  12 239 182  13 236 178  12
-230 174  11 226 170  11 226 170  11 230 174  11
-236 178  12 242 186  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 186  14 239 182  13
-206 162  42 106 106 106  66  66  66  34  34  34
- 14  14  14   6   6   6   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   6   6   6
- 26  26  26  70  70  70 163 133  67 213 154  11
-236 178  12 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 241 196  14
-190 146  13  18  14   6   2   2   6   2   2   6
- 46  46  46 246 246 246 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 221 221 221  86  86  86 156 107  11
-216 158  10 236 178  12 242 186  14 246 186  14
-242 186  14 239 182  13 239 182  13 242 186  14
-242 186  14 246 186  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-242 186  14 225 175  15 142 122  72  66  66  66
- 30  30  30  10  10  10   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   6   6   6
- 26  26  26  70  70  70 163 133  67 210 150  10
-236 178  12 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-232 195  16 121  92   8  34  34  34 106 106 106
-221 221 221 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-242 242 242  82  82  82  18  14   6 163 110   8
-216 158  10 236 178  12 242 186  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 242 186  14 163 133  67
- 46  46  46  18  18  18   6   6   6   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  10  10  10
- 30  30  30  78  78  78 163 133  67 210 150  10
-236 178  12 246 186  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-241 196  14 215 174  15 190 178 144 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 218 218 218
- 58  58  58   2   2   6  22  18   6 167 114   7
-216 158  10 236 178  12 246 186  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 186  14 242 186  14 190 150  46
- 54  54  54  22  22  22   6   6   6   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  14  14  14
- 38  38  38  86  86  86 180 133  36 213 154  11
-236 178  12 246 186  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 232 195  16 190 146  13 214 214 214
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 250 250 250 170 170 170  26  26  26
-  2   2   6   2   2   6  37  26   9 163 110   8
-219 162  10 239 182  13 246 186  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 186  14 236 178  12 224 166  10 142 122  72
- 46  46  46  18  18  18   6   6   6   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   6   6   6  18  18  18
- 50  50  50 109 106  95 192 133   9 224 166  10
-242 186  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-242 186  14 226 184  13 210 162  10 142 110  46
-226 226 226 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-253 253 253 253 253 253 253 253 253 253 253 253
-198 198 198  66  66  66   2   2   6   2   2   6
-  2   2   6   2   2   6  50  34   6 156 107  11
-219 162  10 239 182  13 246 186  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 242 186  14
-234 174  13 213 154  11 154 122  46  66  66  66
- 30  30  30  10  10  10   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   6   6   6  22  22  22
- 58  58  58 154 121  60 206 145  10 234 174  13
-242 186  14 246 186  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 186  14 236 178  12 210 162  10 163 110   8
- 61  42   6 138 138 138 218 218 218 250 250 250
-253 253 253 253 253 253 253 253 253 250 250 250
-242 242 242 210 210 210 144 144 144  66  66  66
-  6   6   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6  61  42   6 163 110   8
-216 158  10 236 178  12 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 239 182  13 230 174  11 216 158  10
-190 142  34 124 112  88  70  70  70  38  38  38
- 18  18  18   6   6   6   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   6   6   6  22  22  22
- 62  62  62 168 124  44 206 145  10 224 166  10
-236 178  12 239 182  13 242 186  14 242 186  14
-246 186  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 236 178  12 216 158  10 175 118   6
- 80  54   7   2   2   6   6   6   6  30  30  30
- 54  54  54  62  62  62  50  50  50  38  38  38
- 14  14  14   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   6   6   6  80  54   7 167 114   7
-213 154  11 236 178  12 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 190  14 242 186  14 239 182  13 239 182  13
-230 174  11 210 150  10 174 135  50 124 112  88
- 82  82  82  54  54  54  34  34  34  18  18  18
-  6   6   6   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   6   6   6  18  18  18
- 50  50  50 158 118  36 192 133   9 200 144  11
-216 158  10 219 162  10 224 166  10 226 170  11
-230 174  11 236 178  12 239 182  13 239 182  13
-242 186  14 246 186  14 246 190  14 246 190  14
-246 190  14 246 190  14 246 190  14 246 190  14
-246 186  14 230 174  11 210 150  10 163 110   8
-104  69   6  10  10  10   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   6   6   6  91  60   6 167 114   7
-206 145  10 230 174  11 242 186  14 246 190  14
-246 190  14 246 190  14 246 186  14 242 186  14
-239 182  13 230 174  11 224 166  10 213 154  11
-180 133  36 124 112  88  86  86  86  58  58  58
- 38  38  38  22  22  22  10  10  10   6   6   6
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0  14  14  14
- 34  34  34  70  70  70 138 110  50 158 118  36
-167 114   7 180 123   7 192 133   9 197 138  11
-200 144  11 206 145  10 213 154  11 219 162  10
-224 166  10 230 174  11 239 182  13 242 186  14
-246 186  14 246 186  14 246 186  14 246 186  14
-239 182  13 216 158  10 185 133  11 152  99   6
-104  69   6  18  14   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   2   2   6   2   2   6   2   2   6
-  2   2   6   6   6   6  80  54   7 152  99   6
-192 133   9 219 162  10 236 178  12 239 182  13
-246 186  14 242 186  14 239 182  13 236 178  12
-224 166  10 206 145  10 192 133   9 154 121  60
- 94  94  94  62  62  62  42  42  42  22  22  22
- 14  14  14   6   6   6   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   6   6   6
- 18  18  18  34  34  34  58  58  58  78  78  78
-101  98  89 124 112  88 142 110  46 156 107  11
-163 110   8 167 114   7 175 118   6 180 123   7
-185 133  11 197 138  11 210 150  10 219 162  10
-226 170  11 236 178  12 236 178  12 234 174  13
-219 162  10 197 138  11 163 110   8 130  83   6
- 91  60   6  10  10  10   2   2   6   2   2   6
- 18  18  18  38  38  38  38  38  38  38  38  38
- 38  38  38  38  38  38  38  38  38  38  38  38
- 38  38  38  38  38  38  26  26  26   2   2   6
-  2   2   6   6   6   6  70  47   6 137  92   6
-175 118   6 200 144  11 219 162  10 230 174  11
-234 174  13 230 174  11 219 162  10 210 150  10
-192 133   9 163 110   8 124 112  88  82  82  82
- 50  50  50  30  30  30  14  14  14   6   6   6
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  6   6   6  14  14  14  22  22  22  34  34  34
- 42  42  42  58  58  58  74  74  74  86  86  86
-101  98  89 122 102  70 130  98  46 121  87  25
-137  92   6 152  99   6 163 110   8 180 123   7
-185 133  11 197 138  11 206 145  10 200 144  11
-180 123   7 156 107  11 130  83   6 104  69   6
- 50  34   6  54  54  54 110 110 110 101  98  89
- 86  86  86  82  82  82  78  78  78  78  78  78
- 78  78  78  78  78  78  78  78  78  78  78  78
- 78  78  78  82  82  82  86  86  86  94  94  94
-106 106 106 101 101 101  86  66  34 124  80   6
-156 107  11 180 123   7 192 133   9 200 144  11
-206 145  10 200 144  11 192 133   9 175 118   6
-139 102  15 109 106  95  70  70  70  42  42  42
- 22  22  22  10  10  10   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   6   6   6  10  10  10
- 14  14  14  22  22  22  30  30  30  38  38  38
- 50  50  50  62  62  62  74  74  74  90  90  90
-101  98  89 112 100  78 121  87  25 124  80   6
-137  92   6 152  99   6 152  99   6 152  99   6
-138  86   6 124  80   6  98  70   6  86  66  30
-101  98  89  82  82  82  58  58  58  46  46  46
- 38  38  38  34  34  34  34  34  34  34  34  34
- 34  34  34  34  34  34  34  34  34  34  34  34
- 34  34  34  34  34  34  38  38  38  42  42  42
- 54  54  54  82  82  82  94  86  76  91  60   6
-134  86   6 156 107  11 167 114   7 175 118   6
-175 118   6 167 114   7 152  99   6 121  87  25
-101  98  89  62  62  62  34  34  34  18  18  18
-  6   6   6   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   6   6   6   6   6   6  10  10  10
- 18  18  18  22  22  22  30  30  30  42  42  42
- 50  50  50  66  66  66  86  86  86 101  98  89
-106  86  58  98  70   6 104  69   6 104  69   6
-104  69   6  91  60   6  82  62  34  90  90  90
- 62  62  62  38  38  38  22  22  22  14  14  14
- 10  10  10  10  10  10  10  10  10  10  10  10
- 10  10  10  10  10  10   6   6   6  10  10  10
- 10  10  10  10  10  10  10  10  10  14  14  14
- 22  22  22  42  42  42  70  70  70  89  81  66
- 80  54   7 104  69   6 124  80   6 137  92   6
-134  86   6 116  81   8 100  82  52  86  86  86
- 58  58  58  30  30  30  14  14  14   6   6   6
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   6   6   6  10  10  10  14  14  14
- 18  18  18  26  26  26  38  38  38  54  54  54
- 70  70  70  86  86  86  94  86  76  89  81  66
- 89  81  66  86  86  86  74  74  74  50  50  50
- 30  30  30  14  14  14   6   6   6   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  6   6   6  18  18  18  34  34  34  58  58  58
- 82  82  82  89  81  66  89  81  66  89  81  66
- 94  86  66  94  86  76  74  74  74  50  50  50
- 26  26  26  14  14  14   6   6   6   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  6   6   6   6   6   6  14  14  14  18  18  18
- 30  30  30  38  38  38  46  46  46  54  54  54
- 50  50  50  42  42  42  30  30  30  18  18  18
- 10  10  10   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   6   6   6  14  14  14  26  26  26
- 38  38  38  50  50  50  58  58  58  58  58  58
- 54  54  54  42  42  42  30  30  30  18  18  18
- 10  10  10   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   6   6   6
-  6   6   6  10  10  10  14  14  14  18  18  18
- 18  18  18  14  14  14  10  10  10   6   6   6
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   6   6   6
- 14  14  14  18  18  18  22  22  22  22  22  22
- 18  18  18  14  14  14  10  10  10   6   6   6
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
-  0   0   0   0   0   0   0   0   0   0   0   0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 1 0  0 0 0  0 0 0  1 1 0
+0 1 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  1 1 0  0 0 0  0 0 0
+0 1 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  1 1 0
+10 15 3  2 3 1  12 18 4  42 61 14  19 27 6  11 16 4
+38 55 13  10 15 3  3 4 1  10 15 3  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  2 3 1
+12 18 4  1 1 0  23 34 8  31 45 11  10 15 3  32 47 11
+34 49 12  3 4 1  3 4 1  3 4 1  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  10 15 3  29 42 10  26 37 9  12 18 4
+55 80 19  81 118 28  55 80 19  92 132 31  106 153 36  69 100 23
+100 144 34  80 116 27  42 61 14  81 118 28  23 34 8  27 40 9
+15 21 5  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  1 1 0  29 42 10  15 21 5  50 72 17
+74 107 25  45 64 15  102 148 35  80 116 27  84 121 28  111 160 38
+69 100 23  65 94 22  81 118 28  29 42 10  17 25 6  29 42 10
+23 34 8  2 3 1  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  3 4 1
+15 21 5  15 21 5  34 49 12  101 146 34  111 161 38  97 141 33
+97 141 33  119 172 41  117 170 40  116 167 40  118 170 40  118 171 40
+117 169 40  118 170 40  111 160 38  118 170 40  96 138 32  89 128 30
+81 118 28  11 16 4  10 15 3  1 1 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+3 4 1  3 4 1  34 49 12  101 146 34  79 115 27  111 160 38
+114 165 39  113 163 39  118 170 40  117 169 40  118 171 40  117 169 40
+116 167 40  119 172 41  113 163 39  92 132 31  105 151 36  113 163 39
+75 109 26  19 27 6  16 23 5  11 16 4  0 1 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  10 15 3
+80 116 27  106 153 36  105 151 36  114 165 39  118 170 40  118 171 40
+118 171 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 170 40  117 169 40  118 170 40  118 170 40
+117 170 40  75 109 26  75 109 26  34 49 12  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  3 4 1
+64 92 22  65 94 22  100 144 34  118 171 40  118 170 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  118 171 41  118 170 40  117 169 40
+109 158 37  105 151 36  104 150 35  47 69 16  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+42 61 14  115 167 39  118 170 40  117 169 40  117 169 40  117 169 40
+117 170 40  117 170 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  118 170 40  96 138 32  17 25 6  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  47 69 16
+114 165 39  117 168 40  117 170 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  118 170 40  117 169 40  117 169 40  117 169 40
+117 170 40  119 172 41  96 138 32  12 18 4  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  10 15 3
+32 47 11  105 151 36  118 170 40  117 169 40  117 169 40  116 168 40
+109 157 37  111 160 38  117 169 40  118 171 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  118 171 40  69 100 23  2 3 1
+0 0 0  0 0 0  0 0 0  0 0 0  19 27 6  101 146 34
+118 171 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 170 40
+118 171 40  115 166 39  107 154 36  111 161 38  117 169 40  117 169 40
+117 169 40  118 171 40  75 109 26  19 27 6  2 3 1  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  16 23 5
+89 128 30  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+111 160 38  92 132 31  79 115 27  96 138 32  115 166 39  119 171 41
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  118 170 40  109 157 37  26 37 9
+0 0 0  0 0 0  0 0 0  0 0 0  64 92 22  118 171 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  118 170 40  118 171 40  109 157 37
+89 128 30  81 118 28  100 144 34  115 166 39  117 169 40  117 169 40
+117 169 40  117 170 40  113 163 39  60 86 20  1 1 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+27 40 9  96 138 32  118 170 40  117 169 40  117 169 40  117 169 40
+117 170 40  117 169 40  101 146 34  67 96 23  55 80 19  84 121 28
+113 163 39  119 171 41  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  119 171 41  65 94 22
+0 0 0  0 0 0  0 0 0  15 21 5  101 146 34  118 171 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  118 170 40  118 171 40  104 150 35  69 100 23  53 76 18
+81 118 28  111 160 38  118 170 40  117 169 40  117 169 40  117 169 40
+117 169 40  114 165 39  69 100 23  10 15 3  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  1 1 0
+31 45 11  77 111 26  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  118 170 40  116 168 40  92 132 31  47 69 16
+38 55 13  81 118 28  113 163 39  119 171 41  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  118 171 41  92 132 31
+10 15 3  0 0 0  0 0 0  36 52 12  115 166 39  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  118 170 40
+118 171 40  102 148 35  64 92 22  34 49 12  65 94 22  106 153 36
+118 171 40  117 170 40  117 169 40  117 169 40  117 169 40  117 169 40
+118 170 40  107 154 36  55 80 19  15 21 5  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+29 42 10  101 146 34  118 171 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  118 171 40  113 163 39
+75 109 26  27 40 9  36 52 12  89 128 30  116 167 40  118 171 40
+117 169 40  117 169 40  117 169 40  117 169 40  118 170 40  104 150 35
+16 23 5  0 0 0  0 0 0  53 76 18  118 171 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  119 171 41  109 157 37
+67 96 23  23 34 8  42 61 14  96 138 32  118 170 40  118 170 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  74 107 25  10 15 3  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  31 45 11  101 146 34  118 170 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+119 171 41  102 148 35  47 69 16  14 20 5  50 72 17  102 148 35
+118 171 40  117 169 40  117 169 40  117 169 40  118 170 40  102 148 35
+15 21 5  0 0 0  0 0 0  50 72 17  118 170 40  117 169 40
+117 169 40  117 169 40  118 170 40  116 167 40  84 121 28  27 40 9
+19 27 6  74 107 25  114 165 39  118 171 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  75 109 26  10 15 4  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  38 55 13  102 148 35  118 171 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  118 170 40  115 167 39  77 111 26  17 25 6  19 27 6
+77 111 26  115 166 39  118 170 40  117 169 40  119 172 41  81 118 28
+3 4 1  0 0 0  0 0 0  27 40 9  111 160 38  118 170 40
+117 169 40  118 171 40  105 151 36  50 72 17  10 15 3  38 55 13
+100 144 34  118 171 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  79 115 27  15 21 5  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  10 15 3  64 92 22  111 160 38  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  118 171 40  96 138 32  32 47 11
+3 4 1  50 72 17  107 154 36  120 173 41  105 151 36  31 45 11
+0 0 0  0 0 0  0 0 0  3 4 1  65 94 22  117 169 40
+118 170 40  89 128 30  26 37 9  3 4 1  60 86 20  111 161 38
+118 171 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+97 141 33  36 52 12  1 1 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  14 20 5  75 109 26  117 168 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  118 171 40  107 154 36
+45 64 15  2 3 1  31 45 11  75 109 26  32 47 11  0 1 0
+0 0 0  0 0 0  0 0 0  0 0 0  10 15 3  55 80 19
+65 94 22  11 16 4  11 16 4  75 109 26  116 168 40  118 170 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  118 170 40  107 154 36
+47 69 16  3 4 1  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  12 18 4  69 100 23  111 161 38  118 171 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  118 170 40
+111 160 38  50 72 17  2 3 1  2 3 1  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  1 1 0
+1 1 0  12 18 4  81 118 28  118 170 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 170 40  118 171 40  101 146 34
+42 61 14  2 3 1  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  3 4 1  36 52 12  89 128 30
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+118 171 41  101 146 34  14 20 5  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  47 69 16  118 170 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 170 40  111 160 38  69 100 23  19 27 6
+0 1 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  11 16 4  69 100 23
+115 167 39  119 172 41  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+119 172 41  75 109 26  3 4 1  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  23 34 8  106 153 36  118 170 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+117 169 40  118 170 40  119 172 41  105 151 36  42 61 14  2 3 1
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  1 1 0  15 21 5
+45 64 15  80 116 27  114 165 39  118 170 40  117 169 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  119 172 41
+97 141 33  20 30 7  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  1 1 0  53 76 18  114 165 39  118 171 40  117 169 40
+117 169 40  117 169 40  117 169 40  117 169 40  117 169 40  117 169 40
+118 171 40  104 150 35  64 92 22  31 45 11  10 15 3  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  36 52 12  97 141 33  109 158 37  113 163 39  116 168 40
+117 169 40  117 170 40  118 170 40  119 172 41  115 167 39  84 121 28
+23 34 8  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  3 4 1  50 72 17  102 148 35  118 171 40
+119 171 41  118 170 40  117 169 40  117 169 40  115 166 39  111 161 38
+109 157 37  79 115 27  12 18 4  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  3 4 1  15 21 5  23 34 8  45 64 15  106 153 36
+116 167 40  111 160 38  101 146 34  79 115 27  42 61 14  10 15 3
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  1 1 0  20 30 7  60 86 20
+89 128 30  106 153 36  113 163 39  117 169 40  84 121 28  29 42 10
+19 27 6  10 15 3  2 3 1  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  16 23 5  38 55 13
+36 52 12  26 37 9  12 18 4  2 3 1  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  1 0 0  19 2 7  52 5 18
+78 7 27  88 8 31  81 7 29  56 5 19  25 2 9  3 0 1
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+3 4 1  19 27 6  31 45 11  38 55 13  32 47 11  3 4 1
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  3 0 1
+9 0 3  12 1 4  9 0 3  4 0 1  0 0 0  0 0 0
+0 0 0  0 0 0  28 3 10  99 9 35  156 14 55  182 16 64
+189 17 66  190 17 67  189 17 66  184 17 65  166 15 58  118 13 41
+45 4 16  3 0 1  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  11 1 4  52 5 18  101 9 35  134 12 47
+151 14 53  154 14 54  151 14 53  113 10 40  11 1 4  0 0 0
+3 0 1  67 6 24  159 14 56  190 17 67  190 17 67  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  190 17 67  191 17 67
+174 16 61  101 9 35  14 1 5  0 0 0  35 3 12  108 10 38
+122 11 43  122 11 43  112 10 39  87 8 30  50 5 17  13 1 5
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+3 0 1  56 5 19  141 13 49  182 16 64  191 17 67  191 17 67
+190 17 67  190 17 67  191 17 67  113 10 40  3 0 1  1 0 0
+79 7 28  180 16 63  190 17 67  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+189 17 66  188 17 66  122 11 43  11 1 4  41 4 14  176 16 62
+191 17 67  191 17 67  191 17 67  190 17 67  181 16 63  146 13 51
+75 7 26  10 1 4  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  7 1 2
+90 8 32  178 16 62  191 17 67  188 17 66  188 17 66  188 17 66
+188 17 66  190 17 67  141 13 49  22 2 8  0 0 0  41 4 14
+173 16 61  190 17 67  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  88 8 31  1 0 0  89 8 31
+185 17 65  189 17 66  188 17 66  188 17 66  189 17 66  191 17 67
+186 17 65  124 11 43  25 2 9  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  2 0 1  89 8 31
+184 17 65  189 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+190 17 67  151 14 53  34 3 12  0 0 0  0 0 0  79 7 28
+190 17 67  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  191 17 67  146 13 51  9 1 3  7 1 2
+108 10 38  187 17 66  189 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  190 17 67  141 13 49  22 2 8  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  52 5 18  176 16 62
+189 17 66  188 17 66  188 17 66  188 17 66  188 17 66  190 17 67
+151 14 53  38 3 13  0 0 0  0 0 0  0 0 0  50 5 17
+180 16 63  189 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  191 17 67  141 13 49  7 1 3  0 0 0
+11 1 4  112 10 39  187 17 66  189 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  190 17 67  113 10 40  5 0 2  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  7 1 3  132 12 46  191 17 67
+188 17 66  188 17 66  188 17 66  188 17 66  190 17 67  146 13 51
+35 3 12  0 0 0  0 0 0  0 0 0  0 0 0  5 0 2
+101 9 35  185 17 65  190 17 67  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  190 17 67  180 16 63  67 6 24  0 0 0  0 0 0
+0 0 0  11 1 4  108 10 38  186 17 65  189 17 66  188 17 66
+188 17 66  188 17 66  189 17 66  180 16 63  56 5 19  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  44 4 15  177 16 62  189 17 66
+188 17 66  188 17 66  189 17 66  189 17 66  134 12 47  28 3 10
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+8 1 3  79 7 28  159 14 56  188 17 66  191 17 67  190 17 67
+189 17 66  189 17 66  189 17 66  189 17 66  190 17 67  191 17 67
+188 17 66  158 14 55  72 7 25  4 0 1  0 0 0  0 0 0
+0 0 0  0 0 0  8 1 3  95 9 33  182 16 64  189 17 67
+188 17 66  188 17 66  188 17 66  191 17 67  122 11 43  3 0 1
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  88 8 31  190 17 67  188 17 66
+188 17 66  189 17 66  185 17 65  113 10 40  18 2 6  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  1 0 0  24 2 8  77 7 27  124 11 43  154 14 54
+168 15 59  173 16 61  173 16 61  168 15 59  154 14 54  124 11 43
+77 7 27  22 2 8  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  5 0 2  77 7 27  173 16 61
+190 17 67  188 17 66  188 17 66  190 17 67  164 15 57  23 2 8
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  1 0 0  118 13 41  191 17 67  188 17 66
+190 17 67  174 16 61  87 8 30  8 1 3  0 0 0  0 0 0
+0 0 0  0 0 0  10 1 4  29 3 10  40 4 14  36 3 13
+18 2 6  2 0 1  0 0 0  0 0 0  3 0 1  14 1 5
+26 2 9  33 3 11  32 3 11  25 2 9  13 1 5  3 0 1
+0 0 0  14 1 5  56 5 19  95 9 33  109 10 38  101 9 35
+77 7 27  35 3 12  5 0 2  0 0 0  1 0 0  56 5 19
+156 14 55  190 17 67  188 17 66  188 17 66  182 16 64  50 5 17
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  5 0 2  134 12 47  191 17 67  189 17 66
+151 14 53  52 5 18  2 0 1  0 0 0  0 0 0  1 0 0
+28 3 10  90 8 32  146 13 51  170 15 60  178 16 62  174 16 61
+158 14 55  112 10 39  40 4 14  1 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  3 0 1
+56 5 19  146 13 51  183 17 64  191 17 67  191 17 67  191 17 67
+188 17 66  173 16 61  122 11 43  41 4 14  1 0 0  0 0 0
+30 3 10  124 11 43  185 17 65  190 17 67  187 17 66  67 6 24
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  6 1 2  134 12 47  168 15 59  99 9 35
+21 2 7  0 0 0  0 0 0  0 0 0  6 1 2  77 7 27
+162 15 57  190 17 67  191 17 67  189 17 66  189 17 66  189 17 66
+190 17 67  191 17 67  169 15 59  75 7 26  3 0 1  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  2 0 1  79 7 28
+178 16 62  191 17 67  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  189 17 66  191 17 67  170 15 60  79 7 28  5 0 2
+0 0 0  10 1 3  78 7 27  159 14 56  188 17 66  75 7 26
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  1 0 0  35 3 12  29 3 10  2 0 1
+0 0 0  0 0 0  0 0 0  9 1 3  101 9 35  183 17 64
+190 17 67  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  190 17 67  178 16 63  67 6 23  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  52 5 18  174 16 61
+190 17 67  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  190 17 67  182 16 64  89 8 31
+4 0 1  0 0 0  0 0 0  25 2 9  73 7 26  31 3 11
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  4 0 1  98 9 34  187 17 66  189 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  190 17 67  158 14 55  25 2 9
+0 0 0  0 0 0  0 0 0  8 1 3  134 12 47  191 17 67
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  189 17 66  180 16 63
+68 6 24  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  6 1 2  19 2 7  3 0 1  0 0 0  0 0 0
+0 0 0  0 0 0  65 6 23  180 16 63  189 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  189 17 66  83 8 29
+0 0 0  0 0 0  0 0 0  41 4 14  177 16 62  189 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  190 17 67
+159 14 56  28 3 10  0 0 0  0 0 0  0 0 0  23 2 8
+41 4 14  5 0 2  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+23 2 8  113 10 40  159 14 56  65 6 23  0 0 0  0 0 0
+0 0 0  16 1 6  146 13 51  191 17 67  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  191 17 67  132 12 46
+5 0 2  0 0 0  0 0 0  77 7 27  189 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+190 17 67  98 9 34  0 0 0  0 0 0  12 1 4  134 12 47
+178 16 63  108 10 38  16 1 6  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  30 3 10
+141 13 49  190 17 67  191 17 67  134 12 47  6 1 2  0 0 0
+0 0 0  68 6 24  186 17 65  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  190 17 67  156 14 55
+14 1 5  0 0 0  0 0 0  98 9 34  191 17 67  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+190 17 67  156 14 55  19 2 7  0 0 0  47 4 16  181 16 63
+190 17 67  189 17 66  126 14 44  17 2 6  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  16 1 6  134 12 47
+191 17 67  188 17 66  190 17 67  162 15 57  19 2 7  0 0 0
+3 0 1  123 11 43  191 17 67  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  190 17 67  163 15 57
+20 2 7  0 0 0  0 0 0  101 9 35  191 17 67  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  182 16 64  52 5 18  0 0 0  73 7 26  188 17 66
+188 17 66  188 17 66  189 17 66  109 10 38  5 0 2  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  95 9 33  189 17 66
+188 17 66  188 17 66  189 17 66  171 15 60  29 3 10  0 0 0
+16 1 6  156 14 55  190 17 67  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  190 17 67  158 14 55
+17 2 6  0 0 0  0 0 0  85 8 30  190 17 67  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  189 17 66  81 7 29  0 0 0  85 8 30  190 17 67
+188 17 66  188 17 66  189 17 66  180 16 63  56 5 19  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  25 2 9  162 15 57  190 17 67
+188 17 66  188 17 66  189 17 66  173 16 61  31 3 11  0 0 0
+30 3 10  171 15 60  189 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  191 17 67  141 13 49
+7 1 2  0 0 0  0 0 0  56 5 19  183 17 64  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  191 17 67  98 9 34  0 0 0  88 8 31  190 17 67
+188 17 66  188 17 66  188 17 66  191 17 67  124 11 43  5 0 2
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  68 6 24  187 17 66  188 17 66
+188 17 66  188 17 66  189 17 66  170 15 60  28 3 10  0 0 0
+34 3 12  174 16 61  189 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  191 17 67  101 9 35
+0 0 0  0 0 0  0 0 0  21 2 7  159 14 56  190 17 67
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  191 17 67  98 9 34  0 0 0  81 7 29  189 17 66
+188 17 66  188 17 66  188 17 66  189 17 66  168 15 59  28 3 10
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  109 10 38  191 17 67  188 17 66
+188 17 66  188 17 66  190 17 67  163 15 57  21 2 7  0 0 0
+26 2 9  168 15 59  189 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  189 17 66  180 16 63  47 4 16
+0 0 0  0 0 0  0 0 0  0 0 0  108 10 38  190 17 67
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  189 17 66  78 7 27  0 0 0  68 6 24  187 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  183 17 64  56 5 19
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  3 0 1  131 12 46  191 17 67  188 17 66
+188 17 66  188 17 66  190 17 67  151 14 53  12 1 4  0 0 0
+11 1 4  146 13 51  190 17 67  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  191 17 67  126 14 44  7 1 2
+0 0 0  0 0 0  0 0 0  0 0 0  32 3 11  164 15 58
+190 17 67  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+189 17 66  178 16 62  44 4 15  0 0 0  50 5 17  182 16 64
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  72 7 25
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  5 0 2  134 12 47  191 17 67  188 17 66
+188 17 66  188 17 66  191 17 67  131 12 46  3 0 1  0 0 0
+0 0 0  101 9 35  190 17 67  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  190 17 67  170 15 60  44 4 15  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  77 7 27
+183 17 64  189 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+191 17 67  134 12 47  9 1 3  0 0 0  31 3 11  171 15 60
+189 17 66  188 17 66  188 17 66  188 17 66  188 17 66  72 7 25
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  2 0 1  124 11 43  191 17 67  188 17 66
+188 17 66  188 17 66  191 17 67  101 9 35  0 0 0  0 0 0
+0 0 0  35 3 12  168 15 59  190 17 67  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  189 17 66  182 16 64  77 7 27  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  6 1 2
+99 9 35  185 17 65  189 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  189 17 66
+177 16 62  56 5 19  0 0 0  0 0 0  13 1 5  151 14 53
+190 17 67  188 17 66  188 17 66  188 17 66  185 17 65  56 5 19
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  99 9 35  191 17 67  188 17 66
+188 17 66  188 17 66  186 17 65  65 6 23  0 0 0  0 0 0
+0 0 0  0 0 0  79 7 28  182 16 64  190 17 67  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+191 17 67  177 16 62  83 8 29  4 0 1  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+8 1 3  89 8 31  175 16 62  191 17 67  189 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  190 17 67  181 16 63
+85 8 30  3 0 1  0 0 0  0 0 0  1 0 0  118 13 41
+191 17 67  188 17 66  188 17 66  189 17 66  173 16 61  34 3 12
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  56 5 19  183 17 64  188 17 66
+188 17 66  189 17 66  169 15 59  30 3 10  0 0 0  0 0 0
+0 0 0  0 0 0  5 0 2  83 8 29  173 16 61  191 17 67
+190 17 67  189 17 66  189 17 66  190 17 67  191 17 67  187 17 66
+151 14 53  56 5 19  3 0 1  0 0 0  16 1 6  50 5 17
+79 7 28  95 9 33  95 9 33  75 7 26  41 4 14  10 1 4
+0 0 0  2 0 1  50 5 17  132 12 46  178 16 62  190 17 67
+191 17 67  191 17 67  191 17 67  186 17 65  154 14 54  68 6 24
+4 0 1  0 0 0  0 0 0  0 0 0  0 0 0  72 7 25
+187 17 66  188 17 66  188 17 66  191 17 67  141 13 49  9 1 3
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  14 1 5  151 14 53  190 17 67
+188 17 66  191 17 67  131 12 46  5 0 2  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  2 0 1  44 4 15  113 10 40
+156 14 55  173 16 61  174 16 61  164 15 58  134 12 47  77 7 27
+18 2 6  0 0 0  16 1 6  85 8 30  151 14 53  182 16 64
+189 17 66  191 17 67  190 17 67  188 17 66  177 16 62  141 13 49
+68 6 24  8 1 3  0 0 0  8 1 3  44 4 15  88 8 31
+113 10 40  122 11 43  108 10 38  67 6 24  20 2 7  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  28 3 10
+166 15 58  190 17 67  188 17 66  187 17 66  79 7 28  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  73 7 26  185 17 65
+189 17 66  184 17 65  65 6 23  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  2 0 1
+17 2 6  32 3 11  34 3 12  22 2 8  6 1 2  0 0 0
+0 0 0  38 3 13  141 13 49  188 17 66  190 17 67  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  189 17 66  191 17 67
+184 17 65  122 11 43  21 2 7  0 0 0  0 0 0  0 0 0
+0 0 0  1 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  1 0 0
+108 10 38  191 17 67  191 17 67  141 13 49  16 1 6  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  8 1 3  112 10 39
+186 17 65  124 11 43  10 1 4  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+36 3 13  156 14 55  191 17 67  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+189 17 66  190 17 67  134 12 47  18 2 6  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  7 1 2  41 4 14  75 7 26  66 5 23  19 2 7
+26 2 9  144 13 50  154 14 54  40 4 14  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  13 1 5
+56 5 19  19 2 7  0 0 0  7 1 2  29 3 10  35 3 12
+19 2 7  2 0 1  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  13 1 5
+134 12 47  191 17 67  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  189 17 67  108 10 38  3 0 1  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  1 0 0
+40 4 14  124 11 43  177 16 62  188 17 66  187 17 66  144 13 50
+24 2 8  17 2 6  22 2 8  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  19 2 7  122 11 43  171 15 60  175 16 62
+159 14 56  112 10 39  40 4 14  2 0 1  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  72 7 25
+186 17 65  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  189 17 66  174 16 61  41 4 14  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  3 0 1  72 7 25
+168 15 59  191 17 67  189 17 66  188 17 66  188 17 66  190 17 67
+95 9 33  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  95 9 33  191 17 67  189 17 66  189 17 66
+190 17 67  191 17 67  171 15 60  90 8 32  12 1 4  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  5 0 2  132 12 46
+191 17 67  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  190 17 67  98 9 34  0 0 0
+0 0 0  0 0 0  0 0 0  5 0 2  88 8 31  180 16 63
+190 17 67  188 17 66  188 17 66  188 17 66  188 17 66  191 17 67
+146 13 51  11 1 4  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  9 1 3  144 13 50  191 17 67  188 17 66  188 17 66
+188 17 66  188 17 66  189 17 66  187 17 66  123 11 43  20 2 7
+0 0 0  0 0 0  0 0 0  0 0 0  21 2 7  163 15 57
+190 17 67  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  191 17 67  134 12 47  5 0 2
+0 0 0  0 0 0  3 0 1  88 8 31  182 16 64  189 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  189 17 66
+171 15 60  31 3 11  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  20 2 7  162 15 57  190 17 67  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  190 17 67  132 12 46
+20 2 7  0 0 0  0 0 0  0 0 0  32 3 11  173 16 61
+189 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  190 17 67  151 14 53  12 1 4
+0 0 0  0 0 0  72 7 25  180 16 63  189 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+181 16 63  47 4 16  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  21 2 7  163 15 57  190 17 67  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  190 17 67
+122 11 43  9 1 3  0 0 0  0 0 0  30 3 10  171 15 60
+189 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  190 17 67  146 13 51  10 1 4
+0 0 0  38 3 13  166 15 58  190 17 67  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+183 17 64  52 5 18  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  13 1 5  154 14 54  190 17 67  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+186 17 65  79 7 28  0 0 0  0 0 0  14 1 5  156 14 54
+190 17 67  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  191 17 67  124 11 43  2 0 1
+5 0 2  122 11 43  191 17 67  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+182 16 64  47 4 16  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  3 0 1  126 14 44  191 17 67  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+190 17 67  158 14 55  23 2 8  0 0 0  1 0 0  113 10 40
+191 17 67  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  78 7 27  0 0 0
+47 4 16  177 16 62  189 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  189 17 66
+173 16 61  34 3 12  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  85 8 30  189 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  79 7 28  0 0 0  0 0 0  47 4 16
+175 16 62  189 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  190 17 67  156 14 55  22 2 8  0 0 0
+109 10 38  191 17 67  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  190 17 67
+151 14 53  13 1 5  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  35 3 12  173 16 61  189 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  191 17 67  134 12 47  7 1 2  0 0 0  3 0 1
+99 9 35  188 17 66  189 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  189 17 66  181 16 63  68 6 24  0 0 0  18 2 6
+156 14 55  190 17 67  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  190 17 67
+101 9 35  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  3 0 1  118 13 41  191 17 67  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  189 17 66  168 15 59  28 3 10  0 0 0  0 0 0
+12 1 4  113 10 40  187 17 66  189 17 67  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+190 17 67  180 16 63  88 8 31  4 0 1  0 0 0  47 4 16
+180 16 63  189 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  190 17 67  168 15 59
+36 3 13  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  38 3 13  164 15 58  190 17 67
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  182 16 64  50 5 17  0 0 0  0 0 0
+0 0 0  11 1 4  90 8 32  169 15 59  190 17 67  190 17 67
+189 17 66  189 17 66  189 17 66  189 17 66  191 17 67  189 17 66
+158 14 55  68 6 24  4 0 1  0 0 0  0 0 0  73 7 26
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  189 17 66  185 17 65  83 8 29
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  65 6 23  174 16 61
+190 17 67  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  185 17 65  56 5 19  0 0 0  0 0 0
+0 0 0  0 0 0  2 0 1  35 3 12  99 9 35  146 13 51
+170 15 60  177 16 62  177 16 62  166 15 58  141 13 49  85 8 30
+24 2 8  0 0 0  0 0 0  0 0 0  0 0 0  85 8 30
+190 17 67  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  189 17 66  112 10 39  8 1 3
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  1 0 0  68 6 24
+170 15 60  191 17 67  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  182 16 64  50 5 17  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  1 0 0  11 1 4
+28 3 10  40 4 14  38 3 13  25 2 9  8 1 3  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  78 7 27
+189 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  189 17 66  187 17 66  113 10 40  14 1 5  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  1 0 0
+47 4 16  141 13 49  186 17 65  191 17 67  190 17 67  189 17 66
+189 17 66  191 17 67  156 14 55  20 2 7  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  44 4 15
+178 16 62  190 17 67  188 17 66  188 17 66  188 17 66  190 17 67
+191 17 67  173 16 61  90 8 32  10 1 4  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  14 1 5  68 6 24  131 12 46  162 15 57  174 16 61
+171 15 60  146 13 51  56 5 19  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  3 0 1  14 1 5  29 3 10
+41 4 14  47 4 16  50 5 17  45 4 16  34 3 12  18 2 6
+5 0 2  0 0 0  0 0 0  0 0 0  0 0 0  5 0 2
+90 8 32  169 15 59  185 17 65  187 17 66  182 16 64  163 15 57
+113 10 40  41 4 14  2 0 1  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  5 0 2  21 2 7  34 3 12
+29 3 10  11 1 4  0 0 0  0 0 0  0 0 0  0 0 0
+3 0 1  32 3 11  79 7 28  124 11 43  154 14 54  171 15 60
+180 16 63  182 16 64  182 16 64  180 16 63  174 16 61  159 14 56
+132 12 46  88 8 31  34 3 12  3 0 1  0 0 0  0 0 0
+3 0 1  29 3 10  56 5 19  65 6 23  50 5 17  23 2 8
+3 0 1  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  25 2 9
+109 10 38  169 15 59  189 17 66  191 17 67  190 17 67  189 17 66
+189 17 66  188 17 66  188 17 66  188 17 66  189 17 66  190 17 67
+191 17 67  190 17 67  171 15 60  98 9 34  10 1 3  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  14 1 5  141 13 49
+191 17 67  189 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  189 17 67  186 17 65  65 6 23  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  23 2 8  166 15 58
+190 17 67  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  189 17 66  176 16 62  45 4 16  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  1 0 0  83 8 29
+183 17 64  189 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+188 17 66  189 17 66  185 17 65  95 9 33  3 0 1  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  5 0 2
+85 8 30  176 16 62  191 17 67  188 17 66  188 17 66  188 17 66
+188 17 66  188 17 66  188 17 66  188 17 66  188 17 66  188 17 66
+191 17 67  180 16 63  95 9 33  7 1 3  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+2 0 1  52 5 18  141 13 49  185 17 65  191 17 67  189 17 67
+189 17 66  188 17 66  188 17 66  189 17 66  191 17 67  187 17 66
+146 13 51  56 5 19  4 0 1  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  14 1 5  68 6 24  131 12 46  166 15 58
+180 16 63  183 17 64  180 16 63  168 15 59  134 12 47  75 7 26
+17 2 6  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  5 0 2  24 2 8
+44 4 15  52 5 18  45 4 16  26 2 9  6 1 2  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0  0 0 0  0 0 0  0 0 0
+0 0 0  0 0 0  0 0 0
diff --git a/drivers/w1/masters/w1-gpio.c b/drivers/w1/masters/w1-gpio.c
index a39fa8bf866a..e33a6e4030c8 100644
--- a/drivers/w1/masters/w1-gpio.c
+++ b/drivers/w1/masters/w1-gpio.c
@@ -35,7 +35,7 @@ static u8 w1_gpio_set_pullup(void *data, int delay)
 			 * This will OVERRIDE open drain emulation and force-pull
 			 * the line high for some time.
 			 */
-			gpiod_set_raw_value(ddata->gpiod, 1);
+			gpiod_direction_output_raw(ddata->gpiod, 1);
 			msleep(ddata->pullup_duration);
 			/*
 			 * This will simply set the line as input since we are doing
@@ -89,6 +89,9 @@ static int w1_gpio_probe(struct platform_device *pdev)
 	if (!master)
 		return -ENOMEM;
 
+	if (device_property_present(dev, "raspberrypi,delay-needs-poll"))
+		master->delay_needs_poll = true;
+
 	ddata->gpiod = devm_gpiod_get_index(dev, NULL, 0, gflags);
 	if (IS_ERR(ddata->gpiod))
 		return dev_err_probe(dev, PTR_ERR(ddata->gpiod), "gpio_request (pin) failed\n");
diff --git a/drivers/w1/w1.c b/drivers/w1/w1.c
index 34a4ade849a0..1211a8e413ab 100644
--- a/drivers/w1/w1.c
+++ b/drivers/w1/w1.c
@@ -733,8 +733,10 @@ int w1_attach_slave_device(struct w1_master *dev, struct w1_reg_num *rn)
 	atomic_set(&sl->refcnt, 1);
 	atomic_inc(&sl->master->refcnt);
 	dev->slave_count++;
+#if 0
 	dev_info(&dev->dev, "Attaching one wire slave %02x.%012llx crc %02x\n",
 		  rn->family, (unsigned long long)rn->id, rn->crc);
+#endif
 
 	/* slave modules need to be loaded in a context with unlocked mutex */
 	mutex_unlock(&dev->mutex);
diff --git a/drivers/w1/w1_io.c b/drivers/w1/w1_io.c
index db3c9522a8a2..b495624984bd 100644
--- a/drivers/w1/w1_io.c
+++ b/drivers/w1/w1_io.c
@@ -6,6 +6,7 @@
 #include <asm/io.h>
 
 #include <linux/delay.h>
+#include <linux/ktime.h>
 #include <linux/moduleparam.h>
 #include <linux/module.h>
 
@@ -36,9 +37,21 @@ static u8 w1_crc8_table[] = {
 	116, 42, 200, 150, 21, 75, 169, 247, 182, 232, 10, 84, 215, 137, 107, 53
 };
 
-static void w1_delay(unsigned long tm)
+static void w1_delay(struct w1_master *dev, unsigned long tm)
 {
-	udelay(tm * w1_delay_parm);
+	ktime_t start, delta;
+
+	if (!dev->bus_master->delay_needs_poll) {
+		udelay(tm * w1_delay_parm);
+		return;
+	}
+
+	start = ktime_get();
+	delta = ktime_add(start, ns_to_ktime(1000 * tm * w1_delay_parm));
+	do {
+		dev->bus_master->read_bit(dev->bus_master->data);
+		udelay(1);
+	} while (ktime_before(ktime_get(), delta));
 }
 
 static void w1_write_bit(struct w1_master *dev, int bit);
@@ -77,14 +90,14 @@ static void w1_write_bit(struct w1_master *dev, int bit)
 
 	if (bit) {
 		dev->bus_master->write_bit(dev->bus_master->data, 0);
-		w1_delay(6);
+		w1_delay(dev, 6);
 		dev->bus_master->write_bit(dev->bus_master->data, 1);
-		w1_delay(64);
+		w1_delay(dev, 64);
 	} else {
 		dev->bus_master->write_bit(dev->bus_master->data, 0);
-		w1_delay(60);
+		w1_delay(dev, 60);
 		dev->bus_master->write_bit(dev->bus_master->data, 1);
-		w1_delay(10);
+		w1_delay(dev, 10);
 	}
 
 	if(w1_disable_irqs) local_irq_restore(flags);
@@ -164,14 +177,14 @@ static u8 w1_read_bit(struct w1_master *dev)
 	/* sample timing is critical here */
 	local_irq_save(flags);
 	dev->bus_master->write_bit(dev->bus_master->data, 0);
-	w1_delay(6);
+	w1_delay(dev, 6);
 	dev->bus_master->write_bit(dev->bus_master->data, 1);
-	w1_delay(9);
+	w1_delay(dev, 9);
 
 	result = dev->bus_master->read_bit(dev->bus_master->data);
 	local_irq_restore(flags);
 
-	w1_delay(55);
+	w1_delay(dev, 55);
 
 	return result & 0x1;
 }
@@ -333,16 +346,16 @@ int w1_reset_bus(struct w1_master *dev)
 		 * cpu for such a short amount of time AND get it back in
 		 * the maximum amount of time.
 		 */
-		w1_delay(500);
+		w1_delay(dev, 500);
 		dev->bus_master->write_bit(dev->bus_master->data, 1);
-		w1_delay(70);
+		w1_delay(dev, 70);
 
 		result = dev->bus_master->read_bit(dev->bus_master->data) & 0x1;
 		/* minimum 70 (above) + 430 = 500 us
 		 * There aren't any timing requirements between a reset and
 		 * the following transactions.  Sleeping is safe here.
 		 */
-		/* w1_delay(430); min required time */
+		/* w1_delay(dev, 430); min required time */
 		msleep(1);
 	}
 
diff --git a/drivers/watchdog/bcm2835_wdt.c b/drivers/watchdog/bcm2835_wdt.c
index bb001c5d7f17..2ce96ba0ee39 100644
--- a/drivers/watchdog/bcm2835_wdt.c
+++ b/drivers/watchdog/bcm2835_wdt.c
@@ -32,13 +32,7 @@
 #define PM_RSTC_WRCFG_SET		0x00000030
 #define PM_RSTC_WRCFG_FULL_RESET	0x00000020
 #define PM_RSTC_RESET			0x00000102
-
-/*
- * The Raspberry Pi firmware uses the RSTS register to know which partition
- * to boot from. The partition value is spread into bits 0, 2, 4, 6, 8, 10.
- * Partition 63 is a special partition used by the firmware to indicate halt.
- */
-#define PM_RSTS_RASPBERRYPI_HALT	0x555
+#define PM_RSTS_PARTITION_CLR          0xfffffaaa
 
 #define SECS_TO_WDOG_TICKS(x) ((x) << 16)
 #define WDOG_TICKS_TO_SECS(x) ((x) >> 16)
@@ -98,9 +92,24 @@ static unsigned int bcm2835_wdt_get_timeleft(struct watchdog_device *wdog)
 	return WDOG_TICKS_TO_SECS(ret & PM_WDOG_TIME_SET);
 }
 
-static void __bcm2835_restart(struct bcm2835_wdt *wdt)
+/*
+ * The Raspberry Pi firmware uses the RSTS register to know which partiton
+ * to boot from. The partiton value is spread into bits 0, 2, 4, 6, 8, 10.
+ * Partiton 63 is a special partition used by the firmware to indicate halt.
+ */
+
+static void __bcm2835_restart(struct bcm2835_wdt *wdt, u8 partition)
 {
-	u32 val;
+	u32 val, rsts;
+
+	rsts = (partition & BIT(0)) | ((partition & BIT(1)) << 1) |
+	       ((partition & BIT(2)) << 2) | ((partition & BIT(3)) << 3) |
+	       ((partition & BIT(4)) << 4) | ((partition & BIT(5)) << 5);
+
+	val = readl_relaxed(wdt->base + PM_RSTS);
+	val &= PM_RSTS_PARTITION_CLR;
+	val |= PM_PASSWORD | rsts;
+	writel_relaxed(val, wdt->base + PM_RSTS);
 
 	/* use a timeout of 10 ticks (~150us) */
 	writel_relaxed(10 | PM_PASSWORD, wdt->base + PM_WDOG);
@@ -118,7 +127,15 @@ static int bcm2835_restart(struct watchdog_device *wdog,
 {
 	struct bcm2835_wdt *wdt = watchdog_get_drvdata(wdog);
 
-	__bcm2835_restart(wdt);
+	unsigned long val;
+	u8 partition = 0;
+
+	// Allow extra arguments separated by spaces after
+	// the partition number.
+	if (data && sscanf(data, "%lu", &val) && val < 63)
+		partition = val;
+
+	__bcm2835_restart(wdt, partition);
 
 	return 0;
 }
@@ -153,19 +170,9 @@ static struct watchdog_device bcm2835_wdt_wdd = {
 static void bcm2835_power_off(void)
 {
 	struct bcm2835_wdt *wdt = bcm2835_power_off_wdt;
-	u32 val;
-
-	/*
-	 * We set the watchdog hard reset bit here to distinguish this reset
-	 * from the normal (full) reset. bootcode.bin will not reboot after a
-	 * hard reset.
-	 */
-	val = readl_relaxed(wdt->base + PM_RSTS);
-	val |= PM_PASSWORD | PM_RSTS_RASPBERRYPI_HALT;
-	writel_relaxed(val, wdt->base + PM_RSTS);
 
-	/* Continue with normal reset mechanism */
-	__bcm2835_restart(wdt);
+	/* Partition 63 tells the firmware that this is a halt */
+	__bcm2835_restart(wdt, 63);
 }
 
 static int bcm2835_wdt_probe(struct platform_device *pdev)
diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c
index 4190cb800cc4..c7499e9127fc 100644
--- a/drivers/watchdog/watchdog_dev.c
+++ b/drivers/watchdog/watchdog_dev.c
@@ -943,7 +943,7 @@ static int watchdog_release(struct inode *inode, struct file *file)
 {
 	struct watchdog_core_data *wd_data = file->private_data;
 	struct watchdog_device *wdd;
-	int err = -EBUSY;
+	int err = EBUSY; /* +ve because this may be expected behaviour */
 	bool running;
 
 	mutex_lock(&wd_data->lock);
@@ -967,6 +967,9 @@ static int watchdog_release(struct inode *inode, struct file *file)
 	if (err < 0) {
 		pr_crit("watchdog%d: watchdog did not stop!\n", wdd->id);
 		watchdog_ping(wdd);
+	} else if (err > 0) {
+		pr_info("watchdog%d: watchdog left running\n", wdd->id);
+		watchdog_ping(wdd);
 	}
 
 	watchdog_update_worker(wdd);
diff --git a/include/drm/drm_blend.h b/include/drm/drm_blend.h
index 88bdfec3bd88..a84c58f3f13c 100644
--- a/include/drm/drm_blend.h
+++ b/include/drm/drm_blend.h
@@ -34,6 +34,7 @@
 struct drm_device;
 struct drm_atomic_state;
 struct drm_plane;
+struct drm_connector;
 
 static inline bool drm_rotation_90_or_270(unsigned int rotation)
 {
@@ -58,4 +59,8 @@ int drm_atomic_normalize_zpos(struct drm_device *dev,
 			      struct drm_atomic_state *state);
 int drm_plane_create_blend_mode_property(struct drm_plane *plane,
 					 unsigned int supported_modes);
+
+int drm_connector_create_rotation_property(struct drm_connector *conn,
+					   unsigned int rotation,
+					   unsigned int supported_rotations);
 #endif
diff --git a/include/drm/drm_color_mgmt.h b/include/drm/drm_color_mgmt.h
index ed81741036d7..00359830a4be 100644
--- a/include/drm/drm_color_mgmt.h
+++ b/include/drm/drm_color_mgmt.h
@@ -91,6 +91,9 @@ int drm_plane_create_color_properties(struct drm_plane *plane,
 				      enum drm_color_encoding default_encoding,
 				      enum drm_color_range default_range);
 
+int drm_plane_create_chroma_siting_properties(struct drm_plane *plane,
+						int32_t default_chroma_siting_h, int32_t default_chroma_siting_v);
+
 /**
  * enum drm_color_lut_tests - hw-specific LUT tests to perform
  *
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 1e2b25e204cb..758b218608cb 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -1139,6 +1139,11 @@ struct drm_connector_state {
 	 * @drm_atomic_helper_connector_hdmi_check().
 	 */
 	struct drm_connector_hdmi_state hdmi;
+
+	/**
+	 * @rotation: Connector property to rotate the maximum output image.
+	 */
+	u32 rotation;
 };
 
 /**
@@ -1926,6 +1931,12 @@ struct drm_connector {
 	 */
 	struct drm_property *broadcast_rgb_property;
 
+	/**
+	 * @rotation_property: Optional DRM property controlling rotation of the
+	 * output.
+	 */
+	struct drm_property *rotation_property;
+
 #define DRM_CONNECTOR_POLL_HPD (1 << 0)
 #define DRM_CONNECTOR_POLL_CONNECT (1 << 1)
 #define DRM_CONNECTOR_POLL_DISCONNECT (1 << 2)
diff --git a/include/drm/drm_crtc.h b/include/drm/drm_crtc.h
index 8b48a1974da3..dd7ad1a44717 100644
--- a/include/drm/drm_crtc.h
+++ b/include/drm/drm_crtc.h
@@ -192,7 +192,7 @@ struct drm_crtc_state {
 	 * @plane_mask: Bitmask of drm_plane_mask(plane) of planes attached to
 	 * this CRTC.
 	 */
-	u32 plane_mask;
+	u64 plane_mask;
 
 	/**
 	 * @connector_mask: Bitmask of drm_connector_mask(connector) of
diff --git a/include/drm/drm_gem.h b/include/drm/drm_gem.h
index ff251745de18..30740bb1cf43 100644
--- a/include/drm/drm_gem.h
+++ b/include/drm/drm_gem.h
@@ -474,6 +474,9 @@ void drm_gem_object_release(struct drm_gem_object *obj);
 void drm_gem_object_free(struct kref *kref);
 int drm_gem_object_init(struct drm_device *dev,
 			struct drm_gem_object *obj, size_t size);
+int drm_gem_object_init_with_mnt(struct drm_device *dev,
+				 struct drm_gem_object *obj, size_t size,
+				 struct vfsmount *gemfs);
 void drm_gem_private_object_init(struct drm_device *dev,
 				 struct drm_gem_object *obj, size_t size);
 void drm_gem_private_object_fini(struct drm_gem_object *obj);
diff --git a/include/drm/drm_gem_shmem_helper.h b/include/drm/drm_gem_shmem_helper.h
index efbc9f27312b..d22e3fb53631 100644
--- a/include/drm/drm_gem_shmem_helper.h
+++ b/include/drm/drm_gem_shmem_helper.h
@@ -97,6 +97,9 @@ struct drm_gem_shmem_object {
 	container_of(obj, struct drm_gem_shmem_object, base)
 
 struct drm_gem_shmem_object *drm_gem_shmem_create(struct drm_device *dev, size_t size);
+struct drm_gem_shmem_object *drm_gem_shmem_create_with_mnt(struct drm_device *dev,
+							   size_t size,
+							   struct vfsmount *gemfs);
 void drm_gem_shmem_free(struct drm_gem_shmem_object *shmem);
 
 void drm_gem_shmem_put_pages(struct drm_gem_shmem_object *shmem);
diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h
index f725f8654611..2836d25b4220 100644
--- a/include/drm/drm_mipi_dsi.h
+++ b/include/drm/drm_mipi_dsi.h
@@ -114,29 +114,43 @@ struct mipi_dsi_host *of_find_mipi_dsi_host_by_node(struct device_node *node);
 
 /* DSI mode flags */
 
-/* video mode */
+/* Video mode display.
+ * Not set denotes a command mode display.
+ */
 #define MIPI_DSI_MODE_VIDEO		BIT(0)
-/* video burst mode */
+/* Video burst mode.
+ * Link frequency to be configured via platform configuration.
+ * This should always be set in conjunction with MIPI_DSI_MODE_VIDEO.
+ * (DSI spec V1.1 8.11.4)
+ */
 #define MIPI_DSI_MODE_VIDEO_BURST	BIT(1)
-/* video pulse mode */
+/* Video pulse mode.
+ * Not set denotes sync event mode. (DSI spec V1.1 8.11.2)
+ */
 #define MIPI_DSI_MODE_VIDEO_SYNC_PULSE	BIT(2)
-/* enable auto vertical count mode */
+/* Enable auto vertical count mode */
 #define MIPI_DSI_MODE_VIDEO_AUTO_VERT	BIT(3)
-/* enable hsync-end packets in vsync-pulse and v-porch area */
+/* Enable hsync-end packets in vsync-pulse and v-porch area */
 #define MIPI_DSI_MODE_VIDEO_HSE		BIT(4)
-/* disable hfront-porch area */
+/* Transmit NULL packets or LP mode during hfront-porch area.
+ * Not set denotes sending a blanking packet instead. (DSI spec V1.1 8.11.1)
+ */
 #define MIPI_DSI_MODE_VIDEO_NO_HFP	BIT(5)
-/* disable hback-porch area */
+/* Transmit NULL packets or LP mode during hback-porch area.
+ * Not set denotes sending a blanking packet instead. (DSI spec V1.1 8.11.1)
+ */
 #define MIPI_DSI_MODE_VIDEO_NO_HBP	BIT(6)
-/* disable hsync-active area */
+/* Transmit NULL packets or LP mode during hsync-active area.
+ * Not set denotes sending a blanking packet instead. (DSI spec V1.1 8.11.1)
+ */
 #define MIPI_DSI_MODE_VIDEO_NO_HSA	BIT(7)
-/* flush display FIFO on vsync pulse */
+/* Flush display FIFO on vsync pulse */
 #define MIPI_DSI_MODE_VSYNC_FLUSH	BIT(8)
-/* disable EoT packets in HS mode */
+/* Disable EoT packets in HS mode. (DSI spec V1.1 8.1)  */
 #define MIPI_DSI_MODE_NO_EOT_PACKET	BIT(9)
-/* device supports non-continuous clock behavior (DSI spec 5.6.1) */
+/* Device supports non-continuous clock behavior (DSI spec V1.1 5.6.1) */
 #define MIPI_DSI_CLOCK_NON_CONTINUOUS	BIT(10)
-/* transmit data in low power */
+/* Transmit data in low power */
 #define MIPI_DSI_MODE_LPM		BIT(11)
 /* transmit data ending at the same time for all lanes within one hsync */
 #define MIPI_DSI_HS_PKT_END_ALIGNED	BIT(12)
diff --git a/include/drm/drm_plane.h b/include/drm/drm_plane.h
index dd718c62ac31..eddf344053fd 100644
--- a/include/drm/drm_plane.h
+++ b/include/drm/drm_plane.h
@@ -183,6 +183,24 @@ struct drm_plane_state {
 	 */
 	enum drm_color_range color_range;
 
+	/**
+	 * @chroma_siting_h:
+	 *
+	 * Location of chroma samples horizontally compared to luma
+	 * 0 means chroma is sited with left luma
+	 * 0x8000 is interstitial. 0x10000 is sited with right luma
+	 */
+	int32_t chroma_siting_h;
+
+	/**
+	 * @chroma_siting_v:
+	 *
+	 * Location of chroma samples vertically compared to luma
+	 * 0 means chroma is sited with top luma
+	 * 0x8000 is interstitial. 0x10000 is sited with bottom luma
+	 */
+	int32_t chroma_siting_v;
+
 	/**
 	 * @fb_damage_clips:
 	 *
@@ -786,6 +804,24 @@ struct drm_plane {
 	 * @kmsg_panic: Used to register a panic notifier for this plane
 	 */
 	struct kmsg_dumper kmsg_panic;
+
+	/**
+	 * @chroma_siting_h_property:
+	 *
+	 * Optional "CHROMA_SITING_H" property for specifying
+	 * chroma siting for YUV formats.
+	 * See drm_plane_create_chroma_siting_properties().
+	 */
+	struct drm_property *chroma_siting_h_property;
+
+	/**
+	 * @chroma_siting_v_property:
+	 *
+	 * Optional "CHROMA_SITING_V" property for specifying
+	 * chroma siting for YUV formats.
+	 * See drm_plane_create_chroma_siting_properties().
+	 */
+	struct drm_property *chroma_siting_v_property;
 };
 
 #define obj_to_plane(x) container_of(x, struct drm_plane, base)
@@ -907,9 +943,9 @@ static inline unsigned int drm_plane_index(const struct drm_plane *plane)
  * drm_plane_mask - find the mask of a registered plane
  * @plane: plane to find mask for
  */
-static inline u32 drm_plane_mask(const struct drm_plane *plane)
+static inline u64 drm_plane_mask(const struct drm_plane *plane)
 {
-	return 1 << drm_plane_index(plane);
+	return 1ULL << drm_plane_index(plane);
 }
 
 struct drm_plane * drm_plane_from_index(struct drm_device *dev, int idx);
diff --git a/include/dt-bindings/clock/rp1.h b/include/dt-bindings/clock/rp1.h
new file mode 100644
index 000000000000..1ebb25f16923
--- /dev/null
+++ b/include/dt-bindings/clock/rp1.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2021 Raspberry Pi Ltd.
+ */
+
+#define RP1_PLL_SYS_CORE		0
+#define RP1_PLL_AUDIO_CORE		1
+#define RP1_PLL_VIDEO_CORE		2
+
+#define RP1_PLL_SYS			3
+#define RP1_PLL_AUDIO			4
+#define RP1_PLL_VIDEO			5
+
+#define RP1_PLL_SYS_PRI_PH		6
+#define RP1_PLL_SYS_SEC_PH		7
+#define RP1_PLL_AUDIO_PRI_PH		8
+
+#define RP1_PLL_SYS_SEC			9
+#define RP1_PLL_AUDIO_SEC		10
+#define RP1_PLL_VIDEO_SEC		11
+
+#define RP1_CLK_SYS			12
+#define RP1_CLK_SLOW_SYS		13
+#define RP1_CLK_DMA			14
+#define RP1_CLK_UART			15
+#define RP1_CLK_ETH			16
+#define RP1_CLK_PWM0			17
+#define RP1_CLK_PWM1			18
+#define RP1_CLK_AUDIO_IN		19
+#define RP1_CLK_AUDIO_OUT		20
+#define RP1_CLK_I2S			21
+#define RP1_CLK_MIPI0_CFG		22
+#define RP1_CLK_MIPI1_CFG		23
+#define RP1_CLK_PCIE_AUX		24
+#define RP1_CLK_USBH0_MICROFRAME	25
+#define RP1_CLK_USBH1_MICROFRAME	26
+#define RP1_CLK_USBH0_SUSPEND		27
+#define RP1_CLK_USBH1_SUSPEND		28
+#define RP1_CLK_ETH_TSU			29
+#define RP1_CLK_ADC			30
+#define RP1_CLK_SDIO_TIMER		31
+#define RP1_CLK_SDIO_ALT_SRC		32
+#define RP1_CLK_GP0			33
+#define RP1_CLK_GP1			34
+#define RP1_CLK_GP2			35
+#define RP1_CLK_GP3			36
+#define RP1_CLK_GP4			37
+#define RP1_CLK_GP5			38
+#define RP1_CLK_VEC			39
+#define RP1_CLK_DPI			40
+#define RP1_CLK_MIPI0_DPI		41
+#define RP1_CLK_MIPI1_DPI		42
+
+/* Extra PLL output channels - RP1B0 only */
+#define RP1_PLL_VIDEO_PRI_PH		43
+#define RP1_PLL_AUDIO_TERN		44
+
+/* MIPI clocks managed by the DSI driver */
+#define RP1_CLK_MIPI0_DSI_BYTECLOCK	45
+#define RP1_CLK_MIPI1_DSI_BYTECLOCK	46
diff --git a/include/dt-bindings/gpio/gpio-fsm.h b/include/dt-bindings/gpio/gpio-fsm.h
new file mode 100644
index 000000000000..eb40cfdc71df
--- /dev/null
+++ b/include/dt-bindings/gpio/gpio-fsm.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * This header provides constants for binding rpi,gpio-fsm.
+ */
+
+#ifndef _DT_BINDINGS_GPIO_FSM_H
+#define _DT_BINDINGS_GPIO_FSM_H
+
+#define GF_IN       0
+#define GF_OUT      1
+#define GF_SOFT     2
+#define GF_DELAY    3
+#define GF_SHUTDOWN 4
+
+#define GF_IO(t, v) (((v) << 16) | ((t) & 0xffff))
+
+#define GF_IP(x)    GF_IO(GF_IN, (x))
+#define GF_OP(x)    GF_IO(GF_OUT, (x))
+#define GF_SW(x)    GF_IO(GF_SOFT, (x))
+
+#endif
diff --git a/include/dt-bindings/mfd/rp1.h b/include/dt-bindings/mfd/rp1.h
new file mode 100644
index 000000000000..80bbfd61b270
--- /dev/null
+++ b/include/dt-bindings/mfd/rp1.h
@@ -0,0 +1,235 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * This header provides constants for the PY MFD.
+ */
+
+#ifndef _RP1_H
+#define _RP1_H
+
+/* Address map */
+#define RP1_SYSINFO_BASE 0x000000
+#define RP1_TBMAN_BASE 0x004000
+#define RP1_SYSCFG_BASE 0x008000
+#define RP1_OTP_BASE 0x00c000
+#define RP1_POWER_BASE 0x010000
+#define RP1_RESETS_BASE 0x014000
+#define RP1_CLOCKS_BANK_DEFAULT_BASE 0x018000
+#define RP1_CLOCKS_BANK_VIDEO_BASE 0x01c000
+#define RP1_PLL_SYS_BASE 0x020000
+#define RP1_PLL_AUDIO_BASE 0x024000
+#define RP1_PLL_VIDEO_BASE 0x028000
+#define RP1_UART0_BASE 0x030000
+#define RP1_UART1_BASE 0x034000
+#define RP1_UART2_BASE 0x038000
+#define RP1_UART3_BASE 0x03c000
+#define RP1_UART4_BASE 0x040000
+#define RP1_UART5_BASE 0x044000
+#define RP1_SPI8_BASE 0x04c000
+#define RP1_SPI0_BASE 0x050000
+#define RP1_SPI1_BASE 0x054000
+#define RP1_SPI2_BASE 0x058000
+#define RP1_SPI3_BASE 0x05c000
+#define RP1_SPI4_BASE 0x060000
+#define RP1_SPI5_BASE 0x064000
+#define RP1_SPI6_BASE 0x068000
+#define RP1_SPI7_BASE 0x06c000
+#define RP1_I2C0_BASE 0x070000
+#define RP1_I2C1_BASE 0x074000
+#define RP1_I2C2_BASE 0x078000
+#define RP1_I2C3_BASE 0x07c000
+#define RP1_I2C4_BASE 0x080000
+#define RP1_I2C5_BASE 0x084000
+#define RP1_I2C6_BASE 0x088000
+#define RP1_AUDIO_IN_BASE 0x090000
+#define RP1_AUDIO_OUT_BASE 0x094000
+#define RP1_PWM0_BASE 0x098000
+#define RP1_PWM1_BASE 0x09c000
+#define RP1_I2S0_BASE 0x0a0000
+#define RP1_I2S1_BASE 0x0a4000
+#define RP1_I2S2_BASE 0x0a8000
+#define RP1_TIMER_BASE 0x0ac000
+#define RP1_SDIO0_APBS_BASE 0x0b0000
+#define RP1_SDIO1_APBS_BASE 0x0b4000
+#define RP1_BUSFABRIC_MONITOR_BASE 0x0c0000
+#define RP1_BUSFABRIC_AXISHIM_BASE 0x0c4000
+#define RP1_ADC_BASE 0x0c8000
+#define RP1_IO_BANK0_BASE 0x0d0000
+#define RP1_IO_BANK1_BASE 0x0d4000
+#define RP1_IO_BANK2_BASE 0x0d8000
+#define RP1_SYS_RIO0_BASE 0x0e0000
+#define RP1_SYS_RIO1_BASE 0x0e4000
+#define RP1_SYS_RIO2_BASE 0x0e8000
+#define RP1_PADS_BANK0_BASE 0x0f0000
+#define RP1_PADS_BANK1_BASE 0x0f4000
+#define RP1_PADS_BANK2_BASE 0x0f8000
+#define RP1_PADS_ETH_BASE 0x0fc000
+#define RP1_ETH_IP_BASE 0x100000
+#define RP1_ETH_CFG_BASE 0x104000
+#define RP1_PCIE_APBS_BASE 0x108000
+#define RP1_MIPI0_CSIDMA_BASE 0x110000
+#define RP1_MIPI0_CSIHOST_BASE 0x114000
+#define RP1_MIPI0_DSIDMA_BASE 0x118000
+#define RP1_MIPI0_DSIHOST_BASE 0x11c000
+#define RP1_MIPI0_MIPICFG_BASE 0x120000
+#define RP1_MIPI0_ISP_BASE 0x124000
+#define RP1_MIPI1_CSIDMA_BASE 0x128000
+#define RP1_MIPI1_CSIHOST_BASE 0x12c000
+#define RP1_MIPI1_DSIDMA_BASE 0x130000
+#define RP1_MIPI1_DSIHOST_BASE 0x134000
+#define RP1_MIPI1_MIPICFG_BASE 0x138000
+#define RP1_MIPI1_ISP_BASE 0x13c000
+#define RP1_VIDEO_OUT_CFG_BASE 0x140000
+#define RP1_VIDEO_OUT_VEC_BASE 0x144000
+#define RP1_VIDEO_OUT_DPI_BASE 0x148000
+#define RP1_XOSC_BASE 0x150000
+#define RP1_WATCHDOG_BASE 0x154000
+#define RP1_DMA_TICK_BASE 0x158000
+#define RP1_SDIO_CLOCKS_BASE 0x15c000
+#define RP1_USBHOST0_APBS_BASE 0x160000
+#define RP1_USBHOST1_APBS_BASE 0x164000
+#define RP1_ROSC0_BASE 0x168000
+#define RP1_ROSC1_BASE 0x16c000
+#define RP1_VBUSCTRL_BASE 0x170000
+#define RP1_TICKS_BASE 0x174000
+#define RP1_PIO_APBS_BASE 0x178000
+#define RP1_SDIO0_AHBLS_BASE 0x180000
+#define RP1_SDIO1_AHBLS_BASE 0x184000
+#define RP1_DMA_BASE 0x188000
+#define RP1_RAM_BASE 0x1c0000
+#define RP1_RAM_SIZE 0x020000
+#define RP1_USBHOST0_AXIS_BASE 0x200000
+#define RP1_USBHOST1_AXIS_BASE 0x300000
+#define RP1_EXAC_BASE 0x400000
+
+/* Interrupts */
+
+#define RP1_INT_IO_BANK0 0
+#define RP1_INT_IO_BANK1 1
+#define RP1_INT_IO_BANK2 2
+#define RP1_INT_AUDIO_IN 3
+#define RP1_INT_AUDIO_OUT 4
+#define RP1_INT_PWM0 5
+#define RP1_INT_ETH 6
+#define RP1_INT_I2C0 7
+#define RP1_INT_I2C1 8
+#define RP1_INT_I2C2 9
+#define RP1_INT_I2C3 10
+#define RP1_INT_I2C4 11
+#define RP1_INT_I2C5 12
+#define RP1_INT_I2C6 13
+#define RP1_INT_I2S0 14
+#define RP1_INT_I2S1 15
+#define RP1_INT_I2S2 16
+#define RP1_INT_SDIO0 17
+#define RP1_INT_SDIO1 18
+#define RP1_INT_SPI0 19
+#define RP1_INT_SPI1 20
+#define RP1_INT_SPI2 21
+#define RP1_INT_SPI3 22
+#define RP1_INT_SPI4 23
+#define RP1_INT_SPI5 24
+#define RP1_INT_UART0 25
+#define RP1_INT_TIMER_0 26
+#define RP1_INT_TIMER_1 27
+#define RP1_INT_TIMER_2 28
+#define RP1_INT_TIMER_3 29
+#define RP1_INT_USBHOST0 30
+#define RP1_INT_USBHOST0_0 31
+#define RP1_INT_USBHOST0_1 32
+#define RP1_INT_USBHOST0_2 33
+#define RP1_INT_USBHOST0_3 34
+#define RP1_INT_USBHOST1 35
+#define RP1_INT_USBHOST1_0 36
+#define RP1_INT_USBHOST1_1 37
+#define RP1_INT_USBHOST1_2 38
+#define RP1_INT_USBHOST1_3 39
+#define RP1_INT_DMA 40
+#define RP1_INT_PWM1 41
+#define RP1_INT_UART1 42
+#define RP1_INT_UART2 43
+#define RP1_INT_UART3 44
+#define RP1_INT_UART4 45
+#define RP1_INT_UART5 46
+#define RP1_INT_MIPI0 47
+#define RP1_INT_MIPI1 48
+#define RP1_INT_VIDEO_OUT 49
+#define RP1_INT_PIO_0 50
+#define RP1_INT_PIO_1 51
+#define RP1_INT_ADC_FIFO 52
+#define RP1_INT_PCIE_OUT 53
+#define RP1_INT_SPI6 54
+#define RP1_INT_SPI7 55
+#define RP1_INT_SPI8 56
+#define RP1_INT_SYSCFG 58
+#define RP1_INT_CLOCKS_DEFAULT 59
+#define RP1_INT_VBUSCTRL 60
+#define RP1_INT_PROC_MISC 57
+#define RP1_INT_END 61
+
+/* DMA peripherals (for pacing) */
+#define RP1_DMA_I2C0_RX 0x0
+#define RP1_DMA_I2C0_TX 0x1
+#define RP1_DMA_I2C1_RX 0x2
+#define RP1_DMA_I2C1_TX 0x3
+#define RP1_DMA_I2C2_RX 0x4
+#define RP1_DMA_I2C2_TX 0x5
+#define RP1_DMA_I2C3_RX 0x6
+#define RP1_DMA_I2C3_TX 0x7
+#define RP1_DMA_I2C4_RX 0x8
+#define RP1_DMA_I2C4_TX 0x9
+#define RP1_DMA_I2C5_RX 0xa
+#define RP1_DMA_I2C5_TX 0xb
+#define RP1_DMA_SPI0_RX 0xc
+#define RP1_DMA_SPI0_TX 0xd
+#define RP1_DMA_SPI1_RX 0xe
+#define RP1_DMA_SPI1_TX 0xf
+#define RP1_DMA_SPI2_RX 0x10
+#define RP1_DMA_SPI2_TX 0x11
+#define RP1_DMA_SPI3_RX 0x12
+#define RP1_DMA_SPI3_TX 0x13
+#define RP1_DMA_SPI4_RX 0x14
+#define RP1_DMA_SPI4_TX 0x15
+#define RP1_DMA_SPI5_RX 0x16
+#define RP1_DMA_SPI5_TX 0x17
+#define RP1_DMA_PWM0 0x18
+#define RP1_DMA_UART0_RX 0x19
+#define RP1_DMA_UART0_TX 0x1a
+#define RP1_DMA_AUDIO_IN_CH0 0x1b
+#define RP1_DMA_AUDIO_IN_CH1 0x1c
+#define RP1_DMA_AUDIO_OUT 0x1d
+#define RP1_DMA_PWM1 0x1e
+#define RP1_DMA_I2S0_RX 0x1f
+#define RP1_DMA_I2S0_TX 0x20
+#define RP1_DMA_I2S1_RX 0x21
+#define RP1_DMA_I2S1_TX 0x22
+#define RP1_DMA_I2S2_RX 0x23
+#define RP1_DMA_I2S2_TX 0x24
+#define RP1_DMA_UART1_RX 0x25
+#define RP1_DMA_UART1_TX 0x26
+#define RP1_DMA_UART2_RX 0x27
+#define RP1_DMA_UART2_TX 0x28
+#define RP1_DMA_UART3_RX 0x29
+#define RP1_DMA_UART3_TX 0x2a
+#define RP1_DMA_UART4_RX 0x2b
+#define RP1_DMA_UART4_TX 0x2c
+#define RP1_DMA_UART5_RX 0x2d
+#define RP1_DMA_UART5_TX 0x2e
+#define RP1_DMA_ADC 0x2f
+#define RP1_DMA_DMA_TICK_TICK0 0x30
+#define RP1_DMA_DMA_TICK_TICK1 0x31
+#define RP1_DMA_SPI6_RX 0x32
+#define RP1_DMA_SPI6_TX 0x33
+#define RP1_DMA_SPI7_RX 0x34
+#define RP1_DMA_SPI7_TX 0x35
+#define RP1_DMA_SPI8_RX 0x36
+#define RP1_DMA_SPI8_TX 0x37
+#define RP1_DMA_PIO_CH0_TX 0x38
+#define RP1_DMA_PIO_CH0_RX 0x39
+#define RP1_DMA_PIO_CH1_TX 0x3a
+#define RP1_DMA_PIO_CH1_RX 0x3b
+#define RP1_DMA_PIO_CH2_TX 0x3c
+#define RP1_DMA_PIO_CH2_RX 0x3d
+#define RP1_DMA_PIO_CH3_TX 0x3e
+#define RP1_DMA_PIO_CH3_RX 0x3f
+
+#endif
diff --git a/include/linux/backlight.h b/include/linux/backlight.h
index ea9c1bc8148e..3caa7b4c1f3c 100644
--- a/include/linux/backlight.h
+++ b/include/linux/backlight.h
@@ -255,6 +255,13 @@ struct backlight_properties {
 	 * @scale: The type of the brightness scale.
 	 */
 	enum backlight_scale scale;
+
+#define BL_DISPLAY_NAME_LEN 32
+	/**
+	 * @display_name: Optional name that can be registered to associate a
+	 * backlight device with a display device.
+	 */
+	char display_name[BL_DISPLAY_NAME_LEN];
 };
 
 /**
@@ -459,12 +466,20 @@ of_find_backlight_by_node(struct device_node *node)
 
 #if IS_ENABLED(CONFIG_BACKLIGHT_CLASS_DEVICE)
 struct backlight_device *devm_of_find_backlight(struct device *dev);
+int backlight_set_display_name(struct backlight_device *bd, const char *name);
 #else
 static inline struct backlight_device *
 devm_of_find_backlight(struct device *dev)
 {
 	return NULL;
 }
+
+static inline int backlight_set_display_name(struct backlight_device *bd,
+					     const char *name)
+{
+	return 0;
+}
+
 #endif
 
 #endif
diff --git a/include/linux/brcmphy.h b/include/linux/brcmphy.h
index 028b3e00378e..535aa34a4196 100644
--- a/include/linux/brcmphy.h
+++ b/include/linux/brcmphy.h
@@ -24,6 +24,7 @@
 #define PHY_ID_BCM5411			0x00206070
 #define PHY_ID_BCM5421			0x002060e0
 #define PHY_ID_BCM54210E		0x600d84a0
+#define PHY_ID_BCM54213PE		0x600d84a2
 #define PHY_ID_BCM5464			0x002060b0
 #define PHY_ID_BCM5461			0x002060c0
 #define PHY_ID_BCM54612E		0x03625e60
diff --git a/include/linux/broadcom/bcm2835_smi.h b/include/linux/broadcom/bcm2835_smi.h
new file mode 100644
index 000000000000..ee3a75edfc03
--- /dev/null
+++ b/include/linux/broadcom/bcm2835_smi.h
@@ -0,0 +1,391 @@
+/**
+ * Declarations and definitions for Broadcom's Secondary Memory Interface
+ *
+ * Written by Luke Wren <luke@raspberrypi.org>
+ * Copyright (c) 2015, Raspberry Pi (Trading) Ltd.
+ * Copyright (c) 2010-2012 Broadcom. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The names of the above-listed copyright holders may not be used
+ *    to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * ALTERNATIVELY, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2, as published by the Free
+ * Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BCM2835_SMI_H
+#define BCM2835_SMI_H
+
+#include <linux/ioctl.h>
+
+#ifndef __KERNEL__
+#include <stdint.h>
+#include <stdbool.h>
+#endif
+
+#define BCM2835_SMI_IOC_MAGIC 0x1
+#define BCM2835_SMI_INVALID_HANDLE (~0)
+
+/* IOCTLs 0x100...0x1ff are not device-specific - we can use them */
+#define BCM2835_SMI_IOC_GET_SETTINGS    _IO(BCM2835_SMI_IOC_MAGIC, 0)
+#define BCM2835_SMI_IOC_WRITE_SETTINGS  _IO(BCM2835_SMI_IOC_MAGIC, 1)
+#define BCM2835_SMI_IOC_ADDRESS	 _IO(BCM2835_SMI_IOC_MAGIC, 2)
+#define BCM2835_SMI_IOC_MAX	     2
+
+#define SMI_WIDTH_8BIT 0
+#define SMI_WIDTH_16BIT 1
+#define SMI_WIDTH_9BIT 2
+#define SMI_WIDTH_18BIT 3
+
+/* max number of bytes where DMA will not be used */
+#define DMA_THRESHOLD_BYTES 128
+#define DMA_BOUNCE_BUFFER_SIZE (1024 * 1024 / 2)
+#define DMA_BOUNCE_BUFFER_COUNT 3
+
+
+struct smi_settings {
+	int data_width;
+	/* Whether or not to pack multiple SMI transfers into a
+	   single 32 bit FIFO word */
+	bool pack_data;
+
+	/* Timing for reads (writes the same but for WE)
+	 *
+	 * OE ----------+	   +--------------------
+	 *		|	   |
+	 *		+----------+
+	 * SD -<==============================>-----------
+	 * SA -<=========================================>-
+	 *    <-setup->  <-strobe ->  <-hold ->  <- pace ->
+	 */
+
+	int read_setup_time;
+	int read_hold_time;
+	int read_pace_time;
+	int read_strobe_time;
+
+	int write_setup_time;
+	int write_hold_time;
+	int write_pace_time;
+	int write_strobe_time;
+
+	bool dma_enable;		/* DREQs */
+	bool dma_passthrough_enable;	/* External DREQs */
+	int dma_read_thresh;
+	int dma_write_thresh;
+	int dma_panic_read_thresh;
+	int dma_panic_write_thresh;
+};
+
+/****************************************************************************
+*
+*   Declare exported SMI functions
+*
+***************************************************************************/
+
+#ifdef __KERNEL__
+
+#include <linux/dmaengine.h> /* for enum dma_transfer_direction */
+#include <linux/of.h>
+#include <linux/semaphore.h>
+
+struct bcm2835_smi_instance;
+
+struct bcm2835_smi_bounce_info {
+	struct semaphore callback_sem;
+	void *buffer[DMA_BOUNCE_BUFFER_COUNT];
+	dma_addr_t phys[DMA_BOUNCE_BUFFER_COUNT];
+	struct scatterlist sgl[DMA_BOUNCE_BUFFER_COUNT];
+};
+
+
+void bcm2835_smi_set_regs_from_settings(struct bcm2835_smi_instance *);
+
+struct smi_settings *bcm2835_smi_get_settings_from_regs(
+	struct bcm2835_smi_instance *inst);
+
+void bcm2835_smi_write_buf(
+	struct bcm2835_smi_instance *inst,
+	const void *buf,
+	size_t n_bytes);
+
+void bcm2835_smi_read_buf(
+	struct bcm2835_smi_instance *inst,
+	void *buf,
+	size_t n_bytes);
+
+void bcm2835_smi_set_address(struct bcm2835_smi_instance *inst,
+	unsigned int address);
+
+ssize_t bcm2835_smi_user_dma(
+	struct bcm2835_smi_instance *inst,
+	enum dma_transfer_direction dma_dir,
+	char __user *user_ptr,
+	size_t count,
+	struct bcm2835_smi_bounce_info **bounce);
+
+struct bcm2835_smi_instance *bcm2835_smi_get(struct device_node *node);
+
+#endif /* __KERNEL__ */
+
+/****************************************************************
+*
+*	Implementation-only declarations
+*
+****************************************************************/
+
+#ifdef BCM2835_SMI_IMPLEMENTATION
+
+/* Clock manager registers for SMI clock: */
+#define CM_SMI_BASE_ADDRESS ((BCM2708_PERI_BASE) + 0x1010b0)
+/* Clock manager "password" to protect registers from spurious writes */
+#define CM_PWD (0x5a << 24)
+
+#define CM_SMI_CTL	0x00
+#define CM_SMI_DIV	0x04
+
+#define CM_SMI_CTL_FLIP (1 << 8)
+#define CM_SMI_CTL_BUSY (1 << 7)
+#define CM_SMI_CTL_KILL (1 << 5)
+#define CM_SMI_CTL_ENAB (1 << 4)
+#define CM_SMI_CTL_SRC_MASK (0xf)
+#define CM_SMI_CTL_SRC_OFFS (0)
+
+#define CM_SMI_DIV_DIVI_MASK (0xf <<  12)
+#define CM_SMI_DIV_DIVI_OFFS (12)
+#define CM_SMI_DIV_DIVF_MASK (0xff << 4)
+#define CM_SMI_DIV_DIVF_OFFS (4)
+
+/* SMI register mapping:*/
+#define SMI_BASE_ADDRESS ((BCM2708_PERI_BASE) + 0x600000)
+
+#define SMICS	0x00	/* control + status register		*/
+#define SMIL	0x04	/* length/count (n external txfers)	*/
+#define SMIA	0x08	/* address register			*/
+#define SMID	0x0c	/* data register			*/
+#define SMIDSR0	0x10	/* device 0 read settings		*/
+#define SMIDSW0	0x14	/* device 0 write settings		*/
+#define SMIDSR1	0x18	/* device 1 read settings		*/
+#define SMIDSW1	0x1c	/* device 1 write settings		*/
+#define SMIDSR2	0x20	/* device 2 read settings		*/
+#define SMIDSW2	0x24	/* device 2 write settings		*/
+#define SMIDSR3	0x28	/* device 3 read settings		*/
+#define SMIDSW3	0x2c	/* device 3 write settings		*/
+#define SMIDC	0x30	/* DMA control registers		*/
+#define SMIDCS	0x34	/* direct control/status register	*/
+#define SMIDA	0x38	/* direct address register		*/
+#define SMIDD	0x3c	/* direct data registers		*/
+#define SMIFD	0x40	/* FIFO debug register			*/
+
+
+
+/* Control and Status register bits:
+ * SMICS_RXF	: RX fifo full: 1 when RX fifo is full
+ * SMICS_TXE	: TX fifo empty: 1 when empty.
+ * SMICS_RXD	: RX fifo contains data: 1 when there is data.
+ * SMICS_TXD	: TX fifo can accept data: 1 when true.
+ * SMICS_RXR	: RX fifo needs reading: 1 when fifo more than 3/4 full, or
+ *		  when "DONE" and fifo not emptied.
+ * SMICS_TXW	: TX fifo needs writing: 1 when less than 1/4 full.
+ * SMICS_AFERR	: AXI FIFO error: 1 when fifo read when empty or written
+ *		  when full. Write 1 to clear.
+ * SMICS_EDREQ	: 1 when external DREQ received.
+ * SMICS_PXLDAT	:  Pixel data:	write 1 to enable pixel transfer modes.
+ * SMICS_SETERR	: 1 if there was an error writing to setup regs (e.g.
+ *		  tx was in progress). Write 1 to clear.
+ * SMICS_PVMODE	: Set to 1 to enable pixel valve mode.
+ * SMICS_INTR	: Set to 1 to enable interrupt on RX.
+ * SMICS_INTT	: Set to 1 to enable interrupt on TX.
+ * SMICS_INTD	: Set to 1 to enable interrupt on DONE condition.
+ * SMICS_TEEN	: Tear effect mode enabled: Programmed transfers will wait
+ *		  for a TE trigger before writing.
+ * SMICS_PAD1	: Padding settings for external transfers. For writes: the
+ *		  number of bytes initially written to  the TX fifo that
+ * SMICS_PAD0	: should be ignored. For reads: the number of bytes that will
+ *		  be read before the data, and should be dropped.
+ * SMICS_WRITE	: Transfer direction: 1 = write to external device, 0 = read
+ * SMICS_CLEAR	: Write 1 to clear the FIFOs.
+ * SMICS_START	: Write 1 to start the programmed transfer.
+ * SMICS_ACTIVE	: Reads as 1 when a programmed transfer is underway.
+ * SMICS_DONE	: Reads as 1 when transfer finished. For RX, not set until
+ *		  FIFO emptied.
+ * SMICS_ENABLE	: Set to 1 to enable the SMI peripheral, 0 to disable.
+ */
+
+#define SMICS_RXF	(1 << 31)
+#define SMICS_TXE	(1 << 30)
+#define SMICS_RXD	(1 << 29)
+#define SMICS_TXD	(1 << 28)
+#define SMICS_RXR	(1 << 27)
+#define SMICS_TXW	(1 << 26)
+#define SMICS_AFERR	(1 << 25)
+#define SMICS_EDREQ	(1 << 15)
+#define SMICS_PXLDAT	(1 << 14)
+#define SMICS_SETERR	(1 << 13)
+#define SMICS_PVMODE	(1 << 12)
+#define SMICS_INTR	(1 << 11)
+#define SMICS_INTT	(1 << 10)
+#define SMICS_INTD	(1 << 9)
+#define SMICS_TEEN	(1 << 8)
+#define SMICS_PAD1	(1 << 7)
+#define SMICS_PAD0	(1 << 6)
+#define SMICS_WRITE	(1 << 5)
+#define SMICS_CLEAR	(1 << 4)
+#define SMICS_START	(1 << 3)
+#define SMICS_ACTIVE	(1 << 2)
+#define SMICS_DONE	(1 << 1)
+#define SMICS_ENABLE	(1 << 0)
+
+/* Address register bits: */
+
+#define SMIA_DEVICE_MASK ((1 << 9) | (1 << 8))
+#define SMIA_DEVICE_OFFS (8)
+#define SMIA_ADDR_MASK (0x3f)	/* bits 5 -> 0 */
+#define SMIA_ADDR_OFFS (0)
+
+/* DMA control register bits:
+ * SMIDC_DMAEN	: DMA enable: set 1: DMA requests will be issued.
+ * SMIDC_DMAP	: DMA passthrough: when set to 0, top two data pins are used by
+ *		  SMI as usual. When set to 1, the top two pins are used for
+ *		  external DREQs: pin 16 read request, 17 write.
+ * SMIDC_PANIC*	: Threshold at which DMA will panic during read/write.
+ * SMIDC_REQ*	: Threshold at which DMA will generate a DREQ.
+ */
+
+#define SMIDC_DMAEN		(1 << 28)
+#define SMIDC_DMAP		(1 << 24)
+#define SMIDC_PANICR_MASK	(0x3f << 18)
+#define SMIDC_PANICR_OFFS	(18)
+#define SMIDC_PANICW_MASK	(0x3f << 12)
+#define SMIDC_PANICW_OFFS	(12)
+#define SMIDC_REQR_MASK		(0x3f << 6)
+#define SMIDC_REQR_OFFS		(6)
+#define SMIDC_REQW_MASK		(0x3f)
+#define SMIDC_REQW_OFFS		(0)
+
+/* Device settings register bits: same for all 4 (or 3?) device register sets.
+ * Device read settings:
+ * SMIDSR_RWIDTH	: Read transfer width. 00 = 8bit, 01 = 16bit,
+ *			  10 = 18bit, 11 = 9bit.
+ * SMIDSR_RSETUP	: Read setup time: number of core cycles between chip
+ *			  select/address and read strobe. Min 1, max 64.
+ * SMIDSR_MODE68	: 1 for System 68 mode (i.e. enable + direction pins,
+ *			  rather than OE + WE pin)
+ * SMIDSR_FSETUP	: If set to 1, setup time only applies to first
+ *			  transfer after address change.
+ * SMIDSR_RHOLD		: Number of core cycles between read strobe going
+ *			  inactive and CS/address going inactive. Min 1, max 64
+ * SMIDSR_RPACEALL	: When set to 1, this device's RPACE value will always
+ *			  be used for the next transaction, even if it is not
+ *			  to this device.
+ * SMIDSR_RPACE		: Number of core cycles spent waiting between CS
+ *			  deassert and start of next transfer. Min 1, max 128
+ * SMIDSR_RDREQ		: 1 = use external DMA request on SD16 to pace reads
+ *			  from device. Must also set DMAP in SMICS.
+ * SMIDSR_RSTROBE	: Number of cycles to assert the read strobe.
+ *			  min 1, max 128.
+ */
+#define SMIDSR_RWIDTH_MASK	((1<<31)|(1<<30))
+#define SMIDSR_RWIDTH_OFFS	(30)
+#define SMIDSR_RSETUP_MASK	(0x3f << 24)
+#define SMIDSR_RSETUP_OFFS	(24)
+#define SMIDSR_MODE68		(1 << 23)
+#define SMIDSR_FSETUP		(1 << 22)
+#define SMIDSR_RHOLD_MASK	(0x3f << 16)
+#define SMIDSR_RHOLD_OFFS	(16)
+#define SMIDSR_RPACEALL		(1 << 15)
+#define SMIDSR_RPACE_MASK	(0x7f << 8)
+#define SMIDSR_RPACE_OFFS	(8)
+#define SMIDSR_RDREQ		(1 << 7)
+#define SMIDSR_RSTROBE_MASK	(0x7f)
+#define SMIDSR_RSTROBE_OFFS	(0)
+
+/* Device write settings:
+ * SMIDSW_WWIDTH	: Write transfer width. 00 = 8bit, 01 = 16bit,
+ *			  10= 18bit, 11 = 9bit.
+ * SMIDSW_WSETUP	: Number of cycles between CS assert and write strobe.
+ *			  Min 1, max 64.
+ * SMIDSW_WFORMAT	: Pixel format of input. 0 = 16bit RGB 565,
+ *			  1 = 32bit RGBA 8888
+ * SMIDSW_WSWAP		: 1 = swap pixel data bits. (Use with SMICS_PXLDAT)
+ * SMIDSW_WHOLD		: Time between WE deassert and CS deassert. 1 to 64
+ * SMIDSW_WPACEALL	: 1: this device's WPACE will be used for the next
+ *			  transfer, regardless of that transfer's device.
+ * SMIDSW_WPACE		: Cycles between CS deassert and next CS assert.
+ *			  Min 1, max 128
+ * SMIDSW_WDREQ		: Use external DREQ on pin 17 to pace writes. DMAP must
+ *			  be set in SMICS.
+ * SMIDSW_WSTROBE	: Number of cycles to assert the write strobe.
+ *			  Min 1, max 128
+ */
+#define SMIDSW_WWIDTH_MASK	 ((1<<31)|(1<<30))
+#define SMIDSW_WWIDTH_OFFS	(30)
+#define SMIDSW_WSETUP_MASK	(0x3f << 24)
+#define SMIDSW_WSETUP_OFFS	(24)
+#define SMIDSW_WFORMAT		(1 << 23)
+#define SMIDSW_WSWAP		(1 << 22)
+#define SMIDSW_WHOLD_MASK	(0x3f << 16)
+#define SMIDSW_WHOLD_OFFS	(16)
+#define SMIDSW_WPACEALL		(1 << 15)
+#define SMIDSW_WPACE_MASK	(0x7f << 8)
+#define SMIDSW_WPACE_OFFS	(8)
+#define SMIDSW_WDREQ		(1 << 7)
+#define SMIDSW_WSTROBE_MASK	 (0x7f)
+#define SMIDSW_WSTROBE_OFFS	 (0)
+
+/* Direct transfer control + status register
+ * SMIDCS_WRITE	: Direction of transfer: 1 -> write, 0 -> read
+ * SMIDCS_DONE	: 1 when a transfer has finished. Write 1 to clear.
+ * SMIDCS_START	: Write 1 to start a transfer, if one is not already underway.
+ * SMIDCE_ENABLE: Write 1 to enable SMI in direct mode.
+ */
+
+#define SMIDCS_WRITE		(1 << 3)
+#define SMIDCS_DONE		(1 << 2)
+#define SMIDCS_START		(1 << 1)
+#define SMIDCS_ENABLE		(1 << 0)
+
+/* Direct transfer address register
+ * SMIDA_DEVICE	: Indicates which of the device settings banks should be used.
+ * SMIDA_ADDR	: The value to be asserted on the address pins.
+ */
+
+#define SMIDA_DEVICE_MASK	((1<<9)|(1<<8))
+#define SMIDA_DEVICE_OFFS	(8)
+#define SMIDA_ADDR_MASK		(0x3f)
+#define SMIDA_ADDR_OFFS		(0)
+
+/* FIFO debug register
+ * SMIFD_FLVL	: The high-tide mark of FIFO count during the most recent txfer
+ * SMIFD_FCNT	: The current FIFO count.
+ */
+#define SMIFD_FLVL_MASK		(0x3f << 8)
+#define SMIFD_FLVL_OFFS		(8)
+#define SMIFD_FCNT_MASK		(0x3f)
+#define SMIFD_FCNT_OFFS		(0)
+
+#endif /* BCM2835_SMI_IMPLEMENTATION */
+
+#endif /* BCM2835_SMI_H */
diff --git a/include/linux/broadcom/vc_mem.h b/include/linux/broadcom/vc_mem.h
new file mode 100644
index 000000000000..3c7079237496
--- /dev/null
+++ b/include/linux/broadcom/vc_mem.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010 - 2011 Broadcom Corporation.  All rights reserved.
+ *
+ * Unless you and Broadcom execute a separate written software license
+ * agreement governing use of this software, this software is licensed to you
+ * under the terms of the GNU General Public License version 2, available at
+ * http://www.broadcom.com/licenses/GPLv2.php (the "GPL").
+ *
+ * Notwithstanding the above, under no circumstances may you combine this
+ * software in any way with any other Broadcom software provided under a
+ * license other than the GPL, without Broadcom's express prior written
+ * consent.
+ */
+
+#ifndef _VC_MEM_H
+#define _VC_MEM_H
+
+#include <linux/ioctl.h>
+
+#define VC_MEM_IOC_MAGIC  'v'
+
+#define VC_MEM_IOC_MEM_PHYS_ADDR    _IOR(VC_MEM_IOC_MAGIC, 0, unsigned long)
+#define VC_MEM_IOC_MEM_SIZE         _IOR(VC_MEM_IOC_MAGIC, 1, unsigned int)
+#define VC_MEM_IOC_MEM_BASE         _IOR(VC_MEM_IOC_MAGIC, 2, unsigned int)
+#define VC_MEM_IOC_MEM_LOAD         _IOR(VC_MEM_IOC_MAGIC, 3, unsigned int)
+
+#ifdef __KERNEL__
+#define VC_MEM_TO_ARM_ADDR_MASK 0x3FFFFFFF
+
+extern unsigned long mm_vc_mem_phys_addr;
+extern unsigned int  mm_vc_mem_size;
+extern int vc_mem_get_current_size(void);
+#endif
+
+#ifdef CONFIG_COMPAT
+#define VC_MEM_IOC_MEM_PHYS_ADDR32  _IOR(VC_MEM_IOC_MAGIC, 0, compat_ulong_t)
+#endif
+
+#endif  /* _VC_MEM_H */
diff --git a/include/linux/cma.h b/include/linux/cma.h
index d15b64f51336..961e452312cd 100644
--- a/include/linux/cma.h
+++ b/include/linux/cma.h
@@ -56,6 +56,7 @@ extern void cma_reserve_pages_on_error(struct cma *cma);
 #ifdef CONFIG_CMA
 struct folio *cma_alloc_folio(struct cma *cma, int order, gfp_t gfp);
 bool cma_free_folio(struct cma *cma, const struct folio *folio);
+int cma_check_range(u64 *start, u64 *end);
 #else
 static inline struct folio *cma_alloc_folio(struct cma *cma, int order, gfp_t gfp)
 {
@@ -66,6 +67,11 @@ static inline bool cma_free_folio(struct cma *cma, const struct folio *folio)
 {
 	return false;
 }
+
+static inline int cma_check_range(u64 *start, u64 *end)
+{
+	return 0;
+}
 #endif
 
 #endif
diff --git a/include/linux/fb.h b/include/linux/fb.h
index 267b59ead432..807b48e9dfb6 100644
--- a/include/linux/fb.h
+++ b/include/linux/fb.h
@@ -511,6 +511,7 @@ struct fb_info {
 
 	bool skip_vt_switch; /* no VT switch on suspend/resume required */
 	bool skip_panic; /* Do not write to the fb after a panic */
+	bool custom_fb_num; /* Use value in node as the preferred node number */
 };
 
 /* This will go away
@@ -600,6 +601,7 @@ extern ssize_t fb_sys_write(struct fb_info *info, const char __user *buf,
 	.fb_imageblit	= sys_imageblit
 
 /* fbmem.c */
+extern void fb_set_lowest_dynamic_fb(int min_fb_dev);
 extern int register_framebuffer(struct fb_info *fb_info);
 extern void unregister_framebuffer(struct fb_info *fb_info);
 extern int devm_register_framebuffer(struct device *dev, struct fb_info *fb_info);
diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h
index 2dd7cb9cc270..f22767a99c7a 100644
--- a/include/linux/gpio/driver.h
+++ b/include/linux/gpio/driver.h
@@ -713,6 +713,7 @@ int bgpio_init(struct gpio_chip *gc, struct device *dev,
 #define BGPIOF_READ_OUTPUT_REG_SET	BIT(4) /* reg_set stores output value */
 #define BGPIOF_NO_OUTPUT		BIT(5) /* only input */
 #define BGPIOF_NO_SET_ON_INPUT		BIT(6)
+#define BGPIOF_REG_DIRECT		BIT(7) /* ignore shadow registers */
 
 #ifdef CONFIG_GPIOLIB_IRQCHIP
 int gpiochip_irqchip_add_domain(struct gpio_chip *gc,
diff --git a/include/linux/irqchip/irq-bcm2836.h b/include/linux/irqchip/irq-bcm2836.h
index ac5719d8f56b..6fe053486447 100644
--- a/include/linux/irqchip/irq-bcm2836.h
+++ b/include/linux/irqchip/irq-bcm2836.h
@@ -59,3 +59,5 @@
 #define LOCAL_IRQ_GPU_FAST	8
 #define LOCAL_IRQ_PMU_FAST	9
 #define LAST_IRQ		LOCAL_IRQ_PMU_FAST
+
+void bcm2836_arm_irqchip_spin_gpu_irq(void);
diff --git a/include/linux/leds.h b/include/linux/leds.h
index 2337f516fa7c..df9b22e5d6e9 100644
--- a/include/linux/leds.h
+++ b/include/linux/leds.h
@@ -109,6 +109,9 @@ struct led_classdev {
 #define LED_INIT_DEFAULT_TRIGGER BIT(23)
 #define LED_REJECT_NAME_CONFLICT BIT(24)
 #define LED_MULTI_COLOR		BIT(25)
+	/* Additions for Raspberry Pi PWR LED */
+#define SET_GPIO_INPUT		BIT(30)
+#define SET_GPIO_OUTPUT		BIT(31)
 
 	/* set_brightness_work / blink_timer flags, atomic, private. */
 	unsigned long		work_flags;
diff --git a/include/linux/mempolicy.h b/include/linux/mempolicy.h
index 1add16f21612..54ace89e59a9 100644
--- a/include/linux/mempolicy.h
+++ b/include/linux/mempolicy.h
@@ -135,6 +135,8 @@ bool vma_policy_mof(struct vm_area_struct *vma);
 
 extern void numa_default_policy(void);
 extern void numa_policy_init(void);
+nodemask_t *numa_policy_nodemask(gfp_t gfp, struct mempolicy *pol, pgoff_t ilx,
+				 int *nid);
 extern void mpol_rebind_task(struct task_struct *tsk, const nodemask_t *new);
 extern void mpol_rebind_mm(struct mm_struct *mm, nodemask_t *new);
 
@@ -238,6 +240,14 @@ static inline void numa_policy_init(void)
 {
 }
 
+static inline nodemask_t *
+numa_policy_nodemask(gfp_t gfp, struct mempolicy *pol, pgoff_t ilx, int *nid)
+{
+	*nid = NUMA_NO_NODE;
+
+	return NULL;
+}
+
 static inline void numa_default_policy(void)
 {
 }
diff --git a/include/linux/mfd/rpisense/framebuffer.h b/include/linux/mfd/rpisense/framebuffer.h
new file mode 100644
index 000000000000..621f24386bb0
--- /dev/null
+++ b/include/linux/mfd/rpisense/framebuffer.h
@@ -0,0 +1,35 @@
+/*
+ * Raspberry Pi Sense HAT framebuffer driver
+ * http://raspberrypi.org
+ *
+ * Copyright (C) 2015 Raspberry Pi
+ *
+ * Author: Serge Schneider
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#ifndef __LINUX_RPISENSE_FB_H_
+#define __LINUX_RPISENSE_FB_H_
+
+#include <linux/regmap.h>
+
+#define SENSEFB_FBIO_IOC_MAGIC 0xF1
+
+#define SENSEFB_FBIOGET_GAMMA _IO(SENSEFB_FBIO_IOC_MAGIC, 0)
+#define SENSEFB_FBIOSET_GAMMA _IO(SENSEFB_FBIO_IOC_MAGIC, 1)
+#define SENSEFB_FBIORESET_GAMMA _IO(SENSEFB_FBIO_IOC_MAGIC, 2)
+
+struct rpisense;
+
+struct rpisense_fb {
+	struct fb_info *info;
+	struct platform_device *pdev;
+	struct regmap *regmap;
+};
+
+#endif
diff --git a/include/linux/microchipphy.h b/include/linux/microchipphy.h
index 517288da19fd..626c450d71f4 100644
--- a/include/linux/microchipphy.h
+++ b/include/linux/microchipphy.h
@@ -61,6 +61,14 @@
 /* Registers specific to the LAN7800/LAN7850 embedded phy */
 #define LAN78XX_PHY_LED_MODE_SELECT		(0x1D)
 
+#define LAN78XX_PHY_CTRL3			(0x14)
+#define LAN78XX_PHY_CTRL3_AUTO_DOWNSHIFT	(0x0010)
+#define LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_MASK	(0x000c)
+#define LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_2	(0x0000)
+#define LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_3	(0x0004)
+#define LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_4	(0x0008)
+#define LAN78XX_PHY_CTRL3_DOWNSHIFT_CTRL_5	(0x000c)
+
 /* DSP registers */
 #define PHY_ARDENNES_MMD_DEV_3_PHY_CFG		(0x806A)
 #define PHY_ARDENNES_MMD_DEV_3_PHY_CFG_ZD_DLY_EN_	(0x2000)
diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h
index 2e455b20c37c..a6c766c157bd 100644
--- a/include/linux/mmc/card.h
+++ b/include/linux/mmc/card.h
@@ -296,6 +296,8 @@ struct mmc_card {
 #define MMC_QUIRK_BROKEN_CACHE_FLUSH	(1<<16)	/* Don't flush cache until the write has occurred */
 #define MMC_QUIRK_BROKEN_SD_POWEROFF_NOTIFY	(1<<17) /* Disable broken SD poweroff notify support */
 #define MMC_QUIRK_NO_UHS_DDR50_TUNING	(1<<18) /* Disable DDR50 tuning */
+#define MMC_QUIRK_WORKING_SD_CQ	(1<<30)		/* SD card has known-good CQ implementation */
+#define MMC_QUIRK_ERASE_BROKEN	(1<<31)		/* Skip erase */
 
 	bool			written_flag;	/* Indicates eMMC has been written since power on */
 	bool			reenable_cmdq;	/* Re-enable Command Queue */
@@ -320,6 +322,7 @@ struct mmc_card {
 	struct sd_switch_caps	sw_caps;	/* switch (CMD6) caps */
 	struct sd_ext_reg	ext_power;	/* SD extension reg for PM */
 	struct sd_ext_reg	ext_perf;	/* SD extension reg for PERF */
+	u8			*ext_reg_buf;	/* 512 byte block for extension register R/W */
 
 	unsigned int		sdio_funcs;	/* number of SDIO functions */
 	atomic_t		sdio_funcs_probed; /* number of probed SDIO funcs */
@@ -342,6 +345,8 @@ struct mmc_card {
 	unsigned int    nr_parts;
 
 	struct workqueue_struct *complete_wq;	/* Private workqueue */
+
+	unsigned int		max_posted_writes; /* command queue posted write limit */
 };
 
 static inline bool mmc_large_sector(struct mmc_card *card)
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index 68068ba57569..22bc8fe396c4 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -404,6 +404,7 @@ struct mmc_host {
 #define MMC_CAP2_CRYPTO		0
 #endif
 #define MMC_CAP2_ALT_GPT_TEGRA	(1 << 28)	/* Host with eMMC that has GPT entry at a non-standard location */
+#define MMC_CAP2_SD_CQE_PERMISSIVE	(1 << 31)	/* Ignore allow-list for CQ capable SD card detection */
 
 	int			fixed_drv_type;	/* fixed driver type for non-removable media */
 
diff --git a/include/linux/mmc/sd.h b/include/linux/mmc/sd.h
index 865cc0ca8543..eb4e05ef9abd 100644
--- a/include/linux/mmc/sd.h
+++ b/include/linux/mmc/sd.h
@@ -29,6 +29,9 @@
 #define SD_APP_OP_COND           41   /* bcr  [31:0] OCR         R3  */
 #define SD_APP_SEND_SCR          51   /* adtc                    R1  */
 
+  /* class 1 */
+#define SD_CMDQ_TASK_MGMT        43   /* ac   See below          R1b */
+
   /* class 11 */
 #define SD_READ_EXTR_SINGLE      48   /* adtc [31:0]             R1  */
 #define SD_WRITE_EXTR_SINGLE     49   /* adtc [31:0]             R1  */
@@ -61,6 +64,15 @@
  *	[7:0] Check Pattern (0xAA)
  */
 
+/*
+ * SD_CMDQ_TASK_MGMT argument format:
+ *
+ * [31:21] Reserved (0)
+ * [20:16] Task ID
+ * [15:4] Reserved (0)
+ * [3:0] Operation - 0x1 = abort all tasks, 0x2 = abort Task ID
+ */
+
 /*
  * SCR field definitions
  */
diff --git a/include/linux/module.h b/include/linux/module.h
index 1cb6f80e1b48..5097355b978f 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -515,7 +515,7 @@ struct module {
 	unsigned int num_bpf_raw_events;
 	struct bpf_raw_event_map *bpf_raw_events;
 #endif
-#ifdef CONFIG_DEBUG_INFO_BTF_MODULES
+#if 1
 	unsigned int btf_data_size;
 	unsigned int btf_base_data_size;
 	void *btf_data;
diff --git a/include/linux/phy.h b/include/linux/phy.h
index 49283facf932..1f74c94b552b 100644
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
@@ -611,7 +611,6 @@ struct macsec_ops;
  * @adv_old: Saved advertised while power saving for WoL
  * @supported_eee: supported PHY EEE linkmodes
  * @advertising_eee: Currently advertised EEE linkmodes
- * @eee_enabled: Flag indicating whether the EEE feature is enabled
  * @enable_tx_lpi: When True, MAC should transmit LPI to PHY
  * @eee_cfg: User configuration of EEE
  * @lp_advertising: Current link partner advertised linkmodes
@@ -731,7 +730,6 @@ struct phy_device {
 	/* used for eee validation and configuration*/
 	__ETHTOOL_DECLARE_LINK_MODE_MASK(supported_eee);
 	__ETHTOOL_DECLARE_LINK_MODE_MASK(advertising_eee);
-	bool eee_enabled;
 
 	/* Host supported PHY interface types. Should be ignored if empty. */
 	DECLARE_PHY_INTERFACE_MASK(host_interfaces);
diff --git a/include/linux/pio_instructions.h b/include/linux/pio_instructions.h
new file mode 100644
index 000000000000..a72934b1ed60
--- /dev/null
+++ b/include/linux/pio_instructions.h
@@ -0,0 +1,481 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+/*
+ * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
+ */
+
+#ifndef _HARDWARE_PIO_INSTRUCTIONS_H
+#define _HARDWARE_PIO_INSTRUCTIONS_H
+
+/** \brief PIO instruction encoding
+ *  \defgroup pio_instructions pio_instructions
+ *  \ingroup hardware_pio
+ *
+ * Functions for generating PIO instruction encodings programmatically. In debug builds
+ *`PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS` can be set to 1 to enable validation of encoding function
+ * parameters.
+ *
+ * For fuller descriptions of the instructions in question see the "RP2040 Datasheet"
+ */
+
+// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS, Enable/disable assertions in the PIO instructions, type=bool, default=0, group=pio_instructions
+#ifndef PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS
+#define PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS 0
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum pio_instr_bits {
+    pio_instr_bits_jmp = 0x0000,
+    pio_instr_bits_wait = 0x2000,
+    pio_instr_bits_in = 0x4000,
+    pio_instr_bits_out = 0x6000,
+    pio_instr_bits_push = 0x8000,
+    pio_instr_bits_pull = 0x8080,
+    pio_instr_bits_mov = 0xa000,
+    pio_instr_bits_irq = 0xc000,
+    pio_instr_bits_set = 0xe000,
+};
+
+#ifndef NDEBUG
+#define _PIO_INVALID_IN_SRC    0x08u
+#define _PIO_INVALID_OUT_DEST 0x10u
+#define _PIO_INVALID_SET_DEST 0x20u
+#define _PIO_INVALID_MOV_SRC  0x40u
+#define _PIO_INVALID_MOV_DEST 0x80u
+#else
+#define _PIO_INVALID_IN_SRC    0u
+#define _PIO_INVALID_OUT_DEST 0u
+#define _PIO_INVALID_SET_DEST 0u
+#define _PIO_INVALID_MOV_SRC  0u
+#define _PIO_INVALID_MOV_DEST 0u
+#endif
+
+/*! \brief Enumeration of values to pass for source/destination args for instruction encoding functions
+ *  \ingroup pio_instructions
+ *
+ * \note Not all values are suitable for all functions. Validity is only checked in debug mode when
+ * `PARAM_ASSERTIONS_ENABLED_PIO_INSTRUCTIONS` is 1
+ */
+enum pio_src_dest {
+    pio_pins = 0u,
+    pio_x = 1u,
+    pio_y = 2u,
+    pio_null = 3u | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_DEST,
+    pio_pindirs = 4u | _PIO_INVALID_IN_SRC | _PIO_INVALID_MOV_SRC | _PIO_INVALID_MOV_DEST,
+    pio_exec_mov = 4u | _PIO_INVALID_IN_SRC | _PIO_INVALID_OUT_DEST | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_SRC,
+    pio_status = 5u | _PIO_INVALID_IN_SRC | _PIO_INVALID_OUT_DEST | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_DEST,
+    pio_pc = 5u | _PIO_INVALID_IN_SRC | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_SRC,
+    pio_isr = 6u | _PIO_INVALID_SET_DEST,
+    pio_osr = 7u | _PIO_INVALID_OUT_DEST | _PIO_INVALID_SET_DEST,
+    pio_exec_out = 7u | _PIO_INVALID_IN_SRC | _PIO_INVALID_SET_DEST | _PIO_INVALID_MOV_SRC | _PIO_INVALID_MOV_DEST,
+};
+
+static inline uint _pio_major_instr_bits(uint instr) {
+    return instr & 0xe000u;
+}
+
+static inline uint _pio_encode_instr_and_args(enum pio_instr_bits instr_bits, uint arg1, uint arg2) {
+    valid_params_if(PIO_INSTRUCTIONS, arg1 <= 0x7);
+#if PARAM_ASSERTIONS_ENABLED(PIO_INSTRUCTIONS)
+    uint32_t major = _pio_major_instr_bits(instr_bits);
+    if (major == pio_instr_bits_in || major == pio_instr_bits_out) {
+        assert(arg2 && arg2 <= 32);
+    } else {
+        assert(arg2 <= 31);
+    }
+#endif
+    return instr_bits | (arg1 << 5u) | (arg2 & 0x1fu);
+}
+
+static inline uint _pio_encode_instr_and_src_dest(enum pio_instr_bits instr_bits, enum pio_src_dest dest, uint value) {
+    return _pio_encode_instr_and_args(instr_bits, dest & 7u, value);
+}
+
+/*! \brief Encode just the delay slot bits of an instruction
+ *  \ingroup pio_instructions
+ *
+ * \note This function does not return a valid instruction encoding; instead it returns an encoding of the delay
+ * slot suitable for `OR`ing with the result of an encoding function for an actual instruction. Care should be taken when
+ * combining the results of this function with the results of \ref pio_encode_sideset and \ref pio_encode_sideset_opt
+ * as they share the same bits within the instruction encoding.
+ *
+ * \param cycles the number of cycles 0-31 (or less if side set is being used)
+ * \return the delay slot bits to be ORed with an instruction encoding
+ */
+static inline uint pio_encode_delay(uint cycles) {
+    // note that the maximum cycles will be smaller if sideset_bit_count > 0
+    valid_params_if(PIO_INSTRUCTIONS, cycles <= 0x1f);
+    return cycles << 8u;
+}
+
+/*! \brief Encode just the side set bits of an instruction (in non optional side set mode)
+ *  \ingroup pio_instructions
+ *
+ * \note This function does not return a valid instruction encoding; instead it returns an encoding of the side set bits
+ * suitable for `OR`ing with the result of an encoding function for an actual instruction. Care should be taken when
+ * combining the results of this function with the results of \ref pio_encode_delay as they share the same bits
+ * within the instruction encoding.
+ *
+ * \param sideset_bit_count number of side set bits as would be specified via `.sideset` in pioasm
+ * \param value the value to sideset on the pins
+ * \return the side set bits to be ORed with an instruction encoding
+ */
+static inline uint pio_encode_sideset(uint sideset_bit_count, uint value) {
+    valid_params_if(PIO_INSTRUCTIONS, sideset_bit_count >= 1 && sideset_bit_count <= 5);
+    valid_params_if(PIO_INSTRUCTIONS, value <= ((1u << sideset_bit_count) - 1));
+    return value << (13u - sideset_bit_count);
+}
+
+/*! \brief Encode just the side set bits of an instruction (in optional -`opt` side set mode)
+ *  \ingroup pio_instructions
+ *
+ * \note This function does not return a valid instruction encoding; instead it returns an encoding of the side set bits
+ * suitable for `OR`ing with the result of an encoding function for an actual instruction. Care should be taken when
+ * combining the results of this function with the results of \ref pio_encode_delay as they share the same bits
+ * within the instruction encoding.
+ *
+ * \param sideset_bit_count number of side set bits as would be specified via `.sideset <n> opt` in pioasm
+ * \param value the value to sideset on the pins
+ * \return the side set bits to be ORed with an instruction encoding
+ */
+static inline uint pio_encode_sideset_opt(uint sideset_bit_count, uint value) {
+    valid_params_if(PIO_INSTRUCTIONS, sideset_bit_count >= 1 && sideset_bit_count <= 4);
+    valid_params_if(PIO_INSTRUCTIONS, value <= ((1u << sideset_bit_count) - 1));
+    return 0x1000u | value << (12u - sideset_bit_count);
+}
+
+/*! \brief Encode an unconditional JMP instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `JMP <addr>`
+ *
+ * \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_jmp(uint addr) {
+    return _pio_encode_instr_and_args(pio_instr_bits_jmp, 0, addr);
+}
+
+/*! \brief Encode a conditional JMP if scratch X zero instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `JMP !X <addr>`
+ *
+ * \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_jmp_not_x(uint addr) {
+    return _pio_encode_instr_and_args(pio_instr_bits_jmp, 1, addr);
+}
+
+/*! \brief Encode a conditional JMP if scratch X non-zero (and post-decrement X) instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `JMP X-- <addr>`
+ *
+ * \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_jmp_x_dec(uint addr) {
+    return _pio_encode_instr_and_args(pio_instr_bits_jmp, 2, addr);
+}
+
+/*! \brief Encode a conditional JMP if scratch Y zero instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `JMP !Y <addr>`
+ *
+ * \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_jmp_not_y(uint addr) {
+    return _pio_encode_instr_and_args(pio_instr_bits_jmp, 3, addr);
+}
+
+/*! \brief Encode a conditional JMP if scratch Y non-zero (and post-decrement Y) instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `JMP Y-- <addr>`
+ *
+ * \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_jmp_y_dec(uint addr) {
+    return _pio_encode_instr_and_args(pio_instr_bits_jmp, 4, addr);
+}
+
+/*! \brief Encode a conditional JMP if scratch X not equal scratch Y instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `JMP X!=Y <addr>`
+ *
+ * \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_jmp_x_ne_y(uint addr) {
+    return _pio_encode_instr_and_args(pio_instr_bits_jmp, 5, addr);
+}
+
+/*! \brief Encode a conditional JMP if input pin high instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `JMP PIN <addr>`
+ *
+ * \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_jmp_pin(uint addr) {
+    return _pio_encode_instr_and_args(pio_instr_bits_jmp, 6, addr);
+}
+
+/*! \brief Encode a conditional JMP if output shift register not empty instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `JMP !OSRE <addr>`
+ *
+ * \param addr The target address 0-31 (an absolute address within the PIO instruction memory)
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_jmp_not_osre(uint addr) {
+    return _pio_encode_instr_and_args(pio_instr_bits_jmp, 7, addr);
+}
+
+static inline uint _pio_encode_irq(bool relative, uint irq) {
+    valid_params_if(PIO_INSTRUCTIONS, irq <= 7);
+    return (relative ? 0x10u : 0x0u) | irq;
+}
+
+/*! \brief Encode a WAIT for GPIO pin instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `WAIT <polarity> GPIO <gpio>`
+ *
+ * \param polarity true for `WAIT 1`, false for `WAIT 0`
+ * \param gpio The real GPIO number 0-31
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_wait_gpio(bool polarity, uint gpio) {
+    return _pio_encode_instr_and_args(pio_instr_bits_wait, 0u | (polarity ? 4u : 0u), gpio);
+}
+
+/*! \brief Encode a WAIT for pin instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `WAIT <polarity> PIN <pin>`
+ *
+ * \param polarity true for `WAIT 1`, false for `WAIT 0`
+ * \param pin The pin number 0-31 relative to the executing SM's input pin mapping
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_wait_pin(bool polarity, uint pin) {
+    return _pio_encode_instr_and_args(pio_instr_bits_wait, 1u | (polarity ? 4u : 0u), pin);
+}
+
+/*! \brief Encode a WAIT for IRQ instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `WAIT <polarity> IRQ <irq> <relative>`
+ *
+ * \param polarity true for `WAIT 1`, false for `WAIT 0`
+ * \param relative true for a `WAIT IRQ <irq> REL`, false for regular `WAIT IRQ <irq>`
+ * \param irq the irq number 0-7
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_wait_irq(bool polarity, bool relative, uint irq) {
+    valid_params_if(PIO_INSTRUCTIONS, irq <= 7);
+    return _pio_encode_instr_and_args(pio_instr_bits_wait, 2u | (polarity ? 4u : 0u), _pio_encode_irq(relative, irq));
+}
+
+/*! \brief Encode an IN instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `IN <src>, <count>`
+ *
+ * \param src The source to take data from
+ * \param count The number of bits 1-32
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_in(enum pio_src_dest src, uint count) {
+    valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_IN_SRC));
+    return _pio_encode_instr_and_src_dest(pio_instr_bits_in, src, count);
+}
+
+/*! \brief Encode an OUT instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `OUT <src>, <count>`
+ *
+ * \param dest The destination to write data to
+ * \param count The number of bits 1-32
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_out(enum pio_src_dest dest, uint count) {
+    valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_OUT_DEST));
+    return _pio_encode_instr_and_src_dest(pio_instr_bits_out, dest, count);
+}
+
+/*! \brief Encode a PUSH instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `PUSH <if_full>, <block>`
+ *
+ * \param if_full true for `PUSH IF_FULL ...`, false for `PUSH ...`
+ * \param block true for `PUSH ... BLOCK`, false for `PUSH ...`
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_push(bool if_full, bool block) {
+    return _pio_encode_instr_and_args(pio_instr_bits_push, (if_full ? 2u : 0u) | (block ? 1u : 0u), 0);
+}
+
+/*! \brief Encode a PULL instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `PULL <if_empty>, <block>`
+ *
+ * \param if_empty true for `PULL IF_EMPTY ...`, false for `PULL ...`
+ * \param block true for `PULL ... BLOCK`, false for `PULL ...`
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_pull(bool if_empty, bool block) {
+    return _pio_encode_instr_and_args(pio_instr_bits_pull, (if_empty ? 2u : 0u) | (block ? 1u : 0u), 0);
+}
+
+/*! \brief Encode a MOV instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `MOV <dest>, <src>`
+ *
+ * \param dest The destination to write data to
+ * \param src The source to take data from
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_mov(enum pio_src_dest dest, enum pio_src_dest src) {
+    valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_MOV_DEST));
+    valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_MOV_SRC));
+    return _pio_encode_instr_and_src_dest(pio_instr_bits_mov, dest, src & 7u);
+}
+
+/*! \brief Encode a MOV instruction with bit invert
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `MOV <dest>, ~<src>`
+ *
+ * \param dest The destination to write inverted data to
+ * \param src The source to take data from
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_mov_not(enum pio_src_dest dest, enum pio_src_dest src) {
+    valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_MOV_DEST));
+    valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_MOV_SRC));
+    return _pio_encode_instr_and_src_dest(pio_instr_bits_mov, dest, (1u << 3u) | (src & 7u));
+}
+
+/*! \brief Encode a MOV instruction with bit reverse
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `MOV <dest>, ::<src>`
+ *
+ * \param dest The destination to write bit reversed data to
+ * \param src The source to take data from
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_mov_reverse(enum pio_src_dest dest, enum pio_src_dest src) {
+    valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_MOV_DEST));
+    valid_params_if(PIO_INSTRUCTIONS, !(src & _PIO_INVALID_MOV_SRC));
+    return _pio_encode_instr_and_src_dest(pio_instr_bits_mov, dest, (2u << 3u) | (src & 7u));
+}
+
+/*! \brief Encode a IRQ SET instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `IRQ SET <irq> <relative>`
+ *
+ * \param relative true for a `IRQ SET <irq> REL`, false for regular `IRQ SET <irq>`
+ * \param irq the irq number 0-7
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_irq_set(bool relative, uint irq) {
+    return _pio_encode_instr_and_args(pio_instr_bits_irq, 0, _pio_encode_irq(relative, irq));
+}
+
+/*! \brief Encode a IRQ WAIT instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `IRQ WAIT <irq> <relative>`
+ *
+ * \param relative true for a `IRQ WAIT <irq> REL`, false for regular `IRQ WAIT <irq>`
+ * \param irq the irq number 0-7
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_irq_wait(bool relative, uint irq) {
+    return _pio_encode_instr_and_args(pio_instr_bits_irq, 1, _pio_encode_irq(relative, irq));
+}
+
+/*! \brief Encode a IRQ CLEAR instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `IRQ CLEAR <irq> <relative>`
+ *
+ * \param relative true for a `IRQ CLEAR <irq> REL`, false for regular `IRQ CLEAR <irq>`
+ * \param irq the irq number 0-7
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_irq_clear(bool relative, uint irq) {
+    return _pio_encode_instr_and_args(pio_instr_bits_irq, 2, _pio_encode_irq(relative, irq));
+}
+
+/*! \brief Encode a SET instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `SET <dest>, <value>`
+ *
+ * \param dest The destination to apply the value to
+ * \param value The value 0-31
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_set(enum pio_src_dest dest, uint value) {
+    valid_params_if(PIO_INSTRUCTIONS, !(dest & _PIO_INVALID_SET_DEST));
+    return _pio_encode_instr_and_src_dest(pio_instr_bits_set, dest, value);
+}
+
+/*! \brief Encode a NOP instruction
+ *  \ingroup pio_instructions
+ *
+ * This is the equivalent of `NOP` which is itself encoded as `MOV y, y`
+ *
+ * \return The instruction encoding with 0 delay and no side set value
+ * \see pio_encode_delay, pio_encode_sideset, pio_encode_sideset_opt
+ */
+static inline uint pio_encode_nop(void) {
+    return pio_encode_mov(pio_y, pio_y);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/linux/pio_rp1.h b/include/linux/pio_rp1.h
new file mode 100644
index 000000000000..68083b83a201
--- /dev/null
+++ b/include/linux/pio_rp1.h
@@ -0,0 +1,1020 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2024 Raspberry Pi Ltd.
+ * All rights reserved.
+ */
+
+#ifndef _PIO_RP1_H
+#define _PIO_RP1_H
+
+#include <uapi/misc/rp1_pio_if.h>
+
+#define PARAM_WARNINGS_ENABLED 1
+
+#ifdef DEBUG
+#define PARAM_WARNINGS_ENABLED 1
+#endif
+
+#ifndef PARAM_WARNINGS_ENABLED
+#define PARAM_WARNINGS_ENABLED 0
+#endif
+
+#define bad_params_if(client, test) \
+	({ bool f = (test); if (f && client) pio_set_error(client, -EINVAL); \
+		if (f && PARAM_WARNINGS_ENABLED) WARN_ON((test)); \
+		f; })
+
+#ifndef PARAM_ASSERTIONS_ENABLE_ALL
+#define PARAM_ASSERTIONS_ENABLE_ALL 0
+#endif
+
+#ifndef PARAM_ASSERTIONS_DISABLE_ALL
+#define PARAM_ASSERTIONS_DISABLE_ALL 0
+#endif
+
+#define PARAM_ASSERTIONS_ENABLED(x) \
+	((PARAM_ASSERTIONS_ENABLED_ ## x || PARAM_ASSERTIONS_ENABLE_ALL) && \
+	 !PARAM_ASSERTIONS_DISABLE_ALL)
+#define valid_params_if(x, test) ({if (PARAM_ASSERTIONS_ENABLED(x)) WARN_ON(test); })
+
+#include <linux/pio_instructions.h>
+
+#define NUM_PIO_STATE_MACHINES		4
+#define PIO_INSTRUCTION_COUNT		32
+#define PIO_ORIGIN_ANY			((uint)(~0))
+#define PIO_ORIGIN_INVALID		((uint)(~0))
+#define GPIOS_MASK			((1 << RP1_PIO_GPIO_COUNT) - 1)
+
+#define PICO_NO_HARDWARE		0
+
+#define pio0				pio_open_helper(0)
+
+#define PROC_PIO_SM0_PINCTRL_OUT_BASE_BITS	0x0000001f
+#define PROC_PIO_SM0_PINCTRL_OUT_BASE_LSB	0
+#define PROC_PIO_SM0_PINCTRL_OUT_COUNT_BITS	0x03f00000
+#define PROC_PIO_SM0_PINCTRL_OUT_COUNT_LSB	20
+#define PROC_PIO_SM0_PINCTRL_SET_BASE_BITS	0x000003e0
+#define PROC_PIO_SM0_PINCTRL_SET_BASE_LSB	5
+#define PROC_PIO_SM0_PINCTRL_SET_COUNT_BITS	0x1c000000
+#define PROC_PIO_SM0_PINCTRL_SET_COUNT_LSB	26
+#define PROC_PIO_SM0_PINCTRL_IN_BASE_BITS	0x000f8000
+#define PROC_PIO_SM0_PINCTRL_IN_BASE_LSB	15
+#define PROC_PIO_SM0_PINCTRL_SIDESET_BASE_BITS	0x00007c00
+#define PROC_PIO_SM0_PINCTRL_SIDESET_BASE_LSB	10
+#define PROC_PIO_SM0_PINCTRL_SIDESET_COUNT_BITS	0xe0000000
+#define PROC_PIO_SM0_PINCTRL_SIDESET_COUNT_LSB	29
+#define PROC_PIO_SM0_EXECCTRL_SIDE_EN_BITS	0x40000000
+#define PROC_PIO_SM0_EXECCTRL_SIDE_EN_LSB	30
+#define PROC_PIO_SM0_EXECCTRL_SIDE_PINDIR_BITS	0x20000000
+#define PROC_PIO_SM0_EXECCTRL_SIDE_PINDIR_LSB	29
+#define PROC_PIO_SM0_CLKDIV_INT_LSB		16
+#define PROC_PIO_SM0_CLKDIV_FRAC_LSB		8
+#define PROC_PIO_SM0_EXECCTRL_WRAP_TOP_BITS	0x0001f000
+#define PROC_PIO_SM0_EXECCTRL_WRAP_TOP_LSB	12
+#define PROC_PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS	0x00000f80
+#define PROC_PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB	7
+#define PROC_PIO_SM0_EXECCTRL_JMP_PIN_BITS	0x1f000000
+#define PROC_PIO_SM0_EXECCTRL_JMP_PIN_LSB	24
+#define PROC_PIO_SM0_SHIFTCTRL_IN_SHIFTDIR_BITS	0x00040000
+#define PROC_PIO_SM0_SHIFTCTRL_IN_SHIFTDIR_LSB	18
+#define PROC_PIO_SM0_SHIFTCTRL_AUTOPULL_BITS	0x00020000
+#define PROC_PIO_SM0_SHIFTCTRL_AUTOPULL_LSB	17
+#define PROC_PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS	0x00010000
+#define PROC_PIO_SM0_SHIFTCTRL_AUTOPUSH_LSB	16
+#define PROC_PIO_SM0_SHIFTCTRL_PUSH_THRESH_BITS	0x01f00000
+#define PROC_PIO_SM0_SHIFTCTRL_PUSH_THRESH_LSB	20
+#define PROC_PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_BITS	0x00080000
+#define PROC_PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_LSB	19
+#define PROC_PIO_SM0_SHIFTCTRL_PULL_THRESH_BITS	0x3e000000
+#define PROC_PIO_SM0_SHIFTCTRL_PULL_THRESH_LSB	25
+#define PROC_PIO_SM0_SHIFTCTRL_FJOIN_TX_BITS	0x40000000
+#define PROC_PIO_SM0_SHIFTCTRL_FJOIN_TX_LSB	30
+#define PROC_PIO_SM0_SHIFTCTRL_FJOIN_RX_BITS	0x80000000
+#define PROC_PIO_SM0_SHIFTCTRL_FJOIN_RX_LSB	31
+#define PROC_PIO_SM0_EXECCTRL_OUT_STICKY_BITS	0x00020000
+#define PROC_PIO_SM0_EXECCTRL_OUT_STICKY_LSB	17
+#define PROC_PIO_SM0_EXECCTRL_INLINE_OUT_EN_BITS	0x00040000
+#define PROC_PIO_SM0_EXECCTRL_INLINE_OUT_EN_LSB	18
+#define PROC_PIO_SM0_EXECCTRL_OUT_EN_SEL_BITS	0x00f80000
+#define PROC_PIO_SM0_EXECCTRL_OUT_EN_SEL_LSB	19
+#define PROC_PIO_SM0_EXECCTRL_STATUS_SEL_BITS	0x00000020
+#define PROC_PIO_SM0_EXECCTRL_STATUS_SEL_LSB	5
+#define PROC_PIO_SM0_EXECCTRL_STATUS_N_BITS	0x0000001f
+#define PROC_PIO_SM0_EXECCTRL_STATUS_N_LSB	0
+
+enum pio_fifo_join {
+	PIO_FIFO_JOIN_NONE = 0,
+	PIO_FIFO_JOIN_TX = 1,
+	PIO_FIFO_JOIN_RX = 2,
+};
+
+enum pio_mov_status_type {
+	STATUS_TX_LESSTHAN = 0,
+	STATUS_RX_LESSTHAN = 1
+};
+
+enum pio_xfer_dir {
+	PIO_DIR_TO_SM,
+	PIO_DIR_FROM_SM,
+	PIO_DIR_COUNT
+};
+
+enum clock_index {
+	clk_sys = 5
+};
+
+typedef struct pio_program {
+	const uint16_t *instructions;
+	uint8_t length;
+	int8_t origin; // required instruction memory origin or -1
+} pio_program_t;
+
+enum gpio_function {
+	GPIO_FUNC_FSEL0 = 0,
+	GPIO_FUNC_FSEL1 = 1,
+	GPIO_FUNC_FSEL2 = 2,
+	GPIO_FUNC_FSEL3 = 3,
+	GPIO_FUNC_FSEL4 = 4,
+	GPIO_FUNC_FSEL5 = 5,
+	GPIO_FUNC_FSEL6 = 6,
+	GPIO_FUNC_FSEL7 = 7,
+	GPIO_FUNC_FSEL8 = 8,
+	GPIO_FUNC_NULL = 0x1f,
+
+	// Name a few
+	GPIO_FUNC_SYS_RIO = 5,
+	GPIO_FUNC_PROC_RIO = 6,
+	GPIO_FUNC_PIO = 7,
+};
+
+enum gpio_irq_level {
+	GPIO_IRQ_LEVEL_LOW = 0x1u,
+	GPIO_IRQ_LEVEL_HIGH = 0x2u,
+	GPIO_IRQ_EDGE_FALL = 0x4u,
+	GPIO_IRQ_EDGE_RISE = 0x8u,
+};
+
+enum gpio_override {
+	GPIO_OVERRIDE_NORMAL = 0,
+	GPIO_OVERRIDE_INVERT = 1,
+	GPIO_OVERRIDE_LOW = 2,
+	GPIO_OVERRIDE_HIGH = 3,
+};
+enum gpio_slew_rate {
+	GPIO_SLEW_RATE_SLOW = 0,
+	GPIO_SLEW_RATE_FAST = 1
+};
+
+enum gpio_drive_strength {
+	GPIO_DRIVE_STRENGTH_2MA = 0,
+	GPIO_DRIVE_STRENGTH_4MA = 1,
+	GPIO_DRIVE_STRENGTH_8MA = 2,
+	GPIO_DRIVE_STRENGTH_12MA = 3
+};
+
+struct fp24_8 {
+	uint32_t val;
+};
+
+typedef rp1_pio_sm_config pio_sm_config;
+
+typedef struct rp1_pio_client *PIO;
+
+int rp1_pio_init(void);
+PIO rp1_pio_open(void);
+void rp1_pio_close(struct rp1_pio_client *client);
+void rp1_pio_set_error(struct rp1_pio_client *client, int err);
+int rp1_pio_get_error(const struct rp1_pio_client *client);
+void rp1_pio_clear_error(struct rp1_pio_client *client);
+int rp1_pio_sm_config_xfer(struct rp1_pio_client *client, uint sm, uint dir,
+			   uint buf_size, uint buf_count);
+int rp1_pio_sm_xfer_data(struct rp1_pio_client *client, uint sm, uint dir,
+			 uint data_bytes, void *data, dma_addr_t dma_addr,
+			 void (*callback)(void *param), void *param);
+
+int rp1_pio_can_add_program(struct rp1_pio_client *client, void *param);
+int rp1_pio_add_program(struct rp1_pio_client *client, void *param);
+int rp1_pio_remove_program(struct rp1_pio_client *client, void *param);
+int rp1_pio_clear_instr_mem(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_claim(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_unclaim(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_is_claimed(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_init(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_set_config(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_exec(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_clear_fifos(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_set_clkdiv(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_set_pins(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_set_pindirs(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_set_enabled(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_restart(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_clkdiv_restart(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_enable_sync(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_put(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_get(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_set_dmactrl(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_fifo_state(struct rp1_pio_client *client, void *param);
+int rp1_pio_sm_drain_tx(struct rp1_pio_client *client, void *param);
+int rp1_pio_gpio_init(struct rp1_pio_client *client, void *param);
+int rp1_pio_gpio_set_function(struct rp1_pio_client *client, void *param);
+int rp1_pio_gpio_set_pulls(struct rp1_pio_client *client, void *param);
+int rp1_pio_gpio_set_outover(struct rp1_pio_client *client, void *param);
+int rp1_pio_gpio_set_inover(struct rp1_pio_client *client, void *param);
+int rp1_pio_gpio_set_oeover(struct rp1_pio_client *client, void *param);
+int rp1_pio_gpio_set_input_enabled(struct rp1_pio_client *client, void *param);
+int rp1_pio_gpio_set_drive_strength(struct rp1_pio_client *client, void *param);
+
+static inline int pio_init(void)
+{
+	return rp1_pio_init();
+}
+
+static inline struct rp1_pio_client *pio_open(void)
+{
+	return rp1_pio_open();
+}
+
+static inline void pio_close(struct rp1_pio_client *client)
+{
+	rp1_pio_close(client);
+}
+
+static inline void pio_set_error(struct rp1_pio_client *client, int err)
+{
+	rp1_pio_set_error(client, err);
+}
+
+static inline int pio_get_error(const struct rp1_pio_client *client)
+{
+	return rp1_pio_get_error(client);
+}
+
+static inline void pio_clear_error(struct rp1_pio_client *client)
+{
+	rp1_pio_clear_error(client);
+}
+
+static inline int pio_sm_config_xfer(struct rp1_pio_client *client, uint sm, uint dir,
+				     uint buf_size, uint buf_count)
+{
+	return rp1_pio_sm_config_xfer(client, sm, dir, buf_size, buf_count);
+}
+
+static inline int pio_sm_xfer_data(struct rp1_pio_client *client, uint sm, uint dir,
+				   uint data_bytes, void *data, dma_addr_t dma_addr,
+				   void (*callback)(void *param), void *param)
+{
+	return rp1_pio_sm_xfer_data(client, sm, dir, data_bytes, data, dma_addr, callback, param);
+}
+
+static inline struct fp24_8 make_fp24_8(uint mul, uint div)
+{
+	struct fp24_8 res = { .val = ((unsigned long long)mul << 8) / div };
+
+	return res;
+}
+
+static inline bool pio_can_add_program(struct rp1_pio_client *client,
+				       const pio_program_t *program)
+{
+	struct rp1_pio_add_program_args args;
+
+	if (bad_params_if(client, program->length > PIO_INSTRUCTION_COUNT))
+		return false;
+	args.origin = (uint16_t)((program->origin == -1) ? PIO_ORIGIN_ANY : program->origin);
+	args.num_instrs = program->length;
+
+	memcpy(args.instrs, program->instructions, args.num_instrs * sizeof(args.instrs[0]));
+	return rp1_pio_can_add_program(client, &args);
+}
+
+static inline bool pio_can_add_program_at_offset(struct rp1_pio_client *client,
+						 const pio_program_t *program, uint offset)
+{
+	struct rp1_pio_add_program_args args;
+
+	if (bad_params_if(client, program->length > PIO_INSTRUCTION_COUNT ||
+			  offset >= PIO_INSTRUCTION_COUNT))
+		return false;
+	args.origin = (uint16_t)offset;
+	args.num_instrs = program->length;
+
+	memcpy(args.instrs, program->instructions, args.num_instrs * sizeof(args.instrs[0]));
+	return !rp1_pio_can_add_program(client, &args);
+}
+
+static inline uint pio_add_program(struct rp1_pio_client *client, const pio_program_t *program)
+{
+	struct rp1_pio_add_program_args args;
+	int offset;
+
+	if (bad_params_if(client, program->length > PIO_INSTRUCTION_COUNT))
+		return PIO_ORIGIN_INVALID;
+	args.origin = (uint16_t)((program->origin == -1) ? PIO_ORIGIN_ANY : program->origin);
+	args.num_instrs = program->length;
+
+	memcpy(args.instrs, program->instructions, args.num_instrs * sizeof(args.instrs[0]));
+	offset = rp1_pio_add_program(client, &args);
+	return (offset >= 0) ? offset : PIO_ORIGIN_INVALID;
+}
+
+static inline int pio_add_program_at_offset(struct rp1_pio_client *client,
+					    const pio_program_t *program, uint offset)
+{
+	struct rp1_pio_add_program_args args;
+
+	if (bad_params_if(client, program->length > PIO_INSTRUCTION_COUNT ||
+				  offset >= PIO_INSTRUCTION_COUNT))
+		return -EINVAL;
+	args.origin = (uint16_t)offset;
+	args.num_instrs = program->length;
+
+	memcpy(args.instrs, program->instructions, args.num_instrs * sizeof(args.instrs[0]));
+	return rp1_pio_add_program(client, &args);
+}
+
+static inline int pio_remove_program(struct rp1_pio_client *client, const pio_program_t *program,
+				     uint loaded_offset)
+{
+	struct rp1_pio_remove_program_args args;
+
+	args.origin = (uint16_t)loaded_offset;
+	args.num_instrs = program->length;
+
+	return rp1_pio_remove_program(client, &args);
+}
+
+static inline int pio_clear_instruction_memory(struct rp1_pio_client *client)
+{
+	return rp1_pio_clear_instr_mem(client, NULL);
+}
+
+static inline int pio_sm_claim(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_claim_args args = { .mask = 1 << sm };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+
+	return rp1_pio_sm_claim(client, &args);
+}
+
+static inline int pio_claim_sm_mask(struct rp1_pio_client *client, uint mask)
+{
+	struct rp1_pio_sm_claim_args args = { .mask = mask };
+
+	if (bad_params_if(client, mask >= (1 << NUM_PIO_STATE_MACHINES)))
+		return -EINVAL;
+
+	return rp1_pio_sm_claim(client, &args);
+}
+
+static inline int pio_sm_unclaim(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_claim_args args = { .mask = 1 << sm };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+
+	return rp1_pio_sm_unclaim(client, &args);
+}
+
+static inline int pio_claim_unused_sm(struct rp1_pio_client *client, bool required)
+{
+	struct rp1_pio_sm_claim_args args = { .mask = 0 };
+	int sm;
+
+	sm = rp1_pio_sm_claim(client, &args);
+	if (sm < 0 && required)
+		WARN_ON("No PIO state machines are available");
+	return sm;
+}
+
+static inline bool pio_sm_is_claimed(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_claim_args args = { .mask = (1 << sm) };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return true;
+	return rp1_pio_sm_is_claimed(client, &args);
+}
+
+static inline int pio_sm_init(struct rp1_pio_client *client, uint sm, uint initial_pc,
+			      const pio_sm_config *config)
+{
+	struct rp1_pio_sm_init_args args = { .sm = sm, .initial_pc = initial_pc,
+					     .config = *config };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES ||
+				  initial_pc >= PIO_INSTRUCTION_COUNT))
+		return -EINVAL;
+
+	return rp1_pio_sm_init(client, &args);
+}
+
+static inline int pio_sm_set_config(struct rp1_pio_client *client, uint sm,
+				    const pio_sm_config *config)
+{
+	struct rp1_pio_sm_init_args args = { .sm = sm, .config = *config };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+
+	return rp1_pio_sm_set_config(client, &args);
+}
+
+static inline int pio_sm_exec(struct rp1_pio_client *client, uint sm, uint instr)
+{
+	struct rp1_pio_sm_exec_args args = { .sm = sm, .instr = instr, .blocking = false };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES || instr > (uint16_t)~0))
+		return -EINVAL;
+
+	return rp1_pio_sm_exec(client, &args);
+}
+
+static inline int pio_sm_exec_wait_blocking(struct rp1_pio_client *client, uint sm, uint instr)
+{
+	struct rp1_pio_sm_exec_args args = { .sm = sm, .instr = instr, .blocking = true };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES || instr > (uint16_t)~0))
+		return -EINVAL;
+
+	return rp1_pio_sm_exec(client, &args);
+}
+
+static inline int pio_sm_clear_fifos(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_clear_fifos_args args = { .sm = sm };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	return rp1_pio_sm_clear_fifos(client, &args);
+}
+
+static inline bool pio_calculate_clkdiv_from_fp24_8(struct fp24_8 div, uint16_t *div_int,
+						   uint8_t *div_frac)
+{
+	uint inum = (div.val >> 8);
+
+	if (bad_params_if(NULL, inum < 1 || inum > 65536))
+		return false;
+	*div_int = (uint16_t)inum;
+	if (*div_int == 0)
+		*div_frac = 0;
+	else
+		*div_frac = div.val & 0xff;
+	return true;
+}
+
+static inline int pio_sm_set_clkdiv_int_frac(struct rp1_pio_client *client, uint sm,
+					     uint16_t div_int, uint8_t div_frac)
+{
+	struct rp1_pio_sm_set_clkdiv_args args = { .sm = sm, .div_int = div_int,
+						   .div_frac = div_frac };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES ||
+			  (div_int == 0 && div_frac != 0)))
+		return -EINVAL;
+	return rp1_pio_sm_set_clkdiv(client, &args);
+}
+
+static inline int pio_sm_set_clkdiv(struct rp1_pio_client *client, uint sm, struct fp24_8 div)
+{
+	struct rp1_pio_sm_set_clkdiv_args args = { .sm = sm };
+
+	if (!pio_calculate_clkdiv_from_fp24_8(div, &args.div_int, &args.div_frac))
+		return -EINVAL;
+	return rp1_pio_sm_set_clkdiv(client, &args);
+}
+
+static inline int pio_sm_set_pins(struct rp1_pio_client *client, uint sm, uint32_t pin_values)
+{
+	struct rp1_pio_sm_set_pins_args args = { .sm = sm, .values = pin_values,
+						 .mask = GPIOS_MASK };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	return rp1_pio_sm_set_pins(client, &args);
+}
+
+static inline int pio_sm_set_pins_with_mask(struct rp1_pio_client *client, uint sm,
+					    uint32_t pin_values, uint32_t pin_mask)
+{
+	struct rp1_pio_sm_set_pins_args args = { .sm = sm, .values = pin_values,
+						 .mask = pin_mask };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	return rp1_pio_sm_set_pins(client, &args);
+}
+
+static inline int pio_sm_set_pindirs_with_mask(struct rp1_pio_client *client, uint sm,
+					       uint32_t pin_dirs, uint32_t pin_mask)
+{
+	struct rp1_pio_sm_set_pindirs_args args = { .sm = sm, .dirs = pin_dirs,
+						    .mask = pin_mask };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES ||
+			      (pin_dirs & GPIOS_MASK) != pin_dirs ||
+			      (pin_mask & pin_mask) != pin_mask))
+		return -EINVAL;
+	return rp1_pio_sm_set_pindirs(client, &args);
+}
+
+static inline int pio_sm_set_consecutive_pindirs(struct rp1_pio_client *client, uint sm,
+						 uint pin_base, uint pin_count, bool is_out)
+{
+	uint32_t mask = ((1 << pin_count) - 1) << pin_base;
+	struct rp1_pio_sm_set_pindirs_args args = { .sm = sm, .dirs = is_out ? mask : 0,
+						    .mask = mask };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES ||
+			      pin_base >= RP1_PIO_GPIO_COUNT ||
+			      pin_count > RP1_PIO_GPIO_COUNT ||
+			      (pin_base + pin_count) > RP1_PIO_GPIO_COUNT))
+		return -EINVAL;
+	return rp1_pio_sm_set_pindirs(client, &args);
+}
+
+static inline int pio_sm_set_enabled(struct rp1_pio_client *client, uint sm, bool enabled)
+{
+	struct rp1_pio_sm_set_enabled_args args = { .mask = (1 << sm), .enable = enabled };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	return rp1_pio_sm_set_enabled(client, &args);
+}
+
+static inline int pio_set_sm_mask_enabled(struct rp1_pio_client *client, uint32_t mask,
+					  bool enabled)
+{
+	struct rp1_pio_sm_set_enabled_args args = { .mask = mask, .enable = enabled };
+
+	if (bad_params_if(client, mask >= (1 << NUM_PIO_STATE_MACHINES)))
+		return -EINVAL;
+	return rp1_pio_sm_set_enabled(client, &args);
+}
+
+static inline int pio_sm_restart(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_restart_args args = { .mask = (1 << sm) };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	return rp1_pio_sm_restart(client, &args);
+}
+
+static inline int pio_restart_sm_mask(struct rp1_pio_client *client, uint32_t mask)
+{
+	struct rp1_pio_sm_restart_args args = { .mask = (uint16_t)mask };
+
+	if (bad_params_if(client, mask >= (1 << NUM_PIO_STATE_MACHINES)))
+		return -EINVAL;
+	return rp1_pio_sm_restart(client, &args);
+}
+
+static inline int pio_sm_clkdiv_restart(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_restart_args args = { .mask = (1 << sm) };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	return rp1_pio_sm_clkdiv_restart(client, &args);
+}
+
+static inline int pio_clkdiv_restart_sm_mask(struct rp1_pio_client *client, uint32_t mask)
+{
+	struct rp1_pio_sm_restart_args args = { .mask = (uint16_t)mask };
+
+	if (bad_params_if(client, mask >= (1 << NUM_PIO_STATE_MACHINES)))
+		return -EINVAL;
+	return rp1_pio_sm_clkdiv_restart(client, &args);
+}
+
+static inline int pio_enable_sm_in_sync_mask(struct rp1_pio_client *client, uint32_t mask)
+{
+	struct rp1_pio_sm_enable_sync_args args = { .mask = (uint16_t)mask };
+
+	if (bad_params_if(client, mask >= (1 << NUM_PIO_STATE_MACHINES)))
+		return -EINVAL;
+	return rp1_pio_sm_enable_sync(client, &args);
+}
+
+static inline int pio_sm_set_dmactrl(struct rp1_pio_client *client, uint sm, bool is_tx,
+				     uint32_t ctrl)
+{
+	struct rp1_pio_sm_set_dmactrl_args args = { .sm = sm, .is_tx = is_tx, .ctrl = ctrl };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	return rp1_pio_sm_set_dmactrl(client, &args);
+};
+
+static inline int pio_sm_drain_tx_fifo(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_clear_fifos_args args = { .sm = sm };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	return rp1_pio_sm_drain_tx(client, &args);
+};
+
+static inline int pio_sm_put(struct rp1_pio_client *client, uint sm, uint32_t data)
+{
+	struct rp1_pio_sm_put_args args = { .sm = (uint16_t)sm, .blocking = false, .data = data };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	return rp1_pio_sm_put(client, &args);
+}
+
+static inline int pio_sm_put_blocking(struct rp1_pio_client *client, uint sm, uint32_t data)
+{
+	struct rp1_pio_sm_put_args args = { .sm = (uint16_t)sm, .blocking = true, .data = data };
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	return rp1_pio_sm_put(client, &args);
+}
+
+static inline uint32_t pio_sm_get(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_get_args args = { .sm = (uint16_t)sm, .blocking = false };
+
+	if (!bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		rp1_pio_sm_get(client, &args);
+	return args.data;
+}
+
+static inline uint32_t pio_sm_get_blocking(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_get_args args = { .sm = (uint16_t)sm, .blocking = true };
+
+	if (!bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		rp1_pio_sm_get(client, &args);
+	return args.data;
+}
+
+static inline int pio_sm_is_rx_fifo_empty(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = false };
+	int ret;
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	ret = rp1_pio_sm_fifo_state(client, &args);
+	if (ret == sizeof(args))
+		ret = args.empty;
+	return ret;
+};
+
+static inline int pio_sm_is_rx_fifo_full(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = false };
+	int ret;
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	ret = rp1_pio_sm_fifo_state(client, &args);
+	if (ret == sizeof(args))
+		ret = args.full;
+	return ret;
+};
+
+static inline int pio_sm_rx_fifo_level(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = false };
+	int ret;
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	ret = rp1_pio_sm_fifo_state(client, &args);
+	if (ret == sizeof(args))
+		ret = args.level;
+	return ret;
+};
+
+static inline int pio_sm_is_tx_fifo_empty(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = true };
+	int ret;
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	ret = rp1_pio_sm_fifo_state(client, &args);
+	if (ret == sizeof(args))
+		ret = args.empty;
+	return ret;
+};
+
+static inline int pio_sm_is_tx_fifo_full(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = true };
+	int ret;
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	ret = rp1_pio_sm_fifo_state(client, &args);
+	if (ret == sizeof(args))
+		ret = args.full;
+	return ret;
+};
+
+static inline int pio_sm_tx_fifo_level(struct rp1_pio_client *client, uint sm)
+{
+	struct rp1_pio_sm_fifo_state_args args = { .sm = sm, .tx = true };
+	int ret;
+
+	if (bad_params_if(client, sm >= NUM_PIO_STATE_MACHINES))
+		return -EINVAL;
+	ret = rp1_pio_sm_fifo_state(client, &args);
+	if (ret == sizeof(args))
+		ret = args.level;
+	return ret;
+};
+
+static inline void sm_config_set_out_pins(pio_sm_config *c, uint out_base, uint out_count)
+{
+	if (bad_params_if(NULL, out_base >= RP1_PIO_GPIO_COUNT ||
+				    out_count > RP1_PIO_GPIO_COUNT))
+		return;
+
+	c->pinctrl = (c->pinctrl & ~(PROC_PIO_SM0_PINCTRL_OUT_BASE_BITS |
+				     PROC_PIO_SM0_PINCTRL_OUT_COUNT_BITS)) |
+			(out_base << PROC_PIO_SM0_PINCTRL_OUT_BASE_LSB) |
+			(out_count << PROC_PIO_SM0_PINCTRL_OUT_COUNT_LSB);
+}
+
+static inline void sm_config_set_set_pins(pio_sm_config *c, uint set_base, uint set_count)
+{
+	if (bad_params_if(NULL, set_base >= RP1_PIO_GPIO_COUNT ||
+				    set_count > 5))
+		return;
+
+	c->pinctrl = (c->pinctrl & ~(PROC_PIO_SM0_PINCTRL_SET_BASE_BITS |
+				     PROC_PIO_SM0_PINCTRL_SET_COUNT_BITS)) |
+			(set_base << PROC_PIO_SM0_PINCTRL_SET_BASE_LSB) |
+			(set_count << PROC_PIO_SM0_PINCTRL_SET_COUNT_LSB);
+}
+
+
+static inline void sm_config_set_in_pins(pio_sm_config *c, uint in_base)
+{
+	if (bad_params_if(NULL, in_base >= RP1_PIO_GPIO_COUNT))
+		return;
+
+	c->pinctrl = (c->pinctrl & ~PROC_PIO_SM0_PINCTRL_IN_BASE_BITS) |
+			(in_base << PROC_PIO_SM0_PINCTRL_IN_BASE_LSB);
+}
+
+static inline void sm_config_set_sideset_pins(pio_sm_config *c, uint sideset_base)
+{
+	if (bad_params_if(NULL, sideset_base >= RP1_PIO_GPIO_COUNT))
+		return;
+
+	c->pinctrl = (c->pinctrl & ~PROC_PIO_SM0_PINCTRL_SIDESET_BASE_BITS) |
+			(sideset_base << PROC_PIO_SM0_PINCTRL_SIDESET_BASE_LSB);
+}
+
+static inline void sm_config_set_sideset(pio_sm_config *c, uint bit_count, bool optional,
+					 bool pindirs)
+{
+	if (bad_params_if(NULL, bit_count > 5 ||
+				    (optional && (bit_count == 0))))
+		return;
+	c->pinctrl = (c->pinctrl & ~PROC_PIO_SM0_PINCTRL_SIDESET_COUNT_BITS) |
+			(bit_count << PROC_PIO_SM0_PINCTRL_SIDESET_COUNT_LSB);
+
+	c->execctrl = (c->execctrl & ~(PROC_PIO_SM0_EXECCTRL_SIDE_EN_BITS |
+				       PROC_PIO_SM0_EXECCTRL_SIDE_PINDIR_BITS)) |
+			(optional << PROC_PIO_SM0_EXECCTRL_SIDE_EN_LSB) |
+			(pindirs << PROC_PIO_SM0_EXECCTRL_SIDE_PINDIR_LSB);
+}
+
+static inline void sm_config_set_clkdiv_int_frac(pio_sm_config *c, uint16_t div_int,
+						 uint8_t div_frac)
+{
+	if (bad_params_if(NULL, div_int == 0 && div_frac != 0))
+		return;
+
+	c->clkdiv =
+		(((uint)div_frac) << PROC_PIO_SM0_CLKDIV_FRAC_LSB) |
+		(((uint)div_int) << PROC_PIO_SM0_CLKDIV_INT_LSB);
+}
+
+static inline void sm_config_set_clkdiv(pio_sm_config *c, struct fp24_8 div)
+{
+	uint16_t div_int;
+	uint8_t div_frac;
+
+	pio_calculate_clkdiv_from_fp24_8(div, &div_int, &div_frac);
+	sm_config_set_clkdiv_int_frac(c, div_int, div_frac);
+}
+
+static inline void sm_config_set_wrap(pio_sm_config *c, uint wrap_target, uint wrap)
+{
+	if (bad_params_if(NULL, wrap >= PIO_INSTRUCTION_COUNT ||
+				    wrap_target >= PIO_INSTRUCTION_COUNT))
+		return;
+
+	c->execctrl = (c->execctrl & ~(PROC_PIO_SM0_EXECCTRL_WRAP_TOP_BITS |
+				       PROC_PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS)) |
+			(wrap_target << PROC_PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB) |
+			(wrap << PROC_PIO_SM0_EXECCTRL_WRAP_TOP_LSB);
+}
+
+static inline void sm_config_set_jmp_pin(pio_sm_config *c, uint pin)
+{
+	if (bad_params_if(NULL, pin >= RP1_PIO_GPIO_COUNT))
+		return;
+
+	c->execctrl = (c->execctrl & ~PROC_PIO_SM0_EXECCTRL_JMP_PIN_BITS) |
+		(pin << PROC_PIO_SM0_EXECCTRL_JMP_PIN_LSB);
+}
+
+static inline void sm_config_set_in_shift(pio_sm_config *c, bool shift_right, bool autopush,
+					  uint push_threshold)
+{
+	if (bad_params_if(NULL, push_threshold > 32))
+		return;
+
+	c->shiftctrl = (c->shiftctrl &
+		~(PROC_PIO_SM0_SHIFTCTRL_IN_SHIFTDIR_BITS |
+		PROC_PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS |
+		PROC_PIO_SM0_SHIFTCTRL_PUSH_THRESH_BITS)) |
+		(shift_right << PROC_PIO_SM0_SHIFTCTRL_IN_SHIFTDIR_LSB) |
+		(autopush << PROC_PIO_SM0_SHIFTCTRL_AUTOPUSH_LSB) |
+		((push_threshold & 0x1fu) << PROC_PIO_SM0_SHIFTCTRL_PUSH_THRESH_LSB);
+}
+
+static inline void sm_config_set_out_shift(pio_sm_config *c, bool shift_right, bool autopull,
+					   uint pull_threshold)
+{
+	if (bad_params_if(NULL, pull_threshold > 32))
+		return;
+
+	c->shiftctrl = (c->shiftctrl &
+		~(PROC_PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_BITS |
+		PROC_PIO_SM0_SHIFTCTRL_AUTOPULL_BITS |
+		PROC_PIO_SM0_SHIFTCTRL_PULL_THRESH_BITS)) |
+		(shift_right << PROC_PIO_SM0_SHIFTCTRL_OUT_SHIFTDIR_LSB) |
+		(autopull << PROC_PIO_SM0_SHIFTCTRL_AUTOPULL_LSB) |
+		((pull_threshold & 0x1fu) << PROC_PIO_SM0_SHIFTCTRL_PULL_THRESH_LSB);
+}
+
+static inline void sm_config_set_fifo_join(pio_sm_config *c, enum pio_fifo_join join)
+{
+	if (bad_params_if(NULL, join != PIO_FIFO_JOIN_NONE &&
+				    join != PIO_FIFO_JOIN_TX &&
+				    join != PIO_FIFO_JOIN_RX))
+		return;
+
+	c->shiftctrl = (c->shiftctrl & (uint)~(PROC_PIO_SM0_SHIFTCTRL_FJOIN_TX_BITS |
+					       PROC_PIO_SM0_SHIFTCTRL_FJOIN_RX_BITS)) |
+		(((uint)join) << PROC_PIO_SM0_SHIFTCTRL_FJOIN_TX_LSB);
+}
+
+static inline void sm_config_set_out_special(pio_sm_config *c, bool sticky, bool has_enable_pin,
+					     uint enable_pin_index)
+{
+	c->execctrl = (c->execctrl &
+		(uint)~(PROC_PIO_SM0_EXECCTRL_OUT_STICKY_BITS |
+			PROC_PIO_SM0_EXECCTRL_INLINE_OUT_EN_BITS |
+		PROC_PIO_SM0_EXECCTRL_OUT_EN_SEL_BITS)) |
+		(sticky << PROC_PIO_SM0_EXECCTRL_OUT_STICKY_LSB) |
+		(has_enable_pin << PROC_PIO_SM0_EXECCTRL_INLINE_OUT_EN_LSB) |
+		((enable_pin_index << PROC_PIO_SM0_EXECCTRL_OUT_EN_SEL_LSB) &
+		 PROC_PIO_SM0_EXECCTRL_OUT_EN_SEL_BITS);
+}
+
+static inline void sm_config_set_mov_status(pio_sm_config *c, enum pio_mov_status_type status_sel,
+					    uint status_n)
+{
+	if (bad_params_if(NULL, status_sel != STATUS_TX_LESSTHAN &&
+				status_sel != STATUS_RX_LESSTHAN))
+		return;
+
+	c->execctrl = (c->execctrl
+		& ~(PROC_PIO_SM0_EXECCTRL_STATUS_SEL_BITS | PROC_PIO_SM0_EXECCTRL_STATUS_N_BITS))
+		| ((((uint)status_sel) << PROC_PIO_SM0_EXECCTRL_STATUS_SEL_LSB) &
+		   PROC_PIO_SM0_EXECCTRL_STATUS_SEL_BITS)
+		| ((status_n << PROC_PIO_SM0_EXECCTRL_STATUS_N_LSB) &
+		   PROC_PIO_SM0_EXECCTRL_STATUS_N_BITS);
+}
+
+static inline pio_sm_config pio_get_default_sm_config(void)
+{
+	pio_sm_config c = { 0 };
+
+	sm_config_set_clkdiv_int_frac(&c, 1, 0);
+	sm_config_set_wrap(&c, 0, 31);
+	sm_config_set_in_shift(&c, true, false, 32);
+	sm_config_set_out_shift(&c, true, false, 32);
+	return c;
+}
+
+static inline uint32_t clock_get_hz(enum clock_index clk_index)
+{
+	const uint32_t MHZ = 1000000;
+
+	if (bad_params_if(NULL, clk_index != clk_sys))
+		return 0;
+	return 200 * MHZ;
+}
+
+static inline int pio_gpio_set_function(struct rp1_pio_client *client, uint gpio,
+					enum gpio_function fn)
+{
+	struct rp1_gpio_set_function_args args = { .gpio = gpio, .fn = fn };
+
+	if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT))
+		return -EINVAL;
+	return rp1_pio_gpio_set_function(client, &args);
+}
+
+static inline int pio_gpio_init(struct rp1_pio_client *client, uint gpio)
+{
+	struct rp1_gpio_init_args args = { .gpio = gpio };
+	int ret;
+
+	if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT))
+		return -EINVAL;
+	ret = rp1_pio_gpio_init(client, &args);
+	if (ret)
+		return ret;
+	return pio_gpio_set_function(client, gpio, RP1_GPIO_FUNC_PIO);
+}
+
+static inline int pio_gpio_set_pulls(struct rp1_pio_client *client, uint gpio, bool up, bool down)
+{
+	struct rp1_gpio_set_pulls_args args = { .gpio = gpio, .up = up, .down = down };
+
+	if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT))
+		return -EINVAL;
+	return rp1_pio_gpio_set_pulls(client, &args);
+}
+
+static inline int pio_gpio_set_outover(struct rp1_pio_client *client, uint gpio, uint value)
+{
+	struct rp1_gpio_set_args args = { .gpio = gpio, .value = value };
+
+	if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT))
+		return -EINVAL;
+	return rp1_pio_gpio_set_outover(client, &args);
+}
+
+static inline int pio_gpio_set_inover(struct rp1_pio_client *client, uint gpio, uint value)
+{
+	struct rp1_gpio_set_args args = { .gpio = gpio, .value = value };
+
+	if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT))
+		return -EINVAL;
+	return rp1_pio_gpio_set_inover(client, &args);
+}
+
+static inline int pio_gpio_set_oeover(struct rp1_pio_client *client, uint gpio, uint value)
+{
+	struct rp1_gpio_set_args args = { .gpio = gpio, .value = value };
+
+	if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT))
+		return -EINVAL;
+	return rp1_pio_gpio_set_oeover(client, &args);
+}
+
+static inline int pio_gpio_set_input_enabled(struct rp1_pio_client *client, uint gpio,
+					     bool enabled)
+{
+	struct rp1_gpio_set_args args = { .gpio = gpio, .value = enabled };
+
+	if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT))
+		return -EINVAL;
+	return rp1_pio_gpio_set_input_enabled(client, &args);
+}
+
+static inline int pio_gpio_set_drive_strength(struct rp1_pio_client *client, uint gpio,
+					      enum gpio_drive_strength drive)
+{
+	struct rp1_gpio_set_args args = { .gpio = gpio, .value = drive };
+
+	if (bad_params_if(client, gpio >= RP1_PIO_GPIO_COUNT))
+		return -EINVAL;
+	return rp1_pio_gpio_set_drive_strength(client, &args);
+}
+
+static inline int pio_gpio_pull_up(struct rp1_pio_client *client, uint gpio)
+{
+	return pio_gpio_set_pulls(client, gpio, true, false);
+}
+
+static inline int pio_gpio_pull_down(struct rp1_pio_client *client, uint gpio)
+{
+	return pio_gpio_set_pulls(client, gpio, false, true);
+}
+
+static inline int pio_gpio_disable_pulls(struct rp1_pio_client *client, uint gpio)
+{
+	return pio_gpio_set_pulls(client, gpio, false, false);
+}
+
+#endif
diff --git a/include/linux/platform_data/dma-bcm2708.h b/include/linux/platform_data/dma-bcm2708.h
new file mode 100644
index 000000000000..6ca874d332a8
--- /dev/null
+++ b/include/linux/platform_data/dma-bcm2708.h
@@ -0,0 +1,143 @@
+/*
+ *  Copyright (C) 2010 Broadcom
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _PLAT_BCM2708_DMA_H
+#define _PLAT_BCM2708_DMA_H
+
+/* DMA CS Control and Status bits */
+#define BCM2708_DMA_ACTIVE	BIT(0)
+#define BCM2708_DMA_INT		BIT(2)
+#define BCM2708_DMA_ISPAUSED	BIT(4)  /* Pause requested or not active */
+#define BCM2708_DMA_ISHELD	BIT(5)  /* Is held by DREQ flow control */
+#define BCM2708_DMA_ERR		BIT(8)
+#define BCM2708_DMA_ABORT	BIT(30) /* stop current CB, go to next, WO */
+#define BCM2708_DMA_RESET	BIT(31) /* WO, self clearing */
+
+/* DMA control block "info" field bits */
+#define BCM2708_DMA_INT_EN	BIT(0)
+#define BCM2708_DMA_TDMODE	BIT(1)
+#define BCM2708_DMA_WAIT_RESP	BIT(3)
+#define BCM2708_DMA_D_INC	BIT(4)
+#define BCM2708_DMA_D_WIDTH	BIT(5)
+#define BCM2708_DMA_D_DREQ	BIT(6)
+#define BCM2708_DMA_S_INC	BIT(8)
+#define BCM2708_DMA_S_WIDTH	BIT(9)
+#define BCM2708_DMA_S_DREQ	BIT(10)
+
+#define	BCM2708_DMA_BURST(x)	(((x) & 0xf) << 12)
+#define	BCM2708_DMA_PER_MAP(x)	((x) << 16)
+#define	BCM2708_DMA_WAITS(x)	(((x) & 0x1f) << 21)
+
+#define BCM2708_DMA_DREQ_EMMC	11
+#define BCM2708_DMA_DREQ_SDHOST	13
+
+#define BCM2708_DMA_CS		0x00 /* Control and Status */
+#define BCM2708_DMA_ADDR	0x04
+/* the current control block appears in the following registers - read only */
+#define BCM2708_DMA_INFO	0x08
+#define BCM2708_DMA_SOURCE_AD	0x0c
+#define BCM2708_DMA_DEST_AD	0x10
+#define BCM2708_DMA_NEXTCB	0x1C
+#define BCM2708_DMA_DEBUG	0x20
+
+#define BCM2708_DMA4_CS		(BCM2708_DMA_CHAN(4) + BCM2708_DMA_CS)
+#define BCM2708_DMA4_ADDR	(BCM2708_DMA_CHAN(4) + BCM2708_DMA_ADDR)
+
+#define BCM2708_DMA_TDMODE_LEN(w, h) ((h) << 16 | (w))
+
+/* When listing features we can ask for when allocating DMA channels give
+   those with higher priority smaller ordinal numbers */
+#define BCM_DMA_FEATURE_FAST_ORD	0
+#define BCM_DMA_FEATURE_BULK_ORD	1
+#define BCM_DMA_FEATURE_NORMAL_ORD	2
+#define BCM_DMA_FEATURE_LITE_ORD	3
+#define BCM_DMA_FEATURE_FAST		BIT(BCM_DMA_FEATURE_FAST_ORD)
+#define BCM_DMA_FEATURE_BULK		BIT(BCM_DMA_FEATURE_BULK_ORD)
+#define BCM_DMA_FEATURE_NORMAL		BIT(BCM_DMA_FEATURE_NORMAL_ORD)
+#define BCM_DMA_FEATURE_LITE		BIT(BCM_DMA_FEATURE_LITE_ORD)
+#define BCM_DMA_FEATURE_COUNT		4
+
+struct bcm2708_dma_cb {
+	u32 info;
+	u32 src;
+	u32 dst;
+	u32 length;
+	u32 stride;
+	u32 next;
+	u32 pad[2];
+};
+
+struct scatterlist;
+struct platform_device;
+
+#if defined(CONFIG_DMA_BCM2708) || defined(CONFIG_DMA_BCM2708_MODULE)
+
+int bcm_sg_suitable_for_dma(struct scatterlist *sg_ptr, int sg_len);
+void bcm_dma_start(void __iomem *dma_chan_base, dma_addr_t control_block);
+void bcm_dma_wait_idle(void __iomem *dma_chan_base);
+bool bcm_dma_is_busy(void __iomem *dma_chan_base);
+int bcm_dma_abort(void __iomem *dma_chan_base);
+
+/* return channel no or -ve error */
+int bcm_dma_chan_alloc(unsigned preferred_feature_set,
+		       void __iomem **out_dma_base, int *out_dma_irq);
+int bcm_dma_chan_free(int channel);
+
+int bcm_dmaman_probe(struct platform_device *pdev, void __iomem *base,
+		     u32 chans_available);
+int bcm_dmaman_remove(struct platform_device *pdev);
+
+#else /* CONFIG_DMA_BCM2708 */
+
+static inline int bcm_sg_suitable_for_dma(struct scatterlist *sg_ptr,
+					  int sg_len)
+{
+	return 0;
+}
+
+static inline void bcm_dma_start(void __iomem *dma_chan_base,
+				 dma_addr_t control_block) { }
+
+static inline void bcm_dma_wait_idle(void __iomem *dma_chan_base) { }
+
+static inline bool bcm_dma_is_busy(void __iomem *dma_chan_base)
+{
+	return false;
+}
+
+static inline int bcm_dma_abort(void __iomem *dma_chan_base)
+{
+	return -EINVAL;
+}
+
+static inline int bcm_dma_chan_alloc(unsigned preferred_feature_set,
+				     void __iomem **out_dma_base,
+				     int *out_dma_irq)
+{
+	return -EINVAL;
+}
+
+static inline int bcm_dma_chan_free(int channel)
+{
+	return -EINVAL;
+}
+
+static inline int bcm_dmaman_probe(struct platform_device *pdev,
+				   void __iomem *base, u32 chans_available)
+{
+	return 0;
+}
+
+static inline int bcm_dmaman_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+#endif /* CONFIG_DMA_BCM2708 || CONFIG_DMA_BCM2708_MODULE */
+
+#endif /* _PLAT_BCM2708_DMA_H */
diff --git a/include/linux/platform_data/simplefb.h b/include/linux/platform_data/simplefb.h
index 4f94d52ac99f..63ba837138f9 100644
--- a/include/linux/platform_data/simplefb.h
+++ b/include/linux/platform_data/simplefb.h
@@ -16,10 +16,12 @@
 #define SIMPLEFB_FORMATS \
 { \
 	{ "r5g6b5", 16, {11, 5}, {5, 6}, {0, 5}, {0, 0}, DRM_FORMAT_RGB565 }, \
+	{ "b5g6r5", 16, {0, 5}, {5, 6}, {11, 5}, {0, 0}, DRM_FORMAT_BGR565 }, \
 	{ "r5g5b5a1", 16, {11, 5}, {6, 5}, {1, 5}, {0, 1}, DRM_FORMAT_RGBA5551 }, \
 	{ "x1r5g5b5", 16, {10, 5}, {5, 5}, {0, 5}, {0, 0}, DRM_FORMAT_XRGB1555 }, \
 	{ "a1r5g5b5", 16, {10, 5}, {5, 5}, {0, 5}, {15, 1}, DRM_FORMAT_ARGB1555 }, \
 	{ "r8g8b8", 24, {16, 8}, {8, 8}, {0, 8}, {0, 0}, DRM_FORMAT_RGB888 }, \
+	{ "b8g8r8", 24, {0, 8}, {8, 8}, {16, 8}, {0, 0}, DRM_FORMAT_BGR888 }, \
 	{ "x8r8g8b8", 32, {16, 8}, {8, 8}, {0, 8}, {0, 0}, DRM_FORMAT_XRGB8888 }, \
 	{ "a8r8g8b8", 32, {16, 8}, {8, 8}, {0, 8}, {24, 8}, DRM_FORMAT_ARGB8888 }, \
 	{ "x8b8g8r8", 32, {0, 8}, {8, 8}, {16, 8}, {0, 0}, DRM_FORMAT_XBGR8888 }, \
diff --git a/include/linux/rp1-firmware.h b/include/linux/rp1-firmware.h
new file mode 100644
index 000000000000..19f11d6d79b1
--- /dev/null
+++ b/include/linux/rp1-firmware.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ *  Copyright (C) 2023 2023-2024 Raspberry Pi Ltd.
+ */
+
+#ifndef __SOC_RP1_FIRMWARE_H__
+#define __SOC_RP1_FIRMWARE_H__
+
+#include <linux/types.h>
+#include <linux/of_device.h>
+
+#define RP1_FOURCC(s) ((uint32_t)((s[0] << 24) | (s[1] << 16) | (s[2] << 8) | (s[3] << 0)))
+
+struct rp1_firmware;
+
+#if IS_ENABLED(CONFIG_FIRMWARE_RP1)
+int rp1_firmware_message(struct rp1_firmware *fw, uint16_t op,
+			 const void *data, unsigned int data_len,
+			 void *resp, unsigned int resp_space);
+void rp1_firmware_put(struct rp1_firmware *fw);
+struct rp1_firmware *rp1_firmware_get(struct device_node *fwnode);
+struct rp1_firmware *devm_rp1_firmware_get(struct device *dev, struct device_node *fwnode);
+int rp1_firmware_get_feature(struct rp1_firmware *fw, uint32_t fourcc,
+			     uint32_t *op_base, uint32_t *op_count);
+#else
+static inline int rp1_firmware_message(struct rp1_firmware *fw, uint16_t op,
+				       const void *data, unsigned int data_len,
+				       void *resp, unsigned int resp_space)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline void rp1_firmware_put(struct rp1_firmware *fw) { }
+
+static inline struct rp1_firmware *rp1_firmware_get(struct device_node *fwnode)
+{
+	return NULL;
+}
+
+static inline struct rp1_firmware *devm_rp1_firmware_get(struct device *dev,
+							 struct device_node *fwnode)
+{
+	return NULL;
+}
+
+static inline int rp1_firmware_get_feature(struct rp1_firmware *fw, uint32_t fourcc,
+					   uint32_t *op_base, uint32_t *op_count)
+{
+	return -EOPNOTSUPP;
+}
+#endif
+
+#endif /* __SOC_RP1_FIRMWARE_H__ */
diff --git a/include/linux/rp1_platform.h b/include/linux/rp1_platform.h
new file mode 100644
index 000000000000..f805dbe1ed9b
--- /dev/null
+++ b/include/linux/rp1_platform.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2021-2022 Raspberry Pi Ltd.
+ * All rights reserved.
+ */
+
+#ifndef _RP1_PLATFORM_H
+#define _RP1_PLATFORM_H
+
+#include <vdso/bits.h>
+
+#define RP1_B0_CHIP_ID 0x10001927
+#define RP1_C0_CHIP_ID 0x20001927
+
+#define RP1_PLATFORM_ASIC BIT(1)
+#define RP1_PLATFORM_FPGA BIT(0)
+
+void rp1_get_platform(u32 *chip_id, u32 *platform);
+
+#endif
diff --git a/include/linux/usb.h b/include/linux/usb.h
index 2c0827cce489..ec74f946573d 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -1879,6 +1879,8 @@ extern int usb_clear_halt(struct usb_device *dev, int pipe);
 extern int usb_reset_configuration(struct usb_device *dev);
 extern int usb_set_interface(struct usb_device *dev, int ifnum, int alternate);
 extern void usb_reset_endpoint(struct usb_device *dev, unsigned int epaddr);
+extern void usb_fixup_endpoint(struct usb_device *dev, int epaddr,
+			       int interval);
 
 /* this request isn't really synchronous, but it belongs with the others */
 extern int usb_driver_set_configuration(struct usb_device *udev, int config);
diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h
index ac95e7c89df5..8fd38368cb31 100644
--- a/include/linux/usb/hcd.h
+++ b/include/linux/usb/hcd.h
@@ -372,6 +372,11 @@ struct hc_driver {
 		 * or bandwidth constraints.
 		 */
 	void	(*reset_bandwidth)(struct usb_hcd *, struct usb_device *);
+		/* Override the endpoint-derived interval
+		 * (if there is any cached hardware state).
+		 */
+	void	(*fixup_endpoint)(struct usb_hcd *hcd, struct usb_device *udev,
+				  struct usb_host_endpoint *ep, int interval);
 		/* Set the hardware-chosen device address */
 	int	(*address_device)(struct usb_hcd *, struct usb_device *udev,
 				  unsigned int timeout_ms);
@@ -437,6 +442,8 @@ extern void usb_hcd_unmap_urb_setup_for_dma(struct usb_hcd *, struct urb *);
 extern void usb_hcd_unmap_urb_for_dma(struct usb_hcd *, struct urb *);
 extern void usb_hcd_flush_endpoint(struct usb_device *udev,
 		struct usb_host_endpoint *ep);
+extern void usb_hcd_fixup_endpoint(struct usb_device *udev,
+		struct usb_host_endpoint *ep, int interval);
 extern void usb_hcd_disable_endpoint(struct usb_device *udev,
 		struct usb_host_endpoint *ep);
 extern void usb_hcd_reset_endpoint(struct usb_device *udev,
diff --git a/include/linux/w1.h b/include/linux/w1.h
index 064805bfae3f..6bcbafb6ea4f 100644
--- a/include/linux/w1.h
+++ b/include/linux/w1.h
@@ -122,6 +122,9 @@ typedef void (*w1_slave_found_callback)(struct w1_master *, u64);
  * @dev_id: Optional device id string, which w1 slaves could use for
  * creating names, which then give a connection to the w1 master
  *
+ * @delay_needs_poll: work around jitter introduced with GPIO controllers
+ * accessed over PCIe (RP1)
+ *
  * Note: read_bit and write_bit are very low level functions and should only
  * be used with hardware that doesn't really support 1-wire operations,
  * like a parallel/serial port.
@@ -156,6 +159,8 @@ struct w1_bus_master {
 		u8, w1_slave_found_callback);
 
 	char		*dev_id;
+
+	bool		delay_needs_poll;
 };
 
 /**
diff --git a/include/media/media-device.h b/include/media/media-device.h
index 53d2a16a70b0..749c327e3c58 100644
--- a/include/media/media-device.h
+++ b/include/media/media-device.h
@@ -11,6 +11,7 @@
 #ifndef _MEDIA_DEVICE_H
 #define _MEDIA_DEVICE_H
 
+#include <linux/atomic.h>
 #include <linux/list.h>
 #include <linux/mutex.h>
 #include <linux/pci.h>
@@ -106,6 +107,9 @@ struct media_device_ops {
  * @ops:	Operation handler callbacks
  * @req_queue_mutex: Serialise the MEDIA_REQUEST_IOC_QUEUE ioctl w.r.t.
  *		     other operations that stop or start streaming.
+ * @num_requests: number of associated requests
+ * @num_request_objects: number of associated request objects
+ * @media_dir:	DebugFS media directory
  * @request_id: Used to generate unique request IDs
  *
  * This structure represents an abstract high-level media device. It allows easy
@@ -179,6 +183,11 @@ struct media_device {
 	const struct media_device_ops *ops;
 
 	struct mutex req_queue_mutex;
+	atomic_t num_requests;
+	atomic_t num_request_objects;
+
+	/* debugfs */
+	struct dentry *media_dir;
 	atomic_t request_id;
 };
 
diff --git a/include/media/media-devnode.h b/include/media/media-devnode.h
index d27c1c646c28..dbcabeffcb57 100644
--- a/include/media/media-devnode.h
+++ b/include/media/media-devnode.h
@@ -20,9 +20,13 @@
 #include <linux/fs.h>
 #include <linux/device.h>
 #include <linux/cdev.h>
+#include <linux/debugfs.h>
 
 struct media_device;
 
+/* debugfs top-level media directory */
+extern struct dentry *media_debugfs_root;
+
 /*
  * Flag to mark the media_devnode struct as registered. Drivers must not touch
  * this flag directly, it will be set and cleared by media_devnode_register and
diff --git a/include/media/media-request.h b/include/media/media-request.h
index 3cd25a2717ce..db0d9edfb38f 100644
--- a/include/media/media-request.h
+++ b/include/media/media-request.h
@@ -56,6 +56,10 @@ struct media_request_object;
  * @access_count: count the number of request accesses that are in progress
  * @objects: List of @struct media_request_object request objects
  * @num_incomplete_objects: The number of incomplete objects in the request
+ * @manual_completion: if true, then the request won't be marked as completed
+ * when @num_incomplete_objects reaches 0. Call media_request_manual_complete()
+ * to set this field to false and complete the request
+ * if @num_incomplete_objects == 0.
  * @poll_wait: Wait queue for poll
  * @lock: Serializes access to this struct
  */
@@ -68,6 +72,7 @@ struct media_request {
 	unsigned int access_count;
 	struct list_head objects;
 	unsigned int num_incomplete_objects;
+	bool manual_completion;
 	wait_queue_head_t poll_wait;
 	spinlock_t lock;
 };
@@ -218,6 +223,35 @@ media_request_get_by_fd(struct media_device *mdev, int request_fd);
 int media_request_alloc(struct media_device *mdev,
 			int *alloc_fd);
 
+/**
+ * media_request_mark_manual_completion - Set manual_completion to true
+ *
+ * @req: The request
+ *
+ * Mark that the request has to be manually completed by calling
+ * media_request_manual_complete().
+ *
+ * This function should be called in the req_queue callback.
+ */
+static inline void
+media_request_mark_manual_completion(struct media_request *req)
+{
+	req->manual_completion = true;
+}
+
+/**
+ * media_request_manual_complete - Set manual_completion to false
+ *
+ * @req: The request
+ *
+ * Set @manual_completion to false, and if @num_incomplete_objects
+ * is 0, then mark the request as completed.
+ *
+ * If there are still incomplete objects in the request, then
+ * WARN for that since that suggests a driver error.
+ */
+void media_request_manual_complete(struct media_request *req);
+
 #else
 
 static inline void media_request_get(struct media_request *req)
@@ -256,6 +290,7 @@ struct media_request_object_ops {
  * struct media_request_object - An opaque object that belongs to a media
  *				 request
  *
+ * @mdev: Media device this object belongs to
  * @ops: object's operations
  * @priv: object's priv pointer
  * @req: the request this object belongs to (can be NULL)
@@ -267,6 +302,7 @@ struct media_request_object_ops {
  * another struct that contains the actual data for this request object.
  */
 struct media_request_object {
+	struct media_device *mdev;
 	const struct media_request_object_ops *ops;
 	void *priv;
 	struct media_request *req;
@@ -336,7 +372,7 @@ void media_request_object_init(struct media_request_object *obj);
  * @req: The media request
  * @ops: The object ops for this object
  * @priv: A driver-specific priv pointer associated with this object
- * @is_buffer: Set to true if the object a buffer object.
+ * @is_buffer: Set to true if the object is a buffer object.
  * @obj: The object
  *
  * Bind this object to the request and set the ops and priv values of
diff --git a/include/media/videobuf2-core.h b/include/media/videobuf2-core.h
index 9b02aeba4108..c75b4f14581a 100644
--- a/include/media/videobuf2-core.h
+++ b/include/media/videobuf2-core.h
@@ -967,6 +967,21 @@ int vb2_core_streamon(struct vb2_queue *q, unsigned int type);
  */
 int vb2_core_streamoff(struct vb2_queue *q, unsigned int type);
 
+/**
+ * vb2_core_expbuf_dmabuf() - Export a buffer as a dma_buf structure
+ * @q:         videobuf2 queue
+ * @type:      buffer type
+ * @index:     id number of the buffer
+ * @plane:     index of the plane to be exported, 0 for single plane queues
+ * @flags:     flags for newly created file, currently only O_CLOEXEC is
+ *             supported, refer to manual of open syscall for more details
+ * @dmabuf:    Returns the dmabuf pointer
+ *
+ */
+int vb2_core_expbuf_dmabuf(struct vb2_queue *q, unsigned int type,
+			   struct vb2_buffer *vb, unsigned int plane,
+			   unsigned int flags, struct dma_buf **dmabuf);
+
 /**
  * vb2_core_expbuf() - Export a buffer as a file descriptor.
  * @q:		pointer to &struct vb2_queue with videobuf2 queue.
diff --git a/include/soc/bcm2835/raspberrypi-firmware.h b/include/soc/bcm2835/raspberrypi-firmware.h
index 73cac8d0287e..3e71d45477a4 100644
--- a/include/soc/bcm2835/raspberrypi-firmware.h
+++ b/include/soc/bcm2835/raspberrypi-firmware.h
@@ -36,6 +36,8 @@ struct rpi_firmware_property_tag_header {
 enum rpi_firmware_property_tag {
 	RPI_FIRMWARE_PROPERTY_END =                           0,
 	RPI_FIRMWARE_GET_FIRMWARE_REVISION =                  0x00000001,
+	RPI_FIRMWARE_GET_FIRMWARE_VARIANT =                   0x00000002,
+	RPI_FIRMWARE_GET_FIRMWARE_HASH =                      0x00000003,
 
 	RPI_FIRMWARE_SET_CURSOR_INFO =                        0x00008010,
 	RPI_FIRMWARE_SET_CURSOR_STATE =                       0x00008011,
@@ -71,6 +73,7 @@ enum rpi_firmware_property_tag {
 	RPI_FIRMWARE_GET_DISPMANX_RESOURCE_MEM_HANDLE =       0x00030014,
 	RPI_FIRMWARE_GET_EDID_BLOCK =                         0x00030020,
 	RPI_FIRMWARE_GET_CUSTOMER_OTP =                       0x00030021,
+	RPI_FIRMWARE_GET_EDID_BLOCK_DISPLAY =                 0x00030023,
 	RPI_FIRMWARE_GET_DOMAIN_STATE =                       0x00030030,
 	RPI_FIRMWARE_GET_THROTTLED =                          0x00030046,
 	RPI_FIRMWARE_GET_CLOCK_MEASURED =                     0x00030047,
@@ -89,9 +92,14 @@ enum rpi_firmware_property_tag {
 	RPI_FIRMWARE_GET_PERIPH_REG =                         0x00030045,
 	RPI_FIRMWARE_SET_PERIPH_REG =                         0x00038045,
 	RPI_FIRMWARE_GET_POE_HAT_VAL =                        0x00030049,
-	RPI_FIRMWARE_SET_POE_HAT_VAL =                        0x00030050,
+	RPI_FIRMWARE_SET_POE_HAT_VAL =                        0x00038049,
+	RPI_FIRMWARE_SET_POE_HAT_VAL_OLD =                    0x00030050,
 	RPI_FIRMWARE_NOTIFY_XHCI_RESET =                      0x00030058,
+	RPI_FIRMWARE_GET_REBOOT_FLAGS =                       0x00030064,
+	RPI_FIRMWARE_SET_REBOOT_FLAGS =                       0x00038064,
 	RPI_FIRMWARE_NOTIFY_DISPLAY_DONE =                    0x00030066,
+	RPI_FIRMWARE_GET_SW_UART =                            0x0003008a,
+	RPI_FIRMWARE_SET_SW_UART =                            0x0003808a,
 
 	/* Dispmanx TAGS */
 	RPI_FIRMWARE_FRAMEBUFFER_ALLOCATE =                   0x00040001,
@@ -105,9 +113,16 @@ enum rpi_firmware_property_tag {
 	RPI_FIRMWARE_FRAMEBUFFER_GET_VIRTUAL_OFFSET =         0x00040009,
 	RPI_FIRMWARE_FRAMEBUFFER_GET_OVERSCAN =               0x0004000a,
 	RPI_FIRMWARE_FRAMEBUFFER_GET_PALETTE =                0x0004000b,
+	RPI_FIRMWARE_FRAMEBUFFER_GET_LAYER =                  0x0004000c,
+	RPI_FIRMWARE_FRAMEBUFFER_GET_TRANSFORM =              0x0004000d,
+	RPI_FIRMWARE_FRAMEBUFFER_GET_VSYNC =                  0x0004000e,
 	RPI_FIRMWARE_FRAMEBUFFER_GET_TOUCHBUF =               0x0004000f,
 	RPI_FIRMWARE_FRAMEBUFFER_GET_GPIOVIRTBUF =            0x00040010,
 	RPI_FIRMWARE_FRAMEBUFFER_RELEASE =                    0x00048001,
+	RPI_FIRMWARE_FRAMEBUFFER_GET_DISPLAY_ID =             0x00040016,
+	RPI_FIRMWARE_FRAMEBUFFER_SET_DISPLAY_NUM =            0x00048013,
+	RPI_FIRMWARE_FRAMEBUFFER_GET_NUM_DISPLAYS =           0x00040013,
+	RPI_FIRMWARE_FRAMEBUFFER_GET_DISPLAY_SETTINGS =       0x00040014,
 	RPI_FIRMWARE_FRAMEBUFFER_TEST_PHYSICAL_WIDTH_HEIGHT = 0x00044003,
 	RPI_FIRMWARE_FRAMEBUFFER_TEST_VIRTUAL_WIDTH_HEIGHT =  0x00044004,
 	RPI_FIRMWARE_FRAMEBUFFER_TEST_DEPTH =                 0x00044005,
@@ -116,22 +131,33 @@ enum rpi_firmware_property_tag {
 	RPI_FIRMWARE_FRAMEBUFFER_TEST_VIRTUAL_OFFSET =        0x00044009,
 	RPI_FIRMWARE_FRAMEBUFFER_TEST_OVERSCAN =              0x0004400a,
 	RPI_FIRMWARE_FRAMEBUFFER_TEST_PALETTE =               0x0004400b,
+	RPI_FIRMWARE_FRAMEBUFFER_TEST_LAYER =                 0x0004400c,
+	RPI_FIRMWARE_FRAMEBUFFER_TEST_TRANSFORM =             0x0004400d,
 	RPI_FIRMWARE_FRAMEBUFFER_TEST_VSYNC =                 0x0004400e,
 	RPI_FIRMWARE_FRAMEBUFFER_SET_PHYSICAL_WIDTH_HEIGHT =  0x00048003,
 	RPI_FIRMWARE_FRAMEBUFFER_SET_VIRTUAL_WIDTH_HEIGHT =   0x00048004,
 	RPI_FIRMWARE_FRAMEBUFFER_SET_DEPTH =                  0x00048005,
 	RPI_FIRMWARE_FRAMEBUFFER_SET_PIXEL_ORDER =            0x00048006,
 	RPI_FIRMWARE_FRAMEBUFFER_SET_ALPHA_MODE =             0x00048007,
+	RPI_FIRMWARE_FRAMEBUFFER_SET_PITCH =                  0x00048008,
 	RPI_FIRMWARE_FRAMEBUFFER_SET_VIRTUAL_OFFSET =         0x00048009,
 	RPI_FIRMWARE_FRAMEBUFFER_SET_OVERSCAN =               0x0004800a,
 	RPI_FIRMWARE_FRAMEBUFFER_SET_PALETTE =                0x0004800b,
+
 	RPI_FIRMWARE_FRAMEBUFFER_SET_TOUCHBUF =               0x0004801f,
 	RPI_FIRMWARE_FRAMEBUFFER_SET_GPIOVIRTBUF =            0x00048020,
 	RPI_FIRMWARE_FRAMEBUFFER_SET_VSYNC =                  0x0004800e,
+	RPI_FIRMWARE_FRAMEBUFFER_SET_LAYER =                  0x0004800c,
+	RPI_FIRMWARE_FRAMEBUFFER_SET_TRANSFORM =              0x0004800d,
 	RPI_FIRMWARE_FRAMEBUFFER_SET_BACKLIGHT =              0x0004800f,
 
 	RPI_FIRMWARE_VCHIQ_INIT =                             0x00048010,
 
+	RPI_FIRMWARE_SET_PLANE =                              0x00048015,
+	RPI_FIRMWARE_GET_DISPLAY_TIMING =                     0x00040017,
+	RPI_FIRMWARE_SET_TIMING =                             0x00048017,
+	RPI_FIRMWARE_GET_DISPLAY_CFG =                        0x00040018,
+	RPI_FIRMWARE_SET_DISPLAY_POWER =		      0x00048019,
 	RPI_FIRMWARE_GET_COMMAND_LINE =                       0x00050001,
 	RPI_FIRMWARE_GET_DMA_CHANNELS =                       0x00060001,
 };
@@ -152,9 +178,12 @@ enum rpi_firmware_clk_id {
 	RPI_FIRMWARE_M2MC_CLK_ID,
 	RPI_FIRMWARE_PIXEL_BVB_CLK_ID,
 	RPI_FIRMWARE_VEC_CLK_ID,
+	RPI_FIRMWARE_DISP_CLK_ID,
 	RPI_FIRMWARE_NUM_CLK_ID,
 };
 
+#define GET_DISPLAY_SETTINGS_PAYLOAD_SIZE 64
+
 /**
  * struct rpi_firmware_clk_rate_request - Firmware Request for a rate
  * @id:	ID of the clock being queried
diff --git a/include/uapi/drm/drm_fourcc.h b/include/uapi/drm/drm_fourcc.h
index 78abd819fd62..dffae5219148 100644
--- a/include/uapi/drm/drm_fourcc.h
+++ b/include/uapi/drm/drm_fourcc.h
@@ -377,6 +377,42 @@ extern "C" {
  */
 #define DRM_FORMAT_Q401		fourcc_code('Q', '4', '0', '1')
 
+/*
+ * 3 plane YCbCr LSB aligned
+ * In order to use these formats in a similar fashion to MSB aligned ones
+ * implementation can multiply the values by 2^6=64. For that reason the padding
+ * must only contain zeros.
+ * index 0 = Y plane, [15:0] z:Y [6:10] little endian
+ * index 1 = Cr plane, [15:0] z:Cr [6:10] little endian
+ * index 2 = Cb plane, [15:0] z:Cb [6:10] little endian
+ */
+#define DRM_FORMAT_S010	fourcc_code('S', '0', '1', '0') /* 2x2 subsampled Cb (1) and Cr (2) planes 10 bits per channel */
+#define DRM_FORMAT_S210	fourcc_code('S', '2', '1', '0') /* 2x1 subsampled Cb (1) and Cr (2) planes 10 bits per channel */
+#define DRM_FORMAT_S410	fourcc_code('S', '4', '1', '0') /* non-subsampled Cb (1) and Cr (2) planes 10 bits per channel */
+
+/*
+ * 3 plane YCbCr LSB aligned
+ * In order to use these formats in a similar fashion to MSB aligned ones
+ * implementation can multiply the values by 2^4=16. For that reason the padding
+ * must only contain zeros.
+ * index 0 = Y plane, [15:0] z:Y [4:12] little endian
+ * index 1 = Cr plane, [15:0] z:Cr [4:12] little endian
+ * index 2 = Cb plane, [15:0] z:Cb [4:12] little endian
+ */
+#define DRM_FORMAT_S012	fourcc_code('S', '0', '1', '2') /* 2x2 subsampled Cb (1) and Cr (2) planes 12 bits per channel */
+#define DRM_FORMAT_S212	fourcc_code('S', '2', '1', '2') /* 2x1 subsampled Cb (1) and Cr (2) planes 12 bits per channel */
+#define DRM_FORMAT_S412	fourcc_code('S', '4', '1', '2') /* non-subsampled Cb (1) and Cr (2) planes 12 bits per channel */
+
+/*
+ * 3 plane YCbCr
+ * index 0 = Y plane, [15:0] Y little endian
+ * index 1 = Cr plane, [15:0] Cr little endian
+ * index 2 = Cb plane, [15:0] Cb little endian
+ */
+#define DRM_FORMAT_S016	fourcc_code('S', '0', '1', '6') /* 2x2 subsampled Cb (1) and Cr (2) planes 16 bits per channel */
+#define DRM_FORMAT_S216	fourcc_code('S', '2', '1', '6') /* 2x1 subsampled Cb (1) and Cr (2) planes 16 bits per channel */
+#define DRM_FORMAT_S416	fourcc_code('S', '4', '1', '6') /* non-subsampled Cb (1) and Cr (2) planes 16 bits per channel */
+
 /*
  * 3 plane YCbCr
  * index 0: Y plane, [7:0] Y
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
index c082810c08a8..563e337d55a5 100644
--- a/include/uapi/drm/drm_mode.h
+++ b/include/uapi/drm/drm_mode.h
@@ -203,6 +203,7 @@ extern "C" {
  */
 #define DRM_MODE_REFLECT_X      (1<<4)
 #define DRM_MODE_REFLECT_Y      (1<<5)
+#define DRM_MODE_TRANSPOSE      (1<<6)
 
 /*
  * DRM_MODE_REFLECT_MASK
diff --git a/include/uapi/drm/v3d_drm.h b/include/uapi/drm/v3d_drm.h
index 87fc5bb0a61e..dbbc404d2b3d 100644
--- a/include/uapi/drm/v3d_drm.h
+++ b/include/uapi/drm/v3d_drm.h
@@ -43,6 +43,7 @@ extern "C" {
 #define DRM_V3D_PERFMON_GET_VALUES                0x0a
 #define DRM_V3D_SUBMIT_CPU                        0x0b
 #define DRM_V3D_PERFMON_GET_COUNTER               0x0c
+#define DRM_V3D_PERFMON_SET_GLOBAL                0x0d
 
 #define DRM_IOCTL_V3D_SUBMIT_CL           DRM_IOWR(DRM_COMMAND_BASE + DRM_V3D_SUBMIT_CL, struct drm_v3d_submit_cl)
 #define DRM_IOCTL_V3D_WAIT_BO             DRM_IOWR(DRM_COMMAND_BASE + DRM_V3D_WAIT_BO, struct drm_v3d_wait_bo)
@@ -61,6 +62,8 @@ extern "C" {
 #define DRM_IOCTL_V3D_SUBMIT_CPU          DRM_IOW(DRM_COMMAND_BASE + DRM_V3D_SUBMIT_CPU, struct drm_v3d_submit_cpu)
 #define DRM_IOCTL_V3D_PERFMON_GET_COUNTER DRM_IOWR(DRM_COMMAND_BASE + DRM_V3D_PERFMON_GET_COUNTER, \
 						   struct drm_v3d_perfmon_get_counter)
+#define DRM_IOCTL_V3D_PERFMON_SET_GLOBAL  DRM_IOW(DRM_COMMAND_BASE + DRM_V3D_PERFMON_SET_GLOBAL, \
+						   struct drm_v3d_perfmon_set_global)
 
 #define DRM_V3D_SUBMIT_CL_FLUSH_CACHE             0x01
 #define DRM_V3D_SUBMIT_EXTENSION		  0x02
@@ -290,6 +293,7 @@ enum drm_v3d_param {
 	DRM_V3D_PARAM_SUPPORTS_MULTISYNC_EXT,
 	DRM_V3D_PARAM_SUPPORTS_CPU_QUEUE,
 	DRM_V3D_PARAM_MAX_PERF_COUNTERS,
+	DRM_V3D_PARAM_SUPPORTS_SUPER_PAGES,
 };
 
 struct drm_v3d_get_param {
@@ -765,6 +769,21 @@ struct drm_v3d_perfmon_get_counter {
 	__u8 reserved[7];
 };
 
+#define DRM_V3D_PERFMON_CLEAR_GLOBAL    0x0001
+
+/**
+ * struct drm_v3d_perfmon_set_global - ioctl to define a global performance
+ * monitor
+ *
+ * The global performance monitor will be used for all jobs. If a global
+ * performance monitor is defined, jobs with a self-defined performance
+ * monitor won't be allowed.
+ */
+struct drm_v3d_perfmon_set_global {
+	__u32 flags;
+	__u32 id;
+};
+
 #if defined(__cplusplus)
 }
 #endif
diff --git a/include/uapi/linux/bcm2835-isp.h b/include/uapi/linux/bcm2835-isp.h
new file mode 100644
index 000000000000..c50e3ca81565
--- /dev/null
+++ b/include/uapi/linux/bcm2835-isp.h
@@ -0,0 +1,347 @@
+/* SPDX-License-Identifier: ((GPL-2.0+ WITH Linux-syscall-note) OR BSD-3-Clause) */
+/*
+ * bcm2835-isp.h
+ *
+ * BCM2835 ISP driver - user space header file.
+ *
+ * Copyright © 2019-2020 Raspberry Pi (Trading) Ltd.
+ *
+ * Author: Naushir Patuck (naush@raspberrypi.com)
+ *
+ */
+
+#ifndef __BCM2835_ISP_H_
+#define __BCM2835_ISP_H_
+
+#include <linux/v4l2-controls.h>
+
+#define V4L2_CID_USER_BCM2835_ISP_CC_MATRIX	\
+				(V4L2_CID_USER_BCM2835_ISP_BASE + 0x0001)
+#define V4L2_CID_USER_BCM2835_ISP_LENS_SHADING	\
+				(V4L2_CID_USER_BCM2835_ISP_BASE + 0x0002)
+#define V4L2_CID_USER_BCM2835_ISP_BLACK_LEVEL	\
+				(V4L2_CID_USER_BCM2835_ISP_BASE + 0x0003)
+#define V4L2_CID_USER_BCM2835_ISP_GEQ		\
+				(V4L2_CID_USER_BCM2835_ISP_BASE + 0x0004)
+#define V4L2_CID_USER_BCM2835_ISP_GAMMA		\
+				(V4L2_CID_USER_BCM2835_ISP_BASE + 0x0005)
+#define V4L2_CID_USER_BCM2835_ISP_DENOISE	\
+				(V4L2_CID_USER_BCM2835_ISP_BASE + 0x0006)
+#define V4L2_CID_USER_BCM2835_ISP_SHARPEN	\
+				(V4L2_CID_USER_BCM2835_ISP_BASE + 0x0007)
+#define V4L2_CID_USER_BCM2835_ISP_DPC		\
+				(V4L2_CID_USER_BCM2835_ISP_BASE + 0x0008)
+#define V4L2_CID_USER_BCM2835_ISP_CDN \
+				(V4L2_CID_USER_BCM2835_ISP_BASE + 0x0009)
+
+/*
+ * All structs below are directly mapped onto the equivalent structs in
+ * drivers/staging/vc04_services/vchiq-mmal/mmal-parameters.h
+ * for convenience.
+ */
+
+/**
+ * struct bcm2835_isp_rational - Rational value type.
+ *
+ * @num:	Numerator.
+ * @den:	Denominator.
+ */
+struct bcm2835_isp_rational {
+	__s32 num;
+	__u32 den;
+};
+
+/**
+ * struct bcm2835_isp_ccm - Colour correction matrix.
+ *
+ * @ccm:	3x3 correction matrix coefficients.
+ * @offsets:	1x3 correction offsets.
+ */
+struct bcm2835_isp_ccm {
+	struct bcm2835_isp_rational ccm[3][3];
+	__s32 offsets[3];
+};
+
+/**
+ * struct bcm2835_isp_custom_ccm - Custom CCM applied with the
+ *				   V4L2_CID_USER_BCM2835_ISP_CC_MATRIX ctrl.
+ *
+ * @enabled:	Enable custom CCM.
+ * @ccm:	Custom CCM coefficients and offsets.
+ */
+struct bcm2835_isp_custom_ccm {
+	__u32 enabled;
+	struct bcm2835_isp_ccm ccm;
+};
+
+/**
+ * enum bcm2835_isp_gain_format - format of the gains in the lens shading
+ *				  tables used with the
+ *				  V4L2_CID_USER_BCM2835_ISP_LENS_SHADING ctrl.
+ *
+ * @GAIN_FORMAT_U0P8_1:		Gains are u0.8 format, starting at 1.0
+ * @GAIN_FORMAT_U1P7_0:		Gains are u1.7 format, starting at 0.0
+ * @GAIN_FORMAT_U1P7_1:		Gains are u1.7 format, starting at 1.0
+ * @GAIN_FORMAT_U2P6_0:		Gains are u2.6 format, starting at 0.0
+ * @GAIN_FORMAT_U2P6_1:		Gains are u2.6 format, starting at 1.0
+ * @GAIN_FORMAT_U3P5_0:		Gains are u3.5 format, starting at 0.0
+ * @GAIN_FORMAT_U3P5_1:		Gains are u3.5 format, starting at 1.0
+ * @GAIN_FORMAT_U4P10:		Gains are u4.10 format, starting at 0.0
+ */
+enum bcm2835_isp_gain_format {
+	GAIN_FORMAT_U0P8_1 = 0,
+	GAIN_FORMAT_U1P7_0 = 1,
+	GAIN_FORMAT_U1P7_1 = 2,
+	GAIN_FORMAT_U2P6_0 = 3,
+	GAIN_FORMAT_U2P6_1 = 4,
+	GAIN_FORMAT_U3P5_0 = 5,
+	GAIN_FORMAT_U3P5_1 = 6,
+	GAIN_FORMAT_U4P10  = 7,
+};
+
+/**
+ * struct bcm2835_isp_lens_shading - Lens shading tables supplied with the
+ *				     V4L2_CID_USER_BCM2835_ISP_LENS_SHADING
+ *				     ctrl.
+ *
+ * @enabled:		Enable lens shading.
+ * @grid_cell_size:	Size of grid cells in samples (16, 32, 64, 128 or 256).
+ * @grid_width:		Width of lens shading tables in grid cells.
+ * @grid_stride:	Row to row distance (in grid cells) between grid cells
+ *			in the same horizontal location.
+ * @grid_height:	Height of lens shading tables in grid cells.
+ * @dmabuf:		dmabuf file handle containing the table.
+ * @ref_transform:	Reference transform - unsupported, please pass zero.
+ * @corner_sampled:	Whether the gains are sampled at the corner points
+ *			of the grid cells or in the cell centres.
+ * @gain_format:	Format of the gains (see enum &bcm2835_isp_gain_format).
+ */
+struct bcm2835_isp_lens_shading {
+	__u32 enabled;
+	__u32 grid_cell_size;
+	__u32 grid_width;
+	__u32 grid_stride;
+	__u32 grid_height;
+	__s32 dmabuf;
+	__u32 ref_transform;
+	__u32 corner_sampled;
+	__u32 gain_format;
+};
+
+/**
+ * struct bcm2835_isp_black_level - Sensor black level set with the
+ *				    V4L2_CID_USER_BCM2835_ISP_BLACK_LEVEL ctrl.
+ *
+ * @enabled:		Enable black level.
+ * @black_level_r:	Black level for red channel.
+ * @black_level_g:	Black level for green channels.
+ * @black_level_b:	Black level for blue channel.
+ */
+struct bcm2835_isp_black_level {
+	__u32 enabled;
+	__u16 black_level_r;
+	__u16 black_level_g;
+	__u16 black_level_b;
+	__u8 padding[2]; /* Unused */
+};
+
+/**
+ * struct bcm2835_isp_geq - Green equalisation parameters set with the
+ *			    V4L2_CID_USER_BCM2835_ISP_GEQ ctrl.
+ *
+ * @enabled:	Enable green equalisation.
+ * @offset:	Fixed offset of the green equalisation threshold.
+ * @slope:	Slope of the green equalisation threshold.
+ */
+struct bcm2835_isp_geq {
+	__u32 enabled;
+	__u32 offset;
+	struct bcm2835_isp_rational slope;
+};
+
+#define BCM2835_NUM_GAMMA_PTS 33
+
+/**
+ * struct bcm2835_isp_gamma - Gamma parameters set with the
+ *			      V4L2_CID_USER_BCM2835_ISP_GAMMA ctrl.
+ *
+ * @enabled:	Enable gamma adjustment.
+ * @X:		X values of the points defining the gamma curve.
+ *		Values should be scaled to 16 bits.
+ * @Y:		Y values of the points defining the gamma curve.
+ *		Values should be scaled to 16 bits.
+ */
+struct bcm2835_isp_gamma {
+	__u32 enabled;
+	__u16 x[BCM2835_NUM_GAMMA_PTS];
+	__u16 y[BCM2835_NUM_GAMMA_PTS];
+};
+
+/**
+ * enum bcm2835_isp_cdn_mode - Mode of operation for colour denoise.
+ *
+ * @CDN_MODE_FAST:		Fast (but lower quality) colour denoise
+ *				algorithm, typically used for video recording.
+ * @CDN_HIGH_QUALITY:		High quality (but slower) colour denoise
+ *				algorithm, typically used for stills capture.
+ */
+enum bcm2835_isp_cdn_mode {
+	CDN_MODE_FAST = 0,
+	CDN_MODE_HIGH_QUALITY = 1,
+};
+
+/**
+ * struct bcm2835_isp_cdn - Colour denoise parameters set with the
+ *			    V4L2_CID_USER_BCM2835_ISP_CDN ctrl.
+ *
+ * @enabled:	Enable colour denoise.
+ * @mode:	Colour denoise operating mode (see enum &bcm2835_isp_cdn_mode)
+ */
+struct bcm2835_isp_cdn {
+	__u32 enabled;
+	__u32 mode;
+};
+
+/**
+ * struct bcm2835_isp_denoise - Denoise parameters set with the
+ *				V4L2_CID_USER_BCM2835_ISP_DENOISE ctrl.
+ *
+ * @enabled:	Enable denoise.
+ * @constant:	Fixed offset of the noise threshold.
+ * @slope:	Slope of the noise threshold.
+ * @strength:	Denoise strength between 0.0 (off) and 1.0 (maximum).
+ */
+struct bcm2835_isp_denoise {
+	__u32 enabled;
+	__u32 constant;
+	struct bcm2835_isp_rational slope;
+	struct bcm2835_isp_rational strength;
+};
+
+/**
+ * struct bcm2835_isp_sharpen - Sharpen parameters set with the
+ *				V4L2_CID_USER_BCM2835_ISP_SHARPEN ctrl.
+ *
+ * @enabled:	Enable sharpening.
+ * @threshold:	Threshold at which to start sharpening pixels.
+ * @strength:	Strength with which pixel sharpening increases.
+ * @limit:	Limit to the amount of sharpening applied.
+ */
+struct bcm2835_isp_sharpen {
+	__u32 enabled;
+	struct bcm2835_isp_rational threshold;
+	struct bcm2835_isp_rational strength;
+	struct bcm2835_isp_rational limit;
+};
+
+/**
+ * enum bcm2835_isp_dpc_mode - defective pixel correction (DPC) strength.
+ *
+ * @DPC_MODE_OFF:		No DPC.
+ * @DPC_MODE_NORMAL:		Normal DPC.
+ * @DPC_MODE_STRONG:		Strong DPC.
+ */
+enum bcm2835_isp_dpc_mode {
+	DPC_MODE_OFF = 0,
+	DPC_MODE_NORMAL = 1,
+	DPC_MODE_STRONG = 2,
+};
+
+/**
+ * struct bcm2835_isp_dpc - Defective pixel correction (DPC) parameters set
+ *			    with the V4L2_CID_USER_BCM2835_ISP_DPC ctrl.
+ *
+ * @enabled:	Enable DPC.
+ * @strength:	DPC strength (see enum &bcm2835_isp_dpc_mode).
+ */
+struct bcm2835_isp_dpc {
+	__u32 enabled;
+	__u32 strength;
+};
+
+/*
+ * ISP statistics structures.
+ *
+ * The bcm2835_isp_stats structure is generated at the output of the
+ * statistics node.  Note that this does not directly map onto the statistics
+ * output of the ISP HW.  Instead, the MMAL firmware code maps the HW statistics
+ * to the bcm2835_isp_stats structure.
+ */
+#define DEFAULT_AWB_REGIONS_X 16
+#define DEFAULT_AWB_REGIONS_Y 12
+
+#define NUM_HISTOGRAMS 2
+#define NUM_HISTOGRAM_BINS 128
+#define AWB_REGIONS (DEFAULT_AWB_REGIONS_X * DEFAULT_AWB_REGIONS_Y)
+#define FLOATING_REGIONS 16
+#define AGC_REGIONS 16
+#define FOCUS_REGIONS 12
+
+/**
+ * struct bcm2835_isp_stats_hist - Histogram statistics
+ *
+ * @r_hist:	Red channel histogram.
+ * @g_hist:	Combined green channel histogram.
+ * @b_hist:	Blue channel histogram.
+ */
+struct bcm2835_isp_stats_hist {
+	__u32 r_hist[NUM_HISTOGRAM_BINS];
+	__u32 g_hist[NUM_HISTOGRAM_BINS];
+	__u32 b_hist[NUM_HISTOGRAM_BINS];
+};
+
+/**
+ * struct bcm2835_isp_stats_region - Region sums.
+ *
+ * @counted:	The number of 2x2 bayer tiles accumulated.
+ * @notcounted:	The number of 2x2 bayer tiles not accumulated.
+ * @r_sum:	Total sum of counted pixels in the red channel for a region.
+ * @g_sum:	Total sum of counted pixels in the green channel for a region.
+ * @b_sum:	Total sum of counted pixels in the blue channel for a region.
+ */
+struct bcm2835_isp_stats_region {
+	__u32 counted;
+	__u32 notcounted;
+	__u64 r_sum;
+	__u64 g_sum;
+	__u64 b_sum;
+};
+
+/**
+ * struct bcm2835_isp_stats_focus - Focus statistics.
+ *
+ * @contrast_val:	Focus measure - accumulated output of the focus filter.
+ *			In the first dimension, index [0] counts pixels below a
+ *			preset threshold, and index [1] counts pixels above the
+ *			threshold.  In the second dimension, index [0] uses the
+ *			first predefined filter, and index [1] uses the second
+ *			predefined filter.
+ * @contrast_val_num:	The number of counted pixels in the above accumulation.
+ */
+struct bcm2835_isp_stats_focus {
+	__u64 contrast_val[2][2];
+	__u32 contrast_val_num[2][2];
+};
+
+/**
+ * struct bcm2835_isp_stats - ISP statistics.
+ *
+ * @version:		Version of the bcm2835_isp_stats structure.
+ * @size:		Size of the bcm2835_isp_stats structure.
+ * @hist:		Histogram statistics for the entire image.
+ * @awb_stats:		Statistics for the regions defined for AWB calculations.
+ * @floating_stats:	Statistics for arbitrarily placed (floating) regions.
+ * @agc_stats:		Statistics for the regions defined for AGC calculations.
+ * @focus_stats:	Focus filter statistics for the focus regions.
+ */
+struct bcm2835_isp_stats {
+	__u32 version;
+	__u32 size;
+	struct bcm2835_isp_stats_hist hist[NUM_HISTOGRAMS];
+	struct bcm2835_isp_stats_region awb_stats[AWB_REGIONS];
+	struct bcm2835_isp_stats_region floating_stats[FLOATING_REGIONS];
+	struct bcm2835_isp_stats_region agc_stats[AGC_REGIONS];
+	struct bcm2835_isp_stats_focus focus_stats[FOCUS_REGIONS];
+};
+
+#endif /* __BCM2835_ISP_H_ */
diff --git a/include/uapi/linux/fb.h b/include/uapi/linux/fb.h
index cde8f173f566..63aac986db0b 100644
--- a/include/uapi/linux/fb.h
+++ b/include/uapi/linux/fb.h
@@ -36,6 +36,12 @@
 #define FBIOPUT_MODEINFO        0x4617
 #define FBIOGET_DISPINFO        0x4618
 #define FBIO_WAITFORVSYNC	_IOW('F', 0x20, __u32)
+/*
+ * HACK: use 'z' in order not to clash with any other ioctl numbers which might
+ * be concurrently added to the mainline kernel
+ */
+#define FBIOCOPYAREA		_IOW('z', 0x21, struct fb_copyarea)
+#define FBIODMACOPY 		_IOW('z', 0x22, struct fb_dmacopy)
 
 #define FB_TYPE_PACKED_PIXELS		0	/* Packed Pixels	*/
 #define FB_TYPE_PLANES			1	/* Non interleaved planes */
@@ -342,6 +348,12 @@ struct fb_copyarea {
 	__u32 sy;
 };
 
+struct fb_dmacopy {
+	void *dst;
+	__u32 src;
+	__u32 length;
+};
+
 struct fb_fillrect {
 	__u32 dx;	/* screen-relative */
 	__u32 dy;
diff --git a/include/uapi/linux/media-bus-format.h b/include/uapi/linux/media-bus-format.h
index d4c1d991014b..ac214f7ef341 100644
--- a/include/uapi/linux/media-bus-format.h
+++ b/include/uapi/linux/media-bus-format.h
@@ -183,4 +183,7 @@
 #define MEDIA_BUS_FMT_META_20			0x8006
 #define MEDIA_BUS_FMT_META_24			0x8007
 
+/* Sensor ancillary metadata formats - next is 0x7002 */
+#define MEDIA_BUS_FMT_SENSOR_DATA		0x7002
+
 #endif /* __LINUX_MEDIA_BUS_FORMAT_H */
diff --git a/include/uapi/linux/media/raspberrypi/pisp_be_config.h b/include/uapi/linux/media/raspberrypi/pisp_be_config.h
index cbeb714f4d61..83456e8f63ef 100644
--- a/include/uapi/linux/media/raspberrypi/pisp_be_config.h
+++ b/include/uapi/linux/media/raspberrypi/pisp_be_config.h
@@ -21,10 +21,11 @@
 /* preferred byte alignment for outputs */
 #define PISP_BACK_END_OUTPUT_MAX_ALIGN 64u
 
-/* minimum allowed tile width anywhere in the pipeline */
-#define PISP_BACK_END_MIN_TILE_WIDTH 16u
-/* minimum allowed tile width anywhere in the pipeline */
-#define PISP_BACK_END_MIN_TILE_HEIGHT 16u
+/* minimum allowed tile sizes anywhere in the pipeline */
+#define PISP_BACK_END_MIN_TILE_WIDTH	16u
+#define PISP_BACK_END_MIN_TILE_HEIGHT	16u
+#define PISP_BACK_END_MAX_TILE_WIDTH	65536u
+#define PISP_BACK_END_MAX_TILE_HEIGHT	65536u
 
 #define PISP_BACK_END_NUM_OUTPUTS 2
 #define PISP_BACK_END_HOG_OUTPUT 1
@@ -716,13 +717,6 @@ struct pisp_be_hog_buffer_config {
 /**
  * struct pisp_be_config - RaspberryPi PiSP Back End Processing configuration
  *
- * @input_buffer:		Input buffer addresses
- * @tdn_input_buffer:		TDN input buffer addresses
- * @stitch_input_buffer:	Stitch input buffer addresses
- * @tdn_output_buffer:		TDN output buffer addresses
- * @stitch_output_buffer:	Stitch output buffer addresses
- * @output_buffer:		Output buffers addresses
- * @hog_buffer:			HOG buffer addresses
  * @global:			Global PiSP configuration
  * @input_format:		Input image format
  * @decompress:			Decompress configuration
@@ -761,28 +755,10 @@ struct pisp_be_hog_buffer_config {
  * @output_format:		Output format configuration
  * @hog:			HOG configuration
  * @axi:			AXI bus configuration
- * @lsc_extra:			LSC extra info
- * @cac_extra:			CAC extra info
- * @downscale_extra:		Downscaler extra info
- * @resample_extra:		Resample extra info
- * @crop:			Crop configuration
- * @hog_format:			HOG format info
- * @dirty_flags_bayer:		Bayer enable dirty flags
- *				(:c:type:`pisp_be_bayer_enable`)
- * @dirty_flags_rgb:		RGB enable dirty flags
- *				(:c:type:`pisp_be_rgb_enable`)
- * @dirty_flags_extra:		Extra dirty flags
  */
 struct pisp_be_config {
-	/* I/O configuration: */
-	struct pisp_be_input_buffer_config input_buffer;
-	struct pisp_be_tdn_input_buffer_config tdn_input_buffer;
-	struct pisp_be_stitch_input_buffer_config stitch_input_buffer;
-	struct pisp_be_tdn_output_buffer_config tdn_output_buffer;
-	struct pisp_be_stitch_output_buffer_config stitch_output_buffer;
-	struct pisp_be_output_buffer_config
-				output_buffer[PISP_BACK_END_NUM_OUTPUTS];
-	struct pisp_be_hog_buffer_config hog_buffer;
+	/* For backward compatibility */
+	uint8_t pad0[112];
 	/* Processing configuration: */
 	struct pisp_be_global_config global;
 	struct pisp_image_format_config input_format;
@@ -823,17 +799,8 @@ struct pisp_be_config {
 				output_format[PISP_BACK_END_NUM_OUTPUTS];
 	struct pisp_be_hog_config hog;
 	struct pisp_be_axi_config axi;
-	/* Non-register fields: */
-	struct pisp_be_lsc_extra lsc_extra;
-	struct pisp_be_cac_extra cac_extra;
-	struct pisp_be_downscale_extra
-				downscale_extra[PISP_BACK_END_NUM_OUTPUTS];
-	struct pisp_be_resample_extra resample_extra[PISP_BACK_END_NUM_OUTPUTS];
-	struct pisp_be_crop_config crop;
-	struct pisp_image_format_config hog_format;
-	__u32 dirty_flags_bayer; /* these use pisp_be_bayer_enable */
-	__u32 dirty_flags_rgb; /* use pisp_be_rgb_enable */
-	__u32 dirty_flags_extra; /* these use pisp_be_dirty_t */
+	/* For backward compatibility */
+	uint8_t pad1[84];
 } __attribute__((packed));
 
 /**
diff --git a/include/uapi/linux/mempolicy.h b/include/uapi/linux/mempolicy.h
index 1f9bb10d1a47..f3df4b6084aa 100644
--- a/include/uapi/linux/mempolicy.h
+++ b/include/uapi/linux/mempolicy.h
@@ -24,6 +24,7 @@ enum {
 	MPOL_LOCAL,
 	MPOL_PREFERRED_MANY,
 	MPOL_WEIGHTED_INTERLEAVE,
+	MPOL_RANDOM,
 	MPOL_MAX,	/* always last member of enum */
 };
 
diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h
index 9c007a106330..e0ca374c0db8 100644
--- a/include/uapi/linux/serial_core.h
+++ b/include/uapi/linux/serial_core.h
@@ -231,6 +231,9 @@
 /* Sunplus UART */
 #define PORT_SUNPLUS	123
 
+/* RPi firmware UART */
+#define PORT_RPI_FW	124
+
 /* Generic type identifier for ports which type is not important to userspace. */
 #define PORT_GENERIC	(-1)
 
diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
index 974fd254e573..f7ac33a1d57b 100644
--- a/include/uapi/linux/v4l2-controls.h
+++ b/include/uapi/linux/v4l2-controls.h
@@ -215,6 +215,16 @@ enum v4l2_colorfx {
  */
 #define V4L2_CID_USER_THP7312_BASE		(V4L2_CID_USER_BASE + 0x11c0)
 
+/* The base for the bcm2835-isp driver controls.
+ * We reserve 16 controls for this driver. */
+#define V4L2_CID_USER_BCM2835_ISP_BASE		(V4L2_CID_USER_BASE + 0x10e0)
+
+/*
+ * The base for IMX500 driver controls.
+ * We reserve 16 controls for this driver.
+ */
+#define V4L2_CID_USER_IMX500_BASE		(V4L2_CID_USER_BASE + 0x2000)
+
 /* MPEG-class control IDs */
 /* The MPEG controls are applicable to all codec controls
  * and the 'MPEG' part of the define is historical */
@@ -1018,6 +1028,7 @@ enum v4l2_auto_n_preset_white_balance {
 	V4L2_WHITE_BALANCE_FLASH		= 7,
 	V4L2_WHITE_BALANCE_CLOUDY		= 8,
 	V4L2_WHITE_BALANCE_SHADE		= 9,
+	V4L2_WHITE_BALANCE_GREYWORLD		= 10,
 };
 
 #define V4L2_CID_WIDE_DYNAMIC_RANGE		(V4L2_CID_CAMERA_CLASS_BASE+21)
diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h
index 27239cb64065..ef1fdf384848 100644
--- a/include/uapi/linux/videodev2.h
+++ b/include/uapi/linux/videodev2.h
@@ -82,6 +82,11 @@
 	((__u32)(a) | ((__u32)(b) << 8) | ((__u32)(c) << 16) | ((__u32)(d) << 24))
 #define v4l2_fourcc_be(a, b, c, d)	(v4l2_fourcc(a, b, c, d) | (1U << 31))
 
+#define V4L2_FOURCC_CONV "%c%c%c%c%s"
+#define V4L2_FOURCC_CONV_ARGS(fourcc) \
+	(fourcc) & 0x7f, ((fourcc) >> 8) & 0x7f, ((fourcc) >> 16) & 0x7f, \
+	((fourcc) >> 24) & 0x7f, (fourcc) & BIT(31) ? "-BE" : ""
+
 /*
  *	E N U M S
  */
@@ -681,12 +686,20 @@ struct v4l2_pix_format {
 #define V4L2_PIX_FMT_P010_4L4 v4l2_fourcc('T', '0', '1', '0') /* 12  Y/CbCr 4:2:0 10-bit 4x4 macroblocks */
 #define V4L2_PIX_FMT_NV12_8L128       v4l2_fourcc('A', 'T', '1', '2') /* Y/CbCr 4:2:0 8x128 tiles */
 #define V4L2_PIX_FMT_NV12_10BE_8L128  v4l2_fourcc_be('A', 'X', '1', '2') /* Y/CbCr 4:2:0 10-bit 8x128 tiles */
+#define V4L2_PIX_FMT_NV12_COL128 v4l2_fourcc('N', 'C', '1', '2') /* 12  Y/CbCr 4:2:0 128 pixel wide column */
+#define V4L2_PIX_FMT_NV12_10_COL128 v4l2_fourcc('N', 'C', '3', '0')
+								/* Y/CbCr 4:2:0 10bpc, 3x10 packed as 4 bytes in
+								 * a 128 bytes / 96 pixel wide column */
 
 /* Tiled YUV formats, non contiguous planes */
 #define V4L2_PIX_FMT_NV12MT  v4l2_fourcc('T', 'M', '1', '2') /* 12  Y/CbCr 4:2:0 64x32 tiles */
 #define V4L2_PIX_FMT_NV12MT_16X16 v4l2_fourcc('V', 'M', '1', '2') /* 12  Y/CbCr 4:2:0 16x16 tiles */
 #define V4L2_PIX_FMT_NV12M_8L128      v4l2_fourcc('N', 'A', '1', '2') /* Y/CbCr 4:2:0 8x128 tiles */
 #define V4L2_PIX_FMT_NV12M_10BE_8L128 v4l2_fourcc_be('N', 'T', '1', '2') /* Y/CbCr 4:2:0 10-bit 8x128 tiles */
+#define V4L2_PIX_FMT_NV12MT_COL128 v4l2_fourcc('N', 'c', '1', '2') /* 12  Y/CbCr 4:2:0 128 pixel wide column */
+#define V4L2_PIX_FMT_NV12MT_10_COL128 v4l2_fourcc('N', 'c', '3', '0')
+			/* Y/CbCr 4:2:0 10bpc, 3x10 packed as 4 bytes in a 128 bytes / 96 pixel wide column */
+
 
 /* Bayer formats - see http://www.siliconimaging.com/RGB%20Bayer.htm */
 #define V4L2_PIX_FMT_SBGGR8  v4l2_fourcc('B', 'A', '8', '1') /*  8  BGBG.. GRGR.. */
@@ -829,6 +842,17 @@ struct v4l2_pix_format {
 #define V4L2_PIX_FMT_PISP_COMP2_BGGR	v4l2_fourcc('P', 'C', '2', 'B') /* PiSP 8-bit mode 2 compressed BGGR bayer */
 #define V4L2_PIX_FMT_PISP_COMP2_MONO	v4l2_fourcc('P', 'C', '2', 'M') /* PiSP 8-bit mode 2 compressed monochrome */
 
+/* The pixel format for all our buffers (the precise format is found in the config buffer). */
+#define V4L2_PIX_FMT_RPI_BE		v4l2_fourcc('R', 'P', 'B', 'P')
+#define V4L2_PIX_FMT_PISP_COMP1_RGGB	v4l2_fourcc('P', 'C', '1', 'R')
+#define V4L2_PIX_FMT_PISP_COMP1_GRBG	v4l2_fourcc('P', 'C', '1', 'G')
+#define V4L2_PIX_FMT_PISP_COMP1_GBRG	v4l2_fourcc('P', 'C', '1', 'g')
+#define V4L2_PIX_FMT_PISP_COMP1_BGGR	v4l2_fourcc('P', 'C', '1', 'B')
+#define V4L2_PIX_FMT_PISP_COMP2_RGGB	v4l2_fourcc('P', 'C', '2', 'R')
+#define V4L2_PIX_FMT_PISP_COMP2_GRBG	v4l2_fourcc('P', 'C', '2', 'G')
+#define V4L2_PIX_FMT_PISP_COMP2_GBRG	v4l2_fourcc('P', 'C', '2', 'g')
+#define V4L2_PIX_FMT_PISP_COMP2_BGGR	v4l2_fourcc('P', 'C', '2', 'B')
+
 /* SDR formats - used only for Software Defined Radio devices */
 #define V4L2_SDR_FMT_CU8          v4l2_fourcc('C', 'U', '0', '8') /* IQ u8 */
 #define V4L2_SDR_FMT_CU16LE       v4l2_fourcc('C', 'U', '1', '6') /* IQ u16le */
@@ -851,6 +875,8 @@ struct v4l2_pix_format {
 #define V4L2_META_FMT_UVC         v4l2_fourcc('U', 'V', 'C', 'H') /* UVC Payload Header metadata */
 #define V4L2_META_FMT_D4XX        v4l2_fourcc('D', '4', 'X', 'X') /* D4XX Payload Header metadata */
 #define V4L2_META_FMT_VIVID	  v4l2_fourcc('V', 'I', 'V', 'D') /* Vivid Metadata */
+#define V4L2_META_FMT_SENSOR_DATA v4l2_fourcc('S', 'E', 'N', 'S') /* Sensor Ancillary metadata */
+#define V4L2_META_FMT_BCM2835_ISP_STATS v4l2_fourcc('B', 'S', 'T', 'A') /* BCM2835 ISP image statistics output */
 
 /* Vendor specific - used for RK_ISP1 camera sub-system */
 #define V4L2_META_FMT_RK_ISP1_PARAMS	v4l2_fourcc('R', 'K', '1', 'P') /* Rockchip ISP1 3A Parameters */
@@ -860,6 +886,12 @@ struct v4l2_pix_format {
 /* Vendor specific - used for RaspberryPi PiSP */
 #define V4L2_META_FMT_RPI_BE_CFG	v4l2_fourcc('R', 'P', 'B', 'C') /* PiSP BE configuration */
 
+/* The metadata format identifier for FE configuration buffers. */
+#define V4L2_META_FMT_RPI_FE_CFG v4l2_fourcc('R', 'P', 'F', 'C')
+
+/* The metadata format identifier for FE configuration buffers. */
+#define V4L2_META_FMT_RPI_FE_STATS v4l2_fourcc('R', 'P', 'F', 'S')
+
 #ifdef __KERNEL__
 /*
  * Line-based metadata formats. Remember to update v4l_fill_fmtdesc() when
diff --git a/include/uapi/misc/rp1_pio_if.h b/include/uapi/misc/rp1_pio_if.h
new file mode 100644
index 000000000000..a9a54d3322ec
--- /dev/null
+++ b/include/uapi/misc/rp1_pio_if.h
@@ -0,0 +1,235 @@
+/* SPDX-License-Identifier: GPL-2.0 + WITH Linux-syscall-note */
+/*
+ * Copyright (c) 2023-24 Raspberry Pi Ltd.
+ * All rights reserved.
+ */
+#ifndef _PIO_RP1_IF_H
+#define _PIO_RP1_IF_H
+
+#include <linux/ioctl.h>
+
+#define RP1_PIO_INSTRUCTION_COUNT   32
+#define RP1_PIO_SM_COUNT            4
+#define RP1_PIO_GPIO_COUNT          28
+#define RP1_GPIO_FUNC_PIO           7
+
+#define RP1_PIO_ORIGIN_ANY          ((uint16_t)(~0))
+
+#define RP1_PIO_DIR_TO_SM           0
+#define RP1_PIO_DIR_FROM_SM         1
+#define RP1_PIO_DIR_COUNT           2
+
+typedef struct {
+	uint32_t clkdiv;
+	uint32_t execctrl;
+	uint32_t shiftctrl;
+	uint32_t pinctrl;
+} rp1_pio_sm_config;
+
+struct rp1_pio_add_program_args {
+	uint16_t num_instrs;
+	uint16_t origin;
+	uint16_t instrs[RP1_PIO_INSTRUCTION_COUNT];
+};
+
+struct rp1_pio_remove_program_args {
+	uint16_t num_instrs;
+	uint16_t origin;
+};
+
+struct rp1_pio_sm_claim_args {
+	uint16_t mask;
+};
+
+struct rp1_pio_sm_init_args {
+	uint16_t sm;
+	uint16_t initial_pc;
+	rp1_pio_sm_config config;
+};
+
+struct rp1_pio_sm_set_config_args {
+	uint16_t sm;
+	uint16_t rsvd;
+	rp1_pio_sm_config config;
+};
+
+struct rp1_pio_sm_exec_args {
+	uint16_t sm;
+	uint16_t instr;
+	uint8_t blocking;
+	uint8_t rsvd;
+};
+
+struct rp1_pio_sm_clear_fifos_args {
+	uint16_t sm;
+};
+
+struct rp1_pio_sm_set_clkdiv_args {
+	uint16_t sm;
+	uint16_t div_int;
+	uint8_t div_frac;
+	uint8_t rsvd;
+};
+
+struct rp1_pio_sm_set_pins_args {
+	uint16_t sm;
+	uint16_t rsvd;
+	uint32_t values;
+	uint32_t mask;
+};
+
+struct rp1_pio_sm_set_pindirs_args {
+	uint16_t sm;
+	uint16_t rsvd;
+	uint32_t dirs;
+	uint32_t mask;
+};
+
+struct rp1_pio_sm_set_enabled_args {
+	uint16_t mask;
+	uint8_t enable;
+	uint8_t rsvd;
+};
+
+struct rp1_pio_sm_restart_args {
+	uint16_t mask;
+};
+
+struct rp1_pio_sm_clkdiv_restart_args {
+	uint16_t mask;
+};
+
+struct rp1_pio_sm_enable_sync_args {
+	uint16_t mask;
+};
+
+struct rp1_pio_sm_put_args {
+	uint16_t sm;
+	uint8_t blocking;
+	uint8_t rsvd;
+	uint32_t data;
+};
+
+struct rp1_pio_sm_get_args {
+	uint16_t sm;
+	uint8_t blocking;
+	uint8_t rsvd;
+	uint32_t data; /* OUT */
+};
+
+struct rp1_pio_sm_set_dmactrl_args {
+	uint16_t sm;
+	uint8_t is_tx;
+	uint8_t rsvd;
+	uint32_t ctrl;
+};
+
+struct rp1_pio_sm_fifo_state_args {
+	uint16_t sm;
+	uint8_t tx;
+	uint8_t rsvd;
+	uint16_t level; /* OUT */
+	uint8_t empty; /* OUT */
+	uint8_t full; /* OUT */
+};
+
+struct rp1_gpio_init_args {
+	uint16_t gpio;
+};
+
+struct rp1_gpio_set_function_args {
+	uint16_t gpio;
+	uint16_t fn;
+};
+
+struct rp1_gpio_set_pulls_args {
+	uint16_t gpio;
+	uint8_t up;
+	uint8_t down;
+};
+
+struct rp1_gpio_set_args {
+	uint16_t gpio;
+	uint16_t value;
+};
+
+struct rp1_pio_sm_config_xfer_args {
+	uint16_t sm;
+	uint16_t dir;
+	uint16_t buf_size;
+	uint16_t buf_count;
+};
+
+struct rp1_pio_sm_config_xfer32_args {
+	uint16_t sm;
+	uint16_t dir;
+	uint32_t buf_size;
+	uint32_t buf_count;
+};
+
+struct rp1_pio_sm_xfer_data_args {
+	uint16_t sm;
+	uint16_t dir;
+	uint16_t data_bytes;
+	void *data;
+};
+
+struct rp1_pio_sm_xfer_data32_args {
+	uint16_t sm;
+	uint16_t dir;
+	uint32_t data_bytes;
+	void *data;
+};
+
+struct rp1_access_hw_args {
+	uint32_t addr;
+	uint32_t len;
+	void *data;
+};
+
+#define PIO_IOC_MAGIC 102
+
+#define PIO_IOC_SM_CONFIG_XFER _IOW(PIO_IOC_MAGIC, 0, struct rp1_pio_sm_config_xfer_args)
+#define PIO_IOC_SM_XFER_DATA _IOW(PIO_IOC_MAGIC, 1, struct rp1_pio_sm_xfer_data_args)
+#define PIO_IOC_SM_XFER_DATA32 _IOW(PIO_IOC_MAGIC, 2, struct rp1_pio_sm_xfer_data32_args)
+#define PIO_IOC_SM_CONFIG_XFER32 _IOW(PIO_IOC_MAGIC, 3, struct rp1_pio_sm_config_xfer32_args)
+
+#define PIO_IOC_READ_HW _IOW(PIO_IOC_MAGIC, 8, struct rp1_access_hw_args)
+#define PIO_IOC_WRITE_HW _IOW(PIO_IOC_MAGIC, 9, struct rp1_access_hw_args)
+
+#define PIO_IOC_CAN_ADD_PROGRAM _IOW(PIO_IOC_MAGIC, 10, struct rp1_pio_add_program_args)
+#define PIO_IOC_ADD_PROGRAM _IOW(PIO_IOC_MAGIC, 11, struct rp1_pio_add_program_args)
+#define PIO_IOC_REMOVE_PROGRAM _IOW(PIO_IOC_MAGIC, 12, struct rp1_pio_remove_program_args)
+#define PIO_IOC_CLEAR_INSTR_MEM _IO(PIO_IOC_MAGIC, 13)
+
+#define PIO_IOC_SM_CLAIM _IOW(PIO_IOC_MAGIC, 20, struct rp1_pio_sm_claim_args)
+#define PIO_IOC_SM_UNCLAIM _IOW(PIO_IOC_MAGIC, 21, struct rp1_pio_sm_claim_args)
+#define PIO_IOC_SM_IS_CLAIMED _IOW(PIO_IOC_MAGIC, 22, struct rp1_pio_sm_claim_args)
+
+#define PIO_IOC_SM_INIT _IOW(PIO_IOC_MAGIC, 30, struct rp1_pio_sm_init_args)
+#define PIO_IOC_SM_SET_CONFIG _IOW(PIO_IOC_MAGIC, 31, struct rp1_pio_sm_set_config_args)
+#define PIO_IOC_SM_EXEC _IOW(PIO_IOC_MAGIC, 32, struct rp1_pio_sm_exec_args)
+#define PIO_IOC_SM_CLEAR_FIFOS _IOW(PIO_IOC_MAGIC, 33, struct rp1_pio_sm_clear_fifos_args)
+#define PIO_IOC_SM_SET_CLKDIV _IOW(PIO_IOC_MAGIC, 34, struct rp1_pio_sm_set_clkdiv_args)
+#define PIO_IOC_SM_SET_PINS _IOW(PIO_IOC_MAGIC, 35, struct rp1_pio_sm_set_pins_args)
+#define PIO_IOC_SM_SET_PINDIRS _IOW(PIO_IOC_MAGIC, 36, struct rp1_pio_sm_set_pindirs_args)
+#define PIO_IOC_SM_SET_ENABLED _IOW(PIO_IOC_MAGIC, 37, struct rp1_pio_sm_set_enabled_args)
+#define PIO_IOC_SM_RESTART _IOW(PIO_IOC_MAGIC, 38, struct rp1_pio_sm_restart_args)
+#define PIO_IOC_SM_CLKDIV_RESTART _IOW(PIO_IOC_MAGIC, 39, struct rp1_pio_sm_restart_args)
+#define PIO_IOC_SM_ENABLE_SYNC _IOW(PIO_IOC_MAGIC, 40, struct rp1_pio_sm_enable_sync_args)
+#define PIO_IOC_SM_PUT _IOW(PIO_IOC_MAGIC, 41, struct rp1_pio_sm_put_args)
+#define PIO_IOC_SM_GET _IOWR(PIO_IOC_MAGIC, 42, struct rp1_pio_sm_get_args)
+#define PIO_IOC_SM_SET_DMACTRL _IOW(PIO_IOC_MAGIC, 43, struct rp1_pio_sm_set_dmactrl_args)
+#define PIO_IOC_SM_FIFO_STATE _IOW(PIO_IOC_MAGIC, 44, struct rp1_pio_sm_fifo_state_args)
+#define PIO_IOC_SM_DRAIN_TX _IOW(PIO_IOC_MAGIC, 45, struct rp1_pio_sm_clear_fifos_args)
+
+#define PIO_IOC_GPIO_INIT _IOW(PIO_IOC_MAGIC, 50, struct rp1_gpio_init_args)
+#define PIO_IOC_GPIO_SET_FUNCTION _IOW(PIO_IOC_MAGIC, 51, struct rp1_gpio_set_function_args)
+#define PIO_IOC_GPIO_SET_PULLS _IOW(PIO_IOC_MAGIC, 52, struct rp1_gpio_set_pulls_args)
+#define PIO_IOC_GPIO_SET_OUTOVER _IOW(PIO_IOC_MAGIC, 53, struct rp1_gpio_set_args)
+#define PIO_IOC_GPIO_SET_INOVER _IOW(PIO_IOC_MAGIC, 54, struct rp1_gpio_set_args)
+#define PIO_IOC_GPIO_SET_OEOVER _IOW(PIO_IOC_MAGIC, 55, struct rp1_gpio_set_args)
+#define PIO_IOC_GPIO_SET_INPUT_ENABLED _IOW(PIO_IOC_MAGIC, 56, struct rp1_gpio_set_args)
+#define PIO_IOC_GPIO_SET_DRIVE_STRENGTH _IOW(PIO_IOC_MAGIC, 57, struct rp1_gpio_set_args)
+
+#endif
diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c
index 046f671532b0..532e2f383f5d 100644
--- a/kernel/cgroup/cgroup.c
+++ b/kernel/cgroup/cgroup.c
@@ -6933,6 +6933,39 @@ static int __init cgroup_disable(char *str)
 }
 __setup("cgroup_disable=", cgroup_disable);
 
+static int __init cgroup_enable(char *str)
+{
+	struct cgroup_subsys *ss;
+	char *token;
+	int i;
+
+	while ((token = strsep(&str, ",")) != NULL) {
+		if (!*token)
+			continue;
+
+		for_each_subsys(ss, i) {
+			if (strcmp(token, ss->name) &&
+			    strcmp(token, ss->legacy_name))
+				continue;
+
+			static_branch_enable(cgroup_subsys_enabled_key[i]);
+			pr_info("Enabling %s control group subsystem\n",
+				ss->name);
+		}
+
+		for (i = 0; i < OPT_FEATURE_COUNT; i++) {
+			if (strcmp(token, cgroup_opt_feature_names[i]))
+				continue;
+			cgroup_feature_disable_mask &= ~(1 << i);
+			pr_info("Enabling %s control group feature\n",
+				cgroup_opt_feature_names[i]);
+			break;
+		}
+	}
+	return 1;
+}
+__setup("cgroup_enable=", cgroup_enable);
+
 void __init __weak enable_debug_cgroup(void) { }
 
 static int __init enable_cgroup_debug(char *str)
diff --git a/kernel/resource.c b/kernel/resource.c
index 2182854dde68..a7438c761e99 100644
--- a/kernel/resource.c
+++ b/kernel/resource.c
@@ -192,6 +192,12 @@ static int __release_resource(struct resource *old, bool release_child)
 {
 	struct resource *tmp, **p, *chd;
 
+	if (!old->parent) {
+		WARN(old->sibling, "sibling but no parent");
+		if (old->sibling)
+			return -EINVAL;
+		return 0;
+	}
 	p = &old->parent->child;
 	for (;;) {
 		tmp = *p;
diff --git a/lib/earlycpio.c b/lib/earlycpio.c
index d2c37d64fd0c..4b1ce69a6ee5 100644
--- a/lib/earlycpio.c
+++ b/lib/earlycpio.c
@@ -139,3 +139,4 @@ struct cpio_data find_cpio_data(const char *path, void *data,
 quit:
 	return cd;
 }
+EXPORT_SYMBOL_GPL(find_cpio_data);
diff --git a/mm/cma.c b/mm/cma.c
index 2d9fae939283..65d2a04ffb98 100644
--- a/mm/cma.c
+++ b/mm/cma.c
@@ -602,3 +602,39 @@ int cma_for_each_area(int (*it)(struct cma *cma, void *data), void *data)
 
 	return 0;
 }
+
+struct cma_check_range_data {
+	u64 start, end;
+};
+
+static int check_range(struct cma *cma_, void *data)
+{
+	struct cma_check_range_data *range = data;
+	struct cma_check_range_data cma;
+	bool starts_in_range;
+	bool ends_in_range;
+
+	cma.start = cma_get_base(cma_);
+	cma.end = cma.start + cma_get_size(cma_) - 1;
+
+	starts_in_range = cma.start >= range->start && cma.start <= range->end;
+	ends_in_range = cma.end >= range->start && cma.end <= range->end;
+
+	if (starts_in_range == ends_in_range)
+		return 0;
+
+	pr_notice("CMA %s [%llx-%llx] straddles range [%llx-%llx]\n",
+		  cma_->name, cma.start, cma.end, range->start, range->end);
+
+	return -EINVAL;
+}
+
+int cma_check_range(u64 *start, u64 *end)
+{
+	struct cma_check_range_data range = {
+		.start = *start,
+		.end = *end,
+	};
+
+	return cma_for_each_area(check_range, &range);
+}
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index 7b908c4cc7ee..2240a63fea79 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -41,6 +41,9 @@
  * preferred many Try a set of nodes first before normal fallback. This is
  *                similar to preferred without the special case.
  *
+ * random         Allocate memory from a random node out of allowed set of
+ *                nodes.
+ *
  * default        Allocate on the local node first, or when on a VMA
  *                use the process policy. This is what Linux always did
  *		  in a NUMA aware kernel and still does by, ahem, default.
@@ -82,6 +85,7 @@
 #include <linux/highmem.h>
 #include <linux/hugetlb.h>
 #include <linux/kernel.h>
+#include <linux/ktime.h>
 #include <linux/sched.h>
 #include <linux/sched/mm.h>
 #include <linux/sched/numa_balancing.h>
@@ -135,6 +139,7 @@ static struct mempolicy default_policy = {
 	.refcnt = ATOMIC_INIT(1), /* never free it */
 	.mode = MPOL_LOCAL,
 };
+static bool mempolicy_cmdline_set;
 
 static struct mempolicy preferred_node_policy[MAX_NUMNODES];
 
@@ -452,6 +457,10 @@ static const struct mempolicy_operations mpol_ops[MPOL_MAX] = {
 		.create = mpol_new_nodemask,
 		.rebind = mpol_rebind_nodemask,
 	},
+	[MPOL_RANDOM] = {
+		.create = mpol_new_nodemask,
+		.rebind = mpol_rebind_nodemask,
+	},
 };
 
 static bool migrate_folio_add(struct folio *folio, struct list_head *foliolist,
@@ -900,6 +909,7 @@ static void get_policy_nodemask(struct mempolicy *pol, nodemask_t *nodes)
 	case MPOL_PREFERRED:
 	case MPOL_PREFERRED_MANY:
 	case MPOL_WEIGHTED_INTERLEAVE:
+	case MPOL_RANDOM:
 		*nodes = pol->nodes;
 		break;
 	case MPOL_LOCAL:
@@ -1615,12 +1625,21 @@ SYSCALL_DEFINE6(mbind, unsigned long, start, unsigned long, len,
 static long kernel_set_mempolicy(int mode, const unsigned long __user *nmask,
 				 unsigned long maxnode)
 {
+	char name[sizeof(current->comm)];
 	unsigned short mode_flags;
 	nodemask_t nodes;
 	int lmode = mode;
 	int err;
 
 	err = sanitize_mpol_flags(&lmode, &mode_flags);
+
+	if (mempolicy_cmdline_set) {
+		// ignore messages during boot which are expected
+		if (ktime_get_boottime_seconds() > 40)
+			pr_info("Request by '%s' to set policy to %d ignored\n", get_task_comm(name, current), mode);
+		return 0;
+	}
+
 	if (err)
 		return err;
 
@@ -1917,6 +1936,27 @@ static unsigned int interleave_nodes(struct mempolicy *policy)
 	return nid;
 }
 
+static unsigned int read_once_policy_nodemask(struct mempolicy *pol, nodemask_t *mask);
+
+static unsigned int random_nodes(struct mempolicy *policy)
+{
+	unsigned int nid = first_node(policy->nodes);
+	unsigned int cpuset_mems_cookie;
+	nodemask_t nodemask;
+	unsigned int r;
+
+	r = get_random_u32_below(read_once_policy_nodemask(policy, &nodemask));
+
+	/* to prevent miscount, use tsk->mems_allowed_seq to detect rebind */
+	do {
+		cpuset_mems_cookie = read_mems_allowed_begin();
+		while (r--)
+			nid = next_node_in(nid, policy->nodes);
+	} while (read_mems_allowed_retry(cpuset_mems_cookie));
+
+	return nid;
+}
+
 /*
  * Depending on the memory policy provide a node from which to allocate the
  * next slab entry.
@@ -1962,6 +2002,9 @@ unsigned int mempolicy_slab_node(void)
 	case MPOL_LOCAL:
 		return node;
 
+	case MPOL_RANDOM:
+		return random_nodes(policy);
+
 	default:
 		BUG();
 	}
@@ -2042,6 +2085,33 @@ static unsigned int interleave_nid(struct mempolicy *pol, pgoff_t ilx)
 	return nid;
 }
 
+static unsigned int random_nid(struct mempolicy *pol,
+			       struct vm_area_struct *vma,
+			       pgoff_t ilx)
+{
+	nodemask_t nodemask;
+	unsigned int r, nnodes;
+	int i, nid;
+
+	nnodes = read_once_policy_nodemask(pol, &nodemask);
+	if (!nnodes)
+		return numa_node_id();
+
+	/*
+	 * QQQ
+	 * Can we say hash of vma+ilx is sufficiently random but still
+	 * stable in case of reliance on stable, as it appears is with
+	 * mpol_misplaced and interleaving?
+	 */
+	r = hash_long((unsigned long)vma + ilx,
+		      ilog2(roundup_pow_of_two(nnodes)));
+
+	nid = first_node(nodemask);
+	for (i = 0; i < r; i++)
+		nid = next_node(nid, nodemask);
+	return nid;
+}
+
 /*
  * Return a nodemask representing a mempolicy for filtering nodes for
  * page allocation, together with preferred node id (or the input node id).
@@ -2085,11 +2155,20 @@ static nodemask_t *policy_nodemask(gfp_t gfp, struct mempolicy *pol,
 			weighted_interleave_nodes(pol) :
 			weighted_interleave_nid(pol, ilx);
 		break;
+	case MPOL_RANDOM:
+		*nid = random_nodes(pol);
+		break;
 	}
 
 	return nodemask;
 }
 
+nodemask_t *numa_policy_nodemask(gfp_t gfp, struct mempolicy *pol, pgoff_t ilx,
+				 int *nid)
+{
+	return policy_nodemask(gfp, pol, ilx, nid);
+}
+
 #ifdef CONFIG_HUGETLBFS
 /*
  * huge_node(@vma, @addr, @gfp_flags, @mpol)
@@ -2147,6 +2226,7 @@ bool init_nodemask_of_mempolicy(nodemask_t *mask)
 	case MPOL_BIND:
 	case MPOL_INTERLEAVE:
 	case MPOL_WEIGHTED_INTERLEAVE:
+	case MPOL_RANDOM:
 		*mask = mempolicy->nodes;
 		break;
 
@@ -2627,6 +2707,7 @@ bool __mpol_equal(struct mempolicy *a, struct mempolicy *b)
 	case MPOL_PREFERRED:
 	case MPOL_PREFERRED_MANY:
 	case MPOL_WEIGHTED_INTERLEAVE:
+	case MPOL_RANDOM:
 		return !!nodes_equal(a->nodes, b->nodes);
 	case MPOL_LOCAL:
 		return true;
@@ -2818,6 +2899,10 @@ int mpol_misplaced(struct folio *folio, struct vm_fault *vmf,
 		polnid = zonelist_node_idx(z);
 		break;
 
+	case MPOL_RANDOM:
+		polnid = random_nid(pol, vma, ilx);
+		break;
+
 	default:
 		BUG();
 	}
@@ -3146,7 +3231,9 @@ void __init numa_policy_init(void)
 /* Reset policy of current process to default */
 void numa_default_policy(void)
 {
-	do_set_mempolicy(MPOL_DEFAULT, 0, NULL);
+	struct mempolicy *pol = &default_policy;
+
+	do_set_mempolicy(pol->mode, pol->flags, &pol->nodes);
 }
 
 /*
@@ -3161,9 +3248,9 @@ static const char * const policy_modes[] =
 	[MPOL_WEIGHTED_INTERLEAVE] = "weighted interleave",
 	[MPOL_LOCAL]      = "local",
 	[MPOL_PREFERRED_MANY]  = "prefer (many)",
+	[MPOL_RANDOM]  = "random",
 };
 
-#ifdef CONFIG_TMPFS
 /**
  * mpol_parse_str - parse string to mempolicy, for tmpfs mpol mount option.
  * @str:  string containing mempolicy to parse
@@ -3176,13 +3263,18 @@ static const char * const policy_modes[] =
  */
 int mpol_parse_str(char *str, struct mempolicy **mpol)
 {
-	struct mempolicy *new = NULL;
+	struct mempolicy *new;
 	unsigned short mode_flags;
 	nodemask_t nodes;
 	char *nodelist = strchr(str, ':');
 	char *flags = strchr(str, '=');
 	int err = 1, mode;
 
+	if (*mpol)
+		new = *mpol;
+	else
+		new = NULL;
+
 	if (flags)
 		*flags++ = '\0';	/* terminate mode string */
 
@@ -3219,6 +3311,7 @@ int mpol_parse_str(char *str, struct mempolicy **mpol)
 		break;
 	case MPOL_INTERLEAVE:
 	case MPOL_WEIGHTED_INTERLEAVE:
+	case MPOL_RANDOM:
 		/*
 		 * Default to online nodes with memory if no nodelist
 		 */
@@ -3262,9 +3355,16 @@ int mpol_parse_str(char *str, struct mempolicy **mpol)
 			goto out;
 	}
 
-	new = mpol_new(mode, mode_flags, &nodes);
-	if (IS_ERR(new))
-		goto out;
+	if (!new) {
+		new = mpol_new(mode, mode_flags, &nodes);
+		if (IS_ERR(new))
+			goto out;
+	} else {
+		atomic_set(&new->refcnt, 1);
+		new->mode = mode;
+		new->flags = mode_flags;
+		new->home_node = NUMA_NO_NODE;
+	}
 
 	/*
 	 * Save nodes for mpol_to_str() to show the tmpfs mount options
@@ -3297,7 +3397,30 @@ int mpol_parse_str(char *str, struct mempolicy **mpol)
 		*mpol = new;
 	return err;
 }
-#endif /* CONFIG_TMPFS */
+
+static int __init setup_numapolicy(char *str)
+{
+	struct mempolicy pol = { }, *ppol = &pol;
+	char buf[128];
+	int ret;
+
+	if (str)
+		ret = mpol_parse_str(str, &ppol);
+	else
+		ret = -EINVAL;
+
+	if (!ret) {
+		default_policy = pol;
+		mpol_to_str(buf, sizeof(buf), &pol);
+		pr_info("NUMA default policy overridden to '%s'\n", buf);
+		mempolicy_cmdline_set = pol.mode != MPOL_DEFAULT;
+	} else {
+		pr_warn("Unable to parse numa_policy=\n");
+	}
+
+	return ret == 0;
+}
+__setup("numa_policy=", setup_numapolicy);
 
 /**
  * mpol_to_str - format a mempolicy structure for printing
@@ -3334,6 +3457,7 @@ void mpol_to_str(char *buffer, int maxlen, struct mempolicy *pol)
 	case MPOL_BIND:
 	case MPOL_INTERLEAVE:
 	case MPOL_WEIGHTED_INTERLEAVE:
+	case MPOL_RANDOM:
 		nodes = pol->nodes;
 		break;
 	default:
diff --git a/mm/numa_emulation.c b/mm/numa_emulation.c
index 9d55679d99ce..15e27dd1cfe8 100644
--- a/mm/numa_emulation.c
+++ b/mm/numa_emulation.c
@@ -7,6 +7,7 @@
 #include <linux/topology.h>
 #include <linux/memblock.h>
 #include <linux/numa_memblks.h>
+#include <linux/cma.h>
 #include <asm/numa.h>
 #include <acpi/acpi_numa.h>
 
@@ -52,6 +53,7 @@ static int __init emu_setup_memblk(struct numa_meminfo *ei,
 {
 	struct numa_memblk *eb = &ei->blk[ei->nr_blks];
 	struct numa_memblk *pb = &pi->blk[phys_blk];
+	int ret;
 
 	if (ei->nr_blks >= NR_NODE_MEMBLKS) {
 		pr_err("NUMA: Too many emulated memblks, failing emulation\n");
@@ -63,6 +65,10 @@ static int __init emu_setup_memblk(struct numa_meminfo *ei,
 	eb->end = pb->start + size;
 	eb->nid = nid;
 
+	ret = cma_check_range(&eb->start, &eb->end);
+	if (ret)
+		return ret;
+
 	if (emu_nid_to_phys[nid] == NUMA_NO_NODE)
 		emu_nid_to_phys[nid] = pb->nid;
 
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index b1a8abe5005e..dd12910b3a59 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -234,6 +234,27 @@ EXPORT_SYMBOL(node_states);
 
 gfp_t gfp_allowed_mask __read_mostly = GFP_BOOT_MASK;
 
+#define ALLOC_IN_CMA_THRESHOLD_MAX 16
+#define ALLOC_IN_CMA_THRESHOLD_DEFAULT 12
+
+static unsigned long _alloc_in_cma_threshold __read_mostly
+				= ALLOC_IN_CMA_THRESHOLD_DEFAULT;
+
+static int __init alloc_in_cma_threshold_setup(char *buf)
+{
+	unsigned long res;
+
+	if (kstrtoul(buf, 10, &res) < 0 ||
+	    res > ALLOC_IN_CMA_THRESHOLD_MAX) {
+		pr_err("Bad alloc_cma_threshold value\n");
+		return 0;
+	}
+	_alloc_in_cma_threshold = res;
+	pr_info("Setting alloc_in_cma_threshold to %lu\n", res);
+	return 0;
+}
+early_param("alloc_in_cma_threshold", alloc_in_cma_threshold_setup);
+
 #ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE
 unsigned int pageblock_order __read_mostly;
 #endif
@@ -2292,12 +2313,13 @@ __rmqueue(struct zone *zone, unsigned int order, int migratetype,
 	if (IS_ENABLED(CONFIG_CMA)) {
 		/*
 		 * Balance movable allocations between regular and CMA areas by
-		 * allocating from CMA when over half of the zone's free memory
-		 * is in the CMA area.
+		 * allocating from CMA when over more than a given proportion of
+		 * the zone's free memory is in the CMA area.
 		 */
 		if (alloc_flags & ALLOC_CMA &&
 		    zone_page_state(zone, NR_FREE_CMA_PAGES) >
-		    zone_page_state(zone, NR_FREE_PAGES) / 2) {
+		    zone_page_state(zone, NR_FREE_PAGES) / ALLOC_IN_CMA_THRESHOLD_MAX
+		    * _alloc_in_cma_threshold) {
 			page = __rmqueue_cma_fallback(zone, order);
 			if (page)
 				return page;
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 0ceed77af0fb..4bf027f0bade 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -4136,7 +4136,7 @@ bool lru_gen_look_around(struct page_vma_mapped_walk *pvmw)
 		if (!folio)
 			continue;
 
-		if (!ptep_clear_young_notify(vma, addr, pte + i))
+		if (!ptep_clear_flush_young_notify(vma, addr, pte + i))
 			continue;
 
 		young++;
diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index 1656448649b9..5e52421b14d7 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -4935,6 +4935,8 @@ static const struct {
  */
 static int hci_dev_setup_sync(struct hci_dev *hdev)
 {
+	struct fwnode_handle *fwnode =
+		hdev->dev.parent ? dev_fwnode(hdev->dev.parent) : NULL;
 	int ret = 0;
 	bool invalid_bdaddr;
 	size_t i;
@@ -4963,7 +4965,8 @@ static int hci_dev_setup_sync(struct hci_dev *hdev)
 			 test_bit(HCI_QUIRK_USE_BDADDR_PROPERTY, &hdev->quirks);
 	if (!ret) {
 		if (test_bit(HCI_QUIRK_USE_BDADDR_PROPERTY, &hdev->quirks) &&
-		    !bacmp(&hdev->public_addr, BDADDR_ANY))
+		    !bacmp(&hdev->public_addr, BDADDR_ANY) &&
+		    (invalid_bdaddr || !fwnode_property_present(fwnode, "fallback-bd-address")))
 			hci_dev_get_bd_addr_from_property(hdev);
 
 		if (invalid_bdaddr && bacmp(&hdev->public_addr, BDADDR_ANY) &&
diff --git a/net/wireless/certs/debian.hex b/net/wireless/certs/debian.hex
new file mode 100644
index 000000000000..c5ab03f8c500
--- /dev/null
+++ b/net/wireless/certs/debian.hex
@@ -0,0 +1,1426 @@
+0x30,
+0x82,
+0x02,
+0xbd,
+0x30,
+0x82,
+0x01,
+0xa5,
+0x02,
+0x14,
+0x57,
+0x7e,
+0x02,
+0x1c,
+0xb9,
+0x80,
+0xe0,
+0xe8,
+0x20,
+0x82,
+0x1b,
+0xa7,
+0xb5,
+0x4b,
+0x49,
+0x61,
+0xb8,
+0xb4,
+0xfa,
+0xdf,
+0x30,
+0x0d,
+0x06,
+0x09,
+0x2a,
+0x86,
+0x48,
+0x86,
+0xf7,
+0x0d,
+0x01,
+0x01,
+0x0b,
+0x05,
+0x00,
+0x30,
+0x1a,
+0x31,
+0x18,
+0x30,
+0x16,
+0x06,
+0x03,
+0x55,
+0x04,
+0x03,
+0x0c,
+0x0f,
+0x62,
+0x65,
+0x6e,
+0x68,
+0x40,
+0x64,
+0x65,
+0x62,
+0x69,
+0x61,
+0x6e,
+0x2e,
+0x6f,
+0x72,
+0x67,
+0x30,
+0x20,
+0x17,
+0x0d,
+0x32,
+0x30,
+0x30,
+0x31,
+0x33,
+0x30,
+0x31,
+0x33,
+0x32,
+0x36,
+0x31,
+0x33,
+0x5a,
+0x18,
+0x0f,
+0x32,
+0x31,
+0x32,
+0x30,
+0x30,
+0x31,
+0x30,
+0x36,
+0x31,
+0x33,
+0x32,
+0x36,
+0x31,
+0x33,
+0x5a,
+0x30,
+0x1a,
+0x31,
+0x18,
+0x30,
+0x16,
+0x06,
+0x03,
+0x55,
+0x04,
+0x03,
+0x0c,
+0x0f,
+0x62,
+0x65,
+0x6e,
+0x68,
+0x40,
+0x64,
+0x65,
+0x62,
+0x69,
+0x61,
+0x6e,
+0x2e,
+0x6f,
+0x72,
+0x67,
+0x30,
+0x82,
+0x01,
+0x22,
+0x30,
+0x0d,
+0x06,
+0x09,
+0x2a,
+0x86,
+0x48,
+0x86,
+0xf7,
+0x0d,
+0x01,
+0x01,
+0x01,
+0x05,
+0x00,
+0x03,
+0x82,
+0x01,
+0x0f,
+0x00,
+0x30,
+0x82,
+0x01,
+0x0a,
+0x02,
+0x82,
+0x01,
+0x01,
+0x00,
+0x9d,
+0xe1,
+0x77,
+0xa0,
+0x24,
+0xa0,
+0xd5,
+0x79,
+0x65,
+0x3a,
+0x07,
+0x90,
+0xc9,
+0xf6,
+0xa5,
+0xa6,
+0x1f,
+0x84,
+0x1c,
+0x23,
+0x07,
+0x4b,
+0x4f,
+0xa5,
+0x03,
+0xc6,
+0x0f,
+0xf7,
+0x54,
+0xd5,
+0x8b,
+0x7e,
+0x79,
+0x81,
+0x00,
+0xd2,
+0xe9,
+0x3d,
+0xf4,
+0x97,
+0xfe,
+0x84,
+0xcd,
+0x55,
+0xbd,
+0xc9,
+0x8f,
+0x21,
+0x57,
+0x88,
+0x06,
+0x39,
+0x90,
+0x66,
+0x41,
+0x26,
+0x79,
+0x2c,
+0xca,
+0x3f,
+0x95,
+0x87,
+0x01,
+0x11,
+0x2f,
+0x2f,
+0xb0,
+0xe1,
+0x0b,
+0x43,
+0xfc,
+0x5f,
+0x2f,
+0x4f,
+0x67,
+0x04,
+0xdb,
+0x4d,
+0xb7,
+0x72,
+0x4d,
+0xd1,
+0xc5,
+0x76,
+0x73,
+0x4d,
+0x91,
+0x69,
+0xb0,
+0x71,
+0x17,
+0x36,
+0xea,
+0xab,
+0x0a,
+0x3a,
+0xcd,
+0x95,
+0x9b,
+0x76,
+0x1b,
+0x8e,
+0x21,
+0x17,
+0x8f,
+0xc5,
+0x02,
+0xbf,
+0x24,
+0xc7,
+0xc0,
+0x40,
+0xb1,
+0x3b,
+0xc4,
+0x80,
+0x7c,
+0x71,
+0xa5,
+0x51,
+0xdc,
+0xf7,
+0x3a,
+0x58,
+0x7f,
+0xb1,
+0x07,
+0x81,
+0x8a,
+0x10,
+0xd1,
+0xf6,
+0x93,
+0x17,
+0x71,
+0xe0,
+0xfa,
+0x51,
+0x79,
+0x15,
+0xd4,
+0xd7,
+0x8f,
+0xad,
+0xbd,
+0x6f,
+0x38,
+0xe1,
+0x26,
+0x7d,
+0xbc,
+0xf0,
+0x3e,
+0x80,
+0x89,
+0xb4,
+0xec,
+0x8e,
+0x69,
+0x90,
+0xdb,
+0x97,
+0x8a,
+0xf0,
+0x23,
+0x23,
+0x83,
+0x82,
+0x3b,
+0x6a,
+0xb1,
+0xac,
+0xeb,
+0xe7,
+0x99,
+0x74,
+0x2a,
+0x35,
+0x8e,
+0xa9,
+0x64,
+0xfd,
+0x46,
+0x9e,
+0xe8,
+0xe5,
+0x48,
+0x61,
+0x31,
+0x6e,
+0xe6,
+0xfc,
+0x19,
+0x18,
+0x54,
+0xc3,
+0x1b,
+0x4f,
+0xd6,
+0x00,
+0x44,
+0x87,
+0x1c,
+0x37,
+0x45,
+0xea,
+0xf5,
+0xc9,
+0xcb,
+0x0f,
+0x0c,
+0x55,
+0xec,
+0xcf,
+0x6a,
+0xc2,
+0x45,
+0x26,
+0x23,
+0xa2,
+0x31,
+0x52,
+0x4d,
+0xee,
+0x21,
+0x7d,
+0xfd,
+0x58,
+0x72,
+0xc2,
+0x28,
+0xc5,
+0x8e,
+0xa9,
+0xd0,
+0xee,
+0x01,
+0x77,
+0x08,
+0xa5,
+0xf0,
+0x22,
+0x2b,
+0x47,
+0x79,
+0x2b,
+0xcf,
+0x9a,
+0x46,
+0xb5,
+0x8f,
+0xfd,
+0x64,
+0xa2,
+0xb5,
+0xed,
+0x02,
+0x03,
+0x01,
+0x00,
+0x01,
+0x30,
+0x0d,
+0x06,
+0x09,
+0x2a,
+0x86,
+0x48,
+0x86,
+0xf7,
+0x0d,
+0x01,
+0x01,
+0x0b,
+0x05,
+0x00,
+0x03,
+0x82,
+0x01,
+0x01,
+0x00,
+0x20,
+0x44,
+0xfe,
+0xa9,
+0x9e,
+0xdd,
+0x9b,
+0xea,
+0xce,
+0x25,
+0x75,
+0x08,
+0xf0,
+0x2b,
+0x53,
+0xf7,
+0x5a,
+0x36,
+0x1c,
+0x4a,
+0x23,
+0x7f,
+0xd0,
+0x41,
+0x3c,
+0x12,
+0x2b,
+0xb9,
+0x80,
+0x4e,
+0x8a,
+0x15,
+0x5d,
+0x1f,
+0x40,
+0xa7,
+0x26,
+0x28,
+0x32,
+0xc3,
+0x5b,
+0x06,
+0x28,
+0x2d,
+0x3d,
+0x08,
+0x09,
+0x1e,
+0x01,
+0xe9,
+0x67,
+0xe3,
+0x33,
+0xe6,
+0x15,
+0x45,
+0x39,
+0xee,
+0x17,
+0x83,
+0xdb,
+0x42,
+0xff,
+0x7f,
+0x35,
+0xf4,
+0xac,
+0x16,
+0xdb,
+0xba,
+0xb8,
+0x1a,
+0x20,
+0x21,
+0x41,
+0xff,
+0xf3,
+0x92,
+0xff,
+0x65,
+0x6e,
+0x29,
+0x16,
+0xd0,
+0xbf,
+0x8d,
+0xdf,
+0x48,
+0x2c,
+0x73,
+0x36,
+0x7f,
+0x22,
+0xe6,
+0xee,
+0x78,
+0xb4,
+0x63,
+0x83,
+0x0e,
+0x39,
+0xeb,
+0xaf,
+0x10,
+0x2a,
+0x90,
+0xd3,
+0xfc,
+0xe6,
+0xc3,
+0x8f,
+0x97,
+0x5b,
+0x76,
+0xbf,
+0x9b,
+0xf5,
+0x98,
+0xd2,
+0x53,
+0x06,
+0x8b,
+0xf8,
+0xa4,
+0x04,
+0x9b,
+0x1b,
+0x62,
+0x6a,
+0x9d,
+0xac,
+0xe6,
+0x4b,
+0x0d,
+0xc9,
+0xd7,
+0x56,
+0x63,
+0x15,
+0x01,
+0x38,
+0x8c,
+0xbe,
+0xf1,
+0x44,
+0xc4,
+0x38,
+0x27,
+0xe0,
+0xcf,
+0x72,
+0xd6,
+0x3d,
+0xe4,
+0xf7,
+0x4b,
+0x3b,
+0xd2,
+0xb1,
+0x0c,
+0xd5,
+0x83,
+0x6d,
+0x1e,
+0x10,
+0x04,
+0x69,
+0x29,
+0x88,
+0x69,
+0xe0,
+0x7d,
+0xd7,
+0xdb,
+0xb4,
+0x59,
+0x72,
+0x8d,
+0x9d,
+0x3c,
+0x43,
+0xaf,
+0xc6,
+0x7d,
+0xb7,
+0x21,
+0x15,
+0x52,
+0x8a,
+0xe9,
+0x9b,
+0x6b,
+0x2e,
+0xe8,
+0x27,
+0x3c,
+0x3f,
+0x2d,
+0x84,
+0xfb,
+0x9a,
+0x22,
+0x0a,
+0x9f,
+0x6a,
+0x25,
+0xe6,
+0x39,
+0xe4,
+0x74,
+0x73,
+0xb6,
+0x2a,
+0x70,
+0xaa,
+0x1d,
+0xcb,
+0xcc,
+0xd4,
+0xa0,
+0x1b,
+0x26,
+0x71,
+0x63,
+0x04,
+0xc5,
+0x12,
+0x21,
+0x48,
+0xba,
+0x92,
+0x27,
+0x06,
+0xa8,
+0x3e,
+0x6d,
+0xa1,
+0x43,
+0xa5,
+0xd2,
+0x2a,
+0xf7,
+0xca,
+0xc4,
+0x26,
+0xe8,
+0x5b,
+0x1f,
+0xe4,
+0xdc,
+0x89,
+0xdc,
+0x1f,
+0x04,
+0x79,
+0x3f,
+0x30,
+0x82,
+0x02,
+0xcd,
+0x30,
+0x82,
+0x01,
+0xb5,
+0x02,
+0x14,
+0x3a,
+0xbb,
+0xc6,
+0xec,
+0x14,
+0x6e,
+0x09,
+0xd1,
+0xb6,
+0x01,
+0x6a,
+0xb9,
+0xd6,
+0xcf,
+0x71,
+0xdd,
+0x23,
+0x3f,
+0x03,
+0x28,
+0x30,
+0x0d,
+0x06,
+0x09,
+0x2a,
+0x86,
+0x48,
+0x86,
+0xf7,
+0x0d,
+0x01,
+0x01,
+0x0b,
+0x05,
+0x00,
+0x30,
+0x22,
+0x31,
+0x20,
+0x30,
+0x1e,
+0x06,
+0x03,
+0x55,
+0x04,
+0x03,
+0x0c,
+0x17,
+0x72,
+0x6f,
+0x6d,
+0x61,
+0x69,
+0x6e,
+0x2e,
+0x70,
+0x65,
+0x72,
+0x69,
+0x65,
+0x72,
+0x40,
+0x67,
+0x6d,
+0x61,
+0x69,
+0x6c,
+0x2e,
+0x63,
+0x6f,
+0x6d,
+0x30,
+0x20,
+0x17,
+0x0d,
+0x32,
+0x30,
+0x30,
+0x32,
+0x32,
+0x34,
+0x31,
+0x39,
+0x30,
+0x31,
+0x34,
+0x34,
+0x5a,
+0x18,
+0x0f,
+0x32,
+0x31,
+0x32,
+0x30,
+0x30,
+0x31,
+0x33,
+0x31,
+0x31,
+0x39,
+0x30,
+0x31,
+0x34,
+0x34,
+0x5a,
+0x30,
+0x22,
+0x31,
+0x20,
+0x30,
+0x1e,
+0x06,
+0x03,
+0x55,
+0x04,
+0x03,
+0x0c,
+0x17,
+0x72,
+0x6f,
+0x6d,
+0x61,
+0x69,
+0x6e,
+0x2e,
+0x70,
+0x65,
+0x72,
+0x69,
+0x65,
+0x72,
+0x40,
+0x67,
+0x6d,
+0x61,
+0x69,
+0x6c,
+0x2e,
+0x63,
+0x6f,
+0x6d,
+0x30,
+0x82,
+0x01,
+0x22,
+0x30,
+0x0d,
+0x06,
+0x09,
+0x2a,
+0x86,
+0x48,
+0x86,
+0xf7,
+0x0d,
+0x01,
+0x01,
+0x01,
+0x05,
+0x00,
+0x03,
+0x82,
+0x01,
+0x0f,
+0x00,
+0x30,
+0x82,
+0x01,
+0x0a,
+0x02,
+0x82,
+0x01,
+0x01,
+0x00,
+0xf0,
+0xb8,
+0x4f,
+0x3f,
+0x70,
+0x78,
+0xf8,
+0x74,
+0x45,
+0xa2,
+0x28,
+0xaf,
+0x04,
+0x75,
+0x04,
+0xa3,
+0xf3,
+0xa7,
+0xc7,
+0x04,
+0xac,
+0xb6,
+0xe1,
+0xfc,
+0xe1,
+0xc0,
+0x3d,
+0xe0,
+0x26,
+0x90,
+0x8a,
+0x45,
+0x60,
+0xc4,
+0x75,
+0xf3,
+0x1a,
+0x33,
+0x37,
+0x56,
+0x7d,
+0x30,
+0x07,
+0x75,
+0x0e,
+0xa6,
+0x79,
+0x06,
+0x95,
+0x9d,
+0x17,
+0x3c,
+0x09,
+0xa9,
+0x7f,
+0xab,
+0x95,
+0x5d,
+0xed,
+0xe0,
+0x75,
+0x26,
+0x2f,
+0x65,
+0x65,
+0xcd,
+0x61,
+0xb1,
+0x33,
+0x27,
+0x67,
+0x41,
+0xa1,
+0x01,
+0x13,
+0xe9,
+0x13,
+0x6a,
+0x6d,
+0x4e,
+0x98,
+0xe1,
+0x9e,
+0x7b,
+0x0b,
+0x5b,
+0x44,
+0xef,
+0x68,
+0x5a,
+0x6f,
+0x7d,
+0x97,
+0xa1,
+0x33,
+0x22,
+0x97,
+0x12,
+0x21,
+0x09,
+0x8f,
+0x90,
+0xe0,
+0x25,
+0x94,
+0xdd,
+0x8a,
+0x3a,
+0xf7,
+0x4a,
+0x60,
+0x04,
+0x26,
+0x6d,
+0x00,
+0x82,
+0xe4,
+0xcf,
+0x64,
+0x1c,
+0x79,
+0x15,
+0x24,
+0xf2,
+0x42,
+0x86,
+0xf5,
+0x10,
+0x86,
+0xac,
+0x20,
+0x88,
+0x90,
+0x87,
+0xdf,
+0x8c,
+0x37,
+0x7c,
+0xbf,
+0x35,
+0xd5,
+0x6f,
+0x9f,
+0x77,
+0xc3,
+0xcd,
+0x69,
+0x25,
+0x06,
+0xc2,
+0x65,
+0x51,
+0x71,
+0x89,
+0x7f,
+0x6e,
+0x4d,
+0xe5,
+0xd5,
+0x8a,
+0x36,
+0x1a,
+0xad,
+0xc1,
+0x18,
+0xd6,
+0x14,
+0x42,
+0x87,
+0xf0,
+0x93,
+0x83,
+0xf1,
+0x99,
+0x74,
+0xc4,
+0x13,
+0xaa,
+0x3b,
+0x66,
+0x85,
+0x6f,
+0xe0,
+0xbc,
+0x5f,
+0xb6,
+0x40,
+0xa6,
+0x41,
+0x06,
+0x0a,
+0xba,
+0x0e,
+0xe9,
+0x32,
+0x44,
+0x10,
+0x39,
+0x53,
+0xcd,
+0xbf,
+0xf3,
+0xd3,
+0x26,
+0xf6,
+0xb6,
+0x2b,
+0x40,
+0x2e,
+0xb9,
+0x88,
+0xc1,
+0xf4,
+0xe3,
+0xa0,
+0x28,
+0x77,
+0x4f,
+0xba,
+0xa8,
+0xca,
+0x9c,
+0x05,
+0xba,
+0x88,
+0x96,
+0x99,
+0x54,
+0x89,
+0xa2,
+0x8d,
+0xf3,
+0x73,
+0xa1,
+0x8c,
+0x4a,
+0xa8,
+0x71,
+0xee,
+0x2e,
+0xd2,
+0x83,
+0x14,
+0x48,
+0xbd,
+0x98,
+0xc6,
+0xce,
+0xdc,
+0xa8,
+0xa3,
+0x97,
+0x2e,
+0x40,
+0x16,
+0x2f,
+0x02,
+0x03,
+0x01,
+0x00,
+0x01,
+0x30,
+0x0d,
+0x06,
+0x09,
+0x2a,
+0x86,
+0x48,
+0x86,
+0xf7,
+0x0d,
+0x01,
+0x01,
+0x0b,
+0x05,
+0x00,
+0x03,
+0x82,
+0x01,
+0x01,
+0x00,
+0x76,
+0x5d,
+0x03,
+0x3d,
+0xb6,
+0x96,
+0x00,
+0x1b,
+0x6e,
+0x0c,
+0xdd,
+0xbb,
+0xc8,
+0xdf,
+0xbc,
+0xeb,
+0x6c,
+0x01,
+0x40,
+0x1a,
+0x2b,
+0x07,
+0x60,
+0xa1,
+0x1a,
+0xe1,
+0x43,
+0x57,
+0xfa,
+0xbe,
+0xde,
+0xbb,
+0x8f,
+0x73,
+0xf3,
+0x92,
+0xa2,
+0xaa,
+0x83,
+0x01,
+0xc1,
+0x17,
+0xe4,
+0x9d,
+0x09,
+0x41,
+0xe0,
+0x32,
+0x33,
+0x97,
+0x4b,
+0xf2,
+0xdc,
+0x0f,
+0x8b,
+0xa8,
+0xb8,
+0x5a,
+0x04,
+0x86,
+0xf6,
+0x71,
+0xa1,
+0x97,
+0xd0,
+0x54,
+0x56,
+0x10,
+0x8e,
+0x54,
+0x99,
+0x0d,
+0x2a,
+0xa9,
+0xaf,
+0x1b,
+0x55,
+0x59,
+0x06,
+0x2b,
+0xa4,
+0x5f,
+0xb1,
+0x54,
+0xa6,
+0xec,
+0xc7,
+0xd6,
+0x43,
+0xee,
+0x86,
+0x2c,
+0x9b,
+0x18,
+0x9d,
+0x8f,
+0x00,
+0x82,
+0xc1,
+0x88,
+0x61,
+0x16,
+0x85,
+0x3c,
+0x17,
+0x56,
+0xfe,
+0x6a,
+0xa0,
+0x7a,
+0x68,
+0xc5,
+0x7b,
+0x3d,
+0x3c,
+0xb6,
+0x13,
+0x18,
+0x99,
+0x6d,
+0x74,
+0x65,
+0x13,
+0x67,
+0xb7,
+0xfc,
+0x5a,
+0x44,
+0x48,
+0x72,
+0xa0,
+0x73,
+0xb8,
+0xff,
+0x02,
+0x9d,
+0x7c,
+0x5b,
+0xf9,
+0x7c,
+0x75,
+0x0a,
+0x3c,
+0x81,
+0x80,
+0x3c,
+0x41,
+0xf2,
+0xd5,
+0xfa,
+0x3d,
+0x1f,
+0xe3,
+0xda,
+0x8c,
+0xa5,
+0x17,
+0x1f,
+0x53,
+0x1a,
+0x75,
+0xad,
+0x4e,
+0x11,
+0x1c,
+0x07,
+0xec,
+0x0a,
+0x69,
+0xfd,
+0x33,
+0xfa,
+0x32,
+0x7e,
+0x66,
+0xf5,
+0x29,
+0xe8,
+0x4d,
+0x8a,
+0xfa,
+0x0d,
+0x4b,
+0x68,
+0xc3,
+0x95,
+0x11,
+0xba,
+0x6f,
+0x1e,
+0x07,
+0x8c,
+0x85,
+0xc7,
+0xc7,
+0xc9,
+0xc1,
+0x30,
+0xa3,
+0x70,
+0xb0,
+0xa1,
+0xe0,
+0xd5,
+0x85,
+0x15,
+0x94,
+0x77,
+0xc1,
+0x1c,
+0x91,
+0xf1,
+0x5f,
+0x50,
+0xcd,
+0x2c,
+0x57,
+0x4b,
+0x22,
+0x4f,
+0xee,
+0x95,
+0xd7,
+0xa7,
+0xa4,
+0x59,
+0x62,
+0xae,
+0xb9,
+0xbf,
+0xd7,
+0x63,
+0x5a,
+0x04,
+0xfc,
+0x24,
+0x11,
+0xae,
+0x34,
+0x4b,
+0xf4,
+0x0c,
+0x9f,
+0x0b,
+0x59,
+0x7d,
+0x27,
+0x39,
+0x54,
+0x69,
+0x4f,
+0xfd,
+0x6e,
+0x44,
+0x9f,
+0x21,
diff --git a/scripts/Makefile.build b/scripts/Makefile.build
index cee3f5b7b937..cabfcee543f9 100644
--- a/scripts/Makefile.build
+++ b/scripts/Makefile.build
@@ -41,6 +41,20 @@ include $(srctree)/scripts/Makefile.compiler
 include $(kbuild-file)
 include $(srctree)/scripts/Makefile.lib
 
+# Do not include hostprogs rules unless needed.
+# $(sort ...) is used here to remove duplicated words and excessive spaces.
+hostprogs := $(sort $(hostprogs))
+ifneq ($(hostprogs),)
+include $(srctree)/scripts/Makefile.host
+endif
+
+# Do not include userprogs rules unless needed.
+# $(sort ...) is used here to remove duplicated words and excessive spaces.
+userprogs := $(sort $(userprogs))
+ifneq ($(userprogs),)
+include $(srctree)/scripts/Makefile.userprogs
+endif
+
 ifndef obj
 $(warning kbuild: Makefile.build is included improperly)
 endif
@@ -57,6 +71,7 @@ endif
 # subdir-builtin and subdir-modorder may contain duplications. Use $(sort ...)
 subdir-builtin := $(sort $(filter %/built-in.a, $(real-obj-y)))
 subdir-modorder := $(sort $(filter %/modules.order, $(obj-m)))
+subdir-dtbslist := $(sort $(filter %/dtbs-list, $(dtb-y)))
 
 targets-for-builtin := $(extra-y)
 
@@ -273,15 +288,10 @@ rust_common_cmd = \
 # would not match each other.
 
 quiet_cmd_rustc_o_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@
-      cmd_rustc_o_rs = $(rust_common_cmd) --emit=obj=$@ $< $(cmd_objtool)
-
-define rule_rustc_o_rs
-	$(call cmd_and_fixdep,rustc_o_rs)
-	$(call cmd,gen_objtooldep)
-endef
+      cmd_rustc_o_rs = $(rust_common_cmd) --emit=obj=$@ $<
 
 $(obj)/%.o: $(obj)/%.rs FORCE
-	+$(call if_changed_rule,rustc_o_rs)
+	+$(call if_changed_dep,rustc_o_rs)
 
 quiet_cmd_rustc_rsi_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@
       cmd_rustc_rsi_rs = \
@@ -353,7 +363,7 @@ $(obj)/%.o: $(obj)/%.S FORCE
 
 targets += $(filter-out $(subdir-builtin), $(real-obj-y))
 targets += $(filter-out $(subdir-modorder), $(real-obj-m))
-targets += $(lib-y) $(always-y)
+targets += $(real-dtb-y) $(lib-y) $(always-y)
 
 # Linker scripts preprocessor (.lds.S -> .lds)
 # ---------------------------------------------------------------------------
@@ -379,6 +389,7 @@ $(obj)/%.asn1.c $(obj)/%.asn1.h: $(src)/%.asn1 $(objtree)/scripts/asn1_compiler
 # To build objects in subdirs, we need to descend into the directories
 $(subdir-builtin): $(obj)/%/built-in.a: $(obj)/% ;
 $(subdir-modorder): $(obj)/%/modules.order: $(obj)/% ;
+$(subdir-dtbslist): $(obj)/%/dtbs-list: $(obj)/% ;
 
 #
 # Rule to compile a set of .o files into one .a file (without symbol table)
@@ -394,8 +405,12 @@ quiet_cmd_ar_builtin = AR      $@
 $(obj)/built-in.a: $(real-obj-y) FORCE
 	$(call if_changed,ar_builtin)
 
-# This is a list of build artifacts from the current Makefile and its
-# sub-directories. The timestamp should be updated when any of the member files.
+#
+# Rule to create modules.order and dtbs-list
+#
+# This is a list of build artifacts (module or dtb) from the current Makefile
+# and its sub-directories. The timestamp should be updated when any of the
+# member files.
 
 cmd_gen_order = { $(foreach m, $(real-prereqs), \
 	$(if $(filter %/$(notdir $@), $m), cat $m, echo $m);) :; } \
@@ -404,6 +419,9 @@ cmd_gen_order = { $(foreach m, $(real-prereqs), \
 $(obj)/modules.order: $(obj-m) FORCE
 	$(call if_changed,gen_order)
 
+$(obj)/dtbs-list: $(dtb-y) $(dtbo-y) FORCE
+	$(call if_changed,gen_order)
+
 #
 # Rule to compile a set of .o files into one .a file (with symbol table)
 #
@@ -432,22 +450,23 @@ intermediate_targets = $(foreach sfx, $(2), \
 				$(patsubst %$(strip $(1)),%$(sfx), \
 					$(filter %$(strip $(1)), $(targets))))
 # %.asn1.o <- %.asn1.[ch] <- %.asn1
-targets += $(call intermediate_targets, .asn1.o, .asn1.c .asn1.h)
+# %.dtb.o <- %.dtb.S <- %.dtb <- %.dts
+# %.dtbo.o <- %.dtbo.S <- %.dtbo <- %.dtso
+# %.lex.o <- %.lex.c <- %.l
+# %.tab.o <- %.tab.[ch] <- %.y
+targets += $(call intermediate_targets, .asn1.o, .asn1.c .asn1.h) \
+	   $(call intermediate_targets, .dtb.o, .dtb.S .dtb) \
+	   $(call intermediate_targets, .dtbo.o, .dtbo.S .dtbo) \
+	   $(call intermediate_targets, .lex.o, .lex.c) \
+	   $(call intermediate_targets, .tab.o, .tab.c .tab.h)
 
-# Include additional build rules when necessary
+# Build
 # ---------------------------------------------------------------------------
 
-# $(sort ...) is used here to remove duplicated words and excessive spaces.
-hostprogs := $(sort $(hostprogs))
-ifneq ($(hostprogs),)
-include $(srctree)/scripts/Makefile.host
-endif
-
-# $(sort ...) is used here to remove duplicated words and excessive spaces.
-userprogs := $(sort $(userprogs))
-ifneq ($(userprogs),)
-include $(srctree)/scripts/Makefile.userprogs
-endif
+$(obj)/: $(if $(KBUILD_BUILTIN), $(targets-for-builtin)) \
+	 $(if $(KBUILD_MODULES), $(targets-for-modules)) \
+	 $(subdir-ym) $(always-y)
+	@:
 
 # Single targets
 # ---------------------------------------------------------------------------
@@ -478,20 +497,6 @@ FORCE:
 targets += $(filter-out $(single-subdir-goals), $(MAKECMDGOALS))
 targets := $(filter-out $(PHONY), $(targets))
 
-# Now that targets is fully known, include dtb rules if needed
-ifneq ($(need-dtbslist)$(dtb-y)$(dtb-)$(filter %.dtb %.dtb.o %.dtbo.o,$(targets)),)
-include $(srctree)/scripts/Makefile.dtbs
-endif
-
-# Build
-# Needs to be after the include of Makefile.dtbs, which updates always-y
-# ---------------------------------------------------------------------------
-
-$(obj)/: $(if $(KBUILD_BUILTIN), $(targets-for-builtin)) \
-	 $(if $(KBUILD_MODULES), $(targets-for-modules)) \
-	 $(subdir-ym) $(always-y)
-	@:
-
 # Read all saved command lines and dependencies for the $(targets) we
 # may be building above, using $(if_changed{,_dep}). As an
 # optimization, we don't need to read them if the target does not
diff --git a/scripts/Makefile.dtbinst b/scripts/Makefile.dtbinst
index 9d920419a62c..4be9ebd3995c 100644
--- a/scripts/Makefile.dtbinst
+++ b/scripts/Makefile.dtbinst
@@ -31,9 +31,14 @@ $(dst)/%: $(obj)/$(1)%
 	$$(call cmd,dtb_install)
 endef
 
-$(foreach d, $(sort $(dir $(dtbs))), $(eval $(call gen_install_rules,$(d))))
+define overlays_install_rules
+$(dst)/overlays/%: $(obj)/$(1)%
+	$$(call cmd,dtb_install)
+endef
+
+$(foreach d, $(sort $(dir $(dtbs))), $(if $(findstring "overlays/","$(d)"),$(eval $(call overlays_install_rules,$(d))),$(eval $(call gen_install_rules,$(d)))))
 
-dtbs := $(notdir $(dtbs))
+dtbs := $(foreach d, $(dtbs), $(if $(findstring overlays/,$(d)),$(d),$(notdir $(d))))
 
 endif # CONFIG_ARCH_WANT_FLAT_DTB_INSTALL
 
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index 85e41c68c2c5..0c2f5bd53cbf 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -45,6 +45,11 @@ else
 obj-y		:= $(filter-out %/, $(obj-y))
 endif
 
+ifdef need-dtbslist
+dtb-y		+= $(addsuffix /dtbs-list, $(subdir-ym))
+always-y	+= dtbs-list
+endif
+
 # Expand $(foo-objs) $(foo-y) etc. by replacing their individuals
 suffix-search = $(strip $(foreach s, $3, $($(1:%$(strip $2)=%$s))))
 # List composite targets that are constructed by combining other targets
@@ -75,6 +80,19 @@ always-y += $(hostprogs-always-y) $(hostprogs-always-m)
 userprogs += $(userprogs-always-y) $(userprogs-always-m)
 always-y += $(userprogs-always-y) $(userprogs-always-m)
 
+# DTB
+# If CONFIG_OF_ALL_DTBS is enabled, all DT blobs are built
+dtb-$(CONFIG_OF_ALL_DTBS)       += $(dtb-)
+
+# Composite DTB (i.e. DTB constructed by overlay)
+multi-dtb-y := $(call multi-search, $(dtb-y), .dtb, -dtbs)
+# Primitive DTB compiled from *.dts
+real-dtb-y := $(call real-search, $(dtb-y), .dtb, -dtbs)
+# Base DTB that overlay is applied onto
+base-dtb-y := $(filter %.dtb, $(call real-search, $(multi-dtb-y), .dtb, -dtbs))
+
+always-y			+= $(dtb-y)
+
 # Add subdir path
 
 ifneq ($(obj),.)
@@ -86,6 +104,10 @@ lib-y		:= $(addprefix $(obj)/,$(lib-y))
 real-obj-y	:= $(addprefix $(obj)/,$(real-obj-y))
 real-obj-m	:= $(addprefix $(obj)/,$(real-obj-m))
 multi-obj-m	:= $(addprefix $(obj)/, $(multi-obj-m))
+dtb-y		:= $(addprefix $(obj)/, $(dtb-y))
+dtbo-y		:= $(addprefix $(obj)/, $(dtbo-y))
+multi-dtb-y	:= $(addprefix $(obj)/, $(multi-dtb-y))
+real-dtb-y	:= $(addprefix $(obj)/, $(real-dtb-y))
 subdir-ym	:= $(addprefix $(obj)/,$(subdir-ym))
 endif
 
@@ -146,9 +168,6 @@ ifneq ($(CONFIG_KASAN_HW_TAGS),y)
 _c_flags += $(if $(patsubst n%,, \
 		$(KASAN_SANITIZE_$(target-stem).o)$(KASAN_SANITIZE)$(is-kernel-object)), \
 		$(CFLAGS_KASAN), $(CFLAGS_KASAN_NOSANITIZE))
-_rust_flags += $(if $(patsubst n%,, \
-		$(KASAN_SANITIZE_$(target-stem).o)$(KASAN_SANITIZE)$(is-kernel-object)), \
-		$(RUSTFLAGS_KASAN))
 endif
 endif
 
@@ -220,7 +239,7 @@ modkern_rustflags =                                              \
 
 modkern_aflags = $(if $(part-of-module),				\
 			$(KBUILD_AFLAGS_MODULE) $(AFLAGS_MODULE),	\
-			$(KBUILD_AFLAGS_KERNEL) $(AFLAGS_KERNEL) $(modfile_flags))
+			$(KBUILD_AFLAGS_KERNEL) $(AFLAGS_KERNEL))
 
 c_flags        = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE)     \
 		 -include $(srctree)/include/linux/compiler_types.h       \
@@ -230,13 +249,19 @@ c_flags        = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE)     \
 rust_flags     = $(_rust_flags) $(modkern_rustflags) @$(objtree)/include/generated/rustc_cfg
 
 a_flags        = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE)     \
-		 $(_a_flags) $(modkern_aflags) $(modname_flags)
+		 $(_a_flags) $(modkern_aflags)
 
 cpp_flags      = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE)     \
 		 $(_cpp_flags)
 
 ld_flags       = $(KBUILD_LDFLAGS) $(ldflags-y) $(LDFLAGS_$(@F))
 
+DTC_INCLUDE    := $(srctree)/scripts/dtc/include-prefixes
+
+dtc_cpp_flags  = -Wp,-MMD,$(depfile).pre.tmp -nostdinc                    \
+		 $(addprefix -I,$(DTC_INCLUDE))                          \
+		 -undef -D__DTS__
+
 ifdef CONFIG_OBJTOOL
 
 objtool := $(objtree)/tools/objtool/objtool
@@ -326,6 +351,114 @@ cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@
 quiet_cmd_gzip = GZIP    $@
       cmd_gzip = cat $(real-prereqs) | $(KGZIP) -n -f -9 > $@
 
+# DTC
+# ---------------------------------------------------------------------------
+DTC ?= $(objtree)/scripts/dtc/dtc
+DTC_FLAGS += \
+	-Wno-unique_unit_address
+
+# Disable noisy checks by default
+ifeq ($(findstring 1,$(KBUILD_EXTRA_WARN)),)
+DTC_FLAGS += -Wno-unit_address_vs_reg \
+	-Wno-gpios_property \
+	-Wno-avoid_unnecessary_addr_size \
+	-Wno-alias_paths \
+	-Wno-graph_child_address \
+	-Wno-simple_bus_reg
+else
+DTC_FLAGS += \
+        -Wunique_unit_address_if_enabled
+endif
+
+ifneq ($(findstring 2,$(KBUILD_EXTRA_WARN)),)
+DTC_FLAGS += -Wnode_name_chars_strict \
+	-Wproperty_name_chars_strict \
+	-Wunique_unit_address
+endif
+
+DTC_FLAGS += $(DTC_FLAGS_$(target-stem))
+
+# Set -@ if the target is a base DTB that overlay is applied onto
+DTC_FLAGS += $(if $(filter $(patsubst $(obj)/%,%,$@), $(base-dtb-y)), -@)
+
+# Generate an assembly file to wrap the output of the device tree compiler
+quiet_cmd_wrap_S_dtb = WRAP    $@
+      cmd_wrap_S_dtb = {								\
+		symbase=__$(patsubst .%,%,$(suffix $<))_$(subst -,_,$(notdir $*));	\
+		echo '\#include <asm-generic/vmlinux.lds.h>';				\
+		echo '.section .dtb.init.rodata,"a"';					\
+		echo '.balign STRUCT_ALIGNMENT';					\
+		echo ".global $${symbase}_begin";					\
+		echo "$${symbase}_begin:";						\
+		echo '.incbin "$<" ';							\
+		echo ".global $${symbase}_end";						\
+		echo "$${symbase}_end:";						\
+		echo '.balign STRUCT_ALIGNMENT';					\
+	} > $@
+
+$(obj)/%.dtb.S: $(obj)/%.dtb FORCE
+	$(call if_changed,wrap_S_dtb)
+
+$(obj)/%.dtbo.S: $(obj)/%.dtbo FORCE
+	$(call if_changed,wrap_S_dtb)
+
+quiet_dtb_check_tag = $(if $(dtb-check-enabled),[C],   )
+cmd_dtb_check = $(if $(dtb-check-enabled),; $(DT_CHECKER) $(DT_CHECKER_FLAGS) -u $(srctree)/$(DT_BINDING_DIR) -p $(DT_TMP_SCHEMA) $@ || true)
+
+quiet_cmd_dtc = DTC $(quiet_dtb_check_tag) $@
+cmd_dtc = $(HOSTCC) -E $(dtc_cpp_flags) -x assembler-with-cpp -o $(dtc-tmp) $< ; \
+	$(DTC) -o $@ -b 0 \
+		$(addprefix -i,$(dir $<) $(DTC_INCLUDE)) $(DTC_FLAGS) \
+		-d $(depfile).dtc.tmp $(dtc-tmp) ; \
+	cat $(depfile).pre.tmp $(depfile).dtc.tmp > $(depfile) \
+	$(cmd_dtb_check)
+
+# NOTE:
+# Do not replace $(filter %.dtb %.dtbo, $^) with $(real-prereqs). When a single
+# DTB is turned into a multi-blob DTB, $^ will contain header file dependencies
+# recorded in the .*.cmd file.
+quiet_cmd_fdtoverlay = OVL $(quiet_dtb_check_tag) $@
+      cmd_fdtoverlay = $(objtree)/scripts/dtc/fdtoverlay -o $@ -i $(filter %.dtb %.dtbo, $^) $(cmd_dtb_check)
+
+$(multi-dtb-y): FORCE
+	$(call if_changed,fdtoverlay)
+$(call multi_depend, $(multi-dtb-y), .dtb, -dtbs)
+
+ifneq ($(CHECK_DTBS),)
+DT_CHECKER ?= dt-validate
+DT_CHECKER_FLAGS ?= $(if $(DT_SCHEMA_FILES),-l $(DT_SCHEMA_FILES),-m)
+DT_BINDING_DIR := Documentation/devicetree/bindings
+DT_TMP_SCHEMA := $(objtree)/$(DT_BINDING_DIR)/processed-schema.json
+dtb-check-enabled = $(if $(filter %.dtb, $@),y)
+endif
+
+$(obj)/%.dtb: $(obj)/%.dts $(DTC) $(DT_TMP_SCHEMA) FORCE
+	$(call if_changed_dep,dtc)
+
+$(obj)/%.dtbo: $(src)/%.dtso $(DTC) FORCE
+	$(call if_changed_dep,dtc)
+
+quiet_cmd_dtco = DTCO    $@
+cmd_dtco = mkdir -p $(dir ${dtc-tmp}) ; \
+	$(CPP) $(dtc_cpp_flags) -x assembler-with-cpp -o $(dtc-tmp) $< ; \
+	$(DTC) -@ -H epapr -O dtb -o $@ -b 0 \
+		-i $(dir $<) $(DTC_FLAGS) \
+		-Wno-interrupts_property \
+		-Wno-label_is_string \
+		-Wno-reg_format \
+		-Wno-pci_device_bus_num \
+		-Wno-i2c_bus_reg \
+		-Wno-spi_bus_reg \
+		-Wno-avoid_default_addr_size \
+		-Wno-interrupt_provider \
+		-d $(depfile).dtc.tmp $(dtc-tmp) ; \
+	cat $(depfile).pre.tmp $(depfile).dtc.tmp > $(depfile)
+
+$(obj)/%.dtbo: $(src)/%-overlay.dts FORCE
+	$(call if_changed_dep,dtco)
+
+dtc-tmp = $(subst $(comma),_,$(dot-target).dts.tmp)
+
 # Bzip2
 # ---------------------------------------------------------------------------
 
@@ -418,17 +551,14 @@ quiet_cmd_fit = FIT     $@
 
 # XZ
 # ---------------------------------------------------------------------------
-# Use xzkern or xzkern_with_size to compress the kernel image and xzmisc to
-# compress other things.
+# Use xzkern to compress the kernel image and xzmisc to compress other things.
 #
 # xzkern uses a big LZMA2 dictionary since it doesn't increase memory usage
 # of the kernel decompressor. A BCJ filter is used if it is available for
-# the target architecture.
-#
-# xzkern_with_size also appends uncompressed size of the data using
-# size_append. The .xz format has the size information available at the end
-# of the file too, but it's in more complex format and it's good to avoid
-# changing the part of the boot code that reads the uncompressed size.
+# the target architecture. xzkern also appends uncompressed size of the data
+# using size_append. The .xz format has the size information available at
+# the end of the file too, but it's in more complex format and it's good to
+# avoid changing the part of the boot code that reads the uncompressed size.
 # Note that the bytes added by size_append will make the xz tool think that
 # the file is corrupt. This is expected.
 #
diff --git a/scripts/dtc/checks.c b/scripts/dtc/checks.c
index 6e06aeab5503..17ae52570058 100644
--- a/scripts/dtc/checks.c
+++ b/scripts/dtc/checks.c
@@ -724,7 +724,7 @@ static void check_alias_paths(struct check *c, struct dt_info *dti,
 			continue;
 		}
 		if (strspn(prop->name, LOWERCASE DIGITS "-") != strlen(prop->name))
-			FAIL(c, dti, node, "aliases property name must include only lowercase and '-'");
+		    FAIL(c, dti, node, "aliases property name (%s) must include only lowercase and '-'", prop->name);
 	}
 }
 WARNING(alias_paths, check_alias_paths, NULL);
diff --git a/sound/drivers/Kconfig b/sound/drivers/Kconfig
index 6debd8e95cb7..d6fd0c6be56a 100644
--- a/sound/drivers/Kconfig
+++ b/sound/drivers/Kconfig
@@ -263,4 +263,23 @@ config SND_AC97_POWER_SAVE_DEFAULT
 
 	  See SND_AC97_POWER_SAVE for more details.
 
+config SND_PIMIDI
+	tristate "Pimidi driver"
+	depends on SND_SEQUENCER && CRC8
+	select SND_RAWMIDI
+	help
+	  Say Y here to include support for Blokas Pimidi.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-pimidi.
+
+config SND_PISOUND_MICRO
+	tristate "Pisound Micro driver"
+	depends on SND_SEQUENCER && CRC8
+	help
+	  Say Y here to include support for Blokas Pisound Micro.
+
+	  To compile this driver as modules, choose M here: the modules
+	  will be called snd-soc-upisnd-ctrl and snd-soc-upisnd-codec.
+
 endif	# SND_DRIVERS
diff --git a/sound/drivers/Makefile b/sound/drivers/Makefile
index a08bdd70ec9c..1cce9943ddef 100644
--- a/sound/drivers/Makefile
+++ b/sound/drivers/Makefile
@@ -9,6 +9,7 @@ snd-aloop-y := aloop.o
 snd-mtpav-y := mtpav.o
 snd-mts64-y := mts64.o
 snd-pcmtest-y := pcmtest.o
+snd-pimidi-y := pimidi.o
 snd-portman2x4-y := portman2x4.o
 snd-serial-u16550-y := serial-u16550.o
 snd-serial-generic-y := serial-generic.o
@@ -23,6 +24,7 @@ obj-$(CONFIG_SND_SERIAL_U16550) += snd-serial-u16550.o
 obj-$(CONFIG_SND_SERIAL_GENERIC) += snd-serial-generic.o
 obj-$(CONFIG_SND_MTPAV) += snd-mtpav.o
 obj-$(CONFIG_SND_MTS64) += snd-mts64.o
+obj-$(CONFIG_SND_PIMIDI) += snd-pimidi.o
 obj-$(CONFIG_SND_PORTMAN2X4) += snd-portman2x4.o
 
-obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/ pcsp/
+obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/ pcsp/ upisnd/
diff --git a/sound/drivers/pimidi.c b/sound/drivers/pimidi.c
new file mode 100644
index 000000000000..c399707425b7
--- /dev/null
+++ b/sound/drivers/pimidi.c
@@ -0,0 +1,1113 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pimidi Linux kernel module.
+ * Copyright (C) 2017-2024  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/completion.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/irqdesc.h>
+#include <linux/bitops.h>
+#include <linux/of_irq.h>
+#include <linux/kfifo.h>
+#include <linux/list.h>
+#include <linux/workqueue.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/mutex.h>
+#include <linux/refcount.h>
+#include <linux/crc8.h>
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/rawmidi.h>
+#include <sound/asequencer.h>
+#include <sound/info.h>
+
+#define PIMIDI_LOG_IMPL(instance, log_func, msg, ...) log_func("pimidi(%s)[%c]: " msg "\n", \
+	__func__, (instance) ? (instance)->d + '0' : 'G', ## __VA_ARGS__)
+
+#ifdef PIMIDI_DEBUG
+#	define printd(instance, ...) PIMIDI_LOG_IMPL(instance, pr_alert, __VA_ARGS__)
+#	define printd_rl(instance, ...) PIMIDI_LOG_IMPL(instance, pr_alert_ratelimited, __VA_ARGS__)
+#	define printd_g(...) printd((struct pimidi_instance *)NULL, __VA_ARGS__)
+#else
+#	define printd(instance, ...) do {} while (0)
+#	define printd_rl(instance, ...) do {} while (0)
+#	define printd_g(...) do {} while (0)
+#endif
+
+#define printe(instance, ...) PIMIDI_LOG_IMPL(instance, pr_err, __VA_ARGS__)
+#define printe_rl(instance, ...) PIMIDI_LOG_IMPL(instance, pr_err_ratelimited, __VA_ARGS__)
+#define printi(instance, ...) PIMIDI_LOG_IMPL(instance, pr_info, __VA_ARGS__)
+#define printw(instance, ...) PIMIDI_LOG_IMPL(instance, pr_warn, __VA_ARGS__)
+#define printw_rl(instance, ...) PIMIDI_LOG_IMPL(instance, pr_warn_ratelimited, __VA_ARGS__)
+
+#define printe_g(...) printe((struct pimidi_instance *)NULL, __VA_ARGS__)
+#define printi_g(...) printi((struct pimidi_instance *)NULL, __VA_ARGS__)
+
+DECLARE_CRC8_TABLE(pimidi_crc8_table);
+enum { PIMIDI_CRC8_POLYNOMIAL = 0x83 };
+enum { PIMIDI_MAX_DEVICES = 4 };
+enum { PIMIDI_MAX_PACKET_SIZE = 17 };
+enum { PIMIDI_PORTS = 2 };
+
+struct pimidi_shared {
+	// lock protects the shared reset_gpio and devices list.
+	struct mutex            lock;
+	struct gpio_desc        *reset_gpio;
+	struct workqueue_struct *work_queue;
+	struct list_head        devices;
+};
+
+static struct pimidi_shared pimidi_global = {
+	.devices = LIST_HEAD_INIT(pimidi_global.devices),
+};
+
+struct pimidi_version_t {
+	u8 hwrev;
+	u8 major;
+	u8 minor;
+	u8 build;
+};
+
+enum { PIMIDI_IN_FIFO_SIZE = 4096 };
+
+struct pimidi_midi_port {
+	// in_lock protects the input substream.
+	struct mutex                      in_lock;
+	// out_lock protects the output substream.
+	struct mutex                      out_lock;
+	DECLARE_KFIFO(in_fifo, uint8_t, PIMIDI_IN_FIFO_SIZE);
+	unsigned int                      last_output_at;
+	unsigned int                      output_buffer_used_in_millibytes;
+	struct work_struct                in_handler;
+	struct delayed_work               out_handler;
+	unsigned long                     enabled_streams;
+	unsigned int                      tx_cnt;
+	unsigned int                      rx_cnt;
+};
+
+struct pimidi_instance {
+	struct list_head                  list;
+	struct i2c_client                 *i2c_client;
+	struct pimidi_version_t           version;
+	char                              serial[11];
+	char                              d;
+	struct gpio_desc                  *data_ready_gpio;
+
+	struct work_struct                drdy_handler;
+
+	// comm_lock serializes I2C communication.
+	struct mutex                      comm_lock;
+	char                              *rx_buf;
+	size_t                            rx_len;
+	int                               rx_status;
+	struct completion                 *rx_completion;
+
+	struct snd_rawmidi                *rawmidi;
+	struct pimidi_midi_port           midi_port[PIMIDI_PORTS];
+	bool                              stopping;
+};
+
+static struct snd_rawmidi_substream *pimidi_find_substream(struct snd_rawmidi *rawmidi,
+							   int stream,
+							   int number
+	)
+{
+	struct snd_rawmidi_substream *substream;
+
+	list_for_each_entry(substream, &rawmidi->streams[stream].substreams, list) {
+		if (substream->number == number)
+			return substream;
+	}
+	return NULL;
+}
+
+static void pimidi_midi_in_handler(struct pimidi_instance *instance, int port)
+{
+	int i, n, err;
+
+	printd(instance, "(%d)", port);
+
+	struct pimidi_midi_port *midi_port = &instance->midi_port[port];
+
+	if (!test_bit(SNDRV_RAWMIDI_STREAM_INPUT, &midi_port->enabled_streams)) {
+		printd(instance, "Input not enabled for %d", port);
+		return;
+	}
+
+	u8 data[512];
+
+	n = kfifo_out_peek(&midi_port->in_fifo, data, sizeof(data));
+	printd(instance, "Peeked %d MIDI bytes", n);
+
+	mutex_lock(&midi_port->in_lock);
+	struct snd_rawmidi_substream *substream =
+		pimidi_find_substream(instance->rawmidi,
+				      SNDRV_RAWMIDI_STREAM_INPUT,
+				      port);
+
+	err = snd_rawmidi_receive(substream, data, n);
+	if (err > 0)
+		midi_port->rx_cnt += err;
+	mutex_unlock(&midi_port->in_lock);
+
+	for (i = 0; i < err; ++i)
+		kfifo_skip(&midi_port->in_fifo);
+
+	if (n != err)
+		printw_rl(instance,
+			  "Not all MIDI data consumed for port %d: %d / %d", port, err, n);
+
+	if (!kfifo_is_empty(&midi_port->in_fifo) && !instance->stopping)
+		queue_work(pimidi_global.work_queue, &midi_port->in_handler);
+
+	printd(instance, "Done");
+}
+
+static void pimidi_midi_in_handler_0(struct work_struct *work)
+{
+	pimidi_midi_in_handler(container_of(work, struct pimidi_instance, midi_port[0].in_handler),
+			       0);
+}
+
+static void pimidi_midi_in_handler_1(struct work_struct *work)
+{
+	pimidi_midi_in_handler(container_of(work, struct pimidi_instance, midi_port[1].in_handler),
+			       1);
+}
+
+static void pimidi_midi_out_handler(struct pimidi_instance *instance, int port)
+{
+	printd(instance, "(%d)", port);
+	if (!test_bit(SNDRV_RAWMIDI_STREAM_OUTPUT, &instance->midi_port[port].enabled_streams)) {
+		printd(instance, "Output not enabled for %d", port);
+		return;
+	}
+
+	struct pimidi_midi_port *midi_port = &instance->midi_port[port];
+
+	struct snd_rawmidi_substream *substream =
+		pimidi_find_substream(instance->rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, port);
+
+	mutex_lock(&midi_port->out_lock);
+
+	enum { MIDI_MILLI_BYTES_PER_JIFFY = 3125000 / HZ };
+	enum { MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES =
+		(512 - PIMIDI_MAX_PACKET_SIZE - 1) * 1000 };
+
+	unsigned int now = jiffies;
+	unsigned int millibytes_became_available =
+		(MIDI_MILLI_BYTES_PER_JIFFY) * (now - midi_port->last_output_at);
+
+	midi_port->output_buffer_used_in_millibytes =
+		midi_port->output_buffer_used_in_millibytes <=
+		millibytes_became_available ? 0 : midi_port->output_buffer_used_in_millibytes -
+		millibytes_became_available;
+
+	unsigned int output_buffer_available =
+		(MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES
+		- midi_port->output_buffer_used_in_millibytes)
+		/ 1000;
+
+	u8 buffer[PIMIDI_MAX_PACKET_SIZE];
+	int n, batch, err;
+
+	for (batch = 0; batch < 3; ++batch) {
+		if (output_buffer_available == 0)
+			printd(instance, "Buffer full");
+
+		printd(instance, "Buffer available: %u (%u +%u, %u -> %u, dt %u) (%u) @ %u",
+		       output_buffer_available, midi_port->output_buffer_used_in_millibytes,
+		       millibytes_became_available, midi_port->last_output_at, now,
+		       now - midi_port->last_output_at, midi_port->tx_cnt, HZ);
+		midi_port->last_output_at = now;
+
+		n = output_buffer_available
+			? snd_rawmidi_transmit_peek(substream, buffer + 1,
+						    min(output_buffer_available,
+							sizeof(buffer) - 2))
+			: 0;
+		if (n > 0) {
+			printd(instance, "Peeked: %d", n);
+			snd_rawmidi_transmit_ack(substream, n);
+
+			buffer[0] = (port << 4) | n;
+			buffer[n + 1] = ~crc8(pimidi_crc8_table, buffer, n + 1, CRC8_INIT_VALUE);
+
+#ifdef PIMIDI_DEBUG
+			pr_debug("%s[%d]: Sending %d bytes:", __func__, instance->d, n + 2);
+			int i;
+
+			for (i = 0; i < n + 2; ++i)
+				pr_cont(" %02x", buffer[i]);
+
+			pr_cont("\n");
+#endif
+			mutex_lock(&instance->comm_lock);
+			err = i2c_master_send(instance->i2c_client, buffer, n + 2);
+			mutex_unlock(&instance->comm_lock);
+
+			if (err < 0) {
+				printe(instance,
+				       "Error occurred when sending MIDI data over I2C! (%d)",
+				       err);
+				goto cleanup;
+			}
+
+			midi_port->tx_cnt += n;
+			midi_port->output_buffer_used_in_millibytes += n * 1000;
+			output_buffer_available -= n;
+		} else if (n < 0) {
+			err = n;
+			printe(instance, "snd_rawmidi_transmit_peek returned error %d!", err);
+			goto cleanup;
+		} else {
+			break;
+		}
+	}
+
+	printd(instance, "Checking if empty %p", substream);
+	if (!snd_rawmidi_transmit_empty(substream) && !instance->stopping) {
+		unsigned int delay = 1;
+
+		if (output_buffer_available == 0)
+			delay = 125000 / MIDI_MILLI_BYTES_PER_JIFFY;
+		printd(instance, "Queue more work after %u jiffies", delay);
+		mod_delayed_work(pimidi_global.work_queue, &midi_port->out_handler, delay);
+	}
+
+cleanup:
+	mutex_unlock(&midi_port->out_lock);
+	printd(instance, "Done");
+}
+
+static void pimidi_midi_out_handler_0(struct work_struct *work)
+{
+	pimidi_midi_out_handler(container_of(work, struct pimidi_instance,
+					     midi_port[0].out_handler.work), 0);
+}
+
+static void pimidi_midi_out_handler_1(struct work_struct *work)
+{
+	pimidi_midi_out_handler(container_of(work, struct pimidi_instance,
+					     midi_port[1].out_handler.work), 1);
+}
+
+static void pimidi_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct pimidi_instance *instance = substream->rmidi->private_data;
+
+	printd(instance, "(%d, %d, %d)", substream->stream, substream->number, up);
+
+	if (up == 0) {
+		clear_bit(substream->stream,
+			  &instance->midi_port[substream->number].enabled_streams);
+	} else {
+		set_bit(substream->stream,
+			&instance->midi_port[substream->number].enabled_streams);
+		if (!delayed_work_pending(&instance->midi_port[substream->number].out_handler)) {
+			printd(instance, "Queueing work");
+			queue_delayed_work(pimidi_global.work_queue,
+					   &instance->midi_port[substream->number].out_handler, 0);
+		}
+	}
+}
+
+static void pimidi_midi_output_drain(struct snd_rawmidi_substream *substream)
+{
+	struct pimidi_instance *instance = substream->rmidi->private_data;
+
+	printd(instance, "(%d, %d)", substream->stream, substream->number);
+
+	printd(instance, "Begin draining!");
+
+	queue_delayed_work(pimidi_global.work_queue,
+			   &instance->midi_port[substream->number].out_handler, 0);
+
+	unsigned long deadline = jiffies + 5 * HZ;
+
+	do {
+		printd(instance, "Before flush");
+		while (delayed_work_pending(&instance->midi_port[substream->number].out_handler))
+			flush_delayed_work(&instance->midi_port[substream->number].out_handler);
+		printd(instance, "Flushed");
+	} while (!snd_rawmidi_transmit_empty(substream) && time_before(jiffies, deadline));
+
+	printd(instance, "Done!");
+}
+
+static int pimidi_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+	struct pimidi_instance *instance = substream->rmidi->private_data;
+	struct pimidi_midi_port *midi_port = &instance->midi_port[substream->number];
+
+	mutex_lock(&midi_port->out_lock);
+	clear_bit(substream->stream, &midi_port->enabled_streams);
+	mutex_unlock(&midi_port->out_lock);
+	return 0;
+}
+
+static int pimidi_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+	struct pimidi_instance *instance = substream->rmidi->private_data;
+	struct pimidi_midi_port *midi_port = &instance->midi_port[substream->number];
+
+	mutex_lock(&midi_port->in_lock);
+	clear_bit(substream->stream, &midi_port->enabled_streams);
+	mutex_unlock(&midi_port->in_lock);
+	return 0;
+}
+
+static void pimidi_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	struct pimidi_instance *instance = substream->rmidi->private_data;
+
+	printd(instance, "(%d, %d, %d)", substream->stream, substream->number, up);
+
+	if (up == 0) {
+		clear_bit(substream->stream,
+			  &instance->midi_port[substream->number].enabled_streams);
+		cancel_work_sync(&instance->midi_port[substream->number].in_handler);
+	} else {
+		set_bit(substream->stream,
+			&instance->midi_port[substream->number].enabled_streams);
+		if (!instance->stopping)
+			queue_work(pimidi_global.work_queue,
+				   &instance->midi_port[substream->number].in_handler);
+	}
+}
+
+static void pimidi_get_port_info(struct snd_rawmidi *rmidi, int number,
+				 struct snd_seq_port_info *seq_port_info)
+{
+	printd_g("%p, %d, %p", rmidi, number, seq_port_info);
+	seq_port_info->type =
+		SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
+		SNDRV_SEQ_PORT_TYPE_HARDWARE |
+		SNDRV_SEQ_PORT_TYPE_PORT;
+	strscpy(seq_port_info->name, number == 0 ? "a" : "b",
+		sizeof(seq_port_info->name));
+	seq_port_info->midi_voices = 0;
+}
+
+static const struct snd_rawmidi_global_ops pimidi_midi_ops = {
+	.get_port_info = pimidi_get_port_info,
+};
+
+static int pimidi_midi_open(struct snd_rawmidi_substream *substream)
+{
+	printd_g("(%p) stream=%d number=%d", substream, substream->stream, substream->number);
+	return 0;
+}
+
+static const struct snd_rawmidi_ops pimidi_midi_output_ops = {
+	.open = pimidi_midi_open,
+	.close = pimidi_midi_output_close,
+	.trigger = pimidi_midi_output_trigger,
+	.drain = pimidi_midi_output_drain,
+};
+
+static const struct snd_rawmidi_ops pimidi_midi_input_ops = {
+	.open = pimidi_midi_open,
+	.close = pimidi_midi_input_close,
+	.trigger = pimidi_midi_input_trigger,
+};
+
+static int pimidi_register(struct pimidi_instance *instance)
+{
+	int err = 0;
+
+	mutex_lock(&pimidi_global.lock);
+	printd(instance, "Registering...");
+	if (!pimidi_global.reset_gpio) {
+		printd_g("Getting reset pin.");
+		pimidi_global.reset_gpio = gpiod_get(&instance->i2c_client->dev, "reset",
+						     GPIOD_OUT_LOW);
+		if (IS_ERR(pimidi_global.reset_gpio)) {
+			err = PTR_ERR(pimidi_global.reset_gpio);
+			printe_g("gpiod_get failed: %d", err);
+			pimidi_global.reset_gpio = NULL;
+			mutex_unlock(&pimidi_global.lock);
+			return err;
+		}
+	}
+	list_add_tail(&instance->list, &pimidi_global.devices);
+	mutex_unlock(&pimidi_global.lock);
+	return err;
+}
+
+static void pimidi_unregister(struct pimidi_instance *instance)
+{
+	mutex_lock(&pimidi_global.lock);
+	printd(instance, "Unregistering...");
+	list_del(&instance->list);
+	if (list_empty(&pimidi_global.devices)) {
+		printd_g("Releasing reset pin");
+		gpiod_put(pimidi_global.reset_gpio);
+		pimidi_global.reset_gpio = NULL;
+	}
+	mutex_unlock(&pimidi_global.lock);
+}
+
+static void pimidi_perform_reset(void)
+{
+	mutex_lock(&pimidi_global.lock);
+
+	printd_g("Performing reset.");
+
+	struct list_head *p;
+
+	list_for_each(p, &pimidi_global.devices) {
+		struct pimidi_instance *instance = list_entry(p, struct pimidi_instance, list);
+
+		printd(instance, "Pausing...");
+		instance->stopping = true;
+		disable_irq(instance->i2c_client->irq);
+		cancel_work(&instance->drdy_handler);
+
+		int i;
+
+		for (i = 0; i < PIMIDI_PORTS; ++i) {
+			cancel_work(&instance->midi_port[i].in_handler);
+			cancel_delayed_work(&instance->midi_port[i].out_handler);
+		}
+
+		drain_workqueue(pimidi_global.work_queue);
+	}
+
+	printd_g("Reset = low");
+	gpiod_set_value(pimidi_global.reset_gpio, 1);
+
+	list_for_each(p, &pimidi_global.devices) {
+		struct pimidi_instance *instance = list_entry(p, struct pimidi_instance, list);
+
+		if (gpiod_is_active_low(instance->data_ready_gpio))
+			gpiod_toggle_active_low(instance->data_ready_gpio);
+		gpiod_direction_output(instance->data_ready_gpio, 1);
+		printd(instance, "DRDY high");
+	}
+
+	usleep_range(1000, 5000);
+	printd_g("Reset = high");
+	gpiod_set_value(pimidi_global.reset_gpio, 0);
+	msleep(30);
+
+	int i;
+
+	for (i = 0; i < PIMIDI_MAX_DEVICES; ++i) {
+		usleep_range(1000, 3000);
+		list_for_each(p, &pimidi_global.devices) {
+			struct pimidi_instance *instance = list_entry(p, struct pimidi_instance,
+								      list);
+
+			if (instance->d < i)
+				continue;
+			printd(instance, "DRDY -> %d", !gpiod_get_value(instance->data_ready_gpio));
+			gpiod_set_value(instance->data_ready_gpio,
+					!gpiod_get_value(instance->data_ready_gpio));
+		}
+	}
+	usleep_range(16000, 20000);
+
+	list_for_each(p, &pimidi_global.devices) {
+		struct pimidi_instance *instance = list_entry(p, struct pimidi_instance, list);
+
+		if (!gpiod_is_active_low(instance->data_ready_gpio))
+			gpiod_toggle_active_low(instance->data_ready_gpio);
+
+		printd(instance, "DRDY input");
+		gpiod_direction_input(instance->data_ready_gpio);
+
+		printd(instance, "Resume...");
+		instance->stopping = false;
+		enable_irq(instance->i2c_client->irq);
+	}
+
+	printd_g("Reset done.");
+	usleep_range(16000, 20000);
+
+	mutex_unlock(&pimidi_global.lock);
+}
+
+static int pimidi_read_version(struct pimidi_version_t *version, struct pimidi_instance *instance)
+{
+	memset(version, 0, sizeof(*version));
+
+	const char cmd[4] = { 0xb2, 0x01, 0x01, 0x95 };
+
+	char result[9];
+
+	memset(result, 0, sizeof(result));
+
+	DECLARE_COMPLETION_ONSTACK(done);
+
+	mutex_lock(&instance->comm_lock);
+	int err = i2c_master_send(instance->i2c_client, cmd, sizeof(cmd));
+
+	if (err < 0) {
+		mutex_unlock(&instance->comm_lock);
+		return err;
+	}
+	instance->rx_buf = result;
+	instance->rx_len = sizeof(result);
+	instance->rx_completion = &done;
+	mutex_unlock(&instance->comm_lock);
+
+	printd(instance, "Waiting for drdy");
+	wait_for_completion_io_timeout(&done, msecs_to_jiffies(1000u));
+	printd(instance, "Done waiting");
+
+	if (!completion_done(&done)) {
+		mutex_lock(&instance->comm_lock);
+		instance->rx_buf = NULL;
+		instance->rx_len = 0;
+		instance->rx_status = -ETIMEDOUT;
+		instance->rx_completion = NULL;
+		mutex_unlock(&instance->comm_lock);
+		return -ETIMEDOUT;
+	}
+
+	if (CRC8_GOOD_VALUE(pimidi_crc8_table) != crc8(pimidi_crc8_table, result, sizeof(result),
+						       CRC8_INIT_VALUE))
+		return -EIO;
+
+	const char expected[4] = { 0xb7, 0x81, 0x01, 0x00 };
+
+	if (memcmp(result, expected, sizeof(expected)) != 0)
+		return -EPROTO;
+
+	u32 v = ntohl(*(uint32_t *)(result + 4));
+
+	version->hwrev = v >> 24;
+	version->major = (v & 0x00ff0000) >> 16;
+	version->minor = (v & 0x0000ff00) >> 8;
+	version->build = v & 0x000000ff;
+
+	return 0;
+}
+
+static int pimidi_read_serial(char serial[11], struct pimidi_instance *instance)
+{
+	memset(serial, 0, sizeof(char[11]));
+
+	const char cmd[4] = { 0xb2, 0x03, 0x04, 0x97 };
+
+	char result[PIMIDI_MAX_PACKET_SIZE];
+
+	memset(result, 0, sizeof(result));
+
+	DECLARE_COMPLETION_ONSTACK(done);
+
+	mutex_lock(&instance->comm_lock);
+	int err = i2c_master_send(instance->i2c_client, cmd, sizeof(cmd));
+
+	if (err < 0) {
+		mutex_unlock(&instance->comm_lock);
+		return err;
+	}
+	instance->rx_buf = result;
+	instance->rx_len = sizeof(result);
+	instance->rx_completion = &done;
+	mutex_unlock(&instance->comm_lock);
+
+	printd(instance, "Waiting for drdy");
+	wait_for_completion_io_timeout(&done, msecs_to_jiffies(1000u));
+	printd(instance, "Done waiting");
+
+	if (!completion_done(&done)) {
+		mutex_lock(&instance->comm_lock);
+		instance->rx_buf = NULL;
+		instance->rx_len = 0;
+		instance->rx_status = -ETIMEDOUT;
+		instance->rx_completion = NULL;
+		mutex_unlock(&instance->comm_lock);
+		printe(instance, "Timed out");
+		return -ETIMEDOUT;
+	}
+
+	if (CRC8_GOOD_VALUE(pimidi_crc8_table) != crc8(pimidi_crc8_table, result,
+						       (result[0] & 0x0f) + 2, CRC8_INIT_VALUE))
+		return -EIO;
+
+	const char expected[4] = { 0xbd, 0x83, 0x04, 0x0a };
+
+	if (memcmp(result, expected, sizeof(expected)) != 0) {
+		printe(instance, "Unexpected response: %02x %02x %02x %02x", result[0], result[1],
+		       result[2], result[3]);
+		return -EPROTO;
+	}
+
+	memcpy(serial, result + 4, 10);
+
+	if (strspn(serial, "\xff") == 10)
+		strscpy(serial, "(unset)", 8);
+
+	return 0;
+}
+
+static void pimidi_handle_midi_data(struct pimidi_instance *instance, int port, const uint8_t *data,
+				    unsigned int n)
+{
+	printd(instance, "Handling MIDI data for port %d (%u bytes)", port, n);
+	if (n == 0)
+		return;
+
+	struct pimidi_midi_port *midi_port = &instance->midi_port[port];
+
+	kfifo_in(&midi_port->in_fifo, data, n);
+
+	if (!instance->stopping)
+		queue_work(pimidi_global.work_queue, &midi_port->in_handler);
+
+	printd(instance, "Done");
+}
+
+static void pimidi_drdy_continue(struct pimidi_instance *instance)
+{
+	if (instance->stopping) {
+		printd(instance, "Refusing to queue work / enable IRQ due to stopping.");
+		return;
+	}
+
+	if (gpiod_get_value(instance->data_ready_gpio)) {
+		printd_rl(instance, "Queue work due to DRDY line still low");
+		queue_work(pimidi_global.work_queue, &instance->drdy_handler);
+	} else {
+		printd_rl(instance, "Enabling irq for more data");
+		enable_irq(gpiod_to_irq(instance->data_ready_gpio));
+	}
+}
+
+static void pimidi_drdy_handler(struct work_struct *work)
+{
+	struct pimidi_instance *instance = container_of(work, struct pimidi_instance, drdy_handler);
+
+	printd(instance, "(%p)", work);
+
+	mutex_lock(&instance->comm_lock);
+	if (!instance->rx_completion) {
+		u8 data[PIMIDI_MAX_PACKET_SIZE];
+		int n = i2c_master_recv(instance->i2c_client, data, 3);
+
+		if (n < 0) {
+			printe(instance, "Error reading from device: %d", n);
+			mutex_unlock(&instance->comm_lock);
+			pimidi_drdy_continue(instance);
+			return;
+		}
+
+		if (data[0] == 0xfe) {
+			printe_rl(instance, "Invalid packet 0x%02x 0x%02x 0x%02x", data[0], data[1],
+				  data[2]);
+			mutex_unlock(&instance->comm_lock);
+			pimidi_drdy_continue(instance);
+			return;
+		}
+
+		int len = (data[0] & 0x0f) + 2;
+
+		if (len > n) {
+			printd(instance, "Need %d more bytes", len - n);
+			int err = i2c_master_recv(instance->i2c_client, data + n, len - n);
+
+			if (err < 0) {
+				printe(instance, "Error reading remainder from device: %d", err);
+				mutex_unlock(&instance->comm_lock);
+				pimidi_drdy_continue(instance);
+				return;
+#ifdef PIMIDI_DEBUG
+			} else {
+				pr_debug("Recv_2:");
+				int i;
+
+				for (i = n; i < len; ++i)
+					pr_cont(" %02x", data[i]);
+				pr_cont("\n");
+#endif
+			}
+		}
+
+		if (CRC8_GOOD_VALUE(pimidi_crc8_table) == crc8(pimidi_crc8_table, data, len,
+							       CRC8_INIT_VALUE)) {
+			switch (data[0] & 0xf0) {
+			case 0x00:
+				pimidi_handle_midi_data(instance, 0, data + 1, len - 2);
+				break;
+			case 0x10:
+				pimidi_handle_midi_data(instance, 1, data + 1, len - 2);
+				break;
+			default:
+				printd(instance, "Unhandled command %02x", data[0]);
+				break;
+			}
+		} else {
+			printe(instance, "I2C rx corruption detected.");
+			pr_info("Packet [%d]:", len);
+			int i;
+
+			for (i = 0; i < len; ++i)
+				pr_cont(" %02x", data[i]);
+			pr_cont("\n");
+		}
+
+		mutex_unlock(&instance->comm_lock);
+	} else {
+		printd(instance, "Completing drdy");
+		instance->rx_status = i2c_master_recv(instance->i2c_client, instance->rx_buf, 3);
+		printd(instance, "Recv_1 %02x %02x %02x", instance->rx_buf[0], instance->rx_buf[1],
+		       instance->rx_buf[2]);
+		if (instance->rx_len > 3 && instance->rx_status == 3) {
+			instance->rx_status = i2c_master_recv(instance->i2c_client,
+							      instance->rx_buf + 3,
+							      instance->rx_len - 3);
+			if (instance->rx_status >= 0)
+				instance->rx_status += 3;
+#ifdef PIMIDI_DEBUG
+			pr_debug("Recv_2:");
+			int i;
+
+			for (i = 3; i < instance->rx_len; ++i)
+				pr_cont(" %02x", instance->rx_buf[i]);
+			pr_cont("\n");
+#endif
+		}
+		struct completion *done = instance->rx_completion;
+
+		instance->rx_buf = NULL;
+		instance->rx_len = 0;
+		instance->rx_completion = NULL;
+		complete_all(done);
+		mutex_unlock(&instance->comm_lock);
+	}
+
+	pimidi_drdy_continue(instance);
+}
+
+static irqreturn_t pimidi_drdy_interrupt_handler(int irq, void *dev_id)
+{
+	struct pimidi_instance *instance = (struct pimidi_instance *)dev_id;
+
+	if (instance->stopping) {
+		printd(instance, "DRDY interrupt, but stopping, ignoring...");
+		return IRQ_HANDLED;
+	}
+
+	printd(instance, "DRDY interrupt, masking");
+	disable_irq_nosync(irq);
+
+	printd(instance, "Queue work due to DRDY interrupt");
+	queue_work(pimidi_global.work_queue, &instance->drdy_handler);
+
+	return IRQ_HANDLED;
+}
+
+static void pimidi_proc_stat_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	const unsigned int *d = entry->private_data;
+
+	snd_iprintf(buffer, "%u\n", *d);
+}
+
+static void pimidi_proc_serial_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct pimidi_instance *instance = entry->private_data;
+
+	snd_iprintf(buffer, "%s\n", instance->serial);
+}
+
+static void pimidi_proc_version_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct pimidi_instance *instance = entry->private_data;
+
+	snd_iprintf(buffer, "%u.%u.%u\n", instance->version.major, instance->version.minor,
+		    instance->version.build);
+}
+
+static void pimidi_proc_hwrev_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct pimidi_instance *instance = entry->private_data;
+
+	snd_iprintf(buffer, "%u\n", instance->version.hwrev);
+}
+
+static int pimidi_i2c_probe(struct i2c_client *client)
+{
+	struct snd_card *card = NULL;
+	int err, d, i;
+
+	d = client->addr - 0x20;
+
+	if (d < 0 || d >= 8) {
+		printe_g("Unexpected device address: %d", client->addr);
+		err = -EINVAL;
+		goto finalize;
+	}
+
+	err = snd_card_new(&client->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, THIS_MODULE,
+			   sizeof(struct pimidi_instance), &card);
+
+	if (err) {
+		printe_g("snd_card_new failed: %d", err);
+		return err;
+	}
+
+	struct pimidi_instance *instance = (struct pimidi_instance *)card->private_data;
+
+	instance->i2c_client = client;
+	instance->d = d;
+
+	struct snd_rawmidi *rawmidi;
+
+	err = snd_rawmidi_new(card, card->shortname, 0, 2, 2, &rawmidi);
+	if (err < 0) {
+		printe(instance, "snd_rawmidi_new failed: %d", err);
+		goto finalize;
+	}
+
+	instance->rawmidi = rawmidi;
+	strscpy(rawmidi->name, "pimidi", sizeof(rawmidi->name));
+
+	rawmidi->info_flags =
+		SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX;
+	rawmidi->private_data = instance;
+	rawmidi->ops = &pimidi_midi_ops;
+
+	snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &pimidi_midi_output_ops);
+	snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT, &pimidi_midi_input_ops);
+
+	instance->data_ready_gpio = devm_gpiod_get(&client->dev, "data-ready", GPIOD_OUT_HIGH);
+	if (IS_ERR(instance->data_ready_gpio)) {
+		err = PTR_ERR(instance->data_ready_gpio);
+		printe(instance, "devm_gpiod_get failed: %d", err);
+		goto finalize;
+	}
+
+	err = pimidi_register(instance);
+	if (err < 0) {
+		printe(instance, "pimidi_register failed: %d", err);
+		goto finalize;
+	}
+
+	pimidi_perform_reset();
+
+	INIT_WORK(&instance->drdy_handler, pimidi_drdy_handler);
+	mutex_init(&instance->comm_lock);
+
+	err = devm_request_irq(&client->dev, client->irq, pimidi_drdy_interrupt_handler,
+			       IRQF_SHARED | IRQF_TRIGGER_LOW, "data_ready_int", instance);
+
+	if (err != 0) {
+		printe(instance, "data_available IRQ request failed! %d", err);
+		goto finalize;
+	}
+
+	err = pimidi_read_version(&instance->version, instance);
+	if (err < 0) {
+		printe(instance, "pimidi_read_version failed: %d", err);
+		goto finalize;
+	}
+
+	err = pimidi_read_serial(instance->serial, instance);
+	if (err < 0) {
+		printe(instance, "pimidi_read_serial failed: %d", err);
+		goto finalize;
+	} else if (instance->serial[0] != 'P' || instance->serial[1] != 'M' ||
+		   strlen(instance->serial) != 10) {
+		printe(instance, "Unexpected serial number: %s", instance->serial);
+		err = -EIO;
+		goto finalize;
+	}
+
+	printi(instance, "pimidi%d hw:%d version %u.%u.%u-%u, serial %s",
+	       d,
+	       card->number,
+	       instance->version.major,
+	       instance->version.minor,
+	       instance->version.build,
+	       instance->version.hwrev,
+	       instance->serial
+	       );
+
+	strscpy(card->driver, "snd-pimidi", sizeof(card->driver));
+	snprintf(card->shortname, sizeof(card->shortname), "pimidi%d", d);
+	snprintf(card->longname, sizeof(card->longname), "pimidi%d %s", d, instance->serial);
+	snprintf(card->id, sizeof(card->id), "pimidi%d", d);
+
+	snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, 0)->name,
+		 10u, "pimidi%d-a", d);
+	snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT,  0)->name,
+		 10u, "pimidi%d-a", d);
+	snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, 1)->name,
+		 10u, "pimidi%d-b", d);
+	snprintf(pimidi_find_substream(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT,  1)->name,
+		 10u, "pimidi%d-b", d);
+
+	err = snd_card_ro_proc_new(card, "a-tx", &instance->midi_port[0].tx_cnt,
+				   pimidi_proc_stat_show);
+	err = snd_card_ro_proc_new(card, "a-rx", &instance->midi_port[0].rx_cnt,
+				   pimidi_proc_stat_show);
+	err = snd_card_ro_proc_new(card, "b-tx", &instance->midi_port[1].tx_cnt,
+				   pimidi_proc_stat_show);
+	err = snd_card_ro_proc_new(card, "b-rx", &instance->midi_port[1].rx_cnt,
+				   pimidi_proc_stat_show);
+	err = snd_card_ro_proc_new(card, "serial", instance, pimidi_proc_serial_show);
+	err = snd_card_ro_proc_new(card, "version", instance, pimidi_proc_version_show);
+	err = snd_card_ro_proc_new(card, "hwrev", instance, pimidi_proc_hwrev_show);
+	if (err < 0) {
+		printe(instance, "snd_card_ro_proc_new failed: %d", err);
+		goto finalize;
+	}
+
+	err = snd_card_register(card);
+	if (err < 0) {
+		printe(instance, "snd_card_register failed: %d", err);
+		goto finalize;
+	}
+
+finalize:
+	if (err) {
+		instance->stopping = true;
+		cancel_work_sync(&instance->drdy_handler);
+		mutex_destroy(&instance->comm_lock);
+		pimidi_unregister(instance);
+		snd_card_free(card);
+		return err;
+	}
+
+	for (i = 0; i < PIMIDI_PORTS; ++i) {
+		struct pimidi_midi_port *port = &instance->midi_port[i];
+
+		mutex_init(&port->in_lock);
+		mutex_init(&port->out_lock);
+		INIT_WORK(&port->in_handler,
+			  i == 0 ? pimidi_midi_in_handler_0 : pimidi_midi_in_handler_1);
+		INIT_DELAYED_WORK(&port->out_handler,
+				  i == 0 ? pimidi_midi_out_handler_0 : pimidi_midi_out_handler_1);
+		INIT_KFIFO(port->in_fifo);
+		port->last_output_at = jiffies;
+	}
+
+	i2c_set_clientdata(client, card);
+	return 0;
+}
+
+static void pimidi_i2c_remove(struct i2c_client *client)
+{
+	printd_g("(%p)", client);
+
+	int i;
+	struct snd_card *card = i2c_get_clientdata(client);
+
+	if (card) {
+		printi_g("Unloading hw:%d %s", card->number, card->longname);
+		struct pimidi_instance *instance = (struct pimidi_instance *)card->private_data;
+
+		instance->stopping = true;
+		i2c_set_clientdata(client, NULL);
+		devm_free_irq(&client->dev, client->irq, instance);
+		cancel_work_sync(&instance->drdy_handler);
+
+		for (i = 0; i < PIMIDI_PORTS; ++i) {
+			cancel_work_sync(&instance->midi_port[i].in_handler);
+			cancel_delayed_work_sync(&instance->midi_port[i].out_handler);
+			mutex_destroy(&instance->midi_port[i].out_lock);
+			mutex_destroy(&instance->midi_port[i].in_lock);
+			kfifo_free(&instance->midi_port[i].in_fifo);
+		}
+
+		mutex_destroy(&instance->comm_lock);
+		pimidi_unregister(instance);
+		snd_card_free(card);
+	}
+}
+
+static const struct i2c_device_id pimidi_i2c_ids[] = {
+	{ "pimidi", 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, pimidi_i2c_ids);
+
+static const struct of_device_id pimidi_i2c_dt_ids[] = {
+	{ .compatible = "blokaslabs,pimidi", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, pimidi_i2c_dt_ids);
+
+static struct i2c_driver pimidi_i2c_driver = {
+	.driver = {
+		.name = "pimidi",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(pimidi_i2c_dt_ids),
+	},
+	.probe = pimidi_i2c_probe,
+	.remove = pimidi_i2c_remove,
+	.id_table = pimidi_i2c_ids,
+};
+
+static int pimidi_module_init(void)
+{
+	int err = 0;
+
+	mutex_init(&pimidi_global.lock);
+
+	INIT_LIST_HEAD(&pimidi_global.devices);
+
+	pimidi_global.work_queue = create_singlethread_workqueue("pimidi");
+	if (!pimidi_global.work_queue) {
+		err = -ENOMEM;
+		goto cleanup;
+	}
+
+	err = i2c_add_driver(&pimidi_i2c_driver);
+	if (err < 0)
+		goto cleanup;
+
+	crc8_populate_msb(pimidi_crc8_table, PIMIDI_CRC8_POLYNOMIAL);
+
+	return 0;
+
+cleanup:
+	mutex_destroy(&pimidi_global.lock);
+	return err;
+}
+
+static void pimidi_module_exit(void)
+{
+	i2c_del_driver(&pimidi_i2c_driver);
+	mutex_lock(&pimidi_global.lock);
+	if (pimidi_global.reset_gpio) {
+		gpiod_put(pimidi_global.reset_gpio);
+		pimidi_global.reset_gpio = NULL;
+	}
+	mutex_unlock(&pimidi_global.lock);
+
+	destroy_workqueue(pimidi_global.work_queue);
+	pimidi_global.work_queue = NULL;
+
+	mutex_destroy(&pimidi_global.lock);
+}
+
+module_init(pimidi_module_init);
+module_exit(pimidi_module_exit);
+
+MODULE_AUTHOR("Giedrius Trainavi\xc4\x8dius <giedrius@blokas.io>");
+MODULE_DESCRIPTION("MIDI driver for Blokas Pimidi, https://blokas.io/");
+MODULE_LICENSE("GPL");
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/drivers/upisnd/Makefile b/sound/drivers/upisnd/Makefile
new file mode 100644
index 000000000000..00f3d5125380
--- /dev/null
+++ b/sound/drivers/upisnd/Makefile
@@ -0,0 +1,23 @@
+# Pisound Micro Linux kernel module.
+# Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; version 2 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+
+-include $(shell pwd)/Makefile.dev
+
+ifdef M
+-include $(M)/Makefile.dev
+endif
+
+obj-$(CONFIG_SND_PISOUND_MICRO) := snd-soc-upisnd-ctrl.o snd-soc-upisnd-codec.o
+snd-soc-upisnd-ctrl-objs := upisnd_module.o upisnd_ctrl.o upisnd_comm.o upisnd_sound.o upisnd_midi.o upisnd_pins.o upisnd_gpio.o upisnd_sysfs.o upisnd_utils.o
+snd-soc-upisnd-codec-objs := upisnd_codec.o
diff --git a/sound/drivers/upisnd/upisnd_codec.c b/sound/drivers/upisnd/upisnd_codec.c
new file mode 100644
index 000000000000..5f69a5c4165e
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_codec.c
@@ -0,0 +1,1748 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+#include <linux/list.h>
+#include <linux/gcd.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "upisnd_codec.h"
+#include "upisnd_debug.h"
+
+// Based on ADAU1361/ADAU1461/ADAU1761/ADAU1961 driver by Lars-Peter Clausen.
+// Customized specifically for ADAU1961 on Pisound Micro.
+
+static int adau_calc_pll_cfg(unsigned int freq_in, unsigned int freq_out,
+			     u8 regs[5])
+{
+	unsigned int r, n, m, i, j;
+	unsigned int div;
+
+	if (!freq_out) {
+		r = 0;
+		n = 0;
+		m = 0;
+		div = 0;
+	} else {
+		if (freq_out % freq_in != 0) {
+			div = DIV_ROUND_UP(freq_in, 13500000);
+			freq_in /= div;
+			r = freq_out / freq_in;
+			i = freq_out % freq_in;
+			j = gcd(i, freq_in);
+			n = i / j;
+			m = freq_in / j;
+			div--;
+		} else {
+			r = freq_out / freq_in;
+			n = 0;
+			m = 0;
+			div = 0;
+		}
+		if (n > 0xffff || m > 0xffff || div > 3 || r > 8 || r < 2)
+			return -EINVAL;
+	}
+
+	regs[0] = m >> 8;
+	regs[1] = m & 0xff;
+	regs[2] = n >> 8;
+	regs[3] = n & 0xff;
+	regs[4] = (r << 3) | (div << 1);
+	if (m != 0)
+		regs[4] |= 1; /* Fractional mode */
+
+	return 0;
+}
+
+/**
+ * enum adau1961_micbias_voltage - Microphone bias voltage
+ * @ADAU1961_MICBIAS_0_90_AVDD: 0.9 * AVDD
+ * @ADAU1961_MICBIAS_0_65_AVDD: 0.65 * AVDD
+ */
+enum adau1961_micbias_voltage {
+	ADAU1961_MICBIAS_0_90_AVDD = 0,
+	ADAU1961_MICBIAS_0_65_AVDD = 1,
+};
+
+/**
+ * enum adau1961_output_mode - Output mode configuration
+ * @ADAU1961_OUTPUT_MODE_HEADPHONE: Headphone output
+ * @ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS: Capless headphone output
+ * @ADAU1961_OUTPUT_MODE_LINE: Line output
+ */
+enum adau1961_output_mode {
+	ADAU1961_OUTPUT_MODE_HEADPHONE,
+	ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS,
+	ADAU1961_OUTPUT_MODE_LINE,
+};
+
+/**
+ * struct adau1961_platform_data - ADAU1961 Codec driver platform data
+ * @input_differential: If true the input pins will be configured in
+ *  differential mode.
+ * @lineout_mode: Output mode for the LOUT/ROUT pins
+ * @headphone_mode: Output mode for the LHP/RHP pins
+ * @monoout_mode: Output mode for the MONOOUT pin. Ignored if headphone_mode
+ *  is set to ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS.
+ */
+struct adau1961_platform_data {
+	bool input_differential;
+	enum adau1961_output_mode lineout_mode;
+	enum adau1961_output_mode headphone_mode;
+	enum adau1961_output_mode monoout_mode;
+};
+
+struct snd_soc_component;
+struct snd_pcm_substream;
+
+struct i2c_client;
+
+enum adau1961_pll {
+	ADAU1961_PLL,
+};
+
+enum adau1961_pll_src {
+	ADAU1961_PLL_SRC_MCLK,
+};
+
+enum adau1961_clk_src {
+	/* Automatically configure PLL based on the sample rate */
+	ADAU1961_CLK_SRC_PLL_AUTO,
+	ADAU1961_CLK_SRC_MCLK,
+	ADAU1961_CLK_SRC_PLL,
+};
+
+struct clk;
+
+struct adau {
+	unsigned int sysclk;
+	unsigned int pll_freq;
+	struct clk *mclk;
+
+	enum adau1961_clk_src clk_src;
+	void (*switch_mode)(struct device *dev);
+
+	unsigned int dai_fmt;
+
+	u8 pll_regs[6];
+
+	bool master;
+	bool vgnd_shorted;
+	bool hpl_unmuted;
+	bool hpr_unmuted;
+
+	unsigned int tdm_slot[2];
+
+	struct regmap *regmap;
+};
+
+#define ADAU1961_CLOCK_CONTROL                 0x4000
+#define ADAU1961_PLL_CONTROL                   0x4002
+#define ADAU1961_DIGMIC_JACKDETECT             0x4008
+#define ADAU1961_REC_MIXER_LEFT0               0x400a
+#define ADAU1961_REC_MIXER_LEFT1               0x400b
+#define ADAU1961_REC_MIXER_RIGHT0              0x400c
+#define ADAU1961_REC_MIXER_RIGHT1              0x400d
+#define ADAU1961_LEFT_DIFF_INPUT_VOL           0x400e
+#define ADAU1961_RIGHT_DIFF_INPUT_VOL          0x400f
+#define ADAU1961_MICBIAS                       0x4010
+#define ADAU1961_ALC_CTRL0                     0x4011
+#define ADAU1961_ALC_CTRL1                     0x4012
+#define ADAU1961_ALC_CTRL2                     0x4013
+#define ADAU1961_ALC_CTRL3                     0x4014
+#define ADAU1961_SERIAL_PORT0                  0x4015
+#define ADAU1961_SERIAL_PORT1                  0x4016
+#define ADAU1961_CONVERTER0                    0x4017
+#define ADAU1961_CONVERTER1                    0x4018
+#define ADAU1961_ADC_CONTROL                   0x4019
+#define ADAU1961_LEFT_INPUT_DIGITAL_VOL        0x401a
+#define ADAU1961_RIGHT_INPUT_DIGITAL_VOL       0x401b
+#define ADAU1961_PLAY_MIXER_LEFT0              0x401c
+#define ADAU1961_PLAY_MIXER_LEFT1              0x401d
+#define ADAU1961_PLAY_MIXER_RIGHT0             0x401e
+#define ADAU1961_PLAY_MIXER_RIGHT1             0x401f
+#define ADAU1961_PLAY_LR_MIXER_LEFT            0x4020
+#define ADAU1961_PLAY_LR_MIXER_RIGHT           0x4021
+#define ADAU1961_PLAY_MIXER_MONO               0x4022
+#define ADAU1961_PLAY_HP_LEFT_VOL              0x4023
+#define ADAU1961_PLAY_HP_RIGHT_VOL             0x4024
+#define ADAU1961_PLAY_LINE_LEFT_VOL            0x4025
+#define ADAU1961_PLAY_LINE_RIGHT_VOL           0x4026
+#define ADAU1961_PLAY_MONO_OUTPUT_VOL          0x4027
+#define ADAU1961_POP_CLICK_SUPPRESS            0x4028
+#define ADAU1961_PLAY_POWER_MGMT               0x4029
+#define ADAU1961_DAC_CONTROL0                  0x402a
+#define ADAU1961_DAC_CONTROL1                  0x402b
+#define ADAU1961_DAC_CONTROL2                  0x402c
+#define ADAU1961_SERIAL_PORT_PAD               0x402d
+#define ADAU1961_CONTROL_PORT_PAD0             0x402f
+#define ADAU1961_CONTROL_PORT_PAD1             0x4030
+#define ADAU1961_JACK_DETECT_PIN               0x4031
+#define ADAU1961_DEJITTER                      0x4036
+
+#define ADAU1961_CLOCK_CONTROL_INFREQ_MASK     0x6
+#define ADAU1961_CLOCK_CONTROL_CORECLK_SRC_PLL BIT(3)
+#define ADAU1961_CLOCK_CONTROL_SYSCLK_EN       BIT(0)
+
+#define ADAU1961_SERIAL_PORT0_BCLK_POL         BIT(4)
+#define ADAU1961_SERIAL_PORT0_LRCLK_POL        BIT(3)
+#define ADAU1961_SERIAL_PORT0_MASTER           BIT(0)
+#define ADAU1961_SERIAL_PORT0_STEREO           (0x0 << 1)
+#define ADAU1961_SERIAL_PORT0_TDM4             (0x1 << 1)
+#define ADAU1961_SERIAL_PORT0_TDM_MASK         (0x3 << 1)
+#define ADAU1961_SERIAL_PORT0_PULSE_MODE       BIT(5)
+
+#define ADAU1961_SERIAL_PORT1_DELAY1           0x00
+#define ADAU1961_SERIAL_PORT1_DELAY0           0x01
+#define ADAU1961_SERIAL_PORT1_DELAY8           0x02
+#define ADAU1961_SERIAL_PORT1_DELAY16          0x03
+#define ADAU1961_SERIAL_PORT1_DELAY_MASK       0x03
+#define ADAU1961_SERIAL_PORT1_BCLK64           (0x0 << 5)
+#define ADAU1961_SERIAL_PORT1_BCLK32           (0x1 << 5)
+#define ADAU1961_SERIAL_PORT1_BCLK48           (0x2 << 5)
+#define ADAU1961_SERIAL_PORT1_BCLK128          (0x3 << 5)
+#define ADAU1961_SERIAL_PORT1_BCLK256          (0x4 << 5)
+#define ADAU1961_SERIAL_PORT1_BCLK_MASK        (0x7 << 5)
+
+#define ADAU1961_CONVERTER0_DAC_PAIR(x)        (((x) - 1) << 5)
+#define ADAU1961_CONVERTER0_DAC_PAIR_MASK      (0x3 << 5)
+#define ADAU1961_CONVERTER0_CONVSR_MASK        0x7
+#define ADAU1961_CONVERTER0_ADOSR              BIT(3)
+
+#define ADAU1961_CONVERTER1_ADC_PAIR(x)        ((x) - 1)
+#define ADAU1961_CONVERTER1_ADC_PAIR_MASK      0x3
+
+#define ADAU1961_DIFF_INPUT_VOL_LDEN           BIT(0)
+
+#define ADAU1961_PLAY_MIXER_MONO_EN            BIT(0)
+
+#define ADAU1961_PLAY_MONO_OUTPUT_VOL_MODE_HP  BIT(0)
+#define ADAU1961_PLAY_MONO_OUTPUT_VOL_UNMUTE   BIT(1)
+
+#define ADAU1961_PLAY_HP_RIGHT_VOL_MODE_HP     BIT(0)
+
+#define ADAU1961_PLAY_LINE_LEFT_VOL_MODE_HP    BIT(0)
+
+#define ADAU1961_PLAY_LINE_RIGHT_VOL_MODE_HP   BIT(0)
+
+static const DECLARE_TLV_DB_MINMAX(adau1961_digital_tlv, -9563, 0);
+
+static int adau1961_pll_event(struct snd_soc_dapm_widget *w,
+			      struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+
+	if (SND_SOC_DAPM_EVENT_ON(event)) {
+		adau->pll_regs[5] = 1;
+	} else {
+		adau->pll_regs[5] = 0;
+		// Bypass the PLL when disabled, otherwise registers will become
+		// inaccessible.
+		regmap_update_bits(adau->regmap, ADAU1961_CLOCK_CONTROL,
+				   ADAU1961_CLOCK_CONTROL_CORECLK_SRC_PLL, 0);
+	}
+
+	// The PLL register is 6 bytes long and can only be written at once.
+	regmap_raw_write(adau->regmap, ADAU1961_PLL_CONTROL,
+			 adau->pll_regs, ARRAY_SIZE(adau->pll_regs));
+
+	if (SND_SOC_DAPM_EVENT_ON(event)) {
+		mdelay(5);
+		regmap_update_bits(adau->regmap, ADAU1961_CLOCK_CONTROL,
+				   ADAU1961_CLOCK_CONTROL_CORECLK_SRC_PLL,
+			ADAU1961_CLOCK_CONTROL_CORECLK_SRC_PLL);
+	}
+
+	return 0;
+}
+
+static int adau1961_adc_fixup(struct snd_soc_dapm_widget *w,
+			      struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+
+	/*
+	 * If we are capturing, toggle the ADOSR bit in Converter Control 0 to
+	 * avoid losing SNR (workaround from ADI). This must be done after
+	 * the ADC(s) have been enabled. According to the data sheet, it is
+	 * normally illegal to set this bit when the sampling rate is 96 kHz,
+	 * but according to ADI it is acceptable for this workaround.
+	 */
+	regmap_update_bits(adau->regmap, ADAU1961_CONVERTER0,
+			   ADAU1961_CONVERTER0_ADOSR, ADAU1961_CONVERTER0_ADOSR);
+	regmap_update_bits(adau->regmap, ADAU1961_CONVERTER0,
+			   ADAU1961_CONVERTER0_ADOSR, 0);
+
+	return 0;
+}
+
+static const char * const adau1961_mono_stereo_text[] = {
+	"Stereo",
+	"Mono Left Channel (L+R)",
+	"Mono Right Channel (L+R)",
+	"Mono (L+R)",
+};
+
+static SOC_ENUM_SINGLE_DECL(adau1961_dac_mode_enum,
+	ADAU1961_DAC_CONTROL0, 6, adau1961_mono_stereo_text);
+
+static const struct snd_kcontrol_new adau1961_dac_mode_mux =
+	SOC_DAPM_ENUM("DAC Mono-Stereo-Mode", adau1961_dac_mode_enum);
+
+static const struct snd_soc_dapm_route adau1961_dapm_pll_route = {
+	"SYSCLK", NULL, "PLL",
+};
+
+static int adau1961_set_dai_pll(struct snd_soc_dai *dai, int pll_id,
+				int source, unsigned int freq_in, unsigned int freq_out)
+{
+	struct snd_soc_component *component = dai->component;
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+	int ret;
+
+	if (freq_in < 8000000 || freq_in > 27000000)
+		return -EINVAL;
+
+	ret = adau_calc_pll_cfg(freq_in, freq_out, adau->pll_regs);
+	if (ret < 0)
+		return ret;
+
+	/* The PLL register is 6 bytes long and can only be written at once. */
+	ret = regmap_raw_write(adau->regmap, ADAU1961_PLL_CONTROL,
+			       adau->pll_regs, ARRAY_SIZE(adau->pll_regs));
+	if (ret)
+		return ret;
+
+	adau->pll_freq = freq_out;
+
+	return 0;
+}
+
+static int adau1961_set_dai_sysclk(struct snd_soc_dai *dai,
+				   int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(dai->component);
+	struct adau *adau = snd_soc_component_get_drvdata(dai->component);
+	bool is_pll;
+	bool was_pll;
+
+	switch (clk_id) {
+	case ADAU1961_CLK_SRC_MCLK:
+		is_pll = false;
+		break;
+	case ADAU1961_CLK_SRC_PLL_AUTO:
+		if (!adau->mclk)
+			return -EINVAL;
+		fallthrough;
+	case ADAU1961_CLK_SRC_PLL:
+		is_pll = true;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (adau->clk_src) {
+	case ADAU1961_CLK_SRC_MCLK:
+		was_pll = false;
+		break;
+	case ADAU1961_CLK_SRC_PLL:
+	case ADAU1961_CLK_SRC_PLL_AUTO:
+		was_pll = true;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	adau->sysclk = freq;
+
+	if (is_pll != was_pll) {
+		if (is_pll) {
+			snd_soc_dapm_add_routes(dapm,
+						&adau1961_dapm_pll_route, 1);
+		} else {
+			snd_soc_dapm_del_routes(dapm,
+						&adau1961_dapm_pll_route, 1);
+		}
+	}
+
+	adau->clk_src = clk_id;
+
+	return 0;
+}
+
+static int adau1961_auto_pll(struct snd_soc_dai *dai,
+			     struct snd_pcm_hw_params *params)
+{
+	struct adau *adau = snd_soc_dai_get_drvdata(dai);
+	unsigned int pll_rate;
+
+	switch (params_rate(params)) {
+	case 48000:
+	case 8000:
+	case 12000:
+	case 16000:
+	case 24000:
+	case 32000:
+	case 96000:
+		pll_rate = 48000 * 1024;
+		break;
+	case 44100:
+	case 7350:
+	case 11025:
+	case 14700:
+	case 22050:
+	case 29400:
+	case 88200:
+		pll_rate = 44100 * 1024;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return adau1961_set_dai_pll(dai, ADAU1961_PLL, ADAU1961_PLL_SRC_MCLK,
+		clk_get_rate(adau->mclk), pll_rate);
+}
+
+static int adau1961_hw_params(struct snd_pcm_substream *substream,
+			      struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+	unsigned int val, div, dsp_div;
+	unsigned int freq;
+	int ret;
+
+	switch (adau->clk_src) {
+	case ADAU1961_CLK_SRC_PLL_AUTO:
+		ret = adau1961_auto_pll(dai, params);
+		if (ret)
+			return ret;
+		fallthrough;
+	case ADAU1961_CLK_SRC_PLL:
+		freq = adau->pll_freq;
+		break;
+	default:
+		freq = adau->sysclk;
+		break;
+	}
+
+	if (freq % params_rate(params) != 0)
+		return -EINVAL;
+
+	switch (freq / params_rate(params)) {
+	case 1024: /* fs */
+		div = 0;
+		dsp_div = 1;
+		break;
+	case 6144: /* fs / 6 */
+		div = 1;
+		dsp_div = 6;
+		break;
+	case 4096: /* fs / 4 */
+		div = 2;
+		dsp_div = 5;
+		break;
+	case 3072: /* fs / 3 */
+		div = 3;
+		dsp_div = 4;
+		break;
+	case 2048: /* fs / 2 */
+		div = 4;
+		dsp_div = 3;
+		break;
+	case 1536: /* fs / 1.5 */
+		div = 5;
+		dsp_div = 2;
+		break;
+	case 512: /* fs / 0.5 */
+		div = 6;
+		dsp_div = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	regmap_update_bits(adau->regmap, ADAU1961_CONVERTER0,
+			   ADAU1961_CONVERTER0_CONVSR_MASK, div);
+
+	if (adau->dai_fmt != SND_SOC_DAIFMT_RIGHT_J)
+		return 0;
+
+	switch (params_width(params)) {
+	case 16:
+		val = ADAU1961_SERIAL_PORT1_DELAY16;
+		break;
+	case 24:
+		val = ADAU1961_SERIAL_PORT1_DELAY8;
+		break;
+	case 32:
+		val = ADAU1961_SERIAL_PORT1_DELAY0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return regmap_update_bits(adau->regmap, ADAU1961_SERIAL_PORT1,
+			ADAU1961_SERIAL_PORT1_DELAY_MASK, val);
+}
+
+static int adau1961_set_dai_fmt(struct snd_soc_dai *dai,
+				unsigned int fmt)
+{
+	struct adau *adau = snd_soc_component_get_drvdata(dai->component);
+	unsigned int ctrl0, ctrl1;
+	unsigned int ctrl0_mask;
+	int lrclk_pol;
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM:
+		ctrl0 = ADAU1961_SERIAL_PORT0_MASTER;
+		adau->master = true;
+		break;
+	case SND_SOC_DAIFMT_CBS_CFS:
+		ctrl0 = 0;
+		adau->master = false;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		lrclk_pol = 0;
+		ctrl1 = ADAU1961_SERIAL_PORT1_DELAY1;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+	case SND_SOC_DAIFMT_RIGHT_J:
+		lrclk_pol = 1;
+		ctrl1 = ADAU1961_SERIAL_PORT1_DELAY0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		ctrl0 |= ADAU1961_SERIAL_PORT0_BCLK_POL;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		lrclk_pol = !lrclk_pol;
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		ctrl0 |= ADAU1961_SERIAL_PORT0_BCLK_POL;
+		lrclk_pol = !lrclk_pol;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (lrclk_pol)
+		ctrl0 |= ADAU1961_SERIAL_PORT0_LRCLK_POL;
+
+	/* Set the mask to update all relevant bits in ADAU1961_SERIAL_PORT0 */
+	ctrl0_mask = ADAU1961_SERIAL_PORT0_MASTER |
+		     ADAU1961_SERIAL_PORT0_LRCLK_POL |
+		     ADAU1961_SERIAL_PORT0_BCLK_POL |
+		     ADAU1961_SERIAL_PORT0_PULSE_MODE;
+
+	regmap_update_bits(adau->regmap, ADAU1961_SERIAL_PORT0, ctrl0_mask,
+			   ctrl0);
+	regmap_update_bits(adau->regmap, ADAU1961_SERIAL_PORT1,
+			   ADAU1961_SERIAL_PORT1_DELAY_MASK, ctrl1);
+
+	adau->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
+
+	return 0;
+}
+
+static int adau1961_xlate_tdm_slot_mask(unsigned int slots,
+					unsigned int *tx_mask,
+					unsigned int *rx_mask)
+{
+	// Workaround for snd_soc_dai_set_tdm_slot issue when slots=0.
+	return 0;
+}
+
+static int adau1961_set_dai_tdm_slot(struct snd_soc_dai *dai,
+				     unsigned int tx_mask,
+				     unsigned int rx_mask,
+				     int slots,
+				     int slot_width)
+{
+	struct adau *adau = snd_soc_component_get_drvdata(dai->component);
+	unsigned int ser_ctrl0, ser_ctrl1;
+	unsigned int conv_ctrl0, conv_ctrl1;
+
+	/* I2S mode */
+	if (slots == 0) {
+		slots = 2;
+		rx_mask = 3;
+		tx_mask = 3;
+		slot_width = 32;
+	}
+
+	switch (slots) {
+	case 2:
+		ser_ctrl0 = ADAU1961_SERIAL_PORT0_STEREO;
+		break;
+	case 4:
+		ser_ctrl0 = ADAU1961_SERIAL_PORT0_TDM4;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (slot_width * slots) {
+	case 32:
+		ser_ctrl1 = ADAU1961_SERIAL_PORT1_BCLK32;
+		break;
+	case 64:
+		ser_ctrl1 = ADAU1961_SERIAL_PORT1_BCLK64;
+		break;
+	case 48:
+		ser_ctrl1 = ADAU1961_SERIAL_PORT1_BCLK48;
+		break;
+	case 128:
+		ser_ctrl1 = ADAU1961_SERIAL_PORT1_BCLK128;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (rx_mask) {
+	case 0x03:
+		conv_ctrl1 = ADAU1961_CONVERTER1_ADC_PAIR(1);
+		adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 0;
+		break;
+	case 0x0c:
+		conv_ctrl1 = ADAU1961_CONVERTER1_ADC_PAIR(2);
+		adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 1;
+		break;
+	case 0x30:
+		conv_ctrl1 = ADAU1961_CONVERTER1_ADC_PAIR(3);
+		adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 2;
+		break;
+	case 0xc0:
+		conv_ctrl1 = ADAU1961_CONVERTER1_ADC_PAIR(4);
+		adau->tdm_slot[SNDRV_PCM_STREAM_CAPTURE] = 3;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (tx_mask) {
+	case 0x03:
+		conv_ctrl0 = ADAU1961_CONVERTER0_DAC_PAIR(1);
+		adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 0;
+		break;
+	case 0x0c:
+		conv_ctrl0 = ADAU1961_CONVERTER0_DAC_PAIR(2);
+		adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 1;
+		break;
+	case 0x30:
+		conv_ctrl0 = ADAU1961_CONVERTER0_DAC_PAIR(3);
+		adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 2;
+		break;
+	case 0xc0:
+		conv_ctrl0 = ADAU1961_CONVERTER0_DAC_PAIR(4);
+		adau->tdm_slot[SNDRV_PCM_STREAM_PLAYBACK] = 3;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	regmap_update_bits(adau->regmap, ADAU1961_CONVERTER0,
+			   ADAU1961_CONVERTER0_DAC_PAIR_MASK, conv_ctrl0);
+	regmap_update_bits(adau->regmap, ADAU1961_CONVERTER1,
+			   ADAU1961_CONVERTER1_ADC_PAIR_MASK, conv_ctrl1);
+	regmap_update_bits(adau->regmap, ADAU1961_SERIAL_PORT0,
+			   ADAU1961_SERIAL_PORT0_TDM_MASK, ser_ctrl0);
+	regmap_update_bits(adau->regmap, ADAU1961_SERIAL_PORT1,
+			   ADAU1961_SERIAL_PORT1_BCLK_MASK, ser_ctrl1);
+
+	return 0;
+}
+
+static int adau1961_startup(struct snd_pcm_substream *substream,
+			    struct snd_soc_dai *dai)
+{
+	struct adau *adau = snd_soc_component_get_drvdata(dai->component);
+	(void)adau;
+	return 0;
+}
+
+static const struct snd_soc_dai_ops adau1961_dai_ops = {
+	.hw_params	= adau1961_hw_params,
+	.set_sysclk	= adau1961_set_dai_sysclk,
+	.set_fmt	= adau1961_set_dai_fmt,
+	.set_pll	= adau1961_set_dai_pll,
+	.set_tdm_slot	= adau1961_set_dai_tdm_slot,
+	.startup	= adau1961_startup,
+	.xlate_tdm_slot_mask = adau1961_xlate_tdm_slot_mask,
+};
+
+static bool adau1961_precious_register(struct device *dev, unsigned int reg)
+{
+	return false;
+}
+
+static bool adau1961_volatile_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	/* The PLL register is 6 bytes long */
+	case ADAU1961_PLL_CONTROL:
+	case ADAU1961_PLL_CONTROL + 1:
+	case ADAU1961_PLL_CONTROL + 2:
+	case ADAU1961_PLL_CONTROL + 3:
+	case ADAU1961_PLL_CONTROL + 4:
+	case ADAU1961_PLL_CONTROL + 5:
+		return true;
+	default:
+		break;
+	}
+
+	return false;
+}
+
+static int adau1961_add_routes(struct snd_soc_component *component)
+{
+	struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+	int ret;
+
+	if (adau->clk_src != ADAU1961_CLK_SRC_MCLK)
+		ret = snd_soc_dapm_add_routes(dapm, &adau1961_dapm_pll_route, 1);
+
+	return ret;
+}
+
+static int adau1961_resume(struct snd_soc_component *component)
+{
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+
+	if (adau->switch_mode)
+		adau->switch_mode(component->dev);
+
+	regcache_sync(adau->regmap);
+
+	return 0;
+}
+
+static void adau1961_remove(struct device *dev)
+{
+	struct adau *adau = dev_get_drvdata(dev);
+
+	clk_disable_unprepare(adau->mclk);
+}
+
+void adau1961_set_vgnd_shorted(struct snd_soc_component *component, bool shorted)
+{
+	struct adau *p = snd_soc_component_get_drvdata(component);
+
+	struct snd_ctl_elem_value value;
+	struct snd_kcontrol *k;
+
+	long prev_left, prev_right;
+
+	if (!p) {
+		printe("Failed to get adau driver data!");
+		return;
+	}
+
+	p->vgnd_shorted = shorted;
+
+	k = snd_soc_card_get_kcontrol(component->card, "Headphone Playback Switch");
+	if (!k) {
+		printe("Failed to get Headphone Playback Switch control!");
+		return;
+	}
+
+	snd_soc_get_volsw(k, &value);
+
+	prev_left = value.value.integer.value[0];
+	prev_right = value.value.integer.value[1];
+
+	if (shorted) {
+		p->hpl_unmuted = value.value.integer.value[0];
+		p->hpr_unmuted = value.value.integer.value[1];
+
+		value.value.integer.value[0] = 0;
+		value.value.integer.value[1] = 0;
+	} else {
+		value.value.integer.value[0] = p->hpl_unmuted ? 1 : 0;
+		value.value.integer.value[1] = p->hpr_unmuted ? 1 : 0;
+	}
+
+	if (value.value.integer.value[0] != prev_left ||
+	    value.value.integer.value[1] != prev_right) {
+		snd_soc_put_volsw(k, &value);
+		snd_ctl_notify(component->card->snd_card, SNDRV_CTL_EVENT_MASK_VALUE, &k->id);
+	}
+
+	regmap_update_bits(p->regmap, ADAU1961_PLAY_MIXER_MONO,
+			   ADAU1961_PLAY_MIXER_MONO_EN, shorted ? 0 : ADAU1961_PLAY_MIXER_MONO_EN);
+}
+EXPORT_SYMBOL(adau1961_set_vgnd_shorted);
+
+bool adau1961_is_hp_capless(struct snd_soc_component *component)
+{
+	struct adau1961_platform_data *pdata = component->dev->platform_data;
+
+	return pdata->headphone_mode == ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS;
+}
+EXPORT_SYMBOL(adau1961_is_hp_capless);
+
+static int upisnd_put_hp_mute_volsw(struct snd_kcontrol *kcontrol,
+				    struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+
+	if (adau->vgnd_shorted) {
+		adau->hpl_unmuted = ucontrol->value.integer.value[0];
+		adau->hpr_unmuted = ucontrol->value.integer.value[1];
+
+		if (ucontrol->value.integer.value[0] != 0 ||
+		    ucontrol->value.integer.value[1] != 0) {
+			printe("Ignoring unmute request while VGND is shorted!");
+
+			// Ensure alsamixer shows the correct state.
+			snd_ctl_notify(component->card->snd_card,
+				       SNDRV_CTL_EVENT_MASK_VALUE,
+				       &kcontrol->id
+			);
+			return 0;
+		}
+	}
+
+	return snd_soc_put_volsw(kcontrol, ucontrol);
+}
+
+static const struct reg_default adau1961_reg_defaults[] = {
+	{ ADAU1961_DEJITTER,			0x03 },
+	{ ADAU1961_REC_MIXER_LEFT0,		0x01 },
+	{ ADAU1961_REC_MIXER_LEFT1,		0x00 },
+	{ ADAU1961_REC_MIXER_RIGHT0,		0x01 },
+	{ ADAU1961_REC_MIXER_RIGHT1,		0x00 },
+	{ ADAU1961_LEFT_DIFF_INPUT_VOL,		0x00 },
+	{ ADAU1961_ALC_CTRL0,			0x00 },
+	{ ADAU1961_ALC_CTRL1,			0x00 },
+	{ ADAU1961_ALC_CTRL2,			0x00 },
+	{ ADAU1961_ALC_CTRL3,			0x00 },
+	{ ADAU1961_RIGHT_DIFF_INPUT_VOL,	0x00 },
+	{ ADAU1961_PLAY_LR_MIXER_LEFT,		0x01 },
+	{ ADAU1961_PLAY_MIXER_LEFT0,		0x00 },
+	{ ADAU1961_PLAY_MIXER_LEFT1,		0x00 },
+	{ ADAU1961_PLAY_MIXER_RIGHT0,		0x00 },
+	{ ADAU1961_PLAY_MIXER_RIGHT1,		0x00 },
+	{ ADAU1961_PLAY_LR_MIXER_RIGHT,		0x01 },
+	{ ADAU1961_PLAY_MIXER_MONO,		0x00 },
+	{ ADAU1961_PLAY_HP_LEFT_VOL,		0x00 },
+	{ ADAU1961_PLAY_HP_RIGHT_VOL,		0x00 },
+	{ ADAU1961_PLAY_LINE_LEFT_VOL,		0x00 },
+	{ ADAU1961_PLAY_LINE_RIGHT_VOL,		0x00 },
+	{ ADAU1961_PLAY_MONO_OUTPUT_VOL,	0x02 },
+	{ ADAU1961_POP_CLICK_SUPPRESS,		0x00 },
+	{ ADAU1961_JACK_DETECT_PIN,		0x00 },
+	{ ADAU1961_CLOCK_CONTROL,		0x00 },
+	{ ADAU1961_PLL_CONTROL,			0x00 },
+	{ ADAU1961_MICBIAS,			0x00 },
+	{ ADAU1961_SERIAL_PORT0,		0x00 },
+	{ ADAU1961_SERIAL_PORT1,		0x00 },
+	{ ADAU1961_CONVERTER0,			0x00 },
+	{ ADAU1961_CONVERTER1,			0x00 },
+	{ ADAU1961_LEFT_INPUT_DIGITAL_VOL,	0x00 },
+	{ ADAU1961_RIGHT_INPUT_DIGITAL_VOL,	0x00 },
+	{ ADAU1961_ADC_CONTROL,			0x00 },
+	{ ADAU1961_PLAY_POWER_MGMT,		0x03 },
+	{ ADAU1961_DAC_CONTROL0,		0x00 },
+	{ ADAU1961_DAC_CONTROL1,		0x00 },
+	{ ADAU1961_DAC_CONTROL2,		0x00 },
+	{ ADAU1961_SERIAL_PORT_PAD,		0xaa },
+	{ ADAU1961_CONTROL_PORT_PAD0,		0xaa },
+	{ ADAU1961_CONTROL_PORT_PAD1,		0x00 },
+};
+
+static const DECLARE_TLV_DB_RANGE(adau1961_mono_output_tlv,
+	1, 1, TLV_DB_SCALE_ITEM(0, 0, 0),   //  0dB MX7[2:1]=01
+	2, 2, TLV_DB_SCALE_ITEM(600, 0, 0)  // +6dB MX7[2:1]=10
+);
+
+static const DECLARE_TLV_DB_SCALE(adau1961_sing_in_tlv, -1500, 300, 1);
+static const DECLARE_TLV_DB_SCALE(adau1961_diff_in_tlv, -1200, 75, 0);
+static const DECLARE_TLV_DB_SCALE(adau1961_out_tlv, -5700, 100, 0);
+static const DECLARE_TLV_DB_SCALE(adau1961_sidetone_tlv, -1800, 300, 1);
+static const DECLARE_TLV_DB_SCALE(adau1961_boost_tlv, -600, 600, 1);
+static const DECLARE_TLV_DB_SCALE(adau1961_pga_boost_tlv, -2000, 2000, 1);
+
+static const DECLARE_TLV_DB_SCALE(adau1961_alc_max_gain_tlv, -1200, 600, 0);
+static const DECLARE_TLV_DB_SCALE(adau1961_alc_target_tlv, -2850, 150, 0);
+static const DECLARE_TLV_DB_SCALE(adau1961_alc_ng_threshold_tlv, -7650, 150, 0);
+
+static const unsigned int adau1961_pga_slew_time_values[] = {
+	3, 0, 1, 2,
+};
+
+static const char * const adau1961_pga_slew_time_text[] = {
+	"Off",
+	"24 ms",
+	"48 ms",
+	"96 ms",
+};
+
+static const char * const adau1961_alc_function_text[] = {
+	"Off",
+	"Right",
+	"Left",
+	"Stereo",
+};
+
+static const char * const adau1961_alc_hold_time_text[] = {
+	"2.67 ms",
+	"5.34 ms",
+	"10.68 ms",
+	"21.36 ms",
+	"42.72 ms",
+	"85.44 ms",
+	"170.88 ms",
+	"341.76 ms",
+	"683.52 ms",
+	"1367 ms",
+	"2734.1 ms",
+	"5468.2 ms",
+	"10936 ms",
+	"21873 ms",
+	"43745 ms",
+	"87491 ms",
+};
+
+static const char * const adau1961_alc_attack_time_text[] = {
+	"6 ms",
+	"12 ms",
+	"24 ms",
+	"48 ms",
+	"96 ms",
+	"192 ms",
+	"384 ms",
+	"768 ms",
+	"1540 ms",
+	"3070 ms",
+	"6140 ms",
+	"12290 ms",
+	"24580 ms",
+	"49150 ms",
+	"98300 ms",
+	"196610 ms",
+};
+
+static const char * const adau1961_alc_decay_time_text[] = {
+	"24 ms",
+	"48 ms",
+	"96 ms",
+	"192 ms",
+	"384 ms",
+	"768 ms",
+	"15400 ms",
+	"30700 ms",
+	"61400 ms",
+	"12290 ms",
+	"24580 ms",
+	"49150 ms",
+	"98300 ms",
+	"196610 ms",
+	"393220 ms",
+	"786430 ms",
+};
+
+static const char * const adau1961_alc_ng_type_text[] = {
+	"Hold",
+	"Mute",
+	"Fade",
+	"Fade + Mute",
+};
+
+static SOC_VALUE_ENUM_SINGLE_DECL(adau1961_pga_slew_time_enum,
+		ADAU1961_ALC_CTRL0, 6, 0x3, adau1961_pga_slew_time_text,
+		adau1961_pga_slew_time_values);
+static SOC_ENUM_SINGLE_DECL(adau1961_alc_function_enum,
+		ADAU1961_ALC_CTRL0, 0, adau1961_alc_function_text);
+static SOC_ENUM_SINGLE_DECL(adau1961_alc_hold_time_enum,
+		ADAU1961_ALC_CTRL1, 4, adau1961_alc_hold_time_text);
+static SOC_ENUM_SINGLE_DECL(adau1961_alc_attack_time_enum,
+		ADAU1961_ALC_CTRL2, 4, adau1961_alc_attack_time_text);
+static SOC_ENUM_SINGLE_DECL(adau1961_alc_decay_time_enum,
+		ADAU1961_ALC_CTRL2, 0, adau1961_alc_decay_time_text);
+static SOC_ENUM_SINGLE_DECL(adau1961_alc_ng_type_enum,
+		ADAU1961_ALC_CTRL3, 6, adau1961_alc_ng_type_text);
+
+static const struct snd_kcontrol_new adau1961_differential_mode_controls[] = {
+	SOC_DOUBLE_R_TLV("Capture Volume", ADAU1961_LEFT_DIFF_INPUT_VOL,
+			 ADAU1961_RIGHT_DIFF_INPUT_VOL, 2, 0x3f, 0,
+		adau1961_diff_in_tlv),
+	SOC_DOUBLE_R("Capture Switch", ADAU1961_LEFT_DIFF_INPUT_VOL,
+		     ADAU1961_RIGHT_DIFF_INPUT_VOL, 1, 1, 0),
+
+	SOC_DOUBLE_R_TLV("PGA Boost Capture Volume", ADAU1961_REC_MIXER_LEFT1,
+			 ADAU1961_REC_MIXER_RIGHT1, 3, 2, 0, adau1961_pga_boost_tlv),
+
+	SOC_ENUM("PGA Capture Slew Time", adau1961_pga_slew_time_enum),
+
+	SOC_SINGLE_TLV("ALC Capture Max Gain Volume", ADAU1961_ALC_CTRL0,
+		       3, 7, 0, adau1961_alc_max_gain_tlv),
+	SOC_ENUM("ALC Capture Function", adau1961_alc_function_enum),
+	SOC_ENUM("ALC Capture Hold Time", adau1961_alc_hold_time_enum),
+	SOC_SINGLE_TLV("ALC Capture Target Volume", ADAU1961_ALC_CTRL1,
+		       0, 15, 0, adau1961_alc_target_tlv),
+	SOC_ENUM("ALC Capture Attack Time", adau1961_alc_decay_time_enum),
+	SOC_ENUM("ALC Capture Decay Time", adau1961_alc_attack_time_enum),
+	SOC_ENUM("ALC Capture Noise Gate Type", adau1961_alc_ng_type_enum),
+	SOC_SINGLE("ALC Capture Noise Gate Switch",
+		   ADAU1961_ALC_CTRL3, 5, 1, 0),
+	SOC_SINGLE_TLV("ALC Capture Noise Gate Threshold Volume",
+		       ADAU1961_ALC_CTRL3, 0, 31, 0, adau1961_alc_ng_threshold_tlv),
+};
+
+static const struct snd_kcontrol_new adau1961_single_mode_controls[] = {
+	SOC_SINGLE_TLV("Input 1 Capture Volume", ADAU1961_REC_MIXER_LEFT0,
+		       4, 7, 0, adau1961_sing_in_tlv),
+	SOC_SINGLE_TLV("Input 2 Capture Volume", ADAU1961_REC_MIXER_LEFT0,
+		       1, 7, 0, adau1961_sing_in_tlv),
+	SOC_SINGLE_TLV("Input 3 Capture Volume", ADAU1961_REC_MIXER_RIGHT0,
+		       4, 7, 0, adau1961_sing_in_tlv),
+	SOC_SINGLE_TLV("Input 4 Capture Volume", ADAU1961_REC_MIXER_RIGHT0,
+		       1, 7, 0, adau1961_sing_in_tlv),
+};
+
+static const struct snd_kcontrol_new adau1961_mono_controls[] = {
+	SOC_SINGLE_TLV("Mono Playback Volume", ADAU1961_PLAY_MONO_OUTPUT_VOL,
+		       2, 0x3f, 0, adau1961_out_tlv),
+	SOC_SINGLE("Mono Playback Switch", ADAU1961_PLAY_MONO_OUTPUT_VOL,
+		   1, 1, 0),
+	SOC_SINGLE_TLV("Mono Playback Boost Volume", ADAU1961_PLAY_MIXER_MONO,
+		       1, 2, 0, adau1961_mono_output_tlv),
+};
+
+static const struct snd_kcontrol_new adau1961_left_mixer_controls[] = {
+	SOC_DAPM_SINGLE_AUTODISABLE("Left DAC Switch",
+				    ADAU1961_PLAY_MIXER_LEFT0, 5, 1, 0),
+	SOC_DAPM_SINGLE_AUTODISABLE("Right DAC Switch",
+				    ADAU1961_PLAY_MIXER_LEFT0, 6, 1, 0),
+	SOC_DAPM_SINGLE_TLV("Aux Bypass Volume",
+			    ADAU1961_PLAY_MIXER_LEFT0, 1, 8, 0, adau1961_sidetone_tlv),
+	SOC_DAPM_SINGLE_TLV("Right Bypass Volume",
+			    ADAU1961_PLAY_MIXER_LEFT1, 4, 8, 0, adau1961_sidetone_tlv),
+	SOC_DAPM_SINGLE_TLV("Left Bypass Volume",
+			    ADAU1961_PLAY_MIXER_LEFT1, 0, 8, 0, adau1961_sidetone_tlv),
+};
+
+static const struct snd_kcontrol_new adau1961_right_mixer_controls[] = {
+	SOC_DAPM_SINGLE_AUTODISABLE("Left DAC Switch",
+				    ADAU1961_PLAY_MIXER_RIGHT0, 5, 1, 0),
+	SOC_DAPM_SINGLE_AUTODISABLE("Right DAC Switch",
+				    ADAU1961_PLAY_MIXER_RIGHT0, 6, 1, 0),
+	SOC_DAPM_SINGLE_TLV("Aux Bypass Volume",
+			    ADAU1961_PLAY_MIXER_RIGHT0, 1, 8, 0, adau1961_sidetone_tlv),
+	SOC_DAPM_SINGLE_TLV("Right Bypass Volume",
+			    ADAU1961_PLAY_MIXER_RIGHT1, 4, 8, 0, adau1961_sidetone_tlv),
+	SOC_DAPM_SINGLE_TLV("Left Bypass Volume",
+			    ADAU1961_PLAY_MIXER_RIGHT1, 0, 8, 0, adau1961_sidetone_tlv),
+};
+
+static const struct snd_kcontrol_new adau1961_left_lr_mixer_controls[] = {
+	SOC_DAPM_SINGLE_TLV("Left Volume",
+			    ADAU1961_PLAY_LR_MIXER_LEFT, 1, 2, 0, adau1961_boost_tlv),
+	SOC_DAPM_SINGLE_TLV("Right Volume",
+			    ADAU1961_PLAY_LR_MIXER_LEFT, 3, 2, 0, adau1961_boost_tlv),
+};
+
+static const struct snd_kcontrol_new adau1961_right_lr_mixer_controls[] = {
+	SOC_DAPM_SINGLE_TLV("Left Volume",
+			    ADAU1961_PLAY_LR_MIXER_RIGHT, 1, 2, 0, adau1961_boost_tlv),
+	SOC_DAPM_SINGLE_TLV("Right Volume",
+			    ADAU1961_PLAY_LR_MIXER_RIGHT, 3, 2, 0, adau1961_boost_tlv),
+};
+
+static const struct snd_kcontrol_new adau1961_controls[] = {
+	SOC_DOUBLE_R_TLV("Digital Capture Volume",
+			 ADAU1961_LEFT_INPUT_DIGITAL_VOL,
+		ADAU1961_RIGHT_INPUT_DIGITAL_VOL,
+		0, 0xff, 1, adau1961_digital_tlv),
+	SOC_DOUBLE_R_TLV("Digital Playback Volume", ADAU1961_DAC_CONTROL1,
+			 ADAU1961_DAC_CONTROL2, 0, 0xff, 1, adau1961_digital_tlv),
+
+	SOC_SINGLE("ADC High Pass Filter Switch", ADAU1961_ADC_CONTROL,
+		   5, 1, 0),
+	SOC_SINGLE("Playback De-emphasis Switch", ADAU1961_DAC_CONTROL0,
+		   2, 1, 0),
+
+	SOC_DOUBLE_R_TLV("Aux Capture Volume", ADAU1961_REC_MIXER_LEFT1,
+			 ADAU1961_REC_MIXER_RIGHT1, 0, 7, 0, adau1961_sing_in_tlv),
+
+	SOC_DOUBLE_R_TLV("Headphone Playback Volume", ADAU1961_PLAY_HP_LEFT_VOL,
+			 ADAU1961_PLAY_HP_RIGHT_VOL, 2, 0x3f, 0, adau1961_out_tlv),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Headphone Playback Switch",
+		.info = snd_soc_info_volsw,
+		.get = snd_soc_get_volsw,
+		.put = upisnd_put_hp_mute_volsw,
+		.private_value = SOC_DOUBLE_R_VALUE(ADAU1961_PLAY_HP_LEFT_VOL,
+			ADAU1961_PLAY_HP_RIGHT_VOL, 1, 1, 0)
+	},
+	SOC_DOUBLE_R_TLV("Lineout Playback Volume", ADAU1961_PLAY_LINE_LEFT_VOL,
+			 ADAU1961_PLAY_LINE_RIGHT_VOL, 2, 0x3f, 0, adau1961_out_tlv),
+	SOC_DOUBLE_R("Lineout Playback Switch", ADAU1961_PLAY_LINE_LEFT_VOL,
+		     ADAU1961_PLAY_LINE_RIGHT_VOL, 1, 1, 0),
+	SOC_SINGLE("MicBias Switch", ADAU1961_MICBIAS, 0, 1, 0),
+};
+
+static int adau1961_dejitter_fixup(struct snd_soc_dapm_widget *w,
+				   struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+
+	// After any power changes have been made the dejitter circuit
+	// has to be reinitialized.
+	regmap_write(adau->regmap, ADAU1961_DEJITTER, 0);
+	if (!adau->master)
+		regmap_write(adau->regmap, ADAU1961_DEJITTER, 3);
+
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget adau1961_dapm_widgets[] = {
+	SND_SOC_DAPM_MIXER("Left Input Mixer", SND_SOC_NOPM, 0, 0,
+			   NULL, 0),
+	SND_SOC_DAPM_MIXER("Right Input Mixer", SND_SOC_NOPM, 0, 0,
+			   NULL, 0),
+
+	SND_SOC_DAPM_SUPPLY("MICBIAS", ADAU1961_MICBIAS, 0, 0, NULL, 0),
+
+	SOC_MIXER_ARRAY("Left Playback Mixer", ADAU1961_PLAY_MIXER_LEFT0,
+			0, 0, adau1961_left_mixer_controls),
+	SOC_MIXER_ARRAY("Right Playback Mixer", ADAU1961_PLAY_MIXER_RIGHT0,
+			0, 0, adau1961_right_mixer_controls),
+
+	SOC_MIXER_ARRAY("Left LR Playback Mixer", SND_SOC_NOPM,
+			0, 0, adau1961_left_lr_mixer_controls),
+	SOC_MIXER_ARRAY("Right LR Playback Mixer", SND_SOC_NOPM,
+			0, 0, adau1961_right_lr_mixer_controls),
+
+	SND_SOC_DAPM_SUPPLY("Headphone", ADAU1961_PLAY_HP_LEFT_VOL,
+			    0, 0, NULL, 0),
+
+	SND_SOC_DAPM_SUPPLY_S("SYSCLK", 2, SND_SOC_NOPM, 0, 0, NULL, 0),
+
+	SND_SOC_DAPM_POST("Dejitter fixup", adau1961_dejitter_fixup),
+
+	SND_SOC_DAPM_INPUT("LAUX"),
+	SND_SOC_DAPM_INPUT("RAUX"),
+	SND_SOC_DAPM_INPUT("LINP"),
+	SND_SOC_DAPM_INPUT("LINN"),
+	SND_SOC_DAPM_INPUT("RINP"),
+	SND_SOC_DAPM_INPUT("RINN"),
+
+	SND_SOC_DAPM_OUTPUT("LOUT"),
+	SND_SOC_DAPM_OUTPUT("ROUT"),
+	SND_SOC_DAPM_OUTPUT("LHP"),
+	SND_SOC_DAPM_OUTPUT("RHP"),
+
+	SND_SOC_DAPM_SUPPLY_S("PLL", 3, SND_SOC_NOPM, 0, 0, adau1961_pll_event,
+			      SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+	SND_SOC_DAPM_SUPPLY("AIFCLK", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+	SND_SOC_DAPM_SUPPLY("Left Playback Enable", SND_SOC_NOPM,
+			    0, 0, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("Right Playback Enable", SND_SOC_NOPM,
+			    1, 0, NULL, 0),
+
+	SND_SOC_DAPM_MUX("Left DAC Mode Mux", SND_SOC_NOPM, 0, 0,
+			 &adau1961_dac_mode_mux),
+	SND_SOC_DAPM_MUX("Right DAC Mode Mux", SND_SOC_NOPM, 0, 0,
+			 &adau1961_dac_mode_mux),
+
+	SND_SOC_DAPM_ADC_E("Left Decimator", NULL, ADAU1961_ADC_CONTROL, 0, 0,
+			   adau1961_adc_fixup, SND_SOC_DAPM_POST_PMU),
+	SND_SOC_DAPM_ADC("Right Decimator", NULL, ADAU1961_ADC_CONTROL, 1, 0),
+	SND_SOC_DAPM_DAC("Left DAC", NULL, ADAU1961_DAC_CONTROL0, 0, 0),
+	SND_SOC_DAPM_DAC("Right DAC", NULL, ADAU1961_DAC_CONTROL0, 1, 0),
+};
+
+static const struct snd_kcontrol_new adau1961_mono_mixer_controls[] = {
+	SOC_DAPM_SINGLE_TLV("Mono Mixer Gain", ADAU1961_PLAY_MIXER_MONO,
+			    1, 2, 0, adau1961_mono_output_tlv),
+};
+
+static const struct snd_soc_dapm_widget adau1961_mono_dapm_widgets[] = {
+	SND_SOC_DAPM_MIXER("Mono Playback Mixer", ADAU1961_PLAY_MIXER_MONO,
+			   0, 0, adau1961_mono_mixer_controls,
+			   ARRAY_SIZE(adau1961_mono_mixer_controls)),
+
+	SND_SOC_DAPM_OUTPUT("MONOOUT"),
+};
+
+static const struct snd_soc_dapm_widget adau1961_capless_dapm_widgets[] = {
+	SND_SOC_DAPM_SUPPLY_S("Headphone VGND", 1, ADAU1961_PLAY_MIXER_MONO,
+			      0, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route adau1961_dapm_routes[] = {
+	{ "Left Input Mixer", NULL, "LINP" },
+	{ "Left Input Mixer", NULL, "LINN" },
+	{ "Left Input Mixer", NULL, "LAUX" },
+
+	{ "Right Input Mixer", NULL, "RINP" },
+	{ "Right Input Mixer", NULL, "RINN" },
+	{ "Right Input Mixer", NULL, "RAUX" },
+
+	{ "Left Input Mixer", NULL, "MICBIAS" },
+	{ "Right Input Mixer", NULL, "MICBIAS" },
+
+	{ "Left Playback Mixer", NULL, "Left Playback Enable"},
+	{ "Right Playback Mixer", NULL, "Right Playback Enable"},
+	{ "Left LR Playback Mixer", NULL, "Left Playback Enable"},
+	{ "Right LR Playback Mixer", NULL, "Right Playback Enable"},
+
+	{ "Left Playback Mixer", "Left DAC Switch", "Left DAC" },
+	{ "Left Playback Mixer", "Right DAC Switch", "Right DAC" },
+
+	{ "Right Playback Mixer", "Left DAC Switch", "Left DAC" },
+	{ "Right Playback Mixer", "Right DAC Switch", "Right DAC" },
+
+	{ "Left LR Playback Mixer", "Left Volume", "Left Playback Mixer" },
+	{ "Left LR Playback Mixer", "Right Volume", "Right Playback Mixer" },
+
+	{ "Right LR Playback Mixer", "Left Volume", "Left Playback Mixer" },
+	{ "Right LR Playback Mixer", "Right Volume", "Right Playback Mixer" },
+
+	{ "LHP", NULL, "Left Playback Mixer" },
+	{ "RHP", NULL, "Right Playback Mixer" },
+
+	{ "LHP", NULL, "Headphone" },
+	{ "RHP", NULL, "Headphone" },
+
+	{ "LOUT", NULL, "Left LR Playback Mixer" },
+	{ "ROUT", NULL, "Right LR Playback Mixer" },
+
+	{ "Left Playback Mixer", "Aux Bypass Volume", "LAUX" },
+	{ "Left Playback Mixer", "Left Bypass Volume", "Left Input Mixer" },
+	{ "Left Playback Mixer", "Right Bypass Volume", "Right Input Mixer" },
+	{ "Right Playback Mixer", "Aux Bypass Volume", "RAUX" },
+	{ "Right Playback Mixer", "Left Bypass Volume", "Left Input Mixer" },
+	{ "Right Playback Mixer", "Right Bypass Volume", "Right Input Mixer" },
+
+	{ "Left Decimator", NULL, "SYSCLK" },
+	{ "Right Decimator", NULL, "SYSCLK" },
+	{ "Left DAC", NULL, "SYSCLK" },
+	{ "Right DAC", NULL, "SYSCLK" },
+	{ "Capture", NULL, "SYSCLK" },
+	{ "Playback", NULL, "SYSCLK" },
+
+	{ "Left DAC", NULL, "Left DAC Mode Mux" },
+	{ "Right DAC", NULL, "Right DAC Mode Mux" },
+
+	{ "Capture", NULL, "AIFCLK" },
+	{ "Playback", NULL, "AIFCLK" },
+
+	{ "Left DAC Mode Mux", "Stereo", "Playback" },
+	{ "Left DAC Mode Mux", "Mono (L+R)", "Playback" },
+	{ "Left DAC Mode Mux", "Mono Left Channel (L+R)", "Playback" },
+	{ "Right DAC Mode Mux", "Stereo", "Playback" },
+	{ "Right DAC Mode Mux", "Mono (L+R)", "Playback" },
+	{ "Right DAC Mode Mux", "Mono Right Channel (L+R)", "Playback" },
+	{ "Capture", NULL, "Left Decimator" },
+	{ "Capture", NULL, "Right Decimator" },
+
+	{ "Left Decimator", NULL, "Left Input Mixer" },
+	{ "Right Decimator", NULL, "Right Input Mixer" },
+};
+
+static const struct snd_soc_dapm_route adau1961_mono_dapm_routes[] = {
+	{ "Mono Playback Mixer", NULL, "Left Playback Mixer" },
+	{ "Mono Playback Mixer", NULL, "Right Playback Mixer" },
+
+	{ "MONOOUT", NULL, "Mono Playback Mixer" },
+};
+
+static const struct snd_soc_dapm_route adau1961_capless_dapm_routes[] = {
+	{ "Headphone", NULL, "Headphone VGND" },
+};
+
+static int adau1961_set_bias_level(struct snd_soc_component *component,
+				   enum snd_soc_bias_level level)
+{
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		regcache_cache_only(adau->regmap, false);
+		regmap_update_bits(adau->regmap, ADAU1961_CLOCK_CONTROL,
+				   ADAU1961_CLOCK_CONTROL_SYSCLK_EN,
+			ADAU1961_CLOCK_CONTROL_SYSCLK_EN);
+		if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF)
+			regcache_sync(adau->regmap);
+		break;
+	case SND_SOC_BIAS_OFF:
+		regmap_update_bits(adau->regmap, ADAU1961_CLOCK_CONTROL,
+				   ADAU1961_CLOCK_CONTROL_SYSCLK_EN, 0);
+		regcache_cache_only(adau->regmap, true);
+		break;
+	}
+	return 0;
+}
+
+static enum adau1961_output_mode adau1961_get_lineout_mode(struct snd_soc_component *component)
+{
+	struct adau1961_platform_data *pdata = component->dev->platform_data;
+
+	if (pdata)
+		return pdata->lineout_mode;
+
+	return ADAU1961_OUTPUT_MODE_LINE;
+}
+
+static enum adau1961_output_mode adau1961_get_monoout_mode(struct snd_soc_component *component)
+{
+	struct adau1961_platform_data *pdata = component->dev->platform_data;
+
+	if (pdata)
+		return pdata->monoout_mode;
+
+	return ADAU1961_OUTPUT_MODE_LINE;
+}
+
+static int adau1961_setup_headphone_mode(struct snd_soc_component *component)
+{
+	struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+	struct adau1961_platform_data *pdata = component->dev->platform_data;
+	enum adau1961_output_mode mode;
+	int ret;
+
+	if (pdata)
+		mode = pdata->headphone_mode;
+	else
+		mode = ADAU1961_OUTPUT_MODE_HEADPHONE;
+
+	switch (mode) {
+	case ADAU1961_OUTPUT_MODE_LINE:
+		break;
+	case ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS:
+		regmap_update_bits(adau->regmap, ADAU1961_PLAY_MONO_OUTPUT_VOL,
+				   ADAU1961_PLAY_MONO_OUTPUT_VOL_MODE_HP |
+			ADAU1961_PLAY_MONO_OUTPUT_VOL_UNMUTE,
+			ADAU1961_PLAY_MONO_OUTPUT_VOL_MODE_HP |
+			ADAU1961_PLAY_MONO_OUTPUT_VOL_UNMUTE);
+		fallthrough;
+	case ADAU1961_OUTPUT_MODE_HEADPHONE:
+		regmap_update_bits(adau->regmap, ADAU1961_PLAY_HP_RIGHT_VOL,
+				   ADAU1961_PLAY_HP_RIGHT_VOL_MODE_HP,
+			ADAU1961_PLAY_HP_RIGHT_VOL_MODE_HP);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (mode == ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS) {
+		ret = snd_soc_dapm_new_controls(dapm,
+						adau1961_capless_dapm_widgets,
+			ARRAY_SIZE(adau1961_capless_dapm_widgets));
+		if (ret)
+			return ret;
+		ret = snd_soc_dapm_add_routes(dapm,
+					      adau1961_capless_dapm_routes,
+			ARRAY_SIZE(adau1961_capless_dapm_routes));
+	} else {
+		ret = snd_soc_add_component_controls(component, adau1961_mono_controls,
+						     ARRAY_SIZE(adau1961_mono_controls));
+		if (ret)
+			return ret;
+		ret = snd_soc_dapm_new_controls(dapm,
+						adau1961_mono_dapm_widgets,
+			ARRAY_SIZE(adau1961_mono_dapm_widgets));
+		if (ret)
+			return ret;
+		ret = snd_soc_dapm_add_routes(dapm,
+					      adau1961_mono_dapm_routes,
+			ARRAY_SIZE(adau1961_mono_dapm_routes));
+	}
+
+	return ret;
+}
+
+static bool adau1961_readable_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case ADAU1961_REC_MIXER_LEFT0:
+	case ADAU1961_REC_MIXER_LEFT1:
+	case ADAU1961_REC_MIXER_RIGHT0:
+	case ADAU1961_REC_MIXER_RIGHT1:
+	case ADAU1961_LEFT_DIFF_INPUT_VOL:
+	case ADAU1961_RIGHT_DIFF_INPUT_VOL:
+	case ADAU1961_PLAY_LR_MIXER_LEFT:
+	case ADAU1961_PLAY_MIXER_LEFT0:
+	case ADAU1961_PLAY_MIXER_LEFT1:
+	case ADAU1961_PLAY_MIXER_RIGHT0:
+	case ADAU1961_PLAY_MIXER_RIGHT1:
+	case ADAU1961_PLAY_LR_MIXER_RIGHT:
+	case ADAU1961_PLAY_MIXER_MONO:
+	case ADAU1961_PLAY_HP_LEFT_VOL:
+	case ADAU1961_PLAY_HP_RIGHT_VOL:
+	case ADAU1961_PLAY_LINE_LEFT_VOL:
+	case ADAU1961_PLAY_LINE_RIGHT_VOL:
+	case ADAU1961_PLAY_MONO_OUTPUT_VOL:
+	case ADAU1961_POP_CLICK_SUPPRESS:
+	case ADAU1961_JACK_DETECT_PIN:
+	case ADAU1961_DEJITTER:
+	case ADAU1961_ALC_CTRL0:
+	case ADAU1961_ALC_CTRL1:
+	case ADAU1961_ALC_CTRL2:
+	case ADAU1961_ALC_CTRL3:
+	case ADAU1961_CLOCK_CONTROL:
+	case ADAU1961_PLL_CONTROL:
+	case ADAU1961_MICBIAS:
+	case ADAU1961_SERIAL_PORT0:
+	case ADAU1961_SERIAL_PORT1:
+	case ADAU1961_CONVERTER0:
+	case ADAU1961_CONVERTER1:
+	case ADAU1961_LEFT_INPUT_DIGITAL_VOL:
+	case ADAU1961_RIGHT_INPUT_DIGITAL_VOL:
+	case ADAU1961_ADC_CONTROL:
+	case ADAU1961_PLAY_POWER_MGMT:
+	case ADAU1961_DAC_CONTROL0:
+	case ADAU1961_DAC_CONTROL1:
+	case ADAU1961_DAC_CONTROL2:
+	case ADAU1961_SERIAL_PORT_PAD:
+	case ADAU1961_CONTROL_PORT_PAD0:
+	case ADAU1961_CONTROL_PORT_PAD1:
+		return true;
+	default:
+		break;
+	}
+
+	return false;
+}
+
+static int adau1961_component_probe(struct snd_soc_component *component)
+{
+	struct adau1961_platform_data *pdata = component->dev->platform_data;
+	struct adau *adau = snd_soc_component_get_drvdata(component);
+	int ret;
+
+	if (pdata && pdata->input_differential) {
+		regmap_update_bits(adau->regmap, ADAU1961_LEFT_DIFF_INPUT_VOL,
+				   ADAU1961_DIFF_INPUT_VOL_LDEN,
+			ADAU1961_DIFF_INPUT_VOL_LDEN);
+		regmap_update_bits(adau->regmap, ADAU1961_RIGHT_DIFF_INPUT_VOL,
+				   ADAU1961_DIFF_INPUT_VOL_LDEN,
+			ADAU1961_DIFF_INPUT_VOL_LDEN);
+		ret = snd_soc_add_component_controls(component,
+						     adau1961_differential_mode_controls,
+			ARRAY_SIZE(adau1961_differential_mode_controls));
+		if (ret)
+			return ret;
+	} else {
+		ret = snd_soc_add_component_controls(component,
+						     adau1961_single_mode_controls,
+			ARRAY_SIZE(adau1961_single_mode_controls));
+		if (ret)
+			return ret;
+	}
+
+	switch (adau1961_get_lineout_mode(component)) {
+	case ADAU1961_OUTPUT_MODE_LINE:
+		break;
+	case ADAU1961_OUTPUT_MODE_HEADPHONE:
+		regmap_update_bits(adau->regmap, ADAU1961_PLAY_LINE_LEFT_VOL,
+				   ADAU1961_PLAY_LINE_LEFT_VOL_MODE_HP,
+			ADAU1961_PLAY_LINE_LEFT_VOL_MODE_HP);
+		regmap_update_bits(adau->regmap, ADAU1961_PLAY_LINE_RIGHT_VOL,
+				   ADAU1961_PLAY_LINE_RIGHT_VOL_MODE_HP,
+			ADAU1961_PLAY_LINE_RIGHT_VOL_MODE_HP);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (adau1961_get_monoout_mode(component)) {
+	case ADAU1961_OUTPUT_MODE_LINE:
+		break;
+	case ADAU1961_OUTPUT_MODE_HEADPHONE:
+		regmap_update_bits(adau->regmap, ADAU1961_PLAY_MONO_OUTPUT_VOL,
+				   ADAU1961_PLAY_MONO_OUTPUT_VOL_MODE_HP,
+			ADAU1961_PLAY_MONO_OUTPUT_VOL_MODE_HP);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	ret = adau1961_setup_headphone_mode(component);
+	if (ret)
+		return ret;
+
+	ret = adau1961_add_routes(component);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static const struct snd_soc_component_driver adau1961_component_driver = {
+	.probe                 = adau1961_component_probe,
+	.resume                = adau1961_resume,
+	.set_bias_level        = adau1961_set_bias_level,
+	.controls              = adau1961_controls,
+	.num_controls          = ARRAY_SIZE(adau1961_controls),
+	.dapm_widgets          = adau1961_dapm_widgets,
+	.num_dapm_widgets      = ARRAY_SIZE(adau1961_dapm_widgets),
+	.dapm_routes           = adau1961_dapm_routes,
+	.num_dapm_routes       = ARRAY_SIZE(adau1961_dapm_routes),
+	.suspend_bias_off      = 1,
+	.idle_bias_on          = 1,
+	.use_pmdown_time       = 1,
+	.endianness            = 1,
+	.legacy_dai_naming     = 0,
+};
+
+#define ADAU1961_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \
+	SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_driver adau1961_dai_driver = {
+	.name = "adau-hifi",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_96000,
+		.formats = ADAU1961_FORMATS,
+	},
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_96000,
+		.formats = ADAU1961_FORMATS,
+	},
+	.ops = &adau1961_dai_ops,
+};
+
+static int adau1961_probe(struct device *dev, struct regmap *regmap,
+			  void (*switch_mode)(struct device *dev))
+{
+	struct snd_soc_dai_driver *dai_drv;
+	int ret;
+
+	dai_drv = &adau1961_dai_driver;
+
+	struct adau *adau;
+
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	adau = devm_kzalloc(dev, sizeof(*adau), GFP_KERNEL);
+	if (!adau)
+		return -ENOMEM;
+
+	adau->mclk = devm_clk_get(dev, "mclk");
+	if (IS_ERR(adau->mclk)) {
+		if (PTR_ERR(adau->mclk) != -ENOENT)
+			return PTR_ERR(adau->mclk);
+		/* Clock is optional (for the driver) */
+		adau->mclk = NULL;
+	} else if (adau->mclk) {
+		adau->clk_src = ADAU1961_CLK_SRC_PLL_AUTO;
+
+		/*
+		 * Any valid PLL output rate will work at this point, use one
+		 * that is likely to be chosen later as well. The register will
+		 * be written when the PLL is powered up for the first time.
+		 */
+		ret = adau_calc_pll_cfg(clk_get_rate(adau->mclk), 48000 * 1024,
+					adau->pll_regs);
+		if (ret < 0)
+			return ret;
+
+		ret = clk_prepare_enable(adau->mclk);
+		if (ret)
+			return ret;
+	}
+
+	adau->regmap = regmap;
+	adau->switch_mode = switch_mode;
+
+	dev_set_drvdata(dev, adau);
+
+	if (switch_mode)
+		switch_mode(dev);
+
+	// Enable cache only mode as we could miss writes before bias level
+	// reaches standby and the core clock is enabled
+	regcache_cache_only(regmap, true);
+
+	return devm_snd_soc_register_component(dev, &adau1961_component_driver, dai_drv, 1);
+}
+
+static const struct regmap_config adau1961_regmap_config = {
+	.val_bits = 8,
+	.reg_bits = 16,
+	.max_register = 0x4036,
+	.reg_defaults = adau1961_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(adau1961_reg_defaults),
+	.readable_reg = adau1961_readable_register,
+	.volatile_reg = adau1961_volatile_register,
+	.precious_reg = adau1961_precious_register,
+	.cache_type = REGCACHE_RBTREE,
+};
+
+static int upisnd_codec_fill_pdata(struct device_node *of_node,
+				   struct adau1961_platform_data *pdata)
+{
+	const char *input_mode = "differential";
+	const char *hp_out_mode = "capless-headphone";
+	const char *line_out_mode = "line-out";
+	const char *mono_out_mode = "line-out";
+
+	of_property_read_string(of_node, "input-mode", &input_mode);
+	of_property_read_string(of_node, "hp-out-mode", &hp_out_mode);
+	of_property_read_string(of_node, "line-out-mode", &line_out_mode);
+	of_property_read_string(of_node, "mono-out-mode", &mono_out_mode);
+
+	if (strncmp(input_mode, "differential", 13) == 0) {
+		pdata->input_differential = true;
+	} else if (strncmp(input_mode, "single-ended", 13) == 0) {
+		pdata->input_differential = false;
+	} else {
+		printe("Invalid input mode: %s", input_mode);
+		return -EINVAL;
+	}
+
+	if (strncmp(hp_out_mode, "headphone", 10) == 0) {
+		pdata->headphone_mode = ADAU1961_OUTPUT_MODE_HEADPHONE;
+	} else if (strncmp(hp_out_mode, "capless-headphone", 18) == 0) {
+		pdata->headphone_mode = ADAU1961_OUTPUT_MODE_HEADPHONE_CAPLESS;
+	} else if (strncmp(hp_out_mode, "line-out", 9) == 0) {
+		pdata->headphone_mode = ADAU1961_OUTPUT_MODE_LINE;
+	} else {
+		printe("Invalid headphone output mode: %s", hp_out_mode);
+		return -EINVAL;
+	}
+
+	if (strncmp(line_out_mode, "line-out", 9) == 0) {
+		pdata->lineout_mode = ADAU1961_OUTPUT_MODE_LINE;
+	} else if (strncmp(line_out_mode, "headphone", 10) == 0) {
+		pdata->lineout_mode = ADAU1961_OUTPUT_MODE_HEADPHONE;
+	} else {
+		printe("Invalid line-out mode: %s", line_out_mode);
+		return -EINVAL;
+	}
+
+	if (strncmp(mono_out_mode, "line-out", 9) == 0) {
+		pdata->monoout_mode = ADAU1961_OUTPUT_MODE_LINE;
+	} else if (strncmp(line_out_mode, "headphone", 10) == 0) {
+		pdata->monoout_mode = ADAU1961_OUTPUT_MODE_HEADPHONE;
+	} else {
+		printe("Invalid mono-out mode: %s", mono_out_mode);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int upisnd_codec_i2c_probe(struct i2c_client *client)
+{
+	struct adau1961_platform_data *pdata = NULL;
+
+	if (client->dev.of_node) {
+		pdata = devm_kzalloc(&client->dev,
+				     sizeof(struct adau1961_platform_data), GFP_KERNEL);
+
+		if (!pdata)
+			return -ENOMEM;
+
+		int err = upisnd_codec_fill_pdata(client->dev.of_node, pdata);
+
+		if (err < 0) {
+			printe("Failed to fill platform data from device tree! (%d)", err);
+			return err;
+		}
+	}
+
+	client->dev.platform_data = pdata;
+
+	struct regmap_config config;
+
+	config = adau1961_regmap_config;
+	config.val_bits = 8;
+	config.reg_bits = 16;
+
+	return adau1961_probe(&client->dev, devm_regmap_init_i2c(client, &config), NULL);
+}
+
+static void upisnd_codec_i2c_remove(struct i2c_client *client)
+{
+	adau1961_remove(&client->dev);
+}
+
+static const struct i2c_device_id upisnd_codec_i2c_ids[] = {
+	{ "upisnd-codec", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, upisnd_codec_i2c_ids);
+
+static const struct of_device_id upisnd_codec_i2c_dt_ids[] = {
+	{ .compatible = "blokas,upisnd-codec", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, upisnd_codec_i2c_dt_ids);
+
+struct i2c_driver upisnd_codec_i2c_driver = {
+	.driver = {
+		.name = "upisnd_codec",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(upisnd_codec_i2c_dt_ids),
+	},
+	.probe = upisnd_codec_i2c_probe,
+	.remove = upisnd_codec_i2c_remove,
+	.id_table = upisnd_codec_i2c_ids,
+};
+module_i2c_driver(upisnd_codec_i2c_driver);
+
+MODULE_DESCRIPTION("Codec Driver for Pisound Micro");
+MODULE_AUTHOR("Giedrius Trainavi\xc4\x8dius <giedrius@blokas.io>");
+MODULE_LICENSE("GPL v2");
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/drivers/upisnd/upisnd_codec.h b/sound/drivers/upisnd/upisnd_codec.h
new file mode 100644
index 000000000000..c82be33711ff
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_codec.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+ #ifndef UPISOUND_CODEC_H
+ #define UPISOUND_CODEC_H
+
+extern void adau1961_set_vgnd_shorted(struct snd_soc_component *component, bool shorted);
+extern bool adau1961_is_hp_capless(struct snd_soc_component *component);
+
+#endif // UPISOUND_CODEC_H
diff --git a/sound/drivers/upisnd/upisnd_comm.c b/sound/drivers/upisnd/upisnd_comm.c
new file mode 100644
index 000000000000..361098324c1d
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_comm.c
@@ -0,0 +1,638 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "upisnd_common.h"
+
+DECLARE_CRC8_TABLE(upisnd_crc8_table);
+enum { UPISND_CRC8_POLYNOMIAL = 0x83 };
+
+static upisnd_msg_id_t upisnd_next_msg_id(struct upisnd_instance *instance)
+{
+	instance->comm.msg_id_counter = instance->comm.msg_id_counter != 127 ?
+		instance->comm.msg_id_counter + 1 : 1;
+
+	return instance->comm.msg_id_counter;
+}
+
+struct upisnd_command_task_t {
+	struct list_head   list;
+	upisnd_msg_id_t    msg_id;
+	struct completion  done;
+	int                result;
+	u8                 cmd[UPISND_MAX_PACKET_LENGTH];
+	u8                 response[UPISND_MAX_PACKET_LENGTH];
+};
+
+static void upisnd_command_response(struct upisnd_command_task_t *task,
+				    const struct upisnd_cmd_t *response,
+				    unsigned int n)
+{
+	memcpy(task->response, response, min_t(unsigned int, n, UPISND_MAX_PACKET_LENGTH));
+	if (upisnd_cmd_matches(response, UPISND_CMD_RESULT, sizeof(struct upisnd_cmd_result_t))) {
+		int response_code = ((const struct upisnd_cmd_result_t *)response)->result;
+
+		printd("hi %p %d %d", task, response_code == -ETIME, response_code);
+		task->result = response_code;
+	} else {
+		task->result = 0;
+	}
+}
+
+static void upisnd_command_timeout(struct upisnd_command_task_t *task)
+{
+	struct upisnd_cmd_result_t result;
+
+	result.cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_RESULT, sizeof(result));
+	result.cmd.flags_and_msg_id = upisnd_msg_id_encode(task->msg_id, true);
+	result.result = -ETIME;
+	upisnd_command_response(task, &result.cmd, sizeof(result));
+}
+
+static int upisnd_i2c_send(struct i2c_client *client, const void *data, int n)
+{
+	u8 buffer[UPISND_MAX_PACKET_LENGTH + 1];
+
+	memcpy(buffer, data, n);
+	buffer[n] = ~crc8(upisnd_crc8_table, buffer, n, CRC8_INIT_VALUE);
+	++n;
+
+#ifdef UPISOUND_DEBUG
+	printk(KERN_DEBUG "%s: Sending %d bytes:", __func__, n);
+	int i;
+
+	for (i = 0; i < n; ++i)
+		printk(KERN_CONT " %02x", buffer[i]);
+	printk(KERN_CONT "\n");
+
+	u8 check = crc8(upisnd_crc8_table, buffer, n, CRC8_INIT_VALUE);
+
+	printd("CRC CHECK: %u, good: %u", check, CRC8_GOOD_VALUE(upisnd_crc8_table));
+#endif
+
+	return i2c_master_send(client, buffer, n);
+}
+
+static void upisnd_command_send(struct upisnd_instance *instance,
+				struct upisnd_command_task_t *task,
+				struct upisnd_cmd_t *cmd)
+{
+	mutex_lock(&instance->comm.comm_lock);
+	upisnd_i2c_send(instance->comm.client, cmd, upisnd_cmd_decode_length(cmd->cmd_and_size));
+	mutex_unlock(&instance->comm.comm_lock);
+}
+
+static void upisnd_init_command_task(struct upisnd_command_task_t *task)
+{
+	memset(task, 0, sizeof(struct upisnd_command_task_t));
+	init_completion(&task->done);
+	task->result = -EINPROGRESS;
+	task->cmd[0] = UPISND_CMD_INVALID;
+}
+
+static int upisnd_execute_void_command(struct upisnd_instance *instance, const void *cmd)
+{
+	mutex_lock(&instance->comm.comm_lock);
+	int result = upisnd_i2c_send(instance->comm.client, cmd,
+		upisnd_cmd_decode_length(*(const uint8_t *)cmd));
+
+	mutex_unlock(&instance->comm.comm_lock);
+	return result;
+}
+
+static int upisnd_execute_command(struct upisnd_instance *instance,
+				  struct upisnd_command_task_t *task)
+{
+	struct upisnd_cmd_t *cmd = (struct upisnd_cmd_t *)task->cmd;
+
+	mutex_lock(&instance->comm.msg_lock);
+	upisnd_msg_id_t msg_id = upisnd_next_msg_id(instance);
+
+	cmd->flags_and_msg_id = upisnd_msg_id_encode(msg_id, false);
+	task->msg_id = msg_id;
+	list_add_tail(&task->list, &instance->comm.msg_handlers);
+	mutex_unlock(&instance->comm.msg_lock);
+
+	upisnd_command_send(instance, task, cmd);
+
+	printd("Before wait");
+	unsigned long t = wait_for_completion_io_timeout(&task->done, msecs_to_jiffies(5000u));
+	(void)t;
+
+	mutex_lock(&instance->comm.msg_lock);
+	if (!completion_done(&task->done)) {
+		printe("Message %d timed out!", msg_id);
+		upisnd_command_timeout(task);
+	}
+	list_del(&task->list);
+	mutex_unlock(&instance->comm.msg_lock);
+	printd("Wait complete! %lu %d", t, task->result);
+
+	return task->result;
+}
+
+static void upisnd_handle_response(struct upisnd_instance *instance,
+				   const struct upisnd_cmd_t *response,
+				   unsigned int n)
+{
+	upisnd_msg_id_t msg_id = upisnd_msg_id_decode_id(response->flags_and_msg_id);
+
+	printd("Response to %d", msg_id);
+
+	struct list_head *p;
+	struct upisnd_command_task_t *t;
+
+	mutex_lock(&instance->comm.msg_lock);
+	list_for_each(p, &instance->comm.msg_handlers) {
+		t = list_entry(p, struct upisnd_command_task_t, list);
+		if (t->msg_id == msg_id) {
+			upisnd_command_response(t, response, n);
+			t->msg_id = UPISND_MSG_ID_INVALID;
+			complete_all(&t->done);
+			break;
+		}
+	}
+	mutex_unlock(&instance->comm.msg_lock);
+}
+
+void upisnd_handle_comm_interrupt(struct upisnd_instance *instance)
+{
+	printd("Comm handler");
+
+	u8 data[UPISND_MAX_PACKET_LENGTH + 1];
+
+	mutex_lock(&instance->comm.comm_lock);
+	int i, n = 0, err;
+
+	err = i2c_master_recv(instance->comm.client, data, 3);
+
+	if (err == 3 && data[0] != UPISND_CMD_INVALID) {
+		n = upisnd_cmd_decode_length(data[0]) + 1; // Includes CRC8 byte at the end.
+		if (n > 3) {
+			err = i2c_master_recv(instance->comm.client, data + 3, n - 3);
+			if (err != n - 3) {
+				printe("Error occurred when receiving data over I2C! (%d)", err);
+				mutex_unlock(&instance->comm.comm_lock);
+				return;
+			}
+		}
+	} else {
+		printe("Error occurred when receiving data over I2C! (%d)", err);
+	}
+
+#ifdef UPISOUND_DEBUG
+	printk(KERN_DEBUG "upisnd_handle_comm_interrupt: Read %d bytes:", n);
+	for (i = 0; i < n; ++i)
+		printk(KERN_CONT " %02x", data[i]);
+	printk(KERN_CONT "\n");
+#endif
+
+	mutex_unlock(&instance->comm.comm_lock);
+
+	if (n <= 0 || data[0] == UPISND_CMD_INVALID) {
+		printe("Error occurred when receiving data over I2C! (%d)", n);
+		return;
+	}
+
+	u8 crc = crc8(upisnd_crc8_table, data, n, CRC8_INIT_VALUE);
+
+	if (crc != CRC8_GOOD_VALUE(upisnd_crc8_table)) {
+		printe("CRC check failed, calculated value: %02x (expected value: %02x)",
+		       crc, CRC8_GOOD_VALUE(upisnd_crc8_table));
+		return;
+	}
+
+	enum upisnd_cmd_type_e type = upisnd_cmd_decode_type(data[0]);
+
+	if (upisnd_cmd_type_has_msg_id(type)) {
+		upisnd_handle_response(instance, (const struct upisnd_cmd_t *)data, n);
+	} else {
+		switch (type) {
+		case UPISND_CMD_MIDI:
+			instance->comm.handler_ops->handle_midi_data(instance,
+				&data[1],
+				upisnd_cmd_decode_length(data[0]) - 1
+			);
+			break;
+		case UPISND_CMD_IRQ_EVENT:
+			{
+				int j = 0;
+				int k = 0;
+				struct irq_event_t gpio_events[15];
+				struct irq_event_t sound_events[15];
+				unsigned int n = upisnd_cmd_decode_length(data[0]) - 1;
+
+				for (i = 0; i < n; ++i) {
+					struct irq_event_t e;
+
+					e.num = data[i + 1] & UPISND_IRQ_NUM_MASK;
+					e.high = (data[i + 1] & UPISND_ON_BIT_MASK) != 0;
+
+					switch (e.num) {
+					case UPISND_IRQ_VGND_SHORT_ALERT:
+						sound_events[j++] = e;
+						break;
+					case UPISND_IRQ_GPIO_START...UPISND_IRQ_GPIO_END:
+						gpio_events[k++] = e;
+						break;
+					default:
+						printe("Unknown IRQ event %d", e.num);
+						continue;
+					}
+				}
+
+				if (j > 0) {
+					instance->comm.handler_ops->handle_sound_irq_events
+						(instance,
+						sound_events,
+						j);
+				}
+				if (k > 0) {
+					instance->comm.handler_ops->handle_gpio_irq_events
+						(instance,
+						gpio_events,
+						k);
+				}
+			}
+			break;
+		case UPISND_CMD_CONTROL_EVENT:
+			{
+				const struct upisnd_cmd_control_event_t *cmd =
+					(const struct upisnd_cmd_control_event_t *)data;
+				int i;
+				unsigned int n = upisnd_cmd_decode_length(data[0])
+					/ sizeof(uint16_t);
+				struct control_event_t events[7];
+
+				printd("Received %u control events", n);
+				for (i = 0; i < n; ++i) {
+					u16 e = ntohs(cmd->values[i]);
+
+					events[i].pin = e >> 10;
+					events[i].raw_value = e & 0x3ff;
+					printd("%d: %d 0x%04x u=%u d=%d", i, events[i].pin,
+					       events[i].raw_value, events[i].raw_value,
+						events[i].raw_value);
+				}
+
+				printd("Start handling control events");
+				instance->comm.handler_ops->handle_control_events(instance,
+										  events,
+										  n);
+				printd("Done handling control events");
+			}
+			break;
+		default:
+			printe("Unknown command received (%02x)", data[0]);
+			break;
+		}
+	}
+}
+
+int upisnd_comm_module_init(void)
+{
+	crc8_populate_msb(upisnd_crc8_table, UPISND_CRC8_POLYNOMIAL);
+	return 0;
+}
+
+int upisnd_comm_init(struct upisnd_instance *instance,
+		     struct i2c_client *client,
+		     const struct upisnd_comm_handler_ops *ops)
+{
+	struct upisnd_comm *comm = &instance->comm;
+
+	mutex_init(&comm->comm_lock);
+	mutex_init(&comm->msg_lock);
+
+	comm->client = client;
+	comm->handler_ops = ops;
+
+	INIT_LIST_HEAD(&comm->msg_handlers);
+	comm->msg_id_counter = 0;
+
+	return 0;
+}
+
+int upisnd_comm_send_midi(struct upisnd_instance *instance, const void *data, unsigned int n)
+{
+	if (n == 0 || n >= UPISND_MAX_PACKET_LENGTH)
+		return -EINVAL;
+
+	u8 buffer[UPISND_MAX_PACKET_LENGTH];
+
+	buffer[0] = upisnd_cmd_encode(UPISND_CMD_MIDI, n + 1);
+	memcpy(&buffer[1], data, n);
+
+	mutex_lock(&instance->comm.comm_lock);
+	int err = upisnd_i2c_send(instance->comm.client, buffer, n + 1);
+
+	mutex_unlock(&instance->comm.comm_lock);
+
+	if (err < 0)
+		printe("Error occurred when sending MIDI data over I2C! (%d)", err);
+
+	return err;
+}
+
+int upisnd_comm_commit_setup(struct upisnd_instance *instance, upisnd_setup_t setup)
+{
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_setup_t *cmd = (struct upisnd_cmd_setup_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SETUP, sizeof(*cmd));
+	cmd->setup = htonl(setup);
+
+	int result = upisnd_execute_command(instance, &task);
+
+	return result <= 0 ? result : 0;
+}
+
+int upisnd_comm_gpio_set(struct upisnd_instance *instance, uint8_t pin, bool high)
+{
+	struct upisnd_cmd_set_gpio_t cmd;
+
+	cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_GPIO, sizeof(cmd));
+	cmd.on_and_pin = pin | (high ? UPISND_ON_BIT_MASK : 0x00);
+
+	return upisnd_execute_void_command(instance, &cmd);
+}
+
+int upisnd_comm_gpio_get(struct upisnd_instance *instance, uint8_t pin)
+{
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_get_gpio_t *cmd = (struct upisnd_cmd_get_gpio_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET_GPIO, sizeof(*cmd));
+	cmd->pin = pin;
+
+	return upisnd_execute_command(instance, &task);
+}
+
+int upisnd_comm_gpio_set_all(struct upisnd_instance *instance,
+			     const struct upisnd_all_gpio_state_t *state)
+{
+	struct upisnd_cmd_set_all_gpios_t cmd;
+
+	cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_ALL_GPIOS, sizeof(cmd));
+
+	memcpy(cmd.state.state, state->state, sizeof(cmd.state));
+
+	return upisnd_execute_void_command(instance, &cmd);
+}
+
+int upisnd_comm_gpio_get_all(struct upisnd_instance *instance,
+			     struct upisnd_all_gpio_state_t *result)
+{
+	int err;
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_get_all_gpios_t *cmd = (struct upisnd_cmd_get_all_gpios_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET_ALL_GPIOS,
+						  sizeof(struct upisnd_cmd_get_all_gpios_t));
+
+	err = upisnd_execute_command(instance, &task);
+	if (err >= 0) {
+		const struct upisnd_cmd_get_all_gpios_response_t *resp =
+			(const struct upisnd_cmd_get_all_gpios_response_t *)task.response;
+
+		if (upisnd_cmd_is_response(&resp->cmd) &&
+		    upisnd_cmd_decode_type(resp->cmd.cmd_and_size) == UPISND_CMD_GET_ALL_GPIOS &&
+		    upisnd_cmd_decode_length(resp->cmd.cmd_and_size) ==
+		    sizeof(struct upisnd_cmd_get_all_gpios_response_t)) {
+			memcpy(result->state, resp->state.state, sizeof(result->state));
+			err = 0;
+		} else {
+			err = -EINVAL;
+		}
+	}
+
+	return err;
+}
+
+int upisnd_comm_set_irq_types(struct upisnd_instance *instance,
+			      const struct upisnd_irq_type_config_t *irq_type_config)
+{
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_set_irq_types_t *cmd = (struct upisnd_cmd_set_irq_types_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_IRQ_TYPES, sizeof(*cmd));
+	memcpy(&cmd->config, irq_type_config, sizeof(struct upisnd_irq_type_config_t));
+
+	return upisnd_execute_command(instance, &task);
+}
+
+int upisnd_comm_set_irq_masks(struct upisnd_instance *instance,
+			      const struct upisnd_irq_mask_config_t *irq_mask_config)
+{
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_set_irq_masks_t *cmd = (struct upisnd_cmd_set_irq_masks_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_IRQ_MASKS, sizeof(*cmd));
+	memcpy(&cmd->config, irq_mask_config, sizeof(struct upisnd_irq_mask_config_t));
+
+	return upisnd_execute_command(instance, &task);
+}
+
+int upisnd_comm_set_subscription(struct upisnd_instance *instance, upisnd_irq_num_t irq, bool on)
+{
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_set_subscription_t *cmd =
+		(struct upisnd_cmd_set_subscription_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET_SUBSCRIPTION, sizeof(*cmd));
+	cmd->on_and_irq_num = (irq & UPISND_IRQ_NUM_MASK) | (on ? UPISND_ON_BIT_MASK : 0);
+
+	return upisnd_execute_command(instance, &task);
+}
+
+int upisnd_comm_get_version(struct upisnd_instance *instance, struct upisnd_version_t *result)
+{
+	memset(result, 0, sizeof(*result));
+
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_get_t *cmd = (struct upisnd_cmd_get_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET, sizeof(*cmd));
+	cmd->value_id = UPISND_VALUE_VERSION_INFO;
+
+	int err = upisnd_execute_command(instance, &task);
+
+	if (err >= 0) {
+		const struct upisnd_cmd_get_response_t *cmd =
+			(const struct upisnd_cmd_get_response_t *)task.response;
+
+		if (upisnd_cmd_decode_length(cmd->cmd_get.cmd.cmd_and_size) ==
+			UPISND_CMD_GET_RESPONSE_INT32_SIZE &&
+			upisnd_cmd_is_response(&cmd->cmd_get.cmd) &&
+			cmd->cmd_get.value_id == UPISND_VALUE_VERSION_INFO) {
+			u32 ver = (uint32_t)ntohl(cmd->value);
+
+			result->bootloader_mode = (ver & 0x80000000) >> 31;
+			result->hwrev = (ver & 0x7f000000) >> 24;
+			result->major = (ver & 0x00ff0000) >> 16;
+			result->minor = (ver & 0x0000ff00) >> 8;
+			result->build = ver & 0x000000ff;
+			err = cmd->result;
+		} else {
+			err = -EINVAL;
+		}
+	}
+
+	return err;
+}
+
+int upisnd_comm_get_serial_number(struct upisnd_instance *instance, char result[12])
+{
+	memset(result, 0, 12);
+
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_get_t *cmd = (struct upisnd_cmd_get_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET, sizeof(*cmd));
+	cmd->value_id = UPISND_VALUE_SERIAL_NUMBER;
+
+	int err = upisnd_execute_command(instance, &task);
+
+	if (err >= 0) {
+		const struct upisnd_cmd_get_response_t *cmd =
+			(const struct upisnd_cmd_get_response_t *)task.response;
+
+		if (upisnd_cmd_decode_length(cmd->cmd_get.cmd.cmd_and_size) ==
+			(UPISND_CMD_GET_RESPONSE_DATA_MIN_SIZE + 11) &&
+			upisnd_cmd_is_response(&cmd->cmd_get.cmd) &&
+			cmd->cmd_get.value_id == UPISND_VALUE_SERIAL_NUMBER) {
+			memcpy(result, cmd->data, 11);
+			err = cmd->result;
+		} else {
+			err = -EINVAL;
+		}
+	}
+
+	return err;
+}
+
+int upisnd_comm_get_element_value(struct upisnd_instance *instance, uint8_t pin, int32_t *result)
+{
+	if (pin >= UPISND_NUM_GPIOS)
+		return -EINVAL;
+
+	*result = 0;
+
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_get_t *cmd = (struct upisnd_cmd_get_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET, sizeof(*cmd));
+	cmd->value_id = UPISND_VALUE_ELEMENT_VAL_BASE + pin;
+
+	int err = upisnd_execute_command(instance, &task);
+
+	if (err >= 0) {
+		const struct upisnd_cmd_get_response_t *cmd =
+			(const struct upisnd_cmd_get_response_t *)task.response;
+
+		if (upisnd_cmd_decode_length(cmd->cmd_get.cmd.cmd_and_size) ==
+			UPISND_CMD_GET_RESPONSE_INT32_SIZE &&
+			upisnd_cmd_is_response(&cmd->cmd_get.cmd) &&
+			cmd->cmd_get.value_id == UPISND_VALUE_ELEMENT_VAL_BASE + pin) {
+			*result = (int32_t)ntohl(cmd->value);
+			err = cmd->result;
+		} else {
+			err = -EINVAL;
+		}
+	}
+
+	return err;
+}
+
+int upisnd_comm_get_value(struct upisnd_instance *instance,
+			  enum upisnd_value_id_t value_id,
+			  int32_t *result)
+{
+	memset(result, 0, sizeof(*result));
+
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_get_t *cmd = (struct upisnd_cmd_get_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_GET, sizeof(*cmd));
+	cmd->value_id = value_id;
+
+	int err = upisnd_execute_command(instance, &task);
+
+	if (err >= 0) {
+		const struct upisnd_cmd_get_response_t *cmd =
+			(const struct upisnd_cmd_get_response_t *)task.response;
+
+		if (upisnd_cmd_decode_length(cmd->cmd_get.cmd.cmd_and_size) ==
+			UPISND_CMD_GET_RESPONSE_INT32_SIZE &&
+			upisnd_cmd_is_response(&cmd->cmd_get.cmd) &&
+			cmd->cmd_get.value_id == value_id) {
+			*result = (int32_t)ntohl(cmd->value);
+			err = cmd->result;
+		} else {
+			err = -EINVAL;
+		}
+	}
+
+	return err;
+}
+
+int upisnd_comm_set_value(struct upisnd_instance *instance,
+			  enum upisnd_value_id_t value_id,
+			  int32_t value)
+{
+	struct upisnd_command_task_t task;
+
+	upisnd_init_command_task(&task);
+
+	struct upisnd_cmd_set_t *cmd = (struct upisnd_cmd_set_t *)task.cmd;
+
+	cmd->cmd.cmd_and_size = upisnd_cmd_encode(UPISND_CMD_SET, sizeof(*cmd));
+	cmd->value_id = value_id;
+	cmd->value = htonl(value);
+
+	return upisnd_execute_command(instance, &task);
+}
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/drivers/upisnd/upisnd_comm.h b/sound/drivers/upisnd/upisnd_comm.h
new file mode 100644
index 000000000000..5ecbaa14f81a
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_comm.h
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef UPISOUND_COMM_H
+#define UPISOUND_COMM_H
+
+struct upisnd_instance;
+
+struct upisnd_version_t {
+	uint8_t bootloader_mode:1;
+	uint8_t hwrev:7;
+	u8 major;
+	u8 minor;
+	u8 build;
+};
+
+struct irq_event_t {
+	upisnd_irq_num_t num;
+	bool             high;
+};
+
+struct control_event_t {
+	upisnd_pin_t pin:6;
+	int16_t      raw_value:10;
+};
+
+struct upisnd_comm_handler_ops {
+	void (*handle_midi_data)(struct upisnd_instance *instance,
+				 const u8 *data,
+				 unsigned int n);
+	void (*handle_gpio_irq_events)(struct upisnd_instance *instance,
+				       const struct irq_event_t *events,
+				       unsigned int n);
+	void (*handle_sound_irq_events)(struct upisnd_instance *instance,
+					const struct irq_event_t *events,
+					unsigned int n);
+	void (*handle_control_events)(struct upisnd_instance *instance,
+				      const struct control_event_t *events,
+				      unsigned int n);
+};
+
+struct upisnd_comm {
+	struct mutex                         comm_lock;
+	// comm_lock serializes I2C communication.
+	struct i2c_client                    *client;
+
+	const struct upisnd_comm_handler_ops *handler_ops;
+
+	// msg_lock protects the message handlers list and id.
+	struct mutex                         msg_lock;
+	struct list_head                     msg_handlers;
+	upisnd_msg_id_t                      msg_id_counter;
+};
+
+int upisnd_comm_module_init(void);
+int upisnd_comm_init(struct upisnd_instance *instance,
+		     struct i2c_client *client,
+		     const struct upisnd_comm_handler_ops *ops);
+int upisnd_comm_send_midi(struct upisnd_instance *instance, const void *data, unsigned int n);
+int upisnd_comm_commit_setup(struct upisnd_instance *instance, upisnd_setup_t setup);
+int upisnd_comm_gpio_set(struct upisnd_instance *instance, u8 pin, bool high);
+int upisnd_comm_gpio_get(struct upisnd_instance *instance, u8 pin);
+int upisnd_comm_gpio_set_all(struct upisnd_instance *instance,
+			     const struct upisnd_all_gpio_state_t *state);
+int upisnd_comm_gpio_get_all(struct upisnd_instance *instance,
+			     struct upisnd_all_gpio_state_t *result);
+int upisnd_comm_set_irq_types(struct upisnd_instance *instance,
+			      const struct upisnd_irq_type_config_t *irq_type_config);
+int upisnd_comm_set_irq_masks(struct upisnd_instance *instance,
+			      const struct upisnd_irq_mask_config_t *irq_mask_config);
+int upisnd_comm_set_subscription(struct upisnd_instance *instance,
+				 upisnd_irq_num_t irq, bool on);
+int upisnd_comm_get_version(struct upisnd_instance *instance,
+			    struct upisnd_version_t *result);
+int upisnd_comm_get_element_value(struct upisnd_instance *instance,
+				  u8 pin,
+				  int32_t *result);
+int upisnd_comm_get_serial_number(struct upisnd_instance *instance,
+				  char result[12]);
+int upisnd_comm_get_value(struct upisnd_instance *instance,
+			  enum upisnd_value_id_t value_id,
+			  int32_t *result);
+int upisnd_comm_set_value(struct upisnd_instance *instance,
+			  enum upisnd_value_id_t value_id,
+			  int32_t value);
+
+void upisnd_handle_comm_interrupt(struct upisnd_instance *instance);
+
+#endif // UPISOUND_COMM_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/drivers/upisnd/upisnd_common.h b/sound/drivers/upisnd/upisnd_common.h
new file mode 100644
index 000000000000..0f810de86030
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_common.h
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef UPISOUND_COMMON_H
+#define UPISOUND_COMMON_H
+
+#include <linux/init.h>
+#include <linux/mutex.h>
+#include <linux/rwsem.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/kfifo.h>
+#include <linux/kobject.h>
+#include <linux/completion.h>
+#include <linux/list.h>
+#include <linux/crc8.h>
+#include <linux/rbtree.h>
+#include <linux/kref.h>
+#include <linux/irq.h>
+#include <linux/irqdesc.h>
+#include <linux/of_irq.h>
+#include <linux/gpio/driver.h>
+#include <linux/gpio/machine.h>
+#include <linux/gpio/consumer.h>
+
+#include <linux/pinctrl/pinconf-generic.h>
+
+#include <sound/asound.h>
+#include <sound/asequencer.h>
+#include <sound/soc.h>
+#include <sound/rawmidi.h>
+#include <sound/pcm_params.h>
+
+#include "upisnd_protocol.h"
+#include "upisnd_comm.h"
+#include "upisnd_debug.h"
+#include "upisnd_midi.h"
+#include "upisnd_sound.h"
+#include "upisnd_sysfs.h"
+#include "upisnd_ctrl.h"
+#include "upisnd_pins.h"
+#include "upisnd_gpio.h"
+#include "upisnd_utils.h"
+
+enum upisnd_flags_e {
+	UPISND_FLAG_DUMMY           = 1 << 0,
+	UPISND_FLAG_ADC_CALIBRATION = 1 << 1,
+};
+
+struct upisnd_instance {
+	struct kref             refcount;
+	struct platform_device  *pdev;
+	struct device           *ctrl_dev;
+	struct device           *codec_dev;
+	struct workqueue_struct *work_queue;
+
+	struct upisnd_ctrl      ctrl;
+	struct upisnd_comm      comm;
+	struct upisnd_midi      midi;
+	struct upisnd_gpio      gpio;
+
+	struct rw_semaphore     rw_gpio_config_sem;
+	struct upisnd_config    *config;
+	DECLARE_KFIFO(ctrl_event_fifo, struct control_event_t, 128);
+	struct work_struct      ctrl_event_handler;
+
+	struct snd_soc_card     sound_card;
+	struct snd_soc_dai_link dai_link;
+
+	u32                     flags;
+};
+
+void upisnd_instance_release(struct kref *kref);
+
+#endif // UPISOUND_COMMON_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/drivers/upisnd/upisnd_ctrl.c b/sound/drivers/upisnd/upisnd_ctrl.c
new file mode 100644
index 000000000000..fcd0b326bef7
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_ctrl.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "upisnd_common.h"
+
+static void upisnd_ctrl_handle_irq_events(struct upisnd_instance *instance,
+					  const struct irq_event_t *events,
+					  unsigned int n);
+static void upisnd_ctrl_handle_control_events(struct upisnd_instance *instance,
+					      const struct control_event_t *events,
+					      unsigned int n);
+
+static const struct upisnd_comm_handler_ops upisnd_comm_handlers = {
+	.handle_midi_data        = &upisnd_handle_midi_data,
+	.handle_gpio_irq_events  = &upisnd_ctrl_handle_irq_events,
+	.handle_sound_irq_events = &upisnd_sound_handle_irq_events,
+	.handle_control_events   = &upisnd_ctrl_handle_control_events,
+};
+
+static void upisnd_ctrl_cleanup(struct upisnd_instance *instance);
+
+static inline bool upisnd_has_data(struct upisnd_instance *instance)
+{
+	bool x = gpiod_get_value(instance->ctrl.data_available);
+
+	printd("data_available = %d", x);
+	return x;
+}
+
+static irqreturn_t upisnd_data_available_interrupt_handler(int irq, void *dev_id)
+{
+	printd("irq_handler called");
+
+	struct upisnd_instance *instance = dev_id;
+
+	if (irq == instance->ctrl.client->irq) {
+		printd("handling comm interrupt");
+		upisnd_handle_comm_interrupt(instance);
+		printd("handling done");
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+static void upisnd_reset(struct upisnd_instance *instance)
+{
+	printd("Resetting...");
+
+	gpiod_direction_output(instance->ctrl.reset, 1);
+	usleep_range(1000, 5000);
+#ifdef UPISOUND_DEV
+	gpiod_direction_input(ctrl->reset);
+#else
+	gpiod_set_value(instance->ctrl.reset, 0);
+#endif
+	msleep(30);
+
+	upisnd_gpio_reset(instance);
+
+	printd("Done!");
+}
+
+int upisnd_ctrl_probe(struct i2c_client *client)
+{
+	int err = 0;
+
+	printd("Ctrl %p irq %d", client, client->irq);
+	printd("Dev %p", &client->dev);
+	printd("Data %p", dev_get_platdata(&client->dev));
+
+	struct upisnd_instance *instance = dev_get_platdata(&client->dev);
+
+	if (!instance)
+		return -EPROBE_DEFER;
+
+	struct upisnd_ctrl *ctrl = &instance->ctrl;
+
+	ctrl->client = client;
+
+	err = upisnd_gpio_init(instance);
+	if (err < 0) {
+		printe("GPIO init failed! %d", err);
+		goto cleanup;
+	}
+
+	ctrl->reset = devm_gpiod_get_index(&client->dev, "reset", 0, GPIOD_ASIS);
+	if (IS_ERR(ctrl->reset)) {
+		printe("Failed getting reset gpio!");
+		err = PTR_ERR(ctrl->reset);
+		ctrl->reset = NULL;
+		goto cleanup;
+	}
+
+	upisnd_reset(instance);
+
+	err = upisnd_comm_init(instance, client, &upisnd_comm_handlers);
+	if (err < 0) {
+		printe("Communication init failed! (%d)", err);
+		goto cleanup;
+	}
+
+	ctrl->data_available = devm_gpiod_get_index(&client->dev, "data_available", 0, GPIOD_IN);
+	if (IS_ERR(ctrl->data_available)) {
+		printe("Failed getting data_available gpio!");
+		err = PTR_ERR(ctrl->data_available);
+		ctrl->data_available = NULL;
+		goto cleanup;
+	}
+
+	err = devm_request_threaded_irq(&client->dev,
+					ctrl->client->irq,
+					NULL,
+					upisnd_data_available_interrupt_handler,
+					IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_LOW,
+					"data_available_int",
+					instance);
+	if (err != 0) {
+		printe("data_available IRQ request failed! %d", err);
+		goto cleanup;
+	}
+
+	err = upisnd_comm_get_version(instance, &instance->ctrl.version);
+	if (err < 0) {
+		printe("Failed getting version! %d", err);
+		goto cleanup;
+	}
+	err = upisnd_comm_get_serial_number(instance, instance->ctrl.serial);
+	if (err < 0) {
+		printe("Failed getting serial number! %d", err);
+		goto cleanup;
+	}
+
+	if (instance->ctrl.serial[0] != 'P' ||
+	    instance->ctrl.serial[1] != 'S' ||
+	    instance->ctrl.serial[2] != 'M' ||
+	    strlen(instance->ctrl.serial) != 11) {
+		printe("Unexpected serial number %s!", instance->ctrl.serial);
+		err = -EINVAL;
+		goto cleanup;
+	}
+
+	printi("Pisound Micro %s version %d.%d.%d, hw rev: %d",
+	       instance->ctrl.serial,
+	       instance->ctrl.version.major,
+	       instance->ctrl.version.minor,
+	       instance->ctrl.version.build,
+	       instance->ctrl.version.hwrev);
+
+	if (instance->ctrl.version.bootloader_mode) {
+		printe("Pisound Micro is in bootloader mode! Please reflash the firmware, refer to documentation on https://blokas.io/");
+		err = -EINVAL;
+		goto cleanup;
+	}
+
+	const char *name = NULL;
+
+	if (client->dev.of_node) {
+		err = of_property_read_string(client->dev.of_node, "kobj-name", &name);
+
+		if (of_property_read_bool(client->dev.of_node, "adc-calibration"))
+			instance->flags |= UPISND_FLAG_ADC_CALIBRATION;
+	}
+	err = upisnd_sysfs_init(instance, name);
+	if (err != 0) {
+		printe("sysfs init failed! %d", err);
+		goto cleanup;
+	}
+
+cleanup:
+	if (err != 0) {
+		printe("Error %d!", err);
+		upisnd_ctrl_cleanup(instance);
+		return err;
+	}
+
+	return 0;
+}
+
+void upisnd_ctrl_remove(struct i2c_client *client)
+{
+	printd("Ctrl %p", client);
+
+	struct upisnd_instance *instance = dev_get_platdata(&client->dev);
+
+	if (!instance)
+		return;
+
+	upisnd_ctrl_cleanup(instance);
+
+	kref_put(&instance->refcount, &upisnd_instance_release);
+	client->dev.platform_data = NULL;
+}
+
+static void upisnd_off(struct upisnd_instance *instance)
+{
+	printd("Turning off...");
+
+#ifndef UPISOUND_DEV
+	gpiod_direction_output(instance->ctrl.reset, 1);
+#else
+	upisnd_reset(&instance->ctrl);
+#endif
+	printd("Done!");
+}
+
+static void upisnd_ctrl_cleanup(struct upisnd_instance *instance)
+{
+	printd("cleanup");
+	upisnd_sysfs_uninit(instance);
+
+	if (instance->ctrl.reset) {
+		upisnd_off(instance);
+		instance->ctrl.reset = NULL;
+	}
+}
+
+static void upisnd_ctrl_handle_irq_events(struct upisnd_instance *instance,
+					  const struct irq_event_t *events,
+					  unsigned int n)
+{
+	upisnd_gpio_handle_irq_event(instance, events, n);
+	upisnd_sysfs_handle_irq_event(instance, events, n);
+}
+
+static void upisnd_ctrl_handle_control_events(struct upisnd_instance *instance,
+					      const struct control_event_t *events,
+					      unsigned int n)
+{
+	upisnd_sysfs_handle_control_event(instance, events, n);
+}
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/drivers/upisnd/upisnd_ctrl.h b/sound/drivers/upisnd/upisnd_ctrl.h
new file mode 100644
index 000000000000..6851a491cc6a
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_ctrl.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef UPISOUND_CTRL_H
+#define UPISOUND_CTRL_H
+
+struct upisnd_ctrl {
+	struct i2c_client       *client;
+	struct upisnd_gpio      *gpio;
+
+	struct upisnd_version_t version;
+	char                    serial[12];
+
+	struct gpio_desc        *data_available;
+	struct gpio_desc        *reset;
+};
+
+int upisnd_ctrl_probe(struct i2c_client *client);
+void upisnd_ctrl_remove(struct i2c_client *client);
+
+#endif // UPISOUND_CTRL_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/drivers/upisnd/upisnd_debug.h b/sound/drivers/upisnd/upisnd_debug.h
new file mode 100644
index 000000000000..b0b3fda9451f
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_debug.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef UPISOUND_DEBUG_H
+#define UPISOUND_DEBUG_H
+
+#define UPISOUND_LOG_IMPL(log_func, msg, ...) \
+	log_func("pisound-micro(%s): " msg "\n", __func__, ## __VA_ARGS__)
+
+#ifdef UPISOUND_DEBUG
+#	define printd(...) UPISOUND_LOG_IMPL(pr_alert, __VA_ARGS__)
+#else
+#	define printd(...) do {} while (0)
+#endif
+
+#define printe(...) UPISOUND_LOG_IMPL(pr_err, __VA_ARGS__)
+#define printi(...) UPISOUND_LOG_IMPL(pr_info, __VA_ARGS__)
+
+#endif // UPISOUND_DEBUG_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/drivers/upisnd/upisnd_gpio.c b/sound/drivers/upisnd/upisnd_gpio.c
new file mode 100644
index 000000000000..7fe9b3aeb0af
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_gpio.c
@@ -0,0 +1,867 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "upisnd_common.h"
+
+enum upisnd_pinconf_dir_e {
+	UPISND_PINCONF_DIR_INPUT_PULL_AS_IS_OR_NONE = 0,
+	UPISND_PINCONF_DIR_INPUT_PULL_UP            = 1,
+	UPISND_PINCONF_DIR_INPUT_PULL_DOWN          = 2,
+	UPISND_PINCONF_DIR_OUTPUT_LOW               = 4,
+	UPISND_PINCONF_DIR_OUTPUT_HIGH              = 5,
+
+	// If 3rd bit is set, direction is output, otherwise it's input.
+	UPISND_PINCONF_DIR_MASK                     = 0x04,
+};
+
+enum { UPISND_PINCONF_DIRECTION = (PIN_CONFIG_END + 1) }; // enum upisnd_pinconf_dir_e
+
+static int upisnd_gpio_chip_get_direction(struct gpio_chip *gc, unsigned int offset);
+static int upisnd_gpio_chip_get(struct gpio_chip *gc, unsigned int offset);
+static void upisnd_gpio_chip_set(struct gpio_chip *gc, unsigned int offset, int val);
+static int upisnd_gpio_chip_get_multiple(struct gpio_chip *gc,
+					 unsigned long *mask,
+					 unsigned long *bits);
+static void upisnd_gpio_chip_set_multiple(struct gpio_chip *gc,
+					  unsigned long *mask,
+					  unsigned long *bits);
+static int upisnd_gpio_chip_set_config(struct gpio_chip *gc,
+				       unsigned int offset,
+				       unsigned long config);
+static int upisnd_gpio_chip_dir_in(struct gpio_chip *gc, unsigned int offset);
+static int upisnd_gpio_chip_dir_out(struct gpio_chip *gc, unsigned int offset, int val);
+static int upisnd_gpio_chip_request(struct gpio_chip *gc, unsigned int offset);
+static void upisnd_gpio_chip_free(struct gpio_chip *gc, unsigned int offset);
+
+static const char * const upisnd_gpio_names[UPISND_NUM_GPIOS] = {
+	"A27", "A28", "A29", "A30", "A31", "A32", "B03", "B04",
+	"B05", "B06", "B07", "B08", "B09", "B10", "B11", "B12",
+	"B13", "B14", "B15", "B16", "B17", "B18", "B23", "B24",
+	"B25", "B26", "B27", "B28", "B29", "B30", "B31", "B32",
+	"B33", "B34", "B37", "B38", "B39"
+};
+
+static const struct gpio_chip upisnd_gpio_chip = {
+	.label = "pisound-micro-gpio",
+	.owner = THIS_MODULE,
+	.request = &upisnd_gpio_chip_request,
+	.free = &upisnd_gpio_chip_free,
+	.direction_input = &upisnd_gpio_chip_dir_in,
+	.direction_output = &upisnd_gpio_chip_dir_out,
+	.get_direction = &upisnd_gpio_chip_get_direction,
+	.get = &upisnd_gpio_chip_get,
+	.set = &upisnd_gpio_chip_set,
+	.get_multiple = &upisnd_gpio_chip_get_multiple,
+	.set_multiple = &upisnd_gpio_chip_set_multiple,
+	.set_config = &upisnd_gpio_chip_set_config,
+	.base = -1,
+	.ngpio = UPISND_NUM_GPIOS,
+	.can_sleep = true,
+	.names = upisnd_gpio_names,
+};
+
+enum {
+	IRQ_FLAG_TYPES_CHANGED_BIT = 0,
+	IRQ_FLAG_MASKS_CHANGED_BIT = 1,
+	IRQ_FLAG_HANDLING_IRQ_BIT  = 2,
+};
+
+static int upisnd_gpio_chip_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+	struct upisnd_gpio *gpio = &instance->gpio;
+
+	down_read(&instance->rw_gpio_config_sem);
+	upisnd_setup_t setup = gpio->pin_configs[offset].setup;
+
+	up_read(&instance->rw_gpio_config_sem);
+
+	int dir = -EINVAL;
+
+	if (upisnd_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_GPIO)
+		dir = upisnd_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_INPUT ? 1 : 0;
+
+	printd("%p %u %d %08x", gc, offset, dir, setup);
+	return dir;
+}
+
+static const char *upisnd_gpio_pull_to_str(enum upisnd_pin_pull_e pull)
+{
+	switch (pull) {
+	default:
+	case UPISND_PIN_PULL_NONE: return "pull_none";
+	case UPISND_PIN_PULL_UP:   return "pull_up";
+	case UPISND_PIN_PULL_DOWN: return "pull_down";
+	}
+}
+
+static void upisnd_print_setup(const upisnd_setup_t setup)
+{
+	switch (upisnd_setup_get_element_type(setup)) {
+	case UPISND_ELEMENT_TYPE_NONE:
+		printi("Setup None %d", upisnd_setup_get_pin_id(setup));
+		break;
+	case UPISND_ELEMENT_TYPE_ENCODER:
+		printi("Setup Encoder %d %s %d %s",
+		       upisnd_setup_get_pin_id(setup),
+		       upisnd_gpio_pull_to_str(upisnd_setup_get_gpio_pull(setup)),
+		       upisnd_setup_get_encoder_pin_b_id(setup),
+		       upisnd_gpio_pull_to_str(upisnd_setup_get_encoder_pin_b_pull(setup)));
+		break;
+	case UPISND_ELEMENT_TYPE_ANALOG_IN:
+		printi("Setup Analog In %d (%d)",
+		       upisnd_setup_get_pin_id(setup),
+		       upisnd_setup_get_pin_id(setup));
+		break;
+	case UPISND_ELEMENT_TYPE_GPIO:
+		if (upisnd_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_INPUT)
+			printi("Setup GPIO Input %d %s",
+			       upisnd_setup_get_pin_id(setup),
+			       upisnd_gpio_pull_to_str(upisnd_setup_get_gpio_pull(setup)));
+		else
+			printi("Setup GPIO Output %d %s",
+			       upisnd_setup_get_pin_id(setup),
+			       upisnd_setup_get_gpio_output(setup) ? "high" : "low");
+		break;
+	case UPISND_ELEMENT_TYPE_ACTIVITY:
+		printi("Setup Activity %d %d",
+		       upisnd_setup_get_pin_id(setup),
+		       upisnd_setup_get_activity_type(setup));
+		break;
+	default:
+		printe("Unknown Setup type %d", upisnd_setup_get_element_type(setup));
+		break;
+	}
+}
+
+int upisnd_gpio_setup(struct upisnd_instance *instance, upisnd_setup_t setup)
+{
+	int err;
+
+#ifdef UPISND_DEBUG
+	printd("Committing setup:");
+	upisnd_print_setup(setup);
+	err = upisnd_comm_commit_setup(instance, setup);
+	printd("Result: %d", err)
+#else
+	err = upisnd_comm_commit_setup(instance, setup);
+
+	if (err < 0) {
+		printe("Failed to commit setup (%d), failed request:", err);
+		upisnd_print_setup(setup);
+	}
+#endif
+
+	return err;
+}
+
+static int upisnd_gpio_chip_get(struct gpio_chip *gc, unsigned int offset)
+{
+	printd("%p %u", gc, offset);
+
+	int value = -EINVAL;
+
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	unsigned long state[2];
+
+	down_read(&instance->rw_gpio_config_sem);
+	upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup;
+
+	state[0] = instance->gpio.gpio_state[0];
+	state[1] = instance->gpio.gpio_state[1];
+	up_read(&instance->rw_gpio_config_sem);
+
+	if (upisnd_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_GPIO) {
+		switch (upisnd_setup_get_gpio_dir(setup)) {
+		case UPISND_PIN_DIR_OUTPUT:
+			value = test_bit(offset, state) ? 1 : 0;
+			break;
+		case UPISND_PIN_DIR_INPUT:
+			if (test_bit(IRQ_FLAG_HANDLING_IRQ_BIT, &instance->gpio.irq_flags)) {
+				value = test_bit(offset, state) ? 1 : 0;
+			} else {
+				down_write(&instance->rw_gpio_config_sem);
+				value = upisnd_comm_gpio_get(instance, offset);
+				assign_bit(offset, instance->gpio.gpio_state, value);
+				up_write(&instance->rw_gpio_config_sem);
+			}
+			break;
+		default:
+			break;
+		}
+	}
+
+	return value;
+}
+
+static void upisnd_gpio_chip_set(struct gpio_chip *gc, unsigned int offset, int val)
+{
+	printd("%p %u %d", gc, offset, val);
+
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	down_write(&instance->rw_gpio_config_sem);
+	upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup;
+
+	if (upisnd_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_GPIO) {
+		if (upisnd_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_OUTPUT) {
+			bool on = val != 0;
+
+			assign_bit(offset, instance->gpio.gpio_state, on);
+			upisnd_comm_gpio_set(instance, offset, on);
+		}
+	}
+	up_write(&instance->rw_gpio_config_sem);
+}
+
+static void encode_gpio_state(struct upisnd_all_gpio_state_t *s, const unsigned long *bits)
+{
+	u8 *p = (uint8_t *)s->state;
+
+	p[0] = (bits[0]) & 0xff;
+	p[1] = (bits[0] >>  8) & 0xff;
+	p[2] = (bits[0] >> 16) & 0xff;
+	p[3] = (bits[0] >> 24) & 0xff;
+	p[4] = (bits[1]) & 0xff;
+}
+
+static void decode_gpio_state(unsigned long *bits, const struct upisnd_all_gpio_state_t *s)
+{
+	const u8 *p = (const uint8_t *)s->state;
+
+	bits[0] =
+		(p[0]) |
+		(p[1] <<  8) |
+		(p[2] << 16) |
+		(p[3] << 24);
+	bits[1] =
+		(p[4]);
+}
+
+static int upisnd_gpio_chip_get_multiple(struct gpio_chip *gc,
+					 unsigned long *mask,
+					 unsigned long *bits)
+{
+	unsigned int i;
+	int err = 0;
+
+	memset(bits, 0, sizeof(bits[0]) * 2);
+
+	printd("%p %02lx%08lx", gc, mask[1], mask[0]);
+
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	unsigned long state[2];
+
+	down_read(&instance->rw_gpio_config_sem);
+	for_each_set_bit(i, mask, UPISND_NUM_GPIOS) {
+		upisnd_setup_t setup = instance->gpio.pin_configs[i].setup;
+
+		if (upisnd_setup_get_element_type(setup) != UPISND_ELEMENT_TYPE_GPIO) {
+			printe("Pin %u is not configured as GPIO!", i);
+			err = -EINVAL;
+		}
+	}
+	up_read(&instance->rw_gpio_config_sem);
+
+	if (err < 0)
+		return err;
+
+	if (test_bit(IRQ_FLAG_HANDLING_IRQ_BIT, &instance->gpio.irq_flags)) {
+		state[0] = instance->gpio.gpio_state[0];
+		state[1] = instance->gpio.gpio_state[1];
+	} else {
+		struct upisnd_all_gpio_state_t s;
+
+		down_write(&instance->rw_gpio_config_sem);
+
+		err = upisnd_comm_gpio_get_all(instance, &s);
+
+		if (err < 0) {
+			up_write(&instance->rw_gpio_config_sem);
+			return err;
+		}
+
+		decode_gpio_state(state, &s);
+
+		instance->gpio.gpio_state[0] = state[0];
+		instance->gpio.gpio_state[1] = state[1];
+		up_write(&instance->rw_gpio_config_sem);
+	}
+
+	bits[0] = state[0] & mask[0];
+	bits[1] = state[1] & mask[1];
+
+	return err;
+}
+
+static void upisnd_gpio_chip_set_multiple(struct gpio_chip *gc,
+					  unsigned long *mask,
+					  unsigned long *bits)
+{
+	unsigned long state[2];
+
+	printd("%p %02lx%08lx %02lx%08lx", gc, mask[1], mask[0], bits[1], bits[0]);
+
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	down_write(&instance->rw_gpio_config_sem);
+
+	state[0] = (instance->gpio.gpio_state[0] & ~mask[0]) | (bits[0] & mask[0]);
+	state[1] = (instance->gpio.gpio_state[1] & ~mask[1]) | (bits[1] & mask[1]);
+
+	struct upisnd_all_gpio_state_t s;
+
+	encode_gpio_state(&s, state);
+
+	upisnd_comm_gpio_set_all(instance, &s);
+
+	instance->gpio.gpio_state[0] = state[0];
+	instance->gpio.gpio_state[1] = state[1];
+
+	up_write(&instance->rw_gpio_config_sem);
+}
+
+static int upisnd_gpio_chip_dir_in(struct gpio_chip *gc, unsigned int offset)
+{
+	printd("%p %u", gc, offset);
+	return upisnd_gpio_chip_set_config(gc, offset, pinconf_to_config_packed
+		((enum pin_config_param)UPISND_PINCONF_DIRECTION,
+		UPISND_PINCONF_DIR_INPUT_PULL_AS_IS_OR_NONE));
+}
+
+static int upisnd_gpio_chip_dir_out(struct gpio_chip *gc, unsigned int offset, int val)
+{
+	printd("%p %u %d", gc, offset, val);
+	return upisnd_gpio_chip_set_config(gc, offset, pinconf_to_config_packed
+		((enum pin_config_param)UPISND_PINCONF_DIRECTION,
+		val ? UPISND_PINCONF_DIR_OUTPUT_HIGH : UPISND_PINCONF_DIR_OUTPUT_LOW));
+}
+
+static int upisnd_gpio_chip_request(struct gpio_chip *gc, unsigned int offset)
+{
+	printd("(%p, %u)", gc, offset);
+
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	int err;
+	upisnd_setup_t setup = 0;
+
+	down_write(&instance->rw_gpio_config_sem);
+
+	if (upisnd_setup_get_element_type(instance->gpio.pin_configs[offset].setup) ==
+					  UPISND_ELEMENT_TYPE_GPIO) {
+		setup = instance->gpio.pin_configs[offset].setup;
+	} else {
+		upisnd_setup_set_element_type(&setup, UPISND_ELEMENT_TYPE_GPIO);
+		upisnd_setup_set_gpio_dir(&setup, UPISND_PIN_DIR_INPUT);
+		upisnd_setup_set_gpio_pull(&setup, UPISND_PIN_PULL_NONE);
+		upisnd_setup_set_pin_id(&setup, offset);
+	}
+
+	// If the pin is already in use via setup interface.
+	if (instance->gpio.pin_configs[offset].element) {
+		// If the pin is not being requested via setup interface, deny the request.
+		if (!(instance->gpio.pin_configs[offset].flags &
+		      UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS)) {
+			printe("Pin %u already exported through pisound-micro sysfs!", offset);
+			err = -EBUSY;
+		} else {
+			err = 0;
+		}
+
+		up_write(&instance->rw_gpio_config_sem);
+		return err;
+	}
+
+	err = upisnd_gpio_setup(instance, setup);
+
+	if (err >= 0) {
+		instance->gpio.pin_configs[offset].element = NULL;
+		instance->gpio.pin_configs[offset].flags = 0;
+		instance->gpio.pin_configs[offset].setup = setup;
+		instance->gpio.pin_configs[offset].gpio_desc = NULL;
+	}
+
+	up_write(&instance->rw_gpio_config_sem);
+
+	return err;
+}
+
+static void upisnd_gpio_chip_free(struct gpio_chip *gc, unsigned int offset)
+{
+	printd("(%p, %u)", gc, offset);
+
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	down_write(&instance->rw_gpio_config_sem);
+	if (instance->gpio.pin_configs[offset].element ||
+	    (instance->gpio.pin_configs[offset].flags & UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS)
+	    ) {
+		// Unsetup will be taken care of in upisnd_element_cleanup,
+		// ignore requests that come through gpiochip_free_own_desc.
+		up_write(&instance->rw_gpio_config_sem);
+		return;
+	}
+
+	upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup;
+
+	upisnd_setup_set_element_type(&setup, UPISND_ELEMENT_TYPE_NONE);
+	upisnd_gpio_setup(instance, setup);
+
+	memset(&instance->gpio.pin_configs[offset], 0, sizeof(struct upisnd_pin_config_t));
+
+	up_write(&instance->rw_gpio_config_sem);
+
+	printd("Done");
+}
+
+static int upisnd_gpio_chip_set_config(struct gpio_chip *gc,
+				       unsigned int offset,
+				       unsigned long config)
+{
+	int p = pinconf_to_config_param(config);
+	u32 arg = pinconf_to_config_argument(config);
+
+	printd("%p %u %08lx p=%d arg=%u", gc, offset, config, p, arg);
+
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	down_write(&instance->rw_gpio_config_sem);
+	if (instance->gpio.pin_configs[offset].flags & UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS) {
+		// Setup has taken care of configuring the pin already,
+		// ignore requests that come through gpiochip_request_own_desc.
+		up_write(&instance->rw_gpio_config_sem);
+		return 0;
+	}
+
+	int err = 0;
+
+	upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup;
+
+	if (upisnd_setup_get_element_type(setup) != UPISND_ELEMENT_TYPE_GPIO) {
+		err = -EINVAL;
+		goto cleanup;
+	}
+
+	switch (p) {
+	case PIN_CONFIG_BIAS_PULL_DOWN:
+	case PIN_CONFIG_BIAS_PULL_UP:
+		if (upisnd_setup_get_gpio_dir(setup) != UPISND_PIN_DIR_INPUT) {
+			err = -EINVAL;
+			goto cleanup;
+		}
+		if (arg != 0)
+			upisnd_setup_set_gpio_pull(&setup, p == PIN_CONFIG_BIAS_PULL_UP ?
+						   UPISND_PIN_PULL_UP : UPISND_PIN_PULL_DOWN);
+		else
+			upisnd_setup_set_gpio_pull(&setup, UPISND_PIN_PULL_NONE);
+		break;
+	case PIN_CONFIG_BIAS_DISABLE:
+	case PIN_CONFIG_BIAS_HIGH_IMPEDANCE:
+		if (upisnd_setup_get_gpio_dir(setup) != UPISND_PIN_DIR_INPUT) {
+			err = -EINVAL;
+			goto cleanup;
+		}
+		upisnd_setup_set_gpio_pull(&setup, UPISND_PIN_PULL_NONE);
+		break;
+	case UPISND_PINCONF_DIRECTION:
+		if (arg & UPISND_PINCONF_DIR_MASK) {
+			// Output.
+			upisnd_setup_set_gpio_dir(&setup, UPISND_PIN_DIR_OUTPUT);
+			upisnd_setup_set_gpio_output(&setup, arg == UPISND_PINCONF_DIR_OUTPUT_HIGH);
+		} else {
+			// Input.
+			if (arg == UPISND_PINCONF_DIR_INPUT_PULL_AS_IS_OR_NONE) {
+				if (upisnd_setup_get_gpio_dir(setup) == UPISND_PIN_DIR_OUTPUT)
+					upisnd_setup_set_gpio_pull(&setup, UPISND_PIN_PULL_NONE);
+			} else {
+				upisnd_setup_set_gpio_pull(&setup, (enum upisnd_pin_pull_e)arg);
+			}
+			upisnd_setup_set_gpio_dir(&setup, UPISND_PIN_DIR_INPUT);
+		}
+		break;
+	default:
+		printe("Not supported param %d", p);
+		err = -ENOTSUPP;
+		goto cleanup;
+	}
+
+	err = upisnd_gpio_setup(instance, setup);
+
+	if (err >= 0)
+		instance->gpio.pin_configs[offset].setup = setup;
+
+	printd("Done");
+
+cleanup:
+	up_write(&instance->rw_gpio_config_sem);
+
+	return err;
+}
+
+static int upisnd_gpio_irq_set_type(struct irq_data *data, unsigned int type)
+{
+	printd("%p %u", data, type);
+
+	if (!(type & IRQ_TYPE_EDGE_BOTH)) {
+		printe("IRQ %d: unsupported type %u\n", data->irq, type);
+		return -ENOTSUPP;
+	}
+
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	unsigned long pin = data->hwirq;
+
+	down_read(&instance->rw_gpio_config_sem);
+	if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) !=
+		UPISND_ELEMENT_TYPE_GPIO ||
+		upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[pin].setup) !=
+		UPISND_PIN_DIR_INPUT) {
+		printe("Pin %s is not set up as an input!", upisnd_pin_name(pin));
+		up_read(&instance->rw_gpio_config_sem);
+		return -EACCES;
+	}
+	up_read(&instance->rw_gpio_config_sem);
+
+	upisnd_irq_config_set_irq_type(&instance->gpio.irq_type_config, pin, type);
+	set_bit(IRQ_FLAG_TYPES_CHANGED_BIT, &instance->gpio.irq_flags);
+
+	return 0;
+}
+
+static void upisnd_gpio_irq_ack(struct irq_data *data)
+{
+	printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq);
+}
+
+static void upisnd_gpio_irq_mask(struct irq_data *data)
+{
+	printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq);
+
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	unsigned long pin = data->hwirq;
+
+	down_read(&instance->rw_gpio_config_sem);
+	if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) !=
+		UPISND_ELEMENT_TYPE_GPIO ||
+		upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[pin].setup) !=
+		UPISND_PIN_DIR_INPUT) {
+		printe("Pin %s is not set up as an input!", upisnd_pin_name(pin));
+		up_read(&instance->rw_gpio_config_sem);
+		return;
+	}
+	up_read(&instance->rw_gpio_config_sem);
+
+	upisnd_irq_mask_config_set(&instance->gpio.irq_mask_config, pin, true);
+	set_bit(IRQ_FLAG_MASKS_CHANGED_BIT, &instance->gpio.irq_flags);
+}
+
+static void upisnd_gpio_irq_unmask(struct irq_data *data)
+{
+	printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq);
+
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	unsigned long pin = data->hwirq;
+
+	down_read(&instance->rw_gpio_config_sem);
+	if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) !=
+		UPISND_ELEMENT_TYPE_GPIO ||
+		upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[pin].setup) !=
+		UPISND_PIN_DIR_INPUT) {
+		printe("Pin %s is not set up as an input!", upisnd_pin_name(pin));
+		up_read(&instance->rw_gpio_config_sem);
+		return;
+	}
+	up_read(&instance->rw_gpio_config_sem);
+
+	upisnd_irq_mask_config_set(&instance->gpio.irq_mask_config, pin, false);
+	set_bit(IRQ_FLAG_MASKS_CHANGED_BIT, &instance->gpio.irq_flags);
+}
+
+static void upisnd_gpio_irq_bus_lock(struct irq_data *data)
+{
+	printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq);
+
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	mutex_lock(&instance->gpio.gpio_irq_lock);
+}
+
+static void upisnd_gpio_irq_bus_sync_unlock(struct irq_data *data)
+{
+	printd("%p mask=%08x irq=%u hwirq=%lu", data, data->mask, data->irq, data->hwirq);
+
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(data);
+	struct upisnd_instance *instance = gpiochip_get_data(gc);
+
+	int result = 0;
+
+	if (test_bit(IRQ_FLAG_TYPES_CHANGED_BIT, &instance->gpio.irq_flags)) {
+		printd("Types changed");
+		result = upisnd_comm_set_irq_types(instance, &instance->gpio.irq_type_config);
+		printd("result: %d", result);
+
+		clear_bit(IRQ_FLAG_TYPES_CHANGED_BIT, &instance->gpio.irq_flags);
+	}
+
+	if (test_bit(IRQ_FLAG_MASKS_CHANGED_BIT, &instance->gpio.irq_flags)) {
+		printd("Masks changed");
+		result = upisnd_comm_set_irq_masks(instance, &instance->gpio.irq_mask_config);
+		printd("result: %d", result);
+
+		clear_bit(IRQ_FLAG_MASKS_CHANGED_BIT, &instance->gpio.irq_flags);
+	}
+
+	printd("done");
+	mutex_unlock(&instance->gpio.gpio_irq_lock);
+}
+
+static const struct irq_chip upisnd_gpio_irq_chip = {
+	.name = "pisound-micro-gpio",
+	.irq_set_type = upisnd_gpio_irq_set_type,
+	.irq_ack = upisnd_gpio_irq_ack,
+	.irq_mask = upisnd_gpio_irq_mask,
+	.irq_unmask = upisnd_gpio_irq_unmask,
+	.irq_bus_lock = upisnd_gpio_irq_bus_lock,
+	.irq_bus_sync_unlock = upisnd_gpio_irq_bus_sync_unlock,
+	.flags = IRQCHIP_MASK_ON_SUSPEND | IRQCHIP_IMMUTABLE,
+};
+
+static void upisnd_gpio_irq_event_handler(struct work_struct *work)
+{
+	struct upisnd_instance *instance = container_of(work,
+							struct upisnd_instance,
+							gpio.irq_event_handler);
+
+	int n;
+	struct irq_event_t events[128];
+
+	set_bit(IRQ_FLAG_HANDLING_IRQ_BIT, &instance->gpio.irq_flags);
+
+	unsigned int offset;
+	unsigned long masked[2];
+
+	memset(masked, 0, sizeof(masked));
+	u8 i;
+
+	down_write(&instance->rw_gpio_config_sem);
+	n = kfifo_out(&instance->gpio.irq_event_fifo, events, ARRAY_SIZE(events));
+	if (n > 0) {
+		printd("Handling %u IRQ events:", n);
+		for (i = 0; i < n; ++i) {
+			offset = events[i].num;
+
+			if (offset == 0x7f) {
+				printi("\tAlert event %s", events[i].high ? "on" : "off");
+				continue;
+			}
+
+			printd("\t%d %s %s", offset, upisnd_pin_name(offset),
+			       events[i].high ? "up" : "down");
+
+			assign_bit(offset, instance->gpio.gpio_state, events[i].high);
+
+			if (upisnd_irq_mask_config_get(&instance->gpio.irq_mask_config, offset))
+				set_bit(offset, masked);
+		}
+	}
+	up_write(&instance->rw_gpio_config_sem);
+
+	for (i = 0; i < n; ++i) {
+		if (!test_bit(i, masked)) {
+			offset = events[i].num;
+			int nested_irq = irq_find_mapping(instance->gpio.gpio_chip.irq.domain,
+							  offset);
+
+			if (unlikely(nested_irq <= 0)) {
+				dev_warn_ratelimited(instance->gpio.gpio_chip.parent,
+						     "unmapped interrupt %d\n",
+						     offset);
+				continue;
+			} else {
+				printd("Before handle_nested_irq");
+				handle_nested_irq(nested_irq);
+				printd("After handle_nested_irq");
+			}
+		}
+	}
+
+	clear_bit(IRQ_FLAG_HANDLING_IRQ_BIT, &instance->gpio.irq_flags);
+
+	if (!kfifo_is_empty(&instance->gpio.irq_event_fifo))
+		queue_work(instance->work_queue, &instance->gpio.irq_event_handler);
+}
+
+void upisnd_gpio_handle_irq_event(struct upisnd_instance *instance,
+				  const struct irq_event_t *events,
+				  unsigned int n)
+{
+	printd("Pushing %u events", n);
+	kfifo_in(&instance->gpio.irq_event_fifo, events, n);
+	if (!work_pending(&instance->gpio.irq_event_handler))
+		queue_work(instance->work_queue, &instance->gpio.irq_event_handler);
+}
+
+void upisnd_gpio_reset(struct upisnd_instance *instance)
+{
+	down_write(&instance->rw_gpio_config_sem);
+
+	memset(&instance->gpio.gpio_state, 0, sizeof(instance->gpio.gpio_state));
+	memset(instance->gpio.pin_configs, 0, sizeof(instance->gpio.pin_configs));
+	memset(&instance->gpio.irq_mask_config, 0xff, sizeof(instance->gpio.irq_mask_config));
+
+	up_write(&instance->rw_gpio_config_sem);
+}
+
+int upisnd_gpio_init(struct upisnd_instance *instance)
+{
+	struct upisnd_gpio *gpio = &instance->gpio;
+
+	mutex_init(&gpio->gpio_irq_lock);
+
+	// Set all IRQs as masked initially.
+	memset(&gpio->irq_mask_config, 0xff, sizeof(gpio->irq_mask_config));
+
+	INIT_KFIFO(gpio->irq_event_fifo);
+	INIT_WORK(&gpio->irq_event_handler, upisnd_gpio_irq_event_handler);
+
+	gpio->gpio_chip = upisnd_gpio_chip;
+	gpio->gpio_chip.parent = instance->ctrl_dev;
+
+	gpio->gpio_irq = &upisnd_gpio_irq_chip;
+
+	struct gpio_irq_chip *girq = &gpio->gpio_chip.irq;
+
+	gpio_irq_chip_set_chip(girq, gpio->gpio_irq);
+	girq->threaded = true;
+	girq->num_parents = 1;
+	girq->parents = devm_kzalloc(instance->ctrl_dev, sizeof(*girq->parents), GFP_KERNEL);
+
+	if (!girq->parents)
+		return -ENOMEM;
+
+	*girq->parents = irq_of_parse_and_map(instance->ctrl_dev->of_node, 0);
+
+	printd("Doing devm_gpiochip_add_data");
+	int err = devm_gpiochip_add_data(instance->ctrl_dev, &gpio->gpio_chip, instance);
+
+	printd("result: %d, base = %d", err, gpio->gpio_chip.base);
+	return err;
+}
+
+void upisnd_gpio_uninit(struct upisnd_instance *instance)
+{
+	kfifo_free(&instance->gpio.irq_event_fifo);
+}
+
+void upisnd_gpio_set(struct upisnd_instance *instance, unsigned int offset, bool value)
+{
+	upisnd_gpio_chip_set(&instance->gpio.gpio_chip, offset, value);
+}
+
+int upisnd_gpio_get(struct upisnd_instance *instance, unsigned int offset)
+{
+	return upisnd_gpio_chip_get(&instance->gpio.gpio_chip, offset);
+}
+
+int upisnd_gpio_get_direction(struct upisnd_instance *instance, unsigned int offset)
+{
+	return upisnd_gpio_chip_get_direction(&instance->gpio.gpio_chip, offset);
+}
+
+int upisnd_gpio_set_irq_type(struct upisnd_instance *instance,
+			     unsigned int offset,
+			     unsigned int irq_type)
+{
+	printd("(%d, %d)", offset, irq_type);
+	if (offset >= UPISND_NUM_GPIOS)
+		return -EINVAL;
+
+	if (!(irq_type & IRQ_TYPE_EDGE_BOTH)) {
+		printe("IRQ %d: unsupported type %u\n", offset, irq_type);
+		return -ENOTSUPP;
+	}
+
+	mutex_lock(&instance->gpio.gpio_irq_lock);
+
+	if (upisnd_setup_get_element_type(instance->gpio.pin_configs[offset].setup) !=
+		UPISND_ELEMENT_TYPE_GPIO ||
+		upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[offset].setup) !=
+		UPISND_PIN_DIR_INPUT) {
+		printe("Pin %s is not set up as an input!", upisnd_pin_name(offset));
+		return -EACCES;
+	}
+	upisnd_irq_config_set_irq_type(&instance->gpio.irq_type_config, offset, irq_type);
+	int result = upisnd_comm_set_irq_types(instance, &instance->gpio.irq_type_config);
+	(void)result;
+
+	mutex_unlock(&instance->gpio.gpio_irq_lock);
+
+	printd("result: %d", result);
+	return 0;
+}
+
+int upisnd_gpio_set_subscription(struct upisnd_instance *instance, unsigned int offset, bool on)
+{
+	printd("(%d, %d)", offset, on);
+
+	if (offset >= UPISND_NUM_GPIOS)
+		return -EINVAL;
+
+	int result = 0;
+	int *rc = &instance->gpio.pin_subscription_refcounts[offset];
+	int c;
+
+	mutex_lock(&instance->gpio.gpio_irq_lock);
+
+	if (on)
+		c = (*rc)++;
+	else
+		c = --(*rc);
+
+	if (c == 0) {
+		printd("Count was 0, setting %d sub to %d", offset, on);
+		result = upisnd_comm_set_subscription(instance, offset, on);
+	}
+
+	mutex_unlock(&instance->gpio.gpio_irq_lock);
+
+	return result;
+}
+
+enum upisnd_element_type_e upisnd_gpio_get_type(struct upisnd_instance *instance,
+						unsigned int offset)
+{
+	if (offset >= UPISND_NUM_GPIOS)
+		return UPISND_ELEMENT_TYPE_NONE;
+
+	down_read(&instance->rw_gpio_config_sem);
+	upisnd_setup_t setup = instance->gpio.pin_configs[offset].setup;
+
+	up_read(&instance->rw_gpio_config_sem);
+
+	return upisnd_setup_get_element_type(setup);
+}
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/drivers/upisnd/upisnd_gpio.h b/sound/drivers/upisnd/upisnd_gpio.h
new file mode 100644
index 000000000000..7ad50b557063
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_gpio.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef UPISOUND_GPIO_H
+#define UPISOUND_GPIO_H
+
+enum {
+	UPISND_PIN_FLAG_IS_ENCODER_B              = 1 << 0,
+	UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS = 1 << 1,
+	UPISND_PIN_FLAG_IRQ_RISING                = 1 << 2,
+	UPISND_PIN_FLAG_IRQ_FALLING               = 1 << 3,
+};
+
+struct upisnd_pin_config_t {
+	upisnd_setup_t                    setup;
+	u32                          flags;
+	struct gpio_desc                  *gpio_desc;
+	struct upisnd_element             *element;
+};
+
+struct upisnd_gpio {
+	struct gpio_chip                  gpio_chip;
+	const struct irq_chip            *gpio_irq;
+	// Protects IRQ state and bus.
+	struct mutex                      gpio_irq_lock;
+	unsigned long                     gpio_state[2];
+	unsigned long                     irq_flags;
+	struct upisnd_irq_type_config_t   irq_type_config;
+	struct upisnd_irq_mask_config_t   irq_mask_config;
+	struct upisnd_pin_config_t        pin_configs[UPISND_NUM_GPIOS];
+	int                               pin_subscription_refcounts[UPISND_NUM_GPIOS];
+	DECLARE_KFIFO(irq_event_fifo, struct irq_event_t, 128);
+	struct work_struct                irq_event_handler;
+};
+
+int upisnd_gpio_init(struct upisnd_instance *instance);
+void upisnd_gpio_uninit(struct upisnd_instance *instance);
+
+void upisnd_gpio_reset(struct upisnd_instance *instance);
+
+int upisnd_gpio_setup(struct upisnd_instance *instance, upisnd_setup_t setup);
+
+void upisnd_gpio_handle_irq_event(struct upisnd_instance *instance,
+				  const struct irq_event_t *events,
+				  unsigned int n);
+
+void upisnd_gpio_set(struct upisnd_instance *instance, unsigned int offset, bool value);
+int upisnd_gpio_get(struct upisnd_instance *instance, unsigned int offset);
+int upisnd_gpio_get_direction(struct upisnd_instance *instance, unsigned int offset);
+
+int upisnd_gpio_set_irq_type(struct upisnd_instance *instance,
+			     unsigned int offset,
+			     unsigned int irq_type);
+
+int upisnd_gpio_set_subscription(struct upisnd_instance *instance, unsigned int offset, bool on);
+
+enum upisnd_element_type_e upisnd_gpio_get_type(struct upisnd_instance *instance,
+						unsigned int offset);
+
+#endif // UPISOUND_GPIO_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/drivers/upisnd/upisnd_midi.c b/sound/drivers/upisnd/upisnd_midi.c
new file mode 100644
index 000000000000..b7fea8227bf6
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_midi.c
@@ -0,0 +1,337 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "upisnd_common.h"
+
+static int upisnd_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+	struct upisnd_instance *instance = substream->rmidi->private_data;
+
+	mutex_lock(&instance->midi.out_lock);
+	instance->midi.midi_output = substream;
+	mutex_unlock(&instance->midi.out_lock);
+	return 0;
+}
+
+static void upisnd_midi_output_drain(struct snd_rawmidi_substream *substream);
+
+static int upisnd_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+	struct upisnd_instance *instance = substream->rmidi->private_data;
+
+	printd("Close: draining output");
+	upisnd_midi_output_drain(substream);
+	printd("Close: setting to null");
+	mutex_lock(&instance->midi.out_lock);
+	instance->midi.midi_output = NULL;
+	mutex_unlock(&instance->midi.out_lock);
+	return 0;
+}
+
+static void upisnd_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	if (up == 0)
+		return;
+
+	struct upisnd_instance *instance = substream->rmidi->private_data;
+
+	if (!delayed_work_pending(&instance->midi.midi_out_handler))
+		queue_delayed_work(instance->work_queue, &instance->midi.midi_out_handler, 0);
+}
+
+static void upisnd_midi_output_drain(struct snd_rawmidi_substream *substream)
+{
+	struct upisnd_instance *instance = substream->rmidi->private_data;
+
+	printd("Begin draining!");
+
+	do {
+		printd("Before flush");
+		while (delayed_work_pending(&instance->midi.midi_out_handler))
+			flush_delayed_work(&instance->midi.midi_out_handler);
+		printd("Flushed");
+	} while (!snd_rawmidi_transmit_empty(substream));
+
+	printd("Done!");
+}
+
+static int upisnd_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+	struct upisnd_instance *instance = substream->rmidi->private_data;
+
+	mutex_lock(&instance->midi.in_lock);
+	instance->midi.midi_input = substream;
+	mutex_unlock(&instance->midi.in_lock);
+	return 0;
+}
+
+static int upisnd_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+	struct upisnd_instance *instance = substream->rmidi->private_data;
+
+	mutex_lock(&instance->midi.in_lock);
+	instance->midi.midi_input = NULL;
+	mutex_unlock(&instance->midi.in_lock);
+	return 0;
+}
+
+static void upisnd_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	if (up == 0)
+		return;
+
+	struct upisnd_instance *instance = substream->rmidi->private_data;
+
+	if (!work_pending(&instance->midi.midi_in_handler))
+		queue_work(instance->work_queue, &instance->midi.midi_in_handler);
+}
+
+static const struct snd_rawmidi_ops upisnd_midi_output_ops = {
+	.open = upisnd_midi_output_open,
+	.close = upisnd_midi_output_close,
+	.trigger = upisnd_midi_output_trigger,
+	.drain = upisnd_midi_output_drain,
+};
+
+static const struct snd_rawmidi_ops upisnd_midi_input_ops = {
+	.open = upisnd_midi_input_open,
+	.close = upisnd_midi_input_close,
+	.trigger = upisnd_midi_input_trigger,
+};
+
+static void upisnd_get_port_info(struct snd_rawmidi *rmidi,
+				 int number,
+				 struct snd_seq_port_info *seq_port_info)
+{
+	seq_port_info->type =
+		SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
+		SNDRV_SEQ_PORT_TYPE_HARDWARE |
+		SNDRV_SEQ_PORT_TYPE_PORT;
+	seq_port_info->midi_voices = 0;
+}
+
+static const struct snd_rawmidi_global_ops upisnd_midi_ops = {
+	.get_port_info = upisnd_get_port_info,
+};
+
+static void upisnd_midi_in_handler(struct work_struct *work)
+{
+	int n, err;
+
+	printd("In handler");
+	struct upisnd_instance *instance = container_of(work,
+							struct upisnd_instance,
+							midi.midi_in_handler);
+
+	mutex_lock(&instance->midi.in_lock);
+	if (!instance->midi.midi_input)
+		goto cleanup;
+
+	u8 data[512];
+
+	n = kfifo_out_peek(&instance->midi.midi_in_fifo, data, sizeof(data));
+	err = snd_rawmidi_receive(instance->midi.midi_input, data, n);
+
+	if (err < 0)
+		printe("snd_rawmidi_receive failed! (%d)", err);
+
+	if (err > 0) {
+		printd("Received %d MIDI bytes", err);
+		instance->midi.rx_cnt += err;
+	}
+
+	kfifo_skip_count(&instance->midi.midi_in_fifo, err);
+
+	if (!kfifo_is_empty(&instance->midi.midi_in_fifo) &&
+	    !work_pending(&instance->midi.midi_in_handler)) {
+		queue_work(instance->work_queue, &instance->midi.midi_in_handler);
+	}
+cleanup:
+	mutex_unlock(&instance->midi.in_lock);
+	printd("Done");
+}
+
+static void upisnd_midi_out_handler(struct work_struct *work)
+{
+	printd("Out handler");
+	struct upisnd_instance *instance = container_of(work,
+							struct upisnd_instance,
+							midi.midi_out_handler.work);
+
+	mutex_lock(&instance->midi.out_lock);
+	printd("midi_output = %p", instance->midi.midi_output);
+	if (!instance->midi.midi_output)
+		goto cleanup;
+
+	enum { MIDI_MILLI_BYTES_PER_JIFFY = 3125000 / HZ };
+	enum { MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES = 4096000 };
+
+	unsigned int now = jiffies;
+	unsigned int millibytes_became_available = (MIDI_MILLI_BYTES_PER_JIFFY) *
+		(now - instance->midi.last_midi_output_at);
+
+	instance->midi.output_buffer_used_in_millibytes =
+		instance->midi.output_buffer_used_in_millibytes <= millibytes_became_available ?
+		0 : instance->midi.output_buffer_used_in_millibytes - millibytes_became_available;
+	instance->midi.last_midi_output_at = now;
+
+	unsigned int output_buffer_available = (MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES -
+		instance->midi.output_buffer_used_in_millibytes) / 1000;
+
+	u8 buffer[UPISND_MAX_PACKET_LENGTH - 1];
+	int n, batch, err;
+
+	printd("Available: %u", output_buffer_available);
+
+	for (batch = 0; batch < 3; ++batch) {
+		if (output_buffer_available == 0)
+			break;
+
+		n = snd_rawmidi_transmit_peek(instance->midi.midi_output,
+					      buffer,
+					      min(output_buffer_available, sizeof(buffer)));
+
+		if (n > 0) {
+			printd("Peeked: %d (batch %d)", n, batch);
+			err = upisnd_comm_send_midi(instance, buffer, (unsigned int)n);
+			if (err < 0) {
+				printe("Error occurred when sending MIDI data over I2C! (%d)", n);
+				goto cleanup;
+			}
+			snd_rawmidi_transmit_ack(instance->midi.midi_output, n);
+
+			instance->midi.tx_cnt += n;
+			instance->midi.output_buffer_used_in_millibytes += n * 1000;
+			output_buffer_available -= n;
+		} else if (n < 0) {
+			printe("snd_rawmidi_transmit_peek returned error %d!", n);
+			goto cleanup;
+		} else {
+			break;
+		}
+	}
+
+	printd("Checking if empty %p", instance->midi.midi_output);
+	if (!snd_rawmidi_transmit_empty(instance->midi.midi_output)) {
+		unsigned int delay = 0;
+
+		if (instance->midi.output_buffer_used_in_millibytes >
+		    MIDI_MAX_OUTPUT_BUFFER_SIZE_IN_MILLIBYTES - 127000)
+			delay = 127000 / MIDI_MILLI_BYTES_PER_JIFFY;
+		printd("Queue more work after %u jiffies", delay);
+		queue_delayed_work(instance->work_queue, &instance->midi.midi_out_handler, delay);
+	}
+
+cleanup:
+	mutex_unlock(&instance->midi.out_lock);
+	printd("Done");
+}
+
+static void upisnd_proc_stat_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	const unsigned int *d = entry->private_data;
+
+	snd_iprintf(buffer, "%u\n", *d);
+}
+
+int upisnd_midi_init(struct upisnd_instance *instance)
+{
+	int err;
+
+	mutex_init(&instance->midi.in_lock);
+	mutex_init(&instance->midi.out_lock);
+
+	err = snd_card_ro_proc_new(instance->sound_card.snd_card, "tx", &instance->midi.tx_cnt,
+				   upisnd_proc_stat_show);
+	err = snd_card_ro_proc_new(instance->sound_card.snd_card, "rx", &instance->midi.rx_cnt,
+				   upisnd_proc_stat_show);
+
+	err = snd_rawmidi_new(instance->sound_card.snd_card,
+			      "pisoundmicro", 0, 1, 1,
+			      &instance->midi.rawmidi);
+
+	struct snd_rawmidi *rawmidi = instance->midi.rawmidi;
+
+	if (err < 0) {
+		printe("snd_rawmidi_new failed: %d\n", err);
+		return err;
+	}
+
+	strscpy(rawmidi->name, "pisound-micro ", sizeof(rawmidi->name));
+	strcat(rawmidi->name, instance->ctrl.serial);
+
+	rawmidi->info_flags =
+		SNDRV_RAWMIDI_INFO_OUTPUT |
+		SNDRV_RAWMIDI_INFO_INPUT |
+		SNDRV_RAWMIDI_INFO_DUPLEX;
+
+	rawmidi->ops = &upisnd_midi_ops;
+
+	rawmidi->private_data = instance;
+
+	snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &upisnd_midi_output_ops);
+
+	snd_rawmidi_set_ops(rawmidi, SNDRV_RAWMIDI_STREAM_INPUT, &upisnd_midi_input_ops);
+
+	INIT_KFIFO(instance->midi.midi_in_fifo);
+	instance->midi.last_midi_output_at = jiffies;
+
+	INIT_WORK(&instance->midi.midi_in_handler, upisnd_midi_in_handler);
+	INIT_DELAYED_WORK(&instance->midi.midi_out_handler, upisnd_midi_out_handler);
+
+	return 0;
+}
+
+void upisnd_midi_uninit(struct upisnd_instance *instance)
+{
+	if (!instance->midi.rawmidi)
+		return;
+
+	cancel_work_sync(&instance->midi.midi_in_handler);
+	cancel_delayed_work_sync(&instance->midi.midi_out_handler);
+
+	instance->midi.rawmidi->private_data = NULL;
+
+	instance->midi.rawmidi = NULL;
+	mutex_lock(&instance->midi.in_lock);
+	instance->midi.midi_input = NULL;
+	mutex_unlock(&instance->midi.in_lock);
+	mutex_lock(&instance->midi.out_lock);
+	instance->midi.midi_output = NULL;
+	mutex_unlock(&instance->midi.out_lock);
+
+	kfifo_free(&instance->midi.midi_in_fifo);
+}
+
+void upisnd_handle_midi_data(struct upisnd_instance *instance, const uint8_t *data, unsigned int n)
+{
+	printd("%p, %u", instance, n);
+	if (n == 0)
+		return;
+
+	int i;
+
+	for (i = 0; i < n; ++i) {
+		kfifo_put(&instance->midi.midi_in_fifo, data[i]);
+		printd("Received MIDI %02x", data[i]);
+	}
+
+	if (!work_pending(&instance->midi.midi_in_handler))
+		queue_work(instance->work_queue, &instance->midi.midi_in_handler);
+	printd("Done");
+}
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/drivers/upisnd/upisnd_midi.h b/sound/drivers/upisnd/upisnd_midi.h
new file mode 100644
index 000000000000..0a4678e17e07
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_midi.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef UPISOUND_MIDI_H
+#define UPISOUND_MIDI_H
+
+struct upisnd_instance;
+
+struct upisnd_midi {
+	// in_lock protects the input substream.
+	struct mutex                      in_lock;
+	// out_lock protects the output substream.
+	struct mutex                      out_lock;
+	struct work_struct                midi_in_handler;
+	struct delayed_work               midi_out_handler;
+
+	struct snd_rawmidi                *rawmidi;
+	struct snd_rawmidi_substream      *midi_input;
+	struct snd_rawmidi_substream      *midi_output;
+	DECLARE_KFIFO(midi_in_fifo, uint8_t, 4096);
+	unsigned int                      last_midi_output_at;
+	unsigned int                      output_buffer_used_in_millibytes;
+	unsigned int                      tx_cnt;
+	unsigned int                      rx_cnt;
+};
+
+int upisnd_midi_init(struct upisnd_instance *instance);
+void upisnd_midi_uninit(struct upisnd_instance *instance);
+
+void upisnd_handle_midi_data(struct upisnd_instance *instance, const uint8_t *data, unsigned int n);
+
+#endif // UPISOUND_MIDI_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/drivers/upisnd/upisnd_module.c b/sound/drivers/upisnd/upisnd_module.c
new file mode 100644
index 000000000000..ef545313c0c2
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_module.c
@@ -0,0 +1,273 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "upisnd_common.h"
+
+void upisnd_instance_release(struct kref *kref)
+{
+	struct upisnd_instance *instance = container_of(kref, struct upisnd_instance, refcount);
+
+	if (instance->codec_dev) {
+		put_device(instance->codec_dev);
+		instance->codec_dev = NULL;
+	}
+
+	if (instance->ctrl_dev) {
+		instance->ctrl_dev->platform_data = NULL;
+		put_device(instance->ctrl_dev);
+		instance->ctrl_dev = NULL;
+	}
+
+	if (instance->sound_card.dev) {
+		snd_soc_unregister_card(&instance->sound_card);
+		memset(&instance->sound_card, 0, sizeof(instance->sound_card));
+	}
+
+	if (instance->work_queue) {
+		flush_workqueue(instance->work_queue);
+		destroy_workqueue(instance->work_queue);
+		instance->work_queue = NULL;
+	}
+
+	printd("Releasing instance %p", instance);
+	kfree(instance);
+}
+
+static int of_dev_node_match(struct device *dev, const void *data)
+{
+	return dev->of_node == data;
+}
+
+static int upisnd_probe(struct platform_device *pdev)
+{
+	printd("Load %p", pdev);
+
+	int err = 0;
+	struct device_node *node;
+	struct device_node *i2c_node;
+	struct device *dev;
+
+	struct upisnd_instance *instance = pdev->dev.platform_data;
+
+	if (!instance) {
+		instance = kzalloc(sizeof(*instance), GFP_KERNEL);
+
+		if (!instance) {
+			printe("Failed to allocate instance!");
+			err = -ENOMEM;
+			goto cleanup;
+		}
+		kref_init(&instance->refcount);
+
+		pdev->dev.platform_data = instance;
+		instance->pdev = pdev;
+
+		init_rwsem(&instance->rw_gpio_config_sem);
+
+		instance->work_queue = create_singlethread_workqueue("upisnd_workqueue");
+		if (!instance->work_queue) {
+			printe("Failed creating single thread work queue!");
+			err = -ENOMEM;
+			goto cleanup;
+		}
+	}
+
+	node = pdev->dev.of_node;
+	if (!node) {
+		printe("Device node not found!");
+		err = -ENODEV;
+		goto cleanup;
+	}
+
+	i2c_node = of_parse_phandle(node, "ctrl", 0);
+	if (!i2c_node) {
+		printe("Failed to read 'ctrl' node!");
+		err = -ENODEV;
+		goto cleanup;
+	}
+
+	dev = bus_find_device(&i2c_bus_type, NULL, i2c_node, of_dev_node_match);
+	of_node_put(i2c_node);
+
+	if (!dev) {
+		printe("Failed to find 'ctrl' device (%pOF)!", i2c_node);
+		err = -ENODEV;
+		goto cleanup;
+	}
+
+	bool got_ref = false;
+
+	if (!dev->platform_data) {
+		kref_get(&instance->refcount);
+		dev->platform_data = instance;
+		got_ref = true;
+	}
+	instance->ctrl_dev = dev;
+
+	if (instance->ctrl.serial[0] == '\0') {
+		printd("Deferring probe until serial is retrieved!");
+		return -EPROBE_DEFER;
+	}
+
+	i2c_node = of_parse_phandle(node, "codec", 0);
+
+	if (!i2c_node) {
+		printe("Failed to read 'codec' node!");
+		err = -ENODEV;
+		goto cleanup;
+	}
+
+	dev = bus_find_device(&i2c_bus_type, NULL, i2c_node, of_dev_node_match);
+	of_node_put(i2c_node);
+
+	if (!dev) {
+		printe("Failed to find 'codec' device!");
+		err = -ENODEV;
+		goto cleanup;
+	}
+
+	instance->codec_dev = dev;
+
+	err = upisnd_sound_init(pdev, instance);
+	if (err != 0) {
+		if (err != -EPROBE_DEFER)
+			printe("Failed initializing sound card! (%d)", err);
+		goto cleanup;
+	}
+
+cleanup:
+	if (err != 0) {
+		if (err != -EPROBE_DEFER)
+			printe("Error %d!", err);
+
+		if (instance) {
+			if (got_ref) {
+				instance->ctrl_dev->platform_data = NULL;
+				kref_put(&instance->refcount, &upisnd_instance_release);
+			}
+
+			if (instance->codec_dev) {
+				put_device(instance->codec_dev);
+				instance->codec_dev = NULL;
+			}
+		}
+	}
+
+	return err;
+}
+
+static void upisnd_remove(struct platform_device *pdev)
+{
+	printd("Unload %p", pdev);
+	struct upisnd_instance *instance = dev_get_platdata(&pdev->dev);
+
+	kref_put(&instance->refcount, &upisnd_instance_release);
+	pdev->dev.platform_data = NULL;
+}
+
+static const struct of_device_id upisnd_of_match[] = {
+	{.compatible = "blokas,pisound-micro" },
+	{}
+};
+
+static const struct of_device_id upisnd_ctrl_of_match[] = {
+	{.compatible = "blokas,upisnd-ctrl" },
+	{}
+};
+
+static struct platform_driver upisnd_driver = {
+	.driver = {
+		.name = "snd-pisound-micro",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(upisnd_of_match),
+	},
+	.probe = upisnd_probe,
+	.remove = upisnd_remove,
+};
+
+static const struct i2c_device_id upisnd_ctrl_idtable[] = {
+	{ "blokas,upisnd-ctrl", 0 },
+	{}
+};
+
+static struct i2c_driver upisnd_ctrl_driver = {
+	.driver = {
+		.name = "snd-pisound-micro-ctrl",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(upisnd_ctrl_of_match),
+	},
+	.id_table = upisnd_ctrl_idtable,
+	.probe = upisnd_ctrl_probe,
+	.remove = upisnd_ctrl_remove,
+};
+
+static int upisnd_module_init(void)
+{
+	int err, progress = 0;
+
+	err = upisnd_comm_module_init();
+	if (err != 0)
+		goto cleanup;
+
+	++progress;
+	err = platform_driver_register(&upisnd_driver);
+	if (err != 0)
+		goto cleanup;
+
+	++progress;
+	err = i2c_add_driver(&upisnd_ctrl_driver);
+	if (err != 0)
+		goto cleanup;
+
+cleanup:
+	if (err) {
+		printe("Error %d occurred, progress: %d", err, progress);
+		switch (progress) {
+		case 2:
+			i2c_del_driver(&upisnd_ctrl_driver);
+			fallthrough;
+		case 1:
+			platform_driver_unregister(&upisnd_driver);
+			fallthrough;
+		case 0:
+			// No comm uninit.
+			fallthrough;
+		default:
+			break;
+		}
+	}
+
+	return err;
+}
+
+static void upisnd_module_exit(void)
+{
+	i2c_del_driver(&upisnd_ctrl_driver);
+	platform_driver_unregister(&upisnd_driver);
+}
+
+module_init(upisnd_module_init);
+module_exit(upisnd_module_exit);
+
+MODULE_DEVICE_TABLE(of, upisnd_of_match);
+MODULE_DEVICE_TABLE(of, upisnd_ctrl_of_match);
+MODULE_DEVICE_TABLE(i2c, upisnd_ctrl_idtable);
+
+MODULE_AUTHOR("Giedrius Trainavi\xc4\x8dius <giedrius@blokas.io>");
+MODULE_DESCRIPTION("Audio, MIDI & I/O Driver for Pisound Micro, https://blokas.io/");
+MODULE_LICENSE("GPL v2");
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/drivers/upisnd/upisnd_pins.c b/sound/drivers/upisnd/upisnd_pins.c
new file mode 100644
index 000000000000..8e3e9bac6bed
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_pins.c
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "upisnd_common.h"
+
+struct upisnd_pin_def_t {
+	const char                   *name;
+	upisnd_pin_capability_mask_t capabilities;
+};
+
+static const struct upisnd_pin_def_t upisnd_pins[UPISND_PIN_COUNT] = {
+	{ "A27", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
+	{ "A28", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
+	{ "A29", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
+	{ "A30", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
+	{ "A31", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
+	{ "A32", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
+	{ "B03", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B04", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B05", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B06", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B07", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B08", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B09", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B10", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	// Pull down is not available in MCU revisions A, B, C, D, E. F and G are not affected.
+	{ "B11", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	// Pull down is not available in MCU revisions A, B, C, D, E. F and G are not affected.
+	{ "B12", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B13", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B14", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B15", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B16", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B17", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B18", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ENCODER },
+	{ "B23", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B24", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B25", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B26", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B27", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B28", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B29", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B30", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B31", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B32", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B33", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B34", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY | UPISND_PIN_CAP_ANALOG_IN },
+	{ "B37", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
+	{ "B38", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
+	{ "B39", UPISND_PIN_CAP_GPIO | UPISND_PIN_CAP_MIDI_ACTIVITY },
+};
+
+upisnd_pin_t upisnd_name_to_pin(const char *name)
+{
+	if (!name || strlen(name) != 3 || !isdigit(name[1]) || !isdigit(name[2]))
+		return UPISND_PIN_INVALID;
+
+	char sanitized[4];
+
+	switch (*name) {
+	case 'a': case 'A':
+		sanitized[0] = 'A';
+		break;
+	case 'b': case 'B':
+		sanitized[0] = 'B';
+		break;
+	default:
+		return UPISND_PIN_INVALID;
+	}
+
+	memcpy(&sanitized[1], &name[1], 2);
+	sanitized[3] = '\0';
+
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(upisnd_pins); ++i) {
+		if (strcmp(sanitized, upisnd_pins[i].name) == 0)
+			return i;
+	}
+
+	return UPISND_PIN_INVALID;
+}
+
+const char *upisnd_pin_name(upisnd_pin_t pin)
+{
+	if (!upisnd_is_pin_valid(pin))
+		return "";
+
+	return upisnd_pins[pin].name;
+}
+
+int upisnd_check_caps(upisnd_pin_t pin, upisnd_pin_capability_mask_t mask)
+{
+	if (!upisnd_is_pin_valid(pin))
+		return -ENXIO;
+
+	return (upisnd_pins[pin].capabilities & mask) == mask ? 0 : -EINVAL;
+}
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/drivers/upisnd/upisnd_pins.h b/sound/drivers/upisnd/upisnd_pins.h
new file mode 100644
index 000000000000..5f4b3a5660a3
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_pins.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef UPISOUND_PINS_H
+#define UPISOUND_PINS_H
+
+enum { UPISND_NUM_GPIOS = 37 };
+
+enum upisnd_pin_e {
+	UPISND_PIN_A27, UPISND_PIN_A28, UPISND_PIN_A29, UPISND_PIN_A30,
+	UPISND_PIN_A31, UPISND_PIN_A32, UPISND_PIN_B03, UPISND_PIN_B04,
+	UPISND_PIN_B05, UPISND_PIN_B06, UPISND_PIN_B07, UPISND_PIN_B08,
+	UPISND_PIN_B09, UPISND_PIN_B10, UPISND_PIN_B11, UPISND_PIN_B12,
+	UPISND_PIN_B13, UPISND_PIN_B14, UPISND_PIN_B15, UPISND_PIN_B16,
+	UPISND_PIN_B17, UPISND_PIN_B18, UPISND_PIN_B23, UPISND_PIN_B24,
+	UPISND_PIN_B25, UPISND_PIN_B26, UPISND_PIN_B27, UPISND_PIN_B28,
+	UPISND_PIN_B29, UPISND_PIN_B30, UPISND_PIN_B31, UPISND_PIN_B32,
+	UPISND_PIN_B33, UPISND_PIN_B34, UPISND_PIN_B37, UPISND_PIN_B38,
+	UPISND_PIN_B39,
+
+	UPISND_PIN_COUNT,
+	UPISND_PIN_INVALID = UPISND_PIN_COUNT
+};
+
+typedef u8 upisnd_pin_t;
+
+static inline bool upisnd_is_pin_valid(upisnd_pin_t pin)
+{
+	return pin < UPISND_PIN_COUNT;
+}
+
+enum upisnd_pin_capability_flags_e {
+	UPISND_PIN_CAP_GPIO_DIR_INPUT  = 1 << 0,
+	UPISND_PIN_CAP_GPIO_DIR_OUTPUT = 1 << 1,
+	UPISND_PIN_CAP_GPIO_PULL_UP    = 1 << 2,
+	UPISND_PIN_CAP_GPIO_PULL_DOWN  = 1 << 3,
+	UPISND_PIN_CAP_GPIO            = UPISND_PIN_CAP_GPIO_DIR_INPUT |
+					 UPISND_PIN_CAP_GPIO_DIR_OUTPUT |
+					 UPISND_PIN_CAP_GPIO_PULL_UP |
+					 UPISND_PIN_CAP_GPIO_PULL_DOWN,
+	UPISND_PIN_CAP_ENCODER         = 1 << 4,
+	UPISND_PIN_CAP_ANALOG_IN       = 1 << 5,
+	UPISND_PIN_CAP_MIDI_ACTIVITY   = 1 << 6,
+};
+
+typedef u8 upisnd_pin_capability_mask_t;
+
+upisnd_pin_t upisnd_name_to_pin(const char *name);
+const char *upisnd_pin_name(upisnd_pin_t pin);
+int upisnd_check_caps(upisnd_pin_t pin, upisnd_pin_capability_mask_t mask);
+
+#endif // UPISOUND_PINS_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/drivers/upisnd/upisnd_protocol.h b/sound/drivers/upisnd/upisnd_protocol.h
new file mode 100644
index 000000000000..59beaa3e6cbf
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_protocol.h
@@ -0,0 +1,323 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef UPISND_PROTOCOL_H
+#define UPISND_PROTOCOL_H
+
+#ifndef __KERNEL__
+#include <stdint.h>
+#include <stdbool.h>
+
+enum upisnd_irq_type_e {
+	IRQ_TYPE_NONE         = 0,
+	IRQ_TYPE_EDGE_RISING  = 1,
+	IRQ_TYPE_EDGE_FALLING = 2,
+	IRQ_TYPE_EDGES_BOTH   = 3,
+};
+#endif
+
+enum { UPISND_ON_BIT_MASK  = 0x80 };
+enum { UPISND_IRQ_NUM_MASK = 0x7f };
+enum { UPISND_PIN_MASK     = 0x3f };
+
+enum upisnd_irq_num_e {
+	UPISND_IRQ_GPIO_START       = 0,
+	// IRQ numbers match GPIO numbers.
+	UPISND_IRQ_GPIO_END         = UPISND_PIN_MASK,
+
+	UPISND_IRQ_VGND_SHORT_ALERT = 0x7f,
+};
+
+typedef u8 upisnd_pin_t;
+typedef u8 upisnd_irq_num_t;
+typedef u8 upisnd_msg_id_t;
+
+typedef u32 upisnd_setup_t;
+
+enum upisnd_activity_type_e {
+	UPISND_ACTIVITY_TYPE_MIDI_IN  = 0,
+	UPISND_ACTIVITY_TYPE_MIDI_OUT = 1,
+};
+
+enum upisnd_pin_pull_e {
+	UPISND_PIN_PULL_NONE = 0,
+	UPISND_PIN_PULL_UP   = 1,
+	UPISND_PIN_PULL_DOWN = 2
+};
+
+enum upisnd_pin_direction_e {
+	UPISND_PIN_DIR_INPUT  = 0,
+	UPISND_PIN_DIR_OUTPUT = 1
+};
+
+enum upisnd_element_type_e {
+	UPISND_ELEMENT_TYPE_NONE          = 0,
+	UPISND_ELEMENT_TYPE_ENCODER       = 1,
+	UPISND_ELEMENT_TYPE_ANALOG_IN     = 2,
+	UPISND_ELEMENT_TYPE_GPIO          = 3,
+	UPISND_ELEMENT_TYPE_ACTIVITY      = 4,
+};
+
+#define UPISND_DEFINE_SETUP_FIELD(shift, bits, type, name) \
+	static inline type upisnd_setup_get_ ## name(upisnd_setup_t setup) \
+	{ \
+		return (type)(((setup) & (((1 << (bits)) - 1) << (shift))) >> (shift)); \
+	} \
+	static inline void upisnd_setup_set_ ## name(upisnd_setup_t *setup, type value) \
+	{ \
+		*(setup) = ((*(setup)) & ~(((1 << (bits)) - 1) << (shift))) | \
+			 (((value) & ((1 << (bits)) - 1)) << (shift)); \
+	}
+
+UPISND_DEFINE_SETUP_FIELD(0, 3, enum upisnd_element_type_e, element_type);
+UPISND_DEFINE_SETUP_FIELD(3, 8, upisnd_pin_t, pin_id);
+UPISND_DEFINE_SETUP_FIELD(11, 2, enum upisnd_pin_pull_e, gpio_pull);
+UPISND_DEFINE_SETUP_FIELD(13, 1, enum upisnd_pin_direction_e, gpio_dir);
+UPISND_DEFINE_SETUP_FIELD(12, 1, bool, gpio_output);
+UPISND_DEFINE_SETUP_FIELD(13, 8, upisnd_pin_t, encoder_pin_b_id);
+UPISND_DEFINE_SETUP_FIELD(21, 2, enum upisnd_pin_pull_e, encoder_pin_b_pull);
+UPISND_DEFINE_SETUP_FIELD(11, 2, enum upisnd_activity_type_e, activity_type);
+
+#undef UPISND_DEFINE_SETUP_FIELD
+
+struct upisnd_irq_type_config_t {
+	u8 irq_types[10];
+} __packed;
+
+static inline void upisnd_irq_config_set_irq_type(struct upisnd_irq_type_config_t *cfg,
+						  upisnd_pin_t pin,
+						  unsigned int type)
+{
+	u8 *d = &cfg->irq_types[pin >> 2];
+	*d = (*d & ~(0x3 << (pin & 0x3))) | ((type & 0x3) << (pin & 0x3));
+}
+
+static inline unsigned int upisnd_irq_config_get_irq_type(const struct upisnd_irq_type_config_t
+							  *cfg, upisnd_pin_t pin)
+{
+	return (cfg->irq_types[pin >> 2] >> (pin & 0x3)) & 0x3;
+}
+
+struct upisnd_irq_mask_config_t {
+	u8 irq_mask[5];
+} __packed;
+
+static inline void upisnd_irq_mask_config_set(struct upisnd_irq_mask_config_t *cfg,
+					      upisnd_pin_t pin,
+					      bool mask)
+{
+	u8 *d = &cfg->irq_mask[pin >> 3];
+
+	if (mask)
+		*d |= 1 << (pin & 0x7);
+	else
+		*d &= ~(1 << (pin & 0x7));
+}
+
+static inline bool upisnd_irq_mask_config_get(const struct upisnd_irq_mask_config_t *cfg,
+					      upisnd_pin_t pin)
+{
+	return (cfg->irq_mask[pin >> 3] & (1 << (pin & 0x7))) != 0;
+}
+
+struct upisnd_all_gpio_state_t {
+	u8 state[5];
+} __packed;
+
+enum { UPISND_MAX_PACKET_LENGTH   = 16   };
+enum { UPISND_MSG_RESPONSE_FLAG   = 0x80 };
+enum { UPISND_MSG_ID_MASK         = 0x7f };
+enum { UPISND_MSG_ID_INVALID      = 0x00 };
+
+enum upisnd_cmd_type_e {
+	UPISND_CMD_MIDI               = 0x00,
+	UPISND_CMD_SETUP              = 0x10,
+	UPISND_CMD_SET_IRQ_TYPES      = 0x20,
+	UPISND_CMD_SET_IRQ_MASKS      = 0x30,
+	UPISND_CMD_SET_GPIO           = 0x40,
+	UPISND_CMD_GET_GPIO           = 0x50,
+	UPISND_CMD_SET_ALL_GPIOS      = 0x60,
+	UPISND_CMD_GET_ALL_GPIOS      = 0x70,
+	UPISND_CMD_RESULT             = 0x80,
+	UPISND_CMD_SET_SUBSCRIPTION   = 0x90,
+	UPISND_CMD_IRQ_EVENT          = 0xa0,
+	UPISND_CMD_GET                = 0xb0,
+	UPISND_CMD_SET                = 0xc0,
+	UPISND_CMD_CONTROL_EVENT      = 0xd0,
+
+	UPISND_CMD_INVALID            = 0xff
+};
+
+enum { UPISND_CMD_TYPE_MASK       = 0xf0 };
+enum { UPISND_CMD_LENGTH_MASK     = 0x0f };
+
+enum upisnd_value_id_t {
+	UPISND_VALUE_INVALID          = 0x00,
+	UPISND_VALUE_VERSION_INFO     = 0x01,
+	UPISND_VALUE_ADC_OFFSET       = 0x02,
+	UPISND_VALUE_ADC_GAIN         = 0x03,
+	UPISND_VALUE_SERIAL_NUMBER    = 0x04,
+	UPISND_VALUE_ELEMENT_VAL_BASE = 0x80,
+};
+
+struct upisnd_cmd_t {
+	u8             cmd_and_size;
+	u8             flags_and_msg_id;
+} __packed;
+
+static inline bool upisnd_cmd_is_response(const struct upisnd_cmd_t *cmd)
+{
+	return (cmd->flags_and_msg_id & UPISND_MSG_RESPONSE_FLAG) != 0;
+}
+
+struct upisnd_cmd_setup_t {
+	struct upisnd_cmd_t cmd;
+	upisnd_setup_t      setup;
+} __packed;
+
+struct upisnd_cmd_set_gpio_t {
+	u8             cmd_and_size;
+	u8             on_and_pin;
+} __packed;
+
+struct upisnd_cmd_get_gpio_t {
+	struct upisnd_cmd_t cmd;
+	u8             pin;
+} __packed;
+
+struct upisnd_cmd_set_all_gpios_t {
+	u8                        cmd_and_size;
+	struct upisnd_all_gpio_state_t state;
+} __packed;
+
+struct upisnd_cmd_get_all_gpios_t {
+	struct upisnd_cmd_t            cmd;
+} __packed;
+
+struct upisnd_cmd_get_all_gpios_response_t {
+	struct upisnd_cmd_t            cmd;
+	struct upisnd_all_gpio_state_t state;
+} __packed;
+
+struct upisnd_cmd_set_irq_types_t {
+	struct upisnd_cmd_t             cmd;
+	struct upisnd_irq_type_config_t config;
+} __packed;
+
+struct upisnd_cmd_set_irq_masks_t {
+	struct upisnd_cmd_t             cmd;
+	struct upisnd_irq_mask_config_t config;
+} __packed;
+
+struct upisnd_cmd_set_subscription_t {
+	struct upisnd_cmd_t cmd;
+	u8             on_and_irq_num;
+} __packed;
+
+struct upisnd_cmd_result_t {
+	struct upisnd_cmd_t cmd;
+	s8              result;
+} __packed;
+
+struct upisnd_cmd_get_t {
+	struct upisnd_cmd_t cmd;
+	u8             value_id;
+} __packed;
+
+enum { UPISND_CMD_GET_RESPONSE_INT32_SIZE    = sizeof(struct upisnd_cmd_get_t) + sizeof(int8_t) +
+					       sizeof(int32_t) };
+enum { UPISND_CMD_GET_RESPONSE_DATA_MIN_SIZE = sizeof(struct upisnd_cmd_get_t) + sizeof(int8_t) };
+
+struct upisnd_cmd_get_response_t {
+	struct upisnd_cmd_get_t cmd_get;
+	s8                  result;
+	union {
+		s32             value;
+		u8             data[12];
+	};
+} __packed;
+
+struct upisnd_cmd_set_t {
+	struct upisnd_cmd_t cmd;
+	u8             value_id;
+	s32             value;
+} __packed;
+
+struct upisnd_cmd_control_event_t {
+	u8             cmd_and_size;
+	u16            values[7];
+} __packed;
+
+static inline enum upisnd_cmd_type_e upisnd_cmd_decode_type(u8 b)
+{
+	return (enum upisnd_cmd_type_e)(b & UPISND_CMD_TYPE_MASK);
+}
+
+static inline u8 upisnd_cmd_decode_length(u8 b)
+{
+	return (b & UPISND_CMD_LENGTH_MASK) + 1u;
+}
+
+static inline u8 upisnd_cmd_encode(enum upisnd_cmd_type_e type, u8 size)
+{
+	return (size <= UPISND_MAX_PACKET_LENGTH && size > 0) ? (type | (size - 1))
+		: UPISND_CMD_INVALID;
+}
+
+static inline u8 upisnd_msg_id_encode(upisnd_msg_id_t msg_id, bool response)
+{
+	return (response ? 0x80 : 0x00) | (msg_id & UPISND_MSG_ID_MASK);
+}
+
+static inline upisnd_msg_id_t upisnd_msg_id_decode_id(u8 b)
+{
+	return b & UPISND_MSG_ID_MASK;
+}
+
+static inline void upisnd_cmd_prepare(struct upisnd_cmd_t *cmd,
+				      enum upisnd_cmd_type_e type,
+				      u8 size,
+				      upisnd_msg_id_t msg_id,
+				      bool response)
+{
+	cmd->cmd_and_size = upisnd_cmd_encode(type, size);
+	cmd->flags_and_msg_id = upisnd_msg_id_encode(msg_id, response);
+}
+
+#define upisnd_cmd_matches(_cmd, type, size) \
+	(upisnd_cmd_decode_type((_cmd)->cmd_and_size) == (type) && \
+	upisnd_cmd_decode_length((_cmd)->cmd_and_size) >= (size))
+
+static inline bool upisnd_cmd_type_has_msg_id(enum upisnd_cmd_type_e type)
+{
+	switch (type) {
+	default:
+		return false;
+	case UPISND_CMD_SETUP:
+	case UPISND_CMD_SET_IRQ_TYPES:
+	case UPISND_CMD_SET_IRQ_MASKS:
+	case UPISND_CMD_GET_GPIO:
+	case UPISND_CMD_GET_ALL_GPIOS:
+	case UPISND_CMD_RESULT:
+	case UPISND_CMD_GET:
+	case UPISND_CMD_SET:
+		return true;
+	}
+}
+
+#endif // UPISND_PROTOCOL_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/drivers/upisnd/upisnd_sound.c b/sound/drivers/upisnd/upisnd_sound.c
new file mode 100644
index 000000000000..69188a0f43d3
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_sound.c
@@ -0,0 +1,277 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "upisnd_common.h"
+#include "upisnd_codec.h"
+
+static void upisnd_proc_serial_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct upisnd_instance *instance = entry->private_data;
+
+	snd_iprintf(buffer, "%s\n", instance->ctrl.serial);
+}
+
+static void upisnd_proc_version_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct upisnd_instance *instance = entry->private_data;
+
+	snd_iprintf(buffer,
+		    "%u.%u.%u\n",
+		    instance->ctrl.version.major,
+		    instance->ctrl.version.minor,
+		    instance->ctrl.version.build);
+}
+
+static void upisnd_proc_hwrev_show(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct upisnd_instance *instance = entry->private_data;
+
+	snd_iprintf(buffer, "%u\n", instance->ctrl.version.hwrev);
+}
+
+static int upisnd_card_probe(struct snd_soc_card *card)
+{
+	struct upisnd_instance *instance = container_of(card, struct upisnd_instance, sound_card);
+	int err;
+
+	err = upisnd_midi_init(instance);
+
+	if (err < 0) {
+		printe("Failed to initialize MIDI subsystem! (%d)", err);
+		return err;
+	}
+
+	err = snd_card_ro_proc_new(card->snd_card, "serial", instance, upisnd_proc_serial_show);
+
+	if (err < 0) {
+		printe("Failed to create serial proc entry! (%d)", err);
+		return err;
+	}
+
+	err = snd_card_ro_proc_new(card->snd_card, "version", instance, upisnd_proc_version_show);
+
+	if (err < 0) {
+		printe("Failed to create version proc entry! (%d)", err);
+		return err;
+	}
+
+	err = snd_card_ro_proc_new(card->snd_card, "hwrev", instance, upisnd_proc_hwrev_show);
+
+	if (err < 0) {
+		printe("Failed to create hwrev proc entry! (%d)", err);
+		return err;
+	}
+
+	return 0;
+}
+
+static int upisnd_card_remove(struct snd_soc_card *card)
+{
+	struct upisnd_instance *instance = container_of(card, struct upisnd_instance, sound_card);
+
+	upisnd_midi_uninit(instance);
+	return 0;
+}
+
+static int upisnd_startup(struct snd_pcm_substream *substream)
+{
+	printd("startup");
+	return 0;
+}
+
+static int upisnd_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	printd("hw_params");
+
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	int ret = 0;
+
+	struct upisnd_instance *instance = container_of(rtd->card,
+							struct upisnd_instance,
+							sound_card);
+
+	if (!(instance->flags & UPISND_FLAG_DUMMY))
+		ret = snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
+
+	if (ret < 0) {
+		printe("Failed setting dai bclk ratio!\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int upisnd_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+	return snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, 0, 0); // Enable I2S mode.
+}
+
+static const struct snd_soc_ops upisnd_ops = {
+	.startup = upisnd_startup,
+	.hw_params = upisnd_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(adau1961,
+		     DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("adau1961", "adau-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static const struct snd_soc_dai_link upisnd_dai_link = {
+	.name = "pisound-micro",
+	.stream_name = "pisound-micro PCM",
+	.dai_fmt =
+		SND_SOC_DAIFMT_I2S |
+		SND_SOC_DAIFMT_NB_NF |
+		SND_SOC_DAIFMT_CBM_CFM,
+	.ops = &upisnd_ops,
+	.init = upisnd_dai_init,
+	SND_SOC_DAILINK_REG(adau1961),
+};
+
+static const struct snd_soc_card upisnd_sound_card = {
+	.name = "pisoundmicro",
+	.owner = THIS_MODULE,
+	.probe = upisnd_card_probe,
+	.remove = upisnd_card_remove,
+};
+
+int upisnd_sound_init(struct platform_device *pdev, struct upisnd_instance *instance)
+{
+	struct snd_soc_dai_link_component *comp = NULL;
+	struct device_node *i2s_node = NULL;
+
+	memcpy(&instance->sound_card, &upisnd_sound_card, sizeof(upisnd_sound_card));
+	memcpy(&instance->dai_link, &upisnd_dai_link, sizeof(upisnd_dai_link));
+
+	if (pdev->dev.of_node) {
+		of_property_read_string(pdev->dev.of_node, "card-name", &instance->sound_card.name);
+		i2s_node = of_parse_phandle(pdev->dev.of_node, "i2s-controller", 0);
+		if (!i2s_node)
+			printi("'i2s-controller' node not specified, will use dummy one instead!");
+	}
+
+	comp = devm_kzalloc(&pdev->dev, sizeof(*comp) * 3, GFP_KERNEL);
+	if (!comp)
+		return -ENOMEM;
+
+	char *long_name = devm_kzalloc(&pdev->dev, 26, GFP_KERNEL);
+
+	if (!long_name)
+		return -ENOMEM;
+
+	instance->dai_link.cpus = &comp[0];
+	instance->dai_link.codecs = &comp[1];
+	instance->dai_link.platforms = &comp[2];
+
+	instance->dai_link.num_cpus = 1;
+	instance->dai_link.num_codecs = 1;
+	instance->dai_link.num_platforms = 1;
+
+	if (i2s_node) {
+		instance->dai_link.cpus->of_node = i2s_node;
+		instance->dai_link.platforms->of_node = i2s_node;
+	} else {
+		printi("Setting up dummy interface.");
+		instance->dai_link.cpus->name = "snd-soc-dummy";
+		instance->dai_link.platforms->name = "snd-soc-dummy";
+		instance->dai_link.cpus->dai_name = "snd-soc-dummy-dai";
+		instance->dai_link.platforms->dai_name = "snd-soc-dummy-dai";
+		instance->dai_link.dai_fmt = (instance->dai_link.dai_fmt & ~SND_SOC_DAIFMT_CBM_CFM)
+					     | SND_SOC_DAIFMT_CBS_CFS;
+		instance->flags |= UPISND_FLAG_DUMMY;
+	}
+	instance->dai_link.codecs->of_node = instance->codec_dev->of_node;
+	instance->dai_link.codecs->dai_name = "adau-hifi";
+	instance->dai_link.stream_name = instance->ctrl.serial;
+
+	instance->sound_card.dev = &pdev->dev;
+
+	instance->sound_card.dai_link = &instance->dai_link;
+	instance->sound_card.num_links = 1;
+
+	snprintf(long_name, 26, "Pisound Micro %s", instance->ctrl.serial);
+	instance->sound_card.long_name = long_name;
+	printd("About to register card %s", instance->sound_card.long_name);
+
+	int err = snd_soc_register_card(&instance->sound_card);
+
+	if (i2s_node)
+		of_node_put(i2s_node);
+
+	if (err < 0) {
+		instance->sound_card.dev = NULL;
+		if (err != -EPROBE_DEFER)
+			printe("snd_soc_register_card failed with %d!", err);
+		return err;
+	}
+
+	struct snd_soc_dai *dai = snd_soc_card_get_codec_dai(&instance->sound_card, "adau-hifi");
+
+	if (!dai) {
+		printe("Failed to get codec dai!");
+		instance->sound_card.dev = NULL;
+		return -ENODEV;
+	}
+
+	if (adau1961_is_hp_capless(dai->component)) {
+		err = upisnd_comm_set_subscription(instance, UPISND_IRQ_VGND_SHORT_ALERT, true);
+		if (err < 0) {
+			instance->sound_card.dev = NULL;
+			printe("Failed to subscribe to VGND short alert IRQ! (%d)", err);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+static void upisnd_sound_handle_irq_event(struct upisnd_instance *instance,
+					  const struct irq_event_t *event)
+{
+	struct snd_soc_dai *dai = NULL;
+
+	switch (event->num) {
+	case UPISND_IRQ_VGND_SHORT_ALERT:
+		dai = snd_soc_card_get_codec_dai(&instance->sound_card, "adau-hifi");
+		printe("VGND short alert %s Headphone output!",
+		       event->high ? "ON, muting" : "OFF, restoring last state");
+		if (dai)
+			adau1961_set_vgnd_shorted(dai->component, event->high);
+		else
+			printe("Failed to get codec dai!");
+		break;
+	default:
+		break;
+	}
+}
+
+void upisnd_sound_handle_irq_events(struct upisnd_instance *instance,
+				    const struct irq_event_t *events,
+				    unsigned int n)
+{
+	unsigned int i;
+
+	printd("Handling %u sound IRQ events", n);
+
+	for (i = 0; i < n; ++i)
+		upisnd_sound_handle_irq_event(instance, &events[i]);
+}
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/drivers/upisnd/upisnd_sound.h b/sound/drivers/upisnd/upisnd_sound.h
new file mode 100644
index 000000000000..100f30733ba2
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_sound.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef UPISOUND_SOUND_H
+#define UPISOUND_SOUND_H
+
+int upisnd_sound_init(struct platform_device *pdev, struct upisnd_instance *instance);
+
+void upisnd_sound_handle_irq_events(struct upisnd_instance *instance,
+				    const struct irq_event_t *events,
+				    unsigned int n);
+
+#endif // UPISOUND_SOUND_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/drivers/upisnd/upisnd_sysfs.c b/sound/drivers/upisnd/upisnd_sysfs.c
new file mode 100644
index 000000000000..3feaa1289dba
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_sysfs.c
@@ -0,0 +1,1826 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "upisnd_common.h"
+
+enum {
+	// Max lengths are with '\0' included.
+	MAX_ELEMENT_NAME_LENGTH  = 64,
+	MAX_SETUP_REQUEST_LENGTH = 63 + MAX_ELEMENT_NAME_LENGTH,
+};
+
+static void upisnd_config_release(struct kobject *kobj);
+static void upisnd_element_release(struct kobject *kobj);
+
+static ssize_t upisnd_attr_show(struct kobject *kobj, struct attribute *attr, char *buf)
+{
+	printd("hi %s %s", kobject_name(kobj), attr->name);
+	struct kobj_attribute *attribute = container_of(attr, struct kobj_attribute, attr);
+
+	if (!attribute->show)
+		return -EIO;
+
+	int err = attribute->show(kobj, attribute, buf);
+
+	return err;
+}
+
+static ssize_t upisnd_attr_store(struct kobject *kobj,
+				 struct attribute *attr,
+				 const char *buf,
+				 size_t len)
+{
+	struct kobj_attribute *attribute = container_of(attr, struct kobj_attribute, attr);
+
+	if (!attribute->store)
+		return -EIO;
+
+	int err = attribute->store(kobj, attribute, buf, len);
+
+	return err;
+}
+
+static const struct sysfs_ops upisnd_attr_ops = {
+	.show = &upisnd_attr_show,
+	.store = &upisnd_attr_store
+};
+
+#define to_config(kobj_) container_of(kobj_, struct upisnd_config, kset.kobj)
+struct upisnd_config {
+	struct kset            kset;
+	struct upisnd_instance *instance;
+	struct kset            *elements;
+};
+
+static int upisnd_element_cleanup(struct kobject *kobj, struct upisnd_instance *instance);
+
+static void upisnd_config_release(struct kobject *kobj)
+{
+	printd("hi");
+	struct upisnd_config *cfg = to_config(kobj);
+
+	kfree(cfg);
+}
+
+static const struct kobj_type upisnd_config_type = {
+	.release = &upisnd_config_release,
+	.sysfs_ops = &upisnd_attr_ops
+};
+
+static const struct kobj_type upisnd_element_type = {
+	.release = &upisnd_element_release,
+	.sysfs_ops = &upisnd_attr_ops
+};
+
+// Used for handling encoder value overflow.
+enum upisnd_value_mode {
+	UPISND_VALUE_MODE_CLAMP = 0,
+	UPISND_VALUE_MODE_WRAP,
+};
+
+#define to_element(kobj_) container_of(kobj_, struct upisnd_element, kobj)
+struct upisnd_element {
+	struct kobject         kobj;
+	// Mapped from upisnd pins to gpio pin numbering.
+	// gpio_pins[1] is used only for encoder B pin.
+	// Negative number is used to indicate invalid/unused pin.
+	int                    gpio_pins[2];
+	int                    value;
+	int                    raw_value;
+	int                    input_min;
+	int                    input_max;
+	int                    value_low;
+	int                    value_high;
+	enum upisnd_value_mode value_mode;
+};
+
+// Returns true if the value changed.
+static bool upisnd_element_update_value(struct upisnd_element *el)
+{
+	int old_value = el->value;
+
+	int value = el->raw_value;
+
+	switch (el->value_mode) {
+	case UPISND_VALUE_MODE_CLAMP:
+		value = min(max(value, el->input_min), el->input_max);
+		break;
+	case UPISND_VALUE_MODE_WRAP:
+		if (el->input_max != el->input_min) {
+			while (value < el->input_min)
+				value += el->input_max - el->input_min;
+			value = (value - el->input_min) % (el->input_max - el->input_min)
+				+ el->input_min;
+		} else {
+			value = el->input_min;
+		}
+		break;
+	default:
+		return false;
+	}
+
+	el->raw_value = value;
+	el->value = upisnd_map_value_range(value, el->input_min, el->input_max, el->value_low,
+					   el->value_high);
+
+	return el->value != old_value;
+}
+
+static int upisnd_parse_gpio_dir(enum upisnd_pin_direction_e *dir, char **s, const char *sep)
+{
+	if (!dir || !s || !sep)
+		return -EINVAL;
+
+	char *t = strsep(s, sep);
+
+	if (!t || *t == '\0')
+		return -EINVAL;
+
+	if (strncasecmp(t, "input", 6u) == 0) {
+		*dir = UPISND_PIN_DIR_INPUT;
+		return 0;
+	} else if (strncasecmp(t, "output", 7u) == 0) {
+		*dir = UPISND_PIN_DIR_OUTPUT;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int upisnd_parse_gpio_input_pull(enum upisnd_pin_pull_e *pull, char **s, const char *sep)
+{
+	if (!pull || !s || !sep)
+		return -EINVAL;
+
+	char *t = strsep(s, sep);
+
+	if (!t || *t == '\0')
+		return -EINVAL;
+
+	if (strncasecmp(t, "pull_up", 8u) == 0) {
+		*pull = UPISND_PIN_PULL_UP;
+		return 0;
+	} else if (strncasecmp(t, "pull_down", 10u) == 0) {
+		*pull = UPISND_PIN_PULL_DOWN;
+		return 0;
+	} else if (strncasecmp(t, "pull_none", 10u) == 0) {
+		*pull = UPISND_PIN_PULL_NONE;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int upisnd_parse_gpio_output_level(bool *level, char **s, const char *sep)
+{
+	if (!level || !s || !sep)
+		return -EINVAL;
+
+	char *t = strsep(s, sep);
+
+	if (!t || *t == '\0')
+		return -EINVAL;
+
+	return kstrtobool(t, level);
+}
+
+static int upisnd_parse_pin(upisnd_pin_t *pin, char **s, const char *sep)
+{
+	if (!pin || !s || !sep)
+		return -EINVAL;
+
+	char *t = strsep(s, sep);
+
+	*pin = upisnd_name_to_pin(t);
+
+	return upisnd_is_pin_valid(*pin) ? 0 : -EINVAL;
+}
+
+static int upisnd_validate_setup(upisnd_setup_t setup)
+{
+	switch (upisnd_setup_get_element_type(setup)) {
+	case UPISND_ELEMENT_TYPE_ENCODER:
+	{
+		int a = upisnd_check_caps(upisnd_setup_get_pin_id(setup), UPISND_PIN_CAP_ENCODER);
+		int b = upisnd_check_caps(upisnd_setup_get_encoder_pin_b_id(setup),
+					  UPISND_PIN_CAP_ENCODER);
+
+		if (a < 0 || b < 0) {
+			printe("Invalid or incapable pins for Encoder (%d, %d)", a, b);
+			return -EINVAL;
+		}
+		return 0;
+	}
+	case UPISND_ELEMENT_TYPE_ANALOG_IN:
+	{
+		int err = upisnd_check_caps(upisnd_setup_get_pin_id(setup),
+					    UPISND_PIN_CAP_ANALOG_IN);
+
+		if (err < 0) {
+			printe("Invalid or incapable pin for Analog In (%d)", err);
+			return err;
+		}
+		return 0;
+	}
+	case UPISND_ELEMENT_TYPE_GPIO:
+	{
+		int err = upisnd_check_caps(upisnd_setup_get_pin_id(setup), UPISND_PIN_CAP_GPIO);
+
+		if (err < 0) {
+			printe("Invalid or incapable pin for GPIO (%d)", err);
+			return err;
+		}
+		return 0;
+	}
+	case UPISND_ELEMENT_TYPE_ACTIVITY:
+	{
+		int err;
+
+		switch (upisnd_setup_get_activity_type(setup)) {
+		case UPISND_ACTIVITY_TYPE_MIDI_IN:
+		case UPISND_ACTIVITY_TYPE_MIDI_OUT:
+			err = upisnd_check_caps(upisnd_setup_get_pin_id(setup),
+						UPISND_PIN_CAP_MIDI_ACTIVITY);
+			if (err < 0) {
+				printe("Invalid or incapable pin for MIDI Activity (%d)", err);
+				return err;
+			}
+			break;
+		default:
+			printe("Invalid activity type!");
+			err = -EINVAL;
+			break;
+		}
+
+		return err;
+	}
+	break;
+	case UPISND_ELEMENT_TYPE_NONE:
+	default:
+		return -EINVAL;
+	}
+}
+
+static int upisnd_parse_setup(upisnd_setup_t *setup, const char *buf, size_t len)
+{
+	if (!setup || !buf || len + 1 >= MAX_SETUP_REQUEST_LENGTH)
+		return -EINVAL;
+
+	*setup = 0;
+	upisnd_setup_set_element_type(setup, UPISND_ELEMENT_TYPE_NONE);
+
+	char b[MAX_SETUP_REQUEST_LENGTH];
+
+	strscpy(b, buf, len + 1);
+
+	static const char *const SEP = "\n\t ";
+
+	char *s = b;
+	char *token = strsep(&s, SEP);
+
+	if (!token || *token == '\0')
+		return -EINVAL;
+
+	if (strncasecmp(token, "encoder", 8u) == 0) {
+		upisnd_setup_set_element_type(setup, UPISND_ELEMENT_TYPE_ENCODER);
+	} else if (strncasecmp(token, "analog_in", 10u) == 0) {
+		upisnd_setup_set_element_type(setup, UPISND_ELEMENT_TYPE_ANALOG_IN);
+	} else if (strncasecmp(token, "activity_", 9u) == 0) {
+		if (strncasecmp(&token[9], "midi_in", 8u) == 0)
+			upisnd_setup_set_activity_type(setup, UPISND_ACTIVITY_TYPE_MIDI_IN);
+		else if (strncasecmp(&token[9], "midi_out", 9u) == 0)
+			upisnd_setup_set_activity_type(setup, UPISND_ACTIVITY_TYPE_MIDI_OUT);
+		else
+			return -EINVAL;
+		upisnd_setup_set_element_type(setup, UPISND_ELEMENT_TYPE_ACTIVITY);
+	} else if (strncasecmp(token, "gpio", 5u) == 0) {
+		upisnd_setup_set_element_type(setup, UPISND_ELEMENT_TYPE_GPIO);
+	} else {
+		return -EINVAL;
+	}
+
+	int err = 0;
+
+	upisnd_pin_t pin;
+	enum upisnd_pin_direction_e dir;
+	enum upisnd_pin_pull_e pull;
+	bool output;
+
+	switch (upisnd_setup_get_element_type(*setup)) {
+	case UPISND_ELEMENT_TYPE_ANALOG_IN:
+	case UPISND_ELEMENT_TYPE_ACTIVITY:
+	case UPISND_ELEMENT_TYPE_GPIO:
+	case UPISND_ELEMENT_TYPE_ENCODER:
+		err = upisnd_parse_pin(&pin, &s, SEP);
+
+		if (err != 0)
+			break;
+
+		upisnd_setup_set_pin_id(setup, pin);
+
+		switch (upisnd_setup_get_element_type(*setup)) {
+		case UPISND_ELEMENT_TYPE_ENCODER:
+			err = upisnd_parse_gpio_input_pull(&pull, &s, SEP);
+			if (err != 0)
+				break;
+			upisnd_setup_set_gpio_pull(setup, pull);
+
+			err = upisnd_parse_pin(&pin, &s, SEP);
+			if (err != 0)
+				break;
+			upisnd_setup_set_encoder_pin_b_id(setup, pin);
+
+			err = upisnd_parse_gpio_input_pull(&pull, &s, SEP);
+			if (err == 0)
+				upisnd_setup_set_encoder_pin_b_pull(setup, pull);
+			break;
+		case UPISND_ELEMENT_TYPE_GPIO:
+			err = upisnd_parse_gpio_dir(&dir, &s, SEP);
+			if (err != 0)
+				break;
+
+			upisnd_setup_set_gpio_dir(setup, dir);
+
+			switch (dir) {
+			case UPISND_PIN_DIR_INPUT:
+				err = upisnd_parse_gpio_input_pull(&pull, &s, SEP);
+				if (err == 0)
+					upisnd_setup_set_gpio_pull(setup, pull);
+				break;
+			case UPISND_PIN_DIR_OUTPUT:
+				err = upisnd_parse_gpio_output_level(&output, &s, SEP);
+				if (err == 0)
+					upisnd_setup_set_gpio_output(setup, output);
+				break;
+			default:
+				return -EINVAL;
+			}
+			break;
+		default:
+			break;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (err >= 0)
+		err = upisnd_validate_setup(*setup);
+
+	return err;
+}
+
+static ssize_t upisnd_element_type_show(struct kobject *kobj, struct kobj_attribute *attr,
+					char *buf);
+static ssize_t upisnd_element_value_show(struct kobject *kobj, struct kobj_attribute *attr,
+					 char *buf);
+static ssize_t upisnd_element_value_store(struct kobject *kobj, struct kobj_attribute *attr,
+					  const char *buf, size_t len);
+static ssize_t upisnd_element_input_min_show(struct kobject *kobj, struct kobj_attribute *attr,
+					     char *buf);
+static ssize_t upisnd_element_input_min_store(struct kobject *kobj, struct kobj_attribute *attr,
+					      const char *buf, size_t len);
+static ssize_t upisnd_element_input_max_show(struct kobject *kobj, struct kobj_attribute *attr,
+					     char *buf);
+static ssize_t upisnd_element_input_max_store(struct kobject *kobj, struct kobj_attribute *attr,
+					      const char *buf, size_t len);
+static ssize_t upisnd_element_value_low_show(struct kobject *kobj, struct kobj_attribute *attr,
+					     char *buf);
+static ssize_t upisnd_element_value_low_store(struct kobject *kobj, struct kobj_attribute *attr,
+					      const char *buf, size_t len);
+static ssize_t upisnd_element_value_high_show(struct kobject *kobj, struct kobj_attribute *attr,
+					      char *buf);
+static ssize_t upisnd_element_value_high_store(struct kobject *kobj, struct kobj_attribute *attr,
+					       const char *buf, size_t len);
+static ssize_t upisnd_element_value_mode_show(struct kobject *kobj, struct kobj_attribute *attr,
+					      char *buf);
+static ssize_t upisnd_element_value_mode_store(struct kobject *kobj, struct kobj_attribute *attr,
+					       const char *buf, size_t len);
+static ssize_t upisnd_element_direction_show(struct kobject *kobj, struct kobj_attribute *attr,
+					     char *buf);
+static ssize_t upisnd_element_pin_show(struct kobject *kobj, struct kobj_attribute *attr,
+				       char *buf);
+static ssize_t upisnd_element_pin_name_show(struct kobject *kobj, struct kobj_attribute *attr,
+					    char *buf);
+static ssize_t upisnd_element_pin_pull_show(struct kobject *kobj, struct kobj_attribute *attr,
+					    char *buf);
+static ssize_t upisnd_element_pin_b_show(struct kobject *kobj, struct kobj_attribute *attr,
+					 char *buf);
+static ssize_t upisnd_element_pin_b_name_show(struct kobject *kobj, struct kobj_attribute *attr,
+					      char *buf);
+static ssize_t upisnd_element_pin_b_pull_show(struct kobject *kobj, struct kobj_attribute *attr,
+					      char *buf);
+static ssize_t upisnd_element_activity_type_show(struct kobject *kobj, struct kobj_attribute *attr,
+						 char *buf);
+static ssize_t upisnd_element_gpio_export(struct kobject *kobj, struct kobj_attribute *attr,
+					  const char *buf, size_t len);
+static ssize_t upisnd_element_gpio_unexport(struct kobject *kobj, struct kobj_attribute *attr,
+					    const char *buf, size_t len);
+
+static struct kobj_attribute upisnd_element_type_attr = __ATTR(type, 0444, upisnd_element_type_show,
+	NULL);
+static struct kobj_attribute upisnd_element_value_attr = __ATTR(value, 0664,
+	upisnd_element_value_show, upisnd_element_value_store);
+static struct kobj_attribute upisnd_element_input_min_attr = __ATTR(input_min, 0664,
+	upisnd_element_input_min_show, upisnd_element_input_min_store);
+static struct kobj_attribute upisnd_element_input_max_attr = __ATTR(input_max, 0664,
+	upisnd_element_input_max_show, upisnd_element_input_max_store);
+static struct kobj_attribute upisnd_element_value_low_attr = __ATTR(value_low, 0664,
+	upisnd_element_value_low_show, upisnd_element_value_low_store);
+static struct kobj_attribute upisnd_element_value_high_attr = __ATTR(value_high, 0664,
+	upisnd_element_value_high_show, upisnd_element_value_high_store);
+static struct kobj_attribute upisnd_element_value_mode_attr = __ATTR(value_mode, 0664,
+	upisnd_element_value_mode_show, upisnd_element_value_mode_store);
+static struct kobj_attribute upisnd_element_direction_attr = __ATTR(direction, 0444,
+	upisnd_element_direction_show, NULL);
+static struct kobj_attribute upisnd_element_pin_attr = __ATTR(pin, 0444, upisnd_element_pin_show,
+	NULL);
+static struct kobj_attribute upisnd_element_pin_name_attr = __ATTR(pin_name, 0444,
+	upisnd_element_pin_name_show, NULL);
+static struct kobj_attribute upisnd_element_pin_pull_attr = __ATTR(pin_pull, 0444,
+	upisnd_element_pin_pull_show, NULL);
+static struct kobj_attribute upisnd_element_pin_b_attr = __ATTR(pin_b, 0444,
+	upisnd_element_pin_b_show, NULL);
+static struct kobj_attribute upisnd_element_pin_b_name_attr = __ATTR(pin_b_name, 0444,
+	upisnd_element_pin_b_name_show, NULL);
+static struct kobj_attribute upisnd_element_pin_b_pull_attr = __ATTR(pin_b_pull, 0444,
+	upisnd_element_pin_b_pull_show, NULL);
+static struct kobj_attribute upisnd_element_activity_type_attr = __ATTR(activity_type, 0444,
+	upisnd_element_activity_type_show, NULL);
+static struct kobj_attribute upisnd_element_gpio_export_attr = __ATTR(gpio_export, 0220, NULL,
+	upisnd_element_gpio_export);
+static struct kobj_attribute upisnd_element_gpio_unexport_attr = __ATTR(gpio_unexport, 0220, NULL,
+	upisnd_element_gpio_unexport);
+
+static struct attribute *upisnd_element_attrs[] = {
+	&upisnd_element_type_attr.attr,
+	&upisnd_element_value_attr.attr,
+	&upisnd_element_input_min_attr.attr,
+	&upisnd_element_input_max_attr.attr,
+	&upisnd_element_value_low_attr.attr,
+	&upisnd_element_value_high_attr.attr,
+	&upisnd_element_value_mode_attr.attr,
+	&upisnd_element_direction_attr.attr,
+	&upisnd_element_pin_attr.attr,
+	&upisnd_element_pin_name_attr.attr,
+	&upisnd_element_pin_pull_attr.attr,
+	&upisnd_element_pin_b_attr.attr,
+	&upisnd_element_pin_b_name_attr.attr,
+	&upisnd_element_pin_b_pull_attr.attr,
+	&upisnd_element_activity_type_attr.attr,
+	&upisnd_element_gpio_export_attr.attr,
+	&upisnd_element_gpio_unexport_attr.attr,
+	NULL
+};
+
+static umode_t upisnd_element_attr_is_visible(struct kobject *kobj, struct attribute *attr, int idx)
+{
+	printd("Hi!");
+
+	struct upisnd_element *element = to_element(kobj);
+	struct upisnd_config *cfg = to_config(kobj->parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	enum upisnd_element_type_e t = upisnd_setup_get_element_type(instance->gpio.pin_configs
+		[element->gpio_pins[0]].setup);
+
+	switch (t) {
+	case UPISND_ELEMENT_TYPE_ENCODER:
+		if (attr == &upisnd_element_value_mode_attr.attr ||
+		    attr == &upisnd_element_pin_b_attr.attr ||
+		    attr == &upisnd_element_pin_b_name_attr.attr ||
+		    attr == &upisnd_element_pin_b_pull_attr.attr)
+			return attr->mode;
+		fallthrough;
+	case UPISND_ELEMENT_TYPE_ANALOG_IN:
+		if (attr == &upisnd_element_input_min_attr.attr ||
+		    attr == &upisnd_element_input_max_attr.attr ||
+			attr == &upisnd_element_value_low_attr.attr ||
+			attr == &upisnd_element_value_high_attr.attr)
+			return attr->mode;
+		break;
+	case UPISND_ELEMENT_TYPE_GPIO:
+		if (attr == &upisnd_element_gpio_export_attr.attr ||
+		    attr == &upisnd_element_gpio_unexport_attr.attr ||
+			attr == &upisnd_element_direction_attr.attr)
+			return attr->mode;
+		break;
+	default:
+		break;
+	}
+
+	switch (t) {
+	case UPISND_ELEMENT_TYPE_ANALOG_IN:
+	case UPISND_ELEMENT_TYPE_ENCODER:
+	case UPISND_ELEMENT_TYPE_GPIO:
+		if (attr == &upisnd_element_value_attr.attr) {
+			if (t == UPISND_ELEMENT_TYPE_GPIO) {
+				if (upisnd_setup_get_gpio_dir(instance->gpio.pin_configs
+				    [element->gpio_pins[0]].setup) == UPISND_PIN_DIR_INPUT)
+					return 0444;
+			}
+			return attr->mode;
+		} else if (attr == &upisnd_element_pin_pull_attr.attr) {
+			if (t == UPISND_ELEMENT_TYPE_GPIO) {
+				if (upisnd_setup_get_gpio_dir(instance->gpio.pin_configs
+				    [element->gpio_pins[0]].setup) == UPISND_PIN_DIR_INPUT)
+					return attr->mode;
+			} else if (t == UPISND_ELEMENT_TYPE_ENCODER) {
+				return attr->mode;
+			}
+		}
+		break;
+	case UPISND_ELEMENT_TYPE_ACTIVITY:
+		if (attr == &upisnd_element_activity_type_attr.attr)
+			return attr->mode;
+		break;
+	case UPISND_ELEMENT_TYPE_NONE:
+	default:
+		break;
+	}
+
+	if (attr == &upisnd_element_type_attr.attr ||
+	    attr == &upisnd_element_pin_attr.attr ||
+	    attr == &upisnd_element_pin_name_attr.attr)
+		return attr->mode;
+
+	return 0;
+}
+
+static struct attribute_group upisnd_element_group = {
+	.attrs = upisnd_element_attrs,
+	.is_visible = &upisnd_element_attr_is_visible
+};
+
+static struct upisnd_element *upisnd_create_element(struct upisnd_config *config, const char *name)
+{
+	struct upisnd_element *el = kzalloc(sizeof(*el), GFP_KERNEL);
+
+	if (!el) {
+		printe("Failed allocating upisnd_element!");
+		return NULL;
+	}
+
+	el->kobj.kset = config->elements;
+	el->gpio_pins[0] = UPISND_PIN_INVALID;
+	el->gpio_pins[1] = UPISND_PIN_INVALID;
+
+	int err = kobject_init_and_add(&el->kobj, &upisnd_element_type, NULL, "%s", name);
+
+	if (err < 0) {
+		printe("Failed initializing upisnd_element kobject! (err=%d, name='%s')",
+		       err, name);
+		goto cleanup;
+	}
+
+	return el;
+
+cleanup:
+	kobject_put(&el->kobj);
+	kfree(el);
+	return NULL;
+}
+
+static void upisnd_element_release(struct kobject *kobj)
+{
+	printd("hi");
+	struct upisnd_element *element = to_element(kobj);
+
+	kfree(element);
+}
+
+static int upisnd_setup_get_pins(int pins[2], upisnd_setup_t setup)
+{
+	pins[0] = UPISND_PIN_INVALID;
+	pins[1] = UPISND_PIN_INVALID;
+
+	switch (upisnd_setup_get_element_type(setup)) {
+	case UPISND_ELEMENT_TYPE_ENCODER:
+		pins[1] = upisnd_setup_get_encoder_pin_b_id(setup);
+		if (!upisnd_is_pin_valid(pins[1]))
+			pins[1] = UPISND_PIN_INVALID;
+		fallthrough;
+	case UPISND_ELEMENT_TYPE_ANALOG_IN:
+	case UPISND_ELEMENT_TYPE_GPIO:
+	case UPISND_ELEMENT_TYPE_NONE:
+	case UPISND_ELEMENT_TYPE_ACTIVITY:
+		pins[0] = upisnd_setup_get_pin_id(setup);
+		if (!upisnd_is_pin_valid(pins[0]))
+			pins[0] = UPISND_PIN_INVALID;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int upisnd_setup_do(struct upisnd_instance *instance, struct upisnd_element *element,
+			   upisnd_setup_t setup, int pins[2])
+{
+	int err;
+
+	element->raw_value = 0;
+	element->value = 0;
+	element->input_min = 0;
+	element->value_low = 0;
+	element->input_max = upisnd_setup_get_element_type(setup) !=
+			     UPISND_ELEMENT_TYPE_ENCODER ? 1023 : 23;
+	element->value_high = element->input_max;
+	element->value_mode = UPISND_VALUE_MODE_CLAMP;
+
+	int i;
+
+	for (i = 0; i < 2; ++i) {
+		if (pins[i] == UPISND_PIN_INVALID)
+			break;
+
+		int pin = pins[i];
+
+		printd("Check %d %08x %d %p %p", pin, instance->gpio.pin_configs[pin].setup,
+		       upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup),
+		       instance->gpio.pin_configs[pin].element, element);
+
+		if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) !=
+			UPISND_ELEMENT_TYPE_NONE && instance->gpio.pin_configs[pin].element !=
+			element) {
+			printe("Pin %s already used via %s%s!",
+			       upisnd_pin_name(pin),
+			       instance->gpio.pin_configs[pin].element ? "sysfs " : "gpio",
+			       instance->gpio.pin_configs[pin].element ?
+			       instance->gpio.pin_configs[pin].element->kobj.name : "");
+			return -EBUSY;
+		}
+	}
+
+	// Unsetup previous config if necessary.
+	for (i = 0; i < 2; ++i) {
+		if (element->gpio_pins[i] != UPISND_PIN_INVALID &&
+		    element->gpio_pins[i] != pins[i]) {
+			upisnd_setup_t s = instance->gpio.pin_configs[element->gpio_pins[i]].setup;
+
+			upisnd_setup_set_element_type(&s, UPISND_ELEMENT_TYPE_NONE);
+			upisnd_gpio_setup(instance, s);
+			instance->gpio.pin_configs[element->gpio_pins[i]].setup = s;
+			instance->gpio.pin_subscription_refcounts[element->gpio_pins[i]] = 0;
+		}
+	}
+
+	err = upisnd_gpio_setup(instance, setup);
+
+	if (err < 0)
+		return err;
+
+	instance->gpio.pin_configs[pins[0]].element = element;
+	instance->gpio.pin_configs[pins[0]].flags = 0;
+	instance->gpio.pin_configs[pins[0]].setup = setup;
+	instance->gpio.pin_configs[pins[0]].gpio_desc = NULL;
+
+	element->gpio_pins[0] = pins[0];
+	element->gpio_pins[1] = pins[1];
+
+	switch (upisnd_setup_get_element_type(setup)) {
+	case UPISND_ELEMENT_TYPE_ENCODER:
+		upisnd_gpio_set_subscription(instance, pins[0], true);
+		upisnd_gpio_set_subscription(instance, pins[1], true);
+		instance->gpio.pin_configs[pins[1]].element = element;
+		instance->gpio.pin_configs[pins[1]].flags = UPISND_PIN_FLAG_IS_ENCODER_B;
+		instance->gpio.pin_configs[pins[1]].setup = setup;
+		instance->gpio.pin_configs[pins[1]].gpio_desc = NULL;
+		break;
+	case UPISND_ELEMENT_TYPE_GPIO:
+		if (upisnd_setup_get_gpio_dir(setup) != UPISND_PIN_DIR_INPUT)
+			break;
+		fallthrough;
+	default:
+		upisnd_gpio_set_subscription(instance, pins[0], true);
+		break;
+	}
+
+	return 0;
+}
+
+static ssize_t upisnd_setup(struct kobject *kobj, struct kobj_attribute *attr, const char *buf,
+			    size_t length)
+{
+	if (length + 1 >= MAX_SETUP_REQUEST_LENGTH) {
+		printe("Request exceeds maximum length!");
+		return -EINVAL;
+	}
+
+	int err;
+
+	char b[MAX_SETUP_REQUEST_LENGTH];
+
+	strscpy(b, buf, length + 1);
+
+	char *s = b;
+	char *name = strsep(&s, " /\n\t");
+
+	if (!name || *name == '\0' || !s || *s == '\0')
+		return -EINVAL;
+
+	upisnd_setup_t setup;
+
+	err = upisnd_parse_setup(&setup, s, length - (s - name));
+	if (err < 0) {
+		printe("Failed parsing setup request! (%d)", err);
+		return err;
+	}
+
+	int pins[2];
+
+	upisnd_setup_get_pins(pins, setup);
+
+	struct upisnd_config *cfg = to_config(kobj);
+
+	struct upisnd_instance *instance = cfg->instance;
+
+	down_write(&instance->rw_gpio_config_sem);
+
+	struct kobject *existing = kset_find_obj(cfg->elements, name);
+
+	if (existing) {
+		bool setup_matches = false;
+		struct upisnd_element *el = to_element(existing);
+
+		if (upisnd_is_pin_valid(el->gpio_pins[0]))
+			setup_matches = instance->gpio.pin_configs[el->gpio_pins[0]].setup == setup;
+
+		kobject_put(existing);
+		up_write(&instance->rw_gpio_config_sem);
+
+		if (setup_matches) {
+			printd("%s already existed and requested setup matched, returning success.",
+			       name);
+			return length;
+		}
+
+		printe("%s is already setup! (%d)", name, -EEXIST);
+		return -EEXIST;
+	}
+
+	struct upisnd_element *element = upisnd_create_element(cfg, name);
+
+	if (!element) {
+		up_write(&instance->rw_gpio_config_sem);
+		return -ENOMEM;
+	}
+
+	err = upisnd_setup_do(instance, element, setup, pins);
+
+	if (err >= 0) {
+		err = sysfs_create_group(&element->kobj, &upisnd_element_group);
+		if (err < 0)
+			printe
+			  ("Failed creating pisound-micro element attributes! (err=%d, name ='%s')",
+			   err, name);
+	}
+
+	up_write(&instance->rw_gpio_config_sem);
+
+	if (err < 0) {
+		kobject_put(&element->kobj);
+		return err;
+	}
+
+	kobject_uevent(&element->kobj, KOBJ_ADD);
+	return length;
+}
+
+static ssize_t upisnd_unsetup(struct kobject *kobj, struct kobj_attribute *attr, const char *buf,
+			      size_t length)
+{
+	if (length + 1 >= MAX_ELEMENT_NAME_LENGTH) {
+		printe("Element name exceeds maximum length!");
+		return -EINVAL;
+	}
+
+	char b[MAX_ELEMENT_NAME_LENGTH];
+
+	strscpy(b, buf, length + 1);
+
+	char *s = b;
+	char *name = strsep(&s, " /\n\t");
+
+	if (!name || *name == '\0')
+		return -EINVAL;
+
+	struct upisnd_config *cfg = to_config(kobj);
+
+	down_write(&cfg->instance->rw_gpio_config_sem);
+
+	struct kobject *existing = kset_find_obj(cfg->elements, name);
+
+	if (!existing) {
+		printe("%s not found!", name);
+		up_write(&cfg->instance->rw_gpio_config_sem);
+		return -ENOENT;
+	}
+
+	upisnd_element_cleanup(existing, cfg->instance);
+
+	printd("Found %p", existing);
+	kobject_put(existing); // kset_find_obj increments the
+	kobject_put(existing); // refcount, so we have to put twice.
+
+	up_write(&cfg->instance->rw_gpio_config_sem);
+
+	return length;
+}
+
+static ssize_t upisnd_adc_gain_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+	struct upisnd_config *cfg = to_config(kobj);
+
+	s32 adc_gain;
+	int err = upisnd_comm_get_value(cfg->instance, UPISND_VALUE_ADC_GAIN, &adc_gain);
+
+	if (err < 0) {
+		printe("Failed getting ADC gain value! (%d)", err);
+		return err;
+	}
+
+	return sprintf(buf, "%d\n", adc_gain);
+}
+
+static ssize_t upisnd_adc_gain_store(struct kobject *kobj, struct kobj_attribute *attr,
+				     const char *buf, size_t length)
+{
+	struct upisnd_config *cfg = to_config(kobj);
+
+	int adc_gain;
+	int err = kstrtoint(buf, 10, &adc_gain);
+
+	if (err < 0) {
+		printe("Failed parsing ADC gain! (%d)", err);
+		return err;
+	}
+
+	err = upisnd_comm_set_value(cfg->instance, UPISND_VALUE_ADC_GAIN, adc_gain);
+	if (err < 0) {
+		printe("Failed setting ADC gain value! (%d)", err);
+		return err;
+	}
+
+	return length;
+}
+
+static ssize_t upisnd_adc_offset_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+	struct upisnd_config *cfg = to_config(kobj);
+
+	s32 adc_offset;
+	int err = upisnd_comm_get_value(cfg->instance, UPISND_VALUE_ADC_OFFSET, &adc_offset);
+
+	if (err < 0) {
+		printe("Failed getting ADC offset value! (%d)", err);
+		return err;
+	}
+
+	return sprintf(buf, "%d\n", adc_offset);
+}
+
+static ssize_t upisnd_adc_offset_store(struct kobject *kobj, struct kobj_attribute *attr,
+				       const char *buf, size_t length)
+{
+	struct upisnd_config *cfg = to_config(kobj);
+
+	int adc_offset;
+	int err = kstrtoint(buf, 10, &adc_offset);
+
+	if (err < 0) {
+		printe("Failed parsing ADC offset! (%d)", err);
+		return err;
+	}
+
+	err = upisnd_comm_set_value(cfg->instance, UPISND_VALUE_ADC_OFFSET, adc_offset);
+	if (err < 0) {
+		printe("Failed setting ADC offset value! (%d)", err);
+		return err;
+	}
+
+	return length;
+}
+
+static struct kobj_attribute upisnd_setup_attr = __ATTR(setup, 0220, NULL, upisnd_setup);
+static struct kobj_attribute upisnd_unsetup_attr = __ATTR(unsetup, 0220, NULL, upisnd_unsetup);
+static struct kobj_attribute upisnd_adc_gain_attr = __ATTR(adc_gain, 0664, upisnd_adc_gain_show,
+							   upisnd_adc_gain_store);
+static struct kobj_attribute upisnd_adc_offset_attr = __ATTR(adc_offset, 0664,
+							     upisnd_adc_offset_show,
+							     upisnd_adc_offset_store);
+
+static struct attribute *upisnd_root_attrs[] = {
+	&upisnd_setup_attr.attr,
+	&upisnd_unsetup_attr.attr,
+	&upisnd_adc_gain_attr.attr,
+	&upisnd_adc_offset_attr.attr,
+	NULL
+};
+
+static umode_t upisnd_root_is_visible(struct kobject *kobj, struct attribute *attr, int idx)
+{
+	struct upisnd_config *cfg = to_config(kobj);
+	struct upisnd_instance *instance = cfg->instance;
+
+	if (!(instance->flags & UPISND_FLAG_ADC_CALIBRATION)) {
+		if (attr == &upisnd_adc_gain_attr.attr || attr == &upisnd_adc_offset_attr.attr)
+			return 0;
+	}
+
+	return attr->mode;
+}
+
+static const struct attribute_group upisnd_root_group = {
+	.attrs = upisnd_root_attrs,
+	.is_visible = &upisnd_root_is_visible
+};
+
+static struct upisnd_config *upisnd_create_config(struct upisnd_instance *instance,
+						  const char *name)
+{
+	struct upisnd_config *cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
+
+	if (!cfg) {
+		printe("Failed allocating upisnd_config!");
+		return NULL;
+	}
+
+	printd("cfg=%p", cfg);
+
+	cfg->instance = instance;
+
+	int err = kobject_set_name(&cfg->kset.kobj, name);
+
+	if (err < 0) {
+		printe("Failed setting config kset name! (%d)", err);
+		goto cleanup;
+	}
+
+	cfg->kset.kobj.ktype = &upisnd_config_type;
+	err = kset_register(&cfg->kset);
+
+	if (err < 0) {
+		printe("Failed registering config kset! (%d)", err);
+		goto cleanup;
+	}
+
+	cfg->kset.kobj.kset = &cfg->kset;
+
+	cfg->elements = kset_create_and_add("elements", NULL, &cfg->kset.kobj);
+	if (!cfg->elements) {
+		printe("Failed creating elements kset!");
+		goto cleanup;
+	}
+
+	err = sysfs_create_group(&cfg->kset.kobj, &upisnd_root_group);
+	if (err < 0) {
+		printe("Failed creating pisound-micro attributes! (%d)", err);
+		goto cleanup;
+	}
+
+	err = kobject_uevent(&cfg->kset.kobj, KOBJ_ADD);
+	printd("kobject_uevent(&cfg->kobj, KOBJ_ADD) = %d", err);
+
+	return cfg;
+
+cleanup:
+	if (cfg->elements) {
+		kset_unregister(cfg->elements);
+		cfg->elements = NULL;
+	}
+
+	kset_unregister(&cfg->kset);
+	return NULL;
+}
+
+static ssize_t upisnd_element_gpio_export(struct kobject *kobj,
+					  struct kobj_attribute *attr,
+					  const char *buf,
+					  size_t len)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+	struct upisnd_config *cfg = to_config(kobj->parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	struct gpio_desc *desc = NULL;
+
+	int pin = element->gpio_pins[0];
+
+	if (pin == UPISND_PIN_INVALID) {
+		printe("Element %s is not set up yet!", kobj->name);
+		return -EINVAL;
+	}
+
+	down_write(&instance->rw_gpio_config_sem);
+	if (upisnd_setup_get_element_type(instance->gpio.pin_configs[pin].setup) !=
+	    UPISND_ELEMENT_TYPE_GPIO) {
+		printe("Element %s is not set up as a GPIO!", kobj->name);
+		up_write(&instance->rw_gpio_config_sem);
+		return -EINVAL;
+	}
+
+	instance->gpio.pin_configs[pin].flags |= UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS;
+	up_write(&instance->rw_gpio_config_sem);
+
+	int err = 0;
+
+	printd("Requesting own %d", pin);
+	desc = gpiochip_request_own_desc(&instance->gpio.gpio_chip, pin, kobj->name, 0, 0);
+	if (IS_ERR(desc)) {
+		printe("Failed requesting own GPIO desc for pin %d! (%ld)",
+		       upisnd_setup_get_pin_id(instance->gpio.pin_configs[pin].setup),
+		       PTR_ERR(desc));
+		err = PTR_ERR(desc);
+		desc = NULL;
+	}
+
+	if (err >= 0) {
+		err = gpiod_export(desc, true);
+
+		if (err < 0) {
+			printe("Failed exporting GPIO via sysfs for pin %d! (%d)",
+			       upisnd_setup_get_pin_id(instance->gpio.pin_configs[pin].setup), err);
+			gpiochip_free_own_desc(desc);
+			desc = NULL;
+		}
+	}
+
+	printd("Result: %p %d", desc, err);
+
+	down_write(&instance->rw_gpio_config_sem);
+	instance->gpio.pin_configs[pin].flags = 0;
+	instance->gpio.pin_configs[pin].gpio_desc = desc;
+	up_write(&instance->rw_gpio_config_sem);
+
+	return err >= 0 ? len : err;
+}
+
+static ssize_t upisnd_element_gpio_unexport(struct kobject *kobj, struct kobj_attribute *attr,
+					    const char *buf, size_t len)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+	struct upisnd_config *cfg = to_config(kobj->parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	int pin = element->gpio_pins[0];
+
+	if (pin == UPISND_PIN_INVALID) {
+		printe("Element %s is not set up yet!", kobj->name);
+		return -EINVAL;
+	}
+
+	down_write(&instance->rw_gpio_config_sem);
+	struct gpio_desc *desc = instance->gpio.pin_configs[pin].gpio_desc;
+
+	if (!desc) {
+		up_write(&instance->rw_gpio_config_sem);
+		printe("Element %s is not exported via gpio sysfs!", kobj->name);
+		return -EINVAL;
+	}
+
+	instance->gpio.pin_configs[pin].flags |= UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS;
+	up_write(&instance->rw_gpio_config_sem);
+
+	printd("Freeing own %d", pin);
+	gpiochip_free_own_desc(desc);
+
+	down_write(&instance->rw_gpio_config_sem);
+	instance->gpio.pin_configs[pin].flags &= ~UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS;
+	instance->gpio.pin_configs[pin].gpio_desc = NULL;
+	up_write(&instance->rw_gpio_config_sem);
+
+	return len;
+}
+
+static int upisnd_element_cleanup(struct kobject *kobj, struct upisnd_instance *instance)
+{
+	printd("(%p)", kobj);
+
+	struct upisnd_element *element = to_element(kobj);
+
+	if (element->gpio_pins[0] == UPISND_PIN_INVALID)
+		return 0;
+
+	upisnd_setup_t setup = instance->gpio.pin_configs[element->gpio_pins[0]].setup;
+
+	if (upisnd_setup_get_element_type(setup) == UPISND_ELEMENT_TYPE_NONE) {
+		printe("Pin %d is assigned, but the '%s' element is not set up",
+		       element->gpio_pins[0], kobj->name);
+		return 0;
+	}
+
+	if (instance->gpio.pin_configs[element->gpio_pins[0]].gpio_desc) {
+		printd("Freeing own desc");
+		struct gpio_desc *desc = instance->gpio.pin_configs[element->gpio_pins[0]]
+					 .gpio_desc;
+
+		instance->gpio.pin_configs[element->gpio_pins[0]].flags |=
+			UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS;
+		up_write(&instance->rw_gpio_config_sem);
+		gpiochip_free_own_desc(desc);
+		down_write(&instance->rw_gpio_config_sem);
+		instance->gpio.pin_configs[element->gpio_pins[0]].flags &=
+			~UPISND_PIN_FLAG_CONFIGURING_THROUGH_SYSFS;
+		printd("Freed");
+	}
+
+	upisnd_setup_set_element_type(&setup, UPISND_ELEMENT_TYPE_NONE);
+	int err = upisnd_gpio_setup(instance, setup);
+
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(element->gpio_pins); ++i) {
+		if (element->gpio_pins[i] == UPISND_PIN_INVALID)
+			break;
+
+		memset(&instance->gpio.pin_configs[element->gpio_pins[i]], 0,
+		       sizeof(struct upisnd_pin_config_t));
+		instance->gpio.pin_subscription_refcounts[element->gpio_pins[i]] = 0;
+
+		element->gpio_pins[i] = UPISND_PIN_INVALID;
+	}
+
+	return err;
+}
+
+static ssize_t upisnd_element_type_show(struct kobject *kobj, struct kobj_attribute *attr,
+					char *buf)
+{
+	const char *type = NULL;
+	struct upisnd_element *element = to_element(kobj);
+	struct upisnd_config *cfg = to_config(kobj->parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE;
+
+	if (element->gpio_pins[0] != UPISND_PIN_INVALID) {
+		t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]]
+						  .setup);
+	}
+
+	switch (t) {
+	case UPISND_ELEMENT_TYPE_ENCODER:
+		type = "encoder";
+		break;
+	case UPISND_ELEMENT_TYPE_ANALOG_IN:
+		type = "analog_in";
+		break;
+	case UPISND_ELEMENT_TYPE_GPIO:
+		type = "gpio";
+		break;
+	case UPISND_ELEMENT_TYPE_ACTIVITY:
+		type = "activity";
+		break;
+	case UPISND_ELEMENT_TYPE_NONE:
+		type = "none";
+		break;
+	default:
+		type = "unknown";
+		break;
+	}
+
+	return sprintf(buf, "%s\n", type);
+}
+
+static ssize_t upisnd_element_value_show(struct kobject *kobj,
+					 struct kobj_attribute *attr,
+					 char *buf)
+{
+	struct upisnd_element *element = to_element(kobj);
+	struct upisnd_config *cfg = to_config(kobj->parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE;
+	int pin = UPISND_PIN_INVALID;
+
+	if (element->gpio_pins[0] != UPISND_PIN_INVALID) {
+		t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]]
+						  .setup);
+		pin = element->gpio_pins[0];
+	}
+
+	switch (t) {
+	case UPISND_ELEMENT_TYPE_ANALOG_IN:
+	case UPISND_ELEMENT_TYPE_ENCODER:
+		return sprintf(buf, "%d\n", element->value);
+	case UPISND_ELEMENT_TYPE_GPIO:
+		return sprintf(buf, "%d\n", upisnd_gpio_get(instance, pin));
+	case UPISND_ELEMENT_TYPE_ACTIVITY:
+	case UPISND_ELEMENT_TYPE_NONE:
+	default:
+		return 0;
+	}
+}
+
+static ssize_t upisnd_element_value_store(struct kobject *kobj, struct kobj_attribute *attr,
+					  const char *buf, size_t len)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+	struct upisnd_config *cfg = to_config(kobj->parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	printd("%p %p", kobj, cfg);
+
+	enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE;
+	int pin = UPISND_PIN_INVALID;
+
+	if (element->gpio_pins[0] != UPISND_PIN_INVALID) {
+		t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]]
+						  .setup);
+		pin = element->gpio_pins[0];
+	}
+
+	int value, err;
+	bool b;
+
+	bool changed = false;
+
+	switch (t) {
+	case UPISND_ELEMENT_TYPE_NONE:
+		return -EINVAL;
+	case UPISND_ELEMENT_TYPE_GPIO:
+		err = kstrtobool(buf, &b);
+		if (err < 0) {
+			printe("Failed parsing the provided boolean! (%d)", err);
+			return err;
+		}
+		changed = b != upisnd_gpio_get(instance, pin);
+		upisnd_gpio_set(instance, pin, b);
+		break;
+	case UPISND_ELEMENT_TYPE_ENCODER:
+	case UPISND_ELEMENT_TYPE_ANALOG_IN:
+		err = kstrtoint(buf, 0, &value);
+		if (err < 0) {
+			printe("Failed parsing the provided integer! (%d)", err);
+			return err;
+		}
+		down_write(&instance->rw_gpio_config_sem);
+		element->raw_value = upisnd_unmap_value_range(value, element->input_min,
+							      element->input_max,
+							      element->value_low,
+							      element->value_high);
+		changed = upisnd_element_update_value(element);
+		up_write(&instance->rw_gpio_config_sem);
+		break;
+	case UPISND_ELEMENT_TYPE_ACTIVITY:
+		return -EINVAL;
+	}
+
+	if (changed)
+		sysfs_notify(kobj, NULL, "value");
+
+	return len;
+}
+
+static ssize_t upisnd_element_input_min_show(struct kobject *kobj, struct kobj_attribute *attr,
+					     char *buf)
+{
+	struct upisnd_element *element = to_element(kobj);
+
+	return sprintf(buf, "%d\n", element->input_min);
+}
+
+static ssize_t upisnd_element_store_value_int(struct upisnd_element *element, int *value,
+					      const char *buf, size_t len)
+{
+	int v;
+	int err = kstrtoint(buf, 0, &v);
+
+	if (err < 0) {
+		printe("Failed parsing the provided integer! (%d)", err);
+		return err;
+	}
+
+	struct upisnd_config *cfg = to_config(element->kobj.parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	down_write(&instance->rw_gpio_config_sem);
+	*value = v;
+	bool changed = upisnd_element_update_value(element);
+
+	up_write(&instance->rw_gpio_config_sem);
+
+	if (changed)
+		sysfs_notify(&element->kobj, NULL, "value");
+
+	return len;
+}
+
+static ssize_t upisnd_element_input_min_store(struct kobject *kobj, struct kobj_attribute *attr,
+					      const char *buf, size_t len)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+	int result = upisnd_element_store_value_int(element, &element->input_min, buf, len);
+
+	if (result >= 0) {
+		if (element->input_min > element->input_max) {
+			int t = element->input_min;
+
+			element->input_min = element->input_max;
+			element->input_max = t;
+		}
+	}
+	return result;
+}
+
+static ssize_t upisnd_element_input_max_show(struct kobject *kobj, struct kobj_attribute *attr,
+					     char *buf)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+
+	return sprintf(buf, "%d\n", element->input_max);
+}
+
+static ssize_t upisnd_element_input_max_store(struct kobject *kobj, struct kobj_attribute *attr,
+					      const char *buf, size_t len)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+	int result = upisnd_element_store_value_int(element, &element->input_max, buf, len);
+
+	if (result >= 0) {
+		if (element->input_min > element->input_max) {
+			int t = element->input_min;
+
+			element->input_min = element->input_max;
+			element->input_max = t;
+		}
+	}
+	return result;
+}
+
+static ssize_t upisnd_element_value_low_show(struct kobject *kobj, struct kobj_attribute *attr,
+					     char *buf)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+
+	return sprintf(buf, "%d\n", element->value_low);
+}
+
+static ssize_t upisnd_element_value_low_store(struct kobject *kobj, struct kobj_attribute *attr,
+					      const char *buf, size_t len)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+
+	return upisnd_element_store_value_int(element, &element->value_low, buf, len);
+}
+
+static ssize_t upisnd_element_value_high_show(struct kobject *kobj, struct kobj_attribute *attr,
+					      char *buf)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+
+	return sprintf(buf, "%d\n", element->value_high);
+}
+
+static ssize_t upisnd_element_value_high_store(struct kobject *kobj, struct kobj_attribute *attr,
+					       const char *buf, size_t len)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+
+	return upisnd_element_store_value_int(element, &element->value_high, buf, len);
+}
+
+static ssize_t upisnd_element_value_mode_show(struct kobject *kobj, struct kobj_attribute *attr,
+					      char *buf)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+
+	const char *s;
+
+	switch (element->value_mode) {
+	case UPISND_VALUE_MODE_CLAMP:
+		s = "clamp";
+		break;
+	case UPISND_VALUE_MODE_WRAP:
+		s = "wrap";
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return sprintf(buf, "%s\n", s);
+}
+
+static ssize_t upisnd_element_value_mode_store(struct kobject *kobj, struct kobj_attribute *attr,
+					       const char *buf, size_t len)
+{
+	if (!kobj || !kobj->parent || !kobj->parent->parent)
+		return -EINVAL;
+
+	static const char *const SEP = "\n\t ";
+
+	char b[8];
+
+	strscpy(b, buf, min(len + 1, sizeof(b)));
+
+	char *s = b;
+	char *token = strsep(&s, SEP);
+
+	if (!token || *token == '\0')
+		return -EINVAL;
+
+	enum upisnd_value_mode mode;
+
+	if (strncasecmp(token, "clamp", 6u) == 0)
+		mode = UPISND_VALUE_MODE_CLAMP;
+	else if (strncasecmp(token, "wrap", 5u) == 0)
+		mode = UPISND_VALUE_MODE_WRAP;
+	else
+		return -EINVAL;
+
+	struct upisnd_element *element = to_element(kobj);
+	struct upisnd_config *cfg = to_config(element->kobj.parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	down_write(&instance->rw_gpio_config_sem);
+	element->value_mode = mode;
+	bool changed = upisnd_element_update_value(element);
+
+	up_write(&instance->rw_gpio_config_sem);
+
+	if (changed)
+		sysfs_notify(&element->kobj, NULL, "value");
+
+	return len;
+}
+
+static ssize_t upisnd_element_direction_show(struct kobject *kobj, struct kobj_attribute *attr,
+					     char *buf)
+{
+	struct upisnd_element *element = to_element(kobj);
+	struct upisnd_config *cfg = to_config(kobj->parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE;
+	int pin = UPISND_PIN_INVALID;
+
+	if (element->gpio_pins[0] != UPISND_PIN_INVALID) {
+		t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]]
+						  .setup);
+		pin = element->gpio_pins[0];
+	}
+
+	switch (t) {
+	case UPISND_ELEMENT_TYPE_ENCODER:
+		return sprintf(buf, "in\n");
+	case UPISND_ELEMENT_TYPE_GPIO:
+		return sprintf(buf, upisnd_gpio_get_direction(instance, pin) == 0 ?
+			       "out\n" : "in\n");
+	case UPISND_ELEMENT_TYPE_ACTIVITY:
+		return sprintf(buf, "out\n");
+	case UPISND_ELEMENT_TYPE_NONE:
+	default:
+		return 0;
+	}
+}
+
+static ssize_t upisnd_element_pin_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+	struct upisnd_element *element = to_element(kobj);
+
+	if (element->gpio_pins[0] != UPISND_PIN_INVALID)
+		return sprintf(buf, "%d\n", element->gpio_pins[0]);
+
+	return -EINVAL;
+}
+
+static ssize_t upisnd_element_pin_name_show(struct kobject *kobj, struct kobj_attribute *attr,
+					    char *buf)
+{
+	struct upisnd_element *element = to_element(kobj);
+
+	if (element->gpio_pins[0] != UPISND_PIN_INVALID)
+		return sprintf(buf, "%s\n", upisnd_pin_name(element->gpio_pins[0]));
+
+	return -EINVAL;
+}
+
+static const char *upisnd_pin_pull_to_string(enum upisnd_pin_pull_e pull)
+{
+	switch (pull) {
+	case UPISND_PIN_PULL_NONE:
+		return "pull_none";
+	case UPISND_PIN_PULL_DOWN:
+		return "pull_down";
+	case UPISND_PIN_PULL_UP:
+		return "pull_up";
+	default:
+		return NULL;
+	}
+}
+
+static ssize_t upisnd_element_pin_pull_show(struct kobject *kobj, struct kobj_attribute *attr,
+					    char *buf)
+{
+	struct upisnd_element *element = to_element(kobj);
+
+	struct upisnd_config *cfg = to_config(kobj->parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE;
+
+	if (element->gpio_pins[0] != UPISND_PIN_INVALID)
+		t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]]
+						  .setup);
+
+	if (t == UPISND_ELEMENT_TYPE_GPIO) {
+		if (upisnd_setup_get_gpio_dir(instance->gpio.pin_configs[element->gpio_pins[0]]
+					      .setup) != UPISND_PIN_DIR_INPUT)
+			return -EINVAL;
+	} else if (t != UPISND_ELEMENT_TYPE_ENCODER) {
+		return -EINVAL;
+	}
+
+	enum upisnd_pin_pull_e pull = upisnd_setup_get_gpio_pull(
+		instance->gpio.pin_configs[element->gpio_pins[0]].setup);
+
+	switch (pull) {
+	case UPISND_PIN_PULL_NONE:
+	case UPISND_PIN_PULL_DOWN:
+	case UPISND_PIN_PULL_UP:
+		return sprintf(buf, "%s\n", upisnd_pin_pull_to_string(pull));
+	default:
+		return 0;
+	}
+}
+
+static ssize_t upisnd_element_pin_b_show(struct kobject *kobj, struct kobj_attribute *attr,
+					 char *buf)
+{
+	struct upisnd_element *element = to_element(kobj);
+
+	if (element->gpio_pins[1] != UPISND_PIN_INVALID)
+		return sprintf(buf, "%d\n", element->gpio_pins[1]);
+
+	return -EINVAL;
+}
+
+static ssize_t upisnd_element_pin_b_name_show(struct kobject *kobj, struct kobj_attribute *attr,
+					      char *buf)
+{
+	struct upisnd_element *element = to_element(kobj);
+
+	if (element->gpio_pins[1] != UPISND_PIN_INVALID)
+		return sprintf(buf, "%s\n", upisnd_pin_name(element->gpio_pins[1]));
+
+	return -EINVAL;
+}
+
+static ssize_t upisnd_element_pin_b_pull_show(struct kobject *kobj, struct kobj_attribute *attr,
+					      char *buf)
+{
+	struct upisnd_element *element = to_element(kobj);
+
+	struct upisnd_config *cfg = to_config(kobj->parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE;
+
+	if (element->gpio_pins[0] != UPISND_PIN_INVALID)
+		t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]]
+						  .setup);
+
+	if (t != UPISND_ELEMENT_TYPE_ENCODER)
+		return -EINVAL;
+
+	enum upisnd_pin_pull_e pull = upisnd_setup_get_encoder_pin_b_pull(
+		instance->gpio.pin_configs[element->gpio_pins[0]].setup);
+
+	switch (pull) {
+	case UPISND_PIN_PULL_NONE:
+	case UPISND_PIN_PULL_DOWN:
+	case UPISND_PIN_PULL_UP:
+		return sprintf(buf, "%s\n", upisnd_pin_pull_to_string(pull));
+	default:
+		return 0;
+	}
+}
+
+static ssize_t upisnd_element_activity_type_show(struct kobject *kobj, struct kobj_attribute *attr,
+						 char *buf)
+{
+	struct upisnd_element *element = to_element(kobj);
+
+	struct upisnd_config *cfg = to_config(kobj->parent->parent);
+	struct upisnd_instance *instance = cfg->instance;
+
+	enum upisnd_element_type_e t = UPISND_ELEMENT_TYPE_NONE;
+
+	if (element->gpio_pins[0] != UPISND_PIN_INVALID)
+		t = upisnd_setup_get_element_type(instance->gpio.pin_configs[element->gpio_pins[0]]
+						  .setup);
+
+	if (t != UPISND_ELEMENT_TYPE_ACTIVITY)
+		return -EINVAL;
+
+	switch (upisnd_setup_get_activity_type(instance->gpio.pin_configs[element->gpio_pins[0]]
+		.setup)) {
+	case UPISND_ACTIVITY_TYPE_MIDI_IN:
+		return sprintf(buf, "midi_in\n");
+	case UPISND_ACTIVITY_TYPE_MIDI_OUT:
+		return sprintf(buf, "midi_out\n");
+	default:
+		return 0;
+	}
+}
+
+static void upisnd_sysfs_ctrl_event_handler(struct work_struct *work);
+
+int upisnd_sysfs_init(struct upisnd_instance *instance, const char *name)
+{
+	instance->config = upisnd_create_config(instance, name ? name : "pisound-micro");
+	if (!instance->config)
+		return -ENOMEM;
+
+	INIT_KFIFO(instance->ctrl_event_fifo);
+	INIT_WORK(&instance->ctrl_event_handler, upisnd_sysfs_ctrl_event_handler);
+
+	return 0;
+}
+
+void upisnd_sysfs_uninit(struct upisnd_instance *instance)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(instance->gpio.pin_configs); ++i) {
+		if (instance->gpio.pin_configs[i].gpio_desc) {
+			gpiochip_free_own_desc(instance->gpio.pin_configs[i].gpio_desc);
+			instance->gpio.pin_configs[i].gpio_desc = NULL;
+		}
+	}
+
+	kfifo_free(&instance->ctrl_event_fifo);
+
+	if (instance->config) {
+		struct kobject *k, *t;
+
+		if (instance->config->elements) {
+			list_for_each_entry_safe(k, t, &instance->config->elements->list, entry) {
+				printd("Putting element %p", k);
+				kobject_put(k);
+			}
+
+			printd("Unregistering elements set");
+			kset_unregister(instance->config->elements);
+			instance->config->elements = NULL;
+		}
+
+		instance->config->kset.kobj.kset = NULL;
+		kset_unregister(&instance->config->kset);
+		instance->config = NULL;
+	}
+}
+
+static void upisnd_sysfs_ctrl_event_handler(struct work_struct *work)
+{
+	printd("ctrl event handler");
+	struct upisnd_instance *instance = container_of(work, struct upisnd_instance,
+							ctrl_event_handler);
+	struct kobject *objs[UPISND_NUM_GPIOS];
+
+	memset(objs, 0, sizeof(objs));
+
+	unsigned int i, j = 0;
+	int n;
+	struct control_event_t events[32];
+
+	down_write(&instance->rw_gpio_config_sem);
+	while ((n = kfifo_out(&instance->ctrl_event_fifo, events, 32)) > 0) {
+		printd("Got %d events", n);
+		for (i = 0; i < n; ++i) {
+			upisnd_pin_t pin = events[i].pin;
+
+			if (!upisnd_is_pin_valid(pin)) {
+				printe("Received invalid pin (%d), ignoring", pin);
+				continue;
+			}
+
+			struct upisnd_element *element = instance->gpio.pin_configs[pin].element;
+
+			if (element) {
+				switch (upisnd_setup_get_element_type(instance->gpio.pin_configs
+					[pin].setup)) {
+				case UPISND_ELEMENT_TYPE_ENCODER:
+					element->raw_value += (int16_t)events[i].raw_value;
+					printd("Encoder %s, old raw: %d, new raw: %d",
+					       element->kobj.name,
+					       element->raw_value - (int16_t)events[i].raw_value,
+					       element->raw_value);
+					break;
+				case UPISND_ELEMENT_TYPE_ANALOG_IN:
+					element->raw_value = (uint16_t)(events[i].raw_value
+							     & 0x03ff);
+					printd("Analog in %s, raw: %d", element->kobj.name,
+					       element->raw_value);
+					break;
+				case UPISND_ELEMENT_TYPE_GPIO:
+					if (upisnd_setup_get_gpio_dir(instance->gpio
+					    .pin_configs[pin].setup) == UPISND_PIN_DIR_INPUT) {
+						element->raw_value = (uint16_t)events[i].raw_value;
+						printd("Button %s, raw: %d", element->kobj.name,
+						       element->raw_value);
+						break;
+					}
+					break;
+				default:
+					printe
+					("Got control event for non gpio, enc or analog element %s!"
+					, element->kobj.name);
+					continue;
+				}
+
+				objs[pin] = &element->kobj;
+			}
+		}
+	}
+	for (i = 0; i < ARRAY_SIZE(objs); ++i) {
+		if (objs[i] && upisnd_element_update_value(to_element(objs[i])))
+			objs[j++] = kobject_get(objs[i]);
+	}
+	up_write(&instance->rw_gpio_config_sem);
+
+	for (i = 0; i < j; ++i) {
+		printd("Notify %d %p", i, objs[i]);
+		sysfs_notify(objs[i], NULL, "value");
+		kobject_put(objs[i]);
+	}
+
+	if (!kfifo_is_empty(&instance->ctrl_event_fifo) &&
+	    !work_pending(&instance->ctrl_event_handler))
+		queue_work(instance->work_queue, &instance->ctrl_event_handler);
+}
+
+void upisnd_sysfs_handle_irq_event(struct upisnd_instance *instance,
+				   const struct irq_event_t *events, unsigned int n)
+{
+	printd("Converting and pushing %u events", n);
+	struct control_event_t cev;
+	unsigned int i;
+
+	for (i = 0; i < n; ++i) {
+		cev.pin = events[i].num;
+		cev.raw_value = events[i].high ? 1 : 0;
+		kfifo_put(&instance->ctrl_event_fifo, cev);
+	}
+	if (!work_pending(&instance->ctrl_event_handler))
+		queue_work(instance->work_queue, &instance->ctrl_event_handler);
+}
+
+void upisnd_sysfs_handle_control_event(struct upisnd_instance *instance,
+				       const struct control_event_t *events, unsigned int n)
+{
+	printd("Pushing %u events", n);
+	kfifo_in(&instance->ctrl_event_fifo, events, n);
+	if (!work_pending(&instance->ctrl_event_handler))
+		queue_work(instance->work_queue, &instance->ctrl_event_handler);
+}
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/drivers/upisnd/upisnd_sysfs.h b/sound/drivers/upisnd/upisnd_sysfs.h
new file mode 100644
index 000000000000..72a5666c0dc6
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_sysfs.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef UPISOUND_SYSFS_H
+#define UPISOUND_SYSFS_H
+
+int upisnd_sysfs_init(struct upisnd_instance *instance, const char *name);
+void upisnd_sysfs_uninit(struct upisnd_instance *instance);
+
+void upisnd_sysfs_handle_irq_event(struct upisnd_instance *instance,
+				   const struct irq_event_t *events,
+				   unsigned int n);
+void upisnd_sysfs_handle_control_event(struct upisnd_instance *instance,
+				       const struct control_event_t *events,
+				       unsigned int n);
+
+#endif // UPISOUND_SYSFS_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/drivers/upisnd/upisnd_utils.c b/sound/drivers/upisnd/upisnd_utils.c
new file mode 100644
index 000000000000..bf3346512d74
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_utils.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "upisnd_common.h"
+
+int upisnd_map_value_range(int v, int input_min, int input_max, int output_min, int output_max)
+{
+	int x = (v - input_min) * (output_max - output_min);
+
+	return x / (input_max - input_min) + output_min;
+}
+
+int upisnd_unmap_value_range(int v, int input_min, int input_max, int output_min, int output_max)
+{
+	int x = (v - output_min) * (input_max - input_min);
+
+	return x / (output_max - output_min) + input_min;
+}
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/drivers/upisnd/upisnd_utils.h b/sound/drivers/upisnd/upisnd_utils.h
new file mode 100644
index 000000000000..e4b41f52fad0
--- /dev/null
+++ b/sound/drivers/upisnd/upisnd_utils.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Pisound Micro Linux kernel module.
+ * Copyright (C) 2017-2025  Vilniaus Blokas UAB, https://blokas.io/
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef UPISOUND_UTILS_H
+#define UPISOUND_UTILS_H
+
+int upisnd_map_value_range(int v, int input_min, int input_max, int output_min, int output_max);
+int upisnd_unmap_value_range(int v, int input_min, int input_max, int output_min, int output_max);
+
+#endif // UPISOUND_UTILS_H
+
+/* vim: set ts=8 sw=8 noexpandtab: */
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index e87bd15a8b43..60a98ef7dcc5 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -106,6 +106,7 @@ source "sound/soc/meson/Kconfig"
 source "sound/soc/mxs/Kconfig"
 source "sound/soc/pxa/Kconfig"
 source "sound/soc/qcom/Kconfig"
+source "sound/soc/raspberrypi/Kconfig"
 source "sound/soc/rockchip/Kconfig"
 source "sound/soc/samsung/Kconfig"
 source "sound/soc/sh/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 775bb38c2ed4..94d7dfa17e25 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -59,6 +59,7 @@ obj-$(CONFIG_SND_SOC)	+= mxs/
 obj-$(CONFIG_SND_SOC)	+= kirkwood/
 obj-$(CONFIG_SND_SOC)	+= pxa/
 obj-$(CONFIG_SND_SOC)	+= qcom/
+obj-$(CONFIG_SND_SOC)	+= raspberrypi/
 obj-$(CONFIG_SND_SOC)	+= rockchip/
 obj-$(CONFIG_SND_SOC)	+= samsung/
 obj-$(CONFIG_SND_SOC)	+= sh/
diff --git a/sound/soc/bcm/Kconfig b/sound/soc/bcm/Kconfig
index 4218057b0874..0429dbeac3a7 100644
--- a/sound/soc/bcm/Kconfig
+++ b/sound/soc/bcm/Kconfig
@@ -26,3 +26,295 @@ config SND_BCM63XX_I2S_WHISTLER
 	  DSL/PON chips (bcm63158, bcm63178)
 
 	  If you don't know what to do here, say N
+
+config SND_BCM2708_SOC_CHIPDIP_DAC
+         tristate "Support for the ChipDip DAC"
+         help
+          Say Y or M if you want to add support for the ChipDip DAC soundcard
+
+config SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD
+	tristate "Support for Google voiceHAT soundcard"
+	select SND_SOC_VOICEHAT
+	select SND_RPI_SIMPLE_SOUNDCARD
+	help
+          Say Y or M if you want to add support for voiceHAT soundcard.
+
+config SND_BCM2708_SOC_HIFIBERRY_ADC
+        tristate "Support for HifiBerry ADC"
+        select SND_SOC_PCM186X_I2C
+        select SND_RPI_HIFIBERRY_ADC
+        help
+         Say Y or M if you want to add support for HifiBerry ADC.
+         Use this module for HiFiBerry's ADC-only sound cards
+
+config SND_BCM2708_SOC_HIFIBERRY_ADC8X
+        tristate "Support for HifiBerry ADC8X"
+        select SND_RPI_SIMPLE_SOUNDCARD
+        help
+         Say Y or M if you want to add support for HifiBerry ADC8X.
+         Note: ADC8X only works on PI5
+
+config SND_BCM2708_SOC_HIFIBERRY_DAC
+        tristate "Support for HifiBerry DAC and DAC8X"
+        select SND_SOC_PCM5102A
+        select SND_RPI_SIMPLE_SOUNDCARD
+        help
+         Say Y or M if you want to add support for HifiBerry DAC and DAC8X.
+         Note: DAC8X only works on PI5
+
+config SND_BCM2708_SOC_HIFIBERRY_DACPLUS
+        tristate "Support for HifiBerry DAC+"
+        select SND_SOC_PCM512x
+        select SND_SOC_TPA6130A2
+        select COMMON_CLK_HIFIBERRY_DACPRO
+        help
+         Say Y or M if you want to add support for HifiBerry DAC+.
+
+config SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD
+        tristate "Support for HifiBerry DAC+ HD"
+        select SND_SOC_PCM179X_I2C
+        select COMMON_CLK_HIFIBERRY_DACPLUSHD
+        help
+         Say Y or M if you want to add support for HifiBerry DAC+ HD.
+
+config SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC
+        tristate "Support for HifiBerry DAC+ADC"
+        select SND_SOC_PCM512x_I2C
+	select SND_SOC_DMIC
+        select COMMON_CLK_HIFIBERRY_DACPRO
+        help
+         Say Y or M if you want to add support for HifiBerry DAC+ADC.
+
+config SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO
+        tristate "Support for HifiBerry DAC+ADC PRO"
+        select SND_SOC_PCM512x_I2C
+        select SND_SOC_PCM186X_I2C
+        select SND_SOC_TPA6130A2
+        select COMMON_CLK_HIFIBERRY_DACPRO
+        help
+         Say Y or M if you want to add support for HifiBerry DAC+ADC PRO.
+
+config SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP
+        tristate "Support for HifiBerry DAC+DSP"
+	select SND_RPI_SIMPLE_SOUNDCARD
+        help
+         Say Y or M if you want to add support for HifiBerry DSP-DAC.
+
+config SND_BCM2708_SOC_HIFIBERRY_STUDIO_DAC8X
+        tristate "Support for HifiBerry Studio DAC8x soundcards"
+        help
+         Say Y or M if you want to add support for
+	 HifiBerry Studio DAC8x soundcards.
+	 Supports mulit channel DAC and ADC combnations.
+	 Only available for Pi 5
+
+config SND_BCM2708_SOC_HIFIBERRY_DIGI
+        tristate "Support for HifiBerry Digi"
+        select SND_SOC_WM8804
+        help
+         Say Y or M if you want to add support for HifiBerry Digi S/PDIF output board.
+
+config SND_BCM2708_SOC_HIFIBERRY_AMP
+        tristate "Support for the HifiBerry Amp"
+        select SND_SOC_TAS5713
+        select SND_RPI_SIMPLE_SOUNDCARD
+        help
+         Say Y or M if you want to add support for the HifiBerry Amp amplifier board.
+
+config SND_BCM2708_SOC_PIFI_40
+         tristate "Support for the PiFi-40 amp"
+         select SND_SOC_TAS571X
+         select SND_PIFI_40
+         help
+          Say Y or M if you want to add support for the PiFi40 amp board
+
+config SND_BCM2708_SOC_RPI_CIRRUS
+        tristate "Support for Cirrus Logic Audio Card"
+        select SND_SOC_WM5102
+        select SND_SOC_WM8804
+        help
+         Say Y or M if you want to add support for the Wolfson and
+         Cirrus Logic audio cards.
+
+config SND_BCM2708_SOC_RPI_DAC
+        tristate "Support for RPi-DAC"
+        select SND_SOC_PCM1794A
+        select SND_RPI_SIMPLE_SOUNDCARD
+        help
+         Say Y or M if you want to add support for RPi-DAC.
+
+config SND_BCM2708_SOC_RPI_PROTO
+	tristate "Support for Rpi-PROTO"
+	select SND_SOC_WM8731_I2C
+	help
+	  Say Y or M if you want to add support for Audio Codec Board PROTO (WM8731).
+
+config SND_BCM2708_SOC_JUSTBOOM_BOTH
+	tristate "Support for simultaneous JustBoom Digi and JustBoom DAC"
+	select SND_SOC_WM8804
+	select SND_SOC_PCM512x
+	help
+		Say Y or M if you want to add support for simultaneous
+		JustBoom Digi and JustBoom DAC.
+
+		This is not the right choice if you only have one but both of
+		these cards.
+
+config SND_BCM2708_SOC_JUSTBOOM_DAC
+	tristate "Support for JustBoom DAC"
+	select SND_SOC_PCM512x
+	help
+	  Say Y or M if you want to add support for JustBoom DAC.
+
+config SND_BCM2708_SOC_JUSTBOOM_DIGI
+	tristate "Support for JustBoom Digi"
+	select SND_SOC_WM8804
+        select SND_RPI_WM8804_SOUNDCARD
+	help
+	  Say Y or M if you want to add support for JustBoom Digi.
+
+config SND_BCM2708_SOC_IQAUDIO_CODEC
+	tristate "Support for IQaudIO-CODEC"
+	select SND_SOC_DA7213
+	help
+	  Say Y or M if you want to add support for IQaudIO-CODEC.
+
+config SND_BCM2708_SOC_IQAUDIO_DAC
+	tristate "Support for IQaudIO-DAC"
+	select SND_SOC_PCM512x_I2C
+	help
+	  Say Y or M if you want to add support for IQaudIO-DAC.
+
+config SND_BCM2708_SOC_IQAUDIO_DIGI
+	tristate "Support for IQAudIO Digi"
+	select SND_SOC_WM8804
+	select SND_RPI_WM8804_SOUNDCARD
+	help
+	  Say Y or M if you want to add support for IQAudIO Digital IO board.
+
+config SND_BCM2708_SOC_I_SABRE_Q2M
+        tristate "Support for Audiophonics I-Sabre Q2M DAC"
+        select SND_SOC_I_SABRE_CODEC
+        help
+        Say Y or M if you want to add support for Audiophonics I-SABRE Q2M DAC
+
+config SND_BCM2708_SOC_ADAU1977_ADC
+	tristate "Support for ADAU1977 ADC"
+	select SND_SOC_ADAU1977_I2C
+	select SND_RPI_SIMPLE_SOUNDCARD
+	help
+	  Say Y or M if you want to add support for ADAU1977 ADC.
+
+config SND_AUDIOINJECTOR_PI_SOUNDCARD
+	tristate "Support for audioinjector.net Pi add on soundcard"
+	select SND_SOC_WM8731_I2C
+	help
+	  Say Y or M if you want to add support for audioinjector.net Pi Hat
+
+config SND_AUDIOINJECTOR_OCTO_SOUNDCARD
+	tristate "Support for audioinjector.net Octo channel (Hat) soundcard"
+	select SND_SOC_CS42XX8_I2C
+	help
+	  Say Y or M if you want to add support for audioinjector.net octo add on
+
+config SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD
+	tristate "Support for audioinjector.net isolated DAC and ADC soundcard"
+	select SND_SOC_CS4271_I2C
+	help
+	  Say Y or M if you want to add support for audioinjector.net isolated soundcard
+
+config SND_AUDIOSENSE_PI
+	tristate "Support for AudioSense Add-On Soundcard"
+	select SND_SOC_TLV320AIC32X4_I2C
+	help
+	  Say Y or M if you want to add support for tlv320aic32x4 add-on
+
+config SND_DIGIDAC1_SOUNDCARD
+        tristate "Support for Red Rocks Audio DigiDAC1"
+        select SND_SOC_WM8804
+        select SND_SOC_WM8741
+        help
+         Say Y or M if you want to add support for Red Rocks Audio DigiDAC1 board.
+
+config SND_BCM2708_SOC_DIONAUDIO_LOCO
+	tristate "Support for Dion Audio LOCO DAC-AMP"
+	select SND_SOC_PCM5102a
+	help
+	  Say Y or M if you want to add support for Dion Audio LOCO.
+
+config SND_BCM2708_SOC_DIONAUDIO_LOCO_V2
+	tristate "Support for Dion Audio LOCO-V2 DAC-AMP"
+	select SND_SOC_PCM5122
+	help
+	  Say Y or M if you want to add support for Dion Audio LOCO-V2.
+
+config SND_BCM2708_SOC_ALLO_PIANO_DAC
+	tristate "Support for Allo Piano DAC"
+	select SND_SOC_PCM512x_I2C
+	help
+	  Say Y or M if you want to add support for Allo Piano DAC.
+
+config SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS
+	tristate "Support for Allo Piano DAC Plus"
+	select SND_SOC_PCM512x_I2C
+	help
+	  Say Y or M if you want to add support for Allo Piano DAC Plus.
+
+config SND_BCM2708_SOC_ALLO_BOSS_DAC
+	tristate "Support for Allo Boss DAC"
+	select SND_SOC_PCM512x_I2C
+	select COMMON_CLK_HIFIBERRY_DACPRO
+	help
+	  Say Y or M if you want to add support for Allo Boss DAC.
+
+config SND_BCM2708_SOC_ALLO_BOSS2_DAC
+	tristate "Support for Allo Boss2 DAC"
+	depends on I2C
+	select REGMAP_I2C
+	select SND_AUDIO_GRAPH_CARD
+	help
+	  Say Y or M if you want to add support for Allo Boss2 DAC.
+
+config SND_BCM2708_SOC_ALLO_DIGIONE
+	tristate "Support for Allo DigiOne"
+	select SND_SOC_WM8804
+	select SND_RPI_WM8804_SOUNDCARD
+	help
+	  Say Y or M if you want to add support for Allo DigiOne.
+
+config SND_BCM2708_SOC_ALLO_KATANA_DAC
+	tristate "Support for Allo Katana DAC"
+	depends on I2C
+	select REGMAP_I2C
+	select SND_AUDIO_GRAPH_CARD
+	help
+	  Say Y or M if you want to add support for Allo Katana DAC.
+
+config SND_BCM2708_SOC_FE_PI_AUDIO
+	tristate "Support for Fe-Pi-Audio"
+	select SND_SOC_SGTL5000
+	help
+	  Say Y or M if you want to add support for Fe-Pi-Audio.
+
+config SND_PISOUND
+	tristate "Support for Blokas Labs pisound"
+	select SND_RAWMIDI
+	help
+	  Say Y or M if you want to add support for Blokas Labs pisound.
+
+config SND_RPI_SIMPLE_SOUNDCARD
+	tristate "Support for Raspberry Pi simple soundcards"
+	help
+	  Say Y or M if you want to add support Raspbery Pi simple soundcards
+
+config SND_RPI_WM8804_SOUNDCARD
+	tristate "Support for Raspberry Pi generic WM8804 soundcards"
+	help
+	  Say Y or M if you want to add support for the Raspberry Pi
+          generic driver for WM8804 based soundcards.
+
+config SND_DACBERRY400
+	tristate "Support for DACBERRY400 Soundcard"
+	select SND_SOC_TLV320AIC3X_I2C
+	help
+	  Say Y or M if you want to add support for tlv320aic3x add-on
diff --git a/sound/soc/bcm/Makefile b/sound/soc/bcm/Makefile
index 0c1325a97b70..0ce245bf8a0a 100644
--- a/sound/soc/bcm/Makefile
+++ b/sound/soc/bcm/Makefile
@@ -12,4 +12,77 @@ obj-$(CONFIG_SND_SOC_CYGNUS) += snd-soc-cygnus.o
 # BCM63XX Platform Support
 snd-soc-63xx-y := bcm63xx-i2s-whistler.o bcm63xx-pcm-whistler.o
 
-obj-$(CONFIG_SND_BCM63XX_I2S_WHISTLER) += snd-soc-63xx.o
\ No newline at end of file
+obj-$(CONFIG_SND_BCM63XX_I2S_WHISTLER) += snd-soc-63xx.o
+
+# Google voiceHAT custom codec support
+snd-soc-googlevoicehat-codec-objs := googlevoicehat-codec.o
+
+# BCM2708 Machine Support
+snd-soc-hifiberry-adc-objs := hifiberry_adc.o
+snd-soc-hifiberry-dacplus-objs := hifiberry_dacplus.o
+snd-soc-hifiberry-dacplushd-objs := hifiberry_dacplushd.o
+snd-soc-hifiberry-dacplusadc-objs := hifiberry_dacplusadc.o
+snd-soc-hifiberry-dacplusadcpro-objs := hifiberry_dacplusadcpro.o
+snd-soc-hifiberry-dacplusdsp-objs := hifiberry_dacplusdsp.o
+snd-soc-hifiberry-studio-dac8x-objs := hifiberry_studio_dac8x.o
+snd-soc-justboom-both-objs := justboom-both.o
+snd-soc-justboom-dac-objs := justboom-dac.o
+snd-soc-rpi-cirrus-objs := rpi-cirrus.o
+snd-soc-rpi-proto-objs := rpi-proto.o
+snd-soc-iqaudio-codec-objs := iqaudio-codec.o
+snd-soc-iqaudio-dac-objs := iqaudio-dac.o
+ snd-soc-i-sabre-q2m-objs := i-sabre-q2m.o
+snd-soc-audioinjector-pi-soundcard-objs := audioinjector-pi-soundcard.o
+snd-soc-audioinjector-octo-soundcard-objs := audioinjector-octo-soundcard.o
+snd-soc-audioinjector-isolated-soundcard-objs := audioinjector-isolated-soundcard.o
+snd-soc-audiosense-pi-objs := audiosense-pi.o
+snd-soc-digidac1-soundcard-objs := digidac1-soundcard.o
+snd-soc-dionaudio-loco-objs := dionaudio_loco.o
+snd-soc-dionaudio-loco-v2-objs := dionaudio_loco-v2.o
+snd-soc-allo-boss-dac-objs := allo-boss-dac.o
+snd-soc-allo-boss2-dac-objs := allo-boss2-dac.o
+snd-soc-allo-piano-dac-objs := allo-piano-dac.o
+snd-soc-allo-piano-dac-plus-objs := allo-piano-dac-plus.o
+snd-soc-allo-katana-codec-objs := allo-katana-codec.o
+snd-soc-pisound-objs := pisound.o
+snd-soc-fe-pi-audio-objs := fe-pi-audio.o
+snd-soc-rpi-simple-soundcard-objs := rpi-simple-soundcard.o
+snd-soc-rpi-wm8804-soundcard-objs := rpi-wm8804-soundcard.o
+snd-soc-pifi-40-objs := pifi-40.o
+snd-soc-chipdip-dac-objs := chipdip-dac.o
+snd-soc-dacberry400-objs := dacberry400.o
+
+obj-$(CONFIG_SND_BCM2708_SOC_GOOGLEVOICEHAT_SOUNDCARD)  += snd-soc-googlevoicehat-codec.o
+obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_ADC) += snd-soc-hifiberry-adc.o
+obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUS) += snd-soc-hifiberry-dacplus.o
+obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSHD) += snd-soc-hifiberry-dacplushd.o
+obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADC) += snd-soc-hifiberry-dacplusadc.o
+obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSADCPRO) += snd-soc-hifiberry-dacplusadcpro.o
+obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_DACPLUSDSP) += snd-soc-hifiberry-dacplusdsp.o
+obj-$(CONFIG_SND_BCM2708_SOC_HIFIBERRY_STUDIO_DAC8X) += snd-soc-hifiberry-studio-dac8x.o
+obj-$(CONFIG_SND_BCM2708_SOC_JUSTBOOM_BOTH) += snd-soc-justboom-both.o
+obj-$(CONFIG_SND_BCM2708_SOC_JUSTBOOM_DAC) += snd-soc-justboom-dac.o
+obj-$(CONFIG_SND_BCM2708_SOC_RPI_CIRRUS) += snd-soc-rpi-cirrus.o
+obj-$(CONFIG_SND_BCM2708_SOC_RPI_PROTO) += snd-soc-rpi-proto.o
+obj-$(CONFIG_SND_BCM2708_SOC_IQAUDIO_CODEC) += snd-soc-iqaudio-codec.o
+obj-$(CONFIG_SND_BCM2708_SOC_IQAUDIO_DAC) += snd-soc-iqaudio-dac.o
+obj-$(CONFIG_SND_BCM2708_SOC_I_SABRE_Q2M) += snd-soc-i-sabre-q2m.o
+obj-$(CONFIG_SND_AUDIOINJECTOR_PI_SOUNDCARD) += snd-soc-audioinjector-pi-soundcard.o
+obj-$(CONFIG_SND_AUDIOINJECTOR_OCTO_SOUNDCARD) += snd-soc-audioinjector-octo-soundcard.o
+obj-$(CONFIG_SND_AUDIOINJECTOR_ISOLATED_SOUNDCARD) += snd-soc-audioinjector-isolated-soundcard.o
+obj-$(CONFIG_SND_AUDIOSENSE_PI) += snd-soc-audiosense-pi.o
+obj-$(CONFIG_SND_DIGIDAC1_SOUNDCARD) += snd-soc-digidac1-soundcard.o
+obj-$(CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO) += snd-soc-dionaudio-loco.o
+obj-$(CONFIG_SND_BCM2708_SOC_DIONAUDIO_LOCO_V2) += snd-soc-dionaudio-loco-v2.o
+obj-$(CONFIG_SND_BCM2708_SOC_ALLO_BOSS_DAC) += snd-soc-allo-boss-dac.o
+obj-$(CONFIG_SND_BCM2708_SOC_ALLO_BOSS2_DAC) += snd-soc-allo-boss2-dac.o
+obj-$(CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC) += snd-soc-allo-piano-dac.o
+obj-$(CONFIG_SND_BCM2708_SOC_ALLO_PIANO_DAC_PLUS) += snd-soc-allo-piano-dac-plus.o
+obj-$(CONFIG_SND_BCM2708_SOC_ALLO_KATANA_DAC) += snd-soc-allo-katana-codec.o
+obj-$(CONFIG_SND_PISOUND) += snd-soc-pisound.o
+obj-$(CONFIG_SND_BCM2708_SOC_FE_PI_AUDIO) += snd-soc-fe-pi-audio.o
+obj-$(CONFIG_SND_RPI_SIMPLE_SOUNDCARD) += snd-soc-rpi-simple-soundcard.o
+obj-$(CONFIG_SND_RPI_WM8804_SOUNDCARD) += snd-soc-rpi-wm8804-soundcard.o
+obj-$(CONFIG_SND_BCM2708_SOC_PIFI_40) += snd-soc-pifi-40.o
+obj-$(CONFIG_SND_BCM2708_SOC_CHIPDIP_DAC) += snd-soc-chipdip-dac.o
+obj-$(CONFIG_SND_DACBERRY400) += snd-soc-dacberry400.o
diff --git a/sound/soc/bcm/allo-boss-dac.c b/sound/soc/bcm/allo-boss-dac.c
new file mode 100644
index 000000000000..aa0723b98fab
--- /dev/null
+++ b/sound/soc/bcm/allo-boss-dac.c
@@ -0,0 +1,470 @@
+/*
+ * ALSA ASoC Machine Driver for Allo Boss DAC
+ *
+ * Author:	Baswaraj K <jaikumar@cem-solutions.net>
+ *		Copyright 2017
+ *		based on code by Daniel Matuschek,
+ *				 Stuart MacLean <stuart@hifiberry.com>
+ *		based on code by Florian Meier <florian.meier@koalo.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include "../codecs/pcm512x.h"
+
+#define ALLO_BOSS_NOCLOCK 0
+#define ALLO_BOSS_CLK44EN 1
+#define ALLO_BOSS_CLK48EN 2
+
+struct pcm512x_priv {
+	struct regmap *regmap;
+	struct clk *sclk;
+};
+
+static struct gpio_desc *mute_gpio;
+
+/* Clock rate of CLK44EN attached to GPIO6 pin */
+#define CLK_44EN_RATE 45158400UL
+/* Clock rate of CLK48EN attached to GPIO3 pin */
+#define CLK_48EN_RATE 49152000UL
+
+static bool slave;
+static bool snd_soc_allo_boss_master;
+static bool digital_gain_0db_limit = true;
+
+static void snd_allo_boss_select_clk(struct snd_soc_component *component,
+	int clk_id)
+{
+	switch (clk_id) {
+	case ALLO_BOSS_NOCLOCK:
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x00);
+		break;
+	case ALLO_BOSS_CLK44EN:
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x20);
+		break;
+	case ALLO_BOSS_CLK48EN:
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x04);
+		break;
+	}
+}
+
+static void snd_allo_boss_clk_gpio(struct snd_soc_component *component)
+{
+	snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x24, 0x24);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_3, 0x0f, 0x02);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_6, 0x0f, 0x02);
+}
+
+static bool snd_allo_boss_is_sclk(struct snd_soc_component *component)
+{
+	unsigned int sck;
+
+	sck = snd_soc_component_read(component, PCM512x_RATE_DET_4);
+	return (!(sck & 0x40));
+}
+
+static bool snd_allo_boss_is_sclk_sleep(
+	struct snd_soc_component *component)
+{
+	msleep(2);
+	return snd_allo_boss_is_sclk(component);
+}
+
+static bool snd_allo_boss_is_master_card(struct snd_soc_component *component)
+{
+	bool isClk44EN, isClk48En, isNoClk;
+
+	snd_allo_boss_clk_gpio(component);
+
+	snd_allo_boss_select_clk(component, ALLO_BOSS_CLK44EN);
+	isClk44EN = snd_allo_boss_is_sclk_sleep(component);
+
+	snd_allo_boss_select_clk(component, ALLO_BOSS_NOCLOCK);
+	isNoClk = snd_allo_boss_is_sclk_sleep(component);
+
+	snd_allo_boss_select_clk(component, ALLO_BOSS_CLK48EN);
+	isClk48En = snd_allo_boss_is_sclk_sleep(component);
+
+	return (isClk44EN && isClk48En && !isNoClk);
+}
+
+static int snd_allo_boss_clk_for_rate(int sample_rate)
+{
+	int type;
+
+	switch (sample_rate) {
+	case 11025:
+	case 22050:
+	case 44100:
+	case 88200:
+	case 176400:
+	case 352800:
+		type = ALLO_BOSS_CLK44EN;
+		break;
+	default:
+		type = ALLO_BOSS_CLK48EN;
+		break;
+	}
+	return type;
+}
+
+static void snd_allo_boss_set_sclk(struct snd_soc_component *component,
+	int sample_rate)
+{
+	struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
+
+	if (!IS_ERR(pcm512x->sclk)) {
+		int ctype;
+
+		ctype =	snd_allo_boss_clk_for_rate(sample_rate);
+		clk_set_rate(pcm512x->sclk, (ctype == ALLO_BOSS_CLK44EN)
+				? CLK_44EN_RATE : CLK_48EN_RATE);
+		snd_allo_boss_select_clk(component, ctype);
+	}
+}
+
+static int snd_allo_boss_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct pcm512x_priv *priv = snd_soc_component_get_drvdata(component);
+
+	if (slave)
+		snd_soc_allo_boss_master = false;
+	else
+		snd_soc_allo_boss_master =
+			snd_allo_boss_is_master_card(component);
+
+	if (snd_soc_allo_boss_master) {
+		struct snd_soc_dai_link *dai = rtd->dai_link;
+
+		dai->name = "BossDAC";
+		dai->stream_name = "Boss DAC HiFi [Master]";
+		dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+			| SND_SOC_DAIFMT_CBM_CFM;
+
+		snd_soc_component_update_bits(component, PCM512x_BCLK_LRCLK_CFG, 0x31, 0x11);
+		snd_soc_component_update_bits(component, PCM512x_MASTER_MODE, 0x03, 0x03);
+		snd_soc_component_update_bits(component, PCM512x_MASTER_CLKDIV_2, 0x7f, 63);
+		/*
+		* Default sclk to CLK_48EN_RATE, otherwise codec
+		*  pcm512x_dai_startup_master method could call
+		*  snd_pcm_hw_constraint_ratnums using CLK_44EN/64
+		*  which will mask 384k sample rate.
+		*/
+		if (!IS_ERR(priv->sclk))
+			clk_set_rate(priv->sclk, CLK_48EN_RATE);
+	} else {
+		priv->sclk = ERR_PTR(-ENOENT);
+	}
+
+	snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x08, 0x08);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
+
+	if (digital_gain_0db_limit) {
+		int ret;
+		struct snd_soc_card *card = rtd->card;
+
+		ret = snd_soc_limit_volume(card, "Digital Playback Volume",
+				207);
+		if (ret < 0)
+			dev_warn(card->dev, "Failed to set volume limit: %d\n",
+					ret);
+	}
+
+	return 0;
+}
+
+static int snd_allo_boss_update_rate_den(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
+	struct snd_ratnum *rats_no_pll;
+	unsigned int num = 0, den = 0;
+	int err;
+
+	rats_no_pll = devm_kzalloc(rtd->dev, sizeof(*rats_no_pll), GFP_KERNEL);
+	if (!rats_no_pll)
+		return -ENOMEM;
+
+	rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64;
+	rats_no_pll->den_min = 1;
+	rats_no_pll->den_max = 128;
+	rats_no_pll->den_step = 1;
+
+	err = snd_interval_ratnum(hw_param_interval(params,
+		SNDRV_PCM_HW_PARAM_RATE), 1, rats_no_pll, &num, &den);
+	if (err >= 0 && den) {
+		params->rate_num = num;
+		params->rate_den = den;
+	}
+
+	devm_kfree(rtd->dev, rats_no_pll);
+	return 0;
+}
+
+static void snd_allo_boss_gpio_mute(struct snd_soc_card *card)
+{
+	if (mute_gpio)
+		gpiod_set_value_cansleep(mute_gpio, 1);
+}
+
+static void snd_allo_boss_gpio_unmute(struct snd_soc_card *card)
+{
+	if (mute_gpio)
+		gpiod_set_value_cansleep(mute_gpio, 0);
+}
+
+static int snd_allo_boss_set_bias_level(struct snd_soc_card *card,
+	struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level)
+{
+	struct snd_soc_pcm_runtime *rtd;
+	struct snd_soc_dai *codec_dai;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+	codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+	if (dapm->dev != codec_dai->dev)
+		return 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_PREPARE:
+		if (dapm->bias_level != SND_SOC_BIAS_STANDBY)
+			break;
+		/* UNMUTE DAC */
+		snd_allo_boss_gpio_unmute(card);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (dapm->bias_level != SND_SOC_BIAS_PREPARE)
+			break;
+		/* MUTE DAC */
+		snd_allo_boss_gpio_mute(card);
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int snd_allo_boss_hw_params(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	int ret = 0;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	int channels = params_channels(params);
+	int width = snd_pcm_format_width(params_format(params));
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_card *card = rtd->card;
+
+	/* Using powers of 2 allows for an integer clock divisor */
+	width = width <= 16 ? 16 : 32;
+
+	/* Mute before changing sample rate */
+	snd_allo_boss_gpio_mute(card);
+
+	if (snd_soc_allo_boss_master) {
+		snd_allo_boss_set_sclk(component, params_rate(params));
+
+		ret = snd_allo_boss_update_rate_den(substream, params);
+		if (ret)
+			goto error;
+	}
+
+	ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width);
+
+	if (ret)
+		goto error;
+
+	ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_codec(rtd, 0), channels * width);
+
+	if (ret)
+		goto error;
+
+	/* Unmute after setting parameters or having an error */
+error:
+	snd_allo_boss_gpio_unmute(card);
+
+	return ret;
+}
+
+static int snd_allo_boss_startup(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_card *card = rtd->card;
+
+	snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
+	snd_allo_boss_gpio_mute(card);
+
+	if (snd_soc_allo_boss_master) {
+		struct pcm512x_priv *priv = snd_soc_component_get_drvdata(component);
+		/*
+		 * Default sclk to CLK_48EN_RATE, otherwise codec
+		 * pcm512x_dai_startup_master method could call
+		 * snd_pcm_hw_constraint_ratnums using CLK_44EN/64
+		 * which will mask 384k sample rate.
+		 */
+		if (!IS_ERR(priv->sclk))
+			clk_set_rate(priv->sclk, CLK_48EN_RATE);
+	}
+
+	return 0;
+}
+
+static void snd_allo_boss_shutdown(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+
+	snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x00);
+}
+
+static int snd_allo_boss_prepare(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_card *card = rtd->card;
+
+	snd_allo_boss_gpio_unmute(card);
+	return 0;
+}
+/* machine stream operations */
+static struct snd_soc_ops snd_allo_boss_ops = {
+	.hw_params = snd_allo_boss_hw_params,
+	.startup = snd_allo_boss_startup,
+	.shutdown = snd_allo_boss_shutdown,
+	.prepare = snd_allo_boss_prepare,
+};
+
+SND_SOC_DAILINK_DEFS(allo_boss,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_allo_boss_dai[] = {
+{
+	.name		= "Boss DAC",
+	.stream_name	= "Boss DAC HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S |
+			  SND_SOC_DAIFMT_NB_NF |
+			  SND_SOC_DAIFMT_CBS_CFS,
+	.ops		= &snd_allo_boss_ops,
+	.init		= snd_allo_boss_init,
+	SND_SOC_DAILINK_REG(allo_boss),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_allo_boss = {
+	.name         = "BossDAC",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_allo_boss_dai,
+	.num_links    = ARRAY_SIZE(snd_allo_boss_dai),
+};
+
+static int snd_allo_boss_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	snd_allo_boss.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai;
+
+		dai = &snd_allo_boss_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+					    "i2s-controller", 0);
+
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		}
+
+		digital_gain_0db_limit = !of_property_read_bool(
+			pdev->dev.of_node, "allo,24db_digital_gain");
+		slave = of_property_read_bool(pdev->dev.of_node,
+						"allo,slave");
+
+		mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute",
+							GPIOD_OUT_LOW);
+		if (IS_ERR(mute_gpio)) {
+			ret = PTR_ERR(mute_gpio);
+			dev_err(&pdev->dev,
+				"failed to get mute gpio: %d\n", ret);
+			return ret;
+		}
+
+		if (mute_gpio)
+			snd_allo_boss.set_bias_level =
+				snd_allo_boss_set_bias_level;
+
+		ret = snd_soc_register_card(&snd_allo_boss);
+		if (ret) {
+			dev_err(&pdev->dev,
+				"snd_soc_register_card() failed: %d\n", ret);
+			return ret;
+		}
+
+		if (mute_gpio)
+			snd_allo_boss_gpio_mute(&snd_allo_boss);
+
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static void snd_allo_boss_remove(struct platform_device *pdev)
+{
+	snd_allo_boss_gpio_mute(&snd_allo_boss);
+	snd_soc_unregister_card(&snd_allo_boss);
+}
+
+static const struct of_device_id snd_allo_boss_of_match[] = {
+	{ .compatible = "allo,boss-dac", },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, snd_allo_boss_of_match);
+
+static struct platform_driver snd_allo_boss_driver = {
+	.driver = {
+		.name   = "snd-allo-boss-dac",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_allo_boss_of_match,
+	},
+	.probe          = snd_allo_boss_probe,
+	.remove         = snd_allo_boss_remove,
+};
+
+module_platform_driver(snd_allo_boss_driver);
+
+MODULE_AUTHOR("Baswaraj K <jaikumar@cem-solutions.net>");
+MODULE_DESCRIPTION("ALSA ASoC Machine Driver for Allo Boss DAC");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/allo-boss2-dac.c b/sound/soc/bcm/allo-boss2-dac.c
new file mode 100644
index 000000000000..20dededd9196
--- /dev/null
+++ b/sound/soc/bcm/allo-boss2-dac.c
@@ -0,0 +1,1130 @@
+/*
+ * Driver for the ALLO KATANA CODEC
+ *
+ * Author: Jaikumar <sudeepkumar@cem-solutions.net>
+ *		Copyright 2018
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <linux/of_gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/pm_runtime.h>
+#include <linux/of_irq.h>
+#include <linux/completion.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <sound/jack.h>
+
+#include "../codecs/cs43130.h"
+
+#include <linux/clk.h>
+#include <linux/gcd.h>
+#define DEBUG
+
+#define CS43130_DSD_EN_MASK             0x10
+#define CS43130_PDN_DONE_INT_MASK        0x00
+
+static struct gpio_desc *snd_allo_clk44gpio;
+static struct gpio_desc *snd_allo_clk48gpio;
+
+struct  cs43130_priv {
+	struct snd_soc_component        *component;
+	struct regmap                   *regmap;
+	struct regulator_bulk_data      supplies[CS43130_NUM_SUPPLIES];
+	struct gpio_desc                *reset_gpio;
+	unsigned int                    dev_id; /* codec device ID */
+	int                             xtal_ibias;
+	/* shared by both DAIs */
+	struct mutex                    clk_mutex;
+	int                             clk_req;
+	bool                            pll_bypass;
+	struct completion               xtal_rdy;
+	struct completion               pll_rdy;
+	unsigned int                    mclk;
+	unsigned int                    mclk_int;
+	int                             mclk_int_src;
+
+	/* DAI specific */
+	struct cs43130_dai              dais[CS43130_DAI_ID_MAX];
+
+	/* HP load specific */
+	bool                            dc_meas;
+	bool                            ac_meas;
+	bool                            hpload_done;
+	struct completion               hpload_evt;
+	unsigned int                    hpload_stat;
+	u16                             hpload_dc[2];
+	u16                             dc_threshold[CS43130_DC_THRESHOLD];
+	u16                             ac_freq[CS43130_AC_FREQ];
+	u16                             hpload_ac[CS43130_AC_FREQ][2];
+	struct workqueue_struct         *wq;
+	struct work_struct              work;
+	struct snd_soc_jack             jack;
+};
+
+static const struct reg_default cs43130_reg_defaults[] = {
+	{CS43130_SYS_CLK_CTL_1, 0x06},
+	{CS43130_SP_SRATE, 0x01},
+	{CS43130_SP_BITSIZE, 0x05},
+	{CS43130_PAD_INT_CFG, 0x03},
+	{CS43130_PWDN_CTL, 0xFE},
+	{CS43130_CRYSTAL_SET, 0x04},
+	{CS43130_PLL_SET_1, 0x00},
+	{CS43130_PLL_SET_2, 0x00},
+	{CS43130_PLL_SET_3, 0x00},
+	{CS43130_PLL_SET_4, 0x00},
+	{CS43130_PLL_SET_5, 0x40},
+	{CS43130_PLL_SET_6, 0x10},
+	{CS43130_PLL_SET_7, 0x80},
+	{CS43130_PLL_SET_8, 0x03},
+	{CS43130_PLL_SET_9, 0x02},
+	{CS43130_PLL_SET_10, 0x02},
+	{CS43130_CLKOUT_CTL, 0x00},
+	{CS43130_ASP_NUM_1, 0x01},
+	{CS43130_ASP_NUM_2, 0x00},
+	{CS43130_ASP_DEN_1, 0x08},
+	{CS43130_ASP_DEN_2, 0x00},
+	{CS43130_ASP_LRCK_HI_TIME_1, 0x1F},
+	{CS43130_ASP_LRCK_HI_TIME_2, 0x00},
+	{CS43130_ASP_LRCK_PERIOD_1, 0x3F},
+	{CS43130_ASP_LRCK_PERIOD_2, 0x00},
+	{CS43130_ASP_CLOCK_CONF, 0x0C},
+	{CS43130_ASP_FRAME_CONF, 0x0A},
+	{CS43130_XSP_NUM_1, 0x01},
+	{CS43130_XSP_NUM_2, 0x00},
+	{CS43130_XSP_DEN_1, 0x02},
+	{CS43130_XSP_DEN_2, 0x00},
+	{CS43130_XSP_LRCK_HI_TIME_1, 0x1F},
+	{CS43130_XSP_LRCK_HI_TIME_2, 0x00},
+	{CS43130_XSP_LRCK_PERIOD_1, 0x3F},
+	{CS43130_XSP_LRCK_PERIOD_2, 0x00},
+	{CS43130_XSP_CLOCK_CONF, 0x0C},
+	{CS43130_XSP_FRAME_CONF, 0x0A},
+	{CS43130_ASP_CH_1_LOC, 0x00},
+	{CS43130_ASP_CH_2_LOC, 0x00},
+	{CS43130_ASP_CH_1_SZ_EN, 0x06},
+	{CS43130_ASP_CH_2_SZ_EN, 0x0E},
+	{CS43130_XSP_CH_1_LOC, 0x00},
+	{CS43130_XSP_CH_2_LOC, 0x00},
+	{CS43130_XSP_CH_1_SZ_EN, 0x06},
+	{CS43130_XSP_CH_2_SZ_EN, 0x0E},
+	{CS43130_DSD_VOL_B, 0x78},
+	{CS43130_DSD_VOL_A, 0x78},
+	{CS43130_DSD_PATH_CTL_1, 0xA8},
+	{CS43130_DSD_INT_CFG, 0x00},
+	{CS43130_DSD_PATH_CTL_2, 0x02},
+	{CS43130_DSD_PCM_MIX_CTL, 0x00},
+	{CS43130_DSD_PATH_CTL_3, 0x40},
+	{CS43130_HP_OUT_CTL_1, 0x30},
+	{CS43130_PCM_FILT_OPT, 0x02},
+	{CS43130_PCM_VOL_B, 0x78},
+	{CS43130_PCM_VOL_A, 0x78},
+	{CS43130_PCM_PATH_CTL_1, 0xA8},
+	{CS43130_PCM_PATH_CTL_2, 0x00},
+	{CS43130_CLASS_H_CTL, 0x1E},
+	{CS43130_HP_DETECT, 0x04},
+	{CS43130_HP_LOAD_1, 0x00},
+	{CS43130_HP_MEAS_LOAD_1, 0x00},
+	{CS43130_HP_MEAS_LOAD_2, 0x00},
+	{CS43130_INT_MASK_1, 0xFF},
+	{CS43130_INT_MASK_2, 0xFF},
+	{CS43130_INT_MASK_3, 0xFF},
+	{CS43130_INT_MASK_4, 0xFF},
+	{CS43130_INT_MASK_5, 0xFF},
+};
+static bool cs43130_volatile_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case CS43130_INT_STATUS_1 ... CS43130_INT_STATUS_5:
+	case CS43130_HP_DC_STAT_1 ... CS43130_HP_DC_STAT_2:
+	case CS43130_HP_AC_STAT_1 ... CS43130_HP_AC_STAT_2:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const char * const pcm_spd_texts[] = {
+	"Fast",
+	"Slow",
+};
+
+static SOC_ENUM_SINGLE_DECL(pcm_spd_enum, CS43130_PCM_FILT_OPT, 7,
+			pcm_spd_texts);
+
+static const SNDRV_CTL_TLVD_DECLARE_DB_MINMAX(master_tlv, -12750, 0);
+
+static const struct snd_kcontrol_new cs43130_controls[] = {
+	SOC_DOUBLE_R_TLV("Master Playback Volume", CS43130_PCM_VOL_B,
+			CS43130_PCM_VOL_A, 0, 255, 1, master_tlv),
+	SOC_DOUBLE("Master Playback Switch", CS43130_PCM_PATH_CTL_1,
+			0, 1, 1, 1),
+	SOC_DOUBLE_R_TLV("Digital Playback Volume", CS43130_DSD_VOL_B,
+			CS43130_DSD_VOL_A, 0, 255, 1, master_tlv),
+	SOC_DOUBLE("Digital Playback Switch", CS43130_DSD_PATH_CTL_1,
+			0, 1, 1, 1),
+	SOC_SINGLE("HV_Enable", CS43130_HP_OUT_CTL_1, 0, 1, 0),
+	SOC_ENUM("PCM Filter Speed", pcm_spd_enum),
+	SOC_SINGLE("PCM Phase Compensation", CS43130_PCM_FILT_OPT, 6, 1, 0),
+	SOC_SINGLE("PCM Nonoversample Emulate", CS43130_PCM_FILT_OPT, 5, 1, 0),
+	SOC_SINGLE("PCM High-pass Filter", CS43130_PCM_FILT_OPT, 1, 1, 0),
+	SOC_SINGLE("PCM De-emphasis Filter", CS43130_PCM_FILT_OPT, 0, 1, 0),
+};
+
+static bool cs43130_readable_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case CS43130_DEVID_AB ... CS43130_SYS_CLK_CTL_1:
+	case CS43130_SP_SRATE ... CS43130_PAD_INT_CFG:
+	case CS43130_PWDN_CTL:
+	case CS43130_CRYSTAL_SET:
+	case CS43130_PLL_SET_1 ... CS43130_PLL_SET_5:
+	case CS43130_PLL_SET_6:
+	case CS43130_PLL_SET_7:
+	case CS43130_PLL_SET_8:
+	case CS43130_PLL_SET_9:
+	case CS43130_PLL_SET_10:
+	case CS43130_CLKOUT_CTL:
+	case CS43130_ASP_NUM_1 ... CS43130_ASP_FRAME_CONF:
+	case CS43130_XSP_NUM_1 ... CS43130_XSP_FRAME_CONF:
+	case CS43130_ASP_CH_1_LOC:
+	case CS43130_ASP_CH_2_LOC:
+	case CS43130_ASP_CH_1_SZ_EN:
+	case CS43130_ASP_CH_2_SZ_EN:
+	case CS43130_XSP_CH_1_LOC:
+	case CS43130_XSP_CH_2_LOC:
+	case CS43130_XSP_CH_1_SZ_EN:
+	case CS43130_XSP_CH_2_SZ_EN:
+	case CS43130_DSD_VOL_B ... CS43130_DSD_PATH_CTL_3:
+	case CS43130_HP_OUT_CTL_1:
+	case CS43130_PCM_FILT_OPT ... CS43130_PCM_PATH_CTL_2:
+	case CS43130_CLASS_H_CTL:
+	case CS43130_HP_DETECT:
+	case CS43130_HP_STATUS:
+	case CS43130_HP_LOAD_1:
+	case CS43130_HP_MEAS_LOAD_1:
+	case CS43130_HP_MEAS_LOAD_2:
+	case CS43130_HP_DC_STAT_1:
+	case CS43130_HP_DC_STAT_2:
+	case CS43130_HP_AC_STAT_1:
+	case CS43130_HP_AC_STAT_2:
+	case CS43130_HP_LOAD_STAT:
+	case CS43130_INT_STATUS_1 ... CS43130_INT_STATUS_5:
+	case CS43130_INT_MASK_1 ... CS43130_INT_MASK_5:
+		return true;
+	default:
+		return false;
+	}
+}
+static bool cs43130_precious_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case CS43130_INT_STATUS_1 ... CS43130_INT_STATUS_5:
+		return true;
+	default:
+		return false;
+	}
+}
+static int cs43130_pcm_pdn(struct snd_soc_component *component)
+{
+	struct cs43130_priv *cs43130 =
+				snd_soc_component_get_drvdata(component);
+	int ret;
+	unsigned int reg, pdn_int;
+
+	regmap_write(cs43130->regmap, CS43130_DSD_PATH_CTL_2, 0x02);
+	regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1,
+			CS43130_PDN_DONE_INT_MASK, 0);
+	regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
+			CS43130_PDN_HP_MASK, 1 << CS43130_PDN_HP_SHIFT);
+	usleep_range(10, 50);
+	ret = regmap_read(cs43130->regmap, CS43130_INT_STATUS_1, &reg);
+	pdn_int = reg & 0xFE;
+	regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
+			CS43130_PDN_ASP_MASK, 1 << CS43130_PDN_ASP_SHIFT);
+	return 0;
+
+}
+static int cs43130_pwr_up_asp_dac(struct snd_soc_component *component)
+{
+	struct cs43130_priv *cs43130 =
+				snd_soc_component_get_drvdata(component);
+
+	regmap_update_bits(cs43130->regmap, CS43130_PAD_INT_CFG,
+			CS43130_ASP_3ST_MASK, 0);
+	regmap_write(cs43130->regmap, CS43130_DXD1, 0x99);
+	regmap_write(cs43130->regmap, CS43130_DXD13, 0x20);
+	regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
+			CS43130_PDN_ASP_MASK, 0);
+	regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
+			CS43130_PDN_HP_MASK, 0);
+	usleep_range(10000, 12000);
+	regmap_write(cs43130->regmap, CS43130_DXD1, 0x00);
+	regmap_write(cs43130->regmap, CS43130_DXD13, 0x00);
+	return 0;
+}
+static int cs43130_change_clksrc(struct snd_soc_component *component,
+				enum cs43130_mclk_src_sel src)
+{
+	int ret;
+	struct cs43130_priv *cs43130 =
+				snd_soc_component_get_drvdata(component);
+	int mclk_int_decoded;
+
+	if (src == cs43130->mclk_int_src) {
+		/* clk source has not changed */
+		return 0;
+	}
+	switch (cs43130->mclk_int) {
+	case CS43130_MCLK_22M:
+		mclk_int_decoded = CS43130_MCLK_22P5;
+		break;
+	case CS43130_MCLK_24M:
+		mclk_int_decoded = CS43130_MCLK_24P5;
+		break;
+	default:
+		dev_err(component->dev, "Invalid MCLK INT freq: %u\n",
+			cs43130->mclk_int);
+		return -EINVAL;
+	}
+
+	switch (src) {
+	case CS43130_MCLK_SRC_EXT:
+		cs43130->pll_bypass = true;
+		cs43130->mclk_int_src = CS43130_MCLK_SRC_EXT;
+		if (cs43130->xtal_ibias == CS43130_XTAL_UNUSED) {
+			regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
+					CS43130_PDN_XTAL_MASK,
+					1 << CS43130_PDN_XTAL_SHIFT);
+		} else {
+			reinit_completion(&cs43130->xtal_rdy);
+			regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1,
+					CS43130_XTAL_RDY_INT_MASK, 0);
+			regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
+					CS43130_PDN_XTAL_MASK, 0);
+			ret = wait_for_completion_timeout(&cs43130->xtal_rdy,
+					msecs_to_jiffies(100));
+			regmap_update_bits(cs43130->regmap, CS43130_INT_MASK_1,
+					CS43130_XTAL_RDY_INT_MASK,
+					1 << CS43130_XTAL_RDY_INT_SHIFT);
+			if (ret == 0) {
+				dev_err(component->dev, "Timeout waiting for XTAL_READY interrupt\n");
+				return -ETIMEDOUT;
+			}
+		}
+	regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1,
+				CS43130_MCLK_SRC_SEL_MASK,
+				src << CS43130_MCLK_SRC_SEL_SHIFT);
+	regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1,
+				CS43130_MCLK_INT_MASK,
+				mclk_int_decoded << CS43130_MCLK_INT_SHIFT);
+	usleep_range(150, 200);
+	regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
+				CS43130_PDN_PLL_MASK,
+				1 << CS43130_PDN_PLL_SHIFT);
+	break;
+	case CS43130_MCLK_SRC_RCO:
+		cs43130->mclk_int_src = CS43130_MCLK_SRC_RCO;
+
+		regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1,
+				CS43130_MCLK_SRC_SEL_MASK,
+				src << CS43130_MCLK_SRC_SEL_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_SYS_CLK_CTL_1,
+				CS43130_MCLK_INT_MASK,
+				CS43130_MCLK_22P5 << CS43130_MCLK_INT_SHIFT);
+		usleep_range(150, 200);
+		regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
+				CS43130_PDN_XTAL_MASK,
+				1 << CS43130_PDN_XTAL_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
+				CS43130_PDN_PLL_MASK,
+				1 << CS43130_PDN_PLL_SHIFT);
+	break;
+	default:
+		dev_err(component->dev, "Invalid MCLK source value\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+static const struct cs43130_bitwidth_map cs43130_bitwidth_table[] = {
+	{8,     CS43130_SP_BIT_SIZE_8,  CS43130_CH_BIT_SIZE_8},
+	{16,    CS43130_SP_BIT_SIZE_16, CS43130_CH_BIT_SIZE_16},
+	{24,    CS43130_SP_BIT_SIZE_24, CS43130_CH_BIT_SIZE_24},
+	{32,    CS43130_SP_BIT_SIZE_32, CS43130_CH_BIT_SIZE_32},
+};
+
+static const struct cs43130_bitwidth_map *cs43130_get_bitwidth_table(
+					unsigned int bitwidth)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(cs43130_bitwidth_table); i++) {
+		if (cs43130_bitwidth_table[i].bitwidth == bitwidth)
+			return &cs43130_bitwidth_table[i];
+	}
+
+	return NULL;
+}
+static int cs43130_set_bitwidth(int dai_id, unsigned int bitwidth_dai,
+				struct regmap *regmap)
+{
+	const struct cs43130_bitwidth_map *bw_map;
+
+	bw_map = cs43130_get_bitwidth_table(bitwidth_dai);
+	if (!bw_map)
+		return -EINVAL;
+
+	switch (dai_id) {
+	case CS43130_ASP_PCM_DAI:
+	case CS43130_ASP_DOP_DAI:
+		regmap_update_bits(regmap, CS43130_ASP_CH_1_SZ_EN,
+				CS43130_CH_BITSIZE_MASK, bw_map->ch_bit);
+		regmap_update_bits(regmap, CS43130_ASP_CH_2_SZ_EN,
+				CS43130_CH_BITSIZE_MASK, bw_map->ch_bit);
+		regmap_update_bits(regmap, CS43130_SP_BITSIZE,
+				CS43130_ASP_BITSIZE_MASK, bw_map->sp_bit);
+		break;
+	case CS43130_XSP_DOP_DAI:
+		regmap_update_bits(regmap, CS43130_XSP_CH_1_SZ_EN,
+				CS43130_CH_BITSIZE_MASK, bw_map->ch_bit);
+		regmap_update_bits(regmap, CS43130_XSP_CH_2_SZ_EN,
+				CS43130_CH_BITSIZE_MASK, bw_map->ch_bit);
+		regmap_update_bits(regmap, CS43130_SP_BITSIZE,
+				CS43130_XSP_BITSIZE_MASK, bw_map->sp_bit <<
+				CS43130_XSP_BITSIZE_SHIFT);
+	break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+static const struct cs43130_rate_map cs43130_rate_table[] = {
+	{32000,         CS43130_ASP_SPRATE_32K},
+	{44100,         CS43130_ASP_SPRATE_44_1K},
+	{48000,         CS43130_ASP_SPRATE_48K},
+	{88200,         CS43130_ASP_SPRATE_88_2K},
+	{96000,         CS43130_ASP_SPRATE_96K},
+	{176400,        CS43130_ASP_SPRATE_176_4K},
+	{192000,        CS43130_ASP_SPRATE_192K},
+	{352800,        CS43130_ASP_SPRATE_352_8K},
+	{384000,        CS43130_ASP_SPRATE_384K},
+};
+
+static const struct cs43130_rate_map *cs43130_get_rate_table(int fs)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(cs43130_rate_table); i++) {
+		if (cs43130_rate_table[i].fs == fs)
+			return &cs43130_rate_table[i];
+	}
+
+	return NULL;
+}
+
+static const struct cs43130_clk_gen *cs43130_get_clk_gen(int mclk_int, int fs,
+	const struct cs43130_clk_gen *clk_gen_table, int len_clk_gen_table)
+{
+	int i;
+
+	for (i = 0; i < len_clk_gen_table; i++) {
+		if (clk_gen_table[i].mclk_int == mclk_int &&
+					clk_gen_table[i].fs == fs)
+			return &clk_gen_table[i];
+	}
+	return NULL;
+}
+
+static int cs43130_set_sp_fmt(int dai_id, unsigned int bitwidth_sclk,
+				struct snd_pcm_hw_params *params,
+				struct cs43130_priv *cs43130)
+{
+	u16 frm_size;
+	u16 hi_size;
+	u8 frm_delay;
+	u8 frm_phase;
+	u8 frm_data;
+	u8 sclk_edge;
+	u8 lrck_edge;
+	u8 clk_data;
+	u8 loc_ch1;
+	u8 loc_ch2;
+	u8 dai_mode_val;
+	const struct cs43130_clk_gen *clk_gen;
+
+	switch (cs43130->dais[dai_id].dai_format) {
+	case SND_SOC_DAIFMT_I2S:
+		hi_size = bitwidth_sclk;
+		frm_delay = 2;
+		frm_phase = 0;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		hi_size = bitwidth_sclk;
+		frm_delay = 2;
+		frm_phase = 1;
+		break;
+	case SND_SOC_DAIFMT_DSP_A:
+		hi_size = 1;
+		frm_delay = 2;
+		frm_phase = 1;
+		break;
+	case SND_SOC_DAIFMT_DSP_B:
+		hi_size = 1;
+		frm_delay = 0;
+		frm_phase = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+	switch (cs43130->dais[dai_id].dai_mode) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		dai_mode_val = 0;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		dai_mode_val = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	frm_size = bitwidth_sclk * params_channels(params);
+	sclk_edge = 1;
+	lrck_edge = 0;
+	loc_ch1 = 0;
+	loc_ch2 = bitwidth_sclk * (params_channels(params) - 1);
+
+	frm_data = frm_delay & CS43130_SP_FSD_MASK;
+	frm_data |= (frm_phase << CS43130_SP_STP_SHIFT) & CS43130_SP_STP_MASK;
+
+	clk_data = lrck_edge & CS43130_SP_LCPOL_IN_MASK;
+	clk_data |= (lrck_edge << CS43130_SP_LCPOL_OUT_SHIFT) &
+			CS43130_SP_LCPOL_OUT_MASK;
+	clk_data |= (sclk_edge << CS43130_SP_SCPOL_IN_SHIFT) &
+			CS43130_SP_SCPOL_IN_MASK;
+	clk_data |= (sclk_edge << CS43130_SP_SCPOL_OUT_SHIFT) &
+			CS43130_SP_SCPOL_OUT_MASK;
+	clk_data |= (dai_mode_val << CS43130_SP_MODE_SHIFT) &
+			CS43130_SP_MODE_MASK;
+	switch (dai_id) {
+	case CS43130_ASP_PCM_DAI:
+	case CS43130_ASP_DOP_DAI:
+		regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_PERIOD_1,
+			CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >>
+			CS43130_SP_LCPR_LSB_DATA_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_PERIOD_2,
+			CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >>
+			CS43130_SP_LCPR_MSB_DATA_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_HI_TIME_1,
+			CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >>
+			CS43130_SP_LCHI_LSB_DATA_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_ASP_LRCK_HI_TIME_2,
+			CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >>
+			CS43130_SP_LCHI_MSB_DATA_SHIFT);
+		regmap_write(cs43130->regmap, CS43130_ASP_FRAME_CONF, frm_data);
+		regmap_write(cs43130->regmap, CS43130_ASP_CH_1_LOC, loc_ch1);
+		regmap_write(cs43130->regmap, CS43130_ASP_CH_2_LOC, loc_ch2);
+		regmap_update_bits(cs43130->regmap, CS43130_ASP_CH_1_SZ_EN,
+			CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_ASP_CH_2_SZ_EN,
+			CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT);
+		regmap_write(cs43130->regmap, CS43130_ASP_CLOCK_CONF, clk_data);
+		break;
+	case CS43130_XSP_DOP_DAI:
+		regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_PERIOD_1,
+			CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >>
+			CS43130_SP_LCPR_LSB_DATA_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_PERIOD_2,
+			CS43130_SP_LCPR_DATA_MASK, (frm_size - 1) >>
+			CS43130_SP_LCPR_MSB_DATA_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_HI_TIME_1,
+			CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >>
+			CS43130_SP_LCHI_LSB_DATA_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_XSP_LRCK_HI_TIME_2,
+			CS43130_SP_LCHI_DATA_MASK, (hi_size - 1) >>
+			CS43130_SP_LCHI_MSB_DATA_SHIFT);
+		regmap_write(cs43130->regmap, CS43130_XSP_FRAME_CONF, frm_data);
+		regmap_write(cs43130->regmap, CS43130_XSP_CH_1_LOC, loc_ch1);
+		regmap_write(cs43130->regmap, CS43130_XSP_CH_2_LOC, loc_ch2);
+		regmap_update_bits(cs43130->regmap, CS43130_XSP_CH_1_SZ_EN,
+			CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_XSP_CH_2_SZ_EN,
+			CS43130_CH_EN_MASK, 1 << CS43130_CH_EN_SHIFT);
+		regmap_write(cs43130->regmap, CS43130_XSP_CLOCK_CONF, clk_data);
+		break;
+	default:
+		return -EINVAL;
+	}
+	switch (frm_size) {
+	case 16:
+		clk_gen = cs43130_get_clk_gen(cs43130->mclk_int,
+						params_rate(params),
+						cs43130_16_clk_gen,
+						ARRAY_SIZE(cs43130_16_clk_gen));
+		break;
+	case 32:
+		clk_gen = cs43130_get_clk_gen(cs43130->mclk_int,
+						params_rate(params),
+						cs43130_32_clk_gen,
+						ARRAY_SIZE(cs43130_32_clk_gen));
+		break;
+	case 48:
+		clk_gen = cs43130_get_clk_gen(cs43130->mclk_int,
+						params_rate(params),
+						cs43130_48_clk_gen,
+						ARRAY_SIZE(cs43130_48_clk_gen));
+		break;
+	case 64:
+		clk_gen = cs43130_get_clk_gen(cs43130->mclk_int,
+						params_rate(params),
+						cs43130_64_clk_gen,
+						ARRAY_SIZE(cs43130_64_clk_gen));
+		break;
+	default:
+		return -EINVAL;
+	}
+	if (!clk_gen)
+		return -EINVAL;
+	switch (dai_id) {
+	case CS43130_ASP_PCM_DAI:
+	case CS43130_ASP_DOP_DAI:
+		regmap_write(cs43130->regmap, CS43130_ASP_DEN_1,
+				(clk_gen->v.denominator & CS43130_SP_M_LSB_DATA_MASK) >>
+				CS43130_SP_M_LSB_DATA_SHIFT);
+		regmap_write(cs43130->regmap, CS43130_ASP_DEN_2,
+				(clk_gen->v.denominator & CS43130_SP_M_MSB_DATA_MASK) >>
+				CS43130_SP_M_MSB_DATA_SHIFT);
+		regmap_write(cs43130->regmap, CS43130_ASP_NUM_1,
+				(clk_gen->v.numerator & CS43130_SP_N_LSB_DATA_MASK) >>
+				CS43130_SP_N_LSB_DATA_SHIFT);
+		regmap_write(cs43130->regmap, CS43130_ASP_NUM_2,
+				(clk_gen->v.numerator & CS43130_SP_N_MSB_DATA_MASK) >>
+				CS43130_SP_N_MSB_DATA_SHIFT);
+		break;
+	case CS43130_XSP_DOP_DAI:
+		regmap_write(cs43130->regmap, CS43130_XSP_DEN_1,
+				(clk_gen->v.denominator & CS43130_SP_M_LSB_DATA_MASK) >>
+				CS43130_SP_M_LSB_DATA_SHIFT);
+		regmap_write(cs43130->regmap, CS43130_XSP_DEN_2,
+				(clk_gen->v.denominator & CS43130_SP_M_MSB_DATA_MASK) >>
+				CS43130_SP_M_MSB_DATA_SHIFT);
+		regmap_write(cs43130->regmap, CS43130_XSP_NUM_1,
+				(clk_gen->v.numerator & CS43130_SP_N_LSB_DATA_MASK) >>
+				CS43130_SP_N_LSB_DATA_SHIFT);
+		regmap_write(cs43130->regmap, CS43130_XSP_NUM_2,
+				(clk_gen->v.numerator & CS43130_SP_N_MSB_DATA_MASK) >>
+				CS43130_SP_N_MSB_DATA_SHIFT);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int cs43130_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct cs43130_priv *cs43130 =
+				snd_soc_component_get_drvdata(component);
+	const struct cs43130_rate_map *rate_map;
+	unsigned int sclk = cs43130->dais[dai->id].sclk;
+	unsigned int bitwidth_sclk;
+	unsigned int bitwidth_dai = (unsigned int)(params_width(params));
+	unsigned int dop_rate = (unsigned int)(params_rate(params));
+	unsigned int required_clk, ret;
+	u8 dsd_speed;
+
+	cs43130->pll_bypass = true;
+	cs43130_pcm_pdn(component);
+	mutex_lock(&cs43130->clk_mutex);
+	if (!cs43130->clk_req) {
+		/* no DAI is currently using clk */
+		if (!(CS43130_MCLK_22M % params_rate(params))) {
+			required_clk = CS43130_MCLK_22M;
+			cs43130->mclk_int =  CS43130_MCLK_22M;
+			gpiod_set_value_cansleep(snd_allo_clk44gpio, 1);
+			gpiod_set_value_cansleep(snd_allo_clk48gpio, 0);
+			usleep_range(13500, 14000);
+		} else {
+			required_clk = CS43130_MCLK_24M;
+			cs43130->mclk_int =  CS43130_MCLK_24M;
+			gpiod_set_value_cansleep(snd_allo_clk48gpio, 1);
+			gpiod_set_value_cansleep(snd_allo_clk44gpio, 0);
+			usleep_range(13500, 14000);
+		}
+		if (cs43130->pll_bypass)
+			cs43130_change_clksrc(component, CS43130_MCLK_SRC_EXT);
+		else
+			cs43130_change_clksrc(component, CS43130_MCLK_SRC_PLL);
+	}
+
+	cs43130->clk_req++;
+	mutex_unlock(&cs43130->clk_mutex);
+
+	switch (dai->id) {
+	case CS43130_ASP_DOP_DAI:
+	case CS43130_XSP_DOP_DAI:
+		/* DoP bitwidth is always 24-bit */
+		bitwidth_dai = 24;
+		sclk = params_rate(params) * bitwidth_dai *
+				params_channels(params);
+
+		switch (params_rate(params)) {
+		case 176400:
+			dsd_speed = 0;
+			break;
+		case 352800:
+			dsd_speed = 1;
+			break;
+		default:
+			dev_err(component->dev, "Rate(%u) not supported\n",
+				params_rate(params));
+			return -EINVAL;
+		}
+
+		regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2,
+					CS43130_DSD_SPEED_MASK,
+					dsd_speed << CS43130_DSD_SPEED_SHIFT);
+		break;
+	case CS43130_ASP_PCM_DAI:
+		rate_map = cs43130_get_rate_table(params_rate(params));
+		if (!rate_map)
+			return -EINVAL;
+
+		regmap_write(cs43130->regmap, CS43130_SP_SRATE, rate_map->val);
+		if ((dop_rate == 176400) && (bitwidth_dai == 24)) {
+			dsd_speed = 0;
+			regmap_update_bits(cs43130->regmap,
+					CS43130_DSD_PATH_CTL_2,
+					CS43130_DSD_SPEED_MASK,
+					dsd_speed << CS43130_DSD_SPEED_SHIFT);
+			regmap_update_bits(cs43130->regmap,
+					CS43130_DSD_PATH_CTL_2,
+					CS43130_DSD_SRC_MASK,
+					CS43130_DSD_SRC_ASP <<
+					CS43130_DSD_SRC_SHIFT);
+			regmap_update_bits(cs43130->regmap,
+					CS43130_DSD_PATH_CTL_2,
+					CS43130_DSD_EN_MASK, 0x01 <<
+					CS43130_DSD_EN_SHIFT);
+		}
+		break;
+	default:
+		dev_err(component->dev, "Invalid DAI (%d)\n", dai->id);
+		return -EINVAL;
+	}
+
+	switch (dai->id) {
+	case CS43130_ASP_DOP_DAI:
+		regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2,
+				CS43130_DSD_SRC_MASK, CS43130_DSD_SRC_ASP <<
+				CS43130_DSD_SRC_SHIFT);
+		regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2,
+				CS43130_DSD_EN_MASK, 0x01 <<
+				CS43130_DSD_EN_SHIFT);
+		break;
+	case CS43130_XSP_DOP_DAI:
+		regmap_update_bits(cs43130->regmap, CS43130_DSD_PATH_CTL_2,
+				CS43130_DSD_SRC_MASK, CS43130_DSD_SRC_XSP <<
+				CS43130_DSD_SRC_SHIFT);
+		break;
+	}
+	if (!sclk && cs43130->dais[dai->id].dai_mode ==
+						SND_SOC_DAIFMT_CBM_CFM) {
+		/* Calculate SCLK in master mode if unassigned */
+		sclk = params_rate(params) * bitwidth_dai *
+				params_channels(params);
+	}
+	if (!sclk) {
+		/* at this point, SCLK must be set */
+		dev_err(component->dev, "SCLK freq is not set\n");
+		return -EINVAL;
+	}
+
+	bitwidth_sclk = (sclk / params_rate(params)) / params_channels(params);
+	if (bitwidth_sclk < bitwidth_dai) {
+		dev_err(component->dev, "Format not supported: SCLK freq is too low\n");
+		return -EINVAL;
+	}
+
+	dev_dbg(component->dev,
+		"sclk = %u, fs = %d, bitwidth_dai = %u\n",
+		sclk, params_rate(params), bitwidth_dai);
+
+	dev_dbg(component->dev,
+		"bitwidth_sclk = %u, num_ch = %u\n",
+		bitwidth_sclk, params_channels(params));
+
+	cs43130_set_bitwidth(dai->id, bitwidth_dai, cs43130->regmap);
+	cs43130_set_sp_fmt(dai->id, bitwidth_sclk, params, cs43130);
+	ret = cs43130_pwr_up_asp_dac(component);
+	return 0;
+}
+
+static int cs43130_hw_free(struct snd_pcm_substream *substream,
+					struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct cs43130_priv *cs43130 =
+				snd_soc_component_get_drvdata(component);
+
+	mutex_lock(&cs43130->clk_mutex);
+	cs43130->clk_req--;
+	if (!cs43130->clk_req) {
+		/* no DAI is currently using clk */
+		cs43130_change_clksrc(component, CS43130_MCLK_SRC_RCO);
+		cs43130_pcm_pdn(component);
+	}
+	mutex_unlock(&cs43130->clk_mutex);
+
+	return 0;
+}
+
+static const unsigned int cs43130_asp_src_rates[] = {
+	32000, 44100, 48000, 88200, 96000, 176400, 192000
+};
+
+static const struct snd_pcm_hw_constraint_list cs43130_asp_constraints = {
+	.count  = ARRAY_SIZE(cs43130_asp_src_rates),
+	.list   = cs43130_asp_src_rates,
+};
+
+static int cs43130_pcm_startup(struct snd_pcm_substream *substream,
+					struct snd_soc_dai *dai)
+{
+	return snd_pcm_hw_constraint_list(substream->runtime, 0,
+					SNDRV_PCM_HW_PARAM_RATE,
+					&cs43130_asp_constraints);
+}
+
+static int cs43130_pcm_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+	struct snd_soc_component *component = codec_dai->component;
+	struct cs43130_priv *cs43130 =
+				snd_soc_component_get_drvdata(component);
+
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		cs43130->dais[codec_dai->id].dai_mode = SND_SOC_DAIFMT_CBS_CFS;
+		break;
+	case SND_SOC_DAIFMT_CBM_CFM:
+		cs43130->dais[codec_dai->id].dai_mode = SND_SOC_DAIFMT_CBM_CFM;
+		break;
+	default:
+		dev_err(component->dev, "unsupported mode\n");
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		cs43130->dais[codec_dai->id].dai_format = SND_SOC_DAIFMT_I2S;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		cs43130->dais[codec_dai->id].dai_format = SND_SOC_DAIFMT_LEFT_J;
+		break;
+	default:
+		dev_err(component->dev,
+			"unsupported audio format\n");
+		return -EINVAL;
+	}
+
+	dev_dbg(component->dev, "dai_id = %d,  dai_mode = %u, dai_format = %u\n",
+			codec_dai->id,
+			cs43130->dais[codec_dai->id].dai_mode,
+			cs43130->dais[codec_dai->id].dai_format);
+
+	return 0;
+}
+
+static int cs43130_set_sysclk(struct snd_soc_dai *codec_dai,
+					int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_component *component = codec_dai->component;
+	struct cs43130_priv *cs43130 =
+				snd_soc_component_get_drvdata(component);
+
+	cs43130->dais[codec_dai->id].sclk = freq;
+	dev_dbg(component->dev, "dai_id = %d,  sclk = %u\n", codec_dai->id,
+				cs43130->dais[codec_dai->id].sclk);
+
+	return 0;
+}
+
+static int cs43130_component_set_sysclk(struct snd_soc_component *component,
+					int clk_id, int source,
+					unsigned int freq, int dir)
+{
+	struct cs43130_priv *cs43130 =
+				snd_soc_component_get_drvdata(component);
+
+	dev_dbg(component->dev, "clk_id = %d, source = %d, freq = %d, dir = %d\n",
+		clk_id, source, freq, dir);
+
+	switch (freq) {
+	case CS43130_MCLK_22M:
+	case CS43130_MCLK_24M:
+		cs43130->mclk = freq;
+		break;
+	default:
+		dev_err(component->dev, "Invalid MCLK INT freq: %u\n", freq);
+		return -EINVAL;
+	}
+
+	if (source == CS43130_MCLK_SRC_EXT) {
+		cs43130->pll_bypass = true;
+	} else {
+		dev_err(component->dev, "Invalid MCLK source\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+static u16 const cs43130_ac_freq[CS43130_AC_FREQ] = {
+	24,
+	43,
+	93,
+	200,
+	431,
+	928,
+	2000,
+	4309,
+	9283,
+	20000,
+};
+static const struct snd_soc_dai_ops cs43130_dai_ops = {
+	.startup        = cs43130_pcm_startup,
+	.hw_params	= cs43130_hw_params,
+	.hw_free        = cs43130_hw_free,
+	.set_sysclk     = cs43130_set_sysclk,
+	.set_fmt        = cs43130_pcm_set_fmt,
+};
+
+static struct snd_soc_dai_driver cs43130_codec_dai = {
+	.name = "allo-cs43130",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_CONTINUOUS,
+		.rate_min = 44100,
+		.rate_max = 192000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S24_LE |
+			SNDRV_PCM_FMTBIT_S32_LE
+
+	},
+	.ops = &cs43130_dai_ops,
+};
+
+static struct snd_soc_component_driver cs43130_component_driver = {
+	.idle_bias_on           = true,
+	.controls		= cs43130_controls,
+	.num_controls		= ARRAY_SIZE(cs43130_controls),
+	.set_sysclk             = cs43130_component_set_sysclk,
+	.idle_bias_on           = 1,
+	.use_pmdown_time        = 1,
+	.endianness             = 1,
+};
+
+static const struct regmap_config cs43130_regmap = {
+	.reg_bits               = 24,
+	.pad_bits               = 8,
+	.val_bits               = 8,
+
+	.max_register           = CS43130_LASTREG,
+	.reg_defaults           = cs43130_reg_defaults,
+	.num_reg_defaults       = ARRAY_SIZE(cs43130_reg_defaults),
+	.readable_reg           = cs43130_readable_register,
+	.precious_reg           = cs43130_precious_register,
+	.volatile_reg           = cs43130_volatile_register,
+	.cache_type             = REGCACHE_RBTREE,
+	/* needed for regcache_sync */
+	.use_single_read        = true,
+	.use_single_write       = true,
+};
+
+static u16 const cs43130_dc_threshold[CS43130_DC_THRESHOLD] = {
+	50,
+	120,
+};
+
+static int cs43130_handle_device_data(struct i2c_client *i2c_client,
+					struct cs43130_priv *cs43130)
+{
+	struct device_node *np = i2c_client->dev.of_node;
+	unsigned int val;
+	int i;
+
+	if (of_property_read_u32(np, "cirrus,xtal-ibias", &val) < 0) {
+	/* Crystal is unused. System clock is used for external MCLK */
+		cs43130->xtal_ibias = CS43130_XTAL_UNUSED;
+		return 0;
+	}
+
+	switch (val) {
+	case 1:
+		cs43130->xtal_ibias = CS43130_XTAL_IBIAS_7_5UA;
+		break;
+	case 2:
+		cs43130->xtal_ibias = CS43130_XTAL_IBIAS_12_5UA;
+		break;
+	case 3:
+		cs43130->xtal_ibias = CS43130_XTAL_IBIAS_15UA;
+		break;
+	default:
+		dev_err(&i2c_client->dev,
+			"Invalid cirrus,xtal-ibias value: %d\n", val);
+		return -EINVAL;
+	}
+
+	cs43130->dc_meas = of_property_read_bool(np, "cirrus,dc-measure");
+	cs43130->ac_meas = of_property_read_bool(np, "cirrus,ac-measure");
+
+	if (of_property_read_u16_array(np, "cirrus,ac-freq", cs43130->ac_freq,
+					CS43130_AC_FREQ) < 0) {
+		for (i = 0; i < CS43130_AC_FREQ; i++)
+			cs43130->ac_freq[i] = cs43130_ac_freq[i];
+	}
+
+	if (of_property_read_u16_array(np, "cirrus,dc-threshold",
+					cs43130->dc_threshold,
+					CS43130_DC_THRESHOLD) < 0) {
+		for (i = 0; i < CS43130_DC_THRESHOLD; i++)
+			cs43130->dc_threshold[i] = cs43130_dc_threshold[i];
+	}
+
+	return 0;
+}
+
+
+static int allo_cs43130_component_probe(struct i2c_client *i2c)
+{
+	struct regmap *regmap;
+	struct regmap_config config = cs43130_regmap;
+	struct device *dev = &i2c->dev;
+	struct cs43130_priv *cs43130;
+	unsigned int devid = 0;
+	unsigned int reg;
+	int ret;
+
+	regmap = devm_regmap_init_i2c(i2c, &config);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	cs43130 = devm_kzalloc(dev, sizeof(struct cs43130_priv),
+					GFP_KERNEL);
+	if (!cs43130)
+		return -ENOMEM;
+
+	dev_set_drvdata(dev, cs43130);
+	cs43130->regmap = regmap;
+
+	if (i2c->dev.of_node) {
+		ret = cs43130_handle_device_data(i2c, cs43130);
+		if (ret != 0)
+			return ret;
+	}
+	usleep_range(2000, 2050);
+
+	ret = regmap_read(cs43130->regmap, CS43130_DEVID_AB, &reg);
+	devid = (reg & 0xFF) << 12;
+	ret = regmap_read(cs43130->regmap, CS43130_DEVID_CD, &reg);
+	devid |= (reg & 0xFF) << 4;
+	ret = regmap_read(cs43130->regmap, CS43130_DEVID_E, &reg);
+	devid |= (reg & 0xF0) >> 4;
+	if (devid != CS43198_CHIP_ID) {
+		dev_err(dev, "Failed to read Chip or wrong Chip id: %d\n", ret);
+		return ret;
+	}
+
+	cs43130->mclk_int_src = CS43130_MCLK_SRC_RCO;
+	msleep(20);
+
+	ret = snd_soc_register_component(dev, &cs43130_component_driver,
+				    &cs43130_codec_dai, 1);
+	if (ret != 0) {
+		dev_err(dev, "failed to register codec: %d\n", ret);
+		return ret;
+	}
+	regmap_update_bits(cs43130->regmap, CS43130_PAD_INT_CFG,
+			CS43130_ASP_3ST_MASK, 0);
+	regmap_update_bits(cs43130->regmap, CS43130_PAD_INT_CFG,
+			CS43130_XSP_3ST_MASK, 1);
+	regmap_update_bits(cs43130->regmap, CS43130_PWDN_CTL,
+			CS43130_PDN_HP_MASK, 1 << CS43130_PDN_HP_SHIFT);
+	msleep(20);
+	regmap_write(cs43130->regmap, CS43130_CLASS_H_CTL, 0x06);
+	snd_allo_clk44gpio = devm_gpiod_get(dev, "clock44", GPIOD_OUT_HIGH);
+	if (IS_ERR(snd_allo_clk44gpio))
+		dev_err(dev, "devm_gpiod_get() failed\n");
+
+	snd_allo_clk48gpio = devm_gpiod_get(dev, "clock48", GPIOD_OUT_LOW);
+	if (IS_ERR(snd_allo_clk48gpio))
+		dev_err(dev, "devm_gpiod_get() failed\n");
+
+	return 0;
+}
+
+static void allo_cs43130_component_remove(struct i2c_client *i2c)
+{
+	snd_soc_unregister_component(&i2c->dev);
+}
+
+static const struct i2c_device_id allo_cs43130_component_id[] = {
+	{ "allo-cs43198", },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, allo_cs43130_component_id);
+
+static const struct of_device_id allo_cs43130_codec_of_match[] = {
+	{ .compatible = "allo,allo-cs43198", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, allo_cs43130_codec_of_match);
+
+static struct i2c_driver allo_cs43130_component_driver = {
+	.probe		= allo_cs43130_component_probe,
+	.remove		= allo_cs43130_component_remove,
+	.id_table	= allo_cs43130_component_id,
+	.driver		= {
+	.name		= "allo-cs43198",
+	.of_match_table = allo_cs43130_codec_of_match,
+	},
+};
+
+module_i2c_driver(allo_cs43130_component_driver);
+
+MODULE_DESCRIPTION("ASoC Allo Boss2 Codec Driver");
+MODULE_AUTHOR("Sudeepkumar <sudeepkumar@cem-solutions.net>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/allo-katana-codec.c b/sound/soc/bcm/allo-katana-codec.c
new file mode 100644
index 000000000000..266a89e4dc94
--- /dev/null
+++ b/sound/soc/bcm/allo-katana-codec.c
@@ -0,0 +1,386 @@
+/*
+ * Driver for the ALLO KATANA CODEC
+ *
+ * Author: Jaikumar <jaikumar@cem-solutions.net>
+ *		Copyright 2018
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/gcd.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include <linux/i2c.h>
+
+
+#define KATANA_CODEC_CHIP_ID		0x30
+#define KATANA_CODEC_VIRT_BASE		0x100
+#define KATANA_CODEC_PAGE		0
+
+#define KATANA_CODEC_CHIP_ID_REG	(KATANA_CODEC_VIRT_BASE + 0)
+#define KATANA_CODEC_RESET		(KATANA_CODEC_VIRT_BASE + 1)
+#define KATANA_CODEC_VOLUME_1		(KATANA_CODEC_VIRT_BASE + 2)
+#define KATANA_CODEC_VOLUME_2		(KATANA_CODEC_VIRT_BASE + 3)
+#define KATANA_CODEC_MUTE		(KATANA_CODEC_VIRT_BASE + 4)
+#define KATANA_CODEC_DSP_PROGRAM	(KATANA_CODEC_VIRT_BASE + 5)
+#define KATANA_CODEC_DEEMPHASIS		(KATANA_CODEC_VIRT_BASE + 6)
+#define KATANA_CODEC_DOP		(KATANA_CODEC_VIRT_BASE + 7)
+#define KATANA_CODEC_FORMAT		(KATANA_CODEC_VIRT_BASE + 8)
+#define KATANA_CODEC_COMMAND		(KATANA_CODEC_VIRT_BASE + 9)
+#define KATANA_CODEC_MUTE_STREAM	(KATANA_CODEC_VIRT_BASE + 10)
+
+#define KATANA_CODEC_MAX_REGISTER	(KATANA_CODEC_VIRT_BASE + 10)
+
+#define KATANA_CODEC_FMT		0xff
+#define KATANA_CODEC_CHAN_MONO		0x00
+#define KATANA_CODEC_CHAN_STEREO	0x80
+#define KATANA_CODEC_ALEN_16		0x10
+#define KATANA_CODEC_ALEN_24		0x20
+#define KATANA_CODEC_ALEN_32		0x30
+#define KATANA_CODEC_RATE_11025		0x01
+#define KATANA_CODEC_RATE_22050		0x02
+#define KATANA_CODEC_RATE_32000		0x03
+#define KATANA_CODEC_RATE_44100		0x04
+#define KATANA_CODEC_RATE_48000		0x05
+#define KATANA_CODEC_RATE_88200		0x06
+#define KATANA_CODEC_RATE_96000		0x07
+#define KATANA_CODEC_RATE_176400	0x08
+#define KATANA_CODEC_RATE_192000	0x09
+#define KATANA_CODEC_RATE_352800	0x0a
+#define KATANA_CODEC_RATE_384000	0x0b
+
+
+struct katana_codec_priv {
+	struct regmap *regmap;
+	int fmt;
+};
+
+static const struct reg_default katana_codec_reg_defaults[] = {
+	{ KATANA_CODEC_RESET,		0x00 },
+	{ KATANA_CODEC_VOLUME_1,	0xF0 },
+	{ KATANA_CODEC_VOLUME_2,	0xF0 },
+	{ KATANA_CODEC_MUTE,		0x00 },
+	{ KATANA_CODEC_DSP_PROGRAM,	0x04 },
+	{ KATANA_CODEC_DEEMPHASIS,	0x00 },
+	{ KATANA_CODEC_DOP,		0x00 },
+	{ KATANA_CODEC_FORMAT,		0xb4 },
+};
+
+static const char * const katana_codec_dsp_program_texts[] = {
+	"Linear Phase Fast Roll-off Filter",
+	"Linear Phase Slow Roll-off Filter",
+	"Minimum Phase Fast Roll-off Filter",
+	"Minimum Phase Slow Roll-off Filter",
+	"Apodizing Fast Roll-off Filter",
+	"Corrected Minimum Phase Fast Roll-off Filter",
+	"Brick Wall Filter",
+};
+
+static const unsigned int katana_codec_dsp_program_values[] = {
+	0,
+	1,
+	2,
+	3,
+	4,
+	6,
+	7,
+};
+
+static SOC_VALUE_ENUM_SINGLE_DECL(katana_codec_dsp_program,
+				  KATANA_CODEC_DSP_PROGRAM, 0, 0x07,
+				  katana_codec_dsp_program_texts,
+				  katana_codec_dsp_program_values);
+
+static const char * const katana_codec_deemphasis_texts[] = {
+	"Bypass",
+	"32kHz",
+	"44.1kHz",
+	"48kHz",
+};
+
+static const unsigned int katana_codec_deemphasis_values[] = {
+	0,
+	1,
+	2,
+	3,
+};
+
+static SOC_VALUE_ENUM_SINGLE_DECL(katana_codec_deemphasis,
+				  KATANA_CODEC_DEEMPHASIS, 0, 0x03,
+				  katana_codec_deemphasis_texts,
+				  katana_codec_deemphasis_values);
+
+static const SNDRV_CTL_TLVD_DECLARE_DB_MINMAX(master_tlv, -12750, 0);
+
+static const struct snd_kcontrol_new katana_codec_controls[] = {
+	SOC_DOUBLE_R_TLV("Master Playback Volume", KATANA_CODEC_VOLUME_1,
+			KATANA_CODEC_VOLUME_2, 0, 255, 1, master_tlv),
+	SOC_DOUBLE("Master Playback Switch", KATANA_CODEC_MUTE, 0, 0, 1, 1),
+	SOC_ENUM("DSP Program Route", katana_codec_dsp_program),
+	SOC_ENUM("Deemphasis Route", katana_codec_deemphasis),
+	SOC_SINGLE("DoP Playback Switch", KATANA_CODEC_DOP, 0, 1, 1)
+};
+
+static bool katana_codec_readable_register(struct device *dev,
+				unsigned int reg)
+{
+	switch (reg) {
+	case KATANA_CODEC_CHIP_ID_REG:
+		return true;
+	default:
+		return reg < 0xff;
+	}
+}
+
+static int katana_codec_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *params,
+			     struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct katana_codec_priv *katana_codec =
+		snd_soc_component_get_drvdata(component);
+	int fmt = 0;
+	int ret;
+
+	dev_dbg(component->card->dev, "hw_params %u Hz, %u channels, %u bits\n",
+			params_rate(params),
+			params_channels(params),
+			params_width(params));
+
+	switch (katana_codec->fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBM_CFM: // master
+		if (params_channels(params) == 2)
+			fmt = KATANA_CODEC_CHAN_STEREO;
+		else
+			fmt = KATANA_CODEC_CHAN_MONO;
+
+		switch (params_width(params)) {
+		case 16:
+			fmt |= KATANA_CODEC_ALEN_16;
+			break;
+		case 24:
+			fmt |= KATANA_CODEC_ALEN_24;
+			break;
+		case 32:
+			fmt |= KATANA_CODEC_ALEN_32;
+			break;
+		default:
+			dev_err(component->card->dev, "Bad frame size: %d\n",
+					params_width(params));
+			return -EINVAL;
+		}
+
+		switch (params_rate(params)) {
+		case 44100:
+			fmt |= KATANA_CODEC_RATE_44100;
+			break;
+		case 48000:
+			fmt |= KATANA_CODEC_RATE_48000;
+			break;
+		case 88200:
+			fmt |= KATANA_CODEC_RATE_88200;
+			break;
+		case 96000:
+			fmt |= KATANA_CODEC_RATE_96000;
+			break;
+		case 176400:
+			fmt |= KATANA_CODEC_RATE_176400;
+			break;
+		case 192000:
+			fmt |= KATANA_CODEC_RATE_192000;
+			break;
+		case 352800:
+			fmt |= KATANA_CODEC_RATE_352800;
+			break;
+		case 384000:
+			fmt |= KATANA_CODEC_RATE_384000;
+			break;
+		default:
+			dev_err(component->card->dev, "Bad sample rate: %d\n",
+					params_rate(params));
+			return -EINVAL;
+		}
+
+		ret = regmap_write(katana_codec->regmap, KATANA_CODEC_FORMAT,
+					fmt);
+		if (ret != 0) {
+			dev_err(component->card->dev, "Failed to set format: %d\n", ret);
+			return ret;
+		}
+		break;
+
+	case SND_SOC_DAIFMT_CBS_CFS:
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int katana_codec_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct snd_soc_component *component = dai->component;
+	struct katana_codec_priv *katana_codec =
+		snd_soc_component_get_drvdata(component);
+
+	katana_codec->fmt = fmt;
+
+	return 0;
+}
+
+static int katana_codec_dai_mute_stream(struct snd_soc_dai *dai, int mute,
+						int stream)
+{
+	struct snd_soc_component *component = dai->component;
+	struct katana_codec_priv *katana_codec =
+		snd_soc_component_get_drvdata(component);
+	int ret = 0;
+
+	ret = regmap_write(katana_codec->regmap, KATANA_CODEC_MUTE_STREAM,
+				mute);
+	if (ret != 0) {
+		dev_err(component->card->dev, "Failed to set mute: %d\n", ret);
+		return ret;
+	}
+	return ret;
+}
+
+static const struct snd_soc_dai_ops katana_codec_dai_ops = {
+	.mute_stream = katana_codec_dai_mute_stream,
+	.hw_params = katana_codec_hw_params,
+	.set_fmt = katana_codec_set_fmt,
+};
+
+static struct snd_soc_dai_driver katana_codec_dai = {
+	.name = "allo-katana-codec",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_CONTINUOUS,
+		.rate_min = 44100,
+		.rate_max = 384000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE |
+			SNDRV_PCM_FMTBIT_S32_LE
+	},
+	.ops = &katana_codec_dai_ops,
+};
+
+static struct snd_soc_component_driver katana_codec_component_driver = {
+	.idle_bias_on = true,
+
+	.controls		= katana_codec_controls,
+	.num_controls	= ARRAY_SIZE(katana_codec_controls),
+};
+
+static const struct regmap_range_cfg katana_codec_range = {
+	.name = "Pages", .range_min = KATANA_CODEC_VIRT_BASE,
+	.range_max = KATANA_CODEC_MAX_REGISTER,
+	.selector_reg = KATANA_CODEC_PAGE,
+	.selector_mask = 0xff,
+	.window_start = 0, .window_len = 0x100,
+};
+
+const struct regmap_config katana_codec_regmap = {
+	.reg_bits = 8,
+	.val_bits = 8,
+
+	.ranges = &katana_codec_range,
+	.num_ranges = 1,
+
+	.max_register = KATANA_CODEC_MAX_REGISTER,
+	.readable_reg = katana_codec_readable_register,
+	.reg_defaults = katana_codec_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(katana_codec_reg_defaults),
+	.cache_type = REGCACHE_RBTREE,
+};
+
+static int allo_katana_component_probe(struct i2c_client *i2c)
+{
+	struct regmap *regmap;
+	struct regmap_config config = katana_codec_regmap;
+	struct device *dev = &i2c->dev;
+	struct katana_codec_priv *katana_codec;
+	unsigned int chip_id = 0;
+	int ret;
+
+	regmap = devm_regmap_init_i2c(i2c, &config);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	katana_codec = devm_kzalloc(dev, sizeof(struct katana_codec_priv),
+					GFP_KERNEL);
+	if (!katana_codec)
+		return -ENOMEM;
+
+	dev_set_drvdata(dev, katana_codec);
+	katana_codec->regmap = regmap;
+
+	ret = regmap_read(regmap, KATANA_CODEC_CHIP_ID_REG, &chip_id);
+	if ((ret != 0) || (chip_id != KATANA_CODEC_CHIP_ID)) {
+		dev_err(dev, "Failed to read Chip or wrong Chip id: %d\n", ret);
+		return ret;
+	}
+	regmap_update_bits(regmap, KATANA_CODEC_RESET, 0x01, 0x01);
+	msleep(10);
+
+	ret = snd_soc_register_component(dev, &katana_codec_component_driver,
+				    &katana_codec_dai, 1);
+	if (ret != 0) {
+		dev_err(dev, "failed to register codec: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void allo_katana_component_remove(struct i2c_client *i2c)
+{
+	snd_soc_unregister_component(&i2c->dev);
+}
+
+static const struct i2c_device_id allo_katana_component_id[] = {
+	{ "allo-katana-codec", },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, allo_katana_component_id);
+
+static const struct of_device_id allo_katana_codec_of_match[] = {
+	{ .compatible = "allo,allo-katana-codec", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, allo_katana_codec_of_match);
+
+static struct i2c_driver allo_katana_component_driver = {
+	.probe		= allo_katana_component_probe,
+	.remove		= allo_katana_component_remove,
+	.id_table	= allo_katana_component_id,
+	.driver		= {
+	.name		= "allo-katana-codec",
+	.of_match_table = allo_katana_codec_of_match,
+	},
+};
+
+module_i2c_driver(allo_katana_component_driver);
+
+MODULE_DESCRIPTION("ASoC Allo Katana Codec Driver");
+MODULE_AUTHOR("Jaikumar <jaikumar@cem-solutions.net>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/allo-piano-dac-plus.c b/sound/soc/bcm/allo-piano-dac-plus.c
new file mode 100644
index 000000000000..07cbd4215b7d
--- /dev/null
+++ b/sound/soc/bcm/allo-piano-dac-plus.c
@@ -0,0 +1,1026 @@
+/*
+ * ALSA ASoC Machine Driver for Allo Piano DAC Plus Subwoofer
+ *
+ * Author:	Baswaraj K <jaikumar@cem-solutions.net>
+ *		Copyright 2020
+ *		based on code by David Knell <david.knell@gmail.com)
+ *		based on code by Daniel Matuschek <info@crazy-audio.com>
+ *		based on code by Florian Meier <florian.meier@koalo.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <linux/firmware.h>
+#include <linux/delay.h>
+#include <sound/tlv.h>
+#include "../codecs/pcm512x.h"
+
+#define P_DAC_LEFT_MUTE		0x10
+#define P_DAC_RIGHT_MUTE	0x01
+#define P_DAC_MUTE		0x11
+#define P_DAC_UNMUTE		0x00
+#define P_MUTE			1
+#define P_UNMUTE		0
+
+struct dsp_code {
+	char i2c_addr;
+	char offset;
+	char val;
+};
+
+struct glb_pool {
+	struct mutex lock;
+	unsigned int dual_mode;
+	unsigned int set_lowpass;
+	unsigned int set_mode;
+	unsigned int set_rate;
+	unsigned int dsp_page_number;
+};
+
+static bool digital_gain_0db_limit = true;
+bool glb_mclk;
+
+static struct gpio_desc *mute_gpio[2];
+
+static const char * const allo_piano_mode_texts[] = {
+	"None",
+	"2.0",
+	"2.1",
+	"2.2",
+};
+
+static SOC_ENUM_SINGLE_DECL(allo_piano_mode_enum,
+		0, 0, allo_piano_mode_texts);
+
+static const char * const allo_piano_dual_mode_texts[] = {
+	"None",
+	"Dual-Mono",
+	"Dual-Stereo",
+};
+
+static SOC_ENUM_SINGLE_DECL(allo_piano_dual_mode_enum,
+		0, 0, allo_piano_dual_mode_texts);
+
+static const char * const allo_piano_dsp_low_pass_texts[] = {
+	"60",
+	"70",
+	"80",
+	"90",
+	"100",
+	"110",
+	"120",
+	"130",
+	"140",
+	"150",
+	"160",
+	"170",
+	"180",
+	"190",
+	"200",
+};
+
+static SOC_ENUM_SINGLE_DECL(allo_piano_enum,
+		0, 0, allo_piano_dsp_low_pass_texts);
+
+static int __snd_allo_piano_dsp_program(struct snd_soc_pcm_runtime *rtd,
+		unsigned int mode, unsigned int rate, unsigned int lowpass)
+{
+	const struct firmware *fw;
+	struct snd_soc_card *card = rtd->card;
+	struct glb_pool *glb_ptr = card->drvdata;
+	char firmware_name[60];
+	int ret = 0, dac = 0;
+
+	if (rate <= 46000)
+		rate = 44100;
+	else if (rate <= 68000)
+		rate = 48000;
+	else if (rate <= 92000)
+		rate = 88200;
+	else if (rate <= 136000)
+		rate = 96000;
+	else if (rate <= 184000)
+		rate = 176400;
+	else
+		rate = 192000;
+
+	if (lowpass > 14)
+		glb_ptr->set_lowpass = lowpass = 0;
+
+	if (mode > 3)
+		glb_ptr->set_mode = mode = 0;
+
+	if (mode > 0)
+		glb_ptr->dual_mode = 0;
+
+	/* same configuration loaded */
+	if ((rate == glb_ptr->set_rate) && (lowpass == glb_ptr->set_lowpass)
+			&& (mode == glb_ptr->set_mode))
+		return 0;
+
+	switch (mode) {
+	case 0: /* None */
+		return 1;
+
+	case 1: /* 2.0 */
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_MUTE, P_DAC_UNMUTE);
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_MUTE, P_DAC_MUTE);
+		glb_ptr->set_rate = rate;
+		glb_ptr->set_mode = mode;
+		glb_ptr->set_lowpass = lowpass;
+		return 1;
+
+	default:
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_MUTE, P_DAC_UNMUTE);
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_MUTE, P_DAC_UNMUTE);
+	}
+
+	for (dac = 0; dac < rtd->dai_link->num_codecs; dac++) {
+		struct dsp_code *dsp_code_read;
+		int i = 1;
+
+		if (dac == 0) { /* high */
+			snprintf(firmware_name, sizeof(firmware_name),
+				"allo/piano/2.2/allo-piano-dsp-%d-%d-%d.bin",
+				rate, ((lowpass * 10) + 60), dac);
+		} else { /* low */
+			snprintf(firmware_name, sizeof(firmware_name),
+				"allo/piano/2.%d/allo-piano-dsp-%d-%d-%d.bin",
+				(mode - 1), rate, ((lowpass * 10) + 60), dac);
+		}
+
+		dev_info(rtd->card->dev, "Dsp Firmware File Name: %s\n",
+				firmware_name);
+
+		ret = request_firmware(&fw, firmware_name, rtd->card->dev);
+		if (ret < 0) {
+			dev_err(rtd->card->dev,
+				"Error: Allo Piano Firmware %s missing. %d\n",
+				firmware_name, ret);
+			goto err;
+		}
+
+		while (i < (fw->size - 1)) {
+			dsp_code_read = (struct dsp_code *)&fw->data[i];
+
+			if (dsp_code_read->offset == 0) {
+				glb_ptr->dsp_page_number = dsp_code_read->val;
+				ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, dac)->component,
+						PCM512x_PAGE_BASE(0),
+						dsp_code_read->val);
+
+			} else if (dsp_code_read->offset != 0) {
+				ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, dac)->component,
+					(PCM512x_PAGE_BASE(
+						glb_ptr->dsp_page_number) +
+					dsp_code_read->offset),
+					dsp_code_read->val);
+			}
+			if (ret < 0) {
+				dev_err(rtd->card->dev,
+					"Failed to write Register: %d\n", ret);
+				release_firmware(fw);
+				goto err;
+			}
+			i = i + 3;
+		}
+		release_firmware(fw);
+	}
+	glb_ptr->set_rate = rate;
+	glb_ptr->set_mode = mode;
+	glb_ptr->set_lowpass = lowpass;
+	return 1;
+
+err:
+	return ret;
+}
+
+static int snd_allo_piano_dsp_program(struct snd_soc_pcm_runtime *rtd,
+		unsigned int mode, unsigned int rate, unsigned int lowpass)
+{
+	struct snd_soc_card *card = rtd->card;
+	struct glb_pool *glb_ptr = card->drvdata;
+	int ret = 0;
+
+	mutex_lock(&glb_ptr->lock);
+
+	ret = __snd_allo_piano_dsp_program(rtd, mode, rate, lowpass);
+
+	mutex_unlock(&glb_ptr->lock);
+
+	return ret;
+}
+
+static int snd_allo_piano_dual_mode_get(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct glb_pool *glb_ptr = card->drvdata;
+
+	ucontrol->value.integer.value[0] = glb_ptr->dual_mode;
+
+	return 0;
+}
+
+static int snd_allo_piano_dual_mode_put(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct glb_pool *glb_ptr = card->drvdata;
+	struct snd_soc_pcm_runtime *rtd;
+	struct snd_card *snd_card_ptr = card->snd_card;
+	struct snd_kcontrol *kctl;
+	struct soc_mixer_control *mc;
+	unsigned int left_val = 0, right_val = 0;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+
+	if (ucontrol->value.integer.value[0] > 0) {
+		glb_ptr->dual_mode = ucontrol->value.integer.value[0];
+		glb_ptr->set_mode = 0;
+	} else {
+		if (glb_ptr->set_mode <= 0) {
+			glb_ptr->dual_mode = 1;
+			glb_ptr->set_mode = 0;
+		} else {
+			glb_ptr->dual_mode = 0;
+			return 0;
+		}
+	}
+
+	if (glb_ptr->dual_mode == 1) { // Dual Mono
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_MUTE, P_DAC_RIGHT_MUTE);
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_MUTE, P_DAC_LEFT_MUTE);
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_DIGITAL_VOLUME_3, 0xff);
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_DIGITAL_VOLUME_2, 0xff);
+
+		list_for_each_entry(kctl, &snd_card_ptr->controls, list) {
+			if (!strncmp(kctl->id.name, "Main Digital Playback Volume",
+				sizeof(kctl->id.name))) {
+				mc = (struct soc_mixer_control *)
+					kctl->private_value;
+				mc->rreg = mc->reg;
+				break;
+			}
+			if (!strncmp(kctl->id.name, "Sub Digital Playback Volume",
+				sizeof(kctl->id.name))) {
+				mc = (struct soc_mixer_control *)
+					kctl->private_value;
+				mc->rreg = mc->reg;
+				break;
+			}
+		}
+	} else {
+		left_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 0)->component,
+						PCM512x_DIGITAL_VOLUME_2);
+		right_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component,
+						PCM512x_DIGITAL_VOLUME_3);
+
+		list_for_each_entry(kctl, &snd_card_ptr->controls, list) {
+			if (!strncmp(kctl->id.name, "Main Digital Playback Volume",
+				sizeof(kctl->id.name))) {
+				mc = (struct soc_mixer_control *)
+					kctl->private_value;
+				mc->rreg = PCM512x_DIGITAL_VOLUME_3;
+				break;
+			}
+			if (!strncmp(kctl->id.name, "Sub Digital Playback Volume",
+				sizeof(kctl->id.name))) {
+				mc = (struct soc_mixer_control *)
+					kctl->private_value;
+				mc->rreg = PCM512x_DIGITAL_VOLUME_2;
+				break;
+			}
+		}
+
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_DIGITAL_VOLUME_3, left_val);
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_DIGITAL_VOLUME_2, right_val);
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_MUTE, P_DAC_UNMUTE);
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_MUTE, P_DAC_UNMUTE);
+	}
+
+	return 0;
+}
+
+static int snd_allo_piano_mode_get(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct glb_pool *glb_ptr = card->drvdata;
+
+	ucontrol->value.integer.value[0] = glb_ptr->set_mode;
+	return 0;
+}
+
+static int snd_allo_piano_mode_put(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_pcm_runtime *rtd;
+	struct glb_pool *glb_ptr = card->drvdata;
+	struct snd_card *snd_card_ptr = card->snd_card;
+	struct snd_kcontrol *kctl;
+	struct soc_mixer_control *mc;
+	unsigned int left_val = 0, right_val = 0;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+
+	if ((glb_ptr->dual_mode == 1) &&
+			(ucontrol->value.integer.value[0] > 0)) {
+		left_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 0)->component,
+						PCM512x_DIGITAL_VOLUME_2);
+		right_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component,
+						PCM512x_DIGITAL_VOLUME_2);
+
+		list_for_each_entry(kctl, &snd_card_ptr->controls, list) {
+			if (!strncmp(kctl->id.name, "Main Digital Playback Volume",
+				sizeof(kctl->id.name))) {
+				mc = (struct soc_mixer_control *)
+					kctl->private_value;
+				mc->rreg = PCM512x_DIGITAL_VOLUME_3;
+				break;
+			}
+			if (!strncmp(kctl->id.name, "Sub Digital Playback Volume",
+				sizeof(kctl->id.name))) {
+				mc = (struct soc_mixer_control *)
+					kctl->private_value;
+				mc->rreg = PCM512x_DIGITAL_VOLUME_2;
+				break;
+			}
+		}
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_DIGITAL_VOLUME_3, left_val);
+		snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_DIGITAL_VOLUME_3, right_val);
+	}
+
+	return(snd_allo_piano_dsp_program(rtd,
+				ucontrol->value.integer.value[0],
+				glb_ptr->set_rate, glb_ptr->set_lowpass));
+}
+
+static int snd_allo_piano_lowpass_get(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct glb_pool *glb_ptr = card->drvdata;
+
+	ucontrol->value.integer.value[0] = glb_ptr->set_lowpass;
+	return 0;
+}
+
+static int snd_allo_piano_lowpass_put(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_pcm_runtime *rtd;
+	struct glb_pool *glb_ptr = card->drvdata;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+	return(snd_allo_piano_dsp_program(rtd,
+				glb_ptr->set_mode, glb_ptr->set_rate,
+				ucontrol->value.integer.value[0]));
+}
+
+static int pcm512x_get_reg_sub(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct glb_pool *glb_ptr = card->drvdata;
+	struct snd_soc_pcm_runtime *rtd;
+	unsigned int left_val = 0;
+	unsigned int right_val = 0;
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+
+	right_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component,
+			PCM512x_DIGITAL_VOLUME_3);
+	if (glb_ptr->dual_mode != 1) {
+		left_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_DIGITAL_VOLUME_2);
+
+	} else {
+		left_val = right_val;
+	}
+
+	ucontrol->value.integer.value[0] =
+				(~(left_val >> mc->shift)) & mc->max;
+	ucontrol->value.integer.value[1] =
+				(~(right_val >> mc->shift)) & mc->max;
+
+	return 0;
+}
+
+static int pcm512x_set_reg_sub(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct glb_pool *glb_ptr = card->drvdata;
+	struct snd_soc_pcm_runtime *rtd;
+	unsigned int left_val = (ucontrol->value.integer.value[0] & mc->max);
+	unsigned int right_val = (ucontrol->value.integer.value[1] & mc->max);
+	int ret = 0;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+
+	// When in Dual Mono, Sub vol control should not set anything.
+	if (glb_ptr->dual_mode != 1) { //Not in Dual Mono mode
+
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_DIGITAL_VOLUME_2, (~left_val));
+		if (ret < 0)
+			return ret;
+
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_DIGITAL_VOLUME_3, (~right_val));
+		if (ret < 0)
+			return ret;
+
+	}
+
+	return 1;
+}
+
+static int pcm512x_get_reg_sub_switch(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_pcm_runtime *rtd;
+	int val = 0;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+	val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component, PCM512x_MUTE);
+
+	ucontrol->value.integer.value[0] =
+			(val & P_DAC_LEFT_MUTE) ? P_UNMUTE : P_MUTE;
+	ucontrol->value.integer.value[1] =
+			(val & P_DAC_RIGHT_MUTE) ? P_UNMUTE : P_MUTE;
+
+	return val;
+}
+
+static int pcm512x_set_reg_sub_switch(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_pcm_runtime *rtd;
+	struct glb_pool *glb_ptr = card->drvdata;
+	unsigned int left_val = (ucontrol->value.integer.value[0]);
+	unsigned int right_val = (ucontrol->value.integer.value[1]);
+	int ret = 0;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+	if (glb_ptr->set_mode != 1) {
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, PCM512x_MUTE,
+				~((left_val & 0x01)<<4 | (right_val & 0x01)));
+		if (ret < 0)
+			return ret;
+	}
+	return 1;
+
+}
+
+static int pcm512x_get_reg_master(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct glb_pool *glb_ptr = card->drvdata;
+	struct snd_soc_pcm_runtime *rtd;
+	unsigned int left_val = 0, right_val = 0;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+
+	left_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 0)->component,
+			PCM512x_DIGITAL_VOLUME_2);
+
+	if (glb_ptr->dual_mode == 1) {  // in Dual Mono mode
+		right_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_DIGITAL_VOLUME_3);
+	} else {
+		right_val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_DIGITAL_VOLUME_3);
+	}
+
+	ucontrol->value.integer.value[0] =
+		(~(left_val  >> mc->shift)) & mc->max;
+	ucontrol->value.integer.value[1] =
+		(~(right_val >> mc->shift)) & mc->max;
+
+	return 0;
+}
+
+static int pcm512x_set_reg_master(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct soc_mixer_control *mc =
+		(struct soc_mixer_control *)kcontrol->private_value;
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct glb_pool *glb_ptr = card->drvdata;
+	struct snd_soc_pcm_runtime *rtd;
+	unsigned int left_val = (ucontrol->value.integer.value[0] & mc->max);
+	unsigned int right_val = (ucontrol->value.integer.value[1] & mc->max);
+	int ret = 0;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+
+	if (glb_ptr->dual_mode == 1) { //in Dual Mono Mode
+
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_DIGITAL_VOLUME_2, (~left_val));
+		if (ret < 0)
+			return ret;
+
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component,
+				PCM512x_DIGITAL_VOLUME_3, (~right_val));
+		if (ret < 0)
+			return ret;
+
+	} else {
+
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_DIGITAL_VOLUME_2, (~left_val));
+		if (ret < 0)
+			return ret;
+
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component,
+				PCM512x_DIGITAL_VOLUME_3, (~right_val));
+		if (ret < 0)
+			return ret;
+
+	}
+	return 1;
+}
+
+static int pcm512x_get_reg_master_switch(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct glb_pool *glb_ptr = card->drvdata;
+	struct snd_soc_pcm_runtime *rtd;
+	int val = 0;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+
+	val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 0)->component, PCM512x_MUTE);
+
+	ucontrol->value.integer.value[0] =
+			(val & P_DAC_LEFT_MUTE) ? P_UNMUTE : P_MUTE;
+
+	if (glb_ptr->dual_mode == 1) {
+		val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, 1)->component, PCM512x_MUTE);
+	}
+	ucontrol->value.integer.value[1] =
+			(val & P_DAC_RIGHT_MUTE) ? P_UNMUTE : P_MUTE;
+
+	return val;
+}
+
+static int pcm512x_set_reg_master_switch(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_pcm_runtime *rtd;
+	struct glb_pool *glb_ptr = card->drvdata;
+	unsigned int left_val = (ucontrol->value.integer.value[0]);
+	unsigned int right_val = (ucontrol->value.integer.value[1]);
+	int ret = 0;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+	if (glb_ptr->dual_mode == 1) {
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, PCM512x_MUTE,
+				~((left_val & 0x01)<<4));
+		if (ret < 0)
+			return ret;
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, PCM512x_MUTE,
+				~((right_val & 0x01)));
+		if (ret < 0)
+			return ret;
+
+	} else if (glb_ptr->set_mode == 1) {
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, PCM512x_MUTE,
+				~((left_val & 0x01)<<4 | (right_val & 0x01)));
+		if (ret < 0)
+			return ret;
+
+	} else {
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 0)->component, PCM512x_MUTE,
+				~((left_val & 0x01)<<4 | (right_val & 0x01)));
+		if (ret < 0)
+			return ret;
+
+		ret = snd_soc_component_write(snd_soc_rtd_to_codec(rtd, 1)->component, PCM512x_MUTE,
+				~((left_val & 0x01)<<4 | (right_val & 0x01)));
+		if (ret < 0)
+			return ret;
+	}
+	return 1;
+}
+
+static const DECLARE_TLV_DB_SCALE(digital_tlv_sub, -10350, 50, 1);
+static const DECLARE_TLV_DB_SCALE(digital_tlv_master, -10350, 50, 1);
+
+static const struct snd_kcontrol_new allo_piano_controls[] = {
+	SOC_ENUM_EXT("Subwoofer mode Route",
+			allo_piano_mode_enum,
+			snd_allo_piano_mode_get,
+			snd_allo_piano_mode_put),
+
+	SOC_ENUM_EXT("Dual Mode Route",
+			allo_piano_dual_mode_enum,
+			snd_allo_piano_dual_mode_get,
+			snd_allo_piano_dual_mode_put),
+
+	SOC_ENUM_EXT("Lowpass Route", allo_piano_enum,
+			snd_allo_piano_lowpass_get,
+			snd_allo_piano_lowpass_put),
+
+	SOC_DOUBLE_R_EXT_TLV("Subwoofer Playback Volume",
+			PCM512x_DIGITAL_VOLUME_2,
+			PCM512x_DIGITAL_VOLUME_3, 0, 255, 1,
+			pcm512x_get_reg_sub,
+			pcm512x_set_reg_sub,
+			digital_tlv_sub),
+
+	SOC_DOUBLE_EXT("Subwoofer Playback Switch",
+			PCM512x_MUTE,
+			PCM512x_RQML_SHIFT,
+			PCM512x_RQMR_SHIFT, 1, 1,
+			pcm512x_get_reg_sub_switch,
+			pcm512x_set_reg_sub_switch),
+
+	SOC_DOUBLE_R_EXT_TLV("Master Playback Volume",
+			PCM512x_DIGITAL_VOLUME_2,
+			PCM512x_DIGITAL_VOLUME_3, 0, 255, 1,
+			pcm512x_get_reg_master,
+			pcm512x_set_reg_master,
+			digital_tlv_master),
+
+	SOC_DOUBLE_EXT("Master Playback Switch",
+			PCM512x_MUTE,
+			PCM512x_RQML_SHIFT,
+			PCM512x_RQMR_SHIFT, 1, 1,
+			pcm512x_get_reg_master_switch,
+			pcm512x_set_reg_master_switch),
+};
+
+static const char * const codec_ctl_pfx[] = { "Main", "Sub" };
+static const char * const codec_ctl_name[] = {
+	"Digital Playback Volume",
+	"Digital Playback Switch",
+	"Auto Mute Mono Switch",
+	"Auto Mute Switch",
+	"Auto Mute Time Left",
+	"Auto Mute Time Right",
+	"Clock Missing Period",
+	"Max Overclock DAC",
+	"Max Overclock DSP",
+	"Max Overclock PLL",
+	"Volume Ramp Down Emergency Rate",
+	"Volume Ramp Down Emergency Step",
+	"Volume Ramp Up Rate",
+	"Volume Ramp Down Rate",
+	"Volume Ramp Up Step",
+	"Volume Ramp Down Step"
+};
+
+static int snd_allo_piano_dac_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_card *card = rtd->card;
+	struct glb_pool *glb_ptr;
+	struct snd_kcontrol *kctl;
+	int i, j;
+
+	glb_ptr = kzalloc(sizeof(struct glb_pool), GFP_KERNEL);
+	if (!glb_ptr)
+		return -ENOMEM;
+
+	card->drvdata = glb_ptr;
+	glb_ptr->dual_mode = 2;
+	glb_ptr->set_mode = 0;
+
+	mutex_init(&glb_ptr->lock);
+
+	// Remove codec controls
+	for (i = 0; i < ARRAY_SIZE(codec_ctl_pfx); i++) {
+		for (j = 0; j < ARRAY_SIZE(codec_ctl_name); j++) {
+			char cname[256];
+
+			sprintf(cname, "%s %s", codec_ctl_pfx[i], codec_ctl_name[j]);
+			kctl = snd_soc_card_get_kcontrol(card, cname);
+			if (kctl) {
+				kctl->vd[0].access =
+					SNDRV_CTL_ELEM_ACCESS_READWRITE;
+				snd_ctl_remove(card->snd_card, kctl);
+			}
+		}
+	}
+
+	return 0;
+}
+
+static void snd_allo_piano_gpio_mute(struct snd_soc_card *card)
+{
+	if (mute_gpio[0])
+		gpiod_set_value_cansleep(mute_gpio[0], P_MUTE);
+
+	if (mute_gpio[1])
+		gpiod_set_value_cansleep(mute_gpio[1], P_MUTE);
+}
+
+static void snd_allo_piano_gpio_unmute(struct snd_soc_card *card)
+{
+	if (mute_gpio[0])
+		gpiod_set_value_cansleep(mute_gpio[0], P_UNMUTE);
+
+	if (mute_gpio[1])
+		gpiod_set_value_cansleep(mute_gpio[1], P_UNMUTE);
+}
+
+static int snd_allo_piano_set_bias_level(struct snd_soc_card *card,
+	struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level)
+{
+	struct snd_soc_pcm_runtime *rtd;
+	struct snd_soc_dai *codec_dai;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+	codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+	if (dapm->dev != codec_dai->dev)
+		return 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_PREPARE:
+		if (dapm->bias_level != SND_SOC_BIAS_STANDBY)
+			break;
+		/* UNMUTE DAC */
+		snd_allo_piano_gpio_unmute(card);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (dapm->bias_level != SND_SOC_BIAS_PREPARE)
+			break;
+		/* MUTE DAC */
+		snd_allo_piano_gpio_mute(card);
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int snd_allo_piano_dac_startup(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_card *card = rtd->card;
+
+	snd_allo_piano_gpio_mute(card);
+
+	return 0;
+}
+
+static int snd_allo_piano_dac_hw_params(
+		struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	unsigned int rate = params_rate(params);
+	struct snd_soc_card *card = rtd->card;
+	struct glb_pool *glb_ptr = card->drvdata;
+	int ret = 0, val = 0, dac;
+
+	for (dac = 0; (glb_mclk && dac < 2); dac++) {
+		/* Configure the PLL clock reference for both the Codecs */
+		val = snd_soc_component_read(snd_soc_rtd_to_codec(rtd, dac)->component,
+					PCM512x_RATE_DET_4);
+
+		if (val & 0x40) {
+			snd_soc_component_write(snd_soc_rtd_to_codec(rtd, dac)->component,
+					PCM512x_PLL_REF,
+					PCM512x_SREF_BCK);
+
+			dev_info(snd_soc_rtd_to_codec(rtd, dac)->component->dev,
+				"Setting BCLK as input clock & Enable PLL\n");
+		} else {
+			snd_soc_component_write(snd_soc_rtd_to_codec(rtd, dac)->component,
+					PCM512x_PLL_EN,
+					0x00);
+
+			snd_soc_component_write(snd_soc_rtd_to_codec(rtd, dac)->component,
+					PCM512x_PLL_REF,
+					PCM512x_SREF_SCK);
+
+			dev_info(snd_soc_rtd_to_codec(rtd, dac)->component->dev,
+				"Setting SCLK as input clock & disabled PLL\n");
+		}
+	}
+
+	ret = snd_allo_piano_dsp_program(rtd, glb_ptr->set_mode, rate,
+						glb_ptr->set_lowpass);
+	if (ret < 0)
+		return ret;
+
+	return ret;
+}
+
+static int snd_allo_piano_dac_prepare(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_card *card = rtd->card;
+
+	snd_allo_piano_gpio_unmute(card);
+
+	return 0;
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_allo_piano_dac_ops = {
+	.startup = snd_allo_piano_dac_startup,
+	.hw_params = snd_allo_piano_dac_hw_params,
+	.prepare = snd_allo_piano_dac_prepare,
+};
+
+static struct snd_soc_dai_link_component allo_piano_2_1_codecs[] = {
+	{
+		.dai_name = "pcm512x-hifi",
+	},
+	{
+		.dai_name = "pcm512x-hifi",
+	},
+};
+
+SND_SOC_DAILINK_DEFS(allo_piano_dai_plus,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004c", "pcm512x-hifi"),
+			   COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_allo_piano_dac_dai[] = {
+	{
+		.name		= "PianoDACPlus",
+		.stream_name	= "PianoDACPlus",
+		.dai_fmt	= SND_SOC_DAIFMT_I2S |
+				SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBS_CFS,
+		.ops		= &snd_allo_piano_dac_ops,
+		.init		= snd_allo_piano_dac_init,
+		SND_SOC_DAILINK_REG(allo_piano_dai_plus),
+	},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_allo_piano_dac = {
+	.name = "PianoDACPlus",
+	.owner = THIS_MODULE,
+	.dai_link = snd_allo_piano_dac_dai,
+	.num_links = ARRAY_SIZE(snd_allo_piano_dac_dai),
+	.controls = allo_piano_controls,
+	.num_controls = ARRAY_SIZE(allo_piano_controls),
+};
+
+static int snd_allo_piano_dac_probe(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = &snd_allo_piano_dac;
+	int ret = 0, i = 0;
+
+	card->dev = &pdev->dev;
+	platform_set_drvdata(pdev, &snd_allo_piano_dac);
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai;
+
+		dai = &snd_allo_piano_dac_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+						"i2s-controller", 0);
+		if (i2s_node) {
+			for (i = 0; i < card->num_links; i++) {
+				dai->cpus->dai_name = NULL;
+				dai->cpus->of_node = i2s_node;
+				dai->platforms->name = NULL;
+				dai->platforms->of_node = i2s_node;
+			}
+		}
+		digital_gain_0db_limit =
+			!of_property_read_bool(pdev->dev.of_node,
+						"allo,24db_digital_gain");
+
+		glb_mclk = of_property_read_bool(pdev->dev.of_node,
+						"allo,glb_mclk");
+
+		allo_piano_2_1_codecs[0].of_node =
+			of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+		allo_piano_2_1_codecs[1].of_node =
+			of_parse_phandle(pdev->dev.of_node, "audio-codec", 1);
+		if (!allo_piano_2_1_codecs[0].of_node || !allo_piano_2_1_codecs[1].of_node)
+			return dev_err_probe(&pdev->dev, -EINVAL,
+				"Property 'audio-codec' missing or invalid\n");
+
+		mute_gpio[0] = devm_gpiod_get_optional(&pdev->dev, "mute1",
+							GPIOD_OUT_LOW);
+		if (IS_ERR(mute_gpio[0]))
+			return dev_err_probe(&pdev->dev, PTR_ERR(mute_gpio[0]),
+				"failed to get mute1 gpio\n");
+
+		mute_gpio[1] = devm_gpiod_get_optional(&pdev->dev, "mute2",
+							GPIOD_OUT_LOW);
+		if (IS_ERR(mute_gpio[1]))
+			return dev_err_probe(&pdev->dev, PTR_ERR(mute_gpio[1]),
+				"failed to get mute2 gpio\n");
+
+		if (mute_gpio[0] && mute_gpio[1])
+			snd_allo_piano_dac.set_bias_level =
+				snd_allo_piano_set_bias_level;
+
+		ret = snd_soc_register_card(&snd_allo_piano_dac);
+		if (ret < 0)
+			return dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n");
+
+		if (digital_gain_0db_limit) {
+			ret = snd_soc_limit_volume(card, "Master Playback Volume",
+						207);
+			if (ret < 0)
+				dev_warn(card->dev, "Failed to set master volume limit: %d\n",
+					ret);
+
+			ret = snd_soc_limit_volume(card, "Subwoofer Playback Volume",
+						207);
+			if (ret < 0)
+				dev_warn(card->dev, "Failed to set subwoofer volume limit: %d\n",
+					ret);
+		}
+
+		if ((mute_gpio[0]) && (mute_gpio[1]))
+			snd_allo_piano_gpio_mute(&snd_allo_piano_dac);
+
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static void snd_allo_piano_dac_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+	kfree(&card->drvdata);
+	snd_allo_piano_gpio_mute(&snd_allo_piano_dac);
+	snd_soc_unregister_card(&snd_allo_piano_dac);
+}
+
+static const struct of_device_id snd_allo_piano_dac_of_match[] = {
+	{ .compatible = "allo,piano-dac-plus", },
+	{ /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, snd_allo_piano_dac_of_match);
+
+static struct platform_driver snd_allo_piano_dac_driver = {
+	.driver = {
+		.name = "snd-allo-piano-dac-plus",
+		.owner = THIS_MODULE,
+		.of_match_table = snd_allo_piano_dac_of_match,
+	},
+	.probe = snd_allo_piano_dac_probe,
+	.remove = snd_allo_piano_dac_remove,
+};
+
+module_platform_driver(snd_allo_piano_dac_driver);
+
+MODULE_AUTHOR("Baswaraj K <jaikumar@cem-solutions.net>");
+MODULE_DESCRIPTION("ALSA ASoC Machine Driver for Allo Piano DAC Plus");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/allo-piano-dac.c b/sound/soc/bcm/allo-piano-dac.c
new file mode 100644
index 000000000000..61640fb95431
--- /dev/null
+++ b/sound/soc/bcm/allo-piano-dac.c
@@ -0,0 +1,122 @@
+/*
+ * ALSA ASoC Machine Driver for Allo Piano DAC
+ *
+ * Author:	Baswaraj K <jaikumar@cem-solutions.net>
+ *		Copyright 2016
+ *		based on code by Daniel Matuschek <info@crazy-audio.com>
+ *		based on code by Florian Meier <florian.meier@koalo.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+static bool digital_gain_0db_limit = true;
+
+static int snd_allo_piano_dac_init(struct snd_soc_pcm_runtime *rtd)
+{
+	if (digital_gain_0db_limit) {
+		int ret;
+		struct snd_soc_card *card = rtd->card;
+
+		ret = snd_soc_limit_volume(card, "Digital Playback Volume",
+					   207);
+		if (ret < 0)
+			dev_warn(card->dev, "Failed to set volume limit: %d\n",
+				 ret);
+	}
+
+	return 0;
+}
+
+SND_SOC_DAILINK_DEFS(allo_piano_dai,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004c", "pcm512x-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_allo_piano_dac_dai[] = {
+{
+	.name		= "Piano DAC",
+	.stream_name	= "Piano DAC HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S |
+			  SND_SOC_DAIFMT_NB_NF |
+			  SND_SOC_DAIFMT_CBS_CFS,
+	.init		= snd_allo_piano_dac_init,
+	SND_SOC_DAILINK_REG(allo_piano_dai),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_allo_piano_dac = {
+	.name         = "PianoDAC",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_allo_piano_dac_dai,
+	.num_links    = ARRAY_SIZE(snd_allo_piano_dac_dai),
+};
+
+static int snd_allo_piano_dac_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	snd_allo_piano_dac.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai;
+
+		dai = &snd_allo_piano_dac_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+					    "i2s-controller", 0);
+
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		}
+
+		digital_gain_0db_limit = !of_property_read_bool(
+			pdev->dev.of_node, "allo,24db_digital_gain");
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &snd_allo_piano_dac);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+
+	return ret;
+}
+
+static const struct of_device_id snd_allo_piano_dac_of_match[] = {
+	{ .compatible = "allo,piano-dac", },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, snd_allo_piano_dac_of_match);
+
+static struct platform_driver snd_allo_piano_dac_driver = {
+	.driver = {
+		.name   = "snd-allo-piano-dac",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_allo_piano_dac_of_match,
+	},
+	.probe          = snd_allo_piano_dac_probe,
+};
+
+module_platform_driver(snd_allo_piano_dac_driver);
+
+MODULE_AUTHOR("Baswaraj K <jaikumar@cem-solutions.net>");
+MODULE_DESCRIPTION("ALSA ASoC Machine Driver for Allo Piano DAC");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/audioinjector-isolated-soundcard.c b/sound/soc/bcm/audioinjector-isolated-soundcard.c
new file mode 100644
index 000000000000..6abe08223f34
--- /dev/null
+++ b/sound/soc/bcm/audioinjector-isolated-soundcard.c
@@ -0,0 +1,184 @@
+/*
+ * ASoC Driver for AudioInjector.net isolated soundcard
+ *
+ *  Created on: 20-February-2020
+ *      Author: flatmax@flatmax.org
+ *              based on audioinjector-octo-soundcard.c
+ *
+ * Copyright (C) 2020 Flatmax Pty. Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/gpio/consumer.h>
+
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/control.h>
+
+static struct gpio_desc *mute_gpio;
+
+static const unsigned int audioinjector_isolated_rates[] = {
+	192000, 96000, 48000, 32000, 24000, 16000, 8000
+};
+
+static struct snd_pcm_hw_constraint_list audioinjector_isolated_constraints = {
+	.list = audioinjector_isolated_rates,
+	.count = ARRAY_SIZE(audioinjector_isolated_rates),
+};
+
+static int audioinjector_isolated_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+	int ret=snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 24576000, 0);
+	if (ret)
+		return ret;
+
+	return snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), 64);
+}
+
+static int audioinjector_isolated_startup(struct snd_pcm_substream *substream)
+{
+	snd_pcm_hw_constraint_list(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_RATE, &audioinjector_isolated_constraints);
+
+	return 0;
+}
+
+static int audioinjector_isolated_trigger(struct snd_pcm_substream *substream,
+								int cmd){
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		gpiod_set_value(mute_gpio, 0);
+		break;
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		gpiod_set_value(mute_gpio, 1);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static struct snd_soc_ops audioinjector_isolated_ops = {
+	.startup	= audioinjector_isolated_startup,
+	.trigger = audioinjector_isolated_trigger,
+};
+
+SND_SOC_DAILINK_DEFS(audioinjector_isolated,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("cs4271.1-0010", "cs4271-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link audioinjector_isolated_dai[] = {
+	{
+		.name = "AudioInjector ISO",
+		.stream_name = "AI-HIFI",
+		.ops = &audioinjector_isolated_ops,
+		.init = audioinjector_isolated_dai_init,
+		.symmetric_rate = 1,
+		.symmetric_channels = 1,
+		.dai_fmt = SND_SOC_DAIFMT_CBM_CFM|SND_SOC_DAIFMT_I2S|SND_SOC_DAIFMT_NB_NF,
+		SND_SOC_DAILINK_REG(audioinjector_isolated),
+	}
+};
+
+static const struct snd_soc_dapm_widget audioinjector_isolated_widgets[] = {
+	SND_SOC_DAPM_OUTPUT("OUTPUTS"),
+	SND_SOC_DAPM_INPUT("INPUTS"),
+};
+
+static const struct snd_soc_dapm_route audioinjector_isolated_route[] = {
+	/* Balanced outputs */
+	{"OUTPUTS", NULL, "AOUTA+"},
+	{"OUTPUTS", NULL, "AOUTA-"},
+	{"OUTPUTS", NULL, "AOUTB+"},
+	{"OUTPUTS", NULL, "AOUTB-"},
+
+	/* Balanced inputs */
+	{"AINA", NULL, "INPUTS"},
+	{"AINB", NULL, "INPUTS"},
+};
+
+static struct snd_soc_card snd_soc_audioinjector_isolated = {
+	.name = "audioinjector-isolated-soundcard",
+	.owner = THIS_MODULE,
+	.dai_link = audioinjector_isolated_dai,
+	.num_links = ARRAY_SIZE(audioinjector_isolated_dai),
+
+	.dapm_widgets = audioinjector_isolated_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(audioinjector_isolated_widgets),
+	.dapm_routes = audioinjector_isolated_route,
+	.num_dapm_routes = ARRAY_SIZE(audioinjector_isolated_route),
+};
+
+static int audioinjector_isolated_probe(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = &snd_soc_audioinjector_isolated;
+	int ret;
+
+	card->dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct snd_soc_dai_link *dai = &audioinjector_isolated_dai[0];
+		struct device_node *i2s_node =
+					of_parse_phandle(pdev->dev.of_node, "i2s-controller", 0);
+
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		} else {
+				dev_err(&pdev->dev,
+				"i2s-controller missing or invalid in DT\n");
+				return -EINVAL;
+		}
+
+		mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute", GPIOD_OUT_LOW);
+		if (IS_ERR(mute_gpio)){
+			dev_err(&pdev->dev, "mute gpio not found in dt overlay\n");
+			return PTR_ERR(mute_gpio);
+		}
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev, card);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+	return ret;
+}
+
+static const struct of_device_id audioinjector_isolated_of_match[] = {
+	{ .compatible = "ai,audioinjector-isolated-soundcard", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, audioinjector_isolated_of_match);
+
+static struct platform_driver audioinjector_isolated_driver = {
+	.driver	= {
+		.name			= "audioinjector-isolated",
+		.owner			= THIS_MODULE,
+		.of_match_table = audioinjector_isolated_of_match,
+	},
+	.probe	= audioinjector_isolated_probe,
+};
+
+module_platform_driver(audioinjector_isolated_driver);
+MODULE_AUTHOR("Matt Flax <flatmax@flatmax.org>");
+MODULE_DESCRIPTION("AudioInjector.net isolated Soundcard");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:audioinjector-isolated-soundcard");
diff --git a/sound/soc/bcm/audioinjector-octo-soundcard.c b/sound/soc/bcm/audioinjector-octo-soundcard.c
new file mode 100644
index 000000000000..e9521d0c60a2
--- /dev/null
+++ b/sound/soc/bcm/audioinjector-octo-soundcard.c
@@ -0,0 +1,347 @@
+/*
+ * ASoC Driver for AudioInjector Pi octo channel soundcard (hat)
+ *
+ *  Created on: 27-October-2016
+ *      Author: flatmax@flatmax.org
+ *              based on audioinjector-pi-soundcard.c
+ *
+ * Copyright (C) 2016 Flatmax Pty. Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/gpio/consumer.h>
+
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/control.h>
+
+static struct gpio_descs *mult_gpios;
+static struct gpio_desc *codec_rst_gpio;
+static unsigned int audioinjector_octo_rate;
+static bool non_stop_clocks;
+
+static const unsigned int audioinjector_octo_rates[] = {
+	96000, 48000, 32000, 24000, 16000, 8000, 88200, 44100, 29400, 22050, 14700,
+};
+
+static struct snd_pcm_hw_constraint_list audioinjector_octo_constraints = {
+	.list = audioinjector_octo_rates,
+	.count = ARRAY_SIZE(audioinjector_octo_rates),
+};
+
+static int audioinjector_octo_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+	return snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), 64);
+}
+
+static int audioinjector_octo_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	snd_soc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = 8;
+	snd_soc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = 8;
+	snd_soc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = 8;
+	snd_soc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = 8;
+	snd_soc_rtd_to_codec(rtd, 0)->driver->capture.channels_max = 8;
+
+	snd_pcm_hw_constraint_list(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_RATE,
+				&audioinjector_octo_constraints);
+
+	return 0;
+}
+
+static void audioinjector_octo_shutdown(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	snd_soc_rtd_to_cpu(rtd, 0)->driver->playback.channels_min = 2;
+	snd_soc_rtd_to_cpu(rtd, 0)->driver->playback.channels_max = 2;
+	snd_soc_rtd_to_cpu(rtd, 0)->driver->capture.channels_min = 2;
+	snd_soc_rtd_to_cpu(rtd, 0)->driver->capture.channels_max = 2;
+	snd_soc_rtd_to_codec(rtd, 0)->driver->capture.channels_max = 6;
+}
+
+static int audioinjector_octo_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+	// set codec DAI configuration
+	int ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_codec(rtd, 0),
+			SND_SOC_DAIFMT_CBS_CFS|SND_SOC_DAIFMT_DSP_A|
+			SND_SOC_DAIFMT_NB_NF);
+	if (ret < 0)
+		return ret;
+
+	// set cpu DAI configuration
+	ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_cpu(rtd, 0),
+			SND_SOC_DAIFMT_CBM_CFM|SND_SOC_DAIFMT_I2S|
+			SND_SOC_DAIFMT_NB_NF);
+	if (ret < 0)
+		return ret;
+
+	audioinjector_octo_rate = params_rate(params);
+
+	// Set the correct sysclock for the codec
+	switch (audioinjector_octo_rate) {
+	case 96000:
+	case 48000:
+		return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 49152000,
+									0);
+		break;
+	case 24000:
+		return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 49152000/2,
+									0);
+		break;
+	case 32000:
+	case 16000:
+		return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 49152000/3,
+									0);
+		break;
+	case 8000:
+		return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 49152000/6,
+									0);
+		break;
+	case 88200:
+	case 44100:
+		return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 45185400,
+									0);
+		break;
+	case 22050:
+		return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 45185400/2,
+									0);
+		break;
+	case 29400:
+	case 14700:
+		return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), 0, 45185400/3,
+									0);
+		break;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int audioinjector_octo_trigger(struct snd_pcm_substream *substream,
+								int cmd){
+	DECLARE_BITMAP(mult, 4);
+
+	memset(mult, 0, sizeof(mult));
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (!non_stop_clocks)
+			break;
+		fallthrough;
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		switch (audioinjector_octo_rate) {
+		case 96000:
+			__assign_bit(3, mult, 1);
+			fallthrough;
+		case 88200:
+			__assign_bit(1, mult, 1);
+			__assign_bit(2, mult, 1);
+			break;
+		case 48000:
+			__assign_bit(3, mult, 1);
+			fallthrough;
+		case 44100:
+			__assign_bit(2, mult, 1);
+			break;
+		case 32000:
+			__assign_bit(3, mult, 1);
+			fallthrough;
+		case 29400:
+			__assign_bit(0, mult, 1);
+			__assign_bit(1, mult, 1);
+			break;
+		case 24000:
+			__assign_bit(3, mult, 1);
+			fallthrough;
+		case 22050:
+			__assign_bit(1, mult, 1);
+			break;
+		case 16000:
+			__assign_bit(3, mult, 1);
+			fallthrough;
+		case 14700:
+			__assign_bit(0, mult, 1);
+			break;
+		case 8000:
+			__assign_bit(3, mult, 1);
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+	gpiod_set_array_value_cansleep(mult_gpios->ndescs, mult_gpios->desc,
+				       NULL, mult);
+
+	return 0;
+}
+
+static struct snd_soc_ops audioinjector_octo_ops = {
+	.startup	= audioinjector_octo_startup,
+	.shutdown	= audioinjector_octo_shutdown,
+	.hw_params = audioinjector_octo_hw_params,
+	.trigger = audioinjector_octo_trigger,
+};
+
+SND_SOC_DAILINK_DEFS(audioinjector_octo,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "cs42448")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link audioinjector_octo_dai[] = {
+	{
+		.name = "AudioInjector Octo",
+		.stream_name = "AudioInject-HIFI",
+		.ops = &audioinjector_octo_ops,
+		.init = audioinjector_octo_dai_init,
+		.symmetric_rate = 1,
+		.symmetric_channels = 1,
+		SND_SOC_DAILINK_REG(audioinjector_octo),
+	},
+};
+
+static const struct snd_soc_dapm_widget audioinjector_octo_widgets[] = {
+	SND_SOC_DAPM_OUTPUT("OUTPUTS0"),
+	SND_SOC_DAPM_OUTPUT("OUTPUTS1"),
+	SND_SOC_DAPM_OUTPUT("OUTPUTS2"),
+	SND_SOC_DAPM_OUTPUT("OUTPUTS3"),
+	SND_SOC_DAPM_INPUT("INPUTS0"),
+	SND_SOC_DAPM_INPUT("INPUTS1"),
+	SND_SOC_DAPM_INPUT("INPUTS2"),
+};
+
+static const struct snd_soc_dapm_route audioinjector_octo_route[] = {
+	/* Balanced outputs */
+	{"OUTPUTS0", NULL, "AOUT1L"},
+	{"OUTPUTS0", NULL, "AOUT1R"},
+	{"OUTPUTS1", NULL, "AOUT2L"},
+	{"OUTPUTS1", NULL, "AOUT2R"},
+	{"OUTPUTS2", NULL, "AOUT3L"},
+	{"OUTPUTS2", NULL, "AOUT3R"},
+	{"OUTPUTS3", NULL, "AOUT4L"},
+	{"OUTPUTS3", NULL, "AOUT4R"},
+
+	/* Balanced inputs */
+	{"AIN1L", NULL, "INPUTS0"},
+	{"AIN1R", NULL, "INPUTS0"},
+	{"AIN2L", NULL, "INPUTS1"},
+	{"AIN2R", NULL, "INPUTS1"},
+	{"AIN3L", NULL, "INPUTS2"},
+	{"AIN3R", NULL, "INPUTS2"},
+};
+
+static struct snd_soc_card snd_soc_audioinjector_octo = {
+	.name = "audioinjector-octo-soundcard",
+	.owner = THIS_MODULE,
+	.dai_link = audioinjector_octo_dai,
+	.num_links = ARRAY_SIZE(audioinjector_octo_dai),
+
+	.dapm_widgets = audioinjector_octo_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(audioinjector_octo_widgets),
+	.dapm_routes = audioinjector_octo_route,
+	.num_dapm_routes = ARRAY_SIZE(audioinjector_octo_route),
+};
+
+static int audioinjector_octo_probe(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = &snd_soc_audioinjector_octo;
+	int ret;
+
+	card->dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct snd_soc_dai_link *dai = &audioinjector_octo_dai[0];
+		struct device_node *i2s_node =
+					of_parse_phandle(pdev->dev.of_node,
+							"i2s-controller", 0);
+		struct device_node *codec_node =
+					of_parse_phandle(pdev->dev.of_node,
+								"codec", 0);
+
+		mult_gpios = devm_gpiod_get_array_optional(&pdev->dev, "mult",
+								GPIOD_OUT_LOW);
+		if (IS_ERR(mult_gpios))
+			return PTR_ERR(mult_gpios);
+
+		codec_rst_gpio = devm_gpiod_get_optional(&pdev->dev, "reset",
+								GPIOD_OUT_LOW);
+		if (IS_ERR(codec_rst_gpio))
+			return PTR_ERR(codec_rst_gpio);
+
+		non_stop_clocks = of_property_read_bool(pdev->dev.of_node, "non-stop-clocks");
+
+		if (codec_rst_gpio)
+			gpiod_set_value(codec_rst_gpio, 1);
+		msleep(500);
+		if (codec_rst_gpio)
+			gpiod_set_value(codec_rst_gpio, 0);
+		msleep(500);
+		if (codec_rst_gpio)
+			gpiod_set_value(codec_rst_gpio, 1);
+		msleep(500);
+
+		if (i2s_node && codec_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+			dai->codecs->name = NULL;
+			dai->codecs->of_node = codec_node;
+		} else
+			if (!i2s_node) {
+				dev_err(&pdev->dev,
+				"i2s-controller missing or invalid in DT\n");
+				return -EINVAL;
+			} else {
+				dev_err(&pdev->dev,
+				"Property 'codec' missing or invalid\n");
+				return -EINVAL;
+			}
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev, card);
+	if (ret != 0)
+		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
+	return ret;
+}
+
+static const struct of_device_id audioinjector_octo_of_match[] = {
+	{ .compatible = "ai,audioinjector-octo-soundcard", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, audioinjector_octo_of_match);
+
+static struct platform_driver audioinjector_octo_driver = {
+	.driver	= {
+		.name			= "audioinjector-octo",
+		.owner			= THIS_MODULE,
+		.of_match_table = audioinjector_octo_of_match,
+	},
+	.probe	= audioinjector_octo_probe,
+};
+
+module_platform_driver(audioinjector_octo_driver);
+MODULE_AUTHOR("Matt Flax <flatmax@flatmax.org>");
+MODULE_DESCRIPTION("AudioInjector.net octo Soundcard");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:audioinjector-octo-soundcard");
diff --git a/sound/soc/bcm/audioinjector-pi-soundcard.c b/sound/soc/bcm/audioinjector-pi-soundcard.c
new file mode 100644
index 000000000000..23958e5bf28f
--- /dev/null
+++ b/sound/soc/bcm/audioinjector-pi-soundcard.c
@@ -0,0 +1,190 @@
+/*
+ * ASoC Driver for AudioInjector Pi add on soundcard
+ *
+ *  Created on: 13-May-2016
+ *      Author: flatmax@flatmax.org
+ *              based on code by  Cliff Cai <Cliff.Cai@analog.com> for the ssm2602 machine blackfin.
+ *              with help from Lars-Peter Clausen for simplifying the original code to use the dai_fmt field.
+ *		i2s_node code taken from the other sound/soc/bcm machine drivers.
+ *
+ * Copyright (C) 2016 Flatmax Pty. Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/control.h>
+
+#include "../codecs/wm8731.h"
+
+static const unsigned int bcm2835_rates_12000000[] = {
+	8000, 16000, 32000, 44100, 48000, 96000, 88200,
+};
+
+static struct snd_pcm_hw_constraint_list bcm2835_constraints_12000000 = {
+	.list = bcm2835_rates_12000000,
+	.count = ARRAY_SIZE(bcm2835_rates_12000000),
+};
+
+static int snd_audioinjector_pi_soundcard_startup(struct snd_pcm_substream *substream)
+{
+	/* Setup constraints, because there is a 12 MHz XTAL on the board */
+	snd_pcm_hw_constraint_list(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_RATE,
+				&bcm2835_constraints_12000000);
+	return 0;
+}
+
+static int snd_audioinjector_pi_soundcard_hw_params(struct snd_pcm_substream *substream,
+				       struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+	switch (params_rate(params)){
+		case 8000:
+			return snd_soc_dai_set_bclk_ratio(cpu_dai, 1);
+		case 16000:
+			return snd_soc_dai_set_bclk_ratio(cpu_dai, 750);
+		case 32000:
+			return snd_soc_dai_set_bclk_ratio(cpu_dai, 375);
+		case 44100:
+			return snd_soc_dai_set_bclk_ratio(cpu_dai, 272);
+		case 48000:
+			return snd_soc_dai_set_bclk_ratio(cpu_dai, 250);
+		case 88200:
+			return snd_soc_dai_set_bclk_ratio(cpu_dai, 136);
+		case 96000:
+			return snd_soc_dai_set_bclk_ratio(cpu_dai, 125);
+		default:
+			return snd_soc_dai_set_bclk_ratio(cpu_dai, 125);
+	}
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_audioinjector_pi_soundcard_ops = {
+	.startup = snd_audioinjector_pi_soundcard_startup,
+	.hw_params = snd_audioinjector_pi_soundcard_hw_params,
+};
+
+static int audioinjector_pi_soundcard_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+	return snd_soc_dai_set_sysclk(snd_soc_rtd_to_codec(rtd, 0), WM8731_SYSCLK_XTAL, 12000000, SND_SOC_CLOCK_IN);
+}
+
+SND_SOC_DAILINK_DEFS(audioinjector_pi,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("wm8731.1-001a", "wm8731-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2835-i2s.0")));
+
+static struct snd_soc_dai_link audioinjector_pi_soundcard_dai[] = {
+	{
+		.name = "AudioInjector audio",
+		.stream_name = "AudioInjector audio",
+		.ops = &snd_audioinjector_pi_soundcard_ops,
+		.init = audioinjector_pi_soundcard_dai_init,
+		.dai_fmt = SND_SOC_DAIFMT_CBM_CFM|SND_SOC_DAIFMT_I2S|SND_SOC_DAIFMT_NB_NF,
+		SND_SOC_DAILINK_REG(audioinjector_pi),
+	},
+};
+
+static const struct snd_soc_dapm_widget wm8731_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_SPK("Ext Spk", NULL),
+	SND_SOC_DAPM_LINE("Line In Jacks", NULL),
+	SND_SOC_DAPM_MIC("Microphone", NULL),
+};
+
+static const struct snd_soc_dapm_route audioinjector_audio_map[] = {
+	/* headphone connected to LHPOUT, RHPOUT */
+	{"Headphone Jack", NULL, "LHPOUT"},
+	{"Headphone Jack", NULL, "RHPOUT"},
+
+	/* speaker connected to LOUT, ROUT */
+	{"Ext Spk", NULL, "ROUT"},
+	{"Ext Spk", NULL, "LOUT"},
+
+	/* line inputs */
+	{"Line In Jacks", NULL, "Line Input"},
+
+	/* mic is connected to Mic Jack, with WM8731 Mic Bias */
+	{"Microphone", NULL, "Mic Bias"},
+};
+
+static struct snd_soc_card snd_soc_audioinjector = {
+	.name = "audioinjector-pi-soundcard",
+	.owner = THIS_MODULE,
+	.dai_link = audioinjector_pi_soundcard_dai,
+	.num_links = ARRAY_SIZE(audioinjector_pi_soundcard_dai),
+
+	.dapm_widgets = wm8731_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(wm8731_dapm_widgets),
+	.dapm_routes = audioinjector_audio_map,
+	.num_dapm_routes = ARRAY_SIZE(audioinjector_audio_map),
+};
+
+static int audioinjector_pi_soundcard_probe(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = &snd_soc_audioinjector;
+	int ret;
+	
+	card->dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct snd_soc_dai_link *dai = &audioinjector_pi_soundcard_dai[0];
+		struct device_node *i2s_node = of_parse_phandle(pdev->dev.of_node,
+								"i2s-controller", 0);
+
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		} else
+			if (!dai->cpus->of_node) {
+				dev_err(&pdev->dev, "Property 'i2s-controller' missing or invalid\n");
+				return -EINVAL;
+			}
+	}
+
+	if ((ret = devm_snd_soc_register_card(&pdev->dev, card)))
+		return dev_err_probe(&pdev->dev, ret, "%s\n", __func__);
+
+	dev_info(&pdev->dev, "successfully loaded\n");
+
+	return ret;
+}
+
+static const struct of_device_id audioinjector_pi_soundcard_of_match[] = {
+	{ .compatible = "ai,audioinjector-pi-soundcard", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, audioinjector_pi_soundcard_of_match);
+
+static struct platform_driver audioinjector_pi_soundcard_driver = {
+       .driver         = {
+		.name   = "audioinjector-stereo",
+		.owner  = THIS_MODULE,
+		.of_match_table = audioinjector_pi_soundcard_of_match,
+       },
+       .probe          = audioinjector_pi_soundcard_probe,
+};
+
+module_platform_driver(audioinjector_pi_soundcard_driver);
+MODULE_AUTHOR("Matt Flax <flatmax@flatmax.org>");
+MODULE_DESCRIPTION("AudioInjector.net Pi Soundcard");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:audioinjector-pi-soundcard");
+
diff --git a/sound/soc/bcm/audiosense-pi.c b/sound/soc/bcm/audiosense-pi.c
new file mode 100644
index 000000000000..200bb76df2cc
--- /dev/null
+++ b/sound/soc/bcm/audiosense-pi.c
@@ -0,0 +1,247 @@
+/*
+ * ASoC Driver for AudioSense add on soundcard
+ * Author:
+ *	Bhargav A K <anur.bhargav@gmail.com>
+ *	Copyright 2017
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/control.h>
+
+#include <sound/tlv320aic32x4.h>
+#include "../codecs/tlv320aic32x4.h"
+
+#define AIC32X4_SYSCLK_XTAL	0x00
+
+/*
+ * Setup Codec Sample Rates and Channels
+ * Supported Rates:
+ *	8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, 96000,
+ */
+static const unsigned int audiosense_pi_rate[] = {
+	48000,
+};
+
+static struct snd_pcm_hw_constraint_list audiosense_constraints_rates = {
+	.list = audiosense_pi_rate,
+	.count = ARRAY_SIZE(audiosense_pi_rate),
+};
+
+static const unsigned int audiosense_pi_channels[] = {
+	2,
+};
+
+static struct snd_pcm_hw_constraint_list audiosense_constraints_ch = {
+	.count = ARRAY_SIZE(audiosense_pi_channels),
+	.list = audiosense_pi_channels,
+	.mask = 0,
+};
+
+/* Setup DAPM widgets and paths */
+static const struct snd_soc_dapm_widget audiosense_pi_dapm_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_LINE("Line Out", NULL),
+	SND_SOC_DAPM_LINE("Line In", NULL),
+	SND_SOC_DAPM_INPUT("CM_L"),
+	SND_SOC_DAPM_INPUT("CM_R"),
+};
+
+static const struct snd_soc_dapm_route audiosense_pi_audio_map[] = {
+	/* Line Inputs are connected to
+	 * (IN1_L | IN1_R)
+	 * (IN2_L | IN2_R)
+	 * (IN3_L | IN3_R)
+	 */
+	{"IN1_L", NULL, "Line In"},
+	{"IN1_R", NULL, "Line In"},
+	{"IN2_L", NULL, "Line In"},
+	{"IN2_R", NULL, "Line In"},
+	{"IN3_L", NULL, "Line In"},
+	{"IN3_R", NULL, "Line In"},
+
+	/* Mic is connected to IN2_L and IN2_R */
+	{"Left ADC", NULL, "Mic Bias"},
+	{"Right ADC", NULL, "Mic Bias"},
+
+	/* Headphone connected to HPL, HPR */
+	{"Headphone Jack", NULL, "HPL"},
+	{"Headphone Jack", NULL, "HPR"},
+
+	/* Speakers connected to LOL and LOR */
+	{"Line Out", NULL, "LOL"},
+	{"Line Out", NULL, "LOR"},
+};
+
+static int audiosense_pi_card_init(struct snd_soc_pcm_runtime *rtd)
+{
+	/* TODO: init of the codec specific dapm data, ignore suspend/resume */
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+
+	snd_soc_component_update_bits(component, AIC32X4_MICBIAS, 0x78,
+				      AIC32X4_MICBIAS_LDOIN |
+				      AIC32X4_MICBIAS_2075V);
+	snd_soc_component_update_bits(component, AIC32X4_PWRCFG, 0x08,
+				      AIC32X4_AVDDWEAKDISABLE);
+	snd_soc_component_update_bits(component, AIC32X4_LDOCTL, 0x01,
+				      AIC32X4_LDOCTLEN);
+
+	return 0;
+}
+
+static int audiosense_pi_card_hw_params(
+		struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params)
+{
+	int ret = 0;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+	/* Set the codec system clock, there is a 12 MHz XTAL on the board */
+	ret = snd_soc_dai_set_sysclk(codec_dai, AIC32X4_SYSCLK_XTAL,
+				     12000000, SND_SOC_CLOCK_IN);
+	if (ret) {
+		dev_err(rtd->card->dev,
+			"could not set codec driver clock params\n");
+		return ret;
+	}
+	return 0;
+}
+
+static int audiosense_pi_card_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	/*
+	 * Set codec to 48Khz Sampling, Stereo I/O and 16 bit audio
+	 */
+	runtime->hw.channels_max = 2;
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+				   &audiosense_constraints_ch);
+
+	runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE;
+	snd_pcm_hw_constraint_msbits(runtime, 0, 16, 16);
+
+
+	snd_pcm_hw_constraint_list(substream->runtime, 0,
+				   SNDRV_PCM_HW_PARAM_RATE,
+				   &audiosense_constraints_rates);
+	return 0;
+}
+
+static struct snd_soc_ops audiosense_pi_card_ops = {
+	.startup = audiosense_pi_card_startup,
+	.hw_params = audiosense_pi_card_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(audiosense_pi,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("tlv320aic32x4.1-0018", "tlv320aic32x4-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link audiosense_pi_card_dai[] = {
+	{
+		.name           = "TLV320AIC3204 Audio",
+		.stream_name    = "TLV320AIC3204 Hifi Audio",
+		.dai_fmt        = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+			SND_SOC_DAIFMT_CBM_CFM,
+		.ops            = &audiosense_pi_card_ops,
+		.init           = audiosense_pi_card_init,
+		SND_SOC_DAILINK_REG(audiosense_pi),
+	},
+};
+
+static struct snd_soc_card audiosense_pi_card = {
+	.name		= "audiosense-pi",
+	.driver_name	= "audiosense-pi",
+	.dai_link	= audiosense_pi_card_dai,
+	.owner		= THIS_MODULE,
+	.num_links	= ARRAY_SIZE(audiosense_pi_card_dai),
+	.dapm_widgets	= audiosense_pi_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(audiosense_pi_dapm_widgets),
+	.dapm_routes	= audiosense_pi_audio_map,
+	.num_dapm_routes = ARRAY_SIZE(audiosense_pi_audio_map),
+};
+
+static int audiosense_pi_card_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	struct snd_soc_card *card = &audiosense_pi_card;
+	struct snd_soc_dai_link *dai = &audiosense_pi_card_dai[0];
+	struct device_node *i2s_node = pdev->dev.of_node;
+
+	card->dev = &pdev->dev;
+
+	if (!dai) {
+		dev_err(&pdev->dev, "DAI not found. Missing or Invalid\n");
+		return -EINVAL;
+	}
+
+	i2s_node = of_parse_phandle(pdev->dev.of_node, "i2s-controller", 0);
+	if (!i2s_node) {
+		dev_err(&pdev->dev,
+			"Property 'i2s-controller' missing or invalid\n");
+		return -EINVAL;
+	}
+
+	dai->cpus->dai_name = NULL;
+	dai->cpus->of_node = i2s_node;
+	dai->platforms->name = NULL;
+	dai->platforms->of_node = i2s_node;
+
+	of_node_put(i2s_node);
+
+	ret = snd_soc_register_card(card);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+
+	return ret;
+}
+
+static void audiosense_pi_card_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+	snd_soc_unregister_card(card);
+}
+
+static const struct of_device_id audiosense_pi_card_of_match[] = {
+	{ .compatible = "as,audiosense-pi", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, audiosense_pi_card_of_match);
+
+static struct platform_driver audiosense_pi_card_driver = {
+	.driver = {
+		.name = "audiosense-snd-card",
+		.owner = THIS_MODULE,
+		.of_match_table = audiosense_pi_card_of_match,
+	},
+	.probe = audiosense_pi_card_probe,
+	.remove = audiosense_pi_card_remove,
+};
+
+module_platform_driver(audiosense_pi_card_driver);
+
+MODULE_AUTHOR("Bhargav AK <anur.bhargav@gmail.com>");
+MODULE_DESCRIPTION("ASoC Driver for TLV320AIC3204 Audio");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:audiosense-pi");
+
diff --git a/sound/soc/bcm/bcm2835-i2s.c b/sound/soc/bcm/bcm2835-i2s.c
index 9bda6499e66e..952f6e5c7536 100644
--- a/sound/soc/bcm/bcm2835-i2s.c
+++ b/sound/soc/bcm/bcm2835-i2s.c
@@ -30,7 +30,6 @@
 #include <linux/init.h>
 #include <linux/io.h>
 #include <linux/module.h>
-#include <linux/of_address.h>
 #include <linux/slab.h>
 
 #include <sound/core.h>
@@ -620,6 +619,10 @@ static int bcm2835_i2s_prepare(struct snd_pcm_substream *substream,
 	struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
 	uint32_t cs_reg;
 
+	snd_pcm_hw_constraint_minmax(substream->runtime,
+		SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 256,
+					~0);
+
 	/*
 	 * Clear both FIFOs if the one that should be started
 	 * is not empty at the moment. This should only happen
@@ -701,6 +704,10 @@ static int bcm2835_i2s_startup(struct snd_pcm_substream *substream,
 	/* Should this still be running stop it */
 	bcm2835_i2s_stop_clock(dev);
 
+	snd_pcm_hw_constraint_minmax(substream->runtime,
+				     SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+				     256, ~0);
+
 	/* Enable PCM block */
 	regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
 			BCM2835_I2S_EN, BCM2835_I2S_EN);
@@ -830,8 +837,7 @@ static int bcm2835_i2s_probe(struct platform_device *pdev)
 	struct bcm2835_i2s_dev *dev;
 	int ret;
 	void __iomem *base;
-	const __be32 *addr;
-	dma_addr_t dma_base;
+	struct resource *res;
 
 	dev = devm_kzalloc(&pdev->dev, sizeof(*dev),
 			   GFP_KERNEL);
@@ -846,7 +852,7 @@ static int bcm2835_i2s_probe(struct platform_device *pdev)
 				     "could not get clk\n");
 
 	/* Request ioarea */
-	base = devm_platform_ioremap_resource(pdev, 0);
+	base = devm_platform_get_and_ioremap_resource(pdev, 0, &res);
 	if (IS_ERR(base))
 		return PTR_ERR(base);
 
@@ -855,19 +861,11 @@ static int bcm2835_i2s_probe(struct platform_device *pdev)
 	if (IS_ERR(dev->i2s_regmap))
 		return PTR_ERR(dev->i2s_regmap);
 
-	/* Set the DMA address - we have to parse DT ourselves */
-	addr = of_get_address(pdev->dev.of_node, 0, NULL, NULL);
-	if (!addr) {
-		dev_err(&pdev->dev, "could not get DMA-register address\n");
-		return -EINVAL;
-	}
-	dma_base = be32_to_cpup(addr);
-
 	dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr =
-		dma_base + BCM2835_I2S_FIFO_A_REG;
+		res->start + BCM2835_I2S_FIFO_A_REG;
 
 	dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].addr =
-		dma_base + BCM2835_I2S_FIFO_A_REG;
+		res->start + BCM2835_I2S_FIFO_A_REG;
 
 	/* Set the bus width */
 	dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr_width =
diff --git a/sound/soc/bcm/chipdip-dac.c b/sound/soc/bcm/chipdip-dac.c
new file mode 100644
index 000000000000..ddf4f5ed90f6
--- /dev/null
+++ b/sound/soc/bcm/chipdip-dac.c
@@ -0,0 +1,275 @@
+/*
+ * ASoC Driver for ChipDip DAC
+ *
+ * Author:	Evgenij Sapunov
+ *		Copyright 2021
+ *		based on code by Milan Neskovic <info@justboom.co>
+ *		based on code by Jaikumar <jaikumar@cem-solutions.net>
+ *
+ * Thanks to Phil Elwell (pelwell) for help.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#define SR_BIT_0                  0 //sample rate bits
+#define SR_BIT_1                  1
+#define SR_BIT_2                  2
+#define BD_BIT_0                  3 //bit depth bits
+#define BD_BIT_1                  4
+
+#define SAMPLE_RATE_MASK_44_1     0
+#define SAMPLE_RATE_MASK_48       (1 << SR_BIT_0)
+#define SAMPLE_RATE_MASK_88_2     ((1 << SR_BIT_2) | (1 << SR_BIT_1))
+#define SAMPLE_RATE_MASK_96       (1 << SR_BIT_1)
+#define SAMPLE_RATE_MASK_176_4    ((1 << SR_BIT_2) | (1 << SR_BIT_1) | (1 << SR_BIT_0))
+#define SAMPLE_RATE_MASK_192      ((1 << SR_BIT_1) | (1 << SR_BIT_0))
+#define SAMPLE_RATE_MASK          ((1 << SR_BIT_2) | (1 << SR_BIT_1) | (1 << SR_BIT_0))
+
+#define BIT_DEPTH_MASK_16         0
+#define BIT_DEPTH_MASK_24         (1 << BD_BIT_0)
+#define BIT_DEPTH_MASK_32         (1 << BD_BIT_1)
+#define BIT_DEPTH_MASK            ((1 << BD_BIT_1) | (1 << BD_BIT_0))
+
+#define MUTE_ACTIVE               0
+#define MUTE_NOT_ACTIVE           1
+
+#define HW_PARAMS_GPIO_COUNT      5
+
+static struct gpio_desc *mute_gpio;
+static struct gpio_desc *sdwn_gpio;
+static struct gpio_desc *hw_params_gpios[HW_PARAMS_GPIO_COUNT];
+static int current_width;
+static int current_rate;
+
+static void snd_rpi_chipdip_dac_gpio_array_set(int value);
+static void snd_rpi_chipdip_dac_gpio_set(struct gpio_desc *gpio_item, int value);
+
+static void snd_rpi_chipdip_dac_gpio_array_set(int value)
+{
+	int i = 0;
+
+	for (i = 0; i < HW_PARAMS_GPIO_COUNT; i++)
+		snd_rpi_chipdip_dac_gpio_set(hw_params_gpios[i], ((value >> i) & 1));
+}
+
+static void snd_rpi_chipdip_dac_gpio_set(struct gpio_desc *gpio_item, int value)
+{
+	if (gpio_item)
+		gpiod_set_value_cansleep(gpio_item, value);
+}
+
+static int snd_rpi_chipdip_dac_init(struct snd_soc_pcm_runtime *rtd)
+{
+	return 0;
+}
+
+static int snd_rpi_chipdip_dac_hw_params(struct snd_pcm_substream *substream,
+					 struct snd_pcm_hw_params *params)
+{
+	int ret = 0;
+	int gpio_change_pending = 0;
+	int sample_rate_state = 0;
+	int bit_depth_state = 0;
+	int param_value = params_width(params);
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+	ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), 2 * 32);
+
+	if (current_width != param_value) {
+		current_width = param_value;
+		gpio_change_pending = 1;
+
+		switch (param_value) {
+		case 16:
+			bit_depth_state = BIT_DEPTH_MASK_16;
+			break;
+		case 24:
+			bit_depth_state = BIT_DEPTH_MASK_24;
+			break;
+		case 32:
+			bit_depth_state = BIT_DEPTH_MASK_32;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	param_value = params_rate(params);
+	if (current_rate != param_value) {
+		current_rate = param_value;
+		gpio_change_pending = 1;
+
+		switch (param_value) {
+		case 44100:
+			sample_rate_state = SAMPLE_RATE_MASK_44_1;
+			break;
+		case 48000:
+			sample_rate_state = SAMPLE_RATE_MASK_48;
+			break;
+		case 88200:
+			sample_rate_state = SAMPLE_RATE_MASK_88_2;
+			break;
+		case 96000:
+			sample_rate_state = SAMPLE_RATE_MASK_96;
+			break;
+		case 176400:
+			sample_rate_state = SAMPLE_RATE_MASK_176_4;
+			break;
+		case 192000:
+			sample_rate_state = SAMPLE_RATE_MASK_192;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	if (gpio_change_pending) {
+		snd_rpi_chipdip_dac_gpio_set(mute_gpio, MUTE_ACTIVE);
+		snd_rpi_chipdip_dac_gpio_array_set(bit_depth_state | sample_rate_state);
+		msleep(300);
+		snd_rpi_chipdip_dac_gpio_set(mute_gpio, MUTE_NOT_ACTIVE);
+	}
+
+	return ret;
+}
+
+static int snd_rpi_chipdip_dac_startup(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+static void snd_rpi_chipdip_dac_shutdown(struct snd_pcm_substream *substream)
+{
+
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_rpi_chipdip_dac_ops = {
+	.hw_params = snd_rpi_chipdip_dac_hw_params,
+	.startup = snd_rpi_chipdip_dac_startup,
+	.shutdown = snd_rpi_chipdip_dac_shutdown,
+};
+
+SND_SOC_DAILINK_DEFS(hifi,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("spdif-transmitter", "dit-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_chipdip_dac_dai[] = {
+{
+	.name		= "ChipDip DAC",
+	.stream_name	= "ChipDip DAC HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBM_CFM,
+	.ops		= &snd_rpi_chipdip_dac_ops,
+	.init		= snd_rpi_chipdip_dac_init,
+	SND_SOC_DAILINK_REG(hifi),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_chipdip_dac = {
+	.name         = "ChipDipDAC",
+	.driver_name  = "ChipdipDac",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_rpi_chipdip_dac_dai,
+	.num_links    = ARRAY_SIZE(snd_rpi_chipdip_dac_dai),
+};
+
+static int snd_rpi_chipdip_dac_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	int i = 0;
+
+	snd_rpi_chipdip_dac.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai = &snd_rpi_chipdip_dac_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+					"i2s-controller", 0);
+
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		}
+	}
+
+	hw_params_gpios[SR_BIT_0] = devm_gpiod_get_optional(&pdev->dev, "sr0", GPIOD_OUT_LOW);
+	hw_params_gpios[SR_BIT_1] = devm_gpiod_get_optional(&pdev->dev, "sr1", GPIOD_OUT_LOW);
+	hw_params_gpios[SR_BIT_2] = devm_gpiod_get_optional(&pdev->dev, "sr2", GPIOD_OUT_LOW);
+	hw_params_gpios[BD_BIT_0] = devm_gpiod_get_optional(&pdev->dev, "res0", GPIOD_OUT_LOW);
+	hw_params_gpios[BD_BIT_1] = devm_gpiod_get_optional(&pdev->dev, "res1", GPIOD_OUT_LOW);
+	mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute", GPIOD_OUT_LOW);
+	sdwn_gpio = devm_gpiod_get_optional(&pdev->dev, "sdwn", GPIOD_OUT_HIGH);
+
+	for (i = 0; i < HW_PARAMS_GPIO_COUNT; i++) {
+		if (IS_ERR(hw_params_gpios[i])) {
+			ret = PTR_ERR(hw_params_gpios[i]);
+			dev_err(&pdev->dev, "failed to get hw_params gpio: %d\n", ret);
+			return ret;
+		}
+	}
+
+	if (IS_ERR(mute_gpio)) {
+		ret = PTR_ERR(mute_gpio);
+		dev_err(&pdev->dev, "failed to get mute gpio: %d\n", ret);
+		return ret;
+	}
+
+	if (IS_ERR(sdwn_gpio)) {
+		ret = PTR_ERR(sdwn_gpio);
+		dev_err(&pdev->dev, "failed to get sdwn gpio: %d\n", ret);
+		return ret;
+	}
+
+	snd_rpi_chipdip_dac_gpio_set(sdwn_gpio, 1);
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_chipdip_dac);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+
+	return ret;
+}
+
+static const struct of_device_id snd_rpi_chipdip_dac_of_match[] = {
+	{ .compatible = "chipdip,chipdip-dac", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, snd_rpi_chipdip_dac_of_match);
+
+static struct platform_driver snd_rpi_chipdip_dac_driver = {
+	.driver = {
+		.name   = "snd-rpi-chipdip-dac",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_rpi_chipdip_dac_of_match,
+	},
+	.probe          = snd_rpi_chipdip_dac_probe,
+};
+
+module_platform_driver(snd_rpi_chipdip_dac_driver);
+
+MODULE_AUTHOR("Evgenij Sapunov <evgenij.sapunov@chipdip.ru>");
+MODULE_DESCRIPTION("ASoC Driver for ChipDip DAC");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/dacberry400.c b/sound/soc/bcm/dacberry400.c
new file mode 100644
index 000000000000..4a2520d077d2
--- /dev/null
+++ b/sound/soc/bcm/dacberry400.c
@@ -0,0 +1,258 @@
+/*
+ * ASoC Driver for Dacberry400 soundcard
+ * Author:
+ *      Ashish Vara<ashishhvara@gmail.com>
+ *      Copyright 2022
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <linux/i2c.h>
+#include <linux/acpi.h>
+#include <linux/slab.h>
+#include "../sound/soc/codecs/tlv320aic3x.h"
+
+static const struct snd_kcontrol_new dacberry400_controls[] = {
+	SOC_DAPM_PIN_SWITCH("MIC Jack"),
+	SOC_DAPM_PIN_SWITCH("Line In"),
+	SOC_DAPM_PIN_SWITCH("Line Out"),
+	SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+};
+
+static const struct snd_soc_dapm_widget dacberry400_widgets[] = {
+	SND_SOC_DAPM_HP("Headphone Jack", NULL),
+	SND_SOC_DAPM_MIC("MIC Jack", NULL),
+	SND_SOC_DAPM_LINE("Line In", NULL),
+	SND_SOC_DAPM_LINE("Line Out", NULL),
+};
+
+static const struct snd_soc_dapm_route dacberry400_audio_map[] = {
+	{"Headphone Jack", NULL, "HPLOUT"},
+	{"Headphone Jack", NULL, "HPROUT"},
+
+	{"LINE1L", NULL, "Line In"},
+	{"LINE1R", NULL, "Line In"},
+
+	{"Line Out", NULL, "LLOUT"},
+	{"Line Out", NULL, "RLOUT"},
+
+	{"MIC3L", NULL, "MIC Jack"},
+	{"MIC3R", NULL, "MIC Jack"},
+};
+
+static int snd_rpi_dacberry400_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_component *component = codec_dai->component;
+	int ret;
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, 2, 12000000,
+					SND_SOC_CLOCK_OUT);
+
+	if (ret && ret != -ENOTSUPP)
+		goto err;
+
+	snd_soc_component_write(component, HPRCOM_CFG, 0x20);
+	snd_soc_component_write(component, DACL1_2_HPLOUT_VOL, 0x80);
+	snd_soc_component_write(component, DACR1_2_HPROUT_VOL, 0x80);
+err:
+	return ret;
+}
+
+static int snd_rpi_dacberry400_set_bias_level(struct snd_soc_card *card,
+	struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level)
+{
+	struct snd_soc_pcm_runtime *rtd;
+	struct snd_soc_dai *codec_dai;
+	struct snd_soc_component *component;
+	struct dacberry_priv *aic3x;
+	u8 hpcom_reg = 0;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+	codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	component = codec_dai->component;
+	aic3x = snd_soc_component_get_drvdata(component);
+	if (dapm->dev != codec_dai->dev)
+		return 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_PREPARE:
+		if (dapm->bias_level != SND_SOC_BIAS_STANDBY)
+			break;
+		/* UNMUTE ADC/DAC */
+		hpcom_reg = snd_soc_component_read(component, HPLCOM_CFG);
+		snd_soc_component_write(component, HPLCOM_CFG, hpcom_reg | 0x20);
+		snd_soc_component_write(component, LINE1R_2_RADC_CTRL, 0x04);
+		snd_soc_component_write(component, LINE1L_2_LADC_CTRL, 0x04);
+		snd_soc_component_write(component, LADC_VOL, 0x00);
+		snd_soc_component_write(component, RADC_VOL, 0x00);
+		pr_info("%s: unmute ADC/DAC\n", __func__);
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		if (dapm->bias_level != SND_SOC_BIAS_PREPARE)
+			break;
+		/* MUTE ADC/DAC */
+		snd_soc_component_write(component, LDAC_VOL, 0x80);
+		snd_soc_component_write(component, RDAC_VOL, 0x80);
+		snd_soc_component_write(component, LADC_VOL, 0x80);
+		snd_soc_component_write(component, RADC_VOL, 0x80);
+		snd_soc_component_write(component, LINE1R_2_RADC_CTRL, 0x00);
+		snd_soc_component_write(component, LINE1L_2_LADC_CTRL, 0x00);
+		snd_soc_component_write(component, HPLCOM_CFG, 0x00);
+		pr_info("%s: mute ADC/DAC\n", __func__);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int snd_rpi_dacberry400_hw_params(struct snd_pcm_substream *substream,
+					   struct snd_pcm_hw_params *params)
+{
+	int ret = 0;
+	u8 data;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	struct snd_soc_component *component = codec_dai->component;
+	int fsref = (params_rate(params) % 11025 == 0) ? 44100 : 48000;
+	int channels = params_channels(params);
+	int width = 32;
+	u8 clock = 0;
+
+	data = (LDAC2LCH | RDAC2RCH);
+	data |= (fsref == 44100) ? FSREF_44100 : FSREF_48000;
+	if (params_rate(params) >= 64000)
+		data |= DUAL_RATE_MODE;
+	ret = snd_soc_component_write(component, 0x7, data);
+	width = params_width(params);
+
+	clock = snd_soc_component_read(component, 2);
+
+	ret = snd_soc_dai_set_bclk_ratio(cpu_dai, channels*width);
+
+	return ret;
+}
+
+static const struct snd_soc_ops snd_rpi_dacberry400_ops = {
+	.hw_params = snd_rpi_dacberry400_hw_params,
+};
+
+
+SND_SOC_DAILINK_DEFS(rpi_dacberry400,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2835-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("tlv320aic3x.1-0018", "tlv320aic3x-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2835-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_dacberry400_dai[] = {
+{
+	.dai_fmt		= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+					SND_SOC_DAIFMT_CBS_CFS,
+	.init			= snd_rpi_dacberry400_init,
+	.ops			= &snd_rpi_dacberry400_ops,
+	.symmetric_rate		= 1,
+	SND_SOC_DAILINK_REG(rpi_dacberry400),
+},
+};
+
+static struct snd_soc_card snd_rpi_dacberry400 = {
+	.owner			= THIS_MODULE,
+	.dai_link		= snd_rpi_dacberry400_dai,
+	.num_links		= ARRAY_SIZE(snd_rpi_dacberry400_dai),
+	.controls		= dacberry400_controls,
+	.num_controls		= ARRAY_SIZE(dacberry400_controls),
+	.dapm_widgets		= dacberry400_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(dacberry400_widgets),
+	.dapm_routes		= dacberry400_audio_map,
+	.num_dapm_routes	= ARRAY_SIZE(dacberry400_audio_map),
+	.set_bias_level		= snd_rpi_dacberry400_set_bias_level,
+};
+
+static int snd_rpi_dacberry400_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	snd_rpi_dacberry400.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_card *card = &snd_rpi_dacberry400;
+		struct snd_soc_dai_link *dai = &snd_rpi_dacberry400_dai[0];
+
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+					    "i2s-controller", 0);
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+			of_node_put(i2s_node);
+		}
+
+		if (of_property_read_string(pdev->dev.of_node, "card_name",
+					    &card->name))
+			card->name = "tlvaudioCODEC";
+
+		if (of_property_read_string(pdev->dev.of_node, "dai_name",
+					    &dai->name))
+			dai->name = "tlvaudio CODEC";
+
+	}
+
+	ret = snd_soc_register_card(&snd_rpi_dacberry400);
+	if (ret) {
+		if (ret != -EPROBE_DEFER)
+			dev_err(&pdev->dev,
+				"snd_soc_register_card() failed: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void snd_rpi_dacberry400_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_card(&snd_rpi_dacberry400);
+}
+
+static const struct of_device_id dacberry400_match_id[] = {
+	{ .compatible = "osaelectronics,dacberry400",},
+	{},
+};
+MODULE_DEVICE_TABLE(of, dacberry400_match_id);
+
+static struct platform_driver snd_rpi_dacberry400_driver = {
+	.driver = {
+		.name = "snd-rpi-dacberry400",
+		.owner = THIS_MODULE,
+		.of_match_table = dacberry400_match_id,
+	},
+	.probe = snd_rpi_dacberry400_probe,
+	.remove = snd_rpi_dacberry400_remove,
+};
+
+module_platform_driver(snd_rpi_dacberry400_driver);
+
+MODULE_AUTHOR("Ashish Vara");
+MODULE_DESCRIPTION("Dacberry400 sound card driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:dacberry400");
+MODULE_SOFTDEP("pre: snd-soc-tlv320aic3x");
diff --git a/sound/soc/bcm/digidac1-soundcard.c b/sound/soc/bcm/digidac1-soundcard.c
new file mode 100644
index 000000000000..e1e7054534c8
--- /dev/null
+++ b/sound/soc/bcm/digidac1-soundcard.c
@@ -0,0 +1,421 @@
+/*
+ * ASoC Driver for RRA DigiDAC1
+ * Copyright 2016
+ * Author: José M. Tasende <vintage@redrocksaudio.es>
+ * based on the HifiBerry DAC driver by Florian Meier <florian.meier@koalo.de>
+ * and the Wolfson card driver by Nikesh Oswal, <Nikesh.Oswal@wolfsonmicro.com>
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <linux/regulator/consumer.h>
+
+#include "../codecs/wm8804.h"
+#include "../codecs/wm8741.h"
+
+#define WM8741_NUM_SUPPLIES 2
+
+/* codec private data */
+struct wm8741_priv {
+	struct wm8741_platform_data pdata;
+	struct regmap *regmap;
+	struct regulator_bulk_data supplies[WM8741_NUM_SUPPLIES];
+	unsigned int sysclk;
+	const struct snd_pcm_hw_constraint_list *sysclk_constraints;
+};
+
+static int samplerate = 44100;
+
+/* New Alsa Controls not exposed by original wm8741 codec driver	*/
+/* in actual driver the att. adjustment is wrong because		*/
+/* this DAC has a coarse attenuation register with 4dB steps		*/
+/* and a fine level register with 0.125dB steps				*/
+/* each register has 32 steps so combining both we have	1024 steps	*/
+/* of 0.125 dB.								*/
+/* The original level controls from driver are removed at startup	*/
+/* and replaced by the corrected ones.					*/
+/* The same wm8741 driver can be used for wm8741 and wm8742 devices	*/
+
+static const DECLARE_TLV_DB_SCALE(dac_tlv_fine, 0, 13, 0);
+static const DECLARE_TLV_DB_SCALE(dac_tlv_coarse, -12700, 400, 1);
+static const char *w8741_dither[4] = {"Off", "RPDF", "TPDF", "HPDF"};
+static const char *w8741_filter[5] = {
+		"Type 1", "Type 2", "Type 3", "Type 4", "Type 5"};
+static const char *w8741_switch[2] = {"Off", "On"};
+static const struct soc_enum w8741_enum[] = {
+SOC_ENUM_SINGLE(WM8741_MODE_CONTROL_2, 0, 4, w8741_dither),/* dithering type */
+SOC_ENUM_SINGLE(WM8741_FILTER_CONTROL, 0, 5, w8741_filter),/* filter type */
+SOC_ENUM_SINGLE(WM8741_FORMAT_CONTROL, 6, 2, w8741_switch),/* phase invert */
+SOC_ENUM_SINGLE(WM8741_VOLUME_CONTROL, 0, 2, w8741_switch),/* volume ramp */
+SOC_ENUM_SINGLE(WM8741_VOLUME_CONTROL, 3, 2, w8741_switch),/* soft mute */
+};
+
+static const struct snd_kcontrol_new w8741_snd_controls_stereo[] = {
+SOC_DOUBLE_R_TLV("DAC Fine Playback Volume", WM8741_DACLLSB_ATTENUATION,
+		WM8741_DACRLSB_ATTENUATION, 0, 31, 1, dac_tlv_fine),
+SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8741_DACLMSB_ATTENUATION,
+		WM8741_DACRMSB_ATTENUATION, 0, 31, 1, dac_tlv_coarse),
+SOC_ENUM("DAC Dither", w8741_enum[0]),
+SOC_ENUM("DAC Digital Filter", w8741_enum[1]),
+SOC_ENUM("DAC Phase Invert", w8741_enum[2]),
+SOC_ENUM("DAC Volume Ramp", w8741_enum[3]),
+SOC_ENUM("DAC Soft Mute", w8741_enum[4]),
+};
+
+static const struct snd_kcontrol_new w8741_snd_controls_mono_left[] = {
+SOC_SINGLE_TLV("DAC Fine Playback Volume", WM8741_DACLLSB_ATTENUATION,
+		0, 31, 0, dac_tlv_fine),
+SOC_SINGLE_TLV("Digital Playback Volume", WM8741_DACLMSB_ATTENUATION,
+		0, 31, 1, dac_tlv_coarse),
+SOC_ENUM("DAC Dither", w8741_enum[0]),
+SOC_ENUM("DAC Digital Filter", w8741_enum[1]),
+SOC_ENUM("DAC Phase Invert", w8741_enum[2]),
+SOC_ENUM("DAC Volume Ramp", w8741_enum[3]),
+SOC_ENUM("DAC Soft Mute", w8741_enum[4]),
+};
+
+static const struct snd_kcontrol_new w8741_snd_controls_mono_right[] = {
+SOC_SINGLE_TLV("DAC Fine Playback Volume", WM8741_DACRLSB_ATTENUATION,
+	0, 31, 0, dac_tlv_fine),
+SOC_SINGLE_TLV("Digital Playback Volume", WM8741_DACRMSB_ATTENUATION,
+	0, 31, 1, dac_tlv_coarse),
+SOC_ENUM("DAC Dither", w8741_enum[0]),
+SOC_ENUM("DAC Digital Filter", w8741_enum[1]),
+SOC_ENUM("DAC Phase Invert", w8741_enum[2]),
+SOC_ENUM("DAC Volume Ramp", w8741_enum[3]),
+SOC_ENUM("DAC Soft Mute", w8741_enum[4]),
+};
+
+static int w8741_add_controls(struct snd_soc_component *component)
+{
+	struct wm8741_priv *wm8741 = snd_soc_component_get_drvdata(component);
+
+	switch (wm8741->pdata.diff_mode) {
+	case WM8741_DIFF_MODE_STEREO:
+	case WM8741_DIFF_MODE_STEREO_REVERSED:
+		snd_soc_add_component_controls(component,
+				w8741_snd_controls_stereo,
+				ARRAY_SIZE(w8741_snd_controls_stereo));
+		break;
+	case WM8741_DIFF_MODE_MONO_LEFT:
+		snd_soc_add_component_controls(component,
+				w8741_snd_controls_mono_left,
+				ARRAY_SIZE(w8741_snd_controls_mono_left));
+		break;
+	case WM8741_DIFF_MODE_MONO_RIGHT:
+		snd_soc_add_component_controls(component,
+				w8741_snd_controls_mono_right,
+				ARRAY_SIZE(w8741_snd_controls_mono_right));
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int digidac1_soundcard_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_card *card = rtd->card;
+	struct snd_soc_pcm_runtime *wm8741_rtd;
+	struct snd_soc_component *wm8741_component;
+	struct snd_card *sound_card = card->snd_card;
+	struct snd_kcontrol *kctl;
+	int ret;
+
+	wm8741_rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]);
+	if (!wm8741_rtd) {
+		dev_warn(card->dev, "digidac1_soundcard_init: couldn't get wm8741 rtd\n");
+		return -EFAULT;
+	}
+	wm8741_component = snd_soc_rtd_to_codec(wm8741_rtd, 0)->component;
+	ret = w8741_add_controls(wm8741_component);
+	if (ret < 0)
+		dev_warn(card->dev, "Failed to add new wm8741 controls: %d\n",
+		ret);
+
+	/* enable TX output */
+	snd_soc_component_update_bits(component, WM8804_PWRDN, 0x4, 0x0);
+
+	kctl = snd_soc_card_get_kcontrol(card,
+		"Playback Volume");
+	if (kctl) {
+		kctl->vd[0].access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
+		snd_ctl_remove(sound_card, kctl);
+		}
+	kctl = snd_soc_card_get_kcontrol(card,
+		"Fine Playback Volume");
+	if (kctl) {
+		kctl->vd[0].access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
+		snd_ctl_remove(sound_card, kctl);
+		}
+	return 0;
+}
+
+static int digidac1_soundcard_startup(struct snd_pcm_substream *substream)
+{
+	/* turn on wm8804 digital output */
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_card *card = rtd->card;
+	struct snd_soc_pcm_runtime *wm8741_rtd;
+	struct snd_soc_component *wm8741_component;
+
+	snd_soc_component_update_bits(component, WM8804_PWRDN, 0x3c, 0x00);
+	wm8741_rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]);
+	if (!wm8741_rtd) {
+		dev_warn(card->dev, "digidac1_soundcard_startup: couldn't get WM8741 rtd\n");
+		return -EFAULT;
+	}
+	wm8741_component = snd_soc_rtd_to_codec(wm8741_rtd, 0)->component;
+
+	/* latch wm8741 level */
+	snd_soc_component_update_bits(wm8741_component, WM8741_DACLLSB_ATTENUATION,
+		WM8741_UPDATELL, WM8741_UPDATELL);
+	snd_soc_component_update_bits(wm8741_component, WM8741_DACLMSB_ATTENUATION,
+		WM8741_UPDATELM, WM8741_UPDATELM);
+	snd_soc_component_update_bits(wm8741_component, WM8741_DACRLSB_ATTENUATION,
+		WM8741_UPDATERL, WM8741_UPDATERL);
+	snd_soc_component_update_bits(wm8741_component, WM8741_DACRMSB_ATTENUATION,
+		WM8741_UPDATERM, WM8741_UPDATERM);
+
+	return 0;
+}
+
+static void digidac1_soundcard_shutdown(struct snd_pcm_substream *substream)
+{
+	/* turn off wm8804 digital output */
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+
+	snd_soc_component_update_bits(component, WM8804_PWRDN, 0x3c, 0x3c);
+}
+
+static int digidac1_soundcard_hw_params(struct snd_pcm_substream *substream,
+				       struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	struct snd_soc_card *card = rtd->card;
+	struct snd_soc_pcm_runtime *wm8741_rtd;
+	struct snd_soc_component *wm8741_component;
+
+	int sysclk = 27000000;
+	long mclk_freq = 0;
+	int mclk_div = 1;
+	int sampling_freq = 1;
+	int ret;
+
+	wm8741_rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[1]);
+	if (!wm8741_rtd) {
+		dev_warn(card->dev, "digidac1_soundcard_hw_params: couldn't get WM8741 rtd\n");
+		return -EFAULT;
+	}
+	wm8741_component = snd_soc_rtd_to_codec(wm8741_rtd, 0)->component;
+	samplerate = params_rate(params);
+
+	if (samplerate <= 96000) {
+		mclk_freq = samplerate*256;
+		mclk_div = WM8804_MCLKDIV_256FS;
+	} else {
+		mclk_freq = samplerate*128;
+		mclk_div = WM8804_MCLKDIV_128FS;
+		}
+
+	switch (samplerate) {
+	case 32000:
+		sampling_freq = 0x03;
+		break;
+	case 44100:
+		sampling_freq = 0x00;
+		break;
+	case 48000:
+		sampling_freq = 0x02;
+		break;
+	case 88200:
+		sampling_freq = 0x08;
+		break;
+	case 96000:
+		sampling_freq = 0x0a;
+		break;
+	case 176400:
+		sampling_freq = 0x0c;
+		break;
+	case 192000:
+		sampling_freq = 0x0e;
+		break;
+	default:
+		dev_err(card->dev,
+		"Failed to set WM8804 SYSCLK, unsupported samplerate %d\n",
+		samplerate);
+	}
+
+	snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, mclk_div);
+	snd_soc_dai_set_pll(codec_dai, 0, 0, sysclk, mclk_freq);
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL,
+		sysclk, SND_SOC_CLOCK_OUT);
+	if (ret < 0) {
+		dev_err(card->dev,
+		"Failed to set WM8804 SYSCLK: %d\n", ret);
+		return ret;
+	}
+	/* Enable wm8804 TX output */
+	snd_soc_component_update_bits(component, WM8804_PWRDN, 0x4, 0x0);
+
+	/* wm8804 Power on */
+	snd_soc_component_update_bits(component, WM8804_PWRDN, 0x9, 0);
+
+	/* wm8804 set sampling frequency status bits */
+	snd_soc_component_update_bits(component, WM8804_SPDTX4, 0x0f, sampling_freq);
+
+	/* Now update wm8741 registers for the correct oversampling */
+	if (samplerate <= 48000)
+		snd_soc_component_update_bits(wm8741_component, WM8741_MODE_CONTROL_1,
+		 WM8741_OSR_MASK, 0x00);
+	else if (samplerate <= 96000)
+		snd_soc_component_update_bits(wm8741_component, WM8741_MODE_CONTROL_1,
+		 WM8741_OSR_MASK, 0x20);
+	else
+		snd_soc_component_update_bits(wm8741_component, WM8741_MODE_CONTROL_1,
+		 WM8741_OSR_MASK, 0x40);
+
+	/* wm8741 bit size */
+	switch (params_width(params)) {
+	case 16:
+		snd_soc_component_update_bits(wm8741_component, WM8741_FORMAT_CONTROL,
+		 WM8741_IWL_MASK, 0x00);
+		break;
+	case 20:
+		snd_soc_component_update_bits(wm8741_component, WM8741_FORMAT_CONTROL,
+		 WM8741_IWL_MASK, 0x01);
+		break;
+	case 24:
+		snd_soc_component_update_bits(wm8741_component, WM8741_FORMAT_CONTROL,
+		 WM8741_IWL_MASK, 0x02);
+		break;
+	case 32:
+		snd_soc_component_update_bits(wm8741_component, WM8741_FORMAT_CONTROL,
+		 WM8741_IWL_MASK, 0x03);
+		break;
+	default:
+		dev_dbg(card->dev, "wm8741_hw_params:    Unsupported bit size param = %d",
+			params_width(params));
+		return -EINVAL;
+	}
+
+	return snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
+}
+/* machine stream operations */
+static struct snd_soc_ops digidac1_soundcard_ops = {
+	.hw_params	= digidac1_soundcard_hw_params,
+	.startup	= digidac1_soundcard_startup,
+	.shutdown	= digidac1_soundcard_shutdown,
+};
+
+SND_SOC_DAILINK_DEFS(digidac1,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("wm8804.1-003b", "wm8804-spdif")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2835-i2s.0")));
+
+SND_SOC_DAILINK_DEFS(digidac11,
+	DAILINK_COMP_ARRAY(COMP_CPU("wm8804-spdif")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("wm8741.1-001a", "wm8741")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link digidac1_soundcard_dai[] = {
+	{
+	.name		= "RRA DigiDAC1",
+	.stream_name	= "RRA DigiDAC1 HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBM_CFM,
+	.ops		= &digidac1_soundcard_ops,
+	.init		= digidac1_soundcard_init,
+	SND_SOC_DAILINK_REG(digidac1),
+	},
+	{
+	.name		= "RRA DigiDAC11",
+	.stream_name	= "RRA DigiDAC11 HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S
+			| SND_SOC_DAIFMT_NB_NF
+			| SND_SOC_DAIFMT_CBS_CFS,
+	SND_SOC_DAILINK_REG(digidac11),
+	},
+};
+
+/* audio machine driver */
+static struct snd_soc_card digidac1_soundcard = {
+	.name		= "digidac1-soundcard",
+	.owner		= THIS_MODULE,
+	.dai_link	= digidac1_soundcard_dai,
+	.num_links	= ARRAY_SIZE(digidac1_soundcard_dai),
+};
+
+static int digidac1_soundcard_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	digidac1_soundcard.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai = &digidac1_soundcard_dai[0];
+
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+					"i2s-controller", 0);
+
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		}
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &digidac1_soundcard);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
+			ret);
+
+	return ret;
+}
+
+static const struct of_device_id digidac1_soundcard_of_match[] = {
+	{ .compatible = "rra,digidac1-soundcard", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, digidac1_soundcard_of_match);
+
+static struct platform_driver digidac1_soundcard_driver = {
+	.driver = {
+			.name		= "digidac1-audio",
+			.owner		= THIS_MODULE,
+			.of_match_table	= digidac1_soundcard_of_match,
+	},
+	.probe		= digidac1_soundcard_probe,
+};
+
+module_platform_driver(digidac1_soundcard_driver);
+
+MODULE_AUTHOR("José M. Tasende <vintage@redrocksaudio.es>");
+MODULE_DESCRIPTION("ASoC Driver for RRA DigiDAC1");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/dionaudio_loco-v2.c b/sound/soc/bcm/dionaudio_loco-v2.c
new file mode 100644
index 000000000000..49aeaf121a3f
--- /dev/null
+++ b/sound/soc/bcm/dionaudio_loco-v2.c
@@ -0,0 +1,118 @@
+/*
+ * ASoC Driver for Dion Audio LOCO-V2 DAC-AMP
+ *
+ * Author:      Miquel Blauw <info@dionaudio.nl>
+ *              Copyright 2017
+ *
+ * Based on the software of the RPi-DAC writen by Florian Meier
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+static bool digital_gain_0db_limit = true;
+
+static int snd_rpi_dionaudio_loco_v2_init(struct snd_soc_pcm_runtime *rtd)
+{
+	if (digital_gain_0db_limit) {
+		int ret;
+		struct snd_soc_card *card = rtd->card;
+
+		ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207);
+		if (ret < 0)
+			dev_warn(card->dev, "Failed to set volume limit: %d\n", ret);
+	}
+
+	return 0;
+}
+
+SND_SOC_DAILINK_DEFS(dionaudio_loco_v2,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_dionaudio_loco_v2_dai[] = {
+{
+	.name		= "DionAudio LOCO-V2",
+	.stream_name	= "DionAudio LOCO-V2 DAC-AMP",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+			  SND_SOC_DAIFMT_CBS_CFS,
+	.init		= snd_rpi_dionaudio_loco_v2_init,
+	SND_SOC_DAILINK_REG(dionaudio_loco_v2),
+},};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_dionaudio_loco_v2 = {
+	.name         = "Dion Audio LOCO-V2",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_rpi_dionaudio_loco_v2_dai,
+	.num_links    = ARRAY_SIZE(snd_rpi_dionaudio_loco_v2_dai),
+};
+
+static int snd_rpi_dionaudio_loco_v2_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	snd_rpi_dionaudio_loco_v2.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai =
+					&snd_rpi_dionaudio_loco_v2_dai[0];
+
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+					    "i2s-controller", 0);
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		}
+
+		digital_gain_0db_limit = !of_property_read_bool(
+			pdev->dev.of_node, "dionaudio,24db_digital_gain");
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_dionaudio_loco_v2);
+	if (ret)
+		dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
+			ret);
+
+	return ret;
+}
+
+static const struct of_device_id dionaudio_of_match[] = {
+	{ .compatible = "dionaudio,dionaudio-loco-v2", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, dionaudio_of_match);
+
+static struct platform_driver snd_rpi_dionaudio_loco_v2_driver = {
+	.driver = {
+		.name   = "snd-rpi-dionaudio-loco-v2",
+		.owner  = THIS_MODULE,
+		.of_match_table = dionaudio_of_match,
+	},
+	.probe          = snd_rpi_dionaudio_loco_v2_probe,
+};
+
+module_platform_driver(snd_rpi_dionaudio_loco_v2_driver);
+
+MODULE_AUTHOR("Miquel Blauw <info@dionaudio.nl>");
+MODULE_DESCRIPTION("ASoC Driver for DionAudio LOCO-V2");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/dionaudio_loco.c b/sound/soc/bcm/dionaudio_loco.c
new file mode 100644
index 000000000000..5d5b677bf11c
--- /dev/null
+++ b/sound/soc/bcm/dionaudio_loco.c
@@ -0,0 +1,121 @@
+/*
+ * ASoC Driver for Dion Audio LOCO DAC-AMP
+ *
+ * Author:      Miquel Blauw <info@dionaudio.nl>
+ *              Copyright 2016
+ *
+ * Based on the software of the RPi-DAC writen by Florian Meier
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+static int snd_rpi_dionaudio_loco_hw_params(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+	unsigned int sample_bits =
+		snd_pcm_format_width(params_format(params));
+
+	/* Using powers of 2 allows for an integer clock divisor */
+	sample_bits = sample_bits <= 16 ? 16 : 32;
+
+	return snd_soc_dai_set_bclk_ratio(cpu_dai, sample_bits * 2);
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_rpi_dionaudio_loco_ops = {
+	.hw_params = snd_rpi_dionaudio_loco_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(dionaudio_loco,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm5102a-codec", "pcm5102a-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_dionaudio_loco_dai[] = {
+{
+	.name		= "DionAudio LOCO",
+	.stream_name	= "DionAudio LOCO DAC-AMP",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S |
+			  SND_SOC_DAIFMT_NB_NF |
+			  SND_SOC_DAIFMT_CBS_CFS,
+	.ops		= &snd_rpi_dionaudio_loco_ops,
+	SND_SOC_DAILINK_REG(dionaudio_loco),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_dionaudio_loco = {
+	.name		= "snd_rpi_dionaudio_loco",
+	.owner		= THIS_MODULE,
+	.dai_link	= snd_rpi_dionaudio_loco_dai,
+	.num_links	= ARRAY_SIZE(snd_rpi_dionaudio_loco_dai),
+};
+
+static int snd_rpi_dionaudio_loco_probe(struct platform_device *pdev)
+{
+	struct device_node *np;
+	int ret = 0;
+
+	snd_rpi_dionaudio_loco.dev = &pdev->dev;
+
+	np = pdev->dev.of_node;
+	if (np) {
+		struct snd_soc_dai_link *dai = &snd_rpi_dionaudio_loco_dai[0];
+		struct device_node *i2s_np;
+
+		i2s_np = of_parse_phandle(np, "i2s-controller", 0);
+		if (i2s_np) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_np;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_np;
+		}
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_dionaudio_loco);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n",
+			ret);
+
+	return ret;
+}
+
+static const struct of_device_id snd_rpi_dionaudio_loco_of_match[] = {
+	{ .compatible = "dionaudio,loco-pcm5242-tpa3118", },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, snd_rpi_dionaudio_loco_of_match);
+
+static struct platform_driver snd_rpi_dionaudio_loco_driver = {
+	.driver = {
+		.name		= "snd-dionaudio-loco",
+		.owner		= THIS_MODULE,
+		.of_match_table	= snd_rpi_dionaudio_loco_of_match,
+	},
+	.probe  = snd_rpi_dionaudio_loco_probe,
+};
+
+module_platform_driver(snd_rpi_dionaudio_loco_driver);
+
+MODULE_AUTHOR("Miquel Blauw <info@dionaudio.nl>");
+MODULE_DESCRIPTION("ASoC Driver for DionAudio LOCO");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/fe-pi-audio.c b/sound/soc/bcm/fe-pi-audio.c
new file mode 100644
index 000000000000..3263e2a9c30d
--- /dev/null
+++ b/sound/soc/bcm/fe-pi-audio.c
@@ -0,0 +1,154 @@
+/*
+ * ASoC Driver for Fe-Pi Audio Sound Card
+ *
+ * Author:	Henry Kupis <kuupaz@gmail.com>
+ *		Copyright 2016
+ *		based on code by Florian Meier <florian.meier@koalo.de>
+ *		based on code by Shawn Guo <shawn.guo@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/io.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "../codecs/sgtl5000.h"
+
+static int snd_fe_pi_audio_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_card *card = rtd->card;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+
+	snd_soc_dapm_force_enable_pin(&card->dapm, "LO");
+	snd_soc_dapm_force_enable_pin(&card->dapm, "ADC");
+	snd_soc_dapm_force_enable_pin(&card->dapm, "DAC");
+	snd_soc_dapm_force_enable_pin(&card->dapm, "HP");
+	snd_soc_component_update_bits(component, SGTL5000_CHIP_ANA_POWER,
+			SGTL5000_VAG_POWERUP, SGTL5000_VAG_POWERUP);
+
+	return 0;
+}
+
+static int snd_fe_pi_audio_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct device *dev = rtd->card->dev;
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+	int ret;
+
+	/* Set SGTL5000's SYSCLK */
+	ret = snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, 12288000, SND_SOC_CLOCK_IN);
+	if (ret) {
+		dev_err(dev, "could not set codec driver clock params\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+
+static struct snd_soc_ops snd_fe_pi_audio_ops = {
+	.hw_params = snd_fe_pi_audio_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(fe_pi,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("sgtl5000.1-000a", "sgtl5000")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_fe_pi_audio_dai[] = {
+	{
+		.name		= "FE-PI",
+		.stream_name	= "Fe-Pi HiFi",
+		.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+					SND_SOC_DAIFMT_CBM_CFM,
+		.ops		= &snd_fe_pi_audio_ops,
+		.init		= snd_fe_pi_audio_init,
+		SND_SOC_DAILINK_REG(fe_pi),
+	},
+};
+
+static const struct snd_soc_dapm_route fe_pi_audio_dapm_routes[] = {
+	{"ADC", NULL, "Mic Bias"},
+};
+
+
+static struct snd_soc_card fe_pi_audio = {
+	.name         = "Fe-Pi Audio",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_fe_pi_audio_dai,
+	.num_links    = ARRAY_SIZE(snd_fe_pi_audio_dai),
+
+	.dapm_routes = fe_pi_audio_dapm_routes,
+	.num_dapm_routes = ARRAY_SIZE(fe_pi_audio_dapm_routes),
+};
+
+static int snd_fe_pi_audio_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	struct snd_soc_card *card = &fe_pi_audio;
+	struct device_node *np = pdev->dev.of_node;
+	struct device_node *i2s_node;
+	struct snd_soc_dai_link *dai = &snd_fe_pi_audio_dai[0];
+
+	fe_pi_audio.dev = &pdev->dev;
+
+	i2s_node = of_parse_phandle(np, "i2s-controller", 0);
+	if (!i2s_node) {
+		dev_err(&pdev->dev, "i2s_node phandle missing or invalid\n");
+		return -EINVAL;
+	}
+
+	dai->cpus->dai_name = NULL;
+	dai->cpus->of_node = i2s_node;
+	dai->platforms->name = NULL;
+	dai->platforms->of_node = i2s_node;
+
+	of_node_put(i2s_node);
+
+	card->dev = &pdev->dev;
+	platform_set_drvdata(pdev, card);
+
+	ret = devm_snd_soc_register_card(&pdev->dev, card);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev, "snd_soc_register_card() failed: %d\n", ret);
+
+	return ret;
+}
+
+static const struct of_device_id snd_fe_pi_audio_of_match[] = {
+	{ .compatible = "fe-pi,fe-pi-audio", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, snd_fe_pi_audio_of_match);
+
+static struct platform_driver snd_fe_pi_audio_driver = {
+        .driver = {
+                .name   = "snd-fe-pi-audio",
+                .owner  = THIS_MODULE,
+                .of_match_table = snd_fe_pi_audio_of_match,
+        },
+        .probe          = snd_fe_pi_audio_probe,
+};
+
+module_platform_driver(snd_fe_pi_audio_driver);
+
+MODULE_AUTHOR("Henry Kupis <fe-pi@cox.net>");
+MODULE_DESCRIPTION("ASoC Driver for Fe-Pi Audio");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/googlevoicehat-codec.c b/sound/soc/bcm/googlevoicehat-codec.c
new file mode 100644
index 000000000000..572db9373e63
--- /dev/null
+++ b/sound/soc/bcm/googlevoicehat-codec.c
@@ -0,0 +1,212 @@
+/*
+ * Driver for the Google voiceHAT audio codec for Raspberry Pi.
+ *
+ * Author:	Peter Malkin <petermalkin@google.com>
+ *		Copyright 2016
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/version.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include <sound/soc-dapm.h>
+
+#define ICS43432_RATE_MIN_HZ	7190  /* from data sheet */
+#define ICS43432_RATE_MAX_HZ	52800 /* from data sheet */
+/* Delay in enabling SDMODE after clock settles to remove pop */
+#define SDMODE_DELAY_MS		5
+
+struct voicehat_priv {
+	struct delayed_work enable_sdmode_work;
+	struct gpio_desc *sdmode_gpio;
+	unsigned long sdmode_delay_jiffies;
+};
+
+static void voicehat_enable_sdmode_work(struct work_struct *work)
+{
+	struct voicehat_priv *voicehat = container_of(work,
+						      struct voicehat_priv,
+						      enable_sdmode_work.work);
+	gpiod_set_value(voicehat->sdmode_gpio, 1);
+}
+
+static int voicehat_component_probe(struct snd_soc_component *component)
+{
+	struct voicehat_priv *voicehat =
+				snd_soc_component_get_drvdata(component);
+
+	voicehat->sdmode_gpio = devm_gpiod_get(component->dev, "sdmode",
+					       GPIOD_OUT_LOW);
+	if (IS_ERR(voicehat->sdmode_gpio)) {
+		dev_err(component->dev, "Unable to allocate GPIO pin\n");
+		return PTR_ERR(voicehat->sdmode_gpio);
+	}
+
+	INIT_DELAYED_WORK(&voicehat->enable_sdmode_work,
+			  voicehat_enable_sdmode_work);
+	return 0;
+}
+
+static void voicehat_component_remove(struct snd_soc_component *component)
+{
+	struct voicehat_priv *voicehat =
+				snd_soc_component_get_drvdata(component);
+
+	cancel_delayed_work_sync(&voicehat->enable_sdmode_work);
+}
+
+static const struct snd_soc_dapm_widget voicehat_dapm_widgets[] = {
+	SND_SOC_DAPM_OUTPUT("Speaker"),
+};
+
+static const struct snd_soc_dapm_route voicehat_dapm_routes[] = {
+	{"Speaker", NULL, "HiFi Playback"},
+};
+
+static const struct snd_soc_component_driver voicehat_component_driver = {
+	.probe = voicehat_component_probe,
+	.remove = voicehat_component_remove,
+	.dapm_widgets = voicehat_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(voicehat_dapm_widgets),
+	.dapm_routes = voicehat_dapm_routes,
+	.num_dapm_routes = ARRAY_SIZE(voicehat_dapm_routes),
+};
+
+static int voicehat_daiops_trigger(struct snd_pcm_substream *substream, int cmd,
+				   struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct voicehat_priv *voicehat = snd_soc_component_get_drvdata(component);
+
+	if (voicehat->sdmode_delay_jiffies == 0)
+		return 0;
+
+	dev_dbg(dai->dev, "CMD             %d", cmd);
+	dev_dbg(dai->dev, "Playback Active %d", dai->stream[SNDRV_PCM_STREAM_PLAYBACK].active);
+	dev_dbg(dai->dev, "Capture Active  %d", dai->stream[SNDRV_PCM_STREAM_CAPTURE].active);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			dev_info(dai->dev, "Enabling audio amp...\n");
+			queue_delayed_work(
+				system_power_efficient_wq,
+				&voicehat->enable_sdmode_work,
+				voicehat->sdmode_delay_jiffies);
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			cancel_delayed_work(&voicehat->enable_sdmode_work);
+			dev_info(dai->dev, "Disabling audio amp...\n");
+			gpiod_set_value(voicehat->sdmode_gpio, 0);
+		}
+		break;
+	}
+	return 0;
+}
+
+static const struct snd_soc_dai_ops voicehat_dai_ops = {
+	.trigger = voicehat_daiops_trigger,
+};
+
+static struct snd_soc_dai_driver voicehat_dai = {
+	.name = "voicehat-hifi",
+	.capture = {
+		.stream_name = "HiFi Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S32_LE
+	},
+	.playback = {
+		.stream_name = "HiFi Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S32_LE
+	},
+	.ops = &voicehat_dai_ops,
+	.symmetric_rate = 1
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id voicehat_ids[] = {
+		{ .compatible = "google,voicehat", }, {}
+	};
+	MODULE_DEVICE_TABLE(of, voicehat_ids);
+#endif
+
+static int voicehat_platform_probe(struct platform_device *pdev)
+{
+	struct voicehat_priv *voicehat;
+	unsigned int sdmode_delay;
+	int ret;
+
+	voicehat = devm_kzalloc(&pdev->dev, sizeof(*voicehat), GFP_KERNEL);
+	if (!voicehat)
+		return -ENOMEM;
+
+	ret = device_property_read_u32(&pdev->dev, "voicehat_sdmode_delay",
+				       &sdmode_delay);
+
+	if (ret) {
+		sdmode_delay = SDMODE_DELAY_MS;
+		dev_info(&pdev->dev,
+			 "property 'voicehat_sdmode_delay' not found default 5 mS");
+	} else {
+		dev_info(&pdev->dev, "property 'voicehat_sdmode_delay' found delay= %d mS",
+			 sdmode_delay);
+	}
+	voicehat->sdmode_delay_jiffies = msecs_to_jiffies(sdmode_delay);
+
+	dev_set_drvdata(&pdev->dev, voicehat);
+
+	return snd_soc_register_component(&pdev->dev,
+					  &voicehat_component_driver,
+					  &voicehat_dai,
+					  1);
+}
+
+static void voicehat_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_component(&pdev->dev);
+}
+
+static struct platform_driver voicehat_driver = {
+	.driver = {
+		.name = "voicehat-codec",
+		.of_match_table = of_match_ptr(voicehat_ids),
+	},
+	.probe = voicehat_platform_probe,
+	.remove = voicehat_platform_remove,
+};
+
+module_platform_driver(voicehat_driver);
+
+MODULE_DESCRIPTION("Google voiceHAT Codec driver");
+MODULE_AUTHOR("Peter Malkin <petermalkin@google.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/hifiberry_adc.c b/sound/soc/bcm/hifiberry_adc.c
new file mode 100644
index 000000000000..bcd7f84d7e91
--- /dev/null
+++ b/sound/soc/bcm/hifiberry_adc.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ASoC Driver for HiFiBerry ADC
+ *
+ * Author:	Joerg Schambacher <joerg@hifiberry.com>
+ *		Copyright 2024
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/tlv.h>
+
+#include "../codecs/pcm186x.h"
+#include "hifiberry_adc_controls.h"
+
+static bool leds_off;
+
+static int pcm1863_add_controls(struct snd_soc_component *component)
+{
+	snd_soc_add_component_controls(component,
+			pcm1863_snd_controls_card,
+			ARRAY_SIZE(pcm1863_snd_controls_card));
+	return 0;
+}
+
+static int snd_rpi_hifiberry_adc_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_component *adc = codec_dai->component;
+	int ret;
+
+	ret = pcm1863_add_controls(adc);
+	if (ret < 0)
+		dev_warn(rtd->dev, "Failed to add pcm1863 controls: %d\n",
+		ret);
+
+	codec_dai->driver->capture.rates =
+		SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
+		SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |
+		SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000;
+
+	/* set GPIO2 to output, GPIO3 input */
+	snd_soc_component_write(adc, PCM186X_GPIO3_2_CTRL, 0x00);
+	snd_soc_component_write(adc, PCM186X_GPIO3_2_DIR_CTRL, 0x04);
+	if (leds_off)
+		snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x00);
+	else
+		snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x40);
+
+	return 0;
+}
+
+static int snd_rpi_hifiberry_adc_hw_params(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	int ret = 0;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	int channels = params_channels(params);
+	int width =  snd_pcm_format_width(params_format(params));
+
+	/* Using powers of 2 allows for an integer clock divisor */
+	width = width <= 16 ? 16 : 32;
+
+	ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width);
+	return ret;
+}
+
+/* machine stream operations */
+static const struct snd_soc_ops snd_rpi_hifiberry_adc_ops = {
+	.hw_params = snd_rpi_hifiberry_adc_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(hifi,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm186x.1-004a", "pcm1863-aif")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_hifiberry_adc_dai[] = {
+{
+	.name		= "HiFiBerry ADC",
+	.stream_name	= "HiFiBerry ADC HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBS_CFS,
+	.ops		= &snd_rpi_hifiberry_adc_ops,
+	.init		= snd_rpi_hifiberry_adc_init,
+	SND_SOC_DAILINK_REG(hifi),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_hifiberry_adc = {
+	.name         = "snd_rpi_hifiberry_adc",
+	.driver_name  = "HifiberryAdc",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_rpi_hifiberry_adc_dai,
+	.num_links    = ARRAY_SIZE(snd_rpi_hifiberry_adc_dai),
+};
+
+static int snd_rpi_hifiberry_adc_probe(struct platform_device *pdev)
+{
+	int ret = 0, i = 0;
+	struct snd_soc_card *card = &snd_rpi_hifiberry_adc;
+
+	snd_rpi_hifiberry_adc.dev = &pdev->dev;
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai;
+
+		dai = &snd_rpi_hifiberry_adc_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+			"i2s-controller", 0);
+		if (i2s_node) {
+			for (i = 0; i < card->num_links; i++) {
+				dai->cpus->dai_name = NULL;
+				dai->cpus->of_node = i2s_node;
+				dai->platforms->name = NULL;
+				dai->platforms->of_node = i2s_node;
+			}
+		}
+	}
+	leds_off = of_property_read_bool(pdev->dev.of_node,
+					"hifiberry-adc,leds_off");
+	ret = snd_soc_register_card(&snd_rpi_hifiberry_adc);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+
+	return ret;
+}
+
+static const struct of_device_id snd_rpi_hifiberry_adc_of_match[] = {
+	{ .compatible = "hifiberry,hifiberry-adc", },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_adc_of_match);
+
+static struct platform_driver snd_rpi_hifiberry_adc_driver = {
+	.driver = {
+		.name   = "snd-rpi-hifiberry-adc",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_rpi_hifiberry_adc_of_match,
+	},
+	.probe          = snd_rpi_hifiberry_adc_probe,
+};
+
+module_platform_driver(snd_rpi_hifiberry_adc_driver);
+
+MODULE_AUTHOR("Joerg Schambacher <joerg@hifiberry.com>");
+MODULE_DESCRIPTION("ASoC Driver for HiFiBerry ADC");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/bcm/hifiberry_adc_controls.h b/sound/soc/bcm/hifiberry_adc_controls.h
new file mode 100644
index 000000000000..8d8911bb0afb
--- /dev/null
+++ b/sound/soc/bcm/hifiberry_adc_controls.h
@@ -0,0 +1,128 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ALSA mixer/Kcontrol definitions common to HiFiBerry ADCs
+ *
+ * used by	DAC+ADC Pro (hifiberry_dacplusadcpro.c),
+ *		ADC (hifiberry_adc.c)
+ *
+ * Author:	Joerg Schambacher <joerg@hifiberry.com>
+ *		Copyright 2024
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+static const unsigned int pcm186x_adc_input_channel_sel_value[] = {
+	0x00, 0x01, 0x02, 0x03, 0x10
+};
+
+static const char * const pcm186x_adcl_input_channel_sel_text[] = {
+	"No Select",
+	"VINL1[SE]",					/* Default for ADCL */
+	"VINL2[SE]",
+	"VINL2[SE] + VINL1[SE]",
+	"{VIN1P, VIN1M}[DIFF]"
+};
+
+static const char * const pcm186x_adcr_input_channel_sel_text[] = {
+	"No Select",
+	"VINR1[SE]",					/* Default for ADCR */
+	"VINR2[SE]",
+	"VINR2[SE] + VINR1[SE]",
+	"{VIN2P, VIN2M}[DIFF]"
+};
+
+static const struct soc_enum pcm186x_adc_input_channel_sel[] = {
+	SOC_VALUE_ENUM_SINGLE(PCM186X_ADC1_INPUT_SEL_L, 0,
+			      PCM186X_ADC_INPUT_SEL_MASK,
+			      ARRAY_SIZE(pcm186x_adcl_input_channel_sel_text),
+			      pcm186x_adcl_input_channel_sel_text,
+			      pcm186x_adc_input_channel_sel_value),
+	SOC_VALUE_ENUM_SINGLE(PCM186X_ADC1_INPUT_SEL_R, 0,
+			      PCM186X_ADC_INPUT_SEL_MASK,
+			      ARRAY_SIZE(pcm186x_adcr_input_channel_sel_text),
+			      pcm186x_adcr_input_channel_sel_text,
+			      pcm186x_adc_input_channel_sel_value),
+};
+
+static const unsigned int pcm186x_mic_bias_sel_value[] = {
+	0x00, 0x01, 0x11
+};
+
+static const char * const pcm186x_mic_bias_sel_text[] = {
+	"Mic Bias off",
+	"Mic Bias on",
+	"Mic Bias with Bypass Resistor"
+};
+
+static const struct soc_enum pcm186x_mic_bias_sel[] = {
+	SOC_VALUE_ENUM_SINGLE(PCM186X_MIC_BIAS_CTRL, 0,
+			      GENMASK(4, 0),
+			      ARRAY_SIZE(pcm186x_mic_bias_sel_text),
+			      pcm186x_mic_bias_sel_text,
+			      pcm186x_mic_bias_sel_value),
+};
+
+static const unsigned int pcm186x_gain_sel_value[] = {
+	0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+	0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+	0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff,
+	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+	0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+	0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+	0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+	0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+	0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+	0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+	0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
+	0x50
+};
+
+static const char * const pcm186x_gain_sel_text[] = {
+	"-12.0dB", "-11.5dB", "-11.0dB", "-10.5dB", "-10.0dB", "-9.5dB",
+	"-9.0dB", "-8.5dB", "-8.0dB", "-7.5dB", "-7.0dB", "-6.5dB",
+	"-6.0dB", "-5.5dB", "-5.0dB", "-4.5dB", "-4.0dB", "-3.5dB",
+	"-3.0dB", "-2.5dB", "-2.0dB", "-1.5dB", "-1.0dB", "-0.5dB",
+	"0.0dB", "0.5dB", "1.0dB", "1.5dB", "2.0dB", "2.5dB",
+	"3.0dB", "3.5dB", "4.0dB", "4.5dB", "5.0dB", "5.5dB",
+	"6.0dB", "6.5dB", "7.0dB", "7.5dB", "8.0dB", "8.5dB",
+	"9.0dB", "9.5dB", "10.0dB", "10.5dB", "11.0dB", "11.5dB",
+	"12.0dB", "12.5dB", "13.0dB", "13.5dB", "14.0dB", "14.5dB",
+	"15.0dB", "15.5dB", "16.0dB", "16.5dB", "17.0dB", "17.5dB",
+	"18.0dB", "18.5dB", "19.0dB", "19.5dB", "20.0dB", "20.5dB",
+	"21.0dB", "21.5dB", "22.0dB", "22.5dB", "23.0dB", "23.5dB",
+	"24.0dB", "24.5dB", "25.0dB", "25.5dB", "26.0dB", "26.5dB",
+	"27.0dB", "27.5dB", "28.0dB", "28.5dB", "29.0dB", "29.5dB",
+	"30.0dB", "30.5dB", "31.0dB", "31.5dB", "32.0dB", "32.5dB",
+	"33.0dB", "33.5dB", "34.0dB", "34.5dB", "35.0dB", "35.5dB",
+	"36.0dB", "36.5dB", "37.0dB", "37.5dB", "38.0dB", "38.5dB",
+	"39.0dB", "39.5dB", "40.0dB"};
+
+static const struct soc_enum pcm186x_gain_sel[] = {
+	SOC_VALUE_ENUM_SINGLE(PCM186X_PGA_VAL_CH1_L, 0,
+			      0xff,
+			      ARRAY_SIZE(pcm186x_gain_sel_text),
+			      pcm186x_gain_sel_text,
+			      pcm186x_gain_sel_value),
+	SOC_VALUE_ENUM_SINGLE(PCM186X_PGA_VAL_CH1_R, 0,
+			      0xff,
+			      ARRAY_SIZE(pcm186x_gain_sel_text),
+			      pcm186x_gain_sel_text,
+			      pcm186x_gain_sel_value),
+};
+
+static const struct snd_kcontrol_new pcm1863_snd_controls_card[] = {
+	SOC_ENUM("ADC Left Input", pcm186x_adc_input_channel_sel[0]),
+	SOC_ENUM("ADC Right Input", pcm186x_adc_input_channel_sel[1]),
+	SOC_ENUM("ADC Mic Bias", pcm186x_mic_bias_sel),
+	SOC_ENUM("PGA Gain Left", pcm186x_gain_sel[0]),
+	SOC_ENUM("PGA Gain Right", pcm186x_gain_sel[1]),
+};
diff --git a/sound/soc/bcm/hifiberry_dacplus.c b/sound/soc/bcm/hifiberry_dacplus.c
new file mode 100644
index 000000000000..a6dacdced79a
--- /dev/null
+++ b/sound/soc/bcm/hifiberry_dacplus.c
@@ -0,0 +1,563 @@
+/*
+ * ASoC Driver for HiFiBerry DAC+ / DAC Pro / AMP100
+ *
+ * Author:	Daniel Matuschek, Stuart MacLean <stuart@hifiberry.com>
+ *		Copyright 2014-2015
+ *		based on code by Florian Meier <florian.meier@koalo.de>
+ *		Headphone/AMP100 Joerg Schambacher <joerg@hifiberry.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <../drivers/gpio/gpiolib.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "../codecs/pcm512x.h"
+
+#define HIFIBERRY_DACPRO_NOCLOCK 0
+#define HIFIBERRY_DACPRO_CLK44EN 1
+#define HIFIBERRY_DACPRO_CLK48EN 2
+
+struct pcm512x_priv {
+	struct regmap *regmap;
+	struct clk *sclk;
+};
+
+/* Clock rate of CLK44EN attached to GPIO6 pin */
+#define CLK_44EN_RATE 22579200UL
+/* Clock rate of CLK48EN attached to GPIO3 pin */
+#define CLK_48EN_RATE 24576000UL
+
+static bool slave;
+static bool snd_rpi_hifiberry_is_dacpro;
+static bool digital_gain_0db_limit = true;
+static bool leds_off;
+static bool auto_mute;
+static int mute_ext_ctl;
+static int mute_ext;
+static bool tas_device;
+static struct gpio_desc *snd_mute_gpio;
+static struct gpio_desc *snd_reset_gpio;
+static struct snd_soc_card snd_rpi_hifiberry_dacplus;
+
+static const u32 master_dai_rates[] = {
+	44100, 48000, 88200, 96000,
+	176400, 192000, 352800, 384000,
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_master = {
+	.count = ARRAY_SIZE(master_dai_rates),
+	.list  = master_dai_rates,
+};
+
+static int snd_rpi_hifiberry_dacplus_mute_set(int mute)
+{
+	gpiod_set_value_cansleep(snd_mute_gpio, mute);
+	return 1;
+}
+
+static int snd_rpi_hifiberry_dacplus_mute_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = mute_ext;
+
+	return 0;
+}
+
+static int snd_rpi_hifiberry_dacplus_mute_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	if (mute_ext == ucontrol->value.integer.value[0])
+		return 0;
+
+	mute_ext = ucontrol->value.integer.value[0];
+
+	return snd_rpi_hifiberry_dacplus_mute_set(mute_ext);
+}
+
+static const char * const mute_text[] = {"Play", "Mute"};
+static const struct soc_enum hb_dacplus_opt_mute_enum =
+	SOC_ENUM_SINGLE_EXT(2, mute_text);
+
+static const struct snd_kcontrol_new hb_dacplus_opt_mute_controls[] = {
+	SOC_ENUM_EXT("Mute(ext)", hb_dacplus_opt_mute_enum,
+			      snd_rpi_hifiberry_dacplus_mute_get,
+			      snd_rpi_hifiberry_dacplus_mute_put),
+};
+
+static void snd_rpi_hifiberry_dacplus_select_clk(struct snd_soc_component *component,
+	int clk_id)
+{
+	switch (clk_id) {
+	case HIFIBERRY_DACPRO_NOCLOCK:
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x00);
+		break;
+	case HIFIBERRY_DACPRO_CLK44EN:
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x20);
+		break;
+	case HIFIBERRY_DACPRO_CLK48EN:
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x04);
+		break;
+	}
+	usleep_range(3000, 4000);
+}
+
+static void snd_rpi_hifiberry_dacplus_clk_gpio(struct snd_soc_component *component)
+{
+	snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x24, 0x24);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_3, 0x0f, 0x02);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_6, 0x0f, 0x02);
+}
+
+static bool snd_rpi_hifiberry_dacplus_is_sclk(struct snd_soc_component *component)
+{
+	unsigned int sck;
+
+	sck = snd_soc_component_read(component, PCM512x_RATE_DET_4);
+	return (!(sck & 0x40));
+}
+
+static bool snd_rpi_hifiberry_dacplus_is_pro_card(struct snd_soc_component *component)
+{
+	bool isClk44EN, isClk48En, isNoClk;
+
+	snd_rpi_hifiberry_dacplus_clk_gpio(component);
+
+	snd_rpi_hifiberry_dacplus_select_clk(component, HIFIBERRY_DACPRO_CLK44EN);
+	isClk44EN = snd_rpi_hifiberry_dacplus_is_sclk(component);
+
+	snd_rpi_hifiberry_dacplus_select_clk(component, HIFIBERRY_DACPRO_NOCLOCK);
+	isNoClk = snd_rpi_hifiberry_dacplus_is_sclk(component);
+
+	snd_rpi_hifiberry_dacplus_select_clk(component, HIFIBERRY_DACPRO_CLK48EN);
+	isClk48En = snd_rpi_hifiberry_dacplus_is_sclk(component);
+
+	return (isClk44EN && isClk48En && !isNoClk);
+}
+
+static int snd_rpi_hifiberry_dacplus_clk_for_rate(int sample_rate)
+{
+	int type;
+
+	switch (sample_rate) {
+	case 11025:
+	case 22050:
+	case 44100:
+	case 88200:
+	case 176400:
+	case 352800:
+		type = HIFIBERRY_DACPRO_CLK44EN;
+		break;
+	default:
+		type = HIFIBERRY_DACPRO_CLK48EN;
+		break;
+	}
+	return type;
+}
+
+static void snd_rpi_hifiberry_dacplus_set_sclk(struct snd_soc_component *component,
+	int sample_rate)
+{
+	struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
+
+	if (!IS_ERR(pcm512x->sclk)) {
+		int ctype;
+
+		ctype = snd_rpi_hifiberry_dacplus_clk_for_rate(sample_rate);
+		clk_set_rate(pcm512x->sclk, (ctype == HIFIBERRY_DACPRO_CLK44EN)
+			? CLK_44EN_RATE : CLK_48EN_RATE);
+		snd_rpi_hifiberry_dacplus_select_clk(component, ctype);
+	}
+}
+
+static int snd_rpi_hifiberry_dacplus_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct pcm512x_priv *priv;
+	struct snd_soc_card *card = &snd_rpi_hifiberry_dacplus;
+
+	if (slave)
+		snd_rpi_hifiberry_is_dacpro = false;
+	else
+		snd_rpi_hifiberry_is_dacpro =
+				snd_rpi_hifiberry_dacplus_is_pro_card(component);
+
+	if (snd_rpi_hifiberry_is_dacpro) {
+		struct snd_soc_dai_link *dai = rtd->dai_link;
+
+		if (tas_device) {
+			dai->name = "HiFiBerry AMP4 Pro";
+			dai->stream_name = "HiFiBerry AMP4 Pro HiFi";
+		} else {
+			dai->name = "HiFiBerry DAC+ Pro";
+			dai->stream_name = "HiFiBerry DAC+ Pro HiFi";
+		}
+		dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+			| SND_SOC_DAIFMT_CBM_CFM;
+
+		snd_soc_component_update_bits(component, PCM512x_BCLK_LRCLK_CFG, 0x31, 0x11);
+		snd_soc_component_update_bits(component, PCM512x_MASTER_MODE, 0x03, 0x03);
+		snd_soc_component_update_bits(component, PCM512x_MASTER_CLKDIV_2, 0x7f, 63);
+	} else {
+		priv = snd_soc_component_get_drvdata(component);
+		priv->sclk = ERR_PTR(-ENOENT);
+	}
+
+	snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x08, 0x08);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02);
+	if (leds_off)
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x00);
+	else
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
+
+	if (digital_gain_0db_limit) {
+		int ret;
+		struct snd_soc_card *card = rtd->card;
+
+		ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207);
+		if (ret < 0)
+			dev_warn(card->dev, "Failed to set volume limit: %d\n", ret);
+	}
+	if (snd_reset_gpio) {
+		gpiod_set_value_cansleep(snd_reset_gpio, 0);
+		msleep(1);
+		gpiod_set_value_cansleep(snd_reset_gpio, 1);
+		msleep(1);
+		gpiod_set_value_cansleep(snd_reset_gpio, 0);
+	}
+
+	if (mute_ext_ctl)
+		snd_soc_add_card_controls(card,	hb_dacplus_opt_mute_controls,
+				ARRAY_SIZE(hb_dacplus_opt_mute_controls));
+
+	if (snd_mute_gpio)
+		gpiod_set_value_cansleep(snd_mute_gpio,	mute_ext);
+
+	return 0;
+}
+
+static int snd_rpi_hifiberry_dacplus_update_rate_den(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
+	struct snd_ratnum *rats_no_pll;
+	unsigned int num = 0, den = 0;
+	int err;
+
+	rats_no_pll = devm_kzalloc(rtd->dev, sizeof(*rats_no_pll), GFP_KERNEL);
+	if (!rats_no_pll)
+		return -ENOMEM;
+
+	rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64;
+	rats_no_pll->den_min = 1;
+	rats_no_pll->den_max = 128;
+	rats_no_pll->den_step = 1;
+
+	err = snd_interval_ratnum(hw_param_interval(params,
+		SNDRV_PCM_HW_PARAM_RATE), 1, rats_no_pll, &num, &den);
+	if (err >= 0 && den) {
+		params->rate_num = num;
+		params->rate_den = den;
+	}
+
+	devm_kfree(rtd->dev, rats_no_pll);
+	return 0;
+}
+
+static int snd_rpi_hifiberry_dacplus_hw_params(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	int ret = 0;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	int channels = params_channels(params);
+	int width = snd_pcm_format_width(params_format(params));
+
+	/* Using powers of 2 allows for an integer clock divisor */
+	width = width <= 16 ? 16 : 32;
+
+	if (snd_rpi_hifiberry_is_dacpro) {
+		struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+
+		snd_rpi_hifiberry_dacplus_set_sclk(component,
+			params_rate(params));
+
+		ret = snd_rpi_hifiberry_dacplus_update_rate_den(
+			substream, params);
+	}
+
+	ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width);
+	if (ret)
+		return ret;
+	ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_codec(rtd, 0), channels * width);
+	return ret;
+}
+
+static int snd_rpi_hifiberry_dacplus_startup(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	int ret;
+
+	if (tas_device && !slave) {
+		ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+					  SNDRV_PCM_HW_PARAM_RATE,
+					  &constraints_master);
+		if (ret < 0) {
+			dev_err(rtd->card->dev,
+				"Cannot apply constraints for sample rates\n");
+			return ret;
+		}
+	}
+
+	if (auto_mute)
+		gpiod_set_value_cansleep(snd_mute_gpio, 0);
+	if (leds_off)
+		return 0;
+	snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
+	return 0;
+}
+
+static void snd_rpi_hifiberry_dacplus_shutdown(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+
+	snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x00);
+	if (auto_mute)
+		gpiod_set_value_cansleep(snd_mute_gpio, 1);
+}
+
+/* machine stream operations */
+static const struct snd_soc_ops snd_rpi_hifiberry_dacplus_ops = {
+	.hw_params = snd_rpi_hifiberry_dacplus_hw_params,
+	.startup = snd_rpi_hifiberry_dacplus_startup,
+	.shutdown = snd_rpi_hifiberry_dacplus_shutdown,
+};
+
+SND_SOC_DAILINK_DEFS(rpi_hifiberry_dacplus,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_hifiberry_dacplus_dai[] = {
+{
+	.name		= "HiFiBerry DAC+",
+	.stream_name	= "HiFiBerry DAC+ HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBS_CFS,
+	.ops		= &snd_rpi_hifiberry_dacplus_ops,
+	.init		= snd_rpi_hifiberry_dacplus_init,
+	SND_SOC_DAILINK_REG(rpi_hifiberry_dacplus),
+},
+};
+
+/* aux device for optional headphone amp */
+static struct snd_soc_aux_dev hifiberry_dacplus_aux_devs[] = {
+	{
+		.dlc = {
+			.name = "tpa6130a2.1-0060",
+		},
+	},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_hifiberry_dacplus = {
+	.name         = "snd_rpi_hifiberry_dacplus",
+	.driver_name  = "HifiberryDacp",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_rpi_hifiberry_dacplus_dai,
+	.num_links    = ARRAY_SIZE(snd_rpi_hifiberry_dacplus_dai),
+};
+
+static int hb_hp_detect(void)
+{
+	struct i2c_adapter *adap = i2c_get_adapter(1);
+	int ret;
+	struct i2c_client tpa_i2c_client = {
+		.addr = 0x60,
+		.adapter = adap,
+	};
+
+	if (!adap)
+		return -EPROBE_DEFER;	/* I2C module not yet available */
+
+	ret = i2c_smbus_read_byte(&tpa_i2c_client) >= 0;
+	i2c_put_adapter(adap);
+	return ret;
+};
+
+static struct property tpa_enable_prop = {
+	       .name = "status",
+	       .length = 4 + 1, /* length 'okay' + 1 */
+	       .value = "okay",
+	};
+
+static int snd_rpi_hifiberry_dacplus_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	struct snd_soc_card *card = &snd_rpi_hifiberry_dacplus;
+	int len;
+	struct device_node *tpa_node;
+	struct device_node *tas_node;
+	struct property *tpa_prop;
+	struct of_changeset ocs;
+	struct property *pp;
+	int tmp;
+
+	/* probe for head phone amp */
+	ret = hb_hp_detect();
+	if (ret < 0)
+		return ret;
+	if (ret) {
+		card->aux_dev = hifiberry_dacplus_aux_devs;
+		card->num_aux_devs =
+				ARRAY_SIZE(hifiberry_dacplus_aux_devs);
+		tpa_node = of_find_compatible_node(NULL, NULL, "ti,tpa6130a2");
+		tpa_prop = of_find_property(tpa_node, "status", &len);
+
+		if (strcmp((char *)tpa_prop->value, "okay")) {
+			/* and activate headphone using change_sets */
+			dev_info(&pdev->dev, "activating headphone amplifier");
+			of_changeset_init(&ocs);
+			ret = of_changeset_update_property(&ocs, tpa_node,
+							&tpa_enable_prop);
+			if (ret) {
+				dev_err(&pdev->dev,
+				"cannot activate headphone amplifier\n");
+				return -ENODEV;
+			}
+			ret = of_changeset_apply(&ocs);
+			if (ret) {
+				dev_err(&pdev->dev,
+				"cannot activate headphone amplifier\n");
+				return -ENODEV;
+			}
+		}
+	}
+
+	tas_node = of_find_compatible_node(NULL, NULL, "ti,tas5756");
+	if (tas_node) {
+		tas_device = true;
+		dev_info(&pdev->dev, "TAS5756 device found!\n");
+	};
+
+	snd_rpi_hifiberry_dacplus.dev = &pdev->dev;
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai;
+
+		dai = &snd_rpi_hifiberry_dacplus_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+			"i2s-controller", 0);
+
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		}
+
+		digital_gain_0db_limit = !of_property_read_bool(
+			pdev->dev.of_node, "hifiberry,24db_digital_gain");
+		slave = of_property_read_bool(pdev->dev.of_node,
+						"hifiberry-dacplus,slave");
+		leds_off = of_property_read_bool(pdev->dev.of_node,
+						"hifiberry-dacplus,leds_off");
+		auto_mute = of_property_read_bool(pdev->dev.of_node,
+						"hifiberry-dacplus,auto_mute");
+
+		/*
+		 * check for HW MUTE as defined in DT-overlay
+		 * active high, therefore default to HIGH to MUTE
+		 */
+		snd_mute_gpio =	devm_gpiod_get_optional(&pdev->dev,
+						 "mute", GPIOD_OUT_HIGH);
+		if (IS_ERR(snd_mute_gpio)) {
+			dev_err(&pdev->dev, "Can't allocate GPIO (HW-MUTE)");
+			return PTR_ERR(snd_mute_gpio);
+		}
+
+		/* add ALSA control if requested in DT-overlay (AMP100) */
+		pp = of_find_property(pdev->dev.of_node,
+				"hifiberry-dacplus,mute_ext_ctl", &tmp);
+		if (pp) {
+			if (!of_property_read_u32(pdev->dev.of_node,
+				"hifiberry-dacplus,mute_ext_ctl", &mute_ext)) {
+				/* ALSA control will be used */
+				mute_ext_ctl = 1;
+			}
+		}
+
+		/* check for HW RESET (AMP100) */
+		snd_reset_gpio = devm_gpiod_get_optional(&pdev->dev,
+						"reset", GPIOD_OUT_HIGH);
+		if (IS_ERR(snd_reset_gpio)) {
+			dev_err(&pdev->dev, "Can't allocate GPIO (HW-RESET)");
+			return PTR_ERR(snd_reset_gpio);
+		}
+
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev,
+			&snd_rpi_hifiberry_dacplus);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+	if (!ret) {
+		if (snd_mute_gpio)
+			dev_info(&pdev->dev, "GPIO%i for HW-MUTE selected",
+					gpio_chip_hwgpio(snd_mute_gpio));
+		if (snd_reset_gpio)
+			dev_info(&pdev->dev, "GPIO%i for HW-RESET selected",
+					gpio_chip_hwgpio(snd_reset_gpio));
+	}
+	return ret;
+}
+
+static const struct of_device_id snd_rpi_hifiberry_dacplus_of_match[] = {
+	{ .compatible = "hifiberry,hifiberry-dacplus", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_dacplus_of_match);
+
+static struct platform_driver snd_rpi_hifiberry_dacplus_driver = {
+	.driver = {
+		.name   = "snd-rpi-hifiberry-dacplus",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_rpi_hifiberry_dacplus_of_match,
+	},
+	.probe          = snd_rpi_hifiberry_dacplus_probe,
+};
+
+module_platform_driver(snd_rpi_hifiberry_dacplus_driver);
+
+MODULE_AUTHOR("Daniel Matuschek <daniel@hifiberry.com>");
+MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/hifiberry_dacplusadc.c b/sound/soc/bcm/hifiberry_dacplusadc.c
new file mode 100644
index 000000000000..d21b006aad7d
--- /dev/null
+++ b/sound/soc/bcm/hifiberry_dacplusadc.c
@@ -0,0 +1,399 @@
+/*
+ * ASoC Driver for HiFiBerry DAC+ / DAC Pro with ADC
+ *
+ * Author:	Daniel Matuschek, Stuart MacLean <stuart@hifiberry.com>
+ *		Copyright 2014-2015
+ *		based on code by Florian Meier <florian.meier@koalo.de>
+ *		ADC added by Joerg Schambacher <joscha@schambacher.com>
+ *		Copyright 2018
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "../codecs/pcm512x.h"
+
+#define HIFIBERRY_DACPRO_NOCLOCK 0
+#define HIFIBERRY_DACPRO_CLK44EN 1
+#define HIFIBERRY_DACPRO_CLK48EN 2
+
+struct platform_device *dmic_codec_dev;
+
+struct pcm512x_priv {
+	struct regmap *regmap;
+	struct clk *sclk;
+};
+
+/* Clock rate of CLK44EN attached to GPIO6 pin */
+#define CLK_44EN_RATE 22579200UL
+/* Clock rate of CLK48EN attached to GPIO3 pin */
+#define CLK_48EN_RATE 24576000UL
+
+static bool slave;
+static bool snd_rpi_hifiberry_is_dacpro;
+static bool digital_gain_0db_limit = true;
+static bool leds_off;
+
+static void snd_rpi_hifiberry_dacplusadc_select_clk(struct snd_soc_component *component,
+	int clk_id)
+{
+	switch (clk_id) {
+	case HIFIBERRY_DACPRO_NOCLOCK:
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x00);
+		break;
+	case HIFIBERRY_DACPRO_CLK44EN:
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x20);
+		break;
+	case HIFIBERRY_DACPRO_CLK48EN:
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x24, 0x04);
+		break;
+	}
+}
+
+static void snd_rpi_hifiberry_dacplusadc_clk_gpio(struct snd_soc_component *component)
+{
+	snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x24, 0x24);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_3, 0x0f, 0x02);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_6, 0x0f, 0x02);
+}
+
+static bool snd_rpi_hifiberry_dacplusadc_is_sclk(struct snd_soc_component *component)
+{
+	unsigned int sck;
+
+	sck = snd_soc_component_read(component, PCM512x_RATE_DET_4);
+	return (!(sck & 0x40));
+}
+
+static bool snd_rpi_hifiberry_dacplusadc_is_sclk_sleep(
+	struct snd_soc_component *component)
+{
+	msleep(2);
+	return snd_rpi_hifiberry_dacplusadc_is_sclk(component);
+}
+
+static bool snd_rpi_hifiberry_dacplusadc_is_pro_card(struct snd_soc_component *component)
+{
+	bool isClk44EN, isClk48En, isNoClk;
+
+	snd_rpi_hifiberry_dacplusadc_clk_gpio(component);
+
+	snd_rpi_hifiberry_dacplusadc_select_clk(component, HIFIBERRY_DACPRO_CLK44EN);
+	isClk44EN = snd_rpi_hifiberry_dacplusadc_is_sclk_sleep(component);
+
+	snd_rpi_hifiberry_dacplusadc_select_clk(component, HIFIBERRY_DACPRO_NOCLOCK);
+	isNoClk = snd_rpi_hifiberry_dacplusadc_is_sclk_sleep(component);
+
+	snd_rpi_hifiberry_dacplusadc_select_clk(component, HIFIBERRY_DACPRO_CLK48EN);
+	isClk48En = snd_rpi_hifiberry_dacplusadc_is_sclk_sleep(component);
+
+	return (isClk44EN && isClk48En && !isNoClk);
+}
+
+static int snd_rpi_hifiberry_dacplusadc_clk_for_rate(int sample_rate)
+{
+	int type;
+
+	switch (sample_rate) {
+	case 11025:
+	case 22050:
+	case 44100:
+	case 88200:
+	case 176400:
+	case 352800:
+		type = HIFIBERRY_DACPRO_CLK44EN;
+		break;
+	default:
+		type = HIFIBERRY_DACPRO_CLK48EN;
+		break;
+	}
+	return type;
+}
+
+static void snd_rpi_hifiberry_dacplusadc_set_sclk(struct snd_soc_component *component,
+	int sample_rate)
+{
+	struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
+
+	if (!IS_ERR(pcm512x->sclk)) {
+		int ctype;
+
+		ctype = snd_rpi_hifiberry_dacplusadc_clk_for_rate(sample_rate);
+		clk_set_rate(pcm512x->sclk, (ctype == HIFIBERRY_DACPRO_CLK44EN)
+			? CLK_44EN_RATE : CLK_48EN_RATE);
+		snd_rpi_hifiberry_dacplusadc_select_clk(component, ctype);
+	}
+}
+
+static int snd_rpi_hifiberry_dacplusadc_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct pcm512x_priv *priv;
+
+	if (slave)
+		snd_rpi_hifiberry_is_dacpro = false;
+	else
+		snd_rpi_hifiberry_is_dacpro =
+				snd_rpi_hifiberry_dacplusadc_is_pro_card(component);
+
+	if (snd_rpi_hifiberry_is_dacpro) {
+		struct snd_soc_dai_link *dai = rtd->dai_link;
+
+		dai->name = "HiFiBerry ADCDAC+ Pro";
+		dai->stream_name = "HiFiBerry ADCDAC+ Pro HiFi";
+		dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+			| SND_SOC_DAIFMT_CBM_CFM;
+
+		snd_soc_component_update_bits(component, PCM512x_BCLK_LRCLK_CFG, 0x31, 0x11);
+		snd_soc_component_update_bits(component, PCM512x_MASTER_MODE, 0x03, 0x03);
+		snd_soc_component_update_bits(component, PCM512x_MASTER_CLKDIV_2, 0x7f, 63);
+	} else {
+		priv = snd_soc_component_get_drvdata(component);
+		priv->sclk = ERR_PTR(-ENOENT);
+	}
+
+	snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x08, 0x08);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02);
+	if (leds_off)
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x00);
+	else
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
+
+	if (digital_gain_0db_limit) {
+		int ret;
+		struct snd_soc_card *card = rtd->card;
+
+		ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207);
+		if (ret < 0)
+			dev_warn(card->dev, "Failed to set volume limit: %d\n", ret);
+	}
+
+	return 0;
+}
+
+static int snd_rpi_hifiberry_dacplusadc_update_rate_den(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
+	struct snd_ratnum *rats_no_pll;
+	unsigned int num = 0, den = 0;
+	int err;
+
+	rats_no_pll = devm_kzalloc(rtd->dev, sizeof(*rats_no_pll), GFP_KERNEL);
+	if (!rats_no_pll)
+		return -ENOMEM;
+
+	rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64;
+	rats_no_pll->den_min = 1;
+	rats_no_pll->den_max = 128;
+	rats_no_pll->den_step = 1;
+
+	err = snd_interval_ratnum(hw_param_interval(params,
+		SNDRV_PCM_HW_PARAM_RATE), 1, rats_no_pll, &num, &den);
+	if (err >= 0 && den) {
+		params->rate_num = num;
+		params->rate_den = den;
+	}
+
+	devm_kfree(rtd->dev, rats_no_pll);
+	return 0;
+}
+
+static int snd_rpi_hifiberry_dacplusadc_hw_params(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	int ret = 0;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	int channels = params_channels(params);
+	int width = snd_pcm_format_width(params_format(params));
+
+	/* Using powers of 2 allows for an integer clock divisor */
+	width = width <= 16 ? 16 : 32;
+
+	if (snd_rpi_hifiberry_is_dacpro) {
+		struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+
+		snd_rpi_hifiberry_dacplusadc_set_sclk(component,
+			params_rate(params));
+
+		ret = snd_rpi_hifiberry_dacplusadc_update_rate_den(
+			substream, params);
+	}
+
+	ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width);
+	if (ret)
+		return ret;
+	ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_codec(rtd, 0), channels * width);
+	return ret;
+}
+
+static int hifiberry_dacplusadc_LED_cnt;
+
+static int snd_rpi_hifiberry_dacplusadc_startup(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+
+	if (leds_off)
+		return 0;
+	snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1,
+					 0x08, 0x08);
+	hifiberry_dacplusadc_LED_cnt++;
+	return 0;
+}
+
+static void snd_rpi_hifiberry_dacplusadc_shutdown(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+
+	hifiberry_dacplusadc_LED_cnt--;
+	if (!hifiberry_dacplusadc_LED_cnt)
+		snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1,
+						 0x08, 0x00);
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_rpi_hifiberry_dacplusadc_ops = {
+	.hw_params = snd_rpi_hifiberry_dacplusadc_hw_params,
+	.startup = snd_rpi_hifiberry_dacplusadc_startup,
+	.shutdown = snd_rpi_hifiberry_dacplusadc_shutdown,
+};
+
+SND_SOC_DAILINK_DEFS(hifi,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi"),
+			   COMP_CODEC("dmic-codec", "dmic-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_hifiberry_dacplusadc_dai[] = {
+{
+	.name		= "HiFiBerry DAC+ADC",
+	.stream_name	= "HiFiBerry DAC+ADC HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBS_CFS,
+	.ops		= &snd_rpi_hifiberry_dacplusadc_ops,
+	.init		= snd_rpi_hifiberry_dacplusadc_init,
+	SND_SOC_DAILINK_REG(hifi),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_hifiberry_dacplusadc = {
+	.name         = "snd_rpi_hifiberry_dacplusadc",
+	.driver_name  = "HifiberryDacpAdc",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_rpi_hifiberry_dacplusadc_dai,
+	.num_links    = ARRAY_SIZE(snd_rpi_hifiberry_dacplusadc_dai),
+};
+
+
+static int snd_rpi_hifiberry_dacplusadc_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	snd_rpi_hifiberry_dacplusadc.dev = &pdev->dev;
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai;
+
+		dai = &snd_rpi_hifiberry_dacplusadc_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+			"i2s-controller", 0);
+		if (i2s_node) {
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->of_node = i2s_node;
+			dai->cpus->dai_name = NULL;
+			dai->platforms->name = NULL;
+		}
+	}
+	digital_gain_0db_limit = !of_property_read_bool(
+		pdev->dev.of_node, "hifiberry,24db_digital_gain");
+	slave = of_property_read_bool(pdev->dev.of_node,
+					"hifiberry-dacplusadc,slave");
+	leds_off = of_property_read_bool(pdev->dev.of_node,
+					"hifiberry-dacplusadc,leds_off");
+
+	ret = devm_snd_soc_register_card(&pdev->dev,
+						 &snd_rpi_hifiberry_dacplusadc);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+
+	return ret;
+}
+
+static const struct of_device_id snd_rpi_hifiberry_dacplusadc_of_match[] = {
+	{ .compatible = "hifiberry,hifiberry-dacplusadc", },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_dacplusadc_of_match);
+
+static struct platform_driver snd_rpi_hifiberry_dacplusadc_driver = {
+	.driver = {
+		.name   = "snd-rpi-hifiberry-dacplusadc",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_rpi_hifiberry_dacplusadc_of_match,
+	},
+	.probe          = snd_rpi_hifiberry_dacplusadc_probe,
+};
+
+static int __init hifiberry_dacplusadc_init(void)
+{
+	int ret;
+
+	dmic_codec_dev = platform_device_register_simple("dmic-codec", -1, NULL,
+							 0);
+	if (IS_ERR(dmic_codec_dev)) {
+		pr_err("%s: dmic-codec device registration failed\n", __func__);
+		return PTR_ERR(dmic_codec_dev);
+	}
+
+	ret = platform_driver_register(&snd_rpi_hifiberry_dacplusadc_driver);
+	if (ret) {
+		pr_err("%s: platform driver registration failed\n", __func__);
+		platform_device_unregister(dmic_codec_dev);
+	}
+
+	return ret;
+}
+module_init(hifiberry_dacplusadc_init);
+
+static void __exit hifiberry_dacplusadc_exit(void)
+{
+	platform_driver_unregister(&snd_rpi_hifiberry_dacplusadc_driver);
+	platform_device_unregister(dmic_codec_dev);
+}
+module_exit(hifiberry_dacplusadc_exit);
+
+MODULE_AUTHOR("Joerg Schambacher <joscha@schambacher.com>");
+MODULE_AUTHOR("Daniel Matuschek <daniel@hifiberry.com>");
+MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+ADC");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/hifiberry_dacplusadcpro.c b/sound/soc/bcm/hifiberry_dacplusadcpro.c
new file mode 100644
index 000000000000..1060069f389c
--- /dev/null
+++ b/sound/soc/bcm/hifiberry_dacplusadcpro.c
@@ -0,0 +1,498 @@
+/*
+ * ASoC Driver for HiFiBerry DAC+ / DAC Pro with ADC PRO Version (SW control)
+ *
+ * Author:	Daniel Matuschek, Stuart MacLean <stuart@hifiberry.com>
+ *		Copyright 2014-2015
+ *		based on code by Florian Meier <florian.meier@koalo.de>
+ *		ADC, HP added by Joerg Schambacher <joerg@hifiberry.com>
+ *		Copyright 2018-21
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/tlv.h>
+
+#include "../codecs/pcm512x.h"
+#include "../codecs/pcm186x.h"
+#include "hifiberry_adc_controls.h"
+
+#define HIFIBERRY_DACPRO_NOCLOCK 0
+#define HIFIBERRY_DACPRO_CLK44EN 1
+#define HIFIBERRY_DACPRO_CLK48EN 2
+
+struct pcm512x_priv {
+	struct regmap *regmap;
+	struct clk *sclk;
+};
+
+/* Clock rate of CLK44EN attached to GPIO6 pin */
+#define CLK_44EN_RATE 22579200UL
+/* Clock rate of CLK48EN attached to GPIO3 pin */
+#define CLK_48EN_RATE 24576000UL
+
+static bool slave;
+static bool snd_rpi_hifiberry_is_dacpro;
+static bool digital_gain_0db_limit = true;
+static bool leds_off;
+
+static int pcm1863_add_controls(struct snd_soc_component *component)
+{
+	snd_soc_add_component_controls(component,
+			pcm1863_snd_controls_card,
+			ARRAY_SIZE(pcm1863_snd_controls_card));
+	return 0;
+}
+
+static void snd_rpi_hifiberry_dacplusadcpro_select_clk(
+					struct snd_soc_component *component, int clk_id)
+{
+	switch (clk_id) {
+	case HIFIBERRY_DACPRO_NOCLOCK:
+		snd_soc_component_update_bits(component,
+				PCM512x_GPIO_CONTROL_1, 0x24, 0x00);
+		break;
+	case HIFIBERRY_DACPRO_CLK44EN:
+		snd_soc_component_update_bits(component,
+				PCM512x_GPIO_CONTROL_1, 0x24, 0x20);
+		break;
+	case HIFIBERRY_DACPRO_CLK48EN:
+		snd_soc_component_update_bits(component,
+				PCM512x_GPIO_CONTROL_1, 0x24, 0x04);
+		break;
+	}
+	usleep_range(3000, 4000);
+}
+
+static void snd_rpi_hifiberry_dacplusadcpro_clk_gpio(struct snd_soc_component *component)
+{
+	snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x24, 0x24);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_3, 0x0f, 0x02);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_6, 0x0f, 0x02);
+}
+
+static bool snd_rpi_hifiberry_dacplusadcpro_is_sclk(struct snd_soc_component *component)
+{
+	unsigned int sck;
+
+	sck = snd_soc_component_read(component, PCM512x_RATE_DET_4);
+	return (!(sck & 0x40));
+}
+
+static bool snd_rpi_hifiberry_dacplusadcpro_is_pro_card(struct snd_soc_component *component)
+{
+	bool isClk44EN, isClk48En, isNoClk;
+
+	snd_rpi_hifiberry_dacplusadcpro_clk_gpio(component);
+
+	snd_rpi_hifiberry_dacplusadcpro_select_clk(component, HIFIBERRY_DACPRO_CLK44EN);
+	isClk44EN = snd_rpi_hifiberry_dacplusadcpro_is_sclk(component);
+
+	snd_rpi_hifiberry_dacplusadcpro_select_clk(component, HIFIBERRY_DACPRO_NOCLOCK);
+	isNoClk = snd_rpi_hifiberry_dacplusadcpro_is_sclk(component);
+
+	snd_rpi_hifiberry_dacplusadcpro_select_clk(component, HIFIBERRY_DACPRO_CLK48EN);
+	isClk48En = snd_rpi_hifiberry_dacplusadcpro_is_sclk(component);
+
+	return (isClk44EN && isClk48En && !isNoClk);
+}
+
+static int snd_rpi_hifiberry_dacplusadcpro_clk_for_rate(int sample_rate)
+{
+	int type;
+
+	switch (sample_rate) {
+	case 11025:
+	case 22050:
+	case 44100:
+	case 88200:
+	case 176400:
+	case 352800:
+		type = HIFIBERRY_DACPRO_CLK44EN;
+		break;
+	default:
+		type = HIFIBERRY_DACPRO_CLK48EN;
+		break;
+	}
+	return type;
+}
+
+static void snd_rpi_hifiberry_dacplusadcpro_set_sclk(struct snd_soc_component *component,
+	int sample_rate)
+{
+	struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
+
+	if (!IS_ERR(pcm512x->sclk)) {
+		int ctype;
+
+		ctype = snd_rpi_hifiberry_dacplusadcpro_clk_for_rate(sample_rate);
+		clk_set_rate(pcm512x->sclk, (ctype == HIFIBERRY_DACPRO_CLK44EN)
+			? CLK_44EN_RATE : CLK_48EN_RATE);
+		snd_rpi_hifiberry_dacplusadcpro_select_clk(component, ctype);
+	}
+}
+
+static int snd_rpi_hifiberry_dacplusadcpro_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_component *adc = snd_soc_rtd_to_codec(rtd, 1)->component;
+	struct snd_soc_dai_driver *adc_driver = snd_soc_rtd_to_codec(rtd, 1)->driver;
+	struct pcm512x_priv *priv;
+	int ret;
+
+	if (slave)
+		snd_rpi_hifiberry_is_dacpro = false;
+	else
+		snd_rpi_hifiberry_is_dacpro =
+				snd_rpi_hifiberry_dacplusadcpro_is_pro_card(dac);
+
+	if (snd_rpi_hifiberry_is_dacpro) {
+		struct snd_soc_dai_link *dai = rtd->dai_link;
+
+		dai->name = "HiFiBerry DAC+ADC Pro";
+		dai->stream_name = "HiFiBerry DAC+ADC Pro HiFi";
+		dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+			| SND_SOC_DAIFMT_CBM_CFM;
+
+		// set DAC DAI configuration
+		ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_codec(rtd, 0),
+				SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+			| SND_SOC_DAIFMT_CBM_CFM);
+		if (ret < 0)
+			return ret;
+
+		// set ADC DAI configuration
+		ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_codec(rtd, 1),
+				SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+			| SND_SOC_DAIFMT_CBS_CFS);
+		if (ret < 0)
+			return ret;
+
+		// set CPU DAI configuration
+		ret = snd_soc_dai_set_fmt(snd_soc_rtd_to_cpu(rtd, 0),
+			SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS);
+		if (ret < 0)
+			return ret;
+
+		snd_soc_component_update_bits(dac, PCM512x_BCLK_LRCLK_CFG, 0x31, 0x11);
+		snd_soc_component_update_bits(dac, PCM512x_MASTER_MODE, 0x03, 0x03);
+		snd_soc_component_update_bits(dac, PCM512x_MASTER_CLKDIV_2, 0x7f, 63);
+	} else {
+		priv = snd_soc_component_get_drvdata(dac);
+		priv->sclk = ERR_PTR(-ENOENT);
+	}
+
+	/* disable 24bit mode as long as I2S module does not have sign extension fixed */
+	adc_driver->capture.formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE;
+
+	snd_soc_component_update_bits(dac, PCM512x_GPIO_EN, 0x08, 0x08);
+	snd_soc_component_update_bits(dac, PCM512x_GPIO_OUTPUT_4, 0x0f, 0x02);
+	if (leds_off)
+		snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x00);
+	else
+		snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
+
+	ret = pcm1863_add_controls(adc);
+	if (ret < 0)
+		dev_warn(rtd->dev, "Failed to add pcm1863 controls: %d\n",
+		ret);
+
+	/* set GPIO2 to output, GPIO3 input */
+	snd_soc_component_write(adc, PCM186X_GPIO3_2_CTRL, 0x00);
+	snd_soc_component_write(adc, PCM186X_GPIO3_2_DIR_CTRL, 0x04);
+	if (leds_off)
+		snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x00);
+	else
+		snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x40);
+
+	if (digital_gain_0db_limit) {
+		int ret;
+		struct snd_soc_card *card = rtd->card;
+
+		ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207);
+		if (ret < 0)
+			dev_warn(card->dev, "Failed to set volume limit: %d\n", ret);
+	}
+
+	return 0;
+}
+
+static int snd_rpi_hifiberry_dacplusadcpro_update_rate_den(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; /* only use DAC */
+	struct pcm512x_priv *pcm512x = snd_soc_component_get_drvdata(component);
+	struct snd_ratnum *rats_no_pll;
+	unsigned int num = 0, den = 0;
+	int err;
+
+	rats_no_pll = devm_kzalloc(rtd->dev, sizeof(*rats_no_pll), GFP_KERNEL);
+	if (!rats_no_pll)
+		return -ENOMEM;
+
+	rats_no_pll->num = clk_get_rate(pcm512x->sclk) / 64;
+	rats_no_pll->den_min = 1;
+	rats_no_pll->den_max = 128;
+	rats_no_pll->den_step = 1;
+
+	err = snd_interval_ratnum(hw_param_interval(params,
+		SNDRV_PCM_HW_PARAM_RATE), 1, rats_no_pll, &num, &den);
+	if (err >= 0 && den) {
+		params->rate_num = num;
+		params->rate_den = den;
+	}
+
+	devm_kfree(rtd->dev, rats_no_pll);
+	return 0;
+}
+
+static int snd_rpi_hifiberry_dacplusadcpro_hw_params(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	int ret = 0;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	int channels = params_channels(params);
+	int width = snd_pcm_format_width(params_format(params));
+	struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_dai *dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_dai_driver *drv = dai->driver;
+	const struct snd_soc_dai_ops *ops = drv->ops;
+
+	/* Using powers of 2 allows for an integer clock divisor */
+	width = width <= 16 ? 16 : 32;
+
+	if (snd_rpi_hifiberry_is_dacpro) {
+		snd_rpi_hifiberry_dacplusadcpro_set_sclk(dac,
+			params_rate(params));
+
+		ret = snd_rpi_hifiberry_dacplusadcpro_update_rate_den(
+			substream, params);
+		if (ret)
+			return ret;
+	}
+
+	ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_cpu(rtd, 0), channels * width);
+	if (ret)
+		return ret;
+	ret = snd_soc_dai_set_bclk_ratio(snd_soc_rtd_to_codec(rtd, 0), channels * width);
+	if (ret)
+		return ret;
+	if (snd_rpi_hifiberry_is_dacpro && ops->hw_params)
+		ret = ops->hw_params(substream, params, dai);
+	return ret;
+}
+
+static int snd_rpi_hifiberry_dacplusadcpro_startup(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_component *adc = snd_soc_rtd_to_codec(rtd, 1)->component;
+
+	if (leds_off)
+		return 0;
+	/* switch on respective LED */
+	if (!substream->stream)
+		snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
+	else
+		snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x40);
+	return 0;
+}
+
+static void snd_rpi_hifiberry_dacplusadcpro_shutdown(
+	struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_component *adc = snd_soc_rtd_to_codec(rtd, 1)->component;
+
+	/* switch off respective LED */
+	if (!substream->stream)
+		snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x00);
+	else
+		snd_soc_component_update_bits(adc, PCM186X_GPIO_IN_OUT, 0x40, 0x00);
+}
+
+
+/* machine stream operations */
+static struct snd_soc_ops snd_rpi_hifiberry_dacplusadcpro_ops = {
+	.hw_params = snd_rpi_hifiberry_dacplusadcpro_hw_params,
+	.startup = snd_rpi_hifiberry_dacplusadcpro_startup,
+	.shutdown = snd_rpi_hifiberry_dacplusadcpro_shutdown,
+};
+
+SND_SOC_DAILINK_DEFS(hifi,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi"),
+			   COMP_CODEC("pcm186x.1-004a", "pcm1863-aif")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_hifiberry_dacplusadcpro_dai[] = {
+{
+	.name		= "HiFiBerry DAC+ADC PRO",
+	.stream_name	= "HiFiBerry DAC+ADC PRO HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBS_CFS,
+	.ops		= &snd_rpi_hifiberry_dacplusadcpro_ops,
+	.init		= snd_rpi_hifiberry_dacplusadcpro_init,
+	SND_SOC_DAILINK_REG(hifi),
+},
+};
+
+/* aux device for optional headphone amp */
+static struct snd_soc_aux_dev hifiberry_dacplusadcpro_aux_devs[] = {
+	{
+		.dlc = {
+			.name = "tpa6130a2.1-0060",
+		},
+	},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_hifiberry_dacplusadcpro = {
+	.name         = "snd_rpi_hifiberry_dacplusadcpro",
+	.driver_name  = "HifiberryDacpAdcPro",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_rpi_hifiberry_dacplusadcpro_dai,
+	.num_links    = ARRAY_SIZE(snd_rpi_hifiberry_dacplusadcpro_dai),
+};
+
+static int hb_hp_detect(void)
+{
+	struct i2c_adapter *adap = i2c_get_adapter(1);
+	int ret;
+	struct i2c_client tpa_i2c_client = {
+		.addr = 0x60,
+		.adapter = adap,
+	};
+
+	if (!adap)
+		return -EPROBE_DEFER;	/* I2C module not yet available */
+
+	ret = i2c_smbus_read_byte(&tpa_i2c_client) >= 0;
+	i2c_put_adapter(adap);
+	return ret;
+};
+
+static struct property tpa_enable_prop = {
+	       .name = "status",
+	       .length = 4 + 1, /* length 'okay' + 1 */
+	       .value = "okay",
+	};
+
+static int snd_rpi_hifiberry_dacplusadcpro_probe(struct platform_device *pdev)
+{
+	int ret = 0, i = 0;
+	struct snd_soc_card *card = &snd_rpi_hifiberry_dacplusadcpro;
+	struct device_node *tpa_node;
+	struct property *tpa_prop;
+	struct of_changeset ocs;
+	int len;
+
+	/* probe for head phone amp */
+	ret = hb_hp_detect();
+	if (ret < 0)
+		return ret;
+	if (ret) {
+		card->aux_dev = hifiberry_dacplusadcpro_aux_devs;
+		card->num_aux_devs =
+				ARRAY_SIZE(hifiberry_dacplusadcpro_aux_devs);
+		tpa_node = of_find_compatible_node(NULL, NULL, "ti,tpa6130a2");
+		tpa_prop = of_find_property(tpa_node, "status", &len);
+
+		if (strcmp((char *)tpa_prop->value, "okay")) {
+			/* and activate headphone using change_sets */
+			dev_info(&pdev->dev, "activating headphone amplifier");
+			of_changeset_init(&ocs);
+			ret = of_changeset_update_property(&ocs, tpa_node,
+							&tpa_enable_prop);
+			if (ret) {
+				dev_err(&pdev->dev,
+				"cannot activate headphone amplifier\n");
+				return -ENODEV;
+			}
+			ret = of_changeset_apply(&ocs);
+			if (ret) {
+				dev_err(&pdev->dev,
+				"cannot activate headphone amplifier\n");
+				return -ENODEV;
+			}
+		}
+	}
+
+	snd_rpi_hifiberry_dacplusadcpro.dev = &pdev->dev;
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai;
+
+		dai = &snd_rpi_hifiberry_dacplusadcpro_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+			"i2s-controller", 0);
+		if (i2s_node) {
+			for (i = 0; i < card->num_links; i++) {
+				dai->cpus->dai_name = NULL;
+				dai->cpus->of_node = i2s_node;
+				dai->platforms->name = NULL;
+				dai->platforms->of_node = i2s_node;
+			}
+		}
+	}
+	digital_gain_0db_limit = !of_property_read_bool(
+		pdev->dev.of_node, "hifiberry-dacplusadcpro,24db_digital_gain");
+	slave = of_property_read_bool(pdev->dev.of_node,
+					"hifiberry-dacplusadcpro,slave");
+	leds_off = of_property_read_bool(pdev->dev.of_node,
+					"hifiberry-dacplusadcpro,leds_off");
+	ret = snd_soc_register_card(&snd_rpi_hifiberry_dacplusadcpro);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+
+	return ret;
+}
+
+static const struct of_device_id snd_rpi_hifiberry_dacplusadcpro_of_match[] = {
+	{ .compatible = "hifiberry,hifiberry-dacplusadcpro", },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_dacplusadcpro_of_match);
+
+static struct platform_driver snd_rpi_hifiberry_dacplusadcpro_driver = {
+	.driver = {
+		.name   = "snd-rpi-hifiberry-dacplusadcpro",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_rpi_hifiberry_dacplusadcpro_of_match,
+	},
+	.probe          = snd_rpi_hifiberry_dacplusadcpro_probe,
+};
+
+module_platform_driver(snd_rpi_hifiberry_dacplusadcpro_driver);
+
+MODULE_AUTHOR("Joerg Schambacher <joerg@hifiberry.com>");
+MODULE_AUTHOR("Daniel Matuschek <daniel@hifiberry.com>");
+MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+ADC");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/hifiberry_dacplusdsp.c b/sound/soc/bcm/hifiberry_dacplusdsp.c
new file mode 100644
index 000000000000..5a6ea965042d
--- /dev/null
+++ b/sound/soc/bcm/hifiberry_dacplusdsp.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ASoC Driver for HiFiBerry DAC + DSP
+ *
+ * Author:	Joerg Schambacher <joscha@schambacher.com>
+ *		Copyright 2018
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <sound/soc.h>
+
+static struct snd_soc_component_driver dacplusdsp_component_driver;
+
+static struct snd_soc_dai_driver dacplusdsp_dai = {
+	.name = "dacplusdsp-hifi",
+	.capture = {
+		.stream_name = "DAC+DSP Capture",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_CONTINUOUS,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE |
+			   SNDRV_PCM_FMTBIT_S24_LE |
+			   SNDRV_PCM_FMTBIT_S32_LE,
+	},
+	.playback = {
+		.stream_name = "DACP+DSP Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_CONTINUOUS,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE |
+			   SNDRV_PCM_FMTBIT_S24_LE |
+			   SNDRV_PCM_FMTBIT_S32_LE,
+	},
+	.symmetric_rate = 1};
+
+#ifdef CONFIG_OF
+static const struct of_device_id dacplusdsp_ids[] = {
+	{
+		.compatible = "hifiberry,dacplusdsp",
+	},
+	{} };
+MODULE_DEVICE_TABLE(of, dacplusdsp_ids);
+#endif
+
+static int dacplusdsp_platform_probe(struct platform_device *pdev)
+{
+	int ret;
+
+	ret = snd_soc_register_component(&pdev->dev,
+			&dacplusdsp_component_driver, &dacplusdsp_dai, 1);
+	if (ret) {
+		pr_alert("snd_soc_register_component failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static void dacplusdsp_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_component(&pdev->dev);
+}
+
+static struct platform_driver dacplusdsp_driver = {
+	.driver = {
+		.name = "hifiberry-dacplusdsp-codec",
+		.of_match_table = of_match_ptr(dacplusdsp_ids),
+		},
+		.probe = dacplusdsp_platform_probe,
+		.remove = dacplusdsp_platform_remove,
+};
+
+module_platform_driver(dacplusdsp_driver);
+
+MODULE_AUTHOR("Joerg Schambacher <joerg@i2audio.com>");
+MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+DSP");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/hifiberry_dacplushd.c b/sound/soc/bcm/hifiberry_dacplushd.c
new file mode 100644
index 000000000000..4a5f7e4f95d9
--- /dev/null
+++ b/sound/soc/bcm/hifiberry_dacplushd.c
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ASoC Driver for HiFiBerry DAC+ HD
+ *
+ * Author:	Joerg Schambacher, i2Audio GmbH for HiFiBerry
+ *		Copyright 2020
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <linux/i2c.h>
+#include <linux/clk.h>
+
+#include "../codecs/pcm179x.h"
+
+#define DEFAULT_RATE		44100
+
+struct brd_drv_data {
+	struct regmap *regmap;
+	struct clk *sclk;
+};
+
+static struct brd_drv_data drvdata;
+static struct gpio_desc *reset_gpio;
+static const unsigned int hb_dacplushd_rates[] = {
+	192000, 96000, 48000, 176400, 88200, 44100,
+};
+
+static struct snd_pcm_hw_constraint_list hb_dacplushd_constraints = {
+	.list = hb_dacplushd_rates,
+	.count = ARRAY_SIZE(hb_dacplushd_rates),
+};
+
+static int snd_rpi_hb_dacplushd_startup(struct snd_pcm_substream *substream)
+{
+	/* constraints for standard sample rates */
+	snd_pcm_hw_constraint_list(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_RATE,
+				&hb_dacplushd_constraints);
+	return 0;
+}
+
+static void snd_rpi_hifiberry_dacplushd_set_sclk(
+		struct snd_soc_component *component,
+		int sample_rate)
+{
+	if (!IS_ERR(drvdata.sclk))
+		clk_set_rate(drvdata.sclk, sample_rate);
+}
+
+static int snd_rpi_hifiberry_dacplushd_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai_link *dai = rtd->dai_link;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+	dai->name = "HiFiBerry DAC+ HD";
+	dai->stream_name = "HiFiBerry DAC+ HD HiFi";
+	dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+		| SND_SOC_DAIFMT_CBM_CFM;
+
+	/* allow only fixed 32 clock counts per channel */
+	snd_soc_dai_set_bclk_ratio(cpu_dai, 32*2);
+
+	return 0;
+}
+
+static int snd_rpi_hifiberry_dacplushd_hw_params(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	int ret = 0;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+
+	snd_rpi_hifiberry_dacplushd_set_sclk(component, params_rate(params));
+	return ret;
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_rpi_hifiberry_dacplushd_ops = {
+	.startup = snd_rpi_hb_dacplushd_startup,
+	.hw_params = snd_rpi_hifiberry_dacplushd_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(hifi,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm179x.1-004c", "pcm179x-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+
+static struct snd_soc_dai_link snd_rpi_hifiberry_dacplushd_dai[] = {
+{
+	.name		= "HiFiBerry DAC+ HD",
+	.stream_name	= "HiFiBerry DAC+ HD HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBS_CFS,
+	.ops		= &snd_rpi_hifiberry_dacplushd_ops,
+	.init		= snd_rpi_hifiberry_dacplushd_init,
+	SND_SOC_DAILINK_REG(hifi),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_hifiberry_dacplushd = {
+	.name         = "snd_rpi_hifiberry_dacplushd",
+	.driver_name  = "HifiberryDacplusHD",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_rpi_hifiberry_dacplushd_dai,
+	.num_links    = ARRAY_SIZE(snd_rpi_hifiberry_dacplushd_dai),
+};
+
+static int snd_rpi_hifiberry_dacplushd_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	static int dac_reset_done;
+	struct device *dev = &pdev->dev;
+	struct device_node *dev_node = dev->of_node;
+
+	snd_rpi_hifiberry_dacplushd.dev = &pdev->dev;
+
+	/* get GPIO and release DAC from RESET */
+	if (!dac_reset_done) {
+		reset_gpio = gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW);
+		if (IS_ERR(reset_gpio)) {
+			dev_err(&pdev->dev, "gpiod_get() failed\n");
+			return -EINVAL;
+		}
+		dac_reset_done = 1;
+	}
+	if (!IS_ERR(reset_gpio))
+		gpiod_set_value(reset_gpio, 0);
+	msleep(1);
+	if (!IS_ERR(reset_gpio))
+		gpiod_set_value(reset_gpio, 1);
+	msleep(1);
+	if (!IS_ERR(reset_gpio))
+		gpiod_set_value(reset_gpio, 0);
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai;
+
+		dai = &snd_rpi_hifiberry_dacplushd_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+			"i2s-controller", 0);
+
+		if (i2s_node) {
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->of_node = i2s_node;
+			dai->cpus->dai_name = NULL;
+			dai->platforms->name = NULL;
+		} else {
+			return -EPROBE_DEFER;
+		}
+
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev,
+			&snd_rpi_hifiberry_dacplushd);
+	if (ret && ret != -EPROBE_DEFER) {
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+		return ret;
+	}
+	if (ret == -EPROBE_DEFER)
+		return ret;
+
+	dev_set_drvdata(dev, &drvdata);
+	if (dev_node == NULL) {
+		dev_err(&pdev->dev, "Device tree node not found\n");
+		return -ENODEV;
+	}
+
+	drvdata.sclk = devm_clk_get(dev, NULL);
+	if (IS_ERR(drvdata.sclk)) {
+		drvdata.sclk = ERR_PTR(-ENOENT);
+		return -ENODEV;
+	}
+
+	clk_set_rate(drvdata.sclk, DEFAULT_RATE);
+
+	return ret;
+}
+
+static void snd_rpi_hifiberry_dacplushd_remove(struct platform_device *pdev)
+{
+	/* put DAC into RESET and release GPIO */
+	gpiod_set_value(reset_gpio, 0);
+	gpiod_put(reset_gpio);
+}
+
+static const struct of_device_id snd_rpi_hifiberry_dacplushd_of_match[] = {
+	{ .compatible = "hifiberry,hifiberry-dacplushd", },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_dacplushd_of_match);
+
+static struct platform_driver snd_rpi_hifiberry_dacplushd_driver = {
+	.driver = {
+		.name   = "snd-rpi-hifiberry-dacplushd",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_rpi_hifiberry_dacplushd_of_match,
+	},
+	.probe          = snd_rpi_hifiberry_dacplushd_probe,
+	.remove		= snd_rpi_hifiberry_dacplushd_remove,
+};
+
+module_platform_driver(snd_rpi_hifiberry_dacplushd_driver);
+
+MODULE_AUTHOR("Joerg Schambacher <joerg@i2audio.com>");
+MODULE_DESCRIPTION("ASoC Driver for HiFiBerry DAC+ HD");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/hifiberry_studio_dac8x.c b/sound/soc/bcm/hifiberry_studio_dac8x.c
new file mode 100644
index 000000000000..ecc84cc0287f
--- /dev/null
+++ b/sound/soc/bcm/hifiberry_studio_dac8x.c
@@ -0,0 +1,938 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * hifiberry_studio_dac8x.c -- driver for more complex
+ * multichannel soundcards with own onboard firmware.
+ *
+ * Copyright (C) 2026 HiFiBerry
+ *
+ * Author: Joerg Schambacher <joerg@hifiberry.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <linux/uuid.h>
+#include <linux/slab.h>
+
+/* register definitions and firmware settings */
+#define FIRMWARE_MAJOR			0x00
+#define FIRMWARE_MINOR			0x01
+#define FIRMWARE_SUBVERSION		0x02
+#define HARDWARE_MAJOR			0x03
+#define HARDWARE_MINOR			0x04
+#define HARDWARE_SUBVERSION		0x05
+#define UUID				0x10
+#define SUPPORTED_RATES_0		0x20
+#define SUPPORTED_RATES_1		0x21
+#define SUPPORTED_RATES_2		0x22
+#define SUPPORTED_RATES_3		0x23
+#define SUPPORTED_FORMATS_0		0x24
+#define SUPPORTED_FORMATS_1		0x25
+#define SUPPORTED_FORMATS_2		0x26
+#define SUPPORTED_FORMATS_3		0x27
+#define NUM_OF_INPUT_CH			0x28
+#define NUM_OF_OUTPUT_CH		0x29
+#define CARD_RESET			0x2A
+#define CURRENT_RATE			0x2B
+#define CURRENT_FORMAT			0x2C
+#define MAX_VOLUME			0x2D
+#define MIN_VOLUME			0x2E
+#define VOLUME_STP			0x2F
+#define MAX_GAIN			0x30
+#define MIN_GAIN			0x31
+#define GAIN_STP			0x32
+#define CARD_BUSY			0x33
+#define CARD_NOERR			0x34
+#define CARD_CLK_OPTIONS		0x35
+#define CARD_CLOCK_MODE			0x36
+#define DAC_STATE			0x40
+#define DAC_CLOCK_SOURCE		0x41
+#define DAC_SYS_CLK			0x42
+#define DAC_SAMPLE_FORMAT		0x43
+#define DAC_FILTER_SETTING_0		0x44
+#define DAC_FILTER_SETTING_1		0x45
+#define DAC_FILTER_SETTING_2		0x46
+#define DAC_FILTER_SETTING_3		0x47
+#define DAC_OUTPUT_MODE			0x48
+#define MASTER_VOL			0x50
+#define VOL_CH0				0x51
+#define VOL_CH1				0x52
+#define VOL_CH2				0x53
+#define VOL_CH3				0x54
+#define VOL_CH4				0x55
+#define VOL_CH5				0x56
+#define VOL_CH6				0x57
+#define VOL_CH7				0x58
+#define MUTE_OUTPUTS			0x59
+#define ADC_INPUT_MODE			0x70
+#define ADC_STATE			0x70
+#define ADC_CLOCK_SOURCE		0x71
+#define ADC_SYS_CLK			0x72
+#define ADC_SAMPLE_FORMAT		0x73
+#define ADC_FILTER_SETTING_0		0x74
+#define ADC_FILTER_SETTING_1		0x75
+#define ADC_FILTER_SETTING_2		0x76
+#define ADC_FILTER_SETTING_3		0x77
+#define ADC_CLIPPING_ATT		0x78
+#define GAIN_CH0			0x81
+#define GAIN_CH1			0x82
+#define GAIN_CH2			0x83
+#define GAIN_CH3			0x84
+#define GAIN_CH4			0x85
+#define GAIN_CH5			0x86
+#define GAIN_CH6			0x87
+#define GAIN_CH7			0x88
+#define MUTE_INPUTS			0x89
+#define CLOCK_CONSUMER_MODE		0x00
+#define CLOCK_PROVIDER_MODE		0x01
+
+/* Mask encoding for provider frequency settings */
+#define MASK_5512			0x00
+#define MASK_8000			0x01
+#define MASK_11025			0x02
+#define MASK_16000			0x03
+#define MASK_22050			0x04
+#define MASK_32000			0x05
+#define MASK_44100			0x06
+#define MASK_48000			0x07
+#define MASK_64000			0x08
+#define MASK_88200			0x09
+#define MASK_96000			0x0A
+#define MASK_176400			0x0B
+#define MASK_192000			0x0C
+#define MASK_352800			0x0D
+#define MASK_384000			0x0E
+
+/* Mask encoding for sample formats */
+#define MASK_16_BIT_SF			0x01
+#define MASK_24_BIT_SF			0x02
+#define MASK_32_BIT_SF			0x03
+
+/* struct definition for easier access to firmware registers */
+struct hb_studio_dac8x_regs_t {
+	unsigned char firmware_major;
+	unsigned char firmware_minor;
+	unsigned char firmware_subversion;
+	unsigned char hardware_major;
+	unsigned char hardware_minor;
+	unsigned char hardware_subversion;
+	unsigned char res1[10];
+	uuid_t uuid;
+	unsigned int supported_rates;		// 0x20
+	unsigned int supported_formats;		// 0x24
+	unsigned char num_of_input_ch;		// 0x28
+	unsigned char num_of_output_ch;		// 0x29
+	unsigned char card_reset;		// 0x2a
+	unsigned char current_rate;		// 0x2b
+	unsigned char current_format;		// 0x2c
+	unsigned char max_volume;		// 0x2d
+	unsigned char min_volume;		// 0x2e
+	unsigned char volume_stp;		// 0x2f
+	unsigned char max_gain;			// 0x30
+	unsigned char min_gain;			// 0x31
+	unsigned char gain_stp;			// 0x32
+	unsigned char card_busy;		// 0x33
+	unsigned char card_noerr;		// 0x34
+	unsigned char card_clk_options;		// 0x35
+	unsigned char card_clk_mode;		// 0x36
+	unsigned char res3[9];			// 0x37
+	unsigned char dac_state;		// 0x40
+	unsigned char dac_clock_source;		// 0x41
+	unsigned char dac_sys_clk;		// 0x42
+	unsigned char dac_sample_format;
+	unsigned char dac_filter_setting_0;
+	unsigned char dac_filter_setting_1;
+	unsigned char dac_filter_setting_2;
+	unsigned char dac_filter_setting_3;
+	unsigned char dac_output_mode;		// 0x48
+	unsigned char res4[7];			// 0x49 - 0x4f
+	unsigned char master_vol;		// 0x50
+	unsigned char vol_ch0;			// 0x51
+	unsigned char vol_ch1;			// 0x52
+	unsigned char vol_ch2;			// 0x53
+	unsigned char vol_ch3;			// 0x54
+	unsigned char vol_ch4;			// 0x55
+	unsigned char vol_ch5;			// 0x56
+	unsigned char vol_ch6;			// 0x57
+	unsigned char vol_ch7;			// 0x58
+	unsigned char mute_outputs;		// 0x59
+	unsigned char res5[22];			// 0x5a- 0x6f
+	unsigned char adc_input_mode;		// 0x70
+	unsigned char adc_state;		// 0x70
+	unsigned char adc_clock_source;		// 0x71
+	unsigned char adc_sys_clk;		// 0x72
+	unsigned char adc_sample_format;	// 0x73
+	unsigned char adc_filter_setting_0;	// 0x74
+	unsigned char adc_filter_setting_1;	// 0x75
+	unsigned char adc_filter_setting_2;	// 0x76
+	unsigned char adc_filter_setting_3;	// 0x77
+	unsigned char adc_clipping_att;		// 0x78
+	unsigned char res6[7];			// 0x79- 0x7f
+	unsigned char res[1];			// 0x80
+	unsigned char gain_ch0;			// 0x81
+	unsigned char gain_ch1;			// 0x82
+	unsigned char gain_ch2;			// 0x83
+	unsigned char gain_ch3;			// 0x84
+	unsigned char gain_ch4;			// 0x85
+	unsigned char gain_ch5;			// 0x86
+	unsigned char gain_ch6;			// 0x87
+	unsigned char gain_ch7;			// 0x88
+	unsigned char mute_inputs;		// 0x89
+	};
+
+
+static struct snd_soc_card snd_rpi_hifiberry_studio_dac8x;
+static struct i2c_client *hb_uni_i2c_client;
+struct hb_uni_private {
+	struct regmap *regmap;
+	uuid_t uuid;
+	unsigned int sample_bits;
+	unsigned int current_rate;
+	struct hb_studio_dac8x_regs_t card_info;
+};
+
+static struct hb_uni_private *priv;
+static bool card_is_clk_provider;
+
+static bool hb_uni_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case CARD_BUSY:
+	case CARD_RESET:
+	case DAC_STATE:
+	case DAC_CLOCK_SOURCE:
+	case DAC_SYS_CLK:
+	case MASTER_VOL:
+	case VOL_CH0:
+	case VOL_CH1:
+	case VOL_CH2:
+	case VOL_CH3:
+	case VOL_CH4:
+	case VOL_CH5:
+	case VOL_CH6:
+	case VOL_CH7:
+	case GAIN_CH0:
+	case GAIN_CH1:
+	case GAIN_CH2:
+	case GAIN_CH3:
+	case GAIN_CH4:
+	case GAIN_CH5:
+	case GAIN_CH6:
+	case GAIN_CH7:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool hb_uni_readable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case FIRMWARE_MAJOR:
+	case FIRMWARE_MINOR:
+	case FIRMWARE_SUBVERSION:
+	case HARDWARE_MAJOR:
+	case HARDWARE_MINOR:
+	case HARDWARE_SUBVERSION:
+	case NUM_OF_INPUT_CH:
+	case NUM_OF_OUTPUT_CH:
+	case SUPPORTED_RATES_0:
+	case SUPPORTED_RATES_1:
+	case SUPPORTED_RATES_2:
+	case SUPPORTED_RATES_3:
+	case SUPPORTED_FORMATS_0:
+	case SUPPORTED_FORMATS_1:
+	case SUPPORTED_FORMATS_2:
+	case SUPPORTED_FORMATS_3:
+	case UUID:
+	case DAC_STATE:
+	case CARD_BUSY:
+	case CARD_RESET:
+	case CARD_CLOCK_MODE:
+	case DAC_CLOCK_SOURCE:
+	case DAC_SYS_CLK:
+	case DAC_SAMPLE_FORMAT:
+	case DAC_FILTER_SETTING_0:
+	case DAC_FILTER_SETTING_1:
+	case DAC_FILTER_SETTING_2:
+	case DAC_FILTER_SETTING_3:
+	case DAC_OUTPUT_MODE:
+	case GAIN_CH0:
+	case GAIN_CH1:
+	case GAIN_CH2:
+	case GAIN_CH3:
+	case GAIN_CH4:
+	case GAIN_CH5:
+	case GAIN_CH6:
+	case GAIN_CH7:
+	case ADC_CLIPPING_ATT:
+		return true;
+	default:
+		return reg < 0xff;
+	}
+}
+
+static const struct regmap_config hb_uni_regmap = {
+	.reg_bits         = 8,
+	.val_bits         = 8,
+	.max_register     = 0xff,
+	.cache_type       = REGCACHE_RBTREE,
+	.volatile_reg     = hb_uni_volatile_reg,
+	.readable_reg     = hb_uni_readable_reg,
+};
+
+static const DECLARE_TLV_DB_MINMAX(adc_att_tlv, -600, -300);
+static const DECLARE_TLV_DB_MINMAX(gain_tlv, -1200, 4000);
+static const DECLARE_TLV_DB_MINMAX(volume_tlv, -10300, 2400);
+static const DECLARE_TLV_DB_MINMAX(spkr_tlv, -10300, 0);
+static const char * const pll_lock_texts[] = {"unlocked", "locked"};
+static const char * const mute_texts[] = {"unmuted", "muted"};
+static const char * const dac_filter_texts[] = {
+	"FIR w/ De-Emph.",
+	"Low Latency IIR w/ De-Emph.",
+	"High Att. w/ De-Emph.",
+	"Ringingless Low Latency FIR w/o Deemph.",
+	};
+static const char * const adc_att_texts[] = {
+	"Clip att. off", "-3dB", "-4dB", "-5dB", "-6dB",
+	};
+
+struct hb_uni_vol_control_single {
+	unsigned int reg;
+	unsigned int shift;
+	int min;
+	int max;
+	bool invert;
+	const unsigned int *tlv;
+};
+
+static int hb_uni_vol_info_single(struct snd_kcontrol *kcontrol,
+			  struct snd_ctl_elem_info *uinfo)
+{
+	struct hb_uni_vol_control_single *ctl = (void *)kcontrol->private_value;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;  // mono
+	uinfo->value.integer.min = ctl->min;
+	uinfo->value.integer.max = ctl->max;
+	uinfo->value.integer.step = 1;
+	return 0;
+}
+
+static int hb_uni_vol_get_single(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	struct hb_uni_vol_control_single *ctl = (void *)kcontrol->private_value;
+	unsigned int val;
+
+	regmap_read(priv->regmap, ctl->reg, &val);
+	val = (val >> ctl->shift) & 0xff;
+	if (ctl->invert)
+		val = ctl->max - val;
+	ucontrol->value.integer.value[0] = val;
+	return 0;
+}
+
+static int hb_uni_vol_put_single(struct snd_kcontrol *kcontrol,
+			 struct snd_ctl_elem_value *ucontrol)
+{
+	struct hb_uni_vol_control_single *ctl = (void *)kcontrol->private_value;
+	unsigned int val = ucontrol->value.integer.value[0];
+	unsigned int new;
+
+	if (ctl->invert)
+		val = ctl->max - val;
+	new = (val & 0xff) << ctl->shift;
+	regmap_write(priv->regmap, ctl->reg, new);
+	return 0;
+}
+
+struct hb_uni_enum_control {
+	unsigned int reg;
+	unsigned int shift;
+	unsigned int mask;
+	const char * const *texts;
+	unsigned int items;
+};
+
+static int hb_uni_enum_info(struct snd_kcontrol *kcontrol,
+			    struct snd_ctl_elem_info *uinfo)
+{
+	struct hb_uni_enum_control *ctl = (void *)kcontrol->private_value;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = ctl->items;
+
+	if (uinfo->value.enumerated.item >= ctl->items)
+		uinfo->value.enumerated.item = ctl->items - 1;
+
+	strscpy(uinfo->value.enumerated.name,
+		ctl->texts[uinfo->value.enumerated.item],
+		sizeof(uinfo->value.enumerated.name) - 1);
+
+	return 0;
+}
+
+static int hb_uni_enum_get(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct hb_uni_enum_control *ctl = (void *)kcontrol->private_value;
+	unsigned int val;
+
+	regmap_read(priv->regmap, ctl->reg, &val);
+
+	val = (val >> ctl->shift) & ctl->mask;
+	if (val >= ctl->items)
+		val = 0;
+
+	ucontrol->value.enumerated.item[0] = val;
+
+	return 0;
+}
+
+static int hb_uni_enum_put(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct hb_uni_enum_control *ctl = (void *)kcontrol->private_value;
+	unsigned int val = ucontrol->value.enumerated.item[0];
+
+	if (val >= ctl->items)
+		return -EINVAL;
+
+	regmap_update_bits(priv->regmap, ctl->reg,
+			   ctl->mask << ctl->shift,
+			   (val & ctl->mask) << ctl->shift);
+
+	return 0;
+}
+
+#define VOL_CTL_SINGLE(kname, controls, ktlv) {\
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.name = kname, \
+	.access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
+		  SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+	.tlv.p = ktlv, \
+	.info = hb_uni_vol_info_single, \
+	.get = hb_uni_vol_get_single, \
+	.put = hb_uni_vol_put_single, \
+	.private_value = (unsigned long)&controls, }
+
+#define ENUM_CTL_SINGLE(kname, controls) {\
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.name = kname, \
+	.info = hb_uni_enum_info, \
+	.get  = hb_uni_enum_get, \
+	.put  = hb_uni_enum_put, \
+	.private_value = (unsigned long)&controls, }
+
+#define ENUM_CTL_SINGLE_RO(kname, controls) {\
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.name = kname, \
+	.info = hb_uni_enum_info, \
+	.get  = hb_uni_enum_get, \
+	.put  = NULL, \
+	.private_value = (unsigned long)&controls, }
+
+static const struct hb_uni_vol_control_single hb_uni_vol_ctls_single[] = {
+	{ MASTER_VOL, 0, 0, 254, true, volume_tlv },
+	{ VOL_CH0, 0, 0, 206, true, spkr_tlv },
+	{ VOL_CH1, 0, 0, 206, true, spkr_tlv },
+	{ VOL_CH2, 0, 0, 206, true, spkr_tlv },
+	{ VOL_CH3, 0, 0, 206, true, spkr_tlv },
+	{ VOL_CH4, 0, 0, 206, true, spkr_tlv },
+	{ VOL_CH5, 0, 0, 206, true, spkr_tlv },
+	{ VOL_CH6, 0, 0, 206, true, spkr_tlv },
+	{ VOL_CH7, 0, 0, 206, true, spkr_tlv },
+};
+
+static const struct hb_uni_vol_control_single hb_uni_gain_ctls_single[] = {
+	{ GAIN_CH0, 0, 0, 104, false, gain_tlv },
+	{ GAIN_CH1, 0, 0, 104, false, gain_tlv },
+	{ GAIN_CH2, 0, 0, 104, false, gain_tlv },
+	{ GAIN_CH3, 0, 0, 104, false, gain_tlv },
+	{ GAIN_CH4, 0, 0, 104, false, gain_tlv },
+	{ GAIN_CH5, 0, 0, 104, false, gain_tlv },
+	{ GAIN_CH6, 0, 0, 104, false, gain_tlv },
+	{ GAIN_CH7, 0, 0, 104, false, gain_tlv },
+};
+
+static const struct snd_kcontrol_new hb_uni_play_controls_single[] = {
+	VOL_CTL_SINGLE("Master Playback Volume",    hb_uni_vol_ctls_single[0], volume_tlv),
+	VOL_CTL_SINGLE("Output Ch0 Playback Volume", hb_uni_vol_ctls_single[1], spkr_tlv),
+	VOL_CTL_SINGLE("Output Ch1 Playback Volume", hb_uni_vol_ctls_single[2], spkr_tlv),
+	VOL_CTL_SINGLE("Output Ch2 Playback Volume", hb_uni_vol_ctls_single[3], spkr_tlv),
+	VOL_CTL_SINGLE("Output Ch3 Playback Volume", hb_uni_vol_ctls_single[4], spkr_tlv),
+	VOL_CTL_SINGLE("Output Ch4 Playback Volume", hb_uni_vol_ctls_single[5], spkr_tlv),
+	VOL_CTL_SINGLE("Output Ch5 Playback Volume", hb_uni_vol_ctls_single[6], spkr_tlv),
+	VOL_CTL_SINGLE("Output Ch6 Playback Volume", hb_uni_vol_ctls_single[7], spkr_tlv),
+	VOL_CTL_SINGLE("Output Ch7 Playback Volume", hb_uni_vol_ctls_single[8], spkr_tlv),
+};
+
+static const struct snd_kcontrol_new hb_uni_rec_controls_single[] = {
+	VOL_CTL_SINGLE("Input Ch0 Capture Volume", hb_uni_gain_ctls_single[0], gain_tlv),
+	VOL_CTL_SINGLE("Input Ch1 Capture Volume", hb_uni_gain_ctls_single[1], gain_tlv),
+	VOL_CTL_SINGLE("Input Ch2 Capture Volume", hb_uni_gain_ctls_single[2], gain_tlv),
+	VOL_CTL_SINGLE("Input Ch3 Capture Volume", hb_uni_gain_ctls_single[3], gain_tlv),
+	VOL_CTL_SINGLE("Input Ch4 Capture Volume", hb_uni_gain_ctls_single[4], gain_tlv),
+	VOL_CTL_SINGLE("Input Ch5 Capture Volume", hb_uni_gain_ctls_single[5], gain_tlv),
+	VOL_CTL_SINGLE("Input Ch6 Capture Volume", hb_uni_gain_ctls_single[6], gain_tlv),
+	VOL_CTL_SINGLE("Input Ch7 Capture Volume", hb_uni_gain_ctls_single[7], gain_tlv),
+};
+
+static const struct hb_uni_enum_control hb_uni_play_enum_ctls[] = {
+	{ DAC_STATE, 0, 0x1, pll_lock_texts, ARRAY_SIZE(pll_lock_texts) },
+	{ DAC_FILTER_SETTING_0, 0, 0x03, dac_filter_texts, ARRAY_SIZE(dac_filter_texts) },
+	{ MUTE_OUTPUTS, 0, 0x01, mute_texts, ARRAY_SIZE(mute_texts) },
+};
+
+static const struct hb_uni_enum_control hb_uni_rec_enum_ctls[] = {
+	{ ADC_CLIPPING_ATT, 0, 0x7, adc_att_texts, ARRAY_SIZE(adc_att_texts) },
+};
+
+static const struct snd_kcontrol_new hb_uni_gen_controls_single[] = {
+	ENUM_CTL_SINGLE("DAC Filter", hb_uni_play_enum_ctls[1]),
+	ENUM_CTL_SINGLE("DAC Mute", hb_uni_play_enum_ctls[2]),
+};
+
+static const struct snd_kcontrol_new adc_controls_single[] = {
+	ENUM_CTL_SINGLE("Clipping Attenuation Capture Volume", hb_uni_rec_enum_ctls[0]),
+};
+
+static int snd_rpi_hifiberry_studio_dac8x_hw_params(
+		struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	struct device *dev = rtd->dev;
+	unsigned char tmp;
+	int trials;
+	int busy;
+	int err;
+
+	priv->sample_bits = snd_pcm_format_width(params_format(params));
+	priv->sample_bits = priv->sample_bits <= 16 ? 16 : 32;
+
+	priv->current_rate = params_rate(params);
+	dev_info(dev, "using %ibits @ %isps\n",
+		priv->sample_bits, priv->current_rate);
+
+	/* write requested samplerate and word length back to card */
+	switch (priv->current_rate) {
+	case 5512:
+		tmp = MASK_5512;
+		break;
+	case 8000:
+		tmp = MASK_8000;
+		break;
+	case 11025:
+		tmp = MASK_11025;
+		break;
+	case 16000:
+		tmp = MASK_16000;
+		break;
+	case 22050:
+		tmp = MASK_22050;
+		break;
+	case 32000:
+		tmp = MASK_32000;
+		break;
+	case 44100:
+		tmp = MASK_44100;
+		break;
+	case 88200:
+		tmp = MASK_88200;
+		break;
+	case 176400:
+		tmp = MASK_176400;
+		break;
+	case 352800:
+		tmp = MASK_352800;
+		break;
+	case 48000:
+		tmp = MASK_48000;
+		break;
+	case 96000:
+		tmp = MASK_96000;
+		break;
+	case 192000:
+		tmp = MASK_192000;
+		break;
+	case 384000:
+		tmp = MASK_384000;
+		break;
+	default:
+		dev_info(dev, "rate not supported (%u)\n", priv->current_rate);
+		return -EINVAL;
+	}
+
+	err = regmap_write(priv->regmap, CURRENT_RATE, tmp);
+	if (err < 0)
+		return err;
+
+	switch (priv->sample_bits) {
+	case 16:
+		tmp = MASK_16_BIT_SF;
+		break;
+	case 24:
+		tmp = MASK_24_BIT_SF;
+		break;
+	case 32:
+		tmp = MASK_32_BIT_SF;
+		break;
+	default:
+		dev_info(dev, "word length not supported (%u)\n",
+		priv->sample_bits);
+		return -EINVAL;
+	}
+	err = regmap_write(priv->regmap, CURRENT_FORMAT, tmp);
+	if (err < 0)
+		return err;
+
+	/* If card provides clocks wait max. ~40ms for PLL */
+	if (card_is_clk_provider) {
+	/* trigger card to set new rate and format */
+		err = regmap_write(priv->regmap, CARD_CLOCK_MODE, 0x02);
+		if (err < 0)
+			return err;
+		trials = 10;
+		do {
+			usleep_range(3000, 4000);
+			regmap_read(priv->regmap, CARD_BUSY, &busy);
+		} while (busy && --trials);
+		if (!trials) {
+			dev_err(dev, "Card is unable to set clocks\n");
+			return -EINVAL;
+		}
+	}
+	/* always run with 64bit frames */
+	return snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
+}
+
+static const struct snd_soc_ops snd_rpi_hifiberry_studio_dac8x_ops = {
+	.hw_params = snd_rpi_hifiberry_studio_dac8x_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(hifiberry_studio_dac8x,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("snd-soc-dummy", "snd-soc-dummy-dai")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static int hifiberry_studio_dac8x_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_card *card = rtd->card;
+
+	/* Configure playback */
+	codec_dai->driver->playback.channels_max =
+		priv->card_info.num_of_output_ch;
+	codec_dai->driver->playback.rates = priv->card_info.supported_rates;
+	codec_dai->driver->playback.formats = priv->card_info.supported_formats;
+
+	if (priv->card_info.num_of_input_ch) {
+		struct snd_soc_dai_link *dai = rtd->dai_link;
+
+		dev_info(card->dev, "inputs detected: capture enabled\n");
+		codec_dai->driver->symmetric_rate = 1;
+		codec_dai->driver->symmetric_sample_bits = 1;
+		codec_dai->driver->capture.formats =
+			priv->card_info.supported_formats;
+		codec_dai->driver->capture.rates =
+			priv->card_info.supported_rates;
+		codec_dai->driver->capture.channels_max =
+			priv->card_info.num_of_input_ch;
+		dai->name = "HiFiBerry Studio DAC8x-ADC8x";
+		dai->stream_name = "HiFiBerry Studio HiFi";
+	} else {
+		rtd->dai_link->playback_only = 1;  // Disable capture
+	}
+
+	if ((priv->card_info.card_clk_options & 0x02) && card_is_clk_provider) {
+		struct snd_soc_dai_link *dai = rtd->dai_link;
+
+		dai->stream_name = "HiFiBerry Studio Pro HiFi";
+		dai->dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+			| SND_SOC_DAIFMT_CBM_CFM;
+	}
+	dev_info(card->dev,
+		 "HiFiBerry Studio DAC8x successfully initialized\n");
+
+	return 0;
+}
+
+static struct snd_soc_dai_link snd_rpi_hifiberry_studio_dac8x_dai[] = {
+	{
+		.name           = "HiFiBerry Studio DAC8x",
+		.stream_name    = "HifiBerry Studio HiFi",
+		.dai_fmt        = SND_SOC_DAIFMT_I2S |
+					SND_SOC_DAIFMT_NB_NF |
+					SND_SOC_DAIFMT_CBS_CFS,
+		.init           = hifiberry_studio_dac8x_init,
+		.ops            = &snd_rpi_hifiberry_studio_dac8x_ops,
+		SND_SOC_DAILINK_REG(hifiberry_studio_dac8x),
+	},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_hifiberry_studio_dac8x = {
+	.name         = "Hifiberry Studio DAC8x",
+	.driver_name  = "HifiberryStudio",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_rpi_hifiberry_studio_dac8x_dai,
+	.num_links    = ARRAY_SIZE(snd_rpi_hifiberry_studio_dac8x_dai),
+};
+
+static int hb_uni_read_card_info(struct platform_device *pdev)
+{
+	int ret;
+
+	/* read basic card info */
+	ret = regmap_bulk_read(priv->regmap, 0x00, &priv->card_info, 0x06);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to read card info: %d\n", ret);
+	return ret;
+	}
+
+	dev_info(&pdev->dev, "hardware V%d.%d.%d\n",
+		priv->card_info.hardware_major,
+		priv->card_info.hardware_minor,
+		priv->card_info.hardware_subversion
+		);
+
+	dev_info(&pdev->dev, "firmware V%d.%d.%d\n",
+		priv->card_info.firmware_major,
+		priv->card_info.firmware_minor,
+		priv->card_info.firmware_subversion
+		);
+
+	/* read card capabilities */
+	ret = regmap_bulk_read(priv->regmap, UUID, &priv->card_info.uuid, 0x20);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to read card info: %d\n", ret);
+		return ret;
+	}
+
+	dev_info(&pdev->dev, "UUID: %*phN\n",
+		(int)sizeof(priv->card_info.uuid.b), priv->card_info.uuid.b);
+	dev_info(&pdev->dev, "%i output channels reported\n",
+				priv->card_info.num_of_output_ch);
+	dev_dbg(&pdev->dev, "supported rates %08x\n",
+				priv->card_info.supported_rates);
+	dev_dbg(&pdev->dev, "supported formats %08x\n",
+				priv->card_info.supported_formats);
+
+	if (priv->card_info.num_of_output_ch > 8 ||
+	    priv->card_info.num_of_input_ch > 8) {
+		dev_err(&pdev->dev, "Maximum of 8 channels exceeded!\n");
+		return -EINVAL;
+	}
+
+	if (priv->card_info.num_of_input_ch > 0) {
+		dev_info(&pdev->dev,
+			"Inputs detected: %u channels\n",
+			priv->card_info.num_of_input_ch);
+	} else {
+		dev_info(&pdev->dev, "No inputs present, playback only\n");
+	}
+
+	ret = regmap_bulk_read(priv->regmap, CARD_BUSY,
+				&priv->card_info.card_busy, 20);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to read card info: %d\n", ret);
+		return ret;
+	}
+
+	if (card_is_clk_provider) {
+		if (priv->card_info.card_clk_options & 0x02) {
+			dev_info(&pdev->dev, "Card provides i2s clocks\n");
+		} else {
+			dev_err(&pdev->dev,
+				"Card cannot provide i2s clocks\n");
+			return -EINVAL;
+		}
+	} else {
+		if ((priv->card_info.card_clk_options == 0x02)) {
+			dev_err(&pdev->dev,
+				"Card cannot run as i2s clock consumer\n");
+			return -EINVAL;
+		}
+	}
+
+	switch (cpu_to_be32(*(unsigned int *)&priv->card_info.uuid)) {
+	case 0x74e7ae95:
+		if (card_is_clk_provider)
+			snd_rpi_hifiberry_studio_dac8x.name =
+						"HiFiBerry Studio DAC8x Pro";
+		else
+			snd_rpi_hifiberry_studio_dac8x.name =
+						"HiFiBerry Studio DAC8x";
+		break;
+	default:
+		break;
+	}
+
+
+	regcache_cache_only(priv->regmap, true);
+	ret = regmap_bulk_read(priv->regmap, MASTER_VOL,
+			&priv->card_info.master_vol, MUTE_OUTPUTS - MASTER_VOL);
+	regcache_cache_only(priv->regmap, false);
+
+	return 0;
+}
+
+static int hb_uni_add_card_controls(struct platform_device *pdev)
+{
+	int ret;
+
+	ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio_dac8x,
+			hb_uni_gen_controls_single,
+			ARRAY_SIZE(hb_uni_gen_controls_single));
+	if (ret < 0) {
+		dev_err(&pdev->dev,
+			"snd_soc_add_card_controls() failed: %d\n", ret);
+		return ret;
+	}
+	ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio_dac8x,
+			hb_uni_play_controls_single,
+			ARRAY_SIZE(hb_uni_play_controls_single) / 9 *
+					(priv->card_info.num_of_output_ch + 1));
+	if (ret < 0) {
+		dev_err(&pdev->dev,
+			"snd_soc_add_card_controls() failed: %d\n", ret);
+		return ret;
+	}
+
+	/* add optional ADC controls if inputs detected */
+	if (priv->card_info.num_of_input_ch > 0) {
+		ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio_dac8x,
+			hb_uni_rec_controls_single,
+			ARRAY_SIZE(hb_uni_rec_controls_single) / 8 *
+					priv->card_info.num_of_input_ch);
+		if (ret < 0) {
+			dev_err(&pdev->dev,
+				"snd_soc_add_card_controls() failed: %d\n", ret);
+		}
+		ret = snd_soc_add_card_controls(&snd_rpi_hifiberry_studio_dac8x,
+			adc_controls_single,
+			ARRAY_SIZE(adc_controls_single));
+		if (ret < 0) {
+			dev_err(&pdev->dev,
+				"snd_soc_add_card_controls() failed: %d\n", ret);
+		}
+	}
+	return ret;
+}
+
+static int hb_controller_probe(struct platform_device *pdev)
+{
+	struct i2c_adapter *adap = i2c_get_adapter(1);
+	struct device_node *np = pdev->dev.of_node;
+	int ret;
+
+	if (!adap)
+		return -EPROBE_DEFER;   /* I2C module not yet available */
+
+	struct i2c_board_info info = {
+		I2C_BOARD_INFO("hb_controller", 0x10),
+	};
+
+	hb_uni_i2c_client = i2c_new_client_device(adap, &info);
+	if (IS_ERR(hb_uni_i2c_client))
+		return PTR_ERR(hb_uni_i2c_client);
+
+	priv = devm_kzalloc(&hb_uni_i2c_client->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->regmap = devm_regmap_init_i2c(hb_uni_i2c_client, &hb_uni_regmap);
+	if (IS_ERR(priv->regmap))
+		return dev_err_probe(&hb_uni_i2c_client->dev,
+			PTR_ERR(priv->regmap), "Failed to init regmap\n");
+
+	if (np && of_property_read_bool(np, "clk-provider"))
+		card_is_clk_provider = true;
+
+	ret = hb_uni_read_card_info(pdev);
+	if (ret < 0) {
+		dev_err(&hb_uni_i2c_client->dev,
+			"Failed to read card info or wrong configuration!\n");
+	}
+
+	return ret;
+};
+
+static int snd_rpi_hifiberry_studio_dac8x_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	/* probe for controller */
+	ret = hb_controller_probe(pdev);
+	if (ret < 0)
+		return ret;
+
+	snd_rpi_hifiberry_studio_dac8x.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai;
+
+		dai = &snd_rpi_hifiberry_studio_dac8x_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+			"i2s-controller", 0);
+
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		}
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev,
+			&snd_rpi_hifiberry_studio_dac8x);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev,
+			"devm_snd_soc_register_card() failed: %d\n", ret);
+
+	/* as we do not have components use card-controls */
+	ret = hb_uni_add_card_controls(pdev);
+
+	return ret;
+}
+
+static const struct of_device_id snd_rpi_hifiberry_studio_dac8x_of_match[] = {
+	{ .compatible = "hifiberry,hifiberry-studio-dac8x", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, snd_rpi_hifiberry_studio_dac8x_of_match);
+
+static struct platform_driver snd_rpi_hifiberry_studio_dac8x_driver = {
+	.driver = {
+		.name   = "snd-rpi-hifiberry-studio-dac8x",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_rpi_hifiberry_studio_dac8x_of_match,
+	},
+	.probe  = snd_rpi_hifiberry_studio_dac8x_probe,
+};
+
+module_platform_driver(snd_rpi_hifiberry_studio_dac8x_driver);
+
+MODULE_AUTHOR("Joerg Schambacher <joerg@hifiberry.com>");
+MODULE_DESCRIPTION("HiFiBerry Studio DAC8x Soundcard Driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/bcm/i-sabre-q2m.c b/sound/soc/bcm/i-sabre-q2m.c
new file mode 100644
index 000000000000..6099651f53ee
--- /dev/null
+++ b/sound/soc/bcm/i-sabre-q2m.c
@@ -0,0 +1,159 @@
+/*
+ * ASoC Driver for I-Sabre Q2M
+ *
+ * Author: Satoru Kawase
+ * Modified by: Xiao Qingyong
+ * Update kernel v4.18+ by : Audiophonics
+ * 		Copyright 2018 Audiophonics
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/fs.h>
+#include <asm/uaccess.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "../codecs/i-sabre-codec.h"
+
+
+static int snd_rpi_i_sabre_q2m_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	unsigned int value;
+
+	/* Device ID */
+	value = snd_soc_component_read(component, ISABRECODEC_REG_01);
+	dev_info(component->card->dev, "Audiophonics Device ID : %02X\n", value);
+
+	/* API revision */
+	value = snd_soc_component_read(component, ISABRECODEC_REG_02);
+	dev_info(component->card->dev, "Audiophonics API revision : %02X\n", value);
+
+	return 0;
+}
+
+static int snd_rpi_i_sabre_q2m_hw_params(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd     = substream->private_data;
+	struct snd_soc_dai         *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	int bclk_ratio;
+
+	/* Using powers of 2 allows for an integer clock divisor */
+	bclk_ratio = (snd_pcm_format_width(params_format(params)) <= 16 ? 16 : 32) *
+				 params_channels(params);
+	return snd_soc_dai_set_bclk_ratio(cpu_dai, bclk_ratio);
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_rpi_i_sabre_q2m_ops = {
+	.hw_params = snd_rpi_i_sabre_q2m_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(rpi_i_sabre_q2m,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("i-sabre-codec-i2c.1-0048", "i-sabre-codec-dai")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_i_sabre_q2m_dai[] = {
+	{
+		.name           = "I-Sabre Q2M",
+		.stream_name    = "I-Sabre Q2M DAC",
+		.dai_fmt        = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
+						| SND_SOC_DAIFMT_CBS_CFS,
+		.init           = snd_rpi_i_sabre_q2m_init,
+		.ops            = &snd_rpi_i_sabre_q2m_ops,
+		SND_SOC_DAILINK_REG(rpi_i_sabre_q2m),
+	}
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_i_sabre_q2m = {
+	.name      = "I-Sabre Q2M DAC",
+	.owner     = THIS_MODULE,
+	.dai_link  = snd_rpi_i_sabre_q2m_dai,
+	.num_links = ARRAY_SIZE(snd_rpi_i_sabre_q2m_dai)
+};
+
+
+static int snd_rpi_i_sabre_q2m_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	snd_rpi_i_sabre_q2m.dev = &pdev->dev;
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai;
+
+		dai = &snd_rpi_i_sabre_q2m_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+							"i2s-controller", 0);
+		if (i2s_node) {
+			dai->cpus->dai_name     = NULL;
+			dai->cpus->of_node      = i2s_node;
+			dai->platforms->name    = NULL;
+			dai->platforms->of_node = i2s_node;
+		} else {
+			dev_err(&pdev->dev,
+			    "Property 'i2s-controller' missing or invalid\n");
+			return (-EINVAL);
+		}
+
+		dai->name        = "I-Sabre Q2M";
+		dai->stream_name = "I-Sabre Q2M DAC";
+		dai->dai_fmt     = SND_SOC_DAIFMT_I2S
+					| SND_SOC_DAIFMT_NB_NF
+					| SND_SOC_DAIFMT_CBS_CFS;
+	}
+
+	/* Wait for registering codec driver */
+	mdelay(50);
+
+	ret = snd_soc_register_card(&snd_rpi_i_sabre_q2m);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+	}
+
+	return ret;
+}
+
+static void snd_rpi_i_sabre_q2m_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_card(&snd_rpi_i_sabre_q2m);
+}
+
+static const struct of_device_id snd_rpi_i_sabre_q2m_of_match[] = {
+	{ .compatible = "audiophonics,i-sabre-q2m", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, snd_rpi_i_sabre_q2m_of_match);
+
+static struct platform_driver snd_rpi_i_sabre_q2m_driver = {
+	.driver = {
+		.name           = "snd-rpi-i-sabre-q2m",
+		.owner          = THIS_MODULE,
+		.of_match_table = snd_rpi_i_sabre_q2m_of_match,
+	},
+	.probe  = snd_rpi_i_sabre_q2m_probe,
+	.remove = snd_rpi_i_sabre_q2m_remove,
+};
+module_platform_driver(snd_rpi_i_sabre_q2m_driver);
+
+MODULE_DESCRIPTION("ASoC Driver for I-Sabre Q2M");
+MODULE_AUTHOR("Audiophonics <http://www.audiophonics.fr>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/bcm/iqaudio-codec.c b/sound/soc/bcm/iqaudio-codec.c
new file mode 100644
index 000000000000..8eafd795d454
--- /dev/null
+++ b/sound/soc/bcm/iqaudio-codec.c
@@ -0,0 +1,283 @@
+/*
+ * ASoC Driver for IQaudIO Raspberry Pi Codec board
+ *
+ * Author:	Gordon Garrity <gordon@iqaudio.com>
+ *		(C) Copyright IQaudio Limited, 2017-2019
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include <linux/acpi.h>
+#include <linux/slab.h>
+#include "../codecs/da7213.h"
+
+static int pll_out = DA7213_PLL_FREQ_OUT_90316800;
+
+static int snd_rpi_iqaudio_pll_control(struct snd_soc_dapm_widget *w,
+				       struct snd_kcontrol *k, int  event)
+{
+	int ret = 0;
+	struct snd_soc_dapm_context *dapm = w->dapm;
+	struct snd_soc_card *card = dapm->card;
+	struct snd_soc_pcm_runtime *rtd =
+		snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+	if (SND_SOC_DAPM_EVENT_OFF(event)) {
+		ret = snd_soc_dai_set_pll(codec_dai, 0, DA7213_SYSCLK_MCLK, 0,
+					  0);
+		if (ret)
+			dev_err(card->dev, "Failed to bypass PLL: %d\n", ret);
+		/* Allow PLL time to bypass */
+		msleep(100);
+	} else if (SND_SOC_DAPM_EVENT_ON(event)) {
+		ret = snd_soc_dai_set_pll(codec_dai, 0, DA7213_SYSCLK_PLL, 0,
+					  pll_out);
+		if (ret)
+			dev_err(card->dev, "Failed to enable PLL: %d\n", ret);
+		/* Allow PLL time to lock */
+		msleep(100);
+	}
+
+	return ret;
+}
+
+static int snd_rpi_iqaudio_post_dapm_event(struct snd_soc_dapm_widget *w,
+                              struct snd_kcontrol *kcontrol,
+                              int event)
+{
+     switch (event) {
+     case SND_SOC_DAPM_POST_PMU:
+           /* Delay for mic bias ramp */
+           msleep(1000);
+           break;
+     default:
+           break;
+     }
+
+     return 0;
+}
+
+static const struct snd_kcontrol_new dapm_controls[] = {
+	SOC_DAPM_PIN_SWITCH("HP Jack"),
+	SOC_DAPM_PIN_SWITCH("MIC Jack"),
+	SOC_DAPM_PIN_SWITCH("Onboard MIC"),
+	SOC_DAPM_PIN_SWITCH("AUX Jack"),
+};
+
+static const struct snd_soc_dapm_widget dapm_widgets[] = {
+	SND_SOC_DAPM_HP("HP Jack", NULL),
+	SND_SOC_DAPM_MIC("MIC Jack", NULL),
+	SND_SOC_DAPM_MIC("Onboard MIC", NULL),
+	SND_SOC_DAPM_LINE("AUX Jack", NULL),
+	SND_SOC_DAPM_SUPPLY("PLL Control", SND_SOC_NOPM, 0, 0,
+			    snd_rpi_iqaudio_pll_control,
+			    SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_POST("Post Power Up Event", snd_rpi_iqaudio_post_dapm_event),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	{"HP Jack", NULL, "HPL"},
+	{"HP Jack", NULL, "HPR"},
+	{"HP Jack", NULL, "PLL Control"},
+
+	{"AUXR", NULL, "AUX Jack"},
+	{"AUXL", NULL, "AUX Jack"},
+	{"AUX Jack", NULL, "PLL Control"},
+
+	/* Assume Mic1 is linked to Headset and Mic2 to on-board mic */
+	{"MIC1", NULL, "MIC Jack"},
+	{"MIC Jack", NULL, "PLL Control"},
+	{"MIC2", NULL, "Onboard MIC"},
+	{"Onboard MIC", NULL, "PLL Control"},
+};
+
+/* machine stream operations */
+
+static int snd_rpi_iqaudio_codec_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	int ret;
+
+	/*
+	 * Disable AUX Jack Pin by default to prevent PLL being enabled at
+	 * startup. This avoids holding the PLL to a fixed SR config for
+	 * subsequent streams.
+	 *
+	 * This pin can still be enabled later, as required by user-space.
+	 */
+	snd_soc_dapm_disable_pin(&rtd->card->dapm, "AUX Jack");
+	snd_soc_dapm_sync(&rtd->card->dapm);
+
+	/* Impose BCLK ratios otherwise the codec may cheat */
+	ret = snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
+	if (ret) {
+		dev_err(rtd->dev, "Failed to set CPU BLCK ratio\n");
+		return ret;
+	}
+
+	ret = snd_soc_dai_set_bclk_ratio(codec_dai, 64);
+	if (ret) {
+		dev_err(rtd->dev, "Failed to set codec BCLK ratio\n");
+		return ret;
+	}
+
+	/* Set MCLK frequency to codec, onboard 11.2896MHz clock */
+	return snd_soc_dai_set_sysclk(codec_dai, DA7213_CLKSRC_MCLK, 11289600,
+				      SND_SOC_CLOCK_OUT);
+}
+
+static int snd_rpi_iqaudio_codec_hw_params(struct snd_pcm_substream *substream,
+					   struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	unsigned int samplerate = params_rate(params);
+
+	switch (samplerate) {
+	case  8000:
+	case 16000:
+	case 32000:
+	case 48000:
+	case 96000:
+		pll_out = DA7213_PLL_FREQ_OUT_98304000;
+		break;
+	case 44100:
+	case 88200:
+		pll_out = DA7213_PLL_FREQ_OUT_90316800;
+		break;
+	default:
+		dev_err(rtd->dev,"Unsupported samplerate %d\n", samplerate);
+		return -EINVAL;
+	}
+
+	return snd_soc_dai_set_pll(codec_dai, 0, DA7213_SYSCLK_PLL, 0, pll_out);
+}
+
+static const struct snd_soc_ops snd_rpi_iqaudio_codec_ops = {
+	.hw_params = snd_rpi_iqaudio_codec_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(rpi_iqaudio,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("da7213.1-001a", "da7213-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2835-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_iqaudio_codec_dai[] = {
+{
+	.dai_fmt 		= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				  SND_SOC_DAIFMT_CBM_CFM,
+	.init			= snd_rpi_iqaudio_codec_init,
+	.ops			= &snd_rpi_iqaudio_codec_ops,
+	.symmetric_rate	= 1,
+	.symmetric_channels	= 1,
+	.symmetric_sample_bits	= 1,
+	SND_SOC_DAILINK_REG(rpi_iqaudio),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_iqaudio_codec = {
+	.owner			= THIS_MODULE,
+	.dai_link		= snd_rpi_iqaudio_codec_dai,
+	.num_links		= ARRAY_SIZE(snd_rpi_iqaudio_codec_dai),
+	.controls		= dapm_controls,
+	.num_controls		= ARRAY_SIZE(dapm_controls),
+	.dapm_widgets		= dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(dapm_widgets),
+	.dapm_routes		= audio_map,
+	.num_dapm_routes	= ARRAY_SIZE(audio_map),
+};
+
+static int snd_rpi_iqaudio_codec_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	snd_rpi_iqaudio_codec.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_card *card = &snd_rpi_iqaudio_codec;
+		struct snd_soc_dai_link *dai = &snd_rpi_iqaudio_codec_dai[0];
+
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+					    "i2s-controller", 0);
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		}
+
+		if (of_property_read_string(pdev->dev.of_node, "card_name",
+					    &card->name))
+			card->name = "IQaudIOCODEC";
+
+		if (of_property_read_string(pdev->dev.of_node, "dai_name",
+					    &dai->name))
+			dai->name = "IQaudIO CODEC";
+
+		if (of_property_read_string(pdev->dev.of_node,
+					"dai_stream_name", &dai->stream_name))
+			dai->stream_name = "IQaudIO CODEC HiFi v1.2";
+
+	}
+
+	ret = snd_soc_register_card(&snd_rpi_iqaudio_codec);
+	if (ret) {
+		if (ret != -EPROBE_DEFER)
+			dev_err(&pdev->dev,
+				"snd_soc_register_card() failed: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void snd_rpi_iqaudio_codec_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_card(&snd_rpi_iqaudio_codec);
+}
+
+static const struct of_device_id iqaudio_of_match[] = {
+	{ .compatible = "iqaudio,iqaudio-codec", },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, iqaudio_of_match);
+
+static struct platform_driver snd_rpi_iqaudio_codec_driver = {
+	.driver = {
+		.name   = "snd-rpi-iqaudio-codec",
+		.owner  = THIS_MODULE,
+		.of_match_table = iqaudio_of_match,
+	},
+	.probe          = snd_rpi_iqaudio_codec_probe,
+	.remove         = snd_rpi_iqaudio_codec_remove,
+};
+
+
+
+module_platform_driver(snd_rpi_iqaudio_codec_driver);
+
+MODULE_AUTHOR("Gordon Garrity <gordon@iqaudio.com>");
+MODULE_DESCRIPTION("ASoC Driver for IQaudIO CODEC");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/iqaudio-dac.c b/sound/soc/bcm/iqaudio-dac.c
new file mode 100644
index 000000000000..f0680af30b74
--- /dev/null
+++ b/sound/soc/bcm/iqaudio-dac.c
@@ -0,0 +1,223 @@
+/*
+ * ASoC Driver for IQaudIO DAC
+ *
+ * Author:	Florian Meier <florian.meier@koalo.de>
+ *		Copyright 2013
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+static bool digital_gain_0db_limit = true;
+
+static struct gpio_desc *mute_gpio;
+
+static int snd_rpi_iqaudio_dac_init(struct snd_soc_pcm_runtime *rtd)
+{
+	if (digital_gain_0db_limit)
+	{
+		int ret;
+		struct snd_soc_card *card = rtd->card;
+
+		ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207);
+		if (ret < 0)
+			dev_warn(card->dev, "Failed to set volume limit: %d\n", ret);
+	}
+
+	return 0;
+}
+
+static void snd_rpi_iqaudio_gpio_mute(struct snd_soc_card *card)
+{
+	if (mute_gpio) {
+		dev_info(card->dev, "%s: muting amp using GPIO22\n",
+			 __func__);
+		gpiod_set_value_cansleep(mute_gpio, 0);
+	}
+}
+
+static void snd_rpi_iqaudio_gpio_unmute(struct snd_soc_card *card)
+{
+	if (mute_gpio) {
+		dev_info(card->dev, "%s: un-muting amp using GPIO22\n",
+			 __func__);
+		gpiod_set_value_cansleep(mute_gpio, 1);
+	}
+}
+
+static int snd_rpi_iqaudio_set_bias_level(struct snd_soc_card *card,
+	struct snd_soc_dapm_context *dapm, enum snd_soc_bias_level level)
+{
+	struct snd_soc_pcm_runtime *rtd;
+	struct snd_soc_dai *codec_dai;
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+	codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+	if (dapm->dev != codec_dai->dev)
+		return 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_PREPARE:
+		if (dapm->bias_level != SND_SOC_BIAS_STANDBY)
+			break;
+
+		/* UNMUTE AMP */
+		snd_rpi_iqaudio_gpio_unmute(card);
+
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		if (dapm->bias_level != SND_SOC_BIAS_PREPARE)
+			break;
+
+		/* MUTE AMP */
+		snd_rpi_iqaudio_gpio_mute(card);
+
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+SND_SOC_DAILINK_DEFS(hifi,
+        DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+        DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004c", "pcm512x-hifi")),
+        DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_iqaudio_dac_dai[] = {
+{
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBS_CFS,
+	.init		= snd_rpi_iqaudio_dac_init,
+	SND_SOC_DAILINK_REG(hifi),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_iqaudio_dac = {
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_rpi_iqaudio_dac_dai,
+	.num_links    = ARRAY_SIZE(snd_rpi_iqaudio_dac_dai),
+};
+
+static int snd_rpi_iqaudio_dac_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	bool gpio_unmute = false;
+
+	snd_rpi_iqaudio_dac.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_card *card = &snd_rpi_iqaudio_dac;
+		struct snd_soc_dai_link *dai = &snd_rpi_iqaudio_dac_dai[0];
+		bool auto_gpio_mute = false;
+
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+					    "i2s-controller", 0);
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		}
+
+		digital_gain_0db_limit = !of_property_read_bool(
+			pdev->dev.of_node, "iqaudio,24db_digital_gain");
+
+		if (of_property_read_string(pdev->dev.of_node, "card_name",
+					    &card->name))
+			card->name = "IQaudIODAC";
+
+		if (of_property_read_string(pdev->dev.of_node, "dai_name",
+					    &dai->name))
+			dai->name = "IQaudIO DAC";
+
+		if (of_property_read_string(pdev->dev.of_node,
+					"dai_stream_name", &dai->stream_name))
+			dai->stream_name = "IQaudIO DAC HiFi";
+
+		/* gpio_unmute - one time unmute amp using GPIO */
+		gpio_unmute = of_property_read_bool(pdev->dev.of_node,
+						    "iqaudio-dac,unmute-amp");
+
+		/* auto_gpio_mute - mute/unmute amp using GPIO */
+		auto_gpio_mute = of_property_read_bool(pdev->dev.of_node,
+						"iqaudio-dac,auto-mute-amp");
+
+		if (auto_gpio_mute || gpio_unmute) {
+			mute_gpio = devm_gpiod_get_optional(&pdev->dev, "mute",
+							    GPIOD_OUT_LOW);
+			if (IS_ERR(mute_gpio)) {
+				ret = PTR_ERR(mute_gpio);
+				dev_err(&pdev->dev,
+					"Failed to get mute gpio: %d\n", ret);
+				return ret;
+			}
+
+			if (auto_gpio_mute && mute_gpio)
+				snd_rpi_iqaudio_dac.set_bias_level =
+						snd_rpi_iqaudio_set_bias_level;
+		}
+	}
+
+	ret = snd_soc_register_card(&snd_rpi_iqaudio_dac);
+	if (ret) {
+		if (ret != -EPROBE_DEFER)
+			dev_err(&pdev->dev,
+				"snd_soc_register_card() failed: %d\n", ret);
+		return ret;
+	}
+
+	if (gpio_unmute && mute_gpio)
+		snd_rpi_iqaudio_gpio_unmute(&snd_rpi_iqaudio_dac);
+
+	return 0;
+}
+
+static void snd_rpi_iqaudio_dac_remove(struct platform_device *pdev)
+{
+	snd_rpi_iqaudio_gpio_mute(&snd_rpi_iqaudio_dac);
+
+	snd_soc_unregister_card(&snd_rpi_iqaudio_dac);
+}
+
+static const struct of_device_id iqaudio_of_match[] = {
+	{ .compatible = "iqaudio,iqaudio-dac", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, iqaudio_of_match);
+
+static struct platform_driver snd_rpi_iqaudio_dac_driver = {
+	.driver = {
+		.name   = "snd-rpi-iqaudio-dac",
+		.owner  = THIS_MODULE,
+		.of_match_table = iqaudio_of_match,
+	},
+	.probe          = snd_rpi_iqaudio_dac_probe,
+	.remove         = snd_rpi_iqaudio_dac_remove,
+};
+
+module_platform_driver(snd_rpi_iqaudio_dac_driver);
+
+MODULE_AUTHOR("Florian Meier <florian.meier@koalo.de>");
+MODULE_DESCRIPTION("ASoC Driver for IQAudio DAC");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/justboom-both.c b/sound/soc/bcm/justboom-both.c
new file mode 100644
index 000000000000..835217ffb0ad
--- /dev/null
+++ b/sound/soc/bcm/justboom-both.c
@@ -0,0 +1,266 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * rpi--wm8804.c -- ALSA SoC Raspberry Pi soundcard.
+ *
+ * Authors: Johannes Krude <johannes@krude.de
+ *
+ * Driver for when connecting simultaneously justboom-digi and justboom-dac
+ *
+ * Based upon code from:
+ * justboom-digi.c
+ * by Milan Neskovic <info@justboom.co>
+ * justboom-dac.c
+ * by Milan Neskovic <info@justboom.co>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "../codecs/wm8804.h"
+#include "../codecs/pcm512x.h"
+
+
+static bool digital_gain_0db_limit = true;
+
+static int snd_rpi_justboom_both_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *digi = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 1)->component;
+
+	/* enable  TX output */
+	snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x4, 0x0);
+
+	snd_soc_component_update_bits(dac, PCM512x_GPIO_EN, 0x08, 0x08);
+	snd_soc_component_update_bits(dac, PCM512x_GPIO_OUTPUT_4, 0xf, 0x02);
+	snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
+
+	if (digital_gain_0db_limit) {
+		int ret;
+		struct snd_soc_card *card = rtd->card;
+
+		ret = snd_soc_limit_volume(card, "Digital Playback Volume",
+									207);
+		if (ret < 0)
+			dev_warn(card->dev, "Failed to set volume limit: %d\n",
+									ret);
+	}
+
+	return 0;
+}
+
+static int snd_rpi_justboom_both_hw_params(struct snd_pcm_substream *substream,
+				       struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_component *digi = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+	int sysclk = 27000000; /* This is fixed on this board */
+
+	long mclk_freq    = 0;
+	int mclk_div      = 1;
+	int sampling_freq = 1;
+
+	int ret;
+
+	int samplerate = params_rate(params);
+
+	if (samplerate <= 96000) {
+		mclk_freq = samplerate*256;
+		mclk_div  = WM8804_MCLKDIV_256FS;
+	} else {
+		mclk_freq = samplerate*128;
+		mclk_div  = WM8804_MCLKDIV_128FS;
+	}
+
+	switch (samplerate) {
+	case 32000:
+		sampling_freq = 0x03;
+		break;
+	case 44100:
+		sampling_freq = 0x00;
+		break;
+	case 48000:
+		sampling_freq = 0x02;
+		break;
+	case 88200:
+		sampling_freq = 0x08;
+		break;
+	case 96000:
+		sampling_freq = 0x0a;
+		break;
+	case 176400:
+		sampling_freq = 0x0c;
+		break;
+	case 192000:
+		sampling_freq = 0x0e;
+		break;
+	default:
+		dev_err(rtd->card->dev,
+		"Failed to set WM8804 SYSCLK, unsupported samplerate %d\n",
+		samplerate);
+	}
+
+	snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, mclk_div);
+	snd_soc_dai_set_pll(codec_dai, 0, 0, sysclk, mclk_freq);
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL,
+					sysclk, SND_SOC_CLOCK_OUT);
+	if (ret < 0) {
+		dev_err(rtd->card->dev,
+		"Failed to set WM8804 SYSCLK: %d\n", ret);
+		return ret;
+	}
+
+	/* Enable TX output */
+	snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x4, 0x0);
+
+	/* Power on */
+	snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x9, 0);
+
+	/* set sampling frequency status bits */
+	snd_soc_component_update_bits(digi, WM8804_SPDTX4, 0x0f, sampling_freq);
+
+	return snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
+}
+
+static int snd_rpi_justboom_both_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *digi = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 1)->component;
+
+	/* turn on digital output */
+	snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x3c, 0x00);
+
+	snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x08);
+
+	return 0;
+}
+
+static void snd_rpi_justboom_both_shutdown(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *digi = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_component *dac = snd_soc_rtd_to_codec(rtd, 1)->component;
+
+	snd_soc_component_update_bits(dac, PCM512x_GPIO_CONTROL_1, 0x08, 0x00);
+
+	/* turn off output */
+	snd_soc_component_update_bits(digi, WM8804_PWRDN, 0x3c, 0x3c);
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_rpi_justboom_both_ops = {
+	.hw_params = snd_rpi_justboom_both_hw_params,
+	.startup   = snd_rpi_justboom_both_startup,
+	.shutdown  = snd_rpi_justboom_both_shutdown,
+};
+
+SND_SOC_DAILINK_DEFS(rpi_justboom_both,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi"),
+			   COMP_CODEC("wm8804.1-003b", "wm8804-spdif")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_justboom_both_dai[] = {
+{
+	.name           = "JustBoom Digi",
+	.stream_name    = "JustBoom Digi HiFi",
+	.dai_fmt        = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+					SND_SOC_DAIFMT_CBM_CFM,
+	.ops            = &snd_rpi_justboom_both_ops,
+	.init           = snd_rpi_justboom_both_init,
+	SND_SOC_DAILINK_REG(rpi_justboom_both),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_justboom_both = {
+	.name             = "snd_rpi_justboom_both",
+	.driver_name      = "JustBoomBoth",
+	.owner            = THIS_MODULE,
+	.dai_link         = snd_rpi_justboom_both_dai,
+	.num_links        = ARRAY_SIZE(snd_rpi_justboom_both_dai),
+};
+
+static int snd_rpi_justboom_both_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	struct snd_soc_card *card = &snd_rpi_justboom_both;
+
+	snd_rpi_justboom_both.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai = &snd_rpi_justboom_both_dai[0];
+
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+					    "i2s-controller", 0);
+
+		if (i2s_node) {
+			int i;
+
+			for (i = 0; i < card->num_links; i++) {
+				dai->cpus->dai_name = NULL;
+				dai->cpus->of_node = i2s_node;
+				dai->platforms->name = NULL;
+				dai->platforms->of_node = i2s_node;
+			}
+		}
+
+		digital_gain_0db_limit = !of_property_read_bool(
+			pdev->dev.of_node, "justboom,24db_digital_gain");
+	}
+
+	ret = snd_soc_register_card(card);
+	if (ret && ret != -EPROBE_DEFER) {
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+	}
+
+	return ret;
+}
+
+static void snd_rpi_justboom_both_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_card(&snd_rpi_justboom_both);
+}
+
+static const struct of_device_id snd_rpi_justboom_both_of_match[] = {
+	{ .compatible = "justboom,justboom-both", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, snd_rpi_justboom_both_of_match);
+
+static struct platform_driver snd_rpi_justboom_both_driver = {
+	.driver = {
+		.name   = "snd-rpi-justboom-both",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_rpi_justboom_both_of_match,
+	},
+	.probe          = snd_rpi_justboom_both_probe,
+	.remove         = snd_rpi_justboom_both_remove,
+};
+
+module_platform_driver(snd_rpi_justboom_both_driver);
+
+MODULE_AUTHOR("Johannes Krude <johannes@krude.de>");
+MODULE_DESCRIPTION("ASoC Driver for simultaneous use of JustBoom PI Digi & DAC HAT Sound Cards");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/justboom-dac.c b/sound/soc/bcm/justboom-dac.c
new file mode 100644
index 000000000000..e3b258a3518d
--- /dev/null
+++ b/sound/soc/bcm/justboom-dac.c
@@ -0,0 +1,147 @@
+/*
+ * ASoC Driver for JustBoom DAC Raspberry Pi HAT Sound Card
+ *
+ * Author:	Milan Neskovic
+ *		Copyright 2016
+ *		based on code by Daniel Matuschek <info@crazy-audio.com>
+ *		based on code by Florian Meier <florian.meier@koalo.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "../codecs/pcm512x.h"
+
+static bool digital_gain_0db_limit = true;
+
+static int snd_rpi_justboom_dac_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	snd_soc_component_update_bits(component, PCM512x_GPIO_EN, 0x08, 0x08);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_OUTPUT_4, 0xf, 0x02);
+	snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08,0x08);
+
+	if (digital_gain_0db_limit)
+	{
+		int ret;
+		struct snd_soc_card *card = rtd->card;
+
+		ret = snd_soc_limit_volume(card, "Digital Playback Volume", 207);
+		if (ret < 0)
+			dev_warn(card->dev, "Failed to set volume limit: %d\n", ret);
+	}
+
+	return 0;
+}
+
+static int snd_rpi_justboom_dac_startup(struct snd_pcm_substream *substream) {
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08,0x08);
+	return 0;
+}
+
+static void snd_rpi_justboom_dac_shutdown(struct snd_pcm_substream *substream) {
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	snd_soc_component_update_bits(component, PCM512x_GPIO_CONTROL_1, 0x08,0x00);
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_rpi_justboom_dac_ops = {
+	.startup = snd_rpi_justboom_dac_startup,
+	.shutdown = snd_rpi_justboom_dac_shutdown,
+};
+
+SND_SOC_DAILINK_DEFS(hifi,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm512x.1-004d", "pcm512x-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_justboom_dac_dai[] = {
+{
+	.name		= "JustBoom DAC",
+	.stream_name	= "JustBoom DAC HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBS_CFS,
+	.ops		= &snd_rpi_justboom_dac_ops,
+	.init		= snd_rpi_justboom_dac_init,
+	SND_SOC_DAILINK_REG(hifi),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_justboom_dac = {
+	.name         = "snd_rpi_justboom_dac",
+	.driver_name  = "JustBoomDac",
+	.owner        = THIS_MODULE,
+	.dai_link     = snd_rpi_justboom_dac_dai,
+	.num_links    = ARRAY_SIZE(snd_rpi_justboom_dac_dai),
+};
+
+static int snd_rpi_justboom_dac_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	snd_rpi_justboom_dac.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+	    struct device_node *i2s_node;
+	    struct snd_soc_dai_link *dai = &snd_rpi_justboom_dac_dai[0];
+	    i2s_node = of_parse_phandle(pdev->dev.of_node,
+					"i2s-controller", 0);
+
+	    if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+	    }
+
+	    digital_gain_0db_limit = !of_property_read_bool(
+			pdev->dev.of_node, "justboom,24db_digital_gain");
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_justboom_dac);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev,
+			"snd_soc_register_card() failed: %d\n", ret);
+
+	return ret;
+}
+
+static const struct of_device_id snd_rpi_justboom_dac_of_match[] = {
+	{ .compatible = "justboom,justboom-dac", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, snd_rpi_justboom_dac_of_match);
+
+static struct platform_driver snd_rpi_justboom_dac_driver = {
+	.driver = {
+		.name   = "snd-rpi-justboom-dac",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_rpi_justboom_dac_of_match,
+	},
+	.probe          = snd_rpi_justboom_dac_probe,
+};
+
+module_platform_driver(snd_rpi_justboom_dac_driver);
+
+MODULE_AUTHOR("Milan Neskovic <info@justboom.co>");
+MODULE_DESCRIPTION("ASoC Driver for JustBoom PI DAC HAT Sound Card");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/pifi-40.c b/sound/soc/bcm/pifi-40.c
new file mode 100644
index 000000000000..cfdc7c673583
--- /dev/null
+++ b/sound/soc/bcm/pifi-40.c
@@ -0,0 +1,281 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ALSA ASoC Machine Driver for PiFi-40
+ *
+ * Author:	David Knell <david.knell@gmail.com)
+ *		based on code by Daniel Matuschek <info@crazy-audio.com>
+ *		based on code by Florian Meier <florian.meier@koalo.de>
+ * Copyright (C) 2020
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <linux/firmware.h>
+#include <linux/delay.h>
+#include <sound/tlv.h>
+
+static struct gpio_desc *pdn_gpio;
+static int vol = 0x30;
+
+// Volume control
+static int pifi_40_vol_get(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = vol;
+	ucontrol->value.integer.value[1] = vol;
+	return 0;
+}
+
+static int pifi_40_vol_set(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_pcm_runtime *rtd;
+	unsigned int v = ucontrol->value.integer.value[0];
+	struct snd_soc_component *dac[2];
+
+	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
+	dac[0] = snd_soc_rtd_to_codec(rtd, 0)->component;
+	dac[1] = snd_soc_rtd_to_codec(rtd, 1)->component;
+
+	snd_soc_component_write(dac[0], 0x07, 255 - v);
+	snd_soc_component_write(dac[1], 0x07, 255 - v);
+
+	vol = v;
+	return 1;
+}
+
+static const DECLARE_TLV_DB_SCALE(digital_tlv_master, -10350, 50, 1);
+static const struct snd_kcontrol_new pifi_40_controls[] = {
+	SOC_DOUBLE_R_EXT_TLV("Master Volume", 0x00, 0x01,
+			     0x00, // Min
+			     0xff, // Max
+			     0x01, // Invert
+			     pifi_40_vol_get, pifi_40_vol_set,
+			     digital_tlv_master)
+};
+
+static const char * const codec_ctl_pfx[] = { "Left", "Right" };
+
+static const char * const codec_ctl_name[] = { "Master Volume",
+					"Speaker Volume",
+					"Speaker Switch" };
+
+static int snd_pifi_40_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_card *card = rtd->card;
+	struct snd_soc_component *dac[2];
+	struct snd_kcontrol *kctl;
+	int i, j;
+
+	dac[0] = snd_soc_rtd_to_codec(rtd, 0)->component;
+	dac[1] = snd_soc_rtd_to_codec(rtd, 1)->component;
+
+
+	// Set up cards - pulse power down first
+	gpiod_set_value_cansleep(pdn_gpio, 1);
+	usleep_range(1000, 10000);
+	gpiod_set_value_cansleep(pdn_gpio, 0);
+	usleep_range(20000, 30000);
+
+	// Oscillator trim
+	snd_soc_component_write(dac[0], 0x1b, 0);
+	snd_soc_component_write(dac[1], 0x1b, 0);
+	usleep_range(60000, 80000);
+
+	// Common setup
+	for (i = 0; i < 2; i++) {
+		// MCLK at 64fs, sample rate 44.1 or 48kHz
+		snd_soc_component_write(dac[i], 0x00, 0x60);
+
+		// Set up for PBTL
+		snd_soc_component_write(dac[i], 0x19, 0x3A);
+		snd_soc_component_write(dac[i], 0x25, 0x01103245);
+
+		// Master vol to -10db
+		snd_soc_component_write(dac[i], 0x07, 0x44);
+	}
+	// Inputs set to L and R respectively
+	snd_soc_component_write(dac[0], 0x20, 0x00017772);
+	snd_soc_component_write(dac[1], 0x20, 0x00107772);
+
+	// Remove codec controls
+	for (i = 0; i < 2; i++) {
+		for (j = 0; j < 3; j++) {
+			char cname[256];
+
+			sprintf(cname, "%s %s", codec_ctl_pfx[i],
+				codec_ctl_name[j]);
+			kctl = snd_soc_card_get_kcontrol(card, cname);
+			if (!kctl) {
+				pr_info("Control %s not found\n",
+				       cname);
+			} else {
+				kctl->vd[0].access =
+					SNDRV_CTL_ELEM_ACCESS_READWRITE;
+				snd_ctl_remove(card->snd_card, kctl);
+			}
+		}
+	}
+
+	return 0;
+}
+
+static int snd_pifi_40_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+	return snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
+}
+
+static struct snd_soc_ops snd_pifi_40_ops = { .hw_params =
+						      snd_pifi_40_hw_params };
+
+static struct snd_soc_dai_link_component pifi_40_codecs[] = {
+	{
+		.dai_name = "tas571x-hifi",
+	},
+	{
+		.dai_name = "tas571x-hifi",
+	},
+};
+
+SND_SOC_DAILINK_DEFS(
+	pifi_40_dai, DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("tas571x.1-001a", "tas571x-hifi"),
+			   COMP_CODEC("tas571x.1-001b", "tas571x-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_pifi_40_dai[] = {
+	{
+		.name = "PiFi40",
+		.stream_name = "PiFi40",
+		.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+			   SND_SOC_DAIFMT_CBS_CFS,
+		.ops = &snd_pifi_40_ops,
+		.init = snd_pifi_40_init,
+		SND_SOC_DAILINK_REG(pifi_40_dai),
+	},
+};
+
+// Machine driver
+static struct snd_soc_card snd_pifi_40 = {
+	.name = "PiFi40",
+	.owner = THIS_MODULE,
+	.dai_link = snd_pifi_40_dai,
+	.num_links = ARRAY_SIZE(snd_pifi_40_dai),
+	.controls = pifi_40_controls,
+	.num_controls = ARRAY_SIZE(pifi_40_controls)
+};
+
+static void snd_pifi_40_pdn(struct snd_soc_card *card, int on)
+{
+	if (pdn_gpio)
+		gpiod_set_value_cansleep(pdn_gpio, on ? 0 : 1);
+}
+
+static int snd_pifi_40_probe(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = &snd_pifi_40;
+	int ret = 0, i = 0;
+
+	card->dev = &pdev->dev;
+	platform_set_drvdata(pdev, &snd_pifi_40);
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai;
+
+		dai = &snd_pifi_40_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node, "i2s-controller",
+					    0);
+		if (i2s_node) {
+			for (i = 0; i < card->num_links; i++) {
+				dai->cpus->dai_name = NULL;
+				dai->cpus->of_node = i2s_node;
+				dai->platforms->name = NULL;
+				dai->platforms->of_node = i2s_node;
+			}
+		}
+
+		pifi_40_codecs[0].of_node =
+			of_parse_phandle(pdev->dev.of_node, "audio-codec", 0);
+		pifi_40_codecs[1].of_node =
+			of_parse_phandle(pdev->dev.of_node, "audio-codec", 1);
+		if (!pifi_40_codecs[0].of_node || !pifi_40_codecs[1].of_node) {
+			dev_err(&pdev->dev,
+				"Property 'audio-codec' missing or invalid\n");
+			return -EINVAL;
+		}
+
+		pdn_gpio = devm_gpiod_get_optional(&pdev->dev, "pdn",
+						   GPIOD_OUT_LOW);
+		if (IS_ERR(pdn_gpio)) {
+			ret = PTR_ERR(pdn_gpio);
+			dev_err(&pdev->dev, "failed to get pdn gpio: %d\n",
+				ret);
+			return ret;
+		}
+
+		ret = snd_soc_register_card(&snd_pifi_40);
+		if (ret < 0) {
+			dev_err(&pdev->dev,
+				"snd_soc_register_card() failed: %d\n", ret);
+			return ret;
+		}
+
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static void snd_pifi_40_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+	kfree(&card->drvdata);
+	snd_pifi_40_pdn(&snd_pifi_40, 0);
+	snd_soc_unregister_card(&snd_pifi_40);
+}
+
+static const struct of_device_id snd_pifi_40_of_match[] = {
+	{
+		.compatible = "pifi,pifi-40",
+	},
+	{ /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, snd_pifi_40_of_match);
+
+static struct platform_driver snd_pifi_40_driver = {
+	.driver = {
+		.name = "snd-pifi-40",
+		.owner = THIS_MODULE,
+		.of_match_table = snd_pifi_40_of_match,
+	},
+	.probe = snd_pifi_40_probe,
+	.remove = snd_pifi_40_remove,
+};
+
+module_platform_driver(snd_pifi_40_driver);
+
+MODULE_AUTHOR("David Knell <david.knell@gmail.com>");
+MODULE_DESCRIPTION("ALSA ASoC Machine Driver for PiFi-40");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/pisound.c b/sound/soc/bcm/pisound.c
new file mode 100644
index 000000000000..84396880e502
--- /dev/null
+++ b/sound/soc/bcm/pisound.c
@@ -0,0 +1,1254 @@
+/*
+ * Pisound Linux kernel module.
+ * Copyright (C) 2016-2024  Vilniaus Blokas UAB, https://blokas.io/pisound
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA  02110-1301, USA.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio.h>
+#include <linux/kobject.h>
+#include <linux/sysfs.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+#include <linux/interrupt.h>
+#include <linux/kfifo.h>
+#include <linux/jiffies.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+#include <sound/rawmidi.h>
+#include <sound/asequencer.h>
+#include <sound/control.h>
+
+static int pisnd_spi_init(struct device *dev);
+static void pisnd_spi_uninit(void);
+
+static void pisnd_spi_flush(void);
+static void pisnd_spi_start(void);
+static uint8_t pisnd_spi_recv(uint8_t *buffer, uint8_t length);
+
+typedef void (*pisnd_spi_recv_cb)(void *data);
+static void pisnd_spi_set_callback(pisnd_spi_recv_cb cb, void *data);
+
+static const char *pisnd_spi_get_serial(void);
+static const char *pisnd_spi_get_id(void);
+static const char *pisnd_spi_get_fw_version(void);
+static const char *pisnd_spi_get_hw_version(void);
+
+static int pisnd_midi_init(struct snd_card *card);
+static void pisnd_midi_uninit(void);
+
+enum task_e {
+	TASK_PROCESS = 0,
+};
+
+static void pisnd_schedule_process(enum task_e task);
+
+#define PISOUND_LOG_PREFIX "pisound: "
+
+#ifdef PISOUND_DEBUG
+#	define printd(...) pr_alert(PISOUND_LOG_PREFIX __VA_ARGS__)
+#else
+#	define printd(...) do {} while (0)
+#endif
+
+#define printe(...) pr_err(PISOUND_LOG_PREFIX __VA_ARGS__)
+#define printi(...) pr_info(PISOUND_LOG_PREFIX __VA_ARGS__)
+
+static struct snd_rawmidi *g_rmidi;
+static struct snd_rawmidi_substream *g_midi_output_substream;
+
+static int pisnd_output_open(struct snd_rawmidi_substream *substream)
+{
+	g_midi_output_substream = substream;
+	return 0;
+}
+
+static int pisnd_output_close(struct snd_rawmidi_substream *substream)
+{
+	g_midi_output_substream = NULL;
+	return 0;
+}
+
+static void pisnd_output_trigger(
+	struct snd_rawmidi_substream *substream,
+	int up
+	)
+{
+	if (substream != g_midi_output_substream) {
+		printe("MIDI output trigger called for an unexpected stream!");
+		return;
+	}
+
+	if (!up)
+		return;
+
+	pisnd_spi_start();
+}
+
+static void pisnd_output_drain(struct snd_rawmidi_substream *substream)
+{
+	pisnd_spi_flush();
+}
+
+static int pisnd_input_open(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static int pisnd_input_close(struct snd_rawmidi_substream *substream)
+{
+	return 0;
+}
+
+static void pisnd_midi_recv_callback(void *substream)
+{
+	uint8_t data[128];
+	uint8_t n = 0;
+
+	while ((n = pisnd_spi_recv(data, sizeof(data)))) {
+		int res = snd_rawmidi_receive(substream, data, n);
+		(void)res;
+		printd("midi recv %u bytes, res = %d\n", n, res);
+	}
+}
+
+static void pisnd_input_trigger(struct snd_rawmidi_substream *substream, int up)
+{
+	if (up) {
+		pisnd_spi_set_callback(pisnd_midi_recv_callback, substream);
+		pisnd_schedule_process(TASK_PROCESS);
+	} else {
+		pisnd_spi_set_callback(NULL, NULL);
+	}
+}
+
+static const struct snd_rawmidi_ops pisnd_output_ops = {
+	.open = pisnd_output_open,
+	.close = pisnd_output_close,
+	.trigger = pisnd_output_trigger,
+	.drain = pisnd_output_drain,
+};
+
+static const struct snd_rawmidi_ops pisnd_input_ops = {
+	.open = pisnd_input_open,
+	.close = pisnd_input_close,
+	.trigger = pisnd_input_trigger,
+};
+
+static void pisnd_get_port_info(
+	struct snd_rawmidi *rmidi,
+	int number,
+	struct snd_seq_port_info *seq_port_info
+	)
+{
+	seq_port_info->type =
+		SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
+		SNDRV_SEQ_PORT_TYPE_HARDWARE |
+		SNDRV_SEQ_PORT_TYPE_PORT;
+	seq_port_info->midi_voices = 0;
+}
+
+static struct snd_rawmidi_global_ops pisnd_global_ops = {
+	.get_port_info = pisnd_get_port_info,
+};
+
+static int pisnd_midi_init(struct snd_card *card)
+{
+	int err;
+
+	g_midi_output_substream = NULL;
+
+	err = snd_rawmidi_new(card, "pisound MIDI", 0, 1, 1, &g_rmidi);
+
+	if (err < 0) {
+		printe("snd_rawmidi_new failed: %d\n", err);
+		return err;
+	}
+
+	strcpy(g_rmidi->name, "pisound MIDI ");
+	strcat(g_rmidi->name, pisnd_spi_get_serial());
+
+	g_rmidi->info_flags =
+		SNDRV_RAWMIDI_INFO_OUTPUT |
+		SNDRV_RAWMIDI_INFO_INPUT |
+		SNDRV_RAWMIDI_INFO_DUPLEX;
+
+	g_rmidi->ops = &pisnd_global_ops;
+
+	g_rmidi->private_data = (void *)0;
+
+	snd_rawmidi_set_ops(
+		g_rmidi,
+		SNDRV_RAWMIDI_STREAM_OUTPUT,
+		&pisnd_output_ops
+		);
+
+	snd_rawmidi_set_ops(
+		g_rmidi,
+		SNDRV_RAWMIDI_STREAM_INPUT,
+		&pisnd_input_ops
+		);
+
+	return 0;
+}
+
+static void pisnd_midi_uninit(void)
+{
+}
+
+static void *g_recvData;
+static pisnd_spi_recv_cb g_recvCallback;
+
+#define FIFO_SIZE 4096
+
+static char g_serial_num[11];
+static char g_id[25];
+enum { MAX_VERSION_STR_LEN = 6 };
+static char g_fw_version[MAX_VERSION_STR_LEN];
+static char g_hw_version[MAX_VERSION_STR_LEN];
+static u32 g_spi_speed_hz;
+
+static uint8_t g_ledFlashDuration;
+static bool    g_ledFlashDurationChanged;
+
+DEFINE_KFIFO(spi_fifo_in,  uint8_t, FIFO_SIZE);
+DEFINE_KFIFO(spi_fifo_out, uint8_t, FIFO_SIZE);
+
+static struct gpio_desc *data_available;
+static struct gpio_desc *spi_reset;
+
+static struct spi_device *pisnd_spi_device;
+
+static struct workqueue_struct *pisnd_workqueue;
+static struct work_struct pisnd_work_process;
+
+static void pisnd_work_handler(struct work_struct *work);
+
+static void spi_transfer(const uint8_t *txbuf, uint8_t *rxbuf, int len);
+static uint16_t spi_transfer16(uint16_t val);
+
+static int pisnd_init_workqueues(void)
+{
+	pisnd_workqueue = create_singlethread_workqueue("pisnd_workqueue");
+	INIT_WORK(&pisnd_work_process, pisnd_work_handler);
+
+	return 0;
+}
+
+static void pisnd_uninit_workqueues(void)
+{
+	flush_workqueue(pisnd_workqueue);
+	destroy_workqueue(pisnd_workqueue);
+
+	pisnd_workqueue = NULL;
+}
+
+static bool pisnd_spi_has_more(void)
+{
+	return gpiod_get_value(data_available);
+}
+
+static void pisnd_schedule_process(enum task_e task)
+{
+	if (pisnd_spi_device != NULL &&
+		pisnd_workqueue != NULL &&
+		!work_pending(&pisnd_work_process)
+		) {
+		printd("schedule: has more = %d\n", pisnd_spi_has_more());
+		if (task == TASK_PROCESS)
+			queue_work(pisnd_workqueue, &pisnd_work_process);
+	}
+}
+
+static irqreturn_t data_available_interrupt_handler(int irq, void *dev_id)
+{
+	if (irq == gpiod_to_irq(data_available) && pisnd_spi_has_more()) {
+		printd("schedule from irq\n");
+		pisnd_schedule_process(TASK_PROCESS);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static uint16_t spi_transfer16(uint16_t val)
+{
+	uint8_t txbuf[2];
+	uint8_t rxbuf[2];
+
+	if (!pisnd_spi_device) {
+		printe("pisnd_spi_device null, returning\n");
+		return 0;
+	}
+
+	txbuf[0] = val >> 8;
+	txbuf[1] = val & 0xff;
+
+	spi_transfer(txbuf, rxbuf, sizeof(txbuf));
+
+	printd("received: %02x%02x\n", rxbuf[0], rxbuf[1]);
+
+	return (rxbuf[0] << 8) | rxbuf[1];
+}
+
+static void spi_transfer(const uint8_t *txbuf, uint8_t *rxbuf, int len)
+{
+	int err;
+	struct spi_transfer transfer;
+	struct spi_message msg;
+
+	memset(rxbuf, 0, len);
+
+	if (!pisnd_spi_device) {
+		printe("pisnd_spi_device null, returning\n");
+		return;
+	}
+
+	spi_message_init(&msg);
+
+	memset(&transfer, 0, sizeof(transfer));
+
+	transfer.tx_buf = txbuf;
+	transfer.rx_buf = rxbuf;
+	transfer.len = len;
+	transfer.speed_hz = g_spi_speed_hz;
+	transfer.delay.value = 10;
+	transfer.delay.unit = SPI_DELAY_UNIT_USECS;
+
+	spi_message_add_tail(&transfer, &msg);
+
+	err = spi_sync(pisnd_spi_device, &msg);
+
+	if (err < 0) {
+		printe("spi_sync error %d\n", err);
+		return;
+	}
+
+	printd("hasMore %d\n", pisnd_spi_has_more());
+}
+
+static int spi_read_bytes(char *dst, size_t length, uint8_t *bytesRead)
+{
+	uint16_t rx;
+	uint8_t size;
+	uint8_t i;
+
+	memset(dst, 0, length);
+	*bytesRead = 0;
+
+	rx = spi_transfer16(0);
+	if (!(rx >> 8))
+		return -EINVAL;
+
+	size = rx & 0xff;
+
+	if (size > length)
+		return -EINVAL;
+
+	for (i = 0; i < size; ++i) {
+		rx = spi_transfer16(0);
+		if (!(rx >> 8))
+			return -EINVAL;
+
+		dst[i] = rx & 0xff;
+	}
+
+	*bytesRead = i;
+
+	return 0;
+}
+
+static int spi_device_match(struct device *dev, const void *data)
+{
+	struct spi_device *spi = container_of(dev, struct spi_device, dev);
+
+	printd("      %s %s %dkHz %d bits mode=0x%02X\n",
+		spi->modalias, dev_name(dev), spi->max_speed_hz/1000,
+		spi->bits_per_word, spi->mode);
+
+	if (strcmp("pisound-spi", spi->modalias) == 0) {
+		printi("\tFound!\n");
+		return 1;
+	}
+
+	printe("\tNot found!\n");
+	return 0;
+}
+
+static struct spi_device *pisnd_spi_find_device(void)
+{
+	struct device *dev;
+
+	printi("Searching for spi device...\n");
+	dev = bus_find_device(&spi_bus_type, NULL, NULL, spi_device_match);
+	if (dev != NULL)
+		return container_of(dev, struct spi_device, dev);
+	else
+		return NULL;
+}
+
+static void pisnd_work_handler(struct work_struct *work)
+{
+	enum { TRANSFER_SIZE = 4 };
+	enum { PISOUND_OUTPUT_BUFFER_SIZE_MILLIBYTES = 127 * 1000 };
+	enum { MIDI_MILLIBYTES_PER_JIFFIE = (3125 * 1000) / HZ };
+	int out_buffer_used_millibytes = 0;
+	unsigned long now;
+	uint8_t val;
+	uint8_t txbuf[TRANSFER_SIZE];
+	uint8_t rxbuf[TRANSFER_SIZE];
+	uint8_t midibuf[TRANSFER_SIZE];
+	int i, n;
+	bool had_data;
+
+	unsigned long last_transfer_at = jiffies;
+
+	if (work == &pisnd_work_process) {
+		if (pisnd_spi_device == NULL)
+			return;
+
+		do {
+			if (g_midi_output_substream &&
+				kfifo_avail(&spi_fifo_out) >= sizeof(midibuf)) {
+
+				n = snd_rawmidi_transmit_peek(
+					g_midi_output_substream,
+					midibuf, sizeof(midibuf)
+				);
+
+				if (n > 0) {
+					for (i = 0; i < n; ++i)
+						kfifo_put(
+							&spi_fifo_out,
+							midibuf[i]
+							);
+					snd_rawmidi_transmit_ack(
+						g_midi_output_substream,
+						i
+						);
+				}
+			}
+
+			had_data = false;
+			memset(txbuf, 0, sizeof(txbuf));
+			for (i = 0; i < sizeof(txbuf) &&
+				((out_buffer_used_millibytes+1000 <
+				PISOUND_OUTPUT_BUFFER_SIZE_MILLIBYTES) ||
+				g_ledFlashDurationChanged);
+				i += 2) {
+
+				val = 0;
+
+				if (g_ledFlashDurationChanged) {
+					txbuf[i+0] = 0xf0;
+					txbuf[i+1] = g_ledFlashDuration;
+					g_ledFlashDuration = 0;
+					g_ledFlashDurationChanged = false;
+				} else if (kfifo_get(&spi_fifo_out, &val)) {
+					txbuf[i+0] = 0x0f;
+					txbuf[i+1] = val;
+					out_buffer_used_millibytes += 1000;
+				}
+			}
+
+			spi_transfer(txbuf, rxbuf, sizeof(txbuf));
+			/* Estimate the Pisound's MIDI output buffer usage, so
+			 * that we don't overflow it. Space in the buffer should
+			 * be becoming available at the UART MIDI byte transfer
+			 * rate.
+			 */
+			now = jiffies;
+			if (now != last_transfer_at) {
+				out_buffer_used_millibytes -=
+					(now - last_transfer_at) *
+					MIDI_MILLIBYTES_PER_JIFFIE;
+				if (out_buffer_used_millibytes < 0)
+					out_buffer_used_millibytes = 0;
+				last_transfer_at = now;
+			}
+
+			for (i = 0; i < sizeof(rxbuf); i += 2) {
+				if (rxbuf[i]) {
+					kfifo_put(&spi_fifo_in, rxbuf[i+1]);
+					if (kfifo_len(&spi_fifo_in) > 16 &&
+						g_recvCallback)
+						g_recvCallback(g_recvData);
+					had_data = true;
+				}
+			}
+		} while (had_data
+			|| !kfifo_is_empty(&spi_fifo_out)
+			|| pisnd_spi_has_more()
+			|| g_ledFlashDurationChanged
+			|| out_buffer_used_millibytes != 0
+			);
+
+		if (!kfifo_is_empty(&spi_fifo_in) && g_recvCallback)
+			g_recvCallback(g_recvData);
+	}
+}
+
+static int pisnd_spi_gpio_init(struct device *dev)
+{
+	spi_reset = gpiod_get_index(dev, "reset", 1, GPIOD_ASIS);
+	data_available = gpiod_get_index(dev, "data_available", 0, GPIOD_ASIS);
+
+	gpiod_direction_output(spi_reset, 1);
+	gpiod_direction_input(data_available);
+
+	/* Reset the slave. */
+	gpiod_set_value(spi_reset, false);
+	mdelay(1);
+	gpiod_set_value(spi_reset, true);
+
+	/* Give time for spi slave to start. */
+	mdelay(64);
+
+	return 0;
+}
+
+static void pisnd_spi_gpio_uninit(void)
+{
+	gpiod_set_value(spi_reset, false);
+	gpiod_put(spi_reset);
+	spi_reset = NULL;
+
+	gpiod_put(data_available);
+	data_available = NULL;
+}
+
+static int pisnd_spi_gpio_irq_init(struct device *dev)
+{
+	return request_threaded_irq(
+		gpiod_to_irq(data_available), NULL,
+		data_available_interrupt_handler,
+		IRQF_TIMER | IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+		"data_available_int",
+		NULL
+		);
+}
+
+static void pisnd_spi_gpio_irq_uninit(void)
+{
+	free_irq(gpiod_to_irq(data_available), NULL);
+}
+
+static int spi_read_info(void)
+{
+	uint16_t tmp;
+	uint8_t count;
+	uint8_t n;
+	uint8_t i;
+	uint8_t j;
+	char buffer[257];
+	int ret;
+	char *p;
+
+	memset(g_serial_num, 0, sizeof(g_serial_num));
+	memset(g_fw_version, 0, sizeof(g_fw_version));
+	strcpy(g_hw_version, "1.0"); // Assume 1.0 hw version.
+	memset(g_id, 0, sizeof(g_id));
+
+	tmp = spi_transfer16(0);
+
+	if (!(tmp >> 8))
+		return -EINVAL;
+
+	count = tmp & 0xff;
+
+	for (i = 0; i < count; ++i) {
+		memset(buffer, 0, sizeof(buffer));
+		ret = spi_read_bytes(buffer, sizeof(buffer)-1, &n);
+
+		if (ret < 0)
+			return ret;
+
+		switch (i) {
+		case 0:
+			if (n != 2)
+				return -EINVAL;
+
+			snprintf(
+				g_fw_version,
+				MAX_VERSION_STR_LEN,
+				"%x.%02x",
+				buffer[0],
+				buffer[1]
+				);
+
+			g_fw_version[MAX_VERSION_STR_LEN-1] = '\0';
+			break;
+		case 3:
+			if (n != 2)
+				return -EINVAL;
+
+			snprintf(
+				g_hw_version,
+				MAX_VERSION_STR_LEN,
+				"%x.%x",
+				buffer[0],
+				buffer[1]
+			);
+
+			g_hw_version[MAX_VERSION_STR_LEN-1] = '\0';
+			break;
+		case 1:
+			if (n >= sizeof(g_serial_num))
+				return -EINVAL;
+
+			memcpy(g_serial_num, buffer, sizeof(g_serial_num));
+			break;
+		case 2:
+			{
+				if (n*2 >= sizeof(g_id))
+					return -EINVAL;
+
+				p = g_id;
+				for (j = 0; j < n; ++j)
+					p += sprintf(p, "%02x", buffer[j]);
+
+				*p = '\0';
+			}
+			break;
+		default:
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int pisnd_spi_init(struct device *dev)
+{
+	int ret;
+	struct spi_device *spi;
+
+	memset(g_serial_num, 0, sizeof(g_serial_num));
+	memset(g_id, 0, sizeof(g_id));
+	memset(g_fw_version, 0, sizeof(g_fw_version));
+	memset(g_hw_version, 0, sizeof(g_hw_version));
+
+	g_spi_speed_hz = 150000;
+	if (dev->of_node) {
+		struct device_node *spi_node;
+
+		spi_node = of_parse_phandle(
+			dev->of_node,
+			"spi-controller",
+			0
+			);
+
+		if (spi_node) {
+			ret = of_property_read_u32(spi_node, "spi-speed-hz", &g_spi_speed_hz);
+			if (ret != 0)
+				printe("Failed reading spi-speed-hz! (%d)\n", ret);
+
+			of_node_put(spi_node);
+		}
+	}
+	printi("Using SPI speed: %u\n", g_spi_speed_hz);
+
+	spi = pisnd_spi_find_device();
+
+	if (spi != NULL) {
+		printd("initializing spi!\n");
+		pisnd_spi_device = spi;
+		ret = spi_setup(pisnd_spi_device);
+	} else {
+		printe("SPI device not found, deferring!\n");
+		return -EPROBE_DEFER;
+	}
+
+	ret = pisnd_spi_gpio_init(dev);
+
+	if (ret < 0) {
+		printe("SPI GPIO init failed: %d\n", ret);
+		spi_dev_put(pisnd_spi_device);
+		pisnd_spi_device = NULL;
+		pisnd_spi_gpio_uninit();
+		return ret;
+	}
+
+	ret = spi_read_info();
+
+	if (ret < 0) {
+		printe("Reading card info failed: %d\n", ret);
+		spi_dev_put(pisnd_spi_device);
+		pisnd_spi_device = NULL;
+		pisnd_spi_gpio_uninit();
+		return ret;
+	}
+
+	/* Flash the LEDs. */
+	spi_transfer16(0xf008);
+
+	ret = pisnd_spi_gpio_irq_init(dev);
+	if (ret < 0) {
+		printe("SPI irq request failed: %d\n", ret);
+		spi_dev_put(pisnd_spi_device);
+		pisnd_spi_device = NULL;
+		pisnd_spi_gpio_irq_uninit();
+		pisnd_spi_gpio_uninit();
+	}
+
+	ret = pisnd_init_workqueues();
+	if (ret != 0) {
+		printe("Workqueue initialization failed: %d\n", ret);
+		spi_dev_put(pisnd_spi_device);
+		pisnd_spi_device = NULL;
+		pisnd_spi_gpio_irq_uninit();
+		pisnd_spi_gpio_uninit();
+		pisnd_uninit_workqueues();
+		return ret;
+	}
+
+	if (pisnd_spi_has_more()) {
+		printd("data is available, scheduling from init\n");
+		pisnd_schedule_process(TASK_PROCESS);
+	}
+
+	return 0;
+}
+
+static void pisnd_spi_uninit(void)
+{
+	pisnd_uninit_workqueues();
+
+	spi_dev_put(pisnd_spi_device);
+	pisnd_spi_device = NULL;
+
+	pisnd_spi_gpio_irq_uninit();
+	pisnd_spi_gpio_uninit();
+}
+
+static void pisnd_spi_flash_leds(uint8_t duration)
+{
+	g_ledFlashDuration = duration;
+	g_ledFlashDurationChanged = true;
+	printd("schedule from spi_flash_leds\n");
+	pisnd_schedule_process(TASK_PROCESS);
+}
+
+static void pisnd_spi_flush(void)
+{
+	while (!kfifo_is_empty(&spi_fifo_out)) {
+		pisnd_spi_start();
+		flush_workqueue(pisnd_workqueue);
+	}
+}
+
+static void pisnd_spi_start(void)
+{
+	printd("schedule from spi_start\n");
+	pisnd_schedule_process(TASK_PROCESS);
+}
+
+static uint8_t pisnd_spi_recv(uint8_t *buffer, uint8_t length)
+{
+	return kfifo_out(&spi_fifo_in, buffer, length);
+}
+
+static void pisnd_spi_set_callback(pisnd_spi_recv_cb cb, void *data)
+{
+	g_recvData = data;
+	g_recvCallback = cb;
+}
+
+static const char *pisnd_spi_get_serial(void)
+{
+	return g_serial_num;
+}
+
+static const char *pisnd_spi_get_id(void)
+{
+	return g_id;
+}
+
+static const char *pisnd_spi_get_fw_version(void)
+{
+	return g_fw_version;
+}
+
+static const char *pisnd_spi_get_hw_version(void)
+{
+	return g_hw_version;
+}
+
+static const struct of_device_id pisound_of_match[] = {
+	{ .compatible = "blokaslabs,pisound", },
+	{ .compatible = "blokaslabs,pisound-spi", },
+	{},
+};
+
+enum {
+	SWITCH = 0,
+	VOLUME = 1,
+};
+
+static int pisnd_ctl_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	if (kcontrol->private_value == SWITCH) {
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+		uinfo->count = 1;
+		uinfo->value.integer.min = 0;
+		uinfo->value.integer.max = 1;
+		return 0;
+	} else if (kcontrol->private_value == VOLUME) {
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+		uinfo->count = 1;
+		uinfo->value.integer.min = 0;
+		uinfo->value.integer.max = 100;
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int pisnd_ctl_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	if (kcontrol->private_value == SWITCH) {
+		ucontrol->value.integer.value[0] = 1;
+		return 0;
+	} else if (kcontrol->private_value == VOLUME) {
+		ucontrol->value.integer.value[0] = 100;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static struct snd_kcontrol_new pisnd_ctl[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "PCM Playback Switch",
+		.index = 0,
+		.private_value = SWITCH,
+		.access = SNDRV_CTL_ELEM_ACCESS_READ,
+		.info = pisnd_ctl_info,
+		.get = pisnd_ctl_get,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "PCM Playback Volume",
+		.index = 0,
+		.private_value = VOLUME,
+		.access = SNDRV_CTL_ELEM_ACCESS_READ,
+		.info = pisnd_ctl_info,
+		.get = pisnd_ctl_get,
+	},
+};
+
+static int pisnd_ctl_init(struct snd_card *card)
+{
+	int err, i;
+
+	for (i = 0; i < ARRAY_SIZE(pisnd_ctl); ++i) {
+		err = snd_ctl_add(card, snd_ctl_new1(&pisnd_ctl[i], NULL));
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+static int pisnd_ctl_uninit(void)
+{
+	return 0;
+}
+
+static struct gpio_desc *osr0, *osr1, *osr2;
+static struct gpio_desc *reset;
+
+static int pisnd_hw_params(
+	struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params
+	)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+	/* Pisound runs on fixed 32 clock counts per channel,
+	 * as generated by the master ADC.
+	 */
+	snd_soc_dai_set_bclk_ratio(cpu_dai, 32*2);
+
+	printd("rate   = %d\n", params_rate(params));
+	printd("ch     = %d\n", params_channels(params));
+	printd("bits   = %u\n",
+		snd_pcm_format_width(params_format(params)));
+	printd("format = %d\n", params_format(params));
+
+	gpiod_set_value(reset, false);
+
+	switch (params_rate(params)) {
+	case 48000:
+		gpiod_set_value(osr0, true);
+		gpiod_set_value(osr1, false);
+		gpiod_set_value(osr2, false);
+		break;
+	case 96000:
+		gpiod_set_value(osr0, true);
+		gpiod_set_value(osr1, false);
+		gpiod_set_value(osr2, true);
+		break;
+	case 192000:
+		gpiod_set_value(osr0, true);
+		gpiod_set_value(osr1, true);
+		gpiod_set_value(osr2, true);
+		break;
+	default:
+		printe("Unsupported rate %u!\n", params_rate(params));
+		return -EINVAL;
+	}
+
+	gpiod_set_value(reset, true);
+
+	return 0;
+}
+
+static unsigned int rates[3] = {
+	48000, 96000, 192000
+};
+
+static struct snd_pcm_hw_constraint_list constraints_rates = {
+	.count = ARRAY_SIZE(rates),
+	.list = rates,
+	.mask = 0,
+};
+
+static int pisnd_startup(struct snd_pcm_substream *substream)
+{
+	int err = snd_pcm_hw_constraint_list(
+		substream->runtime,
+		0,
+		SNDRV_PCM_HW_PARAM_RATE,
+		&constraints_rates
+		);
+
+	if (err < 0)
+		return err;
+
+	err = snd_pcm_hw_constraint_single(
+		substream->runtime,
+		SNDRV_PCM_HW_PARAM_CHANNELS,
+		2
+		);
+
+	if (err < 0)
+		return err;
+
+	err = snd_pcm_hw_constraint_mask64(
+		substream->runtime,
+		SNDRV_PCM_HW_PARAM_FORMAT,
+		SNDRV_PCM_FMTBIT_S16_LE |
+		SNDRV_PCM_FMTBIT_S24_LE |
+		SNDRV_PCM_FMTBIT_S32_LE
+		);
+
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static const struct snd_soc_ops pisnd_ops = {
+	.startup = pisnd_startup,
+	.hw_params = pisnd_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(pisnd,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_DUMMY()),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link pisnd_dai[] = {
+	{
+		.name           = "pisound",
+		.stream_name    = "pisound",
+		.dai_fmt        =
+			SND_SOC_DAIFMT_I2S |
+			SND_SOC_DAIFMT_NB_NF |
+			SND_SOC_DAIFMT_CBM_CFM,
+		.ops            = &pisnd_ops,
+		SND_SOC_DAILINK_REG(pisnd),
+	},
+};
+
+static int pisnd_card_probe(struct snd_soc_card *card)
+{
+	int err = pisnd_midi_init(card->snd_card);
+
+	if (err < 0) {
+		printe("pisnd_midi_init failed: %d\n", err);
+		return err;
+	}
+
+	err = pisnd_ctl_init(card->snd_card);
+	if (err < 0) {
+		printe("pisnd_ctl_init failed: %d\n", err);
+		return err;
+	}
+
+	return 0;
+}
+
+static int pisnd_card_remove(struct snd_soc_card *card)
+{
+	pisnd_ctl_uninit();
+	pisnd_midi_uninit();
+	return 0;
+}
+
+static struct snd_soc_card pisnd_card = {
+	.name         = "pisound",
+	.owner        = THIS_MODULE,
+	.dai_link     = pisnd_dai,
+	.num_links    = ARRAY_SIZE(pisnd_dai),
+	.probe        = pisnd_card_probe,
+	.remove       = pisnd_card_remove,
+};
+
+static int pisnd_init_gpio(struct device *dev)
+{
+	osr0 = gpiod_get_index(dev, "osr", 0, GPIOD_ASIS);
+	osr1 = gpiod_get_index(dev, "osr", 1, GPIOD_ASIS);
+	osr2 = gpiod_get_index(dev, "osr", 2, GPIOD_ASIS);
+
+	reset = gpiod_get_index(dev, "reset", 0, GPIOD_ASIS);
+
+	gpiod_direction_output(osr0,  1);
+	gpiod_direction_output(osr1,  1);
+	gpiod_direction_output(osr2,  1);
+	gpiod_direction_output(reset, 1);
+
+	gpiod_set_value(reset, false);
+	gpiod_set_value(osr0,   true);
+	gpiod_set_value(osr1,  false);
+	gpiod_set_value(osr2,  false);
+	gpiod_set_value(reset,  true);
+
+	return 0;
+}
+
+static int pisnd_uninit_gpio(void)
+{
+	int i;
+
+	struct gpio_desc **gpios[] = {
+		&osr0, &osr1, &osr2, &reset,
+	};
+
+	for (i = 0; i < ARRAY_SIZE(gpios); ++i) {
+		if (*gpios[i] == NULL) {
+			printd("weird, GPIO[%d] is NULL already\n", i);
+			continue;
+		}
+
+		gpiod_put(*gpios[i]);
+		*gpios[i] = NULL;
+	}
+
+	return 0;
+}
+
+static struct kobject *pisnd_kobj;
+
+static ssize_t pisnd_serial_show(
+	struct kobject *kobj,
+	struct kobj_attribute *attr,
+	char *buf
+	)
+{
+	return sprintf(buf, "%s\n", pisnd_spi_get_serial());
+}
+
+static ssize_t pisnd_id_show(
+	struct kobject *kobj,
+	struct kobj_attribute *attr,
+	char *buf
+	)
+{
+	return sprintf(buf, "%s\n", pisnd_spi_get_id());
+}
+
+static ssize_t pisnd_fw_version_show(
+	struct kobject *kobj,
+	struct kobj_attribute *attr,
+	char *buf
+	)
+{
+	return sprintf(buf, "%s\n", pisnd_spi_get_fw_version());
+}
+
+static ssize_t pisnd_hw_version_show(
+	struct kobject *kobj,
+	struct kobj_attribute *attr,
+	char *buf
+)
+{
+	return sprintf(buf, "%s\n", pisnd_spi_get_hw_version());
+}
+
+static ssize_t pisnd_led_store(
+	struct kobject *kobj,
+	struct kobj_attribute *attr,
+	const char *buf,
+	size_t length
+	)
+{
+	uint32_t timeout;
+	int err;
+
+	err = kstrtou32(buf, 10, &timeout);
+
+	if (err == 0 && timeout <= 255)
+		pisnd_spi_flash_leds(timeout);
+
+	return length;
+}
+
+static struct kobj_attribute pisnd_serial_attribute =
+	__ATTR(serial, 0444, pisnd_serial_show, NULL);
+static struct kobj_attribute pisnd_id_attribute =
+	__ATTR(id, 0444, pisnd_id_show, NULL);
+static struct kobj_attribute pisnd_fw_version_attribute =
+	__ATTR(version, 0444, pisnd_fw_version_show, NULL);
+static struct kobj_attribute pisnd_hw_version_attribute =
+__ATTR(hw_version, 0444, pisnd_hw_version_show, NULL);
+static struct kobj_attribute pisnd_led_attribute =
+	__ATTR(led, 0644, NULL, pisnd_led_store);
+
+static struct attribute *attrs[] = {
+	&pisnd_serial_attribute.attr,
+	&pisnd_id_attribute.attr,
+	&pisnd_fw_version_attribute.attr,
+	&pisnd_hw_version_attribute.attr,
+	&pisnd_led_attribute.attr,
+	NULL
+};
+
+static struct attribute_group attr_group = { .attrs = attrs };
+
+static int pisnd_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	int i;
+
+	ret = pisnd_spi_init(&pdev->dev);
+	if (ret < 0) {
+		printe("pisnd_spi_init failed: %d\n", ret);
+		return ret;
+	}
+
+	printi("Detected Pisound card:\n");
+	printi("\tSerial:           %s\n", pisnd_spi_get_serial());
+	printi("\tFirmware Version: %s\n", pisnd_spi_get_fw_version());
+	printi("\tHardware Version: %s\n", pisnd_spi_get_hw_version());
+	printi("\tId:               %s\n", pisnd_spi_get_id());
+
+	pisnd_kobj = kobject_create_and_add("pisound", kernel_kobj);
+	if (!pisnd_kobj) {
+		pisnd_spi_uninit();
+		return -ENOMEM;
+	}
+
+	ret = sysfs_create_group(pisnd_kobj, &attr_group);
+	if (ret < 0) {
+		pisnd_spi_uninit();
+		kobject_put(pisnd_kobj);
+		return -ENOMEM;
+	}
+
+	pisnd_init_gpio(&pdev->dev);
+	pisnd_card.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+
+		i2s_node = of_parse_phandle(
+			pdev->dev.of_node,
+			"i2s-controller",
+			0
+			);
+
+		for (i = 0; i < pisnd_card.num_links; ++i) {
+			struct snd_soc_dai_link *dai = &pisnd_dai[i];
+
+			if (i2s_node) {
+				dai->cpus->dai_name = NULL;
+				dai->cpus->of_node = i2s_node;
+				dai->platforms->name = NULL;
+				dai->platforms->of_node = i2s_node;
+				dai->stream_name = pisnd_spi_get_serial();
+			}
+		}
+	}
+
+	ret = snd_soc_register_card(&pisnd_card);
+
+	if (ret < 0) {
+		if (ret != -EPROBE_DEFER)
+			printe("snd_soc_register_card() failed: %d\n", ret);
+		pisnd_uninit_gpio();
+		kobject_put(pisnd_kobj);
+		pisnd_spi_uninit();
+	}
+
+	return ret;
+}
+
+static void pisnd_remove(struct platform_device *pdev)
+{
+	printi("Unloading.\n");
+
+	if (pisnd_kobj) {
+		kobject_put(pisnd_kobj);
+		pisnd_kobj = NULL;
+	}
+
+	pisnd_spi_uninit();
+
+	/* Turn off */
+	gpiod_set_value(reset, false);
+	pisnd_uninit_gpio();
+
+	snd_soc_unregister_card(&pisnd_card);
+}
+
+MODULE_DEVICE_TABLE(of, pisound_of_match);
+
+static struct platform_driver pisnd_driver = {
+	.driver = {
+		.name           = "snd-rpi-pisound",
+		.owner          = THIS_MODULE,
+		.of_match_table = pisound_of_match,
+	},
+	.probe              = pisnd_probe,
+	.remove             = pisnd_remove,
+};
+
+module_platform_driver(pisnd_driver);
+
+MODULE_AUTHOR("Giedrius Trainavicius <giedrius@blokas.io>");
+MODULE_DESCRIPTION("ASoC Driver for Pisound, https://blokas.io/pisound");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/rpi-cirrus.c b/sound/soc/bcm/rpi-cirrus.c
new file mode 100644
index 000000000000..b04c1bf38f6b
--- /dev/null
+++ b/sound/soc/bcm/rpi-cirrus.c
@@ -0,0 +1,1027 @@
+/*
+ * ASoC machine driver for Cirrus Logic Audio Card
+ * (with WM5102 and WM8804 codecs)
+ *
+ * Copyright 2015-2017 Matthias Reichl <hias@horus.com>
+ *
+ * Based on rpi-cirrus-sound-pi driver (c) Wolfson / Cirrus Logic Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/delay.h>
+#include <sound/pcm_params.h>
+
+#include <linux/mfd/arizona/registers.h>
+
+#include "../codecs/wm5102.h"
+#include "../codecs/wm8804.h"
+
+#define WM8804_CLKOUT_HZ 12000000
+
+#define RPI_CIRRUS_DEFAULT_RATE 44100
+#define WM5102_MAX_SYSCLK_1 49152000 /* max sysclk for 4K family */
+#define WM5102_MAX_SYSCLK_2 45158400 /* max sysclk for 11.025K family */
+
+static inline unsigned int calc_sysclk(unsigned int rate)
+{
+	return (rate % 4000) ? WM5102_MAX_SYSCLK_2 : WM5102_MAX_SYSCLK_1;
+}
+
+enum {
+	DAI_WM5102 = 0,
+	DAI_WM8804,
+};
+
+struct rpi_cirrus_priv {
+	/* mutex for synchronzing FLL1 access with DAPM */
+	struct mutex lock;
+	unsigned int card_rate;
+	int sync_path_enable;
+	int fll1_freq; /* negative means RefClock in spdif rx case */
+
+	/* track hw params/free for substreams */
+	unsigned int params_set;
+	unsigned int min_rate_idx, max_rate_idx;
+	unsigned char iec958_status[4];
+};
+
+/* helper functions */
+static inline struct snd_soc_pcm_runtime *get_wm5102_runtime(
+	struct snd_soc_card *card) {
+	return snd_soc_get_pcm_runtime(card, &card->dai_link[DAI_WM5102]);
+}
+
+static inline struct snd_soc_pcm_runtime *get_wm8804_runtime(
+	struct snd_soc_card *card) {
+	return snd_soc_get_pcm_runtime(card, &card->dai_link[DAI_WM8804]);
+}
+
+
+struct rate_info {
+	unsigned int value;
+	char *text;
+};
+
+static struct rate_info min_rates[] = {
+	{     0, "off"},
+	{ 32000, "32kHz"},
+	{ 44100, "44.1kHz"}
+};
+
+#define NUM_MIN_RATES ARRAY_SIZE(min_rates)
+
+static int rpi_cirrus_min_rate_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = NUM_MIN_RATES;
+
+	if (uinfo->value.enumerated.item >= NUM_MIN_RATES)
+		uinfo->value.enumerated.item = NUM_MIN_RATES - 1;
+	strcpy(uinfo->value.enumerated.name,
+		min_rates[uinfo->value.enumerated.item].text);
+	return 0;
+}
+
+static int rpi_cirrus_min_rate_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+
+	ucontrol->value.enumerated.item[0] = priv->min_rate_idx;
+	return 0;
+}
+
+static int rpi_cirrus_min_rate_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	int changed = 0;
+
+	if (priv->min_rate_idx != ucontrol->value.enumerated.item[0]) {
+		changed = 1;
+		priv->min_rate_idx = ucontrol->value.enumerated.item[0];
+	}
+
+	return changed;
+}
+
+static struct rate_info max_rates[] = {
+	{     0, "off"},
+	{ 48000, "48kHz"},
+	{ 96000, "96kHz"}
+};
+
+#define NUM_MAX_RATES ARRAY_SIZE(max_rates)
+
+static int rpi_cirrus_max_rate_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = NUM_MAX_RATES;
+	if (uinfo->value.enumerated.item >= NUM_MAX_RATES)
+		uinfo->value.enumerated.item = NUM_MAX_RATES - 1;
+	strcpy(uinfo->value.enumerated.name,
+		max_rates[uinfo->value.enumerated.item].text);
+	return 0;
+}
+
+static int rpi_cirrus_max_rate_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+
+	ucontrol->value.enumerated.item[0] = priv->max_rate_idx;
+	return 0;
+}
+
+static int rpi_cirrus_max_rate_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	int changed = 0;
+
+	if (priv->max_rate_idx != ucontrol->value.enumerated.item[0]) {
+		changed = 1;
+		priv->max_rate_idx = ucontrol->value.enumerated.item[0];
+	}
+
+	return changed;
+}
+
+static int rpi_cirrus_spdif_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+	uinfo->count = 1;
+	return 0;
+}
+
+static int rpi_cirrus_spdif_playback_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	int i;
+
+	for (i = 0; i < 4; i++)
+		ucontrol->value.iec958.status[i] = priv->iec958_status[i];
+
+	return 0;
+}
+
+static int rpi_cirrus_spdif_playback_put(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_component *wm8804_component =
+		snd_soc_rtd_to_codec(get_wm8804_runtime(card), 0)->component;
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	unsigned char *stat = priv->iec958_status;
+	unsigned char *ctrl_stat = ucontrol->value.iec958.status;
+	unsigned int mask;
+	int i, changed = 0;
+
+	for (i = 0; i < 4; i++) {
+		mask = (i == 3) ? 0x3f : 0xff;
+		if ((ctrl_stat[i] & mask) != (stat[i] & mask)) {
+			changed = 1;
+			stat[i] = ctrl_stat[i] & mask;
+			snd_soc_component_update_bits(wm8804_component,
+				WM8804_SPDTX1 + i, mask, stat[i]);
+		}
+	}
+
+	return changed;
+}
+
+static int rpi_cirrus_spdif_mask_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.iec958.status[0] = 0xff;
+	ucontrol->value.iec958.status[1] = 0xff;
+	ucontrol->value.iec958.status[2] = 0xff;
+	ucontrol->value.iec958.status[3] = 0x3f;
+
+	return 0;
+}
+
+static int rpi_cirrus_spdif_capture_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_component *wm8804_component =
+		snd_soc_rtd_to_codec(get_wm8804_runtime(card), 0)->component;
+	unsigned int val, mask;
+	int i;
+
+	for (i = 0; i < 4; i++) {
+		val = snd_soc_component_read(wm8804_component,
+			WM8804_RXCHAN1 + i);
+		mask = (i == 3) ? 0x3f : 0xff;
+		ucontrol->value.iec958.status[i] = val & mask;
+	}
+
+	return 0;
+}
+
+#define SPDIF_FLAG_CTRL(desc, reg, bit, invert) \
+{ \
+		.access =  SNDRV_CTL_ELEM_ACCESS_READ \
+			   | SNDRV_CTL_ELEM_ACCESS_VOLATILE, \
+		.iface =   SNDRV_CTL_ELEM_IFACE_MIXER, \
+		.name =    SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE) \
+				desc " Flag", \
+		.info =    snd_ctl_boolean_mono_info, \
+		.get =     rpi_cirrus_spdif_status_flag_get, \
+		.private_value = \
+			(bit) | ((reg) << 8) | ((invert) << 16) \
+}
+
+static int rpi_cirrus_spdif_status_flag_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_component *wm8804_component =
+		snd_soc_rtd_to_codec(get_wm8804_runtime(card), 0)->component;
+
+	unsigned int bit = kcontrol->private_value & 0xff;
+	unsigned int reg = (kcontrol->private_value >> 8) & 0xff;
+	unsigned int invert = (kcontrol->private_value >> 16) & 0xff;
+	unsigned int val;
+	bool flag;
+
+	val = snd_soc_component_read(wm8804_component, reg);
+
+	flag = val & (1 << bit);
+
+	ucontrol->value.integer.value[0] = invert ? !flag : flag;
+
+	return 0;
+}
+
+static const char * const recovered_frequency_texts[] = {
+	"176.4/192 kHz",
+	"88.2/96 kHz",
+	"44.1/48 kHz",
+	"32 kHz"
+};
+
+#define NUM_RECOVERED_FREQUENCIES \
+	ARRAY_SIZE(recovered_frequency_texts)
+
+static int rpi_cirrus_recovered_frequency_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = NUM_RECOVERED_FREQUENCIES;
+	if (uinfo->value.enumerated.item >= NUM_RECOVERED_FREQUENCIES)
+		uinfo->value.enumerated.item = NUM_RECOVERED_FREQUENCIES - 1;
+	strcpy(uinfo->value.enumerated.name,
+		recovered_frequency_texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int rpi_cirrus_recovered_frequency_get(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct snd_soc_component *wm8804_component =
+		snd_soc_rtd_to_codec(get_wm8804_runtime(card), 0)->component;
+	unsigned int val;
+
+	val = snd_soc_component_read(wm8804_component, WM8804_SPDSTAT);
+
+	ucontrol->value.enumerated.item[0] = (val >> 4) & 0x03;
+	return 0;
+}
+
+static const struct snd_kcontrol_new rpi_cirrus_controls[] = {
+	{
+		.iface =   SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =    "Min Sample Rate",
+		.info =    rpi_cirrus_min_rate_info,
+		.get =     rpi_cirrus_min_rate_get,
+		.put =     rpi_cirrus_min_rate_put,
+	},
+	{
+		.iface =   SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =    "Max Sample Rate",
+		.info =    rpi_cirrus_max_rate_info,
+		.get =     rpi_cirrus_max_rate_get,
+		.put =     rpi_cirrus_max_rate_put,
+	},
+	{
+		.iface =   SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =    SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
+		.info =    rpi_cirrus_spdif_info,
+		.get =     rpi_cirrus_spdif_playback_get,
+		.put =     rpi_cirrus_spdif_playback_put,
+	},
+	{
+		.access =  SNDRV_CTL_ELEM_ACCESS_READ
+			   | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+		.iface =   SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =    SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT),
+		.info =    rpi_cirrus_spdif_info,
+		.get =     rpi_cirrus_spdif_capture_get,
+	},
+	{
+		.access =  SNDRV_CTL_ELEM_ACCESS_READ,
+		.iface =   SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =    SNDRV_CTL_NAME_IEC958("", PLAYBACK, MASK),
+		.info =    rpi_cirrus_spdif_info,
+		.get =     rpi_cirrus_spdif_mask_get,
+	},
+	{
+		.access =  SNDRV_CTL_ELEM_ACCESS_READ
+			   | SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+		.iface =   SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name =    SNDRV_CTL_NAME_IEC958("", CAPTURE, NONE)
+				"Recovered Frequency",
+		.info =    rpi_cirrus_recovered_frequency_info,
+		.get =     rpi_cirrus_recovered_frequency_get,
+	},
+	SPDIF_FLAG_CTRL("Audio", WM8804_SPDSTAT, 0, 1),
+	SPDIF_FLAG_CTRL("Non-PCM", WM8804_SPDSTAT, 1, 0),
+	SPDIF_FLAG_CTRL("Copyright", WM8804_SPDSTAT, 2, 1),
+	SPDIF_FLAG_CTRL("De-Emphasis", WM8804_SPDSTAT, 3, 0),
+	SPDIF_FLAG_CTRL("Lock", WM8804_SPDSTAT, 6, 1),
+	SPDIF_FLAG_CTRL("Invalid", WM8804_INTSTAT, 1, 0),
+	SPDIF_FLAG_CTRL("TransErr", WM8804_INTSTAT, 3, 0),
+};
+
+static const char * const linein_micbias_texts[] = {
+	"off", "on",
+};
+
+static SOC_ENUM_SINGLE_VIRT_DECL(linein_micbias_enum,
+	linein_micbias_texts);
+
+static const struct snd_kcontrol_new linein_micbias_mux =
+	SOC_DAPM_ENUM("Route", linein_micbias_enum);
+
+static int rpi_cirrus_spdif_rx_enable_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *kcontrol, int event);
+
+const struct snd_soc_dapm_widget rpi_cirrus_dapm_widgets[] = {
+	SND_SOC_DAPM_MIC("DMIC", NULL),
+	SND_SOC_DAPM_MIC("Headset Mic", NULL),
+	SND_SOC_DAPM_INPUT("Line Input"),
+	SND_SOC_DAPM_MIC("Line Input with Micbias", NULL),
+	SND_SOC_DAPM_MUX("Line Input Micbias", SND_SOC_NOPM, 0, 0,
+		&linein_micbias_mux),
+	SND_SOC_DAPM_INPUT("dummy SPDIF in"),
+	SND_SOC_DAPM_PGA_E("dummy SPDIFRX", SND_SOC_NOPM, 0, 0, NULL, 0,
+		rpi_cirrus_spdif_rx_enable_event,
+		SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_INPUT("Dummy Input"),
+	SND_SOC_DAPM_OUTPUT("Dummy Output"),
+};
+
+const struct snd_soc_dapm_route rpi_cirrus_dapm_routes[] = {
+	{ "IN1L", NULL, "Headset Mic" },
+	{ "IN1R", NULL, "Headset Mic" },
+	{ "Headset Mic", NULL, "MICBIAS1" },
+
+	{ "IN2L", NULL, "DMIC" },
+	{ "IN2R", NULL, "DMIC" },
+	{ "DMIC", NULL, "MICBIAS2" },
+
+	{ "IN3L", NULL, "Line Input Micbias" },
+	{ "IN3R", NULL, "Line Input Micbias" },
+
+	{ "Line Input Micbias", "off", "Line Input" },
+	{ "Line Input Micbias", "on", "Line Input with Micbias" },
+
+	/* Make sure MICVDD is enabled, otherwise we get noise */
+	{ "Line Input", NULL, "MICVDD" },
+	{ "Line Input with Micbias", NULL, "MICBIAS3" },
+
+	/* Dummy routes to check whether SPDIF RX is enabled or not */
+	{"dummy SPDIFRX", NULL, "dummy SPDIF in"},
+	{"AIFTX", NULL, "dummy SPDIFRX"},
+
+	/*
+	 * Dummy routes to keep wm5102 from staying off on
+	 * playback/capture if all mixers are off.
+	 */
+	{ "Dummy Output", NULL, "AIF1RX1" },
+	{ "Dummy Output", NULL, "AIF1RX2" },
+	{ "AIF1TX1", NULL, "Dummy Input" },
+	{ "AIF1TX2", NULL, "Dummy Input" },
+};
+
+static int rpi_cirrus_clear_flls(struct snd_soc_card *card,
+	struct snd_soc_component *wm5102_component) {
+
+	int ret1, ret2;
+
+	ret1 = snd_soc_component_set_pll(wm5102_component,
+		WM5102_FLL1, ARIZONA_FLL_SRC_NONE, 0, 0);
+	ret2 = snd_soc_component_set_pll(wm5102_component,
+		WM5102_FLL1_REFCLK, ARIZONA_FLL_SRC_NONE, 0, 0);
+
+	if (ret1) {
+		dev_warn(card->dev,
+			"setting FLL1 to zero failed: %d\n", ret1);
+		return ret1;
+	}
+	if (ret2) {
+		dev_warn(card->dev,
+			"setting FLL1_REFCLK to zero failed: %d\n", ret2);
+		return ret2;
+	}
+	return 0;
+}
+
+static int rpi_cirrus_set_fll(struct snd_soc_card *card,
+	struct snd_soc_component *wm5102_component, unsigned int clk_freq)
+{
+	int ret = snd_soc_component_set_pll(wm5102_component,
+		WM5102_FLL1,
+		ARIZONA_CLK_SRC_MCLK1,
+		WM8804_CLKOUT_HZ,
+		clk_freq);
+	if (ret)
+		dev_err(card->dev, "Failed to set FLL1 to %d: %d\n",
+			clk_freq, ret);
+
+	usleep_range(1000, 2000);
+	return ret;
+}
+
+static int rpi_cirrus_set_fll_refclk(struct snd_soc_card *card,
+	struct snd_soc_component *wm5102_component,
+	unsigned int clk_freq, unsigned int aif2_freq)
+{
+	int ret = snd_soc_component_set_pll(wm5102_component,
+		WM5102_FLL1_REFCLK,
+		ARIZONA_CLK_SRC_MCLK1,
+		WM8804_CLKOUT_HZ,
+		clk_freq);
+	if (ret) {
+		dev_err(card->dev,
+			"Failed to set FLL1_REFCLK to %d: %d\n",
+			clk_freq, ret);
+		return ret;
+	}
+
+	ret = snd_soc_component_set_pll(wm5102_component,
+		WM5102_FLL1,
+		ARIZONA_CLK_SRC_AIF2BCLK,
+		aif2_freq, clk_freq);
+	if (ret)
+		dev_err(card->dev,
+			"Failed to set FLL1 with Sync Clock %d to %d: %d\n",
+			aif2_freq, clk_freq, ret);
+
+	usleep_range(1000, 2000);
+	return ret;
+}
+
+static int rpi_cirrus_spdif_rx_enable_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_card *card = w->dapm->card;
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	struct snd_soc_component *wm5102_component =
+		snd_soc_rtd_to_codec(get_wm5102_runtime(card), 0)->component;
+
+	unsigned int clk_freq, aif2_freq;
+	int ret = 0;
+
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+		mutex_lock(&priv->lock);
+
+		/* Enable sync path in case of SPDIF capture use case */
+
+		clk_freq = calc_sysclk(priv->card_rate);
+		aif2_freq = 64 * priv->card_rate;
+
+		dev_dbg(card->dev,
+			"spdif_rx: changing FLL1 to use Ref Clock clk: %d spdif: %d\n",
+			clk_freq, aif2_freq);
+
+		ret = rpi_cirrus_clear_flls(card, wm5102_component);
+		if (ret) {
+			dev_err(card->dev, "spdif_rx: failed to clear FLLs\n");
+			goto out;
+		}
+
+		ret = rpi_cirrus_set_fll_refclk(card, wm5102_component,
+			clk_freq, aif2_freq);
+
+		if (ret) {
+			dev_err(card->dev, "spdif_rx: failed to set FLLs\n");
+			goto out;
+		}
+
+		/* set to negative to indicate we're doing spdif rx */
+		priv->fll1_freq = -clk_freq;
+		priv->sync_path_enable = 1;
+		break;
+
+	case SND_SOC_DAPM_POST_PMD:
+		mutex_lock(&priv->lock);
+		priv->sync_path_enable = 0;
+		break;
+
+	default:
+		return 0;
+	}
+
+out:
+	mutex_unlock(&priv->lock);
+	return ret;
+}
+
+static int rpi_cirrus_set_bias_level(struct snd_soc_card *card,
+	struct snd_soc_dapm_context *dapm,
+	enum snd_soc_bias_level level)
+{
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	struct snd_soc_pcm_runtime *wm5102_runtime = get_wm5102_runtime(card);
+	struct snd_soc_component *wm5102_component =
+		snd_soc_rtd_to_codec(wm5102_runtime, 0)->component;
+
+	int ret = 0;
+	unsigned int clk_freq;
+
+	if (dapm->dev != snd_soc_rtd_to_codec(wm5102_runtime, 0)->dev)
+		return 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_PREPARE:
+		if (dapm->bias_level == SND_SOC_BIAS_ON)
+			break;
+
+		mutex_lock(&priv->lock);
+
+		if (!priv->sync_path_enable) {
+			clk_freq = calc_sysclk(priv->card_rate);
+
+			dev_dbg(card->dev,
+				"set_bias: changing FLL1 from %d to %d\n",
+				priv->fll1_freq, clk_freq);
+
+			ret = rpi_cirrus_set_fll(card,
+				wm5102_component, clk_freq);
+			if (ret)
+				dev_err(card->dev,
+					"set_bias: Failed to set FLL1\n");
+			else
+				priv->fll1_freq = clk_freq;
+		}
+		mutex_unlock(&priv->lock);
+		break;
+	default:
+		break;
+	}
+
+	return ret;
+}
+
+static int rpi_cirrus_set_bias_level_post(struct snd_soc_card *card,
+	struct snd_soc_dapm_context *dapm,
+	enum snd_soc_bias_level level)
+{
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	struct snd_soc_pcm_runtime *wm5102_runtime = get_wm5102_runtime(card);
+	struct snd_soc_component *wm5102_component =
+		snd_soc_rtd_to_codec(wm5102_runtime, 0)->component;
+
+	if (dapm->dev != snd_soc_rtd_to_codec(wm5102_runtime, 0)->dev)
+		return 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_STANDBY:
+		mutex_lock(&priv->lock);
+
+		dev_dbg(card->dev,
+			"set_bias_post: changing FLL1 from %d to off\n",
+				priv->fll1_freq);
+
+		if (rpi_cirrus_clear_flls(card, wm5102_component))
+			dev_err(card->dev,
+				"set_bias_post: failed to clear FLLs\n");
+		else
+			priv->fll1_freq = 0;
+
+		mutex_unlock(&priv->lock);
+
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int rpi_cirrus_set_wm8804_pll(struct snd_soc_card *card,
+	struct snd_soc_dai *wm8804_dai, unsigned int rate)
+{
+	int ret;
+
+	/* use 256fs */
+	unsigned int clk_freq = rate * 256;
+
+	ret = snd_soc_dai_set_pll(wm8804_dai, 0, 0,
+		WM8804_CLKOUT_HZ, clk_freq);
+	if (ret) {
+		dev_err(card->dev,
+			"Failed to set WM8804 PLL to %d: %d\n", clk_freq, ret);
+		return ret;
+	}
+
+	/* Set MCLK as PLL Output */
+	ret = snd_soc_dai_set_sysclk(wm8804_dai,
+		WM8804_TX_CLKSRC_PLL, clk_freq, 0);
+	if (ret) {
+		dev_err(card->dev,
+			"Failed to set MCLK as PLL Output: %d\n", ret);
+		return ret;
+	}
+
+	return ret;
+}
+
+static int rpi_cirrus_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_card *card = rtd->card;
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	unsigned int min_rate = min_rates[priv->min_rate_idx].value;
+	unsigned int max_rate = max_rates[priv->max_rate_idx].value;
+
+	if (min_rate || max_rate) {
+		if (max_rate == 0)
+			max_rate = UINT_MAX;
+
+		dev_dbg(card->dev,
+			"startup: limiting rate to %u-%u\n",
+			min_rate, max_rate);
+
+		snd_pcm_hw_constraint_minmax(substream->runtime,
+			SNDRV_PCM_HW_PARAM_RATE, min_rate, max_rate);
+	}
+
+	return 0;
+}
+
+static struct snd_soc_pcm_stream rpi_cirrus_dai_link2_params = {
+	.formats = SNDRV_PCM_FMTBIT_S24_LE,
+	.channels_min = 2,
+	.channels_max = 2,
+	.rate_min = RPI_CIRRUS_DEFAULT_RATE,
+	.rate_max = RPI_CIRRUS_DEFAULT_RATE,
+};
+
+static int rpi_cirrus_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_card *card = rtd->card;
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	struct snd_soc_dai *bcm_i2s_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	struct snd_soc_component *wm5102_component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_dai *wm8804_dai = snd_soc_rtd_to_codec(get_wm8804_runtime(card), 0);
+
+	int ret;
+
+	unsigned int width = snd_pcm_format_width(params_format(params));
+	unsigned int rate = params_rate(params);
+	unsigned int clk_freq = calc_sysclk(rate);
+
+	/* Using powers of 2 allows for an integer clock divisor */
+	width = width <= 16 ? 16 : 32;
+
+	mutex_lock(&priv->lock);
+
+	dev_dbg(card->dev, "hw_params: setting rate to %d\n", rate);
+
+	ret = snd_soc_dai_set_bclk_ratio(bcm_i2s_dai, 2 * width);
+	if (ret) {
+		dev_err(card->dev, "set_bclk_ratio failed: %d\n", ret);
+		goto out;
+	}
+
+	ret = snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_codec(rtd, 0), 0x03, 0x03, 2, width);
+	if (ret) {
+		dev_err(card->dev, "set_tdm_slot failed: %d\n", ret);
+		goto out;
+	}
+
+	/* WM8804 supports sample rates from 32k only */
+	if (rate >= 32000) {
+		ret = rpi_cirrus_set_wm8804_pll(card, wm8804_dai, rate);
+		if (ret)
+			goto out;
+	}
+
+	ret = snd_soc_component_set_sysclk(wm5102_component,
+		ARIZONA_CLK_SYSCLK,
+		ARIZONA_CLK_SRC_FLL1,
+		clk_freq,
+		SND_SOC_CLOCK_IN);
+	if (ret) {
+		dev_err(card->dev, "Failed to set SYSCLK: %d\n", ret);
+		goto out;
+	}
+
+	if ((priv->fll1_freq > 0) && (priv->fll1_freq != clk_freq)) {
+		dev_dbg(card->dev,
+			"hw_params: changing FLL1 from %d to %d\n",
+			priv->fll1_freq, clk_freq);
+
+		if (rpi_cirrus_clear_flls(card, wm5102_component)) {
+			dev_err(card->dev, "hw_params: failed to clear FLLs\n");
+			goto out;
+		}
+
+		if (rpi_cirrus_set_fll(card, wm5102_component, clk_freq)) {
+			dev_err(card->dev, "hw_params: failed to set FLL\n");
+			goto out;
+		}
+
+		priv->fll1_freq = clk_freq;
+	}
+
+	priv->card_rate = rate;
+	rpi_cirrus_dai_link2_params.rate_min = rate;
+	rpi_cirrus_dai_link2_params.rate_max = rate;
+
+	priv->params_set |= 1 << substream->stream;
+
+out:
+	mutex_unlock(&priv->lock);
+
+	return ret;
+}
+
+static int rpi_cirrus_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_card *card = rtd->card;
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	struct snd_soc_component *wm5102_component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	int ret;
+	unsigned int old_params_set = priv->params_set;
+
+	priv->params_set &= ~(1 << substream->stream);
+
+	/* disable sysclk if this was the last open stream */
+	if (priv->params_set == 0 && old_params_set) {
+		dev_dbg(card->dev,
+			"hw_free: Setting SYSCLK to Zero\n");
+
+		ret = snd_soc_component_set_sysclk(wm5102_component,
+			ARIZONA_CLK_SYSCLK,
+			ARIZONA_CLK_SRC_FLL1,
+			0,
+			SND_SOC_CLOCK_IN);
+		if (ret)
+			dev_err(card->dev,
+				"hw_free: Failed to set SYSCLK to Zero: %d\n",
+				ret);
+	}
+	return 0;
+}
+
+static int rpi_cirrus_init_wm5102(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	int ret;
+
+	/* no 32kHz input, derive it from sysclk if needed  */
+	snd_soc_component_update_bits(component,
+			ARIZONA_CLOCK_32K_1, ARIZONA_CLK_32K_SRC_MASK, 2);
+
+	if (rpi_cirrus_clear_flls(rtd->card, component))
+		dev_warn(rtd->card->dev,
+			"init_wm5102: failed to clear FLLs\n");
+
+	ret = snd_soc_component_set_sysclk(component,
+		ARIZONA_CLK_SYSCLK, ARIZONA_CLK_SRC_FLL1,
+		0, SND_SOC_CLOCK_IN);
+	if (ret) {
+		dev_err(rtd->card->dev,
+			"Failed to set SYSCLK to Zero: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int rpi_cirrus_init_wm8804(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_component *component = codec_dai->component;
+	struct snd_soc_card *card = rtd->card;
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	unsigned int val, mask;
+	int i, ret;
+
+	for (i = 0; i < 4; i++) {
+		val = snd_soc_component_read(component,
+			WM8804_SPDTX1 + i);
+		mask = (i == 3) ? 0x3f : 0xff;
+		priv->iec958_status[i] = val & mask;
+	}
+
+	/* Setup for 256fs */
+	ret = snd_soc_dai_set_clkdiv(codec_dai,
+		WM8804_MCLK_DIV, WM8804_MCLKDIV_256FS);
+	if (ret) {
+		dev_err(card->dev,
+			"init_wm8804: Failed to set MCLK_DIV to 256fs: %d\n",
+			ret);
+		return ret;
+	}
+
+	/* Output OSC on CLKOUT */
+	ret = snd_soc_dai_set_sysclk(codec_dai,
+		WM8804_CLKOUT_SRC_OSCCLK, WM8804_CLKOUT_HZ, 0);
+	if (ret)
+		dev_err(card->dev,
+			"init_wm8804: Failed to set CLKOUT as OSC Frequency: %d\n",
+			ret);
+
+	/* Init PLL with default samplerate */
+	ret = rpi_cirrus_set_wm8804_pll(card, codec_dai,
+		RPI_CIRRUS_DEFAULT_RATE);
+	if (ret)
+		dev_err(card->dev,
+			"init_wm8804: Failed to setup PLL for %dHz: %d\n",
+			RPI_CIRRUS_DEFAULT_RATE, ret);
+
+	return ret;
+}
+
+static struct snd_soc_ops rpi_cirrus_ops = {
+	.startup = rpi_cirrus_startup,
+	.hw_params = rpi_cirrus_hw_params,
+	.hw_free = rpi_cirrus_hw_free,
+};
+
+SND_SOC_DAILINK_DEFS(wm5102,
+     DAILINK_COMP_ARRAY(COMP_EMPTY()),
+     DAILINK_COMP_ARRAY(COMP_CODEC("wm5102-codec", "wm5102-aif1")),
+     DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+SND_SOC_DAILINK_DEFS(wm8804,
+     DAILINK_COMP_ARRAY(COMP_CPU("wm5102-aif2")),
+     DAILINK_COMP_ARRAY(COMP_CODEC("wm8804.1-003b", "wm8804-spdif")));
+
+static struct snd_soc_dai_link rpi_cirrus_dai[] = {
+	[DAI_WM5102] = {
+		.name		= "WM5102",
+		.stream_name	= "WM5102 AiFi",
+		.dai_fmt	=   SND_SOC_DAIFMT_I2S
+				  | SND_SOC_DAIFMT_NB_NF
+				  | SND_SOC_DAIFMT_CBM_CFM,
+		.ops		= &rpi_cirrus_ops,
+		.init		= rpi_cirrus_init_wm5102,
+		SND_SOC_DAILINK_REG(wm5102),
+	},
+	[DAI_WM8804] = {
+		.name		= "WM5102 SPDIF",
+		.stream_name	= "SPDIF Tx/Rx",
+		.dai_fmt	=   SND_SOC_DAIFMT_I2S
+				  | SND_SOC_DAIFMT_NB_NF
+				  | SND_SOC_DAIFMT_CBM_CFM,
+		.ignore_suspend = 1,
+		.c2c_params	= &rpi_cirrus_dai_link2_params,
+		.init		= rpi_cirrus_init_wm8804,
+		SND_SOC_DAILINK_REG(wm8804),
+	},
+};
+
+
+static int rpi_cirrus_late_probe(struct snd_soc_card *card)
+{
+	struct rpi_cirrus_priv *priv = snd_soc_card_get_drvdata(card);
+	struct snd_soc_pcm_runtime *wm5102_runtime = get_wm5102_runtime(card);
+	struct snd_soc_pcm_runtime *wm8804_runtime = get_wm8804_runtime(card);
+	int ret;
+
+	dev_dbg(card->dev, "iec958_bits: %02x %02x %02x %02x\n",
+		priv->iec958_status[0],
+		priv->iec958_status[1],
+		priv->iec958_status[2],
+		priv->iec958_status[3]);
+
+	ret = snd_soc_dai_set_sysclk(
+		snd_soc_rtd_to_codec(wm5102_runtime, 0), ARIZONA_CLK_SYSCLK, 0, 0);
+	if (ret) {
+		dev_err(card->dev,
+			"Failed to set WM5102 codec dai clk domain: %d\n", ret);
+		return ret;
+	}
+
+	ret = snd_soc_dai_set_sysclk(
+		snd_soc_rtd_to_cpu(wm8804_runtime, 0), ARIZONA_CLK_SYSCLK, 0, 0);
+	if (ret)
+		dev_err(card->dev,
+			"Failed to set WM8804 codec dai clk domain: %d\n", ret);
+
+	return ret;
+}
+
+/* audio machine driver */
+static struct snd_soc_card rpi_cirrus_card = {
+	.name			= "RPi-Cirrus",
+	.driver_name		= "RPiCirrus",
+	.owner			= THIS_MODULE,
+	.dai_link		= rpi_cirrus_dai,
+	.num_links		= ARRAY_SIZE(rpi_cirrus_dai),
+	.late_probe		= rpi_cirrus_late_probe,
+	.controls		= rpi_cirrus_controls,
+	.num_controls		= ARRAY_SIZE(rpi_cirrus_controls),
+	.dapm_widgets		= rpi_cirrus_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(rpi_cirrus_dapm_widgets),
+	.dapm_routes		= rpi_cirrus_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(rpi_cirrus_dapm_routes),
+	.set_bias_level		= rpi_cirrus_set_bias_level,
+	.set_bias_level_post	= rpi_cirrus_set_bias_level_post,
+};
+
+static int rpi_cirrus_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	struct rpi_cirrus_priv *priv;
+	struct device_node *i2s_node;
+
+	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->min_rate_idx = 1; /* min samplerate 32kHz */
+	priv->card_rate = RPI_CIRRUS_DEFAULT_RATE;
+
+	mutex_init(&priv->lock);
+
+	snd_soc_card_set_drvdata(&rpi_cirrus_card, priv);
+
+	if (!pdev->dev.of_node)
+		return -ENODEV;
+
+	i2s_node = of_parse_phandle(
+			pdev->dev.of_node, "i2s-controller", 0);
+	if (!i2s_node) {
+		dev_err(&pdev->dev, "i2s-controller missing in DT\n");
+		return -ENODEV;
+	}
+
+	rpi_cirrus_dai[DAI_WM5102].cpus->of_node = i2s_node;
+	rpi_cirrus_dai[DAI_WM5102].platforms->of_node = i2s_node;
+
+	rpi_cirrus_card.dev = &pdev->dev;
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &rpi_cirrus_card);
+	if (ret) {
+		if (ret == -EPROBE_DEFER)
+			dev_dbg(&pdev->dev,
+				"register card requested probe deferral\n");
+		else
+			dev_err(&pdev->dev,
+				"Failed to register card: %d\n", ret);
+	}
+
+	return ret;
+}
+
+static const struct of_device_id rpi_cirrus_of_match[] = {
+	{ .compatible = "wlf,rpi-cirrus", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, rpi_cirrus_of_match);
+
+static struct platform_driver rpi_cirrus_driver = {
+	.driver	= {
+		.name   = "snd-rpi-cirrus",
+		.of_match_table = of_match_ptr(rpi_cirrus_of_match),
+	},
+	.probe	= rpi_cirrus_probe,
+};
+
+module_platform_driver(rpi_cirrus_driver);
+
+MODULE_AUTHOR("Matthias Reichl <hias@horus.com>");
+MODULE_DESCRIPTION("ASoC driver for Cirrus Logic Audio Card");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/bcm/rpi-proto.c b/sound/soc/bcm/rpi-proto.c
new file mode 100644
index 000000000000..d3cdadfbeec8
--- /dev/null
+++ b/sound/soc/bcm/rpi-proto.c
@@ -0,0 +1,147 @@
+/*
+ * ASoC driver for PROTO AudioCODEC (with a WM8731)
+ * connected to a Raspberry Pi
+ *
+ * Author:      Florian Meier, <koalo@koalo.de>
+ *	      Copyright 2013
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "../codecs/wm8731.h"
+
+static const unsigned int wm8731_rates_12288000[] = {
+	8000, 32000, 48000, 96000,
+};
+
+static struct snd_pcm_hw_constraint_list wm8731_constraints_12288000 = {
+	.list = wm8731_rates_12288000,
+	.count = ARRAY_SIZE(wm8731_rates_12288000),
+};
+
+static int snd_rpi_proto_startup(struct snd_pcm_substream *substream)
+{
+	/* Setup constraints, because there is a 12.288 MHz XTAL on the board */
+	snd_pcm_hw_constraint_list(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_RATE,
+				&wm8731_constraints_12288000);
+	return 0;
+}
+
+static int snd_rpi_proto_hw_params(struct snd_pcm_substream *substream,
+				       struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	int sysclk = 12288000; /* This is fixed on this board */
+
+	/* Set proto bclk */
+	int ret = snd_soc_dai_set_bclk_ratio(cpu_dai,32*2);
+	if (ret < 0){
+		dev_err(rtd->card->dev,
+				"Failed to set BCLK ratio %d\n", ret);
+		return ret;
+	}
+
+	/* Set proto sysclk */
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL,
+			sysclk, SND_SOC_CLOCK_IN);
+	if (ret < 0) {
+		dev_err(rtd->card->dev,
+				"Failed to set WM8731 SYSCLK: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_rpi_proto_ops = {
+	.startup = snd_rpi_proto_startup,
+	.hw_params = snd_rpi_proto_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(rpi_proto,
+	DAILINK_COMP_ARRAY(COMP_CPU("bcm2708-i2s.0")),
+	DAILINK_COMP_ARRAY(COMP_CODEC("wm8731.1-001a", "wm8731-hifi")),
+	DAILINK_COMP_ARRAY(COMP_PLATFORM("bcm2708-i2s.0")));
+
+static struct snd_soc_dai_link snd_rpi_proto_dai[] = {
+{
+	.name		= "WM8731",
+	.stream_name	= "WM8731 HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S
+				| SND_SOC_DAIFMT_NB_NF
+				| SND_SOC_DAIFMT_CBM_CFM,
+	.ops		= &snd_rpi_proto_ops,
+	SND_SOC_DAILINK_REG(rpi_proto),
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_proto = {
+	.name		= "snd_rpi_proto",
+	.owner		= THIS_MODULE,
+	.dai_link	= snd_rpi_proto_dai,
+	.num_links	= ARRAY_SIZE(snd_rpi_proto_dai),
+};
+
+static int snd_rpi_proto_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+
+	snd_rpi_proto.dev = &pdev->dev;
+
+	if (pdev->dev.of_node) {
+		struct device_node *i2s_node;
+		struct snd_soc_dai_link *dai = &snd_rpi_proto_dai[0];
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+				            "i2s-controller", 0);
+
+		if (i2s_node) {
+			dai->cpus->dai_name = NULL;
+			dai->cpus->of_node = i2s_node;
+			dai->platforms->name = NULL;
+			dai->platforms->of_node = i2s_node;
+		}
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_proto);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev,
+				"snd_soc_register_card() failed: %d\n", ret);
+
+	return ret;
+}
+
+static const struct of_device_id snd_rpi_proto_of_match[] = {
+	{ .compatible = "rpi,rpi-proto", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, snd_rpi_proto_of_match);
+
+static struct platform_driver snd_rpi_proto_driver = {
+	.driver = {
+		.name   = "snd-rpi-proto",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_rpi_proto_of_match,
+	},
+	.probe	  = snd_rpi_proto_probe,
+};
+
+module_platform_driver(snd_rpi_proto_driver);
+
+MODULE_AUTHOR("Florian Meier");
+MODULE_DESCRIPTION("ASoC Driver for Raspberry Pi connected to PROTO board (WM8731)");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/bcm/rpi-simple-soundcard.c b/sound/soc/bcm/rpi-simple-soundcard.c
new file mode 100644
index 000000000000..b7dc818716fa
--- /dev/null
+++ b/sound/soc/bcm/rpi-simple-soundcard.c
@@ -0,0 +1,590 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * rpi-simple-soundcard.c -- ALSA SoC Raspberry Pi soundcard.
+ *
+ * Copyright (C) 2018 Raspberry Pi.
+ *
+ * Authors: Tim Gover <tim.gover@raspberrypi.org>
+ *
+ * Based on code:
+ * hifiberry_amp.c, hifiberry_dac.c, rpi-dac.c
+ * by Florian Meier <florian.meier@koalo.de>
+ *
+ * googlevoicehat-soundcard.c
+ * by Peter Malkin <petermalkin@google.com>
+ *
+ * adau1977-adc.c
+ * by Andrey Grodzovsky <andrey2805@gmail.com>
+ *
+ * merus-amp.c
+ * by Ariel Muszkat <ariel.muszkat@gmail.com>
+ *		Jorgen Kragh Jakobsen <jorgen.kraghjakobsen@infineon.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/gpio/consumer.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+/* Parameters for generic RPI functions */
+struct snd_rpi_simple_drvdata {
+	struct snd_soc_dai_link *dai;
+	const char* card_name;
+	unsigned int fixed_bclk_ratio;
+};
+
+static struct snd_soc_card snd_rpi_simple = {
+	.driver_name  = "RPi-simple",
+	.owner        = THIS_MODULE,
+	.dai_link     = NULL,
+	.num_links    = 1, /* Only a single DAI supported at the moment */
+};
+
+static int snd_rpi_simple_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_rpi_simple_drvdata *drvdata =
+		snd_soc_card_get_drvdata(rtd->card);
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+
+	if (drvdata->fixed_bclk_ratio > 0)
+		return snd_soc_dai_set_bclk_ratio(cpu_dai,
+				drvdata->fixed_bclk_ratio);
+
+	return 0;
+}
+
+static int pifi_mini_210_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *dac;
+	struct gpio_desc *pdn_gpio, *rst_gpio;
+	struct snd_soc_dai *codec_dai;
+	int ret;
+
+	snd_rpi_simple_init(rtd);
+	codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+	dac = codec_dai[0].component;
+
+	pdn_gpio = devm_gpiod_get_optional(snd_rpi_simple.dev, "pdn",
+						GPIOD_OUT_LOW);
+	if (IS_ERR(pdn_gpio)) {
+		ret = PTR_ERR(pdn_gpio);
+		dev_err(snd_rpi_simple.dev, "failed to get pdn gpio: %d\n", ret);
+		return ret;
+	}
+
+	rst_gpio = devm_gpiod_get_optional(snd_rpi_simple.dev, "rst",
+						GPIOD_OUT_LOW);
+	if (IS_ERR(rst_gpio)) {
+		ret = PTR_ERR(rst_gpio);
+		dev_err(snd_rpi_simple.dev, "failed to get rst gpio: %d\n", ret);
+		return ret;
+	}
+
+	// Set up cards - pulse power down and reset first, then
+	// set up according to datasheet
+	gpiod_set_value_cansleep(pdn_gpio, 1);
+	gpiod_set_value_cansleep(rst_gpio, 1);
+	usleep_range(1000, 10000);
+	gpiod_set_value_cansleep(pdn_gpio, 0);
+	usleep_range(20000, 30000);
+	gpiod_set_value_cansleep(rst_gpio, 0);
+	usleep_range(20000, 30000);
+
+	// Oscillator trim
+	snd_soc_component_write(dac, 0x1b, 0);
+	usleep_range(60000, 80000);
+
+	// MCLK at 64fs, sample rate 44.1 or 48kHz
+	snd_soc_component_write(dac, 0x00, 0x60);
+
+	// Set up for BTL - AD/BD mode - AD is 0x00107772, BD is 0x00987772
+	snd_soc_component_write(dac, 0x20, 0x00107772);
+
+	// End mute
+	snd_soc_component_write(dac, 0x05, 0x00);
+
+	return 0;
+}
+
+static int snd_rpi_simple_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	struct snd_rpi_simple_drvdata *drvdata;
+	unsigned int sample_bits;
+
+	drvdata = snd_soc_card_get_drvdata(rtd->card);
+
+	if (drvdata->fixed_bclk_ratio > 0)
+		return 0; // BCLK is configured in .init
+
+	/* The simple drivers just set the bclk_ratio to sample_bits * 2 so
+	 * hard-code this for now, but sticking to powers of 2 to allow for
+	 * integer clock divisors. More complex drivers could just replace
+	 * the hw_params routine.
+	 */
+	sample_bits = snd_pcm_format_width(params_format(params));
+	sample_bits = sample_bits <= 16 ? 16 : 32;
+
+	return snd_soc_dai_set_bclk_ratio(cpu_dai, sample_bits * 2);
+}
+
+static struct snd_soc_ops snd_rpi_simple_ops = {
+	.hw_params = snd_rpi_simple_hw_params,
+};
+
+static int snd_merus_amp_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+	int rate;
+
+	rate = params_rate(params);
+	if (rate > 48000) {
+		dev_err(rtd->card->dev,
+		"Unsupported samplerate %d\n",
+		rate);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static struct snd_soc_ops snd_merus_amp_ops = {
+	.hw_params = snd_merus_amp_hw_params,
+};
+
+enum adau1977_clk_id {
+	ADAU1977_SYSCLK,
+};
+
+enum adau1977_sysclk_src {
+	ADAU1977_SYSCLK_SRC_MCLK,
+	ADAU1977_SYSCLK_SRC_LRCLK,
+};
+
+static int adau1977_init(struct snd_soc_pcm_runtime *rtd)
+{
+	int ret;
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+	ret = snd_soc_dai_set_tdm_slot(codec_dai, 0, 0, 0, 0);
+	if (ret < 0)
+		return ret;
+
+	return snd_soc_component_set_sysclk(codec_dai->component,
+			ADAU1977_SYSCLK, ADAU1977_SYSCLK_SRC_MCLK,
+			11289600, SND_SOC_CLOCK_IN);
+}
+
+SND_SOC_DAILINK_DEFS(adau1977,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("adau1977.1-0011", "adau1977-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_rpi_adau1977_dai[] = {
+	{
+	.name           = "adau1977",
+	.stream_name    = "ADAU1977",
+	.init           = adau1977_init,
+	.dai_fmt = SND_SOC_DAIFMT_I2S |
+		SND_SOC_DAIFMT_NB_NF |
+		SND_SOC_DAIFMT_CBM_CFM,
+	SND_SOC_DAILINK_REG(adau1977),
+	},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_adau1977 = {
+	.card_name = "snd_rpi_adau1977_adc",
+	.dai       = snd_rpi_adau1977_dai,
+};
+
+SND_SOC_DAILINK_DEFS(gvchat,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("voicehat-codec", "voicehat-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_googlevoicehat_soundcard_dai[] = {
+{
+	.name           = "Google voiceHAT SoundCard",
+	.stream_name    = "Google voiceHAT SoundCard HiFi",
+	.dai_fmt        =  SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBS_CFS,
+	SND_SOC_DAILINK_REG(gvchat),
+},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_googlevoicehat = {
+	.card_name = "snd_rpi_googlevoicehat_soundcard",
+	.dai       = snd_googlevoicehat_soundcard_dai,
+};
+
+SND_SOC_DAILINK_DEFS(hifiberry_dacplusdsp,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("dacplusdsp-codec", "dacplusdsp-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_hifiberrydacplusdsp_soundcard_dai[] = {
+{
+	.name           = "Hifiberry DAC+DSP SoundCard",
+	.stream_name    = "Hifiberry DAC+DSP SoundCard HiFi",
+	.dai_fmt        =  SND_SOC_DAIFMT_I2S |
+			   SND_SOC_DAIFMT_NB_NF |
+			   SND_SOC_DAIFMT_CBS_CFS,
+	SND_SOC_DAILINK_REG(hifiberry_dacplusdsp),
+},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_hifiberrydacplusdsp = {
+	.card_name = "snd_rpi_hifiberrydacplusdsp_soundcard",
+	.dai       = snd_hifiberrydacplusdsp_soundcard_dai,
+};
+
+SND_SOC_DAILINK_DEFS(hifiberry_adc,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("snd-soc-dummy", "snd-soc-dummy-dai")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static int hifiberry_adc8x_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+
+	/* set limits of 8 channels and 192ksps sample rate
+	 */
+	codec_dai->driver->capture.channels_max = 8;
+	codec_dai->driver->capture.rates = SNDRV_PCM_RATE_8000_192000;
+
+	return 0;
+}
+
+static struct snd_soc_dai_link snd_hifiberry_adc8x_dai[] = {
+	{
+		.name           = "HifiBerry ADC8x",
+		.stream_name    = "HifiBerry ADC8x HiFi",
+		.dai_fmt        = SND_SOC_DAIFMT_I2S |
+					SND_SOC_DAIFMT_NB_NF |
+					SND_SOC_DAIFMT_CBS_CFS,
+		.init           = hifiberry_adc8x_init,
+		SND_SOC_DAILINK_REG(hifiberry_adc),
+	},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_hifiberry_adc8x = {
+	.card_name = "snd_rpi_hifiberry_adc8x",
+	.dai       = snd_hifiberry_adc8x_dai,
+	.fixed_bclk_ratio = 64,
+};
+
+SND_SOC_DAILINK_DEFS(hifiberry_amp,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("tas5713.1-001b", "tas5713-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_hifiberry_amp_dai[] = {
+	{
+		.name           = "HifiBerry AMP",
+		.stream_name    = "HifiBerry AMP HiFi",
+		.dai_fmt        = SND_SOC_DAIFMT_I2S |
+					SND_SOC_DAIFMT_NB_NF |
+					SND_SOC_DAIFMT_CBS_CFS,
+		SND_SOC_DAILINK_REG(hifiberry_amp),
+	},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_hifiberry_amp = {
+	.card_name        = "snd_rpi_hifiberry_amp",
+	.dai              = snd_hifiberry_amp_dai,
+	.fixed_bclk_ratio = 64,
+};
+
+SND_SOC_DAILINK_DEFS(hifiberry_amp3,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("ma120x0p.1-0020", "ma120x0p-amp")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_hifiberry_amp3_dai[] = {
+	{
+		.name		= "HifiberryAmp3",
+		.stream_name	= "Hifiberry Amp3",
+		.dai_fmt	= SND_SOC_DAIFMT_I2S |
+					SND_SOC_DAIFMT_NB_NF |
+					SND_SOC_DAIFMT_CBS_CFS,
+		SND_SOC_DAILINK_REG(hifiberry_amp3),
+	},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_hifiberry_amp3 = {
+	.card_name	 = "snd_rpi_hifiberry_amp3",
+	.dai		 = snd_hifiberry_amp3_dai,
+	.fixed_bclk_ratio = 64,
+};
+
+SND_SOC_DAILINK_DEFS(hifiberry_dac,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm5102a-codec", "pcm5102a-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_hifiberry_dac_dai[] = {
+	{
+		.name           = "HifiBerry DAC",
+		.stream_name    = "HifiBerry DAC HiFi",
+		.dai_fmt        = SND_SOC_DAIFMT_I2S |
+					SND_SOC_DAIFMT_NB_NF |
+					SND_SOC_DAIFMT_CBS_CFS,
+		SND_SOC_DAILINK_REG(hifiberry_dac),
+	},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_hifiberry_dac = {
+	.card_name = "snd_rpi_hifiberry_dac",
+	.dai       = snd_hifiberry_dac_dai,
+};
+
+SND_SOC_DAILINK_DEFS(hifiberry_dac8x,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("snd-soc-dummy", "snd-soc-dummy-dai")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static int hifiberry_dac8x_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_card *card = rtd->card;
+	struct gpio_desc *gpio_desc;
+	bool has_adc;
+
+	/* Configure the codec for 8 channel playback */
+	codec_dai->driver->playback.channels_max = 8;
+	codec_dai->driver->playback.rates = SNDRV_PCM_RATE_8000_192000;
+
+	/* Activate capture based on ADC8x detection */
+	gpio_desc = devm_gpiod_get(card->dev, "hasadc", GPIOD_IN);
+	if (IS_ERR(gpio_desc)) {
+		dev_err(card->dev, "Failed to get GPIO: %ld\n", PTR_ERR(gpio_desc));
+		return PTR_ERR(gpio_desc);
+	}
+
+	has_adc = gpiod_get_value(gpio_desc);
+
+	if (has_adc) {
+		struct snd_soc_dai_link *dai = rtd->dai_link;
+
+		dev_info(card->dev, "ADC8x detected: capture enabled\n");
+		codec_dai->driver->symmetric_rate = 1;
+		codec_dai->driver->symmetric_channels = 1;
+		codec_dai->driver->symmetric_sample_bits = 1;
+		codec_dai->driver->capture.rates = SNDRV_PCM_RATE_8000_192000;
+		dai->name = "HiFiBerry DAC8xADC8x";
+		dai->stream_name = "HiFiBerry DAC8xADC8x HiFi";
+	} else {
+		dev_info(card->dev, "no ADC8x detected\n");
+		rtd->dai_link->playback_only = 1;  // Disable capture
+	}
+
+	return 0;
+}
+
+static struct snd_soc_dai_link snd_hifiberry_dac8x_dai[] = {
+	{
+		.name           = "HifiBerry DAC8x",
+		.stream_name    = "HifiBerry DAC8x HiFi",
+		.dai_fmt        = SND_SOC_DAIFMT_I2S |
+					SND_SOC_DAIFMT_NB_NF |
+					SND_SOC_DAIFMT_CBS_CFS,
+		.init           = hifiberry_dac8x_init,
+		SND_SOC_DAILINK_REG(hifiberry_dac8x),
+	},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_hifiberry_dac8x = {
+	.card_name = "snd_rpi_hifiberry_dac8x",
+	.dai       = snd_hifiberry_dac8x_dai,
+	.fixed_bclk_ratio = 64,
+};
+
+SND_SOC_DAILINK_DEFS(dionaudio_kiwi,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm1794a-codec", "pcm1794a-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_dionaudio_kiwi_dai[] = {
+{
+	.name		= "DionAudio KIWI",
+	.stream_name	= "DionAudio KIWI STREAMER",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBS_CFS,
+	SND_SOC_DAILINK_REG(dionaudio_kiwi),
+},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_dionaudio_kiwi = {
+	.card_name        = "snd_rpi_dionaudio_kiwi",
+	.dai              = snd_dionaudio_kiwi_dai,
+	.fixed_bclk_ratio = 64,
+};
+
+SND_SOC_DAILINK_DEFS(rpi_dac,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("pcm1794a-codec", "pcm1794a-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_rpi_dac_dai[] = {
+{
+	.name		= "RPi-DAC",
+	.stream_name	= "RPi-DAC HiFi",
+	.dai_fmt	= SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBS_CFS,
+	SND_SOC_DAILINK_REG(rpi_dac),
+},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_rpi_dac = {
+	.card_name        = "snd_rpi_rpi_dac",
+	.dai              = snd_rpi_dac_dai,
+	.fixed_bclk_ratio = 64,
+};
+
+SND_SOC_DAILINK_DEFS(merus_amp,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("ma120x0p.1-0020", "ma120x0p-amp")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_merus_amp_dai[] = {
+	{
+		.name           = "MerusAmp",
+		.stream_name    = "Merus Audio Amp",
+		.ops		= &snd_merus_amp_ops,
+		.dai_fmt        = SND_SOC_DAIFMT_I2S |
+					SND_SOC_DAIFMT_NB_NF |
+					SND_SOC_DAIFMT_CBS_CFS,
+		SND_SOC_DAILINK_REG(merus_amp),
+	},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_merus_amp = {
+	.card_name        = "snd_rpi_merus_amp",
+	.dai              = snd_merus_amp_dai,
+	.fixed_bclk_ratio = 64,
+};
+
+SND_SOC_DAILINK_DEFS(pifi_mini_210,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_CODEC("tas571x.1-001a", "tas571x-hifi")),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_pifi_mini_210_dai[] = {
+	{
+		.name           = "PiFi Mini 210",
+		.stream_name    = "PiFi Mini 210 HiFi",
+		.init			= pifi_mini_210_init,
+		.dai_fmt        = SND_SOC_DAIFMT_I2S |
+					SND_SOC_DAIFMT_NB_NF |
+					SND_SOC_DAIFMT_CBS_CFS,
+		SND_SOC_DAILINK_REG(pifi_mini_210),
+	},
+};
+
+static struct snd_rpi_simple_drvdata drvdata_pifi_mini_210 = {
+	.card_name        = "snd_pifi_mini_210",
+	.dai              = snd_pifi_mini_210_dai,
+	.fixed_bclk_ratio = 64,
+};
+
+static const struct of_device_id snd_rpi_simple_of_match[] = {
+	{ .compatible = "adi,adau1977-adc",
+		.data = (void *) &drvdata_adau1977 },
+	{ .compatible = "googlevoicehat,googlevoicehat-soundcard",
+		.data = (void *) &drvdata_googlevoicehat },
+	{ .compatible = "hifiberrydacplusdsp,hifiberrydacplusdsp-soundcard",
+		.data = (void *) &drvdata_hifiberrydacplusdsp },
+	{ .compatible = "hifiberry,hifiberry-adc8x",
+		.data = (void *) &drvdata_hifiberry_adc8x },
+	{ .compatible = "hifiberry,hifiberry-amp",
+		.data = (void *) &drvdata_hifiberry_amp },
+	{ .compatible = "hifiberry,hifiberry-amp3",
+		.data = (void *) &drvdata_hifiberry_amp3 },
+	{ .compatible = "hifiberry,hifiberry-dac",
+		.data = (void *) &drvdata_hifiberry_dac },
+	{ .compatible = "hifiberry,hifiberry-dac8x",
+		.data = (void *) &drvdata_hifiberry_dac8x },
+	{ .compatible = "dionaudio,dionaudio-kiwi",
+		.data = (void *) &drvdata_dionaudio_kiwi },
+	{ .compatible = "rpi,rpi-dac", &drvdata_rpi_dac},
+	{ .compatible = "merus,merus-amp",
+		.data = (void *) &drvdata_merus_amp },
+	{ .compatible = "pifi,pifi-mini-210",
+		.data = (void *) &drvdata_pifi_mini_210 },
+	{},
+};
+
+static int snd_rpi_simple_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	const struct of_device_id *of_id;
+
+	snd_rpi_simple.dev = &pdev->dev;
+	of_id = of_match_node(snd_rpi_simple_of_match, pdev->dev.of_node);
+
+	if (pdev->dev.of_node && of_id->data) {
+		struct device_node *i2s_node;
+		struct snd_rpi_simple_drvdata *drvdata =
+			(struct snd_rpi_simple_drvdata *) of_id->data;
+		struct snd_soc_dai_link *dai = drvdata->dai;
+
+		snd_soc_card_set_drvdata(&snd_rpi_simple, drvdata);
+
+		/* More complex drivers might override individual functions */
+		if (!dai->init)
+			dai->init = snd_rpi_simple_init;
+		if (!dai->ops)
+			dai->ops = &snd_rpi_simple_ops;
+
+		snd_rpi_simple.name = drvdata->card_name;
+
+		snd_rpi_simple.dai_link = dai;
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+				"i2s-controller", 0);
+		if (!i2s_node) {
+			pr_err("Failed to find i2s-controller DT node\n");
+			return -ENODEV;
+		}
+
+		dai->cpus->of_node = i2s_node;
+		dai->platforms->of_node = i2s_node;
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_simple);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev, "Failed to register card %d\n", ret);
+
+	return ret;
+}
+
+static struct platform_driver snd_rpi_simple_driver = {
+	.driver = {
+		.name   = "snd-rpi-simple",
+		.owner  = THIS_MODULE,
+		.of_match_table = snd_rpi_simple_of_match,
+	},
+	.probe          = snd_rpi_simple_probe,
+};
+MODULE_DEVICE_TABLE(of, snd_rpi_simple_of_match);
+
+module_platform_driver(snd_rpi_simple_driver);
+
+MODULE_AUTHOR("Tim Gover <tim.gover@raspberrypi.org>");
+MODULE_DESCRIPTION("ASoC Raspberry Pi simple soundcard driver ");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/bcm/rpi-wm8804-soundcard.c b/sound/soc/bcm/rpi-wm8804-soundcard.c
new file mode 100644
index 000000000000..3ecbf7adf7af
--- /dev/null
+++ b/sound/soc/bcm/rpi-wm8804-soundcard.c
@@ -0,0 +1,549 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * rpi--wm8804.c -- ALSA SoC Raspberry Pi soundcard.
+ *
+ * Copyright (C) 2018 Raspberry Pi.
+ *
+ * Authors: Tim Gover <tim.gover@raspberrypi.org>
+ *
+ * Generic driver for Pi Hat WM8804 digi sounds cards
+ *
+ * Based upon code from:
+ * justboom-digi.c
+ * by Milan Neskovic <info@justboom.co>
+ *
+ * iqaudio_digi.c
+ * by Daniel Matuschek <info@crazy-audio.com>
+ *
+ * allo-digione.c
+ * by Baswaraj <jaikumar@cem-solutions.net>
+ *
+ * hifiberry-digi.c
+ * Daniel Matuschek <info@crazy-audio.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "../codecs/wm8804.h"
+
+struct wm8804_clk_cfg {
+	unsigned int sysclk_freq;
+	unsigned int mclk_freq;
+	unsigned int mclk_div;
+};
+
+/* Parameters for generic functions */
+struct snd_rpi_wm8804_drvdata {
+	/* Required - pointer to the DAI structure */
+	struct snd_soc_dai_link *dai;
+	/* Required - snd_soc_card name */
+	const char *card_name;
+	/* Optional DT node names if card info is defined in DT */
+	const char *card_name_dt;
+	const char *dai_name_dt;
+	const char *dai_stream_name_dt;
+	/* Optional probe extension - called prior to register_card */
+	int (*probe)(struct platform_device *pdev);
+};
+
+static struct gpio_desc *snd_clk44gpio;
+static struct gpio_desc *snd_clk48gpio;
+static int wm8804_samplerate = 0;
+static struct gpio_desc *led_gpio_1;
+static struct gpio_desc *led_gpio_2;
+static struct gpio_desc *led_gpio_3;
+static struct gpio_desc *custom_reset;
+
+/* Forward declarations */
+static struct snd_soc_dai_link snd_allo_digione_dai[];
+static struct snd_soc_card snd_rpi_wm8804;
+
+
+#define CLK_44EN_RATE 22579200UL
+#define CLK_48EN_RATE 24576000UL
+
+static const char * const wm8805_input_select_text[] = {
+	"Rx 0",
+	"Rx 1",
+	"Rx 2",
+	"Rx 3",
+	"Rx 4",
+	"Rx 5",
+	"Rx 6",
+	"Rx 7"
+};
+
+static const unsigned int wm8805_input_channel_select_value[] = {
+	0, 1, 2, 3, 4, 5, 6, 7
+};
+
+static const struct soc_enum wm8805_input_channel_sel[] = {
+	SOC_VALUE_ENUM_SINGLE(WM8804_PLL6, 0, 7, ARRAY_SIZE(wm8805_input_select_text),
+	wm8805_input_select_text, wm8805_input_channel_select_value),
+};
+
+static const struct snd_kcontrol_new wm8805_input_controls_card[] = {
+	SOC_ENUM("Select Input Channel", wm8805_input_channel_sel[0]),
+};
+
+static int wm8805_add_input_controls(struct snd_soc_component *component)
+{
+	snd_soc_add_component_controls(component, wm8805_input_controls_card,
+	ARRAY_SIZE(wm8805_input_controls_card));
+	return 0;
+}
+
+static unsigned int snd_rpi_wm8804_enable_clock(unsigned int samplerate)
+{
+	switch (samplerate) {
+	case 11025:
+	case 22050:
+	case 44100:
+	case 88200:
+	case 176400:
+		gpiod_set_value_cansleep(snd_clk44gpio, 1);
+		gpiod_set_value_cansleep(snd_clk48gpio, 0);
+		return CLK_44EN_RATE;
+	default:
+		gpiod_set_value_cansleep(snd_clk48gpio, 1);
+		gpiod_set_value_cansleep(snd_clk44gpio, 0);
+		return CLK_48EN_RATE;
+	}
+}
+
+static void snd_rpi_wm8804_clk_cfg(unsigned int samplerate,
+		struct wm8804_clk_cfg *clk_cfg)
+{
+	clk_cfg->sysclk_freq = 27000000;
+
+	if (samplerate <= 96000 ||
+	    snd_rpi_wm8804.dai_link == snd_allo_digione_dai) {
+		clk_cfg->mclk_freq = samplerate * 256;
+		clk_cfg->mclk_div = WM8804_MCLKDIV_256FS;
+	} else {
+		clk_cfg->mclk_freq = samplerate * 128;
+		clk_cfg->mclk_div = WM8804_MCLKDIV_128FS;
+	}
+
+	if (!(IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio)))
+		clk_cfg->sysclk_freq = snd_rpi_wm8804_enable_clock(samplerate);
+}
+
+static int snd_rpi_wm8804_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
+	struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component;
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	int sampling_freq = 1;
+	int ret;
+	struct wm8804_clk_cfg clk_cfg;
+	int samplerate = params_rate(params);
+
+	if (samplerate == wm8804_samplerate)
+		return 0;
+
+	/* clear until all clocks are setup properly */
+	wm8804_samplerate = 0;
+
+	snd_rpi_wm8804_clk_cfg(samplerate, &clk_cfg);
+
+	pr_debug("%s samplerate: %d mclk_freq: %u mclk_div: %u sysclk: %u\n",
+			__func__, samplerate, clk_cfg.mclk_freq,
+			clk_cfg.mclk_div, clk_cfg.sysclk_freq);
+
+	switch (samplerate) {
+	case 32000:
+		sampling_freq = 0x03;
+		break;
+	case 44100:
+		sampling_freq = 0x00;
+		break;
+	case 48000:
+		sampling_freq = 0x02;
+		break;
+	case 88200:
+		sampling_freq = 0x08;
+		break;
+	case 96000:
+		sampling_freq = 0x0a;
+		break;
+	case 176400:
+		sampling_freq = 0x0c;
+		break;
+	case 192000:
+		sampling_freq = 0x0e;
+		break;
+	default:
+		dev_err(rtd->card->dev,
+		"Failed to set WM8804 SYSCLK, unsupported samplerate %d\n",
+		samplerate);
+	}
+
+	snd_soc_dai_set_clkdiv(codec_dai, WM8804_MCLK_DIV, clk_cfg.mclk_div);
+	snd_soc_dai_set_pll(codec_dai, 0, 0,
+			clk_cfg.sysclk_freq, clk_cfg.mclk_freq);
+
+	ret = snd_soc_dai_set_sysclk(codec_dai, WM8804_TX_CLKSRC_PLL,
+			clk_cfg.sysclk_freq, SND_SOC_CLOCK_OUT);
+	if (ret < 0) {
+		dev_err(rtd->card->dev,
+		"Failed to set WM8804 SYSCLK: %d\n", ret);
+		return ret;
+	}
+
+	wm8804_samplerate = samplerate;
+
+	/* set sampling frequency status bits */
+	snd_soc_component_update_bits(component, WM8804_SPDTX4, 0x0f,
+			sampling_freq);
+
+	return snd_soc_dai_set_bclk_ratio(cpu_dai, 64);
+}
+
+static struct snd_soc_ops snd_rpi_wm8804_ops = {
+	.hw_params = snd_rpi_wm8804_hw_params,
+};
+
+static int snd_interlude_audio_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params)
+{
+	int ret = snd_rpi_wm8804_hw_params(substream, params);
+	int samplerate = params_rate(params);
+
+	switch (samplerate) {
+	case 44100:
+		gpiod_set_value_cansleep(led_gpio_1, 1);
+		gpiod_set_value_cansleep(led_gpio_2, 0);
+		gpiod_set_value_cansleep(led_gpio_3, 0);
+		break;
+	case 48000:
+		gpiod_set_value_cansleep(led_gpio_1, 1);
+		gpiod_set_value_cansleep(led_gpio_2, 0);
+		gpiod_set_value_cansleep(led_gpio_3, 0);
+		break;
+	case 88200:
+		gpiod_set_value_cansleep(led_gpio_1, 0);
+		gpiod_set_value_cansleep(led_gpio_2, 1);
+		gpiod_set_value_cansleep(led_gpio_3, 0);
+		break;
+	case 96000:
+		gpiod_set_value_cansleep(led_gpio_1, 0);
+		gpiod_set_value_cansleep(led_gpio_2, 1);
+		gpiod_set_value_cansleep(led_gpio_3, 0);
+		break;
+	case 176400:
+		gpiod_set_value_cansleep(led_gpio_1, 0);
+		gpiod_set_value_cansleep(led_gpio_2, 0);
+		gpiod_set_value_cansleep(led_gpio_3, 1);
+		break;
+	case 192000:
+		gpiod_set_value_cansleep(led_gpio_1, 0);
+		gpiod_set_value_cansleep(led_gpio_2, 0);
+		gpiod_set_value_cansleep(led_gpio_3, 1);
+		break;
+	default:
+		break;
+	}
+	return ret;
+}
+
+const struct snd_soc_ops interlude_audio_digital_dai_ops = {
+	.hw_params = snd_interlude_audio_hw_params,
+};
+
+SND_SOC_DAILINK_DEFS(justboom_digi,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_justboom_digi_dai[] = {
+{
+	.name        = "JustBoom Digi",
+	.stream_name = "JustBoom Digi HiFi",
+	SND_SOC_DAILINK_REG(justboom_digi),
+},
+};
+
+static struct snd_rpi_wm8804_drvdata drvdata_justboom_digi = {
+	.card_name            = "snd_rpi_justboom_digi",
+	.dai                  = snd_justboom_digi_dai,
+};
+
+SND_SOC_DAILINK_DEFS(iqaudio_digi,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_iqaudio_digi_dai[] = {
+{
+	.name        = "IQAudIO Digi",
+	.stream_name = "IQAudIO Digi HiFi",
+	SND_SOC_DAILINK_REG(iqaudio_digi),
+},
+};
+
+static struct snd_rpi_wm8804_drvdata drvdata_iqaudio_digi = {
+	.card_name          = "IQAudIODigi",
+	.dai                = snd_iqaudio_digi_dai,
+	.card_name_dt       = "wm8804-digi,card-name",
+	.dai_name_dt        = "wm8804-digi,dai-name",
+	.dai_stream_name_dt = "wm8804-digi,dai-stream-name",
+};
+
+static int snd_allo_digione_probe(struct platform_device *pdev)
+{
+	pr_debug("%s\n", __func__);
+
+	if (IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio)) {
+		dev_err(&pdev->dev, "devm_gpiod_get() failed\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+SND_SOC_DAILINK_DEFS(allo_digione,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_allo_digione_dai[] = {
+{
+	.name        = "Allo DigiOne",
+	.stream_name = "Allo DigiOne HiFi",
+	SND_SOC_DAILINK_REG(allo_digione),
+},
+};
+
+static struct snd_rpi_wm8804_drvdata drvdata_allo_digione = {
+	.card_name = "snd_allo_digione",
+	.dai       = snd_allo_digione_dai,
+	.probe     = snd_allo_digione_probe,
+};
+
+SND_SOC_DAILINK_DEFS(hifiberry_digi,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link snd_hifiberry_digi_dai[] = {
+{
+	.name        = "HifiBerry Digi",
+	.stream_name = "HifiBerry Digi HiFi",
+	SND_SOC_DAILINK_REG(hifiberry_digi),
+},
+};
+
+static int snd_hifiberry_digi_probe(struct platform_device *pdev)
+{
+	pr_debug("%s\n", __func__);
+
+	if (IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio))
+		return 0;
+
+	snd_hifiberry_digi_dai->name = "HiFiBerry Digi+ Pro";
+	snd_hifiberry_digi_dai->stream_name = "HiFiBerry Digi+ Pro HiFi";
+	return 0;
+}
+
+static struct snd_rpi_wm8804_drvdata drvdata_hifiberry_digi = {
+	.card_name = "snd_rpi_hifiberry_digi",
+	.dai       = snd_hifiberry_digi_dai,
+	.probe     = snd_hifiberry_digi_probe,
+};
+
+SND_SOC_DAILINK_DEFS(interlude_audio_digital,
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()),
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static int snd_interlude_audio_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_component *component =  snd_soc_rtd_to_codec(rtd, 0)->component;
+	int ret;
+
+	ret = wm8805_add_input_controls(component);
+	if (ret != 0)
+		pr_err("failed to add input controls");
+
+	return 0;
+}
+
+
+static struct snd_soc_dai_link snd_interlude_audio_digital_dai[] = {
+{
+	.name        = "Interlude Audio Digital",
+	.stream_name = "Interlude Audio Digital HiFi",
+	.init        = snd_interlude_audio_init,
+	.ops		 = &interlude_audio_digital_dai_ops,
+	SND_SOC_DAILINK_REG(interlude_audio_digital),
+},
+};
+
+
+static int snd_interlude_audio_digital_probe(struct platform_device *pdev)
+{
+	if (IS_ERR(snd_clk44gpio) || IS_ERR(snd_clk48gpio))
+		return 0;
+
+	custom_reset = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW);
+	gpiod_set_value_cansleep(custom_reset, 0);
+	mdelay(10);
+	gpiod_set_value_cansleep(custom_reset, 1);
+
+	snd_interlude_audio_digital_dai->name = "Interlude Audio Digital";
+	snd_interlude_audio_digital_dai->stream_name = "Interlude Audio Digital HiFi";
+	led_gpio_1 = devm_gpiod_get(&pdev->dev, "led1", GPIOD_OUT_LOW);
+	led_gpio_2 = devm_gpiod_get(&pdev->dev, "led2", GPIOD_OUT_LOW);
+	led_gpio_3 = devm_gpiod_get(&pdev->dev, "led3", GPIOD_OUT_LOW);
+	return 0;
+}
+
+
+static struct snd_rpi_wm8804_drvdata drvdata_interlude_audio_digital = {
+	.card_name = "snd_IA_Digital_Hat",
+	.dai       = snd_interlude_audio_digital_dai,
+	.probe     = snd_interlude_audio_digital_probe,
+};
+
+static const struct of_device_id snd_rpi_wm8804_of_match[] = {
+	{ .compatible = "justboom,justboom-digi",
+		.data = (void *) &drvdata_justboom_digi },
+	{ .compatible = "iqaudio,wm8804-digi",
+		.data = (void *) &drvdata_iqaudio_digi },
+	{ .compatible = "allo,allo-digione",
+		.data = (void *) &drvdata_allo_digione },
+	{ .compatible = "hifiberry,hifiberry-digi",
+		.data = (void *) &drvdata_hifiberry_digi },
+	{ .compatible = "interludeaudio,interludeaudio-digital",
+		.data = (void *) &drvdata_interlude_audio_digital },
+	{},
+};
+
+static struct snd_soc_card snd_rpi_wm8804 = {
+	.driver_name  = "RPi-WM8804",
+	.owner        = THIS_MODULE,
+	.dai_link     = NULL,
+	.num_links    = 1,
+};
+
+static int snd_rpi_wm8804_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+	const struct of_device_id *of_id;
+
+	snd_rpi_wm8804.dev = &pdev->dev;
+	of_id = of_match_node(snd_rpi_wm8804_of_match, pdev->dev.of_node);
+
+	if (pdev->dev.of_node && of_id->data) {
+		struct device_node *i2s_node;
+		struct snd_rpi_wm8804_drvdata *drvdata =
+			(struct snd_rpi_wm8804_drvdata *) of_id->data;
+		struct snd_soc_dai_link *dai = drvdata->dai;
+
+		snd_soc_card_set_drvdata(&snd_rpi_wm8804, drvdata);
+
+		if (!dai->ops)
+			dai->ops = &snd_rpi_wm8804_ops;
+		if (!dai->codecs->dai_name)
+			dai->codecs->dai_name = "wm8804-spdif";
+		if (!dai->codecs->name)
+			dai->codecs->name = "wm8804.1-003b";
+		if (!dai->dai_fmt)
+			dai->dai_fmt = SND_SOC_DAIFMT_I2S |
+				SND_SOC_DAIFMT_NB_NF |
+				SND_SOC_DAIFMT_CBM_CFM;
+
+		snd_rpi_wm8804.dai_link = dai;
+		i2s_node = of_parse_phandle(pdev->dev.of_node,
+				"i2s-controller", 0);
+		if (!i2s_node) {
+			pr_err("Failed to find i2s-controller DT node\n");
+			return -ENODEV;
+		}
+
+		snd_rpi_wm8804.name = drvdata->card_name;
+
+		/* If requested by in drvdata get card & DAI names from DT */
+		if (drvdata->card_name_dt)
+			of_property_read_string(i2s_node,
+					drvdata->card_name_dt,
+					&snd_rpi_wm8804.name);
+
+		if (drvdata->dai_name_dt)
+			of_property_read_string(i2s_node,
+					drvdata->dai_name_dt,
+					&dai->name);
+
+		if (drvdata->dai_stream_name_dt)
+			of_property_read_string(i2s_node,
+					drvdata->dai_stream_name_dt,
+					&dai->stream_name);
+
+		dai->cpus->of_node = i2s_node;
+		dai->platforms->of_node = i2s_node;
+
+		/*
+		 * clk44gpio and clk48gpio are not required by all cards so
+		 * don't check the error status.
+		 */
+		snd_clk44gpio =
+			devm_gpiod_get(&pdev->dev, "clock44", GPIOD_OUT_LOW);
+
+		snd_clk48gpio =
+			devm_gpiod_get(&pdev->dev, "clock48", GPIOD_OUT_LOW);
+
+		if (drvdata->probe) {
+			ret = drvdata->probe(pdev);
+			if (ret < 0) {
+				dev_err(&pdev->dev, "Custom probe failed %d\n",
+						ret);
+				return ret;
+			}
+		}
+
+		pr_debug("%s card: %s dai: %s stream: %s\n", __func__,
+				snd_rpi_wm8804.name,
+				dai->name, dai->stream_name);
+	}
+
+	ret = devm_snd_soc_register_card(&pdev->dev, &snd_rpi_wm8804);
+	if (ret && ret != -EPROBE_DEFER)
+		dev_err(&pdev->dev, "Failed to register card %d\n", ret);
+
+	return ret;
+}
+
+static struct platform_driver snd_rpi_wm8804_driver = {
+	.driver = {
+		.name           = "snd-rpi-wm8804",
+		.owner          = THIS_MODULE,
+		.of_match_table = snd_rpi_wm8804_of_match,
+	},
+	.probe  = snd_rpi_wm8804_probe,
+};
+MODULE_DEVICE_TABLE(of, snd_rpi_wm8804_of_match);
+
+module_platform_driver(snd_rpi_wm8804_driver);
+
+MODULE_AUTHOR("Tim Gover <tim.gover@raspberrypi.org>");
+MODULE_DESCRIPTION("ASoC Raspberry Pi Hat generic digi driver for WM8804 based cards");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 399dcf3d1c64..657a4fd9c088 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -125,6 +125,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_IDT821034
 	imply SND_SOC_INNO_RK3036
 	imply SND_SOC_ISABELLE
+	imply SND_SOC_I_SABRE_CODEC
 	imply SND_SOC_JZ4740_CODEC
 	imply SND_SOC_JZ4725B_CODEC
 	imply SND_SOC_JZ4760_CODEC
@@ -132,6 +133,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_LM4857
 	imply SND_SOC_LM49453
 	imply SND_SOC_LOCHNAGAR_SC
+	imply SND_SOC_MA120X0P
 	imply SND_SOC_MAX98088
 	imply SND_SOC_MAX98090
 	imply SND_SOC_MAX98095
@@ -176,6 +178,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_PCM179X_SPI
 	imply SND_SOC_PCM186X_I2C
 	imply SND_SOC_PCM186X_SPI
+	imply SND_SOC_PCM1794A
 	imply SND_SOC_PCM3008
 	imply SND_SOC_PCM3060_I2C
 	imply SND_SOC_PCM3060_SPI
@@ -268,6 +271,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_TLV320ADCX140
 	imply SND_SOC_TLV320AIC23_I2C
 	imply SND_SOC_TLV320AIC23_SPI
+	imply SND_SOC_TAS5713
 	imply SND_SOC_TLV320AIC26
 	imply SND_SOC_TLV320AIC31XX
 	imply SND_SOC_TLV320AIC32X4_I2C
@@ -425,12 +429,12 @@ config SND_SOC_AD193X
 	tristate
 
 config SND_SOC_AD193X_SPI
-	tristate
+	tristate "Analog Devices AU193X CODEC - SPI"
 	depends on SPI_MASTER
 	select SND_SOC_AD193X
 
 config SND_SOC_AD193X_I2C
-	tristate
+	tristate "Analog Devices AU193X CODEC - I2C"
 	depends on I2C
 	select SND_SOC_AD193X
 
@@ -1230,6 +1234,13 @@ config SND_SOC_LOCHNAGAR_SC
 	  This driver support the sound card functionality of the Cirrus
 	  Logic Lochnagar audio development board.
 
+config SND_SOC_MA120X0P
+	tristate "Infineon Merus(TM) MA120X0P Multilevel Class-D Audio amplifiers"
+	depends on I2C
+	help
+		Enable support for Infineon MA120X0P Multilevel Class-D audio power
+		amplifiers.
+
 config SND_SOC_MADERA
 	tristate
 	default y if SND_SOC_CS47L15=y
@@ -1639,6 +1650,10 @@ config SND_SOC_RT5616
 	tristate "Realtek RT5616 CODEC"
 	depends on I2C
 
+config SND_SOC_PCM1794A
+	tristate
+	depends on I2C
+
 config SND_SOC_RT5631
 	tristate "Realtek ALC5631/RT5631 CODEC"
 	depends on I2C
@@ -1996,6 +2011,9 @@ config SND_SOC_TFA9879
 	tristate "NXP Semiconductors TFA9879 amplifier"
 	depends on I2C
 
+config SND_SOC_TAS5713
+	tristate
+
 config SND_SOC_TFA989X
 	tristate "NXP/Goodix TFA989X (TFA1) amplifiers"
 	depends on I2C
@@ -2601,4 +2619,8 @@ config SND_SOC_LPASS_TX_MACRO
 	select SND_SOC_LPASS_MACRO_COMMON
 	tristate "Qualcomm TX Macro in LPASS(Low Power Audio SubSystem)"
 
+config SND_SOC_I_SABRE_CODEC
+	tristate "Audiophonics I-SABRE Codec"
+	depends on I2C
+
 endmenu
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 47c621b3a037..c3efb2b4f347 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -822,3 +822,12 @@ obj-$(CONFIG_SND_SOC_LPASS_TX_MACRO)	+= snd-soc-lpass-tx-macro.o
 
 # Mux
 obj-$(CONFIG_SND_SOC_SIMPLE_MUX)	+= snd-soc-simple-mux.o
+
+snd-soc-i-sabre-codec-objs := i-sabre-codec.o
+snd-soc-ma120x0p-objs := ma120x0p.o
+snd-soc-pcm1794a-objs := pcm1794a.o
+snd-soc-tas5713-objs := tas5713.o
+obj-$(CONFIG_SND_SOC_I_SABRE_CODEC)    += snd-soc-i-sabre-codec.o
+obj-$(CONFIG_SND_SOC_MA120X0P)   += snd-soc-ma120x0p.o
+obj-$(CONFIG_SND_SOC_PCM1794A) += snd-soc-pcm1794a.o
+obj-$(CONFIG_SND_SOC_TAS5713)  += snd-soc-tas5713.o
diff --git a/sound/soc/codecs/adau1977-i2c.c b/sound/soc/codecs/adau1977-i2c.c
index 24c7b9c84c19..328dfdc3f475 100644
--- a/sound/soc/codecs/adau1977-i2c.c
+++ b/sound/soc/codecs/adau1977-i2c.c
@@ -38,9 +38,19 @@ static const struct i2c_device_id adau1977_i2c_ids[] = {
 };
 MODULE_DEVICE_TABLE(i2c, adau1977_i2c_ids);
 
+static const struct of_device_id adau1977_of_ids[] = {
+	{ .compatible = "adi,adau1977", },
+	{ .compatible = "adi,adau1978", },
+	{ .compatible = "adi,adau1979", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, adau1977_of_ids);
+
+
 static struct i2c_driver adau1977_i2c_driver = {
 	.driver = {
 		.name = "adau1977",
+		.of_match_table = adau1977_of_ids,
 	},
 	.probe = adau1977_i2c_probe,
 	.id_table = adau1977_i2c_ids,
diff --git a/sound/soc/codecs/cs42xx8-i2c.c b/sound/soc/codecs/cs42xx8-i2c.c
index ecaebf8e1c8f..ecf7af852ec8 100644
--- a/sound/soc/codecs/cs42xx8-i2c.c
+++ b/sound/soc/codecs/cs42xx8-i2c.c
@@ -58,11 +58,18 @@ static const struct i2c_device_id cs42xx8_i2c_id[] = {
 };
 MODULE_DEVICE_TABLE(i2c, cs42xx8_i2c_id);
 
+const struct of_device_id cs42xx8_i2c_of_match[] = {
+	{ .compatible = "cirrus,cs42448", .data = &cs42448_data, },
+	{ .compatible = "cirrus,cs42888", .data = &cs42888_data, },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, cs42xx8_i2c_of_match);
+
 static struct i2c_driver cs42xx8_i2c_driver = {
 	.driver = {
 		.name = "cs42xx8",
 		.pm = &cs42xx8_pm,
-		.of_match_table = cs42xx8_of_match,
+		.of_match_table = cs42xx8_i2c_of_match,
 	},
 	.probe = cs42xx8_i2c_probe,
 	.remove = cs42xx8_i2c_remove,
diff --git a/sound/soc/codecs/cs42xx8.c b/sound/soc/codecs/cs42xx8.c
index 9c44b6283b8f..8e3b88d7d3af 100644
--- a/sound/soc/codecs/cs42xx8.c
+++ b/sound/soc/codecs/cs42xx8.c
@@ -510,6 +510,16 @@ const struct cs42xx8_driver_data cs42888_data = {
 };
 EXPORT_SYMBOL_GPL(cs42888_data);
 
+const struct of_device_id cs42xx8_of_match[] = {
+	{ .compatible = "cirrus,cs42448", .data = &cs42448_data, },
+	{ .compatible = "cirrus,cs42888", .data = &cs42888_data, },
+	{ /* sentinel */ }
+};
+#if !IS_ENABLED(CONFIG_SND_SOC_CS42XX8_I2C)
+MODULE_DEVICE_TABLE(of, cs42xx8_of_match);
+EXPORT_SYMBOL_GPL(cs42xx8_of_match);
+#endif
+
 int cs42xx8_probe(struct device *dev, struct regmap *regmap, struct cs42xx8_driver_data *drvdata)
 {
 	struct cs42xx8_priv *cs42xx8;
diff --git a/sound/soc/codecs/da7213.c b/sound/soc/codecs/da7213.c
index f17f02d01f8c..9724f50ed021 100644
--- a/sound/soc/codecs/da7213.c
+++ b/sound/soc/codecs/da7213.c
@@ -1346,6 +1346,8 @@ static int da7213_hw_params(struct snd_pcm_substream *substream,
 	switch (params_width(params)) {
 	case 16:
 		dai_ctrl |= DA7213_DAI_WORD_LENGTH_S16_LE;
+		if (da7213->bclk_ratio == 64)
+			break;
 		dai_clk_mode = DA7213_DAI_BCLKS_PER_WCLK_32; /* 32bit for 1ch and 2ch */
 		break;
 	case 20:
@@ -1361,6 +1363,9 @@ static int da7213_hw_params(struct snd_pcm_substream *substream,
 		return -EINVAL;
 	}
 
+	if (da7213->bclk_ratio == 32 && params_width(params) != 16)
+		return -EINVAL;
+
 	/* Set sampling rate */
 	switch (params_rate(params)) {
 	case 8000:
@@ -1523,6 +1528,21 @@ static int da7213_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
 	return 0;
 }
 
+static int da7213_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio)
+{
+	struct snd_soc_component *component = dai->component;
+	struct da7213_priv *da7213 = snd_soc_component_get_drvdata(component);
+
+	if (ratio != 32 && ratio != 64) {
+		dev_err(component->dev, "Invalid bclk ratio %d\n", ratio);
+		return -EINVAL;
+	}
+
+	da7213->bclk_ratio = ratio;
+
+	return 0;
+}
+
 static int da7213_mute(struct snd_soc_dai *dai, int mute, int direction)
 {
 	struct snd_soc_component *component = dai->component;
@@ -1735,6 +1755,7 @@ static const u64 da7213_dai_formats =
 static const struct snd_soc_dai_ops da7213_dai_ops = {
 	.hw_params	= da7213_hw_params,
 	.set_fmt	= da7213_set_dai_fmt,
+	.set_bclk_ratio	= da7213_set_bclk_ratio,
 	.mute_stream	= da7213_mute,
 	.no_capture_mute = 1,
 	.auto_selectable_formats	= &da7213_dai_formats,
diff --git a/sound/soc/codecs/da7213.h b/sound/soc/codecs/da7213.h
index 505b731c0adb..5b6d88356891 100644
--- a/sound/soc/codecs/da7213.h
+++ b/sound/soc/codecs/da7213.h
@@ -600,6 +600,7 @@ struct da7213_priv {
 	struct clk *mclk;
 	unsigned int mclk_rate;
 	unsigned int out_rate;
+	unsigned int bclk_ratio;
 	int clk_src;
 	bool master;
 	bool alc_calib_auto;
diff --git a/sound/soc/codecs/i-sabre-codec.c b/sound/soc/codecs/i-sabre-codec.c
new file mode 100644
index 000000000000..518c57f4558e
--- /dev/null
+++ b/sound/soc/codecs/i-sabre-codec.c
@@ -0,0 +1,389 @@
+/*
+ * Driver for I-Sabre Q2M
+ *
+ * Author: Satoru Kawase
+ * Modified by: Xiao Qingyong
+ * Modified by: JC BARBAUD (Mute)
+ * Update kernel v4.18+ by : Audiophonics
+ * 		Copyright 2018 Audiophonics
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/i2c.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+
+#include "i-sabre-codec.h"
+
+
+/* I-Sabre Q2M Codec Private Data */
+struct i_sabre_codec_priv {
+	struct regmap *regmap;
+	unsigned int fmt;
+};
+
+
+/* I-Sabre Q2M Codec Default Register Value */
+static const struct reg_default i_sabre_codec_reg_defaults[] = {
+	{ ISABRECODEC_REG_10, 0x00 },
+	{ ISABRECODEC_REG_20, 0x00 },
+	{ ISABRECODEC_REG_21, 0x00 },
+	{ ISABRECODEC_REG_22, 0x00 },
+	{ ISABRECODEC_REG_24, 0x00 },
+};
+
+
+static bool i_sabre_codec_writeable(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case ISABRECODEC_REG_10:
+	case ISABRECODEC_REG_20:
+	case ISABRECODEC_REG_21:
+	case ISABRECODEC_REG_22:
+	case ISABRECODEC_REG_24:
+		return true;
+
+	default:
+		return false;
+	}
+}
+
+static bool i_sabre_codec_readable(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case ISABRECODEC_REG_01:
+	case ISABRECODEC_REG_02:
+	case ISABRECODEC_REG_10:
+	case ISABRECODEC_REG_20:
+	case ISABRECODEC_REG_21:
+	case ISABRECODEC_REG_22:
+	case ISABRECODEC_REG_24:
+		return true;
+
+	default:
+		return false;
+	}
+}
+
+static bool i_sabre_codec_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case ISABRECODEC_REG_01:
+	case ISABRECODEC_REG_02:
+		return true;
+
+	default:
+		return false;
+	}
+}
+
+
+/* Volume Scale */
+static const DECLARE_TLV_DB_SCALE(volume_tlv, -10000, 100, 0);
+
+
+/* Filter Type */
+static const char * const fir_filter_type_texts[] = {
+	"brick wall",
+	"corrected minimum phase fast",
+	"minimum phase slow",
+	"minimum phase fast",
+	"linear phase slow",
+	"linear phase fast",
+	"apodizing fast",
+};
+
+static SOC_ENUM_SINGLE_DECL(i_sabre_fir_filter_type_enum,
+				ISABRECODEC_REG_22, 0, fir_filter_type_texts);
+
+
+/* I2S / SPDIF Select */
+static const char * const iis_spdif_sel_texts[] = {
+	"I2S",
+	"SPDIF",
+};
+
+static SOC_ENUM_SINGLE_DECL(i_sabre_iis_spdif_sel_enum,
+				ISABRECODEC_REG_24, 0, iis_spdif_sel_texts);
+
+
+/* Control */
+static const struct snd_kcontrol_new i_sabre_codec_controls[] = {
+SOC_SINGLE_RANGE_TLV("Digital Playback Volume", ISABRECODEC_REG_20, 0, 0, 100, 1, volume_tlv),
+SOC_SINGLE("Digital Playback Switch", ISABRECODEC_REG_21, 0, 1, 1),
+SOC_ENUM("FIR Filter Type", i_sabre_fir_filter_type_enum),
+SOC_ENUM("I2S/SPDIF Select", i_sabre_iis_spdif_sel_enum),
+};
+
+
+static const u32 i_sabre_codec_dai_rates_slave[] = {
+	8000, 11025, 16000, 22050, 32000,
+	44100, 48000, 64000, 88200, 96000,
+	176400, 192000, 352800, 384000,
+	705600, 768000, 1411200, 1536000
+};
+
+static const struct snd_pcm_hw_constraint_list constraints_slave = {
+	.list  = i_sabre_codec_dai_rates_slave,
+	.count = ARRAY_SIZE(i_sabre_codec_dai_rates_slave),
+};
+
+static int i_sabre_codec_dai_startup_slave(
+		struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	int ret;
+
+	ret = snd_pcm_hw_constraint_list(substream->runtime,
+			0, SNDRV_PCM_HW_PARAM_RATE, &constraints_slave);
+	if (ret != 0) {
+		dev_err(component->card->dev, "Failed to setup rates constraints: %d\n", ret);
+	}
+
+	return ret;
+}
+
+static int i_sabre_codec_dai_startup(
+		struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
+{
+	struct snd_soc_component      *component = dai->component;
+	struct i_sabre_codec_priv *i_sabre_codec
+					= snd_soc_component_get_drvdata(component);
+
+	switch (i_sabre_codec->fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:
+		return i_sabre_codec_dai_startup_slave(substream, dai);
+
+	default:
+		return (-EINVAL);
+	}
+}
+
+static int i_sabre_codec_hw_params(
+	struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params,
+	struct snd_soc_dai *dai)
+{
+	struct snd_soc_component      *component = dai->component;
+	struct i_sabre_codec_priv *i_sabre_codec
+					= snd_soc_component_get_drvdata(component);
+	unsigned int daifmt;
+	int format_width;
+
+	dev_dbg(component->card->dev, "hw_params %u Hz, %u channels\n",
+			params_rate(params), params_channels(params));
+
+	/* Check I2S Format (Bit Size) */
+	format_width = snd_pcm_format_width(params_format(params));
+	if ((format_width != 32) && (format_width != 16)) {
+		dev_err(component->card->dev, "Bad frame size: %d\n",
+				snd_pcm_format_width(params_format(params)));
+		return (-EINVAL);
+	}
+
+	/* Check Slave Mode */
+	daifmt = i_sabre_codec->fmt & SND_SOC_DAIFMT_MASTER_MASK;
+	if (daifmt != SND_SOC_DAIFMT_CBS_CFS) {
+		return (-EINVAL);
+	}
+
+	/* Notify Sampling Frequency  */
+	switch (params_rate(params))
+	{
+	case 44100:
+	case 48000:
+	case 88200:
+	case 96000:
+	case 176400:
+	case 192000:
+		snd_soc_component_update_bits(component, ISABRECODEC_REG_10, 0x01, 0x00);
+		break;
+
+	case 352800:
+	case 384000:
+	case 705600:
+	case 768000:
+	case 1411200:
+	case 1536000:
+		snd_soc_component_update_bits(component, ISABRECODEC_REG_10, 0x01, 0x01);
+		break;
+	}
+
+	return 0;
+}
+
+static int i_sabre_codec_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct snd_soc_component      *component = dai->component;
+	struct i_sabre_codec_priv *i_sabre_codec
+					= snd_soc_component_get_drvdata(component);
+
+	/* interface format */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		break;
+
+	case SND_SOC_DAIFMT_RIGHT_J:
+	case SND_SOC_DAIFMT_LEFT_J:
+	default:
+		return (-EINVAL);
+	}
+
+	/* clock inversion */
+	if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) {
+		return (-EINVAL);
+	}
+
+	/* Set Audio Data Format */
+	i_sabre_codec->fmt = fmt;
+
+	return 0;
+}
+
+static int i_sabre_codec_dac_mute(struct snd_soc_dai *dai, int mute, int direction)
+{
+	struct snd_soc_component *component = dai->component;
+
+	if (mute) {
+		snd_soc_component_update_bits(component, ISABRECODEC_REG_21, 0x01, 0x01);
+	} else {
+		snd_soc_component_update_bits(component, ISABRECODEC_REG_21, 0x01, 0x00);
+	}
+
+	return 0;
+}
+
+
+static const struct snd_soc_dai_ops i_sabre_codec_dai_ops = {
+	.startup      = i_sabre_codec_dai_startup,
+	.hw_params    = i_sabre_codec_hw_params,
+	.set_fmt      = i_sabre_codec_set_fmt,
+	.mute_stream  = i_sabre_codec_dac_mute,
+};
+
+static struct snd_soc_dai_driver i_sabre_codec_dai = {
+	.name = "i-sabre-codec-dai",
+	.playback = {
+		.stream_name  = "Playback",
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_CONTINUOUS,
+		.rate_min = 8000,
+		.rate_max = 1536000,
+		.formats      = SNDRV_PCM_FMTBIT_S16_LE
+				| SNDRV_PCM_FMTBIT_S32_LE,
+	},
+	.ops = &i_sabre_codec_dai_ops,
+};
+
+static struct snd_soc_component_driver i_sabre_codec_codec_driver = {
+		.controls         = i_sabre_codec_controls,
+		.num_controls     = ARRAY_SIZE(i_sabre_codec_controls),
+};
+
+
+static const struct regmap_config i_sabre_codec_regmap = {
+	.reg_bits         = 8,
+	.val_bits         = 8,
+	.max_register     = ISABRECODEC_MAX_REG,
+
+	.reg_defaults     = i_sabre_codec_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(i_sabre_codec_reg_defaults),
+
+	.writeable_reg    = i_sabre_codec_writeable,
+	.readable_reg     = i_sabre_codec_readable,
+	.volatile_reg     = i_sabre_codec_volatile,
+
+	.cache_type       = REGCACHE_RBTREE,
+};
+
+
+static int i_sabre_codec_probe(struct device *dev, struct regmap *regmap)
+{
+	struct i_sabre_codec_priv *i_sabre_codec;
+	int ret;
+
+	i_sabre_codec = devm_kzalloc(dev, sizeof(*i_sabre_codec), GFP_KERNEL);
+	if (!i_sabre_codec) {
+		dev_err(dev, "devm_kzalloc");
+		return (-ENOMEM);
+	}
+
+	i_sabre_codec->regmap = regmap;
+
+	dev_set_drvdata(dev, i_sabre_codec);
+
+	ret = snd_soc_register_component(dev,
+			&i_sabre_codec_codec_driver, &i_sabre_codec_dai, 1);
+	if (ret != 0) {
+		dev_err(dev, "Failed to register CODEC: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void i_sabre_codec_remove(struct device *dev)
+{
+	snd_soc_unregister_component(dev);
+}
+
+
+static int i_sabre_codec_i2c_probe(struct i2c_client *i2c)
+{
+	struct regmap *regmap;
+
+	regmap = devm_regmap_init_i2c(i2c, &i_sabre_codec_regmap);
+	if (IS_ERR(regmap)) {
+		return PTR_ERR(regmap);
+	}
+
+	return i_sabre_codec_probe(&i2c->dev, regmap);
+}
+
+static void i_sabre_codec_i2c_remove(struct i2c_client *i2c)
+{
+	i_sabre_codec_remove(&i2c->dev);
+}
+
+
+static const struct i2c_device_id i_sabre_codec_i2c_id[] = {
+	{ "i-sabre-codec", },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, i_sabre_codec_i2c_id);
+
+static const struct of_device_id i_sabre_codec_of_match[] = {
+	{ .compatible = "audiophonics,i-sabre-codec", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, i_sabre_codec_of_match);
+
+static struct i2c_driver i_sabre_codec_i2c_driver = {
+	.driver = {
+		.name           = "i-sabre-codec-i2c",
+		.owner          = THIS_MODULE,
+		.of_match_table = of_match_ptr(i_sabre_codec_of_match),
+	},
+	.probe    = i_sabre_codec_i2c_probe,
+	.remove   = i_sabre_codec_i2c_remove,
+	.id_table = i_sabre_codec_i2c_id,
+};
+module_i2c_driver(i_sabre_codec_i2c_driver);
+
+
+MODULE_DESCRIPTION("ASoC I-Sabre Q2M codec driver");
+MODULE_AUTHOR("Audiophonics <http://www.audiophonics.fr>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/i-sabre-codec.h b/sound/soc/codecs/i-sabre-codec.h
new file mode 100644
index 000000000000..9cac5a2446b9
--- /dev/null
+++ b/sound/soc/codecs/i-sabre-codec.h
@@ -0,0 +1,42 @@
+/*
+ * Driver for I-Sabre Q2M
+ *
+ * Author: Satoru Kawase
+ * Modified by: Xiao Qingyong
+ *      Copyright 2018 Audiophonics
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#ifndef _SND_SOC_ISABRECODEC
+#define _SND_SOC_ISABRECODEC
+
+
+/* ISABRECODEC Register Address */
+#define ISABRECODEC_REG_01	0x01	/* Virtual Device ID  :  0x01 = es9038q2m */
+#define ISABRECODEC_REG_02	0x02	/* API revision       :  0x01 = Revision 01 */
+#define ISABRECODEC_REG_10	0x10	/* 0x01 = above 192kHz, 0x00 = otherwise */
+#define ISABRECODEC_REG_20	0x20	/* 0 - 100 (decimal value, 0 = min., 100 = max.) */
+#define ISABRECODEC_REG_21	0x21	/* 0x00 = Mute OFF, 0x01 = Mute ON */
+#define ISABRECODEC_REG_22	0x22	
+/*
+   0x00 = brick wall,
+   0x01 = corrected minimum phase fast,
+   0x02 = minimum phase slow,
+   0x03 = minimum phase fast,
+   0x04 = linear phase slow,
+   0x05 = linear phase fast,
+   0x06 = apodizing fast,
+*/
+//#define ISABRECODEC_REG_23	0x23	/* reserved */
+#define ISABRECODEC_REG_24	0x24	/* 0x00 = I2S, 0x01 = SPDIF */
+#define ISABRECODEC_MAX_REG	0x24	/* Maximum Register Number */
+
+#endif /* _SND_SOC_ISABRECODEC */
diff --git a/sound/soc/codecs/ma120x0p.c b/sound/soc/codecs/ma120x0p.c
new file mode 100644
index 000000000000..3df7e759ace1
--- /dev/null
+++ b/sound/soc/codecs/ma120x0p.c
@@ -0,0 +1,1380 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ASoC Driver for Infineon Merus(TM) ma120x0p multi-level class-D amplifier
+ *
+ * Authors:	Ariel Muszkat <ariel.muszkat@gmail.com>
+ * Jorgen Kragh Jakobsen <jorgen.kraghjakobsen@infineon.com>
+ *
+ * Copyright (C) 2019 Infineon Technologies AG
+ *
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <linux/i2c.h>
+#include <linux/of_device.h>
+#include <linux/spi/spi.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <linux/interrupt.h>
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/fs.h>
+#include <linux/uaccess.h>
+
+#ifndef _MA120X0P_
+#define _MA120X0P_
+//------------------------------------------------------------------manualPM---
+// Select Manual PowerMode control
+#define ma_manualpm__a 0
+#define ma_manualpm__len 1
+#define ma_manualpm__mask 0x40
+#define ma_manualpm__shift 0x06
+#define ma_manualpm__reset 0x00
+//--------------------------------------------------------------------pm_man---
+// manual selected power mode
+#define ma_pm_man__a 0
+#define ma_pm_man__len 2
+#define ma_pm_man__mask 0x30
+#define ma_pm_man__shift 0x04
+#define ma_pm_man__reset 0x03
+//------------------------------------------ ----------------------mthr_1to2---
+// mod. index threshold value for pm1=>pm2 change.
+#define ma_mthr_1to2__a 1
+#define ma_mthr_1to2__len 8
+#define ma_mthr_1to2__mask 0xff
+#define ma_mthr_1to2__shift 0x00
+#define ma_mthr_1to2__reset 0x3c
+//-----------------------------------------------------------------mthr_2to1---
+// mod. index threshold value for pm2=>pm1 change.
+#define ma_mthr_2to1__a 2
+#define ma_mthr_2to1__len 8
+#define ma_mthr_2to1__mask 0xff
+#define ma_mthr_2to1__shift 0x00
+#define ma_mthr_2to1__reset 0x32
+//-----------------------------------------------------------------mthr_2to3---
+// mod. index threshold value for pm2=>pm3 change.
+#define ma_mthr_2to3__a 3
+#define ma_mthr_2to3__len 8
+#define ma_mthr_2to3__mask 0xff
+#define ma_mthr_2to3__shift 0x00
+#define ma_mthr_2to3__reset 0x5a
+//-----------------------------------------------------------------mthr_3to2---
+// mod. index threshold value for pm3=>pm2 change.
+#define ma_mthr_3to2__a 4
+#define ma_mthr_3to2__len 8
+#define ma_mthr_3to2__mask 0xff
+#define ma_mthr_3to2__shift 0x00
+#define ma_mthr_3to2__reset 0x50
+//-------------------------------------------------------------pwmclkdiv_nom---
+// pwm default clock divider value
+#define ma_pwmclkdiv_nom__a 8
+#define ma_pwmclkdiv_nom__len 8
+#define ma_pwmclkdiv_nom__mask 0xff
+#define ma_pwmclkdiv_nom__shift 0x00
+#define ma_pwmclkdiv_nom__reset 0x26
+//--------- ----------------------------------------------------ocp_latch_en---
+// high to use permanently latching level-2 ocp
+#define ma_ocp_latch_en__a 10
+#define ma_ocp_latch_en__len 1
+#define ma_ocp_latch_en__mask 0x02
+#define ma_ocp_latch_en__shift 0x01
+#define ma_ocp_latch_en__reset 0x00
+//---------------------------------------------------------------lf_clamp_en---
+// high (default) to enable lf int2+3 clamping on clip
+#define ma_lf_clamp_en__a 10
+#define ma_lf_clamp_en__len 1
+#define ma_lf_clamp_en__mask 0x80
+#define ma_lf_clamp_en__shift 0x07
+#define ma_lf_clamp_en__reset 0x00
+//-------------------------------------------------------pmcfg_btl_b.modtype---
+//
+#define ma_pmcfg_btl_b__modtype__a 18
+#define ma_pmcfg_btl_b__modtype__len 2
+#define ma_pmcfg_btl_b__modtype__mask 0x18
+#define ma_pmcfg_btl_b__modtype__shift 0x03
+#define ma_pmcfg_btl_b__modtype__reset 0x02
+//-------------------------------------------------------pmcfg_btl_b.freqdiv---
+#define ma_pmcfg_btl_b__freqdiv__a 18
+#define ma_pmcfg_btl_b__freqdiv__len 2
+#define ma_pmcfg_btl_b__freqdiv__mask 0x06
+#define ma_pmcfg_btl_b__freqdiv__shift 0x01
+#define ma_pmcfg_btl_b__freqdiv__reset 0x01
+//----------------------------------------------------pmcfg_btl_b.lf_gain_ol---
+//
+#define ma_pmcfg_btl_b__lf_gain_ol__a 18
+#define ma_pmcfg_btl_b__lf_gain_ol__len 1
+#define ma_pmcfg_btl_b__lf_gain_ol__mask 0x01
+#define ma_pmcfg_btl_b__lf_gain_ol__shift 0x00
+#define ma_pmcfg_btl_b__lf_gain_ol__reset 0x01
+//-------------------------------------------------------pmcfg_btl_c.freqdiv---
+//
+#define ma_pmcfg_btl_c__freqdiv__a 19
+#define ma_pmcfg_btl_c__freqdiv__len 2
+#define ma_pmcfg_btl_c__freqdiv__mask 0x06
+#define ma_pmcfg_btl_c__freqdiv__shift 0x01
+#define ma_pmcfg_btl_c__freqdiv__reset 0x01
+//-------------------------------------------------------pmcfg_btl_c.modtype---
+//
+#define ma_pmcfg_btl_c__modtype__a 19
+#define ma_pmcfg_btl_c__modtype__len 2
+#define ma_pmcfg_btl_c__modtype__mask 0x18
+#define ma_pmcfg_btl_c__modtype__shift 0x03
+#define ma_pmcfg_btl_c__modtype__reset 0x01
+//----------------------------------------------------pmcfg_btl_c.lf_gain_ol---
+//
+#define ma_pmcfg_btl_c__lf_gain_ol__a 19
+#define ma_pmcfg_btl_c__lf_gain_ol__len 1
+#define ma_pmcfg_btl_c__lf_gain_ol__mask 0x01
+#define ma_pmcfg_btl_c__lf_gain_ol__shift 0x00
+#define ma_pmcfg_btl_c__lf_gain_ol__reset 0x00
+//-------------------------------------------------------pmcfg_btl_d.modtype---
+//
+#define ma_pmcfg_btl_d__modtype__a 20
+#define ma_pmcfg_btl_d__modtype__len 2
+#define ma_pmcfg_btl_d__modtype__mask 0x18
+#define ma_pmcfg_btl_d__modtype__shift 0x03
+#define ma_pmcfg_btl_d__modtype__reset 0x02
+//-------------------------------------------------------pmcfg_btl_d.freqdiv---
+//
+#define ma_pmcfg_btl_d__freqdiv__a 20
+#define ma_pmcfg_btl_d__freqdiv__len 2
+#define ma_pmcfg_btl_d__freqdiv__mask 0x06
+#define ma_pmcfg_btl_d__freqdiv__shift 0x01
+#define ma_pmcfg_btl_d__freqdiv__reset 0x02
+//----------------------------------------------------pmcfg_btl_d.lf_gain_ol---
+//
+#define ma_pmcfg_btl_d__lf_gain_ol__a 20
+#define ma_pmcfg_btl_d__lf_gain_ol__len 1
+#define ma_pmcfg_btl_d__lf_gain_ol__mask 0x01
+#define ma_pmcfg_btl_d__lf_gain_ol__shift 0x00
+#define ma_pmcfg_btl_d__lf_gain_ol__reset 0x00
+//------------ -------------------------------------------pmcfg_se_a.modtype---
+//
+#define ma_pmcfg_se_a__modtype__a 21
+#define ma_pmcfg_se_a__modtype__len 2
+#define ma_pmcfg_se_a__modtype__mask 0x18
+#define ma_pmcfg_se_a__modtype__shift 0x03
+#define ma_pmcfg_se_a__modtype__reset 0x01
+//--------------------------------------------------------pmcfg_se_a.freqdiv---
+//
+#define ma_pmcfg_se_a__freqdiv__a 21
+#define ma_pmcfg_se_a__freqdiv__len 2
+#define ma_pmcfg_se_a__freqdiv__mask 0x06
+#define ma_pmcfg_se_a__freqdiv__shift 0x01
+#define ma_pmcfg_se_a__freqdiv__reset 0x00
+//-----------------------------------------------------pmcfg_se_a.lf_gain_ol---
+//
+#define ma_pmcfg_se_a__lf_gain_ol__a 21
+#define ma_pmcfg_se_a__lf_gain_ol__len 1
+#define ma_pmcfg_se_a__lf_gain_ol__mask 0x01
+#define ma_pmcfg_se_a__lf_gain_ol__shift 0x00
+#define ma_pmcfg_se_a__lf_gain_ol__reset 0x01
+//-----------------------------------------------------pmcfg_se_b.lf_gain_ol---
+//
+#define ma_pmcfg_se_b__lf_gain_ol__a 22
+#define ma_pmcfg_se_b__lf_gain_ol__len 1
+#define ma_pmcfg_se_b__lf_gain_ol__mask 0x01
+#define ma_pmcfg_se_b__lf_gain_ol__shift 0x00
+#define ma_pmcfg_se_b__lf_gain_ol__reset 0x00
+//--------------------------------------------------------pmcfg_se_b.freqdiv---
+//
+#define ma_pmcfg_se_b__freqdiv__a 22
+#define ma_pmcfg_se_b__freqdiv__len 2
+#define ma_pmcfg_se_b__freqdiv__mask 0x06
+#define ma_pmcfg_se_b__freqdiv__shift 0x01
+#define ma_pmcfg_se_b__freqdiv__reset 0x01
+//--------------------------------------------------------pmcfg_se_b.modtype---
+//
+#define ma_pmcfg_se_b__modtype__a 22
+#define ma_pmcfg_se_b__modtype__len 2
+#define ma_pmcfg_se_b__modtype__mask 0x18
+#define ma_pmcfg_se_b__modtype__shift 0x03
+#define ma_pmcfg_se_b__modtype__reset 0x01
+//----------------------------------------------------------balwaitcount_pm1---
+// pm1 balancing period.
+#define ma_balwaitcount_pm1__a 23
+#define ma_balwaitcount_pm1__len 8
+#define ma_balwaitcount_pm1__mask 0xff
+#define ma_balwaitcount_pm1__shift 0x00
+#define ma_balwaitcount_pm1__reset 0x14
+//----------------------------------------------------------balwaitcount_pm2---
+// pm2 balancing period.
+#define ma_balwaitcount_pm2__a 24
+#define ma_balwaitcount_pm2__len 8
+#define ma_balwaitcount_pm2__mask 0xff
+#define ma_balwaitcount_pm2__shift 0x00
+#define ma_balwaitcount_pm2__reset 0x14
+//----------------------------------------------------------balwaitcount_pm3---
+// pm3 balancing period.
+#define ma_balwaitcount_pm3__a 25
+#define ma_balwaitcount_pm3__len 8
+#define ma_balwaitcount_pm3__mask 0xff
+#define ma_balwaitcount_pm3__shift 0x00
+#define ma_balwaitcount_pm3__reset 0x1a
+//-------------------------------------------------------------usespread_pm1---
+// pm1 pwm spread-spectrum mode on/off.
+#define ma_usespread_pm1__a 26
+#define ma_usespread_pm1__len 1
+#define ma_usespread_pm1__mask 0x40
+#define ma_usespread_pm1__shift 0x06
+#define ma_usespread_pm1__reset 0x00
+//---------------------------------------------------------------dtsteps_pm1---
+// pm1 dead time setting [10ns steps].
+#define ma_dtsteps_pm1__a 26
+#define ma_dtsteps_pm1__len 3
+#define ma_dtsteps_pm1__mask 0x38
+#define ma_dtsteps_pm1__shift 0x03
+#define ma_dtsteps_pm1__reset 0x04
+//---------------------------------------------------------------baltype_pm1---
+// pm1 balancing sensor scheme.
+#define ma_baltype_pm1__a 26
+#define ma_baltype_pm1__len 3
+#define ma_baltype_pm1__mask 0x07
+#define ma_baltype_pm1__shift 0x00
+#define ma_baltype_pm1__reset 0x00
+//-------------------------------------------------------------usespread_pm2---
+// pm2 pwm spread-spectrum mode on/off.
+#define ma_usespread_pm2__a 27
+#define ma_usespread_pm2__len 1
+#define ma_usespread_pm2__mask 0x40
+#define ma_usespread_pm2__shift 0x06
+#define ma_usespread_pm2__reset 0x00
+//---------------------------------------------------------------dtsteps_pm2---
+// pm2 dead time setting [10ns steps].
+#define ma_dtsteps_pm2__a 27
+#define ma_dtsteps_pm2__len 3
+#define ma_dtsteps_pm2__mask 0x38
+#define ma_dtsteps_pm2__shift 0x03
+#define ma_dtsteps_pm2__reset 0x03
+//---------------------------------------------------------------baltype_pm2---
+// pm2 balancing sensor scheme.
+#define ma_baltype_pm2__a 27
+#define ma_baltype_pm2__len 3
+#define ma_baltype_pm2__mask 0x07
+#define ma_baltype_pm2__shift 0x00
+#define ma_baltype_pm2__reset 0x01
+//-------------------------------------------------------------usespread_pm3---
+// pm3 pwm spread-spectrum mode on/off.
+#define ma_usespread_pm3__a 28
+#define ma_usespread_pm3__len 1
+#define ma_usespread_pm3__mask 0x40
+#define ma_usespread_pm3__shift 0x06
+#define ma_usespread_pm3__reset 0x00
+//---------------------------------------------------------------dtsteps_pm3---
+// pm3 dead time setting [10ns steps].
+#define ma_dtsteps_pm3__a 28
+#define ma_dtsteps_pm3__len 3
+#define ma_dtsteps_pm3__mask 0x38
+#define ma_dtsteps_pm3__shift 0x03
+#define ma_dtsteps_pm3__reset 0x01
+//---------------------------------------------------------------baltype_pm3---
+// pm3 balancing sensor scheme.
+#define ma_baltype_pm3__a 28
+#define ma_baltype_pm3__len 3
+#define ma_baltype_pm3__mask 0x07
+#define ma_baltype_pm3__shift 0x00
+#define ma_baltype_pm3__reset 0x03
+//-----------------------------------------------------------------pmprofile---
+// pm profile select. valid presets: 0-1-2-3-4. 5=> custom profile.
+#define ma_pmprofile__a 29
+#define ma_pmprofile__len 3
+#define ma_pmprofile__mask 0x07
+#define ma_pmprofile__shift 0x00
+#define ma_pmprofile__reset 0x00
+//-------------------------------------------------------------------pm3_man---
+// custom profile pm3 contents. 0=>a,  1=>b,  2=>c,  3=>d
+#define ma_pm3_man__a 30
+#define ma_pm3_man__len 2
+#define ma_pm3_man__mask 0x30
+#define ma_pm3_man__shift 0x04
+#define ma_pm3_man__reset 0x02
+//-------------------------------------------------------------------pm2_man---
+// custom profile pm2 contents. 0=>a,  1=>b,  2=>c,  3=>d
+#define ma_pm2_man__a 30
+#define ma_pm2_man__len 2
+#define ma_pm2_man__mask 0x0c
+#define ma_pm2_man__shift 0x02
+#define ma_pm2_man__reset 0x03
+//-------------------------------------------------------------------pm1_man---
+// custom profile pm1 contents. 0=>a,  1=>b,  2=>c,  3=>d
+#define ma_pm1_man__a 30
+#define ma_pm1_man__len 2
+#define ma_pm1_man__mask 0x03
+#define ma_pm1_man__shift 0x00
+#define ma_pm1_man__reset 0x03
+//-----------------------------------------------------------ocp_latch_clear---
+// low-high clears current ocp latched condition.
+#define ma_ocp_latch_clear__a 32
+#define ma_ocp_latch_clear__len 1
+#define ma_ocp_latch_clear__mask 0x80
+#define ma_ocp_latch_clear__shift 0x07
+#define ma_ocp_latch_clear__reset 0x00
+//-------------------------------------------------------------audio_in_mode---
+// audio input mode; 0-1-2-3-4-5
+#define ma_audio_in_mode__a 37
+#define ma_audio_in_mode__len 3
+#define ma_audio_in_mode__mask 0xe0
+#define ma_audio_in_mode__shift 0x05
+#define ma_audio_in_mode__reset 0x00
+//-----------------------------------------------------------------eh_dcshdn---
+// high to enable dc protection
+#define ma_eh_dcshdn__a 38
+#define ma_eh_dcshdn__len 1
+#define ma_eh_dcshdn__mask 0x04
+#define ma_eh_dcshdn__shift 0x02
+#define ma_eh_dcshdn__reset 0x01
+//---------------------------------------------------------audio_in_mode_ext---
+// if set,  audio_in_mode is controlled from audio_in_mode register. if not set
+//audio_in_mode is set from fuse bank setting
+#define ma_audio_in_mode_ext__a 39
+#define ma_audio_in_mode_ext__len 1
+#define ma_audio_in_mode_ext__mask 0x20
+#define ma_audio_in_mode_ext__shift 0x05
+#define ma_audio_in_mode_ext__reset 0x00
+//------------------------------------------------------------------eh_clear---
+// flip to clear error registers
+#define ma_eh_clear__a 45
+#define ma_eh_clear__len 1
+#define ma_eh_clear__mask 0x04
+#define ma_eh_clear__shift 0x02
+#define ma_eh_clear__reset 0x00
+//----------------------------------------------------------thermal_compr_en---
+// enable otw-contr.  input compression?
+#define ma_thermal_compr_en__a 45
+#define ma_thermal_compr_en__len 1
+#define ma_thermal_compr_en__mask 0x20
+#define ma_thermal_compr_en__shift 0x05
+#define ma_thermal_compr_en__reset 0x01
+//---------------------------------------------------------------system_mute---
+// 1 = mute system,  0 = normal operation
+#define ma_system_mute__a 45
+#define ma_system_mute__len 1
+#define ma_system_mute__mask 0x40
+#define ma_system_mute__shift 0x06
+#define ma_system_mute__reset 0x00
+//------------------------------------------------------thermal_compr_max_db---
+// audio limiter max thermal reduction
+#define ma_thermal_compr_max_db__a 46
+#define ma_thermal_compr_max_db__len 3
+#define ma_thermal_compr_max_db__mask 0x07
+#define ma_thermal_compr_max_db__shift 0x00
+#define ma_thermal_compr_max_db__reset 0x04
+//---------------------------------------------------------audio_proc_enable---
+// enable audio proc,  bypass if not enabled
+#define ma_audio_proc_enable__a 53
+#define ma_audio_proc_enable__len 1
+#define ma_audio_proc_enable__mask 0x08
+#define ma_audio_proc_enable__shift 0x03
+#define ma_audio_proc_enable__reset 0x00
+//--------------------------------------------------------audio_proc_release---
+// 00:slow,  01:normal,  10:fast
+#define ma_audio_proc_release__a 53
+#define ma_audio_proc_release__len 2
+#define ma_audio_proc_release__mask 0x30
+#define ma_audio_proc_release__shift 0x04
+#define ma_audio_proc_release__reset 0x00
+//---------------------------------------------------------audio_proc_attack---
+// 00:slow,  01:normal,  10:fast
+#define ma_audio_proc_attack__a 53
+#define ma_audio_proc_attack__len 2
+#define ma_audio_proc_attack__mask 0xc0
+#define ma_audio_proc_attack__shift 0x06
+#define ma_audio_proc_attack__reset 0x00
+//----------------------------------------------------------------i2s_format---
+// i2s basic data format,  000 = std. i2s,  001 = left justified (default)
+#define ma_i2s_format__a 53
+#define ma_i2s_format__len 3
+#define ma_i2s_format__mask 0x07
+#define ma_i2s_format__shift 0x00
+#define ma_i2s_format__reset 0x01
+//--------------------------------------------------audio_proc_limiterenable---
+// 1: enable limiter,  0: disable limiter
+#define ma_audio_proc_limiterenable__a 54
+#define ma_audio_proc_limiterenable__len 1
+#define ma_audio_proc_limiterenable__mask 0x40
+#define ma_audio_proc_limiterenable__shift 0x06
+#define ma_audio_proc_limiterenable__reset 0x00
+//-----------------------------------------------------------audio_proc_mute---
+// 1: mute,  0: unmute
+#define ma_audio_proc_mute__a 54
+#define ma_audio_proc_mute__len 1
+#define ma_audio_proc_mute__mask 0x80
+#define ma_audio_proc_mute__shift 0x07
+#define ma_audio_proc_mute__reset 0x00
+//---------------------------------------------------------------i2s_sck_pol---
+// i2s sck polarity cfg. 0 = rising edge data change
+#define ma_i2s_sck_pol__a 54
+#define ma_i2s_sck_pol__len 1
+#define ma_i2s_sck_pol__mask 0x01
+#define ma_i2s_sck_pol__shift 0x00
+#define ma_i2s_sck_pol__reset 0x01
+//-------------------------------------------------------------i2s_framesize---
+// i2s word length. 00 = 32bit,  01 = 24bit
+#define ma_i2s_framesize__a 54
+#define ma_i2s_framesize__len 2
+#define ma_i2s_framesize__mask 0x18
+#define ma_i2s_framesize__shift 0x03
+#define ma_i2s_framesize__reset 0x00
+//----------------------------------------------------------------i2s_ws_pol---
+// i2s ws polarity. 0 = low first
+#define ma_i2s_ws_pol__a 54
+#define ma_i2s_ws_pol__len 1
+#define ma_i2s_ws_pol__mask 0x02
+#define ma_i2s_ws_pol__shift 0x01
+#define ma_i2s_ws_pol__reset 0x00
+//-----------------------------------------------------------------i2s_order---
+// i2s word bit order. 0 = msb first
+#define ma_i2s_order__a 54
+#define ma_i2s_order__len 1
+#define ma_i2s_order__mask 0x04
+#define ma_i2s_order__shift 0x02
+#define ma_i2s_order__reset 0x00
+//------------------------------------------------------------i2s_rightfirst---
+// i2s l/r word order; 0 = left first
+#define ma_i2s_rightfirst__a 54
+#define ma_i2s_rightfirst__len 1
+#define ma_i2s_rightfirst__mask 0x20
+#define ma_i2s_rightfirst__shift 0x05
+#define ma_i2s_rightfirst__reset 0x00
+//-------------------------------------------------------------vol_db_master---
+// master volume db
+#define ma_vol_db_master__a 64
+#define ma_vol_db_master__len 8
+#define ma_vol_db_master__mask 0xff
+#define ma_vol_db_master__shift 0x00
+#define ma_vol_db_master__reset 0x18
+//------------------------------------------------------------vol_lsb_master---
+// master volume lsb 1/4 steps
+#define ma_vol_lsb_master__a 65
+#define ma_vol_lsb_master__len 2
+#define ma_vol_lsb_master__mask 0x03
+#define ma_vol_lsb_master__shift 0x00
+#define ma_vol_lsb_master__reset 0x00
+//----------------------------------------------------------------vol_db_ch0---
+// volume channel 0
+#define ma_vol_db_ch0__a 66
+#define ma_vol_db_ch0__len 8
+#define ma_vol_db_ch0__mask 0xff
+#define ma_vol_db_ch0__shift 0x00
+#define ma_vol_db_ch0__reset 0x18
+//----------------------------------------------------------------vol_db_ch1---
+// volume channel 1
+#define ma_vol_db_ch1__a 67
+#define ma_vol_db_ch1__len 8
+#define ma_vol_db_ch1__mask 0xff
+#define ma_vol_db_ch1__shift 0x00
+#define ma_vol_db_ch1__reset 0x18
+//----------------------------------------------------------------vol_db_ch2---
+// volume channel 2
+#define ma_vol_db_ch2__a 68
+#define ma_vol_db_ch2__len 8
+#define ma_vol_db_ch2__mask 0xff
+#define ma_vol_db_ch2__shift 0x00
+#define ma_vol_db_ch2__reset 0x18
+//----------------------------------------------------------------vol_db_ch3---
+// volume channel 3
+#define ma_vol_db_ch3__a 69
+#define ma_vol_db_ch3__len 8
+#define ma_vol_db_ch3__mask 0xff
+#define ma_vol_db_ch3__shift 0x00
+#define ma_vol_db_ch3__reset 0x18
+//---------------------------------------------------------------vol_lsb_ch0---
+// volume channel 1 - 1/4 steps
+#define ma_vol_lsb_ch0__a 70
+#define ma_vol_lsb_ch0__len 2
+#define ma_vol_lsb_ch0__mask 0x03
+#define ma_vol_lsb_ch0__shift 0x00
+#define ma_vol_lsb_ch0__reset 0x00
+//---------------------------------------------------------------vol_lsb_ch1---
+// volume channel 3 - 1/4 steps
+#define ma_vol_lsb_ch1__a 70
+#define ma_vol_lsb_ch1__len 2
+#define ma_vol_lsb_ch1__mask 0x0c
+#define ma_vol_lsb_ch1__shift 0x02
+#define ma_vol_lsb_ch1__reset 0x00
+//---------------------------------------------------------------vol_lsb_ch2---
+// volume channel 2 - 1/4 steps
+#define ma_vol_lsb_ch2__a 70
+#define ma_vol_lsb_ch2__len 2
+#define ma_vol_lsb_ch2__mask 0x30
+#define ma_vol_lsb_ch2__shift 0x04
+#define ma_vol_lsb_ch2__reset 0x00
+//---------------------------------------------------------------vol_lsb_ch3---
+// volume channel 3 - 1/4 steps
+#define ma_vol_lsb_ch3__a 70
+#define ma_vol_lsb_ch3__len 2
+#define ma_vol_lsb_ch3__mask 0xc0
+#define ma_vol_lsb_ch3__shift 0x06
+#define ma_vol_lsb_ch3__reset 0x00
+//----------------------------------------------------------------thr_db_ch0---
+// thr_db channel 0
+#define ma_thr_db_ch0__a 71
+#define ma_thr_db_ch0__len 8
+#define ma_thr_db_ch0__mask 0xff
+#define ma_thr_db_ch0__shift 0x00
+#define ma_thr_db_ch0__reset 0x18
+//----------------------------------------------------------------thr_db_ch1---
+// thr db ch1
+#define ma_thr_db_ch1__a 72
+#define ma_thr_db_ch1__len 8
+#define ma_thr_db_ch1__mask 0xff
+#define ma_thr_db_ch1__shift 0x00
+#define ma_thr_db_ch1__reset 0x18
+//----------------------------------------------------------------thr_db_ch2---
+// thr db ch2
+#define ma_thr_db_ch2__a 73
+#define ma_thr_db_ch2__len 8
+#define ma_thr_db_ch2__mask 0xff
+#define ma_thr_db_ch2__shift 0x00
+#define ma_thr_db_ch2__reset 0x18
+//----------------------------------------------------------------thr_db_ch3---
+// threshold db ch3
+#define ma_thr_db_ch3__a 74
+#define ma_thr_db_ch3__len 8
+#define ma_thr_db_ch3__mask 0xff
+#define ma_thr_db_ch3__shift 0x00
+#define ma_thr_db_ch3__reset 0x18
+//---------------------------------------------------------------thr_lsb_ch0---
+// thr lsb ch0
+#define ma_thr_lsb_ch0__a 75
+#define ma_thr_lsb_ch0__len 2
+#define ma_thr_lsb_ch0__mask 0x03
+#define ma_thr_lsb_ch0__shift 0x00
+#define ma_thr_lsb_ch0__reset 0x00
+//---------------------------------------------------------------thr_lsb_ch1---
+// thr lsb ch1
+#define ma_thr_lsb_ch1__a 75
+#define ma_thr_lsb_ch1__len 2
+#define ma_thr_lsb_ch1__mask 0x0c
+#define ma_thr_lsb_ch1__shift 0x02
+#define ma_thr_lsb_ch1__reset 0x00
+//---------------------------------------------------------------thr_lsb_ch2---
+// thr lsb ch2 1/4 db step
+#define ma_thr_lsb_ch2__a 75
+#define ma_thr_lsb_ch2__len 2
+#define ma_thr_lsb_ch2__mask 0x30
+#define ma_thr_lsb_ch2__shift 0x04
+#define ma_thr_lsb_ch2__reset 0x00
+//---------------------------------------------------------------thr_lsb_ch3---
+// threshold lsb ch3
+#define ma_thr_lsb_ch3__a 75
+#define ma_thr_lsb_ch3__len 2
+#define ma_thr_lsb_ch3__mask 0xc0
+#define ma_thr_lsb_ch3__shift 0x06
+#define ma_thr_lsb_ch3__reset 0x00
+//-----------------------------------------------------------dcu_mon0.pm_mon---
+// power mode monitor channel 0
+#define ma_dcu_mon0__pm_mon__a 96
+#define ma_dcu_mon0__pm_mon__len 2
+#define ma_dcu_mon0__pm_mon__mask 0x03
+#define ma_dcu_mon0__pm_mon__shift 0x00
+#define ma_dcu_mon0__pm_mon__reset 0x00
+//-----------------------------------------------------dcu_mon0.freqmode_mon---
+// frequence mode monitor channel 0
+#define ma_dcu_mon0__freqmode_mon__a 96
+#define ma_dcu_mon0__freqmode_mon__len 3
+#define ma_dcu_mon0__freqmode_mon__mask 0x70
+#define ma_dcu_mon0__freqmode_mon__shift 0x04
+#define ma_dcu_mon0__freqmode_mon__reset 0x00
+//-------------------------------------------------------dcu_mon0.pps_passed---
+// dcu0 pps completion indicator
+#define ma_dcu_mon0__pps_passed__a 96
+#define ma_dcu_mon0__pps_passed__len 1
+#define ma_dcu_mon0__pps_passed__mask 0x80
+#define ma_dcu_mon0__pps_passed__shift 0x07
+#define ma_dcu_mon0__pps_passed__reset 0x00
+//----------------------------------------------------------dcu_mon0.ocp_mon---
+// ocp monitor channel 0
+#define ma_dcu_mon0__ocp_mon__a 97
+#define ma_dcu_mon0__ocp_mon__len 1
+#define ma_dcu_mon0__ocp_mon__mask 0x01
+#define ma_dcu_mon0__ocp_mon__shift 0x00
+#define ma_dcu_mon0__ocp_mon__reset 0x00
+//--------------------------------------------------------dcu_mon0.vcfly1_ok---
+// cfly1 protection monitor channel 0.
+#define ma_dcu_mon0__vcfly1_ok__a 97
+#define ma_dcu_mon0__vcfly1_ok__len 1
+#define ma_dcu_mon0__vcfly1_ok__mask 0x02
+#define ma_dcu_mon0__vcfly1_ok__shift 0x01
+#define ma_dcu_mon0__vcfly1_ok__reset 0x00
+//--------------------------------------------------------dcu_mon0.vcfly2_ok---
+// cfly2 protection monitor channel 0.
+#define ma_dcu_mon0__vcfly2_ok__a 97
+#define ma_dcu_mon0__vcfly2_ok__len 1
+#define ma_dcu_mon0__vcfly2_ok__mask 0x04
+#define ma_dcu_mon0__vcfly2_ok__shift 0x02
+#define ma_dcu_mon0__vcfly2_ok__reset 0x00
+//----------------------------------------------------------dcu_mon0.pvdd_ok---
+// dcu0 pvdd monitor
+#define ma_dcu_mon0__pvdd_ok__a 97
+#define ma_dcu_mon0__pvdd_ok__len 1
+#define ma_dcu_mon0__pvdd_ok__mask 0x08
+#define ma_dcu_mon0__pvdd_ok__shift 0x03
+#define ma_dcu_mon0__pvdd_ok__reset 0x00
+//-----------------------------------------------------------dcu_mon0.vdd_ok---
+// dcu0 vdd monitor
+#define ma_dcu_mon0__vdd_ok__a 97
+#define ma_dcu_mon0__vdd_ok__len 1
+#define ma_dcu_mon0__vdd_ok__mask 0x10
+#define ma_dcu_mon0__vdd_ok__shift 0x04
+#define ma_dcu_mon0__vdd_ok__reset 0x00
+//-------------------------------------------------------------dcu_mon0.mute---
+// dcu0 mute monitor
+#define ma_dcu_mon0__mute__a 97
+#define ma_dcu_mon0__mute__len 1
+#define ma_dcu_mon0__mute__mask 0x20
+#define ma_dcu_mon0__mute__shift 0x05
+#define ma_dcu_mon0__mute__reset 0x00
+//------------------------------------------------------------dcu_mon0.m_mon---
+// m sense monitor channel 0
+#define ma_dcu_mon0__m_mon__a 98
+#define ma_dcu_mon0__m_mon__len 8
+#define ma_dcu_mon0__m_mon__mask 0xff
+#define ma_dcu_mon0__m_mon__shift 0x00
+#define ma_dcu_mon0__m_mon__reset 0x00
+//-----------------------------------------------------------dcu_mon1.pm_mon---
+// power mode monitor channel 1
+#define ma_dcu_mon1__pm_mon__a 100
+#define ma_dcu_mon1__pm_mon__len 2
+#define ma_dcu_mon1__pm_mon__mask 0x03
+#define ma_dcu_mon1__pm_mon__shift 0x00
+#define ma_dcu_mon1__pm_mon__reset 0x00
+//-----------------------------------------------------dcu_mon1.freqmode_mon---
+// frequence mode monitor channel 1
+#define ma_dcu_mon1__freqmode_mon__a 100
+#define ma_dcu_mon1__freqmode_mon__len 3
+#define ma_dcu_mon1__freqmode_mon__mask 0x70
+#define ma_dcu_mon1__freqmode_mon__shift 0x04
+#define ma_dcu_mon1__freqmode_mon__reset 0x00
+//-------------------------------------------------------dcu_mon1.pps_passed---
+// dcu1 pps completion indicator
+#define ma_dcu_mon1__pps_passed__a 100
+#define ma_dcu_mon1__pps_passed__len 1
+#define ma_dcu_mon1__pps_passed__mask 0x80
+#define ma_dcu_mon1__pps_passed__shift 0x07
+#define ma_dcu_mon1__pps_passed__reset 0x00
+//----------------------------------------------------------dcu_mon1.ocp_mon---
+// ocp monitor channel 1
+#define ma_dcu_mon1__ocp_mon__a 101
+#define ma_dcu_mon1__ocp_mon__len 1
+#define ma_dcu_mon1__ocp_mon__mask 0x01
+#define ma_dcu_mon1__ocp_mon__shift 0x00
+#define ma_dcu_mon1__ocp_mon__reset 0x00
+//--------------------------------------------------------dcu_mon1.vcfly1_ok---
+// cfly1 protcetion monitor channel 1
+#define ma_dcu_mon1__vcfly1_ok__a 101
+#define ma_dcu_mon1__vcfly1_ok__len 1
+#define ma_dcu_mon1__vcfly1_ok__mask 0x02
+#define ma_dcu_mon1__vcfly1_ok__shift 0x01
+#define ma_dcu_mon1__vcfly1_ok__reset 0x00
+//--------------------------------------------------------dcu_mon1.vcfly2_ok---
+// cfly2 protection monitor channel 1
+#define ma_dcu_mon1__vcfly2_ok__a 101
+#define ma_dcu_mon1__vcfly2_ok__len 1
+#define ma_dcu_mon1__vcfly2_ok__mask 0x04
+#define ma_dcu_mon1__vcfly2_ok__shift 0x02
+#define ma_dcu_mon1__vcfly2_ok__reset 0x00
+//----------------------------------------------------------dcu_mon1.pvdd_ok---
+// dcu1 pvdd monitor
+#define ma_dcu_mon1__pvdd_ok__a 101
+#define ma_dcu_mon1__pvdd_ok__len 1
+#define ma_dcu_mon1__pvdd_ok__mask 0x08
+#define ma_dcu_mon1__pvdd_ok__shift 0x03
+#define ma_dcu_mon1__pvdd_ok__reset 0x00
+//-----------------------------------------------------------dcu_mon1.vdd_ok---
+// dcu1 vdd monitor
+#define ma_dcu_mon1__vdd_ok__a 101
+#define ma_dcu_mon1__vdd_ok__len 1
+#define ma_dcu_mon1__vdd_ok__mask 0x10
+#define ma_dcu_mon1__vdd_ok__shift 0x04
+#define ma_dcu_mon1__vdd_ok__reset 0x00
+//-------------------------------------------------------------dcu_mon1.mute---
+// dcu1 mute monitor
+#define ma_dcu_mon1__mute__a 101
+#define ma_dcu_mon1__mute__len 1
+#define ma_dcu_mon1__mute__mask 0x20
+#define ma_dcu_mon1__mute__shift 0x05
+#define ma_dcu_mon1__mute__reset 0x00
+//------------------------------------------------------------dcu_mon1.m_mon---
+// m sense monitor channel 1
+#define ma_dcu_mon1__m_mon__a 102
+#define ma_dcu_mon1__m_mon__len 8
+#define ma_dcu_mon1__m_mon__mask 0xff
+#define ma_dcu_mon1__m_mon__shift 0x00
+#define ma_dcu_mon1__m_mon__reset 0x00
+//--------------------------------------------------------dcu_mon0.sw_enable---
+// dcu0 switch enable monitor
+#define ma_dcu_mon0__sw_enable__a 104
+#define ma_dcu_mon0__sw_enable__len 1
+#define ma_dcu_mon0__sw_enable__mask 0x40
+#define ma_dcu_mon0__sw_enable__shift 0x06
+#define ma_dcu_mon0__sw_enable__reset 0x00
+//--------------------------------------------------------dcu_mon1.sw_enable---
+// dcu1 switch enable monitor
+#define ma_dcu_mon1__sw_enable__a 104
+#define ma_dcu_mon1__sw_enable__len 1
+#define ma_dcu_mon1__sw_enable__mask 0x80
+#define ma_dcu_mon1__sw_enable__shift 0x07
+#define ma_dcu_mon1__sw_enable__reset 0x00
+//------------------------------------------------------------hvboot0_ok_mon---
+// hvboot0_ok for test/debug
+#define ma_hvboot0_ok_mon__a 105
+#define ma_hvboot0_ok_mon__len 1
+#define ma_hvboot0_ok_mon__mask 0x40
+#define ma_hvboot0_ok_mon__shift 0x06
+#define ma_hvboot0_ok_mon__reset 0x00
+//------------------------------------------------------------hvboot1_ok_mon---
+// hvboot1_ok for test/debug
+#define ma_hvboot1_ok_mon__a 105
+#define ma_hvboot1_ok_mon__len 1
+#define ma_hvboot1_ok_mon__mask 0x80
+#define ma_hvboot1_ok_mon__shift 0x07
+#define ma_hvboot1_ok_mon__reset 0x00
+//-----------------------------------------------------------------error_acc---
+// accumulated errors,  at and after triggering
+#define ma_error_acc__a 109
+#define ma_error_acc__len 8
+#define ma_error_acc__mask 0xff
+#define ma_error_acc__shift 0x00
+#define ma_error_acc__reset 0x00
+//-------------------------------------------------------------i2s_data_rate---
+// detected i2s data rate: 00/01/10 = x1/x2/x4
+#define ma_i2s_data_rate__a 116
+#define ma_i2s_data_rate__len 2
+#define ma_i2s_data_rate__mask 0x03
+#define ma_i2s_data_rate__shift 0x00
+#define ma_i2s_data_rate__reset 0x00
+//---------------------------------------------------------audio_in_mode_mon---
+// audio input mode monitor
+#define ma_audio_in_mode_mon__a 116
+#define ma_audio_in_mode_mon__len 3
+#define ma_audio_in_mode_mon__mask 0x1c
+#define ma_audio_in_mode_mon__shift 0x02
+#define ma_audio_in_mode_mon__reset 0x00
+//------------------------------------------------------------------msel_mon---
+// msel[2:0] monitor register
+#define ma_msel_mon__a 117
+#define ma_msel_mon__len 3
+#define ma_msel_mon__mask 0x07
+#define ma_msel_mon__shift 0x00
+#define ma_msel_mon__reset 0x00
+//---------------------------------------------------------------------error---
+// current error flag monitor reg - for app. ctrl.
+#define ma_error__a 124
+#define ma_error__len 8
+#define ma_error__mask 0xff
+#define ma_error__shift 0x00
+#define ma_error__reset 0x00
+//----------------------------------------------------audio_proc_limiter_mon---
+// b7-b4: channel 3-0 limiter active
+#define ma_audio_proc_limiter_mon__a 126
+#define ma_audio_proc_limiter_mon__len 4
+#define ma_audio_proc_limiter_mon__mask 0xf0
+#define ma_audio_proc_limiter_mon__shift 0x04
+#define ma_audio_proc_limiter_mon__reset 0x00
+//-------------------------------------------------------audio_proc_clip_mon---
+// b3-b0: channel 3-0 clipping monitor
+#define ma_audio_proc_clip_mon__a 126
+#define ma_audio_proc_clip_mon__len 4
+#define ma_audio_proc_clip_mon__mask 0x0f
+#define ma_audio_proc_clip_mon__shift 0x00
+#define ma_audio_proc_clip_mon__reset 0x00
+#endif
+
+#define SOC_ENUM_ERR(xname, xenum)\
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname),\
+	.access = SNDRV_CTL_ELEM_ACCESS_READ,\
+	.info = snd_soc_info_enum_double,\
+	.get = snd_soc_get_enum_double, .put = snd_soc_put_enum_double,\
+	.private_value = (unsigned long)&(xenum) }
+
+static struct i2c_client *i2c;
+
+struct ma120x0p_priv {
+	struct regmap *regmap;
+	int mclk_div;
+	struct snd_soc_component *component;
+	struct gpio_desc *enable_gpio;
+	struct gpio_desc *mute_gpio;
+	struct gpio_desc *booster_gpio;
+	struct gpio_desc *error_gpio;
+};
+
+static struct ma120x0p_priv *priv_data;
+
+//Used to share the IRQ number within this file
+static unsigned int irqNumber;
+
+// Function prototype for the custom IRQ handler function
+static irqreturn_t ma120x0p_irq_handler(int irq, void *data);
+
+//Alsa Controls
+static const char * const limenable_text[] = {"Bypassed", "Enabled"};
+static const char * const limatack_text[] = {"Slow", "Normal", "Fast"};
+static const char * const limrelease_text[] = {"Slow", "Normal", "Fast"};
+
+static const char * const err_flycap_text[] = {"Ok", "Error"};
+static const char * const err_overcurr_text[] = {"Ok", "Error"};
+static const char * const err_pllerr_text[] = {"Ok", "Error"};
+static const char * const err_pvddunder_text[] = {"Ok", "Error"};
+static const char * const err_overtempw_text[] = {"Ok", "Error"};
+static const char * const err_overtempe_text[] = {"Ok", "Error"};
+static const char * const err_pinlowimp_text[] = {"Ok", "Error"};
+static const char * const err_dcprot_text[] = {"Ok", "Error"};
+
+static const char * const pwr_mode_prof_text[] = {"PMF0", "PMF1", "PMF2",
+"PMF3", "PMF4"};
+
+static const struct soc_enum lim_enable_ctrl =
+	SOC_ENUM_SINGLE(ma_audio_proc_limiterenable__a,
+		ma_audio_proc_limiterenable__shift,
+		ma_audio_proc_limiterenable__len + 1,
+		limenable_text);
+static const struct soc_enum limatack_ctrl =
+	SOC_ENUM_SINGLE(ma_audio_proc_attack__a,
+		ma_audio_proc_attack__shift,
+		ma_audio_proc_attack__len + 1,
+		limatack_text);
+static const struct soc_enum limrelease_ctrl =
+	SOC_ENUM_SINGLE(ma_audio_proc_release__a,
+		ma_audio_proc_release__shift,
+		ma_audio_proc_release__len + 1,
+		limrelease_text);
+static const struct soc_enum err_flycap_ctrl =
+	SOC_ENUM_SINGLE(ma_error__a, 0, 3, err_flycap_text);
+static const struct soc_enum err_overcurr_ctrl =
+	SOC_ENUM_SINGLE(ma_error__a, 1, 3, err_overcurr_text);
+static const struct soc_enum err_pllerr_ctrl =
+	SOC_ENUM_SINGLE(ma_error__a, 2, 3, err_pllerr_text);
+static const struct soc_enum err_pvddunder_ctrl =
+	SOC_ENUM_SINGLE(ma_error__a, 3, 3, err_pvddunder_text);
+static const struct soc_enum err_overtempw_ctrl =
+	SOC_ENUM_SINGLE(ma_error__a, 4, 3, err_overtempw_text);
+static const struct soc_enum err_overtempe_ctrl =
+	SOC_ENUM_SINGLE(ma_error__a, 5, 3, err_overtempe_text);
+static const struct soc_enum err_pinlowimp_ctrl =
+	SOC_ENUM_SINGLE(ma_error__a, 6, 3, err_pinlowimp_text);
+static const struct soc_enum err_dcprot_ctrl =
+	SOC_ENUM_SINGLE(ma_error__a, 7, 3, err_dcprot_text);
+static const struct soc_enum pwr_mode_prof_ctrl =
+	SOC_ENUM_SINGLE(ma_pmprofile__a, ma_pmprofile__shift, 5,
+		pwr_mode_prof_text);
+
+static const char * const pwr_mode_texts[] = {
+		"Dynamic power mode",
+		"Power mode 1",
+		"Power mode 2",
+		"Power mode 3",
+	};
+
+static const int pwr_mode_values[] = {
+		0x10,
+		0x50,
+		0x60,
+		0x70,
+	};
+
+static SOC_VALUE_ENUM_SINGLE_DECL(pwr_mode_ctrl,
+	ma_pm_man__a, 0, 0x70,
+	pwr_mode_texts,
+	pwr_mode_values);
+
+static const DECLARE_TLV_DB_SCALE(ma120x0p_vol_tlv, -14400, 100,  0);
+static const DECLARE_TLV_DB_SCALE(ma120x0p_lim_tlv, -5000, 100,  0);
+static const DECLARE_TLV_DB_SCALE(ma120x0p_lr_tlv, -5000, 100,  0);
+
+static const struct snd_kcontrol_new ma120x0p_snd_controls[] = {
+	//Master Volume
+	SOC_SINGLE_RANGE_TLV("A.Mstr Vol Volume",
+		ma_vol_db_master__a, 0, 0x18, 0xa8, 1, ma120x0p_vol_tlv),
+
+	//L-R Volume ch0
+	SOC_SINGLE_RANGE_TLV("B.L Vol Volume",
+		ma_vol_db_ch0__a, 0, 0x18, 0x4a, 1, ma120x0p_lr_tlv),
+	SOC_SINGLE_RANGE_TLV("C.R Vol Volume",
+		ma_vol_db_ch1__a, 0, 0x18, 0x4a, 1, ma120x0p_lr_tlv),
+
+	//L-R Limiter Threshold ch0-ch1
+	SOC_DOUBLE_R_RANGE_TLV("D.Lim thresh Volume",
+		ma_thr_db_ch0__a, ma_thr_db_ch1__a, 0, 0x0e, 0x4a, 1,
+		ma120x0p_lim_tlv),
+
+	//Enum Switches/Selectors
+	//SOC_ENUM("E.AudioProc Mute", audioproc_mute_ctrl),
+	SOC_ENUM("F.Limiter Enable", lim_enable_ctrl),
+	SOC_ENUM("G.Limiter Attck", limatack_ctrl),
+	SOC_ENUM("H.Limiter Rls", limrelease_ctrl),
+
+	//Enum Error Monitor (read-only)
+	SOC_ENUM_ERR("I.Err flycap", err_flycap_ctrl),
+	SOC_ENUM_ERR("J.Err overcurr", err_overcurr_ctrl),
+	SOC_ENUM_ERR("K.Err pllerr", err_pllerr_ctrl),
+	SOC_ENUM_ERR("L.Err pvddunder", err_pvddunder_ctrl),
+	SOC_ENUM_ERR("M.Err overtempw", err_overtempw_ctrl),
+	SOC_ENUM_ERR("N.Err overtempe", err_overtempe_ctrl),
+	SOC_ENUM_ERR("O.Err pinlowimp", err_pinlowimp_ctrl),
+	SOC_ENUM_ERR("P.Err dcprot", err_dcprot_ctrl),
+
+	//Power modes profiles
+	SOC_ENUM("Q.PM Prof", pwr_mode_prof_ctrl),
+
+	// Power mode selection (Dynamic,1,2,3)
+	SOC_ENUM("R.Power Mode", pwr_mode_ctrl),
+};
+
+//Machine Driver
+static int ma120x0p_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+	u16 blen = 0x00;
+
+	struct snd_soc_component *component = dai->component;
+
+	priv_data->component = component;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		blen = 0x10;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		blen = 0x00;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		blen = 0x00;
+		break;
+	default:
+		dev_err(dai->dev, "Unsupported word length: %u\n",
+		params_format(params));
+		return -EINVAL;
+	}
+
+	// set word length
+	snd_soc_component_update_bits(component, ma_i2s_framesize__a,
+		ma_i2s_framesize__mask, blen);
+
+	return 0;
+}
+
+static int ma120x0p_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
+{
+	int val = 0;
+
+	struct ma120x0p_priv *ma120x0p;
+
+	struct snd_soc_component *component = dai->component;
+
+	ma120x0p = snd_soc_component_get_drvdata(component);
+
+	if (mute)
+		val = 0;
+	else
+		val = 1;
+
+	gpiod_set_value_cansleep(priv_data->mute_gpio, val);
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops ma120x0p_dai_ops = {
+	.hw_params		=	ma120x0p_hw_params,
+	.mute_stream	=	ma120x0p_mute_stream,
+};
+
+static struct snd_soc_dai_driver ma120x0p_dai = {
+	.name		= "ma120x0p-amp",
+	.playback	=	{
+		.stream_name	= "Playback",
+		.channels_min	= 2,
+		.channels_max	= 2,
+		.rates = SNDRV_PCM_RATE_CONTINUOUS,
+		.rate_min = 44100,
+		.rate_max = 192000,
+		.formats = SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE
+	},
+	.ops        = &ma120x0p_dai_ops,
+};
+
+//Codec Driver
+static int ma120x0p_clear_err(struct snd_soc_component *component)
+{
+	int ret = 0;
+
+	struct ma120x0p_priv *ma120x0p;
+
+	ma120x0p = snd_soc_component_get_drvdata(component);
+
+	ret = snd_soc_component_update_bits(component,
+		ma_eh_clear__a, ma_eh_clear__mask, 0x00);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_component_update_bits(component,
+		ma_eh_clear__a, ma_eh_clear__mask, 0x04);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_component_update_bits(component,
+		ma_eh_clear__a, ma_eh_clear__mask, 0x00);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static void ma120x0p_remove(struct snd_soc_component *component)
+{
+	struct ma120x0p_priv *ma120x0p;
+
+	ma120x0p = snd_soc_component_get_drvdata(component);
+}
+
+static int ma120x0p_probe(struct snd_soc_component *component)
+{
+	struct ma120x0p_priv *ma120x0p;
+
+	int ret = 0;
+
+	i2c = container_of(component->dev, struct i2c_client, dev);
+
+	ma120x0p = snd_soc_component_get_drvdata(component);
+
+	//Reset error
+	ma120x0p_clear_err(component);
+	if (ret < 0)
+		return ret;
+
+	// set serial audio format I2S and enable audio processor
+	ret = snd_soc_component_write(component, ma_i2s_format__a, 0x08);
+	if (ret < 0)
+		return ret;
+
+	// Enable audio limiter
+	ret = snd_soc_component_update_bits(component,
+		ma_audio_proc_limiterenable__a,
+		ma_audio_proc_limiterenable__mask, 0x40);
+	if (ret < 0)
+		return ret;
+
+	// Set lim attack to fast
+	ret = snd_soc_component_update_bits(component,
+		ma_audio_proc_attack__a, ma_audio_proc_attack__mask, 0x80);
+	if (ret < 0)
+		return ret;
+
+	// Set lim attack to low
+	ret = snd_soc_component_update_bits(component,
+		ma_audio_proc_release__a, ma_audio_proc_release__mask, 0x00);
+	if (ret < 0)
+		return ret;
+
+	// set volume to 0dB
+	ret = snd_soc_component_write(component, ma_vol_db_master__a, 0x18);
+	if (ret < 0)
+		return ret;
+
+	// set ch0 lim thresh to -15dB
+	ret = snd_soc_component_write(component, ma_thr_db_ch0__a, 0x27);
+	if (ret < 0)
+		return ret;
+
+	// set ch1 lim thresh to -15dB
+	ret = snd_soc_component_write(component, ma_thr_db_ch1__a, 0x27);
+	if (ret < 0)
+		return ret;
+
+	//Check for errors
+	ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x00, 0);
+	if (ret < 0)
+		return ret;
+	ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x01, 0);
+	if (ret < 0)
+		return ret;
+	ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x02, 0);
+	if (ret < 0)
+		return ret;
+	ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x08, 0);
+	if (ret < 0)
+		return ret;
+	ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x10, 0);
+	if (ret < 0)
+		return ret;
+	ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x20, 0);
+	if (ret < 0)
+		return ret;
+	ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x40, 0);
+	if (ret < 0)
+		return ret;
+	ret = snd_soc_component_test_bits(component, ma_error_acc__a, 0x80, 0);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int ma120x0p_set_bias_level(struct snd_soc_component *component,
+	enum snd_soc_bias_level level)
+{
+	int ret = 0;
+
+	struct ma120x0p_priv *ma120x0p;
+
+	ma120x0p = snd_soc_component_get_drvdata(component);
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+
+	case SND_SOC_BIAS_PREPARE:
+		break;
+
+	case SND_SOC_BIAS_STANDBY:
+		ret = gpiod_get_value_cansleep(priv_data->enable_gpio);
+		if (ret != 0) {
+			dev_err(component->dev, "Device ma120x0p disabled in STANDBY BIAS: %d\n",
+			ret);
+			return ret;
+		}
+		break;
+
+	case SND_SOC_BIAS_OFF:
+		break;
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget ma120x0p_dapm_widgets[] = {
+	SND_SOC_DAPM_OUTPUT("OUT_A"),
+	SND_SOC_DAPM_OUTPUT("OUT_B"),
+};
+
+static const struct snd_soc_dapm_route ma120x0p_dapm_routes[] = {
+	{ "OUT_B",  NULL, "Playback" },
+	{ "OUT_A",  NULL, "Playback" },
+};
+
+static const struct snd_soc_component_driver ma120x0p_component_driver = {
+	.probe = ma120x0p_probe,
+	.remove = ma120x0p_remove,
+	.set_bias_level = ma120x0p_set_bias_level,
+	.dapm_widgets		= ma120x0p_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(ma120x0p_dapm_widgets),
+	.dapm_routes		= ma120x0p_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(ma120x0p_dapm_routes),
+	.controls = ma120x0p_snd_controls,
+	.num_controls = ARRAY_SIZE(ma120x0p_snd_controls),
+	.use_pmdown_time	= 1,
+	.endianness		= 1,
+};
+
+//I2C Driver
+static const struct reg_default ma120x0p_reg_defaults[] = {
+	{	0x01,	0x3c	},
+};
+
+static bool ma120x0p_reg_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case ma_error__a:
+			return true;
+	default:
+			return false;
+	}
+}
+
+static const struct of_device_id ma120x0p_of_match[] = {
+	{ .compatible = "ma,ma120x0p", },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(of, ma120x0p_of_match);
+
+static struct regmap_config ma120x0p_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+
+	.max_register = 255,
+	.volatile_reg = ma120x0p_reg_volatile,
+
+	.cache_type = REGCACHE_RBTREE,
+	.reg_defaults = ma120x0p_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(ma120x0p_reg_defaults),
+};
+
+static int ma120x0p_i2c_probe(struct i2c_client *i2c)
+{
+	int ret;
+
+	priv_data = devm_kzalloc(&i2c->dev, sizeof(*priv_data), GFP_KERNEL);
+	if (!priv_data)
+		return -ENOMEM;
+	i2c_set_clientdata(i2c, priv_data);
+
+	priv_data->regmap = devm_regmap_init_i2c(i2c, &ma120x0p_regmap_config);
+	if (IS_ERR(priv_data->regmap)) {
+		ret = PTR_ERR(priv_data->regmap);
+		return ret;
+	}
+
+	//Startup sequence
+
+	//Make sure the device is muted
+	priv_data->mute_gpio = devm_gpiod_get_optional(&i2c->dev, "mute_gp",
+		GPIOD_OUT_LOW);
+	if (IS_ERR(priv_data->mute_gpio)) {
+		ret = PTR_ERR(priv_data->mute_gpio);
+		dev_err(&i2c->dev, "Failed to get mute gpio line: %d\n", ret);
+		return ret;
+	}
+	msleep(50);
+
+// MA120xx0P devices are usually powered by an integrated boost converter.
+// An option GPIO control line is provided to enable the booster properly and
+// in sync with the enable and mute GPIO lines.
+	priv_data->booster_gpio = devm_gpiod_get_optional(&i2c->dev,
+		"booster_gp", GPIOD_OUT_LOW);
+	if (IS_ERR(priv_data->booster_gpio)) {
+		ret = PTR_ERR(priv_data->booster_gpio);
+		dev_err(&i2c->dev,
+		"Failed to get booster enable gpio line: %d\n", ret);
+		return ret;
+	}
+	msleep(50);
+
+	//Enable booster and wait 200ms until stable PVDD
+	gpiod_set_value_cansleep(priv_data->booster_gpio, 1);
+	msleep(200);
+
+	//Enable ma120x0pp
+	priv_data->enable_gpio = devm_gpiod_get_optional(&i2c->dev,
+		"enable_gp", GPIOD_OUT_LOW);
+	if (IS_ERR(priv_data->enable_gpio)) {
+		ret = PTR_ERR(priv_data->enable_gpio);
+		dev_err(&i2c->dev,
+		"Failed to get ma120x0p enable gpio line: %d\n", ret);
+		return ret;
+	}
+	msleep(50);
+
+	//Optional use of ma120x0pp error line as an interrupt trigger to
+	//platform GPIO.
+	//Get error input gpio ma120x0p
+	priv_data->error_gpio = devm_gpiod_get_optional(&i2c->dev,
+		 "error_gp", GPIOD_IN);
+	if (IS_ERR(priv_data->error_gpio)) {
+		ret = PTR_ERR(priv_data->error_gpio);
+		dev_err(&i2c->dev,
+			"Failed to get ma120x0p error gpio line: %d\n", ret);
+		return ret;
+	}
+
+	if (priv_data->error_gpio != NULL) {
+		irqNumber = gpiod_to_irq(priv_data->error_gpio);
+
+		ret = devm_request_threaded_irq(&i2c->dev,
+			 irqNumber, ma120x0p_irq_handler,
+			 NULL, IRQF_TRIGGER_FALLING,
+			 "ma120x0p", priv_data);
+		if (ret != 0)
+			dev_warn(&i2c->dev, "Failed to request IRQ: %d\n",
+				ret);
+	}
+
+	ret = devm_snd_soc_register_component(&i2c->dev,
+		&ma120x0p_component_driver, &ma120x0p_dai, 1);
+
+	return ret;
+}
+
+static irqreturn_t ma120x0p_irq_handler(int irq, void *data)
+{
+	gpiod_set_value_cansleep(priv_data->mute_gpio, 0);
+	gpiod_set_value_cansleep(priv_data->enable_gpio, 1);
+	return IRQ_HANDLED;
+}
+
+static void ma120x0p_i2c_remove(struct i2c_client *i2c)
+{
+	snd_soc_unregister_component(&i2c->dev);
+	i2c_set_clientdata(i2c, NULL);
+
+	gpiod_set_value_cansleep(priv_data->mute_gpio, 0);
+	msleep(30);
+	gpiod_set_value_cansleep(priv_data->enable_gpio, 1);
+	msleep(200);
+	gpiod_set_value_cansleep(priv_data->booster_gpio, 0);
+	msleep(200);
+
+	kfree(priv_data);
+}
+
+static void ma120x0p_i2c_shutdown(struct i2c_client *i2c)
+{
+	snd_soc_unregister_component(&i2c->dev);
+	i2c_set_clientdata(i2c, NULL);
+
+	gpiod_set_value_cansleep(priv_data->mute_gpio, 0);
+	msleep(30);
+	gpiod_set_value_cansleep(priv_data->enable_gpio, 1);
+	msleep(200);
+	gpiod_set_value_cansleep(priv_data->booster_gpio, 0);
+	msleep(200);
+
+	kfree(priv_data);
+}
+
+static const struct i2c_device_id ma120x0p_i2c_id[] = {
+	{ "ma120x0p", 0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(i2c, ma120x0p_i2c_id);
+
+static struct i2c_driver ma120x0p_i2c_driver = {
+	.driver = {
+		.name = "ma120x0p",
+		.owner = THIS_MODULE,
+		.of_match_table = ma120x0p_of_match,
+	},
+	.probe = ma120x0p_i2c_probe,
+	.remove = ma120x0p_i2c_remove,
+	.shutdown = ma120x0p_i2c_shutdown,
+	.id_table = ma120x0p_i2c_id
+};
+
+static int __init ma120x0p_modinit(void)
+{
+	int ret = 0;
+
+	ret = i2c_add_driver(&ma120x0p_i2c_driver);
+	if (ret != 0) {
+		pr_err("Failed to register MA120X0P I2C driver: %d\n", ret);
+		return ret;
+	}
+	return ret;
+}
+module_init(ma120x0p_modinit);
+
+static void __exit ma120x0p_exit(void)
+{
+	i2c_del_driver(&ma120x0p_i2c_driver);
+}
+module_exit(ma120x0p_exit);
+
+MODULE_AUTHOR("Ariel Muszkat ariel.muszkat@gmail.com>");
+MODULE_DESCRIPTION("ASoC driver for ma120x0p");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/pcm1794a.c b/sound/soc/codecs/pcm1794a.c
new file mode 100644
index 000000000000..3877a138f896
--- /dev/null
+++ b/sound/soc/codecs/pcm1794a.c
@@ -0,0 +1,68 @@
+/*
+ * Driver for the PCM1794A codec
+ *
+ * Author:	Florian Meier <florian.meier@koalo.de>
+ *		Copyright 2013
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/soc.h>
+
+static struct snd_soc_dai_driver pcm1794a_dai = {
+	.name = "pcm1794a-hifi",
+	.playback = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_8000_192000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE |
+			   SNDRV_PCM_FMTBIT_S24_LE
+	},
+};
+
+static struct snd_soc_component_driver soc_component_dev_pcm1794a;
+
+static int pcm1794a_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_component(&pdev->dev, &soc_component_dev_pcm1794a,
+			&pcm1794a_dai, 1);
+}
+
+static void pcm1794a_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_component(&pdev->dev);
+}
+
+static const struct of_device_id pcm1794a_of_match[] = {
+	{ .compatible = "ti,pcm1794a", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, pcm1794a_of_match);
+
+static struct platform_driver pcm1794a_component_driver = {
+	.probe 		= pcm1794a_probe,
+	.remove 	= pcm1794a_remove,
+	.driver		= {
+		.name	= "pcm1794a-codec",
+		.owner	= THIS_MODULE,
+		.of_match_table = of_match_ptr(pcm1794a_of_match),
+	},
+};
+
+module_platform_driver(pcm1794a_component_driver);
+
+MODULE_DESCRIPTION("ASoC PCM1794A codec driver");
+MODULE_AUTHOR("Florian Meier <florian.meier@koalo.de>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/pcm3168a.c b/sound/soc/codecs/pcm3168a.c
index 6cbb8d0535b0..173dfe918e6f 100644
--- a/sound/soc/codecs/pcm3168a.c
+++ b/sound/soc/codecs/pcm3168a.c
@@ -60,6 +60,7 @@ struct pcm3168a_priv {
 	struct clk *scki;
 	struct gpio_desc *gpio_rst;
 	unsigned long sysclk;
+	bool adc_fc, dac_fc; // Force clock consumer mode
 
 	struct pcm3168a_io_params io_params[2];
 	struct snd_soc_dai_driver dai_drv[2];
@@ -478,6 +479,12 @@ static int pcm3168a_hw_params(struct snd_pcm_substream *substream,
 		ms = 0;
 	}
 
+	// Force clock consumer mode if needed
+	if (pcm3168a->adc_fc && dai->id == PCM3168A_DAI_ADC)
+		ms = 0;
+	if (pcm3168a->dac_fc && dai->id == PCM3168A_DAI_DAC)
+		ms = 0;
+
 	format = io_params->format;
 
 	if (io_params->slot_width)
@@ -756,6 +763,11 @@ int pcm3168a_probe(struct device *dev, struct regmap *regmap)
 
 	pcm3168a->sysclk = clk_get_rate(pcm3168a->scki);
 
+	pcm3168a->adc_fc = of_property_read_bool(dev->of_node,
+		"adc-force-cons");
+	pcm3168a->dac_fc = of_property_read_bool(dev->of_node,
+		"dac-force-cons");
+
 	for (i = 0; i < ARRAY_SIZE(pcm3168a->supplies); i++)
 		pcm3168a->supplies[i].supply = pcm3168a_supply_names[i];
 
diff --git a/sound/soc/codecs/pcm512x.c b/sound/soc/codecs/pcm512x.c
index aa8edf87b743..c653998497f3 100644
--- a/sound/soc/codecs/pcm512x.c
+++ b/sound/soc/codecs/pcm512x.c
@@ -537,7 +537,7 @@ static unsigned long pcm512x_ncp_target(struct pcm512x_priv *pcm512x,
 
 static const u32 pcm512x_dai_rates[] = {
 	8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000,
-	88200, 96000, 176400, 192000, 384000,
+	88200, 96000, 176400, 192000, 352800, 384000,
 };
 
 static const struct snd_pcm_hw_constraint_list constraints_slave = {
@@ -630,7 +630,7 @@ static int pcm512x_dai_startup_slave(struct snd_pcm_substream *substream,
 	struct regmap *regmap = pcm512x->regmap;
 
 	if (IS_ERR(pcm512x->sclk)) {
-		dev_info(dev, "No SCLK, using BCLK: %ld\n",
+		dev_dbg(dev, "No SCLK, using BCLK: %ld\n",
 			 PTR_ERR(pcm512x->sclk));
 
 		/* Disable reporting of missing SCLK as an error */
diff --git a/sound/soc/codecs/tas5713.c b/sound/soc/codecs/tas5713.c
new file mode 100644
index 000000000000..01dd21a1d7a2
--- /dev/null
+++ b/sound/soc/codecs/tas5713.c
@@ -0,0 +1,360 @@
+/*
+ * ASoC Driver for TAS5713
+ *
+ * Author:	Sebastian Eickhoff <basti.eickhoff@googlemail.com>
+ *		Copyright 2014
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/of_device.h>
+#include <linux/spi/spi.h>
+#include <linux/regmap.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.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/fs.h>
+#include <asm/uaccess.h>
+
+#include "tas5713.h"
+
+
+static struct i2c_client *i2c;
+
+struct tas5713_priv {
+	struct regmap *regmap;
+	int mclk_div;
+	struct snd_soc_component *component;
+};
+
+static struct tas5713_priv *priv_data;
+
+
+
+
+/*
+ *    _   _    ___   _      ___         _           _
+ *   /_\ | |  / __| /_\    / __|___ _ _| |_ _ _ ___| |___
+ *  / _ \| |__\__ \/ _ \  | (__/ _ \ ' \  _| '_/ _ \ (_-<
+ * /_/ \_\____|___/_/ \_\  \___\___/_||_\__|_| \___/_/__/
+ *
+ */
+
+static const DECLARE_TLV_DB_SCALE(tas5713_vol_tlv, -10000, 50, 1);
+
+
+static const struct snd_kcontrol_new tas5713_snd_controls[] = {
+	SOC_SINGLE_TLV  ("Master"    , TAS5713_VOL_MASTER, 0, 248, 1, tas5713_vol_tlv),
+	SOC_DOUBLE_R_TLV("Channels"  , TAS5713_VOL_CH1, TAS5713_VOL_CH2, 0, 248, 1, tas5713_vol_tlv)
+};
+
+
+
+
+/*
+ *  __  __         _    _            ___      _
+ * |  \/  |__ _ __| |_ (_)_ _  ___  |   \ _ _(_)_ _____ _ _
+ * | |\/| / _` / _| ' \| | ' \/ -_) | |) | '_| \ V / -_) '_|
+ * |_|  |_\__,_\__|_||_|_|_||_\___| |___/|_| |_|\_/\___|_|
+ *
+ */
+
+static int tas5713_hw_params(struct snd_pcm_substream *substream,
+			    struct snd_pcm_hw_params *params,
+			    struct snd_soc_dai *dai)
+{
+	u16 blen = 0x00;
+
+	struct snd_soc_component *component = dai->component;
+	priv_data->component = component;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		blen = 0x03;
+		break;
+	case SNDRV_PCM_FORMAT_S20_3LE:
+		blen = 0x1;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		blen = 0x04;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		blen = 0x05;
+		break;
+	default:
+		dev_err(dai->dev, "Unsupported word length: %u\n",
+			params_format(params));
+		return -EINVAL;
+	}
+
+	// set word length
+	snd_soc_component_update_bits(component, TAS5713_SERIAL_DATA_INTERFACE, 0x7, blen);
+
+	return 0;
+}
+
+
+static int tas5713_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
+{
+	unsigned int val = 0;
+
+	struct tas5713_priv *tas5713;
+	struct snd_soc_component *component = dai->component;
+	tas5713 = snd_soc_component_get_drvdata(component);
+
+	if (mute) {
+		val = TAS5713_SOFT_MUTE_ALL;
+	}
+
+	return regmap_write(tas5713->regmap, TAS5713_SOFT_MUTE, val);
+}
+
+
+static const struct snd_soc_dai_ops tas5713_dai_ops = {
+	.hw_params 		= tas5713_hw_params,
+	.mute_stream	= tas5713_mute_stream,
+};
+
+
+static struct snd_soc_dai_driver tas5713_dai = {
+	.name		= "tas5713-hifi",
+	.playback 	= {
+		.stream_name	= "Playback",
+		.channels_min	= 2,
+		.channels_max	= 2,
+		.rates		    = SNDRV_PCM_RATE_8000_48000,
+		.formats	    = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE ),
+	},
+	.ops        = &tas5713_dai_ops,
+};
+
+
+
+
+/*
+ *   ___         _          ___      _
+ *  / __|___  __| |___ __  |   \ _ _(_)_ _____ _ _
+ * | (__/ _ \/ _` / -_) _| | |) | '_| \ V / -_) '_|
+ *  \___\___/\__,_\___\__| |___/|_| |_|\_/\___|_|
+ *
+ */
+
+static void tas5713_remove(struct snd_soc_component *component)
+{
+	struct tas5713_priv *tas5713;
+
+	tas5713 = snd_soc_component_get_drvdata(component);
+}
+
+
+static int tas5713_probe(struct snd_soc_component *component)
+{
+	struct tas5713_priv *tas5713;
+	int i, ret;
+
+	i2c = container_of(component->dev, struct i2c_client, dev);
+
+	tas5713 = snd_soc_component_get_drvdata(component);
+
+	// Reset error
+	ret = snd_soc_component_write(component, TAS5713_ERROR_STATUS, 0x00);
+	if (ret < 0) return ret;
+
+	// Trim oscillator
+	ret = snd_soc_component_write(component, TAS5713_OSC_TRIM, 0x00);
+	if (ret < 0) return ret;
+	msleep(1000);
+
+	// Reset error
+	ret = snd_soc_component_write(component, TAS5713_ERROR_STATUS, 0x00);
+	if (ret < 0) return ret;
+
+	// I2S 24bit
+	ret = snd_soc_component_write(component, TAS5713_SERIAL_DATA_INTERFACE, 0x05);
+	if (ret < 0) return ret;
+
+	// Unmute
+	ret = snd_soc_component_write(component, TAS5713_SYSTEM_CTRL2, 0x00);
+	if (ret < 0) return ret;
+	ret = snd_soc_component_write(component, TAS5713_SOFT_MUTE, 0x00);
+	if (ret < 0) return ret;
+
+	// Set volume to 0db
+	ret = snd_soc_component_write(component, TAS5713_VOL_MASTER, 0x00);
+	if (ret < 0) return ret;
+
+	// Now start programming the default initialization sequence
+	for (i = 0; i < ARRAY_SIZE(tas5713_init_sequence); ++i) {
+		ret = i2c_master_send(i2c,
+				     tas5713_init_sequence[i].data,
+				     tas5713_init_sequence[i].size);
+		if (ret < 0) {
+			printk(KERN_INFO "TAS5713 CODEC PROBE: InitSeq returns: %d\n", ret);
+		}
+	}
+
+	// Unmute
+	ret = snd_soc_component_write(component, TAS5713_SYSTEM_CTRL2, 0x00);
+	if (ret < 0) return ret;
+
+	return 0;
+}
+
+
+static struct snd_soc_component_driver soc_codec_dev_tas5713 = {
+	.probe = tas5713_probe,
+	.remove = tas5713_remove,
+	.controls = tas5713_snd_controls,
+	.num_controls = ARRAY_SIZE(tas5713_snd_controls),
+};
+
+
+
+
+/*
+ *   ___ ___ ___   ___      _
+ *  |_ _|_  ) __| |   \ _ _(_)_ _____ _ _
+ *   | | / / (__  | |) | '_| \ V / -_) '_|
+ *  |___/___\___| |___/|_| |_|\_/\___|_|
+ *
+ */
+
+static const struct reg_default tas5713_reg_defaults[] = {
+	{ 0x07 ,0x80 },     // R7  - VOL_MASTER    - -40dB
+	{ 0x08 ,  30 },     // R8  - VOL_CH1	   -   0dB
+	{ 0x09 ,  30 },     // R9  - VOL_CH2       -   0dB
+	{ 0x0A ,0x80 },     // R10 - VOL_HEADPHONE - -40dB
+};
+
+
+static bool tas5713_reg_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+		case TAS5713_DEVICE_ID:
+		case TAS5713_ERROR_STATUS:
+		case TAS5713_CLOCK_CTRL:
+			return true;
+	default:
+			return false;
+	}
+}
+
+
+static const struct of_device_id tas5713_of_match[] = {
+	{ .compatible = "ti,tas5713", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, tas5713_of_match);
+
+
+static struct regmap_config tas5713_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+
+	.max_register = TAS5713_MAX_REGISTER,
+	.volatile_reg = tas5713_reg_volatile,
+
+	.cache_type = REGCACHE_RBTREE,
+	.reg_defaults = tas5713_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(tas5713_reg_defaults),
+};
+
+
+static int tas5713_i2c_probe(struct i2c_client *i2c)
+{
+	int ret;
+
+	priv_data = devm_kzalloc(&i2c->dev, sizeof *priv_data, GFP_KERNEL);
+	if (!priv_data)
+		return -ENOMEM;
+
+	priv_data->regmap = devm_regmap_init_i2c(i2c, &tas5713_regmap_config);
+	if (IS_ERR(priv_data->regmap)) {
+		ret = PTR_ERR(priv_data->regmap);
+		return ret;
+	}
+
+	i2c_set_clientdata(i2c, priv_data);
+
+	ret = snd_soc_register_component(&i2c->dev,
+				     &soc_codec_dev_tas5713, &tas5713_dai, 1);
+
+	return ret;
+}
+
+
+static void tas5713_i2c_remove(struct i2c_client *i2c)
+{
+	snd_soc_unregister_component(&i2c->dev);
+	i2c_set_clientdata(i2c, NULL);
+
+	kfree(priv_data);
+}
+
+
+static const struct i2c_device_id tas5713_i2c_id[] = {
+	{ "tas5713", 0 },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(i2c, tas5713_i2c_id);
+
+
+static struct i2c_driver tas5713_i2c_driver = {
+	.driver = {
+		.name = "tas5713",
+		.owner = THIS_MODULE,
+		.of_match_table = tas5713_of_match,
+	},
+	.probe = tas5713_i2c_probe,
+	.remove = tas5713_i2c_remove,
+	.id_table = tas5713_i2c_id
+};
+
+
+static int __init tas5713_modinit(void)
+{
+	int ret = 0;
+
+	ret = i2c_add_driver(&tas5713_i2c_driver);
+	if (ret) {
+		printk(KERN_ERR "Failed to register tas5713 I2C driver: %d\n",
+		       ret);
+	}
+
+	return ret;
+}
+module_init(tas5713_modinit);
+
+
+static void __exit tas5713_exit(void)
+{
+	i2c_del_driver(&tas5713_i2c_driver);
+}
+module_exit(tas5713_exit);
+
+
+MODULE_AUTHOR("Sebastian Eickhoff <basti.eickhoff@googlemail.com>");
+MODULE_DESCRIPTION("ASoC driver for TAS5713");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/tas5713.h b/sound/soc/codecs/tas5713.h
new file mode 100644
index 000000000000..8f019e048987
--- /dev/null
+++ b/sound/soc/codecs/tas5713.h
@@ -0,0 +1,210 @@
+/*
+ * ASoC Driver for TAS5713
+ *
+ * Author:      Sebastian Eickhoff <basti.eickhoff@googlemail.com>
+ *              Copyright 2014
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#ifndef _TAS5713_H
+#define _TAS5713_H
+
+
+// TAS5713 I2C-bus register addresses
+
+#define TAS5713_CLOCK_CTRL              0x00
+#define TAS5713_DEVICE_ID               0x01
+#define TAS5713_ERROR_STATUS            0x02
+#define TAS5713_SYSTEM_CTRL1            0x03
+#define TAS5713_SERIAL_DATA_INTERFACE   0x04
+#define TAS5713_SYSTEM_CTRL2            0x05
+#define TAS5713_SOFT_MUTE               0x06
+#define TAS5713_VOL_MASTER              0x07
+#define TAS5713_VOL_CH1                 0x08
+#define TAS5713_VOL_CH2                 0x09
+#define TAS5713_VOL_HEADPHONE           0x0A
+#define TAS5713_VOL_CONFIG              0x0E
+#define TAS5713_MODULATION_LIMIT        0x10
+#define TAS5713_IC_DLY_CH1              0x11
+#define TAS5713_IC_DLY_CH2              0x12
+#define TAS5713_IC_DLY_CH3              0x13
+#define TAS5713_IC_DLY_CH4              0x14
+
+#define TAS5713_START_STOP_PERIOD       0x1A
+#define TAS5713_OSC_TRIM                0x1B
+#define TAS5713_BKND_ERR                0x1C
+
+#define TAS5713_INPUT_MUX               0x20
+#define TAS5713_SRC_SELECT_CH4          0x21
+#define TAS5713_PWM_MUX                 0x25
+
+#define TAS5713_CH1_BQ0                 0x29
+#define TAS5713_CH1_BQ1                 0x2A
+#define TAS5713_CH1_BQ2                 0x2B
+#define TAS5713_CH1_BQ3                 0x2C
+#define TAS5713_CH1_BQ4                 0x2D
+#define TAS5713_CH1_BQ5                 0x2E
+#define TAS5713_CH1_BQ6                 0x2F
+#define TAS5713_CH1_BQ7                 0x58
+#define TAS5713_CH1_BQ8                 0x59
+
+#define TAS5713_CH2_BQ0                 0x30
+#define TAS5713_CH2_BQ1                 0x31
+#define TAS5713_CH2_BQ2                 0x32
+#define TAS5713_CH2_BQ3                 0x33
+#define TAS5713_CH2_BQ4                 0x34
+#define TAS5713_CH2_BQ5                 0x35
+#define TAS5713_CH2_BQ6                 0x36
+#define TAS5713_CH2_BQ7                 0x5C
+#define TAS5713_CH2_BQ8                 0x5D
+
+#define TAS5713_CH4_BQ0                 0x5A
+#define TAS5713_CH4_BQ1                 0x5B
+#define TAS5713_CH3_BQ0                 0x5E
+#define TAS5713_CH3_BQ1                 0x5F
+
+#define TAS5713_DRC1_SOFTENING_FILTER_ALPHA_OMEGA       0x3B
+#define TAS5713_DRC1_ATTACK_RELEASE_RATE                0x3C
+#define TAS5713_DRC2_SOFTENING_FILTER_ALPHA_OMEGA       0x3E
+#define TAS5713_DRC2_ATTACK_RELEASE_RATE                0x3F
+#define TAS5713_DRC1_ATTACK_RELEASE_THRES               0x40
+#define TAS5713_DRC2_ATTACK_RELEASE_THRES               0x43
+#define TAS5713_DRC_CTRL                                0x46
+
+#define TAS5713_BANK_SW_CTRL            0x50
+#define TAS5713_CH1_OUTPUT_MIXER        0x51
+#define TAS5713_CH2_OUTPUT_MIXER        0x52
+#define TAS5713_CH1_INPUT_MIXER         0x53
+#define TAS5713_CH2_INPUT_MIXER         0x54
+#define TAS5713_OUTPUT_POST_SCALE       0x56
+#define TAS5713_OUTPUT_PRESCALE         0x57
+
+#define TAS5713_IDF_POST_SCALE          0x62
+
+#define TAS5713_CH1_INLINE_MIXER        0x70
+#define TAS5713_CH1_INLINE_DRC_EN_MIXER 0x71
+#define TAS5713_CH1_R_CHANNEL_MIXER     0x72
+#define TAS5713_CH1_L_CHANNEL_MIXER     0x73
+#define TAS5713_CH2_INLINE_MIXER        0x74
+#define TAS5713_CH2_INLINE_DRC_EN_MIXER 0x75
+#define TAS5713_CH2_L_CHANNEL_MIXER     0x76
+#define TAS5713_CH2_R_CHANNEL_MIXER     0x77
+
+#define TAS5713_UPDATE_DEV_ADDR_KEY     0xF8
+#define TAS5713_UPDATE_DEV_ADDR_REG     0xF9
+
+#define TAS5713_REGISTER_COUNT          0x46
+#define TAS5713_MAX_REGISTER            0xF9
+
+
+// Bitmasks for registers
+#define TAS5713_SOFT_MUTE_ALL           0x07
+
+
+
+struct tas5713_init_command {
+        const int size;
+        const char *const data;
+};
+
+static const struct tas5713_init_command tas5713_init_sequence[] = {
+
+        // Trim oscillator
+    { .size = 2,  .data = "\x1B\x00" },
+    // System control register 1 (0x03): block DC
+    { .size = 2,  .data = "\x03\x80" },
+    // Mute everything
+    { .size = 2,  .data = "\x05\x40" },
+    // Modulation limit register (0x10): 97.7%
+    { .size = 2,  .data = "\x10\x02" },
+    // Interchannel delay registers
+    // (0x11, 0x12, 0x13, and 0x14): BD mode
+    { .size = 2,  .data = "\x11\xB8" },
+    { .size = 2,  .data = "\x12\x60" },
+    { .size = 2,  .data = "\x13\xA0" },
+    { .size = 2,  .data = "\x14\x48" },
+    // PWM shutdown group register (0x19): no shutdown
+    { .size = 2,  .data = "\x19\x00" },
+    // Input multiplexer register (0x20): BD mode
+    { .size = 2,  .data = "\x20\x00\x89\x77\x72" },
+    // PWM output mux register (0x25)
+    // Channel 1 --> OUTA, channel 1 neg --> OUTB
+    // Channel 2 --> OUTC, channel 2 neg --> OUTD
+    { .size = 5,  .data = "\x25\x01\x02\x13\x45" },
+    // DRC control (0x46): DRC off
+    { .size = 5,  .data = "\x46\x00\x00\x00\x00" },
+    // BKND_ERR register (0x1C): 299ms reset period
+    { .size = 2,  .data = "\x1C\x07" },
+    // Mute channel 3
+    { .size = 2,  .data = "\x0A\xFF" },
+    // Volume configuration register (0x0E): volume slew 512 steps
+    { .size = 2,  .data = "\x0E\x90" },
+    // Clock control register (0x00): 44/48kHz, MCLK=64xfs
+    { .size = 2,  .data = "\x00\x60" },
+    // Bank switch and eq control (0x50): no bank switching
+    { .size = 5,  .data = "\x50\x00\x00\x00\x00" },
+    // Volume registers (0x07, 0x08, 0x09, 0x0A)
+    { .size = 2,  .data = "\x07\x20" },
+    { .size = 2,  .data = "\x08\x30" },
+    { .size = 2,  .data = "\x09\x30" },
+    { .size = 2,  .data = "\x0A\xFF" },
+    // 0x72, 0x73, 0x76, 0x77 input mixer:
+    // no intermix between channels
+    { .size = 5,  .data = "\x72\x00\x00\x00\x00" },
+    { .size = 5,  .data = "\x73\x00\x80\x00\x00" },
+    { .size = 5,  .data = "\x76\x00\x00\x00\x00" },
+    { .size = 5,  .data = "\x77\x00\x80\x00\x00" },
+    // 0x70, 0x71, 0x74, 0x75 inline DRC mixer:
+    // no inline DRC inmix
+    { .size = 5,  .data = "\x70\x00\x80\x00\x00" },
+    { .size = 5,  .data = "\x71\x00\x00\x00\x00" },
+    { .size = 5,  .data = "\x74\x00\x80\x00\x00" },
+    { .size = 5,  .data = "\x75\x00\x00\x00\x00" },
+    // 0x56, 0x57 Output scale
+    { .size = 5,  .data = "\x56\x00\x80\x00\x00" },
+    { .size = 5,  .data = "\x57\x00\x02\x00\x00" },
+    // 0x3B, 0x3c
+    { .size = 9,  .data = "\x3B\x00\x08\x00\x00\x00\x78\x00\x00" },
+    { .size = 9,  .data = "\x3C\x00\x00\x01\x00\xFF\xFF\xFF\x00" },
+    { .size = 9,  .data = "\x3E\x00\x08\x00\x00\x00\x78\x00\x00" },
+    { .size = 9,  .data = "\x3F\x00\x00\x01\x00\xFF\xFF\xFF\x00" },
+    { .size = 9,  .data = "\x40\x00\x00\x01\x00\xFF\xFF\xFF\x00" },
+    { .size = 9,  .data = "\x43\x00\x00\x01\x00\xFF\xFF\xFF\x00" },
+    // 0x51, 0x52: output mixer
+    { .size = 9,  .data = "\x51\x00\x80\x00\x00\x00\x00\x00\x00" },
+    { .size = 9,  .data = "\x52\x00\x80\x00\x00\x00\x00\x00\x00" },
+    // PEQ defaults
+    { .size = 21,  .data = "\x29\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x2A\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x2B\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x2C\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x2D\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x2E\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x2F\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x30\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x31\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x32\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x33\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x34\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x35\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x36\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x58\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x59\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x5C\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x5D\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x5E\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x5F\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x5A\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+    { .size = 21,  .data = "\x5B\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" },
+};
+
+
+#endif  /* _TAS5713_H */
diff --git a/sound/soc/dwc/dwc-i2s.c b/sound/soc/dwc/dwc-i2s.c
index 5b4f20dbf7bb..eab5c7eb98fb 100644
--- a/sound/soc/dwc/dwc-i2s.c
+++ b/sound/soc/dwc/dwc-i2s.c
@@ -209,12 +209,21 @@ static void i2s_start(struct dw_i2s_dev *dev,
 static void i2s_stop(struct dw_i2s_dev *dev,
 		struct snd_pcm_substream *substream)
 {
+	if (dev->is_jh7110) {
+		struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+		struct snd_soc_dai_link *dai_link = rtd->dai_link;
 
+		dai_link->trigger_stop = SND_SOC_TRIGGER_ORDER_LDC;
+	}
+	i2s_clear_irqs(dev, substream->stream);
+
+	i2s_disable_irqs(dev, substream->stream, 8);
+}
+
+static void i2s_pause(struct dw_i2s_dev *dev,
+		struct snd_pcm_substream *substream)
+{
 	i2s_clear_irqs(dev, substream->stream);
-	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
-		i2s_write_reg(dev->i2s_base, ITER, 0);
-	else
-		i2s_write_reg(dev->i2s_base, IRER, 0);
 
 	if (!(dev->use_pio || dev->is_jh7110))
 		i2s_disable_dma(dev, substream->stream);
@@ -224,14 +233,39 @@ static void i2s_stop(struct dw_i2s_dev *dev,
 
 	if (!dev->active) {
 		i2s_write_reg(dev->i2s_base, CER, 0);
-		i2s_write_reg(dev->i2s_base, IER, 0);
+		/* Keep the device enabled until the shutdown - do not clear IER */
 	}
 }
 
+static void dw_i2s_config(struct dw_i2s_dev *dev, int stream);
 static int dw_i2s_startup(struct snd_pcm_substream *substream,
 			  struct snd_soc_dai *cpu_dai)
 {
 	struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+	union dw_i2s_snd_dma_data *dma_data = NULL;
+	u32 dmacr;
+
+	dev_dbg(dev->dev, "%s(%s)\n", __func__, substream->name);
+	if (!(dev->capability & DWC_I2S_RECORD) &&
+	    substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+		return -EINVAL;
+
+	if (!(dev->capability & DWC_I2S_PLAY) &&
+	    substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		return -EINVAL;
+
+	dw_i2s_config(dev, substream->stream);
+	dmacr = i2s_read_reg(dev->i2s_base, I2S_DMACR);
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		dma_data = &dev->play_dma_data;
+		dmacr |= DMACR_DMAEN_TX;
+	} else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		dma_data = &dev->capture_dma_data;
+		dmacr |= DMACR_DMAEN_RX;
+	}
+
+	snd_soc_dai_set_dma_data(cpu_dai, substream, (void *)dma_data);
+	i2s_write_reg(dev->i2s_base, I2S_DMACR, dmacr);
 
 	if (dev->is_jh7110) {
 		struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
@@ -243,22 +277,52 @@ static int dw_i2s_startup(struct snd_pcm_substream *substream,
 	return 0;
 }
 
+static void dw_i2s_shutdown(struct snd_pcm_substream *substream,
+			    struct snd_soc_dai *dai)
+{
+	struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+	dev_dbg(dev->dev, "%s(%s)\n", __func__, substream->name);
+	i2s_disable_channels(dev, substream->stream);
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		i2s_write_reg(dev->i2s_base, ITER, 0);
+	else
+		i2s_write_reg(dev->i2s_base, IRER, 0);
+
+	i2s_disable_irqs(dev, substream->stream, 8);
+
+	if (!dev->active) {
+		i2s_write_reg(dev->i2s_base, CER, 0);
+		i2s_write_reg(dev->i2s_base, IER, 0);
+	}
+}
+
 static void dw_i2s_config(struct dw_i2s_dev *dev, int stream)
 {
 	u32 ch_reg;
 	struct i2s_clk_config_data *config = &dev->config;
-
+	u32 dmacr;
+	u32 comp1 = i2s_read_reg(dev->i2s_base, dev->i2s_reg_comp1);
+	u32 fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1));
 
 	i2s_disable_channels(dev, stream);
 
+	dmacr = i2s_read_reg(dev->i2s_base, I2S_DMACR);
+
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dmacr &= ~(DMACR_DMAEN_TXCH0 * 0xf);
+	else
+		dmacr &= ~(DMACR_DMAEN_RXCH0 * 0xf);
+
 	for (ch_reg = 0; ch_reg < (config->chan_nr / 2); ch_reg++) {
 		if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
 			i2s_write_reg(dev->i2s_base, TCR(ch_reg),
 				      dev->xfer_resolution);
 			i2s_write_reg(dev->i2s_base, TFCR(ch_reg),
-				      dev->fifo_th - 1);
+				      fifo_depth - dev->fifo_th - 1);
 			i2s_write_reg(dev->i2s_base, TER(ch_reg), TER_TXCHEN |
 				      dev->tdm_mask << TER_TXSLOT_SHIFT);
+			dmacr |= (DMACR_DMAEN_TXCH0 << ch_reg);
 		} else {
 			i2s_write_reg(dev->i2s_base, RCR(ch_reg),
 				      dev->xfer_resolution);
@@ -266,9 +330,11 @@ static void dw_i2s_config(struct dw_i2s_dev *dev, int stream)
 				      dev->fifo_th - 1);
 			i2s_write_reg(dev->i2s_base, RER(ch_reg), RER_RXCHEN |
 				      dev->tdm_mask << RER_RXSLOT_SHIFT);
+			dmacr |= (DMACR_DMAEN_RXCH0 << ch_reg);
 		}
-
 	}
+
+	i2s_write_reg(dev->i2s_base, I2S_DMACR, dmacr);
 }
 
 static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
@@ -276,24 +342,32 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
 {
 	struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
 	struct i2s_clk_config_data *config = &dev->config;
+	union dw_i2s_snd_dma_data *dma_data = NULL;
 	int ret;
 
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		dma_data = &dev->play_dma_data;
+	else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+		dma_data = &dev->capture_dma_data;
+	else
+		return -1;
+
 	switch (params_format(params)) {
 	case SNDRV_PCM_FORMAT_S16_LE:
 		config->data_width = 16;
-		dev->ccr = 0x00;
+		dma_data->dt.addr_width = 2;
 		dev->xfer_resolution = 0x02;
 		break;
 
 	case SNDRV_PCM_FORMAT_S24_LE:
 		config->data_width = 24;
-		dev->ccr = 0x08;
+		dma_data->dt.addr_width = 4;
 		dev->xfer_resolution = 0x04;
 		break;
 
 	case SNDRV_PCM_FORMAT_S32_LE:
 		config->data_width = 32;
-		dev->ccr = 0x10;
+		dma_data->dt.addr_width = 4;
 		dev->xfer_resolution = 0x05;
 		break;
 
@@ -314,17 +388,37 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
 	case TWO_CHANNEL_SUPPORT:
 		break;
 	default:
-		dev_err(dev->dev, "channel not supported\n");
+		dev_err(dev->dev, "channel count %d not supported\n", config->chan_nr);
 		return -EINVAL;
 	}
 
 	dw_i2s_config(dev, substream->stream);
 
-	i2s_write_reg(dev->i2s_base, CCR, dev->ccr);
-
 	config->sample_rate = params_rate(params);
 
 	if (dev->capability & DW_I2S_MASTER) {
+		u32 frame_length = config->data_width * 2;
+
+		if (dev->bclk_ratio)
+			frame_length = dev->bclk_ratio;
+
+		switch (frame_length) {
+		case 32:
+			dev->ccr = 0x00;
+			break;
+
+		case 48:
+			dev->ccr = 0x08;
+			break;
+
+		case 64:
+			dev->ccr = 0x10;
+			break;
+
+		default:
+			return -EINVAL;
+		}
+
 		if (dev->i2s_clk_cfg) {
 			ret = dev->i2s_clk_cfg(config);
 			if (ret < 0) {
@@ -332,8 +426,7 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
 				return ret;
 			}
 		} else {
-			u32 bitclk = config->sample_rate *
-					config->data_width * 2;
+			u32 bitclk = config->sample_rate * frame_length;
 
 			ret = clk_set_rate(dev->clk, bitclk);
 			if (ret) {
@@ -342,6 +435,8 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
 				return ret;
 			}
 		}
+
+		i2s_write_reg(dev->i2s_base, CCR, dev->ccr);
 	}
 	return 0;
 }
@@ -373,9 +468,12 @@ static int dw_i2s_trigger(struct snd_pcm_substream *substream,
 		i2s_start(dev, substream);
 		break;
 
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		dev->active--;
+		i2s_pause(dev, substream);
+		break;
 	case SNDRV_PCM_TRIGGER_STOP:
 	case SNDRV_PCM_TRIGGER_SUSPEND:
-	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
 		dev->active--;
 		i2s_stop(dev, substream);
 		break;
@@ -459,6 +557,18 @@ static int dw_i2s_set_tdm_slot(struct snd_soc_dai *cpu_dai,	unsigned int tx_mask
 	return 0;
 }
 
+static int dw_i2s_set_bclk_ratio(struct snd_soc_dai *cpu_dai,
+				 unsigned int ratio)
+{
+	struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(cpu_dai);
+
+	dev_dbg(dev->dev, "%s(%d)\n", __func__, ratio);
+
+	dev->bclk_ratio = ratio;
+
+	return 0;
+}
+
 static int dw_i2s_dai_probe(struct snd_soc_dai *dai)
 {
 	struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
@@ -470,11 +580,13 @@ static int dw_i2s_dai_probe(struct snd_soc_dai *dai)
 static const struct snd_soc_dai_ops dw_i2s_dai_ops = {
 	.probe		= dw_i2s_dai_probe,
 	.startup	= dw_i2s_startup,
+	.shutdown	= dw_i2s_shutdown,
 	.hw_params	= dw_i2s_hw_params,
 	.prepare	= dw_i2s_prepare,
 	.trigger	= dw_i2s_trigger,
 	.set_fmt	= dw_i2s_set_fmt,
 	.set_tdm_slot	= dw_i2s_set_tdm_slot,
+	.set_bclk_ratio	= dw_i2s_set_bclk_ratio,
 };
 
 #ifdef CONFIG_PM
@@ -605,7 +717,7 @@ static int dw_configure_dai(struct dw_i2s_dev *dev,
 			idx = 1;
 		dw_i2s_dai->playback.channels_min = MIN_CHANNEL_NUM;
 		dw_i2s_dai->playback.channels_max =
-				1 << (COMP1_TX_CHANNELS(comp1) + 1);
+				2 * (COMP1_TX_CHANNELS(comp1) + 1);
 		dw_i2s_dai->playback.formats = formats[idx];
 		dw_i2s_dai->playback.rates = rates;
 	}
@@ -619,7 +731,7 @@ static int dw_configure_dai(struct dw_i2s_dev *dev,
 			idx = 1;
 		dw_i2s_dai->capture.channels_min = MIN_CHANNEL_NUM;
 		dw_i2s_dai->capture.channels_max =
-				1 << (COMP1_RX_CHANNELS(comp1) + 1);
+				2 * (COMP1_RX_CHANNELS(comp1) + 1);
 		dw_i2s_dai->capture.formats = formats[idx];
 		dw_i2s_dai->capture.rates = rates;
 	}
@@ -680,8 +792,8 @@ static int dw_configure_dai_by_pd(struct dw_i2s_dev *dev,
 		dev->capture_dma_data.pd.data = pdata->capture_dma_data;
 		dev->play_dma_data.pd.addr = res->start + I2S_TXDMA;
 		dev->capture_dma_data.pd.addr = res->start + I2S_RXDMA;
-		dev->play_dma_data.pd.max_burst = 16;
-		dev->capture_dma_data.pd.max_burst = 16;
+		dev->play_dma_data.pd.max_burst = dev->fifo_th;
+		dev->capture_dma_data.pd.max_burst = dev->fifo_th;
 		dev->play_dma_data.pd.addr_width = bus_widths[idx];
 		dev->capture_dma_data.pd.addr_width = bus_widths[idx];
 		dev->play_dma_data.pd.filter = pdata->filter;
@@ -701,7 +813,7 @@ static int dw_configure_dai_by_dt(struct dw_i2s_dev *dev,
 	u32 idx2;
 	int ret;
 
-	ret = dw_configure_dai(dev, dw_i2s_dai, SNDRV_PCM_RATE_8000_192000);
+	ret = dw_configure_dai(dev, dw_i2s_dai, SNDRV_PCM_RATE_8000_384000);
 	if (ret < 0)
 		return ret;
 
@@ -712,7 +824,10 @@ static int dw_configure_dai_by_dt(struct dw_i2s_dev *dev,
 		dev->play_dma_data.dt.addr = res->start + I2S_TXDMA;
 		dev->play_dma_data.dt.fifo_size = fifo_depth *
 			(fifo_width[idx2]) >> 8;
-		dev->play_dma_data.dt.maxburst = 16;
+		if (dev->max_dma_burst)
+			dev->play_dma_data.dt.maxburst = dev->max_dma_burst;
+		else
+			dev->play_dma_data.dt.maxburst = fifo_depth / 2;
 	}
 	if (COMP1_RX_ENABLED(comp1)) {
 		idx2 = COMP2_RX_WORDSIZE_0(comp2);
@@ -721,9 +836,14 @@ static int dw_configure_dai_by_dt(struct dw_i2s_dev *dev,
 		dev->capture_dma_data.dt.addr = res->start + I2S_RXDMA;
 		dev->capture_dma_data.dt.fifo_size = fifo_depth *
 			(fifo_width[idx2] >> 8);
-		dev->capture_dma_data.dt.maxburst = 16;
+		if (dev->max_dma_burst)
+			dev->capture_dma_data.dt.maxburst = dev->max_dma_burst;
+		else
+			dev->capture_dma_data.dt.maxburst = fifo_depth / 2;
 	}
 
+	if (dev->max_dma_burst)
+		dev->fifo_th = min(dev->max_dma_burst, dev->fifo_th);
 	return 0;
 
 }
@@ -967,6 +1087,8 @@ static int dw_i2s_probe(struct platform_device *pdev)
 		}
 	}
 
+	of_property_read_u32(pdev->dev.of_node, "dma-maxburst", &dev->max_dma_burst);
+	dev->bclk_ratio = 0;
 	dev->i2s_reg_comp1 = I2S_COMP_PARAM_1;
 	dev->i2s_reg_comp2 = I2S_COMP_PARAM_2;
 	if (pdata) {
diff --git a/sound/soc/dwc/local.h b/sound/soc/dwc/local.h
index dce88c9ad5f3..afff89db5dbb 100644
--- a/sound/soc/dwc/local.h
+++ b/sound/soc/dwc/local.h
@@ -63,6 +63,17 @@
 #define TER_TXSLOT_SHIFT	8
 #define TER_TXCHEN	BIT(0)
 
+#define DMACR_DMAEN_TX		BIT(17)
+#define DMACR_DMAEN_RX		BIT(16)
+#define DMACR_DMAEN_TXCH3	BIT(11)
+#define DMACR_DMAEN_TXCH2	BIT(10)
+#define DMACR_DMAEN_TXCH1	BIT(9)
+#define DMACR_DMAEN_TXCH0	BIT(8)
+#define DMACR_DMAEN_RXCH3	BIT(3)
+#define DMACR_DMAEN_RXCH2	BIT(2)
+#define DMACR_DMAEN_RXCH1	BIT(1)
+#define DMACR_DMAEN_RXCH0	BIT(0)
+
 /* I2SCOMPRegisters */
 #define I2S_COMP_PARAM_2	0x01F0
 #define I2S_COMP_PARAM_1	0x01F4
@@ -117,10 +128,12 @@ struct dw_i2s_dev {
 	unsigned int quirks;
 	unsigned int i2s_reg_comp1;
 	unsigned int i2s_reg_comp2;
+	unsigned int bclk_ratio;
 	struct device *dev;
 	u32 ccr;
 	u32 xfer_resolution;
 	u32 fifo_th;
+	u32 max_dma_burst;
 	u32 l_reg;
 	u32 r_reg;
 	bool is_jh7110; /* Flag for StarFive JH7110 SoC */
diff --git a/sound/soc/raspberrypi/Kconfig b/sound/soc/raspberrypi/Kconfig
new file mode 100644
index 000000000000..389bb82e3651
--- /dev/null
+++ b/sound/soc/raspberrypi/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config SND_RP1_AUDIO_OUT
+	tristate "PWM Audio Out from RP1"
+	select SND_SOC_GENERIC_DMAENGINE_PCM
+	select SND_SOC_SPDIF
+	help
+	 Say Y or M if you want to add support for PWM digital
+	 audio output from a Raspberry Pi 5, 500 or CM5.
+
+	 Output is from RP1 GPIOs pins 12 and 13 only, and additional
+	 components will be needed. It may be useful when HDMI, I2S
+	 or USB audio devices are unavailable, or for compatibility.
diff --git a/sound/soc/raspberrypi/Makefile b/sound/soc/raspberrypi/Makefile
new file mode 100644
index 000000000000..80ff460c785a
--- /dev/null
+++ b/sound/soc/raspberrypi/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_SND_RP1_AUDIO_OUT) += rp1_aout.o
diff --git a/sound/soc/raspberrypi/rp1_aout.c b/sound/soc/raspberrypi/rp1_aout.c
new file mode 100644
index 000000000000..ffcc7b815d5e
--- /dev/null
+++ b/sound/soc/raspberrypi/rp1_aout.c
@@ -0,0 +1,372 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Raspberry Pi Ltd.
+ *
+ * rp1_aout.c -- Driver for Raspberry Pi RP1 Audio Out block.
+ * This is a modified 2-channel PWM with hardware 40 times oversampling,
+ * 2nd order noise shaping and dither. Only output (playback) is supported.
+ *
+ * For ASOC, this is implemented as a "DAI" and will need to be linked to a
+ * dummy codec such as "linux,spdif-dit" and card such as "simple-audio-card".
+ *
+ * Driver/file structure derived in part from "soc/starfive/jh7110_pwmdac.c"
+ * and other example drivers.
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#define AUDIO_OUT_CTRL                      0x0000
+#define AUDIO_OUT_CTRL_PERIPH_EN            BIT(31)
+#define AUDIO_OUT_CTRL_CIC_RATE             GENMASK(11, 8)
+#define AUDIO_OUT_CTRL_CHANNEL_SWAP         BIT(2)
+#define AUDIO_OUT_CTRL_RIGHT_CH_ENABLE      BIT(1)
+#define AUDIO_OUT_CTRL_LEFT_CH_ENABLE       BIT(0)
+
+#define AUDIO_OUT_SDMCTL_LEFT               0x0004
+#define AUDIO_OUT_SDMCTL_RIGHT              0x0008
+#define AUDIO_OUT_SDMCTL_LR_BIAS            GENMASK(31, 16)
+#define AUDIO_OUT_SDMCTL_LR_BYPASS          BIT(7)
+#define AUDIO_OUT_SDMCTL_LR_SDM_ORDER       BIT(6)
+#define AUDIO_OUT_SDMCTL_LR_CLAMP_EN        BIT(5)
+#define AUDIO_OUT_SDMCTL_LR_DITHER_EN       BIT(4)
+#define AUDIO_OUT_SDMCTL_LR_OUTPUT_BITWIDTH GENMASK(3, 0)
+
+#define AUDIO_OUT_QCLAMP_LEFT               0x000c
+#define AUDIO_OUT_QCLAMP_RIGHT              0x0010
+#define AUDIO_OUT_QCLAMP_LR_MAX             GENMASK(31, 16)
+#define AUDIO_OUT_QCLAMP_LR_MIN             GENMASK(15, 0)
+
+#define AUDIO_OUT_MUTE_CTRL_LEFT            0x0014
+#define AUDIO_OUT_MUTE_CTRL_RIGHT           0x0018
+#define AUDIO_OUT_MUTE_CTRL_LR_BYPASS       BIT(31)
+#define AUDIO_OUT_MUTE_CTRL_LR_MUTE_PERIOD  GENMASK(23, 16)
+#define AUDIO_OUT_MUTE_CTRL_LR_STEP_SIZE    GENMASK(15, 8)
+#define AUDIO_OUT_MUTE_CTRL_LR_INIT_UNMUTE  BIT(5)
+#define AUDIO_OUT_MUTE_CTRL_LR_INIT_MUTE    BIT(4)
+#define AUDIO_OUT_MUTE_CTRL_LR_MUTE_FSM     GENMASK(3, 0)
+
+#define AUDIO_OUT_PWMCTL_LEFT               0x001c
+#define AUDIO_OUT_PWMRANGE_LEFT             0x0020
+#define AUDIO_OUT_PWMCTL_RIGHT              0x0028
+#define AUDIO_OUT_PWMRANGE_RIGHT            0x002c
+
+#define AUDIO_OUT_FIFO_CONTROL              0x0034
+#define AUDIO_OUT_FIFO_CONTROL_DMA_DREQ_EN  BIT(31)
+#define AUDIO_OUT_FIFO_CONTROL_FLUSH_DONE   BIT(25)
+#define AUDIO_OUT_FIFO_CONTROL_FIFO_FLUSH   BIT(24)
+#define AUDIO_OUT_FIFO_CONTROL_DWELL_TIME   GENMASK(20, 16)
+#define AUDIO_OUT_FIFO_CONTROL_THRESHOLD    GENMASK(5, 0)
+
+#define AUDIO_OUT_SAMPLE_FIFO               0x0038
+
+struct rp1_aout {
+	void __iomem *regs;
+	phys_addr_t physaddr;
+	struct clk *clk;
+	struct device *dev;
+	struct snd_dmaengine_dai_dma_data play_dma_data;
+	bool initted;
+	bool swap_lr;
+};
+
+static inline void aout_reg_wr(struct rp1_aout *ao, unsigned int offset, u32 val)
+{
+	void __iomem *addr = ao->regs + offset;
+
+	writel(val, addr);
+}
+
+static inline u32 aout_reg_rd(struct rp1_aout *ao, unsigned int offset)
+{
+	void __iomem *addr = ao->regs + offset;
+
+	return readl(addr);
+}
+
+static void audio_init(struct rp1_aout *aout)
+{
+	u32 val;
+
+	/*
+	 * The hardware was tuned to play at 48 kHz with 40 times oversampling and
+	 * 40-level two-sided PWM, for a clock rate of 48000 * 40 * 80 = 153.6 MHz.
+	 * Changing these settings is not recommended. At those rates, the filter
+	 * leaves ~2.2 dB headroom, so Qclamp will clip just slightly below FSD.
+	 */
+
+	/* Clamp to +/- (32767 * 40 / 64) before quantization */
+	val = FIELD_PREP_CONST(AUDIO_OUT_QCLAMP_LR_MAX, 20479) |
+		FIELD_PREP_CONST(AUDIO_OUT_QCLAMP_LR_MIN, (u16)(-20479));
+	aout_reg_wr(aout, AUDIO_OUT_QCLAMP_LEFT, val);
+	aout_reg_wr(aout, AUDIO_OUT_QCLAMP_RIGHT, val);
+	aout_reg_wr(aout, AUDIO_OUT_PWMCTL_LEFT, 0);
+	aout_reg_wr(aout, AUDIO_OUT_PWMCTL_RIGHT, 0);
+
+	/* Range = 39 */
+	aout_reg_wr(aout, AUDIO_OUT_PWMRANGE_LEFT, 0x27);
+	aout_reg_wr(aout, AUDIO_OUT_PWMRANGE_RIGHT, 0x27);
+
+	/* bias = 20 (half FSD). Quantize to 5+1 bits */
+	val = FIELD_PREP_CONST(AUDIO_OUT_SDMCTL_LR_BIAS, 0x14) |
+		AUDIO_OUT_SDMCTL_LR_CLAMP_EN |
+		AUDIO_OUT_SDMCTL_LR_DITHER_EN |
+		FIELD_PREP_CONST(AUDIO_OUT_SDMCTL_LR_OUTPUT_BITWIDTH, 5);
+	aout_reg_wr(aout, AUDIO_OUT_SDMCTL_LEFT, val);
+	aout_reg_wr(aout, AUDIO_OUT_SDMCTL_RIGHT, val);
+
+	/* ~300ms ramp = 12k*40 samples to FSD/2 => step size 1, interval 13 */
+	val = FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_STEP_SIZE, 1) |
+		FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_MUTE_PERIOD, 13);
+	aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_LEFT, val);
+	aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_RIGHT, val);
+
+	/* Configure DMA flow control with threshold at half FIFO depth */
+	val = FIELD_PREP_CONST(AUDIO_OUT_FIFO_CONTROL_DWELL_TIME, 2) |
+		FIELD_PREP_CONST(AUDIO_OUT_FIFO_CONTROL_THRESHOLD, 0x10) |
+		AUDIO_OUT_FIFO_CONTROL_DMA_DREQ_EN;
+	aout_reg_wr(aout, AUDIO_OUT_FIFO_CONTROL, val);
+}
+
+static void audio_startup(struct rp1_aout *aout)
+{
+	u32 val;
+
+	/* CIC rate 10, for an overall upsampling ratio of 40 */
+	val =  FIELD_PREP_CONST(AUDIO_OUT_CTRL_CIC_RATE, 0xa) |
+		AUDIO_OUT_CTRL_LEFT_CH_ENABLE | AUDIO_OUT_CTRL_RIGHT_CH_ENABLE;
+	if (aout->swap_lr)
+		val |= AUDIO_OUT_CTRL_CHANNEL_SWAP;
+	aout_reg_wr(aout, AUDIO_OUT_CTRL, val);
+	aout_reg_rd(aout, AUDIO_OUT_CTRL); /* synchronization delay */
+
+	/* Press the "go" button */
+	val |= AUDIO_OUT_CTRL_PERIPH_EN;
+	aout_reg_wr(aout, AUDIO_OUT_CTRL, val);
+	aout_reg_rd(aout, AUDIO_OUT_CTRL); /* FIFO reset release delay */
+
+	/* Poke zeroes in to avoid undefined values on underrun */
+	aout_reg_wr(aout, AUDIO_OUT_SAMPLE_FIFO, 0);
+}
+
+static void audio_muting(struct rp1_aout *aout, u32 flag)
+{
+	u32 val = FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_STEP_SIZE, 1) |
+		FIELD_PREP_CONST(AUDIO_OUT_MUTE_CTRL_LR_MUTE_PERIOD, 13);
+	aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_LEFT, val);
+	aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_RIGHT, val);
+
+	val |= flag;
+	aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_LEFT, val);
+	aout_reg_wr(aout, AUDIO_OUT_MUTE_CTRL_RIGHT, val);
+	aout_reg_rd(aout, AUDIO_OUT_MUTE_CTRL_RIGHT); /* synchronization delay */
+}
+
+static void audio_mute_sync(struct rp1_aout *aout)
+{
+	static const u32 mask = 0x1 | 0x4; /* transitional states */
+	unsigned int count;
+
+	for (count = 0; count < 500; count++) {
+		if ((aout_reg_rd(aout, AUDIO_OUT_MUTE_CTRL_LEFT) & mask) == 0)
+			break;
+		usleep_range(1000, 5000);
+	}
+	for (; count < 500; count++) {
+		if ((aout_reg_rd(aout, AUDIO_OUT_MUTE_CTRL_RIGHT) & mask) == 0)
+			break;
+		usleep_range(1000, 5000);
+	}
+}
+
+/* Device DAI interface */
+
+static int rp1_aout_startup(struct snd_pcm_substream *substream,
+			    struct snd_soc_dai *dai)
+{
+	struct rp1_aout *aout = dev_get_drvdata(dai->dev);
+	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+	struct snd_soc_dai_link *dai_link = rtd->dai_link;
+
+	dai_link->trigger_stop = SND_SOC_TRIGGER_ORDER_LDC;
+
+	if (!aout->initted) {
+		dev_info(dai->dev, "RP1 Audio Out start\n");
+		audio_init(aout);
+		audio_startup(aout);
+		audio_muting(aout, AUDIO_OUT_MUTE_CTRL_LR_INIT_UNMUTE);
+		aout->initted = true;
+	}
+
+	return 0;
+}
+
+static int rp1_aout_hw_params(struct snd_pcm_substream *substream,
+			      struct snd_pcm_hw_params *params,
+			      struct snd_soc_dai *dai)
+{
+	struct rp1_aout *aout = dev_get_drvdata(dai->dev);
+
+	/* We only support this one configuration */
+	if (params_rate(params) != 48000 || params_channels(params) != 2 ||
+	    params_format(params) != SNDRV_PCM_FORMAT_S16_LE) {
+		dev_err(dai->dev, "Invalid HW params\n");
+		return -EINVAL;
+	}
+
+	audio_mute_sync(aout);
+
+	return 0;
+}
+
+static int rp1_aout_trigger(struct snd_pcm_substream *substream, int cmd,
+			    struct snd_soc_dai *dai)
+{
+	struct rp1_aout *aout = snd_soc_dai_get_drvdata(dai);
+
+	if (cmd == SNDRV_PCM_TRIGGER_STOP ||
+	    cmd == SNDRV_PCM_TRIGGER_SUSPEND ||
+	    cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH) {
+		/* Push a zero sample (assuming DMA has stopped already) */
+		aout_reg_wr(aout, AUDIO_OUT_SAMPLE_FIFO, 0);
+	}
+
+	return 0;
+}
+
+static int rp1_aout_dai_probe(struct snd_soc_dai *dai)
+{
+	struct rp1_aout *aout = dev_get_drvdata(dai->dev);
+
+	snd_soc_dai_init_dma_data(dai, &aout->play_dma_data, NULL);
+	snd_soc_dai_set_drvdata(dai, aout);
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops rp1_aout_dai_ops = {
+	.probe		= rp1_aout_dai_probe,
+	.startup	= rp1_aout_startup,
+	.hw_params	= rp1_aout_hw_params,
+	.trigger	= rp1_aout_trigger,
+};
+
+static const struct snd_soc_component_driver rp1_aout_component = {
+	.name		= "rp1-aout",
+};
+
+static struct snd_soc_dai_driver rp1_aout_dai = {
+	.name		= "rp1-aout",
+	.id		= 0,
+	.playback = {
+		.channels_min = 2,
+		.channels_max = 2,
+		.rates = SNDRV_PCM_RATE_48000,
+		.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	},
+	.ops = &rp1_aout_dai_ops,
+};
+
+static int rp1_aout_platform_probe(struct platform_device *pdev)
+{
+	int ret;
+	struct rp1_aout *aout;
+	struct resource *ioresource;
+
+	dev_info(&pdev->dev, "rp1_aout_platform_probe");
+	aout = devm_kzalloc(&pdev->dev, sizeof(*aout), GFP_KERNEL);
+	if (!aout)
+		return -ENOMEM;
+
+	aout->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(aout->clk))
+		return dev_err_probe(&pdev->dev, PTR_ERR(aout->clk),
+				     "could not get clk\n");
+
+	aout->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &ioresource);
+	if (IS_ERR(aout->regs))
+		return dev_err_probe(&pdev->dev, PTR_ERR(aout->regs),
+				     "could not map registers\n");
+	aout->physaddr = ioresource->start;
+	aout->swap_lr = of_property_read_bool(pdev->dev.of_node, "swap_lr");
+
+	/* Initialize playback DMA parameters */
+	aout->play_dma_data.addr = aout->physaddr + AUDIO_OUT_SAMPLE_FIFO;
+	aout->play_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+	aout->play_dma_data.fifo_size = 16; /* actually 32 but the threshold is 16(?) */
+	aout->play_dma_data.maxburst = 4;
+
+	clk_prepare_enable(aout->clk);
+	aout->dev = &pdev->dev;
+	platform_set_drvdata(pdev, aout);
+
+	ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret, "failed to register pcm\n");
+
+	/* Finally, register the component so simple-audio-card can detect it */
+	ret = devm_snd_soc_register_component(&pdev->dev,
+					      &rp1_aout_component,
+					      &rp1_aout_dai, 1);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret, "failed to register dai\n");
+
+	return 0;
+}
+
+static void rp1_aout_platform_remove(struct platform_device *pdev)
+{
+	struct rp1_aout *aout = platform_get_drvdata(pdev);
+
+	if (aout) {
+		/*
+		 * We leave the PWM carrier/bias running between playbacks,
+		 * but mute it just before shutting down, to avoid a click
+		 * (devm should clear up everything else).
+		 */
+		if (aout->initted) {
+			audio_mute_sync(aout);
+			audio_muting(aout,
+				     AUDIO_OUT_MUTE_CTRL_LR_INIT_MUTE);
+			audio_mute_sync(aout);
+			aout->initted = false;
+		}
+		clk_disable_unprepare(aout->clk);
+	}
+}
+
+static const struct of_device_id rp1_aout_of_match[] = {
+	{ .compatible = "raspberrypi,rp1-audio-out", },
+	{ /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, rp1_aout_of_match);
+
+static struct platform_driver rp1_audio_out_driver = {
+	.probe  = rp1_aout_platform_probe,
+	.remove = rp1_aout_platform_remove,
+	.shutdown = rp1_aout_platform_remove,
+	.driver = {
+		.name = "rp1-audio-out",
+		.of_match_table = rp1_aout_of_match,
+	},
+};
+
+module_platform_driver(rp1_audio_out_driver);
+
+MODULE_DESCRIPTION("RP1 Audio out");
+MODULE_AUTHOR("Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>");
+MODULE_AUTHOR("Jonathan Bell <jonathan@raspberrypi.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index a1e382991426..b6aff1ad0203 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -1436,7 +1436,15 @@ int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd,
 		return 0;
 
 	for_each_rtd_codec_dais(rtd, i, codec_dai) {
-		ret = snd_soc_dai_set_fmt(codec_dai, dai_fmt);
+		unsigned int codec_dai_fmt = dai_fmt;
+
+		// there can only be one master when using multiple codecs
+		if (i && (codec_dai_fmt & SND_SOC_DAIFMT_MASTER_MASK)) {
+			codec_dai_fmt &= ~SND_SOC_DAIFMT_MASTER_MASK;
+			codec_dai_fmt |= SND_SOC_DAIFMT_CBS_CFS;
+		}
+
+		ret = snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt);
 		if (ret != 0 && ret != -ENOTSUPP)
 			return ret;
 	}
diff --git a/sound/usb/card.c b/sound/usb/card.c
index d0a42859208a..15ab289ebebc 100644
--- a/sound/usb/card.c
+++ b/sound/usb/card.c
@@ -875,8 +875,14 @@ static int usb_audio_probe(struct usb_interface *intf,
 	if (ignore_ctl_error)
 		chip->quirk_flags |= QUIRK_FLAG_IGNORE_CTL_ERROR;
 
-	if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND)
+	if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND) {
+		/*
+		* Grab the interface, because on a webcam uvcvideo may race
+		* with snd-usb-audio during probe and re-enable autosuspend.
+		*/
+		usb_autopm_get_interface(intf);
 		usb_disable_autosuspend(interface_to_usbdev(intf));
+	}
 
 	/*
 	 * For devices with more than one control interface, we assume the
diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c
index 5c3a97ea46e0..46b438495ae5 100644
--- a/sound/usb/quirks.c
+++ b/sound/usb/quirks.c
@@ -2397,6 +2397,8 @@ static const struct usb_audio_quirk_flags_table quirk_flags_table[] = {
 		   QUIRK_FLAG_ALIGN_TRANSFER),
 	DEVICE_FLG(0x534d, 0x2109, /* MacroSilicon MS2109 */
 		   QUIRK_FLAG_ALIGN_TRANSFER),
+	DEVICE_FLG(0x09da, 0x2695, /* A4Tech FHD 1080p webcam */
+		   QUIRK_FLAG_DISABLE_AUTOSUSPEND | QUIRK_FLAG_GET_SAMPLE_RATE),
 
 	/* Vendor matches */
 	VENDOR_FLG(0x045e, /* MS Lifecam */
